首页 > 其他分享 >atguigu7 秒杀_分布式锁/分布式信号量_MQ_Sentinel (seckill)

atguigu7 秒杀_分布式锁/分布式信号量_MQ_Sentinel (seckill)

时间:2022-12-07 18:36:19浏览次数:35  
标签:skuId seckill String atguigu7 限流 秒杀 return 分布式


0. 前言

0.1 秒杀架构:

atguigu7 秒杀_分布式锁/分布式信号量_MQ_Sentinel (seckill)_spring

0.2 分布式下定时任务问题:引入分布式锁

秒杀商品上架时,不能多个服务同时上架,防止重复上架

秒杀时,不能多个服务同时秒杀成功,防止重复秒杀

atguigu7 秒杀_分布式锁/分布式信号量_MQ_Sentinel (seckill)_spring_02

0.3 秒杀系统关注的问题和解决:

atguigu7 秒杀_分布式锁/分布式信号量_MQ_Sentinel (seckill)_redis_03

秒杀 问题 + 解决
1. 服务单一职责+独立部署:
秒杀服务即使自己扛不住压力,挂掉。不要影响别人
解决:新增秒杀服务

2. 秒杀链接加密
防止恶意攻击,模拟秒杀请求,1000次ls攻击。
防止链接暴露.自己工作人员,提前秒杀商品。

解决:请求需要随机码,在秒杀开始时随机码才会放在商品信息中
3. 库存预热+快速扣减【限流,并发信号量】
秒杀读多写少。无需每次实时校验库存。我们库存预热,放到redis中。信号量控制进来秒杀的请求

解决:库存放入redis中,使用分布式信号量扣减+限流
4. 动静分离
nginx做好动静分离。保证秒杀和商品详情页的动态请求才打到后端的服务集群。
使用CDN网络,分担本集群压力

解决:nginx
例:10万个人来访问商品详情页,这个详情页会发送63个请求,但是只有3个请求到达后端,60个请求是前端的。一共30万请求到达后端,600万个请求到达nginx或cdn

5. 恶意请求拦截
识别非法攻击请求并进行拦截,网关层拦截,放行到后太服务的请求都是正常请求
在网关层拦截:一些不带令牌的请求循环发送

解决:使用网关拦截
本系统做了登录拦截器【在各微服务创建的,未登录跳转登录页面】

6. 流量错峰
使用各种手段,将流量分担到更大宽度的时间点。比如验证码,加入购物车
1、输入验证码需要时间,将流量错开了【速度有快有慢】
2、加入购物车,然后再结算【速度有快有慢】

解决:使用购物车逻辑

7. 限流&熔断&降级
前踹限流+后端限流
限制次数,限制总量,快速失败降级运行,熔断隔离防止雪崩

前端限流:1、每次点击1s后才能再次点击
2、验证登录
后端限流:1、网关限流,例如访问秒杀的流量到达10W等2S再将请求传过去【其中10W是集群的峰值】
2、就算是合理的10次也只放行1-2次
3、熔断:远程访问失败,快速返回,并且下次不要再请求这个节点【防止请求长时间等待】
4、降级:请求量太大了,直接将请求转发到一个错误页面

出现一种情况:集群的处理能力是10W,网关放行了10W的请求,但此时秒杀服务掉线了2台,处理能力下降导致请求堆积,最后资源耗尽服务器全崩了

解决:spring alibaba sentinel
以前是Hystrix,现在不更新了就不用了

8. 队列削峰
100万个商品,每个商品的秒杀库存是100,会产生1亿的流量到后台,全部放入队列中,然后订单监听队列一个个创建订单扣减库存

解决:秒杀服务将创建订单的请求存入mq,订单服务监听mq。
优点:要崩只会崩秒杀服务,不会打垮其他服务【商品服务、订单服务、购物车服务】【第一套实现逻辑会导致这些问题】【看秒杀请求的两种实现】

0.4 redis秒杀方案:

atguigu7 秒杀_分布式锁/分布式信号量_MQ_Sentinel (seckill)_java_04

