关键词:
Redis 是一个开源的高性能的 Key-Value 服务器。本篇主要介绍一下缓存的设计与优化。
1. 缓存的受益与成本
- | 说明 |
---|---|
缓存的受益 | 1、加速读写,通过缓存加速读写速度,例如 CPU L1/L2/L3 Cache、Linux page Cache 加速硬盘读写、浏览器缓存、Ehcache 缓存数据库结果; 2、降低后端负载,后端服务器通过前端缓存降低负载,业务端使用 Redis 降低后端 MySQL 负载等。 |
缓存的成本 | 1、数据不一致,缓存和数据层有时间窗口不一致,和更新策略有关; 2、代码维护成本增加,多了一层缓存逻辑; 3、运维成本增加。 |
缓存的使用场景:
- 降低后端负载,对高消耗的 SQL,例如 join 结果集/分组统计结果缓存;
- 加速请求响应,利用 Redis/Memcache 优化 IO 响应时间;
- 大量写合并为批量写,例如计数器先 Redis 累加再批量写 DB。
2.单线程架构
Redis 在一个同一时间点只会执行一条命令。
大多情况下,单线程是非常慢的。Redis 单线程架构为什么这么快?
- 主要原因:纯内存;
- 非阻塞 IO,Redis 使用 Event Loop 这样的模型作为 IO 多路复用的实现,并且 Redis 自身实现了一个事件处理,将 Event Loop 连接、读写、关闭转换为自身的一个事件,不再往 IO 上浪费过多时间;
- 避免线程切换和竞态消耗;
单线程架构要注意什么?
- 一次只运行一条命令;
- 拒绝长(慢)命令,例如 keys、flushall、flushdb、slow lua scrip、mutil/exec、operate big value(collection);
2.缓存更新策略
策略 | 说明 | 一致性 | 维护成本 |
---|---|---|---|
LRU/LFU/FIFO 算法剔除 | 例如 maxmemory-policy | 最差 | 低 |
超时剔除 | 例如 expire | 较差 | 低 |
主动更新 | 开发控制生命周期 | 强 | 高 |
两条建议:
低一致性:推荐最大内存和淘汰策略;
高一致性:推荐超时剔除和主动更新结合,超时剔除是给主动更新做了一个兜底,还需要最大内存和淘汰策略二次兜底。
3.缓存粒度控制
从 MySQL 获取用户信息:select * from user where id = id
设置用户信息缓存:set user:id ‘select * from user where id = id’
缓存粒度:
- 全部属性:set user:id ‘select * from user where id = id’
- 部分重要属性:set user:id ‘select importantColumn1, …importantColumnK from user where id = id’
缓存粒度控制的三个角度:
通用性:全部属性更好;
占用空间:部分重要属性更好;
代码维护:表面上全部属性更好,增删字段不需要维护代码。
4.缓存穿透优化
缓存穿透问题,大量请求不命中?
发生缓存穿透的常见原因:
- 业务代码自身问题;
- 恶意攻击、爬虫等等。
如何发现问题?
- 业务的响应时间;
- 业务本身问题;
- 相关监控指标:总调用数、缓存层命中数、存储层命中数;
缓存穿透问题解决方案:
方案一:缓存空对象。示例代码:
public String getPassThrough(String key)
String cacheValue = cache.get(key);
if (StringUtils.isBlank(cacheValue))
String storageValue = storage.get(key);
cache.set(key, storageValue);
// 如果存储数据为空, 需要设置过期时间
if (StringUtils.isBlank(storageValue))
cache.expire(key, 300); // 300秒
return storageValue;
else
return cacheValue;
方案二:布隆过滤器拦截。通过很小的内存来实现对数据的过滤。
5.缓存雪崩优化
缓存雪崩:由于 cache 服务承载大量请求,当 cache 服务异常/脱机后,流量直接压向后端组件(例如 DB),造成级联故障。
缓存雪崩优化方案:
- 保证缓存高可用性,例如 Redis Cluster、Redis Sentinel、VIP;
- 依赖隔离组件为后端限流;
- 提前演练,例如压力测试。
6.无底洞问题优化
无底洞问题:增加机器性能没能提升,反而下降。问题关键点就是批量操作的链化,例如 mget 操作,时间复杂度为 O(node),随着机器的增加,mget 批量操作的时间会越长,更多的机器不代表更多的性能。
但是随着数据增长,水平扩展是必须的。
优化 IO 的几种方法:
- 命令本身优化,例如慢查询 keys、hgetall bigkey;
- 减少网络通信次数;
- 降低接入成本,例如客户端使用长连接/连接池、NIO 等 。
7.热点key优化
发现热点key:
方法一:客户端,可以使用 Guava 的 AtomicLongMap,记录 key 的调用次数:
public static final AtomicLongMap<String> ATOMIC_LONG_MAP = AtomicLongMap.create();
String get(String key)
counterKey(key);
...
String set(String key, String value)
counterKey(key);
...
方法二:代理端
客户端和 Redis 中间加一个代理进行收集统计。
方法三:服务端
使用 monitor 解析,输出统计。
方法四:机器收集
抓取分析 Redis 所在机器的 TPC 数据。
四种方式对比:
方案 | 优点 | 缺点 |
---|---|---|
客户端 | 1、实现简单; | 1、内存泄露隐患,如果 key 量太大不建议使用; 2、维护成本高; 3、只能统计单个客户端; |
代理端 | 1、代理是客户端和服务端的桥梁,实现最方便最系统; | 1、增加代理端的开发部署成本; |
服务端 | 1、实现简单; | 1、monitor 本身的使用成本和危害,只能短时间使用; 2、只能统计单个 Redis 节点; |
机器收集 | 1、对于客户端和服务端无侵入和影响; | 1、需要专业的运维团队开发,并且增加了机器的部署成本; |
优化方案:
- 避免 bigkey;
- 热键不要用 hash_tag,因为 hash_tag 会落到一个节点上;
- 如果真有热点 key 而且业务对一致性要求不高时,可以用本地缓存 + MQ 解决。
8.热点key重建优化
问题:热点 key + 较长的重建时间。
获取缓存 -> 查询数据源 -> 重建缓存 -> 输出,这个步骤在高并发的情况下,由于查询数据源需要时间,所以会有很多请求会进入到 查询数据源 -> 重建缓存 这个过程。对数据源会造成很大压力,响应时间也会变慢。
三个优化目标:
- 减少重建缓存的次数;
- 数据尽可能一致;
- 减少潜在风险。
两个优化方案:
- 互斥锁(mutex key),查询数据源 -> 重建缓存 这个过程加互斥锁;
- 永不过期,缓存层面不设置过期时间(没有用 expire),功能层面为每个 value 添加逻辑过期时间,但发现超过逻辑过期时间后,会使用单独的线程去构建缓存。
两个优化方案的对比:
策略 | 优点 | 缺点 |
---|---|---|
互斥锁 | 思路简单,保证一致性 | 代码复杂度增加,存在死锁的风险 |
永不过期 | 基本杜绝热点 key 重建问题 | 不保证一致性,逻辑过期时间增加维护成本和内存成本 |
9.总结
- 缓存收益:加速读写、降低后端存储负载;
- 缓存成本:缓存和存储数据不一致性、代码维护成本、运维成本;
- 推荐结合剔除、超时、主动更新三种方案共同完成;
- 穿透问题:使用缓存空对象和布隆过滤器来解决,注意它们各自的使用场景和局限性;
- 无底洞问题:分布式缓存中,有更多的机器不保证有更高的性能。有四种批量操作方式:串行命令、串行 IO、并行 IO、hash_tag;
- 雪崩问题:缓存层高可用、客户端降级、提前演练是解决雪崩问题的重要方法;
- 热点 key 重建问题:互斥锁、永不过期能够在一定程度上解决热点 key 问题,开发人员在使用时要了解它们各自的使用成本。
day819.缓存优化系统性能-java性能调优实战(代码片段)
缓存优化系统性能Hi,我是阿昌,今天学习记录的是关于缓存优化系统性能。缓存是提高系统性能的一项必不可少的技术,无论是前端、还是后端,都应用到了缓存技术。前端使用缓存,可以降低多次请求服务... 查看详情
day819.缓存优化系统性能-java性能调优实战(代码片段)
缓存优化系统性能Hi,我是阿昌,今天学习记录的是关于缓存优化系统性能。缓存是提高系统性能的一项必不可少的技术,无论是前端、还是后端,都应用到了缓存技术。前端使用缓存,可以降低多次请求服务... 查看详情
javaweb应用高并发性能优化方案汇总(代码片段)
文章目录背景系统现状优化过程一、应用系统调优准备:调优分析工具1.使用缓存2.优化数据库连接3.优化日志输出4.程序代码优化5.数据库设计优化6.Tomcat运行参数优化二、Tomcat集群三、网络和部署方式调优1.操作系统TCP连接... 查看详情
mysql性能调优04_连接器查询缓存分析器优化器执行器(代码片段)
文章目录①.MySQL的内部组件结构②.连接器③.查询缓存④.分析器⑤.优化器⑥.执行器前言:这部分是理论知识,需要了解MYSQL的执行的过程(查询缓存)①.MySQL的内部组件结构①.大体来说,MySQL可以分为Server层和存储引擎层两部分... 查看详情
mysql性能调优——库表结构优化(代码片段)
良好的数据库逻辑设计和物理设计是数据库获得高性能的基础。数据库结构优化的目的:减少数据冗余(相同的数据在多个地方存在);尽量避免数据维护中出现更新,插入和删除异常(通过范式化设计解... 查看详情
mysql性能优化(代码片段)
文章目录一、引言1.1研究背景1.2目的和方法论二、数据库性能优化的基础知识2.1数据库设计原则2.1.1遵循范式设计2.1.2.字段类型选择2.1.3.数据表分离2.1.4.数据库范畴的定义2.1.5.外键关系的建立2.1.6.索引的设计2.1.7.命名规范的定义2... 查看详情
redis+guava的本地缓存组合,性能优化最佳方案(代码片段)
...到Redis作为缓存,将高频数据放在Redis中能够提高业务性能,降低MySQL等关系型数据库压力,甚至一些系统使用Redis进行数据持久化,Redis松散的文档结构非常适合业务系统开发ÿ 查看详情
day805.使用设计模式优化并发编程-java性能调优实战(代码片段)
使用设计模式优化并发编程Hi,我是阿昌,今天学习记录的是关于使用设计模式优化并发编程。在使用多线程编程时,很多时候需要根据业务场景设计一套业务功能。其实,在多线程编程中,本身就存在很多成... 查看详情
day782.hashmap的设计与优化-java性能调优实战(代码片段)
HashMap的设计与优化Map接口,主要用来存储键值对数据。HashMap作为我们日常使用最频繁的容器之一,相信一定不陌生了。一、常用的数据结构ArrayList是基于数组的数据结构实现,LinkedList是基于链表的数据结构实现的的... 查看详情
day814.电商系统表设计优化案例分析-java性能调优实战(代码片段)
电商系统表设计优化案例分析Hi,我是阿昌,今天学习记录的是关于电商系统表设计优化案例分析。如果在业务架构设计初期,表结构没有设计好,那么后期随着业务以及数据量的增多,系统就很容易出现瓶颈... 查看详情
性能调优单接口性能调优预备阶段(代码片段)
一、现状在性能测试一开始就针对全接口全场景进行性能压测并调优,如果再加上对业务代码不熟悉,基本上就是熟悉的尴尬场景:阶梯递增并发线程数获得服务性能拐点调整服务端数据库连接池配置调整JVM中新生代... 查看详情
jvm性能优化对象内存分配之虚拟机参数调优分析(代码片段)
内容简介本文主要针对于综合层面上进行分析JVM优化方案总结和列举调优参数计划。主要包含:调优之逃逸分析(栈上分配)调优之线程局部缓存(TLAB)调优之G1回收器栈上分配与逃逸分析-XX:+DoEscapeAnalysis... 查看详情
mysql数据库调优(代码片段)
...库对象优化数据库对象的设计优劣直接决定了SQL语句执行性能的上限,如果设计不当会导致很多的性能问题.1.1字段类型的优化字段类型尽量短字段类型尽量高效:整型>浮点型>字符串字段类型的长度需要设计冗余,避免后期因业... 查看详情
java性能调优
设计调优常用优化组件和方法对象复用——“池”(数据库连接池)并行替代串行、异步替代同步负载均衡时间换空间空间换时间缓存(EhCache、Memcached、Redis)Memcached 多线程,非阻塞IO复用的网络模型。 &nbs... 查看详情
《mysql高级篇》十数据库其他调优策略(代码片段)
文章目录1.数据库调优的措施1.1调优的目标1.2如何定位调优问题1.3调优的维度和步骤第1步:选择适合的DBMS第2步:优化表设计第3步:优化逻辑查询第4步:优化物理查询第5步:使用Redis或Memcached作为缓存第6步:库级优化2.优化MyS... 查看详情
mysql性能调优05_覆盖索引索引下推如何选择合适的索引orderby与groupby优化索引设计原则(代码片段)
文章目录①.坏境准备②.覆盖索引、索引下推③.Mysql如何选择合适的索引④.Orderby与Groupby优化⑤.filesort文件排序方式(了解)⑥.索引设计原则①.坏境准备CREATETABLE`employees`(`id`int(11)NOTNULLAUTO_INCREMENT,`name`varchar(24)NOTNU... 查看详情
day808.设计模式一些列问题-java性能调优实战(代码片段)
...;使代码可扩展性、可读性强,同时也起到了优化系统性能的作用。在一些高并发场景中,线程协作相关的设计模式可以大大提高程序的运行性能。1、除了之前那些实现单例模式的方式,还知道其它实现方式吗& 查看详情
kafka调优(代码片段)
...优目标优化漏斗基础性调优JVM层调优Broker调优应用层调优性能指标调优调优吞吐量调优延时调优目标Kafka的性能:吞吐量(TPS):Broker或Client每秒能处理的字节数或消息数(越大越好)延时:从Producer发送消息到Broker持久化完成的时间间隔(... 查看详情