Redis 官方文档

点击查看 Redis 中文官方文档

列表模式 http://redis.cn/topics/

举个案例

高并发秒杀场景中,看一下一段代码,会发生哪些问题?

@Slf4j
@RestController
public class GoodController {private static final String KEY = "goods:001";@Autowiredprivate StringRedisTemplate stringRedisTemplate;@RequestMapping("/goods")public String buyGoods() {String result = stringRedisTemplate.opsForValue().get(KEY);int num = Objects.isNull(result) ? 0 : Integer.parseInt(result);if (num > 0) {int realNum = num - 1;stringRedisTemplate.opsForValue().set(KEY,Integer.toString(realNum));log.info(">>>>>>>>>>你已经成功秒杀了商品,还剩余{}件",realNum);return "你已经成功秒杀了商品,还剩余"+realNum+"件";} log.info(">>>>>>>>>>活动已经售罄,欢迎下次光临.");return "活动已经售罄,欢迎下次光临";}
}

肯定会发生超卖(重复卖同一件商品)问题,所以这里可以加锁保证数据安全性,可以使用 Synchornized、ReentantLock 锁,但是推荐使用 ReentantLock 锁,它有一个 tryLock() 方法可以尝试加锁,不会死等。从而可以做一些自己的业务操作,改进之后如下:

 @RequestMapping("/goods2")public String buyGoods() {if (lock.tryLock()) {try {String result = stringRedisTemplate.opsForValue().get(KEY);int num = Objects.isNull(result) ? 0 : Integer.parseInt(result);if (num > 0) {int realNum = num - 1;stringRedisTemplate.opsForValue().set(KEY, Integer.toString(realNum));log.info(">>>>>>>>>>你已经成功秒杀了商品,还剩余{}件", realNum);return "你已经成功秒杀了商品,还剩余" + realNum + "件";}log.info(">>>>>>>>>>活动已经售罄,欢迎下次光临.");return "活动已经售罄,欢迎下次光临";} finally {lock.unlock();}} else {// do_something....}return "程序结束";}

测试效果如下:

2022-09-14 11:10:26.028  INFO 92342 --- [o-9292-exec-120] com.gwm.cloud.redislock.GoodController2  : >>>>>>>>>>你已经成功秒杀了商品,还剩余9件
2022-09-14 11:10:26.125  INFO 92342 --- [o-9292-exec-185] com.gwm.cloud.redislock.GoodController2  : >>>>>>>>>>你已经成功秒杀了商品,还剩余8件
2022-09-14 11:10:26.148  INFO 92342 --- [o-9292-exec-109] com.gwm.cloud.redislock.GoodController2  : >>>>>>>>>>你已经成功秒杀了商品,还剩余7件
2022-09-14 11:10:26.157  INFO 92342 --- [o-9292-exec-179] com.gwm.cloud.redislock.GoodController2  : >>>>>>>>>>你已经成功秒杀了商品,还剩余6件
2022-09-14 11:10:26.183  INFO 92342 --- [o-9292-exec-120] com.gwm.cloud.redislock.GoodController2  : >>>>>>>>>>你已经成功秒杀了商品,还剩余5件
2022-09-14 11:10:26.189  INFO 92342 --- [io-9292-exec-93] com.gwm.cloud.redislock.GoodController2  : >>>>>>>>>>你已经成功秒杀了商品,还剩余4件
2022-09-14 11:10:26.192  INFO 92342 --- [io-9292-exec-74] com.gwm.cloud.redislock.GoodController2  : >>>>>>>>>>你已经成功秒杀了商品,还剩余3件
2022-09-14 11:10:26.198  INFO 92342 --- [io-9292-exec-67] com.gwm.cloud.redislock.GoodController2  : >>>>>>>>>>你已经成功秒杀了商品,还剩余2件
2022-09-14 11:10:26.203  INFO 92342 --- [o-9292-exec-192] com.gwm.cloud.redislock.GoodController2  : >>>>>>>>>>你已经成功秒杀了商品,还剩余1件
2022-09-14 11:10:26.207  INFO 92342 --- [io-9292-exec-43] com.gwm.cloud.redislock.GoodController2  : >>>>>>>>>>你已经成功秒杀了商品,还剩余0件
2022-09-14 11:10:26.210  INFO 92342 --- [o-9292-exec-110] com.gwm.cloud.redislock.GoodController2  : >>>>>>>>>>活动已经售罄,欢迎下次光临.
2022-09-14 11:10:26.212  INFO 92342 --- [io-9292-exec-89] com.gwm.cloud.redislock.GoodController2  : >>>>>>>>>>活动已经售罄,欢迎下次光临.

以上虽然能够解决单机在 Redis 上的安全行,但是如果是分布式微服务就不能保证了,所以最终还是要使用 Redis 来做分布式锁。

那么这里就是用 Nginx 做负载均衡转发。

安装和配置 nginx 请看:nginx 安装 nginx配置

重新修改后代码如下:

    @RequestMapping("/goods4")public String buyGoods() {// 加上分布式锁String randStr = UUID.randomUUID().toString();Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_KEY, randStr);if (!flag) {return "抢锁失败,请进行重试...";}String result = stringRedisTemplate.opsForValue().get(KEY);int num = Objects.isNull(result) ? 0 : Integer.parseInt(result);if (num > 0) {int realNum = num - 1;stringRedisTemplate.opsForValue().set(KEY, Integer.toString(realNum));log.info(">>>>>>>>>>你已经成功秒杀了商品,还剩余{}件,serverPort={}", realNum,serverPort);// 正常执行业务之后直接释放锁stringRedisTemplate.delete(REDIS_KEY);return "你已经成功秒杀了商品,还剩余" + realNum + "件"+serverPort;}log.info(">>>>>>>>>>活动已经售罄,欢迎下次光临."+serverPort);return "活动已经售罄,欢迎下次光临"+serverPort;}

改成用分布式锁来保证数据安全性,但是这里还有一个问题,就是如果业务执行不正常或者不没有释放锁,就会导致所有请求执行不了,所以必须要把释放锁这个操作放到 finally 操作快里面执行,无论如何都要释放掉锁。

那么重新改进后的代码如下:

    @RequestMapping("/goods5")public String buyGoods() {try {// 加上分布式锁String randStr = UUID.randomUUID().toString();Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_KEY, randStr);if (!flag) {return "抢锁失败,请进行重试...";}String result = stringRedisTemplate.opsForValue().get(KEY);int num = Objects.isNull(result) ? 0 : Integer.parseInt(result);if (num > 0) {int realNum = num - 1;stringRedisTemplate.opsForValue().set(KEY, Integer.toString(realNum));log.info(">>>>>>>>>>你已经成功秒杀了商品,还剩余{}件,serverPort={}", realNum,serverPort);return "你已经成功秒杀了商品,还剩余" + realNum + "件"+serverPort;}log.info(">>>>>>>>>>活动已经售罄,欢迎下次光临."+serverPort);} finally {// 正常执行业务之后直接释放锁stringRedisTemplate.delete(REDIS_KEY);}return "活动已经售罄,欢迎下次光临"+serverPort;}

但是发现这都是正常操作所以可以保证正常释放掉锁,此时如果程序刚好执行到 try 代码块的时候,还没来得及执行 finally 语句块,然后就停电了关机了,那么此时也就释放不了这把锁。

所以这里还需要对锁加入失效时间,保证宕机之后能够正常释放掉锁。修改过后的代码如:

    @RequestMapping("/goods5")public String buyGoods() {try {// 加上分布式锁String randStr = UUID.randomUUID().toString();Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_KEY, randStr);// 加上锁过期时间stringRedisTemplate.expire(REDIS_KEY,30L, TimeUnit.SECONDS);if (!flag) {return "抢锁失败,请进行重试...";}String result = stringRedisTemplate.opsForValue().get(KEY);int num = Objects.isNull(result) ? 0 : Integer.parseInt(result);if (num > 0) {int realNum = num - 1;stringRedisTemplate.opsForValue().set(KEY, Integer.toString(realNum));log.info(">>>>>>>>>>你已经成功秒杀了商品,还剩余{}件,serverPort={}", realNum,serverPort);return "你已经成功秒杀了商品,还剩余" + realNum + "件"+serverPort;}log.info(">>>>>>>>>>活动已经售罄,欢迎下次光临."+serverPort);} finally {// 正常执行业务之后直接释放锁stringRedisTemplate.delete(REDIS_KEY);}return "活动已经售罄,欢迎下次光临"+serverPort;}

加上了过期时间,但是还是有个问题,那就是加锁和设置锁失效时间不是一个原子操作,如果加锁成功,然后又停电宕机了,没有给锁设置过期时间,那么锁又会释放不成功。

将加锁和设置锁失效时间两个步骤换成一个操作,如下所示:

@RequestMapping("/goods6")public String buyGoods() {try {String randStr = UUID.randomUUID().toString();// 加上分布式锁、设置锁过期时间Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_KEY, randStr,30L, TimeUnit.SECONDS);if (!flag) {return "抢锁失败,请进行重试...";}String result = stringRedisTemplate.opsForValue().get(KEY);int num = Objects.isNull(result) ? 0 : Integer.parseInt(result);if (num > 0) {int realNum = num - 1;stringRedisTemplate.opsForValue().set(KEY, Integer.toString(realNum));log.info(">>>>>>>>>>你已经成功秒杀了商品,还剩余{}件,serverPort={}", realNum,serverPort);return "你已经成功秒杀了商品,还剩余" + realNum + "件"+serverPort;}log.info(">>>>>>>>>>活动已经售罄,欢迎下次光临."+serverPort);} finally {// 正常执行业务之后直接释放锁stringRedisTemplate.delete(REDIS_KEY);}return "活动已经售罄,欢迎下次光临"+serverPort;}

现在已经保证了加锁的正确性了,但是还存在一个巨大的问题,就是误删问题,因为你给锁设置的超时时间是 30s,假设业务处理时间需要消耗 40s,那么此时第一个还在处理业务,超过了 30s,锁直接失效释放了,此时第二个线程就会加锁成功,等到第一个线程执行完之后,执行 finally 块释放锁,就把第二个线程的锁给释放了,这就是误删问题,非常严重。那么怎么解决呢?

是不是可以在释放前先获取到锁,然后判断这把锁是不是自己的呢?是自己的就可以释放,不是自己的就不能释放,修改之后如下:

    @RequestMapping("/goods6")public String buyGoods() {String randStr = UUID.randomUUID().toString();try {// 加上分布式锁、设置锁过期时间Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_KEY, randStr,30L, TimeUnit.SECONDS);if (!flag) {return "抢锁失败,请进行重试...";}String result = stringRedisTemplate.opsForValue().get(KEY);int num = Objects.isNull(result) ? 0 : Integer.parseInt(result);if (num > 0) {int realNum = num - 1;stringRedisTemplate.opsForValue().set(KEY, Integer.toString(realNum));log.info(">>>>>>>>>>你已经成功秒杀了商品,还剩余{}件,serverPort={}", realNum,serverPort);return "你已经成功秒杀了商品,还剩余" + realNum + "件"+serverPort;}log.info(">>>>>>>>>>活动已经售罄,欢迎下次光临."+serverPort);} finally {// 加上锁判断、是否允许释放条件if (stringRedisTemplate.opsForValue().get(REDIS_KEY).equals(randStr)) {// 正常执行业务之后直接释放锁stringRedisTemplate.delete(REDIS_KEY);}}return "活动已经售罄,欢迎下次光临"+serverPort;}

但是此时,判断锁和释放锁的步骤不是一个原子操作,还是可能存在时间先后的差异,也会导致误删锁的问题,所以需要保证判断和释放锁必须是一个原子操作,如下所示:

    @RequestMapping("/goods7")public String buyGoods() {String randStr = UUID.randomUUID().toString();try {// 加上分布式锁、设置锁过期时间Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_KEY, randStr,30L, TimeUnit.SECONDS);if (!flag) {return "抢锁失败,请进行重试...";}String result = stringRedisTemplate.opsForValue().get(KEY);int num = Objects.isNull(result) ? 0 : Integer.parseInt(result);if (num > 0) {int realNum = num - 1;stringRedisTemplate.opsForValue().set(KEY, Integer.toString(realNum));log.info(">>>>>>>>>>你已经成功秒杀了商品,还剩余{}件,serverPort={}", realNum,serverPort);return "你已经成功秒杀了商品,还剩余" + realNum + "件"+serverPort;}log.info(">>>>>>>>>>活动已经售罄,欢迎下次光临."+serverPort);} finally {// 通过 Lua 脚本删除分布式锁的 keyJedis jedis = RedisUtils.getJedis();String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +"then\n" +"    return redis.call(\"del\",KEYS[1])\n" +"else\n" +"    return 0\n" +"end";try {Object result = jedis.eval(script, Collections.singletonList(REDIS_KEY), Collections.singletonList(randStr));// 这里记得 toString() 否则不会相等if ("1".equals(result.toString())) {log.info(">>>>>>>>>del lock_key success!!!");} else {log.info(">>>>>>>>del lock_key error... ");}} finally {if (Objects.nonNull(jedis)) {// 关闭资源jedis.close();}}}return "活动已经售罄,欢迎下次光临"+serverPort;}

RedisUtils 工具类如下:


public class RedisUtils {private static JedisPool jedisPool = null;/*** 创建 Jedis 连接池*/static {JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();jedisPoolConfig.setMaxTotal(20);jedisPoolConfig.setMaxIdle(10);jedisPool = new JedisPool(jedisPoolConfig, "localhost", 6379);}/*** 从 JedisPool 中获取到 Jedis 对象*/public static Jedis getJedis() {if (Objects.nonNull(jedisPool)) {return jedisPool.getResource();}throw new RuntimeException("连接池创建失败...");}
}

使用到了传说中的 lua 脚本。点击查看 lua 脚本 或者 中文文档

if redis.call("get",KEYS[1]) == ARGV[1]
thenreturn redis.call("del",KEYS[1])
elsereturn 0
end

经过修改之后发现还是会存在一定问题,Redis 集群是 AP 模式,不像 Zookeeper 是 CP 模式,如下:

而我们的 Redis 有点像 Eureka 集群,如下:

因为 Redis 之间通过异步方式通信,所以如果 master 宕机了,并且此时 master 还没有把锁状态同步给从节点,异步复制失败,导致锁丢失。此时上面写的代码又不能保证数据安全性了,所以这个时候只能采用 RedLock,天生解决这种问题,RedLock 红锁的具体实现是交给 Redisson 、并且 Redisson 还是 Java 语言编写的。

    @RequestMapping("/goods7")public String buyGoods() {RLock lock = redisson.getLock(REDIS_KEY);// 加锁lock.lock();try {String result = stringRedisTemplate.opsForValue().get(KEY);int num = Objects.isNull(result) ? 0 : Integer.parseInt(result);if (num > 0) {int realNum = num - 1;stringRedisTemplate.opsForValue().set(KEY, Integer.toString(realNum));log.info(">>>>>>>>>>你已经成功秒杀了商品,还剩余{}件,serverPort={}", realNum,serverPort);return "你已经成功秒杀了商品,还剩余" + realNum + "件"+serverPort;}log.info(">>>>>>>>>>活动已经售罄,欢迎下次光临."+serverPort);} finally {// 直接调用 unlock() 解锁即可lock.unlock();}return "活动已经售罄,欢迎下次光临"+serverPort;}

但是这里还是存在一个问题,就是解锁不能解锁别人的锁,需要判断能不能解锁才可以解锁,修改之后如下:

@RequestMapping("/goods9")public String buyGoods() {// 直接上 RedissonRLock lock = redisson.getLock(REDIS_KEY);// 加锁lock.lock();try {String result = stringRedisTemplate.opsForValue().get(KEY);int num = Objects.isNull(result) ? 0 : Integer.parseInt(result);if (num > 0) {int realNum = num - 1;stringRedisTemplate.opsForValue().set(KEY, Integer.toString(realNum));log.info(">>>>>>>>>>你已经成功秒杀了商品,还剩余{}件,serverPort={}", realNum,serverPort);return "你已经成功秒杀了商品,还剩余" + realNum + "件"+serverPort;}log.info(">>>>>>>>>>活动已经售罄,欢迎下次光临."+serverPort);} finally {// 先判断自己是否能去释放这把锁if (lock.isLocked() && lock.isHeldByCurrentThread()) {// 直接调用 unlock() 解锁即可lock.unlock();}}return "活动已经售罄,欢迎下次光临"+serverPort;}

经过压测后数据正常,如下:

但是最终发现还是会存在一个非常严重的问题,就是你的锁失效时间具体要怎么填写?你怎么知道要设置多长的过期时间,这个是要和你的处理业务时间联系再一起,通常会经过 Jmeter 工具测试 qps,然后看平均耗时,但是这个还不是一个好的解决方案。

其实我们可以为这个过期时间续命,或者说是刷新过期时间,在你处理业务的时候,后台线程去刷新这个过期时间。

下面先插个小插曲,先来了解下 Redisson 的东西

Redisson 文档: http://redis.cn/topics/

Redis 分布式锁:http://redis.cn/topics/distlock.html

Github 地址: https://github.com/redisson/redisson

传统的基于 setnx 分布式锁有什么缺点,如下图示?

上述图片讲述了基于 setnx 传统的分布式锁的缺点,那么怎么解决呢?

Redis 中就提供了 RedLock 算法,用来实现基于多个实例的分布式锁,锁由多个实例维护,这样即使你有实例发生故障,锁变量依旧还存在,客户端还可以继续完成锁操作。RedLock 算法是实现高可靠用分布式锁的一种有效解决方案,可以在实际开发中应用。

在多主机群模式中,需要部署几台 Redis 服务器,由计算容错率公式推到:

N(需部署 Redis 台数) = 2 * X(宕机的 Redis 台数) + 1(奇数+1),

假设我么允许有 1 台机器宕机,那么最少部署 2 * 1+1 = 3 台 Redis 服务器就可以保证高可用集群
假设我么允许有 2 台机器宕机,那么最少部署 2 * 1+1 = 5 台 Redis 服务器就可以保证高可用集群

+1 的操作是用最少的开销做到高可用集群,+2 的话虽然也可以,但是你需要多准别一台服务器,实现的效果和 +1 实现的效果是一样的。

采用 docker 部署三台 Redis 服务器

docker 命令如下:

docker run -p 6381:6379 --name redis-master-1 -d redis:6.0.7
docker run -p 6382:6379 --name redis-master-2 -d redis:6.0.7
docker run -p 6383:6379 --name redis-master-3 -d redis:6.0.7

配置类如下:

@ConfigurationProperties(prefix = "spring.redis", ignoreUnknownFields = false)
@Data
public class RedisPropertiesAutoConfiguration {private int database;/*** 等待节点回复命令的时间。该时间从命令发送成功时开始计时*/private int timeout;private String password;private String mode;/*** 池配置*/private RedisPoolProperties pool;/*** 单机信息配置*/private RedisSingleProperties single;
}@Data
public class RedisPoolProperties {private int maxIdle;private int minIdle;private int maxActive;private int maxWait;private int connTimeout;private int soTimeout;/*** 池大小*/private  int size;
}@Data
public class RedisSingleProperties {private  String address1;private  String address2;private  String address3;
}

然后配置三个 Redisson 客户端,如下:


@Configuration
@EnableConfigurationProperties(RedisPropertiesAutoConfiguration.class)
public class CacheConfiguration {@AutowiredRedisPropertiesAutoConfiguration redisPropertiesAutoConfiguration;@BeanRedissonClient redissonClient1() {Config config = new Config();String node = redisPropertiesAutoConfiguration.getSingle().getAddress1();node = node.startsWith("redis://") ? node : "redis://" + node;SingleServerConfig serverConfig = config.useSingleServer().setAddress(node).setTimeout(redisPropertiesAutoConfiguration.getPool().getConnTimeout()).setConnectionPoolSize(redisPropertiesAutoConfiguration.getPool().getSize()).setConnectionMinimumIdleSize(redisPropertiesAutoConfiguration.getPool().getMinIdle());if (StringUtils.isNotBlank(redisPropertiesAutoConfiguration.getPassword())) {serverConfig.setPassword(redisPropertiesAutoConfiguration.getPassword());}return Redisson.create(config);}@BeanRedissonClient redissonClient2() {Config config = new Config();String node = redisPropertiesAutoConfiguration.getSingle().getAddress2();node = node.startsWith("redis://") ? node : "redis://" + node;SingleServerConfig serverConfig = config.useSingleServer().setAddress(node).setTimeout(redisPropertiesAutoConfiguration.getPool().getConnTimeout()).setConnectionPoolSize(redisPropertiesAutoConfiguration.getPool().getSize()).setConnectionMinimumIdleSize(redisPropertiesAutoConfiguration.getPool().getMinIdle());if (StringUtils.isNotBlank(redisPropertiesAutoConfiguration.getPassword())) {serverConfig.setPassword(redisPropertiesAutoConfiguration.getPassword());}return Redisson.create(config);}@BeanRedissonClient redissonClient3() {Config config = new Config();String node = redisPropertiesAutoConfiguration.getSingle().getAddress3();node = node.startsWith("redis://") ? node : "redis://" + node;SingleServerConfig serverConfig = config.useSingleServer().setAddress(node).setTimeout(redisPropertiesAutoConfiguration.getPool().getConnTimeout()).setConnectionPoolSize(redisPropertiesAutoConfiguration.getPool().getSize()).setConnectionMinimumIdleSize(redisPropertiesAutoConfiguration.getPool().getMinIdle());if (StringUtils.isNotBlank(redisPropertiesAutoConfiguration.getPassword())) {serverConfig.setPassword(redisPropertiesAutoConfiguration.getPassword());}return Redisson.create(config);}

最终代码如下:

@RequestMapping("/goods10")public String buyGoods() {// 直接上 RedissonRLock lock1 = redissonClient1.getLock(REDIS_KEY);RLock lock2 = redissonClient2.getLock(REDIS_KEY);RLock lock3 = redissonClient3.getLock(REDIS_KEY);RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);boolean isLockBoolean;try {// 开始加锁isLockBoolean = redLock.tryLock(3, 300, TimeUnit.SECONDS);if (isLockBoolean) {String result = stringRedisTemplate.opsForValue().get(KEY);int num = Objects.isNull(result) ? 0 : Integer.parseInt(result);if (num > 0) {int realNum = num - 1;stringRedisTemplate.opsForValue().set(KEY, Integer.toString(realNum));log.info(">>>>>>>>>>你已经成功秒杀了商品,还剩余{}件,serverPort={}", realNum,serverPort);return "你已经成功秒杀了商品,还剩余" + realNum + "件"+serverPort;}log.info(">>>>>>>>>>活动已经售罄,欢迎下次光临."+serverPort);}} catch (Exception e) {e.printStackTrace();}finally {// 先判断自己是否能去释放这把锁if (redLock.isLocked() && redLock.isHeldByCurrentThread()) {// 直接调用 unlock() 解锁即可redLock.unlock();}}return "活动已经售罄,欢迎下次光临"+serverPort;}

这样下面数据安全性保证已经提高到 99% 了。现在就剩下最后一个锁续命问题了。

分布式锁在 Redis 中存的数据格式如下所示:

在 Redisson 的实现中,它会额外开一个线程定期检查线程是否还持有这把锁,如果有则延迟锁过期时间,定期检查默认是每 1/3 的锁时间检查一次,如果锁还持有,那么就会刷新过期时间。而这个额外的线程就叫做 Watch Dog

摘取官网的一段话如下所示:


所以最后一个缓存锁续命问题 Redisson 也帮我们实现了,那么现在我们这个分布式锁应该算是比较完整的了。后续的 Redisson 源码分析请看另一篇文章。

所以实现分布式锁优先推荐 Redisson 去实现,使用传统的 setnx 需要自己解决很多问题。

Redis 分布式锁一步步优化过程相关推荐

  1. redis分布式锁原理与实现

    分布式锁原理 分布式锁,是控制分布式系统之间同步访问共享资源的一种方式.在分布式系统中,常常需要协调他们的动作.如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候, ...

  2. 从青铜到王者,带你完成Redis分布式锁的实现和优化

    0.分布式锁的常见面试题 Redis除了拿来做缓存,你还见过基于Redis的什么用法? Redis做分布式锁的时候有需要注意的问题? 如果是Redis是单点部署的,会带来什么问题? 那你准备怎么解决单 ...

  3. Redis 分布式锁的正确实现原理演化历程与 Redisson 实战总结

    Redis 分布式锁使用 SET 指令就可以实现了么?在分布式领域 CAP 理论一直存在. 分布式锁的门道可没那么简单,我们在网上看到的分布式锁方案可能是有问题的. 一步步带你深入分布式锁是如何一步步 ...

  4. Redis 分布式锁:从小白到大神方案都经历了什么?

    Redis 分布式锁使用 SET 指令就可以实现了么?在分布式领域 CAP 理论一直存在. 分布式锁的门道可没那么简单,我们在网上看到的分布式锁方案可能是有问题的. 「码哥」一步步带你深入分布式锁是如 ...

  5. 全面剖析redis分布式锁

    序言 今天一起学习下分布式锁,分布式锁常见于集群环境下,用于做一些单机锁无法解决的问题,比如扣减库存的场景,如果扣减库存的业务机器是多台部署的就会出现超卖现象(JAVA中常见的lock和Synchro ...

  6. Redis分布式锁使用不当,酿成一个重大事故,超卖了100瓶飞天茅台!!!

    点击关注公众号,Java干货及时送达 来源:juejin.cn/post/6854573212831842311 基于Redis使用分布式锁在当今已经不是什么新鲜事了. 本篇文章主要是基于我们实际项目 ...

  7. Redis 分布式锁使用不当,酿成一个重大事故,超卖了100瓶飞天茅台!!!

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 基于Redis使用分布式锁在当今已经不是什么新鲜事了. 本 ...

  8. 秒杀商品超卖事故:Redis分布式锁请慎用!

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者:浪漫先生 来源:juejin.im/post/6854573 ...

  9. 记一次由Redis分布式锁造成的重大事故,避免以后踩坑!

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者:浪漫先生 juejin.im/post/5f159cd8f2 ...

最新文章

  1. Linux 命令行小技巧《叹号的用处》
  2. 某大厂程序员哀叹:千万不要从大厂往小厂跳,后悔死了!小厂只会逼迫压榨,刚来就一个劲要产出!...
  3. #define与typedef的区别!
  4. 卸载 流程_一款适合于windows端的卸载神器 彻底清理残留软件
  5. ros melodic控制真实机械臂之等周期输出插补点
  6. Android消息推送(Android Push Notification)
  7. 计算机组成原理上机试卷,计算机组成原理试卷及答案
  8. Real-Time Rendering——18.5 Multiprocessing多处理
  9. 用MarkDown写PPT
  10. 摸鱼小组-冲刺日志(第二天)
  11. RI-TRP-DR2B 32mm 玻璃应答器|CID载码体标签在半导体行业重复利用之检测方法
  12. ftp的20 21端口和主动被动模式
  13. 基于JAVA彩票在线购买系统计算机毕业设计源码+系统+lw文档+部署
  14. 指数函数误差平方和matlab,数值分析与实验数学081 张燃 3080801119).doc
  15. 6.汇编语言显示、指令
  16. 企业如何做好邮件归档稽核
  17. Effective STL中文版:50条有效使用STL的经验(双色)
  18. 使用VB.net将PNG图片转成icon类型图标文件
  19. 数据链路层的封装-HDLC协议
  20. 小企鹅fcitx输入法导入搜狗scel词库方法

热门文章

  1. 腾讯又一重磅服务关闭!网友:9年了,还是没了……
  2. 仿360网站广告实现换肤特效
  3. Closed socket connection for client /39.103.162.230:56100 (no session established for client)
  4. 利用Python来刷排行榜!Python就是牛逼!
  5. 文件上传防止攻击的操作
  6. 阿里笔试题破解八卦阵问题
  7. Docker kill 1无效
  8. Deepin 15.9 下安装Nvidia驱动 RTX 2080显卡
  9. Originality Self-portrait 创意自画像——p5.js
  10. 中考计算机应用,计算机应用类专业计算机综合中考试题(259、260).doc