Because servers are often maintained, it is not good to write the address of the service implementation server directly in the client (including the configuration file). A better way is to register your address in the ZooKeeper cluster when the server starts up, and then the client can get a list of all servers when it starts up, and choose a suitable server to serve itself.
This solution not only solves the problem of server address acquisition, but also solves the problem of load balancing.
Take a look at the corresponding code. Project source code has been uploaded to http://download.csdn.net/detail/mrbcy/9747568
package tech.mrbcy.mrpc.demo.demo2; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.Watcher.Event.EventType; import org.apache.zookeeper.ZooDefs.Ids; import org.apache.zookeeper.data.Stat; public class ServerAddrHelper { public static final String DEFAULT_GROUP_NAME = "/MrpcServers"; private String connString; private String groupName; private ZooKeeper zk; private ServerChangeListener listener; private static int sessionTimeout = 2000; public ServerAddrHelper(String connString){ this.connString = connString; this.groupName = DEFAULT_GROUP_NAME; } /** * * @param connString zk Connection string * @param groupName Parent node path, located under / with / example value: "/ MrpcServers" */ public ServerAddrHelper(String connString, String groupName){ this.connString = connString; if(!groupName.startsWith("/")){ groupName = "/" + groupName; } this.groupName = groupName; } /** * Register servers with ZooKeeper cluster * @param registPath Server node path, example value "server" * @param address Server address and port number for client connection * @throws Exception Connection server failed */ public void registServer(String registPath, String address) throws Exception{ zk = new ZooKeeper(connString,sessionTimeout,null); // Determine whether the parent directory exists or not, and create if it does not exist Stat groupStat = zk.exists(groupName, false); if(groupStat == null){ zk.create(groupName, "Mrpc server list".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } // Register Server if(!registPath.startsWith("/")){ registPath = "/" + registPath; } String registAddr = zk.create(groupName+registPath, address.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); System.out.println("Server is starting, reg addr: " + registAddr); } /** * Discovery Server * @param listener The listener, if not null, receives notification when the server list changes * @return * @throws Exception */ public List<String> discoverServers(ServerChangeListener listener) throws Exception{ this.listener = listener; zk = new ZooKeeper(connString,sessionTimeout,new Watcher(){ public void process(WatchedEvent event) { if(event.getType() == EventType.NodeChildrenChanged){ // Server list changed try { List<String> servers = getServerList(); if(ServerAddrHelper.this.listener != null){ ServerAddrHelper.this.listener.onChange(servers); } } catch (Exception e) { e.printStackTrace(); } } } }); return getServerList(); } private List<String> getServerList() throws Exception { zk.getChildren(groupName, true); List<String> children = zk.getChildren(groupName, true); List<String> servers = new ArrayList<String>(); for(String child : children) { byte[] data = zk.getData(groupName+"/"+child, null, null); servers.add(new String(data)); } return servers; } }
Generally speaking, registServer is called by the server-side framework and registers itself with the zk cluster. Discovery Server is invoked with the client framework. Gets a list of currently available servers. It also provides a listening interface when the server list changes.
In addition, a load balancer is implemented, which uses the simplest random selection algorithm to select servers. Further improvements can be made, such as selecting servers based on the current load on the server. The code is as follows:
package tech.mrbcy.mrpc.demo.demo2; import java.net.InetSocketAddress; import java.util.List; import java.util.Random; public class ServerLoadBalancer { /** * Select a server * @param servers Server list sample values: 133.122.5.88:8888 or anode 2:5884 * @return Connect server address */ public static InetSocketAddress chooseServer(List<String> servers){ if(servers == null || servers.size() == 0){ return null; } // Random selection of a server String serverAddr = servers.get(0); if(servers.size() > 1){ int index = new Random().nextInt(servers.size()); serverAddr = servers.get(index); } String[] addrAndPort = serverAddr.split(":"); if(addrAndPort.length != 2){ throw new RuntimeException("Illegal server Address:" + serverAddr); } return new InetSocketAddress(addrAndPort[0], Integer.parseInt(addrAndPort[1])); } }
Finally, the code of a test class is given. See the uploaded project code for other details.
package tech.mrbcy.mrpc.demo.demo2; import java.net.InetSocketAddress; import java.util.List; import org.junit.Test; public class MockClient { private InetSocketAddress serverAddress; @Test public void testClient(){ ServerAddrHelper serverHelper = new ServerAddrHelper("amaster:2181,anode1:2181,anode2:2181"); ServerAddrHelper helper = new ServerAddrHelper("amaster:2181,anode1:2181,anode2:2181"); try { serverHelper.registServer("ServiceImplServer", "localhost:10000"); List<String> serverList = helper.discoverServers(new ServerChangeListener() { public void onChange(List<String> servers) { System.out.println("The list of servers has changed. The current list of servers is:"); System.out.println(servers); changeToServer(servers); } }); System.out.println(serverList); if(serverList == null || serverList.size() == 0){ System.out.println("No servers are available"); } changeToServer(serverList); Thread.sleep(1000); serverHelper.registServer("ServiceImplServer", "localhost:10001"); Thread.sleep(1000); serverHelper.registServer("ServiceImplServer", "localhost:10002"); // During this period, you can manually delete the connected servers and test the severity of the servers. Thread.sleep(50000); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } private void changeToServer(List<String> servers) { if(servers == null || servers.size() == 0){ return; } // Not specifying the server address or the original server has failed to migrate to a new server boolean valid = false; if(servers.size() > 0 && serverAddress != null){ for(String server:servers){ if(server.equals(serverAddress.getHostString() + ":" + serverAddress.getPort())){ valid = true; break; } } } if(serverAddress == null || !valid){ serverAddress = ServerLoadBalancer.chooseServer(servers); System.out.println("Not specifying the server address or the original server has failed to migrate to a new server:" + serverAddress.getHostString() + ":" + serverAddress.getPort()); } } }
The output results are as follows:
Server is starting, reg addr: /MrpcServers/ServiceImplServer0000000030 [localhost:10000] Without specifying the server address or the original server has expired, migrate to the new server: localhost:10000 Server is starting, reg addr: /MrpcServers/ServiceImplServer0000000031 The list of servers has changed. The current list of servers is: [localhost:10001, localhost:10000] Server is starting, reg addr: /MrpcServers/ServiceImplServer0000000032 The list of servers has changed. The current list of servers is: [localhost:10001, localhost:10002, localhost:10000] The list of servers has changed. The current list of servers is: [localhost:10001, localhost:10002] If no server address is specified or the original server has failed, migrate to the new server: localhost:10002