高级框架第一天RPC:远程过程调用

RPC:远程过程调用

主要内容

1.项目结构变化

2.RPC简介

3.RMI实现RPC

4.HttpClient实现RPC

5.Zookeeper安装

6.Zookeeper客户端常用命令

7.向Zookeeper中注册内容

8.从Zookeeper中发现内容

9.手写RPC框架

一.今天学什么?为什么讲?

Dubbo是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.3.1优点

部署简单,维护方便,开发成本低

1.3.2缺点

当项目规模大,用户访问频率高,并发量大,数据量大时,会大大降低程序执行效率,甚至出现服务器宕机等情况

1.4适用项目

传统管理项目,小型互联网项目

2.分布式架构

2.1架构图(简易版)

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

2.2软件代码结构

项目1:

    --com.bjsxt.xxx

        --controller

        --service

        --mapper

项目2:

    --com.bjsxt.mmm

        --controller

        --service

        --mapper

2.3优缺点

2.3.1优点

增大了系统可用性.减少单点故障,导致整个应用不可用

增加重用性.因为模块化,所以重用性更高.高内聚,低耦合

增加可扩展性.有新的模块增加新的项目即可

增加每个模块的负载能力.因为每个模块都是一个项目,所以每个模块的负载能力更强

2.3.2缺点

成本更高.因为技术多,难,所以开大成本,时间成本,维护成本都在变高

架构更加复杂

整体响应之间变长,一些业务需要多项目通信后给出结果

通吐量更大.吞吐量=请求数/秒

2.4适用项目

中,大型互联网项目.客户多,数据多,访问并发高,压力大,吞吐量高

2.5待解决问题

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

可以使用Http协议,也可以石红RPC协议通信,也可以使用其他的通信方式.我们本阶段使用的是RPC协议,因为它比HTTP更适合项目内部通信

三.RPC简介

1.RFC

RFC(Request For Comments)是由互联网工程任务组(IEIF)发布的文件集.文件集中每个文件都有自己唯一编号.例如:rfc1831.目前RFC文件由互联网协会(Internet Society,ISOC)赞助发行

    RPC就收集在RFC1831中.可以通过下面网址查看:

https://datatracker.ietf.org/doc/rfc1831/

2.RPC

RPC在rgc1831中收录,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:如果是HTTP1.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:一般都需要借助第三方工具.如:nginx

3.7综合结论

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

四.HttpClient实现RPC

1.HttpClient简介

在JDK中java.net包下提供了用户HTTP访问的基本功能,但是它缺少灵活性或许多应用所需要的功能

HttpClient起初是Apache Jackarta Common的子项目.用来提供高效的,最新的,功能丰富的支持HTTP协议的客户端编程工具包,并且他支持HTTP协议最新的版本.2007年成为顶级项目

通俗解释:HttpClient可以实现Java代码完成标准HTTP请求及响应

2.代码实现

2.1服务端

新建项目HttpClientServer

2.1.1新建控制器

com.bjsxt.controller.DemoController

@Controller

public class DemoController{

    @RequestMapping("/demo")

    @ResponseBody

    public String demo(String param){

        return "demo"+param;

    }

}

2.1.2新建启动器

com.bjsxt.HttpClientServerApplication

@SpringBootApplication

public class HttpClientServerApplication{

    public static void main(String[] args){

        SpringApplication.run(HttpClientServerApplication.class,args);

    }

}

2.2客户端

新建HttpClientDemo项目

2.2.1添加依赖

<dependencies>

    <dependency>

        <groupId>org.apache.httpcomponents</groupId>

        <artifactId>httpclient</artifactId>

        <version>4.5.10</version>

    </dependency>

</dependencies>

2.2.2新建类

新建com.bsjxt.HttpClientDemo,编写主方法

2.2.2.1使用POST方法访问

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;

            List<NameValuePair>params = new ArrayList<>();

            params.add(new BasicNameValuePair("param","123"));

            StringEntity entity = new UrlEncodedFormEntity(params,"utf-8");

            post.setEntity(entity);

            CloseableHttpResponse response = httpClient.execute(post);

            String result = EntityUtils.toString(response.getEntity());

            System.out.println(result);

            response.close();

            httpClient.close();

        }catcha(IOException e){

            e.printStackTrace();

        }

    }

}

2.2.2.2使用GET方式访问

public static void main(String[] args){

    try{

        CloseableHttpClient httpClient = HttpClients.createDefault();

        URIBuilder uriBuilder = new URIBuilder("http://localhost:8080/demo");

        uriBuilder.addParameter("param","get123");

        HttpGet get = new HttpGet(uriBuilder.build());

        CloseableHttpResponse response = httpClient.execute(get);

        String result = EntityUtils.toString(response.getEntity(),"utf-8");

        System.out.println(result);

        response.close();

        httpClient.close();

    }catch(URISyntaxException e){

        e.printStackTrace();

    }catch(IOException e){

        e.printlnStackTrace();

    }

}

