SpringCloud学习Eureka作服务注册中心

springcloud Netflix Eureke

在服务注册与发现中,有一个注册中心,当服务器启动时,会把当前自己的服务器信息,如服务地址通讯地址等以别名的方式注册到注册中心,服务消费者通过别名的方式从注册中心获取实际调用地址;相当于键值对(key:别名、服务名,value:服务器元数据,如地址、端口、请求路径等);
Eureka采用CS的设计模式,即客户端和服务端Eureka server作为服务端(使用@EnableEurekaServer注解标识),它是服务注册中心而其它微服务作为客户端(使用@EnableEurekaClient标识)注册进Eureka server维持心跳,服务消费者通过RestTemplate(使用@LoadBalanced注解来开启通过微服务名调用,并开启负载均衡)对象调用服务生产者;

Eurka 保证 AP

Eureka Server 各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而 Eureka Client 在向某个 Eureka 注册时,如果发现连接失败,则会自动切换至其它节点。只要有一台 Eureka Server 还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。
Eurka 集群架构如下

Eureka server通过相互注册来构建集群、Eureka Client通过注册多个同服务名的微服务进所有的Eureka server来构建集群;

Eureka集群配置

server:
  port: 8763

eureka:
  instance:
    #hostname: localhost
    hostname: eureka8763.com
  client:
    registerWithEureka: false
    fetchRegistry: false
    serviceUrl:
      #单机
      #defaultZone:http://${eureka.instance.hostname}:${server.port}/eureka/
      #集群
      defaultZone: http://eureka8762.com:8762/eureka,http://eureka8761.com:8761/eureka

Eureka Client集群配置

spring:
  application:
    name: user-server #多个
eureka:
  client:
    serviceUrl: #注册服务到eureka集群
      defaultZone: http://eureka8761.com:8761/eureka,http://eureka8762.com:8762/eureka,http://eureka8763.com:8763/eureka

快速搭建一个Eureka Demo
Eureka Server
pom.xml

...
 <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
...

主启动类

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApp {
    public static void main(String[] args) {
        new SpringApplicationBuilder(EurekaServerApp.class).web(WebApplicationType.SERVLET).run(args);
    }
}

application.yml

server:
  port: 8763 #端口

eureka:
  instance:
    #hostname: localhost  #服务名
    hostname: eureka8763.com  #服务名
  client:
    registerWithEureka: false #不注册自身
    fetchRegistry: false #要不要去注册中心获取其他服务的地址
    serviceUrl:
      #单机
      #defaultZone:http://${eureka.instance.hostname}:${server.port}/eureka/
      #集群
      defaultZone: http://eureka8762.com:8762/eureka,http://eureka8761.com:8761/eureka

Eureka Client
消费者
pom.xml

...
  <!--eureka 客户端-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
...

AdminApp.java

/**
 * @author :jty
 * @date :20-6-11
 * @description :eureka服务集群2
 */
@SpringBootApplication
@EnableEurekaClient
public class AdminApp {
    public static void main(String[] args) {
        SpringApplication.run(AdminApp.class);
    }
}

application.yml

server:
  port: 8002
spring:
  application:
    name: admin-server

mybatis:
    ### xml存放路径
  mapper-locations: classpath:mapper/*Mapper.xml

eureka:
  client:
    serviceUrl: #注册服务到eureka集群
      defaultZone: http://eureka8761.com:8761/eureka,http://eureka8762.com:8762/eureka,http://eureka8763.com:8763/eureka

AdminController.java

/**
 * @author :jty
 * @date :20-7-28
 * @description : 管理员模块
 */
@RestController
public class AdminController {
    @Autowired
    RestTemplate restTemplate;
//    private static final String USER_MODULE_URL = "http://localhost:8001";
    private static final String USER_MODULE_URL = "http://user-server";

