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

Go爬虫学习笔记

N002.02 Go分布式爬虫实战

开篇

学习三阶段

  • 入门,照猫画虎
  • 底层,了解方方面面,深入阅读源码和书籍
  • 借助开源组件来进行复杂设计,窥探各个组件
  • 赋能业务

分布式系统:

  • 扩展性
  • 一致性
  • 可用性
  • 高并发
  • 微服务

爬虫:

  • 前端
  • 数据解析
  • 数据存储
  • 数据可视化分析
  • 高并发

项目流程:

  • 需求
  • image

项目特点:

  • 故障容错
  • 扩展性
  • 领域驱动微服务

微服务治理:

image

image

个人展望:

  • Go基础巩固
  • 分布式、微服务入门
  • 代码更加优美
  • 工程化管理

项目启动篇

谷歌:软件工程服务中的语言设计

语言特性:

  • 面向组合而不是继承,鸭子类型

  • 并发原语:协程、通道

  • 简单健壮性

    • 没有隐式的数值转换
    • 没有指针运算
    • 运行时会检查数组的边界
    • 垃圾回收。
    • 内存逃逸,这意味着我们可以传递栈上变量的地址,而这在 C 语言中会产生类似野指针的问题。
  • 强大丰富的标准库与工具集

开发环境

  • Go语言环境:GOPATH、GOMOD、GOPROXY
  • VS Code、Goland、Vim
  • 环境切换(Goland有,同时你可以配合docker或wsl进行切换到linux)
  • 项目模板
  • 快捷键
  • GO命令:build、clean、fmt、genernate、get、list、mod、run、testtoolinstallwork

基础语法

  • 变量与类型

    • 声明与赋值
    • 内置类型
    • 命名规则
    • 生命周期
    • 作用域
  • 表达式与运算符

    • 算术运算符

    • 关系运算符

    • 逻辑运算符

    • 位运算符

    • 赋值运算符

    • 地址运算符

    • 优先级

        优先级(由高到低)              操作符5                *  /  %  <<  >>  &  &^4                +  -  |  ^3                ==  !=  <  <=  >  >=2                &&1                ||
      
  • 基本控制结构

    • if else
    • switch
    • for:C风格、条件、loop、range
  • 函数

    • 声明
    • 多返回值
    • 可变参数
    • 递归
    • 函数参数
    • 函数返回值
    • 函数值
  • 复合类型

    • 数组

    • 切片

      • 声明与赋值
      • 添加
      • 截取,底层
      • 删除
    • 哈希表

      • 声明、初始化
      • 访问
      • 赋值
      • 删除
    • 结构体

      • 声明与赋值
      • 匿名结构体:测试和序列化
      • 可比较性

假设我们需要学习一门新的语言,你认为我们需要把握哪些核心的知识,又应该沿着什么样的路径去学习呢?

  • 设计之初、语法、特性、、调试、编程模式
  • 小项目
  • 开源项目

语法特性

  • defer

    • 资源释放
    • panic捕获
    • 延迟执行
    • 参数预计算
    • LIFO
  • 接口

    • 空接口
    • 定义、声明
    • 实现
    • 调用
    • 组合
    • 断言
    • 动态类型 v.(type)
    • 比较
  • 并发

    • 协程

    • 通道

      • 声明、初始化
      • 读写
      • 关闭
      • 作为参数
      • 作为返回值
      • 单方向的通道,用于只读和只写场景
      • select,随机执行
    • context

      • 协程优雅退出
      • 级联退出
    • 原子锁:atomic

    • 互斥锁

    • 读写锁:适合多读少写场景。

    • sync.Once、sync.Cond、sync.WaitGroup

  • 项目组织

  • 依赖管理:gomod

  • 组合

  • 工具与库

    • 编辑
    • 测试:
    • 编译
    • 部署:
    • 调试
    • 分析
  • 工具:代码分析与代码规范

    • 静态:检查代码的结构、代码风格以及语法错误,这种工具也被称为 Linter。

      • go fmt
      • go doc
      • go vet
      • go analysis
      • golangci-lint
      • go race
  • 测试

    • go test
    • go test -cover
    • go tool cover
    • go test -bench
  • 调试

    • dlv
    • gdb
    • pprof
    • trace:我们在 pprof 的分析中,能够知道一段时间内 CPU 的占用、内存分配、协程堆栈等信息。这些信息都是一段时间内数据的汇总,但是它们并没有提供整个周期内事件的全貌。例如,指定的协程何时执行,执行了多长时间,什么时候陷入了堵塞,什么时候解除了堵塞,GC 是如何影响协程执行的,STW 中断花费的时间有多长等。而 Go1.5 之后推出的 trace 工具解决了这些问题。trace 的强大之处在于,提供了程序在指定时间内发生的事件的完整信息,让我们可以精准地排查出程序的问题所在,在后面的课程中,还会用 trace 完成对线上实战案例的分析。
    • gops:gops 是谷歌推出的调试工具,它的作用是诊断系统当前运行的 Go 进程。gops 可以显示出当前系统中所有的 Go 进程,并可以查看特定进程的堆栈信息、内存信息等。
  • 标准库

  • 三方库

