当前位置: 首页 > news >正文

mit6824-02-Lab1:MapReduce分布式实现

文章目录

  • 写在前面
  • 总体思路分析
  • 代码实现
  • 参考链接

写在前面

具体上次写6824的第一篇文章已经过去了快一个月,上次学习了MapReduce论文相关理论后一直没有继续写代码实现,自己一边要搞论文没有整块时间实现,这两天抽写了相关代码,算是对Lab1的一个交代。
我写的大部分代码都是参考别人的已经实现的,我写这篇博客的目的也并不是为了传播高深的技术,我只是记录一下自己从代码开始时的一头雾水到参考别人代码实现了感叹Lab1的伟大之处。方便后续自己回顾。

看到别人博客提及,每个hits都有用,自己在没有写代码时并不能完全理解官方页面提到的Hits的具体实现方式。我觉得最重要的自己把这些实现都搞懂了原理,随后自己再去回顾这些提示感觉写的很秒。

总体思路分析

MapReduce论文理解:
https://blog.csdn.net/weixin_45863010/article/details/142641061
官方Lab1链接:
https://pdos.csail.mit.edu/6.824/labs/lab-mr.html
在这里插入图片描述

上面图片简单概述:首先协调者Coordinator启动,并且注册RPC调用通道,随后Woker使用RPC调用去Coordinator获取任务。

  • Worker根本不区分是否是Map-Worker还是ReduceWorker。
  • Worker充当Map还是Reduce的角色是根据任务类型来划分的,如果是Map任务就执行Map任务,如果是Reduce任务就执行Reduce任务。「我在这里没有使用不是Map任务就执行Reduce任务,具体原因可以参考下面代码实现
  • Coordinator启动后首先初始化所有Map任务,当且只有所有Map任务全部执行完成,才能进行下一阶段Reduce,才能够初始化Reduce任务给Worker执行。「Reduce的执行依赖与Worker执行产生的中间文件,会将中间文件根据对应的reduceNum分配给一个Worker执行
  • 对于GFS或者本地磁盘存储Map产生的中间文件非常重要,MapWorker执行任务后会将任务执行结果存储到上述文件系统中,随后Coordinator根据ReduceNum分配需要执行的任务到ReduceWorker。在ReduceWorker获取到所有文件内容后,首先会排序。

我根据我写代码的理解画一个流程图如下:
在这里插入图片描述

代码实现

下面我将给出自己参考别人的代码实现,主要涉及到三个文件,我直接贴出对应的三个文件,代码里面附有详细的注释:

rpc.go

package mr//
// RPC definitions.
//
// remember to capitalize all names.
//import "os"
import "strconv"//
// example to show how to declare the arguments
// and reply for an RPC.
//// 定义任务结构体
type TaskRequest struct{}type TaskType inttype Task struct {TaskType  TaskTypeTaskId    intFileSlice []stringReduceNum int
}// 定义各种任务类型
// WaitingTask是否是必要的:是必要的,解决当Map任务没有全部完成成,此时调用GetTask返回WaitingTask,
// Worker在接受到这个任务状态后进行短暂的休眠,随后继续调用任务执行后续流程
const (MapTask TaskType = iotaReduceTaskWaitingTaskExitTask
)type ExampleArgs struct {X int
}type ExampleReply struct {Y int
}// Add your RPC definitions here.// Cook up a unique-ish UNIX-domain socket name
// in /var/tmp, for the coordinator.
// Can't use the current directory since
// Athena AFS doesn't support UNIX-domain sockets.
func coordinatorSock() string {s := "/var/tmp/5840-mr-"s += strconv.Itoa(os.Getuid())return s
}

coordinator.go

package mrimport ("fmt""io/ioutil""log""strconv""strings""sync""time"
)
import "net"
import "os"
import "net/rpc"
import "net/http"// Phase 定义当前总体进度,分为三个阶段:Map阶段、Reduce阶段、Done阶段
type Phase int// State 定义任务状态
type State int// 当前整体进度,分为三个阶段
const (MapPhase Phase = iotaReducePhaseAllDone
)// 任务状态类型
// 任务对应的三种状态如何切换的:初始任务时将所有任务状态设置为Waiting,
// worker调用rpc执行某个任务时,任务状态由Waiting==>Working
// worker执行任务完成后调用rpc将任务状态有Working==>Done,至此任务完成,上面后两条暂时仅仅针对Map任务
// worker执行Reduce任务时,原理仍然同上,状态由 Waiting==>Working==>Done之间切换
const (Working State = iota // 此阶段在工作Waiting              // 此阶段在等待执行, 当Map任务没有执行完成此时Reduce任务需要等待Done                 // 此阶段已经做完
)// TaskMetaInfo 保存任务的元数据,任务开始时间、任务状态、任务对应的指针以便后续找到任务
// 思考:为什么不将任务执行状态以及任务执行开始时间,定义到具体任务Task结构体中,也可以这样定义,但不符合要求
// 这些信息不需要暴露给Worker,只要Coordinator维护这些信息,并且能够根据这些信息判断出哪些任务需要执行即可
type TaskMetaInfo struct {// 添加任务开始执行时间StartTime time.TimeState     State// 传入任务的指针,为了任务从通道中取出来之后,能够通过地址标记这个任务已经完成TaskAdr *Task
}// TaskMetaHolder 任务元信息结构体,主要存储任务进行的状态、任务开始时间以及任务对应的地址「以能够随时更改任务状态」
type TaskMetaHolder struct {TaskMeta map[int]*TaskMetaInfo
}func (t *TaskMetaHolder) acceptTaskMetaInfo(taskMetaInfo *TaskMetaInfo) bool {taskId := taskMetaInfo.TaskAdr.TaskIdmeta, _ := t.TaskMeta[taskId]if meta != nil {fmt.Printf("[acceptTaskMetaInfo] the taskId :%v have contain metaInfo", taskId)return false} else {t.TaskMeta[taskId] = taskMetaInfo}return true
}// the function is to judge waiting task to working
func (t *TaskMetaHolder) judgeTaskState(taskId int) bool {taskInfo, ok := t.TaskMeta[taskId]if !ok || taskInfo.State != Waiting {return false}taskInfo.StartTime = time.Now()taskInfo.State = Workingreturn true
}// the function is to judge if all tasks have done, server subsequent phase
func (t *TaskMetaHolder) allTaskDone() bool {var (mapDoneNum      = 0mapUnDoneNum    = 0reduceDoneNum   = 0reduceUnDoneNum = 0)taskMeta := t.TaskMetafor _, taskMetaInfo := range taskMeta {if taskMetaInfo.TaskAdr.TaskType == MapTask {if taskMetaInfo.State == Done {mapDoneNum++} else {mapUnDoneNum++}} else if taskMetaInfo.TaskAdr.TaskType == ReduceTask {if taskMetaInfo.State == Done {reduceDoneNum++} else {reduceUnDoneNum++}}}if (mapDoneNum > 0 && mapUnDoneNum == 0) && (reduceDoneNum == 0 && reduceUnDoneNum == 0) {return true} else {if reduceDoneNum > 0 && reduceUnDoneNum == 0 {return true}}return false
}type Coordinator struct {// Your definitions here.Phase          PhaseMapChan        chan *TaskReduceChan     chan *TaskReduceNum      intFiles          []stringTaskId         int // 这个字段主要作用生成递增IDTaskMetaHolder TaskMetaHoldermu             sync.Mutex
}// Your code here -- RPC handlers for the worker to call.// an example RPC handler.
//
// the RPC argument and reply types are defined in rpc.go.
func (c *Coordinator) Example(args *ExampleArgs, reply *ExampleReply) error {reply.Y = args.X + 1return nil
}// start a thread that listens for RPCs from worker.go
func (c *Coordinator) server() {rpc.Register(c)rpc.HandleHTTP()//l, e := net.Listen("tcp", ":1234")sockname := coordinatorSock()os.Remove(sockname)l, e := net.Listen("unix", sockname)if e != nil {log.Fatal("listen error:", e)}go http.Serve(l, nil)
}// main/mrcoordinator.go calls Done() periodically to find out
// if the entire job has finished.
func (c *Coordinator) Done() bool {//The coordinator, as an RPC server, will be concurrent; don't forget to lock shared datac.mu.Lock()defer c.mu.Unlock()ret := false// Your code here.if c.Phase == AllDone {fmt.Println("All tasks have Done")ret = true} else {//fmt.Println("Not All tasks have Done")}return ret
}// create a Coordinator.
// main/mrcoordinator.go calls this function.
// nReduce is the number of reduce tasks to use.
func MakeCoordinator(files []string, nReduce int) *Coordinator {c := Coordinator{Phase:      MapPhase,MapChan:    make(chan *Task, len(files)),ReduceChan: make(chan *Task, nReduce),ReduceNum:  nReduce,Files:      files,TaskMetaHolder: TaskMetaHolder{TaskMeta: make(map[int]*TaskMetaInfo, nReduce+len(files)),},TaskId: 0,}// Your code here.c.MakeMapTask(files)c.server()// 开启一个探测器,监测任务执行时间是否过长go c.crashHandler()return &c
}func (c *Coordinator) MakeMapTask(files []string) {for _, file := range files {taskID := c.genTaskId()task := Task{TaskType:  MapTask,TaskId:    taskID,FileSlice: []string{file},ReduceNum: c.ReduceNum,}// 保存任务初始状态taskMetaInfo := TaskMetaInfo{State: Waiting, TaskAdr: &task}c.TaskMetaHolder.acceptTaskMetaInfo(&taskMetaInfo)c.MapChan <- &task}
}func (c *Coordinator) MakeReduceTask() {for reduceNum := 0; reduceNum < c.ReduceNum; reduceNum++ {taskID := c.genTaskId()task := Task{TaskType:  ReduceTask,TaskId:    taskID,FileSlice: c.selectReduceNum(reduceNum),ReduceNum: c.ReduceNum,}// 保存任务初始状态taskMetaInfo := TaskMetaInfo{State: Waiting, TaskAdr: &task}c.TaskMetaHolder.acceptTaskMetaInfo(&taskMetaInfo)c.ReduceChan <- &task}
}/*
*构建Reduce任务,需要将Map任务存储的所有中间文件按照ReduceNum构建任务:
一个File也就是一个Map任务,执行结果会根据Key的哈希值将一个File分散为ReduceNums个任务,如:mr-0-0\mr-0-1\mr-0-2
末尾数字为需要分配给某个Reduce执行的文件,中间的数字为对于的Map的任务标识,也就是TaskId
*/
func (c *Coordinator) selectReduceNum(reduceNum int) []string {var res []stringpath, _ := os.Getwd()files, err := ioutil.ReadDir(path)if err != nil {log.Fatal("[selectReduceNum] failure", err)return res}for _, file := range files {if strings.HasPrefix(file.Name(), "mr-") && strings.HasSuffix(file.Name(), strconv.Itoa(reduceNum)) {res = append(res, file.Name())}}return res
}/*
* 为什么需要一个全局唯一ID生成器,主要Map的worker个数为len(files), Reduce的worker个数为ReduceNum个,
* Coordinator中有一个属性TaskMetaHolder用于保存任务的元数据,更内层使用一个map表格存储各个任务的元信息,key为任务ID,同时任务总数为
* Map对应的worker+Reduce对应的worker,所以需要使用一个全局任务Id生成器,生成递增的任务ID*/
func (c *Coordinator) genTaskId() int {res := c.TaskIdc.TaskId++return res
}func (c *Coordinator) PullTask(taskReq *TaskRequest, taskResp *Task) error {c.mu.Lock()defer c.mu.Unlock()switch c.Phase {case MapPhase:{if len(c.MapChan) > 0 {*taskResp = *<-c.MapChanif !c.TaskMetaHolder.judgeTaskState(taskResp.TaskId) {fmt.Println("[PullTask] task state is ", c.TaskMetaHolder.TaskMeta[taskResp.TaskId].State)}} else {// Map对应的任务被分发完了,但此时任务并没有全部完成,此时将任务状态设置为waiting状态taskResp.TaskType = WaitingTask// 检查Map任务是否都完成,完成后将流程进入Reduce阶段if c.TaskMetaHolder.allTaskDone() {c.toNextPhase()}return nil}}case ReducePhase:{if len(c.ReduceChan) > 0 {*taskResp = *<-c.ReduceChanif !c.TaskMetaHolder.judgeTaskState(taskResp.TaskId) {fmt.Println("[PullTask] task state is ", c.TaskMetaHolder.TaskMeta[taskResp.TaskId].State)}} else {taskResp.TaskType = WaitingTaskif c.TaskMetaHolder.allTaskDone() {c.toNextPhase()}return nil}}case AllDone:{taskResp.TaskType = ExitTask}default:panic("[PullTask] invalid Phase")}return nil
}func (c *Coordinator) MarkDone(task *Task, taskResp *Task) error {c.mu.Lock()defer c.mu.Unlock()switch task.TaskType {case MapTask:{metaInfo, ok := c.TaskMetaHolder.TaskMeta[task.TaskId]if ok && metaInfo.State == Working {metaInfo.State = Donefmt.Printf("[MarkDone] task is done, the taskId is: %v, the taskType is %v\n", task.TaskId, task.TaskType)} else {fmt.Printf("[MarkDone] error, the task not to be done, taskId id : %v, the tasktype is %v\n ", task.TaskId, task.TaskType)}break}case ReduceTask:{metaInfo, ok := c.TaskMetaHolder.TaskMeta[task.TaskId]if ok && metaInfo.State == Working {metaInfo.State = Donefmt.Printf("[MarkDone] task is done, the taskId is: %v, the taskType is %v\n", task.TaskId, task.TaskType)} else {fmt.Printf("[MarkDone] error, the task not to be done, taskId id : %v, the tasktype is %v\n ", task.TaskId, task.TaskType)}break}default:{panic("[MarkDone] invalid TaskType")}}return nil
}func (c *Coordinator) toNextPhase() {switch c.Phase {case MapPhase:{c.MakeReduceTask()c.Phase = ReducePhase}case ReducePhase:{c.Phase = AllDone}default:panic("[toNextPhase] invalid phase")}
}/*
*
为什么要设置这个探测器:参考如下回答。自己写的时候考虑到这个仅仅将当前对应的任务状态由Working更改为Waiting,并将其加入到对应的chan通道中
但worker中并没有终止相应任务的执行,此举是否会造成一个worker执行多次???
1.无论worker中一个任务是否执行多次,对于Map来说产生的中间文件名称是一样的,后续分配给新的worker后输出文件会覆盖前面的worker输出的文件,并且是从头填写
The coordinator can't reliably distinguish between crashed workers, workers that are alive but have stalled for some reason,
and workers that are executing but too slowly to be useful. The best you can do is have the coordinator wait for some amount of time,
and then give up and re-issue the task to a different worker. For this lab, have the coordinator wait for ten seconds;
after that the coordinator should assume the worker has died (of course, it might not have).
*/
func (c *Coordinator) crashHandler() {for {// 关于这个休眠时间的思考:// 如果不设置这个休眠时间,可能导致探测器协程不断获取锁,释放锁,不断循环,从而导致分发任务的方法PullTask无法获取锁// 从而无法执行后续任务,这里类似时间片算法的使用了。time.Sleep(2 * time.Second)c.mu.Lock()if c.Phase == AllDone {c.mu.Unlock()break}for _, metaInfo := range c.TaskMetaHolder.TaskMeta {if metaInfo.State == Working && time.Since(metaInfo.StartTime) > 9*time.Second {if metaInfo.TaskAdr.TaskType == MapTask {c.MapChan <- metaInfo.TaskAdrmetaInfo.State = Waiting} else if metaInfo.TaskAdr.TaskType == ReduceTask {c.ReduceChan <- metaInfo.TaskAdrmetaInfo.State = Waiting}}}c.mu.Unlock()}
}

worker.go

package mrimport ("encoding/json""fmt""io/ioutil""os""sort""strconv""time"
)
import "log"
import "net/rpc"
import "hash/fnv"// Map functions return a slice of KeyValue.
type KeyValue struct {Key   stringValue string
}// for sorting by key.
type BySort []KeyValue// for sorting by key.
func (a BySort) Len() int           { return len(a) }
func (a BySort) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a BySort) Less(i, j int) bool { return a[i].Key < a[j].Key }// use ihash(key) % NReduce to choose the reduce
// task number for each KeyValue emitted by Map.
func ihash(key string) int {h := fnv.New32a()h.Write([]byte(key))return int(h.Sum32() & 0x7fffffff)
}// main/mrworker.go calls this function.
func Worker(mapf func(string, string) []KeyValue,reducef func(string, []string) string) {// Your worker implementation here.// uncomment to send the Example RPC to the coordinator.//CallExample()// ----------------------------------------第一次实现----------------------------------// GetTask//for i := 0; i < 2; i++ {//	task := GetTask()//	DoMapTask(&task, mapf)//}// ----------------------------------------第二次调整-----------------------------------// 对任务状态加入枚举flag := truefor flag {task := GetTask()switch task.TaskType {case MapTask:{DoMapTask(&task, mapf)TaskDone(&task)}case ReduceTask:{DoReduceTask(&task, reducef)TaskDone(&task)}case WaitingTask:{//执行到此处说明Map任务有部分尚未完成,此时不能继续执行后续的Reduce任务,只有等待所有Map任务全部完成后才能执行Reduce//所以在此处休眠一会fmt.Printf("[Worker] the task is waiting, taskId : %v\n\n", task.TaskId)time.Sleep(2 * time.Second)}case ExitTask:{fmt.Println("[Worker] Exit Task")flag = false}}}
}// GetTask :Get a Task
func GetTask() Task {taskReq := TaskRequest{}taskResp := Task{}ok := call("Coordinator.PullTask", &taskReq, &taskResp)if ok {fmt.Printf("[GetTask] success get task : %v\n", taskResp)} else {fmt.Printf("[GetTask] call failed!\n")}return taskResp
}func DoMapTask(task *Task, mapf func(string, string) []KeyValue) {/***Map任务主要流程分为三部分:1. 根据RPC调用获取到文件名,调用已经写好的Map函数生成中间文件(intermediate)2. 根据RPC返回的任务参数中的ReduceNum的数值对中间文件(intermediate)进行分组,分组依据根据字母结构体的Key取Hash值随后对ReduceNum取余。3. 将分组后的中间文件保存到临时文件中---------------------个人理解-----------------------------------------生成的临时文件名:mr-X-Y(X是文件id,Y是哈希后对应ReduceNum)对于文件Id就是运行coordinator传入第二个参数files(文件集)中文件的顺序也就是说Map函数的主要功能是将传入的某个文件的词频统计出来(准确的说并没有统计词频,可以阅读wc.go源代码)仅仅将「单词-1」统计出来for _, w := range words {kv := mr.KeyValue{w, "1"}kva = append(kva, kv)随后将某个文件的所有词频根据Key也就是单词,根据单词的哈希值将单词分组,分组个数为ReduceNum*/var intermediate []KeyValuefmt.Printf("[DoMapTask] worker is map taskId : %v, fileName : %v\n", task.TaskId, task.FileSlice[0])file, err := os.Open(task.FileSlice[0])if err != nil {log.Fatalf("[DoMapTask] cannot open %v", task.FileSlice[0])}content, err := ioutil.ReadAll(file)if err != nil {log.Fatalf("[DoMapTask] cannot read %v", task.FileSlice[0])}file.Close()intermediate = mapf(task.FileSlice[0], string(content))reduceNum := task.ReduceNumhashKV := make([][]KeyValue, reduceNum)for _, value := range intermediate {index := ihash(value.Key) % reduceNumhashKV[index] = append(hashKV[index], value)}// 放入中间文件for i := 0; i < reduceNum; i++ {fileName := "mr-" + strconv.Itoa(task.TaskId) + "-" + strconv.Itoa(i)tempFile, err := os.Create(fileName)if err != nil {log.Fatalf("[DoMapTask] create temp file: %v failed.", fileName)}enc := json.NewEncoder(tempFile)for _, kv := range hashKV[i] {err = enc.Encode(&kv)if err != nil {log.Fatalf("[DoMapTask] encode error: %v", err)}}tempFile.Close()}
}func DoReduceTask(task *Task, reducef func(string, []string) string) {// 关于这个Reduce任务为什么需要先写入临时中间文件,随后等中间文件写完后,在重命名临时文件为最终文件/**To ensure that nobody observes partially written files in the presence of crashes,the MapReduce paper mentions the trick of using a temporary file and atomically renaming it once it is completely written.You can use ioutil.TempFile (or os.CreateTemp if you are running Go 1.17 or later) to create a temporary fileand os.Rename to atomically rename it.最上面一句:确保发生崩溃时没有人观察到部分写入的文件使用中间临时文件在发生崩溃时临时文件不会保存,此外使用中间文件命令和最终输出文件名称不一样能够保证输出的符合要求格式的文件都是完整的,不完整的文件即使没有删除,由于命名和要求格式不一样也不会考虑*/reduceFileNum := task.TaskIdintermediate := shuffle(task.FileSlice)dir, _ := os.Getwd()tempFile, err := os.CreateTemp(dir, "mr-tmp-*")if err != nil {log.Fatal("[DoReduceTask] Failed to create temp file", err)}i := 0// Debug//fmt.Printf("the intermediate length is %v\n", len(intermediate))for i < len(intermediate) {j := i + 1for j < len(intermediate) && intermediate[j].Key == intermediate[i].Key {j++}var values []stringfor k := i; k < j; k++ {values = append(values, intermediate[k].Value)}output := reducef(intermediate[i].Key, values)fmt.Fprintf(tempFile, "%v %v\n", intermediate[i].Key, output)i = j}tempFile.Close()fn := fmt.Sprintf("mr-out-%d", reduceFileNum)os.Rename(tempFile.Name(), fn)
}func shuffle(files []string) []KeyValue {var kva []KeyValuefor _, filepath := range files {file, _ := os.Open(filepath)dec := json.NewDecoder(file)for {var kv KeyValueif err := dec.Decode(&kv); err != nil {break}kva = append(kva, kv)}file.Close()// 删除临时文件//os.Remove(filepath)}sort.Sort(BySort(kva))return kva
}func TaskDone(task *Task) {taskReq := tasktaskResp := Task{}ok := call("Coordinator.MarkDone", &taskReq, &taskResp)if ok {fmt.Printf("[TaskDone] success mark task : %v\n", taskReq)} else {fmt.Printf("[TaskDone] call failed!\n")}
}// example function to show how to make an RPC call to the coordinator.
//
// the RPC argument and reply types are defined in rpc.go.
func CallExample() {// declare an argument structure.args := ExampleArgs{}// fill in the argument(s).args.X = 99// declare a reply structure.reply := ExampleReply{}// send the RPC request, wait for the reply.// the "Coordinator.Example" tells the// receiving server that we'd like to call// the Example() method of struct Coordinator.ok := call("Coordinator.Example", &args, &reply)if ok {// reply.Y should be 100.fmt.Printf("reply.Y %v\n", reply.Y)} else {fmt.Printf("call failed!\n")}
}// send an RPC request to the coordinator, wait for the response.
// usually returns true.
// returns false if something goes wrong.
func call(rpcname string, args interface{}, reply interface{}) bool {// c, err := rpc.DialHTTP("tcp", "127.0.0.1"+":1234")sockname := coordinatorSock()c, err := rpc.DialHTTP("unix", sockname)if err != nil {log.Fatal("dialing:", err)}defer c.Close()err = c.Call(rpcname, args, reply)if err == nil {return true}fmt.Println(err)return false
}

参考链接

https://blog.csdn.net/weixin_51322383/article/details/132068745
https://blog.csdn.net/weixin_45938441/article/details/124018485

相关文章:

mit6824-02-Lab1:MapReduce分布式实现

文章目录 写在前面总体思路分析代码实现参考链接 写在前面 具体上次写6824的第一篇文章已经过去了快一个月&#xff0c;上次学习了MapReduce论文相关理论后一直没有继续写代码实现&#xff0c;自己一边要搞论文没有整块时间实现&#xff0c;这两天抽写了相关代码&#xff0c;算…...

【NOIP普及组】 装箱问题

【NOIP普及组】 装箱问题 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 有一个箱子容量为V&#xff08;正整数&#xff0c;0&#xff1c;&#xff1d;V&#xff1c;&#xff1d;20000&#xff09;&#xff0c;同时有n个物品&#xff08;0&…...

Flutter主题最佳实践

Styling your Flutter app not only makes it visually appealing but also enhances the user experience. Flutter offers a robust theming system that helps you maintain consistency and customize your app’s look and feel. 设计 Flutter 应用程序的风格不仅能使其在…...

计算机网络:网络层 —— IPv4 数据报的首部格式

文章目录 IPv4数据报的首部格式IPv4数据报分片生存时间 TTL字段协议字段首部检验和字段 IPv4数据报的首部格式 IPv4 数据报的首部格式及其内容是实现 IPv4 协议各种功能的基础。 在 TCP/IP 标准中&#xff0c;各种数据格式常常以32比特(即4字节)为单位来描述 固定部分&#x…...

MySQL 之 索引

索引 概述 是帮助MySQL高效获取数据的数据结构&#xff0c;在数据之外&#xff0c;数据库系统还维护着满足特定查找算法的数据结构&#xff0c;这些数据结构以某种方式引用&#xff08;指向&#xff09;数据&#xff0c;这样就可以在数据结构上实现高效查找算法&#xff0c;这种…...

手动探针台的用途及组成部分

探针台系统分为手动探针台与自动探针台&#xff0c;以下我们主要分析手动探针台。 探针台用途&#xff1a; 手动探针台又称探针测试台主要用途是为半导体芯片的电参数测试提供一个测试平台&#xff0c;探针台可吸附多种规格芯片&#xff0c;并提供多个可调测试针以及探针座&am…...

❤️算法笔记❤️-(每日一刷-5、最长回文串)

文章目录 题目思路解答 题目 给你一个字符串 s&#xff0c;找到 s 中最长的 回文 子串。 示例 1&#xff1a; 输入&#xff1a;s "babad" 输出&#xff1a;"bab" 解释&#xff1a;"aba" 同样是符合题意的答案。示例 2&#xff1a; 输入&#xf…...

nginx 路径匹配,关于“/“对规则的影响

1、基本规则 假如后端实际地址为&#xff1a; http://127.0.0.1:8080/api/user/getById?id123 则&#xff1a; 1&#xff09;通过nginx转发&#xff0c;使用http://127.0.0.1/api/user/getById?id123访问 server {listen 80;server_name 127.0.0.1;location /api…...

安全知识见闻-网络安全热门证书

一、OSCP(Offensive Security Certified Professional) 1. 证书介绍 2.考点 3.部分考试要求 4.练习方法 二、OSEP(Offensive Security Exploit Developer) 1.证书介绍 2.考点 3.练习方法 三、CISSP&#xff08;Certified lnformation Systems Security Professional&a…...

Pandabuy事件警示:反向海淘品牌如何规避风险

Pandabuy&#xff0c;作为一个曾经备受海外消费者青睐的跨境电商平台&#xff0c;以其丰富的商品种类、优质的服务和便捷的购物流程迅速崛起。然而&#xff0c;近期的一系列丑闻&#xff0c;尤其是涉嫌销售大量仿制名牌运动鞋的事件&#xff0c;让Pandabuy陷入了前所未有的信任…...

【纯血鸿蒙】安装hdc工具

这里我先写Mac版的,Windows的在下面 首先要知道你的SDK安装在哪里了,不知道的话,可以打开DevEco Studio,打开设置页面里的HarmonyOS SDK,这个我们之前配置环境变量的时候用过。 其实主要是用到这里toolchains下的hdc命令。 所以我们需要配置环境变量。 1、打开Mac下的…...

TensorFlow面试整理-给定一个任务(如图像分类、文本分类),如何从头构建一个TensorFlow模型?

构建一个 TensorFlow 模型来执行图像分类或文本分类任务的步骤基本类似,虽然数据类型不同,但核心流程相同。以下将以 图像分类任务 和 文本分类任务 为例,展示如何从头构建 TensorFlow 模型,覆盖数据预处理、模型构建、编译、训练和评估的完整流程。 一、图像分类任务:从头…...

unity中出现一些莫名其妙的问题

问题现象&#xff1a;一个功能昨天测试还正常的今天突然不能用了&#xff0c;而且关于这个功能的代码都没调整过。 原因&#xff1a;相关逻辑上存在异常代码&#xff0c;可能是别人提交的代码运行中有异常未处理导致 处理办法&#xff1a;解决异常 查找哪些位置使用了该异常脚本…...

Python爬虫-汽车投诉排行榜单数据

前言 本文是该专栏的第40篇,后面会持续分享python爬虫干货知识,记得关注。 本文以某汽车平台为例,通过python采集其“汽车投诉排行”榜单数据。具体的实现思路以及完整实现代码逻辑,笔者将在正文为你详细介绍。废话不多说,跟着笔者直接往下看正文详细内容。(附带完整代码…...

[C++][数据结构][哈希表]详细讲解

目录 1. 哈希概念 2.哈希冲突 3.哈希函数 4.哈希冲突解决 4.1闭散列 4.1.1何时扩容&#xff1f;如何扩容&#xff1f; 4.1.2线性探测 4.1.3二次探测 4.2开散列(哈希桶) 4.2.1概念 4.2.2开散列增容 1. 哈希概念 顺序结构以及平衡树中&#xff0c;元素关键码与其存储…...

Android Gradle

#1024程序员节&#xff5c;征文# Gradle 是一款强大的自动化构建工具&#xff0c;广泛应用于 Android 应用开发。它通过灵活的配置和丰富的插件系统&#xff0c;为项目构建提供了极大的便利。本文只是简单的介绍 Gradle 在 Android 开发中的使用&#xff0c;包括其核心概念、构…...

Vue2自定义指令及插槽

这里写目录标题 自定义指令基础语法指令的值封装v-loading指令 插槽默认插槽后备内容&#xff08;插槽的默认值&#xff09;具名插槽作用域插槽 自定义指令 自定义指令&#xff1a;自己定义的指令&#xff0c;封装一些dom操作&#xff0c;扩展额外功能 基础语法 全局注册&am…...

【Qt】系统相关——多线程、Qt多线程介绍、常用函数、线程安全、网络、UDP Socket、TCP Socket

文章目录 Qt系统相关1. 多线程1.1 Qt多线程介绍1.2 常用函数1.3 线程安全 2. 网络2.1 UDP Socket2.2 TCP Socket Qt 系统相关 1. 多线程 1.1 Qt多线程介绍 QThread 代表一个在应用程序中可以独立控制的线程&#xff0c;它还可以和进程中的其他线程共享数据。QThread 对象管理…...

1GS/s 4通道14bit PCIE采集卡

1GS/s 4通道14bit PCIE采集卡是一款同时具备直流耦合程控放大器和双极性宽带信号输入的高速数据采集卡。板载FPGA具备实时信号处理能力&#xff0c;这些特性使其成为激光雷达、光纤传感、粒子物理等应用领域进行信号采集和分析的理想工具。提供快速的PCI Express 3.0 x8数据传输…...

动态IP是什么?

随着互联网成为人们生活的重要组成部分&#xff0c;以信息传递为主导的时代种&#xff0c;网络连接质量对我们的工作效率、学习进度以及娱乐体验等方面都有很大影响。 动态IP&#xff0c;作为网络连接中的一种重要IP代理形式&#xff0c;越来越受到用户的欢迎。本文将深入解析…...

XCTF-web-easyupload

试了试php&#xff0c;php7&#xff0c;pht&#xff0c;phtml等&#xff0c;都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接&#xff0c;得到flag...

3.3.1_1 检错编码(奇偶校验码)

从这节课开始&#xff0c;我们会探讨数据链路层的差错控制功能&#xff0c;差错控制功能的主要目标是要发现并且解决一个帧内部的位错误&#xff0c;我们需要使用特殊的编码技术去发现帧内部的位错误&#xff0c;当我们发现位错误之后&#xff0c;通常来说有两种解决方案。第一…...

mongodb源码分析session执行handleRequest命令find过程

mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程&#xff0c;并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令&#xff0c;把数据流转换成Message&#xff0c;状态转变流程是&#xff1a;State::Created 》 St…...

Python爬虫实战:研究feedparser库相关技术

1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...

STM32F4基本定时器使用和原理详解

STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练

前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1)&#xff1a;从基础到实战的深度解析-CSDN博客&#xff0c;但实际面试中&#xff0c;企业更关注候选人对复杂场景的应对能力&#xff08;如多设备并发扫描、低功耗与高发现率的平衡&#xff09;和前沿技术的…...

macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用

文章目录 问题现象问题原因解决办法 问题现象 macOS启动台&#xff08;Launchpad&#xff09;多出来了&#xff1a;Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显&#xff0c;都是Google家的办公全家桶。这些应用并不是通过独立安装的…...

selenium学习实战【Python爬虫】

selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...

有限自动机到正规文法转换器v1.0

1 项目简介 这是一个功能强大的有限自动机&#xff08;Finite Automaton, FA&#xff09;到正规文法&#xff08;Regular Grammar&#xff09;转换器&#xff0c;它配备了一个直观且完整的图形用户界面&#xff0c;使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...

Linux --进程控制

本文从以下五个方面来初步认识进程控制&#xff1a; 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程&#xff0c;创建出来的进程就是子进程&#xff0c;原来的进程为父进程。…...