分布式系统中通过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


		
			org.springframework.boot
			spring-boot-starter-data-redis
			1.5.13.RELEASE
		
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 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 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;

/**
 * 

myproject

*

redis 自增长id公共类

* * @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); } } /** *

redis-id-utils

*

redis 自增长id公共类

* * @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;

/**
 * 

myproject

*

Redis分布式id

* * @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> local = new ThreadLocal<>();

    public boolean lock(String key, final int retry, long expireSecs, long sleepSecs) {
        log.info("开启Redis分布式锁");
        Map 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;

/**
 * 

myProject

*

常量定义

* * @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);

你可能感兴趣的:(Redis,项目应用,redis,java)