你如何理解 Go 语言的一句名言:“不要通过共享内存来通信,通过通信来共享内存”?

  • 共享内存

    • 需要额外的控制机制来确保通信内存中内容的正确性。
    • 容易出bug
  • 通信

    • 一定时正确的,只要等待即可
    • 减少bug
    • 通道一种所有权转移的机制,为我们屏蔽了锁等机制。 通过一些简单的并发模型(例如fan-in、fan-out),开发并行程序会变得非常容易

通过共享内存通信相当于双方必须依靠额外的控制机制来确保通信时内存中的内容是正确的,这一点需要共享双方设置同步机制,并不通用, 还容易有bug。但是通过通信共享内存则可以利用通用的通信基建, 凡是经过通信传递的信息一定是发送方确认正确的, 接收方只需要等待即可, 不用再依赖额外的同步机制,减少了出bug的机会。

并发编程

项目组织

工具与库

master开发与原理篇

etcd

etcd etcd doc #etcd#​

简介

etcd 这个名字是 etc distributed 的缩写。我们知道,在 Linux 中 etc 目录存储了系统的配置文件,所以 etcd 代表了分布式的配置中心系统。然而,它能够实现的功能远不是同步配置文件这么简单。etcd 可以作为分布式协调的组件帮助我们实现分布式系统。

架构

etcd 的第一个版本 v0.1 于 2013 年发布,现在已经更新到了 v3,在这个过程中,etcd 的稳定性、扩展性、性能都在不断提升。我们先来从整体上看一看 etcd 的架构。

image

etcd 从大的方面可以分为几个部分,让我们结合图片从右往左说起。

  • raft-http 模块:由于 etcd 通常为分布式集群部署方式,该层用于处理和其他 etcd 节点的网络通信。etcd 内部使用了 HTTP 协议来进行通信,由于 etcd 中的消息类型很多,心跳探活的数据量较小,快照信息较大(可达 GB 级别),所以 etcd 有两种处理消息的通道,分别是 Pipeline 消息通道与 Stream 消息通道。​#raft#​

    • Stream 消息通道是 etcd 中节点与节点之间维护的长连接,它可以处理频繁的小消息,还能复用 HTTP 底层的连接,不必每次都建立新的连接。
    • Pipeline 消息通道用于处理快照这样数据量大的信息,处理完毕后连接会关闭。
  • etcd-raft 模块:它是 etcd 的核心。该层实现了 Raft 协议,可以完成节点状态的转移节点的选举数据处理等重要功能,确保分布式系统的一致性故障容错性。之前我们也介绍过,Raft 中的节点有 3 种状态,分别是 Leader(领导者),Candidates(候选人)和 Follower(跟随者)。在此基础上,etcd 为 Raft 节点新增了一个 PreCandidate(预候选人)。
    我们在讲解 Raft 协议时介绍过,如果节点收不到来自 Leader 的心跳检测,就会变为 Candidates 开始新的选举。如果当前节点位于不足半数的网络分区中,短期内不会影响集群的使用,但是当前节点在不断发起选举的过程中,当前选举周期的 Term 号会不断增长,当网络分区消失后,由于该节点的 Term 号高于当前集群中 Leader 节点的 Term 号,Raft 协议就会迫使当前的 Leader 切换状态并开始新一轮的选举。
    但是,这种选举是没有意义的。为了解决这样的问题,etcd 在选举之前会有一个新的阶段叫做 PreVote,当前节点会先尝试连接集群中的其他节点,如果能够成功连接到半数以上的节点,才开始新一轮的选举。
    image

  • raft-node 模块:在 etcd-raft 模块基础之上还封装,这个模块主要是上层模块和下层 Raft 模块沟通的桥梁,它同时还有一个重要任务,就是调用 storage 模块,将记录(Record)存储到 WAL ​日志文件中落盘。WAL 日志文件可以存储多种类型的记录,包括下面几种。
    WAL 日志文件非常重要,它能保证我们的消息在大部分节点达成一致且应用到状态机之前,让记录持久化。这样,在节点崩溃并重启后,就能够从 WAL 中恢复数据了。​#wal#​
    WAL 日志的数量与大小随着时间不断增加,可能超过可容纳的磁盘容量。同时,在节点宕机后,如果要恢复数据就必须从头到尾读取全部的 WAL 日志文件,耗时也会非常久。为了解决这一问题,etcd 会定期地创建快照并保存到文件中,在恢复节点时会先加载快照数据,并从快照所在的位置之后读取 WAL 文件,这就加快了节点的恢复速度。快照的数据也有单独的 snap 模块进行管理。

    • WAL 文件的元数据,记录节点 ID、集群 ID 信息。

    • Entry 记录,即客户端发送给服务器处理的数据。

    • 集群的状态信息,包含集群的任期号、节点投票信息。

    • 数据校验信息,它可以校验文件数据的完整性与正确性。

    • 快照信息,包含快照的相关信息,但不包含实际的快照数据。它可以校验快照数据的完整性。

      type Record struct {Type                 int64    `protobuf:"varint,1,opt,name=type" json:"type"`Crc                  uint32   `protobuf:"varint,2,opt,name=crc" json:"crc"`Data                 []byte   `protobuf:"bytes,3,opt,name=data" json:"data,omitempty"`
      }
      
  • **etcd-server 模块:**它最核心的任务是执行 Entry 对应的操作,在这个过程中包含了限流操作与权限控制的能力。所有操作的集合最终会使状态机到达最新的状态。etcd-server 同时还会维护当前 etcd 集群的状态信息,并提供了线性读的能力。
    etcd-server 提供了一些供外部访问的 GRPC API 接口,同时 etcd 也使用了 GRPC-gateway 作为反向代理,使服务器也有能力对外提供 HTTP 协议。

  • 最后,etcd 还提供了客户端工具 etcdctl 和 clientv3 代码库,使用 GRPC 协议与 etcd 服务器交互。客户端支持负载均衡、节点间故障自动转移等机制,极大降低了业务使用 etcd 的难度,提升了开发的效率。

  • 此外,etcd 框架中还有一些辅助的功能,例如权限管理、限流管理、重试、GRPC 拦截器等。由于不是核心点,图中并没有一一列举出来。

