Redis随笔记录

Redis随笔记录

  1. 选用redis原因?

    传统关系型数据类已经不能适应于所有场景了,比如秒杀扣减库存,App首页的访问流量高峰等等,都容易让数据库崩溃,所以需要引入缓存中间件。目前比较常用的缓存中间件由redis和memcached,结合其优缺点,最终选择redis。

  2. redis的数据结构?(了解其使用场景)

    redis自身是一个map类型的存储方式,其中所有的数据都是采用key:value的形式存储

    我们讨论的数据类型只的是存储的数据类型,也就是value部分的类型,key部分永远都是字符串

字符串String,字典hash,列表List,集合set,有序集合SortedSet.

(3条消息) 【Redis】五种数据类型及其使用场景_愿万事胜意-CSDN博客_redis五种类型使用场景

  1. 如果大量的key需要设置同一时间过期,需要注意什么?

    如果大量的key过期时间设置的过于集中,到过期的那个时间点,redis可能会出现短暂的卡顿现象。严重的话会出现缓存雪崩。因此我们一般需要在过期时间上加一个随机值,使得过期时间分散一些。

  2. 使用redis实现分布式锁?

​ 先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间,防止忘记释放锁。

​ 若在setnx之后,expire之前,进程意外crash或者要重新维护了会怎样,怎么办?

​ 锁没法释放了,不过可以同时把setnx和expire合成一条指令来使用,避免这个问题。

  1. 假如redis里面有10亿个key,其中10w个key是以某个已知的前缀开头的,如何将他们找出来----使用KEYS指令可以扫出指定模式的KEY列表。

    如果这个redis正在给线上的业务提供服务,那使用keys指令会造成什么影响?

    redis是单线程的。Keys指令会导致线程阻塞一段时间,线上服务会停止,直到指令执行完毕,服务才会恢复。这个时候可以使用SCAN指令(增量式迭代命令—缺点:在对键进行增量式迭代过程中,键可能会被修改,所以增量式迭代命令只能对被返回的元素提供有限的保证),该指令可以无阻塞的提取出指定模式的Key列表,但是有一定的重复概率,不过没关系,我们可以去客户端做一次去重。不过整体花费的时间比keys指令长。

  2. 如何使用redis做异步队列?

    一般使用list结构作为队列,rpush生产消息,lpop消费消息,当lpop没有消息的时候,要适当sleep一会在重试。

    若不使用sleep那?—使用list的bloop,在没有消息的时候,它会阻塞直到消息的到来。

    如何生产一次消费多次----使用Pub/sub主题订阅者模式,可以实现1:N的消息队列

    那Pub/sub主题订阅模式有什么缺点?—在消费者下线的时候,生产的消息会丢失,需要使用专业的消息队列如rocketMQ等。

  3. 如何用redis实现延时队列

    使用sortedset,将时间戳作为score,消息内容作为key,调用zadd来生产消息,消费者用zrangebyscore指令获取n秒之前的数据,轮询进行处理。

  4. redis是如何持久化的,服务主从数据是怎么交互的?

    rdb做镜像全量持久化,aof做增量持久化。因为rdb会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据,所以需要aof配合使用。在redis实例重启时,会使用rdb持久化文件重新构建内存,再使用aof重放最近的操作指令来实现完整恢复重启之前的状态。(理解:将rdb理解成一整个表全量的数据,AOF理解为每次操作的日志,服务器重启时先把表的数据全部导进去,但是可能不完整,因此还需要回放一下日志,是数据完整)redis本身机制:AOF持久化开启且存在AOF文件时,优先加载AOF文件,AOF关闭或者AOF文件不存在时,加载RDB文件,加载AOF/RDB文件后,redis启动成功,当AOF/RDB文件存在错误,redis启动失败并打印错误信息。

    机器断电会怎样?—取决于AOF日志sync属性的配置,如果不要求性能,在写每条指令时都sync一下磁盘,就不会丢失数据,但是在高性能的要求下,不现实,一般使用定时sync,比如一秒一次,这时候最多丢失1s的数据。

    RDB原理—fork and cow。fork是指redis通过创建子进程来进行RDB操作,cow时copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。(AOF/RDB原理和优缺点下来去掌握)

  5. redis的同步机制?

    redis可以使用主从同步,从从同步。第一次同步时,主节点做一次bgsave,并同时将后续的修改操作记录到内存buffer,待完成后将RDB文件全量同步到复制节点,复制节点接受完成后将RDB镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放,就完成了同步过程。后续的增量数据通过AOF日志同步即可,有点类似数据库的binlog.

  6. 是否使用过redis集群,集群高可用怎么保证,集群原理是什么?

