• SpringCloud学习总结(九)——微服务架构高并发问题


    案例准备

    用例spring_cloud_hystrix 项目地址:传送门

     

     

    通过注册中心已经实现了微服务的服务注册和服务发现,并且通过Ribbon实现了负载均衡,已经借助Feign可以优雅的进行微服务调用。那么我们编写的微服务的性能怎么样呢,是否存在问题呢?

    一、测试工程准备

    注意:我们只使用order_service作为我们这章的教程用例。

    1、新建一个微服务,集成前面几章的订单服务模块,以及修改订单模块的服务端口如下:

    2、商品服务模块

    修改商品的controller的请求findById方法,添加线程延迟返回的代码

    @RequestMapping("/product")
    public class ProductController {
        @Autowired
        private ProductService productService;
        
        @RequestMapping(value = "/{id}",method = RequestMethod.GET)
        public Product findById(@PathVariable Long id) {
            //线程睡眠,模拟请求反馈延迟
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Product product = productService.findById(id);
            return product;
        }
    }

    修改application.yml文件,添加tomcat配置,控制线程访问线程最大个数

    server:
      port: 9002 #端口
      tomcat: #控制容器最大接收线程-压力测试
        max-threads: 10

     

    3、order_service的 Controller类中添加压力测试代码

    @RestController
    @RequestMapping("/order")
    public class OrderController {
    ​
        /**
         * 压力测试用
         * @param id
         * @return
         */
        @RequestMapping(value = "/{id}",method = RequestMethod.GET)
        public String findOrder(@PathVariable Long id) {
            System.out.println(Thread.currentThread().getName());
            return "压力测试-根据id查询订单"+id;
        }
    }

     

    4、启动测试

     

    二、性能工具Jmetter

    为了模拟服务在高并发时出现的延迟问题,我们使用Jmeter工具进行压力测试

     

     

    Apache JMeter 是Apache组织开发的基于Java的压力测试工具。用于对软件做压力测试,它最初被设计用于Web应用测试,但后来扩展到其他测试领域。 它可以用于测试静态和动态资源,例如静态文件、Java 小服务程序、CGI 脚本、Java 对象、数据库、FTP 服务器, 等等。JMeter 可以用于对服务器、网络或对象模拟巨大的负载,来自不同压力类别下测试它们的强度和分析整体性能。另外JMeter能够对应用程序做功能/回归测试,通过创建带有断言的脚本来验证你的程序返回了你期望的结果。为了最大限度的灵活性,JMeter允许使用正则表达式创建断言。

    1、安装Jmetter

    官网下载地址: http://jmeter.apache.org/download_jmeter.cgi

    Jmetter安装十分简单,下载最新安装包,解压找到安装目录下的bin/jmeter.bat 以管理员身份启动即可,如下:

     

    2、 配置Jmetter参数

    (1)创建新的测试计划

    ( 2)测试计划下创建发起请求的线程组

    线程组包含很多属性,目前我们只关注线程属性那一块。

    • 线程数:代表访问的并发数,默认是1。

    • Ramp-UpPeriod:表示多长时间内容启动所有线程,如果时间很短,会造成网站的瞬间高并发,默认值是1秒。

    • 循环次数:表示执行多少次,默认值为1,表示执行一次结束,这里可以勾选永远,让其一直运行下去。

     

    (3)创建http请求模板

    (4)添加监听器(结果树)

     

    用于展示压力测试中http请求的情况,如下:

    (5)压力测试过程

    操作过程
    1. 做完上面的操作后,启动压力测试。此时压力测试20个http请求 http://localhost:9002/order/buy/1 地址

    2. 浏览器访问订单服务的另一个http: http://localhost:9002/order/2

    出现问题

    浏览器访问订单服务的另一个http: http://localhost:9002/order/2 时,加载了一段时间,没有马上返回。

     

    三、系统负载过高存在的问题

    1、问题分析

    在微服务架构中,我们将业务拆分成一个个的服务,服务与服务之间可以相互调用,由于网络原因或者 自身的原因,服务并不能保证服务的100%可用,如果单个服务出现问题,调用这个服务就会出现网络延迟,此时若有大量的网络涌入,会形成任务累计,导致服务瘫痪。

    在SpringBoot程序中,默认使用内置tomcat作为web服务器。单tomcat支持最大的并发请求是有限的,如果某一接口阻塞,待执行的任务积压越来越多,那么势必会影响其他接口的调用。

     

    如上图所示,我们的压力测试订单服务中。我们设置了order_service的订单服的tomcat容器最大可接收10个线程。如果超过10个就是出现其他请求等待的现象(我们通过在商品服务中线程睡眠2s实现这个现象)。因此当压力测试的http请求20个线程都去访问【下单方法】造成了tomcat线程超过10个就要等待。后面用浏览器去访问【查询订单方法】时tomcat线程已经达到10个最大值,因此浏览器一直显示加载中。

     

    2、问题解决

    在高并发访问某个接口时造成线程排队等待时,为了不影响其他接口的正常访问,我们可以采用对服务多个接口之间进行隔离。

    • 线程池隔离(推荐):以上图例子说明,为高并发的接口方法分别都配置线程池,这样【下单方法】接口可以把等待的线程放入此方法对应的线程池中就不会影响其他接口的调用。其他接口请求就不会收影响。

    • 信号量隔离,计数器:为存在高并发的接口方法配置一个“最大阈值”,当请求这个接口方法的请求数超过这个阈值,则直接报错。

     

    3、线程池的形式实现服务隔离

     

    (1) 配置坐标

    为了方便实现线以线程池的形式完成资源隔离,需要引入如下hystrix依赖

     <!--hystrix-->
            <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>

    (2) 配置线程池

    配置HystrixCommand接口的实现类,再实现类中可以对线程池进行配置

    public class OrderCommand extends //调用商品服务方法Command<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(50)
                    .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 {
            System.out.println(Thread.currentThread().getName());
            //调用商品服务方法
            return restTemplate.getForObject("http://127.0.0.1:9001/product/"+id, Product.class);
        }
    ​
        /**
         * 降级方法
         */
        @Override
        protected Product getFallback(){
            Product product = new Product();
            product.setProductName("熔断降级:不好意思,出错了");
            return product;
        }
    }

    可以看到,线程池隔离代码实现起来有些复杂麻烦,后面会介绍Hystrix来简单的实现实现此功能。这里只做测试考用。

    (3) 配置调用

    修改 OrderController ,使用自定义的OrderCommand完成调用

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

    (4)重新进行压力测试

    经过代码添加服务接口的Hystrix的线程池方案,重新按上面的方式进行压力测试,则不会出现浏览器一直显示加载中的问题。

     

     

     


     感谢itheima提供的材料

     

     

  • 相关阅读:
    python3-cookbook笔记:第十三章 脚本编程与系统管理
    python3-cookbook笔记:第十二章 并发编程
    python3-cookbook笔记:第十章 模块与包
    python3-cookbook笔记:第九章 元编程
    python3-cookbook笔记:第八章 类与对象
    python3-cookbook笔记:第七章 函数
    python3-cookbook笔记:第六章 数据编码和处理
    python3-cookbook笔记:第五章 文件与IO
    python中的列表推导式
    python的类属性、实例属性、类方法、静态方法
  • 原文地址:https://www.cnblogs.com/TvvT-kevin/p/12527802.html
Copyright © 2020-2023  润新知