Redis 是一个由C语言开发的、开源的、高性能的Key-Value型NoSQL数据库。
Redis相较于其它 Key-Value 型缓存产品,有如下几个特点: (1)数据存储在内存里,读写速度很快;
(2)支持持久化,可以将内存中的数据存储在磁盘中,在重启系统的时候可以重新加载使用;
(3)丰富的数据类型,有String、list、hash、set、zset(有序集合sorted set)等;
(4)可以进行主从同步;
(5)支持数据的备份,即master-slave模式的数据备份。
Redis常用于缓存,例如记录用户会话信息、记录帖子点赞数、点击数、评论数,缓存文章详情信息等。
从终端进入下载后的目录,然后:
- 解压:tar zxvf redis-6.2.1.tar.gz
- 移动到:sudo mv redis-6.2.1 /usr/local
- 切换到:cd /usr/local/redis-6.2.1/
- 编译测试:make
- 编译安装:sudo make install
启动服务端之后,重新打开另一个终端,输入:redis-cli 就可以进入 redis 客户端。
输入:shutdown
Windows 下安装点击如下链接见菜鸟教程:https://www.runoob.com/redis/redis-install.html
Linux 下安装点击如下链接见菜鸟教程:https://www.runoob.com/redis/redis-install.html
命令 | 示例 |说明
|--------- | --------- | --------- |
| select | select 0 |选择0号数据库
set |set name lily |设置key=name,value=lily
get |get hello |获得key=hello结果
keys| keys he* | 根据Pattern表达式查询符合条件的key
dbsize |dbsize |返回key的总数
exists |exists a| 检查key=a是否存在
del |del a| 删除key=a的数据
expire |expire hello 20 |设置key=hello 20秒后过期
ttl |ttl hello |查看key=hello的过期剩余时间
String最大512Mb,建议单个K-V不超过100Kb。否则在内存中提取数据的效率很低。
命令 | 示例 |说明
|--------- | --------- | --------- |
| get | get hello | 获得key=hello的结果 |
| set | set hello world | 设置key=hello,value=world|
| mset | mset hello world java bset | 一次性设置多个值|
| mget | mget hello java | 一次性获取多个值 |
| del | del hello | 删除key=hello |
| incr | incr count | key值自增 |
| decr | decr count | key值自减|
| incrby | incrby count 99 |自增指定步长|
| decrby | decrby count 99 | 自减指定步长|
| strlen | strlen key | 返回 key 所储存的字符串值的长度 |
| append | append key value | 如果 key 已经存在并且是一个字符串,append命令将指定的 value 追加到该 key 原来值(value)的末尾 |
Hash类型用于存储结构化数据。
命令 | 示例 |说明
|--------- | --------- | --------- |
| hget | hget emp:1 age | 获取hash中key=age的值|
| hset | hset emp:1 age 23 | 设置hash中的age=23|
| hmset |hmset emp:1 age 30 | 设置hash多个值 |
| hmget |hget emp:1 age name | 获取多个hash值 |
| hgetall | hgetall emp:1 | 获取hash所有值 |
| hdel | hdel emp:1 name | 删除emp:1的name|
| hexists | hexists emp:1 name | 检查key=name是否存在,存在返回1|
| hlen | hlen emp:1 | 获取指定长度 |
| hvals | hvals key | 获取哈希表中所有值 |
| hkeys | hkeys key | 获取key中的所有属性 |
| hincrby | hincrby key field increment | 为哈希表 key 中的指定字段的整数值加上增量 increment |
| hincrbyfloat | hincrbyfloat key field increment | 为哈希表 key 中的指定字段的浮点数值加上增量 increment |
List列表就是一系列字符串的“数组”,按插入顺序排序。List列表最大长度为2的32次方-1,可以包含40亿个元素。
| 命令 | 示例 |说明 |
|--------- | --------- | --------- |
| rpush | rpush listkey c b a | 在右侧依次插入c、b、a |
| lpush | lpush listkey f e d | 在左侧依次插入f、e、d |
| rpop | rpop listkey | 在右侧弹出 |
| lpop | lpop listkey | 在左侧弹出 |
| lrange | lrange listkey start stop | 用于输出列表起始到结束的元素|
| llen | llen listkey | 获取listkey的长度 |
| lindex | lindex listkey index | 通过索引获取列表中的元素 |
| lset | set listkey index value | 通过索引修改列表元素的值 |
| linsert | linsert listkey before|after pivot value | 在列表元素pivot前或后插入元素value |
| lrem | lrem listkey count value | 移除列表元素value,count表示移除的个数 |
| ltrim | ltrim listkey startindex stopindex | 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除 |
| del | del listkey | 删除列表 |
Set类型里面存放的是无序不重复元素。
| 命令 | 示例 |说明 |
|--------- | --------- | --------- |
| sadd | sadd myset members | 向集合添加一个或多个数据 |
| smembers | smembers myset | 返回集合中的所有元素(重复的数据自动去重) |
| scard | scard myset | 查看集合中数据的数量 |
| sismember | sismember myset member | 判断某个数据是否在集合中 |
| srem | srem myset members | 移除集合中的一个或多个元素 |
| spop | spop myset [count] | 移除并返回集合中的一个或多个元素(count是移除的数量)|
| srandmember | srandmember myset [count] | 从集合中随机获取count数量的元素 |
| smov | smov set1 set2 member | 将集合set1中的某个元素member移动到集合set2中 |
| sdiff | sdiff set1 set2 | 返回set1与set2中set1独有的元素(差集) |
| sinter | sinter set1 set2 | 返回set1与set2中共有的元素(交集)|
| sunion | sunion set1 set2 | 返回set1和set2中的并集|
Sorted Set 存放的是有序不重复元素。每个元素都会关联一个double类型的score,集合通过score对元素进行从小到大排序。集合中的元素是不可重复的,但score却可以重复。
命令 | 示例 |说明
|--------- | --------- | --------- |
| zadd | zadd zset score1 member1 [score2 member2] | 向有序集合添加一个或多个成员,或者更新已存在成员的分数 |
| zrange | zrange zset start stop [withscores] | 通过索引区间返回有序集合指定区间内的元素,stop为-1表示返回从start开始的所有元素|
| zrank | zrank zset member | 返回集合中指定元素的索引 |
| zscore | zscore zset member | 返回集合中指定元素的score |
|zcard | zcard zset | 获取有序集合的元素数量 |
| zcount | zcount zset score1 score2 | 返回集合分数区间内的元素数量(左右都是闭区间|
| zrangebyscore | zrangebyscore zset score1 score2 [withscores] [limit offset count] | 返回指定分数区间内的元素(闭区间) |
| zrangebyscore | zrangebyscore zset (score1 (score2 [withscores] | 返回指定分数区间内的元素(开区间,有括号“(”表示不包含) |
| zrem | zrem zset members | 移除集合中的一个或多个元素 |
Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。
命令 | 示例 |说明
|--------- | --------- | --------- |
| publish | publish channel message | 向某个频道channel发布信息 |
| subscribe | subscribe channel [channel …] | 订阅一个或多个频道 |
| psubscribe | psubscribe pattern [pattern …] | 订阅符合某种匹配模式的频道 |
| unsubscribe | unsubscribe channel | 取消订阅某个频道 |
RDB(Redis Database)持久化机制:每隔一段时间(每小时或每天),把内存中的数据写入磁盘的临时文件(默认文件名:dump.rdb),作为快照,恢复的时候把快照文件读进内存。
备份与恢复:
内存备份 –> 磁盘临时文件
临时文件 –> 恢复到内存
RDB的优势:
- 每隔一段时间备份,全量备份;
- 灾备简单,可以远程传输;
- 子进程备份的时候,主进程不会有任何io操作(不会有写入修改或删除),保证备份数据的的完整性;
- 相对AOF来说,当有更大文件的时候可以快速重启恢复。
RDB的劣势:
- 发生故障是,有可能会丢失最后一次的备份数据;
- 子进程所占用的内存比会和父进程一模一样,如会造成CPU负担;
- 由于定时全量备份是重量级操作,所以对于实时备份,就无法处理了。
RDB的配置:
- 保存位置,可以在redis.conf自定义:/user/local/redis/working/dump.rdb
保存机制:
save 900 1 -----如果1个缓存更新,则15分钟后备份
save 300 10 ------如果10个缓存更新,则5分钟后备份
save 60 10000 ----- 如果10000个缓存更新,则1分钟后备份
stop-writes-on-bgsave-error
yes:如果save过程出错,则停止写操作
no:可能造成数据不一致
引子:
RDB会丢失最后一次备份的rdb文件,但是其实也无所谓,其实也可以忽略不计,毕竟是缓存,丢了就丢了,但是如果追求数据的完整性,那就的考虑使用AOF了。
AOF特点:
以日志的形式来记录用户请求的写操作。读操作不会记录,因为写操作才会存存储;
文件以追加的形式而不是修改的形式;
redis的aof恢复其实就是把追加的文件从开始到结尾读取执行写操作。
AOF的优势:
- AOF更加耐用,可以以秒级别为单位备份,如果发生问题,也只会丢失最后一秒的数据,大大增加了可靠性和数据完整性。所以AOF可一次,使用fsync操作;
- 以log日志形式追加,如果磁盘满了,会执行 redis-check-aof 工具;
- 当数据太大的时候,redis可以在后台自动重写aof。当redis继续把日志追加到老的文件中去时,重写也是非常安全的,不会影响客户端作;
- AOF 日志包含的所有写操作,会更加便于redis的解析恢复。
AOF的劣势:
- 相同的数据,同一份数据,AOF比RDB大;
- 针对不同的同步机制,AOF会比RDB慢,因为AOF每秒都会备份做写操作,这样相对与RDB来说就略低。每秒备份fsync没毛病,但是的每次写入就做一次备份fsync的话,那么redis的性能就会下降;
- AOF发生过bug,就是数据恢复的时候数据不完整,这样显得AOF会比较脆弱,容易出现bug,因为AOF没有RDB那么简单,但是呢为的产生,AOF就不会根据旧的指令去重构,而是根据当时缓存中存在的数据指令去做重构,这样就更加健壮和可靠了。
AOF的配置:
- appendonly no
no:AOF默认关闭
yes:可以开启
到底采用RDB还是AOF呢?
- 如果你能接受一段时间的缓存丢失,那么可以使用RDB
- 如果你对实时性的数据比较关心,那么就用AOF
- 搭配使用:RDB冷备份,AOF热备份
Redis主从复制可以根据是否是全量分为全量同步和增量同步。
全量同步
Redis全量复制一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份。具体步骤如下:
- 从服务器连接主服务器,发送SYNC命令;
- 主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令;
- 主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;
- 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
- 主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
- 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;
完成上面几个步骤后就完成了从服务器数据初始化的所有操作,从服务器此时可以接收来自用户的读请求。
增量同步
Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。
增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。
Redis主从同步策略
主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。
查看主、从服务器信息的命令: info replication
在从服务器中修改redis的核心配置文件redis.conf的信息:
配置主服务器IP和端口号:replicaof masterip masterport
配置主服务器密码:masterauth master-password
配置主从的读写分离:replica-read-only yes
小知识点:
当从服务器宕机之后,只要重启宕机的服务器就可将数据进行同步;
当主服务器宕机之后,从服务器的role依旧是slave不变,但主服务器连接状态为down(即master_link_status:down);
本质上从服务器是以心跳的方式不断的 ping 主服务器的过程检查主-从服务器之间是否连接。
主服务器会创建一个新的进程写入一些RDB文件,这些文件是写入到 socket 中,它不用接触到磁盘。当开启无磁盘后,可以配置等待从服务器连接主服务器时间(单位:秒),当时间到了之后开始传输RDB文件。无磁盘化适用于磁盘很慢,但网络带宽很快的情况。
使用无磁盘的配置文件:
- repl-diskless-sync no
no:默认是不使用无磁盘化复制(目前无磁盘化属于实验阶段)
yes:开启无磁盘复制
- repl-diskless-sync-delay 5
时间单位为:秒
redis 缓存可以设置定期的时间,一旦某些key过了定期时间就不能查询,但还会占用服务器的内存。
已过期的key如何处理?
设置了expire的key缓存过期了,但是服务器的内存还是会被占用,这是因为redis所基于的两种删除策略。
定期(主动)删除
redis可以定时定期的做检查,它会抽查随机的key,抽查的时间默认 1秒钟10次,一旦抽查到某些key过期,就会将其删除。
- 设置定期删除时间
hz 10 (单位为:秒)
惰性(被动)删除
当客户端请求某个key时,发现其已经过期了,然后才会删除,然后返回一个nil。这种策略友好,不会有太多的损耗,但是内存占用会比较高。
内存缓存淘汰管理机制:MEMORY MANAGEMENT
maxmemory:当内存已使用率到达,则开始清理缓存
- noeviction:旧缓存永不过期,新缓存设置不了,返回错误
- allkeys-lru:清除最少用的旧缓存,然后保存新的缓存(推荐使用)
- allkeys-random:在所有的缓存中随机删除(不推荐)
- volatile-lru:在那些设置了expire过期时间的缓存中,清除最少用的旧缓存,然后保存新的缓存
- volatile-random:在那些设置了expire过期时间的缓存中,随机删除缓
- volatile-ttl:在那些设置了expire过期时间的缓存中,删除即将过期的
LUR:least recently used 最近使用最少
LFU:least frequently used 使用次数最少
哨兵启动命令:redis-sentinel sentinel.conf
哨兵机制原理:
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。当有一个哨兵发现主服务器宕机时,并不会立即进行 failover操作,当有多个哨兵检测到主服务器宕机之后,那么哨兵之间就会进行一次投票,投票的结果由其中一个哨兵发起并进行failover操作,切换成功后,就会通过发布订阅模式,让各个哨兵把发起failover操作哨兵自己监控的从服务器切换为主服务器。
仅仅一个哨兵发现主服务器不可用称为主观下线。
当从服务器切换为主服务器并且其它从服务器更改好主机信息后称为客观下线。
哨兵的作用:
- 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
- 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
哨兵机制文件配置:sentinel.conf
- 将是否开启保护模式前的“#”去掉。不使用保护模式,这样就能保证其它节点服务器能操作访问哨兵。使用默认端口号。
- 开启后台运行哨兵。将默认的“no”改为“yes”。
- pidfile使用默认
- 哨兵执行时的日志输出文件:logfile /usr/local/redis/sentinel/redis-sentinel.log
- 哨兵工作目录:dir /usr/local/redis/sentinel
- 修改哨兵名称:lgk-master
- 哨兵监控的master的内网IP:改为自己的
- 端口号:使用默认的6379
- 哨兵的数量:可更改或使用默认的2
- 将昵称mymaster改为和上面自定义的一样:lgk-master
- 更改密码:将MySUPER–secret-0123password改为自己的密码
- 修改昵称为:mymaster –> lgk-master
- 修改哨兵认为服务器宕机失效的时间(单位为:毫秒):将其改为10s
- 当某个slave被投票为新的master后,其它slave同步数据的数量(“1”表示一个接一个的同步,“2”表示两个两个的同步 ......),使用默认的“1”即可
- 修改昵称为:mymaster –> lgk-master
- 修改昵称为:mymaster –> lgk-master
- 哨兵进行故障转移时的超时时间,当其中某个哨兵超时后就会由另一个哨兵进行故障转移。时间使用默认的3分钟即可。
引子
当master同步数据给slave时,由于是异步复制,假设在复制的过程中,master宕机,slave上的数据可能没有master上的新;当数据同步时,有一定的延时(几百毫秒到一两秒),在这一段时间上的数据有可能丢失,当老的master恢复为slave后,新的数据就会丢失。这些是主从复制或哨兵模式下存在的问题。
Redis 集群:
Redis集群采用去中心化的思想,没有中心节点的说法,对于客户端来说,整个集群可以看成一个整体,可以连接任意一个节点进行操作,就像操作单一Redis实例一样,不需要任何代理中间件,当客户端操作的key没有分配到该node上时,Redis会返回转向指令,指向正确的。
Redis也内置了高可用机制,支持N个master节点,每个master节点都可以挂载多个slave节点,当master节点挂掉时,集群会提升它的某个slave节点作为新的master节点。
集群配置:
- 开启集群:将cluster-enabled yes 前的“#”去掉
- 打开集群节点配置文件
- 打开并设置集群超时时间
- 开启AOF:将“no”改为“yes”
注意:
- 删除 /usr/local/redis/working 下的dump.rdb和appendonly.aof两个文件。这两个文件是单机时使用的,集群之后就不能够有或者将这两个文件里面的内容清空,否则在构建集群会报错。
- rm dump.rdb
- rm appendonly.aof
构建集群:
- 查看可用命令:redis-cli –cluster help
- 命令:redis-cli –cluster create host1:port1 [host2:port2 …] –cluster-replicas args
- args:表示master与slave的比值
- 如果显示没有权限,则可以在redis-cli后加-a password(自己的密码)
redis-cli -a password –cluster create host1:port1 [host2:port2 …] –cluster-replicas args
- 进入集群的控制台的命令:redis-cli -c -a password -h 要进入的服务器ip -p 6379 (-c表示使用集群模式)
- 查看集群相关信息:cluster info
- 查看服务器节点信息:cluster nodes
Redis 单机单实例的配置(.yml)
redis:
database:0 (所使用的redis database)
host: (redis所在服务器的IP)
port:6379
password: (redis的密码)
Redis 哨兵模式的配置(.yml)
redis:
database:0 (所使用的redis database)
password: (redis的密码)
sentinel:
master:lgk-master
nodes:host1:port1,host2:port2,host3:port3 … (所用到的master和slave的IP和端口号)
Redis 集群模式的配置(.yml)
redis:
password: (redis的密码)
cluster:
nodes:host1:port1,host2:port2,host3:port3 … (所用到的master和slave的IP和端口号)
缓存穿透:
缓存穿透是指查询一个一定不存在的数据,由于缓存是查不到时就要从数据库里去查询,当查不到数据时就不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。
缓存穿透解决方案:
- 如果查询返回的数据为空,则把这个空结果(比如空字符串、空对象、空数组或list)进行缓存,但要设置短的过期时间,最长不超过5分钟。
- 布隆过滤器:待后续补充!!!
缓存雪崩:
数据进行缓存时会设置过期时间,一旦过期时间重合,某个时刻大量的key同时到达过期时间,同时失效,而此时恰恰有很大的流量进来,此时所有的请求都会冲击数据库,就会造成数据库的崩溃,这就是缓存雪崩。
缓存雪崩的预防措施:
- 永不过期
- 过期时间错开
- 多种缓存结合
- 采购第三方 redis
答:Redis是一个使用C语言开发的高性能Key-Value型NoSQL数据库。支持的数据类型有String、list、hash、set、zset(有序集合sorted set)等(范围查询)。Redis常用于缓存,例如记录用户会话信息、记录帖子点赞数、点击数、评论数,缓存文章详情信息等。
答:Redis有两种持久化方式,RDB(Redis Database):指定时间间隔对数据进行快照存储;AOF(Append Only File):每一个收到的写命令都通过write函数追加到文件中。
bgsave做镜像全量持久化,AOF做增量持久化。因为bgsave会耗费较长的时间,实时性不够好,在停机的时候会导致大量的数据丢失,所以需要配合AOF来使用;在Redis重启时,会使用bgsave持久化文件重新构建内存,再使用AOF重放近期的操作指令来实现恢复重启前的状态。
(如果机器突然断电会怎样? 取决于AOF日志sync属性的配置,如果不要求性能,可以在写每条指令是都sync一下磁盘,这样就不会丢失数据,但在高性能条件下一般会使用定时的sync,比如1秒一次,这样就算断电,也只会丢失一秒的数据而已。
bgsave的原理是什么? 是fork和cow。fork是指Redis通过创建子进程来进行bgsave操作,cow是copy on write,子进程创建后,父进程共享数据段,父进程继续提供读写服务,写脏的页面数据就会逐渐和子进程分离开来。)
答:可以使用事务机制来对数据库进行增、删、改操作时同步更新Redis缓存。
答:
- 存储方式不同:Memcache把数据全部存在内存之中,断电后会挂掉,而且数据不能超过内存大小;Redis会把部分数据存储在硬盘上,这样就能保证数据的持久性。
- 数据支持类型不同:Memcache支持的数据类型相对简单,而Redis有复杂的数据类型。
- 使用的底层模型不同:它们之间的底层实现方式以及与客户端之间通信使用的协议不一样。因为一般系统在调用系统函数时会浪费一定的时间去移动和请求,所以Redis直接构建了自己的VM机制。
答:Redis可以使用主从同步,从从同步。第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将RDB文件全量同步到复制节点,复制节点接受完成后将RDB镜像加载到内存,加载完成,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。
答:Redis使用setnx(set if not exit)指令来争抢锁,抢到之后再用expire给锁加一个过期时间防止锁忘记释放。
Redis分布式锁不能解决超时的问题,所以要设置一个过期时间。
因为set指令有非常复杂的参数,所以可以同时把setnx指令和expire指令合成一条指令来执行。
答:因为CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存或网络带宽。Redis利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销。
答:Redis Sentinal(哨兵)关注高可用,在master宕机时会自动将slave(从动装置)提升为master继续提供服务。Redis Cluster(集群)关注扩展性,在单个Redis内存不足时,使用Cluster进行分片存储。
答:缓存穿透是指查询一个一定不存在的数据,由于缓存是查不到时就要从数据库里去查询,当查不到数据时就不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。
解决方案:如果查询返回的数据为空,则把这个空结果进行缓存,但要设置短的过期时间,最长不超过5分钟。
答:使用keys指令可以找出指定模式的key列表。
由于Redis是单线程的,keys指令会导致线程阻塞一段时间,线上服务会停顿指导keys指令执行完毕服务才能恢复。这个时候可以使用scan指令无阻塞的提取出指定模式的key列表,但会有一定的重复率,可以在客户端做一次去重。scan指令所花费的时间整体上会比直接使用keys指令长。
答:
- Master写内存快照,save命令调度RDBSave函数,会阻塞主线程的工作,当快照比较大时对性能的影响非常大,会间断性暂停。所以Master最好不要写内存快照。
- Master AOF持久化,如果不重写AOF文件,这个持久化方式对性能的影响是最小的,但是AOF文件会不断增大,AOF文件过大会影响Master重启时的恢复速度。Master最好不要做任何持久化工作,包括内存快照和AOF内存文件,特别是不要启用内存快照做持久化,如果数据比较关键,某个slave开启AOF备份数据,时间为每秒同步一次。
- Redis主从复制性能问题。为了主从复制的速度和连续的稳定性,Slave和Master最好在同一个局域网内。