架构思维:缓存层场景实战_读缓存(下)

文章目录

  • Pre
  • 业务场景
  • 缓存存储数据的时机与常见问题解决方案
    • 1. 缓存读取与存储逻辑
    • 2. 高并发下的缓存问题及解决方案
    • 3. 缓存预热(减少冷启动问题)
  • 缓存更新策略(双写问题)
    • 1. 先更新缓存,再更新数据库(不推荐)
    • 2. 先删除缓存,再更新数据库(不推荐)
    • 3. 先更新数据库,再更新缓存(不推荐)
    • 4. 先更新数据库,再删除缓存(Cache-Aside模式 推荐⭐)
    • 5. 延迟双删(先删缓存→更新DB→再删缓存)(最佳实践⭐)
    • 总结:如何选择缓存更新策略
    • 最终建议
  • 缓存高可用设计核心要点与监控方案
    • 1、缓存高可用设计的5大核心要点
    • 2、缓存监控关键指标与工具
    • 3、总结

在这里插入图片描述


Pre

Java避坑案例 - 高并发场景下的分布式缓存策略

Redis - 缓存设计深度解析:穿透、并发、雪崩与热点策略

深入理解分布式技术 - 先更新数据库,还是先更新缓存

架构思维:读缓存 - 减少数据库读操作压力


业务场景

某系统商品详情页因叠加推荐、成交记录、优惠活动等功能,导致每次访问需执行数十条SQL,平均响应时间从3.61秒恶化至15.53秒。初期考虑本地缓存(如Guava),但测算发现5万商品数据需750GB内存(30节点×25GB),成本过高。最终采用分布式缓存方案,将数据集中存储(如Redis),所有服务节点共享同一缓存源,避免冗余存储,显著提升访问速度至毫秒级,同时降低硬件成本。优化后,异步加载非核心数据(如成交记录)进一步减轻实时查询压力。

技术选型完成后,开始考虑缓存的一些具体问题,先从缓存何时存储数据入手


缓存存储数据的时机与常见问题解决方案

1. 缓存读取与存储逻辑

  1. 先查缓存:请求到达时,优先从缓存(如Redis)读取数据。
  2. 缓存未命中:若数据不存在或过期,则查询数据库,并将结果写入缓存。
  3. 返回数据:最终返回缓存中的数据给调用方。

2. 高并发下的缓存问题及解决方案

这种逻辑唯一麻烦的地方是,当用户发来大量的并发请求时,它们会发现缓存中没有数据,那么所有请求会同时挤在第2)步,此时如果这些请求全部从数据库读取数据,就会让数据库崩溃。

数据库的崩溃可以分为3种情况。

  • 1)单一数据过期或者不存在,这种情况称为缓存击穿

    解决方案:第一个线程如果发现Key不存在,就先给Key加锁,再从数据库读取数据保存到缓存中,最后释放锁。如果其他线程正在读取同一个Key值,那么必须等到锁释放后才行。关于锁的问题前面已经讲过,此处不再赘述。

  • 2)数据大面积过期或者Redis宕机,这种情况称为缓存雪崩

    解决方案:设置缓存的过期时间为随机分布或设置永不过期即可。

  • 3)一个恶意请求获取的Key不在数据库中,这种情况称为缓存穿透

    比如正常的商品ID是从100000到1000000(10万到100万之间的数值),那么恶意请求就可能会故意请求2000000以上的数据。这种情况如果不做处理,恶意请求每次进来时,肯定会发现缓存中没有值,那么每次都会查询数据库,虽然最终也没在数据库中找到商品,但是无疑给数据库增加了负担。这里给出两种解决办法。
    ①在业务逻辑中直接校验,在数据库不被访问的前提下过滤掉不存在的Key。
    ②针对恶意请求的Key存放一个空值在缓存中,防止恶意请求骚扰数据库。

