微服务设计指导-实践springcloud+springboot+nacos+feign+hytrix

什么样的请求需要做成微服务

模块A→模块B。

而,模块B→请求模块C。

如果A和B本身又有其它的请求如:

模块D->模块A,模块E→模块B;

此时,对于:

  • 模块C,需要做成基于单根api的微服务provider;
  • 对于访问模块C的,需要做成微服务consumer/client;

忌讳的做法

  • 服务泛滥,例:把单表的增删改查都做成了微服务,到处调用;
  • 循环无止镜去调用微服务;
  • 什么都做成微服务;

需要记住的微服务开发规范

  • 单体的provider在1,000并发的情况下,必须在毫秒级(绝对不能超过1秒)完成请求;
  • 如果在固定的一个模块内己有服务101、服务102、服务103.而此时需要做一个服务104且服务104包括了(服务101、服务102、服务103),不得在consumer/client端直接组装(特别是还含有循环组装)己有微服务101、微服务102、微服务103. 而是在模块A内再包出一个微服务104(含服务101、102、103的业务逻辑)来给到consumer以避免微服务被”过度重用“;
  • 但是有些情况,微服务的循环调用是不可能避免的,此时遵守”线程数恒定且不能无限扩充、使用CPU时间片轮流转以总体时间可适当延长但是同一时刻微服务被调用数量永远恒定且控制在一个可调节阀值“的法则来做设计;
  • 记得对任何资源调用地方要及时释放;
  • 记得一轮循环必须要给到系统以喘吸时间,即:Thread.sleep(xxxx毫秒),在系统喘吸时间java会根据自身的jvm自行去释放被关闭了的资源(就算调了close,jvm也不会ASAP去释放。但不调close资源一定泄漏);

基于spring cloud的微服务技术栈

  • JDK_1.8_up079及以上;
  • eclipse Version: 2020-06 (4.16.0)+(强烈建议抛弃掉intellij吧,你会被沦为码家的,因为太方便了,要方便真的不要走编码路线);
  • spring boot2.0.x,每一个spring boot甚至是2.0.x后的x的不同都会导致你选择的spring cloud、熔断器、nacos版本的不同,我们这边假设用的spring boot是2.3.1;
  • spring cloud,Hoxton.SR7;
  • spring-cloud-alibaba,用来把spring boot和nacos连接起来的一个必要库,2.2.1.RELEASE;
  • nacos-discovery,服务自动注册发现,2.2.5.RELEASE;
  • hystrix,熔断器(包括服务降级/升级),1.2.3.RELEASE;
  • hystrix-dashboard,辅助类,1.2.3.RELEASE;
  • okhttp,spring-cloud的微服务底层用的是httpClient 4.x去实现的,性能不是最好,因此我们需要换成用okhttp去访问,4.0.0;

一个标准的微服务provider做法

maven pom.xml


    4.0.0
    
        org.sky.demo.timeout
        TimeoutServiceDemo
        0.0.1-SNAPSHOT
    
    TimeoutUser
    
        
            org.aspectj
            aspectjweaver
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
        
            org.springframework.boot
            spring-boot-configuration-processor
            true
        
        
            org.springframework.boot
            spring-boot-starter-log4j2
        
        
            commons-lang
            commons-lang
        
        
            org.springframework.boot
            spring-boot-starter-web
            
                
                    org.springframework.boot
                    spring-boot-starter-logging
                
                
                    org.slf4j
                    slf4j-log4j12
                
            
        
        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-nacos-discovery
                
        
    

application.yml内和微服务相关的配置

spring:
  application:
    name: TimeoutUser
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

把一个spring boot服务注册上nacos的代码

package org.sky.demo.timeout;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.ComponentScan;
 
@SpringBootApplication
@ComponentScan(basePackages = { "org.sky" })
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })
@EnableDiscoveryClient
public class TimeoutUserApp {
 
    public static void main(String[] args) {
        SpringApplication.run(TimeoutUserApp.class);
 
    }
 
}

就一个@EnableDiscoveryClient搞定了。

provider端controller的写法(标准spring boot controller)

package org.sky.demo.timeout.controller;
 
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
@RestController
@RequestMapping("demo")
public class TimeoutUserController {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
 