atguigu7 秒杀_分布式锁/分布式信号量_MQ_Sentinel (seckill)_java_05

京东秒杀:商品价格改为秒杀价格 -> 加入购物车 -> 下单 

本项目:单独的秒杀微服务模块:秒杀商品上架 -> 秒杀 -> 下单 

1. 秒杀商品定时上架 (Redisson分布式锁、Redisson信号量、随机码)

atguigu7 秒杀_分布式锁/分布式信号量_MQ_Sentinel (seckill)_redis_06

定时任务:

定时任务不该阻塞,Spring Schedule 默认是阻塞的,以下是定时任务的实现:
1)、可以让业务以异步的方式,自己提交到线程池
CompletableFuture.runAsync(() -> {},execute);
2)、支持定时任务线程池;
设置 TaskSchedulingProperties
spring.task.scheduling.pool.size: 5
3)、使用异步任务让定时任务异步执行(推荐)
解决:使用异步任务 + 定时任务来完成定时任务不阻塞的功能

定时任务
1、@EnableScheduling 开启定时任务
2、@Scheduled 开启一个定时任务
3、自动配置类 TaskSchedulingAutoConfiguration

异步任务
1、@EnableAsync 开启异步任务功能
2、@Async 给希望异步执行的方法上标注
3、自动配置类 TaskExecutionAutoConfiguration 属性绑定在TaskExecutionProperties

1.1 seckill:

config:

ScheduledConfig.java

package com.atguigu.gulimall.seckill.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableAsync
@EnableScheduling
@Configuration
public class ScheduledConfig {
}

scheduled:

SeckillSkuScheduled.java

package com.atguigu.gulimall.seckill.scheduled;


import com.atguigu.gulimall.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 SeckillSkuScheduled {

@Autowired
SeckillService seckillService;

@Autowired
RedissonClient redissonClient;

private final String upload_lock = "seckill:upload:lock";

//TODO 幂等性处理
// @Scheduled(cron = "*/3 * * * * ?")
@Scheduled(cron = "0 * * * * ?") //每分钟执行一次吧,上线后调整为每天晚上3点执行
// @Scheduled(cron = "0 0 3 * * ?") 线上模式
public void uploadSeckillSkuLatest3Days(){
//1、重复上架无需处理
log.info("上架秒杀的商品信息...");
// 分布式锁。锁的业务执行完成,状态已经更新完成。释放锁以后。其他人获取到就会拿到最新的状态。
RLock lock = redissonClient.getLock(upload_lock);
lock.lock(10, TimeUnit.SECONDS);
try{
seckillService.uploadSeckillSkuLatest3Days();
}finally {
lock.unlock();
}

}

}

service:

SeckillService.java

SeckillServiceImpl.java

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

private void saveSessionInfos(List<SeckillSesssionsWithSkus> sesssions) {
if (sesssions != null)
sesssions.stream().forEach(sesssion -> {

Long startTime = sesssion.getStartTime().getTime();
Long endTime = sesssion.getEndTime().getTime();
String key = SESSIONS_CACHE_PREFIX + startTime + "_" + endTime;
Boolean hasKey = redisTemplate.hasKey(key);
if (!hasKey) {
List<String> collect = sesssion.getRelationSkus().stream().map(item -> item.getPromotionSessionId() + "_" + item.getSkuId().toString()).collect(Collectors.toList());
//缓存活动信息
redisTemplate.opsForList().leftPushAll(key, collect);
//TODO 设置过期时间[已完成]
redisTemplate.expireAt(key, new Date(endTime));
}


});
}

