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