catalogue
1, Commodity spike
- Second kill goods on backstage shelves
- Regular tasks are regularly put on the shelves to kill goods
- Commodity second kill process
2, High concurrency methodology
-
Sentinel
- Fuse degradation current limiting
- Sentinel introduction
- Sentinel basic concepts
- Use to test current limiting, degradation and fusing
-
Sleuth + Zipkin service link tracking
- Why
- Basic terms
- integration
- Zipkim data persistence
1, Commodity spike
1. Sell goods in the background
# Preferential service, second kill service - id: coupon_router uri: lb://guli-shop-coupon predicates: - Path=/api/coupon/** filters: - RewritePath=/api/(?<segment>.*),/$\{segment}
New spike time period, associated spike goods
package henu.soft.xiaosi.coupon.service.impl; import com.alibaba.cloud.commons.lang.StringUtils; import jdk.nashorn.internal.ir.CallNode; import org.springframework.stereotype.Service; import java.util.Map; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import henu.soft.common.utils.PageUtils; import henu.soft.common.utils.Query; import henu.soft.xiaosi.coupon.dao.SeckillSkuRelationDao; import henu.soft.xiaosi.coupon.entity.SeckillSkuRelationEntity; import henu.soft.xiaosi.coupon.service.SeckillSkuRelationService; @Service("seckillSkuRelationService") public class SeckillSkuRelationServiceImpl extends ServiceImpl<SeckillSkuRelationDao, SeckillSkuRelationEntity> implements SeckillSkuRelationService { /** * According to the sessionId of the second kill session * Query the associated product list of second kill sessions * @param params * @return */ @Override public PageUtils queryPage(Map<String, Object> params) { QueryWrapper<SeckillSkuRelationEntity> wrapper = new QueryWrapper<SeckillSkuRelationEntity>(); // Get the commodity associated with the second kill time period, session id String promotionSessionId = (String) params.get("promotionSessionId"); if (!StringUtils.isEmpty(promotionSessionId)){ wrapper.eq("promotion_session_id",promotionSessionId); } IPage<SeckillSkuRelationEntity> page = this.page( new Query<SeckillSkuRelationEntity>().getPage(params), wrapper ); return new PageUtils(page); } }
2. Regular tasks and goods on the shelves
For goods that are regularly put on the shelves, the same product information can be put on the shelves for different sessions. Therefore, the key stored in redis is sessionid skuid session information - product information
1.cron expression
concept
- Official website: http://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html
- Characters separated by 6 spaces represent seconds, minutes, hours, days and months respectively
- Cron expression online generation: https://cron.qqe2.com/
2. Integrate SpringBoot
Timed task
- Create the xxschedule class and place the container
- Add @ enablesscheduling to class
- Add @ Scheduled (cron = "xxx") to the method to start the Scheduled task
be careful
- Scheduled tasks: execution should not be blocked. You can use the asynchronous thread pool completable future Runasync () is executed, but some versions of springboot configuration will not take effect, that is, it is blocked, so manual asynchrony is required
- Asynchronous: let scheduled tasks execute directly and asynchronously, annotate @ EnableAsync on classes and @ Async on methods
package henu.soft.xiaosi.seckill.config; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; /** * <p>Title: ScheduledConfig</p> * Description: * date: 2020/7/6 17:28 */ @EnableAsync @Configuration @EnableScheduling public class ScheduledConfig { }
3. Write code
The second kill service calls the preferential service and scans the second kill goods
package henu.soft.xiaosi.coupon.service.impl; import henu.soft.xiaosi.coupon.entity.SeckillSkuRelationEntity; import henu.soft.xiaosi.coupon.service.SeckillSkuRelationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import henu.soft.common.utils.PageUtils; import henu.soft.common.utils.Query; import henu.soft.xiaosi.coupon.dao.SeckillSessionDao; import henu.soft.xiaosi.coupon.entity.SeckillSessionEntity; import henu.soft.xiaosi.coupon.service.SeckillSessionService; @Service("seckillSessionService") public class SeckillSessionServiceImpl extends ServiceImpl<SeckillSessionDao, SeckillSessionEntity> implements SeckillSessionService { @Autowired SeckillSkuRelationService skuRelationService; @Override public PageUtils queryPage(Map<String, Object> params) { IPage<SeckillSessionEntity> page = this.page( new Query<SeckillSessionEntity>().getPage(params), new QueryWrapper<SeckillSessionEntity>() ); return new PageUtils(page); } @Override public List<SeckillSessionEntity> getLate3DaySession() { // Calculate the time of the last three days List<SeckillSessionEntity> list = this.list(new QueryWrapper<SeckillSessionEntity>().between("start_time", startTime(), endTime())); if(list != null && list.size() > 0){ return list.stream().map(session -> { // Write their second kill for each activity Long id = session.getId(); List<SeckillSkuRelationEntity> entities = skuRelationService.list(new QueryWrapper<SeckillSkuRelationEntity>().eq("promotion_session_id", id)); session.setRelationSkus(entities); return session; }).collect(Collectors.toList()); } return null; } private String startTime(){ LocalDateTime start = LocalDateTime.of(LocalDate.now(), LocalTime.MIN); return start.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); } private String endTime(){ LocalDate acquired = LocalDate.now().plusDays(2); return LocalDateTime.of(acquired, LocalTime.MAX).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); } }
Second kill service: the second kill service that needs to be executed regularly is put on the shelves
package henu.soft.xiaosi.seckill.scheduel; import henu.soft.xiaosi.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; /** * <p>Title: SeckillSkuScheduled</p> * Description: Second kill goods are regularly on the shelves [scheduled task scheduling of second kill] * date: 2020/7/6 17:28 */ @Slf4j @Service public class SeckillSkuScheduled { @Autowired private SeckillService seckillService; @Autowired private RedissonClient redissonClient; private final String upload_lock = "seckill:upload:lock"; /** * This should be idempotent * Execute once every three seconds: * / 3 * * * *? * 8 Once every hour: 0-8 * *? */ @Scheduled(cron = "0 0 0-8 * * ?") public void uploadSeckillSkuLatest3Day(){ log.info("\n Information of goods on the shelves"); // 1. Repeated shelves do not need processing. In addition, the distributed lock status has been updated. After the lock is released, other people can obtain the latest status RLock lock = redissonClient.getLock(upload_lock); lock.lock(10, TimeUnit.SECONDS); try { seckillService.uploadSeckillSkuLatest3Day(); } finally { lock.unlock(); } } }
@Override public void uploadSeckillSkuLatest3Day() { // 1. Scan the goods to participate in the second kill in the last three days R r = couponFeignService.getLate3DaySession(); if(r.getCode() == 0){ List<SeckillSessionsWithSkus> sessions = r.getData(new TypeReference<List<SeckillSessionsWithSkus>>() {}); // 2. Cache activity information saveSessionInfo(sessions); // 3. Cache the associated product information of the activity saveSessionSkuInfo(sessions); } }
- Save seckill activity time information
- Save second kill activity product information
// Cache activity information and save it to redis private void saveSessionInfo(List<SeckillSessionsWithSkus> sessions){ if(sessions != null){ sessions.stream().forEach(session -> { long startTime = session.getStartTime().getTime(); long endTime = session.getEndTime().getTime(); String key = SESSION_CACHE_PREFIX + startTime + "_" + endTime; Boolean hasKey = stringRedisTemplate.hasKey(key); if(!hasKey){ // Get all product IDS List<String> collect = session.getRelationSkus().stream().map(item -> item.getPromotionSessionId() + "-" + item.getSkuId()).collect(Collectors.toList()); // Cache activity information stringRedisTemplate.opsForList().leftPushAll(key, collect); } }); } } private void saveSessionSkuInfo(List<SeckillSessionsWithSkus> sessions){ if(sessions != null){ sessions.stream().forEach(session -> { BoundHashOperations<String, Object, Object> ops = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX); session.getRelationSkus().stream().forEach(seckillSkuVo -> { // 1. Random code of commodity String randomCode = UUID.randomUUID().toString().replace("-", ""); if(!ops.hasKey(seckillSkuVo.getPromotionSessionId() + "-" + seckillSkuVo.getSkuId())){ // 2. Cache items and their details SeckillSkuRedisTo redisTo = new SeckillSkuRedisTo(); BeanUtils.copyProperties(seckillSkuVo, redisTo); // 3.sku's basic data and sku's second kill information R info = productFeignService.skuInfo(seckillSkuVo.getSkuId()); if(info.getCode() == 0){ SkuInfoVo skuInfo = info.getData( new TypeReference<SkuInfoVo>() {}); redisTo.setSkuInfoVo(skuInfo); } // 4. Set the second kill information of the current commodity redisTo.setStartTime(session.getStartTime().getTime()); redisTo.setEndTime(session.getEndTime().getTime()); redisTo.setRandomCode(randomCode); ops.put(seckillSkuVo.getPromotionSessionId() + "-" + seckillSkuVo.getSkuId(), JSON.toJSONString(redisTo)); // If the inventory of goods in the current session has been put on the shelves, there is no need to put it on the shelves // 5. Use inventory as distributed semaphore current limiting RSemaphore semaphore = redissonClient.getSemaphore(SKUSTOCK_SEMAPHONE + randomCode); semaphore.trySetPermits(seckillSkuVo.getSeckillCount().intValue()); } }); }); } } ================== redis Not only need to save skuId,eeckillPrice And other information. You also need to save the details of seckill products. Convenient order============ package henu.soft.xiaosi.seckill.to; import henu.soft.xiaosi.seckill.vo.SkuInfoVo; import lombok.Data; import java.math.BigDecimal; /** * <p>Title: SeckillSkuRedisTo</p> * Description: * date: 2020/7/6 19:08 */ @Data public class SeckillSkuRedisTo { private Long promotionId; /** * Activity session id */ private Long promotionSessionId; /** * Commodity id */ private Long skuId; /** * Second kill random code of commodity */ private String randomCode; /** * Spike price */ private BigDecimal seckillPrice; /** * Total second kill */ private BigDecimal seckillCount; /** * Purchase limit per person */ private BigDecimal seckillLimit; /** * sort */ private Integer seckillSort; /** * sku Details of */ private SkuInfoVo skuInfoVo; /** * Start time of commodity spike */ private Long startTime; /** * End time of commodity spike */ private Long endTime; }
4. Problems needing attention
1. Set commodity random code
The second kill request needs to bring a random code. The random code is valid only at the beginning of the activity
2. Use distributed semaphores to solve the second kill inventory problem (current limit)
package henu.soft.xiaosi.seckill.config; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig; import com.alibaba.fastjson.serializer.SerializerFeature; import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.SerializationException; import org.springframework.data.redis.serializer.StringRedisSerializer; import java.nio.charset.Charset; /** * <p>Title: MyRedisConfig</p> * Description: * date: 2020/6/11 12:33 */ @Configuration public class MyRedisConfig { @Value("${ipAddr}") private String ipAddr; @Bean(destroyMethod = "shutdown") public RedissonClient redisson() { Config config = new Config(); // Create configuration for singleton mode config.useSingleServer().setAddress("redis://" + ipAddr + ":6379"); return Redisson.create(config); } MyRedisConfig(){ //Open the autotype function, and the classes that need to be forcibly converted will be added later at a time ParserConfig.getGlobalInstance() .addAccept("henu.soft.common.to.MemberResponseTo,org.springframework.web.servlet.FlashMap"); } @Bean public StringRedisTemplate redisTemplate(RedisConnectionFactory factory){ StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(factory); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); //Set the serialization method of String for key template.setKeySerializer(stringRedisSerializer); //String serialization is also used to set the key of hash template.setHashKeySerializer(stringRedisSerializer); FastJson2JsonRedisSerializer fastJson2JsonRedisSerializer =new FastJson2JsonRedisSerializer<Object>(Object.class); //Set the serialization method of fastjson used by value template.setValueSerializer(fastJson2JsonRedisSerializer); //Set the fastjson serialization method adopted by the value of hash template.setHashValueSerializer(fastJson2JsonRedisSerializer); //Set other default serialization methods to fastjson template.setDefaultSerializer(fastJson2JsonRedisSerializer); template.afterPropertiesSet(); return template; } public static class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> { public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); private Class<T> clazz; public FastJson2JsonRedisSerializer(Class<T> clazz) { super(); this.clazz = clazz; } @Override public byte[] serialize(T t) throws SerializationException { if (t == null) { return new byte[0]; } return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET); } @Override public T deserialize(byte[] bytes) throws SerializationException { if (bytes == null || bytes.length <= 0) { return null; } String str = new String(bytes, DEFAULT_CHARSET); return JSON.parseObject(str, clazz); } } }
3. Idempotency of second kill goods on the shelf
5. The home page displays the second kill product information
- Determine which second kill session the current time belongs to
- Determine the commodity information required for the current second kill
/** * The home page shows the second kill product information * @return */ @Override public List<SeckillSkuRedisTo> getCurrentSeckillSkus() { // 1. Determine which second kill session the current time belongs to long time = new Date().getTime(); // Define a protected resource Set<String> keys = stringRedisTemplate.keys(SESSION_CACHE_PREFIX + "*"); for (String key : keys) { // seckill:sessions:1593993600000_1593995400000 String replace = key.replace("seckill:sessions:", ""); String[] split = replace.split("_"); long start = Long.parseLong(split[0]); long end = Long.parseLong(split[1]); if(time >= start && time <= end){ // 2. Get all the commodity information of this second kill List<String> range = stringRedisTemplate.opsForList().range(key, 0, 100); BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX); List<String> list = hashOps.multiGet(range); if(list != null){ return list.stream().map(item -> { SeckillSkuRedisTo redisTo = JSON.parseObject(item, SeckillSkuRedisTo.class); // redisTo.setRandomCode(null); return redisTo; }).collect(Collectors.toList()); } break; } } return null; }
/** * The home page shows the second kill product information * @return */ @ResponseBody @GetMapping("/currentSeckillSkus") public R getCurrentSeckillSkus(){ List<SeckillSkuRedisTo> vos = seckillService.getCurrentSeckillSkus(); return R.ok().setData(vos); } ================ Page sending ajax request ====================== $.get("http://seckill.gulishop.cn/currentSeckillSkus",function (resp) { if(resp.data.length > 0){ resp.data.forEach(function (item) { $("<li οnclick='to_href(" + item.skuId + ")'></li>") .append($("<img style='width: 130px; height: 130px;' src='" + item.skuInfoVo.skuDefaultImg + "'/>")) .append($("<p>" + item.skuInfoVo.skuTitle + "</p>")) .append($("<span>¥" + item.seckillPrice + "</span>")) .append($("<s>¥" + item.skuInfoVo.price + "</s>")) .appendTo("#seckillSkuContent"); }) } })
6. The second kill information is displayed on the product details page
When the commodity service encapsulates the commodity information, call the second kill service to query whether there is a second kill time period for the current commodity
Second kill service
/** * The product details page displays the second kill time * @param skuId * @return */ @ResponseBody @GetMapping("/sku/seckill/{skuId}") public R getSkuSeckillInfo(@PathVariable("skuId") Long skuId){ SeckillSkuRedisTo to = seckillService.getSkuSeckillInfo(skuId); return R.ok().setData(to); }
/** * The product details page displays the second kill information * @param skuId * @return */ @Override public SeckillSkuRedisTo getSkuSeckillInfo(Long skuId) { BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX); Set<String> keys = hashOps.keys(); if(keys != null && keys.size() > 0){ String regx = "\\d-" + skuId; for (String key : keys) { if(Pattern.matches(regx, key)){ String json = hashOps.get(key); SeckillSkuRedisTo to = JSON.parseObject(json, SeckillSkuRedisTo.class); // Deal with random codes long current = new Date().getTime(); if(current <= to.getStartTime() || current >= to.getEndTime()){ to.setRandomCode(null); } return to; } } } return null; }
<li style="color: red" th:if="${item.seckillInfoVo!=null}"> <span th:if="${#dates.createNow().getTime() < item.seckillInfoVo.startTime}">The goods will be [[${#dates. Format (new Java. Util. Date (item. Seckillinfovo. Starttime), "yyyy MM DD HH: mm: SS")}]] perform second kill</span> <span th:if="${#dates.createNow().getTime() >= item.seckillInfoVo.startTime && #dates.createNow().getTime() <= item.seckillInfoVo.endTime}">price spike: [[${#numbers.formatDecimal(item.seckillInfoVo.seckillPrice,1,2)}]]</span> </li>
3. Commodity second kill process
1. Second kill is an independent process
// Items that are not second kill are displayed and added to the shopping cart $("#addCartItemA").click(function () { let skuId = $(this).attr("skuId"); let num = $("#productNum").val(); location.href = "http://cart.gulishop.cn/addCartItem?skuId=" + skuId + "&num=" + num; return false; }); // It's a second kill product display. Rush to buy it immediately $("#secKillA").click(function () { var isLogin = [[${session.loginUser != null}]] // The front-end page is current limited and can only be clicked after logging in if(isLogin){ var killId = $(this).attr("sessionid") + "-" + $(this).attr("skuid"); var num = $("#numInput").val(); location.href = "http://seckill.gulishop.cn/kill?killId=" + killId + "&key=" + $(this).attr("code") + "&num=" + num; }else{ layer.msg("Please log in first!") } return false; })
Second kill service generates second kill order information
@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); // 1. Judge whether to log in model.addAttribute("orderSn", orderSn); return "success"; }
Log in interceptor to limit current
/** * Second kill process * @param killId * @param key * @param num * @return */ @Override public String kill(String killId, String key, Integer num) { MemberResponseTo memberRsepVo = LoginUserInterceptor.threadLocal.get(); // 1. Get the details of the current second kill commodity BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX); String json = hashOps.get(killId); if(StringUtils.isEmpty(json)){ return null; }else{ SeckillSkuRedisTo redisTo = JSON.parseObject(json, SeckillSkuRedisTo.class); // Verify legitimacy long time = new Date().getTime(); if(time >= redisTo.getStartTime() && time <= redisTo.getEndTime()){ // 1. Check whether the random code matches the commodity id String randomCode = redisTo.getRandomCode(); String skuId = redisTo.getPromotionSessionId() + "-" + redisTo.getSkuId(); if(randomCode.equals(key) && killId.equals(skuId)){ // 2. Explain that the data is legal BigDecimal limit = redisTo.getSeckillLimit(); if(num <= limit.intValue()){ // 3. Verify whether this person has purchased it String redisKey = memberRsepVo.getId() + "-" + skuId; // Let data expire automatically long ttl = redisTo.getEndTime() - redisTo.getStartTime(); Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(redisKey, num.toString(), ttl<0?0:ttl, TimeUnit.MILLISECONDS); if(aBoolean){ // The successful occupation means that I have never bought it RSemaphore semaphore = redissonClient.getSemaphore(SKUSTOCK_SEMAPHONE + randomCode); boolean acquire = semaphore.tryAcquire(num); if(acquire){ // Second kill success // Quick order sending MQ String orderSn = IdWorker.getTimeId() + UUID.randomUUID().toString().replace("-","").substring(7,8); SecKillOrderTo orderTo = new SecKillOrderTo(); orderTo.setOrderSn(orderSn); orderTo.setMemberId(memberRsepVo.getId()); orderTo.setNum(num); orderTo.setSkuId(redisTo.getSkuId()); orderTo.setSeckillPrice(redisTo.getSeckillPrice()); orderTo.setPromotionSessionId(redisTo.getPromotionSessionId()); rabbitTemplate.convertAndSend("order-event-exchange","order.seckill.order", orderTo); return orderSn; } }else { return null; } } }else{ return null; } }else{ return null; } } return null; }
Separate second kill order information is put into RabbitMQ
package henu.soft.common.to.mq; import lombok.Data; import java.math.BigDecimal; @Data public class SecKillOrderTo { /** * order number */ private String orderSn; /** * Activity session id */ private Long promotionSessionId; /** * Commodity id */ private Long skuId; /** * Spike price */ private BigDecimal seckillPrice; /** * Purchase quantity */ private Integer num; /** * Member ID */ private Long memberId; }
2. The order service creates the queue and binding of second kill information
/** * Commodity spike queue * @return */ @Bean public Queue orderSecKillOrrderQueue() { 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; }
Listen to the second kill information queue
package henu.soft.xiaosi.order.listener; import com.rabbitmq.client.Channel; import henu.soft.common.to.mq.SecKillOrderTo; import henu.soft.xiaosi.order.service.OrderService; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.io.IOException; @Component @RabbitListener(queues = "order.seckill.order.queue") public class SeckillOrderListener { @Autowired private OrderService orderService; @RabbitHandler public void createOrder(SecKillOrderTo orderTo, Message message, Channel channel) throws IOException { System.out.println("***********Second kill message received"); long deliveryTag = message.getMessageProperties().getDeliveryTag(); try { orderService.createSeckillOrder(orderTo); channel.basicAck(deliveryTag, false); } catch (Exception e) { channel.basicReject(deliveryTag,true); } } }
Save second kill order
/** * Save second kill order information * @param */ @Override public void createSeckillOrder(SecKillOrderTo secKillOrderTo) { log.info("\n Create second kill order"); OrderEntity entity = new OrderEntity(); entity.setOrderSn(secKillOrderTo.getOrderSn()); entity.setMemberId(secKillOrderTo.getMemberId()); entity.setCreateTime(new Date()); entity.setPayAmount(secKillOrderTo.getSeckillPrice()); entity.setTotalAmount(secKillOrderTo.getSeckillPrice()); entity.setStatus(OrderStatusEnume.CREATE_NEW.getCode()); entity.setPayType(1); // TODO has a lot of settings BigDecimal price = secKillOrderTo.getSeckillPrice().multiply(new BigDecimal("" + secKillOrderTo.getNum())); entity.setPayAmount(price); this.save(entity); // Save order item information OrderItemEntity itemEntity = new OrderItemEntity(); itemEntity.setOrderSn(secKillOrderTo.getOrderSn()); itemEntity.setRealAmount(price); itemEntity.setOrderId(entity.getId()); itemEntity.setSkuQuantity(secKillOrderTo.getNum()); R info = productFeignService.info(secKillOrderTo.getSkuId()); SpuInfoTo spuInfo = info.getData(new TypeReference<SpuInfoTo>() {}); itemEntity.setSpuId(spuInfo.getId()); itemEntity.setSpuBrand(spuInfo.getBrandId().toString()); itemEntity.setSpuName(spuInfo.getSpuName()); itemEntity.setCategoryId(spuInfo.getCatalogId()); itemEntity.setGiftGrowth(secKillOrderTo.getSeckillPrice().multiply(new BigDecimal(secKillOrderTo.getNum())).intValue()); itemEntity.setGiftIntegration(secKillOrderTo.getSeckillPrice().multiply(new BigDecimal(secKillOrderTo.getNum())).intValue()); itemEntity.setPromotionAmount(new BigDecimal("0.0")); itemEntity.setCouponAmount(new BigDecimal("0.0")); itemEntity.setIntegrationAmount(new BigDecimal("0.0")); orderItemService.save(itemEntity); }
3. After the order service is saved, the order jumps to the second kill service success page
Seckill service success page
@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); // 1. Judge whether to log in model.addAttribute("orderSn", orderSn); return "success"; }
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="utf-8"/> <title>[[${orderSn != null?'Second kill success':'Second kill failed'}]]</title> <script type="text/javascript" src="/static/seckill/js/jquery-3.1.1.min.js"></script> <script type="text/javascript" src="/static/seckill/bootstrap/js/bootstrap.js"></script> <script type="text/javascript" src="/static/seckill/js/swiper.min.js"></script> <script src="/static/seckill/js/swiper.min.js"></script> <link rel="stylesheet" type="text/css" href="/static/seckill/css/swiper.min.css"/> <link rel="stylesheet" type="text/css" href="/static/seckill/bootstrap/css/bootstrap.css"/> <link rel="stylesheet" type="text/css" href="/static/seckill/css/success.css"/> </head> <body> <!--head--> <div class="alert-info"> <div class="hd_wrap_top"> <ul class="hd_wrap_left"> <li class="hd_home"><i class="glyphicon glyphicon-home"></i> <a href="http://gulishop. Cn "> home page of cereal mall</a> </li> </ul> <ul class="hd_wrap_right"> <li class="forel" th:if="${session.loginUser} ==null"> <a href="http://auth. gulishop. cn/login. html" class="link_ Login "> Hello, please login to < / a > & nbsp; <a href="http://auth. gulishop. cn/reg. html" class="link_ Register "> free registration</a> </li> <li class="forel" th:if="${session.loginUser} !=null"> <a class="link_login" style="width: 100px">[[${session.loginUser.username}]]</a> <a href="http://auth. gulishop. cn/oauth2. 0/logout" class="link_ Register "> log out</a> </li> <li class="spacer"></li> <li> <a href="">My order</a> </li> </ul> </div> </div> <div class="nav-tabs-justified"> <div class="nav_wrap"> <div class="nav_top"> <div class="nav_top_one"> <a href="http://gulishop.cn"><img src="/static/seckill/img/logo1.jpg" style="height: 60px;width:180px;"/></a> </div> <div class="nav_top_two"><input type="text"/> <button>search</button> </div> </div> </div> </div> <div class="main"> <div class="success-wrap"> <div class="w" id="result"> <div class="m succeed-box"> <div th:if="${orderSn != null}" class="mc success-cont"> <div class="success-lcol"> <div class="success-top"><b class="succ-icon"></b> <h3 class="ftx-02">Goods spike succeeded Order No: [[${orderSn}]]</h3></div> <div class="p-item"> <div class="clr"></div> </div> </div> <div class="success-btns success-btns-new"> <div class="success-ad"> <a href="#none"></a> </div> <div class="clr"></div> <div class="bg_shop"> <a class="btn-addtocart" th:href="'http://order. gulishop. cn/payOrder? Ordersn = '+ ${ordersn} "id =" gotoshoppingcart "> < b > < / b > pay</a> </div> </div> </div> <div th:if="${orderSn == null}" class="mc success-cont"> <div class="success-lcol"> <div class="success-top"><b class="succ-icon"></b> <h3 class="ftx-02">Second kill failed</h3></div> <div class="p-item"> <div class="p-img"> <a href="" target="_blank"></a> </div> <div class="p-info"> <div class="p-name"> </div> <div class="p-extra"><span class="txt" ></span></div> </div> <div class="clr"></div> </div> </div> <div class="success-btns success-btns-new"> <div class="success-ad"> <a href="#none"></a> </div> <div class="clr"></div> <div class="bg_shop"> <a class="btn-addtocart" href="http://gulishop. Cn "id =" gotoshoppingcart "> < b > < / b > go shopping</a> </div> </div> </div> </div> </div> </div> </div> </body> <script type="text/javascript" src="/static/seckill/js/success.js"></script> </html>
Then it calls the general order payment logic, which needs to be improved
- The database inventory needs to be locked for the goods that start the second kill. After the second kill, the remaining goods in redis will be restored
- ...
Current video transmission progress: P326 high concurrency methodology
2, High concurrency methodology
1.SpringCloud Aliababa Sentinel
1. Fuse degradation current limiting
- What is fusing
When A service invokes A function of B service, the function is caused by network instability or B service card
The room is very long. If this happens too many times. We can directly disconnect B (A no longer requests the B interface), and
Calling B directly returns degraded data without waiting for B's super long execution. In this way, the failure of B will not be affected
Ring A. - What is downgrade
The whole website is at the peak of traffic, and the server pressure increases sharply. According to the current business situation and traffic, some services and
The page is degraded with policies [stop the service, and all calls directly return degraded data]. This alleviates the of server resources
To ensure the normal operation of the core business, while also maintaining the correct response of customers and most customers.
Similarities and differences:
- Similarities:
1. In order to ensure the availability and reliability of most services in the cluster, prevent collapse and sacrifice the ego
2. Users eventually experience that a function is unavailable - difference:
1. Fusing is a system active rule triggered by the fault of the called party
2. Degradation is to stop some normal services and release resources based on global considerations - What is current limiting
Control the flow of requests entering the service so that the service can bear the flow pressure that does not exceed its capacity
2. Sentinel introduction
- Official documents: https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
- Project address: https://github.com/alibaba/Sentinel
With the popularity of microservices, the stability between services and services becomes more and more important. Sentinel takes traffic as the starting point,
Protect the stability of services from multiple dimensions such as flow control, fuse degradation and system load protection. - Sentinel has the following characteristics:
- Rich application scenarios: Sentinel has undertaken the core field of Alibaba's double 11 traffic promotion in recent 10 years
Scenarios, such as second kill (i.e. burst flow is controlled within the range of system capacity), message peak cutting and valley filling, and message collection
Group flow control, real-time fusing, downstream unavailable applications, etc.
Complete real-time monitoring: Sentinel also provides real-time monitoring function. You can see the access in the console
The second level data of a single machine applied, and even the summary operation of clusters with a scale of less than 500. - Extensive open source Ecology: Sentinel provides out of the box integration modules with other open source frameworks / libraries, such as
Integration with Spring Cloud, Dubbo and gRPC. You just need to introduce the corresponding dependencies and make a simple configuration
Set to quickly access Sentinel. - Perfect SPI extension point: Sentinel provides simple, easy-to-use and perfect SPI extension interface. You can
Implement extension interfaces to quickly customize logic. For example, custom rule management, adapting dynamic data sources, etc.
- Rich application scenarios: Sentinel has undertaken the core field of Alibaba's double 11 traffic promotion in recent 10 years
Sentinel is divided into two parts:
- The core library (Java client) does not depend on any framework / library and can run in all Java runtime environments
It also has good support for Dubbo / Spring Cloud and other frameworks. - The Dashboard is developed based on Spring Boot. It can be run directly after packaging without additional software
Tomcat and other application containers.
3. Sentinel basic concepts
- Resources (need protected resources, interfaces)
Resource is a key concept of Sentinel. It can be anything in a Java application, for example, provided by the application
A service provided by an application, or a service provided by another application called by an application, or even a piece of code. In the following article
In the file, we all use resources to describe code blocks.
As long as the code defined through Sentinel API is a resource, it can be protected by Sentinel. In most cases,
You can use method signatures, URL s, and even service names as resource names to identify resources. - Rules (current limiting and fusing rules)
The rules set around the real-time state of resources can include flow control rules, fuse degradation rules and system protection rules
Then. All rules can be dynamically adjusted in real time.
4. Use
- https://github.com/alibaba/Sentinel/wiki/%E4%B8%BB%E9%A1%B5
- What is fuse degradation
In addition to flow control, reducing unstable resources in the call link is also one of Sentinel's missions. Due to call off
Due to the complexity of the system, if a resource in the call link is unstable, it will eventually lead to the accumulation of requests.
-
Hystric isolation is thread pool isolation. If only 50 threads are allowed to access a request concurrently, multiple concurrent requests will be rejected. Multiple requests correspond to multiple thread pools, which will waste thread pool resources and increase server pressure. However, Sential uses a semaphore similar to redis
-
The principles of Sentinel and Hystrix are the same: when it is detected that a resource in the call link is unstable, for example
If the request response time is long or the proportion of exceptions increases, limit the call of this resource to make the request fail quickly,
Avoid cascading failures that affect other resources.
1. Design concept
Sentinel and Hystrix take a completely different approach to the means of restriction.
-
Hystrix isolates dependencies (corresponding resources in Sentinel's concept) through thread pool isolation
Leave. The advantage of this is the most complete isolation between resources. The disadvantage is that in addition to increasing the cost of thread switching
In this (too many thread pools lead to too many threads), it is also necessary to allocate the thread pool size to each resource in advance. -
Sentinel has taken two approaches to this problem:
- Limit by number of concurrent threads
Unlike resource pool isolation, Sentinel reduces the impact of unstable resources on resources by limiting the number of concurrent threads
It has an impact on resources. In this way, there is no loss of thread switching, and you do not need to pre allocate the size of the thread pool. When someone
When resources are unstable, such as longer response time, the direct impact on resources is that the number of threads will gradually increase
Pile up. When the number of threads accumulates to a certain number on a specific resource, new requests for that resource will be rejected. Pile up
The thread does not continue to receive requests until it has completed its task. - Degrade resources by response time
In addition to controlling the number of concurrent threads, Sentinel can also quickly degrade unstable resources through response time.
When the response time of the dependent resource is too long, all access to the resource will be directly denied until the specified time has passed
Restore after the time window.
- Limit by number of concurrent threads
2. Integration test current limiting
How to use:
- https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-examples/se
ntinel-example/sentinel-feign-example/readme-zh.md,- https://github.com/alibaba/Sentinel/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8
What we call resources can be anything, services, methods in services, or even a piece of code. Using Sentinel for resource protection is mainly divided into several steps:
- Define resources: https://github.com/alibaba/Sentinel/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8
- Define rules
- Is the inspection rule effective
-
After determining the resources, you can use the console configuration rules of the essential, and use the reference https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel
-
Import dependency, directly use the default adaptation method to determine resources (unordered operation), or use annotation, etc
<!-- Sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
-
Determine the version and download the Sential client 1.8 0
-
Start Java - jar sentinel-dashboard-1.8 0.jar
-
Configure the console information associated with each microservice
spring: cloud: sentinel: transport: port: 8719 dashboard: localhost:8080
-
Access the interface of the microservice, view the console, edit rules, test flow limits, and console documents
https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0
3. Import information audit of each microservice module
There is a problem with the previous test
- Adjust the current limiting parameters on the console and save them in memory. Restart is invalid
- In order to ensure that the flow restriction rules can be saved persistently, the information audit module needs to be imported
<!-- audit--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
Exposed port, the new version is no longer required
4. Configure user-defined current limit return information
The WebCallbackManager is no longer available
package henu.soft.xiaosi.seckill.config; import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.fastjson.JSON; import henu.soft.common.exception.BizCodeEnume; import henu.soft.common.utils.R; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * <p>Title: SecKillSentinelConfig</p> * Description: Processor after configuration request is restricted * date: 2020/7/10 13:47 */ @Component public class SecKillSentinelConfig implements BlockExceptionHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception { R error = R.error(BizCodeEnume.TO_MANY_REQUEST.getCode(), BizCodeEnume.TO_MANY_REQUEST.getMsg()); httpServletResponse.setCharacterEncoding("UTF-8"); httpServletResponse.setContentType("application/json"); httpServletResponse.getWriter().write(JSON.toJSONString(error)); } }
5. Integrate Feign to test fusing and degradation
Remote call fuse protection mechanism (remote service unreachable)
-
Caller on:
# Enable remote call feign: sentinel: enabled: true
-
Caller failed to set callback
package henu.soft.xiaosi.product.feign.fallback; import henu.soft.common.exception.BizCodeEnume; import henu.soft.common.utils.R; import henu.soft.xiaosi.product.feign.SeckillFeignService; import org.springframework.stereotype.Component; /** * <p>Title: SecKillFeignServiceFalback</p> * Description: * date: 2020/7/10 16:03 */ @Component public class SecKillFeignServiceFalback implements SeckillFeignService { @Override public R getSkuSeckillInfo(Long skuId) { System.out.println("Trigger fuse"); //return R.error(); return R.error(BizCodeEnume.TO_MANY_REQUEST.getCode(), BizCodeEnume.TO_MANY_REQUEST.getMsg()); } }
Remote call degradation protection mechanism (remote service load is too heavy, triggering fuse)
- Caller on:
# Enable remote call feign: sentinel: enabled: true
- The console sets the degradation strategy and triggers the fusing method when the threshold is exceeded
The supplier can also make degradation fuse
-
After defining the failed callback method
-
You can set it directly on the console
6. Customize protected resources
- Use try
- Use @ SentinelResource annotation to directly add value = "xxx" to the method, and optional parameter degradation method blockHandler = "xxx"
7. Gateway flow control
It is directly configured at the gateway layer, and the corresponding request is directly blocked to the micro service module
<!-- Sentinel Gateway current limiting--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId> <version>2.2.6.RELEASE</version> </dependency>
Click to open the API management of the console
Set route
Set various matching parameters and gateway flow control failure callback
package henu.soft.xiaosi.gateway.config; import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager; import com.alibaba.fastjson.JSON; import henu.soft.common.exception.BizCodeEnume; import henu.soft.common.utils.R; import org.springframework.context.annotation.Configuration; import org.springframework.web.reactive.function.server.ServerResponse; import reactor.core.publisher.Mono; /** * <p>Title: SentinelGateWayConfig</p> * Description: * date: 2020/7/10 17:57 */ @Configuration public class SentinelGateWayConfig { public SentinelGateWayConfig(){ GatewayCallbackManager.setBlockHandler((exchange, t) ->{ // When the gateway limits the flow, the request will call back this method R error = R.error(BizCodeEnume.TO_MANY_REQUEST.getCode(), BizCodeEnume.TO_MANY_REQUEST.getMsg()); String errJson = JSON.toJSONString(error); Mono<ServerResponse> body = ServerResponse.ok().body(Mono.just(errJson), String.class); return body; }); } }
2.SpringCloud Sleuth + Zipkin service link tracking
1. Why
-
Positioning problem microservices for better solution
-
Microservice architecture is a distributed architecture. It divides service units according to business. A distributed system often has many services
Unit. Due to the large number of service units and the complexity of business, it is difficult to locate errors and exceptions. main
This is reflected in that a request may need to call many services, and the complexity of calling internal services determines that the problem is difficult to solve
location. Therefore, in the microservice architecture, distributed link tracking must be implemented to follow up which services participate in a request,
What is the order of participation, so that the steps of each request are clearly visible. If something goes wrong, it will be located quickly. -
Link tracking components include Google's Dapper, Twitter's Zipkin, and Alibaba's eagle eye
They are excellent open source components for link tracking.
2. Basic terms
- Every time a microservice is called, the span is updated and the time stamps of cs and sr are recorded
- Span: basic work unit. Sending a remote scheduling task will generate a span. Span is a
A 64 bit ID uniquely identifies the Trace, another 64 bit ID uniquely identifies the Trace, and Span has other data messages
Information, such as summary, timestamp event, Span ID, and progress ID. - Trace: a tree structure composed of a series of spans. It requests an API interface of a microservice system,
This API interface needs to call multiple microservices. Calling each microservice will generate a new Span
The Span generated by this request constitutes the Trace. - Annotation: it is used to record an event in time. Some core annotations are used to define the opening of a request
Start and end. These notes include the following: - cs - Client Sent - the client sends a request. This annotation describes the beginning of the Span
- sr - Server Received - the server obtains the request and is ready to start processing it. If you subtract the cs timestamp from its sr
You can get the time of network transmission. - ss - Server Sent - this annotation indicates the completion of request processing (when the request returns to the customer)
If the ss timestamp is subtracted from the sr timestamp, the time requested by the server can be obtained. - cr - Client Received - the end of the Span if the timestamp of cr is subtracted
cs timestamp can get the time consumed by the whole request. - Official documents:
https://cloud.spring.io/spring-cloud-static/spring-cloud-sleuth/2.1.3.RELEASE/single/spring-cloud
-sleuth.html
If the service call sequence is as follows
3. Integrate Sleuth
-
1. Service provider and consumer import dependency
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-sleuth</artifactId> </dependency>
-
2. Open debug log
logging: level: org.springframework.cloud.openfeign: debug org.springframework.cloud.sleuth: debug
-
3. Initiate a remote call and observe the console
DEBUG [user-service,541450f08573fff5,541450f08573fff5,false] user-service: service name
- 541450f08573fff5: it is a TranceId. There is only one TranceId in a link
- 541450f08573fff5: spanId, the basic work unit id in the link
- false: indicates whether to output data to other services. If true, the information will be output to other visual services for observation
4. Integrate zipkin visual observation
Through the call chain monitoring information generated by Sleuth, we can know the call links between microservices, but the monitoring information is only output
It is inconvenient to view the console. We need a graphical tool - Zipkin. Zipkin is Twitter's open source distributed
Tracking system is mainly used to collect the timing data of the system, so as to track the call problem of the system. The official website address of zipkin is as follows:
https://zipkin.io/
-
1. Docker install zipkin server docker run -d -p 9411:9411 openzipkin/zipkin
-
2. Import
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency>
zipkin dependency also contains sleuth, and sleuth reference can be omitted
-
3. Add zipkin related configuration
spring: application: name: user-service zipkin: base-url: http://192.168.56.10:9411/ # zipkin server address # Turn off service discovery, otherwise Spring Cloud will take the url of zipkin as the service name discoveryClientEnabled: false sender: type: web # Set the way to transmit data using http sleuth: sampler: probability: 1 # Set the sampling collection rate to 100%, and the default is 0.1, i.e. 10%
Send remote request and test zipkin
5. Zipkin data persistence
Zipkin stores the monitoring data in memory by default. If Zipkin hangs up or restarts, the monitoring data will be lost
Lost. Therefore, if you want to build Zipkin available for production, you need to realize the persistence of monitoring data. And want to implement data
Persistence, of course, is to store data in a database. Fortunately, Zipkin supports storing data to:
- Memory (default)
- MySQL
- Elasticsearch
- Cassandra
Zipkin data persistence related official document addresses are as follows: https://github.com/openzipkin/zipkin#storage-component - Among the storage methods supported by Zipkin, memory is obviously not suitable for production, which was also said at the beginning. And use
For MySQL, when the amount of data is large, the query is slow, and it is not recommended. Cassandra is officially used by Twitter
As Zipkin's storage database, there are few companies using Cassandra on a large scale in China, and Cassandra related articles
Not many files.
In conclusion, Elasticsearch is a good choice. The official documents about using Elasticsearch as Zipkin's storage database are as follows:
- elasticsearch-storage: https://github.com/openzipkin/zipkin/tree/master/zipkin-server#elasticsearch-storage
zipkin-storage/elasticsearch
https://github.com/openzipkin/zipkin/tree/master/zipkin-storage/elasticsearch - Via docker
docker run --env STORAGE_TYPE=elasticsearch --env ES_HOSTS=192.168.56.10:9200
openzipkin/zipkin-dependencie
This is the end of the distributed advanced chapter~