基于Redis发布订阅的Caffeine多级缓存一致性优化实践

SpringBoot 多级缓存实战:Caffeine与Redis的黄金组合 本文深入探讨了在SpringBoot项目中如何通过整合Caffeine与Redis构建高效的多级缓存架构。文章从实战角度出发,详细阐述了Caffeine作为本地缓存与Redis作为分布式缓存的分工协作机制,提供了完整的配置、代码实现及核心缓存策略,并针对生产环境中的缓存穿透、数据一致性等关键问题给出了优化方案与避坑指南,是提升系统性能的黄金组合实践。 阅读详情

1. 从一次线上“灵异事件”说起:为什么我的缓存数据“穿越”了?

大概半年前,我们团队负责的物流运单查询系统上线后,平稳运行了几个月。为了扛住高峰期的流量,我们引入了经典的 Redis + Caffeine 多级缓存架构。简单来说,就是用户查询时,先看本地内存里的 Caffeine 有没有(一级缓存),没有就去查 Redis(二级缓存),再没有才去查数据库。上线后效果拔群,接口响应时间从几十毫秒降到了个位数,大家都很开心。

但好景不长,运营同学有一天突然反馈,说有用户投诉查到的物流状态“不对”。比如,用户明明看到快递员已经签收了,但在我们系统的查询页面上,还显示“运输中”。我们一查数据库,记录确实已经更新为“已签收”了。这就怪了,数据明明是对的,为什么查出来是错的?更诡异的是,这个问题不是每次都出现,而是“时好时坏”,同一个运单号,用不同电脑或者刷新几次,可能又正常了。

我们几个人对着监控大盘和日志查了一下午,终于抓到了“鬼”。我们的服务是用 Kubernetes 部署的,平时会有3个实例(Pod)在跑。问题就出在这里:

  1. 用户A第一次查询,请求打到了 节点1。节点1的 Caffeine 和 Redis 里都没有数据,于是去数据库查,结果写入自己的 Caffeine,也写入了 Redis。此时一切正常。
  2. 过了一会儿,后台系统更新了这条运单的状态。这个更新请求,碰巧打到了 节点2。节点2处理更新:它先更新了数据库,然后按照缓存更新策略,删除了自己Caffeine里的旧数据,同时也更新了Redis里的数据。
  3. 用户A再次刷新页面查询,请求又被打到了 节点1。节点1一查,自己本地的 Caffeine 里还有缓存呢!而且没过期,它想都没想,直接就把这个“旧数据”返回给用户了。它根本不知道 Redis 里的数据已经被节点2更新了。

看明白了吗?这就是分布式系统里,多级缓存最经典的“数据一致性”问题。每个服务实例都有自己的“小本本”(本地内存缓存),当数据在某个“小本本”上被修改后,其他实例的“小本本”并不知道,还记着老黄历。数据就这样“穿越”了。这个问题在单机服务里不会出现,一旦服务多实例部署,几乎必然会发生。我们的架构图,就像下面这样,埋下了不一致的种子:

[用户] --> [负载均衡] --> [节点1: Caffeine有旧数据] --> (返回旧数据)
                         [节点2: Caffeine已更新]      --> [Redis: 已更新为新数据]
                         [节点3: Caffeine为空]

当时我们第一反应是:给 Caffeine 设置一个很短的过期时间,比如5秒,让数据尽快失效。但这无异于因噎废食,频繁的缓存失效会大大增加 Redis 和数据库的压力,我们引入多级缓存的优势就没了。那怎么办呢?核心思路其实很简单:一个节点更新了数据,得想办法通知其他所有节点:“嘿,哥们,你手里的那份数据过期了,赶紧扔了吧!”

2. 通知方案的抉择:为什么不用RabbitMQ而用Redis Pub/Sub?

说到“通知”,咱们程序员脑子里能蹦出好几个方案。当时我们团队内部也争论过,主要候选人是两位:RabbitMQ 和 Redis 的发布订阅(Pub/Sub)。

