Redis
Redis是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如字符串(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的主要应用场景
Redis适用场景
缓存
使用Redis可以建立性能非常出色的缓存服务器,查询请求先在Redis中查找所需要的数据,如果能够查询到(命中)则直接返回,大大减轻关系型数据库的压力。
数据临时存储位置
使用token(令牌)作为用户登录系统时的身份标识,这个token就可以在Redis中临时存储。
分布式环境下解决Session不一致问题时的Session库
Spring提供了一种技术解决分布式环境下Session不一致问题,叫SpringSession。而Redis就可以为SpringSession提供一个数据存储空间。
流式数据去重
在Redis中有一种数据类型是set,和Java中的Set集合很像,不允许存储重复数据。借助这个特性我们可以在Redis中使用set类型存储流式数据达到去重的目的。
Redis不适应场景
直接查询value
Redis是一个严格的Key-value数据库,所有数据都必须通过key去找到value,Redis没有提供直接根据查询条件匹配value的方法。
用多键一值表示特定关系
Redis中一个key对应一个value,没有多个key对应同一个value的情况。
事务中回滚
Redis不支持回滚。如果一个命令在加入队列时没有检测出问题,那么队列执行时不会因为某一条命令失败而回滚。
Redis的常用数据结构
Redis是Key-Value存储类型的,Key不要太长最好不要超过1024字节,消耗内存且降低查找效率,但也不要太短,影响可读性(Redis命令中文官网)注:Redis命令不区分大小写 但Key区分
| 数据类型 | 应用场景 |
|---|---|
| string | 分布式Session存储 分布式数据库ID 计数器:统计网站访问量 |
| hash | 存储对象信息(购物车中的商品信息) 存储表的信息 |
| list | 实现队列、栈操作 汇总日志 粉丝列表 关注的人列表 |
| set | 签到 打卡 点赞 |
| zset | 排行榜 百度热点搜索 |
| geospatial | 获取地理位置信息 两地之间的距离 |
| hyperloglogs | 基数统计 |
| bitmaps | 统计用户访问次数 |
string类型
Redis中最基本的类型,它是key对应的一个单一值。二进制安全,不必担心由于编码等问题导致二进制数据变化。所以redis的string可以包含任何数据,比如jpg图片或者序列化的对象。Redis中一个字符串值的最大容量是512M。
list类型
Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。它的底层是双向链表,所以它操作时头尾效率高,中间效率低(额外花费查找插入位置的时间)。
在Redis中list类型是按照插入顺序排序的字符串链表。和数据结构中的普通链表一样,我们可以在其头部(left)和尾部(right)添加新的元素。在插入时,如果该键并不存在,Redis将为该键创建一个新的链表。与此相反,如果链表中所有的元素均被移除,那么该键也将会被从数据库中删除。List中可以包含的最大元素数量是2^32-1个。
list是一个有序可以重复的数据类型。
set类型
Redis的set是String类型的无序集合。基于哈希表实现。set插入时会自动去重
hash类型
本身就是一个键值对集合。可以当做Java中的Map<String,String>对待。每一个hash可以存储2^32-1个键值对。

zset类型
zset和set一样也是String类型的元素的集合,且不允许重复的成员,不同的是每个元素等会关联一个double类型的分数。Redis通过分数对集合中的成员进行排序。zset的成员是唯一的,但是分数(score)是可以重复的
Geospatial
在3.2退出的Geo类型,该功能可以推算出两地之间的位置

HyperLogLogs
用于大数据量基数统计,速度非常快,占用内存非常小。每个HyperLogLog键只需要花费12KB内存,就可以计算接近 2^64个不同元素的基数。比如计算网站UV(User view,用户访问数量,一个用户一天访问同一个URL地址多次合并为一次)。
bitmaps
直接对string的二进制位进行操作的一组命令
Redis持久化机制
(Redis官网描述)Redis工作时数据都存储在内存中,万一服务器断电,则所有数据都会丢失。针对这种情况,Redis采用持久化机制来增强数据安全性。说白了就是把内存里的数据保存到硬盘上。
Redis持久化机制主要分为RDB和AOF
RDB(默认)
机制描述
RDB是一种在特定触发条件下以快照形式将数据本身存储在硬盘文件中
- 在生成快照时,将当前进程fork出一个子进程
- 然后再子进程中循环所有的数据,将数据写入到二进制文件中。
- 当子进程将快照写入临时文件完毕后,用临时文件替换原来的快照文件,然后子进程退出。
触发时机
基于默认的配置
save 900 1
save 300 10
save 60 10000
使用保存命令
save或者bgsave
- save:该命令将在 redis 安装目录中创建dump.rdb文件。直接调用rdbSave函数阻塞Redis主进程,直到保存成功为止。在主进程阻塞期间,服务器不能处理客户端的任何请求。
- bgsave:BGSAVE 命令执行之后立即返回 OK ,然后 Redis fork 出一个新子进程,原来的 Redis 进程(父进程)继续处理客户端请求,而子进程则负责将数据保存到磁盘,然后退出。(不会阻塞主线程)但是对机器的CPU资源和内存资源会造成影响
使用flushall命令
这个命令也会产生dump.rdb文件(空)无意义
服务器关闭
如果执行SHUTDOWN命令让Redis正常退出,那么Redis就会执行一次持久化保存
相关配置
| 配置项 | 取值 | 作用 |
|---|---|---|
| save | “” | 禁用RDB机制 |
| dbfilename | 文件名,例如:dump.rdb | 设置RDB机制下,数据存储文件的文件名 |
| dir | Redis工作目录路径 | 指定存放持久化文件的目录的路径。注意:这里指定的必须是目录不能是文件名 |
AOF
机制描述
Rdeis是默认不开启AOF的,AOF存在的主要意义是为了弥补RDB的不足(数据不一致),所以是已日志的形式来记录每个写操作,
Redis 重启的会根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。AOF的工作原理就是是将写操作追加到文件中,文件的冗余内容会越来越多。所以Redis 新增了重写机制。当AOF文件的大小超过所设定的最大值时,Redis就会对AOF文件的内容压缩。
AOF的基本配置
| 配置项 | 取值 | 作用 |
|---|---|---|
| appendonly | yes | 启用AOF持久化机制 |
| no | 禁用AOF持久化机制[默认值] | |
| appendfilename | “文件名” | AOF持久化文件名 |
| dir | Redis工作目录路径 | 指定存放持久化文件的目录的路径。注意:这里指定的必须是目录不能是文件名 |
| appendfsync | always | 每一次数据修改后都将执行文件写入操作,是最安全的方式但是速度缓慢。 |
| everysec | 每秒执行一次写入操作。折中。 | |
| no | 由操作系统在适当的时候执行写入操作,Redis性能最好,数据保存次数最少。 |
注:当AOF和RDB机制并存的时候,Redis会优先采纳AOF机制。使用 AOF 持久化文件恢复内存中的数据。而 AOF 刚刚开启时 appendonly.aof 持久化文件中没有任何数据。拿空的 appendonly.aof 持久化文件恢复内存,就会导致以前所有数据都丢失。
AOF重写
随着时间推移,AOF备份文件会越来越大,不仅非常占用硬盘空间,复制和移动都很耗时,所以把这个文件压缩的步骤叫AOF重写
| AOF重写前 | AOF重写后 |
|---|---|
| set count 1 incr count incr count incr count | set count 4 |
两组命令执行后对于count来说最终的值是一致的,但是进行AOF重写后省略了中间过程,可以让AOF文件体积更小,缩短数据恢复时间。而Redis会根据AOF文件的体积来决定是否进行AOF重写。参考的配置项如下:
| 配置项 | 含义 |
|---|---|
| auto-aof-rewrite-percentage 100 | 文件体积增大100%时执行AOF重写 |
| auto-aof-rewrite-min-size 64mb | 文件体积增长到64mb时执行AOF重写 |
实际工作中不要进行频繁的AOF重写,因为CPU、内存资源和硬盘资源二者之间肯定是CPU、内存资源更加宝贵,所以不应该过多耗费CPU性能去节省硬盘空间。另外数据恢复也不是高频操作,所以节约数据恢复时间价值也不是非常大。
持久化文件损坏修复
Redis服务器启动时如果读取了损坏的持久化文件会导致启动失败,此时为了让Redis服务器能够正常启动,需要对损坏的持久化文件进行修复。这里以AOF文件为例介绍修复操作的步骤。
-
第一步:备份要修复的appendonly.aof文件
-
第二步:执行修复程序
/usr/local/redis/bin/redis-check-aof --fix /usr/local/redis/appendonly.aof
-
第三步:重启Redis
注意:所谓修复持久化文件仅仅是把损坏的部分去掉,而没法把受损的数据找回。
AOF模式下有两种机制:
1. 重写机制
假设执行了10条自增语句
num = 1
incr ....
incr ....
num = 11
重写后只保存一个命令: incrby 10
这样的话有助于:节约空间;提升性能
2. 修复机制
AOP文件一般情况下不会出错。但是有可能因为人为的原因导致部分命令不能识别或者有错误。
此时redis通过命令
/usr/local/redis/bin/redis-check-aof --fix /usr/local/redis/appendonly.aof
进行修复
RDB和AOF优劣势对比
RDB
优势
适合大规模的数据恢复,速度较快
劣势
会丢失最后一次快照后的所有修改,不能绝对保证数据的高度一致性和完整性。Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑,但上述成立有条件,Linux也有优化手段
AOF
优势
选择appendfsync always方式运行时理论上能够做到数据完整一致,但此时性能又不好。文件内容具备一定可读性,能够用来分析Redis工作情况。
劣势
持久化相同的数据,文件体积比RDB大,恢复速度比RDB慢。效率在同步写入时低于RDB,不同步写入时与RDB相同。
RDB和AOF并存
Redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整
RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件。那要不要只使用AOF呢?作者建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份)、快速重启,而且不会有AOF可能潜在的bug,留着作为一个万一的手段。
Redis事务
事务(Transaction)是访问并可能更新数据库中各项数据项的一个程序执行单元(unit)。 事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。
事务的四大特性(ACID):
- 原子性:指一个事务是一个不可分割的工作单位,整个事务中的所以操作要么全部提交成功,要么全部失败回滚,对于一个事务来说不可能只执行其中的一部分操作
- 一致性:是指事务必须使数据库从一个一致性状态变换到另一个一致性状态也就是事务前后的数据的完整性必须保持一直
- 隔离性:一个事务的执行不能有其他事务的干扰,事务的内部操作和使用数据对其他的并发事务是隔离的,互不干扰
- 持久性:一个事务一旦提交,数据库中数据的改变就是永久性的,即使数据库发现故障也,修改的数据也不会丢失。后续的操作也不会对已经提交的事务产生影响
Redis控制事务的相关命令
| 命令名 | 作用 |
|---|---|
| MULTI | 表示开始收集命令,后面所有命令都不是马上执行,而是加入到一个队列中。 |
| EXEC | 执行MULTI后面命令队列中的所有命令。 |
| DISCARD | 放弃执行队列中的命令。 |
| WATCH | “观察“、”监控“一个KEY,在当前队列外的其他命令操作这个KEY时,放弃执行自己队列的命令 |
| UNWATCH | 放弃监控一个KEY |
命令失败的两种情况
加入队列失败
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set age 20
QUEUED
127.0.0.1:6379> incr age
QUEUED
127.0.0.1:6379> incr age www
(error) ERR wrong number of arguments for 'incr' command
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
遇到了入队时即可检测到的错误,整个队列都不会执行。
执行队列时失败
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set age 30
QUEUED
127.0.0.1:6379> incrby age 5
QUEUED
127.0.0.1:6379> incrby age 5
QUEUED
127.0.0.1:6379> incrby age ww
QUEUED
127.0.0.1:6379> incrby age 5
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) (integer) 35
3) (integer) 40
4) (error) ERR value is not an integer or out of range
5) (integer) 45
127.0.0.1:6379> get age
"45"
错误在入队时检测不出来,整个队列执行时有错的命令执行失败,但是其他命令并没有回滚。
Redis为什么不支持回滚
Redis 不支持事务回滚,因为支持回滚 将对 Redis 的简单性和性能产生重大影响。
- Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
- 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。
有种观点认为 Redis 处理事务的做法会产生 bug , 然而需要注意的是, 在通常情况下, 回滚并不能解决编程错误带来的问题。 举个例子, 如果你本来想通过 INCR 命令将键的值加上 1 , 却不小心加上了 2 , 又或者对错误类型的键执行了 INCR , 回滚是没有办法处理这些情况的。
watch和unwatch指令
watch表示监视某一key;unwatch表示放弃监视;当执行完exec之后也会放弃监视key
ff-- 第一个客户端指向一下语句
127.0.0.1:6379> watch age
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr age
QUEUED
127.0.0.1:6379> incr age
QUEUED
127.0.0.1:6379> exec -- 在此处停顿,先不要执行,然后打开第二个客户端
(nil)
127.0.0.1:6379>
-- 第二个客户端:
127.0.0.1:6379> select 0
OK
127.0.0.1:6379> get age
"45"
127.0.0.1:6379> set age 99
OK
-- 第二个客户端命令执行完,再回到第一个客户端执行exec,我们发现返回的是nil。表示这个事务中的命令都放弃了,没有执行。
-- 我们发现redis采用的是乐观锁
悲观锁和乐观锁
在使用WATCH命令监控一个KEY后,当前队列中的命令会由于外部命令的执行而放弃,这是乐观锁的体现。
悲观锁
认为当前的环境非常容易发生碰撞,所以在执行操作前先将数据锁定,操作完后释放锁,其他操作才可以继续操作
乐观锁
认为当前的环境不容易发生碰撞,所以在执行时不锁定数据,万一发生了碰撞就检查版本号
- 如果是基于最新的版本号,服务器接受,修改成功
- 如果不是基于最新的版本号,服务器不接受,修改失败,整个MULTI队列中的操作全部丢弃
缓存

