Paul Paul Public Builder & 全栈工程师
search
arrow_back 返回标签拓扑

源码解析

共 5 篇。

newest_tech 2022-08-22 · 约 7 分钟

Nacos-Config模块源码讲解

前言 我们知道Nacos其实是由两个重要的模块组成,一是 Naming 模块,另一个就是今天要讲的 Config 模块。 版本说明 Nacos:2.1.1 jdk:1.8 代码分支:develope 配置中心基本原理 配置中心有三个角色分别是是配置发布者、配置服务端、配置客户端。整个流程由配置发布者发起,先发布一条配置到服务端,然后客户端监听这条配置,当配置有变动时由服务端推送到客用户端。 配置发布 我们从web层着手,可以很快的发现入口为com.alibaba.nacos.config.server.controller.ConfigController#publishConfig,这个方法看上去很长,但其实大体逻辑很简单,示意代码如下所示。 java content_copy 复制代码 public class ConfigController { /** * Adds or updates non-aggregated data. * <p> * request and response will be used in aspect, see * {@link com.alibaba.nacos.config.server.aspect.CapacityManagementAspect} and * {@link com.alibaba.nacos.config.server.aspect.RequestLogAspect}. * </p> * @throws NacosException NacosException. */ @PostMapping @Secured(action = ActionTypes.WRITE, signType = SignType.CONFIG) public Boolean publishConfig(HttpServletRequest request, HttpServletResponse response, @RequestParam(value = "dataId") String dataId, @RequestParam(value = "group") String group, @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant, @RequestParam(value = "content") String content, @RequestParam(value = "tag", required = false) String tag, @RequestParam(value = "appName", required = false) String appName, @RequestParam(value = "src_user", required = false) String srcUser, @RequestParam(value = "config_tags", required = false) String configTags, @RequestParam(value = "desc", required = false) String desc, @RequestParam(value = "use", required = false) String use, @RequestParam(value = "effect", required = false) String effect, @RequestParam(value = "type", required = false) String type, @RequestParam(value = "schema", required = false) String schema) throws NacosException { final String srcIp = RequestUtil.getRemoteIp(request); final String requestIpApp = RequestUtil.getAppName(request); if (StringUtils.isBlank(srcUser)) { srcUser = RequestUtil.getSrcUserName(request); } //check type if (!ConfigType.isValidType(type)) { type = ConfigType.getDefaultType().getType(); } // encrypted Pair<String, String> pair = EncryptionHandler.encryptHandler(dataId, content); content = pair.getSecond(); // check tenant ParamUtils.checkTenant(tenant); ParamUtils.checkParam(dataId, group, "datumId", content); ParamUtils.checkParam(tag); Map<String, Object> configAdvanceInfo = new HashMap<>(10); MapUtil.putIfValNoNull(configAdvanceInfo, "config_tags", configTags); MapUtil.putIfValNoNull(configAdvanceInfo, "desc", desc); MapUtil.putIfValNoNull(configAdvanceInfo, "use", use); MapUtil.putIfValNoNull(configAdvanceInfo, "effect", effect); MapUtil.putIfValNoNull(configAdvanceInfo, "type", type); MapUtil.putIfValNoNull(configAdvanceInfo, "schema", schema); ParamUtils.checkParam(configAdvanceInfo); if (AggrWhitelist.isAggrDataId(dataId)) { LOGGER.warn("[aggr-conflict] {} attempt to publish single data, {}, {}", RequestUtil.getRemoteIp(request), dataId, group); throw new NacosException(NacosException.NO_RIGHT, "dataId:" + dataId + " is aggr"); } final Timestamp time = TimeUtils.getCurrentTime(); String betaIps = request.getHeader("betaIps"); ConfigInfo configInfo = new ConfigInfo(dataId, group, tenant, appName, content); configInfo.setType(type); String encryptedDataKey = pair.getFirst(); configInfo.setEncryptedDataKey(encryptedDataKey); if (StringUtils.isBlank(betaIps)) { if (StringUtils.isBlank(tag)) { persistService.insertOrUpdate(srcIp, srcUser, configInfo, time, configAdvanceInfo, false); ConfigChangePublisher.notifyConfigChange( new ConfigDataChangeEvent(false, dataId, group, tenant, time.getTime())); } else { persistService.insertOrUpdateTag(configInfo, tag, srcIp, srcUser, time, false); ConfigChangePublisher.notifyConfigChange( new ConfigDataChangeEvent(false, dataId, group, tenant, tag, time.getTime())); } } else { // beta publish configInfo.setEncryptedDataKey(encryptedDataKey); persistService.insertOrUpdateBeta(configInfo, betaIps, srcIp, srcUser, time, false); ConfigChangePublisher.notifyConfigChange( new ConfigDataChangeEvent(true, dataId, group, tenant, time.getTime())); } ConfigTraceService.logPersistenceEvent(dataId, group, tenant, requestIpApp, time.getTime(), InetUtils.getSelfIP(), ConfigTraceService.PERSISTENCE_EVENT_PUB, content); return true; } } 整个方法就是先组装ConfigInfo,然后委托com.alibaba.nacos.config.server.service.repository.PersistService#insertOrUpdateTag进行处理,这里如果使用的是mysql那么实现类就是com.alibaba.nacos.config.server.service.repository.extrnal.ExternalStoragePersistServiceImpl,这个是一个插入数据库的动作就不细讲了,只是要注意的是这个方法包含新增和更新,当插入失败时会变成更新操作。而操作完数据库之后会发送一个ConfigDataChangeEvent事件,这个事件的处理类是com.alibaba.nacos.config.server.service.notify.AsyncNotifyService,示意代码如下所示。

