一、背景介绍
之前一直在用nacos,对于nacos的原理只是停留在理论的层面上;最近想要再精进一步,于是有了想要自己实现一下nacos的大致原理。
当然对于安全、健壮性等方面的考虑并没有涉及那么多,只是通过简易的代码实现了配置管理和注册管理的逻辑功能。
二、思路&方案
三、过程
service代码
POM
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>NacosTest</artifactId>
<groupId>com.mark</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>Service</artifactId>
<name>Service</name>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 开始-Http请求引入的包依赖,没有明确那些有用那些没有用-->
<!-- 解决编译时,报程序包javax.servlet不存在的错误 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<!-- 只在编译和测试的时候用 -->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.9</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.11</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置文件
server:
port: 8080
spring:
mvc:
static-path-pattern: /static/** #static为存放css,js的文件夹
具体类
package com.tfjy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Service {
public static void main(String[] args) {
SpringApplication.run(Service.class,args);
}
}
package com.tfjy.util;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
public class httpClient {
public static void main(String[] args)throws Exception {
}
public static String doGet1(String httpurl) {
try {
//创建连接
URL url = new URL(httpurl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json");
if (conn.getResponseCode() != 200) {
throw new RuntimeException("Failed : HTTP error code : " + conn.getResponseCode());
}
//从输入流中读取数据
InputStream inputStream = conn.getInputStream();
Scanner scanner = new Scanner(inputStream);
StringBuffer buffer = new StringBuffer();
while (scanner.hasNextLine()) {
buffer.append(scanner.nextLine());
}
scanner.close();
String result = buffer.toString();
conn.disconnect();
return result;
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
public static String doGet(String httpurl) {
HttpURLConnection connection = null;
InputStream is = null;
BufferedReader br = null;
String result = null;// 返回结果字符串
try {
// 创建远程url连接对象
URL url = new URL(httpurl);
// 通过远程url连接对象打开一个连接,强转成httpURLConnection类
connection = (HttpURLConnection) url.openConnection();
// 设置连接方式:get
connection.setRequestMethod("GET");
// 设置连接主机服务器的超时时间:15000毫秒
connection.setConnectTimeout(15000);
// 设置读取远程返回的数据时间:60000毫秒
connection.setReadTimeout(60000);
connection.setRequestProperty("Accept", "application/json");
// 发送请求
connection.connect();
// 通过connection连接,获取输入流
if (connection.getResponseCode() == 200) {
is = connection.getInputStream();
// 封装输入流is,并指定字符集
br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
// 存放数据
StringBuffer sbf = new StringBuffer();
String temp = null;
while ((temp = br.readLine()) != null) {
sbf.append(temp);
sbf.append("\r\n");
}
result = sbf.toString();
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭资源
if (null != br) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != is) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
connection.disconnect();// 关闭远程连接
}
return result;
}
public static String doPost(String httpUrl, String param) {
HttpURLConnection connection = null;
InputStream is = null;
OutputStream os = null;
BufferedReader br = null;
String result = null;
try {
URL url = new URL(httpUrl);
// 通过远程url连接对象打开连接
connection = (HttpURLConnection) url.openConnection();
// 设置连接请求方式
connection.setRequestMethod("POST");
// 设置连接主机服务器超时时间:15000毫秒
connection.setConnectTimeout(15000);
// 设置读取主机服务器返回数据超时时间:60000毫秒
connection.setReadTimeout(60000);
// 默认值为:false,当向远程服务器传送数据/写数据时,需要设置为true
connection.setDoOutput(true);
// 默认值为:true,当前向远程服务读取数据时,设置为true,该参数可有可无
connection.setDoInput(true);
// 设置传入参数的格式:请求参数应该是 name1=value1&name2=value2 的形式。
connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
// 通过连接对象获取一个输出流
os = connection.getOutputStream();
// 通过输出流对象将参数写出去/传输出去,它是通过字节数组写出的
os.write(param.getBytes());
// 通过连接对象获取一个输入流,向远程读取
if (connection.getResponseCode() == 200) {
is = connection.getInputStream();
// 对输入流对象进行包装:charset根据工作项目组的要求来设置
br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
StringBuffer sbf = new StringBuffer();
String temp = null;
// 循环遍历一行一行读取数据
while ((temp = br.readLine()) != null) {
sbf.append(temp);
sbf.append("\r\n");
}
result = sbf.toString();
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭资源
if (null != br) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != os) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != is) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// 断开与远程地址url的连接
connection.disconnect();
}
return result;
}
}
package com.tfjy.controller;
import com.tfjy.util.httpClient;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
@Controller
public class ServiceController {
Map<String, Map<String,Object>> mapMapConfig = new HashMap<>();
Map<String, Map<String,Object>> mapMapRegistion = new HashMap<>();
@PostMapping("/saveService")
@ResponseBody
public String saveService(@RequestBody Map<String,Object> map){
if("config".equalsIgnoreCase(String.valueOf(map.get("serviceType")))){
mapMapConfig.put(String.valueOf(map.get("serviceKey")),(Map)map.get("serviceValue"));
if(mapMapRegistion.containsKey(String.valueOf(map.get("serviceKey")))) {
Map<String,Object> objectMap = mapMapRegistion.get(String.valueOf(map.get("serviceKey")));
String ip = String.valueOf(objectMap.get(String.valueOf(map.get("serviceKey"))));
//请求对应的服务进行通知
String param = "{}";
httpClient.doPost("http://"+ip+"/getConfigMessage", param);
}
}else if("Registion".equalsIgnoreCase(String.valueOf(map.get("serviceType")))){
mapMapRegistion.put(String.valueOf(map.get("serviceKey")),(Map)map.get("serviceValue"));
//循环请求所有服务进行通知
for (Map.Entry entry:mapMapRegistion.entrySet()) {
Map value = (Map)entry.getValue();
String ip = String.valueOf(value.get(String.valueOf(entry.getKey())));
//请求对应的服务进行通知
String param = "{}";
httpClient.doPost("http://"+ip+"/getRegistionMessage", param);
}
}
System.out.println(map);
return " save 成功!";
}
@PostMapping("/getService")
@ResponseBody
public Object getService(@RequestBody Map<String,Object> map){
if("config".equalsIgnoreCase(String.valueOf(map.get("serviceType")))){
return mapMapConfig.get(String.valueOf(map.get("serviceKey")));
}else if("Registion".equalsIgnoreCase(String.valueOf(map.get("serviceType")))){
return mapMapRegistion;
}
return "false";
}
}
apiSDK代码
POM
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>NacosTest</artifactId>
<groupId>com.mark</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>apiSDK</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 开始-Http请求引入的包依赖,没有明确那些有用那些没有用-->
<!-- 解决编译时,报程序包javax.servlet不存在的错误 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<!-- 只在编译和测试的时候用 -->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.9</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.11</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
</dependencies>
<distributionManagement>
<!--Release类型的托管资源库-->
<repository>
<!--id对应nexus仓库的id-->
<id>test-hosted</id>
<!--自定义名称-->
<name>Releases</name>
<!--仓库对应的URL地址-->
<url>http://xxx:8089/repository/test-hosted/</url>
</repository>
<!--Snapshot类型的托管资源库-->
<snapshotRepository>
<!--id对应nexus仓库的id-->
<id>test-snapshot-hosted</id>
<!--自定义名称-->
<name>Snapshot</name>
<!--仓库对应的URL地址-->
<url>http://xxx:8089/repository/test-snapshot-hosted/</url>
</snapshotRepository>
</distributionManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置文件
server:
port: 8081
spring:
mvc:
static-path-pattern: /static/** #static为存放css,js的文件夹
具体类
package com.tfjy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class apiSDK {
public static void main(String[] args) {
SpringApplication.run(apiSDK.class,args);
}
}
package com.tfjy.util;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import java.net.InetAddress;
import java.net.UnknownHostException;
@Component
public class StartInit3 implements ApplicationRunner {
@Value("${server.port}")
String port ;
@Value("${server.name}")
String name ;
@Value("${server.nacosUrl}")
String nacosUrl ;
@Override
public void run(ApplicationArguments args) {
String ipAddress = null;
try {
ipAddress = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
System.out.println(ipAddress.hashCode());
String param = "{\n" +
" \"serviceKey\":\""+name+"\",\n" +
" \"serviceType\":\"Registion\",\n" +
" \"serviceValue\":{\n" +
" \""+name+"\":\""+ipAddress+":"+port+"\"\n" +
" }\n" +
"}";
String result = httpClient.doPost(nacosUrl+"/saveService", param);
System.out.println("服务XXX启动成功,已经进行nacos注册"+result);
}
}
package com.tfjy.controller;
import com.alibaba.fastjson.JSONObject;
import com.tfjy.util.httpClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
@Controller
public class ServiceController {
@Value("${server.nacosUrl}")
String nacosUrl ;
@Value("${server.name}")
String name ;
Map<String,Object> mapMapConfig = new HashMap<>();
Map<String, Object> mapMapRegistion = new HashMap<>();
@PostMapping("/getRegistionMessage")
@ResponseBody
public String getRegistionMessage(@RequestBody Map<String,Object> map){
//请求注册发现的全部信息
String param = "{\n" +
" \"serviceKey\":\""+name+"\",\n" +
" \"serviceType\":\"Registion\"\n" +
"}";
String result = httpClient.doPost(nacosUrl+"/getService", param);
Map<String,Object> resultMap = JSONObject.parseObject(result);
for (Map.Entry entry:resultMap.entrySet()) {
Map<String,Object> value = (Map<String,Object>)entry.getValue();
mapMapRegistion.putAll(value);
}
return " save 成功!";
}
@PostMapping("/getConfigMessage")
@ResponseBody
public Object getConfigMessage(@RequestBody Map<String,Object> map){
//请求单独的配置信息
String param = "{\n" +
" \"serviceKey\":\""+name+"\",\n" +
" \"serviceType\":\"config\"\n" +
"}";
String result = httpClient.doPost(nacosUrl+"/getService", param);
Map resultMap = JSONObject.parseObject(result);
mapMapConfig.putAll(resultMap);
return "true";
}
}
A服务代码
POM
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>NacosTest</artifactId>
<groupId>com.mark</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>A</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 开始-Http请求引入的包依赖,没有明确那些有用那些没有用-->
<!-- 解决编译时,报程序包javax.servlet不存在的错误 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<!-- 只在编译和测试的时候用 -->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.9</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.11</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>com.mark</groupId>
<artifactId>apiSDK</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置文件
server:
port: 8091
name: A
nacosUrl: http://localhost:8080
spring:
mvc:
static-path-pattern: /static/** #static为存放css,js的文件夹
具体类
package com.tfjy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class A {
public static void main(String[] args) {
SpringApplication.run(A.class,args);
}
}
package com.tfjy.controller;
import com.tfjy.util.httpClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Map;
@Controller
public class AController {
@Autowired
ServiceController serviceController;
@PostMapping("/sendToBMessage")
@ResponseBody
public String sendToBMessage(@RequestBody Map<String,Object> map){
//请求注册发现的全部信息
String param = "{\n" +
" \"serviceKey\":\"我是A\",\n" +
" \"serviceType\":\"Registion\"\n" +
"}";
String ip = String.valueOf(serviceController.mapMapRegistion.get("B"));
String result = httpClient.doPost("http://"+ip+"/getConfigB", param);
System.out.println("A服务调用B服务结果:"+result);
return " save 成功!";
}
}
B服务代码
POM
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>NacosTest</artifactId>
<groupId>com.mark</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>B</artifactId>
<name>B</name>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 开始-Http请求引入的包依赖,没有明确那些有用那些没有用-->
<!-- 解决编译时,报程序包javax.servlet不存在的错误 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<!-- 只在编译和测试的时候用 -->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.9</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.11</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>com.mark</groupId>
<artifactId>apiSDK</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置文件
server:
port: 8092
name: B
nacosUrl: http://localhost:8080
spring:
mvc:
static-path-pattern: /static/** #static为存放css,js的文件夹
具体类
package com.tfjy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class B {
public static void main(String[] args) {
SpringApplication.run(B.class,args);
}
}
package com.tfjy.controller;
import com.alibaba.fastjson.JSONObject;
import com.tfjy.util.httpClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
@Controller
public class BController {
@Autowired
ServiceController serviceController;
@PostMapping("/getConfigB")
@ResponseBody
public String getConfigB(@RequestBody Map<String,Object> map){
System.out.println("调用B 服务 getConfigB 方法的入参为:"+map);
System.out.println("B服务的配置文件为:"+serviceController.mapMapConfig);
return " save 成功!";
}
}
四、总结
1.有了实践层面的理解;对于理论的理解更深入了一层
2.实现过程中又进一步体会了sdk封装的精妙,让集成sdk的服务复用性得到了充足的发挥
3.通过SDK缓存机制,将集权制的好管理带来的耦合性强的弊端做了规避
4.小例子中的心跳,数据传递的安全性,代码的健壮性,CAP的考虑没有列入其中;后续再进行迭代研究
五、升华
对于底层内容的学习和理解,让我再看框架的时候,显得那么的通透。