缓存击穿
一个热点Key过期,此时可能有大量请求同时访问这条数据,就会直达MySQL数据库,就会导致MySQL宕机
解决方案
分布式锁
分布式CAP\BASE理论
C:一致性 A:可用性 P:分区容错性
BA:基本可用 S:软状态 E:最终一致性

缓存穿透
大量数据访问一个不存在的数据,由于数据不存在,导致请求直到MySQL数据库,导致MySQL数据库宕机
解决方案
缓存一个为Null的数据
缓存雪崩
过期时间相同,导致大量缓存数据同时过期,此时大量数据请求访问数据,就会直达MySQL数据库,导致MySQL数据库宕机
解决方案
给缓存时间添加一个随机值
Redis哈希槽
slot:哈希槽
Redis集群没有使用一致性hash,而是引入了哈希槽的概念,Redis集群有16384个哈希槽,每个key通 过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。
当需要增加节点时,只需要把其他节点的某些哈希槽挪到新节点就可以了;
当需要移除节点时,只需要把移除节点上的哈希槽挪到其他节点就行了;
使用哈希槽的好处就在于可以方便的添加或移除节点
Redis过期策略和内存淘汰机制
过期策略
定时过期
每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
惰性过期
只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
定期过期
redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,以后会定期遍历这个字典来删除到期的 key。Redis 默认会每秒进行十次过期扫描(100ms一次),过期扫描不会遍历过期字典中所有的 key,而是采用了一种简单的贪心策略。
同时惰性过期和定期过期两种过期策略
Redis过期删除采用的是定期删除,默认是每100ms检测一次,遇到过期的key则进行删除,这里的检测并不是顺序检测,而是随机检测。那这样会不会有漏网之鱼?显然Redis也考虑到了这一点,当我们去读/写一个已经过期的key时,会触发Redis的惰性删除策略,直接回干掉过期的key
内存淘汰策略


