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

Redis如何应对并发访问(redis控制并发量)

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

Redis如何应对并发访问

Redis如果在业务中运用那么肯定需要考虑并发问题,如多个用户对同一个商品进行扣减,这时并发执行很可能导致商品数量不对,那么Redis如何来避免这些问题呢?一般分为两种解决方案分布式锁以及原子性操作。

对于分布式锁显然是可以解决上述问题的,但这不是最优因为分布式锁降低了系统的效率,还需要额外加锁解锁的操作,这里暂不讨论加锁这种方案。

那么对于原子性操作Redis如何实现呢?我们可以先思考并发访问中是对什么进行加锁呢?

以商品库存扣减为例,一般分为三个步骤

  • 读取商品库存数量
  • 将商品库存数量减一
  • 将扣减后的数量写回Redis中

这个流程简称为RMW也就是读取-修改-写回,当进行这三个中的任何一步时我们都需要保证互斥,不允许其它用户读取到商品库存的中间状态,这样才能避免并发导致的库存不正确性。

Redis事务处理

那Redis如何保证RMW的互斥性呢?大多数人这里的第一想法应该是事务,事务去保证要不都成功要不都失败,Redis中的事务怎么玩呢?

redis中提供下面四种命令来保证事务执行

  • MULTI:开启一个事务,执行后客户端可以向服务器发送任意多条命令,这些命令并不会立即执行,而是放到一个队列中,当执行EXEC命令才开始执行,当客户端处于事务状态,执行所有的命令会返回QUEUED。
  • EXEC :执行事务。
  • DISCARD :清空事务队列,并放弃事务。
  • WATCH:类似于乐观锁,当监控的值在WATCH执行后,EXEC命令执行前,修改了该值,那么redis执行当前事务将失败。

简单演示事务操作

-- 开启事务
127.0.0.1:6379> MULTI
OK
-- 会将命令加入到事务队列中,后续exec统一执行
127.0.0.1:6379(TX)> set name zhangsan
QUEUED
127.0.0.1:6379(TX)> get name
QUEUED
127.0.0.1:6379(TX)> set age 12
QUEUED
127.0.0.1:6379(TX)> DECR age
QUEUED
-- 执行事务
127.0.0.1:6379(TX)> EXEC
1) OK
2) "zhangsan"
3) OK
4) (integer) 11

丢弃事务

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set name zhangsan
QUEUED
-- 丢弃事务
127.0.0.1:6379(TX)> DISCARD
OK
-- get事务中添加的值,为空
127.0.0.1:6379> get name
(nil)

WATCH乐观锁

-- 客户端1,中添加age值为12
127.0.0.1:6379> set age 12
OK 
-- 开启监控
127.0.0.1:6379> WATCH age
OK
127.0.0.1:6379> MULTI
127.0.0.1:6379(TX)> set name zhagnsan
QUEUED
-- 同时开启客户端2,将监控的age值改为11
127.0.0.1:6379(TX)> DECR age
QUEUED
-- 事务执行失败
127.0.0.1:6379(TX)> EXEC
(nil)
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> get age
"11"

事务能保证原子性吗?这是不确定的,一般我们熟知的事务遇到错误就会回滚,但Redis是分情况来讨论。

事务中的错误

Redis事务中的错误分为两种

  • 事务在执行EXEC命令之前,入队命令出错,比如命令产生语法错误(参数错误、参数名错误等等),或者一些更加严重的错误如内存不足(Redis设置maxmemory最大内存限制的前提)。
  • 命令在EXEC调用失败后,举个例子,事务命令可能处理了错误类型的键,列表命令用在字符串键上。

模拟EXEC命令执行之前出现错误

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set name zhangsan
QUEUED
127.0.0.1:6379(TX)> seta age 1
(error) ERR unknown command `seta`, with args beginning with: `age`, `1`, 
-- EXEC执行前出现异常,放弃事务
127.0.0.1:6379(TX)> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get name
(nil)

模拟EXEC命令执行之后出现错误

-- 开启事务前先设置一个String类型的键值
127.0.0.1:6379> set name zhangsan
OK
127.0.0.1:6379> MULTI
OK
-- 采用hash方式获取string类型的键值,语法没错
127.0.0.1:6379(TX)> hget name 1
QUEUED
127.0.0.1:6379(TX)> set sex 2
QUEUED
127.0.0.1:6379(TX)> EXEC
-- 执行报错
1) (error) WRONGTYPE Operation against a key holding the wrong kind of value
2) OK
-- 事务没有放弃,执行部分事务
127.0.0.1:6379> get sex
"2"

所以,在EXEC执行之前的错误,Redis会将事务回滚,但是EXEC执行后产生的错误,Redis不会丢弃事务。因为Redis认为在EXEC执行后发生错误一般是语法错误,这个失败是编程造成的,这些错误在开发时就应该被发现,而不是应该出现在生产环境,而且不需要对回滚进行支持所以Redis能保持简单而且快速。

综上严格意义上来讲Redis的事务不支持原子性

既然Redis事务不能支持原子性,那么还能如何保证并发一致性呢?这里还有两种方案。

Redis两种原子操作方法

单命令操作

简单来讲就是将多个操作集成到一个命令上,因为Redis单命令操作是会阻塞主线程的,也就是说是互斥的,在命令执行过程中其它命令不会执行,这种命令形如INCRDECR,例如库存扣减有三个步骤简称RMW,可以通过单命令DECR一次执行。

Lua脚本

Redis从2.6.0版本开始支持Lua脚本,Lua脚本的多样性一般是实现原子操作的最佳选择,Redis会把整个Lua脚本作为一个整体执行,在执行过程中不会被其他命令所打断,从而保证Lua脚本的原子性,如果我们存在多个操作要执行,又无法使用单命令操作实现,那么就可以试试Lua脚本。

Lua脚本简单演示

-- 形如命令 set name zhangsan
127.0.0.1:6379> EVAL "return redis.call('set',KEYS[1],ARGV[1])" 1 name zhangsan
OK
127.0.0.1:6379> get name
"zhangsan"
-- 形如命令 get name
127.0.0.1:6379> EVAL "return redis.call('get',KEYS[1])" 1 name
"zhangsan"
-- 清空预加载lua脚本
127.0.0.1:6379> SCRIPT FLUSH
OK
-- 加载脚本,可以自动生成一个字符串,不需要每次传输脚本,提升传输速度
127.0.0.1:6379> SCRIPT LOAD "return redis.call('set',KEYS[1],ARGV[1])"
"c686f316aaf1eb01d5a4de1b0b63cd233010e63d"
-- 执行预加载脚本
127.0.0.1:6379> EVALSHA "c686f316aaf1eb01d5a4de1b0b63cd233010e63d" 1 age 1
OK
127.0.0.1:6379> get age
"1"
-- 判断预加载脚本是否存在
127.0.0.1:6379> SCRIPT EXISTS "c686f316aaf1eb01d5a4de1b0b63cd233010e63d"
1) (integer) 1

相关推荐

Redis 数据类型深度解析:原理、场景与实战应用

在互联网大厂从事后端开发的伙伴们,在使用Redis数据类型时,是不是经常会遇到这些情况:数据存储效率低下、查询速度达不到预期,又或者在处理复杂业务逻辑时,完全不知道该选哪种数据类型才合适?别担心,...

Flume日志采集系统--初体验(flume收集日志的多种方式)

这两天看了一下Flume的开发文档,并且体验了下Flume的使用。本文就从如下的几个方面讲述下我的使用心得:初体验——与Logstash的对比安装部署启动教程参数与实例分析Flume初体验Flume...

分布式实时日志分析采集三种方案(日志数据采集工具)

ELK已经成为目前最流行的集中式日志解决方案,它主要是由Filebeat、Logstash、Elasticsearch、Kibana等组件组成,来共同完成实时日志的收集,存储,展示等一站式的解决方案...

Redis大Key“隐形杀手”全攻略:从精准排查到根治方案