优点

  • 高内聚:从 etcd 整体的架构图上可以看出,etcd 将相关的核心功能(例如鉴权、网络、Raft 协议)都聚合起来形成了一个单独的模块。功能之间联系紧密,并且只提供核心的接口与外部进行交互。这非常便于理解与开发,也便于后期对功能进行组合。

  • 低耦合:各个模块之间边界清晰,用接口来进行交流与组合的设计给了程序极大的扩展性。举一个例子,数据存储的 store 就是一个 interface。

    
    type Storage interface {InitialState() (pb.HardState, pb.ConfState, error)Entries(lo, hi, maxSize uint64) ([]pb.Entry, error)Term(i uint64) (uint64, error)LastIndex() (uint64, error)FirstIndex() (uint64, error)Snapshot() (pb.Snapshot, error)
    }
    

在 etcd 代码库中有一个示例代码,该示例代码基于 etcd-raft 模块实现了一个最简单的分布式内存 KV 数据库。在示例代码中实现了上游的 KVServer 服务器与 raft-node 节点,并与 etcd-raft 模块进行交互,去掉了 etcd 实现的日志落盘等逻辑,将键值对存储到了内存中。如果你有志于深入地学习 etcd,从这个实例入手是非常不错的选择。

  • 优雅的数据同步:在 etcd 中,我们极少看到使用互斥锁的场景。更多的时候,它是借助协程与通道的相互配合来传递信息的,这就既完成了通信又优雅地解决了并发安全问题。

  • 更快的读取性能:etcd 在 etcd-raft 模块中实现了 Raft 协议。我们知道 Raft 并不能够保证读取的线性一致性,也就是说,它有可能读取到过时的数据。

    怎么解决呢?办法有很多。

    • Follower 可以将读请求直接转发给 Leader,不过这样的话 Leader 的压力会很大,并且 Leader 可能已经不是最新的 Leader 了。
    • 第二种解决方案是 etcdv3.2 之前的处理方式。也就是将该请求记录为一个 Entry,从而借助 Raft 机制来保证读到的数据是最新的。
    • 还有一种更轻量级的方法。在 v3.2 之后,etcd 实现了 ReadIndex 机制,这也是在 Raft 论文当中提到过的。Follower 向 Leader 读取当前最新的 Commit Index,同时 Leader 需要确保自己没有被未知的新 Leader 取代。它会发出新一轮的心跳,并等待集群中大多数节点的确认。一旦收到确认信息,Leader 就知道在发送心跳信息的那一刻,不可能存在更新的 Leader 了。也就是说,在那一刻,ReadIndex 是集群中所有节点见过的最大的 Commit Index。Follower 会在自己的状态机上将日志至少执行到该 Commit Index 之后,然后查询当前状态机生成的结果,并返回结果给客户端。
  • 可靠的 Watch 机制与高性能的并发处理:相对于 etcdv2,etcdv3 版本将所有键值对的历史版本都存储了起来,这就让 Watch 机制的可靠性更高了,它实现的 MVCC 机制也提高了系统处理并发请求的数量。

MVCC

​#mvcc#​

etcd 存储了当前 Key 过去所有操作的版本记录。这样做的好处是,我们可以很方便地获取所有的操作记录,而这些记录常常是实现更重要的特性的基础,例如要实现可靠的事件监听就需要 Key 的历史信息。

etcd v2 会在内存中维护一个较短的全局事件滑动窗口,保留最近的 1000 条变更事件。但是当事件太多的时候,就需要删除老的事件,可能导致事件的丢失。

etcd v3 解决了这一问题,etcd v3 将历史版本存储在了 BoltDB 当中进行了持久化。可靠的 Watch 机制将避免客户端执行一些更繁重的查询操作,提高了系统的整体性能。

借助 Key 的历史版本信息,我们还能够实现乐观锁的机制。 乐观锁即乐观地认为并发操作同一份数据不会发生冲突,所以不会对数据全局加锁。但是当事务提交时,她又能够检测到是否会出现数据处理异常。乐观锁的机制让系统在高并发场景下仍然具备高性能。这种基于多版本技术实现的乐观锁机制也被称为 MVCC。

