首页 > 科技 > 腾讯一面:RMI怎么实现的?爬虫是单进程还是多进程?

腾讯一面:RMI怎么实现的?爬虫是单进程还是多进程?

1.你知道哪些RPC框架

2.RMI怎么实现的?

3.Netty里的线程池用的是什么?

4.线程池的参数怎么设置?如果请求书超过了线程池的线程数会发生什么?

5.restfulAPI和RPC的区别

6.你为什么用SpringBoot这个版本,相比于Spring框架有什么优势?

7.你对SpringCloud有了解嘛?

8.爬虫速度怎么样?单进程还是多进程?

9.访问频繁被禁用了怎么办?

10.什么是协程?协程和线程池的区别?

11.Mybatis和hibernate的区别

12.一个sql查询慢,百万级别,单表,你觉得性能问题可能出在哪?

13.如果两表查慢,问题出在哪?

14.Inner join 和left join 性能不同,inner join反而慢是为什么?

15.redis怎么实现消息队列?怎么实现分布式锁?

16.说几个head首部。Content-type类型有哪些。get和post区别?



1. 你知道哪些RPC框架

(1)RMI(远程方法调用):

JAVA自带的远程方法调用工具,不过有一定的局限性,毕竟是JAVA语言最开始时的设计,后来很多框架的原理都基于RMI

(2)Hessian(基于HTTP的远程方法调用)

基于HTTP协议传输,在性能方面还不够完美,负载均衡和失效转移依赖于应用的负载均衡器,Hessian的使用则与RMI类似,区别在于淡化了Registry的角色,通过显示的地址调用,利用HessianProxyFactory根据配置的地址create一个代理对象,另外还要引入Hessian的Jar包。

(3)Dubbo(淘宝开源的基于TCP的RPC框架)

基于Netty的高性能RPC框架,是阿里巴巴开源的,总体原理如下:

2. RMI怎么实现的?

RMI全称是Remote Method Invocation-远程方法调用,Java RMI在JDK1.1中实现的,其威力就体现在它强大的开发分布式网络应用的能力上,是纯Java的网络分布式应用系统的核心解决方案之一。其实它可以被看作是RPC的Java版本。但是传统RPC并不能很好地应用于分布式对象系统。而Java RMI 则支持存储于不同地址空间的程序级对象之间彼此进行通信,实现远程对象之间的无缝远程调用。

RMI目前使用Java远程消息交换协议JRMP(Java Remote Messaging Protocol)进行通信。由于JRMP是专为Java对象制定的,Java RMI具有Java的"Write Once,Run Anywhere"的优点,是分布式应用系统的百分之百纯Java解决方案。用Java RMI开发的应用系统可以部署在任何支持JRE(Java Run Environment Java,运行环境)的平台上。但由于JRMP是专为Java对象制定的,因此,RMI对于用非Java语言开发的应用系统的支持不足。不能与用非Java语言书写的对象进行通信。

RMI可利用标准Java本机方法接口JNI与现有的和原有的系统相连接。RMI还可利用标准JDBC包与现有的关系数据库连接。RMI/JNI和RMI/JDBC相结合,可帮助您利用RMI与目前使用非Java语言的现有服务器进行通信,而且在您需要时可扩展Java在这些服务器上的使用。RMI可帮助您在扩展使用时充分利用Java的强大功能。

一、RMI(远程方法调用)的组成

一个正常工作的RMI系统由下面几个部分组成:

·远程服务的接口定义

·远程服务接口的具体实现

·桩(Stub)和框架(Skeleton)文件

·一个运行远程服务的服务器

·一个RMI命名服务,它允许客户端去发现这个远程服务

·类文件的提供者(一个HTTP或者FTP服务器)

·一个需要这个远程服务的客户端程序

二、RMI(远程方法调用)原理示意图



方法调用从客户对象经占位程序(Stub)、远程引用层(Remote Reference Layer)和传输层(Transport Layer)向下,传递给主机,然后再次经传 输层,向上穿过远程调用层和骨干网(Skeleton),到达服务器对象。 占位程序扮演着远程服务器对象的代理的角色,使该对象可被客户激活。 远程引用层处理语义、管理单一或多重对象的通信,决定调用是应发往一个服务器还是多个。传输层管理实际的连接,并且追踪可以接受方法调用的远程对象。服务器端的骨干网完成对服务器对象实际的方法调用,并获取返回值。返回值向下经远程引用层、服务器端的传输层传递回客户端,再向上经传输层和远程调用层返回。最后,占位程序获得返回值。

