秒杀功能、高并发系统关注的问题、秒杀系统设计-59

news2024/11/25 2:59:02

一:秒杀

1.1 特点

秒杀具有瞬间高并发的特点,针对这一特点,必须要做限流 + 异步 + 缓存 (+ 页面静态化)。

1.2 限流方式

  1. 前端限流,一些高并发的网站直接在前端页面开始限流,例如:小米的验证码设计
  2. nginx限流,直接负载部分请求到错误的静态页面:令牌算法 漏斗算法
  3. 网关限流,限流的过滤器。或者使用专业的限流组件sentinel
  4. 代码中使用分布式信号量
  5. rabbitmq限流(能者多劳:chanel.basicQos(1)),保证发挥所有服务器的性能。

1.3 秒杀流程

在这里插入图片描述

二:创建秒杀模块

秒杀建议单独写入一个模块里面,这样可以单独部署,及时秒杀模块出现问题,也不会影响其他模块

2.1 pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.8.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.sysg.gulimail</groupId>
    <artifactId>gulimail-seckill</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gulimail-seckill</name>
    <description>谷粒商城-秒杀服务</description>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR3</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.sysg.gulimail</groupId>
            <artifactId>gulimail-common</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <!--排除掉seata依赖-->
            <exclusions>
                <exclusion>
                    <groupId>com.alibaba.cloud</groupId>
                    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

2.2 application.properties

# name
spring.application.name=gulimail-seckill
# port
server.port=25000
# nacos
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
# redis
spring.redis.host=127.0.0.1

2.3 添加注解

在主启动类添加@EnableDiscoveryClient注解,将服务注册到配置中心
在配置类添加@EnableAsync,表示当前方法异步执行
在配置类添加@EnableScheduling,开启定时任务功能

三:秒杀商品定时上架

秒杀系统一次性上架最近三天所需要的商品

3.1 计算出最近三天的时间

当前时间

/**
 * 当前时间
 * @return
 */
public String startTime(){
    LocalDate now = LocalDate.now();
    LocalTime min = LocalTime.MIN;
    LocalDateTime start = LocalDateTime.of(now, min);
    //格式化时间
    return start.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}

三天后的时间

/**
 * 结束时间
 * @return
 */
public String endTime(){
    LocalDate now = LocalDate.now();
    LocalDate plus = now.plusDays(2);
    LocalTime max = LocalTime.MAX;
    LocalDateTime end = LocalDateTime.of(plus, max);
    //格式化时间
    return end.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}

3.2 查询最近三天需要参加秒杀商品的信息

controller

/**
     * 查询最近三天需要参加秒杀商品的信息
     * @return
     */
    @GetMapping(value = "/Lates3DaySession")
    public R getLates3DaySession() {

        List<SeckillSessionEntity> seckillSessionEntities = seckillSessionService.getLates3DaySession();

        return R.ok().setData(seckillSessionEntities);
    }

service

@Override
    public List<SeckillSessionEntity> getLates3DaySession() {

        //计算最近三天
        //查出这三天参与秒杀活动的商品
        QueryWrapper<SeckillSessionEntity> queryWrapper =
                new QueryWrapper<SeckillSessionEntity>().between("start_time", startTime(), endTime());
        List<SeckillSessionEntity> list = this.baseMapper.selectList(queryWrapper);

        if (list != null && list.size() > 0) {
            List<SeckillSessionEntity> collect = list.stream().map(session -> {
                Long id = session.getId();
                //查出sms_seckill_sku_relation表中关联的skuId
                List<SeckillSkuRelationEntity> relationSkus = seckillSkuRelationService.list(new QueryWrapper<SeckillSkuRelationEntity>()
                        .eq("promotion_session_id", id));
                session.setRelationSkus(relationSkus);
                return session;
            }).collect(Collectors.toList());
            return collect;
        }

        return null;
    }

3.3 将上架的商品缓存到redis里面

@Override
    public void uploadSeckillSkuLatest3Days() {
        //1.扫描最近三天需要参与秒杀的活动
        R session = couponFeignService.getLates3DaySession();
        if(session.getCode() == 0){
            //上架商品
            List<SeckillSessionWithSkusVo> sessionData = session.getData("data", new TypeReference<List<SeckillSessionWithSkusVo>>() {
            });
            //将上架的商品缓存到redis里面
            //1.缓存活动信息
            saveSessionInfos(sessionData);
            //2.缓存活动关联的商品信息
            saveSessionSkuInfo(sessionData);
        }

    }