// 在 Redis 中保存秒杀商品信息(使用Redisson信号量、随机码)
private void saveSessionSkuInfos(List<SeckillSesssionsWithSkus> sesssions) {
if (sesssions != null)
sesssions.stream().forEach(sesssion -> {
//准备hash操作
BoundHashOperations<String, Object, Object> ops = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
sesssion.getRelationSkus().stream().forEach(seckillSkuVo -> {
//4、随机码? seckill?skuId=1&key=dadlajldj;
String token = UUID.randomUUID().toString().replace("-", "");

if (!ops.hasKey(seckillSkuVo.getPromotionSessionId().toString() + "_" + seckillSkuVo.getSkuId().toString())) {
//缓存商品
SecKillSkuRedisTo redisTo = new SecKillSkuRedisTo();
//1、sku的基本数据
R skuInfo = productFeignService.getSkuInfo(seckillSkuVo.getSkuId());
if (skuInfo.getCode() == 0) {
SkuInfoVo info = skuInfo.getData("skuInfo", new TypeReference<SkuInfoVo>() {
});
redisTo.setSkuInfo(info);
}

//2、sku的秒杀信息
BeanUtils.copyProperties(seckillSkuVo, redisTo);

//3、设置上当前商品的秒杀时间信息
redisTo.setStartTime(sesssion.getStartTime().getTime());
redisTo.setEndTime(sesssion.getEndTime().getTime());

redisTo.setRandomCode(token);
String jsonString = JSON.toJSONString(redisTo);
//TODO 每个商品的过期时间不一样。所以,我们在获取当前商品秒杀信息的时候,做主动删除,代码在 getSkuSeckillInfo 方法里面
ops.put(seckillSkuVo.getPromotionSessionId().toString() + "_" + seckillSkuVo.getSkuId().toString(), jsonString);
//如果当前这个场次的商品的库存信息已经上架就不需要上架
//5、使用库存作为分布式的信号量 限流;
RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + token);
//商品可以秒杀的数量作为信号量
semaphore.trySetPermits(seckillSkuVo.getSeckillCount());
//TODO 设置过期时间。
semaphore.expireAt(sesssion.getEndTime());
}
});
});
}

@Override
public SecKillSkuRedisTo getSkuSeckillInfo(Long skuId) {

//1、找到所有需要参与秒杀的商品的key
BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);


Set<String> keys = hashOps.keys();
if (keys != null && keys.size() > 0) {
String regx = "\\d_" + skuId;
for (String key : keys) {
//6_4
if (Pattern.matches(regx, key)) {
String json = hashOps.get(key);
SecKillSkuRedisTo skuRedisTo = JSON.parseObject(json, SecKillSkuRedisTo.class);
//TODO 加入非空判断
if (skuRedisTo == null) return null;
//随机码
long current = new Date().getTime();
if (current >= skuRedisTo.getStartTime() && current <= skuRedisTo.getEndTime()) {
//TODO
} else {
//TODO 当前商品已经过了秒杀时间要直接删除
hashOps.delete(key);
skuRedisTo.setRandomCode(null);
}
return skuRedisTo;
}
;
}
}
return null;
}

2. 秒杀流程

  • 点击立即抢购时,会发送请求
  • 秒杀请求会对请求校验时效、商品随机码、当前用户是否已经抢购过当前商品、库存和购买量,通过校验的则秒杀成功,发送消息创建订单

atguigu7 秒杀_分布式锁/分布式信号量_MQ_Sentinel (seckill)_java_07

2.1 seckill:

controller:

SeckillController.java

@GetMapping("/kill")
public String secKill(@RequestParam("killId") String killId,
@RequestParam("key") String key,
@RequestParam("num") Integer num,
Model model){

String orderSn = seckillService.kill(killId,key,num);

model.addAttribute("orderSn",orderSn);
//1、判断是否登录
return "success";
}

service:

SeckillService.java

SeckillServiceImpl.java

// TODO 上架秒杀商品的时候,每一个数据都有过期时间。
// TODO 秒杀后续的流程,简化了收货地址等信息。
@Override
public String kill(String killId, String key, Integer num) {

long s1 = System.currentTimeMillis();
MemberRespVo respVo = LoginUserInterceptor.loginUser.get();

//1、获取当前秒杀商品的详细信息
BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);

String json = hashOps.get(killId);
if (StringUtils.isEmpty(json)) {
return null;
} else {
SecKillSkuRedisTo redis = JSON.parseObject(json, SecKillSkuRedisTo.class);
//校验合法性
Long startTime = redis.getStartTime();
Long endTime = redis.getEndTime();
long time = new Date().getTime();

long ttl = endTime - time;

//1、校验时间的合法性
if (time >= startTime && time <= endTime) {
//2、校验随机码和商品id
String randomCode = redis.getRandomCode();
String skuId = redis.getPromotionSessionId() + "_" + redis.getSkuId();
if (randomCode.equals(key) && killId.equals(skuId)) {
//3、验证购物数量是否合理
if (num <= redis.getSeckillLimit()) {
//4、验证这个人是否已经购买过。幂等性; 如果只要秒杀成功,就去占位。 userId_SessionId_skuId
//SETNX
String redisKey = respVo.getId() + "_" + skuId;
//自动过期
Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(redisKey, num.toString(), ttl, TimeUnit.MILLISECONDS);
if (aBoolean) {
//占位成功说明从来没有买过
RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + randomCode);
//120 20ms
boolean b = semaphore.tryAcquire(num);
if (b) {
//秒杀成功;
//快速下单。发送MQ消息 10ms
String timeId = IdWorker.getTimeId();
SeckillOrderTo orderTo = new SeckillOrderTo();
orderTo.setOrderSn(timeId);
orderTo.setMemberId(respVo.getId());
orderTo.setNum(num);
orderTo.setPromotionSessionId(redis.getPromotionSessionId());
orderTo.setSkuId(redis.getSkuId());
orderTo.setSeckillPrice(redis.getSeckillPrice());
rabbitTemplate.convertAndSend("order-event-exchange", "order.seckill.order", orderTo);
long s2 = System.currentTimeMillis();
log.info("耗时...{}", (s2 - s1));
return timeId;
}
return null;

} else {
//说明已经买过了
return null;
}

}
} else {
return null;
}

} else {
return null;
}
}


return null;
}

2.2 order:

config:

MyMQConfig.java

@Bean
public Queue orderSeckillOrderQueue(){
//String name, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
return new Queue("order.seckill.order.queue",true,false,false);
}

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

listener:

OrderSeckillListener.java

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

@Autowired
OrderService orderService;
@RabbitHandler
public void listener(SeckillOrderTo seckillOrder, Channel channel, Message message) throws IOException {
try{
log.info("准备创建秒杀单的详细信息。。。");
orderService.createSeckillOrder(seckillOrder);
//手动调用支付宝收单;
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}catch (Exception e){
channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
}

}
}

service:

OrderService.java

OrderServiceImpl.java

@Override
public void createSeckillOrder(SeckillOrderTo seckillOrder) {
//TODO 保存订单信息
OrderEntity orderEntity = new OrderEntity();
orderEntity.setOrderSn(seckillOrder.getOrderSn());
orderEntity.setMemberId(seckillOrder.getMemberId());

orderEntity.setStatus(OrderStatusEnum.CREATE_NEW.getCode());

BigDecimal multiply = seckillOrder.getSeckillPrice().multiply(new BigDecimal("" + seckillOrder.getNum()));
orderEntity.setPayAmount(multiply);
this.save(orderEntity);

//TODO 保存订单项信息
OrderItemEntity orderItemEntity = new OrderItemEntity();
orderItemEntity.setOrderSn(seckillOrder.getOrderSn());
orderItemEntity.setRealAmount(multiply);
//TODO 获取当前SKU的详细信息进行设置 productFeignService.getSpuInfoBySkuId()
orderItemEntity.setSkuQuantity(seckillOrder.getNum());

orderItemService.save(orderItemEntity);
}

3. Sentinel

3.1 网关层面限流 (第一层)

pom.xml

<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba.csp/sentinel-spring-cloud-gateway-adapter -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>

config:

SentinelGatewayConfig.java