阅读全文 arrow_forward
newest_tech 2022-08-18 · 约 5 分钟

Nacos-Naming模块源码讲解

前言 上篇文章列举了Nacos大致的功能列表,但没有深入讲解 Nacos 的实现细节。我们知道其实还有很多同类的产品比如Zookeeper、Eureka、Etcd、Consul等等,这些产品的功能在大体上都和 Nacos 很相似,最主要的区别就在于它的实现。今天我们来深入了解 Nacos 的 Naming 模块的实现。 版本说明 Nacos:2.1.1 jdk:1.8 代码分支:develope 服务注册 根据服务注册url可以定位到服务注册的web层入口为com.alibaba.nacos.naming.controllers.InstanceController#register,示意代码如下所示。 java content_copy 复制代码 public class InstanceController{ /** * Register new instance. * * @param request http request * @return 'ok' if success * @throws Exception any error during register */ @CanDistro @PostMapping @Secured(action = ActionTypes.WRITE) public String register(HttpServletRequest request) throws Exception { final String namespaceId = WebUtils .optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID); final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME); NamingUtils.checkServiceNameFormat(serviceName); final Instance instance = HttpRequestInstanceBuilder.newBuilder() .setDefaultInstanceEphemeral(switchDomain.isDefaultInstanceEphemeral()).setRequest(request).build(); getInstanceOperator().registerInstance(namespaceId, serviceName, instance); NotifyCenter.publishEvent(new RegisterInstanceTraceEvent(System.currentTimeMillis(), "", false, namespaceId, NamingUtils.getGroupName(serviceName), NamingUtils.getServiceName(serviceName), instance.getIp(), instance.getPort())); return "ok"; } } 可以看到整个过程只有两个步骤。一是注册Instance,一是通过NotyfyCenter发布事件。我们拆开看,先看注册Instance的过程。

阅读全文 arrow_forward
newest_tech 2022-08-07 · 约 1 分钟

Dubbo3.0-DubboBootstrap模型讲解

