微服务之间的通信
微服务之间消息通信调用有两种方式:
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
依赖<?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
添加配置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
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
依赖<?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
添加配置spring.application.name=book-service eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
-
启动类上添加注解
@EnableEurekaClient
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
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
层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
依赖<?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
配置文件修改server.port=9000 spring.application.name=member-service eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
-
启动类上,添加注解
@EnableEurekaClient
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
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
层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
@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
注解描述参数@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
中添加依赖<?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
中添加配置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
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
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
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
层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)
类似文章
| 0 评论
还没有评论,快来抢沙发吧!