Sharding & IDs at Instagram(转)
英文原文:http://instagram-engineering.tumblr.com/post/10853187575/sharding-ids-at-instagram
译文:http://www.cnblogs.com/xiekeli/archive/2012/07/10/2584255.html
Instagram的存储量非常大,差不多每秒25-90张照片。为了保证我们的重要的数据能够合理的存储以便快速的提取应用,我们对数据进行了分片 -- 换句话说,将数据放到很多小的桶(buckets)中,每个桶存储一部分的数据。
我们的应用服务器跑的是Django ,后端数据库采用PostgreSQL 。我们决定采用分片后,第一个问题是我们是否还保留我们的主数据库,是否应该转换到其他的存储方案。我们评估了一些不同的NOSQL的解决方案,但是最后觉得最适合我们的需求的还是通过PostgreSQL server集群实现分片。
在将数据写入数据库集群前,我们必须解决如何分配数据唯一标识的问题(例如,上传上来的每一张照片)。在一个单数据的情况下,典型的解决方案是使用数据库原有的自增主键特性即可,但是这种方法在同时插入多个数据库的情况下不可行。这篇博客其他的部分将讲述我们如何解决这个问题的。
开始之前,我们列出了我们系统的基本特性:
生成的ID应该按时间排序(这样一个照片ID 的列表就足够了,并不需要照片的其他信息)
ID应该是64位的(为了更小的索引以及更好的存储在像Redis这样的系统中)
该系统应引入尽可能少的考虑新的“移动部件”(这里指额外的技术部件) --- 我们如何能够一直用很少的工程师扩展Instagram是因为选择简单、易于理解的解决方案。(The system should introduce as few new ‘moving parts’ as possible—a large part of how we’ve been able to scale Instagram with very few engineers is by choosing simple, easy-to-understand solutions that we trust. )
现成的解决方案
关于ID生成有很多现成的解决方案,这些是我们曾经考虑过的:
在web程序中生成ID
这个方案是将整个ID生成逻辑放到应用程序层而非数据库层。例如:MongoDB的ObjectId,采用12个字节的长度,并且将时间戳进行编码。另外一种流行的方案是使用UUIDs。
赞成者:
每个应用程序线程独立生成ID,这样最大限度减小了生成ID冲突的概率
如果你采用时间戳编码,得到的ID将保持按时间排序、
反对者:
通常需要更多的存储空间(96位或更高)以保证唯一性约束
一些UUID类型是完全随机的,没有自然顺序
通过专门的服务生成ID;
例如:Twitter的 Snowflake,一个Thrift 服务使用Apache ZooKeeper 去协调节点并生成64位的唯一标识ID
赞成者:
Snowflake ID是64位的,只有UUID的一般大小;
可以使用时间编码,保持有序性
适用于分布式系统(Distributed system that can survive nodes dying )
反对者:
将引入额外的复杂性和更多的“移动部件”(ZooKeeper, Snowflake servers)
DB Ticket Servers
使用数据库的自增长技术保证唯一性。Flickr 采用这种方案,但是使用了两个ticket DBs(一个生成奇数,一个生成偶数),用于避免单点的失败情况;
赞成者:
数据库比较好理解,也有很好的可扩展性
反对者:
最后可能会变成写的瓶颈(虽然据Flickr,已经有很大的规模,也没有发现问题 )
需要管理一对额外的机器(或EC2实例)
如果使用单个数据库,将无法避免单点冲突。如果使用多个数据库,将不再能保证按时间排序。
上面这些方法,Twitter的 Snowflake 是最接近的,但是额外的复杂性是需要跑一个ID服务。取而代之,我们采用了一种概念类似,但是内建于PostgreSQL中的方案。
我们的方案
我们的分片系统由数千个通过代码映射到少量物理分片的逻辑分片组成。使用这种方案,我们可以刚开始使用少量是的数据库服务器,最后逐渐增加,也很容易实现将一些逻辑分片从一个数据库转移到另一个数据库,而不需要re-bucket 数据。我们通过Postgres的schemas特性使脚本化和管理工作变得很简单。
Schemas(不要与单个表的SQL schema 概念混淆)在Postgres中是一个逻辑组。每个Postgres数据库可以有几个schemas,每个schema包含一个或多个表。表名在每个schema中必须唯一。而不是在每个数据库中。默认Postgres 会将创建的东西放到一个叫‘public’的schema 中。
在我们的系统中,每个“逻辑分片”就是一个Postgres 的schema ,每个分片的表(例如,像我们的照片表)存在于每个schema中;
我们通过PL/PGSQL(Postgres’ 的内部编程语言)和Postgres的自增长功能完成每个分片内每个表的ID创建工作。
我们每个ID有以下几部分组成:
1、41位时间的毫秒部分(gives us 41 years of IDs with a custom epoch / 使我们拥有41年的自定义纪元?)
2、13位表示逻辑分片的ID
3、10位表示自增长序列,最大1024.这意味着每个分片,每毫秒我们可以生成1024个ID。
让我们来看一个例子:假设现在是2011年9月9日,上午5:00,我们的纪元从2011年1月,那么从纪元开始到现在就有1387263000毫秒。所以我们生成ID 的时候,通过左移符号将这个值填入最左面的41位:
id = 1387263000 << (64-41)
接下来,我们将要为需要插入的数据进行ID 切片,我们使用user ID,假设有2000个逻辑分片,而我们的user ID是31341,这样分片ID是31341% 2000 -> 1341,将1341填入到接下来的13位中;
id |= 1341 << (64-41-13)
Finally, we take whatever the next value of our auto-increment sequence (this sequence is unique to each table in each schema) and fill out the remaining bits. Let’s say we’d generated 5,000 IDs for this table already; our next value is 5,001, which we take and mod by 1024 (so it fits in 10 bits) and include it too:
id |= (5001 % 1024)
最后,我们将通过自增长序列(这个序列的唯一性针对在每一个schema的每一个表,即每个schema的每个表有自己的序列)获取下一个值,并填入到余下的位中。假设我们已经为这个表生成了5000个序列ID,我们下一个序列ID应该是5001,我们用5001模1024,即为余下的ID值:
id |= (5001 % 1024)
我们现在已经有了我们的ID,我们可以通过INSERT语句的RETURNING 关键字,将ID返回给应用程序;
这里是the PL/PGSQL的完整例子(例子的schema :insta5):
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;
And when creating the table, we do:CREATE TABLE insta5.our_table (
"id" bigint NOT NULL DEFAULT insta5.next_id(),
...rest of table schema...
)
就这样!主键在我们的应用程序中是唯一的(额外的好处,包含在其中切分ID为更易于映射)。我们已经将这一方案应用到我们的产品中了,到目前为止效果良好。有兴趣帮助我们找出问题吗?我们正在招聘!
Mike Krieger, co-founder
Sharding & IDs at Instagram(转)的更多相关文章
- Sharding & IDs at Instagram, Flickr ID generation
Instagram: http://instagram-engineering.tumblr.com/post/10853187575/sharding-ids-at-instagram Flickr ...
- Instagram 在 PyCon 2017 的演讲摘要
Instagram 在 PyCon 2017 的演讲摘要 PyCon 简介 PyCon 是全世界最大的以 Python 编程语言 为主题的技术大会.大会由 Python 社区组织,每年举办一次.在大会 ...
- PyCon大会Python主题演讲摘要
PyCon 是全国际最大的以 Python 编程言语 为主题的技能大会.大会由 Python 社区组织,每年举行一次.在大会上,来自国际各地的 Python 用户与中心开发者齐聚一堂,共同同享 Pyt ...
- Unique ID Generate Notes
Unique ID generation in distributed systems http://www.slideshare.net/davegardnerisme/unique-id-gene ...
- Instagram的技术探索2(转)
原文:http://www.cnblogs.com/xiekeli/archive/2012/05/28/2520770.html 前一篇翻译了Instagram blog上的一篇文章<What ...
- Instagram的技术架构
http://blogread.cn/it/article/5497 Instagram 被 Facebook 以10亿美金收购.团队规模:13 人.而在被Facebook收购前的一个月,整个团队才7 ...
- Instagram的技术探索(转)
add by zhj: 略有修改 原文:http://www.cnblogs.com/xiekeli/archive/2012/05/28/2520770.html 前一篇翻译了Instagram b ...
- Python向来以慢著称,为啥Instagram却唯独钟爱它?
PyCon 是全世界最大的以 Python 编程语言 为主题的技术大会,大会由 Python 社区组织,每年举办一次.在 Python 2017 上,Instagram 的工程师们带来了一个有关 Py ...
- How Instagram Feeds Work: Celery and RabbitMQ(转)
原文:http://blogs.vmware.com/vfabric/2013/04/how-instagram-feeds-work-celery-and-rabbitmq.html Instagr ...
随机推荐
- Nginx proxy_pass 加与不加 "/" 区别
下面四种情况分别用http://192.168.1.100/proxy/test.html 进行访问. 第一种: location /proxy/ { proxy_pass http://127.0 ...
- java中进程与线程的三种实现方式
一:进程与线程 概述:几乎任何的操作系统都支持运行多个任务,通常一个任务就是一个程序,而一个程序就是一个进程.当一个进程运行时,内部可能包括多个顺序执行流,每个顺序执行流就是一个线程. 进程:进程是指 ...
- postgresql之数据字典
greenplum是基于postgresql开发的分布式数据库,里面大部分的数据字典是一样的.我们在维护gp的时候对gp的数据字典比较熟悉,特此分享给大家.在这里不会详细介绍每个字典的内容,只会介绍常 ...
- 09day2
多米诺骨牌 递推+高精度 [问题描述] Jzabc 对多米诺骨牌有很大兴趣,然而他的骨牌比较特别,只有黑色的和白色的两种.他觉得如果存在连续三个骨牌是同一种颜色,那么这个骨牌排列便是不美观的.现在他有 ...
- PHP5.3, PHP5.4, PHP5.5新特性
PHP 5.3中的新特性 1. 支持命名空间 (Namespace) 2. 支持延迟静态绑定(Late Static Binding) 3. 支持goto语句 4. 支持闭包.Lambda/Anony ...
- poj 2127 LCIS 带路径输出
这个题 用一维 为什么错了: 因为 用一维 dp 方程肯定也是一维:但是有没有想,第 i 个字符更新了 j 位置的最优结果,然后 k 字符又一次更新了 j 位置的最优值,然后 我的结果是 i ...
- vector & array
private static const NUM_LOOPS:int = 15; public function VectorTest():void { var vector:Vector.<i ...
- php 使用date()函数的报错
错误提示: Warning: date(): It is not safe to rely on the system's timezone settings. You are *required* ...
- 给产品经理讲技术,不得不懂的TCP和UDP
TCP/IP协议,你一定经常听说吧,其中TCP(Transmission Control Protocol)称为传输控制协议,IP(Internet Protocol)称为因特网互联协议,好吧,这都是 ...
- 使用 foreach 操作数组
foreach 并不是 Java 中的关键字,是 for 语句的特殊简化版本,在遍历数组.集合时, foreach 更简单便捷.从英文字面意思理解 foreach 也就是“ for 每一个”的意思,那 ...