自定义负载均衡算法实现环境隔离

实际开发过程中的问题:

公共环境中多个人注册在同一个 eureka 集群中,根据负载均衡原则,消费者会获取所有的服务提供者对其中一个发起调用,某用户本来想只调用自己的提供者进行 debug,结果调用窜到了其他人注册的提供者上,影响开发效率.

约定名词:

  1. routingTag: 路由键,根据该值决定请求发送到哪个服务提供者分片.
  2. 分片: 具有相同 routingKe的一组服务,包括提供者和消费者,目的就是想做到消费者和提供者在同一个分片.

方案一

image.png

优点:方便简单

缺点: 1. 每次本地调试都有改动,一旦提交,代码合并冲突,对开发者不友好

方案二

自己定制开发负载均衡策略来实现

优点: 不用改动

缺点: 需要自己开发

为了长远的效率考虑选择方案二

思路
  1. 提供者在配置文件注明自己的分片,消费者也标明自己的分片,
  2. 获取所有提供者的信息,根据分片信息进行分组过滤,获取到同一个分片的一组提供者,对其发起调用

Eureka 已经提供了metadata-map 来自定义元数据:

eureka.instance.metadata-map

实现

  1. 在服务的消费者和提供者的元信息中定义 eureka.instance.metadata-map.routingTag=ABC
    PS 这个ABC可以设置成从机器获得唯一标识.
    2019年08月06日更新 我项目中使用 ${user.name}-${spring.cloud.client.ipAddress}
  2. 仿照 com.netflix.loadbalancer.RandomRule继承AbstractLoadBalancerRule
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.ReflectUtil;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.netflix.eureka.EurekaDiscoveryClient;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static java.util.stream.Collectors.groupingBy;

public class TagRule extends AbstractLoadBalancerRule {
    @Autowired
    DiscoveryClient discoveryClient;
    @Value("${eureka.instance.metadata-map.routingTag}")
    String routingTag;


    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {

    }

    @Override
    public void setLoadBalancer(ILoadBalancer lb) {
        super.setLoadBalancer(lb);
    }

    @Override
    public Server choose(Object key) {
        ILoadBalancer loadBalancer = getLoadBalancer();
        if (loadBalancer == null) {
            return null;
        }
        Server resultServer = null;

        while (resultServer == null) {
            if (Thread.interrupted()) {
                return null;
            }
            //获取所有服务提供者信息
            List<Server> allServers = loadBalancer.getAllServers();
            if (allServers.size() == 0) {
                return null;
            } else {
                resultServer = allServers.get(0);
                //获取提供者的实例信息
                List<ServiceInstance> instances = discoveryClient.getInstances(resultServer.getMetaInfo().getServiceIdForDiscovery());
                //根据实例中 metadata.routingTag进行分组
                Map<String, List<ServiceInstance>> collect = instances.stream().collect(groupingBy(s -> s.getMetadata().get("routingTag")));
                //获取与当前消费者在同一个分片的服务生产者 by routingTag
                List<ServiceInstance> serviceInstances = collect.get(routingTag);
                List<Server> sameTagServers = new ArrayList<>();
                for (ServiceInstance serviceInstance : serviceInstances) {
                    for (Server server : allServers) {
                        InstanceInfo instanceInfo = (InstanceInfo) ReflectUtil.getFieldValue(server, "instanceInfo");
                        if (instanceInfo.getInstanceId().equalsIgnoreCase(((EurekaDiscoveryClient.EurekaServiceInstance) serviceInstance).getInstanceInfo().getInstanceId())) {
                            sameTagServers.add(server);
                        }
                    }
                }
                //随机选出一个
                if (CollUtil.isNotEmpty(sameTagServers)) {
                    resultServer = sameTagServers.get(RandomUtil.randomInt(0,sameTagServers.size()));
                }

            }
            if (resultServer == null) {
                /*
                 * The only time this should happen is if the server list were
                 * somehow trimmed. This is a transient condition. Retry after
                 * yielding.
                 */
                Thread.yield();
                continue;
            }

            if (resultServer.isAlive()) {
                return (resultServer);
            }

            // Shouldn't actually happen.. but must be transient or a bug.
            resultServer = null;
            Thread.yield();
        }
        System.out.println("选中的服务是:" + resultServer);
        return resultServer;
    }

}


  1. 配置中启用该 rule

    @Bean
    @Scope("prototype")//这个注解不能少,否则快速调用几次就会出现com.netflix.client.ClientException: Load balancer does not have available server for client 异常
    public IRule tagRule() {
        return new TagRule();
    }

  1. 测试,服务提供者配置一个和消费者相同的routingTag,服务提供者再配置多个不同的进行验证
image.png

image.png

如图:
user是服务提供者,
message 是服务消费者.
8000是 eureka
9001的 routingTag 是 ABC
9999和 9998 的 routingTag 是 www
8002的 routingTag 是 www
8001是 为未启用 TagRule
效果如下:tab1 是 8002 端口,tab2 是 8001 端口

load-balancer.gif

完整代码地址: GitHub

不完善的地方就是同一个分片中的服务是随机选择的,不过一般用于调试也不会在本地启用很多实例来搞自己.
下一篇解决一下 MQ 乱窜的问题.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271