今天,我将用真实故障案例+可视化排查图谱,带你直击大Key问题的七寸,并揭秘一线大厂都在用的"防爆"组合拳。大Key的“四宗罪”:你以为的优化,可能是慢性自杀1.内存黑洞(吞噬者模式...

企业日志架构的4个典型场景(企业日志怎么写)

今天,我们从最简单的日志架构开始,介绍各种不同场景下的日志架构,并分析其在不同场景下的特点或问题。-1-最简单的日志架构这种架构是我使用过最简单的架构,我曾经在做一些个人小项目时,在资源不足的情况...

分布式日志追踪ID实战(分布式追踪组件)

作者:京东物流张小龙本文通过介绍分布式应用下各个场景的全局日志ID透传思路,以及介绍分布式日志追踪ID简单实现原理和实战效果,从而达到通过提高日志查询排查问题的效率。背景开发排查系统问题用得最多的手...

开发利器丨如何使用ELK设计微服务中的日志收集方案?

【摘要】微服务各个组件的相关实践会涉及到工具,本文将会介绍微服务日常开发的一些利器,这些工具帮助我们构建更加健壮的微服务系统,并帮助排查解决微服务系统中的问题与性能瓶颈等。我们将重点介绍微服务架构中...

Redis面试攻防战:如何赢得技术博弈的胜利

今天,我面试了某大厂的java开发岗位,迎面走来一位风尘仆仆的中年男子,手里拿着屏幕还亮着的mac,他冲着我礼貌的笑了笑,然后说了句“不好意思,让你久等了”,然后示意我坐下,说:“我们开始吧。看了你的...

同样写增删改查,有人简历石沉大海,有人能拿30%涨幅

你信不信?同样用SpringBoot写增删改查,有人简历石沉大海,有人能拿30%涨幅?知道差在哪吗?——你还在用技术名词堆简历,高手早把流水账改成钞票印刷机了!说个作死写法:使用Redis实现缓存功能...

Redis客户端缓存的几种实现方式(redis 缓存)

前言:Redis作为当今最流行的内存数据库和缓存系统,被广泛应用于各类应用场景。然而,即使Redis本身性能卓越,在高并发场景下,应用于Redis服务器之间的网络通信仍可能成为性能瓶颈。所以客户端缓存...

简历写'增删改查'永远被刷?Java老哥,这样包装项目直接逆袭!

简历写'增删改查'永远被刷?Java老哥,这样包装项目直接逆袭!老铁们!是不是每次写简历都觉得自己像个CRUD机器人?明明做了三年项目,写出来却像刚毕业?知道大厂HR怎么看这种简历吗?...

让你的系统的QPS突然提升10倍你会怎么设计?Java代码实战

让你的系统的QPS突然提升10倍你会怎么设计?Java代码实战首先,我需要理解QPS是什么。QPS是每秒查询率,也就是系统每秒能处理的请求数。提升10倍意味着系统需要处理更多的并发请求,这可能会导致性...

团队Leader说我只会干活不会思考?教你把架构设计写成简历C位

团队Leader说我只会干活不会思考?教你把架构设计写成简历C位各位老开发,有没有被问过这种送命题:'作为技术骨干,你推动过哪些技术升级?'有个兄弟上周就栽这儿了,他明明主导过服务拆分...

MyBatis缓存机制全解析:一级缓存VS二级缓存

MyBatis作为Java生态中最受欢迎的ORM框架之一,其缓存机制是提升数据库访问性能的关键。本文将深入剖析MyBatis的一级缓存和二级缓存,通过代码示例展示它们的实现方式,帮助开发者合理利用缓存...

Spring Boot 架构下的订单自动取消机制:定时任务篇

引言在电子商务领域,确保交易流程的顺畅和高效至关重要。一个常见的场景是,用户生成订单后,系统会给予一定的支付时间窗口,如果在这个窗口内用户未完成支付,订单应当自动取消,以避免资源锁定和库存占用。本文将...

取消回复欢迎 发表评论: