美拍:大中型 SNS 系统设计漫谈 | UPYUN Open Talk NO.4
洪小军 美拍架构平台负责人
业务系统架构演化
一、计数器服务
谈到业务系统的架构,首先要说计数器服务,虽然看起来是个小功能,如果将计数器整个历程和深层次的东西掌握清楚,会发现很多东西是相通的。通过简单的业务,挖掘背后的技术,是一个很好的学习过程。
SNS最重要的就是社交关系,以及用户信息。在美拍你可以看到美拍评论,有评论数、点赞数,微博有评论数、转发数、点赞数,这些都需要通过计数器来完成。
1、实现计数器最粗暴的方式:数据库直接count
一些产品在开始上线的时候,会用非常粗暴的方式——在数据库中把数据count出来。
计数器服务在SNS系统中属于TOP级别的访问大户,甚至是TOP1,这个简单粗暴的方式自然会面临一些问题:因为没有独立的计数器,每次都需要从数据库count,会出现大量慢查询、慢请求问题。
2、大部分公司常用的方案:独立的计数器表
上面粗暴的计数器方式,没有独立的计数器,每次都要从数据库count。而今大部分公司或系统会独立维护计数器,建立计数器表,包括ID表和纯计数表,每次变更都需要update,与此同时前面还有一个memacached,用来扛大量热的请求。这个方案在前期没有什么问题,但发。展到一段阶段,会出现很多接口响应时间过长的问题。
回到SNS系统的特点,数据库中会有大量的update/select操作。比如美拍,某一个明星发了一条信息,瞬间会有很多评论、点赞,计数表会频繁的update。另外一种情况是,大量的信息是没有被评论和转发。SNS系统只要存放这些评论和转发数等于0的数据,就会有大量的select,这就需要缓存的容量足够大,将所有的数据装进去。
SNS系统还有一个特点,就是长尾特性很明显,数据分布上有一条很长的尾巴。比如最近三个月占90%,比如第4、5、6个月,占比是很均衡的,这就是长尾的特点,这对缓存提出很大的挑战。假设你可能要保证缓存命中率90%,可能要花5G内存,如果到95%,就需要10G,如果要到99%,可能就要100G或200G,这就对容量提出很高的要求。
可进行的优化:
数据库合并写入,即程序段合并写入请求,有起到一定效果,但是不会太明显。
缓存金扩容尽可能存放更多数据,但由于SNS系统的长尾效应还是很难达到非常高的命中率。
3、一定规模公司采用的方案:Redis内存存储
3.1 架设在内存基础上的架构
内存缓存,从请求的访问策略来看,从缓存读取,cache不到,再从mysql读取;命中率取决于它的容量。如果是内存存储,命中率是100%。对于容量要求,很多地方会说存储需要的容量比缓存大很多,但在具体的产品中并不完全是这样。比如,在计数器服务中,采用存储的方式,50%的美拍是没有评论的,存储只需要存50%有被评论过的就足够了。但是采用内存缓存的方式,是没有这个策略的,可能需要存很多没有被评论过的信息。
Redis 内存存储:
Redis是全内存存储,在SNS系统计数器中只需存存放所有计数器>0的数据 ,具有高速的读取性能,保证接口的响应时间可控,这也是大多数公司采用的方案,也比较可控。
3.2 长尾数据
随着时间增长,会出现大量的长尾数据,尤其是SNS系统,就像微博发展4、5年前的数据特别多,全部写入内存成本太高。长尾访问,不需要用内存这么高的成本去支撑。
长尾问题解决方案:
1)做冷热数据分离,将最近个月的数据放在内存,更早的宕到SSD上,成本比较低,这是一个折中方案;
2)通过修改redis,实现更紧凑的存储。
二、FEED服务
FEED业务指的是,好友的消息列表,美拍上的好友动态,微博就是首页的列表。FEED常用推、拉两种模式。
推模式:
推模式写入消息的成本会比较高,相当于发一条美拍需要推给所有关注我美拍的人。推的数量取决于有多少人关注我。相反获取信息的成本非常低,只需简单的SELECT就可以取出来,推模式是基于空间去换时间的策略。
在这种情况下,需要考虑3个问题:
1)容量是否会成为瓶颈;
2)推送量大的情况下,会出现延迟问题;
3)加关注/减关注这类变更通知的成本非常高。
拉模式:
拉模式是和推模式相反的方式,轻量写入。获取消息,先将关注的列表取出来,再将关注的每个人反的美拍列表取出来,最终合并。这是用时间换空间,拉取需要很多计算,会影响到接口的响应时间,需要大量的计算,需要获取大量的资源,这样会造成需求的带宽增多。
因此,在好友多、粉丝少的情况下,推的模式最好。好友少和粉丝多的情况,比较适合拉的方式。
现在的SNS系统大多都是好友多,粉丝又多。在这种情况下,一些业界公司采用了推拉结合的方法,比如Twitter。普通用户(粉丝数少的用户)采用推的方式,大V采用拉的方式。
在发布信息的时候会通过粉丝数及其他策略包括是否在线等状况做推拉的判断。微博,公开性的微博都是基于拉的方式,私密性的功能,都是基于推的方式。
FEED服务中的重点:
FEED服务中数据库和缓存是两个很重要的点。
在业务快速发展的阶段,需要频繁的增加新的产品属性,每个属性都需要在线变更,这样会对在线用户产生影响,同时也很增加成本。
常用的解决方法:索引和内容分离,各司其职。索引保证高效的查找,所有的查找都在索引里完成。索引可能会有不同级别、维度的索引。内容使用KV结构(V为二进制数据),便于数据变更。在这里内容需要尽可能紧凑的存储。
数据库定位:
数据库只是作为存储,不应该有任何逻辑,因为系统追求的是高性能的读取访问。最多有V条件,不应有任何业务逻辑,业务逻辑应该放到程序端处理。
数据库面临很大的写压力,常用的是分库分表的方式,但会出现两个问题,大部分数据库都有自身的IP,跟其他的表关联,分库分表就不能用智能IP,可能会造成IP冲突。
分库分表策略:
最常见的就是,每个库里都有N张表,基于HASF分表。但到一定阶段会出现一些问题:
1)特殊用户查询性能很高,这种用户可能有几百万甚至上千万的记录,一旦穿透到数据库,性能会很糟糕。
2)到一定规模后,会考虑进行硬件的升级,如果将所有的历史数据存放SSD,很浪费。
为解决这个问题,很多公司采用了基于时间分片的方案。最好的方法是建立一个索引表,在索引表里存放每个月用户有多少条数据。
三、缓存:雪崩
说道缓存,先补充一个雪崩的概念。
假设系统有4,5个节点,每个缓存节点命中率是90%,也就是每个节点要承担接近20%的访问量,如果这个时候有1个节点坏了,这个请求会全部打到memacache,整个系统很可能就直接宕掉。这就是所谓的雪崩。
解决方案:
1、在这个阶段,程序端做两节缓存,使得请求在某个节点坏掉了的时候,不会直接到memache,然后请求另外一个节点。
2、随着业务增长,需要扩充。解决方法:
1)扩充节点到20个,但可能出现multiget hole问题,在这种情况下,导致性能下降。
2)搭建多集群。
系统设计的基本原则
CAP原则:一致性、可用性、可扩展性
在SNS场景中,往往上面的三个点,只能选择其中两个,需要一定的折中。最主要的是扩展性和可用性。当然不是说一致性不重要,可以采用最终一致的方法做补充。
前期:快速搭建系统上线。
中期:重点解决可扩展性问题。
通过硬件升级、单机性能优化,提高单机性能,以及采用SACLE分布式支持,可以解决扩展性问题。
SNS系统高可扩展性需要关注以下几个点:
1、支持高并发读取,首先第一点就是支持大量级的接口调用。其次还需要保证很低的响应时间。
2、支持峰值的写入。最重要的一点是前后端分离,前端处于永远可写的状态。(用户成功发布内容后,显示成功,实际并没有完全成功,后面的事情交给后端去做)
3、做好分布式部署,包括一个机房内部多集群,最好是有条件做到多机房。多机房部署,要保证每个机房之间有可靠的消息同步,要避免跨机房读调用。
(多机房如何将主要资源同步:SYSQL可采用master方式,Redis有条件的可采用master/salve方式)
中后期:很多系统需要解决可用性问题。
可用性问题比前面的问题都更加有挑战。
1、避免单点故障。
2、保证响应时间。