Redis集群
主从复制机制

读写分离的好处
- 性能优化:主服务器专注于写操作,可以用更适合写入的数据模式工作:从服务器同理
- 强化数据安全,避免单点故障:由于数据同步机制的存在,各个服务器之间数据保持一致,所以其中某个服务器宕机不会导致数据丢失或无法访问。从这个角度说参与主从复制的Redis服务器构成了一个集群。
搭建步骤
思路
Redis集群在运行时使用的是同一个可执行文件,只是对应的配置文件不同。

每个配置文件中相同的参数是:
daemonize yes
dir /usr/local/cluster-redis
不同的参数有:
| 配置项名称 | 作用 | 取值 |
|---|---|---|
| port | Redis服务器启动后监听的端口号 | 6000 7000 8000 |
| dbfilename | RDB文件存储位置 | dump6000.rdb dump7000.rdb dump8000.rdb |
| logfile | 日志文件位置 | /var/logs/redis6000.log /var/logs/redis7000.log /var/logs/redis8000.log |
| pidfile | pid文件位置 | /var/run/redis6000.pid /var/run/redis7000.pid /var/run/redis8000.pid |
步骤
-
第一步:创建/usr/local/cluster-redis目录
-
第二步:把原始未经修改的redis.conf复制到/usr/local/cluster-redis目录
-
第三步:把/usr/local/cluster-redis目录下的redis.conf复制为redis6000.conf
-
第四步:按照既定计划修改redis6000.conf中的相关配置项
- daemonize yes
- dir
- port
- dbfilename
- logfile
- pidfile
-
第五步:复制redis6000.conf为redis7000.conf
-
第六步:修改redis7000.conf中的相关配置项
- port
- dbfilename
- logfile
- pidfile
-
第七步:复制redis6000.conf为redis8000.conf
-
第八步:修改redis8000.conf中的相关配置项
- port
- dbfilename
- logfile
- pidfile
启动Redis主从复制集群
/usr/local/redis/bin/redis-server /usr/local/cluster-redis/redis6000.conf
/usr/local/redis/bin/redis-server /usr/local/cluster-redis/redis7000.conf
/usr/local/redis/bin/redis-server /usr/local/cluster-redis/redis8000.conf
使用redis-cli停止指定服务器的命令格式如下:
/usr/local/bin/redis-cli -h IP地址 -p 端口号 shutdown
登录到指定IP指定端口的redis
/usr/local/bin/redis-cli -h IP地址 -p 端口号
主从关系
查看主从关系
127.0.0.1:6000> info replication
# Replication
role:master
connected_slaves:0
刚刚启动的集群服务器中每一个节点服务器都认为自己是主服务器。需要建立主从关系。
设定主从关系
在从机上指定主机位置即可
SLAVEOF 127.0.0.1 6000
取消主从关系
在从机上执行命令
SLAVEOF NO ONE
测试
- 测试1:在主机写入数据,在从机查看
- 测试2:在从机写入数据报错。配置文件中的依据是:slave-read-only yes
- 测试3:主机执行SHUTDOWN看从机状态
- 测试4:主机恢复启动,看从机状态
- 测试5:从机SHUTDOWN,此时主机写入数据,从机恢复启动查看状态。重新设定主从关系后看新写入的数据是否同步。
哨兵机制
通过哨兵服务器监控master\salve实现主从复制集群的自动管理

