Eureka介绍

注册中心 : 每个服务都有自己的ip和端口,,一个服务调用另一个服务的时候都需要知道对方的ip,,,

Eureka类似 dubbo中的zookeeper

Eureka 是 netflix公司提供的一款服务注册中心,,,基于REST来实现服务的注册与发现,,,

Eureka两部分:

  • 服务端: 注册中心,,用来接受其他服务的注册
  • 客户端: 是一个java客户端,用来注册服务,并可以实现负载均衡等功能
    Eureka-LMLPHP
    eureka中有三个角色: eureka server,,provider,,consumer
Eureka搭建

Eureka是 使用 java开发的
引入spring-cloud-starter-netflix-eureka-server
spring cloud discovery

使用@EnableEurekaServer

@SpringBootApplication
// 开启 eureka server 功能
@EnableEurekaServer
public class EurekaApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class, args);
    }

}

配置文件:

spring:
  application:
    # 给当前服务取名字
    name: eureka

server:
  port: 1111

# 两层身份 : 1.注册中心  2.普通服务,,,, 即当前服务会 自己把自己注册进来
eureka:
  client:
    # 当前项目  不注册自己
    register-with-eureka: false

    # 是否从 eureka server 上获取注册信息
    fetch-registry: false

Eureka-LMLPHP
端口: 一个后台管理的端口,,一个通讯的端口
Eureka-LMLPHP

Eureka集群

服务注册中心的稳定 非常重要,需要集群
实际开发中Eureka都是以集群的形式出现的

修改hosts 文件 C:\Windows\System32\drivers\etc
Eureka-LMLPHP

Eureka集群 : 就是启动多个 Eureka实例,,相互注册,,相互同步数据,共同组成一个Eureka集群

Eureka Server作用

  1. 服务注册 : 所有的服务都注册到 Eureka server中,,Eureka client向 Eureka server 注册的时候,需要提供自身的一些元数据信息(ip地址,端口,名称,运行状态等)
  2. 提供注册表 : Eureka client 在调用服务时,需要获取这个注册表,,一般来说,这个注册表会缓存下来,如果缓存失效,会直接获取新的注册表
  3. 同步状态 :通过注册,心跳等机制,,和Eureka Server 同步客户端状态

Eureka Client作用

  • 获取注册表信息: eureka client会自动 拉取,更新以及缓存 Eureka server 的信息,,这样即使 Eureka Server 宕机,,Eureka client 依然能够获取到想要调用服务的地址(地址可能不准确),这个注册表会定期更新

    # 是否允许获取注册表信息
    eureka.client.fetch-registry=true
    
    # 定期更新注册表  时间,,默认30s
    eureka.client.registry-fetch-interval-seconds=30
    
  • 服务续约: Eureka client 每隔 30s 会向 Eureka Server 发送一条心跳消息,,来告诉Eureka server,我还在运行,,,如果Eureka Server 连续 90s 都没有收到 Eureka client 的续约消息,,认为这个Eureka client 已经掉线,,会将 eureka client 将服务注册列表中剔除

  • 服务下线: 当Eureka client 下线时,,他会主动发送一条消息,告诉Eureka server

自我保护机制: 防止误杀,,,当Eureka 捕获到大量的心跳失败的时候,则认为可能是网络问题,,进入自我保护机制,,当客户端心跳恢复正常的时候,会自动退出自我保护机制

配置: application-a.yml

spring:
  application:
    # 给当前服务取名字
    name: eureka

server:
  port: 1111


# 两层身份 : 1.注册中心  2.普通服务,,,, 即当前服务会 自己把自己注册进来
eureka:
  client:
    # 当前项目  不注册自己
    register-with-eureka: true

    # 是否从 eureka server 上获取注册信息
    fetch-registry: true
    service-url:
      defaultZone: http://eurekaB:1112/eureka
  instance:
    # 给服务取个别名,,注册到Eureka
    hostname: eurekaA

application-b.yml

spring:
  application:
    # 给当前服务取名字
    name: eureka

server:
  port: 1112

# 两层身份 : 1.注册中心  2.普通服务,,,, 即当前服务会 自己把自己注册进来
eureka:
  client:
    # 当前项目  不注册自己
    register-with-eureka: true
    # 是否从 eureka server 上获取注册信息
    fetch-registry: true
    service-url:
      defaultZone: http://eurekaA:1111/eureka

  instance:
    hostname: eurekaB

打包 java -jar xxx.jar --spring.profiles.active=a

Eureka集群:

Eureka-LMLPHP
通过replicate 进行数据同步
不同的Eureka server ,,不区分 主从节点,,所有的节点都是平等的,,通过指定 service 的url 进行相互注册,,
如果有某一个节点宕机,,Eureka client 会自动切换到 新的Eureka server 上

