当前位置: 首页 > news >正文

电商项目_性能优化_高并发缓存一致性

        如果说要对你的项目进行优化,最先想到的就是使用缓存。说到缓存,最先想到Redis, 可是缓存可不仅仅只有Redis。 缓存有哪些类型?如果保存缓存一致性?是本文要回答的问题。

缓存类型

客户端缓存

1.页面缓存

  • 页面自身对某些元素或全部元素进行缓存;
  • 服务端将静态页面或动态页面的元素进行缓存,然后给客户端使用。

当用户二次访问页面时可以避开网络连接,从而减少负载,提升性能和用户体验。

2.浏览器缓存

浏览器缓存是根据一套 与服务器约定的规则 进行工作的。比如:

  • 服务器侧设置 Expires的HTTP头来告诉客户端在重新请求文件之前缓存多久是安全的。
  • 在HTML页面的节点中加入meta标签,可以告诉浏览器当前页面不被缓存

浏览器会在硬盘上专门开辟一个空间来存储资源副本作为缓存。浏览器缓存相关的场景:

  • 在用户触发“后退”操作
  • 点击一个之前看过的链接
  • 访问系统中的同一张图片

3. APP上的缓存

网络缓存

        网络中的缓存位于客户端和服务端之间,代理或响应客户端的网络请求,从而对重复的请求返回缓存中的数据资源。同时,接受服务端的请求,更新缓存中的内容。

  • 正向代理:为客户端提供代理服务,即服务器不知道真正的客户端是谁。
  • 反向代理:为服务器提供代理服务,即客户端不知道真正的服务器是谁
  • 透明代理:客户端根本不需要知道有代理服务器的存在。

正向与反向代理 两者区别:

1. 位置和功能: 正向代理和反向代理都位于客户端和真实服务器之间,它们的主要功能都是将客户端的请求转发给服务器,然后再将服务器的响应转发给客户端。


2. 提高访问速度: 两者都能通过缓存机制提高访问速度。当客户端请求某个资源时,如果代理服务器已经缓存了该资源,就可以直接从缓存中提供,而无需再次从原始服务器获取,从而节省了时间和带宽。
 

参考:

图文总结:正向代理与反向代理 - Hello-Brand - 博客园

服务端缓存

        这个是后端开发人员最关注的部分。

1. 数据库缓存(InnoDB缓冲池)

在Mysql篇,分析过Mysql Server层的查询缓存用户不大,在Mysql8版本已经去掉。查询缓存,缓存的是SQL语句及结果,移除的原因是:

  • 缓存失效频繁
  • 为保证缓存和DB数据一致性,高并发场景下锁竞争严重
  • 内存管理复杂:缓存占用内存,但是管理效率低下

innodb_buffer_pool_size 是 InnoDB 存储引擎的核心缓存配置,用于缓存 表数据、索引、插入缓冲等,与查询缓存完全不同。需要合理设置值,专用服务器:50%-80% 物理内存,混合部署:25%-50%,避免影响其他服务

  • 减少磁盘 I/O:频繁访问的表数据和索引会被缓存到内存,避免每次查询都访问磁盘

  • 提升查询性能:缓冲池命中率越高,SQL 执行速度越快(理想情况应 >95%)

  • 支持事务和并发:InnoDB 的 MVCC(多版本并发控制)依赖缓冲池管理数据版本

对比项查询缓存(Query Cache)InnoDB 缓冲池(Buffer Pool)
缓存内容存储 SQL 语句及其结果缓存表数据、索引、插入缓冲等
失效机制表数据修改即失效LRU 算法管理,按页替换
适用场景静态数据、极少更新所有 InnoDB 表
并发影响全局锁,高并发下性能差细粒度锁,支持高并发
MySQL 8.0已移除核心组件,必须优化

2. 应用级缓存

在Java语言中,缓存框架更多,例如 Guava Cache(google提供的缓存)、Ehcache 、Caffeine等等。

