在看此篇內容時需要瀏覽下面內容 netty實戰--手寫rpc框架
前文功能簡介以及功能擴充
利用netty來實現一個點對點的rpc調用。客戶端和服務端都是靠手寫地址進行socket同學的,無法1對多,也無法把服務拆分到不同的機器上進行壓力分攤,做不到調用的時候水平擴展。現實的場景是機器單臺作戰能力比較差勁,但是機器多,可以把不同的運算交給不同的機器來做,這樣達到機器的利用。所以有了如下的改造。
- 支持服務水平擴展。
- 支持不同的服務分佈在不同的機器上。
這時候就需要引入zookeeper來做管理。
主體思路
- 上線的數據都寫入到臨時節點裏
- 通過臨時節點來確保,服務器停止之後,節點消失
- 自動發現服務的功能,依靠zk的Watcher來發現服務的上線和下線功能。
數據組織形式
zk數據
/xp
|------ip:port---services
|
|------ip:port---services
|
|------ip:port---services
zk的數據按照如上情況進行組織,xp爲一個永久節點,下面的ip:port組成一個臨時節點。分別代表服務的ip和port,當服務停止或者意外被殺以後,對應的ip:host就會消失。
xp是一個永久節點,我們對他進行watch,只要有新的服務啓動或者停止都會收到事件。
client維護的結構
|-service---client
|
|-service---client
client維護的結構是service和client連接的對應,一般情況下都是一個server裏有多個服務,水平擴展的時候啓動多個server即可。所以組織的時候是按照service組織方便查找。
代碼實現要點
客戶端對永久節點進行watch,這裏加入了一個callback,當發生了節點變化的時候重新更新消息。
public List<String> getInofAndWatcher(final String path, final InfoCallBack callBack) throws Exception {
List<String> nodeList = zk.getChildren(path, new Watcher() {
@Override
public void process(WatchedEvent event) {
if (event.getType() == Event.EventType.NodeChildrenChanged) {
try {
List<String> nodeList = zk.getChildren(path, false);
callBack.getLastList(nodeList);
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
return nodeList;
}
發佈到zk的信息,就是ip,port,service。
public class ServiceInfo {
private String ip;
private int port;
private List<String> interfaces;
}
會把這些數據通過json格式寫入到zk,這裏需要寫明文,方便查看zk下有哪些服務
public void addService(ServiceInfo info) {
zkUtil.create(CommonContext.PATH + "/" + info.toString(), JSONObject.toJSONString(info).getBytes());
}
客戶端在啓動或者事件發生的時候進行service更替。
public void getLastList(List<String> lists) {
Map<String, Set<Client>> serviceTmp = new HashMap<>();
if (lists != null && !lists.isEmpty()) {
for (String node : lists) {
String info;
try {
info = new String(zkUtil.getData(CommonContext.PATH + "/" + node));
ServiceInfo parse = JSONObject.parseObject(info, ServiceInfo.class);
String address = parse.toString();
List<String> interfaces = parse.getInterfaces();
if (interfaces != null && !interfaces.isEmpty()) {
for (String service : interfaces) {
Set<Client> set = serviceTmp.get(service);
if (set == null) {
set = new HashSet<Client>();
}
if (!set.contains(address)) {
Client client = new Client();
client.connect(parse.getIp(), parse.getPort());
set.add(client);
}
serviceTmp.put(service, set);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
services = serviceTmp;
}
數據發送的時候選擇隨機找服務,這樣不至於壓力都在一個服務上。
public static void send(RpcRequest request, String className) {
Set<Client> set = services.get(className);
if (set != null && !set.isEmpty()) {
Client[] array = set.toArray(new Client[0]);
//random
Client client = array[new Random().nextInt(array.length)];
client.write(request);
}
}
測試使用
客戶端
new Observer("127.0.0.1:2181");
ITest proxy = ProxyInterface.getProxy(ITest.class);
String message = proxy.getMessage();
System.out.println(message);
服務端
ITest test= new TestImpl();
Invoker.put(test);
List<String> list = new ArrayList<String>(Invoker.getServices());
ServiceInfo info = new ServiceInfo("127.0.0.1", 6161, list);
Publisher pub = new Publisher("127.0.0.1:2181");
pub.addService(info);
Server.bind(6161);
總結
利用zk的註冊功能,讓服務互相發現,並且做到水平擴展。重點就是一個事件機制和臨時節點的機制。代碼如下 https://github.com/xpbob/lightrpc