下面就让我们来看看 etcd 是如何实现 MVCC 机制,对多版本数据的管理与存储的吧。在 etcd 中,每一个操作不会覆盖旧的操作,而是会指定一个新的版本,其结构为 revision。


type revision struct {main int64sub  int64
}

revision 主要由两部分组成,包括 main 与 sub 两个字段。其中每次出现一个新事务时 main 都会递增 1,而对于同一个事务,执行事务中每次操作都会导致 sub 递增 1,这保证了每一次操作的版本都是唯一的。假设事务 1 中的两条操作分别如下。

key = "zjx"    value = "38"
key = "olaya"  value = "19"

事务 2 中的两条操作是下面的样子。

key = "zjx"    value = "56"
key = "olaya"  value = "22"

那么每条操作对应的版本号就分别是下面这样。

revision = {1,0}
revision = {1,1}
revision = {2,0}
revision = {2,1}

etcd 最终会默认将键值对数据存储到 BoltDB 当中,完成数据的落盘。不过为了管理多个版本,在 BoltDB 中的 Key 对应的是 revision 版本号,而 Value 对应的是该版本对应的实际键值对。BoltDB 在底层使用 B+ 树进行存储,而 B+ 树的优势就是可以实现范围查找,这有助于我们在读取数据以及实现 Watch 机制的时候,查找某一个范围内的操作记录。

看到这里你可能会有疑问,在 BoltDB 中存储的 key 是版本号,但是在用户查找的时候,可能只知道具体数据里的 Key,那如何完成查找呢?

为了解决这一问题,etcd 在内存中实现了一个 B 树的索引 treeIndex,封装了Google 开源的 B 树的实现。B 树的存储结构方便我们完成范围查找,也能够和 BoltDB 对应的 B+ 树的能力对应起来。treeIndex 实现的索引,实现了数据 Key 与 keyIndex 实例之间的映射关系,而在 keyIndex 中存储了当前 Key 对应的所有历史版本信息。 通过这样的二次查找,我们就可以通过 Key 查找到 BoltDB 中某一个版本甚至某一个范围的 Value 值了。

借助 etcd 的 MVCC 机制以及 BoltDB 数据库,我们可以在 etcd 中实现事务的 ACID 特性。etcd clientv3 中提供的简易事务 API正是基于此实现的。

  1. MVCC(Multi-Version Concurrency Control),即多版本并发控制。MVCC 是一种并发控制的方法,可以实现对数据库的并发访问。

  2. MySQL的MVCC工作在RC(读提交)和RR(重复读)的隔离级别。
    表的行记录逻辑上是一个链表,既保留业务数据本身,还有两个隐藏字段:

    • trx_id(最近修改的事务ID)
    • roll_ptr(指向上一个版本数据的指针,通过undo log可以实现从高版本到低版本的迁跃)
  3. ETCD的MVCC同样可以维护一个数据(key对应的值)的多个历史版本,且使得读写操作没有冲突,不使用锁,增加系统吞吐。

窥探etcd对同一个key进行修改,内部版本的变化

> docker exec etcd-gcr-v3.5.5 /bin/sh -c "/usr/local/bin/etcdctl put a 1 "
OK> docker exec etcd-gcr-v3.5.5 /bin/sh -c "/usr/local/bin/etcdctl get a -w=json"
{"header":{"cluster_id":18011104697467366872,"member_id":6460912315094810421,"revision":22,"raft_term":3},"kvs":[{"key":"YQ==","create_revision":22,"mod_revision":22,"version":1,"value":"MQ=="}],"count":1}> docker exec etcd-gcr-v3.5.5 /bin/sh -c "/usr/local/bin/etcdctl put a 2 "
OK> docker exec etcd-gcr-v3.5.5 /bin/sh -c "/usr/local/bin/etcdctl get a -w=json"
{"header":{"cluster_id":18011104697467366872,"member_id":6460912315094810421,"revision":23,"raft_term":3},"kvs":[{"key":"YQ==","create_revision":22,"mod_revision":23,"version":2,"value":"Mg=="}],"count":1}> docker exec etcd-gcr-v3.5.5 /bin/sh -c "/usr/local/bin/etcdctl put a 3 "
OK> docker exec etcd-gcr-v3.5.5 /bin/sh -c "/usr/local/bin/etcdctl get a -w=json"
{"header":{"cluster_id":18011104697467366872,"member_id":6460912315094810421,"revision":24,"raft_term":3},"kvs":[{"key":"YQ==","create_revision":22,"mod_revision":24,"version":3,"value":"Mw=="}],"count":1}

写流程

image

  1. 客户端通过 GRPC 协议访问 etcd-server 服务端。
  2. 如果是一个写请求,会访问 etcd-server 注册的 Put 方法。要注意的是,在访问 etcd-server 时,会进行一些检查,例如 DB 配额(Quota)的检查。此外,如果客户端访问的节点不是 Leader 节点,这个节点会将请求转移到 Leader 中。
  3. etcd-server 会调用 raft-node 模块的 Propose 方法进行限速、鉴权等判断,之后 raft-node 模块调用 etcd-raft 模块完成数据的封装。
  4. 接着,etcd-raft 模块会将封装后的数据返回给 raft-node 模块。
  5. raft-node 模块调用 storage 存储模块,将本次操作对应的 Entry 记录存储到 WAL 日志文件当中。
  6. raft-node 模块将当前 Entry 广播给集群中的其他节点,snap 模块还会在适当时候保存当前数据的快照。
  7. 当 Leader 最终收到了半数以上节点的确认时,该 Entry 的状态会变为 committed ,这时 etcd-raft 模块会将 Commit Index 返回上游,供 etcd-server 模块执行。后面我们还会看到,etcd-server 实现了 MVCC 机制,维护了某一个 Key 过去所有的版本记录。

