当前位置: 首页 > 微博架构, 网站架构 > 正文

浅谈微博系统设计

1 星2 星3 星4 星5 星 (1 次投票, 评分: 5.00, 总分: 5)
Loading ... Loading ...
baidu_share

从2009年起,本人就开始关注并使用微博,逐渐成为微博控,到后来亲自投身于设计开发微博系统,也已约莫两年时间了。现在正好有时间停下来整理,于是写一篇文章总结,供自己学习也可给他人一些参考。

谈起微博,人们自然首先想到的是twitter,无论是从用户量,商业模式还是技术实力来说都是行业的引领者。不过很可惜,因为某些懂的入的原因,在国内无法正常访问。2010年初那会,新浪和腾讯两家都刚起步,twitter分享出来的技术文章也是只言片语,作为一个新型商业模式的项目而言,不管是论技术还是论功能,大家都在摸索中前行。印象中,新浪微博曾大规模当机过三回,不管是小浪被外星人带走还是去火星了,对于用户而言都是一种伤害。当然,twitter的鲸鱼故障也是时有发生。因此,由微博这类网站业务模型的特殊性所引发的海量存储,非关系数据库,实时计算等话题,是近年来各大公司关注的技术点。

任何一家以内容为核心的互联网企业,最有价值的东西就是数据。如何设计微博的数据存储,在数据安全性,可靠性有保证的前提下,提升数据访问性能就成为了永恒的话题。

微博的基础服务围绕的就是网站的数据逻辑:用户信息,用户关系,微博信息,它们都存放于数据库(关系型和非关系型)以及多级Cache中。像图片这类用户自行上传的静态资源,将会由static服务接管,除了服务器内部的复制传输,还会涉及CDN的分发推送。至于MQ(Message Queue)消息队列,则是为了用于缓解数据库服务器高并发环境下写入压力的一剂良方。

在基础服务之上,可以看到一组平台级的服务,这个层面上更多的考虑的是业务逻辑。微博内容服务,短链接服务,关系服务,统计分析服务等,由这组服务提供的API接口,可以满足平台化业务的各种需求。

我想,任何一个从0开始做微博的团队,在项目开始之初都不一定会想出一个这么复杂的架构,因为微博确实可以做的很简单,但是如果你想把它做大,那你不得不考虑这些后续的扩展性服务架构。

在我设计微博的过程中,先后经历了三次架构上的思考变迁。

version#1

技术架构:

MySQL单库单表,基于MyISAM引擎
采用Memcache缓存服务
微博数据库中仅需一张微博表,和一张评论表,通过从用户关系接口中查询关注关系,然后通过SELECT * FROM feeds WHERE userid IN (following userid) LIMIT n,20即可查出从第n条开始的后20条微博。尽管此种方案实现简单,能够快速响应业务需求。但是当系统规模迅速增大后,数据难以拆分导致后期难以维护,而且由于微博的内容数据和计数服务没有进行分离,频繁的转发和回复数update更新以及新微博产生的insert导致写锁产生,从而阻塞正常的读操作,虽然可以通过MySQL主从读写分离来解决这个问题,但这绝对不是一个好的设计。

version#2

技术架构:

数据存储全部基于Redis,开启持久化,使用Snapshot方式,dump.rdb文件定时备份。关闭AOF和VM(当时版本1.2.6,尚未支持VM机制)

数据推送基于推模式
用Redis List结构实现FIFO消息队列

MySQL异步备份Redis数据,防止Redis服务failover导致数据丢失。

此方案充分利用了Redis基于内存操作的优异性能,代码端的逻辑更为清晰简单(只需要根据业务需求实现各种List结构的收件箱和发件箱,将微博ID推送进去,然后通过SORT操作排序取出)。但是该方案的缺点也是显而易见,因为Redis这种NoSQL的无关系性,导致每增加一项新功能,都有可能需要重新设计一个收件箱,因此产生大量的冗余数据,造成资源浪费,且业务扩展性差。其实现在回想当初这种业务数据全部基于Redis的设计,仍有一些担忧和后怕,因为目前本人还没有发现哪个案例能够证明Redis有足够的稳定性和健壮性,足以支撑整个项目的数据存储,除了官方文档中那个Twitter clone。

version#3

技术架构:

