java使用zookeeper实现注册中心服务

1,408 阅读7分钟

「这是我参与2022首次更文挑战的第29天,活动详情查看:2022首次更文挑战

注册中心场景分析

在分布式服务体系结构的场景下,我们可能将一个大系统拆分为多个子系统,然后各个子系统之间再进行互相调用,这样不仅可以保证服务可用性,同时也便于对某一个子系统进行扩容。项目发布上线,并没有多大的访问量,因此一开始我们的服务可能是这样

image.png 如上图order-service需要调用user-service,在order-service中我们可以使用user-service的的ip:port的方式进行访问调用。

但是随着业务的发展,发现用户端压力比较大,因此需要给user-service进行扩容,最终关系图如下:

image.png 如果系统的调用不是很复杂,可以通过配置管理,然后实现一个简单的客户端负载均衡也是OK的,但是随着业务的发展,服务模块进行更加细粒度的划分,业务也变得更加复杂,再使用简单的配置文件管理,将变得难以维护。当然我们可以再前面加一个服务代理,比如nginx做反向代理, 如下

image.png 如果我们是如下场景呢?

image.png 服务不再是A-B,B-C 那么简单,而是错综复杂的微小服务的调用

这个时候我们可以借助于Zookeeper的基本特性来实现一个注册中心,什么是注册中心,顾名思义,就是让众多的服务,都在Zookeeper中进行注册,啥是注册,注册就是把自己的一些服务信息,比如IP,端口,还有一些更加具体的服务信息,都写到 Zookeeper节点上, 这样有需要的服务就可以直接从zookeeper上面去拿,怎么拿呢? 这时我们可以定义统一的名称,比如,User-Service, 那所有的用户服务在启动的时候,都在User-Service 这个节点下面创建一个子节点(临时节点),这个子节点保持唯一就好,代表了每个服务实例的唯一标识,有依赖用户服务的比如Order-Service 就可以通过User-Service 这个父节点,就能获取所有的User-Service 子节点,并且获取所有的子节点信息(IP,端口等信息),拿到子节点的数据后Order-Service可以对其进行缓存,然后实现一个客户端的负载均衡,同时还可以对这个User-Service 目录进行监听, 这样有新的节点加入,或者退出,Order-Service都能收到通知,这样Order-Service重新获取所有子节点,且进行数据更新。这个用户服务的子节点的类型为临时节点。 第一节课有讲过,Zookeeper中临时节点生命周期是和SESSION绑定的,如果SESSION超时了,对应的节点会被删除,被删除时,Zookeeper 会通知对该节点父节点进行监听的客户端, 这样对应的客户端又可以刷新本地缓存了。当有新服务加入时,同样也会通知对应的客户端,刷新本地缓存,要达到这个目标需要客户端重复的注册对父节点的监听。这样就实现了服务的自动注册和自动退出。

image.png

Spring Cloud 生态也提供了Zookeeper注册中心的实现,这个项目叫 Spring Cloud Zookeeper 下面我们来进行实战。

项目说明:

为了简化需求,我们以两个服务来进行讲解,实际使用时可以举一反三

user-center : 用户服务

product-center: 产品服务

用户调用产品服务,且实现客户端的负载均衡,产品服务自动加入集群,自动退出服务。

项目构建

使用spring网站构建项目

打开spring网站,它给我们提供了便捷的构建流程start.spring.io/

image.png 最后点击GENERATE下载项目,通过上面的方式,我们再创建product-center。

解压下载的项目,用IDE打开

1、使用maven导入项目

image.png 2、查看导入的项目\

image.png

配置zookeeper服务

我们配置的方案为:product-center和user-center可以互相调用的方式。

user-center服务

application.properties

#服务的名称,后面调用就使用这个名称
spring.application.name=user-center
#zookeeper 连接地址 ,
#如果使用了 spring cloud zookeeper config这个配置应该配置在 bootstrap.yml/bootstrap.properties中
spring.cloud.zookeeper.connect-string=192.168.253.131:2181
#将本服务注册到zookeeper,如果不希望自己被发现可以配置为false, 默认为 true
spring.cloud.zookeeper.discovery.register=true

代码编写:

修改启动类,添加负载均衡支持

配置 Resttemplate 支持负载均衡方式,这样就可以多服务管理了

