Redis相关
2024-1-31
| 2024-4-22
字数 5015阅读时长 13 分钟
beizhu
type
Post
status
Published
date
Jan 31, 2024
slug
summary
Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。它通常用于实现缓存解决方案,提高数据获取速度、降低数据库负载,并支持多种数据结构,如字符串、哈希、列表、集合、有序集合等。
tags
SQL
category
技术
icon
password
Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。它通常用于实现缓存解决方案,提高数据获取速度、降低数据库负载,并支持多种数据结构,如字符串、哈希、列表、集合、有序集合等。
 
Redis使用单线程模型主要因为它是内存数据库,数据存储在内存中,访问速度非常快。Redis的瓶颈通常是网络I/O而不是CPU,因此使用单线程能够简化内部结构,减少上下文切换和锁竞争等性能消耗,从而实现高效率操作。
 

1. 什么是缓存雪崩、缓存击穿和缓存穿透?怎么解决?

• 缓存雪崩:当大量缓存数据在同一时间过期(失效)或者 Redis 故障宕机时,如果此时有大量的用户请求,都无法在 Redis 中处理,于是全部请求都直接访问数据库,从而导致数据库的压力骤增,严重的会造成数据库宕机,从而形成一系列连锁反应,造成整个系统崩溃。
notion image
• 缓存击穿:如果缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮。
notion image
• 缓存穿透:当用户访问的数据,既不在缓存中,也不在数据库中,导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据,没办法构建缓存数据,来服务后续的请求。那么当有大量这样的请求到来时,数据库的压力骤增。
notion image
 
 
缓存雪崩解决方案:
  • 均匀设置过期时间:如果要给缓存数据设置过期时间,应该避免将大量的数据设置成同一个过期时间。我们可以在对缓存数据设置过期时间时,给这些数据的过期时间加上一个随机数,这样就保证数据不会在同一时间过期。
  • 互斥锁:当业务线程在处理用户请求时,如果发现访问的数据不在 Redis 里,就加个互斥锁,保证同一时间内只有一个请求来构建缓存(从数据库读取数据,再将数据更新到 Redis 里),当缓存构建完成后,再释放锁。未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。实现互斥锁的时候,最好设置超时时间,不然第一个请求拿到了锁,然后这个请求发生了某种意外而一直阻塞,一直不释放锁,这时其他请求也一直拿不到锁,整个系统就会出现无响应的现象。
  • 后台更新缓存:业务线程不再负责更新缓存,缓存也不设置有效期,而是让缓存“永久有效”,并将更新缓存的工作交由后台线程定时更新
 
缓存击穿解决方案:
  • 互斥锁方案,保证同一时间只有一个业务线程更新缓存,未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。
  • 不给热点数据设置过期时间,由后台异步更新缓存,或者在热点数据准备要过期前,提前通知后台线程更新缓存以及重新设置过期时间;
 
缓存穿透解决方案:
  • 非法请求的限制:当有大量恶意请求访问不存在的数据的时候,也会发生缓存穿透,因此在 API 入口处我们要判断求请求参数是否合理,请求参数是否含有非法值、请求字段是否存在,如果判断出是恶意请求就直接返回错误,避免进一步访问缓存和数据库。
  • 缓存空值或者默认值:当我们线上业务发现缓存穿透的现象时,可以针对查询的数据,在缓存中设置一个空值或者默认值,这样后续请求就可以从缓存中读取到空值或者默认值,返回给应用,而不会继续查询数据库。
  • 布隆过滤器:我们可以在写入数据库数据时,使用布隆过滤器做个标记,然后在用户请求到来时,业务线程确认缓存失效后,可以通过查询布隆过滤器快速判断数据是否存在,如果不存在,就不用通过查询数据库来判断数据是否存在。即使发生了缓存穿透,大量请求只会查询 Redis 和布隆过滤器,而不会查询数据库,保证了数据库能正常运行,Redis 自身也是支持布隆过滤器的。
 

2. Redis支持哪些数据类型?

  1. String(字符串):这是Redis最基本的数据类型,一个key对应一个value。String类型是二进制安全的,意味着它可以包含任何数据,如jpg图片或者序列化的对象。一个键最大能存储512MB的数据。这种数据类型常用于缓存一些常用的键值对,以在高并发的情况下提高数据访问速度。
  1. Hash(哈希):Hash是一个键值对的集合,是一个string类型的field和value的映射表。它特别适合存储对象,可以方便的进行数据的存储和读取。每个Hash可以存储2^32-1个键值对。Hash类型适合存储一些结构化的数据,并且可以对Hash中的某个键值对进行修改、删除操作。
  1. List(列表):List是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到头部(左边)或者尾部(右边)。这种数据类型可以实现队列和栈的功能,常用于消息队列,并且可以避免消息丢失等问题。
  1. Set(集合):Set是string类型的无序集合,它是通过哈希表实现的。这种数据类型可以实现去重等功能,例如,你可以使用它来记录一些不重复的值,如用户的ID等。
  1. Sorted Set(有序集合):Sorted Set和Set相似,但每个字符串元素都会关联一个浮点数类型的分数。元素的分数用来排序,如果两个成员有相同的分数,那么他们的排名按照字典序计算。这种数据类型具有类似Set类型的去重功能,但可以根据分值进行排序。
  1. Bitmaps(位图)和 HyperLogLogs:这是用于特殊情况的数据类型,例如统计一系列值的“唯一数量”。
 

3. 布隆过滤器原理是什么?

布隆过滤器(Bloom Filter)是一种空间效率极高的概率型数据结构,它允许你进行快速的成员查询操作。但是布隆过滤器的缺点是它有一定的误报率,意味着有时候会错误地认为某个元素在集合中,而实际上它不在。
 
布隆过滤器由「初始值都为 0 的位图数组」和「 N 个哈希函数」两部分组成。当我们在写入数据库数据时,在布隆过滤器里做个标记,这样下次查询数据是否在数据库时,只需要查询布隆过滤器,如果查询到数据没有被标记,说明不在数据库中。
布隆过滤器会通过 3 个操作完成标记:
  • 第一步,使用 N 个哈希函数分别对数据做哈希计算,得到 N 个哈希值;
  • 第二步,将第一步得到的 N 个哈希值对位图数组的长度取模,得到每个哈希值在位图数组的对应位置。
  • 第三步,将每个哈希值在位图数组的对应位置的值设置为 1;
    举个例子,假设有一个位图数组长度为 8,哈希函数 3 个的布隆过滤器。
    notion image
    在数据库写入数据 x 后,把数据 x 标记在布隆过滤器时,数据 x 会被 3 个哈希函数分别计算出 3 个哈希值,然后在对这 3 个哈希值对 8 取模,假设取模的结果为 1、4、6,然后把位图数组的第 1、4、6 位置的值设置为 1。当应用要查询数据 x 是否数据库时,通过布隆过滤器只要查到位图数组的第 1、4、6 位置的值是否全为 1,只要有一个为 0,就认为数据 x 不在数据库中
    布隆过滤器由于是基于哈希函数实现查找的,高效查找的同时存在哈希冲突的可能性,比如数据 x 和数据 y 可能都落在第 1、4、6 位置,而事实上,可能数据库中并不存在数据 y,存在误判的情况。
    所以,查询布隆过滤器说数据存在,并不一定证明数据库中存在这个数据,但是查询到数据不存在,数据库中一定就不存在这个数据
     
    1. 初始化: 布隆过滤器在开始时是一个由所有位都设置为0的位数组(bit array)或位向量(bit vector),通常表示为一个超长的二进制数。除此之外,还需要几个哈希函数,这些哈希函数可以将任何输入映射到位数组的有效范围内。
    1. 添加元素(Insertion): 当要添加一个元素时,布隆过滤器会使用每一个哈希函数对该元素进行哈希,从而得到几个哈希值。然后,把这些哈希值对应的位都设置成1。举例来说,如果有三个哈希函数,它们将某个元素分别映射到了第2、第4和第5位,那么这三个位置的位就会被设置成1。
    1. 成员查询(Query): 当查询一个元素是否存在于布隆过滤器时,会用同样的哈希函数对元素进行哈希,得到几个哈希值,然后检查这些值对应的位是否都是1。如果其中有任何一位不是1,则该元素绝不可能在集合中(因为如果真的存在于集合中的话,在添加时,所有的位都会被设置成1)。如果所有位都是1,则被假定为该元素存在于集合中。但是,由于不同元素的哈希值可能存在冲突,即不同元素的哈希值可能映射到相同的位置,因此即使所有位都是1,也有可能是因为其他元素导致的。
    1. 误报(False positive): 布隆过滤器允许误报,即假阳性,但不允许假阴性(即如果元素确实在集合中,则查询一定会返回true)。通过增加位数组的大小和使用更多的哈希函数,可以降低误报率,但添加元素的代价也会变得更高,且会增加查询的成本。
    布隆过滤器不支持从集合中删除元素,因为那样会影响到其他元素。一种解决办法是使用计数型布隆过滤器(Counting Bloom Filter),它使用计数器数组代替位数组,以支持删除操作。
     
     

    4. 阐述对Redis的理解,并比较直接在应用程序内存中存储数据与使用Redis作为数据存储的主要差异和潜在优势。

    1. 数据持久性:
        • 应用程序内存: 当应用程序重启或者服务器宕机时,存储在内存中的数据会丢失。
        • Redis: 虽然Redis本质上是一个内存数据库,但提供了数据持久化的功能,可以通过RDB(Redis数据库)快照或AOF(Append Only File)日志保持数据持久化。可以把内存中的数据保存到磁盘,即使是在宕机或者重启后,数据也不会丢失。
    1. 数据结构和操作:
        • 应用程序内存: 受限于编程语言提供的数据结构,可能需要自己实现复杂的数据操作。
        • Redis: 提供了丰富的数据结构,如字符串、列表、集合、有序集合、散列等,并且对这些数据结构提供了丰富的操作命令。
    1. 共享数据:
        • 应用程序内存: 数据只能被同一个进程或者服务内的线程所访问。
        • Redis: 作为独立的服务,可以被多个应用程序或者多个服务器上的应用程序共享。
    1. 可伸缩性:
        • 应用程序内存: 受限于单个应用程序的资源和架构。直接使用应用内存限制了系统的伸缩性,随着数据增长,应用可能会受到物理内存大小的限制。
        • Redis: 支持主从复制、哨兵模式和集群模式,可以很好地扩展以满足更高的吞吐量和更大的数据集。
    1. 网络访问:
        • 应用程序内存: 访问数据需要在应用程序的内部进行。
        • Redis: 通过网络提供服务,可以简化分布式系统中的数据共享和通信,支持多语言客户端和多个应用实例之间的数据共享。
    1. 并发处理:
        • 应用程序内存: 并发访问和修改数据可能需要开发者手动管理锁和同步机制。
        • Redis: 提供了原子操作和其他特性来处理并发访问数据,简化了并发编程的复杂度。
    1. 高可用性和容错:
        • 应用程序内存: 需要开发者自己设计和实现高可用性解决方案。
        • Redis: 提供了哨兵(Sentinel)和集群(Cluster)等机制来实现自动故障转移和高可用性。
    尽管将数据存储在应用程序内存中可以达到快速读取的效果,使用Redis作为数据存储,能够为应用提供高性能的数据处理能力,并带来更好的数据持久性、丰富的数据结构、高可用性的部署以及方便的扩展性。这些优势使得Redis成为了许多高负载、大数据量且对性能有极高要求的应用程序的首选数据库之一。
     
     

    5. Redis主从复制的工作原理

    Redis的主从复制功能允许将一台Redis服务器的数据复制到一个或多个从服务器,实现数据的同步。工作原理简述如下:
    • 当从服务器连接到主服务器时,它会发送一个SYNC命令。
    • 主服务器接到SYNC命令后会开始在后台保存快照(BGSAVE),并将这期间接收到的写命令缓存在内存中。
    • 快照完成后,主服务器将快照文件和所有积累的写命令发送给从服务器。
    • 从服务器首先载入快照文件来恢复数据集,然后执行收到的写命令来与主服务器的数据一致。
    之后,主服务器每接到一个写命令,都会将其发送给所有从服务器,让整个复制系统的数据保持一致。通过这种方式,可以实现读写分离,提高系统的可用性。
     
     
     

    6. mysql和redis在项目中怎么确保数据一致性的?

    对于读数据,选择旁路缓存策略,如果 cache 不命中,会从 db 加载数据到 cache。
    对于写数据,选择更新 db 后,再删除缓存。
    notion image
    针对删除缓存异常的情况,会对 key 设置过期时间兜底,只要过期时间一到,过期的 key 就会被删除了。
    除此之外,还有两种方式应对删除缓存失败的情况。
     
    消息队列方案
    我们可以引入消息队列,将第二个操作(删除缓存)要操作的数据加入到消息队列,由消费者来操作数据。
    • 如果应用删除缓存失败,可以从消息队列中重新读取数据,然后再次删除缓存,这个就是重试机制。当然,如果重试超过的一定次数,还是没有成功,我们就需要向业务层发送报错信息了。
    • 如果删除缓存成功,就要把数据从消息队列中移除,避免重复操作,否则就继续重试。
    举个例子,来说明重试机制的过程。
    notion image
    订阅 MySQL binlog,再操作缓存
    先更新数据库,再删缓存」的策略的第一步是更新数据库,那么更新数据库成功,就会产生一条变更日志,记录在 binlog 里。
    于是我们就可以通过订阅 binlog 日志,拿到具体要操作的数据,然后再执行缓存删除,阿里巴巴开源的 Canal 中间件就是基于这个实现的。
    Canal 模拟 MySQL 主从复制的交互协议,把自己伪装成一个 MySQL 的从节点,向 MySQL 主节点发送 dump 请求,MySQL 收到请求后,就会开始推送 Binlog 给 Canal,Canal 解析 Binlog 字节流之后,转换为便于读取的结构化数据,供下游程序订阅使用。
    下图是 Canal 的工作原理:
    notion image
    所以,如果要想保证「先更新数据库,再删缓存」策略第二个操作能执行成功,我们可以使用「消息队列来重试缓存的删除」,或者「订阅 MySQL binlog 再操作缓存」,这两种方法有一个共同的特点,都是采用异步操作缓存。
     
  • SQL
  • GIT简易常用命令Docker简易持久化Next.js应用日志
    Loading...