《Spring Boot 3实战案例合集》现已囊括超过60篇精选实战文章,并且此合集承诺将永久持续更新,为您带来最前沿的技术资讯与实践经验。欢迎积极订阅,享受不断升级的知识盛宴!订阅用户将特别获赠合集内所有文章的最终版MD文档(详尽学习笔记),以及完整的项目源码,助您在学习道路上畅通无阻。
环境:SpringBoot3.2.5
1. 简介
当系统选择Redis作为数据存储或缓存解决方案时,这通常是因为它具备卓越的性能。单个Redis节点能够处理高达10万QPS(每秒查询率)的流量,这样的高性能表现自然让人期待它能始终保持响应迅速。
然而,任何高性能系统都可能在使用中出现延迟,Redis也不例外。为了确保Redis能够持续发挥其高性能优势,我们必须关注如何避免操作延迟的发生。
为此,我们总结了以下11条建议,旨在帮助你在使用Redis时保持其高性能状态,这些建议将帮助你更好地利用Redis,确保其始终保持高效稳定的运行状态。
2. 优化建议
2.1 避免大Key
大key不仅占用过多内存,而且对Redis的性能也有很大影响。由于Redis是单线程处理请求的,当应用写入一个大key时,会在“内存分配”上消耗更多时间,此时操作延迟会增加。
同样地,在删除一个大key时,“内存释放”也会花费时间。此外,当你读取这个大key时,会在“网络数据传输”上花费更多时间。这时,后续待执行的请求会排队等待,导致Redis的性能下降。

当你无法避免大key时,你可以将大key进行分片存储。拆分为多个小key。
2.2 避免使用过于复杂的命令
Redis采用单线程模型处理请求。除了上面所说的大key在处理请求时会出现排队情况外,执行复杂度过高的命令时也会出现这种情况。
因为执行复杂度过高的命令会消耗更多的CPU资源,而主线程中的其他请求只能等待,此时就会发生排队延迟。
因此,我们应该避免执行诸如SORT、SINTER、SINTERSTORE、ZUNIONSTORE和ZINTERSTORE等聚合命令。对于这类聚合操作,我建议在客户端执行,不要让Redis承担过多的计算工作。
不论是Redis还是DB,我们都应该多加考虑下,是否在程序中处理性能反而会有所提升呢?
2.3 DEL命令时间复杂度问题
在删除键时,如果操作不当,也可能会影响Redis的性能。在删除键时,我们通常使用DEL命令。回想一下,你认为DEL命令的时间复杂度是多少?
O(1)?实际上,未必如此。
当你删除一个String类型的键时,时间复杂度确实是O(1)。但是,当你要删除的键是List/Hash/Set/ZSet类型时,其复杂度实际上是O(N),其中N代表元素的数量。也就是说,在删除键时,元素越多,DEL的执行速度就越慢!
原因是,在删除大量元素时,需要依次回收每个元素的内存。元素越多,所需时间就越长!而且,这个过程默认在主线程中执行,这不可避免地会阻塞主线程,导致性能问题。
那么,我们应该如何处理删除包含较多元素的键呢?
List类型:多次执行LPOP/RPOP,直到所有元素都被删除。
Hash/Set/ZSet类型:首先执行HSCAN/SSCAN/SCAN查询元素,然后依次执行HDEL/SREM/ZREM删除每个元素。
更优的解决方案
对于包含较多元素的键,更好的做法可能是使用
UNLINK命令代替DEL。UNLINK命令在Redis 4.0版本引入,它的行为类似于DEL,但它将实际的内存回收工作放到后台线程中进行,因此不会阻塞主线程。这样可以在不影响其他操作的情况下异步地释放内存。如果你需要渐进式地删除元素,比如为了控制对性能的影响,可以考虑结合
SCAN命令来分批获取并删除元素,或者使用Lua脚本来批量处理,不过这仍然会在一定程度上影响性能,并且可能不如UNLINK那样直接有效。
2.4 启用惰性释放机制
如果你无法避免存储大键,那么建议你启用Redis的惰性释放机制。(该机制在4.0及以上版本中受支持。)
当启用此机制时,Redis在删除一个大key时,释放内存的耗时操作将在后台线程中执行,这样可以最大程度地避免对主线程的影响。

