springcloud遠程服務調用


Feign

OpenFeign是Netflix 開發的聲明式、模板化的HTTP請求客戶端。可以更加便捷、優雅地調用http api。

OpenFeign會根據帶有注解的函數信息構建出網絡請求的模板,在發送網絡請求之前,OpenFeign會將函數的參數值設置到這些請求模板中。

feign主要是構建微服務消費端。只要使用OpenFeign提供的注解修飾定義網絡請求的接口類,就可以使用該接口的實例發送RESTful的網絡請求。還可以集成Ribbon和Hystrix,提供負載均衡和斷路器。

英文表意為“假裝,偽裝,變形”, 是一個 Http 請求調用的輕量級框架,可以以 Java 接口注解的方式調用 Http 請求,而不用像 Java 中通過封裝 HTTP 請求報文的方式直接調用。通過處理注解,將請求模板化,當實際調用的時候,傳入參數,根據參數再應用到請求上,進而轉化成真正的請求,這種請求相對而言比較直觀。Feign 封裝 了HTTP 調用流程,面向接口編程,回想第一節課的SOP。

Feign和OpenFeign的關系

Feign本身不支持Spring MVC的注解,它有一套自己的注解

OpenFeign是Spring Cloud 在Feign的基礎上支持了Spring MVC的注解,如@RequesMapping等等。
OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,
並通過動態代理的方式產生實現類,實現類中做負載均衡並調用其他服務。

聲明式服務調用

provider方提供公用API包,Feign通過SpringMVC的注解來加載URI

1.創建項目User-Provider

選擇依賴

2.創建項目User-API

依賴 spring-boot-starter-web

創建一個接口 RegisterApi

package com.mashibing.UserAPI;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * 用戶操作相關接口
 * @author 
 *
 */
@RequestMapping("/User")
public interface RegisterApi {

	@GetMapping("/isAlive")
	public String isAlive();
}

3.User-Provider 實現API

配置文件

eureka.client.service-url.defaultZone=http://euk1.com:7001/eureka/

server.port=81

spring.application.name=user-provider

引入API

1.maven install User-Api項目

2.User-Provider的Pom.xml添加依賴

      <dependency>
	<groupId>com.mashibing.User-API</groupId>
	<artifactId>User-API</artifactId>
	<version>0.0.1-SNAPSHOT</version>
      </dependency>

創建UserController

實現Api的接口

package com.mashibing.UserProvider;

import com.mashibing.UserAPI.RegisterApi;
@RestController
public class UserController implements RegisterApi {

	@Override
	public String isAlive() {
		// TODO Auto-generated method stub
		return "ok";
	}

}

4.Consumer調用

創建項目User-Consumer

依賴

引入API

Pom.xml添加依賴

		<dependency>
			<groupId>com.mashibing.User-API</groupId>
			<artifactId>User-API</artifactId>
			<version>0.0.1-SNAPSHOT</version>
		</dependency>

配置文件

eureka.client.service-url.defaultZone=http://euk1.com:7001/eureka/

server.port=90

spring.application.name=consumer

創建Service接口

package com.mashibing.UserConsumer;

import org.springframework.cloud.openfeign.FeignClient;

import com.mashibing.UserAPI.RegisterApi;

@FeignClient(name = "user-provider")
public interface UserConsumerService extends RegisterApi {

}

創建Controller

package com.mashibing.UserConsumer;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ConsumerController {

	@Autowired
	UserConsumerService consumerSrv;
	
	@GetMapping("/alive")
	public String alive() {
		
		return consumerSrv.isAlive();
	}
	
}

修改啟動類

package com.mashibing.UserConsumer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients
public class UserConsumerApplication {

	public static void main(String[] args) {
		SpringApplication.run(UserConsumerApplication.class, args);
	}

}

5.測試

訪問 http://localhost:90/alive 即可完成聲明式遠程服務調用

Get和Post

Feign默認所有帶參數的請求都是Post,想要使用指定的提交方式需引入依賴

        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-httpclient</artifactId>
        </dependency>

並指明提交方式

@RequestMapping(value = "/alived", method = RequestMethod.POST)
@GetMapping("/findById")

帶參請求

	@GetMapping("/findById")
	public Map findById(@RequestParam("id") Integer id);
	
	@PostMapping("/register")
	public Map<String, String> reg(@RequestBody User user);

權限

