目录
一:redis的基础知识
二:Redis协议与异步方式
三:Redis的存储原理和数据模型
四:Redis的持久化和高可用性
一:redis的基础知识
1:redis是一个内存数据库,KV数据库。
2:包含的数据结构:string,list,hash,set,zset,其中string是一个安全的二进制字符串,我们可以在这个字符串的末尾添加'\0'来代表结束。
3:hash,set,zset他们的field是唯一的。因此可以作为去重操作。
4:关于具体的使用:Redis相关命令详解_redis命令详解-CSDN博客
二:Redis协议与异步方式
1:我们正常的发送任务是:发送命令,命令排队,命令执行,返回结果,很慢。因此采用pipeline的模式进行发送,将多个任务同时发送,然后接收多次结果,不用一个一个阻塞等待了,可以提高效率。因此pipeline只是将多条命令放在一起发送,这样只是为了减少网络传输的时间,就是字面意义的多个任务同时发送,因此是不会出现原子性的,并且这个pipeline模式是客户端提供的机制,但是需要客户端和服务端共同完成。
2:redis中也含有事务操作,可以通过MULTI,EXEC,DISCARD,WATCH来实现。
3:乐观锁:我们通过watch来实现乐观锁,首先在事务开始之前使用watch来监视一个键或者多个键,然后在事务被提交运行之前,当这个被监视的键被修改了,那我们的事务就会被取消,也就是事务中的操作都不作数。
4:lua脚本实现原子性:我们在redis中存在一个lua虚拟机,专门用来执行lua脚本,lua脚本的执行是原子性的,不会被其他命令打扰,并且当使用了脚本之后,不需要开启事务,他自己就是原子性的。
5:我们使用lua脚本,是使用eval这个命令,当这lua脚本比较大的时候,我们可以通过evalsha来将这个脚本进行hash,然后我们直接使用这个hash值即可。具体操作看:Redis协议与异步方式_redis协议和异步-CSDN博客
6:redis满足原子性和隔离性,不满足一致性和持久性。
A:原子性中,也就是事务是一个不可分割的单位,执行期间不被打扰。但是redis不支持回滚操作,即使是事务中出现错误,也要将整个事务执行下去。
C:一致性:事务前后要保持一个一致的状态,不能违反数据的一致性检测。一致性指的是预期的一致性。比如转账功能:一个扣钱一个加钱。能出现扣钱执行错误,加钱执行正确,那么最终还是会加钱成功;系统凭空多了钱;所以redis不满足一致性。
I:隔离性:各个事务之间互相影响的程度,因为redis是单线程处理任务,所以天然具备隔离性。
D:持久性:redis只有在aof持久化策略的时候,并且要在redis.conf中进行配置才能具有持久性。
7:redis支持同步和异步连接,首先同步连接中采用的是阻塞IO,同步不割裂业务,书写是同步的,但是会阻塞当前的线程,通常可以使用线程池来解决效率问题。而对于异步连接,当然是使用回调函数,异步处理,并且在异步的结构体中来说,它可以完美适配reactor模型的回调函数。
三:Redis的存储原理和数据模型
1:redis是单线程还是多线程?我们常说的单线程是redis处理命令的方式是单线程。而redis这个程序的本身是多线程的。也就是处理命令是有先后顺序的。
2:为什么采用单线程处理:因为我们含有五种数据结构,每种数据结构还有多种结构来实现,这样使我们加锁变得很复杂。而且单线程还可以避免多线程之间频繁的上下文切换操作。提高处理速度。
3:单线程为什么这么快?
1:首先redis是内存数据库,我们将数据直接存在内存中,读取速度比磁盘中的速度高近十倍。
2:redis存储数据是KV类型的,redis把一对KV值放入到hashtable中:因为是KV类型的,所以我们每次set,get之前对这个K进行hash,那就可以做到O(1)的时间复杂度。
3:数据结构高效:比如string类型含有:int,raw(>44),embstr(<=44)(嵌入式字符串)。这三种分别存储不同的类型。为什么字符串要以44字节为分界线呢?首先cpu cache line最小访问单位为64个字节。因为embstr是嵌入式字符串,所以是嵌入到一个结构体中的,这个结构体自己就占据了16个字节,还有一个需要表示这个字符串是动态字符串,占3字节,因为是二进制安全字符串为了兼容c的字符串,所以在最后要添加一个'\0',占1字节。最后64-16-3-1=44。
4:采用单线程:单线程可以避免不必要的上下文切换和竞争条件,不存在多进程和多线程消耗cpu,以及加锁和释放锁的操作
5:使用IO多路复用,完美适配reactor
4:hashtable的详解:当我们redis开辟的空间已经使用完毕,也就是负载因子(used/size)>1 的时候,我们再存数据就会发生hash冲突,这时候要开始扩容操作。
扩容:当负载因子大于1的时候,我们进行扩容(翻倍扩容)。但是当fork(aof,rdb)的时候会阻止扩容,当负载因子大于5的时候立即扩容。
缩容:当负载因子小于0.1的时候,进行缩容,会根据2的n次方来缩容。当存储的元素为9,那么包含该元素的为2的4次,也就是16。
5:渐进式rehash:当我们扩缩容之后,我们hash会发生改变,因此需要重新hash,所以叫rehash。我们看源码会发现有两个hashtable:hashtable[2]。hashtable[0]中是我们默认存储的位置。当我们进行rehash的时候,会将数据存放到hashtable[1]中。然后全部存放完之后会将1覆盖到0中去。因为数据太多的时候,一次性重新hash过去的话,会一直占用线程,无法处理其他命令,所以要慢慢来。可以采用分治思想和定时器方法:在每次增删改查的后面加入这个操作,每次移动一个槽位(比较慢)。定时器是在系统不繁忙的时候,每隔一段时间进行一次rehash,每次移动一百个槽位。
6:redis的多线程工作原理:我们每一次的任务处理:读取数据,解析,处理,加密,发送数据。然后我们有很多任务的时候,启动多线程。一个主线程多个工作线程,我们将这些任务的处理操作放入到主线程,其他操作全放入到工作线程中。所以满足了命令的单线程处理,任务的多线程处理。
7:单线程的缺点:只能用于数据量小的高性能操作,当缓存的时候会容易出现缓存雪崩和缓存击穿等问题。无法发挥多核cpu性能,但是可以开多个redis来解决。
四:Redis的持久化和高可用性
1:淘汰策略:我们对于过期的Key和所有的Key都有淘汰策略。因为我们过期的Key并不会被删除,只是我们无法使用这个数据了。
对于过期数据有这四种淘汰策略:最长时间没有使用,最少次数使用的使用随机采样,最近要过期,随机。
对于所有Key(当存储空间满了)有这三种淘汰策略:最长时间没有使用,最少次数使用的使用随机采样,随机。
当然我们可以禁止淘汰策略:no-eviction:直接报错。满了直接报错。
2:fork进程的写时复制:还是看详解比较好:Redis的持久化和高可用性_redis可用性策略-CSDN博客
3:持久化策略:aof,rdb
aof的存储方式是顺序存储,也就是顺序磁盘IO,约等于内存随机IO。但会造成数据冗余:king,Mark,Darren,这三个字符串并不会被最后一个给覆盖,而是三个都会写入。
aof:三种写入策略:
always:每一次写入数据都直接写入内存。
every_sec:将写入数据的操作先写入缓冲区,每隔一秒写入到内存中。
no:通过page cache系统调用,让系统自己调用,但是就是用户不知道什么时候系统进行调用。
aof-rewrite:aof重写机制:aof文件较大,在恢复的时候会按着"set","teacher","Mark"的方式进行解析协议,所以会很慢。但是会解决上述的数据冗余问题,也就是只加载出最后一个数据。再重写aof期间,对Redis的写操作回记录到重写缓冲区,当重写aof结束后,附加到aof文件末尾。
rdb:rdb是将数据直接存储到结构中,不需要进行解析以及执行的命令,因此rdb的数据恢复速度很快。但是rdb需要经常fork子进程(也就是写时复制)来保存数据集到硬盘上。因此当数据集比较大的时候,fork的过程是很耗时的,可能会导致在一些毫秒级内不能相应客户端的请求。
4:主从复制:为了保证数据的高可用,我们将设置几个从数据库,也来保存数据。我们主从之间的数据同步方式是异步的,并且是从连接主。同步的话,耗时比较长,并且如果一个不成功,那就全部不成功了。如果是主连接从的话,我们需要一开始就要配置好所有的从数据库,想法太超前,并且当要重新添加一个从数据库的话,我们很难再主连接这个从了。但是从连接主的话,就没有这些困难。并且对于从数据库是增量同步,而对于新增加的从数据库来说是全量同步,以后是增量同步。
5:redis哨兵模式:我们使用哨兵模式可以当主数据库宕机之后,我们主动将一个从数据库升级为主数据库。客户端连接的时候,是先连接这个哨兵,通过哨兵来查询主节点的位置,并且通过哨兵来监听主从节点的切换,然后连接主数据库。当主节点发生故障,哨兵主动推送新的主库,这样客户端无须重新连接即可切换。
但是可以使用的数据库还是只有一个主数据库,当主库宕机后,这个时候写入数据,会丢失数据。并且他的致命缺点是无法进行横向扩展。
6:Redis cluster集群:比如我们有三个节点,一个节点含有两个从数据库,我们这三个节点会将这一组数据进行分割成三组,一个负责一组数据,他们之间可以使用二进制进行通信。我们客户端连接集群的时候会得到槽位的配置信息,这样我们可以直接定位到目标节点。我们定位到目标节点就是通过缓存了槽位的相关信息,但是当与服务器的配置信息不同的时候,要及时通过修改配置信息。
集群化解决了数据扩容问题,去中心化和主节点对等。集群化的读写操作都是主节点。当一个主节点下线,集群间会互相发送消息,交换节点的状态。下线的节点会选择一个数据最新的从节点作为主节点。当节点继承之后会进行广播消息给其他节点。缺点是因为主从之间还是异步同步消息,所以还是会发生数据的丢失。
https://github.com/0voice