/首页
/开源
/关于
我所认知的redis(一)--- 数据类型与应用场景
发表@2018-09-13 09:15:21
更新@2023-01-21 22:47:40
redis是我们工作中常用的kv型数据存储软件,尽管其单进程单线程,但得益于epoll依然保持着非常高的吞吐量。**redis中所有的操作都是原子性的**。文章只结合一些使用场景对redis的一些要点进行记录,不是redis手册的搬运工。[redis手册链接点击这里](http://www.redis.cn/ "redis手册链接点击这里")。 redis常用的数据结构一共有五种,如下所示: - **string,也就是字符串类型,这是redis中最基础的数据类型**。string是二进制安全的,也就是说它可以存储任意数据,甚至是某张图片或者某个语言的某个对象,容量上限是512MB。这个数据类型大家最常用就是最简单的set和get,一般大家用memcache就是这么一个用法。结合两个常用的业务场景简单说明下: - 在app server中可以存储用户token和用户uid的对应关系: ```php // 假如用户token是e10adc3949ba59abbe56e057f20f883e , 用户uid是21178 set e10adc3949ba59abbe56e057f20f883e 21178 // 获取这个token对应的user id get e10adc3949ba59abbe56e057f20f883e ``` - 统计投票: ```php // 利用incr decr命令,用 vote:uid 格式当作key,比如uid为1的用户的投票key为 vote:1 // incr vote:1,就会给 vote:1 累加1,如果没有 vote:1 这个key,则直接变为1 incr vote:1 // decr test,就会给test减1,一直到为0 ``` - 流量访问限制 ```php // 假如我们根据ip对ip开启访问流量限制,假入ip为 211.101.111.111 , 一秒钟内该ip最多允许访问三次 // 我们用 ip:timestamp 作为key,timestamp是指当前unix时间戳 // 伪代码如下,注意这次伪代码,不再是纯redis命令了 incr ip:1517207868 if ( get( ip:1517207868 ) > 3 ) { printf " 超过限制了 "; } ``` - **list,列表**。一维的队列,其中元素的类型依然是字符串,一个队列最多允许 2^32-1也就是4294967295个元素,**你可以按照一定地顺序对其进行排列**。对list的头部加入、弹出或者尾部加入、弹出操作。结合一个简单的实例说明一下,假如一个抢购系统,既然是抢购,想必还是要个先来后到的,所以新来的统统往队尾排,也就说FIFO(First Input First Ouput),redis命令(注意不是伪代码)模拟一下: ```php // 从右侧向一个名字为list的队列加入一个人,jim先来 rpush list jim // 然后tom来了 rpush list tom // 最后是lily rpush list lily // 购买开始了,开始从左侧取第一名 lpop list // 第二名 lpop list // 第三名 lpop list ``` 作为经验之谈,我建议大家在模拟这种有顺序队列的时候,将右侧当作队尾,将左侧当作队头,这样在使用lrange取出某个区间内所有元素的时候,避免使用负数下标容易出现失误。 除此之外,**list完全可以充当一个简单轻量级的消息队列**,在某些场合代替笨重庞大的rabbitmq、kafka等。 - **set,无序集合,元素内容依然为字符串,元素具备唯一性**。一个集合中,最多包含2^32-1个元素,除此之外,set还有一个很好的特性:就是往其中加入新的元素会自动去重,这也就意味着在向一个集合中添加元素前,是不需要判断该元素是否存在于集合中的。举个使用场景案例,就是我的关注,比如你关注了10个用户,你朋友关注了12个用户,可以通过redis可以很快速的存储起来,而在mysql中如果要实现这个功能,合理的表设计就很痛苦了。不仅如此,redis还可以很快将你和你朋友关注的好友取交集:也就是得到你和你朋友的共同关注,redis命令(注意不是伪代码)模拟一下: ```php // 假设关注的key格式为 fav:uid ,假如你的uid是1,你朋友的uid为2 // 你关注了5个人,uid分别是3 4 5 6 7 sadd fav:1 3 4 5 6 7 // 你朋友关注了5个人,uid分别是3 4 5 8 9 sadd fav:2 3 4 5 8 9 // 取出你的关注 smembers fav:1 // 查看你和朋友的共同关注 sinter fav:1 fav:2 ``` - **zset,有序集合,元素依然为字符串类型且依然唯一不重复,有序则是靠元素score实现的**,简单说:往一个zset中添加一个元素是需要score的。所以除了利用score进行排序外,**还可以对zset进行分页了**。一个使用场景,假如你是一名网红,每天都会有很多人关注你,然后你希望按照时间倒序每页5个人查看这些关注者,redis命令演示: ```php // 你的关注者key为 follower:1 // 分别有10个人在不同时间关注了你,10个人uid分别是3 6 9 12 15 16 17 18 19 22 // 我们用时间秒当作score,分别是1 2 3 4 5 6 7 8 9 10秒 zadd follower:1 1 3 zadd follower:1 2 6 zadd follower:1 3 9 zadd follower:1 4 12 zadd follower:1 5 15 zadd follower:1 6 16 zadd follower:1 7 17 zadd follower:1 8 18 zadd follower:1 9 19 zadd follower:1 10 22 // 按照score(也就是按照秒倒序排序)取5个 zrevrange follower:1 0 4 withscores ``` - **hash,哈希数据,非常适合存储一些对象**。一个hash结构最多可以存储2^32-1个键值对。举个简单例子,用来存储用户信息就很好。比如一个用户的信息资料有age、gender、username、signature等等,将这些信息存储到redis hash中将会是一个绝佳的选择,redis命令如下: ```php // 假设存放用户的key为 user // hmset可以一次性设置多个属性 hmset user username "jim" signature "I am stu" age 12 gender 2 // 也可以hset设置单个属性 hset user signature "I am boy" // 一次性获取所有属性 hgetall user // 获取单个属性 hget user username ``` 从上面的几个小案例看来,可以发现另一个问题就是redis中key的设计实际上是有些讲究的,这里说些我自己的一些经验: - key要尽量设计的短 - key虽然要短,但是前提最好还是能说明含义 - key之间可以用 ':' 当作分割符 假如要存储一个uid为1的用户信息,那么显然u:1看起来是最好的,但这样让人无法读出其含义,所以可能会有人想到account:1这个key,但这个跟user:1比起来似乎user:1要更胜一筹。