百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

Redis专题-并发/秒杀(redis解决并发)

mhr18 2024-10-25 12:38 25 浏览 0 评论

开局一张图,内容全靠“编”。

昨天晚上在群友里看到有人在讨论库存并发的问题,看到这里我就决定写一篇关于redis秒杀的文章。

1、理论部分

我们看看一般我们库存是怎么出问题的

其实redis提供了两种解决方案:加锁和原子操作

1.1、加锁

加锁:其实非常常见,读取数据前,客户端先获取锁,再操作。
当客户端获得锁后,一直持有直到客户端完成操作,再释放。

怎么操作呢,客户端使用分布式锁来获取锁,(使用redis或者zookeeper来实现一个分布式锁)以商品的维度来加锁,在获取到锁的线程中,按顺序执行商品的库存查询和扣减,同时实现了顺序性和原子性。

但是,但是,有问题:
1、如果使用redis来实现分布式锁,那么
锁的时效性是个问题。太短了,业务还没跑完锁就释放了。太长了,如果异常,其他业务就一直阻塞等着自动释放。

2、如果使用zookeeper,确实不用担心锁释放问题(临时节点),而且一致性好,但是性能不高。ZK中创建和删除节点只能通过Leader服务器来执行,然后Leader服务器还需要将数据同不到所有的Follower机器上,这样频繁的网络通信,性能的短板是非常突出的。(挖坑后续写一个redis和zookeeper实现分布式锁的文章)

所以。。继续往下看。。

1.2、原子操作

原子操作:执行过程中保持原子性操作,而原子性操作是不需要加锁的,也就是无锁操作。所以既保证了并发也不会减少系统并发性能。

redis的原子操作其实也有两种方式:
1、单命令操作:多个操作在redis中一个操作完成
2、lua:多个操作写成lua脚本,以原子性方式执行单个lua脚本

1.2.1、INCR/DECR

Redis 是使用单线程来串行处理客户端的请求操作命令的,所以,当 Redis 执行某个命令操作时,其他命令是无法执行的,这相当于命令操作是互斥执行的。

Redis 的单个命令操作可以原子性地执行,但是在实际应用中,数据修改时可能包含多个操作,至少包括读数据、数据增减、写回数据三个操作,这显然就不是单个命令操作了,那该怎么办呢?

Redis提供INCR/DECR,将读数据、数据增减、写回数据三个操作合并为了一个,可以对数据进行增值 / 减值操作,而且它们本身就是单个命令操作,所以本身具有互斥性。可以直接帮助我们进行并发控制。

// 将商量id的库存减1
DECR id

是的,就是这么简单就搞定了扣减库存。

1.2.2、Lua脚本

Redis 会把整个 Lua 脚本作为一个整体执行,在执行的过程中不会被其他命令打断,从而保证了 Lua 脚本中操作的原子性。

将要执行的操作编写到一个 Lua 脚本中,使用 Redis 的 EVAL 命令来执行脚本。

原生 EVAL 方法的使用语法如下:

EVAL script numkeys key [key ...] arg [arg ...]

script 是我们 Lua 脚本的字符串形式,numkeys 是我们要传入的参数数量,key 是我们的入参,可以传入多个,arg 是额外的入参。

但这种方式需要每次都传入 Lua 脚本字符串,不仅浪费网络开销,同时 Redis 需要每次重新编译 Lua 脚本,对于我们追求性能极限的系统来说,不是很完美。所以这里就要说到另一个命令 EVALSHA 了,原生语法如下:

EVALSHA sha1 numkeys key [key ...] arg [arg ...]

可以看到其语法与 EVAL 类似,不同的是这里传入的不是脚本字符串,而是一个加密串 sha1。这个 sha1 是从哪来的呢?它是通过另一个命令 SCRIPT LOAD 返回的,该命令是预加载脚本用的,语法为:

SCRIPT LOAD script

将 Lua 脚本先存储在 Redis 中,并返回一个 sha1,下次要执行对应脚本时,只需要传入 sha1 即可执行对应的脚本。这完美地解决了 EVAL 命令存在的弊端,所以我们这里也是基于 EVALSHA 方式来实现的。