故,总结如下:

  • 缓存击穿(单一Key失效)

    • 问题:热点Key失效时,大量请求同时穿透到数据库。
    • 解决:加锁(如Redis分布式锁),仅允许一个线程查询DB并回填缓存,其余线程等待。
  • 缓存雪崩(大量Key同时失效或Redis宕机)

    • 问题:缓存大面积失效,导致数据库被压垮。
    • 解决
      • 设置随机过期时间(如30分钟±5分钟)。
      • 缓存永不过期,后台异步更新(如定时任务)。
  • 缓存穿透(恶意查询不存在的数据)

    • 问题:恶意请求查询不存在的Key,导致频繁访问DB。
    • 解决
      1. 业务层校验:如商品ID范围检查(100000~1000000)。
      2. 缓存空值:对不存在的Key存储null或占位符,并设置较短过期时间。

3. 缓存预热(减少冷启动问题)

上面这些逻辑都是在确保查询数据的请求已经过来后如何适当地处理,如果缓存数据找不到,再去数据库查询,最终是要占用服务器额外资源的。那么最理想的就是在用户请求过来之前把数据都缓存到Redis中。这就是缓存预热。

其具体做法就是在深夜无人访问或访问量小的时候,将预热的数据保存到缓存中,这样流量大的时候,用户查询就无须再从数据库读取数据了,将大大减小数据读取压力。

故,总结如下:

  • 时机:在低峰期(如凌晨)提前加载热点数据到缓存。
  • 方式
    • 定时任务扫描DB并写入缓存。
    • 启动时自动加载核心数据。

缓存更新策略(双写问题)

关于缓存何时存数据的问题就讨论完了,接下来开始讨论更新缓存的问题,这部分内容因涉及双写(缓存+数据库),所以会花费一些篇幅。

  • 先更新DB还是缓存?
    • Cache-Aside(旁路缓存):先更新DB,再删除缓存(推荐)。
    • Write-Through(写穿透):先更新缓存,再同步到DB(一致性高,但性能较低)。
    • Write-Behind(写回):先更新缓存,异步刷回DB(高性能,但可能丢数据)。

在缓存更新时,我们需要考虑 数据库与缓存的一致性,同时避免 并发问题性能瓶颈。以下是 5种常见的缓存更新策略,分析它们的优缺点,并给出推荐方案。


1. 先更新缓存,再更新数据库(不推荐)

对于这个组合,会遇到这种情况:假设第二步更新数据库失败了,要求回滚缓存的更新,这时该怎么办呢?Redis不支持事务回滚,除非采用手工回滚的方式,先保存原有数据,然后再将缓存更新回原来的数据,这种解决方案有些缺陷。

这里简单举个例子。
1)原来缓存中的值是a,两个线程同时更新库存。
2)线程A将缓存中的值更新成b,且保存了原来的值a,然后更新数据库。
3)线程B将缓存中的值更新成c,且保存了原来的值b,然后更新数据库。
4)线程A更新数据库时失败了,它必须回滚,那现在缓存中的值更新成什么呢?理论上应该更新成c,因为数据库中的值是c,但是,线程A里面无从获得c这个值。

如果在线程A更新缓存与数据库的整个过程中,先把缓存及数据库都锁上,确保别的线程不能更新,是否可行?当然是可行的。但是其他线程能不能读取?

假设线程A更新数据库失败回滚缓存时,线程C也加入进来,它需要先读取缓存中的值,这时又返回什么值?

看到这个场景,是不是有点儿熟悉?不错,这就是典型的事务隔离级别场景。所以就不推荐这个组合,因为此处只是需要使用一下缓存,而这个组合就要考虑事务隔离级别的一些逻辑,成本太大。接着考虑别的组合。

故, 总结如下:

流程

  1. 更新缓存
  2. 更新数据库
线程A 缓存 数据库 线程B 线程C 初始状态:缓存=a,数据库=a 1. 更新缓存为b(保存旧值a) 2. 更新缓存为c(保存旧值b) 3. 尝试更新数据库为b 4. 更新失败(需回滚) 此时缓存最新值是c\n但线程A只记录旧值a 5. 尝试回滚为a(覆盖当前c值) 6. 读取缓存值(得到a) 数据不一致:数据库值为a\n缓存被错误回滚为a\n实际数据库应为c(线程B已更新成功) 如果使用全局锁: 加锁(阻塞线程B/C) 更新失败后回滚 解锁 读请求被阻塞直到解锁 线程A 缓存 数据库 线程B 线程C

