关于apollo:AREX-Agent-如何实现-Apollo-配置中心-Mock

背景对于 AREXAREX 是基于实在申请与数据的自动化回归测试平台,利用 Java Agent 和字节码加强技术,在生产环境中记录实在申请链路的入口和依赖的申请和响应数据,而后在测试环境中进行模仿申请回放,并逐个验证整个调用链路的逻辑正确性。AREX Agent 当初曾经反对了大部分开源组件的 Mock,本文将介绍 Agent 如何实现 Apollo 配置核心的 Mock。 对于 ApolloApollo(阿波罗)是一款牢靠的分布式配置管理核心,诞生于携程框架研发部,可能集中化治理利用不同环境、不同集群的配置,配置批改后可能实时推送到利用端。 以下是官网对 Apollo 根底模型的形容: 用户在配置核心对配置进行批改并公布;配置核心告诉 Apollo 客户端有配置更新;Apollo 客户端从配置核心拉取最新的配置、更新本地配置并告诉到利用。实现原理下图简要形容了 Apollo 客户端的实现原理: 客户端和服务端放弃了一个长连贯,从而能第一工夫取得配置更新的推送。(通过 Http Long Polling 实现)客户端还会定时从 Apollo 配置核心服务端拉取利用的最新配置。客户端从 Apollo 配置核心服务端获取到利用的最新配置后,会保留在内存中 图片起源:https://www.apolloconfig.com/#/zh/design/apollo-design开发过程从上图可知 AREX 只须要反对 Apollo 客户端的录制和回放,即 Java 利用我的项目外部援用 apollo-client 的组件: <dependency> <groupId>com.ctrip.framework.apollo</groupId> <artifactId>apollo-client</artifactId> <version>{apollo-client.version}</version></dependency>通常,我的项目中应用 Apollo 的形式次要有以下三种: Spring Autowired 注解 configBean (外部还是应用 EnableApolloConfig 注解)基于 Apollo 自带的注解 ApolloConfig,如代码中的 config 对象API 形式,如代码中的 config1 对象:<!----> @AutowiredConfigBean configBean; // 第一种形式,外部基于 EnableApolloConfig 注解 @ApolloConfig("TEST1.lucas")private Config config; // 第二种形式private Config config1; // 第三种形式,在代码中调用 getAppConfig 实例化public void test() { config1 = ConfigService.getAppConfig(); System.out.println("timeout="+config.getProperty("timeout", "0")); System.out.println("switch="+config.getBooleanProperty("switch", false)); System.out.println("json="+config.getProperty("json", "")); System.out.println("white.list="+config1.getProperty("flight.change.white.list", "")); System.out.println("configBean="+configBean); // 监听 Apollo 配置变更 ConfigChangeListener changeListener = changeEvent -> { System.out.println("Changes for namespace:" + changeEvent.getNamespace()); }; config.addChangeListener(changeListener);}@Component@Configuration@EnableApolloConfig("TEST1.sofia")public class ConfigBean { @Value("${age:0}") int age; @Value("${name:}") String name; @ApolloJsonValue("${resume:[]}") private List<JsonBean> jsonBean;}如果 AREX 须要实现 Apollo 的录制和回放就要兼容这3种应用形式,通过查看 Apollo 源码发现前两种基于注解 EnableApolloConfig,ApolloConfig 和最初一种调用 API 的形式底层都是通过 ConfigService.getAppConfig() 创立的实例,也就是说底层 API 是共用的,这样咱们就能够润饰这些 Apollo 底层的办法插入 AREX 的字节码,达到录制和回放的目标。 ...

September 20, 2023 · 2 min · jiezi

关于apollo:聊聊如何利用apollo与druid整合实现数据源动态热切

前言本文的素材起源与某次和敌人技术交换,过后敌人就跟我吐槽说apollo不如nacos好用,而且他们还因为apollo产生过一次线上事变。 故事的背景大略是如下 前阵子敌人部门的数据库产生宕机,导致业务无奈失常操作,过后敌人他们数据库信息是配置在apollo上,敌人的想法是当数据库宕机时,能够通过切换配置在apollo上的数据库信息,实现数据源热变更。但当他们数据库产生宕机时,敌人按他的想法操作,发现事件并不像他设想的那样,他们更换数据源后,发现业务服务连贯依然是旧的数据库服务,前面没方法他们只能分割dba解决。 后边我听了敌人的形容后,我就问他说,你们过后数据库热切是怎么做的,他的答复是:很简略啊,就把数据源信息配置在apollo上,如果要变更数据源,就间接在apollo的portal上变更一下啊。听了敌人话,我就问而后呢?敌人的答复是:什么而后?就没而后了啊。 通过那次交换,就有了明天的文章,明天咱们就来聊聊apollo与druid整合实现数据源动静热切 实现外围思路apollo的配置变更动静监听 + spring AbstractRoutingDataSource预留办法determineCurrentLookupKey来做数据源切换 在介绍实现外围逻辑之前,咱们来聊一下配置核心 何为配置核心?配置核心是一种对立治理各种利用配置的根底服务组件。他的外围是对配置的对立治理。他治理的领域是配置,至于对配置有依赖的对象,比方数据源,他是不归配置核心来治理。为什么我会独自提这个?是因为敌人仿佛陷入了一个误区,认为在apollo上变更了配置,这个配置依赖的数据源也会一起跟着变更 外围代码1、创立动静数据源,代理原来的datasourcepublic class DynamicDataSource extends AbstractRoutingDataSource { public static final String DATASOURCE_KEY = "db"; @Override protected Object determineCurrentLookupKey() { return DATASOURCE_KEY; } public DataSource getOriginalDetermineTargetDataSource(){ return this.determineTargetDataSource(); }}@Configuration@EnableConfigurationProperties(BackupDataSourceProperties.class)@ComponentScan(basePackages = "com.github.lybgeek.ds.switchover")public class DynamicDataSourceAutoConfiguration { @Bean @ConditionalOnMissingBean @Primary @ConditionalOnClass(DruidDataSource.class) public AbstractDataSourceManger abstractDataSourceManger(DataSourceProperties dataSourceProperties, BackupDataSourceProperties backupDataSourceProperties){ return new DruidDataSourceManger(backupDataSourceProperties,dataSourceProperties); } @Bean("dataSource") @Primary @ConditionalOnBean(AbstractDataSourceManger.class) public DynamicDataSource dynamicDataSource(AbstractDataSourceManger abstractDataSourceManger) { DynamicDataSource source = new DynamicDataSource(); DataSource dataSource = abstractDataSourceManger.createDataSource(false); source.setTargetDataSources(Collections.singletonMap(DATASOURCE_KEY, dataSource)); return source; }}这边有个须要留神的点就是DynamicDataSource的bean名称肯定是须要为dataSource,目标是为了让spring默认的datasource取到的bean是DynamicDataSource ...

January 31, 2023 · 2 min · jiezi

关于apollo:apollo一什么是apollo

(一)什么是apollo前言携程开源的apollo配置管理核心,反对不同环境、不同集群的配置管理,配置的同步简直是实时的,且具备欠缺的权限治理、混滚和灰度治理等机制,适宜作为微服务环境下的配置管理。 apollo作为java生态内的品质不错的组件,对spring框架反对良好,在业内很多公司都将其利用到本人的架构设计内,因而学习它的实现形式,对咱们深刻了解什么是配置核心、转化利用业务开发是有帮忙的。 浏览apollo源码,我想搞清楚以下几个问题: apollo如何治理不同环境的配置?apollo如何做到配置实时更新的?apollo怎么实现灰度机制?apollo如何做到高可用,CAP模型是怎么的?什么是配置代码运行的输出参数,有代码运行环境的辨别,且可能会产生变动,须要从代码内独立进去。 为何须要配置管理核心?后面对于程序的了解 参考资料apollo官网文档:https://www.apolloconfig.com/...

November 19, 2022 · 1 min · jiezi

关于apollo:SpringBoot-使用-Apollo

筹备工作JavaJava 版本要求 1.8+,可通过如下命令查看: java -version样例输入: java version "1.8.0_102"Java(TM) SE Runtime Environment (build 1.8.0_102-b14)Java HotSpot(TM) 64-Bit Server VM (build 25.102-b14, mixed mode)MySQLMySQL 版本要求 5.6.5+,MySQL 原生客户端连贯数据库后可通过如下命令查看: select version();样例输入: +------------+| version() |+------------+| 5.7.39-log |+------------+下载 Quick Start 安装包Github 下载地址 https://github.com/apolloconf...百度网盘下载地址 https://pan.baidu.com/s/1Ieel... 提取码:9wwe 装置步骤创立数据库在 MySQL 中创立 ApolloPortalDB 和 ApolloConfigDB 两个数据库: drop database if exists `ApolloPortalDB`;create database `ApolloPortalDB` default character set utf8 collate utf8_general_ci;drop database if exists `ApolloConfigDB`;create database `ApolloConfigDB` default character set utf8 collate utf8_general_ci;导入数据MySQL 原生客户端连贯数据库后向两个数据库中导入数据: ...

August 1, 2022 · 2 min · jiezi

关于apollo:docker安装多环境Apollo

Apollo介绍Apollo(阿波罗)是携程框架部门研发的分布式配置核心,可能集中化治理利用不同环境、不同集群的配置,配置批改后可能实时推送到利用端,并且具备标准的权限、流程治理等个性,实用于微服务配置管理场景。 应用docker装置Apollo因为工作须要,须要应用Apollo作为零碎的配置核心。装置Apollo有很多种形式,能够循序渐进的依据官网文档一步步的装置。然而大多数的程序员对Apollo是不太熟悉的,所以docker就是一种比较简单的装置形式。如果对docker不理解能够先适当的相熟一下docker。本文以Ubuntu零碎为例,装置一个领有两个环境(DEV,PRO)的Apollo配置零碎。心愿对大家有所帮忙。 环境筹备前置装置既然是用docker装置Apollo,那必定须要先装置docker环境。以Ubuntu零碎为例,咱们能够利用Apt命令来装置docker环境。sudo apt install docker 显示装置胜利之后能够应用docker命令来测试一下是否装置胜利。 前置环境筹备Mysql,Apollo的数据是存储在Mysql中的,所以咱们须要一个MySql环境,能够应用docker装置,也能够应用曾经存在的MySql实例。在此就不过多形容做MySql装置的过程了。 创立数据库https://github.com/ctripcorp/apollo/blob/master/scripts/sql/apolloportaldb.sqlhttps://github.com/ctripcorp/apollo/blob/master/scripts/sql/apolloconfigdb.sql 留神:apolloportaldb.sql为portal数据库脚本,apolloconfigdb.sql为环境配置数据库脚本。 portal数据库只须要一个,然而如果须要装置多环境的Apollo的话,须要筹备多个config数据库。能够在apolloconfigdb.sql中稍作批改。 DEV环境数据库名:ApolloConfigDBDevPRO环境数据库名:ApolloConfigDBPro批改数据库表在创立为三个数据库之后,咱们须要改变一下数据库中的两个表。 ApolloPortalDB中的ServerConfig表中,有一行apollo.portal.envs。默认是dev,咱们须要把他改成两个环境并且用逗号隔开:dev,pro。ApolloPortalDB中的ServerConfig表中,有一行apollo.portal.meta.servers。默认为空,咱们须要把他改成{http://{IP}:8080}。留神这边的IP能够是外网地址。ApolloConfigDBDev数据库中的的ServerConfig表,中有一行为eureka.service.url,把他的value字段改成http://{IP}:8080/eureka/。因为DEV环境默认的端口号为8080。ApolloConfigDBPro数据库中的的ServerConfig表,中有一行为eureka.service.url,把他的value字段改成http://{IP}:8083/eureka/。因为DEV环境默认的端口号为8083。 这边提一下,各个环境的默认端口号开启dev环境, 默认端口: config 8080, admin 8090开启fat环境, 默认端口: config 8081, admin 8091开启fat环境, 默认端口: config 8081, admin 8091开启uat环境, 默认端口: config 8082, admin 8092开启pro环境, 默认端口: config 8083, admin 8093自此咱们的筹备工作都曾经做好了,上面开始正式的进入装置步骤。 docker装置Apollodocker pull idoop/docker-apollodocker run --net="host" --name apollo -d \ -e PORTAL_DB='jdbc:mysql://{MySqlIP}:{MySqlPort}/ApolloPortalDB?characterEncoding=utf8' \ -e PORTAL_DB_USER='root' \ -e PORTAL_DB_PWD=‘pwd‘ \ -e DEV_DB='jdbc:mysql://{MySqlIP}:{MySqlPort}/ApolloConfigDBDev?characterEncoding=utf8' \ -e DEV_DB_USER='root' \ -e DEV_DB_PWD='pwd' \ -e PRO_DB='jdbc:mysql://{MySqlIP}:{MySqlPort}/ApolloConfigDBPro?characterEncoding=utf8' \ -e PRO_DB_USER='root' \ -e PRO_DB_PWD='pwd' \ idoop/docker-apollo:latest 首先第一步是下载apollo的镜像,第二步就是正式启动咱们的Apollo命令。 ...

July 13, 2022 · 1 min · jiezi

关于apollo:硬核AI技术叠加无限创意百度文心大模型产业落地加速

随着数据的井喷、算法的提高和算力的冲破,成果好、泛化能力强、通用性强的预训练大模型(以下简称“大模型”)已成为人工智能倒退的新方向。4月16日,2021百度认知 AI 创意赛“AI 创意派”决赛正式举办,这是业内首次将先进的 AI 大模型能力凋谢给公众应用,实现了大模型在激发创意、落地利用上的冲破。文心大模型首场技术开放日也同期举办,文心大模型背地的“技术天团”首次集中亮相,解读大模型有限创意背地的硬核 AI 技术。 大模型助力创意利用,为创意插上腾飞羽翼 元宇宙虚构人生、AI 版“反诈 App”、续写一本小说、神奇动物都有啥……这些趣味 DEMO 和落地计划,均是由 AI 创意派参赛选手基于百度飞桨文心大模型开发发明的。据统计,本次 AI 创意赛共吸引全国各地近2000名不同年龄、不同技术根底的选手参加其中,收集超过300份创意计划,作品笼罩教育、医疗、金融、娱乐、科技、心理健康等多个畛域。最终,开发创意组9组选手怀才不遇闯入决赛,最终“说文”、“图言”、“AI 起论文题目”等我的项目摘得大奖。 百度团体副总裁吴甜在较量现场示意,“创意在民间。人工智能和大模型要面向公众凋谢,只有门槛低到了所有人都可不便地用起来,能力真正大规模暴发出各种创意。这是文心大模型的致力方向。文心源于产业实际,也将在产业中宽泛落地。本次创意赛中,选手展示了基于文心大模型的丰盛创意,让咱们看到了大模型广泛应用的前景。” 本次创意赛邀请了中国科学技术协会专家库科普实体资源开发类专家白藕,天津大学北洋讲席传授、智能与计算学部副主任兼人工智能学院院长胡清华,哈尔滨工业大学计算机科学与技术学院人工智能业余负责人张宇,北方科技大学迷信与人类想象力钻研核心主任吴岩,线性资本董事总经理黄松延,百度技术委员会主席吴华,百度飞桨总架构师于佃海,百度高校合作部总监李轩涯等来自产业、学术以及投资方等各界嘉宾作为评委专家,对参赛我的项目做出了精彩的点评。多位评委专家示意,大模型是目前 AI 行业倒退的新趋势。通过本次创意赛,开掘了多支具备开发和创意双重实力的参赛队伍,丰盛了大模型落地的设想空间。 取得本次 AI 创意赛冠军“顶尖派”的参赛我的项目“说文”的代表卓君示意,百度飞桨凋谢了 ERNIE 3.0 常识加强大模型能力,不仅让咱们领会到了大模型的真正实力,也让每一位对 AI 感兴趣的人都能够充分发挥创造力,去实现对于文字、语言的创意想法。 自2019年,百度便深耕大模型研发并胜利打造文心大模型家族。目前,文心大模型已成为产业级常识加强大模型,蕴含 NLP(自然语言了解)大模型、CV(计算机视觉)大模型、跨模态大模型,既有根底通用的大模型,也蕴含面向重点畛域、重点工作的大模型,以及丰盛的工具与平台。 与行业其余大模型相比,文心大模型具备“常识加强”的外围特色。文心大模型基于大规模常识和海量无构造数据的交融深度学习,一直排汇文本数据中词汇、构造、语义等方面的常识,使得文心大模型的理解能力和生成能力一直进化晋升,并面向不同人群提供丰盛的工具组件及平台,助力各行各业的创意利用。 以此次创意赛所应用的 ERNIE 3.0 为例,作为 NLP 大模型中的一员,不仅领有很强的语言理解能力,还可能实现视频、歌词、艺术画等创意作品的主动生成,用 AI 为创意插上腾飞羽翼。尤其是在 AIGC(AI generated content,人工智能发明内容)畛域,借助大模型的跨模态综合技术能力,能够激发创意,晋升内容多样性,升高制作老本,实现大规模利用。如在本次AI创意赛中,就涌现了像传记生成、高考意愿举荐、心理树洞等诸多创意利用。 吴甜还演示了近期在海内引发关注的“虎年春早晨的中国文化”视频,该视频由百度研究院的智能视频合成平台 VidPress 主动生成,背地就应用了文心大模型的文本摘要、语义剖析和跨模态了解等多项技术,关上了 AIGC 智能创作设想空间。 在现场的大模型互动区,观众也能够亲自体验到大模型在歌词生成、画作生成和对话机器人等利用,同时文创集市上也展示了基于大模型能力生成的T恤、马克杯、帆布袋、画册、明信片等创意周边,切身感受大模型在创意畛域的落地利用。 深耕产业级大模型,文心大模型实现技术、落地双当先 作为国内最早开始进行大模型研发的科技企业之一,百度在2019年3月率先公布中国首个正式凋谢的预训练模型 ERNIE1.0,并继续投入于大模型的研发降级。2021年12月,ERNIE 3.0 降级为寰球首个常识加强千亿大模型,成为目前为止寰球最大的中文单体模型,在60多项权威的自然语言了解和生成工作上获得了世界领先成果。 目前,文心系列模型中已有多个大模型达到世界领先水平。例如,百度打造了寰球首个百亿参数中英文对话大模型 PLATO-XL,首个聚焦中英文场景大规模 OCR 结构化预训练模型 VIMER - StrucTexT,寰球最大规模中文跨模态生成模型 ERNIE-ViLG 等。 超大规模模型的训练和推理,给深度学习框架带来很大考验。在文心大模型的背地,我国首个自主研发、功能丰富、开源凋谢的产业级深度学习平台飞桨提供了无力撑持。飞桨研制了端到端自适应分布式训练框架,实现多硬件反对,并行效率高达90%,无效反对文心大模型高效、稳固训练。基于飞桨与百舸集群,文心大模型在算法、框架、算力层面实现齐全自主技术创新,为中国产业智能化转型打造 AI 大底座。 ...

April 19, 2022 · 1 min · jiezi

关于apollo:百度Apollo系统学习01-安装Apollo

Apollo学习001: 装置Apollo本文旨在具体记录 Apollo在个人电脑的 Ubuntu 18.04 零碎中的残缺装置及运行过程。 硬件要求百度Apollo零碎失常须要装置在工业计算机IPC中,在个人电脑上也能够装置。因为网上没有查到具体的硬件要求,这里PO出集体笔记本电脑配置作为参考:电脑型号:神州战神z7m,CPU i7, 16G内存,GPU: GTX965m。实测能够跑通。(PS:Unbutu零碎须要空间大一些,实测200个G的空间装完并且将Apollo齐全编译后仅剩50G) Apollo装置过程装置Nvidia显卡驱动。 sudo apt-get updatesudo apt-add-repository multiversesudo apt-get updatesudo apt-get install nvidia-driver-455装置结束采纳nvidia-smi 命令查看显卡: Wed Aug 25 21:55:21 2021 +-----------------------------------------------------------------------------+| NVIDIA-SMI 460.73.01 Driver Version: 460.73.01 CUDA Version: 11.2 ||-------------------------------+----------------------+----------------------+| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC || Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. || | | MIG M. ||===============================+======================+======================|| 0 GeForce GTX 965M Off | 00000000:01:00.0 Off | N/A || N/A 61C P0 N/A / N/A | 438MiB / 2002MiB | 25% Default || | | N/A |+-------------------------------+----------------------+----------------------+ +-----------------------------------------------------------------------------+| Processes: || GPU GI CI PID Type Process name GPU Memory || ID ID Usage ||=============================================================================|| 0 N/A N/A 1515 G /usr/lib/xorg/Xorg 216MiB || 0 N/A N/A 1771 G /usr/bin/gnome-shell 26MiB || 0 N/A N/A 3659 G ...AAAAAAAAA= --shared-files 16MiB || 0 N/A N/A 4582 G ...AAAAAAAAA= --shared-files 133MiB || 0 N/A N/A 7588 G ...AAAAAAAAA= --shared-files 7MiB || 0 N/A N/A 10792 G ...AAAAAAAAA= --shared-files 27MiB |+-----------------------------------------------------------------------------+装置docker ...

February 22, 2022 · 2 min · jiezi

关于apollo:百度Apollo参编首批汽车信息安全国家标准跻身车联网信息安全第一梯队

2021年10月11日,国家市场监督管理总局(国家标准化治理委员会)批准公布了602项国家标准,其中包含汽车信息安全畛域首批4项国家标准:GB/T 40855-2021《电动汽车近程服务与管理系统信息安全技术要求及试验办法》、GB/T 40856-2021《车载信息交互零碎信息安全技术要求及试验办法》、GB/T 40857-2021《汽车网关信息安全技术要求及试验办法》,和GB/T 40861-2021《汽车信息安全通用技术要求》,4项规范笼罩了网联车辆从整车到重要域控制器的平安防护措施,为今后我国车联网的平安倒退打下了坚实基础,为我国汽车产品的信息安全提供了无力保障。 作为我国最早一批布局智能网联汽车行业信息安全的企业,百度Apollo此次便以参编单位的身份深度参加了GB/T 40856-2021《车载信息交互零碎信息安全技术要求及试验办法》的起草工作,依靠本身在车机产品安全方面的多年积攒助力规范制订行文。据悉,该办法将于2022年5月1日正式施行,然而早在往年3月至6月,也就是该规范的合乎性验证阶段,百度Apollo的车机产品便齐全满足了规范中各项条款的需要,成为业内最为先进和成熟的智能座舱平安防护计划之一。针对上述规范,百度Apollo信息安全专家也基于打造的汽车平安引擎,就车辆软硬件、通信协议与接口、操作系统、应用软件和数据等智能汽车信息安全内容做出了业余解读。从TARA剖析、平安开发、整车及要害ECU平安测试,到VSOC平安监测中心的建设,百度车联网紧跟国内外日益严苛的车辆信息安全准入要求,打造了一套“点线面体”多向发力的进攻体系,以此满足车企和Tier1的准入合规需要,保证车辆全生命周期的平安防护能力。 通过TARA剖析造成零碎性能点,构筑零部件级平安防线TARA剖析作为信息安全功能设计的前置条件,能够帮忙辨认车载智能终端的潜在威逼和安全漏洞,通过对威逼的危险量化评估与优先级排序,造成一份高层级的网络安全需要,即零碎性能点。若干零碎性能点集结之后,再依据纵深进攻造成零部件级的平安防线,以硬件安全、系统安全、数据安全、利用平安、通信安全的层层嵌套抵挡黑客攻击。 联合车内零部件平安利用协同联动,造成车端平安防护面80%的攻打会集中在20%的控制器上,即网关、T-BOX、车机和主动驾驶控制器。其中每个控制器承载着不同的属性及不同的交互对象,在进行整车设计时,4个控制器的平安性能协同联动起来,便可造成一个平安防护面。 “云端监控、车端响应”,平安防护与平安监测双线并行点线面串联之后,基于对破绽库、威逼情报库、汽车攻打常识图谱及攻击行为的剖析,百度又通过云端安全监控经营平台进行攻打收敛,集中监控平安危险,提出了全方位、立体化的动静防护平安评估计划,以人工智能技术的无力加持,定位安全隐患,量化平安状态。依靠VSOC平安监测平台,除了能够提供安全事件监测、破绽修复以及威逼情报预警,更可将车辆纵深进攻的平安机制在平台中进行展示,让整车企业把握本身平安现状,达成“现状可见、问题可察、危险可辨、将来可测”的最优解。 百度Apollo作为专一于汽车信息安全钻研畛域的业余团队,在产品合规性及理论部署能力方面均有突出劣势,往年除参加《整车信息安全技术与测试方法》、《车载信息交互零碎信息安全技术要求及试验办法》的起草外,百度Apollo后续还将参加《智能网联汽车 数据通用要求》的编写工作,积极响应国家号召,晋升我国汽车产品的信息安全防护技术水平,为解决我国车联网数据的监管问题贡献力量。点击进入取得更多技术信息~~

