专栏首页品茗ITSpringCloud技术指南系列(七)服务注册发现之Zookeeper服务调用

SpringCloud技术指南系列(七)服务注册发现之Zookeeper服务调用

SpringCloud技术指南系列(七)服务注册发现之Zookeeper服务调用

SpringCloud所谓的服务注册与发现,流程大致是:

  • 将Springboot微服务客户端项目的地址等信息,通过网络发送到注册中心,由注册中心保存下来。
  • 另一个客户端B访问已经注册到注册中心的服务A,通过注册中心提供的域名解析方式,解析出服务A的地址等信息。
  • 如果提供服务A的客户端有多个,就按照某个策略(比如轮询、负载均衡等)选取一个地址返回。
  • 客户端B访问注册中心返回的地址,获取结果,这里注意,是B直接访问A,而不是注册中心转发,因此要保证B和A是互通的。

目前服务发现的解决方案有Eureka,Consul,Zookeeper等,这三个是SpringCloud官方支持的。

前几篇已经讲了如何搭建Eureka的服务注册发现,上篇介绍了Consul的服务注册。本篇讲下Consul的服务发现,使用两种方式进行服务发现。

代码可以在SpringBoot组件化构建https://www.pomit.cn/java/spring/springcloud.html中的ZkClient和ZkFeign组件中查看,并下载。

**如果大家正在寻找一个java的学习环境,或者在开发中遇到困难,可以<a

href="https://jq.qq.com/?_wv=1027&k=52sgH1J"

target="_blank">

加入我们的java学习圈,点击即可加入

</a>

,共同学习,节约学习时间,减少很多在学习中遇到的难题。**

一、引入依赖

需要引入spring-boot-starter-web和spring-cloud-starter-zookeeper-discovery,因为要进行服务调用,所以要引入spring-cloud-starter-netflix-ribbon,如果要使用openfeign来进行服务调用,则可以引入spring-cloud-starter-openfeign。

spring-cloud-starter-zookeeper-discovery默认引入的zookeeper是3.5的版本,因此如果你安装的zookeeper是3.4的版本,就必须排除zookeeper的依赖,并引入3.4的版本。

依赖如下:

<?xml version="1.0"?>
<project
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
	xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>cn.pomit</groupId>
		<artifactId>springcloudwork</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>
	<artifactId>ZkClient</artifactId>
	<name>ZkClient</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
		<maven-jar-plugin.version>2.6</maven-jar-plugin.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-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.14</version>
			<exclusions>
				<exclusion>
					<groupId>org.slf4j</groupId>
					<artifactId>slf4j-log4j12</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<!-- 使用ribbon时才用的上 -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
		</dependency>

                <!-- 使用feign时才用的上 -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-openfeign</artifactId>
		</dependency>
	</dependencies>
</project>

父模块pom文件可以在https://www.pomit.cn/spring/SpringCloudWork/pom.xml获取。

二、配置zookeeper服务注册发现

这里使用yaml文件写配置,application.yml:

server:
   port: 8812
spring:
   application:
      name: zkClient
   cloud:
      zookeeper:
         connect-string: localhost:2181
         discovery:
            enabled: true

这里面,包含了端口、应用名、zookeeper注册中心信息。

spring.application.name是标识了应用名,注册到zookeeper之后,显示的就是它。

spring.cloud.zookeeper.connect-string是指明了zookeeper的地址端口。

spring.cloud.zookeeper.discovery.enabled 是开启服务注册发现。

三、启动服务注册发现

上一篇已经注册了一个服务:zkServer。这一篇我们可以注册两个zkClient和zkFeign,来访问zkServer的服务。

在这里插入图片描述

如图所示,用zkCli可以查看我们注册的服务,在/services节点下,我们注册了zkServer、zkClient、zkFeign服务。

3.1 启动类

使用@EnableDiscoveryClient注解启动类, @EnableDiscoveryClient是将项目作为客户端注册到注册中心的注解,开启服务发现功能。

如果是使用Feign,需要加上@EnableFeignClients注解,开启Feign的使用

ZkFeignApplication :

package cn.pomit.springbootwork.zkfeign;

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

//@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class ZkFeignApplication {
	public static void main(String[] args) {
		SpringApplication.run(ZkFeignApplication.class, args);
	}
	
	@Bean
	@LoadBalanced
	RestTemplate restTemplate() {
		return new RestTemplate();
	}
}

这里的RestTemplate使用@LoadBalanced注解,我们跟踪RestTemplate的时候可以看到,RestTemplate多了个LoadBalancerInterceptor。