feign的默認配置類是:org.springframework.cloud.openfeign.FeignClientsConfiguration。默認定義了feign使用的編碼器,解碼器等。

允許使用@FeignClient的configuration的屬性自定義Feign配置。自定義的配置優先級高於上面的FeignClientsConfiguration。

通過權限的例子,學習feign的自定義配置。

服務提供者。上述例子開放service-valuation的權限 后,訪問。

開放權限:
<!-- 安全認證 -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		// 關閉csrf
		http.csrf().disable();
		// 表示所有的訪問都必須認證,認證處理后才可以正常進行
		http.httpBasic().and().authorizeRequests().anyRequest().fullyAuthenticated();
		// 所有的rest服務一定要設置為無狀態,以提升操作效率和性能
		http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
	}
}
		
spring: 
  security: 
    user: 
      name: root
      password: root
      
      

繼續feign原來訪問,報錯。401。

有如下兩種方式:

  1. 自定義配置類。
  2. 增加攔截器。

自定義配置

配置類:
public class FeignAuthConfiguration {
	
	@Bean
	public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
		return new BasicAuthRequestInterceptor("root", "root");
	}
}

在feign上加配置
@FeignClient(name = "service-valuation",configuration = FeignAuthConfiguration.class)


OK,可以正常訪問了。

小結:如果在配置類上添加了@Configuration注解,並且該類在@ComponentScan所掃描的包中,那么該類中的配置信息就會被所有的@FeignClient共享。最佳實踐是:不指定@Configuration注解(或者指定configuration,用注解忽略),而是手動:

@FeignClient(name = "service-valuation",configuration = FeignAuthConfiguration.class)

攔截器

import feign.RequestInterceptor;
import feign.RequestTemplate;

public class MyBasicAuthRequestInterceptor implements RequestInterceptor {

	@Override
	public void apply(RequestTemplate template) {
		// TODO Auto-generated method stub
		template.header("Authorization", "Basic cm9vdDpyb290");
	}
}

feign:
  client: 
    config:  
      service-valuation: 
        
        request-interceptors:
        - com.online.taxi.passenger.feign.interceptor.MyBasicAuthRequestInterceptor

代碼中取消上面的配置,訪問,報401.用下面的方式。

屬性定義

  1. 接上面例子,此例子和上面例子實現的功能一樣。記得兩者取一個即可。說明用屬性而不是用屬性中的configuration。
定義攔截器
public class MyBasicAuthRequestInterceptor implements RequestInterceptor {

	@Override
	public void apply(RequestTemplate template) {
		// TODO Auto-generated method stub
		template.header("Authorization", "Basic cm9vdDpyb290");
	}
}

配置文件
feign:
  client: 
    config:  
      service-valuation: 
        request-interceptors:
        - com.online.taxi.passenger.feign.interceptor.MyBasicAuthRequestInterceptor
        

再次訪問,測試Ok。

  1. 擴展

指定服務名稱配置:

   feign:
     client: 
       config:  
         service-valuation: 
           connect-timeout: 5000
           read-timeout: 5000
           logger-level: full
           

通用配置

   feign:
     client: 
       config:  
         default: 
           connect-timeout: 5000
           read-timeout: 5000
           logger-level: full

屬性配置比Java代碼優先級高。也可通過配置設置java代碼優先級高。

feign:
	client: 
		default-to-properties: false

feign在方法上可以設置:@RequestMapping,@ResponseBody。

方法中的參數可以設置:@RequestBody等等,Spring MVC中的注解。

推薦使用yml配置方式,在yml中按 代碼提示鍵,可以看到所有配置。

原理

  1. 主程序入口添加@EnableFeignClients注解開啟對Feign Client掃描加載處理。根據Feign Client的開發規范,定義接口並加@FeignClient注解。
  2. 當程序啟動時,會進行包掃描,掃描所有@FeignClient注解的類,並將這些信息注入Spring IoC容器中。當定義的Feign接口中的方法被調用時,通過JDK的代理方式,來生成具體的RequestTemplate。當生成代理時,Feign會為每個接口方法創建一個RequestTemplate對象,該對象封裝了HTTP請求需要的全部信息,如請求參數名、請求方法等信息都在這個過程中確定。
  3. 然后由RequestTemplate生成Request,然后把這個Request交給client處理,這里指的Client可以是JDK原生的URLConnection、Apache的Http Client,也可以是Okhttp。最后Client被封裝到LoadBalanceClient類,這個類結合Ribbon負載均衡發起服務之間的調用。

