当前位置: 代码迷 >> 综合 >> 6.824-lecture2
  详细解决方案

6.824-lecture2

热度:84   发布时间:2023-10-13 02:32:52.0

基础架构:RPC和线程

最常见的问题:为啥用Go?

  • 6.824使用c++很多年
  • c++工作良好
  • 但是学生花费很多时间追踪指针和alloc/free错误,而且缺乏满意的c++rpc包
  • go对我们来说更好
    • 并行更好的支持
    • rpc更好的支持
    • gc垃圾收集
    • 类型安全
    • 线程安全的gc是很有吸引力的
  • go编程是愉快的
    • 相对简单且传统
  • Threads
    • 线程是有用的架构工具
    • Go叫他们协程,其他编程语言叫他们线程
    • 非常tricky

为啥要threads?

  • 我们需要并行,这在分布式系统很常见,io并行
  • 当等待另一个服务的响应时候,处理其他的请求
  • 线程在多个核心上是并行运行的

线程

  • 线程允许一个程序逻辑上一次运行很多事物
  • 线程共享内存
  • 每个线程共享他们的线程状态
    • pc 寄存器 栈

一个程序可以有多少线程?

  • 部分是因为结构
    • 一个线程一个client 对于后端任务来说
  • 一些是因为多核心并行的意愿
    • 因此一个核心一个活跃线程
    • Go 运行时候自动的调度可执行协程在`可得到的核心上
  • 一些是由于对io并行的期望
    • 县城数量被容量和潜力所支配
    • 保持增长知道规模不再增长
  • go 线程是非常简洁的
    • 100 1000是很好的,但是几百万线程很糟糕
    • 创造i一个线程想对于一次函数调用是昂贵的

线程带来的挑战

  • 共享数据
    • 一个线程读取另一个线程已经修改的数据
    • 例如 两个线程执行count +1
    • 这两个线程是竞争关系 通常会产生bug
    • 使用互斥
    • 避免共享
  • 线程之间合作
    • 如何等待所有的线程完成
    • 使用go channel 或者WaitGroup
  • 并发粒度
    • 粗糙的-简单,但是很少能并发/并行性能的
    • 优秀的-更加并发,但是竞争 死锁情况更多

什么是爬虫?

  • 目标是获取网页,例如得到一个索引
  • 图结构的网页
  • 每个页面有很多链接
  • 图是有环的

爬虫的挑战

  • io并行的挑战
  • 每秒url获取数量的增加
  • 网络的潜在因素更多
  • 只抓取一个网页
  • 避免带宽的浪费
  • 对远程服务良好
  • 当爬去完成后去要知道哪些url访问过

爬虫策略:爬虫页面的调度方式

串行爬虫

  • 获取页面要避免重复,打破环路
  • 单向图,递归调用
  • 但是一次只爬一个页面 串行的

并行爬虫

  • 为没一个抓取页面创建一个线程
  • 很多并行的抓取,很高的抓取效率
  • 很多线程共享抓取
  • 为什么要互斥?/锁?
    • 不用锁
      • 两个页面包含同样url的链接
      • 两个线程同时抓取两个页面
      • T1检查了url,T2检查了url ,两个抓取线程都看见了url,最后都没有抓取
      • 都抓取了,但是结果错误
      • 同时读/写是竞争,而且会导致bug
      • bug可能展示不幸的线程的交叉存储的结果(读写顺序错误了)
    • 如果我使用了lock和unlock操作
      • go run crawler.go
        go run -race crawler.go
      • 锁导致了原子检查和更新
    • 如何决定他完成了
      • sync.WaitGroup
      • 显式等待子线程完成递归的爬取

并行爬虫

  • a Go channel:
    • channnel是一个对象,ch : make(chan int)
    • channel让一个线程发送一个对象给另一个线程 ch<-x
    • sender等待,直到一些协程收到 y:=<=ch for y := range ch
    • 接受这等待直到一些协程发送
  • 同步
    • 几个线程可以在一个channel里面收发信息
    • 发送者阻塞知道接受者收到,在发送时候设置锁可能是危险的
    • ConcurrentChannel master()
      • master()创建一个worker gorountine 来取得每个页面
      • worker()发送url给一个channel
      • 多个workers在一个channel里面发送
      • master()在channnel里面读取url
      • 需要给抓取的map加锁,因为这不是共享的
    • 有什么共享的数据?
      • channel
      • channel上面的片段和string
      • master()传递给worker()的参数

什么时候使用共享和锁,而不是channel

  • 多部分问题可以以任意的方法解决

  • 最关键的在于程序员是如何思考的

    • state- 共享和锁
    • 通信-channels
    • 等待事件-channels
  • 使用Go 的竞争探测器

  • https://golang.org/doc/articles/race_detector.htmlgo test -race 
    

RPC 远程过程调用

  • 分布式系统的关键组件,所有的labs使用rpc
  • 目标:让程序的客户服务器更容易通信

RPC 信息的术语

  • client server
  • request response

rpc尝试模仿本地调用

  • clients z=fn(x,y)

  • server \

  • fn(x,y){
    compute
    return z
    }
    

软件结构

  • client app handlers
  • stubs 票跟 dispatcher调度员
  • rpc lib rpc lib
  • net net

Go 例子:kv.go 调度页面链接

  • 一个玩具kv存储服务器,put(k,v),gey(k.v)
  • 使用go rpc 库
  • 通常
    • 你需要声明参数和返回的结构体为没一个rpc类型
  • client
    • connect()'s Dial()创建一个tcp连接给服务器
    • call()调用rpc库来执行一个调用
    • 需要明确函数名字,参数,位置来存放回复
    • 库执行参数,发送请求,等待,回复
    • 返回值表明是从哪得到的回复
    • 通常你需要一个reply.err表明服务器级别的错误
  • server
    • go需要你声明一个带有对象的方法来作为rpc处理器
    • 你之后注册对象给rpc库
    • 接收tcp连接,给他们一个rpc库
    • rpc库
      • 读每个请求
      • 创建一个协程来处理请求
      • 获取请求的参数
      • 调用命名的方法
      • 加上参数的回复
      • 通过tcp写回回复
    • 服务器get 和put处理器
      • 必须上锁,因为rpc库给每个请求创建一个协程
      • 读参数,修改回复

细节

  • 绑定:client如何知道和谁交互
    • 对go rpc 服务器名字/端口是一个交流的参数
    • 大系统拥有几个名字或者确认服务器
  • Marshalling:生成数据
    • go rpc传递字符串 数组 对象 map
    • go传递指针通过复制
    • 不能传输channel 和函数

rpc问题:失败怎么办?

  • 丢包,网络故障,服务器慢,服务器崩溃

client rpc 库如何判断失败?

  • client无法看见server的response
  • client 不能确保server看见了请求
    • 可能是服务器无法看见请求
    • 可能是服务器执行,在发送回复前崩溃
    • 可能服务器执行,但是网络故障,无法发送回复

最简单的错误处理范式:最大交付

  • calll() 在一定时间内等待回复
  • 如果没有收到,再次发送
  • 重复几次
  • 仍然失败,放弃,返回错误

最大交互是应用最容易处理的吗?

  • 一个典型的坏的情况
    • client 执行
      • Put(“k”, 10);
        Put(“k”, 20);
      • 都成功了
      • Get(“k”)会得到什么?
      • 超时,再次发送

最大交付是ok的吗?

  • 对于只读操作
  • 操作重复不会造成影响的情况
    • DB会检查已经插入的记录

最好的rpc行为:最好一次

  • server rpc 代码检查重复的请求,返回之前的回复而不是再次执行handler

问题:如何侦测重复的请求?

  • 想法:clienta拥有独一无二的Id 给每个request,使用同一个XID来再次发送
  server:if seen[xid]:r = old[xid]elser = handler()old[xid] = rseen[xid] = trueserver:

most-once的复杂性

  • 体现在lab3中

如何确保XID是唯一的

  • 大随机数
  • 将ip地址和一个序列数结合

server最终会丢弃老的rpc

  • 什么时候丢弃是安全的?

idea

  • 每个client是独一无二的id
  • 每个client rpc顺序的数字
  • client包括seen all replies <= X 给每个rpc
  • 更像tcp的ack顺序
  • 或者当seq+1序列到达,那么丢弃seq之前的

如何处理重复的req?

  • 服务器不知道重复reply
  • pending 标记给没一个rpc 等待 或者忽略

一个most-once 服务器崩溃或者重启怎么办?

  • 如果at-most-once有重复信息在内存中,服务器将会忘记并接受re-start的重复requests
  • 可能他应该写重复信息给磁盘
  • 可能重复服务器也需要重复信息

go prc是一个at most once的简单形式

  • 开放tcp连接
  • 写请求个tcp链接
  • gp rpc不需要重复发送请求
    • 因此server不会看见重复请求
  • go rpc代码返回一个错误值如果他不能得到回复
    • 可能因为网络超时
    • 肯呢个服务器无法看见请求
    • 可能服务器处理请求但是服务器在收到回复前崩溃

exactly once 准确的一次

  • 无限次重试
  • 重复的检测
  • 错误的容忍服务