Redis学习笔记及总结

redis的学习笔记,后续在深入学习会更新,如有错误还请指出,谢谢。

Redis

复制代码
解决分布式nginx 带来的session问题,当服务器发生改变时,
此时session数据存储就不在同一个服务器中。

1.存储到客户端cookie中,但是安全性低

2.session复制,会造成空间浪费

3.使用nosql数据库,用户信息存放到nosql数据库中每当用户访问时如果存在这个用户信息,
便可访问可减少io操作,缓解cpu,内存压力。(作为缓存使用)

列式存储,将数据库每一列当作一部分存储。行式,将每行当作一部分存储。

安装步骤

复制代码
1.安装gcc yum install gcc-c++
2.出现错误升级gcc版本
 yum  -y  install  centos-release-scl
 yum  -y  install  devtoolset-9-gcc  devtoolset-9-gcc-c++  devtoolset-9-binutils
 
 -------环境设置----------
 #临时有效,退出 shell 或重启会恢复原 gcc 版本
 scl enable devtoolset-9 bash
 
 #长期有效
 echo "source /opt/rh/devtoolset-9/enable" >>/etc/profile
 
 3.编译redis 进入redis目录
  make

redis启动

复制代码
进入redis的conf的配置文件
./redis-server ../conf/redis.conf
./redis-cli

NOSQL

复制代码
只为改变性能而不改变逻辑关系。

指非关系型型数据库,使用key-value模式存储,
增加了数据库扩展能力 不遵循sql标准 不支持acid 
超过sql性能。适用于数据高并发读写,对数据海量读写,数据高扩展性。不适用与事务支持。

支持多种类型存储String ,set,list,hash等

redis的操作都是原子性的。

redis会周期性吧更新的数据写入磁盘或者把修改操作写入追加记录文件。

并且实现了master-slave 主从同步。

redis

复制代码
redis-check-dump修复有问题的dumo.rdb文件
reids-check-aof修复有问题的aof文件
redis-sentinel:redis集群使用

redis-server 服务器启动(前台不推荐)

后台启动:
cd /opt
cd redis
复制redis.conf 到/etc/redis.conf
修改复制的redis.conf 中的 daemonno 为 yes
然后进入 /usr/local/bin目录
输入 redis-server /etc/redis.conf 启动redis-cli 输入ping验证输出PONG即成功

redis-cli连接客户端
shutdown 关闭
6379端口:merz对应的按键
默认提供16个数据库0-15编号 所有库密码相同
select 编号 切换数据库

redis集合

查看当前库所有key keys *

exists key 判断key是否存在

set key value 设置key value

type key 查看key的类型

del(unlink) key 删除key对应的value 和key (unlink选择非阻塞删除 在后续会异步操作)

expire key 时间 设置key过期时间

ttl key 查看key过期时间(-2表示过期 -1 表示永不过期)

flushdb 清空数据库

dbsize 查看当前库key数量

flushall 通杀所有库

字符串

动态字符串,SDS结构,本质还是buf[]字符 数组

list

单k多v存储,简单的字符串列表按照插入顺序排序, 将每一个val 都看作字符串

底层实际上是一个双向链表,对两端操作性能高,查找效率低。

lpush/rpush k v1 v2 v3 从左/右插入一个或多个值 (相当于stack栈口向左/右)

rpop/rpop 左/右取出一个值

rpoplpush k1 k2 从k1右取出一个v插入k2左边(相当于栈弹出进入另一个栈)

lrange k 起始 结束 根据下表获得元素(左到右) lrange k 0 -1 取得所有值

lindex k index 按照索引下标获取元素(从左到右)

llen k 获取列表长度

linster k before v newv 在v后面插入一个new插入值

lrem k n v 从左边删除n个相同的v(从左到右)

lset k i v 把下标为index的值替换为v

list数据结构是一个快速链表

在元素较少情况加会使用一块连续的内存地址

也就是 ziplist也即是压缩列表,但随着数据量增多会变成quicklist,

因为IP普通链表需要的指针空间太大,会比较浪费空间,

redis将链表与ziplist联合起,

组成quicklist就是ziplist使用双向指针。满足了快速的插入删除又不会空间冗余。

多个ziplist构成list

set

sadd k v1 v2 v3 添加一个或多个v到k集合中

smembers k 取出该集合所有值

scard k 返回集合元素个数

srem k v1 v2 删除集合中一个或多个个数

spop k 随机取出一个值

srandmember k n 集合随机取出n个值

smove source dest v 从集合中一个值v取出到另一个地方

sinter k k 取两个集合交集

sdiff k k 取两个集合差集

sunion k k 取两个集合并集

set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,

当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,

并且set提供了判断某个成员是否在一个set集合内的重要接口.

这个也是list所不能提供的。Redis的Set是string类型的无序集合。

它底层其实是一个value为null的hash表,所以添加,删除,查找的复杂度都是O(1)。

一个算法,随着数据的增加,执行时间的长短,如果是O(1),

数据增加,查找数据的时间不变。Set数据结构是dict字典,字典是用哈希表实现的。

Java中HashSet的内部实现使用的是HashMap,只不过所有的value都指向同一个对象。

Redis的set结构也是一样,它的内部也使用hash结构,所有的value都指向同一个内部值。

hash

数据类型为Map<Object,Map<String,Object>>

