自定義負(fù)載均衡算法實(shí)現(xiàn)環(huán)境隔離

實(shí)際開發(fā)過程中的問題:

公共環(huán)境中多個(gè)人注冊(cè)在同一個(gè) eureka 集群中,根據(jù)負(fù)載均衡原則,消費(fèi)者會(huì)獲取所有的服務(wù)提供者對(duì)其中一個(gè)發(fā)起調(diào)用,某用戶本來想只調(diào)用自己的提供者進(jìn)行 debug,結(jié)果調(diào)用竄到了其他人注冊(cè)的提供者上,影響開發(fā)效率.

約定名詞:

  1. routingTag: 路由鍵,根據(jù)該值決定請(qǐng)求發(fā)送到哪個(gè)服務(wù)提供者分片.
  2. 分片: 具有相同 routingKe的一組服務(wù),包括提供者和消費(fèi)者,目的就是想做到消費(fèi)者和提供者在同一個(gè)分片.

方案一

image.png

優(yōu)點(diǎn):方便簡單

缺點(diǎn): 1. 每次本地調(diào)試都有改動(dòng),一旦提交,代碼合并沖突,對(duì)開發(fā)者不友好

方案二

自己定制開發(fā)負(fù)載均衡策略來實(shí)現(xiàn)

優(yōu)點(diǎn): 不用改動(dòng)

缺點(diǎn): 需要自己開發(fā)

為了長遠(yuǎn)的效率考慮選擇方案二

思路
  1. 提供者在配置文件注明自己的分片,消費(fèi)者也標(biāo)明自己的分片,
  2. 獲取所有提供者的信息,根據(jù)分片信息進(jìn)行分組過濾,獲取到同一個(gè)分片的一組提供者,對(duì)其發(fā)起調(diào)用

Eureka 已經(jīng)提供了metadata-map 來自定義元數(shù)據(jù):

eureka.instance.metadata-map

實(shí)現(xiàn)

  1. 在服務(wù)的消費(fèi)者和提供者的元信息中定義 eureka.instance.metadata-map.routingTag=ABC
    PS 這個(gè)ABC可以設(shè)置成從機(jī)器獲得唯一標(biāo)識(shí).
    2019年08月06日更新 我項(xiàng)目中使用 ${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;
            }
            //獲取所有服務(wù)提供者信息
            List<Server> allServers = loadBalancer.getAllServers();
            if (allServers.size() == 0) {
                return null;
            } else {
                resultServer = allServers.get(0);
                //獲取提供者的實(shí)例信息
                List<ServiceInstance> instances = discoveryClient.getInstances(resultServer.getMetaInfo().getServiceIdForDiscovery());
                //根據(jù)實(shí)例中 metadata.routingTag進(jìn)行分組
                Map<String, List<ServiceInstance>> collect = instances.stream().collect(groupingBy(s -> s.getMetadata().get("routingTag")));
                //獲取與當(dāng)前消費(fèi)者在同一個(gè)分片的服務(wù)生產(chǎn)者 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);
                        }
                    }
                }
                //隨機(jī)選出一個(gè)
                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("選中的服務(wù)是:" + resultServer);
        return resultServer;
    }

}


  1. 配置中啟用該 rule

    @Bean
    @Scope("prototype")//這個(gè)注解不能少,否則快速調(diào)用幾次就會(huì)出現(xiàn)com.netflix.client.ClientException: Load balancer does not have available server for client 異常
    public IRule tagRule() {
        return new TagRule();
    }

  1. 測(cè)試,服務(wù)提供者配置一個(gè)和消費(fèi)者相同的routingTag,服務(wù)提供者再配置多個(gè)不同的進(jìn)行驗(yàn)證
image.png

image.png

如圖:
user是服務(wù)提供者,
message 是服務(wù)消費(fèi)者.
8000是 eureka
9001的 routingTag 是 ABC
9999和 9998 的 routingTag 是 www
8002的 routingTag 是 www
8001是 為未啟用 TagRule
效果如下:tab1 是 8002 端口,tab2 是 8001 端口

load-balancer.gif

完整代碼地址: GitHub

不完善的地方就是同一個(gè)分片中的服務(wù)是隨機(jī)選擇的,不過一般用于調(diào)試也不會(huì)在本地啟用很多實(shí)例來搞自己.
下一篇解決一下 MQ 亂竄的問題.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖闹伪,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡跋核,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門叛买,熙熙樓的掌柜王于貴愁眉苦臉地迎上來砂代,“玉大人,你說我怎么就攤上這事率挣】桃粒” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵椒功,是天一觀的道長捶箱。 經(jīng)常有香客問我,道長动漾,這世上最難降的妖魔是什么丁屎? 我笑而不...
    開封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮旱眯,結(jié)果婚禮上晨川,老公的妹妹穿的比我還像新娘。我一直安慰自己删豺,他們只是感情好共虑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著吼鳞,像睡著了一般看蚜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上赔桌,一...
    開封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天供炎,我揣著相機(jī)與錄音渴逻,去河邊找鬼。 笑死音诫,一個(gè)胖子當(dāng)著我的面吹牛惨奕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播竭钝,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼梨撞,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了香罐?” 一聲冷哼從身側(cè)響起卧波,我...
    開封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎庇茫,沒想到半個(gè)月后港粱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡旦签,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年查坪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宁炫。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡偿曙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出羔巢,到底是詐尸還是另有隱情望忆,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布朵纷,位于F島的核電站炭臭,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏袍辞。R本人自食惡果不足惜鞋仍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望搅吁。 院中可真熱鬧威创,春花似錦、人聲如沸谎懦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽界拦。三九已至吸申,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背截碴。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來泰國打工梳侨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人日丹。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓走哺,卻偏偏與公主長得像,于是被迫代替她去往敵國和親哲虾。 傳聞我的和親對(duì)象是個(gè)殘疾皇子丙躏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353