相关概念
主管下线
1台哨兵检测到某节点服务器下线
客观下线
认为某个节点服务器下线的哨兵服务器达到指定数量。这个数量后面在哨兵的启动配置文件中指定。
提示:只有 master 服务器做客观下线的判断, slave 只做主观下线的判定。
心跳检查(突击检查Nacos心跳机制)
心跳(heart beat)检查:客户端为了确认服务器端是否正在运行,不断的给服务器端发送数据库包。通过服务器端返回的数据包判断服务器端是否正在运行的工作机制。
配置
简单起见我们只配置一台哨兵。我们所需要做的就是创建一个哨兵服务器运行所需要的配置文件。
vim /usr/local/cluster-redis/sentinel.conf
| 格式 | sentinel monitor 为主机命名 主机IP 主机端口号 将主机判定为下线时需要Sentinel同意的数量 |
|---|---|
| 例子 | sentinel monitor mymaster 127.0.0.1 6000 1 |
启动哨兵
/usr/local/redis/bin/redis-server /usr/local/cluster-redis/sentinel.conf –sentinel
+sdown master mymaster 127.0.0.1 6379 【主观下线】
+odown master mymaster 127.0.0.1 6379 #quorum 1/1【客观下线】
……
+vote-for-leader 17818eb9240c8a625d2c8a13ae9d99ae3a70f9d2 1【选举leader】
……
+failover-state-send-slaveof-noone slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6379【把一个从机设置为主机】
-------------挂掉的主机又重新启动---------------------
-sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6381【离开主观下线状态】
+convert-to-slave slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6381【转换为从机】
选举

