什么是服务治理
SpringCloud封装了Netflix公司的Eureka模块来实现服务治理。
在传统的PRC远程方法调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要服务治理。管理服务于服务之间的依赖关系、可以实现服务调用、负载均衡、容错等,实现服务与注册。
Eureka简介
Eureka是Netflix中的一个开源框架。它和 zookeeper、Consul一样,都是用于服务注册管理的,同样,Spring-Cloud 还集成了Zookeeper和Consul。在项目中使用Spring Cloud Euraka的原因是它可以利用Spring Cloud Netfilix中其他的组件,如zull等,因为Euraka是属于Netfilix的。
Eureka 采用了CS设计架构,Eureka Server作为服务注册功能的服务器,她是服务注册中心。系统中的其他微服务,使用Eureka的客户端连接到 Eureka Server并维持心跳连接。这样系统的维护人员就可以通过Eureka Server来监控系统中各个微服务是否正常运行。
在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息,比如:服务地址、通讯地址等一别名方式注册到注册中心上。另一方(消费者|服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用PRC远程调用框架核心设计实现。在于注册中心,因为注册中心管理每个服务与服务器之间依赖关系。在任何RPC远程框架中,都会有一个注册中心。
Eureka client既可以作为服务的生产者,又可以作为服务的消费者。具体结构如下图:
Eureka组件
Eureka Server
提供服务注册功能,各个微服务节点通过配置启动后会在Eureka中进行注册。这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。
Eureka Client
通过注册中心访问,是一个JAVA客户端,用于简化和Server的交付,客户端同事也具备一个内置的,使用轮询负载算法的负载均衡器。在应用启动后;会将向Eureka Server发送心跳(默认30s)。若Server在多个心跳周期内没有接收到某个节点的心跳,Server将会从服务注册表中吧这个节点移除(默认90s)。
Eureka实践联系
Eureka Server
-
创建maven工程
创建一个名为:cloud-eureka-server-7001 的普通maven工程
-
配置pom.xml
<artifactId>cloud-eureka-server-7001</artifactId> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <!-- boot web actuator --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies>
-
配置application.yml
创建并配置application.yml
server: port: 7001 eureka: instance: appname: localhost client: register-with-eureka: false fetch-registry: false service-url: defaultZone: http://${eureka.instance.appname}:${server.port}/eureka/ server: enable-self-preservation: false eviction-interval-timer-in-ms: 2000
-
编写启动类
编写CloudEurekaServerApplication启动类
@SpringBootApplication @EnableEurekaServer //开启Eureka服务器端 public class CloudEurekaServerApplication { public static void main(String[] args) { SpringApplication.run(CloudEurekaServerApplication.class,args); } }
-
启动程序并访问后台
2021-02-22 13:47:37.934 INFO 3877 --- [ main] c.n.eureka.DefaultEurekaServerContext : Initializing ... 2021-02-22 13:47:37.940 INFO 3877 --- [ main] c.n.eureka.cluster.PeerEurekaNodes : Adding new peer nodes [http://localhost:7001/eureka/] 2021-02-22 13:47:38.200 INFO 3877 --- [ main] c.n.d.provider.DiscoveryJerseyProvider : Using JSON encoding codec LegacyJacksonJson 2021-02-22 13:47:38.200 INFO 3877 --- [ main] c.n.d.provider.DiscoveryJerseyProvider : Using JSON decoding codec LegacyJacksonJson 2021-02-22 13:47:38.200 INFO 3877 --- [ main] c.n.d.provider.DiscoveryJerseyProvider : Using XML encoding codec XStreamXml 2021-02-22 13:47:38.200 INFO 3877 --- [ main] c.n.d.provider.DiscoveryJerseyProvider : Using XML decoding codec XStreamXml 2021-02-22 13:47:38.577 INFO 3877 --- [ main] c.n.eureka.cluster.PeerEurekaNodes : Replica node URL: http://localhost:7001/eureka/ 2021-02-22 13:47:38.597 INFO 3877 --- [ main] c.n.e.registry.AbstractInstanceRegistry : Finished initializing remote region registries. All known remote regions: [] 2021-02-22 13:47:38.600 INFO 3877 --- [ main] c.n.eureka.DefaultEurekaServerContext : Initialized 2021-02-22 13:47:38.623 INFO 3877 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 2 endpoint(s) beneath base path '/actuator' 2021-02-22 13:47:38.836 INFO 3877 --- [ main] o.s.c.n.e.s.EurekaServiceRegistry : Registering application LOCALHOST with eureka with status UP 2021-02-22 13:47:38.843 INFO 3877 --- [ Thread-39] o.s.c.n.e.server.EurekaServerBootstrap : Setting the eureka configuration.. 2021-02-22 13:47:38.845 INFO 3877 --- [ Thread-39] o.s.c.n.e.server.EurekaServerBootstrap : Eureka data center value eureka.datacenter is not set, defaulting to default 2021-02-22 13:47:38.847 INFO 3877 --- [ Thread-39] o.s.c.n.e.server.EurekaServerBootstrap : Eureka environment value eureka.environment is not set, defaulting to test 2021-02-22 13:47:38.897 INFO 3877 --- [ Thread-39] o.s.c.n.e.server.EurekaServerBootstrap : isAws returned false 2021-02-22 13:47:38.900 INFO 3877 --- [ Thread-39] o.s.c.n.e.server.EurekaServerBootstrap : Initialized server context 2021-02-22 13:47:38.900 INFO 3877 --- [ Thread-39] c.n.e.r.PeerAwareInstanceRegistryImpl : Got 1 instances from neighboring DS node 2021-02-22 13:47:38.900 INFO 3877 --- [ Thread-39] c.n.e.r.PeerAwareInstanceRegistryImpl : Renew threshold is: 1 2021-02-22 13:47:38.901 INFO 3877 --- [ Thread-39] c.n.e.r.PeerAwareInstanceRegistryImpl : Changing status to UP 2021-02-22 13:47:38.931 INFO 3877 --- [ Thread-39] e.s.EurekaServerInitializerConfiguration : Started Eureka Server 2021-02-22 13:47:39.437 INFO 3877 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 7001 (http) with context path '' 2021-02-22 13:47:39.441 INFO 3877 --- [ main] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 7001 2021-02-22 13:47:39.781 INFO 3877 --- [ main] i.h.cloud.CloudEurekaServerApplication : Started CloudEurekaServerApplication in 17.963 seconds (JVM running for 26.447) 2021-02-22 13:47:40.115 INFO 3877 --- [on(6)-127.0.0.1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' 2021-02-22 13:47:40.115 INFO 3877 --- [on(6)-127.0.0.1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' 2021-02-22 13:47:40.131 INFO 3877 --- [on(6)-127.0.0.1] o.s.web.servlet.DispatcherServlet : Completed initialization in 15 ms
启动成功后访问界面:
Eureka Client
对之前的项目进行Eureka 客户端改造
-
修改pom.xml
<!-- dependencies 中添加一个eureka客户端的配置 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
-
修改application.yml配置
eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7001/eureka
-
修改启动类
@SpringBootApplication @EnableEurekaClient //添加该注解启动Eureka客户端 public class CloudProviderPaymentApplication { public static void main(String[] args) { SpringApplication.run(CloudProviderPaymentApplication.class,args); } }
-
启动项目后查看后台界面
另外一个消费者项目改造类似,在此不赘述。
Eureka集群搭建
为了达到服务的高可用,通常都采用集群的方式。
搭建Eureka集群
在之前Eureka Server 7001的基础上在创建一个服务器端,具体操作如下:
准备工作
修改hosts文件添加两个域名映射。
# Add by huzd for eureka culster demo
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
创建Eureka Server 7002
复制7001修改如下配置:
修改pom.xml 文件
<artifactId>cloud-eureka-server-7002</artifactId>
修改application.yml
server:
port: 7002
eureka:
instance:
appname: eureka7002.com
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
server:
enable-self-preservation: true
eviction-interval-timer-in-ms: 2000
spring:
application:
name: CLOUD-EUREKA-SERVER-7002
启动类无需修改。
更新Eureka Server 7001
修改application.yml
server:
port: 7001
eureka:
instance:
appname: eureka7001.com
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://eureka7002.com:7002/eureka/
server:
enable-self-preservation: true
eviction-interval-timer-in-ms: 2000
spring:
application:
name: CLOUD-EUREKA-SERVER-7001
启动两个Eureka服务器端
访问后台管理
界面如下图所示:
- 访问7001端口,DS Replicas显示Eureka7002.com
- 访问7002端口,DS Replicas显示Eureka7001.com
服务挂载Eureka集群
支付模块挂载Eureka集群
只需要修改applicaton.yml配置即可
server:
port: 8001
servlet:
context-path: /
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
# 集群方式配置
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
# defaultZone: http://localhost:7001/eureka
healthcheck:
enabled: true
spring:
application:
name: cloud-provider-payment-8001
datasource:
url: jdbc:mysql://localhost:3306/cloud?useUnicode=true&characterEncoding=utf-8&useSSL=false
type: com.alibaba.druid.pool.DruidDataSource
username: root
password: sitechdss
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: info.huzd.cloud.bean
logging:
level:
root: debug
消费者模块挂载Eureka集群
同上
启动集群挂载服务
启动顺序:
- 启动集群节点7001
- 启动集群节点7002
- 启动支付模块
- 启动消费模块
启动后访问控制台截图如下所示:
测试服务
访问:http://localhost/consumer/payment/get/7 一切正常。
搭建服务提供者集群
我在之前8001服务器提供者程序的基础上复制创建8002服务提供者
创建maven项目cloud-provider-payment-8002
复制cloud-provider-payment-8001 pom.xml的依赖配置、复制cloud-provider-payment-8001项目的src。
在调用的controller中添加端口信息
@Value("${server.port}")
Integer port; //注入配置文件端口号
@PostMapping("/save")
@ResponseBody
public CommonResult save(@RequestBody Payment payment) {
Integer result = paymentService.save(payment);
CommonResult cr = null;
if(result>0){
cr = new CommonResult(200,"添加成功(服务端口:"+port+")",result); //返回信息中添加端口信息。
}else{
cr = new CommonResult(400,"添加失败(服务端口:"+port+")",null);
}
log.debug("payment create success!!");
return cr;
}
修改application.yml
server:
port: 8002 #端口号改为8002;其他保持与cloud-provider-payment-8001配置一致。
servlet:
context-path: /
启动集群环境
启动顺序:
- 启动集群节点7001
- 启动集群节点7002
- 启动支付模块8001
- 启动支付模块8002
- 启动消费模块
改写消费者调用地址
修改cloud-consumer-order-80
@Controller
@RequestMapping("/consumer")
@Slf4j
public class OrderController {
public static final String PAYMENT_URL = "http://CLOUD-PROVIDER-PAYMENT/";
// public static final String PAYMENT_URL = "http://localhost:8001/payment/";
@Resource
RestTemplate restTemplate;
@GetMapping("/payment/save")
@ResponseBody
public CommonResult<Payment> createPayment(Payment payment){
System.out.println("请求地址:"+PAYMENT_URL+"save");
return restTemplate.postForObject(PAYMENT_URL+"/payment/save",payment,CommonResult.class);
}
@GetMapping("/payment/get/{id}")
@ResponseBody
public CommonResult<Payment> getPayment(@PathVariable("id") Long id){
return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
}
}
添加负载均衡注解
@Configuration
public class ApplicationConfig {
@Bean
@LoadBalanced //添加负载均衡注解
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
启动测试
启动消费者80项目,在浏览器中访问:http://localhost/consumer/payment/get/1 会看见调用返回信息中端口地址交替的变化。
Actuator信息完善
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
# defaultZone: http://localhost:7001/eureka
healthcheck:
enabled: true
instance:
appname: cloud-provider-payment #在后台管理系统中显示的名称 如下图所示。这里如果为空默认使用 spring.application.name
instance-id: cloudProviderPayment8002 #唯一的实例名称 在系统中status列中显示
prefer-ip-address: true #鼠标放在status连接上时候专为真是IP地址。
服务发现
开启服务发现功能
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient #开启服务发现功能
public class CloudProviderPayment8001Application {
public static void main(String[] args) {
SpringApplication.run(CloudProviderPayment8001Application.class,args);
}
}
在controller中查看相关信息
@Resource
DiscoveryClient discoveryClient;
@GetMapping("/discovery")
@ResponseBody
public Object discovery(){
discoveryClient.getServices().stream().forEach(serviceName->{
System.out.println("ServiceName:"+serviceName);
discoveryClient.getInstances(serviceName).stream().forEach(s->{
System.out.println("Service["+serviceName+"]:"+s.getHost()+":"+s.getPort()+" - "+s.getInstanceId() + " = "+s.getUri());
});
});
return discoveryClient;
}
在练习测试的过程中发现了一些问题。 server.application.name 和 eureka.instance.appname 命名的优先级机制等。
配置不当会造成上面代码第10行,无法获取相关信息。
运行结果截图:
自我保护机制
简介
默认情况下,如果EurekaServer在一定时间内没有接受到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区故障发生(延时、卡顿、拥挤)时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险--因为微服务本身其实是健康的,此时本不应该注销这个微服务。Eureka通过“自我保护模式”来解决这个问题--当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。
机制
Eureka Server 在运行期间会去统计心跳失败比例在 15 分钟之内是否低于 85%,如果低于 85%,Eureka Server 会将这些实例保护起来,让这些实例不会过期,但是在保护期内如果服务刚好这个服务提供者非正常下线了,此时服务消费者就会拿到一个无效的服务实例,此时会调用失败,对于这个问题需要服务消费者端要有一些容错机制,如重试,断路器等。
我们在单机测试的时候很容易满足心跳失败比例在 15 分钟之内低于 85%,这个时候就会触发 Eureka 的保护机制,一旦开启了保护机制,则服务注册中心维护的服务实例就不是那么准确了,此时我们可以使用eureka.server.enable-self-preservation=false
来关闭保护机制,这样可以确保注册中心中不可用的实例被及时的剔除(不推荐)。
自我保护模式被激活的条件是:在 1 分钟后,Renews (last min) < Renews threshold
。
这两个参数的意思:
Renews threshold
:Eureka Server 期望每分钟收到客户端实例续约的总数。Renews (last min)
:Eureka Server 最后 1 分钟收到客户端实例续约的总数。
如何关闭
服务器端设置
配置:eureka.server.enable-self-preservation & eureka.server.eviction-interval-timer-in-ms
server:
port: 7001
eureka:
....
server:
enable-self-preservation: false #关闭自我保护机制
eviction-interval-timer-in-ms: 2000 #设置服务器端有效性检查时间间隔为2秒
spring:
application:
name: CLOUD-EUREKA-SERVER-7001
客户端设置
配置eureka.instance.lease-renewal-interval-in-seconds & eureka.instance.lease-expiration-duration-in-seconds
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
# defaultZone: http://localhost:7001/eureka
healthcheck:
enabled: true
instance:
instance-id: cloudProviderPayment8001
prefer-ip-address: true
lease-renewal-interval-in-seconds: 1 #eureka客户端向服务端发送心跳的间隔,单位秒,默认30
lease-expiration-duration-in-seconds: 2 #eureka服务端在收到最后一次心跳后等待时间上限,单位为秒,默认90,,超时将剔除服务
启动测试
-
启动server7001
-
启动server7002
-
查看eureka控制台
新的提示 HE SELF PRESERVATION MODE IS TURNED OFF. THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS.
-
启动payment8001
-
启动payment8002
-
查看eureka控制台,
-
停止payment8001
-
刷新eureka控制台
配置详解
eureka.client.registry-fetch-interval-seconds
表示eureka client间隔多久去拉取服务注册信息,默认为30秒,对于api-gateway,如果要迅速获取服务注册状态,可以缩小该值,比如5秒
eureka.instance.lease-expiration-duration-in-seconds
leaseExpirationDurationInSeconds,表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该instance。
- 默认为90秒
- 如果该值太大,则很可能将流量转发过去的时候,该instance已经不存活了。
- 如果该值设置太小了,则instance则很可能因为临时的网络抖动而被摘除掉。
- 该值至少应该大于leaseRenewalIntervalInSeconds
eureka.instance.lease-renewal-interval-in-seconds
leaseRenewalIntervalInSeconds,表示eureka client发送心跳给server端的频率。如果在leaseExpirationDurationInSeconds后,server端没有收到client的心跳,则将摘除该instance。除此之外,如果该instance实现了HealthCheckCallback,并决定让自己unavailable的话,则该instance也不会接收到流量。
- 默认30秒
eureka.server.enable-self-preservation
是否开启自我保护模式,默认为true。
默认情况下,如果Eureka Server在一定时间内没有接收到某个微服务实例的心跳,Eureka Server将会注销该实例(默认90秒)。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。
Eureka通过“自我保护模式”来解决这个问题——当Eureka Server节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。一旦进入该模式,Eureka Server就会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该Eureka Server节点会自动退出自我保护模式。
综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留),也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。
eureka.server.eviction-interval-timer-in-ms
eureka server清理无效节点的时间间隔,默认60000毫秒,即60秒
本文由 huzd 创作,采用 知识共享署名4.0 国际许可协议进行许可本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名最后编辑时间
为:
2021/02/25 10:41