目录
Spring:从零开始的Cloud生活(零)——Eureka 服务治理
1.Netfilx Eurake
2.搭建服务注册中心
3.服务提供者
4.高可用注册中心
5.服务发现和消费
之前对于SpringCloud都是一知半解的状态,现在开始系统学习,边学边记,参考来源于《Spring Cloud微服务实战》,由于版本差异,通过各大论坛查漏补缺。
SpringCloud是一系列技术的集合,它像我们买台式电脑中的品牌机一样,模式化地整合了一系列技术供我们使用,高手可以自己选择、替换、配置电脑的组件,但是制式品牌机才是应用最为广泛的。
服务治理的主要功能就是用来实现微服务实例的自动化注册与发现,当服务之间互相调用的时候被调用方都需要一个具体的实例清单来告知调用方有哪些实例可供调用,如果系统功能比较简单,微服务模块的数量较少,还可以通过手工维护的方式来静态配置实例清单,但如果服务超过一定规模,集群的大小、服务的位置、服务命名等都有可能发生改变,再使用手工维护的方式极易发生命名冲突和各种调用错误,即使无错也会消耗大量的人力。
这里就涉及了两个概念:
-
服务注册:通常情况下,微服务的使用都会伴随一个技术——注册中心,每个服务单元都会在注册中心中注册自己的服务,将IP与端口号、版本号、通信协议等附加信息告知注册中心,注册中心会按服务名分类组织服务清单。服务中心会以监听心跳的方式去观察清单中的服务是否可用,若不可用就会剔除该服务,借此来排除故障。
-
服务发现:在服务治理框架中,两个服务之间的调用再通过指定实例地址来实现,所以调用方并不知道被调用方的具体实例地址,因此调用方需要向注册中心发起请求,获取所有服务的实例清单来完成对具体事例的访问。不过在实际使用中,由于诸多因素的限制,不会每次都向注册中心请求获得被调用方的实例地址,在不同场景中会采用不同的缓存及服务剔除机制。
举个小例子,现在有服务提供者A和B,A服务的进程运行在实例192.168.0.1:8200和192.168.0.2:8200上,B服务的进程运行在192.168.0.1:9200、192.168.0.2:9200和192.168.0.3:9200上,假设实例都在启动状态下,并向注册中心注册了自己的服务,那么注册中心就会维护下面这个清单:
服务名 |
位置 |
A |
192.168.0.1:8200 192.168.0.2:8200 |
B |
192.168.0.1:9200 192.168.0.2:9200 192.168.0.3:9200 |
假如服务C此时想调用服务A,就先向注册中心打报告,注册中心告诉服务C,服务A的可用实例地址为 192.168.0.1:8200 192.168.0.2:8200,然后服务C通过设置好的轮询规则选取一个来调用。
1.Netfilx Eurake
Netfilx Eurake是作为SpringCloud中的一部分来实现注册于发现,它既包含了客户端组件,也包含了服务端组件,同时二者都是使用Java语言编写,当然,Eurake提供了完备的RestfulAPI,其他语言也可兼容。
目前自己了解到的注册中心为zookeeper、consul、eurake,除此之外还有阿里的nacos,在选用配置中心的时候可以根据项目选择,这四者的主要区别如下表格:
表格来源:https://blog.csdn.net/fly910905/article/details/100023415
|
Nacos |
Eureka |
Consul |
Zookeeper |
一致性协议 |
CP+AP |
AP |
CP |
CP |
健康检查 |
TCP/HTTP/MYSQL/Client Beat |
Client Beat |
TCP/HTTP/gRPC/Cmd |
Keep Alive |
负载均衡策略 |
权重/ metadata/Selector |
Ribbon |
Fabio |
— |
雪崩保护 |
有 |
有 |
无 |
无 |
自动注销实例 |
支持 |
支持 |
支持 |
支持 |
访问协议 |
HTTP/DNS |
HTTP |
HTTP/DNS |
TCP |
监听支持 |
支持 |
支持 |
支持 |
支持 |
多数据中心 |
支持 |
支持 |
支持 |
不支持 |
跨注册中心同步 |
支持 |
不支持 |
支持 |
不支持 |
SpringCloud集成 |
支持 |
支持 |
支持 |
支持 |
Dubbo集成 |
支持 |
不支持 |
支持 |
支持 |
K8S集成 |
支持 |
不支持 |
支持 |
不支持 |
Eruka作为SpringCloud默认的注册中心,它更偏向于高可用配置,依托强一致性提供良好的服务实例可用性,其中包括自我保护模式、故障分片容灾处理等。比如Netfilx推荐每个可用区域运行一个Eurake服务端,以此形成集群,不同区域的注册中心通过异步来相互复制各自的状态,这就意味着每个不同时间点每个实例中关于所有服务的状态都是有细微差别的。
2.搭建服务注册中心
Eureka客户端通过注解和参数配置的方式嵌入于项目中,在项目运行期间,Eureka客户端在注册中心注册自身提供的服务并通过周期性地向注册中心发送心跳的方式来更新服务租约,同时它也能从服务端查询当前注册的服务信息并把信息缓存到本地且周期性地刷新服务状态。
我们先搭建一个简单的服务注册中心,首先创建好基础的SpringBoot的工程,并在pom.xml中引入必要依赖,需要注意的是,springboot和springcloud二者的版本一定要对应,不然会发生许多莫名其妙的错误,版本的大致对应关系如下,加粗为目前项目使用的版本:
spring-boot-starter-parent(Spring Boot) |
spring-cloud-dependencie |
1.2.x |
Angel版本 |
1.3.x |
Brixton版本 |
1.4.x |
Camden版本 |
1.5.2.RELEASE |
Dalston.RC1 |
1.5.2.RELEASE |
Dalston.RC1 |
1.5.16/20.RELEASE |
Edgware.RELEASE |
>=2.0.0.M3 and <2.0.0.M5 |
Finchley.M2 |
pring Boot >=2.0.0.M5 and <=2.0.0.M5 |
Finchley.M3 |
Spring Boot >=2.0.0.M6 and <=2.0.0.M6 |
Finchley.M4 |
Spring Boot >=2.0.0.M7 and <=2.0.0.M7 |
Finchley.M5 |
Spring Boot >=2.0.0.RC1 and <=2.0.0.RC1 |
Finchley.M6 |
Spring Boot >=2.0.0.RC2 and <=2.0.0.RC2 |
Finchley.M7 |
Spring Boot >=2.0.0.RELEASE and <=2.0.0.RELEASE |
Finchley.M9 |
Spring Boot >=2.0.1.RELEASE and <2.0.2.RELEASE |
Finchley.RC1 |
Spring Boot >=2.0.2.RELEASE and <2.0.3.RELEASE |
Finchley.RC2 |
Spring Boot >=2.0.3.RELEASE and <2.0.999.BUILD-SNAPSHOT |
Finchley.SR4 |
Spring Boot >=2.0.999.BUILD-SNAPSHOT and <2.1.0.M3 |
Finchley.BUILD-SNAPSHOT |
Spring Boot >=2.1.0.M3 and <2.1.0.RELEASE |
Greenwich.M1 |
Spring Boot >=2.1.0.RELEASE and <2.1.9.BUILD-SNAPSHOT |
Greenwich.SR2 |
Spring Boot >=2.1.9.BUILD-SNAPSHOT and <2.2.0.M4 |
Greenwich.BUILD-SNAPSHOT |
Spring Boot >=2.2.0.M4 and <=2.2.0.M5 |
Hoxton.M2 |
Spring Boot >=2.2.0.BUILD-SNAPSHOT |
Hoxton.BUILD-SNAPSHOT |
通过上图可知当前引用依赖为:
</dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
配置文件配置完毕,还需要在启动类上添加@EnableEurekaClient注解或者@EnableDiscoveryClient注解,这两个注解中@EnableEurekaClient只适用于Eureka注册中心,后者却可以适用于所有注册中心,可以根据具体环境选择。代码如下:
@EnableEurekaServer //开启eurake服务注册功能
@SpringBootApplication
public class EurekaDemoApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaDemoApplication.class, args);
}
}
默认情况下,负责充当注册中心的客户端也会尝试自己注册自己,所以需要禁用注册中心的客户端注册行为,在application配置文件中增加如下配置:
## 微服务端口号 ##
server:
port: 1011
## eureka相关配置 ##
eureka:
instance:
hostname: localhost #应用实例主机名
client:
register-with-eureka: false #是否向服务中心注册自己 默认:true
fetch-registry: false #是否从服务中心获取注册信息,默认:true
service-url:
#Eureka服务器的地址,类型为HashMap,默认的key为defaultZone;默认的Value为 http://localhost:8761/eureka
#如果服务注册中心为高可用集群时,多个注册中心地址以逗号分隔。
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
由于当前应用充当注册中心,所以既不用向注册中心注册自己,也不用调用其它服务,所以register-with-eureka和fetch-registry两个属性都为false。配置完毕后,启动应用,在浏览器地址栏输入localhost:1011,就可以查看注册中心的信息面板了:
这里可以看到Instances currently registered with Eureka一行是空的,说明还没有注册服务。
3.服务提供者
服务提供者如其名字那样,就是供其他服务调用的项目。上文已经搭建好了一个注册中心,并可以成功启动,那么首先自然是注册服务提供者的项目到上面,新建项目provider-demo并添加同注册中心一样的依赖。
项目配置文件如下:
## 微服务端口号 ##
server:
port: 1012
## 微服务名 ##
spring:
application:
name: yasuo-service
## eureka相关配置 ##
eureka:
client:
service-url:
defaultZone: http://localhost:1011/eureka/
其中,端口号是较为常规的配置,为了让提供者在注册中心可以更好地被识别,还要设置微服务名。
eureka.client.service-url.defaultZone=http://localhost:1011/eureka/ 则是说明注册中心所在地址,以供提供者注册。
配置文件配置完毕,为了激活Eureka中DiscoveryClient(服务发现客户端),还需要在启动类上添加@EnableEurekaClient注解或者@EnableDiscoveryClient注解,代码如下:
@EnableDiscoveryClient
@SpringBootApplication
public class ProviderDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderDemoApplication.class, args);
}
}
这些前置条件都完成后,就可以写一个简单的测试控制器来测试服务注册了,控制器代码如下:
@RestController
@Slf4j
public class HelloController {
@Autowired
private Registration registration;
@Autowired
private DiscoveryClient client;
@Value("${server.port}")
private String servicePort;
@GetMapping("/yasuo")
public String yasuo(){
//获取本地实例数据
ServiceInstance instance = getLocalServiceInstance();
log.info("/yasuo控制器的端口为{},服务id为{}",instance.getPort(),instance.getServiceId());
return "且听风吟,御剑于心";
}
/**
* 获取本地实例数据方法
* @return 本地实例信息对象
*/
private ServiceInstance getLocalServiceInstance(){
List<ServiceInstance> instances = client.getInstances(registration.getServiceId());
for (ServiceInstance instance : instances) {
if (instance.getPort() == Integer.parseInt(servicePort)) {
return instance;
}
}
return null;
}
}
编码完成后,先启动注册中心Eureka-demo,检查运行正常后再启动服务提供者provider-demo,打开localhost:1011,也就是注册中心页面,可以看到:
这说明我们的服务提供者已经把自己的实例信息注册到了注册中心。直接向服务提供者发起请求,在地址栏中输入localhost:1012/yasuo,可以看到页面和控制台分别有如下输出:
4.高可用注册中心
在分布式架构中,容错是十分重要的一个点,所以需要考虑到发生故障的情况,因此需要在生产环境中对各个组件进行高可用部署,eureka从设计的时候就考虑到了这个问题,任何服务都可以既是消费方,也是提供方,注册中心也是如此。
之前在配置文件中配置了注册中心不注册自己:
eureka:
client:
register-with-eureka: false #是否向服务中心注册自己 默认:true
fetch-registry: false #是否从服务中心获取注册信息,默认:true
为了实现高可用,注册中心就需要向其他注册中心注册自己,同时也需要获取相关注册信息,这两个配置就保持默认的true了。复制一份同样的注册中心eureka-demo2,eureka-demo和eureka-demo2的配置文件如下:
eureka-demo:
### 注册中心eureka-demo ###
## 微服务端口号 ##
server:
port: 1011
#服务名
spring:
application:
name: eureka-server1
## eureka相关配置 ##
eureka:
instance:
hostname: NO1 #应用实例主机名
client:
service-url:
#Eureka服务器的地址,类型为HashMap,默认的key为defaultZone;默认的Value为 http://localhost:8761/eureka
#如果服务注册中心为高可用集群时,多个注册中心地址以逗号分隔。
defaultZone: http://localhost:1021/eureka/
eureka-demo2:
### 注册中心eureka-demo2 ###
## 微服务端口号 ##
server:
port: 1021
#服务名
spring:
application:
name: eureka-server2
## eureka相关配置 ##
eureka:
instance:
hostname: NO2 #应用实例主机名
client:
service-url:
#Eureka服务器的地址,类型为HashMap,默认的key为defaultZone;默认的Value为 http://localhost:8761/eureka
#如果服务注册中心为高可用集群时,多个注册中心地址以逗号分隔。
defaultZone: http://localhost:1011/eureka/
然后修改服务提供者的配置文件,让服务提供者注册到两个注册中心上:
### 服务提供者 ###
## 微服务端口号 ##
server:
port: 1012
## 微服务名 ##
spring:
application:
name: yasuo-service
## eureka相关配置 ##
eureka:
client:
service-url:
defaultZone: http://localhost:1011/eureka/,http://localhost:1021/eureka/
这两个注册中心相互进行注册来实现服务清单的互相同步,来实现高可用的效果。假如需要搭建eureka集群的话,那么eureka.instance.hostname不能重复,否则集群失败,所以不能为localhost或者ip地址。在测试中我刚开始使用的是localhost去进行集群发现失败了。所以我们需要修改一下本地的hosts文件,位置在C:\Windows\System32\drivers\etc\hosts。
#eureka
127.0.0.1 NO1
127.0.0.1 NO2
将两个注册中心启动,启动有先后间隔,先启动的注册中心会报错,因为无法找到要注册的另一个注册中心,等待第二个注册中心启动后在周期内发出心跳就可以了,无需理会。启动完毕后再启动服务提供者并分别访问两个注册中心的主页。
当前三个服务都已经注册,且正常运行(处于UP状态),这样,当其中某个注册中心发生故障的时候,服务消费者依然可以从其他节点获取生产者的地址。如果提供者的配置文件中只注册了一个注册中心,那么其他注册中心也可以获取到提供者的地址,但仅限于提供者注册的注册中心正常运行的情况下。
5.服务发现和消费
前面说了注册中心的高可用模式和单节点模式,也通过修改配置让服务提供者注册到注册中心上,下面就要编码一个消费者来消费提供者的服务。消费者需要完成两个目标,服务发现与服务消费,服务发现通过Eureka的客户端完成,服务消费需要通过Ribbon来完成。
Ribbon其实是一个基于HTTP和TCP的负载均衡器,它可以通过在客户端配置的ribbonServerList(ribbon服务列表)进行轮询访问以达到负载均衡的作用。二者联合使用时,RibbonServerList会被DiscoveryEnableNIWSServerList重写,拓展成从Eureka注册中心获取服务列表,同时会使用NIWSDiscoveryPing替代IPing,Ribbon职责推给了Eureka来确定服务端是否启动,有关ribbon后面会继续详细学习,目前只需要知道Ribbon通过负载均衡策略在Eureka的基础上选择服务,从而实现消费服务。
新建一个消费者项目eureka-consumer,在提供者依赖的基础上新增ribbon相关依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--Eureka依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!--Ribbon 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<!--Cloud依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
在启动类上添加@EnableDiscoveryClient注解开启服务发现功能,并添加名为RestTemplate的Bean实例,添加@LoadBalanced注解开启负载均衡功能。
@EnableDiscoveryClient
@SpringBootApplication
public class ConsumerDemoApplication {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerDemoApplication.class, args);
}
}
构建一个消费者控制器并实现/ruiwen接口用于调用提供者控制器的/yasuo接口,同时配置项目端口、注册中心地址、项目名等信息:
//消费者控制器
@RestController
public class ConsumerController {
@Autowired
RestTemplate restTemplate;
@GetMapping("/ruiwen")
public String ruiwen(){
return restTemplate.getForEntity("http://yasuo-service/yasuo",String.class)
.getBody();
}
}
### 消费者配置文件 ###
## 微服务端口号 ##
server:
port: 1014
## 微服务名 ##
spring:
application:
name: ruiwen-service
## eureka相关配置 ##
eureka:
client:
service-url:
defaultZone: http://localhost:1011/eureka/
依次启动注册中心->两个提供者(笔记中端口号为1012、1013,为了测试负载均衡,服务名都为yasuo-service)->消费者,观察Eureka主页的变化:
提供者yasuo-service(两个)和消费者ruiwen-service都注册到注册中心了,访问一下localhost:1014/ruiwen发起多次请求,观察页面和控制台输出:
访问了六次,发现被调用方yasuo-service的两个服务各调用三次,说明ribbon当前是按照轮询调用提供者服务的。而消费者控制台也会输出调用信息(以调用提供者1013端口为例):
DynamicServerListLoadBalancer for client yasuo-service initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=yasuo-service,current list of Servers=[11.205.243.215:1013, 11.205.243.215:1012],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone; Instance count:2; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;]
},Server stats: [[Server:11.205.243.215:1012; Zone:defaultZone; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0]
, [Server:11.205.243.215:1013; Zone:defaultZone; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0]
]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@54cd75b0
这些信息是ribbon进行输出的,其中包含了实例位置,对各实例的请求总数量,第一次连接信息,上一次连接信息,总请求失败次数等。