Lecture 02 Infrastructure: RPC & threads

一、多线程挑战

  • 共享数据: 使用互斥信号量、或者避免共享
  • 线程间协作: 使用channels 或者 waitgroup 来等待所有map线程结束
  • 并发粒度:
    • 粗粒度: 简单,但是并发性不高
    • 细粒度: 更多的并发,但是处理复杂,可能会有更多的冲突和死锁

以下这段代码就能说明并发的粒度问题:

  1. constructTaskArgs := func(phase jobPhase, task int) DoTaskArgs {
  2. debug("task: %d\n", task)
  3. var taskArgs DoTaskArgs
  4. taskArgs.Phase = phase
  5. taskArgs.JobName = jobName
  6. taskArgs.NumOtherPhase = n_other
  7. taskArgs.TaskNumber = task
  8. if phase == mapPhase {
  9. taskArgs.File = mapFiles[task]
  10. }
  11. return taskArgs
  12. }
  13. tasks := make(chan int) // act as task queue
  14. go func() {
  15. for i := 0; i < ntasks; i++ {
  16. tasks <- i
  17. }
  18. }()
  19. successTasks := 0
  20. success := make(chan int)
  21. loop:
  22. for {
  23. select {
  24. case task := <-tasks:
  25. go func() {
  26. worker := <-registerChan
  27. status := call(worker, "Worker.DoTask", constructTaskArgs(phase, task), nil)
  28. if status {
  29. success <- 1
  30. go func() { registerChan <- worker }()
  31. } else {
  32. tasks <- task
  33. }
  34. }()
  35. case <-success:
  36. successTasks += 1
  37. default:
  38. if successTasks == ntasks {
  39. break loop
  40. }
  41. }
  42. }

里面不仅使用了task的channel, 还使用了success (channel) 来控制 successTask 的共享。

二、爬虫并发的问题

网络是一个有环的图,但是我们设计爬虫需要避免环。

  • 一方面是因为重复遍历url,没有任何意义
  • 另一方面只访问一次url可以减轻目标服务器负担

单线程爬虫:

  1. func Serial(url string, fetcher Fetcher, fetched map[string]bool) {
  2. if fetched[url] {
  3. return
  4. }
  5. fetched[url] = true
  6. urls, err := fetcher.Fetch(url)
  7. if err != nil {
  8. return
  9. }
  10. for _, u := range urls {
  11. Serial(u, fetcher, fetched)
  12. }
  13. return
  14. }

2.1 并发互斥爬虫

因此需要维护一张visited表来记录是否遍历过url,这里就会出现并发问题。

当T1 检查visited[url] , T2也检查visited[url] 两个线程都会认为没有访问过该url,这时候就会发生冲突,发生WW(write + write) 。解决办法是,维护一个Mutex 互斥信号量来访问visited这张表。

  • 判断线程结束

使用sync.WaitGroup来保证线程执行完成

  1. type fetchState struct {
  2. mu sync.Mutex
  3. fetched map[string]bool
  4. }
  5. func ConcurrentMutex(url string, fetcher Fetcher, f *fetchState) {
  6. f.mu.Lock()
  7. if f.fetched[url] {
  8. f.mu.Unlock()
  9. return
  10. }
  11. f.fetched[url] = true
  12. f.mu.Unlock()
  13. urls, err := fetcher.Fetch(url)
  14. if err != nil {
  15. return
  16. }
  17. var done sync.WaitGroup
  18. for _, u := range urls {
  19. done.Add(1)
  20. go func(u string) {
  21. defer done.Done()
  22. ConcurrentMutex(u, fetcher, f)
  23. }(u)
  24. }
  25. done.Wait()
  26. return
  27. }
  28. func makeState() *fetchState {
  29. f := &fetchState{}
  30. f.fetched = make(map[string]bool)
  31. return f
  32. }

2.2 并发通道爬虫

master启动worker去爬取url, worker将url送到同一个通道里面, master从通道获取url去爬取内容

共享的数据:

  • 通道
  • 发送到 通道的 slices 和 字符串
  • 从master发送到worker的参数
  1. //
  2. // Concurrent crawler with channels
  3. //
  4. func worker(url string, ch chan []string, fetcher Fetcher) {
  5. urls, err := fetcher.Fetch(url)
  6. if err != nil {
  7. ch <- []string{}
  8. } else {
  9. ch <- urls
  10. }
  11. }
  12. func master(ch chan []string, fetcher Fetcher) {
  13. n := 1
  14. fetched := make(map[string]bool)
  15. for urls := range ch {
  16. for _, u := range urls {
  17. if fetched[u] == false {
  18. fetched[u] = true
  19. n += 1
  20. go worker(u, ch, fetcher)
  21. }
  22. }
  23. n -= 1
  24. if n == 0 {
  25. break
  26. }
  27. }
  28. }
  29. func ConcurrentChannel(url string, fetcher Fetcher) {
  30. ch := make(chan []string)
  31. go func() {
  32. ch <- []string{url}
  33. }()
  34. master(ch, fetcher)
  35. }

