首页 > 数据库 >Redis分布式锁详解及电商秒杀功能示例

Redis分布式锁详解及电商秒杀功能示例

时间:2024-06-16 15:57:19浏览次数:25  
标签:key 示例 过期 Redis 获取 客户端 电商 分布式

Redis分布式锁是一种在分布式系统中,利用Redis的原子操作特性实现的锁机制,用于保护共享资源的并发访问。

原理

原子性与互斥性

Redis分布式锁的核心原理在于利用Redis的某些原子操作(如`SETNX`、`GETSET`、`SET`带特定选项等)来确保锁的获取与释放操作是原子性的,从而保证了锁的互斥性,即同一时刻只有一个客户端能持有锁。

- `SETNX`(Set if Not Exists):当给定的key不存在时,设置key的值为给定的value,并返回1(设置成功);如果key已经存在,则不做任何操作并返回0(设置失败)。利用`SETNX`,客户端可以尝试创建一个唯一标识的锁key,只有第一个成功创建的客户端才能获得锁。

自动过期与锁续期

        为了避免死锁,通常会在锁key上设置一个过期时间(TTL),当持有锁的客户端崩溃或者未能及时释放锁时,锁会自动过期并释放,允许其他客户端获取锁。

- `EXPIRE`或`PEXPIRE`:在获取锁后立即为锁key设置一个过期时间,确保即使客户端异常,锁也会在一定时间后自动释放。

- 锁续期(Keepalive):在客户端持有锁期间,定期刷新锁的过期时间,防止锁在业务处理尚未完成时提前过期。这通常通过客户端轮询或使用Redis的`PEXPIRE`命令(带有超时参数的`WATCH` + `MULTI` + `EXEC`事务)来实现。

可重入性与公平性

        可重入性:某些实现允许同一个客户端在已经持有锁的情况下再次获取该锁,即递归锁。这通常通过在锁值中保存客户端标识和锁计数来实现。

         公平性:在某些场景下,可能需要保证锁的获取按照请求到达的顺序进行,即先请求的客户端优先获得锁。实现公平性通常需要额外的设计和逻辑,比如使用有序集合(`ZSET`)结合时间戳或FIFO队列来排队等待锁。

实现方式

基础实现

最基础的Redis分布式锁实现通常包括以下步骤:

1. 尝试获取锁:使用`SETNX`或`SET`(带有`NX`和`PX`选项)尝试设置锁key,设置成功则获得锁。

2. 设置过期时间:在获取锁后立即为锁key设置过期时间,防止锁长期不释放。

3. 执行临界区代码:在持有锁期间执行需要互斥访问的业务逻辑。

4. 释放锁:使用`DEL`命令删除锁key,表示释放锁。

高级实现

更完善的实现可能包括以下增强功能:

- 锁续期:在执行临界区代码的过程中,定期刷新锁的过期时间,防止锁过早过期。

- 锁超时与降级:设定获取锁的超时时间,超时后放弃获取锁,或降级为非锁定模式继续执行。

- 锁竞争通知:当锁被其他客户端持有时,可以设置通知机制,让等待的客户端在锁释放时得到通知。

- 锁的公平性保障:通过额外的数据结构或算法设计,实现公平的锁获取顺序。

优缺点

优点

- 性能高:Redis操作通常是单线程、单命令执行,且支持网络IO多路复用,性能优异。

- 易用性好:Redis提供了丰富的API,实现分布式锁相对简单。

- 适应性强:Redis支持多种数据结构,可以根据需求选择合适的方式来实现锁。
 

缺点

- 依赖外部系统:分布式锁的正确性依赖于Redis服务的可用性和一致性。

- 网络延迟:网络抖动可能导致锁的获取或释放延迟,影响并发控制效果。

- 非阻塞获取:基础的Redis分布式锁实现通常是非阻塞的,客户端在无法获取锁时需自行决定如何处理(如轮询、睡眠、降级等)。
 

注意事项

