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

简介: redis实战---分布式锁--单机篇

redis分布式锁

八:总结提升

一:故事背景

本篇文章是redis实战系列的第二篇文章。

本章的主要内容是Redis分布式锁的相关知识。本篇文章将告诉你什么是分布式锁,结合一个业务场景,先带大家看看,单机上是如何实现锁功能的。

学完本篇,你可以了解到什么是锁,为什么要加锁。

二:什么是Redis分布式锁

Redis的分布式锁是一种用于协调分布式系统并保护共享资源的机制。它利用Redis的原子操作和单线程执行特性来保证多个进程或线程之间的互斥性。它可以保证在分布式环境中,同一时刻只有一个客户端能够获取到锁,从而避免了多个客户端同时对同一资源进行修改的问题。


三:Redis分布式锁的作用

高可用性:Redis 分布式锁可以使用 Redis 集群或者 Redis Sentinel 进行部署,保证了高可用性和可扩展性。

可重入性:可以让同一个客户端重复获得锁,避免了同一个客户端重复执行代码的问题。

自动过期:可以设置锁的过期时间,如果锁没有及时释放,就会自动过期,避免了死锁的问题。

支持阻塞和非阻塞:可以根据需要选择阻塞式或者非阻塞式的锁。

高性能:使用 Redis 自带的原子操作实现锁的获取和释放,具有高性能和高并发性。

四:业务场景

接下来我将结合一个秒杀的例子讲述如果实现Redis的分布式锁。

秒杀场景是一个非常经典的需要使用锁的场景。


假设有一个商品限时秒杀的业务场景,多个用户同时在秒杀开始时间内尝试购买该商品,但是该商品数量有限,只有一定数量的用户可以购买成功,其他用户则购买失败。

为了保证秒杀的公平性与真确性,这个时候我们就要通过锁来对商品的数量进行访问

五:代码实现

5.1 未加任何锁

结合上面的业务场景,我们来先来实现一个未加任何锁的代码,简单实现一下这个小需求,并且分析它存在的问题,这样可以更好的帮助我们理解为什么要加锁。


5.1.1 数据准备

首先在redis里添加了 key值为 stock value值 为 200 的数据,模拟我们要秒杀的商品数量为200。

5.1.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";
    }
}

上述是根据我们的业务进行的一个简单的实现,在这个实现里,未对代码进行加锁。如果在并发请求的时候,这段代码将会出现很经典的超卖问题。

5.1.3 接口压测,模拟并发情况

让我们来压测一下接口,看一下对应的效果在这里我们使用的ApiPost进行一键压测。发送了50个请求,让我们来一起看看请求的结果。

5.1.4 压测结果5.1.5 压测问题

我们发现,50个请求进来之后,如果是正常的情况下,是应该减少50个库存,每个请求获得1个商品。

可以根据结果看,我们的50个请求获得了5个商品。同一个商品卖给了多个用户。列如 195号商品同时卖给了10个人。

那么我们该如何去解决这个问题呢?

六:单机情况下JVM级别加锁

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


6.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修饰的代码块或方法,从而避免多个线程同时对共享资源进行修改而导致的数据不一致的问题。

6.2 运行结果
6.3 结果分析

从结果上来看,通过synchronized 可以在jvm级别上进行上锁。但是我们实际的生产环境中,很少有部署单机服务的。如果我们部署了多个服务,那么通过synchronized 是肯定无法影响另一条机器上的请求的。

七:多服务部署

7.1 图像展示
7.2 问题分析

假设我们,部署了两个服务,部署在tomcat1和tomcat2上,使用nginx做负载。此时仅仅通过synchronized 只能保持 tomcat1自己本身。tomcat2自己本身的数据被锁住。如果两个服务同时提供服务,仍然会产生我们上述的超卖问题。


八:总结提升

本文我们主要讲了锁的概念,为什么要加锁,单机上jvm级别的加锁,多服务部署的话,我们现在的代码存在的问题。

