Spring Cloud 搭建基礎綜合框架【實操】

敖奕_Nuage發表於2020-11-08
>> '<搭建說明>:'
> '使用的元件包括: Eureka、Ribbon、Config、Zuul、Hystrix 完成一個使用者資訊管理小服務。'
> '後端框架包括:註冊中心Eureka、配置中心 Config API閘道器Zuul、客戶端負載均衡Ribbon、斷路器Hystrix;'
> '同時後端包含兩個業務服務,一個是使用者服務sc-user-server,一個是資料服務sc-data-server'

技術框架圖

>> 技術方案實現流程圖間 圖-1
> 1'使用者從瀏覽器發起請求',經過瀏覽器,請求達到Nginx,
> 2)開啟前端介面,由前端發起請求後臺資料,
> 3)當請求達到Nginx後,'Nginx對閘道器層進行負載',因為閘道器也需要做'HA'(HA是什麼?底部有註腳定義),
> 4)此時閘道器收到請求後會根據請求路徑進行'動態路由',
> 5)根據'服務名發現' UserService中的服務,則從Ribbon中選擇一臺UsrService的例項進行呼叫
> 6)由UserServer返回資料,如果'此時UserService需要使用第三方DataService返回資料'
> 7)'則跟Zuul一樣,選擇一臺DataService的例項進行呼叫',返回資料到前臺即可渲染頁面
> 8)流程結束?

-HA(Highly Available)(雙機叢集(HA)系統簡稱),高可用性叢集,是保證業務連續性的有效解決方案,
-一般有兩個或兩個以上的節點,且分為活動節點及備用節點。

工程名描述
serverN/A父工程
config-server9090配置中心
eureka-server8761註冊中心
zuul-server7777API GateWay 閘道器
hystrix-dashboard9099儀表盤& Turbine(風機)聚合Hystrix 斷路器 -整合整個叢集下的監控狀態
commonN/A公共基礎包,方便後臺服務引用
user-server9091使用者服務,對使用者資料的操作
data-server8099API 資料服務,提供基礎的資料
<!-父工程-server->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>
    </dependencies>
> config-server |9090| 配置中心:
    <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
     </dependency>
server:
  port: 9090
spring:
  cloud:
    config:
      server:
        git:
          uri: #配置檔案遠端地址URL
          #username:
          #password:
          search-paths: SC-CONFIG
  application:
    name: sc-configserver
/*** @description :  配置中心 */
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);}}
> eureka-server | 8761| 註冊中心:
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
		</dependency>
server:
  port: 8761
spring:
  application:
    name: sc-eurekaserver
eureka:
  client:
    registerWithEureka: false
    fetchRegistry: false
/*** eureka server*/
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);}}
> zuul-server | 7777| API GateWay 閘道器:
>> <Zuul中的Fallback機制:>
> 在微服務應用本身發生問題後,Zuul提供類一個Fallback機制,可以在出現問題時候進行統一處理,
> 需要實現FallbackProvider介面,然後定義自己需要的錯誤碼和錯誤資訊即可。
> -: 實現FallbackProvider介面,加上相應的處理邏輯,在Zuul-Fallback.java 該類中。
	<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
		</dependency>
		<dependency>
			<groupId>cn.springcloud.book</groupId>
			 <artifactId>common</artifactId>
			 <version>${parent.version}</version>
        </dependency>
eureka:
  client:
    serviceUrl:
      defaultZone: http://${eureka.host:127.0.0.1}:${eureka.port:8761}/eureka/
  instance:
    prefer-ip-address: true
management:
  security:
    enabled: false
  endpoints:
    web:
      exposure:
        include: hystrix.stream
feign:
  hystrix:
    enabled: true
ribbon:
  ConnectTimeout: 6000
  ReadTimeout: 6000
  MaxAutoRetries: 0 #對第一次請求的服務的重試次數
  MaxAutoRetriesNextServer: 0 #要重試的下一個服務的最大數量(不包括第一個服務)
  OkToRetryOnAllOperations: false
