分布式架构搭建|高并发下分布式ID各个的解决方案以及redis集群分布式ID代码实现

最近公司要把服务做成分布式的部署,所以生成全局唯一id是首先要考虑的点
总结
目录
1.mysql自增id
2.UUID
3.雪花算法
4.雪花算法的各种变种(解决时间回拨问题)
5.redis集群或单机利用lua生成分布式id(代码演示)
1.mysql自增id 分库分表情况下要设置初始值与步长
优点:不用集成第三方,当下就能使用
缺点:以后随着机器的增多,维护成本以及生成策略不好控制
2.UUID 生来就可以作为分布式id,本身生成简单不需要任何第三方依赖
优点:生成简单,速度快,QPS高(支持100ns级并发),不依赖语言 各个语言都有自己的UUID生成器
缺点:生成长度过长且无序,可读性差,作为分布式主键性能较差,数据库查询和索引效率低
3.雪花算法 1位固定0+41位时间戳(毫秒)+10位工作机器id+12位序列号(一个节点一毫秒最多生成4096个ID)组成生成的64位Long类型的id
优点:所有生成的id按时间趋势递增,整个分布式系统内不会产生重复id(因为有datacenterId和workerId来做区分)
缺点:集群的情况下,服务器之间的时间差会导致时间回拨
4.雪花算法的各种变种(解决时间回拨问题) 百度 uid-generator
传统的雪花算法实现都是通过System.currentTimeMillis()来获取时间并与上一次时间进行比较,这样的实现严重依赖服务器的时间。
而UidGenerator的时间类型是AtomicLong,且通过incrementAndGet()方法获取下一次的时间,从而脱离了对服务器时间的依赖,也就不会有时钟回拨的问题
类似的还有美团的 Leaf 、滴滴的tinyid

这几个生成方案都有一个共同的缺点:增加了系统的复杂度,原本的雪花算法只使用内存计算
而变种系列都或多或少增加了一些第三方中间件
5.redis集群或单机利用lua生成分布式id(代码演示)

原理:
首先,要知道redis的EVAL,EVALSHA命令:
利用redis的lua脚本执行功能,在每个节点上通过lua脚本生成唯一ID。
生成的ID是64位的:
  • 使用41 bit来存放时间,精确到毫秒,可以使用41年。
  • 使用12 bit来存放逻辑分片ID,最大分片ID是4095
  • 使用10 bit来存放自增长ID,意味着每个节点,每毫秒最多可以生成1024个ID
比如GTM时间 Fri Mar 13 10:00:00 CST 2015 ,它的距1970年的毫秒数是 1426212000000,假定分片ID是53,自增长序列是4,则生成的ID是:
5981966696448054276 = 1426212000000 << 22 + 53 << 10 + 41
redis提供了TIME命令,可以取得redis服务器上的秒数和微秒数。因些lua脚本返回的是一个四元组。
second, microSecond, partition, seq
客户端要自己处理,生成最终ID。
((second * 1000 + microSecond / 1000) << (12 + 10)) + (shardId << 10) + seq;
(1)lua脚本代码
-- need redis 3.2+ redis.replicate_commands(); local prefix = '__idgenerator_'; local partitionCount = 4096; local step = 2; local startStep = 1; local tag = KEYS[1]; -- if user do not pass shardId, default partition is 0. local partition if KEYS[2] == nil then partition = 0; else partition = KEYS[2] % partitionCount; endlocal now = redis.call('TIME'); local miliSecondKey = prefix .. tag ..'_' .. partition .. '_' .. now[1] .. '_' .. math.floor(now[2]/1000); local count; repeat count = tonumber(redis.call('INCRBY', miliSecondKey, step)); if count > (1024 - step) then now = redis.call('TIME'); miliSecondKey = prefix .. tag ..'_' .. partition .. '_' .. now[1] .. '_' .. math.floor(now[2]/1000); end until count <= (1024 - step)if count == step then redis.call('PEXPIRE', miliSecondKey, 5); end-- second, microSecond, partition, seq return {tonumber(now[1]), tonumber(now[2]), partition, count + startStep}

