缓存一致性方案:pig平台Redis与数据库双写一致性

缓存一致性方案:pig平台Redis与数据库双写一致性

【免费下载链接】pig 【免费下载链接】pig 项目地址: https://gitcode.com/gh_mirrors/pig/pig

1. 引言:缓存双写一致性的痛点与挑战

你是否在分布式系统开发中遇到过以下问题:用户明明更新了数据,页面却显示旧内容?订单状态已经支付,后台查询却显示未支付?这些令人头疼的问题往往源于缓存(Cache)与数据库(Database)之间的数据一致性问题。在高并发场景下,如何保证Redis缓存与数据库的数据同步,成为影响系统可靠性的关键因素。

本文将以pig微服务平台为实际案例,深入剖析缓存双写一致性的六大实现方案,重点解读pig平台如何通过Spring Cache注解+Redis实现高效可靠的缓存一致性策略,并提供完整的代码实现与最佳实践指南。

读完本文你将获得:

  • 缓存与数据库一致性问题的根源分析
  • 六种缓存更新策略的优缺点对比
  • pig平台缓存一致性实现的源码级解析
  • 基于Spring Cache的企业级缓存方案设计
  • 缓存并发问题的解决方案与性能优化技巧

2. 缓存一致性问题的技术本质

2.1 数据不一致的三大场景

缓存与数据库的数据不一致通常发生在以下三种场景:

mermaid

2.2 一致性问题的数学模型

缓存一致性问题本质上是分布式系统中的最终一致性挑战。我们可以用以下公式定义缓存一致性的程度:

一致性程度 = 1 - (缓存脏数据量 / 总数据量) * 不一致持续时间

理想情况下,我们希望一致性程度达到100%,但这在实际工程中往往需要在性能与一致性之间做出权衡。

3. 缓存更新策略全景对比

3.1 六大缓存更新策略对比表

策略名称实现方式一致性性能复杂度适用场景
Cache-Aside读:缓存有则读缓存,无则读库并回写缓存
写:先更库,后删缓存
大多数读多写少场景
Read-Through读操作通过缓存抽象层,缓存未命中时自动加载数据库数据底层存储访问复杂的场景
Write-Through写操作通过缓存抽象层,同步更新缓存和数据库强一致性要求场景
Write-Behind写操作先更新缓存,异步批量更新数据库极高非实时数据统计场景
Cache-Update先更新缓存,后更新数据库极少使用,仅适用于临时数据
TTL自动过期设置缓存过期时间,依赖过期机制更新极低容忍短期不一致的场景

3.2 各策略的时序图对比

Cache-Aside策略时序图:

mermaid

Write-Through策略时序图:

mermaid

4. pig平台缓存一致性方案实现

4.1 pig平台缓存架构概览

pig平台采用多级缓存架构,结合本地缓存(Caffeine)和分布式缓存(Redis),实现高性能与高一致性的平衡:

mermaid

4.2 Spring Cache + Redis实现方案

pig平台主要基于Spring Cache注解结合Redis实现缓存一致性,核心注解包括:

  • @Cacheable: 缓存查询结果
  • @CacheEvict: 清除缓存
  • @CachePut: 更新缓存
  • @Caching: 组合缓存操作
  • @CacheConfig: 类级缓存配置

4.3 核心代码实现解析

4.3.1 缓存常量定义
/**
 * 缓存常量定义
 */
public interface CacheConstants {
    // 用户信息缓存
    String USER_DETAILS = "user_details";
    
    // 字典缓存
    String DICT_DETAILS = "dict_details";
    
    // 菜单缓存
    String MENU_DETAILS = "menu_details";
    
    // 角色缓存
    String ROLE_DETAILS = "role_details";
    
    // 参数缓存
    String PARAMS_DETAILS = "params_details";
    
    // 客户端缓存
    String CLIENT_DETAILS_KEY = "client_details";
    
    // 项目oauth缓存前缀
    String PROJECT_OAUTH_ACCESS = "pig_oauth:access";
}
4.3.2 用户信息缓存实现
@Service
public class SysUserServiceImpl implements SysUserService {
    private final SysUserMapper sysUserMapper;
    private final RedisTemplate<String, Object> redisTemplate;
    
    // 构造函数注入依赖...
    
