服务治理SpringCloud Eureka
什么是服务治理
在传统rpc远程调用中,服务与服务依赖关系,管理比较复杂,所以需要使用服务治理,管理服务与服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。
服务注册与发现
在服务注册与发现中,有一个注册中心,当服务器启动的时候,会把当前自己服务器的信息 比如 服务地址通讯地址等以别名方式注册到注册中心上。
另一方(消费者|服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后在实现本地rpc调用远程。
搭建注册中心
1、pom文件引入
<!-- 管理依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.M7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--SpringCloud eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<!-- 注意: 这里必须要添加, 否者各种依赖有问题 -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
2、appliaction.yml配置文件
###eureka 服务端口号
server:
port: 8100
###服务注册名称
eureka:
instance:
###注册中心ip地址
hostname: 127.0.0.1
###客户端调用地址
client:
serviceUrl:
###注册地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
###因为该应用为注册中心,不会注册自己(集群是需要为true)
register-with-eureka: false
###因为自己为注册中心 ,不会去在该应用中的检测服务
fetch-registry: false
3、启动项
@SpringBootApplication
//开启EurekaServer服务
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
4、访问Eureka注册中心
注册服务提供者
1、pom文件引入
<!-- 管理依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.M7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- SpringBoot整合eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<!-- 注意: 这里必须要添加, 否者各种依赖有问题 -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
2、配置文件
###会员项目的端口号
server:
port: 8000
###服务名称(服务注册到eureka名称)
spring:
application:
name: app-springCloud-member
###服务注册到eureka地址
eureka:
client:
service-url:
###当前服务注册到Eureka服务地址
defaultZone: http://localhost:8100/eureka
###需要将会员服务注册到Eureka服务中
register-with-eureka: true
###需要检索服务
fetch-registry: true
3、启动项
@SpringBootApplication
//将当前服务注册到Eureka上
@EnableEurekaClient
public class AppMember {
public static void main(String[] args) {
SpringApplication.run(AppMember.class,args);
}
}
4、后端相关代码
@RestController
public class MemberApiController {
@Value("${server.port}")
private String serverPort;
@RequestMapping("/getMember")
public String getMember(){
return "欢迎来到会员服务:端口号"+serverPort;
}
}
5、访问Eureka服务
注册服务消费者
1、pom文件引入
<!-- 管理依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.M7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- SpringBoot整合eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<!-- 注意: 这里必须要添加, 否者各种依赖有问题 -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
2、配置文件
###服务启动端口号
server:
port: 8001
###服务名称(服务注册到eureka名称)
spring:
application:
name: app-springCloud-order
###服务注册到eureka地址
eureka:
client:
service-url:
###当前服务注册到Eureka服务地址
defaultZone: http://localhost:8100/eureka
###需要将会员服务注册到Eureka服务中
register-with-eureka: true
###需要检索服务
fetch-registry: true
3、启动项
@SpringBootApplication
//将当前服务注册到Eureka上
@EnableEurekaClient
public class AppOrder {
public static void main(String[] args) {
SpringApplication.run(AppOrder.class,args);
}
//Spring默认没有对RestTemplate进行管理,
// 所以直接使用@Autowired进入不到Springboot容器中
// 需要手动将RestTemplate注入,通过@Bean注解的方式
@Bean
// 如果使用rest方式以别名方式进行调用依赖ribbon负载均衡器 @LoadBalanced
// @LoadBalanced就能让这个RestTemplate在请求时拥有客户端负载均衡的能力
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
}
4、后端相关代码
@RestController
public class OrderApiController {
// RestTemplate 是有SpringBoot Web组件提供 默认整合ribbon负载均衡器
// rest方式底层是采用httpclient技术
@Autowired
private RestTemplate restTemplate;
// 订单服务调用会员服务
@RequestMapping("/getOrder")
public String getOrder() {
// 有两种方式,一种是采用服务别名方式调用,
// 另一种是直接调用 使用别名去注册中心上获取对应的服务调用地址
String url = "http://app-springCloud-member/getMember";
String result = restTemplate.getForObject(url, String.class);
System.out.println("订单服务调用会员服务result:" + result);
return result;
}
}
5、消费者访问服务者
服务者集群
只需要修改服务者的配置文件中的端口号即可,将端口号由原来的8000改为8100,再启动新的服务(注意不是重启服务),这样Eureka中心就有两个服务者了。
此时消费者访问服务者就会出现负载均衡效果了。
高可用注册中心
在微服务中,注册中心非常核心,可以实现服务治理,如果一旦注册出现故障的时候,可能会导致整个微服务无法访问,在这时候就需要对注册中心实现高可用集群模式。
Eureka高可用原理
Eureka高可用实际上将自己作为服务向其他服务注册中心注册自己,这样就可以形成一组相互注册的服务注册中心,从而实现服务清单的互相同步,达到高可用效果。
1、创建多个注册服务,过程同上。
2、唯一需要注意的是修改配置文件,已达到相互注册的效果。
###eureka 服务端口号
server:
port: 8100
###定义服务注册名称(集群的服务名称一定要相同)
spring:
application:
name: app-springCloud-eureka
eureka:
instance:
###注册中心ip地址
hostname: 127.0.0.1
###客户端调用地址
client:
serviceUrl:
###注册地址
defaultZone: http://${eureka.instance.hostname}:8200/eureka/
###因为是集群,所以需要改为true
register-with-eureka: true
###因为是集群,所以需要改为true
fetch-registry: true
###eureka 服务端口号
server:
port: 8200
###定义服务注册名称(集群的服务名称一定要相同)
spring:
application:
name: app-springCloud-eureka
eureka:
instance:
###注册中心ip地址
hostname: 127.0.0.1
###客户端调用地址
client:
serviceUrl:
###注册地址
defaultZone: http://${eureka.instance.hostname}:8100/eureka/
###因为是集群,所以需要改为true
register-with-eureka: true
###因为是集群,所以需要改为true
fetch-registry: true
注意,如果想要达成集群的效果,服务名称一定要是相同的。
3、访问各自的注册中心
4、修改服务者配置文件
需要将服务注册到Eureka集群中
###会员项目的端口号
server:
port: 8010
###服务名称(服务注册到eureka名称)
spring:
application:
name: app-springCloud-member
###服务注册到eureka地址
eureka:
client:
service-url:
###当前服务注册到Eureka服务地址
defaultZone: http://localhost:8100/eureka,http://localhost:8200/eureka
###需要将会员服务注册到Eureka服务中
register-with-eureka: true
###需要检索服务
fetch-registry: true
5、修改消费者配置文件
###服务启动端口号
server:
port: 8001
###服务名称(服务注册到eureka名称)
spring:
application:
name: app-springCloud-order
###服务注册到eureka地址
eureka:
client:
service-url:
###当前服务注册到Eureka服务地址
defaultZone: http://localhost:8100/eureka,http://localhost:8200/eureka
###需要将会员服务注册到Eureka服务中
register-with-eureka: true
###需要检索服务
fetch-registry: true
6、访问各自的注册中心
因为在注册的过程中,只会保证一台注册中心有对应的服务信息数据(类似于zookeeper),当8200注册中心宕机后,自动启动转移同步数据到8100上去的。
Eureka自我保护机制
默认情况下,EurekaClient会定时向EurekaServer端发送心跳,如果EurekaServer在一定时间内没有收到EurekaClient发送的心跳,便会把该实例从注册服务列表中剔除(默认是90秒),但是在短时间内丢失大量的实例心跳,这时候EurekaServer会开启自我保护机制,Eureka不会踢出该服务。
关闭自我保护机制
修改Eureka服务端配置文件
server:
# 测试时关闭自我保护机制,保证不可用服务及时踢出
enable-self-preservation: false
eviction-interval-timer-in-ms: 2000
修改Eureka客户端配置文件
# 心跳检测检测与续约时间
# 测试时将值设置设置小些,保证服务关闭后注册中心能及时踢出服务
instance:
###Eureka客户端向服务端发送心跳的时间间隔,单位为秒(客户端告诉服务端自己会按照该规则)
lease-renewal-interval-in-seconds: 1
####Eureka服务端在收到最后一次心跳之后等待的时间上限,单位为秒,超过则剔除(客户端告诉服务端按照此规则等待自己)
lease-expiration-duration-in-seconds: 2
但是,万万不幸的是,Eureka闭源了,我们可以想办法用其他的注册中心替代Eureka:Zookeeper、Consul。
使用Consul来替代Eureka
简介
Consul 是一套开源的分布式服务发现和配置管理系统,由 HashiCorp 公司用 Go 语言开发。
它具有很多优点。包括: 基于 raft 协议,比较简洁; 支持健康检查, 同时支持 HTTP 和 DNS 协议 支持跨数据中心的 WAN 集群,提供图形界面 跨平台,支持 Linux、Mac、Windows。
环境搭建
1、下载
Consul下载地址https://www.consul.io/downloads.html
下载window版,解压得到一个可执行文件。
2、设置环境变量
在path后面添加consul所在目录。
3、启动consul命
让我们直接在cmd里可直接使用consul使命。
consul agent -dev -ui -node=cy
-dev开发服务器模式启动,-node结点名为cy,-ui可以用界面访问,默认能访问。
4、访问Consul
Consul客户端
1、pom文件引入
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<!-- 管理依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.M7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--SpringCloud consul-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
</dependencies>
<!-- 注意: 这里必须要添加, 否者各种依赖有问题 -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
2、配置文件
###服务端口号
server:
port: 8502
spring:
### 服务名称
application:
name: consul-member
####consul注册中心地址
cloud:
consul:
### consul地址
host: localhost
### ### consul端口号
port: 8500
discovery:
##服务地址直接为ip地址
hostname: 192.168.18.220
###默认情况下 服务注册到注册中心 地址随机生成英文 pc-yushengjun:
### 换不同注册中心的时候,接口调用方式都不变 无非变化 配置文件和maven依赖信息
3、启动项
@EnableDiscoveryClient 与@EnableEurekaClient区别
- @EnableDiscoveryClient注解是基于spring-cloud-commons依赖,并且在classpath中实现; 适合于consul、zookeeper注册中心
- @EnableEurekaClient注解是基于spring-cloud-netflix依赖,只能为eureka作用
@SpringBootApplication
@EnableDiscoveryClient
// @EnableEurekaClient 是Eureka使用的
// @EnableDiscoveryClient 作用是 如果服务使用connsul、zookeeper 使用
// @EnableDiscoveryClient 向注册中心上注册服务
public class ConsulAPP {
public static void main(String[] args) {
SpringApplication.run(ConsulAPP.class, args);
}
}
4、DiscoveryClient用法
discoveryClient接口,可以获取注册中心上的实例信息。
@RequestMapping("/discoveryClientMember")
public List<ServiceInstance> discoveryClientMember() {
List<ServiceInstance> instances = discoveryClient.getInstances("consul-member");
for (ServiceInstance serviceInstance : instances) {
System.out.println("url:" + serviceInstance.getUri());
}
return instances;
}
5、订单服务调用会员服务
// springcloud 中使用feign或者rest技术实现调用服务接口
// 订单服务调用会员服务
@RequestMapping("/orderToMember")
public String orderToMember() {
// 有两种方式,一种是采用服务别名方式调用,
// 另一种是直接调用 使用别名去注册中心上获取对应的服务调用地址
//第一种方式:String memberUrl = "http://zk-member/getMember";
//第二种方式:使用别名
String serviceUrl = getServiceUrl("consul-member") + "/getMember";
String result = restTemplate.getForObject(serviceUrl, String.class);
System.out.println("订单服务调用会员服务result:" + result);
return result;
}
public String getServiceUrl(String name) {
List<ServiceInstance> list = discoveryClient.getInstances(name);
if (list != null && !list.isEmpty()) {
return list.get(0).getUri().toString();
}
return null;
}
使用Zookeeper来替代Eureka
简介
Zookeeper是一个分布式协调工具,可以实现服务注册与发现、注册中心、消息中间件、分布式配置中心等。
ZK客户端
1、启动zk服务器
2、pom文件引入
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<!-- 管理依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.M7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- SpringBoot整合eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>
</dependencies>
<!-- 注意: 这里必须要添加, 否者各种依赖有问题 -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
3、配置文件
会员配置文件
###服务端口号
server:
port: 8000
###服务名称
spring:
application:
name: zk-member
cloud:
zookeeper:
###注册到zookeeper地址
connect-string: 127.0.0.1:2181
订单配置文件
###服务端口号
server:
port: 8060
###服务名称
spring:
application:
name: zk-order
cloud:
zookeeper:
###注册到zookeeper地址
connect-string: 127.0.0.1:2181
启动zk-member服务和zk-order服务,可以发现在Zk服务器端上有对应的节点信息。
Zookeeper与Eureka区别
首先,我们需要了解CPA理论。
CAP:一个分布式系统不可能同时满足C(一致性)、A(可用性)和P(分区容错性)。由于分区容错性在是分布式系统中必须要保证的,因此我们只能在A和C之间进行权衡。在此Zookeeper保证的是CP, 而Eureka则是AP。
Consistency(一致性), 数据一致更新,所有数据变动都是同步的
Availability(可用性), 好的响应性能
Partition tolerance(分区容忍性) 可靠性
1、“C”是指一致性,即当一个Process(过程)修改了某个数据后,其他Process读取这是数据是,得到的是更新后的数据,但并不是所有系统都 可以做到这一点。例如,在一些并非严格要求一致性的系统中,后来的Process得到的数据可能还是修改之前的数据,或者需要等待一定时间后才能得到修改 之后的数据,这被成为“弱一致性”,最经典的应用就是DNS系统。当用户修改了DNS配置后,往往不会马上在全网更新,必定会有一个延迟,这个延迟被称为 “不一致窗口”,它的长度取决于系统的负载、冗余的个数等因素。但对于某些系统而言,一旦写入,后面读取的一定是修改后的数据,如银行账户信息,这被称为 “强一致性”。
2、“A”是指可用性。即系统总是能够为用户提供连续的服务能力。当用户发出请求是,系统能给出响应(成功或者失败),而且是立即给出响应,而不是等待其他事情完成才响应。如果需要等待某件事情完成才响应,那么“可用性”就不存在了。
3、“P”是指容错性。任何一个分布式计算系统都是由多个节点组成的。在正常情况下,节点与节点之间的通信是正常的。但是在某些情况下,节点之间的通信会 断开,这种断开成为“Partition”。在分布式计算的实现中,Partition是很常见的,因为节点不可能永远不出故障,尤其是对于跨物理地区的 海量存储系统而言,而容错性则可以保证如果只是系统中的部分节点不可用,那么相关的操作仍旧能够正常完成。
Zookeeper是保证CP
当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接down掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。但是zk会出现这样一种情况,当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长,30 ~ 120s, 且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得zk集群失去master节点是较大概率会发生的事,虽然服务能够最终恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。
Eureka是保证AP
Eureka看明白了这一点,因此在设计时就优先保证可用性。Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册或时如果发现连接失败,则会自动切换至其它节点,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。除此之外,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:
1. Eureka不再从注册列表中移除因为长时间没收到心跳而应该过期的服务
2. Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用)
3. 当网络稳定时,当前实例新的注册信息会被同步到其它节点中
因此, Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像zookeeper那样使整个注册服务瘫痪。