November 4, 2021 · 1 min · jiezi

关于apollo:解决apollo的configService服务启动异常

前言apollo是一个十分风行的开源的配置核心我的项目,这里就不多介绍了。接触过apollo和运行过apollo的人必定都遇到过启动configService时抛异样了,而且100%会抛一个异样。起因是,在apollo的架构中configService既作为config服务,同时也承载了metaService的性能,所以这个模块,既作为eureka的服务端也是eureka的客户端,这就造成了利用启动时,eurekaServer未齐全启动,eurekaClient拉取注册表信息时就抛异样了。不过这个拉取动作是在独立的线程中运行的,独立于启动利用的主线程,所以异样并不影响利用的启动,这个问题也就始终从开源到留到了当初。目前,这个问题已被博主解决,正在合并pr中。 本文pr地址:https://github.com/ctripcorp/... 触发起因剖析首先看下异样的信息,异样的信息比拟多,如下: 2020-12-23 09:55:19.882 ERROR 7022 --- [ main] c.n.d.s.t.d.RedirectingEurekaHttpClient : Request execution errorcom.sun.jersey.api.client.ClientHandlerException: java.net.ConnectException: Connection refused (Connection refused) at com.sun.jersey.client.apache4.ApacheHttpClient4Handler.handle(ApacheHttpClient4Handler.java:187) at com.sun.jersey.api.client.filter.GZIPContentEncodingFilter.handle(GZIPContentEncodingFilter.java:123) at com.netflix.discovery.EurekaIdentityHeaderFilter.handle(EurekaIdentityHeaderFilter.java:27) at com.sun.jersey.api.client.Client.handle(Client.java:652) at com.sun.jersey.api.client.WebResource.handle(WebResource.java:682) at com.sun.jersey.api.client.WebResource.access$200(WebResource.java:74) at com.sun.jersey.api.client.WebResource$Builder.get(WebResource.java:509) at com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient.getApplicationsInternal(AbstractJerseyEurekaHttpClient.java:194) at com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient.getApplications(AbstractJerseyEurekaHttpClient.java:165) at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator$6.execute(EurekaHttpClientDecorator.java:137) at com.netflix.discovery.shared.transport.decorator.MetricsCollectingEurekaHttpClient.execute(MetricsCollectingEurekaHttpClient.java:73) at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.getApplications(EurekaHttpClientDecorator.java:134) at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator$6.execute(EurekaHttpClientDecorator.java:137) at com.netflix.discovery.shared.transport.decorator.RedirectingEurekaHttpClient.executeOnNewServer(RedirectingEurekaHttpClient.java:118) at com.netflix.discovery.shared.transport.decorator.RedirectingEurekaHttpClient.execute(RedirectingEurekaHttpClient.java:79) at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.getApplications(EurekaHttpClientDecorator.java:134) at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator$6.execute(EurekaHttpClientDecorator.java:137) at com.netflix.discovery.shared.transport.decorator.RetryableEurekaHttpClient.execute(RetryableEurekaHttpClient.java:120) at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.getApplications(EurekaHttpClientDecorator.java:134) at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator$6.execute(EurekaHttpClientDecorator.java:137) at com.netflix.discovery.shared.transport.decorator.SessionedEurekaHttpClient.execute(SessionedEurekaHttpClient.java:77) at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.getApplications(EurekaHttpClientDecorator.java:134) at com.netflix.discovery.DiscoveryClient.getAndStoreFullRegistry(DiscoveryClient.java:1051) at com.netflix.discovery.DiscoveryClient.fetchRegistry(DiscoveryClient.java:965) at com.netflix.discovery.DiscoveryClient.<init>(DiscoveryClient.java:414) at com.netflix.discovery.DiscoveryClient.<init>(DiscoveryClient.java:269) at org.springframework.cloud.netflix.eureka.CloudEurekaClient.<init>(CloudEurekaClient.java:63) at org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration$RefreshableEurekaClientConfiguration.eurekaClient(EurekaClientAutoConfiguration.java:290) at org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration$RefreshableEurekaClientConfiguration$$EnhancerBySpringCGLIB$$5cf7ced9.CGLIB$eurekaClient$2(<generated>) at org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration$RefreshableEurekaClientConfiguration$$EnhancerBySpringCGLIB$$5cf7ced9$$FastClassBySpringCGLIB$$328872d8.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228) at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:365) at org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration$RefreshableEurekaClientConfiguration$$EnhancerBySpringCGLIB$$5cf7ced9.eurekaClient(<generated>) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:582) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1247) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1096) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:535) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$1(AbstractBeanFactory.java:353) at org.springframework.cloud.context.scope.GenericScope$BeanLifecycleWrapper.getBean(GenericScope.java:390) at org.springframework.cloud.context.scope.GenericScope.get(GenericScope.java:184) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:350) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:35) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:193) at com.sun.proxy.$Proxy145.getApplications(Unknown Source) at org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration.peerAwareInstanceRegistry(EurekaServerAutoConfiguration.java:164) at org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration$$EnhancerBySpringCGLIB$$ef07d99b.CGLIB$peerAwareInstanceRegistry$5(<generated>) at org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration$$EnhancerBySpringCGLIB$$ef07d99b$$FastClassBySpringCGLIB$$458f0ac6.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228) at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:365) at org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration$$EnhancerBySpringCGLIB$$ef07d99b.peerAwareInstanceRegistry(<generated>) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:582) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1247) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1096) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:535) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:251) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1135) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1062) at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:818) at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:724) at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:474) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1247) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1096) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:535) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:251) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1135) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1062) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:583) at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:372) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1341) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:572) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:759) at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:869) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550) at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:780) at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:412) at org.springframework.boot.SpringApplication.run(SpringApplication.java:333) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1277) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1265) at com.ctrip.framework.apollo.configservice.ConfigServiceApplication.main(ConfigServiceApplication.java:33)Caused by: java.net.ConnectException: Connection refused (Connection refused) at java.net.PlainSocketImpl.socketConnect(Native Method) at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:476) at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:218) at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:200) at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:394) at java.net.Socket.connect(Socket.java:606) at org.apache.http.conn.scheme.PlainSocketFactory.connectSocket(PlainSocketFactory.java:121) at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:180) at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:144) at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:134) at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:610) at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:445) at org.apache.http.impl.client.AbstractHttpClient.doExecute(AbstractHttpClient.java:835) at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:118) at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56) at com.sun.jersey.client.apache4.ApacheHttpClient4Handler.handle(ApacheHttpClient4Handler.java:173) ... 107 common frames omitted2020-12-23 09:55:19.883 WARN 7022 --- [ main] c.n.d.s.t.d.RetryableEurekaHttpClient : Request execution failed with message: java.net.ConnectException: Connection refused (Connection refused)2020-12-23 09:55:19.883 ERROR 7022 --- [ main] com.netflix.discovery.DiscoveryClient : DiscoveryClient_APOLLO-CONFIGSERVICE/172.26.203.178:apollo-configservice:8080 - was unable to refresh its cache! status = Cannot execute request on any known servercom.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server at com.netflix.discovery.shared.transport.decorator.RetryableEurekaHttpClient.execute(RetryableEurekaHttpClient.java:112) at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.getApplications(EurekaHttpClientDecorator.java:134) at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator$6.execute(EurekaHttpClientDecorator.java:137) at com.netflix.discovery.shared.transport.decorator.SessionedEurekaHttpClient.execute(SessionedEurekaHttpClient.java:77) at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.getApplications(EurekaHttpClientDecorator.java:134) at com.netflix.discovery.DiscoveryClient.getAndStoreFullRegistry(DiscoveryClient.java:1051) at com.netflix.discovery.DiscoveryClient.fetchRegistry(DiscoveryClient.java:965) at com.netflix.discovery.DiscoveryClient.<init>(DiscoveryClient.java:414) at com.netflix.discovery.DiscoveryClient.<init>(DiscoveryClient.java:269) at org.springframework.cloud.netflix.eureka.CloudEurekaClient.<init>(CloudEurekaClient.java:63) at org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration$RefreshableEurekaClientConfiguration.eurekaClient(EurekaClientAutoConfiguration.java:290) at org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration$RefreshableEurekaClientConfiguration$$EnhancerBySpringCGLIB$$5cf7ced9.CGLIB$eurekaClient$2(<generated>) at org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration$RefreshableEurekaClientConfiguration$$EnhancerBySpringCGLIB$$5cf7ced9$$FastClassBySpringCGLIB$$328872d8.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228) at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:365) at org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration$RefreshableEurekaClientConfiguration$$EnhancerBySpringCGLIB$$5cf7ced9.eurekaClient(<generated>) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:582) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1247) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1096) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:535) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$1(AbstractBeanFactory.java:353) at org.springframework.cloud.context.scope.GenericScope$BeanLifecycleWrapper.getBean(GenericScope.java:390) at org.springframework.cloud.context.scope.GenericScope.get(GenericScope.java:184) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:350) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:35) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:193) at com.sun.proxy.$Proxy145.getApplications(Unknown Source) at org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration.peerAwareInstanceRegistry(EurekaServerAutoConfiguration.java:164) at org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration$$EnhancerBySpringCGLIB$$ef07d99b.CGLIB$peerAwareInstanceRegistry$5(<generated>) at org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration$$EnhancerBySpringCGLIB$$ef07d99b$$FastClassBySpringCGLIB$$458f0ac6.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228) at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:365) at org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration$$EnhancerBySpringCGLIB$$ef07d99b.peerAwareInstanceRegistry(<generated>) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:582) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1247) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1096) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:535) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:251) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1135) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1062) at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:818) at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:724) at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:474) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1247) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1096) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:535) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:251) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1135) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1062) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:583) at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:372) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1341) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:572) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:759) at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:869) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550) at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:780) at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:412) at org.springframework.boot.SpringApplication.run(SpringApplication.java:333) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1277) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1265) at com.ctrip.framework.apollo.configservice.ConfigServiceApplication.main(ConfigServiceApplication.java:33)最终咱们就定位到这一行,定位问题的过程是一个非常复杂的事件。所以这里间接写明,并不是一开始我就晓得关键点在这里,这个须要平时多积攒知识面,能力疾速定位关键点 ...

December 23, 2020 · 4 min · jiezi

关于apollo:集成apollo动态日志消灭logbackspringxml