要完成以上步骤需要有以下几个步骤:

1、 生成一个远程接口

2、 实现远程对象(服务器端程序)

3、 生成占位程序和骨干网(服务器端程序)

4、 编写服务器程序

5、 编写客户程序

6、 注册远程对象

7、 启动远程对象

三、RMI(远程方法调用)的优点

从最基本的角度看,RMI是Java的远程过程调用(RPC)机制。与传统的RPC系统相比,RMI具有若干优点,因为它是Java面向对象方法的一部分。传统的RPC系统采用中性语言,所以是最普通的系统--它们不能提供所有可能的目标平台所具有的功能。

RMI以Java为核心,可与采用本机方法与现有系统相连接。这就是说,RMI可采用自然、直接和功能全面的方式为您提供分布式计算技术,而这种技术可帮助您以不断递增和无缝的方式为整个系统添加Java功能。

RMI的主要优点如下:

面向对象:RMI可将完整的对象作为参数和返回值进行传递,而不仅仅是预定义的数据类型。也就是说,您可以将类似Java哈希表这样的复杂类型作为一个参数进行传递。而在目前的RPC系统中,您只能依靠客户机将此类对象分解成基本数据类型,然后传递这些数据类型,最后在服务器端重新创建哈希表。RMI则不需额外的客户程序代码(将对象分解成基本数据类型),直接跨网传递对象。

可移动属性:RMI可将属性(类实现程序)从客户机移动到服务器,或者从服务器移到客户机。这样就能具备最大的灵活性,因为政策改变时只需要您编写一个新的Java类,并将其在服务器主机上安装一次即可。

设计方式:对象传递功能使您可以在分布式计算中充分利用面向对象技术的强大功能,如二层和三层结构系统。如果您能够传递属性,那么您就可以在您的解决方案中使用面向对象的设计方式。所有面向对象的设计方式无不依靠不同的属性来发挥功能,如果不能传递完整的对象--包括实现和类型--就会失去设计方式上所提供的优点。

安  全:RMI使用Java内置的安全机制保证下载执行程序时用户系统的安全。RMI使用专门为保护系统免遭恶意小应用程序侵害而设计的安全管理程序,可保护您的系统和网络免遭潜在的恶意下载程序的破坏。在情况严重时,服务器可拒绝下载任何执行程序。

便于编写和使用:RMI使得Java远程服务程序和访问这些服务程序的Java客户程序的编写工作变得轻松、简单。远程接口实际上就是Java接口。服务程序大约用三行指令宣布本身是服务程序,其它方面则与任何其它Java对象类似。这种简单方法便于快速编写完整的分布式对象系统的服务程序,并快速地制做软件的原型和早期版本,以便于进行测试和评估。因为RMI程序编写简单,所以维护也简单。

可连接现有/原有的系统:RMI可通过Java的本机方法接口JNI与现有系统进行进行交互。利用RMI和JNI,您就能用Java语言编写客户端程序,还能使用现有的服务器端程序。在使用RMI/JNI与现有服务器连接时,您可以有选择地用Java重新编写服务程序的任何部分,并使新的程序充分发挥Java的功能。类似地,RMI可利用JDBC、在不修改使用数据库的现有非Java源代码的前提下与现有关系数据库进行交互。

编写一次,到处运行:RMI是Java“编写一次,到处运行 ”方法的一部分。任何基于RMI的系统均可100%地移植到任何Java虚拟机上,RMI/JDBC系统也不例外。如果使用RMI/JNI与现有系统进行交互工作,则采用JNI编写的代码可与任何Java虚拟机进行编译、运行。

分布式垃圾收集:RMI采用其分布式垃圾收集功能收集不再被网络中任何客户程序所引用的远程服务对象。与Java 虚拟机内部的垃圾收集类似,分布式垃圾收集功能允许用户根据自己的需要定义服务器对象,并且明确这些对象在不再被客户机引用时会被删除。