etcd 状态机中的数据存储包含了两个部分:

  • 第一部分是内存索引叫做 treeIndex,用于存储 Key 与版本号之间的映射关系
  • 另一部分是数据的持久化存储,默认情况下,etcd 状态机的持久化存储选择的是 BoltDB 数据库(良好的接口设计让我们可以选择不同的存储引擎)。BoltDB 作为 KV 存储引擎,底层使用了 B+ 树,并且支持事务。etcd v3 提供的事务能力就是基于 BoltDB 的事务实现的。在 BoltDB 中存储的数据 Key 值其实是版本号,而 Value 值包括了原始请求中的键值对和相应的版本号。

另外,还要格外注意的是,客户端调用写入方法 Put 成功后,并不意味着数据已经持久化到 BoltDB 了。因为这时 etcd 并未提交事务,数据只更新在了 BoltDB 管理的内存数据结构中。BoltDB 事务提交的过程包含平衡 B+ 树、调整元数据信息等操作,因此提交事务是比较昂贵的。如果我们每次更新都提交事务,etcd 的写性能就会较差。为了解决这一问题,etcd 也有对策。etcd 会合并多个写事务请求,通常情况下定时机制会分批次(默认 100 毫秒 / 次)统一提交事务, 这就大大提高了吞吐量。

但是这种优化又导致了另一个问题。事务未提交时,读请求可能无法从 BoltDB 中获取到最新的数据。为了解决这个问题,etcd 引入了一个 Bucket Buffer 来保存暂未提交的事务数据。etcd 处理读请求的时候,会优先从 Bucket Buffer 里面读取,其次再从 BoltDB 中读取,通过 Bucket Buffer 提升读写性能,同时也保证了数据一致性。

写操作会调用 etcd Put 方法,调用 Put 方法结束时并未真正地执行 BoltDB 的 commit 操作进行事务提交,如果这个时候节点崩溃了,如何保证数据不丢失呢?

读流程

image

  1. 首先,客户端通过 GRPC API 访问 etcd-server 服务端,这一阶段会经过注册到 GRPC 服务器中的拦截器,实现日志打印、Metric 统计等功能。
  2. 读操作调用的是 etcd-server 的 Range 方法,etcd-server 会判断当前的请求是否需要线性一致性的读。
  3. 对于线性一致性读,etcd-server 会调用 raft-node 模块的 ReadIndex 方法。
  4. raft-node 模块在 etcd-raft 模块的帮助下请求 Leader 节点,获取 Leader 节点中当前最新的 Commit Index。
  5. etcd-raft 模块将 Leader 返回的 Commit Index 传递给上游模块 etcd-server 模块。
  6. 读取协程会陷入到等待的状态,一直到当前状态机已经执行的 Apply Index 追赶上当前最新的 Commit Index 为止。一旦 Apply Index 追赶上 Leader 的 Commit Index, 就意味着当前我们读取到的数据一定是在最后一次写入操作之后,这就保证了读的强一致性。
  7. 接着 etcd-server 会在 treeIndex 这个 B 树中,得到当前请求中 Key 的最新的版本号(也可以在请求中指定读取的版本号和范围)。
  8. etcd-server 最终在 BoltDB 中通过版本号查询到对应的 Value 值,并返回给客户端。

Watch机制

​#watch#​

etdc 支持监听某一个特定的 Key,也支持监听一个范围。etcdv3 的 MVCC 机制将历史版本都保存在了 BoltDB 中,避免了历史版本的丢失。 同时,etcdv3 还使用 GRPC 协议实现了客户端与服务器之间数据的流式传输。

那 etcd 服务端是如何实现 Watch 机制的呢?

当客户端向 etcd 服务器发出监听的请求时,etcd 服务器会生成一个 watcher。

etcd 会维护这些 watcher,并将其分为两种类型:synced 和 unsynced。

  • synced watcher 意味着当前 watcher 监听的最新事件都已经同步给客户端,接下来 synced watcher 陷入休眠并等待新的事件。
  • unsynced watcher 意味着当前 watcher 监听的事件并未完全同步到客户端。 etcd 会启动一个单独的协程帮助 unsynced watcher 进行追赶。 当 unsynced watcher 处理完最新的操作,将最新的事件同步到客户端之后,就会变为 synced watcher。

