Coup de Grace

君技术本当上手 1706

标题算 neta 一下日本语本当上手.

最近的工作一直被 block, 正恰逢最近半年一直在搞 kubernetes 导致时间都被吸走了.

姑且记录一下每个月的阅读,偏向系统设计与源码解读方面,一些生活 tips 就不表了.

业务稳定下来我们的系统的确需要在拆分,缓存,多数据源, MQ 方面动动手脚.

都会使用 Linnk 将文章快照免得丢失.


The Log

地址

本文讨论的是 journal形式的 log, 是应用日志的父集. 

- Log可以说是某种将记录按时间排序的文件或者表
- Log记录了何时发生了什么
- Log从保证ACID特性的一种实现,发展成了一种数据库之间数据复制的手段
- 面向机器的Log,不仅仅可被用在数据库中,也可以用在消息系统/数据流/实时计算

分布式系统中

- Log系统的作用,就是将所有的输入流之上的不确定性驱散,确保所有的处理相同输入的复制节点保持同步
- 通过将复制节点所处理过的log中最大的时间戳,作为复制节点的唯一ID,这样,时间戳结合log,就可以唯一地表达此节点的整个状态
- 我们可以记录一系列的机器指令,或者所调用方法的名称及参数,只要数据处理进程的行为相同,这些进程就可以保证跨节点的一致性

对分布式系统,通常有两种方式来处理复制和数据处理:
1) State machine model(active - active)
2) Primary-back model (active - passive)

版本控制与分布式系统中,复制都是基于log的:当你更新版本时,你只是拉取了反映了版本变化的补丁,并应用于当前的分支快照

接下来讲了 ETL, 数据流,kafka 应用等内容. 


RocketMQ 原理与实践

地址

视频

我都有点忘了自己是不是写过 MQ 相关的源码阅读了…

- 我们关注顺序与重复 

顺序

因为网络延迟等原因,想实现严格的顺序,简单且可行的办法就是:
保证生产者 - MQServer - 消费者是一对一对一的关系
但是它带来了消费者响应的阻塞以及吞吐量低下的问题 

重新设计的思路认为
不关注乱序的应用实际大量存在
队列无序并不意味着消息无序

也就是说这里认为不关注顺序的不去处理,关注顺序的那么根据这些元素的特征使之进入同一个 Queue 再被同一个 client使用. 

代码上,假如是订单场景
// 通过MessageQueueSelector中实现的算法来确定消息发送到哪一个队列上
// 默认提供了两种MessageQueueSelector实现:随机/Hash
// 使同一个OrderId获取到的肯定是同一个队列
producer.send(msg,MessageQueueSelector,orderId);


重复 

当网络不可达时,没有得到应答的消息会重复进行发送. 

RocketMQ 设计为全部在 client 端自行解决 
消费端处理消息的业务逻辑保持幂等性
保证每条消息都有唯一编号且保证消息处理成功与去重表的日志同时出现

事务 

面临分布式事务场景 

大事务 = 小事务 + 异步

比如讲转账操作的操作拆分为 
1. A账户锁定,操作金额,解锁 A 账户(同一事务)
2. 异步消息
3. 锁定 B 账户,操作余额,解锁 B 账户(同一事务)

那么第二步的异步消息存在位置就存疑,是否要将其置入 1事务中. 
文中也提到可以将消息发送加入到本地事务中来实现上述. 

ROcketMQ 这边在事务开始时就将 prepared 消息置入 MQ
在事务结束时确认 producer 拿着准备阶段产生的消息地址进行状态修改,发送消息

而后讲了消息存储于消息订阅相关的内容

有关缓存高可用性的思考

地址

地址

- mq 交互的不同系统间消息失败的重试
- 缓存中设立时间或版本标志位来避免不同系统对同一 db 的写入覆盖
- 未命中的热点数据,要使用 map 记录,当回源到 db并更新缓存后,清理 map
- 保证同步/减少并发/杜绝击穿

- 避免大记录
- 提高命中率

- 强一致性缓存:无法接受从缓存拿到过期的数据 (比如用户的在投金额/余额)
- 弱一致性缓存:能接受在一段时间内从缓存拿到过期的数据 (比如产品的销量/总的购买人记录)。
- 不变型缓存:缓存key对应的value不会变更(比方说公司用户基本属性)

有强一致性缓存要求, 那么redis开RDB/AOF持久化,保证数据不会丢失,硬件上选用SSD的硬盘。实时的读写全部走redis,然后异步回写到数据库
由于redis集群是不能100%保证强一致性的,有些访问量不高的重要数据,建议直接读写数据库,来保证强一致性

对于弱一致性的缓存的要求,那么高可用变成了首要任务,在配置上面,会保证集群的稳定性

对于不变型缓存,会单独做一个小的集群,有一个定时任务,如果数据库发生了数据改动,会主动更新缓存,同时也会定期做一次同步

微服务下的数据一致性思考

地址

补充一下博文里面的内容

作者对微服务做了分类
1. 服务间没有直接依赖,采用异步化调用,上游服务完成后,发一个消息异步通知下游服务,下游服务成功与否对上游服务没有影响。
2. 上游服务弱依赖于某个下游服务的处理结果,可降级。降级时可以不返回这部分的数据。同步调用降级时转为异步。
3. 上下游微服务强依赖,上游服务依赖于下游服务的返回或者回调,下游必须正常执行,如果下游服务失败了,本次请求判定为失败。

Case1, 只需要考虑2点:幂等性和消息队列重试机制
Case2则是同步调用失败则发条消息不阻塞应用正常返回
Case3是服务间调用最严格的情况,意味着如果下游服务中有一个服务调用失败,上下游的所有服务必须回滚。意味着服务间必须保证同一个事务