- 客户端一致性:确保客户端在获取锁后的所有操作都在一个事务中,或者通过其他手段保证操作的原子性。

- 锁的释放:确保无论何种情况(正常结束、异常、程序中断等),都要正确释放锁,避免死锁。

- 锁的过期时间设置:过期时间应根据业务需求合理设置,既不能过短导致正常业务执行中锁提前释放,也不能过长增加死锁风险。

- Redis版本与特性支持:不同版本的Redis可能对某些命令或特性支持程度不同,选择合适的Redis版本和命令集来实现分布式锁。

- Redis集群模式下的注意事项:在Redis集群环境中,需考虑数据分片(slot)对锁的影响,确保锁key落在同一分片上,或者使用Redlock算法等针对集群环境的分布式锁方案。

Redis分布式锁利用Redis的原子操作特性实现了一种轻量级的分布式并发控制机制,适用于多种分布式场景,基于Redis分布式锁,我们可以实现电商秒杀场景中的并发控制,防止商品库存超卖。以下是一个使用Jedis客户端实现的Java代码示例:

首先,假设你已经在项目中引入了Jedis库。下面展示的是一个简化的秒杀务类(`SeckillService`),其中使用Redis分布式锁来保护库存扣减的操作:

import redis.clients.jedis.Jedis;

import redis.clients.jedis.params.SetParams;



import java.util.UUID;

import java.util.concurrent.TimeUnit;



public class SeckillService {

    private static final String REDIS_LOCK_KEY = "seckill_lock";

    private static final int LOCK_EXPIRE_TIME_SECONDS = 60; // 锁过期时间,可根据实际需求调整
    private final Jedis jedis; // 假设已经正确初始化Jedis客户端
    public boolean seckill(String userId, String productId) {
        // 尝试获取分布式锁
        String lockValue = UUID.randomUUID().toString();
        long acquireLockResult = jedis.set(REDIS_LOCK_KEY, lockValue, SetParams.setParams().nx().px(LOCK_EXPIRE_TIME_SECONDS * 1000));
        if (acquireLockResult == 1) { // 获取锁成功
            try {
                // 执行秒杀逻辑(如扣除库存、生成订单等)
                boolean seckillSuccess = doSeckill(userId, productId);
                return seckillSuccess;
            } finally {
                // 无论如何,都要释放锁
                String currentValue = jedis.get(REDIS_LOCK_KEY);
                if (currentValue != null && currentValue.equals(lockValue)) {
                    jedis.del(REDIS_LOCK_KEY);
                }
            }
        } else {

            // 获取锁失败,可能是其他客户端已经持有锁,返回秒杀失败
            return false;
        }
    }

    private boolean doSeckill(String userId, String productId) {
        // 实际的秒杀逻辑,如检查库存、扣除库存、生成订单等
        // 这里仅做示例,返回一个随机的成功或失败结果
        return Math.random() < 0.5; // 50%的成功概率
    }
}

在这个示例中:

1. 定义了一个全局唯一的Redis键`REDIS_LOCK_KEY`作为分布式锁的标识。

2. 使用`Jedis.set()`方法(配合`SetParams.nx().px()`参数)尝试获取分布式锁。`nx`表示只有当键不存在时才设置值(保证互斥性),`px`设置锁的过期时间(防止死锁)。

3. 如果成功获取锁(`set()`方法返回`1`),则执行秒杀逻辑(`doSeckill()`方法)。这里只是一个简单的示例,实际应用中应包含检查库存、扣除库存、生成订单等操作。

4. 在`finally`块中,无论秒杀是否成功,都必须释放锁。通过检查当前锁值是否与获取锁时设置的值相等来确保不会误删其他客户端的锁。相等则删除键(释放锁)。

5. 如果获取锁失败(`set()`方法返回`0`),则直接返回秒杀失败,避免继续执行秒杀逻辑。

标签:key,示例,过期,Redis,获取,客户端,电商,分布式
From: https://blog.csdn.net/weixin_53391173/article/details/139584092

相关文章

  • redis常用5种数据类型及其常见问题(缓存穿透,缓存击穿,缓存雪崩)
    1.字符串(String)2.哈希(hash):redishash是一个string类型的字段和value的映射表,hash特别适合存储对象3.列表(List):字符串列表,按照插入的顺序。可以添加一个元素到列表的头部或尾部4.集合(set):String类型的无序集合。集合成员不可重复,redis中集合通过哈希表实现的,添加,删除,查找复杂度......
  • Redis面试准备 第二天
    什么是Redis持久化?        持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失为什么要做Redis持久化?        持久化的目的是做灾难恢复,当redis宕机时,能尽快恢复缓存数据,对外提供服务。有人会问,redis不是缓存吗,为什么要持久化呢,直接重启继续提......
  • Redisson详解
    目录第1章:Redisson简介第2章:Redisson的架构与原理第3章:Redisson的基本使用连接Redis基本操作高级数据结构操作分布式锁的使用第4章:Redisson的高级特性分布式数据结构发布/订阅模型延迟队列与阻塞队列第5章:Redisson的分布式服务分布式锁的实现与应用分布式集合......
  • K-均值聚类算法:原理、应用及实战代码示例
    摘要K-均值聚类算法是数据科学中的一个基础而强大的工具,用于将数据点分组成不同的簇。本文不仅介绍了K-均值聚类算法的基本原理和优缺点,还提供了Python代码示例,展示如何在实际数据集上应用这一算法。关键词K-均值聚类,无监督学习,Python,数据挖掘目录引言K-均值聚类算法原理......
  • python学习 - 操作redis数据库常用指令 案例
    #-*-coding:UTF-8-*-importredisimporttimeclassTestRedis:def__init__(self):self.dbconn=NonedefopenRedis(self):#连接redis,加上decode_responses=True,写入的键值对中的value为str类型,不加这个参数写入的则为字节类型。......
  • Linux下Redis安装教程
    Linux下Redis安装教程一.下载Redis安装包官网下载地址:Redisdownloads推荐下载redis-7.0.0.tar.gz教程是以这个版本安装的二.安装1.下载Redis环境支持#安装GCC,Redis是基于C语言开发的,需要GCC支持yuminstallgcc-c++2.上传至服务器上传工具可以使用XFTPXFTP......
  • Redis实战指南:基础知识、实战技巧、应用场景及最佳实践全攻略
    背景在Java系统实现过程中,我们不可避免地会借助大量开源功能组件。然而,这些组件往往功能丰富且体系庞大,官方文档常常详尽至数百页。而在实际项目中,我们可能仅需使用其中的一小部分功能,这就造成了一个挑战:如何在有限的时间和精力下,高效地掌握并使用这些组件的核心功能,以实现......
  • Redis常见问题
    1key的生存时间到了,Redis会立即删除吗?不会立即删除。定期删除:Redis每隔一段时间就去会去查看Redis设置了过期时间的key,会再100ms的间隔中默认查看3个key。惰性删除:如果当你去查询一个已经过了生存时间的key时,Redis会先查看当前key的生存时间,是否已经到了,直接删除当前key......
  • redis设计与实现(五)RDB与AOF持久化
    RDB持久化因为Redis是内存数据库,它将自己的数据库状态储存在内存里面,所以如果不想办法将储存在内存中的数据库状态保存到磁盘里面,那么一旦服务器进程退出,服务器中的数据库状态也会消失不见。为了解决这个问题,Redis提供了RDB持久化功能,这个功能可以将Redis在内存中的数据库......
  • 企业生产环境Nacos集群部署示例
    Nacos运行环境需要jdk环境,集群各节点服务器需安装jdk1.8:jdk-8u341-linux-x64.tar第一步:上次安装包第二步:解压sudotar-zxvfjdk-8u341-linux-x64.tar.gz第三步:配置环境变量sudovim/etc/profile第四步:添加以下内容exportJAVA_HOME=/usr/local/jdk1.8.0_341exportJRE......