高并发秒杀系统开发全流程指南(第六期):缓存设计与数据一致性保障
创作时间:
作者:
@小白创作中心
高并发秒杀系统开发全流程指南(第六期):缓存设计与数据一致性保障
引用
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 实现一个高效、可靠的缓存层。在实际开发中,我们需要根据具体的业务需求选择合适的缓存策略,并通过不断的优化和测试提升系统的性能和稳定性。
在接下来的文章中,我们将继续深入探讨秒杀系统的其他核心模块,包括:
- 消息队列配置与异步处理
- 监控体系建设与性能分析
热门推荐
早孕试纸怎么用?两条杠一定代表怀孕吗?
新疆盘龙古道:608个弯道的“高原天路”自驾攻略
网上查询案底记录的官方渠道有哪些
加强自我修养应该怎么做才好
苹果手机听筒有杂音滋滋怎么清理?一文读懂操作方法
大众科普——自体输血:用自己的血救自己
学校人工湖水污染的治理方案
特斯拉动能回收系统详解:节能与刹车寿命双丰收
Web抽奖活动设计指南:从界面到管理的全方位解析
《山海经》中的人鱼:陵鱼、鱼妇、鲛人,谁才是中国人鱼的象征?
3D应用在可视化大屏,可不仅仅是漂亮,很多是你想不到的。
从战舰造价可知二战德国缺乏造船技术
闭口合同解除:探究关闭合同的关键因素
两个三维点云模型,该用什么算法进行数据融合
Excel中固定公式结果的11种方法
美国女性的地位及其变化
赵云“浑身是胆”的由来:汉水之战的英勇表现
我的世界工业模组的主要功能和玩法
探访“数字福建”,见证行业新变革
日本五日游攻略:如何安排行程,让你玩转东京、大阪和京都!
电影《敦刻尔克》中,如何营造战争紧张刺激的场面气氛?
青平:彩礼须“重礼”而非“重利”
设备故障率是什么?如何降低设备故障率?设备故障率的影响因素有哪些?
开了3年插电混动,5大优点很明显,3大缺点才是痛点,省油不省钱,你认可吗?
高压氧舱到底多少钱?一文搞定疑问
如何写图片上传api
360万!二手绿牌Cybertruck开回家
春天美丽诗篇,原创4首七绝,最是游人贪看处,数声啼鸟隔花闻
室内装饰墙板设计指南,轻松打造绝美轻奢空间!
曼联球迷请放心!降级概率仅0.1%!最差拿39分, 或排名第12