    @GetMapping(value = "/timeout/liveinfor")
    @ResponseBody
    public String getLiveInfor() {
        try {
            Thread.sleep(8000);
            return "code:0";
        } catch (Exception e) {
            logger.error(">>>>>>giveLiveInfor error: " + e.getMessage(), e);
            return "code: -1";
        }
 
    }
 
    @GetMapping(value = "/timeout/getUserInfo")
    @ResponseBody
    public String getUserInfo() {
        return "code: 0";
    }
}

好了,provider端搞定。

一个标准的微服务的consumer/client端做法

maven pom.xml


    4.0.0
    
        org.sky.demo.timeout
        TimeoutServiceDemo
        0.0.1-SNAPSHOT
    
    AdvancedIndex
    
        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-nacos-config
        
        
            commons-lang
            commons-lang
        
        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-nacos-discovery
        
        
            org.springframework.cloud
            spring-cloud-starter-openfeign
        
        
            org.springframework.cloud
            spring-cloud-starter-hystrix
        
        
            org.springframework.cloud
            spring-cloud-starter-hystrix-dashboard
        
        
            org.springframework.boot
            spring-boot-starter-log4j2
        
        
            org.springframework.boot
            spring-boot-starter-web
            
                
                    org.springframework.boot
                    spring-boot-starter-logging
                
                
                    org.slf4j
                    slf4j-log4j12
                
            
        
 
        
            com.squareup.okhttp3
            okhttp
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
            
                
                    org.ow2.asm
                    asm
                
            
        
        
            org.aspectj
            aspectjweaver
        
        
            com.google.guava
            guava
        
        
            com.alibaba
            fastjson
        
 
        
            org.sky.demo.timeout
            TimeoutUserFacade
        
    

此处的org.sky.demo.timeout.TmeoutUserFacade是微服务的feign+hystrix的熔断出错处理作为子jar包引入到consumer/client端用的后面会给出代码。

application.yml内和微服务相关的配置

为什么这大一陀,比网上的丰富多了(网上很多复制粘贴都是错的)

server:
  port: 9080
spring:
  application:
    name: AdvancedIndex
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 10MB
ribbon:
   eager-load:
      enabled: true
      clients: TimeoutUser
feign:
   okhttp:
      enabled: true
   httpclient:
      enabled: false
      max-connections: 1000
      max-connections-per-route: 100
   compression:
      request:
         mime-types: text/xml,application/xml,application/json
         min-request-size: 2048
         enabled: true
      response:
         enabled: true
   client:
      config:
         default:
            connect-timeout: 2000
            read-timeout: 2000
   hystrix:
      enabled: true
hystrix:
   threadpool:
      default:
         coreSize: 800 #每个微服务容器内可以支持的最大并发
         maximumSize: 2000
         maxQueueSize: -1
   command:
      default:
         execution:
            isolation:
               thread:
                  timeoutInMilliseconds: 30000

其中的:

ribbon: eager-load: enabled: true clients: TimeoutUser

处的clients指向provider的spring.application.name后所指的值。这是因为Ribbon进行客户端负载均衡的Client并不是在服务启动的时候就初始化好的,而是在调用的时候才会去创建相应的Client,所以第一次调用的耗时不仅仅包含发送HTTP请求的时间,还包含了创建RibbonClient的时间,这样一来如果创建时间速度较慢,同时设置的超时时间又比较短的话,很容易就会出现上面所描述的显现。这个问题和ESClient的第一次加载的梗是一样的。

书写“代理provider端“的proxy+熔断器

此处,我们把这个proxy+熔断器作为了一个工程的子jar包,引入到我们的consumer端主体工程内了。

feign+hystrix子jar包pom.xml


    4.0.0
    
        org.sky.demo.timeout
        TimeoutServiceDemo
        0.0.1-SNAPSHOT
    
    TimeoutUserFacade
    
        
            org.springframework.cloud
            spring-cloud-starter-openfeign
            provided
        
        
            org.springframework.boot
            spring-boot-starter-log4j2
        
        
            org.springframework.boot
            spring-boot-starter-web
            
                
                    org.springframework.boot
                    spring-boot-starter-logging
                
                
                    org.slf4j
                    slf4j-log4j12
                
            
        
    
     

