每秒上传超过25张图和90个“喜欢”,在Instagram我们存了很多数据,为了确保把重要的数据都扔到内存里,达到快速响应用户的请求,我们已经开始把数据进行分片-换句话说,把数据放到更多的小桶子里,每个桶了装一部分数据。
在写数据到不同服务器之前,还需要解决一个问题,如何给在数据库里的每块数据都标识上唯一的标识(如,发布到我们系统的每张图)。单库好解决,就是用自增主键-但如果数据同时写到多个库就不行了,本博客将回答如果解决这个问题。
现有的解决方案
在web应用层生成ID
优点:
- 每个应用服务生成的ID是独立的,生成时将失败和竞争降到最小;
- 如果用时间戳作为第一部分,就可以按时间排序
由单独的服务提供ID生成
优势:
- Snowflake生成的ID是64位,只用UUID的一半大小;
- 可以把时间排到前面,可以排序;
- 分布式系统可以保证服务不会挂掉;
数据库计数服务器
所有以上的方法中,Twitter的Snowflake最接近,但添加生成ID服务了复杂调用又冲突了,替换的方案是,我们使用了概念类似的方法,但是从PostgreSQL内部特性实现的。
我们的分片系统由几千个逻辑分片组成,由代码指向极少的几个物理分片,用这个方法,我们可用少数几台服务器就可以实施起来,以后也可以扩展到更多,只要简单的将逻辑分片从一台物理数据器移到另外一台,不需要重新聚合各分片的数据,我们用PostgreSQL的schema特性很容易就做到实施和管理。
我们系统里每个逻辑分片就是一个schema,每个分片的表(如,照片的“喜欢”功能)存在于每个schema中。
每个ID包含有:
15位表示逻辑ID;
看个例子:
id = 1387263000 << (64-41)
id |= 1341 << (64-41-13)
id |= (5001 % 1024)
下面是完整的PL/PGSQL代码(例子中的schema是 insta5):
<code>CREATE OR REPLACe FUNCTION insta5.next_id(OUT result bigint) AS $
DECLARE
our_epoch bigint := 1314220021721;
seq_id bigint;
now_millis bigint;
shard_id int := 5;
BEGIN
SELECT nextval('insta5.table_id_seq') %% 1024 INTO seq_id;
SELECT FLOOR(EXTRACT(EPOCH FROM clock_timestamp) * 1000) INTO now_millis;
result := (now_millis - our_epoch) << 23;
result := result | (shard_id << 10);
result := result | (seq_id);
END;
$ LANGUAGE PLPGSQL;
</code>就这些!主键在所有应用层都是唯一的(另外的好处是,包含了分片ID这样做映射就很容易),这个方法我们已经用到生产环境了,结果到目前为止令人满意,如果您对扩展问题能帮助我们,我们正在招人!
<译者:朱淦 350050183@qq.com 2015.7.29>