3.3.1 缓存活动信息

/**
     * 缓存活动信息
     */
    public void saveSessionInfos(List<SeckillSessionWithSkusVo> sessions){
        sessions.forEach(session ->{
            long startTime = session.getStartTime().getTime();
            long endTime = session.getEndTime().getTime();
            String key = SESSIONS_CATCH_PREFIX + startTime + "_" + endTime;
            List<String> skuIds = session.getRelationSkus().stream().map(item->item.getSkuId().toString()).collect(Collectors.toList());
            //缓存活动信息
            redisTemplate.opsForList().leftPushAll(key,skuIds);
        });
    }

3.3.2 缓存活动关联的商品信息

1)准备hash操作

BoundHashOperations<String, Object, Object> ops = redisTemplate.boundHashOps(SECKILL_CHARE_PREFIX);

2)先查询sku的基本信息,调用远程服务

R info = productFeignService.getSkuInfo(seckillSkuVo.getSkuId());
                if (info.getCode() == 0) {
                    SkuInfoVo skuInfo = info.getData("skuInfo",new TypeReference<SkuInfoVo>(){}
                    );
                    redisTo.setSkuInfo(skuInfo);
                }

3)sku的秒杀信息

BeanUtils.copyProperties(seckillSkuVo,redisTo);

4)设置当前商品的秒杀时间信息

redisTo.setStartTime(session.getStartTime().getTime());
                redisTo.setEndTime(session.getEndTime().getTime());

5)设置商品的随机码(防止恶意攻击)

String token = UUID.randomUUID().toString().replace("-", "");
                redisTo.setRandomCode(token);

6)设置分布式信号量
信号量就是商品的库存,每进来一个,库存就会减一,而且每次都需要携带随机码

  • 获取信号量,信号量作用就是限流
RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + token);
  • 设置信号量的值,设置商品秒杀的数量作为信号值
semaphore.trySetPermits(seckillSkuVo.getSeckillCount());

7)将需要秒杀的商品转化为json

                String seckillValue = JSON.toJSONString(redisTo);
                ops.put(seckillSkuVo.getSkuId(),seckillValue);

3.4 接口幂等性处理

1)加锁:在多台服务器下,要保证只有一个机器的一个方法能去进行秒杀业务

/**
     * 秒杀商品上架功能的锁
     */
    private final String upload_lock = "seckill:upload:lock";

    /**
     * 保证幂等性问题
     */
    @Scheduled(cron = "0 0 1/1 * * ? ")
    public void uploadSeckillSkuLatest3Days() {
        //1、重复上架无需处理
        log.info("上架秒杀的商品...");
        //分布式锁
        RLock lock = redissonClient.getLock(upload_lock);
        try {
            //加锁,10秒后就自动释放锁
            lock.lock(10, TimeUnit.SECONDS);
            seckillService.uploadSeckillSkuLatest3Days();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //lock.unlock();
        }
    }

2)判断:通过key在redis里面查询,如果有了就不需要在进行缓存了

/**
     * 缓存活动信息
     */
    public void saveSessionInfos(List<SeckillSessionWithSkusVo> sessions){
        sessions.forEach(session ->{
            long startTime = session.getStartTime().getTime();
            long endTime = session.getEndTime().getTime();
            String key = SESSIONS_CATCH_PREFIX + startTime + "_" + endTime;
            //缓存活动信息
            //通过key在redis里面查询,如果有了就不需要在进行缓存了
            Boolean hasKey = redisTemplate.hasKey(key);
            if(!Boolean.TRUE.equals(hasKey)){
                List<String> skuIds = session.getRelationSkus().stream().map(item->item.getPromotionSessionId()+"_"+item.getSkuId().toString()).collect(Collectors.toList());
                redisTemplate.opsForList().leftPushAll(key,skuIds);
            }
        });
    }

    /**
     * 缓存活动关联的商品信息
     */
    public void saveSessionSkuInfo(List<SeckillSessionWithSkusVo> sessions){
        sessions.forEach(session ->{
            //准备hash操作
            BoundHashOperations<String, Object, Object> ops = redisTemplate.boundHashOps(SECKILL_CHARE_PREFIX);
            session.getRelationSkus().forEach(seckillSkuVo -> {
                //设置商品的随机码(防止恶意攻击)
                String token = UUID.randomUUID().toString().replace("-", "");
                Boolean hasKey = ops.hasKey(seckillSkuVo.getPromotionSessionId().toString() + "_" + seckillSkuVo.getSkuId().toString());
                if(!Boolean.TRUE.equals(hasKey)){
                    //缓存商品
                    SeckillSkuRedisTo redisTo = new SeckillSkuRedisTo();
                    //1、先查询sku的基本信息,调用远程服务
                    R info = productFeignService.getSkuInfo(seckillSkuVo.getSkuId());
                    if (info.getCode() == 0) {
                        SkuInfoVo skuInfo = info.getData("skuInfo",new TypeReference<SkuInfoVo>(){}
                        );
                        redisTo.setSkuInfo(skuInfo);
                    }
                    //2、sku的秒杀信息
                    BeanUtils.copyProperties(seckillSkuVo,redisTo);
                    //3、设置当前商品的秒杀时间信息
                    redisTo.setStartTime(session.getStartTime().getTime());
                    redisTo.setEndTime(session.getEndTime().getTime());
                    //4、设置商品的随机码(防止恶意攻击)
                    redisTo.setRandomCode(token);
                    // 将需要秒杀的商品转化为json
                    String seckillValue = JSON.toJSONString(redisTo);
                    ops.put(seckillSkuVo.getPromotionSessionId().toString() + "_" + seckillSkuVo.getSkuId().toString(),seckillValue);

                    //5.设置分布式信号量,信号量就是商品的库存,每进来一个,库存就会减一,而且每次都需要携带随机码
                    //5.1 获取信号量,信号量作用就是限流
                    RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + token);
                    //5.2 设置信号量的值,设置商品秒杀的数量作为信号值
                    semaphore.trySetPermits(seckillSkuVo.getSeckillCount());
                }
            });
        });
    }

四:查询秒杀商品

4.1 controller

/**
     * 当前时间可以参与秒杀的商品信息
     * @return
     */
    @GetMapping(value = "/getCurrentSeckillSkus")
    @ResponseBody
    public R getCurrentSeckillSkus() {
        //获取到当前可以参加秒杀商品的信息
        List<SeckillSkuRedisTo> vos = seckillService.getCurrentSeckillSkus();
        return R.ok().setData(vos);
    }

4.2 service

 /**
     * 获取到当前可以参加秒杀商品的信息
     * @return
     */
    @Override
    public List<SeckillSkuRedisTo> getCurrentSeckillSkus() {
        //1.确定当前时间属于哪个秒杀场次
        long time = new Date().getTime();
        //获取所有的keys数据
        Set<String> keys = redisTemplate.keys(SESSIONS_CATCH_PREFIX + "*");
        if(!CollectionUtils.isEmpty(keys)){
            for (String key : keys) {
                //分割后获取时间区间
                String replace = key.replace(SESSIONS_CATCH_PREFIX, "");
                String[] s = replace.split(PREFIX);
                //开始时间
                long start = Long.parseLong(s[0]);
                //结束时间
                long end = Long.parseLong(s[1]);
                //查询当前的场次信息
                if( time >= start && time <= end ){
                    //2.获取这个场次所有的商品信息
                    //range获取-100到100区间的数据
                    List<String> range = redisTemplate.opsForList().range(key, -100, 100);
                    if(!CollectionUtils.isEmpty(range)){
                        //获取绑定的hash值
                        BoundHashOperations<String, String, Object> hashOps = redisTemplate.boundHashOps(SECKILL_CHARE_PREFIX);
                        List<Object> list = hashOps.multiGet(range);
                        if(!CollectionUtils.isEmpty(list)){
                            return list.stream().map(item -> {
                                //不能将随机码字段也返回,所以需要删除掉
                                //redisTo.setRandomCode(null);当前秒杀开始就需要随机码
                                return JSON.parseObject(String.valueOf(item),SeckillSkuRedisTo.class);
                            }).collect(Collectors.toList());
                        }
                        //查询出当前场次以后,后续的就不需要遍历了,直接跳出for循环
                        break;
                    }
                }
            }
        }
        return null;
    }

