使用 CacheManager + Redis集群方式 本地缓存不同步的问题

使用 CacheManager + Redis集群方式 本地缓存不同步的问题

老师好, 目前遇到一个问题, 就是在使用Jetcache + redis cluster 集群模式的情况下, 缓存不同步的问题想问下老师:

我的配置(nacos + 本地):

spring:
  servlet:
    multipart:
      enabled: true
      max-file-size: 2MB
      max-request-size: 2MB
  main:
    allow-circular-references: true
    allow-bean-definition-overriding: true
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    default-property-inclusion: non_null
    time-zone: GMT+8
    serialization:
      WRITE_DATES_AS_TIMESTAMPS: false
  data:
    redis:
      password: app-123456!qj
      cluster:
        nodes:
          - 192.168.9.210:7001
          - 192.168.9.210:7002
          - 192.168.9.210:7003
        max-redirects: 5
      timeout: 20000ms
      lettuce:
        pool:
          max-active: 20
          max-idle: 10
          min-idle: 2
          max-wait: 20000ms
        cluster:
          refresh:
            adaptive: true
            period: 30s

identify:
  secret-key: PIhqklxMNarWuqoNFFGJ5QGgesg==hkl1UBGlqopaKHKq9123h1
  access-token:
    expire-time: 604800
auth:
  template: jwt

# shortlink
shortlink:
  cache:
    stream:
      # 启用本地缓存Stream同步
      enabled: true
      # 消费者超时时间(毫秒)
      consumer-timeout: 2000
      # 批量处理大小
      batch-size: 10
      # 重试间隔(毫秒)
      retry-interval: 1000
      # 最大重试次数
      max-retries: 3
      cleanup-interval: 300000
  cluster:
    enable-read-write-split: false
    connection-timeout: 20000
    socket-timeout: 20000
  # 任务调度器线程池配置
  scheduler:
    threadpool:
      # 调度器线程池大小
      poolSize: 8
      # 线程名称前缀                         
      threadNamePrefix: scheduler-                   
      # 等待任务完成的超时时间
      awaitTerminationSeconds: 30
      # 关闭时等待任务完成
      waitForTasksToCompleteOnShutdown: true
  bloom:
    expected-insertions: 10000000
    false-probability: 0.01
    stream:
      enabled: true
      consumer-timeout: 2000
      batch-size: 10
    local:
      prewarm:
        # 本地时间片预热任务周期 (毫秒), 默认5分钟
        fixed-rate-ms: 300000
    # 时间分片配置
    time-slice:
      # 时间分片粒度(6小时)
      hours: 6
      # 本地布隆过滤器保留2天数据(2天 = 48小时 / 6小时 = 8个时间片)
      # 本地保留8个时间片(2天数据)
      local-keep-count: 8
      # Redis布隆过滤器保留8天数据(8天 = 192小时 / 6小时 = 32个时间片)
      # Redis保留32个时间片(8天数据)
      redis-keep-count: 32
  code:
    strategy: DISTRIBUTED_ID
    length: 10
    max-retries: 3
  machine-id: 1

spring:
  cloud:
    nacos:
      username: nacos
      password: nacos
      discovery:
        server-addr: 192.168.9.200:8848
        namespace: app-mico-service-dev
      config:
        import-check:
          enabled: false
        # 当前服务启动后去 nacos 中读取配置文件的后缀
        file-extension: yaml
        # 读取配置的 nacos 地址
        server-addr: 192.168.9.200:8848
        # 读取配置的 nacos 的名空间
        namespace: app-mico-service-dev
        refresh-enabled: true
    sentinel:
      transport:
        dashboard: localhost:9999
      eager: true
      enabled: true
      datasource:
        flow:
          nacos:
            server-addr: 192.168.9.200:8848
            # app-short-link-api-flow-rules
            dataId: ${spring.application.name}-flow-rules
            groupId: SENTINEL_GROUP
            dataType: json
            ruleType: flow
            namespace: app-mico-service-dev
            username: nacos
            password: nacos
        degrade:
          nacos:
            server-addr: 192.168.9.200:8848
            # app-short-link-api-degrade-rules
            dataId: ${spring.application.name}-degrade-rules
            groupId: SENTINEL_GROUP
            dataType: json
            ruleType: degrade
            namespace: app-mico-service-dev
            username: nacos
            password: nacos
  config:
    import:
      - optional:nacos:app-short-link-api.yaml