开启方式:
# 是否在内存淘汰时使用惰性释放。lazyfree-lazy-eviction yes# 是否在键过期时使用惰性释放lazyfree-lazy-expire yes# 是否在服务器端删除键时使用惰性释放lazyfree-lazy-server-del yes
注意:启用惰性释放机制可以减少大键删除或过期时对Redis主线程的阻塞,但也可能导致内存释放不及时。因此,在使用时需要权衡利弊。
2.5 执行O(N)命令时,要注意N的大小
是不是避免了使用复杂度过高的命令就可以高枕无忧了呢?答案是否定的。
当你执行O(N)命令时,同样需要注意N的大小。如果你一次性查询过多数据,在网络传输过程中也会耗费很长时间,增加操作延迟。
因此,对于容器类型(List/Hash/Set/ZSet),当元素数量未知时,切勿盲目执行如LRANGE key 0 -1、HGETALL、SMEMBERS、ZRANGE key 0 -1等命令。
在查询数据时,应遵循以下原则:
首先查询数据元素的数量(涉及命令:LLEN、HLEN、SCARD、ZCARD)
如果元素数量较少,可以一次性查询所有数据
如果元素数量非常多,则应分批查询数据(涉及命令:LRANGE、HSCAN、SSCAN、ZSCAN)
2.6 使用批量命令
当你需要同时操作多个key时,应该使用批量命令来处理。与多个单独操作相比,批量操作的优势在于可以显著减少客户端与服务器之间网络I/O的往返次数。
通常采用如下建议:
对于String/Hash类型,使用MGET/MSET替代GET/SET,以及HMGET/HMSET替代HGET/HSET。
对于其他数据类型,使用Pipeline将多个命令打包并一次性发送给服务器执行。

如下,在Spring Boot中应用管道进行批量操作:
public class RedisPipeliningService {private final StringRedisTemplate stringRedisTemplate ;public RedisPipeliningService(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate ;}public List<Object> pipe() {List<Object> results = stringRedisTemplate.executePipelined(new RedisCallback<Object>() {public Object doInRedis(RedisConnection connection) throws DataAccessException {StringRedisConnection stringRedisConn = (StringRedisConnection)connection;for(int i = 0; i < 10; i++) {stringRedisConn.rPush("i" + i, String.valueOf(i)) ;}return null;}});return results ;}}
一次IO发送多条命令。
2.7 避免Key同时过期
Redis以定时和惰性的方式清理过期键,并且这个过程是在主线程中执行的。如果你的业务中有大量的键集中过期,那么在Redis清理这些过期键时,也存在阻塞主线程的风险,还很可能出现雪崩。
为了避免这种情况,在设置过期时间时,你可以添加一个随机时间,以分散这些键的过期时间,从而减少集中过期对主线程的影响。
2.8 使用长连接&合理配置连接池
你的业务应该使用长连接来操作Redis,避免使用短连接。在使用短连接操作Redis时,每次都需要进行TCP三次握手和四次挥手,这个过程会增加操作的时间消耗。
同时,你的客户端应该以连接池的方式访问Redis,并设置合理的参数。当长时间不操作Redis时,应及时释放连接资源。
如下基于 Spring Boot 配置的Redis连接池:
spring:data:redis:host: localhostport: 6379: xxxooo: 10000timeout: 10000lettuce:pool:: -1: 8: 8: 8
根据业务场景合理的配置连接池大小 & 超时配置。
2.9 只使用DB 0
尽管Redis提供了16个数据库,但仅推荐你使用db 0。为什么呢?总结了以下三个原因:
当在一个连接上操作多个数据库的数据时,你每次都需要先执行SELECT命令,这会给Redis带来额外的压力。
使用多个数据库的目的是根据不同的业务线存储数据。那么,为什么不将它们拆分并存储在多个实例中呢?部署多个实例并拆分存储,可以使多条业务线互不影响,同时还能提高Redis的访问性能。
Redis Cluster仅支持db 0。如果你将来想迁移到Redis Cluster,这将增加迁移成本。
2.10 使用读写分离+分片集群
如果你的业务的读请求量非常大,那么你可以部署多个从数据库来实现读写分离,让Redis的从数据库共同分担读压力,从而提高性能。

如果你的业务的写请求量非常大,单个Redis实例已经无法再支持如此大的写流量,那么此时你需要使用分片集群来分担写压力。

2.11 AOF持久化策略
对于对数据丢失不敏感的业务,建议不要启用AOF,以避免AOF写入磁盘导致的Redis性能下降。
如果确实需要启用AOF,那么建议你将其配置为appendfsync everysec(每秒同步一次),并将数据持久化的磁盘刷新操作放入后台线程执行,以最大限度地减少对Redis写入磁盘时性能的影响。
以上是本篇文章的全部内容,如对你有帮助帮忙点赞+转发+收藏
推荐文章
高级开发!Spring Boot 自定义SQL日志记录(包括, 参数,耗时),支持MyBatis,JPA等
太强了!SpringBoot结合Camel实现各种协议路由规则定义
查漏补缺!OpenFeign整合Resilience4j,你真的会用吗?
Jackson在Spring Boot高级应用技巧【Long精度丢失, @JsonValue, 数据脱敏】
性能提升!@Async与CompletableFuture优雅应用





