如果你有自己的私人医生,那么你需要时直接与医生进行联系就可以。但大多数人都需要去医院,医院有很多病人,也有很多医生,那么就需要一个窗口来挂号、取号、管理余号等等。同样的道理,当我们的服务数量变得多起来,就需要进行服务注册与发现的管理了。
注:心跳连接就是指像心跳一样周期性的监测服务是否可用。
它与服务提供者、消费者的关系如下图。
(1)建cloud-eureka-server7001模块
(2)写pom
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
<dependency>
<groupId>com.wangzhou.springcloudgroupId>
<artifactId>cloud-api-commonsartifactId>
<version>${project.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
(3)写yml
application.yml
server:
port: 7001
eureka:
instance:
hostname: localhost #eureka服务端的实例名称
client:
#false表示不向注册中心注册自己(想注册也可以,不过没必要)
register-with-eureka: false
#false表示自己端就是注册中心,职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
#设置与eurekaServer交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
(4)主启动
@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7001.class ,args);
}
}
(5)测试
启动模块,访问Eureka,可以看到如下界面。
我们要把服务的提供者:支付微服务入驻到EurekaServer。这很像培训机构(如尚硅谷)向物业公司进行注册。
(1)引入依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
(2)标注解
主启动类标注解@EnableEurekaClient
。
@EnableEurekaClient
@SpringBootApplication
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class, args);
}
}
(3)改yaml
eureka:
client:
#true表示向注册中心注册自己,默认为true
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka
访问:Eureka,可以看到8001服务已经入驻。
服务的消费者:订单微服务也需要入驻到Eureka.
(1)改pom
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
(2)改yaml
spring:
application:
name: cloud-order-service
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka
(3)主启动
@EnableEurekaClient
@SpringBootApplication
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class, args);
}
}
(4)测试
Eureka工作流程可以参考下图。
微服务RPC的核心就是高可用,如果某个Eureka服务节点出现故障,不能够影响整体的服务环境。我们可以搭建Euraka注册中心集群,实现负载均衡和故障容错。参考下图,7001和7002互相注册,然后对外暴露出一个整体。集群主机数量可以随着业务量而定。
(1)建module
cloud-eureka-server7002
(2)写pom
直接cv7001
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
<dependency>
<groupId>com.wangzhou.springcloudgroupId>
<artifactId>cloud-api-commonsartifactId>
<version>${project.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
(3)改yaml
是不是也可以直接cv7001呢?
server:
port: 7002
eureka:
instance:
hostname: localhost #eureka服务端的实例名称
client:
#false表示不向注册中心注册自己(想注册也可以,不过没必要)
register-with-eureka: false
#false表示自己端就是注册中心,职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
#设置与eurekaServer交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
读者看看看7001与7002配置文件hostname
完全相同,无法进行区分。
由于我们现在只有一个主机,我们可以修改C:\Windows\System32\drivers\etc\hosts实现将1个ip地址映射为多个网址。
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
现在修改两个模块的yaml文件的hostname
,并且让他们相互注册。
7001
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com #eureka服务端的实例名称
client:
#false表示不向注册中心注册自己(想注册也可以,不过没必要)
register-with-eureka: false
#false表示自己端就是注册中心,职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
#设置与eurekaServer交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://eureka7002.com:7002/eureka/
7002
server:
port: 7002
eureka:
instance:
hostname: eureka7002.com #eureka服务端的实例名称
client:
#false表示不向注册中心注册自己(想注册也可以,不过没必要)
register-with-eureka: false
#false表示自己端就是注册中心,职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
#设置与eurekaServer交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://eureka7001.com:7001/eureka/
(4)主启动
@EnableEurekaServer
@SpringBootApplication
public class EurekaMain7002 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7002.class ,args);
}
}
(5)测试
访问http://eureka7001.com:7001/,http://eureka7002.com:7002/
将80,8001模块yaml文件修改如下。
测试,重启各个服务,注意先启动EurekaServer(7001/7002),再启动服务提供模块(8001),再启动服务消费模块(80)。
测试结果如下。
现在Eureka服务注册中心已经是集群服务了,我们还要把支付服务也变成支付集群服务。
(1)建module
新建cloud-provider-payment8002
(2)改pom
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.1.10version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>com.wangzhou.springcloudgroupId>
<artifactId>cloud-api-commonsartifactId>
<version>${project.version}version>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
dependencies>
(3)写yaml
# 服务端口号
server:
port: 8002
# 服务名称
spring:
application:
name: cloud-payment-service
#数据库配置
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: com.mysql.jdbc.Driver # mysql驱动包
url: jdbc:mysql://localhost:3306/cloud2021?useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: "root"
password: "123456"
eureka:
client:
#true表示向注册中心注册自己,默认为true
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.wangzhou.springcloud.entities # 所有Bean 别名类所在包
(4)主启动与业务类
直接在存储目录进行cv(如下图),注意,不要在Idea中进行拷贝,可能会爆红。把主启动类的名字改为PaymentMain8002
。
8001,8002模块对于外部暴露的服务名称是一样的,如何区分是哪个服务模块进行请求的响应呢?
可以在两个模块的controller中增加serverPort
属性,在请求方法中使用。
@Value("${server.port}")
private String serverPort;
@PostMapping("/create")
public CommonResult create(@RequestBody Payment payment) {
int result = service.create(payment);
log.info("插入结果是:" + result);
if(result > 0) {
return new CommonResult(200, "插入数据成功, serverPort:" + serverPort, result);
} else {
return new CommonResult(444, "插入数据失败");
}
}
@GetMapping(value = "/get/{id}")
public CommonResult getPaymentById(@PathVariable("id")Long id){
Payment result = service.getPaymentById(id);
log.info("-----查询结果----: "+ result);
if (result != null){
//查询成功
return new CommonResult(200,"查询成功, serverPort:" + serverPort,result);
}else {
return new CommonResult(444,"没有对应记录,查询id: "+id,null);
}
}
serverPort
其实是从yml文件中读取的。
按顺序(Eureka服务7001,7002、服务提供模块8001,8002、服务消费模块80,后面不再赘述)启动各个服务模块。
(5)测试
看上去没有问题,不过您如果多尝试发几次上面的请求,会发现每次使用的端口都是8001.这是因为我们订单服务把访问的url写死了。
可以将其改为支付微服务统一暴露的服务名称.
public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";
改完以后测试,发现程序奔溃。
这是因为我们使用支付微服务统一暴露的名称CLOUD-PAYMENT-SERVICE
进行访问,但是我们程序并不知道需要8001还是8002响应服务.也就是说很多老师在一起办了学校,现在学校办好了,可以统一使用学校的招牌招生了,但具体哪个老师教哪个学生的分配机制我们还没有确立。
现在我们来实现这个机制:负载均衡机制。在OrderPayment模块的ApplicationContextConfig
类中添加注解@LoadBalanced
。
添加后测试发现8001端口测试跑通,8002响应时还是返回异常页面。可以仔细比对两个模块,发现原来少拷贝了内容、将8001中的PaymentMapper.xml
拷贝到8002模块,重新启动服务即可。
不知您是否发现下图暴露了我们电脑的主机名称。我们认为,我们暴露主机名称毫无意义,而且我们希望将ip地址暴露出去。
在8001,8002,80模块的配置类中增加instance
属性即可。
当鼠标放到8001上去,左下角出现了ip地址。
现在Eureka就像一个大的中介公司,里面注册许多服务,比如payment服务集群8001,8002。不过目前为止我们的服务对于其它服务的细节并不清楚,我们有必要提供一个机制把相关信息暴露出去,为此Eureka提供了服务发现机制。这很类似于我们在租房中介公司看各个房源的信息。
在8001模块的PaymentController
中增加如下代码。
@Resource
private DiscoveryClient discoveryClient;
@GetMapping("/discovery")
public Object Discovery() {
List<String> services = discoveryClient.getServices();
for (String service : services) {
log.info("****service:****" + service);
}
List<ServiceInstance> instances = discoveryClient.getInstances("cloud-payment-service");
for (ServiceInstance instance : instances) {
log.info(instance.getServiceId() + "\t" + instance.getHost() + "\t" +instance.getUri());
}
return this.discoveryClient;
}
注:import org.springframework.cloud.client.discovery.DiscoveryClient;
导包不要导错。
在主启动类中增加注解@EnableDiscoveryClient
。
访问localhost:8001/payment/discovery
idea后台可以看到。
事实上,我们可以让80Order
端口获得payment
服务的信息。这更加贴近实际的需求场景。
细心的同学可能发现,访问Eureka (eureka7002.com),会有如下提醒.
如果出现上面的提醒,说明Eureka进入了保护模式,这说明即使某一刻一个微服务变得不可用了,因为可能因为网络延时,卡顿等各种原因没有收到微服务的心跳连接,Eureka也不会立刻清理该服务,这是Eureka对于该服务存在重新恢复可能性的一种现场保护机制,比如疫情期间某商铺无法缴纳租金费,房东可能可以允许其拖欠几天,而不是立刻清理出户.
在7001模块中可以添加如下内容关闭自我保护机制.
在8001模块配置如下
eureka:
instance:
#Eureka客户端向服务端发送心跳的时间间隔,单位秒(默认30秒)
lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待的时间上限,单位秒(默认90秒),超时剔除服务
lease-expiration-duration-in-seconds: 2
访问Eureka (eureka7001.com),提示自我保护机制关闭.
停掉8001服务.可以看到果然服务立刻被清理了.