1. 服务治理简介
什么是服务发现 : 程序如何通过一个标志来获取服务列表,并且这个服务列表是能够随着服务的状态而动态变更。
先介绍两种服务模式:
- 客户端模式:eureka,consule
- 服务端模式:zookeeper
1.1 客户端模式
调用端调用微服务时,首先到服务注册中心获取服务列表,然后再根据调用端本地的负载均衡策略,进行服务调用。
1.2 服务端模式
调用端直接向服务注册中心发起请求,服务注册中心再通过自身负载均衡策略,对微服务进行调用。调用端自身节点不需要维护服务发现逻辑。
两者区别在于,客户端是否保存服务列表信息
1.3 两者对比
客户端模式:
1. 只需要周期性获取列表,在调用服务时可以直接调用少了一个链路。但需要在每个客户端维护获取列表的逻辑;
2. 可用性高,即使注册中心出现故障也能正常工作;
3. 服务上下线对调用方有影响,会出现短暂调用失败
服务端模式:
1. 简单,不需要在客户端维护服务获取列表的逻辑;
2. 可用性由路由器中间件决定,路由中间件故障则所有服务不可用。同时,由于所有调度以及存储都有中间件服务器完成,中间件服务器可能会面临负载过高;
3. 服务上下线调用方无感知;
2. 基于SpringCloud Netflix Eureka 实现注册中心
Eureka由两个组件组成:
- Eureka Server(注册中心)
- Eureka Client (服务注册)
基于1.51版本实现
1: application.properties配置文件
#eurake 注册中心实例名 spring.application.name=eurake-server #eureka注册中心端口 server.port=9090 ##关闭Actuator 验证开关 management.sercurity.enable=false #一个单机的eureka服务,无需注册其他机器,也无需获取服务列表 #不向注册中心获取服务列表 eureka.client.fetch-registry=false #不注册到注册中心上 eureka.client.register-with-eureka=false #不设置会给一个默认的,http://localhost:8761/eureka无效地址 #配置注册中心地址 eureka.client.service-url.defaultZone=http://localhost:9090/eureka
服务端配置详解
参数名 说明 默认值 preferIpAddress 是否优先使用IP地址作为主机名的标识 false leaseRenewalIntervalInSeconds Eureka客户端向服务端发送心跳的时间间隔,单位为秒 30 leaseExpirationDurationInSeconds Eureka服务端在收到最后一次心跳之后等待的时间上限,单位为秒 90 nonSecurePort 非安全的通信端口 80 securePort 安全的通信端口 443 nonSecurePortEnabled 是否启用非安全的通信端口 true securePortEnabled 是否启用安全的通信端口 false appname 服务名 spring.application.name/unknown hostname 主机名 不配置时取操作系统主机名 instanceId 实例ID statusPageUrlPath 客户端应用实例状态URL /info
2 pom.xml文件
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.20.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <java.version>1.8</java.version> <spring-cloud.version>Edgware.SR5</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <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-server</artifactId> </dependency> </dependencies>
3启动类
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication //eureka Server端配置注解 @EnableEurekaServer public class SpringCloudEurekaServerDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringCloudEurekaServerDemoApplication.class, args); } }
3 Eureka 客户端
3.1 客户端--生产者
1:application.properties 配置文件
## Eureka服务提供者实例名 spring.application.name=eureka-provider ## Eureka服务提供者端口 server.port=8070 ## 关闭Actuator验证开关 management.security.enabled=false ## 配置 注册中心的 地址 eureka.client.service-url.defaultZone=http://localhost:9090/eureka/ ## 修改实例的ID组成(自定义示例名称) #eureka.instance.instance-id=${spring.application.name}:${server.port} ## 修改Eureka是在在Eureka管理端statusPageUrlPath #eureka.instance.status-page-url-path=/health
客户端配置文件详解
参数名 说明 默认值 serviceUrl.defaultZone 指定注册中心 http://localhost:8761/eureka enabled 启用Eureka客户端 TRUE registryFetchIntervalSeconds 从Eureka服务端获取注册信息的间隔时间,单位为秒 30 instanceInfoReplicationIntervalSeconds 更新示例信息的变化到Eureka服务端的间隔时间,单位为秒 30 initialInstanceInfoReplicationIntervalSeconds 初始化实例信息到Eureka服务端的间隔时间,单位为秒 40 eurekaServiceUrlPollIntervalSeconds 轮询Eureka服务端地址更改的时间间隔,单位为秒 300 eurekaServerReadTimeoutSeconds 读取Eureka Server信息的超时时间,单位为秒 8 eurekaServerConnectTimeoutSeconds 5 eurekaServerTotalConnections 从Eureka客户端到所有Eureka服务端的连接总数 200 eurekaServerTotalConnectionsPerHost 从Eureka客户端到每个Eureka服务端主机的连接总数 50 eurekaConnectionIdleTimeoutSeconds Eureka服务端连接的空闲关闭时间,单位为秒 30 heartbeatExecutorThreadPoolSize 心跳连接池的初始化线程数 2 heartbeatExecutorExponentialBackOffBound 心跳超时重试延迟时间的最大乘数值 10 cacheRefreshExecutorThreadPoolSize 缓存刷新线程池的初始化线程数 2 cacheRefreshExecutorExponentialBackOffBound 缓存刷新重试延迟时间的最大乘数值 10 useDnsForFetchingServiceUrls 使用DNS来获取Eureka服务端的ServiceURL FALSE registerWithEureka 是否要将自身的实例信息注册到Eureka服务端 TRUE preferSameZoneEureka 是否偏好使用处于相同Zone的Eureka服务端 TRUE fetchRegistry 是否从Eureka服务端获取注册信息 TRUE
2:pom.xml文件
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.20.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <java.version>1.8</java.version> <spring-cloud.version>Edgware.SR5</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <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.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
3:启动类
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication //客户端启动注解 @EnableDiscoveryClient public class SpringCloudEurekaProviderDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringCloudEurekaProviderDemoApplication.class, args); } }
3.1 测试类:ProductController
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map; @RestController public class ProductController { // 定义一个库存集合 private static final Map<String, Integer> productMap = new HashMap<String, Integer>(); /*** * 修改库存 * @param productName * @param num * @return */ @GetMapping("/updateProduct") public String updateProducter(String productName, Integer num){ if(productName != null && !productName.isEmpty()){ productMap.put(productName, (productMap.get(productName) == null ? 0: productMap.get(productName))-num); System.out.println("修改库存:productName-"+productName+", num-"+num); } return "修改库存成功, "+productName+" 剩下 "+productMap.get(productName); } }
3.2 客户端--消费者
1:application.properties
## Eureka服务消费者实例名 spring.application.name=eureka-consumer ## Eureka服务消费者端口 server.port=8080 ## 关闭Actuator验证开关 management.security.enabled=false ## 配置 注册中心的 地址 eureka.client.service-url.defaultZone=http://localhost:9090/eureka/
2:pom.xml文件
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.20.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <java.version>1.8</java.version> <spring-cloud.version>Edgware.SR5</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <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.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
3.1:启动类:SpringCloudEurekaConsumerDemoApplication
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication @EnableDiscoveryClient public class SpringCloudEurekaConsumerDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringCloudEurekaConsumerDemoApplication.class, args); } //eureka负载均衡 @Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } }
3.2OrderController
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.util.ArrayList; import java.util.List; @RestController public class OrderController { private static final List<OrderInfo> list = new ArrayList<OrderInfo>(); @Autowired private RestTemplate restTemplate; /*** * 下单接口 -- 调用服务提供者provider /updateProduct 接口 * @param productName * @param num * @return */ @GetMapping("/order") public String order(String productName, Integer num){ if(productName != null && !productName.isEmpty()){ list.add(new OrderInfo(productName, num)); // 调用服务提供者 String result = restTemplate.getForObject("http://eureka-provider/updateProduct?productName=" + productName + "&num=" + num, String.class); System.out.println(result); return result; } return null; } }
3.3实体类
public class OrderInfo implements Serializable { private String productName; private Integer num; public OrderInfo(String productName, Integer num) { this.productName = productName; this.num = num; } public OrderInfo() { } public String getProductName() { return productName; } public void setProductName(String productName) { this.productName = productName; } public Integer getNum() { return num; } public void setNum(Integer num) { this.num = num; } }
4 测试使用
4.1依次启动 三个工程:服务端,客户端-生产者,客户端-消费者,
4.2 调用消费者接口 :http://127.0.0.1:8080/order?productName=商品名称&num=12
5. eureka高可用
eureka服务端高可用
修改配置文件
#eureka注册中心端口
server.port=9091
#向注册中心获取服务列表
eureka.client.fetch-registry=true
#注册到注册中心上
eureka.client.register-with-eureka=true
#配置另外一台注册中心地址
eureka.client.service-url.defaultZone=http://localhost:9092/eureka
另外一台eureka的配置文件
#eureka注册中心端口
server.port=9092
#向注册中心获取服务列表
eureka.client.fetch-registry=true
#注册到注册中心上
eureka.client.register-with-eureka=true
#配置另外一台注册中心地址,
eureka.client.service-url.defaultZone=http://localhost:9091/eureka
客户端配置高可用
其他客户端服务注册到服务注册中心只需修改注册地址即可:多个地址用逗号隔开
#配置注册中心地址
eureka.client.service-url.defaultZone=http://localhost:9091/eureka,http://localhost:9092/eureka
6. Eureka深入简介:
注意:有关eureka 2.0的现有开源工作已停止。在2.x分支上作为现有工作资料库的一部分发布的代码库和工件被视为使用后果自负。Eureka 1.x是Netflix服务发现系统的核心部分,仍然是一个活跃的项目
参考地址:https://github.com/Netflix/eureka/wiki 推荐一个spring cloud的论坛链接:http://springcloud.cn/
6.1 eureka架构图
6.2 Eureka核心功能点
- 服务注册(register):Eureka Client会通过发送REST请求的方式向Eureka Server注册自己的服务,提供自身的元数 据,比如ip地址、端口、运行状况指标的url、主页地址等信息。Eureka Server接收到注册请求后,就会把这些元数 据信息存储在一个双层的Map中。
- 服务续约(renew):在服务注册后,Eureka Client会维护一个心跳来持续通知Eureka Server,说明服务一直处于可 用状态,防止被剔除。Eureka Client在默认的情况下会每隔30秒(eureka.instance.leaseRenewallIntervalInSeconds)发送一次心跳来进行服务续约
- 服务同步(replicate):Eureka Server之间会互相进行注册,构建Eureka Server集群,不同Eureka Server之间会进 行服务同步,用来保证服务信息的一致性
- 获取服务(get registry):服务消费者(Eureka Client)在启动的时候,会发送一个REST请求给Eureka Server,获 取上面注册的服务清单,并且缓存在Eureka Client本地,默认缓存30秒 (eureka.client.registryFetchIntervalSeconds)。同时,为了性能考虑,Eureka Server也会维护一份只读的服务清 单缓存,该缓存每隔30秒更新一次。
- 服务调用:服务消费者在获取到服务清单后,就可以根据清单中的服务列表信息,查找到其他服务的地址,从而进行 远程调用。Eureka有Region和Zone的概念,一个Region可以包含多个Zone,在进行服务调用时,优先访问处于同 一个Zone中的服务提供者
- 服务下线(cancel):当Eureka Client需要关闭或重启时,就不希望在这个时间段内再有请求进来,所以,就需要提前 先发送REST请求给Eureka Server,告诉Eureka Server自己要下线了,Eureka Server在收到请求后,就会把该服务 状态置为下线(DOWN),并把该下线事件传播出去
- 服务剔除(evict):有时候,服务实例可能会因为网络故障等原因导致不能提供服务,而此时该实例也没有发送请求给 Eureka Server来进行服务下线,所以,还需要有服务剔除的机制。Eureka Server在启动的时候会创建一个定时任 务,每隔一段时间(默认60秒),从当前服务清单中把超时没有续约(默认90秒, eureka.instance.leaseExpirationDurationInSeconds)的服务剔除。
6.3 eureka服务端代码流程
6.4 客户端代码继承关系: