Redis 生成分布式业务单号

Posted 我有一只肥螳螂

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis 生成分布式业务单号相关的知识,希望对你有一定的参考价值。

背景:微服务架构,有三个微服务,分别是签收、对账、开票,需要生成唯一的分布式单号

格式:标识 + 年月日 + 生成顺序(三位)

例子:

  • QS20230301001,即2023年三月一日第一张签收单
  • DZ20230212002,即2023年二月十二日第二张对账单

原理:利用 Redis 的原子性,保证三位生成顺序的唯一性

新建业务id枚举类 IdEnum

public enum IdEnum 

    SIGN("QS"),
    VERIFY("DZ"),
    INVOICE("KP"),
    RECEIPT("SK"),
    ;

    /**
     * 单号前缀
     */
    private String prefix;

    IdEnum(String prefix) 
        this.prefix = prefix;
    

    public String getPrefix() 
        return prefix;
    

新建 IdUtil

  • getCacheKey:获取 redis 的 KEY
  • getDay:获取 服务标识 + 格式化的日期
  • completionSerial:从redis获取生成顺序后,格式化为固定三位
public class IdUtil 

    static final String  PREFIX = "ID_CASH_";

    public static String getCacheKey(String serialPrefix) 
        return PREFIX.concat(serialPrefix);
    

    public static String getDay(IdEnum idEnum) 
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
        StringBuffer sb = new StringBuffer();
        sb.append(idEnum.getPrefix());
        sb.append(formatter.format(LocalDateTime.now()));
        return sb.toString();
    

    public static String completionSerial(Long serial) 
        Format formatCount = new DecimalFormat("000");
        String serialFormat = formatCount.format(serial);
        return  serialFormat;
    

新建 IdService 获取分布式单号

  • dayWithPrefix :获取 服务标识 + 格式化的日期
  • redisCacheKey:Redis key
  • serial:利用 redis increment 获取原子自增数
@Service
public class IdService 

    @Resource
    private RedisTemplate redisTemplate;

    public String generateNumber(IdEnum idEnum) 
        String dayWithPrefix = IdUtil.getDay(idEnum);
        String redisCacheKey = IdUtil.getCacheKey(dayWithPrefix);
        Long serial = redisTemplate.opsForValue().increment(redisCacheKey);
        redisTemplate.expire(redisCacheKey, 2, TimeUnit.DAYS);

        String num =  dayWithPrefix + IdUtil.completionSerial(serial);
        return num;
    

最后使用

String number = idService.generateNumber(IdEnum.INVOICE);

分布式系统订单号唯一策略

1、分布式集群架构

2、分布式高并发环境的订单号要求

  • 全局唯一
  • 订单号信息安全要求
  • 趋势递增要求

3、订单号生成策略总结

策略 优点 缺点 格式
uuid 实现简单不占用带宽 无序、不可读、查询慢 32位
db自增 无代码、递归 DB单点故障、扩展有瓶颈
snowflake 不占用带宽、低位趋势递增 依赖服务器时间 18位
redis 无单点故障、性能优于DB递增 占用带宽、Redis集群需要维护 12位

3.1、策略一:UUID(通用唯一识别码)

组成:当前日期+时间+时钟序列+机器识别号(MAC地址或其他)
分布式系统中,所有元素(web服务器)都不需要通过中央控制端来判断数据唯一性。

3.2、策略二:数据库自增ID

关系型数据库都实现数据库自增ID;mysql通过auto_increment实现、oralce通过sequence实现。
在数据库集群环境下,不同数据库节点可设置不同起步值、相同步长值来实现集群下生成全局唯一、递增ID。

3.3、策略三:snowflake算法

组成:41位时间戳+10为机器ID+12位序列号(自增),转换位长度为18的长整型。
twitter位满足每秒上万条消息的创建,每条消息都必须分配全局唯一id,这些id要趋势递增,方便客户排序。
Github上可以下载到snowflake源码

技术图片

3.4、策略四:Redis自增ID

Redis实现了incr(key)API用于将key的值递增1并返回结果。若key不存在,

4、四种ID自增的格式示例

  • uudi:205f1537-7991-4ed3-a2c7-c05aa8f8428b
  • 数据库自增:自增ID
  • snowflake:386992679587676176
  • redis:173372100001

以上是关于Redis 生成分布式业务单号的主要内容,如果未能解决你的问题,请参考以下文章

分布式系统订单号唯一策略

Spring boot redis自增编号控制 踩坑

接口幂等性解决方案

使用Redis中的incr实行自增,来实现订单号

redis分布式锁和lua脚本

laravel+redis 生成订单号-当天从1开始自增