首页 > 编程语言 > 使用Redis incr解决并发问题的操作
2020
11-25

使用Redis incr解决并发问题的操作

项目背景:

1、新增问题件工单,工单中有工单编码字段,工单编码字段的规则为 “WT”+yyyyMMdd+0000001。

2、每天的工单生成量是30W,所以会存在并发问题

解决思路:

1、首先乐观的认为redis不会宕机,对应的缓存不会被清除(除非人为操作,人为操作会有独立的补救办法)

2、将工单编码存到缓存中(redis),其值只存“WT”+yyyyMMdd后面的数字部分;

对应的key为:key标识+yyyyMMdd,即每天一个key

3、每次生成工单编码时,先调用redis的incr方法,使其在原来编码的基础上加1,并返回结果

4、判断返回的结果,如果返回的是1,说明当前key之前不存在,为生成的新的一天的key,

需要设置此key的生命周期为24小时

5、每个key只会存活24小时

6、如果redis宕机,或者key被删除,调用指定的接口,接口会去数据库查询今天最大的工单编码,

解析后,将其存在redis中,后面的工单编码再在此基础上自增

7、请自行配置redisClient客户端并实例化

代码:

1、编码获取核心方法

/**
 * 通过自定义的方法查询问题件缓存
 * @param redisKey
 * @param codePre
   * @return
   */
 public static String getCodeBySelfRedis(String redisKey,String codePre){
 String nowDateStr = DateTimeUtil.getDateTimeStr(new Date(),DateTimeUtil.DATE_PATTERN_YYYYMMDD);
 Long value = 1L;
 RedisClient uceClient = null;
 Jedis jedis=null;
 try {
  uceClient = SpringContextUtil.getBean("uceClient");
  jedis = uceClient.getResource();
  value = jedis.incr(redisKey+nowDateStr);
  if(value != null){
  //如果值为1说明是第一次设置,那么设置key的存活时间为24小时
  if (value == 1){
   jedis.expire(redisKey+nowDateStr,(24*60*60+1000));
  }
  }else{
  //如指为空,重置缓存
  value = resetCodeRedis(redisKey);
  }
 }catch (Exception e){
  logger.error("获取问题件编码的自定义缓存异常:",e);
  value = resetCodeRedis(redisKey);
 }finally {
  if (uceClient != null){
  uceClient.returnResource(jedis);
  }
 }
 String problemCode = String.valueOf(value);
 for(int i = 0;i < 7;i++){
  if (problemCode.length() < 7){
  problemCode = "0"+problemCode;
  }else{
  break;
  }
 }
 return codePre + nowDateStr + problemCode;
 }

2、宕机时,调用的核心接口方法

/**
 * 重置编码缓存
 * @param redisKey
 * @return
 */
 public static Long resetCodeRedis(String redisKey){
 String oldDateStr = null;
 String nowDateStr = DateTimeUtil.getDateTimeStr(new Date(),DateTimeUtil.DATE_PATTERN_YYYYMMDD);
 //编码的最大字符串
 String databaseMaxCode = null;
 //问题件
 if(StringUtil.isNotEmpty(redisKey) && redisKey.equals(WO_PROBLEM_CODE_KEY)){
  CsWoProblemService csWoProblemService = SpringContextUtil.getBean("csWoProblemService");
  //获取数据库中的问题件的最大编码
  databaseMaxCode = csWoProblemService.getMaxCode(WO_PROBLEM_CODE_PRE);
  //获取编码的日期部分
  if(StringUtil.isNotEmpty(databaseMaxCode)){
  oldDateStr = databaseMaxCode.substring(2,10);
  databaseMaxCode = databaseMaxCode.substring(10);
  }
 }else if(StringUtil.isNotEmpty(redisKey) && redisKey.equals(WO_CUST_SER_CODE_KEY)){
  CsWoCustSerService csWoCustSerService = SpringContextUtil.getBean("csWoCustSerService");
  //获取数据库中的客户服务类工单的最大编码
  databaseMaxCode = csWoCustSerService.getMaxCode("");
  //获取编码的日期部分
  if(StringUtil.isNotEmpty(databaseMaxCode)){
  oldDateStr = databaseMaxCode.substring(0,8);
  databaseMaxCode = databaseMaxCode.substring(8);
  }
 }
 Long value = getRedisValue(oldDateStr,nowDateStr,databaseMaxCode);
 RedisClient uceClient = null;
 Jedis jedisCluster = null;
 try {
  uceClient = SpringContextUtil.getBean("uceClient");
  jedisCluster = uceClient.getResource();
  boolean keyExist = jedisCluster.exists(redisKey + nowDateStr);
  // NX是不存在时才set, XX是存在时才set, EX是秒,PX是毫秒
  if (keyExist) {
  jedisCluster.del(redisKey + nowDateStr);
  }
  //设置缓存值,并设置为24小时后自动失效
  jedisCluster.set(redisKey + nowDateStr, String.valueOf((value + 1)), "NX","EX", (24*60*60+1000));
 }catch (Exception e){
  logger.error((redisKey + "编码重置异常:"),e);
 }finally {
  if(uceClient != null){
  uceClient.returnResource(jedisCluster);
  }
 }
 return value + 1;
 }
 
 /**
 * 解析redis的值
 * @param oldDateStr
 * @param nowDateStr
 * @param databaseMaxCode
 * @return
 */
 public static Long getRedisValue(String oldDateStr,String nowDateStr,String databaseMaxCode){
 Long value = 0L;
 String firstCodeZero = "0";
 //如果日期相同,解析编码数据存到缓存中
 if(StringUtil.isNotEmpty(oldDateStr) && StringUtil.isNotEmpty(nowDateStr) && oldDateStr.equals(nowDateStr) && StringUtil.isNotEmpty(databaseMaxCode)){
  for(int i = 0;i < 7;i++){
  String firstCode = databaseMaxCode.substring(0,1);
  if (StringUtil.isNotEmpty(firstCode) && firstCodeZero.equals(firstCode)){
   databaseMaxCode = databaseMaxCode.substring(1);
  }else{
   break;
  }
  }
  if (StringUtil.isNotEmpty(databaseMaxCode)){
  value = Long.parseLong(databaseMaxCode);
  }
 }
 return value;
 }

注意:

jedis使用后一定要close,否则jedis连接被占用的会越来越多,可用的连接数会越来越少,最终会导致redis宕机,最终项目宕机。

本项目是在finally中调用的自己封装的returnResource()方法,此方法中会进行关闭操作

补充知识:redis在高并发下导致锁失效问题

解决办法:

可以给线程加唯一标识 关闭线程时判断标识是否相同

问题2:线程超时问题如何解决 同一时间会有俩个或俩个以上线程操作同一方法

使用分布式锁redisson

以上这篇使用Redis incr解决并发问题的操作就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持自学编程网。

编程技巧