宁海哪里有做网站的,专门做二手手机的网站吗,站长之家怎么找网址,沈阳大东区做网站公司背景 
分布式场景下需要一个全局 ID 来标识唯一性#xff0c;比如在单数据库时通过表唯一主键即可实现唯一 ID#xff0c;分库分表时就需要全局唯一 ID。 
业务对唯一 ID 的要求如下#xff1a; 
全局唯一性 
不能出现重复的 ID 号#xff0c;既然是唯一标识#xff0c;这…背景 
分布式场景下需要一个全局 ID 来标识唯一性比如在单数据库时通过表唯一主键即可实现唯一 ID分库分表时就需要全局唯一 ID。 
业务对唯一 ID 的要求如下 
全局唯一性 
不能出现重复的 ID 号既然是唯一标识这是最基本的要求。 
趋势递增、单调递增 
保证下一个 ID 一定大于上一个 ID。 
信息安全 
如果 ID 是连续的恶意用户的扒取工作就非常容易做了直接按照顺序下载指定 URL 即可如果是订单号就更危险了竞对可以直接知道我们一天的单量。所以在一些应用场景下会需要 ID 无规则、不规则。 
同时除了对 ID 号码自身的要求业务还对 ID 号生成系统的可用性要求极高。想象一下如果 ID 生成系统不稳定大量依赖 ID 生成系统比如订单生成等关键动作都无法执行。所以一个 ID 生成系统还需要做到平均延迟和 TP999 延迟都要尽可能低。最好能达到可用性 5 个 9、高 QPS。 
方案 
UUID 
UUIDUniversally Unique Identifier的标准型式包含 32 个 16 进制数字以连字号分为五段形式为 8-4-4-4-12 的 36 个字符示例550e8400-e29b-41d4-a716-446655440000到目前为止业界一共有 5 种方式生成 UUID详情见 IETF 发布的 UUID 规范 A Universally Unique IDentifier (UUID) URN Namespace。 
优点 
性能非常高本地生成没有网络消耗。 
缺点 
不易于存储UUID 太长16 字节 128 位通常以 36 长度的字符串表示很多场景不适用。信息不安全基于 MAC 地址生成 UUID 的算法可能会造成 MAC 地址泄露这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。ID 作为主键时在特定的环境会存在一些问题比如做 DB 主键的场景下UUID 就非常不适用 
1、MySQL 官方有明确的建议主键要尽量越短越好[4]36 个字符长度的 UUID 不符合要求。2、对 MySQL 索引不利如果作为数据库主键在 InnoDB 引擎下UUID 的无序性可能会引起数据位置频繁变动严重影响性能。在 MySQL InnoDB 引擎中使用的是聚集索引由于多数 RDBMS 使用 B-tree 的数据结构来存储索引数据在主键的选择上面我们应该尽量使用有序的主键保证写入性能。 
可以直接使用 jdk 自带的 UUID原始生成的是带中划线的如果不需要可自行去除 
public class UUIDTest {public static void main(String[] args) {String uuid  UUID.randomUUID().toString();System.out.println(uuid);uuid  uuid.replaceAll(-, );System.out.println(uuid);/*** f5d728aa-5c07-4fb5-bf58-18c232d2fae8* f5d728aa5c074fb5bf5818c232d2fae8*/}
}雪花算法 
这种方案大致来说是一种以划分命名空间UUID 也算由于比较常见所以单独分析来生成 ID 的一种算法Snowflake 是 Twitter 开源的分布式 ID 生成算法。Snowflake 把 64-bit 分别划分成多段分开来标示机器、时间等比如在 snowflake 中的 64-bit 分别表示如下图所示第 0 位符号位标识正负始终为 0没有用不用管。第 1~41 位一共 41 位用来表示时间戳单位是毫秒可以支撑 2 ^41 毫秒约 69 年。第 42~52 位一共 10 位一般来说前 5 位表示机房 ID后 5 位表示机器 ID实际项目中可以根据实际情况调整这样就可以区分不同集群/机房的节点这样就可以表示 32 个 IDC每个 IDC 下可以有 32 台机器。第 53~64 位一共 12 位用来表示序列号。 序列号为自增值代表单台机器每毫秒能够产生的最大 ID 数(2^12  4096)也就是说单台机器每毫秒最多可以生成 4096 个 唯一 ID。理论上 snowflake 方案的 QPS 约为 409.6w/s这种分配方式可以保证在任何一个 IDC 的任何一台机器在任意毫秒内生成的 ID 都是不同的。有很多基于 Snowflake 算法的开源实现比如美团的 Leaf、百度的 UidGenerator自 18 年后UidGenerator 就基本没有再维护了[https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md](https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md))并且这些开源实现对原有的 Snowflake 算法进行了优化。在实际项目中我们一般也会对 Snowflake 算法进行改造最常见的就是在算法生成的 ID 中加入业务类型信息。 
优点 
毫秒数在高位自增序列在低位整个 ID 都是趋势递增的。不依赖数据库等第三方系统以服务的方式部署稳定性更高生成 ID 的性能也是非常高的。可以根据自身业务特性分配 bit 位非常灵活。 
缺点 
强依赖机器时钟如果机器上时钟回拨会导致发号重复或者服务会处于不可用状态。 
当然在我们自己的项目如果不想自行实现唯一性 ID还可以利用外部中间件比如 Mongdb objectID它也可以算作是和 snowflake 类似方法通过“时间机器码pidinc”共 12 个字节通过 4323 的方式最终标识成一个 24 长度的十六进制字符。其次 Seata 内置了一个分布式 UUID 生成器用于辅助生成全局事务 ID 和分支事务 ID我们同样可以拿来使用完整类名为io.seata.common.util.IdWorker。 
MYSQL 
1 创建一个数据库表 
CREATE TABLE sequence_id (id bigint(20) unsigned NOT NULL AUTO_INCREMENT,stub char(10) NOT NULL DEFAULT ,PRIMARY KEY (id),UNIQUE KEY stub (stub)
) ENGINEInnoDB DEFAULT CHARSETutf8mb4;stub 字段无意义只是为了占位便于我们插入或者修改数据。并且给 stub 字段创建了唯一索引保证其唯一性。2通过 replace into 来插入数据 
BEGIN;
REPLACE INTO sequence_id (stub) VALUES (stub);
SELECT LAST_INSERT_ID();
COMMIT;插入数据这里我们没有使用 insert into 而是使用 replace into 来插入数据。replace 是 insert 的增强版replace into 首先尝试插入数据到表中如果发现表中已经有此行数据根据主键或者唯一索引判断则先删除此行数据然后插入新的数据。否则直接插入新数据。重复插入失败时是会生成新的 ID 的。 
优点 
非常简单利用现有数据库系统的功能实现成本小有 DBA 专业维护。ID 号单调自增存储消耗空间小。 
缺点 
支持的并发量不大、存在数据库单点问题可以使用数据库集群解决不过增加了复杂度ID 没有具体业务含义安全问题比如根据订单 ID 的递增规律就能推算出每天的订单量商业机密啊 每次获取 ID 都要访问一次数据库增加了对数据库的压力获取速度也慢 
对于 MySQL 性能问题可用如下方案解决在分布式系统中我们可以多部署几台机器每台机器设置不同的初始值且步长和机器数相等。比如有两台机器。设置步长 step 为 2TicketServer1 的初始值为 11357911…、TicketServer2 的初始值为 2246810…。这是 Flickr雅虎旗下图片分享网站团队在 2010 年撰文介绍的一种主键生成策略Ticket Servers: Distributed Unique Primary Keys on the Cheap 。为了实现上述方案分别设置两台机器对应的参数TicketServer1 从 1 开始发号TicketServer2 从 2 开始发号两台机器每次发号之后都递增 2。假设我们要部署 N 台机器步长需设置为 N每台的初始值依次为 0,1,2…N-1。这种架构貌似能够满足性能的需求但有以下几个缺点系统水平扩展比较困难比如定义好了步长和机器台数之后如果要添加机器该怎么做假设现在只有一台机器发号是 12345步长是 1这个时候需要扩容机器一台。可以这样做把第二台机器的初始值设置得比第一台超过很多比如 140假设在扩容时间之内第一台不可能发到 140同时设置步长为 2那么这台机器下发的号码都是 140 以后的偶数。然后摘掉第一台把 ID 值保留为奇数比如 7然后修改第一台的步长为 2。让它符合我们定义的号段标准对于这个例子来说就是让第一台以后只能产生奇数。扩容方案看起来复杂吗貌似还好现在想象一下如果我们线上有 100 台机器这个时候要扩容该怎么做简直是噩梦。所以系统水平扩展方案复杂难以实现。ID 没有了单调递增的特性只能趋势递增这个缺点对于一般业务需求不是很重要可以容忍。数据库压力还是很大每次获取 ID 都得读写一次数据库只能靠堆机器来提高性能。 
Redis 
通过 Redis 的 incr 命令即可实现对 id 原子顺序递增例如 
127.0.0.1:6379 incr sequence_id_biz_type
(integer) 2为了提高可用性和并发我们可以使用 Redis Cluster。除了高可用和并发之外我们知道 Redis 基于内存我们需要持久化数据避免重启机器或者机器故障后数据丢失。很明显Redis 方案性能很好并且生成的 ID 是有序递增的。不过我们也知道即使 Redis 开启了持久化不管是快照snapshottingRDB、只追加文件append-only file, AOF还是 RDB 和 AOF 的混合持久化依然存在着丢失数据的可能那就意味着产生的 ID 存在着重复的概率。 
美团 Leaf 
Leaf 这个名字是来自德国哲学家、数学家莱布尼茨的一句话 There are no two identical leaves in the world世界上没有两片相同的树叶。Leaf 分别在 MySQL 和雪花上做了相应的优化实现了 Leaf-segment 和 Leaf-snowflake 方案。 
Leaf-segment 
Leaf-segment 方案在使用数据库的方案上做了如下改变原 MySQL 方案每次获取 ID 都得读写一次数据库造成数据库压力大。改为批量获取每次获取一个 segmentstep 决定大小号段的值。用完之后再去数据库获取新的号段可以大大的减轻数据库的压力。各个业务不同的发号需求用 biz_tag 字段来区分每个 biz_tag 的 ID 获取相互隔离互不影响。如果以后有性能需求需要对数据库扩容不需要上述描述的复杂的扩容操作只需要对 biz_tag 分库分表就行。数据库表设计如下重要字段说明biz_tag用来区分业务max_id表示该 biz_tag 目前所被分配的 ID 号段的最大值step表示每次分配的号段长度。原来获取 ID 每次都需要写数据库现在只需要把 step 设置得足够大比如 1000。那么只有当 1000 个号被消耗完了之后才会去重新读写一次数据库。读写数据库的频率从 1 减小到了1/step。例如现在有 3 台机器每台机器各取 1000 个很明显在第一台 Leaf 机器上是 1~1000 的号段当这个号段用完时会去加载另一个长度为 step1000 的号段假设另外两台号段都没有更新这个时候第一台机器新加载的号段就应该是3001~4000。同时数据库对应的 biz_tag 这条数据的 max_id 会从 3000 被更新成4000更新号段的 SQL 语句如下 
Begin
UPDATE table SET max_idmax_idstep WHERE biz_tagxxx
SELECT tag, max_id, step FROM table WHERE biz_tagxxx
Commit优点 
Leaf 服务可以很方便的线性扩展性能完全能够支撑大多数业务场景。ID 号码是趋势递增的 8byte 的 64 位数字满足上述数据库存储的主键要求。容灾性高Leaf 服务内部有号段缓存即使 DB 宕机短时间内 Leaf 仍能正常对外提供服务。可以自定义 max_id 的大小非常方便业务从原有的 ID 方式上迁移过来。 
缺点 
ID 号码不够随机能够泄露发号数量的信息不太安全。TP999 数据波动大当号段使用完之后还是会在获取新号段时在更新数据库的 I/O 依然会存在着等待tg999 数据会出现偶尔的尖刺。DB 宕机会造成整个系统不可用。 
双 buffer 优化对于第二个缺点Leaf-segment 做了一些优化简单的说就是Leaf 取号段的时机是在号段消耗完的时候进行的也就意味着号段临界点的 ID 下发时间取决于下一次从 DB 取回号段的时间并且在这期间进来的请求也会因为 DB 号段没有取回来导致线程阻塞。如果请求 DB 的网络和 DB 的性能稳定这种情况对系统的影响是不大的但是假如取 DB 的时候网络发生抖动或者 DB 发生慢查询就会导致整个系统的响应时间变慢。为此希望 DB 取号段的过程能够做到无阻塞不需要在 DB 取号段的时候阻塞请求线程即当号段消费到某个点时就异步的把下一个号段加载到内存中。而不需要等到号段用尽的时候才去更新号段。这样做就可以很大程度上的降低系统的 TP999 指标。采用双 buffer 的方式Leaf 服务内部有两个号段缓存区 segment。当前号段已下发 10%时如果下一个号段未更新则另启一个更新线程去更新下一个号段。当前号段全部下发完后如果下个号段准备好了则切换到下个号段为当前 segment 接着下发循环往复。通常推荐 segment 长度设置为服务高峰期发号 QPS 的 600 倍10 分钟这样即使 DB 宕机Leaf 仍能持续发号 10-20 分钟不受影响。每次请求来临时都会判断下个号段的状态从而更新此号段所以偶尔的网络抖动不会影响下个号段的更新。 
Leaf 高可用容灾对于第三点“DB 可用性”问题可以采用一主两从的方式同时分机房部署Master 和 Slave 之间采用半同步方式同步数据。美团内部使用了奇虎 360 的 Atlas 数据库中间件已开源改名为 DBProxy做主从切换。当然这种方案在一些情况会退化成异步模式甚至在非常极端情况下仍然会造成数据不一致的情况但是出现的概率非常小。如果要保证 100% 的数据强一致可以选择使用“类 Paxos算法”实现的强一致 MySQL 方案如 MySQL 5.7 中的 MySQL Group Replication。但是运维成本和精力都会相应的增加根据实际情况选型即可。 
Leaf-snowflake 
Leaf-segment 方案可以生成趋势递增的 ID同时 ID 号是可计算的不适用于订单 ID 生成场景比如竞对在两天中午 12 点分别下单通过订单 id 号相减就能大致计算出公司一天的订单量这个是不能忍受的。面对这一问题美团提供了 Leaf-snowflake 方案。Leaf-snowflake 方案完全沿用 snowflake 方案的 bit 位设计即是“1411012”的方式组装 ID 号。对于 workerID 的分配当服务集群数量较小的情况下完全可以手动配置。Leaf 服务规模较大动手配置成本太高。所以使用 Zookeeper 持久顺序节点的特性自动对 snowflake 节点配置 wokerID。Leaf-snowflake 是按照下面几个步骤启动的启动 Leaf-snowflake 服务连接 Zookeeper在 leaf_forever 父节点下检查自己是否已经注册过是否有该顺序子节点。如果有注册过直接取回自己的 workerIDzk 顺序节点生成的 int 类型 ID 号启动服务。如果没有注册过就在该父节点下面创建一个持久顺序节点创建成功后取回顺序号当做自己的 workerID 号启动服务。 
弱依赖 ZooKeeper除了每次会去 ZK 拿数据以外也会在本机文件系统上缓存一个 workerID 文件。当 ZooKeeper 出现问题恰好机器出现问题需要重启时能保证服务能够正常启动。这样做到了对三方组件的弱依赖。 
解决时钟问题因为这种方案依赖时间如果机器的时钟发生了回拨那么就会有可能生成重复的 ID 号需要解决时钟回退的问题。首先在启动时服务会进行检查 1、新节点通过检查综合对比其余 Leaf 节点的系统时间来判断自身系统时间是否准确具体做法是取所有运行中的 Leaf-snowflake 节点的服务 IP:Port然后通过 RPC 请求得到所有节点的系统时间计算 sum(time)/nodeSize然后看本机时间与这个平均值是否在阈值之内来确定当前系统时间是否准确准确正常启动服务不准确认为本机系统时间发生大步长偏移启动失败并报警。 2、在 ZooKeeper 中登记过的老节点同样会比较自身系统时间和 ZooKeeper 上本节点曾经的记录时间以及所有运行中的 Leaf-snowflake 节点的时间不准确同样启动失败并报警。另外在运行过程中每隔一段时间节点都会上报自身系统时间写入 ZooKeeper 。在服务运行过程中机器的 NTP 同步也会造成秒级别的回退由于强依赖时钟对时间的要求比较敏感美团建议有三种解决方案一是可以直接关闭 NTP 同步二是在时钟回拨的时候直接不提供服务直接返回 ERROR_CODE等时钟追上即可三是做一层重试然后上报报警系统更或者是发现有时钟回拨之后自动摘除本身节点并报警。从美团的实际运行情况来看在 2017 年闰秒出现那一次出现过部分机器回拨由于 Leaf-snowflake 的策略保证成功避免了对业务造成的影响。 
美团 Leaf 现状Leaf 在美团点评公司内部服务包含金融、支付交易、餐饮、外卖、酒店旅游、猫眼电影等众多业务线。目前 Leaf 的性能在 4C8G 的机器上 QPS 能压测到近 5万/sTP999 1ms已经能够满足大部分的业务的需求。每天提供亿数量级的调用量。