資料來源: <<重新定義Spring Cloud>>以及博客和官網
源碼地址:https://gitee.com/08081/hello-springcloud
什么是feign ?
fegin是一種聲明式 模板化的HTTP客戶端(僅在consumer中使用)
1. 什么是聲明式呢?
- 聲明式調用就像調用本地方法一樣調用遠程方法,無感知遠程http請求
- Spring Cloud的聲明式調用,可以做到使用HTTP請求遠程服務時就像調用本地方法一樣的體驗,開發者完全感知不到這是遠程調用,更加不知道這是一個http請求
- 像dubbo一樣,consumer直接調用接口方法調用provider,而不需要通過常規的httpclient構造在解析返回數據
- 解決了.開發者調用遠程和調用本地一樣,無需關心遠程交互的細節,更無需關注分布式環境
2. 工作原理
- 在開發微服務應用時,我們會在主程序入口添加@EnableFeignClients注解開啟對FeginClient掃描加載處理,
- 當程序啟動時,會進行掃描,掃描所有@FeginClients的注解類,並將這些信息,注入到Spring IOC容器中. 當定義的fegin接口中的方法被調用時,通過JDK代理的方式,來生成具體的RequestTemplate,當生成代理時,Fegin會為每一個接口方法創建一個RequestTemplate對象,該對象封裝了HTTP請求需要的全部信息,如請求參數,請求方法等信息都是在這個過程中確定的.
- 然后由RequestTemplate生成request,然后把request交給client去處理,這里說的client 可以是jdk中的urlConnection apache 的httpclient 最后client被封裝到LoadBalanceClient類,這個類接口ribbon負載均衡發起服務之間的調用
入門案例
我們在父工程下創建ch4-fegin 這個父工程在他下面創建 3個模塊
1. book-api: 主要作為一個接口給別的服務依賴,包含 實體對象,和api
2. book-service: 主要是作為book-api 的實現類模塊
3.book-consumer: 調用book服務的類
下面是代碼:
先看book-api的代碼
實體類:
/** * Created by xiaodao * date: 2019/7/17 */ @Data @Builder @AllArgsConstructor @NoArgsConstructor public class Book { private int id; private String name; }
api:
package com.xiaodao.api; import com.xiaodao.entity.Book; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import java.util.List; /** * Created by xiaodao * date: 2019/7/17 */ @RequestMapping("/book") public interface BookApi { @GetMapping(value = "list") List<Book> findList(); }
pom:也是空的pom
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>ch4-fegin</artifactId> <groupId>com.xiaodao</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>ch4-book-api</artifactId> </project>
ch4-book-service的代碼:
pom:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>ch4-fegin</artifactId> <groupId>com.xiaodao</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>ch4-book-service</artifactId> <dependencies> <dependency> <groupId>com.xiaodao</groupId> <artifactId>ch4-book-api</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> </dependencies> </project
bootstrap.yml:
spring: application: name: book-service eureka: client: service-url: defaultZone: http://127.0.0.1:8888/eureka/ instance: prefer-ip-address: true server: port: 8000
bookserviceImpl:

package com.xiaodao.service; import com.xiaodao.api.BookApi; import com.xiaodao.entity.Book; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.ValueConstants; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; /** * Created by xiaodao * date: 2019/7/17 */ @RestController @RequestMapping("book") public class BookServiceImpl implements BookApi { @GetMapping(value = "list") @Override public List<Book> findList() { Book book =Book.builder().id(1).name("第一本書").build(); System.out.println(book); List<Book> list = new CopyOnWriteArrayList<>(); list.add(book); return list; } }
bookApplicatiton

