ID生成器是指能产生不重复ID服务的程序,在后台开发过程中,尤其是分布式服务、微服务程序开发过程中,经常会用到,例如,为用户的每个请求产生一个唯一ID、为每个消息产生一个ID等等,ID生成器也是进行无状态服务开发的重要需求之一。
ID生成器有其特殊要求:
(1) 产生的ID不能重复,在任何情况下产生的ID都不能重复,例如:在ID生成器程序重启之后,ID生成器产生的新ID不能与重启之前产生的ID重复;
(2) ID尽可能短小,由于很多情况下,ID需要被存储或者传输,因此在满足ID不重复的基础上ID长度尽可能短小,以降低存储或传输资源的浪费;
(3) 支持高并发低延时请求,ID生成器需要应付大量的请求服务,因此要提供高并发的请求,同时要求请求响应的延时时间尽可能小,一般在10毫秒以内;
(4) 支持多ID,在实际开发过程中,有太多的情形都需要ID服务,例如:每个消息、每个用户请求、每次事务的处理等等,这些不同的应用场景都需要自己的ID,因此ID生成器需要满足同时为不各应用场景产生不同ID的服务;
(5) 在特殊场合下还需要满足ID有序,例如递增或者递减;
(6) 高可靠服务,在ID生成器的实际部署过程中,可能会遇到断电、断网、程序异常崩溃等等各种奇怪的问题,因此,如何能保证ID生成器提供稳定高效的服务,是分布式服务开发过程中,首先要解决的问题。
下面将介绍如何使用redis的INCR来快速开发一个满足上述需要的ID生成器服务,INCR命令是Redis针对value为字符串操作,它要求value的最大值为64位,每次操作将redis中的value值加1,并返回递增之后的值。其架构如下所示:
使用redis作为ID生成器的数据源,为满足ID生成器的特殊要求,需特殊部署redis:
(1) 要启用两个独立的redis,这里的两个redis需要独立部署,尽量不要和其他业务复用redis;
(2) 两个redis一个为主一个为从,主提供ID生产服务,从做持久化,从而保证数据源的高效、安全;
上述部署中,按照如下方式满足ID生成器的特殊需求:
(1) 用主redis提*生ID服务,但不进行持久化操作,Redis本身的高性能可满足ID生成器的高性能和低延时的要求,在实际测试过程中,可产生300多万/秒的ID,且请求延时为1~2毫秒;
(2) 用从Redis做持久化,保证在master挂掉之后,slave能提供服务,主从全部挂掉之后,ID数据源也被持久化到了硬盘,从而保证重新启动redis之后,ID会从持久化的位置开始产生,就不会再产生重复的ID;
(3) redis本身的key-value机制就可以提供多种ID服务,只需要为每个需求的ID产生不同的key值即可,例如在redis中消息ID的key为“id.msg”,日志ID的key值为“id.log”等等,这样消息的ID和日志的ID就可以独自完成递增操作而互补干扰。
(4) redis的INCR和DECR两个指令,可以产生递增或者递减的ID服务;
(5) redis的ID的类型最大长度为64位,可以从0开始,依次递增产生,所产生的ID就是最短的;
如果项目紧急,使用上述方式就可以很快搭建一个ID生成器服务,但是上诉ID生成器服务还存在一些缺陷:
(1) 与应用耦合高,没有对外屏蔽掉内部实现细节,例如redis,在使用时,用户完全不需要知道ID生成器使用什么产生的ID;
(2) 扩展性差,在项目规模较大时,ID的应用会非常多,如果用一个redis无法满足需求时,不方面扩展;
真正的ID生成器服务还需解决上述问题。
前面介绍的是利用redis快速搭建一个ID生成器服务,这种方式搭建的ID生成器服务还存在一些缺陷:
(1) 与应用耦合高,没有对外屏蔽掉内部实现细节,例如redis,用户完全不需要知道ID生成器使用什么产生的ID;
(2) 扩展性差,在项目规模较大时,ID的应用会非常多,如果用一组redis无法满足需求时,不方面扩展;
下面将对上述的ID生成器进一步改进,改进方式为通过thrift将redis封装起来,形成一个独立的ID生成器服务,对外以rpc方式提供ID服务,采用thrift框架可以带来如下好处:
(1) 高性能,thrift框架提供多种服务运行方式,能提供高性能的rpc服务;
(2) 多语言支持,thrift框架对多语言支持非常好,而ID生成器本身作为一个基础的、通用性的服务,它需要为各种应用场景都能提供ID服务,因此多语言的支持对其提供服务非常有帮助。
修改之后的框架如下所示:
上述架构中,IdGen是通过thrift框架开发的ID生成器服务,为无状态服务,可根据需要水平扩展,它内部管理多个redis主从对,每个redis主从对都按照:主Redis提供服务不持久化,从Redis不提供服务但持久化的方式;IdGen内部屏蔽对redis的操作,并完成对redis主从对的管理,它可根据ID的类型,将不同的Id类型放在不同的redis主从对中,这样在项目规模扩大,ID的类型和请求量增加时,访问压力将会被分散到各个Redis分组中;
另外IdGen需要能动态管理数据源Redis的分组,即满足两个条件:
(1) 动态添加新的ID类型;
(2) 动态添加Redis分组;
要实现上述两种要求的方法非常多,最简单的可以采用数据库来存储Redis分组信息和ID类型与Redis分组的对应关系,如下图所示:
在该框架中,提供如下方式的RPC接口服务:
long getId(String idType)
调用方只需提供他需要的ID类型,ID生成器即可为之产生一个对应类型的ID。
前面两个ID生成器只是简单的完成功能,如果实际应用到生产环境,则对ID生成器的要求更高,具体包括但不限于以下几点:(1) 产生全局唯一、且单调递增的ID;
(2) 任何情况下ID不能重复或者回退;
(3) 具备高效率产生ID的能力;
(4) 具备提供多种ID的能力;
(5) 便于运维管理;
本文档设计的 ID生成器整个系统需要分为四个部分:web管理端、IdGen服务、redis以及mysql,其中:
(1)web管理端用于运维人员通过网页形式管理ID生成器,它可以添加/删除ID、迁移ID等;
(2)IdGen服务属于自研服务,采用Thrift框架,对外提供各种语言的调用接口,对内管理redis和mysql,可水平扩展;
(3)Redis,在本设计中,采用redis作为Id分发器,一个redis可以分发若干类型的ID,当一个redis无法承载请求量时,可以将id类型迁移到新的Redis上。
(4)Mysql,在本设计中,采用Mysql做Id的持久化存储和配置管理,只用一个mysql即可,在实际应用中,还可以采用主从配置提升数据保存的安全性,但是从Mysql只做数据保存,不提供服务,防止主mysql所在主机出现问题时,数据可以正常恢复。 如下图所示:
获取id的接口每调用一次,ID生成器就产生一个id,在接口中每次获取到一个id之后,都需要判断该分配的id值:curID;如下图所示,如果curID < updateID,这里update是更新IP段时对应的ID(当前ID段损耗达到所设置比例对应的ID值),则返回该分配的ID;如果updateID < curID < endID,则要产生一个ID更新任务,如果curID >=endID,则直接返回失败。
每个IdGen都有一个线程池,用于执行异步任务,在更新ID信息的任务中,该任务包含以下信息:ID的名称,该ID所属的Redis、该ID在IdGen中缓存的对象,此任务需要计算申请ID段的大小,其方法为根据当前段的使用速度来计算:(当前ID-起始ID)/(当前时间-本段加载时间) * 最大加载间隔时间如果计算的结果大于配置参数的上限,则使用上限值,反之,如果小于配置的下限值,则使用下限值。
此更新ID信息的任务将完成以下操作:
(1) 从缓存中重新读取当前id的信息,并与本地保存的信息比较,如果不一致(结束ID、更新ID和更新时间都大于本地保存的值),否则说明缓存已经被其他IdGen更新,只需将本地缓存的数据(本段ID的加载时间、起始ID、结束ID、更新ID)更新为Redis中获取的数据即可;否则,说明该ID在Redis中的数据还未更新,则继续执行;
(2)在对应Redis中,对该ID加分布式锁,如果加锁失败,说明有其他的IdGen正在更新该ID的信息,直接结束当前任务即可;否则,说明当前的IdGen获取了更新该ID信息的权利,继续执行;
(3)计算新申请ID段的大小newIdLen,从数据库中申请新的ID段,提供参数包括:newIdLen、当前系统时间curSysTime,采用存储过程完成,具体需完成对数据库的以下操作: 如果当前ID类型不是有效,则直接返回-1; 将start_id字段更新为start_id + newIdLen; 将last_range字段更新为newIdLen; 将last_load_time更新为当前系统时间; 返回新的start_id
(4)IdGen根据start_id计算该ID段的end_id、update_id,并更新Redis中该ID的相关信息end_id、update_id、当前系统时间更新为新的值,并将startid设置为当前正在分配的ID值;
(5)更新本地缓存中该ID的信息:end_id、update_id、start_id和start_time。
附带一个开源的ID生成器,该ID生成器按照此文档的设计思路开发,其源码位置为:https://github.com/xiaoyaozixyz/idgenerator
如何快速开发一个支持高效、高并发的分布式ID生成器的更多相关文章
-
基于c++11新标准开发一个支持多线程高并发的网络库
背景 新的c++11标准出后,c++语法得到了非常多的扩展,比起以往不论什么时候都要灵活和高效,提高了程序编码的效率,为软件开发者节省了不少的时间. 之前我也写过基于ACE的网络server框架,但A ...
-
来吧,自己动手撸一个分布式ID生成器组件
在经过了众多轮的面试之后,小林终于进入到了一家互联网公司的基础架构组,小林目前在公司有使用到架构组研究到分布式id生成器,前一阵子大概看了下其内部的实现,发现还是存在一些架构设计不合理之处.但是又由于 ...
-
利用TokyoTyrant构建兼容Memcached协议、支持故障转移、高并发的分布式Key-value持久存储系统(转)
Tokyo Cabinet 是日本人 平林幹雄 开发的一款 DBM 数据库,该数据库读写非常快,哈希模式写入100万条数据只需0.643秒,读取100万条数据只需0.773秒,是 Berkeley D ...
-
python 开发一个支持多用户在线的FTP
### 作者介绍:* author:lzl### 博客地址:* http://www.cnblogs.com/lianzhilei/p/5813986.html### 功能实现 作业:开发一个支持多用 ...
-
使用.NET Core快速开发一个较正规的命令行应用程序
程序员的世界,命令行工具一直是"体验非常友好"的工具,也能自动化完成很多事情,同时还能结合shell来进行某项任务的批处理(脚本).在.NET Core中,命令行应用程序是基础,但 ...
-
Python3学习之路~8.6 开发一个支持多用户在线的FTP程序-代码实现
作业: 开发一个支持多用户在线的FTP程序 要求: 用户加密认证 允许同时多用户登录 每个用户有自己的家目录 ,且只能访问自己的家目录 对用户进行磁盘配额,每个用户的可用空间不同 允许用户在ftp s ...
-
快速开发一个PHP扩展
快速开发一个PHP扩展 作者:heiyeluren时间:2008-12-5博客:http://blog.csdn.net/heiyeshuwu 本文通过非常快速的方式讲解了如何制作一个PHP 5.2 ...
-
基于django快速开发一个网站(一)
基于django快速开发一个网站(一) * 创建虚拟环境.基于虚拟环境创建django==2.0.0和图片加载库和mysql数据库驱动 1. 创建目录并创建虚拟环境 ╰$ mkdir Cornuco ...
-
Microsoft Orleans构建高并发、分布式的大型应用程序框架
Microsoft Orleans 在.net用简单方法构建高并发.分布式的大型应用程序框架. 原文:http://dotnet.github.io/orleans/ 在线文档:http://dotn ...
随机推荐
-
redis 配置文件 append only file(aof)部分---数据持久化
############################## 仅追加方式 ############################### #默认情况下Redis会异步的将数据导出到磁盘上.这种模式对许 ...
-
Android开发(51) 摄像头自动对焦。在OpenCV图像识别中连续拍照时自动对焦和拍照。
概述 对焦,这里所说的“焦”是指“焦距”.在拍照时,一定是需要调焦的.一般会在目标位置最清晰的时候会停止对焦.最近在处理OpenCV进行图像识别时,需要连续的调焦(对焦),并在对焦完成后进行拍照,获取 ...
-
wsp反编译
最后出于好奇,我把wsp文件解压缩,看看里面是什么(如果您的机器上的压缩软件不能直接解压,可尝试修改后缀名为cab.).我看到的首先是一个清单文件(manifest.xml),一个DLL文件(Shar ...
-
springMVC+MyBatis+Spring 整合(4) ---解决Spring MVC 对AOP不起作用的问题
解决Spring MVC 对AOP不起作用的问题 分类: SpringMVC3x+Spring3x+MyBatis3x myibaits spring J2EE2013-11-21 11:22 640 ...
-
KITTI数据集的使用——雷达与相机的数据融合
目录 目的 如何实现 kitti数据集简介 kitti数据集的raw_data 利用kitti提供的devkit以及相应数据集的calib文件 解读calib文件夹 解读devkit 目的 使用雷达点 ...
-
ffmpeg h264 encdoer 速度对比
h264_qsv frame= 250 fps=0.0 q=-0.0 Lsize= 1401kB time=00:00:10.00 bitrate=1146.8kbits/s speed= 14x l ...
-
linux把程序做成系统服务并自启动
chkconfig命令主要用来更新(启动或停止)和查询系统服务的运行级信息.谨记chkconfig不是立即自动禁止或激活一个服务,它只是简单的改变了符号连接. 一.chkconfig 的使用语法1.c ...
-
MySQL 5.7 模式(SQL_MODE)详细说明 转
5.7 默认模式: ONLY_FULL_GROUP_BY, STRICT_TRANS_TABLES, NO_ZERO_IN_DATE, NO_ZERO_DATE, ERROR_FOR_DIVISION ...
-
Mysql几种索引方式的区别及适用情况 (转)
文章摘自http://blog.sina.com.cn/s/blog_4aca42510102v5l2.html Mysql目前主要有以下几种索引方式:FULLTEXT,HASH,BTREE,RTRE ...
-
JSONP原理及jQuery中的使用
JSONP原理 JSON和JSONP JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,用于在浏览器和服务器之间交换信息. JSONP(JSON ...