壓縮

服務端provider配置

#服務端開啟壓縮
server.compression.enabled=true

調用方consumer配置

#配置請求GZIP壓縮
feign.compression.request.enabled=true
#配置響應GZIP壓縮
feign.compression.response.enabled=true
#單位是B
feign.compression.request.min-request-size=100

請求

API

@FeignClient(name = "user-provider")
public interface ConsumerApi extends UserApi {

	@GetMapping("/getMap")
	Map<Integer, String> getMap(@RequestParam("id") Integer id);
	@GetMapping("/getMap2")
	Map<Integer, String> getMap2(@RequestParam("id") Integer id,@RequestParam("name") String name);
	
	@GetMapping("/getMap3")
	Map<Integer, String> getMap3(@RequestParam Map<String, Object> map);
	
	@PostMapping("/postMap")
	Map<Integer, String> postMap(Map<String, Object> map);

}

Controller

package com.mashibing.UserConsumer;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.mashibing.UserAPI.UserApi;

@RestController
public class MainController {

	@Autowired
	ConsumerApi api;
	
	@Autowired
	MashibingApi mapi;

	@GetMapping("/alive")
	public String alive() {
		/**
		 * URL 不能變 
		 * jar文檔
		 */
		return api.alive();
	}
	
	
	@GetMapping("/vip")
	public String vip() {
		
		return mapi.getVip();
	}
	
	@GetMapping("/map")
	public Map<Integer, String> map(Integer id) {
		System.out.println(id);
		return api.getMap(id);
	}
	
	@GetMapping("/map2")
	public Map<Integer, String> map2(Integer id,String name) {
		System.out.println(id);
		return api.getMap2(id,name);
	}
	
	
	@GetMapping("/map3")
	public Map<Integer, String> map3(@RequestParam Map<String, Object> map) {
//		System.out.println(id);
//		HashMap<String, Object> map = new HashMap<>(2);
//		
//		map.put("id", id);
//		map.put("name", name);
//		syso
		System.out.println(map);
		return api.getMap3(map);
	}
	
	
	@GetMapping("/map4")
	public Map<Integer, String> map4(@RequestParam Map<String, Object> map) {
//		System.out.println(id);
//		HashMap<String, Object> map = new HashMap<>(2);
//		
//		map.put("id", id);
//		map.put("name", name);
//		syso
		System.out.println(map);
		return api.postMap(map);
	}
}

Provider

package com.mashibing.UserProvider;

import java.util.Collections;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.mashibing.UserAPI.UserApi;

@RestController
public class UserController implements UserApi {

	@Value("${server.port}")
	String port;
	
	
	private AtomicInteger count = new AtomicInteger();
	
	@Override
	public String alive() {

		try {
			System.out.println("准備睡");
			
			Thread.sleep(500);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		int i = count.getAndIncrement();
		System.out.println("====好的第:" + i + "次調用");
		return "port:" + port;
	}

	@Override
	public String getById(Integer id) {
		// TODO Auto-generated method stub
		return null;
	}
	
	@GetMapping("/getMap")
	public Map<Integer, String> getMap(@RequestParam("id") Integer id) {
		// TODO Auto-generated method stub
		System.out.println(id);
		return Collections.singletonMap(id, "mmeme");
	}
	@GetMapping("/getMap2")
	public Map<Integer, String> getMap2(Integer id,String name) {
		// TODO Auto-generated method stub
		System.out.println(id);
		return Collections.singletonMap(id, name);
	}
	
	@GetMapping("/getMap3")
	public Map<Integer, String> getMap3(@RequestParam Map<String, Object> map) {
		// TODO Auto-generated method stub
		System.out.println(map);
		return Collections.singletonMap(Integer.parseInt(map.get("id").toString()), map.get("name").toString());
	}
	
	
	@PostMapping("/postMap")
	public Map<Integer, String> postMap(@RequestBody Map<String, Object> map) {
		// TODO Auto-generated method stub
		System.out.println(map);
		return Collections.singletonMap(Integer.parseInt(map.get("id").toString()), map.get("name").toString());
	}

	

}

開啟日志

配置文件

logging.level.com.mashibing.UserConsumer:debug

重寫日志等級

package com.mashibing.UserConsumer;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import feign.Logger;

@Configuration
public class FeiginConfig {

