前言:在分布式环境中数据分库分表后通过数据库自增加是无法保证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
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);