RPC

RFC

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

RPC

RPC在rfc 1831中收录 ,RPC(Remote Procedure Call) 远程过程调用协议

RPC协议规定允许互联网中一台主机程序调用另一台主机程序,而程序员无需对这个交互过程进行编程。在RPC协议中强调当A程序调用B程序中功能或方法时,A是不知道B中方法具体实现的。

RPC是上层协议,底层可以基于TCP协议,也可以基于HTTP协议。一般我们说RPC都是基于RPC的具体实现,如:Dubbo框架。从广义上讲只要是满足网络中进行通讯调用都统称为RPC,甚至HTTP协议都可以说是RPC的具体实现,但是具体分析看来RPC协议要比HTTP协议更加高效,基于RPC的框架功能更多。

RPC协议是基于分布式架构而出现的,所以RPC在分布式项目中有着得天独厚的优势。

RPC和HTTP对比
  • 具体实现

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

HTTP:基于HTTP协议

  • 效率

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

HTTP:如果是HTTP 1.1 报文中很多内容都是无用的。如果是HTTP2.0以后和RPC相差不大,比RPC少的可能就是一些服务治理等功能。

  • 连接方式

RPC:长连接支持。

HTTP:每次连接都是3次握手。

  • 性能

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

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

  • 注册中心

RPC :一般RPC框架都带有注册中心。

HTTP:都是直连。

  • 负载均衡

RPC:绝大多数RPC框架都带有负载均衡测量。

HTTP:一般都需要借助第三方工具。如:nginx

  • 综合结论

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

HttpClient实现RPC
HttpClient简介

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

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

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

代码实现
  • 添加依赖
<dependencies>
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.10</version>
    </dependency>
</dependencies>
  • get方法
public static void main(String[] args) {
    try {
    	//创建http工具(理解成:浏览器) 发起请求,解析响应
        CloseableHttpClient httpClient = HttpClients.createDefault();
        //请求路径
        URIBuilder uriBuilder = new URIBuilder("http://localhost:8080/demo");
        uriBuilder.addParameter("param", "get123");
        //创建HttpGet请求对象
        HttpGet get = new HttpGet(uriBuilder.build());
        //创建响应对象
        CloseableHttpResponse response = httpClient.execute(get);
        //由于响应体是字符串,因此把HttpEntity类型转换为字符串类型,并设置字符编码
        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.printStackTrace();
    }
}
  • post方法
public class HttpClientDemo {
    public static void main(String[] args) {
        try {
        	//创建http工具(理解成:浏览器) 发起请求,解析响应
            CloseableHttpClient httpClient = HttpClients.createDefault();
            //创建HttpPOST请求对象
            HttpPost post = new HttpPost("http://localhost:8080/demo");
            //所有请求参数
            List<NameValuePair> params = new ArrayList<>();
            params.add(new BasicNameValuePair("param","123"));
            //创建HttpEntity接口的文本实现类的对象,放入参数并设置编码
            HttpEntity httpEntity = new UrlEncodedFormEntity(params,"utf-8");
            //放入到HttpPost对象中
            post.setEntity(httpEntity);            
            //创建响应对象
            CloseableHttpResponse response = httpClient.execute(post);
            //由于响应体是字符串,因此把HttpEntity类型转换为字符串类型
            String result = EntityUtils.toString(response.getEntity());
            //输出结果
            System.out.println(result);
            //释放资源
            response.close();
            httpClient.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • jackson用法(补充)
//把对象转换为json字符串
    ObjectMapper objectMapper = new ObjectMapper();
    People peo = new People();
    objectMapper.writeValueAsString(peo);

//把json字符串转换为对象
    ObjectMapper objectMapper = new ObjectMapper();
    People peo = objectMapper.readValue(content, People.class);

//把json字符串转换为List集合
    ObjectMapper objectMapper = new ObjectMapper();
    JavaType javaType = objectMapper.getTypeFactory().constructParametricType(List.class, People.class);
    List<People> list = objectMapper.readValue(content, javaType);
  • HttpClient请求包含JSON
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;
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();
        }
    }
}
RMI实现RPC
RMI简 介

RMI(Remote Method Invocation) 远程方法调用。

RMI是从JDK1.2推出的功能,它可以实现在一个Java应用中可以像调用本地方法一样调用另一个服务器中Java应用(JVM)中的内容。

RMI 是Java语言的远程调用,无法实现跨语言。

执行过程

Registry(注册表)是放置所有服务器对象的命名空间。 每次服务端创建一个对象时,它都会使用bind()或rebind()方法注册该对象。 这些是使用称为绑定名称的唯一名称注册的。

要调用远程对象,客户端需要该对象的引用。即通过服务端绑定的名称从注册表中获取对象(lookup()方法)。

API介绍
  • Remote

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

public interface Remote{}
  • RemoteException

java.rmi.RemoteException

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

  • UnicastRemoteObject

java.rmi.server.UnicastRemoteObject

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

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

  • LocateRegistry

java.rmi.registry.LocateRegistry

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

  • Naming

java.rmi.Naming

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

代码实现
  • 建立RmiServer,编写接口

service.DemoService 编写:接口要继承Remote接口,方法要throws RemoteException

public interface DemoService extends Remote {
    String demo(String demo) throws RemoteException;
}
  • 编写实现类

service.impl.DemoServiceImpl 编写:类要继承UnicastRemoteObject类,方法要throws RemoteException

注意:构造方法是public的。默认生成protected

public class DemoServiceImpl extends UnicastRemoteObject implements DemoService {
    public DemoServiceImpl() throws RemoteException {
    }
    @Override
    public String demo(String demo) throws RemoteException {
        return demo+"123";
    }
}
  • 编写主方法

编写DemoServer类,生成主方法,开始对外提供

public class DemoServer {


    public static void main(String[] args) {
        try {
            //创建接口实例
            DemoService demoService = new DemoServiceImpl();
            //创建注册表
            LocateRegistry.createRegistry(8989);
            //绑定服务
            Naming.bind("rmi://localhost:8989/demoService",demoService);

            System.out.println("服务器启动成功!");
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (AlreadyBoundException e) {
            e.printStackTrace();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
    }

}
  • 运行项目

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

  • 创建项目RmiClient,建立服务端接口,开始远程调用

建立service.DemoService接口

public interface DemoService extends Remote {
    String demo(String demo) throws RemoteException;
}
  • 创建主方法类

新建DemoClient,远程调用

public class ClientDemo {
    public static void main(String[] args) {
        try {
            //将远程调用的远程接口赋给本地接口
            DemoService demoService = (DemoService)Naming.lookup("rmi://localhost:8989/demoService");
            String result = demoService.demo("aaaa");

            System.out.println(result);
        } catch (NotBoundException e) {
            e.printStackTrace();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

Zookeeper

简介

zookeeper分布式管理软件。常用它做注册中心(依赖zookeeper的发布/订阅功能)、配置文件中心、分布式锁配置、集群管理等。

zookeeper一共就有两个版本。主要使用的是java语言写的。

安装
  • 官网下载然后上传压缩文件

上传到 /usr/local/tmp中

  • 解压
# tar zxf apache-zookeeper-3.5.5-bin.tar.gz
# cp -r apache-zookeeper-3.5.5-bin ../zookeeper
  • 新建data目录

进入到zookeeper中

# cd /usr/local/zookeeper
# mkdir data
  • 修改配置文件

进入conf中

# cd conf
# cp zoo_sample.cfg zoo.cfg
# vim zoo.cfg

修改dataDir为data文件夹路径

dataDir=/usr/local/zookeeper/data
  • 启动zookeeper

进入bin文件夹

# cd /usr/local/zookeeper/bin
# ./zkServer.sh start

通过status查看启动状态。稍微有个等待时间

# ./zkServer.sh status

  • win启动时cmd,然后zkserver
常用命令
  • ls

ls [-s][-R] /path

-s 详细信息,替代老版的ls2

-R 当前目录和子目录中内容都罗列出来

例如:ls -R / 显示根目录下所有内容

  • create

create /path [data]

[data] 包含内容

创建指定路径信息

例如:create /demo 创建/demo

  • get

get [-s] /path

[-s] 详细信息

查看指定路径下内容。

例如: get -s /demo

RPC协议报文格式 rpc用什么协议_rpc

null:存放的数据

cZxid:创建时zxid(znode每次改变时递增的事务id)

ctime:创建时间戳

mZxid:最近一次更新的zxid

mtime:最近一次更新的时间戳

pZxid:子节点的zxid

cversion:子节点更新次数

dataversion:节点数据更新次数

aclVersion:节点ACL(授权信息)的更新次数

ephemeralOwner:如果该节点为ephemeral节点(临时,生命周期与session一样), ephemeralOwner值表示与该节点绑定的session id. 如果该节点不是ephemeral节点, ephemeralOwner值为0.

dataLength:节点数据字节数

numChildren:子节点数量

  • set

set /path data

设置节点内容

  • delete

delete /path

删除节点

注册内容
  • 创建/demo

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

./zkCli.sh
create /demos
  • 添加依赖
<dependencies>
    <dependency>
        <groupId>org.apache.zookeeper</groupId>
        <artifactId>zookeeper</artifactId>
        <version>3.5.5</version>
    </dependency>
</dependencies>
  • 编写代码

ZooDefs.Ids.OPEN_ACL_UNSAFE 表示权限。

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

public static void main(String [] args){
    try {
    //三个参数:1 ip+端口,2 访问超时时间 3 连接成功后,编写成功信息
        ZooKeeper zookeeper = new ZooKeeper("127.0.0.1:2181", 10000, new Watcher() {
            @Override
            public void process(WatchedEvent 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();
    }
}
  • 查看上传数据

ls -R / :查看列表

get /demo/nn0000000002 :查看内容

发现内容

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

public static void main(String[] args) {
    try {
        ZooKeeper zookeeper = new ZooKeeper("127.0.0.1:2181", 10000, 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);
            System.out.println(new String(result));
        }
    } catch (IOException e) {
        e.printStackTrace();
    } catch (KeeperException e) {
        e.printStackTrace();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

Dubbo

简介

Apache Dubbo 是一个高可用的,基于Java的开源RPC框架。

Dubbo框架不仅仅是具备RPC访问功能,还包含服务治理功能。

架构图

RPC协议报文格式 rpc用什么协议_dubbo_02

架构说明
  • 虚线

虚线表示异步,实线表示同步。异步不阻塞线程性能高,同步阻塞线程必须等待响应结果才能继续执行,相对性能低。

  • Provider

提供者。编写持久层和事务代码。

  • Container

容器(Spring容器),Dubbo完全基于Spring实现的。

  • Registry

注册中心。放置所有Provider对外提供的信息。包含Provider的IP,访问端口,访问遵守的协议,对外提供的接口,接口中有哪些方法等相关信息。

  • Consumer

消费者(RPC调用者,SOA调用服务的项目)开发中也是一个项目,编写service和controller(还可以报页面等)。调用XXXXServiceImpl中的方法。

  • Monitor

监控中心。监控Provider的压力情况等。每隔2分钟Consumer和Provider会把调用次数发送给Monitor,由Monitor进行统计。

执行流程
  • start:启动Spring容器时会把Provider启动。
  • register:把Provider相关信息注册到Registry里
  • subscribe:Consumer从Registry中订阅Provider的信息
  • notify:通知给Consumer
  • invoke:Consumer根据Registry通知的信息进行调用Provider中方法。
  • count:Consumer和Provider把调用次数信息异步发送给Monitor进行统计。
支持协议
  • Dubbo协议(官方推荐协议)

优点:

采用NIO复用单一长连接,并使用线程池并发处理请求,减少握手和加大并发效率,性能较好(推荐使用)

缺点:

大文件上传时,可能出现问题(不使用Dubbo文件上传)

  • RMI(Remote Method Invocation)协议

优点:

JDK自带的能力。

缺点:

偶尔连接失败.

  • Hessian协议

优点:

可与原生Hessian互操作,基于HTTP协议

缺点:

需hessian.jar支持,http短连接的开销大

Dubbo支持的注册中心
  • Zookeeper(官方推荐)

1) 优点:

支持分布式.很多周边产品.

2) 缺点:

受限于Zookeeper软件的稳定性.Zookeeper专门分布式辅助软件,稳定较优

  • Multicast

1) 优点:

去中心化,不需要单独安装软件.

2) 缺点:

2.2.1 Provider和Consumer和Registry不能跨机房(路由)

  • Redis

1) 优点:

支持集群,性能高

2) 缺点:

要求服务器时间同步.否则可能出现集群失败问题.

  • Simple

1) 优点:

标准RPC服务.没有兼容问题

2) 缺点:

不支持集群.

负载均衡

集群:一个内容,部署多次,形成的整体称为集群。集群中每个个体应该部署到不同的服务器上。

伪集群:集群中内容部署到同一台服务器上,通过不同端口区分不同个体。

负载均衡是在集群前提下,当访问整个集群时,集群中每个节点被访问次数或频率的规则。

Dubbo 内置了四个负载均衡策略。默认为Random

  • 内置策略
  • 1.Random

随机。随机访问集群中节点。访问概率和权重有关。

  • 2.RoundRobin

轮询。访问频率和权重有关。

权重(weight):占有比例。集群中每个项目部署的服务器的性能可能是不同,性能好的服务器权重应该高一些。

  • 3.LeastActive

活跃数相同的随机,不同的活跃数高的放前面。

  • 4.ConsistentHash

一致性Hash。相同参数请求总是发到一个提供者。

  • Provider集群

新建四个启动类。

每次启动启动类修改配置文件dubbo.protocal.port

  • 设置负载均衡
  • 1.@Reference

调用的服务采用的负载均衡

@Reference(loadbalance = "roundrobin")
private DemoDubboService demoDubboService;
  • 2.@Service

当前服务采用的负载均衡算法

@Service(loadbalance = "random")
public class DemoDubboServiceImpl implements DemoDubboService {

设置权重

@Service(weight = 4)
  • 3.配置文件

全局设置所有provider和consumer的负载均衡效果。

dubbo:
  application:
    name: dubbo-provider
  registry:
    address: zookeeper://192.168.32.128:2181
  protocol:
    port: 20884
  provider:
    loadbalance: random
  consumer:
    loadbalance: random

Dubbo实现Provider

  • 提供者的service层的@Service注解必须是dubbo的注解

新建父项目Parent1。最终结构如下:

RPC协议报文格式 rpc用什么协议_学习_03

编写pom.xml继承SpringBoot父项目。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.10.RELEASE</version>
</parent>
新建api项目
  • 创建接口

创建gfk.dubbo.service.DemoDubboService接口

public interface DemoDubboService {
    String demo();
}
新建provider项目
  • 编写pom.xml

注意:不需要依赖web,如果依赖spring-boot-start-web还需要考虑端口问题,且依赖了api项目

<dependencies>

    //注意这里依赖了api项目
    <dependency>
        <artifactId>api</artifactId>
        <groupId>gfk</groupId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <version>2.1.10.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-spring-boot-starter</artifactId>
        <version>2.7.3</version>
    </dependency>
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-recipes</artifactId>
        <version>4.2.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-framework</artifactId>
        <version>4.2.0</version>
    </dependency>
</dependencies>
  • 编写配置文件

新建application.yml

dubbo:
  application:
    name: dubbo-provider
  registry:
    address: zookeeper://127.0.0.1:2181
  • 新建实现类

新建gfk.dubbo.service.impl.DemoDubboServiceImpl

注意:@Service注解是apache的注解。

import gfk.dubbo.service.DemoDubboService;
import org.apache.dubbo.config.annotation.Service;

@Service
public class DemoDubboServiceImpl implements DemoDubboService {
    @Override
    public String demo() {
        System.out.println("demo方法");
        return "123";
    }
}
  • 新建启动类

新建ProviderApplication。

必须要有@EnableDubbo注解,否则Dubbo不生效。

@SpringBootApplication
@EnableDubbo
public class ProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProviderApplication.class,args);
    }
}

Dubbo实现Consumer

创建consumer项目
  • 编写pom.xml
<dependencies>


    //注意这里依赖了api项目
    <dependency>
        <artifactId>api</artifactId>
        <groupId>gfk</groupId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.1.10.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-spring-boot-starter</artifactId>
        <version>2.7.3</version>
    </dependency>
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-recipes</artifactId>
        <version>4.2.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-framework</artifactId>
        <version>4.2.0</version>
    </dependency>
</dependencies>
  • 新建配置文件

新建application.yml

dubbo:
  application:
    name: dubbo-consumer
  registry:
    address: zookeeper://192.168.32.128:2181
  • 新建service及实现类

新建gfk.service.DemoService

新建gfk.service.impl.DemoServiceImpl

调用服务使用@Reference注解,不要倒错包了,有两个

public interface DemoService {
    String consumerDemo();
}
import gfk.dubbo.service.DemoDubboService;
import gfk.service.DemoService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Service;

@Service
public class DemoServiceImpl implements DemoService {

    //调用服务使用@Reference注解,不要倒错包了,有两个。
    @Reference
    private DemoDubboService demoDubboService;

    @Override
    public String consumerDemo() {
        return demoDubboService.demo();
    }
}
  • 新建控制器

新建控制器gfk.controller.DemoController

@Controller
public class DemoController {
    @Autowired
    private DemoService demoService;

    @RequestMapping("/demo")
    @ResponseBody
    public String demo(){
        return demoService.consumerDemo();
    }
}
  • 新建启动器

新建gfk.ConsumerApplication

@SpringBootApplication
@EnableDubbo
public class ConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class,args);
    }
}

Dubbo总结

结合例子:

  • 首先有三个项目:provider,consumer,api
  • provider,consumer都将api项目作为jar在maven中依赖
  • provider,consumer的启动类上必须要有@EnableDubbo注解
  • 第一步:api中建立DemoService接口
  • 第二步:provider中建立api的DemoService接口的实现类
  • 第三步:provider的service层的@Service注解必须是dubbo的注解
  • 第四步:consumer建立正常三层架构,在逻辑层需要dubbo远程调用时用dubbo的@Reference注解进行自动注入即可
  • 注:将provider,consumer理解为俩个不同的服务,后者远程调用前者,调用时注意注解问题