zuul:
  ribbonIsolationStrategy: THREAD
  threadPool:
    useSeparateThreadPools: true
    threadPoolKeyPrefix: zuulgateway
  max:
    host:
      max-per-route-connections: 200
      max-total-connections: 500
  host:
    socket-timeout-millis: 5000
    connect-timeout-millis: 10000
hystrix:
  threadpool:
    default:
      coreSize: 20
      maximumSize: 50
      maxQueueSize: -1
      allowMaximumSizeToDivergeFromCoreSize: true
  command:
    default:
      execution:
        timeout:
          enabled: false
        isolation:
          thread:
            interruptOnTimeout: false
            timeoutInMilliseconds: 15000
spring:
  application:
    name: sc-zuul-server
server:
  port: 7777
> 從閘道器正確的訪問http://localhost:7777/sc-user/service/getContextUserId 這個地址,此時發現報錯:
> 這是自定義類一個異常,沒有傳使用者資訊,因為這裡在閘道器做了攔截,
> 如果請求頭裡沒有x-customs-user 則鑑權不通過,
> 程式碼如下:AuthFilter
/*** 鑑權filter*/
public class AuthFilter extends ZuulFilter {
	private static final Logger logger = LoggerFactory.getLogger(AuthFilter.class);
	@Override
	public boolean shouldFilter() {// 判斷是否需要進行處理return true;}
	@Override
	public Object run() {
		RequestContext rc = RequestContext.getCurrentContext();
		authUser(rc);return null;}
	@Override
	public String filterType() {return "pre";}
	@Override
	public int filterOrder() {return 0;}
	private static Map<String, String> httpRequestToMap(HttpServletRequest request) {
        Enumeration<String> headerNames = request.getHeaderNames();
        Map<String, String> headers = new HashMap<>();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            headers.put(headerName, request.getHeader(headerName));
        }return headers;}
	public static void authUser(RequestContext ctx) {
		HttpServletRequest request = ctx.getRequest();
		Map<String, String> header = httpRequestToMap(request);
		String userId = header.get(User.CONTEXT_KEY_USERID);
		if(StringUtils.isEmpty(userId)) {
			try {
				BaseException BaseException = new 
				BaseException(CommonError.AUTH_EMPTY_ERROR.getCode(),CommonError.
						 AUTH_EMPTY_ERROR.getCodeEn(),CommonError.
						 AUTH_EMPTY_ERROR.getMessage(),1L);
				BaseExceptionBody errorBody = new BaseExceptionBody(BaseException);
				ctx.setSendZuulResponse(false);
				ctx.setResponseStatusCode(401);
				ctx.setResponseBody(JSONObject.toJSON(errorBody).toString());
			} catch (Exception e) {logger.error("println message error",e);}
		}else {
			for (Map.Entry<String, String> entry : header.entrySet()) {
				ctx.addZuulRequestHeader(entry.getKey(), entry.getValue());}}}
@Component
public class ZuulFallback implements FallbackProvider{
	@Override
	public String getRoute() {
		return "*"; //可以配置指定的路由,值為serviceId,如sc-user-service}
	@Override
	public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
		 return new ClientHttpResponse() {
	            @Override
	            public HttpStatus getStatusCode() throws IOException {
	                return HttpStatus.INTERNAL_SERVER_ERROR;}
	            @Override
	            public String getStatusText() throws IOException {
	                return HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase();}
	            @Override
	            public void close() {}           
	            @Override
	            public InputStream getBody() throws IOException {
	            	  //定義自己的錯誤資訊
	                return new ByteArrayInputStream(("microservice error").getBytes()); }
	            @Override
	            public HttpHeaders getHeaders() {
	                HttpHeaders headers = new HttpHeaders();
	                headers.setContentType(MediaType.APPLICATION_JSON);
	                return headers; }
				@Override
				public int getRawStatusCode() throws IOException {
					// TODO Auto-generated method stub
					return HttpStatus.INTERNAL_SERVER_ERROR.value();}};}}
@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
@EnableCircuitBreaker
public class ZuulServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulServerApplication.class, args);}
	@Bean
	public AuthFilter preRequestFilter() {return new AuthFilter();}}
