建设厅八大员报名网站,网站培训班有哪些课程,网站右侧固定标题怎么做,企业网站模板N002.02 Go分布式爬虫实战
开篇
学习三阶段
入门#xff0c;照猫画虎底层#xff0c;了解方方面面#xff0c;深入阅读源码和书籍借助开源组件来进行复杂设计#xff0c;窥探各个组件赋能业务
分布式系统#xff1a;
扩展性一致性可用性高并发微服务
爬虫#xff1…N002.02 Go分布式爬虫实战
开篇
学习三阶段
入门照猫画虎底层了解方方面面深入阅读源码和书籍借助开源组件来进行复杂设计窥探各个组件赋能业务
分布式系统
扩展性一致性可用性高并发微服务
爬虫
前端数据解析数据存储数据可视化分析高并发
项目流程
需求
项目特点
故障容错扩展性领域驱动微服务
微服务治理
个人展望
Go基础巩固分布式、微服务入门代码更加优美工程化管理
项目启动篇
谷歌软件工程服务中的语言设计
语言特性 面向组合而不是继承鸭子类型 并发原语协程、通道 简单健壮性 没有隐式的数值转换没有指针运算运行时会检查数组的边界垃圾回收。内存逃逸这意味着我们可以传递栈上变量的地址而这在 C 语言中会产生类似野指针的问题。 强大丰富的标准库与工具集
开发环境
Go语言环境GOPATH、GOMOD、GOPROXYVS Code、Goland、Vim环境切换Goland有同时你可以配合docker或wsl进行切换到linux项目模板快捷键GO命令build、clean、fmt、genernate、get、list、mod、run、test、tool、install、work
基础语法 变量与类型 声明与赋值内置类型命名规则生命周期作用域 表达式与运算符 算术运算符 关系运算符 逻辑运算符 位运算符 赋值运算符 地址运算符 优先级 优先级(由高到低) 操作符5 * / % ^4 - | ^3 ! 2 1 ||基本控制结构 if elseswitchforC风格、条件、loop、range 函数 声明多返回值可变参数递归函数参数函数返回值函数值 复合类型 数组 切片 声明与赋值添加截取底层删除 哈希表 声明、初始化访问赋值删除 结构体 声明与赋值匿名结构体测试和序列化可比较性
假设我们需要学习一门新的语言你认为我们需要把握哪些核心的知识又应该沿着什么样的路径去学习呢
设计之初、语法、特性、、调试、编程模式库小项目开源项目
语法特性 defer 资源释放panic捕获延迟执行参数预计算LIFO 接口 空接口定义、声明实现调用组合断言动态类型 v.(type)比较 并发 协程 通道 声明、初始化读写关闭作为参数作为返回值单方向的通道用于只读和只写场景select随机执行 context 协程优雅退出级联退出 原子锁atomic 互斥锁 读写锁适合多读少写场景。 sync.Once、sync.Cond、sync.WaitGroup 项目组织 依赖管理gomod 组合 工具与库 编辑测试编译部署调试分析 工具代码分析与代码规范 静态检查代码的结构、代码风格以及语法错误这种工具也被称为 Linter。 go fmtgo docgo vetgo analysisgolangci-lintgo race 测试 go testgo test -covergo tool covergo test -bench 调试 dlvgdbpproftrace我们在 pprof 的分析中能够知道一段时间内 CPU 的占用、内存分配、协程堆栈等信息。这些信息都是一段时间内数据的汇总但是它们并没有提供整个周期内事件的全貌。例如指定的协程何时执行执行了多长时间什么时候陷入了堵塞什么时候解除了堵塞GC 是如何影响协程执行的STW 中断花费的时间有多长等。而 Go1.5 之后推出的 trace 工具解决了这些问题。trace 的强大之处在于提供了程序在指定时间内发生的事件的完整信息让我们可以精准地排查出程序的问题所在在后面的课程中还会用 trace 完成对线上实战案例的分析。gopsgops 是谷歌推出的调试工具它的作用是诊断系统当前运行的 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 的架构。
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当前节点会先尝试连接集群中的其他节点如果能够成功连接到半数以上的节点才开始新一轮的选举。 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,nametype json:typeCrc uint32 protobuf:varint,2,opt,namecrc json:crcData []byte protobuf:bytes,3,opt,namedata 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 机制与高性能的并发处理相对于 etcdv2etcdv3 版本将所有键值对的历史版本都存储了起来这就让 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 {10}
revision {11}
revision {20}
revision {21}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正是基于此实现的。
MVCCMulti-Version Concurrency Control即多版本并发控制。MVCC 是一种并发控制的方法可以实现对数据库的并发访问。 MySQL的MVCC工作在RC(读提交)和RR(重复读)的隔离级别。表的行记录逻辑上是一个链表既保留业务数据本身还有两个隐藏字段 trx_id最近修改的事务IDroll_ptr(指向上一个版本数据的指针,通过undo log可以实现从高版本到低版本的迁跃) 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 -wjson
{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 -wjson
{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 -wjson
{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}写流程
客户端通过 GRPC 协议访问 etcd-server 服务端。如果是一个写请求会访问 etcd-server 注册的 Put 方法。要注意的是在访问 etcd-server 时会进行一些检查例如 DB 配额Quota的检查。此外如果客户端访问的节点不是 Leader 节点这个节点会将请求转移到 Leader 中。etcd-server 会调用 raft-node 模块的 Propose 方法进行限速、鉴权等判断之后 raft-node 模块调用 etcd-raft 模块完成数据的封装。接着etcd-raft 模块会将封装后的数据返回给 raft-node 模块。raft-node 模块调用 storage 存储模块将本次操作对应的 Entry 记录存储到 WAL 日志文件当中。raft-node 模块将当前 Entry 广播给集群中的其他节点snap 模块还会在适当时候保存当前数据的快照。当 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 操作进行事务提交如果这个时候节点崩溃了如何保证数据不丢失呢
读流程
首先客户端通过 GRPC API 访问 etcd-server 服务端这一阶段会经过注册到 GRPC 服务器中的拦截器实现日志打印、Metric 统计等功能。读操作调用的是 etcd-server 的 Range 方法etcd-server 会判断当前的请求是否需要线性一致性的读。对于线性一致性读etcd-server 会调用 raft-node 模块的 ReadIndex 方法。raft-node 模块在 etcd-raft 模块的帮助下请求 Leader 节点获取 Leader 节点中当前最新的 Commit Index。etcd-raft 模块将 Leader 返回的 Commit Index 传递给上游模块 etcd-server 模块。读取协程会陷入到等待的状态一直到当前状态机已经执行的 Apply Index 追赶上当前最新的 Commit Index 为止。一旦 Apply Index 追赶上 Leader 的 Commit Index 就意味着当前我们读取到的数据一定是在最后一次写入操作之后这就保证了读的强一致性。接着 etcd-server 会在 treeIndex 这个 B 树中得到当前请求中 Key 的最新的版本号也可以在请求中指定读取的版本号和范围。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表】的原因
hash表不支持范围查询hash表可能有hash碰撞的问题(Hash_fn(k1) Hash_fn(key2),还需要使用其他方法进行进一步处理(如:拉链法)hash表不支持排序hash表不支持key的前缀索引,prefixxxx,想必是用不了;
不使用【二叉树】的原因:
二叉树造成树的层次太高,查找的时候可能造成磁盘IO的次数较多,性能不好.如果这个时候节点崩溃了如何保证数据不丢失呢 应该是通过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.goetcd go 客户端应用
选主API实现
推荐资料
《etcd 技术内幕》《Go 语言底层原理剖析》GO语言开挂入门之旅《Structure and Interpretation of Computer Programs》EffectiveGo 「此文章为3月Day9学习笔记内容来源于极客时间《Go分布式爬虫实战》强烈推荐该课程/推荐该课程」