SpringCloud学习(一)----Eureka

微服务架构风格图

 Eureka简介

注册发现中心

Eureka来源于古希腊词汇,意为“发现了”。在软件领域, Eureka是Netflix在线影片公司开源的一个服务注册与发现的组件,和其他Netflix 公司的服务组件(例如负载均衡、熔断器、网关等)一起,被Spring Cloud社区整合为Spring Cloud Netflix模块。Eureka是Netflix贡献给Spring Cloud的一个框架! Netflix给Spring cloud 贡献了很多框架。

分布式微服务-----CAP原则

一致性(Consistency):服务器中数据保持一致

可用性(Availability):当一个服务器挂掉时,集群中其他服务器可以继续使用,对外提供服务

分区容错性(Partition tolerance):允许各个原因导致的各个服务器中的数据短暂不同---必须满足

CAP原则指出以上三个要素只能同时实现两点,三者不能同时满足

Eureka为AP(数据可能不一致但是拥有高可用性)     

zookeeper为CP(数据一致但是有服务器宕机时整个服务会有几分钟不能提供服务)

 Eureka快速入门

两种Eureka

1.搭建一个注册中心,提供注册的服务----Eureka-server

2.创建客户端,继续注册----Eureka-client

注意:Eureka-server可以自己注册自己

搭建注册中心

创建一个Eureka依赖的SpringCloud项目

Eureka Server依赖本身提供了web服务

 在pom文件中修改SpringBoot和SpringCloud依赖的版本

 编辑配置文件

server:
  port: 8761  #Eureka 的默认端口是8761
spring:
  application:
    name: eureka-server-a #自定义应用名称,一般使用 -  不要使用特殊字符

在主启动文件上加上注解@EnableEurekaServer

package com.ys;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer  //启动Eureka服务
public class EurekaServerAApplication {

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

}

启动项目,在网页打开

一个应用可以拥有多个实例

 搭建客户端

选择依赖,Eureka Discovery Client 不包含web依赖所以要单独加入

  在pom文件中修改SpringBoot和SpringCloud依赖的版本

编辑配置文件

server:
  port: 8080   #客户端的端口没有要求
spring:
  application:
    name: eureka-client-a
#注册的含义是指将自己的信息(ip,端口(port)等等)发送到(某个地方)
eureka:
  client:
    service-url:    #指定注册地址
      defaultZone: http://localhost:8761/eureka #将信息发送到该地址  注意defaultZone没有提示

在主启动类上添加注解@EnableEurekaClient

package com.ys;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient   //启动客户端服务
public class EurekaClientAApplication {

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

}

 打开网页,查看实例列表,查看是否注册成功

 安装相同的方法再创建一个客户端

最后结果如下

同一个应用创建多个实例

因为前面创建的客户端的例子没有写入其他东西,使用进行复制修改端口即可达到目的

将任意一个客户端的启动文件进行复制

并使用 --server.port:端口号  进行端口的修改 并启动

 查看网页结果

 配置文件详解

 注册中心应该考虑的问题

服务端(注册中心)配置文件详解

server:
  port: 8761  #Eureka 的默认端口是8761
spring:
  application:
    name: eureka-server-a #自定义应用名称,一般使用 -  不要使用特殊字符
# eureka的三大配置   server 服务端    instance  实例    client 客户端
eureka:
  server:
    eviction-interval-timer-in-ms: 10000 #服务端间隔多少毫秒进行定期删除的操作,每过一定的时间客户端没有响应将被剔除
    renewal-percent-threshold: 0.85 #续约(心跳机制)百分比  规定时间内超过百分之八十五的应用没有和服务端续约  那么Eureka的保护机制将启动不会剔除任何一个客户端
  instance:
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}  #定义Eureka中实例名称(主机名:应用名称:端口号)
    hostname: localhost  #定义主机名称
    prefer-ip-address: true   #是否已ip的形式显示具体的服务信息
    lease-renewal-interval-in-seconds: 5 #客户端的续约时间间隔(秒),这个时间要小于服务端的定期删除的时间

 以ip显示服务信息

