3.61 Redis的知识汇总

参考资料
几率大的Redis面试题(含答案):https://blog.csdn.net/Butterfly_resting/article/details/89668661
  1. Redis是什么

    1. Redis 是完全开源的,遵守 BSD 协议,是一个高性能的 key-value非关系型键值对数据库(C语言编写)。
    2. 数据结构服务器,键的值是String。值(value)可以是 字符串(String), 哈希(Hash), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型。
    3. Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
    4. Redis支持数据的备份,即master-slave模式的数据备份,支持主从复制,主机会自动将数据同步到从机,可以进行读写分离
    5. 与传统数据库不同的是 Redis 的数据是存在内存中的单线程模式,所以读写速度非常快,因此 redis 被广泛应用于缓存方向,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。另外,Redis 也经常用来做分布式锁。除此之外,Redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。
    6. (用户第一次使用查数据库,然后第二次使用会主动加载Redis缓存中,缓存可设置自动去数据库更新时间)
    7. 应用场景
      1. 主要能够体现 解决数据库的访问压力。
      2. 例如:短信验证码时间有效期、session共享解决方案
      3. 会话缓存(最常用)
      4. 消息队列,
      5. 比如支付3,活动排行榜或计数
      6. 发布,订阅消息(消息通知)
      7. 商品列表,评论列表等
  2. Redis的优势是什么

    1. 需要考虑的常用的数据写入缓存,(高性能:在内存中直接运行、高并发:把部分数据存到缓存中,缓存会及时更新数据库最新数据)
    2. 性能极高, 因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
      1. 为什么使用单线程
        1. 1. 使用单线程模型能带来更好的 可维护性,方便开发和调试;
        2. 2. 使用单线程模型也能 并发 的处理客户端的请求;(I/O 多路复用机制)
        3. 3. Redis 服务中运行的绝大多数操作的 性能瓶颈都不是 CPU;
      2. 单线程为什么这么快:
        1. 纯内存操作
        2. 单线程操作,避免了频繁的上下文切换
        3. 采用了非阻塞I/O多路复用机制
          1. 当用户线程发起read请求的时候,首先会将socket添加到select中,这时阻塞等待select函数返回。当数据到达时,select被激活,select函数返回,此时用户线程才正式发起read请求,读取数据。
          2. select函数是替当前的用户线程去等待数据到达,这个时候用户线程就空闲出来继续做别的事,而数据到达后,select函数会告诉用户线程数据到达了,用户线程可以发起read请求了,而数据已经达到了,就可以直接返回了,也就不存在阻塞了。
        4. 高效的数据结构,加上底层做了大量优化:Redis 对于底层的数据结构和内存占用做了大量的优化,例如不同长度的字符串使用不同的结构体表示,HyperLogLog 的密集型存储结构等等..
    3. 丰富的数据类型, 支持string,list,set,sorted set,hash和一些高些数据结构
    4. 支持事务,原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
    5. 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除
    6. 支持主从复制,主机自动将数据同步到从机,可以进行读写分离
    7. 持久化俩种方式(RDB和AOF)
      1. Redis是一个支持持久化的内存数据库,通过持久化机制把内存中的数据同步到硬盘文件来保证数据持久化。当Redis重启后通过把硬盘文件重新加载到内存,就能达到恢复数据的目的。
      2. 实现:单独创建fork()一个子进程,将当前父进程的数据库数据复制到子进程的内存中,然后由子进程写入到临时文件中,持久化的过程结束了,再用这个临时文件替换上次的快照文件,然后子进程退出,内存释放。
      3. RDB是Redis默认的持久化方式。按照一定的时间周期策略把内存的数据以快照的形式保存到硬盘的二进制文件。即Snapshot快照存储,对应产生的数据文件为dump.rdb,通过配置文件中的save参数来定义快照的周期。( 快照可以是其所表示的数据的一个副本,也可以是数据的一个复制品。)
      4. AOF:Redis会将每一个收到的写命令都通过Write函数追加到文件最后,类似于MySQL的binlog。当Redis重启是会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。
      5. 当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复。
      6. RDB和AOF的区别和各自优缺点
        1. RDB是在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复。
          1. 优点:使用单独子进程来进行持久化,主进程不会进行任何IO操作,保证了redis的高性能
          2. 缺点:RDB是间隔一段时间进行持久化,如果持久化之间redis发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候
        2. AOF:Append-only file,将“操作 + 数据”以格式化指令的方式追加到操作日志文件的尾部,在append操作返回后(已经写入到文件或者即将写入),才进行实际的数据变更,“日志文件”保存了历史所有的操作过程;当server需要数据恢复时,可以直接replay此日志文件,即可还原所有的操作过程。AOF相对可靠,它和mysql中bin.log、apache.log、zookeeper中txn-log简直异曲同工。AOF文件内容是字符串,非常容易阅读和解析。
          1. 优点:可以保持更高的数据完整性,如果设置追加file的时间是1s,如果redis发生故障,最多会丢失1s的数据;且如果日志写入不完整支持redis-check-aof来进行日志修复;AOF文件没被rewrite之前(文件过大时会对命令进行合并重写),可以删除其中的某些命令(比如误操作的flushall)。
          2. 缺点:AOF文件比RDB文件大,且恢复速度慢。
    8. Redis相对应的缺点
      1. 数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。
      2. Redis 不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的IP才能恢复。
      3. 主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性。
      4. Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源造成了很大的浪费。
    9. redis相比memcached有哪些优势?
      1. 存储方式 Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。 Redis有部份存在硬盘上,redis可以持久化其数据,
      2. 数据支持类型 memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型 ,提供list,set,zset,hash等数据结构的存储
      3. 使用底层模型不同 它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。 Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
      4. value 值大小不同:Redis 最大可以达到 512M;memcache 只有 1mb。
      5. redis的速度比memcached快很多
      6. Redis支持数据的备份,即master-slave模式的数据备份
  3. 数据类型以及命令

    1. String 字符串:字符串类型是 Redis 最基础的数据结构,首先键都是字符串类型,而且 其他几种数据结构都是在字符串类型基础上构建的,我们常使用的 set key value 命令就是字符串。常用在缓存、计数、共享Session、限速等。
      1. set/ get key值 “hello word”
      2. mset 多个key值 key1 “nihao”key2 “hello”
      3. SETEX/SETNX
    2. Hash 哈希:在Redis中,哈希类型是指键值本身又是一个键值对 结构,形如value={{field1,value1},...{fieldN,valueN}},添加命令:hset key field value。哈希可以用来存放用户信息,比如实现购物车
      1. hset/hget
      2. hmset/hmget
      3. HGETALL
    3. List 列表:列表(list)类型是用来存储多个有序的字符串。可以做简单的消息队列的功能。另外,可以利用 lrange 命令,做基于 Redis的分页功能,性能极佳,用户体验好。
      1. lpush
      2. rpush
      3. lrange
      4. LPOP/RPOP
    4. Set 集合:集合(set)类型也是用来保存多个的字符串元素,但和列表类型不一 样的是,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过 索引下标获取元素。利用 Set 的交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。
      1. sadd
      2. SPOP/scard/smebers
    5. Sorted Set 有序集合:Sorted Set 多了一个权重参数 Score,集合中的元素能够按 Score 进行排列。可以做排行榜应用,取 TOP N 操作
      1. ZADD/zcare/zrem/zrank
    6. 高级数据类型扩展
      1. bitMAP位图(存在不精确,但不存在精确)
        1. 可实现布隆过滤器是什么:底层用二进制向量和一系列的随机映射函数,空间效率和查询时间超过其它算法,缺点是有一定识别率和删除困难,常用于检查一个集合中是否存在一个元素
        2. 原理是:当一个元素加入集合,通过K个散列值将这个元素映射成一个数组的K个点,把它们置为1,检索时我们看这些点是不是为1就大约知道有没有它了,如果这些点有任何一个0,则被检查元素一定不在。1的话可能在
      2. PUB/SUB
      3. Hyperloglog:估算基数的近似最优算法
        1. 用于基数统计(Cardinality Counting) 通常是用来统计一个集合中不重复的元素个数。
      4. GeoHash
      5. Stream
  4. 配置相关命令

    1. slect#选择数据库(数据库编号0-15)
    2. 退出#退出连接
    3. 信息#获得服务的信息与统计
    4. monitor#实时监控
    5. config get#获得服务配置
    6. flushdb#删除当前选择的数据库中的key
    7. flushall#删除所有数据库中的键
  5. Redis事务

    1. Redis 事务的本质是通过MULTI、EXEC、WATCH等一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。
      1. Redis事务的三个阶段
        1. 事务开始 MULTI
        2. 命令入队
        3. 事务执行 EXEC
      2. Redis事务相关命令
        1. Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的
          1. redis 不支持回滚,“Redis 在事务失败时不进行回滚,而是继续执行余下的命令”, 所以 Redis 的内部可以保持简单且快速。
          2. 如果在一个事务中的命令出现错误,那么所有的命令都不会执行;
          3. 如果在一个事务中出现运行错误,那么正确的命令会被执行。
        2. 对四个原句的分析
          1. WATCH 命令是一个乐观锁,可以为 Redis 事务提供 check-and-set (CAS)行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。
          2. MULTI命令用于开启一个事务,它总是返回OK。 MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。
          3. EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值 nil 。
          4. 通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出。
          5. UNWATCH命令可以取消watch对所有key的监控。
        3. Redis事务特性
          1. Redis 是单进程程序,并且它保证在执行事务时,不会对事务进行中断,事务可以运行直到执行完所有事务队列中的命令为止。因此,Redis 的事务是总是带有隔离性的。
          2. Redis的事务总是具有ACID中的一致性和隔离性,其他特性是不支持的。当服务器运行在AOF持久化模式下,并且appendfsync选项的值为always时,事务也具有耐久性。
          3. Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。
  6. Redis过期键和内存相关

    1. Redis过期键的删除策略
      1. 我们都知道,Redis是key-value数据库,我们可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理。
        1. 定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
        2. 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
        3. 定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
        4. (expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。)
        5. 总结:redis采用的是定期删除+惰性删除策略。
      2. Redis key的过期时间和永久有效分别怎么设置?EXPIRE和PERSIST命令。
      3. 我们知道通过expire来设置key 的过期时间,那么对过期的数据怎么处理呢?
        1. 定时去清理过期的缓存;
        2. 当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。
        3. 两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!具体用哪种方案,大家可以根据自己的应用场景来权衡
    2. 内存如何保证是热点数据,如何优化内存。
      1. redis内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。
      2. Redis的内存淘汰策略是指在Redis的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据。过期策略用于处理过期的缓存数据
      3. 内存用完后:如果达到设置的上限,Redis的写命令会返回错误信息(但是读命令还可以正常返回)或者你可以配置内存淘汰机制,当Redis达到内存上限时会冲刷掉旧的内容。
        1. 全局的键空间选择性移除
          1. noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。
          2. allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。(这个是最常用的)
          3. allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。
        2. 设置过期时间的键空间选择性移除
          1. volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。
          2. volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。
          3. volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。
      4. 内存优化
        1. 可以好好利用Hash,list,sorted set,set等集合类型数据,因为通常情况下很多小的Key-Value可以用更紧凑的方式存放到一起。尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。比如你的web系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的key,而是应该把这个用户的所有信息存储到一张散列表里面
  7. 集群方案

    1. 哨兵模式

      1. 哨兵(sentinel) 的一些设计思路和zookeeper非常类似
      2. 哨兵是 redis 集群机构中非常重要的一个组件,主要有以下功能
        1. 集群监控:负责监控 redis master 和 slave 进程是否正常工作。
        2. 消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
        3. 故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
        4. 配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。
      3. 哨兵用于实现 redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。
        1. 故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举的问题。
        2. 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的,因为如果一个作为高可用机制重要组成部分的故障转移系统本身是单点的,那就很坑爹了。
      4. 哨兵核心
        1. 哨兵至少需要 3 个实例,来保证自己的健壮性。
        2. 哨兵 + redis 主从的部署架构,是不保证数据零丢失的,只能保证 redis 集群的高可用性。
        3. 对于哨兵 + redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。
      5. 扩展性差
    2. 多机部署,保持一致(实际的Redis集群)
      1. 主从复制,读写分离
      2. 一类是主数据库(master)一类是从数据库(slave),主数据库可以进行读写操作,当发生写操作的时候自动将数据同步到从数据库,而从数据库一般是只读的,并接收主数据库同步过来的数据,一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库。
    3. 分区和分布式方案
      1. 分区相关
        1. 可以在同一个服务器部署多个Redis的实例,并把他们当作不同的服务器来使用,在某些时候,无论如何一个服务器是不够的, 所以,如果你想使用多个CPU,你可以考虑一下分片(shard)。
        2. 分区可以让Redis管理更大的内存,Redis将可以使用所有机器的内存。如果没有分区,你最多只能使用一台机器的内存。分区使Redis的计算能力通过简单地增加计算机得到成倍提升,Redis的网络带宽也会随着计算机和网卡的增加而成倍增长。
      2. 分布式方案
        1. 分布式Redis是前期做
          1. 既然Redis是如此的轻量(单实例只使用1M内存),为防止以后的扩容,最好的办法就是一开始就启动较多实例。即便你只有一台服务器,你也可以一开始就让Redis以分布式的方式运行,使用分区,在同一台服务器上启动多个实例。
          2. 一开始就多设置几个Redis实例,例如32或者64个实例,对大多数用户来说这操作起来可能比较麻烦,但是从长久来看做这点牺牲是值得的。
          3. 这样的话,当你的数据不断增长,需要更多的Redis服务器时,你需要做的就是仅仅将Redis实例从一台服务迁移到另外一台服务器而已(而不用考虑重新分区的问题)。一旦你添加了另一台服务器,你需要将你一半的Redis实例从第一台机器迁移到第二台机器
        2. 什么是 RedLock
          1. Redis 官方站提出了一种权威的基于 Redis 实现分布式锁的方式名叫 Redlock,此种方式比原先的单节点的方法更安全。它可以保证以下特性:
          2. 安全特性:互斥访问,即永远只有一个 client 能拿到锁
          3. 避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区
          4. 容错性:只要大部分 Redis 节点存活就可以正常提供服务
      3. Redis实现分布式锁https://blog.csdn.net/L_BestCoder/article/details/79336986
        1. 如何解决 Redis 的并发竞争 Key 问题?
        2. Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系Redis中可以使用SETNX命令实现分布式锁。
        3. 当且仅当 key 不存在,将 key 的值设为 value。 若给定的 key 已经存在,则 SETNX 不做任何动作(SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写,返回值:设置成功,返回 1 。设置失败,返回 0)
          1. 加锁:setnx(key,1)
            1. SETNX key value功能:当且仅当 key 不存在,将 key 的值设为 value ,并返回1;若给定的 key 已经存在,则 SETNX 不做任何动作,并返回0。
          2. 解锁:使用 del key 命令就能释放锁
            1. DEL key [KEY …]功能:删除给定的一个或多个 key ,不存在的 key 会被忽略。
          3. 锁超时
            1. 解决分布式死锁:为了防止获取锁后程序出现异常,导致其他线程/进程调用SETNX命令总是返回0而进入死锁状态,需要为该key设置一个“合理”的过期时间
            2. 通过Redis中expire()给锁设定最大持有时间,如果超过,则Redis来帮我们释放锁。
            3. 使用 setnx key “当前系统时间+锁持有的时间”和getset key “当前系统时间+锁持有的时间”组合的命令就可以实现。
              1. while(client.setnx("lock",featureCode+":"+String.valueOf(System.currentTimeMillis())) == 0){ Long lockTime = Long.valueOf(client.get("lock").substring(9)); if (lockTime!=null && System.currentTimeMillis() > lockTime+TIMEOUT_SECOUND) { client.del("lock"); } Thread.sleep(10000); }
          4. 分布式锁的问题
            1. 1:必要的超时机制:获取锁的客户端一旦崩溃,一定要有过期机制,否则其他客户端都降无法获取锁,造成死锁问题。
            2. 2:分布式锁,多客户端的时间戳不能保证严格意义的一致性,所以在某些特定因素下,有可能存在锁串的情况。要适度的机制,可以承受小概率的事件产生。
            3. 3:只对关键处理节点加锁,良好的习惯是,把相关的资源准备好,比如连接数据库后,调用加锁机制获取锁,直接进行操作,然后释放,尽量减少持有锁的时间。
            4. 4:在持有锁期间要不要CHECK锁,如果需要严格依赖锁的状态,最好在关键步骤中做锁的CHECK检查机制,但是根据我们的测试发现,在大并发时,每一次CHECK锁操作,都要消耗掉几个毫秒,而我们的整个持锁处理逻辑才不到10毫秒,玩客没有选择做锁的检查。
            5. 5:sleep学问,为了减少对Redis的压力,获取锁尝试时,循环之间一定要做sleep操作。但是sleep时间是多少是门学问。需要根据自己的Redis的QPS,加上持锁处理时间等进行合理计算。
            6. 6:至于为什么不使用Redis的muti,expire,watch等机制,可以查一参考资料,找下原因。
        4. 什么是 RedLock
          1. Redis 官方站提出了一种权威的基于 Redis 实现分布式锁的方式名叫 Redlock,此种方式比原先的单节点的方法更安全。它可以保证以下特性:
          2. 安全特性:互斥访问,即永远只有一个 client 能拿到锁
          3. 避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区
          4. 容错性:只要大部分 Redis 节点存活就可以正常提供服务
  8. Redis单线程模型

    1. 为什么叫单线程模式
      1. Redis基于Reactor模式开发了网络事件处理器,这个处理器被称为文件事件处理器(file event handler)。它的组成结构为4部分:多个套接字、IO多路复用程序、文件事件分派器、事件处理器。因为文件事件分派器队列的消费是单线程的,所以Redis才叫单线程模型。
        1. 文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字, 并根据套接字目前执行的任务来为套接字关联不同的事件处理器。
        2. 当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时, 与操作相对应的文件事件就会产生, 这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。
      2. 虽然文件事件处理器以单线程方式运行, 但通过使用 I/O 多路复用程序来监听多个套接字, 文件事件处理器既实现了高性能的网络通信模型, 又可以很好地与 redis 服务器中其他同样以单线程方式运行的模块进行对接, 这保持了 Redis 内部单线程设计的简单性。
      3. 参考资料:https://www.cnblogs.com/barrywxx/p/8570821.html
  9. 缓存异常

    1. 缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级分别是什么
      1. 缓存雪崩:是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
        1. 解决方案:
          1. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
          2. 一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。
          3. 给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存。
      2. 缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。
        1. 解决方案
          1. 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
          2. 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
          3. 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力
          4. 布隆过滤器(推荐):就是引入了k(k>1)k(k>1)个相互独立的哈希函数,保证在给定的空间、误判率下,完成元素判重的过程。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。Bloom-Filter算法的核心思想就是利用多个不同的Hash函数来解决“冲突”。Hash存在一个冲突(碰撞)的问题,用同一个Hash得到的两个URL的值有可能相同。为了减少冲突,我们可以多引入几个Hash,如果通过其中的一个Hash值我们得出某元素不在集合中,那么该元素肯定不在集合中。只有在所有的Hash函数告诉我们该元素在集合中时,才能确定该元素存在于集合中。这便是Bloom-Filter的基本思想。Bloom-Filter一般用于在大数据量的集合中判定某元素是否存在。
      3. 缓存击穿
        1. 缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
        2. 解决方案
          1. 设置热点数据永远不过期。
          2. 加互斥锁,互斥锁
      4. 缓存降级:当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。
        1. 缓存降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。
        2. 在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案
          1. 一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
          2. 警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;
          3. 错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
          4. 严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。
          5. 服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。
      5. 缓存预热
        1. 缓存预热:缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
          1. 解决方案
            1. 直接写个缓存刷新页面,上线时手工操作一下;
            2. 数据量不大,可以在项目启动的时候自动进行加载;
            3. 定时刷新缓存
      6. 热点数据和冷点数据
        1. 热点数据,缓存才有价值
        2. 对于冷数据而言,大部分数据可能还没有再次访问到就已经被挤出内存,不仅占用内存,而且价值不大。频繁修改的数据,看情况考虑使用缓存
        3. 那存不存在,修改频率很高,但是又不得不考虑缓存的场景呢?有!比如,这个读取接口对数据库的压力很大,但是又是热点数据,这个时候就需要考虑通过缓存手段,减少数据库的压力,比如我们的某助手产品的,点赞数,收藏数,分享数等是非常典型的热点数据,但是又不断变化,此时就需要将数据同步保存到Redis缓存,减少数据库压力。
        4. 数据更新前至少读取两次,缓存才有意义。这个是最基本的策略,如果缓存还没有起作用就失效了,那就没有太大价值了。
      7. 缓存热点key
        1. 缓存中的一个Key(比如一个促销商品),在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
        2. 解决方案:对缓存查询加锁,如果KEY不存在,就加锁,然后查DB入缓存,然后解锁;其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入DB查询
  10. Redis支持的Java客户端都有哪些

    1. Redisson、Jedis、lettuce等等,官方推荐使用Redisson。
    2. Redis支持的Java客户端 为什么推荐是Redisson,有哪些特点
      1. Redis和Redisson有什么关系?
        1. Redisson是一个高级的分布式协调Redis客服端,能帮助用户在分布式环境中轻松实现一些Java的对象 (Bloom filter, BitSet, Set, SetMultimap, ScoredSortedSet, SortedSet, Map, ConcurrentMap, List, ListMultimap, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, ReadWriteLock, AtomicLong, CountDownLatch, Publish / Subscribe, HyperLogLog)。
      2. Jedis与Redisson对比有什么优缺点?
        1. Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持;Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。Redisson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。
  11. redis的发布与订阅
    1. Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
      1. Redis 客户端可以订阅任意数量的频道。
      2. 下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:
      3. 当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
  12. 实现秒杀系统设计

    1. 我们现在要卖100件下面这个婴儿纸尿裤,然后我们根据以往这样秒杀活动的数据经验来看,目测来抢这100件纸尿裤的人足足有10万人。(南极人打钱!)
    2. 可能出现的问题
      1. 保证高并发这个,因为多人抢购,时间极短、 瞬间用户量大。
      2. 缓存雪崩,缓存击穿,缓存穿透这些我之前提到的点都是有可能发生的,出现问题打挂DB那就很难受了,活动失败用户体验差,活动人气没了,最后背锅的还是开发。
      3. 但凡是个秒杀,都怕超卖,我这里举例的只是尿不湿,要是换成100个华为MatePro30,商家的预算经费卖100个可以赚点还可以造势,结果你写错程序多卖出去200个,你不发货用户投诉你,平台封你店,你发货就血亏,你怎么办?
      4. 恶意请求:你这么低的价格,假如我抢到了,我转手卖掉我不是血赚?就算我不卖我也不亏啊,那用户知道,你知道,别的别有用心的人(黑客、黄牛…)肯定也知道的。
      5. 链接暴露:前面几个问题大家可能都很好理解,一看到这个有的小伙伴可能会比较疑惑,啥是链接暴露呀?
      6. 数据库每秒上万甚至十几万的QPS(每秒请求数)直接打到数据库,基本上都要把库打挂掉,而且你服务不单单是做秒杀的还涉及其他的业务,你没做降级、限流、熔断啥的,别的一起挂,小公司的话可能全站崩溃404。
    3. 设计原理
      1. 服务单一职责:单一职责的好处就是就算秒杀没抗住,秒杀库崩了,服务挂了,也不会影响到其他的服务。(强行高可用)
      2. 资源静态化,Nginx动静分离,提前放入CDN服务器的放进去。
      3. 秒杀链接加盐:简单,把URL动态化,就连写代码的人都不知道,你就通过MD5之类的加密算法加密随机的字符串去做url,然后通过前端代码获取url后台校验才能通过。(风控,做信息验证,UUID)
      4. Redis集群:主从同步、读写分离,我们还搞点哨兵,开启持久化直接无敌高可用!
      5. Nginx大家想必都不陌生了吧,这玩意是高性能的web服务器,并发也随便顶几万不是梦,但是我们的Tomcat只能顶几百的并发呀,那简单呀负载均衡嘛,一台服务几百,那就多搞点,在秒杀的时候多租点流量机。
      6. 按钮控制:按钮可以点击之后也得给他置灰几秒,不然他一样在开始之后一直点的。你敢说你们秒杀的时候不是这样的?
      7. 资源静态化:秒杀一般都是特定的商品还有页面模板,现在一般都是前后端分离的,所以页面一般都是不会经过后端的,但是前端也要自己的服务器啊,那就把能提前放入cdn服务器的东西都放进去,反正把所有能提升效率的步骤都做一下,减少真正秒杀时候服务器的压力。
      8. 限流:
        1. 限流这里我觉得应该分为前端限流和后端限流。
          1. 前端限流:这个很简单,一般秒杀不会让你一直点的,一般都是点击一下或者两下然后几秒之后才可以继续点击,这也是保护服务器的一种手段。
          2. 后端限流:秒杀的时候肯定是涉及到后续的订单生成和支付等操作,但是都只是成功的幸运儿才会走到那一步,那一旦100个产品卖光了,return了一个false,前端直接秒杀结束,然后你后端也关闭后续无效请求的介入了。
      9. 超卖
        1. Lua脚本是类似Redis事务,有一定的原子性,不会被其他命令插队,可以完成一些Redis事务性的操作。这点是关键。
      10. 限流&降级&熔断&隔离:
      11. 削峰填谷:
        1. 一说到这个名词,很多小伙伴就知道了,对的MQ,你买东西少了你直接100个请求改库我觉得没问题,但是万一秒杀一万个,10万个呢?服务器挂了,程序员又要背锅的。
      12. 事务解决库存预热和超卖,大家也可以用LUA指令
      13. 数据库;单独给秒杀建立一个数据库,为秒杀服务,表的设计也是竟可能的简单点,现在的互联网架构部署都是分库的。或者连接池,加索引,主从复制。
        1.  
    4. 总结:
      1. 至少你得说出可能出现的情况,需要注意的情况,以及对于的解决思路和方案。
      2. 最后就是需要对整个链路比较熟悉,注意是一个完整的链路,前端怎么设计的呀,网关的作用呀,怎么解决Redis的并发竞争啊,数据的同步方式呀,MQ的作用啊。
posted @ 2020-09-25 07:16  Smileing  阅读(72)  评论(0编辑  收藏  举报