    @GetMapping(value = "/admin/get/user/{userId}", produces = "application/json;charset=utf-8")
    public Result searchUser(@PathVariable int userId) {
        Result result = restTemplate.getForObject(USER_MODULE_URL + "/get/user/" + userId, Result.class);
        return result;
    }

    @GetMapping(value = "/admin/post/create/user", produces = "application/json;charset=utf-8")
    public Result createUser(@RequestBody User user) {
        Result result = restTemplate.postForObject(USER_MODULE_URL + "/post/create/user", user, Result.class);
        return result;
    }
}

RestTemplateConfig.java

/**
 * @author :jty
 * @date :20-7-28
 * @description :注入RestTemplate Bean
 */
@Configuration
public class RestTemplateConfig {
    /**
     * @LoadBalanced 通过服务名调用,开启负载均衡
     * */
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

Eureka Client
服务生产者
pom.xml

...
  <!--eureka 客户端-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
...

UserApp.java

/**
 * @author :jty
 * @date :20-7-20
 * @description :用户模块   @EnableEurekaClient eureka客户端 @EnableDiscoveryClient开启服务发现,获取服务信息
 */
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
public class UserApp {
    public static void main(String[] args) {
        new SpringApplicationBuilder(UserApp.class).web(WebApplicationType.SERVLET).run(args);
    }
}

application.yml

server:
  port: 8001
spring:
  application:
    name: user-server #服务名
eureka:
  client:
    serviceUrl: #注册服务到eureka集群
      defaultZone: http://eureka8761.com:8761/eureka,http://eureka8762.com:8762/eureka,http://eureka8763.com:8763/eureka
#数据库配置略

UserController.java

/**
 * @author :jty
 * @date :20-7-20
 * @description :用户模块
 */
@RestController
public class UserController {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private DiscoveryClient discoveryClient;
    @Value("server.port")
    String serverPort;
    Logger logger = LoggerFactory.getLogger(UserController.class);

    /**
     * 查询用户
     */
    @GetMapping(value = "/get/user/{userId}", produces = "application/json;charset=utf-8")
    public Result searchUser(@PathVariable(value = "userId") int userId) {
        User user = userMapper.getUserById(userId);
        if (user != null) {
            return new Result(200, "成功" + serverPort, user);
        }
        return new Result(-200, "无数据");
    }

    /**
     * 添加用户
     */
    @PostMapping(value = "/post/create/user", produces = "application/json;charset=utf-8")
    public Result createUser(@RequestBody User user) {
        int i = userMapper.createUser(user);
        if (i > 0) {
            return new Result(200, "成功" + serverPort, i);
        }
        return new Result(-200, "插入失败");
    }

