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组件中查看,并下载。
首发地址:
品茗IT-同步发布
品茗IT提供在线支持:
一键快速构建Spring项目工具
一键快速构建SpringBoot项目工具
一键快速构建SpringCloud项目工具
一站式Springboot项目生成
Mysql一键生成Mybatis注解Mapper
Mysql一键生成SpringDataRest项目
如果大家正在寻找一个java的学习环境,或者在开发中遇到困难,可以加入我们的java学习圈,点击即可加入,共同学习,节约学习时间,减少很多在学习中遇到的难题。
一、引入依赖
需要引入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的版本。
依赖如下:
4.0.0
cn.pomit
springcloudwork
0.0.1-SNAPSHOT
ZkClient
ZkClient
http://maven.apache.org
UTF-8
UTF-8
1.8
2.6
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-zookeeper-discovery
org.apache.zookeeper
zookeeper
org.apache.zookeeper
zookeeper
3.4.14
org.slf4j
slf4j-log4j12
org.springframework.cloud
spring-cloud-starter-netflix-ribbon
org.springframework.cloud
spring-cloud-starter-openfeign
父模块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 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;
}
}
快速构建项目
Spring组件化构建
SpringBoot组件化构建
SpringCloud服务化构建
喜欢这篇文章么,喜欢就加入我们一起讨论SpringBoot使用吧!