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

本文涉及的产品
云数据库 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);
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
15小时前
|
存储 供应链 区块链
swap去中心化博饼交易所系统开发|细节详情|技术原理
区块链技术的去中心化和不可篡改性确保了供应链数据的安全性
|
6月前
|
缓存 负载均衡 NoSQL
学成在线----项目优化
学成在线----项目优化
|
11月前
|
消息中间件 NoSQL Java
订单服务------技术点及亮点1
订单服务------技术点及亮点
76 0
|
11月前
|
数据库
订单服务------技术点及亮点2
订单服务------技术点及亮点2
43 0
|
11月前
|
JSON 缓存 NoSQL
认证服务-----技术点及亮点1
认证服务-----技术点及亮点
57 0
|
11月前
|
NoSQL Java 关系型数据库
认证服务-----技术点及亮点2
认证服务-----技术点及亮点2
48 0
|
SQL 缓存 NoSQL
19.【学习心得】学习心得-大促秒杀活动催生缓存技术的高度使用
【学习心得】学习心得-大促秒杀活动催生缓存技术的高度使用
19.【学习心得】学习心得-大促秒杀活动催生缓存技术的高度使用
|
15小时前
|
云安全 安全 网络安全
【看案例】完美日记:保障电商大促活动平稳运行
完美日记在“双11”电商大促、“双12”购物节期间,依靠阿里云DDoS高防IP、云安全中心、Web应用防火墙基础安全三件套,从容面对业务高峰、安全压力,提升大促活动期间系统的快速响应能力和安全性。
|
存储 搜索推荐 算法
复盘6月,从常规到大促怎样分分钟搭建个性化会场
除了搜索,个性化推荐已成为电商行业的标配。那么,个性化推荐怎样快速赋予算法能力,挖掘转化增长新机遇?常规和大促期间有哪些操作技巧?
1279 0
复盘6月,从常规到大促怎样分分钟搭建个性化会场