@Configuration
public class SentinelGatewayConfig {

//TODO 响应式编程
//GatewayCallbackManager
public SentinelGatewayConfig(){
GatewayCallbackManager.setBlockHandler(new BlockRequestHandler(){
//网关限流了请求,就会调用此回调 Mono Flux
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange exchange, Throwable t) {

R error = R.error(BizCodeEnume.TOO_MANY_REQUEST.getCode(), BizCodeEnume.TOO_MANY_REQUEST.getMsg());
String errJson = JSON.toJSONString(error);

// Mono<String> aaa = Mono.just("aaa");
Mono<ServerResponse> body = ServerResponse.ok().body(Mono.just(errJson), String.class);
return body;
}
});

// FlowRule flowRule = new FlowRule();
// flowRule.setRefResource("gulimall_seckill_route");
flowRule.set
// FlowRuleManager.loadRules(Arrays.asList(flowRule));
}
}

3.2 代码层面熔断,限流,降级 (第二层)

1、整合Sentinel  
1)、导入依赖 spring-cloud-starter-alibaba-sentinel
2)、下载sentinel的控制台
3)、配置sentinel控制台地址信息
4) 、在控制台调整参数。【默认所有的流控设置保存在内存中,重启失效】


2、每一个微服务都导入 actuator ();并配合management.endpoints.web.exposure.include=*
3、自定义sentinel流控返回数据

4、使用Sentinel来保护feign远程调用:熔断;
1)、调用方的熔断保护:feign.sentinel.enabled=true
2)、调用方手动指定远程服务的降级策略。远程服务被降级处理。触发我们的熔断回调方法
3)、超大浏览的时候,必须牺牲一些远程服务。在服务的提供方(远程服务)指定降级策略;
提供方是在运行。但是不运行自己的业务逻辑,返回的是默认的降级数据(限流的数据),

5、自定义受保护的资源
1)、代码
try(Entry entry = SphU.entry("seckillSkus")){
//业务逻辑
}
catch(Execption e){}

2)、基于注解。
@SentinelResource(value = "getCurrentSeckillSkusResource",blockHandler = "blockHandler")

无论是1,2方式一定要配置被限流以后的默认返回.
url请求可以设置统一返回:WebCallbackManager

sentinel配置好后,受保护分为两类:

1). 请求:所有url请求都会被扫描

2). 资源:通过 @SentinelResource 配置的资源

核心:

1). 配置Sentinel 会对 Feign 进行监控

feign.sentinel.enabled=true

2). Feign 的降级:在@FeignClient设置​fallback​属性

@FeignClient(value = "kedamall-seckill",fallback = SeckillFeignServiceFallback.class)
public interface SeckillFeignService {
@GetMapping("/sku/seckill/{skuId}")
R getSkuSeckillInfo(@PathVariable("skuId") Long skuId);
}

3.2.1 product: 调用方/服务调用方(熔断,限流)

pom.xml

<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

application.properties

spring.cloud.sentinel.transport.port=8719
spring.cloud.sentinel.transport.dashboard=localhost:8333
management.endpoints.web.exposure.include=*
feign.sentinel.enabled=true

web:

ItemController.java

@Controller
public class ItemController {

@Autowired
SkuInfoService skuInfoService;

/**
* 展示当前sku的详情
* @param skuId
* @return
*/
@GetMapping("/{skuId}.html")
public String skuItem(@PathVariable("skuId") Long skuId, Model model) throws ExecutionException, InterruptedException {

System.out.println("准备查询"+skuId+"详情");
SkuItemVo vo = skuInfoService.item(skuId);
model.addAttribute("item",vo);

return "item";
}
}

service:

SkuInfoService.java

SkuInfoServiceImpl.java

