
redis中的并发问题
使用redis作为缓存已经很久了,redis是以单进程的形式运行的,命令是一个接着一个执行的,一直以为不会存在并发的问题,直到今天看到相关的资料,才恍然大悟(推荐:redis视频教程)
具体问题实例
有个键,假设名称为myNum,里面保存的是阿拉伯数字,假设现在值为1,存在多个连接对myNum进行操作的情况,这个时候就会有并发的问题。假设有两个连接linkA和linkB,这两个连接都执行下面的操作,取出myNum的值,+1,然后再存回去,看看下面的交互:
linkA get myNum => 1 linkB get myNum => 1 linkA set muNum => 2 linkB set myNum => 2
执行完操作之后,结果可能是2,这和我们预期的3不一致。
再看一个具体的例子:
<?php
require "vendor/autoload.php";
$client = new Predis\Client([
'scheme' => 'tcp',
'host' => '127.0.0.1',
'port' => 6379,
]);
for ($i = 0; $i < 1000; $i++) {
$num = intval($client->get("name"));
$num = $num + 1;
$client->setex("name", $num, 10080);
usleep(10000);
}设置name初始值为0,然后同时用两个终端执行上面的程序,最后name的值可能不是2000,而是一个
redis中的事务
redis中也是有事务的,不过这个事务没有mysql中的完善,只保证了一致性和隔离性,不满足原子性和持久性。
redis事务使用multi、exec命令
原子性,redis会将事务中的所有命令执行一遍,哪怕是中间有执行失败也不会回滚。kill信号、宿主机宕机等导致事务执行失败,redis也不会进行重试或者回滚。
持久性,redis事务的持久性依赖于redis所使用的持久化模式,遗憾的是各种持久化模式也都不是持久化的。
隔离性,redis是单进程,开启事务之后,会执行完当前连接的所有命令直到遇到exec命令,才处理其他连接的命令。
一致性,看了文档,觉得挺扯的,但是貌似说的没有问题。
redis中的事务不支持原子性,所以解决不了上面的问题。
SEEK.ai
AI驱动的智能数据解决方案,询问您的任何数据并立即获得答案
88
查看详情
当然了redis还有一个watch命令,这个命令可以解决这个问题,看下面的例子,对一个键执行watch,然后执行事务,由于watch的存在,他会监测键a,当a被修该之后,后面的事务就会执行失败,这就确保了多个连接同时来了,都监测着a,只有一个能执行成功,其他都返回失败。
127.0.0.1:6379> set a 1 OK 127.0.0.1:6379> watch a OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> incr a QUEUED 127.0.0.1:6379> exec 1) (integer) 2 127.0.0.1:6379> get a "2"
失败时候的例子,从最后可以看出,test的值被其他连接修改了:
127.0.0.1:6379> set test 1 OK 127.0.0.1:6379> watch test OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> incrby test 11 QUEUED 127.0.0.1:6379> exec (nil) 127.0.0.1:6379> get test "100"
我的问题如何解决
redis中命令是满足原子性的,因此在值为阿拉伯数字的时候,我可以将get和set命令修改为incr或者incrby来解决这个问题,下面的代码开启两个终端同时执行,得到的结果是满足我们预期的2000。
<?php
require "vendor/autoload.php";
$client = new Predis\Client([
'scheme' => 'tcp',
'host' => '127.0.0.1',
'port' => 6379,
]);
for ($i = 0; $i < 1000; $i++) {
$client->incr("name");
$client->expire("name", 10800);
usleep(10000);
}@manzilu 提到的方法
评论中manzilu提到的方法查了下资料,确实可行,效果还不错,这里写了个例子
<?phprequire "vendor/autoload.php";
$client = new Predis\Client([ 'scheme' => 'tcp', 'host' => '127.0.0.1', 'port' => 6379,
]);class RedisLock{ public $objRedis = null; public $timeout = 3; /**
* @desc 设置redis实例
*
* @param obj object | redis实例
*/
public function __construct($obj)
{ $this->objRedis = $obj;
} /**
* @desc 获取锁键名
*/
public function getLockCacheKey($key)
{ return "lock_{$key}";
} /**
* @desc 获取锁
*
* @param key string | 要上锁的键名
* @param timeout int | 上锁时间
*/
public function getLock($key, $timeout = NULL)
{
$timeout = $timeout ? $timeout : $this->timeout;
$lockCacheKey = $this->getLockCacheKey($key);
$expireAt = time() + $timeout;
$isGet = (bool)$this->objRedis->setnx($lockCacheKey, $expireAt); if ($isGet) { return $expireAt;
} while (1) {
usleep(10);
$time = time();
$oldExpire = $this->objRedis->get($lockCacheKey); if ($oldExpire >= $time) { continue;
}
$newExpire = $time + $timeout;
$expireAt = $this->objRedis->getset($lockCacheKey, $newExpire); if ($oldExpire != $expireAt) { continue;
}
$isGet = $newExpire; break;
} return $isGet;
} /**
* @desc 释放锁
*
* @param key string | 加锁的字段
* @param newExpire int | 加锁的截止时间
*
* @return bool | 是否释放成功
*/
public function releaseLock($key, $newExpire)
{
$lockCacheKey = $this->getLockCacheKey($key); if ($newExpire >= time()) { return $this->objRedis->del($lockCacheKey);
} return true;
}
}
$start_time = microtime(true);
$lock = new RedisLock($client);
$key = "name";for ($i = 0; $i < 10000; $i++) {
$newExpire = $lock->getLock($key);
$num = $client->get($key);
$num++;
$client->set($key, $num);
$lock->releaseLock($key, $newExpire);
}
$end_time = microtime(true);echo "花费时间 : ". ($end_time - $start_time) . "\n";执行shell php setnx.php & php setnx.php&,最后会得到结果:
$ 花费时间 : 4.3004920482635 [2] + 72356 done php setnx.php # root @ ritoyan-virtual-pc in ~/PHP/redis-high-concurrency [20:23:41] $ 花费时间 : 4.4319710731506 [1] + 72355 done php setnx.php
同样循环1w次,去掉usleep,使用incr直接进行增加,耗时在2s左右。
而获取所得时候取消usleep,时间不但没减少,反而增加了,这个usleep的设置要合理,免得进程做无用的循环
总结
看了这么多,简单的总结下,其实redis本事是不会存在并发问题的,因为他是单进程的,再多的command都是one by one执行的。我们使用的时候,可能会出现并发问题,比如get和set这一对。
更多redis相关文章请关注redis数据库教程栏目。
以上就是redis并发问题解决的详细内容,更多请关注其它相关文章!
# 都是
# 日照优化网站工具在哪找
# 陈江网站排名优化
# 优化视频网站速度
# 快速营销店铺怎么做推广
# 服装营销线下怎么推广好
# 商洛全网seo优化
# 网络营销推广月度测试
# 烟台商城网站建设
# 邯郸网站品牌优化公司
# seo优化网站多久能上首页
# redis
# 加锁
# 如何实现
# 解决这个问题
# 网络带宽
# 相关文章
# 多个
# 看了
# 值为
# 的是
相关栏目:
【
Google疑问12 】
【
Facebook疑问10 】
【
优化推广96088 】
【
技术知识133117 】
【
IDC资讯59369 】
【
网络运营7196 】
【
IT资讯61894 】
相关推荐:
谷歌浏览器怎么把网页翻译成中文_Chrome网页翻译功能使用方法
Win10怎么设置快速启动 Win10开启快速启动设置方法
Win10共享文件夹设置方法 Win10局域网文件共享全攻略【教程】
曝《丝之歌》DLC有望开发!开发商还有神秘新企划
百度小说看书时如何翻页_百度小说手动翻页与自动翻页设置
Retrofit根路径POST请求:@POST("/") 的应用与解析
如何在CSS中设置背景图像:一个全面指南
铁路12306官网登录入口 铁路12306在线购票官方平台
《海贝音乐》均衡器设置方法
快递查询,一键速查
c++中的const关键字用法大全_c++ const正确使用指南
电脑开不了机怎么办 电脑无法开机的解决方法
J*aScript模块加载器_RequireJS原理分析
解决VS Code中Python版本冲突与输出异常的指南
《下一站江湖2》武器获取方法
word文档中的分隔符有哪些不同类型和用途_Word分隔符类型与用途方法
VS Code中的Tailwind CSS IntelliSense插件使用技巧
KFC邀请码怎么使用领额外优惠_KFC邀请码输入方式与额外优惠代码获取方法
《饿了么》拼好饭点外卖教程2025
VBA Outlook邮件自动化:高效集成Excel数据与列标题的策略
三角洲行动2025年9月10日摩斯密码分享
VB表达式书写规则解析
macosmonterey系统外接显示器驱动怎么安装_macosmonterey外接显示器驱动与分辨率调整
yandex网页版直接登录 yandex官方入口平台访问方法
J*aScript模拟悬停与点击:自动化网页动态元素交互指南
123网页端官方登录页 123邮箱网页版即时通讯服务
多多买菜门店端app订单查看方法
b站如何管理订阅_b站订阅标签分类管理
Lar*el Dusk 测试中管理浏览器权限:以剪贴板访问为例
Microsoft Edge网页字体太淡看不清怎么办_Microsoft Edge字体渲染优化技巧
TikTok视频播放中断怎么办 TikTok播放异常修复方法
中通快递官网指定查询 中通快递单号查询平台入口
顺丰官方查单号入口 顺丰快递单号查询官网入口
PPT页面尺寸怎么修改 PPT自定义幻灯片大小与方向设置【教程】
圆通快递官方入口不需要登录 在线查询入口快速查询
Go Goroutine调度与并发执行深度解析
tiktok国际版入口_tiktok官网网页版链接
CSS如何使用outline-offset与颜色组合突出元素边框
实时数据流中高效查找最小值与最大值
如何取消数字签名
iSpring三分屏制作教程
全球各国上班时间表外贸邮件时间
如何在CSS中使用伪类:valid实现表单验证提示_结合:valid改变边框颜色
J*a列表元素格式化输出教程
《七读免费小说》开通会员方法
《豆瓣》私信用户方法
抖音号升级成企业资质怎么弄?有什么好处?
BunnyStream TUS视频上传指南:解决401认证错误与参数配置
《红果免费短剧》下载观看方法
谷歌浏览器官网地址整理_谷歌浏览器新版直连2026稳定访问
2019-11-26
运城市盐湖区信雨科技有限公司是一家深耕海外推广领域十年的专业服务商,作为谷歌推广与Facebook广告全球合作伙伴,聚焦外贸企业出海痛点,以数字化营销为核心,提供一站式海外营销解决方案。公司凭借十年行业沉淀与平台官方资源加持,打破传统外贸获客壁垒,助力企业高效开拓全球市场,成为中小企业出海的可靠合作伙伴。