高并发场景下,如何提升苍穹外卖的商品访问速度和购物车操作体验?本文将深入探讨商品缓存和购物车功能的架构设计与优化策略,并结合实际代码案例进行讲解。
问题场景重现:性能瓶颈分析
在业务高峰期,例如午餐和晚餐时段,苍穹外卖的商品浏览和购物车操作会面临巨大的并发压力。如果每次用户请求都直接访问数据库,会导致数据库负载过高,响应时间变慢,甚至出现服务崩溃。常见的性能瓶颈包括:
- 数据库连接数耗尽:大量并发请求导致数据库连接池被打满,新的请求无法获得连接。
- 查询响应延迟:复杂的商品信息查询(例如关联商品分类、评价等)导致查询时间过长。
- 缓存失效雪崩:如果缓存同时失效,大量请求会直接冲击数据库,造成雪崩效应。
底层原理深度剖析
解决上述问题,核心在于引入缓存机制和优化购物车架构。
商品缓存:利用 Redis 等缓存中间件,将热点商品信息存储在内存中,减少数据库访问。常用的缓存策略包括:

- Cache-Aside (旁路缓存):应用程序先从缓存中读取数据,如果缓存未命中,则从数据库读取数据,并将数据写入缓存。
- Cache-Through (穿透缓存):应用程序直接与缓存交互,缓存负责数据的读取和写入。数据库操作对应用程序透明。
- Write-Through (直写缓存):每次写入数据时,同时更新缓存和数据库,保证数据一致性。
- Write-Back (回写缓存):先更新缓存,然后异步地将数据写入数据库,性能较高,但存在数据丢失的风险。
在苍穹外卖的场景下,推荐使用 Cache-Aside 策略,既可以利用缓存的性能优势,又可以保证数据的最终一致性。

购物车功能:购物车功能需要支持高并发的添加、删除、修改操作。常用的购物车架构包括:
- 基于数据库的购物车:将购物车数据存储在数据库中,简单易实现,但性能较差。
- 基于 Redis 的购物车:将购物车数据存储在 Redis 中,性能较高,但需要考虑数据持久化和一致性问题。可以采用 Redis 的 Hash 数据结构存储购物车信息,并设置过期时间。
- 基于 Cookie 的购物车:将购物车数据存储在 Cookie 中,减轻服务器压力,但存在数据安全和容量限制。
对于苍穹外卖这种高并发场景,推荐使用基于 Redis 的购物车,并结合数据库进行持久化。
具体的代码/配置解决方案
1. 商品缓存实现(Java + Redis)
@Service
public class ProductService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private ProductMapper productMapper;
private static final String PRODUCT_CACHE_PREFIX = "product:";
public Product getProductById(Long id) {
String key = PRODUCT_CACHE_PREFIX + id;
Product product = (Product) redisTemplate.opsForValue().get(key);
if (product == null) {
// 缓存未命中,从数据库读取
product = productMapper.selectById(id);
if (product != null) {
// 将数据写入缓存
redisTemplate.opsForValue().set(key, product, 60, TimeUnit.MINUTES); // 设置过期时间为 60 分钟
}
}
return product;
}
// 更新商品信息时,需要同时更新缓存
public void updateProduct(Product product) {
productMapper.updateById(product);
String key = PRODUCT_CACHE_PREFIX + product.getId();
redisTemplate.opsForValue().set(key, product, 60, TimeUnit.MINUTES);
}
// 删除商品时,需要同时删除缓存
public void deleteProduct(Long id) {
productMapper.deleteById(id);
String key = PRODUCT_CACHE_PREFIX + id;
redisTemplate.delete(key);
}
}
2. 购物车功能实现(Java + Redis)
@Service
public class CartService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String CART_KEY_PREFIX = "cart:";
public void addToCart(Long userId, Long productId, int quantity) {
String key = CART_KEY_PREFIX + userId;
String productKey = productId.toString();
// 使用 Redis Hash 存储购物车信息
redisTemplate.opsForHash().increment(key, productKey, quantity); // 增加商品数量
}
public void removeFromCart(Long userId, Long productId, int quantity) {
String key = CART_KEY_PREFIX + userId;
String productKey = productId.toString();
redisTemplate.opsForHash().increment(key, productKey, -quantity); // 减少商品数量
// 如果商品数量为 0,则从购物车中删除
Long currentQuantity = (Long) redisTemplate.opsForHash().get(key, productKey);
if (currentQuantity != null && currentQuantity <= 0) {
redisTemplate.opsForHash().delete(key, productKey);
}
}
public Map<Object, Object> getCart(Long userId) {
String key = CART_KEY_PREFIX + userId;
return redisTemplate.opsForHash().entries(key);
}
// 清空购物车
public void clearCart(Long userId) {
String key = CART_KEY_PREFIX + userId;
redisTemplate.delete(key);
}
}
3. Nginx 配置优化
使用 Nginx 作为反向代理服务器,可以实现负载均衡和静态资源缓存,进一步提升系统性能。以下是一个简单的 Nginx 配置示例:
http {
upstream backend {
server 192.168.1.100:8080; # 后端服务器 1
server 192.168.1.101:8080; # 后端服务器 2
# 可以根据服务器性能设置权重 weight
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend; # 反向代理到后端服务器
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location ~* \.(jpg|jpeg|png|gif|css|js)$ {
expires 30d; # 缓存静态资源 30 天
access_log off; # 关闭静态资源访问日志
}
}
}
实战避坑经验总结
- 缓存穿透:如果缓存和数据库中都不存在某个商品,大量的请求会直接冲击数据库。可以使用布隆过滤器(Bloom Filter) 或设置空值缓存来解决。
- 缓存击穿:某个热点商品缓存过期,大量的请求同时访问该商品,导致数据库压力过大。可以使用互斥锁或设置永不过期的缓存来解决。
- 数据一致性:在更新商品信息时,需要同时更新缓存和数据库,并考虑并发场景下的数据一致性问题。可以使用分布式锁或消息队列来实现最终一致性。
- Redis 集群:为了保证 Redis 的高可用性和可扩展性,可以使用 Redis 集群模式,例如 Redis Sentinel 或 Redis Cluster。 宝塔面板等工具可以简化 Nginx 和 Redis 的部署和管理。
通过以上优化,可以有效提升苍穹外卖在高并发场景下的性能和稳定性,改善用户体验。购物车功能和商品缓存的合理设计是提升性能的关键环节。
冠军资讯
脱发程序员