企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持知识库和私有化部署方案 广告
# [Redis 的简单介绍](https://www.cnblogs.com/yxhblogs/p/12714374.html) ## `Redis`特点[#](https://www.cnblogs.com/yxhblogs/p/12714374.html#3899992678) ### 单线程[#](https://www.cnblogs.com/yxhblogs/p/12714374.html#1885500613) 执行过程按顺序执行,不会同时执行多个操作,保证操作的原子性,省去了很多上下文切换线程的时间,不必考虑资源竞争和可能出现死锁。 **为什么使用单线程 ?** 官方`FAQ`表示:因为`Redis`是基于内存的操作,`CPU`不是`Redis`的瓶颈,`Redis`的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且`CPU`不会成为瓶颈,那就顺理成章地采用单线程的方案了。 ### 性能高[#](https://www.cnblogs.com/yxhblogs/p/12714374.html#3545595975) 官方数据:`Redis`能读的速度是`110000次/s`,写的速度是`81000次/s`。这是因为`Redis`的命令操作都是在内存中进行,可以在纳秒级别就处理完,使用了`epoll`非阻塞`IO`。`epoll`是目前最好的多路复用技术,减少网络`IO`的时间消耗。 注:**多路**指的是多个网络连接,**复用**指的是复用同一个线程 ### 丰富的数据类型[#](https://www.cnblogs.com/yxhblogs/p/12714374.html#95140120) 基本类型:`string`(字符串)、`hash`(哈希)、`list`(列表)、`set`(集合)、`zset`(有序集合)。 扩展类型:`HyperLogLog`、`geo`、`bitmap`。 * HyperLogLog(string):实现独立用户的统计,会用`0.81%`误差。 * geo(zset):记录地理位置信息,可以计算两个地点的距离或是某个点的周边范围。 * bitmap(string):可以实现用户签到等功能,大幅度减少内存消耗。 ~~~ CopyBitmap 对于一些特定类型的计算非常有效。如使用 bitmap 实现用户上线次数统计 假设现在我们希望记录自己网站上的用户的上线频率,比如说,计算用户 A 上线了多少天,用户 B 上线了多少天,诸如此类,以此作为数据,从而决定让哪些用户参加 beta 测试等活动 —— 这个模式可以使用 SETBIT key offset value 和 BITCOUNT key [start] [end] 来实现。 比如说,每当用户在某一天上线的时候,我们就使用 SETBIT key offset value ,以用户名作为 key ,将那天所代表的网站的上线日作为 offset 参数,并将这个 offset 上的为设置为 1 。 举个例子,如果今天是网站上线的第 100 天,而用户 peter 在今天阅览过网站,那么执行命令 SETBIT peter 100 1 ;如果明天 peter 也继续阅览网站,那么执行命令 SETBIT peter 101 1 ,以此类推。 当要计算 peter 总共以来的上线次数时,就使用 BITCOUNT key [start] [end] 命令:执行 BITCOUNT peter ,得出的结果就是 peter 上线的总天数。 ~~~ ## `Redis`使用场景[#](https://www.cnblogs.com/yxhblogs/p/12714374.html#3670509787) 热点数据缓存、秒杀、计数器、并发锁、消息队列、分布式`Session`、限时/限数(验证码过期,错误次数等)、防爬虫(请求次数在单位时间内超过制定数组则可认为被爬虫采集)。除此之外,`Redis`支持事务 、持久化、`Lua`脚本、`LRU`驱动事件、多种集群方案。 ### 事务[#](https://www.cnblogs.com/yxhblogs/p/12714374.html#1462424508) `Redis`事务的执行并不是原子性的。事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。 `Redis`事务可以一次执行多个命令, 并且带有以下三个重要的保证: 1、批量操作在发送`EXEC`命令前被放入队列缓存。 2、收到`EXEC`命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。但出现错误时 ,整个事务会被取消。 3、在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。 ~~~ Copymulti incr num incr num exec get num #2 # 使用 watch 可以监控某个key值发生变化时,事务会被打断 watch num # A 机器 multi num # A 机器 inrc num # A 机器 inrc num # B 机器 exec # A 机器 ,返回 nil,即操作被打断 discard # 取消事务 unwatch # 取消监视 ~~~ ### 管道`Pipeline`[#](https://www.cnblogs.com/yxhblogs/p/12714374.html#1121491098) `Redis`是一种基于客户端-服务端模型以及请求/响应协议的`TCP`服务,这意味着通常情况下一个请求会遵循以下步骤: \-> 客户端向服务端发送一个查询请求,并监听`Socket`返回,通常是以阻塞模式,等待服务端响应。 \-> 服务端处理命令,并将结果返回给客户端。 \->`Redis`管道技术可以在服务端末响应时,客户端可以继续向服务端发送请求,并最终一次性读取所有服务端的相应。 ~~~ Copy$pipe = $redis->multi(Redis::PIPELINE); //开启管道 for($i= 0 ; $i<  10000 ; $i++) {        $pipe->set("key::$i",str_pad($i, 4,'0', 0));        $pipe->get("key::$i"); } $pipe->exec(); //提交管道里操作命令 ~~~ ### 管道和事务的区别[#](https://www.cnblogs.com/yxhblogs/p/12714374.html#4229971979) 1、`Pipeline`选择客户端缓冲,`multi`选择服务端缓冲。 2、请求次数的不一致,`multi`需要每个命令都发送一次给服务端,`Pipeline`最后一次性发送给服务端,请求次数相对于`multi`减少。 3、`multi/exec`可以保证原子性,而`Pipeline`不保证原子性。 ### 发布/订阅[#](https://www.cnblogs.com/yxhblogs/p/12714374.html#930095124) `Redis`提供的发布/订阅功能只是一个低配版的,无法对消息持久化存储。一旦消息被发送,如果没有订阅者接收,那么消息就会丢失。`Redis`并没有提供消息传输保障。如果系统中已经有了`Redis`,并且只需要基本的发布订阅功能,对消息的安全性没有高要求,那就可以使用`Redis`的发布/订阅功能。 ~~~ Copysubscribe news publish news hello # unsubscribe 取消订阅 ~~~ ### 阻塞队列[#](https://www.cnblogs.com/yxhblogs/p/12714374.html#856534487) 使用系统提供的阻塞原语,在新元素到达时立即进行处理,而新元素还没到达时,就一直阻塞住,避免轮询占用资源。也可以实现类似于抢票的功能。 ~~~ Copybrpop ticket 30 # 0表示一直等待 lpush ticket home ~~~ ## Redis 持久化[#](https://www.cnblogs.com/yxhblogs/p/12714374.html#1697931707) ### `RDB`持久化[#](https://www.cnblogs.com/yxhblogs/p/12714374.html#917732661) `RDB`持久化是把当前进程数据生成快照保存到硬盘的过程,触发`RDB`持久化过程分为手动触发`save`和自动触`bgsave`。 ~~~ Copysave 900 1 # 900 秒之内,如果超过 1 个 key 被修改,则发起快照保存; save 300 10 # 300 秒内,如果超过 10 个 key 被修改,则发起快照保存; save 60 10000 # 1 分钟之内,如果 1 万个 key 被修改,则发起快照保存; ~~~ **优点:** 1、`RDB`是一个紧凑压缩的二进制文件,代表`Redis`在某个时间点上的数据快照。非常适用于备份,全量复制等场景。比如每6小时执行`bgsave`备份,并把`RDB`文件拷贝到远程机器或者文件系统中(如hdfs),用于灾难恢复。 2、`Redis`加载`RDB`恢复数据远远快于`AOF`的方式。 **缺点:** 1、`RDB`方式数据没办法做到实时持久化/秒级持久化。因为`bgsave`每次运行都要执行`fork`操作创建子进程,属于重量级操作,频繁执行成本过高。 2、`RDB`文件使用特定二进制格式保存,`Redis`版本演进过程中有多个格式的`RDB`版本,存在老版本`Redis`服务无法兼容新版`RDB`格式的问题。针对`RDB`不适合实时持久化的问题,`Redis`提供了`AOF`持久化方式来解决。 ### `AOF`持久化[#](https://www.cnblogs.com/yxhblogs/p/12714374.html#2382203636) `AOF(append only file)`持久化:以独立日志的方式记录每次写命令,重启时再重新执行`AOF`文件中的命令达到恢复数据的目的。`AOF`的主要作用是解决了数据持久化的实时性,目前已经是`Redis`持久化的主流方式。 `AOF`工作流程: \-> 将命令转换成协议文本。 \-> 所有的写入命令会追加到`aof_buf`(缓冲区)中。 \->`AOF`缓冲区根据对应的策略向硬盘做同步操作`everysec`。 \-> 随着`AOF`文件越来越大,需要定期对`AOF`文件进行重写,达到压缩的目的。 \-> 当`Redis`服务器重启时,可以加载`AOF`文件进行数据恢复。 重写机制: 1、`REWRITE`在主线程中重写`AOF`,会阻塞工作线程,在生产环境中很少使用,处于废弃状态。 2、`BGREWRITE`在后台(子进程)重写`AOF`, 不会阻塞工作线程,能正常服务,此方法最常用`bgrewriteaof`。 开启`AOF`,通过修改`redis.conf`配置文件 ~~~ Copyappendonly yes ##默认不开启。 # appendfsync always appendfsync everysec ## 默认每秒同步一次 # appendfsync no ~~~ `AOF`文件名通过`appendfilename`配置设置,默认文件名`appendonly.aof`。保存路径同`RDB`持久化方式一致,通过`dir`配置指定。 **`AOF`后台`Rewrite`解决方案:** 1、官方解决方案:主要思路是`AOF`重写期间,主进程跟子进程通过管道通信,主进程实时将新写入的数据发送给子进程,子进程从管道读出数据交缓存在`buffer`中,子进程等待存量数据全部写入`AOF`文件后,将缓存数据追加到`AOF`文件中,此方案只是解决阻塞工作线程问题,但占用内存过多问题并没有解决。 2、新解决方案:主要思路是`AOF`重写期间,主进程创建一个新的`aof_buf`,新的`AOF`文件用于接收新写入的命令,`sync`策略保持不变,在`AOF`重写期间,系统需要向两个`aof_buf`,两个`AOF`文件同时追加新写入的命令。当主进程收到子进程重写`AOF`文件完成后,停止向老的`aof_buf`,`AOF`文件追加命令,然后删除旧的`AOF`文件(流程跟原来保持一致)。将子进程新生成的`AOF`文件重命名为`appendonly.aof.last`,具体流程如下: \-> 停止向旧的`aof_buf`,`AOF`文件追加命令。 \-> 删除旧的的`appendonly.aof.last`文件。 \-> 交换两个`aof_buf`,`AOF`文件指针。 \-> 回收旧的`aof_buf`,`AOF`文件。 \-> 重命令子进程生成的`AOF`文件为`appendonly.aof.last`。 系统运行期间同时存在两个`AOF`文件,一个是当前正在写的`AOF`,另一个是存量的`AOF`数据文件。因此需要修改数据库恢复相关逻辑,加载`AOF`时先要加载存量数据`appendonly.aof.last`,再加载`appendonly.aof`。 ### 混合持久化[#](https://www.cnblogs.com/yxhblogs/p/12714374.html#3885689467) AOF 持久化方式在重启加载数据的时候,效率远不如 RDB 方式。因此,Redis 官方在 4.0 版本之后,引入了混合持久化的方式。配置选项是`aof-use-rdb-preamble`,`yes`表示开启。策略是在生成或者写入 AOF 文件时,将 RDB 数据写在前面。AOF 数据追加在后面。在每次重新启动的时候,先加载 RDB,再加载 AOF。 具体的配置如下: ~~~ Copy# When rewriting the AOF file, Redis is able to use an RDB preamble in the # AOF file for faster rewrites and recoveries. When this option is turned # on the rewritten AOF file is composed of two different stanzas: # # [RDB file][AOF tail] # # When loading Redis recognizes that the AOF file starts with the "REDIS" # string and loads the prefixed RDB file, and continues loading the AOF # tail. aof-use-rdb-preamble yes ~~~ ## 主从复制[#](https://www.cnblogs.com/yxhblogs/p/12714374.html#3090416961) 在主从复制的概念中,数据库分为两类,一类是主数据库`master`,另一类是从数据库\[1\]`slave`。主数据库可以进行读写操作,当写操作导致数据变化时会自动将数据同步给从数据库。而从数据库一般是只读的,并接受主数据库同步过来的数据。一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库。 **优点:** 1、实现了对`Master`数据的备份,一旦`Master`出现故障,`Slave`节点可以提升为新的`Master`,顶替旧的`Master`继续提供服务(需要手动切换)。 2、实现读扩展,使用主从复制架,一般都是为了实现读扩展。`Master`主要实现写功能,`Slave`实现读的功能。 **缺点:** 1、当`Maser`宕机后,需要人工参与。 \-> 在`Slave1`上执`slaveof no one`命令提升`Slave1`为新的`Master`节点。 \-> 在`Slave1`上配置为可写,这是因为大多数情况下,都将`Slave`配置只读。 \-> 告诉`Client`端(也就是连接`Redis`的程序)新的`Master`节点的连接地址(改代码)。 \-> 配置`Slave2`从新的`Master`进行数据复制。 2、虽然实现了读写分离,但是主节点的性能会成为主要瓶颈。 ## Sentinel 哨兵机制[#](https://www.cnblogs.com/yxhblogs/p/12714374.html#1344033632) `Redis Sentinel`为`Redis`提供了高可用方案。从实践方面来说,使用`Redis Sentinel`可以创建一个无需人为干预就可以预防某些故障的`Redis`环境。实现自动切换。在部署`Sentinel`的时候,建议使用奇数个`Sentinel`节点,最少三个`Sentinel`节点。 **功能:** 1、 监控,`Sentinel`会不断的检查`master`和`slave`是否像预期那样正常运行。 2、通知,通过`API`,`Sentinel`能够通知系统管理员、程序监控的`Redis`实例出现了故障。 3、自动故障转移,如果`Master`不像预想中那样正常运行,`Sentinel`可以启动故障转移过程,其中的一个`Slave`会提成为`Master`,其它`Slave`会重新配置来使用新的`Master`,使用`Redis`服务的应用程序,当连接时,也会被通知使用新的地址。 4、配置提供者,`Sentinel`可以做为客户端服务发现的认证源:客户端连接`Sentinel`来获取目前负责给定服务的`Redis master`地址。如果发生故障转移,`Sentinel`会报告新的地址。 ### 三个定时任务[#](https://www.cnblogs.com/yxhblogs/p/12714374.html#255323200) * 每 10 秒每个`sentinel`对`master`和`slave`执行`info replication`,以发现新的`slave`节点并确认主从关系。 * 每 2 秒每个`sentinel`通过`master`节点的`channel`(`__sentinel__:hello`)交换信息,以交换对节点的”看法“和自身信息。 * 每 1 秒每个`sentinel`对其他`sentinel`和主、从节点发送`ping`命令来做心跳检测。 ### 故障切换实现原理[#](https://www.cnblogs.com/yxhblogs/p/12714374.html#656557701) * `Sentinel`之间进行选举,选举出一个`leader`,由选举出的`leader`进行`failover`。 * `Sentinel leader`选取`slave`节点中的一个`slave`作为新的`Master`节点。对`slave`选举需要对`slave`进行选举的方法如下: * 与`master`断开时间,如果与`Master`断开的时间超过`down-after-milliseconds`(`sentinel`配置) \* 10秒加上从`sentinel`判定`Master`不可用到`sentinel`开始执行故障转移之间的时间,就认为该`Slave`不适合提升为`Master`。 * `slave`优先级,每个`Slave`都有优先级,保存在`redis.conf`配置文件里。如果优先级相同,则继续进行。 * 复制偏移位置,复制偏移纪录着从`Master`复制数据复制到哪里,复制偏移越大表明从`Master`接受的数据越多,如果复制偏移量也一样,继续进行选举。 * `Run ID`,选举具有最小`Run ID`的`Slave`作为新的`Master`。 * `Sentinel leader`会在上一步选举的新`master`上执行`slaveof no one`操作,将其提升为`master`节点。 * `Sentinel leader`向其它`slave`发送命令,让剩余的`slave`成为新的`master`节点的`slave`。 * `Sentinel leader`会让原来的`master`降级为`slave`,当恢复正常工作,`Sentinel leader`会发送命令让其从新的`master`进行复制。 ### 配置文件说明[#](https://www.cnblogs.com/yxhblogs/p/12714374.html#125444420) ~~~ Copydaemonize yes #以后台进程模式运行 port 27000 # 哨兵的端口号,该端口号默认为26379。 #redis-master sentinel monitor redis-master 192.168.1.51 7000 2 # redis-master是主数据的别名,考虑到故障恢复后主数据库的地址和端口号会发生变化,哨兵提供了命令可以通过别名获取主数据库的地址和端口号。 # 192.168.1.51 7000为初次配置时主数据库的地址和端口号,当主数据库发生变化时,哨兵会自动更新这个配置,不需要我们去关心。 # 2,该参数用来表示执行故障恢复操作前至少需要几个哨兵节点同意,一般设置为N/2+1(N为哨兵总数)。 sentinel down-after-milliseconds redis-master 5000 #如果master在多少秒内无反应哨兵会开始进行master-slave间的切换,使用“选举”机制 sentinel failover-timeout redis-master 900000 # 如果master 在 900000 秒后恢复,则把它加入到 slave 中 logfile "/data/bd/redis/sentinel/sentinel.log" #log文件所在地 ~~~ ## `Docker+Redis`实例[#](https://www.cnblogs.com/yxhblogs/p/12714374.html#1695654261) ### 设置配置[#](https://www.cnblogs.com/yxhblogs/p/12714374.html#216785266) ~~~ Copy# redis-master.conf port 7000 daemonize yes pidfile /var/run/redis.pid logfile "7000.log" sed 's/7000/7001/g' redis-master.conf > redis-slave1.conf sed 's/7000/7002/g' redis-master.conf > redis-slave2.conf echo "slaveof redis-master 7000" >> redis-slave1.conf echo "slaveof redis-master 7000" >> redis-slave2.conf ~~~ ### 启动`Redis-Master`容器[#](https://www.cnblogs.com/yxhblogs/p/12714374.html#988163176) ~~~ Copy# 启动 Redis-Master容器 sudo docker run -d -p 7000:7000 -v /home/summer/homework/redis/config/redis-master.conf:/etc/redis/redis.conf --name redis-master redis redis-server /etc/redis/redis.conf ~~~ ### 启动`Redis-Slave`容器[#](https://www.cnblogs.com/yxhblogs/p/12714374.html#4263693691) ~~~ Copy# 启动 Redis-Slave1 容器 sudo docker run -d -p 7001:7001 -v /home/summer/homework/redis/config/redis-slave1.conf:/etc/redis/redis.conf --name redis-slave1 --link redis-master:master redis redis-server /etc/redis/redis.conf # 启动 Redis-Slave2 容器 sudo docker run -d -p 7002:7002 -v /home/summer/homework/redis/config/redis-slave2.conf:/etc/redis/redis.conf --name redis-slave2 --link redis-master:master redis redis-server /etc/redis/redis.conf ~~~ ## 注意问题[#](https://www.cnblogs.com/yxhblogs/p/12714374.html#2503217611) 1、禁止线上执行`keys *`、`mset`、`mget`、`hmget`、`hmset`等操作,会造成阻塞。 2、避免同一时间缓存大面积失效,造成缓存雪崩。 3、内存数据库,键名长度影响有限内存空间,所以命名应该控制长度,简短易懂。 4、大小写规范。 5、根据业务命名,相同业务统一的`Key`前缀。 ## 常用命令[#](https://www.cnblogs.com/yxhblogs/p/12714374.html#1344225159) ~~~ Copydebug sleep 10 config set xx yy config get xx* info replication # 查看主从信息 info server info sentinel ~~~ ## 参考资料[#](https://www.cnblogs.com/yxhblogs/p/12714374.html#3248715157) * [Redis 哨兵机制](http://www.dczou.com/viemall/837.html) * [Redis 主从复制](https://lw900925.github.io/docker/docker-redis-cluster.html) * [Redis 高可用面试](https://zhuanlan.zhihu.com/p/34985026) * [Redis 使用场景及应用](https://www.jianshu.com/p/1ff269e8869d) * [Redis 文档](http://redisdoc.com/index.html)