    /**
     * 服务发现
     */
    @GetMapping(value = "/get/user/discovery")
    public Object discovery() {
        List<String> services = discoveryClient.getServices();
        List<ServiceInstance> instances = discoveryClient.getInstances("user-server");
        for (String sv : services) {
            logger.info("------------>service{}", sv);
        }
        for (ServiceInstance instance : instances) {
            logger.info("-->{}---{}---{}---{}<--", instance.getInstanceId(), instance.getHost(), instance.getPort(), instance.getUri());
        }
        return instances;
    }

测试

服务监控

服务调用

微服务信息完善
父工程pom.xml

...
<build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <configuration>
                    <delimiters>
                        <delimit>@</delimit>
                    </delimiters>
                </configuration>
            </plugin>
        </plugins>
    </build>
...

application.yml

...
eureka:
  client:
    serviceUrl: #注册服务到eureka集群
      defaultZone: http://eureka8761.com:8761/eureka,http://eureka8762.com:8762/eureka,http://eureka8763.com:8763/eureka
  instance:
    instance-id: ${spring.application.name}:${server.port} #微服务信息提示为服务名:端口,可自定义
    prefer-ip-address: true     #访问路径可以显示IP地址

#微服务信息
info:
  app.name: admin-server
  company.name: www.jty.com
  build.artifactId: @project.artifactId@
  build.version: @project.version@
...
提示信息

详细信息

Eureka自我保护

Eureka Server 在运行期间会去统计心跳失败比例在 15 分钟之内是否低于 85%,如果低于 85%(eureka.server.renewal-percent-threshold=0.85),Eureka Server 会将这些实例保护起来,让这些实例不会过期,但是在保护期内如果服务刚好这个服务提供者非正常下线了,此时服务消费者就会拿到一个无效的服务实例,此时会调用失败,对于这个问题需要服务消费者端要有一些容错机制,如重试,断路器等;即一个服务不可用后,Eureka不会立即对其清理,依旧会对微服务的信息进行保存。

Erueka server配置相关属性

#将IP注册到Eureka Server上,如果不配置就是机器的主机名
eureka.instance.prefer-ip-address=true

#设为false,关闭自我保护
eureka.server.enable-self-preservation=false

#表示是否将自己注册到Eureka Server,默认为true
eureka.client.register-with-eureka=false

#表示是否从Eureka Server获取注册信息,默认为true
eureka.client.fetch-registry=false

# 扫描失效服务的间隔时间(单位毫秒,默认是60*1000)即60秒
eureka.server.eviction-interval-timer-in-ms=5000

#设置 eureka server同步失败的等待时间 默认 5分
#在这期间,它不向客户端提供服务注册信息
eureka.server.wait-time-in-ms-when-sync-empty=5

#设置 eureka server同步失败的重试次数 默认为 5 次
eureka.server.number-of-replication-retries=5

#自我保护系数(默认0.85)
eureka.server.renewal-percent-threshold=0.49
Eureka心跳、下线、自我保护
参考 https://blog.csdn.net/hry2015/article/details/78245149

服务心跳

服务实例会通过心跳(eureka.instance.lease-renewal-interval-in-seconds定义心跳的频率,默认值为30s)续约的方式向Eureka Server定时更新自己的状态。Eureka Server收到心跳后,会通知集群里的其它Eureka Server更新此实例的状态。Service Provider/Service Consumer也会定时更新缓存的实例信息。

服务下线和剔除

服务的下线有两种情况:

在Service Provider服务shutdown的时候,主动通知Eureka Server把自己剔除,从而避免客户端调用已经下线的服务。
Eureka Server会定时(间隔值是eureka.server.eviction-interval-timer-in-ms,默认值为0,默认情况不删除实例)进行检查,如果发现实例在在一定时间(此值由eureka.instance.lease-expiration-duration-in-seconds定义,默认值为90s)内没有收到心跳,则会注销此实例。
这种情况下,Eureka Client的最多需要[eureka.instance.lease-renewal-interval-in-seconds + eureka.client.registry-fetch-interval-seconds]时间才发现服务已经下线。同理,一个新的服务上线后,Eureka Client的服务消费方最多需要相同的时间才发现服务已经上线

服务下线,同时会更新到Eureka Server其他节点和Eureka client的缓存,流程类似同以上的register过程

自我保护模式

如果Eureka Server最近1分钟收到renew的次数小于阈值(即预期的最小值),则会触发自我保护模式,此时Eureka Server此时会认为这是网络问题,它不会注销任何过期的实例。等到最近收到renew的次数大于阈值后,则Eureka Server退出自我保护模式。

自我保护模式阈值计算:

每个instance的预期心跳数目 = 60/每个instance的心跳间隔秒数
阈值 = 所有注册到服务的instance的数量的预期心跳之和 *自我保护系数
以上的参数都可配置的:

instance的心跳间隔秒数:eureka.instance.lease-renewal-interval-in-seconds
自我保护系数:eureka.server.renewal-percent-threshold
如果我们的实例比较少且是内部网络时,推荐关掉此选项。我们也可以通过eureka.server.enable-self-preservation = false来禁用自我保护系数

原文地址:https://www.cnblogs.com/jinit/p/13401410.html