分布式锁中-基于 Redis 的实现需避坑 - Jedis 篇

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 分布式锁中-基于 Redis 的实现需避坑 - Jedis 篇

篇幅太长看着也累,每天进步一点点

欢迎关注公众号「架构染色」交流和学习

前情回顾

分布式锁系列内容规划如下,本篇是第 4 篇:

  1. 《分布式锁上-初探
  2. 《分布式锁中-基于 Zookeeper 的实现是怎样》
  3. 《分布式锁中-基于 etcd 的实现很优雅》
  4. 《分布式锁中-基于 Redis 的实现需避坑 - Jedis 篇》(本篇)
  5. 《分布式锁中-基于 Redis 的实现很多样 - Redission 篇》(写作中)
  6. 《分布式锁中-多维度的对比各种分布式锁实现》(写作中)
  7. 《分布式锁下-分布式锁客户端的抽象、适配与加固》(写作中)

一、redis 介绍

Redis 应该是目前最受欢迎的高性能的缓存数据库了,在五一期间看到一则 Redis 7.0 发布的消息后,回想起多年前学习黄健宏老师《Redis 从入门到精通》2.x 的月伴时光,不由得感慨 Reids 发展之迅速。搜集了一下 3.0 及之后各版本的知名特性,整理出来方便读者朋友们有个简单了解(感兴趣的朋友还需自行深入研究),情况大致如下:

  • 3.0 开始支持 cluster 集群模式
  • 4.0 开发的 lazyfree 和 PSYNC2 解决了 Redis 长久的大 key 删除阻塞问题及同步中断无法续传的问题
  • 5.0 新增了 stream 数据结构使 Redis 具备功能完整的轻量级消息队列能力
  • 6.0 更是发布了诸多企业级特性如 threaded-io、TLS 和 ACL 等,大幅提升了 Redis 的性能和安全性
  • 7.0 Function 彻底解决了过去 Lua 脚本同步丢失的问题;Multi Part AOF 增强了 Redis 的数据持久化的可靠性

1.1 特性介绍

为满足本篇目标所需,这里着重介绍以下几个关键特性:

  • 数据组织:Redis 中支持多种数据结构,将他们灵活组合搭配即可满足分布式锁在不同场景下的功能需求:
  • Jedis 和 Lettuce 这类框架中常使用 String 来做简易的锁信息存储
  • Redisson 中使用 Hash 结构来存储更多维度的锁信息,如:业务名称作为 key,uuid + 线程 id 作为 field,加锁次数作为 value
  • Redisson 中在公平锁的场景下引入 List 和 ZSet, List 类型用于线程排队,Zset 类型存放等待线程的顺序,分数 score 是等待线程的超时时间戳。

2Zmh5D.gif

  • 集群模式:Redis 采用集群模式分片存储数据,整个集群拥有固定的 2 的 32 次方个槽位,数据被分配到这些槽位中,每个实例只分管一部分槽位,而非如 etcd、ZK 这种每个实例中的数据都一致;集群模式提供的是数据规模扩大后的横向 AP 能力,应对单节点的风险需再加上主从模式,但当某个 master 节点挂之后,slave 节点可能还未同步到全部数据,会导致数据丢失;一致性保障能力偏弱

2Zmh5D.gif

  • 顺序变更:一种简单的抢锁逻辑是判断 key 是否已存在,Redis 中没有给变更操作附加顺序信息(如 etcd 中的 Revision),但服务端以串行方式处理数据的变更,那就可以结合其他数据结构来记录请求顺序信息,如公平锁的实现也会依赖其他数据结构存储信息,用于判断锁状态;但当用到的数据类型和指令变多后,由于是非原子性操作,自然就会遇到结果与预期不一致这类问题,Redis 提供的 lua 脚本机制可用于解决此类问题 ,用户在客户端编排自定义脚本逻辑:可用多个指令操控多个数据,然后将脚本发送给服务端,服务端执行 lua 脚本,并保障一个 lua 脚本内的所有操作是原子性的

2Zmh5D.gif

  • TTL 机制:TTL(Time To Live)机制是给单个 key 设置存活时间,超过时间后 Redis 自动删除这个 key

1.2 特性总结

