通过Redis的自增方式,生成并发下的唯一申请单号、订单号

前言

Redis集群至少是3主3从节点,所以需要搭建Redis的6个节点。

通过Redis自增生成申请单号,解决并发问题生成申请单号相同问题,格式:年月日加上流水位数生成并发下的唯一申请单号、订单号。

1、Redis 集群配置

#Idgenerator 缓存数据库配置  Redis集群至少是3主3从节点,所以需要搭建Redis的6个节点
idgenerator.cluster.nodes = localhost:6380,localhost:6381,localhost:6382,localhost:6383,localhost:6384,localhost:6385
idgenerator.cluster.password = 123456
idgenerator.preFixKey = DEMO:IDGENERATOR
idgenerator.maxRedirects = 3
idgenerator.soTimeout = 1000
idgenerator.timeout = 2000
idgenerator.masterName = mymaster
#最小空闲数
idgenerator.minIdle = 4
#最大空闲数
idgenerator.maxIdle = 16
#最大连接数
idgenerator.maxTotal = 256
#最大等待时间 单位毫秒(ms)
idgenerator.maxWaitMillis = 60000
#删除间隔
idgenerator.timeBetweenEvictionRunsMillis = -1
#使用连接时测试连接是否可用
idgenerator.testOnBorrow = true
idgenerator.testOnReturn = true
idgenerator.testOnCreate = true
#redis模式 single cluster sentinel
idgenerator.server = cluster

2、Redis 集群配置类

package com.demo.config;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.JedisPoolConfig;

/**
 * Redis idgenerator 集群配置
 *
 * @author star
 * @date 2023/04/20 10:54
 **/
@Slf4j
@Configuration
@Data
public class LisIdgeneratorRedisClusterConfiguration implements Serializable {
    @Value("${idgenerator.masterName:mymaster}")
    private String masterName;
    @Value("${idgenerator.cluster.password:\"\"}")
    private String password;
    @Value("${idgenerator.maxTotal:256}")
    private int maxTotal;
    @Value("${idgenerator.maxIdle:16}")
    private int maxIdle;
    @Value("${idgenerator.minIdle:4}")
    private int minIdle;
    @Value("${idgenerator.maxWaitMillis:10000}")
    private int maxWaitMillis;
    @Value("${idgenerator.cluster.nodes}")
    private String redisClusterNotes;

    @Value("${idgenerator.maxRedirects:3}")
    private int maxRedirects;
    @Value("${idgenerator.soTimeout:1000}")
    private int soTimeout;
    @Value("${idgenerator.timeout:2000}")
    private int timeout;

    @Value("${idgenerator.testOnBorrow:true}")
    private boolean testOnBorrow;
    @Value("${idgenerator.testOnReturn:true}")
    private boolean testOnReturn;
    @Value("${idgenerator.testOnCreate:true}")
    private boolean testOnCreate;

    @Value("${idgenerator.preFixKey:LIS:IDGENERATOR}")
    private String preFixKey;

    @Value("${idgenerator.server:cluster}")
    private String server;

    @Bean(name = "idgeneratorJedisPoolConfig")
    public JedisPoolConfig getJedisPoolConfig(){
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(maxTotal);
        jedisPoolConfig.setMaxIdle(maxIdle);
        jedisPoolConfig.setMinIdle(minIdle);
        jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
        jedisPoolConfig.setTestOnBorrow(testOnBorrow);
        jedisPoolConfig.setTestOnReturn(testOnReturn);
        jedisPoolConfig.setTestOnCreate(testOnCreate);
        return jedisPoolConfig;
    }