package com.xiaodao; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * Created by xiaodao * date: 2019/7/17 */ @SpringBootApplication public class BookApplication { public static void main(String[] args) { SpringApplication.run(BookApplication.class,args); } }
ch4-book-consumer 的代碼:
pom:

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>ch4-fegin</artifactId> <groupId>com.xiaodao</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>ch4-book-consumer</artifactId> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>com.xiaodao</groupId> <artifactId>ch4-book-api</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> </dependencies> </project>
bootstrap.yml
spring: application: name: book-consumer eureka: client: service-url: defaultZone: http://127.0.0.1:8888/eureka/ instance: prefer-ip-address: true server: port: 8001
service:就是繼承了book-api service的接口
name 是 book-service 的實例名
package com.xiaodao.service; import com.xiaodao.api.BookApi; import org.springframework.cloud.openfeign.FeignClient; /** * Created by xiaodao * date: 2019/7/17 */ @FeignClient(name = "book-service") public interface BookService extends BookApi { }
controller.調用service.->通過Fegin->book-service
package com.xiaodao.controller; import com.xiaodao.entity.Book; import com.xiaodao.service.BookService; import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; /** * Created by xiaodao * date: 2019/7/17 */ @RestController @RequestMapping("/book") @AllArgsConstructor public class BookController { private BookService bookService; @GetMapping("/findList") public List<Book> findList(){ return bookService.findList(); } }
最后我們啟動eurekasever book-service book-consumer 我們可以訪問eurekaService
我們去調用book-consumer里的controller方法,看看能不能調用book-service的實現類:
以上就是一個簡單的hello word 實現的方法.
傳參數
1.單個參數時候沒有問題的
2.多個參數用post方法 (@requestBody Book book) feign是不支持 get方法傳遞實體對象的
3.如果想用get方式傳實體對象需要將feign默認的URLConnection連接換成httpClient
fegin 性能優化
gzip壓縮
- gzip介紹:gzip是一種數據格式,采用defalte算法壓縮data gzip是一種流行的文件壓縮算法,應用十分廣泛,尤其是在liunx平台
- gzip能力: 當gzip壓縮一個純文本文件時,效果是非常明顯,大約可以減少70%以上的文件大小
- gzip作用: 網絡數據通過壓縮之后實際上降低了網絡傳輸的字節數,最明顯的好處時候加快網頁的加載速度,網頁加載速度加快的好處不言而喻,除了節省流量以外,改善用戶體驗外,另一個潛在的好處是Gzip與搜索引擎的抓取工具有着更好的關系,例如:Google 就可以通過讀取Gzip文件來比普通手工抓取 更快的檢索網頁
HTTP協議中關於壓縮傳輸的規定:
1. 客戶端向服務器請求中帶有:Accept-Encoding:Gzip,deflate字段,向服務器表示,客戶端支持的壓縮格式(gzip或者 deflate),如果不發送消息頭,服務器是不會壓縮的
2. 服務端收到請求之后,如果發現請求中含有Accept-Encoding字段,並且支持該類型的壓縮,就對響應報文壓縮之后在返回客戶端,並且攜帶Content-Encoding:gzip消息頭,表示報文是經過壓縮的
3. 客戶端接到服務器的響應之后,先判斷是否有Content-Encoding消息頭,如果有,按格式解壓報文,否者按正常處理
我們在baidu搜索下發送一個消息
看到是百度服務器是經過壓縮的
然后我們在看下我們剛才的代碼.
可以看出是沒有經過壓縮的.
那我們使用fegin怎么做壓縮呢?
spring: application: name: book-consumer eureka: client: service-url: defaultZone: http://127.0.0.1:8888/eureka/ instance: prefer-ip-address: true server: port: 8001 ### Feign 配置 feign: compression: request: # 開啟請求壓縮 enabled: true # 配置壓縮的 MIME TYPE mime-types: text/xml,application/xml,application/json # 配置壓縮數據大小的下限 min-request-size: 2048 response: # 開啟響應壓縮 enabled: true
這里有坑,當我在book-consumer 配置了壓縮的時候,我發現我們請求是配置了gzip壓縮了但是服務器並沒有壓縮.為什么呢?是當我們使用瀏覽器訪問的時候沒有壓縮.
當我們在配置spring boot壓縮之后:在看控制台就顯示服務器也壓縮過了

