基於Zookeeper註冊中心實現簡易手寫RPC框架

基於Zookeeper註冊中心實現簡易手寫RPC框架

一,環境準備

  1. Zookepper單點或集羣環境,演示使用集羣環境
  2. Zookeeper的Curator客戶端工具
  3. 使用Maven進行項目構建

二,代碼實現

服務端開啓服務

    1,jar包依賴

<dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.zookeeper</groupId>
      <artifactId>zookeeper</artifactId>
      <version>3.4.8</version>
    </dependency>
    <dependency>
      <groupId>org.apache.curator</groupId>
      <artifactId>curator-framework</artifactId>
      <version>4.0.0</version>
    </dependency>
    <dependency>
      <groupId>org.apache.curator</groupId>
      <artifactId>curator-client</artifactId>
      <version>4.0.0</version>
    </dependency>

    <dependency>
      <groupId>org.apache.curator</groupId>
      <artifactId>curator-recipes</artifactId>
      <version>4.0.0</version>
    </dependency>
  </dependencies>

    2,VO和服務地址

        * VO

public class RPCRequest implements Serializable {
    private static final long serialVersionUID = -406351179958827029L;

    private String className;

    private String methodName;

    private Object[] parameters;

    private String version;

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public Object[] getParameters() {
        return parameters;
    }

    public void setParameters(Object[] parameters) {
        this.parameters = parameters;
    }
}

        * 服務地址

public class ZKConfig {

    public static final String ZK_HOST = "192.168.91.128:2181,192.168.91.129:2181,192.168.91.130:2181";

    public static final String ZK_REGISTER_PATH = "/registers";
}

        * 自定義註解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RpcAnnotation {

    /**
     * 對外發布的服務接口地址
     */
    Class<?> value();

    /**
     * 版本號
     */
    String version() default "";

}

    3,註冊服務接口

         * 通過構造方法初始化Curator

private CuratorFramework curatorFramework;

    public RegisterService() {
        curatorFramework = CuratorFrameworkFactory.builder()
                .connectString(ZKConfig.ZK_HOST).sessionTimeoutMs(4000)
                .retryPolicy(new ExponentialBackoffRetry(1000, 3)).build();
        curatorFramework.start();
    }

         * 註冊服務

             -- 註冊服務名稱爲根節點下的持久化節點

             -- 註冊服務對應的服務器地址爲服務名稱節點下的臨時節點

             -- 每次服務重啓時會清空臨時節點(服務的分佈式服務器),並重新註冊臨時節點

public void register(String serviceName, String serviceAddress) {
        try {
            // 註冊相應的服務 --> 不存在則創建
            String serviceUrl = ZKConfig.ZK_REGISTER_PATH + "/" + serviceName;
            if (null == curatorFramework.checkExists().forPath(serviceUrl)) {
                curatorFramework.create().creatingParentsIfNeeded()
                        .withMode(CreateMode.PERSISTENT).forPath(serviceUrl, "0".getBytes());
            }
            // 獲取地址的完整路徑
            String addressUrl = serviceUrl + "/" + serviceAddress;
            // 註冊地址的臨時節點
            String rsNode = curatorFramework.create().withMode(CreateMode.EPHEMERAL).forPath(addressUrl, "0".getBytes());
            System.out.println("服務註冊成功, rsNode : " + rsNode);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    4,綁定服務

         * 通過構造初始化構造中心,服務發佈地址已經服務綁定集合(Map)

public class RPCServer {

    private final ExecutorService executorService = Executors.newCachedThreadPool();

    /**
     * 註冊中心
     */
    private IRegisterService registerService;

    /**
     * 服務發佈地址
     */
    private String serviceAddress;

    Map<String, Object> handlerMap = new HashMap<String, Object>();

    public RPCServer(IRegisterService registerService, String serviceAddress) {
        this.registerService = registerService;
        this.serviceAddress = serviceAddress;
    }

         * 對外服務接口

@RpcAnnotation(value = ISelfHelloService.class)
public class SelfHelloService implements ISelfHelloService {

    @Override
    public String sayHello(String msg) {
        return "[I'M 8081]HELLO : " + msg;
    }
}

          * 根據註解信息獲取服務名稱,服務版本號並綁定服務

public void bind(Object ... services) {
        for (Object service : services) {
            RpcAnnotation annotation = service.getClass().getAnnotation(RpcAnnotation.class);
            String serviceName = annotation.value().getName();
            String version = annotation.version();
            if (null != version && !"".equals(version)) {
                serviceName = serviceName + "-" + version;
            }
            // 綁定服務接口名稱對應的服務
            handlerMap.put(serviceName, service);
        }
    }

    5,發佈服務端服務

           * 根據服務地址啓動服務,並註冊服務到註冊中心

public void publishServer() {
        ServerSocket serverSocket = null;
        try {
            // 服務端啓動服務代碼內容==================
            // 啓動服務
            serverSocket = new ServerSocket(Integer.valueOf(serviceAddress.split(":", -1)[1]));
            for (String interfaceName : handlerMap.keySet()) {
                //
                registerService.register(interfaceName, serviceAddress);
                System.out.println("註冊服務成功, interfaceName : " + interfaceName + ", serviceAddress : " + serviceAddress);
            }
            
            // 服務端獲取客戶端請求代碼處理======================
            // 獲取監聽
            while (true) {
                Socket socket = serverSocket.accept();
                executorService.execute(new ProcessorHandler(socket, handlerMap));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != serverSocket) {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

         * main方法啓動服務,通過啓動兩個服務實現模擬分佈式,並驗證負載均衡

public class ClusterServerService_1 {
    public static void main(String[] args) {
        ISelfHelloService helloService = new SelfHelloService();
        IRegisterService registerService = new RegisterService();
        // 初始化註冊中心和服務端口信息
        RPCServer server = new RPCServer(registerService, "127.0.0.1:8081");
        // 綁定服務
        server.bind(helloService);
        // 發佈並註冊服務
        server.publishServer();
        System.out.println("服務發佈成功");
    }
}
public class ClusterServerService_2 {
    public static void main(String[] args) {
        ISelfHelloService helloService = new SelfHello2Service();
        IRegisterService registerService = new RegisterService();
        // 初始化註冊中心和服務端口信息
        RPCServer server = new RPCServer(registerService, "127.0.0.1:8082");
        // 綁定服務
        server.bind(helloService);
        // 發佈並註冊服務
        server.publishServer();
        System.out.println("服務發佈成功");
    }
}

           * 服務啓動執行結果

          * zookeeper查詢節點

客戶端發起請求

    1,jar包依賴

<dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>com.gupao.server</groupId>
      <artifactId>RMIServerProject</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
      <groupId>org.apache.zookeeper</groupId>
      <artifactId>zookeeper</artifactId>
      <version>3.4.8</version>
    </dependency>
    <dependency>
      <groupId>org.apache.curator</groupId>
      <artifactId>curator-framework</artifactId>
      <version>4.0.0</version>
    </dependency>
    <dependency>
      <groupId>org.apache.curator</groupId>
      <artifactId>curator-client</artifactId>
      <version>4.0.0</version>
    </dependency>

    <dependency>
      <groupId>org.apache.curator</groupId>
      <artifactId>curator-recipes</artifactId>
      <version>4.0.0</version>
    </dependency>
  </dependencies>

   

   2,通過Zookeeper獲取註冊服務

           * 通過構造方法初始化Curator客戶端

public class DiscoveryService implements IDiscoveryService {

    private CuratorFramework curatorFramework;

    List<String> serviceNodes = new ArrayList<String>();

    private String address;

    public DiscoveryService(String address){
        curatorFramework = CuratorFrameworkFactory.builder()
                .connectString(address).sessionTimeoutMs(4000)
                .retryPolicy(new ExponentialBackoffRetry(1000, 3)).build();
        curatorFramework.start();
    }

           * 通過serviceName獲取服務節點

public String discovery(String serviceName) {
        try {
            // 根據路徑獲取所有節點
            String serviceUrl = ZKConfig.ZK_REGISTER_PATH + "/" + serviceName;
            serviceNodes = curatorFramework.getChildren().forPath(serviceUrl);
            // 發起服務監聽
            registerWatcher(serviceUrl);
            // 實現簡易負載均衡
            ILoadBanlanceService loadBanlanceService = new LoadBalanceService();
            return loadBanlanceService.selectHost(serviceNodes);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

           * 服務監聽代碼

private void registerWatcher(final String path) {
        try {
            PathChildrenCache pathChildrenCache = new PathChildrenCache(curatorFramework, path, true);
            PathChildrenCacheListener pathChildrenCacheListener = new PathChildrenCacheListener() {
                @Override
                public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {
                    serviceNodes = curatorFramework.getChildren().forPath(path);
                }
            };
            pathChildrenCache.getListenable().addListener(pathChildrenCacheListener);
            pathChildrenCache.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

           * 通過隨機數實現簡易負載均衡代碼

                  * 接口

public interface ILoadBanlanceService {
    String selectHost(List<String> serviceNodes);
}

                  * 抽象類構造模板方法

public abstract class AbstractBalanceService implements ILoadBanlanceService {
    @Override
    public String selectHost(List<String> serviceNodes) {
        if (null == serviceNodes || serviceNodes.size() == 0) {
            return "";
        }
        if (serviceNodes.size() == 1) {
            return serviceNodes.get(0);
        }
        return doSelect(serviceNodes);
    }

    public abstract String doSelect(List<String> selectNodes);
}

                    * 實現類實現簡易負載均衡

public class LoadBalanceService extends AbstractBalanceService {

    @Override
    public String doSelect(List<String> selectNodes) {
        Random random = new Random();
        int indexSelect = random.nextInt(selectNodes.size());
        return selectNodes.get(indexSelect);
    }
}

    3,構建動態代理,發起服務請求

              * 構建動態代理對象

public class RPCClientProxy {

    private IDiscoveryService discoveryService;

    private String version;

    public RPCClientProxy(IDiscoveryService discoveryService, String version) {
        this.discoveryService = discoveryService;
        this.version = version;
    }

    public <T> T clientProxy(final Class<T> interafaceCls) {
        return (T) Proxy.newProxyInstance(interafaceCls.getClassLoader(),
                new Class[]{interafaceCls}, new RemoteInvocationHandler(discoveryService, version));
    }
}

    5,InvocationHandler實現

           * 通過構造方法初始化註冊中心對象和版本號

private IDiscoveryService discoveryService;

    private String version;

    public RemoteInvocationHandler(IDiscoveryService discoveryService, String version) {
        this.discoveryService = discoveryService;
        this.version = version;
    }

             * 動態代理實現方法調用並提交服務請求發送

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        RPCRequest request = new RPCRequest();
        request.setClassName(method.getDeclaringClass().getName());
        request.setMethodName(method.getName());
        request.setParameters(args);
        request.setVersion(version);
        String serviceName = request.getClassName();
        if (null != version && !"".equals(version)) {
            serviceName = serviceName + "-" + version;
        }
        String serviceAddress = discoveryService.discovery(serviceName);
        RPCTransPort transformPort = new RPCTransPort(serviceAddress);
        return transformPort.send(request);
    }

    4,發送服務請求到客戶端

           * 通過負載均衡獲取的服務地址構建套接字

public class RPCTransPort {

    private String serviceAddress;

    public RPCTransPort(String serviceAddress) {
        this.serviceAddress = serviceAddress;
    }

    private Socket newSocket() {
        Socket socket = null;
        try {
            String[] addressArr = serviceAddress.split(":", -1);
            socket = new Socket(addressArr[0], Integer.valueOf(addressArr[1]));
            return socket;
        } catch (Exception e) {
            throw new RuntimeException("CREATED CONNECTION FAILED=======");
        } finally {
        }
    }

           * 通過套接字輸出流構建對象輸出流

           * 通過對象輸出流寫出服務請求參數

public Object send(RPCRequest request) {
        Socket socket = null;
        try {
            // 輸出服務請求參數到服務端代碼======================
            socket = newSocket();
            ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
            outputStream.writeObject(request);
            outputStream.flush();
            
            // 服務端響應結果代碼=========================
            ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
            Object result = inputStream.readObject();
            inputStream.close();
            outputStream.close();
            return result;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    5,客戶端發送請求main方法

public class ClientController {
    public static void main(String[] args) {
        IDiscoveryService discoveryService = new DiscoveryService(ZKConfig.ZK_HOST);
        for (int i = 0; i < 10; i++) {
            RPCClientProxy rpcClientProxy = new RPCClientProxy(discoveryService, null);
            // 構建代理對象
            ISelfHelloService proxyService =
                    rpcClientProxy.clientProxy(ISelfHelloService.class);
            // 調用服務方法
            String result =  proxyService.sayHello("ZPJ");
            System.out.println(result);
        }
    }
}

服務端響應服務

    1,通過服務監聽獲取客戶端請求套接字 (class RPCServer)

public void publishServer() {
        ServerSocket serverSocket = null;
        try {
            // 服務端啓動服務代碼內容==================
            // 啓動服務
            serverSocket = new ServerSocket(Integer.valueOf(serviceAddress.split(":", -1)[1]));
            for (String interfaceName : handlerMap.keySet()) {
                //
                registerService.register(interfaceName, serviceAddress);
                System.out.println("註冊服務成功, interfaceName : " + interfaceName + ", serviceAddress : " + serviceAddress);
            }

            // 服務端獲取客戶端請求代碼處理======================
            // 獲取監聽
            while (true) {
                Socket socket = serverSocket.accept();
                executorService.execute(new ProcessorHandler(socket, handlerMap));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != serverSocket) {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    2,開啓服務器線程處理套接字

public class RPCServer {

    private final ExecutorService executorService = Executors.newCachedThreadPool();
while (true) {
                Socket socket = serverSocket.accept();
                executorService.execute(new ProcessorHandler(socket, handlerMap));
            }

           * 通過構造方法初始化套接字和服務綁定map

public class ProcessorHandler implements Runnable {

    private Socket socket;

    Map<String, Object> handlerMap;

    public ProcessorHandler(Socket socket, Map<String, Object> handlerMap) {
        this.socket = socket;
        this.handlerMap = handlerMap;
    }

           * 獲取套接字輸入流構建對象輸入流

           * 通過輸入流讀取傳輸對象

           * 解析對象參數, 執行請求方法並獲取結果

public void run() {
        // 處理請求
        ObjectInputStream inputStream = null;
        try {
            // 服務端處理客戶端請求代碼======================
            inputStream = new ObjectInputStream(socket.getInputStream());
            RPCRequest request = (RPCRequest) inputStream.readObject();
            Object result = invoke(request);

            // 服務端返回執行結果到客戶端代碼========================
            ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
            outputStream.writeObject(result);
            outputStream.flush();
            outputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != inputStream) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
/**
     * 通過執行執行客戶端請求方法
     */
    public Object invoke(RPCRequest request) {
        try {
            Object[] parameters = request.getParameters();
            Class<?>[] types = new Class[parameters.length];
            for (int i = 0; i < parameters.length; i++) {
                types[i] = parameters[i].getClass();
            }
            String version = request.getVersion();
            String serviceName = request.getClassName();
            if (null != version && !"".equals(version)) {
                serviceName = serviceName + "-" + version;
            }
            Object service = handlerMap.get(serviceName);
            Method method = service.getClass().getMethod(request.getMethodName(), types);
            return method.invoke(service, parameters);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

           * 根據套接字輸出流構建對象輸出流

           * 通過輸出流輸出服務執行結果(代碼片段)

// 服務端返回執行結果到客戶端代碼========================
            ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
            outputStream.writeObject(result);
            outputStream.flush();
            outputStream.close();

客戶端接收響應

    1,根據套接字輸入流構建對象輸入流(class RPCTransPort)

public Object send(RPCRequest request) {
        Socket socket = null;
        try {
            // 輸出服務請求參數到服務端代碼======================
            socket = newSocket();
            ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
            outputStream.writeObject(request);
            outputStream.flush();

            // 服務端響應結果代碼=========================
            ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
            Object result = inputStream.readObject();
            inputStream.close();
            outputStream.close();
            return result;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    2,根據對象輸入流讀取服務端返回執行結果

    3,處理返回結果

// 服務端響應結果代碼=========================
            ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
            Object result = inputStream.readObject();
            inputStream.close();
            outputStream.close();
            return result;

    4,請求結果

           * 從下圖可以看到,8081和8082端口各有出現;這是因爲服務端在註冊服務時模擬分佈式註冊多個服務節點,客戶端在處理服務節                 點時進行了簡易的基於隨機數的負載均衡,main方法中循環調用

           * 請求main方法分十次進行服務獲取,每一次都會隨機獲取8081或者8082端口服務,並進行調用

public class ClientController {
    public static void main(String[] args) {
        IDiscoveryService discoveryService = new DiscoveryService(ZKConfig.ZK_HOST);
        for (int i = 0; i < 10; i++) {
            RPCClientProxy rpcClientProxy = new RPCClientProxy(discoveryService, null);
            // 構建代理對象
            ISelfHelloService proxyService =
                    rpcClientProxy.clientProxy(ISelfHelloService.class);
            // 調用服務方法
            String result =  proxyService.sayHello("ZPJ");
            System.out.println(result);
        }
    }
}

三,實現多版本處理

    1,服務端暴露服務自定義註解上添加version屬性

@RpcAnnotation(value = ISelfHelloService.class, version = "1.0")
public class SelfHelloService implements ISelfHelloService {

    @Override
    public String sayHello(String msg) {
        return "[I'M 8081]HELLO : " + msg;
    }
}
@RpcAnnotation(value = ISelfHelloService.class, version = "2.0")
public class SelfHello2Service implements ISelfHelloService {

    @Override
    public String sayHello(String msg) {
        return "[I'M 8082]HELLO : " + msg;
    }
}

    2,服務綁定根據版本號分別綁定兩個服務並註冊到註冊中心

           * 修改main方法

public class ServerService {
    public static void main(String[] args) {
        ISelfHelloService helloService = new SelfHelloService();
        ISelfHelloService helloService2 = new SelfHello2Service();
        IRegisterService registerService = new RegisterService();
        // 初始化註冊中心和服務端口信息
        RPCServer server = new RPCServer(registerService, "127.0.0.1:8080");
        // 綁定服務
        server.bind(helloService, helloService2);
        // 發佈服務
        server.publishServer();
        System.out.println("服務發佈成功");
    }
}

          * 注意服務綁定處註冊的服務節點爲服務名和版本號的組合

public void bind(Object ... services) {
        for (Object service : services) {
            RpcAnnotation annotation = service.getClass().getAnnotation(RpcAnnotation.class);
            String serviceName = annotation.value().getName();
            String version = annotation.version();
            if (null != version && !"".equals(version)) {
                serviceName = serviceName + "-" + version;
            }
            // 綁定服務接口名稱對應的服務
            handlerMap.put(serviceName, service);
        }
    }

    3,客戶端構建動態代理時,逐層傳遞要請求的版本號到服務發送端(服務層代碼同上

             * RPCClientProxy第二個參數即爲version參數

public class ClientController {
    public static void main(String[] args) {
        IDiscoveryService discoveryService = new DiscoveryService(ZKConfig.ZK_HOST);
        for (int i = 1; i < 3; i++) {
            RPCClientProxy rpcClientProxy = new RPCClientProxy(discoveryService, i + ".0");
            // 構建代理對象
            ISelfHelloService proxyService =
                    rpcClientProxy.clientProxy(ISelfHelloService.class);
            // 調用服務方法
            String result =  proxyService.sayHello("ZPJ");
            System.out.println(result);
        }
    }
}

         * 再次附上RPCClientProxy代碼

public class RPCClientProxy {

    private IDiscoveryService discoveryService;

    private String version;

    public RPCClientProxy(IDiscoveryService discoveryService, String version) {
        this.discoveryService = discoveryService;
        this.version = version;
    }

    public <T> T clientProxy(final Class<T> interafaceCls) {
        return (T) Proxy.newProxyInstance(interafaceCls.getClassLoader(),
                new Class[]{interafaceCls}, new RemoteInvocationHandler(discoveryService, version));
    }
}

    4,服務端解析添加版本號處理(服務層代碼同上

             * 重複代碼, 爲了直觀

public Object invoke(RPCRequest request) {
        try {
            Object[] parameters = request.getParameters();
            Class<?>[] types = new Class[parameters.length];
            for (int i = 0; i < parameters.length; i++) {
                types[i] = parameters[i].getClass();
            }
            String version = request.getVersion();
            String serviceName = request.getClassName();
            if (null != version && !"".equals(version)) {
                serviceName = serviceName + "-" + version;
            }
            Object service = handlerMap.get(serviceName);
            Method method = service.getClass().getMethod(request.getMethodName(), types);
            return method.invoke(service, parameters);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

     5,執行結果

           * 服務端

           * 客戶端

          * zookeeper節點

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章