并行计算:RMI采用多线程处理方法,可使您的服务器利用这些Java线程更好地并行处理客户端的请求。Java分布式计算解决方案:RMI从JDK 1.1开始就是Java平台的核心部分,因此,它存在于任何一台1.1 Java虚拟机中。所有RMI系统均采用相同的公开协议,所以,所有Java 系统均可直接相互对话,而不必事先对协议进行转换。

四、RMI与CORBA的关系

RMI 和 CORBA 常被视为相互竞争的技术,因为两者都提供对远程分布式对象的透明访问。但这两种技术实际上是相互补充的,一者的长处正好可以弥补另一者的短处。RMI 和 CORBA 的结合产生了 RMI-IIOP,RMI-IIOP 是企业服务器端 Java 开发的基础。1997 年,IBM 和 Sun Microsystems启动了一项旨在促进 Java 作为企业开发技术的发展的合作计划。两家公司特别着力于如何将 Java 用作服务器端语言,生成可以结合进现有体系结构的企业级代码。所需要的就是一种远程传输技术,它兼有 Java 的 RMI(Remote Method Invocation,远程方法调用)较少的资源占用量和更成熟的 CORBA(Common Object Request Broker Architecture,公共对象请求代理体系结构)技术的健壮性。出于这一需要,RMI-IIOP问世了,它帮助将 Java 语言推向了目前服务器端企业开发的主流语言的领先地位。


Java RMI 简单示例

1. 定义一个远程接口

/* IHello.java */

package mytest;

/*

* 在Java中,只要一个类extends了java.rmi.Remote接口,即可成为存在于服务器端的远程对象,

* 供客户端访问并提供一定的服务。JavaDoc描述:Remote 接口用于标识其方法可以从非本地虚拟机上

* 调用的接口。任何远程对象都必须直接或间接实现此接口。只有在“远程接口”

* (扩展 java.rmi.Remote 的接口)中指定的这些方法才可被远程调用。

*/

import java.rmi.Remote;

public interface IHello extends Remote {

    /* extends了Remote接口的类或者其他接口中的方法若是声明抛出了RemoteException异常,

    * 则表明该方法可被客户端远程访问调用。

    */

public String sayHello(String name) throws java.rmi.RemoteException;

}

2. 远程接口实现类

/* HelloImpl.java */

package mytest;

import java.rmi.RemoteException;

import java.rmi.server.UnicastRemoteObject;

/*

* 远程对象必须实现java.rmi.server.UniCastRemoteObject类,这样才能保证客户端访问获得远程对象时,

* 该远程对象将会把自身的一个拷贝以Socket的形式传输给客户端,此时客户端所获得的这个拷贝称为“存根”,

* 而服务器端本身已存在的远程对象则称之为“骨架”。其实此时的存根是客户端的一个代理,用于与服务器端的通信,

* 而骨架也可认为是服务器端的一个代理,用于接收客户端的请求之后调用远程方法来响应客户端的请求。

*/

/* java.rmi.server.UnicastRemoteObject构造函数中将生成stub和skeleton */

public class HelloImpl extends UnicastRemoteObject implements IHello {

// 这个实现必须有一个显式的构造函数,并且要抛出一个RemoteException异常

protected HelloImpl() throws RemoteException {

super();

}

private static final long serialVersionUID = 4077329331699640331L;

public String sayHello(String name) throws RemoteException {

return "Hello " + name + " ^_^ ";

}

}

3. 服务端

/* HelloServer.java */

package mytest;

import java.rmi.registry.LocateRegistry;

import java.rmi.registry.Registry;

/* 注册远程对象,向客户端提供远程对象服务

* 远程对象是在远程服务上创建的,你无法确切地知道远程服务器上的对象的名称

* 但是,将远程对象注册到RMI Service之后,客户端就可以通过RMI Service请求

* 到该远程服务对象的stub了,利用stub代理就可以访问远程服务对象了

*/

public class HelloServer {

public static void main(String[] args) {

try {

IHello hello = new HelloImpl(); /* 生成stub和skeleton,并返回stub代理引用 */

/* 本地创建并启动RMI Service,被创建的Registry服务将在指定的端口上侦听到来的请求

* 实际上,RMI Service本身也是一个RMI应用,我们也可以从远端获取Registry:

* public interface Registry extends Remote;

* public static Registry getRegistry(String host, int port) throws RemoteException;

*/

LocateRegistry.createRegistry(1099);

/* 将stub代理绑定到Registry服务的URL上 */

java.rmi.Naming.rebind("rmi://localhost:1099/hello", hello);

System.out.print("Ready");

} catch (Exception e) {

e.printStackTrace();

}

}

}

