分布式架构系统生成全局唯一序列号的一个思路

标签: dev | 发表时间:2017-11-18 08:00 | 作者:
出处:http://itindex.net/relian
作者简介 

丁宜人,10年java开发经验。携程技术中心基础业务研发部用户中心资深java工程师,负责携程账号的基础服务和相关框架组件研发。之前在惠普公司供职6年,负责消息中间件产品研发。


一、相关背景


分布式架构下,唯一序列号生成是我们在设计一个系统,尤其是数据库使用分库分表的时候常常会遇见的问题。当分成若干个sharding表后,如何能够快速拿到一个唯一序列号,是经常遇到的问题。


在携程账号数据库迁移MySql过程中,我们对用户ID的生成方案进行了新的设计,要求能够支撑携程现有的新用户注册体量。


本文通过携程用户ID生成器的实现,希望能够对大家设计分库分表的唯一id有一些新的思路。

二、特性需求


  1. 全局唯一

  2. 支持高并发

  3. 能够体现一定属性

  4. 高可靠,容错单点故障

  5. 高性能

三、业内方案


生成ID的方法有很多,来适应不同的场景、需求以及性能要求。


常见方式有:


1、利用数据库递增,全数据库唯一。


优点:明显,可控。

缺点:单库单表,数据库压力大。


2、UUID, 生成的是length=32的16进制格式的字符串,如果回退为byte数组共16个byte元素,即UUID是一个128bit长的数字,一般用16进制表示。


优点:对数据库压力减轻了。

缺点:但是排序怎么办?


此外还有UUID的变种,增加一个时间拼接,但是会造成id非常长。


3、twitter在把存储系统从MySQL迁移到Cassandra的过程中由于Cassandra没有顺序ID生成机制,于是自己开发了一套全局唯一ID生成服务:Snowflake。


1  41位的时间序列(精确到毫秒,41位的长度可以使用69年)
2  10位的机器标识(10位的长度最多支持部署1024个节点) 
3  12位的计数顺序号(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号) 最高位是符号位,始终为0。


优点:高性能,低延迟;独立的应用;按时间有序。

缺点:需要独立的开发和部署。


4、Redis生成ID


当使用数据库来生成ID性能不够要求的时候,我们可以尝试使用Redis来生成ID。这主要依赖于Redis是单线程的,所以也可以用生成全局唯一的ID。可以用Redis的原子操作INCR和INCRBY来实现。


可以使用Redis集群来获取更高的吞吐量。假如一个集群中有5台Redis。可以初始化每台Redis的值分别是1,2,3,4,5,然后步长都是5。各个Redis生成的ID为:


A:1,6,11,16,21

B:2,7,12,17,22

C:3,8,13,18,23

D:4,9,14,19,24

E:5,10,15,20,25


比较适合使用Redis来生成每天从0开始的流水号。比如订单号=日期+当日自增长号。可以每天在Redis中生成一个Key,使用INCR进行累加。


优点:

不依赖于数据库,灵活方便,且性能优于数据库。

数字ID天然排序,对分页或者需要排序的结果很有帮助。

使用Redis集群也可以防止单点故障的问题。

 

缺点:

如果系统中没有Redis,还需要引入新的组件,增加系统复杂度。

需要编码和配置的工作量比较大,多环境运维很麻烦,

在开始时,程序实例负载到哪个redis实例一旦确定好,未来很难做修改。


5.   Flicker的解决方案


因为MySQL本身支持auto_increment操作,很自然地,我们会想到借助这个特性来实现这个功能。

Flicker在解决全局ID生成方案里就采用了MySQL自增长ID的机制(auto_increment + replace into + MyISAM)。


6.还有其他一些方案,比如京东淘宝等电商的订单号生成。因为订单号和用户id在业务上的区别,订单号尽可能要多些冗余的业务信息,比如:


滴滴:时间+起点编号+车牌号

淘宝订单:时间戳+用户ID

其他电商:时间戳+下单渠道+用户ID,有的会加上订单第一个商品的ID。


而用户ID,则要求含义简单明了,包含注册渠道即可,尽量短。

四、最终方案


最终我们选择了以flicker方案为基础进行优化改进。具体实现是,单表递增,内存缓存号段的方式。


首先建立一张表,像这样:


SEQUENCE_GENERATOR_TABLE

id   stub

1    192.168.1.1


其中id是自增的,stub是服务器ip


因为新数据库采用mysql,所以使用mysql的独有语法 replace to来更新记录来获得唯一id,例如这样:


REPLACE INTO SEQUENCE_GENERATOR_TABLE (stub) VALUES ("192.168.1.1");


再用SELECT id FROM SEQUENCE_GENERATOR_TABLEWHERE stub = "192.168.1.1";   把它拿回来。


到上面为止,我们只是在单台数据库上生成ID,从高可用角度考虑,接下来就要解决单点故障问题。


这也就是为什么要有这个机器ip字段呢?就是为了防止多服务器同时更新数据,取回的id混淆的问题。


所以,当多个服务器的时候,这个表是这样的:


id   stub

5    192.168.1.1

2    192.168.1.2

3    192.168.1.3

4    192.168.1.4


每台服务器只更新自己的那条记录,保证了单线程操作单行记录。


这时候每个机器拿到的分别是5,2,3,4这4个id。


至此,我们似乎解决这个服务器隔离,原子性获得id的问题,也和flicker方案基本一致。


但是追根溯源,在原理上,方案还是依靠数据库的特性,每次生成id都要请求db,开销很大。我们对此又进行优化,把这个id作为一个号段,而并不是要发出去的序列号,并且这个号段是可以配置长度的,可以1000也可以10000,也就是对拿回来的这个id放大多少倍的问题。


OK,我们从DB一次查询操作的开销,拿回来了1000个用户id到内存中了。


现在的问题就是要解决同一台服务器在高并发场景,让大家顺序拿号,别拿重复,也别漏拿。


这个问题简单来说,就是个保持这个号段对象隔离性的问题。


AtomicLong是个靠谱的办法。


当第一次拿回号段id后,扩大1000倍,然后赋值给这个变量atomic,这就是这个号段的第一个号码。


atomic.set(n * 1000);


并且内存里保存一下最大id,也就是这个号段的最后一个号码


currentMaxId = (n + 1) * 1000;


一个号段就形成了。


此时每次有请求来取号时候,判断一下有没有到最后一个号码,没有到,就拿个号,走人。


Long uid = atomic.incrementAndGet();


如果到达了最后一个号码,那么阻塞住其他请求线程,最早的那个线程去db取个号段,再更新一下号段的两个值,就可以了。


这个方案,核心代码逻辑不到20行,解决了分布式系统序列号生成的问题。


这里有个小问题,就是在服务器重启后,因为号码缓存在内存,会浪费掉一部分用户ID没有发出去,所以在可能频繁发布的应用中,尽量减小号段放大的步长n,能够减少浪费。


经过实践,性能的提升远远重要于浪费一部分id。


如果再追求极致,可以监听spring或者servlet上下文的销毁事件,把当前即将发出去的用户ID保存起来,下次启动时候再捞回内存即可。

五、上线效果


运行5个多月,十分稳定。

SOA服务平均响应时间 0.59毫秒;

客户端调用平均响应时间2.52毫秒;


附流程图:


推荐阅读:




相关 [分布 架构 系统] 推荐:

FastDFS分布式文件系统架构

- - 企业架构 - ITeye博客
FastDFS分布式文件系统架构.            FastDFS是一个开源的分布式文件系统,她对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题. 特别适合以文件为载体的在线服务,如相册网站、视频网站等等. 二、 FastDFS系统架构.

分布式会话跟踪系统架构设计与实践

- - 美团点评技术团队
本文整理自美团点评技术沙龙第08期:大规模集群的服务治理设计与实践. 美团点评技术沙龙由美团点评技术团队主办,每月一期. 每期沙龙邀请美团点评及其它互联网公司的技术专家分享来自一线的实践经验,覆盖各主要技术领域. 目前沙龙会分别在北京、上海和厦门等地举行,要参加下一次最新沙龙活动. 赶快关注微信公众号“美团点评技术团队”.

分布式系统的负载均衡 | 架构干货

