库存安全

下单逻辑

  1. 判断库存
  2. 减去库存
  3. 生成订单
 /**
 * 下单操作1
 *
 * @param req
 */
private place1($req) {
    $user = userDao.findOne($req.getUserId());
    $product = productDao.findOne($req.getProductId());
    //下单数量
    $num = $req.getNum();
    //可用库存
    $availableNum = $product.getAvailableNum();
    //可用预定
    if ($availableNum >= $num) {
        //减库存
        $count = productDao.reduceStock1($product.getId(), $num);
        if ($count == 1) {
            //生成订单
            createOrders($user, $product, $num);
        } else {
            logger.info("库存不足 3");
        }
        return 1;
    } else {
        logger.info("库存不足 4");
        return -1;
    }
}

低并发处理方案

  • redis锁,限制并发
/**
 * 采用 Redis 锁  通一个时间 只能一个 请求修改 同一个商品的数量
 * <p>
 * 缺点并发不高,同时只能一个用户抢占操作,用户体验不好!
 *
 * @param req
 */
public void placeOrder2($req) {
    $lockKey = "placeOrder:" + $req.getProductId();
    $isLock = redisService.lock($lockKey);
    if (!$isLock) {
        logger.info("系统繁忙稍后再试!");
        return 2;
    }
    //place2(req);
    place1($req);
    //这两个方法都可以
    redisService.unLock($lockKey);
}

高并发处理方案

  1. 商品库存保存到redis,redis increment操作为原子性
  2. 库存检查与减少库存不是原子性,以increment > 0为准
public placeOrder3($req) {
    $key = "product:" + $req.getProductId();
    // 先检查 库存是否充足
    $num = (int) redisService.get($key);
    if ($num < $req.getNum()) {
        logger.info("库存不足 1");
    }else{
        //不可在这里下单减库存,否则导致数据不安全, 情况类似 方法1;
    }
    //减少库存
    $value = redisService.increment($key, -$req.getNum().longValue());
    //库存充足
    if ($value >= 0) {
        logger.info("成功抢购 ! ");
        //TODO 真正减 扣 库存 等操作 下单等操作  ,这些操作可用通过 MQ 或 其他方式
        place1(req);
    } else {
        //库存不足,需要增加刚刚减去的库存
        redisService.increment($key, $req.getNum().longValue());
        logger.info("库存不足 2 ");
    }
}

高并发核心技术-MQ

安装服务端

  1. 安装Erlanghttps://www.erlang.org/downloads
  2. 安装RabbitMQhttps://www.rabbitmq.com/
  3. 配置环境变量

– ERLANG_HOME=C:\soft\erl10.4
– RABBITMQ_SERVER=C:\soft\RabbitMQ Server\rabbitmq_server-3.7.17
– Path=%ERLANG_HOME%\bin;%RABBITMQ_SERVER%\sbin
4. 基础命令 进入C:\soft\RabbitMQ Server\rabbitmq_server-3.7.17\sbin
– 查看mq状态

rabbitmqctl status
  • 安装 RabbitMQWeb的管理插件
rabbitmq-plugins enable rabbitmq_management
  • RabbitMQWeb管理,输入命令行查看用户列表
rabbitmqctl.bat list_users
  • http://127.0.0.1:15672/ quest quest

php客户端开发代码 AMQP

引用amqplib包
composer require php-amqplib/php-amqplib
代码实现部分
// config.php
<?php
return [
    'vendor' => [
        'path' => dirname(__DIR__) . '/vendor'
    ],
    'rabbitmq' => [
        'host' => '127.0.0.1',
        'port' => '5672',
        'login' => 'guest',
        'password' => 'guest',
        'vhost' => '/'
    ]
];
// send.php 生产者
<?php
$config = require "./config.php";

require_once $config['vendor']['path'] . '/autoload.php';

use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;

// 1.创建连接
$connection = new AMQPStreamConnection($config['rabbitmq']['host'], $config['rabbitmq']['port'],
    $config['rabbitmq']['login'], $config['rabbitmq']['password'], $config['rabbitmq']['vhost']);
// 2.(在连接中)创建频道
$channel = $connection->channel();

//发送方其实不需要设置队列, 不过对于持久化有关,建议执行该行
// 3.声明频道中队列
$channel->queue_declare('hello', false, false, false, false);
// 4. 发送消息
$msg = new AMQPMessage('Hello World!');
$channel->basic_publish($msg, '', 'hello');

echo " [x] Sent 'Hello World!'\n";
// 5. 关闭频道
$channel->close();
// 6. 关闭连接
$connection->close();
<?php
// receive.php
$config = require "./config.php";

require_once $config['vendor']['path'] . '/autoload.php';

use PhpAmqpLib\Connection\AMQPStreamConnection;
// use PhpAmqpLib\Message\AMQPMessage;

// 1.创建连接
$connection = new AMQPStreamConnection($config['rabbitmq']['host'], $config['rabbitmq']['port'],
    $config['rabbitmq']['login'], $config['rabbitmq']['password'], $config['rabbitmq']['vhost']);
// 2.创建频道
$channel = $connection->channel();
// 3.声明队列
$channel->queue_declare('hello', false, false, false, false);

echo ' [*] Waiting for messages. To exit press CTRL+C', "\n";

$callback = function($msg) {
    echo " [x] Received ", $msg->body, "\n";
};
// 4.接收消息
$channel->basic_consume('hello', '', false, true, false, false, $callback);

//while(count($channel->callbacks)) {
//    $channel->wait();
//}
// 5.检测消费者,存在则频道等待中
while($channel->is_consuming()) {
    $channel->wait();
}

$channel->close();
$connection->close();
Scroll to Top