(2)利用lua脚本生成机器节点唯一标识
/** * @Description 根据lua脚本生成redis单节点唯一序列号 * @return sha1 唯一序列号 * @Author liuy */ public String createluacreate(){ #防止同一节点标识重复加锁 String lockKey = "soboot"; String uuid = UUID.getSignUUID(); boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey, uuid, 3, TimeUnit.MINUTES); if (!success) { SoBootLogger.error("锁已存在"); } // 执行 lua 脚本 DefaultRedisScript redisScript = new DefaultRedisScript<>(); // 指定 lua 脚本 redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/redis-script-node.lua"))); // 指定返回类型 redisScript.setResultType(Long.class); // 参数一:redisScript,参数二:key列表,参数三:arg(可多个) redisTemplate.execute(redisScript, Collections.singletonList(lockKey), uuid); return redisScript.getSha1(); }

生成的sha1唯一标识
分布式架构搭建|高并发下分布式ID各个的解决方案以及redis集群分布式ID代码实现
文章图片

【分布式架构搭建|高并发下分布式ID各个的解决方案以及redis集群分布式ID代码实现】
(3)yml配置jedis连接redis

# spring配置 spring: redis: host: 127.0.0.1 port: 6379 password: root timeout: 5000 jedis: pool: max-idle: 50 max-active: 20 min-idle: 30

(4)JedisConfig配置类
package com.soboot.system.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; /** * @Author liuy * @Description jedis配置 * @Date 2021/11/2 15:02 * @Version 1.0 */ @Configuration @EnableCaching public class JedisConfig { @Value("${spring.redis.host}") private String host; @Value("${spring.redis.port}") private int port; @Value("${spring.redis.password}") private String password; @Value("${spring.redis.timeout}") private int timeout; @Value("${spring.redis.jedis.pool.max-idle}") private int maxIdle; @Value("${spring.redis.jedis.pool.max-active}") private int maxActive; @Value("${spring.redis.jedis.pool.min-idle}") private int minIdle; @Bean public JedisPool jedisPool(){ JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); jedisPoolConfig.setMaxIdle(maxIdle); jedisPoolConfig.setMaxTotal(maxActive); jedisPoolConfig.setMinIdle(minIdle); JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout, password); return jedisPool; } }

(5)生成分布式id代码
package com.soboot.system.service; import com.soboot.common.core.text.UUID; import com.soboot.common.core.utils.SoBootLogger; import com.soboot.common.redis.service.RedisService; import org.apache.commons.lang3.tuple.Pair; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ClassPathResource; import org.springframework.scripting.support.ResourceScriptSource; import org.springframework.stereotype.Service; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.exceptions.JedisConnectionException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.concurrent.TimeUnit; @SuppressWarnings(value = https://www.it610.com/article/{"unchecked", "rawtypes"}) @Service("idGenService") public class IdGenService {@Autowired private JedisPool jedisPool; /** * JedisPool, luaSha */ private static List> jedisPoolList = new ArrayList<>(); private static int retryTimes = 0; private int index = 0; private IdGenService() { }private IdGenService(List> list, int time) { jedisPoolList = list; retryTimes = time; }public IdGenService.IdGeneratorBuilder builder() { return new IdGenService.IdGeneratorBuilder(); }public static class IdGeneratorBuilder { List> jedisPoolList = new ArrayList(); int retryTimes = 5; public IdGenService.IdGeneratorBuilder addHost(JedisPool jedisPool, String luaSha) { jedisPoolList.add(Pair.of(jedisPool, luaSha)); return this; }public IdGenService build() { return new IdGenService(jedisPoolList, retryTimes); } }public long next(String tab) { for (int i = 0; i < retryTimes; ++i) { Long id = innerNext(tab); if (id != null) { return id; } } throw new RuntimeException("Can not generate id!"); }Long innerNext(String tab) { index++; int i = index % jedisPoolList.size(); Pair pair = jedisPoolList.get(i); JedisPool jedisPool = pair.getLeft(); String luaSha = pair.getRight(); Jedis jedis = null; try { jedis = jedisPool.getResource(); List result = (List) jedis.evalsha(luaSha, 2, tab, "" + i); long id = buildId(result.get(0), result.get(1), result.get(2), result.get(3)); return id; } catch (JedisConnectionException e) { if (jedis != null) { jedisPool.returnBrokenResource(jedis); } } finally { if (jedis != null) { jedisPool.returnResource(jedis); } } return null; }public static long buildId(long second, long microSecond, long shardId, long seq) { long miliSecond = (second * 1000 + microSecond / 1000); return (miliSecond << (12 + 10)) + (shardId << 10) + seq; }public static List parseId(long id) { long miliSecond = id >>> 22; long shardId = (id & (0xFFF << 10)) >> 10; List re = new ArrayList(4); re.add(miliSecond); re.add(shardId); return re; }/** * @param tab evalsha命令参数 一般填需要生成分布式id的业务模块名称 例如 order、user、log * @return 分布式id */ public long getId(String tab) { this.builder() .addHost(jedisPool, "223b0f1b655aa0396ec9d58b3df027ad7626c26a") .build(); long id = this.next(tab); System.out.println("分布式id值:" + id); List result = this.parseId(id); System.out.println("分布式id生成的时间是:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(result.get(0)))); System.out.println("redis节点:" + result.get(1)); return id; } }

(6)调用getId方法生成
分布式架构搭建|高并发下分布式ID各个的解决方案以及redis集群分布式ID代码实现
文章图片

很多解决方案 还是要根据具体需求使用!

    推荐阅读