基于j2cache实现的redis分布式锁


title: 基于j2cache实现的redis分布式锁 tags:

  • J2cache
  • redis
  • 分布式锁
  • blockingCache categories: j2cache date: 2017-06-25 18:18:53

系统现状:

由于系统目前提醒是在公共模块,对于提醒的数目实时计算。对于db的压力较大。因此采用J2cache的两级缓存。关于两级缓存的说明

但是当部分业务更新时需要将缓存删除,导致系统在某个时间点出现db负载过大。

对应业务系统出现大量慢sql以及由于超时无法完成的请求

出现了系统雪崩的场景。

两级缓存实现分析之缓存设置   ===》当大量请求提醒的业务sql过来导致系统无法及时的响应。而提醒sql又出现超时是的无法真正的缓存下来。使的sql越积越多,db负载过高。直到有成功将缓存写入或者db被击穿。

对策:

  1. 改写sql,尽量提高sql效率
  2. 在缓存处加分布式锁,避免同时回源

第一个策略对于系统并没有得到很好的缓解。主要是由于该发生提醒缓存失效使用的用户较多,用户频繁请求数据,导致结果无法在有效的h时间内缓存,出现大量慢sql请求直至系统超时。

那么我们要做的就是在缓存上增加blockingcache的功能。

那么何为blockingCache呢?需要哪些功能呢?

  1. 对于系统同一个缓存在多个请求过来时能hold住其他请求,单个请求执行业务
  2. 防止出现死锁,在系统挂掉的情况下锁能够自动释放
  3. 可定义的超时时长
  4. 其他请求在缓存中设置过值之后自动苏醒,返回缓存结果
  5. 支持可重入
  6. 其他配置需求等等

由上述可知,我们在缓存get时需要处理的事情如下:

  1. 先取缓存,如果有结果直接返回
  2. 如果没有缓存,直接加锁
  3. 如果加锁成功直接返回null  ===》需要去执行业务逻辑
  4. 如果加锁不成功或者不可重入 则线程需要等待
  5. 线程每隔指定时间sleep结束(默认为300ms)最多等待60s,每次需要检查是否缓存中结果如果有结果直接返回
  6. 还需要检查是否可以加锁成功(如果加锁成功返回null)

查看原先的get的代码如下

    public Object get(Object key) throws CacheException {
        if (null == key)
            return null;
        Object obj = null;
        try {
            byte[] b = redisCacheProxy.hget(region2, getKeyName(key));
            if (b != null)
                obj = SerializationUtils.deserialize(b);
        } catch (Exception e) {
            log.error("Error occured when get data from redis2 cache", e);
            if (e instanceof IOException || e instanceof NullPointerException)
                evict(key);
        }
        return obj;
    }
复制代码

改造后代码如下

    public Object get(Object key) throws CacheException {
        if (null == key)
            return null;
        Object obj = null;
        try {
            byte[] keyName = getKeyName(key);
            byte[] b = redisCacheProxy.hget(region2, keyName);
            if (b != null) {
                obj = SerializationUtils.deserialize(b);
            } else if (redisCacheProxy.isBlock()) {
                byte[] lockKey = getLockKey(key);
                boolean locked = getLock(lockKey);
                if (locked || canReentrant(key)) {
                    return null;
                } else {
                    int timeLeft = redisCacheProxy.getTimeOutMillis();
                    while (timeLeft > 0) {
                        Thread.sleep(redisCacheProxy.getTimeWaitMillis());
                        timeLeft -= redisCacheProxy.getTimeWaitMillis();
                        b = redisCacheProxy.hget(region2, keyName);
                        if (b != null) {
                            obj = SerializationUtils.deserialize(b);
                            break;
                        } else {
                            //如果拿不到再尝试一次获取lock,防止出现部分情况一直没有put导致等待时间过长。后续要改造成可重入
                            if (getLock(lockKey)) {
                                return null;
                            }
                        }
                        //超时是应该抛异常呢还是直接返回null? 目前返回null
                    }
                }
     
            }
        } catch (Exception e) {
            log.error("Error occured when get data from redis2 cache", e);
            if (e instanceof IOException || e instanceof NullPointerException)
                evict(key);
        }
        return obj;
    }
     
    private boolean canReentrant(Object key) {
        //对于缓存来说要求不精确,使用线程id即可
        try {
            String value = redisCacheProxy.get(getLockKeyString(key));
            if (value != null) {
                long oriThreadId = Long.parseLong(value);
                return oriThreadId == Thread.currentThread().getId();
            }
        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
        }
        return false;
    }
     
    private boolean getLock(byte[] lockKey) {
        return getLock(lockKey, String.valueOf(Thread.currentThread().getId()).getBytes());
    }
     
    private boolean getLock(byte[] lockKey, byte[] keyName) {
        return "OK".equals(redisCacheProxy.set(lockKey, keyName, NX, PX, redisCacheProxy.getTimeLockMillis()));
    }
     
    private void releaseLock(byte[] lockKey) {
        redisCacheProxy.del(lockKey);
    }
     
    private String getLockKeyString(Object key) {
        return String.format(lockPattern, region, key.hashCode() % redisCacheProxy.getStripes());
    }
     
    private byte[] getLockKey(Object key) {
        String keyName = getLockKeyString(key);
        return keyName.getBytes();
    }
复制代码

可以参考

https://git.oschina.net/ld/J2Cache/pulls/48

https://git.oschina.net/ld/J2Cache/pulls/47

http://git.oschina.net/ld/J2Cache/commit/928bde1a60b9884f5dff95257a954c61aa2bb367

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值