Spring Cloud笔记-Zookeeper服务注册与发现(六)

https://github.com/Netflix/eureka/wiki可以看到Eureka 2.0已经停更了,如果项目中在使用Eureka,后续的维护需要自己来做,或者就换其他方案。

这里要说的是替换方案,将服务的注册与发现替换为Zookeeper,这里使用的是3.4.9版本。需要将Zookeeper安装到虚拟机上,关闭防火墙。因为在学习Spring Boot的时候,接触到了Docker,所以,这里用Docker部署一下Zookeeper吧,也算是复习复习。关于Docker安装Zookeeper,可以参考这篇文章

注意,在第一步的时候,需要使用docker pull zookeeper:3.4.9命令下载3.4.9版本的镜像,为了和视频里面保持一致,当然,下载最新版应该也可以。启动的时候,如果下载了3.4.9版本的,启动命令里也要带着版本号,即:docker run -d -p 2181:2181 -p 2888:2888 -p 3888:3888 --restart always zookeeper:3.4.9命令,这里的--restart always表示开机自启。启动后,使用docker ps查看,如果有,说明启动成功了。

1.注册中心Zookeeper

Zookeeper是一个分布式协调工具,可以实现注册中心的功能。按照上面的步骤,在虚拟机里通过Docker部署并启动Zookeeper。

2.服务提供者

新建cloud-provider-payment8004模块,修改pom.xml,注意这里新加的spring-cloud-starter-zookeeper-discovery,其他的没变。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>cloud-provider-payment8004</artifactId>
    <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-zookeeper-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

编写application.yml,配置zookeeper的注册地址。

server:
  port: 8004
spring:
  application:
    name: cloud-provider-payment # 应用名称
  cloud:
    zookeeper:
      connect-string: 192.168.0.123:2181 # Zookeeper的地址

编写主启动类。

package com.atguigu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
// @EnableDiscoveryClient用于向使用Consul或Zookeeper注册中心时注册服务
@EnableDiscoveryClient
public class PaymentMain8004 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain8004.class, args);
    }
}

编写业务类。

package com.atguigu.springcloud.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;

@RestController
@Slf4j
public class PaymentController {
    @Value("${server.port}")
    private String port;

    @RequestMapping("/payment/zookeeper")
    public String paymentZookeeper() {
        return "Spring Cloud With Zookeeper:" + port + "\t" + UUID.randomUUID();
    }
}

这里的Zookeeper是安装在Docker里的,要想进入Docker里的Zookeeper,需要执行docker exec -it 容器id zkCli.sh命令。

启动主程序类,发现报错了,提示Zookeeper的jar包冲突,因为spring-cloud-starter-zookeeper-discovery中会引入zookeeper-3.5.3-beta版本,但是我们用的是3.4.9版本。修改pom.xml,将3.5.3-beta排除掉,手动加入3.4.9版本。

弹幕里提到,有的Zookeeper日志版本冲突,也是做类似的修改即可,排除冲突jar包。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.9</version>
</dependency>

进入Zookeeper,执行zkCli.sh,查看注册进来的服务,这里的cloud-provider-payment就是在application.yml里指定的应用名称。

[zk: localhost:2181(CONNECTED) 0] ls /
[services, zookeeper]
[zk: localhost:2181(CONNECTED) 1] ls /services
[cloud-provider-payment]
[zk: localhost:2181(CONNECTED) 2] ls /services/cloud-provider-payment
[f9ef93bc-a060-4b82-8e92-72b195ed7167]
[zk: localhost:2181(CONNECTED) 3] get /services/cloud-provider-payment/f9ef93bc-a060-4b82-8e92-72b195ed7167
{"name":"cloud-provider-payment","id":"f9ef93bc-a060-4b82-8e92-72b195ed7167","address":"192.168.139.1","port":8004,"sslPort":null,"payload":{"@class":"org.springframework.cloud.zookeeper.discovery.ZookeeperInstance","id":"application-1","name":"cloud-provider-payment","metadata":{}},"registrationTimeUTC":1591436682645,"serviceType":"DYNAMIC","uriSpec":{"parts":[{"value":"scheme","variable":true},{"value":"://","variable":false},{"value":"address","variable":true},{"value":":","variable":false},{"value":"port","variable":true}]}}
cZxid = 0xc
ctime = Sat Jun 06 09:44:43 GMT 2020
mZxid = 0xc
mtime = Sat Jun 06 09:44:43 GMT 2020
pZxid = 0xc
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x172888dc67e0003
dataLength = 534
numChildren = 0

此时生产者已经成功注入Zookeeper了,浏览器访问http://localhost:8004/payment/zookeeper可以请求到结果(当然,这跟Zookeeper并没有关系,只是单纯的访问一下)。

