RPC的概念
背景知识
- RPC的基本概念,核心功能
常见的RPC框架
Duboo基本功能
- 远程通讯
- 基于接口方法的透明远程过程调用
- 负载均衡
- 服务注册中心
RPC过程
client 调用远程方法-> request序列化 -> 协议编码 -> 网络传输-> 服务端 -> 反序列化request -> 调用本地方法得到response -> 序列化 ->编码->……
版本迭代过程
目录
从0开始的RPC的迭代过程:
- version0版本:以不到百行的代码完成一个RPC例子
- version1版本:完善通用消息格式(request,response),客户端的动态代理完成对request消息格式的封装
- version2版本:支持服务端暴露多个服务接口, 服务端程序抽象化,规范化
- version3版本:使用高性能网络框架netty的实现网络通信,以及客户端代码的重构
- version4版本:自定义消息格式,支持多种序列化方式(java原生, json…)
- version5版本: 服务器注册与发现的实现,zookeeper作为注册中心
- version6版本: 负载均衡的策略的实现
2.MyRPC 版本2
背景知识
- 代码解耦
- 线程池
本节问题:
如果一个服务端要提供多个服务的接口, 例如新增一个BlogService,怎么处理?
// 自然的想到用一个Map来保存,<interfaceName, xxxServiceImpl>
UserService userService = new UserServiceImpl();
BlogService blogService = new BlogServiceImpl();
Map<String, Object>.put("***.userService", userService);
Map<String, Object>.put("***.blogService", blogService);
// 此时来了一个request,我们就能从map中取出对应的服务
Object service = map.get(request.getInterfaceName())
版本升级过程
更新前的工作: 更新一个新的服务接口样例和pojo类
// 新的服务接口
public interface BlogService {
Blog getBlogById(Integer id);
}
// 服务端新的服务接口实现类
public class BlogServiceImpl implements BlogService {
@Override
public Blog getBlogById(Integer id) {
Blog blog = Blog.builder().id(id).title("我的博客").useId(22).build();
System.out.println("客户端查询了"+id+"博客");
return blog;
}
}
// pojo类
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Blog implements Serializable {
private Integer id;
private Integer useId;
private String title;
}
更新1: HashMap<String, Object> 添加多个服务的实现类
public class TestServer {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
BlogService blogService = new BlogServiceImpl();
Map<String, Object> serviceProvide = new HashMap<>();
// 暴露两个服务接口, 即在RPCServer中加一个HashMap
serviceProvide.put("com.ganghuan.myRPCVersion2.service.UserService",userService);
serviceProvide.put("com.ganghuan.myRPCVersion2.service.BlogService",blogService);
RPCServer RPCServer = new SimpleRPCRPCServer(serviceProvide);
RPCServer.start(8899);
}
}
// 这里先不去讨论实现其中的细节,因为这里还应该进行优化,我们先去把服务端代码松耦合,再回过来讨论
更新2: 服务端代码重构
- 抽象RPCServer,开放封闭原则
// 把RPCServer抽象成接口,以后的服务端实现这个接口即可
public interface RPCServer {
void start(int port);
void stop();
}
- RPCService简单版本的实现
/**
* 这个实现类代表着java原始的BIO监听模式,来一个任务,就new一个线程去处理
* 处理任务的工作见WorkThread中
*/
public class SimpleRPCRPCServer implements RPCServer {
// 存着服务接口名-> service对象的map
private Map<String, Object> serviceProvide;
public SimpleRPCRPCServer(Map<String,Object> serviceProvide){
this.serviceProvide = serviceProvide;
}
public void start(int port) {
try {
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("服务端启动了");
// BIO的方式监听Socket
while (true){
Socket socket = serverSocket.accept();
// 开启一个新线程去处理
new Thread(new WorkThread(socket,serviceProvide)).start();
}
} catch (IOException e) {
e.printStackTrace();
System.out.println("服务器启动失败");
}
}
public void stop(){
}
}
- 为了加强性能,我们还提供了线程池版的实现
public class ThreadPoolRPCRPCServer implements RPCServer {
private final ThreadPoolExecutor threadPool;
private Map<String, Object> serviceProvide;
public ThreadPoolRPCRPCServer(Map<String, Object> serviceProvide){
threadPool = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),
1000, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100));
this.serviceProvide = serviceProvide;
}
public ThreadPoolRPCRPCServer(Map<String, Object> serviceProvide, int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue){
threadPool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
this.serviceProvide = serviceProvide;
}
@Override
public void start(int port) {
System.out.println("服务端启动了");
try {
ServerSocket serverSocket = new ServerSocket(port);
while(true){
Socket socket = serverSocket.accept();
threadPool.execute(new WorkThread(socket,serviceProvide));
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void stop() {
}
}
- 工作任务类,从服务端代码分离出来,简化服务端代码,单一职责原则
/**
* 这里负责解析得到的request请求,执行服务方法,返回给客户端
* 1. 从request得到interfaceName 2. 根据interfaceName在serviceProvide Map中获取服务端的实现类
* 3. 从request中得到方法名,参数, 利用反射执行服务中的方法 4. 得到结果,封装成response,写入socket
*/
@AllArgsConstructor
public class WorkThread implements Runnable{
private Socket socket;
private Map<String, Object> serviceProvide;
@Override
public void run() {
try {
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
// 读取客户端传过来的request
RPCRequest request = (RPCRequest) ois.readObject();
// 反射调用服务方法获得返回值
RPCResponse response = getResponse(request);
//写入到客户端
oos.writeObject(response);
oos.flush();
}catch (IOException | ClassNotFoundException e){
e.printStackTrace();
System.out.println("从IO中读取数据错误");
}
}
private RPCResponse getResponse(RPCRequest request){
// 得到服务名
String interfaceName = request.getInterfaceName();
// 得到服务端相应服务实现类
Object service = serviceProvide.get(interfaceName);
// 反射调用方法
Method method = null;
try {
method = service.getClass().getMethod(request.getMethodName(), request.getParamsTypes());
Object invoke = method.invoke(service, request.getParams());
return RPCResponse.success(invoke);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
System.out.println("方法执行错误");
return RPCResponse.fail();
}
}
}
服务端代码第一次重构完毕。
更新3: 服务暴露类,这里回到了更新1**,我们发现服务接口名是我们**直接手写的,这里其实可以利用class对象自动得到
/**
* 之前这里使用Map简单实现的
* 存放服务接口名与服务端对应的实现类
* 服务启动时要暴露其相关的实现类0
* 根据request中的interface调用服务端中相关实现类
*/
public class ServiceProvider {
/**
* 一个实现类可能实现多个接口
*/
private Map<String, Object> interfaceProvider;
public ServiceProvider(){
this.interfaceProvider = new HashMap<>();
}
public void provideServiceInterface(Object service){
String serviceName = service.getClass().getName();
Class<?>[] interfaces = service.getClass().getInterfaces();
for(Class clazz : interfaces){
interfaceProvider.put(clazz.getName(),service);
}
}
public Object getService(String interfaceName){
return interfaceProvider.get(interfaceName);
}
}
前面服务端的代码中有Sevicerprovide 这个HashMap的地方需要改成ServiProvider,比如
public class TestServer {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
BlogService blogService = new BlogServiceImpl();
// Map<String, Object> serviceProvide = new HashMap<>();
// serviceProvide.put("com.ganghuan.myRPCVersion2.service.UserService",userService);
// serviceProvide.put("com.ganghuan.myRPCVersion2.service.BlogService",blogService);
ServiceProvider serviceProvider = new ServiceProvider();
serviceProvider.provideServiceInterface(userService);
serviceProvider.provideServiceInterface(blogService);
RPCServer RPCServer = new SimpleRPCRPCServer(serviceProvider);
RPCServer.start(8899);
}
}
结果
// 客户中添加新的测试用例
BlogService blogService = rpcClientProxy.getProxy(BlogService.class);
Blog blogById = blogService.getBlogById(10000);
System.out.println("从服务端得到的blog为:" + blogById);
总结:
在一版本中,我们重构了服务端的代码,代码更加简洁,
添加线程池版的服务端的实现,性能应该会有所提升(未测)
并且服务端终于能够提供不同服务了, 功能更加完善,不再鸡肋
此RPC最大的痛点
- 传统的BIO与线程池网络传输性能低