三、什么时候使用共享空间和锁 vs 通道

state -- 共享空间和锁

communication -- 通道

waiting for events -- 通道

使用go 的 race dector

四、Remote Procedure Call(RPC)

4.1 软件架构:

客户端 handlers

stubs dispatcher(调度器)

rpc lib rpc lib


网络 ----- 网络

4.2 rpc过程:

  • 首先双方定义发送的参数, 和返回的结构体
  • 客户端 Dial()创建tcp连接请求 call() 调用rpc库来执行远程调用
  • 服务器 声明一个带返回方法的对象 作为rpc处理器, 然后使用rpc库的Register函数来注册服务, rpc库:
    • 读取每一个请求
    • 为每一个请求创建一个goroutine
    • 反序列化请求
    • 调用目标函数
    • 序列化返回值
    • 将返回值通过tcp连接返回

4.3rpc 示例

源码

client:

  1. //
  2. // Client
  3. //
  4. func connect() *rpc.Client {
  5. client, err := rpc.Dial("tcp", ":1234")
  6. if err != nil {
  7. log.Fatal("dialing:", err)
  8. }
  9. return client
  10. }
  11. func get(key string) string {
  12. client := connect()
  13. args := GetArgs{"subject"}
  14. reply := GetReply{}
  15. err := client.Call("KV.Get", &args, &reply)
  16. if err != nil {
  17. log.Fatal("error:", err)
  18. }
  19. client.Close()
  20. return reply.Value
  21. }
  22. func put(key string, val string) {
  23. client := connect()
  24. args := PutArgs{"subject", "6.824"}
  25. reply := PutReply{}
  26. err := client.Call("KV.Put", &args, &reply)
  27. if err != nil {
  28. log.Fatal("error:", err)
  29. }
  30. client.Close()
  31. }

server

  1. //
  2. // Server
  3. //
  4. type KV struct {
  5. mu sync.Mutex
  6. data map[string]string
  7. }
  8. func server() {
  9. kv := new(KV)
  10. kv.data = map[string]string{}
  11. rpcs := rpc.NewServer()
  12. rpcs.Register(kv)
  13. l, e := net.Listen("tcp", ":1234")
  14. if e != nil {
  15. log.Fatal("listen error:", e)
  16. }
  17. go func() {
  18. for {
  19. conn, err := l.Accept()
  20. if err == nil {
  21. go rpcs.ServeConn(conn)
  22. } else {
  23. break
  24. }
  25. }
  26. l.Close()
  27. }()
  28. }
  29. func (kv *KV) Get(args *GetArgs, reply *GetReply) error {
  30. kv.mu.Lock()
  31. defer kv.mu.Unlock()
  32. val, ok := kv.data[args.Key]
  33. if ok {
  34. reply.Err = OK
  35. reply.Value = val
  36. } else {
  37. reply.Err = ErrNoKey
  38. reply.Value = ""
  39. }
  40. return nil
  41. }
  42. func (kv *KV) Put(args *PutArgs, reply *PutReply) error {
  43. kv.mu.Lock()
  44. defer kv.mu.Unlock()
  45. kv.data[args.Key] = args.Value
  46. reply.Err = OK
  47. return nil
  48. }

4.3 rpc怎么处理失败

问题:

  • 网络延迟
  • 丢包
  • 服务器慢或者崩溃

处理办法:

  • best effort:

    • client调用call( ) 等待响应, 如果过了一会没收到响应那就再发送一个call( )
    • 这个过程重复几次,然后放弃并且返回一个错误
  • at most once:
    • 针对服务端说的:当服务端收到相同的请求时

      • 根据xid(client id 判断)如果收到相同请求 返回之前的处理结果
      • xid 怎么保证唯一性
  • exactly once:
    • 无限重试
    • 冗余检查
    • 容错服务