Redis 的分布式锁正是基于以上特性来实现的,简单来说是:

  1. TTL 机制:用于支撑异常情况下的锁自动释放的能力
  2. 顺序变更:用于支撑获取锁和排队等待的能力
  3. 集群+主从模式:用于支撑锁服务的高可用

Redis 没有提供对分布式锁亲和的监听机制,需要客户端主动轮询感知数据变更。

二. 加锁解锁的流程描述

使用 Jedis 指令实现分布式锁的核心流程如下图所示:

2Zmh5D.gif

  1. 准备客户端、key 和 value
  2. 若 key 不存在,指定过期时间成功写入 Key-Value 则抢锁成功,并定时推后 key 的过期时间
  3. 若 key 已存在,则采用重试策略间歇性抢锁。
  4. 解锁时,删除 key 并撤销推后 key 过期时间的逻辑

其中第 2 和第 4 是核心环节,有几个版本的演进很有趣味:

  1. 插入 key 和设置过期时间并非原子操作:setnx + expire 加锁和设置过期是两个分开的独立操作;若发生异常,导致设置过期操作未执行,则此锁就成了永恒锁,其他客户端就再也抢不到了
  2. 以原子性操作完成插入 key 和设置过期时间:使用 set 的扩展指令,如下:
SET key value [EX seconds] [PX milliseconds] [NX|XX]
复制代码
  • NX :当 key 不存在时,才插入 Key
  • XX :当插入 key 时,指定值为固定的 lockValue
  • EX second :设置 key 的过期时间单位秒(PX\EX 二选一)
  • PX millisecond :设置键的过期时间单位毫秒(PX\EX 二选一)
if(jedis.set(key, lockValue, "NX", "EX", 100) == 1){ //加锁成功
  try {
      do work //执行业务
      //这里缺点什么?
  }catch(Exception e){
      //...
  }finally {
     jedis.del(key); //释放锁,这里可能误删其他client的锁key
  }
}
复制代码
  1. 引入 lockValue 的随机值校验,避免误释放其它客户端的锁,场景如下:
  • client1 加锁成功,key 10s 后过期,完成逻辑后,删除 key 之前,因 GC 导致持锁超过 10s,Redis 自动删除了 key,之后其他客户端可以抢锁
  • 假如是 client2 接下来成功抢锁,开始处理持锁后的逻辑。而此时 client1 GC 结束了会继续执行删除 key 的操作,但此时释放的其实是 client2 的 key

解决办法是:加锁时指定的 lockValue 为随机值,每次加锁时的值都是唯一的,释放锁时若 lockValue 与加锁时的值一致才可释放,否则什么都不做,逻辑如下:

if(jedis.set(key, randomLockValue, "NX", "EX", 100) == 1){ //加锁
   try {
       do something  //业务处理
   }catch(){
 }
 finally {
      //判断是不是当前线程加的锁,是才释放
      //但判断和释放锁两个操作不是原子性的
      if (randomLockValue.equals(jedis.get(key))) {
         jedis.del(key); //释放锁
      }
   }
}
复制代码

以上代码遗留的问题是判断 randomlockValue 和释放锁两个操作不是原子性的。

  1. 引入 lua 脚本,保障判断 randomlockValue 和删除 key 这两个操作的原子性,逻辑如下:
String script =
        "if redis.call('get',KEYS[1]) == ARGV[1] then" +
                "   return redis.call('del',KEYS[1]) " +
                "else" +
                "   return 0 " +
                "end";
Object result = jedis.eval(script, Collections.singletonList(key),
Collections.singletonList(randomLockValue));
if("1".equals(result.toString())){
    return true;
}
复制代码

至此依然存在的一个问题是:若持锁后,业务逻辑执行耗时 超过了 key 的过期时间,则锁 Key 会被 Reids 主动删除。

  1. 引入 watchDog 定时推后 key 的过期时间,避免业务未执行完时,key 过期被 Redis 删除。