先说说 RabbitMQ。它是一个功能完备的消息队列,可靠性高,支持复杂的路由、确认机制、持久化,用在金融、订单等对一致性要求极高的场景是没问题的。如果用它来做缓存失效通知,大致的流程是:节点2更新数据后,向一个特定的 Exchange 发送一条消息;节点1和节点3都订阅了这个 Exchange 的队列,收到消息后,删除自己本地对应的缓存。

这方案行不行?当然行。但我们评估后,觉得有点“杀鸡用牛刀”。为什么?

  1. 复杂度与依赖:引入 RabbitMQ 意味着要维护一套新的中间件集群,增加了系统的复杂度和运维成本。对于“缓存失效通知”这个单一职责来说,它的队列、确认、持久化等高级功能我们可能用不上,反而带来了额外的资源消耗和学习成本。
  2. 时效性:虽然 RabbitMQ 很快,但它毕竟是一个独立的服务,消息的传递需要经过网络IO、序列化、入队、出队等过程。而我们的目标是让缓存失效尽可能快。
  3. 轻量级需求:缓存失效通知这个场景,对消息的“可靠性”要求其实是有弹性的。偶尔丢失一条失效通知,最坏的结果就是某个节点的本地缓存多保留了一段时间(直到自然过期),用户可能多看到几秒旧数据。这在很多业务场景(如物流状态、商品库存非核心字段)是可以接受的。我们追求的是 最终一致性,而不是强一致性。

这时候,Redis 发布订阅(Pub/Sub) 就进入了我们的视野。它就像是 Redis 自带的一个“广播喇叭”。工作原理非常直观:

  • 发布者(Publisher):某个服务节点(比如节点2),在更新数据后,向一个指定的“频道(Channel)”喊一嗓子:“运单号 TRACK_123456 的数据变了!”
  • 订阅者(Subscriber):所有服务节点(节点1、2、3)都竖着耳朵在听这个频道。只要听到广播,就立刻检查是不是喊的自己关心的数据,如果是,就马上把本地对应的缓存删除。

用 Redis Pub/Sub 的好处显而易见:

  • 零额外依赖:缓存用的就是 Redis,复用同一个基础设施,不用引入新的组件。
  • 极致轻量与快速:消息直接在 Redis 内存中传递,速度极快,几乎是实时的。
  • 足够简单:模型简单,API 简单,理解和实现成本低。

当然,它也有缺点,最主要的就是 消息的“非持久化”。如果某个服务节点在消息广播时刚好重启了,它就错过了这条消息。但对于我们缓存失效的场景,这可以通过结合 设置合理的本地缓存过期时间 来兜底。比如,本地缓存设置30秒过期,即使错过了失效通知,最多30秒后数据也会自动刷新。这种“轻量级通知 + 过期兜底”的组合拳,在实践中非常有效且平衡。

所以,我们最终拍板:就用 Redis 的发布订阅来解决这个多级缓存一致性问题!

3. 手把手实战:三部分代码搞定一致性

光说不练假把式,下面我就把我们项目中核心的三块代码拆开揉碎了讲给你听,保证你跟着做就能实现。

3.1 第一步:配置消息监听器(竖起耳朵)

首先,我们需要让每个 Spring Boot 服务实例启动时,就自动去订阅 Redis 的特定频道。这需要在配置类里完成。

我们创建一个 RedisConfig 类(如果已有,就在里面添加)。关键是要定义一个频道主题名,所有服务节点都用这个主题来收发消息。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;

@Configuration
public class RedisConfig {

    // 定义一个公共的频道主题,所有节点都订阅和发布这个主题
    public static final String CACHE_INVALIDATE_CHANNEL = "cache:invalidate:transport";

    /**
     * 创建Redis消息监听容器
     * 这个容器就像是一个总机,负责管理谁在听什么频道。
     *
     * @param connectionFactory Redis连接工厂,Spring会自动注入
     * @param listenerAdapter   消息监听器的适配器,我们下面会定义
     * @return 配置好的监听容器
     */
    @Bean
    public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
                                                   MessageListenerAdapter listenerAdapter) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        // 设置连接工厂
        container.setConnectionFactory(connectionFactory);
        // 添加监听器,并指定监听的频道
        // 这里的意思是:让 listenerAdapter 去监听 CACHE_INVALIDATE_CHANNEL 这个频道
        container.addMessageListener(listenerAdapter, new ChannelTopic(CACHE_INVALIDATE_CHANNEL));
        return container;
    }
}

