分布式系统中通过redis生成自增长唯一id 号

前言:在分布式环境中数据分库分表后通过数据库自增加是无法保证id的唯 一性,这个时候可以利用Redis 的数自增长原子操作来实现id的唯一。

需要意点:每个id号的起始值初始化,防止分布式环境中一个服务操作导致已初始化的起始值重新被初始化,这样后果会导致id生成重复。

好了,不说这么多了,上代码:

application.properties

#reids config
spring.redis.host=redis.XXXXXX.local.host
spring.redis.password=XXXXXX
spring.redis.port=6379

spring.redis.pool.max-idle=8
spring.redis.pool.min-idle=0
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=-1
spring.redis.timeout=20000

pom.xml

<!-- redis -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
			<version>1.5.13.RELEASE</version>
		</dependency>
RedisConfig
package com.myproject.config;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
import com.myproject.service.RedisIdService;
import com.myproject.util.RedisIdUtils;
import com.myproject.util.SpringContextHolder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

import javax.annotation.Resource;

@Configuration
public class RedisConfig {

	@Resource
	private RedisConnectionFactory redisConnectionFactory;

	/**
	 * redis缓存管理模板
	 *
	 * @return RedisTemplate
	 */
	@Bean("redisTemplateCustomize")
	public RedisTemplate<String, Object> redisTemplate(Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder) {
		ObjectMapper objectMapper = jackson2ObjectMapperBuilder.build();

		TypeResolverBuilder<?> defaultTyping = new ObjectMapper.DefaultTypeResolverBuilder(ObjectMapper.DefaultTyping.NON_FINAL);
		defaultTyping = defaultTyping.init(JsonTypeInfo.Id.CLASS, null);
		defaultTyping = defaultTyping.inclusion(JsonTypeInfo.As.PROPERTY);
		objectMapper.setDefaultTyping(defaultTyping);

		StringRedisSerializer keySerializer = new StringRedisSerializer();
		GenericJackson2JsonRedisSerializer valueSerializer = new GenericJackson2JsonRedisSerializer(objectMapper);

		RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
		redisTemplate.setConnectionFactory(redisConnectionFactory);
		redisTemplate.setEnableDefaultSerializer(false);
		redisTemplate.setKeySerializer(keySerializer);
		redisTemplate.setValueSerializer(valueSerializer);
		redisTemplate.setHashKeySerializer(keySerializer);
		redisTemplate.setHashValueSerializer(valueSerializer);
		return redisTemplate;
	}

	@Bean
	public SpringContextHolder springContextHolder(ApplicationContext applicationContext){
		SpringContextHolder contextHolder=  new SpringContextHolder();
		contextHolder.setApplicationContext(applicationContext);

		RedisIdService redisIdService = (RedisIdService) SpringContextHolder.getBean("redisIdService");
		RedisIdUtils.init(redisIdService);
		return contextHolder;
	}
}
RedisIdUtils
package com.myproject.util;

import com.myproject.constant.CommonConstant;
 
import com.myproject.service.RedisIdService;
import lombok.extern.slf4j.Slf4j;

/**
 * <h3>myproject</h3>
 * <p>redis 自增长id公共类</p>
 *
 * @Author :  krics
 * @Date : 2021-12-15 10:52
 **/
@Slf4j
public class RedisIdUtils {

    private  static RedisIdService redisIdService  ;
    private   RedisIdUtils(){
        throw new IllegalStateException("Utility class");
    }

    public static void init(RedisIdService redisIdService){
        RedisIdUtils.redisIdService = redisIdService;
    }

    /**
     * 获取下一个sid
     * @param redisIdKey redis 生成业务id KEY
     * @return 新的序列号
     */
    public static long nextId(CommonConstant.RedisIdKeyEnum redisIdKey){
        if(null == redisIdKey){
            log.warn("Redis KEY 不存在");
            return CommonConstant.DEFAULT_NUMBER_ZERO;
        }
       return redisIdService.nextId(redisIdKey);
    }

}

/**
 * <h3>redis-id-utils</h3>
 * <p>redis 自增长id公共类</p>
 *
 * @Author :  krics
 * @Date : 2021-12-15 10:52
 **/
@Slf4j
public class RedisIdUtils {

    private  static RedisIdService redisIdService  ;
    private   RedisIdUtils(){
        throw new IllegalStateException("Utility class");
    }

    public static void init(RedisIdService redisIdService){
        RedisIdUtils.redisIdService = redisIdService;
    }

    /**
     * 获取下一个sid
     * @param redisIdKey redis 生成业务id KEY
     * @return 新的序列号
     */
    public static long nextId(CommonConstant.RedisIdKeyEnum redisIdKey){
        if(null == redisIdKey){
            log.warn("Redis KEY 不存在");
            return SHConstant.DEFALUT_NUMBER_ZERO;
        }
       return redisIdService.nextId(redisIdKey);
    }

}

RedisIdService
package com.myproject.service;

