redis

为什么要使用redis?

高性能:假如用户第一次访问数据库中的某些数据,会比较慢,将用户访问的数据存在缓存中,下一次访问就可以直接从缓存中获取,操作缓存就是直接操作内存,所以速度快

高并发:直接缓存操作能够承受的请求远远大于直接访问数据库,所以我们可以考虑把数据库中的部分数据转移到缓存中,这样用户的一部分请求会直接到缓存而不用去数据库

 

redis的好处

redis被称为分布式缓存,在多实例的情况下,各实例共用一份缓存数据,缓存具有一致性,缺点是需要保持redis的高可用,架构上比较复杂

 

跳表?

  跳表是有序集合的底层实现之一,基于多个有序链表实现,每个节点都包括向右和向下两个节点,它能够提高搜索性能和插入删除操作的效率。与红黑数相比的优点:

                                                                            1.插入速度非常快,因为不需要进行旋转来维护平衡

                                                                             2.在进行范围查找时,跳表只需要要最底层做做一次遍历就行了,而红黑树就算找到了一个值,你要找到范围内的其他值也需要进行中序遍历

                                                                           3.支持无锁操作

 

跳表的查询操作?

   从最上层开始,如果key小于等于当前层的后一个节点的key,那么向右移动,如果大于后一个节点的key,则向下移动,然后继续刚才的操作,最后就能到达第一层判断是否存在目标值

 

跳表的插入操作?

   先随机选取一层作为开始的层数K,然后在1——K层各个层的链表都插入该元素,用update数组记录插入位置,从顶层开始,逐层找到需要插入的位置进行插入

 

 

跳表的删除操作?

     删除指定的元素,如果删除后某一层只剩下头尾两个节点,那么删除这一层

 

跳表的查找,插入,删除的时间复杂度都为logn。

 

redis为什么采用跳表而不是红黑树?

   因为在做范围查找时,红黑树比跳表操作要复杂,在红黑树上,找到指定范围的小值之后,还需要通过中序遍历继续寻找不找过大值的节点,而跳表进行范围查找就比较简单,它只需要在找到小值后,进行遍历就可以实现了。而且红黑树的插入删除操作可能会导致树结构的调整,而跳表只需要删除或者添加节点就行了。
 

                                                            

 

redis的常见数据结构及使用场景

1.string

String数据结构是简单的key-value类型   常规的key-value缓存应用

2.hash   hash是一个string类型的field和value的映射表,hash特别适合用于存储对象,后续操作的时候,可以直接仅仅修改这个对象中的某个字段的值

3.List    list是redis最重要的数据结构之一,这里是一个双向链表,可以基于list实现分页查询,性能高

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

 

redis设置过期时间

redis可以对存储在数据库中的值设置一个过期时间,应用场景比如token,登录信息,短信验证码。我们set key的时候,都可以给一个expire time

过期后是通过定期删除和惰性删除的

仅仅通过设置过期时间还是有问题,因为定期删除会漏掉过期key,如果不及时去查也没有惰性删除,就会堆积在内存里,导致内存耗尽,解决这个问题就是靠redis的内存淘汰机制

 

redis的淘汰策略

1)volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰

2)volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰

3)volatile-random:从已设置过期时间的数据集中随机淘汰

4)allkeys-lru:当内存不足够时,移除最少使用的key(最常用)

5)allkeys-random:从数据集中随机淘汰

6)no-eviction: 禁止淘汰数据,内存不够时又有新数据写入时直接拒绝写入

 

 

2)AOF持久化

与快照持久化相比,AOF持久化的实时性更好,因此成为主流的持久化方案,可以通过 appendonly yes来开启

开启AOF持久化后每执行一条会更改redis中的数据的命令,redis就会将该命令写入硬盘中的AOF文件

 

redis持久化?

   redis的持久化主要分为快照持久化(RDB)和AOF持久化

   快照持久化会进行定期存储,保存的是数据本身,aof持久化是每次修改数据时同步到硬盘,保存的是数据的变更记录

   rdb优点:存储文件紧凑,适用于备份和容灾恢复,并且最大化了redis的性能

         缺点:如果redis没有正常关闭,那么此时到上个快照点之间的所有数据都会丢失

   AOF优点:由于是日志追加文件,所以不需要定位,就算redis异常关闭也可以通过redus-check-aof工具修复,当aof文件很大时redis会在后台自动重写,启AOF持久化后每执行一条会更改redis中的数据的命令,redis就会将该命令写入硬盘中的AOF文件

          缺点:对于同样的数据,aof文件通常更大,并且速度可能会比rdb慢,aof通过递增方式更新数据,而rdb从头开始创建,所以rdb更加健壮和稳定(更适合备份)

 

集群脑裂?

   由于网络问题导致master节点和slave节点无法互相感知到,这个时候slave节点会升级成为master节点,就会出现两个master节点,这个时候如果客户端还基于原来的master节点继续写入数据,那么新的master节点是无法同步这些数据的,当网络问题解决后,如果要从新的master中同步数据就会造成大量数据的丢失

 redis的解决方案:设置连接到master的最小slave数量和最大延迟时间。如果slave数量小于设置的值并且延迟时间小于最大延迟时间那么master会拒绝掉写请求。这样的话就算出现脑裂,也不会写入新的数据,就能够减少数据同步之后的数据丢失了
 

 

 

redis事务

redis通过multi,exec,watch等命令来实现事务功能,不过redis的事务中如果有一条命令执行失败,后面的命令仍然会被执行,不会回滚,redis的事务主要是为了保证redis进行这一系列操作时没有其他干扰

 

缓存雪崩和缓存穿透问题?

缓存雪崩:缓存同一时间内大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉

解决办法:1.加锁或者用队列的方式保证缓存的单线程写,从而避免失效时大量请求直接到达mysql数据库

                  2.随机设置缓存过期时间

 

缓存穿透:一般是黑客故意去请求缓存中不存在的数据,导致所有的请求都到数据库,造成数据库短时间承受大量请求而崩掉

解决办法:采用布隆过滤器,将所有可能存在的数据哈希到一个足够大bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。另一个方法也更推荐的是:如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但是它的过期时间很短

 

redis的并发竞争key的问题?

并发竞争key也就是多个系统同时对一个key进行操作,但是最后执行的顺序和我们期望的顺序不同,也就导致了结果的不同

推荐解决方案:分布式锁(ZooKeeper和redis都可实现分布式锁),但是如果不存在redis的并发竞争key问题就不要使用,因为只要是锁,都会影响性能

基于ZooKeeper实现分布式锁:每个客户端对某个方法加锁时,在ZooKeeper上与该目录对应的指定节点的目录下,生成一个唯一的瞬时有序节点。判断是否获取锁的方式就是判断有序节点号最小的一个,当释放锁的时候,只需要将这个瞬时节点删除即可。并且分布式锁可以避免死锁问题。完成业务流程后,删除对应子节点释放锁,推荐ZooKeeper

 

如何保证缓存与数据库的一致性?

  先更新数据库,再删除缓存

  依然会存在并发问题:假如两个请求A执行查询操作,B执行更新操作

           1.缓存刚好失效

           2.请求A查询数据库,得到一个旧值

           3.请求B将新值写入数据库

            4.请求B删除缓存

            5.请求A将旧值写入缓存

如何解决:1.给缓存设置有效时间      2:采用异步延时删除策略,保证读请求完成后,再进行删除操作(也就是开启一个异步线程,等待一会儿后再去删除缓存)

         不过此时依然有一致性问题,就是在B更新数据但是还没有删除缓存时假如A来查询,那么得到的就是一个旧值:解决方案就是用分布式锁,将写操作和缓存失效的操作变为原子操作。写数据时尝试加锁,锁定指定的时间,如果加锁成功,那么开始更新数据库库,无论更新成功或者失败都就锁,如果是成功的话就时缓存过期

 

为什么redis是单线程的?

因为redis是基于内存的操作,cpu不是redis的瓶颈,redis的瓶颈可能是内存的大小,并且redis使用队列将并发访问变为串行访问

1)绝大部分请求是纯粹的内存操作

2)采用单线程,避免了不必要的上下文切换和竞争条件

3)非阻塞IO

 

redis实现分布式锁?

redis是单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端的对redis的连接并不存在竞争条件,redis中通过使用setnx命令实现分布式锁:将key的值设置为value,当且仅当key不存在。若给定的key已经存在,则setnx不做任何操作

解锁:使用del key 命令就能释放锁

怎么解决死锁?

1)通过redis中expire()给锁设定最大持有时间

2)使用setnx key "当前系统时间+锁持有的时间”和 getset key "当前系统时间+锁持有的时间"组合的命令就可以实现

 

redis的主从同步?

     redis主从同步可以根据是否为全量分为全量同步和增量同步

     全量同步:一般发生在slave初始阶段,这时候slave需要将master上的所有数据都复制一份。具体步骤如下:

                  1.从服务器连接主服务器,发送sync命令

                  2.主服务器收到sync命令后,开始执行bgsave命令生成rdb文件并使用缓冲区记录伺候执行的所有写命令

                 3.主服务器bgsave执行完成后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令

                 4.从服务器收到快照文件后丢弃所有旧数据,载入收到的快照

                 5.主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令

                 6.从服务器完成对快照的载入,开始接受命令请求,并执行来自主服务器缓冲区的写命令

 

 

     增量同步: 是指slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。它的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令

   redis的主从同步策略:主从服务器刚刚连接的时候,会进行全量同步,全量同步结束后会进行增量同步。但是如果有需要,slave可以在任何时候进行全量同步。redis默认情况下会首先尝试增量同步,不成功再尝试全量同步

 

redis的一致性哈希算法?

   有一个大小为2^32次方的环形空间被称为哈希环,然后对各个服务器进行一次哈希(具体就是服务器的ip或者主机名作为关键字进行哈希)。然后对数据的key使用相同的哈希函数计算出哈希值,确定它在哈希环上的位置,从此位置出发顺时针直到遇到第一台服务器,该服务器就是定位到的服务器

   一致性哈希具有很好的容错性,假如某个节点宕机了,那么定位到其他节点的对象不受影响,只是定位到该宕机节点的数据会重新定位新的节点。也具有很好的扩展性,当新增了一个节点,那么离这个新节点最近的对象会重新定位到新节点,而其他对象不受影响

  缺点:服务器节点太少时,容易因为节点分布不均匀导致数据倾斜(也就是大多数对象都集中缓存在某一台服务器上)

 

 

 

 

4.假如redis里面有1亿个key,其中10w个以固定的已知前缀开头,怎么全部找出来?

使用keys指令可以扫描出指定模式的key列表,并且由于redis是单线程的,假如这个redis正在提供服务,我们可以用scan指令,这样可以无阻塞的取出指定模式的key列表,但是会有一定的重复率,再做一次去重就行,不过整体花费时间更长

 

 

redisson是可重入的吗?可重入怎么实现?

   redisson是可重入的。

   通过锁标识来实现可重入,可以用lock name作为key,然后当前线程会生成一个uuid,这个uuid作为value,当想要重入的获得锁时只需要判断uuid是否是当前线程的uuid就行了。具体实现可以利用ThreadLocal来实现,将lock name和线程uuid都保存在ThreadLocal里面,同一个线程再次尝试获取锁时去比较ThreadLocal里面的value和当前线程的uuid是否相同,相同就获得锁

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值