文章目录
前言
redis(全称:Remote Dictionary Server,即远程字典服务),近年来可以说是火遍了大江南北,它能干的事儿是真不少。最基本的,做为系统缓存我们可以使用它;我们还可以使用它维护一个高效的消息队列。然而Redis能够实现的功能远远不止于此,小小的键值对被玩出了无数的花样(当然这和Redis本身提供的丰富功能也脱不了关系)。
如果你已经迫不及待的想要使用Redis了,那么,这里有一些关于Redis你不得不知道的事儿
本篇不着重阐述原理,而是从最基本的API使用上进行较为详细的介绍,方便快速代入使用场景
为什么要使用Redis
- 最为重要的一点就是Redis速度够快,能够明显的提高系统性能。
- Redis能够非常简单的实现特定的业务场景,比如,排行榜,好友关系等
- 能够实现简单的消息队列
速度
Redis速度快的最主要的原因是它是“完全”基于内存的数据库(当然也提供了持久化),这使得 它避免了使用写磁盘的格式对内存数据接口编码的开销(磁盘的数据依旧是要读到内存中才能够被使用,这里面存在着很大的开销),故而直接从内存读取数据是要比读磁盘要快非常多。
另外,其他次要一些原因还有:
- Redis是由C语言实现的(语言级别速度的保证(´・ω・`) )
- 其线程模型为单线程,避免了线程切换的开销
- 网络方面使用epoll解决了高并发问题
- …
根据官方说法,Redis最高能够达到 10w OPS(operation per second)!
高效、丰富的数据结构
除了最基本的键值对,Redis提供了丰富的数据结构,包括
- 字符串(String)
- 哈希(Hash Tables)
- 列表(Linked List)
- 集合(Sets)
- 有序集合(Sorted Set)
其他还有一些基于上述结构实现的数据结构
- 位图 (Bit Map)
- HyperLogLog(使用超小内存(12k)实现唯一值,本质是字符串)
- GEO (地理信息定位)
持久化
虽然内存只要一断电就嗝屁,但是Redis也是提供持久化功能的,在内存上操作保证速度的同时,也提供了RDB和AOF两种方式进行持久化,将数据的更新异步的保存到磁盘上,无需担心数据的丢失了:)
其他
丰富的功能
- Redis支持多种编程语言
- 提供发布订阅,事务功能
- 支持编写Lua脚本
- 支持pipline
- 支持主从复制,分布式(Redis-Cluster),高可用(Redis-Sentinel)
简单
使用简约但不简单!
Redis API
下面通过介绍Redis API来多方面的展现Redis的强大功能
字符串
字符串是redis最基本的结构,很多redis的高级数据结构底层都是由字符串实现的,下面来看看redis字符串的API。
字符串的使用场景很多,最为基本的是作为缓存,另外还有计数器(字符串提供的自增),分布式锁等等。
- get/del操作 O(1)
# 获取key对应的value
get key
# 删除key
del key
- set操作 O(1)
# 设置一个值(无论是否存在)
set key value
# key不存在时才做设置
setnx key value
# key存在时才设置
set key value xx
- mget O(n)
# 批量获取key值
mget key1 key2 key3
对于redis来说,所有的key都是字符串,value则类型多样
对于value字符串来说,value可以是字符串,也可以是数字和二进制类型,这两种类型是在redis内部自动转换的,之后将要提到的位图(bitmp)即是使用字符串的二进制形式所实现的。
单个字符串value的大小小于等于512MB,通常一个value限制在100k以内是比较合适的。
对于批量获取操作mget来说,如果有需要多次取值的操作,不妨使用mget进行批量获取,相比于多次get更加的高效。
- 整型操作
# key自增1,如果key不存在,自增后get(key)= 1
incr key
# 同上相反 ,自减
decr key
# key自增k,如果key不存在,自增后get(key)= k
incrby key k
# key 自减k,其他同incrby相反
decrby key k
前面说的计数器,就可以用上面的自增API进行实现,非常的方便:)
- 其他一些操作
# set key newvalue并返回旧的value
getset key newvalue
# 将value追加到旧的value
append key value
# 返回字符串长度(注意中文)
strlen key
# 增加key对应的值
incrbyfloat key value
# 获取字符串指定下标所有的值
getrange key start end
# 设置,其他同上
setrange key index value
Hash
redis的hash是一个mapMap的结构,其结构长这样
hash的key依旧是字符串,其value分为两个部分:field,val,其中field的值不能相同,val值可以相同(如果理解哈希概念的话,这个地方应该不会陌生),你可以把它理解为编程语言中的哈希表,字典等数据结构。其实redis本身就是一个大字典,hash相当于一个嵌套字典了。
举个🌰, 我们可以用hash来存储一个用户的相关信息,像这样
下面我们来看看redis hash结构的相关API
# 获取hash key对应的field的value
hget key field
# 设置hash key对应的field的value
hset key field value
# 删除hash key对应的field的value
hdel key field
# 判断hash key是否有field
hexists key field
# 获取hash key field的数量
hlen key
# 批量获取hash key的一批field对应的值
hmget key field1 field2 ... fieldN
# 批量设置hash key的一批field value
hmset key field1 value field2 value2...fieldN valueN
# 返回hash key 对应所有的field 和 value(谨慎使用), 数据量大时会阻塞
hgetall key
# 返回hash key对应所有field的value
hvals key
# 返回hash key对应的所有field
hkeys key
# 设置hash key对应的field的value(如field已经存在则失败)
hsetnx key filed value
# hash key对应的field的value自增inCounter
hincrby key field intCounter
# hincrby浮点数版
hincrbyfloat key field floatCounter
使用hash保存复杂信息
涉及到复杂信息更新时,我们通常可以有限考虑使用redis的hash结构,从上面的用户信息例子可以看出,hash结构存储往往更加直观,并且能够节省内存(这是由其数据结构决定的),最关键的是,它可以部分更新。同样的实现用户信息存储则必须全量更新才行。
但是需要注意的是,我们可以为redis数据设置过期时间,而hash结构的过期精度只支持到key级别,不能为field单独设置过期时间
list
直观一点,就是一个列表结构,像这样
你可以直接把它当成编程语言中的一个列表结构(尽管内部实现可能不一致):有序、内部元素可重复,可以在列表左边和右边插入和弹出元素。
看看redis list的API
# 从列表右端插入值(1-N个)
# e.g. rpush listkey c b a: 结果 c—>b—>a
rpush key value1 value2... valueN
# 和rpush相反
lpush
# 从列表左侧弹出一个item
lpop key
# 与lpop相反
rpop
# 根据count值,从列表中删除所有value相等的项
# count > 0,从左到右,删除最多count个value相等的项
lrem key count value
# 在list指定的值前 | 后插入newValue
linsert key before | after value newValue
# 按照索引范围修剪列表(start end为需要保留的范围)
ltrim key start end
# 获取列表指定索引范围所有item(包含end)
lrange key start end
# 获取列表指定索引的item
lindex key
# 获取列表长度
llen key
# 设置列表指定索引值为newValue
lset key index newValue
# lpop阻塞版本,timeout是阻塞超时时间,timeout=0为永远不阻塞(对生产者消费者模型,消息队列实现有帮助)
blpop key timeout
# 与blpop相反
brpop key timeout
通过使用redis的list API,我们可以非常方便的实现一些功能,例如
list的使用场景(栈、队列、消息队列…)
- 使用
lpush
+lpop
可以实现一个简单的栈(后进先出) - 使用
lpush
+rpop
可以实现一个简单的队列(先进先出) - 使用
lpush
+ltrim
可以实现一个固定数量的列表 - 使用
lpush
+brpop
可以实现一个消息队列
set 集合
我们直接类比到编程语言的集合数据结构(比如python的set),它长这样
集合内部的元素是无序的且不重复的,多个集合直接我们可以进行一些类似并集,交集的操作,话不多说,直接来看看redis提供的set API
=====================集合内操作===========================
# 添加集合元素
sadd key value1 value2
# 删除集合元素
srem key value1 value2
# 计算集合大小
scard key
# 判断value是否在集合中(返回1表示存在)
sismember key value
# 从集合中随机挑count个元素
srandmember key count
# 从集合中随机弹出一个元素
spop key
# 获取集合所有元素(小心使用, 可能会阻塞)
smembers key
=====================集合间操作===========================
# 取value交集
sinter key1 key2
# 取所有不同value
sdiff key1 key2
# 取所有value
sunion key1 key2
# 将上述集合间操作结果保存至destkey
sinter|sdiff|sunion + store destkey
set的使用场景(抽奖,点赞,共同关注…)
这是微博上随便截取的一个抽奖微博截图,如果让我们用redis来实现一个抽奖,那么,使用set就是一个非常好的选择。
假设我们的有一个key叫users,他对应的value是一个set,里面存储了所有用户的id,那么,我们就可以使用spop
和srandmember
来很方便的实现抽奖功能。
其他的业务场景比如知乎文章的赞同,反对,我们可以使用集合的API来实现(对于每一篇文章,当用户点赞时,调用sadd将用户id推进集合当中)
还有类似于共同关注的功能,我们也可以很方便的使用集合的取交集和并集的操作来完成。
zset(有序集合)
zset同样也是一个集合,但是与上节的set相比,zset是有序的,redis中的zset结构长这样:
如果说set的value是一个个游离的单独元素的话,那么zset就是给这些元素打上了一个序号(score)
老规矩,先来看看API
# 添加socre和value(可以是多对)
zadd key score1 value1 score2 value2
# 删除元素(可以是多个)
zrem key value1 value2
# 返回元素的score
zscore key value
# 增加或减少元素的socre
zincrby key increSocre value
# 返回元素的总个数
zcard key
# 返回value的排名(类似索引id)
zrank key value
# 返回范围内的 value score ,-1代表最后一个
zrange key start end [WITHSCORES]
# 返回指定分数范围内的升序元素[分值]
zrangebyscore key minScore maxScore [WITHSCORES]
# 获取有序集合内在指定分数范围内的个数
zcount key minScore maxScore
# 删除指定排名内的升序元素
zremrangebyrank key start end
# 删除指定分数内的升序元素
zremrangebyscore key minScore maxScore
使用zset实现一个排行榜
这是一个微博的热搜截图,在这里热搜数据相当于一个zset,图上的4685796, 4384536,4368888…就是zset的score(搜索量),具体的信息就是一个个value,zset内部会根据socore进行排序, 这里的排名跟就是根据这个socre来的。你看,通过对zset API的灵活运用,一个基本的排名功能是不是就出来了呢:)
后记
本篇只是对redis中几种典型的数据结构进行了一番介绍,在下一章中,我们还将进行一些redis高级特性的讨论(比如redis的慢查询,管道pipline,发布订阅,位图,HyperLoglog以及GEO等等),这些特性会给我们在日常开发当中提供许多的便利~