目录
1 Feign简介
2 服务搭建
2.1 系统架构
2.2 构建Controller API项目
2.3 构建Server Provider项目
2.4 构建Server Consumer
3 记录Consumer请求Provider的相关信息
4 提高并发请求——http连接池
5 ribbon优化负载均衡
1 Feign简介
Feign是一种声明式、模板化的HTTP客户端。在Spring Cloud中使用Feign, 可以做到使用HTTP请求远程服务时能与调用本地方法一样的编码体验,开发者完全感知不到这是远程方法,更感知不到这是个HTTP请求。
2 服务搭建
2.1 系统架构
系统分为Eureka Server,Server Provider,Server Consumer以及被Provider和Consumer调用和实现的接口API,其中API中定义了Model和Controller访问接口,这样做的好处是只需要在API中维护一份Model和Controller接口,Provider和Consumer只需要负责调用或实现即可,其结构图如下:(此处不介绍Eureka Server的搭建)
2.2 构建Controller API项目
A:POM文件
Controller API maven gav如下:
<parent>
<groupId>bookshop</groupId>
<artifactId>bookshop</artifactId>
<version>0.0.1</version>
<relativePath />
</parent>
<artifactId>bookshop-api</artifactId>
主要依赖如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
如果该项目的父项目不是org.springframework.boot,而是继承于自己定义的父项目,则需要配置dependencyManagement来引入org.springframework.boot。(https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using-boot-maven-without-a-parent)
<dependencyManagement>
<dependencies>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
B:定义Model
public class Book implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String bookName;
private String author;
public Book() {}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
注:定义Model时,需要提供无参的构造函数,否则Feign在序/反列化时会出错。
C:定义Controller 接口
定义HTTP请求的参数、格式、地址等信息
public interface BookControllerAPI {
@GetMapping(value="/getbooklist")
public List<Book> getBookList();
@RequestMapping(value="/get",method=RequestMethod.GET)
public Book getBook(@RequestParam("id") Integer id);
/**
* 获取多参数的访问
* */
//@RequestMapping(value="/getbook",method=RequestMethod.GET)未引入feign httpclient
@RequestMapping(value="/getbook",method=RequestMethod.GET,consumes=MediaType.APPLICATION_JSON_VALUE)
public Book getBook(Book book);
/**
* id=1&bookName=abc&author=xiaoming
* */
@RequestMapping(value="/getbook2",method=RequestMethod.GET)
public Book getBook(@RequestParam("id") Integer id, @RequestParam("bookName") String bookName, @RequestParam("author") String author);
@RequestMapping(value="/addbook",method=RequestMethod.POST)
public Book addBook(@RequestBody Book book);
}
2.3 构建Server Provider项目
A:引入POM依赖
<dependency>
<groupId>bookshop</groupId>
<artifactId>bookshop-api</artifactId>
<version>0.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
这里引入了 bookshop-api工程,来实现对Controller api的实现和Model的调用。
B:实现Controller API接口
@RestController
public class BookControllerImp implements BookControllerAPI {
@Override
public List<Book> getBookList() {
List<Book> list = new ArrayList<Book>();
Book book = new Book();
book.setAuthor("小明");
book.setBookName("小明自传");
list.add(book);
return list;
}
@Override
public Book getBook(Integer id) {
Book book = new Book();
book.setId(id);
book.setAuthor("小明");
book.setBookName("小明自传");
return book;
}
@Override
//public Book getBook(Book book) {
public Book getBook(@RequestBody Book book) {
return book;
}
@Override
public Book getBook(Integer id, String bookName, String author) {
Book book = new Book();
book.setId(id);
book.setAuthor(author);
book.setBookName(bookName);
return book;
}
@Override
public Book addBook(@RequestBody Book book) {
return book;
}
}
注:在实现的Controller中,需要加类注解@RestController ,API中不需要。
C:配置application.yml
eureka:
client:
serviceUrl: #注册中心的注册地址
defaultZone: http://127.0.0.1:9091/eureka/
server:
port: 9092 #服务端口号
spring:
application:
name: bookshop-service-provider #服务名称--调用的时候根据名称来调用该服务的方法
D:启动类
@SpringBootApplication
@EnableEurekaClient //开启Eureka
public class BookshopProviderApplication {
public static void main(String[] args) {
SpringApplication.run(BookshopProviderApplication.class, args);
}
}
2.4 构建Server Consumer
A:POM依赖
<dependency>
<groupId>bookshop</groupId>
<artifactId>bookshop-api</artifactId>
<version>0.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
主要是引入bookshop-api和feign依赖。
B:继承Consumer API
编写一个接口,来继承bookshop-api中定义的Controller接口,此处无需具体的实现。
@FeignClient("bookshop-service-provider")
public interface BookConsumerControllerInterface extends BookControllerAPI {
}
此处加上注解@FeignClient("bookshop-service-provider")声明当前Interface为bookshop-service-provider服务的客户端
通过这种方式在开发调用远程服务时可以像调用本地服务一样
C:编写Web Controller,接收来自web客户端的访问,注意此处返回的内容,引用了上一步骤定义的接口类
@RestController
public class BookWebContrller {
@Autowired
BookConsumerControllerInterface bookCon;
//@GetMapping(value="getbooklist")
@RequestMapping(value="getbooklist",method=RequestMethod.GET)
public List<Book> getBookList(){
return bookCon.getBookList();
}
//@GetMapping(value="get")
@RequestMapping(value="get",method=RequestMethod.GET)
public Book getBook(@RequestParam Integer id){
return bookCon.getBook(id);
}
/**
* http://127.0.0.1:9093/getbook?id=1&bookName=abc&author=xiaoming
* feign会自动将请求转化为POST,故会报405的错误
* There was an unexpected error (type=Internal Server Error, status=500).
* status 405 reading BookConsumerControllerInterface#getBook(Book); content: {"timestamp":"2018-12-21T14:12:36.530+0000","status":405,"error":"Method Not Allowed","message":"Request method 'POST' not supported","path":"/getbook"}
* ********利用feign的HttpClient解决上述问题--------------------
* feign的http客户端默认支持3种框架:HttpURLConnection,httpclient,okhttp默认是HttpURLConnection
* --HttpURLConnection是jdk自带的,不支持连接池,
* --HttpClient封装了访问http的请求头,参数,内容体,响应等等,是客户端发送请求变得容易,方便
* 开发人员测试接口,提高开发效率,另外高并发大量的请求网络,用连接池提升吞吐量.
* 1:引入jar包依赖
* 2:application.yml中开启feign.httpclient
* 3:API中的requestmapping 添加consumers的mediatype=application.json.value
* */
@RequestMapping(value="/getbook",method=RequestMethod.GET)
public Book getBook(Book book) {
return bookCon.getBook(book);
}
/**
* 多个参数的get访问
* */
@RequestMapping(value="/getbook2",method=RequestMethod.GET)
public Book getBook(@RequestParam("id") Integer id, @RequestParam("bookName") String bookName, @RequestParam("author") String author) {
return bookCon.getBook(id, bookName, author);
}
/**
* POST请求
* */
@RequestMapping(value="/addbook",method=RequestMethod.POST)
public Book addBook(@RequestBody Book book) {
return bookCon.addBook(book);
}
}
D:配置application.yml
eureka:
client:
serviceUrl: #注册中心的注册地址
defaultZone: http://127.0.0.1:9091/eureka/
server:
port: 9093 #服务端口号
spring:
application:
name: bookshop-service-consumer #服务名称--调用的时候根据名称来调用该服务的方法
E:启动类
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class BookshopConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(BookshopConsumerApplication.class, args);
}
}
3 记录Consumer请求Provider的相关信息
记录Consumer中每个URL访问Provider的请求信息,状态码,响应时间等
A:向容器中注入Logger.Level,此处在Consumer启动类中直接注入,也可以单独写成配置类
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class BookshopConsumerApplication {
/**
* 实现打印请求日志
* NONE:不记录任何日志(默认)
* BASIC:仅记录请求方法,url,响应状态码和执行时间
* HEADERS:在basic的基础上,记录请求和响应的header
* FULL:记录请求响应的header,body和元数据
* */
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
public static void main(String[] args) {
SpringApplication.run(BookshopConsumerApplication.class, args);
}
}
B:将Feign接口的日志级别设置成debug,Feign的Logger.Level只对debug做出响应
logging:
level:
com.bookshop.consumer.api.BookConsumerControllerInterface: DEBUG
注意:com.bookshop.consumer.api.BookConsumerControllerInterface是Consumer中继承了Controller API的接口类
C:效果
2018-12-27 16:24:13.669 DEBUG 21120 --- [nio-9093-exec-3] c.b.c.a.BookConsumerControllerInterface : [BookConsumerControllerInterface#getBook] ---> GET http://bookshop-service-provider/getbook2?id=1&bookName=abc&author=xiaoming HTTP/1.1
2018-12-27 16:24:13.670 DEBUG 21120 --- [nio-9093-exec-3] c.b.c.a.BookConsumerControllerInterface : [BookConsumerControllerInterface#getBook] ---> END HTTP (0-byte body)
2018-12-27 16:24:13.724 DEBUG 21120 --- [nio-9093-exec-3] c.b.c.a.BookConsumerControllerInterface : [BookConsumerControllerInterface#getBook] <--- HTTP/1.1 200 (53ms)
2018-12-27 16:24:13.724 DEBUG 21120 --- [nio-9093-exec-3] c.b.c.a.BookConsumerControllerInterface : [BookConsumerControllerInterface#getBook] content-type: application/json;charset=UTF-8
2018-12-27 16:24:13.724 DEBUG 21120 --- [nio-9093-exec-3] c.b.c.a.BookConsumerControllerInterface : [BookConsumerControllerInterface#getBook] date: Thu, 27 Dec 2018 08:24:13 GMT
2018-12-27 16:24:13.724 DEBUG 21120 --- [nio-9093-exec-3] c.b.c.a.BookConsumerControllerInterface : [BookConsumerControllerInterface#getBook] transfer-encoding: chunked
2018-12-27 16:24:13.724 DEBUG 21120 --- [nio-9093-exec-3] c.b.c.a.BookConsumerControllerInterface : [BookConsumerControllerInterface#getBook]
2018-12-27 16:24:13.724 DEBUG 21120 --- [nio-9093-exec-3] c.b.c.a.BookConsumerControllerInterface : [BookConsumerControllerInterface#getBook] {"id":1,"bookName":"abc","author":"xiaoming"}
2018-12-27 16:24:13.724 DEBUG 21120 --- [nio-9093-exec-3] c.b.c.a.BookConsumerControllerInterface : [BookConsumerControllerInterface#getBook] <--- END HTTP (45-byte body)
4 提高并发请求——http连接池
Feign的HTTP客户端支持3种框架:HttpURLConnection、httpclient、okhttp;默认是HttpURLConnection,HttpURLConnection是JDK自带的,不支持连接池。HttpClient封装了访问http的请求头,参数,内容体,响应体等等。
A:在Server Consumer的POM中加入HttpClient 依赖
<!--引入Apache HttpClient 替换Feign原生httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.6</version>
</dependency>
<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>8.18.0</version>
</dependency>
B:在Consumer的application.yml中打开httpclient
#启动HttpClient
feign:
httpClient:
enabled: true
5 ribbon优化负载均衡
A:Consumer侧引入Ribbon依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
B:Consumer侧配置
1) 全局配置
#作用于Consumer与Provider之间的http请求访问
ribbon:
ConnectTimeout: 5000 #请求连接的超时时间
ReadTimeout: 3000 #请求处理的超时时间
在provider的一个请求中设置线程睡眠时间6s,可以模拟出请求超时的情况:
2018-12-27 18:06:15.547 DEBUG 25592 --- [io-9093-exec-10] c.b.c.a.BookConsumerControllerInterface : [BookConsumerControllerInterface#getBookList] ---> GET http://bookshop-service-provider/getbooklist HTTP/1.1
2018-12-27 18:06:15.548 DEBUG 25592 --- [io-9093-exec-10] c.b.c.a.BookConsumerControllerInterface : [BookConsumerControllerInterface#getBookList] ---> END HTTP (0-byte body)
2018-12-27 18:06:21.553 DEBUG 25592 --- [io-9093-exec-10] c.b.c.a.BookConsumerControllerInterface : [BookConsumerControllerInterface#getBookList] <--- ERROR SocketTimeoutException: Read timed out (6004ms)
2018-12-27 18:06:21.554 DEBUG 25592 --- [io-9093-exec-10] c.b.c.a.BookConsumerControllerInterface : [BookConsumerControllerInterface#getBookList] java.net.SocketTimeoutException: Read timed out
2) 局部配置
对某个Provider的访问进行设置,而不是对所有的Provider进行限制。
在Consumer中配置请求超时重试信息:
#局部
bookshop-service-provider: #provider实例名
ribbon:
OkToRetryOnAllOperations: true #对所有的请求进行重试
MaxAutoRetries: 2 #当前服务实例的重试次数
MaxAutoRetriesNextServer: 0 #切换实例的重试测试
ConnectTimeout: 3000 #请求连接超时时间
ReadTimeout: 3000 #请求处理的超时时间
在provider server中的BookControllerImp.getBookList()方法中添加打印和线程休眠代码:
public List<Book> getBookList() {
System.out.println("======================"+new Date());
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
List<Book> list = new ArrayList<Book>();
Book book = new Book();
book.setAuthor("小明");
book.setBookName("小明自传");
list.add(book);
return list;
}
web访问consumer的对应接口,Consumer日志窗口效果如下:(表明在超时后重试了两次,总共访问了三次)
======================Thu Dec 27 18:48:16 CST 2018
======================Thu Dec 27 18:48:19 CST 2018
======================Thu Dec 27 18:48:22 CST 2018