纯小白
文章目录
一篇带你了解RPC,ZooKeeper的小博文(小白写的)
一、 项目结构变化
从之前的单体架构—>分布式架构
分布式架构会把一个项目按照特定的要求(多按照模块和功能)拆分成多个项目,每个项目分别部署到不同的服务器上。
优点:
- 增大了系统可用性。减少单点故障,导致整个应用不可用。
- 增加重用性。因为模块化,所以重用性更高。
- 增加可抗展性。有新的模块增加新的项目即可。
- 增加每个模块的负载能力。因为每个模块都是一个项目,所以每个模块的负载能力更强。
缺点:
- 成本更高
- 架构更加复杂。
- 整体响应之间变长,一些业务需要多项目通信后给出结果。
- 吞吐量更大。吞吐量=请求数/秒
还需要解决的问题:
分布式架构中各个模块如何进行通信?
可以使用Http协议,也可以使用RPC协议通信,也可以使用其他的通信方式。我们本阶段使用的是RPC协议,因为它比HTTP更适合项目内部通信。
二、RPC简介
RPC在rfc1831中收录,RPC( Remote Procedure Call)远程过程调用协议。
可以在https://datatracker.ietf.org/doc/rfc1831/中查看RPC协议的具体内容
RPC协议规定允许互联网中一台主机程序调用另一台主机程序,而程序员无需对这个交互过程进行编程。在RPC协议中强调当A程序调用B程序中功能或方法时,A是不知道B中方法具体实现的。
RPC是上层协议,底层可以基于TCP协议,也可以基于HTTP协议。一般我们说RPC都是基于RPC的具体实现,如: Dubbo框架。从广义上讲只要是满足网络中进行通讯调用都统称为RPC甚至HTTP协议都可以说是RPC的具体实现,但是具体分析看来RPC协议要比HTTP协议更加高效,基于RPC的框架功能更多。
RPC协议是基于分布式架构而出现的,所以RPC在分布式项目中有着得天独厚的优势。
RPC和Http对比:
-
具体实现:
RPC可以基于TCP,也可以基于HTTP协议。
Http只能基于HTTP协议。
-
效率
RPC:自定义具体实现可以减少很多无用的报文内容,使得报文体积更小。
HTTP:在Http1.1中,报文有许多内容是无用的,HTTP2.0与RPC效率相差无几,但缺少一些小功能。
-
连接方式
RPC:长连接支持。
HTTP:每次连接都是三次握手。HTTP2.0支持长连接。
-
性能
RPC:可以基于很多序列化方式,如thrift
HTTP:主要是通过JSON,序列化和反序列化效率更低。
-
注册中心
RPC:一般RPC框架都带有注册中心。A想要B的东西,A往注册中心要,注册中心再往B要,最后注册中心再给A。B发生改变,注册中心去管,A不需要改动。
HTTP:都是直连。A和B直连,B发生改变,就需要改动A的代码,以适应B。
-
负载均衡(把一个功能交给多个服务器完成,一个完成压力太大)
RPC:一般RPC框架都带有负载均衡测量。
HTTP:一般需要借助一些第三方工具,如nginx
-
综合
RPC一般带有丰富的服务治理等功能,更适用企业内部接口调用。而HTTP更适用多平台之间的相互调用。
三、RMI实现RPC
RMI,远程方法调用。
无法实现跨语言。
RMI的执行流程:
服务器创建一个对象,RMI使用bind()和rebind()方法在RMI中注册该对象,客户端访问时,就使用lookup()方法在RNMI中找到该对象的IP地址和端口号,然后去服务器端直接调用即可。
RMI的API介绍:
-
Remote
java.rmi.Remote定义了此接口为远程调用接口。如果接口被外部调用,需要继承此接口。
public interface Remote{}
-
RemoteException
继承了 Remote接口的接口,如果方法是允许被远程调用的,需要抛出此异常
-
UnicastRemoteObject
此类实现了 Remote接口和Serializable接口,自定义接口实现类除了实现自定义接口还需要继承此类。
-
LocateRegistry
java.rmi.registry.LocateRegistry
可以通过 LocateRegistryRegistry在本机上创建,通过特定的端口就可以访问这个 Registry
-
Naming
Naming定义了发布内容可访问RMI名称,也是通过Naming获取到指定的远程方法。
RMI的实现
https://www.jianshu.com/p/de85fad05dcb
四、HttpClient实现RPC
HTtpClient可以实现使用Java代码完成标准HTTP请求及响应。
代码实现:
服务器端
新建项目HttpClientServer。
新建控制器:返回的是String类型
@Controller
public class DemoController {
@RequestMapping("/demo")
@ResponseBody
public String demo(String param){
System.out.println(param);
return param+"demo";
}
}
新建启动器
@SpringBootApplication
public class RpcApplication {
public static void main(String[] args) {
SpringApplication.run(RpcApplication.class, args);
}
}
客户端
新建项目HttpClientDemo
添加客户端Apache HttpClient依赖
<!--HttpClient所需的依赖-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.10</version>
</dependency>
新建类,编写主方法(客户端项目)
新建启动器和下面的Test方法
使用RPC-get方式访问
@SpringBootTest
class RfcApplicationTests {
@Test
public void rpcGetTest() throws URISyntaxException, IOException {
//1.创建一个Http工具(理解为:浏览器),发送请求,解析响应
CloseableHttpClient httpClient = HttpClients.createDefault();
//2.请求路径
URIBuilder uriBuilder = new URIBuilder("http://localhost:8080/demo");
//可以向服务器发送参数,服务器端接收参数用的是param,所以这里也是param----》键值对的形式
uriBuilder.addParameter("param", "ABC");//ABCdemo
//3.创建HTTP的get请求对象:uriBuilder使用build请求的路径交给get请求
HttpGet httpGet = new HttpGet(uriBuilder.build());
//4.创建响应对象
CloseableHttpResponse httpResponse = httpClient.execute(httpGet);
// 5.通过response获取结果,
// 由于响应体是字符串String,返回体是httpEntity类型
// 因此把httpEntity类型转换为字符串类型,并设置字符编码
String result = EntityUtils.toString(httpResponse.getEntity(), "UTF-8");
//输出结果
System.out.println(result);
// 6.释放资源
httpResponse.close();
httpClient.close();
}
}
使用post方式访问
@Test
public void rpcPostTest() throws IOException {
//1. 获取HttpClient对象,创建Http工具(理解成浏览器),发送请求,解析响应
CloseableHttpClient httpClient = HttpClients.createDefault();
//2.创建一个HttpPost请求对象
HttpPost httpPost = new HttpPost("http://localhost:8080/demo");
//经过下面三步,就把参数放进去了
//2.1 创建请求参数
ArrayList<NameValuePair> params = new ArrayList<>();
//NameValuePair是一个接口,不能直接创建对象
// 即创建NameValuePair实现类
params.add(new BasicNameValuePair("param", "ABCD"));
params.add(new BasicNameValuePair("param", "ABCDE"));//ABCD,ABCDEdemo
//2.2 创建HttpEntity接口的文本实现类对象,放入参数并设置编码
HttpEntity httpEntity = new UrlEncodedFormEntity(params,"UTF-8");
//2.3 放入到HttpPost对象中
httpPost.setEntity(httpEntity);
//3.创建响应对象
CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
//4. 由于响应体是字符串,因此把HttpEntity类型转换为字符串类型
String result = EntityUtils.toString(httpResponse.getEntity(), "UTF-8");
//5.输出结果
System.out.println(result);
//6.释放资源
httpResponse.close();
httpClient.close();
}
实际开发中,不可能返回的都是String类型,接下来是实体类,或者集合对象
新建实体类(服务器端项目)
public class User {
private Long id ;
private String name;
public User() {
}
public User(Long id, String name) {
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
新建控制器(返回的是实体类)(服务器端项目)
@RequestMapping("/demo2")
@ResponseBody
public User demo2(User user){
System.out.println(user.getId()+"_"+user.getName());
//http://localhost:8080/demo2?id=1&name=张三
//前台输出{"id":1,"name":"张三"}
return user;
}
模拟客户端,新建test类(使用post方式,因为post较难)(客户端项目)
/**
* 响应对象转换为json格式的字符串
*/
@Test
public void rpcObjectTest() throws IOException {
//1. 创建Http工具(理解成浏览器),发送请求,解析响应
CloseableHttpClient httpClient = HttpClients.createDefault();
//2.创建一个HttpPost请求对象
HttpPost httpPost = new HttpPost("http://localhost:8080/demo2");
//3.创建参数
ArrayList<NameValuePair> params = new ArrayList<>();
//NameValuePair是一个接口,不能直接创建对象,即创建NameValuePair实现类
params.add(new BasicNameValuePair("id", "1"));
params.add(new BasicNameValuePair("name", "ZT"));
//把参数放进HttpEntity中
HttpEntity httpEntity = new UrlEncodedFormEntity(params,"uTF-8");
httpPost.setEntity(httpEntity);
//4.创建响应对象,发送post请求
CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
String result = EntityUtils.toString(httpResponse.getEntity(), "UTF-8");
//{"id":1,"name":"ZT"}
System.out.println(result);
//6.释放资源
httpResponse.close();
httpClient.close();
}
但是我们日常使用,并不一定需要JSON对象,而是实体类对象或是集合对象(客户端项目)
返回对象类型
创建实体对象(客户端项目)
public class User {
private Long id ;
private String name;
public User() {
}
public User(Long id, String name) {
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
添加JSON和实体对象转换所需的依赖
<!--添加JSON和实体对象转换所需的依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.1</version>
</dependency>
1. jackson把JSON字符串转换为对象
//jackson把JSON字符串转换为对象
ObjectMapper objectMapper = new ObjectMapper();
User user = objectMapper.readValue(content, User.class);
2.使用jackson把实体类对象转换为json
//使用jackson把实体类对象转换为json
ObjectMapper objectMapper = new ObjectMapper();
User user = new User();
String userJson = objectMapper.writeValueAsString(user);
/**
* 对象互相转换为json格式的字符串
*/
@Test
public void rpcObjectPostTest() throws IOException {
//1. 创建Http工具(理解成浏览器),发送请求,解析响应
CloseableHttpClient httpClient = HttpClients.createDefault();
//2.创建一个HttpPost请求对象
HttpPost httpPost = new HttpPost("http://localhost:8080/demo2");
//3.创建参数
ArrayList<NameValuePair> params = new ArrayList<>();
//NameValuePair是一个接口,不能直接创建对象,即创建NameValuePair实现类
params.add(new BasicNameValuePair("id", "1"));
params.add(new BasicNameValuePair("name", "ZT"));
//把参数放进HttpEntity中
HttpEntity httpEntity = new UrlEncodedFormEntity(params,"uTF-8");
httpPost.setEntity(httpEntity);
//4.创建响应对象,发送post请求
CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
String content = EntityUtils.toString(httpResponse.getEntity());
//{"id":1,"name":"ZT"}
System.out.println(content);
//jackson把JSON字符串转换为对象
ObjectMapper objectMapper = new ObjectMapper();
User user = objectMapper.readValue(content, User.class);
System.out.println(user);//User{id=1, name='ZT'}
//使用jackson把实体类对象转换为json
String userJson = objectMapper.writeValueAsString(user);
System.out.println(userJson); //{"id":1,"name":"ZT"}
//6.释放资源
httpResponse.close();
httpClient.close();
}
返回集合类型
3.把json字符串转换为List集合
ObjectMapper objectMapper = new ObjectMapper();
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(ArrayList.class,User.class);
List<User> list = objectMapper.readValue(content, javaType);
服务器端
@RequestMapping("/demo3")
@ResponseBody
public ArrayList<User> demo3() {
ArrayList<User> users = new ArrayList<>();
users.add(new User(1L,"A"));
users.add(new User(2L,"哈哈O(∩_∩)O😄"));
//http://localhost:8080/demo3
//{"id":1,"name":"A"},{"id":2,"name":"B"}]
return users;
}
客户端
/**
* json转换为集合类型
*/
@Test
public void rpcListPostTest() throws IOException {
//1. 创建Http工具(理解成浏览器),发送请求,解析响应
CloseableHttpClient httpClient = HttpClients.createDefault();
//2.创建一个HttpPost请求对象
HttpPost httpPost = new HttpPost("http://localhost:8080/demo3");
//不需要再创建参数,响应集合对象即可
//3.创建响应对象,发送post请求
CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
String content = EntityUtils.toString(httpResponse.getEntity());
//[{"id":1,"name":"A"},{"id":2,"name":"哈哈O(∩_∩)O\uD83D\uDE04"}]
System.out.println(content);
//使用Jackson,将json转换为集合对象
ObjectMapper objectMapper = new ObjectMapper();
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(ArrayList.class,User.class);
List<User> list = objectMapper.readValue(content, javaType);
//[User{id=1, name='A'}, User{id=2, name='哈哈O(∩_∩)O😄'}]
System.out.println(list);
//6.释放资源
httpResponse.close();
httpClient.close();
}
传入的参数为流数据类型
在以上服务器端的控制器controller中传入的都是文本类型,如果需要传入的是集合类型,那就要使用流类型数据
/**
* 接收流数据参数
* 当传入的参数不仅仅是文本类型,而是一个集合类型
要想接收流数据对象,必须在流数据参数前面加上@RequestBody
*/
@RequestMapping("/demo5")
@ResponseBody
public String demo5(@RequestBody List<User> list) throws IOException {
System.out.println(list);
return list.toString();
}
Test
/**
* 接收流数据参数
*/
@Test
public void rpcInputStreamPostTest() throws IOException {
//1. 创建Http工具(理解成浏览器),发送请求,解析响应
CloseableHttpClient httpClient = HttpClients.createDefault();
//2.创建一个HttpPost请求对象
HttpPost httpPost = new HttpPost("http://localhost:8080/demo5");
//创建集合对象
List<User> listParam = new ArrayList<>();
listParam.add(new User(1L,"O(∩_∩)O"));
listParam.add(new User(1L,"😄"));
//将集合对象转换为JSON类型的字符串
ObjectMapper objectMapper = new ObjectMapper();
String jsonParam = objectMapper.writeValueAsString(listParam);
//ContentType.APPLICATION_JSON-->告诉它这是一个json类型的数据
HttpEntity httpEntity = new StringEntity(jsonParam, ContentType.APPLICATION_JSON);
httpPost.setEntity(httpEntity);
//3.创建响应对象,发送post请求
CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
String content = EntityUtils.toString(httpResponse.getEntity());
System.out.println(content);
//6.释放资源
httpResponse.close();
httpClient.close();
}
使用Ajax发送json数据(以前是使用HttoClient完成RPC,现在是使用Ajax完成RPC)
现在是使用Ajax完成RPC,唯一的区别是Ajax是异步的
在自己的服务器端(没有什么价值,一般使用Ajax完成跨域请求)
新建html文件
在resources下新建static文件夹,再新建js文件夹,将jQuery.js放进去。再在static下新建html文件夹,新建index.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script type="text/javascript" src="/js/jquery.js"></script>
<script type="text/javascript">
$(function () {
var json = '[{"id":123,"name":"O(∩_∩)O"},{"id":12345,"name":"😄"}]'
$.ajax({
url:'/demo6',
type:'post',
/*回调函数*/
success:function(data){
for (var i = 0; i < data.length; i++) {
alert(data[i].id+" "+data[i].name)
}
},
contentType:'application/json',/*请求体中内容有哪些*/
dataType:'json',/*响应内容类型*/
data:json
})
})
</script>
</head>
<body>
<!--http://localhost:8080/html/index.html-->
<button>按钮</button>
</body>
</html>
控制器
@RequestMapping("/demo6")
@ResponseBody
public List<User> demo6(@RequestBody List<User> list) throws IOException {
System.out.println(list);
//[User{id=123, name='O(∩_∩)O'}, User{id=12345, name='😄'}]
return list;
}
Ajax完成跨域请求
跨域:协议,IP,端口中只要有一个不同就是跨域请求
同源策略:浏览器默认只允许Ajax访问同源(协议,IP,端口都相同)内容
如何解决同源策略:(具体可以在网上搜索@CrossOrigin的使用)
@CrossOrigin还可以设置跨域白名单和黑名单的功能,在这里*是直接允许所有跨域请求
在控制器接口上添加@CrossOrigin,表示允许跨域。本质是在响应头中添加Access-Control-Allow-Origin:*
@RequestMapping("/demo6")
@ResponseBody
@CrossOrigin
public List<User> demo6(@RequestBody List<User> list) throws IOException {
System.out.println(list);
return list;
}
下面这个另一个服务器访问8080端口的demo6
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script type="text/javascript" src="/js/jquery.js"></script>
<script type="text/javascript">
$(function () {
var json = '[{"id":123,"name":"O(∩_∩)O"},{"id":12345,"name":"😄"}]'
$.ajax({
url:'http://localhost:8080/demo6',
type:'post',
/*回调函数*/
success:function(data){
for (var i = 0; i < data.length; i++) {
alert(data[i].id+" "+data[i].name)
}
},
contentType:'application/json',/*请求体中内容有哪些*/
dataType:'json',/*响应内容类型*/
data:json
})
})
</script>
</head>
<body>
<!--http://localhost:8080/html/index.html-->
<button>按钮</button>
</body>
</html>
五、Zookeeper安装
分布式管理软件,注册中心
需在Linux下完成以下操作:
在linux端完成JDK的安装和环境配置。
下载Zookeeper安装包:https://zookeeper.apache.org/releases.html
解压后
硬性要求1:
需要新建一个data目录(Zookeeper的硬性要求):
mkdir data
硬性要求2:
进入到zookeeper中的conf文件夹,发现有一个叫zoo_sample.cfg的文件,这时zookeeper的配置文件
但是Zookerper要求这个配置文件不能叫这个名字
执行以下操作:
-
cp zoo_sample.cfg zoo.cfg (备份文件,并改名为zoo.cfg,第二步要对这个zoo.cfg文件进行操作)
-
vi zoo.cfg
[root@localhost zookeeper-3.7.0]# mkdir data [root@localhost zookeeper-3.7.0]# cd conf/ [root@localhost conf]# cp zoo_sample.cfg zoo.cfg [root@localhost conf]# vi zoo.cfg
-
对里面dataDir=后面的进行更改,把它的位置改成我们刚才新建的data文件夹的位置
并记住那个端口号2181
-
修改后:wq退出,进入bin文件夹
-
启动Zookeeper:
输入:./zkServer.sh start (前面有有一个点),回车,
[root@localhost zookeeper-3.7.0]# cd bin [root@localhost bin]# ./zkServer.sh start ZooKeeper JMX enabled by default Using config: /home/ZhouT/zookeeper-3.7.0/bin/../conf/zoo.cfg Starting zookeeper ... STARTED
显示 Starting zookeeper … STARTED代表启动成功
-
查看状态
[root@localhost bin]# ./zkServer.sh status ZooKeeper JMX enabled by default Using config: /home/ZhouT/zookeeper-3.7.0/bin/../conf/zoo.cfg Client port found: 2181. Client address: localhost. Client SSL: false. Mode: standalone
Mode:standalone 说明这是一个单机版
端口号为:2181
说明Zookeeper安装启动成功了。
六、Zookeeper客户端常用命令
[root@localhost bin]# ls
README.txt zkCli.sh zkServer.cmd zkSnapshotComparer.cmd zkSnapShotToolkit.sh
zkCleanup.sh zkEnv.cmd zkServer-initialize.sh zkSnapshotComparer.sh zkTxnLogToolkit.cmd
zkCli.cmd zkEnv.sh zkServer.sh zkSnapShotToolkit.cmd zkTxnLogToolkit.sh
进入到./zkCli.sh命令行工具后,可以使用下面常用命令。
[root@localhost bin]# ./zkCli.sh
WATCHER::
WatchedEvent state:SyncConnected type:None path:null
[zk: localhost:2181(CONNECTED) 0]
当出现[zk: localhost:2181(CONNECTED) 0] 代表启动命令行成功
进入后,不能在里面使用linux命令了,已经进入Zookeeper了
-
ls
ls -s /path
-s 详细信息。
-R 当前目录和子目录下所有内容
例如:ls -R /显示根目录下所有内容
[zk: localhost:2181(CONNECTED) 0] ls / [zookeeper] [zk: localhost:2181(CONNECTED) 1] ls -R / / /zookeeper /zookeeper/config /zookeeper/quota [zk: localhost:2181(CONNECTED) 2] ls -s / [zookeeper] cZxid = 0x0 ctime = Thu Jan 01 08:00:00 CST 1970 mZxid = 0x0 mtime = Thu Jan 01 08:00:00 CST 1970 pZxid = 0x0 cversion = -1 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 0 numChildren = 1
-
create
create /path [data]
[data] 包含内容
创建指定路径信息
例如:create /demo 创建/demo
[zk: localhost:2181(CONNECTED) 3] create /demo Created /demo [zk: localhost:2181(CONNECTED) 5] ls / [demo, zookeeper]
-
get
get [-s] /path
[-s] 详细信息
查看指定路径下的内容
例如:get -s /demo
[zk: localhost:2181(CONNECTED) 6] get /demo null //为空 [zk: localhost:2181(CONNECTED) 7] get -s /demo null //为空 cZxid = 0x2 //创建时zxid(znode)每次改变时递增的事务id ctime = Fri Apr 23 13:30:16 CST 2021 //创建时间戳 mZxid = 0x2 //最近一次更新的zxid mtime = Fri Apr 23 13:30:16 CST 2021 //最近一次更新的时间戳 pZxid = 0x2 //子节点的zxid cversion = 0 //子节点更新次数 dataVersion = 0 //节点数据更新次数 aclVersion = 0 //节点ACL(授权信息)的更新信息 ephemeralOwner = 0x0 //如果该节点为ephemeral节点(临时,生命周期与session一样),ephemeralowner值表示与该节点绑定的session id.如果该节点不是ephemeral节点,ephemeralOwner值为0. dataLength = 0 //节点数据字节数 numChildren = 0 //子节点数量
-
set
set /path data
设置节点内容
[zk: localhost:2181(CONNECTED) 8] set /demo 哈哈O(∩_∩)O Hello World! [zk: localhost:2181(CONNECTED) 0] get /demo 哈哈O(∩_∩)O [zk: localhost:2181(CONNECTED) 1] get -s /demo 哈哈O(∩_∩)O cZxid = 0x2 ctime = Fri Apr 23 13:30:16 CST 2021 mZxid = 0x3 mtime = Fri Apr 23 13:43:46 CST 2021 pZxid = 0x2 cversion = 0 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 17 numChildren = 0
七、向Zookeeper中注册内容
在IDEA新建项目ZookerperClient
1.创建/demo
使用zookeeper的客户端命令工具创建/demo
[zk: localhost:2181(CONNECTED) 4] create /demo
Created /demo
[zk: localhost:2181(CONNECTED) 5] ls /
[demo, zookeeper]
2.添加依赖(根据自己的zookeeper版本选择对应的依赖)
<!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.7.0</version>
</dependency>
3.新建SendContent类
public class SendContent {
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
/**
* 创建Zookeeper对象
* 参数1:zookeeper ip+端口号
* 参数2:访问超时设置
* 参数3:通过观察者模式发出访问回复:当连接成功后,编写成功信息
*/
long l = System.currentTimeMillis();
ZooKeeper zooKeeper = new ZooKeeper("192.168.0.242:2181", 30000, new Watcher() {
@Override
public void process(WatchedEvent event) {
System.out.println("获取链接");
}
});
/**
* 发送内容向zookeeper服务器中
* 参数1:发送内容文件
* 参数2:发送的内容:要是"rmi:localhost:8080".getBytes(),即字符串的bytes模式
* 参数3:权限
* 参数4:内容的模式
*/
String content = zooKeeper.create(
"/demo/rmi-address",
"rmi:localhost:8080/demoService".getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT_SEQUENTIAL);
System.out.println("content = "+content);
zooKeeper.close();
System.out.println((System.currentTimeMillis() - l) / 1000 + "s");
}
}
[zk: localhost:2181(CONNECTED) 2] ls /
[demo, zookeeper]
[zk: localhost:2181(CONNECTED) 7] ls -R /
/
/demo
/zookeeper
/demo/rmi-address0000000003
/zookeeper/config
/zookeeper/quota
[zk: localhost:2181(CONNECTED) 8] get /demo/rmi-address0000000003
rmi:localhost:8080/demoService
[zk: localhost:2181(CONNECTED) 9]
八、向Zookeeper中发现内容
获取内容
新建一个类:ReceiveContent
public class ReceiveContent {
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
/**
* 创建Zookeeper对象
* 参数1:zookeeper ip+端口号
* 参数2:访问超时设置
* 参数3:通过观察者模式发出访问回复:当连接成功后,编写成功信息
*/
long l = System.currentTimeMillis();
ZooKeeper zooKeeper = new ZooKeeper("192.168.0.242:2181", 30000, new Watcher() {
@Override
public void process(WatchedEvent event) {
System.out.println("获取链接");
}
});
/**
* 从Zookeeper获取内容
*/
//1.获取节点
List<String> children = zooKeeper.getChildren("/demo", false);
for (String child : children) {
byte[] result = zooKeeper.getData("/demo/" + child, false, null);
System.out.println(new String(result));//rmi:localhost:8080/demoService
}
}
}
九、手写RPC框架
使用ZooKeeper作为注册中心,RMI为连接技术,手写RPC框架
-
创建项目ParentDemo
创建父项目ParentDemo。
包含3个聚合子项目。
<groupId>org.example</groupId> <artifactId>ParentDemo</artifactId> <packaging>pom</packaging> <version>1.0-SNAPSHOT</version> <modules> <module>pojo</module> <module>service</module> <module>provider</module> <module>consumer</module> </modules>
pojo:service中需要的实体类
<parent> <artifactId>ParentDemo</artifactId> <groupId>org.example</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>pojo</artifactId>
service:包含被serviceImpl和consumer依赖的接口
<parent> <artifactId>ParentDemo</artifactId> <groupId>org.example</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>service</artifactId> <dependencies> <dependency> <groupId>org.example</groupId> <artifactId>pojo</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>
provider:为serviceImpl提供的服务内容
<parent> <artifactId>ParentDemo</artifactId> <groupId>org.example</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>provider</artifactId> <dependencies> <dependency> <groupId>org.example</groupId> <artifactId>service</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>
consumer:消费者,调用服务内容
<parent> <artifactId>ParentDemo</artifactId> <groupId>org.example</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>consumer</artifactId> <dependencies> <dependency> <groupId>org.example</groupId> <artifactId>service</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>
-
在父项目添加依赖
org.apache.zookeeper zookeeper 3.7.0 -
service类:需要注意的是在Zookeeper中接口需要继承Remote类,方法需抛出RemoteException异常
package com.zt.service; import com.zhout.pojo.Person; /** * 继承Remote和抛出RemoteException是必要的,硬性要求 */ public interface MyPersonService extends Remote { List<Person> findAll() throws RemoteException; }
-
provider项目下的Impl中实现类需继承UnicastRemoteObject类,并且重写里面的方法,并将其protected修饰符改为public(这是必要的)
package com.zt.service.impl; public class MyPersonServiceImpl extends UnicastRemoteObject implements MyPersonService { public MyPersonServiceImpl() throws RemoteException { } @Override public List<Person> findAll() throws RemoteException { ArrayList<Person> people = new ArrayList<>(); people.add(new Person(1,"哈哈")); people.add(new Person(2,"O(∩_∩)O")); return people; } }
provide的service包下新建RMI启动类:
package com.zt.service; import com.zhout.service.impl.MyPersonServiceImpl; import org.apache.zookeeper.*; /** * @Date 2021/4/25 12:38 */ public class ProviderRun { public static void main(String[] args) throws IOException, AlreadyBoundException, KeeperException, InterruptedException { MyPersonService myPersonService = new MyPersonServiceImpl(); LocateRegistry.createRegistry(9090); String url = "rmi://localhost:9090/myPersonService"; Naming.bind(url, myPersonService); System.out.println("RMI启动成功!"); //创建Zookeeper并发布信息 ZooKeeper zooKeeper = new ZooKeeper("192.168.0.242:2181", 30000, new Watcher() { @Override public void process(WatchedEvent event) { System.out.println("获取链接"); } }); System.out.println(url); zooKeeper.create( "/rpc/provider2", url.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); System.out.println("注册成功"); } }
-
在consumer项目中以特殊方式添加SpringBoot依赖,因为已经有parent节点了,所以使用其他方式添加依赖
另外一种解决双继承的方法
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>ParentDemo</artifactId> <groupId>org.example</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>provider</artifactId> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.1.10.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.example</groupId> <artifactId>service</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> </project>
接口
package com.zt.service; import com.zhout.pojo.Person; import org.apache.zookeeper.KeeperException; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import java.io.IOException; import java.rmi.NotBoundException; import java.util.List; /** * @author ZhouT */ @Component public interface PersonService { public List<Person> show() throws IOException, KeeperException, InterruptedException, NotBoundException; }
实现类:
package com.zt.service.impl; import com.zhout.pojo.Person; import com.zhout.service.MyPersonService; import com.zhout.service.PersonService; import org.apache.zookeeper.*; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; /** * @Date 2021/4/25 13:36 */ @Service public class PersonServiceImpl implements PersonService { @Override public List<Person> show(){ //创建Zookeeper并发布信息 try { ZooKeeper zooKeeper = new ZooKeeper("192.168.0.242:2181", 30000, new Watcher() { @Override public void process(WatchedEvent event) { System.out.println("获取链接"); } }); byte[] result = zooKeeper.getData("/rpc/provider2", false, null); MyPersonService myPersonService = (MyPersonService) Naming.lookup(new String(result)); return myPersonService.findAll(); } catch (IOException | KeeperException | InterruptedException | NotBoundException e) { e.printStackTrace(); } return null; } }
控制器:
package com.zt.controller; import com.zhout.pojo.Person; import com.zhout.service.PersonService; import org.apache.zookeeper.KeeperException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.Mapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; /** * @Date 2021/4/25 13:49 */ @Controller public class PersonController { @Autowired private PersonService personService; @ResponseBody @RequestMapping("/show") public List<Person> show() throws InterruptedException, NotBoundException, KeeperException, IOException { return personService.show(); } }
SpringBoot启动器:
package com.zt; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @Date 2021/4/25 13:56 */ @SpringBootApplication public class ConsumerApplication { public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); } }
之后的Dubbo是一个具体的RPC框架
课程来源于马士兵教育的RPC课程