    /**
     * 根据用户名查询用户信息,使用缓存
     */
    @Override
    @Cacheable(value = CacheConstants.USER_DETAILS, key = "#username", unless = "#result == null")
    public SysUser getUserByUsername(String username) {
        SysUser user = sysUserMapper.selectOne(new QueryWrapper<SysUser>()
            .lambda().eq(SysUser::getUsername, username));
        if (user == null) {
            return null;
        }
        // 获取用户角色信息
        List<String> roles = sysRoleService.findRolesByUserId(user.getUserId());
        user.setRoles(roles);
        return user;
    }
    
    /**
     * 更新用户信息,清除缓存
     */
    @Override
    @CacheEvict(value = CacheConstants.USER_DETAILS, key = "#userDto.username")
    public Boolean updateUser(UserDTO userDto) {
        SysUser user = new SysUser();
        BeanUtils.copyProperties(userDto, user);
        return sysUserMapper.updateById(user) > 0;
    }
    
    /**
     * 删除用户,清除缓存
     */
    @Override
    @CacheEvict(value = CacheConstants.USER_DETAILS, key = "#username")
    public Boolean removeUser(String username) {
        return sysUserMapper.delete(new QueryWrapper<SysUser>()
            .lambda().eq(SysUser::getUsername, username)) > 0;
    }
}
4.3.3 字典缓存实现
@RestController
@RequestMapping("/dict")
public class SysDictController {
    private final SysDictService sysDictService;
    
    // 构造函数注入依赖...
    
    /**
     * 根据字典类型查询字典数据,使用缓存
     */
    @GetMapping("/type/{type}")
    @Cacheable(value = CacheConstants.DICT_DETAILS, key = "#type", unless = "#result.data.isEmpty()")
    public R<List<SysDictItem>> getDictByType(@PathVariable String type) {
        List<SysDictItem> dictItems = sysDictService.getDictByType(type);
        return R.ok(dictItems);
    }
    
    /**
     * 新增字典,清除所有字典缓存
     */
    @PostMapping
    @CacheEvict(value = CacheConstants.DICT_DETAILS, allEntries = true)
    public R<Boolean> saveDict(@Valid @RequestBody SysDict sysDict) {
        return R.ok(sysDictService.save(sysDict));
    }
    
    /**
     * 更新字典,清除对应类型的字典缓存
     */
    @PutMapping
    @CacheEvict(value = CacheConstants.DICT_DETAILS, key = "#sysDict.dictType")
    public R<Boolean> updateDict(@Valid @RequestBody SysDict sysDict) {
        return R.ok(sysDictService.updateById(sysDict));
    }
}

4.4 缓存一致性保障机制

pig平台通过以下多层机制保障缓存一致性:

  1. 延迟双删策略:在更新数据库后,先删除缓存,延迟一段时间(如500ms)后再次删除缓存,解决缓存更新过程中的并发问题。
/**
 * 延迟双删工具类
 */
@Component
public class CacheDelayUtils {
    private final RedisTemplate<String, Object> redisTemplate;
    private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
    
    // 构造函数注入依赖...
    
    /**
     * 延迟双删
     * @param key 缓存key
     * @param delay 延迟时间(毫秒)
     */
    public void delayDoubleDelete(String key, long delay) {
        // 立即删除
        redisTemplate.delete(key);
        // 延迟删除
        executorService.schedule(() -> redisTemplate.delete(key), delay, TimeUnit.MILLISECONDS);
    }
    
    /**
     * 延迟双删(默认延迟500ms)
     * @param key 缓存key
     */
    public void delayDoubleDelete(String key) {
        delayDoubleDelete(key, 500);
    }
}
  1. 缓存过期时间设置:为所有缓存设置合理的过期时间,作为防止缓存不一致的最后一道防线。
/**
 * Redis缓存配置
 */
@Configuration
@EnableCaching
public class RedisCacheConfig {
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        // 默认配置
        RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofHours(1))  // 默认缓存过期时间1小时
            .serializeKeysWith(RedisSerializationContext.SerializationPair
                .fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(RedisSerializationContext.SerializationPair
                .fromSerializer(new GenericJackson2JsonRedisSerializer()))
            .disableCachingNullValues();
        
        // 不同缓存设置不同的过期时间
        Map<String, RedisCacheConfiguration> configs = new HashMap<>();
        configs.put(CacheConstants.USER_DETAILS, defaultConfig.entryTtl(Duration.ofMinutes(30)));
        configs.put(CacheConstants.DICT_DETAILS, defaultConfig.entryTtl(Duration.ofHours(24)));
        configs.put(CacheConstants.MENU_DETAILS, defaultConfig.entryTtl(Duration.ofHours(12)));
        configs.put(CacheConstants.ROLE_DETAILS, defaultConfig.entryTtl(Duration.ofHours(12)));
        
        return RedisCacheManager.builder(connectionFactory)
            .cacheDefaults(defaultConfig)
            .withInitialCacheConfigurations(configs)
            .transactionAware()
            .build();
    }
}
  1. 缓存更新重试机制:对于缓存删除失败的情况,通过重试机制确保最终删除成功。