Redis hash 是一个键值对集合。

Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。

hset <key> <field> <value>给<key>集合中的 <field>键赋值<value>

hget <key1> <field>从<key1>集合<field>取出 value

hmset <key1><field1><value1><field2><value2>... 批量设置hash的值

若key相同时候设置的不同val 以最后一个 k v 设置值

hexists<key1><field>查看哈希表 key 中,给定域 field 是否存在。

hkeys <key>列出该hash集合的所有field

hvals <key>列出该hash集合的所有value

hincrby <key><field><increment>为哈希表 key 中的域 field 的值加上增量 1 -1

hsetnx <key><field><value>将哈希表 key 中的域 field 的值设置为 value ,

当且仅当域 field 不存在 .

可用购物车的商品用户数量等有着hash类型的应用场景

zset

Redis有序集合zset与普通集合set非常相似,是一个没有重复元素的字符串集合。 可用作排行榜

不同之处是有序集合的每个成员都关联了一个评分(score),这个评分(score)被用来按照从最低分到最高分的方式排序集合中的成员。

集合的成员是唯一的,但是评分可以是重复了 。

因为元素是有序的,

所以你也可以很快的根据评分(score)或者次序(position)来获取一个范围的元素。

访问有序集合的中间元素也是非常快的,因此你能够使用有序集合作为一个没有重复成员的智能列表。

zadd <key><score1><value1><score2><value2> 将一个或多个 member 元素及其 score 值加入到有序集 key 当中。

zrange <key><start><stop> withscores 返回有序集 key 中,下标在<start><stop>之间的元素带WITHSCORES,

可以让分数一起和值返回到结果集。

zrangebyscore key min max withscores limit offset count

返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。

有序集成员按 score 值递增(从小到大)次序排列。

zrevrangebyscore key max min ............................withscores limit offset count

同上,改为从大到小排列。

zincrby <key> <n> <value> 为元素的score加上增量n

zrem <key> <value>删除该集合下,指定值的元素

zcount <key> <min> <max>统计该集合,分数区间内的元素个数

zrank <key> <value>返回该值在集合中的排名,从0开始。

SortedSet(zset)是Redis提供的一个非常特别的数据结构,一方面它等价于Java的数据结构Map<String, Double>,

可以给每一个元素value赋予一个权重score,另一方面它又类似于TreeSet,内部的元素会按照权重score进行排序,

可以得到每个元素的名次,还可以通过score的范围来获取元素的列表。

zset底层使用了两个数据结构

(1)hash,hash的作用就是关联元素value和权重score,保障元素value的唯一性,可以通过元素value找到相应的score值。

(2)跳跃表,跳跃表的目的在于给元素value排序,根据score的范围获取元素列表。

跳表

跳表类似于一个二分过程

跳表是一种多层的有序链表结构,最底层是包含所有元素的完整有序链表,每往上一层都是下一层的子集,并且元素在各层链表中都保持有序排列。高层链表中的元素会 "跳跃" 过一些底层链表的元素,以此来加速查找过程。每个节点除了存储数据值,还会有多个指针,分别指向该节点在不同层级链表中的下一个节点。

首先zset数据本身是一个有序的链表,为了加快链表的查询速度,借鉴一下二分的想法。

因为我们猜数据的大概位置使用二分肯定是最快的方法,所以在这个跳表中。我们随机决定每个数据分数其所在的层数。

  1. 如果下一个节点的值小于 target,则沿着当前层级链表继续前进
    在跳表的查找过程中,我们从最高层链表的头节点开始。当我们比较当前节点的下一个节点的值与 target 时,如果下一个节点的值小于 target,这意味着目标元素 target 肯定不在当前节点到下一个节点这个区间内,并且由于链表是有序的,目标元素可能在更靠后的位置。所以,我们沿着当前层级的链表继续向前移动,继续比较下一个节点的值与 target 的大小,直到找到一个节点,其下一个节点的值大于等于 target 为止。

  2. 如果下一个节点的值大于 target,则下降到下一层链表继续查找
    当我们发现当前节点的下一个节点的值大于 target 时,说明目标元素 target 不在当前层级链表中当前节点之后的部分。由于跳表中高层链表的元素是稀疏分布的,我们需要到下一层链表中去进一步查找,因为下一层链表包含了更多的元素,更有可能找到目标元素。通过下降到下一层链表,我们缩小了查找范围,继续在新的层级上重复上述比较和移动的过程。

  3. 如果下一个节点的值等于 target,则查找成功
    如果在比较过程中,发现当前节点的下一个节点的值恰好等于 target,这就意味着我们已经找到了目标元素,查找操作成功结束。

    复制代码
     有序集合在生活中比较常见,例如根据成绩对学生排名,

    根据得分对玩家排名等。对于有序集合的底层实现,
    可以用数组、平衡树、链表等。
    数组不便元素的插入、删除;
    平衡树或红黑树虽然效率高但结构复杂.
    链表查询需要遍历所有效率低。

    Redis采用的是跳跃表。
    跳跃表效率堪比红黑树,实现远比红黑树简单。
    从第一个开始每次跳跃规定的元素数,
    当大时候往回找小的时候继续跳

redis配置文件

复制代码
tcp-backlog

设置tcp的backlog,backlog实是一个连接队列,backlog队列总和=未完成三次握手队列 + 已经完成三次握手队列。