>hystrix-dashboard | 9099| 儀表盤& Turbine(風機)聚合Hystrix 斷路器 -整合整個叢集下的監控狀態:
	<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
		</dependency> 
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
		</dependency>
        <dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
		</dependency>
  		 <dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
		</dependency>
eureka:
  client:
    serviceUrl:
      defaultZone: http://${eureka.host:127.0.0.1}:${eureka.port:8761}/eureka/
  instance:
    prefer-ip-address: true
management:
  security:
    enabled: false
  endpoints:
    web:
      exposure:
        include: hystrix.stream
turbine:
  appConfig: sc-user-service,sc-zuul-service,sc-data-service
  clusterNameExpression: "'default'"
server:
  port: 9099
spring:
  cloud:
    config:
      label: master
      uri: http://localhost:9090
      name: config-info
      profile: dev
  application:
    name: sc-hystrix-dashboard
@SpringBootApplication
@EnableDiscoveryClient
@EnableTurbine
@EnableHystrixDashboard
public class HystrixDashboardTurbineApplication {
    public static void main(String[] args) {
        SpringApplication.run(HystrixDashboardTurbineApplication.class, args);}}
> common | N/A| 公共基礎包,方便後臺服務引用:
>> '公共包(物件,攔截器,工具類等)'
> 框架一般會有一些值物件,攔截器,分頁物件,許可權等這些基礎資料,
> 並且其它服務都是需要共有的能力的,所以會抽出這部分物件放入公共包裡,
> 供其它服務引用。建立common的工程.
> 如 user-service 和 data-service 都會用到它裡面一些元件和類。
> 該包裡存有/存放的類或東西可以有如下幾部分:
> 1)'使用者上下文物件傳遞'> 1-1: 如使用者物件,從Zuul閘道器一直到後面的微服務都需要使用者物件
> 1-2: 後面的微服務需要獲取到使用者ID後開展非一些業務操作,比如:
> 1-2-1:在 Zuul獲取到使用者資訊,存入Header頭,後臺服務進入方法前,'獲取到Header進行組裝User使用者物件'> 1-2-1:'後臺服務通過UserContextHolder獲取'> 1-2-2:在後臺服務之間相互呼叫時,增加攔截器,獲取當前使用者然後轉換成Header放入請求頭,
> 1-2-3:'呼叫服務攔截器後解析到Header放入上下文',服務通過UserContextHolder獲取。 
> 1-3:具體實現步驟:
> 1-3-1:一呢,在公共SDK裡定義使用者物件User。
> 1-3-2:二呢,增加三個攔截器。
> 1-3-3:FeignUserContextInterceptor類中,在使用Feign進行服務間呼叫時會攔截到請求,
>  並將使用者屬性放到Header裡,程式碼在類內。
>  RestTemplateUserContextInterceptor類,在使用使用RestTemplate進行服務之間呼叫時,會攔截到請求
>  並將使用者屬性放到Header裡,該類中有具體實現步驟。
>  UserContexttInterceptor類,進入controller控制器時,會攔截到請求,
>  從Header頭解析出使用者物件存入上下文中,方便服務裡使用,具體實現程式碼檢視該類。
>  1-3-4:三呢,增加UserContextHolder類。
>  1-3-5: Hystrix併發策略,檢視SpringCloudHystrixConcurrencyStrategy類具體實現。
>  此類使用了 'ThreadLocal 物件'儲存使用者資訊,由於線上執行緒池隔離的模式下,會導致前後執行緒傳遞物件丟失,
>  該類中,使用自定義併發策略 HystrixConcurrencyStrategy解決此問題,具體方法看該類的實現。
>  1-3-6:在配置類中註冊三個攔截器和併發策略,程式碼看CommonConfiguration.java 類。
		<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
		    <groupId>com.alibaba</groupId>
		    <artifactId>fastjson</artifactId>
		    <version>1.2.31</version>
		</dependency>
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  cn.springcloud.book.common.config.CommonConfiguration
/*** 使用者物件 **/
public class User implements Serializable {
	private static final long serialVersionUID = -4083327605430665846L;
	public final static String CONTEXT_KEY_USERID = "x-customs-user";
	/** * 使用者ID*/
	private String userId;
	private String userName;
	public String getUserName() {return userName;}
	public void setUserName(String userName) {this.userName = userName;}
	public String getUserId() {return userId;}
	public void setUserId(String userId) {this.userId = userId;}
	public User() {}
	public User(Map<String, String> headers) {
		userId = headers.get(CONTEXT_KEY_USERID);}
	/*** 將user物件轉換成為http物件頭* @return http頭鍵值對*/
	public Map<String, String> toHttpHeaders() {
		Map<String, String> headers = new HashMap<>();
		headers.put(CONTEXT_KEY_USERID,userId);
		return headers;}}
