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

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 高并发下分布式ID各个的解决方案以及redis集群分布式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位的:

  1. 使用41 bit来存放时间,精确到毫秒,可以使用41年。
  2. 使用12 bit来存放逻辑分片ID,最大分片ID是4095
  3. 使用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)ua脚本代码

-- 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;
end
local 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<Long> 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唯一标识

35e09dcd72b1455a87c2c4cff47d2db1.png


(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 = {"unchecked", "rawtypes"})
@Service("idGenService")
public class IdGenService {
    @Autowired
    private JedisPool jedisPool;
    /**
     * JedisPool, luaSha
     */
    private static List<Pair<JedisPool, String>> jedisPoolList = new ArrayList<>();
    private static int retryTimes = 0;
    private int index = 0;
    private IdGenService() {
    }
    private IdGenService(List<Pair<JedisPool, String>> list, int time) {
        jedisPoolList = list;
        retryTimes = time;
    }
    public IdGenService.IdGeneratorBuilder builder() {
        return new IdGenService.IdGeneratorBuilder();
    }
    public static class IdGeneratorBuilder {
        List<Pair<JedisPool, String>> 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<JedisPool, String> pair = jedisPoolList.get(i);
        JedisPool jedisPool = pair.getLeft();
        String luaSha = pair.getRight();
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            List<Long> result = (List<Long>) 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<Long> parseId(long id) {
        long miliSecond = id >>> 22;
        long shardId = (id & (0xFFF << 10)) >> 10;
        List<Long> re = new ArrayList<Long>(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<Long> 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方法生成


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

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
20天前
|
NoSQL Java 中间件
【📕分布式锁通关指南 02】基于Redis实现的分布式锁
本文介绍了从单机锁到分布式锁的演变,重点探讨了使用Redis实现分布式锁的方法。分布式锁用于控制分布式系统中多个实例对共享资源的同步访问,需满足互斥性、可重入性、锁超时防死锁和锁释放正确防误删等特性。文章通过具体示例展示了如何利用Redis的`setnx`命令实现加锁,并分析了简化版分布式锁存在的问题,如锁超时和误删。为了解决这些问题,文中提出了设置锁过期时间和在解锁前验证持有锁的线程身份的优化方案。最后指出,尽管当前设计已解决部分问题,但仍存在进一步优化的空间,将在后续章节继续探讨。
462 131
【📕分布式锁通关指南 02】基于Redis实现的分布式锁
|
23天前
|
NoSQL Java Redis
Springboot使用Redis实现分布式锁
通过这些步骤和示例,您可以系统地了解如何在Spring Boot中使用Redis实现分布式锁,并在实际项目中应用。希望这些内容对您的学习和工作有所帮助。
153 83
|
19天前
|
缓存 NoSQL 搜索推荐
【📕分布式锁通关指南 03】通过Lua脚本保证redis操作的原子性
本文介绍了如何通过Lua脚本在Redis中实现分布式锁的原子性操作,避免并发问题。首先讲解了Lua脚本的基本概念及其在Redis中的使用方法,包括通过`eval`指令执行Lua脚本和通过`script load`指令缓存脚本。接着详细展示了如何用Lua脚本实现加锁、解锁及可重入锁的功能,确保同一线程可以多次获取锁而不发生死锁。最后,通过代码示例演示了如何在实际业务中调用这些Lua脚本,确保锁操作的原子性和安全性。
48 6
【📕分布式锁通关指南 03】通过Lua脚本保证redis操作的原子性
|
1月前
|
缓存 NoSQL 中间件
Redis,分布式缓存演化之路
本文介绍了基于Redis的分布式缓存演化,探讨了分布式锁和缓存一致性问题及其解决方案。首先分析了本地缓存和分布式缓存的区别与优劣,接着深入讲解了分布式远程缓存带来的并发、缓存失效(穿透、雪崩、击穿)等问题及应对策略。文章还详细描述了如何使用Redis实现分布式锁,确保高并发场景下的数据一致性和系统稳定性。最后,通过双写模式和失效模式讨论了缓存一致性问题,并提出了多种解决方案,如引入Canal中间件等。希望这些内容能为读者在设计分布式缓存系统时提供有价值的参考。感谢您的阅读!
130 6
Redis,分布式缓存演化之路
|
15天前
|
缓存 NoSQL Redis
Redis原理—3.复制、哨兵和集群
详细介绍了Redis的复制原理、哨兵原理和集群原理。
|
3月前
|
存储 SpringCloudAlibaba Java
【SpringCloud Alibaba系列】一文全面解析Zookeeper安装、常用命令、JavaAPI操作、Watch事件监听、分布式锁、集群搭建、核心理论
一文全面解析Zookeeper安装、常用命令、JavaAPI操作、Watch事件监听、分布式锁、集群搭建、核心理论。
【SpringCloud Alibaba系列】一文全面解析Zookeeper安装、常用命令、JavaAPI操作、Watch事件监听、分布式锁、集群搭建、核心理论
|
3月前
|
消息中间件 架构师 数据库
本地消息表事务:10Wqps 高并发分布式事务的 终极方案,大厂架构师的 必备方案
45岁资深架构师尼恩分享了一篇关于分布式事务的文章,详细解析了如何在10Wqps高并发场景下实现分布式事务。文章从传统单体架构到微服务架构下分布式事务的需求背景出发,介绍了Seata这一开源分布式事务解决方案及其AT和TCC两种模式。随后,文章深入探讨了经典ebay本地消息表方案,以及如何使用RocketMQ消息队列替代数据库表来提高性能和可靠性。尼恩还分享了如何结合延迟消息进行事务数据的定时对账,确保最终一致性。最后,尼恩强调了高端面试中需要准备“高大上”的答案,并提供了多个技术领域的深度学习资料,帮助读者提升技术水平,顺利通过面试。
本地消息表事务:10Wqps 高并发分布式事务的 终极方案,大厂架构师的 必备方案
|
3月前
|
存储 NoSQL Redis
redis主从集群与分片集群的区别
主从集群通过主节点处理写操作并向从节点广播读操作,从节点处理读操作并复制主节点数据,优点在于提高读取性能、数据冗余及故障转移。分片集群则将数据分散存储于多节点,根据规则路由请求,优势在于横向扩展能力强,提升读写性能与存储容量,增强系统可用性和容错性。主从适用于简单场景,分片适合大规模高性能需求。
91 5
|
3月前
|
存储 NoSQL Java
使用lock4j-redis-template-spring-boot-starter实现redis分布式锁
通过使用 `lock4j-redis-template-spring-boot-starter`,我们可以轻松实现 Redis 分布式锁,从而解决分布式系统中多个实例并发访问共享资源的问题。合理配置和使用分布式锁,可以有效提高系统的稳定性和数据的一致性。希望本文对你在实际项目中使用 Redis 分布式锁有所帮助。
254 5
|
4月前
|
NoSQL Java 数据处理
基于Redis海量数据场景分布式ID架构实践
【11月更文挑战第30天】在现代分布式系统中,生成全局唯一的ID是一个常见且重要的需求。在微服务架构中,各个服务可能需要生成唯一标识符,如用户ID、订单ID等。传统的自增ID已经无法满足在集群环境下保持唯一性的要求,而分布式ID解决方案能够确保即使在多个实例间也能生成全局唯一的标识符。本文将深入探讨如何利用Redis实现分布式ID生成,并通过Java语言展示多个示例,同时分析每个实践方案的优缺点。
127 8