基於Zookeeper註冊中心實現簡易手寫RPC框架
一,環境準備
- Zookepper單點或集羣環境,演示使用集羣環境
- Zookeeper的Curator客戶端工具
- 使用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節點