基于redis的规则流水号生成(二)

上文主要包含的最基础的redis自增生成规则流水号。

但仔细分析会发现有几个问题:

1、流水号规则调整了怎么办?只能去改代码?

2、redis切换了怎么办?数据不迁移或者redis挂了从头生成流水号会导致重复怎么办?

3、redis异常怎么办?

 

设计方案:

1、建立流水号规则配置表:

CREATE TABLE `crl_serial_number_rule` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
  `rule_code` varchar(30)  NOT NULL COMMENT '规则编码',
  `rule_desc` varchar(60)  NOT NULL COMMENT '规则描述',
  `prefix` varchar(10)  NULL COMMENT '前缀',
  `date_formatter` varchar(20) NULL COMMENT '日期格式',
  `random_mark` varchar(10)  NOT NULL COMMENT '随机位',
  `number_type` varchar(10)  NOT NULL COMMENT '序号类型,numberTypeEnum:RANDOM 随机;ORDER 顺序',
  `number_length` varchar(2)  NOT NULL COMMENT '序号位数',  
  `number_start` varchar(10)  DEFAULT NULL COMMENT '序号起始位',
  `number_end` varchar(10)  DEFAULT NULL COMMENT '序号结束位',
  `current_number` varchar(10)  DEFAULT NULL COMMENT '当前序号',
  `created_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `modified_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  PRIMARY KEY (`id`),
  UNIQUE INDEX `uniq_idx_rule_code` (`rule_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='流水号规则表';

2、针对redis切换,在1中其实已经设计好数据库表,其中的随机位即可降低切换时的重复概率。

3、针对redis异常可以做出补充方案,确保可以生成一个流水号。

附代码(部分引入涉及敏感工程已去除):

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.TimeUnit;


/**
 * @description:
 * @author: xxx
 * @date: 2018-06-13 下午3:17
 */
@Service
public class CrlSerialNumberServiceImpl implements CrlSerialNumberService {
    private static final Logger logger = LoggerFactory.getLogger(CrlSerialNumberServiceImpl.class);
    /*每次增长数值*/
    private static int increase=100;
    /*超时时间:代码中与TimeUnit.SECONDS共用*/
    private static int expireTime=10;
    /*重试次数*/
    private static int retryCount=5;
    @Resource
    private CrlSerialNumberRuleService crlSerialNumberRuleService;

    @Resource
    private DistributedLocker distributedLocker;

    @Resource
    private CacheBizService cacheClient;

    @Override
    public String generateSerialNumberByRuleCode(String ruleCode){
        String methodDesc = "按规则编码生成流水号";
        StringBuilder crlSerialNumber = new StringBuilder();
        logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|查询流水号规则开始" + "|ruleCode:" + ruleCode);
        CrlSerialNumberRule crlSerialNumberRule = crlSerialNumberRuleService.getSerialNumberRuleByRuleCode(ruleCode);
        if (null == crlSerialNumberRule) {
            logger.warn("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|查询流水号规则为空,请检查流水号规则配置" + "|ruleCode:" + ruleCode);
            return crlSerialNumber.append("ERR").append(new SimpleDateFormat(DateFormatterEnum.yyMMddHHmmss.getCode()).format(new Date())).append(String.valueOf(new Random().nextInt(9))).append(new Random().nextInt(getMaxValueString("8"))).toString();
        }
        logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|查询流水号规则结束" + "|ruleCode:" + ruleCode + "|crlSerialNumberRule:" + crlSerialNumberRule);

        String dateFormatter = crlSerialNumberRule.getDateFormatter();
        String dateString="";
        if (StringUtils.isNotBlank(dateFormatter)) {
            dateString = new SimpleDateFormat(dateFormatter).format(new Date());
        }

        if (NumberTypeEnum.RANDOM.getCode().equals(crlSerialNumberRule.getNumberType())) {
            String randomMark="";
            if(StatusEnum.VALID.getCode().equals(crlSerialNumberRule.getRandomMark())){
                randomMark=String.valueOf(new Random().nextInt(9));
            }
            int random=new Random().nextInt(getMaxValueString(crlSerialNumberRule.getNumberLength()));
            return crlSerialNumber.append(crlSerialNumberRule.getPrefix()).append(dateString).append(randomMark).append(getCurrentValueString(crlSerialNumberRule.getNumberLength(),String.valueOf(random))).toString();
        } else if (NumberTypeEnum.ORDER.getCode().equals(crlSerialNumberRule.getNumberType())) {

            try {
                logger.info("CrlSerialNumberRuleServiceImpl|getSerialNumberRuleByRuleCode|" + methodDesc + "|加分布式锁开始" + "|ruleCode:" + ruleCode);
                CrlCommonResponse<String> crlCommonResponse = distributedLocker.lockRetry(ruleCode, expireTime,TimeUnit.SECON
<think>首先,用户的问题是比较Redis和Snowflake在分布式ID生成中的优缺点。我需要基于系统级指令和用户可见层的规则来构建回答。 关键点: - 所有行内数学表达式必须用$...$格式。 - 独立公式用$$...$$格式并单独成段。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识。 - 回答结构清晰,帮助用户逐步解决问题。 - 保证回答真实可靠。 - 参考提供的站内引用,但用户的问题是基于这些引用,所以我要整合这些信息。 提供的引用: - [^1]: Snowflake的特点:生成ID按时间有序,64位整数,不会碰撞,效率高。 - [^2]: Redis适合生成每天从0开始的流水号,如订单号=日期+当日自增长号,使用INCR。 - [^3]: 分布式ID生成策略,Redis基于单线程使用INCR生成ID。 - [^4]: 分布式唯一ID生成策略,包括Snowflake和Redis,提到其他如百度、美团、滴滴的实现。 用户的问题:比较Redis和Snowflake在分布式ID生成中的优缺点。 结构回答: 1. **介绍**:简要说明Redis和Snowflake在分布式ID生成中的角色。 2. **Snowflake算法**:解释其原理、优点和缺点。 3. **Redis方法**:解释其原理、优点和缺点。 4. **对比**:直接比较两者的优缺点。 5. **总结**:给出适用场景建议。 6. **生成相关问题**:在最后添加相关问题。 确保使用中文。 在回答中,自然地添加引用标识。例如,如果提到引用中的内容,就在末尾加[^n]。 生成相关问题:基于内容,生成3-5个相关问题。 现在,规划内容: **Snowflake算法**: - 原理:基于时间戳、工作机器ID、序列号生成64位ID。 - 优点:有序、高效、无碰撞、不依赖外部服务。 - 缺点:依赖系统时钟(时钟回拨问题)、需要配置机器ID。 - 引用[^1]:提到Snowflake的特点。 **Redis方法**: - 原理:使用Redis的INCR或INCRBY命令生成递增ID。 - 优点:简单、原子操作保证唯一性、适合日期相关ID。 - 缺点:依赖Redis服务、单点故障风险、性能瓶颈。 - 引用[^2]和[^3]:提到Redis生成ID。 **对比**: - 性能:Snowflake本地生成,更快;Redis需要网络请求。 - 可靠性:Snowflake无外部依赖;Redis依赖Redis服务。 - 有序性:Snowflake时间有序;Redis可以有序。 - 适用场景:Snowflake适合高吞吐系统;Redis适合简单流水号。 - 引用[^4]:提到其他实现,但可以用于上下文。 最后,生成相关问题: 1. 如何解决Snowflake算法中的时钟回拨问题? 2. Redis分布式ID生成如何避免单点故障? 3. 在什么场景下选择Snowflake优于Redis? 确保回答清晰、逐步解释。</think>### Redis与Snowflake在分布式ID生成中的优缺点对比 在分布式系统中,生成唯一ID是关键需求。Redis和Snowflake是两种常见方案:Redis基于内存数据库的原子操作生成ID,而Snowflake是一种算法,本地生成时间有序ID。下面我将逐步分析它们的原理、优缺点,并进行直接对比。内容基于可靠技术文档和实际应用经验[^1][^2][^3][^4]。 #### 1. **Snowflake算法概述** Snowflake由Twitter设计,生成64位整数ID,结构包括时间戳、工作机器ID和序列号。例如,ID格式为: $$ \text{ID} = \text{时间戳} \times 2^{22} + \text{工作机器ID} \times 2^{12} + \text{序列号} $$ - **优点**: - **高效有序**:ID按时间递增,支持范围查询(如数据库索引优化),本地生成无网络延迟,吞吐量高(每秒可生成百万级ID)[^1]。 - **无碰撞**:工作机器ID(如datacenter和workerID)确保分布式系统内唯一性[^1]。 - **无外部依赖**:算法在应用层实现,不依赖数据库或缓存服务,系统更健壮[^4]。 - **缺点**: - **时钟回拨问题**:依赖系统时钟,如果时钟回退(如NTP同步错误),可能导致ID重复或不一致[^4]。 - **配置复杂**:需手动分配工作机器ID,集群扩展时可能引发冲突(如IP或配置文件管理)[^1]。 - **位长度固定**:64位ID可能在某些场景下长度不足(如需要更大范围时需定制扩展)[^1]。 #### 2. **Redis方法概述** Redis使用单线程模型,通过原子命令(如`INCR`或`INCRBY`)生成递增ID。例如,生成每日订单号: ```python # 伪代码示例:Redis生成ID key = "order_id:" + current_date id = redis.incr(key) # 返回当日自增序列 ``` - **优点**: - **简单易用**:API简单(如`INCR`),适合生成日期相关流水号(如订单号=日期+自增数),无需额外算法[^2]。 - **原子性保证**:Redis单线程确保操作原子性,避免并发冲突,ID绝对唯一[^3]。 - **灵活性**:支持自定义前缀(如日期),便于业务分片查询[^2]。 - **缺点**: - **性能瓶颈**:依赖网络请求,高并发时Redis可能成为单点瓶颈(吞吐量受限于Redis性能,通常每秒万级)[^3]。 - **可靠性风险**:Redis服务故障会导致ID生成中断(需高可用部署如哨兵模式,但增加复杂度)[^3]。 - **存储依赖**:ID状态存储在Redis中,持久化失败可能丢失序列(需备份策略)[^2]。 #### 3. **直接优缺点对比** 下表总结关键差异(基于实际应用场景): | 维度 | Snowflake算法 | Redis方法 | 对比说明 | |--------------|-----------------------------------------------|-----------------------------------------------|--------------------------------------------------------------------------| | **性能** | 高:本地生成,无I/O延迟,吞吐量高(>1M/s)[^1] | 中:网络依赖,吞吐量较低(~10K/s),易瓶颈[^3] | Snowflake更适合高并发系统,如电商秒杀;Redis适合中小规模应用[^4]。 | | **可靠性** | 高:无外部服务依赖,时钟问题可缓解(如NTP优化) | 中:依赖Redis可用性,单点故障风险高[^3] | Snowflake更健壮;Redis需额外HA方案(如集群)[^4]。 | | **有序性** | 强:时间戳保证ID全局有序[^1] | 可控:需手动设计(如日期前缀),否则无序[^2] | Snowflake利于范围查询;Redis需业务层处理有序性[^2]。 | | **复杂度** | 中:需实现算法,处理时钟回拨[^4] | 低:开箱即用,集成简单[^3] | Redis上手快;Snowflake开发成本更高但可控[^1]。 | | **适用场景** | 全局唯一ID、高吞吐系统(如日志、监控)[^1] | 日期流水号、低频业务(如订单生成)[^2] | Snowflake用于分布式核心服务;Redis用于简单、带时间标识的ID[^4]。 | #### 4. **总结与建议** - **选择Snowflake时**:优先考虑性能和独立性,例如金融交易或物联网设备ID生成,但需解决时钟问题(如美团Leaf的优化版本)[^4]。 - **选择Redis时**:适合简单、可读性强的ID,如每日订单,但必须部署Redis高可用集群[^3]。 - **混合方案**:在复杂系统中,可结合两者(如用Snowflake生成主ID,Redis处理业务流水号),平衡效率与灵活性[^4]。 总之,Snowflake在性能和可靠性上占优,但实现复杂;Redis简单灵活,但受限于外部服务。实际选型需根据系统规模、并发需求和运维能力综合评估[^1][^2][^3][^4]。 ### 相关问题 1. Snowflake算法如何处理时钟回拨问题? 2. Redis分布式ID生成如何实现高可用以避免单点故障? 3. 在微服务架构中,如何设计一个混合分布式ID生成方案? 4. Snowflake和UUID在分布式ID生成中有哪些核心区别? [^1]: SnowFlake生成ID能够按照时间有序生成,算法生成ID的结果是一个64Bit大小的整数,为一个Long型,分布式系统内不会产生ID碰撞,并且效率较高。 [^2]: 比较适合使用Redis生成每天从0开始的流水号。比如订单号=日期+当日自增长号。可以每天在Redis生成一个Key,使用INCR进行累加。 [^3]: 当使用数据库来生成ID性能不够要求的时候,可以尝试使用Redis生成ID。这主要依赖于Redis是单线程的,所以也可以用生成全局唯一的ID。可以用Redis的原子操作INCR和INCRBY来实现。 [^4]: 分布式唯一id生成策略。百度(uid-generator)基于snowflake算法实现。美团(Leaf)支持号段模式和snowflake算法模式,可自由切换。滴滴(Tinyid)基于号段模式实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值