问题

  • 事务回滚困难:如果数据库更新失败,缓存无法自动回滚(Redis不支持事务回滚)。
  • 并发问题
    • 线程A更新缓存为 b,线程B更新缓存为 c,最终缓存可能是 bc,而数据库可能是另一个值。
    • 需要加锁,但会降低性能。

结论:❌ 不推荐,容易导致数据不一致。


2. 先删除缓存,再更新数据库(不推荐)

使用这种方案,即使更新数据库失败了也不需要回滚缓存。这种做法虽然巧妙规避了失败回滚的问题,却引出了两个更大的问题。

1)假设线程A先删除缓存,再更新数据库。在线程A完成更新数据库之前,后执行的线程B反而超前完成了操作,读取Key发现没有数据后,将数据库中的旧值存放到了缓存中。线程A在线程B都完成后再更新数据库,这样就会出现缓存(旧值)与数据库的值(新值)不一致的问题。

2)为了解决一致性问题,可以让线程A给Key加锁,因为写操作特别耗时,这种处理方法会导致大量的读请求卡在锁中。

以上描述的是典型的高可用和一致性难以两全的问题,如果再加上分区容错就是CAP(一致性Consistency、可用性Availability、分区容错性Partition Tolerance)了,这里不展开讨论,接下来继续讨论另外3种组合


故, 总结如下:

流程

  1. 删除缓存
  2. 更新数据库
线程A 缓存 数据库 线程B 初始状态:缓存=a(旧值),数据库=a 1. 删除缓存Key 2. 开始更新数据为b(未提交) 3. 读取Key(未命中) 4. 查询旧值a 5. 写入缓存值为a(旧值回填) 6. 提交更新为b(完成) 数据不一致:缓存=a(旧值)\n数据库=b(新值) 加锁方案导致的问题: 获取Key锁(阻塞线程B) 更新期间占用锁 读请求等待锁释放 释放锁 线程A 缓存 数据库 线程B

问题

  • 缓存与数据库不一致(旧数据问题):
    • 线程A删除缓存 → 线程B查询缓存未命中 → 从DB读取旧数据并写入缓存 → 线程A更新DB,导致缓存是旧数据。
  • 高并发下缓存击穿:大量请求穿透到数据库。

解决方案

  • 加锁(如分布式锁),但会影响性能。

结论:❌ 不推荐,容易导致缓存与数据库不一致。


3. 先更新数据库,再更新缓存(不推荐)

对于组合3,同样需要考虑两个问题。
1)假设第一步(更新数据库)成功,第二步(更新缓存)失败了怎么办?
因为缓存不是主流程,数据库才是,所以不会因为更新缓存失败而回滚第一步对数据库的更新。此时一般采取的做法是重试机制,但重试机制如果存在延时还是会出现数据库与缓存不一致的情况,不好处理。

2)假设两个线程同时更新同一个数据,线程A先完成了第一步,线程B先完成了第二步怎么办?线程A把值更新成a,线程B把值更新成b,此时数据库中的最新值是b,因为线程A先完成了第一步,后完成第二步,所以缓存中的最新值是a,数据库与缓存的值还是不一致,这个逻辑还是有问题的。

因此,也不建议采用这个组合

故, 总结如下:

流程

  1. 更新数据库
  2. 更新缓存
线程A 线程B 数据库 缓存 场景1:缓存更新失败导致不一致 1. 更新数据为a(成功) 2. 尝试更新缓存为a(失败) 3. 读取数据(命中旧值b) 场景2:并发更新导致顺序错乱 1. 更新数据为a(完成) 2. 更新数据为b(完成) 3. 更新缓存为b(先完成) 4. 更新缓存为a(后完成) 最终状态:数据库=b\n缓存=a(不一致) 重试机制问题 更新成功 更新失败 异步重试更新(延迟) 在此期间读取旧值 线程A 线程B 数据库 缓存