现在我们将生产者服务停掉,去Zookeeper查看服务是否还存着,即ls /services/cloud-provider-payment命令。过了一阵子后,由原来的id值,变成了空。所以,Zookeeper里的服务结点是临时的。

这里补充一点关于Zookeeper和Eureka的比较,那么,需要先说一下CAP理论的东西。

CAP理论是分布式系统的一个概念,C是Consistency(一致性)的首字母,A是Availability(可用性)的首字母,P是Partition tolerance(分区容错性)的首字母。任何一个分布式系统都不能同时满足CAP,只能满足其中的两个。因为在分布式系统中,分区容错性是必须要保证的,那么A和C就二选一了。

为什么C和A不能同时存在?

如果要保证一致性,那么,在一个结点写操作的时候,其他结点必须是锁定读写的,只有完成了数据同步,才能放开读写,锁定期间,其他结点是不可用的。

如果要保证可用性,那么,在一个结点写操作的时候,其他结点就不能锁定,此时,可能还没有完成同步操作,于是,读取到的数据就是旧数据,无法保证一致性。

Zookeeper保证CP

一致性的意思是:写操作后的读操作,必须返回该值。Zookeeper不能保证每次的请求可用性,比如在leader选举时候,集群就是不可用的。选举leader的时间为30-120s,这段时间Zookeeper集群都是不可用的。

Eureka保证AP

可用性的意思是:集群中各个节点是平等的,如果有几个结点挂掉不影响正常结点的工作,剩余结点依旧可以提供服务,只要有一台Eureka还存着,就能保证注册服务的可用,不过,信息可能不是最新的。

3.服务消费者

创建cloud-consumerzk-order80模块,修改pom.xml。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>cloud-consumerzk-order80</artifactId>
    <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-zookeeper-discovery</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.zookeeper</groupId>
                    <artifactId>zookeeper</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.9</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

编写application.yml配置文件。

server:
  port: 80
spring:
  application:
    name: cloud-consumer-order # 应用名称
  cloud:
    zookeeper:
      connect-string: 192.168.0.123:2181 # Zookeeper的地址

编写主启动类。

package com.atguigu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class OrderZkMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderZkMain80.class, args);
    }
}

在消费者端调用生产者,这里依旧采用RestTemplate来调用,编写配置类,把RestTemplate注入到容器中。

package com.atguigu.springcloud.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ApplicationContextConfig {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

编写业务类,通过RestTemplate,用应用名称访问Zookeeper中注册地址,实现调用。

package com.atguigu.springcloud.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

@RestController
@Slf4j
public class OrderZkController {
    private static final String INVOKE_URL = "http://cloud-provider-payment";
    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer/payment/zookeeper")
    public String paymentInfo() {
        return restTemplate.getForObject(INVOKE_URL + "/payment/zookeeper", String.class);
    }
}

启动cloud-provider-payment8004和cloud-consumerzk-order80的主启动类,在zkCli.sh下,通过命令ls /services可以查看到两个服务,这就表明服务注册成功了。通过浏览器访问http://localhost:8004/payment/zookeeperhttp://localhost/consumer/payment/zookeeper都可以看到信息,其中/consumer/payment/zookeeper请求会调用http://cloud-provider-payment/payment/zookeeper,其中cloud-provider-payment是服务名,从Zookeeper中找到真实的地址,发送/payment/zookeeper请求,此时,就发送到了生产者的controller上面,从而完成请求。

在RestTemplate这个bean注入的时候,我们发现在方法上添加了@LoadBalanced注解,不过,此时只有一台Zookeeper,为什么要加@LoadBalanced注解呢?一台机器怎么做负载均衡?

之前,对@LoadBalanced的理解停留在:加上它就可以实现负载均衡了,除了负载均衡,它还有将服务名转换成IP的功能,也就是根据服务名cloud-provider-payment,找到192.168.0.123地址。

我们查看LoadBalancerAutoConfiguration类,在restTemplateCustomizer()方法中,会给RestTemplate加上一个拦截器,从而让RestTemplate成为一个具有负载均衡功能的请求器。这个拦截器是ClientHttpRequestInterceptor类型的,这是一个接口,我们关注它的实现类LoadBalancerInterceptor,找到interceptor()方法,通过getHost()获取服务名,调用this.loadBalancer.execute()方法,发送请求。这里的loadBalancer是LoadBalancerClient类型的,而LoadBalancerClient是一个接口,我们关注它的实现类RibbonLoadBalancerClient,找到execute()方法,根据serviceId(也就是服务名)通过getLoadBalancer()方法,获取一个ILoadBalancer对象,再调用getServer()方法,在getServer()方法中调用chooseServer()方法拿到server(也就是根据服务名获取到ip地址和端口号)。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值