-- 调用Redis的get指令,查询活动库存,其中KEYS[1]为传入的参数1,即库存key
local c_s = redis.call('get', KEYS[1])
-- 判断活动库存是否充足,其中KEYS[2]为传入的参数2,即当前抢购数量
if not c_s or tonumber(c_s) < tonumber(KEYS[2]) then
   return 0
end
-- 如果活动库存充足,则进行扣减操作。其中KEYS[2]为传入的参数2,即当前抢购数量
redis.call('decrby',KEYS[1], KEYS[2])
return 1

我们可以将脚本先写在配置中心,代码执行的时候就去拉取最新的sha1。或者代码里面写死。

当然这个脚本也可以扩展,比如加上IP限制等等。但是太多操作放在Lua里也会降低redis的并发性能,所以非并发控制就不写到lua了。

理论看完了,实操一下吧

2、Talk is cheap. Show me the code

2.1、安装redis

跳过,不会安装的出门右拐。

我自己用podman。

podman run -p 6379:6379 --name my_redis --privileged=true -v D:\podman\redis\conf\redis.conf:/etc/redis/redis.conf  -v D:\podman\redis\data:/data -d docker.io/library/redis redis-server /etc/redis/redis.conf --appendonly yes

2.2、代码

在下.neter,就写C#代码了

   [ApiController]
    [Route("[controller]")]
    public class HomeController : ControllerBase
    {
        private static string _redisConnection = "localhost:6379";
        private static ConnectionMultiplexer _connMultiplexer;
         
        private string _redisScript = @"local c_s = redis.call('get', KEYS[1])
                                        if not c_s or tonumber(c_s) < tonumber(KEYS[2]) then
                                           return 0
                                        end
                                        redis.call('decrby',KEYS[1], KEYS[2])
                                          return 1";
        private string _sha1 = string.Empty;
        /// <summary>
        /// 锁
        /// </summary>
        private static readonly object Locker = new object();

        private static int _count = 0;
        private static int _rushToPurchaseCount = 0;

        /// <summary>
        /// 获取 Redis 连接对象
        /// </summary>
        /// <returns></returns>
        private IConnectionMultiplexer GetConnectionRedisMultiplexer()
        {
            if ((_connMultiplexer == null) || !_connMultiplexer.IsConnected)
            {
                lock (Locker)
                {
                    if ((_connMultiplexer == null) || !_connMultiplexer.IsConnected)
                    {
                        _connMultiplexer = ConnectionMultiplexer.Connect(_redisConnection);
                    } 
                }
            }
            return _connMultiplexer;
        }
        
        [HttpPost("/Init")]
        public IActionResult Init()
        {
            GetConnectionRedisMultiplexer();
            return Ok();
        }
        [HttpPost]
        public async Task<IActionResult> Post()
        {
            System.Diagnostics.Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start(); 
            var db = _connMultiplexer.GetDatabase();
            var cache = db.ScriptEvaluateAsync(_redisScript,
                new RedisKey[] { "key999", "1" });

            var results = (string[]?)await cache;
            
            if (results[0] == "1")
            {
                Interlocked.Increment(ref _rushToPurchaseCount);
                Console.WriteLine(#34;恭喜您抢到了,{_rushToPurchaseCount}");
            }
            else
            {
                Console.WriteLine("很遗憾,您没有抢到");
            }
            return Ok();
        }
    }

我们在redis中新增5个库存

配置一下Jmeter,100个线程3秒内跑完

家人们!准备开枪!3!2!1!上链接!

让我们恭喜这5位大冤种

Jmeter聚合报告

redis库存为0

好了,到这里就先结束了。拜拜

原文链接:https://www.cnblogs.com/hanfan/p/17639662.html

相关推荐

【推荐】一个开源免费、AI 驱动的智能数据管理系统,支持多数据库

如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!.前言在当今数据驱动的时代,高效、智能地管理数据已成为企业和个人不可或缺的能力。为了满足这一需求,我们推出了这款开...

Pure Storage推出统一数据管理云平台及新闪存阵列

PureStorage公司今日推出企业数据云(EnterpriseDataCloud),称其为组织在混合环境中存储、管理和使用数据方式的全面架构升级。该公司表示,EDC使组织能够在本地、云端和混...

对Java学习的10条建议(对java课程的建议)

不少Java的初学者一开始都是信心满满准备迎接挑战,但是经过一段时间的学习之后,多少都会碰到各种挫败,以下北风网就总结一些对于初学者非常有用的建议,希望能够给他们解决现实中的问题。Java编程的准备:...

SQLShift 重大更新:Oracle→PostgreSQL 存储过程转换功能上线!

官网:https://sqlshift.cn/6月,SQLShift迎来重大版本更新!作为国内首个支持Oracle->OceanBase存储过程智能转换的工具,SQLShift在过去一...

JDK21有没有什么稳定、简单又强势的特性?

佳未阿里云开发者2025年03月05日08:30浙江阿里妹导读这篇文章主要介绍了Java虚拟线程的发展及其在AJDK中的实现和优化。阅前声明:本文介绍的内容基于AJDK21.0.5[1]以及以上...

「松勤软件测试」网站总出现404 bug?总结8个原因,不信解决不了

在进行网站测试的时候,有没有碰到过网站崩溃,打不开,出现404错误等各种现象,如果你碰到了,那么恭喜你,你的网站出问题了,是什么原因导致网站出问题呢,根据松勤软件测试的总结如下:01数据库中的表空间不...

Java面试题及答案最全总结(2025版)

大家好,我是Java面试陪考员最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Spring、MySQL、JVM、Redis、Linux、Sprin...

数据库日常运维工作内容(数据库日常运维 工作内容)

#数据库日常运维工作包括哪些内容?#数据库日常运维工作是一个涵盖多个层面的综合性任务,以下是详细的分类和内容说明:一、数据库运维核心工作监控与告警性能监控:实时监控CPU、内存、I/O、连接数、锁等待...

分布式之系统底层原理(上)(底层分布式技术)

作者:allanpan,腾讯IEG高级后台工程师导言分布式事务是分布式系统必不可少的组成部分,基本上只要实现一个分布式系统就逃不开对分布式事务的支持。本文从分布式事务这个概念切入,尝试对分布式事务...

oracle 死锁了怎么办?kill 进程 直接上干货

1、查看死锁是否存在selectusername,lockwait,status,machine,programfromv$sessionwheresidin(selectsession...

SpringBoot 各种分页查询方式详解(全网最全)

一、分页查询基础概念与原理1.1什么是分页查询分页查询是指将大量数据分割成多个小块(页)进行展示的技术,它是现代Web应用中必不可少的功能。想象一下你去图书馆找书,如果所有书都堆在一张桌子上,你很难...

《战场兄弟》全事件攻略 一般事件合同事件红装及隐藏职业攻略

《战场兄弟》全事件攻略,一般事件合同事件红装及隐藏职业攻略。《战场兄弟》事件奖励,事件条件。《战场兄弟》是OverhypeStudios制作发行的一款由xcom和桌游为灵感来源,以中世纪、低魔奇幻为...

LoadRunner(loadrunner录制不到脚本)

一、核心组件与工作流程LoadRunner性能测试工具-并发测试-正版软件下载-使用教程-价格-官方代理商的架构围绕三大核心组件构建,形成完整测试闭环:VirtualUserGenerator(...

Redis数据类型介绍(redis 数据类型)

介绍Redis支持五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sortedset:有序集合)。1、字符串类型概述1.1、数据类型Redis支持...

RMAN备份监控及优化总结(rman备份原理)

今天主要介绍一下如何对RMAN备份监控及优化,这里就不讲rman备份的一些原理了,仅供参考。一、监控RMAN备份1、确定备份源与备份设备的最大速度从磁盘读的速度和磁带写的带度、备份的速度不可能超出这两...

取消回复欢迎 发表评论: