1 准备环境

1.1 准备商品微服务和订单微服务

  • 其中商品微服务的findById()方法设置休眠2秒,用来模拟网络波动等情况:
  1. package com.sunxiaping.product.controller;
  2. import com.sunxiaping.product.domain.Product;
  3. import com.sunxiaping.product.service.ProductService;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.beans.factory.annotation.Value;
  6. import org.springframework.web.bind.annotation.*;
  7. @RestController
  8. @RequestMapping(value = "/product")
  9. public class ProductController {
  10. @Autowired
  11. private ProductService productService;
  12. @Value("${server.port}")
  13. private String port;
  14. @Value("${spring.cloud.client.ip-address}")
  15. private String ip;
  16. @PostMapping(value = "/save")
  17. public String save(@RequestBody Product product) {
  18. productService.save(product);
  19. return "新增成功";
  20. }
  21. @GetMapping(value = "/findById/{id}")
  22. public Product findById(@PathVariable(value = "id") Long id) {
  23. try {
  24. //休眠2秒,用来模拟 网络波动等情况
  25. Thread.sleep(2000);
  26. } catch (InterruptedException e) {
  27. e.printStackTrace();
  28. }
  29. Product product = productService.findById(id);
  30. product.setProductName("访问的地址是:" + ip + ":" + port);
  31. return product;
  32. }
  33. }
  • 设置订单微服务的Tomcat的最大线程数是10:
  1. server:
  2. port: 9002 # 微服务的端口号
  3. tomcat:
  4. max-threads: 10 # 最大线程数是10
  5. spring:
  6. application:
  7. name: service-order # 微服务的名称
  8. datasource:
  9. url: jdbc:mysql://192.168.1.57:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
  10. driver-class-name: com.mysql.cj.jdbc.Driver
  11. username: root
  12. password: 123456
  13. jpa:
  14. generate-ddl: true
  15. show-sql: true
  16. open-in-view: true
  17. database: mysql
  18. jmx:
  19. unique-names: true
  20. # 配置Eureka
  21. eureka:
  22. instance:
  23. # 实例的名称
  24. instance-id: service-order:9002
  25. # 显示IP信息
  26. prefer-ip-address: true
  27. lease-renewal-interval-in-seconds: 5 # 发送心跳续约间隔(默认30秒)
  28. lease-expiration-duration-in-seconds: 10 # Eureka Client发送心跳给Eureka Server端后,续约到期时间(默认90秒)
  29. client:
  30. healthcheck:
  31. enabled: true
  32. service-url: # Eureka Server的地址
  33. # defaultZone: http://localhost:9000/eureka/
  34. defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
  35. # Ribbon的重试机制
  36. service-product:
  37. ribbon:
  38. # 修改ribbon的负载均衡策略 服务名 - ribbon - NFLoadBalancerRuleClassName :负载均衡策略
  39. # NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 修改ribbon的负载均衡策略为权重策略
  40. # Ribbon的重试机制参数
  41. ConnectTimeout: 250 # Ribbon的连接超时时间
  42. ReadTimeout: 1000 # Ribbon的数据读取超时时间
  43. OkToRetryOnAllOperations: true # 是否对所有操作都进行重试
  44. MaxAutoRetriesNextServer: 50 # 切换实例的重试次数
  45. MaxAutoRetries: 1 # 对当前实例的重试次数
  46. # 微服务info内容详细信息
  47. info:
  48. app.name: xxx
  49. company.name: xxx
  50. build.artifactId: $project.artifactId$
  51. build.version: $project.version$
  52. # 开启日志debug
  53. logging:
  54. level:
  55. root: info
  • 订单微服务中的SpringConfig.java
  1. package com.sunxiaping.order.config;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.web.client.RestTemplate;
  5. @Configuration
  6. public class SpringConfig {
  7. @Bean
  8. // @LoadBalanced
  9. public RestTemplate restTemplate() {
  10. return new RestTemplate();
  11. }
  12. }
  • 订单微服务的OrderController.java
  1. package com.sunxiaping.order.controller;
  2. import com.sunxiaping.order.domain.Product;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.web.bind.annotation.GetMapping;
  5. import org.springframework.web.bind.annotation.PathVariable;
  6. import org.springframework.web.bind.annotation.RequestMapping;
  7. import org.springframework.web.bind.annotation.RestController;
  8. import org.springframework.web.client.RestTemplate;
  9. @RestController
  10. @RequestMapping(value = "/order")
  11. public class OrderController {
  12. @Autowired
  13. private RestTemplate restTemplate;
  14. /**
  15. * @param id
  16. * @return
  17. */
  18. @GetMapping(value = "/buy/{id}")
  19. public Product buy(@PathVariable(value = "id") Long id) {
  20. Product product = restTemplate.getForObject("http://localhost:9001/product/findById/" + id, Product.class);
  21. return product;
  22. }
  23. @GetMapping(value = "/findOrder")
  24. public String findOrder() {
  25. return "商品查询到了";
  26. }
  27. }

2 使用Jmeter测试接口