在高并发环境下你需要一个高backlog值来避免慢客户端连接问题。

注意Linux内核会将这个值减小到/proc/sys/net/core/somaxconn的值(128),所以需要确认增大/proc/sys/net/core/somaxconn和/proc/sys/net/ipv4/tcp_max_syn_backlog(128)两个值来达到想要的效果


loglevel 
指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为notice
四个级别根据使用阶段来选择,生产环境选择notice 或者warning

redis发布和订阅

发布订阅:消息的通信模式,发送者发送消息,订阅者接受消息。

redis新数据类型

bitmaps

复制代码
合理地使用操作位能够有效地提高内存使用率和开发效率。 底层仍然是一个string类型 并且bitmap扩充以字节为单位 
只有用到第二个字节时才会扩容一个byte
	Redis提供了Bitmaps这个"数据类型"可以实现对位的操作:
(1)	Bitmaps本身不是一种数据类型, 实际上它就是字符串(key-value) , 但是它可以对字符串的位进行操作。
(2)	Bitmaps单独提供了一套命令, 所以在Redis中使用Bitmaps和使用字符串的方法不太相同。
可以把Bitmaps想象成一个以位为单位的数组, 数组的每个单元只能存储0和1,表示状态 数组的下标在Bitmaps中叫做偏移量。

    
setbit<key><offset><value>设置Bitmaps中某个偏移量的值(0或1)offset:偏移量从0开始
getbit<key><offset>获取Bitmaps中某个偏移量的值 获取键的第offset位的值(从0开始算)
bitcount
统计字符串被设置为1的bit数。一般情况下,给定的整个字符串都会被进行计数,
通过指定额外的 start 或 end 参数,可以让计数只在特定的位上进行。
start 和 end 参数的设置,都可以使用负数值:
比如 -1 表示最后一个位,而 -2 表示倒数第二个位,start、end 是指bit组的字节的下标数,二者皆包含。
bitcount<key>[start end] 统计字符串从start字节到end字节比特值为1的数量

bitop  and(or/not/xor) <destkey> [key...]
bitop是一个复合操作,
它可以做多个Bitmaps的and(交集) 、 or(并集) 、 not(非) 、 xor(异或)操作并将结果保存在destkey中。

 bitmap适合适用于巨大的数据量存储

hyperLogLog

复制代码
进行对元素的去重统计
Redis HyperLogLog 是用来做基数统计的算法,
HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,
计算基数所需的空间总是固定的、并且是很小的。
但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,
所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
pfadd <key>< element> [element ...]   添加指定元素到 HyperLogLog 中
pfcount<key> [key ...] 进行对元素的去重统计
pfmerge<destkey><sourcekey> [sourcekey ...]  将一个或多个HLL合并后的结果存

geospatail

复制代码
经纬度的查询
geoadd key 经 纬

事务

Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。Redis事务的主要作用就是串联多个命令防止别的命令插队。

在exec执行事务之前redis 事务中出现错误指令 则指令队列不会被执行

在exec执行事务之后 出现错误 就会让正确指令正确执行 但是错误指令不会执行 而且不会回滚

Multi、Exec、discard

从输入Multi命令开始,输入的命令都会依次进入命令队列中(组队阶段),但不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行。(执行阶段)

组队的过程中可以通过discard来放弃组队。 (执行中断和事务回滚相似但本质不同)

复制代码
multi 开启事务
exec 执行命令
discard 中断执行
组队阶段有一个命令发生错误,最终都不会执行
执行阶段发生错误,执行到错误的命令时错误的不会执行成功,其他的会执行成功。

事务冲突

悲观锁

悲观锁(Pessimistic Lock) , 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁**,**表锁等,读锁,写锁等,都是在做操作之前先上锁。

乐观锁

乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。若有则会更新失败

乐观锁适用于多读的应用类型,这样可以提高吞吐量。

Redis就是利用这种check-and-set机制实现事务的。(CAS)

Redis事务三特性

单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

没有隔离级别的概念:队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行

不保证原子性:事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚

redis 持久化

分为两种方法1、RDB 数据快照 2、AOF 日志记录

RDB

redis6 与 7 在 RDB 文件的一次保存中做出了更改 6中更加频繁 7中更加稳定例如6中 15min中 有过一次key的变化即可重写快照一次 7中可以1h 内一次key的变动 (是在redis内 有key的变动即可算一次)

在指定的时间间隔内将内存中的数据集快照形成一个 rdb文件(当前数据状态保存到文件中)写入磁盘,也就是Snapshot快照,它恢复时是将快照rdb文件直接读到内存里

shutdown 或者将数据库清空的操作都会有dump.rdb文件进行快照存储

并且在启动redis时会自动读取dump.rdb文件 rdb文件与redis做分机备份存储。

备份方式:
1、主动触发

设置触发时间为 多少s内,key的改变次数。例如 save 5 2 表示5s内,有2次key的改变就会主动触发备份机制。

2、被动触发

备份执行:Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中(开辟的一个临时区域,临时文件dump.rdb),待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件**。**

手动触发方式

1、save

在主程序中执行会阻塞当前redis服务器,直到持久化工作完成,执行sava命令期间,Redis不能处理其他命令。

2、bgsave

Redis会在后台异步进行快照操作,不阻塞快照同时还可以响应客户端请求,该触发方式会fork一个子进程由子进程复制持久化过程。