public class HttpConvertUtil {
    /*** convert the httpServletRequest headers to headers map
     * @param request* @return*/
    public static Map<String, String> httpRequestToMap(HttpServletRequest request) {
        Enumeration<String> headerNames = request.getHeaderNames();
        Map<String, String> headers = new HashMap<>();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            headers.put(headerName, request.getHeader(headerName));
        }return headers; }}
public class ExceptionUtil {
	/*** 異常列舉轉型別換為英文code
	 * @param error 異常列舉
	 * @return 大駝峰編碼*/
	public static String errorToCodeEN(Enum<?> error) {
		String errorName = error.name().toLowerCase();
		String[] sp = errorName.split("_");
		StringBuffer code = new StringBuffer();
		for (String s : sp) {
			code.append(StringUtils.capitalize(s));
		}return code.toString();}}
public class AuthUtil {
	public static boolean authUser(User user, HttpServletResponse respone) 
	throws Exception{
		if(StringUtils.isEmpty(user.getUserId())) {
			return false;}else {return true;}}}
> 觀察UserController類中的三個Controller層類提供介面,並且順序執行:
> eureka-server(伺服器)、zuul-server(伺服器) data-service(服務) user-service(服務)
> 成功後瀏覽器訪問http://localhost:9091/getContextUserId,
> 發現頁面空白,控制檯列印"the user is null,please access from gateway or chexk usr info."
> 說明攔截器起到作用,對於/沒有使用者資訊這樣不合法對請求進行了攔截。
> 程式碼:(UserContextInterceptor.java -定義的公共基礎包中) 類中所示。
public class UserContextInterceptor implements HandlerInterceptor {
	private static final Logger log = 
	LoggerFactory.getLogger(UserContextInterceptor.class);
	@Override
	public boolean preHandle(HttpServletRequest request, 
	HttpServletResponse respone, Object arg2) throws Exception {
		User user = new User(HttpConvertUtil.httpRequestToMap(request));
		if(StringUtils.isEmpty(user.getUserId()) && StringUtils.isEmpty(user.getUserName())) {
			log.error("the user is null, please access from gateway or check user info");
			return false;}
		UserContextHolder.set(user);return true;}
	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse respone, 
	Object arg2, ModelAndView arg3)
			throws Exception {// DOING NOTHING}
	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse 
	respone, Object arg2, Exception arg3)
			throws Exception { UserContextHolder.shutdown();}}
/**
 * RestTemplate傳遞使用者上下文*/
public class RestTemplateUserContextInterceptor implements ClientHttpRequestInterceptor {
	@Override
	public ClientHttpResponse intercept(HttpRequest request, byte[] body, 
	ClientHttpRequestExecution execution)
			throws IOException {
		User user = UserContextHolder.currentUser();
		Map<String, String> headers = user.toHttpHeaders();
		for (Map.Entry<String, String> header : headers.entrySet()) {
			request.getHeaders().add(header.getKey(), header.getValue());}
		// 呼叫
		return execution.execute(request, body);}}