业界公认的一个解决方案就是 TCC (Try-Confirm-Cancel) 模式:
1. 主业务服务分别调用下游业务执行try操作,并在活动管理器中登记所有下游服务。当所有下游微服务的try操作都调用成功,或者某个下游微业务服务的try操作失败。
2. 业务活动管理器根据第1步的执行结果来执行confirm或cancel操作。如果之前所有try操作都成功,则活动管理器调用所有下游微业务,执行confirm操作。否则调用所有下游微服务的cancel操作。
3. 如果第2步,执行confirm或concel操作出现失败,活动管理器会启动重试机制,保证所有微服务最终提交或者回滚操作成功。


分页与分库那些事儿

地址

问:目前准备做数据库水平切分,需要注意什么关键问题?目前了解需要避免跨库事务,请老师指点。
答:
需要注意分库patition key的选取,要保证两个均衡:数据量的均衡,请求量的均衡。
分库后,需要注意分之前用SQL满足的需求是否还能满足,需要怎么改进满足,例如max, min, avg, sum都需要在服务层再做一次聚合。
跨库事务,分布式事务,在吞吐量是主要矛盾的互联网场景,目前没有能够很好解决的方案,尽量避免。

问:采用hash取模方式的表扩容策略及采用一致性hash分表的表扩容策略如何实现?
答:数据库水平切分的方式,常用的有两种:
hash取模:user_id%2=0为0库,user_id%2=1为1库。
数据分段:user_id属于[0, 1亿]为0库,属于[1亿, 2亿]为2库。
方案一    
优点:简单、数据均衡、负载均衡。
缺点:扩容困难,要迁移数据,%2变%3麻烦。
大部分互联网公司使用方案一

问:如果分库分表的情况下碰到要对一个表或多个表关联并且按多个字段为条件进行检索的情况下怎么办呢?
答:分库后join怎么办,我这么解释:
前端用户侧业务,流量大,并发大,join真的很少,58同城用户库几亿数据,帖子库300亿数据,没有join。
如果真要join,分库后冗余数据、索引表、分页,for循环低效查询 -> 总能解决的,只是看性能是不是主要矛盾、一致性是不是主要矛盾了。
拆成小sql是互联网的玩法,互联网很少用join、子查询、视图、外键、用户自定义函数、存储过程的。当然,我指面向用户侧的高并发业务。

问:单表多大数据量时才考虑分库分表?
答:“单表多大数据量时才考虑分库分表”,我们的经验,mysql,1000w-2000w,要考虑分了。如果查询比较简单,例如订单全是单key查询,5000w。

问:我们业务使用了1000个分片表,这样可以吗?
答:个人建议,一律使用分库,而不是分表。
分表:
1. 表名不同吧?DAO层搞一个实例,还是多个实例,还是怎么trick一下?
2. 物理上,还在一个库文件里,还是有潜在瓶颈。
3. 未来扩展到多机,比较麻烦。
所以,建议一律分库。
副作用是,数据库连接会比较多,但一般不是瓶颈。

Feature Toggles

地址

补充:

面临开飞机换零件的场景

作者提出了以下几种不同的切换类型:

- 发布类切换将部署从发布功能中分离出来,是暂时性的。它们常常用于金丝雀发布策略。“产品经理也可能会使用这个方法——不过是一个以产品为中心的变体——防止只完成了一半的产品特性暴露给终端用户。”它们可以用作特性分支的替代方案,在后一种情况下,太晚将分支特性合并到发布版本已被证实是一项复杂的任务。
- Ops类切换是对源于Netflix Hystrix实现的断路器模式的一种推广。这类切换会在运维层面修改系统,可以用于在高负载情况下优雅地停用某项服务。
- 试验类切换是短期切换,可以用于市场营销目的的A/B测试。在这种情况下,“系统的每个用户都被归入一个用户群,在运行时,切换路由会根据用户所在的群始终如一地将特定用户导向一个或另一个代码路径。”通过评估不同用户群的群体行为可以评价特性的效果。
- 权限类切换主要是长期的可选特性开关,可以用于实现定价策略,比如一个付费模型,针对白银、黄金或铂金产品层级。这类切换需要一种比简单的if/then/else语句更健壮的实现以提高可维护性。

1. 运用策略模式,避免切换点条件语句,而将if/then/else语句封装到路由层。
2. 引入实现决策逻辑的决策对象,将决策点从决策逻辑中分离出来。这样,举例来说,特性分组变化不会破坏代码。
3. 把决策对象作为构造函数参数注入,将代码从特性切换基础结构中分离出来,以便可以用它切换任何想要切换的方法。这样,代码设计就可以不考虑切换路由,简化了开发测试。

另外对于 Scope 来讲: 发布生效/ session/request 这几样必不可少. 

另外在于某些对于per-request/session进行判断的场景,有些思路是用 redis 的 bitmap 来实现. 

当然,推荐使用 etcd/consul 等也不在少数. 

当然了,我们通过规则引擎来区分某些用户群,这再理所当然不过了. 

An overview of realtime protocols

地址

designed to deliver messages, synchronize data and facilitate request/response workflows over persistent, bidirectional, connections — e.g. WebSockets or TCP.

- Pure Messaging 
  Low(er) level protocols such as TCP are designed to deliver one message from one sender to one receiver. They have no opinion on how this message should be structured, requested, retrieved, secured or stored.
  Protocols like WebSockets sit on top of TCP and add additional functionality, such as headers to transport meta-data, splitting larger messages across multiple packets, basic authentication mechanisms or routing/redirection information
  
- Pub/Sub
  Also called an observer pattern,This paradigm is enormously flexible, efficient and scalable.
  
# MQTT
  The Message Queue Telemetry Transport
# STOMP
# WAMP
# AMQP


Understanding AMQP

地址