3.2 第二步:编写消息处理器(听到后该干嘛)

光竖起耳朵还不行,听到广播后得知道干什么。我们需要一个真正的消息处理器,它负责在收到消息时,执行删除本地 Caffeine 缓存的动作。

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;
import com.github.benmanes.caffeine.cache.Cache;

/**
 * Redis缓存失效消息监听器
 * 实现 MessageListener 接口,专门处理订阅到的消息
 */
@Slf4j
@Component // 声明为Spring组件,让容器管理
public class CacheInvalidateMessageListener implements MessageListener {

    // 注入我们业务中使用的Caffeine缓存实例
    // 假设我们的缓存叫 `transportInfoCache`,缓存键是运单号,值是运单信息DTO
    @Autowired
    private Cache<String, TransportInfoDTO> transportInfoCache;

    /**
     * 这是核心方法,当订阅的频道有消息时,这个方法会被自动调用
     *
     * @param message Redis传来的原始消息,里面包含我们发布的内容
     * @param pattern 匹配到的频道模式,这里我们暂时用不到
     */
    @Override
    public void onMessage(Message message, byte[] pattern) {
        // 1. 解析消息体。我们约定消息内容就是需要失效的缓存键,比如运单号 "TRACK_123456"
        String cacheKey = new String(message.getBody());
        log.info("收到缓存失效通知,开始清理本地缓存,键: {}", cacheKey);

        // 2. 执行本地缓存失效
        // 这一步是点睛之笔!直接让本机内存里的旧数据消失
        transportInfoCache.invalidate(cacheKey);

        log.debug("本地缓存清理完成: {}", cacheKey);
    }
}

注意,这里我们直接实现了 MessageListener 接口,比用 MessageListenerAdapter 更直接。还需要修改上一步的配置,将监听器适配器指向我们这个类:

// 在 RedisConfig 中修改或添加这个Bean
@Bean
public MessageListenerAdapter listenerAdapter(CacheInvalidateMessageListener listener) {
    // 将我们的监听器实例包装成适配器
    return new MessageListenerAdapter(listister);
}

3.3 第三步:在数据更新处发布消息(拿起喇叭喊)

最后,也是最关键的一步:在任何一个会修改数据的地方(比如 update、save 方法),在更新完数据库和 Redis 之后,必须记得“拿起喇叭广播一下”。

我们通常会在 Service 层的数据更新方法里做这件事:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.cache.annotation.CacheEvict;
import javax.annotation.Resource;

@Service
public class TransportInfoServiceImpl implements TransportInfoService {

    @Autowired
    private TransportInfoMapper transportInfoMapper; // 假设是MyBatis Mapper

    // 注入Redis模板,用于发布消息
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 更新运单信息
     * @param transportOrderId 运单号
     * @param updateInfo 更新内容
     * @return 更新后的实体
     */
    @Override
    // 使用Spring Cache注解,更新数据后清除Redis中的缓存(二级缓存)
    // 注意:这个注解只管理Redis缓存,不管Caffeine
    @CacheEvict(cacheNames = "transport-info", key = "#transportOrderId")
    public TransportInfoEntity updateInfo(String transportOrderId, TransportInfoDetail updateInfo) {
        // 1. 更新数据库
        TransportInfoEntity entity = transportInfoMapper.selectById(transportOrderId);
        // ... 更新entity字段 ...
        transportInfoMapper.updateById(entity);

        // 2. 发布缓存失效消息(核心步骤!)
        // 向我们在Config里定义的频道,发送一条消息,内容就是运单号
        stringRedisTemplate.convertAndSend(RedisConfig.CACHE_INVALIDATE_CHANNEL, transportOrderId);
        log.info("数据已更新,已发布缓存失效通知,运单号: {}", transportOrderId);

        // 注意:我们不需要在这里手动操作Caffeine,因为监听器会处理。
        // 也不需要再手动操作Redis,因为 @CacheEvict 注解帮我们做了。

        return entity;
    }
}