【MIT 6.824 】分布式系统 课程笔记(一)的更多相关文章

  1. 【MIT 6.824 】分布式系统 课程笔记(二)Lecture 03 : GFS

    Lecture 03 : GFS 一.一致性 1, 弱一致性 可能会读到旧数据 2, 强一致性 读到的数据都是最新的 3, 一致性比较 强一致性对于app的写方便, 但是性能差 弱一致性有良好的性能, ...

  2. MIT 6.824 lab1:mapreduce

    这是 MIT 6.824 课程 lab1 的学习总结,记录我在学习过程中的收获和踩的坑. 我的实验环境是 windows 10,所以对lab的code 做了一些环境上的修改,如果你仅仅对code 感兴 ...

  3. MIT 6.824(Spring 2020) Lab1: MapReduce 文档翻译

    首发于公众号:努力学习的阿新 前言 大家好,这里是阿新. MIT 6.824 是麻省理工大学开设的一门关于分布式系统的明星课程,共包含四个配套实验,实验的含金量很高,十分适合作为校招生的项目经历,在文 ...

  4. CS231n课程笔记翻译9:卷积神经网络笔记

    译者注:本文翻译自斯坦福CS231n课程笔记ConvNet notes,由课程教师Andrej Karpathy授权进行翻译.本篇教程由杜客和猴子翻译完成,堃堃和李艺颖进行校对修改. 原文如下 内容列 ...

  5. MIT 6.828 JOS学习笔记2. Lab 1 Part 1.2: PC bootstrap

    Lab 1 Part 1: PC bootstrap 我们继续~ PC机的物理地址空间 这一节我们将深入的探究到底PC是如何启动的.首先我们看一下通常一个PC的物理地址空间是如何布局的:        ...

  6. MIT 6.828 JOS学习笔记0. 写在前面的话

    0. 简介 操作系统是计算机科学中十分重要的一门基础学科,是一名计算机专业毕业生必须要具备的基础知识.但是在学习这门课时,如果仅仅把目光停留在课本上一些关于操作系统概念上的叙述,并不能对操作系统有着深 ...

  7. Linux内核分析课程笔记(一)

    linux内核分析课程笔记(一) 冯诺依曼体系结构 冯诺依曼体系结构实际上就是存储程序计算机. 从两个层面来讲: 从硬件的角度来看,冯诺依曼体系结构逻辑上可以抽象成CPU和内存,通过总线相连.CPU上 ...

  8. (1/18)重学Standford_iOS7开发_iOS概述_课程笔记

    写在前面:上次学习课程对iOS还是一知半解,由于缺乏实践,看公开课的视频有时不能很好地领会知识.带着问题去学习永远是最好的方法,接触一段时间iOS开发以后再来看斯坦福iOS公开课,又会有许多新的发现, ...

  9. Andrew Ng机器学习课程笔记(五)之应用机器学习的建议

    Andrew Ng机器学习课程笔记(五)之 应用机器学习的建议 版权声明:本文为博主原创文章,转载请指明转载地址 http://www.cnblogs.com/fydeblog/p/7368472.h ...

随机推荐

  1. js 中数组对象的定义赋值 以及方法

    1.定义数组 var m=new Array(); var n=[]; 2.数组的赋值(两种) A. var m=new Array(2); 一个值表示数组length var m=new Array ...

  2. ICEM-蜗壳

    原视频下载地址:https://yunpan.cn/cY8XxpyLN4QaE  访问密码 a792

  3. Spring|IOC启动流程

    1.IOC启动流程 IOC的启动流程分为两个阶段,第一阶段是容器的启动阶段,第二阶段是Bean实例化阶段. 容器的启动阶段:加载配置信息,分析配置信息,其他 Bean实例化阶段:实例化对象,装配依赖, ...

  4. ubuntu之路——day11.3 不匹配数据划分的偏差和方差

    在11.2中,我们提到了一种数据划分的方法,那么怎么衡量这种数据划分方法中的误差呢? 来看一个例子:有20w条各种领域的语音识别数据,2w条汽车语音定位数据 train+dev+test,其中trai ...

  5. 第十五周助教工作总结——NWNU李泓毅

    助教博客链接:https://www.cnblogs.com/NWNU-LHY/ 本次作业的要求:团队项目需求改进与系统设计:https://www.cnblogs.com/nwnu-daizh/p/ ...

  6. CentOs7设置主机名称,以及主机名称和ip的对应关系

    一.修改主机名称 在CentOS7中有三种定义的主机名:静态的(static).瞬态的(transient).和灵活的(pretty).静态主机名也称为内核主机名,是系统在启动时从/etc/hostn ...

  7. 自定义alert弹框,title不显示域名(重写alert)

    问题: 系统默认的alert弹框的title会默认显示网页域名 解决办法: (修改弹框样式) (function() { window.alert = function(name) { $(" ...

  8. sass - for循环写法

    如要设置多个li的动画延迟时间时 注:这里选择器要加#{}才行 不然就会编译成: 6.7. 插值语句 #{} (Interpolation: #{}) 通过 #{} 插值语句可以在选择器或属性名中使用 ...

  9. 前端通用下载文件方法(兼容IE)

    之前在网上看到一个博主写的前端通用的下载文件的方法,个人觉得很实用,所以mark一下,方便以后查阅 源文地址(源文还有上传/下载excel文件方法) 因为项目要求要兼容IE浏览器,所以完善了一下之前博 ...

  10. springboot配置Filter的两种方法

    一.使用注解1. 假设Filter类的路径为com.sanro.filter @Slf4j @WebFilter(filterName = "authFilter", urlPat ...