前言 Dubbo3.0有很多新的类(较2.0来说),本文主要就3.0中DubboBootstrap及其相关的类进行讲解,主要为想了解Dubbo3.0启动模型的同学做参考。 版本说明 Dubbo版本:3.0.9 DubboBootstrap的组成部分 DubboBootstrap的关键属性有4个,分别是ApplicationModel、ConfigManager、Environment、ApplicationDeployer。他们功能分别是应用的定义(可以理解为领域对象中的Entity)、配置管理、环境变量管理、应用声明周期管理。 ApplicationModel讲解 讲到ApplicationModel就不得不提到Dubbo的另外两个领域模型FrameworkModel和ModuleModel。 模型之间的依赖关系 这三个模型紧密相连,整个Dubbo共用一个FrameworkModel,一个FrameworkModel中允许有多个ApplicationModel存在,同时在一个ApplicationModel里面又允许有多个ModuleModel同时存在。同时,ApplicationModel又保留FrameworkModel的引用,ModuleModel也保留了ApplicationModel的引用。(也就是说FrameworkModel和ApplicationModel之间保持了双向引用,ApplicationModel和ModuleModel之间也保持了双向引用) 模型共有的特性 这三个模型都集成自ScopeModel,其中我想重点讲的是classLoaders、extensionScope、extensionDirector这三个。 每个Model其实都有自己的classLoader,他们都通过自己的classLoader来加载类(在没有特殊指定的情况下默认是使用AppClassLoader) extensionScope和extensionDirector配合使用来控制SPI扩展点的的实例(ExtensionLoader)。ExtensionLoader现在不是全局单例的,而是在ExtensionScope维度下是单例的(可以理解是按照ExtensionScope维度对SPI扩展点的加载做了隔离,不能跨scope获取SPI实例)。 模型的差别 这三个模型大致功能相同,不同的点在于功能维度不同。FrameworkModel主要提供的是已经暴露过的Provider信息的查询。ApplicationModel和ModuleModel的差别主要体现在Environment、ServiceRepository、ConfigManager、Deployer上。 ApplicationModel中可以查询整个应用的配置、环境变量,以及应用的初始化。 ModuleModel中只能查当前进程里面的相关应用配置、环境变量,同时他的ModuleModel也只负责服务的暴露和引用。 ConfigManager讲解 Config和Environment是有差别的,前者代表的是写在应用内的配置(可以理解为必须要的配置,比如:想要应用启动那一定要配置registry的配置,想要暴露一个服务就必须定义一个ServiceConfig),后者代表外部化配置(可以理解为一个值的获取方式,比如:RegistryConfig的地址是${spring.zookeeper.url},而它的取值方式可以是多种多样的,从spring、环境变量、JVM变量等等。我们可以通过这种方式,让部分属性值做到根据不同环境来设置不同的值) Dubbo将配置做了抽象,比如:ProtocolConfig、RegistryConfig、ReferenceConfig、ServiceConfig、ApplicationConfig等等。每个Config代表的是Dubbo的配置,比如:注册中心的配置在RegistryConfig中、服务暴露的配置在ServiceConfig中等等。当然也没什么说的,这个类主要是提供配置的查询功能,唯一要注意的是ModuleConfigManager和ConfigManager其实就是能加载的配置不一样(在各自构造函数中限制了能加载那些配置)。 Environment讲解 Dubbo的环境变量有几种来源方式,比如:系统环境变量、Spring环境变量。详情可以参考小马哥的文章 ApplicationDeployer讲解 ApplicationDeployer和ModuleDeployer两个类的创建都需要Model(ApplicationDeployer的创建需要ApplicationModel,ModuleDeployer的创建需要ModuleModel),而我们知道Model是有双向引用的,因此这两个Deployer可以通过Model互通。 在DubboBootstrap中我们也能看到这两个类的互调,比如:org.apache.dubbo.config.deploy.DefaultApplicationDeployer#start -> org.apache.dubbo.config.deploy.DefaultApplicationDeployer#doStart -> org.apache.dubbo.config.deploy.DefaultApplicationDeployer#startModules 通过这个调用链我们可以看到ApplicationDeployer调用ModuleDeployer,org.apache.dubbo.config.deploy.DefaultModuleDeployer#start我们从这个方法里面也能看到很多ApplicationDeployer的身影。 两个Deployer的差别 ApplicationDeployer负责应用的启动和初始化,ModuleDeployer负责服务的暴露和引用。 这两个类的差别其实就是功能上的差别,其他的都是由于功能差别导致的(比如内部属性的差异、接口的差异,这些其实都是由他们的职责差异导致的)。

