大家好,我是阿七。
在上一篇文章中,我們已經(jīng)實現(xiàn)了內(nèi)容中心總能夠調(diào)用用戶中心,那如何實現(xiàn)負載均衡呢?請聽阿七為你娓娓道來。(沒看到上篇文章的同學請戳這里:實戰(zhàn)(一)nacos注冊與發(fā)現(xiàn))
一、負載均衡的兩種方式
眾所周知,在負載均衡領(lǐng)域一般有兩種方式去實現(xiàn),分別是:
1、服務(wù)器端負載均衡;
2、客戶端側(cè)負載均衡;
在單體架構(gòu)時代,我們一般會部署多個實例在服務(wù)器上,然后使用Nginx做負載均衡(nginx也是部署在服務(wù)器上的)。請求全部打在nginx上,nginx根據(jù)負載均衡策略將請求轉(zhuǎn)發(fā)到不同的實例上。如下面這幅圖所示:

有同學會疑問了,為什么nginx可以抗住這么多的請求呢?因為它異步,非阻塞,使用了epoll 和大量的底層代碼優(yōu)化,哈哈跑題了,阿七后面再專門寫文章說說nginx。
說完了服務(wù)器端負載均衡,那么什么是客戶端負載均衡呢?且看下圖:

在這幅圖中,內(nèi)容中心是要調(diào)用用戶中心的,那么內(nèi)容中心相對于用戶中心就是客戶端,對吧,這個應該可以理解。現(xiàn)在我們已經(jīng)可以通過DiscoveryClient來獲取用戶中心的多個實例,如果我們在內(nèi)容中心自己寫一個負載均衡規(guī)則,然后交給RestTemplate來請求一個用戶中心實例,這樣就實現(xiàn)了客戶端負載均衡了。說干就干,咱這就來寫一個負載均衡策略。
二、手寫一個客戶端負載均衡器
第一步,修改代碼:之前是調(diào)用findFirst()默認返回第一個用戶中心實例,現(xiàn)在注釋掉了,改成隨機獲取一個用戶中心實例。
public ShareDTO findById(Integer id) {
Share share = this.shareMApper.selectByPrimaryKey(id);
Integer userId = share.getUserId();
//用戶中心的所有實例信息
List<ServiceInstance> instances = discoveryClient.getInstances("user-center");
// String targetUrl = instances
// .stream()
// .map(instance -> instance.getUri().toString() + "/users/{id}")
// .findFirst()
// .orElseThrow(() -> new IllegalArgumentException("當前沒有實例對象"));
List<String> targetUrls = instances
.stream()
.map(instance -> instance.getUri().toString() + "/users/{id}")
.collect(Collectors.toList());
//隨機算法
int i = ThreadLocalRandom.current().nextInt(targetUrls.size());
String targetUrl = targetUrls.get(i);
log.info("請求的目標地址:{}",targetUrl);
//根據(jù)userId查詢用戶信息
UserDTO userDTO = this.restTemplate.getForObject(targetUrl, UserDTO.class, userId);
ShareDTO shareDTO = new ShareDTO();
BeanUtils.copyProperties(share, shareDTO);
shareDTO.setWxNickname(userDTO.getWxNickname());
return shareDTO;
}
第二步,測試:
1、我們先啟動一個用戶中心實例,端口為8888;然后修改端口號為8887,點擊Edit Configurations,勾選Allow parallel run,保存,再啟動一次項目,這樣就可以啟動兩個用戶中心實例。如下圖:

2、啟動內(nèi)容中心實例,打開nacos看看

3、接口測試,訪問http://localhost:8889/shares/1

這時,我們?nèi)タ刂婆_看看:

哎,我們會發(fā)現(xiàn),內(nèi)容中心在隨機調(diào)用用戶中心的實例。說白了,負載均衡器就是給你一個list,你隨機從中獲取一個實例來調(diào)用。 但是我們這個代碼還是比較簡單的,只是模擬一下。如果我們每次進行服務(wù)調(diào)用都寫這么一堆代碼肯定也是不現(xiàn)實的。
那么下面我們就要ribbon進行重構(gòu)咯,同學們繼續(xù)往下看。
三、整合ribbon
在使用ribbon進行重構(gòu)之前,我們肯定得了解什么是ribbon對吧。一句話闡述ribbon是Netflix開源的客戶端側(cè)負載均衡器。 其實,ribbon就是我們剛剛寫的代碼的一個組件,但是它封裝的更好,提供了更多的負載均衡策略。
下面我們就來整合ribbon到我們的項目中。還記得三步驟嗎?
第一步加依賴:因為nacos-discovery中已經(jīng)集成了netflix-ribbon,所以這里就不要在單獨集成了。

