Redis 常见应用场景

Redis 系列目录

第一篇:Redis 基础命令
第二篇:Redis 常见应用场景
第三篇:Redis Cluster集群搭建
第四篇:Redis 主从及哨兵搭建
第五篇:Redis 主从及集群
第六篇:Redis 持久化
第七篇:Redis 分布式锁
第八篇:Redis 底层数据存储结构
第八篇:Redis 面试常问问题



前言

redis常用五种数据类型的应用场景。[个人笔记]


一、string类型

1.缓存

作为Key-Value形态的内存数据库,Redis 最先会被想到的应用场景便是作为数据缓存。而使用 Redis 缓存数据非常简单,只需要通过string类型将序列化后的对象存起来即可,不过也有一些需要注意的地方:

  • 必须保证不同对象的 key 不会重复,并且使 key 尽量短,一般使用类名(表名)加主键拼接而成。
  • 选择一个优秀的序列化方式也很重要,目的是提高序列化的效率和减少内存占用。


缓存内容与数据库的一致性(单例模式),这里一般有两种做法:

  • 只在数据库查询后将对象放入缓存,如果对象发生了修改或删除操作,直接清除对应缓存(或设为过期)。
  • 在数据库新增和查询后将对象放入缓存,修改后更新缓存,删除后清除对应缓存(或设为过期)。

示例:

    public function getUserInfo($id)
    {
        if (isset(self::$user->id) && $user->id == $id) {
            return self::$user;
        }

        $user = unserialize(Redis::get('user_'.$id));
        if ($user->id == $id) {
            self::$user = $user;
            return $user;
        }

        $user = User::find($id);
        if ($user->id) {
            self::$user = $user;
            Redis::set('user_'.$id, serialize($user));
            return $user;
        }
        return null;
    }

2.分布式Session

分布式 session 就是将多个服务器的 session 都存储到中,可使用 redis 来实现session共享。一般的PHP框架都会有可以配置session的存储方式。以lavael为例,下面是 config/session.php 的内容:

<?php

use Illuminate\Support\Str;

return [
	改为redis存储session,直接修改.env配置文件,SESSION_DRIVER改为redis。
    'driver' => env('SESSION_DRIVER', 'file'), 			// 存储 Session 数据的位置,如:file、cookie、database、memcached / redis、dynamodb、array。
    'lifetime' => env('SESSION_LIFETIME', 120), 		// 会话生存时间
    'expire_on_close' => false, 						// 有效期开关
    'encrypt' => false,									// 会话加密
    'files' => storage_path('framework/sessions'),		// 文件存储位置
    'connection' => env('SESSION_CONNECTION', null),	// 会话数据库连接(使用数据库和redis时可配置)
    'table' => 'sessions',								// 使用数据库时存储session的表名
    'store' => env('SESSION_STORE', null),				// 会话缓存存储
    'lottery' => [2, 100],								// 某些会话驱动程序必须手动扫描其存储位置,才能从存储中删除旧会话。以下是在给定的请求下发生这种情况的可能性。默认情况下,概率为百分之二
    'cookie' => env(									// 会话Cookie名称
        'SESSION_COOKIE',
        Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
    ),
    'path' => '/',										// 会话Cookie路径
    'domain' => env('SESSION_DOMAIN', null),			// 域名
    'secure' => env('SESSION_SECURE_COOKIE'),			// 证书认证
    'http_only' => true,								// 仅HTTP访问
    'same_site' => 'lax',								// 相同网站Cookie
];

3.分布式锁

set lock_key locked NX EX 1 # 一般带上过期时间(具体情况会比这里写的复杂的多,都可以单独写一篇博客了)
由于这个操作是原子性的,可以简单地以此实现一个分布式的锁


setnx 命令:表示SET if Not eXists,即如果 key 不存在,才会设置它的值,否则什么也不做。
两个客户端同时向redis写入try_lock,客户端1写入成功,即获取分布式锁成功。客户端2写入失败,则获取分布式锁失败。