/**
 * 缓存重试删除工具类
 */
@Component
public class CacheRetryUtils {
    private final RedisTemplate<String, Object> redisTemplate;
    private static final Logger log = LoggerFactory.getLogger(CacheRetryUtils.class);
    
    // 构造函数注入依赖...
    
    /**
     * 带重试机制的缓存删除
     * @param key 缓存key
     * @param retryCount 重试次数
     * @param interval 重试间隔(毫秒)
     * @return 是否删除成功
     */
    public boolean deleteWithRetry(String key, int retryCount, long interval) {
        for (int i = 0; i <= retryCount; i++) {
            try {
                Boolean result = redisTemplate.delete(key);
                if (Boolean.TRUE.equals(result)) {
                    return true;
                }
                if (i < retryCount) {
                    Thread.sleep(interval);
                }
            } catch (Exception e) {
                log.error("缓存删除失败,key: {}, 重试次数: {}", key, i, e);
                if (i < retryCount) {
                    try {
                        Thread.sleep(interval);
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                        return false;
                    }
                }
            }
        }
        return false;
    }
}

5. pig平台缓存性能优化实践

5.1 缓存粒度控制策略

pig平台根据数据访问特征,采用不同的缓存粒度:

  1. 粗粒度缓存:适用于变动频率低、访问频率高的数据,如字典表、配置参数等。
// 粗粒度缓存示例:缓存整个字典类型的数据
@Cacheable(value = CacheConstants.DICT_DETAILS, key = "#type")
public List<SysDictItem> getDictByType(String type) {
    return sysDictItemMapper.selectList(new QueryWrapper<SysDictItem>()
        .lambda().eq(SysDictItem::getDictType, type)
        .orderByAsc(SysDictItem::getSort));
}
  1. 细粒度缓存:适用于变动频率高但部分字段变动少的数据,如用户基本信息和权限信息分离缓存。
// 细粒度缓存示例:分别缓存用户基本信息和权限信息
@Cacheable(value = CacheConstants.USER_BASE_INFO, key = "#userId")
public UserBaseInfo getUserBaseInfo(Long userId) {
    // 查询用户基本信息
}

@Cacheable(value = CacheConstants.USER_PERMISSIONS, key = "#userId")
public List<String> getUserPermissions(Long userId) {
    // 查询用户权限信息
}

5.2 缓存穿透防护

pig平台通过布隆过滤器空值缓存两种机制防止缓存穿透:

/**
 * 布隆过滤器配置
 */
@Configuration
public class BloomFilterConfig {
    @Bean
    public BloomFilter<Long> userIdBloomFilter() {
        // 预计数据量100万,误判率0.01
        BloomFilter<Long> filter = BloomFilter.create(Funnels.longFunnel(), 1000000, 0.01);
        // 初始化加载所有用户ID到布隆过滤器
        List<Long> userIds = sysUserMapper.selectObjs(new QueryWrapper<SysUser>()
            .select("user_id").lambda())
            .stream()
            .map(obj -> (Long) obj)
            .collect(Collectors.toList());
        userIds.forEach(filter::put);
        return filter;
    }
}

/**
 * 用户服务中的缓存穿透防护
 */
@Service
public class SysUserServiceImpl implements SysUserService {
    private final BloomFilter<Long> userIdBloomFilter;
    
    // 构造函数注入依赖...
    