控制台(服务端)每十秒刷新定期删除

 客户端文件配置详解

server:
  port: 8080   #客户端的端口没有要求
spring:
  application:
    name: eureka-client-a
#注册的含义是指将自己的信息(ip,端口(port)等等)发送到(某个地方)
eureka:  #客户端相关配置
  client:
    service-url:    #指定注册地址
      defaultZone: http://localhost:8761/eureka #将信息发送到该地址  注意defaultZone没有提示
    register-with-eureka: true # 是否往Eureka-Server 中注册
    fetch-registry: true #应用是否从注册中心拉取服务列表到本地缓存
    registry-fetch-interval-seconds: 10 #为了缓解服务列表的脏读问题,设置每过多少秒就从注册中心重新拉取列表,时间越短脏读越少,但是系统资源消耗越多
  instance:
    hostname: localhost #应用主机名称
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port} #实例名称
    lease-renewal-interval-in-seconds: 5  #续约时间
    prefer-ip-address: true #以ip形式显示服务信息

Eureka集群

集群的方案

中心化集群(主从模式集群):有一个核心服务器,当这个服务器无法使用时,整个集群也无法使用

去中心化模式集群:没有核心服务器(没有主从概念),更加的高可用。当某一台服务器接收到数据等,该服务器会将数据扩散和广播到其他服务器中去,保持一致。   Eureka就是这种模式。

创建一个集群

利用与前面相同的方法,再次创建俩个服务器,并修改pom文件中版本

在主启动类上添加@EnableEurekaServer

并修改配置文件,使的服务端的Eureka相互注册,三个服务器的配置文件大致相同,只需要修改端口号和注册地址

# 集群的配置
server:
  port: 8763  #Eureka 的默认端口是8761
spring:
  application:
    name: eureka-server #自定义应用名称,一般使用 -  不要使用特殊字符  一个集群的服务器应用名称相同
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka,http://localhost:8762/eureka
  instance:
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}  #定义Eureka中实例名称(主机名:应用名称:端口号)
    hostname: localhost  #定义主机名称
    prefer-ip-address: true   #是否已ip的形式显示具体的服务信息
    lease-renewal-interval-in-seconds: 5 #客户端的续约时间间隔(秒),这个时间要小于服务端的定期删除的时间


启动三个服务端,三个端口号的网页内容一致

http://localhost:8761/

 http://localhost:8762/

 http://localhost:8763/

 到目前为止,这不能算是集群,网页中没有出现集群信息,系统只是认为同一个服务启动了多台,没有数据交互,不是真正意义上的集群。因为只是修改了端口,所以Eureka只会认为只有一个机器,所以这里需要修改hosts文件(网络映射文件)。

在hosts文件中添加红色方框的内容,作用是peer1,peer2,peer3 为不同名称但是代表的ip地址相同,这样就可以做到欺骗Eureka,使其认为有三台服务器,分别为peer1,peer2,peer3

 再去修改配置文件,三个文件都是这样

# 集群的配置
server:
  port: 8761  #Eureka 的默认端口是8761
spring:
  application:
    name: eureka-server #自定义应用名称,一般使用 -  不要使用特殊字符  一个集群的服务器应用名称相同
eureka:
  client:
    service-url:
      defaultZone: http://peer2:8762/eureka,http://peer3:8763/eureka
  instance:
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}  #定义Eureka中实例名称(主机名:应用名称:端口号)
    hostname: peer1  #定义主机名称
    prefer-ip-address: true   #是否已ip的形式显示具体的服务信息
    lease-renewal-interval-in-seconds: 5 #客户端的续约时间间隔(秒),这个时间要小于服务端的定期删除的时间

 全部修改后,重新启动三个服务端

http://peer1:8761/   ----网页链接输入localhost或者peer2、peer3都是可以访问的,因为在hosts文件中,这几个名称代表的ip地址相同

再次进入网页后可以看到DS Replicas中多出了其他两个服务器,这就形成了真正意义上的集群

 修改客户端的注册地址

