欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

6.824-lecture2

程序员文章站 2022-07-12 17:08:06
...

基础架构: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.html
        go 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]
    else
      r = handler()
      old[xid] = r
      seen[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 准确的一次

  • 无限次重试
  • 重复的检测
  • 错误的容忍服务
相关标签: 分布式系统

推荐阅读