写扩散和读扩散
今天看一篇文章时,提到了微信的群聊是写扩散的,第一次接触到写扩散这个名词,于是去查了下资料,有写扩散就有读扩散嘛
定义如下:
根据上面的定义,我是这样理解的,什么是写扩散,什么是读扩散,要怎么区分他们,区别之处就在于“主动”
写扩散是主动把消息写到订阅者的消息列表里,这样订阅者就不用去我的outbox拉取消息 ,所以当我要是有很多订阅者时,我就要写很多次,这就是上面定义中说的写很重
读扩散是主动去拉取被订阅者的outbox,这样就不需要被订阅者主动写消息到我的消息列表里来,所以当我要是订阅了很多人时,我就要去读取这些人的所有新消息,所以就出现了读很重
Clustering VS Sharding
下面着重讲解一下Pinterest在数据库层是如何扩容的。面对集群和分片两种方法,Pinterest的工程团队选择了更加简洁的分片模式。
- 集群和分片的特点
集群模式(Cassandra, HBase等等)的一个好处就是没有SPOF(Single Point of Failure),这可以保证服务的高可用,而且,数据在节点上的分布、迁移都是自动完成的,对用户透明。从技术审美的角度来看,集群是非常“优雅”的解决方案。但是,集群中的各个节点之间互相通信,需要复杂的协议来维持数据在各节点的一致性,这种复杂度带来的是各种失败模式(Failure Modes),特别是当这种技术还不成熟、没有经过广泛验证的时候,故障是难以预料的。正因为各个节点之间互相通信,当一个bug出现的时候,影响的不仅仅是自身节点,会扩散到集群的所有节点。用Pinterest工程师的话来说就是:“发生了数据丢失,但是我们不知道哪一部分丢失了,这比丢失所有数据还要糟糕。” 需要升级的时候,发生故障的可能性更高,因为需要考虑不同版本的节点之间的兼容性问题。
分片模式需要应用层手动完成数据分布。各个分片的节点之间没有通信,这能保证故障能被隔离在一个节点内部。在这种架构中需要一个master节点来对请求做分发,这是一个SPOF,但是和集群算法相比,分发的逻辑相当简单,往往只需要几十行代码,出现程序错误的几率很小。分片的另一个问题是,不支持跨节点的JOIN,也无法保证事务的原子性。如果需要这些功能,都需要在应用层来完成。Pinterest的不同用户之间的数据关联度很低,所以按照用户ID对所有数据分片,并不会给应用层增加太多复杂性。因此,针对Pinterest这个应用的业务特点,数据分片是非常英明的抉择。
- 分片(Sharding)的实现
Pinterest在每个数据库节点上配置了多个DB,每个DB是一个虚拟分片。通过维护一张分片配置表,就可以查找一个分片ID所在的节点:
[{“range”: (0,511), “master”: “MySQL001A”, “slave”: “MySQL001B”},
{“range”: (512, 1023), “master”: “MySQL002A”, “slave”: “MySQL002B”},
...
{“range”: (3584, 4095), “master”: “MySQL008A”, “slave”: “MySQL008B”}]
Pinterest使用了一个64位全局资源ID来完成数据的定位:
ID = (shard ID << 46) | (type ID << 36) | (local ID<<0)
其中16位用于分片ID,10位用于类型(Pin/board等), 32位用于local ID,也就是数据库中的主键。
举个例子,Pinterest中一个图钉的地址是:
http://www.pinterest.com/pin/241294492511762325/
我们可以分解一下这个全局ID:241294492511762325
Shard ID = (241294492511762325 >> 46) & 0xFFFF = 3429
Type ID = (241294492511762325 >> 36) & 0x3FF = 1
Local ID = (241294492511762325 >> 0) & 0xFFFFFFFFF = 7075733
所以这个图钉在分片3429上,而且他在数据库Pin表中的ID是7075733。假设这个分片在节点MySQL012A上,我们可以这样访问其数据:
conn = MySQLdb.connect(host=”MySQL012A”)
conn.execute(“SELECT data FROM db03429.pins where local_id=7075733”)
Pinterest是根据用户的ID做数据分片的,也就是说一个用户的数据大部分情况下是在同一个库中。这样做的好处是,在渲染一个用户页面的时候,所有数据可以在同一个库中查询到,大大减少了查询延迟。用户数据会永久存储在一个固定的分片上不会迁移,新增用户会被分配到新的分片ID上。Pinterest预留了4096个分片,可能会随着业务的增长扩展到8192个。
唯一的特例是User表,它是存在一个独立数据库中不分片的。这是为了保证用户注册时用户名/邮箱的全局唯一性约束。