Author: liuchao
email: mirschao@gmail.com
github: https://github.com/mirschao
gitee: https://gitee.com/mirschao
Redis(Remote Dictionary Server)是一款开源的高性能键值对存储数据库。最初由 Salvatore Sanfilippo 开发,它以其在内存中存储数据并提供持久化功能的特点而闻名。作为一种NoSQL(非关系型数据库),Redis可以将数据持久保存在磁盘中,为应用程序提供了快速且可靠的数据存储解决方案。
Redis的数据结构非常丰富,它包括了多种基础数据类型,例如:
字符串(String):用于存储文本或二进制数据
列表(List):基于双向链表实现的数据结构,适用于存储有序的元素列表
散列(Hash):存储了键值对集合,适用于存储对象的各个属性
集合(Set):存储了不重复元素的无序集合,适用于标签、标记等场景
有序集合(Sorted Set):与集合类似,但每个元素都有一个分数,可以用于排行榜等应用
这些丰富的数据结构使得Redis不仅仅是简单的键值存储,还可以用于存储和处理更加复杂的数据。无论是缓存、会话存储、实时分析还是排行榜,Redis都在各种场景下发挥着重要作用
Redis以其高性能、丰富的数据结构和多种特性在众多领域拥有广泛的应用。以下是它的主要应用场景:
缓存:作为缓存数据库,Redis将频繁访问的数据存储在内存中,从而避免了频繁的数据库读写操作,显著提高了应用的响应速度。它可以用来存储热点数据,减轻后端数据库的负担,进而提升整体系统的吞吐量。
计数器:Redis的原子性操作使其成为实现高效计数器的理想选择,如网站的点赞、浏览次数等统计功能。由于Redis的原子性,计数器的更新可以并发执行,而不会引发竞争问题。
消息队列:Redis的发布-订阅功能和列表数据结构可以用来构建简单的消息队列,以实现系统各模块之间的解耦。生产者发布消息到指定频道,消费者订阅感兴趣的频道并处理消息,从而实现异步消息传递。
排行榜:利用有序集合数据结构,可以轻松实现排行榜功能,如游戏中的玩家排名。通过有序集合的分数属性,可以对玩家得分进行排序和排名,实现实时的排行榜展示。
会话缓存:在Web应用中,Redis可用于存储用户会话数据,实现分布式会话管理。用户登录后,可以将会话数据存储在Redis中,实现多台服务器之间的会话共享,从而提高用户体验。
Redis高性能键值数据库第一章 非关系型数据库Redis部署及配置1.1 Yum方式部署Redis服务1.2 源码方式部署Redis服务1.3 redis的常用配置识别第二章 redis的数据结构类型及使用场景2.1 字符串(strings)类型 及 哈希类型2.2 设定键的超时时间2.3 列表类型(list)搞定队列使用第三章 redis的数据持久化3.1 AOF RDB模式概述3.2 AOF模式3.3 RDB模式第四章 redis的哨兵模式及集群模式4.1 哨兵模式构建4.2 集群模式构建第五章 redis 常见问题处理5.1 雪崩5.2 击穿5.3 穿透附录:a. 缓存预加载python脚本
Redis的历史可以追溯到2009年,以下是关于Redis历史的一些重要里程碑:
2009年:Redis项目由Salvatore Sanfilippo开始开发。旨在创建一个支持不同数据结构的高性能内存数据库,以满足他在另一个项目中的需求。
2010年:Redis的第一个稳定版本(1.0)于2010年3月发布。这个版本包含了基本的数据结构(字符串、列表、集合、有序集合和散列)以及一些简单的持久化功能
2012年:Redis 2.6版本引入了虚拟内存支持,允许用户扩展数据库容量,而不必将所有数据放在物理内存中
2013年:Redis 2.8版本引入了非常重要的特性——发布-订阅(Pub/Sub)模式,允许客户端订阅频道并接收实时消息
2014年:Redis 3.0版本发布,引入了集群模式,使得Redis能够以分布式方式工作,提高了可用性和可扩展性
2016年:Redis 4.0版本发布,带来了许多重要的改进,包括更好的持久化、多线程、GeoHash地理空间索引等功能
2019年:Redis 5.0版本发布,引入了诸多新特性,如流数据类型、延迟队列、模块系统等
2021年:随着时间的推移,Redis继续保持活跃的开发和社区支持。社区不断提出建议和贡献,使Redis保持在NoSQL数据库领域的领先地位
总体来说,Redis自从诞生以来,不断地通过不断的版本迭代和创新,逐渐成为了一个强大而多功能的数据存储解决方案,广泛应用于各种领域,包括缓存、实时分析、会话管理等。它的演变也体现了数据库技术在应对不同需求和挑战时的进步和变革
x#> 安装 epel-release
$ yum -y install epel-release
#> 安装 redis
$ yum -y install redis
#> 启动redis
$ systemctl enable --now redis
xxxxxxxxxx
#> 下载Redis源码安装脚本
$ wget https://mirrors.qfcc.online/scripts/redis/installer.sh
#> 执行脚本
$ VERSION='6.2.6'
$ bash installer.sh ${VERSION}
#> 等待安装完成即可
xxxxxxxxxx
bind 127.0.0.1 # 监听地址
port 6379 # 监听的端口
daemonize yes # 是否后台运行
requirepass "这里设定连接redis的密码" # 设置redis的连接密码
tcp-backlog 511 # tcp半队列长度: 相当于已经建立tcp的连接数+未完成tcp连接的数量 x=<50000 即可
timeout 0 # 设置空闲连接超时时间 0标识关闭该功能
tcp-keepalive 300 # 设置心跳检测确保服务在线, 建议设置为 60s
pidfile /var/run/redis.pid # 设置redis进程的pid存储位置
loglevel notice # 设置日志级别: debug(打印所有运行信息, 调试使用, 包含debug信息)
: verbose(打印除了debug信息外的所有信息)
: notice(只打印运行日志)
: warning(只打印重要的信息, 比如报错)
logfile /var/log/redis.log # 设定redis的日志存储位置, 如果不填则直接输出到/dev/null中
maxclient 10000 # 客户端最大连接数
maxmemory 16106127360 # 设置最大内存 单位 1byte=8bit 1kb=1024byte 1mb=1024kb 1g=1024mb
maxmemory-policy noeviction # 设定达到最大内存时的策略: noeviction 表示永不删除key
: allkeys-lru 根据lru算法淘汰key
: volatile-ttl 根据ttl时间将即将淘汰的key删掉
maxmemory-simples 5 # 设定内存淘汰的样本采集队列长度
它支持多种不同的数据类型,每种数据类型都有特定的用途和优势。以下是Redis的主要数据类型:
字符串(Strings): 最基本的数据类型,可以存储文本、数字或二进制数据。字符串可以包含任何类型的数据,例如用户会话信息、计数器等。
哈希(Hashes): 哈希类型用于存储具有字段-值对的数据。类似于关联数组,适用于存储对象的属性,如用户信息、文章内容等。
列表(Lists): 列表是一个有序的字符串元素集合,可以在两端进行插入和删除操作。适用于实现队列、栈等数据结构。
集合(Sets): 集合是无序的唯一元素集合,不允许重复值。常用于存储用户标签、好友关系等。
有序集合(Sorted Sets): 有序集合类似于集合,但每个元素都关联一个分数,用于排序。适用于排行榜、优先级队列等场景。
位图(Bitmaps): 位图是由二进制位组成的数据类型,支持位操作。可以用于记录用户的在线状态、权限控制等。
HyperLogLog: 用于基数估算的数据结构,可以估计集合中唯一元素的数量,适用于统计、计数等应用。
地理空间索引(Geospatial Indexes): 用于存储地理位置信息,并支持查询附近位置的元素。适用于地理位置服务、附近商家等应用。
在Redis中,字符串(Strings)是最基本的数据类型之一,它可以存储文本、二进制数据或数字。每个字符串值可以最大存储512MB的数据。字符串类型支持一系列操作,如设置、获取、追加等,使其在多种应用场景下都能发挥重要作用。
字符串类型的常见操作:
SET key value
: 设置指定键的值为给定的字符串。
GET key
: 获取指定键的值。
APPEND key value
: 在键的值后面追加给定的字符串。
STRLEN key
: 获取键的值的长度。
使用场景示例:用户会话管理
一个常见的使用场景是用户会话管理。在Web应用中用户登录后,通常需要存储一些与用户会话相关的信息,例如用户ID、权限、登录时间等。这些信息可以存储在Redis中的字符串类型中,以便快速访问和更新。
假设有一个在线商城,用户登录后需要保持其会话状态,下面是一个简化的例子:
xxxxxxxxxx
#> 设定用户登陆信息
> SET user:123:session "user_info:123"
#> 存储用户的hash值
> HSET user_info:123 id 123
> HSET user_info:123 username "alice"
> HSET user_info:123 role "customer"
#> 获取用户的信息
> GET user:123:session
#> 获取用户会话的详细信息
> HGETALL user_info:123
在这个场景中,字符串类型用于存储用户会话信息的引用,实际的用户信息则以哈希类型存储。这种方式可以有效地管理用户会话状态,同时在需要时快速地获取和更新用户信息。
EXPIRE key seconds:设置一个键的过期时间,单位为秒。当键的过期时间到达后,该键会被自动删除
xxxxxxxxxx
SET my_key "Hello"
EXPIRE my_key 60 # 设置my_key在60秒后过期
在Redis中,列表(Lists)是一种有序的字符串元素集合,它允许在列表的两端进行插入和删除操作。列表类型常用于实现队列、栈、消息发布与订阅等场景,特别适合存储一系列有序的数据。
列表类型的常见操作:
LPUSH key value [value ...]
:在列表左侧插入一个或多个值
RPUSH key value [value ...]
:在列表右侧插入一个或多个值
LPOP key
:移除并返回列表左侧的元素
RPOP key
:移除并返回列表右侧的元素
LRANGE key start stop
:获取列表中指定范围的元素
LLEN key
:获取列表的长度
使用场景示例:任务队列
一个常见的使用场景是构建任务队列,用于处理异步任务,例如在Web应用中后台处理耗时的操作,如发送电子邮件或生成报告。列表类型可以很好地支持这种异步任务处理模式。
xxxxxxxxxx
#> 将任务添加到队列
LPUSH task_queue "generate_report_123"
LPUSH task_queue "send_email_456"
#> 获取待处理任务
RPOP task_queue
> 然后去处理获取到的数据
#> 再次获取任务
RPOP task_queue
在这个场景中,列表类型用于存储待处理的任务。通过在列表的左侧插入任务,你可以确保先插入的任务先被处理,形成一个FIFO(先进先出)的处理队列。在任务处理完成后,你可以从列表的右侧获取下一个待处理任务。这种方式可以有效地实现后台任务的异步处理,避免阻塞主要应用线程
Redis是一种高性能的内存数据存储系统,常用于缓存、会话存储以及实时数据分析等场景。由于其数据存储在内存中,一旦服务器重启或崩溃,内存中的数据将会丢失。为了解决这个问题,Redis提供了数据持久化机制,使数据可以在重启或崩溃后恢复。
Redis支持两种主要的数据持久化方式:
RDB快照持久化: 这种方式会周期性地将内存中的数据生成一个快照(snapshot)并存储在磁盘上。快照是一个二进制文件,保存了某一时刻的数据库状态。你可以通过配置文件来设置生成快照的频率。RDB快照持久化适用于备份、灾难恢复以及在内存数据变动较少时使用,因为生成快照可能会对性能产生一些影响
AOF日志持久化: AOF(Append-Only File)持久化方式会记录每次对Redis数据进行修改的操作,将这些操作以日志的形式追加到一个文件中。这样,在重启时,Redis可以通过重新执行这些操作来恢复数据状态。AOF持久化相对于RDB方式更加实时,因为它记录了每一次修改操作,但文件可能会变得较大。Redis提供了不同的AOF持久化策略,如每秒同步、每次写入等,可以根据需要进行配置
可以根据业务需求来选择RDB持久化、AOF持久化或两者结合使用。为了更好的数据安全性,也可以将两者同时启用。要注意的是,持久化机制虽然能够提供数据恢复的能力,但并不能替代正式的备份和灾难恢复策略。在Redis中,你可以通过配置文件来调整持久化相关的设置,包括持久化方式、频率、文件路径等。持久化的配置参数可以在Redis的配置文件中找到并进行修改
使用AOF(Append-Only File)模式对Redis进行数据持久化,可以通过以下流程和操作步骤完成:
配置AOF持久化: 首先,你需要确保Redis的配置文件中正确配置了AOF持久化选项。打开Redis的配置文件(通常是redis.conf
)找到以下相关配置项并进行设置:
xxxxxxxxxx
appendonly yes # 启用AOF持久化
appendfilename "appendonly.aof" # AOF日志文件的名称
此外,你还可以根据需要设置AOF持久化的其他选项,如appendfsync
用于控制何时将AOF日志写入磁盘。
执行写操作: Redis会将每次对数据进行修改的操作追加到AOF日志文件中。在正常运行期间,Redis会自动将写操作添加到AOF日志中。只要发生对数据的写操作(例如SET、INCR等),相应的操作会被追加到AOF日志中。
手动触发AOF持久化: 虽然Redis会自动将写操作追加到AOF日志中,但你也可以通过执行以下命令手动触发AOF持久化:
xxxxxxxxxx
BGREWRITEAOF # 在后台执行AOF重写,优化AOF日志文件大小
BGREWRITEAOF
命令会在后台执行AOF日志文件的重写,去除其中的冗余操作,从而优化AOF文件的大小。这个操作并不影响正在运行的Redis服务器。
恢复数据: 当Redis服务器启动时,它会加载AOF日志文件并依次执行其中的操作,从而将数据恢复到最近的状态。AOF日志中的写操作会按顺序被重新执行,将数据还原到原始状态。
需要注意的是,AOF持久化记录了每次写操作,因此它提供了更精确的数据恢复能力。但由于AOF日志文件可能会变得较大,Redis提供了不同的appendfsync
选项,允许你在数据安全性和性能之间做出权衡选择。定期执行AOF重写可以帮助缩小AOF文件的大小,从而保持良好的性能。
总的来说,AOF持久化提供了更实时和精确的数据持久化能力,但也需要考虑合适的配置和管理以避免性能和存储问题。
使用RDB(Redis DataBase)模式对Redis进行数据持久化涉及以下流程和操作步骤:
配置RDB持久化: 首先,你需要确保Redis的配置文件中正确配置了RDB持久化选项。打开Redis的配置文件(通常是redis.conf
)找到以下相关配置项并进行设置:
xxxxxxxxxx
save 900 1 # 在900秒(15分钟)内,如果至少有1个key发生变化,则生成快照
save 300 10 # 在300秒(5分钟)内,如果至少有10个key发生变化,则生成快照
save 60 10000 # 在60秒内,如果至少有10000个key发生变化,则生成快照
这些配置项表示了RDB持久化的触发条件,即在指定的时间间隔内,如果满足条件(至少有指定数量的key发生变化),就会生成一个快照。
手动触发RDB持久化: 除了根据配置项的条件触发RDB持久化外,你还可以通过执行命令手动触发生成RDB快照。在Redis的命令行界面或通过客户端工具,执行以下命令:
xxxxxxxxxx
SAVE # 执行SAVE命令生成RDB快照
BGSAVE # 执行BGSAVE命令在后台生成RDB快照
SAVE
命令会阻塞Redis服务器,直到生成完整的RDB快照为止,可能会对性能产生较大影响。而BGSAVE
命令会在后台生成RDB快照,不会阻塞服务器。
生成RDB快照: 当RDB持久化触发时,Redis会将当前内存中的数据状态保存到一个二进制文件中,该文件的默认命名格式是dump.rdb
。生成的RDB快照文件会保存在Redis配置中指定的目录中。
恢复数据: 如果Redis服务器在重启或崩溃后需要恢复数据,它会读取最近生成的RDB快照文件。Redis会读取该文件并将数据恢复到内存中,从而使服务器恢复到最近一次生成快照时的状态。
需要注意的是,虽然RDB持久化提供了数据恢复的能力,但在某些情况下,因为持久化间隔可能较长,可能会导致数据的一些丢失。因此,根据业务需求,你可以适当调整RDB持久化的配置参数,以及结合其他备份和恢复策略来保障数据的安全性。
Redis的哨兵模式(Sentinel)是一种用于实现高可用性的机制,它可以监控Redis主从集群中的状态,并在主节点失效时自动进行故障转移,确保系统的稳定运行。在一个Redis主从集群中,哨兵模式允许你设置一组哨兵进程,它们会定期检查主节点和从节点的状态。如果主节点发生故障,哨兵会协调进行故障转移操作,将一个从节点提升为新的主节点,然后通知其他节点更新配置,以确保系统的高可用性。这使得系统可以自动适应主节点故障的情况,而无需手动干预。
安装Redis Sentinel:首先确保你的系统中安装了Redis Sentinel。它是一个单独的可执行文件,用于运行哨兵进程
配置哨兵:创建一个哨兵配置文件sentinel.conf
,在文件中指定监控的主节点和其他配置。重要的配置项包括监控的主节点地址和端口,以及要保持的从节点数量等
启动哨兵:运行哨兵进程,使用命令类似于 redis-sentinel /path/to/sentinel.conf
故障转移设置:在哨兵配置中,你可以设置何时触发故障转移以及新主节点的选举规则
监控和管理:通过哨兵提供的命令和状态信息,你可以监控主从集群的状态,查看故障转移日志,手动触发故障转移等
xxxxxxxxxx
[root@redis-service-a ~]# /usr/local/redis/bin/redis-cli -h 192.168.153.22 -p 6379
192.168.153.22:6379> SLAVEOF 192.168.153.21 6379
OK
192.168.153.22:6379> exit
[root@redis-service-a ~]# /usr/local/redis/bin/redis-cli -h 192.168.153.23 -p 6379
192.168.153.23:6379> SLAVEOF 192.168.153.21 6379
OK
[root@redis-service-a ~]# vim /usr/local/redis/etc/sentinel.conf
sentinel monitor mymaster 192.168.153.21 6379 2
[root@redis-service-a ~]# /usr/local/redis/bin/redis-sentinel /usr/local/redis/etc/sentinel.conf
[root@redis-service-b ~]# vim /usr/local/redis/etc/sentinel.conf
sentinel monitor mymaster 192.168.153.21 6379 2
[root@redis-service-b ~]# /usr/local/redis/bin/redis-sentinel /usr/local/redis/etc/sentinel.conf
[root@redis-server-c ~]# vim /usr/local/redis/etc/sentinel.conf
sentinel monitor mymaster 192.168.153.21 6379 2
[root@redis-service-c ~]# /usr/local/redis/bin/redis-sentinel /usr/local/redis/etc/sentinel.conf
Redis Cluster是Redis数据库的分布式解决方案,用于在多个节点之间分布和管理数据,以实现高可用性和横向扩展。它允许将数据分散存储在多个节点上,以提供更大的存储容量和吞吐量,并在节点故障时保持数据的可用性。以下是关于Redis Cluster的一些介绍:
特点和优势:
分布式架构: Redis Cluster采用分布式架构,将数据分散存储在多个节点上,允许扩展到多个机器以应对大规模数据和高并发请求
高可用性: Redis Cluster在主从复制的基础上,使用了自动分片和数据复制来实现高可用性。当一个主节点发生故障时,系统会自动将一个从节点提升为新的主节点,以确保数据的可用性
自动分片: Redis Cluster将数据分成多个分片(slots),每个分片可以存储不同的数据。每个节点负责管理一部分分片,从而实现数据的分布存储
故障转移: 当主节点不可用时,Redis Cluster会自动进行故障转移,将一个从节点提升为新的主节点,以确保数据的连续可用性
内置复制: Redis Cluster支持主从复制,每个主节点都可以有多个从节点,从而实现数据的冗余和读扩展
工作原理:
分片: Redis Cluster将整个数据集分成16384个槽(0-16383),每个节点负责管理其中一部分槽
节点发现: 客户端和节点之间的交互是通过节点发现进行的。客户端通过向一个或多个节点发送消息来获取关于集群拓扑的信息
数据迁移: 当增加或删除节点时,Redis Cluster会自动进行数据迁移,将槽从一个节点迁移到另一个节点
故障检测和转移: 哨兵机制会定期检测节点的状态,并在主节点故障时进行自动故障转移
xxxxxxxxxx
$ vim /usr/local/redis/etc/redis.conf # 增加如下配置
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 15000
$ /usr/local/redis/bin/redis-cli --cluster create 192.168.153.21:6379 192.168.153.22:6379 192.168.153.23:6379 192.168.153.24:6379 192.168.153.25:6379 192.168.153.26:6379 --cluster-replicas 1
$ /usr/local/redis/bin/redis-cli -h 192.168.153.21 -p 6379
192.168.153.21:6379> set hello worlds
OK
$ /usr/local/redis/bin/redis-cli -c -h 192.168.153.23 -p 6379
192.168.153.23:6379> get hello
-> Redirected to slot [866] located at 192.168.153.21:6379
"worlds"
Redis雪崩是指在缓存中的大量数据同时过期或失效,导致大量请求直接访问数据库,造成数据库负载骤增,从而引发系统性能问题甚至宕机。主要原因包括:
缓存过期: 当一批缓存数据在相同时间窗口内过期,未命中的请求会直接访问数据库,引发大量请求压力
重启: 当Redis服务发生重启,所有缓存数据都会被清空,导致大量请求击穿缓存,直接访问数据库
高并发: 在高并发环境下,大量请求同时涌入,如果缓存失效或过期,会导致瞬间的数据库请求激增
Redis雪崩可能发生在以下场景中:
热点数据失效: 系统中某些热门数据过期,导致大量请求同时访问数据库
大规模缓存批量失效: 缓存层出现问题,导致大量缓存数据同时失效
分布式系统中的故障: 在分布式环境中,一个或多个节点宕机或网络异常,导致缓存失效
为了应对Redis雪崩问题,可以采取以下多种解决方案:
设置随机过期时间: 针对缓存数据设置随机的过期时间,避免大量数据同时失效。可以在原始过期时间上加上一个小的随机偏移量
使用二级缓存: 引入一个二级缓存,避免全部依赖Redis。即使Redis失效,仍然可以从二级缓存中获取部分数据
缓存预热: 在系统启动阶段,提前加载热门数据到缓存中,避免大量请求同时访问数据库
限流和熔断: 对于高并发请求,引入限流和熔断机制,控制请求的并发量,避免突发大量请求导致缓存失效
异步更新缓存: 在缓存失效时,采用异步方式来更新缓存,避免阻塞请求并减轻数据库压力
以设置随机的过期时间为例, 并使用python代码进行逻辑演示
xxxxxxxxxx
import redis
import time
import threading
# 连接Redis
r = redis.Redis(host='localhost', port=6379, db=0)
# 用于模拟异步更新缓存的后台线程
def async_update_cache(key, value, expire_time):
time.sleep(expire_time) # 模拟后台更新时间
print(f"Updating cache for key '{key}' with value '{value}'")
def set_cached_data_with_random_expiration(key, value, base_expire_time):
# 计算随机偏移量
random_offset = random.randint(1, 10) # 假设随机范围为1到10秒
random_expire_time = base_expire_time + random_offset
# 设置缓存
r.setex(key, random_expire_time, value)
print(f"Cache set for key '{key}' with value '{value}' and random expiration {random_expire_time} seconds")
# 检测数据是否即将过期,触发异步更新缓存
current_expire_time = r.ttl(key)
if current_expire_time > 0:
update_thread = threading.Thread(target=async_update_cache, args=(key, value, current_expire_time))
update_thread.start()
def main():
key = "my_key"
value = "my_value"
base_expire_time = 60 # 基础过期时间,例如60秒
set_cached_data_with_random_expiration(key, value, base_expire_time)
# 模拟获取缓存数据
cached_value = r.get(key)
print(f"Retrieved cached value: {cached_value}")
if __name__ == "__main__":
main()
Redis缓存击穿是指在高并发情况下,一个或多个请求查询一个不存在于缓存中但存在于数据库中的数据,导致大量请求直接访问数据库,造成数据库负载骤增,从而引发系统性能问题。主要原因包括:
热点数据失效: 系统中某些热门数据在缓存中过期,导致大量请求同时访问数据库
恶意请求: 恶意用户或攻击者通过发送大量不存在于缓存中的请求,引发数据库请求激增
首次访问: 数据首次被访问时,在缓存中不存在,导致请求直接访问数据库
大量并发请求: 在高并发环境下,大量请求同时查询不存在于缓存中的数据
为了应对Redis缓存击穿问题,可以采取以下多种解决方案:
缓存预热: 在系统启动阶段,提前加载热门数据到缓存中,避免在高并发时数据不存在于缓存中
布隆过滤器: 使用布隆过滤器判断请求的数据是否存在于数据库中,避免无效的数据库查询
互斥锁: 发现缓存中不存在数据时,用互斥锁阻止多个请求同时访问数据库,一个请求去数据库查询,其他请求等待结果
空值缓存: 当数据库中的数据为空时,也将空值缓存一段时间,避免大量请求反复查询数据库
以互斥锁为例, 并使用python来演示后端的代码逻辑
xxxxxxxxxx
import redis
import threading
# 创建Redis连接
r = redis.Redis(host='localhost', port=6379, db=0)
# 互斥锁
cache_lock = threading.Lock()
def get_data_from_cache(key):
return r.get(key)
def fetch_data_from_database(key):
# 模拟从数据库获取数据
data = "data_from_database" # 实际应用中应该从数据库中获取
# 将数据存入缓存
r.setex(key, 3600, data) # 假设数据缓存1小时
return data
def get_data(key):
# 检查缓存
cached_data = get_data_from_cache(key)
if cached_data:
return cached_data
# 缓存不存在,加锁
with cache_lock:
# 再次检查缓存,因为可能其他线程已经更新缓存
cached_data = get_data_from_cache(key)
if cached_data:
return cached_data
# 查询数据库
data = fetch_data_from_database(key)
return data
def main():
key = "my_key"
result = get_data(key)
print("Result:", result)
if __name__ == "__main__":
main()
Redis穿透是指恶意用户或攻击者通过发送请求查询一个不存在于缓存和数据库中的数据,导致大量请求直接访问数据库,造成数据库负载骤增,从而引发系统性能问题。主要原因包括:
Redis穿透可能发生在以下场景中:
不存在的数据: 恶意用户发送大量请求查询根本不存在的数据,导致数据库直接查询
特殊字符请求: 恶意用户通过发送特殊字符的请求,绕过缓存查询直接访问数据库
为了应对Redis穿透问题,可以采取以下多种解决方案:
布隆过滤器: 使用布隆过滤器判断请求的数据是否可能存在于数据库中,从而避免无效的数据库查询
缓存空值: 将不存在于数据库中的数据也缓存一段时间,这样可以避免频繁查询数据库
请求合法性检查: 在缓存层或应用层对请求进行合法性检查,过滤掉无效请求
这里以部署 布隆过滤器为例, 使用python演示逻辑
xxxxxxxxxx
import redis
import hashlib
from bitarray import bitarray
# 创建布隆过滤器
class BloomFilter:
def __init__(self, size, hash_count):
self.size = size
self.hash_count = hash_count
self.bit_array = bitarray(size)
self.bit_array.setall(0)
def add(self, item):
for seed in range(self.hash_count):
index = self._hash(item, seed) % self.size
self.bit_array[index] = 1
def contains(self, item):
for seed in range(self.hash_count):
index = self._hash(item, seed) % self.size
if self.bit_array[index] == 0:
return False
return True
def _hash(self, item, seed):
return int(hashlib.sha256(str(item + seed).encode()).hexdigest(), 16)
# 连接Redis
r = redis.Redis(host='localhost', port=6379, db=0)
# 创建布隆过滤器
bloom_filter = BloomFilter(size=1000, hash_count=5)
# 添加数据到布隆过滤器
bloom_filter.add("data1")
bloom_filter.add("data2")
def check_cache_or_query_db(data):
if bloom_filter.contains(data):
cached_data = r.get(data)
if cached_data:
return cached_data
else:
db_data = f"db_data_for_{data}"
r.setex(data, 60, db_data) # 缓存60秒
return db_data
else:
return "Data not found"
def main():
query_data = "data1"
result = check_cache_or_query_db(query_data)
print("Result:", result)
if __name__ == "__main__":
main()
xxxxxxxxxx
import redis
import pymysql
# 连接Redis
r = redis.Redis(host='localhost', port=6379, db=0)
# MySQL数据库连接配置
mysql_config = {
'host': 'localhost',
'user': 'your_username',
'password': 'your_password',
'db': 'your_database',
'cursorclass': pymysql.cursors.DictCursor
}
# 要加载到缓存的表格列表
tables_to_load = ['table1', 'table2', 'table3']
def fetch_data_from_mysql(table):
try:
connection = pymysql.connect(**mysql_config)
cursor = connection.cursor()
# 执行查询语句,获取数据
query = f"SELECT * FROM {table}"
cursor.execute(query)
data = cursor.fetchall()
cursor.close()
connection.close()
return data
except pymysql.Error as err:
print("MySQL Error:", err)
return []
def preload_cache_from_mysql():
for table in tables_to_load:
data_from_mysql = fetch_data_from_mysql(table)
for row in data_from_mysql:
key = str(row['id']) # 使用合适的键字段名
value = row['name'] # 使用合适的值字段名
r.set(key, value)
print(f"Preloaded key: '{key}' with value: '{value}' from table: '{table}'")
def main():
preload_cache_from_mysql()
print("Cache preloading completed.")
if __name__ == "__main__":
main()