3.2 Ribbon做服务调用

如果我们使用Ribbon做服务调用,需要使用RestTemplate,这个RestTemplate是标识为负载均衡的。我们来调用上一篇提供的ip服务:

IpInfoService :

package cn.pomit.springbootwork.zkclient.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import cn.pomit.springbootwork.zkclient.model.ResultModel;

@Service
public class IpInfoService {
	/**
	 * 第一个zkServer是配置文件种的spring.application.name,第二个zkApi是controller中配置的路径
	 */
	public static String remoteIpServiceUrl = "http://zkServer/zkApi/ip";
	@Autowired
	private RestTemplate restTemplate;

	public ResultModel getIpInfo() {
		ResponseEntity<ResultModel> ipModel = restTemplate.getForEntity(remoteIpServiceUrl, ResultModel.class);
		return ipModel.getBody();
	}
}

这里,第一个zkServer是服务提供方配置文件种的spring.application.name,第二个zkApi是服务提供方的controller中配置的路径。我们使用统一的实体ResultModel进行数据接收转换。

3.4 Feign做服务调用

如果我们使用Feign做服务调用,写法就和controller中写法类似,需要注意的是,如果带参数,需要使用@RequestParam("")标识参数名 :

IpInfoService :

package cn.pomit.springbootwork.zkfeign.service;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import cn.pomit.springbootwork.zkfeign.model.ResultModel;

@FeignClient("zkServer")
public interface IpInfoService {
	@RequestMapping(method = RequestMethod.GET, value = "/zkApi/ip", consumes = "application/json")
	public ResultModel getIpInfo();
}

这里,@FeignClient中的zkServer是服务提供方配置文件种的spring.application.name,/zkApi/ip中的zkApi是服务提供方的controller中配置的路径。方法尽量和服务提供方的方法一样。

3.5 测试Web

ZkClientRest :

package cn.pomit.springbootwork.zkclient.web;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import cn.pomit.springbootwork.zkclient.model.ResultModel;
import cn.pomit.springbootwork.zkclient.service.IpInfoService;

@RestController
@RequestMapping("/zkClient")
public class ZkClientRest {
	@Autowired
	IpInfoService ipInfoService;

	@RequestMapping(value = "/test", method = { RequestMethod.GET })
	public String test() {
		return "Hello, Hello";
	}

	@RequestMapping(value = "/ip", method = { RequestMethod.GET })
	public ResultModel ip() {
		return ipInfoService.getIpInfo();
	}
}

四、过程中用到的实体

过程中用到了ResultModel实体和ResultCode枚举类,是和上一篇中的实体对应的,作为统一的实体来用。

ResultModel:

package cn.pomit.springbootwork.zkclient.model;

/**
 * @author cff
 */
public class ResultModel {

	private String errorCode;
	private String message;
	private Object data;

	public ResultModel() {

	}

	public ResultModel(String errorCode, String message) {
		this.errorCode = errorCode;
		this.message = message;
	}

	public ResultModel(String errorCode, String message, Object data) {
		this.errorCode = errorCode;
		this.message = message;
		this.data = data;
	}

	public ResultModel(ResultCode resultCodeEnum, Object data) {
		this.errorCode = resultCodeEnum.getCode();
		this.message = resultCodeEnum.getDesc();
		this.data = data;
	}

	public ResultModel(ResultCode resultCodeEnum) {
		this.errorCode = resultCodeEnum.getCode();
		this.message = resultCodeEnum.getDesc();
	}

	public String geterrorCode() {
		return errorCode;
	}

	public void seterrorCode(String errorCode) {
		this.errorCode = errorCode;
	}

	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}

	public Object getData() {
		return data;
	}

	public void setData(Object data) {
		this.data = data;
	}

	public static ResultModel ok() {
		return new ResultModel(ResultCode.CODE_00000);
	}

	public static ResultModel ok(Object data) {
		return new ResultModel(ResultCode.CODE_00000, data);
	}

	public static ResultModel error() {
		return new ResultModel(ResultCode.CODE_00001);
	}

	public static ResultModel error(String msg) {
		return new ResultModel(ResultCode.CODE_00001.getCode(), msg);
	}

	public static ResultModel error(String msg, Object data) {
		return new ResultModel(ResultCode.CODE_00001.getCode(), msg, data);
	}

	public static ResultModel unAuth() {
		return new ResultModel(ResultCode.CODE_40004);
	}
}

ResultCode:

package cn.pomit.springbootwork.zkclient.model;

/**
 * 响应码及其描述 Created by txl on 15/7/9.
 */
public enum ResultCode {

	/**
	 * 通用
	 */
	CODE_00000("00000", "操作成功"), CODE_00001("00001", "请求失败"), CODE_00002("00002", "错误的请求方法"), CODE_00003("00003", "非法的参数字段"), CODE_00004("00004", "异常抛出"), CODE_00005("00005", "权限不足"), CODE_00006("00006", "分页limit参数错误"), CODE_00007("00007", "分页offset参数错误"), CODE_00009("00009", "请求过于频繁"), CODE_00010("00010", "数据已存在"), CODE_00011("00011", "数据不存在"), CODE_00012("00012", "参数缺失"), CODE_00013("00013", "系统维护中"), CODE_00014("00014", "token缺失"), CODE_00015("00015", "token失效"), CODE_00016("00016", "签名错误"),

	CODE_10000("10000", "操作部分成功"),
	/**
	 * 系统
	 */
	CODE_30000("30000", "系统ID错误"),

	/**
	 * 授权
	 */
	CODE_40001("40001", "用户未找到"), CODE_40002("40002", "该用户状态异常"), CODE_40003("40003", "该用户已被删除"), CODE_40004("40004", "授权异常"),

	CODE_99999("99999", "签名无效");

	private String code;
	private String desc;

	ResultCode(String code, String desc) {
		this.code = code;
		this.desc = desc;
	}

	public String getCode() {
		return code;
	}

	public String getDesc() {
		return desc;
	}

	/**
	 * 根据code匹配枚举
	 * 
	 * @param code
	 * @return
	 */
	public static ResultCode getResultCodeByCode(String code) {
		for (ResultCode resultCode : ResultCode.values()) {
			if (code.equals(resultCode.getCode())) {
				return resultCode;
			}
		}
		return null;
	}

	public static ResultCode getResultCodeByDesc(String desc) {
		for (ResultCode resultCode : ResultCode.values()) {
			if (desc.equals(resultCode.getDesc())) {
				return resultCode;
			}
		}
		return null;
	}
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Netty自动重连机制的Demo

    官方案例地址:https://netty.io/4.1/xref/io/netty/example/uptime/package-summary.html

    Java猫说
  • ES6中的Class回顾总结三:封装与多态

    在Java中,实现了public完全开放、protected对子类开放、private对自己开放这三种封装的方式。但在ES6中目前并不支持,未来是否会支持也不知...

    前端_AWhile
  • 解Bug之路-dubbo流量上线时的非平滑问题

    笔者最近解决了一个困扰了业务系统很久的问题。这个问题只在发布时出现,每次只影响一两次调用,相较于其它的问题来说,这个问题有点不够受重视。由于种种原因,使得这个问...

    无毁的湖光-Al
  • 《你不知道的JavaScript》:迭代器Iterator的背景梳理

    本篇复习下上篇用到的Symbol.iterator,它是ES6内置的十一个Symbol值之一。ES6中规定对象的Symbol.iterator属性指向该对象的默...

    前端_AWhile
  • V8 Ignition:JS 引擎与字节码的不解之缘(转载)

    首先贴个Javascript性能测试站点,测试并展示了数个 JavaScript 引擎的性能数据:arewefastyet

    Clearlove
  • 《你不知道的JavaScript》:弄清生成器与迭代器的区别

    这篇将整理下生成器与迭代器的区别。这两个东西初学的时候我是混淆的,尤其《你不知道的Javascript》书中没有进行基础的介绍,我看了之后还特地翻了下其他资料才...

    前端_AWhile
  • 《你不知道的JavaScript》:生成器中的生产者和迭代器

    生成器的一种有趣用法是作为一种产生值的方式。而这也是“生成器”这个名称的最初使用场景。前面说过生成器函数每次调用都会创建一个迭代器实例。这个迭代器实例有next...

    前端_AWhile
  • 《你不知道的JavaScript》:利用生成器实现消息传递

    js中生成器函数是一个特殊的函数,具有上一篇中展示出来的“暂停”的执行模式。但它仍然是一个函数,这意味着它仍然有一些基本的特性没有改变。例如它仍然可以接收参数(...

    前端_AWhile
  • ELK学习笔记之filebeat合并多行日志示例

    来自Elasticsearch等服务的活动日志通常以时间戳开始,然后是关于特定活动的信息,如下例所示:

    Jetpropelledsnake21
  • Excel VBA解读(160): 数据结构—字典对象的基本操作(续)

    Set dict = CreateObject("Scripting.Dictionary")

    fanjy

扫码关注云+社区

领取腾讯云代金券