接下来我会讲解如何解决我们这次遗留的问题,在分布式环境下,如何加锁,如何解决可能会存在的问题。


目录
相关文章
|
7月前
|
存储 负载均衡 NoSQL
【赵渝强老师】Redis Cluster分布式集群
Redis Cluster是Redis的分布式存储解决方案,通过哈希槽(slot)实现数据分片,支持水平扩展,具备高可用性和负载均衡能力,适用于大规模数据场景。
499 2
|
7月前
|
存储 缓存 NoSQL
【📕分布式锁通关指南 12】源码剖析redisson如何利用Redis数据结构实现Semaphore和CountDownLatch
本文解析 Redisson 如何通过 Redis 实现分布式信号量(RSemaphore)与倒数闩(RCountDownLatch),利用 Lua 脚本与原子操作保障分布式环境下的同步控制,帮助开发者更好地理解其原理与应用。
453 6
|
6月前
|
存储 NoSQL 前端开发
Redis专题-实战篇一-基于Session和Redis实现登录业务
本项目基于SpringBoot实现黑马点评系统,涵盖Session与Redis两种登录方案。通过验证码登录、用户信息存储、拦截器校验等流程,解决集群环境下Session不共享问题,采用Redis替代Session实现数据共享与自动续期,提升系统可扩展性与安全性。
399 3
Redis专题-实战篇一-基于Session和Redis实现登录业务
|
6月前
|
存储 缓存 NoSQL
Redis专题-实战篇二-商户查询缓存
本文介绍了缓存的基本概念、应用场景及实现方式,涵盖Redis缓存设计、缓存更新策略、缓存穿透问题及其解决方案。重点讲解了缓存空对象与布隆过滤器的使用,并通过代码示例演示了商铺查询的缓存优化实践。
296 1
Redis专题-实战篇二-商户查询缓存
|
6月前
|
NoSQL Java 调度
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
分布式锁是分布式系统中用于同步多节点访问共享资源的机制,防止并发操作带来的冲突。本文介绍了基于Spring Boot和Redis实现分布式锁的技术方案,涵盖锁的获取与释放、Redis配置、服务调度及多实例运行等内容,通过Docker Compose搭建环境,验证了锁的有效性与互斥特性。
543 0
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
|
6月前
|
缓存 NoSQL 关系型数据库
Redis缓存和分布式锁
Redis 是一种高性能的键值存储系统,广泛用于缓存、消息队列和内存数据库。其典型应用包括缓解关系型数据库压力,通过缓存热点数据提高查询效率,支持高并发访问。此外,Redis 还可用于实现分布式锁,解决分布式系统中的资源竞争问题。文章还探讨了缓存的更新策略、缓存穿透与雪崩的解决方案,以及 Redlock 算法等关键技术。
|
存储 缓存 NoSQL
Redis实战之入门进阶到精通
Redis 是一个远程内存数据库,它不仅性能强劲,而且还具有复制特性以及为解决问题而生的独一无二的数据模型。Redis 提供了 5 种不同类型的数据结构,各式各样的问题都可以很自然地映射到这些数据结构上:Redis 的数据结构致力于帮助用户解决问题,而不会像其他数据库那样,要求用户扭曲问题来适应数据库。除此之外,通过复制、持久化(persistence)和客户端分片(client-side sharding)等特性,用户可以很方便地将 Redis 扩展成一个能够包含数百 GB 数据、每秒处理上百万次请求的系统。
Redis实战之入门进阶到精通
|
存储 NoSQL Java
当Java遇到Redis:Jedis实战入门
Redis是一个开源,高级的键值存储和一个适用的解决方案,用于构建高性能,可扩展的Web应用程序。本文将概要介绍Redis的特性和语法,并以实例代码的形式介绍如何通过Jedis在java语言环境下控制Redis,帮助各位读者快速入门。
1863 0