五:查询商品有没有秒杀信息

5.1 根据skuId查询商品是否参加秒杀活动

  • controller
   /**
     * 根据skuId查询商品是否参加秒杀活动
     * @param skuId
     * @return
     */
    @GetMapping(value = "/sku/seckill/{skuId}")
    @ResponseBody
    public R getSkuSeckillInfo(@PathVariable("skuId") Long skuId) {
        SeckillSkuRedisTo to = seckillService.getSkuSeckilInfo(skuId);
        return R.ok().setData(to);
    }
  • service
   /**
     * 根据skuId查询商品是否参加秒杀活动
     * @param skuId
     * @return
     */
    @Override
    public SeckillSkuRedisTo getSkuSeckilInfo(Long skuId) {
        //1.找到所有参与秒杀的商品的key
        BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(SECKILL_CACHE_PREFIX);
        Set<String> keys = hashOps.keys();
        if(!CollectionUtils.isEmpty(keys)){
            //6_4 通过正则表达式去判断
            //d表示匹配一个数字
            String regx = "\\d_" + skuId;
            for (String key : keys) {
                boolean matches = Pattern.matches(regx, key);
                if(matches){
                    String json = hashOps.get(key);
                    SeckillSkuRedisTo redisTo = JSON.parseObject(json, SeckillSkuRedisTo.class);
                    if(redisTo != null){
                        //随机码
                        Long startTime = redisTo.getStartTime();
                        Long endTime = redisTo.getEndTime();
                        long currentTime = new Date().getTime();
                        //判断当前时间是否在秒杀时间之间,如果是就返回随机码
                        if( currentTime < startTime || currentTime > endTime ){
                            redisTo.setRandomCode(null);
                        }
                        return redisTo;
                    }

                }
            }
        }
        return null;
    }

5.2 新建远程调用的feign接口

@FeignClient(value = "gulimail-seckill")
public interface SeckillFeignService {

    /**
     * 根据skuId查询商品是否参加秒杀活动
     * @param skuId
     * @return
     */
    @GetMapping(value = "/sku/seckill/{skuId}")
    R getSkuSeckillInfo(@PathVariable("skuId") Long skuId);

}

5.3 远程调用查询当前sku是否参与秒杀优惠活动

//3、远程调用查询当前sku是否参与秒杀优惠活动
        CompletableFuture<Void> seckillFuture = CompletableFuture.runAsync(() -> {
            R skuSeckillInfo = seckillFeignService.getSkuSeckillInfo(skuId);
            if (skuSeckillInfo.getCode() == 0) {
                SeckillSkuVo skuSeckillInfoData = skuSeckillInfo.getData("data", new TypeReference<SeckillSkuVo>() {
                });
                skuItemVo.setSeckillSkuVo(skuSeckillInfoData);
                if (skuSeckillInfoData != null) {
                    long currentTime = System.currentTimeMillis();
                    if (currentTime > skuSeckillInfoData.getEndTime()) {
                        skuItemVo.setSeckillSkuVo(null);
                    }
                }
            }
        }, executor);

        //等到所有任务都完成
        CompletableFuture.allOf(saleAttrFuture,descFuture,baseAttrFuture,imageFuture,seckillFuture).get();

六:高并发系统关注的问题

1) 服务单一职责+独立部署
秒杀服务即使自己扛不住压力,挂掉。不要影响别人
2)秒杀链接加密
防止恶意攻击,模拟秒杀请求,1000次/s攻击。
防止链接暴露,自己工作人员,提前秒杀商品。
3)库存预热+快速扣减
秒杀读多写少。无需每次实时校验库存。我们库存预热,放到redis中。信号量控制进来秒杀的请求
4)动静分离
nginx做好动静分离。保证秒杀和商品详情页的动态请求才打到后端的服务集群。使用CDN网络,分担本集群压力
5)恶意请求拦截
识别非法攻击请求并进行拦截,网关层
6)流量错峰
使用各种手段,将流量分担到更大宽度的时间点。比如验证码,加入购物车
7)限流&熔断&降级
前端限流+后端限流。限制次数,限制总量,快速失败降级运行,熔断隔离防止雪崩
8)队列削峰
1万个商品,每个1000件秒杀。双11所有秒杀成功的请求,进入队列,慢慢创建订单,扣减库存即可。

七:秒杀系统设计-立即抢购

7.1 秒杀流程

在这里插入图片描述

7.2 发送请求

    $(".seckill").click(function () {
        var isLogin = [[${session.loginUser != null}]];     //true
        if (isLogin) {
            var killId = $(this).attr("sessionid") + "-" + $(this).attr("skuid");
            var code = $(this).attr("code");
            var num = $("#productNum").val();
            location.href = "http://seckill.gulimall.com/kill?killId=" + killId + "&key=" + code + "&num=" + num;
        } else {
            alert("秒杀请先登录");
        }
        return false;
    });

7.3 代码实现

  • controller
/**
     * 商品进行秒杀(秒杀开始)
     * @param killId 秒杀id
     * @param key 随机码
     * @param num 秒杀的总数
     * @return
     */
    @GetMapping(value = "/kill")
    public String seckill(@RequestParam("killId") String killId,
                          @RequestParam("key") String key,
                          @RequestParam("num") Integer num,
                          Model model) {

        String orderSn = null;
        try {
            //1、判断是否登录
            orderSn = seckillService.kill(killId,key,num);
            model.addAttribute("orderSn",orderSn);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "success";
    }
  • service
/**
     * 商品进行秒杀(秒杀开始)
     * @param killId
     * @param key
     * @param num
     * @return
     */
    @Override
    public String kill(String killId, String key, Integer num) {
        //1、判断是否登录,拦截器已经处理,此时无需处理
        MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();
        //2.判断参数的合法性
        //2.1 获取当前秒杀商品的详细信息
        BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(SECKILL_CACHE_PREFIX);
        String json = hashOps.get(killId);
        if(!StringUtils.isEmpty(json)){
            SeckillSkuRedisTo redisTo = JSON.parseObject(json, SeckillSkuRedisTo.class);
            if(redisTo != null){
                //2.2 判断秒杀时间是否过期
                Long startTime = redisTo.getStartTime();
                Long endTime = redisTo.getEndTime();
                long currentTime = new Date().getTime();
                long ttl = endTime - startTime;
                if(startTime <= currentTime && endTime >= currentTime){
                    //2.3 判断随机码是否正确和商品id是否一致
                    String skuId = redisTo.getPromotionSessionId() + "_" + redisTo.getSkuId();
                    if(key.equals(redisTo.getRandomCode()) && killId.equals(skuId)){
                        //2.4 验证购物数量是否合理
                        if( num <= redisTo.getSeckillLimit()){
                            //2.5 验证这个用户是否购买过,幂等性处理。只要秒杀成功,就占位
                            String redisKey = memberRespVo.getId() + "_" + skuId;
                            //设置超时时间,只要过了当前秒杀场次就取消,自动过期
                            Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(redisKey, num.toString(), ttl, TimeUnit.MILLISECONDS);
                            //如果占位成功就说明这个人从来没买过,就可以买
                            if(Boolean.TRUE.equals(aBoolean)){
                                //2.6 使用信号量减库存
                                RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + redisTo.getRandomCode());
                                try {
                                    //等上100毫秒
                                    boolean b = semaphore.tryAcquire(num, 100, TimeUnit.MILLISECONDS);
                                    //秒杀成功,快速下单
                                    //生成订单号
                                    return IdWorker.getTimeId();
                                } catch (InterruptedException e) {
                                    return null;
                                }
                            }

                        }

                    }
                }
            }
        }
        return null;
    }

7.4 将秒杀成功的商品订单信息发送给MQ队列

1)引入依赖

         <!--引入mq依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

2)配置信息

# RabbitMQ配置
spring.rabbitmq.host=192.168.77.130
spring.rabbitmq.port=5672
# 虚拟主机配置
spring.rabbitmq.virtual-host=/

3)添加配置类

@Configuration
public class MyRabbitMQConfig {

    /**
     * 配置消息为json类型
     * @return
     */
    @Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }

}

4)发送订单号

//2.5 验证这个用户是否购买过,幂等性处理。只要秒杀成功,就占位
                            String redisKey = memberRespVo.getId() + "_" + skuId;
                            //设置超时时间,只要过了当前秒杀场次就取消,自动过期
                            Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(redisKey, num.toString(), ttl, TimeUnit.MILLISECONDS);
                            //如果占位成功就说明这个人从来没买过,就可以买
                            if(Boolean.TRUE.equals(aBoolean)){
                                //2.6 使用信号量减库存
                                RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + redisTo.getRandomCode());
                                try {
                                    //等上100毫秒
                                    boolean semaphoreCount = semaphore.tryAcquire(num, 100, TimeUnit.MILLISECONDS);
                                    if(Boolean.TRUE.equals(semaphoreCount)){
                                        //秒杀成功,快速下单
                                        //生成订单号
                                        String timeId = IdWorker.getTimeId();
                                        SeckillOrderTo orderTo = new SeckillOrderTo();
                                        orderTo.setOrderSn(timeId);
                                        orderTo.setMemberId(memberRespVo.getId());
                                        orderTo.setNum(num);
                                        orderTo.setPromotionSessionId(redisTo.getPromotionSessionId());
                                        orderTo.setSkuId(redisTo.getSkuId());
                                        orderTo.setSeckillPrice(redisTo.getSeckillPrice());
                                        rabbitTemplate.convertAndSend("order-event-exchange","order.seckill.order",orderTo);
                                        long s2 = System.currentTimeMillis();
                                        log.info("耗时..." + (s2 - s1));
                                        return timeId;
                                    }
                                } catch (InterruptedException e) {
                                    return null;
                                }
                            }

5)新建队列和绑定关系

/**
     * 商品秒杀队列
     * @return
     */
    @Bean
    public Queue orderSecKillOrderQueue() {
        Queue queue = new Queue("order.seckill.order.queue", true, false, false);
        return queue;
    }

    @Bean
    public Binding orderSecKillOrrderQueueBinding() {
        //String destination, DestinationType destinationType, String exchange, String routingKey,
        // 			Map<String, Object> arguments
        Binding binding = new Binding(
                "order.seckill.order.queue",
                Binding.DestinationType.QUEUE,
                "order-event-exchange",
                "order.seckill.order",
                null);

        return binding;
    }

6)新建秒杀监听器

@Slf4j
@Component
@RabbitListener(queues = "order.seckill.order.queue")
public class OrderSeckillListener {

    @Autowired
    private OrderService orderService;

    /**
     * 监听订单的entity消息
     * @param seckillOrderTo
     * @param channel
     * @param message
     */
    @RabbitHandler
    public void listener(SeckillOrderTo seckillOrderTo, Channel channel, Message message) throws IOException {
        //关闭OrderEntity订单
        try {
            log.info("准备创建秒杀单的详细信息:{}",seckillOrderTo.toString());
            orderService.createSeckillOrder(seckillOrderTo);
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
        } catch (Exception e) {
            //true重新回到队列里面,不能丢弃
            channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
        }
    }

}

7)创建秒杀订单

/**
     * 创建秒杀单
     * @param orderTo
     */
    @Override
    public void createSeckillOrder(SeckillOrderTo orderTo) {
        //保存订单信息
        OrderEntity orderEntity = new OrderEntity();
        orderEntity.setOrderSn(orderTo.getOrderSn());
        orderEntity.setMemberId(orderTo.getMemberId());
        orderEntity.setCreateTime(new Date());
        BigDecimal totalPrice = orderTo.getSeckillPrice().multiply(BigDecimal.valueOf(orderTo.getNum()));
        orderEntity.setPayAmount(totalPrice);
        orderEntity.setStatus(OrderStatusEnum.CREATE_NEW.getCode());
        //保存订单
        this.save(orderEntity);
        //保存订单项信息
        OrderItemEntity orderItem = new OrderItemEntity();
        orderItem.setOrderSn(orderTo.getOrderSn());
        orderItem.setRealAmount(totalPrice);
        orderItem.setSkuQuantity(orderTo.getNum());
        //保存商品的spu信息
        R spuInfo = productFeignService.getSpuInfoBySkuId(orderTo.getSkuId());
        SpuInfoVo spuInfoData = spuInfo.getData("data", new TypeReference<SpuInfoVo>() {
        });
        orderItem.setSpuId(spuInfoData.getId());
        orderItem.setSpuName(spuInfoData.getSpuName());
        orderItem.setSpuBrand(spuInfoData.getBrandName());
        orderItem.setCategoryId(spuInfoData.getCatalogId());
        //保存订单项数据
        orderItemService.save(orderItem);
    }

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

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

相关文章

Python 生成 svg 图片,一篇博客带你掌握 Python 与 svg 之间的操作

python svgwritePython 操作 SVG 图片的库清单svgwrite 库svgwrite 库其他图形绘制储备反爬技术 svgwrite 生成一个手机号Python 操作 SVG 图片的库清单 在 Python 中&#xff0c;可以使用以下几种库来生成 SVG 图片&#xff1a; svgwrite&#xff1a;这是一个简单易用的 Pyt…

民生银行联手火山引擎,一场“1+1>2”的金融数字化征程

数字化时代下&#xff0c;信息成为企业生长的升维秘钥。管理学者德鲁克在《21世纪的管理挑战》一书中指出&#xff0c;我们正经历着一场信息革命。其中特别提出&#xff0c;不是某种软硬件的革命&#xff0c;而是“信息”被使用和利用的方式转变了。近年来&#xff0c;金融行业…

【终极UI/UX工具包】上海道宁与Infragistics助力您简化程序开发,​创建精美应用程序

​​​​​​​​​​​​​​ Infragistics Ultimate是 开发者的UI/UX工具包 可以简化程序开发 加速从设计到代码的应用程序创建 为Web、移动和桌面 创建精美应用程序所需的一切帮助 Infragistics Ultimate附带 完整的企业级.NET和JavaScript图表 网格和UI组件以及可用…

SQL--DML

目录 1、添加数据&#xff08;insert&#xff09; 1. 给指定字段添加数据 2. 给全部字段添加数据 3. 批量添加数据 2、修改数据&#xff08;update&#xff09; 3、删除数据&#xff08;delete&#xff09; DML英文全称是Data Manipulation Language(数据操作语言)&…

AIParsing(TIP2022)-人体解析论文阅读

文章目录解决问题算法人体解析AIParsingFPN检测头边缘引导解析头实验结论论文&#xff1a; 《AIParsing: Anchor-Free Instance-Level Human Parsing》github&#xff1a; https://github.com/31sy/AIParsing解决问题 目前SOTA实例级人体解析模型使用二阶段基于anchor的检测器…

使用 curl multi interface 编写的高性能封装类和示例

CurlMultiDemo 使用 libCurl 的 Multi Interface 的一个简单封装类,支持功能: 单线程 纯异步 的 Get/Post 支持极高的性能(使用 POST 上传 600 文件,仅耗时4~6秒) 使用方式: 1.编译或下载 libcurl 库, 我采用的是 vcpkg2.搭建或找一个支持 MultiPart 上传文件的服务器, 如没…

linux下安装python环境 + Faster-Rcnn环境配置

目录 在Windows上配置环境 在Ubuntu上配置环境 装虚拟机 全屏问题 中文输入 --- 搜狗输入法 将Windows上文件传输给ubuntu --- winscp 分配内存给根目录 深夜惊魂&#xff0c;ubuntu根目录空间不足 - 知乎 给conda或者pip换源 安装anacondapythonpycharm 下载安装…

Spirng bean

spring bean 作用域&#xff08;scope&#xff09; 作用域描述singleton单例模式&#xff0c;每一个spring context中只有一个实例prototype每次调用一个getBen方法都会产生一个新的对象request每个request请求产生一个实例session每个htttp session中产生一个实例application…

Qt之基于Graphics View实现Mesh网络拓扑图

Mesh是一种多节点、无中心、自组织的无线多跳通信网络,也就是网状结构网络。网络中所有的节点都互相连接,每个节点拥有多条连接通道,所有的节点之间形成一个整体的网络。 一.效果 Mesh网络拓扑形态并不固定,完全依据各节点之间的信道质量自适应变化。这里演示了四个节点四…

商场室内地图导航如何实现,便捷、低成本智慧商业综合体一站式解决方案

试想一下&#xff0c;在大型商场内&#xff0c;顾客结队购物&#xff0c;同伴走散或者儿童走失&#xff0c;顾客不知道自己和同伴的位置&#xff0c;这是多么糟糕的事情。的确&#xff0c;在室内迷路&#xff0c;是一件令人很头疼的事情&#xff0c;如何把室外导航这种完整度高…