4. 客户端


/* Hello_RMI_Client.java */

package mytest;

import java.rmi.Naming;

/* 客户端向服务端请求远程对象服务 */

public class Hello_RMI_Client {

public static void main(String[] args) {

try {

/* 从RMI Registry中请求stub

* 如果RMI Service就在本地机器上,URL就是:rmi://localhost:1099/hello

* 否则,URL就是:rmi://RMIService_IP:1099/hello

*/

IHello hello = (IHello) Naming.lookup("rmi://localhost:1099/hello");

/* 通过stub调用远程接口实现 */

System.out.println(hello.sayHello("zhangxianxin"));

} catch (Exception e) {

e.printStackTrace();

}

}

}

RMI 应用各个类的交互时序图


RMI应用需要用到的类




1. RemoteObject抽象类实现了Remote接口和序列化Serializable接口,它和它的子类提供RMI服务器函数。

为什么远端调用方法抛出的RemoteException异常的父类竟然是IOException呢?

这是因为,远端调用方法其实际上是通过网络IO进行的。

3. 当客户端通过RMI注册表找到一个远程接口的时候,所得到的其实是远程接口的一个动态代理对象。当客户端调用其中的方法的时候,方法的参数对象会在序列化之后,传输到服务器端。服务器端接收到之后,进行反序列化得到参数对象。并使用这些参数对象,在服务器端调用实际的方法。调用的返回值Java对象经过序列化之后,再发送回客户端。客户端再经过反序列化之后得到Java对象,返回给调用者。

这中间的序列化过程对于使用者来说是透明的,由动态代理对象自动完成。除了序列化之外,RMI还使用了动态类加载技术。当需要进行反序列化的时候,如果该对象的类定义在当前JVM中没有找到,RMI会尝试从远端下载所需的类文件定义。可以在RMI程序启动的时候,通过JVM参数java.rmi.server.codebase来指定动态下载Java类文件的URL。

实现一个stub和skeleton程序

明白了RMI应用的原理后,可以自行实现一个stub和skeleton程序,进一步探索RMI的代理访问原理。

RMI的本质就是实现在不同JVM之间的调用,它的实现方法就是在两个JVM中各开一个Stub和Skeleton,二者通过socket通信来实现参数和返回值的传递。

1. 定义一个Person的接口,其中有两个business method, getAge() 和getName()

Person代码:

public interface Person {

public int getAge() throws Throwable;

public String getName() throws Throwable;

}

2. Stub的实现

Person_Stub代码:

import java.io.ObjectOutputStream;

import java.io.ObjectInputStream;

import java.net.Socket;

public class Person_Stub implements Person {

private Socket socket;

public Person_Stub() throws Throwable {

// connect to skeleton

socket = new Socket("computer_name", 9000);

}

public int getAge() throws Throwable {

// pass method name to skeleton

ObjectOutputStream outStream =

new ObjectOutputStream(socket.getOutputStream());

outStream.writeObject("age");

outStream.flush();

ObjectInputStream inStream =

new ObjectInputStream(socket.getInputStream());

return inStream.readInt();

}

public String getName() throws Throwable {

// pass method name to skeleton

ObjectOutputStream outStream =

new ObjectOutputStream(socket.getOutputStream());

outStream.writeObject("name");

outStream.flush();

ObjectInputStream inStream =

new ObjectInputStream(socket.getInputStream());

return (String)inStream.readObject();

}

}

Person_Stub是建立socket连接,并向Skeleton发请求,然后通过Skeleton调用PersonServer的方法,最后接收返回的结果。

3. 骨架(Skeleton)的实现

Person_Skeleton代码:

import java.io.ObjectOutputStream;

import java.io.ObjectInputStream;

import java.net.Socket;

import java.net.ServerSocket;

