算法的核心思想是结合机器的网卡、当地时间、一个随记数来生成UUID。java
使用数据库的id自增策略,如MySQL的auto_increment。而且可使用两台数据库分别设置不一样步长,生成不重复ID的策略来实现高可用。git
twitter生成全局ID生成器的算法策略。github
简单来讲:就是把64的Long型数据由如下几个部分组成:
符号位(1位)-时间戳(41位)-数据中心标识(5位)-ID生成器实例标识(5位)-序列号(12位)
经过部署多个ID生成器,位各个业务系统生成全局惟一的Long型ID。redis
生成策略相似雪花算法。算法
时间戳+机器ID+进程ID+序列号=>ObjectId对象数据库
方式 | 优势 | 缺点 |
---|---|---|
UUID | 本地生成,无中心,无性能瓶颈 | 无序,过长 |
MongoDB ObjectId | 本地生成,含时间戳,有序 | 过长 |
数据库sequence | 有序 | 中心生成,独立部署数据库 |
雪花算法 | 有序,Long型 | 中心生成,独立部署ID生成器 |
大体的总结优略点以下:缓存
方式 | 优势 | 缺点 |
---|---|---|
UUID | 本地生成,无中心,无性能瓶颈 | 无序,过长 |
MongoDB ObjectId | 本地生成,含时间戳,有序 | 过长 |
数据库sequence | 有序 | 中心生成,独立部署数据库 |
雪花算法 | 有序,Long型 | 中心生成,独立部署ID生成器 |
看了以上,我想要的ID生成策略是怎样的呢?
64位易操做存储,按时间有序,无中心本地生成。
好吧,其实本文也没有彻底实现以上需求,若是哪位小伙伴有更好方案欢迎回复分享!!!
本文只是基于对以上几种方案的认识,稍加改进,尽量的知足需求!架构
个人想法:app
使用Long型,不可避免参考雪花算法的实现,可是要实现本地化生成,要参考ObjectId的生成策略,使用相似机器ID,进程ID来保证惟一性。分布式
如何解决使用机器ID,进程ID时致使ID过长的问题?
解决方式:放弃使用机器ID,进程ID,使用serverId标识服务,使用instanceId标识服务进程,可是。。。没办法,须要一个中心来进行注册,保证惟一性,本例中使用Redis(不限于redis,database,memcached均可)。
public class IdGenerator { // 时间基线 2016/1/1 private final long timeBaseLine = 1454315864414L; // 服务编号 private volatile long serverId = -1; //服务实例编号 private volatile long instanceId = -1; private static final long serverIdBits = 7; private static final long instanceIdBits = 10; private static final long sequenceBits = 5; ... }
服务A请求redis分配instanceId
/** * 应用启动完成后调用init * * @param serverId */ public synchronized void init(long serverId) { this.serverId = serverId; if (!inited) { inited = true; Jedis jedis = new Jedis("localhost", 6379); ScheduledExecutorService scheduledService = Executors.newScheduledThreadPool(1); RegisterIdCreatorInstanceTask registerIdCreatorInstanceTask = new RegisterIdCreatorInstanceTask(jedis); // 定义定时任务,按期调用redis注册,续约instanceId scheduledService.scheduleWithFixedDelay(registerIdCreatorInstanceTask, 0, RegisterIdCreatorInstanceTask.INTERVAL_SECONDS, TimeUnit.SECONDS); } else { System.out.println("已经初始化!"); } } /** * 注册id生成器实例 */ private class RegisterIdCreatorInstanceTask implements Runnable { private Logger logger = Logger.getLogger(RegisterIdCreatorInstanceTask.class.getCanonicalName()); public static final int INTERVAL_SECONDS = 30; private Jedis jedis; private RegisterIdCreatorInstanceTask(Jedis jedis) { this.jedis = jedis; } public void run() { try { long srvId = idGenerator.getServerId(); long currentInstanceId = idGenerator.getInstanceId(); String prefixKey = ID_CREATOR_KEY + KEY_SEP + srvId + KEY_SEP; if (currentInstanceId < 0) { //注册 registerInstanceIdWithIpv4(); } else { //续约 String result = jedis.set(prefixKey + currentInstanceId, srvId + KEY_SEP + currentInstanceId, "XX", "EX", INTERVAL_SECONDS * 3); if (!"OK".equals(result)) { logger.warning("服务[" + srvId + "]ID生成器:" + currentInstanceId + "续约失败,等待从新注册"); registerInstanceIdWithIpv4(); } else { logger.info("服务[" + srvId + "]ID生成器:" + currentInstanceId + "续约成功"); } } } catch (JedisException e) { logger.severe("Redis 出现异常!"); e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } finally { if (idGenerator.getInstanceId() < 0) { idGenerator.setInited(false); } if (jedis != null) { jedis.close(); } } } private int registerInstanceIdWithIpv4() { long ip4Value = getIp4LongValue(); // Redis key 格式:key->val , ID_CREATOR:serverId:instanceId -> serverId:instanceId String prefixKey = ID_CREATOR_KEY + KEY_SEP + serverId + KEY_SEP; // 须要使用java8 int regInstanceId = registerInstanceId((int) (ip4Value % (maxInstanceId + 1)), (int) maxInstanceId, (v) -> { String res = jedis.set(prefixKey + v, serverId + KEY_SEP + v, "NX", "EX", INTERVAL_SECONDS * 3); return "OK".equals(res) ? v : -1; }); idGenerator.setInstanceId(regInstanceId); idGenerator.setInited(true); logger.info("服务[" + serverId + "]注册了一个ID生成器:" + regInstanceId); return regInstanceId; } /** * 注册instance,成功就返回 * * @param basePoint * @param max * @param action * @return */ public int registerInstanceId(int basePoint, int max, Function<Integer, Integer> action) { int result; for (int i = basePoint; i <= max; i++) { result = action.apply(i); if (result > -1) { return result; } } for (int i = 0; i < basePoint; i++) { result = action.apply(i); if (result > -1) { return result; } } return 0; } /** * IPV4地址转Long * * @return */ private long getIp4LongValue() { try { InetAddress inetAddress = Inet4Address.getLocalHost(); byte[] ip = inetAddress.getAddress(); return Math.abs(((0L | ip[0]) << 24) | ((0L | ip[1]) << 16) | ((0L | ip[2]) << 8) | (0L | ip[3])); } catch (Exception ex) { ex.printStackTrace(); return 0; } } } /** * 获取ID * * @return */ public long getId() { long id = nextId(); return id; } private synchronized long nextId() { if (serverId < 0 || instanceId < 0) { throw new IllegalArgumentException("目前不能生成惟一性ID,serverId:[" + serverId + "],instanceId:[" + instanceId + "]!"); } long timestamp = currentTime(); if (timestamp < lastTimestamp) { throw new IllegalStateException("Err clock"); } sequence = (sequence + 1) & maxSequence; if (lastTimestamp == timestamp) { if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp); } } lastTimestamp = timestamp; long id = ((timestamp - timeBaseLine) << timeBitsShift) | (serverId << serverIdBitsShift) | (instanceId << instanceIdBitsShift) | sequence; return id; }
若有问题欢迎指正!!
若有其余方案,欢迎回复告知,谢谢!!!