1. com.google.common.cache.CacheBuilder

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;public class DistrictProCacheImpl implements DistrictProCache{private LoadingCache<String, String> UPTODATE_ADDRESS =CacheBuilder.newBuilder().expireAfterWrite(7, TimeUnit.DAYS).build(new CacheLoader<String, String>() {@Overridepublic String load(String key) {return queryDistrictNameByCode(key);}});
}

2. Ehcache

import org.ehcache.Cache;
import org.ehcache.CacheManager;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.CacheManagerBuilder;
import org.ehcache.config.builders.ResourcePoolsBuilder;public class EhcacheExample {public static void main(String[] args) {// 1. 创建缓存管理器CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder().build();cacheManager.init();// 2. 创建缓存Cache<String, String> myCache = cacheManager.createCache("myCache",CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, String.class,ResourcePoolsBuilder.heap(100) // 堆内存中存储100个条目));// 3. 使用缓存myCache.put("key1", "value1");String value = myCache.get("key1");System.out.println("从缓存中获取的值: " + value);// 4. 关闭缓存管理器cacheManager.close();}
}

3. Caffeine(有资料表明Caffeine性能是Guava Cache的6倍)

Cache<String, Object> cache = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).maximumSize(1000).build();cache.put("key1", "value1");
Object value = cache.getIfPresent("key1");

3.平台级缓存

Redis、 MongoDB、 Memcached都可以作为平台级缓存的重要技术。

Redis、Memcached用的更多一些,MongoDB更多的时候是做为持久化的NoSQL数据库来说使用的。

如何保证缓存数据一致性

        缓存一致性指的是有修改数据时,在同一时刻,如何保证缓存和DB的一致性的问题。本地缓存和Redis缓存都存在一致性的问题。

在不使用分布式锁、分布式事务的情况下,分析缓存一致性的几种方案:

1、先更新缓存,再更新数据库

        一般不考虑。原因是更新缓存成功,更新数据库出现异常了,因为缓存中数据一直都在,不易察觉。

        即使有本地事务,也无法回滚缓存。

2、先更新数据库,再更新缓存

        同上。

        说明:如果更新缓存失败,虽然可以使用本地事务回滚数据库,保持数据的最终一致性。但是在正常操作情况下,数据不一致的问题仍然存在。

3、先删除缓存,后更新数据库

  1. 线程1:删缓存 -》 写主库
  2. 线程2:读缓存 -》 读从库
  3. 数据库:写主库 -》 同步从库

这里的操作不是原子性的, 所以一定会存在问题。

数据不一致场景: 主从库同步数据的间隙, 读线程在从库中读到了修改前的数据

解决方案:

  1. 延时双删, 写线程写完主库,延迟几百ms(主从同步时间),在删除下缓存。也可以异步线程做延迟删缓存。
  2. 缓存不存在时,直接读主库。

4、先更新DB,后删除缓存

  1. 线程1:写主库 -》 删缓存
  2. 线程2:读缓存 -》 读从库
  3. 数据库:写主库 -》 同步从库

这里的操作不是原子性的, 所以一定会存在问题。

数据不一致场景1:在线程1写主库和删缓存的中间,线程2读到了旧缓存值,和主库不一致

数据不一致场景2:缓存失败,线程2在从库读到数据;线程1更新了DB并删除了缓存; 线程1将读到的数据写入缓存,出现缓存和数据库不一致。

解决方案:

  1. 延时删除
  2. 使用消息队列,通过消息再次进行删除缓存,保证缓存一定可以删除成功。 缺点是引入了消息中间件,对代码的侵入性较大,另外要考虑消息的可靠性。
  3. 订阅 Mysql 数据库的 binlog 日志对缓存进行操作,利用工具(canal)将binlog日志采集发送到MQ中,然后通过ACK机制确认处理删除缓存。

缓存更新模式

Cache aside:先写DB后删缓存

Read through/Write through:

Cache Aside套路中,我们的应用代码需要维护两个数据存储,一个是缓存(Cache),一个是数据库(Repository),而Read/Write Through套路是把更新数据库(Repository)的操作由缓存自己代理了,所以,对于应用层,可以理解为只操作一个单一存储。

  • Read through:在查询操作中,如果缓存未查到则更新缓存。
  • Write through:在更新操作中,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后再由Cache自己更新数据库。

