高并发秒杀系统开发全流程指南(第六期):缓存设计与数据一致性保障
创作时间:
作者:
@小白创作中心
高并发秒杀系统开发全流程指南(第六期):缓存设计与数据一致性保障
引用
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 实现一个高效、可靠的缓存层。在实际开发中,我们需要根据具体的业务需求选择合适的缓存策略,并通过不断的优化和测试提升系统的性能和稳定性。
在接下来的文章中,我们将继续深入探讨秒杀系统的其他核心模块,包括:
- 消息队列配置与异步处理
- 监控体系建设与性能分析
热门推荐
汽车滤芯更换指南:保持引擎健康的秘诀!
瑜伽与力量训练在塑形上有什么不同
如何制作美味田园薯片(用新鲜土豆轻松制作,让口感更健康可口)
Web3D基础: GLTF 文件的关键真动画原理讲解。
双氧水装置的生产与安全
“人与血管同寿”:别让血管问题成为生命的 “定时炸弹”
探索美食之都:四川成都深度旅行攻略指南
达摩祖师:佛性为什么不生不灭?
三世因果的意思是什么
胀接工艺详解:换热器制造中的关键连接技术
影响太阳能电池转换效率的主要原因与改善方法
美国六大顶尖文理学院:学术与社交的完美平衡
基于深度学习的无人机目标检测算法研究
气温骤降、风雪来袭,上海落实各项防御措施,保障城市安全运行
网络兼职套路深,“偏门”来财莫当真!
中文系跨专业考研:挑战与机遇并存
凌晨,廊坊地震,收到预警信息后怎么做?
工业互联网平台赋能制造业数字化转型解决方案
怎样调试好自行车利于自己骑行操控
从学区房看教育资源与房价的复杂纠缠
猫咪零食推荐,9种安全零食整理给你挑
早上运动好还是晚上运动好?研究发现:不同的人“最佳运动时间”不同
21岁中国飞人徐卓一:巴黎奥运110米栏半决赛止步,未来可期
个人所得税筹划基本技巧是什么
绿色建材的基本特征是什么
冬季养鸡秘籍:寒冷季节蛋鸡管理的关键措施
盘点《天龙八部》中不合理的地方,这书竟然这么多漏洞
明朝皇帝一览,揭秘那些尘封的历史记忆
如何在新股市场中进行有效的投资?这种投资策略有哪些具体的步骤和风险?
皮卡丘设计故事:原型是松鼠,以“可爱”为目标