架构篇 -- 搭建gitlab ci远程自动化部署

001.后端服务自动化部署 本文将以pass-runtime服务为例子&#xff0c;进行介绍&#xff0c;有相关自动部署需求&#xff0c;可参考此文档。 最近换了份工作公司要求使用gitlab ci去部署&#xff0c;原来比较习惯使用jenkins&#xff0c;但是还是要适应新挑战&#xff0c;看了网…

大语言模型集成工具 LangChain

大语言模型集成工具 LangChain LangChain 介绍 介绍&#xff1a; 通过可组合性使用大型语言模型构建应用程序【背景】大型语言模型 (LLM) 正在成为一种变革性技术&#xff0c;使开发人员能够构建他们以前无法构建的应用程序&#xff0c;但是单独使用这些 LLM 往往不足以创建一…

内存映射(Linux)

文章目录概念 内存映射&#xff08;Memory-mapped I/O&#xff09;是将磁盘文件的数据映射到内存&#xff0c;用户通过修改内存就能修改磁盘文件。 API 内存映射相关系统调用&#xff0c;使用man 2 mmap查看帮助 void *mmap(void *addr, size_t length, int prot, int flags, …

服务端大量处于TIME_WAIT和CLOSE_WAIT状态连接的原因

服务端大量处于TIME_WAIT和CLOSE_WAIT状态连接的原因1.服务端大量处于TIME_WAIT状态的连接原因&#xff1f;1.HTTP没有使用长连接2.HTTP长连接超时3.HTTP长连接的请求数量达到上限TIME_WAIT状态连接过多的危害&#xff1f;2.服务器大量处于CLOSE_WAIT状态的连接原因&#xff1f…

网络流量监控系统为企业SAP接口性能分析

前言 某汽车总部已部署NetInside网络流量监控系统&#xff0c;使用流量分析系统提供实时和历史原始流量&#xff0c;重点针对SAP系统性能进行分析&#xff0c;以供安全取证、应用事务分析、网络质量监测以及深层网络分析。 本次分析报告包含&#xff1a;SAP接口性能分析案例。…

TSD simple(0)时序数据库简介

时间序列数据库&#xff1a;Time Series Database 时序数据库全称为时间序列数据库。 时间序列数据&#xff1a;带时间标签的数据 时间序列数据库是用于存储和管理时间序列数据的专业化数据库&#xff0c;具备写多读少、冷热分明、高并发写入、无事务要求、海量数据持续写入等…

LeetCode155-最小栈

题目 解题 这道题本来是打算用栈然后加一个标记位&#xff0c;这个标记位存最小的数&#xff0c;但是之后发现解决不这种情况&#xff1a; 1、弹出以后&#xff0c;这个标记怎么办&#xff0c;没法回溯 那么就得换一个数据结构&#xff0c;比如额外放一个线性表&#xff0c;里…

【电子学会】2022年12月图形化四级 -- 求最大公约数

求最大公约数 如果6除以2的余数是0,那么我们就说2是6的约数。4除以4的余数是0,4也是4的约数。同理可以求出,4的约数有1、2和4,6的约数有1、2、3和6。两个数的最大公约数是指两个数相同的约数中最大的那一个,如4和6两个数的最大公约数是2。 辗转相除法求最大公约数的步骤…

Windows部署定时任务,每天定期执行手机机型爬取

项目背景 有一个手机机型自动更新获取的需求&#xff0c;在我写好相关的爬虫后&#xff0c;需要将爬虫部署&#xff0c;并且需要配置定时任务每天定期执行。之前在Mac上开发&#xff0c;现在部门给配了一台windows定期执行。 环境准备 A、安装ChromeDriver 1、我是通过Chro…

JAVA中创建线程池的五种方法及比较

之前写过JAVA中创建线程的三种方法及比较。这次来说说线程池。 JAVA中创建线程池主要有两类方法&#xff0c;一类是通过Executors工厂类提供的方法&#xff0c;该类提供了4种不同的线程池可供使用。另一类是通过ThreadPoolExecutor类进行自定义创建。 目录 一、通过Executors…