高并发秒杀系统开发全流程指南(第六期):缓存设计与数据一致性保障
创作时间:
作者:
@小白创作中心
高并发秒杀系统开发全流程指南(第六期):缓存设计与数据一致性保障
引用
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 实现一个高效、可靠的缓存层。在实际开发中,我们需要根据具体的业务需求选择合适的缓存策略,并通过不断的优化和测试提升系统的性能和稳定性。
在接下来的文章中,我们将继续深入探讨秒杀系统的其他核心模块,包括:
- 消息队列配置与异步处理
- 监控体系建设与性能分析
热门推荐
一文读懂全抛式胰岛素泵驱动方式的现状和未来
验证码数据库组成与管理指南
右眼流眼泪的原因是什么
两臂血压不一样?这类人群要小心!
奥拉星手游新手攻略:从入门到精通的全面指南
定制衣柜:个性化空间的完美选择
土地征收是怎么规定的
中国象棋:从北周“象戏”到现代竞技运动的千年演变
肺癌患者,不同治疗阶段的饮食营养管理
4 招删除「Windows 更新」缓存文件,轻松清理硬盘空间
智慧步道设计理念及其对城市的影响
雷声的声音怎么形容?不同场景下雷声的拟声词和形容词!
盖浇饭,才是最适合中国胃的健身餐!
塔罗牌情感运势怎么解读?
套筒价格、种类及选购指南
一文读懂国际海运中DDP术语、流程、注意事项!
御驾亲征:古代皇帝的决策与影响
眼皮下垂看什么科?一文详解5个相关科室
活悦新前 未来产城 黄岩区新前街道:打造“工业特色型”现代化美丽城镇
了解如何最大限度地延长太阳能电池板的使用寿命
网络购物合同取消:法律规定与实务操作
Windows11 蓝牙耳机没反应?10种连接问题及解决方案
环保科普|解锁低碳生活的N种打开方式
做尿常规能查出怀孕吗?
2024年万爆铠最新出装有哪些?如何选择最佳装备?
资本寡头的崛起与影响:从苏联到现代经济全球化
科普知识:香蕉为何成为运动员的"能量包"?
四人斗地主玩法解析:规则、策略与团队合作技巧分享
心前沿丨补充酮体:HFrEF心肌能量代谢新疗法
《冒险大作战》武魂属性选择攻略