if(jedis.set(key, randomLockValue, "NX", "EX", 100) == 1){ //加锁成功
  try {
      do work //执行业务
      //watchDog定时延后Key的过期时间
  }catch(Exception e){
      //...
  }finally {
     String script =
              "if redis.call('get',KEYS[1]) == ARGV[1] then" +
                      "   return redis.call('del',KEYS[1]) " +
                      "else" +
                      "   return 0 " +
                      "end";
      try {
          Object result = jedis.eval(script, Collections.singletonList(key),
                                  Collections.singletonList(randomLockValue));
          if("1".equals(result.toString())){
              return true;
          }
          return false;
      }catch(Exception e){
      //...
    }
  }
}
复制代码

三. Jedis 分布式锁的能力

可能读者是单篇阅读,这里引入第一篇《分布式锁上-初探》中的一些内容,一个分布式锁应具备这样一些功能特点:

  • 互斥性:在同一时刻,只有一个客户端能持有锁
  • 安全性:避免死锁,如果某个客户端获得锁之后处理时间超过最大约定时间,或者持锁期间发生了故障导致无法主动释放锁,其持有的锁也能够被其他机制正确释放,并保证后续其它客户端也能加锁,整个处理流程继续正常执行
  • 可用性:也被称作容错性,分布式锁需要有高可用能力,避免单点故障,当提供锁的服务节点故障(宕机)时不影响服务运行,这里有两种模式:一种是分布式锁服务自身具备集群模式,遇到故障能自动切换恢复工作;另一种是客户端向多个独立的锁服务发起请求,当某个锁服务故障时仍然可以从其他锁服务读取到锁信息(Redlock)
  • 可重入性:对同一个锁,加锁和解锁必须是同一个线程程,即不能把其他线程持有的锁给释放了
  • 高效灵活:加锁、解锁的速度要快;支持阻塞和非阻塞;支持公平锁和非公平锁

基于上文对 Jedis 分布式锁的介绍,这里简单总结一下 Jedis 的能力矩阵,ZK 请看《分布式锁中-基于 Zookeeper 的实现》,etcd 请看《分布式锁中-基于 etcd 的实现很优雅》 ,表格中标题使用 Redis-简单锁,主要是跟 RedLock 做区分,这种简单锁使用 Jedis 、Lettuce、Redisson 都能实现,任何一把锁的信息只保存在一个 Redis master 实例中,而 RedLock 是 Redisson 提供的高阶分布式锁,它需要客户端同时跟多个 Redis master 实例协作才能完成,即一把锁的信息同时存在于多个 master 实例中。它的情况会在后续文章中补充(感兴趣的读者可以关注本号 【架构染色】 ,文章完成时会主动推送给你)

能力 ZK etcd Redis-简单锁 Redlock MySql
互斥
安全 链接异常时,session 丢失自动释放锁 基于租约,超时自动释放锁 基于 TTL,超时自动释放锁
可用性 相对可用性还好
可重入 服务端非可重入,本地线程可重入 服务端非可重入,Resission本地线程可重入 服务端非可重入,本地线程可重入需自研
加解锁速度 速度不算快 速度快,GRPC 协议优势以及服务端能力的优势 速度快
阻塞非阻塞 客户端两种能力都提供 jetcd-core 中,阻塞非阻塞由 Future#get 支撑 Jedis非阻塞,# Redission提供阻塞能力
公平非公平 公平锁 公平锁 非公平锁,# Redission提供公平锁
可续期 天然支持 天然支持 Jedis需自研 watchDog,Redission自带
其他因素 技术栈偏老,性能不佳 多数公司不熟悉 容易受业务缓存操作干扰

四、Jedis 库实现分布式锁

Jedis 是 Redis 官方推出的用于通过 Java 连接 Redis 客户端的一个工具包,提供了 Redis 的各种命令支持。

4.1 pom 依赖

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>4.3.0</version>
</dependency>
复制代码

4.2 相关的 API 介绍

  • 使用 SET 的扩展指令加锁(SET key value [EX seconds][px milliseconds] [NX|XX])
SetParams params = SetParams.setParams().nx().ex(lockState.getLeaseTTL());
 String result = client.set(lockState.getLockKey(), lockState.getLockValue(), params);
复制代码
  • 使用 lua 解锁
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = client.eval(script, 1, lockState.getLockKey(), lockState.getLockValue());
复制代码

4.3 分布式锁示例

  • 锁的封装