Write behind caching(write back):在更新数据的时候,只更新缓存,不更新数据库。缓存会异步地批量更新数据库。

电商平台缓存应用

缓存应用

1. 首页促销产品, 放到redis

2. 促销产品,放到内存级缓存

缓存预热

        首页服务如果出现重启会导致缓存中的数据丢失,导致查询请求直接访问数据库,所以针对缓存要有专门的缓存预热机制。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;@Component
public class PreheatCache implements CommandLineRunner {@Autowiredprivate HomeService homeService;@Overridepublic void run(String... args) throws Exception {for(String str : args) {log.info("系统启动命令行参数: {}",str);}homeService.preheatCache();}}

数据一致性

1. 缓存过期时间

2. 定时任务刷新缓存

3. Mysql数据库,利用Canal检测数据库的更新,然后删除缓存中对应部分。

缓存过期引起毛刺现象

private Cache<String, HomeContentResult> promotionCache;private Cache<String, HomeContentResult> promotionCacheBak;/*先从本地缓存中获取推荐内容*/HomeContentResult result = allowLocalCache ?promotionCache.getIfPresent(brandKey) : null;if(result == null){result = allowLocalCache ?promotionCacheBak.getIfPresent(brandKey) : null;}

对促销数据,使用双缓存。两个缓存使用了不同的策略:

  • 正式缓存是最后一次写入后经过固定时间过期
  • 备份缓存是设置最后一次访问后经过固定时间过期。意味着备份缓存中内容不管是读写后,实际过期时间都会后延。

在本地缓存的异步刷新机制上:

  • 正式缓存只有无效才会被重新写入,保证对客户端的稳定响应。
  • 备份缓存无论是否无效都会重新写入,一则可以保证数据不至于真的永久无法过期而太旧,二则使过期时间不管用户是否访问首页都可以不断后延。

手动干预缓存

后端的一个兜底操作, 可以手动刷新缓存

http://www.dtcms.com/a/308788.html

相关文章:

  • 【Unity笔记04】数据持久化
  • HTM 5 的离线储存的使用和原理
  • Unity游戏开发中的3D数学基础详解
  • MATLAB 2025a的下载以及安装,安装X310的测试附加功能(附加安装包)
  • 因为想开发新项目了~~要给老Python项目整个虚拟环境
  • 旋转花键在机械加工中心ATC装置中有什么优势?
  • 01 全基因组关联分析原理
  • vlan技术
  • 【PHP属性详解:从基础到只读的完全指南】
  • 企业智脑1.3.1技术升级全面解读:AI笔记引擎如何重塑企业知识管理范式
  • 计算机系统基础与操作系统笔记
  • Spring Boot Admin 监控模块笔记-实现全链路追踪
  • 另外几种语言挑战100万行字符串文本排序
  • Web开发-PHP应用原生语法全局变量数据接受身份验证变量覆盖任意上传(代码审计案例)
  • 风力发电场景下设备状态监测与智能润滑预测性维护策略
  • 【Python气象可视化】用Cartopy+Matplotlib绘制青藏高原涡移动轨迹图(附完整代码+颜色渐变时间轴)
  • 数据库学习--------数据库日志类型及其与事务特性的关系
  • 题目:BUUCTF之rip(pwn)
  • [算法]Leetcode3487
  • 【高等数学】第七章 微分方程——第五节 可降阶的高阶微分方程
  • 第三章·数据链路层
  • 前端路由深度解析:Hash 模式 vs. History 模式
  • 数字化应急预案:构筑现代安全防线
  • 实时语音流分段识别技术解析:基于WebRTC VAD的智能分割策略
  • MySQL 中的事务隔离级别有哪些?分别解决什么问题?
  • 图结构知识构造方法详解 ——面向垂直领域的高效知识库构建方案
  • CentOS 7 编译 Redis 6.x 完整教程(解决 GCC 版本不支持 C11)
  • lesson29:Python元类与抽象类深度解析:从接口定义到元编程实践
  • mysql 日志机制
  • Java 接口(上)