上篇文章介紹了springcloud中eureka以及ribbon的基本使用方式,本文繼續(xù)記錄feign的學(xué)習(xí)歷程
什么是feign
feign與ribbon一樣剩膘,作用都是在springcloud中調(diào)用服務(wù)的遵馆。feign底層也是ribbon粥血,對(duì)ribbon進(jìn)行了再次封裝湿滓。
feign與ribbon的使用方式區(qū)分
- .ribbon通過(guò)注入restTemplate來(lái)實(shí)現(xiàn)對(duì)服務(wù)的調(diào)用诗良,在調(diào)用時(shí)需要手動(dòng)構(gòu)建http請(qǐng)求
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
log.info("consumer啟動(dòng)成功");
}
@Bean
@LoadBalanced
RestTemplate restTemplate()
{
return new RestTemplate();
}
}
@Autowired
RestTemplate restTemplate;
@Override
public String hiService(String name) throws UnknownHostException {
//當(dāng)前機(jī)器ip
InetAddress ia = InetAddress.getLocalHost();
//構(gòu)建http請(qǐng)求
for (int i =0;i<1;i++){
String str = restTemplate.getForObject("http://SERVICE-HI/hi?name=" + name, String.class);
log.info( str + " 第 " + i + " 次調(diào)用");
}
return "0";
}
- feign通過(guò)指定FeignClient,通過(guò)接口直接調(diào)用服務(wù)
@FeignClient("SERVICE-HI")
public interface DemoServiceFeign {
/**
* 注意:
* @RequestParam 中 value要與服務(wù)提供方接口中聲明的參數(shù)一致
* @PostMapping 中 url要與服務(wù)提供方url一致
* @param name
* @return
*/
@PostMapping("/hi")
String hello(@RequestParam(value = "name") String name);
}
feign調(diào)用服務(wù)
@Autowired
private DemoServiceFeign demoServiceFeign;
@RequestMapping(value = "/hello")
public String hello(@RequestParam String name) throws UnknownHostException {
return demoServiceFeign.hello(name);
}
feign依賴
- 不同版本的springcloud糠亩,對(duì)應(yīng)的feign依賴也不同鸥拧,文中使用這個(gè)maven坐標(biāo)
<!--feign依賴-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 老版本坐標(biāo),如果版本不符合,maven會(huì)顯示spring-cloud-starter-feign:unknow
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
使用feign
- 引入feign,啟動(dòng)類添加@EnableFeignClients注解削解,
詳情見官網(wǎng) https://cloud.spring.io/spring-cloud-static/spring-cloud-openfeign/2.2.0.RC1/reference/html/#netflix-feign-starter
中文文檔 https://www.springcloud.cc/spring-cloud-dalston.html#spring-cloud-feign
@Slf4j
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
//@EnableHystrix
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
log.info("consumer啟動(dòng)成功");
}
}
- 編寫接口富弦,接口添加@FeignClient注解
@FeignClient("SERVICE-HI")
public interface DemoServiceFeign {
/**
* 注意:
* @RequestParam 中 value要與服務(wù)提供方接口中聲明的參數(shù)一致
* @PostMapping 中 url要與服務(wù)提供方url一致
* @param name
* @return
*/
@PostMapping("/hi")
String hello(@RequestParam(value = "name") String name);
}
官網(wǎng)中有說(shuō)明,F(xiàn)eignClient注解中需要加入被feign調(diào)用服務(wù)的 應(yīng)用名稱:spring.application.name=service-hi氛驮;文檔還提到“您還可以使用url屬性(絕對(duì)值或只是主機(jī)名)指定URL”,本文使用服務(wù)名稱
- 控制層調(diào)用腕柜,注入接口調(diào)用,當(dāng)做普通方法調(diào)用即可
注意這里不需要在注入restTemplate類來(lái)手動(dòng)構(gòu)建http請(qǐng)求了矫废。
feign底層的介紹推薦這幾篇文章:https://blog.csdn.net/qq_39470742/article/details/83539517
博主有很多篇介紹feign源碼的文章
@Autowired
private DemoServiceFeign demoServiceFeign;
@RequestMapping(value = "/hello")
public String hello(@RequestParam String name) throws UnknownHostException {
return demoServiceFeign.hello(name);
}
-
調(diào)用結(jié)果,已經(jīng)使用feign實(shí)現(xiàn)了服務(wù)間調(diào)用
feign切換負(fù)載均衡策略
文章開頭有說(shuō)道feign本質(zhì)上調(diào)用的也是ribbon盏缤,是對(duì)ribbon的高度封裝,因此切換負(fù)載均衡策略蓖扑,實(shí)際上就是對(duì)ribbon切換負(fù)載均衡策略
SERVICE-HI:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
ribbon默認(rèn)輪詢策略唉铜,這里切換為隨機(jī)策略,可以看到結(jié)果已經(jīng)成功了
到這里feign簡(jiǎn)單使用已經(jīng)完成了,至于服務(wù)間調(diào)用選擇feign還是ribbon律杠,網(wǎng)上有很多種說(shuō)法潭流,個(gè)人感覺feign確實(shí)很好用,接口以及注解標(biāo)明調(diào)用的api可讀性很強(qiáng)柜去,更容易上手灰嫉。下面介紹一些使用feign時(shí)遇到的問題
read time out
feign底層使用的ribbon,ribbon本身有默認(rèn)的超時(shí)時(shí)間嗓奢,在LoadBalancerFeignClient.java類中有execute方法讼撒,如下:
@Override
public Response execute(Request request, Request.Options options) throws IOException {
execute方法中有兩個(gè)參數(shù),Request是包含一些請(qǐng)求信息股耽,Options是配置相關(guān)信息根盒,Options類內(nèi)容如下:
public static class Options {
private final int connectTimeoutMillis;
private final int readTimeoutMillis;
private final boolean followRedirects;
public Options(int connectTimeoutMillis, int readTimeoutMillis, boolean followRedirects) {
this.connectTimeoutMillis = connectTimeoutMillis;
this.readTimeoutMillis = readTimeoutMillis;
this.followRedirects = followRedirects;
}
public Options(int connectTimeoutMillis, int readTimeoutMillis) {
this(connectTimeoutMillis, readTimeoutMillis, true);
}
public Options() {
this(10000, '\uea60');
}
public int connectTimeoutMillis() {
return this.connectTimeoutMillis;
}
public int readTimeoutMillis() {
return this.readTimeoutMillis;
}
public boolean isFollowRedirects() {
return this.followRedirects;
}
}
這里可以看到,如果ribbon默認(rèn)的連接時(shí)間為10秒物蝙,默認(rèn)的readTimeOut時(shí)間為60秒炎滞;我們讓服務(wù)提供者睡眠10秒后提供服務(wù),來(lái)測(cè)試readTimeOut時(shí)間是否合理茬末,代碼如下
//服務(wù)提供者厂榛,休眠10秒
@RestController
public class DemoController {
@Value("${server.port}")
String port;
@RequestMapping("/hi")
public String home(@RequestParam(value = "name") String name)
{
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hi " + name + ",i am from port:" + port;
}
}
結(jié)果
idea報(bào)錯(cuò)
java.net.SocketTimeoutException: Read timed out
at java.net.SocketInputStream.socketRead0(Native Method) ~[na:1.8.0_181]
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116) ~[na:1.8.0_181]
at java.net.SocketInputStream.read(SocketInputStream.java:171) ~[na:1.8.0_181]
at java.net.SocketInputStream.read(SocketInputStream.java:141) ~[na:1.8.0_181]
at java.io.BufferedInputStream.fill(BufferedInputStream.java:246) ~[na:1.8.0_181]
at java.io.BufferedInputStream.read1(BufferedInputStream.java:286) ~[na:1.8.0_181]
at java.io.BufferedInputStream.read(BufferedInputStream.java:345) ~[na:1.8.0_181]
at sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:735) ~[na:1.8.0_181]
at sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:678) ~[na:1.8.0_181]
at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1587) ~[na:1.8.0_181]
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1492) ~[na:1.8.0_181]
at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:480) ~[na:1.8.0_181]
at feign.Client$Default.convertResponse(Client.java:82) ~[feign-core-10.4.0.jar:na]
可以看到盖矫,在沒有主動(dòng)修改ribbon超時(shí)時(shí)間時(shí),他默認(rèn)是60秒击奶,但休眠10秒就已經(jīng)超時(shí)了辈双,事實(shí)上及時(shí)休眠1秒也會(huì)超時(shí),感興趣的可以測(cè)試一下柜砾,原因是hystrix默認(rèn)是1秒超時(shí)湃望,這里就需要指定feign的超時(shí)時(shí)間,具體設(shè)置多少需要看各個(gè)項(xiàng)目的需求
feign學(xué)習(xí)到這里就基本完成了痰驱,接下來(lái)會(huì)記錄hystrix的學(xué)習(xí)证芭,歡迎大家來(lái)討論