- - SegmentFault 最新的文章
记得第一次接触 Nginx 是在实验室,那时候在服务器部署网站需要用 Nginx. Nginx 是一个服务组件,用来反向代理、负载平衡和 HTTP 缓存等. 负载均衡(LB,Load Balance),是一种技术解决方案. 用来在多个资源(一般是服务器)中分配负载,达到最优化资源使用,避免过载. 资源,相当于每个服务实例的执行操作单元,负载均衡就是将大量的数据处理操作分摊到多个操作单元进行执行,用来解决互联网分布式系统的大流量、高并发和高可用的问题.

分布式文件系统FastDFS设计原理及技术架构

- - mysqlops
FastDFS是一个开源的轻量级分布式文件系统,由跟踪服务器(tracker server)、存储服务器(storage server)和客户端(client)三个部分组成,主要解决了海量数据存储问题,特别适合以中小文件(建议范围:4KB < file_size <500MB)为载体的在线服务. Storage server(后简称storage)以组(卷,group或volume)为单位组织,一个group内包含多台storage机器,数据互为备份,存 储空间以group内容量最小的storage为准,所以建议group内的多个storage尽量配置相同,以免造成存储空间的浪费.

大规模分布式系统架构下调测能力构建之道

- - 互联网 - ITeye博客
大规模分布式系统架构下调测能力构建之道. 最近有朋友辗转找到我,索要我今年参加QCon全球软件开发大会所用的PPT资料. 在这里我将PPT和讲稿做了整理,分享给大家. 这个分享,我首先会给大家总结一下,在分布式环境下做开发,我们都会遇到哪些调测方面的效率问题;并针对这些问题探讨在技术和管理上的应对之道;最后,通过我们所做的一个调测框架的展示来具体说明构建实践中的调测方法论.

利用Jaeger打造云原生架构下分布式追踪系统

- -
Jaeger由Uber开源并被云原生基金会(CNCF)纳入孵化项目,背后有大厂和强大的组织支持,项目目前开发活跃;. 原生支持 OpenTracing 标准(可以认为是OpenTracing协议的参考实现),支持多种主流语言,可以复用大量的 OpenTracing 组件;. 高扩展,易伸缩,没有单点故障,可以随着业务方便扩容;.

【书籍】“凤凰架构”-构建可靠的大型分布式系统

- -
“Phoenix”这个词东方人不常用,但在西方的软件工程读物——尤其是关于 Agile、DevOps 话题的作品中时常出现. The Phoenix Project》讲述了徘徊在死亡边缘的 Phoenix 项目在精益方法下浴火重生的故事;马丁·福勒(Martin Fowler)对《. Continuous Delivery》的诠释里,曾多次提到“.

HBase 系统架构

- - 博客园_首页
HBase是Apache Hadoop的数据库,能够对大型数据提供随机、实时的读写访问. HBase的目标是存储并处理大型的数据. HBase是一个开源的,分布式的,多版本的,面向列的存储模型. 5 可在廉价PC Server搭建大规模结构化存储集群. HBase是Google BigTable的开源实现,其相互对应如下:.

基于支付系统场景的微服务架构的分布式事务解决方案

- - 研发管理 - ITeye博客
分布式系统架构中,分布式事务问题是一个绕不过去的挑战. 而微服务架构的流行,让分布式事问题日益突出. 下面我们以电商购物支付流程中,在各大参与者系统中可能会遇到分布式事务问题的场景进行详细的分析. 如上图所示,假设三大参与平台(电商平台、支付平台、银行)的系统都做了分布式系统架构拆分,按上数中的流程步骤进行分析:.

微博广告 Hubble 系统:秒级大规模分布式智能监控平台架构实践

- - IT瘾-dev
关键词:微博广告 Hubble 监控平台 D+ 大数据 机器学习 LSTM Tensorflow. Hubble(哈勃,其含义是数据如浩瀚宇宙之大,Hubble 如太空望远镜,能窥见璀璨的星辰,发现数据的真正价值)平台定位为 微博广告智能全景监控、数据透视和商业洞察. 计算广告系统是集智能流量分发、投放、结算、CTR 预估、客户关系管理等为一体的大型互联网业务系统.