问题

  • 缓存更新失败:如果第二步失败,缓存仍是旧数据。
  • 并发问题
    • 线程A更新DB为 a,线程B更新DB为 b → 线程B先更新缓存为 b,线程A后更新缓存为 a,导致缓存是 a,而DB是 b

结论:❌ 不推荐,仍可能不一致。


4. 先更新数据库,再删除缓存(Cache-Aside模式 推荐⭐)

针对组合4,先看看它能不能解决组合3的第二个问题。
假设两个线程同时更新同一个数据,线程A先完成第一步,线程B先完成第二步怎么办?
线程A把值更新成a,线程B把值更新成b,此时数据库中的最新值是b,因为线程A先完成了第一步,所以第二步谁先完成已经不重要了,因为都是直接删除缓存数据。这个问题解决了。

那么,它能解决组合3的第一个问题吗?假设第一步成功,第二步失败了怎么办?
这种情况的出现概率与组合3相比明显低不少,因为删除比更新容易多了。虽然这个组合方案不完美,但出现一致性问题的概率较低。

故, 总结如下:

流程

  1. 更新数据库
  2. 删除缓存
线程A 线程B 数据库 缓存 线程C 场景:组合4(先更新DB再删缓存) 1. 更新数据为a(成功) 2. 删除Key(成功) 3. 更新数据为b(成功) 4. 删除Key(成功) 对比组合3(先更新DB再更新缓存) 更新为a(成功) 更新为b(成功) 更新为b(先完成) 更新为a(后完成) 组合4的核心优势 5. 读Key(未命中) 6. 查询最新值b 7. 回填b(强制重新加载) 线程A 线程B 数据库 缓存 线程C

优点

  • 缓存删除失败概率低(删除比更新更简单)。
  • 并发问题较少
    • 即使线程A更新DB后未及时删除缓存,线程B读取旧数据,但下次查询会重新加载最新数据。

问题

  • 短暂不一致
    • 线程A更新DB后,未删除缓存前,线程B可能读到旧数据。
  • 缓存击穿:删除缓存后,大量请求穿透到DB。

解决方案

  • 延迟双删(组合5)可进一步降低不一致概率。
  • 缓存预热减少击穿影响。

结论:✅ 推荐,相比前3种方案更可靠。


5. 延迟双删(先删缓存→更新DB→再删缓存)(最佳实践⭐)

除了组合3会碰到的问题,组合4还会碰到别的问题吗?

是的。假设线程A要更新数据,先完成第一步更新数据库,在线程A删除缓存之前,线程B要访问缓存,那么取得的就是旧数据。这是一个小小的缺陷。

那么,以上问题有办法解决吗?

还有一个方案,就是先删除缓存,再更新数据库,再删除缓存。这个方案其实和先更新数据库,再删除缓存差不多,因为还是会出现类似的问题:假设线程A要更新数据库,先删除了缓存,这一瞬间线程C要读缓存,先把数据迁移到缓存;然后线程A完成了更新数据库的操作,这一瞬间线程B也要访问缓存,此时它访问到的就是线程C放到缓存里面的旧数据。

不过组合5出现类似问题的概率更低,因为要刚好有3个线程配合才会出现问题(比先更新数据库,再删除缓存的方案多了一个需要配合的线程)。

但是相比于组合4,组合5规避了第二步删除缓存失败的问题——组合5是先删除缓存,再更新数据库,假设它的第三步“再删除缓存”失败了,也没关系,因为缓存已经删除了。

其实没有一个组合是完美的,它们都有读到脏数据(这里指旧数据)的可能性,只不过概率不同。根据以上分析,组合5相对来说是比较好的选择。

不过这个组合也有一些问题要考虑,具体如下。

  • 1)删除缓存数据后变相出现缓存击穿,此时该怎么办?此问题在前面已经给出了方案。
  • 2)删除缓存失败如何重试?这个重试可以做得复杂一点,也可以做得简单一点。简单一点就是使用try…catch…,假设删除缓存失败了,在catch里面重试一次即可;复杂一点就是使用一个异步线程不断重试,甚至用到MQ。不过这里没有必要大动干戈。而且异步重试的延时大,会带来更多的读脏数据的可能性。所以仅仅同步重试一次就可以了。
  • 3)不可避免的脏数据问题。虽然这个问题在组合5中出现的概率已经大大降低了,但是还是有的。关于这一点就需要与业务沟通,毕竟这种情况比较少见,可以根据实际业务情况判断是否需要解决这个瑕疵。

