【菜鸟教程】Dubbo基础入门上——初识RPC框架

基础概念
Apache Dubbo是一款高性能、轻量级的Java RPC框架,前身是阿里的开源Java RPC框架,可以和spring无缝集成。

Dubbo提供了三大核心能力:面向接口的远程方法调用、智能容错和负载均衡、服务自动注册和发现。

本章先来了解一下RPC框架

RPC框架

RPC全称为remote procedure call,即远程过程调用。例如两台服务器A和B,A服务器部署一个应用,B服务器上部署一个应用,A服务器上的应用想调用B服务器上应用提供的方法,由于两个应用不在一个内存空间,不能直接调用,所以需要通过网络来表达调用的语义和传达调用的数据。需要注意的是RPC并不是具体的技术,而是指整个网络远程调用过程。

RPC调用过程:
客户端调用、序列化、通过网络发送消息给服务端
服务端反序列化、调用本地服务、处理服务、返回处理结果、将结果序列化、
通过网络返回消息给客户端、客户端反序列化、得到调用结果

常见的RPC框架:
RMI、Hessian、gRPC(google)、bRPC、Dubbo、Thrift(facebook)

RMI(remote method invocation)

Java原生支持的远程调用,应用JRMP作为通信协议,可以认为是纯Java版本的分布式远程调用解决方案。
核心原理:服务器在注册中心注册服务、客户端从注册中心获取服务

使用RMI的步骤

创建远程接口,并且继承并实现java.rmi.Remote接口

先创建一个maven父工程,再创建一个服务端子工程,创建接口继承Remote

public interface UserService extends Remote {

	//记得要抛出异常 不然会报错
    String sayHello(String name) throws RemoteException;

}

实现远程接口,并且继承UnicastRemoteObject

public class UserServiceImpl extends UnicastRemoteObject implements UserService {

    public UserServiceImpl() throws RemoteException {
    }

    public String sayHello(String name) {
        return name+"调用了服务端的服务";
    }
}

创建服务端程序:使用createRegistry()方法注册远程对象

public class Server {

    public static void main(String[] args) throws Exception {
        //对外暴露服务
        //1.启动rmi注册服务,指定端口号
        LocateRegistry.createRegistry(8888);
        //2.创建要被访问的远程对象的实例
        UserService userService = new UserServiceImpl();
        //3.把远程对象实例注册到rmi注册中心
        Naming.bind("rmi://localhost:8888/UserService",userService);

        System.out.println("服务端开始启动");
    }
}

创建客户端 获取注册信息、远程调用服务

创建maven子工程作为客户端,客户端需要依赖服务接口

public class Client {

    public static void main(String[] args) throws Exception{

        UserService userService = (UserService) Naming.lookup("rmi://localhost:8888/UserService");
        String s = userService.sayHello("客户端");
        System.out.println(s);
    }
}

注意还要把服务端的UserService接口在客户端的工程中再创建一份,接口的包结构一定要和服务端是一模一样的。

工程的整体结构:注意UserService的接口包路径结构是一样的
【菜鸟教程】Dubbo基础入门上——初识RPC框架_第1张图片

测试 先启动服务端,再启动客户端,成功输出

【菜鸟教程】Dubbo基础入门上——初识RPC框架_第2张图片
【菜鸟教程】Dubbo基础入门上——初识RPC框架_第3张图片


Hessian

Hessian使用C/S方式,基于HTTP协议传输,使用二进制进行序列化

先构建一个服务器子工程,将项目打包默认设为war模式,并创建一个webapp目录,并生成WEB.xml配置文件
【菜鸟教程】Dubbo基础入门上——初识RPC框架_第4张图片

导入相关依赖,设置版本

	<dependencies>
        <dependency>
            <groupId>com.cauchogroupId>
            <artifactId>hessianartifactId>
            <version>4.0.7version>
        dependency>
    dependencies>

	<properties>
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        <maven.compiler.source>1.8maven.compiler.source>
        <maven.compiler.target>1.8maven.compiler.target>
    properties>

创建服务端接口和实现类

public interface UserService  {

    String sayHello(String name);
}


public class UserServiceImpl implements UserService {

    public String sayHello(String name) {
        return name+"调用了hessian的服务端服务";
    }
}

在web.xml中进行相关配置

	<servlet>
        <servlet-name>HessianServeltservlet-name>
        <servlet-class>com.caucho.hessian.server.HessianServletservlet-class>
        <init-param>
            <param-name>service-classparam-name>
            <param-value>com.sjh.service.impl.UserServiceImplparam-value>
        init-param>
    servlet>
    
    <servlet-mapping>
        <servlet-name>HessianServeltservlet-name>
        <url-pattern>/hessianServleturl-pattern>
    servlet-mapping>