/***  Feign傳遞使用者上下文 */
public class FeignUserContextInterceptor implements RequestInterceptor {
	@Override
	public void apply(RequestTemplate template) {
//		User user = UserContextHolder.currentUser();
		ServletRequestAttributes attributes = (ServletRequestAttributes) 
		RequestContextHolder
                .getRequestAttributes();
		HttpServletRequest request = attributes.getRequest();
        Enumeration<String> headerNames = request.getHeaderNames();
        if (headerNames != null) {
            while (headerNames.hasMoreElements()) {
                String name = headerNames.nextElement();
                String values = request.getHeader(name);
                template.header(name, values);}}}}
/** * 通用異常資訊 */
public enum CommonError {
	/*** 1001, "使用者資訊為空" */
	AUTH_EMPTY_ERROR(10001, "the user is null, please check");
	private Integer code;
	private String message;
	CommonError(Integer code, String message) {
		this.code = code;this.message = message;}
	public Integer getCode() {return code;}
	public void setCode(Integer code) {
		this.code = code;}
	public String getMessage() {return message;}
	public void setMessage(String message) {this.message = message;}
	public String getCodeEn() {return ExceptionUtil.errorToCodeEN(this);}}
public class BaseExceptionBody implements Serializable {
	/*** serialVersionUID*/
	private static final long serialVersionUID = -1270478894426234738L;
	/*** 相關業務ID */
	private Long businessId;
	/*** 異常編碼:數字*/
	private Integer code;
	/*** 異常編碼:英文短語*/
	private String codeEN;
	/*** 異常資訊*/
	private String businessMessage;
	/** * 異常型別*/
	private String exceptionType;
		public BaseExceptionBody(BaseException exception) {
		this.businessId = exception.getBusinessId();
		this.code = exception.getCode();
		this.codeEN = exception.getCodeEN();
		this.businessMessage = exception.getMessage();
		this.exceptionType = exception.getClass().getName();}}
public class BaseException extends RuntimeException {
	private static final long serialVersionUID = 1796731834836398434L;
	private Long businessId;
	private Integer code;
	private String codeEN;
	private String businessMessage;
		/** * 基礎異常*  * @param code 異常編碼:數字 
	 * @param codeEN 異常編碼:英文短語 * @param message 異常資訊
	 * @param businessId 相關業務ID*/
	public BaseException(Integer code, String codeEN, String message, Long businessId) 
	{ this(code, codeEN, message, businessId, null);}
	public BaseException(Integer code, String codeEN, String message, 
	Long businessId, Throwable t) {
		super(message, t);
		this.businessId = businessId;
		this.code = code;
		this.codeEN = codeEN;
		this.businessMessage = message;}}
/*** 使用者上下文*/
public class UserContextHolder {
	public static ThreadLocal<User> context = new ThreadLocal<User>();
	public static User currentUser() {return context.get();}
	public static void set(User user) {context.set(user);}
	public static void shutdown() {context.remove();}}
/*** Spring上下文管理工具 */
@Component
public class SpringContextManager implements ApplicationContextAware {
	private static ApplicationContext applicationContext;
	public void setApplicationContext(ApplicationContext applicationContext) 
	throws BeansException {
		SpringContextManager.applicationContext = applicationContext;}
	/*** 獲取上下文* * @return Spring上下文 */
	public static ApplicationContext getApplicationContext() {
		return applicationContext;}
	/** * 獲取Spring配置 * @param key 配置名稱 * @return 配置值*/
	public static String getProperties(String key) {
		return applicationContext.getEnvironment().getProperty(key);}
	/** * 獲取Spring配置<br> * 沒有配置時,返回預設值* @param key 配置名稱
	 * @param defaultValue 預設值 * @return 配置值*/
	public static String getProperties(String key, String defaultValue) {
		return applicationContext.getEnvironment().getProperty(key, defaultValue);}}
