高迸发 架构设计方案

news2024/7/4 5:24:02


前言

​​在实际生活业务场景开发中,在我们的网站知名度越来越大的时候,随之而来的就是业务体量越来越大,用户群体越来越大,随之而来的技术要求也越来越高,其中核心点对网站的稳定性要求是硬性的。如果一个系统都无法正常提供访问了,那么对用户的体验感应该是极差的吧。本文总结了我所见所闻的知识,来提升网站吞吐能力,应对高迸发,高安全的实现方案。

业务痛点

数据库

一般来说,我们的系统崩溃,首先崩溃的是哪个设备?答案:肯定是数据库。

单机环境下,我们对数据库做  CURD 操作时,是需要占用 I/O 的,哪怕数据库提供了 I/O 缓冲,在大流量下也很容易把数据库干死,所以,我们首要目标是提升数据库的 吞吐 性能,保证 数据库 的稳定性。

读写分离

将 CURD 操作中,写操作(新增、更新、删除)交给 Master 主机操作,读操作(查询)交给 slave 从机操作。主从数据库通过 binlog 日志同步数据。将操作分开,大幅度提升数据的处理能力。其中从库(读库)可 横向扩展

可购买市面上 第三方服务商 提供的专业的数据库服务,如 阿里云RDS、阿里云PloarDb 底层架构中写库为 M-M 架构,采用ID 取模(%)判断是否等于0为基准处理数据是写入db1还是db2。双倍提升写库性能。

连接池

在程序中与 数据库 建立连接是需要消耗 资源 的,在某些后端开发框架的上下文全局环境中,哪怕我不对 数据库 做任何操作,一次请求也需要和 数据库 建立连接(未实现数据库连接 懒加载 功能),大量消耗了 数据库 的资源。以及某些业务场景下,对 数据库 的 I/O消耗不高,但对 数据库 连接的占用特别高(常见如根据主键 id查询某行数据,但流量太大,cpu持续飘高,但I/O指数不高)的场景。

由此的解决方案为建立数据库连接池。

1、基于 php swoole 可实现 数据库 连接池,相关内容可在百度中检索到。

2、采用 第三方服务商 提供的专业数据库服务,如数据库中间件默认就有连接池的实现。(推荐)

索引

索引对 数据库 是一个很重要的概念。它让原本很大数据体量的表能以较小的资源消耗,快速查询到目标数据(

如mysql 索引法:bree + 、hash 。类型:unique(唯一)、normal(普通)、fulltext(全文索引)、spatial(空间索引)

),但如何合理的使用索引是重要的点。

1、我们尽量让每一次查询都命中索引去查询,减少数据库的回表次数,达到最优查询的目的。

2、但不能无节制的去建立索引(如我给一张表每个字段都建立索引,这样是没有任何意义的)。

3、尽量避免子查询,子查询回会一次表,如果子查询表回的数据体量大,mysql需要开相应的内存区来存放回表数据。

4、避免索引失效(在where 中  字段使用表达式、隐式类型替换、不合理的(like "%x"),Or 条件中未使用索引查询,不合理的索引下推 均会导致全表扫描,降低查询速度,给 数据库 增加检索负担)

如何合理的建立索引说明可参考百度其它文档。

缓存

已 redis 为参照,缓存加入方案的目的有以下几点。

1、提升业务接口访问速度(部分业务数据的获取,从缓存中获取数据,因缓存是存放在内存中的,所以 I/O 处理极快)

2、对数据库进行减压出力(减少部分原是从数据库获取的数据途径,改为了从缓存中获取)

业务分析

分析业务场景,哪些数据是经常在读且不易发生变化的(如首页的Banner位显示、用户的基础数据:id、昵称、头像、性别等),这类数据可加入缓存中,在需要的直接从缓存中取出。不用从数据库中获取数据。达到降低数据库压力的目的。

热更新技术

这不是一个新技术,仅是一种设计模式。在后端开发技术中,他监听某个对象是否发生了变化,当发生变化后会把此对象数据同步至相关的缓存中来。

举例:商品库

某个商品的基本信息存放在 数据库 中,因业务需要和提升性能的目的,将商品的基本数据同样拷贝到了 缓存 中。在 业务场景 中,因业务需要,商品管理员在后端更改了商品的名称,而程序监听到这个动作(商品更新)发生后,抛出了商品更新 事件 ,由缓存数据中心监听到此 事件 的发生后,修改缓存中对应商品的信息。(事件机制原理详解,csdn 搜索-倾听岁月,其下有事件说明)

读写分离

 没错,你没看错,redis 也支持 读写分离。类似 mysql 读写分离 的原理,M-S架构,依托binlog日志达到主从数据同步的目的。在redis 6.0 版本之后,工作 cpu 不再仅是单核的了。原理是命令的执行由一个cpu处理执行(单线程处理),但命令产生的I/O操作可以利用多核 cpu 的优势,由多核处理。性能进一步增强。

数据分片

如下图所示

我们将 业务数据 分摊给多个 redis 服务器存储,降低单台物理机或是单节点集群的压力。提升整体性能的设计。

注意事项

1、雪崩:单位时间内,缓存大量失效,且同时有大量的请求进来穿过了 redis 打在了数据库上执行查询操作。

对数据元不设置其过期时间,采用热更新技术+定时更新机制,维护热点数据

2、击穿:单位时间内,某个热点数据源因缓存时间过期失效,且同时有大量的请求进来穿过了 redis 打在了数据库上执行查询操作。

对数据元不设置其过期时间,采用 热更新技术 + 定时更新机制 ,维护热点数据

3、击穿:单位时间内,在缓存中未查找到目标数据,在数据库也没有查找到目标数据。如此大量请求循环。

常见如查询一个不存在的ID

服务器技术

单台服务器的处理能力总有其极限,我们不能说让一个1000W+访问量的系统交给一台机器去单独承受压力(服务器说:我承受了我不该承受的压力),既然是1000W+日活访问,单台服务器很难抗下来,那么我们就只有多加服务器来摊流了。

DNS 权重解析技术

这不是一个新技术,只要你买了域名,在一个域名下面挂载解析的ip后(多个ip),都会给你一个ip权重配比(权重你可以理解为就是解析的百分比)。 一个域名下最多可以解析10个ip(我试过,阿里云的域名解析最多允许我挂载10个ip,其它服务商的域名解析情况怎么样我还不清楚)。使用此技术,我们要处理session问题,将session数据的存储,放置在redis中,所有服务器关于session会话均连接同一台redis或是同一节点redis集群处理。

负载均衡技术(推荐)

核心技术:反向代理。 在负载均衡器下挂载多台服务器,当负载均衡器监听到有新的请求进来后会判断当前下面那些服务器的状态最好,然后优先将此请求交给目标服务器处理。基于 DNS 解析技术(解析的目标ip 地址是负载均衡地址),可达到 10 * 10 = 100 台服务器的挂载。 

文件存储系统(OSS + CDN)

我们的系统中,总是有各种各样的静态资源,如css、js、image、mp3、mp4、其它静态资源等。针对这类资源,我们可以将其独立出去,不从业务服务器上供用户下载,将其放置在第三方文件服务系统上(阿里云的OSS 搭配 CDN加速(支持全球加速) 就不错),降低业务服务器的带宽和读写缓冲区的压力,让业务服务器专注于处理业务逻辑。

开发技术优化

以后端为php开发语言为例,我们采用lnmp 即是  linux + nginx + mysql + php  其中关于nginx和php-fpm的优化如下

nginx

1、keep-alive 长连接应该设置为开启,根据自己的业务需求分析,配置其长连接维持时间,过短或过长都不好,取一个能接受的居中值。

2、工作进程数一般设置为和 cpu 核数相同的工作进程数。   nginx-work 采用惊群模式和类似redis底层的epoll红黑树结构处理请求,具有超高的迸发性能。

php-fpm 

1、php-fpm 的工作进程数一般设为 cpu 核数的2-8倍,具体几倍需要分析自己的业务。

进程序并不是越多越好,进程越多 cpu 就一直在不停的切换进程,结果反而消耗了不必要的资源(进程间的切换)。

2、分析自己的业务场景,是否有可以延时处理的操作。如 欢迎短信|邮件,excel导出,不需要即时反馈的数据统计,资金流转。

针对这类业务复杂但又不是必须立即给用户反馈的业务,可采用 消息队列 技术来处理,降低服务器压力

3、采用php8.x 版本,php8 的新特性 JIT 再一次加速了php的运行速度, JIT 依托扩展 opcode 所以要使用 JIT 一定要先开启 opcode,底层原理为多级缓存,原本是交给vm虚拟机运行的代码,直接编译为机器码缓存,之后在二次复用时直接从缓存获取机器码并在 cpu 上运行,速度极快,绕过了vm虚拟机,直接在 cpu 上执行。

架构设计高级进阶

经典案例 - 秒杀业务场景

说到秒杀,我们可以分析出秒杀业务场景的特点。

