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级别的加锁,多服务部署的话,我们现在的代码存在的问题。

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


目录
相关文章
|
8月前
|
存储 负载均衡 NoSQL
【赵渝强老师】Redis Cluster分布式集群
Redis Cluster是Redis的分布式存储解决方案,通过哈希槽(slot)实现数据分片,支持水平扩展,具备高可用性和负载均衡能力,适用于大规模数据场景。
569 2
|
8月前
|
存储 缓存 NoSQL
【📕分布式锁通关指南 12】源码剖析redisson如何利用Redis数据结构实现Semaphore和CountDownLatch
本文解析 Redisson 如何通过 Redis 实现分布式信号量(RSemaphore)与倒数闩(RCountDownLatch),利用 Lua 脚本与原子操作保障分布式环境下的同步控制,帮助开发者更好地理解其原理与应用。
641 6
|
7月前
|
存储 NoSQL 前端开发
Redis专题-实战篇一-基于Session和Redis实现登录业务
本项目基于SpringBoot实现黑马点评系统,涵盖Session与Redis两种登录方案。通过验证码登录、用户信息存储、拦截器校验等流程,解决集群环境下Session不共享问题,采用Redis替代Session实现数据共享与自动续期,提升系统可扩展性与安全性。
449 3
Redis专题-实战篇一-基于Session和Redis实现登录业务
|
7月前
|
存储 缓存 NoSQL
Redis专题-实战篇二-商户查询缓存
本文介绍了缓存的基本概念、应用场景及实现方式,涵盖Redis缓存设计、缓存更新策略、缓存穿透问题及其解决方案。重点讲解了缓存空对象与布隆过滤器的使用,并通过代码示例演示了商铺查询的缓存优化实践。
334 1
Redis专题-实战篇二-商户查询缓存
|
7月前
|
NoSQL Java 调度
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
分布式锁是分布式系统中用于同步多节点访问共享资源的机制,防止并发操作带来的冲突。本文介绍了基于Spring Boot和Redis实现分布式锁的技术方案,涵盖锁的获取与释放、Redis配置、服务调度及多实例运行等内容,通过Docker Compose搭建环境,验证了锁的有效性与互斥特性。
656 0
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
|
7月前
|
缓存 NoSQL 关系型数据库
Redis缓存和分布式锁
Redis 是一种高性能的键值存储系统,广泛用于缓存、消息队列和内存数据库。其典型应用包括缓解关系型数据库压力,通过缓存热点数据提高查询效率,支持高并发访问。此外,Redis 还可用于实现分布式锁,解决分布式系统中的资源竞争问题。文章还探讨了缓存的更新策略、缓存穿透与雪崩的解决方案,以及 Redlock 算法等关键技术。
|
9月前
|
存储 缓存 NoSQL
Redis核心数据结构与分布式锁实现详解
Redis 是高性能键值数据库,支持多种数据结构,如字符串、列表、集合、哈希、有序集合等,广泛用于缓存、消息队列和实时数据处理。本文详解其核心数据结构及分布式锁实现,帮助开发者提升系统性能与并发控制能力。
|
数据采集 存储 数据可视化
分布式爬虫框架Scrapy-Redis实战指南
本文介绍如何使用Scrapy-Redis构建分布式爬虫系统,采集携程平台上热门城市的酒店价格与评价信息。通过代理IP、Cookie和User-Agent设置规避反爬策略,实现高效数据抓取。结合价格动态趋势分析,助力酒店业优化市场策略、提升服务质量。技术架构涵盖Scrapy-Redis核心调度、代理中间件及数据解析存储,提供完整的技术路线图与代码示例。
1589 0
分布式爬虫框架Scrapy-Redis实战指南
|
NoSQL Java 中间件
【📕分布式锁通关指南 02】基于Redis实现的分布式锁
本文介绍了从单机锁到分布式锁的演变,重点探讨了使用Redis实现分布式锁的方法。分布式锁用于控制分布式系统中多个实例对共享资源的同步访问,需满足互斥性、可重入性、锁超时防死锁和锁释放正确防误删等特性。文章通过具体示例展示了如何利用Redis的`setnx`命令实现加锁,并分析了简化版分布式锁存在的问题,如锁超时和误删。为了解决这些问题,文中提出了设置锁过期时间和在解锁前验证持有锁的线程身份的优化方案。最后指出,尽管当前设计已解决部分问题,但仍存在进一步优化的空间,将在后续章节继续探讨。
1580 131
【📕分布式锁通关指南 02】基于Redis实现的分布式锁
|
11月前
|
数据采集 存储 NoSQL
基于Scrapy-Redis的分布式景点数据爬取与热力图生成
基于Scrapy-Redis的分布式景点数据爬取与热力图生成
818 67