资料来源:https://www.bilibili.com/video/BV11i4y1N7LQ?p=8
主要内容
项目结构变化
2. RPC简介
3. RMI实现RPC
4. HttpClient实现RPC
5. Zookeeper安装
6. Zookeeper客户端常用命令
7. 向Zookeeper中注册内容
8. 从Zookeeper中发现内容
9. 手写RPC框架一、项目架构变化
1、单体架构
1.1 架构图
单体架构就是一个项目里面包含这个项目中全部代码。一个应用搞定全部功能。
DNS 服务器可以是单映射,也可以配置多个映射。
1.2 软件代码结构
在单体架构项目中,团队都是通过包(package)进行区分每个模块。
总体包结构:com.bjsxt.*.分层包。项目名:-- com--bjsxt-- common-- utils--user-- controller-- service-- mapper-- sys-- controller-- service-- mapper
1.3 优缺点
1、优点:部署简单、维护方便、开发成本低
2、缺点:当项目规模大、用户访问频率高、并发量大、数据量大时,会大大降低程序执行效率,甚至出现服务器宕机等情况。1.4 适用项目
2、分布式架构
2.1 架构图(简易版)
分布式架构会把一个项目按照特定要求(多按照模块或功能)拆分成多个项目,每个项目分别部署到不同的服务器上。
2.2 软件代码结构
项目1:--com.bjsxt.xxx-- controller-- service-- mapper项目2--com.bjsxt.mmm-- controller-- service-- mapper
2.3 优缺点
1、优点
- 增大了系统可用性。减少单点故障,导致整个应用不可用。
- 增加重用性。因为模块化,所以重用性更高。高内聚、低耦合。
- 增加可扩展性。有新的模块增加新的项目即可。
- 增加每个模块的负载能力。因为每个模块都是一个项目,所以每个模块的负载能力更强。
2、缺点
- 成本更高。因为技术多、难,所以开发成本、时间成本、维护成本都在变高。
- 架构更加复杂。
- 整体响应之间变长,一些业务需要多项目通信后给出结果。
- 吞吐量更大。吞吐量= 请求数/秒。
2.4 适用项目
中、大型互联网项目。客户多,数据多,访问并发高,压力大,吞吐量高。2.5 待解决问题
分布式架构中各个模块如何进行通信?
可以使用Http协议,也可以使用RPC协议通信,也可以使用其他的通信方式。我们本阶段使用的是RPC协议,因为它比HTTP更适合项目内部通信。二、RPC简介
1、RFC
RFC(Request For Comments) 是由互联网工程任务组(IETF)发布的文件集。文件集中每个文件都有自己唯一编号,例如:rfc1831。目前RFC文件由互联网协会(Internet Society,ISOC)赞助发行。
RPC就收集到了rfc 1831中。可以通过下面网址查看:https://datatracker.ietf.org/doc/rfc1831/2、RPC
RPC在rfc 1831中收录 ,RPC(Remote Procedure Call) 远程过程调用协议
RPC协议规定允许互联网中一台主机程序调用另一台主机程序,而程序员无需对这个交互过程进行编程。在RPC协议中强调当A程序调用B程序中功能或方法时,A是不知道B中方法具体实现的。
RPC是上层协议,底层可以基于TCP协议,也可以基于HTTP协议。一般我们说RPC都是基于RPC的具体实现,如:Dubbo框架。从广义上讲只要是满足网络中进行通讯调用都统称为RPC,甚至HTTP协议都可以说是RPC的具体实现,但是具体分析看来RPC协议要比HTTP协议更加高效,基于RPC的框架功能更多。
RPC协议是基于分布式架构而出现的,所以RPC在分布式项目中有着得天独厚的优势。3、RPC和HTTP对比
3.1 具体实现
RPC:可以基于TCP协议,也可以基于HTTP协议。
HTTP:基于HTTP协议。3.2 效率
RPC:自定义具体实现可以减少很多无用的报文内容,使得报文体积更小。
HTTP:如果是HTTP 1.1 报文中很多内容都是无用的。如果是HTTP2.0以后和RPC相差不大,比RPC少的可能就是一些服务治理等功能。3.3 连接方式
RPC:支持长连接。
HTTP:每次连接都是3次握手。(断开链接为4次挥手)3.4 性能
RPC可以基于很多序列化方式。如:thrift
HTTP 主要是通过JSON,序列化和反序列效率更低。3.5 注册中心
RPC :一般RPC框架都带有注册中心。
HTTP:都是直连。3.6 负载均衡
RPC:绝大多数RPC框架都带有负载均衡测量。
HTTP:一般都需要借助第三方工具。如:nginx3.7 综合结论
RPC框架一般都带有丰富的服务治理等功能,更适合企业内部接口调用。而HTTP更适合多平台之间相互调用。三、HttpClient实现RPC
1、HttpClient简介
在JDK中java.net包下提供了用户HTTP访问的基本功能,但是它缺少灵活性或许多应用所需要的功能。
HttpClient起初是Apache Jakarta Common 的子项目。用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本。2007年成为顶级项目。
通俗解释:HttpClient可以实现使用Java代码完成标准HTTP请求及响应。2、代码实现
2.1 服务端
1、新建控制器
```java package com.bjsxt.httpclientserver.controller;
@Controller public class TestController { /**
* CrossOrigin - 跨域请求注解。在响应头上增加跨域处理允许。 可以让ajax跨域请求当前的服务方法。* 如果用于注解类型(必须是控制器),代表当前控制器中所有的方法,都允许跨域访问。* @param users* @return*/// 使用请求体传递请求参数@RequestMapping(value="/bodyParams", produces = {"application/json;charset=UTF-8"})@ResponseBody@CrossOriginpublic String bodyParams(@RequestBody List<User> users){System.out.println(users);return users.toString();}// GET有参请求访问@RequestMapping(value="/params", produces = {"application/json;charset=UTF-8"})@ResponseBodypublic String params(String name, String password){System.out.println("name - " + name + " ; password - " + password);return "{\"msg\":\"登录成功\", \"user\":{\"name\":\"" + name + "\",\"password\":\"" + password + "\"}}";}// GET无参请求访问@RequestMapping(value="/test", produces = {"application/json;charset=UTF-8"})@ResponseBodypublic String test(){return "{\"msg\":\"处理返回\"}";}
}
<a name="LZQiq"></a>#### 2、新建启动器```javapackage com.bjsxt.httpclientserver;@SpringBootApplicationpublic class ServerApp {public static void main(String[] args) {SpringApplication.run(ServerApp.class, args);}}
2.2 客户端
1、添加依赖
<dependencies><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.12</version></dependency></dependencies>
2、使用GET无参请求访问
package com.bjsxt.httpclient;/*** 使用Main方法,测试HttpClient技术。*/public class TestHttpClient {public static void main(String[] args) throws Exception {testGetNoParams();}/*** 无参数GET请求* 使用浏览器,访问网站的过程是:* 1、 打开浏览器* 2、 输入地址* 3、 访问* 4、 看结果* 使用HttpClient,访问WEB服务的过程:* 1、 创建客户端,相当于打开浏览器* 2、 创建请求地址, 相当于输入地址* 3、 发起请求, 相当于访问网站(回车键)* 4、 处理响应结果, 相当于浏览器显示结果*/public static void testGetNoParams() throws IOException {// 创建客户端对象HttpClient client = HttpClients.createDefault();// 创建请求地址HttpGet get = new HttpGet("http://localhost:80/test");// 发起请求,接收响应对象HttpResponse response = client.execute(get);// 获取响应体。 响应数据是一个基于HTTP协议标准字符串封装的对象。// 所以,响应体和响应头,都是封装的HTTP协议数据。直接使用可能有乱码或解析错误HttpEntity entity = response.getEntity();// 通过HTTP实体工具类,转换响应体数据。 使用的字符集是UTF-8String responseString = EntityUtils.toString(entity, "UTF-8");System.out.println("服务器响应数据是 - [ " + responseString + " ]");// 回收资源client = null;}}
2、使用GET有参请求访问
package com.bjsxt.httpclient;/*** 使用Main方法,测试HttpClient技术。*/public class TestHttpClient {public static void main(String[] args) throws Exception {testGetParams();}/*** 有参数GET请求* @throws IOException*/public static void testGetParams() throws IOException, URISyntaxException {HttpClient client = HttpClients.createDefault();// 基于Builder构建请求地址URIBuilder builder = new URIBuilder("http://localhost/params");// 基于单参数传递,构建请求地址// builder.addParameter("name", "bjsxt");// builder.addParameter("password", "admin123");// URI uri = builder.build();// 基于多参数传递,构建请求地址List<NameValuePair> nvps = new ArrayList<>();nvps.add(new BasicNameValuePair("name","bjsxt"));nvps.add(new BasicNameValuePair("password", "admin123"));builder.addParameters(nvps);URI uri = builder.build();System.out.println(uri.toASCIIString());HttpGet httpGet = new HttpGet(uri);HttpResponse response = client.execute(httpGet);HttpEntity entity = response.getEntity();String result = EntityUtils.toString(entity);System.out.println(result);}}
3、使用POST请求访问
package com.bjsxt.httpclient;/*** 补充:* property - 类型中getter和setter的命名。去除set|get,剩余部分首字母转小写。* <bean><property name="abc"></property></bean>* field - 类型中定义的实例变量。** ajax -> 请求, 参数的json格式的字符串对象。** 使用Main方法,测试HttpClient技术。*/public class TestHttpClient {public static void main(String[] args) throws Exception {testPost();}/*** POST请求*/public static void testPost() throws Exception{HttpClient client = HttpClients.createDefault();//1、无参数Post请求HttpPost post = new HttpPost("http://localhost/test");HttpResponse response = client.execute(post);System.out.println(EntityUtils.toString(response.getEntity(), "UTF-8"));//2、有参数的Post请求//2.1 请求头传递参数。和Get请求携带参数的方式一致。URIBuilder builder = new URIBuilder("http://localhost/params");builder.addParameter("name", "post");builder.addParameter("password", "postPassword");URI uri = builder.build();HttpPost httpPost = new HttpPost(uri);HttpResponse postResponse = client.execute(httpPost);System.out.println(EntityUtils.toString(postResponse.getEntity(), "UTF-8"));//2.2 请求体传递参数HttpPost bodyParamsPost = new HttpPost("http://localhost/bodyParams");// 定义请求协议体,设置请求参数。 使用请求体传递参数的时候,需要定义请求体格式。默认是表单格式。// 使用URIBuilder构建的URI对象,就是请求体传递参数的。// 方式一:使用这种方式传递参数不符合要求,输出结果为null// HttpEntity entity = new StringEntity("name=abc&password=123");// bodyParamsPost.setEntity(entity);// String responseString = EntityUtils.toString(client.execute(bodyParamsPost).getEntity(), "UTF-8");User u1 = new User();u1.setName("name1");u1.setPassword("password1");User u2 = new User();u2.setName("name2");u2.setPassword("password2");// 方式二:拼接一个JSON格式字符串,表示请求参数, 一个List<User>,请求头设置为application/json// String paramsString = "[" + u1.toString() + ", " + u2.toString() + "]";// HttpEntity entity = new StringEntity(paramsString, "application/json", "UTF-8");// bodyParamsPost.setEntity(entity);// String responseString = EntityUtils.toString(client.execute(bodyParamsPost).getEntity(), "UTF-8");// 方式三:List<User> users = new ArrayList<>();users.add(u1);users.add(u2);// 把集合users -> JSON字符串// 创建Jackson中的转换器对象ObjectMapper objectMapper = new ObjectMapper();// java对象转换成JSON格式字符串String paramsString = objectMapper.writeValueAsString(users);HttpEntity entity = new StringEntity(paramsString, "application/json", "UTF-8");bodyParamsPost.setEntity(entity);String responseString = EntityUtils.toString(client.execute(bodyParamsPost).getEntity(), "UTF-8");String userString = responseString.substring(1, responseString.indexOf("},")+1);User responseUser = objectMapper.readValue(userString, User.class);System.out.println(responseUser);// 构建一个Jackson识别的Java类型映射。JavaType valueType = objectMapper.getTypeFactory().constructParametricType(List.class, User.class);List<User> responseList = objectMapper.readValue(responseString, valueType);System.out.println(responseList);}}
3、HttpClient请求包含JSON
package com.bjsxt.httpclient;/*** 补充:* property - 类型中getter和setter的命名。去除set|get,剩余部分首字母转小写。* <bean><property name="abc"></property></bean>* field - 类型中定义的实例变量。** ajax -> 请求, 参数的json格式的字符串对象。** 使用Main方法,测试HttpClient技术。*/public class TestHttpClient {public static void main(String[] args) throws Exception {testPost();}/*** POST请求*/public static void testPost() throws Exception{HttpClient client = HttpClients.createDefault();HttpPost bodyParamsPost = new HttpPost("http://localhost/bodyParams");// 定义请求协议体,设置请求参数。 使用请求体传递参数的时候,需要定义请求体格式。默认是表单格式。// 使用URIBuilder构建的URI对象,就是请求体传递参数的。User u1 = new User();u1.setName("name1");u1.setPassword("password1");User u2 = new User();u2.setName("name2");u2.setPassword("password2");List<User> users = new ArrayList<>();users.add(u1);users.add(u2);// 把集合users -> JSON字符串// 创建Jackson中的转换器对象ObjectMapper objectMapper = new ObjectMapper();// java对象转换成JSON格式字符串String paramsString = objectMapper.writeValueAsString(users);System.out.println(paramsString);HttpEntity entity = new StringEntity(paramsString, "application/json", "UTF-8");bodyParamsPost.setEntity(entity);String responseString = EntityUtils.toString(client.execute(bodyParamsPost).getEntity(), "UTF-8");// 将单一对象转换为字符串String userString = responseString.substring(1, responseString.indexOf("},") + 1);User responseUser = objectMapper.readValue(userString, User.class);System.out.println(responseUser);// 构建一个Jackson识别的Java类型映射。JavaType valueType = objectMapper.getTypeFactory().constructParametricType(List.class, User.class);List<User> responseList = objectMapper.readValue(responseString, valueType);System.out.println(responseList);}}
public class HttpClientDemo {public static void main(String[] args) {try {CloseableHttpClient httpClient = HttpClients.createDefault();HttpPost post = new HttpPost("http://localhost:8080/demo");HttpEntity httpEntity= null;String json = "{}";StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);post.setEntity(entity);CloseableHttpResponse response = httpClient.execute(post);String result = EntityUtils.toString(response.getEntity());System.out.println(result);response.close();httpClient.close();} catch (IOException e) {e.printStackTrace();}}}
4、服务端控制器接口参数
@RequestBody把请求体中流数据转换为指定的对象。多用在请求参数是json数据且请求的Content-Type=”application/json”
@RequestMapping("/demo4")@ResponseBodypublic String demo4(@RequestBody List<People> list) {System.out.println(list);return list.toString();}
5、Jackson用法
5.1 把对象转换为json字符串
ObjectMapper objectMapper = new ObjectMapper();People peo = new People();String jsonStr = objectMapper.writeValueAsString(peo);
5.2 把json字符串转换为对象
ObjectMapper objectMapper = new ObjectMapper();People peo = objectMapper.readValue(jsonStr, People.class);
5.3 把json字符串转换为List集合
ObjectMapper objectMapper = new ObjectMapper();JavaType javaType = objectMapper.getTypeFactory().constructParametricType(List.class, People.class);List<People> list = objectMapper.readValue(jsonStr, javaType);
6、Ajax发送json参数写法
var json = '[{"id":123,"name":"bjsxt"},{"id":123,"name":"bjsxt"}]';$.ajax({url:'/demo5',type:'post',success:function(data){alert(data);for(var i = 0 ;i<data.length;i++){alert(data[i].id +" "+data[i].name);}},contentType:'application/json',//请求体中内容类型dataType:'json',//响应内容类型。data:json});
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title><script type="text/javascript" src="jquery.min.js"></script><script type="text/javascript">var url = "123";// javascript中默认的,ajax请求不能跨域。// 跨域 - ajax所属的站点,和被请求的站点,不是同一个域。// 域 - ip,端口,域名,主机名,任何一个有变化,都是不同域。// 需要服务器,返回的响应中增加跨域请求头。function sendBodyParams(){$.ajax({"url": "http://localhost:80/bodyParams","type":"post","data":"[{\"name\":\"abc\", \"password\":\"123\"},{\"name\":\"def\", \"password\":\"321\"}]","contentType":"application/json", // 必须设定,代表请求体的格式。默认是text/plain; 默认是 参数名=参数值&参数名=参数值 的字符串"dataType":"json","success":function(data){alert(data);console.log(data);}});}</script></head><body style="text-align: center"><button onclick="sendBodyParams()">测试AJAX请求,请求体传递JSON参数</button></body></html>
7、跨域
跨域:协议、ip、端口中只要有一个不同就是跨域请求。
同源策略:浏览器默认只允许ajax访问同源(协议、ip、端口都相同)内容。
解决同源策略:在控制器接口上添加@CrossOrigin。表示允许跨域。本质在响应头中添加Access-Control-Allow-Origin: *
@RequestMapping("/demo5")@ResponseBody@CrossOriginpublic List<People> demo5(@RequestBody List<People> list) {System.out.println(list);return list;}
四、RMI实现RPC
1、RMI简介
RMI(Remote Method Invocation)远程方法调用。
RMI是从JDK1.2推出的功能,它可以实现在一个Java应用中可以像调用本地方法一样调用另一个服务器中Java应用(JVM)中的内容。可以跨机器、跨系统、跨网段
RMI 是Java语言的远程调用,无法实现跨语言。
2、执行流程

Registry(注册表)是放置所有服务器对象的命名空间。 每次服务端创建一个对象时,它都会使用bind()或rebind()方法注册该对象。 这些是使用称为绑定名称的唯一名称注册的。
要调用远程对象,客户端需要该对象的引用。即通过服务端绑定的名称从注册表中获取对象:lookup()方法。
3、API介绍
3.1 Remote
java.rmi.Remote:定义了此接口为远程调用接口。如果接口被外部调用,需要继承此接口。服务器和客户端都需要实现此接口
3.2 RemoteException
java.rmi.RemoteException:继承了Remote接口的接口中,如果方法是允许被远程调用的,需要抛出此异常。
3.3 UnicastRemoteObject
java.rmi.server.UnicastRemoteObject:适配器类。此类实现了Remote接口和Serializable接口。
自定义接口实现类除了实现自定义接口还需要继承此类。
3.4 LocateRegistry
java.rmi.registry.LocateRegistry:可以通过LocateRegistry在本机上创建Registry,通过特定的端口就可以访问这个Registry。
3.5 Naming
java.rmi.Naming:Naming定义了发布内容可访问RMI名称。也是通过Naming获取到指定的远程方法
4、代码实现
4.1 创建服务端RmiServer
1、编写接口,定义标准
定义标准:服务器实现此接口。客户端基于此接口创建动态代理
package com.bjsxt.rmi.api;// 定义一个远程服务接口。RMI强制要求,必须是Remote接口的实现。public interface FirstInterface extends Remote {// RMI强制要求,所有的远程服务方法,必须抛出RemoteException。String first(String name) throws RemoteException;}
2、服务端编写实现类
注意:构造方法是public的。默认生成protected
package com.bjsxt.rmi.impl;// 实现远程服务接口。 所有的远程服务实现,必须是Remote接口直接或间接实现类。// 如果不会创建基于RMI的服务标准实现,可以直接继承UnicastRemoteObject来实现此部分功能。public class FirstRMIImpl extends UnicastRemoteObject implements FirstInterface, Remote {// RMI强制要求,所有的方法必须抛出RemoteException,包括构造方法。public FirstRMIImpl() throws RemoteException{super();}// 服务实现内容@Overridepublic String first(String name) throws RemoteException {System.out.println("客户端请求参数是:" + name);return "你好," + name;}}
3、编写主方法,服务创建与注册
package com.bjsxt.rmi;// 主方法,创建一个服务实现对象,提供服务,并注册到Registry上。// RMI的Registry在创建的时候,会自动启动一个子线程,并升级为守护线程(服务线程|精灵线程)。提供持久的服务。public class MainClass {public static void main(String[] args) {try {System.out.println("服务器启动中...");// 创建服务对象FirstInterface first = new FirstRMIImpl();// 注册到Registry(注册中心)上。LocateRegistry.createRegistry(9999);// 绑定一个服务到注册中心。提供命名,格式为:rmi://ip:port/别名// 如果服务重复,抛出异常。 重复的定义是命名冲突。// Naming.bind("rmi://localhost:9999/first", first);// 重新绑定一个服务到自注册中心。 和bind的区别是,命名冲突,直接覆盖。Naming.rebind("rmi://localhost:9999/first", first);System.out.println("服务器启动完毕!");}catch (Exception e){e.printStackTrace();}}}
4、运行项目
运行后项目,项目一直处于启动状态,表示可以远程访问此项目中的远程方法。
4.2 创建客户端代码
1、复制服务端接口
把服务端com.bjsxt.rmi.api.FirstInterface粘贴到项目中。
注意:复制代码仅为快速完成案例,学习技术,在商业开发中,FirstInterface接口应该使用独立的工程定义,并在服务端和客户端工程中通过依赖的方式引入。
2、创建主方法类
package com.bjsxt.rmi;// 客户端主方法public class ClientMainClass {public static void main(String[] args) {// 代理对象的创建。FirstInterface first = null;try{// 使用lookup找服务。通过名字找服务,并自动创建代理对象。// 类型是Object,对象一定是Proxy的子类型,且一定实现了服务接口。first = (FirstInterface) Naming.lookup("rmi://localhost:9999/first");System.out.println("对象的类型是:" + first.getClass().getName());String result = first.first("S106,今天课程讲不完了");System.out.println(result);}catch (Exception e){e.printStackTrace();}}}
五、 Zookeeper基础
1、Zookeeper简介
zookeeper分布式管理软件。常用它做注册中心(依赖zookeeper的发布/订阅功能)、配置文件中心、分布式锁配置、集群管理等。
zookeeper一共就有两个版本。主要使用的是java语言写的。
2、Zookeeper安装
2.1 上传压缩文件
上传到 /usr/local/tmp中
2.2 解压
# tar zxf apache-zookeeper-3.5.5-bin.tar.gz
# cp -r apache-zookeeper-3.5.5-bin /usr/local/zookeeper
2.3 新建data数据目录
进入到zookeeper中
# cd /usr/local/zookeeper
# mkdir data
2.4 修改配置文件
进入conf中
# cd conf
# cp zoo_sample.cfg zoo.cfg
# vim zoo.cfg
修改dataDir为data文件夹路径,用作Zookeeper的数据存储目录。
2.5 启动zookeeper
进入bin文件夹
# cd /usr/local/zookeeper/bin
# ./zkServer.sh start
通过status查看启动状态。稍微有个等待时间
# ./zkServer.sh status
3、Zookeeper客户端常用命令
进入到./zkCli.sh命令行工具后,可以使用下面常用命令。【Zookeeper有个非常好的命令提示方式,只要输入的命令是Zookeeper不识别的命令,Zookeeper会立刻显示所有有效命令提示列表】
zkCli.sh 默认连接的是 localhost:2181 Zookeeper主机。
zkCli.sh -server ip:port 指定连接到 ip:port Zookeeper主机。
3.1、quit
退出ZK客户端控制台。
3.2、ls
ls [-s] [-R] /path
-s 详细信息,替代老版的ls2
-R 当前目录和子目录中内容都罗列出来
例如:ls -R / 显示根目录下所有内容
3.3、create
create [-e] [-s] /path [data]
[data] 包含内容
创建指定路径信息
-e 创建临时节点, 代表当前会话断开,自动删除。
-s 创建节点时,自动为节点命名增加后缀。
例如:
create /demo 创建/demo
create -e /demo 创建临时节点 /demo
create -s /demo 创建命名前缀为/demo的节点
create -e -s /demo 创建命名前缀为/demo的临时节点
3.4、get
get [-s] /path
[-s] 详细信息
查看指定路径下内容。
例如: get -s /demo
null:存放的数据
cZxid:创建时zxid(znode每次改变时递增的事务id)
ctime:创建时间戳
mZxid:最近一次更近的zxid
mtime:最近一次更新的时间戳
pZxid:子节点的zxid
cversion:子节点更新次数
dataversion:节点数据更新次数
aclVersion:节点ACL(授权信息)的更新次数
ephemeralOwner:如果该节点为ephemeral节点(临时,生命周期与session一样), ephemeralOwner值表示与该节点绑定的session id. 如果该节点不是ephemeral节点, ephemeralOwner值为0.
dataLength:节点数据字节数
numChildren:子节点数量
3.5、set
set /path data
设置节点内容
3.6、delete
delete /path
删除节点,不能删除有子节点的节点。
deleteall /path
删除节点,可以删除带有子节点的节点。
rmr /path
删除节点,可以删除带有子节点的节点。已过时,建议使用deleteall。 在旧版本的zk中,没有deleteall命令。
六、Zookeeper增删查内容
1、添加依赖
<dependencies><dependency><groupId>org.apache.zookeeper</groupId><artifactId>zookeeper</artifactId><version>3.6.1</version></dependency></dependencies>
2、创建节点
使用zookeeper的客户端命令工具创建/demo
./zkCli.shcreate /demos
ZooDefs.Ids.OPEN_ACL_UNSAFE:表示权限。
CreateMode.PERSISTENT_SEQUENTIAL:永久存储,文件内容编号递增。
/*** 什么是会话?* 持久、长期、有状态的对象。* 使用Java远程访问zk,步骤是:* 1、 创建客户端* 2、 使用客户端发送命令* 3、 处理返回结果* 4、 回收资源*/public static void create() throws IOException, KeeperException, InterruptedException {// 创建客户端对象ZooKeeper zooKeeper = new ZooKeeper("127.0.0.1:2181", 10000, new Watcher() {@Override // 创建或回收资源public void process(WatchedEvent watchedEvent) {System.out.println("watch中的方法执行");}});// 创建一个节点String result = zooKeeper.create("/parent", "parent data".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);System.out.println("创建parent节点结果:" + result);// 创建一个临时节点String tmpResult = zooKeeper.create("/parent/tmp", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);System.out.println("创建/parent/tmp节点结果:" + tmpResult);// 创建一个带序号的节点String seqResult = zooKeeper.create("/parent/sequence", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);System.out.println("创建/parent/sequence节点结果:"+ seqResult);Thread.sleep(6000);// 关闭客户端zooKeeper.close();}
3、查询节点
/*** 查询节点, 相当于遍历Key*/public static void list() throws Exception{ZooKeeper zooKeeper = new ZooKeeper("127.0.0.1:2181", 10000, new Watcher() {@Overridepublic void process(WatchedEvent watchedEvent) {}});// 遍历zk中所有的节点。listAll(zooKeeper, "/");}private static void listAll(ZooKeeper zooKeeper, String path) throws Exception{// 获取当前节点的所有子节点。List<String> children = zooKeeper.getChildren(path, false);for(String child : children){String currentNodeName = "/".equals(path) ? (path + child) : (path + "/" + child);System.out.println(currentNodeName);listAll(zooKeeper, currentNodeName);}}
4、查询节点中存储的数据
/*** 查询节点中存储的数据。相当于根据key获取value*/public static void get() throws Exception{ZooKeeper zooKeeper = new ZooKeeper("127.0.0.1:2181", 10000, new Watcher() {@Overridepublic void process(WatchedEvent watchedEvent) {}});// 获取数据byte[] datas = zooKeeper.getData("/parent", false, null);System.out.println("节点/parent中存储的数据是:" + new String(datas));}
5、删除节点
/*** 删除节点.* 删除节点前,需要先查询节点的状态(cversion)。通过getData来查询这个版本。* 设计是为了保证删除的节点是你想删除的那个。*/public static void delete() throws Exception{ZooKeeper zooKeeper = new ZooKeeper("127.0.0.1:2181", 10000, new Watcher() {@Overridepublic void process(WatchedEvent watchedEvent) {}});Stat stat = new Stat();System.out.println(stat);zooKeeper.getData("/parent/sequence0000000001", false, stat);System.out.println(stat.getCversion());zooKeeper.delete("/parent/sequence0000000001", stat.getCversion());}
七、手写RPC框架
使用Zookeeper作为注册中心,RMI作为连接技术,手写RPC框架。
1 创建项目ParentDemo
创建父项目ParentDemo。
包含4个聚合子项目。
commons:包含Zookeeper访问工具类。
service:包含被serviceimpl和consumer依赖的接口。
serviceimpl:provider提供的服务内容
consumer:消费者,调用服务内容。
8 在父项目中添加依赖
| <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.0.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.6.1</version> </dependency> </dependencies> |
|---|
9 创建commons项目
13.6 创建ZooKeeperUtils工具类
创建com.bjsxt.commons.ZooKeeperUtils类
| // 工具public class ZooKeeperUtils {
private static ZooKeeper _zooKeeper;
static {
// 初始化ZK客户端对象 try {
_zooKeeper = new ZooKeeper(“192.168.89.140:2181”, 10000, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
}<br /> });<br /> } **catch **(IOException e) {<br /> e.printStackTrace();<br /> _// 初始化代码块发生错误,后续代码没有任何执行的价值。_ _**throw new **ExceptionInInitializerError(**"初始化ZK客户端错误"**);<br /> }<br /> }<br /> _// 存URL到ZK; 注册服务, 就是把本地LocateRegistry中注册的URL存储到ZK。_ // 存储URL的节点,有一个规则, 必须是 /myrpc/接口名称/providers_ // /myrpc/com.bjsxt.service.FirstService/providers_ _**public static boolean **register(String URL, String className){<br /> String path = **"/myrpc/" **+ className + **"/providers"**;**try **{<br /> _// 判断是否需要初始化_ _List<String> children = _zooKeeper_.getChildren(**"/"**, **false**);**if**(!children.contains(**"myrpc"**)){<br /> _// 没有节点,创建_ zooKeeper_.create(**"/myrpc"**, **""**.getBytes(),<br /> ZooDefs.Ids.**_OPEN_ACL_UNSAFE_**,<br /> CreateMode.**_PERSISTENT_**);_// 临时的节点_ zooKeeper_.create(**"/myrpc/"**+className, **""**.getBytes(),<br /> ZooDefs.Ids.**_OPEN_ACL_UNSAFE_**,<br /> CreateMode.**_PERSISTENT_**);_// 临时的节点_ _}**else**{<br /> _// 有节点,判断子节点是否存在_ _children = _zooKeeper_.getChildren(**"/myrpc"**, **false**);<br /> **if**(!children.contains(className)){<br /> _// 没有节点,创建_ zooKeeper_.create(**"/myrpc/"**+className, **""**.getBytes(),<br /> ZooDefs.Ids.**_OPEN_ACL_UNSAFE_**,<br /> CreateMode.**_PERSISTENT_**);_// 临时的节点_ _}<br /> }_zooKeeper_.create(path, URL.getBytes(**"UTF-8"**),<br /> ZooDefs.Ids.**_OPEN_ACL_UNSAFE_**,<br /> CreateMode.**_PERSISTENT_**);_// 节点_ // 代码没有异常。创建成功, 返回true_ _**return true**;<br /> } **catch **(KeeperException e) {<br /> e.printStackTrace();<br /> } **catch **(InterruptedException e) {<br /> e.printStackTrace();<br /> } **catch **(UnsupportedEncodingException e) {<br /> e.printStackTrace();<br /> }<br /> _// 代码发生异常。创建失败,返回false_ _**return false**;<br /> }_// 从ZK中读取URL_ // 从ZK中,查询服务的URL地址。 节点名称有规则,规则是/myrpc/类型名称/providers_ _**public static **String getURL(String className){<br /> **try **{<br /> **byte **[] datas =<br /> _zooKeeper_.getData(**"/myrpc/"**+className+**"/providers"**, **false**, **null**);<br /> **return new **String(datas, **"UTF-8"**);<br /> } **catch **(KeeperException e) {<br /> e.printStackTrace();<br /> } **catch **(InterruptedException e) {<br /> e.printStackTrace();<br /> } **catch **(UnsupportedEncodingException e){<br /> e.printStackTrace();<br /> }<br /> **return null**;<br /> }
}
|
| —- |
10 创建service项目
13.7 创建DemoService接口
创建com.bjsxt.DemoService,具体内容如下:
| public interface DemoService extends Remote { String demo(String param) throws RemoteException; } |
|---|
11 创建serviceimpl项目
此项目编写接口具体实现,RMI服务发布和把信息发送到Zookeeper中。
在pom.xml中添加对service和commons项目的依赖
| <dependencies> <dependency> <artifactId>service</artifactId> <groupId>com.bjsxt</groupId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <artifactId>commons</artifactId> <groupId>com.bjsxt</groupId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> |
|---|
13.8 创建DemoServiceImpl
创建com.bjsxt.service.impl.DemoServiceImpl
| public class DemoServiceImpl extends UnicastRemoteObject implements DemoService { public DemoServiceImpl() throws RemoteException { } @Override public String demo(String param) throws RemoteException{ return param+“123”**; } } |
|---|
13.9 创建RmiRun
创建com.bjsxt.RmiRun。实现RMI服务的发布和Zookeeper消息的发布。
| public class MainClass {
public static void main(String[] args) {
try {
System.out.println(“服务器正在启动…”);
DemoService service = new DemoServiceImpl();
String url = “rmi://localhost:9999/demoService”;
// 创建Registry LocateRegistry._createRegistry(9999);
// 注册服务 Naming._rebind(url, service);
// 存储url到ZK,其他的使用服务的客户端,访问zk获取url。 boolean isSuccess =
ZooKeeperUtils._register(url, DemoService.class.getName());
if(isSuccess){
System.out.println(“服务器启动成功…”);
}else{
System.out.println(“服务器启动失败… 请检查后重新尝试!”);
System.exit(9);
}
}**catch**(Exception e){<br /> e.printStackTrace();<br /> }<br /> }<br />} |
| —- |
12 创建Consumer项目
新建consumer项目,此项目需要从zookeeper中获取rmi信息,并调用rmi服务
在pom.xml中添加对service和commons项目的依赖
| <dependencies> <dependency> <artifactId>service</artifactId> <groupId>com.bjsxt</groupId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <artifactId>commons</artifactId> <groupId>com.bjsxt</groupId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> |
|---|
13 创建接口和实现类
创建com.bjsxt.service.ConsumerService接口
创建com.bjsxt.service.impl.ConsumerServiceImpl实现类
| public interface ConsumerService { String consumerService(String param); } |
|---|
| @Servicepublic class ConsumerServiceImpl implements ConsumerService { @Override public String consumerService(String param) { try { String url = ZooKeeperUtils.getURL(DemoService.class.getName()); if(url == null){ System.out.println(“服务获取失败!”); return; } DemoService demoService =(DemoService) Naming.lookup(_url); String result = demoService.demo(param); System.**_out.println(result); return result; } catch (IOException e) { e.printStackTrace(); } catch (KeeperException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } catch (NotBoundException e) { e.printStackTrace(); } return null**; } } |
14 创建控制器
创建com.bjsxt.controller.DemoController控制器
| @Controllerpublic class **DemoController {
**@Autowired** private **ConsumerService **consumerService**;**@RequestMapping**(**"/demo"**)<br /> **@ResponseBody** public **String demo(String param){<br /> **return consumerService**.consumerService(param);<br /> }<br />} |
| —- |
15 创建启动器
创建com.bjsxt.ConsumerApplication
| @SpringBootApplicationpublic class ConsumerApplication { public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class**,args); } } |
|---|
16 测试
在浏览器输入:http://localhost:8080/demo?param=demo
观察结果是否是:demo123





