运用前边写的RPC demo,结合zookeeper实现服务注册与发现、负载均衡(轮询)
本例模拟了user,order,good三个服务,每个服务有2~3个地址,数据只存放在java容器中未和数据库交互,未使用任何成熟框架(后面会用spring cloud实现真实些的项目),配置未实现热加载,后续可能会写关于热加载的东西。。过程中尽量实现基于配置的自动化,避免写死一些东西。
本人电脑很辣鸡内存只有2G,虚拟机卡爆,只能在Windows下操作,且只有这一台电脑,只能用不同端口模拟不同的主机,所以ip都是本地,本例未用到分布式锁,因为用了也测不出效果,后续会基于zookeeper写个简单的。代码较多,只贴出重点。
- 服务注册代码
public static void regServer(String serverAddressKey) {
//LoadConfig为读取文件的配置文件工具类
String rigCenterAddress = LoadConfig.getConfigValue("server_rigister_center");
String[] rigCenterPathArr = rigCenterAddress.split(":");
if(rigCenterPathArr.length != 2
|| rigCenterPathArr[0] == null || rigCenterPathArr[1] == null){
throw new RuntimeException("获取的注册中心地址有误");
}
ZkClient zkClient = new ZkClient(rigCenterAddress, 5000, 2000);
//若不存在根节点先创建根节点,持久节点
String rootPath = ExposeAll.rootPath;
if (!zkClient.exists(rootPath)) {
zkClient.createPersistent(rootPath,rigCenterAddress);
}
//若不存在相应服务父节点,则先创建,持久节点
String serverType = serverAddressKey.substring(0, serverAddressKey.indexOf("_"));
String serverPath = rootPath + LoadConfig.getConfigValue(serverType + "_server_name");
System.out.println("serverPath:"+serverPath);
if (!zkClient.exists(serverPath)) {
//其子节点才携带具体地址
zkClient.createPersistent(serverPath);
}
String serverAddress = LoadConfig.getConfigValue(serverAddressKey);
String[] addressArr = serverAddress.split(":");
if(addressArr.length != 2
|| addressArr[0] == null || addressArr[1] == null){
throw new RuntimeException("获取" + serverAddress + "地址有误");
}
//若不存在相应服务节点,则先删除再创建,临时节点
String nodePath = serverPath + "/" + serverAddressKey;
if (zkClient.exists(nodePath)) {
zkClient.delete(nodePath);
}
zkClient.createEphemeral(nodePath, serverAddress);
System.out.println(nodePath + "服务注册成功");
}
- 服务发现与负载均衡(轮询)
package testZookeeper.testZKServer;
import org.junit.Test;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.Socket;
import java.util.*;
public class ServiceProxyFactory {
//private static Class consumer;
public static Object getService(Class serviceClazz){
return Proxy.newProxyInstance(serviceClazz.getClassLoader(), new Class[]{serviceClazz},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object back = null;
String[] path = getPath(serviceClazz);
if(null == path || path.length != 2
|| path[0] == null || path[1] == null){
throw new RuntimeException("获取的地址有误");
}
System.out.println((Arrays.asList(path)));
String ip = path[0];
int port;
try {
port = Integer.parseInt(path[1]);
} catch (NumberFormatException e) {
throw new RuntimeException("获取的端口有误");
}
ObjectOutputStream objOutStream = null;
Socket client;
try {
client = new Socket(ip,port);
objOutStream = new ObjectOutputStream(client.getOutputStream());
objOutStream.writeUTF(method.getName());
objOutStream.writeObject(method.getParameterTypes());
objOutStream.writeObject(args);
try (ObjectInputStream objInStream = new ObjectInputStream(client.getInputStream())) {
back = objInStream.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
finally {
try {
if(null != objOutStream)
objOutStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return back;
}
});
}
/**
* 根据配置去注册中心查找服务地址
* @return ip和端口
*/
private static String[] getPath(Class consumer){
try {
//用负载均衡算法获取具体服务地址
String ipAndPort = getServerAddress(consumer);
//为简化,假设ip和端口格式合法,暂不做格式校验
System.out.println("具体服务地址为:" + ipAndPort);
return ipAndPort.split(":");
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
// 服务调用次数
private static int count = 1;
//客户端实现轮询
private static String getServerAddress(Class consumer) {
//serversMap;
System.out.println("App.serversMap=" + App.serversMap);
List<String> serverList = App.serversMap.get(getFatherServerNode(consumer));
String balancingAlgorithms = LoadConfig.getConfigValue("load_balancing_algorithms");
//轮询算法
if("polling".equals(balancingAlgorithms)){
String serverAddress = serverList.get(count % serverList.size());
count++;
return serverAddress;
}
//其他情况,取首
return serverList.get(0);
}
@Test
public void test(){
System.out.println(App.serversMap.get(getFatherServerNode(GoodService.class)));
}
private static String getFatherServerNode(Class consumerClazz) {
String clazzName = consumerClazz.getName();
String serverName = clazzName.substring(clazzName.lastIndexOf(".")+1);
String serverKey = serverName.substring(0,serverName.indexOf("Service")).toLowerCase() + "_server_name";
return LoadConfig.getConfigValue(serverKey).replace("/","");
}
}
- 配置文件
#注册中心根路径
root_node_name=/rigister_center
#注册中心地址
server_rigister_center=127.0.0.1:2181
#user服务父节点路径
user_server_name=/user_server_address
#user服务主机地址1
user_server_address_1=127.0.0.1:6060
#user服务主机地址2
user_server_address_2=127.0.0.1:6061
#user服务主机地址3
user_server_address_3=127.0.0.1:6062
#order服务父节点路径(相对根节点)
order_server_name=/order_server_address
#order服务主机地址1
order_server_address_1=127.0.0.1:7070
#order服务主机地址2
order_server_address_2=127.0.0.1:7071
#good服务父节点路径(相对根节点)
good_server_name=/good_server_address
#good服务主机地址1
good_server_address_1=127.0.0.1:9090
#good服务主机地址2
good_server_address_2=127.0.0.1:9091
#负载均衡算法类型,本例只实现了一中
load_balancing_algorithms=polling
- zokeeper节点结构
- 所有代码截图,没有模块化,时间有限请谅解