任何一个方案都不是完美的,但如果剩下1%的问题需要花好几倍的代价去解决,从技术上来讲得不偿失,这就要求架构师去说服业务方,去平衡技术的成本和收益。

故, 总结如下:

流程

  1. 删除缓存
  2. 更新数据库
  3. 延迟几百毫秒后,再次删除缓存
线程A 缓存 数据库 线程B 线程C 延迟双删核心流程 1. 第一次删除缓存 2. 更新数据为b 3. 延迟500ms后二次删除缓存 第一次删除后的读穿透 4. 读缓存(未命中) 5. 查询旧值a(未提交) 6. 回填旧值a 二次删除后的强一致性 7. 读缓存(未命中) 8. 查询新值b(已提交) 9. 回填新值b 最终状态:缓存=b,数据库=b(强一致) 线程A 缓存 数据库 线程B 线程C

优点

  • 减少不一致窗口:第二次删除能清理可能的脏数据。
  • 避免缓存更新失败问题:即使第二次删除失败,缓存已被第一次删除,不会长期存储旧数据。

问题

  • 短暂不一致仍存在(但概率更低)。
  • 实现稍复杂:需要引入延迟任务(如MQ、定时任务)。

适用场景

  • 对一致性要求较高的电商、金融等业务。

结论:✅ 最佳实践,比方案4更可靠。 延迟双删通过两次删除操作建立安全窗口,在工程实践上实现了性能与一致性的最佳平衡,是分布式系统缓存更新的首选方案 .


总结:如何选择缓存更新策略

方案描述一致性推荐度
1️⃣ 先更新缓存,再更新DB易回滚问题❌ 差❌ 不推荐
2️⃣ 先删缓存,再更新DB旧数据问题❌ 差❌ 不推荐
3️⃣ 先更新DB,再更新缓存并发问题⚠️ 一般❌ 不推荐
4️⃣ 先更新DB,再删缓存较可靠✅ 较好⭐ 推荐
5️⃣ 延迟双删最可靠✅ 最佳⭐⭐⭐ 最佳

最终建议

  • 普通业务:使用 方案4(先更新DB,再删缓存),简单可靠。
  • 高一致性业务(如支付、库存):使用 方案5(延迟双删),减少不一致窗口。
  • 补充优化
    • 缓存预热减少击穿影响。
    • 异步重试(如MQ)处理删除失败情况。
    • 加锁(如Redis分布式锁)防止并发问题。

没有完美方案,但 方案4和5 在大多数场景下能平衡 性能与一致性


缓存高可用设计核心要点与监控方案


1、缓存高可用设计的5大核心要点

  1. 负载均衡(读扩展)

    • 目标:通过多节点分摊读请求压力。
    • 方案
      • 使用代理层(如Twemproxy、Redis Cluster)自动分配请求。
      • 客户端分片(如一致性哈希)直接路由请求。
  2. 数据分片(写扩展)

    • 目标:分散数据存储与写压力。
    • 方案
      • Redis Cluster:自动分片(16384个槽),支持动态扩缩容。
      • Codis:Proxy层分片,兼容原生Redis协议。
  3. 数据冗余(容灾)

    • 目标:单节点故障时数据不丢失。
    • 方案
      • 主从复制(Replication):主节点写,从节点读+备份。
      • 多副本存储:如Redis Cluster的每个分片包含主从节点。
  4. 故障自动转移(Failover)

    • 目标:节点宕机时自动切换,保障服务可用。
    • 方案
      • 哨兵模式(Sentinel):监控主节点,自动选举新主。
      • Redis Cluster内置Failover:主节点宕机时,从节点自动升级。
  5. 一致性保证

    • 目标:数据分片、故障恢复时避免脏数据。
    • 挑战:Redis默认异步复制,可能丢失少量数据。
    • 解决方案
      • WAIT命令:同步复制(牺牲性能)。
      • 业务层补偿:如定时校对DB与缓存。