Eureka server 的连接方式 可以是单线的 a-->b--->c,a连接b,b连接c,,a和c之间不用直接连,,但是一般不建议这种写法,一旦b宕机,a和c就不能同步,,,在配置时,可以配置多个注册地址,用,分开

Eureka分区:

  1. region :地理上的不同区域
  2. zone : 具体的机房
    在同一个分区里面的 client 和 同一个分区里面的 server 会优先进行 心跳同步

Eureka client 使用

简单使用

客户端starter : spring-cloud-starter-netflix-eureka-client
配置文件:

spring:
  application:
    # 服务名字
    name: provider
server:
  port: 1113
eureka:
  client:
    service-url:
      # 注册地址
      defaultZone: http://localhost:1111/eureka

Eureka-LMLPHP

使用DiscoveryClient可以获取客户端的信息(ip,端口等)
DiscoveryClient 根据 服务名,,查询到 服务的详细信息
DiscoveryClient查询到的服务列表是一个集合,因为服务可能是集群化部署

@RestController
public class UseHelloController {
    @GetMapping("/hello")
    public String hello() throws IOException {

        // java?????????????  代码写死了,,只能固定一个连接
        HttpURLConnection  connection = null;
        URL url = new URL("http://localhost:1113/hello");
         connection = (HttpURLConnection) url.openConnection();
         // 如果这个连接相应200
         if (connection.getResponseCode()==200){
             BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
             String s = reader.readLine();
             reader.close();
             return s;
         }

         return "error";
    }


    @Autowired
    DiscoveryClient discoveryClient;

    @GetMapping("/hello2")
    public String hello2() throws IOException {
        // serviceId: 你要调用服务的名字
        //  provider 可能是集群,,
        List<ServiceInstance> providerList = discoveryClient.getInstances("provider");
        // 获取服务
        ServiceInstance instance = providerList.get(0);
        String host = instance.getHost();
        int port = instance.getPort();

        HttpURLConnection con = null;

        StringBuilder sb = new StringBuilder("http://");
        sb.append(host).append(":").append(port).append("/hello");

        URL url = new URL(sb.toString());
        // 连接
        con = (HttpURLConnection) url.openConnection();

        if (con.getResponseCode() ==200){
            BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream()));
            return reader.readLine();
        }

        return "error";
    }
}

provider代码: 将port写进去

@RestController
public class HelloController {

    @Value("${server.port}")
    Integer port;


    @GetMapping("/hello")
    public String hello(){
        return "hello  :"+port;
    }
}

将provider打包,,运行两个provider实例:

java -jar provider-0.0.1-SNAPSHOT.jar --server.port=1116

Eureka-LMLPHP

设置一个count,,让consumer 线性负载均衡

    int count = 0;

    @GetMapping("/hello3")
    public String hello3() throws IOException {
        // serviceId: 你要调用服务的名字
        //  provider 可能是集群,,
        List<ServiceInstance> providerList = discoveryClient.getInstances("provider");
        // 获取服务  取余,,线性负载均衡
        ServiceInstance instance = providerList.get((count++)%providerList.size());
        String host = instance.getHost();
        int port = instance.getPort();

        HttpURLConnection con = null;

        StringBuilder sb = new StringBuilder("http://");
        sb.append(host).append(":").append(port).append("/hello");

        URL url = new URL(sb.toString());
        // 连接
        con = (HttpURLConnection) url.openConnection();

        if (con.getResponseCode() ==200){
            BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream()));
            return reader.readLine();
        }

        return "error";
    }
升级改造
  • 使用 restTemplate 发送请求
    代码:
    @Bean  // 没有Ribbon的Bean。。。有Ribbon的bean会将请求拦截下来解析服务名字
    RestTemplate restTemplateOne(){
        return new RestTemplate();
    }

    @GetMapping("/hello4")
    public String hello4()  {
        List<ServiceInstance> providerList = discoveryClient.getInstances("provider");
        ServiceInstance instance = providerList.get((count++)%providerList.size());
        String host = instance.getHost();
        int port = instance.getPort();
        StringBuilder sb = new StringBuilder("http://");
        sb.append(host).append(":").append(port).append("/hello");

        String result = restTemplate.getForObject(sb.toString(), String.class);
        return result;
    }
  • 使用Ribbon负载均衡
    代码:
    @Bean
    // 开启负载均衡  ,此时的restTemplate 就自动具备了负载均衡的功能
    @LoadBalanced
    RestTemplate restTemplateOne(){
        return new RestTemplate();
    }
    
    @GetMapping("/hello5")
    public String hello5()  {

        // 直接给一个模糊的地址,,provider,, 这个请求会被拦截下来,解析provider,,会利用DiscoveryClient从里面提取出来provider,,
        // 查出来在根据你本地配好的负载均衡策略,,选一个服务出来,拼接出来地址
        String result = restTemplate.getForObject("http://provider/hello", String.class);
        return result;
    }
12-08 08:50