当前位置: 首页 > 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.lryc.cn/news/606132.html

相关文章:

  • 当过滤条件不符合最左前缀时,如何有效利用索引? | OceanBase SQL 优化实践
  • 0731 IO进程基础
  • FATFS文件系统
  • 从“救火”到“先知”:润建曲尺运维大模型如何重构网络运维价值链
  • 科研快报 |无人机+AI:广东防控基孔热背后的技术革命
  • 大疆无人机开发:MQTT 赋能机场系统集成的Java实战之旅
  • 九识智能与星逻智能达成战略合作,共推“无人车 + 无人机”空地一体巡检升级
  • 5G 单兵终端 + 无人机:消防应急场景的 “空 - 地” 救援协同体系
  • 无人机光伏巡检缺陷检出率↑32%:陌讯多模态融合算法实战解析
  • Lombok 字段魔法:用 @FieldDefaults 解锁“隐身+锁死”双重特效
  • php session 和 jwt 区别和使用场景
  • Java试题-选择题(2)
  • sqli-labs:Less-13关卡详细解析
  • 数据大集网:引领精准获客新时代的优质平台
  • 智慧医院导航系统:基于GPS+蓝牙ibeacon多源融合定位与deepseek•AI导诊问答的设计与实现
  • Linux 时钟同步配置:基础管理与 chrony 工具应用
  • 多架构镜像整合全攻略:在Docker中实现单一镜像支持同时支持amd64和arm64架构
  • hive新增列之后插入新数据时,新列为NULL的解决办法
  • CentOS 7 编译 Redis 6.x 完整教程(解决 GCC 版本不支持 C11)
  • 告别物业思维:科技正重构产业园区的价值坐标系
  • AR智能巡检:工业4.0时代的降本增效利器
  • [人工智能-综述-17]:AI革命:重塑职业版图,开启文明新篇
  • 数据集归一化
  • 机器学习之逻辑回归(Logistic Regression)
  • 视觉图像处理中级篇 [2]—— 外观检查 / 伤痕模式的原理与优化设置方法
  • 【支持Ubuntu22】Ambari3.0.0+Bigtop3.2.0——Step5—Nginx安装
  • Qt 常用控件 - 3
  • vue-seamless-scroll 与 echarts 三联水球图循环滚动的渲染难题-出现短暂空白
  • iOS高级开发工程师面试——其他
  • Linux大页内存导致服务内存不足