save与bgsave区别

当执行save时其他进程会被阻塞 redis不能服务, 但是bgsave会在主进程执行命令时创建一个子进程进行持久化操作。

整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。(若在进行数据快照的过程中,没有到达快照时间时,却因为服务器关闭的原因,会导致想要进行数据快照的数据的丢失)

这个配置文件默认叫做dump.rdb文件 默认创建在启动redis目录下

FORK

Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等)数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程

在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,Linux中引入了"写时复制技术"。

写时复制技术:指可自定义设置,规定时间内有规定的key的变化,则将变化的数据进行快照,接着保存到磁盘中。

一般情况父进程和子进程会共用同一段物理内存,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。

save :save时只管保存,其它不管,全部阻塞。手动保存。不建议。

bgsave Redis 会在后台异步进行快照操作, 快照同时还可以响应客户端请求。

可以通过lastsave 命令获取最后一次成功执行快照的时间

rdb优势: 适合大规模的数据恢复。 对数据完整性和一致性要求不高更适合。使用节省磁盘空间。恢复速度快。

劣势:Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑。虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能。在备份周期在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改。

Redis持久化之AOF

日志 的形式来记录每个写操作(增量保存),将Redis执行过的所有写指令记录下来(读操作不记录 ),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作

在配置文件配置开启

写回策略

同步写(全写法)

每秒写回(默认使用 性能较均衡)

回写法(都写在aof日志的内存缓冲区,由 os控制何时写回磁盘)

在redis6 之前 aof-rdb 使用的dir保存 aof rdb文件使用同一个dir

7之后使用的 根据conf文件配置的 aof文件名称 但是也在dir内 会再次创建一个目录并在此目录下创建aof文件

redis7之后使用multipart类型的aof文件 base

持久化过程

(1)客户端的请求写命令会被append追加到AOF缓冲区内;

(2)AOF缓冲区根据AOF持久化策略[always,everysec,no]将操作sync同步到磁盘的AOF文件中;

(3)AOF文件大小超过重写策略或手动重写时,会对AOF文件rewrite重写,压缩AOF文件容量;

(4)Redis服务重启时,会重新load加载AOF文件中的写操作达到数据恢复的目的;

只记录写操作 默认写入incr文件

重写机制

1、主动触发

  • 满足配置文件中而选项后,Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时

2、手动触发

使用bgrewriteaof

AOF默认不开启

可以在redis.conf中配置文件名称,默认为appendonly.aof

AOF文件的保存路径,同RDB的路径一致。

AOF和RDB同时开启,redis听谁的?

AOF和RDB同时开启,系统默认取AOF的数据(数据不会存在丢失)

AOF启动/修复/恢复

aof文件出现错误时 就不能启动 redis

使用 redis-check-aof --fix 可以修复aof文件

aof文件可将执行的错误命令直接删除后 例如 flushall 致使缓存清除 可直接修改aof文件删除 flushall 命令后再次重启redis使缓存恢复

同时修改aof文件也可导致 缓存被清除

复制代码
  AOF的备份机制和性能虽然和RDB不同, 但是备份和恢复的操作同RDB一样,都是拷贝备份文件,需要恢复时再拷贝到Redis工作目录下,启动系统即加载。
正常恢复
 修改默认的appendonly no,改为yes
将有数据的aof文件复制一份保存到对应目录
(查看目录:config get dir)
恢复:重启redis然后重新加载
异常恢复
 修改默认的appendonly no,改为yes
 如遇到AOF文件损坏,
通过/usr/local/bin/redis-check-aof--fix appendonly.aof
进行恢复
备份被写坏的AOF文件
恢复:重启redis,然后重新加载
AOF同步频率设置

appendfsync always

始终同步,每次Redis的写入都会立刻记入日志;性能较差但数据完整性比较好

appendfsync everysec

每秒同步,每秒记入日志一次,如果宕机,本秒的数据可能丢失。

appendfsync no

redis不主动进行同步,把同步时机交给操作系统。