子jar包内的客户端(application.yml配置跟着consumer主体包内的application.yml走)

package org.sky.demo.timeout.advancedindex.proxy;
 
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
 
@FeignClient(value = "TimeoutUser", fallbackFactory = TimeoutUserServiceProxyFallback.class)
public interface TimeoutUserServiceProxy {
    @GetMapping(value = "/demo/timeout/liveinfor")
    @ResponseBody
    public String getLiveInfor();
 
    @GetMapping(value = "/demo/timeout/getUserInfo")
    @ResponseBody
    public String getUserInfo();
}

子jar内的熔断器TimeoutUserServiceProxyFallback.class(application.yml配置跟着consumer主体包内的application.yml走)

package org.sky.demo.timeout.advancedindex.proxy;
 
import feign.hystrix.FallbackFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
 
@Component
public class TimeoutUserServiceProxyFallback implements FallbackFactory {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
 
    @Override
    public TimeoutUserServiceProxy create(Throwable throwable) {
        return new TimeoutUserServiceProxy() {
            @Override
            public String getLiveInfor() {
                StringBuffer msg = new StringBuffer();
                msg.append("code: -1");
                msg.append("服务报错导致服务降级: " + throwable.getMessage());
                logger.error(">>>>>>服务报错导致服务降级: " + throwable.getMessage(), throwable);
                return new String(msg.toString());
            }
 
            @Override
            public String getUserInfo() {
                StringBuffer msg = new StringBuffer();
                msg.append("code: -1");
                msg.append("服务报错导致服务降级: " + throwable.getMessage());
                logger.error(">>>>>>服务报错导致服务降级: " + throwable.getMessage(), throwable);
                return new String(msg.toString());
            }
        };
    }
 
}

consumer端controller的写法(标准spring boot controller)

package org.sky.demo.timeout.advancedindex.controller;
 
import javax.annotation.Resource;
 
import org.sky.demo.timeout.advancedindex.proxy.TimeoutUserServiceProxy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
@RequestMapping("demo")
public class IndexController {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
 
    @Resource
    private TimeoutUserServiceProxy timeoutUserServiceProxy;
 
    @GetMapping(value = "/timeout/advancedIndexWithLive")
    @ResponseBody
    public String indexWithLive() {
        try {
            logger.info(">>>>>>into indexWithLive");
            String liveResult = timeoutUserServiceProxy.getLiveInfor();
            String userInfoResult = timeoutUserServiceProxy.getUserInfo();
            logger.info(">>>>>>live room result->{} user info result->{}", liveResult, userInfoResult);
            return userInfoResult;
        } catch (Exception e) {
            logger.error(">>>>>>into AdvancedIndex error: " + e.getMessage(), e);
            return "code: -1";
        }
    }
 
    @GetMapping(value = "/timeout/advancedIndexNoLive")
    @ResponseBody
    public String indexNoLive() {
        try {
            logger.info(">>>>>>into indexNoLive");
            String userInfoResult = timeoutUserServiceProxy.getUserInfo();
            logger.info(">>>>>>user info result->{}", userInfoResult);
            return userInfoResult;
        } catch (Exception e) {
            logger.error(">>>>>>into AdvancedIndex error: " + e.getMessage(), e);
            return "code: -1";
        }
    }
}

consumer启动类的写法

package org.sky.demo.timeout.advancedindex;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
 
@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
public class AdvancedIndexApp {
 
    public static void main(String[] args) {
        SpringApplication.run(AdvancedIndexApp.class, args);
 
    }
}

此处有两个annotation:

  • @EnableFeignClients

  • @EnableDiscoveryClient

示例运行

把3个项目启动起来,然后通过http://localhost:9080/demo//timeout/advancedIndexWithLive记问。

然后你会看到因为该微服务连着的实际业务服务里有一个Thread.sleep(8000);而我们的微服务超时为:2秒。

因此你会看到AdvancedIndex工程内抛出一个熔断信息为“服务报错导致服务降级”的exception,进而打断服务。

你可能感兴趣的:(微服务,微服务,spring,cloud,spring,boot,nacos,feign)