这三段代码就构成了一个完整的闭环。流程再捋一遍:

  1. 节点2 更新数据 -> 更新DB,@CacheEvict 清理Redis -> 发布消息到 cache:invalidate:transport 频道。
  2. Redis 收到发布的消息,立即广播给所有订阅该频道的客户端。
  3. 节点1 和 节点3 的 CacheInvalidateMessageListener 的 onMessage 方法被触发 -> 根据消息中的运单号,删除自己本地的 Caffeine 缓存。
  4. 下次查询时,节点1发现本地缓存没了,就会去查Redis(此时已是新数据),从而保证了一致性。

4. 避坑指南:实际部署中遇到的三个“大坑”与优化

方案跑通后,我们兴高采烈地上了预发布环境,结果还是踩了几个坑。这里分享出来,希望你能直接避开。

坑一:消息的“轰炸”与“静默” 一开始,我们任何数据更新都发消息。结果发现,在批量更新运单状态的夜间任务时,Redis频道里瞬间涌进几十万条消息。虽然每条消息很小,但数量巨大,导致网络带宽瞬间飙升,甚至影响了正常的缓存读写。同时,所有服务实例都在疯狂地执行 invalidate 操作,CPU 也有毛刺。

  • 优化方案:我们引入了 消息合并与延迟。对于批量操作,不再逐条发送,而是在本地积累一批运单号(比如最多100个,或最多等待100毫秒),合并成一条消息发送,内容是一个JSON数组。监听器收到后,再批量失效。这极大地减少了消息数量。对于非核心实时性要求的数据,甚至可以延迟1-2秒再发送失效消息,平滑流量。

坑二:监听器处理慢,阻塞了其他命令 我们有一次发现,某个服务实例的 Redis 客户端响应变慢了。排查后发现,是在 onMessage 方法里,我们不仅做了 invalidate,还同步记录了一条很详细的审计日志到数据库,这个数据库操作偶尔比较慢。而 Redis 的 Pub/Sub 消息分发是同步的!如果监听器处理消息太慢,会阻塞 Redis 服务器向该客户端发送其他命令(甚至是 PING),可能导致连接超时。

  • 优化方案:确保 onMessage 方法必须快速返回! 所有耗时的操作(如写数据库、调用外部接口)必须异步化。我们改成了这样:
    @Override
    public void onMessage(Message message, byte[] pattern) {
        String cacheKey = new String(message.getBody());
        // 立即失效缓存
        transportInfoCache.invalidate(cacheKey);
        // 将日志记录扔到一个独立的线程池或MQ中异步处理,不阻塞当前线程
        asyncTaskExecutor.execute(() -> auditLogService.logInvalidate(cacheKey));
    }
    

坑三:新节点上线与消息丢失 这是 Redis Pub/Sub 的固有缺陷。如果一个服务实例在消息广播期间正在重启,或者新扩容一个实例,它会错过之前以及重启期间的所有消息。这意味着,新实例的本地缓存可能一开始就是旧的。

  • 优化方案:我们采用了 “双重保障”策略。
    1. 兜底过期时间:给 Caffeine 设置的过期时间不再是“永不过期”或很长,而是一个合理的、业务可接受的值,比如 60秒。这样即使错过消息,最坏情况是数据延迟60秒刷新。
    2. 启动时预热:新的服务实例启动后,在执行第一次查询业务前,可以主动去 Redis 查询一批热点数据,加载到自己的本地缓存中。虽然不能保证全部最新,但能快速建立一份相对较新的缓存视图。

此外,我们还把频道主题设计得更精细,比如按业务模块划分:cache:invalidate:order、cache:invalidate:user 等,避免不相关的消息干扰所有监听器。

5. 效果对比:一张表看清方案优劣

方案上线稳定运行一段时间后,我们做了一次全面的复盘,和最初考虑的几种方案做了个对比,你可以参考下面的表格:

对比维度无通知方案 (仅靠过期)RabbitMQ 通知方案Redis Pub/Sub 通知方案 (本文)
数据一致性弱,不一致窗口期长(等于本地缓存过期时间)强,可靠消息保证最终一致,近乎实时,有极低概率消息丢失
时效性差(依赖过期)好(毫秒级)极好(亚毫秒级)
系统复杂度极低高(需维护MQ集群)低(复用Redis)
运维成本低高低
资源消耗低中高(MQ服务器资源)低(仅增加Redis少量内存和CPU)
适用场景数据变化不频繁,对一致性要求极低的场景金融、交易等对强一致性、可靠性要求极高的场景高并发读、数据变更频率中等、允许秒级最终一致的业务(如商品信息、物流状态、用户资料)
我们的选择理由无法满足业务需求牛刀杀鸡,过度设计轻量、快速、简单、够用,与现有技术栈完美融合

从实际监控来看,在采用优化后的 Redis Pub/Sub 方案后,我们物流查询接口的 缓存命中率 依然保持在98%以上(主要命中本地缓存),而 数据不一致的用户投诉 降为了0。系统的整体响应时间中位数在2ms左右,完全达到了预期目标。

6. 进阶思考:这套方案是银弹吗?

当然不是。没有任何技术方案是放之四海而皆准的银弹。基于 Redis Pub/Sub 的多级缓存一致性优化,是一个在 特定约束 下非常漂亮的解决方案。在决定采用它之前,建议你再从以下几个角度审视你的项目:

第一,业务容忍度。 你的业务是否能接受“在极端情况下(如消息丢失、网络分区),用户可能看到几秒到几十秒的旧数据”?对于电商扣库存、支付状态这类场景,这显然是无法接受的。但对于文章阅读量、热搜榜、非核心的商品属性(如颜色描述),这就完全没问题。我们物流状态也属于后者,短暂的状态显示延迟在业务上是可以解释的。

第二,数据变更的维度。 我们上面的例子,失效粒度是单个运单号(缓存键)。如果你的数据更新经常是“批量更新某一类商品”(比如修改了某个分类下所有商品的活动标签),那么发布一条消息,让所有节点失效整个分类的缓存,可能比遍历失效每个商品键更高效。这就需要你设计更灵活的消息协议,比如消息内容可以是 invalidate:category:1024。

第三,与更高级缓存模式的结合。 比如 Caffeine 的 RefreshAfterWrite 策略。你可以配置 Caffeine 在写入后一段时间异步刷新数据,而不是被动失效。这样,即使失效消息延迟,用户请求也可能触发后台异步加载新数据,下次请求就能拿到新的。这可以和 Pub/Sub 失效通知结合,提供更平滑的体验。

第四,监控与告警。 一定要给这套机制加上监控。比如,监控 Redis Pub/Sub 频道的消息生产/消费速率是否正常;监控各个服务实例本地缓存的命中率和失效次数。如果某个实例长时间没有失效操作,但其他实例有,可能意味着它的监听器出问题了。我们当时就通过监控发现过一个因为线程池满导致异步日志任务丢弃,进而使得监听器变慢的案例。

在我个人看来,技术选型就像搭积木,没有最好的,只有最适合的。Redis Pub/Sub 解决多级缓存一致性,就是一块在“性能”、“复杂度”、“一致性”之间取得了很好平衡的积木。它可能不适用于所有宫殿,但用来搭建我们“高并发查询服务”这座高楼,却是结实又趁手的好材料。希望这次分享的实战经验和踩坑记录,能帮你少走弯路,更顺畅地构建出稳定高效的缓存系统。

Redis+Caffeine多级缓存实战:如何用Redis Pub/Sub解决分布式环境下的数据一致性问题 本文深入探讨了Redis+Caffeine多级缓存在分布式环境下的数据一致性问题,并提出了基于Redis Pub/Sub的解决方案。通过详细解析Pub/Sub机制的工作原理及在Spring Boot中的实战实现,帮助开发者有效解决缓存同步难题,提升系统性能与数据一致性。 阅读详情

