[Mrpc] Demo2 Server Sensing and Load Balancing Based on Zookeeper

Keywords: Zookeeper Java Apache Junit

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

Posted by Zeceer on Thu, 21 Mar 2019 15:57:51 -0700