server:
  port: 8080   #客户端的端口没有要求
spring:
  application:
    name: eureka-client-a
#注册的含义是指将自己的信息(ip,端口(port)等等)发送到(某个地方)
eureka:  #客户端相关配置
  client:
    service-url:    #指定注册地址
      defaultZone: http://peer1:8761/eureka #将信息发送到该地址  注意defaultZone没有提示
    register-with-eureka: true # 是否往Eureka-Server 中注册
    fetch-registry: true #应用是否从注册中心拉取服务列表到本地缓存
    registry-fetch-interval-seconds: 10 #为了缓解服务列表的脏读问题,设置每过多少秒就从注册中心重新拉取列表,时间越短脏读越少,但是系统资源消耗越多
  instance:
    hostname: localhost #应用主机名称
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port} #实例名称
    lease-renewal-interval-in-seconds: 5  #续约时间
    prefer-ip-address: true #以ip形式显示服务信息

启动客户端,在服务端观察,此客户端只在一个服务端中注册,最后可以在三个服务端中观察到,说明了Eureka集群是去中心化模式,服务器之间是可以扩散和广播的。

 当集群中某一个服务器下线,其他服务器是可以正常运行的,不会受到影响。这就是Eureka的AP原则,高可用性。

简化服务端的配置,往三个服务器中都进行注册,不需要定义hostname,且instance-id也只需要应用名称和端口号了,主机名称在注册地址中系统会根据端口号找到相应的名称。

# 集群的配置
server:
  port: 8761  #Eureka 的默认端口是8761
spring:
  application:
    name: eureka-server #自定义应用名称,一般使用 -  不要使用特殊字符  一个集群的服务器应用名称相同
eureka:
  client:
    service-url:
      defaultZone: http://peer2:8762/eureka,http://peer3:8763/eureka,http://peer1:8762/eureka,
  instance:
    instance-id:${spring.application.name}:${server.port}  
#   hostname: peer1  #定义主机名称
    prefer-ip-address: true   #是否已ip的形式显示具体的服务信息
    lease-renewal-interval-in-seconds: 5 #客户端的续约时间间隔(秒),这个时间要小于服务端的定期删除的时间

 此时客户端需要往三个服务器中同时注册,防止唯一进行的注册的服务端下线导致无法注册,提高了注册的成功几率

server:
  port: 8080   #客户端的端口没有要求
spring:
  application:
    name: eureka-client-a
#注册的含义是指将自己的信息(ip,端口(port)等等)发送到(某个地方)
eureka:  #客户端相关配置
  client:
    service-url:    #指定注册地址
      defaultZone: http://peer1:8761/eureka,http://peer2:8762/eureka,http://peer3:8763/eureka #将信息发送到该地址  注意defaultZone没有提示
    register-with-eureka: true # 是否往Eureka-Server 中注册
    fetch-registry: true #应用是否从注册中心拉取服务列表到本地缓存
    registry-fetch-interval-seconds: 10 #为了缓解服务列表的脏读问题,设置每过多少秒就从注册中心重新拉取列表,时间越短脏读越少,但是系统资源消耗越多
  instance:
    hostname: localhost #应用主机名称
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port} #实例名称
    lease-renewal-interval-in-seconds: 5  #续约时间
    prefer-ip-address: true #以ip形式显示服务信息

 分布式数据一致协议----Paxos    raft

Raft 可以在网页上观看官方的动画,深入了解协议和机制

zookeeper 是Paxos

Eureka 没有分布式数据的一致性的机制,节点都是相同的

nacos 是raft

在主从模式的集群中,一般都需要遵守这样的协议 才可以稳定的对外提供服务

Eureka概念的理解

服务的注册