public class SpringCloudHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {
    private HystrixConcurrencyStrategy delegateHystrixConcurrencyStrategy;
    @Override
    public <T> Callable<T> wrapCallable(Callable<T> callable) {
        return new HystrixThreadCallable<>(callable, 
       RequestContextHolder.getRequestAttributes(),
       HystrixThreadLocal.threadLocal.get()); }
    public SpringCloudHystrixConcurrencyStrategy() {init();}
    private void init() {
   	 try {
            this.delegateHystrixConcurrencyStrategy = 
            HystrixPlugins.getInstance().getConcurrencyStrategy();
            if (this.delegateHystrixConcurrencyStrategy instanceof 
            SpringCloudHystrixConcurrencyStrategy) {  return;}
            HystrixCommandExecutionHook commandExecutionHook = 
            HystrixPlugins.getInstance().getCommandExecutionHook();
            HystrixEventNotifier eventNotifier = 
            HystrixPlugins.getInstance().getEventNotifier();
            HystrixMetricsPublisher metricsPublisher = 
            HystrixPlugins.getInstance().getMetricsPublisher();
            HystrixPropertiesStrategy propertiesStrategy = 
            HystrixPlugins.getInstance().getPropertiesStrategy();
            HystrixPlugins.reset();
            HystrixPlugins.getInstance().registerConcurrencyStrategy(this);
HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook);
            HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);
            HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);
            HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);}
        catch (Exception e) {throw e;}}
    @Override
	public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
			HystrixProperty<Integer> corePoolSize,
			HystrixProperty<Integer> maximumPoolSize,
			HystrixProperty<Integer> keepAliveTime, TimeUnit unit,
			BlockingQueue<Runnable> workQueue) {
		return this.delegateHystrixConcurrencyStrategy.getThreadPool(
		threadPoolKey, corePoolSize, maximumPoolSize,
				keepAliveTime, unit, workQueue);}
	@Override
	public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
			HystrixThreadPoolProperties threadPoolProperties) {
		return this.delegateHystrixConcurrencyStrategy.getThreadPool(
		threadPoolKey, threadPoolProperties);	}
	@Override
	public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) {
		return this.delegateHystrixConcurrencyStrategy.getBlockingQueue(maxQueueSize);}
	@Override
	public <T> HystrixRequestVariable<T> getRequestVariable(
			HystrixRequestVariableLifecycle<T> rv) {
		return this.delegateHystrixConcurrencyStrategy.getRequestVariable(rv);}}
public class HystrixThreadLocal {
    public static ThreadLocal<String> threadLocal = new ThreadLocal<>();}
public class HystrixThreadCallable<S> implements Callable<S>{
	 private final RequestAttributes requestAttributes;  
	 private final Callable<S> delegate;
	 private String params;
     public HystrixThreadCallable(Callable<S> callable, RequestAttributes 
     requestAttributes,String params) {  
         this.delegate = callable; 
         this.requestAttributes = requestAttributes;  
         this.params = params; }
     @Override  
     public S call() throws Exception {  
         try { RequestContextHolder.setRequestAttributes(requestAttributes);
             HystrixThreadLocal.threadLocal.set(params);
             return delegate.call();  } finally {
             RequestContextHolder.resetRequestAttributes();
             HystrixThreadLocal.threadLocal.remove();}  }  }
@Configuration
@EnableWebMvc
public class CommonConfiguration extends WebMvcConfigurerAdapter{
	/*** 請求攔截器*/
	@Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new UserContextInterceptor());}
    /*** 建立Feign請求攔截器,在傳送請求前設定認證的使用者上下文資訊*/
    @Bean
    @ConditionalOnClass(Feign.class)
    public FeignUserContextInterceptor feignTokenInterceptor() {
        return new FeignUserContextInterceptor();}
    /*** RestTemplate攔截器 * @return */
    @LoadBalanced
    @Bean
    public RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.getInterceptors().add(new RestTemplateUserContextInterceptor());
        return restTemplate;}    
    @Bean
	public SpringCloudHystrixConcurrencyStrategy springCloudHystrixConcurrencyStrategy() 
	{return new SpringCloudHystrixConcurrencyStrategy();}}
> user-server | 9091| 使用者服務,對使用者資料的操作:
	<dependency>
			<groupId>cn.springcloud.book</groupId>
			 <artifactId>common</artifactId>
			 <version>${parent.version}</version>
        </dependency>
        <dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
		</dependency>
         <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-client</artifactId>
        </dependency>
eureka:
  client:
    serviceUrl:
      defaultZone: http://${eureka.host:127.0.0.1}:${eureka.port:8761}/eureka/
  instance:
    prefer-ip-address: true
management:
  security:
    enabled: false
  endpoints:
    web:
      exposure:
        include: hystrix.stream
feign:
  hystrix:
    enabled: true
ribbon:
  ConnectTimeout: 6000
  ReadTimeout: 6000
  MaxAutoRetries: 0
  MaxAutoRetriesNextServer: 0
hystrix:
  command:
    default:
      execution:
        timeout:
        isolation:
          thread:
            timeoutInMilliseconds: 15000
server:
  port: 9091
spring:
  cloud:
    config:
      label: master
      uri: http://localhost:9090
      name: config-info
      profile: dev
  application:
    name: sc-user-service
public interface IUserService {
    public String getDefaultUser();
    public String getContextUserId();
    public List<String> getProviderData();}
@Component
public class UserService implements IUserService{
    @Autowired
    private DataService dataService;
    @Autowired
    private RestTemplate restTemplate;
	@Override
	public String getDefaultUser() {return dataService.getDefaultUser();}
	@Override
	public String getContextUserId() {return dataService.getContextUserId();}
	@Override
	public List<String> getProviderData() {
		List<String> result = restTemplate.getForObject
		("http://sc-data-service/getProviderData", List.class);
		return result;}}
@Component
public class UserClientFallback implements DataService{
	@Override
	public String getDefaultUser() {return new String("get getDefaultUser failed");}
	@Override
	public String getContextUserId() {return new String("get getContextUserId failed");}}
/*** feign呼叫資料服務*/
@FeignClient(name = "sc-data-service", fallback=UserClientFallback.class)
public interface DataService {
	@RequestMapping(value = "/getDefaultUser", method = RequestMethod.GET)
    public String getDefaultUser();
    @RequestMapping(value = "/getContextUserId", method = RequestMethod.GET)
    public String getContextUserId();}
@RestController
public class UserController {
	@Autowired
	private IUserService userService;
	/*** 獲取配置檔案中系統預設使用者* @return*/
    @GetMapping("/getDefaultUser")
    public String getDefaultUser(){
        return userService.getDefaultUser();}
    /*** 獲取上下文使用者* @return */
    @GetMapping("/getContextUserId")
    public String getContextUserId(){
        return userService.getContextUserId();}
    /*** 獲取供應商資料*/
    @GetMapping("/getProviderData")
    public List<String> getProviderData(){
        return userService.getProviderData();}}

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableCircuitBreaker
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args); }}
> data-server | 8099| API 資料服務,提供基礎的資料:
<dependency>
			<groupId>cn.springcloud.book</groupId>
			 <artifactId>common</artifactId>
			 <version>${parent.version}</version>
        </dependency>
        <dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
		</dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
spring:
  application:
    name: sc-data-service
eureka:
  client:
    serviceUrl:
      defaultZone: http://${eureka.host:127.0.0.1}:${eureka.port:8761}/eureka/
  instance:
    prefer-ip-address: true
management:
  security:
    enabled: false
  endpoints:
    web:
      exposure:
        include: hystrix.stream

server:
  port: 8099
  
spring:
  cloud:
    config:
      label: master
      uri: http://localhost:9090
      name: config-info
      profile: dev
  application:
    name: sc-data-service