    @Bean(name = "idgeneratorRedisClusterConfiguration")
    public RedisClusterConfiguration getRedisClusterConfiguration(){
        if(!"cluster".equals(server)){
            return null;
        }
        if (StringUtils.isEmpty(redisClusterNotes)){
            log.error("redis集群节点为空");
            throw new RuntimeException();
        }

        String[] hostAndPorts = redisClusterNotes.split(",");
        Set nodes = new HashSet<>();
        for (String hostAndPort : hostAndPorts){
            String[] ipAndPort = hostAndPort.split(":");
            nodes.add(new RedisNode(ipAndPort[0],Integer.parseInt(ipAndPort[1])));
        }

        RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration();

        redisClusterConfiguration.setClusterNodes(nodes);
        redisClusterConfiguration.setMaxRedirects(maxRedirects);
        return redisClusterConfiguration;
    }
    @Bean(name = "idgeneratorRedisSentinelConfiguration")
    public RedisSentinelConfiguration getRedisSentinelConfiguration(){
        if(!"sentinel".equals(server)){
            return null;
        }
        if (StringUtils.isEmpty(redisClusterNotes)){
            log.error("redis哨兵节点为空");
            throw new RuntimeException();
        }
        RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration();
        String[] hostAndPorts = redisClusterNotes.split(",");
        for (String hostAndPort : hostAndPorts){
            String[] ipAndPort = hostAndPort.split(":");
            redisSentinelConfiguration.addSentinel(new RedisNode(ipAndPort[0],Integer.parseInt(ipAndPort[1])));
        }
        redisSentinelConfiguration.setMaster(masterName);
        return redisSentinelConfiguration;
    }

    @Bean(name = "idgeneratorJedisConnectionFactory")
    public JedisConnectionFactory jedisConnectionFactory(@Qualifier("idgeneratorJedisPoolConfig") JedisPoolConfig jedisPoolConfig) {
        log.info("redis--idgenerator-{}模式",server);
        if("single".equals(server)){

            JedisConnectionFactory  factory = new JedisConnectionFactory(jedisPoolConfig);
            String[] hostAndPorts = redisClusterNotes.split(",");
            Set nodes = new HashSet<>();
            for (String hostAndPort : hostAndPorts){
                String[] ipAndPort = hostAndPort.split(":");
                nodes.add(new RedisNode(ipAndPort[0],Integer.parseInt(ipAndPort[1])));
            }
            factory.setHostName(nodes.iterator().next().getHost());
            factory.setPort(nodes.iterator().next().getPort());
            if(StringUtils.isNotBlank(password)){
                factory.setPassword(password);
            }
            return factory;
        }else if("sentinel".equals(server)){
            RedisSentinelConfiguration redisSentinelConfiguration = getRedisSentinelConfiguration();
            JedisConnectionFactory  factory = new JedisConnectionFactory(redisSentinelConfiguration,jedisPoolConfig);
            if(StringUtils.isNotBlank(password)){
                factory.setPassword(password);
            }
            return factory;
        }else if("cluster".equals(server)){
            RedisClusterConfiguration redisClusterConfiguration1 = getRedisClusterConfiguration();
            JedisConnectionFactory  factory = new JedisConnectionFactory(redisClusterConfiguration1,jedisPoolConfig);
            if(StringUtils.isNotBlank(password)){
                factory.setPassword(password);
            }
            return factory;
        }else {
            log.error("redis配置错误");
            throw new RuntimeException();
        }
    }

    @Bean(name = "idgeneratorRedisTemplate")
    public RedisTemplate redisTemplate(@Qualifier("idgeneratorJedisConnectionFactory") JedisConnectionFactory jedisConnectionFactory){
        StringRedisTemplate redisTemplate = new StringRedisTemplate();
        redisTemplate.setConnectionFactory(jedisConnectionFactory);
        return redisTemplate;
    }
    public String getPreFixKey(String key){
        return String.format("%s:%s",preFixKey,key);
    }
}

3、Redis 缓存工具类

package com.demo.idgenerator.redis;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.annotation.Resource;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import com.demo.config.LisIdgeneratorRedisClusterConfiguration;

import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.exceptions.JedisConnectionException;
import redis.clients.jedis.exceptions.JedisDataException;

/**
 * Cache Redis 缓存工具类
 *
 * @author star
 * @date 2023/04/20 14:11
 **/