Rewrite压缩
复制代码
是什么:
AOF采用文件追加方式,文件会越来越大为避免出现此种情况,新增了重写机制, 
当AOF文件的大小超过所设定的阈值时,
Redis就会启动AOF文件的内容压缩,
只保留可以恢复数据的最小指令集.可以使用命令bgrewriteaof
重写原理,如何实现重写
AOF文件持续增长而过大时,
会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),
文件会改变 并且将重复写的命令压缩为 一条 例如重复set k1 111。。。 直至超过了设置的峰值 这时候会将aof文件压缩 只设置一条set k1 111.。命令
redis4.0版本后的重写,实质上就是把rdb 的快照,
以二级制的形式附在新的aof头部,
作为已有的历史数据,替换掉原来的流水账操作。
no-appendfsync-on-rewrite:
如果 no-appendfsync-on-rewrite=yes ,不写入aof文件只写入缓存,用户请求不会阻塞,但是在这段时间如果宕机会丢失这段时间的缓存数据。(降低数据安全性,提高性能)
如果 no-appendfsync-on-rewrite=no,  还是会把数据往磁盘里刷,但是遇到重写操作,可能会发生阻塞。(数据安全,但是性能降低)
触发机制,何时重写
Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发
重写虽然可以节约大量磁盘空间,减少恢复时间。但是每次重写还是有一定的负担的,因此设定Redis要满足一定条件才会进行重写。
auto-aof-rewrite-percentage:设置重写的基准值,文件达到100%时开始重写(文件是原来重写后文件的2倍时触发)
auto-aof-rewrite-min-size:设置重写的基准值,最小文件64MB。达到这个值开始重写。
例如:文件达到70MB开始重写,降到50MB,下次什么时候开始重写?100MB
系统载入时或者上次重写完毕时,Redis会记录此时AOF大小,设为base_size,
如果Redis的AOF当前大小>= base_size +base_size*100% (默认)且当前大小>=64mb(默认)的情况下,Redis会对AOF进行重写。
3、重写流程
(1)bgrewriteaof触发重写,判断是否当前有bgsave或bgrewriteaof在运行,如果有,则等待该命令结束后再继续执行。
(2)主进程fork出子进程执行重写操作,保证主进程不会阻塞。
(3)子进程遍历redis内存中数据到临时文件,客户端的写请求同时写入aof_buf缓冲区和aof_rewrite_buf重写缓冲区保证原AOF文件完整以及新AOF文件生成期间的新的数据修改动作不会丢失。
(4)1).子进程写完新的AOF文件后,向主进程发信号,父进程更新统计信息。2).主进程把aof_rewrite_buf中的数据写入到新的AOF文件。
(5)使用新的AOF文件覆盖旧的AOF文件,完成AOF重写。
优劣势

优势: 备份机制更稳健,丢失数据概率更低。 可读的日志文本,通过操作AOF稳健,可以处理误操作。

劣势:比起RDB占用更多的磁盘空间。恢复备份速度要慢。每次读写都同步的话,有一定的性能压力。存在个别Bug,造成恢复不能。

总结
复制代码
官方推荐两个都启用。
如果对数据不敏感,可以选单独用RDB。
不建议单独用 AOF,因为可能会出现Bug。
如果只是做纯内存缓存,可以都不用。
RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储
 AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾. 
 Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大
只做缓存:如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.
 同时开启两种持久化方式
在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据, 因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.
RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件。那要不要只使用AOF呢?
 建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有AOF可能潜在的bug,留着作为一个万一的手段。
性能建议
因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则。
如果使用AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了。
代价,一是带来了持续的IO,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。
只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上。
默认超过原大小100%大小时重写可以改到适当的数值。

可以关闭自动触发 但是仍然可以使用指令进行人工触发 这时候redis即为纯缓存模式

aof+rdb方式

RDB+AOF的混合方式---->RDB镜像做全量持久化,AOF做增量持久化,先使用RDB进行快照存储,然后使用AOF持久化记录所有的写操作,当重写策略满足或者手动触发重写的时候,将最新的数据存储为新的RDB记录。这样的话,重启服务器的时候会从RDB和AOF两部分恢复数据,既保证了数据完整性,又提高了恢复数据的性能。简单来说:混合持久化方式产生的文件一部分是RDB格式,一部分是AOF格式--->AOF包括了RDB头部+AOF混写

事务

  • 单独的隔离操作:Redis的事务仅仅是保证事务里的操作会被连续独占的执行,redis命令执行是单线程架构,在执行完事务内所有指令前是不可能再去同时执行其他客户端的请求的。
  • 没有隔离级别的概念:因为事务提交前任何指令都不会被实际执行,也就不存在"事务内的查询要看到事务里的更新,在事务外查询不能看到"这种问题了。
  • 不保证原子性:Redis的事务不保证原子性,也就是不保证所有指令同时成功或同时失败,只有决定是否开始执行全部指令的能力,没有执行到一半进行回滚的能力
  • 排他性:Redis会保证一个事务内的命令依次执行,而不会被其他命令插入

管道

使用管道时要注意管道文件大小 使用合理的大小才能够对内存的占用最少且管道性能最好。

pipeline是为了解决RTT往返时间,仅仅是将命令打包一次性发送,对整个redis的执行不造成其他任何影响。

多路复用

redis单线程的,避免了多线程环境下的线程同步和切换问题,所有操作都在一个线程上顺序执行,这使得 Redis 的执行路径非常清晰,并且更容易进行优化。

redis 使用一个主线程监听命令,当有命令过来时,主线程便会建立与redis的连接,处理命令,提高IO读写性能。

  • IO 多路复用:是指通过一种机制,可以监视多个文件描述符(Socket 连接等)的状态变化,当有文件描述符就绪(可读或可写)时,就会通知程序进行相应的处理,而不是让程序阻塞在某个特定的文件描述符上等待其就绪。
  • 文件描述符:在操作系统中,一切皆文件,网络连接也被视为文件。文件描述符是一个整数,是操作系统为了标识不同的文件或网络连接而分配的索引值,程序通过文件描述符来对文件或网络连接进行读写等操作。

redis中使用的IO多路复用方式:epoll 使用事件驱动的方式,当文件描述符就绪时,会将该事件添加到一个就绪队列中,程序只需要从就绪队列中获取事件并处理即可,而不需要像 select 和 poll 那样遍历所有文件描述符。epoll 能高效处理大量的文件描述符,并且具有较低的延迟,适合处理高并发的网络连接。

当epoll 监听的文件描述符 是就绪状态时候就会调用epoll_wait ,主线程就会阻塞去处理IO。

