什么是服务治理?
Spring Cloud 封装了Netflix公司开发的Eureka来实现服务治理
在传统的rpc远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务于服务之间的依赖关系,可以实现服务调用,负载均衡,容错等,实现服务发现与注册
什么是服务注册?
Eureka采用了CS的设计架构,Eureka Server 作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用 Eureka的客户端连接到 Eureka Server并维持心跳连接。这样系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行。
在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息 比如 服务地址通讯地址等以别名方式注册到注册中心上。另一方(消费者|服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用RPC远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址))
Eureka Server:提供服务的注册与发现,类似Zookeeper
Service Provider:将自服务注册到Eureka中,从而使消费方能够找到
Service Consumer:服务消费方从Eureka中获取注册服务列表,从而找到消费服务
Eureka两大组件:Eureka Server和Eureka Client
Eureka Server提供服务注册服务
各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。
EurekaClient通过注册中心进行访问
是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)
Spring cloud聚合父工程project添加链接描述
参数名称 | 说明 | 默认值 |
---|---|---|
eureka.server.enable-self-preservation | 启用自我保护机制 | true |
eureka.server.eviction-interval-timer-in-ms | 清除无效服务实例的时间间隔(ms) | 60000 |
eureka.server.delta-retention-timer-interval-in-ms | 清理无效增量信息的时间间隔(ms) | 30000 |
eureka.server.disable-delta | 禁用增量获取服务实例信息 | false |
eureka.server.log-identity-headers | 是否记录登录日志 | true |
eureka.server.rate-limiter-burst-size | 限流大小 | 10 |
eureka.server.rate-limiter-enabled | 是否启用限流 false | |
eureka.server.rate-limiter-full-fetch-average-rate | 平均请求速率 | 100 |
eureka.server.rate-limiter-throttle-standard-clients | 是否对标准客户端进行限流 | false |
eureka.server.rate-limiter-registry-fetch-average-rate | 服务注册与拉取的平均速率 | 500 |
eureka.server.rate-limiter-privileged-clients | 信任的客户端列表 | |
eureka.server.renewal-percent-threshold | 15分钟内续约服务的比例小于0.85,则开启自我保护机制,再此期间不会清除已注册的任何服务(即便是无效服务) | 0.85 |
eureka.server.renewal-threshold-update-interval-ms | 更新续约阈值的间隔(分钟) | 15 |
eureka.server.response-cache-auto-expiration-in-seconds | 注册信息缓存有效时长(s) | 180 |
eureka.server.response-cache-auto-expiration-in-seconds | 注册信息缓存更新间隔(s) | 30 |
eureka.server.retention-time-in-m-s-in-delta-queue | 保留增量信息时长(分钟) | 3 |
eureka.server.sync-when-timestamp-differs | 当时间戳不一致时,是否进行同步 | true |
eureka.server.use-read-only-response-cache | 是否使用只读缓存策略 | true |
参数名称 | 说明 | 默认值 |
---|---|---|
eureka.client.enabled | 用于指示Eureka客户端已启用的标志 | true |
eureka.client.registry-fetch-interval-seconds | 指示从eureka服务器获取注册表信息的频率(s) | 30s |
eureka.client.instance-info-replication-interval-seconds | 更新实例信息的变化到Eureka服务端的间隔时间(s) | 30s |
eureka.client.initial-instance-info-replication-interval-seconds | 初始化实例信息到Eureka服务端的间隔时间(s) | 40s |
eureka.client.eureka-service-url-poll-interval-seconds | 询问Eureka Server信息变化的时间间隔(s) | 300s |
eureka.client.eureka-server-read-timeout-seconds | 读取Eureka Server 超时时间(s) | 8s |
eureka.client.eureka-server-connect-timeout-seconds | 连接Eureka Server 超时时间(s) | 5s |
eureka.client.eureka-server-total-connections | 获取从eureka客户端到所有eureka服务器的连接总数 | 200个 |
eureka.client.eureka-server-total-connections-per-host | 获取从eureka客户端到eureka服务器主机允许的连接总数 | 50个 |
eureka.client.eureka-connection-idle-timeout-seconds | 连接到 Eureka Server 空闲连接的超时时间(s) | 30 |
eureka.client.registry-refresh-single-vip-address | 指示客户端是否仅对单个VIP的注册表信息感兴趣 | null |
eureka.client.heartbeat-executor-thread-pool-size | 心跳保持线程池初始化线程数 | 2个 |
eureka.client.heartbeat-executor-exponential-back-off-bound | 心跳超时重试延迟时间的最大乘数值 | 10 |
eureka.client.serviceUrl.defaultZone | 可用区域映射到与eureka服务器通信的完全限定URL列表。每个值可以是单个URL或逗号分隔的备用位置列表。 | |
eureka.client.use-dns-for-fetching-service-urls | 指示eureka客户端是否应使用DNS机制来获取要与之通信的eureka服务器列表。当DNS名称更新为具有其他服务器时,eureka客户端轮询eurekaServiceUrlPollIntervalSeconds中指定的信息后立即使用该信息。 | |
eureka.client.register-with-eureka | 指示此实例是否应将其信息注册到eureka服务器以供其他服务发现 | true |
eureka.client.fetch-registry | 指示此实例是否从eureka中获取注册信息 | true |
eureka.client.prefer-same-zone-eureka | 实例是否使用同一zone里的eureka服务器,理想状态下,eureka客户端与服务端是在同一zone下 | true |
eureka.client.log-delta-diff | 是否记录eureka服务器和客户端之间在注册表的信息方面的差异 | false |
eureka.client.disable-delta | 指示eureka客户端是否禁用增量提取 | false |
eureka.client.fetch-remote-regions-registry | 逗号分隔的区域列表,提取eureka注册表信息 | |
eureka.client.on-demand-update-status-change | 客户端的状态更新到远程服务器上 | true |
eureka.client.allow-redirects | 指示服务器是否可以将客户端请求重定向到备份服务器/集群。如果设置为false,则服务器将直接处理请求。如果设置为true,则可以将HTTP重定向发送到具有新服务器位置的客户端。 | false |
eureka.client.availability-zones.* | 获取此实例所在区域的可用区域列表(在AWS数据中心中使用)。更改在运行时在registryFetchIntervalSeconds指定的下一个注册表获取周期生效。 | |
eureka.client.backup-registry-impl | 获取实现BackupRegistry的实现的名称,该实现仅在eureka客户端启动时第一次作为后备选项获取注册表信息。 对于需要额外的注册表信息弹性的应用程序,可能需要这样做,否则它将无法运行。 | |
eureka.client.cache-refresh-executor-exponential-back-off-bound | 在发生一系列超时的情况下,它是重试延迟的最大乘数值。 | 10 |
eureka.client.cache-refresh-executor-thread-pool-size | 缓存刷新线程池初始化线程数量 | 2 |
eureka.client.client-data-accept | 客户端数据接收的名称 | full |
eureka.client.decoder-name | 解码器名称 | |
eureka.client.dollar-replacement eureka | 服务器序列化/反序列化的信息中获取“$”符号的替换字符串。 | 默认为“_-” |
eureka.client.encoder-name | 编码器名称 | |
eureka.client.escape-char-replacement eureka | 服务器序列化/反序列化的信息中获取“_”符号的的替换字符串。 | 默认为“__“ |
eureka.client.eureka-server-d-n-s-name | 获取要查询的DNS名称来获得eureka服务器,此配置只有在eureka服务器ip地址列表是在DNS中才会用到 | null |
eureka.client.eureka-server-port | 获取eureka服务器的端口,此配置只有在eureka服务器ip地址列表是在DNS中才会用到 | null |
eureka.client.eureka-server-u-r-l-context | 表示eureka注册中心的路径,如果配置为eureka,则为http://ip:port/eureka/,在eureka的配置文件中加入此配置表示eureka作为客户端向注册中心注册,从而构成eureka集群。此配置只有在eureka服务器ip地址列表是在DNS中才会用到 | null |
eureka.client.filter-only-up-instances | 是否过滤掉非up实例 | true |
eureka.client.g-zip-content | 当服务端支持压缩的情况下,是否支持从服务端获取的信息进行压缩 | true |
eureka.client.property-resolver | 属性解析器 | |
eureka.client.proxy-host | 获取eureka server 的代理主机名 | null |
eureka.client.proxy-password | 获取eureka server 的代理主机密码 | null |
eureka.client.proxy-port | 获取eureka server 的代理主机端口 | null |
eureka.client.proxy-user-name | 获取eureka server 的代理用户名 | null |
eureka.client.region | 获取此实例所在的区域(在AWS数据中心中使用)。 | us-east-1 |
eureka.client.should-enforce-registration-at-init client | 在初始化阶段是否强行注册到注册中心 | false |
eureka.client.should-unregister-on-shutdown client | 在shutdown情况下,是否显示从注册中心注销 | true |
参数名称 | 说明 | 默认值 |
---|---|---|
eureka.instance.appname | 注册到注册中心的应用名称 | unknown |
eureka.instance.a-s-g-name | 注册到注册中心的应用所属分组名称(AWS服务器) | null |
eureka.instance.app-group-name | 注册到注册中心的应用所属分组名称 | null |
eureka.instance.data-center-info | 指定服务实例所属数据中心 | |
eureka.instance.instance-enabled-onit | 指示是否应在eureka注册后立即启用实例以获取流量 | false |
eureka.instance.non-secure-port | http通信端口 | 80 |
eureka.instance.non-secure-port-enabled | 是否启用HTTP通信端口 | true |
eureka.instance.secure-port | HTTPS通信端口 | 443 |
eureka.instance.secure-port-enabled | 是否启用HTTPS通信端口 | false |
eureka.instance.secure-virtual-host-name | 服务实例安全主机名称(HTTPS) | unknown |
eureka.instance.virtual-host-name | 该服务实例非安全注解名称(HTTP) | unknown |
eureka.instance.secure-health-check-url | 该服务实例安全健康检查地址(URL) | 绝对地址 |
eureka.instance.lease-renewal-interval-in-seconds | 该服务实例向注册中心发送心跳间隔(s) | 30 |
eureka.instance.lease-expiration-duration-in-seconds | 指示eureka服务器在删除此实例之前收到最后一次心跳之后等待的时间(s) | 90 |
eureka.instance.metadata-map.* | 自定义元数据,符合KEY/VALUE的存储格式。这 些元数据可以在远程客户端中访问。 | |
eureka.instance.ip-address | 该服务实例的IP地址 | null |
eureka.instance.prefer-ip-address | 是否优先使用服务实例的IP地址,相较于hostname | false |
eureka.instance.status-page-url | 该服务实例的状态检查地址(url),绝对地址 | null |
eureka.instance.status-page-url-path | 该服务实例的状态检查地址,相对地址 | /actuator/info |
eureka.instance.home-page-url | 该服务实例的主页地址(url),绝对地址 | |
eureka.instance.home-page-url-path | 该服务实例的主页地址,相对地址 | / |
eureka.instance.health-check-url | 该服务实例的健康检查地址(url),绝对地址 | null |
eureka.instance.health-check-url-path | 该服务实例的健康检查地址,相对地址 | /actuator/health |
eureka.instance.instance-id | 该服务实例在注册中心的唯一实例ID | |
eureka.instance.hostname | 该服务实例所在主机名 | |
eureka.instance.namespace | 获取用于查找属性的命名空间。 在Spring Cloud中被忽略。 | eureka |
eureka.instance.environment | 该服务实例环境配置 | |
eureka.instance.default-address-resolution-order | 默认地址解析顺序 | |
eureka.instance.initial-status | 该服务实例注册到Eureka Server 的初始状态 | up |
eureka.instance.registry.default-open-for-traffic-count | 【Eureka Server 端属性】默认开启通信的数量 | 1 |
eureka.instance.registry.expected-number-of-renews-per-min | 【Eureka Server 端属性】每分钟续约次数 | 1 |
参数 | 说明 | 默认值 |
---|---|---|
eureka.server.enable-replicated-request-compression | 复制数据请求时,数据是否压缩 | false |
eureka.server.batch-replication | 节点之间数据复制是否采用批处理 | false |
eureka.server.max-elements-in-peer-replication-pool | 备份池最大备份事件数量 | 1000 |
eureka.server.max-elements-in-status-replication-pool | 状态备份池最大备份事件数量 | 1000 |
eureka.server.max-idle-thread-age-in-minutes-for-peer-replication | 节点之间信息同步线程最大空闲时间(分钟) | 15 |
eureka.server.max-idle-thread-age-in-minutes-for-peer-replication | 节点之间状态同步线程最大空闲时间(分钟) | 10 |
eureka.server.max-threads-for-peer-replication | 节点之间信息同步最大线程数量 | 20 |
eureka.server.max-threads-for-status-replication | 节点之间状态同步最大线程数量 | 1 |
eureka.server.max-time-for-replication | 节点之间信息复制最大通信时长(ms) | 3000 |
eureka.server.min-available-instances-for-peer-replication | 集群中服务实例最小数量,-1 表示单节点 | -1 |
eureka.server.min-threads-for-peer-replication | 节点之间信息复制最小线程数量 | 5 |
eureka.server.min-threads-for-status-replication | 节点之间信息状态同步最小线程数量 | 1 |
eureka.server.number-of-replication-retries | 节点之间数据复制时,可重试次数 | 5 |
eureka.server.peer-eureka-nodes-update-interval-ms | 节点更新数据间隔时长(分钟) | 10 |
eureka.server.peer-eureka-status-refresh-time-interval-ms | 节点之间状态刷新间隔时长(ms) | 30000 |
eureka.server.peer-node-connect-timeout-ms | 节点之间连接超时时长(ms) | 200 |
eureka.server.peer-node-connection-idle-timeout-seconds | 节点之间连接后,空闲时长(s) | 30 |
eureka.server.peer-node-read-timeout-ms | 几点之间数据读取超时时间(ms) | 200 |
eureka.server.peer-node-total-connections | 集群中节点连接总数 | 1000 |
eureka.server.peer-node-total-connections-per-host | 节点之间连接,单机最大连接数量 | 500 |
eureka.server.registry-sync-retries | 节点启动时,尝试获取注册信息的次数 | 500 |
eureka.server.registry-sync-retry-wait-ms | 节点启动时,尝试获取注册信息的间隔时长(ms) | 30000 |
eureka.server.wait-time-in-ms-when-sync-empty | 在Eureka服务器获取不到集群里对等服务器上的实例时,需要等待的时间(分钟) | 5 |
<?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>cloud2024</artifactId>
<groupId>com.my.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-eureka-server7001</artifactId>
<dependencies>
<!--eureka-server-->
<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>
<!--一devtools热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
</project>
spring:
application:
name: Eureka-Service7001
server:
port: 7001
eureka:
client:
register-with-eureka: true #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
#单机就是7001自己
defaultZone: http://eureka7001.com:7001/eureka/
#集群指向其它eureka
#defaultZone: http://eureka7002.com:7002/eureka/
instance:
#向注册中心注册服务ID
instance-id: ${spring.cloud.client.ip-address}:${server.port}
prefer-ip-address: true #显示IP地址
server:
#Eureka自我保护机制,(默认是开启的)
enable-self-preservation: true
# 设置心跳时间(eureka默认心跳时间是30秒)
eviction-interval-timer-in-ms: 30000
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer //eureka server 注册中心
public class EurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7001.class, args);
}
}
到这里服务注册中心就基本搭建完成了,现在我们需要将其他模块中的服务注册进来
cloud-provider-payment8001将注册进EurekaServer成为服务提供者provider
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
server:
port: 8001
spring:
application:
name: cloud-payment-service
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
defaultZone: http://localhost:7001/eureka
instance:
instance-id: payment8001
prefer-ip-address: true #访问路径可以显示IP地址
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;
/**
*
*/
@SpringBootApplication
@EnableEurekaClient//代表Eureka客户端
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class, args);
}
}
先要启动EurekaServer,然后启动cloud-provider-payment8001模块,最后访问http://localhost:7001/
微服务RPC远程服务调用最核心的是什么?
高可用,试想你的注册中心只有一个only one, 它出故障了那就呵呵了,会导致整个为服务环境不可用。
解决办法:搭建Eureka注册中心集群 ,实现负载均衡+故障容错
集群实现方式:多台Eureka Server互相注册
Eureka集群原理说明
注册中心这么关键的服务,如果是单点话,遇到故障就是毁灭性的。在一个分布式系统中,服务注册中心是最重要的基础部分,理应随时处于可以提供服务的状态。为了维持其可用性,使用集群是很好的解决方案。Eureka通过互相注册的方式来实现高可用的部署,所以我们只需要将Eureke Server配置其他可用的serviceUrl就能实现高可用部署。
在生产中我们可能需要三台或者大于三台的注册中心来保证服务的稳定性,配置的原理其实都一样,将注册中心分别指向其它的注册中心。这里只介绍三台集群的配置情况,其实和双节点的注册中心类似,每台注册中心分别又指向其它两个节点即可,使用application.yml来配置。
Eureka Server 集群环境构建步骤
参考cloud-eureka-server7001微服务
<!--eureka-server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
找到C:\Windows\System32\drivers\etc路径下的hosts文件,添加如下配置
不添加这个使用端口号访问的方式也是可以的,做这个由于咱是在一台机器上面模拟的
7001:
spring:
application:
name: Eureka-Service7001
server:
port: 7001
eureka:
client:
register-with-eureka: false #false表示不向注册中心注册自己
fetch-registry: false #false表示自己就是注册中心,我的职责就是维护服务实例,不需要去检索服务
service-url:
#单机就是7001自己
#defaultZone: http://eureka7001.com:7001/eureka/
#集群指向其它eureka
defaultZone: http://eureka7002.com:7002/eureka/
instance:
#向注册中心注册服务ID
instance-id: ${spring.cloud.client.ip-address}:${server.port}
prefer-ip-address: true #显示IP地址
server:
#Eureka自我保护机制,(默认是开启的)
enable-self-preservation: true
# 清除无效服务实例的时间间隔(默认60000 ms)
eviction-interval-timer-in-ms: 60000
7002:
spring:
application:
name: Eureka-Service7002
server:
port: 7002
eureka:
client:
register-with-eureka: true #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
#单机就是7002自己
#defaultZone: http://eureka7002.com:7002/eureka/
#集群指向其它eureka
defaultZone: http://eureka7001.com:7001/eureka/
instance:
#向注册中心注册服务ID
instance-id: ${spring.cloud.client.ip-address}:${server.port}
prefer-ip-address: true #显示IP地址
server:
#Eureka自我保护机制,(默认是开启的)
enable-self-preservation: true
# 清除无效服务实例的时间间隔(默认60000 ms)
eviction-interval-timer-in-ms: 60000
集群搭建好之后,查看这两台注册中心是否已经户向注册
可以看到,两台节点互相注册成功,一般集群最少也要搭建三个节点,这里为了方便就两个
服务的提供者,将服务注册到Eureka当中
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
server:
# 服务器的HTTP端口
port: 8001
# Spring配置
spring:
application:
name: cloud-payment-service #微服务名称
#配置数据源
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driverClassName: com.mysql.cj.jdbc.Driver # mysql驱动包 com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db2024?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: 123456
#eureka配置
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
#defaultZone: http://localhost:7001/eureka # 单机版
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 集群版
instance:
#向注册中心注册服务ID
instance-id: ${spring.cloud.client.ip-address}:${server.port}
prefer-ip-address: true #访问路径可以显示IP地址
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-renewal-interval-in-seconds: 30
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 90
#mybatis配置
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.my.springcloud.entities # 所有Entity别名类所在包
package com.my.springcloud;
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;
@SpringBootApplication
@EnableEurekaClient //服务的提供者,将服务注册到Eureka当中
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class, args);
}
}
import com.my.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface PaymentDao {
public int create(Payment payment);
public Payment getPaymentById(@Param("id") Long id);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.my.springcloud.dao.PaymentDao">
<resultMap id="BaseResultMap" type="com.my.springcloud.entities.Payment">
<id column="id" property="id" jdbcType="BIGINT"/>
<id column="serial" property="serial" jdbcType="VARCHAR"/>
</resultMap>
<select id="getPaymentById" parameterType="Long" resultMap="BaseResultMap">
select *
from payment
where id = #{id};
</select>
<insert id="create" parameterType="Payment" useGeneratedKeys="true" keyProperty="id">
insert into payment(serial)
values (#{serial});
</insert>
</mapper>
import com.my.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Param;
public interface PaymentService {
public int create(Payment payment);
public Payment getPaymentById(@Param("id") Long id);
}
import com.my.springcloud.dao.PaymentDao;
import com.my.springcloud.entities.Payment;
import com.my.springcloud.service.PaymentService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class PaymentServiceImpl implements PaymentService {
@Resource
private PaymentDao paymentDao;
public int create(Payment payment) {
return paymentDao.create(payment);
}
public Payment getPaymentById(Long id) {
return paymentDao.getPaymentById(id);
}
}
import com.my.springcloud.utils.RestResponse;
import com.my.springcloud.entities.Payment;
import com.my.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.TimeUnit;
@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
@GetMapping(value = "/payment/get/{id}")
public RestResponse<Payment> getPaymentById(@PathVariable("id") Long id) {
Payment payment = paymentService.getPaymentById(id);
if (payment != null) {
return RestResponse.success(200, "查询成功,serverPort: " + serverPort, payment);
} else {
return RestResponse.fail(444, "没有对应记录,查询ID: " + id, null);
}
}
@PostMapping(value = "/payment/create")
public RestResponse create(@RequestBody Payment payment) {
int result = paymentService.create(payment);
log.info("*****插入结果:" + result);
if (result > 0) {
return RestResponse.success(200, "插入数据库成功,serverPort: " + serverPort, result);
} else {
return RestResponse.fail(444, "插入数据库失败", null);
}
}
}
先启动EurekaServer:7001和7002服务
再启动服务提供者 cloud-provider-payment8001查看服务注册情况
cloud-provider-payment8001服务已经注册成功
测试服务是否正常: 访问 http://localhost/payment/get/1
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
server:
port: 80
spring:
application:
name: cloud-order-service
#eureka配置
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
#defaultZone: http://localhost:7001/eureka # 单机版
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 集群版
instance:
#向注册中心注册服务ID
instance-id: ${spring.cloud.client.ip-address}:${server.port}
prefer-ip-address: true #访问路径可以显示IP地址
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-renewal-interval-in-seconds: 30
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 90
Ribbon搭配Eureka实现负载均衡,在RestTemplate上添加@LoadBalanced注解
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* @auther
* @desc Ribbon负载均衡配置类(Ribbon默认使用的是RoundRobinRule 轮询策略)
*/
@Configuration
public class LoadConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
package com.my.springcloud;
import com.my.myrule.MyLoadConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
@SpringBootApplication
@EnableEurekaClient
public class OrderConsumer80 {
public static void main(String[] args) {
SpringApplication.run(OrderConsumer80.class, args);
}
}
@RestController
@Slf4j
public class OrderController {
public static final String INVOKE_URL = "http://cloud-provider-payment";
@Resource
private RestTemplate restTemplate;
@GetMapping("/consumer/payment/get/{id}")
public RestResponse<Payment> getPayment(@PathVariable("id") Long id) {
return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, RestResponse.class);
}
}
查看服务是否注册成功, 访问 http://localhost:7001/
生产环境部署都是多台实例,这里为了演示方便:模拟下多节点
指定端口号
-Dserver.port=8002
启动刚才复制8003服务
查看服务是否注册成功, 访问 http://localhost:7001/
测试调用服务提供者,是否能成功消费
(1)启动eureka 7001和7002,服务提供者8001和8002,服务消费者 consumer-order80
(2)访问 http://localhost/consumer/payment/get/1
保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务。
简单一句话:(好死不如赖活着)
用电视剧新三国中,曹操说的:“宁可天下人负我,我不负天下人”即:Eureka服务端,进入自我保护模式,就算所有的微服务真的都出问题了,也不会里面删除它们的
一句话:某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该服务的信息进行保存。属于CAP里面的AP分支,也即是:保证可用性、分区容错性。
PS:CAP原则:分布式系统中,C:一致性;A:可用性;P:分区容错性。
我们来看看百度百科对CAP原则的详细介绍,如下图:
综上,自我保护是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的、不健康的微服务都会保留),也不盲目注销任何健康的微服务。
职用自我保护模式,可以让Eureka集群更加的健壮和稳定
修改Eureka Service7001application.yml配置文件:
(1)关闭自我保护
(2)清除无效服务实例的时间间隔修改成2秒
eureka:
server:
#Eureka自我保护机制,(默认是开启的)
enable-self-preservation: false
# 清除无效服务实例的时间间隔修改成2秒
eviction-interval-timer-in-ms: 2000
启动7001,访问 http://localhost:7001/
这句话的意思是: 关闭自我保护模式,在网络/其他问题的情况下,这可能无法保护实例过期。
修改Eureka client8001application.yml配置文件:
(1)修改eureka客户端向服务端发送心跳的时间间隔
(2)修改Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
#eureka配置
eureka:
instance:
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 2
启动7001和8001 ,访问 http://localhost:7001/
我这里直接模拟8001服务宕机:关闭8001服务
此时访问 http://localhost:7001/ 发现8001服务被剔除了