使用Jmeter测试接口.jpg

3 系统负载过高存在的问题

3.1 问题分析

  • 在微服务架构中,我们将业务拆成一个个的服务,服务和服务之间可以相互调用,由于网络原因或者自身的原因,服务并不能保证100%可用,如果单个服务出现问题,调用这个服务就会出现网络延迟,此时如果有大量的网络请求涌入,会形成任务累计,导致服务瘫痪。
  • 换句话说,Tomcat等容器会以线程池的方式对所有的请求进行统一的管理,如果某个方法可能存着耗时问题,随着外面积压的请求越来越多,势必会造成系统的崩溃、瘫痪等。

Tomcat容器以线程数的方式管理所有线程.jpg

  • 为了不影响其他接口的正常访问:对多个服务之间进行隔离。
  • 服务隔离的方式:
    • 微服务架构的高并发问题 - 图3线程池隔离。

线程池隔离.jpg

  • 微服务架构的高并发问题 - 图5信号量隔离(计数器,就是对某个方法进行设置阈值,如果超过了阈值,直接报错)。

4 线程池隔离的方式处理积压问题

4.1 在订单微服务中引入相关jar包的Maven坐标

  1. <dependency>
  2. <groupId>com.netflix.hystrix</groupId>
  3. <artifactId>hystrix-metrics-event-stream</artifactId>
  4. <version>1.5.12</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>com.netflix.hystrix</groupId>
  8. <artifactId>hystrix-javanica</artifactId>
  9. <version>1.5.12</version>
  10. </dependency>

4.2 配置线程池

  • 配置HystrixCommand接口的实现类,在实现类中可以对线程池进行配置:
  1. package com.sunxiaping.order.command;
  2. import com.netflix.hystrix.*;
  3. import com.sunxiaping.order.domain.Product;
  4. import org.springframework.web.client.RestTemplate;
  5. public class OrderCommand extends HystrixCommand<Product> {
  6. private RestTemplate restTemplate;
  7. private Long id;
  8. public OrderCommand(RestTemplate restTemplate, Long id) {
  9. super(setter());
  10. this.restTemplate = restTemplate;
  11. this.id = id;
  12. }
  13. private static Setter setter() {
  14. // 服务分组
  15. HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("order_product");
  16. // 服务标识
  17. HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey("product");
  18. // 线程池名称
  19. HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("order_product_pool");
  20. /**
  21. * 线程池配置
  22. * withCoreSize : 线程池大小为10
  23. * withKeepAliveTimeMinutes: 线程存活时间15秒
  24. * withQueueSizeRejectionThreshold :队列等待的阈值为100,超过100执行拒绝策略
  25. */
  26. HystrixThreadPoolProperties.Setter threadPoolProperties = HystrixThreadPoolProperties.Setter().withCoreSize(50)
  27. .withKeepAliveTimeMinutes(15).withQueueSizeRejectionThreshold(100);
  28. // 命令属性配置Hystrix 开启超时
  29. HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter()
  30. // 采用线程池方式实现服务隔离
  31. .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD)
  32. // 禁止
  33. .withExecutionTimeoutEnabled(false);
  34. return Setter.withGroupKey(groupKey).andCommandKey(commandKey).andThreadPoolKey(threadPoolKey)
  35. .andThreadPoolPropertiesDefaults(threadPoolProperties).andCommandPropertiesDefaults(commandProperties);
  36. }
  37. @Override
  38. protected Product run() throws Exception {
  39. System.out.println(Thread.currentThread().getName());
  40. return restTemplate.getForObject("http://localhost:9001/product/findById/" + id, Product.class);
  41. }
  42. /**
  43. * 服务降级
  44. *
  45. * @return
  46. */
  47. @Override
  48. protected Product getFallback() {
  49. Product product = new Product();
  50. product.setProductName("不好意思,出错了");
  51. return product;
  52. }
  53. }

4.3 修改Controller

  1. package com.sunxiaping.order.controller;
  2. import com.sunxiaping.order.command.OrderCommand;
  3. import com.sunxiaping.order.domain.Product;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.web.bind.annotation.GetMapping;
  6. import org.springframework.web.bind.annotation.PathVariable;
  7. import org.springframework.web.bind.annotation.RequestMapping;
  8. import org.springframework.web.bind.annotation.RestController;
  9. import org.springframework.web.client.RestTemplate;
  10. @RestController
  11. @RequestMapping(value = "/order")
  12. public class OrderController {
  13. @Autowired
  14. private RestTemplate restTemplate;
  15. /**
  16. * 使用OrderCommand调用远程远程服务
  17. *
  18. * @param id
  19. * @return
  20. */
  21. @GetMapping(value = "/buy/{id}")
  22. public Product buy(@PathVariable(value = "id") Long id) {
  23. return new OrderCommand(restTemplate, id).execute();
  24. }
  25. @GetMapping(value = "/findOrder")
  26. public String findOrder() {
  27. System.out.println(Thread.currentThread().getName());
  28. return "商品查询到了";
  29. }
  30. }