• Redis多容器高并发场景 , 设置缓存的时候,要考虑多容器加锁的场景。(incr计数和redis分布式锁区别)


    1.设置缓存的时候,要考虑多容器加锁的场景。

    (1)场景,短信回执场景,会有二次回执的情况,但是我们只处理一次回执的消息体,如何不处理二次回执呢?

    // 队列中有数据且容量未达到100,可继续放入队列数据
    	if (viberReceiptQueue.size() > 0 && (dlvViberReceiptMap.size() + fldViberReceiptMap.size()) <= 100) {
    	while (viberReceiptQueue.size() > 0
    	      && (dlvViberReceiptMap.size() + fldViberReceiptMap.size()) <= 100) {
    	  ViberReceipt viberReceipt = viberReceiptQueue.take();
    	  logger.info("HandlerRunnable viberReceipt take:{}", JSONObject.toJSONString(viberReceipt));
    	  if (Objects.isNull(viberReceipt.getPartId())) {
    	      MccContact mccContact = mccContactMapper.selectByPrimaryKey(viberReceipt.getMessageId());
    	      viberReceipt.setPartId(Long.parseLong(mccContact.getPartId().toString()));
    	      // 这里做一个二次回执业务过滤,判断contact表的状态是否已经被修改,如果已经被修改,则不处理
    	      if (!CmsStateDef.CONTACT_STATE_CSD.equals(mccContact.getContactState())) {
    	          continue;
    	      }
    	  }
    	  //failed 发送失败  pending mmgw发送成功   success 发送成功  ceg一次成功回执pending 但一次失败回执failed 去重
    	  if ("failed".equals(viberReceipt.getResult())) {
    	      String key = viberReceipt.getMessageId() + "-" + viberReceipt.getResult();
    	      String value = RedisUtil.getValue(RedisUtil.FUNTYPE1, key);
    	      logger.info("qry FLD cache key:{}, is repeat flag:{}", key, value);
    	
    	      if (StringUtils.isEmpty(value)) {
    	          fldViberReceiptMap.put(viberReceipt.getMessageId(), viberReceipt);
    	          RedisUtil.setValue(RedisUtil.FUNTYPE1, key, "2", 3600);
    	      }
    	      else {
    	          RedisUtil.delKey(RedisUtil.FUNTYPE1, key);
    	      }
    	  }
    	  else if ("success".equals(viberReceipt.getResult())) {
    	      dlvViberReceiptMap.put(viberReceipt.getMessageId(), viberReceipt);
    	  }
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    这里我们通过RedisUtil.setValue(RedisUtil.FUNTYPE1, key, “2”, 3600);方法。将一次回执之后的key设置到缓存中去,这样二次回执过来的时候,会去查看缓存中是否有值,有值,则一次回执已经处理,该消息体为二次回执。不处理。

    (2)上面这种解决方法看起来好像没有什么问题,但是Redis一般都是分布在多容器的,如果两个容器的两个消息同时getValue,这样既取不到数据,并且两条消息都进行了处理,显然与我们要求的不符合。
    解决方法:使用RedisUtil的单线程处理方法incr

    	// 队列中有数据且容量未达到100,可继续放入队列数据
    	if (viberReceiptQueue.size() > 0 && (dlvViberReceiptMap.size() + fldViberReceiptMap.size()) <= 100) {
    	    while (viberReceiptQueue.size() > 0
    	            && (dlvViberReceiptMap.size() + fldViberReceiptMap.size()) <= 100) {
    	        ViberReceipt viberReceipt = viberReceiptQueue.take();
    	        logger.info("HandlerRunnable viberReceipt take:{}", JSONObject.toJSONString(viberReceipt));
    	        MccContact mccContact = mccContactMapper.selectByPrimaryKey(viberReceipt.getMessageId());
    	        viberReceipt.setPartId(Long.parseLong(mccContact.getPartId().toString()));
    	        // 这里做一个二次回执业务过滤,判断contact表的状态是否已经被修改,如果已经被修改,则不处理
    	        if (!CmsStateDef.CONTACT_STATE_CSD.equals(mccContact.getContactState())) {
    	            continue;
    	        }
    	
    	        //failed 发送失败  pending mmgw发送成功   success 发送成功  ceg一次成功回执pending 但一次失败回执failed 去重
    	        if ("failed".equals(viberReceipt.getResult())) {
    	            String key = viberReceipt.getMessageId() + "-" + viberReceipt.getResult();
    	            int incr = RedisUtil.incr(RedisUtil.FUNTYPE1, key, 1,  3600l);
    	
    	            logger.info("qry FLD cache key:{}, is repeat flag:{}", key, incr);
    	
    	            if (incrValue != incr) {
    	                fldViberReceiptMap.put(viberReceipt.getMessageId(), viberReceipt);
    	            }
    	        }
    	        else if ("success".equals(viberReceipt.getResult())) {
    	            dlvViberReceiptMap.put(viberReceipt.getMessageId(), viberReceipt);
    	        }
    	    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    (3)为什么使用incr:(自增计数器)

    1.Redis自增key 的好处

    1. 原子性(atomicity):一个事务是一个不可分割的最小工作单位,事务中包括的诸操作要么都做,要么都不做。
    2. Redis所有单个命令的执行都是原子性的,这与它的单线程机制有关;
    3. Redis命令的原子性使得我们不用考虑并发问题,可以方便的利用原子性自增操作

    (4)为什么不使用加锁的方式(不选择Redis锁的原因:)

    1. 它获取锁的方式简单粗暴,获取不到锁直接不断尝试获取锁,比较消耗性能。
    2. 另外来说的话,Redis 的设计定位决定了它的数据并不是强一致性的,在某些极端情况下,可能会出现问题。锁的模型不够健壮。
    3. 即便使用 Redlock 算法来实现,在某些复杂场景下,也无法保证其实现 100% 没有问题,关于 Redlock 的讨论可以看 How to do distributed locking。
      Redis 分布式锁,其实需要自己不断去尝试获取锁,比较消耗性能。

    (5)其他使用场景:。

    限流可以使用redis原子计数器incr.但是除了限流以外,很多系统会在一些节日的时候搞一些活动,当然,这些活动是有奖品的,并且奖品的数量也是有限的。为了防止在高并发的时候,出现多个人中奖的情况,那么可以使用分布式锁,比如redis的分布式锁,zookeeper的分布式锁。当然,我们也可以采用简单一点的方案,就是使用redis原子计数器incr来统计。

  • 相关阅读:
    校园食堂明厨亮灶智能视频监控
    IOS浏览器不支持对element ui table的宽度设置百分比
    jQuery相关知识
    ENVI:如何进行遥感图像的分类?(决策树模型)
    C语言——全局变量和局部变量重名了会怎么样
    Android codec2 编码 -- 基于录屏
    (题目练习)条件概率+权值线段树+FWT+后缀数组
    Idea设置
    240.食物链(并查集的扩展,维护额外信息)
    2023年中秋·国庆节放假通知
  • 原文地址:https://blog.csdn.net/qq_43436117/article/details/125443618