3.HttpClient请求包含JSON

3.1java代理实现

public class HttpClientDemo{

    public static void main(Stirng[] 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().constructParametericType(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',

    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

});

7.跨域

跨域:协议,端口,ip中只要有一个不同就是跨域请求

同源策略:浏览器默认只允许ajax访问同源(协议,ip,端口都相同)内容

解决同源策略:

在控制器接口上天啊及@CrossOrigin.表示允许跨域.本质在响应头中添加

Access-Control-Allow-Origin:*

@RequestMapping("/demo5")

@ResponseBody

@CrossOrigin

public List<People>demo5(@RequestBody List<People>list){

    System.put.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.1Remote

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

3.2RemoteException

java.rmi.RemoteException

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

3.3UnicastRemoteObject

java.rmi.server.UnicastRemoteObject

此类实现了Remote接口和Serializable接口

自定义接口实现类除了实现自定义接口还需要继承此类

3.4LocateRegistry

java.rmi.registry.LocateRegistry

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

3.5Naming

java.rmi.Naming

Naming定义了发布内容可访问RMI名称.也是通过Naming获取到指定的远程方法

4.代码实现

4.1服务端创建

创建RmiServer项目

4.1.1编写接口

com.bjsxt.service.DemoService编写

public interface DemoService extends Remote{

    String demo(String demo)throws RemoteException;

}

4.1.2.编写实现类

com.bsjxt.service.impl.DemoServiceImpl编写

注意:

    构造方法是public的.默认生成protected

public class DemoServiceImpl extends UnicastRemoteObject implements DemoService{

    public DemoServiceImpl() throws RemoteException{}

    @Override

    public String demo(String demo)throws RemoteException{

        return demo+"123";

    }

}

4.1.3编写主方法

编写com.bjsxt.DemoService类,生成主方法

public class DemoServer{

    public static void main(String[] args){

        try{

            DemoService demoService = new DemoServiceImpl();

            LocateRegistry.createRegistry(8888);

            Naming.bind("rmi://localhsot:8888/demoService",demoService);

        }catch(RemoteException e){

            e.printStackTrace();

        }catch(AlreadyBoundException e){

            e.printStackTrace();

        }catch(malformedURLException){

            e.printStackTrace();

        }

    }

}

4.1.4运行项目

运行项目后,项目一直处于启动状态,表示可以远程访问此项目中的远程方法

4.2创建客户端代码

创建项目RmiClient

4.2.1复制服务端接口

把服务端com.bjsxt.service.DemoService粘贴到项目中

注意:复制代码仅为快速完成案例,学习技术,在商业开发中,DemoService接口应该使用独立的工程定义,并在服务端和客户端工程中通过依赖的方式引入

4.2.2创建主方法类

新建com.bjsxt.DemoClient

public class DemoClient{

    public static void main(String[] args){

        try{

            DemoService demoService = (DemoService)Naming.lookup("rmi://localhost:8888/demoService");

            String result = demoService.demo("demo34");

            System.out.println(result);

        }catch(NotBoundException e){

            e.printStackTrace();

        }catch(MalformedURLException e){

            e.printStackTrace();

        }catch(RemoteException e){

            e.printStackTrace();

        }

    }

}

六.Zookeeper安装

七.常用命令

八.向Zookeeper中注册内容

先进项目ZookeeperClient

1.创建/demo节点

使用zookeeper的客户端命令工具创建/demo

./zkCli.sh

create /demo

2.添加依赖

<dependencies>

    <dependency>

        <groupId>org.apache.zookeeper</groupId>

        <artifactId>zookeeper</artifactId>

        <version>3.5.5</version>

    </dependency>

</dependencies>

3.编写代码

创建类com.bjsxt.MyApp

ZooDefs.Ids.OPEN-ACL_UNSAFE表示权限

CreateMode.PERSISTENT_SEQUENTIAL永久存储,文件内容编写递增

public static void main(String[] args){

    try{

        Zookeeper zookeeper = new Zookeeper("192.168.232.132:2181",60000,new Watcher(){;

            @Override

            public void process(WatcherEvent watchedEvent){

                System.out.println("获取连接");

            }

    });

    String content = zookeeper.create("/demo/nn","content".getBytes(),ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT_SEQUENTIAL);

    System.out.println("content"+content);

    }catch(IOException e){

        e.printStackTrace();

    }catch(KeeperException e){

        e.printStackTrace();

    }catch(InterruptedException e){

        e.printStackTrace();

    }

}

4.查看上传数据

ls -R /    查看列表

get /demo/nn000000002     查看内容

九.从zookeeper中发现内容

在原有项目中新建一个类,类中编写主方法

public static void main(String[] args){

    try{

        Zookeeper zookeeper = new Zookeeper("192.168.232.132:2181",60000,new Watcher(){

            @Override

            public void process(WatchedEvent watchedEvent){

                System.out.println("获取连接");

            }

        });

        //获取列表

        List<String>list = zookeeper.getChildren("/demo",false);

        for(String child:list){

            byte[] result = zookeeper.getData("/demo/"+child,false,null);

            Ssytem.out.println(new String(result));

        }

    }catch(IOException e){

        e.printStackTrace();

    }catch(KeeperException e){

        e.printStackTrace();

    }catch(InterruptedException e){

        e.printStackTrace();

    }

}

十.手写RPC框架

使用Zookeeper作为注册中心,RMI作为连接技术,手写RPC框架

1.创建父项目ParentDemo

包含3个集合子项目

    service:包含被serviceImpl和consumer依赖的接口

    serviceImpl:provider提供的服务内容

    consumer:消费者,调用服务内容

2.在父项目中添加依赖

<parent>

    <groupId>org.springframework.boot<groupId>

    <artifactId>spring-boot-starter-parent</artifactId>

    <version>2.1.10.RELEASE</version>

</parent>

<dependencies>

    <dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-web</artifactId>

    </dependency>

    <dependency>

        <groupId>org.apache.zookeeper</groupId>

        <aritfactId>zookeeper</artifactId>

        <version>3.5.5</version>

    </dependency>

</dependencies>

3.创建service项目

项目结构如下:此项目中重点编写需要被两个项目依赖的接口

4.创建DemoService接口

创建com.bjsxt.DemoService,具体内容如下

public interface DemoService extends Remote{

    String demo(String param)throws RemoteException;

}

5.创建serviceImpl项目

此项目编写接口具体实现,RMI服务发布和把信息发送到Zookeeper中

项目结构如下:

在pom.xml中添加对service项目的依赖

<dependencies>

    <dependency>

        <artifactId>service</artifactId>

        <groupId>com.bjsxt</groupId>

        <version>1.0-SNAPSHOT</version>

    </dependency>

</dependencies>

6.创建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";

    }

}