当项目启动时((eureka的客户端),就会向eureka-server发送自己的元数据(原始数据)(运行的ip,端口port,健康的状态监控等,因为使用的是http/ResuFul请求风格),eureka-server 会在自己内部保留这些元数据(内存中)。(有一个服务列表)(restful风格,以http.动词的请求方式,完成对url资源的操作)

服务的续约

项目启动成功了,除了向eureka-server注册自己成功,还会定时的向eureka-server汇报自己,心跳,表示自己还活着。(修改一个时间)

服务的下线(主动下线)

当项目关闭时,会给eureka-server报告,说明自己要下机了。

服务的剔除(被动下线,主动剔除)

当项目超过了指定时间没有向eureka-server汇报自己,那么eureka-server就会认为此节点死掉了,会把它剔除掉,也不会放流量和请求到此节点了。

服务发现

通过应用名找到该应用的注册信息

 修改配置文件,Eureka-Server-A

server:
  port: 8761  #Eureka 的默认端口是8761
spring:
  application:
    name: eureka-server-a #自定义应用名称,一般使用 -  不要使用特殊字符
# eureka的三大配置   server 服务端    instance  实例    client 客户端
eureka:
  server:
    eviction-interval-timer-in-ms: 10000 #服务端间隔多少毫秒进行定期删除的操作,每过一定的时间客户端没有响应将被剔除
    renewal-percent-threshold: 0.85 #续约(心跳机制)百分比  规定时间内超过百分之八十五的应用没有和服务端续约  那么Eureka的保护机制将启动不会剔除任何一个客户端
  instance:
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}  #定义Eureka中实例名称(主机名:应用名称:端口号)
    hostname: localhost  #定义主机名称
    prefer-ip-address: true   #是否已ip的形式显示具体的服务信息
    lease-renewal-interval-in-seconds: 5 #客户端的续约时间间隔(秒),这个时间要小于服务端的定期删除的时间
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka
    fetch-registry: true  #拉取缓存注册中心的实例列表
    register-with-eureka: false#将服务端自己注册自己默认关闭开关自由

Eureka-Client-A配置文件

server:
  port: 8080   #客户端的端口没有要求
spring:
  application:
    name: eureka-client-a
#注册的含义是指将自己的信息(ip,端口(port)等等)发送到(某个地方)
eureka:  #客户端相关配置
  client:
    service-url:    #指定注册地址
      defaultZone: http://localhost:8761/eureka #将信息发送到该地址  注意defaultZone没有提示
    register-with-eureka: true # 是否往Eureka-Server 中注册
    fetch-registry: true #应用是否从注册中心拉取服务列表到本地缓存
    registry-fetch-interval-seconds: 10 #为了缓解服务列表的脏读问题,设置每过多少秒就从注册中心重新拉取列表,时间越短脏读越少,但是系统资源消耗越多
  instance:
    hostname: localhost #应用主机名称
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port} #实例名称
    lease-renewal-interval-in-seconds: 5  #续约时间
    prefer-ip-address: true #以ip形式显示服务信息

Eureka-Client-B配置文件

server:
  port: 8081
spring:
  application:
    name: eureka-client-b
eureka:
  client:
    service-url: #指定注册地址
      defaultZone: http://localhost:8761/eureka
  instance:
    prefer-ip-address: true
    hostname: localhost
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
    lease-renewal-interval-in-seconds: 10  #续约时间

修改后启动Eureka-Client-B、Eureka-Server-A服务

在Eureka-Client-A编写代码,创建controller包下DiscoveryController.java,获取信息

package com.ys.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class DiscoveryController {

    @Autowired
    private DiscoveryClient discoveryClient; //SpringCloud提供的接口

    @GetMapping("/test")
    public String doDiscovery(String serviceName){
//        寻找Eureka-Client-B的ip和port   服务发现,找到服务的具体信息
        List<ServiceInstance> instances=discoveryClient.getInstances(serviceName);
// List<ServiceInstance>是集合,一个服务可能有多个实例
//       对 List<ServiceInstance>集合进行遍历
        instances.forEach(System.out::println);
//        取出第一个值
        ServiceInstance serviceInstance=instances.get(0);
//        获取ip地址
        String ip=serviceInstance.getHost();
//        获取端口号
        int port=serviceInstance.getPort();
//        输出ip和端口
        System.out.println("ip:"+ip+"port:"+port);
//        返回 List<ServiceInstance>集合中的一个值   get(元素下标)
        return instances.get(0).toString();
    }
}

启动Eureka-Client-A

 修改网址为http://localhost:8080/test?serviceName=eureka-client-b

获取Eureka-Client-B的注册信息,并在控制台返回信息,单独返回ip和port

RestTemplate

客户端工具,可以发送不同类型的请求。

创建一个只有web依赖的springboot项目

在测试类中编辑

package com.ys;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.web.client.RestTemplate;

@SpringBootTest
class RestTemplateApplicationTests {

    @Test
    void contextLoads() {
//        在java代码中去发送一个请求  请求一个页面
        RestTemplate restTemplate=new RestTemplate();
        String url="https://www.baidu.com";  //发送请求到那个网址
//        url  网址   String.class是指返回值的类型  当返回值为一个页面但是要求为字符串类型时,会返回页面的组成代码
        String result=restTemplate.getForObject(url,String.class);
        System.out.println(result);
    }

}

运行方法,得到的返回结果

 创建Controller类 进行传参

package com.ys.controller;

import com.ys.controller.domain.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

@org.springframework.web.bind.annotation.RestController
public class RestController {
    @GetMapping("testGet")
    public String testGet(String name){
        System.out.println(name);
        return "ok";
    }
//  json参数的核心 header content-type=application/json charset=utf-8
//  使用一个接收类User和@RequestBody接受json类型参数
    @PostMapping("testPost")
    public String testPost(@RequestBody User user){
        System.out.println(user);
        return "ok";
    }
//    接收表单参数
    /**
     * <form action=/testPost(地址)
     * <input name=ccc  value=sss>
     * 核心  header content-type=application/x-www-form-urlencoded
     * 使用一个接收类User不用加@RequestBody,直接如下即可:
     * public String testPost(User user){
     *         System.out.println(user);
     *         return "ok";
     *     }
     */
    @PostMapping("testPost2")
    public String testPost2(User user){
        System.out.println(user);
        return "ok";
    }
}

编辑测试类

package com.ys;

import com.ys.controller.domain.User;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;

@SpringBootTest
class RestTemplateApplicationTests {

    @Test
    void contextLoads() {
//        在java代码中去发送一个请求  请求一个页面
        RestTemplate restTemplate=new RestTemplate();
        String url="https://www.baidu.com";  //发送请求到那个网址
//        url  网址   String.class是指返回值的类型  当返回值为一个页面但是要求为字符串类型时,会返回页面的组成代码
        String result=restTemplate.getForObject(url,String.class);
        System.out.println(result);
    }
    @Test
    void testGet(){
        RestTemplate restTemplate=new RestTemplate();
        String url="http://localhost:8080/testGet?name=cxk";
        String result=restTemplate.getForObject(url,String.class);
//        http协议:请求头 请求参数....响应头 响应状态码 报文...
        ResponseEntity<String> responseEntity=restTemplate.getForEntity(url,String.class);
        System.out.println(responseEntity);
        System.out.println(result);
    }
    @Test
    void testPost(){
        RestTemplate restTemplate=new RestTemplate();
        String url="http://localhost:8080/testPost";
        User user=new User("雷哥",18,10000D);
//    发生一个post请求,而且参数是json,因为web里面默认使用jackson,会把你的对象转化成json字符串
        String result= restTemplate.postForObject(url,user,String.class);
        System.out.println(result);
    }
    @Test
    void testPost2(){
        RestTemplate restTemplate=new RestTemplate();
        String url="http://localhost:8080/testPost2";
//        构建表单参数
        LinkedMultiValueMap<String,Object> map = new LinkedMultiValueMap<>();
//        注意这里要使用add而不是put
        map.add("name","林妹");
        map.add("age",15);
        map.add("price",8000D);
        String result= restTemplate.postForObject(url,map,String.class);
        System.out.println(result);
    }
}

启动springboot服务器,如果报错,就先把测试类中的方法全部注释掉之后启动服务器,之后在测试类取消注释再运行测试方法,此时查看控制台返回的数据


版权声明:本文为yshjvc原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。