秒杀服务------技术点及亮点

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 秒杀服务------技术点及亮点

大技术

使用Redisson

使用Redisson在秒杀服务中有两个作用,一个是作为分布式锁来确保多个秒杀服务同时在线时同时上架秒杀商品,只允许有一个秒杀服务成功上架秒杀商品,其他的上架失败。第二个作用是作为分布式信号量,每个秒杀商品在存到Redis中时都设置一个分布式信号量,把每个秒杀商品的数量作为信号量的值,这是为了防止秒杀的时候出现穿库的情况(就是只设置了3个秒杀数量,结果秒杀结束后秒杀数量是5个,这就亏本了)


1、导入依赖

<!-- 以后使用redisson作为分布式锁,分布式对象等功能框架 -->
<dependency>
  <groupId>org.redisson</groupId>
  <artifactId>redisson</artifactId>
  <version>3.16.8</version>
</dependency>


2、设置Redission配置类

package com.saodai.saodaimall.saodaimall.seckill.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
/**
* redission分布式锁配置类
*/
@Configuration
public class MyRedissonConfig {
    /**
    * 所有对Redisson的使用都是通过RedissonClient
    * @return
    * @throws IOException
    */
    @Bean(destroyMethod="shutdown")
    public RedissonClient redisson() throws IOException {
        //1、创建配置
        Config config = new Config();
        //配置虚拟机的地址
        config.useSingleServer().setAddress("redis://192.168.241.128:6379");
        //2、根据Config创建出RedissonClient实例(单个实例)
        //Redis url should start with redis:// or rediss://
        RedissonClient redissonClient = Redisson.create(config);
        return redissonClient;
    }
}


3、Redission作为分布式锁

package com.saodai.saodaimall.saodaimall.seckill.scheduled;
import com.saodai.saodaimall.saodaimall.seckill.service.SeckillService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* 秒杀商品定时上架
*  每天晚上3点,上架最近三天需要三天秒杀的商品
*  当天00:00:00 - 23:59:59
*  明天00:00:00 - 23:59:59
*  后天00:00:00 - 23:59:59
*/
@Slf4j
@Service
public class SeckillScheduled 
    @Autowired
    private SeckillService seckillService;
    @Autowired
    private RedissonClient redissonClient;
    //秒杀商品上架功能的锁
    private final String upload_lock = "seckill:upload:lock";
    /**保证幂等性问题**/
    //     @Scheduled(cron = "*/5 * * * * ? ") //秒 分 时 日 月 周
    @Scheduled(cron = "0 0 1/1 * * ? ") 
    public void uploadSeckillSkuLatest3Days() {
        //1、重复上架无需处理
        log.info("上架秒杀的商品...");
        //分布式锁
        RLock lock = redissonClient.getLock(upload_lock);
        try {
            //加锁(指定锁定时间为10s)
            lock.lock(10, TimeUnit.SECONDS);
            seckillService.uploadSeckillSkuLatest3Days();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}


使用Redisson作为分布式锁是为了确保多个秒杀服务同时在线时同时上架秒杀商品,只允许有一个秒杀服务成功上架秒杀商品,其他的上架失败

4、Redission实现分布式信号量

作为分布式信号量,每个秒杀商品在存到Redis中时都设置一个分布式信号量,把每个秒杀商品的数量作为信号量的值,这是为了防止秒杀的时候出现穿库的情况(就是只设置了3个秒杀数量,结果秒杀结束后秒杀数量是5个,这就亏本了)

/**
     * 封装秒杀活动的关联商品信息到缓存里
     * @param sessions 秒杀活动信息
     */
    private void saveSessionSkuInfo(List<SeckillSessionWithSkusVo> sessions) {
        if (sessions!=null){
            sessions.stream().forEach(session -> {
                //准备hash操作,绑定hash值seckill:skus
                BoundHashOperations<String, Object, Object> operations = redisTemplate.boundHashOps(SECKILL_CHARE_PREFIX);
                //遍历秒杀活动中的商品项(seckillSkuVo表示的就是每个遍历的商品项)
                session.getRelationSkus().stream().forEach(seckillSkuVo -> {
                    //生成随机码
                    String token = UUID.randomUUID().toString().replace("-", "");
                    //查看redis中有没有这个key (秒杀场次id_秒杀商品id)
                    String redisKey = seckillSkuVo.getPromotionSessionId().toString() + "-" + seckillSkuVo.getSkuId().toString();
                    if (!operations.hasKey(redisKey)) {
                        //缓存我们商品信息(SeckillSkuRedisTo是存入缓存中的对象)
                        SeckillSkuRedisTo redisTo = new SeckillSkuRedisTo();
                        Long skuId = seckillSkuVo.getSkuId();
                        //1、先查询sku的基本信息,调用远程服务
                        R info = productFeignService.getSkuInfo(skuId);
                        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格式存入Redis中
                        String seckillValue = JSON.toJSONString(redisTo);
                       //秒杀活动的商品项的详细信息存入redis
                        /**格式是key:4_47 value:SeckillSkuRedisTo对象的String类型**/
                        operations.put(seckillSkuVo.getPromotionSessionId().toString() + "-" + seckillSkuVo.getSkuId().toString(),seckillValue);
                        //如果当前这个场次的商品库存信息已经上架就不需要上架
                        /**5、使用库存作为分布式Redisson信号量(限流)**/
                        //把每个秒杀商品的总数量作为信号量存入redis缓存,信号量标识seckill:stock:+随机(相当于key)
                        RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + token);
                        /**库存的格式key:seckill:stock:5d1df46618d34f9f9808f25cda60ba01 value:秒杀商品的总数量 其中5d1df46618d34f9f9808f25cda60ba01是随机码**/
                        semaphore.trySetPermits(seckillSkuVo.getSeckillCount());
                    }
                });
            });
        }else {
            log.error("没有秒杀活动");
        }
    }
