Redis学记笔记
Redis是一个开源的内存中的数据结构存储系统,它可以用作:数据库、缓存和消息中间件。
Redis 内置了复制(Replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(Transactions) 和不同级别的磁盘持久化(Persistence),并通过 Redis哨兵(Sentinel)和自动分区(Cluster)提供高可用性(High Availability)。
Redis也提供了持久化的选项,Redis的所有数据都是保存在内存中,然后不定期的通过异步方式保存到磁盘上(这称为“半持久化模式” RDB持久化);也可以把每一次数据变化都写入到一个append only file( AOF持久化)里面(这称为“全持久化模式”)。
Redis不使用表,他的数据库不会预定义或者强制去要求用户对Redis存储的不同数据进行关联。
数据库的工作模式按存储方式可分为:硬盘数据库和内存数据库。Redis 将数据储存在内存里面,读写数据的时候都不会受到硬盘 I/O 速度的限制,所以速度极快。
- Redis类型
- String(字符串)
string 是 redis 最基本的类型,你可以理解成与 Memcached 一模一样的类型,一个 key 对应一个 value。
string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象。
string 类型是 Redis 最基本的数据类型,string 类型的值最大能存储 512MB。
-
- Hash(哈希)
Redis hash 是一个键值(key=>value)对集合。
Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。
-
- List(列表)
Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。
-
- Set(集合)
Redis 的 Set 是 string 类型的无序集合。
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
-
- zset(sorted set:有序集合)
Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
zset的成员是唯一的,但分数(score)却可以重复。
- Redis事务(Transactions)
概念,定义:所谓的事务,就是指对数据进行读写的一系列操作。事务在执行时,会提供专门的属性保证,包括原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability),也就是 ACID 属性。这些属性既包括了对事务执行结果的要求,也有对数据库在事务执行前后的数据状态变化的要求。
redis官方事务解释:Transactions – Redis
特性 | 解释 | Redis是否满足 |
原子性(Atomicity) | 一个事务中的多个操作必须都完成,或者都不完成 | |
一致性(Consistency) | 指数据库中的数据在事务执行前后是一致的 | |
隔离性(Isolation) | 它要求数据库在执行一个事务时,其它操作无法存取到正在执行事务访问的数据。 | |
持久性(Durability) | 数据库执行事务后,数据的修改要被持久化保存下来。当数据库重启后,数据的值需要是被修改后的值。 |
-
- Redis 如何实现事务?
- mysql ----->start trantation ---->普通sql ------->回滚rollback------>commit
- redis -------> multi-(放入queue队列)-->普通命令----->discard (取消,不是真正的回滚感觉) ---->exec
事务的执行过程包含三个步骤。
- 第一步,事务的开启 MULTI。
- 第二步,具体操作(例如增删改数据)发送给服务器端。例如 GET、SET 等。
不过,这些命令虽然被客户端发送到了服务器端,但 Redis 实例只是把这些命令暂存到一个命令队列中,并不会立即执行。(Queued)
- 第三步,提交事务EXEC 命令,当服务器端收到 EXEC 命令后,才会实际执行命令队列中的所有命令。
做一个简单的事务
multi
get sarula_string_test_name
set sarula_string_test_name sarula_new_name
exec
-
- Redis 的事务机制能保证哪些属性?
- 原子性(Atomicity)
我们需要分三种情况来看。
- 第一种情况是,在执行 EXEC 命令前,客户端发送的操作命令本身就有错误(比如语法错误,使用了不存在的命令),在命令入队时就被 Redis 实例判断出来了。
multi
get sarula_string_test_name
happy sarula_string_test_name
set sarula_string_test_name sarula_11111
exec
解释:对于这种情况,在命令入队时,Redis 就会报错并且记录下这个错误。此时,我们还能继续提交命令操作。等到执行了 EXEC 命令之后,Redis 就会拒绝执行所有提交的命令操作,返回事务失败的结果。这样一来,事务中的所有命令都不会再被执行了,保证了原子性。
- 第一种情况不同的是,事务操作入队时,命令和操作的数据类型不匹配,但 Redis 实例没有检查出错误。但是,在执行完 EXEC 命令以后,Redis 实际执行这些事务操作时,就会报错。不过,需要注意的是,虽然 Redis 会对错误命令报错,但还是会把正确的命令执行完。在这种情况下,事务的原子性就无法得到保证了。
multi
get sarula_string_test_name
lpop sarula_string_test_name
set sarula_string_test_name sarula_11111
exec
* 传统数据库(例如 MySQL)在执行事务时,会提供回滚机制,当事务执行发生错误时,事务中的所有操作都会撤销,Redis 中并没有提供回滚机制。虽然 Redis 提供了 DISCARD 命令,但是,这个命令只能用来主动放弃事务执行,把暂存的命令队列清空,起不到回滚的效果。
编程无bug,那是不太可能的。
那么我们怎么去避免,脚本方式,脚本里面写判断:Redis 脚本使用 Lua 解释器来执行脚本。
简单理解就是,一组动作,要么全部执行,要么就全部不执行.从而避免出现数据不一致的情况(原子性).
- 第三种情况:在执行事务的 EXEC 命令时,Redis 实例发生了故障,导致事务执行失败。
在这种情况下,如果 Redis 开启了 AOF 日志,那么,只会有部分的事务操作被记录到 AOF 日志中。我们需要使用 redis-check-aof 工具检查 AOF 日志文件,这个工具可以把未完成的事务操作从 AOF 文件中去除。这样一来,我们使用 AOF 恢复实例后,事务操作不会再被执行,从而保证了原子性。当然,如果 AOF 日志并没有开启,那么实例重启后,数据也都没法恢复了,此时,也就谈不上原子性了。
redis-check-aof --fix AOF文件
总结
命令入队时就报错,会放弃事务执行,保证原子性;命令入队时没报错,实际执行时报错,不保证原子性;EXEC 命令执行时实例故障,如果开启了 AOF 日志,可以保证原子性。
注意理解误区:单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。
- 一致性(Consistency)
- 情况一:命令入队时就报错在这种情况下,事务本身就会被放弃执行,所以可以保证数据库的一致性。
- 情况二:命令入队时没报错,实际执行时报错在这种情况下,有错误的命令不会被执行,正确的命令可以正常执行,也不会改变数据库的一致性。
- 情况三:EXEC 命令执行时实例发生故障在这种情况下,实例故障后会进行重启,这就和数据恢复的方式有关了,
总结来说,在命令执行错误或 Redis 发生故障的情况下,Redis 事务机制对一致性属性是有保证的。
*如果只有部分操作被记录到了 AOF 日志,我们可以使用 redis-check-aof 清除事务中已经完成的操作,数据库恢复后也是一致的。
- 隔离性(Isolation)
两种情况来分析:
- 并发操作在 EXEC 命令前执行,此时,隔离性的保证要使用 WATCH 机制来实现,否则隔离性无法保证;
Watch机制:
解释:WATCH 机制的作用是,在事务执行前,监控一个或多个键的值变化情况,当事务调用 EXEC 命令执行时,WATCH 机制会先检查监控的键是否被其它客户端修改了。如果修改了,就放弃事务执行,避免事务的隔离性被破坏。然后,客户端可以再次执行事务,此时,如果没有并发修改事务数据的操作了,事务就能正常执行,隔离性也得到了保证。
- 并发操作在 EXEC 命令后执行,此时,隔离性可以保证。(好理解,不用讲)
因为 Redis 是用单线程执行命令,而且,EXEC 命令执行后,Redis 会保证先把命令队列中的所有命令执行完。所以,在这种情况下,并发操作不会破坏事务的隔离性。
- Redis持久性
redis会fork一个子进程作为主进程的副本;
主进程负责接收并处理客户端请求,子进程负责将内存中的数据写入硬盘中的临时文件;
RDB(Redis DataBase)------数据 name - sarula
AOF(AppendOnlyFiel)------命令 set name sarula
两种方式:aof rdb
-
- RDB(Redis DataBase)
RDB持久化概念:是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。
Redis会将数据集的快照dump到dump.rdb文件中。此外,我们也可以通过配置文件来修改Redis服务器dump快照的频率,在打开redis6380.conf文件之后,我们搜索save,可以看到下面的配置信息:
cd /usr/local/redis
save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,则dump内存快照。
save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,则dump内存快照。
save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,则dump内存快照。
-
- AOF(AppendOnlyFiel)
AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。
cd /usr/local/redis
vim redis6380.conf
在Redis的配置文件中存在三种同步方式,它们分别是:
appendfsync always #每次有数据修改发生时都会写入AOF文件。
appendfsync everysec #每秒钟同步一次,该策略为AOF的缺省策略。
appendfsync no #从不同步。高效但是数据不会被持久化。
#如果AOF和RDB同时存在的时候,Redis会优先使用从AOF文件来还原数据库状态,如果AOF关闭状态时,则从RDB中恢复
- Redis为什么这么快
- 采用单线程,完全基于内存,内存运算级别 (单进程多线程模型:MySQL)
- 数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;
- 使用多路I/O复用模型,非阻塞IO;
(相关概念:并发性I/O流,意味着能够让一个计算单元来处理来自多个客户端的流请求。并行性,意味着服务器能够同时执行几个事情,具有多个计算单元)
-
- 数据库工作模式
数据库的工作模式按存储方式可分为:硬盘数据库和内存数据库。Redis 将数据储存在内存里面,读写数据的时候都不会受到硬盘 I/O 速度的限制,所以速度极快。
-
- I/O复用之 Epoll
说白一点,就是“代理”
- epoll 是Linux内核中的一种可扩展IO事件处理机制,核心方法只有三个。
Linux系统内核提供了三个系统调用: include/linux/syscalls.h
源码:eventpoll.c - fs/eventpoll.c - Linux source code (v4.19.76) - Bootlin
- epoll_create;用于创建 保存epoll文件描述符的空间。redis服务器在启动时,创建事件循环,调用epoll_create方法创建epoll实例
- epoll_ctl:用于添加和删除监视对象的文件描述符。当有新的客户端连接时,把新的连接描述符注册到epoll实例。
- epoll_wait:等待文件描述符发生变化。调用epoll_wait获取客户端产生的io事件
- epoll效率高,是因为基于红黑树(自平衡二叉树)、双向链表、事件回调机制
- redis的IO多路复用,Linux上用epoll进行了实现
- 多进程redis设置
- 设置步骤
cd /usr/local/redie #切换到redis安装路径下
cp redis.conf redis6380.conf #复制到新的配置文件供第二个redis使用
-
- 启动多进程netstat -lnpt
重启redis
redis-cli shutdown
redis-server /usr/local/redis/redis.conf
redis-server /usr/local/redis/redis6380.conf
查看 ps -aux | grep redis
- Redis 常见问题及解决方案
- 缓存穿透
就是客户持续向服务器发起对不存在服务器中数据的请求。客户先在Redis中查询,查询不到后去数据库中查询。
解决方案:
1.接口层增加校验,对传参进行个校验,比如说我们的id是从1开始的,那么id<=0的直接拦截;
2.缓存中取不到的数据,在数据库中也没有取到,这时可以将key-value对写为key-null,这样可以防止攻击用户反复用同一个id暴力攻击
- 缓存击穿
就是一个很热门的数据,突然失效,大量请求到服务器数据库中。
大家都喜欢看腾讯视频上的《水果传》,但是你的会员突然到期了,大家在你的网站上看不到腾讯视频的账号,纷纷打电话向你询问,这就是缓存击穿
解决方案:
最好的办法就是设置热点数据永不过期,拿到刚才的比方里,那就是你买腾讯一个永久会员
- 缓存雪崩
就是大量数据同一时间失效
解决方案:
1.缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
2.如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
- 防火墙端口的打开
centos系统防火墙需要开启
查看已开放端口
1 | firewall-cmd --zone=public --list-ports |
添加开放端口
1 | firewall-cmd --zone=public --add-port=80/tcp --permanent |
重载防火墙配置,不然查看开放端口都查不到,也不能用,重载配置后即可
1 | firewall-cmd --reload |