- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
上周末考完试,这周正好把工作整理整理,然后也把之前的一些素材,整理一番,也当自己再学习一番。
一方面正好最近看到几篇这方面的文章,另一方面也是正好工作上有所涉及,所以决定写一篇这样的文章。
先是简单介绍概念和现有解决方案,然后是我对这些方案的总结,最后是我自己项目的解决思路。
在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识。
如在金融、电商、支付、等产品的系统中,数据日渐增长,对数据分库分表后需要有一个唯一ID来标识一条数据或消息,数据库的自增ID显然不能满足需求,此时一个能够生成全局唯一ID的系统是非常必要的。
分布式全局唯一ID(数据库的分库分表后需要有一个唯一ID来标识一条数据或消息;特别一点的如订单、骑手、优惠券也都需要有唯一ID做标识;MQ中消息的高可用性(确认消息是否发送成功,是否已发送等)等)
其实分布式全局ID是一个比较复杂,重要的分布式问题(什么问题涉及真正的分布式,高并发后都会比较复杂)。常见解决方案有UUID,Snowflake,Flicker,Redis,Zookeeper,Leaf等。
生成一个32位16进制字符串(16字节的128位数据,通常以32位长度的字符串表示)(结合机器识别码(全局唯一的IEEE机器识别号,如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得),当前时间,一个随机数)。
不需要考虑空间占用,不需要生成有递增趋势的ID,且不在MySQL中存储。
Twitter开源,生成一个64bit(0和1)字符串(1bit不用,41bit表示存储时间戳,10bit表示工作机器id(5位数据标示位,5位机器标识位),12bit序列号)
最后生成64位Long型数值(这里指,一般Long数据就是64位bit的)。
要求高性能,可以不连续,数据类型为long型。
主要思路是涉及单独的库表,利用数据库的自增ID+replace_into,来生成全局ID。
replace into跟insert功能类似,不同点在于:replace into首先尝试插入数据列表中,如果发现表中已经有此行数据(根据主键或唯一索引判断)则先删除,再插入。否则直接插入新数据。
建表:
create table t_global_id(
id bigint(20) unsigned not null auto_increment,
stub char(1) not null default '',
primary key (id),
unique key stub (stub)
) engine=MyISAM;
(stub:票根,对应需要生成ID的业务方编码,可以是项目名,表名,甚至是服务器IP地址。
MyISAM(MYSQL5.5.8前默认数据库存储引擎,5.5.8及之后默认存储引擎为InnoDB):(此处应当有MyISAM与InnoDB引擎的区别,乃至其他引擎)基于ISAM类型。不是事务安全(没有事务隔离??),不支持外键,没有行级锁。如果执行大量的select,建议MyISAM。
获取数据:
# 每次业务可以使用以下SQL读写MySQL得到ID号
replace into t_golbal_id(stub) values('a');
select last_insert_id();
扩展:为解决单点问题,启用多台服务器,如MySQL,利用给字段设置auto_increment_increment和auto_increment_offset来保证ID自增(如通过设置起始值与步长,生成奇偶数ID)
数据量不大,并发量不大。
由于Redis的所有命令是单线程的,所以可以利用Redis的原子操作INCR和INCRBY,来生成全局唯一的ID。
可以通过集群来提升吞吐量(可以通过为不同Redis节点设置不同的初始值并同意步长,从而利用Redis生成唯一且趋势递增的ID)(其实这个方法和Flicker一致,只是利用到了Redis的一些特性,如原子操作,内存数据库读写快等)(Incrby:将key中储存的数字加上指定的增量值。这是一个“INCR AND GET”的原子操作,业务方可以定义一个自己的key值,通过INCR命令来获取对应的ID)
不依赖数据库,灵活方便,且性能优于基于数据库的Flicker方案。
Redis集群高可用,并发量高。
利用Redis来生成每天从0开始的流水号。如订单号=日期+当日自增长号。可以每天在Redis中生成一个Key,适用INCR进行累加。
通过其znode数据版本来生成序列号,可以生成32位和64位的数据版本号,客户端可以使用这个版本号来作为唯一的序列号。
小结:很少会使用zookeeper来生成唯一ID。主要是由于需要依赖zookeeper,并且是多步调用API,如果在竞争较大的情况下,需要考虑使用分布式锁。因此,性能在高并发的分布式环境下,也不甚理想。
美团的Leaf分布式ID生成系统,在Flicker策略与Snowflake算法的基础上做了两套优化的方案:Leaf-segment数据库方案(相比Flicker方案每次都要读取数据库,该方案改用proxy server批量获取,且做了双buffer的优化)与Leaf-snowflake方案(主要针对时钟回拨问题做了特殊处理。若发生时钟回拨则拒绝发号,并进行告警)。
ObjectID可以算作和snowflake类似方法,通过”时间+机器码+pid+inc”共12个字节,通过4+3+2+3的方式,最终标识一个24长度的十六进制字符。
其实除了上述方案外,还有ins等的方案,但总的来看,方案主要分为两种:第一有中心(如数据库,包括MYSQL,REDIS等),其中可以会利用事先的预约来实现集群(起始步长)。第二种就是无中心,通过生成足够散落的数据,来确保无冲突(如UUID等)。站在这两个方向上,来看上述方案的利弊就方便多了。
技术是无穷无尽的,我们不仅需要看到其中体现的思想与原则,在学习新技术或方案时,需要明确其中一些特性,优缺点的来源,从而进行有效的总结归纳。
应用角度来说:(一方面想要标示符短,便于处理与存储,另一方面想要足够大,而不会产生冲突。呵呵)。最理想就是追求从0开始,每个标示符都被使用,且不重复,而且不用担心并发。呵呵。完全应该根据当前业务场景来选择,毕竟业务场景在当前是确定的。如果业务变动较大(比如发展初期,业务增长很快),那就需要考虑扩展性,便于日后进行该模块的更新与技术方案的替换实现(避免一个系统开发一年,用不到一年,那就尴尬了))。
我曾经做过一个“工业物联网”系统,该系统系统是分为三个子系统:终端服务器(用于收集终端传感器数据);企业中控服务器(接收来自多个终端服务器的数据,进行综合查看与控制);云平台服务器(提供上云)。其中就涉及多个终端服务器的传感器数据辨识问题,这里以倾斜传感器数据为例。简述不同终端服务器的倾斜数据的如何实现全局唯一标识。
简单说,就是终端服务器发送一个数据到企业中控室,企业中控服务器就将该数据保存到数据库中,那么每个数据在企业中控服务器数据库中都有唯一的ID,并且保持了自增。
优点是实现简单,只需要做好数据收发,与数据的插入工作即可。唯一需要注意的是数据库插入时注意资源互斥,防止出现数据插入异常问题(Springframework生成的Bean默认时单例的)。
缺点是需要实时收发数据,防止数据丢失,数据积压,数据的create_time异常等问题。
简单说,就是终端服务器要发送的数据赋予UUID这样的ID,来确保全局唯一。这样终端服务器就可以和中控服务器保持同样且不冲突的ID了。数据的生成是实现在终端服务器的,而中控服务器只是作为数据的保存与调用(通过统一ID调用)。
优点是不需要数据的实时收发,避免系统在弱网络情况下出现各类异常。
缺点是数据的ID过长,并且无法保持自增。并且在某种程度上带来了数据复杂度,从而提高了系统复杂度。
由于实际业务的需求,如弱网络,数据交互频率跨度大等情况。最终我的实现是先由终端服务器在启动之初,在企业中控服务器注册TerminalId,作为不同终端服务器的标识。不同终端服务器接收与保存数据时,都会在每条数据中插入TerminalId,便于企业中控服务器的识别。当然,具体实现当中还有一些细节。如终端服务器在注册时由于网络等情况注册失败,会先建立一个类似UUID的TerminalId来先保存监测数据。当注册成功时(系统会根据TerminalId的长度等特性来判断是否注册失败,是否需要重新注册),会重新修改所有数据的TerminalId,再允许数据上传。
优点是确保了数据在弱网络情况下的正确性,并且实现了自动注册等通用模块的实现。
缺点是最终数据插入企业中控服务器数据库时,并没有严格实现数据符合实际时间的增长(如某终端服务器由于网络等情况没法发送数据,等待一段时间后发送了这段时间堆积的数据),但保持了总体增长的趋势。
IT没有银弹,我们要做的是多去了解现有的技术方案,再产生符合自己需求的技术方案。因为不同的技术方案都因为其使用场景有着各自的特点,而我们需要了解各种特点的技术来源(是什么技术造就了这一特点,或者说是什么架构造就了这一特点等),从而构建出最符合自己需求的技术方案。
没有最好,只有最适合。
我的应用程序中有一个 settings.php 页面,它使用 $GLOBALS 来存储网络应用程序中使用的配置。 例如,他是我使用的一个示例设置变量: $GLOBALS["new_login_page
我正在尝试编译我们在 OS 类上获得的简单操作系统代码。它在 Ubuntu 下运行良好,但我想在 OS X 上编译它。我得到的错误是: [compiling] arch/i386/arch/start
我知道distcp无法使用通配符。 但是,我将需要在更改的目录上安排distcp。 (即,仅在星期一等“星期五”目录中复制数据),还从指定目录下的所有项目中复制数据。 是否有某种设计模式可用于编写此类
是否可以在config.groovy中全局定义资源格式(json,xml)的优先级,而不是在每个Resource上指定?例如,不要在@Resource Annotation的参数中指定它,例如: @R
是否有一些简单的方法来获取大对象图的所有关联,而不必“左连接获取”所有关联?我不能只告诉 Hibernate 默认获取 eager 关联吗? 最佳答案 即使有可能有一个全局 lazy=false(谷歌
我正在尝试实现一个全局加载对话框...我想调用一些静态函数来显示对话框和一些静态函数来关闭它。与此同时,我正在主线程或子线程中做一些工作...... 我尝试了以下操作,但对话框没有更新...最后一次,
当我偶然发现 this question 时,我正在阅读更改占位符文本。 无论如何,我回去学习了占位符。一个 SO 的回答大致如下: Be careful when designing your pl
例如,如果我有这样的文字: "hello800 more text 1234 and 567" 它应该匹配 1234 和 567,而不是 800(因为它遵循 hello 的 o,这不是一个数字)。 这
我一直在尝试寻找一种无需使用 SMS 验证系统即可验证电话号码(Android 和 iPhone)的方法。原因纯粹是围绕成本。我想要一个免费的解决方案。 我可以安全地假设 Android 操作系统会向
解决此类问题的规范 C++ 设计模式是什么? 我有一些共享多个类的多线程服务器。我需要为大多数类提供各种运行时参数(例如服务器名称、日志记录级别)。 在下面的伪 C++ 代码中,我使用了一个日志记录类
这个问题在这里已经有了答案: Using global variables in a function (25 个答案) 关闭 9 年前。 我是 python 的新手,所以可能有一个简单的答案,但我
这个问题在这里已经有了答案: 关闭 10 年前。 Possible Duplicate: Does C++ call destructors for global and class static
我正在尝试使用 Objective-C 中的 ArrayList 的等价物。我知道我必须使用 NSMutableArray。我想要一个字符串列表 (NSString)。关键是我的列表应该可以从我类(c
今天刚开始学习 Android 开发,我找不到任何关于如何定义 Helper 类或将全局加载的函数集合的信息,我会能够在我创建的任何 Activity 中使用它们。 我的计划是创建(至少目前)2 个几
为什么这段代码有效: var = 0 def func(num): print num var = 1 if num != 0: func(num-1) fun
$GLOBALS["items"] = array('one', 'two', 'three', 'four', 'five' ,'six', 'seven'); $alter = &$GLOBALS
我想知道如何实现一个可以在任何地方使用您自己的设置的全局记录器: 我目前有一个自定义记录器类: class customLogger(logging.Logger): ... 该类位于一个单独的
我需要使用 React 测试库和 Jest 在我的测试中模拟不同的窗口大小。 目前我必须在每个测试文件中包含这个beforeAll: import matchMediaPolyfill from 'm
每次我遇到单例模式或任何静态类(即(几乎)只有静态成员的类)的实现时,我想知道这是否实际上不是一种黑客行为,因此只是为了设计而严重滥用类和实例的原则单个对象,而不是设计类和创建单个实例。对我来说,看起
这个问题在这里已经有了答案: Help understanding global flag in perl (2 个回答) 7年前关闭。 my $test = "There was once an\n
我是一名优秀的程序员,十分优秀!