IT@程序员猿媛

读了这篇,还怕掌握不了redis吗?

2019-11-03  本文已影响0人  祥哥去哪里

前言:

redis作为被广泛使用的非关系型数据,一直都是一门热门技术。作为后端开发工程师,redis的学习肯定是绕不过的。那么今天就分解下redis的各种数据结构及一些应用场景


头图

数据结构:

既然要分解数据结构,那么首先我么需要知道redis有哪些数据结构。
不同于memcache,redis的数据结构更加丰富。

  1. String:字符串
  2. Hash:散列
  3. List:列表
  4. Set:集合
  5. 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)

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 和 DECR 往往用在记数上面。在INCR可以用来统计点赞。DECR可以用来做错误次数的拦截下面搞两个例子来说明

  1. 简单的点赞。对用户点赞直接统计到缓存中,再使用计划任务将缓存中的数据同步到关系型数据库中
$userId=1;//用户id
$cacheName="good-num-user-".$userId;
$result=$redisClient->incr($cacheName);
return $result;
  1. 密码错误拦截,如果在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就是个不错的办法

我们先设定一个电商购物车的场景。购物车里面有三个重要的要素

  1. 哪个用户的购物车
  2. 购物车里面有哪些商品
  3. 每件商品的数量

然后我们使用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);
  1. 将某件商品移除购物车
$delGoodsId=1;
$userId=1;
$redisClient->del($userId,$delGoodsId);
  1. 统计商品数(仅商品数,不是商品件数)
$userId=1;
$goodsNum=$redisClient->hLen($userId);
  1. 减少一件商品的数量
$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("商品不存在")
}
  1. 获取购物车所有信息
$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介绍文章就写到这里,如果有哪里写的不对的地方,欢迎大佬指出。如果你觉得这篇文章写的不错的话,欢迎点赞加关注。谢谢大家支持

上一篇 下一篇

猜你喜欢

热点阅读