主从复制

主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主

主从复制的作用:读写分离,性能扩展。

容灾快速恢复(指在从中若其中一个从数据损坏,在其他的从中可以读取) 都是一主多从

配从库不配主库

replicaof 在配置文件中配置 主机的ip port

slaveof 是指令进行主从配置

slaveof no one 切除与其他主机的联系 成为主机

使用命令指定的主从关系重启后关系就没有了

一主多从

拷贝多个redis.conf文件include(写绝对路径)

开启daemonize yes

Pid文件名字pidfile

指定端口port

Log文件名字

dump.rdb名字dbfilename

Appendonly 关掉或者换名字

新建一个redis6379.conf写入

include /myredis/redis.conf

pidfile /var/run/redis_6379.pid

port 6379

dbfilename dump6379.rdb

复制代码
slaveof  <ip><port>
成为某个实例的从服务器
写入主机 再从机即可读取。
从机只可以读取 不可以写。 写操作在主机
即使从机宕机 但是也可以读取到主机之前写入的缓存数据
若主机宕机 从机不会选取一个做主机 而且从机会等待主机
主机再次启动后 仍然以原来的关系继续运行
薪火相传与反客为主

上一个Slave可以是下一个slave的Master,Slave同样可以接收其他slaves的连接和同步请求,那么该slave作为了链条中下一个的master, 可以有效减轻master的写压力,去中心化降低风险。

用 slaveof <ip><port>

中途变更转向:会清除之前的数据,重新建立拷贝最新的

风险是一旦某个slave宕机,后面的slave都没法备份

主机挂了,从机还是从机,无法写数据了

但是 即是从机也是主机的实例依然不能写操作

当一个master宕机后,后面的slave可以立刻升为master,其后面的slave不用做任何修改。

用slaveof no one 将从机变为主机。

复制原理

Slave启动成功连接到master后会发送一个sync命令

Master接到命令启动后台的存盘进程(主从复制会触发RDB),同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,以完成一次全同步(首次连接会进行全量复制),连接后进行增量复制。若从机宕机,再次连接后会进行断点处的重传

全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中

增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步

但是只要是重新连接master,一次完全同步(全量复制)将被自动执行

哨兵模式

主从+哨兵 不等于 集群

投票数由哨兵数量决定

哨兵一般配置3个

客观下线;

哨兵的配置文件会根据监控的配置自动写入配置文件中

master宕机后 slave数据不会损坏 但是会进行投票后选出 成为master的主机

并且原先是master的主机会变成slave

sentinel哨兵会先选出来一个哨兵leader然后这leader去选取slave中的master

选取哨兵leader算法是raft算法 思想是先到先得

slave选取规则

1.根据节点优先级选取 2.变为master节点 让其他节点变为当前的slave 3. 原先的master重启后也会变成slave 三步骤自动完成

哨兵使用:

反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库

复制代码
自定义的/myredis目录下新建sentinel.conf文件,名字绝不能错
配置哨兵,填写内容
sentinel monitor mymaster 127.0.0.1 6379 1
其中mymaster为监控对象起的服务器名称,1 为至少有多少个哨兵同意迁移的数量。
启动哨兵 redis-sentinel sentinel.conf --sentienel

redis做压测可以用自带的redis-benchmark工具
执行redis-sentinel  /myredis/sentinel.conf 
哪个从机会被选举为主机呢?根据优先级别:slave-priority 
原主机重启后会变为从机。
若master宕机 从机会继续等待主机重新上线 
这时候只能读取不能写入 所以要尽快选取一个从机当作主机
复制延时

由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。

故障恢复

优先级在redis.conf中默认:slave-priority 100,值越小优先级越高

偏移量是指获得原主机数据最全的

每个redis实例启动后都会随机生成一个40位的runid

区分redis集群和主从架构

主从架构是主节点和从节点的数据保持一致的,写操作都落在主节点上。

redis集群是 主从架构+分片 的叠加,每一个主从架构都只有属于本分片的数据。

呢么水平扩容就是指 集群扩容,纵向扩容就是指主从架构的扩容,增大节点硬件设备。

redis集群

redis分区方法
  1. 取余分区 由redis实例的数量决定 hash算法固定 。但是扩容麻烦 容易故障
  2. 一致性hash 尽量减少服务器变动的影响

hash环 : 经过hash算法计算后,让其空间变成一个环形的逻辑空间

节点映射:将ip地址经过hash映射后到环空间的某个位置。从这个位置顺时针进行遍历,找到遇到的第一个服务器 呢么这对k-v就放在这个服务器上、

扩展性和容错性较好 但具有数据倾斜问题,数据分布不均匀,大多数数据缓存在某一台服务器上。

hash槽:均匀分布数据。主要管理数据与节点关系 将数据放在一个槽内 ,每个槽对应着某一个redis节点。 且以槽为单位移动数据。

redis集群不保证强一致性。

集群联通后会出现conf文件。

集群

集群通信策略:gossip协议进行通信,节点之间通讯的目的是为了维护节点之间的元数据信息。这些元数据就是每个节点包含哪些数据,是否出现故障,通过gossip协议,达到最终数据的一致性。