第二步加注解:在RestTemplate上加注解@LoadBalanced
@MapperScan("com.seven")
@SpringBootApplication
public class ContentCenterApplication {
public static void main(String[] args) {
SpringApplication.run(ContentCenterApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
第三步寫配置:在yml文件中寫配置,這里不需要寫。
第四步修改代碼:ribbon會根據(jù)user-center去nacos中找到請求的真是路徑。
public ShareDTO findById(Integer id) {
Share share = this.shareMapper.selectByPrimaryKey(id);
Integer userId = share.getUserId();
//根據(jù)userId查詢用戶信息
UserDTO userDTO = this.restTemplate
.getForObject("http://user-center/users/{userId}", UserDTO.class, userId);
ShareDTO shareDTO = new ShareDTO();
BeanUtils.copyProperties(share, shareDTO);
shareDTO.setWxNickname(userDTO.getWxNickname());
return shareDTO;
}
ok,到這里ribbon就整合完了。那有人會問,到這就結(jié)束了嗎?那顯然不是,阿七不是浮于表面的人,咱學習一個東西就要刨根問底。
四、ribbon組成
ribbon組件雖小,但是五臟俱全,它的組成都有哪些呢?貼心的我已經(jīng)為大家準備好了。

同時,ribbon支持8種負載均衡策略,如下所示:

這里的Zone本意為地區(qū)、地帶的意思,這里作機房的架子,也就是放服務(wù)器的機架。那我們實際上沒有Zone的,也就是說默認采用的負載均衡策略是RoundRibonRule(輪訓策略)。我們看一下源碼,找到RoundRobinRule.JAVA類,其中最主要的就是這一段,實際上就是取余得到訪問的server的index。
private int incrementAndGetModulo(int modulo) {
int current;
int next;
do {
current = this.nextServerCyclicCounter.get();
next = (current + 1) % modulo;
} while(!this.nextServerCyclicCounter.compareAndSet(current, next));
return next;
}
compareAndSet是AtomicInteger的方法,其實類似參數(shù)列表compareAndSwapInt(var1, var2, var5, var4),作為base的var1加上偏移量var2之后和var5比較是不是值相同,相同就update為var4. valueOffset是native的C的方法指針找到的地址。
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
五、細粒度配置ribbon
那我們?nèi)绾渭毩6取⒆远x配置負載均衡策略呢?比如,內(nèi)容中心想指定一種負載均衡策略來獲取用戶中心的實例。很簡單,我們只需要加一下yml配置文件即可。
#指定服務(wù)名
user-center:
ribbon:
#負載均衡策略的全路徑
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
這樣我們就修改ribbon負載均衡策略為RandomRule,也就是隨機獲取一個實例。
六、ribbon解餓加載
在實際代碼運行中發(fā)現(xiàn),當我們第一次請求http://localhost:8889/shares/1這個接口時,運行結(jié)果是很慢的,為什么呢?因為ribbon默認是懶加載。只有在下面代碼第一次執(zhí)行的時候,才會創(chuàng)建一個名叫user-center的ribbon client。
//根據(jù)userId查詢用戶信息
UserDTO userDTO = this.restTemplate
.getForObject("http://user-center/users/{userId}", UserDTO.class, userId);
我們可以通過修改yml配置文件來解決這個問題。
ribbon:
eager-load:
enabled: true
#多個用逗號隔開 user-center,xxx,yyy
clients: user-center
這樣就可以第一次請求也變得很快啦。
好了ribbon就學習到這里了。但是,我們的代碼還是存在很多問題的,那么下一篇我們將一起學習聲明式http客戶端--feign,一起將代碼優(yōu)化的更好吧。
喜歡的朋友記得點個關(guān)注吧,一起探討,共同進步。