前言动静调整线上日志级别是一个十分常见的场景,借助apollo这种配置核心组件非常容易实现。作为apollo的官网技术支持,博主常常在技术群看到有使用者询问apollo是否能够托管logback的配置文件,毕竟有了配置核心后,毁灭所有的本地配置全副交给apollo治理是咱们的最终目标。可是,apollo不具备间接托管logback-spring.xml配置文件能力,然而,咱们能够基于spring和logback的装载机制,齐全取缔logback-spring.xml配置,以apollo中的配置驱动。而且,革新后,大大提高了日志零碎的灵活性和可扩展性。 apollo动静日志何为apollo动静日志?间接这样说可能会有歧义,认为是apollo里的日志,其实不然。举个简略的例子,比方,咱们我的项目很多中央应用了log.debug()打印日志,为了不便通过日志信息排查问题,然而个别状况下,生产环境的日志级别会配置成info。只有遇到须要排查线上问题的时候才会长期关上debug级别日志。这个时候只能需改配置文件,将日志级别调整成debug,而后从新打包部署验证。不仅流程繁琐耗时,还会毁坏过后的"案发现场的环境",导致判断不精确。如果利用具备了apollo动静日志这种能力,就只需在apollo批改下配置而后提交,就能够热更新日志级别,马上打印debug级别日志。这就是所谓的apollo动静日志。实现这个成果,须要具备两个能力,别离由spring和apollo提供 spring日志零碎热更新日志级别spring利用中,spring适配了支流的日志框架,如logback、log4j2等,在这些日志框架之上,又形象了本人的日志零碎服务,这里咱们用到了spring的LoggingSystem,用它来热更新日志级别,这个类在日志零碎初始化时就增加到了spring的容器中,所以只有在spring的上下文治理范畴内,就能够间接注入,以下为次要应用到的api形容: /** * 设置给定日志记录器的日志级别. * @param loggerName 要设置的日志记录器的名称({@code null}可用于根日志记录器)。 * @param level 日志级别 */ public void setLogLevel(String loggerName, LogLevel level) { throw new UnsupportedOperationException("Unable to set log level"); }apollo日志配置变更动静下发apollo作为分布式配置核心,配置集中管理和配置热更新是其最外围的性能,此外,apollo还提供了配置变更下发监听的性能。基于这个配置监听的设计,实现动静日志就变得非常简单了。而且不仅能够实现日志动静热更,基于这个思路,连接池、数据源等都能够轻松实现。apollo实现监听配置变更有多种形式,能够通过Config实例手动增加,如: @ApolloConfig public Config config; public void addConfigChangeListener(){ config.addChangeListener(changeEvent->{ System.out.println("config change keys" + changeEvent.changedKeys()); }); }也能够通过注解间接驱动 @ApolloConfigChangeListener public void addConfigChangeListener(ConfigChangeEvent changeEvent){ System.out.println("config change keys" + changeEvent.changedKeys()); }实现日志调整热更新有了上述能力,在联合spring反对的日志加载配置形式,如: logging.level.org.springframework.web=debuglogging.level.org.hibernate=error能够实现如下代码实现性能,遇到须要调整日志级别时,批改apollo里的配置,即可实时失效 @Configurationpublic class LogbackConfiguration { private static final Logger logger = LoggerFactory.getLogger(LoggerConfiguration.class); private static final String LOGGER_TAG = "logging.level."; private final LoggingSystem loggingSystem; public LogbackConfiguration(LoggingSystem loggingSystem) { this.loggingSystem = loggingSystem; } @ApolloConfigChangeListener private void onChange(ConfigChangeEvent changeEvent) { for (String key : changeEvent.changedKeys()) { if (this.containsIgnoreCase(key, LOGGER_TAG)) { String strLevel = changeEvent.getChange(key).getNewValue(); LogLevel level = LogLevel.valueOf(strLevel.toUpperCase()); loggingSystem.setLogLevel(key.replace(LOGGER_TAG, ""), level); logger.info("logging changed: {},oldValue:{},newValue:{}", key, changeEvent.getChange(key).getOldValue(), strLevel); } } } private boolean containsIgnoreCase(String str, String searchStr) { if (str == null || searchStr == null) { return false; } int len = searchStr.length(); int max = str.length() - len; for (int i = 0; i <= max; i++) { if (str.regionMatches(true, i, searchStr, 0, len)) { return true; } } return false; }}毁灭logback-spring.xml配置在"毁灭"logback-xml配置之前,先看下这个配置文件有哪些配置信息,起到了哪些作用,上面贴出一个典型的配置文件内容: ...

November 20, 2020 · 2 min · jiezi

关于apollo:apollo在项目的使用

Apollo(阿波罗)是携程框架部门研发的分布式配置核心,可能集中化治理利用不同环境、不同集群的配置,配置批改后可能实时推送到利用端,并且具备标准的权限、流程治理等个性,实用于微服务配置管理场景。 性能: 对立治理不同环境、不同集群的配置,以命名空间namespace为最小粒度进行配置,一个服务引入了这个命名空间,即应用了该命名空间的配置。配置批改实时失效版本公布治理灰度公布权限治理、公布审核、操作审计客户端配置信息监控应用: 服务端的配置: 新建appId,appId能够了解为是一套利用。在appId新建namespace增加配置内容。Namespace能够了解为是配置的汇合,原先一个yml文件寄存配置,当初能够通过某个环境 ,某个appId下的namespace引入。客户端的应用: 引入maven包并在启动类退出@EnableApollo即可,通过meta.server,appId和namespace找到所需的配置。能够了解为在apollo的配置就在配置文件里。通过@Value,@ConfigurationProperties引入的变量不受影响,对代码的入侵比拟小。监听配置的变动,不同的namespace在我的项目的应用: 把配置信息放入apollo。间接的做法是每个服务应用一个namespace,然而通过梳理发现,有些配置是多个服务独特应用的。服务的配置进行分类: 通用:Log,eureka,feign调用相干的某些服务专用:database(openapi monitor) redis(card openapi double) kafka activemq ElasticSearch各自的服务领有独自的namespace通过分类后,如果要批改数据库地址,redis地址,或者新增一个中间件的地址,只用新增namespace,在配置文件引入/批改该namespace即可。如果想更改某个服务的配置,在相应的namespace下批改,并重启docker服务。能够看到公布历史,有哪些实例在应用。我的项目是用docker部署的,原先一部分配置在服务里,一部分配置在docker-compose里,革新后尽可能所有的配置都放在apollo里,apollo的配置放在docker-compose.yml里,docker-compose.yml能够引入公共的配置文件env_file,真正的apollo配置寄存文件,包含apollo.meta,app.id,apollo.cacheDir,不必再通过profile.active辨别。一些无奈通过apollo配置的放在服务的配置或docker的配置中。实践上能当在服务的配置文件里的都能放在apollo里。每个服务只需一个配置文件,甚至不必配置文件。为了本地开发的不便,在本地搁置了sit环境的配置。通过apollo.meta辨别不同的环境,apollo.meta变量通过docker-compose文件的公共配置env_file引入 须要留神的: 配置的备份假如两个人对同一个服务进行开发,须要批改配置Application里的配置会实时失效,自定义的namespace不会。开关,限流额度,门路对于日志级别,database等须要更新bean的,须要写代码 https://github.com/ctripcorp/apollo-use-casesNamespace的类型: 公有:namespace从属于appId,只有配置了该appId才可拜访私有:只有连上了apollo就能应用,属于所有利用,全局惟一关联:属性是公有的,能够了解为在继承私有属性的根底上,可对私有属性进行批改其余的性能: 密钥治理,爱护配置不被其余利用获取权限调配:以namespace为粒度调配批改,公布,查看的权限增加集群配置笼罩,后面的会笼罩前面的,尽可能不要笼罩,保障配置的唯一性架构:从逻辑上来说比拟清晰,将配置的编辑/公布与客户端获取配置离开,用两个服务实现Portal通过调用admin service接口批改配置Client通过config service接口获取配置为了保障高可用,增加了一层eureka,client和portal通过eureka调用接口服务。为了使不同语言的client可通过http接口即可获取admin service和config service的信息,eureka上搭建Meta service,用来封装服务发现的细节,不必关怀背地的注册核心和服务发现组件(用于不同语言的服务注册到eureka上)。 残缺架构如下:次要模块: Config Service:配置的读取、推送等,服务对象是Apollo客户端Admin Service:配置的批改、公布等,服务对象是Apollo Portal(治理界面)Config Service和Admin Service都是多实例、无状态部署,所以须要将本人注册到Eureka中并放弃心跳Meta Server:在Eureka之上搭建,用于封装Eureka的服务发现接口: Client通过域名拜访Meta Server获取Config Service服务列表(IP+Port),而后间接通过IP+Port拜访服务,同时在Client侧会做load balance、谬误重试Portal通过域名拜访Meta Server获取Admin Service服务列表(IP+Port),而后间接通过IP+Port拜访服务,同时在Portal侧会做load balance、谬误重试为了简化部署,实际上会把Config Service、Eureka和Meta Server三个逻辑角色部署在同一个JVM过程中服务端设计:Admin service公布了配置批改的事件,所有config service收到音讯并告诉客户端批改配置。有两个值得去摸索的点: Admin service公布事件的形式:能够用中间件redis,activemq实现。这里为了缩小内部组件的依赖,通过数据库实现。具体的做法是Admin公布一条配置后,这个配置必定属于某个namespace,在ReleaseMessage表插入一条记录AppId+Cluster+Namespace,config service有一个线程每秒扫描ReleaseMessage表,如果有新的音讯记录,就告诉客户端更新配置。所以配置如果有更新,个别一秒内就可告诉到客户端。 Config service告诉客户端的形式:客户端发动获取配置的申请,config service用defferResult将申请挂起,如果60秒内没有感兴趣的namespace配置发生变化,返回304,否则立刻返回变动后的配置。应用defferResult能够进步申请的并发量客户端设计: 客户端和服务端放弃了一个长连贯,从而能第一工夫取得配置更新的推送。(通过Http Long Polling实现)客户端还会定时从Apollo配置核心服务端拉取利用的最新配置。 这是一个fallback机制,为了避免推送机制生效导致配置不更新客户端定时拉取会上报本地版本,所以个别状况下,对于定时拉取的操作,服务端都会返回304 - Not Modified定时频率默认为每5分钟拉取一次,客户端也能够通过在运行时指定System Property: apollo.refreshInterval来笼罩,单位为分钟。客户端从Apollo配置核心服务端获取到利用的最新配置后,会保留在内存中客户端会把从服务端获取到的配置在本地文件系统缓存一份 在遇到服务不可用,或网络不通的时候,仍然能从本地复原配置应用程序能够从Apollo客户端获取最新的配置、订阅配置更新告诉测试环境下多环境部署SIT,QA,UAT,用一套画面治理不同的环境: 与spring的集成原理Spring的ApplicationContext会蕴含一个Environment,在服务启动的时候把参数注入即可。

November 2, 2020 · 1 min · jiezi

apollo使用

增加依赖jar客户端jar <dependency> <groupId>com.ctrip.framework.apollo</groupId> <artifactId>apollo-client</artifactId> <version>1.5.0</version></dependency>开启配置核心类加注解 @EnableApolloConfig从配置核心读数据1.注入数据 在类外面,注入数据 @ApolloConfigConfig config;2.读配置数据 在办法里读数据 String merQrcodeNum = config.getProperty("merQrcodeNum", "5"); //1.从配置核心读数据 2.如果没有读到数据,就应用默认值5(即第二个参数)配置配置文件增加 ### apollo配置核心# 我的项目idapp.id=xxx-manager-service //在配置核心管理系统增加利用和利用id# 注册核心ip/portapollo.meta=${filter.apollo.meta} //参考https://github.com/ctripcorp/...

July 15, 2020 · 1 min · jiezi

Sentinel-Dashboard中修改规则同步到Apollo存储