在服务端子项目的pom.xml导入tomcat7插件

	<build>
        <plugins>
            <plugin>
                <groupId>org.apache.tomcat.mavengroupId>
                <artifactId>tomcat7-maven-pluginartifactId>
                <version>2.2version>
                <configuration>
                    <port>8888port>
                    <path>/path>
                    <uriEncoding>UTF-8uriEncoding>
                configuration>
            plugin>
        plugins>
    build>

创建客户端子项目,添加依赖

	<dependencies>
        <dependency>
            <groupId>com.cauchogroupId>
            <artifactId>hessianartifactId>
            <version>4.0.7version>
        dependency>
    dependencies>
    
	<properties>
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        <maven.compiler.source>1.8maven.compiler.source>
        <maven.compiler.target>1.8maven.compiler.target>
    properties>

跟RMI的步骤类似,在客户端创建一个目录结构相同的UserService接口

【菜鸟教程】Dubbo基础入门上——初识RPC框架_第5张图片

创建客户端测试类

public class Client {

    public static void main(String[] args) throws MalformedURLException {
        //定义远端连接地址 端口号和服务端定义的一样 路径是服务端web.xml文件servlet设置的路径
        String url="http://localhost:8888/hessianServlet";
        HessianProxyFactory hessianProxyFactory = new HessianProxyFactory();
        //第一个参数为服务接口字节码 第二个参数为url地址
        UserService userService= (UserService) hessianProxyFactory.create(UserService.class,url);
        String s = userService.sayHello("hessian客户端");
        System.out.println(s);
    }
}

启动服务器

【菜鸟教程】Dubbo基础入门上——初识RPC框架_第6张图片

启动客户端

【菜鸟教程】Dubbo基础入门上——初识RPC框架_第7张图片

通过浏览器访问

【菜鸟教程】Dubbo基础入门上——初识RPC框架_第8张图片


手写RPC框架

核心原理

  • 注册中心
    ①保存服务名与服务地址映射关系②服务地址变动主动通知服务消费方
  • 服务提供方
    ①提供服务接口②实现服务接口③注册服务(远程注册,本地注册)④暴露服务(tomcat)
  • 服务消费方
    ①启动时从注册中心获取服务地址并缓存②根据负载均衡策略选出一个服务地址进行服务调用

环境搭建

创建一个maven父工程,然后创建服务提供方和服务消费方两个子工程
【菜鸟教程】Dubbo基础入门上——初识RPC框架_第9张图片


创建服务接口和实现类

public interface HelloService {
    
    String sayHello(String name);
    
}


public class HelloServiceImpl implements HelloService {

    @Override
    public String sayHello(String name) {
        return name+"调用了RPC的服务!";
    }
}

创建URL类封装主机地址和端口

public class URL {
    
    private String hostName;
    private Integer port;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        URL url = (URL) o;
        return Objects.equals(hostName, url.hostName) &&
                Objects.equals(port, url.port);
    }

    @Override
    public int hashCode() {
        return Objects.hash(hostName, port);
    }

    public URL(String hostName, Integer port) {
        this.hostName = hostName;
        this.port = port;
    }

    public String getHostName() {
        return hostName;
    }

    public void setHostName(String hostName) {
        this.hostName = hostName;
    }

    public Integer getPort() {
        return port;
    }

    public void setPort(Integer port) {
        this.port = port;
    }
}

编写注册中心

public class NativeRegistry {
    
    //注册中心 第一个参数是接口全类名 Class是实现类
    private static Map<String, Map<URL,Class>> registerCenter=new HashMap<>();
    
    //注册服务
    public static void register(String interfaceName,URL url,Class implClass){
        Map<URL,Class> map=new HashMap<>();
        map.put(url,implClass);
        registerCenter.put(interfaceName,map);
    }
    
    //获取服务信息
    public static Class getRegister(String interfaceName,URL url){
        Map<URL, Class> map = registerCenter.get(interfaceName);
        Class clazz = map.get(url);
        return clazz;
    }
}

注册服务

public class ServiceProvider {

    public static void main(String[] args) {
        //注册服务
        URL url = new URL("localhost", 8080);
        NativeRegistry.register(HelloService.class.getName(),url, HelloServiceImpl.class);
        
        //启动tomcat 暴露服务
        HttpServer httpServer = new HttpServer();
        httpServer.start(url.getHostName(),url.getPort());
    }
}

暴露服务

服务之前调用的通信协议采用HTTP,所以在服务中启动tomcat暴露服务

添加内置tomcat的依赖

	<properties>
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        <maven.compiler.source>1.8maven.compiler.source>
        <maven.compiler.target>1.8maven.compiler.target>
    properties>
    
	<dependencies>
        <dependency>
            <groupId>org.apache.tomcat.embedgroupId>
            <artifactId>tomcat-embed-coreartifactId>
            <version>9.0.12version>
        dependency>
		<dependency>
            <groupId>commons-iogroupId>
            <artifactId>commons-ioartifactId>
            <version>2.6version>
        dependency>
    dependencies>

