Grain mall project part 15_ Distributed Advanced_ Commodity spike service, high concurrency methodology

catalogue

1, Commodity spike

  1. Second kill goods on backstage shelves
  2. Regular tasks are regularly put on the shelves to kill goods
  3. Commodity second kill process

2, High concurrency methodology

  1. Sentinel

    1. Fuse degradation current limiting
    2. Sentinel introduction
    3. Sentinel basic concepts
    4. Use to test current limiting, degradation and fusing
  2. 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>&nbsp;
        <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.


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.
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~

Keywords: Java

Added by dandaman2007 on Sun, 19 Dec 2021 20:10:28 +0200