文章目录
什么是Restful架构
Rest是Representational State Transfer的缩写词
a) 每一个uri代表一种资源
b) 客户端和服务端之间传递这种资源的表现层
c) 客户端通过四个Http动词,对服务端资源进行操作,实现"表现层状态转换"
GET 用来获取资源,
POST 用来新建资源(也可以用于更新资源),
PUT 用来更新资源,
DELETE 用来删除资源。
通过RestTemplate可以方便的使用Rest资源,内部使用了模板方法的设计模式
- getForEntity() 发送一个HTTP GET请求,返回的ResponseEntity包含了响应体所映射成的对象
- getForObject() 发送一个HTTP GET请求,返回的响应体映射为一个对象
- postForEntity() 发送一个HTTP POST请求,返回包含一个对象的ResponseEntity
- postForObject() 发送一个HTTP POST请求,返回的响应体将映射为一个对象
- execute() 在URL上执行特定的HTTP方法,返回一个从响应体映射得到的对象
- exchange()在URL上执行特定的HTTP方法,返回包含对象的ResponseEntity
什么是RPC
RPC就是要像调用本地的函数一样去调远程函数。进程通信有两种方式,第一种是LPC,通过共享内存空间进行进程间的通信,多用于在多任务操作系统中,另一种是RPC,通过网络进行进程间的通信,他的目的是让调用远程方法就像调用本地方法一样,调用着并不知道这个方法的服务提供者是谁,通过RPC能够解耦服务。
RPC框架的由四个核心组件构成,他们是Client,Cleint Stub,Server和Server Stub,stub可以理解为存根。
Client是服务调用方,Server是服务提供者
Client Stub存放服务端的地址消息,将客户端请求封装为消息,通过序列化后进行网络传输给服务端。
Server Stub存放客户端发来的消息,通过反序列化解析为客户端请求,然后调用本地方法。
实现RPC需要解决的三个问题
- 函数-call id的映射解决了标识函数的问题。在客户端和服务端之间维护函数和call id的对应关系,当调用远程方法时,先从这个表查出call id,然后传给服务端,然后服务端根据id查出所调用的函数
- 序列化和反序列化解决了参数传递的问题。本地调用通过内存栈就可以传递参数,但是远程调用需要把参数序列化为字节数组,然后在反序列为参数
- 网络传输解决函数id和函数参数在网络种传输的问题。大部分RPC框架都使用TCP协议,但也有使用UDP或者是HTTP的
有了Rest为何还要RPC
有一种常见错误就是将Rest视为另一种远程过程调用机制,但是它们却没有任何关系。
RPC是面向服务的,关注行为和动作;而REST是面向资源的,关注资源本身,很多都是做网站的。也就是说Rest是将资源的状态在b/s之间传递,而RPC是调用远程主机应用程序的某个方法,关注减小本地调用和远程调用的差异。RPC更偏向内部调用,REST更偏向外部调用
考虑用RPC还是HTTP构建自己服务时,考虑的因素
- 接口是否需要Schema约束 HTTP是弱Schema约束的
- 是否需要更高效的传输协议 RPC直接使用TCP进行传输
- 是否对数据包大小敏感 HTTP头部有很多冗余
论复杂度,RPC框架肯定是高于简单的HTTP接口的。但毋庸置疑,HTTP接口由于受限于HTTP协议,需要带HTTP请求头,导致传输起来效率或者说安全性不如RPC
SpringCloud和Dubbo的区别
Dubbo:基于TCP的远程过程调用
Spring Cloud:基于spirngboot,而springboot是基于http协议rest风格的RPC
- 服务间通信协议不同,Dubbo基于TCP协议,而Spring Cloud基于http协议
- Dubbo比SpringCloud效率高
- SpirngCloud基于http协议,服务提供方可用多语言开发
- spring cloud是微服务生态,包括完善的微服务工具集;而Dubbo是RPC的实现框架,仅仅是微服务的一部分。
对于效率要求比较高,开发时使用统一的技术方法栈,则选择RPC。。。。常用的为dubbo(耦合Java语言)
如果需要更加灵活的,跨语言,跨平台,用HTTP
Dubbo的基本原理
通过netty实现一个RPC框架
RPC框架让调用方像调用本地函数一样调用远程方法
调用方直接调用本地函数,传入相应参数。通过动态代理的方式将通讯细节以及序列化操作交给代理类来处理,这就需要通过网络来完整无误的将参数传递过去,而Netty框架就是一个高性能的网络通信框架,它可以胜任任务。
RPC框架需要关注的点:1. 动态代理 2. 通讯协议 3. 序列化 4. 网络传输
Protocol协议
首先我们需要确定通信双方的协议格式,请求对象和响应对象。
RpcRequest={requestId,className,methodName,parameterTypes(参数类型),parameters(入参)} //requestId验证请求和响应是否匹配
RpcResponse={requestId,error,result}
序列化
市面有很多序列化协议,为了方便起见,使用JSON作为序列化协议,fastjson作为JSON框架。
public class JSONSerializer implements Serializer{
public byte[] serialize(){
return JSON.toJSONBytes(object);
}
public <T> T deserialize(Class<T> clazz,byte[] bytes){
return JSON.parseObject(bytes,clazz);
}
}
编解码器
编码器实现
public class RpcEncoder extends MessageToByteEncoder {
private Class<?> clazz;
private Serializer serializer;
public RpcEncoder(Class<?> clazz, Serializer serializer) {
this.clazz = clazz;
this.serializer = serializer;
}
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, Object msg, ByteBuf byteBuf) throws Exception {
if (clazz != null && clazz.isInstance(msg)) {
byte[] bytes = serializer.serialize(msg);
byteBuf.writeInt(bytes.length);
byteBuf.writeBytes(bytes);
}
}
}
解码器实现
public class RpcDecoder extends ByteToMessageDecoder {
private Class<?> clazz;
private Serializer serializer;
public RpcDecoder(Class<?> clazz, Serializer serializer) {
this.clazz = clazz;
this.serializer = serializer;
}
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
//因为之前编码的时候写入一个Int型,4个字节来表示长度
if (byteBuf.readableBytes() < 4) {
return;
}
//标记当前读的位置
byteBuf.markReaderIndex();
int dataLength = byteBuf.readInt();
if (byteBuf.readableBytes() < dataLength) {
byteBuf.resetReaderIndex();
return;
}
byte[] data = new byte[dataLength];
//将byteBuf中的数据读入data字节数组
byteBuf.readBytes(data);
Object obj = serializer.deserialize(clazz, data);
list.add(obj);
}
}
Netty客户端
a) 编写启动方法,指定传输使用的Channel
b) 指定ChannelHandler,对网络中的数据进行读写处理
c) 添加编解码器
d) 添加失败重试机制
e) 添加发送请求消息的方法
public class NetyClient{
private String host;
private Integer port;
private Channel channel;
private ClientHandler clientHandler;
private EventLoopGroup evenetLoopGrouop;
private static final int MAX_RETRY = 5; //最大重试次数
public NettyClient(String host, Integer port) {
this.host = host;
this.port = port;
}
public void connect(){
clientHandler = new ClientHandler();
eventLoopGroup = new NioEventLoopGroup();
//开始编写启动类
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup)
//指定传输使用的Channel
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,5000)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//添加编码器
pipeline.addLast(new RpcEncoder(RpcRequest.class, new JSONSerializer()));
//添加解码器
pipeline.addLast(new RpcDecoder(RpcResponse.class, new JSONSerializer()));
//请求处理类
pipeline.addLast(clientHandler);
}
});
进行失败重连
connect(bootstrap, host, port, MAX_RETRY);
}
/*
* 进行失败重连
*/
private void connect(Bootstrap bootstrap, String host, int port, int retry) {
ChannelFuture channelFuture = bootstrap.connect(host, port).addListener(future -> {
if (future.isSuccess()) {
log.info("连接服务端成功");
} else if (retry == 0) {
log.error("重试次数已用完,放弃连接");
} else {
//第几次重连:
int order = (MAX_RETRY - retry) + 1;
//本次重连的间隔
int delay = 1 << order;
log.error("{} : 连接失败,第 {} 重连....", new Date(), order);
bootstrap.config().group().schedule(() -> connect(bootstrap, host, port, retry - 1), delay, TimeUnit.SECONDS);
}
});
channel = channelFuture.channel();
}
/*
* 发送消息
*/
public RpcResponse send(){
try {
//1. 将请求对象写入通道
channel.writeAndFlush(request).await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//2. 根据请求id获得响应对象
return clientHandler.getRpcResponse(request.getRequestId())
}
/*
* 关闭通道
*/
@PreDestroy
public void close() {
eventLoopGroup.shutdownGracefully();
channel.closeFuture().syncUninterruptibly();
}
}
编写数据处理类,使用Map维护请求对象ID与响应结果的映射关系,目的是为了客户端验证服务端响应是否与请求相匹配,因为Netty的channel可能被多个线程使用,当返回结果是不确定是从哪个线程返回的。
public class ClientHandler extends ChannelDuplexHandler {
/**
* 使用Map维护请求对象ID与响应结果Future的映射关系
* 目的是
*/
private final Map<String, DefaultFuture> futureMap = new ConcurrentHashMap<>();
//1. 将RpcRequest写入通道
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
if (msg instanceof RpcRequest) {
RpcRequest request = (RpcRequest) msg;
//发送请求对象之前,先把请求ID保存下来,并构建一个与响应Future的映射关系
futureMap.putIfAbsent(request.getRequestId(), new DefaultFuture());
}
super.write(ctx, msg, promise);
}
//2. 从通道读取返回的RpcResponse
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof RpcResponse) {
//获取响应对象
RpcResponse response = (RpcResponse) msg;
DefaultFuture defaultFuture =
futureMap.get(response.getRequestId());
//将结果写入DefaultFuture
defaultFuture.setResponse(response);
}
super.channelRead(ctx,msg);
}
//根据响应id查询发送请求的请求id
public RpcResponse getRpcResponse(String requsetId) {
try {
DefaultFuture future = futureMap.get(requsetId);
return future.getRpcResponse(5000);
} finally {
//获取成功以后,从map中移除
futureMap.remove(requsetId);
}
}
}
响应结果是封装在DefaultFuture中的,因为Netty是异步框架,所有的返回都是基于Future和Callback机制的
实际上用了wait和notify机制,同时使用一个boolean变量做辅助
public class DefaultFuture {
private RpcResponse rpcResponse;
private volatile boolean isSucceed = false;
private final Object object = new Object();
public RpcResponse getRpcResponse(int timeout) {
synchronized (object) {
while (!isSucceed) {
try {
object.wait(timeout);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return rpcResponse;
}
}
public void setResponse(RpcResponse response) {
if (isSucceed) {
return;
}
synchronized (object) {
this.rpcResponse = response;
this.isSucceed = true;
object.notify();
}
}
}
Netty服务端
与客户端差不多,但注意对请求解码后,需要通过代理的方式调用本地函数
public class NettyServer implements InitializingBean {
private EventLoopGroup boss = null;
private EventLoopGroup worker = null;
@Autowired
private ServerHandler serverHandler;
@Override
public void afterPropertiesSet() throws Exception {
//此处使用了zookeeper做注册中心,本文不涉及,可忽略
ServiceRegistry registry = new ZkServiceRegistry("127.0.0.1:2181");
start(registry);
}
public void start(ServiceRegistry registry) throws Exception {
//负责处理客户端连接的线程池
boss = new NioEventLoopGroup();
//负责处理读写操作的线程池
worker = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boss, worker)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//添加解码器
pipeline.addLast(new RpcEncoder(RpcResponse.class, new JSONSerializer()));
//添加编码器
pipeline.addLast(new RpcDecoder(RpcRequest.class, new JSONSerializer()));
//添加请求处理器
pipeline.addLast(serverHandler);
}
});
bind(serverBootstrap, 8888);
}
/**
* 如果端口绑定失败,端口数+1,重新绑定
*
* @param serverBootstrap
* @param port
*/
public void bind(final ServerBootstrap serverBootstrap,int port) {
serverBootstrap.bind(port).addListener(future -> {
if (future.isSuccess()) {
log.info("端口[ {} ] 绑定成功",port);
} else {
log.error("端口[ {} ] 绑定失败", port);
bind(serverBootstrap, port + 1);
}
});
}
@PreDestroy
public void destory() throws InterruptedException {
boss.shutdownGracefully().sync();
worker.shutdownGracefully().sync();
log.info("关闭Netty");
}
}
处理读写操作的Handler类
@Component
@Slf4j
@ChannelHandler.Sharable
public class ServerHandler extends SimpleChannelInboundHandler<RpcRequest> implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
protected void channelRead0(ChannelHandlerContext ctx, RpcRequest msg) {
RpcResponse rpcResponse = new RpcResponse();
rpcResponse.setRequestId(msg.getRequestId());
try {
Object handler = handler(msg);
log.info("获取返回结果: {} ", handler);
rpcResponse.setResult(handler);
} catch (Throwable throwable) {
rpcResponse.setError(throwable.toString());
throwable.printStackTrace();
}
ctx.writeAndFlush(rpcResponse);
}
/**
* 服务端使用代理处理请求
*
* @param request
* @return
*/
private Object handler(RpcRequest request) throws ClassNotFoundException, InvocationTargetException {
//使用Class.forName进行加载Class文件
Class<?> clazz = Class.forName(request.getClassName());
Object serviceBean = applicationContext.getBean(clazz);
log.info("serviceBean: {}",serviceBean);
Class<?> serviceClass = serviceBean.getClass();
log.info("serverClass:{}",serviceClass);
String methodName = request.getMethodName();
Class<?>[] parameterTypes = request.getParameterTypes();
Object[] parameters = request.getParameters();
//使用CGLIB Reflect
FastClass fastClass = FastClass.create(serviceClass);
FastMethod fastMethod = fastClass.getMethod(methodName, parameterTypes);
log.info("开始调用CGLIB动态代理执行服务端方法...");
return fastMethod.invoke(serviceBean, parameters);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
客户端使用Java动态代理,在代理类实现通信细节,Java动态代理类需要实现InvocationHandler接口
在invoke方法中封装请求对象,构建NettyClient对象开启客户端,然后发送请求返回调用结果
单独的T 代表一个类型 ,而 Class代表这个类型所对应的类, Class<?>表示类型不确定的类
@Slf4j
public class RpcClientDynamicProxy<T> implements InvocationHandler {
private Class<T> clazz;
public RpcClientDynamicProxy(Class<T> clazz) throws Exception {
this.clazz = clazz;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
RpcRequest request = new RpcRequest();
String requestId = UUID.randomUUID().toString();
String className = method.getDeclaringClass().getName();
String methodName = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes();
request.setRequestId(requestId);
request.setClassName(className);
request.setMethodName(methodName);
request.setParameterTypes(parameterTypes);
request.setParameters(args);
log.info("请求内容: {}",request);
//开启Netty 客户端,直连
NettyClient nettyClient = new NettyClient("127.0.0.1", 8888);
log.info("开始连接服务端:{}",new Date());
nettyClient.connect();
RpcResponse send = nettyClient.send(request);
log.info("请求调用返回结果:{}", send.getResult());
return send.getResult();
}
}
代理工厂类通过Proxy.newProxyInstance创建接口的代理类
public class ProxyFactory {
public static <T> T create(Class<T> interfaceClass) throws Exception {
return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),new Class<?>[] {interfaceClass}, new RpcClientDynamicProxy<T>(interfaceClass));
}
}
测试RPC框架
客户端使用Java动态代理,在代理类中实现通信细节,
服务端也是用动态代理,通过CGLIB动态代理类执行目标方法
- 公共接口
public interface HelloService {
String hello(String name);
}
- 服务端实现接口方法
//服务端实现
@Service
public class HelloServiceImpl implements HelloService {
@Override
public String hello(String name) {
return "hello, " + name;
}
}
- 客户端应用
@SpringBootApplication
@Slf4j
public class ClientApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(ClientApplication.class, args);
HelloService helloService = ProxyFactory.create(HelloService.class);
log.info("响应结果“: {}",helloService.hello("pjmike"));
}
}
以上我们基于Netty实现了一个非非非常简陋的RPC框架,比起成熟的RPC框架来说相差甚远,甚至说基本的注册中心都没有实现,但是通过本次实践,可以说我对于RPC的理解更深了,了解了一个RPC框架到底需要关注哪些方面,未来当我们使用成熟的RPC框架时,比如Dubbo,能够做到心中有数,能明白其底层不过也是使用Netty作为基础通讯框架。往后,如果更深入翻看开源RPC框架源码时,也相对比较容易
补充
CGLIB和Java动态代理的区别
- Java动态代理只能够对接口进行代理,不能对普通的类进行代理(因为所有生成的代理类的父类为Proxy,Java类继承机制不允许多重继承);CGLIB能够代理普通类;
- Java动态代理使用Java原生的反射API进行操作,在生成类上比较高效;CGLIB使用ASM框架直接对字节码进行操作,在类的执行过程中比较高效
参考优秀博客:https://segmentfault.com/a/1190000020599687?utm_source=tag-newest