资料来源: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
@CrossOrigin
public String bodyParams(@RequestBody List<User> users){
System.out.println(users);
return users.toString();
}
// GET有参请求访问
@RequestMapping(value="/params", produces = {"application/json;charset=UTF-8"})
@ResponseBody
public 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"})
@ResponseBody
public String test(){
return "{\"msg\":\"处理返回\"}";
}
}
<a name="LZQiq"></a>
#### 2、新建启动器
```java
package com.bjsxt.httpclientserver;
@SpringBootApplication
public 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-8
String 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")
@ResponseBody
public 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
@CrossOrigin
public 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();
}
// 服务实现内容
@Override
public 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.sh
create /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() {
@Override
public 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() {
@Override
public 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() {
@Override
public 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