美文网首页
2017 6.824学习笔记 Lecture 2: RPC an

2017 6.824学习笔记 Lecture 2: RPC an

作者: openex | 来源:发表于2018-04-04 16:57 被阅读0次

    线程

    • 线程是非常有用的构建工具
    • 再Go中线程称为goroutines,其他语言中叫做线程
    • 他们通常比较棘手

    为什么使用线程?

    • 允许利用并发,这在分布式系统中很自然的体现出来
    • 再I/O并发中,允许在等待一个IO响应时,处理下一个请求
    • 多线程可以平行运行在多核上

    Thread = "thread of execution"

    • 线程允许一个程序在逻辑上一次执行许多事情
    • 线程间有共享内存
    • 每个线程包含自己的运行状态:
    • 程序计数器
    • 寄存器

    一个程序会有多少个线程?

    • 尽可能多的“有用的”线程在一个应用中
    • Go鼓励创建更多的线程
    • 通常线程数会大于CPU核心数
    • Go的调度器将他们调度到可用的核心上
    • Go的线程也时有损耗的,但他要比通常意义中创建一个系统线程要轻量

    使用线程的挑战?

    • 共享数据:e.g. 一个线程读数据同时另一个线程改变数据,将导致竞争条件
    • 不要共享数据或者协调共享(使用互斥锁等)
    • 线程间的协调:(e.g. 等待所有Map结束),导致死锁
    • 使用Go的channel或者waitGroup
    • 并发性的粒度
    • 粗粒度:简单,较少的并发/并行
    • 细粒度:更多的并发,但也有更多的竞争和死锁

    远程过程调用(RPC)

    概述

    RPC是分布式系统中的一个关键机制

    目标:轻易地编写网络通信

    • 隐藏许多客户端/服务端之间的通讯细节
    • 客户端调用就像普通的程序调用
    • 服务端的处理程序非常像普通程序
    • RPC使用非常广泛

    理想中RPC让网络通讯就像函数调用一样:

      Client:
        z = fn(x, y)
      Server:
        fn(x, y) {
          compute
          return z
        }
    

    RPC的目标就是这个水平的透明度

    RPC 通信模型

      Client             Server
        request--->
           <---response
    

    RPC 软件结构

    Software structure
      client app         handlers
        stubs           dispatcher
       RPC lib           RPC lib
         net  ------------ net
    

    一些细节:

    • 那个服务函数去处理调用?
      • 在go中通过Call()
    • 编码:格式化数据到网络包中
    • 一些类型比较棘手,数组 指针 对象 引用等
    • Go的RPC包很强大,但是一些类型不允许传输(channel function等)
    • 绑定:客户端如何选择服务端?
    • 可能是客户端写死
    • 可能是有一个服务名称映射服务来选择最优的服务端

    RPC的问题:失败如何处理?

    例如丢包,网络故障,慢服务,服务端崩溃

    从RPC的客户端角度失败是什么样子的?

    • 客户端从未收到服务器端的响应数据
    • 客户端不会知道服务端有没有接收到请求报文
    • 故障可能发生在服务端回复响应之前

    解决方案:

    最简单RPC行为“最少一次”
    RPC库一直等待响应结果,如果规定时间内没有接收到,则重新发送这个请求。这样一直尝试很多次后,若一直没有收到响应,则返回错误给调用程序

    Q:对于应用来说“至少一次”是很容易处理的吗?

    A:问题举例

    • 客户端发送从银行账户扣除10元钱的请求,可能会导致多扣钱;
    • 向数据库的同一个key赋值,当出现网络超时,导致重发时,先发送的写操作可能后到达
    • Put(“KEY1”,10)
    • Put(“KEY1”,20)
    • 期望结果为20,但结果可能为10

    Q:什么场景下可以使用“至少一次”模型?

    A:

    • 重复操作是可以接受的,比如只读操作
    • 若应用程序有自己的方法去应对重复写入问题时,可以使用

    更好的RPC行为“最多一次”

    想法:服务端RPC代码中检测重复的请求,返回之前的结果提单重新运行的操作

    Q:如何检测重复操作?

    A:客户端使用唯一ID在每个请求中,超时重新请求时使用相同的唯一ID

      server:
        if seen[xid]:
          r = old[xid]
        else
          r = handler()
          old[xid] = r
          seen[xid] = true
    

    一些“最多一次”模型的难点:

    1.如何保证唯一ID?
    大的随机数?client的唯一地址?等

    2.老的RPC结果最终是一定要删除(缓存RPC结果已保证“最多一次”)的
    如何保证删除这些数据是安全的?

    • 保证各客户端ID是不重的
    • 方法1:每个RPC请求要保证之前的所有响应已经回复 像TCP通信中的SEQ ACK
    • 方法2:只允许一个暂未结束的RPC请求,当seq+1到达时,删除掉<seq的数据
    • 方法3:只保留一段时间内的请求,超过时间范围的都删除掉

    3.如何解决重复请求到来时老的请求未产生结果?

    • 增加等待标识,返回给RPC调用,由调用方决定等待还是忽略

    当服务崩溃或者重启时怎么办?
    副本信息在内存中,重启后这些信息会丢失,同时会接收到重复的请求在重启后

    • 将往期结果(duplicate )写入磁盘?
    • 备用服务应该保存副本往期结果?

    什么是恰好一次模型?

    最多一次模型+无限重试+故障容忍服务

    GO RPC库是 最多一次模型

    • 打开TCP链接
    • 写请求到这个链接
    • TCP可能会重传,但是服务端的TCP会过滤掉这些重复数据
    • 不会在Go的代码中重复
    • 当Go RPC库没有获取到响应结果时会返回一个错误,这可能由以下原因导致:
    • TCP超时
    • 服务端没由看到请求
    • 服务端执行了请求但返回结果前网络出现了故障

    相关文章

      网友评论

          本文标题:2017 6.824学习笔记 Lecture 2: RPC an

          本文链接:https://www.haomeiwen.com/subject/wcflhftx.html