    @Override
    public UserBaseInfo getUserBaseInfo(Long userId) {
        // 布隆过滤器判断ID是否存在,不存在直接返回null
        if (!userIdBloomFilter.mightContain(userId)) {
            return null;
        }
        
        // 查询缓存
        UserBaseInfo userBaseInfo = (UserBaseInfo) redisTemplate.opsForValue().get(
            CacheConstants.USER_BASE_INFO + ":" + userId);
        
        if (userBaseInfo == null) {
            // 查询数据库
            userBaseInfo = sysUserMapper.selectUserBaseInfoById(userId);
            
            if (userBaseInfo != null) {
                // 缓存存在的用户信息
                redisTemplate.opsForValue().set(
                    CacheConstants.USER_BASE_INFO + ":" + userId, 
                    userBaseInfo, 
                    30, TimeUnit.MINUTES);
            } else {
                // 缓存空值,设置较短的过期时间
                redisTemplate.opsForValue().set(
                    CacheConstants.USER_BASE_INFO + ":" + userId, 
                    new UserBaseInfo(), 
                    5, TimeUnit.MINUTES);
            }
        }
        
        // 返回结果(如果是空对象则返回null)
        return userBaseInfo.getId() == null ? null : userBaseInfo;
    }
}

5.3 缓存击穿防护

pig平台通过互斥锁防止缓存击穿:

/**
 * 缓存击穿防护工具类
 */
@Component
public class CacheBreakdownUtils {
    private final StringRedisTemplate stringRedisTemplate;
    
    // 构造函数注入依赖...
    
    /**
     * 获取缓存,带互斥锁机制防止缓存击穿
     */
    public <T> T getWithLock(String key, Supplier<T> supplier, long expireTime, TimeUnit timeUnit) {
        // 1. 尝试从缓存获取
        String value = stringRedisTemplate.opsForValue().get(key);
        if (StringUtils.isNotBlank(value)) {
            return JSON.parseObject(value, new TypeReference<T>() {});
        }
        
        // 2. 获取互斥锁
        String lockKey = "lock:" + key;
        boolean locked = false;
        try {
            locked = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "1", 300, TimeUnit.SECONDS);
            
            if (locked) {
                // 3. 再次检查缓存,防止并发线程已经加载过数据
                value = stringRedisTemplate.opsForValue().get(key);
                if (StringUtils.isNotBlank(value)) {
                    return JSON.parseObject(value, new TypeReference<T>() {});
                }
                
                // 4. 从数据库加载数据
                T result = supplier.get();
                
                // 5. 缓存数据
                if (result != null) {
                    stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(result), expireTime, timeUnit);
                }
                return result;
            } else {
                // 6. 获取锁失败,等待后重试
                Thread.sleep(50);
                return getWithLock(key, supplier, expireTime, timeUnit);
            }
        } catch (Exception e) {
            log.error("缓存获取异常,key: {}", key, e);
            return null;
        } finally {
            // 7. 释放锁
            if (locked) {
                stringRedisTemplate.delete(lockKey);
            }
        }
    }
}

5.4 缓存雪崩防护

pig平台通过过期时间随机化多级缓存防止缓存雪崩:

/**
 * 带随机过期时间的缓存工具类
 */
@Component
public class RandomExpireCacheUtils {
    private final RedisTemplate<String, Object> redisTemplate;
    
    // 构造函数注入依赖...
    
    /**
     * 设置带随机过期时间的缓存
     * @param key 缓存key
     * @param value 缓存值
     * @param baseExpire 基础过期时间
     * @param randomRange 随机时间范围(正负)
     * @param timeUnit 时间单位
     */
    public void setWithRandomExpire(String key, Object value, long baseExpire, long randomRange, TimeUnit timeUnit) {
        // 转换为毫秒
        long baseMs = timeUnit.toMillis(baseExpire);
        long randomMs = timeUnit.toMillis(randomRange);
        
        // 生成随机过期时间
        long randomOffset = (long) (Math.random() * randomMs * 2 - randomMs);
        long actualExpireMs = baseMs + randomOffset;
        
        redisTemplate.opsForValue().set(key, value, actualExpireMs, TimeUnit.MILLISECONDS);
    }
}

6. 缓存一致性监控与运维

6.1 缓存监控指标体系

pig平台定义了完善的缓存监控指标,包括:

/**
 * 缓存监控指标
 */
public interface CacheMetrics {
    // 缓存命中率
    double getCacheHitRate();
    
    // 缓存穿透率
    double getCachePenetrationRate();
    
    // 缓存平均响应时间
    double getAverageResponseTime();
    
    // 缓存更新成功率
    double getCacheUpdateSuccessRate();
    
    // 缓存容量使用率
    double getCacheCapacityUsage();
}

6.2 缓存监控实现

/**
 * Redis缓存监控实现
 */
