Spring Cloud (二):服务发现 Eureka 和 Consul

Eureka

Netflix 开发的组件,使用 Eureka 可以配合其他 Netflix 的组件比如 API 网关 ZUUL

2.0 起不再开发维护了
https://github.com/Netflix/eureka/wiki

The existing open source work on eureka 2.0 is discontinued. The code base and artifacts that were released as part of the existing repository of work on the 2.x branch is considered use at your own risk.

Eureka 1.x is a core part of Netflix's service discovery system and is still an active project.

Server

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR9</spring-cloud.version>
    </properties>

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

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
@EnableEurekaServer
@SpringBootApplication
public class Demo1Application {
    public static void main(String[] args) {
        SpringApplication.run(Demo1Application.class, args);
    }
}
server.port=8761
eureka.instance.hostname=localhost
eureka.client.registerWithEureka=false
eureka.client.fetchRegistry=false
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:8761/eureka/

一个 Client 程序向 Server 注册自己的信息

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR9</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
server.port=8762

eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
eureka.client.healthcheck.enabled=true
spring.application.name=service-provider-1
@EnableDiscoveryClient
@SpringBootApplication
public class EurekaClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaClientApplication.class, args);
    }
}
@RestController
public class Controller {
    @RequestMapping("/api")
    public String index() {
        return "Greetings from Eureka Client!";
    }
}

登上 Eureka Server 的 URL http://localhost:8761/ 可以看到注册了一个服务,这个服务的端口名字是 service-provider-1,端口是 8762,以及有这个服务对应的 IP

再写一个程序,通过 Eureka Server 找到上面这个服务

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR9</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
server.port=8763
## 如果 Eureka Server 使用默认的 8761 端口,并且这个程序不注册,就不需要写 Eureka 信息
@EnableDiscoveryClient
@SpringBootApplication
public class EurekaConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaConsumerApplication.class, args);
    }
}
@RestController
public class Controller {
    @Autowired
    private DiscoveryClient discoveryClient;
 
    @RequestMapping("/queryService")
    public String query() {
        List<ServiceInstance> instances = discoveryClient.getInstances("service-provider-1");
        StringBuilder urls= new StringBuilder();
        for(ServiceInstance instance : instances){
            urls.append(instance.getHost()+":"+instance.getPort()).append(",");
        }
        return "service name : service-provider-1<br>" + "host : " + urls.toString();
    }
}

查询 http://localhost:8763/queryService 可以看到返回

service name : service-provider-1
host : 192.168.0.101:8762,

Consul

Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置
https://www.consul.io/docs/intro

与 Eureka 相比,Consul 提供更完整的功能,包括了

  • 服务发现,和 Eureka 类似
  • 健康检查,比如定期检查某个服务是否返回 200,检查当前节点的内存使用率是否超过 90% 等
  • Key-Value 存储,可用于动态配置,功能标记,leader 选举,一致性协定,等等
  • 安全服务通信,可以产生分发安全认证给服务,建立双向安全链接
  • 多数据中心

和 Eureka 是 Spring Boot 实现不同,Consul 是通过 go 语言编写的

安装 Consul

sudo apt-get install consul

debug 环境可以用 -dev 只启动一个用就可以

## 如果机器有多个 IP 就需要用 bind 指定一个进行绑定
consul agent -dev -bind=10.0.2.1

启动后就可以访问 http://localhost:8500

生产环境下应该启动 Consul 集群,集群包括 Server 和 Client

Server 的数量是有限的若干个,比如 3 个,5 个,通常为奇数,方便选举,Server 负责数据的存储和同步,可能是为了防止每个服务都直接和 Server 打交道造成 Server 压力太大,又为每台跑服务的机器安装 Client,因为每台机都有,所以 Client 可以有很多很多个,Client 不存储数据,只是将本机的服务请求、服务状态,整合然后统一和 Server 进行交互

而每个服务实际上是在和本机的 Consul Client 打交道,Consul Server 对它们是透明的

这里的 Server、Client 和平时理解的可能有点区别,可能叫 Consul Server 和 Consul Agent/Proxy 比较合适,但在这里 Server 和 Client 都被称为 Agent

假设起 3 个 server 和 1 个 client

consul agent -server -ui -bootstrap-expect=3 -node=s1 
                     -data-dir=~/projects/consul/data -config-dir ~/projects/consul/consul.d 
                     -bind=10.0.2.15 -client=0.0.0.0
consul agent -server -ui -bootstrap-expect=3 -node=s2 
                     -data-dir=~/projects/consul/data -config-dir ~/projects/consul/consul.d 
                     -bind=10.0.2.16 -client=0.0.0.0 -join=10.0.2.15
consul agent -server -ui -bootstrap-expect=3 -node=s3 
                     -data-dir=~/projects/consul/data -config-dir ~/projects/consul/consul.d 
                     -bind=10.0.2.17 -client=0.0.0.0 -join=10.0.2.15
consul agent -node=c1 
                     -data-dir=~/projects/consul/data -config-dir ~/projects/consul/consul.d 
                     -bind=10.0.2.18 -client=0.0.0.0 -join=10.0.2.15

可以查看集群成员

consul members

退出集群

consul leave

注册服务

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>
server.port=8501
spring.application.name=spring-cloud-consul-service-1
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
# 注册到 consul 的服务名称
spring.cloud.consul.discovery.serviceName=consul-service-1
@SpringBootApplication
@EnableDiscoveryClient
public class ConsulServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsulServiceApplication.class, args);
    }
}
@RestController
public class Controller {
    @RequestMapping("/hello")
    public String hello() {
        return "consul-service-1";
    }
}
# 实现另一个 service,注册的名字一样,但端口(或 IP)不一样,这样可以在消费端实现负载均衡
server.port=8502
spring.application.name=spring-cloud-consul-service-1
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
# 注册到 consul 的服务名称
spring.cloud.consul.discovery.serviceName=consul-service-1

消费服务

pom 文件参考 service-1
spring.application.name=spring-cloud-consul-consumer
server.port=8503
spring.cloud.consul.host=127.0.0.1
spring.cloud.consul.port=8500
# 设置不需要注册到 consul 中
spring.cloud.consul.discovery.register=false
@SpringBootApplication
public class ConsulConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsulConsumerApplication.class, args);
    }
}
@RestController
public class ServiceController {
    @Autowired
    private LoadBalancerClient loadBalancer;
    @Autowired
    private DiscoveryClient discoveryClient;
 
    @RequestMapping("/services")
    public Object services() {
        // 获取 consul-service-1 服务的信息
        // 这里会返回两个,因为有两个服务注册了这个名字
        return discoveryClient.getInstances("consul-service-1");
    }
 
    @RequestMapping("/discover")
    public Object discover() {
        // 通过 LoadBalancerClient 获取 consul-service-1 服务的其中一个 host
        // 可以看到有时返回 8501 端口,有时返回 8502 端口,这样就实现了负载均衡
        return loadBalancer.choose("consul-service-1").getUri().toString();
    }
}

CAP(C:一致性,A:可用性,P:分区容错性)理论:分布式系统只能满足 CAP 的其中两个

P 是必须保证的
保证 C 就要保证同步,那么同步没完成就不能用,这样就保证不了 A
保证 A 就必须立即返回结果,那么就不能等同步完成,这样就保证不了 C

Consul 属于 CP,优先保证一致性
Eureka 属于 AP,优先保证可用性

ZooKeeper(CP)、etcd(CP) 一定程度上也提供了分布式协调的功能

原文地址:https://www.cnblogs.com/moonlight-lin/p/14129458.html