一、背景介绍
之前一直在用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的考虑没有列入其中;后续再进行迭代研究
五、升华
对于底层内容的学习和理解,让我再看框架的时候,显得那么的通透。