之前的做文章讲述了一些常用的组件,这次我们来聊聊基于上面组件的高并发问题。我们先以高并发时,项目程序出现的现象入手。

一、修改项目

order-service

这里面我们不采用feign,而是采用普通的http请求的方式,用restTemplate。这里有两个接口。

@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private ProductFeginClient productFeginClient;

    @Autowired
    private RestTemplate  restTemplate;

    @RequestMapping(value = "/buy/{id}", method = RequestMethod.GET)
    public Product findById(@PathVariable Long id) {
//        Product product = productFeginClient.findById(id);
        Product product = restTemplate.getForObject("http://localhost:9001/product/1",Product.class);
        return product;
    }

     @RequestMapping(value = "/order", method = RequestMethod.GET)
    public String findByIdHei() {
        return "hehhe";
    }

修改配置文件(设置最大线程数):

product-service

利用线程模拟一个耗时的操作。

@RequestMapping(value = "/{id}",method = RequestMethod.GET)
	public Product findById(@PathVariable Long id) {
		try {
			Thread.sleep(4000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		Product product = productService.findById(id);
		product.setProductName("访问的服务地址:"+ip + ":" + port);
		return product;
	}

不要纠结端口了好吧,一定是能够调用的。

我们来访问一下这两个端口。6.38s,证明耗时操作是可以得。

293ms,访问很快。

二、搞一个压力测试工具

下载一个jmeter

配置线程组(每次20个线程访问,循环50个)

配置请求得地址

启动测试

启动之后访问/order/order接口。访问变成了6s多。这就是高并发出现的问题,影响了其他接口的正常访问

三、问题分析

我们看这张图,如果用户很多的话,并发量很高,我们的tomcat设置最大并发数是10。tomcat底层处理请求是一个线程池(要知道),最大线程数就是10,其余的请求多余,都会进入一个阻塞的有界队列中,之后等某个线程空闲,再从队列中取出,处理。

一个接口高并发的时候,我们发现另一个接口会变慢,主要原因就是起初所有的线程资源都被上一个接口占用。导致访问下面接口需要进入队列。如果并发量特别巨大的时候,可能会导致我们的程序崩溃或者服务器瘫痪。

如何解决?

有一种方案是,给两个接口都设立一个线程池,分别处理对应的接口请求。

四、实现用线程池隔离

引入依赖:

<dependency>
            <groupId>com.netflix.hystrix</groupId>
            <artifactId>hystrix-metrics-event-stream</artifactId>
            <version>1.5.12</version>
        </dependency>
        <dependency>
            <groupId>com.netflix.hystrix</groupId>
            <artifactId>hystrix-javanica</artifactId>
            <version>1.5.12</version>
        </dependency>

引入新类。这个就是我们单独配置的线程隔离。设置的线程数5很重要。(因为tomcat一共有10个,给了其中最大5个,剩余的自然是下一个接口的了)

package com.springcloud.demo.command;

import com.netflix.hystrix.*;
import com.springcloud.demo.entity.Product;
import org.springframework.web.client.RestTemplate;

public class OrderCommand extends HystrixCommand<Product> {

	private RestTemplate restTemplate;
	
	private Long id;

	public OrderCommand(RestTemplate restTemplate, Long id) {
		super(setter());
		this.restTemplate = restTemplate;
		this.id = id;
	}

	private static Setter setter() {

		// 服务分组
		HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("order_product");
		// 服务标识
		HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey("product");
		// 线程池名称
		HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("order_product_pool");
		/**
		 * 线程池配置
		 *     withCoreSize :  线程池大小为10
		 *     withKeepAliveTimeMinutes:  线程存活时间15秒
		 *     withQueueSizeRejectionThreshold  :队列等待的阈值为100,超过100执行拒绝策略
		 */
		HystrixThreadPoolProperties.Setter threadPoolProperties = HystrixThreadPoolProperties.Setter().withCoreSize(5)
				.withKeepAliveTimeMinutes(15).withQueueSizeRejectionThreshold(100);

		// 命令属性配置Hystrix 开启超时
		HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter()
				// 采用线程池方式实现服务隔离
				.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD)
				// 禁止
				.withExecutionTimeoutEnabled(false);
		return Setter.withGroupKey(groupKey).andCommandKey(commandKey).andThreadPoolKey(threadPoolKey)
				.andThreadPoolPropertiesDefaults(threadPoolProperties).andCommandPropertiesDefaults(commandProperties);

	}

	@Override
	protected Product run() throws Exception {
		return restTemplate.getForObject("http://localhost:9001/product/"+id, Product.class);
	}

	/**
	 * 降级方法!!
	 */
	@Override
	protected Product getFallback(){
		Product product = new Product();
		product.setProductName("不好意思,出错了!!!");
		return product;
	}
}

修改接口方法。

    @RequestMapping(value = "/buy/{id}", method = RequestMethod.GET)
    public Product findById(@PathVariable Long id) {
//        Product product = productFeginClient.findById(id);
//        Product product = restTemplate.getForObject("http://localhost:9001/product/1",Product.class);
        Product product = new OrderCommand(restTemplate,id).execute();
        return product;
    }

重新启动就可以了。再次测试,访问接口时间

 

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