# MytinyRPC **Repository Path**: loulabi/mytiny-rpc ## Basic Information - **Project Name**: MytinyRPC - **Description**: 自写RPC框架,http调用JSON数据封装,采用zookeeper作为服务注册和发现中心,实现多种负载均衡算法,支持平滑加权随机,低侵入,易扩展 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 3 - **Forks**: 0 - **Created**: 2020-11-01 - **Last Updated**: 2022-07-01 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # MytinyRPC #### 介绍 项目实现RPC底层http调用逻辑,采用zookeeper实现服务提供者和服务消费者解耦,负载均衡采用平滑加权随机算法,实现了多种序列化机制 #### 软件架构 环境:jdk11 zookeeper RPC结构图: ![image-20201102095758186](.\images\image-20201102095758186.png) 项目监控中心有待完善,目前实现了消费端,注册中心,服务端的功能 #### **特性:** 1.采用HTTP协议传输实现RPC调用 2.实现了3种负载均衡算法:随机算法,轮转算法,平滑加权随机算法 3.采用Zookeeper作为服务注册和发现中心 4.服务注解自动扫描注册,简化启动配置 5.模块耦合度低,易扩展 一、服务注册和发现中心采用了zookeeper。ZkServiceRegistry是服务注册类,ZkServiceDiscovery 是服务发现类 二、http调用逻辑 调用信息Class格式: ```java public class Invocation implements Serializable { private String interfaceName; private String methodName; private Class[] paramTypes; private Object[] params; } ``` HttpClient封装请求信息通过HTTP协议携带JSON格式数据发送给服务器负载均衡选择 ```java public static T getProxy(final Class interfaceClass){ return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //用的是jdk11中标准化的httpClient HttpClient httpClient=new HttpClient(); Invocation invocation=new Invocation(interfaceClass.getName(), method.getName(),method.getParameterTypes(),args); //负载均衡选择服务器 URL url=ProxyFactory.loadBalance.loadBalance(interfaceClass.getName()); String result=httpClient.send(url.getHostname(),url.getPort(),invocation); return result; } }); } ``` httpClient的send方法用json封装对象发送 ```java public String send(String hostname, Integer port, Invocation invocation){ //HttpClient封装http请求发送给服务器 String result=new String(); try{ HttpRequest request=HttpRequest.newBuilder() .uri(new URI("http",null,hostname,port,"/",null,null)) .POST(HttpRequest.BodyPublishers.ofByteArray(serializer.serialize(invocation))) .build(); java.net.http.HttpClient client= java.net.http.HttpClient.newHttpClient(); HttpResponse response=client.send(request,HttpResponse.BodyHandlers.ofString()); result=response.body(); } catch (Exception e){ e.printStackTrace(); } return result; } ``` 接受到调用请求后HTTPServer调用Handle方法,反射调用封装结果返回 ```java public void handle(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //处理请求 // 获取对象 try { //序列化传输 InputStream is = req.getInputStream(); Invocation invocation= (Invocation) serializer.deserialize(is.readAllBytes(),Invocation.class); //找出实现类 Class imolClass= localRegister.get(invocation.getInterfaceName()); //获取类的方法 Method method = imolClass.getMethod(invocation.getMethodName(),invocation.getParamTypes()); //反射调用方法 String result = (String) method.invoke(imolClass.newInstance(),invocation.getParams()); //把结果返回给调用者 IOUtils.write(result,resp.getOutputStream()); } catch (IOException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } ``` 三、实现了三种负载均衡策略: 1.随机负载均衡 ```java public class RandomLoadBalance implements LoadBalance { private static final ServiceDiscovery serviceDiscovery; static { serviceDiscovery = SingletonFactory.getInstance(ZkServiceDiscovery.class); } private URL random(List list){ Random random = new Random(); int i = random.nextInt(list.size()); System.out.println("选择负载服务器:"+ list.get(i).toString()); return list.get(i); } public URL loadBalance(String interfaceName) { List urlList= serviceDiscovery.getURL(interfaceName); return random(urlList); } } ``` 2.轮询负载均衡 ```java public class RoundLoadBalance implements LoadBalance { //初始游标 private int choose = -1; private static final ServiceDiscovery serviceDiscovery; static { serviceDiscovery = SingletonFactory.getInstance(ZkServiceDiscovery.class); } @Override public URL loadBalance(String interfaceName) { List urlList= serviceDiscovery.getURL(interfaceName); choose++; choose = choose%urlList.size(); System.out.println("选择负载服务器:"+ urlList.get(choose).toString()); return urlList.get(choose); } } ``` 3.平滑加权随机算法 ```java public class SmoothWeightedRandom implements LoadBalance { //集群权值之和 private static volatile Integer smoothtotal=0; //保存当前权重用于平滑加权随机算法 private static Map> curWeights=new ConcurrentHashMap<>(); private static final ServiceDiscovery serviceDiscovery; static { serviceDiscovery = SingletonFactory.getInstance(ZkServiceDiscovery.class); } /*** * @Author HP * @Description //平滑加权随机算法 * @Name SmoothWeightedRandom * @Param [list] * @return framework.URL **/ private URL SmoothWeightedRandom(List list,String interfaceName){ if (list.size()<=0){ return null; } ConcurrentHashMap weights=curWeights.get(interfaceName); //计算权值之和 smoothtotal=0; for (int i = 0; i (weights.get(b)-weights.get(a))); //选中的URL的当前权值减去smoothtotal URL result=list.get(0); int afterweight=weights.get(result)-smoothtotal; weights.replace(list.get(0),afterweight); //URL当前权值加上固定权值 for (int i = 0; i ()); } //List urlList= RemoteMapRegister.getURL(interfaceName); List urlList= serviceDiscovery.getURL(interfaceName); for (URL url:urlList){ if (!curWeights.get(interfaceName).containsKey(url)){ curWeights.get(interfaceName).put(url,0); } } //return random(urlList); return SmoothWeightedRandom(urlList,interfaceName); } } ``` #### 使用说明 1. 配置依赖包,启动zookeeper 2. 启动Provider,Provider2,Provider3三个服务器,接口权重分别为: ```java URL url1=new URL("localhost",8080,5); URL url2=new URL("localhost",8081,1); URL url3=new URL("localhost",8083,1); ``` 3. 再启动消费端Consumer,经平滑加权负载均衡后调用顺序为 选择负载服务器:localhost:8081 选择负载服务器:localhost:8080 选择负载服务器:localhost:8080 选择负载服务器:localhost:8083 选择负载服务器:localhost:8080 选择负载服务器:localhost:8080 选择负载服务器:localhost:8080 选择负载服务器:localhost:8081 选择负载服务器:localhost:8080 选择负载服务器:localhost:8080 ![image-20201102153213791](.\images\image-20201102153213791.png) **Version2** 加入了注解自动扫描启动功能,在启动类上加@RpcServiceScan,需要配置的服务类上加上@RpcService运行时即可自动扫描加载。 ```java //启动服务只需注解加上下面的语句,启动更加简单方便 new HttpServer().start(url); ``` 待做 1.支持多种协议,多种服务器切换 2.监控中心的实现 3.服务心跳检测机制 4.service mesh