用NoSQL进行内容数据存储(K-V模式,单Key存储,支持持久化存储,Redis/MongoDB)
用MySQL进行关系数据存储(处理业务,纯数字型关系数据写入,基于InnoDB引擎)

使用成熟的消息队列产品(RabbitMQ)

数据推拉模式与冷热数据处理

服务——接口——应用模式(业务框架设计)

记住一句话:把合适的技术用在最擅长的领域。NoSQL适合用于大数据量高速读写操作,以及海量数据存储场景,即使MongoDB支持条件查询,但还是应该把关系查询交给
关系数据库,因为MySQL的稳定可靠性以及运维成本上来说都是相当成熟的。最后还是回到了文章开头的图,分析如何设计业务可扩展性强,并能够快速响应需求变更的平台化微博架构,是需要在前人的经验基础上,结合自身业务的实际情况不断总结,不断重构才能实现。当然,这种技术方案的问题就在于技术体系庞大,开发周期长,需合理考虑多方因素。

最后是我在开发微博系统中曾经遇到的一些问题或思考,有的已解决,有的未解决或者解决方案不妥,希望拿出来分享讨论。

微博ID生成问题。如果仅有单库单表,可以直接通过数据表自增主键实现,但是一旦未来数据拆分,分库分表将会极其痛苦。一种可选的方案是用userid+timestamp时间戳拼装的字符串作为微博的ID,如果精确到微秒级,理论上是不会发生ID重复问题的,但是该方案需要微博ID为字符串,而且微博ID过长,在前端JS处理时也需要格外小心;另一种解决方案是在每台DB或者Redis上建一张表或者是一个Key,每次生成微博的时候都先去修改这个自增字段来获取ID,也可以通过将每台DB设置一个编号,在ID可用范围内,将其作为自增ID的开头,这样也方便在日后对数据库故障进行定位。当然,最靠谱的还是需要提供一个全局的计数服务,供外部调用。

微博列表数据拉取问题。由于每时每刻都有大量新数据产生,因此微博列表的结果集其实都是在实时改变的,倘若此时仍以数组偏移量来作为分页查询的条件,则很有可能拉取的新数据会与上面的数据重复或者出现拉不到数据的情况。为了解决这个问题,可以在每次拉取数据的时候,将最后一条的微博ID作为游标传给查询语句SELECT * FROM feeds WHERE id > cursor LIMIT 20;这样,始终拉取与页面最新或最旧一条相关的20条微博,就不会找到重复数据了。

Redis脏数据处理。由于使用了Redis的List数据结构,因此若因程序Bug插入了一些脏数据导致列表页显示不正常,可以通过直接定位List的key进行删除,或者扫描List对脏数据进行清理(不建议经常使用)。

Reids数据拆分问题。Redis默认提供了16个数据库,尽管该配置可以修改,但由于Redis会将所有数据全部存在dump.rdb单个文件中,如果在未来需要对数据进行拆分的时候将会变得很困难,因此在使用Redis的时候应尽早做出规划,例如通过多个端口开启多个Redis服务,对数据进行隔离。

由于Redis早期版本无hash结构,导致数据存储中内存使用量偏大。该问题从不是问题,随着版本的升级逐渐变成了问题,可以尝试通过脚本将原有数据转化成hash结构存储,当然相应的程序代码也需要改变。

Redis的List数据量过大,SORT操作排序会产生性能问题。SORT操作的时间复杂度O(N+M*log(M)),这种操作只能尽量减少或避免,或者将排序操作转移至关系型数据库中实现。

Redis未开启VM机制,若rdb文件过大超出可用物理内存,导致Redis无法启动。目前除了增加内存或者开启VM之外,没有太好的办法。

Redis单点故障。目前较为稳定的做法应该是对Redis做主从复制,难不成真的从MySQL中将数据重新灌至Redis 。

一路走来,参考过不少优秀的文档和技术博客,对我的设计有很大的帮助和指导意义。以上就是我在微博设计开发中的一些经验总结,只言片语虽不成文,我也不是什么架构师,但仍希望能给看客一些有价值的参考,避免再走弯路。如果你有更好的想法和设计,欢迎交流学习。

本文固定链接: http://www.chepoo.com/weibo-system-design.html | IT技术精华网

浅谈微博系统设计:等您坐沙发呢!

发表评论