Skip to content

Latest commit

 

History

History
218 lines (153 loc) · 6.28 KB

67.分布式高阶实战:Netty节点的负载均衡.md

File metadata and controls

218 lines (153 loc) · 6.28 KB

67 分布式高阶实战:Netty节点的负载均衡

什么是负载均衡?

防止单个Netty服务器负载过大,而导致服务不可用,因为搭建了Netty服务的集群,理想的情况下,是每个Netty服务节点都处理连接的数量都大致均衡,当然,如果服务器配置不一样,比如有某些服务器的配置相对较高,可以把更多的流量打到配置高的服务器上,这依赖于具体的负载均衡策略或者算法。

在IM集群中,当用户登录成功之后,短连接网关需要返回给用户一个最佳的Netty节点的地址,让用户来建立Netty长链接。

负载均衡就是为了给用户挑选出一个最佳的Netty节点的地址。

如何实现负载均衡策略呢

ImLoadBalance实现简易的负载均衡策略:

大致思路:

  • 找出指定路径下所有的字节点信息
  • ImWorker中存储了一个balance字段用来记录当前Netty节点的连接数量
  • 所有的节点按照balance从小到大排序,返回连接数最小的那个节点信息即可
@Data
@Slf4j
public class ImLoadBalance {

    //Zk客户端
    private CuratorFramework client = null;
    private String managerPath;

    public ImLoadBalance(CuratorZKclient curatorZKClient) {
        this.client = curatorZKClient.getClient();
        managerPath = ServerConstants.MANAGE_PATH;
    }

    /**
     * 获取负载最小的IM节点
     *
     * @return
     */
    public ImNode getBestWorker() {
        List<ImNode> workers = getWorkers();
        log.info("全部节点如下:");
        workers.stream().forEach(node ->
        {
            log.info("节点信息:{}", JsonUtil.pojoToJson(node));
        });
        ImNode best = balance(workers);
        return best;
    }

    /**
     * 按照负载排序
     *
     * @param items 所有的节点
     * @return 负载最小的IM节点
     */
    protected ImNode balance(List<ImNode> items) {
        if (items.size() > 0) {
            // 根据balance值由小到大排序
            Collections.sort(items);
            // 返回balance值最小的那个
            ImNode node = items.get(0);
            log.info("最佳的节点为:{}", JsonUtil.pojoToJson(node));
            return node;
        } else {
            return null;
        }
    }


    /**
     * 从zookeeper中拿到所有IM节点
     */
    public List<ImNode> getWorkers() {
        List<ImNode> workers = new ArrayList<ImNode>();
        List<String> children = null;
        try {
            children = client.getChildren().forPath(managerPath);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

        for (String child : children) {
            log.info("child:", child);
            byte[] payload = null;
            try {
                payload = client.getData().forPath(managerPath + "/" + child);

            } catch (Exception e) {
                e.printStackTrace();
            }
            if (null == payload) {
                continue;
            }
            ImNode node = JsonUtil.jsonBytes2Object(payload, ImNode.class);
            node.setId(getIdByPath(child));
            workers.add(node);
        }
        return workers;
    }

    /**
     * 取得IM 节点编号
     *
     * @param path 路径
     * @return 编号
     */
    public long getIdByPath(String path) {
        String sid = null;
        if (null == path) {
            throw new RuntimeException("节点路径有误");
        }
        int index = path.lastIndexOf(ServerConstants.PATH_PREFIX_NO_STRIP);
        if (index >= 0) {
            index += ServerConstants.PATH_PREFIX_NO_STRIP.length();
            sid = index <= path.length() ? path.substring(index) : null;
        }

        if (null == sid) {
            throw new RuntimeException("节点ID获取失败");
        }

        return Long.parseLong(sid);

    }

    /**
     * 从zookeeper中删除所有IM节点
     */
    public void removeWorkers() {
        try {
            client.delete().deletingChildrenIfNeeded().forPath(managerPath);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

如何测试负载均衡

这里有一个单元测试:

 @Test
public void testGetBestWorker() throws Exception {
    ImNode bestWorker = imLoadBalance.getBestWorker();
    System.out.println("bestWorker = " + bestWorker);
    ThreadUtil.sleepSeconds(Integer.MAX_VALUE);
}

常见的负载均衡策略

轮训策略

轮训是一种非常简单的实现方式,依次把请求分配给可用服务

优点:

实现比较简单,有很多开源中间件都有实现,比如Ribbon默认的策略就是轮训策略

还有一个优点就是,请求能够比较平均的分摊到可用服务上面

缺点:

请求均匀分配,因为后端服务器可能存在性能差异,说白了,就是有些服务器配置好,有些配置一般。

可能会造成某些机器资源利用率低的问题。

加权轮训

加权本质是一种带有优先级的方式,加权轮训就是一种改进的轮训算法,轮训算法是权值相等的加权轮训。

需要给后端每个服务器设置不同的权重,决定分配的请求书的比例,这个算法应用就比较广泛,对于无状态的负载场景,非常适用。

优点:

解决了服务器性能不一的情况

缺点:

权重值需要静态配置无法动态更新或者自动调节,也不适合对长链接和命中率有要求的场景

随机

随机把请求分配给后端服务器,请求分配的均匀程度依赖于随机算法,实现简单,常常用于配合处理一些极端的情况,比如,热点请求,这个时候就可以随机分配到任意可用的后端,以分散热点。

哈希算法

这个其实比较常见,也比较常用。 包括分布式场景下,比较常见的一致性哈希算法。

最小连接数

把请求分配给活动连接数量最少的后端服务器,它通过活动来估计服务器的负载,比较智能,但是需要维护后端服务器的连接列表。

加权最小连接数

在最小连接数的基础上,考虑权重分配。

最短响应时间

一定时间内,记录每个节点的最短响应时间,那个节点响应时间最短,就把请求负载到这个节点上面。