	@Bean
	Logger.Level logLevel(){
		
		return Logger.Level.BASIC;
	}
}

超時

Feign默認支持Ribbon;Ribbon的重試機制和Feign的重試機制有沖突,所以源碼中默認關閉Feign的重試機制,使用Ribbon的重試機制

#連接超時時間(ms)
ribbon.ConnectTimeout=1000
#業務邏輯超時時間(ms)
ribbon.ReadTimeout=6000

重試

#同一台實例最大重試次數,不包括首次調用
ribbon.MaxAutoRetries=1
#重試負載均衡其他的實例最大重試次數,不包括首次調用
ribbon.MaxAutoRetriesNextServer=1
#是否所有操作都重試
ribbon.OkToRetryOnAllOperations=false

使用ribbon重試機制,請求失敗后,每個6秒會重新嘗試

Hystrix

spring cloud 用的是 hystrix,是一個容錯組件。

Hystrix實現了 超時機制和斷路器模式。

Hystrix是Netflix開源的一個類庫,用於隔離遠程系統、服務或者第三方庫,防止級聯失敗,從而提升系統的可用性與容錯性。主要有以下幾點功能:

  1. 為系統提供保護機制。在依賴的服務出現高延遲或失敗時,為系統提供保護和控制。
  2. 防止雪崩。
  3. 包裹請求:使用HystrixCommand(或HystrixObservableCommand)包裹對依賴的調用邏輯,每個命令在獨立線程中運行。
  4. 跳閘機制:當某服務失敗率達到一定的閾值時,Hystrix可以自動跳閘,停止請求該服務一段時間。
  5. 資源隔離:Hystrix為每個請求都的依賴都維護了一個小型線程池,如果該線程池已滿,發往該依賴的請求就被立即拒絕,而不是排隊等候,從而加速失敗判定。防止級聯失敗。
  6. 快速失敗:Fail Fast。同時能快速恢復。側重點是:(不去真正的請求服務,發生異常再返回),而是直接失敗。
  7. 監控:Hystrix可以實時監控運行指標和配置的變化,提供近實時的監控、報警、運維控制。
  8. 回退機制:fallback,當請求失敗、超時、被拒絕,或當斷路器被打開時,執行回退邏輯。回退邏輯我們自定義,提供優雅的服務降級。
  9. 自我修復:斷路器打開一段時間后,會自動進入“半開”狀態,可以進行打開,關閉,半開狀態的轉換。前面有介紹。

hystrix獨立使用脫離spring cloud

package com.mashibing.UserConsumer;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;

public class HystrixTest extends HystrixCommand {

	protected HystrixTest(HystrixCommandGroupKey group) {
		super(group);
		// TODO Auto-generated constructor stub
	}

	public static void main(String[] args) {

		
	//	HystrixTest hystrixTest = new HystrixTest(HystrixCommandGroupKey.Factory.asKey("ext"));
		/**
		 * execute():以同步阻塞方式執行run()。以demo為例,調用execute()后,
		 * hystrix先創建一個新線程運行run(),
		 * 	接着調用程序要在execute()調用處一直阻塞着,直到run()運行完成 
		 */
	//	System.out.println("result:" + hystrixTest.execute());
		
		/**
		 * queue():以異步非阻塞方式執行run()。以demo為例,
		 * 	一調用queue()就直接返回一個Future對象,
		 * 	同時hystrix創建一個新線程運行run(),
		 * 	調用程序通過Future.get()拿到run()的返回結果,
		 * 	而Future.get()是阻塞執行的
		 */
		Future<String> futureResult = new HystrixTest(HystrixCommandGroupKey.Factory.asKey("ext")).queue();
		String result = "";
		try {
			result = futureResult.get();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ExecutionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		System.out.println("程序結果:"+result);
	}

	@Override
	protected Object run() throws Exception {
		// TODO Auto-generated method stub
		System.out.println("執行邏輯");
		int i = 1/0;
		return "ok";
	}

	@Override
	protected Object getFallback() {
		// TODO Auto-generated method stub
		return "getFallbackgetFallback";
	}
	
	
	
}

整合Resttemplate

Service

	@HystrixCommand(fallbackMethod = "back")
	public String alive() {
		// 自動處理URL
		
		RestTemplate restTemplate = new RestTemplate();
		
		String url ="http://user-provider/User/alive";
		String object = restTemplate.getForObject(url, String.class);
		
		return object;
		
	}
	
	
	public String back() {
		
		return "請求失敗~bbb...";
	}

啟動類

@EnableCircuitBreaker

整合Feign

配置

feign.hystrix.enabled=true

接口

@FeignClient(name = "user-provider",fallback = AliveBack.class)
public interface ConsumerApi {

