隨著時間的流逝揖庄,java技術(shù)也在一次次的革新阿逃,現(xiàn)在大家基本都已經(jīng)在使用微服務(wù)開發(fā)了,spring-cloud就是java技術(shù)棧下最流行的微服務(wù)解決方案惹恃,在微服務(wù)領(lǐng)域,對服務(wù)數(shù)量的大小調(diào)整已成為一個非常常規(guī)的一個操作棺牧,那么在某個服務(wù)擴(kuò)容和削減服務(wù)數(shù)量的時候座舍,怎么能不影響線上的服務(wù)使用呢
一、關(guān)于擴(kuò)容
對于擴(kuò)容其實(shí)比較簡單陨帆,啟動新的服務(wù)實(shí)例,注冊到eurake上即可采蚀,其他調(diào)用服務(wù)的client端(包括zuul)每隔一段時間會從eureka server上拉取新的服務(wù)列表疲牵,當(dāng)啟動完成的服務(wù)實(shí)例注冊完成,各個client拉取到新的服務(wù)列表榆鼠,便可以正常訪問新增的服務(wù)實(shí)例了纲爸,全程無感知。
二妆够、關(guān)于下線削減服務(wù)數(shù)量
下線微服務(wù)實(shí)例识啦,使用kill -9直接強(qiáng)制殺掉進(jìn)程,肯定是不行的神妹,因?yàn)楦鱾€客戶端都還在調(diào)用該實(shí)例時颓哮,突然被殺掉進(jìn)程,肯定會報(bào)錯(這里我們先不考慮鸵荠,重試機(jī)制冕茅,這是另外的一個問題,粗暴的重試也會導(dǎo)致所有接口都做冪等性處理),如果調(diào)用springboot提供的優(yōu)雅關(guān)閉服務(wù)實(shí)例的方法姨伤,可能會導(dǎo)致關(guān)閉失敗哨坪,進(jìn)程無法關(guān)閉,而且關(guān)閉后乍楚,因?yàn)楦鱾€客戶端會緩存eureka上的服務(wù)列表(如果都使用默認(rèn)配置当编,緩存最長還會持續(xù)90秒的時間),這樣客戶端調(diào)用已經(jīng)下線的服務(wù)還是會報(bào)錯徒溪,那么下面進(jìn)入本文主題忿偷,介紹一種可以,完全不報(bào)錯词渤,無感知的下線服務(wù)實(shí)例方式:
eureka提供了一個方法eurekaClient.shutdown()牵舱,調(diào)用后會將該服務(wù)實(shí)例從eureka上摘除,不會再進(jìn)行注冊缺虐,但是服務(wù)實(shí)例并不會關(guān)閉芜壁,我們從eureka摘除服務(wù)以后,先不要?dú)⒌舴?wù)進(jìn)程高氮,這個時候因?yàn)楦鱾€客戶端的緩存的服務(wù)列表沒有更新慧妄,還會繼續(xù)調(diào)用該服務(wù),所以就要等待剪芍,等90秒后所有的客戶端都從eureka上拉去到最新的服務(wù)列表塞淹,不再調(diào)用該服務(wù)時,將進(jìn)程殺掉罪裹,這樣客戶端就不會感知到該服務(wù)的下線饱普。這種方式就是會耗費(fèi)一些等待緩存過期的時間,這個我們可以通過修改緩存時長状共,來縮短這個時間套耕,當(dāng)然也可以同時下線多個服務(wù)
三、代碼實(shí)例
/**
* @author anjl
* 2018-7-12
*/
@SuppressWarnings("unused,unchecked")
@Api(tags = "eureka操作接口")
@ApiResponses(value = {@ApiResponse(code = 400, response = GlobalExceptionHandler.class, message = "數(shù)據(jù)校驗(yàn)失敗"), @ApiResponse(code = 500, response = GlobalExceptionHandler.class, message = "內(nèi)部錯誤")})
@Slf4j
@RestController
@RequestMapping("/eureka")
public class EurekaClientController {
@ApiOperation(value = "注銷eureka client", notes = "注銷eureka client", position = 10)
@GetMapping("/shutdown")
public BusinessResult shutdown() throws Exception {
InstanceInfo info = eurekaClient.getApplicationInfoManager().getInfo();
log.info("服務(wù)下線:instanceId = {}", info.getInstanceId());
singleThreadPool.execute(this::shutdownClient);
return BusinessResult.createSuccessInstance(null);
}
@ApiOperation(value = "用于檢測中間是否有停機(jī)", position = 10)
@GetMapping("/test")
public BusinessResult test() throws Exception {
return BusinessResult.createSuccessInstance(null);
}
private void shutdownClient() {
eurekaClient.shutdown();
log.info("服務(wù)下線成功O考獭7肱邸!");
}
@Autowired
private EurekaClient eurekaClient;
private ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("app-eureka-shutdown-pool-%d").build();
private ExecutorService singleThreadPool = new ThreadPoolExecutor(1, 1, 10L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
}
四碾牌、后續(xù)的一些說明
(1)從eureka server上也可以下線服務(wù)康愤,但是因?yàn)槲⒎?wù)實(shí)例的心跳并沒有停止,所以還是會重新注冊到eureka server上
(2)下線服務(wù)實(shí)例舶吗,采用異步處理主要是為了征冷,快速返回結(jié)果,在服務(wù)實(shí)例訪問量比較大的情況下裤翩,下線服務(wù)處理時間較長资盅,所以采用異步處理的方式调榄,先返回結(jié)果,同時異步去處理下線操作呵扛。
(3)如果我們項(xiàng)目資源比較緊張每庆,暫時只有一個服務(wù)實(shí)例,也可以通過這種方式實(shí)現(xiàn)不停機(jī)升級服務(wù)今穿,我們只需要在額外的端口啟動一個備份服務(wù)缤灵,然后通過上述方式將舊的服務(wù)下線,然后更新代碼后啟動蓝晒,再用上述方式下線備份服務(wù)即可(這樣的方案需要機(jī)器提供可以啟動一個備份服務(wù)的內(nèi)存)腮出。在啟動備份服務(wù)階段還可以檢查一些低級錯誤,比如配置文件錯誤或者編譯錯誤導(dǎo)致備份服務(wù)無法啟動成功芝薇,就可以不再向下執(zhí)行胚嘲。
(4)此方案好處:在聯(lián)調(diào)階段,不影響前端使用的情況下洛二,升級服務(wù)馋劈。
在測試階段不影響測試的情況下,修復(fù)bug晾嘶。
在線上環(huán)境不影響使用的情況下妓雾,服務(wù)升級。
總之大大提高了服務(wù)的可用性垒迂。
(ps:以前小編公司械姻,測試或者聯(lián)調(diào)階段,每次要更新代碼机断,都要在群里大喊”服務(wù)重啟“楷拳,重啟完,再大喊”完成“吏奸,喊的次數(shù)少了唯竹,更新不及時,喊的次數(shù)多了苦丁,又怕影響其他人工作,著實(shí)令小編為難N锉邸旺拉!
從此小編再也不用擔(dān)心重啟次數(shù)多的問題了,想怎么更新就怎么更新?昧住6旯贰)