public class Person_Skeleton extends Thread {

private PersonServer myServer;

public Person_Skeleton(PersonServer server) {

// get reference of object server

this.myServer = server;

}

public void run() {

try {

// new socket at port 9000

ServerSocket serverSocket = new ServerSocket(9000);

// accept stub's request

Socket socket = serverSocket.accept();

while (socket != null) {

// get stub's request

ObjectInputStream inStream =

new ObjectInputStream(socket.getInputStream());

String method = (String)inStream.readObject();

// check method name

if (method.equals("age")) {

// execute object server's business method

int age = myServer.getAge();

ObjectOutputStream outStream =

new ObjectOutputStream(socket.getOutputStream());

// return result to stub

outStream.writeInt(age);

outStream.flush();

}

if(method.equals("name")) {

// execute object server's business method

String name = myServer.getName();

ObjectOutputStream outStream =

new ObjectOutputStream(socket.getOutputStream());

// return result to stub

outStream.writeObject(name);

outStream.flush();

}

}

} catch(Throwable t) {

t.printStackTrace();

System.exit(0);

}

}

}

Skeleton类 extends from Thread,它长驻在后台运行,随时接收client发过来的request。并根据发送过来的key去调用相应的business method。

4. Person的实现PersonServer类

PersonServer代码:

public class PersonServer implements Person {

private int age;

private String name;

public PersonServer(String name, int age) {

this.age = age;

this.name = name;

}

public int getAge() {

return age;

}

public String getName() {

return name;

}

public static void main(String args []) {

// new object server

PersonServer person = new PersonServer("Richard", 34);

Person_Skeleton skel = new Person_Skeleton(person);

skel.start();

}

}

PersonServer中对Person的接口进行了真正的实现,创建PersonServer实例对象,并启动Person_Skeleton服务。

4. Client的实现

PersonClient 代码:

public class PersonClient {

public static void main(String [] args) {

try {

Person person = new Person_Stub();

int age = person.getAge();

String name = person.getName();

System.out.println(name + " is " + age + " years old");

} catch(Throwable t) {

t.printStackTrace();

}

}

}

Client(PersonClient)的本质是,它要知道Person接口的定义,并实例一个Person_Stub,通过Stub来调用business method,至于Stub怎么去和Server沟通,Client就不用管了。

注意它的写法:

Person person = new Person_Stub();而不是Person_Stub person = new Person_Stub();为什么?因为要面向接口编程嘛,呵呵。

实际上,对于PersonClient来说,它只关注Person接口,而且它的本质就是远程调用Person接口,而Person_Stub只是负责代理PersonClient去与Server交互。


5. 线程池的参数怎么设置?如果请求书超过了线程池的线程数会发生什么?

可以通过线程池+等待队列的形式来处理,比如说线程池是500,高峰期可能出现线程池已经满了但是还有用户请求不断过来,这时就要把这些请求放到等待队列里面,等待队列设定一个最大等待时间,超过最大等待时间的请求自动拒绝,当线程池有空位的时候自动通知等待队列把用户请求转发到线程池处理。

当然条件允许的话还是尽量加大线程池,实在没办法才使用等待队列的方式

8. 爬虫速度怎么样?单进程还是多进程?

IO密集型代码(文件处理、网络爬虫等),多线程能够有效提升效率(单线程下有IO操作会进行IO等待,造成不必要的时间浪费,而开启多线程能在线程A等待时,自动切换到线程B,可以不浪费CPU的资源,从而能提升程序执行效率)。在实际的数据采集过程中,既考虑网速和响应的问题,也需要考虑自身机器的硬件情况,来设置多进程或多线程

16. 说几个head首部。Content-type类型有哪些。get和post区别?

x-forwarded-for

x-remote-IP

x-originating-IP

x-remote-ip

x-remote-addr

x-client-ip

x-client-IP

X-Real-ip


常规撕撕代码:



1.定义头尾两个指针;

2.left指针从左向右,直到不符合条件停下来,注意循环完毕后,left指针只能到数组倒数第二个数位置,right亦是如此;

3.判断指针是否落在同一个元素上作为返回值;

4.完结。

public boolean validMountainArray(int[] A) {

if (A.length < 3)

return false;

int left = 0, right = A.length - 1;

while (left < A.length - 2 && A[left] < A[left + 1])//注意循环完毕后,left指针只能到数组倒数第二个数位置

left++;

while (right > 1 && A[right] < A[right - 1])//和left指针条件一样

right--;

return left == right;

}


需要往期整理好的资料,私信小编!

本文来自投稿,不代表本人立场,如若转载,请注明出处:http://www.souzhinan.com/kj/271773.html