redis实战---乐观锁与悲观锁

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: redis实战---乐观锁与悲观锁

乐观锁和悲观锁

七:总结提升

一:故事背景

最近一直在研究Redis,今天学习到了乐观锁与悲观锁的部分,在这里进行总结。


二:概念

Redis是一个内存中的键值存储系统,支持多种数据结构,如字符串、哈希、列表等。

Redis提供了两种锁机制,即乐观锁和悲观锁。


三:乐观锁

3.1 什么是乐观锁

乐观锁是一种乐观的并发控制策略,它认为数据在大多数情况下不会被其他线程占用,因此每次需要修改数据时,都不会获取锁,而是直接进行修改。

在Redis中,可以通过WATCH和CAS命令来实现乐观锁,WATCH命令用于监视一个或多个键,CAS命令用于检查并更新键的值。

3.2 举例说明

例如,假设有一个计数器键counter,多个客户端都需要对其进行操作。使用乐观锁的方式,可以在每个客户端执行操作之前,先通过WATCH命令监视counter键:

WATCH counter
current_count = GET counter
new_count = current_count + 1
MULTI
SET counter new_count
EXEC

然后,在EXEC命令执行之前,使用GET命令再次获取counter键的值,并将其与之前获取的值进行比较。如果值相等,就说明期间没有其他客户端对counter键进行了修改,此时可以使用CAS命令将新值设置到counter键中。如果值不相等,则说明期间有其他客户端对counter键进行了修改,需要重新执行操作。

GET counter

四:悲观锁

4.1 什么是悲观锁

悲观锁是一种悲观的并发控制策略,它认为数据在大多数情况下都会被其他线程占用,因此每次需要修改数据时,都会先获取锁,确保在修改期间没有其他线程可以访问该数据。在Redis中,可以通过WATCH命令来实现悲观锁,该命令可以监视一个或多个键,如果在事务执行期间有任何被监视键的值发生了变化,整个事务会被回滚。


4.2 举例说明

还是上文的例子

WATCH counter
current_count = GET counter
new_count = current_count + 1
MULTI
SET counter new_count
EXEC

如果在执行事务期间,有其他客户端修改了counter键,那么整个事务会被回滚,需要重新执行。


悲观锁的优点在于它可以确保数据的一致性,但缺点在于它需要获取锁,可能会引起线程的阻塞,影响并发性能。


五:乐观锁代码示例

5.1 业务背景

假设有一个电商平台,用户可以在平台上购买商品。为了保证数据的一致性,我们可以使用Redis的乐观锁来实现商品库存的扣减。


5.2 数据结构

首先,我们需要在Redis中保存每个商品的库存信息,使用hash数据结构来保存,例如:

HSET stock sku001 100

5.3 业务流程

然后,在业务逻辑中,当用户购买一个商品时,需要执行以下步骤:


1.使用WATCH命令监视商品库存键,例如stock:sku001;

2.使用GET命令获取当前商品库存数量;

3.检查商品库存是否足够,如果不足,直接返回错误信息;

4.计算新的库存数量,并使用MULTI命令开启一个事务;

5.使用HSET命令将新的库存数量保存到Redis中;

执行事务,如果在执行期间有其他客户端修改了商品库存,会回滚事务,需要重新执行。

5.4 代码实现

下面是使用Spring Boot实现的示例代码:

@Service
public class OrderService {
    private final RedisTemplate<String, Integer> redisTemplate;
    @Autowired
    public OrderService(RedisTemplate<String, Integer> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    public void placeOrder(String sku, int quantity) {
        String stockKey = "stock:" + sku;
        while (true) {
            // 监视商品库存键,以便在事务开始前检测是否有其他客户端修改了库存
            redisTemplate.watch(stockKey);
            // 获取当前库存数量
            int currentStock = redisTemplate.opsForHash().get(stockKey, sku);
            // 检查库存是否足够
            if (currentStock < quantity) {
                // 库存不足,放弃事务并抛出异常
                redisTemplate.unwatch();
                throw new RuntimeException("Out of stock");
            }
            // 计算新的库存数量
            int newStock = currentStock - quantity;
            // 开始事务
            redisTemplate.multi();
            // 更新库存数量
            redisTemplate.opsForHash().put(stockKey, sku, newStock);
            // 提交事务
            List<Object> results = redisTemplate.exec();
            // 如果事务执行成功,则退出循环
            if (results != null) {
                break;
            }
            // 如果事务执行失败,则重试
        }
    }
}

在上面的代码中,我们使用RedisTemplate来操作Redis,其中watch方法用于监视商品库存键,opsForHash方法用于获取和修改商品库存的值,multi和exec方法用于开启和提交事务。


六:悲观锁示例

除了乐观锁,Redis还支持悲观锁,可以通过设置NX(Not Exist)或XX(Exist)标志来实现。


6.1 悲观锁流程

例如,当NX标志设置为true时,如果锁不存在,会返回OK,并创建一个锁;如果锁已经存在,会返回null,表示获取锁失败。反之,当XX标志设置为true时,如果锁已经存在,会返回OK,表示获取锁成功;如果锁不存在,会返回null,表示获取锁失败。


6.2 代码实现

下面是使用Spring Boot实现的悲观锁示例代码:

@Service
public class OrderService {
    private final RedisTemplate<String, String> redisTemplate;
    @Autowired
    public OrderService(RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    public void placeOrder(String sku, int quantity) {
        String lockKey = "lock:" + sku;
        // 尝试获取锁,如果锁已经存在,说明有其他线程正在执行相关操作
        Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "locked");
        if (!locked) {
            // 获取锁失败,抛出异常
            throw new RuntimeException("Unable to acquire lock");
        }
        // 设置锁的过期时间,防止锁被一直占用
        redisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);
        try {
            // 执行订单创建、扣减库存等操作
        } finally {
            // 释放锁
            redisTemplate.delete(lockKey);
        }
    }
}

