大数据-redis进阶

  1. redis持久化
    redis主要工作在内存中,断电后数据会清空,redis提供了两种不同级别的持久化机制.
    ① RDB 能够在指定的时间间隔内对数据进行快照存储
    ② AOF 记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始数据,AOF命令以redis协议追加保存每次写的操作到文件末尾,redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大.同时开启两种持久化机制的时候,当redis重启的时候会优先导入AOF文件来恢复原始的数据,在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集完整
    (1) RDB
    在指定时间间隔内将内存中的数据集快照写入磁盘,也就是snapshot快照,恢复时是将快照文件直接读到内存中. 每隔一段时间,就把内存中的数据保存到硬盘上指定的文件中.RDB默认是开启的.
    Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,xxx.rdb,待持久化过程结束了,再用这个临时文件替换上次持久化好的文件,整个过程中,主进程不进行任何io操作,确保了极高的性能,如果需要进行大规模数的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式比AOF方式更加的高效.
    RDB的缺点是最后一次持久化后的数据可能消失.
    保存策略: save 900 1 900秒内如果至少有1个key的值变化,则保存
    save 300 10 300秒内如果至少有10个key的值变化,则保存
    save 60 1000 60秒内如果至少有1000个key的值变化,则保存
    save “” 禁用RDB模式
    优点: RDB是个非常紧凑的文件,保存了某个时间点的数据集,非常适合于数据集的备份,比如你在每个小时保存一下过去24小时的数据,同时每天保存过去30天的数据,这样出问题了也可以根据需求恢复到不同版本的数据集.RDB是一个紧凑的单一文件,很方便的传送到另一个远端数据中心,适用于灾难恢复.RDB在保存文件时父进程唯一需要做的就是fork一个子进程,接下来的工作都由子进程来完成,父进程不需要再做其他io操作,所以RDB持久化的操作可以最大化redis的性能,与AOF相比,在恢复大的数据集的时候,RDB方式会更方便一点.
    缺点: 如果希望丢数据最少,RDB不适合使用,虽然可以配置不同的save时间点,但是redis意外宕机的话会丢失最后一次备份之后的数据.RDB经常需要fork子进程来保存数据集到硬盘上,当数据集大的时候,fork的过程是非常耗时的,可能导致redis在溢写毫秒级内不能响应客户端的请求,如果数据集巨大CPU性能不是很好的话,这种情况会持续1秒,AOF虽然也需要fork,但是你可以调节重写日志文件的频率来提高数据集的耐久度.

    (2) AOF
    AOF是以日志的形式来记录每个写操作,将每次对数据进行修改的命令都保存在指定文件中,redis重新启动时读取这个文件,重新执行修改数据的命令恢复数据.
    默认不开启,需要手动开启,appendonly yes
    AOF文件的保存路径,和RDB路径一致.
    AOF在保存命令的时候,只会保存对数据有修改的命令,也就是写操作.
    当RDB和AOF存的不一样的时候,按照AOF来恢复,因为AOF是对RDB的补充,备份周期短,更可靠.
    保存策略: appendfsync always:每次产生一条新的修改数据的命令都执行保存操作,效率低,但是安全
    appendfsync everysec: 每秒执行一次保存操作,如果未保存当前秒操作的时候发生了断电,仍然会导致一部分数据丢失(丢1秒的数据)
    appendfsync no: 从不保存,将数据交给操作系统来处理,更快,更不安全
    默认的是每秒fsync一次,可以兼顾速度和安全性.
    优点: 使用AOF会让你的Redis更加耐久,可以使用不同的fsync策略,fsync是由后台线程进行处理,主线程会尽力处理客户端请求,一旦出现故障,最多丢失1秒数据.AOF文件是一个只进行追加的日志文件,所以不需要写入seek,即使某些原因(磁盘空间满了,写的过程宕机了)未执行完整的写入命令,也可以使用redis-check-aof工具修复这些问题.redis可以在AOF文件体积变的过大时,自动的在后台对AOF进行重写,重写后的新文件包含了恢复当前数据集所需的最小命令集合,整个重写操作是绝对安全的,因为redis在创建新的AOF文件的过程中,会继续将命令追加到现有的AOF文件中,即使重写过程发生了停机,现有的AOF文件也不会丢失,而一旦新AOF文件创建完毕,redis就会从旧AOF文件切换到新AOF文件,并开始对新AOF文件进行追加操作.AOF文件有序的保证了对数据库执行的所有写操作,这些写操作以redis协议的格式保存,因此AOF文件的内容容易被人读懂,对文件进行分析也很轻松,导出AOF文件也很简单.
    缺点: 对于相同的数据集来说,AOF文件的体积通常要大于RDB文件的体积.根据所使用的fsync策略,AOF的速度可能会慢于RDB,在一般情况下,每秒fsync的性能依然非常高,而关闭fsync可以让AOF的速度和RDB一样快,即使在高负荷之下也是如此,在处理巨大的写入载入时,RDB可以提供更有保证的最大延迟时间.

  2. redis主从复制
    配置多台redis服务器,以主机和备机的身份分开,主机数据更新后,根据配置和策略,自动同步到备机的master/salver机制,master以写为主,salver以读为主,二者之间自动同步数据.
    目的: 读写分离提到redis性能,避免单点故障,容灾快速恢复
    原理: 每次从机联通后,都会给主机发送sync指令,主机立即进行存盘操作,发送RDB文件给从机,从机接收到RDB文件后,进行全盘加载,之后每次主机的写操作,都会立刻发送给从机,从机执行相同的命令.

    ① 从机是从头开始复制主机的信息,实际上是读取主机的rdb
    ② 从机不可以写,当主机shutdown后,从机原地待命,当主机重新连接之后,新增记录,从机继续复制主机信息,宕机期间主机新增的记录也会顺利复制
    ③ 如果从机都从主机同步数据,此时主机io压力增大,可以按照主机–>从机(主机)–>从机的方式配置

  3. 哨兵模式
    哨兵的作用: 后台监控主机是否故障,如果Master状态异常,则会进行Master-Slave切换,将其中一个Slave作为Master,将之前的Master作为Slave
    ① 主机宕机后,从下线的主服务的所有从服务里面选一个,将其转换成主服务,选择条件: 选择优先级靠前的,选择偏移量最大的,选择runid最小的从服务
    ② 挑选出新的主服务之后,sentinel向原主服务的从服务发送slaveof新主服务 的命令,复制新master
    ③ 当已经下线的服务重新上线后,sentinel会向其发送slaveof 命令,让其成为新主的从服务

  4. redis集群发展(单机–>主从–>哨兵–>cluster)
    此问题转载自: https://blog.csdn.net/java_zyq/article/details/83818341
    https://blog.csdn.net/angjunqiang/article/details/81190562
    (1) 单机: 启动单节点实例
    (2) 主从模式: 一主多从,master节点可以进行读写,slave节点只能读;master挂掉后不影响其他slave节点的读,redis不再提供写服务,master节点启动后redis重新对外提供写服务.
    主从模式一个作用是备份数据,一个作用是负载均衡,读写分离
    (3) 哨兵: 主从模式的缺点就是master节点挂掉了之后不能提供写服务,因为剩下的slave不能成为master,对于生产环境来说是不行的,所以有了哨兵模式.
    sentinel模式建立在主从模式上,sentinel因为也是一个进程有挂掉的可能,所以sentinel也会启动多个形成一个sentinel集群,sentinel集群可以管理多个主从redis,监控的redis集群都会定义一个master名字,代表redis集群的master redis,一般来说,sentinel最好不要和redis部署在同一机器.
    当使用sentinel模式时,客户端就可以直接连接sentinel的ip和port,有sentinel提供可用的redis实现,sentinel模式基本可以满足一般生产的需求,具备高可用性,但是当数据量过大到一台服务器存放不下的时候,这就需要对存储的数据进行分片,将数据存储到多个redis实例中.
    (4) cluster模式
    Redis Cluster是redis的分布式解决方案,在Redis3.0版本正式推出,有效解决了Redis分布式方面的需求,当遇到单机内存,并发,流量等瓶颈时,可以采用Cluster架构达到负载均衡的目的.Redis Cluster采用哈希分区规则中的虚拟槽分区.虚拟槽分区巧妙地使用了哈希空间,使用分散良好的哈希函数把所有的数据映射到一个固定范围内的整数集合,整数定义为槽(slot),Redis Cluster槽的范围是0~16383.槽是集群内数据管理和迁移的基本单位,采用大范围的槽的目的是为了方便数据的拆分和集群的扩展,每个节点负责一定数量的槽.slot=CRC16(key)&16383.每个实节点负责维护一部分槽以及槽所映射的键值数据.
    cluster可以说是sentinel和主从模式的结合,通过cluster可以实现主从和master重选功能,所以如果配置两个副本三个分片的话,就需要6个redis实例.

  5. 面试常见问题
    (1) 为什么用redis?
    因为传统的mysql已经不能适用所有场景,比如秒杀的库存扣减,APP首页的访问流量高峰等,很容易把数据库打崩,所以引入缓存中间件,常用的缓存中间件有Redis和Memcached,综合考虑了他们优缺点,选择Redis.

    (2) Redis和Memcached的区别
    ① redis和memcached都是将数据存放在内存中,都是内存数据库,不过memcached还可以用于缓存其他东西,例如图片,视频等
    ② redis不仅仅支持简单的kv类型数据,同时还提供list,set,hash等数据结构的存储
    ③ redis当物理内存用完之后,可以将一些很久没用到的value交换到磁盘
    ④ 过期策略: memcache在set时就指定,redis可以通过expire设定
    ⑤ 分布式: 设定memcache集群,利用magenta做一主多从,redis也可以做一主多从
    ⑥ 存储数据安全:redis可以定期保存到磁盘,持久化机制,memcache挂掉后,数据没了
    ⑦ 灾难恢复: memcache挂掉后,数据不可恢复,redis数据丢失后可以通过aof恢复
    ⑧ redis支持数据的备份,即master-slave模式的数据备份
    ⑨ 应用场景: Redis作为NoSQL数据库使用外,还能做消息队列,数据堆栈,数据缓存等;Memcached适合用于缓存SQL语句,数据集,用户临时性文件,延迟查询数据和session等
    如果在数据持久化方面或者对数据类型和处理有要求应该使用redis,如果简单的key/value存储选择memcached

    (3) Redis 的数据结构
    常见的数据结构如字符串String,字典Hash,列表list,集合set,有序集合Zset,除此之外还有用来基数统计的HyperLogLog,存储地理位置Geo,订阅发布Pub/Sub
    对此问题Redis基础有相关介绍

    (4) 如果大量的key需要设置同一时间过期,一般需要注意什么
    如果大量的key过期时间设置的过于集中,到过期的那个时间点,redis可能会出现短暂的卡顿,严重的话会出现雪崩,一般需要在时间上加一个随机值,使得过期时间分散一些.

    (5) redis分布式锁是怎么回事
    先拿setnx来争抢锁,抢到之后,再用expire给锁一个过期时间防止锁忘记释放

    (6) 如果在setnx之后执行expire之前进程意外的crash或者要重启维护,会怎样
    这种情况,锁得不到释放,我们可以使用set命令完成setnx和expire的操作,并且这种操作时原子性的. set key value [EX seconds] [PX milliseconds] [NX|XX]
    EX: 设置失效时长秒
    PX: 设置失效时长毫秒
    NX: key不存在时设置value,成功返回OK,失败返回nil
    XX: key存在时设置value,成功返回OK,失败返回nil

    (7) 假如redis里面有1亿个key,其中10W个key是以某个固定的已知的前缀开头的,如何将他们都找出来
    redis数据库keys可以模糊匹配,支持的通配符 * ? []
    * 匹配任意字符 keys *keyword* key中含有keyword字符的key
    ? 匹配一个字符 keys m?? m开头,长度3的key
    [] 匹配括号里的一个字符 keys m[a v c d] m开头,avcd其中一个字母结尾的key

    (8) 如果此时redis正在给线上的业务提供服务,那使用keys指令会有什么问题
    redis是单线程的,keys会导致线程阻塞一段时间,线上服务会停顿,知道指令执行完毕,服务才会恢复,这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体花费时间会比直接用keys指令长.
    对于增量式迭代命令scan来说,在对键进行增量迭代的过程中,键可能会被修改,所以增量迭代命令只能对被返回的元素提供有限的保证.
    SCAN 命令用于迭代当前数据库中的数据库键,SSCAN命令用于迭代集合键中的元素,HSCAN命令用于迭代hash键中的键值对,ZSCAN命令由于迭代有序集合中的元素(包括元素成员和元素分值), 以上命令每次执行只会返回少量元素,所以这些命令可以用于生产环境SCAN cursor [MATCH pattern] [COUNT count]
    SSCAN,HSCAN,ZSCAN的第一个参数总是一个数据库键,而SCAN命令则不需要在第一个参数提供任何数据库键,因为它迭代的是当前数据库中的所有数据库键.count选项指定每次迭代返回元素数的最大值,count参数的默认值是10
    scan命令是一个基于游标的迭代器,每次被调用后,都会向用户返回一个新的游标,用户在下次迭代时需要使用这个新游标作为scan命令的游标参数,以此延续之前的迭代,当scan命令的游标参数设置为0时,服务器将开始一次新的迭代,当服务器向用户返回值为0的游标时,表示迭代已经结束.

    (9) redis做异步队列,怎么用的
    一般使用list结构作为队列,rpush生产消息,lpop消费消息,当lpop没有消息的时候,可以适当sleep一会再重试,也可以使用blpop,在没有消息的时候,会阻塞住直到消息到来

    (10) 如果要做到生产一次消费多次?
    使用pub/sub主题订阅模式,实现1对N的消息队列,缺点是在消费者下线的情况下,生产的消息会丢失,要使用专业的消息队列

    (11) Redis实现延时队列
    使用Sortedset,用时间戳作为score,消息内容作为key调用zadd生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理

    (12) Redis持久化(开头有详细信息)
    RDB做镜像全量持久化,AOF做增量持久化,redis实例重启时,会使用RDB文件持久化文件重新构建内存,在使用AOF重放近期的操作指令来实现完整恢复重启之前的状态.redis本身机制是在AOF持久化机制开启时,优先加载AOF,AOF关闭或者AOF文件不存在时,加载RDB文件,加载AOF/RDB文件后,Redis启动成功,AOF/RDB文件存在错误时,Redis启动失败并打印错误信息
    如果机器突然断电,那么取决于AOF日志sync属性的配置,如果是每条写指令都sync一下磁盘的话,那么不会丢失数据,一般都是1s一次,这时最多丢失1s数据

    (13) RDB原理
    fork和cow. fork是指redis通过创建子进程来进行RDB操作,cow指的是copy on write,子进程只有在父进程发生写操作修改内存数据时,才会真正的分配内存空间,并复制内存数据,而且也只是复制被修改的内存页中的数据,并不是全部内存数据. 子进程和父进程共享内存空间,两者只是虚拟内存不同,但是对应的物理空间是同一个.

    (14) pipeline有什么好处
    可以将多次io往返的时间缩短为一次,前提是pipeline执行的指令之间没有因果相关性.
    原生批命令(mset,mget)与pipeline对比:① 原生批命令是原子性,pipeline是非原子性② 原生批命令是一命令多个key,pipeline是多个命令(存在事务)③ 原生批命令是服务端实现,而pipeline需要服务端和客户端共同完成
    pipeline组装的命令不适合太多,不然数据量过大,增加了客户端的等待时间,还可能造成网络阻塞,可以将大量命令拆分成多个小的pipeline命令完成.
    redis的简单事务: 一组需要一起执行的命令放到multi和exec两个命令之间,其中multi代表事务开始,exec表示事务结束.停止事务discard.
    使用watch后,multi失效,事务失效.watch机制: 在事务exec执行时,redis会检查被watch的key,只有被watch的key从watch起始时至今没有发生过变更,exec才会被执行,如果watch的key在watch命令到exec命令之间发生过变化,则exec命令会返回失败.

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

    (16) redis集群高可用的保证
    redis sentinel 着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务.
    redis cluster 着眼于扩展性,当单个redis内存不足时,使用cluster进行分片存储.
    详细信息参考上文 4.redis集群发展

    (17) Redis常见的三个问题: 缓存雪崩 击穿 穿透
    ① 雪崩
    例如: 如果首页所有的key失效时间都是12小时,中午12点刷新,在0点有个秒杀活动,大量的用户涌入,本来是先访问缓存,可是缓存当时所有的key失效了,此时所有的访问都请求数据库,数据库直接就挂掉了,重启又会被新的流量打的挂掉.
    应对: 在批量往Redis存数据的时候,把每个key的失效时间都加个随机值,这样可以保证数据不会在同一时间大面积失效.如果redis是集群部署,将热点数据均匀分布在不同的redis库中也能避免全部失效的问题.还可以设置热点数据永不过期,有更新操作就更新缓存.
    ② 穿透
    缓存穿透是指缓存和数据库中都没有的数据,而用户不断的发起请求.当大量用户的请求都没有缓存命中,都去了数据库,这会给数据库造成很大的压力,这时候就相当于出现了缓存穿透.
    应对: 在接口层做好数据校验,从缓存读不到的数据,在数据库中也没有,这时候可以将对应key的value写为null,缓存有效时间设置短点,不影响后续正常情况的使用.还可以使用布隆过滤器,原理就是利用高效的数据结构和算法快速判断出这个key是否在数据库中存在,不存在直接return,存在了就去查数据库,然后刷新缓存再返回.
    ③击穿
    key对应的数据存在,但是在redis中过期了,此时如果有大量的并发请求过来,发现缓存过期会直接访问数据库并回设到缓存中,此时大并发请求可能会瞬间把后端DB压垮.
    应对: 比较常用的就是互斥锁,使用mutex,在缓存失效的时候,不是立即去加载数据,而是先用缓存工具的某些带成功操作返回值的操作去set一个mutex key,当操作返回成功时,再进行加载数据的操作并回设缓存;否则就重试整个get缓存的方法,setnx 是只有在不存在的时候才设置,可以利用它实现锁的效果.

public String get(key) {
      String value = redis.get(key);
      if (value == null) { //代表缓存值过期
          //设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db
      if (redis.setnx(key_mutex, 1, 3 * 60) == 1) {  //代表设置成功
               value = db.get(key);
                      redis.set(key, value, expire_secs);
                      redis.del(key_mutex);
              } else {  //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可
                      sleep(50);
                      get(key);  //重试
              }
          } else {
              return value;      
          }
 }

.
还可以设置key永不过期,在redis中没有设置过期时间,但是将过期时间放在了value中,如果value中的时间过期则进行异步更新,此时过来的请求返回旧数据,当value重新更新后,再次来的请求就可以返回新值.
还可以做资源保护,用Hystrix限流加降级.

解决方案			优点					缺点
分布式锁		思路简单,保证一致性	  代码复杂性增大,存在死锁风险,存在线程池阻塞风险

不过期		异步构建缓存,不会阻塞	  不保证一致性,代码复杂度增大(每个value都要维护一个time)
			线程池				  占用一定的内存空间(每个value都要维护一个time)

资源隔离		hystrix技术成熟,有效    部分访问存在降级策略
			保证后端,监控强大

.
(18) Redis为什么这么快?
Redis采用的是基于内存的采用的是单进程单线程模型的kv数据库,由C语言编写,官方提供的数据是可以达到100000+的QPS(每秒内查询次数)
① 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速,数据存在内存中,类似于HashMap,优势就是查找和操作的时间复杂度都是O(1)
② 数据结构简单,对数据操作也简单.
③ 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗CPU,不用去考虑各种锁的问题,不存在加锁释放的问题,没有因为可能出现死锁而导致的性能消耗
④ 使用多路I/O复用机制,非阻塞io
⑤ 使用底层模型不同,它们之间底层实现方式以及与客户端之间的通信协议不一样,redis直接自己构建了VM机制,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求.
(19) Redis的内存淘汰策略
Redis的过期策略,是有定期删除和惰性删除两种.
定期默认100ms就随机抽一些设置了过期时间的key,去检查是否过期,过期了就删掉.
惰性删除,就是不主动删除,等待被查询的时候判断是否过期,过期就删除,而且不返回.
Redis的内存淘汰机制:
首先客户端发起需要更多内存的申请,其次,redis检查内存的使用情况,如果实际使用内存已经超出maxmemory,redis会根据用户配置的淘汰策略选出过期的key,最后确认选出的数据没有问题,成功执行淘汰任务.
早期版本提供了6种淘汰策略:
① volatile-lru: 从设置过期时间的数据集中挑选出最近最少使用的数据淘汰,没有设置过期时间的key不会被淘汰,这样就可以增加内存空间的同时保证需要持久化的数据不会丢失.
② volatile-ttl: 除了淘汰机制采用LRU,策略基本上与volatile-lru相似,从设置过期时间的数据集中挑选将要过期的数据淘汰,ttl值越大越优先被淘汰
③ volatile-random: 从已设置过期时间的数据集中任意选择数据淘汰,当内存达到限制无法写入非过期时间的数据集时,可以通过该淘汰策略在主键空间中随机移除某个key
④ allkeys-lru: 从数据集中挑选最近最少使用的数据淘汰,该策略要淘汰的key面向的是全体key集合,而非过期的key集合
⑤ allkeys-random : 从数据集中选择任意数据淘汰
⑥ no-envication: 禁止驱逐数据,也就是当内存不足以容纳新入数据时,新写入操作就会报错,请求可以继续执行,线上任务也不能持续进行,这种策略可以保证数据不丢失,也是系统默认的一种淘汰策略
Redis5.0新提供了两种
⑦ volatile-lfu: 从已设置过期时间的数据集中选出使用频率最低的数据淘汰
⑧ allkeys-lfu: 从全体数据集中选出使用频率最低的数据淘汰
LRU淘汰:
LRU(least recently used)算法根据数据的历史访问记录来进行淘汰数据,核心思想是"如果数据最近被访问过,那么将来被访问的几率也很高"

以下代码转载自: LRU算法和Redis的LRU实现

public class LRUCache<K, V> extends LinkedHashMap<K, V> {

    private final int CACHE_SIZE;

    // 这里就是传递进来最多能缓存多少数据
    public LRUCache(int cacheSize) {
        // 设置一个hashmap的初始大小,最后一个true指的是让linkedhashmap按照访问顺序来进行排序,最近访问的放在头,最老访问的就在尾
        super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
        CACHE_SIZE = cacheSize;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        // 当map中的数据量大于指定的缓存个数的时候,就自动删除最老的数据
        return size() > CACHE_SIZE;
    }
}

.
(20) 多个系统同时操作Redis带来的数据问题
某个时刻,多个系统实例都去更新某个key,可以基于zookeeper实现分布式锁,每个系统通过zookeeper获取分布式锁,确保同一时间,只能有一个系统实例在操作某个key,别人都不允许读写.
要写入缓存的数据,都是从数据库查出来的,也都要写入数据库,写入数据库中的时候必须保持保存一个时间戳,从数据库查出来的时候,时间戳也查出来,每次要写之前,判断一下这个value的时间戳是否比缓存里的value的时间戳要新,如果是的话,那么可以写,否则,就不能用旧的数据覆盖新的数据.
使用缓存,就会涉及缓存和数据库双存储双写,你只要双写,就一定会有数据一致性的问题,俺么如何解决一致性的问题?
如果系统不是严格要求"缓存+数据库"必须一致性的话,最好不用这个方案,即"读请求和写请求串行化",串到一个内存队列中.串行化可以保证一定不会出现不一致的情况,但是会导致系统的吞吐量大幅度降低,而且并发高了,队列容易堵塞.
经典的缓存+数据库读写模式就是 Cache Aside Pattern
① 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应
② 更新的时候,先更新数据库,然后再删除缓存
为什么不是更新缓存呢?
很多时候,在复杂的缓存场景,缓存不单单是数据库中直接取出来的值,比如更新了某个表的一个字段,然后其对应的缓存,是需要查询其他的表进行计算,才能运算出缓存的值,另外更新缓存的代价有时候是很高的,而且要考虑到这个缓存是否是被频繁访问的.
.
(21) Redis的线程模型
Redis内部使用文件事件处理器 file event handler,这个文件事件处理器是单线程的,所以redis才叫做单线程的模型,采用IO多路复用机制同时监听多个Socket,根据Socket上的事件来选择对应的事件处理器进行处理.
文件事件处理器的结构包括4个部分:多个Socket, IO多路复用程序, 文件事件分派器,事件处理器(连接应答处理器,命令请求处理器,命令回复处理器)
多个Socket可能会并发产生不同的操作,每个操作对应不同的文件事件,但是IO多路复用程序会监听多个Socket,会将Socket产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值