package com.jony.usercenter;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class UserCenterApplication {

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

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

@Bean干啥的?
Spring的@Bean注解用于告诉方法,产生一个Bean对象,然后这个Bean对象交给Spring管理。产生这个Bean对象的方法Spring只会调用一次,随后这个Spring将会将这个Bean对象放在自己的IOC容器中。

SpringIOC 容器管理一个或者多个bean,这些bean都需要在@Configuration注解下进行创建,在一个方法上使用@Bean注解就表明这个方法需要交给Spring进行管理。
@LoadBalanced又是干啥的?
在使用 RestTemplate 的时候 如果 RestTemplate 上面有 这个注解,那么 这个 RestTemplate 调用的 远程地址,会走负载均衡器

编写测试类

TestController, Spring Cloud 支持 Feign, Spring RestTemplate,WebClient 以 逻辑名称,替代具体url的形式访问,如下调用produce不适用ip+port而是改为服务名

package com.jony.usercenter;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpServletRequest;

@RestController
public class TestController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/test")
    public String test(){
        return this.restTemplate.getForObject( "http://product-center/getInfo" ,String.class);
    }

    @GetMapping("/getInfo")
    public String getServerPortAndName(HttpServletRequest request){
        return  "user-center" +" : "+ request.getServerPort();
    }
}

product-center 服务:

application.properties

#服务名称
spring.application.name=product-center
#zookeeper 连接地址
spring.cloud.zookeeper.connect-string=192.168.253.131:2181
#将本服务注册到zookeeper
spring.cloud.zookeeper.discovery.register=true

代码编写

修改启动类
package com.jony.productcenter;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpServletRequest;

@SpringBootApplication
public class ProductCenterApplication {

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


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

}
编写主类,提供服务

主类,接收一个getInfo 请求

package com.jony.productcenter;

import lombok.Value;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpServletRequest;

@RestController
public class ProductController {

    @Autowired
    private RestTemplate restTemplate;


    @GetMapping("/getInfo")
    public String getServerPortAndName(HttpServletRequest request){
        return  "product-center" +" : "+ request.getServerPort();
    }

    @GetMapping("/getUser")
    public String getUserCenter(HttpServletRequest request){
        return this.restTemplate.getForObject( "http://user-center/getInfo" ,String.class);
    }
}

启动两个product-center 实例

通过idea 配置,启动多个实例,用端口区分不同的应用
1、打开配置界面

image.png

2、配置端口号服务名称再复制一个

image.png

3、修改复制服务的服务名称及端口号

image.png

4、启动product的两个服务

image.png

user-center服务启动

启动一个user-center 实例,默认8080端口

image.png

三个服务全部启动完毕,开始浏览器访问

执行结果

user-center调用product-center

访问user-cent端(一个服务),去调用product-center(两个服务) http://localhost:8080/test

image.png

image.png

通过刷新浏览器可以看到,我们成功的完成了通过服务名称进行互相调用的方案啦。这个时候,我们关闭其中product-center:1001,再次刷新浏览器查看结果

image.png

我们可以发现,因为关闭了一个produc-center服务,在调用的时候还是会指向这个服务,导致页面发生错误,过一段时间之后,再次刷新,浏览器就可以正常访问,并且只有product-center:1002的服务。

product-center调用user-center

image.png 可以正常访问,因为user-center只启动了一个服务,因此端口只有8080,有兴趣的同学可以自行再启动一个服务端口。

查看zookeeper客户端信息

我们可以通过连接zookeeper的客户端查看相关信息,如下:

image.png

我们的服务注册到了zookeeper 下 service节点下。

进入product或者ucenter下的子节点 都是一个uuid生成的临时节点,然后我们查节点数据可以得到相关信息,如下:

image.png

总结

在分布式服务集群的情况下,我们可以使用zookeeper来作为注册中心,让我们服务之间的调用,变得便捷,缺点是某个服务在宕机的情况下,无法很快的对非正常服务进行阻断,这个不用担心,目前有很多框架可以实现阻断功能.后面再进行讲述。

要实现服务可以通过服务名进行注册,主要就是以下几个步骤:
1、在配置文件中,声明服务名称,以及将服务注册到zookeeper中。
2、在springboot启动类中,添加Resttemplate,来支持负载均衡 3、调用服务的时候使用服务名称和暴露的方法服务名称。