@Slf4j
@Component
public class IdgeneratorRedisTool {
    @Resource
    LisIdgeneratorRedisClusterConfiguration idgeneratorConfig;

    @Resource(name = "idgeneratorRedisTemplate")
    private RedisTemplate redisTemplate;

    /**
     * 获取自增的ID
     */
    public synchronized Long next(String key){
        try {
            long result = redisTemplate.opsForValue().increment(idgeneratorConfig.getPreFixKey(key),1);
            return result;
        } catch (JedisConnectionException e) {
            log.error("JedisConnectionException",e);
        } catch (JedisDataException e) {
            log.error("JedisDataException",e);
        }
        return null;
    }

    /**
     * 获取一个范围内的ID
     * @param key 业务编码
     * @param size 范围长度
     * @return
     */
    public synchronized List nextList(String key,int size){
        List currentList = new ArrayList();
        try {
            long result = redisTemplate.opsForValue().increment(idgeneratorConfig.getPreFixKey(key), size);
            long current = result - Long.valueOf(size);
            for(int i=0;i < size;i++){
                ++current;
                currentList.add(current);
            }
            return currentList;
        } catch (JedisConnectionException e) {
            log.error("JedisConnectionException",e);
        } catch (JedisDataException e) {
            log.error("JedisDataException",e);
        }
        return null;
    }

    /**
     * 流水位数
     */
    private static final int ONE = 1;
    private static final int TWO = 2;
    private static final int THERE = 3;
    private static final int FOUR = 4;
    private static final int FIVE = 5;

    /**
     * 通过Redis自增生成申请单号,解决并发问题生成申请单号相同问题,格式:年月日加上流水位数
     * @param key 业务编号
     * @param suffixNum 流水位数
     * @return
     */
    public synchronized Long nextRequestNo(String key , int suffixNum){
        try {
            // 生成8位的日期作为申请单号的前部分
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
            String sDate = dateFormat.format(new Date());
            Long requestNo = 0L;
            // 生成8位的日期作为key区分每天从1开始生成
            long id = redisTemplate.opsForValue().increment(idgeneratorConfig.getPreFixKey(key+":"+sDate),1);
            if (ONE == suffixNum){
                requestNo = Long.parseLong(sDate+String.format("%01d", id));
            } if (TWO == suffixNum){
                // %02d  02表示补全为两位
                requestNo = Long.parseLong(sDate+String.format("%02d", id));
            } if (THERE == suffixNum){
                requestNo = Long.parseLong(sDate+String.format("%03d", id));
            } if (FOUR == suffixNum){
                requestNo = Long.parseLong(sDate+String.format("%04d", id));
            } if (FIVE == suffixNum){
                requestNo = Long.parseLong(sDate+String.format("%05d", id));
            }
            return requestNo;
        } catch (JedisConnectionException e) {
            log.error("JedisConnectionException",e);
        } catch (JedisDataException e) {
            log.error("JedisDataException",e);
        }
        return null;
    }
}

4、生成申请单号(申请单号、订单号)

@Resource
private IdgeneratorRedisTool idgeneratorRedisTool;

/**
  * 通过Redis自增生成申请单号,解决并发问题生成申请单号相同问题,格式:年月日加上流水位数
  * @param key 业务编号
  * @param suffixNum 流水位数
  * @return
  */
public String getMaxRequestNo(DemoDto dto) {
    // 当天最大申请单号
    Long maxRequestNo = 0L;

    // 从redis中获取申请单号
    maxRequestNo = idgeneratorRedisTool.nextRequestNo(Contrast.DEMO_SQID + ":"+dto.getJgid(), 2).longValue();
    return maxRequestNo ;
}

5、输出结果

输出今天申请单号:2023081801,生成2位申请单号的编号,从01开始。

输出明天申请单号:2023081901,生成2位申请单号的编号,从01开始。

6、页面展示

通过Redis的自增方式,生成并发下的唯一申请单号、订单号_第1张图片

你可能感兴趣的:(Java基础,redis,数据库,缓存)