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()方法,此方法中会进行关闭操作

 

    原文作者:chen_lay
    原文地址: https://blog.csdn.net/chen_lay/article/details/99624125
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