「这是我参与2022首次更文挑战的第17天,活动详情查看:2022首次更文挑战」
SpringCloud 微服务分布式
本篇是结合上一篇代码进行修改学习 点击👍
Eureka的基本架构
上一篇学习了 Eureka的基本学习
- Eureka包含两个组件:
Eureka Server和Eureka Client - Eureka Server 注册中心对 客户端Client 提供: 服务注册与服务发现 功能 各个节点Client启动后,会在EurekaServer中进行注册 这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息;
(服务节点的信息可以在界面中直观的看到) - EurekaClient是一个Java客户端 为了简化与 Eureka Server的交互, 客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。 在应用启动后,会
定期向Eureka Server发送心跳(默认周期为30秒)。 如果没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)
Eureka Server 高可用集群
微服务在消费远程API时总是使用本地缓存中的数据。 因此一般来说,即使Eureka Server发生宕机,也不会影响到服务之间的调用。
-
但如果EurekaServer宕机时,某些微服务也出现了不可用的情况
-
Eureka Server中的缓存若不被刷新,就可能会影响到微服务的调用,甚至影响到整个应用系统的
高可用。 -
因此,在生成环境中,通常会部署一个高可用的Eureka Server集群。
-
好处: 搭建 Eureka集群, 开发应用过程中如果
一个Eureka注册中心不幸发生了宕机。 这个时候就算, 某个Eureka Client 发生了更改/停止... 也会被其它注册中心管理记录...使应用高可用 -
Eureka Server可以通过运行多个实例并
相互注册的方式实现 集群 高可用部署 -
Eureka Server实例会彼此增量地同步信息, 从而确保所有节点数据一致。 节点之间相互注册是Eureka Server的默认行为。
(上图:三个注册中心之间的集群操作...注册中心之间数据同步, 共同管理 provider 和 comsumer
一个挂了也不会影响操作..)
搭建 Eureka Server高可用集群
复制以前写的 eureka-server注册中心
为eureka-server2 为了方便操作之间copy
- 修改名字...
- 修改pom.xml 中的项目名
eureka-server2 - 主Maven工程
pom.xml中添加依赖~
这个学过Maven的都清除不过多解释...Maven主pom.xml
<!-- Maven父工程包含管理的子工程... -->
<modules>
<module>common_api</module>
<module>common_orderService</module>
<module>common_userService</module>
<module>eureka-server</module>
<module>eureka-server2</module> <!-- 手动添加刷新Maven以将copy的工程添加至项目中... -->
<module>import-demo</module>
</modules>
ok,到这里大致就又 搭建好了一个注册中心了
修改注册中心的端口/配置:
接下来就是配置一下注册中心了.... eureka-server2 的.yml
server:
port: 7002 #修改...注册中心的端口; (要知道端口是唯一的...)
spring:
application:
name: eureka-server #注册中心应用名称,这里不需要修改...
#配置注册中心....
eureka:
client:
service-url: #注册中心对外暴漏的注册地址... 逗号分隔,不要有空格!!
defaultZone: http://localhost:7002/eureka/,http://localhost:7001/eureka/
fetch-registry: false
register-with-eureka: false
eureka-server 的.yml
server:
port: 7001 #注册中心的端口;
spring:
application:
name: eureka-server #注册中心应用名称,这里不需要修改...
#配置注册中心....
eureka:
client:
service-url: #注册中心对外暴漏的注册地址...
defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/
fetch-registry: false
register-with-eureka: false
测试
启动eureka-server 注册中心 启动common_orderService 订单模块 | common_userService用户模块 启动eureka-server2 注册中心
关闭7001 注册中心,测试并不会影响使用...
注意:
- 因为关闭了注册中心, Eureka的客户端,会检测
一个注册中心宕机而出异常! 但并不会影响使用!
ok,Eureka生效!
Eureka集群总结:
-
创建两个除了端口
port外,基本一致的注册中心 -
给其
.yml配置中两个注册中心相互依赖: ==defaultZone: http://localhost:7002/eureka/,http://localhost:7001/eureka/==Eureka源码解析
SpringBoot中的自动装载
ImportSelector接口是Spring导入外部配置的核心接口
在SpringBoot的自动化配置和@EnableXXX(功能性注解)中起到了决定性的作用。
首先ImportSelector 是一个接口 作用:
- 当在@Configuration标注的Class上
- 使用@Import引入了一个ImportSelector实现类后
- 会把实现类中, 方法返回的所有 Class名称都定义为bean。
public interface ImportSelector {
//实现接口需要重写方法, 返回值是一个数组... 存放 class类名的数据;
//在一个@Configuration类中使用@Import引入该接口的实现类;
//会将实现类,实现方法的返回值所有的 class名都定义为Bean...
String[] selectImports(AnnotationMetadata var1);
}
DeferredImportSelector 接口和 ImportSelector
DeferredImportSelector 是 ImportSelector的子接口... DeferredImportSelector.Java接口
public interface DeferredImportSelector extends ImportSelector {
....
}
查看:
- 进入 @SpringBootApplication——进入——@EnableAutoConfiguration
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class}) //进入AutoConfigurationImportSelector.class
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
进入——AutoConfigurationImportSelector.class DeferredImportSelector作用:
DeferredImportSelector接口继承ImportSelector- 它和ImportSelector的区别在于装载bean的时机上 DeferredImportSelector需要等所有的@Configuration都执行完毕后才会进行装载...
比ImportSelector慢一定...
使用ImportSelector 自定义一个 @Eablexx 启动注解:
实现:@Eabelexxx启动类注解,当使用注解才可以在程序中使用这个类, 不@Eable启动则不能使用类即方法()...
这次加入一个模块 import_Eable
com.wsm.entity包下创建实体:
定义Bean对象User.Java
public class User {
String name;
String age;
//省略get/set/toString....
}
com.wsm.config 配置包下开始实现代码:
定义配置类Configuration类UserConfig.Java
//类并没有通过@Configuration 声明一个配置类,但内部已经使用@Bean 进行加载注入;
public class UserConfig {
@Bean
public User createUser() {
User u = new User();
u.setName("张三");
u.setAge("12");
return u;
}
}
//虽然不会立刻加载至Spring容器中,但后面类通过 ImportSelector 来进行统一加载...
定义ImportSelectorUserImportSelector.Java
//UserImportSelector 实现 ImportSelector接口,重写方法...
public class UserImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{UserConfig.class.getName()}; //返回一个String数组,里存放着要加载的类名..
}
//方法返回值 字符数组[] 中存放着要通过 @import来加载实现的类...
}
自定义创建开启注解: EnableUser.Java
@Retention(RetentionPolicy.RUNTIME) //类加载时候完成类执行
@Documented
@Target(ElementType.TYPE)
//-------以上都是Java注解
@Import(UserImportSelector.class) //通过import(UserImportSelector.Class) 引入实现类..
public @interface EnableUser {
//使用 @interface 自定义一个注解: @EnableUser;
//它是一个绑定注解内部包含一个: @Import(UserImportSelector.class)
//使用 @EnableUser 就等于使用了 @Import(UserImportSelector.class)!!!
}
ok !.项目Maven发布:install:内部包含清理 打包 部署 测试 编译...操作;
测试:
已经打好了 Jar包并发布至 Maven的本地仓库了... 那么现在直接:项目引用就可以使用注解了... 本人使用的是一个注册中心反之不常用而且 订单和用户模块中已经有了一个User类为了避免冲突方便测试...
操作开始:
- eureka-server2 模块的pom.xml 引入 import_Eable 模块不用多说了....
- 刷新Maven到主程序类中使用 @EableUser注解
MyEurekaServer2 .Java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
import org.springframework.context.ConfigurableApplicationContext;
import wsm.config.EnableUser; //这是刚才自定义的注解!!通过它就可以启动User类的加载了...
import wsm.entity.User;
@SpringBootApplication //而且要知道@SpringBootApplication注解下有一个@SpringBootConfiguration
@EnableEurekaServer //注解主程序开启 Eureka服务
@EnableUser //引入自定义的注解即可完成下面 User
public class MyEurekaServer2 {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(MyEurekaServer2.class, args);
User u = (User) run.getBean("uu"); //根据 @Bean("uu") 以加载至Spring容器; 通过@EableUser实现!
System.out.println(u);
}
}
把@Eable注解注释掉,在运行就报错了!!这个注解真强! 这就相当于一个类的开关, 需要的时候打开,不用的时候关闭! 那么
@EnableEurekaServer 的自动装配大致如此.... (后面单独抽出一篇来详解吧...过年更!)
Eureka替换方案Consul
Eureka闭源影响
在Euraka的GitHub上,宣布Eureka 2.x闭源。 近这意味着如果开发者继续使用作为 2.x 分支上现有工作repo 一部分发布的代码库和工件,则将自负风险。 唉,就比较可惜所以就需要找新的注册中心
consul 概述
consul是近几年比较流行的服务发现工具,工作中用到,简单了解一下。 consul的三个主要应用场景:服务发现、服务隔离、服务配置。
- Consul 是 HashiCorp 公司推出的开源工具 用于实现分布式系统的服务发现与配置。
- 与其它分布式服务注册与发现的方案,Consul 的方案更“一站式”
- 内置了服务注册与发现框 架、分布一致性协议实现、健康检查、Key/Value 存储、多数据中心方案
- 不再需要依赖其它工具(比如 ZooKeeper 等)。使用起来也较 为简单。
- Consul 使用 Go 语言编写,因此具有天然可移植性(支持Linux、windows和Mac OS X);
安装包仅包含一个可执行文件,方便部署,与 Docker 等轻量级容器可无缝配合。
Consul 的优势:
- 使用 Raft 算法来保证一致性, 比复杂的 Paxos 算法更直接 相比较而言zookeeper采用的是Paxos, 而etcd使用的则是 Raft。
- 支持多数据中心,内外网的服务采用不同的端口进行监听。 多数据中心集群可以避免单数据中心的单点故障,而其部署则需要考虑网络延迟, 分片等情况等。
zookeeper 和 etcd 均不提供多数据中心功能的支持。 - 支持健康检查。 etcd 不提供此功能。
- 支持 http 和 dns 协议接口。 zookeeper 的集成较为复杂, etcd 只支持 http 协议。
- 官方提供 web 管理界面 etcd 无此功能。
综合比较, Consul 作为服务注册和配置管理的新星, 比较值得关注和研究
consul与Eureka的区别
- 一致性
CAPConsul强一致性(CP) Eureka保证高可用和 最终一致性(AP) - 开发语言和使用 eureka就是个servlet程序,跑在servlet容器中
SpringBoot内部集成Tomcat..Consul则是go编写而成,安装启动即可使用前启动一个启动程序consul.exe
CAP原则又称CAP定理
- 指的是在一个分布式系统中,一致性
Consistency、可用性Availability、分区容错性Partition tolerance - CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。
- 一致性C 在分布式系统中的所有数据备份,在同一时刻是否同样的值。 注册中心中表现就是:
集群注册中心环境下, 一个客户端服务发送更改, 注册到注册中心,必须, 所有的注册中心同步了才能继续进行请求... - 可用性A 保证每个请求不管成功或者失败都有响应。
最终一致性注册中心中表现就是:集群注册中心环境下, 一个客户端服务发送更改, 注册到注册中心,不是必须, 所有的注册中心同步了才能继续进行操作...它会继续当前的操作,后台默默的做同步操作 - 分区容忍性p 系统中任意信息的丢失或失败不会影响系统的继续运作。
p 就表示同步过程中的 网络通信协议...
什么是高可用?
- 高可用,简称 HA
- 是系统一种特征或者指标,通常是指,提供一定性能上的服务运行时间,高于平均正常时间段。
consul的下载与安装
上面说了需要一个客户端执行程序启动:consul.exe
cmd下 指定 客户端执行程序目录下:
consul.exe agent -dev -client=0.0.0.0本地启动访问:
localhost:8500通过get请求到
http://127.0.0.1:8500/v1/catalog/services查看所有的服务列表 通过get请求到http://127.0.0.1:8500/v1/catalog/service/服务名查看具体的服务详情
consul的基本使用
- Consul 支持健康检查,并提供了 HTTP 和 DNS 调用的API接口完成服务注册
- 服务发现,以及K/V存储这些功能。接下来通过发送HTTP请求的形式来了解一下Consul
后续了解..服务注册与发现
可以去百度一下这都是啥值...通过postman发送put请求到http://127.0.0.1:8500/v1/catalog/register地址可以完成服务注册: { "Datacenter": "dc1", "Node": "node01", "Address": "192.168.74.102", "Service": { "ID":"mysql-01", "Service": "mysql", "tags": ["master","v1"], "Address": "192.168.74.102", "Port": 3306 }![]()
基于consul的服务注册
随便找一个微服务模块进行修改, 首先将这个模块之前的 Eureka的配置全部注释掉!! 因为我们要使用 consul做注册中心...
consul注册中心通过 应用程序直接启动!!
修改微服务 common_orderService 订单模块的相关pom文件:
将eureka的依赖pom.xml和配置.yml去掉, 使用consul注册中心
添加SpringCloud提供的基于Consul的依赖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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>SpringCloudBJ</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>common_orderService</artifactId>
<!-- 关键!! -->
<dependencies>
<!-- common_orderService工程引入common_api -->
<dependency>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
<artifactId>common_api</artifactId>
</dependency>
<!-- 直接注释掉: Eureka-server服务依赖 -->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>-->
<!-- </dependency>-->
<!-- 基于consul的服务注册篇~ -->
<!--SpringCloud提供的基于Consul的服务发现-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!--actuator用于心跳检查-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>
配置服务注册 .yml
server:
port: 6001 #设置端口6001 当有多个微服务时候注意端口号可别冲突了..
spring:
application:
name: order-server #设置当然微服务名,后面的 注册/调用服务,都是根据这个来的;
#consul的相关配置;
cloud:
consul: #server.cloud中添加: consul的相关配置;
host: 127.0.0.1 #表示Consul的Server的请求地址
port: 8500 #表示Consul的Server的端口
discovery: #服务注册与发现的相关配置
register: true
instance-id: ${spring.application.name}-${server.port} #实例的唯一id (推荐必填,cloud官网推荐,为了保证生成唯一的id可以使用: ${spring.application.name}-${server.port}
service-name: ${spring.application.name}
port: ${server.port}
ip-address: ${spring.cloud.client.ip-address} #当前微服务的请求ip
prefer-ip-address: true #开启ip地址注册 就是展示ip
#原Eureka的注册服务配置...
#eureka:
# client:
# service-url:
# defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/ #指定服务注册的注册中心;
# instance:
# prefer-ip-address: true #在Eureka上显示ip号..
# instance-id: ${spring.cloud.client.ip-address}:${server.port} #在Eureka上显示ip号..
# lease-renewal-interval-in-seconds: 5 #客户端向注册中心发送心跳时间
# lease-expiration-duration-in-seconds: 10 #如果没有发送心跳的延迟续约时间...
修改 Service/Client consul注册中心
将 common_orderService订单模块主程序注解修改 MyOrderServer.Java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
//import org.springframework.cloud.netflix.eureka.EnableEurekaClient; //注释 Eureka的引用
@SpringBootApplication
//@EnableEurekaClient //Eureka专用注解启动 客户端注册~
@EnableDiscoveryClient //通用注册中心注解~
public class MyOrderServer {
public static void main(String[] args) {
SpringApplication.run(MyOrderServer.class, args);
}
}
- 将@EnableEurekaServer
Eureka专用注册注解更换为 @EnableDiscoveryClient通用注册中心注解 - 启动主程序:
别忘了启动Consul客户端不然会报错!启动不起来~ - @EnableDiscoveryClient 这是一个万能的注解, 客户端 服务端都得用这个注解, 甚至你注册中心是Eureka也可以用这个!
使用Consul 注册中心总结:
- 如果之前是其它注册中心的 微服务;
将其它的注册中心代码清理干净 - 本地事先启动好
Consul注册中心 - 导入Consul依赖
pom.xml - 配置好
.yml文件 - 使用通用启动注册服务注解:
@EnableDiscoveryClient刷新 页面,可以看到微服务以
注册至Consul
Ribbon 客户端负载均衡
学习前先将上面的 cousul注册中心切换为 Eureka注册中心来讲解: Eureka内部集成Reibbon
什么是负载均衡
- Load balancing,即负载均衡
是一种计算机技术 - 用来在多个计算机(计算机集群)、网络连接、CPU、磁盘驱动器或其他资源中分配负载
- 以达到最优化资源使用、最大化吞吐率、最小化响应时间、同时避免过载的目的。
为什么需要负载均衡
- 我们在日常生活中经常免不了要去一些比较拥挤的地方,比如地铁站、火车站、电影院、银行等。
- 无论是买票,还是排队入场,这些场所一般都会设置多个服务点或者入口的。 如果没有人引导的话,大多数情况下,最近的入口会挤满人。 而哪些距离较远的服务点或者入口就宽松很多。这种情况下,就会大大浪费资源
- 因为如果可以把这些排队的人很好的分散到各个入口的话会大大缩短排队时间。
- 其实,网站的建设也是一样的。 为了提升网站的服务能力,很多网站采用集群部署,就像话剧院有多个入口一样。 这时候,就需要一个协调者,来均衡的分配这些用户的请求,可以让用户的可以均匀的分派到不同的服务器上。
什么是Ribbon?
- Ribbon是网飞(Netflix)发布的负载均衡器 有助于控制HTTP和TCP客户端行为。
- 在SpringCloud中,Eureka一般配合Ribbon进行使用, Ribbon提供了客户端负载均衡功能,Ribbon利用从Eureka中读取到的服务信息,
- 在调用服务节点提供服务时,会合理的进行负载。
是客户端的负载均衡,不是服务器端。
Ribbon的主要作用
- 服务调用 给予Ribbon实现服务调用 时通过拉渠道的所有服务列表组成(服务名-请求路径的)映射关系。 借助RestTemplate最终进行调用
- 负载均衡 当有多个服务提供者时,Ribbon可以根据负载均衡的算法自动选择要调用的服务地址
构建
由于Eureka内部集成了Ribbon所以无需额外的依赖加载。
在创建RestTemplate的时候,声明@LoadBalanced- 使用restTemplate调用远程微服务:
不需要拼接微服务的URL,以待请求的服务名替换IP地址!!
直接copy一个订单模块 common_orderService2
提供者provide 两个一模一样的订单模块做负载均衡~
- 修改pom.xml 项目名
- 父pom.xml 引入使Maven连接起来~
和上面两个注册中心一样~ 修改端口号虽然服务功能相同但是, port端口可是唯一的common_orderService1 6001 common_orderService2 60036002被用户模块用了...
区分两个订单模块~
ok,两个几乎一模一样的订单模块搞完了之后(模块名和端口不一样)... 为了区分可以这样~↓↓ 两个订单模块都要修改代码都是一样的~,这里就展示一个了~ 展示:common_orderService OrderService.Java
@Service
public class OrderService {
//模拟假订单集合...
private static List<Order> orders = new ArrayList<>();
//类加载时候默认获取一些数据..模拟假订单..
static {
orders.add(new Order(1, "订单11", 10.0, 1));
orders.add(new Order(2, "订单21", 10.0, 2));
orders.add(new Order(3, "订单31", 10.0, 1));
orders.add(new Order(4, "订单41", 10.0, 2));
}
//获取主机号ip post------------------------------------------------------------------负载均衡~
//@Value("可以通过${} 来获取 属性文件中的值..并赋值到对象上~")
@Value("${spring.cloud.client.ip-address}")
private String ip;
@Value("${server.port}")
private String post;
//根据输入用户id获取当然用订单集合..
public List<Order> findOrderByUser(Integer uid) {
//为了区分负载均衡的调用~打印 ip 和 post----------------------------------------------负载均衡~
System.out.println(ip+":"+post);
List<Order> myorders = new ArrayList<>();
//遍历集合从中获取 uid一致的数据存放在集合中返回...
for (Order order : orders) {
if (order.getUid() == uid) {
myorders.add(order);
}
}
return myorders;
}
}
分别启动 注册中心 和 两个订单微服务模块~
修改调用者 Consumer 的调用方式...
修改调用者 用户模块 RestTemplate 负载均衡是配置在调用者的
对RestTemplate类的实例添加 @LoadBalanced MyUserServer.Java
@SpringBootApplication
@EnableEurekaClient
public class MyUserServer {
public static void main(String[] args) {
SpringApplication.run(MyUserServer.class, args);
}
@LoadBalanced //给RestTemplate类加上 @LoadBalanced 注解,及通过给RestTemplate来调用服务默认采用------负载均衡来使用!
@Bean
//实例化 RestTemplate 方便Service
//可别忘了@SpringBootApplication复合注解底层可以有@SpringBootConfiguration 它也是一个Spring的配置类!
public RestTemplate createRestTemplate() {
return new RestTemplate();
}
}
更改 RestTemplate 的调用方式
UserService.Java ** 17 ~ 18行的负载均衡调用方式~**
@Service
@Slf4j //加载lo4g使用...
public class UserService {
@Autowired
private RestTemplate restTemplate;
//通过restTemplate进行网络通信,返回..其它远程模块的数据(虽然现在都是本地的不过就是模拟了..)
@Autowired //实现动态调用...
private DiscoveryClient discoveryClient;
public List<Order> currentUserOrder(Integer uid) {
//获取注册中心上的微服模块实例 根据服务名;
//返回一个集合: 有可能这个服务在多个注册中心上存在,`负载均衡~` 所以是一个集合;
List<ServiceInstance> instances = discoveryClient.getInstances("order-server");
ServiceInstance serviceInstance = instances.get(0); //而本次只有一个...
log.info("用户服务调用订单服务");
//ribbon的负载均衡调用-----------------------------------------------------------------
//应为你现在已经有两个相同的 order-server 的微服务了,还使用ip+端口是固定的了,要通过负载均衡来完成动态调用~所以直接使用模块服务名来表示微服!
String myurl = "http://order-server/findOrderByUser/" + uid;
//动态调用服务 服务的host 服务端口号 这个就是服务controller请求 给其参数uid
//String myurl = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/findOrderByUser/" + uid;
log.info(myurl);
List<Order> list = restTemplate.getForObject(myurl, List.class);
return list;
}
}
测试
连续刷新用户模块查看 订单模块被调用的结果...分布被调用了三次 负载均衡 默认采用:
轮循负载均衡..及轮流一人跑一次...
总结:
- 负载均衡配置在
Consumer调用方 实现对相同 服务名功能模块的, 负载均衡调用~ - 给调用方的 RestTemplate对象添加 @LoadBalanced注解以负载均衡形式进行调用~
- 修改Service层的, 调用方式采用
http://服务名/请求名/{参数}/{参..}形式进行 负载均衡调用;
修改负载均衡配置
对负载均衡的相关配置,默认使用轮询 前缀:com.netflix.loadbalancer.
- BestAvailableRule 选择一个最小的并发请求的server
- AvailabilityFilteringRule 过滤掉那些因为一直连接失败的被标记为circuit tripped的后端server, 并过滤掉那些高并发的的后端server(active connections 超过配置的阈值)
- WeightedResponseTimeRule 根据响应时间分配一个weight,响应时间越长,weight越小,被选中的可能性越低。
- RetryRule 对选定的负载均衡策略机上重试机制。
- RoundRobinRule roundRobin方式轮询选择server (默认)
- RandomRule 随机选择一个server
- ZoneAvoidanceRule 复合判断server所在区域的性能和server的可用性选择server
.yml
在调用者模块下配置 负载均衡调用的方式:那么就是在用户模块下配置订单模块的负载均衡配置
order-server: #这里的order-server是我服务的名称...目前的我的订单模块~
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #修改负载均衡的策略 随机方法服务
因为是服务的名称并没有代码提醒:建议之间copy
支持重试微服
当负载均衡的模块中一个 宕机了,Ribbon检测到并尝试重启服务 如果服务, 实在起不来了...就不在重试~
同样这个是在服务,调用者 模块声明的 User用户模块
引入依赖 pom.xml
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
.yml 配置
.yml
order-server:
ribbon:
ConnectTimeout: 250 #连接超时时间
ReadTimeout: 1000 #数据读取超时时间
OkToRetryOnAllOperations: true #是否对所有操作进行重试
MaxAutoRetries: 1 #对当前实例的重试次数
MaxAutoRetriesNextServer: 1 #切换实例的重试次数
Feign 服务调用
目前为止我们已经了解了SpringBoot的三种微服务调用方式: Consumer——调用——Provide三种方式:
- RestTemplate硬编码形式服务调用
- DiscoveryClient 实现动态调用... List instances = discoveryClient.getInstances("模块服务名");
根据模块服务名返回一个模块集合ServiceInstance serviceInstance = instances.get(0);获取某一个模块;//动态调用服务 String myurl = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/请求名/" + 参数~; serviceInstance.getHost()服务的hostserviceInstance.getPort()服务端口号.... - 被 @LoadBalanced 注解声明过的 RestTemplate 采用负载均衡形式进行 服务调用
String myurl = "http://多个的提供者provide模块/请求名/"
根据不同工作需求,采用不同的服务调用方式....下面介绍 Fegin的方式
Feign简介
面向接口开发~
Feign是Netflflix开发的声明式模板化的HTTP客户端
- 其灵感来自Retrofifit,JAXRS-2.0以及WebSocket. Feign可帮助我们更加便捷,优雅的调用HTTP API
- Feign支持多种注解,例如Feign自带的注解或者JAX-RS注解等。
- SpringCloud对Feign进行了增强 使Feign支持了SpringMVC注解,并整合了
Ribbon和Eureka, 从而让Feign的使用更加方便。 - 在SpringCloud中,使用Feign非常简单, 创建一个接口 并在接口上添加一些注解,代码就完成了;
在Eureka中Consumer还可以像方法调用一样调用Provider的接口非常容易,也非常快速。可以有效的减少代码量。
构建开始
创建一个用户fegin模块:common_userService_fegin
实现采用fegin的调用方式,调用 订单模块(provide)
添加 fegin 依赖组件:pom.xml
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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>SpringCloudBJ</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>common_userService_fegin</artifactId>
<dependencies>
<!-- Maven子工程api实体 -->
<dependency>
<artifactId>common-api</artifactId>
<groupId>com.zb</groupId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- Eureka依赖~ -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- fegin依赖!! -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
</project>
添加 fegin 的 .yam 配置
这里的配置就很正常~端口 服务名 Eureka配置中心管理... 为了防止出错就copy了,上面已经有很多重复的东西了.... application.yml
server:
port: 6004 #更改端口 6004
spring:
application:
name: user-server—fegin #设置当然微服务名,后面的 注册/调用服务,都是根据这个来的;
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port} #在Eureka上显示ip号..
- SpringBoot 的Tomcat默认端口是8080
- 因为端口的唯一的, SpringCloud又是一个微服务架构每个服务一个所以要频繁的更换端口
如果某个服务起不起来. 就先排查端口问题~
创建主程序类:
通过@EnableFeignClients注解开启Spring Cloud Feign的支持功能 MyUserFeginServer.java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients //启动类添加Feign的支持
public class MyUserFeginServer {
public static void main(String[] args) {
SpringApplication.run(MyUserFeginServer.class, args);
}
}
com.wsm.client 接口包:
基于面向接口编程...
创建一个接口,此接口是在Feign中调用微服务的核心接口
- 接口上声明
@FeignClient("指定需要调用的微服务提供者 服务名称") - 之后类中声明一个和 提供者服务中, 要调用的相同的方法, 包括参数.
fegin就会底层自动进行匹配映射调用~
OrderFeginClient.Java
import com.zb.entity.Order;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.List;
//@FeignClient指定需要调用的微服务名称
@FeignClient("order-server")
public interface OrderFeginClient {
//调用的请求路径,要注意这里尽量要和 提供者的实现方法一模一样包括参数,参数类型!!
@GetMapping("/findOrderByUser/{uid}")
public List<Order> findOrderByUser(@PathVariable("uid") Integer uid);
}
之后就是正常的 service 和 controller的实现了...
UserService.Java
import com.wsm.client.OrderFeginClient;
import com.zb.entity.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
@Autowired
private OrderFeginClient ofc; //获取接口对象,@feginClient 会自动将类注入Spring
public List<Order> currentUserOrder(Integer uid){
return ofc.findOrderByUser(uid); //调用接口的方法,fegin底层会默认调用,对于服务的方法...
}
}
UserController.Java 不详细解释了...
@RestController
public class UserController {
@Autowired
private UserService us;
@GetMapping("/showUserOrder")
public List<Order> showUserOrder(Integer uid){
return us.currentUserOrder(uid);
}
}
测试:
ok, 实现调用!
fegin服务调用总结:
- 导入依赖pom.xml
- fegin的.yml 并没有什么必须的配置...
- 创建主程序类的时候, 要添加
@EnableFeignClients启动类添加Feign的支持 - fegin是面向接口编程: 创建一个接口类:
类上声明@FeignClient("指定提供者模块名")并且:接口中方法,注意⚠️:要与Provider提供者的控制器一致!!注解 参数 参数类型!
Feign负载均衡
Feign中本身已经集成了Ribbon依赖和自动配置
- 因此我们不需要额外引入依赖,也不需要再注册 RestTemplate 对象。自动实现负载均衡~
- 这个确实厉害, 自动的就实现的
负载均衡~默认论循~ - 另外,我们直接去配置Ribbon 可以通过 ribbon.xx 来进 行全局配置。也可以通过 服务名.ribbon.xx 来对指定服务配置
Feign 的 .yml设置
对于电脑性能较慢的朋友, Fegin默认调用时间 2秒; 如果超时了就不会在执行请求...为了解决这个问题 延长请求响应时间:
.yml 设置超时时间默认 10s
feign:
httpclient:
connection-timeout: 10000
client:
config:
default: # 指定feignclients对应的名称 如果指定的是default 表示全局所有的client 的超时时间设置
connectTimeout: 10000
readTimeout: 10000
loggerLevel: basic
对于Fegin 的接口上需要 @FeignClient("指定提供者模块名") 指定调用模块 如果多次接口上使用同一个 @FeginClient("多个接口声明相同模块名") 模块. 会报错:
.yml 需要把 spring.main.allow-bean-definition-overriding属性设置为 true
spring:
main:
allow-bean-definition-overriding: true
可以避免这个问题!!