使用Java自制一個(gè)一個(gè)Nacos
什么是Nacos
Nacos是 Dynamic Naming and Configuration Service的首字母簡(jiǎn)稱,一個(gè)更易于構(gòu)建云原生應(yīng)用的動(dòng)態(tài)服務(wù)發(fā)現(xiàn)、配置管理和服務(wù)管理平臺(tái)。
Nacos的主要功能:
- 服務(wù)發(fā)現(xiàn)和服務(wù)健康監(jiān)測(cè)
- 動(dòng)態(tài)配置服務(wù)
- 動(dòng)態(tài) DNS 服務(wù)
- 服務(wù)及其元數(shù)據(jù)管理
直接參考Nacos文檔 :Nacos文檔
我們要做的功能是
Nacos的功能實(shí)在是太成熟了,但是我們可以通過(guò)官網(wǎng)和文檔總結(jié)出Nacos的兩大核心功能:
- 服務(wù)發(fā)現(xiàn)
- 配置管理
一、實(shí)現(xiàn) 服務(wù)發(fā)現(xiàn)
服務(wù)發(fā)現(xiàn),與其說(shuō)是實(shí)現(xiàn)服務(wù)與發(fā)現(xiàn),不如說(shuō)是實(shí)現(xiàn)以下三個(gè)功能:
- 服務(wù)啟動(dòng)時(shí)候進(jìn)行注冊(cè)
- 查詢已注冊(cè)服務(wù)信息
- 確認(rèn)服務(wù)狀態(tài)是否健康
1、創(chuàng)建一個(gè)SpringBoot項(xiàng)目,用來(lái)做服務(wù)端
這個(gè)項(xiàng)目要實(shí)現(xiàn)幾個(gè)目的:
- 首先我們的服務(wù)需要可以支持Http請(qǐng)求(GRPC更好,Nacos用的就是GRPC,Http請(qǐng)求我們更熟悉一點(diǎn),以后我們會(huì)專門(mén)出有關(guān)于JAVA使用GRPC的文章)。
- 其次我們需要我們的服務(wù)可以集成和連接一個(gè)關(guān)系型數(shù)據(jù)庫(kù)(Mysql或者Oracle...)和非關(guān)系型數(shù)據(jù)庫(kù)(Redis...)
所以我們需要?jiǎng)?chuàng)建一個(gè)SringBoot項(xiàng)目,因?yàn)槲覀冃枰粋€(gè)配置與發(fā)現(xiàn)中心的這么一個(gè)服務(wù),類似于搭建Nacos,我們只不過(guò)是將這個(gè)中間件變成一個(gè)我們熟悉的SpringBoot項(xiàng)目,方便我們開(kāi)發(fā)。
2、服務(wù)端
在 注冊(cè)中心 服務(wù)端 ,我們需要一個(gè)注冊(cè)接口。
實(shí)例 數(shù)據(jù)庫(kù)同理創(chuàng)建
public class ClientBody
{
private static final long serialVersionUID=1L;
/** 自增id */
private Long id;
/** 項(xiàng)目名 */
private String projectName;
/** 端口 */
private String port;
/** 健康檢測(cè)回調(diào)接口 */
private String CallbackInterface;
/** 內(nèi)網(wǎng)ip */
private String inNetIp;
/** 外網(wǎng)ip */
private String outNetIp;
/** 注冊(cè)時(shí)間 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date regSerTime;
/** 0-健康 1-異常 2-死亡 */
private String serType;
/** 異常時(shí)間 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date exceptTime;
/** 死亡時(shí)間 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date deathTime;
/** 檢測(cè)次數(shù) */
private Long checkNum;
/** 異常次數(shù) */
private Long exceNum;
}
服務(wù)端注冊(cè)接口
/**
* 令牌自定義標(biāo)識(shí)
*/
@Value("${token.header}")
private String header;
/**
* 服務(wù)注冊(cè)
*/
@PostMapping("/serviceRegistration")
public Boolean add(HttpServletRequest request, @RequestBody ClientBody clientBody){
/**
* 獲取token
*/
String token = request.getHeader("header");
/**
* 驗(yàn)證請(qǐng)求合法性
*/
Boolean isLegal = SecurityUtils.verLegal(token);
if(isLegal){
/**
* 檢測(cè) serType!=2 端口+內(nèi)網(wǎng)地址
*/
ClientBody client = clientBodyService.checkClient(clientBody);
if(ObjectUtils.isNotEmpty(client)){
/**
* 說(shuō)明是在心跳檢測(cè)期間重新啟動(dòng)。
* 注銷這臺(tái)實(shí)例.
*
* Mysql client 這條數(shù)據(jù)
* deathTime 改為現(xiàn)在時(shí)間
* serType 改為 2
*
* Redis 直接刪除這個(gè)數(shù)據(jù)id 為 Key的數(shù)據(jù)
* 直接刪除這臺(tái)實(shí)例
*/
clientBodyService.logoutCient(client);
/**
* redis 和 Mysql 中新增一臺(tái)實(shí)例
* regSerTime 現(xiàn)在時(shí)間
* serType 為 0
*/
clientBodyService.insert(clientBody);
}else{
/**
* 啟動(dòng)了一臺(tái)實(shí)例
*/
clientBodyService.insert(clientBody);
}
return true;
}else{
return false;
}
}
服務(wù)端健康檢測(cè)定時(shí)任務(wù)
/**
* 60秒定時(shí)健康檢測(cè)
*/
@Scheduled(cron = "0/60 * * * * ?")
public void heartbeatCheck(){
/**
* 查詢 serType = 0 健康的 實(shí)例List
*/
List<ClientBody> clientBodyList = clientBodyService.selectOnlineServer();
for (ClientBody clientBody : clientBodyList) {
//回調(diào)接口
String CallbackInterface = clientBody.getCallbackInterface();
//內(nèi)網(wǎng)地址
String inNetIp = clientBody.getInNetIp();
//拿到端口號(hào)
String port = clientBody.getPort();
//發(fā)送http請(qǐng)求 true為正常 false為異常
Boolean state = HttpUtils.sendHead(CallbackInterface,inNetIp,port);
if(state){
//檢測(cè)次數(shù) + 1(checkNum + 1)
clientBodyService.updateCientNormal(clientBody);
}else{
/**
* 檢測(cè)ping不通原因可能時(shí)網(wǎng)絡(luò)波動(dòng)
* 檢測(cè)十次 都是異常 才判定死亡
*/
if(clientBody.getCheckNum() > 10){
//修改這條數(shù)據(jù)為死亡 (serType = 2)
clientBodyService.updateCientDeath(clientBody);
}else{
//修改這條數(shù)據(jù)為異常,然后檢測(cè)次數(shù) + 1,異常次數(shù) + 1,異常時(shí)間[現(xiàn)在時(shí)間字符串拼接在原有數(shù)據(jù)之后](serType = 1;checkNum + 1;exceNum + 1;)
clientBodyService.updateCientException(clientBody);
}
}
}
}
3、客戶端
在我們的 客戶端服務(wù) 我們需要在啟動(dòng)的時(shí)候注冊(cè):
客戶端注冊(cè)接口
/**
* 注冊(cè)
* @PostConstruct 解釋:在項(xiàng)目啟動(dòng)時(shí)加載數(shù)據(jù)
*/
@PostConstruct
public void registerService(){
HashMap<String, Object> map = new HashMap<String,Object>(){{
//項(xiàng)目名
put("projectName",MyConfig.getName());
//我注冊(cè)選擇內(nèi)網(wǎng)地址,這個(gè)可以根據(jù)自己項(xiàng)目的實(shí)際情況選用。
put("inNetIp", MyConfig.getUrl());
//回調(diào)接口,這個(gè)接口就是下邊的健康檢測(cè)接口
put("CallbackInterface", "/checkHealthy");
//項(xiàng)目端口號(hào)
put("port", MyConfig.getPort());
}};
AjaxResult ajaxResult = HttpUtils.sendPostRequest('注冊(cè)與配置中心url', "/serviceRegistration", map);
}
我們還需要一個(gè)健康檢測(cè)客戶端接口,以便于服務(wù)注冊(cè)中心心跳檢測(cè)。我們選擇用輕量級(jí)的頭請(qǐng)求。
客戶端健康檢測(cè)接口
好了,我們的服務(wù)注冊(cè)就完成了!
二、實(shí)現(xiàn) 配置管理
配置管理只需要我們完成兩件事情
- 服務(wù)端管理配置
- 客戶端啟動(dòng)的時(shí)候拉取配置
1、服務(wù)端管理配置
第一步我們需要在服務(wù)注冊(cè)中心 服務(wù)端管理配置,我們將所有application.yml中的文件用properties的方式存入數(shù)據(jù)庫(kù)。
/**
* 健康檢測(cè)接口
*/
@RequestMapping(value = "/CallbackInterface",method = RequestMethod.HEAD)
public void healthyByHead(HttpServletResponse response) {
response.setHeader("data","200");
}
入庫(kù)的形式就是這種:

服務(wù)端拉取配置接口
public class ConfigData
{
private static final long serialVersionUID=1L;
/** id */
private Long id;
/** key */
private String key;
/** value */
private String value;
/** tag */
private String tag;
/** remark */
private String remark;
}
2、客戶端拉取配置
客戶端拉取配置接口,我們客戶端程序啟動(dòng)的時(shí)候需要從注冊(cè)服務(wù)中心來(lái)拉取配置。
首先我們?cè)诼窂?code>src\main\resources\META-INF\spring.factories中的spring.factories文件中加入到最后一行。
@GetMapping("/getConfigDataByTag")
public List<Map<String, String>> getConfig(@RequestParam String tag) throws JsonProcessingException {
Map<String, String> configData = configDataService.selectconfigDataList(new configData(tag));
return list;
}
然后我們需要?jiǎng)?chuàng)建一個(gè)拉取配置文件的類ServerConfigProcessor實(shí)現(xiàn)EnvironmentPostProcessor
public class ServerConfigProcessor implements EnvironmentPostProcessor {
private static final String PROPERTY_SOURCE_NAME = "databaseProperties";
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
log.info("ServerConfig loading start");
Map<String, Object> propertySource = new HashMap<>();
try {
/**
* 拉取配置
* MyConfig.ServerConfigHttpUrl 服務(wù)端url
* MyConfig.ServerConfigInterface 服務(wù)端接口
* MyConfig.MyServerTag 本服務(wù)標(biāo)識(shí)(用來(lái)判斷拉取的配置條件)
*/
Map<String, Object> configSource = HttpUtils.sendGet(MyConfig.ServerConfigHttpUrl,MyConfig.ServerConfigInterface,MyConfig.MyServerTag);
propertySource.putAll(configSource);
environment.getPropertySources().addFirst(new MapPropertySource(PROPERTY_SOURCE_NAME, propertySource));
log.info("ServerConfig loading Success");
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
OK到此為止我們的自定義Nacos就做好了。
總結(jié)
我們的自定義Nacos就做好了,它可以實(shí)現(xiàn)的功能有:
- 客戶端啟動(dòng)可以注冊(cè)到服務(wù)端。
- 服務(wù)端可以心跳檢測(cè)每個(gè)客戶端的項(xiàng)目。
- 數(shù)據(jù)分析,比如我們什么時(shí)候項(xiàng)目異常?總共啟動(dòng)多少次項(xiàng)目?...
- 可以不用application.yml,配置文件全部放在數(shù)據(jù)庫(kù)中。(和Nacos一樣)
- 可以實(shí)現(xiàn)熱部署配置文件,遠(yuǎn)程更改,實(shí)時(shí)有效。
其他問(wèn)題
src\main\resources\META-INF\spring.factories為什么可以自動(dòng)裝載配置?
這就要涉及SpringBoot源碼啦,關(guān)于SpringBoot啟動(dòng)時(shí)候加載配置的優(yōu)先級(jí)和環(huán)境配置的上下文,請(qǐng)參考SpringBoot源碼。(讀源碼啦~ 必須要過(guò)這一關(guān)的嘛)
自定義拓展功能
可以把服務(wù)注冊(cè)發(fā)現(xiàn)和配置管理都用前端展示到頁(yè)面上方便管理。以下是我自己實(shí)現(xiàn)的前端界面,不美觀無(wú)所謂,看的懂就行。
服務(wù)注冊(cè)發(fā)現(xiàn)

配置管理(可以和Nacos一樣在頁(yè)面進(jìn)行修改和熱部署)

以上就是使用Java自制一個(gè)一個(gè)Nacos的詳細(xì)內(nèi)容,更多關(guān)于Java Nacos的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java web實(shí)現(xiàn)郵箱發(fā)送功能
這篇文章主要為大家詳細(xì)介紹了java web實(shí)現(xiàn)郵箱發(fā)送功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-05-05
java模擬TCP通信實(shí)現(xiàn)客戶端上傳文件到服務(wù)器端
這篇文章主要為大家詳細(xì)介紹了java模擬TCP通信實(shí)現(xiàn)客戶端上傳文件到服務(wù)器端,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-10-10
Java實(shí)現(xiàn)給網(wǎng)站上傳圖片蓋章的方法
這篇文章主要介紹了Java實(shí)現(xiàn)給網(wǎng)站上傳圖片蓋章的方法,涉及java針對(duì)圖片的合成操作技巧,類似水印功能,需要的朋友可以參考下2015-07-07
JAVA Comparator 和 Comparable接口使用方法
本文介紹了Java中Comparable和Comparator接口的使用,包括它們的定義、方法和應(yīng)用場(chǎng)景,Comparable用于定義類的自然排序規(guī)則,而Comparator提供了一種靈活的方式來(lái)定義對(duì)象之間的排序規(guī)則,無(wú)需修改類本身,感興趣的朋友一起看看吧2025-03-03
Java遞歸算法經(jīng)典實(shí)例(經(jīng)典兔子問(wèn)題)
本文主要對(duì)經(jīng)典的兔子案例分析,來(lái)進(jìn)一步更好的理解和學(xué)習(xí)java遞歸算法,具有很好的參考價(jià)值,需要的朋友一起來(lái)看下吧2016-12-12
Java LocalCache 本地緩存的實(shí)現(xiàn)實(shí)例
本篇文章主要介紹了Java LocalCache 本地緩存的實(shí)現(xiàn)實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-05-05
springcloud gateway網(wǎng)關(guān)服務(wù)啟動(dòng)報(bào)錯(cuò)的解決
這篇文章主要介紹了springcloud gateway網(wǎng)關(guān)服務(wù)啟動(dòng)報(bào)錯(cuò)的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03