当 etcd 收到一个写请求,Key-Value 发生变化的时候,对应的 synced watcher 需要能够感知到并完成最新事件的推送。这一步主要是在 Put 事务结束时来做的。Put 事务结束后,会调用 watchableStore.notify,获取监听了当前 Key 的 watcher,然后将 Event 送入这些 watcher 的 Channel 中,完成最终的处理和发送。监听当前 Key 的 watcher 可能很多,你可能会想到用一个哈希表来存储 Key 与 watcher 的对应关系,但是这还不够,因为一个 watcher 可能会监听 Key 的范围和前缀。因此,为了能够高效地获取某一个 Key 对应的 watcher,除了使用哈希表,etdc 还使用了区间树结构来存储 watcher 集合。当产生一个事件时,etcd 首先需要从哈希表查找是否有 watcher 监听了该 Key,然后它还需要从区间树重找出满足条件的所有区间,从区间的值获取监听的 watcher 集合。

总结

  • etcd 完整的读写流程。在整个复杂的流程中,核心模块无外乎是 GRPC 请求、权限和参数的检查、WAL 日志的存储、Raft 节点的网络协调以及执行操作更新状态机的状态等。把握这些核心处理流程和模块,也就能理解 etcd 是如何实现一致性、容错性以及高性能的了。
  • etcd 存储实现了 MVCC 机制,保存了历史版本的所有数据。这种机制主要是依靠了内存索引 treeIndex 与后端存储 BoltDB,它不仅提高了 etcd 系统的并发处理能力,也为构建可靠的 Watch 机制和事务提供了基础。
  • etcd 将 watch 对象分为了 unsynced watcher 与 synced watcher,其中 synced watcher 表示最新事件已经同步给客户端,而 unsynced watcher 表示最新事件还未同步到客户端。etcd 在初始化时就建立了一个单独的协程完成 unsynced watcher 的追赶,通过范围查找,即便存在大量的 watcher,也能轻松应对。

treeIndex 的结构为什么是 B 树而不是哈希表或者是二叉树?