相关推荐

SpringBoot + Redis 多级缓存实战:L1本地缓存与L2分布式缓存自动同步及一致性保障

本文基于SpringBoot整合Caffeine与Redis,实现了生产级的多级缓存架构,重点解决了L1本地缓存与L2 Redis缓存的自动同步及一致性问题,核心亮点如下:代码可直接落地:所有核心代码均经过生产环境验证,封装完善,可直接复制到项目中使用。一致性保障可靠:采用“更新顺序+发布订阅+过期时间”三重方案,覆盖绝大多数生产场景。面试针对性强:梳理了核心原理、优缺点及一致性方案,贴合后端面试高频考点。

srlsong的博客 469

Caffeine+Redis缓存一致性避坑指南:从原理到选型的完整解析

本文深入解析Caffeine+Redis多级缓存架构中的数据一致性问题,提供从原理到工程实践的完整解决方案。探讨双删策略、发布订阅模式等关键技术,并给出混合策略的架构设计和生产环境最佳实践,帮助开发者有效规避缓存一致性陷阱。

efc1234567的博客 804

Spring Boot项目实战:如何用Caffeine+Redis构建高性能多级缓存(附避坑指南)

本文详细介绍了在Spring Boot项目中,如何整合Caffeine与Redis构建高性能多级缓存架构。通过实战代码演示了缓存管理器的实现,并针对缓存穿透、雪崩、击穿及一致性等经典问题提供了解决方案与避坑指南,旨在帮助开发者设计出能应对高并发场景的立体化缓存方案。

weixin_29292535的博客 232

SpringBoot缓存优化:Redis+Caffeine多级缓存架构设计

单一的缓存方案往往存在一定的局限性,例如Redis作为分布式缓存,虽然具有高可扩展性和持久化能力,但网络开销较大;而Caffeine作为本地缓存,读写速度极快,但不具备分布式特性。因此,采用Redis + Caffeine多级缓存架构可以充分发挥两者的优势,提高系统的响应速度和吞吐量。本文将详细介绍如何在Spring Boot应用中设计和实现Redis + Caffeine多级缓存架构。

梵心白莲的博客 893

分布式二级缓存组件实战(Redis+Caffeine实现)

核心是get方法与put方法。@Getter/*** 默认key超时时间 3600s*/{}}}@Override}@Override}@Override= null) {}//key在redis和缓存中均不存在}try {= null) {}//执行原方法获得value}}@Overrideif (!return;}

Cr1556648487的博客 1102

SpringBoot实战:基于多级缓存+分布式锁+熔断降级的高可用架构搭建

随着业务流量的增长,后端系统面临的高并发压力日益凸显,单纯依赖单一缓存或锁机制,已无法满足生产环境的高可用需求:1. 仅使用Redis作为缓存,受网络I/O限制,响应速度难以达到极致,热点Key集中访问易导致Redis崩溃;2. 多节点部署时,并发修改数据缺乏有效锁保护,易出现脏数据、重复提交等数据一致性问题;3. 当Redis、数据库或第三方依赖服务故障时,大量请求阻塞会引发服务雪崩,导致系统整体不可用。

srlsong的博客 910

天机学堂——多级缓存

本文针对天机学堂项目中课程分类数据查询的性能优化问题,提出了一套多级缓存解决方案。文章分析了原有查询流程的痛点(高频网络请求、数据库资源浪费),基于分类数据量小、更新不频繁的特点,设计了"Caffeine本地缓存+Redis分布式缓存+数据库"的三级架构。重点介绍了Caffeine本地缓存的核心优势、API使用方法和三种缓存淘汰策略,并详细展示了课程分类缓存的SpringBoot实现代码,包括配置类封装和工具类设计。最后还提出了扩展Redis二级缓存的思路,并列举了其他适用本地缓存的业务

27届Java后端炒粉练习生,AICoding中........ 831

海康摄像头V3.4 升级改造VUE3