/*** @description : 配置資訊*/
@Component
@ConfigurationProperties(prefix = "遠端服務路徑")
public class DataConfig {
    private String defaultUser;
	public String getDefaultUser() {return defaultUser;}
	public void setDefaultUser(String defaultUser) {
		this.defaultUser = defaultUser;}}
@RestController
public class DataController {
   	@Autowired
	private DataConfig dataConfig;
    @GetMapping("/getContextUserId")
    public String getContextUserId(){
        return UserContextHolder.currentUser().getUserId();}
    @GetMapping("/getDefaultUser")
    public String getDefaultUser(){
        return dataConfig.getDefaultUser();}
    @GetMapping("/getProviderData")
    public List<String> getProviderData(){
    	List<String> provider = new ArrayList<String>();
    	provider.add("Beijing Company");
    	provider.add("Shanghai Company");
    	provider.add("Shenzhen Company");
        return provider;}}
/*** 資料服務* */
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class DataServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(DataServiceApplication.class, args);}}

=====================================================================================================

最後,給點進來的’碼’同志,提供一份參考配置生產環境,簡單模版供大家參考:

1) Eureka 服務端配置參考:

server:
	port: 8761
spring:
	appliction:
	name: eurcka-server
eureka:
	client:
	serviceUrl:
	defaultZone: http://127.0.0.1:8761/eureka, http://127.0.0.1:8761/eureka
	instance:
	prefer-ip-address: true
	server: enable-self-preser
	vation: false
	eviction-interval-timer-in-ms: 30000

Eureka屬性描述表格服務端:
Eureka屬性描述表格

2) Eureka 客戶端配置參考:

eureka:
	client:
	serviceUrl:
	defaultZone: http://127.0.0.1:8761/eureka, http://127.0.0.2:eureka/
	instance:
	prefer-ip-address: true

3) Ribbon 配置參考:

Ribbon的配置,一般都配置全域性的,也可以配置為單個服務的,這裡列了個全域性的。
–對於是否重試,在真實專案中觀察,由於冪等性或網路不穩定等原因導致易出問題,預設都不進行重試,
如果對於一些查詢比較多的服務可以開啟重試,根據具體專案來定義,
具體的配置可以看原始碼的 DefaultClientConfigImpl類.

ribbon:
	ConnectTimeout: 2000 #全域性請求連線的超時時間,預設2秒
	ReadTimeout: 5000 #全域性請求的超時時間,預設5秒
	MaxAutoRetries: 0 #對當前例項的重試次數
	MaxAutoRetriesNextServer: 0 #切換下一個例項重試次數
	OkToRetryOnAllOperations: false #對所有操作請求都進行重試

4) Hystrix 配置參考:

Hystrix斷路器的配置,原始碼類 HystrixCommandProperties
通常全域性請求連線超時時間推薦設定為10秒,如果請求需要的時間過長則根據你的請求進行修改或
單獨時間,如果要設定執行緒池大小,可以看閘道器的配置如下:
hystrix.command.default.exection.isonlation.thread.timeoutlnMilliseconds

hystreix:
	command:
	default:
	execution:
	isolation:
	thread:
	timeoutlnMilliseconds: 10000 #全域性請求連線的超時時間預設為1秒

5) Zuul 閘道器 配置參考:

zuul:
	ribbonlsolationStrategy: THREAD
	threadPool: 
	userSeparateThreadPools: true
	threadPoolKeyPrefix: zuulgateway
host:
	max-per-route-connections: 50
	max-total-connections: 300
	socket-timeout-millis: 5000
	connect-timeout-millis: 5000
	hystrix:
		threadpool:
		default:
		coreSize: 20
		maximunmSize: 50
		allowMaximumSizeToDivergeFromCoreSize: true
		command:
		default:
		execution:
		isolation:
		thread:
			timeoutlnMilliseconds: 10000

Zuul的屬性描述表:
Zuul的屬性描述表

相關文章