You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

70 lines
9.4 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 微博技术解密(下)| 微博存储的那些事儿
今天是微博技术解密系列的第二期我们来聊聊微博存储的使用经验。上一期“微博技术解密”我讲到微博主要使用了两大类存储一类是数据库主要以MySQL为主一类是缓存主要以Memcached和Redis为主。
今天我来分享一下微博在使用数据库和缓存方面的经验,也欢迎你给我留言一起切磋讨论。
## MySQL
上一期我讲到微博Feed的存储使用了两层的结构为了减少对MySQL数据库的访问压力在前面部署了Memcached缓存挡住了99%的访问压力只有1%的请求会访问数据库。然而对于微博业务来说这1%的请求也有几万QPS对于单机只能扛几千QPS的MySQL数据库来说还是太大了。为此我们又对数据库端口进行了拆分你可以看下面的示意图每个用户的UID是唯一的不同UID的用户按照一定的Hash规则访问不同的端口这样的话单个数据库端口的访问量就会变成原来的1/8。除此之外考虑到微博的读请求量要远大于写请求量所以有必要对数据库的读写请求进行分离写请求访问Master读请求访问Slave这样的话Master只需要一套Slave根据访问量的需要可以有多套也就是“一主多从”的架构。最后考虑到灾备的需要还会在异地部署一套冷备的灾备数据库平时不对外提供线上服务每天对所有最新的数据进行备份以防线上数据库发生同时宕机的情况。
![](https://static001.geekbang.org/resource/image/ac/b3/ac361340ab002db47cafaa596c4293b3.png)
## Memcached
在MySQL数据库前面还使用了Memcached作为缓存来承担几百万QPS的数据请求产生的带宽问题是最大挑战。为此微博采用了下图所示的多层缓存结构即L1-Master-Slave它们的作用各不相同。
L1主要起到分担缓存带宽压力的作用并且如果有需要可以无限进行横向扩展任何一次数据请求都随机请求其中一组L1缓存这样的话假如一共10组L1数据请求量是200万QPS那么每一组L1缓存的请求量就是1/10也就是20万QPS同时每一组缓存又包含了4台机器按照用户UID进行Hash每一台机器只存储其中一部分数据这样的话每一台机器的访问量就只有1/4了。
Master主要起到防止访问穿透到数据库的作用所以一般内存大小要比L1大得多以存储尽可能多的数据。当L1缓存没有命中时不能直接穿透到数据库而是先访问Master。
Slave主要起到高可用的目的以防止Master的缓存宕机时从L1穿透访问的数据直接请求数据库起到“兜底”的作用。
![](https://static001.geekbang.org/resource/image/fc/2d/fc8a927caf6d2991b0a2562863441f2d.png)
## Redis
微博的存储除了大量使用MySQL和Memcached以外还有一种存储也被广泛使用那就是Redis。并且基于微博自身的业务特点我们对原生的Redis进行了改造因此诞生了两类主要的Redis存储组件CounterService和Phantom。
**1\. CounterService**
CounterService的主要应用场景就是计数器比如微博的转发、评论、赞的计数。早期微博曾采用了Redis来存储微博的转发、评论、赞计数但随着微博的数据量越来越大发现Redis内存的有效负荷还是比较低的它一条KV大概需要至少65个字节但实际上一条微博的计数Key需要8个字节Value大概4个字节实际上有效的只有12个字节其余四十多个字节都是被浪费的。这还只是单个KV如果一条微博有多个计数的情况下它的浪费就更多了比如转评赞三个计数一个Key是long结构占用8个字节每个计数是int结构占用4个字节三个计数大概需要20个字节就够了而使用Redis的话需要将近200个字节。正因为如此我们研发了CounterService相比Redis来说它的内存使用量减少到原来的1/151/5。而且还进行了冷热数据分离热数据放到内存里冷数据放到磁盘上并使用LRU如果冷数据重新变热就重新放到内存中。
你可以看下面的示意图CounterService的存储结构上面是内存下面是SSD预先把内存分成N个Table每个Table根据微博ID的指针序列划出一定范围。任何一个微博ID过来先找到它所在的Table如果有的话直接对它进行增减如果没有就新增加一个Key。有新的微博ID过来发现内存不够的时候就会把最小的Table dump到SSD里面去留着新的位置放在最上面供新的微博ID来使用。如果某一条微博特别热转发、评论或者赞计数超过了4个字节计数变得很大该怎么处理呢对于超过限制的我们把它放在Aux Dict进行存放对于落在SSD里面的Table我们有专门的Index进行访问通过RDB方式进行复制。
![](https://static001.geekbang.org/resource/image/5a/7b/5ac693490234b05eb37cec6841a3097b.png)
**2\. Phantom**
微博还有一种场景是“存在性判断”比如某一条微博某个用户是否赞过、某一条微博某个用户是否看过之类的。这种场景有个很大的特点它检查是否存在因此每条记录非常小比如Value用1个位存储就够了但总数据量又非常巨大。比如每天新发布的微博数量在1亿条左右是否被用户读过的总数据量可能有上千亿怎么存储是个非常大的挑战。而且还有一个特点是大多数微博是否被用户读过的存在性都是0如果存储0的话每天就得存上千亿的记录如果不存的话就会有大量的请求最终会穿透Cache层到DB层任何DB都没有办法抗住那么大的流量。
假设每天要存储上千亿条记录用原生的Redis存储显然是不可行的因为原生的Redis单个KV就占了65个字节这样每天存储上千亿条记录需要增加将近6TB存储显然是不可接受的。而用上面提到的微博自研的CounterService来存储的话一个Key占8个字节Value用1个位存储就够了一个KV就占大约8个字节这样每天存储上千亿条记录需要增加将近800GB存储。虽然相比于原生的Redis存储方案已经节省了很多但存储成本依然很高每天将近1TB。
所以就迫切需要一种更加精密的存储方案针对存在性判断的场景能够最大限度优化存储空间后来我们就自研了Phantom。
就像下图所描述的那样Phantom跟CounterService一样采取了分Table的存储方案不同的是CounterService中每个Table存储的是KV而Phantom的每个Table是一个完整的BloomFilter每个BloomFilter存储的某个ID范围段的Key所有Table形成一个列表并按照Key范围有序递增。当所有Table都存满的时候就把最小的Table数据清除存储最新的Key这样的话最小的Table就滚动成为最大的Table了。
![](https://static001.geekbang.org/resource/image/5c/1c/5caa16bfda4b80f635276501b257871c.png)
下图描述了Phantom的请求处理过程当一个Key的读写请求过来时先根据Key的范围确定这个Key属于哪个Table然后再根据BloomFilter的算法判断这个Key是否存在。
![](https://static001.geekbang.org/resource/image/3d/d4/3dd0fc260df19565c223f981564a1cd4.png)
这里我简单介绍一下BloomFilter是如何判断一个Key是否存在的感兴趣的同学可以自己搜索一下BloomFilter算法的详细说明。为了判断某个Key是否存在BloomFilter通过三次Hash函数到Table的不同位置然后判断这三个位置的值是否为1如果都是1则证明Key存在。
来看下面这张图假设x1和x2存在就把x1和x2通过Hash后找到的三个位置都设置成1。
![](https://static001.geekbang.org/resource/image/01/80/018815b6621a6e55fb5a69e0764f1180.png)
再看下面这张图判断y1和y2是否存在就看y1和y2通过Hash后找到的三个位置是否都是1。比如图中y1第二个位置是0说明y1不存在而y2的三个位置都是1说明y2存在。
![](https://static001.geekbang.org/resource/image/47/9a/4710ee8196a1d979515457718d926e9a.png)
Phantom正是通过把内存分成N个Table每一个Table内使用BloomFilter判断是否存在最终每天使用的内存只有120GB。而存在性判断的业务场景最高需要满足一周的需求所以最多使用的内存也就是840GB。
## 总结
今天我给你讲解了微博业务中使用范围最广的三个存储组件一个是MySQL主要用作持久化存储数据由于微博数据访问量大所以进行了数据库端口的拆分来降低单个数据库端口的请求压力并且进行了读写分离和异地灾备采用了Master-Slave-Backup的架构一个是Memcached主要用作数据库前的缓存减少对数据库访问的穿透并提高访问性能采用了L1-Master-Slave的架构一个是Redis基于微博自身业务需要我们对Redis进行了改造自研了CounterService和Phantom分别用于存储微博计数和存在性判断大大减少了对内存的使用节省了大量机器成本。
专栏更新到这里就要跟同学们说再见了,感谢你们在过去大半年时间里的陪伴,值此新春到来之际,老胡给您拜年啦,恭祝各位同学在新的一年里,工作顺顺利利,生活开开心心!