package com.rock.dlock.jedis;
import com.rock.dlock.common.DtLockException;
import com.rock.dlock.common.KeepAliveAction;
import com.rock.dlock.common.KeepAliveTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.JedisPooled;
import redis.clients.jedis.params.SetParams;
import java.net.SocketTimeoutException;
import java.util.concurrent.TimeUnit;
/**
 * @author zs
 * @date 2022/11/13 4:44 PM
 */
public class DemoJedisLock {
    private final static Logger log = LoggerFactory.getLogger(DemoJedisLock.class);
    private JedisPooled client;
    private LockState lockState;
    private KeepAliveTask keepAliveTask;
    private int sleepMillisecond;
    private final static String RESULT_OK = "OK";
    private static final Long UNLOCK_SUCCESS = 1L;
    class LockState {
        private String lockKey;
        private String lockValue;
        private String errorMsg;
        private int leaseTTL;
        private long leaseId;
        private boolean lockSuccess;
        public LockState(String lockKey, int leaseTTL) {
            this.lockKey = lockKey;
            this.leaseTTL = leaseTTL;
        }
        public LockState(String lockKey, String value, int leaseTTL) {
            this.lockKey = lockKey;
            this.lockValue = value;
            this.leaseTTL = leaseTTL;
        }
        public String getLockKey() {
            return lockKey;
        }
        public void setLockKey(String lockKey) {
            this.lockKey = lockKey;
        }
        public String getLockValue() {
            return lockValue;
        }
        public void setLockValue(String lockValue) {
            this.lockValue = lockValue;
        }
        public String getErrorMsg() {
            return errorMsg;
        }
        public void setErrorMsg(String errorMsg) {
            this.errorMsg = errorMsg;
        }
        public long getLeaseId() {
            return leaseId;
        }
        public void setLeaseId(long leaseId) {
            this.leaseId = leaseId;
        }
        public boolean isLockSuccess() {
            return lockSuccess;
        }
        public void setLockSuccess(boolean lockSuccess) {
            this.lockSuccess = lockSuccess;
        }
        public int getLeaseTTL() {
            return leaseTTL;
        }
        public void setLeaseTTL(int leaseTTL) {
            this.leaseTTL = leaseTTL;
        }
    }
    public DemoJedisLock(JedisPooled client, String key, String value, int ttlSeconds) {
        //1.准备客户端
        this.client = client;
        this.lockState = new LockState(key, value, ttlSeconds);
        this.sleepMillisecond = (ttlSeconds * 1000) / 3; //抢锁的重试间隔可由用户指定
    }
    public boolean tryLock(long waitTime, TimeUnit waitUnit) throws DtLockException {
        long totalMillisSeconds = waitUnit.toMillis(waitTime);
        long start = System.currentTimeMillis();
        //重试,直到成功或超过指定时间
        while (true) {
            // 抢锁
            try {
                SetParams params = SetParams.setParams().nx().ex(lockState.getLeaseTTL());
                String result = client.set(lockState.getLockKey(), lockState.getLockValue(), params);
                if (RESULT_OK.equals(result)) {
                    manualKeepAlive();
                    log.info("[jedis-lock] lock success 线程:{} 加锁成功,key:{} , value:{}", Thread.currentThread().getName(), lockState.getLockKey(), lockState.getLockValue());
                    lockState.setLockSuccess(true);
                    return true;
                } else {
                    if (System.currentTimeMillis() - start >= totalMillisSeconds) {
                        return false;
                    }
                    Thread.sleep(sleepMillisecond);
                }
            } catch (Exception e) {
                Throwable cause = e.getCause();
                if (cause instanceof SocketTimeoutException) {//忽略网络抖动等异常
                }
                log.error("[jedis-lock] lock failed:" + e);
                throw new DtLockException("[jedis-lock] lock failed:" + e.getMessage(), e);
            }
        }
    }
    //此实现中忽略,网络通信异常部分的处理,可参考tryLock
    public void unlock() throws DtLockException {
        try {
            // 首先停止续约
            if (keepAliveTask != null) {
                keepAliveTask.close();
            }
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            Object result = client.eval(script, 1, lockState.getLockKey(), lockState.getLockValue());
            if (UNLOCK_SUCCESS.equals(result)) {
                log.info("[jedis-lock] unlock success 线程 : {} 解锁成功,锁key : {} ,路径:{}", Thread.currentThread().getName(), lockState.getLockKey(), lockState.getLockValue());
            } else {
                log.info("[jedis-lock] unlock del key failed ,线程 : {} 解锁成功,锁key : {} ,路径:{}", Thread.currentThread().getName(), lockState.getLockKey(), lockState.getLockValue());
            }
        } catch (Exception e) {
            log.error("[jedis-lock] unlock failed:" + e.getMessage(), e);
            throw new DtLockException("[jedis-lock] unlock failed:" + e.getMessage(), e);
        }
    }
    // 定时将Key的过期推迟
    private void manualKeepAlive() {
        final String t_key = lockState.getLockKey();
        final int t_ttl = lockState.getLeaseTTL();
        keepAliveTask = new KeepAliveTask(new KeepAliveAction() {
            @Override
            public void run() throws DtLockException {
                // 刷新值
                try {
                    client.expire(t_key, t_ttl);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, t_ttl);
        keepAliveTask.start();
    }
}
复制代码
  • 异常类的简单实现
package com.rock.dlock.common;
public class DtLockException extends RuntimeException{
    public DtLockException(String message) {
        super(message);
    }
    public DtLockException(String message, Throwable cause) {
        super(message, cause);
    }
    public static DtLockException clientException(){
        return new DtLockException("client is empty");
    }
}
复制代码
  • watchDog 的任务抽象
package com.rock.dlock.common;
public interface KeepAliveAction {
    void run() throws DtLockException;
}
复制代码
  • watchDog 的简单实现
package com.rock.dlock.common;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.TimeUnit;
/**
 * @author zs
 * @date 2022/11/7 4:20 PM
 */
public class KeepAliveTask extends Thread {
    private static final Logger LOGGER = LoggerFactory.getLogger(KeepAliveTask.class);
    public volatile boolean isRunning = true;
    /**
     * 过期时间,单位s
     */
    private long ttlSeconds;
    private KeepAliveAction action;
    public KeepAliveTask(KeepAliveAction action, long ttlSeconds) {
        this.ttlSeconds = ttlSeconds;
        this.action = action;
        this.setDaemon(true);
    }
    @Override
    public void run() {
        final long sleep = this.ttlSeconds * 1000 / 3; // 每隔三分之一过期时间,续租一次
        while (isRunning) {
            try {
                // 1、续租,刷新值
                action.run();
                LOGGER.debug("续租成功!");
                TimeUnit.MILLISECONDS.sleep(sleep);
            } catch (InterruptedException e) {
                close();
            } catch (DtLockException e) {
                close();
            }
        }
    }
    public void close() {
        isRunning = false;
        this.interrupt();
    }
}
复制代码

4.4 测试锁

import com.rock.dlock.jedis.DemoJedisLock;
import redis.clients.jedis.JedisPooled;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
 * @author zs
 * @date 2022/11/13 4:51 PM
 */
public class TestJedisLock {
    public static void main(String[] args) {
        JedisPooled jedis = new JedisPooled("127.0.0.1", 6379);
        DemoJedisLock demoEtcdLock1 = new DemoJedisLock(jedis, "rock", UUID.randomUUID().toString(), 10);
        DemoJedisLock demoEtcdLock2 = new DemoJedisLock(jedis, "rock", UUID.randomUUID().toString(), 10);
        boolean lock1 = demoEtcdLock1.tryLock(20, TimeUnit.SECONDS);
        if (lock1) {
            try {
                System.out.printf("do something");
            } finally {
                demoEtcdLock1.unlock();
            }
        }
        demoEtcdLock1.tryLock(20, TimeUnit.SECONDS);
        demoEtcdLock2.tryLock(20, TimeUnit.SECONDS);//等待锁,超时后放弃
    }
}
复制代码

五、使用 Jedis 的一些注意事项

通常分布式锁服务会和业务逻辑使用同一个Redis 集群,自然也使用同一个 Jedis 客户端;当业务逻辑侧对 Redis 的读写并发提高时,会给 Redis 集群和 Jedis 客户度带来压力;为应对一些异常情况,我们除了解功能层面的 API,还需要了解一下客户端的一些配置调优,主要是池化管理和网络通信两个方面

5.1 池化管理

在使用 Jedis 时可以配置 JedisPool 连接池,池化处理有许多好处,如:提高响应的速度、降低资源的消耗、方便管理和维护;JedisPool 配置参数大部分是由 JedisPoolConfig 的对应项来赋值的,在生产中我们需要关注它的配置并合理的赋值,如此能够提升 Redis 的服务性能,降低资源开销。下边是对一些重要参数的说明、默认及设置建议:

参数 说明 默认值 建议
maxTotal 资源池中的最大连接数 8
maxIdle 资源池允许的最大空闲连接数 8
minIdle 资源池确保的最少空闲连接数 0
blockWhenExhausted 当资源池用尽后,调用者是否要等待。只有当值为 true 时,下面的maxWaitMillis才会生效。 true 建议使用默认值。
maxWaitMillis 当资源池连接用尽后,调用者的最大等待时间(单位为毫秒)。 -1(表示永不超时) 不建议使用默认值。
testOnBorrow 向资源池借用连接时是否做连接有效性检测(ping)。检测到的无效连接将会被移除。 false 业务量很大时候建议设置为 false,减少一次 ping 的开销。
testOnReturn 向资源池归还连接时是否做连接有效性检测(ping)。检测到无效连接将会被移除。 false 业务量很大时候建议设置为 false,减少一次 ping 的开销。
jmxEnabled 是否开启 JMX 监控 true 建议开启,请注意应用本身也需要开启。

空闲 Jedis 对象的回收检测由以下四个参数组合完成,testWhileIdle是该功能的开关。

名称 说明 默认值 建议
testWhileIdle 是否开启空闲资源检测。 false true
timeBetweenEvictionRunsMillis 空闲资源的检测周期(单位为毫秒) -1(不检测) 建议设置,周期自行选择,也可以默认也可以使用下方JedisPoolConfig 中的配置。
minEvictableIdleTimeMillis 资源池中资源的最小空闲时间(单位为毫秒),达到此值后空闲资源将被移除。 180000(即 30 分钟) 可根据自身业务决定,一般默认值即可,也可以考虑使用下方JeidsPoolConfig中的配置。
numTestsPerEvictionRun 做空闲资源检测时,每次检测资源的个数。 3 可根据自身应用连接数进行微调,如果设置为 -1,就是对所有连接做空闲监测。

通过源码可以发现这些配置是 GenericObjectPoolConfig 对象的属性,这个类实际上是 rg.apache.commons.pool2.impl apache 提供的,也就是说 jedis 的连接池是依托于 apache 提供的对象池来,这个对象池的声明周期如下图,感兴趣的可以看下:

2Zmh5D.gif

5.2 网络调优

  • max-redirects:这个是集群模式下,重定向的最大数量;举例说明,比如第一台挂了,连第二台,第二台挂了连第三台,重新连接的次数不能超过这个值
  • timeout:客户端超时时间,单位是毫秒

Rsdis 节点故障或者网络抖动时,这两个值如果不合理可能会导致很严重的问题,比如 timeout 设置为 1000,maxRedirect 为 2,一旦出现 redis 连接问题,将会导致请求阻塞 3s 左右。而这个 3 秒的阻塞在可能导致常规业务流量下的线程池耗尽,需根据业务场景调整。

六、总结

本篇介绍了如何基于 Redis 的特性来实现一个分布式锁,并基于 Jedis 库提供了一个分布式锁的示例,呈现了其关键 API 的用法;此示例尚未达到生产级可用,如异常、可重入、可重试、超时控制等功能都未补全,计划在下一篇介绍完 redlock 之后,再介绍一个健壮的分布式锁客户端要如何抽象设计,如何适配 ZK 、Redis 、etcd 。

分布式锁系列内容规划如下,本篇是第 4 篇:

  1. 《分布式锁上-初探
  2. 《分布式锁中-基于 Zookeeper 的实现是怎样》
  3. 《分布式锁中-基于 etcd 的实现很优雅》
  4. 《分布式锁中-基于 Redis 的实现需避坑 - Jedis 篇》(本篇)
  5. 《分布式锁中-基于 Redis 的实现很多样 - Redission 篇》(写作中)
  6. 《分布式锁中-多维度的对比各种分布式锁实现》(写作中)
  7. 《分布式锁下-分布式锁客户端的抽象、适配与加固》(写作中)

七、最后说一句(请关注,莫错过)

如果这篇文章对您有所帮助,或者有所启发的话,帮忙扫描下发二维码关注一下,关注公众号:【 架构染色 】,进行交流和学习。您的支持是我坚持写作最大的动力。


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
2月前
|
NoSQL Java Redis
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
Redis分布式锁在高并发场景下是重要的技术手段,但其实现过程中常遇到五大深坑:**原子性问题**、**连接耗尽问题**、**锁过期问题**、**锁失效问题**以及**锁分段问题**。这些问题不仅影响系统的稳定性和性能,还可能导致数据不一致。尼恩在实际项目中总结了这些坑,并提供了详细的解决方案,包括使用Lua脚本保证原子性、设置合理的锁过期时间和使用看门狗机制、以及通过锁分段提升性能。这些经验和技巧对面试和实际开发都有很大帮助,值得深入学习和实践。
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
|
24天前
|
存储 NoSQL Java
使用lock4j-redis-template-spring-boot-starter实现redis分布式锁
通过使用 `lock4j-redis-template-spring-boot-starter`,我们可以轻松实现 Redis 分布式锁,从而解决分布式系统中多个实例并发访问共享资源的问题。合理配置和使用分布式锁,可以有效提高系统的稳定性和数据的一致性。希望本文对你在实际项目中使用 Redis 分布式锁有所帮助。
70 5
|
27天前
|
NoSQL Java 数据处理
基于Redis海量数据场景分布式ID架构实践
【11月更文挑战第30天】在现代分布式系统中,生成全局唯一的ID是一个常见且重要的需求。在微服务架构中,各个服务可能需要生成唯一标识符,如用户ID、订单ID等。传统的自增ID已经无法满足在集群环境下保持唯一性的要求,而分布式ID解决方案能够确保即使在多个实例间也能生成全局唯一的标识符。本文将深入探讨如何利用Redis实现分布式ID生成,并通过Java语言展示多个示例,同时分析每个实践方案的优缺点。
61 8
|
1月前
|
NoSQL Redis
Redis分布式锁如何实现 ?
Redis分布式锁通过SETNX指令实现,确保仅在键不存在时设置值。此机制用于控制多个线程对共享资源的访问,避免并发冲突。然而,实际应用中需解决死锁、锁超时、归一化、可重入及阻塞等问题,以确保系统的稳定性和可靠性。解决方案包括设置锁超时、引入Watch Dog机制、使用ThreadLocal绑定加解锁操作、实现计数器支持可重入锁以及采用自旋锁思想处理阻塞请求。
59 16
|
1月前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
43 5
|
8天前
|
存储 缓存 NoSQL
解决Redis缓存数据类型丢失问题
解决Redis缓存数据类型丢失问题
132 85
|
2月前
|
消息中间件 缓存 NoSQL
Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。
【10月更文挑战第4天】Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。随着数据增长,有时需要将 Redis 数据导出以进行分析、备份或迁移。本文详细介绍几种导出方法:1)使用 Redis 命令与重定向;2)利用 Redis 的 RDB 和 AOF 持久化功能;3)借助第三方工具如 `redis-dump`。每种方法均附有示例代码,帮助你轻松完成数据导出任务。无论数据量大小,总有一款适合你。
84 6
|
5天前
|
缓存 监控 NoSQL
Redis经典问题:缓存穿透
本文详细探讨了分布式系统和缓存应用中的经典问题——缓存穿透。缓存穿透是指用户请求的数据在缓存和数据库中都不存在,导致大量请求直接落到数据库上,可能引发数据库崩溃或性能下降。文章介绍了几种有效的解决方案,包括接口层增加校验、缓存空值、使用布隆过滤器、优化数据库查询以及加强监控报警机制。通过这些方法,可以有效缓解缓存穿透对系统的影响,提升系统的稳定性和性能。
|
1月前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题