一篇带你了解RPC,ZooKeeper的小博文(小白写的)


纯小白


文章目录

一篇带你了解RPC,ZooKeeper的小博文(小白写的)

一、 项目结构变化

从之前的单体架构—>分布式架构

分布式架构会把一个项目按照特定的要求(多按照模块和功能)拆分成多个项目,每个项目分别部署到不同的服务器上。

优点:

  1. 增大了系统可用性。减少单点故障,导致整个应用不可用。
  2. 增加重用性。因为模块化,所以重用性更高。
  3. 增加可抗展性。有新的模块增加新的项目即可。
  4. 增加每个模块的负载能力。因为每个模块都是一个项目,所以每个模块的负载能力更强。

缺点:

  1. 成本更高
  2. 架构更加复杂。
  3. 整体响应之间变长,一些业务需要多项目通信后给出结果。
  4. 吞吐量更大。吞吐量=请求数/秒

还需要解决的问题:

分布式架构中各个模块如何进行通信?

可以使用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对比:

  1. 具体实现:

    RPC可以基于TCP,也可以基于HTTP协议。

    Http只能基于HTTP协议。

  2. 效率

    RPC:自定义具体实现可以减少很多无用的报文内容,使得报文体积更小。

    HTTP:在Http1.1中,报文有许多内容是无用的,HTTP2.0与RPC效率相差无几,但缺少一些小功能。

  3. 连接方式

    RPC:长连接支持。

    HTTP:每次连接都是三次握手。HTTP2.0支持长连接。

  4. 性能

    RPC:可以基于很多序列化方式,如thrift

    HTTP:主要是通过JSON,序列化和反序列化效率更低。

  5. 注册中心

    RPC:一般RPC框架都带有注册中心。A想要B的东西,A往注册中心要,注册中心再往B要,最后注册中心再给A。B发生改变,注册中心去管,A不需要改动。

    HTTP:都是直连。A和B直连,B发生改变,就需要改动A的代码,以适应B。

  6. 负载均衡(把一个功能交给多个服务器完成,一个完成压力太大)

    RPC:一般RPC框架都带有负载均衡测量。

    HTTP:一般需要借助一些第三方工具,如nginx

  7. 综合

    RPC一般带有丰富的服务治理等功能,更适用企业内部接口调用。而HTTP更适用多平台之间的相互调用。

三、RMI实现RPC

RMI,远程方法调用。

无法实现跨语言。

RMI的执行流程:

服务器创建一个对象,RMI使用bind()和rebind()方法在RMI中注册该对象,客户端访问时,就使用lookup()方法在RNMI中找到该对象的IP地址和端口号,然后去服务器端直接调用即可。

RMI的API介绍:

  1. Remote

    java.rmi.Remote定义了此接口为远程调用接口。如果接口被外部调用,需要继承此接口。

    public interface Remote{}

  2. RemoteException

    继承了 Remote接口的接口,如果方法是允许被远程调用的,需要抛出此异常

  3. UnicastRemoteObject

    此类实现了 Remote接口和Serializable接口,自定义接口实现类除了实现自定义接口还需要继承此类。

  4. LocateRegistry

    java.rmi.registry.LocateRegistry

    可以通过 LocateRegistryRegistry在本机上创建,通过特定的端口就可以访问这个 Registry

  5. 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要求这个配置文件不能叫这个名字

执行以下操作:

  1. cp zoo_sample.cfg zoo.cfg (备份文件,并改名为zoo.cfg,第二步要对这个zoo.cfg文件进行操作)

  2. 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 
    
  3. 对里面dataDir=后面的进行更改,把它的位置改成我们刚才新建的data文件夹的位置

在这里插入图片描述

并记住那个端口号2181

  1. 修改后:wq退出,进入bin文件夹

  2. 启动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代表启动成功

  3. 查看状态

    [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了

  1. 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
    
    
  2. 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]
    
  3. 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		//子节点数量
    
    
  4. 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
    
    
    1. delete

      delete /path

      删除节点

      [zk: localhost:2181(CONNECTED) 2] delete /demo
      [zk: localhost:2181(CONNECTED) 3] ls /
      [zookeeper]
      
      

七、向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框架

  1. 创建项目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>
    
  2. 在父项目添加依赖

    org.apache.zookeeper zookeeper 3.7.0
  3. 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;
    }
    
  4. 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("注册成功");
        }
    }
    
  5. 在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课程

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值