创建HttpServer用来模拟tomcat

public class HttpServer {

    public void start(String hostName,int port){
        //实例一个tomcat
        Tomcat tomcat=new Tomcat();
        //构建server
        Server server=tomcat.getServer();
        //获取service
        Service service=server.findService("Tomcat");
        //构建连接
        Connector connector=new Connector();
        connector.setPort(port);
        connector.setURIEncoding("UTF-8");
        //构建Engine
        StandardEngine engine = new StandardEngine();
        engine.setDefaultHost(hostName);
        //构建host
        StandardHost host = new StandardHost();
        host.setName(hostName);
        //构建context
        String contextPath="";
        StandardContext context = new StandardContext();
        context.setPath(contextPath);
        context.addLifecycleListener(new Tomcat.FixContextListener());//生命周期监听器
        //按照server.xml 一层层把子结点添加到父结点
        host.addChild(context);
        engine.addChild(host);
        service.setContainer(engine);
        service.addConnector(connector);
        //设置路径与映射
        tomcat.addServlet(contextPath,"dispatcher",new DispatcherServlet());
        context.addServletMappingDecoded("/*","dispatcher");

        try{
            tomcat.start();
            tomcat.getServer().await();
        }catch (LifecycleException e){
            e.printStackTrace();
        }
    }
}

DispatcherServlet类

public class DispatcherServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //处理远程调用请求
        new HttpServerHander().handle(req,resp);
    }
}

HttpServerHander类

public class HttpServerHander {

    public void handle(HttpServletRequest req, HttpServletResponse resp){
        try{
            //通过请求流获取请求服务调用的参数
            ServletInputStream inputStream = req.getInputStream();
            ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
            Invocation invocation= (Invocation) objectInputStream.readObject();
            //从注册中心获取服务列表
            Class implClass= NativeRegistry.getRegister(invocation.getInterfaceName(), new URL("localhost",8080));
            //调用服务 反射
            Method method=implClass.getMethod(invocation.getMethodName(),invocation.getParamTypes());
            String result= (String) method.invoke(implClass.newInstance(),invocation.getParams());
            //返回结构
            IOUtils.write(result,resp.getOutputStream(),"UTF-8");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

Invocation类封装服务消费方调用服务时需要的参数

//封装调用参数
public class Invocation implements Serializable {

    private String interfaceName;//接口名
    private String methodName;//方法名
    private Object[] params;//参数值列表
    private Class[] paramTypes;//参数类型列表

    public String getInterfaceName() {
        return interfaceName;
    }

    public Invocation(String interfaceName, String methodName, Object[] params, Class[] paramTypes) {
        this.interfaceName = interfaceName;
        this.methodName = methodName;
        this.params = params;
        this.paramTypes = paramTypes;
    }

    public void setInterfaceName(String interfaceName) {
        this.interfaceName = interfaceName;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public Object[] getParams() {
        return params;
    }

    public void setParams(Object[] params) {
        this.params = params;
    }

    public Class[] getParamTypes() {
        return paramTypes;
    }

    public void setParamTypes(Class[] paramTypes) {
        this.paramTypes = paramTypes;
    }
}

启动服务端测试

【菜鸟教程】Dubbo基础入门上——初识RPC框架_第10张图片


服务消费方编写

public class HttpClient {

    public String post(String hostName, int port, Invocation invocation) throws Exception {
        //进行连接
        URL url = new URL("http", hostName, port, "/client");
        HttpURLConnection connection= (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("POST");
        connection.setDoOutput(true);
        //发送调用信息
        OutputStream os = connection.getOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(os);
        oos.writeObject(invocation);
        oos.flush();
        oos.close();
        //将输入流转换成字符串获取远程调用接口
        InputStream is = connection.getInputStream();
        return IOUtils.toString(is, StandardCharsets.UTF_8);
    }
}

创建测试类,进行测试(记得先打开服务端tomcat)

public class ConsumerTest {

    public static void main(String[] args) throws Exception{
        //调用需要的参数
        Invocation invocation = new Invocation(HelloService.class.getName(), "sayHello", new Object[]{"myRPC的客户端"}, new Class[]{String.class});
        //调用服务
        HttpClient httpClient = new HttpClient();
        String result = httpClient.post("localhost", 8080, invocation);
        System.out.println(result);
    }
}

结果

【菜鸟教程】Dubbo基础入门上——初识RPC框架_第11张图片

【菜鸟教程】Dubbo基础入门上——初识RPC框架_第12张图片


整体结构

【菜鸟教程】Dubbo基础入门上——初识RPC框架_第13张图片


你可能感兴趣的:(JAVA,计算机网络)