7.创建RmiRun

创建com.bsjxt.RmiRun.实现RMI服务的发布和Zookeeper消息的发布

public class RmiRun{

    public static void main(String[] args){

        try{

            DemoService demoService = new DemoServiceImpl();

            LocateRegistry.createRegistry(8888);

            String url = "rmi://localhsot:8888/demoService";

            Naming.bind(url,demoService);

            Zookeeper zookeeper = new Zookeeper("192.168.232.132:2181",60000,new Watcher(){

                @Override

                public void process(WatcherEvent watchedEvent){

                    Ssytem.out.println("获取连接");

                }

            });

            String content = zookeeper.create("/demo/demoService",url.getBytes(),ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);

            System.out.println("服务发布成功...");

        }catch(AlreadyBoundException e){

            e.printStackTrace();

        }catch(IOException e){

            e.printStackTrace();

        }catch(KeeperException e){

            e.printStackTrace();

        }catch(InterruptedException e){

            e.printStackTrace();

        }

    }

}

8.创建Consumer项目

新建consumer项目,此项目需要从zookeeper中获取rmi信息,并调用rmi服务

在pom.xml中添加对service项目的依赖

<dependencies>

    <dependency>

        <artifactId>service</artifactId>

        <groupId>com.bjsxt</groupId>

        <version>1.0-SNAPSHOT</version>

    </dependency>

</dependencies>

9.创建接口和实现类

创建com.bjsxt.service.ConsumerService接口

创建com.bjsxt.servioce.impl.ConsumerServiceImpl实现类

public interface ConsumerService{

    String consumerService(String param);

}

@Service

public class ConsumerServiceImpl implements ConsumerService{

    @Override

    public String consumerService(String param){

        try{

            Zookeeper zookeeper = new Zookeeper("192.168.232.132:2181",60000,new Watcher(){

                @Override

                public void process(WatchedEvent watchedEvent){

                    System.out.println("获取连接");

                }

            });

            byte[] urlByte = zookeeper.getData("/demo/demoService",false,null);

            DemoService demoService = (DemoService)Naming.lookup(new String(urlByte));

            String result = demoService.demo(param);

            System.out.println(result);

            return result;

        }catch(IOException e){

            e.printStackTrace();

        }catch(InteruptedException e){

            e.printlnStackTrace();

        }catch(NotBoundException e){

            e.printStackTrace();

        }

        return null;

    }

}

10.创建控制器

创建com.bjsxt.controller.DemoController控制器

@Controller

public class DemoController{

    @Autowired

    private ConsumerService consumerService;

    @RequestMapping("/demo")

    @ResponseBody

    public String demo(String param){

        return consumerService.consumerService(param);

    }

}

11.创建启动器

创建com.bjsxt.ConsumerApplication

@SpringBootApplication

public class ConsumerApplication{

    public static void main(String[] args){

        SpringApplication.run(ConsumerApplication.class,args);

    }

}

12.测试

在浏览器输入:http://localhost:8080/demo?param=demo

观察结果是否是:demo123

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352