微服务笔记之Eureka(1)
1、Eureka是什么?
Eureka由Netflix开源,并被Pivatal集成到SpringCloud体系中,它是基于 RestfulAPI 风格开发的服务注册与发现组件,它是一个服务注册中心。
服务注册中心本质上是为了解耦服务提供者和服务消费者。
在服务注册中心之前,服务消费者调用服务提供者,会把服务提供者的url地址硬编码到代码中(代码如下),这样做的坏处是不方便后期维护,以及不知道服务提供者的状态信息。
@RestController
@RequestMapping("/page")
public class PageController {
@Resource
private RestTemplate restTemplate;
@GetMapping("/query/{id}")
public Products getProductById(@PathVariable int id){
Products products = restTemplate.getForObject("http://localhost:9999/product/query/" + id, Products.class);
return products;
}
}
2、服务注册中心如何解耦?
分布式微服务架构中服务注册中心用于存储服务提供者地址信息、服务发布相关的属性信息,消费者通过主动查询和被动通知的方式获取服务提供者的地址信息,而不再需要通过硬编码方式得到提供者的
地址信息。消费者只需要知道当前系统发布了那些服务,而不需要知道服务具体存在于什么位置, 这就是透明化路由。如下图:
上图说明:
- 服务提供者、服务消费者启动
- 服务提供者将相关服务信息主动注册到注册中心
- 服务消费者获取服务注册信息:服务消费者直接调用服务提供者
- pull模式:服务消费者可以主动拉取可用的服务提供者清单
- push模式:服务消费者订阅服务(当服务提供者有变化时,注册中心也会主动推送更新后的服务清单给消费者
- 服务消费者直接调用服务提供者
注册中心也需要完成服务提供者的健康监控,当发现服务提供者失效时需要及时剔除;
3、那么讲完服务注册中心,我们来看看Eureka的基础架构
Eureka交互流程
Eureka 包含两个组件:Eureka Server 和 Eureka Client,Eureka Client是一个Java客户端,用于简化与Eureka Server的交互;Eureka Server提供服务发现的能力,各个微服务启动时,会通过EurekaClient向Eureka Server 进行注册自己的信息(例如网络信息),Eureka Server会存储该服务的信息;
- Application Service作为服务提供者向Eureka Server中注册服务,Eureka Server接受到注册事件会在集群和分区中进行数据同步,ApplicationClient作为消费端(服务消费者)可以从Eureka Server中获取到服务注册信息,进行服务调用。
- 微服务启动后,会周期性地向Eureka Server发送心跳(默认周期为30秒,默认Eureka Server 90S会将还没有续约的给剔除)以续约自己的信息
- Eureka Server在一定时间内没有接收到某个微服务节点的心跳,Eureka Server将会注销该微服务节点(默认90秒)
- 每个Eureka Server同时也是Eureka Client,多个Eureka Server之间通过复制的方式完成服务 注册列表的同步
- Eureka Client会缓存Eureka Server中的信息。即使所有的Eureka Server节点都宕掉,服务消费者依然可以使用缓存中的信息找到服务提供者
总之,Eureka通过心跳检测、健康检查和客户端缓存等机制,提高系统的灵活性、可伸缩性和高可用性。
4、Eureka服务端详解
4.1、服务下线
当服务正常关闭操作时,会发送服务下线的REST请求给EurekaServer
服务中心接收到请求后,将该服务置为下线状态
4.2、失效剔除
Eureka Server会定时(间隔值是eureka.server.eviction-interval-timer-in-ms,默认60s)进行检查,如果发现实例在一定时间(此值由客户端设置的eureka.instance.lease-expiration-duration-in-seconds定义,默认值为90s)内没有收到心跳,则会注销此实例
4.3、自我保护机制
自我保护模式正是一种针对网络异常波动的安全保护措施,使用自我保护模式能使Eureka集群更加的健壮、稳定的运行。自我保护机制的工作机制是:如果在15分钟内超过85%的客户端节点都没有正常心跳,那么Eureka就认为客户端与注册中心出现了网络故障,Eureka Server自动进入自我保护机制,此时会出现一下几种情况:
- Eureka Server不再从注册列表中移除,因为长时间没收到心跳而应该过期的服务
- Eureka Server仍然能够接收新服务的注册和查询请求,但是不会被同步到其他节点上,保证当前节点依然可用
- 当网络稳定时,当前Eureka Server新的注册信息会被同步到其他节点中
- 当我们导入eureka-client依赖坐标,配置Eureka服务注册中心地址
- 服务在启动时会向注册中心发起注册请求,并携带服务元数据信息
- Eureka注册中心会把服务的信息保存在Map中(ConcurrentHashMap)
5.2、服务续约详解(服务提供者)
服务每隔30s回向注册中心续约一次,如果没有续约,租约在90s后到期,然后服务会被失效。每隔30s的续约操作我们称之为心跳检测。
- Eureka Client:30s续约一次,在Eureka Server更新自己的状态(Client端进行配置)
- Eureka Server:90s还没有进行续约,将该微服务实例从服务注册表(Map)剔除(Client端进行配置)
- Eureka Client: 30s拉取服务最新的注册表并缓存到本地(Client端进行配置)
通过以下配置
#向Eureka服务中心集群注册服务
eureka:
instance:
# 租约续约间隔时间,默认30秒
lease-renewal-interval-in-seconds: 30
# 租约到期,服务时效时间,默认值90秒,服务超过90秒没有发生心跳,EurekaServer会将服务从列 表移除
lease-expiration-duration-in-seconds: 90
5.3、获取服务列表详解
每个30s服务会从注册中心拉取一份服务列表,这个时间可以通过配置修改。但往往不需要我们调整
- 服务消费者启动时,从Eureka Server服务列表获取只读备份,缓存到本地
- 每隔30s会重新获取并更新数据
- 每隔30s可以通过配置eureka.client.registry-fetch-interval-seconds修改
#向Eureka服务中心集群注册服务
eureka:
client:
# 每隔多久拉取一次服务列表
registry-fetch-interval-seconds: 30
6、Eureka的元数据
Eureka元数据有两种:标准元数据和自定义元数据
6.1、标准元数据
它包含主机名、IP地址、端口号等信息,这些信息都会被发布在服务注册表中,用于服务之间的调用
6.2、自定义元数据
它可以使用eureka.instance.metadata-map配置,符合KEY/VALUE的存储格式。这些元数据可以在远程客户端中访问
通过以下代码调试获取
@RestController
@RequestMapping("/metadata")
public class MetaDataController {
@Resource
private DiscoveryClient discoveryClient;
@GetMapping("/show")
public Map<String,String> getMetaData(){
List<ServiceInstance> instances = discoveryClient.getInstances("hy-service-product");
ServiceInstance serviceInstance = instances.get(0);
Map<String, String> metadata = serviceInstance.getMetadata();
return metadata;
}
}
7、Eureka的应用
项目结构
第一步、在父目录的pom文件中引入下面内容
<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>
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.1.6.RELEASE</version>
</parent>
其中dependency里面的version内容要和parent里面的version要对应,否则会报错
第二步:在两个Eureka服务的pom文件引入
<!--引入Eureka Server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
第三步:配置Eureka服务下面的yml文件(记得服务端口号要修改)
server:
port: 11111
spring:
application:
name: hy-cloud-eureka-server # 应用名称,会在Eureka中作为服务的id标识(serviceId)
eureka:
instance:
hostname: HyCloudEurekaServiceA
client:
service-url: # 客户端与EurekaServer交互的地址,如果是集群,也需要写其它Server的地址
defaultZone: http://HyCloudEurekaServiceB:22222/eureka/
register-with-eureka: true # 自己就是服务不需要注册自己
fetch-registry: true #自己就是服务不需要从Eureka Server获取服务信息,默认为true,置为false
第四步:启动类
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
第五步:配置两个微服务下的yml文件
page:
server:
port: 8888 # 后期该微服务多实例,端口从9100递增(10个以内)
Spring:
application:
name: hy-service-page
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/hy?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
username: "root"
password: "123456"
eureka:
client:
serviceUrl: # eureka server的路径
defaultZone: http://HyCloudEurekaServiceA:11111/eureka/,http://HyCloudEurekaServiceB:22222/eureka/
instance:
#使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip)
prefer-ip-address: true
#自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
product:
server:
port: 9999 # 后期该微服务多实例,端口从9100递增(10个以内)
Spring:
application:
name: hy-service-product
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/hy?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
username: "root"
password: "123456"
eureka:
client:
serviceUrl: # eureka server的路径
defaultZone: http://HyCloudEurekaServiceA:11111/eureka/,http://HyCloudEurekaServiceB:22222/eureka/
instance:
#使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip)
prefer-ip-address: true
#自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
第六步:编写Product的Controller
@RestController
@RequestMapping("/product")
public class ProductController {
@Resource
private ProductService productService;
@RequestMapping("/query/{id}")
public Products query(@PathVariable Integer id){
return productService.findById(id);
}
}
此处省略service代码
第七步:编写Page微服务的Controller
@RestController
@RequestMapping("/page")
public class PageController {
@Resource
private RestTemplate restTemplate;
@Resource
private DiscoveryClient discoveryClient;
@GetMapping("/query/{id}")
public Products getProductById(@PathVariable int id){
List<ServiceInstance> instances = discoveryClient.getInstances("hy-service-product");
System.out.println(instances);
ServiceInstance serviceInstance = instances.get(0);
String host = serviceInstance.getHost();
int port = serviceInstance.getPort();
System.out.println(host + " " + port);
String url = "http://"+host +":" + port +"/product/query/" + id;
Products products = restTemplate.getForObject(url, Products.class);
return products;
}
}
第八步:启动四个服务:随便登录一个Eureka的服务如下图所示,表明启动成功
第九步:测试