首页 新能源汽车

苍穹外卖菜品管理优化:新增与删除的高并发解决方案

字数: (9662)
阅读: (1038)
内容摘要:苍穹外卖菜品管理优化:新增与删除的高并发解决方案,

在苍穹外卖这种高并发的餐饮系统中,菜品的新增和删除操作看似简单,但在实际应用中却可能面临严重的性能瓶颈。尤其是在促销活动期间,大量商家同时操作菜品,很容易出现数据一致性问题,影响用户体验。本文将深入探讨这一问题,并提供具体的代码和配置方案。

问题场景重现:并发导致的库存不一致

假设我们有一个菜品“麻辣小龙虾”,库存为 100 份。在促销期间,多个商家同时修改该菜品的信息(如价格、库存),如果没有合适的并发控制机制,可能会出现以下问题:

  1. 超卖:多个请求同时读取到剩余库存为 1,然后都执行减库存操作,导致库存变为负数。
  2. 数据覆盖:一个请求更新了菜品信息,但另一个请求稍后也更新了,导致前一个请求的修改被覆盖。

底层原理深度剖析:分布式锁与乐观锁

要解决上述问题,我们需要引入并发控制机制。常见的并发控制方案包括:

苍穹外卖菜品管理优化:新增与删除的高并发解决方案
  1. 悲观锁(Pessimistic Locking):在修改数据之前,先锁定数据,防止其他线程修改。例如,在数据库中使用 SELECT ... FOR UPDATE 语句。
  2. 乐观锁(Optimistic Locking):在更新数据时,检查数据是否被其他线程修改过。常见的实现方式是使用版本号或时间戳。
  3. 分布式锁:当应用采用分布式架构时,需要使用分布式锁来保证多个服务实例对同一资源的互斥访问。常用的分布式锁方案包括 Redis 分布式锁和 ZooKeeper 分布式锁。

考虑到苍穹外卖的高并发特性,以及对性能的极致追求,我们选择乐观锁结合 Redis 分布式锁的方案。乐观锁避免了长时间的锁等待,Redis 分布式锁则保证了在分布式环境下的数据一致性。

代码/配置解决方案:基于 Redis 分布式锁的菜品修改

以下是一个使用 Redis 分布式锁控制菜品修改的示例代码(Java):

苍穹外卖菜品管理优化:新增与删除的高并发解决方案
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

public class DishService {

    private JedisPool jedisPool; // Redis 连接池
    private DishRepository dishRepository; // 菜品数据访问层

    public DishService(JedisPool jedisPool, DishRepository dishRepository) {
        this.jedisPool = jedisPool;
        this.dishRepository = dishRepository;
    }

    public boolean updateDish(Long dishId, int newStock, int version) {
        String lockKey = "dish:" + dishId; // Redis 锁的 key
        String lockValue = String.valueOf(System.currentTimeMillis() + 5000); // 锁的过期时间(5 秒)

        try (Jedis jedis = jedisPool.getResource()) {
            // 尝试获取锁
            String result = jedis.set(lockKey, lockValue, "NX", "PX", 5000); // NX: 不存在才设置, PX: 毫秒

            if ("OK".equals(result)) {
                // 获取锁成功
                Dish dish = dishRepository.findById(dishId); // 获取菜品信息
                if(dish.getVersion() != version) {
                    return false; //版本号不一致,更新失败
                }
                dish.setStock(newStock);
                dish.setVersion(version + 1);
                dishRepository.update(dish); // 更新菜品信息
                return true; // 更新成功
            } else {
                // 获取锁失败,稍后重试或者返回错误信息
                return false;
            }
        } finally {
            // 释放锁(需要原子操作,防止误删)
            try (Jedis jedis = jedisPool.getResource()) {
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                jedis.eval(script, 1, lockKey, lockValue);
            }
        }
    }
}

代码解释:

  1. jedis.set(lockKey, lockValue, "NX", "PX", 5000):使用 Redis 的 SETNX 命令尝试获取锁,如果 key 不存在则设置成功,并设置过期时间为 5 秒。
  2. dish.getVersion():获取菜品的版本号,用于乐观锁的判断。
  3. dishRepository.update(dish):更新菜品信息,同时增加版本号。
  4. jedis.eval(script, 1, lockKey, lockValue):使用 Lua 脚本原子性地判断锁是否属于当前线程,并释放锁。

数据库表结构建议:

苍穹外卖菜品管理优化:新增与删除的高并发解决方案
CREATE TABLE `dish` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `name` varchar(255) NOT NULL COMMENT '菜品名称',
  `stock` int(11) NOT NULL DEFAULT '0' COMMENT '库存',
  `price` decimal(10,2) NOT NULL COMMENT '价格',
  `version` int(11) NOT NULL DEFAULT '0' COMMENT '版本号,用于乐观锁',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='菜品表';

Nginx 配置(可选,用于负载均衡):

如果应用采用多实例部署,可以使用 Nginx 进行负载均衡。以下是一个简单的 Nginx 配置:

苍穹外卖菜品管理优化:新增与删除的高并发解决方案
upstream dish_service {
    server 192.168.1.100:8080;
    server 192.168.1.101:8080;
}

server {
    listen 80;
    server_name api.cangqiong.com;

    location /dish/ {
        proxy_pass http://dish_service/dish/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

实战避坑经验总结

  1. 锁的过期时间:设置合理的锁过期时间非常重要。如果过期时间太短,可能导致锁被提前释放,引发并发问题。如果过期时间太长,可能导致死锁。建议设置一个略大于业务处理时间的过期时间,并增加自动续租机制(Watchdog)。
  2. Redis 集群:在高并发场景下,单节点的 Redis 可能无法承受压力。建议使用 Redis 集群(如 Redis Cluster 或 Redis Sentinel)来提高可用性和性能。
  3. 数据库索引:在菜品表上建立合适的索引,可以提高查询性能。例如,可以为 dishIdversion 字段建立联合索引。
  4. 重试机制:如果获取锁失败,可以采用重试机制,但需要设置合理的重试次数和间隔,避免死循环。
  5. 熔断降级:当 Redis 服务出现故障时,可以采用熔断降级策略,例如直接返回默认值或者提示用户稍后重试,保证系统的可用性。
  6. 监控与告警:建立完善的监控和告警机制,及时发现和解决问题。可以监控 Redis 的 CPU 使用率、内存使用率、连接数等指标。

通过以上方案,可以有效地解决苍穹外卖菜品新增、删除操作中的并发问题,提高系统的稳定性和性能。同时,合理的 Nginx 配置能够进一步提升系统的整体负载能力。

苍穹外卖菜品管理优化:新增与删除的高并发解决方案

转载请注明出处: 加班到秃头

本文的链接地址: http://m.acea5.store/blog/414371.SHTML

本文最后 发布于2026-04-07 13:40:15,已经过了20天没有更新,若内容或图片 失效,请留言反馈

()
您可能对以下文章感兴趣
评论
  • 猫奴本奴 4 天前
    菜品数量非常多的时候,redis锁会不会导致性能下降?有什么优化的方法?
  • 蓝天白云 1 天前
    版本号控制很关键,避免了幻读问题。学习了!
  • 秃头程序员 4 天前
    菜品数量非常多的时候,redis锁会不会导致性能下降?有什么优化的方法?
  • 星河滚烫 1 天前
    版本号控制很关键,避免了幻读问题。学习了!