​ redis sentinal着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。

​ redis cluster着眼于扩展性,在单机redis内存不足时,使用cluster进行分布式存储。

  1. 缓存雪崩

​ 目前电商首页以及热点数据都会去做缓存,一般缓存都是定时任务去刷新,或者是查不到之后去更新的,这里定时任务刷新就有一个问题。举个简单的例子。如果首页的key失效时间都是12小时,中午12点刷新的。现在0点有个秒杀活动大量用户涌入,假设当时每秒6000个请求,本来缓存存在可以扛住每秒5000个请求,但此时缓存中所有的key都失效了。此时1秒6000个请求全部落在了数据库,数据库会扛不住,重启,但重启的数据库立马又会被新的流量打死。这就是我理解的缓存雪崩。

如何解决?

①在批量往redis存数据的时候,把每个key的失效时间都加个随机值,这样就保证数据不会在同一时间大面积失效。

setRedis(key,value,time + Math.random()*10000);

②如果redis是集群部署,将热点数据均匀分布在不同的redis库中也能避免全部失效的问题(不过一般在项目中做集群的时候,单个服务都是对应单个redis分片,是为了方便数据管理,但也同样有了雪崩的弊端,失效时间随机是个很好的策略)

③设置热点数据永远不过期,当有更新时就更新缓存就好了。(电商首页数据就可以用这个操作)

  1. 缓存穿透

​ 缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求。比如我们数据库的id都是1开始自增上去的,如发起id值为-1的数据或者id特别大不存在的数据。造成数据库压力过大,严重会击垮数据库。

解决----在接口层增加校验,比如用户鉴权校验,参数校验,不合法的参数直接return,比如:id做基础校验,id<=0的直接拦截等。

从缓存中取不到的数据,在数据库中也取不到,这是可以将对应的Key的value写为null,位置错误,稍后重试这样的值。并设置一个缓存失效时间,缓存有效时间可以设置短点,这样就不会多次请求数据库了,第二次就会直接返回null.

采用布隆过滤器,使用一个足够大的bitmap,用于存储可能访问的key,不存在的key直接被过滤。

  1. 缓存击穿

    缓存击穿是指一个KEY非常热点,在不停的扛着大并发,大并发集中对这个KEY进行访问,当这个KEY在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库。

    解决:

    ①设置热点数据永远不过期

    ②在访问key之前,采用setnx来设置另一个短期key来锁住当前key的访问,访问结束后再删除该短期key.

    一般避免上述4,5,6情况发生我们可以从三个时间段去分析

    事前:redis高可用,主从+哨兵,redis cluster,避免全盘崩溃

    事中:本地ehcache缓存 + Hystrix 限流+降级,避免mysql被打死

    事后:redis持久化 RDB + AOF,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。

  2. redis快的原因

    ①完全基于内存,它的数据在内存中

    ②数据结构简单,对数据操作也简单,redis中的数据结构是专门进行设计的

    ③采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗CPU,不用去考虑各种锁问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致性能消耗;

    ④NIO,使用多路I/O复用模型,非阻塞IO;(再去加深印象)

    —单线程,我们服务器现在都是多核的,不会很浪费?

    我们可通过在单机开多个redis实例

    —如何解决单机瓶颈

    集群部署(redis cluster),并且是主从同步,读写分离。redis cluster 支持N个redis master node,每个master node都可以挂载多个slave node.这样redis就可以横向扩容。如果你要支持更大数量的缓存,那就横向扩容更多的master节点,每个master节点就能存放更多的数据了。

    ----master,slave如何交互?redis如何持久化,redis数据在内存中,一断电或者重启不就没有了?

    持久化是redis高可用比较重要的一个环节,因为redis的数据在内存中,所以必须要持久化。我了解到的持久化有两种方式:

    AOF:对每条写入命令作为日志,以append-only的模式写入一个日志文件中,因为这个模式是只追加的方式,所以没有任何磁盘寻址开销,所以很快,像MySQL中的binlog。

    缺点:一样的数据,AOF文件比RDB还要大。

    RDB:对redis中的数据做周期性的持久化。

    优点:rdb对redis的性能影响非常小,因为在同步数据的时候它只是fork了一个子进程去做持久化的,而且他在数据恢复的时候速度比aof快。

    缺点:RDB都是快照文件,都是默认5分钟甚至更久的时间才会生成一次,这就意味着这次同步到下次同步这5分钟的数据都很有可能全部丢失。AOF则最多丢失1秒的数据。还有就是RDB在生成数据快照的时候,如果文件很大,客户端可能会暂停几毫秒甚至几秒。

    ----redis保证集群高可用方式?

    哨兵集群sentinel

    哨兵必须使用三个实例去保证自己的健壮性,哨兵+主从不能保证数据不丢失,但是可以保证集群高可用。(M1所在的机器挂了,哨兵还有两个,两个人一看他不是挂了嘛,那我们就选举一个出来执行故障转移不就好啦)

    哨兵组件功能:

    ①集群监控:负责监控master and slave进程是否正常工作

    ②消息通知:如果某个redis实列有故障,那么哨兵负责发送消息作为报警通知给管理员

    ③故障转移:如果 master node挂掉了,会自动转移到slave node上

    ④配置中心:如果故障发生转移了,通知client客户端新的master地址

    ----redis主从之间是如何同步的?

    这个我先说一下为什么会用到主从同步这样的模式,前面提到单机的QPS是有上限的,而且redis的特性就是支撑必须读高并发的,如果一台机器又读又写肯定是不行的。因此这个时候我们可以让这个master去写,数据同步给slave,他们都拿去读,master就分发掉了大量请求,而且扩容的时候还可以轻松实现水平扩容。那么回到正题,怎么扩容呐?

    当启动一台slave的时候,它会发送一个psync命令给master,如果这个slave是第一次连接到master,它会触发一个全量复制。master就会启动一个线程,生成RDB快照,还会把新的写请求都缓存在内存中,RDB文件生成后,master会将这个RDB发送给slave的,slave拿到之后做的第一件 事情就是写进本地磁盘,然后加载进内存。然后master会把内存里面的那些缓存的那些重新命名都发给slave。

    ----那数据传输的时候断网或者服务器挂了?

    传输过程中有什么网络问题的,会自动重连的,并且连接之后会把缺少的数据补上的。

  3. 内存淘汰机制

    redis的过期策略:定期删除+惰性删除

    定期删除:比如默认100s就随机抽一些设置了过期时间的key,去检查是否过期,过期就删了。

    惰性删除:等来查询我就看你过期没,过期就删了并且不返回,没过期就该怎样就怎样。

    -----内存淘汰机制

    LRU(最近最少使用)

