1
0
mirror of https://github.com/chai2010/advanced-go-programming-book.git synced 2025-05-24 04:22:22 +00:00

ch6 图片增加编号

This commit is contained in:
Xargin 2018-12-27 15:40:55 +08:00
parent 2dd023101c
commit 217820254c
4 changed files with 38 additions and 4 deletions

View File

@ -4,10 +4,12 @@
在插入数据库之前,我们需要给这些消息/订单先打上一个ID然后再插入到我们的数据库。对这个id的要求是希望其中能带有一些时间信息这样即使我们后端的系统对消息进行了分库分表也能够以时间顺序对这些消息进行排序。
Twitter的snowflake算法是这种场景下的一个典型解法。先来看看snowflake是怎么一回事
Twitter的snowflake算法是这种场景下的一个典型解法。先来看看snowflake是怎么一回事,见*图 6-1*
![snowflake](../images/ch6-snowflake.png)
*图 6-1 snowflake中的比特位分布*
首先确定我们的数值是64 位int64类型被划分为四部分不含开头的第一个bit因为这个bit是符号位。用41位来表示收到请求时的时间戳单位为毫秒然后五位来表示数据中心的id然后再五位来表示机器的实例id最后是12位的循环自增id(到达 1111 1111 1111 后会归 0)。
这样的机制可以支持我们在同一台机器上,同一毫秒内产生`2 ^ 12 = 4096`条消息。一秒共409.6万条消息。从值域上来讲完全够用了。
@ -49,6 +51,8 @@ mysql> select last_insert_id();
![ch6-snowflake-easy](../images/ch6-snowflake-easy.png)
*图 6-2 snowflake库*
和标准的snowflake完全一致。使用上比较简单
```go
@ -101,10 +105,12 @@ Epoch 就是本节开头讲的起始时间NodeBits指的是机器编号的位
### 6.1.2.2 sonyflake
sonyflake是Sony公司的一个开源项目基本思路和snowflake差不多不过位分配上稍有不同
sonyflake是Sony公司的一个开源项目基本思路和snowflake差不多不过位分配上稍有不同,见*图 6-2*
![sonyflake](../images/ch6-snoyflake.png)
*图 6-3 sonyflake*
这里的时间只用了39个bit但时间的单位变成了10ms所以理论上比41位表示的时间还要久(174 years)。
`Sequence ID`和之前的定义一致,`Machine ID`其实就是节点id。`sonyflake`与众不同的地方在于其在启动阶段的配置参数:

View File

@ -17,10 +17,12 @@ timer的实现在工业界已经是有解的问题了。常见的就是时间堆
### 6.3.1.1 时间堆
最常见的时间堆一般用小顶堆实现,小顶堆其实就是一种特殊的二叉树
最常见的时间堆一般用小顶堆实现,小顶堆其实就是一种特殊的二叉树,见*图6-4*
![二叉堆](../images/ch6-binary_tree.png)
*图 6-4 二叉堆结构*
小顶堆的好处是什么呢实际上对于定时器来说如果堆顶元素比当前的时间还要大那么说明堆内所有元素都比当前时间大。进而说明这个时刻我们还没有必要对时间堆进行任何处理。所以对于定时check来说时间复杂度是O(1)的。
当我们发现堆顶的元素小于当前时间时那么说明可能已经有一批事件已经开始过期了这时进行正常的弹出和堆调整操作就好。每一次堆调整的时间复杂度都是O(LgN)。
@ -29,6 +31,8 @@ Go自身的timer就是用时间堆来实现的不过并没有使用二叉堆
![四叉堆](../images/ch6-four-branch-tree.png)
*图 6-5 四叉堆结构*
小顶堆的性质父节点比其4个子节点都小子节点之间没有特别的大小关系要求。
四叉堆中元素超时和堆调整与二叉堆没有什么本质区别。
@ -37,6 +41,8 @@ Go自身的timer就是用时间堆来实现的不过并没有使用二叉堆
![timewheel](../images/ch6-timewheel.png)
*图 6-6 时间轮*
用时间轮来实现timer时我们需要定义每一个格子的“刻度”可以将时间轮想像成一个时钟中心有秒针顺时针转动。每次转动到一个刻度时我们就需要去查看该刻度挂载的tasklist是否有已经到期的任务。
从结构上来讲,时间轮和哈希表很相似,如果我们把哈希算法定义为:触发时间%时间轮元素大小。那么这就是一个简单的哈希表。在哈希冲突时,采用链表挂载哈希冲突的定时器。
@ -51,6 +57,8 @@ Go自身的timer就是用时间堆来实现的不过并没有使用二叉堆
![task-dist](../images/ch6-task-sched.png)
*图 6-7 分布式任务分发*
每一个实例每隔一小时会去数据库里把下一个小时需要处理的定时任务捞出来捞取的时候只要取那些task_id % shard_count = shard_id的那些task即可。
当这些定时任务被触发之后需要通知用户侧,有两种思路:
@ -70,6 +78,8 @@ Go自身的timer就是用时间堆来实现的不过并没有使用二叉堆
![数据分布](../images/ch6-data-dist1.png)
*图 6-8 任务数据分布*
一份数据虽然有两个持有者,但持有者持有的副本会进行区分,比如持有的是主副本还是非主副本,主副本在图中为摸黑部分,非主副本为正常线条。
一个任务只会在持有主副本的节点上被执行。
@ -78,6 +88,8 @@ Go自身的timer就是用时间堆来实现的不过并没有使用二叉堆
![数据分布2](../images/ch6-data-dist2.png)
*图 6-9 故障时数据分布*
节点1的数据会被迁移到节点2和节点3上。
当然也可以用稍微复杂一些的思路比如对集群中的节点进行角色划分由协调节点来做这种故障时的任务重新分配工作考虑到高可用协调节点可能也需要有1 ~ 2个备用节点以防不测。

View File

@ -28,12 +28,16 @@ Elasticsearch是开源分布式搜索引擎的霸主其依赖于Lucene实现
![posting-list](../images/ch6-posting_list.png)
*图 6-10 倒排列表*
对Elasticsearch中的数据进行查询时本质就是求多个排好序的序列求交集。非数值类型字段涉及到分词问题大多数内部使用场景下我们可以直接使用默认的bi-gram分词。什么是bi-gram分词呢
即将所有Ti和T(i+1)组成一个词(在es中叫term),然后再编排其倒排列表,这样我们的倒排列表大概就是这样的:
即将所有Ti和T(i+1)组成一个词(在Elasticsearch中叫term),然后再编排其倒排列表,这样我们的倒排列表大概就是这样的:
![terms](../images/ch6-terms.png)
*图 6-11 “今天天气很好”的分词结果*
当用户搜索'天气很好'时,其实就是求:天气、气很、很好三组倒排列表的交集,但这里的相等判断逻辑有些特殊,用伪代码表示一下:
```go
@ -345,6 +349,8 @@ SQL的where部分就是boolean expression。我们之前提到过这种bool
![ast](../images/ch6-ast-dsl.png)
*图 6-12 AST和DSL之间的对应关系*
既然结构上完全一致逻辑上我们就可以相互转换。我们以广度优先对AST树进行遍历然后将二元表达式转换成json字符串再拼装起来就可以了限于篇幅本文中就不给出示例了读者朋友可以查看
> github.com/cch123/elasticsql
@ -361,6 +367,8 @@ SQL的where部分就是boolean expression。我们之前提到过这种bool
![sync to es](../images/ch6-sync.png)
*图 6-13 基于时间戳的数据同步*
这种同步方式与业务强绑定例如wms系统中的出库单我们并不需要非常实时稍微有延迟也可以接受那么我们可以每分钟从MySQL的出库单表中把最近十分钟创建的所有出库单取出批量存入es中具体的逻辑实际上就是一条SQL
```sql
@ -381,6 +389,8 @@ select * from wms_orders where update_time >= date_sub(
![binlog-sync](../images/ch6-binlog-sync.png)
*图 6-13 基于binlog的数据同步*
业界使用较多的是阿里开源的Canal来进行binlog解析与同步。canal会伪装成MySQL的从库然后解析好行格式的binlog再以更容易解析的格式(例如json)发送到消息队列。
由下游的Kafka消费者负责把上游数据表的自增主键作为es的document的id进行写入这样可以保证每次接收到binlog时对应id的数据都被覆盖更新为最新。MySQL的row格式的binlog会将每条记录的所有字段都提供给下游所以实际上在向异构数据目标同步数据时不需要考虑数据是插入还是更新只要一律按id进行覆盖即可。

View File

@ -78,6 +78,8 @@ func main() {
![dist-crawler](../images/ch6-dist-crawler.png)
*图 6-14 爬虫工作流程*
上游的主要工作是根据预先配置好的起点来爬取所有的目标“列表页”列表页的html内容中会包含有所有详情页的链接。详情页的数量一般是列表页的10~100倍所以我们将这些详情页链接作为“任务”内容通过消息队列分发出去。
针对页面爬取来说,在执行时是否偶尔会有重复其实不太重要,因为任务结果是幂等的(这里我们只爬页面内容,不考虑评论部分)。
@ -94,10 +96,14 @@ nats的服务端项目是gnatsd客户端与gnatsd的通信方式为基于tcp
![nats-protocol-pub](../images/ch6-09-nats-protocol-pub.png)
*图 6-15 nats协议中的pub*
以workers的queue从tasks subject订阅消息
![nats-protocol-sub](../images/ch6-09-nats-protocol-sub.png)
*图 6-16 nats协议中的sub*
其中的queue参数是可选的如果希望在分布式的消费端进行任务的负载均衡而不是所有人都收到同样的消息那么就要给消费端指定相同的queue名字。
#### 基本消息生产