-
情况1:
- slave A:投票给 slave B
- slave B:投票给 slave A
- 两个服务器各得一票,平手,不能确定,需要继续投票
-
情况2:
- slave A:投票给自己
- slave B:投票给自己
- 两个服务器各得一票,平手,不能确定,需要继续投票
-
情况3:
- slave A:投票给自己
- slave B:投票给 slave A
- slave A 得 2 票,slave B 没有得票,所以 slave A 当选
发布订阅
这个功能不是 Redis 的主要功能,实际开发时发布订阅方面的需求还是要找专门的消息队列产品来完成
1.订阅一个频道
127.0.0.1:6379> SUBSCRIBE cctv
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "cctv"
3) (integer) 1
2.在一个频道上发布信息
127.0.0.1:6379> PUBLISH cctv hai
(integer) 1
127.0.0.1:6379> SUBSCRIBE cctv
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "cctv"
3) (integer) 1
1) "message"
2) "cctv"
3) "hai"
Jedis
Jedis 是我们 Java 程序连接 Redis 服务器的客户端。
对比
| MySQL | Redis | |
|---|---|---|
| 连接 | Connection | Jedis |
| 连接池 | C3P0、DBCP、Druid等等 | JedisPool |
| 操作完成 | 关闭连接 | 关闭连接 |
Redis准备
理解Redis配置文件中bind配置项含义
|bind后面跟的ip地址是客户端访问Redis时使用的IP地址。规则是:Redis要求客户端访问的地址,必须是 bind 配置项绑定的地址。看下面例子:||
| bind值 | 访问方式 |
|---|---|
| 127.0.0.1 | ./redis-cli -h 127.0.0.1 |
| 192.168.200.100 | ./redis-cli -h 192.168.200.100 |
![]() |
所以,结论是:bind 配置项要绑定可以对外暴露的本机地址。那么 Redis 为什么会有这样的要求?就是因为在实际项目中,Redis 不是给用户直接访问的,而是给 Java 程序访问的。所以 Redis 只要绑定一个内部访问地址,就能够屏蔽外部的访问,所以这个地址绑定机制,能够对 Redis 进行保护。
查看Linux系统本机IP
远程客户端访问Linux服务器时不能使用127.0.0.1,要使用网络上的实际IP。可以用ifconfig命令查看。
将Redis配置文件中的bind配置项设置为本机IP。
bind [你的实际IP]
bind 192.168.200.100
特别要注意,搜索关键字"bind"之后,在下面还有一行: bind 127.0.0.1 一定要把这一行注释掉,否则修改无效,而且下面的jedis连接也会被拒绝。
可以先使用命令测试bind是否生效:
/usr/local/redis/bin/redis-cli -h 192.168.10.100 -p 6000
如果修改成功,那么上面的命令能够执行成功。而下面的命令会执行失败:
[root@localhost cluster-redis]# /usr/local/redis/bin/redis-cli -p 6000
Could not connect to Redis at 127.0.0.1:6000: Connection refused
Could not connect to Redis at 127.0.0.1:6000: Connection refused
如果你的连接结果与上面的正好相反,说明"bind 127.0.0.1"这一句话没有注释掉
Jedis
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
//指定Redis服务器的IP地址和端口号
Jedis jedis = new Jedis("192.168.200.100", 6379);
//执行ping命令
String ping = jedis.ping();
System.out.println(ping);
//关闭连接
jedis.close();
JedisPool
为什么要用JedisPool
- 获取Jedis实例需要从JedisPool中获取
- 用完Jedis实例需要返还给JedisPool
- 如果Jedis在使用过程中出错,也需要还给JedisPool
//声明Linux服务器IP地址
String host = "192.168.200.100";
//声明Redis端口号
int port = Protocol.DEFAULT_PORT;
//创建连接池对象
JedisPool jedisPool = new JedisPool(host, port);
//获取Jedis对象连接Redis
Jedis jedis = jedisPool.getResource();
//执行具体操作
String ping = jedis.ping();
System.out.println(ping);
//关闭连接
jedisPool.close();
主要面试题
Redis集群如果主宕机了怎么办,说说你的想法
Redis持久化机制
Redis 持久化机制(RDB)(AOF)(RDB+AOF)
**RDB:**在满足特定触发条件时把内存中的数据本身作为一个快照保存到硬盘上的文件中。Redis默认开启RDB机制
每隔一段时间,将内存中的数据集写到磁盘
优点:
1、只有一个文件 dump.rdb,方便持久化。
2、容灾性好,一个文件可以保存到安全的磁盘。
3、性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO 最大化。使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能
4.相对于数据集大时,比 AOF 的启动效率更高。
缺点:
1、数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候)
2、会丢失最后一次快照后的所有修改,不能绝对保证数据的高度一致性和完整性。Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑,但上述成立有条件,Linux也有优化手段
区别:Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。
- 使用保存命令
save或bgsave
- 使用flushall命令
这个命令也会产生dump.rdb文件,但里面是空的,没有意义
- 服务器关闭
如果执行SHUTDOWN命令让Redis正常退出,那么此前Redis就会执行一次持久化保存。
| 配置 | 含义 |
|---|---|
| save 900 1 | 900秒内至少有一次修改则触发保存操作 |
| save 300 10 | 300秒内至少有10次修改则触发保存操作 |
| save 60 10000 | 60秒内至少有1万次修改则触发保存操作 |
**AOF:**以日志形式记录每个更新((总结、改)操作
AOF(Append-only file)持久化方式: 是指所有的命令行记录以 redis 命令请 求协议的格式完全持久化存储)保存为 aof 文件。
Redis重新启动时读取这个文件,重新执行新建、修改数据的命令恢复数据(三种策略)
-
appendfsync always:每次产生一条新的修改数据的命令都执行保存操作;效率低,但是安全!
-
appendfsync everysec:每秒执行一次保存操作。如果在未保存当前秒内操作时发生了断电,仍然会导致一部分数据丢失(即1秒钟的数据)。
-
appendfsync no:从不保存,将数据交给操作系统来处理。更快,也更不安全的选择。
-
推荐(并且也是默认)的措施为每秒 fsync 一次, 这种 fsync 策略可以兼顾速度和安全性。
缺点:
1 、比起RDB占用更多的磁盘空间
2 、恢复备份速度要慢
3 、每次读写都同步的话,有一定的性能压力
4 、存在个别Bug,造成恢复不能(ecerysec\no)
选择策略:
-
可读的日志文本,通过操作AOF
-
如果对数据不敏感,可以选单独用RDB;不建议单独用AOF,因为可能出现Bug;如果只是做纯内存缓存,可以都不用
RDB+AOF
将RDB和AOF混合一起使用,在使用混合模式时,所有的数据操作也是保存在AOF当中,当进行恢复文件的时候,会将原有的AOF删除,并且将其中的数据全部以快照的形式保存至RDB文件当中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bsROTFNP-1681555223047)(assets/image-20220919161023-3x9tbi8.png)]
- 持久化效率高
- 保证数据的安全性
- 不会丢失数据且恢复的具有安全性
说一下缓存雪崩 缓存穿透 缓存击穿
缓存穿透:
第一种方案,非法请求的限制
当有大量恶意请求访问不存在的数据的时候,也会发生缓存穿透,因此在 API 入口处我们要判断求请求参数是否合理,请求参数是否含有非法值、请求字段是否存在,如果判断出是恶意请求就直接返回错误,避免进一步访问缓存和数据库。
第二种方案,缓存空值或者默认值
当我们线上业务发现缓存穿透的现象时,可以针对查询的数据,在缓存中设置一个空值或者默认值,这样后续请求就可以从缓存中读取到空值或者默认值,返回给应用,而不会继续查询数据库。
第三种方案,使用布隆过滤器快速判断数据是否存在,避免通过查询数据库来判断数据是否存在。
我们可以在写入数据库数据时,使用布隆过滤器做个标记,然后在用户请求到来时,业务线程确认缓存失效后,可以通过查询布隆过滤器快速判断数据是否存在,如果不存在,就不用通过查询数据库来判断数据是否存在。
即使发生了缓存穿透,大量请求只会查询 Redis 和布隆过滤器,而不会查询数据库,保证了数据库能正常运行,Redis 自身也是支持布隆过滤器的。
缓存雪崩
-
给缓存时间添加随机值 *
- 如果要给缓存数据设置过期时间,应该避免将大量的数据设置成同一个过期时间。我们可以在对缓存数据设置过期时间时,给这些数据的过期时间加上一个随机数,这样就保证数据不会在同一时间过期。
-
互斥锁
当业务线程在处理用户请求时,如果发现访问的数据不在 Redis 里,就加个互斥锁,保证同一时间内只有一个请求来构建缓存(从数据库读取数据,再将数据更新到 Redis 里),当缓存构建完成后,再释放锁。未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。
实现互斥锁的时候,最好设置超时时间,不然第一个请求拿到了锁,然后这个请求发生了某种意外而一直阻塞,一直不释放锁,这时其他请求也一直拿不到锁,整个系统就会出现无响应的现象。
-
双 key 策略
我们对缓存数据可以使用两个 key,一个是主 key,会设置过期时间,一个是备 key,不会设置过期,它们只是 key 不一样,但是 value 值是一样的,相当于给缓存数据做了个副本。
当业务线程访问不到「主 key 」的缓存数据时,就直接返回「备 key 」的缓存数据,然后在更新缓存的时候,同时更新「主 key 」和「备 key 」的数据。
-
后台更新缓存
业务线程不再负责更新缓存,缓存也不设置有效期,而是让缓存“永久有效”,并将更新缓存的工作交由后台线程定时更新。
事实上,缓存数据不设置有效期,并不是意味着数据一直能在内存里,因为当系统内存紧张的时候,有些缓存数据会被“淘汰”,而在缓存被“淘汰”到下一次后台定时更新缓存的这段时间内,业务线程读取缓存失败就返回空值,业务的视角就以为是数据丢失了。
解决上面的问题的方式有两种。
第一种方式,后台线程不仅负责定时更新缓存,而且也负责频繁地检测缓存是否有效,检测到缓存失效了,原因可能是系统紧张而被淘汰的,于是就要马上从数据库读取数据,并更新到缓存。
这种方式的检测时间间隔不能太长,太长也导致用户获取的数据是一个空值而不是真正的数据,所以检测的间隔最好是毫秒级的,但是总归是有个间隔时间,用户体验一般。
第二种方式,在业务线程发现缓存数据失效后(缓存数据被淘汰),通过消息队列发送一条消息通知后台线程更新缓存,后台线程收到消息后,在更新缓存前可以判断缓存是否存在,存在就不执行更新缓存操作;不存在就读取数据库数据,并将数据加载到缓存。这种方式相比第一种方式缓存的更新会更及时,用户体验也比较好。
在业务刚上线的时候,我们最好提前把数据缓起来,而不是等待用户访问才来触发缓存构建,这就是所谓的缓存预热,后台更新缓存的机制刚好也适合干这个事情。
缓存击穿
分布式锁
Redis持久化方式,区别,在什么情况下使用
RDB AOF
RDB:在满足一定的触发条件时将内存的数据本身作为快照保存在硬盘文件上
AOF:以日志的形式记录每个更新(总结.改)操作
RDB:如果对数据不敏感的情况下 选择RDB
AOF:如果以日志形象记录 则使用AOF 一般不推荐单独使用AOF 可能会出现bug (AOF的三种策略 (no/everysec))
谈一下Redis的哨兵机制
通过哨兵服务器监控master/slave实现主从复制集群的自动管理。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JNOJBVcg-1681555223047)(assets/image-20220920143838-ymk2crz.png)]
哨兵机制:
**监控:**监控主数据库和从数据库是否正常运行;
**提醒:**当被监控的某个redis出现问题的时候,哨兵可以通过API向管理员或者其他应用程序发送通知;
**自动故障迁移:**主数据库出现故障时,可以自动将从数据库转化为主数据库,实现自动切换;
**配置中心:**如果故障转移发生了,通知 client 客户端新的 master 地址。
哨兵用于实现 redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。
故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举的问题。
即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的,因为如果一个作为高可用机制重要组成部分的故障转移系统本身是单点的,那就很坑爹了。
具体的配置步骤面试中可以说参考的网上的文档。要注意的是,如果master主服务器设置了密码,记得在哨兵的配置文件(sentinel.conf)里面配置访问密码
Redis集群怎么搭建的,主从怎么设置的
Redis为什么是单线程的
Redis的基本数据类型
Redis安全机制
1.开启防火墙
2.使用低级别权限的用户安装redis
3.不要bind 0.0.0.0:6379,指定ip地址可以方法
4.给redis添加用户名和密码
5.禁止一些高危命令
6.做log监控,及时发现攻击
redis的过期策略以及内存淘汰机制有了解过吗
redis集群后是从主机还是从机上读数据,数据不一致怎么办
高并发的互联网公司中,有1亿条数据需要缓存,请问如何设计存储这批数据?
(分布式存储 可以利用哈希槽的特点)

2532

被折叠的 条评论
为什么被折叠?



