前言:
redis作为被广泛使用的非关系型数据,一直都是一门热门技术。作为后端开发工程师,redis的学习肯定是绕不过的。那么今天就分解下redis的各种数据结构及一些应用场景
头图
数据结构:
既然要分解数据结构,那么首先我么需要知道redis有哪些数据结构。
不同于memcache,redis的数据结构更加丰富。
- String:字符串
- Hash:散列
- List:列表
- Set:集合
- Sorted Set:有序集合
String
最基本的String类型也是最常用的类型。String类型的操作有有很多我介绍一些我比较常用的
命令 | 介绍 |
---|---|
SET key value | 设置指定key的值 |
GET key value | 获取指定key的值 |
SETEX key seconds value | 将值value关联到key,并将key的过期时间设为seconds(以秒为单位) |
INCR key | 将key中储存的数字增一(如果没有这个key,就创建一个并初始化值为1) |
DECR key | 将key中存储的数字减一(如果没有这个key,就创建一个并初始化值为-1) |
- SET key value 就是将一个字符串存储到一个键上面去。注意使用这个命令是不能设置过期时间的,往往我们在开发中主要使用编程语言去操作redis。set方法中都是可以选择设过期时间和不设置过期时间。其实在底层是使用的不同的命令
- GET key value get命令算是String 类型中最常用的命令了,作用就是将存储在数据库中的数据读取出来
- SETEX key seconds value 同SET 功能大致一样,主要是多了一个可以设置过期时间的功能。大多数情况我们还是会对缓存做过期时间的设置
get,set的主要应用场景就在热数据的储存。比如一个接口,数据很少发生变化,但是获取的频率又非常的高。这个时候我们就可以使用redis将这个数据存储起来。在频繁读取数据的时候直接去redis中读,而不走数据库
$cacheName="hot-cache-name";
$cacheData=$redisClient->get($cacheName);
if(empty($cacheData)){
//如果没有缓存,则从数据库里去取数
$hotService=new HotService();
$data=$hotService->getData();
//由于使用的是redis的String类型。但往往我们的数据都是数组或者对象。那么只需要做一下序列化就可以完美存入redis了
$redisClient->set($cacheName,json_encode($data),(60*60));//设置1小时的缓存过期时间
}else{
// 如果缓存不为空的话,就像数据反序列化为数组。
$data=json_decode($cacheData,true);
}
return $data;
- INCR key 将key存储的数字+1。且返回+1后的结果
- DECR key 与INCR相反,是将key存储的数字-1。且返回-1后的结果
INCR 和 DECR 往往用在记数上面。在INCR可以用来统计点赞。DECR可以用来做错误次数的拦截下面搞两个例子来说明
- 简单的点赞。对用户点赞直接统计到缓存中,再使用计划任务将缓存中的数据同步到关系型数据库中
$userId=1;//用户id
$cacheName="good-num-user-".$userId;
$result=$redisClient->incr($cacheName);
return $result;
- 密码错误拦截,如果在2分钟内密码错误超过三次就拦截掉,不走数据库验证了
$ip=get_user_ip();//获取用户id的方法
$cacheName=$ip;
$times=$redisClient->get($cacheName);
if(empty($times)){
//如果没有数据则说明在2分钟内是第一次。那么就设置一个2分钟过期的缓存
$redisClient->set($cacheName,3,(60*2));
}else{
//判断是否超过三次
if($times==-1){//为什么是-1。因为3减3次1 就是-1了
throw new Exception("对不起,你尝试密码次数过多");
}
}
//这里假装是判断用户密码是否正确
$checkResult=$userService->checkPassword($userName,$password);
if(!$checkResult){
$redisClient->decr($cacheName);
throw new Exception("密码错误,请重试");
}
return "登录成功'
HASH
Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。
Redis 中每个 hash 可以存储 232 - 1 键值对(40多亿)。
命令 | 介绍 |
---|---|
HGET key field | 获取存储在哈希表中指定字段的值 |
HGETALL key | 获取某个hash表的所有字段和值 |
HLEN key | 获取hash表中字段的数量 |
HSET key field value | 将哈希表 key 中的字段 field 的值设为 value |
HVALS key | 获取哈希表中所有值 |
HDEL key field | 删除hash中某个字段和对应的值 |
HEXISTS key field | 判断一个字段是否存在 |
hash非常适合存储易变的对象。当一个对象不怎么变化的时候,大可以将对象json序列化后存储在String中。但是如果这个对象的属性值经常变化。那么你就需要将对象反序列化后,修改,然后再序列化存储。麻烦不说,还会带来线程安全的问题。这个时候选用hash就是个不错的办法
我们先设定一个电商购物车的场景。购物车里面有三个重要的要素
- 哪个用户的购物车
- 购物车里面有哪些商品
- 每件商品的数量
然后我们使用hash来玩一下这个购物车
1.新增一个商品到购物车中或者新增一件商品的数量
$goodsId=1;//商品id
$goodsNum=2;//商品数量
$userId=1;//用户id
//判断这件商品是否存在
$result=$redisClient -> hExists($userId,$goodsId);
//如果存在
if($result){
//读取出原本存在购物车中的商品数量
$oldGoodsNum=$redistClient->hGet($userId,$goodsId);
//加上这次会话中加入的商品数量
$goodsNum+=$oldGoodsNum;
}
$redisClient -> hSet($userId,$goodsId,$goodsNum);
- 将某件商品移除购物车
$delGoodsId=1;
$userId=1;
$redisClient->del($userId,$delGoodsId);
- 统计商品数(仅商品数,不是商品件数)
$userId=1;
$goodsNum=$redisClient->hLen($userId);
- 减少一件商品的数量
$goodsId=1;//商品id
$goodsNum=2;//减少的商品数量
$userId=1;//用户id
/判断这件商品是否存在
$result=$redisClient -> hExists($userId,$goodsId);
//如果存在
if($result){
//读取出原本存在购物车中的商品数量
$oldGoodsNum=$redistClient->hGet($userId,$goodsId);
//判断原商品数量减少之后是否小于0
if($oldGoodsNum>=$goodsNum){
//参数合法
$newGoodsNum=$oldGoodsNum-$goodsNum;
//将新商品数量存入hash表
$redisClient -> hSet($userId,$goodsId,$goodsNum);
}else{
//这里除了抛异常之外也可以将用户购物车中这件商品删除
throw new Exception("减少商品数大于购物车的商品数");
}
}else{
throw new Exception("商品不存在")
}
- 获取购物车所有信息
$userId=1;
$allInfo=$redisClient->hGetAll($userId);
List
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。
命令 | 介绍 |
---|---|
LPUSH key value [value2] | 将一个或多个值插入到列表头部 |
RPUSH key value [value2] | 将一个或多个值插入到列表尾部 |
LSET key index value | 通过索引设置列表元素的值 |
RPOP key | 移除列表的最后一个元素,返回值为移除的元素 |
LPOP key | 移出并获取列表的第一个元素 |
其实List的使用,在我之前写的redis秒杀中也有提到。我还是拿那个案例来举例吧。
首先场景是有20件秒杀商品,价格非常犀利,有1万人来抢。我们就用redis的列表来承接秒杀的任务
秒杀分为两步,第一步是装载商品
//首先从数据库中读取需要秒杀的商品id
$killGoodsService=new KillService();
$goodsIdArr=$killGoodsService->getKillGoods($killId);
$cacheListName="killGoodsList";
foreach($goodsIdArr as $goodsId){
$redisClient->lPush($cacheListName,$goodsId);
}
第二部就是准备接受秒杀
//假装是用户的唯一标识
$uuid=md5(uniqid('user').time());
$listKey="killGoodsList";//装有秒杀商品的list
$orderKey="buyOrder";//用来装秒杀订单的hash key名字
$failUserNum="failUserNum";//用来记录秒杀失败的用人数
//从列表中取出商品(因为redis是单线程的原因,无论多大的压力,都不会出现,一个商品被两个人买到的情况)
if ($goodsId=$redis->lPop($listKey)) {
//秒杀成功
//将幸运用户存在集合中
$redis->hSet($orderKey,$goodsId,$uuid);
}else{
//秒杀失败
//将失败用户计数
$redis->incr($failUserNum);
}
echo "SUCCESS";
SET
Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。
命令 | 介绍 |
---|---|
SADD key member1 [member2] | 向集合添加一个或多个成员 |
SDIFF key [key2] | 返回给定所有集合的差集 |
SINTER key [key2] | 返回给定所有集合的交集 |
SPOP key | 移除并返回集合中的一个随机元素 |
集合的应用,我举两个比较简单的。
一 利用集合的无序性,用来抽奖。将9999个代表为未中间的数据放进去。再放一个代表中奖的数据。当用户来请求抽奖的时候,就从集合里面取一个,判断是否中奖。
//首先把抽奖数字放进集合
$prizeSet="prizeCacheSet";
for($i=0;$I<=10000,$I++){
$redisClient->sAdd($prizeSet,$i);
}
//用户进入抽奖环节的时候只需要,从集合中取出随机成员,在比对中奖号码判断是否中奖
$userId=1;
$prizeSet="prizeCacheSet";
$luckNum=$redisClient->sPop($prizeSet);
if($luckNum==999){
//中奖啦,下面写中奖逻辑
return "中奖啦";
}else{
return "很遗憾您没中奖";
}
二 使用集合来做共同好友和二度人脉的推荐。集合提供了多个集合取交集和差集的能力。共同好友的逻辑很简单,就是将两个或多个的好友拿来取合集,就是他们的共同好友了。二度人脉就可以将用户A的所有好友的好友拿出来和用户A的好友取差集。就能取到用户A的二度人脉,就可以做一个好友推荐
$userAFriend=array("zhangsan","lisi","wangwu");
$userBFriend=array("wangwu","zhubajie","sunwukong");
//将用户A和用户B的好友放入各自的集合
foreach($userAFriend as $friend){
$redisClient->sAdd("userAFriendSet",$friend);
}
foreach($userBFriend as $friend){
$redisClient->sAdd("userBFriendSet",$friend);
}
//取共同好友
$commonFriends=$redisClient-> sInter("userAFriendSet","userBFriendSet");
$userA="zhangsan";
$friendService=new FriendService();
//假装去取了userA的所有好友
$friends=$friendService->getUserFriends($userA);
//假装去取了userA的所有好友的好友
$secondFriends=$friendService->getUserSecondNetwork($userA);
//调用将数组放入结合的方法
$redisUntil->makeArrToSet("friendSet{$userA}",$friends);
$redisUntil->makeArrToSet("secondFriendsSet{$userA}",$secondFriends);
//获取10个还未成为朋友的人脉做推荐
$recommendFriends=$redisClient-> sDiff("friendSet{$userA}","secondFriendsSet{$userA}");
shuffle($recommendFriends);
return array_slice($recommendFriends,0,10);
ZSet
Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
有序集合的成员是唯一的,但分数(score)却可以重复。
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。
命令 | 介绍 |
---|---|
ZADD key score1 member1 [score2 member2] | 向有序集合添加一个或多个成员,或者更新已存在成员的分数 |
ZREVRANGE key start stop [WITHSCORES] | 返回有序集中指定区间内的成员,通过索引,分数从高到低 |
ZREVRANGEBYSCORE key max min [WITHSCORES] | 返回有序集中指定分数区间内的成员,分数从高到低排序 |
由于有序集合我用过的场景也不多。就介绍一个常见的排行版吧。排行榜肯定大家都非常熟悉了,尤其在游戏中,基本是一个固定元素了。有序集合的 有序性,唯一性,确定性 正好瞒住我们的需求
//现在5位同学,分别是小梅,小明,小米,小猫,小马
//将他们的考试分数计入有序集合
$redisClient->zAdd("testScore",95,"小梅");
$redisClient->zAdd("testScore",92,"小明");
$redisClient->zAdd("testScore",90,"小米");
$redisClient->zAdd("testScore",63,"小猫");
$redisClient->zAdd("testScore",12,"小马");
//通过ZREVRANGE 命令对分数排序,获得排行版
//如果指定0,-1 则代表获取所有同学的信息
$allRankingList=$redisClient->zRevRange('testScore', 0, -1, true);
//只取前三名的成绩
$topThree=$redisClient->zRevRang('testScore', 0, 2, true);
结语
这篇redis介绍文章就写到这里,如果有哪里写的不对的地方,欢迎大佬指出。如果你觉得这篇文章写的不错的话,欢迎点赞加关注。谢谢大家支持
网友评论