内容简介:原文链接:Redis作为一个开源的(BSD)基于内存的高性能存储系统,已经被各大互联网公司广泛使用,并且有着诸多的应用场景。本篇文章将基于PHP来详细讲解Redis在Web项目中的主要应用与实践。这里所介绍的缓存是指可以丢失或过期的数据。常用的命令有
原文链接: Redis在Web项目中的应用与实践
Redis作为一个开源的(BSD)基于内存的高性能存储系统,已经被各大互联网公司广泛使用,并且有着诸多的应用场景。本篇文章将基于 PHP 来详细讲解 Redis 在Web项目中的主要应用与实践。
缓存
这里所介绍的缓存是指可以丢失或过期的数据。常用的命令有 set
, hset
, get
, hget
,使用redis作为缓存时需要注意一下几个问题:
maxmemory
存储
在web项目中,redis可存储读写非常频繁的数据来缓解 MySQL 等数据库的压力。redis如果作为存储系统的话,为了防止数据丢失,持久化必须开启。
典型场景
- 计数器
计数器的需求非常普遍,例如微博点赞数、帖子收藏数、文章分享数、用户关注数等。
- 社交列表
比如使用Sets结构存储关注列表、收藏列表、点赞列表等。
- Session
借助redis高性能的key-value存储,可将用户登录状态保存到redis中。
- ...
队列
简单队列
一般使用redis的list结构作为队列, rpush
生产消息, lpop
消费消息,当 lpop
没有消息的时候,要进行适当的sleep操作。
$queueKey = "queue";
// 生产者
$redis->rpush($queueKey, $data)
// 消费者
while (true) {
$data = $redis->lpop($queueKey);
if (null === $data) {
usleep(100000);
continue;
}
// 业务逻辑
...
}
由于没有消息时使用的sleep事件不好控制,生产环境尽量不要使用sleep来休眠,可使用 blpop
来消费消息,在没有新消息的时候它会阻塞到消息到来。
延时队列
延时队列可使用redis的 sorted set
数据结构,使用时间戳作为 score
,消息内容作为 member
,使用 zadd
命令来生产消息,消费者使用 zrangebyscore
命令获取指定时间之前的消息数据轮询进行处理。
$queueKey = "queue";
// 生产消息
// 消费时间, 这里设置为1小时候
$consumeTimestamp = time() + 3600;
// $data需要添加随机串前缀(or后缀),防止出现重复member被丢弃
$data = $data . md5(uniqid(rand(), true));
$redis->zadd($queueKey, $consumeTimestamp, $data);
// 消费消息
while (tue) {
$arrData = $redis->zrangebyscore($queueKey, 0, time());
if (!$arrData) {
usleep(100000);
continue;
}
// 业务逻辑
foreach ($arrData as $data) {
$data = substr($data, 0, strlen($data) - 32);
// 消费$data
}
}
多消费者
使用pub/sub主题订阅者模式,可以实现1:N的消息队列。这种模式中在消费者下线的情况下,生产的消息会丢失,在这里不推荐使用。
需要强调的是不推荐使用redis作为消息队列服务,这不是redis的设计目标。如果一定要用可考虑 disque ,是由redis的作者开发。
分布式锁
分布式锁主要解决的几个问题:
- 互斥性: 同一时刻只能有一个服务(或应用)访问资源
- 安全性: 锁只能被持有该锁的服务(或应用)释放
- 容错: 在持有锁的服务crash时,锁仍能得到释放
- 避免死锁
方案1
我们可能会考虑使用 setnx
和 expire
命令来实现加锁,即当没有key存在时才会成功写入value:
$lockStatus = $redis->setnx($lockKey, 1);
if (1 === $lockStatus) {
// 加锁成功,为锁设置超时时间
$redis->expire($lockKey, 300);
// 进行后续操作
} elseif (0 === $lockStatus) {
// 加锁失败
} else {
// 其他异常
}
但这种操作不是原子性的,如果在进行setnx时服务崩溃,没有来得及对Key进行超时设置,该锁将一直无法释放。
方案2
我们推荐 set key value [EX seconds] [PX milliseconds] [NX|XX]
命令来进行加锁
- EX: key在多少秒之后过期
- PX:key在多少毫秒之后过期
- NX: 当key不存在的时候,才创建key,效果等同于setnx
- XX:当key存在的时候,覆盖key
$lockStatus = $this->redis->set($lockKey, 1, "EX", 30, "NX");
if ("OK" === $lockStatus) {
// 加锁成功,可进行后续操作
//业务逻辑执行完毕,释放锁
$this->redis->del($lockKey);
} elseif (null === $lockStatus) {
// 加锁失败
}
如上代码所示,如果 set
命令返回OK,那么客户端就可以获得锁(如果返回null,那么应用服务可以在一段时间之后重新尝试获取锁),并且可以通过 del
命令来释放锁。
此方法需要注意的问题:
del
可以通过如下优化使得上面的锁系统变得更加健壮:
del
优化后的代码可参考如下:
$lockToken = md5(uniqid(rand(), true));
// 此处超时时间根据具体业务逻辑配置
$expire = rand(280, 320);
$lockStatus = $this->redis->set($lockKey, $lockToken, "EX", $expire, "NX");
if ("OK" === $lockStatus) {
// 加锁成功,可进行后续操作
// 业务逻辑执行完毕,释放锁
// 删除锁之前需要判断是否是自己上的锁
$currentToken = $this->redis->get($lockKey);
if ($currentToken === $lockToken) {
$this->redis->del($lockKey);
}
} elseif (null === $lockStatus) {
// 加锁失败
}
计算
redis提供的原子自增减方法以及有序集合结构等可以承担一些计算任务,例如浏览量统计等。
浏览计数
文章浏览量+1
$redis->incr($postsKey);
批量获取文章浏览量
$arrPostsKey = [
//...
];
$arrPostsViewNum = $redis->mget($arrPostsKey);
排行榜
可以使用redis的有序集合来实现排行榜的功能,score作为权重 排序 并取前n条记录。
// 存储数据 $sortKey = "sort_key"; $redis->zadd($sortKey, 100, "tom"); $redis->zadd($sortKey, 80, "Jon"); $redis->zadd($sortKey, 59, "Lilei"); $redis->zadd($sortKey, 87, "Hanmeimei"); // 获取排行 // 由大到小排序 $arrRet = $redis->zrevrange($sortKey, 0, -1, true); // 由小到大排序 $arrRet = $redis->zrange($sortKey, 0, -1, true);
结尾
redis涉及的应用实践非常繁多的,由于篇幅所限无法全部顾及,本文只针对web应用中最常用的几个场景进行了展开介绍,渴望进一步拓展redis知识的同学可参考以下链接进一步学习。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Mastering Regular Expressions, Second Edition
Jeffrey E F Friedl / O'Reilly Media / 2002-07-15 / USD 39.95
Regular expressions are an extremely powerful tool for manipulating text and data. They have spread like wildfire in recent years, now offered as standard features in Perl, Java, VB.NET and C# (and an......一起来看看 《Mastering Regular Expressions, Second Edition》 这本书的介绍吧!