@Autowired
private RedissonClient redissonClient;
/**5、使用库存作为分布式Redisson信号量(限流)**/
//获取分布式信号量,信号量名称为seckill:stock:+随机码
RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + token);
// 秒杀商品的库存数量作为信号量的值(允许同时seckillSkuVo.getSeckillCount()个用户获取到信号量)
semaphore.trySetPermits(seckillSkuVo.getSeckillCount());

实现uploadSeckillSkuLatest3Days方法中saveSessionSkuInfo(封装秒杀活动的关联商品信息到缓存里,每一个Redis缓存中具体sku信息用的是HashMap结构),通过HashMap结构把每个秒杀商品的详细信息以下面的格式存到Redis中,然后通过Redisson实现分布式信号量来把秒杀商品的库存总数量作为信号量存入redis缓存,每一个Redis缓存中具体sku信息的格式如下:


hash值是seckill:skus key: 4_47 value: SeckillSkuRedisTo对象


其中key的4表示秒杀的场次id,47表示秒杀商品的skuId,由于用的是hashMap结构,其中hash值是seckill:skus


Redission实现分布式信号量设置时就会把信号量以key-value的格式存到reids缓存中,Redis缓存中信号量信息的格式如下:


key: seckill:stock:随机码 value:每个秒杀商品的总数量


其中key的seckill:stock是固定前缀,随机码就是随机成功的uuid值,把每个秒杀商品的总数量作为信号量的值


准备hash操作,绑定seckill:skus关键字的hash


遍历封装存入redis的秒杀活动的秒杀商品项


生成随机码


封装SeckillSkuRedisTo对象并序列化后存入redis缓存


远程调用product商品服务


使用Redission实现分布式信号量来把秒杀商品的库存总数量作为信号量存入redis缓存(限流)


秒杀活动的商品项的详细信息存入redis缓存


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


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


封装秒杀活动中秒杀商品项信息


4、秒杀时具体实现

@Autowired
private RedissonClient redissonClient;
//分布式锁
RSemaphore semaphore = redissonClient.getSemaphore(key);
//尝试快速拿到信号量,100毫秒没有用拿到就返回false
//在指定的时间内尝试地获取1个许可,如果获取不到就返回false
boolean semaphoreCount = semaphore.tryAcquire(num, 100, TimeUnit.MILLISECONDS);
目录
相关文章
|
安全 API 数据安全/隐私保护
API接口知识小结
应用程序接口API(Application Programming Interface),是提供特定业务输出能力、连接不同系统的一种约定。这里包括外部系统与提供服务的系统(中后台系统)或后台不同系统之间的交互点。包括外部接口、内部接口,内部接口又包括:上层服务与下层服务接口、同级接口。
|
8月前
|
关系型数据库 测试技术 分布式数据库
刷新世界纪录!阿里云PolarDB凭借创新的「三层解耦」架构刷新TPC-C基准测试世界纪录
刷新世界纪录!阿里云PolarDB凭借创新的「三层解耦」架构刷新TPC-C基准测试世界纪录
|
SQL 关系型数据库 MySQL
数据库导入SQL文件:全面解析与操作指南
在数据库管理中,将SQL文件导入数据库是一个常见且重要的操作。无论是迁移数据、恢复备份,还是测试和开发环境搭建,掌握如何正确导入SQL文件都至关重要。本文将详细介绍数据库导入SQL文件的全过程,包括准备工作、操作步骤以及常见问题解决方案,旨在为数据库管理员和开发者提供全面的操作指南。一、准备工作在导
1641 0
|
存储 分布式计算 数据库
阿里云国际版设置数据库云分析工作负载的 ClickHouse 版
阿里云国际版设置数据库云分析工作负载的 ClickHouse 版
多线程并发之Semaphore(信号量)使用详解
多线程并发之Semaphore(信号量)使用详解
5527 0
|
人工智能 PyTorch TensorFlow
分布式训练:大规模AI模型的实践与挑战
【7月更文第29天】随着人工智能的发展,深度学习模型变得越来越复杂,数据集也越来越大。为了应对这种规模的增长,分布式训练成为了训练大规模AI模型的关键技术。本文将介绍分布式训练的基本概念、常用框架(如TensorFlow和PyTorch)、最佳实践以及可能遇到的性能瓶颈和解决方案。
1478 2
|
存储 弹性计算 运维
阿里云容器服务Kubernetes版(ACK)部署与管理体验评测
阿里云容器服务Kubernetes版(ACK)是一个功能全面的托管Kubernetes服务,它为企业提供了快速、灵活的云上应用管理能力。
408 2
|
Linux C语言 编译器
gcc指定头文件路径及动态链接库路径
gcc指定头文件路径及动态链接库路径   本文详细介绍了linux 下gcc头文件指定方法,以及搜索路径顺序的问题。另外,还总结了,gcc动态链接的方法以及路径指定,同样也讨论了搜索路径的顺序问题。
2180 0
|
机器学习/深度学习 存储 自然语言处理
一文带你了解【深度学习】中CNN、RNN、LSTM、DBN等神经网络(图文解释 包括各种激活函数)
一文带你了解【深度学习】中CNN、RNN、LSTM、DBN等神经网络(图文解释 包括各种激活函数)
687 0
|
分布式计算 Prometheus 资源调度
分布式计算引擎 Flink/Spark on k8s 的实现对比以及实践
以 Flink 和 Spark 为代表的分布式流批计算框架的下层资源管理平台逐渐从 Hadoop 生态的 YARN 转向 Kubernetes 生态的 k8s 原生 scheduler 以及周边资源调度器,比如 Volcano 和 Yunikorn 等。这篇文章简单比较一下两种计算框架在 Native Kubernetes 的支持和实现上的异同,以及对于应用到生产环境我们还需要做些什么。
772 54
分布式计算引擎 Flink/Spark on k8s 的实现对比以及实践
下一篇
开通oss服务