关于配置:避免-10-大-NGINX-配置错误下

原文作者:Timo Stark of F5 和 Sergey Budnevich of F5原文链接:防止 10 大 NGINX 配置谬误转载起源:NGINX 官方网站在帮忙 NGINX 用户解决问题时,咱们常常会发现配置谬误,这种配置谬误也每每呈现在其余用户的配置中,甚至有时还会呈现在咱们的 NGINX 工程师共事编写的配置中!本文介绍了 10 个最常见的谬误,并解释了问题所在以及相应的解决办法。 1.每个 worker 的文件描述符有余2.error_log off 指令3.未启用与上游服务器的 keepalive 连贯4.遗记指令继承的工作机制5.proxy_buffering off 指令6.if 指令使用不当7.过多的健康检查8.不平安地拜访指标9.当所有流量都来自同一个 /24 CIDR 块时应用 ip_hash10.不采纳上游组 谬误 6:if 指令使用不当if 指令应用起来很辣手,尤其是在 location{}块中。它通常不会依照预期执行,甚至还会导致呈现段谬误。事实上,在 NGINX Wiki 中有一篇题为“if 问题多多 (If is Evil)”的文章,具体探讨了 if 问题以及如何防止这些问题。 通常,在 if{} 块中,您能够始终平安应用的指令只有 return 和 rewrite。以下示例应用 if 来检测蕴含 X‑Test http音讯头的申请(能够是您想要测试的任何条件)。NGINX 返回 430 (Request Header Fields Too Large) 谬误,在指定的地位 @error_430 进行拦挡并将申请代理到名为 b 的上游 group。 ...

August 17, 2022 · 4 min · jiezi

关于配置:避免-10-大-NGINX-配置错误上

原文作者:Timo Stark of F5, Sergey Budnevich of F5原文链接:防止 10 大 NGINX 配置谬误转载起源:NGINX 官方网站在帮忙 NGINX 用户解决问题时,咱们常常会发现配置谬误,这种配置谬误也每每呈现在其余用户的配置中,甚至有时还会呈现在咱们的 NGINX 工程师共事编写的配置中!本文介绍了 10 个最常见的谬误,并解释了问题所在以及相应的解决办法。 1.每个 worker 的文件描述符有余2.error_log off 指令3.未启用与上游服务器的 keepalive 连贯4.遗记指令继承的工作机制5.proxy_buffering off 指令6.if 指令使用不当7.过多的健康检查8.不平安地拜访指标9.当所有流量都来自同一个 /24 CIDR 块时应用 ip_hash10.不采纳上游组 谬误 1:每个 worker 没有足够的文件描述符worker_connections 指令用于设置 NGINX worker 过程能够关上的最大并发连接数(默认为 512)。所有类型的连贯(例如与代理服务器的连贯)都计入最大值,而不仅仅是客户端连贯。但重要的是要记住,最终每个 worker 的并发连接数还有另一个限度:操作系统对调配给每个过程的文件描述符 (file descriptor,即FD) 最大数量的限度。在古代 UNIX 发行版中,默认限度为 1024。 对于除最小的NGINX部署之外的 所有部署,将每个 worker 的连接数限度为 512 可能太少了。事实上,咱们将随 NGINX 开源版二进制文件和 NGINX Plus 一起散发的默认 nginx.conf 文件将其减少到 1024。 常见的配置谬误是没有将 FD 的限度减少到至多两倍的 worker_connections 的值。解决办法是在主配置上下文中应用 worker_rlimit_nofile 指令设置该值。 ...

August 11, 2022 · 3 min · jiezi

关于配置:etcresolvconf配置文件详解

该文件是dns的配置文件,最近总是呈现UnknownHostEception这个谬误,很多状况下是dns配置不正确造成的,它的配置文件格式很简略,每行以关键字开明,前面配参数 例如:nameserver 8.8.8.8 /etc/resolv.conf的关键字次要有四个,别离是:nameserver #定义DNS服务器的IP地址 其中最终要的就是nameserver,其余都是可选的,能够配置多个,在查问时,依照nameserver在本文中的秩序进行,当第一个dns没有反馈时,才查问第二个 domain #定义本地域名 申明主机的域名。很多程序用到它,如邮件体系;当为沒有域名的主机进行DNS查问时,也要用到。如果沒有域名,主机名将被应用,删除所有在第一个点( . )后面的內容。search #定义域名的搜寻列表 它的多个参数指明域名查问秩序。当要查问沒有域名的主机,主机将在由search申明的域中拆散查找。domain和search不能共存;如果同时存在,前面出现的将会被应用。sortlist #对返回的域名进行排序 容许将失去域名进行特定的排序。它的参数为网络/掩码对,许可任意的排列秩序。参考链接:understanding /etc/resolv.conf file in Linux

July 2, 2022 · 1 min · jiezi

从零开始入门-K8s-可观测性你的应用健康吗

一、基本知识存储快照产生背景在使用存储时,为了提高数据操作的容错性,我们通常有需要对线上数据进行 snapshot ,以及能快速 restore 的能力。另外,当需要对线上数据进行快速的复制以及迁移等动作,如进行环境的复制、数据开发等功能时,都可以通过存储快照来满足需求,而 K8s 中通过 CSI Snapshotter controller 来实现存储快照的功能。 存储快照用户接口-Snapshot我们知道,K8s 中通过 pvc 以及 pv 的设计体系来简化用户对存储的使用,而存储快照的设计其实是仿照  pvc & pv 体系的设计思想。当用户需要存储快照的功能时,可以通过 VolumeSnapshot 对象来声明,并指定相应的 VolumeSnapshotClass 对象,之后由集群中的相关组件动态生成存储快照以及存储快照对应的对象 VolumeSnapshotContent 。如下对比图所示,动态生成 VolumeSnapshotContent 和动态生成 pv 的流程是非常相似的。 存储快照用户接口-Restore有了存储快照之后,如何将快照数据快速恢复过来呢?如下图所示: 如上所示的流程,可以借助 PVC 对象将其的 dataSource 字段指定为 VolumeSnapshot 对象。这样当 PVC 提交之后,会由集群中的相关组件找到 dataSource 所指向的存储快照数据,然后新创建对应的存储以及 pv 对象,将存储快照数据恢复到新的 pv 中,这样数据就恢复回来了,这就是存储快照的 restore 用法。 Topolopy-含义首先了解一下拓扑是什么意思:这里所说的拓扑是 K8s 集群中为管理的 nodes 划分的一种“位置”关系,意思为:可以通过在 node 的 labels 信息里面填写某一个 node 属于某一个拓扑。   常见的有三种,这三种在使用时经常会遇到的: 第一种,在使用云存储服务的时候,经常会遇到 region,也就是地区的概念,在 K8s 中常通过 label failure-domain.beta.kubernetes.io/region 来标识。这个是为了标识单个 K8s 集群管理的跨 region 的 nodes 到底属于哪个地区;第二种,比较常用的是可用区,也就是 available zone,在 K8s 中常通过 label failure-domain.beta.kubernetes.io/zone 来标识。这个是为了标识单个 K8s 集群管理的跨 zone 的 nodes 到底属于哪个可用区;第三种,是 hostname,就是单机维度,是拓扑域为 node 范围,在 K8s 中常通过 label kubernetes.io/hostname 来标识,这个在文章的最后讲 local pv 的时候,会再详细描述。上面讲到的三个拓扑是比较常用的,而拓扑其实是可以自己定义的。可以定义一个字符串来表示一个拓扑域,这个 key 所对应的值其实就是拓扑域下不同的拓扑位置。 ...

October 15, 2019 · 5 min · jiezi

生存还是毁灭一文读懂挖矿木马的战略战术

前言比特币等虚拟货币在2019年迎来了久违的大幅上涨,从最低3000美元上涨至7月份的14000美元,涨幅达300%,巨大的金钱诱惑使得更多的黑产团伙加入了恶意挖矿的行列。阿里云安全团队通过对云上僵尸网络家族的监控,发现恶意挖矿已成为黑产团伙主要的牟利方式。2019年共监控到58个成规模的挖矿木马团伙(数据截止到8月底),以累积感染量定义木马活跃度,下图/表是活跃TOP10的木马家族及简介。本文尝试从宏观角度分析、总结挖矿木马常用技术及发展趋势,以期能够给企业安全防护带来启示。 家族名 简介 平台 攻击方式ddgs一个Go语言实现的挖矿僵尸网络,最早曝光于2017年10月。LinuxSSH、Redis爆破MinerGuard一个Go语言实现的挖矿僵尸网络,2019年4月开始爆发Windows、Linux双平台SSH、Redis、Sqlserver爆破、多种web服务漏洞(ElasticSearch、Weblogic、Spring、ThinkPHP等)kerberods2019年4月开始爆发的挖矿僵尸网络LinuxSSH爆破、Redis爆破、Confluence RCE等Web服务漏洞kworkerdsrootkit挖矿蠕虫,最早曝光于2018年9月Linux、Windows双平台ssh、redis爆破、WebLogic远程代码执行漏洞等buleheroWindows下的挖矿蠕虫病毒,最早曝光于2018年8月Windows永恒之蓝漏洞、ipc$爆破以及Struts2RCE、ThinkPHP RCE等多个Web服务漏洞CryptoSink2019年3月开始爆发,会将其他矿池地址解析为127.0.0.1Linux、Windows双平台ElasticSearch未授权访问漏洞、Redis爆破systemdMiner2019年4月爆发,借助入侵ddgs的c&c服务器快速扩张LinuxHadoop Yarn未授权访问漏洞、SSH密钥watchdogs2019年2月爆发,Linux下的挖矿蠕虫LinuxSSH爆破、Redis爆破8220Miner8220挖矿团伙是一个长期活跃的利用多个漏洞进行攻击和部署挖矿程序的国内团伙,最早曝光于2018年8月Linux、Windows双平台Hadoop Yarn未授权访问漏洞、docker未授权访问漏洞等多个web服务漏洞ibusPerl脚本实现的挖矿蠕虫,2019年1月开始爆发LinuxThinkPHP5 RCE、java反序列化漏洞、Weblogic WLS组件RCE漏洞、WebLogic 任意文件上传漏洞、redis 未授权访问漏洞等核心观点木马投放方式全面蠕虫化,多种漏洞组合攻击成为趋势,N-day漏洞利用速度在加快 这种趋势无疑令人异常担忧,意味着挖矿木马的传播能力在大幅增强,变得无孔不入。一旦企业的信息系统存在任意可被利用的漏洞,那么企业内网将会快速沦陷,挖矿行为会抢占CPU资源,严重影响企业信息系统运行。这对企业的漏洞管理和安全防御能力有了更高、更快的要求。 木马持久化技术被成熟利用,rootkit、无文件技术成为趋势 挖矿木马入侵成功后必定希望能够长久稳定的挖掘出虚拟货币,其技术上使用rootkit、无文件等技术实现长期、隐蔽运行。对于一般的运维人员,系统一旦中招后,顽固木马是难以清除的,这需要企业具备更专业的安全应急响应能力。 木马开始出现跨平台趋势 Golang语言天生具备跨平台编译能力,这个特点方便黑产团伙在多平台间移植,新出现的恶意软件使用GO也成为了一种趋势。TOP10中有6个是2019年爆发的挖矿木马,其中有5个是GO语言实现的,MinerGuard和kworkerds已具备了Linux/Windows双平台传播的能力。 工欲善,利其器-攻击之术全网漏洞扫描/投放->蠕虫化投放1、利用单一或多个IP攻击全网漏洞进行投放 这种投放方式较为原始,无横向传播能力,效率比较低下,且容易被防御拦截。在我们的监控中,使用这种方式的木马规模普遍较小。例如:8220Miner固定使用多个国外IP进行持续攻击,会定期更换IP,但更换频率较低。而ddsMiner通过sqlserver入侵,攻击载荷会下载名为dds.exe的PE文件(如:[http://113[.]69[.]206[.]219:4523/dds.exe)](https://yq.aliyun.com/go/arti...,该团伙每天至少使用1个新的IP进行全网攻击,攻击时间持续数小时,该IP也是当天的恶意文件托管站。 2、蠕虫化投放 蠕虫化的挖矿botnet具备攻击模块,扫描并感染网络上的其他服务器,使用这种方式进行传播会按照指数增长的速度扩张,且这种方式会使得溯源和防御变得更加困难。TOP10中2019年爆发的几个挖矿botnet全部使用蠕虫化投放方式,他们都是在短时间内(几天时间)得到快速扩张跻身TOP10。 单一漏洞利用->组合漏洞横向传播早期的挖矿木马使用固定漏洞在公网传播,传播速度和规模受限。而使用多种漏洞组合攻击的方式使得挖矿木马具备了内网横向传播的能力,攻击模块会集成通用的WEB服务漏洞、爆破、数据库漏洞等攻击方式。‘聪明’的挖矿木马作者更是使用不同的内外网攻击策略,进行更高效的传播。例如:Windows平台下的Bulehero挖矿木马,在内网优先使用永恒之蓝漏洞、ipc$爆破、RDP爆破进行传播,在公网则优先使用Web服务漏洞进行传播。Linux平台下的kerberods挖矿木马,在内网优先使用本地ssh key、ssh爆破进行传播。在这种高效策略下,企业内网通常在几分钟内被全部入侵。 N-day漏洞快速利用互联网大规模存在且未被修复的通用漏洞往往成为挖矿僵尸网络争夺的'肥肉',N-day漏洞爆发后难以在短时间内得到有效修复,'嗅觉'灵敏的黑产团伙会很快纳入挖矿木马的武器库。据我们观察,N-day漏洞留给运维人员进行修复的空窗期越来越短,如Jboss反序列化漏洞于2017年5月被发现,年底JbossMiner开始对其大规模利用。2018年12月ThinkPhp远程代码执行漏洞爆发,十几天后被BuleHero团伙利用进行。2019年4月8日Confluence RCE 漏洞利用POC发布,4月10日就kererods蠕虫就开始利用该漏洞大肆传播,中间只隔了短短两天。再次对云平台及用户的快速响应能力构成严峻考验。 通往财富之路-牟利之术矿池配置方式:挖矿木马植入开源挖矿程序进行挖矿,矿机程序启动时通过命令行传入挖矿参数,但是这种方式较为原始,释放出的木马无法修改配置参数。第二种方式使用配置文件下发,结合定时任务实现对挖矿参数的控制,这种使用方式较为常见。以上两种方式都存在易被检测的特点,有些黑产团伙会对开源挖矿程序进行二次开发,将矿机配置参数硬编码在恶意程序中,并进行加壳对抗检测,以达到隐蔽挖矿的目的。 挖矿程序命令行配置  通过配置文件:MinerGuard矿机配置文件截图 挖矿软件硬编码配置 ddgs硬编码的矿池及钱包地址 挖矿方式:公共矿池->矿池代理1、公共矿池方式 使用匿名的公共矿池是恶意挖矿最常见的方式,使用方法简单,但是由于需要配置独立的钱包地址,因此易被跟踪溯源,而且对bot挖矿无管理能力。 如下图是在公共矿池上查询8220Miner的钱包地址及算力,目前该地址挖掘到15.5个门罗币,可借此估算出该挖矿僵尸网络的规模。 2、矿池代理 有些挖矿僵尸网络会自己搭建矿池代理,通过代理可降低挖矿难度,也可根据收益随时切换高收益矿池、高收益矿币种,这种方式无法通过钱包地址进行跟踪。 下图是监控到masscanMiner使用了矿池代理进行挖矿,通过进程cmdline可知矿池开放在121.42.151.137的28850端口,这并非一公共矿池常用的端口,登陆账号也为默认账号。 其他变现方式:附带DDos、Socks代理除了挖矿这种本职工作,有些黑产团伙会顺带通过其他方式进行变现,比如DDos、代理等。如下图是sicMiner恶意样本会运行一个python脚本,该脚本是github上开源的socks5代理,代理运行在7081端口,该团伙可能是通过出售代理进行变现。 生存还是毁灭-持久化之术挖矿木马入侵成功后需要长期驻留于目标操作系统,以达到长期稳定地产出虚拟货币,通常会使用各种技术对抗安全检测和运维人员的清除。 清除/卸载安全软件卸载宿主机的安全防护软件是常规操作,由于挖矿的攻击行为多针对服务器,黑产团伙也特别针对云环境的安全软件精准对抗。如下是kworderds蠕虫在windows、Linux下卸载不同安全软件的行为。 kworkerds关闭杀毒软件 kworkerds挖矿蠕虫卸载安骑士等安全工具 rootkit技术1、通过定时任务/计划任务实现常驻 Linux下的crontab定时任务是很多恶意软件常见的驻留方式,他们并不仅仅会把自己写入用户的crontab,还会写入软件包的crontab,如/etc/cron.d,这样使得自己更不容易被发现。而Windows下则通过计划任务、修改注册表实现类似的驻留方式。如下是ddgs蠕虫恶意进程行为通过crondtab启动。 ddgs通过CROND定时任务启动恶意shell:/bin/sh -c curl -fsSL [http://218[.]248[.]40[.]228:9999/i.sh?6](https://yq.aliyun.com/go/arti... | sh 2、动态链接库预加载型rootkit Linux下的动态链接库预加载机制在加载其他常规系统库之前就会预先加载用户定义的动态链接库,如果自定义库的函数与系统库中找到的函数具有相同的名称,自定义动态链接库就会覆盖系统库中的函数。攻击者通过动态连接库预加载,实现对libc中诸如readdir等常用函数的hook,当ps、top等shell指令尝试读取/proc/目录获取进程信息时对恶意进行隐藏。 如下图是8220miner使用该技术劫持linux动态链接库配置文件/etc/ld.so.preload。 无文件攻击技术无文件攻击不需要将恶意软件落地到磁盘,因此难以被杀软查杀,具备更好的隐蔽性。在挖矿僵尸网络中通常使用各种工具(比如Windows的WMI命令行工具wmic.exe)或者脚本编程语言(如PowerShell)提供的API接口访问WMI,来实现无文件攻击。如下是TheHidden使用wmic、WannaMine使用powershell进行无文件攻击。 TheHidden使用wmic无文件攻击的代码片段 WannaMine的恶意进程使用powershell隐藏、编码功能进行无文件攻击 文件名/路径混淆除了对抗各种安全工具,还要对抗运维人员的手动排查,将文件名和路径进行混淆也是常用的手段。比如ibus会将恶意文件写入多个系统目录下,并且通过随机变更大小写等方式生成和隐藏目录下文件名类似的混淆文件名。 ibus生成在系统目录下的混淆目录 ibus生成混淆文件名 c&c通信由于恶意挖矿行为不需要对bot进行强控制,大部分的挖矿木马都不具备完备的c&c控制模块,他们通常使用配置文件结合定时任务,实现对bot的配置变更和版本更新。在TOP10中只有ddgs和ibus有完备的c&c控制功能。如ddgs,它的c&c通信使用uMsg序列化,能够实现攻击指令下发、版本配置更新等功能,在今年1月份的更新中甚至开始使用P2P进行c&c控制IP的下发。 ddgs反序列化后的c&c控制指令 ibus的c&c控制模块的恶意代码 ...

October 14, 2019 · 1 min · jiezi

K8s-从懵圈到熟练-–-集群网络详解

导读:阿里云 K8S 集群网络目前有两种方案:一种是 flannel 方案;另外一种是基于 calico 和弹性网卡 eni 的 terway 方案。Terway 和 flannel 类似,不同的地方在于 terway 支持 Pod 弹性网卡,以及 NetworkPolicy 功能。本文中,作者基于当前的 1.12.6 版本,以 flannel 为例,深入分析阿里云 K8S 集群网络的实现方法。鸟瞰总体上来说,阿里云 K8S 集群网络配置完成之后,如下图所示:包括集群 CIDR、VPC 路由表、节点网络、节点的 podCIDR、节点上的虚拟网桥 cni0、连接 Pod 和网桥的 veth 等部分。 类似的图大家可能在很多文章中都看过,但因为其中相关配置过于复杂,比较难理解。这里我们可以看下这些配置背后的逻辑。 基本上我们可以把这些配置分三种情况来理解:集群配置,节点配置以及 Pod 配置。与这三种情况对应的,其实是对集群网络 IP 段的三次划分:首先是集群 CIDR,接着是为每个节点分配 podCIDR(即集群 CIDR 的子网段),最后在 podCIDR 里为每个 Pod 分配自己的 IP。 集群网络搭建初始阶段集群的创建,基于云资源 VPC 和 ECS,在创建完 VPC 和 ECS 之后,我们基本上可以得到如下图的资源配置。我们得到一个 VPC,这个 VPC 的网段是 192.168.0.0/16,我们得到若干 ECS,他们从 VPC 网段里分配到 IP 地址。 ...

October 9, 2019 · 2 min · jiezi

基于Topic消息路由的M2M设备间通信Node-JS-SDK-示例

概述M2M(即Machine-to-Machine)是一种端对端通信技术。本章节以Node JS SDK为例,使用基于Topic消息路由的M2M设备间通信,主要介绍如何基于物联网平台构建一个M2M设备间通信架构。实验步骤第一部分:配置相关 1、产品、设备、Topic的创建参考链接 消息路由建立 本部分目前不支持门户直接配置,需要基于管理API: CreateTopicRouteTable 来建立消息路由关系。测试可以直接使用OpenAPI来快速实现相关功能,本地集成相关功能直接基于SDK即可。 2、JAVA SDK Demo import com.aliyuncs.DefaultAcsClient;import com.aliyuncs.IAcsClient;import com.aliyuncs.exceptions.ClientException;import com.aliyuncs.exceptions.ServerException;import com.aliyuncs.iot.model.v20170420.CreateTopicRouteTableRequest;import com.aliyuncs.iot.model.v20170420.CreateTopicRouteTableResponse;import com.aliyuncs.profile.DefaultProfile;import com.google.gson.Gson;import java.util.*;public class CreateTopicRouteTable { public static void main(String[] args) { DefaultProfile profile = DefaultProfile.getProfile("cn-shanghai", "LTAIOZZg********", "v7CjUJCMk7j9aKduMAQLjy********"); IAcsClient client = new DefaultAcsClient(profile); CreateTopicRouteTableRequest request = new CreateTopicRouteTableRequest(); request.setRegionId("cn-shanghai"); List<String> dstTopicList = new ArrayList<String>(); dstTopicList.add("/a12OcQ4****/device2/user/RouteData"); request.setDstTopics(dstTopicList); request.setSrcTopic("/a12OcQ4****/device1/user/RouteData"); try { CreateTopicRouteTableResponse response = client.getAcsResponse(request); System.out.println(new Gson().toJson(response)); } catch (ServerException e) { e.printStackTrace(); } catch (ClientException e) { System.out.println("ErrCode:" + e.getErrCode()); System.out.println("ErrMsg:" + e.getErrMsg()); System.out.println("RequestId:" + e.getRequestId()); } }}注意:SDK版本差异按照实际版本调整即可。3、查询路由关系 ...

October 8, 2019 · 2 min · jiezi

阿里开源分布式事务组件-seata-配置机制简析

seata 的客户端代码和服务端代码逻辑里,读取配置时统一采用的以下这种 API ConfigurationFactory.getInstance().getString()seata 目前(0.8.0)支持以下几种配置方式 本地文件方式zookeepernacosapolloconsuletcd在分析 seata 的配置解析细节之前,先看看 seata 对于配置解析机制的设计具体来说,seata 的配置项的命名风格都是类似于 computer.apple.macbookpro 这种的文本扁平化风格。它本质上还是一个结构形的配置方式,即每个具体配置项都有父节点,举个例子: computer.apple { macbookpro = 12000 macbookair = 8000}这种配置方式与普通的 xml 配置文件在结构上没有什么大的区别,但与 xml 相比 还是有不少的好处和优点。 简洁明了,xml 本质上还是个标记型语言,引入了许多不必要的复杂标记,间接增加了解析成本解析阶段,xml 定位到某个配置项的逻辑更加繁琐配置更新时如果需要更新文件,xml 的文件的更新动作不够轻量级,如果依赖一些第三方实现,还会造成代码入侵,可扩展性差。相比之下,目前主流的配置中心例如 zookeeper 或者 apollo,这些软件设计之初对数据结构的选型就与 computer.apple.macbookpro 这样的配置形态很相称。例如,zookeeper 的数据结构是类似于文件系统的树状风格,所以 zookeeper 之前是很适合拿来做公共配置中心的,直到后面更优秀的配置中心出现,zookeeper 才慢慢淡出配置界,毕竟 zookeeper 它更擅长做的事情是分布式一致性的协调。正是 seata 采用了这种配置风格,所以 seata 在配置中心的支持这一块,就很方便地与当前主流的配置中心做集成。 试想一下,如果要把一个 xml 配置文件存到 zookeeper 上做全局管理。大概有这么两种方式吧: 把整个 xml 文本存到一个 znode 上把 xml 的结构解析称树状结构,再一个一个对应地到 zookeeper 上创建节点第一种方式,显然很 low 哦。干脆存到数据库算了。第二种方式,会带来额外的解析成本,并且不容易做配置变更这样的逻辑,因为一个配置项的变更,意味着要重新在内存里生成整个 xml 文本,然后再写进本地配置文件里面。 下面说一说 seata 的配置机制,因为 seata 支持上述这么多主流的配置中心,但是实际上使用的时候,必须也只能用一个。因此,一个 seata 相关的进程启动的时候,必然要从本地某个地方直到要用什么配置中心。而实际上,seata 是先读取本地的一个配置文件 registry.conf,再决定要用什么样的配置方式的。这个逻辑在我们上面提到的 API ConfigurationFactory.getInstance() 的源码里可以看到具体的细节。 ...

September 19, 2019 · 3 min · jiezi

Java-12-新特性概述

Java 12 已如期于 3 月 19 日正式发布,此次更新是 Java 11 这一长期支持版本发布之后的一次常规更新,截至目前,Java 半年为发布周期,并且不会跳票承诺的发布模式,已经成功运行一年多了。通过这样的方式,Java 开发团队能够将一些重要特性尽早的合并到 Java Release 版本中,以便快速得到开发者的反馈,避免出现类似 Java 9 发布时的两次延期的情况。 Java 12 早在 2018 年 12 月便进入了 Rampdown Phase One 阶段,这意味着该版本所有新的功能特性被冻结,不会再加入更多的 JEP。该阶段将持续大概一个月,主要修复 P1-P3 级错误。主要时间节点如下: 2018-12-13 Rampdown 第一阶段 ( 从主线分离 )2019-01-17 Rampdown 第二阶段2019-02-07 发布候选阶段2019-03-19 正式发布 本文主要针对 Java 12 中的新特性展开介绍,让您快速了解 Java 12 带来的变化。 Shenandoah:一个低停顿垃圾收集器(实验阶段)Java 12 中引入一个新的垃圾收集器:Shenandoah,它是作为一中低停顿时间的垃圾收集器而引入到 Java 12 中的,其工作原理是通过与 Java 应用程序中的执行线程同时运行,用以执行其垃圾收集、内存回收任务,通过这种运行方式,给虚拟机带来短暂的停顿时间。 Shenandoah 垃圾回收器是 Red Hat 在 2014 年宣布进行的一项垃圾收集器研究项目,旨在针对 JVM 上的内存收回实现低停顿的需求。该设计将与应用程序线程并发,通过交换 CPU 并发周期和空间以改善停顿时间,使得垃圾回收器执行线程能够在 Java 线程运行时进行堆压缩,并且标记和整理能够同时进行,因此避免了在大多数 JVM 垃圾收集器中所遇到的问题。 ...

September 9, 2019 · 4 min · jiezi

Knative-Serving-之路由管理和-Ingress

Knative 默认会为每一个 Service 生成一个域名,并且 Istio Gateway 要根据域名判断当前的请求应该转发给哪个 Knative Service。Knative 默认使用的主域名是 example.com,这个域名是不能作为线上服务的。本文我首先介绍一下如何修改 默认主域名,然后再深入一层介绍如何添加自定义域名以及如何根据 path 关联到不同的 Knative Service。 Knative Serving 的默认域名 example.com首先需要部署一个 Knative Service,可以参考 Knative 初体验:Serving Hello World。如果你已经有了一个 Knative 集群,那么直接把下面的内容保存到 helloworld.yaml 文件中。然后执行一下 kubectl apply -f helloworld.yaml  即可把 hello 服务部署到 helloworld namespace 中。 ---apiVersion: v1kind: Namespacemetadata: name: helloworld---apiVersion: serving.knative.dev/v1alpha1kind: Servicemetadata: name: hello namespace: helloworldspec: template: metadata: labels: app: hello annotations: autoscaling.knative.dev/target: "10" spec: containers: - image: registry.cn-hangzhou.aliyuncs.com/knative-sample/simple-app:132e07c14c49 env: - name: TARGET value: "World!"现在我们来看一下 Knative Service 自动生成的域名配置: ...

August 19, 2019 · 2 min · jiezi

阿里云Link-TEE获得全球首款GlobalPlatform-TEE全配置安全认证

2019年7月12日,阿里云Link TEE正式获得由国际标准组织GlobalPlatform(以下简称GP)颁发的TEE安全评估认证证书,也成为全球首款获得GP TEE全配置(支持TEE Time and Rollback PP-Module和TEE Debug PP-Module)安全认证的产品。 GP是跨行业的国际标准组织,也是全球基于安全芯片的安全基础设施统一的标准的制定者,制定了可信执行环境的标准,是TEE(Trusted Execution Environment)产品全球最权威的专业安全性评估机构。目前,GP TEE安全性认证已是公认的TEE方案在行业纵深方向发展的必由之路。阿里云Link TEE本次斩获的 “全球首款”是国内TEE安全方案在国际上的一个具有里程碑意义的事件。 此次GP TEE安全认证,由国内知名第三方安全实验室DPLS Lab提供专业测评,基于Link TEE v1.1.3软件产品,使用NXP的i.MX6QP SoC作为硬件载体,支持可信应用程序的硬件隔离,提供数据和密钥安全增强保护,可以为客户提供符合GP TEE Protection Profile全配置的商业TEE安全解决方案。 阿里云智能IoT资深安全专家董侃非常看好TEE在物联网领域的应用场景,“阿里云Link TEE是物联网设备的可信计算框架,是专为物联网设计的安全方案,可以提供不同安全等级的可信保护。阿里云Link TEE基于ARM Trustzone或C-SKY的安全扩展技术提供硬件级别的可信根,也提供软件级别的保护方案,具有代码小、运行速度快、安全等级高等优点。该产品在智能汽车、路测单元、智能售卖机、智能音响、摄像头、无人机、智能机器人、区块链可信应用和eSIM领域有广阔的应用场景和市场需求。” “市场一直在等待能增强用户信心的安全标准,”恩智浦高级副总裁Geoff.Lees 说,“阿里云Link TEE提供了这一安全保障,并开启了更快部署安全的工业和物联网解决方案的大门。我们正积极与阿里云合作,提供我们特有的价值与能力,共同制定统一的认证标准,提高安全和隐私的门槛。” DPLS Lab的实验室副主任安焘表示,“作为GlobalPlatform国内首家认可并授权的具备TEE全面评估能力的的安全实验室,有幸能担当此次阿里云Link TEE安全评估实验室,与阿里云物联网安全团队精诚合作对该产品功能符合性与安全抗攻击能力进行全面评估,并经过不懈努力最终达到GP严苛安全标准,完成了此次全球首款全配置TEE安全认证,同时该产品也是GP全球范围内认可的第三款TEE产品。” 本文作者:云媗阅读原文 本文为云栖社区原创内容,未经允许不得转载。

July 16, 2019 · 1 min · jiezi

Aliyun-Serverless-VSCode-Extension-上架并开源

Aliyun Serverless VSCode ExtensionAliyun Serverless VSCode Extension 是阿里云 Serverless 产品 函数计算 Function Compute 的 VSCode 插件,该插件是结合了函数计算 Fun 工具以及函数计算 SDK ,为用户提供 VSCode 图形化开发调试函数计算以及操作函数计算资源的工具。 通过该插件,您可以: 快速地在本地初始化项目、创建函数运行、调试本地函数(调试功能目前支持 nodejs、python、php)拉取云端的服务函数列表,执行云端函数部署服务函数至云端,并更新相关配置前置需求如果您期望使用 Aliyun Serverless VSCode Extension 的所有功能,那么您需要确保系统中有以下组件: VSCode:在 Visual Studio Code 官网 中可以下载安装函数计算 Fun 工具以及 Docker:可以在 aliyun/fun 中根据教程安装配置 Fun 以及 Docker安装插件打开 VSCode 并进入插件市场。在插件市场中搜索 “Aliyun Serverless”,查看详情并安装。重启 VSCode,左侧边栏中会展示已安装的 Aliyun Serverless VSCode Extension 插件。快速入门绑定阿里云账户打开左侧 Aliyun Serverless VSCode Extension,单击绑定阿里云账户的按钮。 依次输入阿里云 Account ID,阿里云 Access Key ID,阿里云 Access Key Secret。 绑定完成后,可以看到所绑定的阿里云账户的云端服务与函数列表。 您可以通过切换区域 Region 来查看不同区域的服务与函数。单击云端资源面板的切换区域按钮或 VSCode 下方的区域信息。 ...

July 12, 2019 · 1 min · jiezi

如何使用confdACM管理Nginx配置

Nginx 作为优秀的开源软件,凭借其高性能高并发等特点,常常作为web和反向代理服务部署在生产环境中。但是当 Nginx 的规模较大时, Nginx 的运维成本也是不断上升。本文介绍如何通过confd+ACM来管理 Nginx 配置,通过集中式的配置管理方式解决 Nginx 的大规模运维问题,运维和开发人员不用登陆到 Nginx 机器上,只需要配置好confd,然后在ACM上操作就可以动态修改 Nginx 的配置参数。 准备工作在操作本文的示例之前需要配置好开通ACM和对confd的使用有基本概念,ACM的开通及其基本使用可以参考:这里confd的基本使用可以参考:这里 Nginx 在日常开发中使用得比较多的功能是负载均衡、限流、缓存等, Nginx 的使用和安装可以在网上查阅相关资料。本文结合负载均衡和限流功能讲解如何使用confd+ACM实现 Nginx 的大规模运维操作。 创建confd配置文件创建confd所需的toml格式配置文件 vim /etc/confd/conf.d/myapp.tomlcheck_cmd用于检验 Nginx 配置的正确性,当src配置错误则不会覆盖 Nginx 配置reload_cmd用于reload Nginx 配置 [template]src = " Nginx .conf.tmpl"dest = "/usr/local/ Nginx /conf/ Nginx .conf"keys = ["/myapp/ Nginx /conf",]check_cmd = "/usr/local/ Nginx /sbin/ Nginx -t -c {{.src}}"reload_cmd = "/usr/local/ Nginx /sbin/ Nginx -s reload"创建模版文件vim /etc/confd/templates/ Nginx .conf.tmplgetv从ACM中获取对应dataId的配置,/myapp/ Nginx /conf对应的dataId为myapp. Nginx .conf,配置格式为json格式,模版文件包含了 Nginx 的upstream、限流、黑白名单配置内容,通过json指令解析配置文件。upstream后端ip通过从ACM的配置的backends数组中获取,同样地,白名单和黑名单ip分别存储在whiteList和blackList的数组中,限流的速率和并发数通过rateLimit和connectionLimit设置 ...

July 12, 2019 · 2 min · jiezi

如何在云上使用confdACM管理敏感数据

在前面的一些文章中,我们介绍了如何在云上安全的存放配置数据,但是上面的方法都是有代码侵入性的,也就是说需要修改应用程序,本文会讲解如何使用 confd+ACM 在不修改代码的情况下动态修改应用所需的配置,并且可以重新启动应用加载最新的配置。这样做可以在无代码侵入的情况下加强应用程序的安全性和运维效率: 安全性:应用程序的数据可能是敏感数据,ACM 具有健壮的访问控制机制,可以对敏感数据进行加密来安全的保存密码、API密钥、证书等敏感信息;运维效率:当需要修改应用的某些配置内容时,如果只有一两台机器可以手工操作,但是当涉及几十上百台数量的时候,confd+ACM可以通过配置的发布批量进行配置修改和重启操作;无代码侵入:通过confd+ACM的组合可以做到无需修改应用代码即可达到让应用配置动态生效的效果 下面以应用的数据库配置为例讲解如何使用confd+ACM安全管理应用配置 准备工作在操作本文的示例之前需要配置好开通ACM和对confd的使用有基本概念,ACM的开通及其基本使用可以参考:这里confd的基本使用可以参考:这里 创建confd配置文件创建confd所需的toml格式配置文件 vim /etc/confd/conf.d/myapp.toml指定模版文件,ACM中的加密配置需要以/cipher-开头check_cmd用于检验配置的正确性,防止错误配置导致应用加载失败reload_cmd用于重启应用或者让应用动态加载配置 [template]src = "jdbc.properties.tmpl"dest = "/tmp/jdbc.properties"keys = ["/cipher-myapp/database/jdbc",]#check_cmd = "check config is correct"reload_cmd = "restart app"创建模版文件vim /etc/confd/templates/jdbc.properties.tmplgetv从ACM中获取对应dataId的配置:/cipher-myapp/database/jdbc对应的dataId为cipher-myapp.database.jdbcconfd基于kms会自动对/cipher-开头的配置进行解密 {{$data := json (getv "/cipher-myapp/database/jdbc")}}jdbc.url={{$data.url}}jdbc.username={{$data.username}}jdbc.password={{$data.password}}在ACM上创建所需的配置文件创建dataId为cipher-myapp.database.jdbc的配置文件,group使用默认的DEFAULT_GROUP即可,配置内容为 {"url":"jdbc:mysql://localhost:3306/dbName","username":"testuser","password":"testpassword"} 启动confd和官网文档不同的是,要支持解密功能,需要设置confd的-openKMS开关,并且设置kms服务的regionId,这个信息可以从示例代码中获得 confd -backend nacos -endpoint {endpoint}:8080 -namespace {namespace} -accessKey {accessKey} -secretKey {secretKey} -openKMS true -regionId {regionId} -watch 生成配置文件查看生成的/tmp/jdbc.properties配置文件,如果生成了该文件,并且文件内容如下则说明整个流程运行正常 jdbc.url=jdbc:mysql://localhost:3306/dbNamejdbc.username=testuserjdbc.password=testpassword变更ACM配置内容当需要修改数据库的连接串的时候,直接在ACM上修改cipher-myapp.database.jdbc配置,confd会重新生成数据库配置文件,并让应用加载最新配置。当然在实际生产环境中,可以使用ACM的Beta功能对几台机器先进行灰度发布,检验没问题再继续全量发布 本文演示了如何使用confd+ACM安全管理敏感数据,ACM安全配置更多信息还可以参考:这里 本文作者:风卿,Nacos 社区 Committer 本文作者:中间件小哥阅读原文 本文为云栖社区原创内容,未经允许不得转载。

July 11, 2019 · 1 min · jiezi

公众号开发微信公众平台配置

很久就想写一下开发微信公众号的所有配置,以及需要注意的坑点。最近项目提测,配合测试调试的空暇做个记录 微信端配置1、公众平台配置1、长期运营者和开发者配置绑定长期运营者:用于配置ip白名单和公众号后台登录Note:进入人员设置 Note:绑定运营者根据提示还需要关注名为公众号助手的公众号,不截图了 绑定开发者:用于开发者工具测试网站接口 2、基本配置Note:基本配置中,如图上半部分直接设置并保存在代码的配置文件中,下半部分需要在服务器配置好/wechat接口,并正确响应微信请求,注意此接口需要使用any方式 3、网页授权、JS安全域名、业务域名配置Note:两个入口可以进入配置 Note:这里的这个文件,前后端域名都需要可以直接访问,同一公众号三个需要配置的地方文件一模一样,不同公众号文件名和内容均不一样(vue不能放在/static下面) 2、商户号配置1、要开通微信支付功能,必须要自己申请商户号并与公众号进行绑定,这个流程根据微信提示操作即可(开通商户号,提交资料之后需要等待微信审核) Note:商户号登录建议用qq浏览器,总之用qq浏览器就对了,如图在画框的地方进行安装 2、安装完证书,就到api安全处获取后端调用退款的证书文件,包括设置密钥(这个页面因为要校验证书等,所以会巨卡,得有耐心) 3、设置支付授权目录,注意:授权目录我配置了前端的url,后端处理支付的接口必须要配置到最后一个"/",(比如,后端接口www.XX.com/wechat/pay/notice处理支付通知,那这个地方就要填写:www.XX.com/wechat/pay/,而且在后端处理退款回调通知的url最好也在/pay/下面,比如www.XX.com/wechat/pay/refund,这样在商户号这里就只需要配置一条就好了) 后端配置公众号1、公众号基础配置note:结合公众号后台的参数,写入配置 Note:/wechat接口逻辑处理 2、欢迎语,配置了根据公众号后台输入内容自动回复的内容 3、微信页授权回调 4、模拟用户,为了能在本地开发,于是配置了模拟用户,只需要在中间件中写入session就行了 5、再贴一个我单独配置的菜单吧,我把它写在配置文件里头,有artisan命令直接调用菜单接口就可以实现自定义菜单Note:还有一个更方便的设置菜单的方法,就是用微信的在线测试接口,聪明如我~_~ 支付1、支付配置 Note:支付相关的具体实现就不贴出来了,网上一大堆模板~

July 4, 2019 · 1 min · jiezi

就是要你懂负载均衡lvs和转发模式

本文希望阐述清楚LVS的各种转发模式,以及他们的工作流程和优缺点,同时从网络包的流转原理上解释清楚优缺点的来由,并结合阿里云的slb来说明优缺点。如果对网络包是怎么流转的不太清楚,推荐先看这篇基础:程序员的网络知识 -- 一个网络包的旅程,对后面理解LVS的各个转发模式非常有帮助。 几个术语和缩写cip:Client IP,客户端地址vip:Virtual IP,LVS实例IPrip:Real IP,后端RS地址RS: Real Server 后端真正提供服务的机器LB: Load Balance 负载均衡器LVS: Linux Virtual Serversip: source ipdip: destinationLVS的几种转发模式DR模型 -- (Director Routing-直接路由)NAT模型 -- (NetWork Address Translation-网络地址转换)fullNAT -- (full NAT)ENAT --(enhence NAT 或者叫三角模式/DNAT,阿里云提供)IP TUN模型 -- (IP Tunneling - IP隧道)DR模型(Director Routing--直接路由) 如上图所示基本流程(假设 cip 是200.200.200.2, vip是200.200.200.1): 请求流量(sip 200.200.200.2, dip 200.200.200.1) 先到达 LVS然后LVS,根据负载策略挑选众多 RS中的一个,然后将这个网络包的MAC地址修改成这个选中的RS的MAC然后丢给交换机,交换机将这个包丢给选中的RS选中的RS看到MAC地址是自己的、dip也是自己的,愉快地手下并处理、回复回复包(sip 200.200.200.1, dip 200.200.200.2)经过交换机直接回复给client了(不再走LVS)我们看到上面流程,请求包到达LVS后,LVS只对包的目的MAC地址作了修改,回复包直接回给了client。 同时还能看到多个RS和LVS都共用了同一个IP但是用的不同的MAC,在二层路由不需要IP,他们又在同一个vlan,所以这里没问题。 RS上会将vip配置在lo回环网卡上,同时route中添加相应的规则,这样在第四步收到的包能被os正常处理。 优点: DR模式是性能最好的一种模式,入站请求走LVS,回复报文绕过LVS直接发给Client缺点: 要求LVS和rs在同一个vlan;RS需要配置vip同时特殊处理arp;不支持端口映射。为什么要求LVS和RS在同一个vlan(或者说同一个二层网络里)因为DR模式依赖多个RS和LVS共用同一个VIP,然后依据MAC地址来在LVS和多个RS之间路由,所以LVS和RS必须在一个vlan或者说同一个二层网络里 DR 模式为什么性能最好因为回复包不走LVS了,大部分情况下都是请求包小,回复包大,LVS很容易成为流量瓶颈,同时LVS只需要修改进来的包的MAC地址。 DR 模式为什么回包不需要走LVS了因为RS和LVS共享同一个vip,回复的时候RS能正确地填好sip为vip,不再需要LVS来多修改一次(后面讲的NAT、Full NAT都需要) 总结下 DR的结构 ...

July 4, 2019 · 1 min · jiezi

K8S环境中NAS卷添加noresvport方法

通过K8S使用NAS卷,请区分以下场景: 静态存储卷: 使用阿里云ACK,PV、PVC方式,nfs驱动;使用阿里云ACK,PV、PVC方式,Flexvolume驱动;使用阿里云ACK,Volume方式,nfs驱动;使用阿里云ACK,Volume方式,Flexvolume驱动;自建K8S,PV、PVC方式,nfs驱动;自建K8S,Volume方式,nfs驱动;动态存储卷: 使用阿里云ACK使用自建K8S静态卷-使用阿里云Kubernetes(ACK)时1. 使用PV、PVC方式(nfs驱动)首先确认当前的挂载是否配置了noresvport参数,参考NAS团队提供的方式; 例如当前的pv如下面yaml: apiVersion: v1kind: PersistentVolumemetadata: name: pv-nasspec: accessModes: - ReadWriteOnce capacity: storage: 2Gi mountOptions: - vers=3 nfs: path: /default server: 2564f49129-ggu23.cn-shenzhen.nas.aliyuncs.com persistentVolumeReclaimPolicy: Retain编辑PV: kubectl edit pv pv-nas更新mountOptions:mountOptions: - vers=4.0 - noresvport或者: mountOptions: - vers=3 - nolock,tcp,noresvport重启使用这个pv的pod; 需要注意: 由于一个节点上,如果已经有某个挂载点挂载在一个目录下了,其他的挂载(相同挂载点)即使配置了noresvport参数,还是会follow以前的挂载参数。即noresvport不生效;解决方法:方法1:修改pv参数后,把所有使用这个挂载点的pod掉离这个节点,然后再调回来。方法2:使用新的挂载点创建新的pv使用(一个nas文件系统可以有2个挂载点); 示例方法1: 集群中有2个worker节点,部署一个deploy包含3个Pod;# kubectl get node | grep -v masterNAME STATUS ROLES AGE VERSIONcn-shenzhen.i-wz9c9m0m4oldr6mt89rd Ready <none> 55d v1.12.6-aliyun.1cn-shenzhen.i-wz9gvy73m4qyk03xzg1y Ready <none> 60d v1.12.6-aliyun.1# kubectl get podNAME READY STATUS RESTARTS AGEnas-static-784496fbb9-cqr97 1/1 Running 0 63mnas-static-784496fbb9-gljbq 1/1 Running 0 63mnas-static-784496fbb9-ngzkq 1/1 Running 0 63m编辑pv,添加- nolock,tcp,noresvport Options;编辑deploy,把这个deploy的pod都调度到节点:cn-shenzhen.i-wz9c9m0m4oldr6mt89rd上;> 在deploy中添加 nodeName: cn-shenzhen.i-wz9c9m0m4oldr6mt89rd> 如果您的集群节点较多,可以给一批节点添加label,然后通过nodeSelector把pod调度到这写节点;> 参考:https://kubernetes.io/zh/docs/tasks/configure-pod-container/assign-pods-nodes/注意:如果您用的时候statefulset的应用,需要把updateStrategy.type配置为RollingUpdate;然后再把pod调度到其他节点:cn-shenzhen.i-wz9gvy73m4qyk03xzg1y到节点cn-shenzhen.i-wz9gvy73m4qyk03xzg1y 上验证noresport,已经生效。2564f49129-ggu23.cn-shenzhen.nas.aliyuncs.com:/default on /var/lib/kubelet/pods/aa79e380-9bdb-11e9-a545-00163e0eff42/volumes/kubernetes.io~nfs/pv-nas type nfs (rw,relatime,vers=3,rsize=1048576,wsize=1048576,namlen=255,hard,nolock,noresvport,proto=tcp,timeo=600,retrans=2,sec=sys,mountaddr=192.168.0.11,mountvers=3,mountport=4002,mountproto=tcp,local_lock=all,addr=192.168.0.11)最后,由于当前使用nas的pod是有nodeName标签的,可以编辑deploy,把nodeName(nodeSelector)去掉。2. 使用PV、PVC方式(Flexvolume驱动)首先确认当前的挂载是否配置了noresvport参数,参考NAS团队提供的方式; ...

July 3, 2019 · 2 min · jiezi

基于Tablestore-Tunnel的数据复制实战

前言数据复制主要指通过互联的网络在多台机器上保存相同数据的副本,通过数据复制方案,人们通常希望达到以下目的:1)使数据在地理位置上更接近用户,进而降低访问延迟;2)当部分组件出现故障时,系统依旧可以继续工作,提高可用性;3)扩展至多台机器以同时提供数据访问服务,从而提升读吞吐量。如果复制的数据一成不变,那么数据复制就非常容易,只需要将数据复制到每个节点,一次性即可搞定,面对持续更改的数据如何正确而有效的完成数据复制是一个不小的挑战。 使用DataX进行Tablestore数据复制表格存储(Tablestore)是阿里云自研的NoSQL多模型数据库,提供海量结构化数据存储以及快速的查询和分析服务,表格存储的分布式存储和强大的索引引擎能够提供PB级存储、千万TPS以及毫秒级延迟的服务能力。DataX是阿里巴巴集团内被广泛使用的离线数据同步工具,DataX本身作为数据同步框架,将不同数据源的同步抽象为从源头数据源读取数据的Reader插件,以及向目标端写入数据的Writer插件。通过使用DataX可以完成Tablestore表的数据复制,如下图所示,otsreader插件实现了从Tablestore读取数据,并可以通过用户指定抽取数据范围可方便的实现数据增量抽取的需求,otsstreamreader插件实现了Tablestore的增量数据导出,而otswriter插件则实现了向Tablestore中写入数据。通过在DataX中配置Tablestore相关的Reader和Writer插件,即可以完成Tablestore的表数据复制。 使用通道服务进行Tablestore数据复制通道服务(Tunnel Service)是基于表格存储数据接口之上的全增量一体化服务。通道服务为您提供了增量、全量、增量加全量三种类型的分布式数据实时消费通道。通过为数据表建立数据通道,可以简单地实现对表中历史存量和新增数据的消费处理。 借助于全增量一体的通道服务,我们可以轻松构建高效、弹性的数据复制解决方案。本文将逐步介绍如何结合通道服务进行Tablestore的数据复制,完整代码开源在github上的 tablestore-examples中。本次的实战将基于通道服务的Java SDK来完成,推荐先阅读下通道服务的相关文档,包括快速开始等。 1. 配置抽取配置抽取其实对应的是数据同步所具备的功能,在本次实战中,我们将完成指定时间点之前的表数据同步,指定的时间点可以是现在或者未来的某个时刻。具体的配置如下所示,ots-reader中记录的是源表的相关配置,ots-writer中记录的是目的表的相关配置。 { "ots-reader": { "endpoint": "https://zhuoran-high.cn-hangzhou.ots.aliyuncs.com", "instanceName": "zhuoran-high", "tableName": "testSrcTable", "accessId": "", "accessKey": "", "tunnelName": "testTunnel", "endTime": "2019-06-19 17:00:00" }, "ots-writer": { "endpoint": "https://zhuoran-search.cn-hangzhou.ots.aliyuncs.com", "instanceName": "zhuoran-search", "tableName": "testDstTable", "accessId": "", "accessKey": "", "batchWriteCount": 100 }}ots-reader中各参数的说明如下: endpoint: Tablestore服务的Endpoint地址,例如https://zhuoran-high.cn-hangzhou.ots.aliyuncs.com。在进行数据复制前,请检查下连通性(可以使用curl命令)。instanceName: Tablestore的实例名。tableName: Tablestore的表名。accessId: 访问Tablestore的云账号accessId。accessKey: 访问Tablestore的云账号accessKey。tunnelName: Tablestore的通道名,配置endTime: 数据同步的截止时间点,对应到Java里SimpleFormat的格式为:yyyy-MM-dd HH:mm:ss 。ots-writer中各参数的说明如下(略去相同的参数): batchWriteCount: Tablestore单次批量写入的条数,最大值为200。注:未来会开放更多的功能配置,比如指定时间范围的数据复制等。2. 编写主逻辑数据复制的主逻辑主要分为以下4步,在第一次运行时,会完整的进行所有步骤,而在程序重启或者断点续传场景时,只需要进行第3步和第4步。 1.创建复制目的表 通过使用DesribeTable接口,我们可以获取到源表的Schema,借此可以创建出目的表,值得注意的是需要把目的表的有效版本偏差设成一个足够大的值(默认为86400秒),因为服务端在处理写请求时会对属性列的版本号进行检查,写入的版本号需要在一个范围内才能写入成功,对于源表中的历史存量数据而言,时间戳往往是比较小的,会被服务端过滤掉,最终导致同步数据的丢失。sourceClient = new SyncClient(config.getReadConf().getEndpoint(), config.getReadConf().getAccessId(), config.getReadConf().getAccessKey(), config.getReadConf().getInstanceName());destClient = new SyncClient(config.getWriteConf().getEndpoint(), config.getWriteConf().getAccessId(), config.getWriteConf().getAccessKey(), config.getWriteConf().getInstanceName());if (destClient.listTable().getTableNames().contains(config.getWriteConf().getTableName())) { System.out.println("Table is already exist: " + config.getWriteConf().getTableName());} else { DescribeTableResponse describeTableResponse = sourceClient.describeTable( new DescribeTableRequest(config.getReadConf().getTableName())); describeTableResponse.getTableMeta().setTableName(config.getWriteConf().getTableName()); describeTableResponse.getTableOptions().setMaxTimeDeviation(Long.MAX_VALUE / 1000000); CreateTableRequest createTableRequest = new CreateTableRequest(describeTableResponse.getTableMeta(), describeTableResponse.getTableOptions(), new ReservedThroughput(describeTableResponse.getReservedThroughputDetails().getCapacityUnit())); destClient.createTable(createTableRequest); System.out.println("Create table success: " + config.getWriteConf().getTableName());}2.在源表上创建通道 ...

July 1, 2019 · 2 min · jiezi

让开发部署提速-8-倍我参与贡献这款-IDE-插件的全过程

如何像参与开源那样,去参与一款 IDE 插件的设计? 作为一款 IDE 插件的使用者,我是否能决定下一个版本的功能? 自从产品经理银时小伙和他的开发小哥们在去年12月发布 Cloud Toolkit(一款 IDE 插件)以来,已帮助数以万计的开发者们提高了业务的部署效率。期间,开发者们不仅是 Cloud Toolkit 的使用者,同时也作为设计者参与了插件的更新迭代。 本文来自开发者徐靖峰,分享了他和 Cloud Toolkit 的故事 遇见 Cloud Toolkit在与中间件小姐姐的一次聊天中,偶然间了解到这款插件,小姐姐跟我提到自己正在运营一款 IDE 开发者工具,能够使开发部署效率提高 8 倍,出于好奇心,我就上手体验了一下,看看究竟是一个什么样的产品。使用了一段时间之后,便迫不及待地向小姐姐分享了我作为开发者对插件的一些看法。 我对这款产品最直观的感受:这是一款发布工具,帮助用户在 IDE 中直接打包应用并部署到各种终端。一开始看到这款产品位于阿里云的页面中,原本以为是一款和阿里云服务强绑定的产品,但试用过后才发现,即使对于普通的云主机,也非常适用,还可以解决很多开发运维的痛点,非阿里云用户可以放心使用。 在 Cloud Toolkit 出现之前作为一个 Java 程序员,我们大多数会在 Intellij IDEA 中基于 SpringBoot 来开发 WEB 应用,所以本文中的测评将会基于以下几个架构来构建: 开发环境:IDEA项目组织方式:Maven开发框架:SpringBoot在接触 Cloud Toolkit 之前,用什么方法来部署一个 SpringBoot 应用呢?作为一个偏正经的测评人员,我不会为了凸显出 Cloud Toolkit 的强大而去翻出一些上古的部署工具来做对比,而是直接使用 Intellij IDEA 的内置功能与之对比。 第一步:配置服务器信息 在 Tools -> Deployment 中找到 IDEA 对项目部署支持的内置插件,我们可以在其中进行服务器信息的配置,包括服务器地址和权限认证,并且在 Mapping 选项卡中完成本地工程与服务器路径的映射。 第二步:配置 Maven 打包插件<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins></build>由于是 SpringBoot 应用,配置专用的打包插件后,可以将整个工程打成一个 fatjar,示例工程非常简单: ...

June 28, 2019 · 3 min · jiezi

Express-的使用

以下内容,基于 Express 4.x 版本 Node.js 的 ExpressExpress 估计是那种你第一次接触,就会喜欢上用它的框架。因为它真的非常简单,直接。 在当前版本上,一共才这么几个文件: lib/├── application.js├── express.js├── middleware│   ├── init.js│   └── query.js├── request.js├── response.js├── router│   ├── index.js│   ├── layer.js│   └── route.js├── utils.js└── view.js这种程度,说它是一个“框架”可能都有些过了,几乎都是工具性质的实现,只限于 Web 层。 当然,直接了当地实现了 Web 层的基本功能,是得益于 Node.js 本身的 API 中,就提供了 net 和 http 这两层, Express 对 http 的方法包装一下即可。 不过,本身功能简单的东西,在 package.json 中却有好长一串 dependencies 列表。 Hello World在跑 Express 前,你可能需要初始化一个 npm 项目,然后再使用 npm 安装 Express: mkdir pcd pnpm initnpm install express --save新建一个 app.js : const express = require('express');const app = express();app.all('/', (req, res) => res.send('hello') );app.listen(8888);调试信息是通过环境变量 DEBUG 控制的: const process = require('process');process.env['DEBUG'] = 'express:*';这样就可以在终端看到带颜色的输出了,嗯,是的,带颜色控制字符,vim 中直接跑就 SB 了。 应用 ApplicationApplication 是一个上层统筹的概念,整合“请求-响应”流程。 express() 的调用会返回一个 application ,一个项目中,有多个 app 是没问题的: ...

June 26, 2019 · 7 min · jiezi

AutoScaling-成本优化模式升级混合实例策略

伸缩组成本优化模式以成本为目标,始终创建最低价的实例,同时,通过多可用区,多实例规格分布,以此来提高服务稳定性。但是,对于成本优势最大化的竞价实例,伸缩组难以防范竞价实例大范围回收可能导致的服务雪崩,本次升级允许用户制定更详细的成本控制策略,在成本和稳定性之间进行调整和权衡。 成本优化模式简介当您的伸缩配置选择了多实例规格,并想以最低的价格来使用同等规模的 ECS 实例配置时,您可以选择使用 成本优化策略 的伸缩组,来降低您的 ECS 实例使用成本;当您的伸缩配置选择的实例为抢占式实例时,您可能会遇到由于价格、库存等原因导致抢占式实例创建失败场景,从而导致扩容不及时,影响到业务,您也可以选择使用 成本优化策略 的伸缩组,在抢占式实例创建失败的时候自动为您尝试创建同规格的按量实例,来保证业务的稳定性。 从上述的描述,我们可以清晰的看到,成本优化模式的核心策略: 创建实例时,以单核cpu价格价格最低来选择创建实例的 InstanceType(实例规格),ZoneId(可用区)等配置信息。竞价实例创建失败时,调整为创建按量实例,以保证业务连续性。我们将上述的策略称为最低价策略(LowestPrice)。 关于成本优化模式更详细的信息,请查看 AutoScaling 推出成本优化模式。 成本优化模式升级成本优化模式的升级策略主要针对竞价实例回收机制可能带来的业务雪崩情况。主要集中在以下两点: 混合实例配比。允许用户为成本优化伸缩组制定按量实例与竞价实例的混合策略。竞价实例主动替换。在竞价实例释放前创建新实例,主动替换掉当前的竞价实例。在下面的文章中,我们将原成本优化伸缩组称为普通成本优化伸缩组,将指定实例混合策略的成本优化伸缩组称为成本优化混合实例伸缩组。 参数详解OnDemandBaseCapacity伸缩组所需要的按量实例的最小个数,当伸缩组中按量实例个数小于该值时,将优先创建按量实例。 OnDemandPercentageAboveBaseCapacity满足 OnDemandBaseCapacity 条件后,创建实例中按量实例所占的比例。 SpotInstancePoolsSpotInstancePools 指定了最低价的多个实例规格,当创建竞价实例时,将在 SpotInstancePools 中进行均衡分布。 SpotInstanceRemedy是否开启竞价实例的补偿机制。开启后在竞价实例被回收前5分钟左右,将主动替换掉当前竞价实例。 兼容性介绍成本优化混合实例伸缩组与普通成本优化伸缩组在接口和功能方面是完全兼容的。当您不指定混合实例策略的相关参数时,您将创建出普通成本优化伸缩组。同时,对于成本优化混合实例伸缩组,通过合理的制定混合实例策略,能够具有与普通成本优化伸缩组完全相同的行为。下面举例说明: 假设普通成本优化伸缩组创建的全为按量实例。此时,你创建的成本优化混合实例伸缩组只需要指定OnDemandBaseCapacity=0, OnDemandPercentageAboveBaseCapacity=100,spotInstancePools=1,那么将拥有完全相同的行为。 假设普通成本优化伸缩组优先创建竞价实例。此时,你创建的成本优化混合实例伸缩组只需要指定OnDemandBaseCapacity=0, OnDemandPercentageAboveBaseCapacity=0,spotInstancePools=1,那么将拥有完全相同的行为。 扩缩容策略成本优化混合实例伸缩组拥有一套相对独立的扩缩容策略,您在大多数情况下不需要关注实例的选择过程,如果您需要对伸缩组行为具有更详细的了解,本节中对扩缩容过程进行了详细的描述。 扩容策略当指定了伸缩组的实例混合策略之后,伸缩组并非仅对新创建出来的实例按照混合比例进行创建,而是保证伸缩组整体的实例配比趋近目标配比。 按量实例扩容策略按量实例部分,采用了 LowestPrice 的创建方式,多实例规格与多可用区按照优先级方式依此进行选择,该部分与普通成本优化伸缩组保持一致。 竞价实例扩容策略竞价实例部分,采用了 LowestPrice 的创建方式,当配置多实例规格时,将根据 SpotInstancePools 配置,在最低价的多个实例规格之间平均分配,针对每一种实例规格,当无法成功创建时,按照价格顺序依次选取下一规格继续进行创建,当竞价实例全部不可创建,将退回到创建对应的按量实例。多可用区则按照优先级的方式依次进行选择。 下面,我们通过示例来描述成本优化混合实例伸缩组的扩容行为: 假设伸缩组组内按量实例个数为3,竞价实例为1个ecs.n1.tiny规格实例,OnDemandBaseCapacity = 5,OnDemandPercentageAboveBaseCapacity = 40,SpotInstancePools = 2,伸缩组实例规格配置为:ecs.n1.tiny, ecs.n1.small,ecs.n1.medium(价格依此上升)。 扩容数量按量实例分配情况竞价实例分配情况031(tiny)141(tiny)251(tiny)361(tiny)471(tiny)571(tiny)1(small)672(tiny)1(small)782(tiny)1(small)882(tiny)2(small)缩容策略成本优化混合实例伸缩组的释放策略不遵循伸缩组上指定的释放策略,为了保持实例伸缩组内实例的混合配比,将采用以下描述的实例释放策略。首先,将根据伸缩组实例混合策略,确定将要释放的按量实例与竞价实例的个数,我们将在保证足够数量的实例被释放的前提下,按照伸缩组整体趋近期望配比的方式确定释放按量实例和竞价实例的个数。当按量实例个数不足时,将释放更多的竞价实例;当竞价实例个数不足时,将改为释放按量实例。 按量实例缩容策略释放按量实例时,将按照以下条件选择可释放的实例: 优先释放价格高的实例;价格相同时,按照伸缩组指定的释放策略选取合适数量的实例进行释放。竞价实例缩容策略释放竞价实例时,将按照以下条件选择可释放的实例: 将首先释放不属于spotInstancePools中规格类型的实例,这部分实例的释放策略与上述按量实例的缩容策略相同;如果还需要释放规格类型属于spotInstancePools的实例,将进一步选择释放所需要的实例,选择方式如下: 选择释放的实例将使得剩余实例的实例规格在spotInstancePools中趋于均衡分布;相同规格的多个实例可供选择时,将按照伸缩组指定的释放策略选择释放的实例。下面,同样我们通过简单的示例来描述成本优化混合实例伸缩组在缩容时的实例选择过程: 假设伸缩组组内按量实例个数为8,竞价实例为2个ecs.n1.tiny规格实例,2个ecs.n1.small规格实例,OnDemandBaseCapacity = 5,OnDemandPercentageAboveBaseCapacity = 40,SpotInstancePools = 2,伸缩组实例规格配置为:ecs.n1.tiny, ecs.n1.small,ecs.n1.medium(价格依此上升)。 缩容数量按量实例分配情况竞价实例分配情况082(tiny)2(small)182(tiny)1(small)272(tiny)1(small)371(tiny)1(small)471(tiny)561(tiny)660750840竞价实例补偿竞价实例在系统回收之前五分钟左右将会发送系统回收消息,当您开启竞价实例主动替换功能之后,在系统发送竞价实例的回收消息之后,弹性伸缩将会为该竞价实例创建补偿任务,并在稍后通过创建新的竞价实例来替换即将释放的实例。我们将这一主动替换即将被回收的竞价实例的行为称为竞价实例补偿。 竞价实例补偿是保障业务连续性的辅助保障机制,该补偿机制具有以下特点,你需要对这些特点有充分的认识,以便您配置合理的成本优化伸缩组。 竞价实例补偿的时间窗口。在收到竞价实例系统回收消息后,将为对应的实例生成补偿任务,大约五分钟时间后实例将被回收,当实例被回收后,伸缩组内的对应实例将被健康检查机制清除伸缩组(大约6分钟)。竞价实例补偿任务的有效期为:补偿任务生成到健康检查将实例移除伸缩组之间。一旦错过补偿时间窗口,对应的补偿任务将会失效和清理,意味着对应实例错过补偿期。有限的补偿能力。一次竞价实例的补偿过程分为新实例启动和旧实例释放两个过程,补偿任务执行过程中,伸缩组将处于锁定状态。由于暂时伸缩组不支持并行伸缩活动处理,因此,在有限的补偿时间窗口内,能够进行的补偿任务次数和实例数是有限的。由于竞价实例回收通常是呈现批次状,因此,为了最大程度利用有限的补偿能力,我们将对补偿任务进行一定程度的聚合之后,按批次进行下发,最大程度的补偿更多的实例。最佳实践这里我们主要展示如何使用java SDK创建伸缩规则,并采用maven进行依赖管理。创建目标追踪伸缩规则,需要使用aliyun-java-sdk-ess 2.3.1及以上版本。 程序所需的maven依赖如下: <dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-core</artifactId> <version>3.0.8</version> </dependency> <dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-ess</artifactId> <version>2.3.1</version> </dependency>创建混合实例的成本优化伸缩组: ...

June 25, 2019 · 1 min · jiezi

项目配置文件选择

.env这种文件是在 laravel 框架里看到的,他是使用 phpdotenv composer 包来进行读取和使用的。 使用方法加载 composer 包 composer require vlucas/phpdotenv项目使用 <?phprequire __DIR__ . '/vendor/autoload.php';$dotenv = Dotenv\Dotenv::create(__DIR__);$dotenv->load();$projectName = getenv('project_name');$projectName = $_ENV['project_name'];$projectName = $_SERVER['project_name'];深入请查看项目的 README.md 文件。 yamlYAML 试图用一种比 XML 更敏捷的方式,来完成XML所完成的任务。 安装配置yaml 文件的使用是需要 php 加载 yaml 扩展的。yaml 扩展又依赖于 libyaml,下面以 centos7 系统为例安装: # http://pecl.php.net/package/yaml 取最新扩展包地址wget http://pecl.php.net/get/yaml-2.0.4.tgz# 解压tar -zxvf yaml-2.0.4.tgz# 进行扩展文件夹cd yaml-2.0.4phpizevim install_sh> ./configure --with-php-config=/usr/local/php/bin/php-config# 编译安装sh install_shmake -j 2make install# php 加载扩展vim /usr/local/php/etc/php.ini> extension=yaml.so项目使用<?php$config = yaml_parse_file(__DIR__ . '/config.yaml');print_r($config['project_name']);深入了解 学习经过疑问为什么不直接使用 php 文件当配置,而用其他类型文件来配置好像没有什么特殊的作用,不管是 .env 还是 yaml 文件都需要配置,反而直接 php 文件当配置会方便易懂一些(.env/yaml 都有自己的语法,不懂的人需要学习)。 ...

June 24, 2019 · 1 min · jiezi

IoT-SaaS加速器助力阿尔茨海默病人护理

场景介绍阿尔茨海默病,是导致中老年人认知功能障碍的最常见疾病之一,是发生在老年期及老年前期的一种原发性退行性脑病。据估计,全世界痴呆症患者数量为4700万,到2030年将达到7500万人。痴呆症患者数量到2050年预计将是现在的近三倍。疾病的高昂费用给卫生系统应对未来预计不断增加的病例构成挑战。据估计,目前每年的支出为8180亿美元,而支出的增长速度预计会比疾病流行率上升还要快。照料痴呆症患者给照护者带来巨大压力,包括身体上、情感上和经济上的压力。(by世界卫生组织) 用技术解决阿尔茨海默病护理的问题,让老人和其护理者有更好的生活质量,是我们可以解决的方法。基于物联网技术,已经有一些设备实现了阿尔茨海默病老人走失定位。但是我们要做更高一层,除了单独分发的硬件之外,我们要使用开发工具IoT Studio帮助医疗机构做一个硬件SaaS管理系统,让他们可以随时监控旗下所有阿尔兹海默护理设备的数据以及定位,对老人的情况实现实时监控。同时也有能力对掌控的设备进行增删改查,方便他们自己管理设备。通过IoT Studio赋能开发者,让他们帮助包括医疗在内的各个行业用上物联网技术,惠及百姓。 我们首先构建一个可以拍照,检测心跳的手环设备,然后基于这个设备帮助护理机构开发一个集合管理监控告警的SaaS系统。设备由一个可以检测心跳的光学模块,一个可以检测老人所在地场景的摄像头,一个GPS定位模块,一个物联网通讯模块(一般为GPRS),MCU和电源组成。云端由物联网平台为基础建立设备与云端通讯,配合RDS存储心跳&GPS数据,OSS存储图片数据,最后用IoT Studio的服务开发与Web可视化开发功能完成功能页面搭建。整个云端开发过程只需要2小时以内即可。 最终效果如图。 硬件部分在demo阶段,我们采用树莓派3B+摄像头+心跳模块+GPS+电池的方法,验证不同数据的上报方法以及数据存储链路。考虑简单化,联网暂时采用WIFI方法。如果觉得使用电路比较麻烦,也可以使用服务开发+虚拟设备上报的方式,具体查看这篇文档。 虽然带手环的老人不一样,但是每个手环上报的属性类别是一样的,我们可以类似编程开发里把它们归结为同一个类(class)。我们首先需要在物联网平台上为我们的demo手环建立一个设备类(即产品),这样我们才能在以后不断的往这个产品下实例化新的设备。进入阿里云物联网平台,在产品页面新建一个产品,选择自定义品类即可,命名为“阿尔茨海默氏症老人监控手环”。 进入产品的功能定义页,定义5个自定义功能——剩余电量,地理位置,心跳,图片地址(存放摄像机上传图片的URL)。 地理位置只需要在“添加功能”里用标准的功能即可,如图,其他全部配置项默认即可。 心跳为一个整型数据,剩余电量为浮点型数据,图片地址为字符型数据,如图。 在设备面板点击“添加设备”,选择刚才创建的手环产品,然后输入随意的设备名称即可。 IoT Studio为交付型业务做了项目维度的隔离,因此需要将用到的设备导入到对应的项目中。首先打开物联网平台的“开发服务”选项进入IoT Studio。点击某个项目名称的“查看”进入项目详情页。然后点击右上角的“导入产品”。选择刚才的手环产品,然后导入,可以在设备管理页看到产品以及下属的设备已经导入项目里。 这样就完成了产品的定义,实例化与项目维度的隔离了。 上云部分树莓派采用python编程,因此我们需要参考物联网平台的python SDK,同时开发者社区也有很多相关文章。在这里我们直接跳过。由于物联网平台的属性不支持直接存储图片,因此我们暂时使用oss进行存储。你也可以选择使用HTTP/2通道(已支持python SDK)将图片上传至物联网平台每个设备单独的存储空间,不过从该存储空间调用图片的URL需要动态生成,可以参考这篇文档。 OSS存储空间准备阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以通过调用 API,在任何应用、任何时间、任何地点上传和下载数据,也可以通过 Web 控制台对数据进行简单的管理。OSS 适合存放任意类型的文件,适合各种网站、开发企业及开发者使用。 首先点击“立即开通”进入开通页面并点击同意协议。 然后进入控制台,新建一个Bucket,一个Bucket相当于一个文件夹,可以通过API路径访问里面的文件。在这里我们选择公共读写。 然后可以看到OSS控制台新建了一个bucket,里面是空的,我们可以尝试上传一些图片。 然后点击文件右方的操作项里的“复制文件URL”,把复制的URL粘贴到浏览器,看看能否预览。 可以看到通过URL访问我们就能看到图片了,这样OSS的配置就完成了。 考虑到Bucket的公共读写特性,安全性会有一定的问题,可以考虑将bucket私有化,然后图片上传的时候设置图片为公共读写,并采用时间戳加盐等方式将图片文件名随机化的方式解决。当然安全性上HTTP/2通道为更优方案。 树莓派代码树莓派的配置与连接在此不再赘述,可以在树莓派新建py文件,直接将此份代码复制过去,并且设置为开机执行,也可以参考这篇文档。代码如下(基于python 3.6),需要根据备注填入自己的账号信息,产品信息等: ##注意,本demo代码忽略了电池电量检测模块import aliyunsdkiotclient.AliyunIotMqttClient as iot ##导入阿里云的设备MQTT库,如果import失败需要先pip3 install 一下import jsonimport multiprocessingimport timeimport randomimport oss2 ##导入阿里云的OSS库,如果import失败需要先pip3 install oss2from picamera import PiCamera ##树莓派的摄像头,系统自带import RPi.GPIO as GPIO ##GPIO口,接红外PIR用import serialimport pynmea2from pulsesensor import Pulsesensor ##导入树莓派的pulsesensor库,https://github.com/tutRPi/Raspberry-Pi-Heartbeat-Pulse-Sensor/blob/master/example.pyauth = oss2.Auth('**AccessId*****','**AccessSecret*****') ##OSS的授权需要阿里云账号AccessId和AccessSecret,具体查看https://usercenter.console.aliyun.com/#/manage/akbucket = oss2.Bucket(auth,'http://oss-cn-beijing.aliyuncs.com','***你的bucket名称***') ##需要根据服务器区域修改节点路径,见文档global picURLtoIoTcamera = PiCamera()camera.resolution = (800,600) ##拍照分辨率,越高越容易分析,但是上次越慢GlobalBpm = 0 ##记录心跳数据Latitude = 0 ##记录GPS数据Longtitude = 0##初始化树莓派def init(): GPIO.setwarnings(False) GPIO.setmode(GPIO.BOARD) GPIO.setup(3, GPIO.IN) passdef take_photo(): ticks = int(time.time()) fileName = 'test%s.jpg' % ticks ##在文件名加入了时间戳作为简易加密手段 filePath = '/home/pi/Pictures/%s' % fileName camera.capture(filePath) bucket.put_object_from_file('bucket_file_name/%s', fileName) ##在这里改bucket名字 global picURLtoIoT picURLtoIoT = 'http://***你的bucket名称**.oss-cn-beijing.aliyuncs.com/bucket_file_name/%s' % fileName ##在这里改bucket名字和bucket内文件夹的名字 print(str(picURLtoIoT))def detect_Heartbeat(): p = Pulsesensor() p.startAsyncBPM() try: while True: bpm = p.BPM if bpm > 0: print("BPM: %d" % bpm) GlobalBpm = bpm; else: print("No Heartbeat found") time.sleep(1) except: p.stopAsyncBPM()def get_GPS(): ser = serial.Serial("/dev/ttyAMA0",9600) while True: line = ser.readline() if line.startswith('$GNRMC'): rmc = pynmea2.parse(line) print "Latitude: ", float(rmc.lat)/100 print "Longitude: ", float(rmc.lon)/100 Latitude = float(rmc.lat)/100 Longtitude = float(rmc.lon)/100 break options = { 'productKey':'**你的ProductKey**', 'deviceName':'**你的deviceName**', 'deviceSecret':'**你的deviceSecret**', 'port':1883, 'host':'iot-as-mqtt.cn-shanghai.aliyuncs.com' ##注意阿里云IoT国内都是华东2,不一定跟OSS的节点一致}host = options['productKey'] + '.' + options['host']def on_message(client, userdata, msg): topic = '/' + productKey + '/' + deviceName + '/update' print(msg.payload)def on_connect(client, userdata, flags_dict, rc): print("Connected with result code " + str(rc))def on_disconnect(client, userdata, flags_dict, rc): print("Disconnected.")##设备上报的定义def upload_device(client): topic = '/sys/'+options['productKey']+'/'+options['deviceName']+'/thing/event/property/post' while True: payload_json = { 'id': int(time.time()), 'params': { 'BPM': GlobalBpm, 'picURL': picURLtoIoT, 'Geo': { 'CoordinateSystem":1, 'Latitude':Latitdue, 'Longitude':Longtitude, 'Altitude':0 }, }, 'method': "thing.event.property.post" } print('send data to iot server: ' + str(payload_json)) client.publish(topic, payload=str(payload_json))if __name__ == '__main__': client = iot.getAliyunIotMqttClient(options['productKey'], options['deviceName'], options['deviceSecret'], secure_mode=3) client.on_connect = on_connect client.connect(host=host, port=options['port'], keepalive=60) p = multiprocessing.Process(target=upload_device, args=(client,)) p.start() get_GPS() detect_Heartbeat() take_photo() GPIO.cleanup() client.loop_forever()结束,把这个python文件设置为开机运行即可。 ...

June 24, 2019 · 3 min · jiezi

借助-Cloud-Toolkit-快速创建-Dubbo-工程

Cloud Toolkit 是一个 IDE 插件,帮助开发者更高效地开发、测试、诊断并部署应用。在最新版的插件中,提供了快速创建 Dubbo 工程的功能,下面就来快速体验下吧。 Dubbo 采用全 Spring 配置方式,透明化接入应用,对应用没有任何 API 侵入,只需用 Spring 加载 Dubbo 的配置即可,Dubbo 基于 Spring 的 Schema 扩展 进行加载。如果不想使用 Spring 配置,可以通过 API 的方式 进行调用。 功能预览 功能入口打开 IntelliJ IDEA,进入菜单:File - New - Project... 第一步:选择 JAVA SDK 版本 第二步:填写应用基本信息 包括选择 Dubbo 版本、Spring Boot 版本等。 第三步:确定创建 如下图所示,就完成了一个完整的 Dubbo 工程的创建了,此工程的结构和Apache Dubbo 官方样例工程完全一致。参考 Apache Dubbo 官方样例工程:https://dubbo.apache.org/zh-cn/docs/user/quick-start.html 立即点击下载 官网 https://toolkit.aliyun.com 亮点1:本地应用一键部署 Deploy to ECS开发者本地编写的应用程序,在图形化界面上进行配置,即可持续便利的部署到云端的 ECS 服务器上;在 Eclipse 中完成编码后,无须在 Maven 、Git 以及其他运维脚本和工具的之间切换,借助 Cloud Toolkit for Eclipse 插件,在 IDE 的图形界面上选择一个或若干个 ECS 实例,即可将应用程序部署至 ECS 指定目录Deploy to EDAS针对阿里云 EDAS 产品的开发者,我们也在插件上打通了本地应用程序和云端部署,在 Eclipse 中完成编码后,将 IDE 内的项目工程,关联上 EDAS 的应用,即可实现快速部署。Deploy to CS Kubernetes针对阿里云 容器服务 Kubernetes 产品的开发者,我们也在插件上打通了本地应用程序和云端Kubernetes部署,在 Eclipse 中完成编码后,将 IDE 内的项目工程,关联上 容器服务 Kubernetes 的部署,即可实现快速部署。亮点2:内置终端 Terminal ...

June 20, 2019 · 1 min · jiezi

修改AndroidStudio缓存目录

文章首发自公众号: nullobject 。个人站点:https://www.nullobject.cn这篇文章主要介绍如何修改Android Studio缓存目录1 说明修改Android Studio缓存目录,主要是修改.AndroidStudio文件夹和m2文件夹所在目录。.AndroidStudio主要用于保存AS的配置以及插件。默认情况下,.AndroidStudio 默认的目录位于C盘系统用户文件夹下: AS默认会为每个版本都生成一个.AndroidStudio目录,当然这可以在安装和更新AS时指定。m2文件夹为AS本地仓库缓存,在使用远程仓库时会先缓存到本地的仓库之后才添加到项目。这两个文件夹会随着使用日渐膨胀,占用C盘空间,因此C盘容量小的朋友可以通过这个方法转移AS缓存目录以减缓系统盘压力。 开始配置之前,建议先关闭所有Android Studio实例。Android Studio基于IntelliJ IDEA开发,可以通过修改Android Studio可执行文件目录下的idea.properties文件实现修改缓存目录。该方法理论上同样适用于修改JetBrains家其他的软件缓存目录。备份idea.properties文件开始配置之前,建议备份一份原始的idea.properties文件,以确保发生意外时候能够快速地恢复原有配置(相信细心的Coder们不会犯这种错误)。 2 修改idea.properties如果您是初次修改idea.properties,建议先熟悉该属性文件中的配置选项,一定程度上这也能够达到知其所以然的效果。idea.properties部分内容如下: 图中箭头所指的内容就是用于修改AS缓存目录的四个选项。可以看到,被注释掉的这四个选项即AS默认的缓存目录配置,要修改AS缓存目录为指定的目录,只需要去掉这四句的注释,并修改为目标路径即可。例如,笔者希望将AS缓存目录修改为D:/Android/.AndroidStudio目录,可以按以下方式修改: 修改完成后保存并关闭文件,重启Android Studio既可生效。 注意:修改完成后,重新打开AS会出现配置引导界面让用户重新配置。想沿用原有的配置,只需要将旧的缓存目录内容复制到新的路径下即可: 3 修改m2文件夹m2文件夹默认的路径为C:/Users/username/.m2 ,其中username即系统用户名。将m2文件夹复制到新的缓存目录下,例如D:/Android。接下来需要打开AndroidStudio配置其路径的环境变量: 配置好之后,重启AS生效。

June 19, 2019 · 1 min · jiezi

十分钟上线-函数计算构建支付宝小程序的后端

阿里云函数计算服务(FunctionCompute,FC)是一个事件驱动的全托管计算服务。通过函数计算与云端各个服务的广泛集成,开发者只需要编写函数代码,就能够快速地开发出弹性高可用的后端系统。接下来我们使用FC,来快速实现一个图片转换服务, 并把这个图片转换服务作为支付宝小程序的后端。 支付宝小程序demo前端效果图: 资源下载及准备工作示例代码附件 【必须】 支付宝小程序开发工具下载 【非必须】 函数计算FC 快捷入口对象存储OSS 快捷入口日志服务Log Service 快捷入口 简明架构图 函数入口普通函数入口 def my_handler(event, context): return 'hello world'函数名my_handler需要与创建函数时的"Handler"字段相对应:例如创建函数时指定的 Handler 为main.my_handler,那么函数计算会去加载main.py中定义的my_handler函数 event 参数event 参数是用户调用函数时传入的数据,其类型是str context 参数context 参数中包含一些函数的运行时信息(例如 request id/临时 AK 等)。其类型是FCContext,具体结构和使用在下面的使用 context介绍 返回值函数的返回值会作为调用函数的结果返回给用户,它可以是任意类型:对于简单类型会函数计算会把它转换成 str 返回,对于复杂类型会把它转换成 JSON 字符串返回 HTTP 触发器的函数入口 HELLO_WORLD = b"Hello world!\n"def handler(environ, start_response): context = environ['fc.context'] status = '200 OK' response_headers = [('Content-type', 'text/plain')] start_response(status, response_headers) return [HELLO_WORLD]environ : environ 参数是一个 python 字典,里面存放了所有和客户端相关的信息,具体详情参考 environ 参数,函数计算增加了两个自定义的 key,分别是 fc.context 和 fc.request_uri ...

June 19, 2019 · 2 min · jiezi

Knative-初体验Serving-Hello-World

通过前面两章的学习你已经掌握了很多 Knative 的理论知识,基于这些知识你应该对 Knative 是谁、它来自哪里以及它要做什么有了一定的认识。可是即便如此你可能还是会有一种犹抱琵琶半遮面,看不清真容的感觉,这就好比红娘拿姑娘的 100 张生活照给你看也不如你亲自去见一面。按常理出牌,一般到这个阶段就该 Hello World 出场了。本篇文章就通过一个 Hello World 和 Knative 来一个“约会”,让你一睹 Knative 这位白富美的真容。 安装 KnativeKnative 社区提供的安装步骤见这里,整个流程大概包含如下三个部分: 准备 kubernetes 环境(你可以在阿里云容器服务中快速创建一个 kubernetes 集群 )安装istio安装 Knative组件虽然看起来只有三步,但是每一步其实都需要手动做大量的工作,执行一堆命令。另外社区文档提供的 yaml 文件默认使用了大量的 gcr.io 镜像,目前国内无法拉取 gcr.io 镜像。所以这些 yaml 文件在国内不能直接使用,至少需要手动同步 30 多个镜像才行。 不过别着急,阿里云容器服务的应用目录已经有 Knative 的安装包,现在只需要在阿里云容器服务上面点击几下鼠标就能轻轻松松搭建一个 Knative 集群 O ^ ~ ^ O O ^ ~ ^ O O ^ ~ ^ O 创建 Kubernetes 集群阿里云容器服务可以通过管理控制台非常方便地创建 Kubernetes 集群。具体过程可以参考创建Kubernetes集群。容器服务提供了专有集群和托管集群两种类型,如果不知道该怎么选择建议你直接选择托管版的 Kubernetes 集群。托管版无需你自己承担 Kubernetes Master 组件的管理和运维,你只需要提供 Node 节点即可。 ...

June 11, 2019 · 6 min · jiezi

CICD联动阿里云容器服务Kubernetes实践之Bamboo篇

本文档以构建一个 Java 软件项目并部署到 阿里云容器服务的Kubernetes集群 为例说明如何使用 Bamboo在阿里云Kubernetes服务上运行Remote Agents并在agents上运行Build Plans。 1. 源码项目本示例中创建的GitHub源码项目地址为: https://github.com/AliyunContainerService/jenkins-demo.git 分支为: bamboo2. 在Kubernetes中部署Remote Agent2.1 创建kaniko-docker-cfg secret kaniko-docker-cfg secret用于Remote Agent上构建任务使用kaniko推送容器镜像时的权限配置 kubectl -n bamboo create secret generic kaniko-docker-cfg --from-file=/root/.docker/config.json上面命令中的/root/.docker/config.json,是在linux服务器上使用root用户通过以下命令生成的: docker login registry.cn-hangzhou.aliyuncs.com2.2 创建serviceaccount bamboo以及clusterrolebinding用于kubectl部署应用到kubernetes集群的权限设置,创建bamboo-agent deployment 注意: 本示例中的clusterrolebinding为admin权限, 具体使用中可以根据自己的需要创建最小权限的serviceaccount bamboo-agent.yaml: ---apiVersion: v1kind: ServiceAccountmetadata: namespace: bamboo name: bamboo---apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata: name: bamboo-cluster-adminsubjects: - kind: ServiceAccount name: bamboo namespace: bambooroleRef: kind: ClusterRole name: cluster-admin apiGroup: rbac.authorization.k8s.io---apiVersion: apps/v1beta2kind: Deploymentmetadata: name: bamboo-agentspec: replicas: 1 selector: matchLabels: app: bamboo-agent template: metadata: labels: app: bamboo-agent spec: serviceAccountName: bamboo containers: - name: bamboo-agent env: - name: BAMBOO_SERVER_URL value: http://xx.xx.xx.xx:8085 image: registry.cn-hangzhou.aliyuncs.com/haoshuwei/docker-bamboo-agent:v1 imagePullPolicy: Always volumeMounts: - mountPath: /root/.docker/ name: kaniko-docker-cfg volumes: - name: kaniko-docker-cfg secret: secretName: kaniko-docker-cfgkubectl -n bamboo apply -f bamboo-agent.yaml上述kubernetes资源创建完毕后等待remote agent完成初始化配置, 可以使用如下命令查看日志: ...

June 10, 2019 · 1 min · jiezi

大盘点-KubeCon-EU-2019-应用管理领域的新看点

摘要: KubeCon EU 2019 刚刚在巴塞罗那拉下帷幕,来自阿里巴巴经济体的讲师团,在大会上分享了互联网场景下规模化 Kubernetes 集群的各项落地经验和教训。所谓“独行速而众行远”,从不断发展壮大的社区中,我们看到越来越多的人拥抱开源,往标准演进,搭上了这趟开往云原生的高速列车。众所周知,云原生架构的中心项目是 Kubernetes,而 Kubernetes 则围绕着“应用”来展开。让应用部署得更好,让开发者更高效,才能给团队和组织带来切实的利益,才能让云原生技术变革发挥更大的作用。变革的势头既如洪水般吞没着老旧封闭的内部系统,又如春雨般孕育出更多的新开发者工具。在本次 KubeCon 中,就出现了许多有关应用管理与部署的新知识。这些知识中有哪些想法和思路值得借鉴,让我们少走弯路?在它们背后,又预示着什么样的技术演进方向? 在本文中,我们邀请到了阿里云容器平台技术专家、原 CoreOS 公司工程师、 K8s Operator 项目的核心作者之一邓洪超,为读者精选了此次会议“应用管理”领域的精华内容来一一进行分析与点评。 The Config ChangedKubernetes 上部署的应用一般会把配置存放到 ConfigMap 里,然后挂载到 Pod 文件系统中去。当 ConfigMap 发生更改时,只有 Pod 里挂载的文件会被自动更新。这种做法对某些会自动做热更新的应用(比如 nginx)来说是 OK 的。但是,对于大多数应用开发者来说,他们更倾向于认为更改配置要做一次新的灰度发布、跟 ConfigMap 相关联的容器应该做一次灰度升级。 灰度升级不仅简化了用户代码,增强了安全稳定性,更是体现了不可变基础架构的思想。应用一旦部署,就不再做变更。需要升级时,只要部署一个新版系统,验证 OK 后再摧毁旧版就好了;验证不通过时也容易回滚到旧版。正是基于这样的思路,来自 Pusher 的工程师们研发了 Wave,一款自动监听 Deployment 相关联的 ConfigMap/Secret 并随之改动而触发 Deployment 升级的工具。这款工具的独特之处在于它会自动搜索该 Deployment PodTemplate 里面的 ConfigMap/Secret,然后把里面所有数据计算一次 hash 放到 PodTemplate annotations 里面;当有变动时,会重新计算一次 hash 并更新 PodTemplate annotations,从而触发 Deployment 升级。无独有偶,开源社区里还有另一款工具 Reloader 也做了类似的功能——不同的是,Reloader 还能让用户自己选择填写监听哪几个 ConfigMap/Secret。 ...

June 3, 2019 · 2 min · jiezi

阿里PB级Kubernetes日志平台建设实践

摘要: 将在QCon上分享的《阿里PB级Kubernetes日志平台建设实践》整理出来,分享给大家。阿里PB级Kubernetes日志平台建设实践QCon是由InfoQ主办的综合性技术盛会,每年在伦敦、北京、纽约、圣保罗、上海、旧金山召开。有幸参加这次QCon10周年大会,作为分享嘉宾在刘宇老师的运维专场发表了《阿里PB级Kubernetes日志平台建设实践》,现将PPT和文字稿整理下来,希望和更多的爱好者分享。 计算形态的发展与日志系统的演进 在阿里的十多年中,日志系统伴随着计算形态的发展在不断演进,大致分为3个主要阶段: 在单机时代,几乎所有的应用都是单机部署,当服务压力增大时,只能切换更高规格的IBM小型机。日志作为应用系统的一部分,主要用作程序Debug,通常结合grep等Linux常见的文本命令进行分析。随着单机系统成为制约阿里业务发展的瓶颈,为了真正的Scale out,飞天项目启动:2009年开始了飞天的第一行代码,2013年飞天5K项目正式上线。在这个阶段各个业务开始了分布式改造,服务之间的调用也从本地变为分布式,为了更好的管理、调试、分析分布式应用,我们开发了Trace(分布式链路追踪)系统、各式各样的监控系统,这些系统的统一特点是将所有的日志(包括Metric等)进行集中化的存储。为了支持更快的开发、迭代效率,近年来我们开始了容器化改造,并开始了拥抱Kubernetes生态、业务全量上云、Serverless等工作。要实现这些改造,一个非常重要的部分是可观察性的工作,而日志是作为分析系统运行过程的最佳方式。在这阶段,日志无论从规模、种类都呈现爆炸式的增长,对日志进行数字化、智能化分析的需求也越来越高,因此统一的日志平台应运而生。日志平台的重要性与建设目标 日志不仅仅是服务器、容器、应用的Debug日志,也包括各类访问日志、中间件日志、用户点击、IoT/移动端日志、数据库Binlog等等。这些日志随着时效性的不同而应用在不同的场景: 准实时级别:这类日志主要用于准实时(秒级延迟)的线上监控、日志查看、运维数据支撑、问题诊断等场景,最近两年也出现了准实时的业务洞察,也是基于这类准实时的日志实现。小时/天级别:当数据积累到小时/天级别的时候,这时一些T+1的分析工作就可以开始了,例如用户留存分析、广告投放效果分析、反欺诈、运营监测、用户行为分析等。季度/年级别:在阿里,数据是我们最重要的资产,因此非常多的日志都是保存一年以上或永久保存,这类日志主要用于归档、审计、攻击溯源、业务走势分析、数据挖掘等。在阿里,几乎所有的业务角色都会涉及到各式各样的日志数据,为了支撑各类应用场景,我们开发了非常多的工具和功能:日志实时分析、链路追踪、监控、数据清洗、流计算、离线计算、BI系统、审计系统等等。其中很多系统都非常成熟,日志平台主要专注于智能分析、监控等实时的场景,其他功能通常打通的形式支持。 阿里日志平台现状 目前阿里的日志平台覆盖几乎所有的产品线和产品,同时我们的产品也在云上对外提供服务,已经服务了上万家的企业。每天写入流量16PB以上,对应日志行数40万亿+条,采集客户端200万,服务数千Kubernetes集群,是国内最大的日志平台之一。    为何选择自建                       日志系统存在了十多年,目前也有非常多的开源的方案,例如最典型的ELK(Elastic Search、Logstash、Kibana),通常一个日志系统具备以下功能:日志收集/解析、查询与检索、日志分析、可视化/告警等,这些功能通过开源软件的组合都可以实现,但最终我们选择自建,主要有几下几点考虑: 数据规模:这些开源日志系统可以很好的支持小规模的场景,但很难支持阿里这种超大规模(PB级)的场景。资源消耗:我们拥有百万规模的服务器/容器,同时日志平台的集群规模也很大,我们需要减少对于采集以及平台自身的资源消耗。多租户隔离:开源软件搭建的系统大部分都不是为了多租户而设计的,当非常多的业务 / 系统使用日志平台时,很容易因为部分用户的大流量 / 不恰当使用而导致打爆整个集群。运维复杂度:在阿里内部有一套非常完整的服务部署和管理系统,基于内部组件实现会具备非常好的运维复杂度。高级分析需求:日志系统的功能几乎全部来源与对应的场景需求,有很多特殊场景的高级分析需求开源软件没办法很好的支持,例如:上下文、智能分析、日志类特殊分析函数等等。 Kubernetes日志平台建设难点围绕着Kubernetes场景的需求,日志平台建设的难点主要有以下几点: 日志采集:采集在Kubernetes中极其关键和复杂,主要因为Kubernetes是一个高度复杂的场景,K8s中有各式各样的子系统,上层业务支持各种语言和框架,同时日志采集需要尽可能的和Kubernetes系统打通,用K8的形式来完成数据采集。资源消耗:在K8s中,服务通常都会拆的很小,因此数据采集对于服务自身的资源消耗要尽可能的少。这里我们简单的做一个计算,假设有100W个服务实例,没个采集Agent减少1M的内存、1%的CPU开销,那整体会减少1TB的内存和10000个CPU核心。运维代价:运维一套日志平台的代价相当之大,因此我们不希望每个用户搭建一个Kubernetes集群时还需再运维一个独立的日志平台系统。因此日志平台一定是要SaaS化的,应用方/用户只需要简单的操作Web页面就能完成数据采集、分析的一整套流程。便捷使用:日志系统最核心的功能是问题排查,问题排查的速度直接决定了工作效率、损失大小,在K8s场景中,更需要一套高性能、智能分析的功能来帮助用户快速定位问题,同时提供一系列简单有效的可视化手段进行辅助。阿里PB级Kubernetes日志平台建设实践Kubernetes日志数据采集 无论是在ITOM还是在未来的AIOps场景中,日志获取都是其中必不可少的一个部分,数据源直接决定了后续应用的形态和功能。在十多年中,我们积累了一套物理机、虚拟机的日志采集经验,但在Kubernetes中不能完全适用,这里我们以问题的形式展开: 问题1:DaemonSet or Sidecar 日志最主要的采集工具是Agent,在Kubernetes场景下,通常会分为两种采集方式: DaemonSet方式:在K8S的每个node上部署日志agent,由agent采集所有容器的日志到服务端。Sidecar方式:一个POD中运行一个sidecar的日志agent容器,用于采集该POD主容器产生的日志。每种采集方式都有其对应的优缺点,这里简单总结如下: DaemonSet方式Sidecar方式采集日志类型标准输出+部分文件文件部署运维一般,需维护DaemonSet较高,每个需要采集日志的POD都需要部署sidecar容器日志分类存储一般,可通过容器/路径等映射每个POD可单独配置,灵活性高多租户隔离一般,只能通过配置间隔离强,通过容器进行隔离,可单独分配资源支持集群规模中小型规模,业务数最多支持百级别无限制资源占用较低,每个节点运行一个容器较高,每个POD运行一个容器查询便捷性较高,可进行自定义的查询、统计高,可根据业务特点进行定制可定制性低高,每个POD单独配置适用场景功能单一型的集群大型、混合型、PAAS型集群在阿里内部,对于大型的PAAS集群,主要使用Sidecar方式采集数据,相对隔离性、灵活性最好;而对与功能比较单一(部门内部/产品自建)的集群,基本都采用DaemonSet的方式,资源占用最低。 问题2:如何降低资源消耗 我们数据采集Agent使用的是自研的Logtail,Logtail用C++/Go编写,相对开源Agent在资源消耗上具有非常大的优势,但我们还一直在压榨数据采集的资源消耗,尤其在容器场景。通常,为了提高打日志和采集的性能,我们都使用本地SSD盘作为日志盘。这里我们可以做个简答的计算:假设每个容器挂载1GB的SSD盘,1个物理机运行40个容器,那每台物理机需要40GB的SSD作为日志存储,那5W物理机则会占用2PB的SSD盘。为了降低这部分资源消耗,我们和蚂蚁金服团队的同学们一起开发了FUSE的日志采集方式,使用FUSE(Filesystem in Userspace,用户态文件系统)虚拟化出日志盘,应用直接将日志写入到虚拟的日志盘中,最终数据将直接从内存中被Logtail采集到服务端。这种采集的好处有: 物理机无需为容器提供日志盘,真正实现日志无盘化。应用程序视角看到的还是普通的文件系统,无需做任何额外改造。数据采集绕过磁盘,直接从内存中将数据采集到服务端。所有的数据都存在服务端,服务端支持横向扩展,对于应用来说他们看到的日志盘具有无线存储空间。问题3:如何与Kubernetes无缝集成 Kubernetes一个非常大的突破是使用声明式的API来完成服务部署、集群管理等工作。但在K8s集群环境下,业务应用/服务/组件的持续集成和自动发布已经成为常态,使用控制台或SDK操作采集配置的方式很难与各类CI、编排框架集成,导致业务应用发布后用户只能通过控制台手动配置的方式部署与之对应的日志采集配置。因此我们基于Kubernetes的CRD(CustomResourceDefinition)扩展实现了采集配置的Operator,用户可以直接使用K8s API、Yaml、kubectl、Helm等方式直接配置采集方式,真正把日志采集融入到Kubernetes系统中,实现无缝集成。 问题4:如何管理百万级Logtail 对于人才管理有个经典的原则:10个人要用心良苦,100个人要杀伐果断,1000个人要甩手掌柜。而同样对于Logtail这款日志采集Agent的管理也是如此,这里我们分为3个主要过程: 百规模:在好几年前,Logtail刚开始部署时,也就在几百台物理机上运行,这个时期的Logtail和其他主流的Agent一样,主要完成数据采集的功能,主要流程为数据输入、处理、聚合、发送,这个时期的管理基本靠手,采集出现问题的时候人工登录机器去看问题。万规模:当越来越多的应用方接入,每台机器上可能会有多个应用方采集不同类型的数据,手动配置的接入过程也越来越难以维护。因此我们重点在多租户隔离以及中心化的配置管理,同时增加了很多控制相关的手段,比如限流、降级等。百万规模:当部署量打到百万级别的时候,异常发生已经成为常态,我们更需要的是靠一系列的监控、可靠性保证机制、自动化的运维管理工具,让这些机制、工具来自动完成Agent安装、监控、自恢复等一系列工作,真正做到甩手掌柜。Kubernetes日志平台架构 上图是阿里Kubernetes日志平台的整体架构,从底到上分为日志接入层、平台核心层以及方案整合层: 平台提供了非常多的手段用来接入各种类型的日志数据。不仅仅只有Kubernetes中的日志,同时还包括和Kubernetes业务相关的所有日志,例如移动端日志、Web端应用点击日志、IoT日志等等。所有数据支持主动Push、被动Agent采集,Agent不仅支持我们自研的Logtail,也支持使用开源Agent(Logstash、Fluentd、Filebeats等)。日志首先会到达平台提供的实时队列中,类似于Kafka的consumer group,我们提供实时数据订阅的功能,用户可以基于该功能实现ETL的相关需求。平台最核心的功能包括: 实时搜索:类似于搜索引擎的方式,支持从所有日志中根据关键词查找,支持超大规模(PB级)。实时分析:基于SQL92语法提供交互式的日志分析方法。机器学习:提供时序预测、时序聚类、根因分析、日志聚合等智能分析方法。流计算:对接各类流计算引擎,例如:Flink、Spark Stream、Storm等。离线分析:对接离线分析引擎,例如Hadoop、Max Compute等。基于全方位的数据源以及平台提供的核心功能,并结合Kubernetes日志特点以及应用场景,向上构建Kubernetes日志的通用解决方案,例如:审计日志、Ingress日志分析、ServiceMesh日志等等。同时对于有特定需求的应用方/用户,可直接基于平台提供的OpenAPI构建上层方案,例如Trace系统、性能分析系统等。下面我们从问题排查的角度来具体展开平台提供的核心功能。 PB级日志查询 排查问题的最佳手段是查日志,大部分人脑海中最先想到的是用 grep 命令查找日志中的一些关键错误信息, grep 是Linux程序员最受欢迎的命令之一,对于简单的问题排查场景也非常实用。如果应用部署在多台机器,那还会配合使用pgm、pssh等命令。然而这些命令对于Kubernetes这种动态、大规模的场景并不适用,主要问题有: 查询不够灵活,grep命令很难实现各种逻辑条件的组合。grep是针对纯文本的分析手段,很难将日志格式化成对应的类型,例如Long、Double甚至JSON类型。grep命令的前提条件是日志存储在磁盘上。而在Kubernetes中,应用的本地日志空间都很小,并且服务也会动态的迁移、伸缩,本地的数据源很可能会不存在。grep是典型的全量扫描方式,如果数据量在1GB以内,查询时间还可以接受,但当数据量上升到TB甚至PB时,必须依赖搜索引擎的技术才能工作。我们在2009年开始在飞天平台研发过程中,为够解决大规模(例如5000台)下的研发效率、问题诊断等问题,开始研支持超大规模的日志查询平台,其中最主要的目标是“快”,对于几十亿的数据也能够轻松在秒级完成。 日志上下文 当我们通过查询的方式定位到关键的日志后,需要分析当时系统的行为,并还原出当时的现场情况。而现场其实就是当时的日志上下文,例如: 一个错误,同一个日志文件中的前后数据一行LogAppender中输出,同一个进程顺序输出到日志模块前后顺序一次请求,同一个Session组合一次跨服务请求,同一个TraceId组合在Kubernetes的场景中,每个容器的标准输出(stdout)、文件都有对应的组合方式构成一个上下文分区,例如Namesapce+Pod+ContainerID+FileName/Stdout。为支持上下文,我们在采集协议中对每个最小区分单元会带上一个全局唯一并且单调递增的游标,这个游标对单机日志、Docker、K8S以及移动端SDK、Log4J/LogBack等输出中有不一样的形式。 为日志而生的分析引擎 ...

May 30, 2019 · 1 min · jiezi

把握数据库发展趋势-DBA应如何避免踩坑

摘要:在DTCC 2019大会上,阿里云智能数据库产品事业部高级产品专家萧少聪做了题为《如何构建云时代DBA的知识体系》的演讲,进行云时代以后,IT行业各工种的职责都在发生变化,云数据库使得日常DBA管理实现更多的自动化,大大提高日常管理效率,同时也对于企业整体投资产出可以更快获得成效。面对云数据库的发展趋势,DBA应如何避免“踩坑”呢?本文就为大家揭晓答案。 专家简介:萧少聪(花名:铁庵),阿里云智能数据库产品事业部高级产品专家,PostgreSQL中国社区常委。 直播回放链接:https://yq.aliyun.com/live/1046 议题PPT下载,戳这里!https://yq.aliyun.com/download/3562 本文将主要围绕以下四个方面进行分享: 管理模式的变化云数据库VS.自建数据库云DBA知识体系构成如何成为优秀的云DBA一、管理模式的变化对于数据库技术而言,“云”已经成为大家无法忽视的技术趋势。在Gartner 2018年的数据库魔力四象限里面,云计算数据库厂商已经占LEADERS及VISIONARIES领域的绝对比例,这也代表了业界对于云的认可。 那么,云和传统架构有什么不同呢?对于传统数据库系统而言,需要搭建很多的硬件,连接很多的网线,在自己搭建的私有云里面可能会有一些虚拟化或者容器化的架构,再往上对于DBA而言其实需要的就是一个数据库,需要能够连接进去进行操作。当然了,在传统架构下,DBA能够对数据库有更多的操作和配置,但是在云上可能只会提供一部分数据库配置文件的修改权限,并不会允许修改全部配置,这是因为云为DBA提供的是SLA,也就是说云数据库提供的是服务。针对于服务而言,不太可能允许DBA去对操作系统进行改变,因为这样可能会破坏HA,因此会有一些限制,但是对于数据库操作而言,依旧是通过一个端口就连接进去的。 除了数据库架构设计之外,传统架构和云架构在做安装配置的时候也会有所不同。在传统架构下,DBA需要去规划数据库所有的一切,包括操作系统、硬件以及各种安装准备以及验收、切换等一系列演练。在云架构之下,整体的配置、安装以及部署是不需要DBA敲各种命令或者安装各种业务系统的,操作系统、参数优化以及整体的HA只需要在云控制台上点击几下就可以配置完成,无论使用阿里的公共云还是私有云都是这样的状态。这些就是在管理模式方面或者在系统创建过程中已经能够看到的变化。 二、云数据库VS.自建数据库有很多人存在这样一个疑问。那就是“云数据库和自建数据库有哪些区别?”。这里首先澄清一个概念,在阿里巴巴看来,真正云托管的数据库才是云数据库,而如果只是使用ECS云服务器来自行搭建的数据库并不算是真正的云数据库。 实际上,云数据库最终提供的是一个服务,其包括了系统的可靠性、可用性、安全、备份等一系列的东西,当建立完云数据库这些都是配置完成的,无需DBA进行二次配置。当然,如果DBA有自己配置的需求,阿里云所提供的云数据库服务也会提供API接口进行调配,或者也可以通过阿里云的管理平台进行操作,而不像传统情况下需要非常高的数据库初始建设费用。 成本模式的变迁 对于成本而言,传统情况下自己建设数据中心需要规划好未来3到5年到底需要多少资源,所以成本是一次性提供的。此外,对于DBA而言,一般将其分为业务DBA和运维DBA,前者为数据库业务解决问题,发挥功用,后者纯粹地负责运维工作,比如安装、部署、定期进行各种类型的巡检。未来,运维DBA会因为云架构的体现慢慢地减少,而业务DBA却不会消亡,因此DBA应该更加关注于企业在做什么业务,数据架构应该如何优化,帮助企业改变本身的运营状态。 以往成本的开支,一下子就是一台服务器,但是如今在云上或者互联网上有很多的创业公司,所谓的“独角兽”就是从很小规模开始起步,突然之间变成很大。当这些创业公司小的时候或许并不需要购买一台服务器,通过云架构,就可以从很小开始,逐渐弹性上去,这样的弹性能力使得IT实现资源的释放。如果今天还在使用传统的数据库服务器购买方式,而竞争对手或许就能够将节省下来的资金用于技术人员或者业务上去,因为没有了固定资产初期的开销,对于创业公司而言,其运行的资金链也会更加健康,发展的速度也会更快。 三、云DBA知识体系构成随着数据库技术的发展,企业对于DBA的需求也不断提高。从对于OLTP这样的SQL数据库和NoSQL数据的掌握,进一步演进,为了解决性能问题可能需要Key-Value缓存数据库,之后建立OLAP数据仓库,再之后实现大数据离线分析。 而对于初创公司而言,就会发现在最开始可能三两台机器就搞定了,只需要一个兼职的DBA。 进一步当开始使用Key-Value缓存数据库之后,业务越来越重,单台服务器无法搞定,需要实现HA。此时就比较困难了,因此需要一个比较神奇的DBA,需要DBA什么都懂。 当企业进一步发展到更大的时候,可能不仅仅需要解决一套系统的问题,可能需要解决多套系统的问题。此时可能需要一个DBA团队,分工会变得更为细致,不仅有专业的DBA,还应该有顶尖的架构级别DBA来解决整体问题。 更进一步,可能需要做数据仓库和大数据,那么整个DBA团队的分工就会更加明细。 在企业的实际运行过程中,DBA需要做大量的工作,有的时候甚至是操作系统的各种细节都需要了解清楚才能将数据库调优好。 云数据库的理论基础 而当进入云数据库时代,需要看到的是另外一种景象。这里有一些云计算的新名词,比如Region地域、AZ可用区、VPC以及VSwith等,这些都是云DBA需要了解和掌握的。从数据库的角度来看,云数据库的确出现了很多新名词,但是数据库基础理论依然是不变的,依然会有实例、高可用、分布式、SQL、ACID和CAP等理论。 运维简化:自动化部署 以往都会说需要部署一个主备集群,而今天如果想要部署主备集群也会在一个IDC中心进行部署。如果想要部署跨IDC的主备集群,在传统架构下往往需要购买光纤、光缆,并且需要确定光纤、光缆的延迟情况,判断其所造成的延迟是否能够接受。而在云数据库架构之下,这些信息都不需要进行管理,所需要管理的就是在购买云数据库时进行选择,比如选择跨中心的主备就可以直接建立起来,因此这种复杂架构的构建并不需要自己来规划,可以节省DBA去做传统底层业务处理的时间。 运维简化:跨地域部署及切换 除了对于传统架构比较容易的同一个城市跨AZ之外,其实如果想要实现跨省就会变得非常复杂了。然而,在云上就会变得非常容易,如果想要实现跨Region的搭建就可以利用阿里云上的DTS工具将数据拉过去,需要进行数据复制的时候才会收费,平时不用的时候甚至可以直接将其关闭掉。 当搭建了跨Region的数据中心之后,后面就会有更多的事情。比如到底敢不敢进行主备切换,以往做主备切换的时候都需要配置一大堆的DNS,自己写很多脚本做确认,而在云架构底下,只需要通过一个按钮就可以实现。因此,大家一定要清楚,作为云DBA应该去学习哪些东西,同时需要放弃哪些东西的学习。因此当有云架构之后,DBA可以将重心放到学习如何优化SQL以及各种不同的数据库特性以及它们之间的组合架构如何解决业务上的问题,而底层的业务架构可以交给云去做。 运维简化:定期全/增量备份 在云上面,如果需要做定期增量备份也仅仅需要点击几个按钮进行构建即可。 运维简化:恢复到时间点 无论针对于哪个数据库,阿里云的服务都可以做到任意时间点的秒级恢复。这一功能并不只是为了帮助用户找回数据,很多用户的DBA和开发的互动越来越频繁,如果开发收到某个时间段系统运行较慢的反馈,就可以直接克隆一个那个时间段的新实例出来,并且只需要按需购买即可,克隆出来实例调试完程序之后直接将其关闭掉即可,一切的成本都在DBA的掌握之中。 运维简化:按需横向扩展 DBA对于数据库的横向扩展也会做很多动作,传统的方式通过只读实例可以做相应的扩展,同时还有像阿里云的DRDS分布式数据库分片的运行方案,也能够比较容易地搭建出来,进一步地还可以走向PolarDB,通过分布式的一写多读来简化业务规则。未来,DBA需要重点关注的点在于什么时候使用什么样的架构。举例而言,如果需要解决某个大促时间段大量的读请求问题,应该通过只读实例来实现。而如果老旧业务完全可以基于互联网改写,就可以选择直接通过DRDS做整个系统的分库分表操作。如果需要非常强的与关系型数据库一致性的业务,并且与此同时数据量非常大,可能需要选择PolarDB的架构,因此DBA需要对于不同的数据库架构以及其背后原理有自己的理解。 运维简化:自动读写分离阿里云数据库帮助用户实现了读写分离,DBA不需要再进行应用程序上的业务改写,比如对于读写分离的设置都可以实现自动化。通过对于请求的分析来判断应该分发到读实例还是写实例。 以上这些都是云数据库能够提供的能力,大家会发现以往的管理模型已经都覆盖到了。未来运维方面的DBA工作可能减轻,因此DBA应该跳到业务方向上进行发展。 四、如何成为优秀的云DBA在云数据库的背景下,DBA是否还需要学习每一部分的数据库管理知识呢?因为人的时间是有限的,未来除非真的要做类似于阿里云的整体管控系统时需要深入底层进行分析,而如果不是,那么这些数据库管理就可以交给云管控平台来实现。但是数据库优化却需要DBA知道和掌握,这里并不是指修改哪些参数能够优化成什么样子,因为这些在云平台上就已经配置好了,但是DBA需要知道的是针对于某个数据库,什么样的索引对它更加有效,表与表之间的关系应该如何建立才能使得数据库性能更好。 云数据库提供了很多的集群架构,也并不一定需要全部学习。无论是单节点、双节点还是三节点,通过阿里云都可以实现一键式部署。因此作为DBA更加需要了解不同的数据库实例之间应该如何进行互动,从而产生对业务有效的架构方案和规划方案,这正是DBA需要深入思考的,而不是每天都在备份服务器,部署数据库,检修各种硬件。 云服务支持边界 基于云的运行环境,云数据库服务和DBA的边界会发生改变。资源调度、基础优化、平台能力以及准确输出都是由云来提供的,而企业的DBA需要做这样几件事情:对于表结构需要花费更多的时间来规划,定义自己企业的SQL标准来规范开发模型,对于SQL以及结构进行优化来提升业务性能。此外,DBA不仅应该关注于数据库,实际上也应该做企业成本的控制,通过不同的数据模型组合来解决不同的业务问题,也需要了解云数据库日志的不同,并通过故障检测自查或者发起服务需求。 性能问题甄别 对于云DBA而言,如果出现了数据库性能问题应该怎么做呢?其实任何的云厂商都会有自己成熟的一整套监控以及性能分析方案,比如阿里云的方案就源自于阿里巴巴内部的经验,能够帮助DBA发现故障并提供解决方案,使用起来非常方便。 云服务支持边界 此外,阿里云也提供了一种能力,就是阿里云后端的DBA会帮助用户解决数据库相关的问题。以往情况下,如果数据库出现了问题,需要打电话给服务商来约时间解决,存在一定的延迟。而今天在阿里云上面,DBA随时可以进入。并且阿里云还提供了安全保障,具有完善的授权机制,只有用户授权阿里云的DBA访问用户数据库或者进行服务的时候,阿里云的DBA才有权限为用户提供服务,而如果没有得到授权,阿里云的DBA是不能够进入的。 高危SQL预防 阿里巴巴具有自己的一整套数据库开发规范,而用户的DBA也可以自己定义一套数据库开发规范,比如可以定义某一个字段是否可以以某种方式编写,这样就从系统设计和规范的层面避免烂SQL进入系统,进而造成系统故障。 跨云管理 今天,阿里云本身在运营云,而其实阿里云也会提供跨云的管理工具。无论用户使用的是哪里的云,只要管理的是MySQL、MongoDB、Redis数据库都会提供HDM工具来协助用户管理跨云数据库。 总结一下,云数据库带来了标准化部署、自动化运维、按需扩容以及工具化调优等优势。对于企业而言,不要再让DBA为部署和备份等琐碎的运维工作所缠绕了,他们应该将精力投入到优化架构、写好SQL以及做好数据库的整体构造上,进而为企业输出核心技术生产力。 本文作者:七幕 阅读原文 ...

May 29, 2019 · 1 min · jiezi

Nacos-Namespace-和-Endpoint-在生产环境下的最佳实践

随着使用 Nacos 的企业越来越多,遇到的最频繁的两个问题就是:如何在我的生产环境正确的来使用 namespace 以及 endpoint。这篇文章主要就是针对这两个问题来聊聊使用 nacos 过程中关于这两个参数配置的最佳实践方式。 namespce关于 namespace ,以下主要从 namespace 的设计背景 和 namespace 的最佳实践 两个方面来讨论。 namespace 的设计背景namespace 的设计是 nacos 基于此做多环境以及多租户数据(配置和服务)隔离的。即: 从一个租户(用户)的角度来看,如果有多套不同的环境,那么这个时候可以根据指定的环境来创建不同的 namespce,以此来实现多环境的隔离。例如,你可能有日常,预发和生产三个不同的环境,那么使用一套 nacos 集群可以分别建以下三个不同的 namespace。如下图所示: 从多个租户(用户)的角度来看,每个租户(用户)可能会有自己的 namespace,每个租户(用户)的配置数据以及注册的服务数据都会归属到自己的 namespace 下,以此来实现多租户间的数据隔离。例如超级管理员分配了三个租户,分别为张三、李四和王五。分配好了之后,各租户用自己的账户名和密码登录后,创建自己的命名空间。如下图所示。 注意: 该功能还在规划中。 namespace 的最佳实践关于 namespace 的最佳实践 ,这部分主要包含有两个 Action: 如何来获取 namespace 的值namespace 参数初始化方式如何来获取 namespace 的值无论您是基于 Spring Cloud 或者 Dubbo 来使用 nacos,都会涉及到 namespace 的参数输入,那么这个时候 namespace 的值从哪里可以获取呢? 如果您在使用过程中没有感知到这个参数的输入,那么 nacos 统一会使用一个默认的 namespace 作为输入,nacos naming 会使用 public 作为默认的参数来初始化,nacos config 会使用一个空字符串作为默认的参数来初始化。。如果您需要自定义自己的 namespace,那么这个值该怎么来产生?可以在 nacos 的控制台左边功能侧看到有一个 命名空间 的功能,点击就可以看到 新建命名空间 的按钮,那么这个时候就可以创建自己的命名空间了。创建成功之后,会生成一个命名空间ID,主要是用来避免命名空间名称有可能会出现重名的情况。因此当您在应用中需要配置指定的 namespace 时,填入的是命名空间ID。重要的事情说三遍, 当您在应用中需要配置指定的 namespace 时,填入的是命名空间 ID当您在应用中需要配置指定的 namespace 时,填入的是命名空间 ID当您在应用中需要配置指定的 namespace 时,填入的是命名空间 ID说明: namesace 为 public 是 nacos 的一个保留控件,如果您需要创建自己的 namespace,最好不要和 public 重名,以一个实际业务场景有具体语义的名字来命名,以免带来字面上不容易区分自己是哪一个 namespace。 ...

May 28, 2019 · 1 min · jiezi

开发函数计算的正确姿势-移植-nextjs-服务端渲染框架

首先介绍下在本文出现的几个比较重要的概念: 函数计算(Function Compute): 函数计算是一个事件驱动的服务,通过函数计算,用户无需管理服务器等运行情况,只需编写代码并上传。函数计算准备计算资源,并以弹性伸缩的方式运行用户代码,而用户只需根据实际代码运行所消耗的资源进行付费。函数计算更多信息 参考。Fun: Fun 是一个用于支持 Serverless 应用部署的工具,能帮助您便捷地管理函数计算、API 网关、日志服务等资源。它通过一个资源配置文件(template.yml),协助您进行开发、构建、部署操作。Fun 的更多文档 参考。2.0 版本的 Fun,在部署这一块做了很多努力,并提供了比较完善的功能,能够做到将云资源方便、平滑地部署到云端。但该版本,在本地开发上的体验,还有较多的工作要做。于是,我们决定推出 Fun Init 弥补这一处短板。Fun Init: Fun Init 作为 Fun 的一个子命令存在,只要 Fun 的版本大于等于 2.7.0,即可以直接通过 fun init 命令使用。Fun Init 工具可以根据指定的模板快速的创建函数计算应用,快速体验和开发函数计算相关业务。官方会提供常用的模板,用户也可以自定自己的模板。背景next.js 是一种 React 的服务端渲染框架,且 next.js 集成度极高,框架自身集成了 webpack、babel、express 等,使得开发者可以仅依赖 next、react、react-dom 就可以非常方便的构建自己的 SSR React 应用,开发者甚至都不用像以前那样关心路由。 next.js 的高度集成性,使得我们很容易就能实现代码分割、路由跳转、热更新以及服务端渲染和前端渲染。 next.js 可以与 express、koa 等服务端结合使用。为了能让 next.js 在函数计算运行,首先需要让 next.js 在 express 中运行起来,然后再移植 express 到函数计算中运行。express 应用移植相关文章: 开发函数计算的正确姿势——移植 Express移植 express.js 应用到函数计算next.js 运行在 express 中现在,我们提供了一个 fun 模块,通过该模板,三分钟就可以让 next.js 应用在函数计算中运行起来。效果如下: 快速开始1. 安装 node ...

May 24, 2019 · 1 min · jiezi

Blink-有何特别之处菜鸟供应链场景最佳实践

阿里妹导读:菜鸟供应链业务链路长、节点多、实体多,使得技术团队在建设供应链实时数仓的过程中,面临着诸多挑战,如:如何实现实时变Key统计?如何实现实时超时统计?如何进行有效地资源优化?如何提升多实时流关联效率?如何提升实时作业的开发效率? 而 Blink 能否解决这些问题?下面一起来深入了解。背景菜鸟从2017年4月开始探索 Blink(即 Apache Flink 的阿里内部版本),2017年7月开始在线上环境使用 Blink,作为我们的主流实时计算引擎。 为什么短短几个月的探索之后,我们就选择Blink作为我们主要的实时计算引擎呢? 在效率上,Blink 提供 DataStream、TableAPI、SQL 三种开发模式,强大的 SQL 模式已经满足大部分业务场景,配合半智能资源优化、智能倾斜优化、智能作业压测等功能,可以极大地提升实时作业的开发效率;在性能上,诸如MiniBatch&MicroBatch、维表 Async&Cache、利用 Niagara 进行本地状态管理等内部优化方案,可以极大地提升实时作业的性能;在保障上,Blink 自带的 Failover 恢复机制,能够实现线程级的恢复,可以做到分钟级恢复,配合 Kmonitor 监控平台、烽火台预警平台,可以有效地实现实时作业的数据保障。 接下来,我将结合供应链业务的一些业务场景,简要说明,Blink 如何解决我们遇到的一些实际问题。 回撤机制订单履行是供应链业务中最常见的物流场景。什么是订单履行呢?当商家 ERP 推单给菜鸟之后,菜鸟履行系统会实时计算出每笔订单的出库、揽收、签收等节点的预计时间,配送公司需要按照各节点的预计时间进行订单的配送。为了保证订单的准点履约,我们经常需要统计每家配送公司每天各个节点的预计单量,便于配送公司提前准备产能。 看似很简单的实时统计加工,我们在开发过程中遇到了什么问题呢?履行重算!当物流订单的上游某个节点延迟时,履行系统会自动重算该笔订单下游所有节点的预计时间。比如某个物流订单出库晚点后,其后的预计揽收时间、预计签收时间都会重算。而对于大部分的实时计算引擎来说,并不能很友好的支持这种变 Key 统计的问题。以前,数据量没那么大的时候,还可以通过 OLAP 数据库来解决这类场景,当量上来后, OLAP 方案的成本、性能都是很大的问题。 除了 OLAP 方案,我们提倡采用 Blink 已经内置的 Retraction 机制,来解决这类变 Key 统计的问题,这也是我们在2017年初就开始尝试 Blink 的重要原因。Blink 的Retraction 机制,使用 State 在内存或者外部存储设备中对数据进行统计处理,当上游数据源对某些汇总 Key 的数据做更新时,Blink 会主动给下游下发一个删除消息从而“撤回”之前的那条消息,并用最新下发的消息对表做更新操作。 下面是一个简化后的案例,供了解Blink Retraction的内部计算过程: 对于上述案例,可以通过 Blink 提供的强大的、灵活的、简易的 SQL 开发模式来实现,只需要几行 SQL 即可完成。 select plan_tms_sign_time ,sum(1) as plan_tms_sign_lgtord_cntfrom (select lg_order_code ,last_value(plan_tms_sign_time) as plan_tms_sign_time from dwd_csn_whc_lgt_fl_ord_ri group by lg_order_code ) ssgroup by plan_tms_sign_time;维表关联供应链业务的实体角色非常多(仓、配、分拨、站点、小件员、货主、行业、地区等),实体繁多,这意味着我们在建设实时明细中间层的时候,会使用大量的维表关联,这对 Blink 在维表关联的性能上提出了更高的要求——如何提升大量的大小维表的关联性能?Blink 从来没让用户失望,Blink SQL 模式在维表关联的性能上,也做了大量的优化: ...

May 24, 2019 · 2 min · jiezi

TalkingData的Spark-On-Kubernetes实践

摘要: 本文整理自talkingdata云架构师徐蓓的分享,介绍了Spark On Kubernetes在TalkingData的实践。众所周知,Spark是一个快速、通用的大规模数据处理平台,和Hadoop的MapReduce计算框架类似。但是相对于MapReduce,Spark凭借其可伸缩、基于内存计算等特点,以及可以直接读写Hadoop上任何格式数据的优势,使批处理更加高效,并有更低的延迟。实际上,Spark已经成为轻量级大数据快速处理的统一平台。Spark作为一个数据计算平台和框架,更多的是关注Spark Application的管理,而底层实际的资源调度和管理更多的是依靠外部平台的支持: Spark官方支持四种Cluster Manager:Spark standalone cluster manager、Mesos、YARN和Kubernetes。由于我们TalkingData是使用Kubernetes作为资源的调度和管理平台,所以Spark On Kubernetes对于我们是最好的解决方案。 如何搭建生产可用的Kubernetes集群部署 目前市面上有很多搭建Kubernetes的方法,比如Scratch、Kubeadm、Minikube或者各种托管方案。因为我们需要简单快速地搭建功能验证集群,所以选择了Kubeadm作为集群的部署工具。部署步骤很简单,在master上执行: kubeadm init在node上执行: kubeadm join --token : --discovery-token-ca-cert-hash sha256:具体配置可见官方文档:https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/。需要注意的是由于国内网络限制,很多镜像无法从k8s.gcr.io获取,我们需要将之替换为第三方提供的镜像,比如:https://hub.docker.com/u/mirrorgooglecontainers/。 网络 Kubernetes网络默认是通过CNI实现,主流的CNI plugin有:Linux Bridge、MACVLAN、Flannel、Calico、Kube-router、Weave Net等。Flannel主要是使用VXLAN tunnel来解决pod间的网络通信,Calico和Kube-router则是使用BGP。由于软VXLAN对宿主机的性能和网络有不小的损耗,BGP则对硬件交换机有一定的要求,且我们的基础网络是VXLAN实现的大二层,所以我们最终选择了MACVLAN。CNI MACVLAN的配置示例如下: { "name": "mynet", "type": "macvlan", "master": "eth0", "ipam": { "type": "host-local", "subnet": "10.0.0.0/17", "rangeStart": "10.0.64.1", "rangeEnd": "10.0.64.126", "gateway": "10.0.127.254", "routes": [ { "dst": "0.0.0.0/0" }, { "dst": "10.0.80.0/24", "gw": "10.0.0.61" } ] }}Pod subnet是10.0.0.0/17,实际pod ip pool是10.0.64.0/20。cluster cidr是10.0.80.0/24。我们使用的IPAM是host-local,规则是在每个Kubernetes node上建立/25的子网,可以提供126个IP。我们还配置了一条到cluster cidr的静态路由10.0.80.0/24,网关是宿主机。这是因为容器在macvlan配置下egress并不会通过宿主机的iptables,这点和Linux Bridge有较大区别。在Linux Bridge模式下,只要指定内核参数net.bridge.bridge-nf-call-iptables = 1,所有进入bridge的流量都会通过宿主机的iptables。经过分析kube-proxy,我们发现可以使用KUBE-FORWARD这个chain来进行pod到service的网络转发: ...

May 23, 2019 · 2 min · jiezi

gradle多模块打jar上传本地仓库并给本地其他项目使用

1、前言本篇主要讲述:gradle多模块打jar包,上传本地仓库,并交由本地其他项目使用2、环境准备操作系统: mac osgradle版本:4.1.0开发软件:idea注:gradle版本不同,引入依赖方式可能不同,如果你发现本地导包是OK的,但是打包就报错,可以看看是不是gradle版本所引起的问题3、多模块gradle文档【gradle多环境讲解,官方文档】,官方文档描述了多模块配置中的几个闭包的常规使用allprojects{}, subprojects{}。前者配置应用包括root模块在内的所有模块,后者只应用子模块,详细的使用规则进入官方文档详细了解 4、项目实战项目1结构 |──root-project-one 项目1名称 ├── common-project 基础子项目 │ └── build.gradle 基础子项目配置文件 ├── example-project 依赖common项目的示例项目 │ └── build.gradle 依赖common项目的示例项目的配置文件 ├── build.gradle 项目1的配置文件项目2结构 |──root-project-two 项目2名称 ├── build.gradle 项目2的配置文件(需要引用项目1中的example-project)各个项目的配置文件(此处只列举主要配置) root-project-one/build.gradle //所有子项目共享配置subprojects { apply plugin: 'maven' // 获取本地仓库路径 def localRepositoryPath = 'file://' + new File(System.getProperty('user.home'), '.m2/repository').absolutePath //打包至本地仓库配置 uploadArchives { repositories { //mavenDeployer 需要依赖 apply plugin: 'maven' mavenDeployer { repository(url: localRepositoryPath) pom.project { name = project.name // 当前项目名称 version = project.version //当前项目版本 0.0.1 groupId = project.group // 当前项目组 com.xxx } } } }}common-project/build.gradle ...

May 18, 2019 · 1 min · jiezi

利用Packer自定义镜像创建容器集群

阿里云容器服务Kubernetes集群支持CentOS操作系统,在绝大多数情况下可以满足客户的要求。但是有些客户由于业务系统对操作系统依赖比较高,希望定制化一些操作系统参数,则可以用自定义镜像来创建Kubernetes集群。 创建自定义操作系统镜像有两种方式,一是在控制台上通过为一台ECS创建快照的方式创建镜像,注意一定要基于阿里云CentOS作为基础镜像,把对操作系统的定制化更新完打成镜像即可。但这种方式的不便之处在于,如果每次对操作系统镜像有更新,则都要手动操作一遍,很难自动化。而且如果是从已有的Kubernetes节点制作镜像,还需要把Docker,Kubelet等清理干净才能制作镜像,步骤繁琐且容易遗漏。 另外一种方式就是本文介绍的用Packer构建镜像。相关的参考文档:使用Packer创建自定义镜像。采用Packer构建镜像的好处是可以把构建方式自动化,构建所需的参数文件中包含了对干净的基础镜像所做的修改,一目了然,并且可以把配置进行版本化管理。后期需要构建新的镜像,只需改变配置重新执行一下Packer构建即可,非常方便,是在生产环境中使用自定义镜像的推荐方式。 那么有没有一个针对容器服务集群的Packer配置模版呢?容器服务团队开源的ack-image-builder就是一个这样的示例项目。下面我们就来一起动手实践一下。 安装Packer可以根据官方文档安装Packer https://www.packer.io/intro/getting-started/install.html 。 创建自定义镜像克隆ack-image-builder项目到本地,可以看到config和scripts目录下是一些示例定制化脚本,读者可以根据自己的需求更新改。 $ git clone https://github.com/AliyunContainerService/ack-image-builder.git$ cd ack-image-builderack-image-builder $ tree.├── LICENSE.txt├── README.md├── ack-centos.json├── config│   └── default.sh└── scripts ├── cleanUpKerneles.sh ├── reboot.sh ├── updateKernel.sh └── verify.sh2 directories, 8 files在ack-centos.json 可以配置在把生成好的自定义镜像存在哪个区(示例中为cn-hangzhou)。 { "variables": { "region": "cn-hangzhou", "image_name": "ack_test_image{{timestamp}}", "source_image": "centos_7_06_64_20G_alibase_20190218.vhd", ... },配置好阿里云账号的AK,然后执行构建命令。 export ALICLOUD_ACCESS_KEY=XXXexport ALICLOUD_SECRET_KEY=XXXpacker build ack-centos.json大约7-8分钟一个新的自定义镜像就构建成功了。可以进入ECS控制台查看新生成的镜像。 利用自定义镜像创建容器集群开通自定义镜像白名单 读者如果需要尝试自定义镜像能力,需要先开工单,申请在容器服务控制台上开通自定义镜像的白名单。 创建容器集群 白名单开通后进入容器服务控制台 https://cs.console.aliyun.com/#/k8s/cluster/list,创建Kubernetes集群。选择自定义镜像所在的区,在示例中是cn-hangzhou。 在创建集群的页面中点击"显示高级选项",会出现"自定义镜像"的选择界面: 如果在选择中找不到刚创建的镜像,请检查一下集群和自定义镜像是否在同一个Region。 选择了自定义镜像后点击创建集群即可完成一个自定义镜像集群的创建。 集群扩容与自动伸缩 使用自定义镜像创建集群后,集群的扩容与自动伸缩中所用的都是自定义镜像。 Terraform 中自定义镜像支持利用Terraform创建容器集群也可以使用自定义镜像,具体参数是: image_id - The ID of node image.相关链接如下: ...

May 16, 2019 · 1 min · jiezi

如何实现724小时灵活发布阿里技术团队这么做

阿里妹导读:研发效能分为两块,一是用技术的更新来提升效率;二是提高整个技术生态中的协同效率,激发技术活力。阿里巴巴技术团队在此基础上要实现的终极目标是打造7*24小时灵活发布的通道,以及提供更快的业务代码迭代能力。今天,阿里巴巴高级测试开发专家傲野为你带来关于研发效能的一些思考,希望对你有启发。7*24小时发布窗口的实现其实并不简单,受限于很多因素。我简单地进行了分解。 一、系统先从最基础的开始说,当一个创业团队只有几个人,一两个系统的情况下,是可以不考虑研发效率这回事的。因为不存在系统间的依赖,系统内的依赖也完全在一个可控的范围内,本地起一个 Tomcat 或 Apache 就能开发、调试。另外再加上团队成员之间的高频交流,基本上可以实现随时随地,想发就发的要求。 当业务逐渐复杂,开发人数扩展到10几个人时。提效的第一步是理清系统内的依赖关系,并促进角色的专业化。这也是大家所熟知的MVC,通过对视图、模型、控制器的分离,对系统内的逻辑进行分层。把复杂的代码逻辑下沉到Model层,而视图层交由更专业的前端来负责。 当然,在系统内部仍然有一些扩展的空间,比如模块化,为不同的业务划分bundle等。但仍然没有突破本身的瓶颈,而且单一的系统也很难突破机器的特性。 二、架构当技术团队已经达到几十个上百人的规模,当业务已经无法通过单一的应用来进行水平扩展时。分布式的架构是解决问题的有效手段。在07年时,阿里集团就在推进SOA化,无论是淘宝还是支付宝,原来的单一应用不断被拆分出来,也在此时,承载服务化中枢的消息等中间件蓬勃发展。 这种方式实现了系统之间的解藕,激活了技术人员的生产力,同时增大了系统的弹性,实现了服务能力的低成本水平扩展。但因为复杂的调用关系,对于某一个贯穿多个应用的项目来说,无疑增加了集成的成本和质量的风险。 同时,如果对应用规模不加以规划和控制的话,会导致应用数的不断扩张,从而影响到整体的开发维护成本。 三、配置管理在5 - 10年前,阿里是有一个专门的岗位叫SCM的,负责技术团队内的代码管理,配置项管理和应用部署。特别是在服务化初期,开发人员的coding生产力被极度释放,应用数出现一个井喷,对配置管理的需求不断增强,并最终促使了配置管理的变革。 在讲配置管理前,先讲讲代码分支管理机制。这也是很多研发模式变革的起点。在此,笔者先表达自己的观点:没有对与错(先进与落后)的代码分支管理机制,只有适不适合自己团队当下以及未来发展的管理模式。 先从大的层面上来说,我们当前所讨论的都是为了解决并行开发的问题,即有多个项目或团队对于同一系列应用进行功能开发。如果仅仅是串行开发,是基本不用太考虑代码管理策略。 1、分支开发、主干发布。核心理念是使用固定的主干作为集成分支。使用分支进行开发,在合并到主干分支后生命周期终止。当然除此之外,还有紧急发布分支等。 2、分支开发、分支发布。发布成功后执行写基线操作,确保主干的及时更新和稳定。同时分支发布的方式不依赖于大集成,保持很强的灵活性。 体现在项目上的流程为: 3、其他模式:主干开发、分支分布等。由于我们并不常用,所以略过。 平台化的支持:早期配管的人肉化,也造成了代码集成和部署的效率很低。不同角色之间的协同靠人来完成。因此在那个背景下,还需要一个配套的PMO组织来保障。在这样一个历史背景下,Aone(对外版本是云效)也孕育而生,从平台化的角度来解决研发过程的协同、构建、集成和测试几个复杂的过程。为了更清楚的了解那个时期的痛点,我找了2009年左右的Aone的蓝图,可以管中窥豹(这个时期我并没有亲自经历过,只是针对于当时的前辈做了些访谈和收集了一些资料)。我猜想也正因为这条道路面向未来解决问题造就了现在的Aone平台。 四、测试当一个技术团队小,负责应用少以及业务的用户群体少时,是完全可以不用测试的。只有当业务发展到一定阶段,用户对于质量的容忍程度越来越低时,才引入专业的测试角色。其次,在软件离线交付阶段,由于软件的召回成本很高,所以对于测试是不遗余力的,但随着在线交付时代的深入,测试团队是否能够快速的实现软件质量的评测反馈,成为一个非常关键的问题。而也决定着,在打通上述各个环节后,7*24小时软件持续交付通道是否能够真正实现。 在讲之前,我们再回顾一下上个章节。Aone平台实现了开发代码、配置、应用部署的在线化,现在只剩下最后的一环:测试。从2010年以来,B2B的测试团队就希望可以把分层自动化平台跟Aone研发协作平台绑定在一起,通过系统调用的方式来实现一个测试的快速验证机制,并最终实现回归测试过程中的无人值守。 这个意义非常重大。应用的服务化后,技术的风险实际上是收敛的,大家都可以面向服务来进行开发,实现高内聚、构耦合。并且应用的发布也更加灵活了。但对于测试来说,却是极大的挑战。 1、测试的层次增加了。 2、测试的轮次变多了。每次集成,每次发布就有可能是一次完整的测试回归。 就如Aone的推进间接取替了SCM这个角色一样。研发平台的快速发展和业务7*24小时发布的诉求,也开始冲击测试在代码集成后的快速反馈能力。这是一个挑战,也是一个机会。否则,前期释放出来的所有生产力,最后全都被卡在了测试这最后一个环节,而且没有办法拆解(每拆解出来一个,测试工作量就增加一倍)。只能通过不断叠加集成的应用量来提高集成测试的效率。 经过1688测试团队几代同学的努力,现在我们在这个方面总算有了些成绩。我们已经通过分层自动化体系实现了60%以上发布测试的无人值守,并且全年拦截故障在数百个级别(含页面、UI等)。 它的实现逻辑如下: 五、文化至此,真正所谓的7*24小时业务的持续交付通道已经完全打造出来。我们再回顾一下。 1、应用内的架构分层,前端、后端、测试各应其职,通过专业化的力量激发了一轮生产力。 2、服务化的架构,让技术人员可以面向服务来进行业务的开发,实现了架构上的高内聚低耦合。进一步释放大规模技术团队的活力。 3、研发平台的搭建,提供了持续交付管道,实现了开发、测试过程的快速、准确传递。 4、依托于研发平台,实现了环境的自动化部署,应用监控,代码检查。扫除了研发过程的基建设施。让技术人员聚焦于代码的生产。 5、测试自动化验证体系,减少系统集成风险,提高集成的频率。最终实现了代码的快速上线。 本文作者: 施翔阅读原文 本文来自云栖社区合作伙伴“阿里技术”,如需转载请联系原作者。

May 15, 2019 · 1 min · jiezi

WAFSLB负载不均衡案例分享

问题演变过程时间点1:高防+WAF+SLB+2台ECS时间点2:高防+WAF+SLB+4台ECS 问题描述在时间点1时,没有发现明显的负载不均衡的情况。在时间点2时,出现大部分请求都打到了其中一台ECS上。需要定位问题原因 问题梳理问题链路是SLB后端的ECS出现负载不均衡的请求,那么直接影响这个转发算法的,是WAF以及SLB。那么和高防没有关系了。 配置情况 SLB:TCP监听,WRR转发算法,开启会话保持WAF:无特殊配置,域名直接回源负载均衡IP问题点1:轮询算法+会话保持措施:尝试修改轮询算法为WLC,会话保持时间调短。然而这个优化措施效果并不明显,由于开启了会话保持,那原有负载不均衡的情况下,调整WRR算法到WLC的算法,没有实现预期的WLC。 但是从另外一个角度来说,如果源IP非常分散的场景下,即使有会话保持,理论上还是应该在经过一个较长的时间段之后,依然能够到达均衡。这里由于是使用WAF的回源地址进行访问,所以对负载均衡来说,客户端的公网IP地址是固定的,一直是固定的几个;从而调整WLC+会话保持的调整收效甚微。 问题点2:会话保持模板刷新问题措施:尝试关闭会话保持。 稍有成效:关闭会话保持后,经过一段时间的通信,4台ECS初步的开始均衡,但是到了一个固定值之后;没有继续均衡,一直保持着1:2的状态。 这里有2个知识点: 1、WLC算法的计数开始是从调整为这个算法的时间点开始的;那么如果历史开始就出现不均衡,那么开启后还是会不均衡的。2、由于WAF的回源地址与SLB的通信一直在,没有断过所以历史的会话保持的效果依然存在,已经会话保持的IP,依然会发给对应负载均衡的RS,导致不均衡。 推荐的解法为:使用负载均衡的权重功能,将连接数多的机器的权重调低,待4台机器的连接数基本均衡后,将RS的权重都调整为一致。 本文作者:枫凡阅读原文 本文为云栖社区原创内容,未经允许不得转载。

May 14, 2019 · 1 min · jiezi

阿里云Kubernetes服务上从零搭建GitLabJenkinsGitOps应用发布模型的实践全纪录

关于GitOps的介绍,可以参考 GitOps:Kubernetes多集群环境下的高效CICD实践 1. 在 容器服务控制台 创建kubernetes集群1.1 新建Kubernetes集群: 1.2 新建命名空间gitops 我们将会把gitlab和jenkins全部部署到此命名空间下 2. 创建GitLab应用 (可选项,可以对接已有GitLab环境)容器服务控制台上依次点击 市场 -> 应用目录 -> gitlab-ce : 在 参数 中设置externalUrl和gitlabRootPassword后选择gitops命名空间并创建应用,本次实践中 externalUrl 设置为 http://ls-gitlab.example.com/, 如果没有dns解析的话,可以在创建成功后直接使用ip 容器服务控制台上依次点击 路由与负载均衡 -> 服务 查看gitlab应用的访问地址,大约2分钟后可访问gitlab并登陆: 3. 设置GitLab并上传示例源码项目3.1 新建private group application 创建private group application: 3.2 新建并上传private project application-demo 创建private project application-demo, 示例源码地址: https://code.aliyun.com/haoshuwei/application-demo.git 从master新建一个分支latest: 设置master和latest分支只有管理员才能merge和push代码的操作: 3.3 新建private group builds 3.4 新建并上传private project preview-pipeline staging-pipeline production-pipeline preview-pipeline示例源码地址为: https://code.aliyun.com/haoshuwei/preview-pipeline.gitstaging-pipeline示例源码地址为: https://code.aliyun.com/haoshuwei/staging-pipeline.gitproduction-pipeline示例源码地址为: https://code.aliyun.com/haoshuwei/production-pipeline.git上传3个构建项目之前需要替换以下字段:IMAGE_REPO:  应用容器镜像要上传到哪个镜像仓库,镜像仓库地址dingTalkToken: 钉钉通知所使用的钉钉机器人accessTokenFetch Git Repo -> credentialsId : 用于Jenkins拉取git项目的证书名称,需要在Jenkins中创建名为gitlab的证书Fetch Git Repo -> url : Jenkins拉取git repo的url ...

May 9, 2019 · 2 min · jiezi

使用Kettle导入数据到ADB-for-PostgreSQL

摘要: 文章介绍了使用Kettle将数据导入到AnalyticDB for PostgreSQL,包括使用表输出方式(INSERT)和批量加载方式(COPY)导入到AnalyticDB for PostgreSQL的详细步骤和操作流程。Kettle简介Kettle(现也称为Pentaho Data Integration,简称PDI)是一款非常受欢迎的开源ETL工具软件,主要用于数据整合、转换和迁移。Kettle除了支持各种关系型数据库,HBase MongoDB这样的NoSQL数据源外,它还支持Excel、Access这类小型的数据源。并且通过这些插件扩展,kettle可以支持各类数据源。 下图显示了Kettle和ADB for PostgreSQL之间的关系,数据源通过Kettle进行ETL或数据集成操作以后可以和ADB for PostgreSQL进行交互: Kettle支持的数据来源非常丰富,主要包括以下分类: 表输入文本文件输入生成记录/自定义常量获取系统信息各类格式文件输入Json输入以及其他输入更详细的输入可以从界面中的“核心对象”的“输入”分类中查看。 Kettle支持的表输入来源自数据库连接中使用SQL语句获取,其中数据库连接支持非常丰富的连接方式,包括: Native(JDBC)连接ODBC连接OCI连接JNDI连接通过这些连接方式,可以支持连接大多数主流数据库,如Oracle, SQL Server, MySQL, DB2, PostgreSQL, Sybase, Teradata等等,更详细的连接信息可以参考官方文档:https://help.pentaho.com/Documentation/8.2/Setup/Configuration/Define_Data_Connections Kettle导入到ADB for PostgreSQLKettle支持导入到ADB for PostgreSQL的方式目前,Kettle支持的数据导入到ADB for PostgreSQL的方式有: 导入方式说明表输出(INSERT方式)采用JDBC作为导入方式 支持批量插入,批量插入使用JDBC的batch insert方法 批量加载(COPY方式)采用COPY作为导入方式 对于大表,COPY方式性能达到批量插入性能的10倍左右 表输出(INSERT方式)导入会流过Master节点并做解析之后分布到对应的Segment节点上,这种方式相对较慢并且不适合导入大量数据。批量加载(COPY方式)导入方式比INSERT语句插入多行的效率更高。 以下将分别介绍如何通过这两种方式将外部数据迁移到AnalyticDB for PostgresSQL。 准备工作使用Kettle将外部数据导入AnalyticDB for PostgresSQL之前,需要完成以下准备工作。 在本地主机中安装kettle在AnalyticDB for PostgreSQL中创建目标数据库、模式和表。表输出方式导入数据到ADB for PostgreSQLKettle采用表输出方式,支持使用通用的JDBC接口,从各种数据库源导入到ADB for PostgreSQL中。以下就以MySQL为例说明如何通过JDBC接口导入数据到ADB for PostgreSQL中。 1.在Kettle中新建一个转换。2.在转换中新建一个MySQL数据库连接作为输出源,详细的参数配置如下表所示。配置参数时,不要勾选Use Result Streaming Cursor。 配置项说明连接名称数据连名连接类型选择MySQL连接方式选择Native(JDBC)主机名MySQL的连接地址数据库名称MySQL的数据库名端口号连接地址对应的端口号用户名用户名密码用户密码3.完成上述参数配置后,单击测试测试连通性,测试通过后单击确认。4.在转换中新建一个Greenplum数据库连接作为输入源,详细的参数配置如下表所示。 配置项说明连接名称数据连名连接类型选择Greenplum连接方式选择Native(JDBC)主机名AnalyticDB for PostgreSQL的连接地址数据库名称AnalyticDB for PostgresSQL的数据库名端口号连接地址对应的端口号用户名用户名密码用户密码5.完成上述参数配置后,单击测试测试连通性,测试通过后单击确认。6.在kettle左侧核心对象的输入中,找到表输入,并将其拖动入到工作区。 ...

May 7, 2019 · 1 min · jiezi

XPack-Spark归档POLARDB数据做分析

简介POLARDB数据库是阿里云自研的下一代关系型云数据库,100%兼容MySQL,性能最高是MySQL的6倍,但是随着数据量不断增大,面临着单条SQL无法分析出结果的现状。X-Pack Spark为数据库提供分析引擎,旨在打造数据库闭环,借助X-Pack Spark可以将POLARDB数据归档至列式存储Parquet文件,一条SQL完成复杂数据分析,并将分析结果回流到业务库提供查询。本文主要介绍如何使用X-Pack Spark数据工作台对POLARDB数据归档。 业务架构业务需要对多张表出不同纬度,按天、按月的报表并对外提供查询服务;最大表当前500G,数据量还在不断的增加。尝试过spark直接通过jdbc去分析POLARDB,一方面比较慢,另外一方面每次扫全量的POLARDB数据,对在线业务有影响。基于以下几点考虑选择POLARDB+Spark的架构: 选择POLARDB按天增量归档到spark列存,每天增量数据量比较少,选择业务低峰期归档,对在线查询无影响选择Spark作为报表分析引擎,因为Spark很适合做ETL,且内置支持数据回流到POLARDB、MongoDB等多种在线库选择Spark离线数仓作为数据的中转站,对于分析的结果数据回流到在线库提供查询,能够一条Spark SQL完成分析,不需要按维度值拆分多条分析SQL 前置条件1. 设置Spark访问POLARDB白名单 Spark集群和POLARDB需在同一个VPC下才能访问,目前X-Pack Spark上还不支持一键关联POLARDB数据库,需要将Spark集群的IP加到POLARDB白名单中。后续将会开放一键关联POLARDB的功能。在“HBase控制台”->“集群列表”中找到分析Spark实例,在“数据库连接”栏中找到“VSwitch ID”交换机ID,如下图: 然后在“专有网络VPC控制台”->"交换机"搜索交换机实例ID,查询到IPV4网段。 将Spark集群网络加入到POLARDB白名单,进入“控制台”->“集群列表”找到所要关联的POLARDB实例,然后在“基本信息”->“访问信息”->“白名单”加入Spark集群所属网段。 2. 创建测试表 POLARDB中已经存在测试表,如果没有可登录POLARDB数据库创建测试表,下文也以该测试表为例。 CREATE TABLE IF NOT EXISTS test.us_population ( state CHAR(2) NOT NULL PRIMARY KEY, city VARCHAR(10), population INTEGER, dt TIMESTAMP );INSERT INTO test.us_population VALUES('NY','New York',8143197, CURRENT_DATE );INSERT INTO test.us_population VALUES('CA','Los Angeles',3844829, CURRENT_DATE);INSERT INTO test.us_population VALUES('IL','Chicago',2842518, '2019-04-13');INSERT INTO test.us_population VALUES('TX','Houston',2016582, '2019-04-14');INSERT INTO test.us_population VALUES('PA','Philadelphia',1463281, '2019-04-13');INSERT INTO test.us_population VALUES('AZ','Phoenix',1461575, '2019-04-15');INSERT INTO test.us_population VALUES('SA','San Antonio',1256509, CURRENT_DATE);INSERT INTO test.us_population VALUES('SD','San Diego',1255540, CURRENT_DATE);INSERT INTO test.us_population VALUES('DL','Dallas',1213825, '2019-04-15');INSERT INTO test.us_population VALUES('SJ','San Jose',912332,'2019-04-15');一、使用交互式工作台归档数据(调试、测试)创建Spark运行会话 ...

May 7, 2019 · 1 min · jiezi

Sentinel-成为-Spring-Cloud-官方推荐的主流熔断降级方案

近日,Sentinel 贡献的 spring-cloud-circuitbreaker-sentinel  模块正式被Spring Cloud社区合并至 Spring Cloud Circuit Breaker,由此,Sentinel 加入了 Spring Cloud Circuit Breaker 俱乐部,成为 Spring Cloud 官方的主流推荐选择之一。这意味着,Spring Cloud 微服务的开发者在熔断降级领域有了更多的选择,可以更方便地利用 Sentinel 来保障微服务的稳定性。 一、什么是 Spring Cloud Circuit Breaker?Spring Cloud Circuit Breaker是 Spring Cloud 官方的熔断器组件库,提供了一套统一的熔断器抽象API接口,允许开发者自由选择合适的熔断器实现。这个官方的熔断器组件库,截至目前,官方推荐的熔断器组件有: HystrixResilience4JSentinelSpring Retry当前,Spring Cloud Circuit Breaker 处于孵化阶段中,未来将合并到 Spring Cloud 主干版本正式发布。 Spring Cloud Circuit Breaker https://github.com/spring-cloud-incubator/spring-cloud-circuitbreaker 二、Sentinel 发展历程2012 年,Sentinel 诞生于阿里巴巴集团内部,主要功能为入口流量控制; 2013 - 2018 年,Sentinel 在阿里巴巴集团内部迅速发展,成为基础技术模块,覆盖了所有的核心场景。Sentinel 也因此积累了大量的流量控制场景以及生产实践; 2018年7月,阿里巴巴宣布限流降级框架组件 Sentinel 正式开源,在此之前,Sentinel 作为阿里巴巴“大中台、小前台”架构中的基础模块,已经覆盖了阿里的所有核心场景,因此积累了大量的流量归整场景以及生产实践; 2018年9月,Sentinel 发布 v0.2.0版本,释放异步调用支持、热点参数限流等多个重要特性; 2018年10月,Sentinel 发布首个 GA 版本 v1.3.0,该版本包括 Sentinel 控制台功能的完善和一些 bug 修复,以及其它的产品改进; ...

April 29, 2019 · 1 min · jiezi

使用DataWorks来调度AnalyticDB任务

DataWorks作为阿里云上广受欢迎的大数据开发调度服务,最近加入了对于AnalyticDB的支持,意味着所有的AnalyticDB客户可以获得任务开发、任务依赖关系管理、任务调度、任务运维等等全方位强大的能力,现在就给大家仔细介绍下如何使用DataWorks来调度AnalyticDB任务。 开通AnalyticDB进入阿里云分析型数据库 MySQL版产品详情页,点击免费试用。最近上线了15天免费试用活动,需要首先填写申请表单,审批通过后即可享受免费试用AnalyticDB活动。进入购买页面,选择好地域、可用区、ECU类型、ECU数量和数据库名,点击立即购买,稍等几分钟时间就可以开通AnalyticDB实例。 开通DataWorks开通完AnalyticDB服务后,紧接着要开通DataWorks。选择好region后点击下一步。 填写工作空间名称,注意模式要改成“标准模式”,创建工作空间。 AnalyticDB中表和数据准备为了演示如何在DataWorks上调度AnalyticDB的任务,我们后面会用到一些测试数据,这里我们用著名的TPCH的测试数据集中的ORDERS表, 数据已经提前存入表中。前面开通成功后,我们就可以在AnalyticDB中找到数据库,登陆数据库后,创建ORDERS表,如下: CREATE TABLE ads_dla_test.orders ( o_orderkey int COMMENT '', o_custkey int COMMENT '', o_orderstatus varchar COMMENT '', o_totalprice double COMMENT '', o_orderdate date COMMENT '', o_orderpriority varchar COMMENT '', o_clerk varchar COMMENT '', o_shippriority int COMMENT '', o_comment varchar COMMENT '', PRIMARY KEY (O_ORDERKEY,O_CUSTKEY))PARTITION BY HASH KEY (O_ORDERKEY) PARTITION NUM 32TABLEGROUP tpch_50x_groupOPTIONS (UPDATETYPE='realtime')COMMENT ''CREATE TABLE ads_dla_test.finished_orders ( o_orderkey int COMMENT '', o_totalprice double COMMENT '', PRIMARY KEY (O_ORDERKEY))PARTITION BY HASH KEY (O_ORDERKEY) PARTITION NUM 32TABLEGROUP tpch_50x_groupOPTIONS (UPDATETYPE='realtime')COMMENT ''CREATE TABLE ads_dla_test.high_value_finished_orders ( o_orderkey int COMMENT '', o_totalprice double COMMENT '', PRIMARY KEY (O_ORDERKEY))PARTITION BY HASH KEY (O_ORDERKEY) PARTITION NUM 32TABLEGROUP tpch_50x_groupOPTIONS (UPDATETYPE='realtime')COMMENT ''任务调度其中一个重要的功能是任务之间的依赖,为了演示这个功能,我们这里会在DataWorks里面创建两个AnalyticDB任务, 我们的表、任务之间的关系如下图: ...

April 22, 2019 · 1 min · jiezi

主流微服务注册中心浅析和对比

开源产品受开发者热捧,是因为其代码透明、可以参与共建、有社区进行交流和学习,当然更重要的是开源产品的接入成本低。个人开发者或者中小型公司往往会将开源产品作为选型首选。 开发者通过阅读源代码,理解产品的功能设计和架构设计,同时也可以通过本地部署来测试性能,随之而来的是对各类开源产品的对比,用以选型。不过当前关于微服务注册中心的对比,大多聚焦在功能上的对比,对架构或者性能的深入探讨,比较少见。 另一方面,作为默默支持的产品,服务注册中心往往隐藏在服务框架背后。优秀的服务框架往往会支持多种配置中心,但是注册中心的选择依然与服务框架强关联,普遍的情况是一种服务框架会带一个默认的服务注册中心。这样虽然免去了用户在选型上的烦恼,但是单个注册中心的局限性,导致用户使用多个服务框架时,必须部署多套完全不同的注册中心,这些注册中心之间的数据协同是一个问题。 本文来自Nacos社区,作者是 Nacos PMC 朱鹏飞,作者力求公正和客观的去看待主流微服务注册中心的各个维度。本文不仅仅包含常见服务注册中心产品的对比,也试图从Nacos的经验和调研中总结并阐述服务注册中心产品设计上应该去遵循和考虑的要点,文章篇幅较长,若您有不同的看法,欢迎在文末留言,或到Nacos @GitHub 提issue。 前言服务发现是一个古老的话题,当应用开始脱离单机运行和访问时,服务发现就诞生了。目前的网络架构是每个主机都有一个独立的IP地址,那么服务发现基本上都是通过某种方式获取到服务所部署的IP地址。DNS协议是最早将一个网络名称翻译为网络IP的协议,在最初的架构选型中,DNS+LVS+Nginx基本可以满足所有的RESTful服务的发现,此时服务的IP列表通常配置在Nginx或者LVS。后来出现了RPC服务,服务的上下线更加频繁,人们开始寻求一种能够支持动态上下线并且推送IP列表变化的注册中心产品。 ZooKeeper是一款经典的服务注册中心产品(虽然它最初的定位并不在于此),在很长一段时间里,它是国人在提起RPC服务注册中心时心里想到的唯一选择,这很大程度上与Dubbo在中国的普及程度有关。Consul和Eureka都出现于2014年,Consul在设计上把很多分布式服务治理上要用到的功能都包含在内,可以支持服务注册、健康检查、配置管理、Service Mesh等。而Eureka则借着微服务概念的流行,与SpringCloud生态的深度结合,也获取了大量的用户。去年开源的Nacos,则携带着阿里巴巴大规模服务生产经验,试图在服务注册和配置管理这个市场上,提供给用户一个新的选择。 当市面上有多种相似功能的产品出现时,人们往往希望知道这些产品相比较的优劣。产品本身的定位会决定它包含了哪些功能,而产品架构的设计,则会影响产品的性能和可用性等。开源产品的一个优势是开发人员可以去阅读源代码,理解产品的功能设计和架构设计,同时也可以通过本地部署来测试性能,随之而来的是各种产品的对比文章。不过当前关于注册中心的对比,往往停留在表面的功能对比上,对架构或者性能并没有非常深入的探讨。 另一个现象是服务注册中心往往隐藏在服务框架背后,作为默默支持的产品。优秀的服务框架往往会支持多种配置中心,但是注册中心的选择依然强关联与服务框架,一种普遍的情况是一种服务框架会带一个默认的服务注册中心。这样虽然免去了用户在选型上的烦恼,但是单个注册中心的局限性,导致用户使用多个服务框架时,必须部署多套完全不同的注册中心,这些注册中心之间的数据协同也是一个问题。 本文是一篇来自Nacos项目组的文章,虽然是来自Nacos,我们依然力求公正和客观的去看待服务发现所有产品的各个维度。本文不仅仅包含常见服务注册中心产品的对比,还试图从我们的经验和调研中总结和阐述服务注册中心产品设计上应该去遵循和考虑的要点。 数据模型注册中心的核心数据是服务的名字和它对应的网络地址,当服务注册了多个实例时,我们需要对不健康的实例进行过滤或者针对实例的一些特征进行流量的分配,那么就需要在实例上存储一些例如健康状态、权重等属性。随着服务规模的扩大,渐渐的又需要在整个服务级别设定一些权限规则、以及对所有实例都生效的一些开关,于是在服务级别又会设立一些属性。再往后,我们又发现单个服务的实例又会有划分为多个子集的需求,例如一个服务是多机房部署的,那么可能需要对每个机房的实例做不同的配置,这样又需要在服务和实例之间再设定一个数据级别。 Zookeeper没有针对服务发现设计数据模型,它的数据是以一种更加抽象的树形K-V组织的,因此理论上可以存储任何语义的数据。而Eureka或者Consul都是做到了实例级别的数据扩展,这可以满足大部分的场景,不过无法满足大规模和多环境的服务数据存储。Nacos在经过内部多年生产经验后提炼出的数据模型,则是一种服务-集群-实例的三层模型。如上文所说,这样基本可以满足服务在所有场景下的数据存储和管理。 Nacos的数据模型虽然相对复杂,但是它并不强制你使用它里面的所有数据,在大多数场景下,你可以选择忽略这些数据属性,此时可以降维成和Eureka和Consul一样的数据模型。 另外一个需要考虑的是数据的隔离模型,作为一个共享服务型的组件,需要能够在多个用户或者业务方使用的情况下,保证数据的隔离和安全,这在稍微大一点的业务场景中非常常见。另一方面服务注册中心往往会支持云上部署,此时就要求服务注册中心的数据模型能够适配云上的通用模型。Zookeeper、Consul和Eureka在开源层面都没有很明确的针对服务隔离的模型,Nacos则在一开始就考虑到如何让用户能够以多种维度进行数据隔离,同时能够平滑的迁移到阿里云上对应的商业化产品。 Nacos提供了四层的数据逻辑隔离模型,用户账号对应的可能是一个企业或者独立的个体,这个数据一般情况下不会透传到服务注册中心。一个用户账号可以新建多个命名空间,每个命名空间对应一个客户端实例,这个命名空间对应的注册中心物理集群是可以根据规则进行路由的,这样可以让注册中心内部的升级和迁移对用户是无感知的,同时可以根据用户的级别,为用户提供不同服务级别的物理集群。再往下是服务分组和服务名组成的二维服务标识,可以满足接口级别的服务隔离。 Nacos 1.0.0介绍的另外一个新特性是:临时实例和持久化实例。在定义上区分临时实例和持久化实例的关键是健康检查的方式。临时实例使用客户端上报模式,而持久化实例使用服务端反向探测模式。临时实例需要能够自动摘除不健康实例,而且无需持久化存储实例,那么这种实例就适用于类Gossip的协议。右边的持久化实例使用服务端探测的健康检查方式,因为客户端不会上报心跳,那么自然就不能去自动摘除下线的实例。 在大中型的公司里,这两种类型的服务往往都有。一些基础的组件例如数据库、缓存等,这些往往不能上报心跳,这种类型的服务在注册时,就需要作为持久化实例注册。而上层的业务服务,例如微服务或者Dubbo服务,服务的Provider端支持添加汇报心跳的逻辑,此时就可以使用动态服务的注册方式。 数据一致性数据一致性是分布式系统永恒的话题,Paxos协议的艰深更让数据一致性成为程序员大牛们吹水的常见话题。不过从协议层面上看,一致性的选型已经很长时间没有新的成员加入了。目前来看基本可以归为两家:一种是基于Leader的非对等部署的单点写一致性,一种是对等部署的多写一致性。当我们选用服务注册中心的时候,并没有一种协议能够覆盖所有场景,例如当注册的服务节点不会定时发送心跳到注册中心时,强一致协议看起来是唯一的选择,因为无法通过心跳来进行数据的补偿注册,第一次注册就必须保证数据不会丢失。而当客户端会定时发送心跳来汇报健康状态时,第一次的注册的成功率并不是非常关键(当然也很关键,只是相对来说我们容忍数据的少量写失败),因为后续还可以通过心跳再把数据补偿上来,此时Paxos协议的单点瓶颈就会不太划算了,这也是Eureka为什么不采用Paxos协议而采用自定义的Renew机制的原因。 这两种数据一致性协议有各自的使用场景,对服务注册的需求不同,就会导致使用不同的协议。在这里可以发现,Zookeeper在Dubbo体系下表现出的行为,其实采用Eureka的Renew机制更加合适,因为Dubbo服务往Zookeeper注册的就是临时节点,需要定时发心跳到Zookeeper来续约节点,并允许服务下线时,将Zookeeper上相应的节点摘除。Zookeeper使用ZAB协议虽然保证了数据的强一致,但是它的机房容灾能力的缺乏,无法适应一些大型场景。 Nacos因为要支持多种服务类型的注册,并能够具有机房容灾、集群扩展等必不可少的能力,在1.0.0正式支持AP和CP两种一致性协议并存。1.0.0重构了数据的读写和同步逻辑,将与业务相关的CRUD与底层的一致性同步逻辑进行了分层隔离。然后将业务的读写(主要是写,因为读会直接使用业务层的缓存)抽象为Nacos定义的数据类型,调用一致性服务进行数据同步。在决定使用CP还是AP一致性时,使用一个代理,通过可控制的规则进行转发。 目前的一致性协议实现,一个是基于简化的Raft的CP一致性,一个是基于自研协议Distro的AP一致性。Raft协议不必多言,基于Leader进行写入,其CP也并不是严格的,只是能保证一半所见一致,以及数据的丢失概率较小。Distro协议则是参考了内部ConfigServer和开源Eureka,在不借助第三方存储的情况下,实现基本大同小异。Distro重点是做了一些逻辑的优化和性能的调优。 负载均衡负载均衡严格的来说,并不算是传统注册中心的功能。一般来说服务发现的完整流程应该是先从注册中心获取到服务的实例列表,然后再根据自身的需求,来选择其中的部分实例或者按照一定的流量分配机制来访问不同的服务提供者,因此注册中心本身一般不限定服务消费者的访问策略。Eureka、Zookeeper包括Consul,本身都没有去实现可配置及可扩展的负载均衡机制,Eureka的负载均衡是由ribbon来完成的,而Consul则是由Fabio做负载均衡。 在阿里巴巴集团内部,却是使用的相反的思路。服务消费者往往并不关心所访问的服务提供者的负载均衡,它们只关心以最高效和正确的访问服务提供者的服务。而服务提供者,则非常关注自身被访问的流量的调配,这其中的第一个原因是,阿里巴巴集团内部服务访问流量巨大,稍有不慎就会导致流量异常压垮服务提供者的服务。因此服务提供者需要能够完全掌控服务的流量调配,并可以动态调整。 服务端的负载均衡,给服务提供者更强的流量控制权,但是无法满足不同的消费者希望使用不同负载均衡策略的需求。而不同负载均衡策略的场景,确实是存在的。而客户端的负载均衡则提供了这种灵活性,并对用户扩展提供更加友好的支持。但是客户端负载均衡策略如果配置不当,可能会导致服务提供者出现热点,或者压根就拿不到任何服务提供者。 抛开负载均衡到底是在服务提供者实现还是在服务消费者实现,我们看到目前的负载均衡有基于权重、服务提供者负载、响应时间、标签等策略。其中Ribbon设计的客户端负载均衡机制,主要是选择合适现有的IRule、ServerListFilter等接口实现,或者自己继承这些接口,实现自己的过滤逻辑。这里Ribbon采用的是两步负载均衡,第一步是先过滤掉不会采用的服务提供者实例,第二步是在过滤后的服务提供者实例里,实施负载均衡策略。Ribbon内置的几种负载均衡策略功能还是比较强大的,同时又因为允许用户去扩展,这可以说是一种比较好的设计。 基于标签的负载均衡策略可以做到非常灵活,Kubernetes和Fabio都已经将标签运用到了对资源的过滤中,使用标签几乎可以实现任意比例和权重的服务流量调配。但是标签本身需要单独的存储以及读写功能,不管是放在注册中心本身或者对接第三方的CMDB。 在Nacos 0.7.0版本中,我们除了提供基于健康检查和权重的负载均衡方式外,还新提供了基于第三方CMDB的标签负载均衡器,具体可以参考CMDB功能介绍文章。使用基于标签的负载均衡器,目前可以实现同标签优先访问的流量调度策略,实际的应用场景中,可以用来实现服务的就近访问,当您的服务部署在多个地域时,这非常有用。使用这个标签负载均衡器,可以支持非常多的场景,这不是本文要详细介绍的。虽然目前Nacos里支持的标签表达式并不丰富,不过我们会逐步扩展它支持的语法。除此以外,Nacos定义了Selector,作为负载均衡的统一抽象。关于Selector,由于篇幅关系,我们会有单独的文章进行介绍。 理想的负载均衡实现应该是什么样的呢?不同的人会有不同的答案。Nacos试图做的是将服务端负载均衡与客户端负载均衡通过某种机制结合起来,提供用户扩展性,并给予用户充分的自主选择权和轻便的使用方式。负载均衡是一个很大的话题,当我们在关注注册中心提供的负载均衡策略时,需要注意该注册中心是否有我需要的负载均衡方式,使用方式是否复杂。如果没有,那么是否允许我方便的扩展来实现我需求的负载均衡策略。 健康检查Zookeeper和Eureka都实现了一种TTL的机制,就是如果客户端在一定时间内没有向注册中心发送心跳,则会将这个客户端摘除。Eureka做的更好的一点在于它允许在注册服务的时候,自定义检查自身状态的健康检查方法。这在服务实例能够保持心跳上报的场景下,是一种比较好的体验,在Dubbo和SpringCloud这两大体系内,也被培养成用户心智上的默认行为。Nacos也支持这种TTL机制,不过这与ConfigServer在阿里巴巴内部的机制又有一些区别。Nacos目前支持临时实例使用心跳上报方式维持活性,发送心跳的周期默认是5秒,Nacos服务端会在15秒没收到心跳后将实例设置为不健康,在30秒没收到心跳时将这个临时实例摘除。 不过正如前文所说,有一些服务无法上报心跳,但是可以提供一个检测接口,由外部去探测。这样的服务也是广泛存在的,而且以我们的经验,这些服务对服务发现和负载均衡的需求同样强烈。服务端健康检查最常见的方式是TCP端口探测和HTTP接口返回码探测,这两种探测方式因为其协议的通用性可以支持绝大多数的健康检查场景。在其他一些特殊的场景中,可能还需要执行特殊的接口才能判断服务是否可用。例如部署了数据库的主备,数据库的主备可能会在某些情况下切换,需要通过服务名对外提供访问,保证当前访问的库是主库。此时的健康检查接口,可能就是一个检查数据库是否是主库的MYSQL命令了。 客户端健康检查和服务端健康检查有一些不同的关注点。客户端健康检查主要关注客户端上报心跳的方式、服务端摘除不健康客户端的机制。而服务端健康检查,则关注探测客户端的方式、灵敏度及设置客户端健康状态的机制。从实现复杂性来说,服务端探测肯定是要更加复杂的,因为需要服务端根据注册服务配置的健康检查方式,去执行相应的接口,判断相应的返回结果,并做好重试机制和线程池的管理。这与客户端探测,只需要等待心跳,然后刷新TTL是不一样的。同时服务端健康检查无法摘除不健康实例,这意味着只要注册过的服务实例,如果不调用接口主动注销,这些服务实例都需要去维持健康检查的探测任务,而客户端则可以随时摘除不健康实例,减轻服务端的压力。 Nacos既支持客户端的健康检查,也支持服务端的健康检查,同一个服务可以切换健康检查模式。我们认为这种健康检查方式的多样性非常重要,这样可以支持各种类型的服务,让这些服务都可以使用到Nacos的负载均衡能力。Nacos下一步要做的是实现健康检查方式的用户扩展机制,不管是服务端探测还是客户端探测。这样可以支持用户传入一条业务语义的请求,然后由Nacos去执行,做到健康检查的定制。 性能与容量虽然大部分用户用到的性能不高,但是他们仍然希望选用的产品的性能越高越好。影响读写性能的因素很多:一致性协议、机器的配置、集群的规模、存量数据的规模、数据结构及读写逻辑的设计等等。在服务发现的场景中,我们认为读写性能都是非常关键的,但是并非性能越高就越好,因为追求性能往往需要其他方面做出牺牲。Zookeeper在写性能上似乎能达到上万的TPS,这得益于Zookeeper精巧的设计,不过这显然是因为有一系列的前提存在。首先Zookeeper的写逻辑就是进行K-V的写入,内部没有聚合;其次Zookeeper舍弃了服务发现的基本功能如健康检查、友好的查询接口,它在支持这些功能的时候,显然需要增加一些逻辑,甚至弃用现有的数据结构;最后,Paxos协议本身就限制了Zookeeper集群的规模,3、5个节点是不能应对大规模的服务订阅和查询的。 在对容量的评估时,不仅要针对企业现有的服务规模进行评估,也要对未来3到5年的扩展规模进行预测。阿里巴巴的中间件在内部支撑着集团百万级别服务实例,在容量上遇到的挑战可以说不会小于任何互联网公司。这个容量不仅仅意味着整体注册的实例数,也同时包含单个服务的实例数、整体的订阅者的数目以及查询的QPS等。Nacos在内部淘汰Zookeeper和Eureka的过程中,容量是一个非常重要的因素。 Zookeeper的容量,从存储节点数来说,可以达到百万级别。不过如上面所说,这并不代表容量的全部,当大量的实例上下线时,Zookeeper的表现并不稳定,同时在推送机制上的缺陷,会引起客户端的资源占用上升,从而性能急剧下降。 Eureka在服务实例规模在5000左右的时候,就已经出现服务不可用的问题,甚至在压测的过程中,如果并发的线程数过高,就会造成Eureka crash。不过如果服务规模在1000上下,几乎目前所有的注册中心都可以满足。毕竟我们看到Eureka作为SpringCloud的注册中心,在国内也没有看到很广泛的对于容量或者性能的问题报告。 Nacos在开源版本中,服务实例注册的支撑量约为100万,服务的数量可以达到10万以上。在实际的部署环境中,这个数字还会因为机器、网络的配置与JVM参数的不同,可能会有所差别。图9展示了Nacos在使用1.0.0版本进行压力测试后的结果总结,针对容量、并发、扩展性和延时等进行了测试和统计。 完整的测试报告可以参考Nacos官网:https://nacos.io/en-us/docs/nacos-naming-benchmark.htmlhttps://nacos.io/en-us/docs/nacos-config-benchmark.html 易用性易用性也是用户比较关注的一块内容。产品虽然可以在功能特性或者性能上做到非常先进,但是如果用户的使用成本极高,也会让用户望而却步。易用性包括多方面的工作,例如API和客户端的接入是否简单,文档是否齐全易懂,控制台界面是否完善等。对于开源产品来说,还有一块是社区是否活跃。在比较Nacos、Eureka和Zookeeper在易用性上的表现时,我们诚邀社区的用户进行全方位的反馈,因为毕竟在阿里巴巴集团内部,我们对Eureka、Zookeeper的使用场景是有限的。从我们使用的经验和调研来看,Zookeeper的易用性是比较差的,Zookeeper的客户端使用比较复杂,没有针对服务发现的模型设计以及相应的API封装,需要依赖方自己处理。对多语言的支持也不太好,同时没有比较好用的控制台进行运维管理。 Eureka和Nacos相比较Zookeeper而言,已经改善很多,这两个产品有针对服务注册与发现的客户端,也有基于SpringCloud体系的starter,帮助用户以非常低的成本无感知的做到服务注册与发现。同时还暴露标准的HTTP接口,支持多语言和跨平台访问。Eureka和Nacos都提供官方的控制台来查询服务注册情况。不过随着Eureka 2.0宣布停止开发,Eureka在针对用户使用上的优化后续应该不会再有比较大的投入,而Nacos目前依然在建设中,除了目前支持的易用性特性以外,后续还会继续增强控制台的能力,增加控制台登录和权限的管控,监控体系和Metrics的暴露,持续通过官网等渠道完善使用文档,多语言SDK的开发等。 从社区活跃度的角度来看,目前由于Zookeeper和Eureka的存量用户较多,很多教程以及问题排查都可以在社区搜索到,这方面新开源的Nacos还需要随着时间继续沉淀。 集群扩展性集群扩展性和集群容量以及读写性能关系紧密。当使用一个比较小的集群规模就可以支撑远高于现有数量的服务注册及访问时,集群的扩展能力暂时就不会那么重要。从协议的层面上来说,Zookeeper使用的ZAB协议,由于是单点写,在集群扩展性上不具备优势。Eureka在协议上来说理论上可以扩展到很大规模,因为都是点对点的数据同步,但是从我们对Eureka的运维经验来看,Eureka集群在扩容之后,性能上有很大问题。 集群扩展性的另一个方面是多地域部署和容灾的支持。当讲究集群的高可用和稳定性以及网络上的跨地域延迟要求能够在每个地域都部署集群的时候,我们现有的方案有多机房容灾、异地多活、多数据中心等。 首先是双机房容灾,基于Leader写的协议不做改造是无法支持的,这意味着Zookeeper不能在没有人工干预的情况下做到双机房容灾。在单机房断网情况下,使机房内服务可用并不难,难的是如何在断网恢复后做数据聚合,Zookeeper的单点写模式就会有断网恢复后的数据对账问题。Eureka的部署模式天然支持多机房容灾,因为Eureka采用的是纯临时实例的注册模式:不持久化、所有数据都可以通过客户端心跳上报进行补偿。上面说到,临时实例和持久化实例都有它的应用场景,为了能够兼容这两种场景,Nacos支持两种模式的部署,一种是和Eureka一样的AP协议的部署,这种模式只支持临时实例,可以完美替代当前的Zookeeper、Eureka,并支持机房容灾。另一种是支持持久化实例的CP模式,这种情况下不支持双机房容灾。 在谈到异地多活时,很巧的是,很多业务组件的异地多活正是依靠服务注册中心和配置中心来实现的,这其中包含流量的调度和集群的访问规则的修改等。机房容灾是异地多活的一部分,但是要让业务能够在访问服务注册中心时,动态调整访问的集群节点,这需要第三方的组件来做路由。异地多活往往是一个包含所有产品线的总体方案,很难说单个产品是否支持异地多活。 多数据中心其实也算是异地多活的一部分。从单个产品的维度上,Zookeeper和Eureka没有给出官方的多数据中心方案。Nacos基于阿里巴巴内部的使用经验,提供的解决方案是才有Nacos-Sync组件来做数据中心之间的数据同步,这意味着每个数据中心的Nacos集群都会有多个数据中心的全量数据。Nacos-Sync是Nacos生态组件里的重要一环,不仅会承担Nacos集群与Nacos集群之间的数据同步,也会承担Nacos集群与Eureka、Zookeeper、Kubernetes及Consul之间的数据同步。 ...

April 22, 2019 · 1 min · jiezi

干货|Spring Cloud Bus 消息总线介绍

继上一篇 干货|Spring Cloud Stream 体系及原理介绍 之后,本期我们来了解下 Spring Cloud 体系中的另外一个组件 Spring Cloud Bus (建议先熟悉 Spring Cloud Stream,不然无法理解 Spring Cloud Bus 内部的代码)。Spring Cloud Bus 对自己的定位是 Spring Cloud 体系内的消息总线,使用 message broker 来连接分布式系统的所有节点。Bus 官方的 Reference 文档 比较简单,简单到连一张图都没有。这是最新版的 Spring Cloud Bus 代码结构(代码量比较少):Bus 实例演示在分析 Bus 的实现之前,我们先来看两个使用 Spring Cloud Bus 的简单例子。所有节点的配置新增Bus 的例子比较简单,因为 Bus 的 AutoConfiguration 层都有了默认的配置,只需要引入消息中间件对应的 Spring Cloud Stream 以及 Spring Cloud Bus 依赖即可,之后所有启动的应用都会使用同一个 Topic 进行消息的接收和发送。Bus 对应的 Demo 已经放到了 github 上: https://github.com/fangjian0423/rocketmq-binder-demo/tree/master/rocketmq-bus-demo 。 该 Demo 会模拟启动 5 个节点,只需要对其中任意的一个实例新增配置项,所有节点都会新增该配置项。访问任意节点提供的 Controller 提供的获取配置的地址(key为hangzhou):curl -X GET ‘http://localhost:10001/bus/env?key=hangzhou’所有节点返回的结果都是 unknown,因为所有节点的配置中没有 hangzhou 这个 key。Bus 内部提供了 EnvironmentBusEndpoint 这个 Endpoint 通过 message broker 用来新增/更新配置。访问任意节点该 Endpoint 对应的 url /actuator/bus-env?name=hangzhou&value=alibaba 进行配置项的新增(比如访问 node1 的url):curl -X POST ‘http://localhost:10001/actuator/bus-env?name=hangzhou&value=alibaba’ -H ‘content-type: application/json’然后再次访问所有节点 /bus/env 获取配置:$ curl -X GET ‘http://localhost:10001/bus/env?key=hangzhou’unknown%~ ⌚$ curl -X GET ‘http://localhost:10002/bus/env?key=hangzhou’unknown%~ ⌚$ curl -X GET ‘http://localhost:10003/bus/env?key=hangzhou’unknown%~ ⌚$ curl -X GET ‘http://localhost:10004/bus/env?key=hangzhou’unknown%~ ⌚$ curl -X GET ‘http://localhost:10005/bus/env?key=hangzhou’unknown%~ ⌚$ curl -X POST ‘http://localhost:10001/actuator/bus-env?name=hangzhou&value=alibaba’ -H ‘content-type: application/json’~ ⌚$ curl -X GET ‘http://localhost:10005/bus/env?key=hangzhou’alibaba%~ ⌚$ curl -X GET ‘http://localhost:10004/bus/env?key=hangzhou’alibaba%~ ⌚$ curl -X GET ‘http://localhost:10003/bus/env?key=hangzhou’alibaba%~ ⌚$ curl -X GET ‘http://localhost:10002/bus/env?key=hangzhou’alibaba%~ ⌚$ curl -X GET ‘http://localhost:10001/bus/env?key=hangzhou’alibaba%可以看到,所有节点都新增了一个 key 为 hangzhou 的配置,且对应的 value 是 alibaba。这个配置项是通过 Bus 提供的 EnvironmentBusEndpoint 完成的。这里引用 程序猿DD 画的一张图片,Spring Cloud Config 配合 Bus 完成所有节点配置的刷新来描述之前的实例(本文实例不是刷新,而是新增配置,但是流程是一样的):部分节点的配置修改比如在 node1 上指定 destination 为 rocketmq-bus-node2 (node2 配置了 spring.cloud.bus.id 为 rocketmq-bus-node2:10002,可以匹配上) 进行配置的修改:curl -X POST ‘http://localhost:10001/actuator/bus-env/rocketmq-bus-node2?name=hangzhou&value=xihu’ -H ‘content-type: application/json’访问 /bus/env 获取配置(由于在 node1 上发送消息,Bus 也会对发送方的节点 node1 进行配置修改):~ ⌚$ curl -X POST ‘http://localhost:10001/actuator/bus-env/rocketmq-bus-node2?name=hangzhou&value=xihu’ -H ‘content-type: application/json’~ ⌚$ curl -X GET ‘http://localhost:10005/bus/env?key=hangzhou’alibaba%~ ⌚$ curl -X GET ‘http://localhost:10004/bus/env?key=hangzhou’alibaba%~ ⌚$ curl -X GET ‘http://localhost:10003/bus/env?key=hangzhou’alibaba%~ ⌚$ curl -X GET ‘http://localhost:10002/bus/env?key=hangzhou’xihu%~ ⌚$ curl -X GET ‘http://localhost:10001/bus/env?key=hangzhou’xihu%可以看到,只有 node1 和 node2 修改了配置,其余的 3 个节点配置未改变。Bus 的实现Bus 概念介绍事件Bus 中定义了远程事件 RemoteApplicationEvent,该事件继承了 Spring 的事件 ApplicationEvent,而且它目前有 4 个具体的实现:EnvironmentChangeRemoteApplicationEvent: 远程环境变更事件。主要用于接收一个 Map<String, String> 类型的数据并更新到 Spring 上下文中 Environment 中的事件。文中的实例就是使用这个事件并配合 EnvironmentBusEndpoint 和 EnvironmentChangeListener 完成的。AckRemoteApplicationEvent: 远程确认事件。Bus 内部成功接收到远程事件后会发送回 AckRemoteApplicationEvent 确认事件进行确认。RefreshRemoteApplicationEvent: 远程配置刷新事件。配合 @RefreshScope 以及所有的 @ConfigurationProperties 注解修饰的配置类的动态刷新。UnknownRemoteApplicationEvent:远程未知事件。Bus 内部消息体进行转换远程事件的时候如果发生异常会统一包装成该事件。Bus 内部还存在一个非 RemoteApplicationEvent 事件 - SentApplicationEvent 消息发送事件,配合 Trace 进行远程消息发送的记录。这些事件会配合 ApplicationListener 进行操作,比如 EnvironmentChangeRemoteApplicationEvent 配了 EnvironmentChangeListener 进行配置的新增/修改:public class EnvironmentChangeListener implements ApplicationListener<EnvironmentChangeRemoteApplicationEvent> { private static Log log = LogFactory.getLog(EnvironmentChangeListener.class); @Autowired private EnvironmentManager env; @Override public void onApplicationEvent(EnvironmentChangeRemoteApplicationEvent event) { Map<String, String> values = event.getValues(); log.info(“Received remote environment change request. Keys/values to update " + values); for (Map.Entry<String, String> entry : values.entrySet()) { env.setProperty(entry.getKey(), entry.getValue()); } }}收到其它节点发送来的 EnvironmentChangeRemoteApplicationEvent 事件之后调用 EnvironmentManager#setProperty 进行配置的设置,该方法内部针对每一个配置项都会发送一个 EnvironmentChangeEvent 事件,然后被 ConfigurationPropertiesRebinder 所监听,进行 rebind 操作新增/更新配置。Actuator EndpointBus 内部暴露了 2 个 Endpoint,分别是 EnvironmentBusEndpoint 和 RefreshBusEndpoint,进行配置的新增/修改以及全局配置刷新。它们对应的 Endpoint id 即 url 是 bus-env 和 bus-refresh。配置Bus 对于消息的发送必定涉及到 Topic、Group 之类的信息,这些内容都被封装到了 BusProperties 中,其默认的配置前缀为 spring.cloud.bus,比如:spring.cloud.bus.refresh.enabled 用于开启/关闭全局刷新的 Listener。spring.cloud.bus.env.enabled 用于开启/关闭配置新增/修改的 Endpoint。spring.cloud.bus.ack.enabled 用于开启开启/关闭 AckRemoteApplicationEvent 事件的发送。spring.cloud.bus.trace.enabled 用于开启/关闭消息记录 Trace 的 Listener。消息发送涉及到的 Topic 默认用的是 springCloudBus,可以配置进行修改,Group 可以设置成广播模式或使用 UUID 配合 offset 为 lastest 的模式。每个 Bus 应用都有一个对应的 Bus id,官方取值方式较复杂:${vcap.application.name:${spring.application.name:application}}:${vcap.application.instance_index:${spring.application.index:${local.server.port:${server.port:0}}}}:${vcap.application.instance_id:${random.value}}建议手动配置 Bus id,因为 Bus 远程事件中的 destination 会根据 Bus id 进行匹配:spring.cloud.bus.id=${spring.application.name}-${server.port}Bus 底层分析Bus 的底层分析无非牵扯到这几个方面:消息是如何发送的;消息是如何接收的;destination 是如何匹配的;远程事件收到后如何触发下一个 action;BusAutoConfiguration 自动化配置类被 @EnableBinding(SpringCloudBusClient.class) 所修饰。@EnableBinding 的用法在上期文章 干货|Spring Cloud Stream 体系及原理介绍 中已经说明,且它的 value 为 SpringCloudBusClient.class,会在 SpringCloudBusClient 中基于代理创建出 input 和 output 的 DirectChannel:public interface SpringCloudBusClient { String INPUT = “springCloudBusInput”; String OUTPUT = “springCloudBusOutput”; @Output(SpringCloudBusClient.OUTPUT) MessageChannel springCloudBusOutput(); @Input(SpringCloudBusClient.INPUT) SubscribableChannel springCloudBusInput();}springCloudBusInput 和 springCloudBusOutput 这两个 Binding 的属性可以通过配置文件进行修改(比如修改 topic):spring.cloud.stream.bindings: springCloudBusInput: destination: my-bus-topic springCloudBusOutput: destination: my-bus-topic消息的接收的发送:// BusAutoConfiguration@EventListener(classes = RemoteApplicationEvent.class) // 1public void acceptLocal(RemoteApplicationEvent event) { if (this.serviceMatcher.isFromSelf(event) && !(event instanceof AckRemoteApplicationEvent)) { // 2 this.cloudBusOutboundChannel.send(MessageBuilder.withPayload(event).build()); // 3 }}@StreamListener(SpringCloudBusClient.INPUT) // 4public void acceptRemote(RemoteApplicationEvent event) { if (event instanceof AckRemoteApplicationEvent) { if (this.bus.getTrace().isEnabled() && !this.serviceMatcher.isFromSelf(event) && this.applicationEventPublisher != null) { // 5 this.applicationEventPublisher.publishEvent(event); } // If it’s an ACK we are finished processing at this point return; } if (this.serviceMatcher.isForSelf(event) && this.applicationEventPublisher != null) { // 6 if (!this.serviceMatcher.isFromSelf(event)) { // 7 this.applicationEventPublisher.publishEvent(event); } if (this.bus.getAck().isEnabled()) { // 8 AckRemoteApplicationEvent ack = new AckRemoteApplicationEvent(this, this.serviceMatcher.getServiceId(), this.bus.getAck().getDestinationService(), event.getDestinationService(), event.getId(), event.getClass()); this.cloudBusOutboundChannel .send(MessageBuilder.withPayload(ack).build()); this.applicationEventPublisher.publishEvent(ack); } } if (this.bus.getTrace().isEnabled() && this.applicationEventPublisher != null) { // 9 // We are set to register sent events so publish it for local consumption, // irrespective of the origin this.applicationEventPublisher.publishEvent(new SentApplicationEvent(this, event.getOriginService(), event.getDestinationService(), event.getId(), event.getClass())); }}利用 Spring 事件的监听机制监听本地所有的 RemoteApplicationEvent 远程事件(比如 bus-env 会在本地发送 EnvironmentChangeRemoteApplicationEvent 事件,bus-refresh 会在本地发送 RefreshRemoteApplicationEvent 事件,这些事件在这里都会被监听到)。判断本地接收到的事件不是 AckRemoteApplicationEvent 远程确认事件(不然会死循环,一直接收消息,发送消息…)以及该事件是应用自身发送出去的(事件发送方是应用自身),如果都满足执行步骤 3。构造 Message 并将该远程事件作为 payload,然后使用 Spring Cloud Stream 构造的 Binding name 为 springCloudBusOutput 的 MessageChannel 将消息发送到 broker。@StreamListener 注解消费 Spring Cloud Stream 构造的 Binding name 为 springCloudBusInput 的 MessageChannel,接收的消息为远程消息。如果该远程事件是 AckRemoteApplicationEvent 远程确认事件并且应用开启了消息追踪 trace 开关,同时该远程事件不是应用自身发送的(事件发送方不是应用自身,表示事件是其它应用发送过来的),那么本地发送 AckRemoteApplicationEvent 远程确认事件表示应用确认收到了其它应用发送过来的远程事件,流程结束。如果该远程事件是其它应用发送给应用自身的(事件的接收方是应用自身),那么进行步骤 7 和 8,否则执行步骤 9。该远程事件不是应用自身发送(事件发送方不是应用自身)的话,将该事件以本地的方式发送出去。应用自身一开始已经在本地被对应的消息接收方处理了,无需再次发送。如果开启了 AckRemoteApplicationEvent 远程确认事件的开关,构造 AckRemoteApplicationEvent 事件并在远程和本地都发送该事件(本地发送是因为步骤 5 没有进行本地 AckRemoteApplicationEvent 事件的发送,也就是自身应用对自身应用确认; 远程发送是为了告诉其它应用,自身应用收到了消息)。如果开启了消息记录 Trace 的开关,本地构造并发送 SentApplicationEvent 事件bus-env 触发后所有节点的 EnvironmentChangeListener 监听到了配置的变化,控制台都会打印出以下信息:o.s.c.b.event.EnvironmentChangeListener : Received remote environment change request. Keys/values to update {hangzhou=alibaba}如果在本地监听远程确认事件 AckRemoteApplicationEvent,都会收到所有节点的信息,比如 node5 节点的控制台监听到的 AckRemoteApplicationEvent 事件如下:ServiceId [rocketmq-bus-node5:10005] listeners on {“type”:“AckRemoteApplicationEvent”,“timestamp”:1554124670484,“originService”:“rocketmq-bus-node5:10005”,“destinationService”:”",“id”:“375f0426-c24e-4904-bce1-5e09371fc9bc”,“ackId”:“750d033f-356a-4aad-8cf0-3481ace8698c”,“ackDestinationService”:"",“event”:“org.springframework.cloud.bus.event.EnvironmentChangeRemoteApplicationEvent”}ServiceId [rocketmq-bus-node5:10005] listeners on {“type”:“AckRemoteApplicationEvent”,“timestamp”:1554124670184,“originService”:“rocketmq-bus-node1:10001”,“destinationService”:"",“id”:“91f06cf1-4bd9-4dd8-9526-9299a35bb7cc”,“ackId”:“750d033f-356a-4aad-8cf0-3481ace8698c”,“ackDestinationService”:"",“event”:“org.springframework.cloud.bus.event.EnvironmentChangeRemoteApplicationEvent”}ServiceId [rocketmq-bus-node5:10005] listeners on {“type”:“AckRemoteApplicationEvent”,“timestamp”:1554124670402,“originService”:“rocketmq-bus-node2:10002”,“destinationService”:"",“id”:“7df3963c-7c3e-4549-9a22-a23fa90a6b85”,“ackId”:“750d033f-356a-4aad-8cf0-3481ace8698c”,“ackDestinationService”:"",“event”:“org.springframework.cloud.bus.event.EnvironmentChangeRemoteApplicationEvent”}ServiceId [rocketmq-bus-node5:10005] listeners on {“type”:“AckRemoteApplicationEvent”,“timestamp”:1554124670406,“originService”:“rocketmq-bus-node3:10003”,“destinationService”:"",“id”:“728b45ee-5e26-46c2-af1a-e8d1571e5d3a”,“ackId”:“750d033f-356a-4aad-8cf0-3481ace8698c”,“ackDestinationService”:"",“event”:“org.springframework.cloud.bus.event.EnvironmentChangeRemoteApplicationEvent”}ServiceId [rocketmq-bus-node5:10005] listeners on {“type”:“AckRemoteApplicationEvent”,“timestamp”:1554124670427,“originService”:“rocketmq-bus-node4:10004”,“destinationService”:"",“id”:“1812fd6d-6f98-4e5b-a38a-4b11aee08aeb”,“ackId”:“750d033f-356a-4aad-8cf0-3481ace8698c”,“ackDestinationService”:"",“event”:“org.springframework.cloud.bus.event.EnvironmentChangeRemoteApplicationEvent”}那么回到本章节开头提到的 4 个问题,我们分别做一下解答:消息是如何发送的: 在 BusAutoConfiguration#acceptLocal 方法中通过 Spring Cloud Stream 发送事件到 springCloudBus topic 中。消息是如何接收的: 在 BusAutoConfiguration#acceptRemote 方法中通过 Spring Cloud Stream 接收 springCloudBus topic 的消息。destination 是如何匹配的: 在 BusAutoConfiguration#acceptRemote 方法中接收远程事件方法里对 destination 进行匹配。远程事件收到后如何触发下一个 action: Bus 内部通过 Spring 的事件机制接收本地的 RemoteApplicationEvent 具体的实现事件再做下一步的动作(比如 EnvironmentChangeListener 接收了 EnvironmentChangeRemoteApplicationEvent 事件, RefreshListener 接收了 RefreshRemoteApplicationEvent 事件)。总结Spring Cloud Bus 自身内容还是比较少的,不过还是需要提前了解 Spring Cloud Stream 体系以及 Spring 自身的事件机制,在此基础上,才能更好地理解 Spring Cloud Bus 对本地事件和远程事件的处理逻辑。目前 Bus 内置的远程事件较少,大多数为配置相关的事件,我们可以继承 RemoteApplicationEvent 并配合 @RemoteApplicationEventScan 注解构建自身的微服务消息体系。本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

April 15, 2019 · 4 min · jiezi

如何把创建ECS(CreateInstance)作为触发器来触发函数计算

摘要: 函数计算是阿里云上类似lamda的服务。 本文介绍了如何通过日志服务投递ECS创建等行为的日志,从而触发函数计算服务。问题描述函数计算虽然不支持直接集成到ECS的管控事件上,但是函数计算本身是支持日志服务作为触发器的。即可以配置日志服务中logstore里的增强日志作为触发器来触发函数计算服务中的函数,同时可以传递project 和 logstore的name以及beginCursor/endCursor 等相关日志信息作为event到函数计算服务,供其做二次处理和加工。这样相当于提供了一个思路,即我们可以把创建ECS或者其他相关的操作想办法作为日志投递到日志服务中,这样就可以触发相关的函数计算服务了。那么这种方法是什么呢?一种可行的方式是操作审计服务。操作审计可以记录所有API级别的用户记录,当然也包括CreateInstance这类操作。所以整个流程就变成了:开通操作审计服务->配置操作审计跟踪,将event投递到日志服务中->配置日志服务作为函数计算触发器并传递日志->触发函数举个栗子开通操作审计服务后,创建一个日志跟踪然后创建一个实例,可以看到操作审计记录了这个行为同时日志服务里也找到了这个行为记录接下来我们可以配置一个函数计算服务,具体的过程可以参考文中最后的文档,这里强调下配置触发器的配置,这里要注意的是图中有关logstore的配置,上面的是触发日志的logstore,下面的是写日志的lostore,不能搞混。然后复制进去一段代码,这段代码的核心是拿到触发event的具体日志信息,然后写到函数计算本地的日志库里。# -- coding: utf-8 --import loggingimport jsonfrom aliyun.log import LogClientfrom time import timedef logClient(endpoint, creds): logger = logging.getLogger() logger.info(‘creds info’) logger.info(creds.access_key_id) logger.info(creds.access_key_secret) logger.info(creds.security_token) accessKeyId = ‘XXX’ accessKey = ‘XXX’ client = LogClient(endpoint, accessKeyId, accessKey) return clientdef handler(event, context): logger = logging.getLogger() logger.info(‘start deal SLS data’) logger.info(event.decode().encode()) info_arr = json.loads(event.decode()) fetchdata(info_arr[‘source’],context) return ‘hello world’def fetchdata(event,context): logger = logging.getLogger() endpoint = event[’endpoint’] creds = context.credentials client = logClient(endpoint, creds) if client == None : logger.info(“client creat failed”) return False project = event[‘projectName’] logstore = event[’logstoreName’] start_cursor = event[‘beginCursor’] end_cursor = event[’endCursor’] loggroup_count = 10 shard_id = event[‘shardId’] while True: res = client.pull_logs(project, logstore, shard_id, start_cursor, loggroup_count, end_cursor) res.log_print() next_cursor = res.get_next_cursor() if next_cursor == start_cursor : break start_cursor = next_cursor #log_data = res.get_loggroup_json_list() return True以上配置完成后,一个控制台创建ECS(当然也包括其他可以被审计的行为)的行为就可以用来触发函数计算的函数了。结果我们把刚才创建的实例再释放掉,看到操作审计的日志然后我们在函数计算的日志库里也看到了对应的日志,这个日志是刚才操作审计记录的日志传递给函数计算并记录的。在真正的应用场景下,客户可以拿到这个日志中的相关信息做更多操作。更多sample可以参考:https://github.com/aliyun/aliyun-log-fc-functions总结产品侧无法直接支持的功能,可以看下是否有workaround很多阿里云产品之间的集成,都可以看下是否可以通过日志服务来做。参考资料https://help.aliyun.com/document_detail/84090.html?spm=a2c4g.11174283.6.619.5a6552120001Hlhttps://help.aliyun.com/document_detail/60781.html?spm=a2c4g.11186623.2.12.62727c9fIeDQwY本文作者:拱卒阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

April 15, 2019 · 1 min · jiezi

基于MaxCompute的数仓数据质量管理

声明本文中介绍的非功能性规范均为建议性规范,产品功能无强制,仅供指导。参考文献《大数据之路——阿里巴巴大数据实践》——阿里巴巴数据技术及产品部 著。背景及目的数据对一个企业来说已经是一项重要的资产,既然是资产,肯定需要管理。随着业务的增加,数据的应用越来越多,企业在创建的数仓过程中对数据的管理也提出了更高的要求,而数据质量也是数仓建设过程不容忽视的环节。本文针对MaxCompute数仓建设过程中如何做数据质量给出规范建议,为实际数据治理提供依据及指导。数据质量保障原则评估数据质量的好坏不同行业甚至不同企业有不同标准,在此我们主要从四个方面进行评估,即完整性、准确性、一致性和及时性。完整性。完整性是指数据的记录和信息是否完整,是否存在缺失情况。数据缺失主要包括记录的缺失和记录中某个字段信息的缺失,两者都会造成统计结果不准确,可以说,完整性是数据质量最基础的保障。如某个相对稳定的业务数据量每天的都有100万条记录,某天突然下降1万条,那么可能就是记录缺失。而对于记录中某个字段信息缺失,如某科高考成绩表中一个考卷分数要对应一个准考证号,这个字段的空值数就该为0,一旦大于0,说明该信息缺失了。准确性。准确性是指数据中记录的信息和数据是否准确,是否存在异常或者错误的信息。比如成绩单中分数出现负数,比如订单没有买家信息等,这些都是有问题的。确保记录的准确性也是抱着数据质量必不可少的一个原则。一致性。一致性一般体现在跨度很大的数据仓库体现中。 比如公司中有很多业务数仓分支,对于同一份数据必须保证一致性。例如用户ID,从在线业务库加工到数据仓库,再到各个数据应用节点,必须都是同一种类型、长度保持一致。因此在《MaxCompute数仓建设规范指南》中有了“公共层”的加工,确保数据的一致性。及时性。保障数据的及时产出,体现数据的价值。如决策的分析师一般都希望当天可以看到前一天的数据而不是要等三五天才能看到某一个数据分析结果,否则就失去了数据及时性的价值,使得数据分析工作变得毫无意义。数据质量管理流程要做数据质量管理,制定满足以上数据质量原则集基础上的质量管理规范,需要考虑几方面:什么数据需要做质量管理。什么环节进行数据质量管理。数据质量管理具体怎么做。数据质量定义定义哪些数据需要做质量管理一般可以通过数据资产等级划分和元数据的应用链路分析得出。根据应用的影响程度,确定数据资产等级;根据数据链路血缘,将数据资产等级上推至各数据生产加工的各个环节,确定链路上所涉及的数据的资产等级和在各个加工环节上根据资产等级的不同所采取的不同处理方式。数据资产等级定义对于数据的资产等级,在质量管理方向,可以从数据质量“不满足四个原则”情况下对业务的影响性质,比如可以划分为5个等级的性质,即毁灭性质、全局性质、局部性质、一般性质、未知性质,不同性质的重要性一次降低,具体定义如下:毁灭性质,即数据一旦出错,将会引起重大资产损失,面临重大收益损失等。全局性质,即数据直接或间接用于企业级业务和效果评估、重要决策等。局部性质,即数据直接或间接用于某些业务线的运营、报告等,若出现问题会给业务线造成影响或者造成工作效率损失。一般性质,即数据主要用于日常数据分析,出现问题带来的影响极小。未知性质,即无法明确数据的应用场景。如table的label等级,资产等级可以用Asset进行标记:毁灭性质-A1,全局性质-A2,局部性质-A3,一般性质-A4,未知性质-Ax。重要程度为:A1>A2>A3>A4>Ax。若一份数据出现在多个应用场景汇总则遵循就高原则。数据资产等级落地方法定义划分好数据资产等级后,接下来就考虑怎么落地,对数仓中庞大的数据量进行资产等级打标。可以从数据流转链路着手。MaxCompute进行数据加工基本基本流程:数据从业务系统上产生,通过同步工具(DataWorks的数据集成或阿里云DTS)进入数据数仓系统(MaxCompute),数据在数仓中进行清洗、加工、整合、算法、模型等一系列运算后,再通过同步工具输出到数据产品中进行消费。整个流程数据都是以存放在表的形式体现,流转链路大致如下图:从数据流转链路上,整理哪些表是被哪些应用业务产品消费,通过给这些应用业务产品划分数据资产等级,再结合数据的上下游血缘,将整个链路打上某一类资产等级的标签。如,一个A2等级的的数据应用产品,对应导入这个数据产品的table即数仓(MaxCompute)的导出表Table1、Table2、Table3,几个表都打上A2-xxx数据产品标记,根据血缘往上追溯,将这几个表的上有都打上A2的标记,一直标记到源数据业务系统。通过如上方式完成数据资产等级的确认,给不同的数据定义不同的重要程度。知道了数据的重要等级针对不同的等级,采取不同的保障措施,接下来我们介绍在基于MaxCompute的数据仓库中针对不同等级的数据的保障方法。数据加工过程卡点校验在线系统卡点校验在线系统数据加工过程卡点校验,主要是指在业务系统的数据生成过程中进行的卡点校验。在线业务系统产生的数据也是数据仓库的数据来源,然而在线业务系统普遍都是复杂多变,且每次变更不可避免会带来数据的变化,数仓需要适应多变的业务发展,及时做到数据的准确性。因此,在线业务的变更如何高效的通知到基于MaxCompute的离线数据仓库,也是需要考虑的问题。这里我们介绍两个方法拱参考:工具和人员双管齐下。纪要在工具上自动捕捉每一次业务的变化,同时也要求开发人员在意识上自动进行业务变更通知。工具——发布平台。在业务进行重大变更时,订阅这个发布过程,通知到离线开发人员,使其知晓此次变更内容。当业务系统足够繁杂,日常发布变更频繁的情况下,若每次变更都通知离线业务,势必会造成不必要的浪费,同时也影响业务迭代效率。此时,可以通过数据资产等级的标识,对业务进行打标后,针对高等级的数据资产,整理出什么变更会影响数据的加工,如相关财务报表,如果业务系统的改造影响到财务报表的计算,使得约定好的计算口径被业务系统发布变更修改了,这种情况必须要告知离线业务,而离线开发人员也必须主动关注这类发布变更通知。注意:这里指的发布平台非阿里云提供发布平台,只是一种统称,指各个企业自己在线业务的相关发布平台。工具——数据库的变化感知。随着业务的发展,业务数据库(MaxCompute数仓的数据源)不可避免会出现数据库扩容或者DDL变更,这些变更都要主动通知到离线开发人员。基于MaxCompute的数据仓库在进行离线数据抽取时,通过DataWorks的数据集成工具,可能会限制某个业务数据库表,如果该数据库表发生扩容或者迁移等,数据集成工具感知不到,会可能导致数据抽取错漏,而一旦错漏,会影响下游一系列依赖该表的应用,因此建议业务数据库也需要有库表变更通知。工具只是一种辅助手段,操作工具的人员才是核心。数据资产等级的上下游打通,同样也将这个过程给到在线开发人员,使其知晓哪些是重要的核心数据资产,提高在线开发人员的数据风险意识。通过培训等方式将离线数据的诉求、离线数据的加工过程、数据产品的应用方式告诉在线业务开发人员,让其了解数据的重要性,了解数据的价值,同时也告知出错后果。让在线开发人员在完成业务目标时,也要考虑数据的目标,做到业务端和数据端一致。离线系统卡点校验首先我们再次认识MaxCompute进行数据加工的基本流程:数据从业务系统上产生,通过同步工具(DataWorks的数据集成或阿里云DTS)进入数仓系统(MaxCompute),数据在数仓中进行清洗、加工、整合、算法、模型等一系列运算后,再通过同步工具输出到数据产品中进行消费。整个流程中,有了数据加工,才有了数据仓库模型和数据仓库代码的建设,如何保障数据加工过程中的质量是离线数据仓库保障数据质量的一个重要环节。MaxCompute进行数据加工,可以通过DataWorks、也可以通过MaxCompute studio、或者直接通过MaxCompute SDK提交各种任务进行加工。无论用什么工具,都会经历代码开发->测试、发布->运维、变更 的过程,可以对这个过程每个环节进行卡点校验。代码提交的卡点校验。即在sql提交前进行相关规则校验。这个校验目前公共云没有直接可用的工具辅助,有能力的用户可以自己开发相关的工具。规则分类如:代码规范类规则,如表命名规范、生命周期设置、表注释等。代码质量类规则,如分母为0提醒、NULL值参与计算影响结果提醒、插入字段顺序错误等。代码性能类规则,如分区裁剪失效、扫描大表提醒、重复计算检测等。任务发布上线时的卡点校验。为了保障线上数据的准确性,每一次变更都需要测试后再发布到线上生产环境,且生产环境测试通过后才算发布成功。任务变更或者数据重跑,在离线数据加工过程中不可避免都会出现的操作。针对这个操作,在进行更新前,需要通知下游,将变更原因、变更逻辑、变更时间等信息表明,下游对此次变更没有异议后再按照约定时间执行发布变更,将变更对下游的影响降到最低。数据风险点监控前一章节主要介绍通过数据加工过程的卡点校验保障在线数据和离线数据的一致性问题,本章节主要通过对数据风险点的监控来介绍如何保障数据的准确性。在线数据风险点监控在线业务系统的数据生成过程需要保证数据质量,主要根据业务规则对数据进行监控。MaxCompute本身没有配套的工具,需用户自己实现,在此只能给出一些建议拱参考。如针对数据库表的记录进行规则校验,制定一些监控规则,在业务系统中,每个业务过程进行数据落库时对数据进行校验。监控规则如交易系统中,订单拍下时间、订单完结时间、订单支付金额、订单状态流转都配置校验规则,订单拍下时间不会大于当天时间,也不会小于业务系统上线时间,一旦出现异常校验就不通过。当业务繁杂且规则繁多,规则配置等运行成本高时,同样根据数据资产等级进行监控。离线数据风险点监控本小节将介绍基于MaxCompute的数据仓库建设过程中离线数据的风险点监控,主要报对数据准确性和数据产出及时性的监控。数据准确性数据准确性是数据质量的关键,因此数据准确成为数据直连的重中之重,是所有离线系统加工时的第一保障要素,在此我们主要介绍通过DataWorks的数据质量工具——DQC来保障MaxCompute离线数据的准确性。注意,要用DQC,必须是使用DataWorks进行任务调度执行。我们先来认识DQC工具架构:DQC以数据集(DataSet)为监控对象,当离线MaxCompute数据发生变化时,DQC会对数据进行校验,并阻塞生产链路,以避免问题数据污染扩散。同时,DQC提供了历史校验结果的管理,以便对数据质量分析和定级。由上图我们看出DQC主要是通过配置数据质量校验规则,自动在数据处理过程中进行数据质量监控。DQC能监控数据质量并报警,本身不对数据产出进行处理,需要报警接收人判断并决定如何处理。DQC数据监控规则有强规则和弱规则之分。强规则,一旦触发报警就会阻断任务的执行(将任务置为失败状态,使下游任务不会被触发执行);弱规则,只告警不会阻断任务的执行。DQC根据阿里内部的经验,提供了一些常用的规则模板,包括:表行数较N天前波动率、表空间大小较N天前波动率、字段最大/最小/平均值相比N天前波动率、字段空值/唯一个数等等,更多请参考DataWorks用户手册中数据质量模块介绍。DQC的工作流程如下图所示:由此看出DQC的检查其实也是运行SQL任务,只是这个任务是嵌套在主任务中,若检查的太多也会影响整体的任务执行性能,因此哪些数据需要配置DQC规则,应该配置什么规则,也要根据数据资产等级来确定。如A1、A2类数据监控率要达到90%以上,规则类需要3种以上,而不重要的数据资产不做强要求。类似的规则都是有离线开发人员进行配置来确保数据准确性,当然不同的业务会有业务规则的约束,这些规则来源于数据产品或者消费的业务需求,有消费节点进行配置,然后上推到离线系统的起点进行监控,做到规则影响最小化。数据的及时性在确保数据准确性的前提下,需要进一步让数据能够及时的提供服务,否则数据的价值将大幅降低,甚至无价值,所以确保数据及时性也是保障数据质量重中之重的一环。基于MaxCompute的离线任务,如常见的以天作为时间间隔,对于天任务,一些重要的业务会对数据产出有时间要求,比如一些决策报表要求9:00或更早必须产出。为确保数据完整性,天任务一般都是0点开始执行,计算刚过去的一天的数据,这些任务大多在夜里运行,要确保数据按时产出,需要考虑任务的优先执行(当Project里任务很多而资源有限的时候不得不考虑)和任务执行失败或时长过长时的告警问题。这里说的重要业务的“重要性”同样是前面所说的数据资产等级的划分,等级越高保障优先级越高。任务优先级。MaxCompute平台上任务优先级都是一样,无法配置。因此要对MaxCompute的任务实现“优先级”功能,只能从调度平台入手,优先调度下发重要的任务。DataWorks平台的调度任务,当对应的Project是使用预付费资源(预购固定的计算资源仅供当前项目使用)时,可以通过“智能监控”工具进行优先级设置。DataWorks的调度是一个树形结构,当配置了叶子节点的优先级,这个优先级会传递到所有的上游节点,而叶子节点往往就是服务业务的消费节点。因此在优先级的设置上,先确定业务的资产等级,等级越高的业务对应的消费节点优先级配置越高,优先调度从而优先占用计算资源,确保高等级业务准时产出。当DataWorks的节点任务所属的Project使用的是MaxCompute的后付费资源(计算按量付费,无固定资源使用),智能监控配置的优先级无效,因此,需要评估是否要购买预付费资源,同时对任务进行优化,减少不必要的资源浪费,力争在有限的资源下更高效的完成计算。任务报警。任务报警和优先级类似,通过DataWorks的“智能监控”工具进行配置,只需要配置叶子节点即可向上游传递。任务执行过程中出错或者可能出现延迟都是不可避免的,为了保障最重要数据(资产等级高)产出,我们需要“出错”立即处理、“可能”延迟必须知晓并介入。DataWorks—智能监控。MaxCompute的离线任务,通过DataWorks进行离线任务调度时,DataWorks提供智能监控工具,对调度任务进行监控告警。智能监控是DataWorks任务运行的监控及分析系统。根据监控规则和任务运行情况,智能监控决策是否报警、何时报警、如何报警以及给谁报警。智能监控会自动选择最合理的报警时间,报警方式以及报警对象。智能监控旨在:降低您的配置成本。杜绝无效报警。自动覆盖所有重要任务(数量已经多到您自己无法梳理)。数据质量衡量前面章节给出了保障基于MaxCompute的数据仓库数据质量的方案,但是这些方案是否真的合适,或者哪些点需要改进,这些需制定一套指标进行度量。比如:频繁的接到DataWorks的智能监控发出的告警;每一个数据质量事件发生,必须分析有原因、处理过程、后续同类事件预防方案;严重的数据质量事件升级为故障,并对故障进行定义、等级划分、处理、review。相关工具链接DataWorks-数据质量管理工具,文档,工具界面。DataWorks—智能监控工具,文档,工具界面。本文作者:海清阅读原文本文为云栖社区原创内容,未经允许不得转载。

April 12, 2019 · 1 min · jiezi

探索Java日志的奥秘:底层日志系统-log4j2

前言log4j2是apache在log4j的基础上,参考logback架构实现的一套新的日志系统(我感觉是apache害怕logback了)。log4j2的官方文档上写着一些它的优点:在拥有全部logback特性的情况下,还修复了一些隐藏问题API 分离:现在log4j2也是门面模式使用日志,默认的日志实现是log4j2,当然你也可以用logback(应该没有人会这么做)性能提升:log4j2包含下一代基于LMAX Disruptor library的异步logger,在多线程场景下,拥有18倍于log4j和logback的性能多API支持:log4j2提供Log4j 1.2, SLF4J, Commons Logging and java.util.logging (JUL) 的API支持避免锁定:使用Log4j2 API的应用程序始终可以选择使用任何符合SLF4J的库作为log4j-to-slf4j适配器的记录器实现自动重新加载配置:与Logback一样,Log4j 2可以在修改时自动重新加载其配置。与Logback不同,它会在重新配置发生时不会丢失日志事件。高级过滤: 与Logback一样,Log4j 2支持基于Log事件中的上下文数据,标记,正则表达式和其他组件进行过滤。插件架构: Log4j使用插件模式配置组件。因此,您无需编写代码来创建和配置Appender,Layout,Pattern Converter等。Log4j自动识别插件并在配置引用它们时使用它们。属性支持:您可以在配置中引用属性,Log4j将直接替换它们,或者Log4j将它们传递给将动态解析它们的底层组件。Java 8 Lambda支持自定义日志级别产生垃圾少:在稳态日志记录期间,Log4j 2 在独立应用程序中是无垃圾的,在Web应用程序中是低垃圾。这减少了垃圾收集器的压力,并且可以提供更好的响应时间性能。和应用server集成:版本2.10.0引入了一个模块log4j-appserver,以改进与Apache Tomcat和Eclipse Jetty的集成。Log4j2类图:这次从四个地方去探索源码:启动,配置,异步,插件化源码探索启动log4j2的关键组件LogManager根据配置指定LogContexFactory,初始化对应的LoggerContextLoggerContext1、解析配置文件,解析为对应的java对象。2、通过LoggerRegisty缓存Logger配置3、Configuration配置信息4、start方法解析配置文件,转化为对应的java对象5、通过getLogger获取logger对象LoggerLogManaer该组件是Log4J启动的入口,后续的LoggerContext以及Logger都是通过调用LogManager的静态方法获得。我们可以使用下面的代码获取LoggerLogger logger = LogManager.getLogger();可以看出LogManager是十分关键的组件,因此在这个小节中我们详细分析LogManager的启动流程。LogManager启动的入口是下面的static代码块:/** * Scans the classpath to find all logging implementation. Currently, only one will be used but this could be * extended to allow multiple implementations to be used. / static { // Shortcut binding to force a specific logging implementation. final PropertiesUtil managerProps = PropertiesUtil.getProperties(); final String factoryClassName = managerProps.getStringProperty(FACTORY_PROPERTY_NAME); if (factoryClassName != null) { try { factory = LoaderUtil.newCheckedInstanceOf(factoryClassName, LoggerContextFactory.class); } catch (final ClassNotFoundException cnfe) { LOGGER.error(“Unable to locate configured LoggerContextFactory {}”, factoryClassName); } catch (final Exception ex) { LOGGER.error(“Unable to create configured LoggerContextFactory {}”, factoryClassName, ex); } } if (factory == null) { final SortedMap<Integer, LoggerContextFactory> factories = new TreeMap<>(); // note that the following initial call to ProviderUtil may block until a Provider has been installed when // running in an OSGi environment if (ProviderUtil.hasProviders()) { for (final Provider provider : ProviderUtil.getProviders()) { final Class<? extends LoggerContextFactory> factoryClass = provider.loadLoggerContextFactory(); if (factoryClass != null) { try { factories.put(provider.getPriority(), factoryClass.newInstance()); } catch (final Exception e) { LOGGER.error(“Unable to create class {} specified in provider URL {}”, factoryClass.getName(), provider .getUrl(), e); } } } if (factories.isEmpty()) { LOGGER.error(“Log4j2 could not find a logging implementation. " + “Please add log4j-core to the classpath. Using SimpleLogger to log to the console…”); factory = new SimpleLoggerContextFactory(); } else if (factories.size() == 1) { factory = factories.get(factories.lastKey()); } else { final StringBuilder sb = new StringBuilder(“Multiple logging implementations found: \n”); for (final Map.Entry<Integer, LoggerContextFactory> entry : factories.entrySet()) { sb.append(“Factory: “).append(entry.getValue().getClass().getName()); sb.append(”, Weighting: “).append(entry.getKey()).append(’\n’); } factory = factories.get(factories.lastKey()); sb.append(“Using factory: “).append(factory.getClass().getName()); LOGGER.warn(sb.toString()); } } else { LOGGER.error(“Log4j2 could not find a logging implementation. " + “Please add log4j-core to the classpath. Using SimpleLogger to log to the console…”); factory = new SimpleLoggerContextFactory(); } } }这段静态代码段主要分为下面的几个步骤:首先根据特定配置文件的配置信息获取loggerContextFactory如果没有找到对应的Factory的实现类则通过ProviderUtil中的getProviders()方法载入providers,随后通过provider的loadLoggerContextFactory方法载入LoggerContextFactory的实现类如果provider中没有获取到LoggerContextFactory的实现类或provider为空,则使用SimpleLoggerContextFactory作为LoggerContextFactory。根据配置文件载入LoggerContextFactory// Shortcut binding to force a specific logging implementation. final PropertiesUtil managerProps = PropertiesUtil.getProperties(); final String factoryClassName = managerProps.getStringProperty(FACTORY_PROPERTY_NAME); if (factoryClassName != null) { try { factory = LoaderUtil.newCheckedInstanceOf(factoryClassName, LoggerContextFactory.class); } catch (final ClassNotFoundException cnfe) { LOGGER.error(“Unable to locate configured LoggerContextFactory {}”, factoryClassName); } catch (final Exception ex) { LOGGER.error(“Unable to create configured LoggerContextFactory {}”, factoryClassName, ex); } }在这段逻辑中,LogManager优先通过配置文件”log4j2.component.properties”通过配置项”log4j2.loggerContextFactory”来获取LoggerContextFactory,如果用户做了对应的配置,通过newCheckedInstanceOf方法实例化LoggerContextFactory的对象,最终的实现方式为:public static <T> T newInstanceOf(final Class<T> clazz) throws InstantiationException, IllegalAccessException, InvocationTargetException { try { return clazz.getConstructor().newInstance(); } catch (final NoSuchMethodException ignored) { // FIXME: looking at the code for Class.newInstance(), this seems to do the same thing as above return clazz.newInstance(); } }在默认情况下,不存在初始的默认配置文件log4j2.component.properties,因此需要从其他途径获取LoggerContextFactory。通过Provider实例化LoggerContextFactory对象代码:if (factory == null) { final SortedMap<Integer, LoggerContextFactory> factories = new TreeMap<>(); // note that the following initial call to ProviderUtil may block until a Provider has been installed when // running in an OSGi environment if (ProviderUtil.hasProviders()) { for (final Provider provider : ProviderUtil.getProviders()) { final Class<? extends LoggerContextFactory> factoryClass = provider.loadLoggerContextFactory(); if (factoryClass != null) { try { factories.put(provider.getPriority(), factoryClass.newInstance()); } catch (final Exception e) { LOGGER.error(“Unable to create class {} specified in provider URL {}”, factoryClass.getName(), provider .getUrl(), e); } } } if (factories.isEmpty()) { LOGGER.error(“Log4j2 could not find a logging implementation. " + “Please add log4j-core to the classpath. Using SimpleLogger to log to the console…”); factory = new SimpleLoggerContextFactory(); } else if (factories.size() == 1) { factory = factories.get(factories.lastKey()); } else { final StringBuilder sb = new StringBuilder(“Multiple logging implementations found: \n”); for (final Map.Entry<Integer, LoggerContextFactory> entry : factories.entrySet()) { sb.append(“Factory: “).append(entry.getValue().getClass().getName()); sb.append(”, Weighting: “).append(entry.getKey()).append(’\n’); } factory = factories.get(factories.lastKey()); sb.append(“Using factory: “).append(factory.getClass().getName()); LOGGER.warn(sb.toString()); } } else { LOGGER.error(“Log4j2 could not find a logging implementation. " + “Please add log4j-core to the classpath. Using SimpleLogger to log to the console…”); factory = new SimpleLoggerContextFactory(); } }这里比较有意思的是hasProviders和getProviders都会通过线程安全的方式去懒加载ProviderUtil这个对象。跟进lazyInit方法:protected static void lazyInit() { //noinspection DoubleCheckedLocking if (INSTANCE == null) { try { STARTUP_LOCK.lockInterruptibly(); if (INSTANCE == null) { INSTANCE = new ProviderUtil(); } } catch (final InterruptedException e) { LOGGER.fatal(“Interrupted before Log4j Providers could be loaded.”, e); Thread.currentThread().interrupt(); } finally { STARTUP_LOCK.unlock(); } } }再看构造方法:private ProviderUtil() { for (final LoaderUtil.UrlResource resource : LoaderUtil.findUrlResources(PROVIDER_RESOURCE)) { loadProvider(resource.getUrl(), resource.getClassLoader()); } }这里的懒加载其实就是懒加载Provider对象。在创建新的providerUtil实例的过程中就会直接实例化provider对象,其过程是先通过getClassLoaders方法获取provider的类加载器,然后通过loadProviders(classLoader);加载类。在providerUtil实例化的最后,会统一查找”META-INF/log4j-provider.properties”文件中对应的provider的url,会考虑从远程加载provider。而loadProviders方法就是在ProviderUtil的PROVIDERS列表中添加对一个的provider。可以看到默认的provider是org.apache.logging.log4j.core.impl.Log4jContextFactoryLoggerContextFactory = org.apache.logging.log4j.core.impl.Log4jContextFactoryLog4jAPIVersion = 2.1.0FactoryPriority= 10很有意思的是这里懒加载加上了锁,而且使用的是lockInterruptibly这个方法。lockInterruptibly和lock的区别如下:lock 与 lockInterruptibly比较区别在于:lock 优先考虑获取锁,待获取锁成功后,才响应中断。lockInterruptibly 优先考虑响应中断,而不是响应锁的普通获取或重入获取。ReentrantLock.lockInterruptibly允许在等待时由其它线程调用等待线程的Thread.interrupt 方法来中断等待线程的等待而直接返回,这时不用获取锁,而会抛出一个InterruptedException。 ReentrantLock.lock方法不允许Thread.interrupt中断,即使检测到Thread.isInterrupted,一样会继续尝试获取锁,失败则继续休眠。只是在最后获取锁成功后再把当前线程置为interrupted状态,然后再中断线程。上面有一句注释值得注意:/* * Guards the ProviderUtil singleton instance from lazy initialization. This is primarily used for OSGi support. * * @since 2.1 / protected static final Lock STARTUP_LOCK = new ReentrantLock(); // STARTUP_LOCK guards INSTANCE for lazy initialization; this allows the OSGi Activator to pause the startup and // wait for a Provider to be installed. See LOG4J2-373 private static volatile ProviderUtil INSTANCE;原来这里是为了让osgi可以阻止启动。再回到logManager:可以看到在加载完Provider之后,会做factory的绑定:if (factories.isEmpty()) { LOGGER.error(“Log4j2 could not find a logging implementation. " + “Please add log4j-core to the classpath. Using SimpleLogger to log to the console…”); factory = new SimpleLoggerContextFactory(); } else if (factories.size() == 1) { factory = factories.get(factories.lastKey()); } else { final StringBuilder sb = new StringBuilder(“Multiple logging implementations found: \n”); for (final Map.Entry<Integer, LoggerContextFactory> entry : factories.entrySet()) { sb.append(“Factory: “).append(entry.getValue().getClass().getName()); sb.append(”, Weighting: “).append(entry.getKey()).append(’\n’); } factory = factories.get(factories.lastKey()); sb.append(“Using factory: “).append(factory.getClass().getName()); LOGGER.warn(sb.toString()); }到这里,logmanager的启动流程就结束了。配置在不使用slf4j的情况下,我们获取logger的方式是这样的:Logger logger = logManager.getLogger(xx.class)跟进getLogger方法: public static Logger getLogger(final Class<?> clazz) { final Class<?> cls = callerClass(clazz); return getContext(cls.getClassLoader(), false).getLogger(toLoggerName(cls)); }这里有一个getContext方法,跟进,public static LoggerContext getContext(final ClassLoader loader, final boolean currentContext) { try { return factory.getContext(FQCN, loader, null, currentContext); } catch (final IllegalStateException ex) { LOGGER.warn(ex.getMessage() + " Using SimpleLogger”); return new SimpleLoggerContextFactory().getContext(FQCN, loader, null, currentContext); } }上文提到factory的具体实现是Log4jContextFactory,跟进getContext方法:public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext, final boolean currentContext) { final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext); if (externalContext != null && ctx.getExternalContext() == null) { ctx.setExternalContext(externalContext); } if (ctx.getState() == LifeCycle.State.INITIALIZED) { ctx.start(); } return ctx; }直接看start:public void start() { LOGGER.debug(“Starting LoggerContext[name={}, {}]…”, getName(), this); if (PropertiesUtil.getProperties().getBooleanProperty(“log4j.LoggerContext.stacktrace.on.start”, false)) { LOGGER.debug(“Stack trace to locate invoker”, new Exception(“Not a real error, showing stack trace to locate invoker”)); } if (configLock.tryLock()) { try { if (this.isInitialized() || this.isStopped()) { this.setStarting(); reconfigure(); if (this.configuration.isShutdownHookEnabled()) { setUpShutdownHook(); } this.setStarted(); } } finally { configLock.unlock(); } } LOGGER.debug(“LoggerContext[name={}, {}] started OK.”, getName(), this); }发现其中的核心方法是reconfigure方法,继续跟进:private void reconfigure(final URI configURI) { final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null; LOGGER.debug(“Reconfiguration started for context[name={}] at URI {} ({}) with optional ClassLoader: {}”, contextName, configURI, this, cl); final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(this, contextName, configURI, cl); if (instance == null) { LOGGER.error(“Reconfiguration failed: No configuration found for ‘{}’ at ‘{}’ in ‘{}’”, contextName, configURI, cl); } else { setConfiguration(instance); / * instance.start(); Configuration old = setConfiguration(instance); updateLoggers(); if (old != null) { * old.stop(); } / final String location = configuration == null ? “?” : String.valueOf(configuration.getConfigurationSource()); LOGGER.debug(“Reconfiguration complete for context[name={}] at URI {} ({}) with optional ClassLoader: {}”, contextName, location, this, cl); } }可以看到每一个configuration都是从ConfigurationFactory拿出来的,我们先看看这个类的getInstance看看:public static ConfigurationFactory getInstance() { // volatile works in Java 1.6+, so double-checked locking also works properly //noinspection DoubleCheckedLocking if (factories == null) { LOCK.lock(); try { if (factories == null) { final List<ConfigurationFactory> list = new ArrayList<ConfigurationFactory>(); final String factoryClass = PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FACTORY_PROPERTY); if (factoryClass != null) { addFactory(list, factoryClass); } final PluginManager manager = new PluginManager(CATEGORY); manager.collectPlugins(); final Map<String, PluginType<?>> plugins = manager.getPlugins(); final List<Class<? extends ConfigurationFactory>> ordered = new ArrayList<Class<? extends ConfigurationFactory>>(plugins.size()); for (final PluginType<?> type : plugins.values()) { try { ordered.add(type.getPluginClass().asSubclass(ConfigurationFactory.class)); } catch (final Exception ex) { LOGGER.warn(“Unable to add class {}”, type.getPluginClass(), ex); } } Collections.sort(ordered, OrderComparator.getInstance()); for (final Class<? extends ConfigurationFactory> clazz : ordered) { addFactory(list, clazz); } // see above comments about double-checked locking //noinspection NonThreadSafeLazyInitialization factories = Collections.unmodifiableList(list); } } finally { LOCK.unlock(); } } LOGGER.debug(“Using configurationFactory {}”, configFactory); return configFactory; }这里可以看到ConfigurationFactory中利用了PluginManager来进行初始化,PluginManager会将ConfigurationFactory的子类加载进来,默认使用的XmlConfigurationFactory,JsonConfigurationFactory,YamlConfigurationFactory这三个子类,这里插件化加载暂时按下不表。回到reconfigure这个方法,我们看到获取ConfigurationFactory实例之后会去调用getConfiguration方法:public Configuration getConfiguration(final String name, final URI configLocation, final ClassLoader loader) { if (!isActive()) { return null; } if (loader == null) { return getConfiguration(name, configLocation); } if (isClassLoaderUri(configLocation)) { final String path = extractClassLoaderUriPath(configLocation); final ConfigurationSource source = getInputFromResource(path, loader); if (source != null) { final Configuration configuration = getConfiguration(source); if (configuration != null) { return configuration; } } } return getConfiguration(name, configLocation); }跟进getConfiguration,这里值得注意的是有很多个getConfiguration,注意甄别,如果不确定的话可以通过debug的方式来确定。public Configuration getConfiguration(final String name, final URI configLocation) { if (configLocation == null) { final String config = this.substitutor.replace( PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FILE_PROPERTY)); if (config != null) { ConfigurationSource source = null; try { source = getInputFromUri(FileUtils.getCorrectedFilePathUri(config)); } catch (final Exception ex) { // Ignore the error and try as a String. LOGGER.catching(Level.DEBUG, ex); } if (source == null) { final ClassLoader loader = LoaderUtil.getThreadContextClassLoader(); source = getInputFromString(config, loader); } if (source != null) { for (final ConfigurationFactory factory : factories) { final String[] types = factory.getSupportedTypes(); if (types != null) { for (final String type : types) { if (type.equals(””) || config.endsWith(type)) { final Configuration c = factory.getConfiguration(source); if (c != null) { return c; } } } } } } } } else { for (final ConfigurationFactory factory : factories) { final String[] types = factory.getSupportedTypes(); if (types != null) { for (final String type : types) { if (type.equals(”*”) || configLocation.toString().endsWith(type)) { final Configuration config = factory.getConfiguration(name, configLocation); if (config != null) { return config; } } } } } } Configuration config = getConfiguration(true, name); if (config == null) { config = getConfiguration(true, null); if (config == null) { config = getConfiguration(false, name); if (config == null) { config = getConfiguration(false, null); } } } if (config != null) { return config; } LOGGER.error(“No log4j2 configuration file found. Using default configuration: logging only errors to the console.”); return new DefaultConfiguration(); }这里就会根据之前加载进来的factory进行配置的获取,具体的不再解析。回到reconfigure,之后的步骤就是setConfiguration,入参就是刚才获取的configprivate synchronized Configuration setConfiguration(final Configuration config) { Assert.requireNonNull(config, “No Configuration was provided”); final Configuration prev = this.config; config.addListener(this); final ConcurrentMap<String, String> map = config.getComponent(Configuration.CONTEXT_PROPERTIES); try { // LOG4J2-719 network access may throw android.os.NetworkOnMainThreadException map.putIfAbsent(“hostName”, NetUtils.getLocalHostname()); } catch (final Exception ex) { LOGGER.debug(“Ignoring {}, setting hostName to ‘unknown’”, ex.toString()); map.putIfAbsent(“hostName”, “unknown”); } map.putIfAbsent(“contextName”, name); config.start(); this.config = config; updateLoggers(); if (prev != null) { prev.removeListener(this); prev.stop(); } firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config)); try { Server.reregisterMBeansAfterReconfigure(); } catch (final Throwable t) { // LOG4J2-716: Android has no java.lang.management LOGGER.error(“Could not reconfigure JMX”, t); } return prev; }这个方法最重要的步骤就是config.start,这才是真正做配置解析的public void start() { LOGGER.debug(“Starting configuration {}”, this); this.setStarting(); pluginManager.collectPlugins(pluginPackages); final PluginManager levelPlugins = new PluginManager(Level.CATEGORY); levelPlugins.collectPlugins(pluginPackages); final Map<String, PluginType<?>> plugins = levelPlugins.getPlugins(); if (plugins != null) { for (final PluginType<?> type : plugins.values()) { try { // Cause the class to be initialized if it isn’t already. Loader.initializeClass(type.getPluginClass().getName(), type.getPluginClass().getClassLoader()); } catch (final Exception e) { LOGGER.error(“Unable to initialize {} due to {}”, type.getPluginClass().getName(), e.getClass() .getSimpleName(), e); } } } setup(); setupAdvertisement(); doConfigure(); final Set<LoggerConfig> alreadyStarted = new HashSet<LoggerConfig>(); for (final LoggerConfig logger : loggers.values()) { logger.start(); alreadyStarted.add(logger); } for (final Appender appender : appenders.values()) { appender.start(); } if (!alreadyStarted.contains(root)) { // LOG4J2-392 root.start(); // LOG4J2-336 } super.start(); LOGGER.debug(“Started configuration {} OK.”, this); }这里面有如下步骤:获取日志等级的插件初始化初始化Advertiser配置先看一下初始化,也就是setup这个方法,setup是一个需要被复写的方法,我们以XMLConfiguration作为例子,@Override public void setup() { if (rootElement == null) { LOGGER.error(“No logging configuration”); return; } constructHierarchy(rootNode, rootElement); if (status.size() > 0) { for (final Status s : status) { LOGGER.error(“Error processing element {}: {}”, s.name, s.errorType); } return; } rootElement = null; }发现这里面有一个比较重要的方法constructHierarchy,跟进:private void constructHierarchy(final Node node, final Element element) { processAttributes(node, element); final StringBuilder buffer = new StringBuilder(); final NodeList list = element.getChildNodes(); final List<Node> children = node.getChildren(); for (int i = 0; i < list.getLength(); i++) { final org.w3c.dom.Node w3cNode = list.item(i); if (w3cNode instanceof Element) { final Element child = (Element) w3cNode; final String name = getType(child); final PluginType<?> type = pluginManager.getPluginType(name); final Node childNode = new Node(node, name, type); constructHierarchy(childNode, child); if (type == null) { final String value = childNode.getValue(); if (!childNode.hasChildren() && value != null) { node.getAttributes().put(name, value); } else { status.add(new Status(name, element, ErrorType.CLASS_NOT_FOUND)); } } else { children.add(childNode); } } else if (w3cNode instanceof Text) { final Text data = (Text) w3cNode; buffer.append(data.getData()); } } final String text = buffer.toString().trim(); if (text.length() > 0 || (!node.hasChildren() && !node.isRoot())) { node.setValue(text); } }发现这个就是一个树遍历的过程。诚然,配置文件是以xml的形式给出的,xml的结构就是一个树形结构。回到start方法,跟进doConfiguration:protected void doConfigure() { if (rootNode.hasChildren() && rootNode.getChildren().get(0).getName().equalsIgnoreCase(“Properties”)) { final Node first = rootNode.getChildren().get(0); createConfiguration(first, null); if (first.getObject() != null) { subst.setVariableResolver((StrLookup) first.getObject()); } } else { final Map<String, String> map = this.getComponent(CONTEXT_PROPERTIES); final StrLookup lookup = map == null ? null : new MapLookup(map); subst.setVariableResolver(new Interpolator(lookup, pluginPackages)); } boolean setLoggers = false; boolean setRoot = false; for (final Node child : rootNode.getChildren()) { if (child.getName().equalsIgnoreCase(“Properties”)) { if (tempLookup == subst.getVariableResolver()) { LOGGER.error(“Properties declaration must be the first element in the configuration”); } continue; } createConfiguration(child, null); if (child.getObject() == null) { continue; } if (child.getName().equalsIgnoreCase(“Appenders”)) { appenders = child.getObject(); } else if (child.isInstanceOf(Filter.class)) { addFilter(child.getObject(Filter.class)); } else if (child.getName().equalsIgnoreCase(“Loggers”)) { final Loggers l = child.getObject(); loggers = l.getMap(); setLoggers = true; if (l.getRoot() != null) { root = l.getRoot(); setRoot = true; } } else if (child.getName().equalsIgnoreCase(“CustomLevels”)) { customLevels = child.getObject(CustomLevels.class).getCustomLevels(); } else if (child.isInstanceOf(CustomLevelConfig.class)) { final List<CustomLevelConfig> copy = new ArrayList<CustomLevelConfig>(customLevels); copy.add(child.getObject(CustomLevelConfig.class)); customLevels = copy; } else { LOGGER.error(“Unknown object "{}" of type {} is ignored.”, child.getName(), child.getObject().getClass().getName()); } } if (!setLoggers) { LOGGER.warn(“No Loggers were configured, using default. Is the Loggers element missing?”); setToDefault(); return; } else if (!setRoot) { LOGGER.warn(“No Root logger was configured, creating default ERROR-level Root logger with Console appender”); setToDefault(); // return; // LOG4J2-219: creating default root=ok, but don’t exclude configured Loggers } for (final Map.Entry<String, LoggerConfig> entry : loggers.entrySet()) { final LoggerConfig l = entry.getValue(); for (final AppenderRef ref : l.getAppenderRefs()) { final Appender app = appenders.get(ref.getRef()); if (app != null) { l.addAppender(app, ref.getLevel(), ref.getFilter()); } else { LOGGER.error(“Unable to locate appender {} for logger {}”, ref.getRef(), l.getName()); } } } setParents(); }发现就是对刚刚获取的configuration进行解析,然后塞进正确的地方。回到start方法,可以看到昨晚配置之后就是开启logger和appender了。异步AsyncAppenderlog4j2突出于其他日志的优势,异步日志实现。我们先从日志打印看进去。找到Logger,随便找一个log日志的方法。public void debug(final Marker marker, final Message msg) { logIfEnabled(FQCN, Level.DEBUG, marker, msg, msg != null ? msg.getThrowable() : null); }一路跟进@PerformanceSensitive // NOTE: This is a hot method. Current implementation compiles to 29 bytes of byte code. // This is within the 35 byte MaxInlineSize threshold. Modify with care! private void logMessageTrackRecursion(final String fqcn, final Level level, final Marker marker, final Message msg, final Throwable throwable) { try { incrementRecursionDepth(); // LOG4J2-1518, LOG4J2-2031 tryLogMessage(fqcn, level, marker, msg, throwable); } finally { decrementRecursionDepth(); } }可以看出这个在打日志之前做了调用次数的记录。跟进tryLogMessage,@PerformanceSensitive // NOTE: This is a hot method. Current implementation compiles to 26 bytes of byte code. // This is within the 35 byte MaxInlineSize threshold. Modify with care! private void tryLogMessage(final String fqcn, final Level level, final Marker marker, final Message msg, final Throwable throwable) { try { logMessage(fqcn, level, marker, msg, throwable); } catch (final Exception e) { // LOG4J2-1990 Log4j2 suppresses all exceptions that occur once application called the logger handleLogMessageException(e, fqcn, msg); } }继续跟进:@Override public void logMessage(final String fqcn, final Level level, final Marker marker, final Message message, final Throwable t) { final Message msg = message == null ? new SimpleMessage(Strings.EMPTY) : message; final ReliabilityStrategy strategy = privateConfig.loggerConfig.getReliabilityStrategy(); strategy.log(this, getName(), fqcn, marker, level, msg, t); }这里可以看到在实际打日志的时候,会从config中获取打日志的策略,跟踪ReliabilityStrategy的创建,发现默认的实现类为DefaultReliabilityStrategy,跟进看实际打日志的方法@Override public void log(final Supplier<LoggerConfig> reconfigured, final String loggerName, final String fqcn, final Marker marker, final Level level, final Message data, final Throwable t) { loggerConfig.log(loggerName, fqcn, marker, level, data, t); }这里实际打日志的方法居然是交给一个config去实现的。。。感觉有点奇怪。。跟进看看@PerformanceSensitive(“allocation”) public void log(final String loggerName, final String fqcn, final Marker marker, final Level level, final Message data, final Throwable t) { List<Property> props = null; if (!propertiesRequireLookup) { props = properties; } else { if (properties != null) { props = new ArrayList<>(properties.size()); final LogEvent event = Log4jLogEvent.newBuilder() .setMessage(data) .setMarker(marker) .setLevel(level) .setLoggerName(loggerName) .setLoggerFqcn(fqcn) .setThrown(t) .build(); for (int i = 0; i < properties.size(); i++) { final Property prop = properties.get(i); final String value = prop.isValueNeedsLookup() // since LOG4J2-1575 ? config.getStrSubstitutor().replace(event, prop.getValue()) // : prop.getValue(); props.add(Property.createProperty(prop.getName(), value)); } } } final LogEvent logEvent = logEventFactory.createEvent(loggerName, marker, fqcn, level, data, props, t); try { log(logEvent, LoggerConfigPredicate.ALL); } finally { // LOG4J2-1583 prevent scrambled logs when logging calls are nested (logging in toString()) ReusableLogEventFactory.release(logEvent); } }可以清楚的看到try之前是在创建LogEvent,try里面做的才是真正的log(好tm累),一路跟进。private void processLogEvent(final LogEvent event, LoggerConfigPredicate predicate) { event.setIncludeLocation(isIncludeLocation()); if (predicate.allow(this)) { callAppenders(event); } logParent(event, predicate); }接下来就是callAppender了,我们直接开始看AsyncAppender的append方法:/** * Actual writing occurs here. * * @param logEvent The LogEvent. / @Override public void append(final LogEvent logEvent) { if (!isStarted()) { throw new IllegalStateException(“AsyncAppender " + getName() + " is not active”); } final Log4jLogEvent memento = Log4jLogEvent.createMemento(logEvent, includeLocation); InternalAsyncUtil.makeMessageImmutable(logEvent.getMessage()); if (!transfer(memento)) { if (blocking) { if (AbstractLogger.getRecursionDepth() > 1) { // LOG4J2-1518, LOG4J2-2031 // If queue is full AND we are in a recursive call, call appender directly to prevent deadlock AsyncQueueFullMessageUtil.logWarningToStatusLogger(); logMessageInCurrentThread(logEvent); } else { // delegate to the event router (which may discard, enqueue and block, or log in current thread) final EventRoute route = asyncQueueFullPolicy.getRoute(thread.getId(), memento.getLevel()); route.logMessage(this, memento); } } else { error(“Appender " + getName() + " is unable to write primary appenders. queue is full”); logToErrorAppenderIfNecessary(false, memento); } } }这里主要的步骤就是:生成logEvent将logEvent放入BlockingQueue,就是transfer方法如果BlockingQueue满了则启用相应的策略同样的,这里也有一个线程用来做异步消费的事情private class AsyncThread extends Log4jThread { private volatile boolean shutdown = false; private final List<AppenderControl> appenders; private final BlockingQueue<LogEvent> queue; public AsyncThread(final List<AppenderControl> appenders, final BlockingQueue<LogEvent> queue) { super(“AsyncAppender-” + THREAD_SEQUENCE.getAndIncrement()); this.appenders = appenders; this.queue = queue; setDaemon(true); } @Override public void run() { while (!shutdown) { LogEvent event; try { event = queue.take(); if (event == SHUTDOWN_LOG_EVENT) { shutdown = true; continue; } } catch (final InterruptedException ex) { break; // LOG4J2-830 } event.setEndOfBatch(queue.isEmpty()); final boolean success = callAppenders(event); if (!success && errorAppender != null) { try { errorAppender.callAppender(event); } catch (final Exception ex) { // Silently accept the error. } } } // Process any remaining items in the queue. LOGGER.trace(“AsyncAppender.AsyncThread shutting down. Processing remaining {} queue events.”, queue.size()); int count = 0; int ignored = 0; while (!queue.isEmpty()) { try { final LogEvent event = queue.take(); if (event instanceof Log4jLogEvent) { final Log4jLogEvent logEvent = (Log4jLogEvent) event; logEvent.setEndOfBatch(queue.isEmpty()); callAppenders(logEvent); count++; } else { ignored++; LOGGER.trace(“Ignoring event of class {}”, event.getClass().getName()); } } catch (final InterruptedException ex) { // May have been interrupted to shut down. // Here we ignore interrupts and try to process all remaining events. } } LOGGER.trace(“AsyncAppender.AsyncThread stopped. Queue has {} events remaining. " + “Processed {} and ignored {} events since shutdown started.”, queue.size(), count, ignored); } /* * Calls {@link AppenderControl#callAppender(LogEvent) callAppender} on all registered {@code AppenderControl} * objects, and returns {@code true} if at least one appender call was successful, {@code false} otherwise. Any * exceptions are silently ignored. * * @param event the event to forward to the registered appenders * @return {@code true} if at least one appender call succeeded, {@code false} otherwise / boolean callAppenders(final LogEvent event) { boolean success = false; for (final AppenderControl control : appenders) { try { control.callAppender(event); success = true; } catch (final Exception ex) { // If no appender is successful the error appender will get it. } } return success; } public void shutdown() { shutdown = true; if (queue.isEmpty()) { queue.offer(SHUTDOWN_LOG_EVENT); } if (getState() == State.TIMED_WAITING || getState() == State.WAITING) { this.interrupt(); // LOG4J2-1422: if underlying appender is stuck in wait/sleep/join/park call } } }直接看run方法:阻塞获取logEvent将logEvent分发出去如果线程要退出了,将blockingQueue里面的event消费完在退出。AsyncLogger直接从AsyncLogger的logMessage看进去:public void logMessage(final String fqcn, final Level level, final Marker marker, final Message message, final Throwable thrown) { if (loggerDisruptor.isUseThreadLocals()) { logWithThreadLocalTranslator(fqcn, level, marker, message, thrown); } else { // LOG4J2-1172: avoid storing non-JDK classes in ThreadLocals to avoid memory leaks in web apps logWithVarargTranslator(fqcn, level, marker, message, thrown); } }跟进logWithThreadLocalTranslator,private void logWithThreadLocalTranslator(final String fqcn, final Level level, final Marker marker, final Message message, final Throwable thrown) { // Implementation note: this method is tuned for performance. MODIFY WITH CARE! final RingBufferLogEventTranslator translator = getCachedTranslator(); initTranslator(translator, fqcn, level, marker, message, thrown); initTranslatorThreadValues(translator); publish(translator); }这里的逻辑很简单,就是将日志相关的信息转换成RingBufferLogEvent(RingBuffer是Disruptor的无所队列),然后将其发布到RingBuffer中。发布到RingBuffer中,那肯定也有消费逻辑。这时候有两种方式可以找到这个消费的逻辑。找disruptor被使用的地方,然后查看,但是这样做会很容易迷惑按照Log4j2的尿性,这种Logger都有对应的start方法,我们可以从start方法入手寻找在start方法中,我们找到了一段代码:final RingBufferLogEventHandler[] handlers = {new RingBufferLogEventHandler()}; disruptor.handleEventsWith(handlers);直接看看这个RingBufferLogEventHandler的实现:public class RingBufferLogEventHandler implements SequenceReportingEventHandler<RingBufferLogEvent>, LifecycleAware { private static final int NOTIFY_PROGRESS_THRESHOLD = 50; private Sequence sequenceCallback; private int counter; private long threadId = -1; @Override public void setSequenceCallback(final Sequence sequenceCallback) { this.sequenceCallback = sequenceCallback; } @Override public void onEvent(final RingBufferLogEvent event, final long sequence, final boolean endOfBatch) throws Exception { event.execute(endOfBatch); event.clear(); // notify the BatchEventProcessor that the sequence has progressed. // Without this callback the sequence would not be progressed // until the batch has completely finished. if (++counter > NOTIFY_PROGRESS_THRESHOLD) { sequenceCallback.set(sequence); counter = 0; } } /* * Returns the thread ID of the background consumer thread, or {@code -1} if the background thread has not started * yet. * @return the thread ID of the background consumer thread, or {@code -1} / public long getThreadId() { return threadId; } @Override public void onStart() { threadId = Thread.currentThread().getId(); } @Override public void onShutdown() { }}顺着接口找上去,发现一个接口:/* * Callback interface to be implemented for processing events as they become available in the {@link RingBuffer} * * @param <T> event implementation storing the data for sharing during exchange or parallel coordination of an event. * @see BatchEventProcessor#setExceptionHandler(ExceptionHandler) if you want to handle exceptions propagated out of the handler. /public interface EventHandler<T>{ /* * Called when a publisher has published an event to the {@link RingBuffer} * * @param event published to the {@link RingBuffer} * @param sequence of the event being processed * @param endOfBatch flag to indicate if this is the last event in a batch from the {@link RingBuffer} * @throws Exception if the EventHandler would like the exception handled further up the chain. / void onEvent(T event, long sequence, boolean endOfBatch) throws Exception;}通过注释可以发现,这个onEvent就是处理逻辑,回到RingBufferLogEventHandler的onEvent方法,发现里面有一个execute方法,跟进:public void execute(final boolean endOfBatch) { this.endOfBatch = endOfBatch; asyncLogger.actualAsyncLog(this); }这个方法就是实际打日志了,AsyncLogger看起来还是比较简单的,只是使用了一个Disruptor。插件化之前在很多代码里面都可以看到final PluginManager manager = new PluginManager(CATEGORY);manager.collectPlugins(pluginPackages);其实整个log4j2为了获得更好的扩展性,将自己的很多组件都做成了插件,然后在配置的时候去加载plugin。跟进collectPlugins。 public void collectPlugins(final List<String> packages) { final String categoryLowerCase = category.toLowerCase(); final Map<String, PluginType<?>> newPlugins = new LinkedHashMap<>(); // First, iterate the Log4j2Plugin.dat files found in the main CLASSPATH Map<String, List<PluginType<?>>> builtInPlugins = PluginRegistry.getInstance().loadFromMainClassLoader(); if (builtInPlugins.isEmpty()) { // If we didn’t find any plugins above, someone must have messed with the log4j-core.jar. // Search the standard package in the hopes we can find our core plugins. builtInPlugins = PluginRegistry.getInstance().loadFromPackage(LOG4J_PACKAGES); } mergeByName(newPlugins, builtInPlugins.get(categoryLowerCase)); // Next, iterate any Log4j2Plugin.dat files from OSGi Bundles for (final Map<String, List<PluginType<?>>> pluginsByCategory : PluginRegistry.getInstance().getPluginsByCategoryByBundleId().values()) { mergeByName(newPlugins, pluginsByCategory.get(categoryLowerCase)); } // Next iterate any packages passed to the static addPackage method. for (final String pkg : PACKAGES) { mergeByName(newPlugins, PluginRegistry.getInstance().loadFromPackage(pkg).get(categoryLowerCase)); } // Finally iterate any packages provided in the configuration (note these can be changed at runtime). if (packages != null) { for (final String pkg : packages) { mergeByName(newPlugins, PluginRegistry.getInstance().loadFromPackage(pkg).get(categoryLowerCase)); } } LOGGER.debug(“PluginManager ‘{}’ found {} plugins”, category, newPlugins.size()); plugins = newPlugins; }处理逻辑如下:从Log4j2Plugin.dat中加载所有的内置的plugin然后将OSGi Bundles中的Log4j2Plugin.dat中的plugin加载进来再加载传入的package路径中的plugin最后加载配置中的plugin逻辑还是比较简单的,但是我在看源码的时候发现了一个很有意思的东西,就是在加载log4j2 core插件的时候,也就是PluginRegistry.getInstance().loadFromMainClassLoader()这个方法,跟进到decodeCacheFiles:private Map<String, List<PluginType<?>>> decodeCacheFiles(final ClassLoader loader) { final long startTime = System.nanoTime(); final PluginCache cache = new PluginCache(); try { final Enumeration<URL> resources = loader.getResources(PluginProcessor.PLUGIN_CACHE_FILE); if (resources == null) { LOGGER.info(“Plugin preloads not available from class loader {}”, loader); } else { cache.loadCacheFiles(resources); } } catch (final IOException ioe) { LOGGER.warn(“Unable to preload plugins”, ioe); } final Map<String, List<PluginType<?>>> newPluginsByCategory = new HashMap<>(); int pluginCount = 0; for (final Map.Entry<String, Map<String, PluginEntry>> outer : cache.getAllCategories().entrySet()) { final String categoryLowerCase = outer.getKey(); final List<PluginType<?>> types = new ArrayList<>(outer.getValue().size()); newPluginsByCategory.put(categoryLowerCase, types); for (final Map.Entry<String, PluginEntry> inner : outer.getValue().entrySet()) { final PluginEntry entry = inner.getValue(); final String className = entry.getClassName(); try { final Class<?> clazz = loader.loadClass(className); final PluginType<?> type = new PluginType<>(entry, clazz, entry.getName()); types.add(type); ++pluginCount; } catch (final ClassNotFoundException e) { LOGGER.info(“Plugin [{}] could not be loaded due to missing classes.”, className, e); } catch (final LinkageError e) { LOGGER.info(“Plugin [{}] could not be loaded due to linkage error.”, className, e); } } } final long endTime = System.nanoTime(); final DecimalFormat numFormat = new DecimalFormat("#0.000000”); final double seconds = (endTime - startTime) * 1e-9; LOGGER.debug(“Took {} seconds to load {} plugins from {}”, numFormat.format(seconds), pluginCount, loader); return newPluginsByCategory; }可以发现加载时候是从一个文件(PLUGIN_CACHE_FILE)获取所有要获取的plugin。看到这里的时候我有一个疑惑就是,为什么不用反射的方式直接去扫描,而是要从文件中加载进来,而且文件是写死的,很不容易扩展啊。然后我找了一下PLUGIN_CACHE_FILE这个静态变量的用处,发现了PluginProcessor这个类,这里用到了注解处理器。/* * Annotation processor for pre-scanning Log4j 2 plugins. /@SupportedAnnotationTypes(“org.apache.logging.log4j.core.config.plugins.")public class PluginProcessor extends AbstractProcessor { // TODO: this could be made more abstract to allow for compile-time and run-time plugin processing /** * The location of the plugin cache data file. This file is written to by this processor, and read from by * {@link org.apache.logging.log4j.core.config.plugins.util.PluginManager}. */ public static final String PLUGIN_CACHE_FILE = “META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat”; private final PluginCache pluginCache = new PluginCache(); @Override public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) { System.out.println(“Processing annotations”); try { final Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Plugin.class); if (elements.isEmpty()) { System.out.println(“No elements to process”); return false; } collectPlugins(elements); writeCacheFile(elements.toArray(new Element[elements.size()])); System.out.println(“Annotations processed”); return true; } catch (final IOException e) { e.printStackTrace(); error(e.getMessage()); return false; } catch (final Exception ex) { ex.printStackTrace(); error(ex.getMessage()); return false; } }}(不太重要的方法省略)我们可以看到在process方法中,PluginProcessor会先收集所有的Plugin,然后在写入文件。这样做的好处就是可以省去反射时候的开销。然后我又看了一下Plugin这个注解,发现它的RetentionPolicy是RUNTIME,一般来说PluginProcessor是搭配RetentionPolicy.SOURCE,CLASS使用的,而且既然你把自己的Plugin扫描之后写在文件中了,RetentionPolicy就没有必要是RUNTIME了吧,这个是一个很奇怪的地方。小结总算是把Log4j2的代码看完了,发现它的设计理念很值得借鉴,为了灵活性,所有的东西都设计成插件式。互联网技术日益发展,各种中间件层出不穷,而作为工程师的我们更需要做的是去思考代码与代码之间的关系,毫无疑问的是,解耦是最具有美感的关系。本文作者:netflix阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

April 10, 2019 · 19 min · jiezi

Node.js 应用故障排查手册 —— Node.js 性能平台使用指南

楔子前一节中我们借助于 Chrome devtools 实现了对线上 Node.js 应用的 CPU/Memory 问题的排查定位,但是在实际生产实践中,大家会发现 Chrome devtools 更加偏向本地开发模式,因为显然 Chrome devtools 不会负责去生成分析问题所需要的 Dump 文件,这意味着开发者还得额外在线上项目中设置好 v8-profiler 和 heapdump 这样的工具,并且通过额外实现的服务来能够去对线上运行的项目进行实时的状态导出。加上实际上预备章中除了 CPU/Memory 的问题,我们还会遇到一些需要分析错误日志、磁盘和核心转储文件等才能定位问题的状况,因此在这些场景下,仅仅靠 Chrome devtools 显然会有一些力不从心。正是为了解决广大 Node.js 开发者的这些痛点,我们在这里推荐大家在使用 Node.js 性能平台,即原来的 AliNode,它已经在阿里巴巴集团内部承载了几乎所有的 Node.js 应用线上运行监控和问题排查,因此大家可以放心在生产环境部署使用。本节将从 Node.js 性能平台 的设计架构、核心能力以及最佳实践等角度,帮助开发者更好地使用这一工具来解决前面提到的异常指标分析和线上 Node.js 应用故障定位。本书首发在 Github,仓库地址:https://github.com/aliyun-node/Node.js-Troubleshooting-Guide,云栖社区会同步更新。架构Node.js 性能平台其实简单的说由三部分组成:云控制台 + AliNode runtime + Agenthub,如下图所示:具体的部署步骤可以查看官方文档:自助式部署 Runtime。借助于 Node.js 性能平台的整套解决方案,我们可以很方便地实现预备章节中提到的绝大部分异常指标的告警分析的能力。在生产实践过程中,实际上在笔者看来,Node.js 性能平台解决方案其实仅仅是提供了三个最核心却也是最有效的能力:异常指标告警,即预备节中一些异常指标出现异常时能通过短信/钉钉通知到开发者导出线上 Node.js 应用状态,包括但不限于即前面 Chrome devtools 一节中提到的 CPU/Memory 状态导出在线分析结果和更好的 UI 展示,定制化解析应用导出状态和展示,更符合国内开发者习惯换言之,Node.js 性能平台作为一个产品本身功能也在不断迭代新增修改中,但是以上的三个核心能力一定是第一优先级保障的,其它边边角角的功能则相对来说响应优先级没有那么高。实际上我们也理解作为使用平台的开发者希望能在一个地方看到 Node.js 线上应用从底层到业务层的所有细节,然而我个人感觉不同的工具都有应该有其核心的能力输出,很多时候不断做加法容易让产品本身定位模糊化以及泛而不精,Node.js 性能平台实际上始终在致力于让原本线上黑盒的运行时状态,能更加直观地反馈给开发者,让 Node.js 应用的开发者面对一些偏向底层的线上疑难杂症能够不再无所适从。最佳实践I.配置合适的告警线上应用的告警实际上是一种自我发现问题的保护机制,如果没有告警能力,那每次都会等到问题暴露到用户侧导致其反馈才能发现问题,这显然对用户体验非常的不友好。因此部署完成一个项目后,开发者首先需要去配置合适的告警,而在我们的生产实践中,线上问题通过错误日志、Node.js 进程 CPU/Memory 的分析、核心转储(Core dump)的分析以及磁盘分析能够得出结论,因此我们需要的基本的告警策略也是源自以上五个部分。幸运的是平台已经给我们预设好了这些告警,大家只需要选择一下即可完整这里的告警配置,如下图所示:在 Node.js 性能平台 的告警页面上有 快速添加规则,点开选中后会自动生成告警规则的阈值表达式模板和报警说明模板,我们可以按照项目实际监控需求进行修改,比如想要对 Node.js 进程的堆内存进行监控,可以选中 Memory 预警 选项,如下图所示:此时点击 添加报警项 即完整了对进程堆内存的告警,并且将出现告警时需要点击 通知设置->添加到联系人列表 来添加的联系人加入此条规则,如下图所示:那么在例子中的这条默认的规则里,当我们的 Node.js 进程分配的堆内存超过堆上线的 80%(默认 64 位机器上堆上限是 1.4G)时会触发短信通知到配置绑定到此条规则的联系人。实际上快速添加规则列表中给大家提供的是最常见的一些预配置好的告警策略,如果这些尚不能满足你的需求,更多定制化的自定义的服务告警策略配置方法可以看官方文档 报警设置。并且除了短信告警,也支持钉钉机器人推送告警消息到群,方便群发感知线上 Node.js 应用态势。II. 按照告警类型进行分析按照 I 节中配置完成合适的告警规则后,那么当收到告警短信时就可以按照策略类型进行对应的分析了。本节将按照预备节中比较常见的五大类问题来逐一讲解。a. 磁盘监控这个是比较好处理的问题,在快速添加的规则里实际上我们会在服务器的磁盘使用超过 85% 时进行告警,那么收到磁盘告警后,可以连接到服务器,使用如下命令查看那个目录占用比较高:sudo du -h –max-depth=1 /找到占比比较高的目录和文件后,看看是否需要备份后删除来释放出磁盘空间。b. 错误日志收到特定的错误日志告警后,只需要去对应的项目的 Node.js 性能平台 控制台找到问题 实例 去查看其 异常日志 即可,如下图所示:这里会按照错误类型进行规整,大家可以结合展示的错误栈信息来进行对应的问题定位。注意这里的错误日志文件需要你在部署 Agenthub 的时候写入配置文件,详细可以参见文档 配置和启动 Agenthub 一节中的 详细配置 内容。c. 进程 CPU 高终于到了前一节中借助 v8-profiler 导出 CPU Profile 文件再使用 Chrome devtools 进行分析的异常类型了。那么在 Node.js 性能平台 的整套解决方案下,我们并不需要额外的去依赖类似 v8-profiler 这样的第三方库来实现进程状态的导出,与此相对的,当我们收到 Node.js 应用进程的 CPU 超过我们设置的阈值告警时,我们只需要在控制台对应的 实例 点击 CPU Profile 按钮即可:默认会给抓取的进程生成 3 分钟的 CPU Profile 文件,等到结束后生成的文件会显示在 文件 页面:此时点击 转储 即可上传到云端以供在线分析展示了,如下图所示:这里可以看到有两个 分析 按钮,其实第二个下标带有 (devtools) 的分析按钮实际上就是前一节中提到的 Chrome devtools 分析,这里不再重复讲解了,如果有遗忘的同学可以再去回顾下本大章前一节的内容。我们重点看下第一个 AliNode 定制的分析,点击第一个分析按钮后,可以在新页面看到如下所示内容:这里其实也是火焰图,但与 Chrome devtools 提供的火焰图不一样的地方在于,这里是将抓取的 3 分钟内的 JS 函数执行进行了聚合展示出来的火焰图,在一些存在多次执行同一个函数(可能每次执行非常短)的情况下,聚合后的火焰图可以很方便的帮助我们找到代码的执行瓶颈来进行对应的优化。值得一提的是,如果你使用的 AliNode runtime 版本在 v3.11.4 或者 v4.2.1 以上(包含这两个版本)的话,当你的应用出现类死循环问题,比如由于异常的用户参数导致的正则回溯(即执行完这个正则要十几年,类似于 Node.js 进程发生了死循环)这类问题时,可以通过抓取 CPU Profile 文件来很方便地定位到问题代码,详细信息有兴趣的同学可以看下 Node.js 性能平台支持死循环和正则攻击定位。d. 内存泄漏与 CPU 高的问题一样,当我们收到 Node.js 应用进程的堆内存占据堆上限的比率超过我们设置的阈值时,我们也不需要类似 heapdump 这样的第三方模块来导出堆快照进行分析,我们还是在控制台对应的 实例 点击 堆快照 按钮即可生成对应 Node.js 进程的堆快照:生成的堆快照文件同样会显示在 文件 列表页面,点击 转储 将堆快照上传至云端以供接下来的分析:与上面一样,下标带有 (devtools) 的分析按钮还是前一节中提到的 Chrome devtools 分析,这里还是着重解析下 AliNode 定制的第一个分析按钮,点击后新页面如下图所示:首先解释下上面的总览栏目的内容信息:文件大小: 堆快照文件本身的大小Shallow Szie 总大小: 回顾下上一节中的内容,GC 根的 Retained Size 大小其实就是堆大小,也等于堆上分配的所有对象的 Shallow Size 总大小,因此这里其实就是已使用的堆空间对象个数: 当前堆上分配的 Heap Object 总个数对象边个数: 这个稍微抽象一些,假如 Object A.b 指向另一个 Object B,我们则认为表示指向关系的 b 属性就是一条边GC Roots 个数: V8 引擎实现的堆分配,其实并不是我们之前为了帮助大家理解简化的只有一个 GC 根的情况,在实际的运行模型下,堆空间内存在许多的 GC 根,这里是真实的 GC 根的个数这部分的信息旨在给大家一个概览,部分信息需要深入解读堆快照才能彻底理解,如果你实在无法理解其中的几个概览指标信息,其实也无伤大雅,因为这并不影响我们定位问题代码。简单了解了概览信息的含义后,接着我们来看对于定位 Node.js 应用代码段非常重要的信息,第一个是默认展示的 可疑点 信息,上图中的内容表示 @15249 这个对象占据了堆空间 97.41% 的内存,那么它很可能就是一个泄漏对象,这里又存在两种可能:此对象本身应该被释放但是却没有释放,造成堆空间占用如此大此对象的某些属性应该被释放但是却没有释放,造成表象是此对象占据大量的堆空间要判断是哪一种情况,以及追踪到对应的代码段,我们需要点击图中的 簇视图 链接进行进一步观察:这里继续解释下什么是簇视图,簇视图实际上是支配树的一个别名,也就是这个视图下我们看到的正是前面一节中提到的从可疑泄漏对象出发的支配树视图,它的好处是,在这个视图下,父节点的 Retained Size 可以直接由其子节点的 Retained Size 累加后再加上父节点自身的 Shallow Size 得到,换言之,在这个视图下我们层层展开即可以看到可疑泄漏对象的内存究竟被哪些子节点占用了。并且结合前一节的支配树描述,我们可以知道支配树下的父子节点关系,并不一定是真正的堆上空间内的对象父子关系,但是对于那些支配树下父子关系在真正的堆空间内也存在父子节点关系的簇节点,我们将真正的 边 也用浅紫色标识出来,这部分的 边 信息对于我们映射到真正的代码段非常有帮助。在这个简单的例子中,我们可以很清晰的看到可疑泄漏对象 @15249 实际上是下属的 test-alinode.js 中存在一个 array 变量,其中存储了四个 45.78 兆的数组导致的问题,这样就找到了问题代码可以进行后续优化。而在实际生产环境的堆快照分析下,很多情况下簇视图下的父子关系在真实的堆空间中并不存在,那么就不会有这些紫色的边信息展示,这时候我们想要知道可疑泄漏对象如何通过 JavaScript 生成的对象间引用关系引用到后面真正占据掉堆空间的对象(比如上图中的 40 多兆的 Array 对象),我们可以点击 可疑节点自身的地址链接 :这样就进入到以此对象为起点的堆空间内真正的对象引用关系视图 Search 视图:这个视图因为反映的是堆空间内各个 Heap Object 之间真正的引用连接关系,因此父对象的 Retained Size 并不能直接由子节点的 Retained Size 累加获取,如上图红框内的内容,显然这里的三个子节点 Retained Size 累加已经超过 100%,这也是 Search 视图和簇视图很大的不同点。借助于 Search 视图,我们可以根据其内反映出来的对象和边之间的关系来定位可疑泄漏对象具体是在我们的 JavaScript 代码中的哪一部分生成。其实看到这边,一些读者应该意识到了这里的 Search 视图实际上对应的就是前一节中提到的 Chrome devtools 的 Containment 视图,只不过这里的起始点是我们选中的对象本身罢了。最后就是需要提一下 Retainers 视图,它和前一节中提到的 Chrome devtools 中解析堆快照展示结果里面的 Retainers 含义是一致的,它表示对象的父引用关系链,我们可以来看下:这里 globa@1279 对象的 clearImmediate 属性指向 timers.js()@15325,而 timers.js()@15325 的 context 属性指向了可疑的泄漏对象 system / Context@15249。在绝大部分的情况下,通过结合 Search 视图 和 Retainers 视图 我们可以定位到指定对象在 JavaScript 代码中的生成位置,而 簇视图 下我们又可以比较方便的知道堆空间被哪些对象占据掉了,那么综合这两部分的信息,我们就可以实现对线上内存泄漏的问题进行分析和代码定位了。e. 出现核心转储最后就是收到服务器生成核心转储文件(Core dump 文件)的告警了,这表示我们的进程已经出现了预期之外的 Crash,如果你的 Agenthub 配置正常的话,在 文件 -> Coredump 文件 页面会自动将生成的核心转储文件信息展示出来:和之前的步骤类似,我们想要看到服务端分析和结果展示,首先需要将服务器上生成的核心转储文件转储到云端,但是与之前的 CPU Profile 和堆快照的转储不一样的地方在于,核心转储文件的分析需要我们提供对应 Node.js 进程的启动执行文件,即 AliNode runtime 文件,这里简化处理为只需要设置 Runtime 版本即可:点击 设置 runtime 版本 即可进行设置,格式为 alinode-v{x}.{y}.{z} 的形式,比如 alinode-v3.11.5,版本会进行校验,请务必填写你的应用真实在使用的 AliNode runtime 版本。版本填写完成后,我们就可以点击 转储 按钮进行文件转储到云端的操作了:显然对于核心转储文件来说,Chrome devtools 是没有提供解析功能的,所以这里只有一个 AliNode 定制的分析按钮,点击这个 分析 按钮,即可以看到结果:这里第一栏的概览信息看文字描述就能理解其含义,所以这里就不再多做解释了,我们来看下比较重要的默认视图 BackTrace 信息视图,此视图下展示的实际上是 Node.js 应用在 Crash 时刻的线程信息,许多开发者认为 Node.js 是单线程的运行模型,其实这句话也不是完全错误,更准确的说法是 单主 JavaScript 工作线程,因为实际上 Node.js 还会开启一些后台线程来处理诸如 GC 里的部分任务。绝大部分的情况下,应用的 Crash 都是由 JavaScript 工作线程引发的,因此我们需要关注的也仅仅是这个线程,这里显然 BackTrace 信息视图中将 JavaScript 工作线程做了标红和置顶处理,展开后可以看到 Node.js 应用 Crash 那一刻的错误堆栈信息:因为就算在 JavaScript 的工作线程中,也会存在 Native C/C++ 代码的穿透,但是在问题排查中我们往往只需要去看同样标红的 JavaScript 栈信息即可,像在这个简单的例子中,显然 Crash 是因为视图去启动一个不存在的 JS 文件导致的。值得一提的是,核心转储文件的分析功能非常的强大,因为在预备节中我们提到其生成的途径除了 Node.js 应用 Crash 的时候由系统内核控制输出外,还可以由 gcore 这样的命令手动强制输出,而本小节我们又看到核心转储文件的分析实际上可以看到此刻的 JavaScript 栈信息以及其入参,结合这两点,我们可以在线上出现 CPU Profile 一节中提到的类死循环问题时直接采用 gcore 生成核心转储文件,然后上传至平台云端进行分析,这样不仅可以看到我们的 Node.js 应用是阻塞在哪一行的 JavaScript 代码,甚至引发阻塞的参数我们也能完整获取到,这对本地复现定位问题的帮助无疑是无比巨大的。结尾本节其实给大家介绍了 Node.js 性能平台 的整套面向 Node.js 应用开发的监控、告警、分析和定位问题的解决方案的架构和最佳实践,希望能让大家对平台的能力和如何更好地结合自身项目进行使用有一个整体的理解。限于篇幅,最佳实践中的 CPU Profile、堆快照和核心转储文件的分析例子都非常的简单,这部分的内容也更多的是旨在帮助大家理解平台提供的工具如何使用以及其分析结果展示的指标含义,那么本书的第三节中,我们会通过一些实际的生产遇到的案例问题借助于 Node.js 性能平台 提供的上述工具分析过程,来帮助大家更好的理解这部分信息,也希望大家在读完这些内容后能有所收获,能对 Node.js 应用在生产中的使用更有信心。本文作者:奕钧阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

April 3, 2019 · 3 min · jiezi

Node.js 应用故障排查手册 —— 冗余配置传递引发的内存溢出

楔子前面一小节我们以一个真实的压测案例来给大家讲解如何利用 Node.js 性能平台 生成的 CPU Profile 分析来进行压测时的性能调优。那么与 CPU 相关的问题相比,Node.js 应用中由于不当使用产生的内存问题是一个重灾区,而且这些问题往往都是出现在生产环境下,本地压测都难以复现,实际上这部分内存问题也成为了很多的 Node.js 开发者不敢去将 Node.js 这门技术栈深入运用到后端的一大阻碍。本节将以一个开发者容易忽略的生产内存溢出案例,来展示如何借助于性能平台实现对线上应用 Node.js 应用出现内存泄漏时的发现、分析、定位问题代码以及修复的过程,希望能对大家有所启发。本书首发在 Github,仓库地址:https://github.com/aliyun-node/Node.js-Troubleshooting-Guide,云栖社区会同步更新。最小化复现代码因为内存问题相对 CPU 高的问题来说比较特殊,我们直接从问题排查的描述可能不如结合问题代码来看比较直观,因此在这里我们首先给出了最小化的复现代码,大家运行后结合下面的分析过程应该能更有收获,样例基于 Egg.js:如下所示:‘use strict’;const Controller = require(’egg’).Controller;const DEFAULT_OPTIONS = { logger: console };class SomeClient { constructor(options) { this.options = options; } async fetchSomething() { return this.options.key; }}const clients = {};function getClient(options) { if (!clients[options.key]) { clients[options.key] = new SomeClient(Object.assign({}, DEFAULT_OPTIONS, options)); } return clients[options.key];}class MemoryController extends Controller { async index() { const { ctx } = this; const options = { ctx, key: Math.random().toString(16).slice(2) }; const data = await getClient(options).fetchSomething(); ctx.body = data; }}module.exports = MemoryController;然后在 app/router.js 中增加一个 Post 请求路由:router.post(’/memory’, controller.memory.index);造成问题的 Post 请求 Demo 这里也给出来,如下所示:‘use strict’;const fs = require(‘fs’);const http = require(‘http’);const postData = JSON.stringify({ // 这里的 body.txt 可以放一个比较大 2M 左右的字符串 data: fs.readFileSync(’./body.txt’).toString()});function post() { const req = http.request({ method: ‘POST’, host: ’localhost’, port: ‘7001’, path: ‘/memory’, headers: { ‘Content-Type’: ‘application/json’, ‘Content-Length’: Buffer.byteLength(postData) } }); req.write(postData); req.end(); req.on(’error’, function (err) { console.log(12333, err); });}setInterval(post, 1000);最后我们在启动完成最小化复现的 Demo 服务器后,再运行这个 Post 请求的客户端,1s 发起一个 Post 请求,在平台控制台可以看到堆内存在一直增加,如果我们按照本书工具篇中的 Node.js 性能平台使用指南 - 配置合适的告警 一节中配置了 Node.js 进程堆内存告警的话,过一会就会收到平台的 短信/邮件 提醒。问题排查过程收到性能平台的进程内存告警后,我们登录到控制台并且进入应用首页,找到告警对应实例上的问题进程,然后参照工具篇中的 Node.js 性能平台使用指南 - 内存泄漏 中的方法抓取堆快照,并且点击 分析 按钮查看 AliNode 定制后的分解结果展示:这里默认的报表页面顶部的信息含义已经提到过了,这里不再重复,我们重点来看下这里的可疑点信息:提示有 18 个对象占据了 96.38% 的堆空间,显然这里就是我们需要进一步查看的点。我们可以点击 对象名称 来看到这18 个 system/Context 对象的详细内容:这里进入的是分别以这 18 个 system/Context 为根节点起始的支配树视图,因此展开后可以看到各个对象的实际内存占用情况,上图中显然问题集中在第一个对象上,我们继续展开查看:很显然,这里真正吃掉堆空间的是 451 个 SomeClient 实例,面对这样的问题我们需要从两个方面来判断这是否真的是内存异常的问题:当前的 Node.js 应用在正常的逻辑下,是否单个进程需要 451 个 SomeClient 实例如果确实需要这么多 SomeClient 实例,那么每个实例占据 1.98MB 的空间是否合理对于第一个判断,在对应的实际生产面临的问题中,经过代码逻辑的重新确认,我们的应用确实需要这么多的 Client 实例,显然此时排查重点集中在每个实例的 1.98MB 的空间占用是否合理上,假如进一步判断还是合理的,这意味着 Node.js 默认单进程 1.4G 的堆上限在这个场景下是不适用的,需要我们来通过启动 Flag 调大堆上限。正是基于以上的判断需求,我们继续点开这些 SomeClient 实例进行查看:这里可以很清晰的看到,这个 SomeClient 本身只有 1.97MB 的大小,但是下面的 options 属性对应的 Object@428973 对象一个就占掉了 1.98M,进一步展开这个可疑的 Object@428973 对象可以看到,其 ctx 属性对应的 Object@428919 对象正是 SomeClient 实例占据掉如此大的对空间的根本原因所在!我们可以点击其它的 SomeClient 实例,可以看到每一个实例均是如此,此时我们需要结合代码,判断这里的 options.ctx 属性挂载到 SomeClient 实例上是否也是合理的,点击此问题 Object 的地址:进入到这个 Object 的关系图中:Search 展示的视图不同于 Dom 结果图,它实际上展示的是从堆快中解析出来的原始对象关系图,所以边信息是一定会存在的,靠边名称和对象名称,我们比较容易判断对象在代码中的位置。但是在这个例子中,仅仅依靠以 Object@428973 为起始点的内存原始关系图,看不到很明确的代码位置,毕竟不管是 Object.ctx 还是 Object.key 都是相当常见的 JavaScript 代码关系,因此我们继续点击 Retainer 视图:得到如下信息:这里的 Retainer 信息和 Chrome Devtools 中的 Retainer 含义是一样的,它代表了节点在堆内存中的原始父引用关系,正如本文的内存问题案例中,仅靠可疑点本身以及其展开无法可靠地定位到问题代码的情况下,那么展开此对象的 Retainer 视图,可以看到它的父节点链路可以比较方便的定位到问题代码。这里我们显然可以通过在 Retainer 视图下的问题对象父引用链路,很方便地找到代码中创建此对象的代码:function getClient(options) { if (!clients[options.key]) { clients[options.key] = new SomeClient(Object.assign({}, DEFAULT_OPTIONS, options)); } return clients[options.key];}结合看 SomeClient 的使用,看到用于初始化的 options 参数中实际上只是用到了其 key 属性,其余的属于冗余的配置信息,无需传入。代码修复与确认知道了原因后修改起来就比较简单了,单独生成一个 SomeClient 使用的 options 参数,并且仅将需要的数据从传入的 options 参数上取过来以保证没有冗余信息即可:function getClient(options) { const someClientOptions = Object.assign({ key: options.key }, DEFAULT_OPTIONS); if (!clients[options.key]) { clients[options.key] = new SomeClient(someClientOptions); } return clients[options.key];}重新发布后运行,可以到堆内存下降至只有几十兆,至此 Node.js 应用的内存异常的问题完美解决。结尾本节中也比较全面地给大家展示了如何使用 Node.js 性能平台 来排查定位线上应用内存泄漏问题,其实严格来说本次问题并不是真正意义上的内存泄漏,像这种配置传递时开发者图省事直接全量 Assign 的场景我们在写代码时或多或少时都会遇到,这个问题带给我们的启示还是:当我们去编写一个公共组件模块时,永远不要去相信使用者的传入参数,任何时候都应当只保留我们需要使用到的参数继续往下传递,这样可以避免掉很多问题。本文作者:奕钧 阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

April 1, 2019 · 2 min · jiezi

使用weixin-java-miniapp配置进行单个小程序的配置

在进行小程序后端接口开发方面,使用weixin-java-tools中的weixin-java-miniapp模块,往往可以事半功倍。引入weixin-java-tools在https://mvnrepository.com/中搜索weixin-java-miniapp,进入微信小程序 Java SDK这个项目中。选择相应正式版本来进行使用。maven中在依赖中添加如下配置项:<dependency> <groupId>com.github.binarywang</groupId> <artifactId>weixin-java-miniapp</artifactId> <version>3.3.0</version></dependency>gradle中添加如下配置项:compile(“com.github.binarywang:weixin-java-miniapp:3.3.0”)注意:以上我用的版本是3.3.0,实际中根据你要使用的版本来用。配置文件配置文件中主要配置四项参数,分别是:appIdsecrettokenaesKey配置初始化:weixin-java-miniapp可以使用注解来进行配置,具体步骤如下:在config包中创建WxMaConfiguration类。使用@Configuration注解来进行小程序相关的参数配置,可参考以下代码。该代码示例中是单个小程序配置示例,如果需要配置多个小程序的参数,请参考官方案例点击进入。package com.diboot.miniapp.config;import cn.binarywang.wx.miniapp.api.WxMaService;import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;import cn.binarywang.wx.miniapp.config.WxMaInMemoryConfig;import dibo.framework.config.BaseConfig;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configurationpublic class WxMaConfiguration { // 此处获取配置的方式可以改成你自己的方式,也可以注解等方式获取配置等。 private static final String appId = BaseConfig.getProperty(“wechat.appId”); private static final String secret = BaseConfig.getProperty(“wechat.secret”); private static final String token = BaseConfig.getProperty(“wechat.token”); private static final String aesKey = BaseConfig.getProperty(“wechat.aesKey”); private static WxMaService wxMaService = null; @Bean public Object services(){ WxMaInMemoryConfig config = new WxMaInMemoryConfig(); config.setAppid(appId); config.setSecret(secret); config.setToken(token); config.setAesKey(aesKey); wxMaService = new WxMaServiceImpl(); wxMaService.setWxMaConfig(config); return Boolean.TRUE; } public static WxMaService getWxMaService(){ return wxMaService; }}开始使用在需要使用小程序相关接口的地方,只需要通过该配置类中的静态方法getWxMaService()来获取到wxMaService即可开始使用,如: // 获取小程序服务实例WxMaService wxMaService = WxMaConfiguration.getWxMaService();// 获取小程序二维码生成实例WxMaQrcodeService wxMaQrcodeService = wxMaService.getQrcodeService();// 便可以开始使用wxMaQrcodeService来进行二维码相关的处理了…. ...

March 28, 2019 · 1 min · jiezi

我在阿里云做前端

前言今年是我毕业的第10个年头,半路出家做了前端,title一直是前端,你可以说我很专注,有时候也有些遗憾。一直以来,当别人问起你是做什么的,我说前端或者全栈,别人说:哦,做页面的啊!心里难免有些失落。前端是个资源型角色,在认知里对业务的理解深度不够,加上通常负责业务领域很广,比较难有积累和沉淀。如果你问一个毕业10年的JAVA老司机,他跟你谈的一定是大流量下的分布式架构,而前端可能还是昨天茶余饭后讨论vue和react,或者是angular谁更强。如何突破,如何提供业务更多价值,前端们一直在苦苦探寻。网上很多文章,给人启发,但每个人面对的环境,负责的业务不同,不一定都适用。结合自己过去几年在阿里云的前端经验,也做个总结。1.0版本-工具&团队今年是我来阿里云的第五个年头了,从没有想过会在一个公司呆的如此之久,更没想过我能在一个岗位上沉淀4、5年。刚入职在阿里云控制台团队,主要负责云盾控制台、drds控制台等,开发过程中发现大部分场景是「表格」、「表单」,为了避免自己不断重复开发,封装了simpleForm以及控制台cli脚手架,可以做到新开发控制台一键敲定(这个脚手架直到去年还有人问我如何用……也是经久不衰)。这个时候也萌生了做个ide,可视化搭建UI视图,不过限于精力和当时团队的方向,且当时vscode还没今天这么流行,没有尝试,比较遗憾。不过做WebIDE这个点,算是在心里种下了。后由于组织结构调整,我从控制台团队独立出来,负责当时的网站运营方向,开始比较艰难的从0-1组件团队过程。当时业务相对比较简单,主要是:阿里云官网以及官网的Nodejs、云市场业务。由于在控制台团队主要用的angularjs,感觉上手成本比较大,在建立新团队,以及我自己可以选择的时候,React成了我的首选。当时vue还没成熟,其实能选的也只有react。新的技术体系,需要有配套的工程化体系,当时Def还处在1.0时期,为了稳定以及减少开发成本,很自然我们在xef上做了插件式开发,结合自己业务特性,分装了响应的模板,以及定制了开发周期。后由于xef1.0升级2.0,导致我们工具的不稳定,且改动非常大,逐步将我们的工程化体系独立,这就有了后续的DBL(当时叫屌爆了)。我跟老板做汇报时,老板觉得这名字上不了大雅之堂,还硬是憋了个比较有内涵的名字——实在不好记,我现在都记不起来了。这个阶段,我们做了很多技术上的尝试,团队成员都非常苦逼,也非常有激情。团队基础设施建设,我们一直在优化,随着Dawn的基于中间件式的pipeline方式设计,可以说是将工程化做到一个比较高的高度,未来不管是webpack升级到多少,或者新的打包工具出现,对我们来说影响都比较小。面向未来的模式,让我们只需要修改内核,使用者无感知。新的工程化方案也积极跟阿里云其他团队沟通和交流,之前跟风驰和释然也达成一致意见作为阿里云统一构建工具推进,不过落地的不是很好。同时,新的方案也完全遵循集团的标准,跟淘宝阿大团队无缝对接。另外还有一个好处是:dawn不局限在react,你可以使用任何框架;dawn不局限在redux,你可以使用任何你喜欢的数据管理,实际上我们自己有用mota,mobx,graphql-apollo等等。Dawn连接:https://github.com/alibaba/dawn讲完工程化,其实应该讲讲组件化,这是个无法回避的问题,但这对我们来说也是个艰难的过程。15年的时候,我们用过antd(已开源),但是在上层做了一层业务封装;后来fusion开始盛行,在跟ued沟通后,考虑到集团统一,用了一段时间的fusion(已开源);最后我们还是选择了自建组件库,这是一个很无奈的举动。具体细节不表,其中一个重要原因是我们的前台业务需要「阿里云自己的设计元素」,在经过团队很长时间的建设,APS组件库已经作为团队组建库的基础,在其之上构建了业务组件,并在之上构建了业务解决方案。除了折腾传统前端领域,也尝试做了很多跨栈的事情。在我所负责的业务中,由于「端」业务的确实,我们更多的是偏「全栈」。前端同学做全栈,讲实话是不行的——绝大部分前端同学在架构、运维部署方面还是经验偏少,考虑更多的是跟展现层相关。在全栈路径上,由于我们负责的是核心交易链路,我们没有用大家熟悉的nodejs,而选择跟后端一样的语言——Java。做这个决策,其实是挺困难的,也是有故事的。原先有个系统,前端同学用Nodejs写的,但由于业务非常复杂,加上前端一直是个资源瓶颈的角色,一个人干三个人的活,所以这个同学最后搞不定,离职了。这么个系统就变成了后端想接无法接,前端「没人力」接的状况,非常尴尬。从那以后,业务系统中就决定了直接使用Java。在全栈路上,我们主要有两个策略:大前端下自己写部分业务的Java利用serverless通过代理统一配置化转大前端写Java,阿里云其实非常多的前端都已经具备了这个能力,我自己也有独立开发几个系统从0-1上线,分布式部署且支持国际化的经历。做了一段时间后发现,其实效率上还好,并没有传说中nodejs比Java要高效很多的体感.利用serverless通过代理统一配置化转,有段时间看社区有部分人提到Graphql,对此产生了兴趣,就顺便了解了下,通过代理的方式可以将后端数据转换成前端需要的格式,非常吸引人,也就一下子扎进去。我自己也同时做了Nodejs的版本给团队同学普及,同时做了Java版本的demo给后端普及。同时了解到b2b的Mbox平台跟我们想要的能力比较像,找过他们给我们分享,但由于业务系统整个搭建在他们平台有一定得风险,于是决定了自建代理平台,这也是「云查询」平台的背景。云查询主要是战锋主导并推进落地的,在集团内取得了不错的影响力,很多BU很多部门去做过分享。在业务上,通过云查询,我们实现了不用管应用的运维和部署,实现业务逻辑和接口的转换,并自动扩容。尤其是营销体系,在元策&隐天和战锋得协同下,取得了比较大的效率提升,并支持了阿里云去年的双十一。具体「云查询」文章介绍可以看我另外一篇文章。云查询经过一段比较长时间的发展,我们已经逐步将它作为基础能力下沉,在云查询的serverless之上长出了不同的「轻应用」,以支持不同的垂直业务场景。比如:可视化搭建领域「页橱」、基于权限&角色的中后台解决方案「Flex」等;还记得我之前说过5年前我想做WebIde,没有开始;2年前,看到其他云厂商有WebIDE,我们由于业务压力,业务压力没有搞成;今年我们总算是有一点启动,已经和appStudio的同学在共建,基于appStudio基础之上把我们的dawn、云查询做打通,做云端集成化、一站式的研发体验。通过以上的技术基础建设,已经为我们构建了很好的基础基础。业务布局对应着团队、组织的建设。过去几年,团队从0-1建设,到目前xx个在岗同学,形成了4个梯队,三个3业务方向&一个技术架构方向,一路走来,感觉带团管理水很深,很多时候不是说你带的人越多越好,最近看到一本书提到一个词「情感成熟」,这个非常重要。一个技术好,做业务的好手未必能管理好团队,在不同阶段需要适应不同角色的要求,最重要的是时刻保持忧患意识、保持持续学习。在团队建设时,需要重点区分manager和leader,尤其是业务团队我们更希望成为leader,去带着做业务,而不仅仅是做绩效管理。2.0,也就是过去一年,我们在业务思维指导下,owner了部分业务,并利用横向的技术打通、横向的业务思维,取到了一些成果,接下来进入2.02.0业务思维-以横向视角推进业务赋能我们通常把组织中的人分为:一字型、|字型、T字型、+字型。前端正好是—字型团队,负责的业务非常广,而前端又是个非常稀缺的岗位,招聘很困难,所以盘子越大资源瓶颈越明显。「一字型」角色团队,典型的问题就是对业务的深度理解不够,单纯从技术层面去做营销的搭建、中后台的可视化,结果都不尽如人意。这么说起来,可能你觉得没法往下看了,天花板在那里,如何突破其实并没有太多可参考的例子。我写这篇总结,正是有些这样的感悟,希望给大家做一些输入,帮助大家去思考。「一字型」虽然从业务上看我们的深度不够,但从专业技能看我们是标准的「|字型」。前端经过这10来年的发展,语言、框架、工具已经逐步趋于稳定,各种端的性能也越来越流畅,生态非常活跃,任何你碰到的困难相信社区都已经有比较成熟的方案。前端生态快速发展的10年,也验证了我们这些人有着非常强大的学习能力,7天一个框架、3天一个数据库估计都不是太大难事(略夸张,但表达的是这么个意思)。前端直接对接客户,对客户体验的敏感、对流程数据化的敏感、对业务逻辑流程的感知,都是我们生存的根,也是我们独一无二的能力,这个根我们不能丢。有句话叫:饱暖思淫欲,不太恰当,姑且一比。在前端纵深领域的深耕,让我们成为了「紧缺资源」,随着工具的完善,前端们也更希望利用技术为业务赋能。如何赋能?挡在我们面前的是「意识」。在营销中,认知、需求、品牌、品类、价格五个要素中,「认知」最为重要。比如阿里是做电商的、腾讯是做社交的、百度是做搜索的,bat在自己主营业务范畴不断布局,构建了庞大的生态,做过很多尝试,但看起来最终还是围绕本身的基因做生态投资成功率要高一些。那我们想要业务赋能,瓶颈就在于「你个切页面」也要赋能吗?好好做好体验、提效不好吗?我认为「体验、提效」这是前端最核心的能力,也是毕生都努力要实现的目标,坦白讲我们没法马上解决资源瓶颈问题,毕竟现在毕业生都在应聘算法、ai、人工智能;我们也没办法搞一轮体验提升计划;这是个很漫长的过程。但如果我们能以业务的角度出发,去发现问题进而辅助以技术手段解决,并沉淀平台,应对未来千变万化的需求,可能更为实际一些。做为团队的TL,除了在专业上给与同学「|」型的能力纵深,也更希望带着团队同学获得更多业务体感。离开业务谈技术、谈中台都是空中楼阁;离开业务谈前端,注定只能是重复造轮子,而这种低水平的重复正在发生,且可能会持续很久。在很长一段时间里,我都试图把我们「一字型」业务广度做个抽象和融合,希望把「点状」形成「线」,进而形成整体「面」解决方案。我所负责的业务中,主要有4个大方向:官网&营销—for长尾商业化流程后台-for 小二核心售卖流程—核心能力层销售、合作伙伴官网&营销:主要包含获客、激活、转换、留存几个节点,构建高效的「人」、「货」、「场」。很长一段时间里,阿里云的内容维护、营销大促都是基于集团CMS来的。传统大促会场、卡片的方式,前端挖坑后运营编辑内容,而阿里云的「商品」跟淘系有着比较大的差别,另外我们也没有招商、选品的体系,导致这种简单人肉运行的大促方式存在很多弊端,比如不高效、不复用、不能做个性化、数据流程监控力度不够精细等。此外「投放」能力的建设还不够,没有办法做到精细化的人群做精准的营销内容投放。为了解决这些问题,去年开始,由前端团队和pd一起推进完善的营销体系建设:在原有商品的基础上,构建了「营销商品」的概念。更抽象的「货」,且可视化在线配置直接拉取了实时价格和库存。通过我们1.0工具建设的dawn,打通开发流程,使得整个开发链路一致,成本更低。可抽象的货匹配上专门为货打造的UI视图,形成场景能力沉淀。构建ACE(Alibaba Cloud Experience)架构体系,打通搭建体系,通过技术降级打通各类「场」,更好的承载好营销商品的投放。通过全链路场景的曝光,点击,转化,以及最终成交的商品信息等数据的积累,生成用户画像,提供内容场景化方案(在不同场景中精确得向用户展示商品或信息)完善「人」的定位。商业化商品配置:上面提到「营销商品」时提到「基础商品」。目前阿里云基础商品主要分为:「公有云商品」和「技术输出型」。过去很长一段时间我们偏公有云的能力建设,今年年初才开始逐步融入专有云体系。在商业化系统中,我们的小二需要配置售卖规则、价格,需要定义商品模型;同时复杂的规格之间的约束,异常复杂。如何提高商业化的输入和输出的强体验,我们还有很长的路要走。结合今年的专有云项目,从模板的方式出发,将一类产品做个聚合,简化商品模型配置的步骤,大大提高了配置效率,提高体验。销售&合作伙伴:15年刚开始组建团队(这里指的都是前端团队,不是业务团队),15年-18.3月大部门的核心kpi是营收、是首购用户数,主打的是中长尾客户,获得了非常高速的市场增长。后来团队cover范围不断扩大,也负责销售&合作伙伴体系,围绕着「市场营销」、「商机培育」、「商机转化」、「合同履约」构建了我们自己的销售crm系统。toC的业务通常比较好理解,毕竟我们也是c的一员。这段toB的经历,结合业务一号位的培训班,让了解到销售系统的核心,除了工具,最想要的是解决方案,是产品能力的丰富。大概介绍了各个方向的业务,回到我们讨论的主题来——借助横向优势,整合资源&架构提供业务赋能。为了分析他们之间的共性,我们经过很多次的讨论,终于汇聚得到我们的业务流程大图(对外脱敏后的示意图):从这个流程大图中,各个分支最后都需要依赖「售卖能力」,这个售卖能力表现在营销中是「弹窗buy(减少跳出,直接购买)」、购物车(多产品交叉购买、数据算法推荐)、套餐(多产品打包优惠售卖)、提货券(下单和生产分离的售卖能力);表现在销售链路中是「产品配置清单」、「采购单」、「CBM提供给大客户的CTO价格计算器」表现在商品商业化链路中是「模板化」配置清单能力在一大团子中找到业务的共性「售卖能力」,在经历一段时间比较耗资源、耗时的烟囱式开发方式后,抽象出了售卖的核心支持层——紫金阙。这一层,我们定位为业务中台,偏前端层面,也是大前端的领域范畴。唯一需要指出的是,我们用的是Java,没有用nodejs,无其他差别。最后架构如下(脱敏,细节忽略):新的架构模式下,我们减少了大量的前后端沟通,比如「分销采购市场」以传统开发方式需要1-2个月,我们2周就搞定了。新的架构模式,在可预见的未来,可以很好的支持各种营销新玩法,也可以支持销售和合作伙伴的『解决方案』。我想说的是,如果没有我们全量业务的横向视角,我们的抽象方案不会这么通用,这是前端团队的优势。如果没有大前端稳定的技术生态,我们也没机会去做业务赋能。这才是前端的未来,利用横向优势整合,结合某个领域做深做透,形成垂直深度,为业务提供价值,也让我们的技术方案「有的放矢」。前端经常是围绕一个点做需求,得到工具,但无法提供解决方案,因为没有业务属性;唯有结合业务特性,做好沉淀,工具变成平台才能释放更大价值。3.0探索以技术能力为业务提供增值「云计算」核心是解决企业成本的问题,用低成本获得超强的计算、存储能力,获得高并发下弹性扩容的能力。云计算提出了很多概念:IAAS、PAAS、SAAS。。。相对前端角色来讲,体感并不是很强。但是BAAS的出现,让前端眼前一亮。试下想,原先我们需要大量后台开发的应用,逐步都沉淀成领域能力,提供baas服务给前端调用,前端再也不用考虑部署、运维,只关心业务代码,想想也是心动。目前市面上提供类似服务的公司很多,有专门做后台数据存储的如Leancloud、有做数据分析的、有做消息推送的等。所以,Baas会是前端的春天吗?这个拭目以待。扯了理想,我们也说说现状。目前阿里云大概是Buy In Aliyun,我们售卖的是IAAS层的资源,用户核心的业务流程还是基于自己的研发体系。在前端这个纵深领域内,基于云打造「云端一站式研发流程」,将企业前端变成:Work In Aliyun or Dev In Aliyun。通过对企业前端生命周期的分解,通过WebIDE来承载整个流程:1. 将创建关联阿里云的code2.阿里云前端构建工具dawn作为基础构建能力,可定制化团队构建的中间件(webpack、lint、server、mock等)、构建stage(init、dev、test、publish);基于工程化化能力提供统一的规范,提供各种不同应用框架的初始化模板。3.代码点击发布后,自动编译,并发布到cdn。在此基础流程之上,我们提供serverless相关能力,通过调用BaaS领域服务能力,以及FaaS网关触发能力,实际上我们可以完全一站式,且是前端主导的应用开发。还记得我前面提到我们的serverless应用「云查询」,这一层我们逐步进行能力下沉,变成serverless基础能力。各公司几乎都有营销搭建体系,过去搭建的玩法不够多样,主要依托cms能力自行开发,随着现在各种「端」能力的延伸、多样性化,营销搭建也变得越来越复杂。而我们基于「云查询」之上沉淀出的「页橱」搭建体系,完全可以借助「云端一站式研发流程」提供很好的SAAS化服务。这是我们的优势,「云端前端解决方案」也只有我们适合做这个,这里只列举了其中一个场景,类似的机会还有很多。总体感觉,一云多端借助serverless前端的春天已然来临。抓住我们核心的竞争力,并同时发现业务中的问题,跨端推进解决,这是最好的出路。你问我做什么的,我…… 我就是阿里云CPO(首席页面仔啊)ps:阿里云智能业务中台&阿里通信招P6-P8前端,欢迎来撩。base可北京可杭州,杭州工位在美丽的西溪园区哦。旁边挨着的都是UED妹子&测试妹子。xiaoming.dxm@alibaba-inc.com本文作者:城池cc阅读原文本文为云栖社区原创内容,未经允许不得转载。

March 28, 2019 · 1 min · jiezi

微服务的集中化配置 | 从0开始构建SpringCloud微服务(13)

照例附上项目github链接。本项目实现的是将一个简单的天气预报系统一步一步改造成一个SpringCloud微服务系统的过程。本章主要讲解微服务的集中化配置。微服务为什么需要集中化配置微服务数量多,配置多手工管理配置繁琐使用Config实现Server端的配置中心集成Config Server添加依赖 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency>@EnableConfigServer注解//@ServletComponentScan(basePackages=“com.demo.web.servlet”)@SpringBootApplication@EnableDiscoveryClient@EnableConfigServerpublic class Sifoudemo02Application { public static void main(String[] args) { SpringApplication.run(Sifoudemo02Application.class, args); }}修改配置文件spring.application.name: msa-weather-config-serverserver.port= 8088eureka.client.serviceUrl.defaultZone: http://localhost:8761/eureka/spring.cloud.config.server.git.uri=https://github.com/ShimmerPig/spring-cloud-config-server-test01spring.cloud.config.server.git.searchPaths=config-repo这里指定的是我github上面的一个仓库将服务端启动后,发送请求进行访问,可以查看在云端配置中心的配置文件,当以后需要使用繁多的配置时,我们可以通过此方法对服务进行集中化的配置。

March 16, 2019 · 1 min · jiezi

在 Ali Kubernetes 系统中,我们这样实践混沌工程

在传统的软件测试中,我们通常通过一个给定的条件来判断系统的反馈,通过断言来判断是否符合预期,测试条件和结果通常比较明确和固定。而混沌工程,是通过注入一些“不确定”因素,象放进了一群淘气的猴子,在系统资源、可用性、安全性、延迟、压力等方面进行捣乱,而此过程中,要求系统可以毫无影响的提供服务,用户无感知。这其实对系统的自愈能力,健壮性都有很高的要求。故障注入一般是指比较受控的一些实验条件,通过注入一些相对极端的异常场景,为系统提供可靠性测试的过程。 整体来说,混沌是一种故障注入规则,强调了一些不确定性、随机性,比较常见的"猴子"有 Netflix 的"猴子军团",可以用来随机关闭系统实例,注入延时,回收资源,检查安全漏洞等等。开源工具介绍除了一般系统的 monkey,基于 Kubernetes 已经有一些"猴子"工具可以测试系统的健壮性。接下来,介绍一下比较常见的三种 Kubernetes monkey:kube-monkeyhttps://github.com/asobti/kube-monkey运行方式:kube-monkey 通过 label 设置受害者 pod,创建了一个单独的 kube-monkey pod 对受害者 pod 施加影响;注入类型:目前支持的故障注入类型仅有杀容器;配置项:可以通过配置文件设置运行周期和频率,在一定时间内随机的杀死打标范围内的 pod。powerfulsealhttps://github.com/bloomberg/powerfulseal注入类型:powerfulseal 的故障注入类型包括杀 pod 和启停 node。运行方式:包括交互模式,自动模式、打标模式和示例模式。交互模式通过界面交互查询node/namespace/pod,启停 node 或杀死 pod 操作;自动模式通过读取配置文件确定注入范围,注入频率;打标模式通过给 pod 打标确定注入的靶向 pod 及注入频率;示例模式可以反映根据使用资源情况进行故障注入的过程。Chaos Toolkit-kubernetes[https://github.com/chaostoolk…;是 chaos 工具包中的一个,通过 chaos run experiment.json 设置 json 文件来指定 namespace,正则匹配名字等等来随机杀一个 pod。以上三种"猴子",主要是基于杀 pod 场景来注入故障,虽然是最有力的场面但是比较有局限性,对于商业化系统面临的复杂场景,是值得参考但是不够的。](https://github.com/chaostoolk…结合 Ali Kubernetes 故障场景分析Ali Kubernetes 作为一个管理大规模集群的商业调度系统,需要应对的不仅包括一些基本的 Kubernetes 中 pod 误删误停的故障现象,也包含一些底层 OS、内核、网络、误配置等灾难场景。同时由于其支撑业务生态的复杂性,全链路综合异常流也需要特殊的验证。为更系统的进行演练,在过程中主要进行了以下几部分工作:FMEA 分析就是失效模式和效果分析,旨在对系统范围内潜在的失效模式加以分析,以便按照严重程度加以分类,或者确定失效对于该系统的影响。从故障场景上,分析得出较为符合 Ali Kubernetes 的三大类场景:通用故障场景:包括网络相关故障(网络 iohang ,断网,网络延迟等),宿主机相关故障(机器重启,机器 load 高等)Ali Kubernetes 业务场景故障:包括 Kubernetes 相关的故障(pod 删除,pod patch等),pod 迁移,混部、etcd 等业务相关场景;chaos 故障:较为随机的故障注入,可以为以上任何故障的组合从影响面上,需要 case by case 确定影响范围为无任何影响,仅影响部分功能,影响核心功能等等;从验证恢复手段上,也可以分为自动恢复、手动恢复,同时需要关注监控情况及恢复时间。在分析过程中,我们发现,已有的开源工具无法完全满足 Ali Kubernetes 的故障场景。下面举 2 个典型故障场景:pod 被误删这个场景并不是简单的 pod 随机删除,而是在 kubelet 连错 apiserver 配置等异常情况下,重启 ali-kubelet 后,al 自行判断了容器在当前集群内不存在,自己做了删除操作。要引入这个故障需要修改 kubelet 组件的配置,重启 kubelet,才算是真正引入了故障,而当前的无论是 kube-monkey 还是 powerfulseal 场景都无法满足。master 组件断网有的人可能会说,直接指定 master 组件的机器引入断网操作,是不是就可以了呢?然鹅现实是比较骨感的,我们也许只知道这个 master 所在集群的 kubeconfig,组件的机器其实也可以随着每次升级变动的。在仅仅已知 kubeconfig 的情况下我们只能先查一下 master 组件的机器信息,再在机器上引入断网的操作,才算是一个整体的故障引入。而目前所有的开源工具也没有此类稍微复杂一些的场景,只是通过指定 pod namespace 来随机的删除一些 pod。所以综上所述,其实我们需要对此进行扩展开发,除了简单的杀 pod,我们亟需一套可以自由开发的小程序,把这个步骤拼接起来,进行更为复杂的故障注入。套件实现为了满足此类复杂的故障注入,我们使用了目前集团内正在开发的一套故障注入系统 monkeyking,并在它的基础上扩展了一些 kubernetes 相关的套件,来达到既可以注入 kubernetes 相关的故障,又可以注入一些通用故障,同时又可以相对自由的扩展故障集合的目的。这个故障注入的演练流程如下图所示:它的每一个步骤都可以是我们自由扩展的一个或者多个小程序,各个小程序之间的执行顺序也可以自由的定义。考虑到 Ali Kubernetes 的场景,我们在其中扩展了四大类小程序套件。通用故障小程序在这一部分主要实现了一些比较通用的 os 故障,网络故障,比如最基本的指定一个宿主机断网,指定宿主机重启这类。Kubernetes 套件小程序这一部分主要实现了一些通用的 Kubernetes 命令,通过指定这些命令和入参,我们可以执行比如 create delete apply patch 这些操作,来间接的达到注入一些 Kubernetes 相关故障的目的。实现原理如下:要点说明如下:下载集群证书的地址及证书的 md5 码都作为小程序的输入,在执行实际的 kubectl 生效命令前进行下载校验;底层 toolkit 中已经加入了 kubectl 命令行工具,无需自己找环境进行配置和下载;目前已经支持了 apply,create,delete,patch,get 操作,支持指定 label,namespace,-o json 的操作举个例子,上文中 master 组件故障的场景中,我们就可以利用以上的两类小程序来完成故障注入的操作:开源工具小程序目前我们和集团安全生产的 MonkeyKing 团队合作,联合在故障注入平台 monkeyking 中集成了开源工具 kube-monkey,实现过程借助了上文的 kubernetes 套件执行,可以通过打标的方式标记受害者,让 kube-monkey 随机的杀受害者 pod。步骤如下:环境准备锁演练环境在当前集群中初始化kube-monkey: 使用kubernetes套件的apply功能提交km-config.yaml文件,部署 kube-monkey deployment给应用标记受害者 label使用 Kubernetes 套件的 patch 功能,标记受害者验证步骤自定义组件校验应用服务是否可用故障恢复使用 Kubernetes 套件的 patch 功能,给受害者去标使用 Kubernetes 套件的 delete 功能,删除 kube-monkey deployment解锁演练环境其他业务相关小程序这一部分比较自由,主要根据 Ali Kubernetes 的业务需求,接入了一些常用的小程序。比如故障演练过程中,环境需要独占,不允许其他测试执行,在这里实现了一个小程序用来对环境进行加解锁操作;比如校验阶段需要验证服务是否可用,这里实现了一个通过 curl 命令校验返回值的方式验证服务是否可用的小程序;比如故障注入过程可能影响vip挂载,这里也实现了一个调用 vip 服务校验 vip 下 ip 数量及是否可用的小程序。总结在 Ali Kubernetes 中,我们将故障以场景化的方式进行沉淀,将底层 os,内核、网络、误配置等故障联合 Kubernetes 相关故障,引入混沌工程的理念进行注入,有效的发现了很多系统稳定性问题,驱动开发人员更多关注系统健壮性。后续我们会在 Ali Kubernetes 演进过程中持续发力,基于架构和业务场景输入更多 Kubernetes 相关的故障场景,为系统的高可用保驾护航。本文作者:jessie筱姜阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 14, 2019 · 1 min · jiezi

阿里巴巴基于 Nacos 实现环境隔离的实践

随着Nacos 0.9版本的发布,Nacos 离正式生产版本(GA)又近了一步,其实已经有不少企业已经上了生产,例如虎牙直播。本周三(今天),晚上 19:00~21:00 将会在 Nacos 钉钉群(群号:21708933)直播 Nacos 1.0.0 所有发布特性的预览以及升级和使用上的指导。Nacos环境隔离通常,企业研发的流程是这样的:先在测试环境开发和测试功能,然后灰度,最后发布到生产环境。并且,为了生产环境的稳定,需要将测试环境和生产环境进行隔离,此时,必然会遇到问题是多环境问题,即:多个环境的数据如何隔离?如何优雅的隔离?(不需要用户做任何改动)本文将就 Nacos 环境隔离,向大家介绍阿里在这方面的实践经验。什么是环境?说到环境隔离,首先应该定义好什么是环境。环境这个词目前还没有一个比较统一的定义,有些公司叫环境,在阿里云上叫 region,在 Kubernetes 架构中叫 namespace。本文认为,环境是逻辑上或物理上独立的一整套系统,这套系统中包含了处理用户请求的全部组件,例如网关、服务框架、微服务注册中心、配置中心、消息系统、缓存、数据库等,可以处理指定类别的请求。举个例子,很多网站都会有用户 ID 的概念,可以按照用户 ID 划分,用户 ID 以偶数结尾的请求全部由一套系统处理,而奇数结尾的请求由另一套系统处理。如下图所示。 我们这里说的环境隔离是指物理隔离,即不同环境是指不同的机器集群。环境隔离有什么用上一节定义了环境的概念,即一套包含了处理用户请求全部必要组件的系统,用来处理指定类别的请求。本节跟大家讨论一下环境隔离有哪些好处。从概念的定义可以看出,环境隔离至少有三个方面的好处:故障隔离、故障恢复、灰度测试;故障隔离首先,因为环境是能够处理用户请求的独立组件单元,也就是说用户请求的处理链路有多长,都不会跳出指定的机器集群。即使这部分机器故障了,也只是会影响部分用户,从而把故障隔离在指定的范围内。如果我们按照用户id把全部机器分为十个环境,那么一个环境出问题,对用户的影响会降低为十分之一,大大提高系统可用性。故障恢复环境隔离的另一个重要优势是可以快速恢复故障。当某个环境的服务出现问题之后,可以快速通过下发配置,改变用户请求的路由方向,把请求路由到另一套环境,实现秒级故障恢复。当然,这需要一个强大的分布式系统支持,尤其是一个强大的配置中心(如Nacos),需要快速把路由规则配置数据推送到全网的应用进程。灰度测试灰度测试是研发流程中不可或缺的一个环节。传统的研发流程中,测试和灰度环节,需要测试同学做各种各样的配置,如绑定host、配置jvm参数、环境变量等等,比较麻烦。经过多年的实践,阿里巴巴内部的测试和灰度对开发和测试非常友好,通过环境隔离功能来保证请求在指定的机器集群处理,开发和测试不需要做任何做任何配置,大大提高了研发效率。Nacos如何做环境隔离前两节讲到了环境的概念和环境隔离的作用,本节介绍如何基于 Nacos,实现环境的隔离。Nacos 脱胎于阿里巴巴中间件部门的软负载小组,在环境隔离的实践过程中,我们是基于 Nacos 去隔离多个物理集群的,同时,在 Nacos 客户端不需要做任何代码改动的情况下,就可以实现环境的自动路由。开始前,我们先做一些约束:一台机器上部署的应用都在一个环境内;一个应用进程内默认情况下只连一个环境的 Nacos;通过某种手段可以拿到客户端所在机器 IP;用户对机器的网段有规划;基本原理是:网络中 32 位的 IPV4 可以划分为很多网段,如192.168.1.0/24,并且一般中大型的企业都会有网段规划,按照一定的用途划分网段。我们可以利用这个原理做环境隔离,即不同网段的 IP 属于不同的环境,如192.168.1.0/24属于环境A, 192.168.2.0/24属于环境B等。Nacos 有两种方式初始化客户端实例,一种是直接告诉客户端 Nacos 服务端的IP;另一种是告诉客户端一个 Endpoint,客户端通过 HTTP 请求到 Endpoint,查询 Nacos 服务端的 IP 列表。这里,我们利用第二种方式进行初始化。增强 Endpoint 的功能。在 Endpoint 端配置网段和环境的映射关系,Endpoint 在接收到客户端的请求后,根据客户端的来源 IP 所属网段,计算出该客户端的所属环境,然后找到对应环境的 IP 列表返回给客户端。如下图一个环境隔离server的示例上面讲了基于IP段做环境隔离的约束和基本原理,那么如何实现一个地址服务器呢。最简单的方法是基于nginx实现,利用nginx的geo模块,做IP端和环境的映射,然后利用nginx返回静态文件内容。安装nginx http://nginx.org/en/docs/install.html在nginx-proxy.conf中配置geo映射,参考这里geo $env { default “”; 192.168.1.0/24 -env-a; 192.168.2.0/24 -env-b;}配置nginx根路径及转发规则,这里只需要简单的返回静态文件的内容;# 在http模块中配置根路径root /tmp/htdocs;# 在server模块中配置location / { rewrite ^(.*)$ /$1$env break;}配置Nacos服务端IP列表配置文件,在/tmp/hotdocs/nacos目录下配置以环境名结尾的文件,文件内容为IP,一行一个$ll /tmp/hotdocs/nacos/total 0-rw-r–r– 1 user1 users 0 Mar 5 08:53 serverlist-rw-r–r– 1 user1 users 0 Mar 5 08:53 serverlist-env-a-rw-r–r– 1 user1 users 0 Mar 5 08:53 serverlist-env-b$cat /tmp/hotdocs/nacos/serverlist192.168.1.2192.168.1.3验证curl ’localhost:8080/nacos/serverlist'192.168.1.2192.168.1.3至此, 一个简单的根据IP网段做环境隔离的示例已经可以工作了,不同网段的nacos客户端会自动获取到不同的Nacos服务端IP列表,实现环境隔离。这种方法的好处是用户不需要配置任何参数,各个环境的代码和配置是一样的,但需要提供底层服务的同学做好网络规划和相关配置。总结本文简单介绍了环境隔离的概念,环境隔离的三个好处以及 Nacos 如何基于网段做环境隔离。最后,给出了一个基于 Nginx 做 Endpoint 服务端的环境隔离配置示例。需要注意的是,本文只是列出了一种可行的方法,不排除有更优雅的实现方法,如果大家有更好的方法,欢迎到Nacos 社区或官网贡献方案。本文作者:正己,GitHub ID @jianweiwang,负责 Nacos 的开发和社区维护,阿里巴巴高级开发工程师。本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 14, 2019 · 1 min · jiezi

SpringBoot使用prometheus监控

本文介绍SpringBoot如何使用Prometheus配合Grafana监控。1.关于PrometheusPrometheus是一个根据应用的metrics来进行监控的开源工具。相信很多工程都在使用它来进行监控,有关详细介绍可以查看官网:https://prometheus.io/docs/in…。2.有关GrafanaGrafana是一个开源监控利器,如图所示。从图中就可以看出来,使用Grafana监控很高大上,提供了很多可视化的图标。官网地址:https://grafana.com/3.SpringBoot使用Prometheus3.1 依赖内容在SpringBoot中使用Prometheus其实很简单,不需要配置太多的东西,在pom文件中加入依赖,完整内容如下所示。<?xml version=“1.0” encoding=“UTF-8”?><project xmlns=“http://maven.apache.org/POM/4.0.0" xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> <relativePath/> <!– lookup parent from repository –></parent><groupId>com.dalaoyang</groupId><artifactId>springboot2_prometheus</artifactId><version>0.0.1-SNAPSHOT</version><name>springboot2_prometheus</name><description>springboot2_prometheus</description><properties> <java.version>1.8</java.version></properties><dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> <version>1.1.3</version> </dependency></dependencies><build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins></build></project>3.2 配置文件配置文件中加入配置,这里就只进行一些简单配置,management.metrics.tags.application属性是本文配合Grafana的Dashboard设置的,如下所示:spring.application.name=springboot_prometheusmanagement.endpoints.web.exposure.include=*management.metrics.tags.application=${spring.application.name}3.3 设置application修改启动类,如下所示.@SpringBootApplicationpublic class Springboot2PrometheusApplication {public static void main(String[] args) { SpringApplication.run(Springboot2PrometheusApplication.class, args);}@BeanMeterRegistryCustomizer<MeterRegistry> configurer( @Value("${spring.application.name}”) String applicationName) { return (registry) -> registry.config().commonTags(“application”, applicationName);}}SpringBoot项目到这里就配置完成了,启动项目,访问http://localhost:8080/actuator/prometheus,如图所示,可以看到一些度量指标。4.Prometheus配置4.1 配置应用在prometheus配置监控我们的SpringBoot应用,完整配置如下所示。my global configglobal: scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute. evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute. # scrape_timeout is set to the global default (10s).Alertmanager configurationalerting: alertmanagers:static_configs:targets: # - alertmanager:9093Load rules once and periodically evaluate them according to the global ’evaluation_interval’.rule_files: # - “first_rules.yml” # - “second_rules.yml"A scrape configuration containing exactly one endpoint to scrape:Here it’s Prometheus itself.scrape_configs:job_name: ‘prometheus’ static_configs:targets: [‘127.0.0.1:9090’]以下内容为SpringBoot应用配置job_name: ‘springboot_prometheus’ scrape_interval: 5s metrics_path: ‘/actuator/prometheus’ static_configs:- targets: [‘127.0.0.1:8080’]4.2 启动Prometheus启动Prometheus,浏览器访问,查看Prometheus页面,如图所示。点击如图所示位置,可以查看Prometheus监控的应用。列表中UP的页面为存活的实例,如图所示。也可以查看很多指数,如下所示。5.Grafana配置启动Grafana,配置Prometheus数据源,这里以ID是4701的Doshboard为例(地址:https://grafana.com/dashboard…)如图。在Grafana内点击如图所示import按钮在如图所示位置填写4701,然后点击load。接下来导入Doshboard。导入后就可以看到我们的SpringBoot项目对应的指标图表了,如图。6.源码源码地址:https://gitee.com/dalaoyang/s…本文作者:dalaoyang阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 8, 2019 · 1 min · jiezi

通过阿里云K8S Ingress Controller实现路由配置的动态更新

摘要: 本文主要描述了阿里云Kubernetes集群Ingress Controller如何通过动态更新的方式来极大地缓解转发平面Nginx频繁Reload带来的影响。简介在Kubernetes集群中,Ingress作为集群内服务对外暴露的访问接入点,其几乎承载着集群内服务访问的所有流量。我们知道,Nginx Ingress Controller是Kubernetes社区很重要的一个子项目,其内部主要依托于高性能的负载均衡软件Nginx,将Kubernetes Ingress资源对象实时地自动化转换为Nginx配置规则来对外提供期望的授权访问入口。现实问题当随着Kubernetes集群中部署的微服务越来越多,对外暴露的路由规则越来越复杂,服务后端Endpoint变化的越来越频繁,那么对应地都会引起Nginx Ingress Controller组件内Nginx配置文件的变化越来越频繁;而我们知道,任何一行Nginx配置的变化,都需要Reload Nginx才能生效,这在变化频率较低的场景下索性还能接受,但在高频率变化的场景下就会引起Nginx的频繁Reload。而Nginx频繁Reload带来的问题,这已是一个老生常谈的话题了,其问题本质主要还是源于Nginx本身最初的架构设计模型:一般在Linux服务器中,我们会配置使用Nginx的EPOLL多进程模式;当我们修改了Nginx配置文件后,需要通过nginx -s reload命令来重新热加载新的Nginx配置规则;当Nginx Master进程接收到reload signal后,其会从指定路径重新加载新的Nginx配置文件内容,并校验配置规则的有效性,若检验为有效的配置文件,则会依据新的配置文件中的worker_processes值fork出指定数量的新的Nginx Worker进程,此时新fork出来的子进程完全继承了父进程的内存数据ngx_cycle(其包含了新的解析后的Nginx配置规则),同时将配置中的每一个Listen Socket FD注册到内核的EPOLL事件监听中,此时这些新的Nginx Worker进程可以接收处理来自客户端的请求;同时Nginx Master进程会发送QUIT signal通知老的Nginx Worker进程平滑退出,当老的Nginx Worker进程接收到QTUI信号后,将其之前注册到EPOLL中的监听Event移除,至此不再接收处理新的客户端请求,并依据老配置文件中设置的worker_shutdown_timeout值来设置定时器,然后继续处理完已接收的客户端请求;若在worker_shutdown_timeout之前处理完已有的客户端请求,则自动退出,若未处理完,则被强制Kill退出,此时就会导致该客户端请求响应异常。因此,对于在高频率变化的场景下,Nginx频繁Reload会带来较明显的请求访问问题:造成一定的QPS抖动和访问失败情况对于长连接服务会被频繁断掉造成大量的处于shutting down的Nginx Worker进程,进而引起内存膨胀动态更新为缓解Nginx频繁Reload带来的影响,我们需要通过动态更新的方式来加载Nginx配置规则,即在不Fork新Nginx Worker进程的情况下来实时更新已加载到内存中的Nginx配置规则。首先我们看下Nginx的配置文件样式,主要包含下面几部分配置章节:# 1. main configurationdaemon off;worker_processes 4;events { # 2. event configuration multi_accept on; worker_connections 1024; use epoll;}http { # 3. http main configuration access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; upstream { # 4. upstream configuration server 0.0.0.1; } server { # 5. server configuration server_name _ ; listen 80 default_server; location / { # 6. location configuration proxy_pass http://upstream_balancer; }}而在Kubernetes集群中,一个Ingress资源对象主要被解析映射到Nginx的HTTP Main Block、Server Block、Upstream Block和Location Block等章节的配置规则上,因此我们可以将这几部分频繁变化的配置内容以Shared Memory的方式统一维持在内存中,同时在Ingress Controller内部暴露出管控端口,通过API的方式来实时管理Nginx路由规则配置:当K8S Ingress Controller监控到集群内Ingress及相关联的资源发生变化时,均可通过Internal API将最新的Nginx配置规则推送到统一的共享内存配置中,而不用再通过Reload Nginx的方式来使新配置生效,至此当Nginx处理任何新接收的客户端请求时,都可以基于最新的共享内存中的配置进行规则匹配和路由转发;配置说明1、目前阿里云容器服务Kubernetes集群中最新版本的Nginx Ingress Controller组件默认已开启Upstream的动态更新,同时支持应用服务的灰度发布和蓝绿发布功能,具体配置说明可参考这里;我们可以通过如下命令来查看当前共享内存中的Nginx Upstream的配置列表:kubectl -n kube-system exec -it <NGINX-INGRESS-CONOTROLLER-POD-NAME> – curl http://127.0.0.1:18080/configuration/backends2、同时也支持HTTPS证书的动态更新,可通过修改nginx-ingress-controller deployment的如下参数配置来开启Nginx Ingress Controller的证书动态更新:- args: - /nginx-ingress-controller - –configmap=$(POD_NAMESPACE)/nginx-configuration - –tcp-services-configmap=$(POD_NAMESPACE)/tcp-services - –udp-services-configmap=$(POD_NAMESPACE)/udp-services - –annotations-prefix=nginx.ingress.kubernetes.io - –publish-service=$(POD_NAMESPACE)/nginx-ingress-lb - –enable-dynamic-certificates=true ### 添加该配置 - –v=2当开启HTTPS证书的动态更新后,Ingress的TLS证书都统一维护在Nginx的共享内存中,我们可通过如下命令来查看当前共享内存中配置的证书列表:kubectl -n kube-system exec -it <NGINX-INGRESS-CONOTROLLER-POD-NAME> – curl http://127.0.0.1:18080/configuration/certs3、进一步地我们也支持Nginx Server和Location配置的动态更新,可通过修改nginx-ingress-controller deployment的如下参数配置来开启Nginx Ingress Controller的Server和Location的动态更新:- args: - /nginx-ingress-controller - –configmap=$(POD_NAMESPACE)/nginx-configuration - –tcp-services-configmap=$(POD_NAMESPACE)/tcp-services - –udp-services-configmap=$(POD_NAMESPACE)/udp-services - –annotations-prefix=nginx.ingress.kubernetes.io - –publish-service=$(POD_NAMESPACE)/nginx-ingress-lb - –enable-dynamic-certificates=true ### 添加该配置 - –enable-dynamic-servers=true ### 添加该配置,同时也要enable-dynamic-certificates - –v=2同样地,当我们开启了Nginx Ingress Controller的Server动态更新后,所有Nginx Server和Location的配置都统一维护在共享内存中,我们同样可以通过如下命令来查看当前共享内存中的Server配置列表:kubectl -n kube-system exec -it <NGINX-INGRESS-CONOTROLLER-POD-NAME> – curl http://127.0.0.1:18080/configuration/servers注意说明:当开启Server的动态更新特性后,部分Ingress Annotation配置暂不支持,正在逐步优化支持中,相应地您可直接通过ConfigMap方式来进行配置;本文作者:chenqz阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 8, 2019 · 1 min · jiezi

开发函数计算的正确姿势——运行 Selenium Java

前言首先介绍下在本文出现的几个比较重要的概念:函数计算(Function Compute): 函数计算是一个事件驱动的服务,通过函数计算,用户无需管理服务器等运行情况,只需编写代码并上传。函数计算准备计算资源,并以弹性伸缩的方式运行用户代码,而用户只需根据实际代码运行所消耗的资源进行付费。函数计算更多信息参考。Fun: Fun 是一个用于支持 Serverless 应用部署的工具,能帮助您便捷地管理函数计算、API 网关、日志服务等资源。它通过一个资源配置文件(template.yml),协助您进行开发、构建、部署操作。Fun 的更多文档参考。备注: 本文介绍的技巧需要 Fun 版本大于等于 2.10.2。依赖工具本项目是在 MacOS 下开发的,涉及到的工具是平台无关的,对于 Linux 和 Windows 桌面系统应该也同样适用。在开始本例之前请确保如下工具已经正确的安装,更新到最新版本,并进行正确的配置。DockerFunFcliFun 和 Fcli 工具依赖于 docker 来模拟本地环境。对于 MacOS 用户可以使用 homebrew 进行安装:brew cask install dockerbrew tap vangie/formulabrew install funbrew install fcliWindows 和 Linux 用户安装请参考:https://github.com/aliyun/fun/blob/master/docs/usage/installation.mdhttps://github.com/aliyun/fcli/releases安装好后,记得先执行 fun config 初始化一下配置。注意, 如果你已经安装过了 fun,确保 fun 的版本在 2.10.2 以上。$ fun –version2.10.1快速开始初始化使用 fun init 命令可以快捷地将本模板项目初始化到本地。fun init vangie/selenium-java-example安装依赖$ fun install…本地测试测试代码 ChromeDemo 的内容为:public class ChromeDemo implements StreamRequestHandler { public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException { System.setProperty(“webdriver.chrome.driver”, “/code/chromedriver”); ChromeOptions options = new ChromeOptions(); options.setBinary("/code/headless-chromium"); options.addArguments("–disable-extensions"); // disabling extensions options.addArguments("–disable-gpu"); // applicable to windows os only options.addArguments("–disable-dev-shm-usage"); // overcome limited resource problems options.addArguments("–no-sandbox"); // Bypass OS security model options.addArguments("–headless"); WebDriver driver = new ChromeDriver(options); driver.get(“https://ide.fc.aliyun.com”); outputStream.write((“Page title is: " + driver.getTitle() + “\n”).getBytes()); driver.quit(); }}本地运行$ mvn package && fun local invoke selenium…FC Invoke Start RequestId: 68c83b4c-b053-479c-9b0e-9503582ccb56handle user request is com.aliyun.fc.selenium.ChromeDemo::handleRequestcache is null!Starting ChromeDriver 2.35.528139 (47ead77cb35ad2a9a83248b292151462a66cd881) on port 20652Only local connections are allowed.Mar 05, 2019 11:34:27 AM org.openqa.selenium.remote.ProtocolHandshake createSessionINFO: Detected dialect: OSSPage title is: 云端集成开发环境FC Invoke End RequestId: 68c83b4c-b053-479c-9b0e-9503582ccb56RequestId: 68c83b4c-b053-479c-9b0e-9503582ccb56 Billed Duration: 5265 ms Memory Size: 1998 MB Max Memory Used: 240 MB部署$ mvn package && fun deploy执行$ fcli function invoke -s chrome -f selenium Page title is: 云端集成开发环境关于文件尺寸由于 chromedriver 和 headless-chromium 压缩后体积已经非常接近 50MB,留给用户 Jar 的空间非常少,所以另外制作了一个高压缩比版本,使用压缩比更高的 brotli 算法进行压缩,压缩后的大小为 32.7MB。然后在运行时使用 initializer 进行解压,解压耗时大约为 3.7 S。https://github.com/vangie/packed-selenium-java-example参考阅读https://github.com/smithclay/lambdiumhttps://medium.com/clog/running-selenium-and-headless-chrome-on-aws-lambda-fb350458e4df本文作者:倚贤阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 7, 2019 · 1 min · jiezi

使用Grab的实验平台进行混沌实验编排

Roman Atachiants · Tharaka Wijebandara · Abeesh Thomas原文: https://engineering.grab.com/chaos-engineering译:时序背景对每个用户来说,Grab是一个可以叫车,叫外卖或付款的一个APP。对工程师来说,Grab是一个有许多服务并通过RPC交互的分布式系统,有时也可以叫做微服务架构。在数千台服务器上运行的数百个服务每天都有工程师在上面进行变更。每次复杂的配置,事情可能都会变糟。 幸运的是,很多Grab App的内部服务不像用户叫车那样的动作这么重要。例如,收藏夹可以帮用户记住之前的位置,但如果它们不工作,用户仍然可以得到较合理的用户体验。服务部分可用并不是没有风险。工程师需要对于RPC调用非核心服务时需要有有备用计划。如果应急策略没有很好地执行,非核心服务的问题也可能导致停机。所以我们如何保证Grab的用户可以使用核心功能,例如叫车,而此时非核心服务正在出问题?答案是混沌工程。在Grab,我们通过在整体业务流的内部服务或组件上引入故障来实践混沌工程。但失败的服务不是实验的关注点。我们感兴趣的是测试依赖这个失败服务的服务。照理来说,上游服务应该有弹性并且整体业务流应该可以继续工作。比如,叫车流程就算在司机地址服务上出现故障时仍应该可以工作。我们测试重试和降级是否配置正确,是否熔断器被正确的设置。为了将混沌引入我们的系统,我们使用了我们的实验平台(ExP)和Grab-Kit.混沌实验平台Exp将故障注入到处理流量服务的中间件(gRPC或HTTP服务器)。如果系统的行为与期望一致,你将对非核心服务故障时服务会平稳降级产生信心。混沌实验平台ExP在Grab的基础设施中模拟不同的混沌类型,如延迟和内存泄漏。这保证了每个组件在系统的依赖不响应或响应很高时仍能返回一些东西。它能保证我们对于实例级失败有弹性,因为微服务级别的中断对于可用性也是一个威胁。配置混沌实验为了构建我们的混沌工程系统,我们认为需要在两个主要领域引入混沌:基础设置:随机关闭基础设施的实例和其他部分应用: 在较粗粒度引入运行时故障(如endpoint/request级别)你可以稍后启用有意的或随机的混沌实验:随机的比较适合‘一次性’基础设施(如EC2实例)测试冗余的基础设施对最终用户的影响当影响面已经十分确定实验精确度量影响使用实验参数控制对最终用户有限的影响适用于对于影响不十分确定的复杂故障(如延迟)最后,你可以将故障模式按以下分类:资源:CPU,内存,IO,磁盘网络:黑洞,延迟,丢包,DNS状态:关机,时间,杀进程这些模型都可以在基础设施或应用级别使用或模拟:对于Grab,进行应用级别的混沌实验并仔细度量影响面很重要。我们决定使用一个已有的实验平台来对围绕系统的应用级别混沌实验进行编排,即紫色部分,通过对下层像Grab-Kit这样的中间件进行注入来实现。为什么使用实验平台?现在有一些混沌工程工具。但是,使用它们经常需要较高级的基础设施和运维技巧,有能力设计和执行实验,以受控的方式有资源手工编排失败场景。混沌工程不是简单的在生产环境搞破坏。将混沌工程理解成受控的实验。我们的ExP SDK提供弹性和异步追踪。这样,我们可以将潜在的业务属性度量对应到混沌失败上。比如,在订车服务上进行10秒延迟的混沌故障,我们可以知道多少辆车被影响了进而知道损失了多少钱。使用ExP作为混沌工程的工具意味着我们可以基于应用或环境精确定制,让它可以像监控和部署管道一样与其他环境紧密集成。在安全上也可以获得收益。使用ExP,所有的连接都在我们的内部网络中,给我们攻击表面区域的能力。所有东西都可以掌控在手中,对外部世界没有依赖。这也潜在的使监控和控制流量变容易了。混沌故障可以点对点,编程式的,或定期执行。你可以让它们在特定日期的特定时间窗口来执行。你可以设定故障的最大数量并定制它们(比如泄漏的内存MB数量,等待的秒)。ExP的核心价值是让工程师可以启动,控制和观察系统在各种失败条件下的行为。ExP提供全面的故障原子集,用来设计实验并观察问题在复杂分布式系统发生时的表现。而且,将混沌测试集成到ExP,我们对于部署流水线或网络基础设施不需要任何改动。因此这种组合可以很容易的在各种基础设施和部署范式上使用。我们如何打造Chaos SDK和UI要开发混沌工程SDK,我们使用我们已有ExP SDK的属性 - single-digit , 不需要网络调用。你可以看这里对于ExP SDK的实现。现在我们要做两件事:一个在ExP SDK之上的很小的混沌SDK。我们将这个直接集成在我们的已有中间件,如Grab-Kit和DB层。一个专门的用来创建混沌实验的基于web的UI归功于我们与Grab-Kit的集成,Grab工程师不需要直接使用混沌SDK。当Grab-Kit处理进入的请求时,它先使用ExP SDK进行检查。如果请求“应该失败”,它将产生适合的失败类型。然后它被转发到特定endpoint的处理器。我们现在支持以下失败类型:Error - 让请求产生errorCPU Load - 在CPU上加大load内存泄漏 - 产生一些永远不能释放的内存延迟 - 在一小段随机时间内停止请求的处理磁盘空间 - 在机器上填入一些临时文件Goroutine泄漏 - 创建并泄漏goroutinesPanic -限流 - 在请求上设置一个频率限制并在超过限制时拒绝请求举个例子,如果一个叫车请求到了我们的叫车服务,我们调用GetVariable(“chaosFailure”)来决定请求是否应该成功。请求里包含所有需要用来做决定的信息(如请求ID,实例的IP地址等)。关于实验SDK的实现细节,看这篇博客。为了在我们的工程师中推广混沌工程我们围绕它建立了很好的开发者体验。在Grab不同的工程团队会有很多不同的技术和领域。所以一些人可能没有对应的知识和机能来进行合适的混沌实验。但使用我们简化过的用户界面,他们不需要担心底层实现。并且,运行混沌实验的工程师是与像产品分析师和产品经理不同的实验平台用户。所以我们使用一种简单和定制化UI配置新的混沌实验来提供一种不同的创建实验的体验。在混沌工程平台,一个实验有以下四步:定义系统正常情况下的理想状态。创建一个控制组的配置和一个对比组的配置。控制组的变量使用已有值来赋值。对比组的变量使用新值来赋值。引入真实世界的故障,例如增加CPU负载。找到区分系统正确和失败状态标志性不同。要创建一个混沌实验,标明你想要实验破坏的服务。你可以在以后通过提供环境,可用区或实例列表来更细化这个选择范围。下一步,指定一组会被破坏的服务影响的服务列表。你在试验期间需要仔细监控这些服务。尽管我们持续跟踪表示系统健康的整体度量指标,它仍能帮助你在稍后分析实验的影响。然后,我们提供UI来指定目标组和对比组的策略,失败类型,每个对比组的配置。最后一步,提供时间周期并创建实验。你已经在你的系统中加入了混沌故障并可以监控它对系统的影响了。结论在运行混沌实验后,一般会有两种可能输出。你已经确认了在引入的故障中系统保持了足够的弹性,或你发现了需要修复的问题。如果混沌实验最初被运行在预发环境那么两种都是不错的结果。在第一种场景,你对系统的行为产生了信心。在另一个场景,你在导致停机故障前发现了一个问题。混沌工程是让你工作更简单的工具。通过主动测试和验证你系统的故障模式你减轻了你的运维负担,增加了你的弹性,在晚上也能睡个好觉。本文作者:时序阅读原文本文为云栖社区原创内容,未经允许不得转载。

March 6, 2019 · 1 min · jiezi

高可用服务 AHAS 在消息队列 MQ 削峰填谷场景下的应用

在消息队列中,当消费者去消费消息的时候,无论是通过 pull 的方式还是 push 的方式,都可能会出现大批量的消息突刺。如果此时要处理所有消息,很可能会导致系统负载过高,影响稳定性。但其实可能后面几秒之内都没有消息投递,若直接把多余的消息丢掉则没有充分利用系统处理消息的能力。我们希望可以把消息突刺均摊到一段时间内,让系统负载保持在消息处理水位之下的同时尽可能地处理更多消息,从而起到“削峰填谷”的效果:上图中红色的部分代表超出消息处理能力的部分。我们可以看到消息突刺往往都是瞬时的、不规律的,其后一段时间系统往往都会有空闲资源。我们希望把红色的那部分消息平摊到后面空闲时去处理,这样既可以保证系统负载处在一个稳定的水位,又可以尽可能地处理更多消息,这时候我们就需要一个能够控制消费端消息匀速处理的利器 — AHAS 流控降级,来为消息队列削峰填谷,保驾护航。AHAS 是如何削峰填谷的AHAS 的流控降级是面向分布式服务架构的专业流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统保护等多个维度来帮助您保障服务的稳定性,同时提供强大的聚合监控和历史监控查询功能。AHAS 专门为这种场景提供了匀速排队的控制特性,可以把突然到来的大量请求以匀速的形式均摊,以固定的间隔时间让请求通过,以稳定的速度逐步处理这些请求,起到“削峰填谷”的效果,从而避免流量突刺造成系统负载过高。同时堆积的请求将会排队,逐步进行处理;当请求排队预计超过最大超时时长的时候则直接拒绝,而不是拒绝全部请求。比如在 RocketMQ 的场景下配置了匀速模式下请求 QPS 为 5,则会每 200 ms 处理一条消息,多余的处理任务将排队;同时设置了超时时间,预计排队时长超过超时时间的处理任务将会直接被拒绝。示意图如下图所示:RocketMQ Consumer 接入示例本部分将引导您快速在 RocketMQ 消费端接入 AHAS 流控降级 Sentinel。1. 开通 AHAS首先您需要到AHAS 控制台开通 AHAS 功能(免费)。可以根据 开通 AHAS 文档 里面的指引进行开通。2. 代码改造在结合阿里云 RocketMQ Client 使用 Sentinel 时,用户需要引入 AHAS Sentinel 的依赖 ahas-sentinel-client (以 Maven 为例):<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>ahas-sentinel-client</artifactId> <version>1.1.0</version></dependency>由于 RocketMQ Client 未提供相应拦截机制,而且每次收到都可能是批量的消息,因此用户在处理消息时需要手动进行资源定义(埋点)。我们可以在处理消息的逻辑处手动进行埋点,资源名可以根据需要来确定(如 groupId + topic 的组合): private static Action handleMessage(Message message, String groupId, String topic) { Entry entry = null; try { // 资源名称为 groupId 和 topic 的组合,便于标识,同时可以针对不同的 groupId 和 topic 配置不同的规则 entry = SphU.entry(“handleMqMessage:” + groupId + “:” + topic); // 在此处编写真实的处理逻辑 System.out.println(System.currentTimeMillis() + " | handling message: " + message); return Action.CommitMessage; } catch (BlockException ex) { // 在编写处理被流控的逻辑 // 示例:可以在此处记录错误或进行重试 System.err.println(“Blocked, will retry later: " + message); return Action.ReconsumeLater; // 会触发消息重新投递 } finally { if (entry != null) { entry.exit(); } } }消费者订阅消息的逻辑示例:Consumer consumer = ONSFactory.createConsumer(properties);consumer.subscribe(topic, “*”, (message, context) -> { return handleMessage(message);});consumer.start();更多关于 RocketMQ SDK 的信息可以参考 消息队列 RocketMQ 入门文档。3. 获取 AHAS 启动参数注意:若在本地运行接入 AHAS Sentinel 控制台需要在页面左上角选择 公网 环境,若在阿里云 ECS 环境则在页面左上角选择对应的 Region 环境。我们可以进入 AHAS 控制台,点击左侧侧边栏的 流控降级,进入 AHAS 流控降级控制台应用总览页面。在页面右上角,单击添加应用,选择 SDK 接入页签,到 配置启动参数 页签拿到需要的启动参数(详情请参考 SDK 接入文档),类似于:-Dproject.name=AppName -Dahas.license=<License>其中 project.name 配置项代表应用名(会显示在控制台,比如 MqConsumerDemo),ahas.license 配置项代表自己的授权 license(ECS 环境不需要此项)。4. 启动 Consumer,配置规则接下来我们添加获取到的启动参数,启动修改好的 Consumer 应用。由于 AHAS 流控降级需要进行资源调用才能触发初始化,因此首先需要向对应 group/topic 发送一条消息触发初始化。消费端接收到消息后,我们就可以在 AHAS Sentinel 控制台上看到我们的应用了。点击应用卡片,进入详情页面后点击左侧侧边栏的“机器列表”。我们可以在机器列表页面看到刚刚接入的机器,代表接入成功:点击“请求链路”页面,我们可以看到之前定义的资源。点击右边的“流控”按钮添加新的流控规则:我们在“流控方式”中选择“排队等待”,设置 QPS 为 10,代表每 100ms 匀速通过一个请求;并且设置最大超时时长为 2000ms,超出此超时时间的请求将不会排队,立即拒绝。配置完成后点击新建按钮。5. 发送消息,查看效果下面我们可以在 Producer 端批量发送消息,然后在 Consumer 端的控制台输出处观察效果。可以看到消息消费的速率是匀速的,大约每 100 ms 消费一条消息:1550732955137 | handling message: Hello MQ 24531550732955236 | handling message: Hello MQ 91621550732955338 | handling message: Hello MQ 49441550732955438 | handling message: Hello MQ 55821550732955538 | handling message: Hello MQ 44931550732955637 | handling message: Hello MQ 30361550732955738 | handling message: Hello MQ 13811550732955834 | handling message: Hello MQ 14501550732955937 | handling message: Hello MQ 5871同时不断有排队的处理任务完成,超出等待时长的处理请求直接被拒绝。注意在处理请求被拒绝的时候,需要根据需求决定是否需要重新消费消息。我们也可以点击左侧侧边栏的“监控详情”进入监控详情页面,查看处理消息的监控曲线:对比普通限流模式的监控曲线(最右面的部分):如果不开启匀速模式,只是普通的限流模式,则只会同时处理 10 条消息,其余的全部被拒绝,即使后面的时间系统资源充足多余的请求也无法被处理,因而浪费了许多空闲资源。两种模式对比说明匀速模式下消息处理能力得到了更好的利用。Kafka 接入代码示例Kafka 消费端接入 AHAS 流控降级的思路与上面的 RocketMQ 类似,这里给出一个简单的代码示例:private static void handleMessage(ConsumerRecord<String, String> record, String groupId, String topic) { pool.submit(() -> { Entry entry = null; try { // 资源名称为 groupId 和 topic 的组合,便于标识,同时可以针对不同的 groupId 和 topic 配置不同的规则 entry = SphU.entry(“handleKafkaMessage:” + groupId + “:” + topic); // 在此处理消息. System.out.printf(”[%d] Receive new messages: %s%n", System.currentTimeMillis(), record.toString()); } catch (BlockException ex) { // Blocked. // NOTE: 在处理请求被拒绝的时候,需要根据需求决定是否需要重新消费消息 System.err.println(“Blocked: " + record.toString()); } finally { if (entry != null) { entry.exit(); } } });}消费消息的逻辑:while (true) { try { ConsumerRecords<String, String> records = consumer.poll(1000); // 必须在下次 poll 之前消费完这些数据, 且总耗时不得超过 SESSION_TIMEOUT_MS_CONFIG // 建议开一个单独的线程池来消费消息,然后异步返回结果 for (ConsumerRecord<String, String> record : records) { handleMessage(record, groupId, topic); } } catch (Exception e) { try { Thread.sleep(1000); } catch (Throwable ignore) { } e.printStackTrace(); }}其它以上介绍的只是 AHAS 流控降级的其中一个场景 —— 请求匀速,它还可以处理更复杂的各种情况,比如:流量控制:可以针对不同的调用关系,以不同的运行指标(如 QPS、线程数、系统负载等)为基准,对资源调用进行流量控制,将随机的请求调整成合适的形状(请求匀速、Warm Up 等)。熔断降级:当调用链路中某个资源出现不稳定的情况,如平均 RT 增高、异常比例升高的时候,会使对此资源的调用请求快速失败,避免影响其它的资源导致级联失败。系统负载保护:对系统的维度提供保护。当系统负载较高的时候,提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。您可以参考 AHAS 流控降级文档 来挖掘更多的场景。本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 5, 2019 · 2 min · jiezi

配置管理 ACM 在高可用服务 AHAS 流控降级组件中的应用场景

应用配置管理(Application Configuration Management,简称 ACM)是一款应用配置中心产品。基于ACM您可以在微服务、DevOps、大数据等场景下极大地减轻配置管理的工作量,同时保证配置的安全合规。ACM 有着丰富的使用场景,本文将介绍其在 AHAS 流控降级 中的应用。什么是 AHAS 流控降级AHAS 流控降级 是面向分布式服务架构的专业流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统保护等多个维度帮助您保障服务的稳定性,同时提供强大的聚合监控和历史监控查询功能。在 AHAS 流控降级中,我们需要针对不同的资源(比如服务接口、方法)配置不同的规则(流控规则、降级规则、系统保护规则等)。由于流量的不确定性,我们的规则也需要根据流量的实时情况进行动态管理。AHAS 流控降级使用了 ACM 作为动态配置中心,借助其实时动态推送的能力达到规则实时推送的效果。如何使用 ACMAHAS 流控降级分为两部分:客户端(基于开源的 Sentinel)以及AHAS 控制台。用户使用时只需要引入 AHAS Sentinel 客户端相关依赖 ahas-sentinel-client 并在启动时指定相关参数即可接入到 AHAS 流控降级控制台,在 AHAS 控制台上查看监控、配置规则。Sentinel 抽象出了动态数据源接口,可以方便地对接任意配置中心。Sentinel 推荐使用 push 模式的动态规则源,推送流程为 Sentinel 控制台 → 配置中心 → Sentinel 数据源 → Sentinel,非常清晰:AHAS 流控降级客户端提供了 ACM 动态规则源适配,实现了监听远程规则变更的逻辑,而 AHAS 流控降级控制台实现了相应的规则推送逻辑。用户在 AHAS 流控降级控制台保存规则的时候,AHAS 控制台会在保存规则后将规则推送至 ACM 相应的坐标上,ACM 会实时地将规则 push 到接入端。AHAS 流控降级客户端的动态配置源会自动注册当前应用对应坐标的监听器监听规则变化,当监听到变更时就将其加载到 Sentinel 的规则管理器中,新的规则就生效了。以上就是 ACM 在 AHAS 流控降级中的应用场景,有关 ACM 的更多信息可以参考官方文档。本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。

March 5, 2019 · 1 min · jiezi

如何用30分钟快速优化家中Wi-Fi?阿里工程师有绝招

阿里妹导读:现代人离不开手机,更离不开Wi-Fi。很多同学经常吐槽家中Wi-Fi用得不爽,打游戏看视频又卡又慢。针对大家常见的问题,和坊间各种“谣传”,今天我们特别邀请了阿里工程师艺超,来为大家做全面的梳理分类,希望让每一位同学都能享受如丝滑般顺畅的Wi-Fi体验。前言家庭网络从出口宽带到终端是一条整体链路,分为以下几个部分。出口区域:电信运营商入户线路最终交付到户侧的是一条以太网网线,包含入户光纤(PON)终端及出口路由器,主要提供的“NAT”(实现家内[私网]IP地址到运营商[公网]IP的地址转换翻译)PPPOE拨号、安全访问控制等功能。核心区域:该区域是所有网络设备互联的中心点,是家中网络骨干通道。通常为路由器或者交换机,实现网段路由互通、DHCP、上网行为管理、限速和安全等功能。并提供直接与有线设备互联,提供较大流量的能力输入输出,例如家用NAS、监控录像机、服务器连接等。接入区域:是网络最面向用户的区域,但其却是网络边缘设备,其实现与终端(手机、电脑、IOT设备等)互联功能,通常为无线路由器(AP)和交换机有线端口。参考上图,网络是个整体工程,以上链路任何一个环节出现瓶颈都会影响“体感”。从历史处理的咨询类问题看,其中90%的问题与Wi-Fi信号质量相关,所以在此重点和大家分享下Wi-Fi信号侧通用技术原理和优化方法,以达到快速提升Wi-Fi质量的目的。具体设备配置方法可映射到各位同学购买的各款无线路由器(AP)的说明书。Wi-Fi网络故障表现表现1:网络断线、丢包、卡顿、速度忽快忽慢、抖动幅度大,不稳定。简析:从客户端角度出发,诊断网络质量好坏的直接体现在协商速率上:因为协商速率是根据信号值、噪声、干扰、重传率等一系列射频参数综合体现出来的。故障如下图所示(网络速率不稳定1分钟内大幅度跳动)。表现2:上网速度非常慢。虽然扩容购买了很高的外线带宽,并购买了高功率无线路由器,但是速度依然没有改善。简析:问题出现在Wi-Fi信号上,部分无线路由器厂商大力宣传其产品发射功率高信号可穿墙,但是忽略了终端发射功率回传能力和漫游切换的问题。由于其中无线路由器发射功率较高从而信号强,导致了离路由器较远的终端接收信号显示为满格,但是距离或者阻挡已经超出了终端自身发射功率回传的功率范围。如下图所示(信号接近满格,但协商速率只有13Mbps)。表现3:明明手机在卧室A,却经常连接到远处卧室B的路由器上,虽然屏幕上显示信号杠杠滴,但是实际使用起来网速“捉急”。简析:发射功率过高,家中安装多个无线路由器(AP)时,基本都会产生终端粘连不漫游现像。表现为如下图(蓝色为当前连接AP),电脑没有连接到信号最优距离自己最近的无线路由器上。Wi-Fi信号优化原则一、适当降低无线路由器发射功率(17db,50mw),参考室内通信半径为8-12米。1、双向通信:城市收音机广播基站发射塔只需要建设一座即可覆盖全城,村村通大喇叭只需要一个覆盖全村,其核心优势都是单向通信。当我们需要双向通信时,比如手机打电话,需要将手机MIC取到的声音通过抽样压缩变成电波信号返回给通信基站,此时就受制于手机功率,也就是有效发射距离。因为手机端发射功率比较小,所以移动运营商需要蜂窝状覆盖且数量庞大的通信基站,其目的是为了接收到终端有效的回传,实现有效的双向通信。Wi-Fi也是空中介质通信技术,同理相似。当通信双方功率不匹配时就会造成Wi-Fi使用中频繁中断、速率低等情况发生,所以Wi-Fi路由器要匹配终端功率的有效工作能力,建议配置为14-17db发射范围,从而使通信双方达到100%速率协商。反之Wi-Fi路由器功率越高,传输距离越远时表现出终端连接速率低,通信质量越差的现象。中国国家标准,室内Wi-Fi最大发射功率为20db(100mw)。功率参数对照表如下:2、蜂窝设计:按照原理,无线电通信双方距离越近,双方可协商的带宽就越高。目前普遍使用的5GHZ Wi-Fi协议,其主要运行在5.2GHZ~5.8GHZ频段,可以调制出高达1.3Gbps的理论通信带宽,主要面对室内小型蜂窝场景设计。另外,加上手机天线和外观、电池使用时间、功耗等考虑,目前市面上大部分手机功率为10-14db左右,如下图。笔记本电脑Intel Wi-Fi网卡大多为12-14db范围。3、通信距离:以iphone6手机为例,Wi-Fi 5GHZ频段无任何遮挡室内环境,满速协商速率情况下通信距离为8-12米,维持连接通信距离为70-90米。二、减少无线路由器数量、选好房子合适的中心点,进行吊顶或者桌面放装,保证终端和路由器之间视距可达无遮挡,或者最多穿1堵墙。1、选好位置:有条件的话尽量吊顶安装或者放于桌面高处。即保障了覆盖质量又节约了地面空间,若对美观有要求可以购买外观符合装修风格路由器或者自行刷漆解决。规避干扰:远离强磁0.5米之外(微波炉、电磁炉、高压设备、大屏电视等)。规避遮挡:不要把路由器藏起来放于弱电箱,尤其是金属材质的箱体或者木质柜子、衣帽间、储藏室、角落,或者地面上。2、完善网口:100%无死角且高质量满速的Wi-Fi是基于各房间完善的有线端口基础之上的。尽管通过无线桥接等方式进行扩展,但是通信质量会作出很大牺牲。3、减少设备:无线路由器数量越多,终端产生漫游、断线、重连的机率就越高,这是成正比的,其工作中出现协议冲突(例如DHCP覆盖冲突、同频干扰等)的机率也相对增加,网络架构就会越复杂,所以尽可能少设备数量,尽力做到大道至简。4、无线中继:在弱电网口不完善的大户型(大别野),为了减少施工和影响室内视觉冲击感,我们可以选择“桥接中继”、或者MESH方式简便部署的方案,但是当采用中继MESH这类方案,使用无线桥接互联,在使用体感上就不会那么“爽”。道理很简单易见,每次连接都会有带宽损失,最终会让总体带宽打折扣。桥接每增加一跳带宽对应下降50%,所以我们通常进行桥接设计时:最多下挂一级子设备。5、利器“电力猫”:弱电网口(有线)不完善的大户型(大别野),为了减少施工也可以考虑“电力猫”方式。它利用传统电线,采用分频段技术,转变电线为通信线路使用,只要是在于同一电表区域内的电线系统部署“电力猫“即可实现扩展通信网络的目的。但是在电磁特性上,电线并没有针对高频信号传输做相应的优化设计,不能和光缆、双绞线、同轴电缆这类专用通信线缆相比,存在干扰较大、信号完整性等缺点,并且在用电高峰或者电压不稳时,容易受大电流波动影响。所以这种实现方式,通信质量和稳定性有天生的缺陷,故障点也比较多,如果对网络稳定性要求较高,则不建议采用此方式。三、家用Wi-Fi配置为5GHZ频段40MHZ频宽,IOT设备及访客使用2.4G频段。1、频段选择:2.4Ghz频段低穿透性好,但可用信道只有3个,如下图。同5G频段相比,它的频段窄、速率低、干扰大,建议留作智能家居及IOT设备及访客使用,并配置单独SSID和安全策略。5 GHZ频段在原理上提供了更多的可用信道。在中国许可频段内共有13个可用信道,所以使用5GHZ有条件进行多信道绑定,以得到更高的带宽。2、信道绑定:更宽的信道带宽可以得到更高通信带宽,笔记本电脑普通Intel 802.11ac网卡,使用20Mhz=173Mbps,40Mhz=400Mbps,80Mhz=866Mbps,但是这也是双刃剑,更宽的信道也更容易受到干扰,使得延迟增加。所以我们抛弃20MHZ和80MHZ折中选用40Mhz部署。示意图如下:3、信道选择:使用工具软件(下文会提到具体软件名称) 扫描出空闲信道,并配置到无线路由器。家中每台路由器要配置为不同信道以规避同频干扰。网络优化方法一、不同户型Wi-Fi点位和频谱设计示例1、小户型:2、中户型3、大户型4、别墅,按单楼层布局参考以上户型即可。二、网络架构设计:家庭网络架构设计建议参考本文前言描述部分进行分层设计。我们通常还会有这样的疑问:1、我家使用的100M ADSL带宽,为何电脑手机测速始终无法达到这么高?可参考前言部分架构图,梳理出瓶颈位置进行针对性优化。另外注意大小字节单位换算,网络入户带宽通常以小byte计算(100Mbps/S带宽,实际下载文件速度为10MBps/S),可使用电脑测速www.speedtest.net确认入户带宽是否达标。出口带宽计算:基于家中同时段并发高峰所有终端流量叠加计算得出,通常情况下高清电视流量约为4MBps、电脑普通视频业务为2MBps、手机视频通常为1MBps,如有P2P下载上传等大流量需求,则根据应用需求叠加计算。重点访问资源:较为普及的热点网站及APP应用流量,三大运营商均可以满足。如有特殊需求,例如你需要与另外一个城市家中时常需要大流量视频互联,可以根据目标IP在不同运营商的跳数选择跳数最低的一家(可使用电脑上命令 tracert x.x.x.x 进行跳数和延迟跟踪)。2、我家网络经常断网,有一部分设备无法上网,终端网络连接图标上显示叹号,是什么原因?此类问题大多为DHCP协议冲突所致,非网关的路由器,上联的网线应接在LAN口而非WAN口,并且必须关闭DHCP等与核心层冲突的功能,只作为无线信号发射使用。三、网络安全安全是一个整体包含家中网络上参与的所有设备的软、硬件系统,安全和易用之间也需要相对平衡,可以根据自己的需求作出选择。可以参考以下3个层次设计。普通防范:家用Wi-Fi 与客人用(Guest) Wi-Fi 区分网段,并在核心设备上作路由隔离,配置WPA2 AES CCMP强加密,定期更新密码。相对保密:建议隐藏家用Wi-Fi SSID,当然,技术上通过抓包可以抓取到SSID名称所以需要增加其它配合策略。配置WPA2 AES CCMP强加密,同时关闭DHCP功能,采用静态地址分配、增加MAC地址绑定或者限制MAC访问等等策略及其叠加使用。严格保密:在前两项目基础上,根据自身需求购买相应级别防火墙、严格设计进出数据流量策略,并对家中所有设备系统安全进行严格安全防范,例如定期对路由器设备,AP设备软件进行更新,弥补软件的安全和功能漏洞。我们通常还会有这样的疑问:我的Wi-Fi密码设置的挺复杂的,我自己都记不住,为什么还是被其它人蹭网了?如果解决?若装有“万能密钥”类APP的手机,曾经接入过该网络,这类APP会把密码共享到云端或者其APP配置端。那么,自家中的密码设置再复杂也经不住这样的广播。我们严格控制家庭成员不安装此类APP,并且部署上参考前面介绍过的策略:通过访客和家人区分SSID也可以有效降低此类问题造成的影响,但是一旦发生“被蹭网”事件,建议立刻修改密码。四、设备选择根据自己的户型需求进行选择,例如小户型,选用一台无线路由器即可(相当于路由交换无线路由一体机)。超/大户型可分区/层设计。我们通常还会有这样的疑问:1、同样是家用无线路由器价格相差很大,它们的差距究竟在哪里?相关指标考虑如下要素:2.4&5G双频、802.11ac、千兆以太网、开机稳定运行时长、高并发流量稳定性、多终端并发数量、防潮抗高温及高处跌落保护、软件迭代售后支持能力、天线阵列设计和灵敏度、软件功能等等。2、网上有很多Wi-Fi信号增强的攻略,哪一种最实用?不要迷信高发射功率,任何所谓的穿墙王全是忽悠(参考上文优化原则部分)。也不要试图去增加路由器功率,装易拉罐、加功放、加天线,这样做确实发射端信号增强了。但是“有效”通信是双向的,只加强了发射端功率,终端、手机端功率很低,就会出现功率不匹配的现像,距离发射端较远处信号确实显示满格了,但是手机端回传的数据包无法返回,这样对通信而言实际上没有任何帮助,而且干扰了全局Wi-Fi信号部署的质量。(如下图,绿色信号范围是固定的,扩展出来的是红色的质量较差的信号)3、天线数量越多越好吗?天线数量的多寡,跟穿墙能力是否有关?理论上是的,如果将所有无线设备都放置于同一规格标准下来比较,越多天线代表其灵敏度越高,自然穿墙能力表现会更佳。但实际上受产品硬件设计(价格成本限制)、包含天线用料的影响,所以结论并无绝对,要依产品而定,内置天线路由器也不一定就会比外接式天线的灵敏度更差,因此天线数量只能说是一种参考的指标之一。4、大户型打算采用桥接方式覆盖,设备如何选型?上文提到,尽量不要选择这种方式覆盖。如果一定要采用这种方式,可以考虑使用天猫路由和钉钉路由产品,空口容量大、工作稳定、配置简单。五、效果测试相关工具:PC端可选择 Inssider、WirelessMon、Wireless Netview等,分系统而论。手机端可选择speed test、WiFi分析仪、WiFi Analyzer等,非常简单易用。分系统而论。测试方法:在目标覆盖区域使用以上工具软件进行信号和带宽测试,同时进行ping网关测试,并浏览国内大型互联网站点。相关指标:目标覆盖区域内,接收信号强度大于等于-75dBm,有语音和视频业务的区域,接收信号大于等于-67dBm;ping网关,包大小为1500bytes,ping包次数为100次,时延不大于100ms,Ping包的丢包率不大于1%;漫游切换成功率不小于90%,AP间切换时长ping网关记录,最多允许丢1个包。电脑连接Wi-Fi情况下使用www.speedtest.net 测试网速可以达到出口带宽数值。电脑连接Wi-Fi情况下,点击国内热点网站20次,访问成功率不低于100%;访问国内大型站点显示时延不大于2秒。大家还有哪些优化家中Wi-Fi的方法?欢迎在留言区分享补充。本文作者:艺超阅读原文本文来自云栖社区合作伙伴“ 阿里技术”,如需转载请联系原作者。

March 5, 2019 · 1 min · jiezi

程序员如何让自己 Be Cloud Native - 配置篇

前言这是《程序员如何让自己 Be Cloud Native》系列文章的第二篇,从第一篇的反馈来看,有些同学反馈十二要素太形式主义,不建议盲目跟从。作者认为任何理论和技术都需要有自己的观点,这些观点是建立在个体知识体系逐渐锻炼出来的辩别能力之上的。Be Cloud Native这一系列的文章,会基于十二要素为理论基础,加上作者在云计算诞生以来对于架构的演进所观察到的变化去分享自己的一些心得。第一篇:仓库与依赖。「传送门」实例配置这个要素的核心思想就是代码与数据隔离,一开始我们的软件很小很小的时候,我们会可能直接把各种配置、甚至生产环境中的代码直接写在代码中,配置甚至就是代码的一部分?比如以下的这断代码就是这样:public boolean serviceConnectable() { return ping(“edas.console.aliyun.com”, 3); }该方法实现一个本地是否可以连接到远端 server 的功能。如果按照上面的代码进行编写,只能保证在公共云的环境达到想要的效果。该程序如果部署到了一个专有云或者 IDC 的环境中的时候,就需要改代码了。如果改成以下的方式,效果就会截然不同。那时如果程序部署到了一套新的环境中,只需改变edas.server.address 这个配置。@Value(“edas.server.address”)private String remoteAddress;public boolean serviceConnectable() {return ping(remoteAddress, 3);}定义与示例这个例子应该比较贴近大家的日常,我们将上面这个例子往上提升一层,可以做出一个如下的初步定义:应用的行为(Behavior) = 代码(Code) + 输入(Data)。代码是固定的,需要重新编译分发,无法根据环境进行变化的;而输入是活的。上面的例子,就是一个把 Code 中的一部分内容抽离,变成 Data 的过程。做完这个变化之后,应用对变化的适应性就更强了。当然,这些 Data 有些是用户输入的,有些是系统启动时就已经确定的,后者是我们定义的 配置 ,也是我们今天讨论的主题。从这个层面(Data)说起来,配置其实可以包含很多种,以 Java 语言为例,至少分为以下几种:代码中的文件配置: *.properties 文件。机器上的文件配置 *.properties 文件。通过 -D 参数指定的启动参数。环境变量。配置管理系统,简单的有 DB;比较流行的领域产品如 Nacos、ZooKeeper、ectd 等。选择多了,貌似世界就不那么美妙了,因为我们总是会陷入到“用什么”和“为什么”中去。作者的观点是,在用什么之前,先弄清楚需求层面的两点:隔离粒度:每个版本不一样?每台机器不一样?每个进程不一样?安全性:运维人员可见?开发人员可见?还是都不应该可见?仔细讨论上述两点之前,我们举几个关于配置的例子:程序版本号:从代码 Release (build) 开始,基本上就确定了;这一类配置基本上不存在变化的可能,即这一类配置的隔离粒度等同于 Code 。某个前端组件的版本号:前端版本号有一个特点,就是变动很频繁,尤其是在上线的过程中,发布三四个版本是很常见的现象;而且在上线的过程中,一般都会先灰度验证,再进行现网发布。所以这类配置需要具备一个特点就是,可灵活变动与可按照环境(不同机器、不同流量)粒度发布。服务器端口号:服务器的端口号是需要和进程绑定的,尤其在某些微服务场景;同一个服务,如果部署在同一台机器上,必须准确的告知其他服务本服务的确切的地址和端口。数据库元信息配置:这种配置,同一套环境中的相同服务会是一样的,而且其真实的值隐藏的越深越好,其他还有某些 AK/SK、用户名密码之类,每套环境会不一样,同时不同的产品对待这类配置的安全性要求也可能不一样。归纳通过上面例子简单的论述,我们大致可以把相关的配置做如下的归类:配置项所在位置隔离性安全性代码文件控制不同版本的软件行为,等同于代码、基本不会改动所有有代码权限的人员都可见机器上的文件机器环境级别的隔离可根据文件的系统权限灵活设置可见性启动参数进程级别隔离能进入系统便可见环境变量可根据容器、系统、用户、进程进行非常灵活的搭配可设置到系统用户级别配置管理系统程序员可自由编程实现,一般是服务级别的隔离性取决于不同产品的实现,有的方案可以做到安全性最好这里作者想额外强调的是安全性这一个点,尤其某些金融场景。原生的配置方式,如果不做代码的改动的话,都无法做到很高的安全性,但是在一些分布式产品中,尤其是一些云产品内部,就可以做到很安全,具体可以参考下图:以 Nacos 的云上实现 ACM 为例,对上图进行一个简单的阐述。一般的程序读取配置方式如左图,当执行启动脚本后,应用程序从脚本中设置的环境变量、文件或启动参数中获取配置。这些方式可以满足大部分的场景,但是如果你的应用是一个分布式的大集群,这个时候如果想改一个配置是不可能在机器上配置的,然后一台台的区修改,此时我们需要一个支持大集群的分布式配置服务来支持。开源的配置中心有很多,如 ZooKeeper、etcd、Nacos 等。但是有一种场景,一般意义上的配置中心也是满足不了的,那就是诸如数据库密码这一类安全性要求很高的配置。这类在云上会有一些很好的实现,以上图右边为例解释一下在云上是如何做到的:当用户往配置服务中写入一个配置时,会先使用加密服务进行加密,图中的 A。如果有客户端需要,将相应的加密数据推往对应的客户端,图中的 B。客户端收到数据,会根据机器上的_云角色_,请求云加密服务进行解密,图中的 C。从上面这个描述,我们就能很感受到整个过程,利用云生态的能力,可以做得很优雅、很安全,而且也没有额外的代码侵入。总结写到这里,我需要点一下题,要做到 “Be Cloud Native” ,配置是必不可少的一个环节。能让我们的世界变得稍微美好点的方式之一,就是把每个硬编码的字符,变成一个个可运维、安全的配置。同时在云上,我们会看到有不一样的、更加优雅、更安全、成本更低的解决方案。配置的安全无小事,一时图简单省事,可能就会造成生产级别的敏感信息、甚至 DB 的泄露。Be Cloud Native 的另外一层意思,正是尽可能多的利用云厂商提供的原生技术能力,来构建一个更安全、优雅、可扩展、高可用的应用架构。本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 4, 2019 · 1 min · jiezi

深入 Nginx 之配置篇

常用配置项在工作中,我们与 Nginx 打交道更多的是通过其配置文件来进行。那么掌握这些配置项各自的作用就很有必要了。首先,nginx.conf 的内容通常是这样的:… … #核心摸块events { #事件模块 …}http { # http 模块 server { # server块 location [PATTERN] { # location块 … } location [PATTERN] { … } } server { … } }mail { # mail 模块 server { # server块 … }}我们依次看一下每个模块一般有哪些配置项:核心模块user admin; #配置用户或者组。worker_processes 4; #允许生成的进程数,默认为1 pid /nginx/pid/nginx.pid; #指定 nginx 进程运行文件存放地址 error_log log/error.log debug; #错误日志路径,级别。事件模块events { accept_mutex on; #设置网路连接序列化,防止惊群现象发生,默认为on multi_accept on; #设置一个进程是否同时接受多个网络连接,默认为off use epoll; #事件驱动模型select|poll|kqueue|epoll|resig worker_connections 1024; #最大连接数,默认为512}http 模块http { include mime.types; #文件扩展名与文件类型映射表 default_type application/octet-stream; #默认文件类型,默认为text/plain access_log off; #取消服务日志 sendfile on; #允许 sendfile 方式传输文件,默认为off,可以在http块,server块,location块。 sendfile_max_chunk 100k; #每个进程每次调用传输数量不能大于设定的值,默认为0,即不设上限。 keepalive_timeout 65; #连接超时时间,默认为75s,可以在http,server,location块。 server { keepalive_requests 120; #单连接请求上限次数。 listen 80; #监听端口 server_name 127.0.0.1; #监听地址 index index.html index.htm index.php; root your_path; #根目录 location ~ .php$ { fastcgi_pass unix:/var/run/php/php7.1-fpm.sock; #fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; include fastcgi_params; } }}配置项解析worker_processesworker_processes 用来设置 Nginx 服务的进程数。该值推荐使用 CPU 内核数。worker_cpu_affinityworker_cpu_affinity 用来为每个进程分配CPU的工作内核,参数有多个二进制值表示,每一组代表一个进程,每组中的每一位代表该进程使用CPU的情况,1代表使用,0代表不使用。所以我们使用 worker_cpu_affinity 0001 0010 0100 1000;来让进程分别绑定不同的核上。默认情况下worker进程不绑定在任何一个CPU上。worker_rlimit_nofile设置毎个进程的最大文件打开数。如果不设的话上限就是系统的 ulimit –n的数字,一般为65535。worker_connections设置一个进程理论允许的最大连接数,理论上越大越好,但不可以超过 worker_rlimit_nofile 的值。use epoll设置事件驱动模型使用 epoll。epoll 是 Nginx 支持的高性能事件驱动库之一。是公认的非 常优秀的事件驱动模型。accept_mutex off关闭网络连接序列化,当其设置为开启的时候,将会对多个 Nginx 进程接受连接进行序列化,防止多个进程对连接的争抢。当服务器连接数不多时,开启这个参数会让负载有一定程度的降低。但是当服务器的吞吐量很大时,为了效率,请关闭这个参数;并且关闭这个参数的时候也可以让请求在多个 worker 间的分配更均衡。所以我们设置 accept_mutex off;multi_accept on 设置一个进程可同时接受多个网络连接Sendfile onSendfile是 Linux2.0 以后的推出的一个系统调用,它能简化网络传输过程中的步骤,提高服务器性能。不用 sendfile的传统网络传输过程:硬盘 >> kernel buffer >> user buffer >> kernel socket buffer >> 协议栈用 sendfile()来进行网络传输的过程:硬盘 >> kernel buffer (快速拷贝到 kernelsocket buffer) >>协议栈tcp_nopush on;设置数据包会累积一下再一起传输,可以提高一些传输效率。 tcp_nopush 必须和 sendfile 搭配使用。tcp_nodelay on;小的数据包不等待直接传输。默认为on。
看上去是和 tcp_nopush 相反的功能,但是两边都为 on 时 nginx 也可以平衡这两个功能的使用。keepalive_timeoutHTTP 连接的持续时间。设的太长会使无用的线程变的太多。这个根据服务器访问数量、处理速度以及网络状况方面考虑。send_timeout设置 Nginx 服务器响应客户端的超时时间,这个超时时间只针对两个客户端和服务器建立连接后,某次活动之间的时间,如果这个时间后,客户端没有任何活动,Nginx服务器将关闭连接gzip on启用 gzip,对响应数据进行在线实时压缩,减少数据传输量。gzip_disable “msie6"Nginx服务器在响应这些种类的客户端请求时,不使用 Gzip 功能缓存应用数据,gzip_disable “msie6”对IE6浏览器的数据不进行 GZIP 压缩。常用的配置项大致这些,对于不同的业务场景,有的需要额外的其他配置项,这里不做展开。其他http 配置里有 location 这一项,它是用来根据请求中的 uri 来为其匹配相应的处理规则。location 查找规则location = / { # 精确匹配 / ,主机名后面不能带任何字符串 [ config A ]}location / { # 因为所有的地址都以 / 开头,所以这条规则将匹配到所有请求 # 但是正则和最长字符串会优先匹配 [ config B ]}location /documents/ { # 匹配任何以 /documents/ 开头的地址,匹配符合以后,还要继续往下搜索 # 只有后面的正则表达式没有匹配到时,这一条才会采用这一条 [ config C ]}location ~ /documents/Abc { # 匹配任何以 /documents/Abc 开头的地址,匹配符合以后,还要继续往下搜索 # 只有后面的正则表达式没有匹配到时,这一条才会采用这一条 [ config CC ]}location ^~ /images/ { # 匹配任何以 /images/ 开头的地址,匹配符合以后,停止往下搜索正则,采用这一条。 [ config D ]}location * .(gif|jpg|jpeg)$ { # 匹配所有以 gif,jpg或jpeg 结尾的请求 # 然而,所有请求 /images/ 下的图片会被 config D 处理,因为 ^ 到达不了这一条正则 [ config E ]}location /images/ { # 字符匹配到 /images/,继续往下,会发现 ^~ 存在 [ config F ]}location /images/abc { # 最长字符匹配到 /images/abc,继续往下,会发现 ^~ 存在 # F与G的放置顺序是没有关系的 [ config G ]}location ~ /images/abc/ { # 只有去掉 config D 才有效:先最长匹配 config G 开头的地址,继续往下搜索,匹配到这一条正则,采用 [ config H ]}正则查找优先级从高到低依次如下:“ = ” 开头表示精确匹配,如 A 中只匹配根目录结尾的请求,后面不能带任何字符串。“ ^~ ” 开头表示uri以某个常规字符串开头,不是正则匹配“ ~ ” 开头表示区分大小写的正则匹配;“ ~* ”开头表示不区分大小写的正则匹配“ / ” 通用匹配, 如果没有其它匹配,任何请求都会匹配到负载均衡配置Nginx 的负载均衡需要用到 upstream 模块,可通过以下配置来实现:upstream test-upstream { ip_hash; # 使用 ip_hash 算法分配 server 192.168.1.1; # 要分配的 ip server 192.168.1.2;}server { location / { proxy_pass http://test-upstream; } }上面的例子定义了一个 test-upstream 的负载均衡配置,通过 proxy_pass 反向代理指令将请求转发给该模块进行分配处理。 ...

March 1, 2019 · 2 min · jiezi

安排!活动素材的亿级用户精准投放

1.背景随着闲鱼用户快速增长,运营活动越来越趋于精细和个性化,运营会根据用户偏好为其投放合适的活动,如下图所示在闲鱼首页商品展示时,会在商品的列表中插入活动Banner,通过这些活动banner引导用户进入到相应活动会场,实现会场导流。闲鱼投放系统负责闲鱼运营活动的配置、管理、投放。主要解决了以下几个问题1.配置环境隔离问题,根据开发规范,任何线上业务必须先进行线下环境发布,测试验证通过后再发布上线。所以提供的管理平台需要拥有线上和线下环境隔离的能力。2.检索中的性能问题,在同一资源位下会配置多个活动,每次检索时需要把该资源位下的所有活动拉出来,按照条件进行筛选出符合要求的活动,这个过程会随着资源位下的活动增多检索遇到性能瓶颈。3.人群管理问题,在活动中会配置投放的人群,每次检索活动时人群作为活动的检索条件,需要验证用户是否属于当前活动的人群,所以需要解决用户和人群关系的管理。4.AB测试问题,运营投放的活动往往需要进行AB测试比较不同策略的表现,此时需要提供AB测试的能力。下面将通过介绍闲鱼投放系统设计、技术方案设计和实现过程,进一步阐述如何解决上面提出的四个问题。2.系统架构设计闲鱼投放系统是一个配置管理和配置检索的系统,换句话讲他不生产任何活动素材,他只是活动素材的搬运工。下面介绍闲鱼投放的系统设计。如上图所示闲鱼投放系统共分为了活动素材层、投放配置层、业务流程层和应用层四个层次。活动素材层是对在闲鱼投放系统中所有素材源的汇总,目前闲鱼投放中主要汇集了三种素材鲁班素材、马赫素材、TPP素材,鲁班素材提供了用户个性化Banner的能力,他的原理是根据用户的行为获取到偏好的商品,然后把商品图和素材模板组合为一个Banner;马赫素材提供规则圈选商品,他的原理是利用规则引擎把规则转换为SQL语句,然后利用该SQL在商品表中捞出符合规则的商品,同时马赫利用流计算能力对增量商品也实现了实时的规则圈选。TPP素材提个性化商品推荐,例如首页的商品推荐和猜你喜欢的商品推荐,TPP作为个性化推荐平台,可以根据不同的算法实现不同的推荐策略。投放配置层是开放给运营能力的汇总,包含活动配置、环境隔离、数据报表三种能力。活动配置是对一个资源位下所有投放行为的具体配置,如下图所示每个资源位会投放多个活动,活动与活动之间需要进行排期,每个投放活动中包含人群和素材两类信息,人群用来确定该活动投放的对象,素材用来确定该活动投放的内容,同时在人群下支持AB的能力。环境隔离是为了能够区分线上和线下业务,保证线上的投放环境在线下充分验证后再进行上线。数据报表是对所有投放活动关键指标的数据汇总。业务流程层是闲鱼投放系统的关键,主要负责投放活动的检索,根据调用方传入的用户信息和资源位信息,返回该资源位下符合该用户的活动。具体的检索逻辑如上图所示,首先在DB中查询当前资源位下的所有在线活动,然后依次过滤每个活动下的人群信息是否符合当前用户,从符合该用户的活动列表中选取一个活动,如果该活动下有AB测试,需要请求AB测试平台获取AB测试中配置的素材信息,最后返回该活动下的素材内容,客户端拿到活动素材后进行展示。应用层是对客户端能力的汇总,包括获取素材、素材样式展示、数据埋点。素材展示是直接与用户交互的部分,需要前端提供多种展示样式,数据埋点是为了验证AB策略和收集活动关键指标数据。3. 技术方案设计在上文中我们提到闲鱼投放面临四个需要解决的问题,分别是环境隔离问题、活动检索问题、人群管理问题、AB能力问题。下面将分别从这几个问题出发介绍解决的方法。3.1 人群管理问题人群管理使用的是集团内的奥格人群平台,他为我们提供了人群圈选和人群验证的能力,在很大程度上解放了闲鱼投放的人群管理,下面简单的介绍一下该平台的实现原理。如上图所示展示了奥格几个核心功能点,实现原理是这样的,首先平台会提供给用户可选择的规则,然后利用规则引擎把所选的规则,生成SQL查询语句和流计算规则,SQL查询语句用来离线圈选用户和流计算规则用来实时筛选新增用户,通过离线规则和在线规则实现了奥格的人群圈选,在人群验证阶段,首先利用倒排检索的思想,用户和人群的关系利用倒排数据结构标识,该方法解决了单用户与多人群关系验证的效率问题,最后利用多级缓存和热点数据本地缓存的方式解决人群检索RT问题。3.2 AB测试管理问题AB测试是用来验证方案的常用方法,常用的AB测试方案大多是用户唯一属性取模的方式按比例划分用户,但是会面临很多复杂的问题1.按照用户的id进行取模计算,对于未登录用户处理是一个常被忽略的问题。2.测试白名单管理,在AB测试时需要把特定人员划分到特定测试桶里。3.多个AB正交测试,如果有多个AB测试,此时需要正交测试时会出现更复杂的情况。在闲鱼投放中使用了集团的一休AB平台,一休提供基于用户、设备等多维度AB策略,同时支持白名单与正交AB测试的复杂场景,在AB基本能力的基础上提供了数据分析的能力,实现了调用到数据管理的一体化。3.3环境隔离问题解决环境隔离问题主要是为了方便测试,先在线下看效果,然后再把数据配置到线上。为了实现环境隔离迭代两次技术方案。首先介绍第一个方案,依照总体功能设计我希望平台中每个模块都可以灵活复用,可以利用已有模块,快速搭建出满足业务要求的投放活动,所以从业务角度进行了抽象,把能拆分的模块尽可能的抽象出来,最终的实体关系如下图所示。从业务逻辑角度共抽象了6个实体分别是资源位(Resource)、活动(Activity)、人群(Crowd)、素材(Data Source)、资源位和活动的关系(Resource Plan)、活动和人群素材的关系(Activity Plan)实体,每个模块之间可以按照下图的关系进行自由组合成一个投放活动。在该方案中利用每个实体中的env字段解决环境隔离问题,无论是在投放活动配置还是在检索过程中,只可以利用相同env字段的实体,该方法完全实现了环境隔离,但是在实际的应用中效果却不是很好,因为利用一份数据表中的env字段实现环境隔离,所以线上和线下对应的Resource Plan和Activity Plan关系表中关联的实体ID不同,那么将无法实现线下配置直接拷贝到线上,此时需要在线下和线上两次配置,由于配置过于复杂增加错误风险。下面介绍第二个方案,第二个技术方案中对方案一中提出的问题进行优化。具体的设计如下图所示:如上图所示,实体对象由6个转换为4个,下面一次介绍这些实体和如何解决环境隔离问题。首先介绍新引入的Data Schema实体,DataSchema是由开发同学负责,提供了一个配置好的JSON配置模板,他与Resource进行关联,意味着当前Resource下的所有DataSource都将按照该DataSchema提供的JSON模板进行配置,同时在解析时也按照当前的DataSchema进行解析Resource不再区分线上和线下环境,因为Resource无论是线上和线下他总是存在的并且不会改变的,所以区分线上和线下是没有必要的。DataSource不再用env字段区分线上和线下环境,利用preData和onlineData进行区分线上和线下配置,由于引入了DataSchema模板,所以彻底解放了DataSource,他不再需要进行繁琐的配置,只需按照DataSchema把所有的需要字段都配置到对应的Schema中即可。这样在线上和线下DataSource是一条数据主键不再改变。Activity实体是DataSource和Resource的关系实体,同时包括活动的人群、起止时间等属性。由于DataSource和Resource实体线上和线下环境中主键ID都不会改变,那么意味着Activity可以把线下的配置直接同步到线上,在同步过程中需要做的是如果线上没有配置就插入一条如果存在就更新。那么怎么映射Activity线下和线上的关系呢,在Activity里面引入了mapId字段,线下的Activity实体在mapId中存储线上Activity实体的主键Id,利用这种映射关系实现了线下和线上的映射。具体的如上图所示,通过这种表和表之间映射关系,实现了环境隔离问题,同时简化了业务中的实体,让配置更简单更易用。3.4活动检索问题在实际应用中,我们遇到了检索能力的性能瓶颈,根据每次检索时都需要拉出当前资源位下的全部活动,然后按照起止时间、人群作为过滤条件,筛选出满足当前用户的活动列表。以上过程中每次检索都会发生与数据库的IO操作。当资源位和访问QPS增多时,数据库IO操作将成倍数增长,此时会成为检索的瓶颈,所以在以上的技术方案中,需要一个完备的缓存方案支撑检索的正常运行。按照常规的缓存设计方案进行了如下的缓存方案设计。所有的查询都是先进行缓存查询,如果未命中再查询数据库,把查询到的数据回写到缓存中。对于所有的更新操作,都是先更新数据库,然后再失效缓存,在更新活动时,需要在失效活动缓存的同时,也要失效该活动对应资源位下活动列表的缓存。但是在使用过程中遇到了一个问题,资源位下的活动列表存储采用了kv结构,key为资源位ID,value为活动列表的JSON序列化,当资源位下的活动增多时value也会随着膨胀最后超出阈值,所以把活动对象进行了简化仅存储活动Id和人群Id。优化后检索过程将有所变化如下图所示:4.总结与展望4.1总结通过以上的整体功能设计、技术方案设计、代码实现,介绍了一个投放平台从设计到实现过程中遇到的问题点和解决方案。目前投放平台已经在闲鱼的用户实时触达、首页feeds投放、淘宝闲鱼小程序投放中使用,完美支持运营根据人群精准投放活动。4.2展望闲鱼素材投放平台但仍有需要持续完善的地方,首先是精准人群的个性化,例如在首页的投放中,针对圈选的人群透出的Banner图片都是一样的,目前我们的投放最小粒度是人群未来将会做到个人。然后是投放能力自优化,目前活动针对资源位的争夺还是利用权重、人群、起止时间作为前置条件,未来将会通过投放数据回流利用算法计算其关键指标实现投放的自优化。同时闲鱼素材投放将对接集团内部的更多优秀的素材提供源丰富闲鱼的活动。本文作者:闲鱼技术-齐悟阅读原文本文为云栖社区原创内容,未经允许不得转载。

March 1, 2019 · 1 min · jiezi

Spring Cloud Alibaba迁移指南(四):零代码兼容 Api-Gateway

自 Spring Cloud 官方宣布 Spring Cloud Netflix 进入维护状态后,我们开始制作《Spring Cloud Alibaba迁移指南》系列文章,向开发者提供更多的技术选型方案,并降低迁移过程中的技术难度。第一篇:一行代码从 Hystrix 迁移到 Sentinel第二篇:零代码替换 Eureka第三篇:极简的 Config如果你为 Api-Gateway(可能是 Zuul,也可能是 spring cloud gateway) 选择了 Eureka 为注册中心, 找不到一个合适的替换方案而苦苦烦恼时,那接下来的内容将是非常值得你一读。Spring Cloud Alibaba 不管是开源的服务注册组件还是商业化,都实现了 Spring Cloud 服务注册的标准规范。这就天然的给开发者提供了一种非常便利的方式将服务注册中心的 Eureka 迁移到开源的 Nacos。兼容 Api-Gateway:零代码替换 Eureka使用 Spring Cloud Alibaba 的开源组件 spring-cloud-starter-alibaba-nacos-discovery 来替换 Eureka,兼容 Api-Gateway(注意: 这里的 Api-Gateway 是一个统称,有可能是基于 Zuul 来实现,也有能可能是基于 spring cloud gateway 来实现。)仅需要完成以下几个简单的步骤即可。环境准备工作:本地需要安装 Nacos。Nacos 的安装方式也是极其的简单,参考 Nacos 官网。假设现在已经正常启动了 Nacos 。添加 Nacos 的 pom 依赖,同时去掉 Eureka。 在需要替换的工程目录下找到 maven 的配置文件 pom.xml。添加如下的 pom 依赖:<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>0.2.1.RELEASE</version> </dependency>同时将依赖的 spring-cloud-starter-netflix-eureka-client pom 给去掉。 application.properties 配置。 一些关于 Nacos 基本的配置也必须在 application.properties(也可以是application.yaml)配置,如下所示:spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848同时将与 Eureka 相关的配置删除。(可选) 更换 EnableEurekaClient 注解。 如果在你的应用启动程序类加了 EnableEurekaClient 注解,这个时候需要更符合 Spring Cloud 规范的一个注解 EnableDiscoveryClient 。注意:以上几个步骤不仅仅是在集成 Api-Gateway 网关的项目中做相应的更改,通过 Api-Gateway 网关进行转发的后端服务也都要做相应的更改。完成以上三个步骤,就已经兼容了 Api-Gateway 网关的路由转发。关于如何使用 Spring Cloud Alibaba 的商业化组件 ANS 来替换掉 Api-Gateway 的注册中心 Eureka,详细的文档可参考这里。至此,《Spring Cloud Alibaba迁移指南》系列文章的四篇已全部,若您在迁移过程遇到了其他难题,欢迎到Spring Cloud Alibaba@GitHub 提issue。本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 1, 2019 · 1 min · jiezi

Spring Cloud Alibaba迁移指南(三):极简的 Config

自 Spring Cloud 官方宣布 Spring Cloud Netflix 进入维护状态后,我们开始制作《Spring Cloud Alibaba迁移指南》系列文章,向开发者提供更多的技术选型方案,并降低迁移过程中的技术难度。第一篇:一行代码从 Hystrix 迁移到 Sentinel第二篇:零代码替换 Eureka第三篇,我们一起来看看 Spring Cloud Alibaba 是如何使用极简的方式来做到分布式应用的外部化配置,使得应用在运行时动态更新某些配置成为可能。 目前关于 Spring Cloud Config 的标准实现开源方面有三个,分别是:Spring Cloud Alibaba Nacos ConfigSpring Cloud Consul ConfigSpring Cloud Config (Spring Cloud 官方集成的方式)那面对于这么多的实现,Spring Cloud Alibaba Nacos Config 的实现它具有哪些优势呢?大致从以下几个方面来全方位的分析。 Spring Cloud Alibaba Nacos ConfigSpring Cloud Consul ConfigSpring Cloud Config (Spring Cloud 官方集成的方式)配置存储直接依赖于 Nacos。直接依赖于 Consul。通常的组合是Config-server 和 git。配置刷新无需人工干预,自动秒级刷新。无需人工干预,自动秒级刷新。需要人工干预,手动触发/bus/refresh 接口,才能达到配置动态刷新的效果。是否集成第三方服务不需要。不需要。存储需要依赖于git,刷新依赖于 RabbitMQ 。运维组件只需要运维 Nacos 本身即可。只需要运维 Consul本身。通常是要运维 Config-erver,MQ 的服务,提供存储能力的 Git。比较重的第三方依赖无,直接引入starter 即可 。无,直接引入 starter 即可。不仅需要引入 starter,而且还需要引入配置刷新依赖的 spring-cloud-starter-bus-amqp 。推送状态支持无无更新历史查询支持无无配置回滚支持无无配置加解密支持待确认待确认多重容灾支持无无同时 Spring Cloud Alibaba 还可以基于 Spring Cloud Alibaba Nacos Config 无缝对接云上的 ACM,这给一些需要上云的用户带来了极其的方便。综上全方位的对比,Spring Cloud Alibaba Nacos Config 无疑提供了性价比最高的 Spring Cloud Config 的开源实现。下面以一个快速上手的案例体验一下 Spring Cloud Alibaba Nacos Config 的实现是如何使用的。同时也提供了简单的方式给那些想转用 Spring Cloud Alibaba Nacos Config 的同学做一些参考。第 1 步:Nacos 服务端初始化。1.1 启动 Nacos Server。启动方式可见 Nacos 官网 。1.2 添加配置。启动好 Nacos 之后,在 Nacos 控制台添加如下的配置。Data ID: ${spring.application.name}.propertiesGroup : DEFAULT_GROUP配置格式: Properties配置内容: ${key}=${value}注意:Data Id 是以 properties(默认的文件扩展名方式)为扩展名。文件名以 &dollar;{spring.application.name} 配置参数为主。配置内容:当你想从其他的存储源(例如: git) 要往 Nacos 进行迁移的话,目前只能通过手动的方式进行逐个的添加。&dollar;{key} 是根据您的业务场景需要配置的或者迁移的 key, &dollar;{value} 就是对应的具体值。第 2 步:Spring Cloud Alibaba Nacos Config 客户端使用方式。2.1 添加 maven 依赖。为了能够在应用程序中使用 Nacos 来实现应用的外部化配置,在构建应用的同时或者已经存在的应用需要引入一个 Starter,如下所示:<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> <version>0.2.2.BUILD-SNAPSHOT</version></dependency>2.2 添加相关配置。客户端需要和 Nacos 服务端进行通信,因此需要配置 Nacos 服务端的地址。在您的应用配置文件中新增如下配置,这里以 application.properties 为例。spring.cloud.nacos.config.server-addr=127.0.0.1:8848完成以上两个步骤,就已经完成了 Spring Cloud Alibaba Nacos Config 的基本使用。完整的使用可参考 Spring Cloud Alibaba 的管方 Wiki 文档。本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

February 28, 2019 · 1 min · jiezi

Spring Cloud Alibaba迁移指南(二):零代码替换 Eureka

自 Spring Cloud 官方宣布 Spring Cloud Netflix 进入维护状态后,我们开始制作《Spring Cloud Alibaba迁移指南》系列文章,向开发者提供更多的技术选型方案,并降低迁移过程中的技术难度。第二篇,Spring Cloud Alibaba 实现了 Spring Cloud 服务注册的标准规范,这就天然的给开发者提供了一种非常便利的方式将服务注册中心的 Eureka 迁移到开源的 Nacos 。 第一篇回顾:一行代码从 Hystrix 迁移到 Sentinel零代码使用 Nacos 替换 Eureka如果你需要使用 Spring Cloud Alibaba 的开源组件 spring-cloud-starter-alibaba-nacos-discovery 来替换 Eureka。需要完成以下几个简单的步骤即可。1. __本地需要安装 Nacos。__Nacos 的安装方式也是极其的简单,参考 Nacos 官网。假设现在已经正常启动了 Nacos 。2. 添加 Nacos 的 pom 依赖,同时去掉 Eureka。 在需要替换的工程目录下找到 maven 的配置文件 pom.xml。添加如下的 pom 依赖:<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>0.2.1.RELEASE</version> </dependency></dependencies>同时将依赖的 spring-cloud-starter-netflix-eureka-client pom 给去掉。 3. application.properties 配置。 一些关于 Nacos 基本的配置也必须在 application.properties(也可以是application.yaml)配置,如下所示: application.properties:spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848同时将和 Eureka 相关的配置删除。4. (可选) 更换 EnableEurekaClient 注解。如果在你的应用启动程序类加了 EnableEurekaClient 注解,这个时候需要更符合 Spring Cloud 规范的一个注解EnableDiscoveryClient 。直接启动你的应用即可。到目前为止,就已经完成了 “零行代码使用 Nacos 替换 Eureka”。完整的方式可参考 Spring Cloud Alibaba 的官方 Wiki 文档。零代码使用 ANS 替换 Eureka如果你需要使用 Spring Cloud Alibaba 的商业化组件 spring-cloud-starter-alicloud-ans 来替换 Eureka。也是仅需完成以下几个简单的步骤即可。1. 本地需要安装 轻量版配置中心。 轻量版配置中心的下载和启动方式可参考 这里。假设现在已经正常启动了轻量版配置中心 。2. 添加 ANS 的 pom 依赖,同时去掉 Eureka。 在需要替换的工程目录下找到 maven 的配置文件 pom.xml。添加如下的 pom 依赖:<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alicloud-ans</artifactId> <version>0.2.1.RELEASE</version> </dependency></dependencies>同时将依赖的 org.springframework.cloud:spring-cloud-starter-netflix-eureka-client pom 给去掉。 3. (可选) application.properties 配置。 一些关于 ANS 基本的配置也可以在 application.properties(也可以是application.yaml)配置,如下所示: application.properties:spring.cloud.alicloud.ans.server-list=127.0.0.1spring.cloud.alicloud.ans.server-port=8080如果不配置的话,默认值就是 127.0.0.1 和 8080 ,因此这一步是可选的。同时将和 Eureka 相关的配置删除。4. (可选) 更换 EnableEurekaClient 注解。如果在你的应用启动程序类加了 EnableEurekaClient 注解,这个时候需要更符合 Spring Cloud 规范的一个注解EnableDiscoveryClient 。代码层面不需要改动任何代码,直接启动你的应用即可。到目前为止,就已经完成了 “零代码使用 ANS 替换 Eureka”。完整的使用方式可参考 Spring Cloud Alibaba 的官方 Wiki 文档。本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

February 27, 2019 · 1 min · jiezi

使用Logtail采集Kubernetes上挂载的NAS日志

采集k8s挂载Nas后的日志该文档主要介绍使用logtail以两种不同的方式进行k8s挂载Nas后的日志采集。两种采集方式的实现原理是一样的,都是通过将Logtail和业务容器挂载到相同的NAS上,使Logtail和业务容器的日志数据共享,以此实现日志采集。下面是两种采集方式的各自特点:1. SideCar模式。比较灵活、适合水平扩容,适用于数据量较大的场景;2. 单独部署Logtail的Deployment。资源消耗比较低、但灵活性以及伸缩性不强,适用于整体集群数据量较少的场景(建议整体日志量不超过每秒10M)。1. Sidecar NAS采集方式通过 链接 使用PV&PVC的方式配置挂载Nas的nas-pvc步骤一 创建pv步骤二 创建pvc步骤三 根据下面的yaml模板创建含有logtail的Pod,进行单个Pod的内部采集sideCar模式实验yaml内容:apiVersion: batch/v1kind: Jobmetadata: name: nginx-log-sidecar1-demospec: template: metadata: name: nginx-log-sidecar-demo spec: # volumes配置 volumes: - name: nginx-log persistentVolumeClaim: claimName: nas-pvc containers: # 主容器配置 - name: nginx-log-demo image: registry.cn-hangzhou.aliyuncs.com/log-service/docker-log-test:latest command: ["/bin/mock_log"] args: ["–log-type=nginx", “–stdout=false”, “–stderr=true”, “–path=/var/log/nginx/access.log”, “–total-count=1000000000”, “–logs-per-sec=100”] volumeMounts: - name: nginx-log mountPath: /var/log/nginx # Logtail的Sidecar容器配置 - name: logtail image: registry.cn-hangzhou.aliyuncs.com/log-service/logtail:latest env: # user id - name: “ALIYUN_LOGTAIL_USER_ID” value: “${your_aliyun_user_id}” # user defined id - name: “ALIYUN_LOGTAIL_USER_DEFINED_ID” value: “${your_machine_group_user_defined_id}” # config file path in logtail’s container - name: “ALIYUN_LOGTAIL_CONFIG” value: “/etc/ilogtail/conf/${your_region_config}/ilogtail_config.json” # env tags config - name: “ALIYUN_LOG_ENV_TAGS” value: “pod_name|pod_ip|namespace|node_name|node_ip” - name: “pod_name” valueFrom: fieldRef: fieldPath: metadata.name - name: “pod_ip” valueFrom: fieldRef: fieldPath: status.podIP - name: “namespace” valueFrom: fieldRef: fieldPath: metadata.namespace - name: “node_name” valueFrom: fieldRef: fieldPath: spec.nodeName - name: “node_ip” valueFrom: fieldRef: fieldPath: status.hostIP # 和主容器共享volume volumeMounts: - name: nginx-log mountPath: /var/log/nginx # 健康检查 livenessProbe: exec: command: - /etc/init.d/ilogtaild - status initialDelaySeconds: 30 periodSeconds: 30 restartPolicy: “Never"SLS控制台采集配置设置如下图:日志路径与被采集容器的日志所在路径一致注意:由于NAS路径已经挂载到了Logtail容器上,所以不需要打开docker文件的按钮采集上来的系统默认字段含义:source: pod容器内部IP__tag__:hostname: pod名称__tag__:path: 日志路径__tag__:receive_time: 采集时间__tag__:user_defined_id: 用户自定义标识__tag__:namespace: pod所属namaspace__tag__:node_ip: pod所在Node的IP地址__tag__:node_name: pod所属Node的name__tag__:pod_ip: pod容器内部IP__tag__:pod_name: pod名称用户参数:2. 一个Logtail采集所有POD的NAS数据注意项:副本数spec.replicas只能为1,不能更多,多了会重复采集。首先,创建一个logtail的deployment,以下是本次使用的模板:apiVersion: apps/v1kind: Deploymentmetadata: name: logtail-deployment namespace: kube-system labels: k8s-app: nas-logtail-collecterspec: replicas: 1 selector: matchLabels: k8s-app : nas-logtail-collecter template: metadata: name: logtail-deployment labels: k8s-app : nas-logtail-collecter spec: containers: # Logtail的配置 - name: logtail image: registry.cn-hangzhou.aliyuncs.com/log-service/logtail:latest env: # aliuid - name: “ALIYUN_LOGTAIL_USER_ID” value: “${your_aliyun_user_id}” # user defined id - name: “ALIYUN_LOGTAIL_USER_DEFINED_ID” value: “${your_machine_group_user_defined_id}” # config file path in logtail’s container - name: “ALIYUN_LOGTAIL_CONFIG” value: “/etc/ilogtail/conf/${your_region_config}/ilogtail_config.json” volumeMounts: - name: nginx-log mountPath: /var/log/nginx # volumes配置 volumes: - name: nginx-log persistentVolumeClaim: claimName: pvc-test-nginx__注意:__这里的 claimName: pvc-test-nginx 以及mountPath: /var/log/nginx 是将logtail的/var/log/nginx挂载了Nas下的/nginx文件夹相关参数设置请参考方案1中的表格说明logtail运行成功之后,可以在SLS控制台根据模板中的ALIYUN_LOGTAIL_USER_DEFINED_ID创建对应的机器组,请参考方案1中的表格说明。这里新建2个Pod来测试采集是否成功,其中一个POD的模板为:apiVersion: v1kind: Podmetadata: name: “test-nginx-2"spec: containers: - name: “nginx-log-demo” image: “registry.cn-hangzhou.aliyuncs.com/log-service/docker-log-test:latest” command: ["/bin/mock_log”] args: [”–log-type=nginx", “–stdout=false”, “–stderr=true”, “–path=/var/log/nginx/access.log”, “–total-count=1000000000”, “–logs-per-sec=100”] volumeMounts: - name: “nas2” mountPath: “/var/log/nginx” volumes: - name: “nas2” flexVolume: driver: “alicloud/nas” options: server: “Nas挂载地址” path: “/nginx/test2” vers: “4.0"另一个Pod将 /var/log/nginx 挂载在了 /nginx/test1 目录下;结合logtail的挂载情况,现在两个Pod分别挂载在 /nginx/test1 和 /nginx/test2,而logtail挂载在了 /nginx 下。最后配置logtail的采集配置因为logtail也挂载了相同的Nas,所以logtail只需要采集自身文件夹下的日志就可以了,这里的是否为docker文件选项关闭。注意:由于NAS路径已经挂载到了Logtail容器上,所以不需要打开docker文件的按钮本文作者:元乙阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

February 27, 2019 · 2 min · jiezi

微服务架构下,解决数据一致性问题的实践

随着业务的快速发展,应用单体架构暴露出代码可维护性差、容错率低、测试难度大和敏捷交付能力差等诸多问题,微服务应运而生。微服务的诞生一方面解决了上述问题,但是另一方面却引入新的问题,其中主要问题之一就是:如何保证微服务间的业务数据一致性。本文将通过一个商品采购的业务,来看看在Dubbo的微服务架构下,如何通过Fescar来保障业务的数据一致性。本文所述的例子中,Dubbo 和 Fescar 的注册配置服务中心均使用 Nacos。Fescar 0.2.1+ 开始支持 Nacos 注册配置服务中心。业务描述用户采购商品的业务,包含3个微服务:库存服务: 扣减给定商品的库存数量。订单服务: 根据采购请求生成订单。账户服务: 用户账户金额扣减。业务结构图如下:库存服务(StorageService)public interface StorageService { /** * deduct storage count / void deduct(String commodityCode, int count);}订单服务(OrderService)public interface OrderService { /* * create order / Order create(String userId, String commodityCode, int orderCount);}账户服务(AccountService)public interface AccountService { /* * debit balance of user’s account */ void debit(String userId, int money);}说明: 以上三个微服务均是独立部署。8个步骤实现数据一致性Step 1:初始化 MySQL 数据库(需要InnoDB 存储引擎)在 resources/jdbc.properties 修改StorageService、OrderService、AccountService 对应的连接信息。jdbc.account.url=jdbc:mysql://xxxx/xxxxjdbc.account.username=xxxxjdbc.account.password=xxxxjdbc.account.driver=com.mysql.jdbc.Driver# storage db configjdbc.storage.url=jdbc:mysql://xxxx/xxxxjdbc.storage.username=xxxxjdbc.storage.password=xxxxjdbc.storage.driver=com.mysql.jdbc.Driver# order db configjdbc.order.url=jdbc:mysql://xxxx/xxxxjdbc.order.username=xxxxjdbc.order.password=xxxxjdbc.order.driver=com.mysql.jdbc.DriverStep 2:创建 undo_log(用于Fescar AT 模式)表和相关业务表相关建表脚本可在 resources/sql/ 下获取,在相应数据库中执行 dubbo_biz.sql 中的业务建表脚本,在每个数据库执行 undo_log.sql 建表脚本。CREATE TABLE undo_log ( id bigint(20) NOT NULL AUTO_INCREMENT, branch_id bigint(20) NOT NULL, xid varchar(100) NOT NULL, rollback_info longblob NOT NULL, log_status int(11) NOT NULL, log_created datetime NOT NULL, log_modified datetime NOT NULL, ext varchar(100) DEFAULT NULL, PRIMARY KEY (id), KEY idx_unionkey (xid,branch_id)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;DROP TABLE IF EXISTS storage_tbl;CREATE TABLE storage_tbl ( id int(11) NOT NULL AUTO_INCREMENT, commodity_code varchar(255) DEFAULT NULL, count int(11) DEFAULT 0, PRIMARY KEY (id), UNIQUE KEY (commodity_code)) ENGINE=InnoDB DEFAULT CHARSET=utf8;DROP TABLE IF EXISTS order_tbl;CREATE TABLE order_tbl ( id int(11) NOT NULL AUTO_INCREMENT, user_id varchar(255) DEFAULT NULL, commodity_code varchar(255) DEFAULT NULL, count int(11) DEFAULT 0, money int(11) DEFAULT 0, PRIMARY KEY (id)) ENGINE=InnoDB DEFAULT CHARSET=utf8;DROP TABLE IF EXISTS account_tbl;CREATE TABLE account_tbl ( id int(11) NOT NULL AUTO_INCREMENT, user_id varchar(255) DEFAULT NULL, money int(11) DEFAULT 0, PRIMARY KEY (id)) ENGINE=InnoDB DEFAULT CHARSET=utf8;说明: 需要保证每个物理库都包含 undo_log 表,此处可使用一个物理库来表示上述三个微服务对应的独立逻辑库。Step 3:引入 Fescar、Dubbo 和 Nacos 相关 POM 依赖 <properties> <fescar.version>0.2.1</fescar.version> <dubbo.alibaba.version>2.6.5</dubbo.alibaba.version> <dubbo.registry.nacos.version>0.0.2</dubbo.registry.nacos.version> </properties> <dependency> <groupId>com.alibaba.fescar</groupId> <artifactId>fescar-spring</artifactId> <version>${fescar.version}</version> </dependency> <dependency> <groupId>com.alibaba.fescar</groupId> <artifactId>fescar-dubbo-alibaba</artifactId> <version>${fescar.version}</version> <exclusions> <exclusion> <artifactId>dubbo</artifactId> <groupId>org.apache.dubbo</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>${dubbo.alibaba.version}</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo-registry-nacos</artifactId> <version>${dubbo.registry.nacos.version}</version> </dependency>说明: 由于当前 apache-dubbo 与 dubbo-registry-nacos jar存在兼容性问题,需要排除 fescar-dubbo 中的 apache.dubbo 依赖并手动引入 alibaba-dubbo,后续 apache-dubbo(2.7.1+) 将兼容 dubbo-registry-nacos。在Fescar 中 fescar-dubbo jar 支持 apache.dubbo,fescar-dubbo-alibaba jar 支持 alibaba-dubbo。Step 4:微服务 Provider Spring配置分别在三个微服务Spring配置文件(dubbo-account-service.xml、 dubbo-order-service 和 dubbo-storage-service.xml )进行如下配置:配置 Fescar 代理数据源<bean id=“accountDataSourceProxy” class=“com.alibaba.fescar.rm.datasource.DataSourceProxy”> <constructor-arg ref=“accountDataSource”/></bean><bean id=“jdbcTemplate” class=“org.springframework.jdbc.core.JdbcTemplate”> <property name=“dataSource” ref=“accountDataSourceProxy”/></bean>此处需要使用 com.alibaba.fescar.rm.datasource.DataSourceProxy 包装 Druid 数据源作为直接业务数据源,DataSourceProxy 用于业务 SQL 的拦截解析并与 TC 交互协调事务操作状态。配置 Dubbo 注册中心 <dubbo:registry address=“nacos://${nacos-server-ip}:8848”/>配置 Fescar GlobalTransactionScanner<bean class=“com.alibaba.fescar.spring.annotation.GlobalTransactionScanner”> <constructor-arg value=“dubbo-demo-account-service”/> <constructor-arg value=“my_test_tx_group”/></bean>此处构造方法的第一个参数为业务自定义 applicationId,若在单机部署多微服务需要保证 applicationId 唯一。构造方法的第二个参数为 Fescar 事务服务逻辑分组,此分组通过配置中心配置项 service.vgroup_mapping.my_test_tx_group 映射到相应的 Fescar-Server 集群名称,然后再根据集群名称.grouplist 获取到可用服务列表。Step 5:事务发起方配置在 dubbo-business.xml 配置以下配置:配置 Dubbo 注册中心同 Step 4配置 Fescar GlobalTransactionScanner同 Step 4在事务发起方 service 方法上添加 @GlobalTransactional 注解@GlobalTransactional(timeoutMills = 300000, name = “dubbo-demo-tx”)timeoutMills 为事务的总体超时时间默认60s,name 为事务方法签名的别名,默认为空。注解内参数均可省略。Step 6:启动 Nacos-Server下载 Nacos-Server 最新 release 包并解压运行 Nacos-serverLinux/Unix/Macsh startup.sh -m standaloneWindowscmd startup.cmd -m standalone访问 Nacos 控制台:http://localhost:8848/nacos/index.html#/configurationManagement?dataId=&group=&appName=&namespace若访问成功说明 Nacos-Server 服务运行成功(默认账号/密码: nacos/nacos)Step 7:启动 Fescar-Server下载 Fescar-Server 最新 release 包并解压初始化 Fescar 配置进入到 Fescar-Server 解压目录 conf 文件夹下,确认 nacos-config.txt 的配置值(一般不需要修改),确认完成后运行 nacos-config.sh 脚本初始化配置。sh nacos-config.sh $Nacos-Server-IPeg:sh nacos-config.sh localhost 脚本执行最后输出 “init nacos config finished, please start fescar-server.” 说明推送配置成功。若想进一步确认可登陆Nacos 控制台 配置列表 筛选 Group=FESCAR_GROUP 的配置项。修改 Fescar-server 服务注册方式为 nacos进入到 Fescar-Server 解压目录 conf 文件夹下 registry.conf 修改 type=“nacos” 并配置 Nacos 的相关属性。 registry { # file nacos type = “nacos” nacos { serverAddr = “localhost” namespace = “public” cluster = “default” } file { name = “file.conf” }}type: 可配置为 nacos 和 file,配置为 file 时无服务注册功能nacos.serverAddr: Nacos-Sever 服务地址(不含端口号)nacos.namespace: Nacos 注册和配置隔离 namespacenacos.cluster: 注册服务的集群名称file.name: type = “file” classpath 下配置文件名运行 Fescar-serverLinux/Unix/Macsh fescar-server.sh $LISTEN_PORT $PATH_FOR_PERSISTENT_DATA $IP(此参数可选)Windowscmd fescar-server.bat $LISTEN_PORT $PATH_FOR_PERSISTENT_DATA $IP(此参数可选)服务端口 PATH_FOR_PERSISTENT_DATA: 事务操作记录文件存储路径(已存在路径)$IP(可选参数): 用于多 IP 环境下指定 Fescar-Server 注册服务的IPeg: sh fescar-server.sh 8091 /home/admin/fescar/data/运行成功后可在 Nacos 控制台看到 服务名 =serverAddr 服务注册列表:Step 8:启动微服务并测试修改业务客户端发现注册方式为 nacos同Step 7 中[修改 Fescar-server 服务注册方式为 nacos] 步骤启动 DubboAccountServiceStarter启动 DubboOrderServiceStarter启动 DubboStorageServiceStarter启动完成可在 Nacos 控制台服务列表 看到启动完成的三个 provider:启动 DubboBusinessTester 进行测试注意: 在标注 @GlobalTransactional 注解方法内部显示的抛出异常才会进行事务的回滚。整个 Dubbo 服务调用链路只需要在事务最开始发起方的 service 方法标注注解即可。通过以上8个步骤,我们实现了用户采购商品的业务中库存、订单和账户3个独立微服务之间的数据一致性。参考链接:本文 sample 地址: https://github.com/fescar-group/fescar-samples/tree/master/nacosFescar: https://github.com/alibaba/fescarDubbo: https://github.com/apache/incubator-dubboNacos: https://github.com/alibaba/nacos本文作者:清铭,社区昵称 slievrly,Fescar 开源项目发起人之一,阿里巴巴中件间 TXC/GTS 核心研发成员,长期从事于分布式中间件核心研发工作,在分布式事务领域有着较丰富的技术积累。有关 Fescar 的更多信息:分布式事务中间件 Fescar - RM 模块源码解读关于开源分布式事务中间件Fescar,我们总结了开发者关心的13个问题本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

February 25, 2019 · 3 min · jiezi

IDEA 插件开发入门教程

IntelliJ IDEA 是目前最好用的 JAVA 开发 IDE,它本身的功能已经非常强大了,但是每个人的需求不一样,有些需求 IDEA 本身无法满足,于是我们就需要自己开发插件来解决。工欲善其事,必先利其器,想要提高开发效率,我们可以借助 IDEA 提供的插件功能来满足我们的需求。如果没有我需要的功能怎么办?很简单,我们自己造一个!插件能做什么?IDEA 的插件几乎可以做任何事情,因为它把 IDE 本身的能力都封装好开放出来了。主要的插件功能包含以下四种:自定义语言支持:如果有 IDEA 暂时不支持的语言,你可以自己写一个插件来支持,例如 Go 语言原来的支持就是通过插件做的,后来单独做了一个 Goland。官方有自定义语言插件支持的教程。框架支持:例如Struts 2 的框架支持工具集成:可以给 IDEA 的自带功能进行增强,例如对 Git 的操作增加 CodeReview 的功能。参考Gerrit用户界面:自定义的插件改变用户界面。参考BackgroundImage我为了减少重复代码的编写,写了一个代码生成的插件IDEA代码生成插件CodeMaker,支持自定义代码生成的模板。Hello world 插件依照惯例,我们从 Hello world 开始。新建一个 Gradle 的插件工程有些教程推荐用 IDEA 默认的插件工程来开始,但是我比较推荐用 Gradle 来管理整个插件工程,后面的依赖管理会很方便,否则都得靠手动管理。点击新建工程,选择 Gradle接下来填写项目属性配置 Gradle,用默认配置就行新建完工程之后,IDEA 会自动开始解析项目依赖,因为它要下载一个几百兆的 SDK 依赖包,所以会比较久,打开科学上网能快一点。Gradle 依赖解析完成之后,项目结构如下图,其中 plugin.xml 是插件的配置,build.gradle 是项目依赖的配置(类比 pom.xml)。下面就是默认生成的 plugin.xml<idea-plugin> <!–插件id–> <id>com.xiaokai.test.demo</id> <!–插件名称–> <name>Demo</name> <!–开发者信息–> <vendor email=“support@yourcompany.com” url=“http://www.yourcompany.com”>YourCompany</vendor> <!–插件说明–> <description><![CDATA[ Enter short description for your plugin here.<br> <em>most HTML tags may be used</em> ]]></description> <!– please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/plugin_compatibility.html on how to target different products –> <!– uncomment to enable plugin in all products <depends>com.intellij.modules.lang</depends> –> <!–依赖的其他插件能力–> <extensions defaultExtensionNs=“com.intellij”> <!– Add your extensions here –> </extensions> <!–插件动作–> <actions> <!– Add your actions here –> </actions></idea-plugin>创建一个 ActionAction 是 IDEA 中对事件响应的处理器,它的 actionPerformed 就像是 JS 中的 onClick 方法。可以看出来,插件的开发本质上跟 web、Android 的开发没有什么不同,因为都是事件驱动的编程。我们可以直接使用 IDEA 提供的 Action 生成器点击 OK 之后会在 src 生成类文件:package com.xiaokai.test;import com.intellij.openapi.actionSystem.AnAction;import com.intellij.openapi.actionSystem.AnActionEvent;public class HelloWorldAction extends AnAction { @Override public void actionPerformed(AnActionEvent e) { // TODO: insert action logic here }}同时,动作的信息也会注册到 plugin.xml 中 <!–插件动作–> <actions> <!– Add your actions here –> <action id=“demo.hello.world” class=“com.xiaokai.test.HelloWorldAction” text=“HelloWorld” description=“Say Hello World”> <add-to-group group-id=“GenerateGroup” anchor=“last”/> </action> </actions>弹出对话框创建完 Action 之后我们就要开始往里面写逻辑了,既然是 Hello World 教学,那我们就来试一下最简单的弹出对话框。 @Override public void actionPerformed(AnActionEvent e) { //获取当前在操作的工程上下文 Project project = e.getData(PlatformDataKeys.PROJECT); //获取当前操作的类文件 PsiFile psiFile = e.getData(CommonDataKeys.PSI_FILE); //获取当前类文件的路径 String classPath = psiFile.getVirtualFile().getPath(); String title = “Hello World!”; //显示对话框 Messages.showMessageDialog(project, classPath, title, Messages.getInformationIcon()); }代码写完之后,打开 Gradle 的界面,点击 runIde 就会启动一个安装了插件的 IDEA,然后就可以进行测试。你还可以右键启动 Debug 模式,这样还能进行断点。运行的效果如下图:可以看到,我们右键打开 Generate 菜单之后,里面最后一项就是我们添加的 Action,进阶的教程如果想学习更多的原理和设计理念可以看IntelliJ Platform SDK的官方文档。不过老实说,它的文档写的挺差的,基本上就是简单讲了一下概念和原理,没有深入的分析。所以如果要深入研究还得靠自己。最靠谱的学习方式就是看别人写的插件,举个例子,你想知道怎么样实现自动生成代码,你就去找支持这个功能的插件,看他的源码是怎么写的。我当时写CodeMaker的时候也是靠自己啃源码之后写出来的。下面我简单介绍一下我用过的一些 API,这些 API 基本都没有文档说明,全靠代码相传。判断当前光标选择的元素是什么 //获取当前事件触发时,光标所在的元素 PsiElement psiElement = anActionEvent.getData(LangDataKeys.PSI_ELEMENT); //如果光标选择的不是类,弹出对话框提醒 if (psiElement == null || !(psiElement instanceof PsiClass)) { Messages.showMessageDialog(project, “Please focus on a class”, “Generate Failed”, null); return; }获取当前类文件的所有类对象一个类文件中可能会有内部类,所以读取的时候返回的是一个列表 public static List<PsiClass> getClasses(PsiElement element) { List<PsiClass> elements = Lists.newArrayList(); List<PsiClass> classElements = PsiTreeUtil.getChildrenOfTypeAsList(element, PsiClass.class); elements.addAll(classElements); for (PsiClass classElement : classElements) { //这里用了递归的方式获取内部类 elements.addAll(getClasses(classElement)); } return elements; }格式化代码 public static void reformatJavaFile(PsiElement theElement) { CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(theElement.getProject()); try { codeStyleManager.reformat(theElement); } catch (Exception e) { LOGGER.error(“reformat code failed”, e); } }使用粘贴板 CopyPasteManager.getInstance() .setContents(new SimpleTransferable(table.toString(), DataFlavor.allHtmlFlavor));更多更多的技巧可以参考我的项目CodeMaker,以及其他的开源插件。本文作者:风马萧萧 阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

February 25, 2019 · 2 min · jiezi

高效开发者是如何个性化VS Code插件与配置的?

译者按: IDE是生产力的保证!原文:Visual Studio Code Settings and Extensions for Faster JavaScript Development译者: Fundebug本文采用意译,版权归原作者所有2年之前,我放弃了Sublime Text,选择了Visual Studio Code作为代码编辑器。我每天花在VS Code上的时间长达5~6个小时,因此按照我的需求优化VS Code配置十分必要。过去这2年里,我试过各种各样的插件与配置,而现在我感觉一切都完美了,是时候给大家分享一下我的使用技巧了!插件VS Code有着非常丰富的插件,这里我给大家推荐几个我最喜欢的VS Code插件。Prettier Code Formatter下载量:167万我使用Prettier来统一代码风格,当我保存HTML/CSS/JavaScript文件时,它会自动调整代码格式。这样,我不用担心代码格式问题了。由于Prettier本身不能个性化配置,有时可能会引起不适,但是至少保证团队成员可以轻易统一代码风格。npm下载量:119万npm插件可以检查package.json中所定义的npm模块与实际安装的npm模块是否一致:package.json中定义了,但是实际未安装package.json中未定义,但是实际安装了package.json中定义的版本与实际安装的版本不一致npm Intellisense下载量:105万npm Intellisense插件会为package.json建立索引,这样当我require某个模块时,它可以自动补全。Bracket Pair Colorizer下载量:95万Bracket Pair Colorizer可以为代码中的匹配的括号自动着色,以不同的颜色进行区分,这样我们可以轻易地辨别某个代码块的开始与结束。Fundebug, 1代码搞定BUG监控!Auto Close Tag下载量:117万Auto Close Tag插件的功能非常简单,它可以自动补全HTML/XML的关闭标签。GitLens下载量:164万我非常喜欢Gitlens,因为它可以帮助我快速理解代码的修改历史。Current Line Blame:查看当前行代码的结尾查看最近一次commit的姓名、时间以及信息Current Line Hovers:在当前行代码的悬浮框查看详细的最近一次的commit信息。Markdown All in One下载量:45万Markdown All in One插件帮助我编写README以及其他MarkDown文件。我尤其喜欢它处理列表以及表格的方式。自动调整列表的数字序号自动格式化表格用户配置除了安装各种各样的插件,我们还可以通过配置VS Code的User Settings来个性化我们的VS Code。字体设置我非常喜欢带有ligatures(合字、连字、连结字或合体字)的字体。ligatures就是将多于一个字母的合成一个字形。我主要使用Fira Code作为我编程所使用的字体,如下图中的=>与===:我的字体配置如下:“editor.fontFamily”: “‘Fira Code’, ‘Operator Mono’, ‘iA Writer Duospace’, ‘Source Code Pro’, Menlo, Monaco, monospace”,“editor.fontLigatures”: true关于缩进,我是这样配置的: “editor.detectIndentation”: true, “editor.renderIndentGuides”: false,import路径移动或者重命名时,自动更新:“javascript.updateImportsOnFileMove.enabled”: “always”,user-settings.json下面是我的VS Code的配置文件user-settings.json:{ “workbench.colorCustomizations”: { “activityBar.background”: “#111111”, “activityBarBadge.background”: “#FFA000”, “list.activeSelectionForeground”: “#FFA000”, “list.inactiveSelectionForeground”: “#FFA000”, “list.highlightForeground”: “#FFA000”, “scrollbarSlider.activeBackground”: “#FFA00050”, “editorSuggestWidget.highlightForeground”: “#FFA000”, “textLink.foreground”: “#FFA000”, “progressBar.background”: “#FFA000”, “pickerGroup.foreground”: “#FFA000”, “tab.activeBorder”: “#FFA000”, “notificationLink.foreground”: “#FFA000”, “editorWidget.resizeBorder”: “#FFA000”, “editorWidget.border”: “#FFA000”, “settings.modifiedItemIndicator”: “#FFA000”, “settings.headerForeground”: “#FFA000”, “panelTitle.activeBorder”: “#FFA000”, “breadcrumb.activeSelectionForeground”: “#FFA000”, “menu.selectionForeground”: “#FFA000”, “menubar.selectionForeground”: “#FFA000” }, “editor.fontSize”: 14, “editor.lineHeight”: 24, // These are for subliminal, check them out. “editor.hideCursorInOverviewRuler”: true, “editor.lineNumbers”: “on”, “editor.overviewRulerBorder”: false, “editor.renderIndentGuides”: false, “editor.renderLineHighlight”: “none”, “editor.quickSuggestions”: true, // end subliminal changes “editor.fontFamily”: “‘Fira Code’, ‘Operator Mono’, ‘iA Writer Duospace’, ‘Source Code Pro’, Menlo, Monaco, monospace”, “vsicons.projectDetection.autoReload”: true, “editor.formatOnPaste”: false, “editor.formatOnSave”: true, “editor.fontLigatures”: true, “prettier.tabWidth”: 4, “editor.wordWrap”: “on”, “editor.detectIndentation”: true, “workbench.iconTheme”: “eq-material-theme-icons-palenight”, “editor.minimap.enabled”: false, “editor.minimap.renderCharacters”: false, “prettier.parser”: “flow”, “workbench.editor.enablePreview”: false, “emmet.includeLanguages”: { “javascript”: “javascriptreact”, “jsx-sublime-babel-tags”: “javascriptreact” }, “emmet.triggerExpansionOnTab”: true, “emmet.showExpandedAbbreviation”: “never”, “workbench.statusBar.visible”: true, “workbench.activityBar.visible”: true, “workbench.editor.showIcons”: false, “editor.multiCursorModifier”: “ctrlCmd”, “explorer.confirmDelete”: false, “window.zoomLevel”: 0, “javascript.updateImportsOnFileMove.enabled”: “always”, “materialTheme.accent”: “Yellow”, “editor.cursorBlinking”: “smooth”, “editor.fontWeight”: “500”}如果你想知道更多的VS Code使用技巧,可以查看VSCode Can Do That。推荐阅读30个极大提高开发效率的VS Code插件[我为什么推荐Prettier来统一代码风格关于FundebugFundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了6亿+错误事件,得到了Google、360、金山软件等众多知名用户的认可。欢迎免费试用! ...

February 25, 2019 · 1 min · jiezi

函数运行环境系统动态链接库版本太低?函数计算 fun 神助力分忧解难

背景最近在处理线上工单的时候,遇到一个用户使用 nodejs runtime 时因为函数计算运行环境的 gcc 版本过低导致无法运行的问题,觉得非常有意思,所以深入的帮用户寻找了解决方案。觉得这个场景应该具有一定的通用性,所以在这篇文章里面重点的介绍一下如何使用函数计算的周边工具 fun 解决因为 runtime 中系统版本导致的各种兼容性问题。场景介绍用户问题简要描述一下用户当时遇到的问题:用户使用函数计算的 nodejs8 runtime,在本地自己的开发环境使用 npm install couchbase 安装了 couchbase 这个第三方库。couchbase 封装了 C 库,依赖系统底层动态链接库 libstdc++.so.6。因为用户自己的开发环境的操作系统内核比较新,所以本地安装、编译和调试都比较顺利。所以,最后按照函数计算的打包方式成功创建了 Function,但是执行 InvokeFunction 时,遇到了这样的错误:“errorMessage”: “/usr/lib/x86_64-linux-gnu/libstdc++.so.6: version CXXABI_1.3.9' not found (required by /code/node_modules/couchbase/build/Release/couchbase_impl.node)", "errorType": "Error", "stackTrace": [ "Error: /usr/lib/x86_64-linux-gnu/libstdc++.so.6: version CXXABI_1.3.9’ not found (required by /code/node_modules/couchbase/build/Release/couchbase_impl.node)”,…错误发生的原因如堆栈描述,即没有 CXXABI_1.3.9 这个版本,可以看到函数计算 nodejs 环境中的支持情况:root@1fe79eb58dbd:/code# strings /usr/lib/x86_64-linux-gnu/libstdc++.so.6 |grep CXXABI_ CXXABI_1.3CXXABI_1.3.1CXXABI_1.3.2CXXABI_1.3.3CXXABI_1.3.4CXXABI_1.3.5CXXABI_1.3.6CXXABI_1.3.7CXXABI_1.3.8CXXABI_TM_1升级底层系统版本的代价比较大,需要长时间的稳定性、兼容性测试和观察,所以,为了支持这类使用场景,我们希望能够有比较简单的方式绕行。场景复现和问题解决前提:先按照 fun 的安装步骤安装 fun工具,并进行 fun config 配置。在本地很快搭建了一个项目目录:- test_code/ - index.js - template.yml其中 index.js 和 template.yml 的 内容分别为# index.jsconst couchbase = require(‘couchbase’).Mock;module.exports.handler = function(event, context, callback) { var cluster = new couchbase.Cluster(); var bucket = cluster.openBucket(); bucket.upsert(’testdoc’, {name:‘Frank’}, function(err, result) { if (err) throw err; bucket.get(’testdoc’, function(err, result) { if (err) throw err; console.log(result.value); // {name: Frank} }); }); callback(null, { hello: ‘world’ })}# template.yml ROSTemplateFormatVersion: ‘2015-09-01’Transform: ‘Aliyun::Serverless-2018-04-03’Resources: fc: # service name Type: ‘Aliyun::Serverless::Service’ Properties: Description: ‘fc test’ helloworld: # function name Type: ‘Aliyun::Serverless::Function’ Properties: Handler: index.handler Runtime: nodejs8 CodeUri: ‘./’ Timeout: 60为了能够在本地模拟函数计算的真实环境进行依赖包安装和调试,这里生成一个 fun.yml 文件用于 fun install 安装使用,内容如下:runtime: nodejs8tasks: - shell: |- if [ ! -f /code/.fun/root/usr/lib/x86_64-linux-gnu/libstdc++.so.6 ]; then mkdir -p /code/.fun/tmp/archives/ curl http://mirrors.ustc.edu.cn/debian/pool/main/g/gcc-6/libstdc++6_6.3.0-18+deb9u1_amd64.deb -o /code/.fun/tmp/archives/libstdc++6_6.3.0-18+deb9u1_amd64.deb bash -c ‘for f in $(ls /code/.fun/tmp/archives/*.deb); do dpkg -x $f /code/.fun/root; done;’ rm -rf /code/.fun/tmp/archives fi - name: install couchbase shell: npm install couchbasefun.yml中参数说明:前面的分析已经了解到函数计算 nodejs8 runtime 的 libstdc++.so.6 的版本偏低,所以,我们找到一个更新的版本来支持,见新版本的 libstdc++.so.6 的 CXXABI_ 参数:$strings .fun/root/usr/lib/x86_64-linux-gnu/libstdc++.so.6|grep CXXABI_CXXABI_1.3CXXABI_1.3.1CXXABI_1.3.2CXXABI_1.3.3CXXABI_1.3.4CXXABI_1.3.5CXXABI_1.3.6CXXABI_1.3.7CXXABI_1.3.8CXXABI_1.3.9CXXABI_1.3.10CXXABI_TM_1CXXABI_FLOAT128执行 fun install 命令安装各种第三方依赖,显示如下:本地执行情况执行 fun local invoke helloworld,可以看到执行成功的效果:$fun local invoke helloworld begin pullling image aliyunfc/runtime-nodejs8:1.4.0………………………………………………………pull image finishedpull image finishedFC Invoke Start RequestId: 78e20963-b314-4d69-843a-35a3f465796cload code for handler:index.handlerFC Invoke End RequestId: 78e20963-b314-4d69-843a-35a3f465796c{“hello”:“world”}2019-02-19T08:16:45.073Z 78e20963-b314-4d69-843a-35a3f465796c [verbose] { name: ‘Frank’ }发布上线使用 fun deploy 发布上线,然后到控制台执行一下线上实际的运行效果:总结fun install 功能能够将代码和依赖文件分离开,独立安装系统依赖文件,而且 fun local 和 fun deply 都能够自动帮你设置第三方库的依赖引用路径,让您无需关心环境变量问题。本文的解法只是提供了一个对于系统版本偏低无法满足用户一些高级库使用需求时的简单绕行方案,仅供参考,对于一些复杂的环境依赖问题,可能还需要具体情况具体分析。更多参考:函数计算 nodejs runtimefun local99dXnVk4I)fun install本文作者:清宵阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

February 21, 2019 · 2 min · jiezi

如何在Kubernetes集群动态使用 NAS 持久卷

介绍:本文介绍的动态生成NAS存储卷的方案:在一个已有文件系统上,自动生成一个目录,这个目录定义为目标存储卷;镜像地址:registry.cn-hangzhou.aliyuncs.com/acs/alicloud-nas-controller:v1.11.5.4-433631d-aliyun默认生成资源:生成的PV名字为:pvc-${pvc-uid}生成目录的名字:namespace-pvcname-pvname可以再pvc的annotations中如下声明,自定义名字:生成的pv、目录名字为下面定义的名字。 annotations: pv-name-created: replace-user-id2. 部署NAS Controller创建alicloud-nas-controller,实现动态provider nas pv;创建alicloud-nas storageclass,为nas pv provision 提供模板;apiVersion: storage.k8s.io/v1kind: StorageClassmetadata: name: alicloud-nasprovisioner: alicloud/nasreclaimPolicy: Deleteparameters: drivertype: flexvolume nfsversion: “4.0” options: “”—kind: DeploymentapiVersion: extensions/v1beta1metadata: name: alicloud-nas-controller namespace: kube-systemspec: replicas: 1 strategy: type: Recreate template: metadata: labels: app: alicloud-nas-controller spec: tolerations: - effect: NoSchedule operator: Exists key: node-role.kubernetes.io/master - effect: NoSchedule operator: Exists key: node.cloudprovider.kubernetes.io/uninitialized serviceAccount: admin containers: - name: alicloud-nas-controller image: registry.cn-hangzhou.aliyuncs.com/acs/alicloud-nas-controller:v1.11.5.4-433631d-aliyun imagePullPolicy: Always volumeMounts: - mountPath: /persistentvolumes name: nfs-client-root env: - name: NFS_SERVER value: 154154b095-.cn-beijing.nas.aliyuncs.com - name: NFS_PATH value: / volumes: - name: nfs-client-root flexVolume: driver: alicloud/nas options: path: / server: 154154b095-.cn-beijing.nas.aliyuncs.com vers: “4.0"StorageClass使用说明:drivertype: 用来表示生成pv存储类型,可选nfs, flexvolume. nfs: 默认选项,表示使用k8s原生NFS驱动挂载; flexvolume: 表示使用阿里云提供的Flexvolume NAS驱动挂载;nfsversion: 挂载nfs使用的版本,支持3,4.0.默认为4.0; drivertype为flexvolume的时候在这里配置; 为nfs的时候通过mountOptions 配置;options:为挂载nfs的可选项配置; drivertype为flexvolume的时候在这里配置; 为nfs的时候通过mountOptions 配置;StorageClass举例:## 使用kubernetes提供的NFS驱动,并配置mountOptions,reclaimPolicy为Delete;apiVersion: storage.k8s.io/v1kind: StorageClassmetadata: name: alicloud-nas-nfsmountOptions:- vers=4.0- noresvportprovisioner: alicloud/nasreclaimPolicy: Delete## 使用阿里云提供的Flexvolume NAS驱动,配置nfs版本、options;apiVersion: storage.k8s.io/v1kind: StorageClassmetadata: name: alicloud-nas-flexprovisioner: alicloud/nasreclaimPolicy: Deleteparameters: drivertype: flexvolume nfsversion: “3” options: “noresvport"3. 创建应用-Deployment:kind: PersistentVolumeClaimapiVersion: v1metadata: name: replace-user-id annotations: pv-name-created: replace-user-idspec: storageClassName: alicloud-nas accessModes: - ReadWriteMany resources: requests: storage: 5Gi—apiVersion: extensions/v1beta1kind: Deploymentmetadata: name: “deploy-nas"spec: replicas: 1 strategy: type: Recreate template: metadata: labels: app: deploy-nas spec: containers: - name: “nginx” image: “nginx” volumeMounts: - name: pvc-nas mountPath: “/data” volumes: - name: pvc-nas persistentVolumeClaim: claimName: replace-user-id执行:# userID=“hello-123”# cat deploy.yaml | sed “s/replace-user-id/"$userID"/g” | kubectl create -f -# kubectl get pod | grep deploy-nasdeploy-nas-85696b6bfc-t5dmh 1/1 Running 0 28m# kubectl get pvc | grep hellhello-123 Bound hello-123 5Gi RWX alicloud-nas-flex 28m# kubectl get pv | grep hellhello-123 5Gi RWX Delete Bound default/hello-123 alicloud-nas-flex 28m# Nas目录下查看生成目录:# ls -l | grep hellodrwxrwxrwx 2 root root 4096 2月 19 09:58 hello-1234. 创建应用-StatefulSet:使用volumeTemplateClaim不支持使用pv-name-created配置pv名字;apiVersion: v1kind: Servicemetadata: name: nginx labels: app: nginxspec: ports: - port: 80 name: web clusterIP: None selector: app: nginx—apiVersion: apps/v1beta1kind: StatefulSetmetadata: name: webspec: replicas: 2 serviceName: “nginx” template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:alpine volumeMounts: - mountPath: “/data” name: pvc-sts volumeClaimTemplates: - metadata: name: pvc-sts spec: accessModes: - ReadWriteOnce storageClassName: alicloud-nas-flex resources: requests: storage: 2Gi 创建后查看:# kubectl get pod | grep webweb-0 1/1 Running 0 7sweb-1 1/1 Running 0 4s# kubectl get pvc | grep webpvc-sts-web-0 Bound pvc-65ab251a-33ec-11e9-a151-00163e066784 2Gi RWO alicloud-nas-flex 13mpvc-sts-web-1 Bound pvc-8437c50e-33ed-11e9-a151-00163e066784 2Gi RWO alicloud-nas-flex 5m# kubectl get pv | grep webpvc-65ab251a-33ec-11e9-a151-00163e066784 2Gi RWO Delete Bound default/pvc-sts-web-0 alicloud-nas-flex 13mpvc-8437c50e-33ed-11e9-a151-00163e066784 2Gi RWO Delete Bound default/pvc-sts-web-1 alicloud-nas-flex 5m# Nas目录下查看生成目录:# ls -l | grep stsdrwxrwxrwx 2 root root 4096 2月 19 10:16 default-pvc-sts-web-0-pvc-65ab251a-33ec-11e9-a151-00163e066784drwxrwxrwx 2 root root 4096 2月 19 10:24 default-pvc-sts-web-1-pvc-8437c50e-33ed-11e9-a151-00163e0667845. 创建应用-Pod:kind: PersistentVolumeClaimapiVersion: v1metadata: name: replace-user-id annotations: pv-name-created: replace-user-idspec: storageClassName: alicloud-nas-flex accessModes: - ReadWriteMany resources: requests: storage: 5Gi—apiVersion: v1kind: Podmetadata: name: “nas-pod"spec: containers: - name: “nginx” image: “nginx” volumeMounts: - name: pvc-nas mountPath: “/data” volumes: - name: pvc-nas persistentVolumeClaim: claimName: replace-user-id # userID=“pod-123”# cat pod.yaml | sed “s/replace-user-id/"$userID"/g” | kubectl create -f -# kubectl get pod | grep podnas-pod 1/1 Running 0 32s# kubectl get pvc | grep podpod-123 Bound pod-123 5Gi RWX alicloud-nas-flex 44s# kubectl get pv | grep podpod-123 5Gi RWX Delete Bound default/pod-123 alicloud-nas-flex 48s# ls -l | grep poddrwxrwxrwx 2 root root 4096 2月 19 10:54 pod-123本文作者:kanjunbao阅读原文本文为云栖社区原创内容,未经允许不得转载。

February 20, 2019 · 3 min · jiezi

优酷IPv6改造纪实:视频行业首家拥抱下一代网络技术

阿里妹导读:2018年双11前,优酷开启了IPV6的大门。9月份PC端业务开启灰度,迎来首位IPV6 VIP用户后,优酷移动客户端也马不停蹄地加入灰度大军。从0到1,花了几个月;从10到1000,花了几天;从1000到50W,只要几小时。IPV6灰度的马车一旦起跑,将再也不需要停止。IPV6在优酷,技术驱动产品的验证2018 世界杯期间,我们验证了IPV6的改造方案和技术可行性,双11期间优酷PC端和App在国内教育网及一线重点城市都有一定比例的IPV6用户,享受着高清直播点播服务,体验着一条刚刚建完的高速大道却没有几辆车的快感。专属的用户身份标识,专属的客户端网络检测能力,专属的会员卡,专属的红包,这一切可能不知不觉中就属于你了。随着双11后阿里集团几大应用相继开启灰度,我们迎来了中国首次IPv6大规模应用上线,优酷不仅能跑而且跑得快。这象征着优酷大家庭通过双11、世界杯等的洗礼,已经拥有了一支能战敢战,战则必胜的技术团队。IPV6不是一个人的功劳,是所有技术人努力的结晶。今天,我们采用专访问答的形式,带你走进优酷IPV6从立项到实践的全过程。优酷IPV6改造项目,由优酷应用架构吴灵晓(盖优)负责。应用架构部主要负责整体架构设计实施优化工作,探索从DEVOPS向AIOPS转变,智能运维、深度学习、大数据分析、智能机器人等新技术也有大量的成熟运用。立项Q:IPV4和IPV6有哪些区别呢?安全:IPv6把IPSec作为必备协议,保证了网络层端到端通信的完整性和机密性。业务无限扩展:不受地址短缺的影响。个性化服务:流标签的使用让我们可以为数据包所属类型提供个性化的网络服务,并有效保障相关业务的服务质量。减少开销:报头格式大大简化,从而有效减少路由器或交换机对报头的处理开销。万物互联:大容量的地址空间能够真正地实现无状态地址自动配置。Q:为什么想到要做IPV6改造?第一,IPV4环境恶化:第二,政策驱动:2017-11-26 中共中央办公厅 国务院办公厅印发《推进互联网协议第六版(IPv6)规模部署行动计划》。2018-05-03 工业和信息化部关于贯彻落实《推进互联网协议第六版(IPv6)规模部署行动计划》的通知。第三,技术驱动产品业务:IPv6在客户端-服务端-阿里云CDN-优酷直播点播业务的全线贯通应用,优酷完成了新网络技术到产品应用的实现,改写技术服务产品,服务倒逼技术升级的局面,使得IPV6网络技术能够支撑优酷今后几年甚至十几年的业务需求,5G、P2P、人工智能、AI、物联网等,在网络技术上已经没有障碍。第四,天时地利俱备,差人和:政策支持,集团推动,技术展现为一体,这么好的机会,不能错过。拥有一群打过双11,打过春晚,打过世界杯的战友们,没有什么事是做不好的。Q:决定做之后,怎么列的计划呢?目标是什么?这还真是前无古人,后有来者。谁都没有经验,没有相似可参照的案例,涉及团队众多。各种调查与讨论,以及集团相关的计划安排,整个IPV6项目将分成三步走,包括外网阶段,用户端与接入层支持 v6、内网阶段,服务端内部v4/v6双栈、以及内外网IPv6-only 。外网改造:实现应用快速对外服务,以web/App请求服务为核心,满足IPv6生态发展的需求,并且以外网拉动应用的不同需求;内网改造:应用逐渐扩大到爬虫、邮箱、DB、存储等V6直接交互,需要内网服务器部分采用IPv6,需要整网双栈交付;IPv6 Only:当超过50%应用逐步迁移到IPv6后,新应用默认采用v6开发,遗留一些老旧应用、用户继续采用IPv4服务,内网采用4over6进行封装。相对双栈而言,IPv6 only成本更低,查表转发速度更快,只需维护一套协议栈。阿里网络演进从外到内,从用户到应用逐层迭代,尽量做到成本最低,效率最高。核心目标18自然年底,实现全量灰度。还是那句话,要么不做,要做就做最好。评估后全量灰度目标虽然风险偏大,但努力一把还是可控的。过程Q:对研发同学来说,需要关心哪些?怎么确认哪些要改哪些不要改?对研发的同学来说,只需要关心客户端APP/PC端网页及服务端的改造。客户端基本上涉及到基础网络包NetworkSDK等集团二方包的升级。使用httpdns解析的,需要改造实现客户端网络的判断和接收httpdns服务端下发的AAAA记录;使用localdns解析的,需要改造实现DNS服务请求参数中添加AAAA记录解析的标识。使用三方库的,要升级至最新版并确认支持IPV6,不支持的需要考虑更换三方库。IP 字段是否正确extra : {‘firstIp’: ‘xxxxx’} 是否正确携带探测埋点弱网、DNS耗时的情况下,探测能否正常网络切换特别频繁的场景PC端/服务端要关心的就多一点了,只要你的业务处理中有以下任意一点的,都是要做业务改造的,也就是写Bug。1.IP地址库使用:是否有用到地址库,对用户IP进行地域来源等判断。有的话需要升级到IPV6地址库,并更新调用方法。2.IP地址格式判断:是否对用户IP进行验证,有的话需要加入IPV6地址格式的正则表达式判断。3.IP地址保存:是否对IP有存库等保存操作,需要修改相应字段的长度,IPV6长于IPV4,MySQL 建议字段类型 VARBINARY(16)。4.依赖链路上的修改:是否会将IP作为接口参数传递给下游依赖业务。有的话,下游依赖业务也需要改造。5.客户端IP地址的取得方式:是否从客户端请求的头部获取。是的话,那么在双栈环境中,同一请求,你只能获取到V4和V6地址中的一个,不可能两个都获取。如果是通过请求正文中的某个字段,把客户端地址传上来的,那么,你需要考虑是否需要获取客户端的v4v6的所有地址。是的话,那么就需要扩展请求字段,将v4,v6分成两个字段提交,同时服务端也需要做接收改造处理。6.日志,数据的采集等数据产品的改造:是否用了第三方的采集工具,如果采集工具不支持IPV6的话,那么采集上来的数据会和服务端的请求日志无法对齐,产生GAP。类似还包括广告投放与监测等分别属于多方的业务场景。从业务上来看,需要区分IPv4用户请求和IPv6用户请求,并分开进行数据分析。所以数据产品数据存储等都需要能够支持用户IPv6数据的采集。7.安全产品:内容安全:文本安全过滤,七层流量清洗等等,安全产品改造,业务升级二方包/客户端。8.监控:以用户IP作为判断条件/统计条件的监控配置,需要改造。9.大数据统计:以用户IP作为判断条件/统计条件的内容,需要业务改造。10.依赖服务:原有阿里云产品需要更新为支持IPV6的产品,VPC,ECS,OSS,CDN等都是更换范围。Q:改造中主要遇到哪些问题呢?1.没有IPV6环境:办公网不具备IPv6接入环境,阻塞业务开发;内网尚未改造,无法打通日常(测试)环境。一开始,基础环境还没有具备的时候,我们使用IPV6 over ipv4链路VPN的方式连入测试环境 ,需要PC/客户端加证书改hosts,移动端无法改hosts的,需要root,越狱,各种凌乱,但至少业务测试可以开始启动。然后,我们加强了基础网络和IT合作,在多个园区部署多个IPV6的接入环境,打通IPV6出口,打通办公网和机房的IPV6链路,慢慢实现外网IPV6,日常环境通,预发通,正式通,慢慢使业务能够测试逐步提升到IPV4相同的测试体验,通过域名劫持等手段,跳过了Hosts配置的尴尬,达到标准的测试效率。2.OS网络模块问题:需要让容器支持从请求头部获取IPV6地址,那么就需要把用户IP一级一级透传过来,就需要在各级的服务器上升级网络模块,扩展报文头部。例如toa模块,toa模块是为了让后端的realserver能够看到真实的clientip而不是lvs的dip。同时,tengine/nginx等应用需要升级到支持IPV6的版本(支持新toa模块等),由于历史原因存在各种老版本无法升级,导致升级受阻。我们通过推动应用接入统一接入改造,避免自行升级网络模块带来的风险。通过老版本应用的升级,去nginx的方式,统一升级安装tengine-proxy(安装在ecs测试机器或宿主机上都可以),为了能减少业务改造工作量,在接入层架构我们做了大量的改造。3.地址库特殊需求:优酷有自己的地域编码,优酷广告业务还有广协提供的地域编码,还有业务使用集团的地址库,总之地址库各种不统一。首先,统一地址库,要求所有业务必须统一使用集团地址库。并且协调集团地址库生产方,满足优酷使用场景需求,使统一过程中业务改造工作量减少。再次,对于广告等必须要使用行业统一地址库的场景,我们也制定了多套方案去解决。兜底方案: 将广协地址库中的地区编码,加入到集团地址库中,使集团库具备行业库的能力,在行业库没有完全产出之前,广告业务可以临时使用集团地址库进行改造和测试,保障业务不受损。后续方案: 主动出击,联系广协等行业协会,加快产出IPV6地址库,并且主动无偿提供集团地址库数据,体验了阿里的企业责任,更加快了整个行业的改造进度。最终行业协会从立项到产出地址库的时间,只用了不到1个月,而集团收集这些数据花费了一年半的时间。4.MTU问题:IPV4时代,中间网络三层设备会进行分片,所以一般设置为1500的最大值,以降低网络开销。但IPV6协议为了减轻中间网络层设备繁杂度及成本,中间设备不再分片,由两端的协商指定。导致默认mtu1500的情况下,中间设备出现大量丢包,原因是NAT转换,TCP Option等额外叠加,实际超过1500。短期解决办法是,开启SYN Proxy,通过MSS与端进行协商。调整MTU为最小值1280。发现中间层MTU小于1280时,进行网络报障等办法 。5.客户端是否IPV6,如何验证问题:这是一个很现实的问题,我的网络已经是IPV6了,业务也能正常运行,但怎么确认网络是运行在IPV6上,没有被降级呢?主要有以下两个手段:1)抓取客户端日志:这也是最笨最准确的手段,具体抓日志的方法有很多,就不再重复介绍了。2)业务改造,加入网络检测能力,将优酷客户端当做网络测试的工具。6.协议回落问题7.安全问题:有运营商的出口能力,黑洞能力进展缓慢或者暂不具备。有安全基础产品的存在绑定域名后就能直接访问任意服务,灰度放大。8.CDN灰度问题:CDN域名由阿里云进行调度控制,无法和业务同一灰度范围。增加IPV6专属CDN域名,通过业务侧增加业务逻辑,分别下发不同的域名来实现同一灰度节奏能力。结果Q:灰度是如何操作执行的?通过httpdns方式 ,提供两种灰度能力:基于用户设备的白名单基于地域+运营商+百分比+用户设备白名单基于app版本的全量百分比通过localdns(ADNS),提供一种能力:ADNS新开发并上线了一个能力,支持一个域名下配置多CNAME解析功能,并且每条解释都可以配置权重,通过修改IDNS的cname权重配置来达到比例控制。同时加上自有的线路和运营商的选择能力,满足地域级的灰度需求。我们也开发了自动化的灰度系统,根据起始参数和灰度目标,自动安排灰度比例和时间节奏,实现完全自动化的灰度引流。监控预警+自动回滚能力,边喝咖啡边看灰度量,就是这么实现的。Q:如何确认业务是否正常呢?业务层:业务配置的IPV6监控平台,IPV4与IPV6监控曲线对比。接入层:IPV6流量大盘,分域名,分接口展示成功量,成功率及RT。数据平台:业务指标的大数据分析及报表展示。基础网络:省份运营商的链路成功率,IPV6用户占比,链路质量链路延迟,IPV6降级IPV4比例等数据。有了这些,还怕业务有问题吗?我们知道,视频的生命周期,是从采集到制作到生产,最后到视频的呈现,这里有很多环节,每个环节上都有非常专业的团队来保障。在制作环节会做调音、调色,在生产环节会做编码压缩,在呈现环节的会做解码和后处理,每个环节独立来看都做得不错。但如果我们站在链条的两端来看,制作侧和呈现侧看到的视频效果存在比较大的差异。但是今天,我们的场景发生了改变,我们有形形色色的终端,有手机、Pad、PC、电视、投影,呈现场景已经不可控的,所以今天我们看到的画面,已经不是我们的导演原本希望呈现的画面了。这是今天我们想去解决的问题,当然整个链条上的联动不是靠优酷一家可以解决的,因此我们需要更多产业链上的朋友和我们一起来解决这个问题。当每个人都是导演、演员、编辑、美工的时候,缺少的是什么?空间容量,没有创作空间,没有办公空间,没有消费空间,可持续增量空间。这些不全都是IPV4的错,但确实IPV4是瓶颈,当一个人有上百个设备的时候,IPV4肯定满足不了。有了IPV6,再多设备都OK,每个设备都能互联互通,万物互联。那么,智能化的后期制作变得可能。快速将优酷内容库批量处理为适合视频互联网传播的版本,我们使用了自适应调整映射曲线的算法,根据内容明暗程度,有时提升暗区对比度,有时提升亮区对比度。任何端设备都能干。那么,内容版权将变得容易控制,不用花精力去防盗版了。区块链技术+IPV6+5G,每个设备都记录播放信息,要串改内容必须修改超过51%的设备,盗版成本无限放大。没有了防盗成本后,版权不再昂贵,都能接受。展望技术的价值在于帮助人,不是替代人。通过技术保障项目成功,项目成功也推动技术落地。这一切都在IPV6改造项目中体验。随着灰度扩大,下一期改造来临。普通用户根据不知道IPV6是什么的情况下,通过业务,通过产品去更好地展现出来,让用户能有感知。例如:视频变快变清晰了,走到哪里看到哪里了。会员时间增长了,不用花钱了。技术驱动业务,将会更美好。本文作者:期待被关注的阅读原文本文来自云栖社区合作伙伴“阿里技术”,如需转载请联系原作者。

January 30, 2019 · 1 min · jiezi

IIS 7(7.5)服务器多域名SSL证书配置方法

获取SSL证书 1.1 选择SSL证书从沃通申请证书后,将会下载一个.zip的压缩包,解压该压缩包,得到for Apache.zip、for Nginx.zip、for other server.zip三个压缩包,IIS服务器需要用到for nginx.zip里面的.crt文件以及申请证书时自主生成的私钥.key文件。1.2 合成SSL证书由于IIS服务器要求导入PFX格式的证书,所以要将上面说的两个文件合成.pfx格式的文件,具体步骤如下:合成工具下载:https://download.wotrus.com/w…下载并运行wosigncode.exe工具,点击证书,选择转换证书格式,原始格式pem,目标格式pfx,证书文件选择for nginx.zip解压出来的.crt文件,私钥选择自主生成的.key文件,设置pfx密码,点击转换,输入名称,保存下来即可,详情可见下图: 注意:私钥密码一般为空,若创建CSR时设置了私钥密码,则此处私钥密码和PFX密码请与之前设置的私钥密码保持一致。安装SSL证书2.1 导入SSL证书登录到IIS7(7.5)所在的服务器,点击左下角的开始菜单,输入MMC,运行mmc.exe,具体见图1、2;在弹出的控制台界面上,点击“文件”-“添加删除管理单元”,见图3;在新弹出的界面左侧“可用的管理单元中”,找到“证书”,点击中间的“添加”,选择“计算机账户”-“本地计算机”,具体见图4、5、6;双击控制台左侧的“证书(本地计算机)”,右键列表中的“个人”,选择“所有任务”-“导入”,具体见图7;点击“下一步”-“浏览”,选择“个人信息交换”,然后选择之前合成好的.pfx证书导入,具体见图8;选择pfx证书后,点击“打开”-“下一步”,输入之前合成pfx时设置的密码,点击“下一步”,选择“根据证书类型,自动选择存储机构”,点击“下一步”-“完成”,具体见图9;证书导入完成后,在“个人”-“证书”目录下,可见到该域名证书接下来,是最关键的一步,右键该域名证书-属性,将友好名称设置为.domain.com,比如.wosign.com,然后点击“应用”-“确定”,具体见下图:按照上述设置后,接下来就可以去IIS服务器上绑定证书啦!2.2 绑定SSL证书打开IIS 7(7.5)服务器,选择需要绑定SSL证书的站点,点击右侧的“绑定”-“添加”-类型选择“https”,端口默认使用443(可以自定义端口,访问时需要加端口号),点击SSL证书下的下拉键,选择之前设置的友好名称对应的证书,然后“主机名”就可以填写了,填写该站点对应的域名,点击确定,重启站点,其余的站点重复此操作即可。 测试SSL访问打开浏览器,输入https://yourdomain.com(证书绑定的实际域名),如浏览器地址栏显示加密小锁,则表示证书配置成功。若显示无法连接,请确保防火墙或安全组等策略有放行443端口(SSL配置端口)。备份SSL证书请将下载的.zip压缩包和自主生成的私钥.key文件备份,以防丢失,影响后续使用!沃通技术支持原创文章,转载请注明来源 www.wosign.com

January 28, 2019 · 1 min · jiezi

如何使用阿里云ARMS轻松重现用户浏览器问题

客户投诉不断,本地却无法重现?页面加载较慢是用户经常会反馈的问题,也是前端非常关注的问题之一。但定位、排查解决这类问题就通常会花费非常多的时间,主要原因如下:页面是在用户端的浏览器上加载执行,复现困难页面上线前,开发同学都会进行测试,在测试环境下页面加载一般都是正常的才会正式上线。用户在访问页面时,页面的加载是在用户端的浏览器上进行的,由于页面的加载耗时与地域、网络情况、浏览器或者运营商等有关系,想知道用户在访问页面时的具体情况,复现是非常困难的。监控信息缺少,导致无法深入排查大部分前端监控会通过PerformanceTiming对象,获取完整的页面加载耗时信息,但这类监控就缺失了页面静态资源的加载情况,无法直接复现现场,从而无法深入定位性能瓶颈。为了方便用户更快地定位性能瓶颈,阿里云ARMS前端监控推出一新功能: 会话追踪,提供页面静态资源加载的性能瀑布图,根据页面性能数据可深入定位页面资源加载情况。如何通过会话追踪帮助你快速定位问题在阿里云ARMS前端监控SDK上将sendResource配置为true,重新部署应用后,在页面onload时会上报当前页面加载的静态资源信息。从而在阿里云前端监控平台即可以对慢页面加载问题快速进行定位。SDK配置在阿里云ARMS前端监控SDK部分,默认是不上报页面加载的静态资源信息的,如果想获取页面加载的静态资源信息,只需在SDK的config部分将sendResource配置为true,重新部署后,就可以上报相关信息。具体配置如下:<script>!(function(c,b,d,a){c[a]||(c[a]={});c[a].config={pid:“atc889zkcf@8cc3f63543da641”,imgUrl:“https://arms-retcode.aliyuncs.com/r.png?",sendResource:true};with(b)with(body)with(insertBefore(createElement("script"),firstChild))setAttribute("crossorigin","",src=d)})(window,document,"https://retcode.alicdn.com/retcode/bl.js","__bl");</script>注意:静态资源加载信息的上报是在页面onload时会触发,上报信息量较大,如果对于页面性能要求很高的应用,可以不开启该配置。问题排查过程1. 发现问题进入访问速度菜单后,发现页面的性能较差,11点钟的页面完全加载时间达到35s,如下:2. 慢页面会话追踪在慢页面会话追踪模块,提供该页面在指定时间段内加载较慢的TOP20,这样可以快速发现哪些会话加载较慢,如下图所示。在该模块,你可以快速发现在11点钟有一次会话的页面加载时间在36.72s,这次访问应该是直接导致页面加载时间详情中折线图突然暴增的原因了。其中在在模块有7次会话访问的页面加载时间在7s以上,点击对应的页面,可以直接进入到会话详情页面,从而直观查看页面静态资源加载的瀑布图。通过页面资源加载的瀑布图,可以快速定位到资源加载的性能瓶颈,同时可以查看本次访问的客户端IP地址、浏览器、操作系统等UA信息,从而进一步确认是由于网络原因还是其他原因导致的,针对性进行相应的优化。3. 其他发现问题入口会话追踪也可以进入“会话追踪”菜单,可以看到该应用下的会话列表。会话列表中会根据页面完全加载时间排序,展示TOP100,帮助用户可以快速发现耗时较长的会话信息。同时支持按照页面、会话Id、浏览器、浏览器版本号进行过滤,展示相关的会话信息。点击操作后,是该会话的页面资源加载详情。访问明细如果当前会话列表中无法找到你要排查的会话信息,可以通过访问明细查找到相应的日志详细信息,在param中找到对应的sid即会话Id,然后在会话列表中查找相应的会话Id,即可以定位到想排查的会话信息。例如:在已知用户的客户端IP的情况下,想定位相应的会话信息,即可以在访问明细中,通过t=res and 117.136.32.110 进行搜索,找到对应的会话Id。根据查找到的会话Id, 就可以在会话列表中进行过滤,定位到具体的会话内容。使用入口指南进入访问速度菜单,如果发现页面性能较差,可以在"慢页面会话追踪Top20"中查看访问较慢的会话情况点击详情后,可以查看具体的页面资源加载瀑布图如果Top20不满足,可以点击"更多”,从而进入"会话列表"进入会话追踪菜单,展示的是TOP100的会话列表信息,根据页面完全加载时间从高到底排序,排查页面资源加载情况至此,慢页面会话追踪功能及使用方法介绍完成。该功能可以帮助你复现用户在访问页面时的页面资源加载情况,快速定位性能瓶颈问题。附录官网文档介绍阿里云ARMS前端监控官网本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。

January 23, 2019 · 1 min · jiezi

日志服务与SIEM(如Splunk)集成方案实战

背景信息目标本文主要介绍如何让阿里云日志服务与您的SIEM方案(如Splunk)对接, 以便确保阿里云上的所有法规、审计、与其他相关日志能够导入到您的安全运维中心(SOC)中。名词解释LOG(SLS) - 阿里云日志服务,简写SLS表示(Simple Log Service)。SIEM - 安全信息与事件管理系统(Security Information and Event Management),如Splunk, QRadar等。Splunk HEC - Splunk的Http事件接收器(Splunk Http Event Collector), 一个 HTTP(s)接口,用于接收日志。审计相关日志安全运维团队一般对阿里云相关的审计日志感兴趣,如下列出所有存在于所有目前在日志服务中可用的相关日志(但不限于):Regions化 - 时刻更新,请以最新的产品文档为准。阿里云日志服务阿里云的日志服务(log service)是针对日志类数据的一站式服务,无需开发就能快捷完成海量日志数据的采集、消费、投递以及查询分析等功能,提升运维、运营效率。日志服务主要包括 实时采集与消费、数据投递、查询与实时分析 等功能,适用于从实时监控到数据仓库的各种开发、运维、运营与安全场景:目前,以上各个阿里云产品已经与日志服务打通,提供近实时的日志自动采集存储、并提供基于日志服务的查询分析、报表报警、下游计算对接与投递的能力。集成方案建议概念项目(Project)项目(Project)是日志服务中的资源管理单元,用于资源隔离和控制。您可以通过项目来管理某一个应用的所有日志及相关的日志源。它管理着用户的所有日志库(Logstore),采集日志的机器配置等信息,同时它也是用户访问日志服务资源的入口。日志库(Logstore)日志库(Logstore)是日志服务中日志数据的收集、存储和查询单元。每个日志库隶属于一个项目,且每个项目可以创建多个日志库。分区(Shard)每个日志库分若干个分区(Shard),每个分区由MD5左闭右开区间组成,每个区间范围不会相互覆盖,并且所有的区间的范围是MD5整个取值范围。服务入口(Endpoint)日志服务入口是访问一个项目(Project)及其内部日志数据的 URL。它和 Project 所在的阿里云区域(Region)及 Project 名称相关。https://help.aliyun.com/document_detail/29008.html访问秘钥(AccessKey)阿里云访问秘钥是阿里云为用户使用 API(非控制台)来访问其云资源设计的“安全口令”。您可以用它来签名 API 请求内容以通过服务端的安全验证。https://help.aliyun.com/document_detail/29009.html假设这里假设您的SIEM(如Splunk)位于组织内部环境(on-premise)中,而不是云端。为了安全考虑,没有任何端口开放让外界环境来访问此SIEM。概览推荐使用SLS消费组构建程序来从SLS进行实时消费,然后通过Splunk API(HEC)来发送日志给Splunk。使用消费组编程协同消费库(Consumer Library)是对日志服务中日志进行消费的高级模式,提供了消费组(ConsumerGroup)的概念对消费端进行抽象和管理,和直接使用SDK进行数据读取的区别在于,用户无需关心日志服务的实现细节,只需要专注于业务逻辑,另外,消费者之间的负载均衡、failover等用户也都无需关心。Spark Streaming、Storm 以及Flink Connector都以Consumer Library作为基础实现。基本概念消费组(Consumer Group) - 一个消费组由多个消费者构成,同一个消费组下面的消费者共同消费一个logstore中的数据,消费者之间不会重复消费数据。消费者(Consumer) - 消费组的构成单元,实际承担消费任务,同一个消费组下面的消费者名称必须不同。在日志服务中,一个logstore下面会有多个shard,协同消费库的功能就是将shard分配给一个消费组下面的消费者,分配方式遵循以下原则:每个shard只会分配到一个消费者。一个消费者可以同时拥有多个shard。新的消费者加入一个消费组,这个消费组下面的shard从属关系会调整,以达到消费负载均衡的目的,但是上面的分配原则不会变,分配过程对用户透明。协同消费库的另一个功能是保存checkpoint,方便程序故障恢复时能接着从断点继续消费,从而保证数据不会被重复消费。部署建议硬件建议硬件参数:需要一台机器运行程序,安装一个Linux(如Ubuntu x64),推荐硬件参数如下:2.0+ GHZ X 8核16GB 内存,推荐32GB1 Gbps网卡至少2GB可用磁盘空间,建议10GB以上网络参数:从组织内的环境到阿里云的带宽应该大于数据在阿里云端产生的速度,否则日志无法实时消费。假设数据产生一般速度均匀,峰值在2倍左右,每天100TB原始日志。5倍压缩的场景下,推荐带宽应该在4MB/s(32Mbps)左右。使用(Python)这里我们描述用Python使用消费组进行编程。对于Java语言用法,可以参考这篇文章.注意:本篇文章的代码可能会更新,最新版本在这里可以找到:Github样例.安装环境强烈推荐PyPy3来运行本程序,而不是使用标准CPython解释器。日志服务的Python SDK可以如下安装:pypy3 -m pip install aliyun-log-python-sdk -U更多SLS Python SDK的使用手册,可以参考这里程序配置如下展示如何配置程序:配置程序日志文件,以便后续测试或者诊断可能的问题。基本的日志服务连接与消费组的配置选项。消费组的一些高级选项(性能调参,不推荐修改)。SIEM(Splunk)的相关参数与选项。请仔细阅读代码中相关注释并根据需要调整选项:#encoding: utf8import osimport loggingfrom logging.handlers import RotatingFileHandlerroot = logging.getLogger()handler = RotatingFileHandler("{0}_{1}.log".format(os.path.basename(file), current_process().pid), maxBytes=10010241024, backupCount=5)handler.setFormatter(logging.Formatter(fmt=’[%(asctime)s] - [%(threadName)s] - {%(module)s:%(funcName)s:%(lineno)d} %(levelname)s - %(message)s’, datefmt=’%Y-%m-%d %H:%M:%S’))root.setLevel(logging.INFO)root.addHandler(handler)root.addHandler(logging.StreamHandler())logger = logging.getLogger(name)def get_option(): ########################## # 基本选项 ########################## # 从环境变量中加载SLS参数与选项 endpoint = os.environ.get(‘SLS_ENDPOINT’, ‘’) accessKeyId = os.environ.get(‘SLS_AK_ID’, ‘’) accessKey = os.environ.get(‘SLS_AK_KEY’, ‘’) project = os.environ.get(‘SLS_PROJECT’, ‘’) logstore = os.environ.get(‘SLS_LOGSTORE’, ‘’) consumer_group = os.environ.get(‘SLS_CG’, ‘’) # 消费的起点。这个参数在第一次跑程序的时候有效,后续再次运行将从上一次消费的保存点继续。 # 可以使”begin“,”end“,或者特定的ISO时间格式。 cursor_start_time = “2018-12-26 0:0:0” ########################## # 一些高级选项 ########################## # 一般不要修改消费者名,尤其是需要并发跑时 consumer_name = “{0}-{1}".format(consumer_group, current_process().pid) # 心跳时长,当服务器在2倍时间内没有收到特定Shard的心跳报告时,服务器会认为对应消费者离线并重新调配任务。 # 所以当网络不是特别好的时候,不要调整的特别小。 heartbeat_interval = 20 # 消费数据的最大间隔,如果数据生成的速度很快,并不需要调整这个参数。 data_fetch_interval = 1 # 构建一个消费组和消费者 option = LogHubConfig(endpoint, accessKeyId, accessKey, project, logstore, consumer_group, consumer_name, cursor_position=CursorPosition.SPECIAL_TIMER_CURSOR, cursor_start_time=cursor_start_time, heartbeat_interval=heartbeat_interval, data_fetch_interval=data_fetch_interval) # Splunk选项 settings = { “host”: “10.1.2.3”, “port”: 80, “token”: “a023nsdu123123123”, ‘https’: False, # 可选, bool ’timeout’: 120, # 可选, int ‘ssl_verify’: True, # 可选, bool “sourcetype”: “”, # 可选, sourcetype “index”: “”, # 可选, index “source”: “”, # 可选, source } return option, settings数据消费与转发如下代码展示如何从SLS拿到数据后转发给Splunk。from aliyun.log.consumer import from aliyun.log.pulllog_response import PullLogResponsefrom multiprocessing import current_processimport timeimport jsonimport socketimport requestsclass SyncData(ConsumerProcessorBase): "”" 这个消费者从SLS消费数据并发送给Splunk """ def init(self, splunk_setting): “““初始化并验证Splunk连通性””” super(SyncData, self).init() assert splunk_setting, ValueError(“You need to configure settings of remote target”) assert isinstance(splunk_setting, dict), ValueError(“The settings should be dict to include necessary address and confidentials.”) self.option = splunk_setting self.timeout = self.option.get(“timeout”, 120) # 测试Splunk连通性 s = socket.socket() s.settimeout(self.timeout) s.connect((self.option[“host”], self.option[‘port’])) self.r = requests.session() self.r.max_redirects = 1 self.r.verify = self.option.get(“ssl_verify”, True) self.r.headers[‘Authorization’] = “Splunk {}".format(self.option[’token’]) self.url = “{0}://{1}:{2}/services/collector/event”.format(“http” if not self.option.get(‘https’) else “https”, self.option[‘host’], self.option[‘port’]) self.default_fields = {} if self.option.get(“sourcetype”): self.default_fields[‘sourcetype’] = self.option.get(“sourcetype”) if self.option.get(“source”): self.default_fields[‘source’] = self.option.get(“source”) if self.option.get(“index”): self.default_fields[‘index’] = self.option.get(“index”) def process(self, log_groups, check_point_tracker): logs = PullLogResponse.loggroups_to_flattern_list(log_groups, time_as_str=True, decode_bytes=True) logger.info(“Get data from shard {0}, log count: {1}".format(self.shard_id, len(logs))) for log in logs: # 发送数据到Splunk # 如下代码只是一个样例(注意:所有字符串都是unicode) # Python2: {u”time”: u"12312312", u"topic": u"topic", u"field1": u"value1", u"field2": u"value2"} # Python3: {"time": “12312312”, “topic”: “topic”, “field1”: “value1”, “field2”: “value2”} event = {} event.update(self.default_fields) if log.get(u"topic") == ‘audit_log’: # suppose we only care about audit log event[’time’] = log[u’time’] event[‘fields’] = {} del log[’time’] event[‘fields’].update(log) data = json.dumps(event, sort_keys=True) try: req = self.r.post(self.url, data=data, timeout=self.timeout) req.raise_for_status() except Exception as err: logger.debug(“Failed to connect to remote Splunk server ({0}). Exception: {1}”, self.url, err) # TODO: 根据需要,添加一些重试或者报告的逻辑 logger.info(“Complete send data to remote”) self.save_checkpoint(check_point_tracker)主逻辑如下代码展示主程序控制逻辑:def main(): option, settings = get_monitor_option() logger.info("** start to consume data…") worker = ConsumerWorker(SyncData, option, args=(settings,) ) worker.start(join=True)if name == ‘main’: main()启动假设程序命名为"sync_data.py",可以如下启动:export SLS_ENDPOINT=<Endpoint of your region>export SLS_AK_ID=<YOUR AK ID>export SLS_AK_KEY=<YOUR AK KEY>export SLS_PROJECT=<SLS Project Name>export SLS_LOGSTORE=<SLS Logstore Name>export SLS_CG=<消费组名,可以简单命名为"syc_data">pypy3 sync_data.py限制与约束每一个日志库(logstore)最多可以配置10个消费组,如果遇到错误ConsumerGroupQuotaExceed则表示遇到限制,建议在控制台端删除一些不用的消费组。监测在控制台查看消费组状态通过云监控查看消费组延迟,并配置报警性能考虑启动多个消费者基于消费组的程序可以直接启动多次以便达到并发作用:nohup pypy3 sync_data.py &nohup pypy3 sync_data.py &nohup pypy3 sync_data.py &…注意: 所有消费者使用了同一个消费组的名字和不同的消费者名字(因为消费者名以进程ID为后缀)。因为一个分区(Shard)只能被一个消费者消费,假设一个日志库有10个分区,那么最多有10个消费者同时消费。Https如果服务入口(endpoint)配置为https://前缀,如https://cn-beijing.log.aliyuncs.com,程序与SLS的连接将自动使用HTTPS加密。服务器证书*.aliyuncs.com是GlobalSign签发,默认大多数Linux/Windows的机器会自动信任此证书。如果某些特殊情况,机器不信任此证书,可以参考这里下载并安装此证书。性能吞吐基于测试,在没有带宽限制、接收端速率限制(如Splunk端)的情况下,以推进硬件用pypy3运行上述样例,单个消费者占用大约10%的单核CPU下可以消费达到5 MB/s原始日志的速率。因此,理论上可以达到50 MB/s原始日志每个CPU核,也就是每个CPU核每天可以消费4TB原始日志。注意: 这个数据依赖带宽、硬件参数和SIEM接收端(如Splunk)是否能够较快接收数据。高可用性消费组会将检测点(check-point)保存在服务器端,当一个消费者停止,另外一个消费者将自动接管并从断点继续消费。可以在不同机器上启动消费者,这样当一台机器停止或者损坏的清下,其他机器上的消费者可以自动接管并从断点进行消费。理论上,为了备用,也可以启动大于shard数量的消费者。更多参考日志服务Python消费组实战(一):日志服务与SIEM(如Splunk)集成实战日志服务Python消费组实战(二):实时日志分发日志服务Python消费组实战(三):实时跨域监测多日志库数据本文Github样例本文作者:成喆阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

January 22, 2019 · 3 min · jiezi

老代码多=过度耦合=if else?阿里巴巴工程师这样捋直老代码

简介在业务开发的过程中,往往存在平台代码和业务代码耦合严重难以分离、业务和业务之间代码交织缺少拆解的现象。平台和业务代码交织导致不易修改,不同业务的代码交织增加了不同负责团队之间的协同成本。因此不论从代码质量,还是从团队协作的角度来看都严重地影响了开发团队之间的协同效率和开发效率,最终影响到了用户体验和业务发展。在闲鱼,商品发布和编辑功能也是如此。本文将以闲鱼商品发布和编辑功能的改造为例,向大家展示闲鱼是如何解决此类问题,从而更有效地协同更多团队更快更稳定地支撑各种业务的。发布编辑功能的升级改造为了实现上述目标,针对发布和编辑功能,进行了两轮升级。第一轮的目标在于“平台和业务分离、业务和业务隔离”;而第二轮将更进一步,目标在于“系统之间的解耦合,提升团队协同效率”。1.平台和业务分离,业务和业务隔离第一轮改造中,闲鱼将原先的商品发布和编辑功能从老应用中抽取到了新应用item。为了实现“平台和业务分离、业务和业务隔离”的目标,闲鱼自研了一套技术框架SWAK,具体请参考文章《业务代码解构利器–SWAK》,该文介绍了其设计思想和实现原理。接入SWAK框架后,平台逻辑和业务逻辑得到了分离,各个业务(如租房业务、免费送业务)之间的逻辑也不再耦合,而是变成package隔离(当然也是可以做成jar包隔离)。看一看改造后的应用情况示意图:我们根据发布和编辑的主干流程,抽象了17个SWAK扩展点。发布和编辑的主干流程主要就是对这些扩展点的编排。主干流程的编写并不需要考虑业务上怎么实现这些扩展点的。我们根据不同的业务(在SWAK里面更准确的表述是tag)对这些扩展点进行了各自的实现。根据这样的开发方式,我们可以把开发同学分成如下的两种角色:各业务开发人员。各业务开发人员主要是负责各个业务相关的代码。在item应用里面,业务同学需要维护其业务中和发布编辑相关的个性化业务逻辑。主干开发人员。主干的人员只需要维护主干的代码,尤其是扩展点的抽象。随着不同业务的不断接入,原先的扩展点也需要随之调整。如在之前的《业务代码解构利器–SWAK》一文中指出的一样,经过SWAK改造后,获得了如下的几个优点:代码逻辑清晰,可变和不可变一目了然。代码复用度变高。可变逻辑按照标签进行隔离,单个标签的实现不会影响到其他标签的实现,降低开发和测试成本。无论是按照“类型”分还是按照类目分,对应的开发和测试同学只需要关注对应的逻辑即可。新接手的开发人员能够快速理解,轻松上手。2.系统之间的解耦合,提升团队协同效率以租房为例——租房业务的同学需要在item应用中维护一套租房发布编辑相关的逻辑(如校验地小区数据、地铁数据真实性等);租房业务的同学还需要在详情应用的逻辑中维护一套和租房详情相关的逻辑(如展示地图,展示内部设施标签);租房业务的同学还需要在交易应用的逻辑中维护一套和租房交易相关的逻辑(如预约看房)等等。租房的同学不仅仅需要着手于自己的代码逻辑,还需要修改发布和编辑应用item、还需要修改详情应用,还需要修改交易应用……这种体验是非常糟糕的,有极大的可能性接手一个简单业务就需要修改和发布四五个应用。另一方面,从主干开发人员的角度来说,其应用不仅仅由自己或自己的小团队来维护,还有很多业务开发人员也在修改和发布此应用,且频率会远远超过主干开发任务的发布和部署频次(否则就是主干扩展点逻辑抽取得不好了)。这不利于整个应用的稳定性。A业务服务挂了,应该只影响A业务,而不应该影响主干。依此逻辑,最好能做到JVM隔离。本质上来说,第一轮改造完成了业务之间的解耦合,而第二轮则是系统之间的解耦合。康威定律告诉我们:Any organization that designs a system (defined more broadly here than just information systems) will inevitably produce a design whose structure is a copy of the organization’s communication structure.简而言之就是人员组织结构和系统结构之间的一致性。而完成系统之间的解耦合又恰恰是符合康威定律的。这一轮的改造,我们称之为“业务服务化”。业务服务化改造方案首先,我们把租房业务给单独抽取出来。原先的帖子和拍卖业务暂时没有独立的团队来予以维护(但也基本上没有什么新需求)因此暂时仍然放在主干应用中,时机合适将会和租房应用一样迁移出去。其次,租房业务通过远程服务的方式给主干应用提供服务。接口即是主干业务的提供的扩展点。由于现在是优先使用远程服务来连接主干应用和垂直应用,考虑到性能问题和安全问题,我们在扩展点的定义上也做了一些特殊的改动,后文会有针对性的详述。最后,SWAK框架做了一些改变以注册和调用远程服务。相对于本地服务,远程服务一般都是有超时、连接异常等问题。然而不同接口对于这些异常情况其处理策略也是截然不同的,后文“SWAK框架的针对性改进”会详述这些改动。通过这种方式,我们将主干应用和各业务应用彻底分离了。仍然以租房业务为例,租房团队负责开发和维护租房业务的独立应用rent。租房个性化的发布和编辑需求只需要开发和部署rent应用,而不必修改主干应用。主干应用只由主干团队的同学负责维护,不会被其他业务团队的同学所开发和部署,稳定性更加能得以保障。各业务系统独立开发、独立部署。这些都大幅地减少了不必要的沟通成本、提升协同效率。主干应用和业务应用是通过薄薄的一层接口所联系起来的,这层薄薄的接口都是“声明”:Interface定义、DO的定义和扩展点的默认Reduce策略定义。SWAK框架的针对性改进在之前的《业务代码解构利器–SWAK》一文中指出了,SWAK框架在应用启动的时候会通过各种注册器(registery)注册框架所需的信息。其中最重要的信息就是——业务tag及其对应的SWAK接口的实现类类名或者类实例instance。大多RPC框架都会在client端提供一个代理,代理掉内部的服务发现、保活、序列化、网络通信、反序列化等一系列操作。实际上,SWAK为了支持远程服务调用,只需要将业务tag,以及这些RPC的client的instance的对应关系注册进去就可以了。在闲鱼,RPC使用的是阿里通用的HSF框架(其类似的一个开源框架是Dubbo),这里的RPC的client就是HSF中的ConsumerBean。上文还提到了RPC调用会引入服务超时、连接异常的概念。为何要限制超时?是因为不能被单个应用的超时占据了主干应用的服务资源而引起其他服务和整个应用系统受到影响(如大多数线程阻塞在超时调用上)。无论是超时异常还是连接异常,在业务上也有对应的处理策略。在这里,我们定义了三种异常处理策略,通过在配置上设置相应的注解,SWAK框架会自动按照策略来处理异常。这三种策略是:IGNORE。 即,直接向上层抛出异常。SKIP。对于一个接口有多个tag执行的时候,本tag下该扩展点将跳过,继续执行其他tag下该扩展点的实现。DEFAULT_VALUE。返回默认值。默认值通过spel表达式进行设置。减少扩展点数量众所周知,RPC调用相对于本地调用会增加一部分的网络传输和序列化开销。对于单次调用来说,增加若干ms并没有什么问题,但对于调用10次、20次或更多,这笔开销就相当可观而应该引起重视了。为此,如何降低RPC开销,是一个必须要考虑的问题。最可靠的方法就是降低RPC的次数。在实践中我们发现,很多扩展点实际上都是获取业务配置。如在闲鱼业务中,“是否支持多库存”就是一种配置,如租房不支持多库存。这些业务配置项是由其业务形态所决定的,基本不会变动。因此可以将一组配置项打包一起调用,并且可以缓存下来,也可以直接由主干应用进行维护。在item应用里,这些配置项关系到主干的通用存储过程,目前由各业务方委托主干开发人员进行维护,目前配置在主干环境。可以通过阿里的动态配置平台(如Switch、Diamond)进行动态修改。另外我们对部分邻接的扩展点进行了合并。这些相邻扩展点之间的逻辑比较简单,且不会中断主流程。通过“配置型接口”和“邻接扩展点合并”这两种操作,我们将扩展点数量降低由17个降低到了6个。要注意的是,扩展点并不是越少越好。扩展点越少,越意味着“过度拟合”,可能会对后续业务变更无法适应导致主干需要大幅改动,因此需要在数量和扩展性之间找到一个平衡。另外值得一提的是,SWAK为配置型扩展点做了相应的小改造,并提供了查看当前配置型扩展点返回值的可视化界面。开发人员可以直观地了解当前各个业务的配置值。接口对象定义和细节设计在闲鱼,各种业务所需要存储的东西大同小异,从闲鱼的发布界面上来看就不难发现这一点,都是在基础对象(如标题、描述、图片)之外添加一些业务相关的数据,如拍卖业务中指定拍卖的开拍时间等信息,免费送业务中设置兑换币值,图书业务上设置条形码。即对一本图书进行拍卖当然也是允许的,这就出现了拍卖业务和图书业务叠加起来的复合型业务。对于主干应用开发人员来说,应该提供单个接口以支持所有业务类型,这样不用每次修改或者新增业务时都需要提供新接口。从稳定性的角度考虑,这样的要求很合理。既然是单个接口,那么DO的定义也应该统一。以商品DO为例,有以下三种方式:第一种是继承型结构,该结构不适用于业务叠加的情况。另外主干需要知晓各个业务的DO,每次业务修改或新增,主干都需要做变动。第二种是组合型结构,适用于业务叠加的情况,但同上一种一样,主干需要知晓各个业务的DO,每次业务修改或新增,主干也需要随之变动。第三种使用了Map类型类承载各个业务(biz)的定义类型。主干完全不知道、也不需要知道各个业务DO是如何组成的。这种方式具有最好的扩展性(有点无边界的扩展),也分离了主干应用和业务应用,最接近主干和业务分离的期望。最终我们选择了这一种。使用第三种的对象模型,以新加一种业务为例,其开发流程是:新业务服务端开发人员和客户端开发人员约定各业务的DO,这些DO会存储到bizMap字段。主干应用开发人员不需要了解这些约定。主干应用新增一份新业务的配置,实际上是新业务的识别信息和路由信息。新业务应用实现主干扩展点。联调、测试和上线。业务应用在扩展点返回值中设置需要更新的数据,由主干应用合并。业务应用不应该也不可以直接修改ItemDO,避免影响其他业务的处理逻辑。对于发布和编辑这种需要持久化存储的逻辑来说,必须要强控各业务对ItemDO的修改,否则理论上来说,各业务都有可能将所有的关键字段修改得面目全非。前面提到的“配置型接口”中,就有这样的配置——该业务是否可以修改属性字段、该业务是否可以修改描述字段等配置。总结闲鱼的商品发布和编辑功能基于SWAK框架经过了两次改造升级,第一次升级完成了平台和业务之间的解耦合以及业务和业务之间的解耦合,第二次升级通过平台和业务间使用RPC调用完成了系统和系统之间的解耦合。改造之后,能更有效地协同更多团队更快更稳定地支撑各种业务。SWAK框架依然在继续演进,如部分扩展点原则上可以通过并行处理或异步化处理来提升性能,但暂时还没有提供支持。在这两次改造中, 我们还在测试用例的采集、回放、监控告警等方面也有很多积累,敬请期待后续的文章分享。本文作者:闲鱼技术-紫思阅读原文本文为云栖社区原创内容,未经允许不得转载。

January 18, 2019 · 1 min · jiezi

MongoDB 如何使用内存?为什么内存满了?

最近接到多个MongoDB内存方面的线上case及社区问题咨询,主要集中在:为什么我的 MongoDB 使用了 XX GB 内存?一个机器上部署多个 Mongod 实例/进程,WiredTiger cache 应该如何配置?MongoDB 是否应该使用 SWAP 空间来降低内存压力?MongoDB 内存用在哪?Mongod 进程启动后,除了跟普通进程一样,加载 binary、依赖的各种library 到内存,其作为一个DBMS,还需要负责客户端连接管理,请求处理,数据库元数据、存储引擎等很多工作,这些工作都涉及内存的分配与释放,默认情况下,MongoDB 使用 Google tcmalloc 作为内存分配器,内存占用的大头主要是「存储引擎」与 「客户端连接及请求的处理」。存储引擎 CacheMongoDB 3.2 及以后,默认使用 WiredTiger 存储引擎,可通过 cacheSizeGB 选项配置 WiredTiger 引擎使用内存的上限,一般建议配置在系统可用内存的60%左右(默认配置)。举个例子,如果 cacheSizeGB 配置为 10GB,可以认为 WiredTiger 引擎通过tcmalloc分配的内存总量不会超过10GB。为了控制内存的使用,WiredTiger 在内存使用接近一定阈值就会开始做淘汰,避免内存使用满了阻塞用户请求。目前有4个可配置的参数来支持 wiredtiger 存储引擎的 eviction 策略(一般不需要修改),其含义是:参数默认值含义eviction_target80当 cache used 超过 eviction_target,后台evict线程开始淘汰 CLEAN PAGEeviction_trigger95当 cache used 超过 eviction_trigger,用户线程也开始淘汰 CLEAN PAGEeviction_dirty_target5当 cache dirty 超过 eviction_dirty_target,后台evict线程开始淘汰 DIRTY PAGEeviction_dirty_trigger20当 cache dirty 超过 eviction_dirty_trigger, 用户线程也开始淘汰 DIRTY PAGE在这个规则下,一个正常运行的 MongoDB 实例,cache used 一般会在 0.8 * cacheSizeGB 及以下,偶尔超出问题不大;如果出现 used>=95% 或者 dirty>=20%,并一直持续,说明内存淘汰压力很大,用户的请求线程会阻塞参与page淘汰,请求延时就会增加,这时可以考虑「扩大内存」或者 「换更快的磁盘提升IO能力」。TCP 连接及请求处理MongoDB Driver 会跟 mongod 进程建立 tcp 连接,并在连接上发送数据库请求,接受应答,tcp 协议栈除了为连接维护socket元数据为,每个连接会有一个read buffer及write buffer,用户收发网络包,buffer的大小通过如下sysctl系统参数配置,分别是buffer的最小值、默认值以及最大值,详细解读可以google。net.ipv4.tcp_wmem = 8192 65536 16777216net.ipv4.tcp_rmem = 8192 87380 16777216redhat7(redhat6上并没有导出这么详细的信息) 上通过 ss -m 可以查看每个连接的buffer的信息,如下是一个示例,读写 buffer 分别占了 2357478bytes、2626560bytes,即均在2MB左右;500个类似的连接就会占用掉 1GB 的内存;buffer 占到多大,取决于连接上发送/应答的数据包的大小、网络质量等,如果请求应答包都很小,这个buffer也不会涨到很大;如果包比较大,这个buffer就更容易涨的很大。tcp ESTAB 0 0 127.0.0.1:51601 127.0.0.1:personal-agent skmem:(r0,rb2357478,t0,tb2626560,f0,w0,o0,bl0)除了协议栈上的内存开销,针对每个连接,Mongod 会起一个单独的线程,专门负责处理这条连接上的请求,mongod 为处理连接请求的线程配置了最大1MB的线程栈,通常实际使用在几十KB左右,通过 proc 文件系统看到这些线程栈的实际开销。 除了处理请求的线程,mongod 还有一系列的后台线程,比如主备同步、定期刷新 Journal、TTL、evict 等线程,默认每个线程最大ulimit -s(一般10MB)的线程栈,由于这批线程数量比较固定,占的内存也比较可控。# cat /proc/$pid/smaps7f563a6b2000-7f563b0b2000 rw-p 00000000 00:00 0Size: 10240 kBRss: 12 kBPss: 12 kBShared_Clean: 0 kBShared_Dirty: 0 kBPrivate_Clean: 0 kBPrivate_Dirty: 12 kBReferenced: 12 kBAnonymous: 12 kBAnonHugePages: 0 kBSwap: 0 kBKernelPageSize: 4 kBMMUPageSize: 4 kB线程在处理请求时,需要分配临时buffer存储接受到的数据包,为请求建立上下文(OperationContext),存储中间的处理结果(如排序、aggration等)以及最终的应答结果等。当有大量请求并发时,可能会观察到 mongod 使用内存上涨,等请求降下来后又慢慢释放的行为,这个主要是 tcmalloc 内存管理策略导致的,tcmalloc 为性能考虑,每个线程会有自己的 local free page cache,还有 central free page cache;内存申请时,按 local thread free page cache ==> central free page cache 查找可用内存,找不到可用内存时才会从堆上申请;当释放内存时,也会归还到 cache 里,tcmalloc 后台慢慢再归还给 OS, 默认情况下,tcmalloc 最多会 cache min(1GB,1/8 * system_memory) 的内存, 通过 setParameter.tcmallocMaxTotalThreadCacheBytesParameter 参数可以配置这个值,不过一般不建议修改,尽量在访问层面做调优)tcmalloc cache的管理策略,MongoDB 层暴露了几个参数来调整,一般不需要调整,如果能清楚的理解tcmalloc原理及参数含义,可做针对性的调优;MongoDB tcmalloc 的内存状态可以通过 db.serverStatus().tcmalloc 查看,具体含义可以看 tcmalloc 的文档。重点可以关注下 total_free_bytes ,这个值告诉你有多少内存是 tcmalloc 自己缓存着,没有归还给 OS 的。mymongo:PRIMARY&gt; db.serverStatus().tcmalloc{ “generic” : { “current_allocated_bytes” : NumberLong(“2545084352”), “heap_size” : NumberLong(“2687029248”) }, “tcmalloc” : { “pageheap_free_bytes” : 34529280, “pageheap_unmapped_bytes” : 21135360, “max_total_thread_cache_bytes” : NumberLong(1073741824), “current_total_thread_cache_bytes” : 1057800, “total_free_bytes” : 86280256, “central_cache_free_bytes” : 84363448, “transfer_cache_free_bytes” : 859008, “thread_cache_free_bytes” : 1057800, “aggressive_memory_decommit” : 0, … }}如何控制内存使用?合理配置 WiredTiger cacheSizeGB如果一个机器上只部署 Mongod,mongod 可以使用所有可用内存,则是用默认配置即可。如果机器上多个mongod混部,或者mongod跟其他的一些进程一起部署,则需要根据分给mongod的内存配额来配置 cacheSizeGB,按配额的60%左右配置即可。控制并发连接数TCP连接对 mongod 的内存开销上面已经详细分析了,很多同学对并发有一定误解,认为「并发连接数越高,数据库的QPS就越高」,实际上在大部分数据库的网络模型里,连接数过高都会使得后端内存压力变大、上下文切换开销变大,从而导致性能下降。MongoDB driver 在连接 mongod 时,会维护一个连接池(通常默认100),当有大量的客户端同时访问同一个mongod时,就需要考虑减小每个客户端连接池的大小。mongod 可以通过配置 net.maxIncomingConnections 配置项来限制最大的并发连接数量,防止数据库压力过载。是否应该配置 SWAP官方文档上的建议如下,意思是配置一下swap,避免mongod因为内存使用太多而OOM。For the WiredTiger storage engine, given sufficient memory pressure, WiredTiger may store data in swap space.Assign swap space for your systems. Allocating swap space can avoid issues with memory contention and can prevent the OOM Killer on Linux systems from killing mongod. 开启 SWAP 与否各有优劣,SWAP开启,在内存压力大的时候,会利用SWAP磁盘空间来缓解内存压力,此时整个数据库服务会变慢,但具体变慢到什么程度是不可控的。不开启SWAP,当整体内存超过机器内存上线时就会触发OOM killer把进程干掉,实际上是在告诉你,可能需要扩展一下内存资源或是优化对数据库的访问了。是否开启SWAP,实际上是在「好死」与「赖活着」的选择,个人觉得,对于一些重要的业务场景来说,首先应该为数据库规划足够的内存,当内存不足时,「及时调整扩容」比「不可控的慢」更好。其他尽量减少内存排序的场景,内存排序一般需要更多的临时内存主备节点配置差距不要过大,备节点会维护一个buffer(默认最大256MB)用于存储拉取到oplog,后台从buffer里取oplog不断重放,当备同步慢的时候,这个buffer会持续使用最大内存。控制集合及索引的数量,减少databse管理元数据的内存开销;集合、索引太多,元数据内存开销是一方面的影响,更多的会影响启动加载的效率、以及运行时的性能。本文作者:张友东阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

January 10, 2019 · 2 min · jiezi

阿里云容器服务DaemonSet实践

DaemonSet 保证在每个 Node 上都运行一个容器副本,常用来部署一些集群的日志、监控或者其他系统管理应用。下面以日志收集 fluentd 为例,看下如何使用阿里云容器服务控制台创建DaemonSet。准备Kubernetes环境在阿里云容器服务控制台中创建Kubernetes 集群(1.11.5),3 master,3 worker安装fluentd1、选择应用->守护进程集->使用镜像创建填写应用名称,选择部署集群、命名空间,进入下一步2、选择镜像并进行相应配置注意:这里挂载了配置项fluentd-conf,用来覆盖镜像中的默认配置,需要提前创建出来,内容如下:apiVersion: v1kind: ConfigMapmetadata: name: fluentd-conf namespace: kube-systemdata: td-agent.conf: | <match fluent.> type null </match> <source> type tail path /var/log/containers/.log pos_file /var/log/es-containers.log.pos time_format %Y-%m-%dT%H:%M:%S.%NZ tag kubernetes. format json read_from_head true </source> <filter kubernetes.> type kubernetes_metadata verify_ssl false </filter>否则会遇到pod 启动问题[error]: config error file="/etc/td-agent/td-agent.conf" error=“Invalid Kubernetes API v1 endpoint https://172.21.0.1:443/api: SSL_connect returned=1 errno=0 state=error: certificate verify failed"3、设置更新策略可以在高级配置中选择升级方式:滚动升级(RollingUpdate):更新 DaemonSet 模版后,自动删除旧的 Pod 并创建新的 Pod替换升级(OnDelete):更新模板后,只有手动删除了旧的 Pod 后才会创建新的 Pod4、指定节点调度只选择worker节点安装。设置节点亲和性如图。5、创建完成点击创建,可以看到创建成功。6、问题排查与更新按着上述步骤可以看到在3个worker节点分别起了对应的pod,但pod并没有成功启动。选择其中的一个容器,查看一下日志发现如下错误:config error file="/etc/td-agent/td-agent.conf” error=“Exception encountered fetching metadata from Kubernetes API endpoint: pods is forbidden: User cannot list pods at the cluster scope"Google后发现需要设置ClusterRoleapiVersion: v1kind: ServiceAccountmetadata: name: fluent-account namespace: kube-system—apiVersion: rbac.authorization.k8s.io/v1beta1kind: ClusterRoleBindingmetadata: name: fluent-accountroleRef: kind: ClusterRole name: view apiGroup: rbac.authorization.k8s.iosubjects: - kind: ServiceAccount name: fluent-account namespace: kube-system创建成功后更新fluent-es 的yaml,编辑yaml,提交更新。Pod启动成功,日志已经可以正常采集了。总结使用阿里云容器服务控制台支持方便的创建DaemonSet,欢迎使用体验。https://cs.console.aliyun.com/本文作者:来随便逛逛阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

January 4, 2019 · 1 min · jiezi

KubeCon 2018 参会记录 —— FluentBit Deep Dive

在最近的上海和北美KubeCon大会上,来自于Treasure Data的Eduardo Silva(Fluentd Maintainer)带来了最期待的关于容器日志采集工具FluentBit的最新进展以及深入解析的分享;我们知道Fluentd是在2016年底正式加入CNCF,成为CNCF项目家族的一员,其被广泛用于容器集群中进行应用日志的采集、处理和聚合,但今天主要是跟大家分享一下同样来自于Treasure Data新开源的日志采集工具——FluentBit。FluentBit vs Fluentd既然已经有了Fluentd,那么为什么还要开发一个FluentBit呢?我们知道,Fluentd是基于Ruby语言的,在一些应用日志量较大或者单节点日志量较大的场景下,通过Fluentd采集日志的速率会远落后于应用日志的产生速率,进而导致日志采集的延迟时间较大,这对于一些实时性要求较高的业务系统或者监控系统来说是不可接受的;另外一方面,也是由于Fluentd自身的日志处理逻辑越来越复杂,全部放置在一个组件里来完成会导致越来越臃肿,因此Treasure Data在基于Fluentd优秀的架构和设计理念上重新开发了一个更加轻量级、更加高性能的日志采集工具——FluentBit,其主要采用C语言进行开发。从上面我们可以清晰地看到FluentBit本身占用的内存资源会比Fluentd少很多,且基本没有其他额外的环境依赖,但是支持的插件数相较于Fluentd会少很多,需要时间来慢慢丰富。FluentBit WorkflowFluentBit 内置了一个Service Engine,其每采集到一条日志时都会执行从Input到Output的整个Action Chain:- Input日志数据入口,FluentBit支持多种不同数据来源类型的Input Plugin,不仅能采集容器日志、内核日志、syslog、systemd日志,还支持通过TCP监听接收远程客户端的日志,同时还能够采集系统的CPU、内存和DISK的使用率情况以及本机Network流量日志。- Parser通过情况下我们的应用日志都是非结构化的,那么Parser主要是负责将采集到的非结构化日志解析成结构化的日志数据,一般为JSON格式;FluentBit 默认已经预置了下面几种Parser:JSON:按照JSON格式来进行日志数据解析;Regex:依据配置的正则表达式来进行日志数据解析;Apache:遵循Apache日志格式来进行解析;Nginx:遵循Nginx日志格式来进行解析;Docker:遵循Docker标准输出日志格式进行解析;Syslog rfc5424:按照syslog rfc5424规范格式进行日志解析;Syslog rfc3164:按照syslog rfc3164规范格式进行日志解析;- Filter在实际的生产应用中,我们通常需要对采集到的应用日志记录进行修改或者添加一些关键信息,这都可以Filter Plugin来完成;目前FluentBit也已预置了多种Filter插件:Grep:允许匹配或者过滤掉符合特定正则表达式的日志记录;Record Modifier:允许对日志数据进行修改或者添加新的KV数据,通过此可以方便我们对日志数据进行打标;Throttle:支持采用漏桶和滑动窗口算法进行日志采集速率控制;Kubernetes:自动提取容器或者POD相关信息并添加到日志数据中;Modify:基于设置的规则来对日志数据进行修改;Standard Output:允许将日志数据直接打印到标准输出;Lua:支持通过嵌入Lua Script来修改添加日志数据;- BufferFluentBit 内部本身提供了Buffer机制,会将采集到的日志数据暂存在Memory中直到该日志数据被成功路由转发到指定的目标存储后端。- Routing路由是FluentBit的一个核心功能,它允许我们配置不同的路由规则来将同一条日志数据记录转发到一个或多个不同的接收后端,其内部主要是基于每条日志数据的Tag来进行路由转发,同时支持正则匹配方式;如下面配置则表示希望将Tag满足正则表达式my_的日志直接打印到标准输出中:[INPUT] Name cpu Tag my_cpu[INPUT] Name mem Tag my_mem[OUTPUT] Name stdout Match my_- OutputOutput 主要是用来配置采集到的日志数据将要被转发到哪些日志存储服务中,目前已支持多种主流的存储服务,如ElasticSearch、NATS、InfluxDB、Kafka、Splunk、File、Console等,同样也支持将日志数据继续通过HTTP(S)协议将其传输到其他服务接口中;另外这里有一个比较特殊的Output就是Fluentd,可能大家会比较奇怪,其实在未来的日志架构模型中,FluentBit主要是在采集端专职负责日志的高性能采集,然后可以将采集到的日志在Fluentd中进行较复杂的聚合处理(同Filebeat和Logstash):Other FeaturesEvent Driven内置的Service Engine采用完全异步的事件驱动模型来进行日志的采集和分发。Configuration简单灵活的、高可读性的配置方式,FluentBit的Workflow模型可完全通过配置文件的方式清晰制定。Upstream Manager采用统一的日志上游服务的网络连接管理,包括Keepalive和IO Error处理。TLSv1.2 / Security对于安全敏感的日志数据,支持通过TLS加密通道进行日志传输。Upcoming FeaturesFilesystem buffering mode当前FluentBit只支持Memory的buffer方式,但考虑到内存的易失性,未来也将会支持基于Filesystem的buffer机制。Optional plugins as shared libraries未来会将一些已内置的但又不是必需的插件以共享链接库的方式来进行动态加载。Kubernetes Filter improvements未来会继续深度整合Kubernetes,通过API获取更多POD关键信息并自动添加到日志数据记录中。Summary这两次的KubeCon大会上Eduardo Silva对日志采集工具FluentBit都进行了深度的解析分享,不仅介绍了FluentBit的整个架构模型,而且还分享了未来的发展方向,从整个分享来看FluentBit会侧重在日志的高性能采集方面;而阿里云容器服务在2017年初开源的Log-Pilot:https://github.com/AliyunContainerService/log-pilot ,其不仅能够采集容器的标准输出日志,而且还能动态地发现采集容器内文件日志,同时支持简单高效的日志声明式配置、支持日志路由、日志数据打标以及多种日志采集插件,未来我们将进一步与社区紧密结合,整合FluentBit的高性能采集特性以及Log-Pilot的动态发现和声明式配置优势来进一步增强容器化应用日志的配置采集效率。本文作者:chenqz阅读原文本文为云栖社区原创内容,未经允许不得转载。

December 24, 2018 · 1 min · jiezi

在Kubernetes上运行区块链服务(BaaS)

笔者注:本文是在2018年11月15日由Linux基金会CNCF主办的KubeCon & CloudNativeCon China 2018大会的“Running Blockchain as a Service (BaaS) on Kubernetes”演讲内容基础上整理而成,从技术上介绍了阿里云如何将基于区块链Hyperledger Fabric的BaaS和容器集群技术Kubernetes进行结合的设计理念和实践经验分享。大家好!我是来自于阿里云区块链团队的余珊,今天给大家分享的是《在Kubernetes上运行区块链服务(BaaS)》这个主题。以上是今天分享的内容大纲,其中重点在第三部分,我们将对BaaS结合Kubernetes的一些典型问题进行深入探讨。首先我们分享一下在我们眼中的区块链和BaaS的定义是什么。从狭义上来说,区块链是一种分布式共享账本技术,基于智能合约,在各参与方之间达成对交易的共识,并实现账本交易历史的不可篡改。这个定义是大家所熟知的,并且是从技术和功能上进行的概括。而从广义上来说,我们认为,区块链也是一种在机构、个人、机器之间,构建分布式信任网络、连接可信数据、实现价值流动的新的架构和协作模式。这也是跳出技术和功能维度,从更高维度去进行的理解和总结。对于另一个概念"BaaS",即"Blockchain as a Service", 我们认为,是云平台之上的区块链平台服务,提供了区块链系统的部署、运维、治理能力,以及提供了区块链应用运行和管理的能力。区块链从其类型上可分为私有链、公有链、联盟链三种类型,而从系统拓扑上我们可以将其对应为下述三种模式。对于传统的中心化系统或私有链来说,它基本属于一种星型中心化系统。对于公有链来说,是一种将所有参与企业和个人都对等连接在一起的完全去中心化系统。而对于联盟链来说,是一种带分层结构的多中心化系统。而阿里云今天主要关注的是面向企业场景的联盟链技术类型。下面我们来探讨一下为什么将区块链与容器技术以及Kubernetes进行结合。首先,我们来分析一下区块链的特点。我们将其分为区块链系统和区块链业务应用两类。区块链系统是以数据为核心、高度分布式、Full-Mesh网络拓扑、Long-Running、复杂系统类型。数据为核心:其中最重要的是账本上的数据。高度分布式:因为区块链节点可能部署于不同机房、不同region、不同国家等等。Full-Mesh: 区块链节点之间要依赖全连通的网络以实现共识、账本同步等过程。Long-Running:区块链服务和节点是长时间运行,而不是像Web应用或批处理任务那样短生命周期的。复杂系统类型:区块链系统不是一两个模块构成的简单应用,而是往往一整天解决方案或系统的形式。区块链业务应用:没有统一的标准,可能包含各种应用类型,包括无状态应用、有状态应用、Web应用、数据型应用等等类型。接下来,我们分析一下区块链结合容器技术会带来哪些优势:容器技术为区块链系统和业务应用提供了标准化的软件打包、分发的能力。容器技术实现了区块链运行环境的一致性,以及与底层基础架构的解耦,使得区块链系统和业务应用可以很方便地移植和运行在各种平台之上。进一步的,我们发现,区块链使用Kubernetes集群技术可获得以下几方面的优势:Kubernetes提供了灵活的区块链所需要的底层资源的调度能力,如计算、存储、网络等。Kubernetes强大的运维管理能力,使得我们的区块链服务的产品上线速度以及运维的效率大大提升。Kubernetes支持各种应用类型以及微服务架构,因此对于上面区块链系统和区块链业务应用各自的需求都能很好地满足。使用Kubernetes,可以更好地跟云平台进行集成,因为今天在业界它已经成为了各大云厂商云原生应用的标准底座了。Kubernetes还提供了丰富的安全和隔离功能,这对我们区块链的安全防护体系是很大的增强。另外,围绕Kubernetes有着非常活跃的社区和丰富的技术和业务生态,因此为结合区块链的研发提供了强大的技术支持和资源。这里解答图中的一个疑问,微服务架构是否适合区块链,这要结合上面的区块链特点分析来看待:对区块链系统来说,内部组件之间是强耦合、强依赖的关系,比较难解耦,内部各组件本身不是通用化的服务定位,也不是REST化服务接口,更多是例如gRPC调用,因此不是太适合微服务架构。但是对区块链业务应用来说,则很适合考虑与微服务架构进行结合。上面这幅图展示了阿里云区块链产品形态的演进历史,同时也可以看出我们在区块链结合容器以及Kubernetes方面所在的工作。在2017年10月,我们开始提供基于容器服务的区块链解决方案,支持Hyperledger Fabric,为企业提供一键式的区块链自动部署能力,当时是基于Docker Swarm集群技术。紧接着在2017年12月,我们推出了支持Kubernetes的容器服务区块链解决方案,并且在业界也是较早开始使用Helm Chart部署区块链的。在今年7月底,我们正式推出了阿里云区块链服务BaaS,支持Hyperledger Fabric,同样也是基于Kubernetes。而在今年9月杭州云栖大会上,阿里云BaaS也正式支持了蚂蚁区块链,在这背后蚂蚁区块链也通过适配改造工作实现了在Kubernetes上的部署运行。这一页展示的是阿里云BaaS的产品架构大图。其中最核心的是BaaS,目前已经支持Hyperledger Fabric和蚂蚁区块链。它们的运行实例底座都是基于阿里云容器服务Kubernetes集群。今天的演讲内容主要是围绕Hyperledger Fabric跟Kubernetes结合这方面展开讨论的。上面这一页展示了阿里云容器服务Kubernetes版的产品架构图。这里我们展示了一套跨region的Hyperledger Fabric联盟链的部署架构图。在联盟管理方的Kubernetes集群上部署了Orderer organization和Peer Organization, 而在其他业务参与方所在region的Kubernetes上部署了各自的Peer Organization. 这里的CA、Peer、Orderer、Kafka、ZooKeeper的每个实例都是用了Kubernetes的Service和Deployment类型对象来定义。此外区块链的业务应用可以部署在Kubernetes上或者其他环境上,通过SLB映射到集群worker节点的NodePort上,来访问区块链的各个service。接下来我们进入重点的第三部分,对于实现BaaS运行在Kubernetes的过程,我们曾经遇到的一些有代表性的问题,以及我们的解决思路和实践经验。首先是关于区块链BaaS的打包、发布、服务编排等方面的问题。对于以Hyperledger Fabric为代表的区块链系统来说,这方面面临的主要问题是:区块链系统本身较为复杂,在一套典型部署里可能涉及到三十多个容器、近二十个服务、十来个容器镜像;而且这些服务相互之间有较强的依赖。对于这个问题,我们的解决思路是:在打包部署方面,从一开始我们便选用了容器镜像以及Kuberentes的Helm Chart作为标准的格式和工具。这里尤其提一下,为了保证联盟链各组织创建的独立性和灵活性,我们采用的是一类组织(例如Orderer Org或Peer Org)使用一套chart的方式。在存储库管理方面,目前使用的是阿里云OSS作为Chart Repo(当然可以使用功能更丰富的如ChartMuseum等工具),使用阿里云容器镜像服务作为镜像仓库。这里我们还采用了region化的镜像仓库配置,加快大体积镜像的下载速度,同时采用imagePullSecret保护镜像的安全。在配置方式方面,我们采用了Kubernetes的ConfigMap和Secrets来存储主要的配置和安全信息,以及使用Chart Values来让管控可以根据客户的输入去定制BaaS联盟链实例。在服务编排方面,为了满足服务的依赖性需求,我们结合了Chart Template,Chart的Hook(钩子)机制,以及Kubernetes的Init Container加上Shell脚本方式,实现各种服务尤其在启动过程中的依赖和顺序保证。对于企业来说,业务系统的高可用性是非常重要的,尤其是对生产环境的系统运行和应用访问。这里我们分享一下在BaaS的每一个层面上的高可用设计思路,以及Kubernetes在其中起到怎样的帮助。首先在云基础架构和云资源服务层,我们通过云数据中心的多可用区、所依赖的云服务本身的高可用性和高可靠性来提供保障。在BaaS管控层,通过管控组件的多实例化部署避免单点故障。在容器服务的Kubernetes集群,采用3个master节点和多个worker节点的方式提供应用底座的高可用。在Hyperledger Fabric这一层,它的Orderer、Peer、Kafka、ZooKeeper、CA等类型节点均有集群或高可用互备的设计,比如任一节点挂掉的话,其他节点依然能正常提供服务。但这里有一个关键的点,就是在Kubernetes集群上部署的时候,为了避免这些本应高可用互备的Fabric节点的pod被调度到同一个worker node上,我们采用了Kubernetes Pod Anti-Affinity的功能区将高可用集群的pod调度到不同的worker上,这样保证了真正高可用的部署,提高了对故障的容忍度。在区块链业务应用层,则需要各个企业客户对应用进行周全的高可用设计和实现。在运行时,应用访问Fabric各个服务的这一环节,我们BaaS内置了云平台的SLB负载均衡能力(包含对服务端口的健康检查),以及Fabric的Service Discovery,来保证即使后端部分节点或服务不可用时,应用的调用链路都会被调度到可用的节点或服务上。下面我们谈谈BaaS数据持久化存储的问题。虽然上面已经介绍了BaaS的高可用性设计,但我们仍需考虑如何将链上账本数据、区块链关键配置等重要内容持久化保存到可靠的外部存储而不是容器内部,这样便可以在服务器节点全部发生故障,或者系统重启、备份恢复等场合依然可以实现对系统和数据的恢复。首先,作为背景,我们分析了如果使用本地盘方式可能存在的问题:Kubernetes本身对pod的调度默认并没有限定worker节点,因此如果使用本地盘,就会因为在重启或恢复过程中调度导致的pod漂移而无法读取原来worker节点上的本地盘。对于第一个问题,Kubernetes提供了NodeSelector的机制可以让pod可以绑定worker节点进行部署,不会调度到其他worker节点上,这样就可以保证能始终访问到一个本地盘。但这又带来另一个问题,即在容灾的场景,如果这个节点包括其本地盘受到损坏无法恢复时,会导致整个pod也无法恢复。因此,我们在设计BaaS中的选择是阿里云的NAS文件系统存储、以及阿里云的云盘。在数据可靠性方面,NAS和云盘可以分别提供99.999999999%和99.9999999%的数据可靠性。此外,我们都选用了SSD类型以保证I/O性能。在Kubernetes部署的时候,Fabric的节点通过Persistent Volume和Persistent Volume Claim挂载上相应的存储,并且这些存储是为每一个Fabric的业务organization独立分配的,保证了业务数据的隔离性。在和参加KubeCon大会的一些区块链用户交流的时候,有朋友提到随着账本数据的持续增长,可以怎样解决存储问题。在我们的实践中,我们发现阿里云的NAS有一些很适合区块链账本存储的一些特点:首先,阿里云NAS可提供存储容量动态无缝扩容,在这过程中Fabric节点或区块链业务应用均无需重启或停机,对存储扩容过程完全无感知。其次,有用户担心随着存储数据量的增大,存储的性能是否会明显下降。恰恰相反的是,在阿里云NAS随着所使用的数据量变得越大,NAS所提供的吞吐性能会变得更高,这样可以打消企业用户在长期生产运行方面的顾虑。在上图的右边是Fabric不同类型的区块链节点使用不同类型存储的一个示意图。接下来我们探讨一下在设计搭建BaaS联盟链跨企业的网络方面遇到的挑战。对于大多数区块链技术而言,包括Hyerpedger Fabric, 在网络上要求区块链节点之间形成Full Mesh全连通网络,以实现节点间的账本数据同步、区块广播、节点发现、交易背书等过程,同时企业也要求保障跨企业链路的安全性。对于这些需求,我们梳理了业界目前常见的几类解决方案如下,并进一步分析它们存在的一些不足之处。方案一是采用单一VPC的联盟链网络方案,在这种模式下,联盟链的所有区块链节点均被部署到一套Kubernetes集群网络或VPC内。这种方案实质上是一种私有链的模式,失去了联盟链的各方自治的价值。方案二是基于公网的联盟链网络方案,区块链节点分布式部署在不同区域,通过公网IP对外提供服务以及实现互相通信。这种方案可以较灵活、较低成本低满足大多数基本需求,但对于高级需求如企业级安全网络则不能很好地满足。方案三是基于专线互联的联盟链网络方案,它采用运营商专线方式将不同网络或数据中心进行两两互联,在业界一些企业中得到了采用。但这里面主要存在两方面的问题,首先是如果联盟链参与企业采用了不同电信运营商的专线方案的话,项目实施的复杂性和成本都会很高;其次,如果几家企业已经建好了这样一个联盟网络,对于新来的参与企业,它的接入复杂度和成本也是一个不小的问题。针对上述各种问题,我们在阿里云BaaS基础之上,结合了CEN云企业网,提供了一种安全的联盟链网络方案,主要面向高端需求的企业用户。方案的核心,是采用CEN云企业网打通不同企业的VPC网络,就像一张跨企业的环网,这样不同企业不同的VPC内的网络就可以在CEN内实现全连通。在实现网络连通之后,因为阿里云BaaS联盟链中的Peer,Orderer,CA等服务是通过域名来访问的,目的是提升应用访问的灵活性,而在CEN的这套方案中,我们可以结合云解析PrivateZone,来实现企业环网内各企业VPC之间的统一域名解析。而且上述的网络连通性和域名解析仅限于联盟内部,不会暴露到外网。除了在公共云环境之外,对于那些将区块链节点部署于本地IDC的企业来说,他们也可以通过VPN或者专线方式,接入到云上已和CEN打通的任一VPC中,便可实现和联盟任意节点的通信。作为一个小提醒,在方案实施环节,需要注意提前规划好不同VPC内的内网地址分配,避免在环网中发生冲突。这样我们便形成了一套真正跨企业、跨账户,打通各个VPC的安全联盟链网络方案。下面我们将探讨一个非常有挑战性的问题。众所周知,智能合约是区块链的一个核心。Hyperledger Fabric中的智能合约即chaincode是运行于容器当中。在上面这幅图里我们总结了Hyperledger Fabric的chaincode容器生成过程的示意图:Peer通过Docker Client发起对Docker Daemon的调用,以fabric-ccenv为基础镜像创建出一个chaincode构建容器(chaincode builder container)Peer将chaincode源代码传入chaincode构建容器,在里面完成智能合约编译Peer再调用Docker Daemon创建以fabric-baseos为基础镜像的chaincode镜像,并将在第2步编译好的chaincode二进制文件内置在chaincode镜像中。启动运行chaincode容器。从上述过程我们分析一下这里面存在的一些问题:由于该过程是独立于Kubernetes体系之外运行的,难以对chaincode容器进行生命周期管理。无法基于Kubernetes的namaspace隔离、NetworkPolicy等机制实现对chaincode容器的安全管理。针对上面分析发现的问题,我们研究了几种问题解决的思路。第一种思路,是将chaincode容器纳入到Kubernete的体系(如pod)进行管理。这在我们的角度看来,其实是最理想的方案。因为它不仅可以实现chaincode容器全生命周期与Fabric其他类型节点一致的管理方式,并且可以结合Kubernetes的NetowrkPolicy控制chaincode容器的网络访问策略。其实此前Hyperledger Fabric社区已经创建了一个相关的需求即JIRA(FAB-7406),但目前仍未实现。假设未来在此功能实现之后,我们进一步展望一下,还可以将智能合约的容器调度运行于Serverless Kubernetes之上,提供kernal级别的隔离,保证应用容器之间的安全隔离。第二种思路,如社区和网上的一些观点所提到的,将chaincode容器放入Docker-in-Docker(DIND)环境运行。这个思路背后的出发点,主要是为了降低对宿主机Docker Daemon的依赖以及动态生成chaincode镜像和容器的不可管理性。对于这个思路,我们也基于Hyperledger Fabric和Kubernetes进行了试验,在右边的这部分Kubernetes部署模板yaml代码里,绿色框的部分是用于配置DIND的容器作为peer pod的一个sidecar,同时将DIND容器内的Docker Daemon通过本地端口2375以环境变量的形式配置到peer的参数中,使得peer可以将chaincode创建相关请求发送到DIND内。通过对结果的观察和分析,我们发现了以下这几点。DIND的思路有如下一些优点:无需依赖宿主节点的/var/run/docker.sock。无需专门清理每个Kubernetes worker节点的chaincode镜像。但DIND有着一些更为明显的不足:每次创建部署或恢复peer节点会变得很慢,因为DIND内需要去拉取fabric-ccenv镜像,其大小约1.4GB;而如果用传统部署方式的话,只需在worker节点拉取一次镜像即可。Chaincode的实例化(instantiate)过程稍微变慢,推测这和DIND容器本身运行所需的开销有一定关系。当peer节点或者整个组织(organization)删掉重建之后(复用原有的数据目录),启动速度比起传统方式会慢很多,这背后的原因和第1点相同。在业界实践中,DIND方法主要用于CI/CD的场景,但对于生产环境使用的话,则在稳定性等方面仍有较多的挑战。DIND的思路仍然不能解决chaincode容器的安全访问控制和隔离的问题。第三种思路,是我们目前在BaaS中采用的方法,即综合各种配置的手段先解决最主要的问题。这包括以下几个方面的工作:首先,通过Fabric peer的合理配置(如图中右上角的示例配置)保证chaincode和peer的通信。其次,使用docker rm和docker rmi命令清理chaincode容器和镜像(它们均包含“dev-”前缀)。这里面有不同的可选位置。2.1 适合事后清理的可选位置是采用DaemonSet结合lifecycle.preStop.exec.command的位置来运行这些清理命令。2.2 适合事前清理的可选位置是在initContainer中运行上述清理命令。采用iptables规则,对chaincode容器进行网络隔离。主要是通过在Helm Chart安装阶段配置Kubernetes worker节点的iptables规则,实现限制chaincode容器对Kubernetes网络和对外部网络的访问(同时也可以限制进入chaincode容器的网络访问)。通过上述一系列手段,我们得到了对chaincode容器实现生命周期管理、安全隔离和网络访问限制的一个实用的方案。未来我们也会继续朝着思路一这种最理想方式进行更多的探索。今天阿里巴巴集团的区块链已经在多个行业、多种场景实现了结合以及业务落地,包含了如商品溯源、数字内容版权、供应链金融、数据资产共享、公益慈善、医疗处方等等。我们的客户在生产环境已经达到了百万级的交易规模以及百GB的账本数据,这也为我们提供了很丰富的区块链应用实践经验。基于这些实践,我们想跟大家分享的是,其实区块链应用设计开发并不复杂,这一页总结了构建于Kubernete之上的区块链系统和应用的基本模式。可以看到,Kubernetes帮我们解决了底层基础架构和资源的复杂性,提供了应用的标准底座;而区块链服务BaaS则帮我们解决了区块链系统配置部署和运维的复杂性,提供了统一的接口,那么对企业来说,便可以聚焦在业务流程和业务逻辑的实现,及业务应用的开发上,以及与业务数据的交互和管理上来,实现核心价值的最大化。下面,我们将进行阿里云BaaS Hyperledger Fabric的一个demo,主要展示一下几方面的过程:首先,快速创建跨企业(跨账号)、跨region的联盟链。接着,动态添加新组织、新通道,完成企业间协同,包括邀请企业,以及企业各自的审批流程。在一些关键操作点上,BaaS内置了风控保障,强制邀请短信验证才允许完成操作,这看似麻烦的环节实际上是企业对生产安全保障以及审计都非常看重和需要的。最后,我们在BaaS上部署了经典的Marbles虚拟数字资产交易的应用,包含chaincode的部署和client SDK应用的部署。最后,欢迎有兴趣的朋友进一步了解和使用阿里云的区块链服务BaaS,通过扫描图中的两个二维码可快速访问相关产品主页,申请开通免费公测试用,以及访问产品文档获得更多使用和开发指南。以上就是我今天跟大家分享的全部内容,谢谢大家!本文作者:余珊阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

December 19, 2018 · 1 min · jiezi

Flutter路由管理代码这么长长长长长,阿里工程师怎么高效解决?(实用)

背景:在flutter的业务开发过程中,flutter侧会逐渐丰富自己的路由管理。一个轻量的路由管理本质上是页面标识(或页面路径)与页面实例的映射。本文基于dart注解提供了一个轻量路由管理方案。 不论是在native与flutter的混合工程,还是纯flutter开发的工程,当我们实现一个轻量路由的时候一般会有以下几种方法:较差的实现,if-else的逻辑堆叠: 做映射时较差的实现是通过if-else的逻辑判断把url映射到对应的widget实例上,class Router { Widget route(String url, Map params) { if(url == ‘myapp://apage’) { return PageA(url); } else if(url == ‘myapp://bpage’) { return PageB(url, params); } }}这样做的弊端比较明显: 1)每个映射的维护影响全局映射配置的稳定性,每次维护映射管理时需要脑补所有的逻辑分支. 2)无法做到页面的统一抽象,页面的构造器和构造逻辑被开发者自定义. 3)映射配置无法与页面联动,把页面级的配置进行中心化的维护,导致维护责任人缺失.一般的实现,手动维护的映射表: 稍微好一点的是将映射关系通过一个配置信息和一个工厂方法来表现class Router { Map<String, dynamic> mypages = <String, dynamic> { ‘myapp://apage’: ‘pagea’, ‘myapp://bpage’: ‘pageb’ } Widget route(String url, Map params) { String pageId = mypages[url]; return getPageFromPageId(pageId); } Widget getPageFromPageId(String pageId) { switch(pageId) { case ‘pagea’: return PageA(); case ‘pageb’: return PageB(); } return null; }在flutter侧这种做法仍然比较麻烦,首先是问题3仍然存在,其次是由于flutter目前不支持反射,必须有一个类似工厂方法的方式来创建页面实例。 为了解决以上的问题,我们需要一套能在页面级使用、自动维护映射的方案,注解就是一个值得尝试的方向。我们的路由注解方案annotation_route(github地址:https://github.com/alibaba-flutter/annotation_route)) 应运而生,整个注解方案的运行系统如图所示: 让我们从dart注解开始,了解这套系统的运作。dart注解注解,实际上是代码级的一段配置,它可以作用于编译时或是运行时,由于目前flutter不支持运行时的反射功能,我们需要在编译期就能获取到注解的相关信息,通过这些信息来生成一个自动维护的映射表。那我们要做的,就是在编译时通过分析dart文件的语法结构,找到文件内的注解块和注解的相关内容,对注解内容进行收集,最后生成我们想要的映射表,这套方案的构想如图示: 在调研中发现,dart的部分内置库加速了这套方案的落地。source_gendart提供了build、analyser、source_gen这三个库,其中source_gen利用build库和analyser库,给到了一层比较好的注解拦截的封装。从注解功能的角度来看,这三个库分别给到了如下的功能:build库:整套资源文件的处理analyser库:对dart文件生成完备的语法结构source_gen库:提供注解元素的拦截 这里简要介绍下source_gen和它的上下游,先看看我们捋出来的它注解相关的类图:source_gen的源头是build库提供的Builder基类,该类的作用是让使用者自定义正在处理的资源文件,它负责提供资源文件信息,同时提供生成新资源文件的方法。source_gen从build库提供的Builder类中派生出了一个自己的builder,同时自定义了一套生成器Generator的抽象,派生出来的builder接受Generator类的集合,然后收集Generator的产出,最后生成一份文件,不同的派生builder对generator的处理各异。这样source_gen就把一个文件的构造过程交给了自己定义的多个Generator,同时提供了相对build库而言比较友好的封装。 在抽象的生成器Generator基础上,source_gen提供了注解相关的生成器GeneratorForAnnotation,一个注解生成器实例会接受一个指定的注解类型,由于analyser提供了语法节点的抽象元素Element和其metadata字段,即注解的语法抽象元素ElementAnnotation,注解生成器即可通过检查每个元素的metadata类型是否匹配声明的注解类型,从而筛选出被注解的元素及元素所在上下文的信息,然后将这些信息包装给使用者,我们就可以利用这些信息来完成路由注解。annotation_route在了解了source_gen之后,我们开始着手自己的注解解析方案annotation_route 刚开始介入时,我们遇到了几个问题:只需要生成一个文件:由于一个输入文件对应了一个生成文件后缀,我们需要避免多余的文件生成需要知道在什么时候生成文件:我们需要在所有的备选文件扫描收集完成后再能进行映射表的生成source_gen对一个类只支持了一个注解,但存在多个url映射到一个页面 在一番思索后我们有了如下产出首先将注解分成两类,一类用于注解页面@ARoute,另一类用于注解使用者自己的router@ARouteRoot。routeBuilder拥有RouteGenerator实例,RouteGenerator实例,负责@ARoute注解;routeWriteBuilder拥有RouteWriterGenerator实例,负责@ARouteRoot注解。通过build库支持的配置文件build.yaml,控制两类builder的构造顺序,在routeBuilder执行完成后去执行routeWriteBuilder,这样我们就能准确的在所有页面注解扫描完成后开始生成自己的配置文件。 在注解解析工程中,对于@ARoute注解的页面,通过RouteGenerator将其配置信息交给拥有静态存储空间的Collector处理,同时将其输出内容设为null,即不会生成对应的文件。在@ARoute注解的所有页面扫描完成后,RouteWriteGenerator则会调用Writer,它从Collector中提取信息,并生成最后的配置文件。对于使用者,我们提供了一层友好的封装,在使用annotation_route配置到工程后,我们的路由代码发生了这样的变化: 使用前: class Router { Widget pageFromUrlAndQuery(String urlString, Map<String, dynamic> query) { if(urlString == ‘myapp://testa’) { return TestA(urlString, query); } else if(urlString == ‘myapp://testb’) { String absoluteUrl = Util.join(urlString, query); return TestB(url: absoluteUrl); } else if(urlString == ‘myapp://testc’) { String absoluteUrl = Util.join(urlString, query); return TestC(config: absoluteUrl); } else if(urlString == ‘myapp://testd’) { return TestD(PageDOption(urlString, query)); } else if(urlString == ‘myapp://teste’) { return TestE(PageDOption(urlString, query)); } else if(urlString == ‘myapp://testf’) { return TestF(PageDOption(urlString, query)); } else if(urlString == ‘myapp://testg’) { return TestG(PageDOption(urlString, query)); } else if(urlString == ‘myapp://testh’) { return TestH(PageDOption(urlString, query)); } else if(urlString == ‘myapp://testi’) { return TestI(PageDOption(urlString, query)); } return DefaultWidget; } }使用后:import ‘package:annotation_route/route.dart’; class MyPageOption { String url; Map<String, dynamic> query; MyPageOption(this.url, this.query); } class Router { ARouteInternal internal = ARouteInternalImpl(); Widget pageFromUrlAndQuery(String urlString, Map<String, dynamic> query) { ARouteResult routeResult = internal.findPage(ARouteOption(url: urlString, params: query), MyPageOption(urlString, query)); if(routeResult.state == ARouteResultState.FOUND) { return routeResult.widget; } return DefaultWidget; } }目前该方案已在闲鱼app内稳定运行,我们提供了基础的路由参数,随着flutter业务场景越来越复杂,我们也会在注解的自由度上进行更深的探索。关于annotation_route更加详细的安装和使用说明参见github地址:https://github.com/alibaba-flutter/annotation_route ,在使用中遇到任何问题,欢迎向我们反馈。本文作者:闲鱼技术-兴往阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

December 18, 2018 · 2 min · jiezi

从一道简单的“SpringBoot配置文件”相关面试题,我就能知道你的水平

面试要套路,也要技巧。别被背题目的兄弟们给忽悠了。【你来发挥】你比较喜欢什么技术,哪一种最熟?一般自信的面试官都喜欢问这个问题,这次面试的小伙比较年轻,咱也装回B,不然都对不起自己。答: 我比较喜欢Spring,比较有趣。目的: 希望应聘者能够有广度且有深度。如果最感兴趣的是Spring本身,而不是其上的解决方案,那顶多会承担被分解后的编码工作。巧了,咱也熟。【工作经验】SpringBoot相比较SpringMVC,有什么优缺点?答: 有很多方面。觉得最好的就是不用写那么多配置文件了,直接写个注解,通过自动配置,就完成了初始化。目的: 说什么无所谓,主要看有没有总结能力。判断是否用过早期的Spring版本,经历过版本更新更能了解软件开发之痛,接受新知识会考虑兼容和迭代。【实现原理】我想要SpringBoot自动读取我的自定义配置,该做哪些事情?答: 写一个相应的starter目的: 判断是否了解和写过Spring Boot Starter,主要是META-INF目录下的spring.factories文件和AutoConfiguration。了解AOP更佳。【烟幕弹】配置文件是yml格式,这种格式你喜欢么?答: 比较喜欢properties格式,感觉yml格式的配置文件缩进比较难处理。比如当我从网上拷贝一些别人长长的配置文件,可能要花较多时间整理文件格式。目的 此问题没有具体的意图,主要是过渡用。【动手能力】这么喜欢properties方式,能够写一段代码,将yml翻译成properties么? 要是回答相反则反着来。目的 通过简单的伪代码,判断应聘者的动手能力和编码风格。是喜欢问题抽象化还是喜欢立刻动手去写。我希望回答能够有条理,而且能够考虑各种异常情况,比如把自己判断不了的配置交给用户处理;比如空格和<TAB>的处理。【提示】提示一下,你用什么数据结构进行存储?目的 假如应聘者在一段时间内不能有任何产出,会给出简单的提示。找准了存储结构,你就基本完成了工作,此问题还判断了应聘者的培养成本和价值。【基础算法】哦,是树结构,遍历方式是怎样的?前序,后序,中序?目的 判断是否有基础的算法知识。做工程先不要求会什么动态规划或者贪心算法,但起码的数据结构是要了解的。【基础知识】你用到了Map?Java中如何做可排序的Map?目的 是否对java的基础集合类熟悉,期望回答TreeMap,如果回答上来,可能会追问它是什么数据结构(红黑树)。【知识广度】你还接触过哪些配置方式?比较喜欢那种?目的 了解应聘者的知识广度,说不出来也无所谓,了解的多会加分。比如ini、cfg、json、toml、序列化等。【项目规模】我想要把我的配置放在云端,比如数据库密码等,改怎么做?目的 是否了解SpringBoot的组件SpringConfig,或者了解一些其他的开源组件如携程的apollo等。【知识广度】我想要配置文件改动的时候,所有机器自动更新,该怎么办?目的 了解是否知晓常用的同步方式。有两种:一种是定时去轮询更新;一种是使用zk或者etcd这种主动通知的组件。【实现细节】Spring是如何进行配置文件刷新的?目的 这个可真是没写过就真不知道了,主要是org.springframework.cloud.context.scope.refresh.RefreshScope这个类【架构能力】现在我想要将配置文件分发到一部分机器,也就是带有版本的灰度概念,你该如何处理?目的 如果能够从网关、微服务约定,后台操作原型方面去多方位描述一下,更佳。这样筛选的小伙伴,都很棒!能力多少,心中有数。

December 11, 2018 · 1 min · jiezi

Flink SQL 核心解密 —— 提升吞吐的利器 MicroBatch

之前我们在 Flink SQL 中支持了 MiniBatch, 在支持高吞吐场景发挥了重要作用。今年我们在 Flink SQL 性能优化中一项重要的改进就是升级了微批模型,我们称之为 MicroBatch,也叫 MiniBatch2.0。在设计和实现 Flink 的流计算算子时,我们一般会把“面向状态编程”作为第一准则。因为在流计算中,为了保证状态(State)的一致性,需要将状态数据存储在状态后端(StateBackend),由框架来做分布式快照。而目前主要使用的RocksDB,Niagara状态后端都会在每次read和write操作时发生序列化和反序列化操作,甚至是磁盘的 I/O 操作。因此状态的相关操作通常都会成为整个任务的性能瓶颈,状态的数据结构设计以及对状态的每一次访问都需要特别注意。微批的核心思想就是缓存一小批数据,在访问状态状态时,多个同 key 的数据就只需要发生一次状态的操作。当批次内数据的 key 重复率较大时,能显著降低对状态的访问频次,从而大幅提高吞吐。MicroBatch 和 MiniBatch 的核心机制是一样的,就是攒批,然后触发计算。只是攒批策略不太一样。我们先讲解触发计算时是如何节省状态访问频次的。微批计算MicroBatch 的一个典型应用场景就是 Group Aggregate。例如简单的求和例子:SELECT key, SUM(value) FROM T GROUP BY key如上图所示,当未开启 MicroBatch 时,Aggregate 的处理模式是每来一条数据,查询一次状态,进行聚合计算,然后写入一次状态。当有 N 条数据时,需要操作 2*N 次状态。当开启 MicroBatch 时,对于缓存下来的 N 条数据一起触发,同 key 的数据只会读写状态一次。例如上图缓存的 4 条 A 的记录,只会对状态读写各一次。所以当数据的 key 的重复率越大,攒批的大小越大,那么对状态的访问会越少,得到的吞吐量越高。攒批策略攒批策略一般分成两个维度,一个是延时,一个是内存。延时即控制多久攒一次批,这也是用来权衡吞吐和延迟的重要参数。内存即为了避免瞬间 TPS 太大导致内存无法存下缓存的数据,避免造成 Full GC 和 OOM。下面会分别介绍旧版 MiniBatch 和 新版 MicroBatch 在这两个维度上的区别。MiniBatch 攒批策略MiniBatch 攒批策略的延时维度是通过在每个聚合节点注册单独的定时器来实现,时间分配策略采用简单的均分。比如有4个 aggregate 节点,用户配置 10s 的 MiniBatch,那么每个节点会分配2.5s,例如下图所示:但是这种策略有以下几个问题:用户能容忍 10s 的延时,但是真正用来攒批的只有2.5秒,攒批效率低。拓扑越复杂,差异越明显。由于上下游的定时器的触发是纯异步的,可能导致上游触发微批的时候,下游也正好触发微批,而处理微批时会一段时间不消费网络数据,导致上游很容易被反压。计时器会引入额外的线程,增加了线程调度和抢锁上的开销。MiniBatch 攒批策略在内存维度是通过统计输入条数,当输入的条数超过用户配置的 blink.miniBatch.size 时,就会触发批次以防止 OOM。但是 size 参数并不是很好评估,一方面当 size 配的过大,可能会失去保护内存的作用;而当 size 配的太小,又会导致攒批效率降低。MicroBatch 攒批策略MicroBatch 的提出就是为了解决 MiniBatch 遇到的上述问题。MicroBatch 引入了 watermark 来控制聚合节点的定时触发功能,用 watermark 作为特殊事件插入数据流中将数据流切分成相等时间间隔的一个个批次。实现原理如下所示:MicroBatch 会在数据源之后插入一个 MicroBatchAssigner 的节点,用来定时发送 watermark,其间隔是用户配置的延时参数,如10s。那么每隔10s,不管数据源有没有数据,都会发一个当前系统时间戳的 watermark 下去。一个节点的当前 watermark 取自所有 channel 的最小 watermark 值,所以当聚合节点的 watermark 值前进时,也就意味着攒齐了上游的一个批次,我们就可以触发这个批次了。处理完这个批次后,需要将当前 watermark 广播给下游所有 task。当下游 task 收齐上游 watermark 时,也会触发批次。这样批次的触发会从上游到下游逐级触发。这里将 watermark 作为划分批次的特殊事件是很有意思的一点。Watermark 是一个非常强大的工具,一般我们用来衡量业务时间的进度,解决业务时间乱序的问题。但其实换一个维度,它也可以用来衡量全局系统时间的进度,从而非常巧妙地解决数据划批的问题。因此与 MiniBatch 策略相比,MicroBatch 具有以下优点:相同延时下,MicroBatch 的攒批效率更高,能攒更多的数据。由于 MicroBatch 的批次触发是靠事件的,当上游触发时,下游不会同时触发,所以不像 MiniBatch 那么容易引起反压。解决数据抖动问题(下一小节分析)我们利用一个 DAU 作业进行了性能测试对比,在相同的 allowLatency(6秒)配置的情况下,MicroBatch 能得到更高的吞吐,而且还能得到与 MiniBatch 相同的端到端延迟!另外,仍然是上述的性能测试对比,可以发现运行稳定后 MicroBatch 的队列使用率平均值在 50% 以下,而 MiniBatch 基本是一直处于队列满载下。说明 MicroBatch 比 MiniBatch 更加稳定,更不容易引起反压。MicroBatch 在内存维度目前仍然与 MiniBatch 一样,使用 size 参数来控制条数。但是将来会基于内存管理,将缓存的数据存于管理好的内存块中(BytesHashMap),从而减少 Java 对象的空间成本,减少 GC 的压力和防止 OOM。防止数据抖动所谓数据抖动问题是指,两层 AGG 时,第一层 AGG 发出的更新消息会拆成两条独立的消息被下游消费,分别是retract 消息和 accumulate 消息。而当第二层 AGG 消费这两条消息时也会发出两条消息。从前端看到就是数据会有抖动的现象。例如下面的例子,统计买家数,这里做了两层打散,第一层先做 UV 统计,第二级做SUM。SELECT day, SUM(cnt) totalFROM ( SELECT day, MOD(buy_id, 1024), COUNT(DISTINCT buy_id) as cnt FROM T GROUP BY day, MOD(buy_id, 1024))GROUP BY day当第一层count distinct的结果从100上升到101时,它会发出 -100, +101 的两条消息。当第二层的 SUM 会依次收到这两条消息并处理,假设此时 SUM 值是 900,那么在处理 -100 时,会先发出 800 的结果值,然后处理 +101 时,再发出 901 的结果值。从用户端的感受就是买家数从 900 降到了 800 又上升到了 901,我们称之为数据抖动。而理论上买家数只应该只增不减的,所以我们也一直在思考如何解决这个问题。数据抖动的本质原因是 retract 和 accumulate 消息是一个事务中的两个操作,但是这两个操作的中间结果被用户看到了,也就是传统数据库 ACID 中的隔离性(I) 中最弱的 READ UNCOMMITTED 的事务保障。要从根本上解决这个问题的思路是,如何原子地处理 retract & accumulate 的消息。如上文所述的 MicroBatch 策略,借助 watermark 划批,watermark 不会插在 retract & accumulate 中间,那么 watermark 就是事务的天然分界。按照 watermark 来处理批次可以达到原子处理 retract & accumulate 的目的。从而解决抖动问题。适用场景与使用方式MicroBatch 是使用一定的延迟来换取大量吞吐的策略,如果用户有超低延迟的要求的话,不建议开启微批处理。MicroBatch 目前对于无限流的聚合、Join 都有显著的性能提升,所以建议开启。如果遇到了上述的数据抖动问题,也建议开启。MicroBatch默认关闭,开启方式:# 攒批的间隔时间,使用 microbatch 策略时需要加上该配置,且建议和 blink.miniBatch.allowLatencyMs 保持一致blink.microBatch.allowLatencyMs=5000# 使用 microbatch 时需要保留以下两个 minibatch 配置blink.miniBatch.allowLatencyMs=5000# 防止OOM,每个批次最多缓存多少条数据blink.miniBatch.size=20000后续优化MicroBatch 目前只支持无限流的聚合和 Join,暂不支持 Window Aggregate。所以后续 Window Aggregate 会重点支持 MicroBatch 策略,以提升吞吐性能。另一方面,MicroBatch 的内存会考虑使用二进制的数据结构管理起来,提升内存的利用率和减轻 GC 的影响。本文作者:jark阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

December 7, 2018 · 2 min · jiezi

云数据库POLARDB优势解读之①——10分钟了解

什么是POLARDBPOLARDB 是阿里云自研的下一代关系型分布式数据库,100%兼容MySQL,之前使用MySQL的应用程序不需要修改一行代码,即可使用POLARDB。POLARDB在运行形态上是一个多节点集群,集群中有一个Writer节点(主节点)和多个Reader节点,他们之间节点间通过分布式文件系统(PolarFileSystem)共享底层的同一份存储(PolarStore)。POLARDB通过内部的代理层(Proxy)对外提供服务,也就是说所有的应用程序都先经过这层代理,然后才访问到具体的数据库节点。Proxy不仅可以做安全认证(Authorization)和保护(Protection),还可以解析SQL,把写操作(比如事务、Update、Insert、Delete、DDL等)发送到Writer节点,把读操作(比如Select)均衡地分发到多个Reader节点,这个也叫读写分离。POLARDB对外默认提供了两个数据库地址,一个是集群地址(Cluster),一个是主地址(Primary),推荐使用集群地址,因为它具备读写分离功能可以把所有节点的资源整合到一起对外提供服务。主地址是永远指向主节点,访问主地址的SQL都被发送到主节点,当发生主备切换(Failover)时,主地址也会在30秒内自动漂移到新的主节点上,确保应用程序永远连接的都是可写可读的主节点。如上图,底层一套存储,节省成本,是『合』;中间多个节点,提高扩展性,是『分』;上层一套代理层,统一入口,使用简单,也是『合』。如此『合-分-合』的架构,在扩展性和使用便捷性之间保持了平衡,使得对于上层应用程序来说,就像使用一个单点的MySQL数据库一样简单。如何使用POLARDB部署在云端,创建时先选择使用的地域可用区和具体的VPC网络,然后指定节点的数量(从 2个 到 16 个)和配置(从 2核 到 88核)即可,存储空间不用提前配置,也不需要关心容量大小,系统会根据实际的使用量自动收取费用。创建过程可能持续5-10分钟,然后配置好白名单、创建完高权限账号就可以使用了。逻辑DB和账号User,可以在控制台创建,也可以通过高权限账号登录到数据库执行SQL创建,二者效果完全一样,没有区别。如果您需要迁移老的数据库到POLARDB,推荐使用DTS。不管源库是在RDS,还是在ECS自建MySQL,甚至是在云下有公网地址可访问的MySQL,都可以通过DTS做在线平滑迁移,停机时间5-10分钟。特点除了可以像使用MySQL一样使用POLARDB,这里还有一些传统MySQL数据库不具备的优势。容量大最高100T,不再因为单机容量的天花板而去购买多个MySQL实例做Sharding,甚至也不需要考虑分库分表,简化应用开发,降低运维负担。高性价比多个节点只收取一份存储的钱,也就是说只读实例越多越划算。分钟级弹性存储与计算分离的架构,再加上共享存储,使得快速升级成为现实。读一致性集群的读写分离地址,利用LSN(Log Sequence Number)确保读取数据时的全局一致性,避免因为主备延迟引起的不一致问题。毫秒级延迟——物理复制利用基于Redo的物理复制代替基于Binlog的逻辑复制,提升主备复制的效率和稳定性。即使是加索引、加字段的大表DDL操作,也不会对数据库造成延迟。无锁备份利用存储层的快照,可以在60秒内完成2T数据量大小的数据库的备份。并且这个备份过程不需要对数据库加锁,对应用程序几乎无影响,全天24小时均可进行备份。复杂SQL查询加速内置并行查询引擎,对执行时长超过1分钟的复杂分析类SQL加速效果明显。该功能需要额外连接地址。本文作者:乙休阅读原文本文为云栖社区原创内容,未经允许不得转载。

November 29, 2018 · 1 min · jiezi

如何在优雅地Spring 中实现消息的发送和消费

本文将对rocktmq-spring-boot的设计实现做一个简单的介绍,读者可以通过本文了解将RocketMQ Client端集成为spring-boot-starter框架的开发细节,然后通过一个简单的示例来一步一步的讲解如何使用这个spring-boot-starter工具包来配置,发送和消费RocketMQ消息。通过本文,您将了解到:Spring的消息框架介绍rocketmq-spring-boot具体实现使用示例前言上世纪90年代末,随着Java EE(Enterprise Edition)的出现,特别是Enterprise Java Beans的使用需要复杂的描述符配置和死板复杂的代码实现,增加了广大开发者的学习曲线和开发成本,由此基于简单的XML配置和普通Java对象(Plain Old Java Objects)的Spring技术应运而生,依赖注入(Dependency Injection), 控制反转(Inversion of Control)和面向切面编程(AOP)的技术更加敏捷地解决了传统Java企业及版本的不足。随着Spring的持续演进,基于注解(Annotation)的配置逐渐取代了XML文件配置, 2014年4月1日,Spring Boot 1.0.0正式发布,它基于“约定大于配置”(Convention over configuration)这一理念来快速地开发、测试、运行和部署Spring应用,并能通过简单地与各种启动器(如 spring-boot-web-starter)结合,让应用直接以命令行的方式运行,不需再部署到独立容器中。这种简便直接快速构建和开发应用的过程,可以使用约定的配置并且简化部署,受到越来越多的开发者的欢迎。Apache RocketMQ是业界知名的分布式消息和流处理中间件,简单地理解,它由Broker服务器和客户端两部分组成:其中客户端一个是消息发布者客户端(Producer),它负责向Broker服务器发送消息;另外一个是消息的消费者客户端(Consumer),多个消费者可以组成一个消费组,来订阅和拉取消费Broker服务器上存储的消息。为了利用Spring Boot的快速开发和让用户能够更灵活地使用RocketMQ消息客户端,Apache RocketMQ社区推出了spring-boot-starter实现。随着分布式事务消息功能在RocketMQ 4.3.0版本的发布,近期升级了相关的spring-boot代码,通过注解方式支持分布式事务的回查和事务消息的发送。本文将对当前的设计实现做一个简单的介绍,读者可以通过本文了解将RocketMQ Client端集成为spring-boot-starter框架的开发细节,然后通过一个简单的示例来一步一步的讲解如何使用这个spring-boot-starter工具包来配置,发送和消费RocketMQ消息。Spring 中的消息框架顺便在这里讨论一下在Spring中关于消息的两个主要的框架,即Spring Messaging和Spring Cloud Stream。它们都能够与Spring Boot整合并提供了一些参考的实现。和所有的实现框架一样,消息框架的目的是实现轻量级的消息驱动的微服务,可以有效地简化开发人员对消息中间件的使用复杂度,让系统开发人员可以有更多的精力关注于核心业务逻辑的处理。2.1 Spring MessagingSpring Messaging是Spring Framework 4中添加的模块,是Spring与消息系统集成的一个扩展性的支持。它实现了从基于JmsTemplate的简单的使用JMS接口到异步接收消息的一整套完整的基础架构,Spring AMQP提供了该协议所要求的类似的功能集。 在与Spring Boot的集成后,它拥有了自动配置能力,能够在测试和运行时与相应的消息传递系统进行集成。单纯对于客户端而言,Spring Messaging提供了一套抽象的API或者说是约定的标准,对消息发送端和消息接收端的模式进行规定,不同的消息中间件提供商可以在这个模式下提供自己的Spring实现:在消息发送端需要实现的是一个XXXTemplate形式的Java Bean,结合Spring Boot的自动化配置选项提供多个不同的发送消息方法;在消息的消费端是一个XXXMessageListener接口(实现方式通常会使用一个注解来声明一个消息驱动的POJO),提供回调方法来监听和消费消息,这个接口同样可以使用Spring Boot的自动化选项和一些定制化的属性。如果有兴趣深入的了解Spring Messaging及针对不同的消息产品的使用,推荐阅读这个文件。参考Spring Messaging的既有实现,RocketMQ的spring-boot-starter中遵循了相关的设计模式并结合RocketMQ自身的功能特点提供了相应的API(如,顺序,异步和事务半消息等)。2.2 Spring Cloud StreamSpring Cloud Stream结合了Spring Integration的注解和功能,它的应用模型如下:Spring Cloud Stream框架中提供一个独立的应用内核,它通过输入(@Input)和输出(@Output)通道与外部世界进行通信,消息源端(Source)通过输入通道发送消息,消费目标端(Sink)通过监听输出通道来获取消费的消息。这些通道通过专用的Binder实现与外部代理连接。开发人员的代码只需要针对应用内核提供的固定的接口和注解方式进行编程,而不需要关心运行时具体的Binder绑定的消息中间件。在运行时,Spring Cloud Stream能够自动探测并使用在classpath下找到的Binder。这样开发人员可以轻松地在相同的代码中使用不同类型的中间件:仅仅需要在构建时包含进不同的Binder。在更加复杂的使用场景中,也可以在应用中打包多个Binder并让它自己选择Binder,甚至在运行时为不同的通道使用不同的Binder。Binder抽象使得Spring Cloud Stream应用可以灵活的连接到中间件,加之Spring Cloud Stream使用利用了Spring Boot的灵活配置配置能力,这样的配置可以通过外部配置的属性和Spring Boo支持的任何形式来提供(包括应用启动参数、环境变量和application.yml或者application.properties文件),部署人员可以在运行时动态选择通道连接destination(例如,Kafka的topic或者RabbitMQ的exchange)。Binder SPI的方式来让消息中间件产品使用可扩展的API来编写相应的Binder,并集成到Spring Cloud Steam环境,目前RocketMQ还没有提供相关的Binder,我们计划在下一步将完善这一功能,也希望社区里有这方面经验的同学积极尝试,贡献PR或建议。spring-boot-starter的实现在开始的时候我们已经知道,spring boot starter构造的启动器对于使用者是非常方便的,使用者只要在pom.xml引入starter的依赖定义,相应的编译,运行和部署功能就全部自动引入。因此常用的开源组件都会为Spring的用户提供一个spring-boot-starter封装给开发者,让开发者非常方便集成和使用,这里我们详细的介绍一下RocketMQ(客户端)的starter实现过程。3.1. spring-boot-starter的实现步骤对于一个spring-boot-starter实现需要包含如下几个部分:在pom.xml的定义定义最终要生成的starter组件信息<groupId>org.apache.rocketmq</groupId><artifactId>spring-boot-starter-rocketmq</artifactId><version>1.0.0-SNAPSHOT</version>定义依赖包,它分为两个部分: A、Spring自身的依赖包; B、RocketMQ的依赖包<dependencies> <!– spring-boot-start internal depdencies –> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!– rocketmq dependencies –> <dependency> <groupId>org.apache.rocketmq</groupId> <artifactId>rocketmq-client</artifactId> <version>${rocketmq-version}</version> </dependency></dependencies> <dependencyManagement> <dependencies> <!– spring-boot-start parent depdency definition –> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>${spring.boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies></dependencyManagement>配置文件类定义应用属性配置文件类RocketMQProperties,这个Bean定义一组默认的属性值。用户在使用最终的starter时,可以根据这个类定义的属性来修改取值,当然不是直接修改这个类的配置,而是spring-boot应用中对应的配置文件:src/main/resources/application.properties.定义自动加载类定义 src/resources/META-INF/spring.factories文件中的自动加载类, 其目的是让spring boot更具文中中所指定的自动化配置类来自动初始化相关的Bean,Component或Service,它的内容如下:org.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.apache.rocketmq.spring.starter.RocketMQAutoConfiguration在RocketMQAutoConfiguration类的具体实现中,定义开放给用户直接使用的Bean对象. 包括:RocketMQProperties 加载应用属性配置文件的处理类;RocketMQTemplate 发送端用户发送消息的发送模板类;ListenerContainerConfiguration 容器Bean负责发现和注册消费端消费实现接口类,这个类要求:由@RocketMQMessageListener注解标注;实现RocketMQListener泛化接口。最后具体的RocketMQ相关的封装在发送端(producer)和消费端(consumer)客户端分别进行封装,在当前的实现版本提供了对Spring Messaging接口的兼容方式。3.2. 消息发送端实现普通发送端发送端的代码封装在RocketMQTemplate POJO中,下图是发送端的相关代码的调用关系图:为了与Spring Messaging的发送模板兼容,在RocketMQTemplate集成了AbstractMessageSendingTemplate抽象类,来支持相关的消息转换和发送方法,这些方法最终会代理给doSend()方法;doSend()以及RocoketMQ所特有的一些方法如异步,单向和顺序等方法直接添加到RoketMQTempalte中,这些方法直接代理调用到RocketMQ的Producer API来进行消息的发送。事务消息发送端对于事务消息的处理,在消息发送端进行了部分的扩展,参考下图的调用关系类图:RocketMQTemplate里加入了一个发送事务消息的方法sendMessageInTransaction(), 并且最终这个方法会代理到RocketMQ的TransactionProducer进行调用,在这个Producer上会注册其关联的TransactionListener实现类,以便在发送消息后能够对TransactionListener里的方法实现进行调用。3.3. 消息消费端实现在消费端Spring-Boot应用启动后,会扫描所有包含@RocketMQMessageListener注解的类(这些类需要集成RocketMQListener接口,并实现onMessage()方法),这个Listener会一对一的被放置到DefaultRocketMQListenerContainer容器对象中,容器对象会根据消费的方式(并发或顺序),将RocketMQListener封装到具体的RocketMQ内部的并发或者顺序接口实现。在容器中创建RocketMQ Consumer对象,启动并监听定制的Topic消息,如果有消费消息,则回调到Listener的onMessage()方法。使用示例上面的一章介绍了RocketMQ在spring-boot-starter方式的实现,这里通过一个最简单的消息发送和消费的例子来介绍如何使这个rocketmq-spring-boot-starter。4.1 RocketMQ服务端的准备启动NameServer和Broker要验证RocketMQ的Spring-Boot客户端,首先要确保RocketMQ服务正确的下载并启动。可以参考RocketMQ主站的快速开始来进行操作。确保启动NameServer和Broker已经正确启动。创建实例中所需要的Topics在执行启动命令的目录下执行下面的命令行操作bash bin/mqadmin updateTopic -c DefaultCluster -t string-topic4.2. 编译rocketmq-spring-boot-starter目前的spring-boot-starter依赖还没有提交的Maven的中心库,用户使用前需要自行下载git源码,然后执行mvn clean install 安装到本地仓库。git clone https://github.com/apache/rocketmq-externals.gitcd rocketmq-spring-boot-startermvn clean install4.3. 编写客户端代码用户如果使用它,需要在消息的发布和消费客户端的maven配置文件pom.xml中添加如下的依赖:<properties> <spring-boot-starter-rocketmq-version>1.0.0-SNAPSHOT</spring-boot-starter-rocketmq-version></properties><dependency> <groupId>org.apache.rocketmq</groupId> <artifactId>spring-boot-starter-rocketmq</artifactId> <version>${spring-boot-starter-rocketmq-version}</version></dependency>属性spring-boot-starter-rocketmq-version的取值为:1.0.0-SNAPSHOT, 这与上一步骤中执行安装到本地仓库的版本一致。消息发送端的代码发送端的配置文件application.properties# 定义name-server地址spring.rocketmq.name-server=localhost:9876# 定义发布者组名spring.rocketmq.producer.group=my-group1# 定义要发送的topicspring.rocketmq.topic=string-topic发送端的Java代码import org.apache.rocketmq.spring.starter.core.RocketMQTemplate;…@SpringBootApplicationpublic class ProducerApplication implements CommandLineRunner { // 声明并引用RocketMQTemplate @Resource private RocketMQTemplate rocketMQTemplate; // 使用application.properties里定义的topic属性 @Value("${spring.rocketmq.springTopic}") private String springTopic; public static void main(String[] args){ SpringApplication.run(ProducerApplication.class, args); } public void run(String… args) throws Exception { // 以同步的方式发送字符串消息给指定的topic SendResult sendResult = rocketMQTemplate.syncSend(springTopic, “Hello, World!”); // 打印发送结果信息 System.out.printf(“string-topic syncSend1 sendResult=%s %n”, sendResult); }}消息消费端代码消费端的配置文件application.properties# 定义name-server地址spring.rocketmq.name-server=localhost:9876# 定义发布者组名spring.rocketmq.consumer.group=my-customer-group1# 定义要发送的topicspring.rocketmq.topic=string-topic消费端的Java代码@SpringBootApplicationpublic class ConsumerApplication { public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); }}// 声明消费消息的类,并在注解中指定,相关的消费信息@Service@RocketMQMessageListener(topic = “${spring.rocketmq.topic}”, consumerGroup = “${spring.rocketmq.consumer.group}")class StringConsumer implements RocketMQListener<String> { @Override public void onMessage(String message) { System.out.printf(”——- StringConsumer received: %s %f", message); }}这里只是简单的介绍了使用spring-boot来编写最基本的消息发送和接收的代码,如果需要了解更多的调用方式,如: 异步发送,对象消息体,指定tag标签以及指定事务消息,请参看github的说明文档和详细的代码。我们后续还会对这些高级功能进行陆续的介绍。预告:近期还会推出第二篇文章解读springboot框架下消息事务的用法。参考文档1.Spring Boot features-Messaging2.Enterprise Integration Pattern-组成简介3.Spring Cloud Stream Reference Guide4.https://dzone.com/articles/creating-custom-springboot-starter-for-twitter4j本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

November 29, 2018 · 2 min · jiezi

AspectJ在Spring中的使用

在上一篇AspectJ的入门中,简单的介绍了下AspectJ的使用,主要是以AspectJ的example作为例子。介绍完后也留下了几个问题:1)我们在spring中并没有看到需要aspectj之类的关键词,而是使用java代码就可以了,这是如何做到的2)Spring中如何做到不使用特殊的编译器实现aop的(AspectJ如何在运行期使用)3)Spring源码中与aspectJ 相关的AjType究竟是啥?这篇文章会继续试着解决这几个问题。aspectJ的几种织入方式compile-time、post-compile 和 load-time Weavers首先了解下AspectJ的几种织入方式,分别是compile-time、post-compile 和 load-time,分别对应着编译期、后编译期、加载期织入编译期织入首先是编译期织入,上一篇博客所介绍的方式就是使用的编译期织入。很容易理解,普通的java源码+ aspectJ特殊语法的‘配置’ 文件 + aspectJ特殊的编译器,编译时候生成已织入后的.class文件,运行时直接运行即可。后编译期织入后编译期织入和编译期的不同在于,织入的是class字节码或者jar文件。这种形式,可以织入一个已经织入过一次的切面。同样这种情况也需要特殊的编译器加载期织入加载期顾名思义,是在类被加载进虚拟机之前织入,使用这种方式,须使用AspectJ agent。了解了这些概念,下面就要知道,spring是使用哪种呢?spring哪一种都不是,spring是在运行期进行的织入。Spring 如何使用AspectJAspectJ 本身是不支持运行期织入的,日常使用时候,我们经常回听说,spring 使用了aspectJ实现了aop,听起来好像spring的aop完全是依赖于aspectJ其实spring对于aop的实现是通过动态代理(jdk的动态代理或者cglib的动态代理),它只是使用了aspectJ的Annotation,并没有使用它的编译期和织入器,关于这个可以看这篇文章 ,也就是说spring并不是直接使用aspectJ实现aop的spring aop与aspectJ的区别看了很多篇博客以及源码,我对spring aop与aspectJ的理解大概是这样;1)spring aop 使用AspectJ语法的一个子集,一些method call, class member set/get 等aspectJ支持的语法它都不支持2)spring aop 底层是动态代理,所以受限于这点,有些增强就做不到,比如 调用自己的方法就无法走代理看下下面的例子:@Componentpublic class A{ public void method1(){ method2(); } public void method2(){ //… }}这个时候method2是无法被切到的,要想被切到可以通过如下奇葩的方式:@Componentpublic class A{ @Autowired private A a; public void method1(){ a.method2(); } public void method2(){ //… }}之前碰到这样的问题时,我还特别不能理解,现在想下aop的底层实现方式就很容易理解了。在之前写的jdk动态代理与cglib动态代理实现原理,我们知道了jdk动态代理是通过动态生成一个类的方式实现的代理,也就是说代理是不会修改底层类字节码的,所以可能生成的代理方法是这样的public void method1(){ //执行一段代码 a.method1() //执行一段代码}public void method2(){ //执行一段代码 a.method2() //执行一段代码}回头看a.method1()的源码,也就明白了,为啥method2()没有被切到,因为a.method1()执行的方法,最后调用的不是 代理对象.method2(),而是它自己的method2()(this.method2()) 这个方法本身没有任何改动反观aspectJ,aspectJ是在编译期修改了方法(类本身的字节码被改了),所以可以很轻松地实现调用自己的方法时候的增强。3)spring aop的代理必须依赖于bean被spring管理,所以如果项目没有使用spring,又想使用aop,那就只能使用aspectJ了(不过现在没有用spring的项目应该挺少的吧。。。)4)aspectJ由于是编译期进行的织入,性能会比spring好一点5)spring可以通过@EnableLoadTimeWeaving 开启加载期织入(只是知道这个东西,没怎么研究。。有兴趣的可以自己去研究下)6)spring aop很多概念和aspectJ是一致的AspectJ的注解在spring aop中的应用了解了spring与aspectJ的关系后,就能更清晰的了解spring 的aop了。先说明一点,虽然我介绍aspect的配置时,一直介绍的aspectJ文件配置方式,但是aspectJ本身是支持注解方式配置的。可以看官方文档,注解在aspectJ中的使用而spring 使用了aspectJ注解的一小部分(正如前面所说的,受限于jdk的动态代理,spring只支持方法级别的切面)回头看看AjType回头看看之前看到的这段源码,什么是AjType,经过aspectJ解析器解析后对类的一种描述,比如正常的方法可能是这样/* * 配置前置通知,使用在方法aspect()上注册的切入点 * 同时接受JoinPoint切入点对象,可以没有该参数 /@Before(“aspect()")public void before(JoinPoint joinPoint) { log.info(“before " + joinPoint);}在AjType中就能获取到很多其他的aspectJ所需的相关信息(除了java反射所能获取到的信息以外)/* * Return the pointcut object representing the specified pointcut declared by this type /public Pointcut getDeclaredPointcut(String name) throws NoSuchPointcutException;/* * Return the pointcut object representing the specified public pointcut */public Pointcut getPointcut(String name) throws NoSuchPointcutException;比如看着两个方法,可以获取到切入点信息。在看看PerClauseKind.SINGLETON 这里就复用了aspectJ的概念,详细可以看这篇文章最后部分总结下这篇文章回答了之前学习aspectJ时候碰到的几个问题,然后讨论了下aspectJ在spring中的应用。最大的收获是了解了spring与aspectJ 的关系,了解了两者对aop的不同实现所造成的使用上的影响。以后当遇到了spring aop相关的概念如果不理解,可以去aspectJ上去搜搜看了 。参考文章:Intro to AspectJspring 使用 load-time weavingspring aop和 aspectJ 的比较本文作者:端吉阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

November 22, 2018 · 1 min · jiezi

Centos7 防火墙 firewalld 实用操作

一.前言Centos7以上的发行版都试自带了firewalld防火墙的,firewalld去带了iptables防火墙。其原因是iptables的防火墙策略是交由内核层面的netfilter网络过滤器来处理的,而firewalld则是交由内核层面的nftables包过滤框架来处理。 相较于iptables防火墙而言,firewalld支持动态更新技术并加入了区域(zone)的概念。简单来说,区域就是firewalld预先准备了几套防火墙策略集合(策略模板),用户可以根据生产场景的不同而选择合适的策略集合,从而实现防火墙策略之间的快速切换。区域对于 firewalld 来说是一大特色,但是对于我们使用Centos7一般是在服务器上,需要切换zone的需求比较少,所以本文不做介绍了,网上资料也比较多,大家可以去百度找找资料。二.操作与配置1.服务操作启动服务:systemctl start firewalld这里不用担心启用了防火墙以后无法通过ssh远程,22端口默认加入了允许规则停止服务:systemctl stop firewalld重启服务:systemctl restart firewalld查看服务状态:systemctl status firewalld2.配置文件说明firewalld 存放配置文件有两个目录,/usr/lib/firewalld 和 /etc/firewalld,前者存放了一些默认的文件,后者主要是存放用户自定义的数据,所以我们添加的service或者rule都在后者下面进行。server 文件夹存储服务数据,就是一组定义好的规则。zones 存储区域规则firewalld.conf 默认配置文件,可以设置默认使用的区域,默认区域为 public,对应 zones目录下的 public.xml三.命令这里需要首先说明的是,在执行命令时,如果没有带 –permanent 参数表示配置立即生效,但是不会对该配置进行存储,相当于重启服务器就会丢失。如果带上则会将配置存储到配置文件,,但是这种仅仅是将配置存储到文件,却并不会实时生效,需要执行 firewall-cmd –reload 命令重载配置才会生效。1.重载防火墙配置firewall-cmd –reload2.查看防火墙运行状态firewall-cmd –state3.查看默认区域的设置firewall-cmd –list-all4.应急命令firewall-cmd –panic-on # 拒绝所有流量,远程连接会立即断开,只有本地能登陆firewall-cmd –panic-off # 取消应急模式,但需要重启firewalld后才可以远程sshfirewall-cmd –query-panic # 查看是否为应急模式5.服务firewall-cmd –add-service=<service name> #添加服务firewall-cmd –remove-service=<service name> #移除服务6.端口firewall-cmd –add-port=<port>/<protocol> #添加端口/协议(TCP/UDP)firewall-cmd –remove-port=<port>/<protocol> #移除端口/协议(TCP/UDP)firewall-cmd –list-ports #查看开放的端口7.协议firewall-cmd –add-protocol=<protocol> # 允许协议 (例:icmp,即允许ping)firewall-cmd –remove-protocol=<protocol> # 取消协议firewall-cmd –list-protocols # 查看允许的协议8.允许指定ip的所有流量firewall-cmd –add-rich-rule=“rule family=“ipv4” source address="<ip>” accept"例:firewall-cmd –add-rich-rule=“rule family=“ipv4” source address=“192.168.2.1” accept” # 表示允许来自192.168.2.1的所有流量9.允许指定ip的指定协议firewall-cmd –add-rich-rule=“rule family=“ipv4” source address="<ip>” protocol value="<protocol>" accept"例:firewall-cmd –add-rich-rule=“rule family=“ipv4” source address=“192.168.2.208” protocol value=“icmp” accept” # 允许192.168.2.208主机的icmp协议,即允许192.168.2.208主机ping10.允许指定ip访问指定服务firewall-cmd –add-rich-rule=“rule family=“ipv4” source address="<ip>” service name="<service name>" accept"例:firewall-cmd –add-rich-rule=“rule family=“ipv4” source address=“192.168.2.208” service name=“ssh” accept” # 允许192.168.2.208主机访问ssh服务11.允许指定ip访问指定端口firewall-cmd –add-rich-rule=“rule family=“ipv4” source address="<ip>” port protocol="<port protocol>" port="<port>" accept"例:firewall-cmd –add-rich-rule=“rule family=“ipv4” source address=“192.168.2.1” port protocol=“tcp” port=“22” accept” # 允许192.168.2.1主机访问22端口12.将指定ip改为网段8-11 的各个命令都支持 source address 设置为网段,即这个网段的ip都是适配这个规则:例如:firewall-cmd –zone=drop –add-rich-rule=“rule family=“ipv4” source address=“192.168.2.0/24” port protocol=“tcp” port=“22” accept"表示允许192.168.2.0/24网段的主机访问22端口 。13.禁止指定ip/网段8-12 各个命令中,将 accept 设置为 reject 表示拒绝,设置为 drop表示直接丢弃(会返回timeout连接超时)例如:firewall-cmd –zone=drop –add-rich-rule=“rule family=“ipv4” source address=“192.168.2.0/24” port protocol=“tcp” port=“22” reject"表示禁止192.168.2.0/24网段的主机访问22端口 。四.参考资料firewalld防火墙详解 by xuad88.本文作者:晓晨master阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

November 13, 2018 · 1 min · jiezi