推荐架构

  • 中小规模:Redis Sentinel + 主从复制。
  • 大规模:Redis Cluster(内置分片、Failover)。

2、缓存监控关键指标与工具

  1. 核心监控指标

    • 命中率keyspace_hits / (keyspace_hits + keyspace_misses),低于80%需优化缓存策略。
    • 内存使用used_memory,避免超过最大内存(maxmemory)触发淘汰。
    • 慢查询slowlog分析耗时命令(如KEYS *、大Value操作)。
    • 延迟redis-cli --latency,超过1ms需排查网络或阻塞命令。
    • 连接数connected_clients,防止连接泄漏。
  2. 开源监控工具

    • RedisLive:实时仪表盘展示关键指标。
    • Prometheus + Grafana:自定义报警与可视化。
    • Redis-exporter:为Prometheus提供Redis指标。
  3. 自研监控建议

    • 定时巡检脚本:检查INFO命令输出的关键指标。
    • 自动化报警:如内存使用率超90%、命中率低于70%时触发告警。

3、总结

  • 高可用核心:分片扩展写、冗余保障读、自动Failover。
  • 监控关键点:命中率、内存、慢查询、延迟。
  • 工具选型
    • 快速上手:RedisLive。
    • 长期运维:Prometheus+Grafana。

最终建议:根据业务规模选择Redis Sentinel或Cluster,并配套监控告警体系,确保缓存稳定支撑业务高峰。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/79058.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Odrive源码分析(七) 逆park变换

Odrive源码分析(七) Park逆变换 Odrive中FOC部分代码分散在各个对象中,并不是集中在某一块,所以试图在某一段代码就能得到FOC全貌是不现实的。 先看下FOC的整个流程: 控制变量到三相电流输出的关键部分分为Park逆变换和SVPWM。本文主要讨论…

Flink Hive Catalog最佳实践

Flink Hive Catalog 最佳实践 一、配置与初始化 依赖管理 Hive Connector 版本对齐:需确保 flink-sql-connector-hive 版本与 Hive 版本严格匹配(如 Hive 3.1.3 对应 flink-sql-connector-hive-3.1.3_2.12),同时添加 Hadoop 遮蔽…

通过人类和机器人演示进行联合逆向和正向动力学的机器人训练

25年3月来自哥伦比亚大学的论文“Train Robots in a JIF: Joint Inverse and Forward Dynamics with Human and Robot Demonstrations”。 在大型机器人演示数据集上进行预训练是学习各种操作技能的强大技术,但通常受到收集以机器人为中心数据的高成本和复杂性限制…

金融简单介绍及金融诈骗防范

在当今社会,金融学如同一股无形却强大的力量,深刻影响着我们生活的方方面面。无论是个人的日常收支、投资理财,还是国家的宏观经济调控,都与金融学紧密相连。​ 一、金融学的概念​ 金融学,简单来说,是研…

JavaScript `new Date()` 方法移动端 `兼容 ios`,ios环境new Date()返回NaN

在 iOS 环境下,new Date() 方法会返回 NaN,这通常是由于时间字符串的格式问题。iOS 的 Date 构造函数对时间字符串的格式要求比其他平台更严格。 原因:ios端不兼容“-”为连接符的时间。 解决办法: 替换时间格式 IOS 不支持某…

【网络编程】网络编程基础和Socket套接字

目录 一. 网络编程的概念 二. 网络编程基础知识 1)网卡 2)接收端和发送端 3)客户端和服务器 4)请求和响应 5)客户端和服务器的交互模式 三. Socket 套接字模型 一. 网络编程的概念 网络编程 是通过编程实现不同…

盛水最多的容器问题详解:双指针法与暴力法的对比与实现

