目录

apollo架构

流程解析(用户界面和服务端(apollo-adminservice)通信过程)

流程解析(服务端(apollo-adminservice)和configservice端通信过程)

流程解析(configservice和client端通信过程)


apollo架构

流程解析(用户界面和服务端(apollo-adminservice)通信过程)

1、用户在前端触发增、删、改操作

2、前端服务会将请求发送至apollo-portal服务(ItemController),在发送请求之前apollo-portal做了以下事情:

  • 验证请求对象各参数有效性(是否为空)以及权限验证,有独立的数据库apolloportaldb
  • 获取用户信息并设置进请求对象中
  • 通过apollo-configservice提供的接口(ServiceController),configservice从注册中心拉取apollo-adminservice的实例列表集合(ip,端口等)并且缓存到本地
  • 通过apollo-adminservic提供的接口(NamespaceController),查询apolloconfigdb库中的namespace表,获取namespaceid设置进请求对象中
  • 发送真正的请求到apollo-adminservice(ItemController)
 @PreAuthorize(value = "@permissionValidator.hasModifyNamespacePermission(#appId, #namespaceName, #env)")@PostMapping("/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/item")public ItemDTO createItem(@PathVariable String appId, @PathVariable String env,@PathVariable String clusterName, @PathVariable String namespaceName,@RequestBody ItemDTO item) {checkModel(isValidItem(item));//验证item.setLineNum(0);item.setId(0);String userId = userInfoHolder.getUser().getUserId();//获取用户信息item.setDataChangeCreatedBy(userId);item.setDataChangeLastModifiedBy(userId);item.setDataChangeCreatedTime(null);item.setDataChangeLastModifiedTime(null);return configService.createItem(appId, Env.valueOf(env), clusterName, namespaceName, item);}

3、apollo-adminservice将数据持久化到apolloconfigdb库的item表中,同时持久化apolloconfigdb库的audit表,同时将本次变更(增删改)的数据转换为json后持久化至apolloconfigdb库commit表

@PreAcquireNamespaceLock@PostMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items")public ItemDTO create(@PathVariable("appId") String appId,@PathVariable("clusterName") String clusterName,@PathVariable("namespaceName") String namespaceName, @RequestBody ItemDTO dto) {Item entity = BeanUtils.transform(Item.class, dto);ConfigChangeContentBuilder builder = new ConfigChangeContentBuilder();Item managedEntity = itemService.findOne(appId, clusterName, namespaceName, entity.getKey());if (managedEntity != null) {throw new BadRequestException("item already exists");} else {entity = itemService.save(entity);builder.createItem(entity);}dto = BeanUtils.transform(ItemDTO.class, entity);Commit commit = new Commit();commit.setAppId(appId);commit.setClusterName(clusterName);commit.setNamespaceName(namespaceName);commit.setChangeSets(builder.build());commit.setDataChangeCreatedBy(dto.getDataChangeLastModifiedBy());commit.setDataChangeLastModifiedBy(dto.getDataChangeLastModifiedBy());commitService.save(commit);return dto;}

流程解析(服务端(apollo-adminservice)和configservice端通信过程)

1、用户在前端触发发布操作

2、前端服务会将请求发送至apollo-portal服务(ReleaseController)

  • 发送真正的请求到apollo-adminservice(ReleaseController)
  • apollo-adminservice查询apolloconfigdb库的item表的当前namespace下所有的items(所有的配置),并且查询当前namespace下的父namespace(如有父)的所有的items
  • 父的items的数据从apolloconfigdb库的release表中查询(json存储),然后合并父(如果有)和子的所有item
  • apollo-adminservice保存上述合并后的父(如果有)子的所有items,保存至apolloconfigdb库的release表,配置以json字段形式保存
  • apollo-adminservice将本次发布的历史记录(操作类型、操作人、操作时间、前后配置等)保存至apolloconfigdb库的releasehistory表
  • apollo-adminservice获取当前namespace存在的子namespace,如果存在则将子namespace的配置也同时获取并做上述的流程(merge、save、save history)
  • apollo-adminservice将appId+cluster+namespace保存至apolloconfigdb库的releasemessage表中,每次发布都会新增一条新数据,并且删除老数据,
    其实就是id会增大,并且保持同一个appid+cluster+namespace只有一条数据,这一步就是apollo-adminservice和configservice通信的连接点,在
    这一步将2个服务串起来了(底下的configservice服务会定时默认1秒扫描一次这个表)。很多场景下我们是用消息队列来做这一步进行解耦,但是apollo这里
    采用了本地数据库的方式,其实和消息队列是一样的。
  • apollo-portal发布事件ConfigPublishEvent(继承自Spring的ApplicationEvent)
  • ConfigPublishListener监听到上述事件后进行异步的发送Email(无需关注)和发消息(ActiveMQ,topic名称为ops.noc.record.created),这里如果我们配置了activemq的话也可以通过监听这个队列来实现配置变更的感知。
@PreAuthorize(value = "@permissionValidator.hasReleaseNamespacePermission(#appId, #namespaceName, #env)")@PostMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/releases")public ReleaseDTO createRelease(@PathVariable String appId,@PathVariable String env, @PathVariable String clusterName,@PathVariable String namespaceName, @RequestBody NamespaceReleaseModel model) {model.setAppId(appId);model.setEnv(env);model.setClusterName(clusterName);model.setNamespaceName(namespaceName);if (model.isEmergencyPublish() && !portalConfig.isEmergencyPublishAllowed(Env.valueOf(env))) {throw new BadRequestException(String.format("Env: %s is not supported emergency publish now", env));}ReleaseDTO createdRelease = releaseService.publish(model);ConfigPublishEvent event = ConfigPublishEvent.instance();event.withAppId(appId).withCluster(clusterName).withNamespace(namespaceName).withReleaseId(createdRelease.getId()).setNormalPublishEvent(true).setEnv(Env.valueOf(env));publisher.publishEvent(event);return createdRelease;}

流程解析(configservice和client端通信过程)

1、apollo-configservice服务中,通过自动装配类ConfigServiceAutoConfiguration在初始化bean ReleaseMessageScanner的过程中做了以下事情:

  • 注册了定时任务的线程池,该线程池默认配置核心线程为1,最大线程为Integer.maxvalue,线程存活时间为10ms,执行周期为每秒(默认,可配置)执行一次。
  • 为ReleaseMessageScanner加入6个listener
@Beanpublic ReleaseMessageScanner releaseMessageScanner() {ReleaseMessageScanner releaseMessageScanner = new ReleaseMessageScanner();releaseMessageScanner.addMessageListener(releaseMessageServiceWithCache);releaseMessageScanner.addMessageListener(grayReleaseRulesHolder);//主要关注这个releaseMessageScanner.addMessageListener(configService);releaseMessageScanner.addMessageListener(configFileController);//主要关注这个releaseMessageScanner.addMessageListener(notificationControllerV2);releaseMessageScanner.addMessageListener(notificationController);return releaseMessageScanner;}
  • 获取最大扫描的全局变量maxIdScanned,此变量的值为服务启动的过程中查询apolloconfigdb库的releasemessage表的当前最大的主键,如果没记录(初次部署apollo的时候)则为0。

2、apollo-configservice执行定时任务:

  • 每次查询apolloconfigdb库的releasemessage表中,大于上述记录中的maxIdScanned值的的500条记录(排好序的),若为空则结束。
  • 将所有扫描到的数据(ReleaseMessage)依次发送给上述1的所有listener串行处理(每个listener处理逻辑不同,只列举一个)
  • 如果单次扫描的有变更的数据量等于500,则继续循环继续扫描,如果小于500则结束循环进行下一步
  • 更新maxIdScanned为实际获取的最后一条message的id
  public ReleaseMessageScanner() {listeners = Lists.newCopyOnWriteArrayList();executorService = Executors.newScheduledThreadPool(1, ApolloThreadFactory.create("ReleaseMessageScanner", true));}@Overridepublic void afterPropertiesSet() throws Exception {databaseScanInterval = bizConfig.releaseMessageScanIntervalInMilli();maxIdScanned = loadLargestMessageId();//注册定时任务executorService.scheduleWithFixedDelay((Runnable) () -> {Transaction transaction = Tracer.newTransaction("Apollo.ReleaseMessageScanner", "scanMessage");try {//具体的扫描逻辑scanMessages();transaction.setStatus(Transaction.SUCCESS);} catch (Throwable ex) {transaction.setStatus(ex);logger.error("Scan and send message failed", ex);} finally {transaction.complete();}}, databaseScanInterval, databaseScanInterval, TimeUnit.MILLISECONDS);} 

3、列举apollo-configservice服务的ConfigServiceWithCache(上述2步骤的listener之一,也是client通信获取服务端数据的数据源)

  • 服务初始化结束,调用initialize方法,此方法初始化了本地缓存的更新策略,使用的是guaua的LoadingCache,具体策略是通过key去查询本地数据库apolloconfigdb库的releasemessage表
  • handleMessage方法,收到上述2发送过来的message数据(当服务启动的时候/配置发布的时候(增删改)),先去缓存LoadingCache中的此key的数据清空失效,然后调用LoadingCache的的getUnchecked方法,这个方法底层的逻辑是先从map中获取值,如果不存在,会执行上述初始化的逻辑,即从数据库去查询数据,在执行getUnchecked方法之前,已经在缓存中删除了此key的数据,所以当收到消息以后会拿着key去数据库查询值。
  • 最终能保证,所有的配置变更都会实时存在此缓存对象中,最终apollo-configservice会对外提供一个接口ConfigController,查询数据的时候会从此缓存中查询,而不访问数据库
@Override
public void handleMessage(ReleaseMessage message, String channel) {logger.info("message received - channel: {}, message: {}", channel, message);if (!Topics.APOLLO_RELEASE_TOPIC.equals(channel) || Strings.isNullOrEmpty(message.getMessage())) {return;}try {//让缓存失效invalidate(message.getMessage());//获取如果没有则从数据库查configCache.getUnchecked(message.getMessage());} catch (Throwable ex) {//ignore}
}

4、client端与服务端的通信的最终原理,就是服务端会调用3步骤中configservice提供的接口/configs/{appId}/{clusterName}/{namespace},具体看下client如何调用的。

5、client调用apollo-configservice的接口

  • ApolloApplicationContextInitializer
//这里才是真正设置application namespace的值进入spring中的地方
protected void initialize(ConfigurableEnvironment environment) {if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {//already initializedreturn;}
//这一行,获取不到这个变量,用默认的,即application namespaceString namespaces = environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_NAMESPACES, ConfigConsts.NAMESPACE_APPLICATION);logger.debug("Apollo bootstrap namespaces: {}", namespaces);List<String> namespaceList = NAMESPACE_SPLITTER.splitToList(namespaces);CompositePropertySource composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME);for (String namespace : namespaceList) {Config config = ConfigService.getConfig(namespace);composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));}environment.getPropertySources().addFirst(composite);}
  • 最终会执行DefaultConfigFactory类中的createLocalConfigRepository方法,创建LocalFileConfigRepository之前先创建RemoteConfigRepository对象
LocalFileConfigRepository createLocalConfigRepository(String namespace) {if (m_configUtil.isInLocalMode()) {logger.warn("==== Apollo is in local mode! Won't pull configs from remote server for namespace {} ! ====",namespace);return new LocalFileConfigRepository(namespace);}return new LocalFileConfigRepository(namespace, //看这里 createRemoteConfigRepository(namespace));}
  • 在RemoteConfigRepository对象的构造方法中会调用trySync、schedulePeriodicRefresh、scheduleLongPollingRefresh三个方法
public RemoteConfigRepository(String namespace) {m_namespace = namespace;m_configCache = new AtomicReference<>();m_configUtil = ApolloInjector.getInstance(ConfigUtil.class);m_httpUtil = ApolloInjector.getInstance(HttpUtil.class);m_serviceLocator = ApolloInjector.getInstance(ConfigServiceLocator.class);remoteConfigLongPollService = ApolloInjector.getInstance(RemoteConfigLongPollService.class);m_longPollServiceDto = new AtomicReference<>();m_remoteMessages = new AtomicReference<>();m_loadConfigRateLimiter = RateLimiter.create(m_configUtil.getLoadConfigQPS());m_configNeedForceRefresh = new AtomicBoolean(true);m_loadConfigFailSchedulePolicy = new ExponentialSchedulePolicy(m_configUtil.getOnErrorRetryInterval(),m_configUtil.getOnErrorRetryInterval() * 8);gson = new Gson();
//先看这里,去同步数据this.trySync();
//这个是默认5分钟调用一次this.trySync();this.schedulePeriodicRefresh();
//这个是默认定时5秒去拉取是否变更的数据,也是最终调用this.trySync();简单理解为5秒调用一次this.trySync();但是不严谨this.scheduleLongPollingRefresh();}
  • trySync方法调用loadApolloConfig方法获取远程配置数据,从服务的启动参数中获取http请求的必要参数,上述4步骤中占位符的参数{appId}/{clusterName}/{namespace},其中appid我们的ApolloApplicationRunListener在Spring的environmentPrepared阶段有做过2次封装,从启动参数获取不到会获取应用的spring.application.name的值作为appid,准备好请求参数后,去请求apollo的metaService下的ServiceController提供的/services/config接口,从eureka中获取apollo-configservice的实例列表,随机找一个机器去访问上述4步骤提供的获取配置接口拿到数据
 @Overrideprotected synchronized void sync() {Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "syncRemoteConfig");try {ApolloConfig previous = m_configCache.get();
//看这里ApolloConfig current = loadApolloConfig();// reference equals means HTTP 304if (previous != current) {logger.debug("Remote Config refreshed!");m_configCache.set(current);this.fireRepositoryChange(m_namespace, this.getConfig());}if (current != null) {Tracer.logEvent(String.format("Apollo.Client.Configs.%s", current.getNamespaceName()),current.getReleaseKey());}transaction.setStatus(Transaction.SUCCESS);} catch (Throwable ex) {transaction.setStatus(ex);throw ex;} finally {transaction.complete();}}
  • RemoteConfigRepository的构造方法注册2个定时任务,第一个schedulePeriodicRefresh是默认5分钟执行一次的请求一次当前namespace的所有config数据,scheduleLongPollingRefresh定时任务是定时5秒去获取apollo-configservice提供的配置变更接口,上述2步骤的其中一个listener(NotificationControllerV2),如果有新的配置变更,则去重新执行remoteRepository的trySync方法。
  • trySync方法调用sync方法,判断RemoteConfigRepository本地缓存对象(ApolloConfig)和loadApolloConfig刚刚拿到的数据是否一致,如果不一致,先替换本地缓存的值为刚刚拿到的数据,再调用fireRepositoryChange方法,此方法里面会去串行执行所有的监听配置变更的监听器去执行自己的onRepositoryChange方法,这个监听器由业务方自己去编写,可以根据实际业务需要编写。apollo默认添加了一个监听器AutoUpdateConfigChangeListener(后面会赘述)
@Overrideprotected synchronized void sync() {Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "syncRemoteConfig");try {ApolloConfig previous = m_configCache.get();ApolloConfig current = loadApolloConfig();// reference equals means HTTP 304if (previous != current) {
//不一样,说明有变更(增/删/改)logger.debug("Remote Config refreshed!");m_configCache.set(current);
//记住这里,这里很重要,而且很绕,现在先不用深究细节this.fireRepositoryChange(m_namespace, this.getConfig());}if (current != null) {Tracer.logEvent(String.format("Apollo.Client.Configs.%s", current.getNamespaceName()),current.getReleaseKey());}transaction.setStatus(Transaction.SUCCESS);} catch (Throwable ex) {transaction.setStatus(ex);throw ex;} finally {transaction.complete();}}
  • ApolloApplicationContextInitializer类中的initialize,放在了spring管理的MutablePropertySource对象中去。
protected void initialize(ConfigurableEnvironment environment) {if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {//already initializedreturn;}String namespaces = environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_NAMESPACES, ConfigConsts.NAMESPACE_APPLICATION);logger.debug("Apollo bootstrap namespaces: {}", namespaces);List<String> namespaceList = NAMESPACE_SPLITTER.splitToList(namespaces);CompositePropertySource composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME);for (String namespace : namespaceList) {Config config = ConfigService.getConfig(namespace);//看这里      composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));}environment.getPropertySources().addFirst(composite);}
  • PropertySourceProcessor类在服务初始化过程中为当前应用添加了配置变更监听器AutoUpdateConfigChangeListener,当运行过程中有变更的时候都会通知此监听器,注意,此监听器在服务启动过程中并未注册,因为还没启动成功,只有全量拉取。
//注意这里也很重要,和上面的this.fireRepositoryChange(m_namespace, this.getConfig());有关系
private void initializeAutoUpdatePropertiesFeature(ConfigurableListableBeanFactory beanFactory) {if (!configUtil.isAutoUpdateInjectedSpringPropertiesEnabled() ||!AUTO_UPDATE_INITIALIZED_BEAN_FACTORIES.add(beanFactory)) {return;}AutoUpdateConfigChangeListener autoUpdateConfigChangeListener = new AutoUpdateConfigChangeListener(environment, beanFactory);List<ConfigPropertySource> configPropertySources = configPropertySourceFactory.getAllConfigPropertySources();for (ConfigPropertySource configPropertySource : configPropertySources) {configPropertySource.addChangeListener(autoUpdateConfigChangeListener);}}
  • AutoUpdateConfigChangeListener收到的是增量的配置变更,会调用自身的onChange方法,在此方法中会循环所有变更的配置,以此对spring bean里面的值进行反射的替换。

6、在client接收到服务端的配置变更后会有一系列的监听器的监听和处理,主要有ConfigChangeListener和RepositoryChangeListener2种类型,大致的顺序和过程如下

  • 首先由RemoteConfigRepository这个listener接收到这个namespace有变更(注意拿到的是所有的变更数据包含已变更的和未变更)
  • RemoteConfigRepository会调用自身的fireRepositoryChange方法,此方法由父类AbstractConfigRepository定义和实现,同时LocalFileConfigRepository也实现了AbstractConfigRepository
  • 我们在上述第5步骤的第二小点介绍过,创建LocalFileConfigRepository对象的同时会先去创建RemoteConfigRepository类,并且将自身(this)作为监听器注册到RemoteConfigRepository对象中
public LocalFileConfigRepository(String namespace, //注意这个对象就是上述的RemoteConfigRepository ConfigRepository upstream) {m_namespace = namespace;m_configUtil = ApolloInjector.getInstance(ConfigUtil.class);this.setLocalCacheDir(findLocalCacheDir(), false);//再看这里this.setUpstreamRepository(upstream);this.trySync();}@Overridepublic void setUpstreamRepository(ConfigRepository upstreamConfigRepository) {if (upstreamConfigRepository == null) {return;}//clear previous listenerif (m_upstream != null) {m_upstream.removeChangeListener(this);}m_upstream = upstreamConfigRepository;trySyncFromUpstream();‘//看这里,记住了,给RemoteConfigRepository里面加了一个LocalFileConfigRepository的listenerupstreamConfigRepository.addChangeListener(this);}
  • 所以RemoteConfigRepository类此时是有一个listener对象的,即为LocalFileConfigRepository,这时候会调用进入LocalFileConfigRepository的onRepositoryChange方法中,做了2件事,先更新本地文件的配置缓存,第二调用自身的fireRepositoryChange方法,本地文件这个不需要太多关注,猜测可能是因为担心服务器挂了的一种兜底吧。主要看LocalFileConfigRepository的fireRepositoryChange
  • 同样的也是调用父类的默认实现,还是找监听器,上述步骤5中的第三小节,createLocalConfigRepository是在DefaultConfigFactory调用工厂方法create的时候执行的调用,在这之前new 了一个DefaultConfig对象,在这个构造方法中,传入的ConfigRepository是上面的LocalFileConfigRepository对象,然后接下来在finally中将当前对象(this)添加进去LocalFileConfigRepository的listener。所以,上述LocalFiIeConfigRepository调用自身的fireRepositoryChange其实是进入到了DefaultConfig类中的onRepositoryChange方法
@Overridepublic Config create(String namespace) {ConfigFileFormat format = determineFileFormat(namespace);if (ConfigFileFormat.isPropertiesCompatible(format)) {return new DefaultConfig(namespace, createPropertiesCompatibleFileConfigRepository(namespace, format));}
//看这里new DefaultConfigreturn new DefaultConfig(namespace, createLocalConfigRepository(namespace));}//构造方法调用了这里private void initialize() {try {updateConfig(m_configRepository.getConfig(), m_configRepository.getSourceType());} catch (Throwable ex) {Tracer.logError(ex);logger.warn("Init Apollo Local Config failed - namespace: {}, reason: {}.",m_namespace, ExceptionUtil.getDetailMessage(ex));} finally {
//看这里记住了吗,在当前LocalFileConfigRepository对象中加入了DefaultConfig为listenerm_configRepository.addChangeListener(this);}}
  • 到这一步其实已经有变化了,到目前为止DefaultConfig类拿到的还是全量的配置数据,我们可以看见DefaultConfig类中的onRepositoryChange方法调用的是他的父类AbstractConfig中的fireConfigChange,在这里调用的方法名变了,从fireRepositoryChange变为了fireConfigChange,在这里我们可以看见DefaultConfig类中的onRepositoryChange方法通过自身updateAndCalcConfigChanges方法计算出来本次变更涉及的key都有哪些,变更类型是什么(增/删/改),最终封装成一个Map<String,ConfigChange>对象,并且更新内存里面的缓存。
@Overridepublic synchronized void onRepositoryChange(String namespace, Properties newProperties) {if (newProperties.equals(m_configProperties.get())) {return;}ConfigSourceType sourceType = m_configRepository.getSourceType();Properties newConfigProperties = new Properties();newConfigProperties.putAll(newProperties);Map<String, ConfigChange> actualChanges = updateAndCalcConfigChanges(newConfigProperties, sourceType);if (actualChanges.isEmpty()) {return;}this.fireConfigChange(new ConfigChangeEvent(m_namespace, actualChanges));Tracer.logEvent("Apollo.Client.ConfigChanges", m_namespace);}
  • 最后发布一个一个ConfigChangeEvent事件,调用AbstractConfig中的fireConfigChange方法,上述第5步骤倒数第二小节已经说过了,apollo已经将此监听器AutoUpdateConfigChangeListener加入进来了,并且只有此一个监听器,直接调用AutoUpdateConfigChangeListener类的onchange方法,注意前面所有的监听器的执行都是串行的,此步骤是异步线程池执行的。
protected void fireConfigChange(final ConfigChangeEvent changeEvent) {for (final ConfigChangeListener listener : m_listeners) {// check whether the listener is interested in this change eventif (!isConfigChangeListenerInterested(listener, changeEvent)) {continue;}m_executorService.submit(new Runnable() {@Overridepublic void run() {String listenerName = listener.getClass().getName();Transaction transaction = Tracer.newTransaction("Apollo.ConfigChangeListener", listenerName);try {listener.onChange(changeEvent);transaction.setStatus(Transaction.SUCCESS);} catch (Throwable ex) {transaction.setStatus(ex);Tracer.logError(ex);logger.error("Failed to invoke config change listener {}", listenerName, ex);} finally {transaction.complete();}}});}}
  • 最终就是循环所有变更的key去一一替换spring容器中的值

apollo全流程、原理、源码解析相关推荐

  1. c++ socket线程池原理_ThreadPoolExecutor线程池实现原理+源码解析

    推荐学习 被微服务轰炸?莫怕!耗时35天整出的「微服务学习教程」送你 死磕「并发编程」100天,全靠阿里大牛的这份最全「高并发套餐」 闭关28天,奉上[Java一线大厂高岗面试题解析合集],备战金九银 ...

  2. HDFS追本溯源:HDFS操作的逻辑流程与源码解析

    转自:http://www.it165.net/admin/html/201404/2726.html 本文主要介绍5个典型的HDFS流程,这些流程充分体现了HDFS实体间IPC接口和stream接口 ...

  3. apollo local 模式_Apollo 源码解析 —— 客户端配置 API(一)之一览

    摘要: 原创出处 http://www.iocoder.cn/Apollo/client-config-api-1/ 「芋道源码」欢迎转载,保留摘要,谢谢! 1. 概述 本文,我们来一览 Apollo ...

  4. 集合深度学习07—Set、HashSet、LinkedHashSet、TreeSet、 底层原理 源码解析

    一.Set接口 特点: 唯一 无序(相对List接口部分来说的,无序不等于随机) 没有索引相关的方法 遍历方式: 迭代器 增强 for 循环(底层还是 Itr 迭代器) 二.HashSet 1. Ha ...

  5. feign扫描_feign原理+源码解析

    1 架构图. 2 feign的初始化扫包原理. (1)feign使用,是main上的@EnableFeignClients 和 api的@FeignClient(value = "servi ...

  6. 源码解析 --skywalking agent 插件加载流程

    1. 插件 目前很多框架,都采用框架 + 插件的模式开发. 如DataX.FlinkX通过插件支持众多异构数据源, Skywalking通过插件实现针对很多软件如redis.mysql.dubbo等方 ...

  7. webpack那些事:浅入深出-源码解析构建优化

    基础知识回顾 入口(entry) module.exports = {entry: './path/to/my/entry/file.js' }; //或者 module.exports = {ent ...

  8. 并发编程与源码解析 (三)

    并发编程 (三) 1 Fork/Join分解合并框架 1.1 什么是fork/join ​ Fork/Join框架是JDK1.7提供的一个用于并行执行任务的框架,开发者可以在不去了解如Thread.R ...

  9. Alibaba-AndFix Bug热修复框架原理及源码解析

    小憩之后,继续为你解读AndFix热修复框架,呵呵. 上一篇Alibaba-AndFix Bug热修复框架的使用已经介绍了AndFix的使用,这篇主要介绍AndFix原理以及源码解析. AndFix原 ...

  10. SpringBoot入门-源码解析(雷神)

    一.Spring Boot入门 视频学习资料(雷神): https://www.bilibili.com/video/BV19K4y1L7MT?p=1 github: https://github.c ...

最新文章

  1. 封装时间转换工具类_推荐一款封装各种Util工具类,这款神仙级框架你值得拥有!...
  2. linux修改密码和宽限天数,Linux chage用法详解:修改用户密码状态
  3. vue开发(2) 使用vue-cli来构建项目
  4. c++用什么软件编程_为什么要学习“C”编程语言?
  5. mysql 字典索引_【大白话mysql】你真的了解 mysql 索引吗?
  6. C++语言基础 —— STL —— 容器与迭代器 —— map 与 multimap
  7. 深度学习可解释性!深度taylor分解
  8. LeetCode--27. 移除元素(双指针)
  9. java-pdf转word,java开发面试笔试题
  10. 贪心----汽车加油问题
  11. 浏览器输入网址到页面呈现的过程
  12. PHP无限极分类巧用引用生成树
  13. 暑假前挑战赛1—— A,B题解
  14. 微服务框架自带uuid生成器
  15. API文档打开显示'已取消到该网页的导航'的解决方法
  16. 【“玩物立志”-scratch少儿编程】亲手实现小猫走迷宫小游戏:其实挺简单
  17. 能测试手机信号不好的软件,买手机别只看性能!教你测试手机信号好坏
  18. [Matlab科学计算] 四阶Runge-Kutta法解常微分方程
  19. 手机遇到性能BUG怎么破?
  20. 爬取贝壳网的40000条基本数据

热门文章

  1. R语言使用data.table包中的merge函数连接(内连接)两个dataframe数据(Inner join)
  2. 电脑操作精典密芨60式【实用】
  3. chrome 新浪微博分享插件
  4. 新年快到了,送给大家这三大成功定律
  5. Winform如何进行左边菜单栏,右边内容的设计
  6. Extreme Programming (XP)实践
  7. [控制基础] 定时器TIM的PWM输出+用积分思想分析PWM对直流减速有刷电机的控制(基于STM32F103+CubeMX+HAL)
  8. MySQL数据库查基础命令
  9. 阿里云天池Python训练营
  10. App - Download