目录

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的搭建)

feign 如何查看服务注册中心地址_spring

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