这段时间因为工作的关系,研究了一下流水号这一块业务处理,很多时候主键使用相应的流水号,还挺不错的,流水号同样也适用于分布式系统,这里直接上代码,边上代码,边写注释把
因为我负责的系统是很久以前的了,用的还是Springmvc+JPA的架构,所以这里引用的maven jar包会比较旧,用cloud的同学可以使用 新的jar包
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
接下来是spring配置,放在applicationContext.xml下面
<!-- Redis 配置 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="${redis.maxActive}"/>
<property name="maxIdle" value="${redis.maxIdle}"/>
<property name="maxWaitMillis" value="${redis.maxWaitMillis}"/>
<property name="testOnBorrow" value="true"/>
</bean>
<!-- redis单节点数据库连接配置 -->
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="${redis.host}"/>
<property name="port" value="${redis.port}"/>
<property name="password" value="${redis.password}"/>
<property name="poolConfig" ref="jedisPoolConfig"/>
</bean>
<!-- redisTemplate配置 -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory"/>
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"></bean>
</property>
<property name="valueSerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"></bean>
</property>
<property name="hashKeySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="hashValueSerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
</bean>
注意一点: keySerializer 和valueSerializer 以及下面那两个,这里配置是解决乱码的问题,redis客户端连上去能看到正常的数据
redis配置好了,我们来说一下要达到的流水号要求 ---- XJ202011051000,XJ202011051001,XJ202011051002,XJ202011051003
不难看出后面的4位位数都是逐个递增的,我考虑到并发量高的原因,为了不影响主键生成带来系统压力,所以才选择redis这一块,redis的数值自增可以帮我们很好的解决这个问题
@Component
public class RedisUtils {
private static final int POOLINDEX = 1;
private static final int defaultInitValue = 1000;
@Resource
private RedisTemplate<String, Integer> redisTemplate;
public void setValue(String key, Integer value) {
JedisConnectionFactory connectionFactory = (JedisConnectionFactory) redisTemplate.getConnectionFactory();
connectionFactory.setDatabase(POOLINDEX);
redisTemplate.opsForValue().set(key, value);
}
public void setValue(String key, Integer value, long timeout, TimeUnit unit) {
JedisConnectionFactory connectionFactory = (JedisConnectionFactory) redisTemplate.getConnectionFactory();
connectionFactory.setDatabase(POOLINDEX);
redisTemplate.opsForValue().set(key, value, timeout, unit);
}
public Object getValue(Object key) {
JedisConnectionFactory connectionFactory = (JedisConnectionFactory) redisTemplate.getConnectionFactory();
connectionFactory.setDatabase(POOLINDEX);
Object value = redisTemplate.opsForValue().get(key);
return value;
}
public Long getIncrementNum(String key) {
JedisConnectionFactory connectionFactory = (JedisConnectionFactory) redisTemplate.getConnectionFactory();
connectionFactory.setDatabase(POOLINDEX);
if (getValue(key) != null) {
RedisAtomicLong enquiryRedisAtomicLong = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
return enquiryRedisAtomicLong.incrementAndGet();
} else {
RedisAtomicLong enquiryRedisAtomicLong = new RedisAtomicLong(key, redisTemplate.getConnectionFactory(), defaultInitValue);
return enquiryRedisAtomicLong.incrementAndGet();
}
}
}
这里要注意一点,如果redis里面不存在那个键的话,默认从1000开始自增,getIncrementNum就是获取自增的方法。
public class IncreatementIdUtils implements Serializable {
@Inject
private RedisUtils redisUtils;
public String getComposeId(String prefix) {
LocalDateTime now = LocalDateTime.now();
Long incrementNum = redisUtils.getIncrementNum(ProductEnquiry.REDIS_KEY);
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMdd");
String format = dtf.format(now);
return prefix + format + incrementNum;
}
}
这是主键生成的工具类,引用redisUtil,生成组合型流水号主键
测试类和效果
@GetMapping("/testRedisKey")
@ResponseBody
public String testRedisKey(){
return increatementIdUtils.getComposeId("XJ");
}
但是代码其实还能写的再优雅一点,后面来优化一下,这里还有个很疑惑的问题
既然是以来于redis,那就要考虑一下redis宕机怎么办,现在的架构,如果redis宕机,主键生成就失败了,如果是分布式条件下,大概率会被熔断器截掉
而且流水号不能丢失,我的想法是由两个,一个是配置redis持久化,及时重启了,也能够恢复这个流水号数据,还有一个方案是存入数据库中。但是怎么
存入数据库中,确保数据库不会被高并发压垮,这也是一个值得深思的问题,如果有大神知道的话,就相互交流一下哈