原理就是在不同的节点间不断地通信交换信息,一段时间后,所有的节点就都有了整个集群的完整信息,并且所有节点的状态都会达成一致。每个节点可能知道所有其他节点,也可能仅知道几个邻居节点,但只要这些节可以通过网络连通,最终他们的状态就会是一致的。Gossip协议最大的好处在于,即使集群节点的数量增加,每个节点的负载也不会增加很多,几乎是恒定的。

集群数据同步策略

在Redis中主从复制的同步策略有两种

全量同步:复制master所有数据到slave,第一次主从节点建立连接时候及逆行全量同步

增量同步:仅复制master与slave差异数据到slave

集群读写:

一定要注意槽位。 即key对应的槽位才能写入。在启动redis时 在命令中加入 redis-cli -a -password -c 可以自动进行路由变换。即自动映射key到对应的槽位中。

集群主从容错:

主机master宕机,slave可作为master。且原来的master重启后,不在成为master。但是使用 cluster failover命令可以恢复原本的m-s拓扑。并且在s升为m过程中不可以写 故不可保证强一致性

集群扩容:

需要使用redis-cli -- cluster add-node ip:port ip:port命令添加新增节点。并且使用redis-cli --cluster reshard ip:port (此处的ip:port是主节点)进行重新分槽片

id是新加入的节点的master节点。新加入的节点分配到的槽片从原本各个节点的槽片中分出一些得到。

使用redis-cli --cluster add-node ip:port(s)IP:port(m) --cluster-slave --cluster-mater-id id (id为master的id 由cluser nodes指令可以查询)

集群缩容:

先删从节点再删主节点

使用 redis-cli --cluster check ip:port可以查询节点信息

redis-cli --cluster del-node ip:port(从机ip:port) 从机id 删除从节点

可全部分配给receive节点

receving是接受槽片的节点id

source1是被释放的节点id

mset mget
集群的动态扩容缩容,水平扩展

集群中的扩容缩容需要注意的就是hash槽的重新分配,redis在集群中数据是分片存储,即key-valule 需要存储到对应的hash槽中,呢么不管是扩容缩容,都需要对hash槽中数据进行迁移。

若redis集群完整才进行服务提供则可配置
可分为16384个槽片 但最大建议1000个

redis节点之间需要进行心跳包的交互,使用16384个槽每次发送的心跳包大小2KB不会导致数据量过大 。并且节点不会超过1000个16384个槽片够用,

除此之外,

redis中中文乱码

由于默认使用JDK序列化故会乱码

1.使用Stringredis 2.配置redis序列化 使用StringRedis 代替默认的序列化类

并且使用redis-cli --raw 进行redis库解析中文

redis集群某个节点宕机

redis有容灾机制,slave会变成master,是集群继续工作。但是这时候微服务程序会报错 找不到原来的主机redis。springboot2.X 使用的letture不会自动更新redis集群信息。可使用配置自动更新拓扑结构

复制代码
#支持集群拓扑动态感应刷新,自适应拓扑刷新是否使用所有可用的更新,默认false关闭
spring.redis.lettuce.cluster.refresh.adaptive=true
#定时刷新
spring.redis.lettuce.cluster.refresh.period=2000

缓存穿透

key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会压到数据源,从而可能压垮数据源。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。

1.服务器压力突然加大

2.redis命中率降低查不到数据

3.一直查询数据库导致数据库崩溃

redis查询不到数据,出现很多非正常的url访问

解决方案

复制代码
一个一定不存在缓存及查询不到的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。
解决方案:
(1)对空值缓存:如果一个查询返回的数据为空(不管是数据是否不存在),
我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过五分钟

(2)采用布隆过滤器:(布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。
布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。)
将所有可能存在的数据哈希到一个足够大的bitmaps中,一个一定不存在的数据会被这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力。

布隆过滤器

由一个位图和多个hash函数组成 没来一条数据 会进行一系列hash函数得到多个hash值

然后在位图中多个位置将其对应的状态设置为1

查找某个数据时候 如果多个hash 的状态都是 1 呢么查询成功 否则失败

查询不存在就是一定不存在 但是若存在则不一定存在。

误判解决策略:

1、首先在创建层面就是hash函数重新选择

2、已创建好的过滤器就要使用 二次验证方法,过滤器只做初步的过滤, 如果存在则在数据库或者是redis中在验证一次,确保存在的元素一定存在.

3、设置多级过滤器, 逐层过滤,不断减少误判的可能性

布隆过滤器的扩容
1 扩容

动态rehash,扩大过滤器的长度,进行过滤器的扩容.

2 多级过滤器

设置每层装满的阈值,例如第一层设置1000长度为扩容阈值,当第一层装满1000个数据时候, 创建第二层 ,当第二层装满2000个数据时候,创建第三层. 插入数据的时候,要插入所有的过滤器中.

呢么在后续就按照此方法创建多级过滤器. 判断时候,要看到多有的过滤器,只要有一个过滤器判定存在呢,那么就可能存在. 在进行二次判断,确定一定存在. 呢么即使数据增长,可能在以前层的数据被挤掉,但是由于所有层的过滤器都被插入了数据,所以数据只要可能存在,呢么一定会被检测到.

缓存击穿

key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

redis中大量key过期

redis某个key过期了大量访问使用这个key,导致redis无法命中

解决方案

key可能会在某些时间点被超高并发地访问,是一种非常"热点"的数据。这个时候,需要考虑一个问题:缓存被"击穿"的问题。

