derby数据库的介绍
官网地址:http://db.apache.org/derby/ 一 款java语言编写的内嵌于jvm的数据库,可以支持sql查询,以及jdbc协议,关于其持久化,大概推断是存储到了指定的目录文件下边:
服务列表源码分析
服务注册原理跟踪
根据debug会发现,在com.alibaba.nacos.naming.core.ServiceManager 类里面包含了相关的服务列表存储信息:
在源码里面会发现存储这些服务列表的本质就是一个ConcurrentHashMap数据结构:
(采用了ConcurrentHashMap来解决并发冲突问题,1.8之前是采用了分段锁,但是这种方式的锁粒度过大,所以后边改为了采用cas+synchronized的方式来进行加锁,通过使用无所插入头结点,如果插入失败,说明同一时刻有其他线程进行头插入,再次循坏插入)
private Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();
有点奇怪,这个map的数据是存储在内存里面的,那么服务在重启的时候应该是有进行初始化操作。并且当我们将provider的服务下架之后nacos依旧会有服务信息,在服务关闭之后的三十秒后nacos就查询不出任何信息了。
借此推测会有一个调度去专门维护这些数据信息。(猜测是心跳机制)
注册服务信息到nacos的接口:
/nacos/v1/ns/instance com.alibaba.nacos.naming.controllers.InstanceController#register -> com.alibaba.nacos.naming.core.ServiceManager#registerInstance
那么,假设我们通过启动dubbo工程,注册dubbo服务到nacos服务中心之后会看到哪些情况呢?
发现循环调用某些接口
- 【DistroFilter request url】/nacos/v1/ns/instance/beat
- 【DistroFilter request url】/nacos/v1/ns/instance/list
通过日志过滤发现会循环调用这两个接口,后来查询文档估计是某些调度在维护两端的数据。
客户端会重复发送心跳包到nacos这边,这份心跳包包含的数据还挺多的。关于心跳模块涉及到的类为:
com.alibaba.nacos.client.naming.beat.BeatReactor
发送的心跳数据基本格式通过BeatInfo格式进行数据传输。
关于循环发送心跳数据包的核心是借助了jdk内部的
ScheduledExecutorService
这个api来实现的,相关模板代码:
这样就能实现每个三秒发送一次心跳的功能。
同理,在nacos的服务端和客户端之间也存在心跳协调的代码:
class BeatTask implements Runnable { BeatInfo beatInfo; public BeatTask(BeatInfo beatInfo) { this.beatInfo = beatInfo; } @Override public void run() { if (beatInfo.isStopped()) { return; } long nextTime = beatInfo.getPeriod(); try { //发送心跳包 JSONObject result = serverProxy.sendBeat(beatInfo, BeatReactor.this.lightBeatEnabled); long interval = result.getIntValue("clientBeatInterval"); boolean lightBeatEnabled = false; if (result.containsKey(CommonParams.LIGHT_BEAT_ENABLED)) { lightBeatEnabled = result.getBooleanValue(CommonParams.LIGHT_BEAT_ENABLED); } BeatReactor.this.lightBeatEnabled = lightBeatEnabled; if (interval > 0) { nextTime = interval; } int code = NamingResponseCode.OK; if (result.containsKey(CommonParams.CODE)) { code = result.getIntValue(CommonParams.CODE); } if (code == NamingResponseCode.RESOURCE_NOT_FOUND) { //如果服务实例消失或者不存在,则注册一个服务实例 Instance instance = new Instance(); instance.setPort(beatInfo.getPort()); instance.setIp(beatInfo.getIp()); instance.setWeight(beatInfo.getWeight()); instance.setMetadata(beatInfo.getMetadata()); instance.setClusterName(beatInfo.getCluster()); instance.setServiceName(beatInfo.getServiceName()); instance.setInstanceId(instance.getInstanceId()); instance.setEphemeral(true); try { serverProxy.registerService(beatInfo.getServiceName(), NamingUtils.getGroupName(beatInfo.getServiceName()), instance); } catch (Exception ignore) { } } } catch (NacosException ne) { NAMING_LOGGER.error("[CLIENT-BEAT] failed to send beat: {}, code: {}, msg: {}", JSON.toJSONString(beatInfo), ne.getErrCode(), ne.getErrMsg()); } //每隔5秒重新发送一次心跳包 executorService.schedule(new BeatTask(beatInfo), nextTime, TimeUnit.MILLISECONDS); } }
其实我们深入sendbeat函数可以看到最底层就是请求nacos服务端的心跳接口
public JSONObject sendBeat(BeatInfo beatInfo, boolean lightBeatEnabled) throws NacosException { if (NAMING_LOGGER.isDebugEnabled()) { NAMING_LOGGER.debug("[BEAT] {} sending beat to server: {}", namespaceId, beatInfo.toString()); } Map<String, String> params = new HashMap<String, String>(8); String body = StringUtils.EMPTY; if (!lightBeatEnabled) { try { body = "beat=" + URLEncoder.encode(JSON.toJSONString(beatInfo), "UTF-8"); } catch (UnsupportedEncodingException e) { throw new NacosException(NacosException.SERVER_ERROR, "encode beatInfo error", e); } } params.put(CommonParams.NAMESPACE_ID, namespaceId); params.put(CommonParams.SERVICE_NAME, beatInfo.getServiceName()); params.put(CommonParams.CLUSTER_NAME, beatInfo.getCluster()); params.put("ip", beatInfo.getIp()); params.put("port", String.valueOf(beatInfo.getPort())); String result = reqAPI(UtilAndComs.NACOS_URL_BASE + "/instance/beat", params, body, HttpMethod.PUT); return JSON.parseObject(result); }
结合springboot的starter如何做服务发现
首先你可能会有思路推断,加入了一个starter就能生效,估计是有什么springboot的自动化配置在生效吧。
springboot也有自己的一套spi机制,将spirng.factories配置文件下的类进行实例化操作。
然后根据这些配置的类进行初始化操作。
这里面有个 NacosServiceRegistryAutoConfiguration
类
参考源代码:
com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration com.alibaba.cloud.nacos.registry.NacosAutoServiceRegistration org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration
这个类里面继承类spring的事件,ApplicationListener,当spring容器启动的时候会去触发onApplicationEvent函数的。
bind(event)-->start --> register--> com.alibaba.nacos.api.naming.NamingService#registerInstance(java.lang.String, java.lang.String, com.alibaba.nacos.api.naming.pojo.Instance)
其实本质就是在这里调用类nacos的一个远程方法,关于nacos的远程方法看看源码包就了解了,这个不难。
注册的参数
private Instance getNacosInstanceFromRegistration(Registration registration) { Instance instance = new Instance(); instance.setIp(registration.getHost()); instance.setPort(registration.getPort()); instance.setWeight(nacosDiscoveryProperties.getWeight()); instance.setClusterName(nacosDiscoveryProperties.getClusterName()); instance.setMetadata(registration.getMetadata()); return instance; }
整体的注册源码其实可以浓缩为下边这张图
nacos的集群化
基本配置条件:
一般集群需要至少3个节点。我们先准备3台机器,我这里选择了三台机器作为集群搭建基础:
192.168.11.200:8748 192.168.11.196:8748 192.168.11.126:8748
首先需要有三台基本的服务器用于运行多个nacos服务端程序。
然后修改conf配置文件:
[root@localhost conf]# ls application.properties application.properties.example cluster.conf cluster.conf.example.bak nacos-logback.xml nacos-mysql.sql schema.sql [root@localhost conf]# cat cluster.conf #it is ip #example 192.168.164.131:8848 192.168.164.132:8848 192.168.164.133:8848
最后再配置一下数据库连接部分:
### Count of DB: db.num=1 ### Connect URL of DB: db.url.0=jdbc:mysql://10.11.9.243:3306/linhao_test?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true db.user=crm db.password=USszJ497whda
启动之后日志会有明显说明提示nacos的集群已经部署成功。
如果需要方便操作可以借助使用nginx来做页面的转发。
upstream nacos_server { server 192.168.11.200:8748; server 192.168.11.196:8748; server 192.168.11.126:8748; } server { listen 80; server_name localhost; #charset koi8-r; #access_log logs/host.access.log main; location / { proxy_pass http://nacos_server; index index.html index.htm; } }
初始化登录账号
登录账号可以从源码里面翻查,然后根据这里的加密方式在数据库里面设置账号信息:
package com.alibaba.nacos.console.utils; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; /** * Password encoder tool * * @author nacos */ public class PasswordEncoderUtil { public static void main(String[] args) { System.out.println(new BCryptPasswordEncoder().encode("nacos")); } public static Boolean matches(String raw, String encoded) { return new BCryptPasswordEncoder().matches(raw, encoded); } public static String encode(String raw) { return new BCryptPasswordEncoder().encode(raw); } }
下边这段是nacos初始化时候给定的账号密码:
INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE); INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');
经过检测,不同账号登录nacos看到的基础配置信息大多都是相似的。
nacos里面的日志输出在nacos-logback.xml 配置了日志输出位置和等级,如果需要跟踪或者调整可以进去进行修改。
END