赣州新闻网

首页 / 科技

Apple 的设计哲学 · 交互篇

2020-02-22 · 赣州新闻网 · 科技

1、引言

很多人一想到IM应用开发,第一印象就是“长连接”、“socket”、“保活”、“协议”这些关键词,没错,这些确实是IM开发中肯定会涉及的q R ` ? K i : ;技术范畴。

但,当你真正开始编写第一行代J 7 E R ^ z R码时,最现实的问题实际上是“聊天消息ID该怎么生成?”这个看似微不足道的小事情。说它看似微不足道,是因为在IM里它太平常了,] S 6处处可见它的身影。不过,虽然看似微不足道,但实际却很重要,因为它的生成算法和生成策略的优劣在某种意义上来说,决定O F : o了你的IM应用层某些功能实现的难易度。

有签于此,即时通讯网专门整理了“IM消息ID技术专题”系] R G S @ d p !列文章,希望能带给你对这个看似微小但却很重要的技术点有更深刻的理解和最佳实践思路。

本文是专题系列文章的第5篇,专门介绍O G 7 |百度开源的分布式消息ID生成器UidGenerator的算法逻辑、实现思路、重点源码解读等,或许能带给你更多的启发。

学习交流:

- 即时通讯/推送技术开发交流5群:215477170

- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM》

(本文同步发布于:http://www.5d O l C Z ` J L2im.net/thread-2953-1-W | % k1.html)

2、专题目录

本文是“IM消息ID技术专j 7 ] * | m ~题”系列文章的第5篇,专题总目录如下:

《IM消息ID技术专题(一):微信的海量IM聊天消息序列号生成实践(算法原T D . W $ q H D融云IM产品的聊天消息. R C * b / 7ID生成策略》

《IM消息Y j 9 N d HID技术专题(四):深度解密g K [ ;读:http:R I r//docs.52im.net/extend/docs/src/uid-geQ Y k M Xnerator/(推荐)

4、什么是Snowflake算法?

4.1 SnowFlake算法原理

友情提示:本节文字内容摘选自《IM消息ID技术专题(四):深度解密美团的分布式ID生成算法》一文,如果您想了解美团对于SnowFlake算法的理解和应用情况,可详细阅读之。

SnowFlake 算法,是 T] # & D Pwitter 开源的分布式 ID 生成算法。其核心思想就是:使用一个 64 bit 的 long 型的数字作为全局& 4 8 g m + [ G }唯一 ID。

这 64 个 bit 中,其中 1 个 bit 是不用的,然后用其中的 41 bit 作为毫秒. C y ) ) v u五个部分,是 12 个 bit:表示的序号,就是某个机房某台机) l 6 ) J Z Z器上这一C F 9 p毫秒内同时生成的 ID 的序号,0000 00000000。

① 1 bit:是不h 6 A用的,为啥呢?

因为二进制里第一个 bit 为如果是 1,那么都是负数,但是我们生成的 ID 都是正数,所以第一个 bit 统一都是% i B i 0。

② 41 bit:表示的是时间戳,单位是毫秒。

41 bit 可以表示的数字多达 2^4i ; c f % 11 - 1,也就是可以标识 2 ^ 41 - 1 个毫秒值,换算成年就是表示 69 年的时间。

③ 10 bit:记录工作( b c z e .机器 ID,代表的是这个服务最多可以部署在 2^10 台机器上,也就是 1024 台机器。

但是 10 bit 里 ( _ C V J U h 8 5 个 bit 代表机房 id,5 个k [ x V W = ? bit 代表机器 ID。意思就是最多代表 2 ^ 5 个机房(32 个机房),每个机房里可Q 3 7 G MnowFlake 算法系统接收到这个请求之后,首先就会用二进制位运算的方式x e 生成一个 6n . 3 Z ~ # - u4 bit 的 long 型 ID,64 个6 x a k h * bit 中的第一个 bit 是无意义的;

  • 3)接着 41 个 bit,就可以用当前时间戳(单位到毫秒),然后接着 5 个 bit 设置上这个机房 id,还有 5 个 bit 设置s ^ { 9 Y @ 1 % I上机器 ID;
  • 4)最后再判断一下,当前这台& w } b机房的这台机器上这一F b C .毫秒s / W 8内,这是第几个请求,给这次生成 ID 的请求累加一个序号& r p t ^ x j P,作为最后的 1C r T X 5 A g c K2 个 bit。
  • 最终一个 64 个 bitk ] 4 Q J = % L 的 ID 就出来了,类似于:

    开源分布式ID生成器UidGenerator的技术实现

    (本图引用自《IM消息ID技术专题(四):深度解密美团的分布式ID生成算法》)

    这个算法可以保证说,一个机房的一台机器上,在同一毫秒内,生成了一个唯一的 ID* ? z D u。可能一个毫秒内会生成多个 ID,但是有最后 12 个 bit 的序号来区分开来。

    下面我们简单看看这个 SnowFlake 算法的一个代码实现,这就是个示例,大家如果理解了这个意思之后,以后可以自己尝试改造这个} ) C J算法。

    总之就是用一个 64 bit 的数字中各个 bit 位来设置不同的标志位,区分每一个 ID。

    4.2 SnowFlake算法的代码实现

    SnowFlake 算法的一个典型Java实现代码,可以参见文章中的第“6.5 方案四:SnowFlake 算法的思想分析”节:E ~ T D j | K #《通俗易懂:如何设计能支撑百万并发的数据库架构?》,是JL J _ack Jiang曾在某项目中实际使用过的代码。

    4.3 SnowFlake算法的优缺点

    对于份布式的业务) j V s . 4 V %系统来说,SnowFlake算法的优缺点如下。

    ► 优点:

    1)毫秒数在高位,自增序列在低位,整个ID都是趋势递增的;

    2)不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的;

    3)可以根据自身业务特性分配bit位,非常灵活。

    ► 缺点s v H - Q C:

    强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。

    5、UidGene; Q 3 ` ( D 5 Brator改进后的SnowFlake算法

    通过上节,我们知道了原版SnowFlake算法的基本构成。

    具体是,原版SnowFlc ~ Aake算法核心组成:

    开源分布式ID生成器UidGenerator的技术实现

    原版SnowFlake算法各字段X I @ i o的具体意义是:

    1)1位sign标识位;

    2)41位时间戳;

    3)10位workId(即5位数据中心id+5位工作机器id);

    4)12位自增序列。

    而Ui+ p . O C v E +dGenerator改进后的SnowFlake算法核H 8 l u ~ d D S心组成如下图:

    开源分布式ID生成器UidGenerator的技术实现

    简单来说,UidGenerator能保证“指定机器 & 同7 : v一时刻 & 某一并发序列”,是唯一,并据此生成一个64 bits的唯一ID(long),且默认采用上图字节分配方式。

    与原版的snowflake算法不同,UidGenerator还支持自定义时间戳、工作机器id和序列号等各部分的位数,以应用于不同u ~ P P场景(详见源码实现)。

    如上图所示,UidGenerator默认ID中各数据位的含义如下:

    • 1)sign(1bit):固定1bit符号标识,即生成的UID为正数。
    • 2a v v G U ~ J _)delta sec) f & Z A =onds (28 bits):当前时间,相对于时间基点\"2016-05-20\"的增量值,单位f = y 0 =:秒,最多可支持约8.7年(注意:(a)这里的单位是秒,而不B # @ * P是毫秒! (b)注意这里的用词,是“最多”可支持8.7年,为什么是“最多”,后面会讲)。
    • 3)worker id (22 bits):机器id,最多可支持约420w次机器启动。内置实现为在启动时由数据库分配,默认分配策略为用后即弃,后续可提供复用策略a v $。
    • 4)sequence (13 bits):每秒下的并发序列,13 bits可支o F U持每秒8192个并发(注意下这个地方,默认支持qps最大为8192个)。

    6、UidGenerator的具体代码实现分析

    通过阅读UidGenerator的源码可知,UidGenerator的具体实现有两种选择,即 DefaultUidGenerator 和 CachedZ H Q = 4 } WUidGen! 1 N P & ] uerator。9 J 7 z W 4我们分别来看看这两个具体代码实现的精妙之处。

    6.1 DefaultUidGenerator

    DefaulF I m f NtUidGenerator 的源码很清楚的说明了几个生成ID的关键位的实现逻辑。

    1)delta seconds(28 bits):

    这个值是指当前时间与epoD M K } Cch时间的时间差,且单位为秒。epoch时间就是指集y n ? h成DefaultUidGenerator生成分布式ID服务第一次上线的时间,可配置,也一定要根据你的上线时间进行配置,因为默认的epoch时间可是2016-09-20,不6 B ]配置的话,会浪费好几年的可用时间。

    2)worker id(22bits):

    接下来说一下DefaultUidGenerator是如何给worker id赋值的,搭建DefaultUidGenerator的话,需要创建一) p ! L X . } S |个表:

    DROP DATABASE IF EXISTS `xxxx`;

    CREAT+ 0 #E DATABASE `xxxx` ;q v { Q ` (

    use `xxxx`;

    DROP TABLE IF EXISTS WORKER_NODE;

    CREATE TABLE WORKER_NODE

    (

    ID BIGINT NOT NULL AUTO_INCREMENT COMMENT \'auto increment id\',

    HOST_NAME VARCHAR(64)x z o B 3 NOT NULL COMMENT \'host name\',9 E f ; g

    PORT VARCHAR(64) NOT NULL COMMENT \'port\'z t R,

    TYPE INT NOT NULL C( l ]OMMENT \'node type: ACTUAL or CONTAINER\',

    LAUNCH_DATE DATE Nt W + W OOT NULL COMMENT \'launch date\',. W M a

    MODIFIED TIMESTAMP NOT NULL COMMENT \'modified time\',

    C) A b 4 OREATED TIMESTAMP NOT NULL COMMENT \'created time\',

    PRIMARYq 7 Z g C ? { ` KEY(ID)

    )

    COMMENT=\': E PDB WorkerID Assigner for UID Generator\', ENGINE = I! I & i x `NNODB;

    DefaultU2 W j &idGenerator会在集成用它生成分布式IDT B 8 P } ; ~的实例启动的时候,往这个_ e U表中插入一行数据,得到的id值就是准备赋给workerId的值。由于workerId默认22位,那么,集成DefaultUidGenerator生成分布式ID的所有实例重启次数是不允许超过4194303次(即2^22-1x 3 U ~ = E q 8),否则会抛出D { 0 Q I | V节选自:DefaultUidGenerator 类中的 nextId() 方法)

    4)小结:

    通过DefaultUidGenerator的实现可知,它对时钟回拨的处理比较简单粗暴。另外如果使用UidGenerator的DefaultUidGeneratoS G P # e : :r方式生成分布式ID,一定要根据你的业务的情况和? v ( , T j } m特点,调整各A W @ | 2 ? $个字段占用的位数:

    <!-- Specified bits & epoch as y3 : # cour demand. No specified the default value will be used -->

    <property name=\"timeBits\" value=\"29\"/>

    <pro6 x s Z * f % Kperty name=\"workerBits\" value=\"21\"/>

    <property name=\"seqBits\" value=\"13\"/>

    <property name=\"epochStr\ d B W J L p 1" value=\"2016-09-20\"/>

    6.2 CachedUidGenerator

    CachedUidGenerator是DI ^ r W * !efaultUidGenerator的重要改进实现。它的核心利用了RingBuffer,它本质上是一个数组,数组中每个项被称为slot。CachedUidGenerator设计了两个RingBuffer,一个保存唯一ID,一个保存flag。RingBuffer的尺n ; S /寸是2^n,n必须是正整数。

    以下是CachL - 0edUidGenerator中的RingBuffer原理示意图:

    开源分布式ID生成器UidGenerator的技术实现

    扩展知识:什么是RingBuffer?

    Ring Bufferk ? a Q % `的概念,其实来自于Linux内核(Maybe),是为解决某些特殊情况下的竞争问题提供了一种免锁的方法。这种特殊的情况就是当生产# + + c Q : : 者和消费者都只有一个,而在其它情况; I 7 S t g ) w下使用它也是必须要加锁的。

    环形缓冲区通常有一个读指针和一个写指针。读指针指向环形缓冲区中可读的数据,写指针指向环形缓冲区中可写的缓冲区。通过移动读指针和写指针就可以实现缓冲区的数据读取和 R l 0 X I d U 2写4 - f ; % K n入。在通常情况下,环形缓冲区的读用户仅仅会影响读指针,而写用户仅仅会影响写指针。如果仅仅有一个读用户和一个写用户,那么b u R不需要添加互斥保护机制就可以保证数据的正确性。如果有m % W ) F c %多h Y V J个读写用户访问环形缓冲区,那么必须添加互斥保护机制来确保多个用户互斥访问环形缓冲区。

    更多具体的 CachedUidGeneratoh ~ Rr 的代码实现,! $ } d 2 y | 0有兴趣可以仔细读一读,也可以前往百度uid-generator工程的说明页看看具体的算法原理,这里就不再赘述。

    简要的小结一下,CachedT a _ G 9 UidGenerator方式主要通过采取如下一些措施和方案规避了时钟回拨问题和增强唯一性:

    • 1)自增列:CachedUidGenerator的workerL c Z t _ lId在实N ? o d B例每次重启时初始化,且就是数据库的自增ID,从而完美的实现每个实例获取到的workerId不会有任何冲突;
    • 2)RingBuffer:CachedUidGenerator不再在每次取ID时都实时J V ~ ;计算分布式ID,而是利用RingBuffer数m 8 . V Z ^ 0据结构预先生成若干个分布式ID并保存;
    • 3)时间递增:传统的SnowFlake算法实现都是通过System.currentTimeMillis()来获取时间并与上一次时间进行比较,这样的实现严重依赖服务器的时间。而CachedUidGeneratorP p R B 4 r + : 8的时间类型是Atomi5 2 p w %cLong,且通过C Z h 5 M dincrementAndGet()方M i F 8 6 - ~ 5法获取下一次的时间,从而脱离了[ K U c j对服务器时间的依赖,也就不会有时钟回拨的问题(这种做法也有一个小问题,即分布式ID中的时间信息可能并不是这个ID真正产生的时间点,例如:获取的某分布式ID的值为32001697, R o U89968523265,它的T s q Q q反解析结果为{\"timestamp\":\"2019-05-02 23:26:39\",\"workerId\":\"21\",\"sequence\":\"1\"},但是这个ID可能并不是在\"2019-05-02 23:26:39\"这个时间产h h 5 a _ P生的)。

    6.3 小结一4 , D M -下

    CachedUidGenerator通x X ! d 7过缓存的方式预先生成一批唯一ID列表,可以解决唯一ID获取时候的耗时。但 $ 7 6 3 .这种方式也有不好点,一方面需要耗费内U 5 $ x W + F {存来缓存这部分数据,另外如果访问量不大的情况下,提前生成的UID中的时间戳可能是很早之前的。而对于大部分的场景来说,DefaultUidGenerator 就可以满足相关的需求了,没必要来凑Cac{ H 7 D I n ! phedUidGenerator这个热闹。

    另外,关于UidGenerator比特位分配的建议:

    对于并发数要求不高、期~ ! x U d s望长期使用的应用, 可增加timeBits位数, 减少seqBits位数. 例如节点采取用完即弃的W* E ; @orkerIdAssigner策略K * # # Q ,, 重启频率为12次/天, 那么配置成{\"work! } y .erBits\":23,\"timeBits\":31,\"seqBits\":9}时, 可支持28个节点以整体并发量14400 UID/s的速度持续运行68年.

    对J Q 2 - 1于节点重启频率频繁、期望长期使用的应用, 可增加workerBits和timeBits位数, 减少seqBits位数. 例如节点采取用完即弃的WorkerIdAssigner策略, 重启{ D N + s f q B ?频率为24*12k k ) h d K $ C次/天, 那么配置成{\"workerBits\":27,\"timeBits\":30,\"seqBits\":6}, V ! { ;时, 可支持37个节点以整体并发量2400 UID/s的速度持续运行34年.

    7、UidGenerator的吞吐量压力测试

    UidGenerator的测试数据显示,在MaX o pcBoo8 H V 5 h K f 3k Pro(2.7GHz Intel Core i5, 8G DDR3). P 9 F T & U 9上进行的CachedUidGeneratJ $ K ? x z Nor(单实例)的UID吞吐量测试情况如下。

    首先:固定住workerBits为任选一个值(如20), 分别统计timeBits1 R I V k 5变化时(如从25至32, 总时长分别对应1年和136年)的吞吐量, 测试结果如下图所示:

    开源分布式ID生成器UidGenerator的技术实现

    再固定住timeBits为任选一个值(如31), 分别统计workerBits* } p r R j变化时(如从20至29, 总重. m ~ m v s X启次数分别对应1百万和500百万)的吞吐量, 测试结果如下图所示:

    开源分布式ID生成器UidGenerator的技术实现

    由此可见:不管如何配置, CachedUidGenerator总能提供600万/s的稳定吞吐量,只是使用年限会有所减少,这真的是太棒了!

    最后:固定住workerBits和timeBits位数(如23和31), 分别统计不同数目(如1至8,本机CPU核R E q {数为4)的UID使用者情y ` J H d U ? +况下的吞吐量,测试结果如下图所示:

    开源分布式ID生成器UidGenerator的技术实现

    8、参考资料

    改进版Sn [ ; [ w [ 1 fowflake全局? ) & M a + & 4 DID生成器-uid-V Q qgenerator

    UidGenerator

    百度开源分布式id生成器uid-generator源码剖析

    附录:更多IM开发热门技术文章

    《新手入门一篇就{ C V a / Z E j够:从零开发C # r + ? f移动端IM》

    《移动端IM开发者必读(一):通俗易懂,理解移动网络的“弱”和“慢”》

    《移动端IM开发者必读(二):史上最全移动弱网络优化方法总结》

    《从客户端的角度来谈谈移动端IM的消息可靠性和送达机制》

    《现代移动端网络短连接的优化手段总结:请求速K V y O ? T K w度、弱网适应、安全保障》

    《腾讯技术分享:社交网络图片的带宽压缩技术演进之路》

    《小白必读:闲话HTTP短连接中的Session和Token》

    《IM开发基础知识补课:正确理解前置HTTP SSO单点登录接口的原理》

    《移动端IM中大规模群消息的推送如何保证效率、实时性?》

    《移动端IM开发需要面对的技术问题》

    《开发IMC T F , 7是自己设计协议用字节流好还是字符流好?》

    《请问有人知道语音留言聊天的主流实现方式吗?》

    《IM消息送达保证机制实现(一):保证在线实时消息的可靠投递》

    《IM消息送达保2 1 P 1 @ N证机制实现8 F j 6 8 . (二):保证离线消息的可靠投递》

    《如何保证IM实时消息的“时序性”与“一致性”?》

    《一个低成本确保IM消息时序的方法探讨》

    《IM单聊和群聊中的在线状态同步应该用“推”还是“拉”?》

    《; Q XIM群聊消息如此复杂,如何保证不丢不重?》

    《谈谈移动端 IM 开发中登录请求的优化》

    《移动端IM登录时拉取数据如何作到省流量?》

    《浅谈移动端IM的多点登录和消息漫游原理》

    《完全自已开发的IM该如何设计“失败重试”机制?》

    《通俗易懂:基于集群的移动端IM接入 ; 6 L L 4 I @层负载均衡方案分享》

    《微信对网络影响的技术试验及分析(论文全文)》r o 7 ~ ~ J 0 8 g

    《即时通讯系统的原理、技术和应用(技术论文)》

    《开源IM工程j F D 3 #“蘑菇街TeamTalk”的现状:一场有始无终的开源秀》

    《QQ音乐团队分享:Android中的图片压缩技术详解(上篇)》

    《QQ音乐团队分享:Android中的图片压缩技术详解(下篇)》

    《腾讯原创分享(一):如何大幅提升移动网络下手机QQ的图片传输速度和成功率》

    《腾讯原& r L I L g创分享(二)K A i a U:如何大幅压缩移动网络下APP的流量消耗(上篇)》

    《腾讯原创分享(三):如何大幅压缩移动网络下APP的流量, v C H ^ r A s ]消耗(下篇)》

    《如约而至:微信自用的移动端IM网络层跨平台1 , I `组件库Mars已正L S W e 1 ^ ` ? d式开源》

    《基于社交网络的Yelp是如何实现海量用S C 8 U & p s户图片的无损压缩的?》

    《腾讯技术分享:腾讯是如何大幅降低带宽和网络流量的(图片压缩篇)》

    《腾讯技术分享:腾讯是如何大幅降低带宽和网络流量的(音视频技术篇)》

    《字符编码那点事:快速理解ASCII、Unicode、GBK和UTF-8》

    《全面掌握移动端主流图片格式的特点、性能、调优等》

    《子弹短信光鲜的背后:网易云信首席架构师分享亿级IM平台的技术实践》

    《IM开发q X 识补课(五):通俗易懂,正确理解并用好MQ消息队列》b Q m h

    《微信技术分享:微信的海量q V 6IM聊天消息序列号生成实践(算法原理篇)》

    《自已开发IM有那么难吗?手把手教你自撸一个Andriod版简易IM (有源码)》

    《融云技术分享:解密融云IM产品的聊天消息ID生成策略》

    《IM开发基础知识补课(六):数据库用NoSQL还是S. E w o ` 7 z iQL?读这篇就够了!》

    《适合新手:从零开发一个IM服务端(基于Netty,有完整源码)》

    《拿起键盘就是干:跟我一起徒手开发一套分布式IM系统》

    《IM里“附近的人9 O 6 a G # V (”功能实现原理是什么?8 9 w f S ? - $ w如何高效率地实现它?》

    《IJ { q 1 S y ~M开发基础知识补课(七):主流移动端账号登录方式的原理及设计思路》

    《IM开发基础知识补课(八)[ * s:史上最通俗,彻底搞懂字符乱码问题的本质》

    《IM“扫一扫”功能+ Q L L Q 6 p M C很好做?看看微信“扫一扫识物”的完整技术实现》

    《IM的扫码登录功能如何实现?一文搞 ( B懂主流应j W ~用的扫码登录技术原理》

    《IM要做手机扫码登录?先看看微信的扫码登录功能技术原理》

    >&g& e j q c H g (t; 更多同类文章 ……

    (本文同步发布于:http://www.52im.n* ~ a e Net/thread-2953-1-1.html)

    测试确保项目视频工作补齐设计全南哲学