import com.myprojectg.constant.CommonConstant;
import com.myproject.util.RedisLock;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Date;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * <h3>myproject</h3>
 * <p>Redis分布式id</p>
 *
 * @Author : krics
 * @Date : 2021-12-13 17:22
 **/
@Slf4j
@Component
public class RedisIdService {

    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private  RedisLock redisLock;



    /**
     * 服务启动时初始化对应业务ID值。
     */
    @PostConstruct
    private  void init(){
        Arrays.asList(CommonConstant.RedisIdKeyEnum.values()).stream().parallel().forEach(redisIdKey -> {
            //初始化Redis 相关业务自增加id初始值
            executeInitValue(redisIdKey);
        });
    }
    //如果对应业务未执行初始化,则加锁初始化
    private void executeInitValue(CommonConstant.RedisIdKeyEnum redisIdKey){
        //业务主键Redis KEY
        String key = redisIdKey.getKey();
        //业务主键是否初始化标记KEY
        String initStatusKey = getInitStatusKey(key);
        if(!redisTemplate.hasKey(initStatusKey)){
            log.info("Redis KEY:{} 准许备初始化",redisIdKey.getKey());
            //首次初始化时分布式锁KEY
            String lockKey = getInitKey(key);
            boolean lockSuccess = false;
            try{
                 //分布式锁
                lockSuccess =  redisLock.lock(lockKey,2,10,2);
                //设置初始条件是获取锁且未被初始化
                if(lockSuccess && !redisTemplate.hasKey(initStatusKey)){
                    String  initValueKey = getInitValueKey(key);
                    //枚举类默认起始值
                    long initValue = redisIdKey.getInitialValue();
                    if(redisTemplate.hasKey(initValueKey)){
                        //redis 指定了默认初始化值,则优先使用redis中(方便日后生成id区间段刷新调整)
                        initValue = Long.valueOf(Optional.ofNullable((String)redisTemplate.opsForValue().get(initValueKey)).orElse("0"));
                    }
                    log.info("准许备初始化 Redis KEY:{} ,起始值:{}",redisIdKey.getKey(),initValue);
                    set(key,initValue,getExpireTime());
                    //设置对应业务初始化标志为已初始化
                    redisTemplate.opsForSet().add(initStatusKey,Boolean.TRUE);
                }
            }finally {
                if(lockSuccess){
                    redisLock.unlock();
                }
            }
        }
    }
    /**
     * @param key
     * @param initValue
     * @param expireTime
     * @Title: set
     * @Description: set cache.
     */
    public void set(String key, long initValue, Date expireTime) {
        //初始化基础id
        RedisAtomicLong counter =   new RedisAtomicLong(key, redisTemplate.getConnectionFactory(),initValue);
        //设置过期时间
        counter.expireAt(expireTime);
    }

    /**
     * redisIdKey redis 生成id key
     * @param redisIdKey
     * @return
     */
    public long nextId(CommonConstant.RedisIdKeyEnum redisIdKey) {
        RedisAtomicLong counter  =   new RedisAtomicLong(redisIdKey.getKey(), redisTemplate.getConnectionFactory());;
        long nextId = counter.incrementAndGet();
        log.info("next id:{}",nextId);
        return nextId;
    }
    /**
     * 获取指定KEY 当前最大的ID
     * @param key
     * @return
     */
    public long getCureentId(String key) {
        RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
        return counter.get();
    }
    public String getInitValueKey(String key){
        return StringUtils.join("InitValue:",key);
    }
    private String getInitKey(String key){
        return StringUtils.join("Init:",key);
   }
    private String getInitStatusKey(String key){
        return StringUtils.join("Init:Status:",key);
    }

    /**
     * 获取redis过期时间
     * @return
     */
    private   Date getExpireTime() {
        Date maxExpireTime = null;
        try {
              maxExpireTime =   DateUtils.parseDate(CommonConstant.MAX_EXPIRED_TIME,CommonConstant.PATTERN_DATE_YYYY_MM_DD);
        } catch (ParseException e) {
            log.error("getExpireTime exception:",e);
        }

        return maxExpireTime;
    }
    /**
     * 删除指定KEY ID的缓存值。
     * @param key
     */
    public void deleteAtomicLong(String key) {
        RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
        entityIdCounter.expire(0, TimeUnit.MILLISECONDS);
        //删除已初始化标志
        redisTemplate.delete(getInitStatusKey(key));

    }

}
RedisLock
package com.project.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisConnectionUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@Slf4j
@Service
public class RedisLock {

    @Resource(name = "stringRedisTemplate")
    private StringRedisTemplate redisTemplate;

    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";
    private static final Long RELEASE_SUCCESS = 1L;

    private static final String THREAD_KEY_VALUE = "value";
    private static final String THREAD_KEY_TIMES = "times";
    private static final String THREAD_KEY_KEY = "key";
    private static final String THREAD_KEY_RETRY = "retry";
    private static final String THREAD_KEY_EXPIRE_SECS = "expireSecs";
    private static final String THREAD_KEY_SLEEP_SECS = "sleepSecs";

    private ThreadLocal<Map<String, Object>> local = new ThreadLocal<>();

    public boolean lock(String key, final int retry, long expireSecs, long sleepSecs) {
        log.info("开启Redis分布式锁");
        Map<String, Object> params = new HashMap<>();
        params.put(THREAD_KEY_KEY, key);
        params.put(THREAD_KEY_RETRY, retry);
        params.put(THREAD_KEY_EXPIRE_SECS, expireSecs);
        params.put(THREAD_KEY_SLEEP_SECS, sleepSecs);
        params.put(THREAD_KEY_TIMES, 0);
        local.set(params);
        return this.lock();
    }

    public void unlock() {
        RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
        RedisConnection conn = factory.getConnection();
        try {
            if (null != local.get()) {
                String key = String.valueOf(local.get().get(THREAD_KEY_KEY));
                String value = String.valueOf(local.get().get(THREAD_KEY_VALUE));
                if (StringUtils.isNotBlank(key) && redisTemplate.hasKey(key)) {
                    Jedis jedis = (Jedis) conn.getNativeConnection();
                    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                    Object result = jedis.eval(script, Collections.singletonList(key), Collections.singletonList(value));
                    if (RELEASE_SUCCESS.equals(result)) {
                        log.info("Redis分布式锁,释放锁成功!");
                    } else {
                        log.info("Redis分布式锁,释放锁失败!");
                    }
                }
            }
        } finally {
            RedisConnectionUtils.releaseConnection(conn, factory);
        }
    }

    private boolean lock() {
        long sleepSecs = Long.parseLong(String.valueOf(local.get().get(THREAD_KEY_SLEEP_SECS)));
        int retry = Integer.parseInt(String.valueOf(local.get().get(THREAD_KEY_RETRY)));
        log.info("加锁尝试次数:" + retry + ",每次间隔时间(s):" + sleepSecs);
        int times = Integer.parseInt(local.get().get(THREAD_KEY_TIMES).toString());
        if (!tryLock()) {
            try {
                Thread.sleep(sleepSecs * 1000);
            } catch (InterruptedException e) {
                log.error("Redis分布式锁休眠异常", e);
                Thread.currentThread().interrupt();
            }
            times++;
            local.get().put(THREAD_KEY_TIMES, times);
            if (retry == times) {
                log.info("Redis分布式锁,加锁失败!");
                return false;
            }
            return lock();
        }
        return true;
    }

    private boolean tryLock() {
        RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
        RedisConnection conn = factory.getConnection();
        try {
            Jedis jedis = (Jedis) conn.getNativeConnection();
            String key = String.valueOf(local.get().get(THREAD_KEY_KEY));
            String value = UUID.randomUUID().toString();
            local.get().put(THREAD_KEY_VALUE, value);
            long expireSecs = Long.parseLong(String.valueOf(local.get().get(THREAD_KEY_EXPIRE_SECS)));
            String ret = jedis.set(key, value, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireSecs * 1000);

            if (LOCK_SUCCESS.equals(ret)) {
                log.info("Redis分布式锁,加锁成功!");
                log.info("key:" + key + ",value:" + value + ",expire(s):" + expireSecs);
                return true;
            }
            return false;
        } finally {
            RedisConnectionUtils.releaseConnection(conn, factory);
        }

    }
}
CommonConstant
package com.pateo.qingcloud.tsp.remoteconfig.constant;

import lombok.Getter;

import java.util.Arrays;

/**
 * <h3>myProject</h3>
 * <p>常量定义</p>
 *
 * @Author :  krics
 * @Date : 2021-12-20 17:43
 **/
public interface CommonConstant {
       long  DEFAULT_NUMBER_ZERO = 0;
    //缓存过期时间
       String MAX_EXPIRED_TIME = "2099-12-30 23:59:59";
       String PATTERN_DATE_YYYY_MM_DD = "yyyy-MM-dd HH:mm:ss";
    @Getter
       enum RedisIdKeyEnum{
        SYS_LOG_ID_KEY("Redis:ID:SysLog",1000000000000000L,"系统日志起始id"),
        SYS_LOGIN_LOGIN_ID_KEY("Redis:ID:SysLoginLog",200000000000000L,"系统登录日志起始id");
        private String key;
        private  long initialValue;
        private String desc;

        RedisIdKeyEnum(String code,long initialValue, String desc){
            this.key = code;
            this.initialValue = initialValue;
            this.desc = desc;
        }
    }
      static  RedisIdKeyEnum getRedisIdKeyByKey(String key){
        return Arrays.asList(RedisIdKeyEnum.values()).stream()
                .filter(redisIdKey -> redisIdKey.getKey().equals(key))
                .findFirst().orElse(null);
    }
}

使用:

long nextId = RedisIdUtils.nextId(CommonConstant.RedisIdKeyEnum.SYS_LOG_ID_KEY);

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值