Categories
technology|技术 中文

支撑10亿用户的技术方案(细节篇)

  • 在思路篇中,我们谈到了解决海量用户的两个基本思路:1.化整为零,逐个击破;2.抽丝剥茧,直达核心。在这篇文章中,我们会对这两个策略中遇到的技术细节问题,进行详细的分析。

何为化整为零?其中有哪些需要注意的技术细节?

用技术的语言来解释化整为零,就是:分库分表。在思路篇中,我们谈到可以把10亿用户分成100份1000万的,那么分表就是解决1份1000万用户访问效率的问题,而分库就是对10亿用户进行的100份拆分。

分表是解决慢查询的一个非常实用的方案,分表一定要根据实际的使用场景来进行,最核心的原则就是:保证面向用户端的业务只需要查1~2次表(大部分1次,少量2次),就可以获取到所有的数据,而不需要查询所有相关的表做并集(内部的统计数据多查一些表,慢一些,是无所谓的,因为查出来后就可以存到新的数据表里)。

举例说明,抛砖引玉:Twitter这样的应用,1000万用户,平均每个用户follow 200个人,那么follow表的数据量将高达20亿条,那么从这样一个巨大的表中查询某个用户的数据,必然是很耗时的,这就是“慢查询”。

按照分表的思路,我们把这个follow关系表定义如下,以mysql数据库来举例:

CREATE TABLE tb_follow (
id bigint unsigned NOT NULL AUTO_INCREMENT,
user_id bigint unsigned NOT NULL,
target_user bigint unsigned NOT NULL,
created_at bigint NOT NULL DEFAULT '0',
updated_at bigint NOT NULL DEFAULT '0',
PRIMARY KEY (id),
UNIQUE KEY uiq_user_id_follwer_id (user_id,target_user),
KEY idx_follower_id (target_user)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

CREATE TABLE tb_follower (
id bigint NOT NULL AUTO_INCREMENT,
user_id bigint NOT NULL DEFAULT ‘0’,
follower_id bigint NOT NULL DEFAULT ‘0’,
created_at bigint NOT NULL DEFAULT ‘0’,
updated_at bigint NOT NULL DEFAULT ‘0’,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

上面我定义了两个表,通过user_id可以从tb_follow查到follow list,通过user_id可以从tb_follower查到follower list,User A关注User B,将会在tb_follow中插入user_id=A_uid, target_user=B_uid,会在tb_follower中插入user_id=B_uid, follower_id=A_uid,即:1个follow关系,2条数据。

然后对tb_follow和tb_follower表都按照user_id拆分成10000个表,那么每个表的数据就变成了20万个,这样的数据量就很小了,查询起来就不会有慢查询了。

同理,在整个架构中的cache,kafka,也可以根据实际的需求进行拆分,避免单个实例的压力超出可承受的范围。

如何抽丝剥茧,找到性能瓶颈的核心所在?

在压测之后,我们通常会收到一份报告,类似下面这样的:

通过这份报告,可以看到正常请求几十毫秒的API,在压测的情况下,有的平均时间居然达到了100秒以上,是不是惊呆了?就问你慌不慌?

  • 先来对压测的请求做第一层分解:

压测机器发起请求,通过网络链路达到应用的机器的网卡(中间通常会有load balancer进行流量分发);网卡把命令发送给web service再转发给应用程序执行请求,并获取结果;结果通过网卡传输到网络返回到压测机器。

这里跟应用程序相关的:

一是网络,网络流量是否会占满带宽或者网卡?现在的服务器网卡基本上千兆级别,一般不会遇到网卡占满的情况,但是带宽被占满是有可能的,例如:压测的时候是通过外网访问的,而外网的带宽会有限制,而通过内网访问通常不会有这个问题

二是应用服务器,需要查看下应用服务器的cpu占用是否过高,内存是否占满,磁盘空间是否足够?另外,还有一个新手不了解的细节:文件描述符与并发tcp连接数的关系及限制。这个细节,可以通过google查相关的关键字,以及ulimit去做详细的了解,当你了解清楚之后,就会知道服务器上的相关参数是否限制了并发数。

  • 第二层分解,主要针对应用服务器内部执行的细节进行:

Web service把请求分发给应用程序进行执行,执行通常会有的操作:判断参数执行代码逻辑,读写cache,读写db,有的会包含写日志文件。

代码本身逻辑的问题需要具体问题具体分析,这里不做论述。关于Cache需要注意几点:1.命中率是否足够高(高访问量的情况下通常应该超过99%);2.单个实例是否被用满了(单实例250 million objects);3.实例的带宽、CPU等使用情况。

当然,最最重要的还是要分析db了。通常db出现性能问题,可以从错误日志中得到信息,例如:connect out of time。对于db的优化需要关注以下几点:

1.慢查询:可以通过优化查询、建立索引,来减少慢查询;当然,最最核心的还是通过分表来减少单表的数据量,以提升查询效率

2.实例的连接数:db instance都是有连接数限制的,通过调整相关参数,可以提高连接数,还可以通过建立连接池来提高连接的复用性;在参数都优化到极致之后,cpu和内存都耗尽,无法再提高上限,那么就需要用到分库的策略了,增加多个db instances来分担应用的连接。分库的规则可以参考分表的原则,基本上是一致的。

Leave a Reply

Your email address will not be published.