当前位置: 首页 > news >正文

上海中小企业网站wordpress调用文章自定义字段

上海中小企业网站,wordpress调用文章自定义字段,海尔网站的建设目标,saas系统是什么样的系统在互联网的业务系统中#xff0c;涉及到各种各样的ID#xff0c;如在支付系统中就会有支付ID、退款ID等。那一般生成ID都有哪些解决方案呢#xff1f;特别是在复杂的分布式系统业务场景中#xff0c;我们应该采用哪种适合自己的解决方案是十分重要的。下面我们一一来列举一…在互联网的业务系统中涉及到各种各样的ID如在支付系统中就会有支付ID、退款ID等。那一般生成ID都有哪些解决方案呢特别是在复杂的分布式系统业务场景中我们应该采用哪种适合自己的解决方案是十分重要的。下面我们一一来列举一下不一定全部适合这些解决方案仅供参考或许对你有用。 一、分布式ID 1.什么是分布式ID 日常开发中我们需要对系统中的各种数据使用 ID 唯一表示比如 用户 ID 对应且仅对应一个人商品 ID 对应且仅对应一件商品订 单 ID 对应且仅对应一个订单。 拿MySQL数据库举个例子 在我们业务数据量不大的时候单库单表完全可以支撑现有业务数据再大一点搞个MySQL主从同步读写分离也能对付。 但随着数据日渐增长主从同步也扛不住了就需要对数据库进行分库分表但分库分表后需要有一个唯一ID来标识一条数据数据库的自增ID显然不能满足需求特别一点的如订单、优惠券也都需要有唯一ID做标识。此时一个能够生成全局唯一ID的系统是非常必要的。那么这个全局唯一ID就叫分布式ID。 2.分布式ID的特性 唯一性确保生成的ID是全网唯一的。有序递增性确保生成的ID是对于某个用户或者业务是按一定的数字有序递增的。高可用性确保任何时候都能正确的生成ID。带时间ID里面包含时间一眼扫过去就知道哪天的交易。 二、分布式ID的生成方案 1. UUID 算法的核心思想是结合机器的网卡、当地时间、一个随记数来生成UUID。 优点 代码实现简单本地生成没有性能问题没有高可用风险全球唯一的数据迁移容易 缺点 长度过长存储冗余且无序不可读查询效率低每次生成的ID是无序的不满足趋势递增UUID是字符串而且比较长占用空间大查询效率低ID没有含义可读性差 2. 数据库自增ID 使用数据库的id自增策略如 MySQL 的 auto_increment。并且可以使用两台数据库分别设置不同步长生成不重复ID的策略来实现高可用。 优点数据库生成的ID绝对有序高可用实现方式简单缺点需要独立部署数据库实例成本高有性能瓶颈 3. 批量生成ID 一次按需批量生成多个ID每次生成都需要访问数据库将数据库修改为最大的ID值并在内存中记录当前值及最大值。 优点避免了每次生成ID都要访问数据库并带来压力提高性能缺点属于本地生成策略存在单点故障服务重启造成ID不连续 4. Redis生成ID Redis的所有命令操作都是单线程的本身提供像 incr 和 increby 这样的自增原子命令所以能保证生成的 ID 肯定是唯一有序的。 优点有序递增可读性强性能较高。不依赖于数据库灵活方便且性能优于数据库数字ID天然排序对分页或者需要排序的结果很有帮助。缺点占用带宽依赖Redis如果系统中没有Redis还需要引入新的组件增加系统复杂度需要编码和配置的工作量比较大。 考虑到单节点的性能瓶颈可以使用 Redis 集群来获取更高的吞吐量。假如一个集群中有5台 Redis。可以初始化每台 Redis 的值分别是1, 2, 3, 4, 5然后步长都是 5。各个 Redis 生成的 ID 为 A1, 6, 11, 16, 21 B2, 7, 12, 17, 22 C3, 8, 13, 18, 23 D4, 9, 14, 19, 24 E5, 10, 15, 20, 25 随便负载到哪个机确定好未来很难做修改。步长和初始值一定需要事先确定。使用 Redis 集群也可以防止单点故障的问题。另外比较适合使用 Redis 来生成每天从0开始的流水号。比如订单号 日期 当日自增长号。可以每天在 Redis 中生成一个 Key 使用 INCR 进行累加。 5. Twitter的snowflake算法 Twitter开源的snowflake以​时间戳机器递增序列​组成基本趋势递增且性能很高。 snowflake生成的是一个Long类型的值Long类型的数据占用8个 字节也就是64位。SnowFlake将64进行拆分每个部分具有不同 的含义当然机器码、序列号的位数可以自定义也可以。 把64-bit分别划分成多段分开来标示机器、时间等比如在snowflake中的64-bit分别表示如下图图片来自网络所示 符号位 (1bit)预留的符号位恒为零。由于 long 类型在 java 中带符号的最高位为符号位正数为 0负数为 1且实际系统中所使用的ID一般都是正数所以最高位为 0时间戳位 (41bit)41 位的时间戳可以容纳的毫秒数是 2 的 41 次幂一年所使用的毫秒数是365 * 24 * 60 * 60 * 1000。通过计算可知Math.pow(2, 41) / (365 * 24 * 60 * 60 * 1000L);结果约等于 69.73 年 ShardingSphere 的雪花算法的时间纪元从 2016 年 11 月 1 日零点开始可以使用到 2086 年相信能满足绝大部分系统的要求。 工作进程位 (10bit)该标志在 Java 进程内是唯一的如果是分布式应用部署应保证每个工作进程的 id 是不同的。该值默认为 0可通过属性设置。10-bit机器可以分别表示1024台机器。如果我们对IDC划分有需求还可以将10-bit分5-bit给IDC分5-bit给工作机器。这样就可以表示32个IDC每个IDC下可以有32台机器可以根据自身需求定义。序列号位 (12bit)该序列是用来在同一个毫秒内生成不同的 ID。如果在这个毫秒内生成的数量超过 4096 (2 的 12 次幂)那么生成器会等待到下个毫秒继续生成。这 12 位计数支持每个节点每毫秒同一台机器同一时刻最多生成 1 12 4096个ID 优点本地生成不依赖中间件。 生成的分布式id足够小只有8个字节而且是递增的 能满足高并发分布式系统环境下ID不重复生成效率高基于时间戳可以保证基本有序递增不依赖于第三方的库或者中间件生成的id具有时序性和唯一性 缺点时钟回拨问题强烈依赖于服务器的时间如果时间出现时间回拨 就可能出现重复的id 6. 百度UidGenerator 具体可以参考官网说明https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md 7. 美团Leaf Leaf 是美团开源的分布式ID生成器能保证全局唯一性、趋势递增、单调递增、信息安全里面也提到了几种分布式方案的对比但也需要依赖关系数据库、ZooKeeper等中间件。 https://github.com/Meituan-Dianping/Leaf/tree/master/leaf-core 具体可以参考官网说明https://tech.meituan.com/2017/04/21/mt-leaf.html 8.滴滴Tinyid Tinyid由滴滴开发Github地址https://github.com/didi/tinyid。 Tinyid是基于号段模式原理实现的与Leaf如出一辙每个服务获取一个号段1000,2000]、2000,3000]、3000,4000] 说了这么多我们今天就主要说一下 Twitter开源的snowflake 三、snowflake 1.流程 2.java 代码实现 如下示例41bit给时间戳5bit给IDC5bit给工作机器12bit给序列号代码中是写死的如果某些bit需要动态调整可在成员属性定义。计算过程需要一些位运算基础。 import java.util.Date;/*** Author allen* Description TODO* Date 2023-07-26 9:51* Version 1.0*/ public class SnowFlakeUtil {private static SnowFlakeUtil snowFlakeUtil;static {snowFlakeUtil new SnowFlakeUtil();}// 初始时间戳(纪年)可用雪花算法服务上线时间戳的值// 16507899648862022-04-24 16:45:59private static final long INIT_EPOCH 1650789964886L;// 时间位取private static final long TIME_BIT 0b1111111111111111111111111111111111111111110000000000000000000000L;// 记录最后使用的毫秒时间戳主要用于判断是否同一毫秒以及用于服务器时钟回拨判断private long lastTimeMillis -1L;// dataCenterId占用的位数private static final long DATA_CENTER_ID_BITS 5L;// dataCenterId占用5个比特位最大值31// 0000000000000000000000000000000000000000000000000000000000011111private static final long MAX_DATA_CENTER_ID ~(-1L DATA_CENTER_ID_BITS);// dataCenterIdprivate long dataCenterId;// workId占用的位数private static final long WORKER_ID_BITS 5L;// workId占用5个比特位最大值31// 0000000000000000000000000000000000000000000000000000000000011111private static final long MAX_WORKER_ID ~(-1L WORKER_ID_BITS);// workIdprivate long workerId;// 最后12位代表每毫秒内可产生最大序列号即 2^12 - 1 4095private static final long SEQUENCE_BITS 12L;// 掩码最低12位为1高位都为0主要用于与自增后的序列号进行位与如果值为0则代表自增后的序列号超过了4095// 0000000000000000000000000000000000000000000000000000111111111111private static final long SEQUENCE_MASK ~(-1L SEQUENCE_BITS);// 同一毫秒内的最新序号最大值可为 2^12 - 1 4095private long sequence;// workId位需要左移的位数 12private static final long WORK_ID_SHIFT SEQUENCE_BITS;// dataCenterId位需要左移的位数 125private static final long DATA_CENTER_ID_SHIFT SEQUENCE_BITS WORKER_ID_BITS;// 时间戳需要左移的位数 1255private static final long TIMESTAMP_SHIFT SEQUENCE_BITS WORKER_ID_BITS DATA_CENTER_ID_BITS;/*** 无参构造*/public SnowFlakeUtil() {this(1, 1);}/*** 有参构造* param dataCenterId* param workerId*/public SnowFlakeUtil(long dataCenterId, long workerId) {// 检查dataCenterId的合法值if (dataCenterId 0 || dataCenterId MAX_DATA_CENTER_ID) {throw new IllegalArgumentException(String.format(dataCenterId 值必须大于 0 并且小于 %d, MAX_DATA_CENTER_ID));}// 检查workId的合法值if (workerId 0 || workerId MAX_WORKER_ID) {throw new IllegalArgumentException(String.format(workId 值必须大于 0 并且小于 %d, MAX_WORKER_ID));}this.workerId workerId;this.dataCenterId dataCenterId;}/*** 获取唯一ID* return*/public static Long getSnowFlakeId() {return snowFlakeUtil.nextId();}/*** 通过雪花算法生成下一个id注意这里使用synchronized同步* return 唯一id*/public synchronized long nextId() {long currentTimeMillis System.currentTimeMillis();//System.out.println(currentTimeMillis);// 当前时间小于上一次生成id使用的时间可能出现服务器时钟回拨问题if (currentTimeMillis lastTimeMillis) {throw new RuntimeException(String.format(可能出现服务器时钟回拨问题请检查服务器时间。当前服务器时间戳%d上一次使用时间戳%d, currentTimeMillis,lastTimeMillis));}if (currentTimeMillis lastTimeMillis) {// 还是在同一毫秒内则将序列号递增1序列号最大值为4095// 序列号的最大值是4095使用掩码最低12位为1高位都为0进行位与运行后如果值为0则自增后的序列号超过了4095// 那么就使用新的时间戳sequence (sequence 1) SEQUENCE_MASK;if (sequence 0) {currentTimeMillis getNextMillis(lastTimeMillis);}} else { // 不在同一毫秒内则序列号重新从0开始序列号最大值为4095sequence 0;}// 记录最后一次使用的毫秒时间戳lastTimeMillis currentTimeMillis;// 核心算法将不同部分的数值移动到指定的位置然后进行或运行// 左移运算符, 1 2 即将二进制的 1 扩大 2^2 倍// |位或运算符, 是把某两个数中, 只要其中一个的某一位为1, 则结果的该位就为1// 优先级 |return// 时间戳部分((currentTimeMillis - INIT_EPOCH) TIMESTAMP_SHIFT)// 数据中心部分| (dataCenterId DATA_CENTER_ID_SHIFT)// 机器表示部分| (workerId WORK_ID_SHIFT)// 序列号部分| sequence;}/*** 获取指定时间戳的接下来的时间戳也可以说是下一毫秒* param lastTimeMillis 指定毫秒时间戳* return 时间戳*/private long getNextMillis(long lastTimeMillis) {long currentTimeMillis System.currentTimeMillis();while (currentTimeMillis lastTimeMillis) {currentTimeMillis System.currentTimeMillis();}return currentTimeMillis;}/*** 获取随机字符串,length13* return*/public static String getRandomStr() {return Long.toString(getSnowFlakeId(), Character.MAX_RADIX);}/*** 从ID中获取时间* param id 由此类生成的ID* return*/public static Date getTimeBySnowFlakeId(long id) {return new Date(((TIME_BIT id) 22) INIT_EPOCH);}public static void main(String[] args) {SnowFlakeUtil snowFlakeUtil new SnowFlakeUtil();long id snowFlakeUtil.nextId();System.out.println(id);Date date SnowFlakeUtil.getTimeBySnowFlakeId(id);System.out.println(date);long time date.getTime();System.out.println(time);System.out.println(getRandomStr());/* System.out.println();long startTime System.currentTimeMillis();for (int i 0; i 10000000; i) {long id2 snowFlakeUtil.nextId();System.out.println(id2);}System.out.println(System.currentTimeMillis() - startTime);*/}} 主要就这个 long id snowFlakeUtil.nextId(); 时钟同步问题解决方案 雪花算法-Java实现-解决时钟回拨的一种方法_雪花算法时钟回拨_fierys的博客-CSDN博客 这个代码是借鉴另外一位博主的方案可以借鉴一下仅供参考 import java.io.IOException; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter;/**相较于标准算法,加入了时钟回拨解决方法,仅单机研究,仅个人思考,仅供参考*/ public class SnowFlow {//因为二进制里第一个 bit 为如果是 1那么都是负数但是我们生成的 id 都是正数所以第一个 bit 统一都是 0。//机器ID 2进制5位 32位减掉1位 31个private long workerId;//机房ID 2进制5位 32位减掉1位 31个private long datacenterId;//代表一毫秒内生成的多个id的最新序号 12位 4096 -1 4095 个private long sequence;//设置一个时间初始值 2^41 - 1 差不多可以用69年private long twepoch 1420041600000L;//5位的机器idprivate long workerIdBits 5L;//5位的机房id。‘private long datacenterIdBits 5L;//每毫秒内产生的id数 2 的 12次方private long sequenceBits 12L;// 这个是二进制运算就是5 bit最多只能有31个数字也就是说机器id最多只能是32以内private long maxWorkerId -1L ^ (-1L workerIdBits);// 这个是一个意思就是5 bit最多只能有31个数字机房id最多只能是32以内private long maxDatacenterId -1L ^ (-1L datacenterIdBits);private long workerIdShift sequenceBits;private long datacenterIdShift sequenceBits workerIdBits;private long timestampLeftShift sequenceBits workerIdBits datacenterIdBits;// -1L 二进制就是1111 1111 为什么// -1 左移12位就是 1111 1111 0000 0000 0000 0000// 异或 相同为0 不同为1// 1111 1111 0000 0000 0000 0000// ^// 1111 1111 1111 1111 1111 1111// 0000 0000 1111 1111 1111 1111 换算成10进制就是4095private long sequenceMask -1L ^ (-1L sequenceBits);//记录产生时间毫秒数判断是否是同1毫秒private long lastTimestamp -1L;public long getWorkerId(){return workerId;}public long getDatacenterId() {return datacenterId;}public long getTimestamp() {return System.currentTimeMillis();}//是否发生了时钟回拨private boolean isBackwordsFlag false;//是否是第一次发生时钟回拨, 用于设置时钟回拨时间点private boolean isFirstBackwordsFlag true;//记录时钟回拨发生时间点, 用于判断回拨后的时间达到回拨时间点时, 跳过 已经用过的 时钟回拨发生时间点 之后的时间 到 未来时间的当前时间点private long backBaseTimestamp -1L;public SnowFlow() {}public SnowFlow(long workerId, long datacenterId, long sequence) {// 检查机房id和机器id是否超过31 不能小于0if (workerId maxWorkerId || workerId 0) {throw new IllegalArgumentException(String.format(worker Id cant be greater than %d or less than 0,maxWorkerId));}if (datacenterId maxDatacenterId || datacenterId 0) {throw new IllegalArgumentException(String.format(datacenter Id cant be greater than %d or less than 0,maxDatacenterId));}this.workerId workerId;this.datacenterId datacenterId;this.sequence sequence;}// 这个是核心方法通过调用nextId()方法// 让当前这台机器上的snowflake算法程序生成一个全局唯一的idpublic synchronized long nextId() {// 这儿就是获取当前时间戳单位是毫秒long timestamp timeGen();//--20220813--1---------------------------------------if (isBackwordsFlag) {//当回拨时间再次叨叨回拨时间点时, 跳过回拨这段时间里已经使用了的未来时间if (timestamp backBaseTimestamp timestamp lastTimestamp) {//直接将当前时间设置为最新的未来时间timestamp lastTimestamp;} else if(timestamp lastTimestamp) {//当前时间已经大于上次时间, 重置时钟回拨标志isBackwordsFlag false;isFirstBackwordsFlag true;System.out.println(时间已恢复正常-- timestamp);} else {// timestamp lastTimestamp 等于的情况在后面}}//--20220813--1----------------------------------------// 判断是否小于上次时间戳如果小于的话就抛出异常if (timestamp lastTimestamp) {System.err.printf(lastTimestamp%d, timestamp%d, l-t%d \n, lastTimestamp, timestamp, lastTimestamp - timestamp); // throw new RuntimeException( // String.format(Clock moved backwards. Refusing to generate id for %d milliseconds, // lastTimestamp - timestamp));//--20220813--2---------------------------------------//这里不再抛出异常, 改为记录时钟回拨发生时间点//发生时钟回拨后, 当前时间 timestamp 就变成了 过去的时间//此时将 timestamp 设置为 上一次时间, 即相对于当前时间的未来时间timestamp lastTimestamp;isBackwordsFlag true;//记录时钟回拨发生的时间点, 后续需要跳过已经使用的未来时间段if (isFirstBackwordsFlag) {backBaseTimestamp timestamp;isFirstBackwordsFlag false;System.out.println(时钟回拨已发生-- backBaseTimestamp);}//--20220813--2---------------------------------------}// 下面是说假设在同一个毫秒内又发送了一个请求生成一个id// 这个时候就得把seqence序号给递增1最多就是4096if (timestamp lastTimestamp) {// 这个意思是说一个毫秒内最多只能有4096个数字无论你传递多少进来//这个位运算保证始终就是在4096这个范围内避免你自己传递个sequence超过了4096这个范围sequence (sequence 1) sequenceMask;//当某一毫秒的时间产生的id数 超过4095系统会进入等待直到下一毫秒系统继续产生IDif (sequence 0) {//timestamp tilNextMillis(lastTimestamp);//--20220813--3---------------------------------------//这里也不能阻塞了, 因为阻塞方法中需要用到当前时间, 改为将此时代表未来的时间 加 1if (isBackwordsFlag) {lastTimestamp;//根据博友评论反馈, 这里可能需要重新赋值, 如果有人看到这个, 可以验证//timestamp lastTimestamp;} else {timestamp tilNextMillis(lastTimestamp);}//--20220813--3---------------------------------------}} else {//sequence 0;//每毫秒的序列号都从0开始的话会导致没有竞争情况返回的都是偶数。解决方法是用时间戳1这样就会随机得到1或者0。sequence timestamp 1;}// 这儿记录一下最近一次生成id的时间戳单位是毫秒//lastTimestamp timestamp;//--20220813--4---------------------------------------if(isBackwordsFlag) {//什么都不做} else {lastTimestamp timestamp;}//--20220813--4---------------------------------------// 这儿就是最核心的二进制位运算操作生成一个64bit的id// 先将当前时间戳左移放到41 bit那儿将机房id左移放到5 bit那儿将机器id左移放到5 bit那儿将序号放最后12 bit// 最后拼接起来成一个64 bit的二进制数字转换成10进制就是个long型long sn ((timestamp - twepoch) timestampLeftShift) |(datacenterId datacenterIdShift) |(workerId workerIdShift) | sequence;if (isBackwordsFlag) {System.out.printf(sn%d\n, sn);}return sn;}/*** 当某一毫秒的时间产生的id数 超过4095系统会进入等待直到下一毫秒系统继续产生ID* param lastTimestamp* return*/private long tilNextMillis(long lastTimestamp) {long timestamp timeGen();while (timestamp lastTimestamp) {timestamp timeGen();}return timestamp;}//获取当前时间戳private long timeGen(){return System.currentTimeMillis();}/*** main 测试类* param args*/public static void main(String[] args) throws IOException, InterruptedException {SnowFlow snowFlow new SnowFlow(1, 1, 1);int count 10000000;//int count 100;for (int i 0; i count; i) {//实际测试发现遍历太快输出日志过多导致卡顿, 增加睡眠时间, 或输出到文件snowFlow.nextId();//Thread.sleep(100);// System.out.println(snowFlow.nextId());// if (i 1000) {//不具有管理员权限的用户, 修改不成功//testClockMvoedBackwords(30); // }//改为 手动修改, 右键cmd,以管理员权限打开后,使用time命令即可, time 16:15:00}System.out.println(System.currentTimeMillis());/*** 这里为什么特意输出一个开始时间呢, 其实就是一个运行了两年的程序突然有一天出bug了,导致了严重的生产事件!* 那么时间初始化影响什么呢, 答案是 序列的长度* 有人就说了, 这个一般是作为 主键用的, 长度貌似影响不大, 确实是这样的* 这次的bug不是雪花算法本身的问题, 而是程序里面有个功能是严格长度截取的, 并且只考虑了长度不够的情况, 没有考虑到变长的情况* 最根本的原因是 本人截取的时候 序列的长度一直是18位, 然后截取9位的代码是这么写的 substring(9);* 当未来的某一天序列长度增加到了19位,那么这个截取就会返回10位长度, 最终导致一个大范围的交易失败......* 锅当然是本人背, 这里提出这种情况, 供大家参考.* 经过仔细研究所谓的序列可以使用69年, 序列的长度变化是这样的, 假设以当前时间为初始化值* 12 13 14 15 16 17 18(约7年) 19(约58年)* 长度随时间增加, 长度越长, 保持相同长度的时间越长*/DateTimeFormatter dtf2 DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss);String dateString 2015-01-01 00:00:00;LocalDateTime localDateTime LocalDateTime.parse(dateString,dtf2);System.out.println(localDateTime.toInstant(ZoneOffset.ofHours(8)).toEpochMilli());}//windows os 模拟时钟回拨, 将当前时间减去几秒private static void testClockMvoedBackwords(long seconds) throws IOException {System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofPattern(HH:mm:ss)));LocalDateTime localDateTime LocalDateTime.now();String backTime localDateTime.minusSeconds(seconds).format(DateTimeFormatter.ofPattern(HH:mm:ss));System.out.println(backTime);if (System.getProperty(os.name).contains(Windows)) {String cmd cmd /c start time 15:41:56;// backTime;//不具有管理员权限的用户, 修改不生效, 提示 客户端无所需特权Runtime.getRuntime().exec(cmd); // Runtime.getRuntime().exec(cmd /c notepad);System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofPattern(HH:mm:ss)));}} }3.Snowflake生产方案 时钟回拨问题解决思路 第一种办法就是关闭时钟同步避免产生时钟同步问题不过这个不太现实因为强依赖时间的系统一般都得做时钟同步避免时间严重错误在虚拟机上部署一些东西玩儿虚拟机休眠再次恢复之后往往虚拟机里的时间和宿主机的时间是不同步的导致一些大数据的分布式系统会崩溃掉节点之间通信会依赖时间戳进行比对心跳过慢就会导致节点挂掉 第二种办法记录下来上一次生成ID的时间如果发现本次生成ID的时候时间戳小于上次的时间戳说明时钟回拨了此时就这个时间内不允许生成ID一直等等待到当前时间追上上一次生成时间问题在于万一回拨的时间太多了呢可能要等很久影响了系统的可用性所以也不是特别好的办法内存里可以存上一次生成唯一ID的时间戳时钟回拨了把当前时间戳会回拨到上次时间戳之前请求过来要生成唯一ID你不要直接就返回一个ID给他你先做一个比较如果你发现当前时间戳跟上一次生成唯一ID的时间戳想比比他小判定时钟回拨只要你生成ID就有可能会发生ID的重复可用性这么差的话人家业务服务万一此时是要生成一个账单数据申请一个ID此时你好不容易等待了几百毫秒之后你还告诉他你内部异常没法获取到唯一ID反复的重试你会影响他的业务服务的运行。   第三种办法针对第二种办法的优化如果发现时钟回拨太狠了比如超过了1分钟此时直接就报警同时不再对外提供服务把自己从集群里摘了比如你要是基于微服务注册中心进行注册的就得主动做一个下线当你发现当前时间戳比上一次生成ID的时间戳要小发现时钟回拨了判断一下回拨了多少毫秒比如说回拨时间在500ms以内此时可以hang住请求等待500ms等到500ms之后当前时间戳比上一次生成ID的时间戳要大了 此时就可以正常生成唯一ID返回给业务方了对于业务方而言仅仅是在个别少数的时钟回拨的情况之下请求平时只要50ms500ms还在接受范围之内所以说还是可以的只不过请求慢了一些 如果你要是发现你当前时间戳和上一次生成唯一ID的时间戳想比你一比较就发现超过了500ms了超过了500ms了但是在5s之内此时你可以返回一个异常状态异常持续时间给客户端不要说有问题可以通知他自行进行重试 重试机制最好不要让业务方自己去做你完全可以去封装一个你的唯一ID生成服务的客户端基于RPC请求你的接口但是你在自己的客户端里封装一个自动重试的机制他一旦发现某台服务器返回的响应说自己短时间内没法提供服务他自动就去请求其他机器上的服务获取唯一ID了 如果要解决时钟回拨一般是第二种和第三种结合在一起来用但是被动等待甚至主动下线总是影响系统可用性的都不是特别好 服务端的时钟回拨检测机制 客户端自己封装 1s以内阻塞请求等待客户端的超时时间应该也是1s暴露1s内每一毫秒生成过的唯一ID最大的序号根据当前时间戳的毫秒定位到之前生成过ID的这一毫秒的最大ID序号此时继续生成ID直接在之前生成过的这一毫秒的最大ID序号基础上递增就可以了优化之后就可以保证不需要阻塞等待 1s~10s之间返回异常码和异常持续时间客户端在指定时间内不请求这台机器 10s以上返回故障码请求服务注册中心让自己下线客户端收到故障码之后就直接把这个机器从服务机器列表里剔除掉不请求他了后续等到那台机器部署的ID服务他发现自己的时间可能过了几秒钟缓过来了恢复了可用了就可以再次进行服务注册你客户端刷新服务注册列表的时候就会发现他此时可以再次去请求他   第四种办法要在内存里维护最近几秒内生成的ID值一般时钟回拨都是几十毫秒到几百毫秒很少会超过秒的所以保存最近几秒的就行了然后如果发生了时钟回拨此时就看看回拨到了哪一毫秒因为时间戳是毫秒级的接着就看那一毫秒 从那一毫秒生产过的ID序号往后继续生成就可以了后续每一毫秒都是依次类推这样就可以完美避免重复问题还不用等待 但是这里也得做一个兜底机制就是比如你保留最近10s内每一毫秒生成的ID那么万一时钟回拨碰巧超过了10s呢此时这种概率很低你可以跟二三两个方案结合设置几个阈值比如说你保留最近10s的ID回拨10s内都可以保证不重复不停顿如果超过10s在60s内可以有一个等待的过程让他时间前进到你之前保留过的10s范围内去如果回拨超过了60s直接下线 上一次生成唯一ID的时间戳也没了最近1s内每一毫秒的最大ID序号也没了重启之后出现了时间回拨发现不了时间回拨问题其次也没有办法继续按照之前的思路去生成不重复的唯一ID了 4.时钟回拨优化 1、我们一般需要打开时钟同步功能这样ID才能够最大化的保证按照时间有序但是时钟同步打开后就可能会时钟回拨了如果时钟回拨了那么生成的ID就会重复为此我们一般打开时钟同步的同时关闭时钟回拨功能 2、序列号的位数有限能表示的ID个数有限时钟同步的时候如果某台服务器快了很多虽然关闭了时钟回拨但是在时间追赶上前ID可能已经用完当自增序列号用完了我们可以做如下的工作停止ID生成服务并告警、如果时钟回拨小于一定的阈值则等待、如大于一定的阈值则通过第三方组件如ZK重新生成一个workerid或者自增时间戳借用下一个时间戳的ID 3、服务重启后ID可能会重复为此我们一般需要定期保存时间戳重启后的时间戳必须大于保存的时间戳几倍保存间隔时间如3倍为什么要几倍呢主要是考虑到数据丢失的情况但是如果保存到本地硬盘且每次保存都fsync此时1倍即可。重启后如果小于可以像第二点那样类似处理 4、如果请求ID的QPS不高比如每毫秒一个那么每次获取的ID的尾号都是0那么基于ID做分库分表可能数据分布就会不均此时我们可以增加时间戳的时间间隔或者序列号每次从随机一个值开始自增。 主要是生成id的这个方法我把这个再优化一下这个是根据美团leaf做了进一步优化请参考 /*** 通过雪花算法生成下一个id注意这里使用synchronized同步* return 唯一id*/public synchronized long nextId() {long currentTimeMillis System.currentTimeMillis();//System.out.println(currentTimeMillis);// 当前时间小于上一次生成id使用的时间可能出现服务器时钟回拨问题//long timestamp timeGen();if (currentTimeMillis lastTimeMillis) {long offset lastTimeMillis - currentTimeMillis;if (offset 5) {try {wait(offset 1);currentTimeMillis timeGen();if (currentTimeMillis lastTimeMillis) {throw new RuntimeException(id生成失败);}} catch (InterruptedException e) {throw new RuntimeException(生成id时出现错误);}} else {throw new RuntimeException(String.format(可能出现服务器时钟回拨问题请检查服务器时间。当前服务器时间戳%d上一次使用时间戳%d, currentTimeMillis,lastTimeMillis));}}/*if (currentTimeMillis lastTimeMillis) {throw new RuntimeException(String.format(可能出现服务器时钟回拨问题请检查服务器时间。当前服务器时间戳%d上一次使用时间戳%d, currentTimeMillis,lastTimeMillis));}*/if (currentTimeMillis lastTimeMillis) {// 还是在同一毫秒内则将序列号递增1序列号最大值为4095// 序列号的最大值是4095使用掩码最低12位为1高位都为0进行位与运行后如果值为0则自增后的序列号超过了4095// 那么就使用新的时间戳sequence (sequence 1) SEQUENCE_MASK;if (sequence 0) {currentTimeMillis getNextMillis(lastTimeMillis);}} else { // 不在同一毫秒内则序列号重新从0开始序列号最大值为4095sequence 0;}// 记录最后一次使用的毫秒时间戳lastTimeMillis currentTimeMillis;// 核心算法将不同部分的数值移动到指定的位置然后进行或运行// 左移运算符, 1 2 即将二进制的 1 扩大 2^2 倍// |位或运算符, 是把某两个数中, 只要其中一个的某一位为1, 则结果的该位就为1// 优先级 |return// 时间戳部分((currentTimeMillis - INIT_EPOCH) TIMESTAMP_SHIFT)// 数据中心部分| (dataCenterId DATA_CENTER_ID_SHIFT)// 机器表示部分| (workerId WORK_ID_SHIFT)// 序列号部分| sequence;} 【分布式】分布式唯一 ID 的 8 种生成方案 SnowFlake 雪花算法详解与实现 - 掘金 雪花算法SnowFlake_文丑颜不良啊的博客-CSDN博客 分布式唯一Id(雪花算法),原理对比方案 - 简书 雪花算法snowflake分布式id生成原理详解以及对解决时钟回拨问题几种方案讨论_51CTO博客_snowflake 分布式id Leaf——美团点评分布式ID生成系统 - 美团技术团队 https://github.com/Meituan-Dianping/Leaf/tree/master/leaf-core Snowflake生产方案 时钟回拨问题解决思路_启动报错 snowflake 时钟回拨解决方法_都是底层的博客-CSDN博客
http://www.yingshimen.cn/news/60335/

相关文章:

  • 棋牌网站怎么做优化网站开发流程php
  • 中交建设设计院有限公司网站图片转链接生成器在线制作
  • 商务网站建设毕业设计模板下载免费网站建设域名
  • 苏州建网站的公collectfrom企业网站模版
  • 长沙平台网站建设国外学做咖啡的网站
  • 怎么看网站空间html网站模板 免费
  • 厦门专业网站建设网站管理员是干什么的
  • 小公司建网站 优帮云asp.net网站开发
  • 国外做储物的网站ps制作网站
  • 西安市网站搭建天门网站
  • 东莞seo建站优化方法影响网站pr的主要因素有哪些
  • 网站服务器可以自己做吗公主岭网站建设规划
  • 上海做网站的网站做宣传图片的网站
  • 阿里云 网站建设方案书12306网站是哪家公司做开发的
  • php mysql网站开发书商丘seo排名
  • 商城网站互动性广西壮族自治区行政执法人员网络培训系统
  • wap商城网站模板素材网站开发的目的和意义
  • thinkphp 网站模版wordpress分类含有中文
  • 单位如何建设网站株洲seo优化
  • 数据库能上传网站模板广东模板网站建设
  • 室内设计招标网站北京企业网站模板建站怎么用
  • 网站突然消失了如何做漂亮的网站首页
  • 大型新型网站网站建设要多少钱
  • 苏州网站建设系统电话哈尔滨网站建设方案外包
  • 网站开发多用什么语言网站首页添加代码
  • 营口网站建设做app挣钱还是网站
  • 网站建设是不是要有营业执照北京网站开发哪里好薇
  • 深圳网站开发企业黄冈黄页88网黄冈房产估价
  • 网站导航背景 蓝色深圳建网站兴田德润团队
  • 电商 网站建设黄江镇网站建设公司