万字好文!带你入门 redis
- 数据库
- 2023-01-23
引言|本文将会从:Redis 使用场景与介绍 -> 数据结构与简单使用 -> 小功能大用处 -> 持久化、主从同步与缓存设计 -> 知识拓展 来书写,初学的童鞋只要能记住 Redis 是用来干嘛,各功能的使用场景有哪些,然后对 Redis 有个大概的认识就好啦,剩下的以后有需要的时候再来查看和实践吧!
一、Redis 介绍
Redis 是什么?
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。
Redis 支持多种类型的数据结构,如 字符串(strings),散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) ,范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。
Redis 内置了复制(replication),LUA 脚本(Lua scripting),LRU驱动事件(LRU eviction),事务(transactions)和不同级别的 磁盘持久化(persistence),
Redis 通过 哨兵(Sentinel) 和自动分区(Cluster)提供高可用性(high availability)。
Redis 特性
速度快
单节点读 110000次/s,写81000次/s
数据存放内存中
用C语言实现,离操作系统更近
单线程架构,6.0开始支持多线程(CPU、IO读写负荷)
持久化
数据的更新将异步地保存到硬盘(RDB和 AOF)
多种数据结构
不仅仅支持简单的 key-value 类型数据,还支持:字符串、hash、列表、集合、有序集合,
支持多种编程语言
功能丰富
HyperLogLog、GEO、发布订阅、Lua脚本、事务、Pipeline、Bitmaps,key 过期
简单稳定
源码少、单线程模型
主从复制
Redis 支持数据的备份(master-slave)与集群(分片存储),以及拥有哨兵监控机制。
Redis 的所有操作都是原子性的,同时 Redis 还支持对几个操作合并后的原子性执行。
Redis 典型使用场景
缓存:
计数器:
消息队列:
排行榜:
社交网络:
Redis 高并发原理
Redis是纯内存数据库,一般都是简单的存取操作,线程占用的时间很多,时间的花费主要集中在IO上,所以读取速度快
Redis使用的是非阻塞 IO,IO 多路复用,使用了单线程来轮询描述符,将数据库的开、关、读、写都转换成了事件,减少了线程切换时上下文的切换和竞争。
Redis采用了单线程的模型,保证了每个操作的原子性,也减少了线程的上下文切换和竞争。
Redis存储结构多样化,不同的数据结构对数据存储进行了优化,如压缩表,对短数据进行压缩存储,再如,跳表,使用有序的数据结构加快读取的速度。
Redis采用自己实现的事件分离器,效率比较高,内部采用非阻塞的执行方式,吞吐能力比较大。
Redis 安装
这里只提供 linux 版本的安装部署
下载 Redis进入官网找到下载地址:https://redis.io/download
右键 Download 按钮,选择复制链接地址,然后进入 linux 的 shell 控制台:输入 wget 将上面复制的下载链接粘贴上,如下命令:
回车后等待下载完毕。
解压并安装 Redis下载完成后需要将压缩文件解压,输入以下命令解压到当前目录:
解压后在根目录上输入 ls 列出所有目录会发现与下载 redis 之前多了一个 redis-6.2.4.tar.gz 文件和 redis-6.2.4 的目录。
移动 Redis 目录(可选)若你不想在下载的目录安装 Redis,可以将 Redis 移动到特定目录安装,我习惯放在 ‘/usr/local/’ 目录下,所以我这里输入命令将目前在 ‘/root’ 目录下的 redis-6.2.4 文件夹更改目录,同时修改其名字为 redis:
cd 到 /usr/local 目录下输入 ls 命令可以查询到当前目录已经多了一个 redis 子目录,同时 /root 目录下已经没有 redis-6.2.4 文件:
编译cd 到 /usr/local/redis 目录,输入命令 make 执行编译命令,接下来控制台会输出各种编译过程中输出的内容:
最终运行结果如下:
安装输入以下命令:
这里多了一个关键字 PREFIX= 这个关键字的作用是编译的时候用于指定程序存放的路径。比如我们现在就是指定了 redis 必须存放在 /usr/local/redis 目录。假设不添加该关键字 linux 会将可执行文件存放在 /usr/local/bin 目录,库文件会存放在 /usr/local/lib 目录。配置文件会存放在 /usr/local/etc 目录。其他的资源文件会存放在 usr/local/share 目录。这里指定好目录也方便后续的卸载,后续直接 rm -rf /usr/local/redis 即可删除 Redis。
执行结果如下图:
到此为止,Redis 已经安装完毕,可以开始使用了~
Redis 启动
根据上面的操作已经将 redis 安装完成了。在目录 ‘/usr/local/redis’ 输入下面命令启动 redis:
上面的启动方式是采取后台进程方式,下面是采取显示启动方式(如在配置文件设置了 daemonize 属性为 yes 则跟后台进程方式启动其实一样):
两种方式区别无非是有无带符号&的区别。redis-server 后面是配置文件,目的是根据该配置文件的配置启动 redis 服务。redis.conf 配置文件允许自定义多个配置文件,通过启动时指定读取哪个即可。
启动可以概括为:
最简默认启动
安装后在 bin 目录下直接执行 redis-server
验证(ps –aux | grep redis)
动态参数启动(可配置一下参数,例如指定端口)
./bin/redis-server –port 6380
配置文件启动
./bin/redis-server& ./redis.conf
生产环境一般选择配置启动
单机多实例配置文件可以用端口区分开
注:若在进行 redis 命令操作,直接在 redis 中的 bin 目录下运行 redis-cli 命令即可,若开启了多个则需要加上对应的端口参数:
若运行 redis-cli 提示不未安装,则安装一下即可:
redis.conf 配置文件
在目录 /usr/local/redis 下有一个 redis.conf 的配置文件。我们上面启动方式就是执行了该配置文件的配置运行的。我们可以通过 cat、vim、less 等 linux 内置的读取命令读取该文件。
这里列举下比较重要的配置项:
配置项名称
配置项值范围
说明
这里我要将 daemonize 改为 yes,不然我每次启动都得在 redis-server 命令后面加符号 &,不这样操作则只要回到 linux 控制台则 redis 服务会自动关闭,同时也将 bind 注释,将p rotected-mode 设置为 no。这样启动后我就可以在外网访问了。修改方式通过 vim 或者你喜欢的方式即可:
通过 /daemonize 查找到属性,默认是 no,更改为 yes 即可。(通过/关键字查找出现多个结果则使用 n 字符切换到下一个即可,按 i 可以开始编辑,ESC 退出编辑模式,输入 :wq 命令保存并退出),如下图:
其他属性也是同样方式查找和编辑即可。
安装部署部分参考:https://www.cnblogs.com/hunanzp/p/12304622.html
二、Redis 数据结构与命令使用
Redis 的数据结构有:string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集 合)。但这些只是Redis 对外的数据结构,实际上每种数据结构都有自己底层的内部编码实现,而且是多种实现, 这样 Redis 会在合适的场景选择合适的内部编码。
可以看到每种数据结构都有两种以上的内部编码实现,例如 list 数据结 构包含了 linkedlist 和 ziplist 两种内部编码。同时,有些内部编码,例如 ziplist, 可以作为多种外部数据结构的内部实现,可以通过 object encoding 命令查询内部编码。
Redis 所有的数据结构都是以唯一的 key 字符串作为名称,然后通过这个唯一 key 值来获取相应的 value 数据。不同类型的数据结 构的差异就在于 value 的结构不一样。
通用全局命令
常用全局命令
keys:查看所有键
dbsize:键总数
exists key:检查键是否存在
del key [key ...]:删除键
expire key seconds:键过期
ttl key: 通过 ttl 命令观察键键的剩余过期时间
type key:键的数据结构类型
简单使用截图
根据上面的命令解释,大家应该比较容易看懂截图里面的所有命令含义,这里就不过多解释了。
字符串使用
字符串 string 是 Redis 最简单的数据结构。Redis 的字符串是动态字符串,是可以修改的字符串,内部结构实现上类似于 Java 的 ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配。
字符串结构使用非常广泛,一个常见的用途就是缓存用户信息。我们将用户信息结构体 使用 JSON 序列化成字符串,然后将序列化后的字符串塞进 Redis 来缓存。同样,取用户 信息会经过一次反序列化的过程。
常用字符串命令
set key value [ex seconds] [px milliseconds] [nx|xx]: 设置值,返回 ok 表示成功
ex seconds:为键设置秒级过期时间。
px milliseconds:为键设置毫秒级过期时间。
nx:键必须不存在,才可以设置成功,用于添加。可单独用 setnx 命令替代
xx:与nx相反,键必须存在,才可以设置成功,用于更新。可单独用 setxx 命令替代
get key:获取值
mset key value [key value ...]:批量设置值,批量操作命令可以有效提高业务处理效率
mget key [key ...]:批量获取值,批量操作命令可以有效提高业务处理效率
incr key:计数,返回结果分 3 种情况:
值不是整数,返回错误。
值是整数,返回自增后的结果。
键不存在,按照值为0自增,返回结果为1。
decr(自减)、incrby(自增指定数字)、 decrby(自减指定数字)
字符串简单使用截图
根据上面的命令解释,大家应该比较容易看懂截图里面的所有命令含义,这里就不过多解释了。
字符串使用场景
缓存数据,提高查询性能。比如存储登录用户信息、电商中存储商品信息
可以做计数器(想知道什么时候封锁一个IP地址(访问超过几次)),短信限流
共享 Session,例如:一个分布式Web服务将用户的Session信息(例如用户登录信息)保存在各自服务器中,这样会造成一个问题,出于负载均衡的考虑,分布式服务会将用户的访问均衡到不同服务器上,用户刷新一次访问可 能会发现需要重新登录,为了解决这个问题,可以使用Redis将用户的Session进行集中管理,在这种模式下只要保证Redis是高可用和扩展性的,每次用户 更新或者查询登录信息都直接从Redis中集中获取,如图:
哈希 hash
哈希相当于Java中的HashMap,以及 Js 中的 Map,内部是无序字典。实现原理跟HashMap一致。一个哈希表有多个节点,每个节点保存一个键值对。
与Java中的HashMap不同的是,rehash 的方式不一样,因为 Java 的 HashMap 在字典很大时,rehash 是个耗时的操作,需要一次性全部 rehash。
Redis 为了高性能,不能堵塞服务,所以采用了渐进式 rehash 策略。
渐进式 rehash 会在 rehash 的同时,保留新旧两个 hash 结构,查询时会同时查询两个 hash 结构,然后在后续的定时任务中以及 hash 操作指令中,循序渐进地将旧 hash 的内容一点点迁移到新的 hash 结构中。当搬迁完成了,就会使用新的hash结构取而代之。
当 hash 移除了最后一个元素之后,该数据结构自动被删除,内存被回收。
常用哈希命令
hset key field value:设置值
hget key field:获取值
hdel key field [field ...]:删除field
hlen key:计算field个数
hmset key field value [field value ...]:批量设置field-value
hmget key field [field ...]:批量获取field-value
hexists key field:判断field是否存在
hkeys key:获取所有field
hvals key:获取所有value
hgetall key:获取所有的field-value
incrbyfloat和hincrbyfloat:就像incrby和incrbyfloat命令一样,但是它们的作 用域是 filed
哈希简单使用截图
根据上面的命令解释,大家应该比较容易看懂截图里面的所有命令含义,这里同样不过多解释了
哈希使用场景
Hash也可以同于对象存储,比如存储用户信息,与字符串不一样的是,字符串是需要将对象进行序列化(比如 json 序列化)之后才能保存,而 Hash 则可以讲用户对象的每个字段单独存储,这样就能节省序列化和反序列的时间。如下:
此外还可以保存用户的购买记录,比如 key 为用户 id,field 为商品i d,value 为商品数量。同样还可以用于购物车数据的存储,比如 key 为用户 id,field 为商品 id,value 为购买数量等等:
列表(lists)
Redis 中的 lists 相当于 Java 中的 LinkedList,实现原理是一个双向链表(其底层是一个快速列表),即可以支持反向查找和遍历,更方便操作。插入和删除操作非常快,时间复杂度为 O(1),但是索引定位很慢,时间复杂度为 O(n)。
常用列表命令
rpush key value [value ...]:从右边插入元素
lpush key value [value ...]:从左边插入元素
linsert key before|after pivot value:向某个元素前或者后插入元素
lrange key start end:获取指定范围内的元素列表,lrange key 0 -1可以从左到右获取列表的所有元素
lindex key index:获取列表指定索引下标的元素
llen key:获取列表长度
lpop key:从列表左侧弹出元素
rpop key:从列表右侧弹出
lrem key count value:删除指定元素,lrem命令会从列表中找到等于value的元素进行删除,根据count的不同 分为三种情况:
·count>0,从左到右,删除最多count个元素。
count<0,从右到左,删除最多count绝对值个元素。
count=0,删除所有。
ltrim key start end:按照索引范围修剪列表
lset key index newValue:修改指定索引下标的元素
blpop key [key ...] timeout 和 brpop key [key ...] timeout:阻塞式弹出
列表简单使用截图
根据上面的命令解释,大家应该比较容易看懂截图里面的所有命令含义,这里同样不过多解释了
列表使用场景
热销榜,文章列表
实现工作队列(利用lists的push操作,将任务存在lists中,然后工作线程再用pop操作将任务取出进行执行 ),例如消息队列
最新列表,比如最新评论
使用参考:
lpush+lpop=Stack(栈)
lpush+rpop=Queue(队列)
lpsh+ltrim=Capped Collection(有限集合)
lpush+brpop=Message Queue(消息队列)
set 集合和 zset 有序集合
Redis 的集合相当于 Java 语言里面的 HashSet 和 JS 里面的 Set,它内部的键值对是无序的唯一的。Set 集合中最后一个 value 被移除后,数据结构自动删除,内存被回收。
zset 可能是 Redis 提供的最为特色的数据结构,它也是在面试中面试官最爱问的数据结构。它类似于 Java 的 SortedSet 和 HashMap 的结合体,一方面它是一个 set,保证了内部 value 的唯一性,另一方面它可以给每个 value 赋予一个 score,代表这个 value 的排序权重。它的内部实现用的是一种叫着「跳跃列表」(后面会简单介绍)的数据结构。
常用集合命令
sadd key element [element ...]:添加元素,返回结果为添加成功的元素个数
srem key element [element ...]:删除元素,返回结果为成功删除元素个数
smembers key:获取所有元素
sismember key element:判断元素是否在集合中,如果给定元素element在集合内返回1,反之返回0
scard key:计算元素个数,scard的时间复杂度为O(1),它不会遍历集合所有元素
spop key:从集合随机弹出元素,从3.2版本开始,spop也支持[count]参数。
srandmember key [count]:随机从集合返回指定个数元素,[count]是可选参数,如果不写默认为1
sinter key [key ...]:求多个集合的交集
suinon key [key ...]:求多个集合的并集
sdiff key [key ...]:求多个集合的差集
集合简单使用截图
常用有序集合命令
zadd key score member [score member ...]:添加成员,返回结果代表成功添加成员的个数。Redis3.2为zadd命令添加了nx、xx、ch、incr四个选项:
nx:member必须不存在,才可以设置成功,用于添加
xx:member必须存在,才可以设置成功,用于更新
ch:返回此次操作后,有序集合元素和分数发生变化的个数
incr:对score做增加,相当于后面介绍的zincrby
zcard key:计算成员个数
zscore key member:计算某个成员的分数
zrank key member 和 zrevrank key member:计算成员的排名,zrank是从分数从低到高返回排名,zrevrank反之
zrem key member [member ...]:删除成员
zincrby key increment member:增加成员的分数
zrange key start end [withscores] 和 zrevrange key start end [withscores]:返回指定排名范围的成员,zrange是从低到高返回,zrevrange反之。
zrangebyscore key min max [withscores] [limit offset count] 和 zrevrangebyscore key max min [withscores] [limit offset count] 返回指定分数范围的成员,其中zrangebyscore按照分数从低到高返回,zrevrangebyscore反之
zcount key min max:返回指定分数范围成员个数
zremrangebyrank key start end:删除指定排名内的升序元素
zremrangebyscore key min max:删除指定分数范围的成员
zinterstore 和 zunionstore 命令求集合的交集和并集,可用参数比较多,可用到再查文档
有序集合相比集合提供了排序字段,但是也产生了代价,zadd的时间 复杂度为O(log(n)),sadd的时间复杂度为O(1)。
有序集合简单使用截图
集合和有序集合使用场景
给用户添加标签
给标签添加用户
根据某个权重进行排序的队列的场景,比如游戏积分排行榜,设置优先级的任务列表,学生成绩表等
关于跳跃列表
跳跃列表就是一种层级制,最下面一层所有的元素都会串起来。然后每隔几个元素挑选出一个代表来,再将这几个代表使用另外一级指针串起来。然后在这些代表里再挑出二级代表,再串起来。最终就形成了金字塔结构,如图:
更多可以看:https://www.jianshu.com/p/09c3b0835ba6
列表、集合和有序集合异同
三、小功能大用处
慢查询分析
许多存储系统(例如MySQL)提供慢查询日志帮助开发和运维人员定位系统存在的慢操作。
所谓慢查询日志就是系统在命令执行前后计算每条命令的执行时间,当超过预设阈值,就将这条命令的相关信息(例如:发生时间,耗时,命令的详细信息)记录下来,Redis 也提供了类似的功能。这里可以顺带了解一下 Redis 客户端执行一条命令的过程,分为如下4个部分:
对于慢查询功能,需要明确 3 件事:
1、预设阈值怎么设置?
在 redis 配置文件中修改配置 ‘slowlog-log-slower-than’ 的值,单位是微妙(1秒 = 1000毫秒 = 1000000微秒),默认是 10000 微秒,如果把 slowlog-log-slower-than 设置为 0,将会记录所有命令到日志中。如果把 slowlog-log-slower-than 设置小于0,将会不记录任何命令到日志中。
2、慢查询记录存放在哪?
在 redis 配置文件中修改配置 ‘slowlog-max-len’ 的值。slowlog-max-len 的作用是指定慢查询日志最多存储的条数。实际上,Redis 使用了一个列表存放慢查询日志,slowlog-max-len 就是这个列表的最大长度。当一个新的命令满足满足慢查询条件时,被插入这个列表中。当慢查询日志列表已经达到最大长度时,最早插入的那条命令将被从列表中移出。比如,slowlog-max-len 被设置为 10,当有第11条命令插入时,在列表中的第1条命令先被移出,然后再把第11条命令放入列表。
记录慢查询指 Redis 会对长命令进行截断,不会大量占用大量内存。在实际的生产环境中,为了减缓慢查询被移出的可能和更方便地定位慢查询,建议将慢查询日志的长度调整的大一些。比如可以设置为 1000 以上。
除了去配置文件中修改,也可以通过 config set 命令动态修改配置
3、如何获取慢查询日志?
可以使用 slowlog get 命令获取慢查询日志,在 slowlog get 后面还可以加一个数字,用于指定获取慢查询日志的条数,比如,获取2条慢查询日志:
可以看出每一条慢查询日志都有4个属性组成:
唯一标识ID
命令执行的时间戳
命令执行时长
执行的命名和参数
此外,可以通过 slowlog len 命令获取慢查询日志的长度;通过 slowlog reset 命令清理慢查询日志。
Pipeline(流水线)机制
Redis 提供了批量操作命令(例如mget、mset等),有效地节约RTT。但大部分命令是不支持批量操作的,例如要执行 n 次 hgetall 命令,并没有 mhgetall 命令存在,需要消耗 n 次 RTT。
Redis 的客户端和服务端可能部署在不同的机器上。例如客户端在北京,Redis 服务端在上海,两地直线距离约为 13