【算法】【数据分片算法】----一致性hash算法【实战】基于Guava的一致性哈希算法,用于Netty分布式分片场景

前言

netty没有提供官方的分布式解决方法,当实战中用户量过大,并发过大,带宽不够用时,采用一致性哈希对netty做分布式处理。

下面实现基于Guava的一致性哈希算法,用于Netty分布式分片场景,将 50 万用户均匀分配到3台服务器节点。

一致性Hash算法实现(基于Guava)

一致性哈希管理器 ConsistentHashManager

package com.zgb.consistenthash;

import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import lombok.Getter;
import lombok.Setter;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * 一致性哈希管理器,用于Netty分布式分片
 * 基于Guava实现,负责将用户请求均匀分配到3台服务器节点
 */
@Component
public class ConsistentHashManager {

    /**
     * 虚拟节点数量,增加虚拟节点可以使哈希分布更均匀
     */
    private static final int VIRTUAL_NODES = 100;

    /**
     * 使用MD5哈希函数,提供较好的哈希分布特性
     */
    private final HashFunction hashFunction = Hashing.md5();

    /**
     * 环形哈希空间,存储虚拟节点的哈希值与实际服务器节点的映射关系
     * 采用TreeMap实现,便于通过tailMap寻找顺时针最近的节点
     */
    private final SortedMap<Long, ServerNode> circle = new TreeMap<>();

    /**
     * 初始化一致性哈希环,添加服务器节点
     *
     * @param serverNodes 服务器节点集合,在本场景下应为3个节点
     */
    public void init(Collection<ServerNode> serverNodes) {
        // 清空哈希环
        circle.clear();
        
        // 添加所有服务器节点及其虚拟节点到哈希环
        for (ServerNode serverNode : serverNodes) {
            addServerNode(serverNode);
        }
    }

    /**
     * 添加服务器节点到哈希环,包括其对应的虚拟节点
     *
     * @param serverNode 服务器节点对象
     */
    public void addServerNode(ServerNode serverNode) {
        // 为每个实际节点创建多个虚拟节点
        for (int i = 0; i < VIRTUAL_NODES; i++) {
            // 生成虚拟节点的唯一标识
            String virtualNodeName = serverNode.getNodeId() + "#" + i;
            // 计算虚拟节点的哈希值
            long hash = hash(virtualNodeName);
            // 将虚拟节点添加到哈希环
            circle.put(hash, serverNode);
        }
    }

    /**
     * 从哈希环中移除服务器节点,包括其对应的虚拟节点
     *
     * @param serverNode 服务器节点对象
     */
    public void removeServerNode(ServerNode serverNode) {
        // 移除该节点对应的所有虚拟节点
        for (int i = 0; i < VIRTUAL_NODES; i++) {
            String virtualNodeName = serverNode.getNodeId() + "#" + i;
            long hash = hash(virtualNodeName);
            circle.remove(hash);
        }
    }

    /**
     * 根据用户ID获取对应的服务器节点
     *
     * @param userId 用户唯一标识
     * @return 分配给该用户的服务器节点,如果哈希环为空则返回null
     */
    public ServerNode getServerNode(String userId) {
        if (circle.isEmpty()) {
            return null;
        }

        // 计算用户ID的哈希值
        long hash = hash(userId);

        // 如果哈希值不在环上,则获取大于该哈希值的子Map
        if (!circle.containsKey(hash)) {
            SortedMap<Long, ServerNode> tailMap = circle.tailMap(hash);
            // 如果子Map为空,则取哈希环的第一个节点(循环查找)
            hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();
        }

        // 返回对应的服务器节点
        return circle.get(hash);
    }

    /**
     * 计算字符串的哈希值
     *
     * @param key 要计算哈希值的字符串
     * @return 哈希值(长整型)
     */
    private long hash(String key) {
        // 使用MD5哈希函数计算哈希值,并转换为长整型
        return hashFunction.hashString(key, StandardCharsets.UTF_8).asLong();
    }

    /**
     * 服务器节点模型类,存储节点信息
     */
    @Getter
    @Setter
    public static class ServerNode {
        /**
         * 节点唯一标识
         */
        private String nodeId;
        
        /**
         * 节点IP地址
         */
        private String ip;
        
        /**
         * 节点端口号
         */
        private int port;

        /**
         * 构造函数
         *
         * @param nodeId 节点唯一标识
         * @param ip 节点IP地址
         * @param port 节点端口号
         */
        public ServerNode(String nodeId, String ip, int port) {
            this.nodeId = nodeId;
            this.ip = ip;
            this.port = port;
        }

        @Override
        public String toString() {
            return "ServerNode{" +
                    "nodeId='" + nodeId + '\'' +
                    ", ip='" + ip + '\'' +
                    ", port=" + port +
                    '}';
        }
    }
}

配置类 ConsistentHashConfig

package com.zgb.consistenthash;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Arrays;
import java.util.List;

/**
 * 一致性哈希配置类,用于初始化服务器节点
 */
@Configuration
public class ConsistentHashConfig {

    /**
     * 初始化一致性哈希管理器,并配置3台服务器节点
     *
     * @return 配置好的一致性哈希管理器
     */
    @Bean
    public ConsistentHashManager consistentHashManager() {
        ConsistentHashManager manager = new ConsistentHashManager();
        
        // 创建3个服务器节点
        ConsistentHashManager.ServerNode node1 = new ConsistentHashManager.ServerNode(
                "node-1", "192.168.1.101", 8080);
        ConsistentHashManager.ServerNode node2 = new ConsistentHashManager.ServerNode(
                "node-2", "192.168.1.102", 8080);
        ConsistentHashManager.ServerNode node3 = new ConsistentHashManager.ServerNode(
                "node-3", "192.168.1.103", 8080);
        
        // 初始化哈希环
        List<ConsistentHashManager.ServerNode> nodes = Arrays.asList(node1, node2, node3);
        manager.init(nodes);
        
        return manager;
    }
}

使用示例 UserShardingService

package com.zgb.service;

import com.zgb.consistenthash.ConsistentHashManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 用户分片服务,用于将用户分配到不同的服务器节点
 */
@Service
public class UserShardingService {

    @Autowired
    private ConsistentHashManager consistentHashManager;

    /**
     * 根据用户ID获取用户所在的服务器节点
     *
     * @param userId 用户唯一标识
     * @return 服务器节点信息,包含IP和端口
     */
    public ConsistentHashManager.ServerNode getUserServerNode(String userId) {
        return consistentHashManager.getServerNode(userId);
    }

    /**
     * 计算50万用户的分片分布情况,用于验证哈希分布均匀性
     *
     * @return 各节点的用户数量分布
     */
    public int[] calculateUserDistribution() {
        int[] counts = new int[3]; // 索引0: node-1, 1: node-2, 2: node-3
        
        // 模拟50万用户
        for (int i = 1; i <= 500000; i++) {
            String userId = "user_" + i;
            ConsistentHashManager.ServerNode node = consistentHashManager.getServerNode(userId);
            
            // 统计各节点的用户数量
            if ("node-1".equals(node.getNodeId())) {
                counts[0]++;
            } else if ("node-2".equals(node.getNodeId())) {
                counts[1]++;
            } else if ("node-3".equals(node.getNodeId())) {
                counts[2]++;
            }
        }
        
        return counts;
    }
}

实现说明

  1. 核心原理

    • 使用Guava的HashFunction实现哈希计算,采用MD5算法
    • 通过虚拟节点(每个实际节点对应100个虚拟节点)提高哈希分布均匀性
    • 采用TreeMap实现环形哈希空间,支持高效查找
  2. 主要组件

    • ConsistentHashManager:核心类,实现一致性哈希的添加、移除节点和查找功能
    • ServerNode:内部类,封装服务器节点信息
    • ConsistentHashConfig:配置类,初始化3个服务器节点
    • UserShardingService:业务服务类,提供用户分片功能
  3. 使用方式

    • 通过getUserServerNode(String userId)方法获取用户对应的服务器节点
    • calculateUserDistribution()方法可验证50万用户在3个节点上的分布情况

该实现具有良好的扩展性,当需要增加或减少服务器节点时,只会影响少量用户的分片结果,符合分布式系统的高可用需求。

如果文章对你有一点点帮助,欢迎【点赞、留言、+ 关注】
您的关注是我创作的动力!若有疑问/交流/需求,欢迎留言/私聊!
多一个朋友多一条路!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值