摘要:本文介绍了一个基于Vue3的海康威视摄像头组件封装方案。针对官方仅提供HTML版本SDK的问题,作者通过封装实现了Vue3组件化集成,解决了iframe/webview引入方式的弊端。组件核心功能包括实时预览、本地录像、录像下载、截图抓拍等,并优化了SDK初始化逻辑(单例模式)、定位问题修复和错误处理机制。关键技术点包括:1)SDK插件容器定位修复;2)录像下载进度监控;3)多格式图片数据处理;4)多窗口场景下的SDK实例管理。该组件可直接通过npm安装使用,提供了完善的API和事件接口,显著提升了海

LINLINLINShi的博客 114

Redis与Caffeine本地缓存相结合的双层缓存架构

本文探讨了Redis与Caffeine双层缓存架构的设计与实践。该架构结合本地缓存(Caffeine)的低延迟和分布式缓存(Redis)的高可用性,通过分层策略实现性能优化。文章详细解析了技术原理、实现方法及最佳实践,包括数据同步机制、性能优化点和典型应用场景。实践表明,该架构能显著提升系统吞吐量,降低延迟,适用于电商、社交等高并发场景。开发者可从简单模块入手,逐步优化配置,构建更健壮的系统。

2201_75797969的博客 976

无人机飞控传感器全解析:从IMU到光流传感器的实战应用

本文深入解析了无人机飞控系统的核心传感器,从IMU、气压计、磁力计到光流传感器,详细阐述了其工作原理、数据融合策略及常见挑战。结合ICF5、Pixhawk 6C等主流飞控硬件,提供了从传感器校准、滤波器调优到应对室内光流定位、强磁干扰等实战场景的系统性优化指南,帮助开发者突破飞控“黑箱”,实现更稳定、可靠的飞行控制。

weixin_28396629的博客 169

Halcon 简单的形状匹配定位与区域跟随

【代码】Halcon简单的形状匹配定位与区域跟随。

唐大狗 2806

多级缓存 架构设计

面试经常遇到一个很重要的面试题:20w的QPS的场景下,服务端架构应如何设计?10w的QPS的场景下,缓存架构应如何设计?尼恩提示,缓存架构、缓存规划、缓存淘汰、多级缓存的数据一致性 相关的问题,是架构的核心知识,又是线上的重点难题。

架构师尼恩 2245

ArcGIS天地图矢量化秘籍:用重分类+ArcScan高效提取建筑轮廓线

本文详细介绍了利用ArcGIS重分类和ArcScan技术高效提取天地图建筑轮廓线的半自动化流程。通过动态中断值计算、多级重分类策略和ArcScan高级矢量化技巧,显著提升城市规划中的矢量化效率与精度,适用于中级GIS用户。

weixin_30879169的博客 475

本地缓存和Redis双剑合璧:5种实战方案解决数据一致性问题(附代码示例)

本文深入探讨了在高并发应用中,如何通过本地缓存与Redis构建多级缓存架构,并解决其核心的数据一致性问题。文章详细解析了延迟双删、发布订阅、短TTL刷新、版本号控制等五种实战方案,对比了其一致性强度与实现复杂度,并提供了可直接应用于生产环境的代码示例,旨在帮助开发者构建高吞吐且数据可靠的系统。

qsc9012345的博客 977

libfranka---joint_impendence_control例程分析

joint_impedance_control.cpp例程分析 部分内容来自链接,我个人以初学者的角度对代码进行了更加细致的解释。 核心部分: // Set and initialize trajectory parameters. const double radius = 0.05; const double vel_max = 0.25; const double acceleration_time = 2.0; const double run_time = 20.0; d

weixin_41567783的博客 1121

电力系统设计实战:如何用ETAP绘制一次接线图并优化供电方案

本文详细介绍了如何使用ETAP软件进行电力系统一次接线图的绘制与供电方案优化。从基础环境配置到主接线拓扑构建,再到典型接线方式的对比分析与智能优化,为工业与民用电力系统设计提供实用指南。通过ETAP的可靠性分析和动态仿真功能,帮助工程师选择最优供电方案,提升系统可靠性和运维效率。

