第1页
微架构设计之 微博计数器服务
杜传赢 @cydu chuanying@staff.sina.com.cn
http://weibo.com/cydu http://blog.cydu.net/
第2页
• 新浪微博 -- 中国最具影响力的微博产品 • 信息实时聚合平台 • 数千个消息源的聚合 & 千万级的消息接收者 • 巨大的数据量 • 微博数据量: 千亿级 • 高峰微博增加量: 每秒3万条(春节) • 巨大的访问量 • 每天动态API请求量超过300亿次 • 每秒数百万次的服务调用(包括内部调用)
第3页
微博计数器服务
未读计数 用户计数
微博计数
第4页
第⼀一版 从无到有
架构挑战: 开发速度
第5页
2009.7月
正式立项
2009.8.28
新浪微博
上线
t.sina.com.cn
第6页
SELECT count(mid) FROM Status WHERE uid = 888
• mid => 每条微博的唯⼀一标识(64bit) • uid => 每位用户的唯⼀一标识(64bit)
第7页
实时计算
SELECT count(mid) FROM Status WHERE uid = 888
• mid => 每条微博的唯⼀一标识(64bit) • uid => 每位用户的唯⼀一标识(64bit)
第8页
计数索引
SELECT repost_count, comment_count FROM Status_count WHERE mid = 888
• 微博变多: 实时Count代价高 • 流量增大: Feed页出微博计数 • 数据⼀一致性? 如何更新?
第9页
Scale UP
硬件加速
client app
libmysql
Applications
libhsclient
mysqld
Listener for libmysql
SQL Layer
Handlersocket Plugin
Handler Interface
Innodb
MyISAM
Other storage engines …
MySQL HandleSocket
• 摩尔定律: 每隔18个月, 性能提升⼀一倍! • Jim Gray: • Tape is dead, disk is tape, flash is disk, ram locality is king
第10页
小结: 切忌过度优化
数据量(1000亿)
先建设, 再优化
开发速度
访问量(100W rps)
第11页
第二版(1) 从小到大
架构挑战1: 访问量
第12页
weibo.com独立访问量已是国内 第 六 大网站
第13页
突发热点更新
第14页
异步更新
ID分配器 + 消息队列
• •MessageQueue
更新消息队列 消息更新异步化
• 削峰填谷
• 避免高峰时数据丢失
第15页
批量更新
UPDATE Status_count SET repost_count = repost_count +1 WHERE mid = 888;
UPDATE Status_count SET repost_count = repost_count +1 WHERE mid = 888;
UPDATE Status_count SET repost_count = repost_count +1 WHERE mid = 888;
Update Status_count SET repost_count =
repost_count +3 WHERE mid = 888;
• 批量更新: • 取多条消息, Merge后再更新, 降低写压力
第16页
大量读挑战
超过15倍的放大效应!
statuses/home_timeline get mid list
get mid content get mid content get mid content
get mid counter get mid counter get mid counter
第17页
缓存优化
Client
Client
MC
Mysql
Mysql
• 读写比 && 命中率 • Cache效率的关键 • “Cache就像万金油, 哪痒你就抹哪, 但是千万记得脱了
皮鞋再抹!” -- by 朱聪
第18页
Client Mysql
MC
MultiGet
• MultiGet Hole ? More Machines != More Capacity
• Evections (缓存踢出)
第19页
第二版(2) 从小到大
架构挑战1: 访问量
从少到多 架构挑战2: 数据量
第20页
用户数(单位:千)
400,000
300,000
300,000 250,000
140,000 2010,090.10125001,000.100 2011.04 2011.10
2012.03
368,000 2012.8
400,000 2012.11
200,000 100,000 0
第21页
Partition 1
uid fans_num 20 ... 22 ... 24 ...
Partition 2
uid fans_num 21 ... 23 ... 25 ...
水平扩展
Partition by primary key 用户类数据
Partition by time/mid 微博类数据
Partition 2 Partition 1
mid 24 23
mid 22 21
repost_num ... ...
repost_num ... ...
第22页
Partition 4 Partition 3
mid 34 32
mid 33 31
repost_num ... ...
repost_num ... ...
Partition 2 Partition 1
mid 24 22
mid 23 21
repost_num ... ...
repost_num ... ...
两层划分
Partition by mid + time 微博类数据
第23页
小结: 见招拆招, 快速迭代
数据量(1000亿)
认真用好成熟解决方案
数据量 访问量
+ 开发速度
访问量(100W rps)
第24页
第二版的问题: Pain Driven Development
• 数据量越来越大, 分表越来越频繁/复杂 • 操作更复杂, 风险大, 成本也越来越高
• 访问量越来越大, Cache命中率如何提升? • 内存使用效率低下(Eg: 字符串,0数据,旧数据) • 副本过多时, 数据更新代价高 • 完全依赖内存, 存储成本高
• 高可用的要求 • 机器故障时的快速恢复能力及最低的线上影响
• 越来越复杂的需求带来的业务挑战
第25页
第三版(1) 由粗到细
架构挑战1: 高可用
第26页
REmote DIctionary Server
by @antirez
• 成熟 : 社区活跃 + 生产环境使用多 • 高速 : 全内存, 性能优异 + 数据镜像,快速恢复 • 简单 : 友好的DSL接口 + 丰富的数据结构 • 可控 : 架构简洁 + 代码量不大
第27页
AOF 0
RDB+AOF
RDB 0 off: 1989
RDB 1 off: 4698
• RDB + AOF 同步模式
• 定期 bgsave 生成RDB
• 避免AOF Load过慢
• syncfrom aof offset
• 避免从头Sync
• 热升级
RDB 2 off: 6489
@果爸果爸 / @Jokea
第28页
Web Service (Update)
IDC 0:
Mysql Cluster
Web Service (Update)
Message Queue
Mysql Cluster
多机房Web Service (Update)
IDC : 2
Mysql Cluster
Transformer
Redis
Transformer
Redis
Transformer
Redis
Web Service (Query)
Web Service (Query)
Web Service (Query)
第29页
A. 正常服务
B. 降级写服务
• 写延迟(堵队列) • 拒绝写(事后修数据)
C. 降级读服务
• 只读新数据(老数据出0) • 新数据部分可 • 全部出默认值
D. 服务读写均不可用
• 不出计数
灾难降级
第30页
• 最终⼀一致性! • Randomkey check • RDB CRC check • Digest check (write crc) • 定期全量sync • 数据基准修复
数据一致
第31页
第三版 (2) 由粗到细
架构挑战1: 高可用
架构挑战2: 低成本
第32页
关键字域
64bit mid
h(mid 14) h(mid 69)
h(mid 45) h(mid 25)
h(mid 92)
h(mid 78)
数据结构
14 45
9|0 3|9
69 7 | 2
0|5
78 25
2|0 9|1
传统的Hash表大量的指针开销
第33页
开放寻址Hash节省内存 mid
repost_num comment_num
h(k,i) = (h1(k)+i*h2(k)) % m
struct item { int64_t mid; u_short repost_num; u_short comment_num;
};
• Value的长度短于指针长度 • 开放寻址Hash表(双重散列) • 以节省指针存储
69 45
72 39
第34页
E[X]: 期望探查次数 α: 装载因子
冲突key数占总数据
该图表由@果爸果爸 分析微博计数线上真实数据得出
装载因子
冲突次数(<=)
第35页
数据压缩
• repost_num & comment_num • 32bit *2 => 16 bit * 2
• mid • string => int64_t
• Value block compress by @吴廷彬 • Value定长块实时压缩和解压(对上层透明)
• Key prefix compress by @吴廷彬 @drdrxp • key基本有序后, 64 bit的key相同前缀(Eg: 高32位)提取
第36页
还有几个小问题
• repost_num 和 comment_num 只用16位存储,超 过65535的转发数和评论数怎么办?
• 采用开放寻址仍会冲突, 在极端情况下, 冲突加大影 响性能怎么办? 会有潜在的”死循环”吗?
• 内存不够怎么办? (尽管已经精简使用,但数据量仍很 夸张)
• 选取哪些Key放到内存? 为什么不用LRU? • 用户突然大量访问老的微博怎么办?
• Eg: “转发我的第⼀一条微博”, “去年今日的我”
第37页
Weibo Counter Service新架构
Increase with id Split array by range
900 996 Block 10 910 999
...
310 310 Block 3 330 400
201 280 Block 2 250 310
Memory SSD Disk
Sort Dump
100 110 Sorted 1 160 201
0 30 Sorted 0 80 100
Range Index
Cold Data Buffer
0 46 Extend Block 98 1000 0 89 Aux Block 64 1000 0 19 Cold Block 85 201
Index 1 Index 0
310 Unsorted 3 330 280 Unsorted 2 250
Rdb
第38页
第三版(3) 由粗到细
架构挑战1: 高可用
架构挑战2: 低成本
架构挑战3: 多变需求
第39页
• 微博计数 • 评论数 / 转发数 • 表态数 • 喜欢数 / 开心数 / 吃惊数 / 悲伤数 / 愤怒数
• 用户计数 • 关注数 / 粉丝数 / 好友数 / 微博数 / 原创微博数 ...
• 其他计数 • 未读数 / 提醒数 • 链接点击数 / 收藏数 • 会员数 / 应用计数 / 管理类计数...
第40页
• add counter weibo
服务化支持
• add column weibo mid hint=64 max=64 primarykey
• add column weibo comment hint=16 max=32 default=0 suffix=cntcm
• add column weibo repost hint=16 max=32 suffix=cntrn
• add column weibo attitude hint=8 max=32 suffix=cntan
• set 19089006004.cntcm 987654
• incr 888888.cntrn
• get 123456.cntan
• del 19089006004
第41页
• 每个计数的统计
统计支持
• 容量 / 目前使用量
• getCount / setCount / missCount / hitCount
• errorCount / fullCount / collisionCount
• 计数中每个列的统计
• 计数中每个Table的统计
• 慢查询的统计
对业务需求,业务状态,服务状态,架构缺 陷等更好的理解才能支持更好的决策!
第42页
小结: 量体裁衣, 精益求精!
第43页
小结: 量体裁衣, 精益求精
数据量(1000亿)
架构没有最好, 只有合适和更优 高可用 服务化
数据量 访问量
+
开发速度
低成本
访问量(100W rps)
第44页
Q&A
• 感谢各位, 欢迎各种意见和建议, 当然也包括拍砖!
• 本文中提及计数服务相关设计和实现主要贡献者: • @微博平台架构 @果爸果爸 @LinuxQueue @cydu • 之前的设计总结和讨论见Blog: • http://blog.cydu.net/2012/09/weibo-counter-service-design-2.html