Redis的基本数据类型1.String:Redis并没有直接使用C语言传统的字符串表示,而是自己实现了一个抽象类型,叫做简单动态字符串SDS。C语言中的字符串不记录自己的长度信息,但是SDS保存了长度信息,使得获取字符串长度的时间从O(N)减少到O(1),并且可以避免缓冲区溢出,减少修改字符。字符串长度所需的内存重新分配数。2.Linkedlist链表:Redis链表是一种双向无环链表结构。很多发布订阅、慢查询、监控等功能都是使用链表实现的。每个链表节点都用一个listNode结构表示,每个节点都有指向前一个节点和后一个节点的指针,头节点的前后节点都指向NULL。3.Dictionaryhashtable:一种存储键值对的抽象数据结构。Redis使用哈希表作为底层实现。每个字典都有两个哈希表,用于正常使用和重新哈希。哈希表使用链地址法来解决键冲突。多个键值对被分配到相同的索引位置。将形成一个单向链表。在扩容或缩容哈希表时,为了服务的可用性,rehash过程不是一次完成,而是逐步完成。4、跳表skiplist:跳表是有序集合的底层实现之一。Redis使用跳表来实现有序的集合键和集群节点的内部结构。redis跳表由zskiplist和zskiplistNode组成,zskiplist用来保存跳表信息(表头、表尾节点、长度等),zskiplistNode用来表示表跳节点,以及每个跳表的层高table从1到32随机在同一个跳表中,多个节点可以包含相同的score,但是每个节点的成员对象必须是唯一的,节点按照score排序,如果score相同,则为根据成员对象的大小排序。5.整数集intset:一种用于存储整数值的抽象数据结构。不会有重复元素,底层实现是数组。6.压缩列表ziplist:压缩列表是一种为节省内存而开发的顺序数据结构。它可以包含多个节点,每个节点可以存储一个字节数组或一个整数值。基于这些基本数据结构,redis封装了自己的对象系统,包括string对象string、list对象list、hash对象hash、集合对象set、有序集合对象zset,每个对象至少使用一种基本数据结构。Redis通过encoding属性来设置对象的编码形式,提高灵活性和效率,redis会根据不同的场景自动优化。不同对象的编码如下:string对象string:int整数,embstr编码的简单动态字符串,raw简单动态字符串list对象list:ziplist,linkedlisthash对象hash:ziplist,hashtable集合对象set:intset,hashtable是顺序集合objectzset:ziplist,skiplist为什么Redis很快?redis的速度很快。单机redis可以支持每秒几万个并发。与mysql相比,其性能是mysql的几十倍。速度快的原因有几个:完全基于内存操作C语言实现,优化数据结构,在几种基本数据结构的基础上,redis做了很多优化,性能极高。使用单线程,没有上下文切换成本基于非阻塞IO多路复用机制,为什么Redis在6.0之后转为多线程?redis使用多线程并不意味着它完全抛弃了单线程。Redis仍然使用单线程模型来处理客户端请求,但是使用了多线程。处理数据读写和协议分析,执行命令或使用单线程。这样做的目的是因为redis的性能瓶颈在于网络IO而不是CPU。使用多线程可以提高IO读写的效率,从而提高redis的整体性能。你知道什么是热键吗?如何解决热键问题?所谓hotkey问题就是突然有几十万个请求去访问redis上某个特定的key,会导致流量过于集中,达到物理网卡的上限,从而导致redis服务器宕机并导致雪崩。热键的解决方案:提前把热键分散到不同的服务器,减压加入二级缓存,热键数据提前加载到内存,如果redis崩溃了,去内存查询什么是缓存击穿,缓存击穿透明,缓存雪崩?缓存击穿缓存击穿的概念是单个键的并发访问过高。当它过期时,所有请求将直接发送到数据库。这类似于热键的问题,但重点是所有的请求都会在到达到期日期时发送到DB。.解决方案:加锁更新,比如请求查询A,发现没有缓存,加锁A的key,同时去数据库查询数据,写入缓存,然后返回用户,以便后续请求可以从缓存数据中获取。将过期时间组合写入value,异步刷新过期时间,防止此类现象。https://tva缓存穿透缓存穿透是指查询缓存中不存在的数据,每次请求都会命中DB,就好像缓存不存在一样。为了解决这个问题,加了一层Bloomfilter。Bloomfilter的原理是当你存储数据的时候,会通过一个hash函数映射到位数组中的K个点,同时将它们置1。这样当用户再次查询A时,A在Bloomfilter中的值为0,则直接返回,不会向DB发送细分请求。很明显,使用Bloomfilter后,会出现误判的问题,因为它本身就是一个数组,多个值可能会落到同一个位置。理论上只要我们数组的长度足够长,误判的概率越低,这种问题还是根据实际情况来问比较好。缓存雪崩当某个时刻缓存发生大规模失效,比如你的缓存服务宕机了,大量的请求会进来,直接打到DB上,可能会导致整个系统崩溃,这叫做雪崩。雪崩问题与击穿和热键问题不同。它指的是大型缓存的到期。Avalanche的几种解决方案:为不同的key设置不??同的过期时间,避免同时过期。限流,如果redis宕机了,可以限流,避免同时有大量请求导致DB二级缓存崩溃,同hotkey方案。Redis的过期策略有哪些?Redis主要有两种过期删除策略。惰性删除惰性删除是指在我们查询key的时候检测到key,如果到了过期时间就删除。很明显,他有个缺点就是如果不访问这些过期的key,就无法删除,一直占用内存。周期性删除周期性删除是指redis每隔一段时间检查一次数据库,删除其中过期的key。由于不可能轮询所有的key去删除,redis每次都会随机选择一些key去检查删除。那么如果不定期+懒惰地删除过期密钥怎么办?假设redis在定时随机查询key的时候不会每次都删除key,如果不查询这些key,那么这些key会一直保存在redis中,无法删除。这个时候就会去到redis的内存淘汰机制。volatile-lru:从设置过期时间的key中移除最近最少使用的key进行淘汰volatile-ttl:从设置过期时间的key中移除将要过期的keytheexpirationtimeset随机选择key淘汰allkeys-lru:选择最近最少使用的key淘汰allkeys-random:从key中随机选择key淘汰noeviction:当内存达到阈值时,有哪些持久化方式新的写操作??有什么不同?Redis的持久化方案分为RDB和AOF。RDBRDB持久化可以根据配置手动或定期执行。它的作用是将某个时间点的数据库状态保存到RDB文件中。RDB文件是一个压缩的二进制文件,通过它可以恢复某个时刻的数据库。状态。由于RDB文件是保存在硬盘上的,即使redis崩溃或者退出,只要RDB文件存在,就可以用来恢复被恢复数据库的状态。可以通过SAVE或BGSAVE生成RDB文件。SAVE命令会阻塞redis进程,直到RDB文件生成。在进程阻塞期间,redis无法处理任何命令请求,这显然是不合适的。BGSAVE会fork一个子进程,然后由子进程负责生成RDB文件。父进程可以继续处理命令请求而不会阻塞进程。AOFAOF不同于RDB。AOF通过保存redis服务器执行的写命令来记录数据库的状态。AOF通过追加、写入、同步三个步骤来实现持久化机制。当AOF持久化开启后,服务器执行写命令后,写命令会追加到aof_buf缓冲区的末尾。服务器在每次事件循环结束前,会调用flushAppendOnlyFile函数来决定是否将aof_buf的内容保存到AOF文件中,可以通过配置appendfsync来决定。always##aof_buf内容被写入并同步到AOF文件everysec##将aof_buf中的内容写入AOF文件,如果上次同步AOF文件距离现在超过1秒,将再次同步AOF文件no##aof_buf内容写入AOF文件,但是AOF文件没有同步。同步时间由操作系统决定。如果不设置,默认选项是everysec,因为always是最安全的(只会丢失一个eventloopwritecommand),但是性能较差,everysec模式可能只会丢失1秒的数据,而no模式与everysec效率相同,但会在上次同步AOF文件后丢失所有写命令数据。Redis如何实现高可用?要实现高可用,一台机器肯定是不够的,redis有两种方案来保证高可用。主从架构主从模式是实现高可用最简单的方案,核心是主从同步。主从同步的原理如下:slave向master发送sync命令。master收到sync后,执行bgsave生成一个完整的RDB文件。master将slave的写命令记录到缓存中。bgsave执行后,将rdb文件发送给slave,slave执行master发送缓存中的write命令发送给slave,slave执行我这里写的命令为sync,但是psync一直在redis2.8之后使用,而不是sync,因为sync命令会消耗大量的系统资源,而psync效率更高。基于主从方案的Sentinel的缺点还是很明显的。如果master挂了,那么数据就写不出来了,slave也就失去了作用,整个架构也就不可用了。除非你手动切换,主要是没有自动故障转移机制。哨兵的功能比简单的主从架构要全面得多。具有自动故障转移、集群监控、消息通知等功能。Sentry可以同时监控多个主从服务器,当被监控的master下线时,会自动将一个slave提升为master,然后新的master继续接收命令。整个过程如下:初始化sentinel,将普通的redis代码替换成sentinel专用代码,初始化masters字典和服务器信息,服务器信息主要保存ip:port,记录实例的地址和ID创建两个连接与master,命令connection和Subscribe连接,订阅sentinel:hello通道每隔10秒向master发送info命令,获取master及其下所有slave的当前信息。当发现master有新的slave时,sentinel和新的slave也建立两个连接,同时每隔10秒发送一次info命令更新master信息。Sentinel每1秒向所有服务器发送一次ping命令。如果服务器在配置的响应时间内返回无效回复,它将被标记为离线并选举领导哨兵,leadingsentinel需要半数以上的sentinel同意,leadingsentinel选择下线master的所有slave中的一个转换为master,这样所有的slave都可以??从新的master复制数据并设置原来的master作为新的master从服务器,当原来的master再次回复连接时,成为新master的从服务器哨兵每秒都会向所有实例(包括主从服务器和其他哨兵)发送一个ping命令,并根据回复判断是否已经下线,这种方式称为主观下线。当主观判断下线时,会询问其他监控哨兵。如果超过一半的投票认为它下线,则将其标记为客观下线,同时触发故障转移。能说说redis集群的原理吗?如果依赖哨兵可以实现redis的高可用,又想在容纳海量数据的同时支持高并发,就需要redis集群。Redis集群是redis提供的分布式数据存储解决方案。集群通过数据分片共享数据,并提供复制和故障转移功能。节点一个redis集群由多个节点组成,多个节点通过clustermeet命令连接起来。节点握手过程:节点A根据接收到的IP地址和端口号收到客户端的集群meet命令A,向B发送meet报文NodeB收到meet报文并返回pongA知道B已经收到meet报文,返回一个ping报文,握手成功最后,节点A会将节点B的信息通过gossip协议传播到集群中的其他节点,其他节点也会与B握手。Slotredis以集群分片的形式保存数据。整个集群数据库分为16384个槽位。集群中的每个节点可以处理0-16384个槽。当数据库有16384个slot,所有节点都在处理时,集群处于online状态,否则只要有一个slot没有处理完,就会在offline状态处理。可以通过clusteraddslots命令将slot分配给对应的节点进行处理。slot是一个位数组,数组的长度为16384/8=2048,数组的每一位为1表示被节点处理,0表示没有处理。如图所示,表示节点A处理slot0-7。当客户端向节点发送命令时,如果恰好槽属于当前节点,则节点执行该命令,否则返回MOVED命令给客户端,引导客户端到正确的节点。(MOVED过程是自动的。)如果添加或删除节点,对于槽的重新分配也很方便。Redis提供了帮助实现槽迁移的工具。整个过程完全在线,无需停止服务。Failover如果节点A向节点B发送ping消息,节点B在指定时间内没有响应pong,则节点A会将节点B标记为pfail疑似离线状态,并将B的状态以如下形式发送给其他节点一条消息,如果超过半数的节点将B标记为pfail状态,则B将标记为failoffline状态,此时会发生failover,选择其中一个复制数据较多的slave节点首先作为主节点,接管下线节点的slot整个过程和sentinel很相似,选举也是基于Raft协议。你了解Redis的事务机制吗?Redis通过MULTI、EXEC、WATCH等命令实现事务机制。事务执行过程一次按顺序执行一系列多条命令,在执行过程中,事务不会被打断,也不会执行客户端的其他请求。直到执行完所有命令。事务的执行过程如下:服务端收到客户端请求,事务以MULTI开头。如果客户端处于事务状态,则将事务放入队列并返回给客户端QUEUED。否则,当客户端收到EXEC命令时,会直接执行这条命令,WATCH命令监听整个事务中的key是否被修改。如果是,它将返回空并回复客户端以指示失败。否则,redis会遍历整个事务队列,执行队列中保存的所有命令,最后将结果返回给客户端。WATCH机制本身就是一个CAS机制。被监控的key会保存在一个链表中。如果一个键被修改,REDIS_DIRTY_CAS标志将被打开,服务器将拒绝执行事务。