set 命令加过期时间:

//参数一: $key					缓存键
//参数二: $value					缓存值
//参数三: $expireResolution		EX/PX
//                             		EX	设置键key的过期时间,单位时秒(seconds)
//                              	PX	设置键key的过期时间,单位时毫秒(milliseconds)
//参数四: $expireTTL				缓存键过期时间,单位由参数三决定
//参数五: $flag                 	NX/XX
//                            		NX	只有键key不存在的时候才会设置key的值
//                        			XX	只有键key存在的时候才会设置key的值
set($key, $value, $expireResolution = null, $expireTTL = null, $flag = null)

laravel示例:
Redis::set('shop:shop_'.$id, 1, "EX", 10, "NX");
Redis::setex("shop:shop_".$id, 10, '手机');  // 会覆盖旧值

setnx + expire,需配合 LUA 脚本来保证原子性(下面只是普通用法,不涉及LUA脚本):

// 不存在则创建,存在不做任何操作
Redis::setnx('shop:shop_'.$id, 34);
// 设置过期时间
Redis::expire("shop:shop_".$id, 10); 	# 秒
Redis::pexpire("shop:shop_".$id, 10000); 	# 毫秒

一般使用第一种就够了,如果需要使用 redis 操作的命令较多,需配合使用 LUA 脚本实现原子性。

参考:Redis实现分布式锁

4.全局ID

int类型,incrby,利用原子性
incrby userid 1000
分库分表的场景,一次性拿一段

$id = Redis::incr('id'); 		# 递增 阶梯为1
$id = Redis::incrby('id', 2);	# 递增 阶梯为2

相关命令:
Redis::decr('id'); 		# 递减 阶梯为1
Redis::decrby('id', 2);	# 递减 阶梯为2

5.计数器

int类型,incr方法
 

例如:文章的阅读量、微博点赞数、允许一定的延迟,先写入Redis再定时同步到数据库。
 
计数功能应该是最适合 Redis 的使用场景之一了,因为它高频率读写的特征可以完全发挥 Redis 作为内存数据库的高效。在 Redis 的数据结构中,string、hash和sorted set都提供了incr方法用于原子性的自增操作,下面举例说明一下它们各自的使用场景:

  • 如果应用需要显示每天的注册用户数,便可以使用string作为计数器,设定一个名为REGISTERED_COUNT_TODAY的 key,并在初始化时给它设置一个到凌晨 0 点的过期时间,每当用户注册成功后便使用incr命令使该 key 增长 1,同时当每天凌晨 0 点后,这个计数器都会因为 key 过期使值清零。
  • 每条微博都有点赞数、评论数、转发数和浏览数四条属性,这时用hash进行计数会更好,将该计数器的 key 设为weibo:weibo_id,hash的 field 为like_number、comment_number、forward_number和view_number,在对应操作后通过hincrby使hash 中的 field 自增。
  • 如果应用有一个发帖排行榜的功能,便选择sorted set吧,将集合的 key 设为POST_RANK。当用户发帖后,使用zincrby将该用户 id 的 score 增长 1。sorted set会重新进行排序,用户所在排行榜的位置也就会得到实时的更新。

文章阅读量:

Redis::incr('article_read_num_'.$id);

6.限流

int类型,incr方法
以访问者的ip和其他信息作为key,访问一次增加一次计数,超过次数则返回false

限制用户访问次数:

$userid = 1;
$accessTime = 10; # 10s内最多访问20次
$$accessNum= 20;

$key = md5($_SERVER['REMOTE_ADDR'].$userid);
$access = Redis::get('access:'.$key);
if ($access && $access >= $accessNum){
	echo '用户访问超过限制';
}

Redis::incr('access:'.$key);
!$access && Redis::expire($key, $accessTime); // 设置锁的有效时间
echo '正常访问';

二、hash类型

1.存储对象

例如:商品的库存,价格,评论数,关注度经常变换;计数器(参考string计算器)

$id = 1;
//$shopInfo = Shop::find($id);
$shopInfo = [
	'id' => 1,
	'name' => "红米手机",
	'price' => 999.99,
	'stock' => 100,			# 库存
	'salesVolume' => 1000, 	# 销售量
];
foreach ($shopInfo as $key => $value){
	Redis::hset('shop:'.$id, $key, $value);
}

//卖出后,销售量加一,库存减一
Redis::hincrby('shop:'.$id, 'salesVolume', 1);
$stock = Redis::hget('shop:'.$id, 'stock'); # 由于hash类型没有递减命令,只能先取出计算再重新存入
$stock = $stock - 1 >= 0 ? $stock - 1 : 0;
Redis::hset('shop:'.$id, 'stock', $stock);

三、list类型

1.时间轴(Timeline)

list作为双向链表,不光可以作为队列使用。如果将它用作栈便可以成为一个公用的时间轴。当用户发完微博后,都通过lpush将它存放在一个 key 为LATEST_WEIBO的list中,之后便可以通过lrange取出当前最新的微博。

// 模拟用户发微博
for ($i = 1; $i < 10; $i++){
	Redis::lpush('LATEST_WEIBO', 'weibo_'.$i);
}

$list = Redis::lrange('LATEST_WEIBO', 0, 5);
dd($list);
//array:6 [▼
//  0 => "weibo_9"
//  1 => "weibo_8"
//  2 => "weibo_7"
//  3 => "weibo_6"
//  4 => "weibo_5"
//  5 => "weibo_4"
//]

2.消息队列

Redis 中list的数据结构实现是双向链表,所以可以非常便捷的应用于消息队列(生产者 / 消费者模型)。消息的生产者只需要通过lpush将消息放入 list,消费者便可以通过rpop取出该消息,并且可以保证消息的有序性。如果需要实现带有优先级的消息队列也可以选择sorted set。而pub/sub功能也可以用作发布者 / 订阅者模型的消息。无论使用何种方式,由于 Redis 拥有持久化功能,也不需要担心由于服务器故障导致消息丢失的情况。
 
List提供了两个阻塞的弹出操作:blpop/brpop,可以设置超时时间
 
blpop:blpop key1 timeout 移除并获取列表的第一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
brpop:brpop key1 timeout 移除并获取列表的最后一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

 
队列:先进先出:rpush blpop,左头右尾,右边进入队列,左边出队列
栈:先进后出:rpush brpop

用户评论后需要人工审核,需要先把评论放入待审核队列中,等待审核:
// 模拟用户评论,
for ($i = 1; $i < 10; $i++){
	Redis::lpush('comment', 'comment'.$i);
}

// 取出审核
$comment = Redis::brpop('comment');
$status = $this->verify($comment);
if (!$status){
	echo "审核失败";
} else {
	echo "审核通过";
}

四、set类型

1.抽奖

利用set结构的无序性,通过 Spop( Redis Spop 命令用于移除集合中的指定 key 的一个或多个随机元素,移除后会返回移除的元素。 ) 随机获得值

//填充奖池
Redis::sadd('jackpot', 'a', 'b', 'c', 'd', 'e', 'd'); // 会去重
//开始抽奖
$result = Redis::spop('jackpot');
dd($result); // a

2.点赞、签到、打卡

假如用户这条微博ID是 t1001,用户ID是 u3001,下面使用 redis的 集合 来实现对这条微博的点赞操作:

用 like:t1001 来维护 t1001 这条微博的所有点赞用户

// u3001 点赞了这条微博:
Redis::sadd('like:t1001', 'u3001');

// 取消点赞:
Redis::srem('like:t1001', 'u3001');

// 是否点赞:
Redis::sismember('like:t1001', 'u3001'); // true

// 点赞的所有用户:
Redis::smembers('like:t1001');
//array:1 [▼
//  0 => "u3001"
//]