# 7901, 7911, 7921
server:
  port: 7911
  tomcat:
    uri-encoding: UTF-8
  servlet:
    context-path: /api

# 7999 7989 7979
dubbo:
  application:
    name: ${spring.application.name}
    qos-enable: false
  registry:
    address: nacos://192.168.9.200:8848?namespace=app-mico-service-dev&username=nacos&password=nacos
  protocol:
    name: dubbo
    port: 7989
    threadpool: fixed
    dispatcher: execution
    threads: 500
    accepts: 500
  provider:
    filter: globalDubboExceptionFilter

# jetcache
jetcache:
  ## 统计间隔, 单位分钟, 0 标识不统计
  statIntervalMinutes: 0
  ## 是否将areaName作为缓存key前缀, false更合理
  areaInCacheName: false
  ## 隐藏的包名前缀, 用于缩短自动生成的缓存名
  hiddenPackages: org.cy.micoservice.app.shortlink.api.service.impl
  ## 本地缓存配置
  local:
    default:
      # 本地缓存类型: linked-hashmap 或 caffeine (推荐)
      type: caffeine
      # 本地缓存最大元素数量
      limit: 100000
      # key转换器
      # other choose: fastjson/jackson
      keyConvertor: fastjson2
      # 本地缓存过期时间(毫秒), 15min
#      expireAfterWriteInMillis: 900000
  ## 远程缓存配置(Redis)
  remote:
    default:
      # 远程缓存客户端类型: jedis or lettuce(推荐), 单机: redis.lettuce, 集群: redis.lettuce.cluster
      type: redis.lettuce
      # key转换器
      # other choose: fastjson/fastjson2/jackson
      keyConvertor: fastjson2
      # 广播通道: 用于多节点缓存同步
      # 当缓存更新 / 删除时, 通过 Redis pub/sub 通知其他节点使本地缓存失效
      broadcastChannel: app-short-link-cache-sync
      ## 值编码器 (java / kryo / kryo5)
      # other choose: kryo/kryo5
      valueEncoder: java
      ## 值解码器
      # other choose: kryo/kryo5
      valueDecoder: java
      # 本地缓存过期时间(毫秒), 1 hour
      expireAfterWriteInMillis: 3600000
      mode: cluster
      # 连接池优化
      poolConfig:
        minIdle: 5
        maxIdle: 10
        maxTotal: 200
        maxWaitMillis: 2000
      maxAttempt: 5
      ## remote 缓存连接地址
      uri:
        - redis://:app-123456!qj@192.168.9.210:7001
        - redis://:app-123456!qj@192.168.9.210:7002
        - redis://:app-123456!qj@192.168.9.210:7003

Redis-Cluster 集群信息:
图片描述

本地启动:
图片描述

我的代码:

// 创建 CacheManager 的工厂
@Component
public class JetCacheFactory {

  @Autowired
  private ShortLinkCacheKeyBuilder cacheKeyBuilder;
  @Autowired
  private CacheManager cacheManager;

  public <K,V> Cache<K,V> createJetCache(String name,
                                         Duration localExpire,
                                         Duration remoteExpire,
                                         CacheType cacheType,
                                         int limit,
                                         boolean penetrationProtect,
                                         boolean cacheNullValue,
                                         boolean syncLocal
                                         ) {
    QuickConfig qc = QuickConfig.newBuilder(name)
      .localExpire(localExpire)
      .expire(remoteExpire)
      .cacheType(cacheType)
      .localLimit(limit)
      // 穿透保护
      .penetrationProtect(penetrationProtect)
      // .penetrationProtectTimeout()
      // 缓存 null 值
      .cacheNullValue(cacheNullValue)
      // 开启多节点同步
      .syncLocal(syncLocal)
      .build();
    return cacheManager.getOrCreateCache(qc);
  }
}

