一、什么是Redis
Redis 本质上是一个 Key-Value 类型的内存数据库,很像 memcached,整个 数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据 flush 到硬盘 上进行保存。因为是纯内存操作,Redis 的性能非常出色,每秒可以处理超过 10 万次读写操作,是已知性能最快的 Key-Value DB。
Redis 的出色之处不仅仅是性能,Redis 最大的魅力是支持保存多种数据结构,此外单 个 value 的最大限制是1GB,不像 memcached 只能保存 1MB 的数据,另外 Redis 也可以对存入的 Key-Value 设置 expire 时间。
Redis 的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此 Redis 适合的场景主要
局限在较小数据量的高性能操作和运算上。
二、Redis的数据类型
Redis 的数据结构有五种,分别是:
String——字符串
String 数据结构是简单的 key-value 类型,value 不仅可以是 String,也可以是数字(当数字类型用 Long 可以表示的时候 encoding 就是整型,其他都存储在 sdshdr 当做字符串)。
Hash——字典
在 Memcached 中,我们经常将一些结构化的信息打包成 hashmap,在客户端序列化后存储为一个字符串的值(一般是 JSON 格式),比如用户的昵称、年龄、性别、积分等。
List——列表
List 说白了就是链表(redis 使用双端链表实现的 List),相信学过数据结构知识的人都应该能理解其结构。
Set——集合
Set 就是一个集合,集合的概念就是一堆不重复值的组合。利用 Redis 提供的 Set 数据结构,可以存储一些集合性的数据。
Sorted Set——有序集合
和 Sets 相比,Sorted Sets 是将 Set 中的元素增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,比如一个游戏的用户得分排行榜。
注意: 这里的Sorted Set有争议,也有人说是zset,但总的来说原理是一样的只是在叫法上有些不一样,也有可能是随着redis的更新所导致的名称的变换,有懂的人可以评论区留言交流一下。
2.1 Redis的使用场景
Redis(REmote DIctionary Server)是一个基于内存的高性能键值存储系统。由于其快速、可靠和灵活的特性,Redis 在各种场景下被广泛应用,包括以下几个常见的使用场景:
-
缓存(Caching):Redis 作为内存数据库,对于经常访问的数据进行缓存是它最常见的用途之一。通过将计算结果、数据库查询结果或经常使用的数据存储在 Redis 中,可以大大提高系统响应速度和吞吐量。Redis 提供了灵活的缓存策略和过期时间设置,以满足不同缓存需求。
-
分布式锁(Distributed Locking):Redis 提供了一种简单高效的方法来实现分布式锁。通过使用 Redis 的原子操作,比如 SETNX(set if not exist)命令,可以实现多个分布式节点之间的互斥访问,确保某个任务只能由一个节点处理。
-
会话存储(Session Storage):在分布式环境中,使用 Redis 储存和管理用户会话数据可以实现无状态应用。将用户的登录状态或其他会话数据存储在 Redis 中,使得不同节点之间可以共享会话状态,提高了系统的可伸缩性和容错性。
-
消息队列(Message Queue):Redis 的 Pub/Sub(发布/订阅)功能可以用作轻量级的消息队列系统。通过发布者将消息发送到指定的频道,订阅者可以实时获取到消息并进行处理。这种方式可用于实现异步消息传递、任务分发和事件驱动等应用。
-
计数器和统计信息(Counters & Statistics):Redis 提供了一些特殊的数据类型(如计数器和有序集合),可用于存储和操作计数器、计量数据和统计信息。它支持原子操作,可以快速地对计数器进行增减操作,并提供了方便的集合操作(如按范围获取数据、按权重排序)。
-
实时排行榜(Leaderboard):Redis 的有序集合数据类型可以用于创建实时排行榜。通过将用户的分数和标识存储在有序集合中,可以方便地进行排名、范围查询和 Top N 查询等操作,用于实现游戏排行榜、热门内容排名等功能。
除了以上场景,Redis 还可以用于实现分布式缓存、实时数据分析、时间序列数据存储等各种应用。由于 Redis 的高性能和丰富的功能,可以根据具体需求灵活地在各种场景下使用。
2.2 Redis的优缺点
优点:
a) 性能极高 – Redis 能支持超过 100K+ 每秒的读写频率。
b) 丰富的数据类型 – Redis 支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
c) 原子 – Redis 的所有操作都是原子性的,同时 Redis 还支持对几个操作全并后的原子性执行。
attention 原子性定义:例如,A 想要从自己的帐户中转 1000 块钱到 B 的帐户里。那个从 A 开始转帐,到转帐结束的这一个过程,称之为一个事务。如果在 A 的帐户已经减去了 1000 块钱的时候,忽然发生了意外,比如停电什么的,导致转帐事务意外终止了,而此时 B 的帐户里还没有增加 1000 块钱。那么,我们称这个操作失败了,要进行回滚。回滚就是回到事务开始之前的状态,也就是回到 A 的帐户还没减 1000 块的状态,B 的帐户的原来的状态。此时A 的帐户仍然有 3000 块,B 的帐户仍然有 2000 块。我们把这种要么一起成功(A 帐户成功减少 1000,同时 B 帐户成功增加 1000),要么一起失败(A 帐户回到原来状态,B 帐户也回到原来状态)的操作叫原子性操作。如果把一个事务可看作是一个程序,它要么完整的被执行,要么完全不执行,这种特性就叫原子性。
d)丰富的特性 – Redis 还支持 publish/subscribe, 通知, key 过期等等特性。
缺点:
a)由于是内存数据库,所以,单台机器,存储的数据量,跟机器本身的内存大小。虽然 redis 本身有 key 过期策略,但是还是需要提前预估和节约内存。如果内存增长过快,需要定期删除数据。
b) 如果进行完整重同步,由于需要生成 rdb 文件,并进行传输,会占用主机的 CPU,并会消耗现网的带宽。不过 redis2.8 版本,已经有部分重同步的功能,但是还是有可能有完整重同步的。比如,新上线的备机。
c) 修改配置文件,进行重启,将硬盘中的数据加载进内存,时间比较久。在这个过程中,redis 不能
提供服务。
三、Redis常见的问题有哪些如何解决?
3.1 Redis 常见的性能问题都有哪些?如何解决?
(1)、Master 写内存快照,save 命令调度 rdbSave 函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以 Master 最好不要写内存快照。
(2)、Master AOF 持久化,如果不重写 AOF 文件,这个持久化方式对性能的影响是最小的,但是 AOF 文件会不断增大,AOF 文件过大会影响 Master 重启的恢复速度。Master 最好不要做任何持久化工作,包括内存快照和 AOF日志文件,特别是不要启用内存快照做持久化,如果数据比较关键,某个 Slave 开启 AOF 备份数据,策略为每秒同步一次。
(3)、Master 调用 BGREWRITEAOF 重写 AOF 文件,AOF 在重写的时候会占大量的 CPU 和内存资源,导致服务 load 过高,出现短暂服务暂停现象。
(4)、Redis 主从复制的性能问题,为了主从复制的速度和连接的稳定性,Slave 和 Master 最好在同一个局域网内。
3.2 Redis常见三大问题
在大多数互联网应用中,缓存的使用方式如下:
我们的查询操作在缓存上完成,而对数据的增删改则在数据库完成。
3.2.1 缓存穿透
业务系统要查询的数据根本就存在!当业务系统发起查询时,按照上述流程,首先会前往缓存中查询,由于缓存中不存在,然后再前往数据库中查询。由于该数据压根就不存在,因此数据库也返回空。这就是缓存穿透。
综上所述:业务系统访问压根就不存在的数据,就称为缓存穿透。
但是在如果大量查询数据库当中不存在的数据会怎么样呢?当然会导致数据库的崩溃。
解决方法:
它需要在缓存之前再加一道屏障,里面存储目前数据库中存在的所有key,如下图所示:
当业务系统有查询请求的时候,首先去BloomFilter中查询该key是否存在。若不存在,则说明数据库中也不存在该数据,因此缓存都不要查了,直接返回null。若存在,则继续执行后续的流程,先前往缓存中查询,缓存中没有的话再前往数据库中的查询。
这种方法适用于数据命中不高,数据相对固定实时性低(通常是数据集较大)的应用场景,代码维护较为复杂,但是缓存空间占用少。
3.2.2 缓存雪崩
缓存其实扮演了一个保护数据库的角色。它帮数据库抵挡大量的查询请求,从而避免脆弱的数据库受到伤害。
如果缓存因某种原因发生了宕机,那么原本被缓存抵挡的海量查询请求就会像疯狗一样涌向数据库。此时数据库如果抵挡不了这巨大的压力,它就会崩溃。
这就是缓存雪崩。缓存雪崩在实际当中不只会对数据库造成伤害,严重的话可能还会导致整个服务宕机,因为在具体的应用中服务可能是分布式的其中一台机器宕机可能会导致请求无法继续进行,进而导致整个服务宕机。
解决方法:
Hystrix是一款开源的“防雪崩工具”,它通过 熔断、降级、限流三个手段来降低雪崩发生后的损失。
Hystrix就是一个Java类库,它采用命令模式,每一项服务处理请求都有各自的处理器。所有的请求都要经过各自的处理器。处理器会记录当前服务的请求失败率。一旦发现当前服务的请求失败率达到预设的值,Hystrix将会拒绝随后该服务的所有请求,直接返回一个预设的结果。这就是所谓的“熔断”。当经过一段时间后,Hystrix会放行该服务的一部分请求,再次统计它的请求失败率。如果此时请求失败率符合预设值,则完全打开限流开关;如果请求失败率仍然很高,那么继续拒绝该服务的所有请求。这就是所谓的“限流”。而Hystrix向那些被拒绝的请求直接返回一个预设结果,被称为“降级”。
Hystrix是由Netflix开发的一款用于处理分布式系统之间的故障和延迟的开源库。它主要解决服务之间的依赖关系和网络调用的不可靠性,通过提供容错机制和断路器模式来提高系统的弹性和稳定性。
Hystrix的核心概念包括:
-
断路器(Circuit Breaker):Hystrix通过监控对远程服务的调用,当调用失败率超过事先设定的阈值时,会打开断路器,阻止对服务的请求,并返回预设的 fallback(回退)响应,避免继续对故障的服务进行调用,从而减少对故障服务的压力。
-
回退(Fallback):当断路器打开或远程服务调用失败时,Hystrix可以提供一个备选的回退响应,即返回一个默认值、缓存数据或执行一段备用逻辑,以保证系统的可用性,并进行适当的降级处理。
-
隔离策略(Isolation):为了提高系统的弹性和稳定性,Hystrix将每个远程服务的调用封装在独立的线程池中,以实现请求的隔离。这样可以避免一个故障服务的请求阻塞或影响其他服务。
-
超时控制(Timeout):Hystrix能够根据服务的响应时间设定超时时间,如果服务的响应时间超过设定的时间,则认为服务调用超时,触发回退逻辑。
-
实时监控和度量(Metrics):Hystrix提供了实时监控系统的功能,可以通过该功能收集和展示每个服务的调用指标、错误率、请求量、延迟等,以便于运维人员实时监控和调整服务的配置。
总结来说,Hystrix通过断路器、回退、隔离策略、超时控制和实时监控等机制,帮助开发人员构建弹性和可靠的分布式系统,使得系统能够更加优雅地处理分布式系统之间的故障和延迟。它与Spring Cloud等微服务框架结合使用,可以提供更完善的分布式系统解决方案。
3.2.3 缓存击穿
我们一般都会给缓存设定一个失效时间,过了失效时间后,该数据库会被缓存直接删除,从而一定程度上保证数据的实时性。
但是,对于一些请求量极高的热点数据而言,一旦过了有效时间,此刻将会有大量请求落在数据库上,从而可能会导致数据库崩溃。其过程如下图所示:
如果某一个热点数据失效,那么当再次有该数据的查询请求[req-1]时就会前往数据库查询。但是,从请求发往数据库,到该数据更新到缓存中的这段时间中,由于缓存中仍然没有该数据,因此这段时间内到达的查询请求都会落到数据库上,这将会对数据库造成巨大的压力。此外,当这些请求查询完成后,都会重复更新缓存。
解决方法:
加锁。
此方法只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可。
当第一个数据库查询请求发起后,就将缓存中该数据上锁;此时到达缓存的其他查询请求将无法查询该字段,从而被阻塞等待;当第一个请求完成数据库查询,并将数据更新值缓存后,释放锁;此时其他被阻塞的查询请求将可以直接从缓存中查到该数据。
当某一个热点数据失效后,只有第一个数据库查询请求发往数据库,其余所有的查询请求均被阻塞,从而保护了数据库。但是,由于采用了互斥锁,其他请求将会阻塞等待,此时系统的吞吐量将会下降。这需要结合实际的业务考虑是否允许这么做。
互斥锁可以避免某一个热点数据失效导致数据库崩溃的问题,而在实际业务中,往往会存在一批热点数据同时失效的场景。那么,对于这种场景该如何防止数据库过载呢?
设置不同的失效时间
当我们向缓存中存储这些数据的时候,可以将他们的缓存失效时间错开。这样能够避免同时失效。如:在一个基础时间上加/减一个随机数,从而将这些缓存的失效时间错开
关于缓存的三大问题参考自 缓存三大问题及解决方案大家可以去看原文,原文作者讲的更加细致。
3.3 数据库缓存数据同步问题
当我们使用缓存的时候,无可避免的会遇到这个问题。当我们在多线程的场景下遇到这个问题无论我们是先操作数据库还是操作缓存都会出现线程安全问题。
这个时候我们如何在保证CAP(一致性、效率、分区安全性)的前提下对缓存进行同步呢?
延时双删
延时指的是在数据库更改操作完成之后要等待一会,因为我们的数据库可能是主从模式或者是集群模式,读取数据的操作是在从数据库上完成的。所以我们要保证数据库的一致。
双删指的是两次删除缓存,第一次删除缓存是为了让线程直接去数据库查找,第二次删除则是为了保证数据的一致性;当我对数据库的修改并为未完成的时候数据被读回到缓存了怎么办呢?二次删除防止的就是这种情况。
当然这种方法只是保证了数据的最终一致性,如果对数据安全要求比较高的情况下,则需要通过加锁的方法,或者添加事务来解决数据一致性的问题了。也就是对CAP的取舍。