// 点赞数:
Redis::scard('like:t1001'); // 1

3.好友关系、用户关注、推荐模型

可通过set的交集、并集、差集命令实现:

// A的关注和粉丝,B的关注和粉丝
Redis::sadd('A:follow', 'B', 'C', 'D'); 		# A的关注
Redis::sadd('A:fans', 'B', 'E', 'F', 'H');		# A的粉丝
Redis::sadd('B:follow', 'C', 'D', 'X', 'Y'); 	# B的关注
Redis::sadd('B:fans', 'B', 'E', 'Z');			# B的粉丝


// 与A互相关注的用户
$res = Redis::sinter('A:follow', 'A:fans');
dd($res);
//array:1 [▼
//  0 => "B"
//]        

// A和B共同关注的人
$res = Redis::sinter('A:follow', 'B:follow');
dd($res);
//array:2 [▼
//  0 => "D"
//  1 => "C"
//]

// A的关注人也关注了B
$res = Redis::sinter('A:follow', 'B:fans');
dd($res); # B也关注了自己
//array:1 [▼
//  0 => "B"
//]

// 推荐模型:通过关注人的关注进行推荐(差集)
// A 可能认识的人(A关注了B,给A推荐B的关注中A未关注的人)
$res = Redis::sdiff('B:follow', 'A:follow');
dd($res);
//array:2 [▼
//  0 => "X"
//  1 => "Y"
//]

4.倒排索引

倒排索引是构造搜索功能的最常见方式,在 Redis 中也可以通过set进行建立倒排索引,这里以搜索城市景点举例:
 
假设要搜索北京天安门,通过分词将这个词分为若干个前缀索引,有:北、北京、…门。将这些索引分别作为set的 key(例如:index:北)并存储北京的 id,倒排索引便建立好了。接下来只需要在搜索时通过关键词取出对应的set并得到其中的 id 即可。

// ‘北京天安门’ 分词后可得到下面数组:
$id = 1;
$terms = ['北','北京','北京天','北京天安','北京天安门','京','京天','京天安','京天安门','天','天安','天安门','安','安门','门'];
foreach ($terms as $v){
	Redis::sadd('index:'.$v, $id);
}

五、zset类型

1.排行榜

使用sorted set(有序set)和一个计算热度的算法便可以轻松打造一个热度排行榜,zrevrangebyscore可以得到以分数倒序排列的序列,zrank可以得到一个成员在该排行榜的位置(是分数正序排列时的位置,如果要获取倒序排列时的位置需要用zcard-zrank)。
 
id 为6001 的新闻点击数加1:
 
zincrby hotNews:20190926 1 n6001
 
获取今天点击最多的15条:
 
zrevrange hotNews:20190926 0 15 withscores

为每一天的新闻点击添加排行
// 比如20200101这一天刚开始就有四个新闻点击:
Redis::zadd('news:20200101', 1, 6001); # 新闻id为6001
Redis::zadd('news:20200101', 1, 6002);
Redis::zadd('news:20200101', 1, 6003);
Redis::zadd('news:20200101', 1, 6004);
Redis::zadd('news:20200101', 1, 6005);
// 也可以直接使用zincrby添加到集合中
Redis::zincrby('news:20200101', 1, 6001);

// 获取某一天前十条点击数最多的新闻
Redis::zrevrange('news:20200101', 0, 10);
$res = Redis::zrevrange('news:20200101', 0, 10);
dd($res);
//array:5 [▼
//  0 => "6001"
//  1 => "6005"
//  2 => "6004"
//  3 => "6003"
//  4 => "6002"
//]

// 获取排序位置
dd(Redis::zrank('news:20200101', 6001)); // 2

// 获取集合数元素个数
dd(Redis::zcard("news:20200101")); // 5

参考地址:Redis 16 个常见使用场景
命令详情可参考另一篇博客:Redis 基础命令

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值