解决问题:

(1)预先设置热门数据:在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长

(2)实时调整:现场监控哪些数据热门,实时调整key的过期时长

(3)使用锁:

(1) 就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db。

(2) 先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX)去set一个mutex key

(3) 当操作返回成功时,再进行load db的操作,并回设缓存,最后删除mutex key;

(4) 当操作返回失败,证明有线程在load db,当前线程睡眠一段时间再重试整个get缓存的方法。

缓存雪崩

key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

缓存雪崩与缓存击穿的区别在于这里针对很多key缓存,前者则是某一个key

数据库压力变大,服务器大量访问等待

在极少的时间段内,出现了大量key集中key过期

解决方案

(1) 构建多级缓存架构:nginx缓存 + redis缓存 +其他缓存(ehcache等)

(2) 使用锁或队列:

用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。不适用高并发情况

(3) 设置过期标志更新缓存:

记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际key的缓存。

(4) 将缓存失效时间分散开:

比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

redis淘汰策略

两个维度:

1、所有的数据

2、只有过期时间的数据

三种方式

1、随即淘汰

2、访问频率最低的淘汰

3、最近最久未使用的淘汰

对于有过期时间的数据,呢么就还有按照过期时间淘汰

跳表

改进的链表,带有层级关系,每个节点可能存在这个跳表中的多个层,每个层有多个节点,而且这些每层中的节点都会用指针相连,在初始时,每个节点的层级是随机生成的,生成一个随机数,若小于0.25呢么层级+1,如果大于0.25呢么就会停止生成层级。结构如下

跳表和B+树区别

跳表主要用于内存中数据查找,而B+树优化的目标是IO操作次数。跳表插入并不保证有序性,只需调整链表指针即可,而B+树插入数据要保持有序且可能会导致节点分裂效率不高。

压缩列表

有连续内存块组成的顺序型数据结构,类似于数组:

redis中的COW时机问题,以及大key对AOF的影响

问题:redis中 进行COW的时机我有点不理解,fork子进程后,应该是父子进程都有了页表,呢也就是指向了同一个物理内存,即使父进程对大key进行了修改,但是页表没有变的话,为什么要COW?

在 Redis 里,写时复制(Copy-On-Write,COW)是一种重要的内存优化机制,它和 Redis 的持久化操作紧密相关,下面为你详细解释其时机与原理。

1. Redis 的持久化和 fork

Redis 的 RDB 持久化会借助fork()系统调用来生成子进程。当执行fork()时,内核会复制父进程的页表,使得父子进程的页表都指向相同的物理内存页。这个时候,物理内存并不会复制,父子进程共享同一份物理内存,这就避免了在fork时产生大量的内存复制开销。

2. 触发 COW 的时机

一旦fork完成,父进程和子进程开始执行不同的任务。子进程的任务是将当前内存中的数据写入 RDB 文件,而父进程则继续处理客户端的请求。当父进程需要对某个内存页进行写操作(比如修改一个大 key)时,就会触发 COW 机制。

3. 为什么要 COW

虽然父子进程初始时共享相同的物理内存页和页表,但为了保证数据的一致性,就需要进行 COW。具体的原因如下:

  • 数据一致性:子进程的任务是生成 RDB 文件,它需要保证自己看到的内存数据在整个持久化过程中是一致的。要是父进程直接在共享的物理内存页上进行写操作,就会改变子进程看到的数据,进而导致生成的 RDB 文件内容不准确。
  • 页表的作用:页表仅仅是虚拟地址到物理地址的映射。当父进程尝试写操作时,内核会检测到这个页是父子进程共享的。为了保证子进程数据的一致性,内核会复制一份该物理内存页,并且更新父进程的页表,让其指向新复制的物理内存页。这样一来,父进程的写操作就不会影响子进程看到的原始数据。

4. 总结

COW 机制的核心目的就是在保证子进程数据一致性的同时,避免在fork时进行大量的内存复制。只有在父进程真正进行写操作时,才会复制相关的物理内存页,这有效减少了内存开销和fork的时间。

综上所述,即使父进程修改大 key 时页表没有变,但为了保证子进程数据的一致性,仍然需要进行 COW 操作。

相关推荐
李慕婉学姐7 小时前
【开题答辩过程】以《基于JAVA的校园即时配送系统的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·开发语言·数据库
奋进的芋圆8 小时前
Java 延时任务实现方案详解(适用于 Spring Boot 3)
java·spring boot·redis·rabbitmq
sxlishaobin9 小时前
设计模式之桥接模式
java·设计模式·桥接模式
model20059 小时前
alibaba linux3 系统盘网站迁移数据盘
java·服务器·前端
荒诞硬汉9 小时前
JavaBean相关补充
java·开发语言
提笔忘字的帝国9 小时前
【教程】macOS 如何完全卸载 Java 开发环境
java·开发语言·macos
2501_941882489 小时前
从灰度发布到流量切分的互联网工程语法控制与多语言实现实践思路随笔分享
java·开发语言
華勳全栈10 小时前
两天开发完成智能体平台
java·spring·go
alonewolf_9910 小时前
Spring MVC重点功能底层源码深度解析
java·spring·mvc
沛沛老爹10 小时前
Java泛型擦除:原理、实践与应对策略
java·开发语言·人工智能·企业开发·发展趋势·技术原理