微服务之间的通信
微服务之间消息通信调用有两种方式:
RestTemplate
@LoadBalanced
显式调用OpenFeign
隐藏微服务间通信细节
Ribbon客户端负载均衡
- Ribbon是Netfilix开源的客户端负载均衡组件
- Ribbon是RestTemplate与OpenFeign的通信基础
Ribbon执行过程
Ribbon
作为消费者微服务一端,也会向注册中心进行注册,注册中心Eureka service
会向消费者Ribbon
提供当前注册的所有节点数据(url、端口)信息。然后根据轮循(默认)策略请求服务提供者。
一、基于RestTemplate服务间通信
RestTemplate
- 是Spring Cloud访问Restful API的请求对象
- 与HttpClient、OKHttp职能类似
@LoadBalanced注解
- @LoadBalanced是Ribbon提供的客户端负载均衡注解
- 通常RestTemplate与@LoadBalanced联合使用
1.1、简单案例
1.2、微服务结构
1.3、创建Eureka Service
注册中心
pom.xml依赖
代码语言:javascript复制<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.15.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.codesofun</groupId>
<artifactId>eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-server</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR6</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties添加配置
代码语言:javascript复制server.port=8761
spring.application.name=provider-service
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
eureka.server.enable-self-preservation=false
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
启动类上添加注解@EnableEurekaServer
代码语言:javascript复制package com.codesofun.eurekaserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
1.4、创建book-service
图书服务
pom.xml依赖
代码语言:javascript复制<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.15.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.codesofun</groupId>
<artifactId>book-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>book-service</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR6</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties添加配置
代码语言:javascript复制spring.application.name=book-service
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
启动类上添加注解@EnableEurekaClient
代码语言:javascript复制package com.codesofun.bookservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class BookServiceApplication {
public static void main(String[] args) {
SpringApplication.run(BookServiceApplication.class, args);
}
}
实体类Book
代码语言:javascript复制package com.codesofun.bookservice.entity;
/**
* @ClassName Book
* @Description
* @Author mozhijun
* @Date 2020/7/1 17:17
* @Version 1.0
**/
public class Book {
private String sn;
private String name;
private String desc;
public Book() {
}
public Book(String sn, String name, String desc) {
this.sn = sn;
this.name = name;
this.desc = desc;
}
public String getSn() {
return sn;
}
public void setSn(String sn) {
this.sn = sn;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
controller层
代码语言:javascript复制package com.codesofun.bookservice.controller;
import com.codesofun.bookservice.entity.Book;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/**
* @ClassName BookController
* @Description
* @Author mozhijun
* @Date 2020/7/1 17:18
* @Version 1.0
**/
@RestController
public class BookController {
@GetMapping("/book/{sn}")
public Book findBySN(@PathVariable("sn") String sn, HttpServletRequest request) {
Book book = null;
if (sn.equals("1111")) {
book = new Book("1111", "罗志祥的时间管理", String.valueOf(request.getLocalPort()));
} else if (sn.equals("2222")) {
book = new Book("2222", "钢铁是怎样练成的", String.valueOf(request.getLocalPort()));
} else if (sn.equals("3333")) {
book = new Book("3333", "西游记", String.valueOf(request.getLocalPort()));
}
return book;
}
}
IDEA中,点击Edit Configurations,通过启动不同端口参数,模拟多个图书服务Book-service
1.5、创建member-service
会员服务
pom.xml依赖
代码语言:javascript复制<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.15.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.codesofun</groupId>
<artifactId>member-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>member-service</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR6</spring-cloud.version>
</properties>
<dependencies>
<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-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties配置文件修改
代码语言:javascript复制server.port=9000
spring.application.name=member-service
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
启动类上,添加注解@EnableEurekaClient
代码语言:javascript复制package com.codesofun.memberservice;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RoundRobinRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableEurekaClient
public class MemberServiceApplication {
@Bean
@LoadBalanced //负载均衡
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(MemberServiceApplication.class, args);
}
}
实体类Book
代码语言:javascript复制package com.codesofun.memberservice.entity;
/**
* @ClassName Book
* @Description
* @Author mozhijun
* @Date 2020/7/1 18:17
* @Version 1.0
**/
public class Book {
private String sn;
private String name;
private String desc;
public Book() {
}
public String getSn() {
return sn;
}
public void setSn(String sn) {
this.sn = sn;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
controller层
代码语言:javascript复制package com.codesofun.memberservice.controller;
import com.codesofun.memberservice.entity.Book;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
/**
* @ClassName MemberController
* @Description
* @Author mozhijun
* @Date 2020/7/1 18:12
* @Version 1.0
**/
@RestController
public class MemberController {
@Resource
private RestTemplate restTemplate;
@GetMapping("/borrow/{sn}")
public String borrow(@PathVariable("sn") String sn) {
/**
* RestTemplate 负载均衡格式要求:http://微服务id/webapi地址
*/
Book book = restTemplate.getForObject("http://book-service/book/" sn, Book.class);
return book.getName() ":" book.getDesc() "图书借阅成功";
}
}
1.6、Ribbon负载均衡策略
- Ribbon共有七种负载均衡模式,分别如下:
策略类 | 说明 |
---|---|
BestAvailableRule | 选择一个最小的并发请求的服务实例 |
AvailabilityFilteringRule | 过滤掉那些因为一直连接失败的被标记为circuit tripped的服务实例,并过滤掉那些高并发的的服务实例 |
WeightedResponseTimeRule | 根据响应时间分配一个weight,响应时间越长,weight越小,被选中的可能性越低。 |
RetryRule | 对选定的负载均衡策略机上重试机制。 |
RoundRobinRule(默认) | roundRobin方式轮询选择服务实例 |
RandomRule | 随机选择一个服务实例 |
ZoneAvoidanceRule | 复合判断服务实例所在区域的性能和可用性选择服务实例 |
配置使用
通过在application.properties
中进行配置。这里在消费服务(member-service
)中进行配置如下:
#ribbon负载均衡策略,随机。默认是轮询模式
book-service.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
通过全局创建一个策略模式实例bean
代码语言:javascript复制 @Bean
public IRule rule(){
return new RoundRobinRule();
}
二、OpenFeign服务间通信
Feign是一个开源声明式WebService客户端,用于简化服务通信 Feign采用“接口 注解”方式开发,屏蔽了网络通信的细节 OpenFeign是SpringCloud对Feign的增强,支持Spring MVC注解
2.1、OpenFeign工作原理
2.2、OpenFeign基于Ribbon负载均衡
策略类 | 说明 |
---|---|
BestAvailableRule | 选择一个最小的并发请求的服务实例 |
AvailabilityFilteringRule | 过滤掉那些因为一直连接失败的被标记为circuit tripped的服务实例,并过滤掉那些高并发的的服务实例 |
WeightedResponseTimeRule | 根据响应时间分配一个weight,响应时间越长,weight越小,被选中的可能性越低。 |
RetryRule | 对选定的负载均衡策略机上重试机制。 |
RoundRobinRule(默认) | roundRobin方式轮询选择服务实例 |
RandomRule | 随机选择一个服务实例 |
ZoneAvoidanceRule | 复合判断服务实例所在区域的性能和可用性选择服务实例 |
application.properties
中的配置:
#ribbon负载均衡策略,随机。默认是轮询模式
book-service.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
Spring Bean
中的配置,若配置文件中同时配置了负载均衡规则,以实例bean中配置的为准:
@Bean
public IRule rule(){
return new RandomRule();
}
2.3、OpenFeign开启通信日志
OpenFeign开启通信日志
- 基于SpringBoot的logback输出,默认debug级别
- 设置项:feign.client.config.微服务id.loggerLevel
- 微服务id:default代表全局默认配置
通信日志输出格式
- NONE: 不输出任何通信日志
- BASIC: 只包含URL、请求方法、状态码、执行时间
- HEADERS:在BASIC基础上,额外包含请求与响应头
- FULL:包含请求与响应内容最完整的信息
备注:一般生产环境上使用headers
或basic
。
application.properties
中的配置:
#全局日志级别
logging.level.root=info
#指定包的包下面的类输出的日志级别
logging.level.com.codesofun.memberservice.openfeign.service=debug
#feign通信日志设置
feign.client.config.default.logger-level=headers
#feign指定微服务id通信日志,以此为准,没有则以default设置为准
feign.client.config.book-service.logger-level=headers
2.4、替换OpenFeign
通信组件
Open feign
基于JDK原生的URLConnection
提供Http通信(默认),当然其也可以配置支持Apache HttpClient
和 Square OkHttp
。SpringCloud
会按条件自动加载应用通信组件。
Maven 引入
feign-okhttp
或者feign-httpclient
依赖 设置feign.[httpclient|okhttp].enabled=true
application.properties
中的配置:
#底层通信开启 okhttp 的支持,两者只能开启一个
feign.okhttp.enabled=false
feign.httpclient.enabled=true
pom.xml
中需要添加对应依赖:
<!--feign okhttp-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
<!--feign httpclient-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
2.5、OpenFeign
多参数传递
POST方式传递对象使用@RequestBody注解描述参数
代码语言:javascript复制@PostMapping("/book/create")
String createBook(@RequestBody Book book);
GET
方式将对象转换为Map
后利用@RequestParam
注解描述
/**
* 需要使用map作为参数对象映射,这里不能使用Book 对象传参
*/
@GetMapping("/book/search")
List<Book> search(@RequestParam Map book);
2.6、完整示例
pom.xml中添加依赖
代码语言:javascript复制<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.15.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.codesofun</groupId>
<artifactId>member-service-openfeign</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>member-service-openfeign</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR6</spring-cloud.version>
</properties>
<dependencies>
<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-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<!--feign okhttp-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
<!--feign httpclient-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties中添加配置
代码语言:javascript复制server.port=9000
spring.application.name=member-service
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
#ribbon负载均衡策略,随机。默认是轮询模式
book-service.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
#全局日志级别
logging.level.root=info
#指定包的包下面的类输出的日志级别
logging.level.com.codesofun.memberservice.openfeign.service=debug
#feign通信日志设置
feign.client.config.default.logger-level=headers
#feign指定微服务id通信日志,以此为准,没有则以default设置为准
feign.client.config.book-service.logger-level=headers
#客户端向微服务发起连接的最长等待时间
feign.client.config.default.connect-timeout=15000
#连接后,等待响应返回的最长时间
feign.client.config.default.read-timeout=15000
feign.client.config.book-service.connect-timeout=5000
feign.client.config.book-service.read-timeout=5000
#底层通信开启 okhttp 的支持
feign.okhttp.enabled=false
feign.httpclient.enabled=true
debug=true
启动类中,添加注解@EnableFeignClients
代码语言:javascript复制package com.codesofun.memberservice.openfeign;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
//开启feign client注解
@EnableFeignClients
public class MemberServiceOpenfeignApplication {
@Bean
public IRule rule(){
return new RandomRule();
}
public static void main(String[] args) {
SpringApplication.run(MemberServiceOpenfeignApplication.class, args);
}
}
实体类Book
代码语言:javascript复制package com.codesofun.memberservice.openfeign.entity;
import lombok.Data;
/**
* @ClassName Book
* @Description
* @Author mozhijun
* @Date 2020/7/1 18:17
* @Version 1.0
**/
@Data
public class Book {
private String sn;
private String name;
private String desc;
}
业务接口类BookService
代码语言:javascript复制package com.codesofun.memberservice.openfeign.service;
import com.codesofun.memberservice.openfeign.entity.Book;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* @Author: 小莫
* @Date: 2020-07-06 13:29
* @Description TODO
*/
//http://book-service/book/{sn}
@FeignClient(value = "book-service")
public interface BookService {
@GetMapping("/book/{sn}")
Book findBySn(@PathVariable(value = "sn") String sn);
@PostMapping("/book/create")
String createBook(@RequestBody Book book);
/**
* 需要使用map作为参数对象映射,这里不能使用Book 对象传参
*/
@GetMapping("/book/search")
List<Book> search(@RequestParam Map book);
}
controller层
代码语言:javascript复制package com.codesofun.memberservice.openfeign.controller;
import com.codesofun.memberservice.openfeign.entity.Book;
import com.codesofun.memberservice.openfeign.service.BookService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @ClassName MemberServiceOpenfeignController
* @Description
* @Author mozhijun
* @Date 2020/7/6 13:35
* @Version 1.0
**/
@RestController
public class MemberServiceOpenfeignController {
@Resource
private BookService bookService;
@GetMapping("/borrow/{sn}")
public Book findBookBySN(@PathVariable("sn") String sn) {
return bookService.findBySn(sn);
}
@GetMapping("/compensate")
public String compensate() {
Book book = new Book();
book.setName("赔偿图书");
book.setSn("5555");
String result = bookService.createBook(book);
return result;
}
@GetMapping("/s")
public List<Book> search() {
Map<String,Object> param = new HashMap();
param.put("name", "x");
param.put("sn", "1111");
return bookService.search(param);
}
}
详细代码见仓库:https://gitee.com/xmlvhy/springcloud-learn
参考链接:https://www.itlaoqi.com/
本文作者: AI码真香
本文标题: SpringCloud入门系列之微服务之间的通信
本文网址: https://www.xmlvhy.com/article/89.html
版权说明: 自由转载-非商用-非衍生-保持署名 署名-非商业性使用4.0 国际 (CC BY-NC 4.0)