spring: application: name: book-consumer eureka: client: service-url: defaultZone: http://127.0.0.1:8888/eureka/ instance: prefer-ip-address: true server: port: 8001 #配置spring boot 壓縮 compression: enabled: true # mime-types: ### Feign 配置 feign: compression: request: # 開啟請求壓縮 enabled: true # 配置壓縮的 MIME TYPE mime-types: text/xml,application/xml,application/json # 配置壓縮數據大小的下限 min-request-size: 2048 response: # 開啟響應壓縮 enabled: true
也可以不寫fegin的壓縮,直接 就用springboot的壓縮就好了.
HTTP連接池優化
為什么http連接池能提升性能?
http的背景原理
1.倆台服務器建立http的過程是很富在的工程,涉及到了多個數據包的交換,並且也耗費時間
2. http連接需要3次握手4次分手開銷很大,這一開銷對於大量比較小的http消息來說更大
優化解決方案
1.如果我們采用http連接池,節約了大量的3次握手4次分手,這樣能大大的提高性能
2.fegin的http客戶端支持3種框架 : HttpURLConnection httpclient okhttp 默認是HTTPURLConnection.
3.傳統的HTTPURLConnection是JDK自帶的,並不支持線程池,如果要實現連接池的機制,還需要自己管理連接對象,對於網絡請求這種底層相對復雜的操作,如果有可用的方案,也沒有必要自己去實現
4.HttpClient相比於JDK自帶的URLConnection,它封裝了訪問http的請求頭,參數,內容體響應等等,它不僅是客戶端發送http請求變的容易,而且方便開發人員測試接口(基於http協議), 也提高的效率,也方便提高代碼的健壯性,另外高並發大量請求網絡的時候,還是使用"連接池"提升吞吐量的.
使用httpClient連接池
1.在book-consumer 的pom中:加入
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.5</version> </dependency> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> <version>9.5.1</version> </dependency>
2.在bootstrap.yml中加入

spring: application: name: book-consumer eureka: client: service-url: defaultZone: http://127.0.0.1:8888/eureka/ instance: prefer-ip-address: true server: port: 8001 #配置spring boot 壓縮 compression: enabled: true # mime-types: ### Feign 配置 feign: compression: request: # 開啟請求壓縮 enabled: true # 配置壓縮的 MIME TYPE mime-types: text/xml,application/xml,application/json # 配置壓縮數據大小的下限 min-request-size: 2048 response: # 開啟響應壓縮 enabled: true #啟動httpclient httpclient: enabled: true
日志輸出:
bootstrap.yml
logging:
level:
#這個是使用feign的那個接口類
com:
xiaodao:
service:
BookService: DEBUG
@Configuration public class LoggerConfig { @Bean public Logger.Level feignLoggerLevel(){ return Logger.Level.FULL; } }
Feign的優化配置
feign的負載均衡,是使用ribbon,就需要我們對ribbon的配置進行配置.
ribbon的配置分為全局配置,和局部配置.
先來看全局配置:
在consumer的bootstrap.yml中加入:
#全局配置 ribbon: #請求連接時間 ConnectTimeout: 5000 #請求處理時間 ReadTimeout: 5000
在book-service的controller讓他睡眠:
@RestController @RequestMapping("book") public class BookServiceImpl implements BookApi { @GetMapping(value = "list") @Override public List<Book> findList() { try { TimeUnit.SECONDS.sleep(6); } catch (InterruptedException e) { e.printStackTrace(); } Book book =Book.builder().id(1).name("第一本書").build(); System.out.println(book); List<Book> list = new CopyOnWriteArrayList<>(); list.add(book); return list; } }
這個時候就超時了.
局部配置
但是我們一般不用全局配置,因為你的cosumer一般情況會調用很多provider,他們的情況不一.
#請求連接的超時時間
book-service.ribbon.ConnectTimeout=3000
這里有很多配置,有重試等可以去官網查看.