1、用户流量瞬时爆发。(大家都是在那1秒钟或是那一分钟来参与,在此时间段内,系统的流量暴涨)

2、库存的安全化。(如2W个库存,可能就在1-2秒就被秒没得了,单位时间内对数据库的压力极大,要保障数据库的安全性)

基于以上业务场景特点设计的架构图如下

思维

1、前端限制:当用户点击秒杀后,秒杀立即变灰(不可再次点击),考虑下用户,当看到一直在请求旋转的时候,我想是个用户都想再去点击一下,这样就产生了一个用户有多个秒杀请求

2、限流:一个用户在某一分钟内最多可请求n次,超出n次我们就提示排队中。

3、多库存记录:一个秒杀商品的库存,我们完全可以将这个库存摊到多个记录上,这样可以避免锁抢占,即是某一个时刻有n个请求在同时抢占一个记录的锁。如果只开一个库存记录,就好像几百万辆车依靠一条车道通信,速度自然很慢,容易产生堵塞,但如果我规划出1W条车道,那么解决这几百万的车流量不是轻轻松松么。

4、合理利用redis来限流:redis 是高性能的,我们可以利用这个特性,来帮助我们执行库存的查询操作。每次redis扣减库存事务执行成功,我们就认为这个用户拿到了通行证,可以执行后续的流程,未拿到通行证的用户将会返回获取通行证或是通知秒杀失败

5、乐观锁与死锁的联合使用,绝对保证商品库存不超出。

简易设计代码说明

step-1 : 先设计一个商品服务,用于管理商品

ShopServiceInterface

<?php
namespace App\Services\Shop;

use App\Models\Shop;

interface ShopServiceInterface
{
    /**
     * Notes:获取商品信息
     * Author:tanyong
     * DateTime:2022/7/13
     * @param int $id 商品id
     * @param array|null $fields 要查询的字段
     * @param boolean 是否锁住记录(需配合事务使用)
     * @return Shop | null
     */
    public function get(int $id,array $fields=null,$lockForUpdate = false);

    /**
     * Notes:增加商品销售额
     * Author:tanyong
     * DateTime:2022/7/14
     * @param Shop $shop 商品对象
     * @param int $num 销售数量
     * @return boolean
     */
    public function incrShopDraw(Shop $shop,int $num);

    /**
     * Notes:增加商品销售额2
     * Author:tanyong
     * DateTime:2022/7/29
     * @param array $where 查询条件
     * @param int $num 销售数量
     * @return int 受影响的行数
     */
    public function incrShopDraw2(array $where, int $num);

    /**
     * Notes:更新商品信息
     * Author:tanyong
     * DateTime:2022/7/13
     * @param Shop $shop 商品id
     * @param array $data 需要更新的数据
     * @return boolean 是否更新成功
     */
    public function update(Shop $shop,array $data);
}

商品服务的实现

ShopService

<?php

namespace App\Services\Shop;

use App\Exceptions\AppException;
use App\Models\Shop;

class ShopService implements ShopServiceInterface
{
    public function get(int $id, array $fields = null, $lockForUpdate = false)
    {
        $query = Shop::query();

        if (!empty($fields)) {
            $query->select($fields);
        }

        if ($lockForUpdate) {
            $query->lockForUpdate();
        }

        return $query->where('id', $id)->first();
    }

    public function update(Shop $shop, array $data)
    {
        if (isset($data['id'])) {
            throw new AppException('不可更新id');
        }

        foreach ($data as $k => $v) {
            $shop->{$k} = $v;
        }

        return $shop->save();
    }

    public function incrShopDraw(Shop $shop, int $num)
    {
        $shop->increment('draw', $num);
    }

    public function incrShopDraw2(array $where, int $num)
    {
        $query = Shop::query();

        return $query->where($where)->increment('draw',$num);
    }
}

step2 - 秒杀会产生订单,所以我们再设计一个订单服务接口

OrderServiceInterface

<?php

namespace App\Services\Order;

use App\Models\Order;
use App\Models\Rely\CreateOrder;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Collection;

interface OrderServiceInterface
{
    /**
     * Notes:创建订单
     * Author:tanyong
     * DateTime:2022/7/13
     * @param CreateOrder $order
     * @return Order
     */
    public function createOrder(CreateOrder $order);

    /**
     * Notes:获取订单 - 复杂搜索
     * Author:tanyong
     * DateTime:2022/7/13
     * @param array $whereComplexSearch 复杂搜索,使用参见\App\Components\ORM\WhereUtil
     * @param array|null $fields 要查询的字段
     * @param null $type 返回类型 取值 App\V2\Components\ORM\OrmResultType
     * @return Collection | LengthAwarePaginator | boolean | null | Order
     */
    public function getComplexSearchOrder(array $whereComplexSearch, array $fields = null, $type = null);
}

