微服务模块-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 从二级缓存向三级缓存同步数据

二级缓存有效
从二级缓存向三级缓存同步数据

二级缓存失效
触发二级缓存的同步(从注册表中拉取数据)

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值