@Component
public class RedisCacheMetrics implements CacheMetrics {
    private final RedisTemplate<String, Object> redisTemplate;
    private final MeterRegistry meterRegistry;
    
    // 构造函数注入依赖...
    
    private final Counter cacheHitCounter = meterRegistry.counter("cache.hit.count");
    private final Counter cacheMissCounter = meterRegistry.counter("cache.miss.count");
    private final Counter cacheUpdateSuccessCounter = meterRegistry.counter("cache.update.success.count");
    private final Counter cacheUpdateFailCounter = meterRegistry.counter("cache.update.fail.count");
    private final Timer cacheResponseTimer = meterRegistry.timer("cache.response.time");
    
    @Override
    public double getCacheHitRate() {
        double hitCount = cacheHitCounter.count();
        double missCount = cacheMissCounter.count();
        if (hitCount + missCount == 0) {
            return 0;
        }
        return hitCount / (hitCount + missCount);
    }
    
    @Override
    public double getCachePenetrationRate() {
        // 实现缓存穿透率计算逻辑
    }
    
    @Override
    public double getAverageResponseTime() {
        return cacheResponseTimer.mean(TimeUnit.MILLISECONDS);
    }
    
    @Override
    public double getCacheUpdateSuccessRate() {
        double successCount = cacheUpdateSuccessCounter.count();
        double failCount = cacheUpdateFailCounter.count();
        if (successCount + failCount == 0) {
            return 0;
        }
        return successCount / (successCount + failCount);
    }
    
    @Override
    public double getCacheCapacityUsage() {
        // 获取Redis内存使用情况
        Properties info = redisTemplate.execute(RedisConnection::info);
        long usedMemory = Long.parseLong(info.getProperty("used_memory"));
        long maxMemory = Long.parseLong(info.getProperty("maxmemory"));
        return maxMemory > 0 ? (double) usedMemory / maxMemory : 0;
    }
}

7. 企业级缓存最佳实践总结

7.1 缓存策略选择指南

选择缓存策略时应考虑以下因素:

  1. 业务一致性要求:金融交易、支付等核心业务需优先保证一致性;非核心的统计分析业务可适当降低一致性要求。

  2. 读写比例:读多写少场景(Cache-Aside);写多读少场景需谨慎使用缓存。

  3. 数据更新频率:高频更新数据(如实时库存)不适合缓存;低频更新数据(如字典配置)适合缓存。

  4. 数据量级:大数据量单条记录(如商品详情)适合缓存;小数据量集合(如下拉列表)可整体缓存。

7.2 缓存设计 checklist

实施缓存方案前,建议使用以下checklist进行全面检查:

  •  缓存key设计是否合理,是否包含业务标识
  •  是否设置了合理的过期时间
  •  是否处理了缓存穿透问题
  •  是否处理了缓存击穿问题
  •  是否处理了缓存雪崩问题
  •  是否有缓存一致性保障机制
  •  是否有缓存监控和告警机制
  •  是否考虑了缓存性能和容量规划
  •  是否有缓存降级和容灾方案
  •  缓存更新策略是否合理

7.3 缓存优化路线图

mermaid

8. 总结与展望

缓存一致性是分布式系统中的经典难题,没有放之四海而皆准的完美解决方案。pig平台通过Cache-Aside策略为主体,结合Spring Cache注解Redis实现了高效可靠的缓存一致性方案,在性能与一致性之间取得了良好平衡。

未来,随着云原生技术的发展,缓存一致性方案将向以下方向演进:

  1. 智能化:基于AI算法自动学习数据访问模式,动态调整缓存策略。

  2. 服务化:将缓存管理抽象为独立服务,统一处理缓存一致性问题。

  3. 云原生:结合Service Mesh等技术,在服务网格层实现透明缓存。

  4. 强一致性:新的分布式缓存协议(如Redis Cluster)提供更强的一致性保证。

希望本文介绍的pig平台缓存一致性方案能为你的项目提供借鉴,在实际应用中,还需根据具体业务场景灵活调整,找到最适合的缓存策略。

如果本文对你有帮助,请点赞、收藏、关注三连,你的支持是我们持续输出高质量技术文章的动力!

下期预告:《分布式事务解决方案:从理论到实践》,深入探讨微服务架构下的事务一致性问题。

【免费下载链接】pig 【免费下载链接】pig 项目地址: https://gitcode.com/gh_mirrors/pig/pig

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值