阅读全文 arrow_forward
2022-03-20 · 约 5 分钟

SpringBoot可执行jar原理

前言 随着SpringBoot的流行,越来越多的企业开始使用SpingBoot。我认为之所以这个框架越来越流行,社区越来越活跃,跟这个框架为开发者带来的便利性有关。它不仅改变了原来我们的编程习惯,简化了业务编程模型,而且还大大的降低了搭建一个应用程序的复杂度。 但随着对这个框架的使用的深入,我发现这个框架远非其表面那么简单,日常所用的SpringBoot的功能特性仅仅是其冰山一角,我不禁有了疑问。SpringBoot到底为我们带来了哪些便利?它与Spring Framework的差别是什么?由于篇幅有限,本文着重讲解SpringBoot的可执行jar,其他特性也会顺带聊一聊。 要了解一个框架,首先要知道这个框架的功能特性,以及框架设计的目的,正所谓“名不正则言不顺”。我第一时间想到的是它的官方文档,官方总结的SpringBoot的特性包括如下: Create stand-alone Spring applications Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files) Provide opinionated ‘starter’ dependencies to simplify your build configuration Automatically configure Spring and 3rd party libraries whenever possible Provide production-ready features such as metrics, health checks, and externalized configuration Absolutely no code generation and no requirement for XML configuration https://spring.io/projects/spring-boot#overview 根据官方文档再结合个人经验总结后的SpringBoot的特性如下: 创建独立运行的Spring应用 — 可执行jar、SpringApplication 嵌入式web容器 —SpringApplication中的一部分 提供固化的starter,简化构建配置 —starter特性 自动装配Spring和第三方的依赖 —自动装配 提供Production-Ready特性(如Metrics、健康检查、外部化配置) 完全的无中间代码生成,也不再需要XML配置 —注解驱动 从上面可以看到SpringBoot可执行jar正是其提供的六大核心功能之一。不知大家是否还记得,在使用SpringMVC构建应用时想要运行自己的应用,必须要先将应用代码打包成一个WAR,再借助一个独立部署的Web容器(Tomcat、Jetty、UnderTow等等)才能运行。可是,当使用SpringBoot时,既不需要将应用打包成WAR,也不需要独立部署的Web容器,反而是直接将应用打包成一个JAR,然后直接通过java -jar命令就能启动。大家是否和我一样有疑问,为什么直接打成JAR就能运行?下面我将揭开它的神秘面纱。(可执行jar只是指jar的运行,本文不讨论嵌入式Web容器相关内容)

阅读全文 arrow_forward
2022-03-20 · 约 2 分钟

Enable模块驱动

前言 很多同学在使用Springboot时都会看到一个注解“@EnableXXX”,该注解用于显示的装配指定的模块,如@EnableScheduling用来装配spring的定时任务模块、@EnableCaching用于激活缓存等等。但是,不知道大家有没有思考过其背后的原理?本篇文章会从代码层面来解释Enable模块驱动的原理,同时也会讲解如何编写自定义的Enable注解来实现Enable模块驱动。 在正式开始讲解之前先讲解“Enable的前世今生”,便于让更加清楚的了解其背后的设计理念。 自定义Enable模块驱动 后面的例子都是围绕CustomServer这个接口来展开,该接口的功能很简单,就是启动和停止“自定义服务器”这两个功能。下面展示的是CustomServer接口的定义,同时也展示了两个该接口的具体实现。 java content_copy 复制代码 public interface CustomServer { void start(); void stop(); } java content_copy 复制代码 public class ServletCustomServer implements CustomServer{ @Override public void start() { System.out.println("servlet custom server start."); } @Override public void stop() { System.out.println("servlet custom server stop."); } } java content_copy 复制代码 public class ReactCustomServer implements CustomServer{ @Override public void start() { System.out.println("react custom server start."); } @Override public void stop() { System.out.println("react custom server stop."); } } 至此,CustomServer模块的功能已经实现完毕,那么接下来的工作就是要通过Enable模块驱动来将CustomServer模块集成到Spring应用中了。

阅读全文 arrow_forward