高并发秒杀系统开发全流程指南(第六期):缓存设计与数据一致性保障
创作时间:
作者:
@小白创作中心
高并发秒杀系统开发全流程指南(第六期):缓存设计与数据一致性保障
引用
CSDN
1.
https://blog.csdn.net/2301_78858041/article/details/145675445
在高并发秒杀系统中,缓存设计是提升系统性能和用户体验的关键环节。通过合理设计缓存机制,我们可以显著减少数据库压力、加快响应速度,并提升系统的可用性。本文将聚焦于缓存设计部分,从技术选型到核心策略,再到数据一致性保障,为你详细讲解如何打造一个高效、可靠的缓存层。
缓存的作用与技术选型
缓存的核心作用
- 加速数据访问:将频繁访问的数据存储在内存中,减少数据库访问延迟。
- 缓解数据库压力:通过分担数据库的读写压力,提升系统整体性能。
- 提高系统可用性:在数据库不可用时,缓存可以作为应急数据源。
为什么选择 Redis?
- 高性能:Redis 是基于内存的存储系统,适用于高并发场景。
- 丰富的数据结构:支持字符串、列表、哈希、集合、有序集合等多种数据结构。
- 持久化:支持 RDB 和 AOF 持久化方式,保障数据不丢失。
- 高可用性:支持主从复制和哨兵模式,确保高可用性。
Redis 配置与优化
基础配置
以下是 Redis 的基础配置示例:
# Redis 配置文件示例
port 6379
bind 0.0.0.0
daemonize yes
pidfile /var/run/redis.pid
loglevel notice
logfile /var/log/redis.log
dbfilename dump.rdb
dir /var/lib/redis
save 900 1
save 300 10
save 60 10000
appendonly yes
持久化配置
Redis 提供两种持久化方式:
- RDB 持久化:适合备份和恢复,但可能会丢失部分数据。
save 900 1
save 300 10
save 60 10000
- AOF 持久化:提供更高的数据安全性,但文件较大。
appendonly yes
内存优化
为了提升 Redis 的性能,可以配置最大内存并启用 LRU 等淘汰策略:
maxmemory 4gb
maxmemory-policy allkeys-lru
网络配置
为了提升 Redis 的网络性能,可以调整 TCP 参数:
tcp-backlog 511
timeout 0
keepalive 60
缓存设计的核心策略
热点数据缓存
热点数据是指被高频访问的数据,如热门商品信息。通过将热点数据缓存到 Redis 中,可以显著提升访问速度。
@Service
public class ProductService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private ProductRepository productRepository;
public Product getProductById(String productId) {
// 先从 Redis 中获取
Product product = (Product) redisTemplate.opsForValue().get("product:" + productId);
if (product != null) {
return product;
}
// 如果 Redis 中没有,从数据库中获取
product = productRepository.findById(productId).orElse(null);
if (product != null) {
// 将数据存入 Redis 并设置过期时间
redisTemplate.opsForValue().set("product:" + productId, product, 3600, TimeUnit.SECONDS);
}
return product;
}
}
缓存预热
缓存预热是指在系统启动时预先加载热点数据到 Redis 中,避免初次访问时的延迟。
@Component
public class CachePreheater implements CommandLineRunner {
@Autowired
private ProductService productService;
@Override
public void run(String... args) throws Exception {
List<Product> hotProducts = productService.getHotProducts();
for (Product product : hotProducts) {
productService.getProductById(product.getId().toString());
}
}
}
分布式锁
在高并发场景下,分布式锁可以有效避免并发冲突。Redis 提供了 RedLock 算法来实现高可用的分布式锁。
@Service
public class StockService {
@Autowired
private RedissonClient redissonClient;
@Autowired
private ProductRepository productRepository;
public boolean decreaseStock(String productId, int num) {
RLock lock = redissonClient.getLock("stock_lock:" + productId);
try {
boolean isLocked = lock.tryLock(10, 10, TimeUnit.SECONDS);
if (!isLocked) {
throw new RuntimeException("获取锁失败");
}
Product product = productRepository.findById(productId).orElseThrow(() -> new RuntimeException("商品不存在"));
if (product.getStock() < num) {
throw new RuntimeException("库存不足");
}
product.setStock(product.getStock() - num);
productRepository.save(product);
return true;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("线程中断", e);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
缓存穿透、击穿与雪崩
- 缓存穿透:查询不存在的数据导致数据库压力过大。
- 解决方案:布隆过滤器或缓存空值。
- 缓存击穿:热点数据过期导致大量请求同时访问数据库。
- 解决方案:互斥锁或永不过期。
- 缓存雪崩:大量缓存同时过期导致系统崩溃。
- 解决方案:随机过期时间或续命机制。
布隆过滤器实现
@Component
public class BloomFilterUtil {
private static final int DEFAULT_SIZE = 1 << 24;
private static final int DEFAULT_HASH_COUNT = 5;
private BitSet bitSet = new BitSet(DEFAULT_SIZE);
private Random random = new Random();
public void add(String key) {
for (int i = 0; i < DEFAULT_HASH_COUNT; i++) {
int index = Math.abs(random.nextInt()) % DEFAULT_SIZE;
bitSet.set(index, true);
}
}
public boolean contains(String key) {
for (int i = 0; i < DEFAULT_HASH_COUNT; i++) {
int index = Math.abs(random.nextInt()) % DEFAULT_SIZE;
if (!bitSet.get(index)) {
return false;
}
}
return true;
}
}
永不过期缓存
@Service
public class ProductService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private ProductRepository productRepository;
public Product getProductById(String productId) {
Product product = (Product) redisTemplate.opsForValue().get("product:" + productId);
if (product != null) {
return product;
}
synchronized (this) {
product = (Product) redisTemplate.opsForValue().get("product:" + productId);
if (product != null) {
return product;
}
product = productRepository.findById(productId).orElseThrow(() -> new RuntimeException("商品不存在"));
redisTemplate.opsForValue().set("product:" + productId, product, -1, TimeUnit.SECONDS);
return product;
}
}
}
缓存与数据库的数据一致性
异步刷新
通过异步刷新机制,可以在不影响用户体验的情况下保持缓存与数据库的一致性。
@Service
public class ProductService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private ProductRepository productRepository;
@Async
public void asyncRefreshCache(String productId) {
Product product = productRepository.findById(productId).orElse(null);
if (product != null) {
redisTemplate.opsForValue().set("product:" + productId, product, 3600, TimeUnit.SECONDS);
}
}
}
缓存更新策略
- 写通策略:写数据库时同时更新缓存。
- 读写策略:写数据库时先删除缓存,读时再加载。
- 最终一致性:允许一定时间内的数据不一致。
写通策略实现
@Service
public class ProductService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private ProductRepository productRepository;
public void updateProduct(Product product) {
productRepository.save(product);
redisTemplate.opsForValue().set("product:" + product.getId(), product, 3600, TimeUnit.SECONDS);
}
}
容器化部署与缓存同步
在容器化部署场景下,可以通过 Redis 集群实现缓存同步。
# Redis 集群配置示例
spring:
redis:
cluster:
nodes: 192.168.1.1:6379,192.168.1.2:6379,192.168.1.3:6379
案例分析:库存信息的缓存设计
场景描述
在秒杀场景中,库存信息是最容易成为性能瓶颈的部分。我们需要通过缓存设计来提升库存查询和扣减的效率。
方案设计
- 使用 Redis 缓存库存信息。
- 结合分布式锁实现库存扣减。
- 使用异步刷新机制保持缓存与数据库的一致性。
代码实现
@Service
public class StockService {
@Autowired
private RedisTemplate<String, Integer> redisTemplate;
@Autowired
private ProductRepository productRepository;
public boolean decreaseStock(String productId, int num) {
// 先尝试从 Redis 中扣减库存
Integer stock = redisTemplate.opsForValue().get("stock:" + productId);
if (stock != null && stock >= num) {
redisTemplate.opsForValue().decrement("stock:" + productId, num);
return true;
}
// 如果 Redis 中没有库存或库存不足,从数据库中获取
Product product = productRepository.findById(productId).orElseThrow(() -> new RuntimeException("商品不存在"));
if (product.getStock() < num) {
return false;
}
// 扣减数据库中的库存
product.setStock(product.getStock() - num);
productRepository.save(product);
// 更新 Redis 中的库存
redisTemplate.opsForValue().set("stock:" + productId, product.getStock(), 3600, TimeUnit.SECONDS);
return true;
}
}
总结与展望
通过本文的讲解,我们已经了解了如何基于 Redis 实现一个高效、可靠的缓存层。在实际开发中,我们需要根据具体的业务需求选择合适的缓存策略,并通过不断的优化和测试提升系统的性能和稳定性。
在接下来的文章中,我们将继续深入探讨秒杀系统的其他核心模块,包括:
- 消息队列配置与异步处理
- 监控体系建设与性能分析
热门推荐
髋关节置换术后患者的归家康复指南
脑机接口定义及相关概念
移动硬盘传输速度越来越慢是什么原因
年轻人快行动!改善睡眠质量刻不容缓!
藏在古诗词里的烂漫春日,美到失语
早上空腹跑步好还是吃早餐再跑比较好
十二星座婚配对象完整统计:星座与婚姻匹配详解饼状图
外带食物容器使用指南:哪些可以直接微波加热?
香港林大辉中学组织师生赴南京开展文化交流活动
胃寒和胃热的症状区别
冷冻海鲜的最大优点,很多人都不知道(不是便宜)
面试自我介绍技巧
社会工作者工作稳定吗?深度分析社会工作者的职业前景
第一掌骨骨折治疗的现代观念与循证实践
小小油菜花,不只是“美美哒”!
生产电梯模拟器的软件有哪些
运维工程师简历应该怎么写
股票投资必懂:关键财务指标全解析
Excel表中IF公式的使用指南
压力烧结炉的工作原理及其关键技术要素
奥运巡礼|中国游泳迎来“黄金一代”,咱们打一场富裕仗
服务器部件介绍和选型指南
二级建造师考试案例分析题高分攻略:如何抓住得分点?
奥迪Q5L大灯高度调节按钮位置指南
电动车故障诊断与解决策略:全面指南
砂金和沙金是否相同?
深入探讨:功能安全要求(FSR)与技术安全要求(TSR)的区别
提高“一阳穿三线”选股法成功率的五大要点
小孩牙齿矫正需要注意什么
从欧美到东南亚:海外移民的新选择与新挑战