侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

Redis实现分布式锁和等待序列的方法示例

Redis  /  管理员 发布于 5年前   229

在集群下,经常会因为同时处理发生资源争抢和并发问题,但是我们都知道同步锁 synchronized 、 cas 、 ReentrankLock 这些锁的作用范围都是 JVM ,说白了在集群下没啥用。这时我们就需要能在多台 JVM 之间决定执行顺序的锁了,现在分布式锁主要有 redis 、 Zookeeper 实现的,还有数据库的方式,不过性能太差,也就是需要一个第三方的监管。

背景

最近在做一个消费 Kafka 消息的时候发现,由于线上的消费者过多,经常会遇到,多个机器同时处理一个主键类型的数据的情况发生,如果最后是执行更新操作的话,也就是一个更新顺序的问题,但是如果恰好都需要插入数据的时候,会出现主键重复的问题。这是生产上不被允许的(因为公司有异常监管的机制,扣分啥的),这是就需要个分布式锁了,斟酌后用了 Redis 的实现方式(因为网上例子多)

分析

redis 实现的分布式锁,实现原理是 set 方法,因为多个线程同时请求的时候,只有一个线程可以成功并返回结果,还可以设置有效期,来避免死锁的发生,一切都是这么的完美,不过有个问题,在 set 的时候,会直接返回结果,成功或者失败,不具有阻塞效果,需要我们自己对失败的线程进程处理,有两种方式

  • 丢弃
  • 等待重试 由于我们的系统需要这些数据,那么只能重新尝试获取。这里使用 redis 的 List 类型实现等待序列的作用

代码

直接上代码 其实直接redis的工具类就可以解决了

package com.testimport redis.clients.jedis.Jedis;import java.util.Collections;import java.util.List;/** * @desc redis队列实现方式 * @anthor  * @date  **/public class RedisUcUitl {  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 RedisUcUitl() {  }  /**   * logger   **/  /**   * 存储redis队列顺序存储 在队列首部存入   *   * @param key  字节类型   * @param value 字节类型   */  public static Long lpush(Jedis jedis, final byte[] key, final byte[] value) {    return jedis.lpush(key, value);    }  /**   * 移除列表中最后一个元素 并将改元素添加入另一个列表中 ,当列表为空时 将阻塞连接 直到等待超时   *   * @param srckey   * @param dstkey   * @param timeout 0 表示永不超时   * @return   */  public static byte[] brpoplpush(Jedis jedis,final byte[] srckey, final byte[] dstkey, final int timeout) {    return jedis.brpoplpush(srckey, dstkey, timeout);  }  /**   * 返回制定的key,起始位置的redis数据   * @param redisKey   * @param start   * @param end -1 表示到最后   * @return   */  public static List<byte[]> lrange(Jedis jedis,final byte[] redisKey, final long start, final long end) {        return jedis.lrange(redisKey, start, end);  }  /**   * 删除key   * @param redisKey   */  public static void delete(Jedis jedis, final byte[] redisKey) {         return jedis.del(redisKey);  }  /**   * 尝试加锁   * @param lockKey key名称   * @param requestId 身份标识   * @param expireTime 过期时间   * @return   */  public static boolean tryGetDistributedLock(Jedis jedis,final String lockKey, final String requestId, final int expireTime) {    String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);    return LOCK_SUCCESS.equals(result);  }  /**   * 释放锁   * @param lockKey key名称   * @param requestId 身份标识   * @return   */  public static boolean releaseDistributedLock(Jedis jedis,final String lockKey, final String requestId) {    final String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";    jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));    return RELEASE_SUCCESS.equals(result);  }}

业务逻辑主要代码如下

1.先消耗队列中的

while(true){  // 消费队列  try{    // 被放入redis队列的数据 序列化后的    byte[] bytes = RedisUcUitl.brpoplpush(keyStr.getBytes(UTF_8), dstKeyStr.getBytes(UTF_8), 1);    if(bytes == null || bytes.isEmpty()){      // 队列中没数据时退出      break;    }    // 反序列化对象    Map<String, Object> singleMap = (Map<String, Object>) ObjectSerialUtil.bytesToObject(bytes);    // 塞入唯一的值 防止被其他线程误解锁    String requestId = UUID.randomUUID().toString();    boolean lockGetFlag = RedisUcUitl.tryGetDistributedLock(keyStr,requestId, 100);    if(lockGetFlag){      // 成功获取锁 进行业务处理      //TODO      // 处理完毕释放锁       boolean freeLock = RedisUcUitl.releaseDistributedLock(keyStr, requestId);    }else{      // 未能获得锁放入等待队列     RedisUcUitl.lpush(keyStr.getBytes(UTF_8), ObjectSerialUtil.objectToBytes(param));      }      }catch(Exception e){    break;  }  }

2.处理最新接到的数据

同样是走尝试获取锁,获取不到放入队列的流程

一般序列化用 fastJson 之列的就可以了,这里用的是 JDK 自带的,工具类如下

public class ObjectSerialUtil {  private ObjectSerialUtil() {//    工具类  }  /**   * 将Object对象序列化为byte[]   *   * @param obj 对象   * @return byte数组   * @throws Exception   */  public static byte[] objectToBytes(Object obj) throws IOException {    ByteArrayOutputStream bos = new ByteArrayOutputStream();    ObjectOutputStream oos = new ObjectOutputStream(bos);    oos.writeObject(obj);    byte[] bytes = bos.toByteArray();    bos.close();    oos.close();    return bytes;  }  /**   * 将bytes数组还原为对象   *   * @param bytes   * @return   * @throws Exception   */  public static Object bytesToObject(byte[] bytes) {    try {      ByteArrayInputStream bin = new ByteArrayInputStream(bytes);      ObjectInputStream ois = new ObjectInputStream(bin);      return ois.readObject();    } catch (Exception e) {      throw new BaseException("反序列化出错!", e);    }  }}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


  • 上一条:
    redis实现分布式的方法总结
    下一条:
    Redis实现分布式锁的几种方法总结
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 在Redis中能实现的功能、常见应用介绍(0个评论)
    • 2024年Redis面试题之一(0个评论)
    • 在redis缓存常见出错及解决方案(0个评论)
    • 在redis中三种特殊数据类型:地理位置、基数(cardinality)估计、位图(Bitmap)使用场景介绍浅析(2个评论)
    • Redis 删除 key用 del 和 unlink 有啥区别?(1个评论)
    • 近期文章
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2017-12
    • 2020-03
    • 2020-05
    • 2021-04
    • 2022-03
    • 2022-05
    • 2022-08
    • 2023-02
    • 2023-04
    • 2023-07
    • 2024-01
    • 2024-02
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客