做网站总结体会,做好网站建设工作总结,wordpress 内部错误,广西网站建设产品优化背景
为什么需要使用分布式唯一id#xff1f; 如果我们的系统是单体的#xff0c;数据库是单库#xff0c;那无所谓#xff0c;怎么搞都行。 但是如果系统是多系统#xff0c;如果id是和业务相关#xff0c;由各个系统生成的情况下#xff0c;那每个主机生成的主键id就…背景
为什么需要使用分布式唯一id 如果我们的系统是单体的数据库是单库那无所谓怎么搞都行。 但是如果系统是多系统如果id是和业务相关由各个系统生成的情况下那每个主机生成的主键id就是不可控的多个主机就有可能会造成主键冲突的问题。
方案
1、数据库自增
1024表不是依赖每一张表的自增主键不同的表都从1开始累加id
专门搞一个库搞一个表专门用于生成全局唯一idinsert into插入一条数据他会返回给你一个全局唯一id然后你把这个id设置给数据插入分表后的1024张表里去全局唯一的
优点超简单落实起来非常方便公司有一个统一的库和表专门用于生成id或者你自己的系统的库里你专门弄一张表用来生成id
缺点单库单表并发抗不住一旦达到每秒几千的高并发不停的在表里插入数据获取id表数据会越来越多还得定期清理很麻烦
适用场景分库分表是因为数据量大但是低并发低负载而且数据库单机有高可用问题必须上高可用方案另外是单表数据一直增长也是个问题一般不会直接投入生产投入生产环境的时候会用下面说的flickr的数据库唯一id生成方案
2、UUID
优点本地生成没有所谓的并发压力 缺点太长了作为主键绝对是不靠谱的数据库频繁页分裂问题
适用场景除数据库主键之外的其他唯一键场景都适合这个方案一般不考虑在分布式唯一ID生成里在我们的主题里其实可以忽略
3、Twitter开源的Snowflake方案
核心思想64个bit位41位放时间最多使用69年10位放机器标识最多把snowflake程序部署在1024台机器上12位放序号每毫秒每台机器可以顺序生成4096个ID最高位1个bit是0
snowflake程序分布式部署在多台机器上每台机器生成的每个ID都是这一毫秒、机器id、序号每台机器每毫秒最多4096个ID绝对够用了分布式方案可以抗高并发大不了加机器最多1024台机器纯基于内存生成性能很高
优点高性能高并发分布式可伸缩最多扩展1024台机器ID绝对够用
缺点光是开源算法还不用还得考虑时钟回拨等一系列问题如果要解决那堆问题需要开发很多机制开发完了还得独立部署有独立部署和维护的成本
适用场景中大型公司有高并发生成唯一ID场景基于snowflake算法自研加入时钟回拨解决方案多机房方案等等各种生产方案有人力去维护有少数大厂采用了这个方案可以作为生产级方案但是需要解决很多问题
4、Redis自增机制
核心思想Redis单线程绝对有序自增incrby集群部署比如5台机器那么每台机器的初始值依次为1、2、3、4、5每台机器的自增步长是5第1台机器就是1、6、11、16、21第2台机器就是2、7、12、17、22以此类推直到第5台机器就是5、10、15、20、25
优点不用额外开发一般公司都提供redis集群直接用就行
缺点客户端需要自己封装基于Jedis去封装客户端里需要写死Redis机器数量每次获取1个ID都是找到一台机器然后按步长去incrby接着返回给系统而且扩容麻烦如果5台机器抗不住并发了怎么办扩容的时候加机器客户端需要修改代码或者基于动态感知这其实也有开发成本另外扩容的时候步长就会改变那之前的ID怎么办都得重新洗掉全部从头开始计算极为麻烦
适用场景鉴于他的缺点一般不用redis集群玩自增主键生成分库分表了然后每秒在万左右的高并发但是可预见的不会达到几万以及十万级的并发那么此时可以用Redis单机去生成自增主键避免redis集群扩容的步长改变问题但是还得部署Redis主从同步哨兵高可用可是主从同步是异步的有id重复问题所以最终生产一般不用
5、基于时间业务id的组合
核心思想比如打车软件可以用时间戳起点编号车牌号作为一个id业务组合上是不会有重复的比如电商订单可以用时间戳用户id一个用户在1毫秒内一般最多就下一个订单一般不会重复除非用户基于程序刷单否则手点的情况下这个组合id一般没问题还可以加个下单渠道、第一个商品id等其他业务id组合起来
优点实现简单没额外成本没并发之类的扩容问题
缺点有的业务场景比如订单之类的还可以用这种方案但是有的业务场景可能根本没法通过业务来组合而且始终担心有重复问题
适用场景很多大厂都用这个方案做订单编号这些但是分库分表不光是订单还有什么用户、账号以及各种其他的业务场景所以部分适用于生产
6、flickr雅虎旗下的图片分享平台公司的方案
CREATE TABLE uid_sequence ( id bigint(20) unsigned NOT NULL auto_increment, stub char(1) NOT NULL default ‘’, PRIMARY KEY (id), UNIQUE KEY stub (stub) ) ENGINEMyISAM;
REPLACE INTO uid_sequence (stub) VALUES (‘test’); SELECT LAST_INSERT_ID();
replace into语法替代insert into避免表行数过大一张表就一行数据然后再select获取这个表的最新idlast_insert_id()函数是connection级别的就你这个连接的最近insert生成的id多个客户端之间没影响
当然其实也可以优化成这样就是每次你一台机器要申请一个唯一id你就REPLACE INTO uid_sequence (stub) VALUES (‘192.168.31.226’)用你自己机器的ip地址去replace into那么就你自己机器会有id不停自增完了用select id from table where stub机器地址就可以了
最多如果你要考虑到多线程并发问题那么就在机器地址后加入线程编号这样一台机器的不同线程都是对自己的id在自增
这个方案本质跟第一个方案没区别唯一优化就是用replace into替代了insert into避免表数据量过大缺点也在于数据库并发能力不高所以适用场景就是分库分表的时候低并发用这个方案生成唯一id低并发场景下可以用于生产
而且一般会部署数据库高可用方案两个库设置不同的起始位置和步长分别是1、3、5以及2、4、6
7、基于flickr方案的高并发优化
有一种变种方案是基于flickr方案的高并发优化他核心问题在于每一次生成id都得找数据库所以这就是并发瓶颈所以这里可以把数据库优化为号段而不是id号什么意思呢一起来看看
每台机器都引入一个自己封装的客户端只要一旦服务启动就直接采用flickr方案获取一个id但是他仅仅代表的是一个号段什么意思呢比如说一个服务启动通过flickr方案的replace into拿到一个id假设是1吧
此时你的号段可以配置为一个号段是10000个id号那么此时你这个号段的起始id就是1 * 10000然后可以把起始id设置到AtomicLong里去还可以保存一下号段的最大id也就是n 1* 10000就是2 * 1000020000
所以这个号段的id就是[10000, 2000020000是不包含在内的
接着服务里如果要获取唯一id直接找你封装的客户端每次拿一个id就是AtomicLong.incrementAndGet()直接原子递增这样你大部分的id获取都是在内存里通过号段内递增实现的
高并发问题解决了数据库仅仅用于维护号段罢了
如果拿到了号段里最大id此时对获取id的请求得阻塞住只要拿到的id大于等于了最大id请求全部自己陷入阻塞比如大家都去while循环阻塞过一会儿再次获取id跟最大id比较
发号器客户端的线程定时轮询一旦发现这个问题此时就重新利用flickr方案获取一个号段再次设置AtomicLong里的初始id以及更新最大id在这个过程中别的任何一个线程来获取id都会发现AtomicLong自增值比最大id是大的
即使是发号器客户端线程刚刚设置了AtomicLong的值然后还没设置volatile的最大id值此时别的线程在while循环过程中获取了idAotmicLong自增值一定大于之前的最大id值也会继续陷入阻塞的
只有当发号器客户端线程更新了volatile最大id值之后其他线程才会在while循环之后发现AtomicLong自增值是小于最大id值的此时就可以继续工作了这种情况通常是很少的所以大部分情况下各个服务都是基于本地的号段在内存里获取id而且全局上还是唯一的没有高并发问题数据库的并发也是很低的
这个方案的唯一缺点就是每次重启服务就会浪费一个号段里还没自增到的大量id重启后又是新的号段了但是如果要优化可以在spring销毁事件里发号器内部设置一个volatile标识不允许获取id了接着把AtomicLong的值持久化到本地磁盘下次服务重启后直接从本地磁盘里读取就不会浪费了
其实这个优化以后的方案就可以投入生产了确实也有个别大厂是这么做的也运行的很好。如果一定要说这个方案有什么弊端那就是归根结底还是有一个数据库这么个外部依赖其实如果方案真做好了你还得考虑数据库的高可用方案这些东西就是牵扯到了外部依赖就容易做的很重
另外一个问题就是对于这个方案你还得去做步长的配置那么到底允许多长的步长呢是否允许用户自己配置呢如果不允许你固定一个步长那个步长会不会在一些特殊高并发场景下比如你1000作为步长1000个号瞬间被秒光一个服务每秒都得请求一次数据库获取新的号段此时你有上千个服务实例数据库不还是抗不住
所以这个方案适合一些没有特殊超高并发的场景而且扩展性和灵活性不是很强总是让人担心他的号段步长会出一些问题但是在一些普通场景下其实一般可能也没什么问题所以有普通高并发场景的生产环境还是可用的
基于数据库的方案就是flickr方案以及flickr高并发优化方案但是没有snowflake生产级方案那么具备普适性snowflake方案不涉及什么号段问题也不会额外依赖数据库不需要考虑数据库高可用之类的他自己就是peer-to-peer的一个集群架构随时可以扩容
时间戳业务id相当好用推荐第一选择是他能用时间戳业务id的就别搞分布式id生成如果不行的再考虑flickr方案或者snowflake方案