前文功能简介以及功能扩充在看此篇内容时需要浏览下面内容
netty实战--手写rpc框架
利用netty来实现一个点对点的rpc调用。客户端和服务端都是靠手写地址进行socket同学的,无法1对多,也无法把服务拆分到不同的机器上进行压力分摊,做不到调用的时候水平扩展。现实的场景是机器单台作战能力比较差劲,但是机器多,可以把不同的运算交给不同的机器来做,这样达到机器的利用。所以有了如下的改造。
- 支持服务水平扩展。
- 支持不同的服务分布在不同的机器上。
这时候就需要引入zookeeper来做管理。
主体思路- 上线的数据都写入到临时节点里
- 通过临时节点来确保,服务器停止之后,节点消失
- 自动发现服务的功能,依靠zk的Watcher来发现服务的上线和下线功能。
/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
原创首发于慕课网