class LRUCache<K,V> extends LinkedHashMap<K,V>{
    private final int CACHE_SIZE;
    public LRUCache(int cacheSize){
        //true 表示让LinkedHashMap 按照访问顺序来进行排序,最近访问的放在头部,最老访问的放在尾部
        super((int)Math.ceil(cacheSize / 0.75) + 1,0.75f,true);
        CACHE_SIZE = cacheSize;
    }
    @Override
    protected boolean removeEldestEntry(Map.Entry<K,V> eldest){
        //当map中的数据量大于指定的缓存个数的时候,就自动删除最老的数据
        return size() > CACHE_SIZE;
    }
}
  1. 多个操作系统同时操作(并发)redis带来的数据问题?

    某个时刻,多个系统都去更新某个key。可以基于Zookeeper实现分布式锁。每个系统通过zookeeper获取分布式锁,确保同一时间,只能有一个系统实列在操作某个key,别人都不允许读和写。

    我要写入缓存的数据都是从数据库里查出来的,然后都得写入数据库中,写入数据库的时候必须保存一个时间戳,从数据库查出来的时候,时间戳也查出来。每次要写之前,先判断一下当前这个value的时间戳是否比缓存里的value的时间戳要新。如果是的话,那么可以写,否则,就不能用旧的数据覆盖新的数据。

  2. redis的线程模型?

    redis内部使用文件事件处理器 file event handler,这个文件事件处理器是单线程的,所以redis才叫做单线程模型。它采用IO多路复用机制同时监听多个socket,根据socket上的事件来选择对应事件处理器进行处理。多个socket可能会并发产生不同的操作,每个操作对应不同的文件事件,但是IO多路复用程序会监听多个socket,会将socket产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。

    文件事件处理器结构:

    ①多个socket

    ②IO多路复用程序

    ③文件事件分派器

    ④事件处理器

  3. 双写数据不一致问题?

    缓存+数据库读写模式

    读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据放入缓存,同时返回相应。

    更新的时候,先更新数据库,然后再删除缓存

    —为什么是删除缓存,而不是更新缓存?

    因为很多时候,在复杂点的缓存场景中,缓存不单单是从数据库中直接取出来的值。比如更新了某个表的一个字段,然后其对应的缓存,是需要查询另外两个表的数据并进行运算,才能计算出缓存最新的值的。

    更新缓存开销大,一般我们是用到缓存才去算缓存。

上一篇:测开实战 / 接口自动化测试框架开发(pytest+allure+aiohttp+用例自动生成)


下一篇:第26节课:pytest结合Allure操作