// 实例化对象
@Configuration
public class JetCacheConfig {

  public static final Duration TEST_EXPIRE_TIME = Duration.ofMillis(5000);
  public static final Duration LOCAL_EXPIRE_TIME = Duration.ofMinutes(45);
  public static final Duration DEFAULT_EXPIRE_TIME = Duration.ofHours(1);
  public static final Duration HOT_DATA_EXPIRE_TIME = Duration.ofHours(24);
  public static final Duration COUNT_EXPIRE_TIME = Duration.ofDays(7);

  @Autowired
  private ShortLinkCacheKeyBuilder cacheKeyBuilder;

  /**
   * short url cache
   * @param factory
   * @return
   */
  @Bean
  public Cache<String, ShortUrlMapping> shortUrlCache(JetCacheFactory factory) {
    return factory.createJetCache(
      cacheKeyBuilder.buildUrlCacheKey(),
      LOCAL_EXPIRE_TIME,
      DEFAULT_EXPIRE_TIME.plusMillis(ThreadLocalRandom.current().nextInt(30)),
      CacheType.BOTH,
      10000,
      true,
      true,
      true);
  }
}

// 业务代码使用:
  @Autowired
  private Cache<String, ShortUrlMapping> shortUrlCache;
  
  @SentinelResource(
    value = "databaseQuery",
    blockHandler = "databaseQueryBlockHandler",
    fallback = "databaseQueryFallback"
  )
  @Override
  public ShortUrlMapping getShortUrlWithSentinel(String shortCode) {
    return shortUrlCache.computeIfAbsent(shortCode, key -> {
      try {
        RpcResponse<CreateShortUrlRespDTO> response = shortUrlFacade.findByShortCode(key);
        if (response.getData() == null) {
          return null;
        }
        ShortUrlMapping mapping = BeanCopyUtils.convert(response.getData(), ShortUrlMapping.class);
        log.debug("DB加载成功 shortCode={}", key);
        return mapping;
      } catch (Exception e) {
        log.error("数据库查询失败 shortCode={}", key, e);
        throw new RuntimeException(e);
      }
    });
  }

先使用7901的端口进行查询, 然后数据正常写入Redis集群, 然后将redis中的数据删掉, 验证 7911 能否同步本地缓存, 结果还是将请求打到了DB上, 想问下老师大概是什么原因?谢谢~~

正在回答 回答被采纳积分+1

登陆购买课程后可参与讨论,去登陆

1回答
EMing_J 10小时前

同学,你想要达到的效果是?我看着感觉你的场景是正常的才对。首先:你先查询7901的端口,这时候数据正常被缓存进redis和7901的本地缓存里。(注意:此时,7911端口本地还没有缓存到数据)。然后你在访问7911端口前,你把redis的数据删了,删除redis缓存后,你才去访问7911,这时候,根据我课堂讲的多级缓存的查询路径:先是查看本地缓存是否有值(无)--->再查redis(无)--->这时就会去查DB。所以根据你的描述,这个场景是正常的才对。

  • 提问者 Screenly #1

    就是本地的缓存并没有通过 pub/sub 同步到第二个节点,  但是redis的连接客户端能观察到 pub的信息, 有点奇怪来的

    10小时前
  • 提问者 Screenly #2

    貌似多节点的redis集群模式下, pub/sub 不会所有节点都广播, 我把后面的内容看完吧,谢谢老师

    9小时前
问题已解决,确定采纳
还有疑问,暂不采纳

恭喜解决一个难题,获得1积分~

来为老师/同学的回答评分吧

0 星
Java+大数据+AI架构师实战营
  • 参与学习       80    人
  • 解答问题       98    个

专为1–5年开发者,打造极具竞争力的人才培养方案,快速突破技术/职业瓶颈 “三高”架构稳态托底,海量数据秒级吞吐,实时计算,AI智能化融合 大型 “社交+电商” 平台从0到1全程贯穿,高复杂架构落地

了解课程
请稍等 ...
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号

在线咨询

领取优惠

免费试听

领取大纲

扫描二维码,添加
你的专属老师