在上面的代码中,我们使用setIfAbsent方法来尝试获取锁,如果锁已经存在,说明其他线程正在执行相关操作,此时会返回false,表示获取锁失败;否则,会返回true,表示获取锁成功。如果获取锁成功,我们会设置锁的过期时间,并执行相关操作,最后释放锁。


七:总结提升

需要注意的是,悲观锁一般适用于并发量不大的场景,如果并发量较高,容易导致性能问题。因此,在实际应用中,需要根据具体情况选择合适的锁策略。


目录
相关文章
|
2月前
|
存储 NoSQL 前端开发
Redis专题-实战篇一-基于Session和Redis实现登录业务
本项目基于SpringBoot实现黑马点评系统,涵盖Session与Redis两种登录方案。通过验证码登录、用户信息存储、拦截器校验等流程,解决集群环境下Session不共享问题,采用Redis替代Session实现数据共享与自动续期,提升系统可扩展性与安全性。
222 3
Redis专题-实战篇一-基于Session和Redis实现登录业务
|
2月前
|
存储 缓存 NoSQL
Redis专题-实战篇二-商户查询缓存
本文介绍了缓存的基本概念、应用场景及实现方式,涵盖Redis缓存设计、缓存更新策略、缓存穿透问题及其解决方案。重点讲解了缓存空对象与布隆过滤器的使用,并通过代码示例演示了商铺查询的缓存优化实践。
184 1
Redis专题-实战篇二-商户查询缓存
|
8月前
|
数据采集 存储 数据可视化
分布式爬虫框架Scrapy-Redis实战指南
本文介绍如何使用Scrapy-Redis构建分布式爬虫系统,采集携程平台上热门城市的酒店价格与评价信息。通过代理IP、Cookie和User-Agent设置规避反爬策略,实现高效数据抓取。结合价格动态趋势分析,助力酒店业优化市场策略、提升服务质量。技术架构涵盖Scrapy-Redis核心调度、代理中间件及数据解析存储,提供完整的技术路线图与代码示例。
840 0
分布式爬虫框架Scrapy-Redis实战指南
|
5月前
|
缓存 监控 NoSQL
Redis 实操要点:Java 最新技术栈的实战解析
本文介绍了基于Spring Boot 3、Redis 7和Lettuce客户端的Redis高级应用实践。内容包括:1)现代Java项目集成Redis的配置方法;2)使用Redisson实现分布式可重入锁与公平锁;3)缓存模式解决方案,包括布隆过滤器防穿透和随机过期时间防雪崩;4)Redis数据结构的高级应用,如HyperLogLog统计UV和GeoHash处理地理位置。文章提供了详细的代码示例,涵盖Redis在分布式系统中的核心应用场景,特别适合需要处理高并发、分布式锁等问题的开发场景。
383 41
|
5月前
|
缓存 NoSQL 算法
高并发秒杀系统实战(Redis+Lua分布式锁防超卖与库存扣减优化)
秒杀系统面临瞬时高并发、资源竞争和数据一致性挑战。传统方案如数据库锁或应用层锁存在性能瓶颈或分布式问题,而基于Redis的分布式锁与Lua脚本原子操作成为高效解决方案。通过Redis的`SETNX`实现分布式锁,结合Lua脚本完成库存扣减,确保操作原子性并大幅提升性能(QPS从120提升至8,200)。此外,分段库存策略、多级限流及服务降级机制进一步优化系统稳定性。最佳实践包括分层防控、黄金扣减法则与容灾设计,强调根据业务特性灵活组合技术手段以应对高并发场景。
1518 7
|
5月前
|
机器学习/深度学习 存储 NoSQL
基于 Flink + Redis 的实时特征工程实战:电商场景动态分桶计数实现
本文介绍了基于 Flink 与 Redis 构建的电商场景下实时特征工程解决方案,重点实现动态分桶计数等复杂特征计算。通过流处理引擎 Flink 实时加工用户行为数据,结合 Redis 高性能存储,满足推荐系统毫秒级特征更新需求。技术架构涵盖状态管理、窗口计算、Redis 数据模型设计及特征服务集成,有效提升模型预测效果与系统吞吐能力。
543 2
|
8月前
|
缓存 NoSQL Java
基于SpringBoot的Redis开发实战教程
Redis在Spring Boot中的应用非常广泛,其高性能和灵活性使其成为构建高效分布式系统的理想选择。通过深入理解本文的内容,您可以更好地利用Redis的特性,为应用程序提供高效的缓存和消息处理能力。
740 79
|
12月前
|
NoSQL 安全 测试技术
Redis游戏积分排行榜项目中通义灵码的应用实战
Redis游戏积分排行榜项目中通义灵码的应用实战
265 4
|
NoSQL 关系型数据库 MySQL
MySQL与Redis协同作战:优化百万数据查询的实战经验
【10月更文挑战第13天】 在处理大规模数据集时,传统的关系型数据库如MySQL可能会遇到性能瓶颈。为了提升数据处理的效率,我们可以结合使用MySQL和Redis,利用两者的优势来优化数据查询。本文将分享一次实战经验,探讨如何通过MySQL与Redis的协同工作来优化百万级数据统计。
652 5
|
缓存 NoSQL Java
Spring Boot与Redis:整合与实战
【10月更文挑战第15天】本文介绍了如何在Spring Boot项目中整合Redis,通过一个电商商品推荐系统的案例,详细展示了从添加依赖、配置连接信息到创建配置类的具体步骤。实战部分演示了如何利用Redis缓存提高系统响应速度,减少数据库访问压力,从而提升用户体验。
535 2