eureka的详细教程
eureka简介
Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。SpringCloud将它集成在其子项目spring-cloud-netflix中,以实现SpringCloud的服务发现功能。Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。SpringCloud将它集成在其子项目spring-cloud-netflix中,以实现SpringCloud的服务发现功能。目前Eureka 1.x已经不在维护,Eureka 2.x闭源。
单机eureka使用
eureka注册中心注册
1.启动类加注解@EnableEurekaServer
2.属性文件配置
spring.application.name=spring-cloud-eureka
server.port=8761
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
#fase表示不向注册中心注册自己
eureka.client.register-with-eureka=false
#false表示自己就是注册中心,我的职责就是维护实例,并不需要去检索服务
eureka.client.fetch-registry=false
3.ip:端口号可以直接访问注册中心
4.ip:端口号/euraka/apps可以查看服务详细信息
提供者
属性文件配置:
spring.application.name=spring-cloud-product
server.port=8081
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
消费者
配置类:
@Configuration
public class Config {
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
调用
@Autowired
RestTemplate restTemplate;
@GetMapping(“testWay”)
public String testWay(){
return restTemplate.getForObject(“http://spring-cloud-product/way”,String.class);
}
eureka集群使用/高可用
本质就是他们之间互相注册,并做实例同步
配置(第一个eureka)
spring.application.name=spring-cloud-eureka
server.port=8761
eureka.client.service-url.defaultZone=http://localhost:8762/eureka/
#开启自我保护
eureka.server.enable-self-preservation=true
配置(第二个eureka)
spring.application.name=spring-eureka-server-replict
server.port=8762
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
#开启自我保护
eureka.server.enable-self-preservation=true
Eureka、EurekaClient工作流程
1.EurekaClient服务注册,存储在EurekaServer里
2.Eureka是AP模型,保证其高可用,因为需做EurekaServer集群保证其高可用,Euraka会向客户端服务发送“心跳”,动态感知服务上线、下线,剔除无效服务(默认开启自我保护机制,如需剔除需关闭自我保护机制)
3.Euraka以key,value形式存储服务地址在ConcurentHashMap集合中
4.消费端发送服务请求,Ribbon中的LoadBalanceIntercetor进行请求拦截,通过key(服务名),ILoadBalance获取注册中心中的value(服务列表),结合IRule实现负载均衡,获取集群中的某个具体的某个服务,然后发送请求到这个服务
Eureka特性
eureka自我保护机制
如果Eureka Server在一定时间内(默认90秒)没有接收到某个微服务实例的心跳,Eureka Server将会移除该实例。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,而微服务本身是正常运行的,此时不应该移除这个微服务,所以引入了自我保护机制(默认开启)。自我保护模式正是一种针对网络异常波动的安全保护措施,使用自我保护模式能使Eureka集群更加的健壮、稳定的运行。
Eureka服务端会检查最近15分钟内所有Eureka 实例正常心跳的占比,如果低于85%就会触发自我保护机制。触发了保护机制,Eureka将暂时把这些失效的服务保护起来,不让其过期。如果在此期间服务恢复了并且实例心跳占比高于85%时,就会退出自我保护机制。
进入自我保护机制后:
1.Eureka Server不再从注册列表中移除因为长时间没收到心跳而应该过期的服务。
2.Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上,保证当前节点依然可用。
3.当网络稳定时,当前Eureka Server新的注册信息会被同步到其它节点中。
Eureka自我保护机制,通过配置 eureka.server.enable-self-preservation 来true打开/false禁用自我保护机制,默认打开状态,建议生产环境打开此配置。
开发环境配置
开发环境中如果要实现服务失效能自动移除,只需要修改以下配置。
#关闭自我保护
eureka.server.enable-self-preservation=false
建议开发关闭此配置,生产开启此配置
如何接收EurekaClient发送的请求
Euraka向EurekaClient提供了一些接口,就跟我们自己在项目里写的Controller一样,通过指定地址就可访问。
ApplicationResource/ApplicationsResource
@Produces({"application/xml", "application/json"})
public class ApplicationResource
@GET
public Response getApplication(@PathParam("version") String version,
@HeaderParam("Accept") final String acceptHeader,
@HeaderParam(EurekaAccept.HTTP_X_EUREKA_ACCEPT) String eurekaAccept) {
if (!registry.shouldAllowAccess(false)) {
return Response.status(Status.FORBIDDEN).build();
}
EurekaMonitors.GET_APPLICATION.increment();
CurrentRequestVersion.set(Version.toEnum(version));
KeyType keyType = Key.KeyType.JSON;
if (acceptHeader == null || !acceptHeader.contains("json")) {
keyType = Key.KeyType.XML;
}
Key cacheKey = new Key(
Key.EntityType.Application,
appName,
keyType,
CurrentRequestVersion.get(),
EurekaAccept.fromString(eurekaAccept)
);
String payLoad = responseCache.get(cacheKey);
if (payLoad != null) {
logger.debug("Found: {}", appName);
return Response.ok(payLoad).build();
} else {
logger.debug("Not Found: {}", appName);
return Response.status(Status.NOT_FOUND).build();
}
}
@Path("{id}")
public InstanceResource getInstanceInfo(@PathParam("id") String id) {
return new InstanceResource(this, id, serverConfig, registry);
}
@POST
@Consumes({"application/json", "application/xml"})
public Response addInstance(InstanceInfo info,
@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
logger.debug("Registering instance {} (replication={})", info.getId(), isReplication);
// validate that the instanceinfo contains all the necessary required fields
if (isBlank(info.getId())) {
return Response.status(400).entity("Missing instanceId").build();
} else if (isBlank(info.getHostName())) {
return Response.status(400).entity("Missing hostname").build();
} else if (isBlank(info.getIPAddr())) {
return Response.status(400).entity("Missing ip address").build();
} else if (isBlank(info.getAppName())) {
return Response.status(400).entity("Missing appName").build();
} else if (!appName.equals(info.getAppName())) {
return Response.status(400).entity("Mismatched appName, expecting " + appName + " but was " + info.getAppName()).build();
} else if (info.getDataCenterInfo() == null) {
return Response.status(400).entity("Missing dataCenterInfo").build();
} else if (info.getDataCenterInfo().getName() == null) {
return Response.status(400).entity("Missing dataCenterInfo Name").build();
}
// handle cases where clients may be registering with bad DataCenterInfo with missing data
DataCenterInfo dataCenterInfo = info.getDataCenterInfo();
if (dataCenterInfo instanceof UniqueIdentifier) {
String dataCenterInfoId = ((UniqueIdentifier) dataCenterInfo).getId();
if (isBlank(dataCenterInfoId)) {
boolean experimental = "true".equalsIgnoreCase(serverConfig.getExperimental("registration.validation.dataCenterInfoId"));
if (experimental) {
String entity = "DataCenterInfo of type " + dataCenterInfo.getClass() + " must contain a valid id";
return Response.status(400).entity(entity).build();
} else if (dataCenterInfo instanceof AmazonInfo) {
AmazonInfo amazonInfo = (AmazonInfo) dataCenterInfo;
String effectiveId = amazonInfo.get(AmazonInfo.MetaDataKey.instanceId);
if (effectiveId == null) {
amazonInfo.getMetadata().put(AmazonInfo.MetaDataKey.instanceId.getName(), info.getId());
}
} else {
logger.warn("Registering DataCenterInfo of type {} without an appropriate id", dataCenterInfo.getClass());
}
}
}
registry.register(info, "true".equals(isReplication));
return Response.status(204).build(); // 204 to be backwards compatible
}
如何存储服务列表
ConcurrentHashMap是一个线程安全的、支持高效并发(分段式锁)。在默认理想状态下,ConcurrentHashMap可以支持16个线程执行并发写操作及任意数量线程的读操作
服务信息存储在ConcurrentHashMap<Stirng,ConcurrentHashmap>
三级缓存
如何提高效率呢,采用缓存,同时读写分离(三级缓存)
三级缓存是什么
一级缓存(注册表)ConcurrentHashMap
二级缓存(ReadWriteMap)guava#LoadingCache (60秒)
三级缓存(ReadOnlyMap)ConcurrentHashMap (30秒)
注册一个服务实例
向注册表中写入服务实例信息,并使得二级缓存失效
寻找一个服务
从三级缓存中找,如果有则返回,如果没有则去二级缓存拿并更新,如果二级缓存已经失效,触发guava的回调函数从注册表中同步。
数据同步定时器
每 30s 从二级缓存向三级缓存同步数据
二级缓存有效
从二级缓存向三级缓存同步数据
二级缓存失效
触发二级缓存的同步(从注册表中拉取数据)