	@RequestMapping(value = "/User/alive",method = RequestMethod.GET)
	public String alive();
	
	@RequestMapping(value = "/User/getById",method = RequestMethod.GET)
	public String getById(Integer id);
}

實現

package com.mashibing.UserConsumer;

import java.util.Map;

import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
@Component
public class AliveBack implements ConsumerApi{

	@Override
	public String alive() {
		// TODO Auto-generated method stub
		return "aaa";
	}

	@Override
	public String getById(Integer id) {
		// TODO Auto-generated method stub
		return null;
	}

}

使用fallbackFactory檢查具體錯誤

實現類

package com.mashibing.UserConsumer;

import java.util.Map;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.springframework.stereotype.Component;

import com.mashibing.UserAPI.Person;

import feign.hystrix.FallbackFactory;

@Component
public class WebError implements FallbackFactory<ConsumerApi> {

	@Override
	public ConsumerApi create(Throwable cause) {
		// TODO Auto-generated method stub
		return new ConsumerApi() {
			
		@Override
		public Person postPserson(Person person) {
			// TODO Auto-generated method stub
			return null;
		}
			
		@Override
		public String getById(Integer id) {
			// TODO Auto-generated method stub
			return null;
		}
			
		@Override
		public String alive() {
			// TODO Auto-generated method stub
			System.out.println(cause.getLocalizedMessage());
			cause.printStackTrace();
			return ToStringBuilder.reflectionToString(cause);
		}
			
		@Override
		public Map<Integer, String> postMap(Map<String, Object> map) {
			// TODO Auto-generated method stub
			return null;
		}
			
		@Override
		public Map<Integer, String> getMap3(Map<String, Object> map) {
			// TODO Auto-generated method stub
			return null;
		}
			
		@Override
		public Map<Integer, String> getMap2(Integer id, String name) {
			// TODO Auto-generated method stub
			return null;
		}
			
		@Override
		public Map<Integer, String> getMap(Integer id) {
			// TODO Auto-generated method stub
			return null;
		}
	};
      }

}

針對不同異常返回響應

      @Override
      public String alive() {
            // TODO Auto-generated method stub
            System.out.println(cause);
            if(cause instanceof InternalServerError) {
            System.out.println("InternalServerError");
                  return "遠程服務報錯";
            }else if(cause instanceof RuntimeException) {
                  return "請求時異常:" + cause;
            }else {
                  return "都算不上";
            }
      }

信號量隔離與線程隔離

默認情況下hystrix使用線程池控制請求隔離

線程池隔離技術,是用 Hystrix 自己的線程去執行調用;而信號量隔離技術,是直接讓 tomcat 線程去調用依賴服務。信號量隔離,只是一道關卡,信號量有多少,就允許多少個 tomcat 線程通過它,然后去執行。

信號量隔離主要維護的是Tomcat的線程,不需要內部線程池,更加輕量級。

配置

hystrix.command.default.execution.isolation.strategy 隔離策略,默認是Thread, 可選Thread|Semaphore
thread 通過線程數量來限制並發請求數,可以提供額外的保護,但有一定的延遲。一般用於網絡調用
semaphore 通過semaphore count來限制並發請求數,適用於無網絡的高並發請求
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 命令執行超時時間,默認1000ms
hystrix.command.default.execution.timeout.enabled 執行是否啟用超時,默認啟用true
hystrix.command.default.execution.isolation.thread.interruptOnTimeout 發生超時是是否中斷,默認true
hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests 最大並發請求數,默認10,該參數當使用ExecutionIsolationStrategy.SEMAPHORE策略時才有效。如果達到最大並發請求數,請求會被拒絕。理論上選擇semaphore size的原則和選擇thread size一致,但選用semaphore時每次執行的單元要比較小且執行速度快(ms級別),否則的話應該用thread。
semaphore應該占整個容器(tomcat)的線程池的一小部分。

Feign下配置

hystrix.command.default.execution.isolation.strategy=SEMAPHORE

開啟dashboard

啟動類

@EnableHystrixDashboard

引入依賴

<dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
		
<dependency>
      <groupId>org.springframework.boot</groupId>      
      <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM