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
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访问功能,还包含服务治理功能。
架构图
架构说明
- 虚线
虚线表示异步,实线表示同步。异步不阻塞线程性能高,同步阻塞线程必须等待响应结果才能继续执行,相对性能低。
- 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。最终结构如下:
编写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理解为俩个不同的服务,后者远程调用前者,调用时注意注解问题