文章目录 问题描述方法探讨方法一:暴力法(Brute Force)思路代码实现复杂度分析 方法二:双指针法(Two Pointers)思路正确性证明代码实现复杂度分析 方法对比总结 摘要 盛水最多的容器(Container …

图论-BFS搜索图/树-最短路径问题的解决

续上篇~图论--DFS搜索图/树-CSDN博客 先看第一次学习的博客!!👇👇👇👇 👉 有一些问题是广搜 和 深搜都可以解决的,例如岛屿问题,这里我们记dfs的写法就好啦,…

C++进阶——C++11_智能指针

目录 1、问题引入 2、RAII和智能指针 3、C标准库的智能指针 3.1 auto_ptr (不好) 3.2 unique_ptr 3.3 shared_ptr (重点) 3.4 weak_ptr (重点) 4、shared_ptr的循环引用问题(重点) 5、shared_ptr的线程安全问题 6、C11智能指针和boost的关系 7、内存泄漏 7.1 什么是…

数据库的基本原则

数据库的核心原则 原子性与持久性:原子性(Atomicity)确保一个事务中的所有操作要么全部完成,要么完全不执行,不会出现部分完成的情况。持久性(Durability)则保证一旦事务提交成功,即…

Java设计模式实战:装饰模式在星巴克咖啡系统中的应用

一、装饰模式简介 装饰模式(Decorator Pattern)是一种结构型设计模式,它允许向一个现有的对象添加新的功能,同时又不改变其结构。这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前…

使用MPI-IO并行读写HDF5文件

使用MPI-IO并行读写HDF5文件 HDF5支持通过MPI-IO进行并行读写,这对于大规模科学计算应用非常重要。下面我将提供C和Fortran的示例程序,展示如何使用MPI-IO并行读写HDF5文件。 准备工作 在使用MPI-IO的HDF5之前,需要确保: HDF5库编译时启用…

七、自动化概念篇

自动化测试概念 自动化测试是把以人为驱动的测试行为转化为机器执行的一种过程。通常,在设计了测试用例并通过评审之后,由测试人员根据测试用例中描述的过程一步步执行测试,得到实际结果与期望结果的比较。在此过程中,为了节省人…

redis cluster 的通信机制

Redis Cluster 的通信机制是其分布式架构的核心,基于 Gossip 协议 和 Cluster Bus 实现节点间状态同步与数据协调。以下是其通信机制的核心要点: 二进制协议:数据以字节流形式编码(如Protobuf、Thrift、MQTT、Gossip)。…

CTF web入门之文件上传

知识点 产生文件上传漏洞的原因 原因: 对于上传文件的后缀名(扩展名)没有做较为严格的限制 对于上传文件的MIMETYPE(用于描述文件的类型的一种表述方法) 没有做检查 权限上没有对于上传的文件目录设置不可执行权限,(尤其是对于shebang类型的文件) 对于web server对于上传…

PhotoShop学习09

1.弯曲钢笔工具 PhotoShop提供了弯曲钢笔工具可以直观地创建路径,只需要对分段推拉就能够进行修改。弯曲港币工具位于工具面板中的钢笔工具里,它的快捷键为P。 在使用前,可以把填充和描边选为空颜色,并打开路径选项,勾…

tsconfig.json配置不生效

说明一下我遇到的问题,这是我的配置文件代码的 {"compilerOptions": {"module": "none","target": "ES5","outFile": "./dist/bundle.js"} } 和我想象不同的是,我编译成 js 没…

源代码加密之零日攻击

# SDC沙盒:有效防御零日攻击的多层防护体系 在当今复杂多变的网络安全环境中,零日攻击已成为企业面临的重大威胁之一。零日攻击利用尚未被公众发现或尚未被软件供应商修复的漏洞进行攻击,具有极高的隐蔽性和破坏性。SDC沙盒作为一种先进的数…

记录一次TDSQL网关夯住故障

环境信息: TDSQL-MySQL同城双中心集群,集中式实例,一主三副本,每个中心两个db副本,每个中心一个VIP,V每个IP通过硬件做负载均衡指向该中心两个proxy,操作系统为麒麟v10 arm。 故障描述&#xf…

代码随想录八股训练营完结总结

! 40天的训练营,我总结了自己完整的八股文,后续在面试过程中可以补充 很感谢这次训练营,真的高频,在面试中能击中60%以上,剩下的就靠平时的积累了。 感谢训练营的小伙伴,很多次想偷懒&#x…