redis实战---分布式锁--单机篇

redis分布式锁

  • 故事背景
  • 什么是Redis分布式锁
  • 业务场景
    • 未加任何锁的代码
  • 单机情况下JVM级别加锁
  • 多服务部署
  • 总结提升

故事背景

本篇文章是redis实战系列的第二篇文章。本章的主要内容是Redis分布式锁的相关知识。本篇文章将告诉你什么是分布式锁,结合一个业务场景,先带大家看看,单机上是如何实现锁功能的。学完本篇,你可以了解到什么是锁,为什么要加锁。

什么是Redis分布式锁

Redis分布式锁是一种基于Redis实现的分布式锁机制,它可以保证在分布式环境中,同一时刻只有一个客户端能够获取到锁,从而避免了多个客户端同时对同一资源进行修改的问题。
redis实战---分布式锁--单机篇_第1张图片
接下来我将结合一个秒杀的例子讲述如果实现Redis的分布式锁。

业务场景

秒杀场景是一个非常经典的需要使用锁的场景。
假设有一个商品限时秒杀的业务场景,多个用户同时在秒杀开始时间内尝试购买该商品,但是该商品数量有限,只有一定数量的用户可以购买成功,其他用户则购买失败。
为了保证秒杀的公平性与真确性,这个时候我们就要通过来对商品的数量进行访问

未加任何锁的代码

结合上面的业务场景,我们来先来实现一个未加任何锁的代码,简单实现一下这个小需求:

  1. 首先在redis里添加了 key值为 stock value值 为 200 的数据,模拟我们要秒杀的商品数量为200
    redis实战---分布式锁--单机篇_第2张图片
    2.编写业务逻辑代码
@RestController
@RequestMapping("/test")
public class IndexController {

    // 自动注入 StringRedisTemplate 对象
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    // 处理 HTTP GET 请求路径是 /test/lock
    @GetMapping("lock")
    public String deductStock() {
        // 获取当前库存
        String stock1 = stringRedisTemplate.opsForValue().get("stock");
        if( stock1 == null){
            System.out.println("秒杀未开始");
            return "end";
        }

        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
        if (stock > 0) {
            // 扣减库存
            int realStock = stock - 1;
            // 更新库存
            stringRedisTemplate.opsForValue().set("stock", realStock + "");
            System.out.println("扣减成功,剩余的库存为:" + realStock);

        } else {
            System.out.println("扣减失败,库存不足");
        }
        return "end";
    }

}


上述是根据我们的业务进行的一个简单的实现,在这个实现里,未对代码进行加锁。这段代码将会出现很经典的超卖问题。我们来压测一下接口,看一下效果
3. 接口压测,模拟并发
redis实战---分布式锁--单机篇_第3张图片
在这里我们使用的ApiPost进行一键压测。让我们一起来看一下结果吧
redis实战---分布式锁--单机篇_第4张图片
我们发现,50个请求进来之后,如果是正常的情况下,是应该减少50个库存,每个请求获得1个商品。可以根据结果看,我们的50个请求获得了5个商品。同一个商品卖给了多个用户。列如 195号商品同时卖给了10个人。
那么我们该如何去解决这个问题呢?

单机情况下JVM级别加锁

首先我们来看一下,如果是单机(项目只部署在一台机器上),如何给我们的代码加锁,解决上述问题。
使用 synchronized 进行jvm级别加锁。

  1. 代码
synchronized (this){
            // 获取当前库存
            String stock1 = stringRedisTemplate.opsForValue().get("stock");
            if( stock1 == null){
                System.out.println("秒杀未开始");
                return "end";
            }

            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                // 扣减库存
                int realStock = stock - 1;
                // 更新库存
                stringRedisTemplate.opsForValue().set("stock", realStock + "");
                System.out.println("扣减成功,剩余的库存为:" + realStock);

            } else {
                System.out.println("扣减失败,库存不足");
            }
        }

代码非常的简单,使用 synchronized 关键字,将我们的业务逻辑进行包裹即可。
synchronized,保证同一时刻只有一个线程执行被synchronized修饰的代码块或方法,从而避免多个线程同时对共享资源进行修改而导致的数据不一致的问题。
2. 运行结果
redis实战---分布式锁--单机篇_第5张图片
从结果上来看,通过synchronized 可以在jvm级别上进行上锁。但是我们实际的生产环境中,很少有部署单机服务的。如果我们部署了多个服务,那么通过synchronized 是肯定无法影响另一条机器上的请求的。

多服务部署

redis实战---分布式锁--单机篇_第6张图片
假设我们,部署了两个服务,部署在tomcat1和tomcat2上,使用nginx做负载。此时仅仅通过synchronized 只能保持 tomcat1自己本身。tomcat2自己本身的数据被锁住。如果两个服务同时提供服务,仍然会产生我们上述的超卖问题。

总结提升

本文我们主要讲了锁的概念,为什么要加锁,单机上jvm级别的加锁,多服务部署的话,我们现在的代码存在的问题。接下来我会讲解如何解决我们这次遗留的问题,在分布式环境下,如何加锁,如何解决可能会存在的问题。
专栏地址

你可能感兴趣的:(redis实战,redis,分布式,缓存)