不使用【hash表】的原因:

  1. h​ash表不支持范围查询;
  2. hash表可能有hash碰撞的问题(Hash_fn(k1) = Hash_fn(key2),还需要使用其他方法进行进一步处理(如:拉链法);
  3. hash表不支持排序;
  4. hash表不支持key的前缀索引,prefix=xxx,想必是用不了;

不使用【二叉树】的原因:

  1. 二叉树造成树的层次太高,查找的时候,可能造成磁盘IO的次数较多,性能不好.
  2. 如果这个时候节点崩溃了,如何保证数据不丢失呢? 应该是通过WAL进行保障,先写日志在提交.

cobra

  • 如何使用cobra
  • 如何通过命令注入版本信息
  • flag、环境变量在环境中的应用

通过命令注入版本信息


// Makefile
LDFLAGS = -X "github.com/dreamerjackson/crawler/version.BuildTS=$(shell date -u '+%Y-%m-%d %I:%M:%S')"
LDFLAGS += -X "github.com/dreamerjackson/crawler/version.GitHash=$(shell git rev-parse HEAD)"
LDFLAGS += -X "github.com/dreamerjackson/crawler/version.GitBranch=$(shell git rev-parse --abbrev-ref HEAD)"
LDFLAGS += -X "github.com/dreamerjackson/crawler/version.Version=${VERSION}"build:go build -ldflags '$(LDFLAGS)' $(BUILD_FLAGS) main.go

etcd go 客户端应用

  • 选主API实现

推荐资料

  • 《etcd 技术内幕》
  • 《Go 语言底层原理剖析》
  • GO语言开挂入门之旅
  • 《Structure and Interpretation of Computer Programs》
  • EffectiveGo
    「此文章为3月Day9学习笔记,内容来源于极客时间《Go分布式爬虫实战》,强烈推荐该课程!/推荐该课程」

相关文章:

Go爬虫学习笔记

N002.02 Go分布式爬虫实战 开篇 学习三阶段 入门&#xff0c;照猫画虎底层&#xff0c;了解方方面面&#xff0c;深入阅读源码和书籍借助开源组件来进行复杂设计&#xff0c;窥探各个组件赋能业务 分布式系统&#xff1a; 扩展性一致性可用性高并发微服务 爬虫&#xff1…...

数据结构课程设计:高铁信息管理系统(C++实现)

目录 简介实验输出实验要求代码运行环境结语简介 Hello! 非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您指出~ ଘ(੭ˊᵕˋ)੭ 昵称:海轰 标签:程序猿|C++选手|学生 简介:因C语言结识编程,随后转入计算机专业,获得过国家奖学金,有幸在竞赛中拿过一些国奖…...

Python 模块之 datetime

datetime 对象格式化为字符串 标准转换格式符号&#xff1a; %a 本地星期的短名称 如&#xff1a;Sun, Mon, ..., Sat (en_US); So, Mo, ..., Sa (de_DE) %A 本地星期全名称 如 &#xff1a;Sunday, Monday, ..., Saturday (en_US);Sonntag, Montag, ..., Samstag (de_DE) %w…...

linux安装编译ffmpeg

ffmpeg下载&#xff1a;http://ffmpeg.org/releases可以下载适合自己的版本。我下载的是5.0版本&#xff0c;然后解压&#xff1a;tar xvf ffmpeg-5.0.tar.gz进入ffmpegcd ffmpeg-5.0编译ffmpeg./configure --prefix/root/ffmpeg //编译文件存放的路径如果是交叉编译添加以下参…...

嵌入式Linux驱动开发(二)LED驱动

1. Linux下LED驱动原理 与裸机区别在于&#xff0c;编写驱动要符合linux驱动框架规范。裸机直接对寄存器物理地址进行读写&#xff0c;linux下需要经过MMU。 1.1 地址映射相关概念 1&#xff09;MMU&#xff08;Memory Manage Unit - 内存管理单元&#xff09;&#xff1a; …...

C++学习

强制转换运算符 C 引入了四种不同的强制转换运算符以进行强制转换&#xff1a; const_caststatic_castreinterpret_castdynamic_cast C语言强制类型转换缺点&#xff1a; 主要是为了克服C语言强制类型转换的以下三个缺点。 没有从形式上体现转换功能和风险的不同。 例如&am…...

JavaEE简单示例——依赖注入

简单介绍&#xff1a; 首先我们要知道&#xff0c;依赖注入就是赋值&#xff0c;带着这句话去理解依赖注入就非常的简单了&#xff0c;将文中所有的依赖注入全部脑部替换成给属性赋值&#xff0c;再去理解依赖注入的概念。 依赖注入&#xff08;DI&#xff09;是指IoC容器在运…...

大数据框架之Hive: 第7章 综合案例练习(初级)

第7章 综合案例练习&#xff08;初级&#xff09; 一 环境准备 1.1 建表语句 hive> -- 创建学生表 DROP TABLE IF EXISTS student; create table if not exists student_info(stu_id string COMMENT 学生id,stu_name string COMMENT 学生姓名,birthday string COMMENT 出…...

kafka:linux 安装 kafka集群

kafka运行依赖于 jdk、zookeeper&#xff0c;kafka可视化工具选择kafka-eagle。所以要装的组件有&#xff1a;jdk、zookeeper、kafka、kafka-eagle一、安装jdk下载linux版本的jdk包&#xff0c;比如&#xff1a;jdk-8u192-linux-x64.tar.gz。将其复制到 /opt 目录下并解压&…...

springboot实现Hessian协议的RPC服务

背景 这段时间在公司接手了一个新项目&#xff0c;看到一段代码非常奇怪的&#xff0c;研究了好久。内容大概是这样 public void getUser (int id) {userService.getById(id); }当我点击这个方法进去的时候&#xff0c;我发现这个UserService类居然是导入jar包的一个接口&…...

2-6 SpringCloud快速开发入门: Eureka 服务注册中心发现与消费服务

接上一章节向Eureka 服务注册中心注册服务&#xff0c;这里讲讲Eureka 服务注册中心发现与消费服务 Eureka 服务注册中心发现与消费服务 我们已经搭建一个服务注册中心&#xff0c;同时也向这个服务注册中心注册了服务&#xff0c;接下来我们就可以发现和消费服务了&#xff0…...

Java-Web之s2-001与CommonsCollections

本文源自我个人入坑Java-Web安全的一点小经验&#xff0c;献给那些看得懂java代码但不知道从哪里入手代审的师傅们&#xff1a;&#xff09; Struts2之s2-001 环境配置 说说环境配置的问题&#xff0c;大多数人对漏洞复现的恐惧感还是来自于环境的配置&#xff0c;也许配了大…...

【JavaSE】数组的定义和使用(下)

数组的定义和使用&#xff08;下&#xff09;4. 数组练习4.1 模拟实现toString4.2 数组拷贝4.3 比较两个数组是否相同4.4 填充数组4.3 求数组中元素的平均值4.4 查找数组中指定元素&#xff08;顺序查找&#xff09;4.5 查找数组中指定元素&#xff08;二分查找&#xff09;4.6…...

Oracle 实现对全局错误语句的审计监控 ORA- alert

--将所有数据库ora-错误写入表create table error_tab (username varchar2(4000), d_current_nr_error varchar2(4000), ora_server_error_msg varchar2(4000), full_text varchar2(4000),errdate date);create or replace trigger error_triggerafter servererror on database…...

React解决样式冲突问题的方法

React解决样式冲突问题的方法 前言&#xff1a; 1、React最终编译打包后都在一个html页面中&#xff0c;如果在两个组件中取一样类名分别引用在自身&#xff0c;那么后者会覆盖前者。 2、默认情况下&#xff0c;只要导入了组件&#xff0c;不管组件有没有显示在页面中&#x…...

Go项目(用户操作微服务)

简介 用户留言、收藏、修改收货地址等&#xff0c;统一放在用户操作微服务这里按照业务类型划分微服务表设计&#xff0c;三张表// 用户收藏 type UserFav struct {BaseModel// 联合索引 idx_user_goods&#xff0c;且唯一User int32 gorm:"type:int;index:idx_user_goo…...

Spring Boot统一功能处理

目录 一、统一用户登录权限验证 1.1 自定义拦截器 1.2 将自定义拦截器加入到系统配置 1.3 统一访问前缀 二、统一异常处理 三、统一数据格式返回 一、统一用户登录权限验证 1.1 自定义拦截器 拦截器是一个普通的类&#xff0c;需要实现HandlerInterceptor接口并重写pre…...

ETCD多次出现CONTEXT DEADLINE EXCEEDED

roothqa-master-01:~# etcdctl --endpoints$ETCD_ENDPOINTS member list --write-outtable {“level”:“warn”,“ts”:“2020-03-23T14:19:45.0330800”,“caller”:“clientv3/retry_interceptor.go:61”,“msg”:“retrying of unary invoker failed”,“target”:“endpoi…...

git 提交 多人开发避免冲突

代码正常提交 git add . git commit -m ‘备注信息’ git status 查看本地提交状态 git pull 拉取代码 git push origin master 指定远程仓库名和分支名 ‘’ 如果多人开发 A和B 提交避免冲突 B拉取代码修改内容直接提交后 A也修改了内容在git add / git commit / git pull / g…...

求职复盘:干了四年外包出来,面试5次全挂

我的情况 大概介绍一下个人情况&#xff0c;男&#xff0c;毕业于普通二本院校非计算机专业&#xff0c;18年跨专业入行测试&#xff0c;第一份工作在湖南某软件公司&#xff0c;做了接近4年的外包测试工程师&#xff0c;今年年初&#xff0c;感觉自己不能够再这样下去了&…...

AXI总线核心解读---基于官方文档

AXI总线 何处使用AXI ZYNQ异构芯片&#xff0c;内部总线使用的AXI总线纯FPGA的IP接口也要用高速接口&#xff0c;DDR&#xff08;AXI、传统&#xff09;等模块都有涉及到 什么是AXI总线 AXI的三种形式: AXI-FULL&#xff1a;高性能的存储器映射需求—可以256个以内发送 存储器…...

【Linux修炼】15.进程间通信

每一个不曾起舞的日子&#xff0c;都是对生命的辜负。 进程间通信进程间通信一.理解进程间通信1.1 什么是通信1.2 为什么要有通信1.3 如何进行进程间通信二.管道2.1 匿名管道2.2 匿名管道编码部分2.3 管道的特点2.4 如何理解命令行中的管道2.5 进程控制多个子进程三.命名管道3.…...

每天一道大厂SQL题【Day15】微众银行真题实战(五)

每天一道大厂SQL题【Day15】微众银行真题实战(五) 大家好&#xff0c;我是Maynor。相信大家和我一样&#xff0c;都有一个大厂梦&#xff0c;作为一名资深大数据选手&#xff0c;深知SQL重要性&#xff0c;接下来我准备用100天时间&#xff0c;基于大数据岗面试中的经典SQL题&…...

如何优化查询大数据量的表

给你100万条数据的一张表&#xff0c;你将如何查询优化&#xff1f;1.两种查询引擎查询速度&#xff08;myIsam 引擎 &#xff09;InnoDB 中不保存表的具体行数&#xff0c;也就是说&#xff0c;执行select count(*) from table时&#xff0c;InnoDB要扫描一遍整个表来计算有多…...

卷麻了,00后Jmeter用的比我还熟练,简直没脸见人......

经常看到无论是刚入职场的新人&#xff0c;还是工作了一段时间的老人&#xff0c;都会对测试工具的使用感到困扰&#xff1f;前言性能测试是一个全栈工程师/架构师必会的技能之一&#xff0c;只有学会性能测试&#xff0c;才能根据得到的测试报告进行分析&#xff0c;找到系统性…...

力扣-树节点

大家好&#xff0c;我是空空star&#xff0c;本篇带大家了解一道中等的力扣sql练习题。 文章目录前言一、题目&#xff1a;608. 树节点二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.正确示范③提交SQL运行结果4.正确示范④提交SQL运行结果5.其他总结前言 …...

MySQL8启动错误“Neither found #innodb_redo subdirectory, nor ib_logfile* files”

今天做MySQL备份文件回复测试,用来检验MySQL备份文件可用性。 MySQL版本8.0.32 备份文件为腾讯云MySQL实例,版本8.0 使用xtrabackup恢复备份。执行过程顺利,启动MySQL时发生错误。提示如下: 注意,这里使用了systemctl stop mysql。虽然启动失败了,但是如果不执行这条…...

JVM系列——详细说明Volatile,原子性/可见性,先行发生原则

上篇我们讨论了JMM中的工作内存和主内存、内存直接的交互指令&#xff0c;以及指令之间的顺序规则。 本篇将会以上篇为基础&#xff0c;详细介绍并发编程中的三个重要概念/工具&#xff1a;Volatile、原子性/可见性和先行发生&#xff08;happens-before)原则。 volatile型变量…...

ArcGIS:栅格计算器的运算符和函数详解

01 栅格计算器在哪&#xff1f;02 运算符说明栅格计算器的表达式书写与Python语法一致&#xff08;由于其为解释型语言并且语言简洁优美&#xff0c;因此简单上手&#xff09;&#xff0c;这里主要简单说明各个运算符即可使用栅格计算器构建地图代数表达式以输出要求的栅格图像…...

spring的beanfactory与applicationContext的区别以及继承关系

applicationContext继承关系 首先可以看一张图 ListableBeanFactory 可列举的bean工厂 hierarchical 分层bean工厂 messageSource 国际化信息 //国际化&#xff08;internationalization&#xff09;是设计和…...