@Override
public SkuItemVo item(Long skuId) throws ExecutionException, InterruptedException {

SkuItemVo skuItemVo = new SkuItemVo();

CompletableFuture<SkuInfoEntity> infoFuture = CompletableFuture.supplyAsync(() -> {
//1、sku基本信息的获取 pms_sku_info
SkuInfoEntity info = this.getById(skuId);
skuItemVo.setInfo(info);
return info;
}, executor);


CompletableFuture<Void> saleAttrFuture = infoFuture.thenAcceptAsync((res) -> {
//3、获取spu的销售属性组合
List<SkuItemSaleAttrVo> saleAttrVos = skuSaleAttrValueService.getSaleAttrsBySpuId(res.getSpuId());
skuItemVo.setSaleAttr(saleAttrVos);
}, executor);


CompletableFuture<Void> descFuture = infoFuture.thenAcceptAsync((res) -> {
//4、获取spu的介绍 pms_spu_info_desc
SpuInfoDescEntity spuInfoDescEntity = spuInfoDescService.getById(res.getSpuId());
skuItemVo.setDesc(spuInfoDescEntity);
}, executor);


CompletableFuture<Void> baseAttrFuture = infoFuture.thenAcceptAsync((res) -> {
//5、获取spu的规格参数信息
List<SpuItemAttrGroupVo> attrGroupVos = attrGroupService.getAttrGroupWithAttrsBySpuId(res.getSpuId(), res.getCatalogId());
skuItemVo.setGroupAttrs(attrGroupVos);
}, executor);

//2、sku的图片信息 pms_sku_images
CompletableFuture<Void> imageFuture = CompletableFuture.runAsync(() -> {
List<SkuImagesEntity> imagesEntities = imagesService.getImagesBySkuId(skuId);
skuItemVo.setImages(imagesEntities);
}, executor);

CompletableFuture<Void> seckillFuture = CompletableFuture.runAsync(() -> {
//3、远程调用查询当前sku是否参与秒杀优惠活动
R skuSeckilInfo = seckillFeignService.getSkuSeckilInfo(skuId);
if (skuSeckilInfo.getCode() == 0) {
//查询成功
SeckillSkuVo seckilInfoData = skuSeckilInfo.getData("data", new TypeReference<SeckillSkuVo>() {
});
skuItemVo.setSeckillSkuVo(seckilInfoData);

if (seckilInfoData != null) {
long currentTime = System.currentTimeMillis();
if (currentTime > seckilInfoData.getEndTime()) {
skuItemVo.setSeckillSkuVo(null);
}
}
}
}, executor);

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

return skuItemVo;
}

feign:

SeckillFeignService.java

@FeignClient(value = "gulimall-seckill",fallback = SeckillFeignServiceFallBack.class)
public interface SeckillFeignService {

@GetMapping("/sku/seckill/{skuId}")
R getSkuSeckillInfo(@PathVariable("skuId") Long skuId);
}

feign.fallback:

SeckillFeignServiceFallBack.java

@Slf4j
@Component
public class SeckillFeignServiceFallBack implements SeckillFeignService {
@Override
public R getSkuSeckillInfo(Long skuId) {
log.info("熔断方法调用...getSkuSeckillInfo");
return R.error(BizCodeEnume.TOO_MANY_REQUEST.getCode(),BizCodeEnume.TOO_MANY_REQUEST.getMsg());
}
}

3.2.2 seckill:被调方/服务提供方(降级):全局流量大

pom.xml

<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

application.properties

spring.cloud.sentinel.transport.port=8719
spring.cloud.sentinel.transport.dashboard=localhost:8333
management.endpoints.web.exposure.include=*
feign.sentinel.enabled=true

config: 自定义返回

SeckillSentinelConfig.java

@Slf4j
@Component
public class SeckillFeignServiceFallBack implements SeckillFeignService {
@Override
public R getSkuSeckillInfo(Long skuId) {
log.info("熔断方法调用...getSkuSeckillInfo");
return R.error(BizCodeEnume.TOO_MANY_REQUEST.getCode(),BizCodeEnume.TOO_MANY_REQUEST.getMsg());
}
}

3.2.3 seckill:自定义受保护的资源

atguigu7 秒杀_分布式锁/分布式信号量_MQ_Sentinel (seckill)_redis_08

/**
* blockHandler 函数会在原方法被限流/降级/系统保护的时候调用,而 fallback 函数会针对所有类型的异常。
* @return
*/
//返回当前时间可以参与的秒杀商品信息
@SentinelResource(value = "getCurrentSeckillSkusResource",blockHandler = "blockHandler")
@Override
public List<SecKillSkuRedisTo> getCurrentSeckillSkus() {
//1、确定当前时间属于哪个秒杀场次。
//1970 -
long time = new Date().getTime();

try(Entry entry = SphU.entry("seckillSkus")){
Set<String> keys = redisTemplate.keys(SESSIONS_CACHE_PREFIX + "*");
for (String key : keys) {
//seckill:sessions:1582250400000_1582254000000
String replace = key.replace(SESSIONS_CACHE_PREFIX, "");
String[] s = replace.split("_");
Long start = Long.parseLong(s[0]);
Long end = Long.parseLong(s[1]);
if (time >= start && time <= end) {
//2、获取这个秒杀场次需要的所有商品信息
List<String> range = redisTemplate.opsForList().range(key, -100, 100);
BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
List<String> list = hashOps.multiGet(range);
if (list != null) {
List<SecKillSkuRedisTo> collect = list.stream().map(item -> {
SecKillSkuRedisTo redis = JSON.parseObject((String) item, SecKillSkuRedisTo.class);
redis.setRandomCode(null); 当前秒杀开始就需要随机码
return redis;
}).collect(Collectors.toList());
return collect;
}
break;
}
}
}catch (BlockException e){
log.error("资源被限流,{}",e.getMessage());
}
return null;
}

标签:skuId,seckill,String,atguigu7,限流,秒杀,return,分布式
From: https://blog.51cto.com/u_15905340/5919904

相关文章

  • atguigu3 三级菜单/目录_分布式缓存/分布式锁(product/category)
    0.问题:使用分布式锁解决读模式缓存失效(缓存击穿)和写模式缓存一致性问题!!!公共代码:CategoryBrandRelationService.javapackagecom.atguigu.gulimall.product.service;import......
  • 本地事务和分布式事务
    1.本地事务1.1 @Transactional注解@Transactional是本地事务,在分布式系统中。只能控制住自己的回滚,控制不了其他服务的回滚。1.1.1isolation1.1.1.1READUNCOMMITTED(读......
  • 【分布式应用】GFS分布式文件系统
    GlusterFS概述文件系统:用于存储和管理文件的相关系统。存储系统类型存储技术块存储硬盘文件存储NFS、SISC、FTP对......
  • 分布式系统扩展
    1.共享结构(垂直扩展):购买更强大的机器1.1共享内存架构:共享cpu,内存,磁盘缺点是成本增长过快,无异地容错能力1.2共享磁盘架构:共享磁盘每个服务器拥有独立的cpu和内存,将数据存......
  • 分布式面试题
    0. 1.2.3. 分布式锁的几种实现方式分布式锁是控制分布式系统之间同步访问共享资源的一种方式。其典型的使用场景为:不同系统或者是同一系统的不同主机之间共享了一个或一......
  • 深度剖析Saga分布式事务
    简介:大家好,我是枫哥,......
  • 分布式事务
    分布式事务什么是事务举个生活中的例子:你去商店买东西就是一个事务的例子,买东西是一个交易,包含“一手交钱,一手交货”两个动作,交钱和交货这两个动作必须全部成功,交易才算成功......
  • Git分布式版本控制工具
    Git分布式版本控制工具1.Git工作流程图命令:clone(克隆):从远程仓库中克隆代码到本地仓库checkout(检出):从本地仓库中检出一个仓库分支然后进行修订add(添加):在提交......
  • Redis实现分布式锁的7种方案
    种方案前言日常开发中,秒杀下单、抢红包等等业务场景,都需要用到分布式锁。而Redis非常适合作为分布式锁使用。本文将分七个方案展开,跟大家探讨Redis分布式锁的正确使用方式......
  • scrapy-redis分布式
    一、简介  scrapy是一个基于redis的scrapy组件,用于快速实现scrapy项目的分布式数据爬取。(一)安装redispipinstallscrapy_redis(二)执行流程图调度器、管道不可以......