在之前的两篇教程中我们分别介绍了如何将Sentinel的限流规则存储到Nacos和Apollo中。同时,在文末的思考中,我都指出了这两套整合方案都存在一个不足之处:不论采用什么配置中心,限流规则都只能通过Nacos界面或Apollo界面来完成修改才能得到持久化存储,而在Sentinel Dashboard中修改限流规则虽然可以生效,但是不会被持久化到配置中心。而在这两个配置中心里存储的数据是一个Json格式,当存储的规则越来越多,对该Json配置的可读性与可维护性会变的越来越差。所以,下面我们就来继续探讨这个不足之处,并给出相应的解决方案。本文以Apollo存储为例,下一篇介绍Nacos的改在示例。 问题分析在实际操作之前,我们先通过下图了解一下之前我们所实现的限流规则持久化方案的配置数据流向图: 蓝色箭头代表了限流规则由配置中心发起修改的更新路径橙色箭头代表了限流规则由Sentinel Dashboard发起修改的更新路径从图中可以很明显的看到,Sentinel Dashboard与业务服务之间本身是可以互通获取最新限流规则的,这在没有整合配置中心来存储限流规则的时候就已经存在这样的机制。最主要的区别是:配置中心的修改都可以实时的刷新到业务服务,从而被Sentinel Dashboard读取到,但是对于这些规则的更新到达各个业务服务之后,并没有一个机制去同步到配置中心,作为配置中心的客户端也不会提供这样的逆向更新方法。 改造方案关于如何改造,现来解读一下官方文档中关于这部分的说明: 要通过 Sentinel 控制台配置集群流控规则,需要对控制台进行改造。我们提供了相应的接口进行适配。 从 Sentinel 1.4.0 开始,我们抽取出了接口用于向远程配置中心推送规则以及拉取规则: DynamicRuleProvider<T>: 拉取规则DynamicRulePublisher<T>: 推送规则对于集群限流的场景,由于每个集群限流规则都需要唯一的 flowId,因此我们建议所有的规则配置都通过动态规则源进行管理,并在统一的地方生成集群限流规则。 我们提供了新版的流控规则页面,可以针对应用维度推送规则,对于集群限流规则可以自动生成 flowId。用户只需实现 DynamicRuleProvider 和 DynamicRulePublisher 接口,即可实现应用维度推送(URL: /v2/flow)。 这段内容什么意思呢?简单的说就是Sentinel Dashboard通过DynamicRuleProvider和DynamicRulePublisher两个接口来获取和更新应用的动态规则。默认情况下,就如上一节中Sentinel Dashboard与各业务服务之间的两个箭头,一个接口负责获取规则,一个接口负责更新规则。 所以,只需要通过这两个接口,实现对配置中心中存储规则的读写,就能实现Sentinel Dashboard中修改规则与配置中心存储同步的效图如下: ![图片上传中...] 其中,绿色箭头为公共公共部分,即:不论从培中心修改,还是从Sentinel Dashboard修改都会触发的操作。这样的话,从上图的两处修改起点看,所有涉及的部分都能获取到一致的限流规则了。 代码实现下面继续说说具体的代码实现,这里参考了Sentinel Dashboard源码中关于Apollo实现的测试用例。但是由于考虑到与Spring Cloud Alibaba的结合使用,略作修改。 第一步:修改pom.xml中的Apollo OpenAPi的依赖,将<scope>test</scope>注释掉,这样才能在主程序中使用。 <dependency> <groupId>com.ctrip.framework.apollo</groupId> <artifactId>apollo-openapi</artifactId> <version>1.2.0</version> <!--<scope>test</scope>--></dependency>第二步:找到resources/app/scripts/directives/sidebar/sidebar.html中的这段代码: <li ui-sref-active="active"> <a ui-sref="dashboard.flowV1({app: entry.app})"> <i class="glyphicon glyphicon-filter"></i>&nbsp;&nbsp;流控规则 </a></li>修改为: <li ui-sref-active="active"> <a ui-sref="dashboard.flow({app: entry.app})"> <i class="glyphicon glyphicon-filter"></i>&nbsp;&nbsp;流控规则 </a></li>第三步:在com.alibaba.csp.sentinel.dashboard.rule包下新建一个apollo包,用来编写针对Apollo的扩展实现。 第四步:创建Apollo的配置类,定义Apollo的portal访问地址以及第三方应用访问的授权Token(通过Apollo管理员账户登录,在“开放平台授权管理”功能中创建),具体代码如下: @Configurationpublic class ApolloConfig { @Bean public Converter<List<FlowRuleEntity>, String> flowRuleEntityEncoder() { return JSON::toJSONString; } @Bean public Converter<String, List<FlowRuleEntity>> flowRuleEntityDecoder() { return s -> JSON.parseArray(s, FlowRuleEntity.class); } @Bean public ApolloOpenApiClient apolloOpenApiClient() { ApolloOpenApiClient client = ApolloOpenApiClient.newBuilder() .withPortalUrl("https://apollo.xxx.com") // TODO 根据实际情况修改 .withToken("open api token") // TODO 根据实际情况修改 .build(); return client; }}第五步:实现Apollo的配置拉取实现。 ...

May 7, 2019 · 2 min · jiezi

Spring Cloud Alibaba基础教程:Sentinel使用Apollo存储规则

上一篇我们介绍了如何通过Nacos的配置功能来存储限流规则。Apollo是国内用户非常多的配置中心,所以,今天我们继续说说Spring Cloud Alibaba Sentinel中如何将流控规则存储在Apollo中。 使用Apollo存储限流规则Sentinel自身就支持了多种不同的数据源来持久化规则配置,目前包括以下几种方式: 文件配置Nacos配置ZooKeeper配置Apollo配置本文我们就来一起动手尝试一下,如何使用Apollo来存储限流规则。 准备工作下面我们将同时使用到Apollo和Sentinel Dashboard,所以可以先把Apollo和Sentinel Dashboard启动起来。 如果还没入门Sentinel Dashboard可以通过文末的系列目录先学习之前的内容。Apollo的话相对复杂一些,这里不做详细介绍了,如果还没有接触过Apollo的读者可以查看其官方文档进一步学习。 应用配置第一步:在Spring Cloud应用的pom.xml中引入Spring Cloud Alibaba的Sentinel模块和Apollo存储扩展: <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-apollo</artifactId> <version>1.4.0</version> </dependency></dependencies>第二步:在Spring Cloud应用中配置的服务信息,在resource目录下,创建apollo-env.properties文件,内容样例: local.meta=http://192.168.0.201:8080dev.meta=http://192.168.0.202:8080这里需要了解Apollo对多环境的配置,这里设置的是每个环境不同的配置服务地址,读者需要根据自己的实际情况修改。 第三步:在Spring Cloud应用中添加配置信息: spring.application.name=sentinel-datasource-apolloserver.port=8002# apollo configapp.id=${spring.application.name}# sentinel dashboardspring.cloud.sentinel.transport.dashboard=localhost:8080# sentinel datasource apollospring.cloud.sentinel.datasource.ds.apollo.namespaceName=applicationspring.cloud.sentinel.datasource.ds.apollo.flowRulesKey=sentinel.flowRulesapp.id:Apollo中的创建的项目名称,这里采用spring.application.name参数的引用,从而达到服务名与配置项目名一致的效果spring.cloud.sentinel.transport.dashboard:sentinel dashboard的访问地址,根据上面准备工作中启动的实例配置spring.cloud.sentinel.datasource.ds.apollo.namespaceName:Apollo的空间名spring.cloud.sentinel.datasource.ds.apollo.flowRulesKey:配置规则的key名称关于Apollo相关配置的对应关系可见下图所示: 第四步:创建应用主类,并提供一个rest接口,比如: @EnableApolloConfig@SpringBootApplicationpublic class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } @Slf4j @RestController static class TestController { @GetMapping("/hello") public String hello() { return "didispace.com"; } }}其中@EnableApolloConfig注解是开启Apollo的配置加载功能。 ...

April 21, 2019 · 1 min · jiezi

GraphQL 从入门到实践

本文首先介绍了 GraphQL,再通过 MongoDB + graphql + graph-pack 的组合实战应用 GraphQL,详细阐述如何使用 GraphQL 来进行增删改查和数据订阅推送,并附有使用示例,边用边学印象深刻~如果希望将 GraphQL 应用到前后端分离的生产环境,请期待后续文章。本文实例代码:Github0. 什么是 GraphQLGraphQL 是一种面向数据的 API 查询风格。传统的 API 拿到的是前后端约定好的数据格式,GraphQL 对 API 中的数据提供了一套易于理解的完整描述,客户端能够准确地获得它需要的数据,没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具。1. 概述前端的开发随着 SPA 框架全面普及,组件化开发也随之成为大势所趋,各个组件分别管理着各自的状态,组件化给前端仔带来便利的同时也带来了一些烦恼。比如,组件需要负责把异步请求的状态分发给子组件或通知给父组件,这个过程中,由组件间通信带来的结构复杂度、来源不明的数据源、不知从何订阅的数据响应会使得数据流变得杂乱无章,也使得代码可读性变差,以及可维护性的降低,为以后项目的迭代带来极大困难。试想一下你都开发完了,产品告诉你要大改一番,从接口到组件结构都得改,后端也骂骂咧咧不愿配合让你从好几个 API 里取数据自己组合,这酸爽 ????在一些产品链复杂的场景,后端需要提供对应 WebApp、WebPC、APP、小程序、快应用等各端 API,此时 API 的粒度大小就显得格外重要,粗粒度会导致移动端不必要的流量损耗,细粒度则会造成函数爆炸 (Function Explosion);在此情景下 Facebook 的工程师于 2015 年开源了 GraphQL 规范,让前端自己描述自己希望的数据形式,服务端则返回前端所描述的数据结构。简单使用可以参照下面这个图:比如前端希望返回一个 ID 为 233 的用户的名称和性别,并查找这个用户的前十个雇员的名字和 Email,再找到这个人父亲的电话,和这个父亲的狗的名字(别问我为什么有这么奇怪的查找 ????),那么我们可以通过 GraphQL 的一次 query 拿到全部信息,无需从好几个异步 API 里面来回找:query { user (id : “233”) { name gender employee (first: 10) { name email } father { telephone dog { name } } }}返回的数据格式则刚好是前端提供的数据格式,不多不少,是不是心动了 ????2. 几个重要概念这里先介绍几个对理解 GraphQL 比较重要的概念,其他类似于指令、联合类型、内联片段等更复杂的用法,参考 GraphQL 官网文档 ~2.1 操作类型 Operation TypeGraphQL 的操作类型可以是 query、mutation 或 subscription,描述客户端希望进行什么样的操作query 查询:获取数据,比如查找,CRUD 中的 Rmutation 变更:对数据进行变更,比如增加、删除、修改,CRUD 中的 CUDsubstription 订阅:当数据发生更改,进行消息推送这些操作类型都将在后文实际用到,比如这里进行一个查询操作query { user { id }}2.2 对象类型和标量类型 Object Type & Scalar Type如果一个 GraphQL 服务接受到了一个 query,那么这个 query 将从 Root Query 开始查找,找到对象类型(Object Type)时则使用它的解析函数 (Resolver) 来获取内容,如果返回的是对象类型则继续使用解析函数获取内容,如果返回的是标量类型(Scalar Type)则结束获取,直到找到最后一个标量类型。对象类型:用户在 schema 中定义的 type标量类型:GraphQL 中内置有一些标量类型 String、Int、Float、Boolean、ID,用户也可以定义自己的标量类型比如在 Schema 中声明type User { name: String! age: Int}这个 User 对象类型有两个字段,name 字段是一个为 String 的非空标量,age 字段为一个 Int 的可空标量。2.3 模式 Schema如果你用过 MongoOSE,那你应该对 Schema 这个概念很熟悉,翻译过来是『模式』。它定义了字段的类型、数据的结构,描述了接口数据请求的规则,当我们进行一些错误的查询的时候 GraphQL 引擎会负责告诉我们哪里查询有问题,和详细的错误信息,对开发十分友好。Schema 使用一个简单的模式语法编写,称为模式描述语言(Schema Definition Language, SDL),我们可以用一个真实的例子来展示一下一个真实的 Schema 文件是怎么用 SDL 编写的:# src/schema.graphql# Query 入口type Query { hello: String users: [User]! user(id: String): [User]!}# Mutation 入口type Mutation { createUser(id: ID!, name: String!, email: String!, age: Int,gender: Gender): User! updateUser(id: ID!, name: String, email: String, age: Int, gender: Gender): User! deleteUser(id: ID!): User}# Subscription 入口type Subscription { subsUser(id: ID!): User}type User implements UserInterface { id: ID! name: String! age: Int gender: Gender email: String!}# 枚举类型enum Gender { MAN WOMAN}# 接口类型interface UserInterface { id: ID! name: String! age: Int gender: Gender}这个例子就是一个简单的 Schema 文件了,从 Query、Mutation、Subscription 的入口开始定义了各个对象类型或标量类型,这些字段的类型也可能是其他的对象类型或标量类型,这样组成了一个树形的结构,而用户在向服务端发送请求的时候,沿着这个树选择一个或多个分支就可以同时获取多组信息。注意:在 Query 查询字段时,是并行执行的,而在 Mutation 变更的时候,是线性执行,一个接着一个,防止同时变更带来的竞态问题,比如说我们在一个请求中发送了两个 Mutation,那么前一个将始终在后一个之前执行。2.4 解析函数 Resolver前端请求信息到达后端之后,需要由解析函数 Resolver 来提供数据,比如这样一个 Query:query { hello}那么同名的解析函数应该是这样的Query: { hello (parent, args, context, info) { return … }}解析函数接受四个参数,分别为parent:当前上一个解析函数的返回值args:查询中传入的参数context:提供给所有解析器的上下文信息info:一个保存与当前查询相关的字段特定信息以及 schema 详细信息的值解析函数的返回值可以是一个具体的值,也可以是 Promise 或 Promise 的数组。一些常用的库可以帮省略一些简单的解析函数,比如一个字段没有提供对应的解析函数时,会从上层返回对象中读取和返回和这个字段同名的属性。2.5 请求格式GraphQL 最常见的是通过 HTTP 来发送请求,那么如何通过 HTTP 来进行 GraphQL 通信呢举个栗子,如何通过 Get/Post 方式来执行下面的 GraphQL 查询呢query { me { name }}Get 是将请求内容放在 URL 中,Post 是在 content-type: application/json 情况下,将 JSON 格式的内容放在请求体里# Get 方式http://myapi/graphql?query={me{name}}# Post 方式的请求体{ “query”: “…”, “operationName”: “…”, “variables”: { “myVariable”: “someValue”, … }}返回的格式一般也是 JSON 体# 正确返回{ “data”: { … }}# 执行时发生错误{ “errors”: [ … ]}如果执行时发生错误,则 errors 数组里有详细的错误信息,比如错误信息、错误位置、抛错现场的调用堆栈等信息,方便进行定位。3. 实战这里使用 MongoDB + graph-pack 进行一下简单的实战,并在实战中一起学习一下,详细代码参见 Github MongoDB 是一个使用的比较多的 NoSQL,可以方便的在社区找到很多现成的解决方案,报错了也容易找到解决方法。graph-pack 是集成了 Webpack + Express + Prisma + Babel + Apollo-server + Websocket 的支持热更新的零配置 GraphQL 服务环境,这里将其用来演示 GraphQL 的使用。3.1 环境部署首先我们把 MongoDB 启起来,这个过程就不赘述了,网上很多教程;搭一下 graph-pack 的环境npm i -S graphpack在 package.json 的 scripts 字段加上:“scripts”: { “dev”: “graphpack”, “build”: “graphpack build”}创建文件结构:.├── src│ ├── db // 数据库操作相关│ │ ├── connect.js // 数据库操作封装│ │ ├── index.js // DAO 层│ │ └── setting.js // 配置│ ├── resolvers // resolvers│ │ └── index.js│ └── schema.graphql // schema└── package.json这里的 schema.graphql 是 2.3 节的示例代码,其他实现参见 Github,主要关注 src/db 、src/resolvers、src/schema.graphql 这三个地方src/db:数据库操作层,包括 DAO 层和 Service 层(如果对分层不太了解可以看一下最后一章)src/resolvers:Resolver 解析函数层,给 GraphQL 的 Query、Mutation、Subscription 请求提供 resolver 解析函数src/schema.graphql:Schema 层然后 npm run dev ,浏览器打开 http://localhost:4000/ 就可以使用 GraphQL Playground 开始调试了,左边是请求信息栏,左下是请求参数栏和请求头设置栏,右边是返回参数栏,详细用法可以参考 Prisma 文档 3.2 Query首先我们来试试 hello world,我们在 schema.graphql 中写上 Query 的一个入口 hello,它接受 String 类型的返回值# src/schema.graphql# Query 入口type Query { hello: String}在 src/resolvers/index.js 中补充对应的 Resolver,这个 Resolver 比较简单,直接返回的 String// src/resolvers/index.jsexport default { Query: { hello: () => ‘Hello world!’ }}然后我们在 Playground 中进行 Query# 请求query { hello}# 返回值{ “data”: { “hello”: “Hello world!” }}这里拿到了 hello world 返回值,hello world 总是如此愉快,下面我们来进行稍微复杂一点的查询一个查询入口 users 查找所有用户列表,返回一个不可空但长度可以为 0 的数组,数组中如果有元素,则必须为 User类型;另一个查询入口 user 接受一个字符串,查找 ID 为这个字符串的用户,并返回一个 User 类型的可空字段# src/schema.graphql# Query 入口type Query { user(id: String): User users: [User]!}type User { id: ID! name: String! age: Int email: String!}增加对应的 Resolver// src/resolvers/index.jsimport Db from ‘../db’export default { Query: { user: (parent, { id }) => Db.user({ id }), users: (parent, args) => Db.users({}) }}这里的两个方法 Db.user、Db.users 分别是查找对应数据的函数,返回的是 Promise,如果这个 Promise 被 resolve,那么被 resolve 的数据将被作为结果返回。然后进行一次查询就可以查找我们所希望的所有信息# 请求query { user(id: “2”) { id name email age } users { id name }}# 返回值{ “data”: { “user”: { “id”: “2”, “name”: “李四”, “email”: “mmmmm@qq.com”, “age”: 18 }, “users”: [{ “id”: “1”, “name”: “张三” },{ “id”: “2”, “name”: “李四” }] }}注意这里,返回的数组只希望拿到 id、name 这两个字段,因此 GraphQL 并没有返回多余的数据,怎么样,是不是很贴心呢3.3 Mutation知道如何查询数据,还得了解增加、删除、修改,毕竟这是 CRUD 工程师必备的几板斧,不过这里只介绍比较复杂的修改,另外两个方法可以看一下 Github 上。# src/schema.graphql# Mutation 入口type Mutation { updateUser(id: ID!, name: String, email: String, age: Int): User!}type User { id: ID! name: String! age: Int email: String!}同理,Mutation 也需要 Resolver 来处理请求// src/resolvers/index.jsimport Db from ‘../db’export default { Mutation: { updateUser: (parent, { id, name, email, age }) => Db.user({ id }) .then(existUser => { if (!existUser) throw new Error(‘没有这个id的人’) return existUser }) .then(() => Db.updateUser({ id, name, email, age })) }}Mutation 入口 updateUser 拿到参数之后首先进行一次用户查询,如果没找到则抛个错,这个错将作为 error 信息返回给用户,Db.updateUser 这个函数返回的也是 Promise,不过是将改变之后的信息返回# 请求mutation UpdataUser ($id: ID!, $name: String!, $email: String!, $age: Int) { updateUser(id: $id, name: $name, email: $email, age: $age) { id name age }}# 参数{“id”: “2”, “name”: “王五”, “email”: “xxxx@qq.com”, “age”: 19}# 返回值{ “data”: { “updateUser”: { “id”: “2”, “name”: “王五”, “age”: 19 } }}这样完成了对数据的更改,拿到了更改后的数据,并且给定希望返回哪些字段。### 3.4 SubscriptionGraphQL 还有一个有意思的地方就是它可以进行数据订阅,当前端发起订阅请求之后,如果后端发现数据改变,可以给前端推送实时信息,我们用一下看看照例,首先在 Schema 中定义 Subscription 的入口# src/schema.graphql# Subscription 入口type Subscription { subsUser(id: ID!): User}type User { id: ID! name: String! age: Int email: String!}然后补充上它的 Resolver// src/resolvers/index.jsimport Db from ‘../db’const { PubSub, withFilter } = require(‘apollo-server’)const pubsub = new PubSub()const USER_UPDATE_CHANNEL = ‘USER_UPDATE’export default { Mutation: { updateUser: (parent, { id, name, email, age }) => Db.user({ id }) .then(existUser => { if (!existUser) throw new Error(‘没有这个id的人’) return existUser }) .then(() => Db.updateUser({ id, name, email, age })) .then(user => { pubsub.publish(USER_UPDATE_CHANNEL, { subsUser: user }) return user }) }, Subscription: { subsUser: { subscribe: withFilter( (parent, { id }) => pubsub.asyncIterator(USER_UPDATE_CHANNEL), (payload, variables) => payload.subsUser.id === variables.id ), resolve: (payload, variables) => { console.log(’???? 接收到数据: ‘, payload) } } }}这里的 pubsub 是 apollo-server 里负责订阅和发布的类,它负责收到订阅的时候返回一个异步迭代器,并且在后端觉得需要发布订阅的时候向前端发布 payload,这里的 withFilter 的作用是过滤掉不需要的订阅消息,详细用法参照订阅过滤器。首先我们发布一个订阅请求# 请求subscription subsUser($id: ID!) { subsUser(id: $id) { id name age email }}# 参数{ “id”: “2” }然后我们用刚刚的数据更新操作来进行一次数据的更改,那么我们将获取并打印出 pubsub.publish 发布的 payload,这样就完成了数据订阅。在 graph-pack 中数据推送是基于 websocket 来实现的,可以在通信的时候打开 Chrome DevTools 看一下。4. 总结目前前后端的结构大概如下图。后端通过 DAO 层与数据库连接,服务于主要处理业务逻辑的 Service 层,为 Controller 层提供数据源并产出 API;前端通过浏览器 URL 进行路由命中获取目标视图状态,而页面视图是由组件嵌套组成,每个组件维护着各自的组件级状态,一些稍微复杂的应用还会使用集中式状态管理的工具,比如 Vuex、Redux、Mobx 等。前后端只通过 API 来交流,这也是现在前后端分离开发的基础。如果使用 GraphQL,那么后端将不再产出 API,而是将 Controller 层维护为 Resolver,并且和前端维护一套 Schema,这个 Schema 将用来生成接口文档,前端直接通过 Schema 或生成的接口文档来进行自己期望的请求。经过几年一线开发者的填坑,已经有一些不错的工具链可以使用于开发与生产,很多语言也提供了对 GraphQL 的支持,比如 JavaScript/Nodejs、Java、PHP、Ruby、Python、Go、C# 等。一些比较有名的公司比如 Twitter、IBM、Coursera、Airbnb、Facebook、Github、携程等,内部或外部 API 从 RESTful 转为了 GraphQL 风格,特别是 Github,它的 v4 版外部 API 只使用 GraphQL。据一位在 Twitter 工作的大佬说硅谷不少一线二线的公司都在想办法转到 GraphQL 上,但是同时也说了 GraphQL 还需要时间发展,因为将它使用到生产环境需要前后端大量的重构,这无疑需要高层的推动和决心。正如尤雨溪所说,为什么 GraphQL 两三年前没有广泛使用起来呢,可能有下面两个原因:GraphQL 的 field resolve 如果按照 naive 的方式来写,每一个 field 都对数据库直接跑一个 query,会产生大量冗余 query,虽然网络层面的请求数被优化了,但数据库查询可能会成为性能瓶颈,这里面有很大的优化空间,但并不是那么容易做。FB 本身没有这个问题,因为他们内部数据库这一层也是抽象掉的,写 GraphQL 接口的人不需要顾虑 query 优化的问题。GraphQL 的利好主要是在于前端的开发效率,但落地却需要服务端的全力配合。如果是小公司或者整个公司都是全栈,那可能可以做,但在很多前后端分工比较明确的团队里,要推动 GraphQL 还是会遇到各种协作上的阻力。大约可以概括为性能瓶颈和团队分工的原因,希望随着社区的发展,基础设施的完善,会渐渐有完善的解决方案提出,让广大前后端开发者们可以早日用上此利器。网上的帖子大多深浅不一,甚至有些前后矛盾,在下的文章都是学习过程中的总结,如果发现错误,欢迎留言指出参考:GraphQL | 一种为你的 API 而生的查询语言JSON-RPC 2.0 规范 - wiki . leozvcGraphQL 为何没有火起来? - 尤雨溪的回答 - 知乎GraphQL什么鬼 | kazaff’s blog ...

March 12, 2019 · 5 min · jiezi

Laravel接入Apollo

废话不说,直接上代码namespace App\Console\Commands\Apollo;use Illuminate\Console\Command;use Illuminate\Support\Arr;use Org\Multilinguals\Apollo\Client\ApolloClient;class SyncCommand extends Command{ /** * The name and signature of the console command. * * @var string / protected $signature = ‘ue:apollo:sync’; /* * The console command description. * * @var string / protected $description = ‘阿波罗同步’; protected $config = []; /* * Create a new command instance. * * @return void / public function __construct() { parent::__construct(); } /* * Execute the console command. * * @return mixed */ public function handle() { while (true) { $this->doSync(); sleep(10); } } protected function doSync() { if (!$this->config) { $this->config = config(‘apollo’); } $server = Arr::get($this->config, ‘server’); $appid = Arr::get($this->config, ‘app_id’); $namespaces = Arr::get($this->config, ’namespace’); $apollo = new ApolloClient($server, $appid, $namespaces); $error = $apollo->start(); logger()->error(null, [’error’ => $error, ‘class’ => CLASS]); }} ...

March 1, 2019 · 1 min · jiezi

Nacos系列:基于Nacos的配置中心

前言在看正文之前,我想请你回顾一下自己待过的公司都是怎么管理配置的,我想应该会有以下几种方式:1、硬编码没有什么配置不配置的,直接写在代码里面,比如使用常量类优势:对开发友好,开发清楚地知道代码需要用到什么配置劣势:涉及秘钥等敏感配置直接暴露给开发人员,不安全;如果想修改配置必须重新发版,比较麻烦2、外部化配置文件Spring项目经常会在resoures目录下放很多配置文件,各个环境对应不同的配置文件,通过SVN管理优势:配置文件外部化,支持多环境配置管理,修改配置只需重启服务,无需发版劣势:系统庞大时,配置文件很多,多人开发,配置格式不统一,维护麻烦;敏感配置不需要暴露给开发人员,降低风险,但开发经常要和运维沟通怎么修改配置,沟通不恰当容易引发生产事故;而且,如果应用部署在多台机器,对运维来说,修改配置也是非常头疼的事情(当然也可以引入NFS系统来解决一部分问题)3、数据库配置信息存储在数据库中,灵活修改优势:可以灵活管理配置,无需重启服务劣势:界面不友好,配置没有版本管理,一旦出现问题,回滚或定位问题都比较麻烦;此外,数据库必须要保证高可用,避免因此而造成生产故障4、配置中心微服务基础架构体系中的一个不可或缺的基础组件优势:集中化管理,敏感配置可控;多版本存储,方便追溯;界面友好,修改配置一键发布;即使面对多集群也能从容应对,十分淡定劣势:引入组件,增加系统风险;如果是中途切换成配置中心,也会增加研发接入成本;配置中心也需要保证高可用,否则容易造成大面积影响以上几种管理配置文件的方式,我想都会有公司在用,不要因为配置中心有诸多优点,就盲目引进项目中,我觉得应该遵守以下两个原则:做人做事,要知道自己几斤几两释义:没深入研究过的技术,就不要随便拿到公司项目中来试水啦,恐怕到时候坑够你填的,要不然就是你有信心玩得转它。杀只鸡而已,你拿牛刀来做甚?释义:小团队小项目选择简单的配置管理方式就好了,要什么配置中心,纯属没事找事。总而言之,我们必须从实际出发,实事求是,选择适合自己的技术栈。关于为什么需要有配置中心,我推荐一篇文章给你看,讲得比较透彻:《微服务架构为什么需要配置中心?》另外,我觉得对开发本身来说,是宁愿自己管理自己代码的配置的,交给运维总是会有各种各样的问题,至于敏感配置,说实话,开发人员要真想做点“坏事”,那拦得住吗?但是,从公司的角度来讲,把服务器的配置管理交给运维同事是符合常理的,系统需要稳定且安全地运行,这是对客户的负责,从这一方面去思考,这么做是合情合理的。Okay,我就啰嗦到这里吧,下面正式介绍Nacos作为配置中心是怎么使用的。Nacos 结合 Spring添加 maven 依赖:<dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-spring-context</artifactId> <version>${nacos-spring-context.version}</version></dependency>使用 @EnableNacosConfig 开启 Nacos Spring 的配置管理功能@Configuration@EnableNacosConfig(globalProperties = @NacosProperties(serverAddr = “127.0.0.1:8848”))@NacosPropertySource(dataId = “nacos.spring.config”, autoRefreshed = true)public class NacosConfig {}其中:@Configuration:Spring的注解,配置应用上下文@EnableNacosConfig:Nacos的注册,启用 Nacos Spring 的配置管理服务@NacosProperties:全局和自定义Nacos属性的统一注解@NacosPropertySource:加载数据源globalProperties:全局 Nacos 属性serverAddr:Nacos Server服务器地址dataId:配置的数据集IDautoRefreshed:是否开启配置动态更新再写一个Controller类,来验证Nacos的配置管理功能,代码如下:package com.learn.nacos;import com.alibaba.nacos.api.annotation.NacosInjected;import com.alibaba.nacos.api.config.ConfigService;import com.alibaba.nacos.api.config.annotation.NacosValue;import com.alibaba.nacos.api.exception.NacosException;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;@Controller@RequestMapping(value = “config”)public class NacosConfigController { @NacosInjected private ConfigService configService; @NacosValue(value = “${useLocalCache:false}”, autoRefreshed = true) private boolean useLocalCache; @RequestMapping(value = “/get”, method = RequestMethod.GET) @ResponseBody public boolean get() { return useLocalCache; } @RequestMapping(method = RequestMethod.GET) @ResponseBody public ResponseEntity<String> publish(@RequestParam String dataId, @RequestParam(defaultValue = “DEFAULT_GROUP”) String group, @RequestParam String content) throws NacosException { boolean result = configService.publishConfig(dataId, group, content); if (result) { return new ResponseEntity<String>(“Success”, HttpStatus.OK); } return new ResponseEntity<String>(“Fail”, HttpStatus.INTERNAL_SERVER_ERROR); }}该Controller类提供了两个HTTP接口读取配置:http://127.0.0.1:8080/config/get发布配置:http://127.0.0.1:8080/config?dataId=XXX&content=XXX发布配置还可以通过 Nacos Open API:curl -X POST “http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=XXX&group=XXX&content=XXX 发布配置,你也可以用Postman工具模拟POST请求进行配置发布,我这里主要是为了方便验证问题,采用了这种方式。在验证之前,请先确保 Nacos Server 已经启动,Nacos Server 的安全及启动方式详见:《Nacos系列:欢迎来到Nacos的世界!》启动Tomcat,观察Console控制台20:50:13.646 [RMI TCP Connection(5)-127.0.0.1] WARN com.alibaba.nacos.spring.core.env.AnnotationNacosPropertySourceBuilder - There is no content for NacosPropertySource from dataId[nacos.spring.config] , groupId[DEFAULT_GROUP] , properties[{encode=${nacos.encode:UTF-8}, namespace=${nacos.namespace:}, contextPath=${nacos.context-path:}, endpoint=${nacos.endpoint:}, serverAddr=${nacos.server-addr:}, secretKey=${nacos.secret-key:}, accessKey=${nacos.access-key:}, clusterName=${nacos.cluster-name:}}].20:50:17.825 [RMI TCP Connection(5)-127.0.0.1] INFO com.alibaba.nacos.spring.context.event.LoggingNacosConfigMetadataEventListener - Nacos Config Metadata : dataId=‘nacos.spring.config’, groupId=‘DEFAULT_GROUP’, beanName=‘nacosConfig’, bean=‘null’, beanType=‘class com.learn.nacos.NacosConfig’, annotatedElement=‘null’, xmlResource=‘null’, nacosProperties=’{serverAddr=127.0.0.1:8848, encode=UTF-8}’, nacosPropertiesAttributes=’{encode=${nacos.encode:UTF-8}, namespace=${nacos.namespace:}, contextPath=${nacos.context-path:}, endpoint=${nacos.endpoint:}, serverAddr=${nacos.server-addr:}, secretKey=${nacos.secret-key:}, accessKey=${nacos.access-key:}, clusterName=${nacos.cluster-name:}}’, source=‘org.springframework.core.type.classreading.AnnotationMetadataReadingVisitor@66e4d430’, timestamp=‘1550753413647’我们先通过http://127.0.0.1:8080/config?dataId=nacos.spring.config&content=useLocalCache=true发布一个dataId为nacos.spring.config且配置内容为useLocalCache=true的配置集,观察Nacos控制台的变化再通过http://127.0.0.1:8080/config/get读取配置然后在Nacos控制台将useLocalCache的值改为false,并发布配置再次访问http://127.0.0.1:8080/config/getNacos 结合 Spring Boot添加 Starter 依赖:<dependency> <groupId>com.alibaba.boot</groupId> <artifactId>nacos-config-spring-boot-starter</artifactId> <version>0.2.1</version></dependency>注意:版本 0.2.x.RELEASE 对应的是 Spring Boot 2.x 版本,版本 0.1.x.RELEASE 对应的是 Spring Boot 1.x 版本。在application.properties中添加如下配置信息:nacos.config.server-addr=127.0.0.1:8848添加NacosConfigApplication启动类@SpringBootApplication@NacosPropertySource(dataId = “nacos.springboot.config”, autoRefreshed = true)public class NacosConfigApplication { public static void main(String[] args) { SpringApplication.run(NacosConfigApplication.class, args); }}如果你看过我的上一篇文章:《Nacos系列:基于Nacos的注册中心》,那么你应该知道 Spring Boot 实现方式和 Spring 的没太大差别,所以我就不再细说了,请参考我的源码示例或者官网资料学习。这里说下我在学习过程中遇到的一个问题,在application.properties添加配置文件的时候,不小心将nacos.config.server-addr写成了nacos.discovery.server-addr,结果启动项目时,一直报错:ERROR 9028 — [ main] o.s.b.d.LoggingFailureAnalysisReporter : ——APPLICATION FAILED TO START——Description:client error: invalid param. nullAction:please check your client configuration刚开始一直找不到原因,后面跟着官网代码示例复核,才发现是配置问题导致的,呵呵哒,自己给自己挖坑。后语我挺喜欢Nacos的,既然做服务发现和管理,又能做配置管理,这两者本质没多大区别,Nacos把这两者统一起来,一举两得,我觉得没什么不好,要不然你引入了Zookeeper作为注册中心,还要引入Apollo作为配置中心,无端增加学习成本。就像之前听音乐,我一般用网易云音乐就好,后面因为搞了版权的事,不得不下载了虾米和QQ音乐,我就听个歌而已,手机里装了三个APP,你说,这叫什么事儿?示例源码Nacos + Spring :learn-nacos-spring-configNacos + Spring Boot : learn-nacos-springboot-config代码已上传至码云和Github上,欢迎下载学习GiteeGithub参考资料微服务架构为什么需要配置中心?Nacos Spring 快速开始Nacos Spring Boot 快速开始SpringBoot使用Nacos配置中心Spring Cloud Alibaba基础教程:使用Nacos作为配置中心 ...

February 21, 2019 · 2 min · jiezi

SpringCloud灰度发布实践(整合Apollo配置中心)

代码git地址feature[x] 灰度服务[x] 配置中心[x] 动态路由前言上篇文章介绍了SpringCloud灰度的实现及流程,每次修改服务的元数据信息metadata-map值需要重新调用一次eureka的RestFul接口,不仅如此当服务重启后又会重新读最初的配置值,这样不仅麻烦而且还不可靠。在经过与SpringCloud Config 、Disconf、Apollo等配置中心作出对比后,发现被Apollo友好方便的管理端所深深吸引,再加上该配置中心支持配置文件的灰度发布简直不要太完美。Apollo灰度配置让多个实例共享一个配置文件,示例配置spring.application.name = provide-testserver.port = 7770eureka.client.service-url.defaultZone = http://localhost:1111/eureka/然后新起一个灰度配置,让对应的服务使用该配置。eureka.instance.metadata-map.version = v1事件监听监听Apollo事件,当发现配置文件中的eureka.instance.metadata-map.version值若发生改变,则调用eureka接口更改metadata-map元数据 @ApolloConfigChangeListener(“application”) private void someOnChange(ConfigChangeEvent changeEvent) { changeEvent.changedKeys().forEach(key -> { ConfigChange change = changeEvent.getChange(key); // 灰度配置 if(“eureka.instance.metadata-map.version”.equals(change.getPropertyName())) { String appname = eurekaInstanceConfig.getAppname(); String instanceId = eurekaInstanceConfig.getInstanceId(); String value = StringUtils.isEmpty(change.getNewValue()) ? "" : change.getNewValue(); //TODO 调用eureka更改元数据接口 } }); }这样一来,只需要通过修改配置文件然后就会触发监听事件从而自动触发请求eureka更改元数据的值。动态路由在网关zuul整合了动态路由功能,监听Apollo配置文件使其修改配置文件后可以马上生效。此处不对此功能做过多的介绍,详情见代码配置示例url.map.provide-test = /pt/**url.map.为固定写法,provide-test为服务名称,/pt/**为映射路径参考文章灰度使用在启动类添加注解@SpringBootApplication@EnableDiscoveryClient@EnableGrayConfigpublic class ProviderApplication { public static void main(String[] args) { SpringApplication.run(ProviderApplication.class, args); }}

January 22, 2019 · 1 min · jiezi

SpringBoot集成MQTT

SpringBoot集成MQTTMQTTMQTT(消息队列遥测传输)是ISO标准(ISO/IEC PRF 20922)下基于发布/订阅范式的消息协议。它工作在 TCP/IP协议族上,是为硬件性能低下的远程设备以及网络状况糟糕的情况下而设计的发布/订阅型消息协议。国内很多企业都广泛使用MQTT作为Android手机客户端与服务器端推送消息的协议。特点MQTT协议是为大量计算能力有限,且工作在低带宽、不可靠的网络的远程传感器和控制设备通讯而设计的协议,它具有以下主要的几项特性:使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合;对负载内容屏蔽的消息传输;使用TCP/IP提供网络连接;有三种消息发布服务质量;至多一次:消息发布完全依赖底层 TCP/IP 网络。会发生消息丢失或重复。这一级别可用于如下情况,环境传感器数据,丢失一次读记录无所谓,因为不久后还会有第二次发送。至少一次:确保消息到达,但消息重复可能会发生。只有一次:确保消息到达一次。这一级别可用于如下情况,在计费系统中,消息重复或丢失会导致不正确的结果。小型传输,开销很小(固定长度的头部是 2 字节),协议交换最小化,以降低网络流量;使用Last Will和Testament特性通知有关各方客户端异常中断的机制。Apache-ApolloApache Apollo是一个代理服务器,其是在ActiveMQ基础上发展而来的,可以支持STOMP, AMQP, MQTT, Openwire, SSL, WebSockets 等多种协议。 原理:服务器端创建一个唯一订阅号,发送者可以向这个订阅号中发东西,然后接受者(即订阅了这个订阅号的人)都会收到这个订阅号发出来的消息。以此来完成消息的推送。服务器其实是一个消息中转站。下载下载地址:http://activemq.apache.org/ap…配置与启动需要安装JDK环境在命令行模式下进入bin,执行apollo create mybroker d:\apache-apollo\broker,创建一个名为mybroker虚拟主机(Virtual Host)。需要特别注意的是,生成的目录就是以后真正启动程序的位置。在命令行模式下进入d:\apache-apollo\broker\bin,执行apollo-broker run,也可以用apollo-broker-service.exe配置服务。访问http://127.0.0.1:61680打开web管理界面。(密码查看broker/etc/users.properties)启动端口,看cmd输出。SpringBoot2的开发添加依赖<!– spring-boot版本 2.1.0.RELEASE–><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-integration</artifactId></dependency><dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-stream</artifactId></dependency><dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-mqtt</artifactId></dependency>自定义配置# src/main/resources/config/mqtt.properties################### MQTT 配置################### 用户名mqtt.username=admin# 密码mqtt.password=password# 推送信息的连接地址,如果有多个,用逗号隔开,如:tcp://127.0.0.1:61613,tcp://192.168.1.61:61613mqtt.url=tcp://127.0.0.1:61613################### MQTT 生产者################### 连接服务器默认客户端IDmqtt.producer.clientId=mqttProducer# 默认的推送主题,实际可在调用接口时指定mqtt.producer.defaultTopic=topic1################### MQTT 消费者################### 连接服务器默认客户端IDmqtt.consumer.clientId=mqttConsumer# 默认的接收主题,可以订阅多个Topic,逗号分隔mqtt.consumer.defaultTopic=topic1配置MQTT发布和订阅import org.apache.commons.lang3.StringUtils;import org.eclipse.paho.client.mqttv3.MqttConnectOptions;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.integration.annotation.ServiceActivator;import org.springframework.integration.channel.DirectChannel;import org.springframework.integration.core.MessageProducer;import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;import org.springframework.integration.mqtt.core.MqttPahoClientFactory;import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;import org.springframework.messaging.Message;import org.springframework.messaging.MessageChannel;import org.springframework.messaging.MessageHandler;import org.springframework.messaging.MessagingException;/** * MQTT配置,生产者 * * @author BBF /@Configurationpublic class MqttConfig { private static final Logger LOGGER = LoggerFactory.getLogger(MqttConfig.class); private static final byte[] WILL_DATA; static { WILL_DATA = “offline”.getBytes(); } /* * 订阅的bean名称 / public static final String CHANNEL_NAME_IN = “mqttInboundChannel”; /* * 发布的bean名称 / public static final String CHANNEL_NAME_OUT = “mqttOutboundChannel”; @Value("${mqtt.username}") private String username; @Value("${mqtt.password}") private String password; @Value("${mqtt.url}") private String url; @Value("${mqtt.producer.clientId}") private String producerClientId; @Value("${mqtt.producer.defaultTopic}") private String producerDefaultTopic; @Value("${mqtt.consumer.clientId}") private String consumerClientId; @Value("${mqtt.consumer.defaultTopic}") private String consumerDefaultTopic; /* * MQTT连接器选项 * * @return {@link org.eclipse.paho.client.mqttv3.MqttConnectOptions} / @Bean public MqttConnectOptions getMqttConnectOptions() { MqttConnectOptions options = new MqttConnectOptions(); // 设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录, // 这里设置为true表示每次连接到服务器都以新的身份连接 options.setCleanSession(true); // 设置连接的用户名 options.setUserName(username); // 设置连接的密码 options.setPassword(password.toCharArray()); options.setServerURIs(StringUtils.split(url, “,”)); // 设置超时时间 单位为秒 options.setConnectionTimeout(10); // 设置会话心跳时间 单位为秒 服务器会每隔1.520秒的时间向客户端发送心跳判断客户端是否在线,但这个方法并没有重连的机制 options.setKeepAliveInterval(20); // 设置“遗嘱”消息的话题,若客户端与服务器之间的连接意外中断,服务器将发布客户端的“遗嘱”消息。 options.setWill(“willTopic”, WILL_DATA, 2, false); return options; } /** * MQTT客户端 * * @return {@link org.springframework.integration.mqtt.core.MqttPahoClientFactory} / @Bean public MqttPahoClientFactory mqttClientFactory() { DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory(); factory.setConnectionOptions(getMqttConnectOptions()); return factory; } /* * MQTT信息通道(生产者) * * @return {@link org.springframework.messaging.MessageChannel} / @Bean(name = CHANNEL_NAME_OUT) public MessageChannel mqttOutboundChannel() { return new DirectChannel(); } /* * MQTT消息处理器(生产者) * * @return {@link org.springframework.messaging.MessageHandler} / @Bean @ServiceActivator(inputChannel = CHANNEL_NAME_OUT) public MessageHandler mqttOutbound() { MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler( producerClientId, mqttClientFactory()); messageHandler.setAsync(true); messageHandler.setDefaultTopic(producerDefaultTopic); return messageHandler; } /* * MQTT消息订阅绑定(消费者) * * @return {@link org.springframework.integration.core.MessageProducer} / @Bean public MessageProducer inbound() { // 可以同时消费(订阅)多个Topic MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter( consumerClientId, mqttClientFactory(), StringUtils.split(consumerDefaultTopic, “,”)); adapter.setCompletionTimeout(5000); adapter.setConverter(new DefaultPahoMessageConverter()); adapter.setQos(1); // 设置订阅通道 adapter.setOutputChannel(mqttInboundChannel()); return adapter; } /* * MQTT信息通道(消费者) * * @return {@link org.springframework.messaging.MessageChannel} / @Bean(name = CHANNEL_NAME_IN) public MessageChannel mqttInboundChannel() { return new DirectChannel(); } /* * MQTT消息处理器(消费者) * * @return {@link org.springframework.messaging.MessageHandler} / @Bean @ServiceActivator(inputChannel = CHANNEL_NAME_IN) public MessageHandler handler() { return new MessageHandler() { @Override public void handleMessage(Message<?> message) throws MessagingException { LOGGER.error("===================={}============", message.getPayload()); } }; }}消息发布器import org.springframework.integration.annotation.MessagingGateway;import org.springframework.integration.mqtt.support.MqttHeaders;import org.springframework.messaging.handler.annotation.Header;import org.springframework.stereotype.Component;/* * MQTT生产者消息发送接口 * <p>MessagingGateway要指定生产者的通道名称</p> * @author BBF /@Component@MessagingGateway(defaultRequestChannel = MqttConfig.CHANNEL_NAME_OUT)public interface IMqttSender { /* * 发送信息到MQTT服务器 * * @param data 发送的文本 / void sendToMqtt(String data); /* * 发送信息到MQTT服务器 * * @param topic 主题 * @param payload 消息主体 / void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, String payload); /* * 发送信息到MQTT服务器 * * @param topic 主题 * @param qos 对消息处理的几种机制。<br> 0 表示的是订阅者没收到消息不会再次发送,消息会丢失。<br> * 1 表示的是会尝试重试,一直到接收到消息,但这种情况可能导致订阅者收到多次重复消息。<br> * 2 多了一次去重的动作,确保订阅者收到的消息有一次。 * @param payload 消息主体 / void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, @Header(MqttHeaders.QOS) int qos, String payload);}发送消息/* * MQTT消息发送 * * @author BBF /@Controller@RequestMapping(value = “/")public class MqttController { /* * 注入发送MQTT的Bean / @Resource private IMqttSender iMqttSender; /* * 发送MQTT消息 * @param message 消息内容 * @return 返回 / @ResponseBody @GetMapping(value = “/mqtt”, produces =“text/html”) public ResponseEntity<String> sendMqtt(@RequestParam(value = “msg”) String message) { iMqttSender.sendToMqtt(message); return new ResponseEntity<>(“OK”, HttpStatus.OK); }}入口类import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;import org.springframework.context.annotation.PropertySource;/* * SpringBoot 入口类 * * @author BBF */@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class})@PropertySource(encoding = “UTF-8”, value = {“classpath:config/mqtt.properties”})public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }} ...

January 8, 2019 · 3 min · jiezi