制作一个订单服务的实现

<?php

namespace App\Services\Order;

use App\Components\ORM\OrmResultType;
use App\Components\ORM\WhereUtil;
use App\Models\Order;
use App\Models\Rely\CreateOrder;

use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Collection;

class OrderService implements OrderServiceInterface
{
    public function createOrder(CreateOrder $order)
    {

        return Order::query()->create([
            'orderno'       =>  $order->orderNo,
            'relationUid'   =>  $order->relationUid,
            'relationSid'   =>  $order->relationSid,
            'address'       =>  json_encode($order->address,JSON_UNESCAPED_UNICODE),
            'shopInfo'      =>  json_encode($order->shopInfo,JSON_UNESCAPED_UNICODE),
            'status'        =>  $order->status
        ]);
    }

    public function getComplexSearchOrder(array $whereComplexSearch, array $fields = null, $type = null)
    {
        $query = Order::query();

        WhereUtil::assemble($query,$whereComplexSearch);

        return OrmResultType::returnResult($query,$type,$fields);
    }
}

step3-因秒杀有瞬时爆炸的访问流量,所以我们要做好数据库的减压防护工作,所以在这里我们引入一个中间服务,用于缓存数据库中商品的库存,减小数据库的访问压力,数据缓存依赖redis服务

DataCacheServiceInterface

<?php

namespace App\Services\DataCache;

use App\Models\Shop;

/**
 * 数据缓存服务
 */
interface DataCacheServiceInterface
{
    /**
     * Notes:获得商品缓存
     * Author:tanyong
     * DateTime:2022/7/13
     * @param int $shopId
     * @return Shop
     */
    public function getShop(int $shopId);

    /**
     * Notes:设置商品缓存
     * Author:tanyong
     * DateTime:2022/7/13
     * @param Shop $shop
     * @return boolean
     */
    public function setShop(Shop $shop);

    /**
     * Notes:清除商品缓存
     * Author:tanyong
     * DateTime:2022/7/13
     * @param int $shopId
     * @return mixed
     */
    public function clearShop(int $shopId);

    /**
     * Notes:获取商品库存
     * Author:tanyong
     * DateTime:2022/7/13
     * @param int $shopId 商品id
     * @return int 商品库存
     */
    public function getShopTotal(int $shopId);

    /**
     * Notes:设置商品库存
     * Author:tanyong
     * DateTime:2022/7/13
     * @param Shop $shop
     * @return boolean
     */
    public function setShopTotal(int $shopId,$total);

    /**
     * Notes:商品库存自增
     * Author:tanyong
     * DateTime:2022/7/13
     * @param int $shopId 商品id
     * @return boolean
     */
    public function incrShopTotal(int $shopId);

    /**
     * Notes:获得某商品所有的购买者(去重)
     * Author:tanyong
     * DateTime:2022/7/13
     * @param int $shopId
     * @return array
     */
    public function getShopPurchaseUser(int $shopId);

    /**
     * Notes:向商品添加购买者
     * Author:tanyong
     * DateTime:2022/7/13
     * @param int $shopId 商品id
     * @param int $uids
     * @return boolean
     */
    public function addShopPurchaseUser(int $shopId,int ... $uids);

    /**
     * Notes:效验某个用户是否购买某商品
     * Author:tanyong
     * DateTime:2022/7/13
     * @param int $shopId 商品id
     * @param int $uid 用户uid
     * @return boolean
     */
    public function checkShopPurchaseUser(int $shopId,int $uid);

    /**
     * Notes:事务 - 扣减redis - 商品库存
     * Author:tanyong
     * DateTime:2022/7/13
     * @param int $shopId 商品id
     * @return boolean
     */
    public function atomDecrShopTotal(int $shopId,int $operationNum = 1);
}

DataCacheService

<?php

namespace App\Services\DataCache;

use App\Components\ORM\OrmResultType;
use App\Enums\CacheKeyEnum;
use App\Enums\CacheTimeEnum;
use App\Exceptions\Codes;
use App\Exceptions\RedisException;
use App\Models\Shop;
use App\Services\Order\OrderServiceInterface;
use App\Services\Shop\ShopServiceInterface;
use Illuminate\Support\Facades\Redis;

class DataCacheService implements DataCacheServiceInterface
{
    public $redis;

    /**
     * 商品服务
     */
    public ShopServiceInterface $shopService;

    /**
     * 订单服务
     */
    public OrderServiceInterface $orderService;

    public function __construct(ShopServiceInterface $shopService,OrderServiceInterface $orderService)
    {
        $this->shopService = $shopService;

        $this->orderService = $orderService;

        $this->redis = Redis::connection()->client();
    }

    public function getShop(int $shopId)
    {
        $data = $this->redis->get(CacheKeyEnum::SHOP . $shopId);

        if(empty($data))
        {
            $shop = $this->shopService->get($shopId);

            $this->setShop($shop);
        }else{
            $shop = Shop::dispatchArray(json_decode($data,JSON_UNESCAPED_UNICODE));
        }

        return $shop;
    }

    public function setShop(Shop $shop)
    {
        return $this->redis->setex(
            CacheKeyEnum::SHOP . $shop->id,
            CacheTimeEnum::SHOP,
            $shop->toJson(JSON_UNESCAPED_UNICODE)
        );
    }

    public function clearShop(int $shopId)
    {
        return $this->redis->del(CacheKeyEnum::SHOP . $shopId);
    }

    public function getShopTotal(int $shopId)
    {
        $total = $this->redis->get(CacheKeyEnum::SHOP_TOTAL . $shopId);

        if($total === null)
        {
            $shop = $this->getShop($shopId);
            $total = $shop->getStock();
            $this->setShopTotal($shop->id,$total);
        }

        return $total;
    }

    public function setShopTotal(int $shopId,$total)
    {
        return $this->redis->setex(
            CacheKeyEnum::SHOP_TOTAL . $shopId,
            CacheTimeEnum::SHOP_TOTAL,
            $total
        );
    }

    public function atomDecrShopTotal(int $shopId,int $operationNum = 1)
    {
        //反复执行3次均未通过则视为秒杀失败
        if($operationNum >= 3)
            return false;

        $key = CacheKeyEnum::SHOP_TOTAL . $shopId;

        $this->redis->watch($key);

        $this->redis->multi();

        $this->redis->decr($key);

        $result = $this->redis->exec();

        if(!$result)
        {
            $operationNum++;
            return $this->atomDecrShopTotal($shopId,$operationNum);
        }

        return true;
    }

    public function incrShopTotal(int $shopId)
    {
        return $this->redis->incr(CacheKeyEnum::SHOP_TOTAL . $shopId);
    }

    public function getShopPurchaseUser(int $shopId)
    {
        return $this->redis->hGetAll(CacheKeyEnum::SHOP_PURCHASE_USERS . $shopId);
    }

    public function addShopPurchaseUser(int $shopId, int ... $uids)
    {
        $arr = [];
        foreach($uids as $uid)
            $arr[$uid]  = $uid;

        return $this->redis->hMSet(CacheKeyEnum::SHOP_PURCHASE_USERS . $shopId,$arr);
    }

    public function checkShopPurchaseUser(int $shopId, int $uid)
    {
        $key = CacheKeyEnum::SHOP_PURCHASE_USERS . $shopId;
        if(!$this->redis->exists($key)) {
            $wheres = [
                ['relationSid', 'eq', $shopId]
            ];
            $orders = $this->orderService->getComplexSearchOrder($wheres, null, OrmResultType::GET);

            if (!empty($orders))
            {
                $relationUids = $orders->map(function($order){
                    return $order->relationUid;
                })->toArray();

                if(!empty($relationUids))
                    $this->addShopPurchaseUser($shopId,...$relationUids);
            }
        }
        return $this->redis->hExists($key,$uid);
    }

}

step4-我们认为秒杀商品是一个业务动作行为,我们将秒杀商品抽象成一个接口

BusinessServiceInterface

<?php
namespace App\Services\Business;

use App\Exceptions\AppException;
use App\Models\Order;

/**
 * 业务服务
 *
 */
interface BusinessServiceInterface
{
    /**
     * Notes:商品购买
     * Author:tanyong
     * DateTime:2022/7/13
     * @param int $memberId 会员id
     * @param int $shopId 商品id
     * @return Order 商品订单
     * @throws AppException
     */
    public function purchaseShop(int $memberId,int $shopId);
}

BusinessService

双锁实现

悲观锁:锁住商品记录,不允许同时操作一个商品记录

乐观锁:最后的保证商品库存的真实性

<?php

namespace App\Services\Business;

use App\Components\ORM\OrmResultType;
use App\Components\Util\StringUtil;
use App\Enums\OrderStatusEnum;
use App\Exceptions\BusinessException;
use App\Exceptions\Codes;
use App\Models\Rely\CreateOrder;
use App\Services\Order\OrderServiceInterface;
use App\Services\Shop\ShopServiceInterface;
use Illuminate\Support\Facades\DB;

class BusinessService implements BusinessServiceInterface
{
    public function __construct(
        /**
         * 商品服务
         */
        public ShopServiceInterface $shopService,

        /**
         * 订单服务
         */
        public OrderServiceInterface $orderService,
    )
    {}

    public function purchaseShop(int $memberId, int $shopId)
    {
        return DB::transaction(function() use($memberId,$shopId){
            //获取商品信息并锁住记录 死锁
            $shop = $this->shopService->get($shopId,null,true);
            if($shop->total <= $shop->draw)
                throw new BusinessException("库存不足",Codes::BUSINESS_ERROR);

            //最终效验 - 用户是否已购买此商品
            $wheres = [
                ['relationSid','eq',$shopId],
                ['relationUid','eq',$memberId]
            ];
            $count = $this->orderService->getComplexSearchOrder($wheres,null,OrmResultType::COUNT);
            if($count > 0)
                throw new BusinessException("您已参与本次秒杀",Codes::BUSINESS_ERROR);

            //创建订单
            $createOrder                = new CreateOrder();
            $createOrder->orderNo       = time() . StringUtil::randStr(8);
            $createOrder->relationUid   = $memberId;
            $createOrder->relationSid   = $shopId;
            $createOrder->address       = ['address'=>'测试地址'];
            $createOrder->shopInfo      = $shop->getSimple();
            $createOrder->status        = OrderStatusEnum::CREATE;
            $order = $this->orderService->createOrder($createOrder);

            //增加销售数量
            //$this->shopService->incrShopDraw($shop,1);

            //乐观锁
            $result = $this->shopService->incrShopDraw2([
                'id'    =>  $shop->id,
                'draw'  =>  $shop->draw
            ],1);

            if(!$result)
                throw new BusinessException("秒杀失败",Codes::BUSINESS_ERROR);
//            $shopService->update($shop,[
//                'draw'  =>  ($shop->draw + 1)
//            ]);

            return $order;
        },3);
    }

}

step5-效验服务

CheckServiceInterface

<?php
namespace App\Services\Check;

interface CheckServiceInterface
{
    /**
     * Notes:效验用户是否购买某商品 (redis 队列效验)
     * Author:tanyong
     * DateTime:2022/7/13
     * @param int $memberId 用户id
     * @param int $shopId 商品id
     * @return boolean
     */
    public function checkMemberPurchaseShop(int $memberId,int $shopId);
}



CheckService

<?php
namespace App\Services\Check;

use App\Services\DataCache\DataCacheServiceInterface;

class CheckService implements CheckServiceInterface
{
    public function checkMemberPurchaseShop(int $memberId, int $shopId)
    {
        /**
         * 数据缓存服务
         * @var DataCacheServiceInterface $dataCacheService
         */
        $dataCacheService = app()->get(DataCacheServiceInterface::class);

        return $dataCacheService->checkShopPurchaseUser($shopId,$memberId);
    }

}

step6-控制层:业务逻辑封装,流程控制

<?php

namespace App\Http\Controllers;

use App\Components\ORM\OrmResultType;
use App\Exceptions\AppException;
use App\Exceptions\Codes;
use App\Exceptions\RedisException;
use App\Http\Requests\OrderCreateOrderRequest;
use App\Services\Business\BusinessServiceInterface;
use App\Services\Check\CheckServiceInterface;
use App\Services\DataCache\DataCacheServiceInterface;
use App\Services\Order\OrderServiceInterface;
use App\Services\Shop\ShopServiceInterface;
use App\Services\User\UserServiceInterface;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class OrderController extends Controller
{
    public function createOrder(OrderCreateOrderRequest $request)
    {
        try{
            /**
             * 会员服务
             * @var UserServiceInterface $userService
             */
            $userService = app()->get(UserServiceInterface::class);

            /**
             * 数据缓存服务
             * @var DataCacheServiceInterface $dataCacheService
             */
            $dataCacheService = app()->get(DataCacheServiceInterface::class);

            /**
             * 效验服务
             * @var CheckServiceInterface $checkService
             */
            $checkService = app()->get(CheckServiceInterface::class);

            /**
             * 业务服务
             * @var BusinessServiceInterface $businessService
             */
            $businessService = app()->get(BusinessServiceInterface::class);

            //获取用户信息
            $user = $userService->get($request->memberId);
            if(empty($user))
                throw new AppException('not get userinfo',Codes::USER_ERROR);

            //获取商品库存
            $shopTotal = $dataCacheService->getShopTotal($request->shopId);
            if($shopTotal <= 0)
                throw new AppException('库存不足',Codes::BUSINESS_ERROR);

            //用户是否已成功参与秒杀
            if($checkService->checkMemberPurchaseShop(shopId: $request->shopId,memberId: $request->memberId))
                throw new AppException('您已成功参与秒杀 - 2',Codes::BUSINESS_ERROR);

            //原子操作 - 自减redis商品库存
            $result = $dataCacheService->atomDecrShopTotal($request->shopId);
            if(!$result)
                throw new AppException('抢购失败,请重新参与',Codes::BUSINESS_ERROR);

            try{
                //开始秒杀
                $result = $businessService->purchaseShop($request->memberId,$request->shopId);

                //加入队列
                $dataCacheService->addShopPurchaseUser($request->shopId,$request->memberId);
            }catch(\Exception $e)
            {
                //将分配的库存补充回redis
                $dataCacheService->incrShopTotal($request->shopId);

                throw $e;
            }
            return $result;
        }catch(RedisException $e)
        {
            return [
                'code'      =>  101,
                'message'   =>  "database error"
            ];
        }
    }
}

设计完毕。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/725802.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

新装idea的一些常用设置

新装idea的一些常用设置 新安装的idea常用配置 1、提示内容忽略大小写 File -> Settings -> Editor -> General -> Code Completion -> Match case 2、修改字体大小 File -> Settings -> Editor -> Font -> Size 3、取消启动时自动打开最后开…

阿里云国际站:阿里云计算服务指的是什么?

标题&#xff1a;阿里云计算服务指的是什么&#xff1f; 一、阿里云计算服务的定义   阿里云计算服务&#xff0c;是阿里巴巴集团旗下的云服务平台&#xff0c;提供了从云服务器、云数据库到大数据处理等丰富的云计算服务。它通过构建一种共享的计算资源池&#xff0c;使得用…

《SpringBoot》第05章 配置文件解析

前言 SpringBoot中的application.properties(或application.yaml)文件都是再熟悉不过的了。它是应用的配置文件&#xff0c;我们可以把需要的一些配置信息都写在这个文件里面&#xff0c;需要的时候&#xff0c;我们可以通过Value注解来直接获取即可&#xff0c;那这个文件是什…

deepstream指北——python接口的使用

目录 一、机器配置二、环境配置三、运行实例 一、机器配置 电脑&#xff1a;台式机系统&#xff1a;Ubuntu 20.04.5显卡&#xff1a;GTX 1070&#xff0c;8G显存软件版本&#xff1a;deepstream&#xff1a;6.1.1显卡驱动版本&#xff1a;515.76CUDA版本&#xff1a;11.7.1cud…

基于PyQt5的桌面图像调试仿真平台开发(10)色彩矩阵

系列文章目录 基于PyQt5的桌面图像调试仿真平台开发(1)环境搭建 基于PyQt5的桌面图像调试仿真平台开发(2)UI设计和控件绑定 基于PyQt5的桌面图像调试仿真平台开发(3)黑电平处理 基于PyQt5的桌面图像调试仿真平台开发(4)白平衡处理 基于PyQt5的桌面图像调试仿真平台开发(5)…

当赛博朋克碰上小鸡舞 segment anything

Segment Anything能给我们做什么 前言内容具体实现成果 前言 最近&#xff0c;大模型的热度确实是非常非常的高&#xff0c;从chatgpt到segment anything&#xff0c;这些东西整的我这刚入门的小白确实有点懵逼。最近实在是不知道干啥&#xff0c; 想想能不能用大模型整点花活…

Spring后置处理器BeanFactoryPostProcessor与BeanPostProcessor源码解析

文章目录 一、简介1、BeanFactoryPostProcessor2、BeanPostProcessor 二、BeanFactoryPostProcessor 源码解析1、BeanDefinitionRegistryPostProcessor 接口实现类的处理流程2、BeanFactoryPostProcessor 接口实现类的处理流程3、总结 三、BeanPostProcessor 源码解析 一、简介…

uniapp 之 多端实现图片压缩(含H5实现)

compressImage 说明 文档平台差异说明已标出&#xff1a;官网提供的api uni.compressImage除了H5平台&#xff0c;其余平台都支持&#xff0c;所以我们利用条件编译&#xff0c;然后单独处理一下H5的图片压缩即可。 utils.js 里面封装一下该方法&#xff0c;方便调用 /*** 图…

-Xloggc:d:/gc.log

-Xloggc:d:/gc.log把信息记录成 log文件参数-Xmx20m -Xms20m -XX:NewRatio4 -XX:SurvivorRatio2 -Xss1m -XX:PrintGCDetails -XX:UseSerialGC -XX:PrintCommandLineFlags -Xloggc:d:/gc.log结果[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y3PpljS…

【PyTorch API】 nn.RNN 和 nn.LSTM 介绍和代码详解

文章目录 1. nn.RNN 构建单向 RNN2. nn.LSTM 构建单向 LSTM3. 推荐参考资料 1. nn.RNN 构建单向 RNN torch.nn.RNN 的 PyTorch 链接&#xff1a;torch.nn.RNN(*args, **kwargs) nn.RNN 的用法和输入输出参数的介绍直接看代码&#xff1a; import torch import torch.nn as n…

商业模式画布

商业模式画布给了创业者一个思考的框架&#xff0c;在行动之前充分思考和演练。 文章目录 认识商业模式画布九个组成部分&#xff08;以Zoom为例拆解&#xff09;收入成本 九个组成部分的关系总结 认识商业模式画布 九个组成部分&#xff08;以Zoom为例拆解&#xff09; 收入 成…

七月创作之星挑战赛开始咯~

活动火热进行中&#xff01; 欢迎各位大佬积极参与~ 大家请加入卡奥斯开源社区官方社群哦&#xff0c;最新活动实时更新&#xff01; 还有专属群内福利&#xff08;蛋糕券、购物卡、周边礼品&#xff09;等你来拿~ 礼品详情

高效管理工作任务,推荐优秀任务管理软件助力工作效率提升

任务管理软件是一种用于组织任务、将任务分配给个人并监控其进展的软件。该软件可以帮助确保任务在预算内按时完成。它在协同工作环境中特别有用&#xff0c;在这种环境中&#xff0c;多人在处理需要跟踪和监视的任务。 任务管理软件可以帮助简化分配任务和监控任务进度的过程。…

ModaHub魔搭社区:基于阿里云 ACK 搭建开源向量数据库 Milvus

目录 一、准备资源 二、集群创建&#xff1a; 本集群基于Terway网络构建 二、连接刚刚创建的ACK集群 三、部署Milvus数据库 四、优化Milvus配置 简介&#xff1a; 生成式 AI&#xff08;Generative AI&#xff09;引爆了向量数据库&#xff08;Vector Database&#xff0…

STM8低门槛快速入门,类似Arduino封装库模式开发介绍

STM8低门槛快速入门&#xff0c;类似Arduino封装库模式开发介绍 &#x1f4cc;STM8外设封装库原项目开源地址&#xff1a;https://github.com/gicking/STM8_templates&#x1f4cd;个人整理过的项目地址&#xff1a;https://github.com/perseverance51/STM8-Templates &#x1…

前端开发常用Nginx设置说明

前端部署常用到Nginx&#xff0c;作为前端开发常用的配置不多&#xff0c;担也需要掌握 常见配置说明&#xff0c;这里只列表server模块的核心代码 server {listen 9015; # 端口号server_name 172.16.101.191; # 浏览器访问域名&#xff0c;不配置默认为本服务器地址index in…

redhat6安装mysql8.0.33

1、下载mysql 官网地址&#xff1a;https://downloads.mysql.com/archives/community/ 下载步骤&#xff1a; 过滤操作系统版本 下载后&#xff0c;上传到服务器Downloads目录 2、安装mysql8 解压压缩包 tar -xvf mysql-8.0.31-1.el9.x86_64.rpm-bundle.tar [rootrhel64 …

node搭建一个简单的脚手架

一、什么是脚手架 脚手架&#xff08;Scaffold&#xff09;是指在软件开发过程中为提高开发效率而提供的一套基础代码结构、组织规范、开发工具和工程化配置的工具。脚手架可以帮助开发团队快速搭建项目的基础框架&#xff0c;规范项目的开发流程&#xff0c;并提供一些常用的…

指针函数与函数指针

指针函数 指针函数&#xff1a;指针函数是一个函数&#xff0c;返回值是一个指针。 int *fun; //fun是指针变量 int *fun(x,y); //fun是指针函数; #include<iostream> using namespace std;char* day_name() {return("Monday"); //返回地址 }int main() {char…

堆排序选择排序

选择排序 选择排序&#xff08;Selection sort&#xff09;是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小&#xff08;大&#xff09;元素&#xff0c;存放到排序序列的起始位置&#xff0c;然后&#xff0c;再从剩余未排序元素中继续寻找最小&…