weixin_29250573的博客 205

多级缓存架构下,如何通过双删策略与发布订阅机制确保数据一致性?

本文深入探讨了多级缓存架构下如何通过双删策略与发布订阅机制确保数据一致性。针对本地缓存和Redis等分布式缓存的数据同步问题,详细解析了双删策略的实现原理、延迟时间设置技巧,以及发布订阅模式在分布式环境中的优化方案,帮助开发者构建高可用的缓存系统。

weixin_28688385的博客 388

手把手教你学Simulink--基于微电网的建模与仿真场景实例:独立运行模式下微电网的能量管理系统设计

本文详细介绍了使用Simulink对独立运行微电网能量管理系统进行建模与仿真的完整流程。主要包括:系统结构设计(分布式能源、储能系统、负荷等模块)、建模步骤(从创建项目到实现能量管理策略)、仿真设置与结果分析。通过建立基于规则的控制逻辑和优化调度算法,实现微电网的自主运行和能量优化管理。文章还提供了参数优化和多目标改进建议,为新能源和智能电网领域的研究与应用提供了实用指导。

MHD0815的博客 8584

多级缓存基础架构组件设计

实现多级缓存组件

qq_41214487的博客 1844

自动权重化图卷积网络(Auto-Weighted GCN)算法在反洗钱识别中的应用及代码实现

该算法通过自动学习节点之间的权重信息,能够更好地捕捉AML数据中的关系和结构,从而提高反洗钱识别的准确性和效果。传统的AML识别方法通常依赖于专家定义的规则和手动提取的特征,但这些方法在应对大规模和复杂的数据时存在一定的局限性。GCN通过在图上执行卷积操作来捕捉节点之间的关系和结构信息,从而实现对图数据的学习和表示。自动权重化图卷积网络是一种基于GCN的改进算法,能够自动学习节点之间的权重信息,从而更好地捕捉图中的关系。利用自动计算的权重值执行图卷积操作。根据节点之间的相似度,计算每个节点的权重。

syntax_api860的博客 377

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

多级缓存架构设计:本地缓存Caffeine与分布式缓存Redis的一致性协同方案

Java 16+ 的record非常适合做这种不可变的数据传输对象。/*** 缓存失效广播消息* @param cacheName 缓存名称(分区)* @param key 具体失效的Key* @param sourceInstanceId 发出消息的实例ID(防止自己删自己,虽无大碍但可优化)*/做多级缓存,不是为了炫技,而是为了保命(保系统的命)。1.分层原则:L1 挡住高频重复读,L2 挡住全量读,DB 只负责兜底和写入。2.一致性方案•低一致性要求。

varyall的专栏 82

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

Caffeine缓存实战:5种分布式一致性方案对比与选型指南

本文深入探讨了Caffeine缓存在分布式环境中的5种一致性方案,包括事件通知模式、发布订阅模式、版本号控制等,帮助开发者在性能、实时性和系统复杂度之间找到最佳平衡点。特别针对分布式环境下的数据一致性问题,提供了详细的代码实现和实测数据对比。

weixin_29198045的博客 437

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

本地缓存+分布式缓存,性能翻倍还是踩坑?Caffeine+Redis实战全解析!

本地缓存+分布式缓存不是银弹,但用得好,性能真能飞。Caffeine+Redis组合拳,既快又稳,但也要注意一致性和容量管理。关注服务端技术精选,获取更多后端实战干货!你在多级缓存中遇到过哪些坑?欢迎在评论区分享你的故事!

我爱娃哈哈的专栏 554

Spring Boot 3 缓存:Caffeine 与 Redis 集成

Bean// 1. Caffeine 本地缓存管理器// 2. Redis 缓存管理器.build();// 3. 组合两级缓存(先查 Caffeine,未命中则查 Redis)

2501_93894473的博客 450
上一篇: GaussDB(DWS)数据转换实战:字符串、二进制与十六进制的高效互转技巧
下一篇: 避开这些坑!Stateflow断点调试的5个常见错误与正确姿势
sql99
博客等级 码龄19年 168粉丝 1905原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值