微服务:SpringCloud 注册中心(Eureka)+ 源码分析est
一、没有注册中心的模拟微服务
这里有四个项目,其中三个是服务提供者,这三个服务提供者通过@RestController暴露rest接口,供消费者去调用。消费者可以用java代码编写get或者post请求,我们通常用的是RestTmplate,这个spring为我们提供好的类,特别好用!正常情况下,我们就可以很顺利的调用这些服务了。这个是没有问题的!
小伙伴们可能要问,这能调用。我们直接搞就行了,为啥还用注册中心呢?多此一举?
我们来考虑这样几个问题:(1)在我们调用提供者的接口的时候,是不是要将他们的url编写到消费者代码中呢,比如
restTemplate.getForObject("http://192.128.0.1:9002/product/1", Product.class);
如果服务换ip了,换端口了,肿么办?
(2)为了实现系统的高可用,我们部署同一服务的多个服务提供者怎么办?也就是说提供服务A的有两个系统,你又怎么达到负载均衡?
这两个问题没有很好的办法解决吧?
二、注册中心
当服务提供者启动的时候,会像注册中心注册一个信息,“hello 注册中心,帮我注册一下好吗,我的ip是…,我要注册的服务是…”。这个时候注册中心就有了服务提供者的信息,当消费者启动的时候也会向注册中心注册信息,指定注册的要调用的服务。同时拿到服务的信息,消费者拿到对应的信息之后,存了一个小小的备份,当调用的时候,直接调用服务。就是图中最黑的那根线。
服务注册中心(下称注册中心)是微服务架构非常重要的一个组件,在微服务架构里主要起到了协调者
的一个作用。注册中心一般包含如下几个功能:
(1) 服务发现:服务注册/ 反注册:保存服务提供者和服务调用者的信息 。服务订阅/ 取消订阅:服务调用者订阅服务提供者的信息,最好有实时推送的功能。服务路由(可选):具有筛选整合服务提供者的能力。
(2) 服务配置: 配置订阅:服务提供者和服务调用者订阅微服务相关的配置。配置下发:主动将配置推送给服务提供者和服务调用者。
(3)服务健康检测 :检测服务提供者的健康情况。
常见的注册中心: Zookeeper、Eureka、Consul 、Nacos。
我们主要研究的是 Eureka。
三、Eureka注册中心
服务提供者会向Eureka注册一个自己的信息,Eureka会保存所有的信息到内存中,提供者还会每30s向Eureka发送一次心跳,表明“我还活着”,如果长时间收不到服务的心跳,认为宕机,从内存中删除信息。
服务消费者启动的时候会向注册中心获取所有服务的信息,调用的时候直接调用。可能兆成信息不一致的情况。
四、搭建注册中心
1、创建一个父工程
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.springcloud.demo</groupId>
<artifactId>SpringCloudDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>order_service</module>
<module>product_service</module>
<module>eureka_server</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<scope>provided</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2、创建子工程eureka_server子工程
<?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>SpringCloudDemo</artifactId>
<groupId>com.springcloud.demo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>eureka_server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
</project>
配置文件
#模拟两个EurekaServer
#端口9000 , 8000
#两个server需要相互注册
spring:
application:
name: eureka-server
server:
port: 9000 #端口
#配置eureka server
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false #是否将自己注册到注册中心
fetch-registry: false #是否从eureka中获取注册信息
service-url: #配置暴露给Eureka Client的请求地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
启动类
package com.springcloud.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* @author Shubo Dai
* @description 注册中心启动类
*/
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class,args);
}
}
启动注册中心!
药药切克闹,是不是特别简单?
五、注册服务到注册中心
再新创建一个 项目,叫做product_service。这里我之前是用的jpa查询的数据库,这块的具体的信息我就不再展示了。可以去了解一下jpa好吧。
<?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>SpringCloudDemo</artifactId>
<groupId>com.springcloud.demo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>product_service</artifactId>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--引入EurekaClient-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
</project>
server:
port: 9011 #端口
spring:
application:
name: service-product #服务名称
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=utf8
username: root
password:
jpa:
database: MySQL
show-sql: true
open-in-view: true
#配置Eureka
eureka:
client:
service-url:
defaultZone: http://localhost:9000/eureka/ #多个eurekaserver之间用,隔开
instance:
prefer-ip-address: true #使用ip地址注册
eureka配置信息,这里面指定注册中心的地址和使用什么进行注册。
package com.springcloud.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @author Shubo Dai
* @description 提供者服务类,@EnableEurekaClient、@EnableDiscoveryClient标识激活eureka客户端
*/
@SpringBootApplication
@EntityScan("com.springcloud.demo.entity")
@EnableEurekaClient
public class ProductApplication {
public static void main(String[] args) {
SpringApplication.run(ProductApplication.class,args);
}
}
启动之后,显示注册成功
这里实际上还有dao、service、controller,可以自己想怎么写怎么写,这里不展开了!!!!
六、消费者通过注册中心调用提供者
<?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>SpringCloudDemo</artifactId>
<groupId>com.springcloud.demo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>order_service</artifactId>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--引入EurekaClient-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
</project>
server:
port: 9002 #端口
spring:
cloud:
loadbalancer:
retry:
enabled: true # 开启Spring Cloud的重试功能
application:
name: service-order #服务名称
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=utf8
username: root
password: 111111
jpa:
database: MySQL
show-sql: true
open-in-view: true
#配置Eureka
eureka:
client:
service-url:
defaultZone: http://localhost:9000/eureka/,http://localhost:8000/eureka/
instance:
prefer-ip-address: true #使用ip地址注册
package com.springcloud.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
/**
* @ClassName OrderApplication
* @Description
* @Author 戴书博
* @Date 2020/5/21 10:23
* @Version 1.0
**/
@SpringBootApplication
@EntityScan("com.springcloud.demo.entity")
public class OrderApplication {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class,args);
}
}
package com.springcloud.demo.controller;
import com.springcloud.demo.entity.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@RequestMapping(value = "/buy/{id}",method = RequestMethod.GET)
public Product findById(@PathVariable Long id) {
List<ServiceInstance> list = discoveryClient.getInstances("service-product");
ServiceInstance serviceInstance = list.get(0);
Product product = restTemplate.getForObject("http://"+serviceInstance.getHost()+":"
+serviceInstance.getPort()
+"/product/1",Product.class);
return product;
}
}
启动服务试一下。
注册中心
可以调用成功!!!
七、注册中心的高可用
如果我们只有一个注册中心的话,那么这个注册中心宕机了,怎么办?为了保证高可用,那么我们可以搞成一个eureka集群。
在注册中心一块我们只需要修改配置文件就可以了,比如两个注册中心的端口:8000、9000。需要相互获取信息、将自己注册到对应的注册中心中。
8000端口的配置文件:
#模拟两个EurekaServer
#端口9000 , 8000
#两个server需要相互注册
spring:
application:
name: eureka-server
server:
port: 9000 #端口
#配置eureka server
eureka:
client:
service-url: #配置暴露给Eureka Client的请求地址
defaultZone: http://127.0.0.1:8000/eureka/
9000端口的配置文件:
#模拟两个EurekaServer
#端口9000 , 8000
#两个server需要相互注册
spring:
application:
name: eureka-server
server:
port: 8000 #端口
#配置eureka server
eureka:
client:
service-url: #配置暴露给Eureka Client的请求地址
defaultZone: http://127.0.0.1:9000/eureka/
服务注册到多个注册中心
实际上由于两个注册中心之间相互都是交换信息的,所以在一个注册上,另一个也会同步。当然我们也可以配置两个都注册。
eureka:
client:
service-url:
defaultZone: http://localhost:9000/eureka/,http://localhost:8000/eureka/
完善配置
eureka:
client:
service-url:
defaultZone: http://localhost:9000/eureka/ #多个eurekaserver之间用,隔开
instance:
prefer-ip-address: true #使用ip地址注册
instance-id: ${spring.cloud.client.ip-address}:${server.port} #向注册中心中注册服务id
lease-renewal-interval-in-seconds: 5 #发送心跳的间隔
lease-expiration-duration-in-seconds: 10 #续约到期的时间
我们在服务提供者设置下面三行配置。
第一个是在注册中心配置一个名称ip+端口号。
第二个是发送心跳的时间默认是30s。
第三个是默认的宕机时间,到了这个时间提供者未向注册中心发送心跳就会认为提供者宕机。
关闭自我保护机制
在eureka内部存在一个自我保护机制,这个机制会统计其他提供者发送心跳的次数和认为宕机的次数。如果发现打部门提供者的心跳都结束了,那么就不再剔除服务。一般测试阶段关闭这个自我保护机制。
eureka:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
server:
enable-self-preservation: false #关闭自我保护
eviction-interval-timer-in-ms: 4000 #剔除服务间隔
最后一个配置是4s执行一次定时任务剔除已经宕机的提供者服务。
8、源码分析
服务端
(1)**EnableEurekaServer**注解作用
我们首先来看一下@EnableEurekaServer注解。
这个类注入了一个Marker对象。 这个对象是实例化核心配置类的前提条件。
(2)**自动装载核心配置类**
首先你必须要了解springboot自动加载机制https://blog.csdn.net/weixin_44588495/article/details/106310221
我们来查看这个类EurekaServerAutoConfiguration
现在我们展开来说这个Eureka服务端的自动配置类:
(1)这个配置类实例化的前提条件是上下文中存在 EurekaServerMarkerConfifiguration.Marker 这个 bean,解释了上面的问题。
(2)通过@EnableConfifigurationProperties({ EurekaDashboardProperties.class, InstanceRegistryProperties.class })导入了两个配置类:
EurekaDashboardProperties : 配置 EurekaServer的管控台
InstanceRegistryProperties : 配置期望续约数量和默认的通信数量
(3) 通过 @Import({EurekaServerInitializerConfifiguration.class}) 引入启动配置类
看一下EurekaServerInitializerConfiguration这个类
(3)EurekaServerInitializerConfifiguration
可以看到 EurekaServerInitializerConfifiguration 实现了 SmartLifecycle ,也就意味着 Spring 容器启动时 会去执行start() 方法。加载所有的 EurekaServer 的配置。
(4)**EurekaServerAutoConfifiguration**
注入了一个controller。实例化了EurekaServer的管控台的Controller类 EurekaController
实际上这个controller跟我们平时写的差不多,他的作用就是配合页面完成控制台相关功能。
注意一下这个方法,再点进去!
是两个包名,实际上就是eureka去扫描包下面的类。查看类是否含有@Path、@Provider、@Produce等注解。
(5)暴露的服务端接口
对应扫描的包
通过扫描这些类生成web的接口,供客户端调用。比如连接等等。这些类都是通过Jersey发布的供客户端调用的服务接口。
jerseyApplication 方法,在容器中存放了一个 jerseyApplication 对象, jerseyApplication() 方法里的 东西和Spring 源码里扫描 @Component 逻辑类似,扫描 @Path 和 @Provider 标签,然后封装成 beandefifinition,封装到 Application 的 set 容器里。通过 fifilter 过滤器来过滤 url 进行映射到对象的 Controller。
(6)服务端接受客户端的注册
在 ApplicationResource.addInstance() 方法中可以看到
我们来看注册的这个方法!!
(7)接受客户端续约
在 InstanceResource 的 renewLease 方法中完成客户端的心跳(续约)处理,其中最关键的方法就是
(8)服务剔除
在 AbstractInstanceRegistry.postInit() 方法,在此方法里开启了一个每 60 秒调用一次 EvictionTask.evict()的定时器。
客户端
(1)自动装载
每个服务我们都只在配置文件中配置了一些信息,然后就能够注册到注册中心等之类的。我们来看一下客户端为我们加载了哪些配置类?
看一下里面这个方法,就是创建了一个注册中心客户端到ioc中
找到这个类。里面参数的clientConfig,其实就是我们在配置文件中配置的信息。
(2)服务注册
实际上这里面是发送了一个http的请求去请求注册中心注册。
(2)服务下架
(3)心跳续约
在 com.netflflix.discovery.DiscoveryClient.HeartbeatThread 中定义了续约的操作 , 我们查看 renew() 方法;
源码:git@gitee.com:Zesystem/springclouddemoerueka.git
还没有评论,来说两句吧...