能够进入互联网相关公司做开发工作的,或多或少都了解分布式扩容等内容,或者从书本上学到,或者从博客中找到。但到用的时候却很少考虑以后怎么扩容,怎么更方便的扩展。本文结合我在实际工作中碰到的扩容的情况进行实例说明。


说个现实一点儿的现象,在新项目初期基本上不考虑以后怎么扩容谁去扩容的问题,某次其他组的一个同学问我:愿哥,我们有个线上的业务需要扩容,怎么办?我问他采用什么方式分片的,他告诉我hash取模。我头晕,这。。。没啥好办法,只能停服。如果不能停服,只能用双写移库的方法。


以后再考虑扩容?

这是典型的不负责任的表现,以我这几年对项目的观察看,一般扩容发生在两年后,而新项目如果还算成功的话,第一年基本上该拿的奖基本上都拿了,该被肯定的也被肯定了。第三年的时候,主程还在不在还不好说,大多数项目的初期开发者都离职了。而没有离职的都把项目转手给其他人了,而其他人用1周解决,还是用两个月解决不关最初设计者的事情了。当然,如果是dau成直线上升的项目除外,这种项目最考验初期架构师的能力,如果设计不好,真的会坑自己。


为什么后期后期难重构?

业务是延续的,如果是一个重要的业务,肯定是不会停掉,给你留下慢慢的搞的时间,老板的期望是你能够立即解决问题。这时你说,初期没考虑扩容。。。我刚来北京的时候,当时做农场项目的两个公司就是,同样的项目,同样的DAU爆发式增长。一个公司的项目因为难以扩容,结果在一周之后所有用户都走光了,而另外一个公司因为初期架构方便扩容,开启了辉煌的一章。


双写移库,其实是我认为一种比较low的懒惰的方法,是让别人接坑的方法。说下特点:

1. 影响接口速度,双写无疑会增加调用redis的次数,增大时间,提高错误率。

2. 周期,从4个库,移动几百G的数据,在线上 用多长时间,开着飞机换轮子是否会造成出现偏差。在之前给某业务导数据过程中,挂死过一个redis,导致线上的业务出现问题。


那么程序设计之初就要有明确的目标:

 1. DBA如果能在提供固定实例端口的基础上,内容消化扩容,那么问题就好办了,也就没有以下内容。

我们曾经碰到过一种case,DBA提供了4个端口,然后通过内部中间件实现,然而,大约两年过后,中间件维护者离职了,此方法就再也没有人维护了。然后3年后,DBA说单台机器空间打满了,需要扩容。此时接收的RD满满都是泪。

  

 2. 你存储的信息是什么,根据不同的内容其实是有不同的方案的,如果存共享session这类临时数据,那么无疑一致性hash是最好的解决方案,运维增加一台机器或者减少一台机器,顶多会造成,一部分用户重新登录下,不会造成其他问题。

   而如果存储的用户信息,是不能随便增加节点和减少节点的,因为任何变动都会引起用户信息的丢失,这种丢失不可逆,不能通过重新登录解决。此时,对于你采取 一致性hash,取模,或者区间算法实际是相同的。

 3. 扩容:扩容的目标应该是,解决问题而又线上业务没有问题,这个应该最高优先的目标。也就是,优化是程序架构侧的事不是产品层面的事,不应引起系统服务的波动。


一致性hash

有些时候还是需要客户端在代码中进行hash,这时候选择hash算法尤为重要。有些项目中,架构师用了一致性hash算法做分库分表,其实真的没有考虑后期缩容和扩容的问题。

一致性hash算法主要解决的是从N到N-1的问题。

image.png

结合redis的集群架构槽指派思路,我在某个项目中使用了区间算法,理由如下:

1. 由一个简单的算法如md5 (uid) 取得最后两位,哈希到00-FF共计256个节点,假设分配如下:00-3F  40-7F 80-BF  C0-FF  分别对应4个redis实例A B C D。此时如果4个redis满了,想尽快增加端口解决问题。可以2中的步骤。


2. 对于00-3F的数据,可以摘取一个从库,单独最为主库。如A  A1, 此时A和A1中的数据是一样的。然后修改配置 00-1F到A上面, 20-3F到A1上面,这样就完成了扩充端口。

然后再写一个脚本,线下删除A里面根据算法落到20-3F的数据,A1里面根据算法落到A的数据。这样,应该会很快的删除到制定大小,无疑删除数据远大于移动写入数据的速度。


Redis集群架构

image.png

某项目一致性hash的算法示意图:image.png

区间算法示意图:image.png


总之,当你选择服务的时候,你会选择一种在你扩容的时候对线上有影响,还是一种靠线下努力,按照步骤对线上没影响的扩容方案呢。

image.png