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

阿里巴巴在应用性能测试场景设计和实现上的实践

本文是《Performance Test Together》(简称PTT)系列专题分享的第5期,该专题将从性能压测的设计、实现、执行、监控、问题定位和分析、应用场景等多个纬度对性能压测的全过程进行拆解,以帮助大家构建完整的性能压测的理论体系,并提供有例可依的实战。 该系列专题分享由阿里巴巴 PTS 团队出品,欢迎在文末处加入性能压测交流群,参与该系列的线上分享。 第1期:《压测环境的设计和搭建》 第2期:《性能压测工具选型对比》 第3期:《阿里巴巴在开源压测工具 JMeter 上的实践和优化》 第4期:《并发模式与 RPS 模式之争,性能压测领域的星球大战》 本文将介绍应用性能测试场景的设计和实现,旨在借助阿里巴巴在这方面的沉淀帮助您更准确的找到性能瓶颈,文章将围绕以下: 性能测试的常见分类应用性能测试场景的设计应用性能测试场景的设计实践应用性能测试场景的实现性能测试的常见分类负载测试:一种验证性测试,它的目的是验证预设负载条件下的性能表现是否达到性能目标(可用性、并发数/RPS、响应时间等),在达到性能目标之后不会继续增加负载。 稳定性测试:负载测试的一个子集,侧重于发现、验证只有经过长时间的运行才会暴露的问题。比如内存泄漏、FGC 等。 压力测试:一种破坏性测试,尝试探测应用或者基础设施的极限能力。因此压力测试过程中会一直增加负载直到部分性能指标不再符合性能预期。压力测试能发现仅在高负载条件下出现的同步问题、竞争条件、内存泄漏等。通过压力测试我们还可以确定应用服务在什么条件下会变得不可用,不可用的现象,以及可以通过哪些监控指标来监控即将发生的不可用,压测结果通常可以为限流等管控系统提供数据支撑。 容量测试:往往与容量规划一起进行,是在保证用户体验不受影响(稳定性)的前提下,使有限的资源的利用率最大化(成本)。也可以用它来预估未来用户量增长到某个量级的情况下,需要多少资源(例如处理器、内存、磁盘、网络带宽)来支持。 应用性能测试场景的设计在了解了相关背景之后,我们开始进入正题。性能场景的设计主要包括:业务场景建模、测试数据准备、监控指标确认三个关键步骤。下面我们用实战的方式说明每个步骤的常见做法。 业务场景建模确定压测场景范围:人类是不可预测的,在性能测试中模拟每个用户可能的操作场景基本上是不可能实现的。一般情况下我们必须要关注的性能场景包括但不限于: 高频使用的场景关键的业务场景最耗性能的场景曾经出现过问题的场景……在测试具有大量新功能的业务时,往往需要与业务方一起确认预期内有哪些功能点可能会被高频使用,需要与研发人员确认哪些功能虽然使用频率不高,但是存在性能隐患、容易引起雪崩效应;在测试已经上线的功能时,还可以通过业务监控、系统日志来分析现有用户的行为模式,得到更加逼近真实用户行为的业务场景。 业务场景的操作路径:业务场景的操作路径可以借助一些可视化的工具来描述,这部分工作相对比较简单,不再详细深入。我们详细说明一下比较常见的延时策略。 思考时间:思考时间模拟的是用户在等待响应、阅读页面内容、表单填写等延迟操作的场景。每个人的阅读速度、输入速度都存在非常大的差异,决定了每个人的思考时间也是不一样的,在性能测试配置中有常见的四种延时模型覆盖了绝大部分的延时场景: 固定时间:顾名思义,设置一个固定的思考时间。均匀分布:均匀分布在范围的上限和下限之间的随机数。正态分布:根据中心极限定理,如果一个事物受到多种因素的影响,不管每个因素本身是什么分布,它们加总后,结果的平均值就是正态分布。负指数分布:该模型将延迟时间的频率强烈地偏向该范围的一端。双驼峰正态分布:双峰驼正态分布可以模拟第一次访问时把页面说明整个仔细的阅读一遍,但下次访问时直接扫过页面,点击页面深处的操作链接。我们通常可以通过以下方式对思考时间进行建模: 如果是已上线系统,可以从线上日志统计分析出来平均值以及标准方差没有线上日志,可以从内部人员的使用模式中收集响应的数据可以计算自己和同事访问的时候,在不同页面停留的时间如果没有更好的来源,也可以从第三方统计数据获取延时数据集合点集合点模拟的是大量的用户在同一时刻一起做同样的操作(加购、付款等),集合的方式通常包括按时间集合和按量集合。一般只有具备秒杀特性的业务才会使用到。虽然直接在压测工具中设置巨大的起步量级看似也能模拟秒杀的行为,但是压测工具一般都存在一个不太稳定的预热的过程,因此不推荐超高的起步量级模拟秒杀。 确定场景的施压参数施压模式:常见的施压模式有以下两种, 并发模式与 RPS 模式没有优劣,各自有各自适用的场景。 1、并发模式(虚拟用户模式)并发是指虚拟并发用户数,从业务角度,也可以理解为同时在线的用户数。如果需要从客户端的角度出发,摸底业务系统各节点能同时承载的在线用户数,可以使用该模式设置目标并发。 2、RPS 模式(吞吐量模式)RPS(Requests Per Second)是指每秒请求数。RPS 模式即“吞吐量模式”,通过设置每秒发出的请求数,从服务端的角度出发,直接衡量系统的吞吐能力,免去并发到 RPS 的繁琐转化,一步到位。 目标量级:目标量级往往来自于对项目计划、目标,业务方要求,或者是技术文档的量化。 场景的负载占比:已上线应用,尽量使用线上的日志、埋点数据结合业务运营的预期目标确保分配比例尽可能的符合实际情况;新上线的应用一般依靠事前预期分配虚拟用户,在测试的执行过程中可以逐步的调整。 测试数据准备高质量的测试数据应当能真实的反映用户的使用场景。我们一般会选择以线上真实数据作为数据源,经过采样、过滤、脱敏,作为性能测试的测试数据。低质量的测试数据也许能够测试出一些问题,但是更大的可能性是无效的测试结果。压测数据至少包括基础数据和运行时数据两种。基础数据,主要是应用系统存储的元数据,比如用户信息、产品信息、商品信息等;基础数据的数据量、数据分布应当与线上运行的数据量相当,否则容易引起无效测试。运行时数据,主要是虚拟用户操作过程中需要使用的表单数据,比如虚拟用户的用户名、密码、搜索关键词等;运行数据的逼真度也是至关重要的。 确认监控指标在性能测试执行过程中,往往需要实时观察各项指标是否正常,包括客户端指标、应用服务器、数据库、中间件、网络入口等各方面的指标。更重要的是,监控的过程是发现系统瓶颈的过程,监控数据是性能基线管理、容量规划甚至是高可用架构的重要基础。我们通常需要关注的监控指标包括: 业务接口指标,响应时间、RPS、成功率等;网络指标,数据吞吐量、数据错误率等;服务器指标,连接数、CPU、内存、I/O、磁盘等;……最理想的状态是,这些监控指标能够与性能测试工具集成,在一个操作界面上展示各个维度的监控数据,并能够基于策略来智能化、自动化识别指标异常。这对快速、准确的定位压测过程中可能出现的各种问题是至关重要的。 应用性能测试场景设计的实践JPetStore 是一个开源的简单的Java语言开发的在线宠物商店,可以从 GitHub 获取到源码。为了方便演示,我们用阿里云 EDAS 部署了一套 JPetStore 宠物购物网站。 业务场景建模在这次的实战演示中,我们通过实际操作体验的方式来获取所有的业务场景、操作路径、思考时间。我们先用文字的方式来描述场景和操作路径。 用户登录,访问首页->进登录页->登录操作购买流程1,访问首页->选择产品大类->进入产品列表->进入型号列表->查看型号详情->加购物车->思考(3s-5s)->提交订单->确认订单购买流程2,访问首页->搜索产品->进入产品列表->进入型号列表->查看型号详情>加购物车->思考(3s-5s)->提交订单->确认订单购买流程3,访问首页->搜索商品->进入产品列表->进入型号列表->加购物车->思考(3s-5s)->提交订单->确认订单我们的目的是做压力测试。我们选择 RPS 模式,梯度递增,漏斗模型; 与并发模式相比,RPS 模式可以实现更加精准的流量控制;常见的限流设施都是基于TPS设置阈值的;因此我们首选 RPS 模式。我们使用手动递增的方式,逐步的逼近系统极限。在真实的业务中,用户会由于各种原因(网络、库存、不喜欢、付款失败等)而放弃购买,在此我们构造一个漏斗模型,我们假定100个人查看详情之后有30个人加入了购物车,15个人提交订单,最终10个人确认订单,购买成功;在真实的场景中我们可以从线上用户行为中采集到这些信息。假定用户登录容量足够,不是这次压力测试的重点业务。我们基于线上日志和产品运营分析得出以下结论: 使用购买流程1的用户占比10%使用购买流程2的用户占比60%使用购买流程3的用户占比为30%最终,我们得到的业务模型如下图所示: 测试数据准备因为该应用专为测试而生,不用考虑数据污染,我们免去采样、过滤、脱敏步骤,直接使用线上的基础数据作为压测的基础数据。基础数据的结构如下图所示,当然真实系统的基础数据,比这个复杂的多: 常见的压测工具都支持 CSV 格式(可以简单理解为逗号分隔值,但是实际上更加复杂)的数据源。我们构造的用户登录的运行时数据格式如下图所示: ...

August 20, 2019 · 1 min · jiezi

DLedger-基于-raft-协议的-commitlog-存储库

“点击获取上云帮助文档” 尊敬的阿里云用户: 您好!为方便您试用开源 RocketMQ 客户端访问阿里云MQ,我们申请了专门的优惠券,优惠券可以直接抵扣金额。请填写下您公司账号信息,点击上图,了解更多哦。 一、DLedger引入目的 在 RocketMQ 4.5 版本之前,RocketMQ 只有 Master/Slave 一种部署方式,一组 broker 中有一个 Master ,有零到多个 Slave,Slave 通过同步复制或异步复制的方式去同步 Master 数据。Master/Slave 部署模式,提供了一定的高可用性。 但这样的部署模式,有一定缺陷。比如故障转移方面,如果主节点挂了,还需要人为手动进行重启或者切换,无法自动将一个从节点转换为主节点。因此,我们希望能有一个新的多副本架构,去解决这个问题。 新的多副本架构首先需要解决自动故障转移的问题,本质上来说是自动选主的问题。这个问题的解决方案基本可以分为两种: 利用第三方协调服务集群完成选主,比如 zookeeper 或者 etcd。这种方案会引入了重量级外部组件,加重部署,运维和故障诊断成本,比如在维护 RocketMQ 集群还需要维护 zookeeper 集群,并且 zookeeper 集群故障会影响到 RocketMQ 集群。利用 raft 协议来完成一个自动选主,raft 协议相比前者的优点是不需要引入外部组件,自动选主逻辑集成到各个节点的进程中,节点之间通过通信就可以完成选主。因此最后选择用 raft 协议来解决这个问题,而 DLedger 就是一个基于 raft 协议的 commitlog 存储库,也是 RocketMQ 实现新的高可用多副本架构的关键。 二、DLedger 设计理念1. DLedger 定位 Raft 协议是复制状态机的实现,这种模型应用到消息系统中就会存在问题。对于消息系统来说,它本身是一个中间代理,commitlog 状态是系统最终状态,并不需要状态机再去完成一次状态构建。因此 DLedger 去掉了 raft 协议中状态机的部分,但基于raft协议保证commitlog 是一致的,并且是高可用的。 另一方面 DLedger 又是一个轻量级的 java library。它对外提供的 API 非常简单,append 和 get。Append 向 DLedger 添加数据,并且添加的数据会对应一个递增的索引,而 get 可以根据索引去获得相应的数据。因此 DLedger 是一个 append only 的日志系统。 ...

August 8, 2019 · 2 min · jiezi

记一次代码重构

单一职责功能单一功能单一是SRP最基本要求,也就是你一个类的功能职责要单一,这样内聚性才高。 比如,下面这个参数类,是用来查询网站Buyer信息的,按照SRP,里面就应该放置查询相关的Field就好了。 @Datapublic class BuyerInfoParam { // Required Param private Long buyerCompanyId; private Long buyerAccountId; private Long callerCompanyId; private Long callerAccountId; private String tenantId; private String bizCode; private String channel; //这个Channel在查询中不起任何作用,不应该放在这里}可是呢? 事实并不是这样,下面的三个参数其实查询时根本用不到,而是在组装查询结果的时候用到,这给我阅读代码带来了很大的困惑,因为我一直以为这个channel(客户来源渠道)是一个查询需要的一个重要信息。 那么如果和查询无关,为什么要把它放到查询param里面呢,问了才知道,只是为了组装查询结果时拿到数据而已。 所以我重构的时候,果断把查询没用到的参数从BuyerInfoParam中移除了,这样就消除了理解上的歧义。 Tips:不要为了图方便,而破坏SOLID原则,方便的后果就是代码腐化,看不懂,往后要付出的代价更高。 功能内聚在类的职责单一基础之上,我们还要识别出是不是有功能相似的类或者组件,如果有,是不是要整合起来,而不要让功能类似的代码散落在多处。 比如,代码中我们有一个TenantContext,而build这个Context统一是在ContextPreInterceptor中做的,其中Operator的值一开始只有crmId,但是随着业务的变化operator的值在不同的场景值会不一样,可能是aliId,也可能是accountId。 这样就需要其它id要转换成crmId的工作,重构前这个转换工作是分散在多个地方,不满足SRP。 //在BaseMtopServiceImpl中有crmId转换的逻辑 public String getCrmUserId(Long userId){ AccountInfoDO accountInfoDO = accountQueryTunnel.getAccountDetailByAccountId(userId.toString(), AccountTypeEnum.INTL.getType(), false); if(accountInfoDO != null){ return accountInfoDO.getCrmUserId(); } return StringUtils.EMPTY; } //在ContextUtilServiceImpl中有crmId转换的逻辑 public String getCrmUserIdByMemberSeq(String memberSeq) { if(StringUtil.isBlank(memberSeq)){ return null; } MemberMappingDO mappingDO = memberMappingQueryTunnel.getMappingByAccountId(Long.valueOf(memberSeq)); if(mappingDO == null || mappingDO.getAliId() == null){ return null; } }重构的做法是将build context的逻辑,包括前序的crmId的转换逻辑,全部收拢到ContextPreInterceptor,因为它的职责就是build context,这样代码的内聚性,可复用性和可读性都会好很多。 ...

August 7, 2019 · 4 min · jiezi

测试之HTTP协议脑图

在做接口测试的过程中特别是在面试的过程中,都会遇到一些HTTP协议的基础知识问题。一般来说一开始做接口测试并不需要对HTTP协议了解很清楚,但是随着测试的深入,自然而然地会遇到各类各样的问题,如果前期知识储备不足,就需要抓紧时间恶补了。 我属于遇到问题,发现不足,然后恶补的,对于HTTP协议的基础,我十分推荐一本书《图解HTTP》这本书,通俗易懂,才用漫画+文字的形式,更加形象。 下面内容摘自豆瓣: 本书对互联网基盘——HTTP协议进行了全面系统的介绍。作者由HTTP协议的发展历史娓娓道来,严谨细致地剖析了HTTP协议的结构,列举诸多常见通信场景及实战案例,最后延伸到Web安全、最新技术动向等方面。本书的特色为在讲解的同时,辅以大量生动形象的通信图例,更好地帮助读者深刻理解HTTP通信过程中客户端与服务器之间的交互情况。读者可通过本书快速了解并掌握HTTP协议的基础,前端工程师分析抓包数据,后端工程师实现REST API、实现自己的HTTP服务器等过程中所需的HTTP相关知识点本书均有介绍。在看完这本书之后,本着传播知识共同进步的精神,自己写了一个脑图,分享出来供大家参考。 欢迎有兴趣的童鞋一起交流

July 26, 2019 · 1 min · jiezi

实例解说AngularJS在自动化测试中的应用

7月25日晚8点,线上直播,【AI中台——智能聊天机器人平台】,点击了解详情。 一、什么是AngularJS ?1、AngularJS是一组用来开发web页面的框架、模板以及数据绑定和丰富UI的组件; 2、AngularJS提供了一系列健壮的功能,以及将代码隔离成模块的方法; 3、AngularJS于2009年发布第一个版本,由Google进行维护,压缩版94k。 二、AngularJS的核心思想 1、在AngularJS中通过数据视图双向绑定实现视图与业务逻辑解耦,这将提高代码的可测试性; 2、遵循MVC模式开发,鼓励视图、数据、逻辑组件间松耦合; 3、将测试与应用程序编写放在同等重要的位置,在编写模块的同时编写测试。因为各组件的松耦合,使得这种测试得以实现; 4、 应用程序页面端与服务器端解耦。两方只需定义好通信API,即可并行开发。 三、简单的栗子问题:假设我们需要编写一个手机列表,支持对手机信息进行模糊搜索,且按指定字段排序,要怎么实现呢? 如上图所示,几乎没有DOM操作,更专注于业务逻辑! 下面编写HTML 编写控制器Controller PhoneListCtrl 控制器。例子中注入了$scope(数据模型)、$http(封装了ajax的服务)这两个服务都是angularjs内置服务,服务是可以自定义的。 $scope.phones = data; 在这个地方后台返回的数据应用到了数据模型中,这时前台UI会自动响应更新。 四、指令4.1 什么是指令?指令是AngularJS用来扩展浏览器能力的技术之一。在DOM编译期间,和HTML关联着的指令会被检测到,并且被执行。这使得指令可以为DOM指定行为或者改变DOM的结构。例如ng-controller、ng-src、ng-model等。 4.2 AngularJS的编译 4.3 简单的AngularJS指令写法自定义指令的一般格式: angular.application(‘myApp’, []).directive(‘myDirective’,function(){//一个指令定义对象return{ }; //通过设置项来定义指令,在这里进行覆写});下面我们来看一个简单的自定义指令的例子: module:这个方法将新建一个模块。AngularJS以模块管理代码。directive:在模块中新建指令,指定的方法在编译步骤会被执行,执行后返回一个自定义的链接函数,这个链接函数在完成双向绑定后执行。Restrict:它告诉AngularJS这个指令在DOM中可以何种形式被声明。E(元素), A(属性,默认值), C(类名)。scope :可以被设置为true或一个对象。默认值是false。当scope设置为true时,会从父作用域继承并创建一个新的作用域对象。有三种绑定策略@ = &。Template:一段HTML文本,或一个可以接受两个参数的函数,参数为tElement和tAttrs,并返回一个代表模板的字符串。4.4 使用指令 ng-app="MyModule":在angularjs启动时指定初始化的模块(module)。当前指定的是自定义的模块。drink water="{{pureWater}}":调用自定义的drink指令,将$scope中的pureWater属性赋值给指令中的water属性。drink可以是一个属性,也可以是一个标签。五、模块和服务在AngularJS中,模块负责组织、启动、实例化应用。 模块的两个部分,一个是配置块,另一个是运行块。 配置块:在实例工厂(provider)注册和配置阶段运行。只有工厂、常量才可以注入到配置块中(常量的配置要放在前面); 运行块:注入器(injector)被创建后执行,被用来启动应用。实例和常量、变量等都能被注入。 AngularJS应用中的服务是一些用依赖注入捆绑在一起的、可替换的对象。这些对象可以提供一些封装好的逻辑操作,以供调用。 AngularJS内置了很多有用的服务,例如前面提到的$timeout、$http等,我们可以通过使用内置服务完成大部分业务逻辑。但很多时候我们还需要自定义服务: 服务的使用 上图的代码中定义了一个服务notify,它依赖另外一个服务$window。$window中封装了window对象的方法,定义了一个控制器myController,并为这个控制器注入了notify服务,同时在控制器的scope中定义了一个方法callNotify来调用服务。$inject是依赖注入的一种方式,请参看下文依赖注入章节。 六、依赖注入我们可以将需要的服务比作一件工具,比如一把锤子,那我们要怎么获得锤子呢? 第一种方法:自己打造一把锤子。如果锤子的工艺改变了,我们就需要重新制造。相当于我们在程序中new了一个服务,服务的实现改变时,只能修改代码,这将产生风险。 第二种方法:我们找到一间工厂,告诉工厂锤子的型号,然后工厂为我们制造。这时候就不需要关系锤子是怎么做的,我们只管使用。但是这种方式还是很麻烦,我们需要知道工厂在哪。类似于在代码中通过工厂方法获取我们想要的服务。这种方会对工厂产生依赖。 第三种方法:我们在门前贴张单子,声明我们需要一把什么型号的锤子,第二天就有人默默地送来了一把锤子。这在现实生活中简直是痴心妄想,但这种方式确实很轻松,不需要考虑任何东西,我们只关注使用锤子。这就是程序里的依赖注入。只要声明了需要什么,在使用的时候就可以得到什么。 6.1 AngularJS中的依赖注入第一种方式:通过方法参数名声明依赖。这种方式不推荐使用,因为js文件压缩后方法参数名会改变。 第二种方式:声明一个数组,依赖列表放数组的前部,注入目标放数组最后一个元素。推荐使用这个方法。 第三种方式:通过$inject属性来声明依赖列表。 ...

July 16, 2019 · 1 min · jiezi

MongoDB-sharding-集合不分片性能更高

最近云上用户用户遇到一个 sharding 集群性能问题的疑惑,比较有代表性,简单分享一下 测试配置mongos x 2、shard x 3测试1:集合不开启分片,批量 insert 导入数据,每个 batch 100 个文档测试2:集合开启分片,随机生成 shardKey,chunk 已提前 split 好,能确保写入均分到3个shard测试结果测试1:单个 shard cpu 跑满,insert qps 在 6w 左右测试2:3个 shard cpu 跑满,insert qps 在 7w 左右(平均每个分片2.4w左右)注:两个测试里,mongos 都不是瓶颈,能力足够 从测试结果看,每个shard都承担 1/3 的负载,的确达到横向扩张的目的,但为啥分片之后,单个shard的能力就下降了呢?如果是这样,sharding的扩展能力如何体现? 结果分析这里核心的问题在于 batch insert 在 mongos 和 mongod 上处理行为的差别 导入数据时,一次 insert 一条数据,和一次 insert 100 条数据,性能差距是很大的;首先减少了client、server 端之间的网络交互;同时 server 可以将 batch insert 放到一个事务里,降低开销;mongos 在收到 batch insert 时,因为一个 batch 里的数据需要根据 shardKey 分布到不同的shard,所以一个 batch 实际上需要被拆开的;这里 mongos 也做了优化,会尽量将连续的分布在一个shard上的文档做 batch 发到后端 shard。在集合不开启分片的情况,mongos 收到的 batch 肯定是转发给 primary shard,所以转发过去还是一整个 batch 操作; 而在集合开启分片的情况下,因为用户测试时,shardKey 是随机生成的,基本上整个 batch 被打散成单条操作,逐个往后端 shard 上发送,请求到后端 shard 基本已经完全没有合并了。所以在上述测试中,不分片的单个 shard 6w qps、与分片后每个 shard 2.4w qps,实际上就是请求是否 batch 执行的差别。 ...

July 11, 2019 · 1 min · jiezi

JVMSANDBOX从阿里精准测试走出的开源贡献奖

阿里妹导读:稳定性是历年双11的技术质量保障核心。从 2016 年开始淘宝技术质量部潜心修行,创新地研发了一套实时无侵入的字节码增强框架,于是「JVM-SANDBOX」诞生了,并且顺手在 MTSC 大会上拿了开源贡献奖,今天,我们来瞅瞅这个拿奖的项目。 在近日举行的中国移动互联网测试开发大会(简称MTSC大会),来自淘系技术质量开源项目「JVM-SANDBOX」以及淘系同学参与维护的「 ATX」 包揽了 MTSC 2019 年度开源贡献奖,表彰过去一年在测试领域开源项目中的突出贡献。其中,「JVM-SANDBOX」致力于为服务端稳定性领域提供实时无侵入的字节码增强框架。 一、JVM-Sandbox的诞生功能回归、业务/系统监控、问题排查定位、强弱依赖、故障演练等是阿里 10 年双十一沉淀积累下来的稳定性专项,也是历年双十一质量保障的核心要素。要有效、轻量级地实现这些稳定性专项,都会触及到一块底层技术—— java 字节码增强。如果每个专项都能自己实现一套字节码增强逻辑,实现的门槛高、投入和维护成本高,且不同专项间相互影响造成不可预知的风险。如何屏蔽字节码增强技术的高门槛,降低成本,同时又能支持上层多个专项功能的快速实现和动态管理,成为淘宝技术质量部的目标。从 2016 年开始我们潜心修行,创新地研发了一套实时无侵入的字节码增强框架,于是 「JVM-SANDBOX」 诞生了。 对上面提到的专项进行抽象分析: 故障演练:在运行前,抛出异常或增加运行时间,即:干预方法的执行顺序和改变返回值;强弱依赖梳理:系统运行时,实时记录系统的对外调用情况,即:感知方法的入参和返回值;录制回放:运行时,记录方法的入参和返回值,回放时,不真实对外调用,而是直接返回录制时的返回值。即:感知方法入参和返回值,干预方法执行顺序,改变返回值;精准回归:获取每个请求的行调用链路,根据行调用链路进行场景去重,根据代码改动的情况和行调用链路确定需要回归范围,即:运行时行链路感知。不难发现,要解决这些问题本质就是如何完成 java 方法的环绕管控和运行时行链路的获取,即 AOP 框架的解决方案。目前常用 AOP 框架的解决方案有两种:proxy 和埋点。proxy 的优点在于已实现了统一的 API,减少了重复投入,但是不能实时生效,需要系统编译重启。埋点的优点在于动态生效灵活度高,但是没有统一 API。 要快速解决上边的三个问题,我们需要的 AOP 解决方案必须具备两个特性: 动态可插拔,即实现埋点方式的统一的 API;无侵入性,即解决 JVM 类隔离的问题。基于以上需求,我们研发了 JVM-Sandbox。 二、实现方式JVM-Sandbox 由纯 Java 编码完成,基于 JVMTI 技术规范,为观察和改变代码运行结果提供了即插即用模块接口的容器,提供两个核心功能:实时无侵入 AOP 框架和动态可插拔的模块管理容器。 2.1 JVM-Sandbox的核心功能 使用埋点技术提供统一的 API,来实现无需重启的 AOP 解决方案;使用容器完成 JVM 类隔离,来解决侵入性问题;提供容器管理机制,来完成各种容器的管理。2.2 JVM—Sandbox的核心事件模型 BEFORE、RETURN 和 THROWS 三个环节事件的正常流转和干预流转。 2.3 整体架构 ...

July 5, 2019 · 1 min · jiezi

容器十年-一部软件交付编年史

作者| 张磊,阿里云容器平台高级技术专家,CNCF Ambassador (CNCF 官方大使),Kubernetes 项目资深成员与维护者,曾就职于 Hyper、微软研究院(MSR),现在负责 Kubernetes 技术及上下游相关工作。 2019年,全世界的开发人员都开始习惯用容器测试自己的软件,用容器做线上发布,开始对容器化的软件构建和交付流程习以为常。全世界的架构师们都在对“云原生”侃侃而谈,描绘多云时代的应用治理方式,不经意间就把 “sidecar” 这种容器组织方式当做了默认选项。在“云”已经成为了大众基础设施的今天,我们已经习惯了把“容器“当做现代软件基础设施的基本依赖。这就像我们每天打开 Eclipse 编写 Java 代码一样自然。 但往回倒数两年, 整个容器生态都还在围着 Docker 公司争得不可开交,看起来完全没有定数。当时的国内很多公有云厂商,甚至都没有正式的 Kubernetes 服务。在那个时候,要依托容器技术在云上托管完整的软件生命周期,可以说是相当前沿的探索。谁能想到短短两年之后,容器这个站在巨人肩膀上的设计,就真的成为技术人员日常工作的一部分呢? 伴随着容器技术普及到“千家万户”,我们在这两年期间所经历的,是现代软件交付形式的一次重要变革。 源起:属于 Jails 们的时代虚拟化容器技术(virtualized container)的历史,其实可以一直追溯上世纪 70 年代末。时间回到 1979 年,贝尔实验室( Bell Laboratories 正在为 Unix V7 (Version 7 Unix)操作系统的发布进行最后的开发和测试工作。 Ken Thompson(sitting) and Dennis Ritchie at PDP-11 ©wikipedia 在那个时候,Unix 操作系统还是贝尔实验室的内部项目,而运行 Unix 的机器则是长得像音响一样的、名叫 PDP 系列的巨型盒子。在那个“软件危机(The Software Crisis)”横行的末期,开发和维护 Unix 这样一套操作系统项目,即使对贝尔实验室来说也绝非易事。更何况,那里的程序员们还得一边开发 Unix ,一边开发 C 语言本身呢。 而在 Unix V7 的开发过程中,系统级别软件构建(Build)和测试(Test)的效率其实是其中一个最为棘手的难题。这里的原因也容易理解:当一个系统软件编译和安装完成后,整个测试环境其实就被“污染”了。如果要进行下一次构建、安装和测试,就必须重新搭建和配置整改测试环境。在有云计算能力的今天,我们或许可以通过虚拟机等方法来完整的复现一个集群。但在那个一块 64K 的内存条要卖 419 美元的年代,“快速销毁和重建基础设施”的想法还是有点“科幻”了。 ...

July 3, 2019 · 3 min · jiezi

使用Quick-BI连接AnalyticDB-for-PostgreSQL数据源

本文介绍如何通过阿里云Quick BI连接AnalyticDB for PostgreSQL数据库。 在Quick BI中新建AnalyticDB for PostgreSQL数据源登录Quick BI控制台。单击工作空间>数据源,进入数据源管理页面。单击新建数据源>AnalyticDB for PostgreSQL。在添加AnalyticDB for PostgreSQL数据源页面进行参数配置。 配置项说明显示名称数据源名称。数据库地址AnalyticDB for PostgreSQL的连接地址 。端口链接地址对应的端口号。数据库数据库名。Schema数据库Schema名。用户名AccessKey ID。密码Access Key Secret。完成上述参数配置后,单击连接测试测试连通性,测试通过后,单击添加添加数据源。使用Quick BI成功连接AnalyticDB for PostgreSQL数据源后,用户可以参考以下步骤学习如何在Quick BI中完成报表分析等操作。 创建数据集制作仪表板制作电子表格制作数据门户关于Quick BI的更过功能,请参见Quick BI相关文档。 本文作者:陆封阅读原文 本文为云栖社区原创内容,未经允许不得转载。

July 3, 2019 · 1 min · jiezi

性能压测工具选型对比

本文是《Performance Test Together》(简称PTT)系列专题分享的第二期,该专题将从性能压测的设计、实现、执行、监控、问题定位和分析、应用场景等多个纬度对性能压测的全过程进行拆解,以帮助大家构建完整的性能压测的理论体系,并提供有例可依的实战。 该系列专题分享由阿里巴巴 PTS 团队出品。 第一期:《压测环境的设计和搭建》,点击这里。 本文致力于给出性能压测的概念与背景介绍,同时针对市场上的一些性能压测工具,给出相应的对比,从而帮助大家更好地针对自身需求实现性能压测。 为什么要做性能压测在介绍性能压测概念与背景之前,首先解释下为什么要做性能压测。从09年的淘宝双十一大促导致多家合作银行后台系统接连宕机,到春运期间12306购票难,再到前不久聚美优品促销活动刚开始就遭秒杀。根据Amazon统计,每慢100毫秒,交易额下降1%。这些事件和统计数据为大家敲响了警钟,也客观说明了性能压测对于企业应用的重要性。 从具体的作用上讲,性能压测可以用于新系统上线支持、技术升级验证、业务峰值稳定性保障、站点容量规划以及性能瓶颈探测。 1. 新系统上线支持在新系统上线前,通过执行性能压测能够对系统的负载能力有较为清晰的认知,从而结合预估的潜在用户数量保障系统上线后的用户体验。 2. 技术升级验证在系统重构过程中,通过性能压测验证对比,可以有效验证新技术的高效性,指导系统重构。 3. 业务峰值稳定性保障在业务峰值到来前,通过充分的性能压测,确保大促活动等峰值业务稳定性,保障峰值业务不受损。 4. 站点容量规划通过性能压测实现对站点精细化的容量规划,指导分布式系统机器资源分配。 5. 性能瓶颈探测通过性能压测探测系统中的性能瓶颈点,进行针对性优化,从而提升系统性能。 综上所述,性能压测伴随着系统开发、重构、上线到优化的生命周期,因此有效的性能压测对系统的稳定性具有重要的指导意义,是系统生命周期中不可或缺的一部分。 性能压测概念性能压测是通过自动化的测试工具模拟多种正常、峰值以及异常负载条件来对系统的各项性能指标进行测试。从测试目的上性能压测又可以划分为负载测试、压力测试、并发测试、配置测试以及可靠性测试。 负载测试是测试当负载逐渐增加时,系统各项性能指标的变化情况。压力测试是通过确定一个系统的瓶颈或者不能接受的性能点,来获得系统能提供的最大服务级别的测试。并发测试通过模拟用户并发访问,测试多用户并发访问同一个软件、同一个模块或者数据记录时是否存在死锁等性能问题。配置测试是通过对被测系统的软/硬件环境的调整,了解各种不同方法对软件系统的性能影响的程度,从而找到系统各项资源的最优分配原则。可靠性测试是在给系统加载一定业务压力的情况下,使系统运行一段时间,以此检测系统是否稳定。总的来说,性能压测是在对系统性能有一定程度了解的前提下,在确定的环境下针对压测需求进行的一种测试。 如何选取性能压测工具在选取合适的性能压测工具之前,我们需要先先了解执行一次完整的性能压测所需要的步骤: 1. 确定性能压测目标:性能压测目标可能源于项目计划、业务方需求等 2. 确定性能压测环境:为了尽可能发挥性能压测作用,性能压测环境应当尽可能同线上环境一致 3. 确定性能压测通过标准:针对性能压测目标以及选取的性能压测环境,制定性能压测通过标准,对于不同于线上环境的性能压测环境,通过标准也应当适度放宽 4. 设计性能压测:编排压测链路,构造性能压测数据,尽可能模拟真实的请求链路以及请求负载 5. 执行性能压测:借助性能压测工具,按照设计执行性能压测 6. 分析性能压测结果报告:分析解读性能压测结果报告,判定性能压测是否达到预期目标,若不满足,要基于性能压测结果报告分析原因 由上述步骤可知,一次成功的性能压测涉及到多个环节,从场景设计到施压再到分析,缺一不可。工欲善其事,必先利其器,而一款合适的性能工具意味着我们能够在尽可能短的时间内完成一次合理的性能压测,达到事半功倍的效果。 工具选型对比在论述了性能压测必要性之后,如何选取性能压测工具成为一个重要的议题?本文选取了市场上主流性能压测工具:(ab)Apache Bench、LoadRunner、JMeter、阿里云PTS,并从多个方面出发分析了各个工具的优缺点,汇总后的优缺点如下表所示: 压测工具Apache Bench(ab)LoadRunnerJMeterPTS学习成本低高高低安装部署成本低高高低是否免费是否是否是否支持多协议否是是是压测结果是否能够图形化展示否是是是是否支持TPS模式否否否是是否有链路、场景编排管理支持否是是是是否支持场景录制否是是是生态环境强弱弱弱弱强监控指标是否完备否否否是是否原生支持流量地域定制否否否是Apache Bench(ab) ab是一款用来针对HTTP协议做性能压测的命令行工具,支持在本地环境发起测试请求,验证服务器的处理性能。它主要具有以下特点: 首先,作为一款开源工具,ab具有较好的扩展性,测试开发人员可以基于自身需求对其进行二次开发,同时它对HTTP协议支持度较好,比如支持设定HTTP请求头、支持Cookie以及HTTP的多种方法。此外,使用ab时还可以通过指定性能压测产生的总请求数、并发数与压测时长控制性能压测,结合其能够输出性能压测过程中的TPS(每秒事务数)、RT(响应时延)等信息的特点,ab具有简单易上手的特点。但ab也存在一些缺点,如无图形化界面支持,支持协议较为单一,只支持HTTP协议,缺少对HTTPS协议、WebSocket等协议的支持,对于较为复杂的性能压测场景,ab缺少链路编排、场景管理等支持,只能够对单一地址发起性能压测,此外,它的性能压测统计指标纬度较少,缺少性能压测过程中的数据统计,只能够在压测结束后获取相关的统计数据,无法实时获取系统负载等指标,难以应用于生产环境下的性能压测。 总的来说,ab作为一款命令行测试工具,适用于本地对支持HTTP协议的单一地址进行性能压测,但缺少相应的链路编排、场景管理、数据可视化等大规模性能压测基础功能,无法应用于生产环境。 LoadRunner LoadRunner,是一款发布于1993年11月的预测系统行为和性能的负载测试工具。通过以模拟上千万用户实施并发负载及实时性能监测的方式来确认和查找问题,LoadRunner作为一款历史悠久的商业性能压测工具,能够对整个企业架构进行测试。企业使用LoadRunner能最大限度地缩短测试时间,优化性能和加速应用系统的发布周期。 LoadRunner可适用于各种体系架构的自动负载测试,能预测系统行为并评估系统性能。 LoadRunner从组件上可划分为四部分: 负载生成器:模拟用户对服务器发起请求虚拟用户生成器:捕捉用户业务流,用于录制和生成脚本控制器:用于提供场景设计与场景监控,能够实时监控脚本的运行情况分析器:汇集来自各种负载生成器的日志并格式化报告,以便可视化运行结果数据和监控数据从组件划分上可以看出 LoadRunner 对于性能压测拥有较为系统的支持,结合多个组件的功能特性,用户可以较为方便地设计复杂背景下的性能压测场景,例如结合场景设计设置虚拟用户数量、设置执行时间等,结合虚拟用户生成器实现复杂链路、场景的高效设计与编排。此外,LoadRunner支持设置思考时间、集合点,还可以结合分析器实现压测报告统计数据、指标的可视化,助力测试人员理解性能压测结果。但 LoadRunner 作为一款商业软件,价格较高,需要本地安装,安装过程较复杂,在实际设计执行压测时需要编写相应的脚本,对使用人员来说学习成本比较高,此外缺少监控告警等支持,性能压测过程中难以实时发现问题。 总的来说,LoadRunner 作为一款性能压测商业软件,功能较为齐全,使用者能够借助 LoadRunner 达到简单的性能压测场景编排、施压目标;但它也存在学习成本居高不下、扩展性差等缺点,此外支持的协议有限,不适合复杂的性能压测环境。 JMeter Apache JMeter是Apache组织开发的基于Java的压力测试工具。它可以用于测试静态和动态资源,例如静态文件、Java 小服务程序、CGI 脚本、Java 对象、数据库、FTP 服务器等等。另外,JMeter能够对应用程序做功能/回归测试,通过创建带有断言的脚本来验证你的程序返回了你期望的结果。为了最大限度的灵活性,JMeter允许使用正则表达式创建断言。同时JMeter支持对性能压测结果做图形分析。 JMeter 作为一款开源软件,扩展性强,具有强大的开源社区支持,社区内开发者活跃程度高,也正是在开源社区的积极发展下,JMeter 具有性能压测的诸多特性,如支持场景编排、断言设置,支持对多种资源施压,有图形化界面支持,支持脚本录制,使用人员能够较为简单的设计并发起性能压测,此外 JMeter 提供资源监控、性能压测报告生成等功能。但在需要高负载施压的场景下,JMeter 需要部署分布式环境,部署成本比较高,在使用时,需要编写相应的脚本,而每个脚本文件只能保存一个测试用例,学习门槛居高不下的同时也不利于脚本的维护,此外它缺少监控告警等支持,在性能压测过程中使用人员难以借助 JMeter 实时发现问题。 ...

July 2, 2019 · 1 min · jiezi

TC基础与自动化

前言互联网产品的测试常常要覆盖在不同网络下的表现,例如丢包,带宽受限,时延及抖动等网络较差的情况,为了测试场景的网络情况可定义及复现,就需要有个工具对弱网进行模拟。业界一些通用的弱网模拟工具,如NEWT,fiddler,charles,atc,tc和packetstorm等软硬件弱网模拟工具。相较于硬件模拟器昂贵的价格和其他软件模拟器部署难度大,TC成本低,部署难度较低且容易和自动化结合实现自动化测试,此外TC适用任何平台的设备(包括PC、移动端等)。本文着重介绍TC的原理及应用。一、TC及Netem简介讲到tc工具不能不提iproute2工具集,iproute2工具集包括了一系列网络相关的工具,像大名鼎鼎的ip命令行工具,本文主要通过iptables创建一条linux系统的两块网卡的桥接。 netem 是 Linux 在2.6 及以上内核版本提供的一个网络模拟功能模块。该功能模块可以用来在性能良好的局域网中,模拟出复杂的互联网传输性能,如时延,丢包,抖动等场景。2.6 (或以上) 版本内核的很多发行版 Linux 都开启了该内核功能,比如Fedora、Ubuntu、Redhat、OpenSuse、CentOS、Debian等,本文以Ubuntu18为例。 tc 是 Linux 系统中的一个工具,全名为traffic control(流量控制)。tc 可以用来控制 netem 的工作模式,每一个物理网卡都会和一个qdisc关联,然后通过netem添加不同设置参数以实现对网络的控制。 二、Linux中的队列规则Linux操作系统中的流量控制器TC(Traffic Control)用于Linux内核的流量控制,它利用队列规定建立处理数据包的队列,并定义队列中的数据包被发送的方式, 从而实现对流量的控制。TC使用的队列规则分为两类,一类是无类别队列规则, 另一类是分类队列规则。 无类别队列规则相对简单,而分类队列规则则引出了分类和过滤器等概念,使其流量控制功能增强,本文中TC主要使用分类队列规则。1、无类别队列规则是对进入网络设备(网卡) 的数据流不加区分统一对待的队列规则。这类队列规则形成的队列可以对整个网络设备(网卡)的流量进行整形,但不能细分各种情况。常用的无类别队列规则主要有pfifo _fast (先进先出) 、TBF ( 令牌桶过滤器) 、SFQ(随机公平队列) 、RED (随机早期检测)等等。这类队列规则使用的整形手段主要是排序、限速和丢包。1.1 pfifo和pfifo_fastFIFO是linux网络接口的默认qdisc规则,没有整形和重排序的功能,仅对收到的包按照先入先出的顺序排出。pfifo_fast与pfifo类似,只是针对收到的流排出三个优先级。1.2 SFQ此种队列企图用一套公平的队列算法(哈希函数)来实现收包随机分配到不同FIFO序列,但是有些讨厌的软件上就无法保证折中公平性(分布在多个FIFO序列上)。1.3 TBF如名字所示,令牌桶过滤器基于令牌和桶,只有当令牌足够时,数据包才会从网口发出,否则,数据包将会被延迟发送,以此来实现流量限速。2、分类队列规则是对进入网络设备的数据包根据不同的需求以分类的方式区分对待的队列规则。数据包进入一个分类的队列后, 它就需要被送到某一个类中, 也就是说需要对数据包做分类处理。对数据包进行分类的工具是过滤器,队列规则会根据过滤器的分类结果把数据包送入相应的类进行排队。每个子类都可以使用它们的过滤器进一步分类,直到不需要进一步分类为止。如下图所示,为本文中TC用到的HTB,图中详细展示了qdisc和类以及子类的关系:三、TC的实现TC主要包括三个基本要素:队列规则(qdisc,queueing discipline)、类(class)和过滤器(filter)qdisc:队列规则,TC的核心,用于确定数据包的发送方式。如下命令实现了指定的eth0网卡上所有的包固定加了200ms延时 # tc qdisc add dev eth0 root netem delay 200msclass和filter:类和过滤器。类即是数据流量的类别,各种应用和终端的流量通过filter进行分类,进入到队列规则里排队进行发送。如下命令行所示即通过class和filter实现了对指定ip的限速,其他弱网类似: # tc class add dev eth0 parent 1:1 classid 1:2 htb rate 500kbit # tc filter add dev eth0 protocol ip parent 1:0 prio 3 u32 match 192.168.2.10 flowid 1:3其他常用到的弱网场景:延迟波动,如下命令表示延迟为时延150ms-250ms波动(由于网卡上发出的包延迟不同,所以会有一定程度的乱序发生): # tc qdisc add dev eth0 root netem delay 200ms 50ms乱序,如下代表随机丢包30% # tc qdisc add dev eth0 root netem delay 50ms reorder 25% ...

June 28, 2019 · 2 min · jiezi

案例精选-左耳朵耗子如何写出让同事无法维护的代码

对,你没看错,本文就是教你怎么写出让同事无法维护的代码。对于有下面这些编程习惯的朋友,请大家对号入座。 程序命名 容易输入的变量名。比如:Fred,asdf... 单字母的变量名。比如:a,b,c, x,y,z(如果不够用,可以考虑a1,a2,a3,a4,….) 有创意地拼写错误。比如:SetPintleOpening,SetPintalClosing。这样可以让人很难搜索代码。 抽象。比如:ProcessData, DoIt, GetData… 抽象到就跟什么都没说一样。 缩写。比如:WTF,RTFSC …… (使用拼音缩写也同样给力,比如: BT,TMD,TJJTDS) 随机大写字母。比如:gEtnuMbER... 重用命名。在内嵌的语句块中使用相同的变量名有奇效。 使用重音字母。比如:int ínt(第二个 ínt不是int) 使用下划线。比如:_, __, ___。 使用不同的语言。比如混用英语,德语,或是中文拼音。 使用字符命名。比如:slash, asterix, comma… 使用无关的单词。比如:god, superman, iloveu…. 混淆l和1。字母l和数字1有时候是看不出来的。 伪装欺诈 把注释和代码交织在一起。 for(j=0; j<array_len; j+ =8){ total += array[j+0 ]; total += array[j+1 ]; total += array[j+2 ]; / Main body of total += array[j+3]; loop is unrolled total += array[j+4]; for greater speed. total += array[j+5]; / total += array[j+6 ]; total += array[j+7 ];}代码和显示不一致。比如,你的界面显示叫postal code,但是代码里确叫 zipcode。 ...

June 25, 2019 · 2 min · jiezi

AnalyticDB-for-PG-如何作为数据源对接帆软-FineBI

AnalyticDB for PostgreSQL 基于开源数据库 Greenplum 构建,兼容Greenplum 和 PostgreSQL 的语法,接口和生态。本章节介绍如何通过FineBI连接 分析型数据库PostgreSQL版 并进行报表开发。 准备工作 开始使用FineBI之前,用户需要先完成以下准备工作。下载并安装FineBI 操作步骤 首先进行”新建数据连接“,并选择 "Greenplum Database"。 之后将 JDBC URL,数据库名称,用户名密码等输入进行连接测试。 注意事项 对于新安装的FineBI,第一次连接 Greenplum 或 PostgreSQL 数据源时,需要先下载其 JDBC Driver,可以按操作步骤下载并将对应JDBC 驱动安装到 FineBI 目录。AnalyticDB for PostgreSQL 既支持 PostgreSQL JDBC Driver,也支持 Greenplum 社区 Driver。 本文作者:陆封阅读原文 本文为云栖社区原创内容,未经允许不得转载。

June 25, 2019 · 1 min · jiezi

测试相关mock与stub的区别

复习软工二的时候遇到的占坑考完试回来写

June 14, 2019 · 1 min · jiezi

阿里敏捷教练全面解析淘宝直播敏捷实践之路

背景介绍阿里很少提敏捷转型或DevOps,阿里是强业务驱动的,不管用什么办法,一定要达到业务目标。 我来自敏捷教练团队,我们的职责是帮助团队拿结果。这里的团队不限于研发团队,我现在支持的团队包括销售团队和产品运营团队。我们要帮助整个业务链上所有职能角色协作起来达成业务目标。 阿里同学对敏捷的态度非常有意思。大家有问题才找我,同时会提醒我一句话,“我们不在乎敏捷,只要解决痛点和问题就行”。所以阿里的同学非常实在,就是要见效,只要他感觉到有效果,原来痛的地方不痛了,原来不通畅的地方顺畅了,他就觉得敏捷转型的努力是值得的。 面临的问题我们更像一个内部顾问,团队带着痛点和问题来找敏捷教练,我们要贴着他的问题想办法,一起做实践的落地,一起评估效果。 迭代过了一半,需求还没定2016年5月底,我进淘宝直播团队的时候,主要的痛点是“需求定不下来”。当时直播跟电商结合还是新业务,没有人知道应该做成什么样。运营和产品一直在摸索。摸索的过程中有很多犹豫,这样需求出来的比较晚。手机淘宝一个月发一个大版本,可能离封版只有两周,这一版到底做什么还没想明白,开发和测试非常着急。 开发时间紧,加班赶工需求出来后,开发非常赶,基本在5-8个工作日把1个月的版本需求都开发完。一个大版本总要有些亮点,不能只做一些小改进。所以开发工作量很集中,这个时候开发都在玩命加班赶工。 质量不达标,版本发不出赶工是有代价的,赶出来的东西可能表面上看是OK了,但是内在欠的技术债比较多,质量容易出问题。手机淘宝用户量非常大,质量卡点非常严,有严重缺陷没修好绝对不允许上线。淘宝直播2016年3月底发布第一个公众版本(淘宝的用户都可以用),3月、4月、5月连续三个版本,每一个版本都没有赶上正常的发版节奏。要申请紧急发版,提申请的人超级尴尬觉得很没面子。 线上问题多,运营变客服版本发出去了,可是质量太差了,主播天天在说直播间怎么黑屏了,怎么闪退了。运营同学本来应该做一些拉新、留存,想一些玩法,结果很苦的在主播群里做客服,运营同学一片抱怨。 着手解决问题数据度量 我需要一个仪表盘快速了解团队。我们经常讲到底怎么去衡量一个团队是不是敏捷?或者现在有没有比过去更敏捷?有几个维度还是值得大家去看的。 速率怎么样?一个月能不能交付更多功能,或者交付功能的价值有没有提高。 周期时长有多长?从打算做一个功能到用户可以用上这个功能,享受到它的价值要多久。这个时长越短,团队的适应性越好,在短时间内能响应一个新需求并把它交付。 质量怎么样?很多团队敏捷转型的时候,一上来就追求快。短时间内是快了,却欠了很多技术债。过一段时间速率会下来,最后既没有快也没有好。我的思路是先保证交付的东西质量都特别好,一次把有价值的事情做对,去掉中间的返工、浪费。如果有很好的质量,架构演进会更容易,开发新功能会更快。从质量出发先好再快,长期来讲能够拿到又快又好的效果。 最后准时性很重要。在阿里尤其电商系,可能90%以上需求是倒排的。需求提出来老板不会让团队评估多久可以做出来,老板通常说这个东西很重要,什么时间之前一定要,而且不是光要功能,还要业务结果。阿里不看苦劳看功劳,我们直接拉业务指标看。 还有一个最重要的维度是业务目标。敏捷也好、DevOps也好,最重要的还是业务,如果业务没做好其他都是零。即便做了一百个功能,如果业务指标没上来也是白搭。对于团队来讲,老板跟你说10月底要达到什么样的业务目标,即便没有100%把握做到,也要找到一条可行的路,10月底前把这件事搞定,在阿里这样才是靠谱的。 接下来会讲我们怎么始终扣紧业务目标,做的每一件事情都可以帮助我们拿到业务目标。这点在阿里特别重要。我们会找一些具体的指标来度量这几个维度。 速率度量 完成需求数是一个简单的度量,说它简单是因为我们只度量了单位时间内完成需求的个数,我们没有算故事点数,也没有考虑功能大小。 如果需求非常大,意味着它的开发测试时间都会变长,第一次得到反馈的时间也会很晚。一个大需求如果拆成两个小需求,并且每个需求都可以独立发布,先上一个再上一个,其实是比完成一个大需求再发布更好。这个指标有一个积极的副作用是鼓励团队把需求拆小一点,逐步的迭代和优化。我会跟产品经理商量,有没有办法把需求拆到研发团队在5个工作日内可以提测这样的粒度。如果一个团队有四五个开发,一周之内搞不定一个需求,意味着这个东西本身很大或者很复杂。 这个度量指标提出来后有人问我,需求大小不均,为什么只算个数。我说是为了鼓励大家拆需求。他说为什么要拆需求,我说不要憋大招小步快跑。这样他自己会把逻辑理顺。 质量度量 看质量更多是看过程的质量,在提测以后发现缺陷的数量,还有严重缺陷和低级缺陷占比。如果同一批人,同样的周期,缺陷数量突增,就有点不靠谱了。从5月到8月缺陷数量有明显的下降。 严重缺陷很好理解,我们来看看低级缺陷。低级缺陷是傻子都能发现的缺陷。这个指标衡量的是提测质量。如果开发比较上心,对自己交付的东西有责任心,通常不会有很多低级缺陷。回顾会上我会问低级缺陷数量我们有没有办法降下去?团队商量后觉得一个月不应该超过十个,就变成一个目标了,团队会朝着这个方向努力。 周期时长度量 周期时长我们拆了三段:分析时长、开发时长和测试时长,合起来是总的周期时长。 6月的周期时长大概是30天,分析时长大约占了一半。需求准备的时间特别长,大家觉得应该花更多时间分析需求,以免没想明白。实际上我们会发现即便多一倍时间分析需求,也未必能把所有问题都想明白。我们做的是创新的事情,这里有非常多的未知,想在一开始就把所有坑找出来不现实。我们要在研发过程中去探索,而不是在前面增加复杂的流程和评审。 大家会发现从6月到8月分析时长缩短了,开发时长和测试时长增加了。尤其是测试时长从3天增加到了7天。以前我们是小瀑布模式:一个月的功能最后三天一起提测,测试同学加班到凌晨。后来我们改进为小批次逐步提测,迭代的早期开始就不断有需求提测,测试压力分布在整个迭代周期。 还有一点大家可能很困惑,为什么7月的时长这么可怕,如果翻到前面会发现7月份交付需求数量也变少了,这里面有一个很有意思的故事。7月有两个很大的需求插队进来,团队的并发增加了。那个时候看板上有些卡片好几天拖不动,因为开工了太多需求,研发同学根本顾不过来。7月是一个比较失败的版本,我把7月的度量数据拿给开发负责人,我问改进了一个多月,为什么周期时长反而变长了,完成的需求反而变少了。开发负责人非常聪明,说我们并发太高了,这时候我觉得不需要再多说了。其实数据的力量很强大,大家知道高并发的伤害,但是伤害多严重不清晰。数据显示出来,因为并发提高,增加了那么多等待,大家觉得这件事代价太大了划不来。 8月淘宝直播火了,不断有合作方找我们想要加塞需求。经历了7月版本,团队通过反思学会说不。到了8月,我们比较能控制自己的节奏了。 准时性度量 准时性我们看计划交付的功能有多少按时交付了。7月并发度提高了,速率并没有提高,准时交付率也下降了。我们6月和8月是100%准时交付 , 7月没做到。没关系,只要找到原因,吃一堑长一智就可以了。 变化的背后聚焦业务目标 阿里是强业务驱动的公司,做任何事情在一个季度或半年,业务效果一定要被验证。淘宝直播是一个新业务,大家不知道往哪里去,这时候特别需要快速试错和验证。 我到手淘我也不了解他们的业务,就做了一个业务指标板,列出9月底要达到的目标,每个月发版后更新数据。 这些数据在BI系统里可以看到,为什么还要费力做个物理板呢?我观察虽然在BI系统里随时可以看到,并且大家都有权限,但是真正去看的就那么几个人,主要是运营和产品同学。研发 TL会看,一线同学一般不会看。大家也不太清楚正在做的功能对提升业务指标有什么帮助。 可视化以后,大家经常路过这个板,有时候就会聊两句,7月底了某某指标还没到一半怎么办,还有同学自告奋勇跟运营说有好点子,要知道以前都是运营说服产品和开发同学赶紧做。 业务主线 业务目标只是一个方向或者要去的地方,怎么到那里要有一个路线图,要有一个规划,这个规划是按季度做。产品、研发和业务三方负责人清楚季度规划,一线同学不清楚。后来我们决定季度规划定下来以后要分享给全员,所有人都要知道接下来三个月要去哪里,要攻什么目标,打法和策略怎样,分解到每个月要交付什么核心功能。这个规划就是我们的业务主线。 迭代目标 业务主线不落地也是空的,接下来迭代里的核心功能要扣住季度规划的业务目标和业务打法。我们做了比较狠的事情,产品经理不只要讲做什么功能,还要说明白做这个功能的业务价值在哪里,这个价值还要可度量。发布了这个功能以后看数据,比如直播间的观众有不同来源,有人从直播列表进来,有人从微博过来,有人是关注了主播从主播的直播预告列表进来,通过埋点可以知道每个来源对直播间UV的贡献。直播间UV这个月相比上个月有提升,到底哪个来源贡献比较大,上了哪个功能带来了这样的变化。有个新入职的产品经理以前做游戏直播也没有电商经验,但是她提的需求经过数据验证确实非常有效,大家非常信任她。反过来讲如果一个产品经理一次没命中,我们会觉得他运气不好,如果总是摸不中,再提需求可能大家要打一个问号。 迭代计划 我们的迭代计划可以一层层展开,从业务主线链接到核心需求。我刚去的时候他们刚好要发版,我问这个版本三个最重要的需求是什么。我分别问了三个开发同学,他们的回答不一样,有个开发同学直接跟我说做了很多,但是零零碎碎都想不起。6月、7月、8月我们主线很清晰。 迭代过程 迭代过程我们有物理看板,这是一个完整的端到端的板,这里只显示了一段。白色的是需求卡片,黄色的是任务卡片,红色的是风险、问题或缺陷,绿色的是谁做这个需求。我跟开发同学讲,每个人只有两张绿纸条,每个同学同一个时刻最多领两个任务,先领高优先级需求的任务,完成一个任务再领新任务。6月份开始用看板,集成封板前一天,我在钉钉上收到电子照片,所有需求在待集成那一列,然后开发TL跟我说感谢。之前连续三个版本都没赶上节奏,这次顺利集成了,大家都很开心。6月我们没有做更多的改进,只是把研发过程可视化出来,每天按照优先级的顺序更新今天进展如何,明天计划到哪里,有没有问题和风险。大家会有一种强烈的动力想把卡片拖到终点。 我刚进团队的时候大家觉得敏捷教练不干活,就是做了几个板弄了点数据,到底有什么用。大家也不太认敏捷这一套,比如开回顾会,我跟开发TL说开个回顾会吧,开发TL说代码写不过来没空开,我就说我很会控场,保证一小时之内开完。他有点活动心思,就开一个小时。回顾会开了以后,他觉得说的问题都在点儿上,改进行动也靠谱,就比较认同了。 去年双十一之后我离开淘宝直播去支持别的团队,今年1月底我去回访,发现他们的敏捷实践坚持得非常好,那个板比原来的更漂亮。阿里的同学都是价值驱动的,他觉得这个东西有用,才会坚持做下去。 快速验证假设 快速验证假设的工具在很多公司都有,就是A/B Test。在手淘A/B Test有非常好的技术支持,在APP里面集成SDK,服务端是现成的,很快可以接入。怎么样把工具用好是另外一个挑战了。 首页改版 当时想尝试在直播列表里透出直播信息,最容易想到把评论信息透出来,这样气氛能够感染到用户,吸引用户进来看直播。开发同学尝试了一个礼拜很苦恼地找我说,把评论透出来很麻烦,消息系统我们用了别人的,这个功能他们没有,要现开发一个。他们有一时排不上,就想看代码自己改,结果花了一个礼拜才调通接口,有没有办法可以快一点?我说最核心验证点在哪里,是不是透出来评论吸引用户进直播间?如果透出来的评论信息不是从消息流里自动获取的,而是在某几个直播间手动抓一些评论透出来,多久能实现?他说快的话今晚就可以搞定。先弄清楚验证的核心点是什么,再去看验证这个核心点最快最轻成本最低的方法是什么。 直播首页改版是很大的需求,我们不会所有东西一块做,而是拆成小点。每个点可以独立验证,而且非常轻,用户几乎感觉不到变化。这个例子里有两个点,一是底下赞的地方从静态变成动态,还有一个是从主播的静态图片改成直播间当前的十秒视频回放。这样可能气氛好一点,会吸引更多用户看直播。不需要PRD和交互视觉设计,运营直接和开发同学聊一下,大概知道要验证什么做成什么样,开发实现核心功能,推1%的用户做一个A/B TEST。数据如果有明显统计意义上的区别,可能摸对了,再按照做产品的方式精细地做出来。没摸对,成本肯定不会超过一个礼拜,这个事情不用再投入了。 一起打磨需求 需求为什么定不下来? ...

June 13, 2019 · 1 min · jiezi

报表网红是Tableau提测网红是MadPecker

近期Tableau着实刷屏了,身边的朋友纷纷“墙裂”推荐,其强大的数据处理及可视化分析号称比传统excel等现有解决方案提高10-100倍,堪称是【报表网红】。同样的,我们MadPecker在提测领域也可以帮助测试人员、开发人员提升10-100倍的工作效率,【提测网红】实至名归。 对于大多数团队而言,提测应该都是比较”糟心“的事情,测试用例的录制,测试计划的编排,测试过程中产生的bug的跟踪和修复,都将直接影响项目的上线质量。今天小啄给大家分享一下如何让每个团队的测试变得更有效率。 高效使用测试框架 测试人员在接到一份新项目的测试任务的时候,肯定是需要先理清楚测试思路的。为此,我们搭建了一套完整的测试框架,从用例录制到BUG提交形成规范的测试机制,即使是测试新手也几乎不用学习成本就可以完成测试任务。关于一些用户提出的在测试过程中遇到的问题: 1.测试用例写不全,总是感觉还有缺漏 2.测试用例深度不够,只能发现一些表面的问题,扎根深处的问题没法发现 3.测试过程中总是这点点那点点,没有根据场景进行项目测试,也不知道实际用户的使用习惯 针对类似的问题,我们根据长期积累下来的经验统一了测试用例的编写规范,目的在于提高测试用例的可读性、可执行性和合理性。因为编写测试用例相当于整个测试流程的地基,没有把地基打好,测试结果就不言而喻了。所以测试人员在编写测试用例时,需要以最小功能模块来划分用例,这样才能保证用例覆盖足够广,也使得后续问题的出现大大减小。那么如何高效地使用测试框架呢?我们建议先测主线再测支线。主线指的是真实用户完成一套正常流程所走的路线,支线指的是围绕主线的功能模块及其功能细节,这两条路线在系统中都可以使用同一套框架。 为保证测试流程更加接近真实用户的使用情况,我们构建了可复用的测试用例和测试场景,方便大家使用场景法进行测试。场景法是通过运用场景对系统的功能点或业务流程的描述来提高测试效果的。可复用的用例、场景也帮助测试人员大大地提高了工作效率。 同样的,测试执行也是测试框架中的一部分,测试执行将所有测试用例量化,然后整合起来指定给执行的负责人,并保证测试计划的有效性,确立每个测试阶段测试完成以及测试成功的标准和要实现的目标。通过执行中的通过率和完成度两项指标,测试人员或者项目的负责人都可以很清楚地了解到本轮测试的完成情况并对项目进行风险评估。在测试过程中,我们设定了BUG反馈机制,执行中产生的BUG直接反馈给开发人员,使得整个测试流程形成闭环。 打造测试界的网红产品 在MadPecker测试管理功能上线的短短几个月内,大量团队争相涌进,并将MadPecker作为团队的首要测试管理工具,使得我们的产品在业内积攒了一定的知名度。在此,小啄代表团队所有人感谢大家的信任。同时,我们也欢迎越来越多的团队加入进来,让每个团队的提测变得更有效率!

June 13, 2019 · 1 min · jiezi

飞猪项目管理数字化实践

6月29日,阿里巴巴研发效能部与PMI、Teambition联合举办的阿里巴巴研发效能实践日将在杭州西溪园区举行,活动聚焦敏捷精益项目管理。活动详情及报名可点我前往。 项目管理的目的是什么?面对工作中的各种不确定性,如何利用数据帮助项目管理?又有哪些数据是项目经理需要关注的?这里分享一篇飞猪技术部高级项目管理专家姚澍的文章,希望给你带来一些启发。 前言项目管理是起源于20世纪中期美国的航空项目,经过大量专业的项目管理从业人士总结出来的一门学科,并且随着时间发展在不断演进、更新,比如,在2018最新的PMBOK第六版中就提出了敏捷适应型的项目管理方法、拉动式的进度规划、新型项目经理价值等等。当下,全球IT公司中都在广泛使用LeSS、SaFe、精益、DevOps,这些新颖的方法论都集中关注在高效的产品开发或生产阶段,依然无法完全取代更系统化的项目管理。 项目管理的目的经常听到周边的同学说,项目经理一没权威、二没前途,谁都不愿意做项目经理,因为干不好就要背锅。现实中,看到的很多项目经理项目计划拍脑袋,执行过程拍胸脯,项目战报拍马屁,最后草草收尾、拍拍手走人。似乎因为带着“管理”两个字,让大家误以为这是一个务虚的官僚主义行为,丝毫不考虑当中的科学性、知识体系、方法论,最后学敏捷只学会了站会、学精益只学会了画看板、学DevOps只学会了刷脸好办事。 为什么要有项目?为什么要项目管理?我们在管理什么?难道只是为了运动式的完成一项任务?难道只是为了每年绩效好考核?我们经常被身在其中的身份制约了视野,总是想着用多快好省的方法达到目的,结果在不知不觉当中,就走入了小巷之中。在那个窄巷当中行走,不是进,就是退,甚至无法转身。只有跳出自己的小巷思维、凌空跃起再去审视局面时,才能看到还有很多其他的选择和出路。这时,才会自然的去追问:为什么要立项?项目里为什么要有这些需求?哪些产品特性需要改变?研发团队的开发活动如何分解?谁在关键路径上?要花费多少工时?如何保证所有研发活动最后能按时按质量交付?如何保证产品上线后实现业务目标?如何监控过程中的风险、问题?最后项目的投资回报比如何?是否完成了企业的财年目标?财报中咱们今年将是亏还是盈? 这样一系列追问下来,是不是慢慢感觉自带CEO视角了? 对,没错。项目就是企业的日常活动的组织方式,在质疑要不要立项时就分辨了哪些是临时任务哪些是每日例行,就能识别出企业最核心的关键任务,采用合适的方法管理项目和日常。 初创企业里,团队很小,7、8个人一间房、通讯靠喊的时代,沟通、管理成本是相对较小的,确定优先级就可以直接执行了,甚至也没什么好失去的。然而团队体量上去之后,稍有疏忽,沉没成本太大,会直接影响企业存亡。 项目管理铁三角里的几个因素:范围、成本、工期、质量,都和企业的关注高度吻合,企业管理核心也就自然落到了关键项目的管理上。 为何要数字化管理项目互联网公司要面对很多不确定的用户、对手、市场,面对未知,我们怎么办?只有两个方法: 试验,在小范围快速实现产品原型,灰度测试或者A/B测试收取反馈,结合运营效果快速反馈,在下一个迭代优化、改进。度量,准确定义度量维度,精确、及时收集数据,运用数据分析暴露问题、验证试验结果,从而持续优化。度量离不开数据,我见过很多项目经理,在描述自己带过的项目规模时,说不出准确的数字:多少团队成员、多大项目范围(代码行、特性数量、开发工时),多长的项目周期,多少项目成本,怎样的业务目标?很多人甚至分不清楚OPEX和CAPEX,更不用说ROI。因为缺少谈数字的环境,缺少对数字的敏感,没有鼓励和培养人人习惯用数字分析来系统性思考的内部环境,导致大部分的技术甚至PD只会埋头干活,鲜少追问为什么,沟通时也用大量的篇幅主观、模糊的描述项目进展,造成理解偏差、沟通低效。 为什么要习惯在工作中谈数字呢? 数字是量化的目标。企业运作是有详细细节规划的,比如,财年的业务目标、成本预算,应该层层分解,落实到各个项目、各个节点当中去,甚至要能反向推导,这样才能预测月度、季度的运营结果。同时,在过程当中能作为组织行为的基准线,让大家自觉针对目标及时调整行为。数字更客观,能更准确描述细节。数字比感觉更可靠,人往往容易被自己的情绪、偏见、误解欺骗,会对事实作出错误的判断。论据越客观、越细致,才越能经得起推敲,不管是用来决策还是沟通,都远胜于简单的拍脑袋。数字是驱动力。项目经理的核心价值应该在数据分析上,能从大量的、有效的数据当中发现趋势,识别风险或者机会,及时调整项目策略,保障项目目标达成。遗憾的是,现实中大多数项目经理都干的是初级的信息、事实收集工作。不是说信息收集不重要,而是应该建立起通用的、自动化的、可视化的数据大盘,把精力投入到收集完之后如何整合,如何解读,如何决策。哪些数据是项目经理必须关注的如下表所示,项目的不同阶段的关注重点不一样,相应的要有高效的手段挖掘出有用的信息。 注:文中提到的Aone是阿里一站式研发协作平台,对外叫云效,下同 事实上,AONE已经能基本提供收集以上所有信息的功能。很多大型的IT企业一直不遗余力的在寻找合适的工具管理各类信息,因为历史遗留问题,不得不花费大量的时间、精力打通所有的信息通道,迁移、整合数据,而AONE已经帮助我们弯道超车,实现了完整的需求产生、生产、集成、发布、部署。在飞猪,我们更进一步,针对特定的应用场景,基于AONE的数据,二次开发,用魔法石生成了重点项目的定制化项目数据大盘。 飞猪项目数据大盘实践那么数据大盘包含了哪些内容呢? 首先,从19财年业务策略开始梳理重点战役结构,用一张图画出各战役之间的关联(以下为示意图),并明确负责的项目经理和产品经理,形成第一级的项目目录。重点在: 分清楚项目发起人和项目集经理。我们经常容易把这两个角色弄混,一个项目会出现好几个管理者,在不同场合项目经理的名字不一样。项目经理是第一责任人,必须是直接指挥、跟踪、汇报项目的执行人,明确其唯一性和权威性并广而告之很重要,能加速问题解决和减少沟通误解。整合项目结构,合并相关联的,并广泛沟通。目标明确再立项。 设定统一的项目里程碑规范。以前项目的里程碑计划很随意,可有可无,大小不一。容易造成:颗粒度太小,外部干系人看不懂;颗粒度太大,缺少对项目组内的指导。现在采用统一的M系列里程碑定义,明确项目考核的时间点和标准,严格执行重要里程碑(M1&M4)的评审制度,有效的管理干系人期望,并随时度量下一个里程碑的可实现性。这样的好处是: 用统一的术语规范的项目执行的质量标准。里程碑评审增加项目执行的严肃性和完整性,做到有始有终,防止虎头蛇尾,帮助持续改进。增加沟通有效性,项目的评审结果和跟踪直观、易读、统一。 整理AONE项目空间,明确产品线、项目组合、项目集、项目的结构关系。需求、测试、缺陷、发布全部落入AONE,在魔法石生成对应的报表结构,层层钻取细节信息。 在飞猪产品线下设置子产品线,所有需求都直接在对应的子产品线下创建,遵循统一的规范模版,这样才能保证所有需求属性一致,方便魔法石度量。所有重要项目在PMO注册、创建,不允许私建项目。简化项目结构,只允许两层项目归属关系,避免过深的项目结构带来的责任不明确。由PMO组织重点项目例会,项目集经理向项目发起人汇报项目状态,方便及时调整策略、暴露问题,解决冲突,同步信息。 建设项目经理的责任制,要求项目经理必须对项目整体表现给出信心判断,依据AONE里的项目健康度信息形成项目晴雨表,红绿灯直观表现出项目的健康状态。要注意的是: 项目状态是项目经理的主观判断,是报告里为数不多的非客观评价,但不能省略,整体判断项目健康状态,同时也给出责任人的承诺项目的趋势比单点状态更重要。对持续告警的项目,要开始专项治理。 针对需求管理,首先明确不同角色的责任和合作方式。清晰的项目边界是成功的关键。项目启动初期,应该花大量的时间和精力明确需求范围、优先级、技术方案,而不是盲目开工,毫无纪律的边讨论边干活,不断返工,最后越做越困惑。比如,下面的两个实例, 项目A,对各类需求范围的管理都很严格,需求总量只出不进,每月评审上线效果,在后期果断丢弃低优先级的需求保证项目按时完结。而项目B,各类需求都在缓慢增长,实际表现就是项目做的像日常,未来没有规划,想到什么就做什么,目标不明确。使用按优先级分类的需求累积流图,识别项目核心交付内容,通过日常监管防止项目边界蔓延,做到有始有终,清空桌面再结项。 测试计划可以使用甘特图,测试过程中要有定期(每日/每周)进展推进图。项目后半段的时候,往往是测试的白热化阶段,有些项目可能需要用日会结合缺陷报告重点推进,识别出阻塞测试的缺陷,是否需要组织特殊小分队集中解决难题,是否需要不断升级警告直到团队的高层领导桌面上,要能结合进展明确指出问题以及解决办法,而不要罗列繁琐的细节。 缺陷分析,在宏观上,要能指出质量问题是否收敛,解决速度是否够快,识别重点问题集中区;微观上要就重点问题清单,点对点分析原因、找出解决方案、给出实施计划。缺陷不仅仅是质量风险,也是工作量,不管是修复问题,还是提出新需求,都是整个项目的新增工作量。实际上,缺陷也是可以预测的,根据千行缺陷率、改动代码行数、修复工时、合适的数据预估模型,就能更合理的估计产品上线时间,而不是总倒排工期。做到符合一定质量标准的产品才允许上线,杜绝只开发不测试、只测试不修复、无纪律上线等一系列严重影响用户体验的行为。 风险管理一直是项目管理的难点。首先,我们只能管理看得到的风险,有些风险也不可避免的会实现,更不用说那些完全无法预测的风险。其次,风险管理更考验项目经理个人的经验和敏锐。AONE提供了风险管理的功能,但“重风险识别,轻用风险应对分析”的现象还是比较普遍。AONE中提供的风险汇总视图(下图左)只能单维度的展现风险严重性,缺少可能性指标,于是我们在数据大盘里加上了风险矩阵(下图右),按严重性、可能性划分出9宫格,把注意力集中在矩阵右上角,一目了然。 人力资源投入是大家都很感兴趣的一个话题。AONE暂时不提供相关统计,我们只能另外开发小工具,由团队TL每月填写各项目参与人员的数量。资源分配可以宏观上帮助研发团队规划项目投入,不仅仅对过去的资源投入分析总结,更重要是可以整体上预计未来的资源分配是否能支持业务需求。 理论上,大家填好AONE里的需求工时估计,计算出来的工作总量应该是最准确的资源耗费成本,也可以生成项目的工作量燃尽图,以此能准确的预测项目实际上线、验收的日期。但实际执行中面临挑战太大,需求拆分不明确导致工时估计落实困难,而不得不折中由TL来汇总人力资源分配信息。 经过半年的项目规范、数据运营落地实践,从S1半年的项目需求交付周期回顾,我们发现: 强管控的需求交付周期明显短于平均值。交付周期中占比较大的是需求分析阶段,表征就是项目前期需求、目标不明确,导致后期开发赶工,测试压缩,最后的结果自然就不够好。AONE的使用规范统一化非常重要,对需求的跟踪、更新不及时就会造成数据偏差。在使用好项目例会同步信息的同时,通过关联代码和需求自动汇总状态变化信息,让数据更准确。 项目管理体系的未来 我经常被问到PMO是干什么的?很多人直接把PMO和过程改进、提高研发效能相等,我觉得PMO应该承担的责任是:首先,协助分解战略,合理部署资源,把关项目立项,整合项目结构。其次,建立系统的适配的管理规范,拉通上下游,让研发团队如同工厂生产线一般有质量、有效率的交付产品;最后,赋能项目经理,提高管理水平。优化、提效应该是一以贯之的持续改进。 经过半年的数据建设,飞猪技术部运行的项目已经逐渐规范,完成了两个重点战役的M4验收,明确了项目边界,逐步培养项目经理们的数据意识,倡导大家追问业务目标,量化过程指标,每个迭代都及时收集业务反馈,保证用户价值在运营、产品、技术、客满团队之间的顺利传递,并形成闭环。当然,我们依然面临巨大的挑战: 用户价值的追问、传递必须持之以恒的坚持下去。日常需求和弱管控的项目开发支持不够。AONE中沉淀了大量的数据,需要人性化的自动收集和分析焦点问题。项目的迭代规划、定期演示尚未制度化。项目经理赋能不够,只有越来越多的优秀项目经理成长起来,才能更广泛的保证系统健康运行。这半年,有成功交付上线的项目,也有目标不明确业务效果不明显而被叫停的项目,我们为成功喝彩,也为挫败反思,至少我们已经迈出了第一步。希望有一天能真正实现项目的可视化、可度量、可预测,希望有更多的人有热情投身项目管理。未来的路还很长,我们还需努力。 在此文结尾,不得不提到龙幽、欧旋两位数据挖掘专家过去5个月中对PMO工作的倾力支持,总是容忍并及时满足需求方提出的各种琐碎、奇葩、紧急的要求,衷心感谢!! 本文作者:云效鼓励师原文链接 本文为云栖社区原创内容,未经允许不得转载。

June 12, 2019 · 1 min · jiezi

如果测试没有梦想那跟咸鱼有什么区别

阿里妹导读:质量不是测出来的,但为什么又有这么多测试工程师为了质量而工作?测试是一个成本部门,测试创造的价值是什么?研发的模式在不断地变化,测试的定位如何不断去定义,未来的测试又会是什么形态?今天,阿里巴巴高级测试开发专家傲野总结了对未来测试形态的一些思考,希望对正在做测试的同学有所启发。前言从社会发展上来说,各领域的分工越来越细。但从技术部门的发展上来看,测试和开发的角色却是在不断融合,背后的原因是什么?是互联网迭代的速度越来越快促成的多角色融合,还是因为技术(特别是质量技术)先进生产力在逐渐取代落后的生产力? 在回答这些问题之前,我们先来回顾“测试工程师”作为一个职能或者个体在过去的发展历程: 10年前,最初级的测试产出工件是比较一次性的,比如项目中写的文本型测试用例,基本在项目发布后就废弃了。那个时期测试工作的进阶是方法论,比如能够把测试用例的设计方法,项目流程管理讲得头头是道已经是高阶了。有一些技术能力的测试同学,投身于自动化脚本的编写。自动化在“软件”测试时代和互联网初期,是真正的硬核能力。但这样的测试模式和效率都是非常低的,显然无法支撑互联网无快不破的浪潮。2010年以后,在头部企业的测试团队发生了一系列的变革,快速地从上述的这些初级能力,扩大到以 CI/CD 为驱动的技术体系,并最终推动了测试技术产品化进程,形成一个较为清晰的测试平台发展脉络。 在这个将近十年的周期中,由于测试工具、平台的不断创新,测试团队得到了一个突破性的发展。但工具作为传统测试模式的辅助手段,仍然会遇到突破的瓶颈。比如,从全球来看质量也发生了一定的分支: 一种是不断坚持平台化的发展路径:项目质量是基础,不断孵化出各类的效能平台,解决的问题也从传统的质量领域本身,往研发各环节拓展。有些大型的企业也开始沉淀了通用的研发协同平台(研发流水线)。一种是从内往外突破:比如 Google 的 SRE 团队,以纯技术的手段,打造一个内建且自洽的质量体系(传统以证伪为理论依据的是一个外建的质量体系)。[1]这两者的方向和目标,是有一定的重合的,比如有些公司以测试负责线下,SRE 负责线上进行区分。但如果从质量这个大的目标来看,未来的成功画面应该是:“质量和效率的结合”和“外建与自洽的结合”。因为只有这样,才能打造一个真正完整的技术质量生态。 实时质量也是基于上述的一些思考和实践,我们在2017年底提出了“实时质量”的概念。“它不是一个具体的测试技术产品,而是一种面向未来解决质量问题的方法和手段。” 它的主要特性是:运行含测试,实时可反馈。 为什么要往这个方向发展? 随着技术的不断创新和交付模式的不断改变,对于测试团队来说,需要尽快地从交付型质量往实时质量方向进行转移。传统的交付型质量,把测试作为一道道关卡,以任务的方式布防在开发提测、项目发布时。这种方式存在不同角色之间的过多交互,只能起到单点的质量保障。而实时质量的目标是:将质量手段以模块、组件乃至系统化的方式嵌入到业务型应用中,形成实时保障质量的能力。未来开发和测试人员之间的合作(或者就不区分开发测试了),不仅仅是人与人之间的协同,更多是双方分别为完成“业务特性服务的代码”和为完成”业务质量服务的代码“而相互配合,并形成系统级的依赖关系。在提供的这些质量系统上,我们希望公司内部的各种角色都能成为质量的操作者。只在做到这些,我们才可能将测试工作真正从面向过程到面向对象。 实时质量的架构 要做到质量的实时反馈和面向对象测试,这意味着我们的测试方法和协同方式发生了较为根本性的变化。我们需要以一个合适的方式参与到业务应用中,与此同时我们还需要把测试的各种能力封装成一个个服务,而不是现在的工具。工具终究是需要人来操作的,而我们希望未来测试任务的主体是机器、算法。测试人员只构建测试服务,而不参与测试过程,这也是最符合测试开发 Test Development Engineer 的 job design 。 那测试到底还需不需要做功能测试?可能在很长一段时间内仍然是需要的,但那一定只是日常工作中很小一部分。 实时质量是基于现有测试能力改造 我们在推进一个新的方向时,尽量不要去推翻重来。如果要面向未来,实时质量必须是可以向下兼容的,因为只是这样才能继承现有的测试沉淀,也才能被团队中的测试人员所接受和支持。只有自己不断进化才符合自然规律。所以我们需要更多强调对现有测试能力的改造,而避免另起炉灶。以下用运营页面测试的实时质量改造作为一个案例。 案例:运营页面的实时质量改造 作为电商域的同学对于运营页面应该非常熟悉,在之前也非常痛恨。比如: “CBU的一次大促,运营人员至少需要配置千级以上的活动页面,而每一个页面上又包含几百上千个商品等活动元素,平均一个页面需要5到10分钟的人肉检测,同时运营和测试人员需要不断就测试标准和 Bug 来回讨论、提交。一次大促下来,我们至少需要十几人/日的测试资源才能保证会场的正确性。” 这个过程很痛苦,运营人员需要不断去找对应的测试同学协同,幸福感很差。而测试人员来说,这些页面的测试更多是一个重复劳动,一个黑盒。能力也得不到什么成长。我们如何对它来进行实时质量的改造呢? 总共分两步: 我们对传统的测试体系进行了改造。把以往通过人工测试的各个测试点,通过自动化的方式来实现。比如基于 DOM 树制定一系列规则,例如403这些的错误都可以被很好地扫描出来。同时,针对于一些无法通过规则排查的问题,我们运用了算法能力。例如空坑检测,一致性检测等。把以上测试组件,通过消息的方式跟运营页面发布系统对接。它的系统依赖关系是如下的: 同时针对于不同的业务场景,我们开发了不同的页面检测能力,比如针对于 DOM 树的页面检查: 还有基于算法能力的识别能力: 通过上述的改造后,对于运营人员发布页面以及页面的测试就极简化为三步一站式的能力。从以往运营、测试、开发之间的来回交接,变成了运营跟系统之间的交互。不仅提升了运营人员的页面搭建体验,也极大地提升了测试的效率。 在某次运行中活动中实际的执行结果【示意图】: 以上的过程和结果数据,也充分体现了“运行含测试,实时可反馈”的价值。 数据和算法是实时质量的核心 测试出现以来,我们一直习惯于代码逻辑类的测试,但数据一直都是测试很重要的生产材料。因为人肉执行任务的局限性,我们发明了等价类和边界值等测试理论和方法来用尽可能少的成本来尽可能多的验证问题。但一方面算法的不断应用,每一个数据都可能存在个性化的业务表达,我们可能无法找到一个通用的预期结果较验(还是会有一些通用的预期结果的,比如非空判断和区间等,但这类的预期不能很好地做业务判断)。因此,我们也需要用数据和算法能力来武装自己。 在以数据驱动的业务发展进程中,我们的测试主体已经从简单的代码转变为数据+算法。或者说,业务对质量的核心述求,已经从简单的页面错误、代码 BUG 到数据的准确性、算法的有效性(我老板在每次大促前,都要再三叮嘱我数据不能错)。如何来感知质量风险,以及捕获各类的异常?那必须先把数据、流量、监控来做收口,同时提升测试工具在大数据分析上的能力。 基于这些思考,我们构建了全域实时数据校验能力,是一款通过实时获取线上 DB 中的海量业务数据,完成业务数据校验、质量风险感知的产品。 案例:Captain 全域实时数据校验 它具备的一些能力: 严格的安全策略。实时获取线上数据:通过强大的数据支持能力,平台可以在无损线上数据库表的前提下,通过 SQL 查询获取线上 DB 中的真实业务数据,且做到了实时获取,通过数据可以进行完善健壮的数据校验,从根本上提高对于业务的把控。多样的数据获取方式:目前平台支持多种数据获取方式:单库单表查询、单库多表联表查询、分库分表查询、跨库的多表的联表查询。多种比对方式支持,比如跨库查询和联表查询等等。最主要,它可以用一套脚本无损地支持测试环境、灰度、生产环境等。让线下测试的所有经验可以得到复用和沉淀。(我们内部调侃说,这才是带着测试的灵魂的,而其他的很多产品都只是一个面向开发的工具) ...

June 11, 2019 · 1 min · jiezi

蚂蚁金服终端实验室演进之路

摘要: 本文将从支付宝业务特性出发,深度解析无线实验集群在支付宝的演进与发展,并探讨 IoT 与人机如何交互并提供真正落地的时间方案。作者:周力(问瑾),蚂蚁金服技术专家。本文将从支付宝业务特性出发,深度解析无线实验集群在支付宝的演进与发展,并探讨 IoT 与人机如何交互并提供真正落地的时间方案。 现场视频(复制地址到浏览器中打开):http://t.cn/AiKDZg5G 0. 背景作为国民级 App,支付宝客户端需要为亿级用户提供多元化的服务,因此应用的稳定性与可靠性面临巨大的挑战,需要不断地完善和优化。 今天,让我们站在服务质量的全方位监控与优化的角度,从蚂蚁终端实验室的演进之路展开探讨,从借助使用开源的自动化方案,到自研并逐步完善无线实验集群技术体系,支付宝内部经历了怎样的业务场景演练,以及相应的技术架构如何借助移动开发平台 mPaaS 对外输出。 1. 发展历程 总的来说, 蚂蚁终端实验室从诞生到现在,一共经历过三个阶段(工具化、服务化以及中台化),其每个阶段都有特点和意义: 工具化阶段:该阶段主要以使用市面上主流开源软件为主,如客户端开源软件 Appium, 其覆盖的端为 Android 和 iOS;通过这种开源工具和 App 测试流程结合的方式,快速满足业务方的提测需求,从而帮助业务方完成一般意义上的自动化测试工作(如基本的功能测试、兼容性测试等)。 服务化阶段:服务化阶段存在一个重要的背景:支付宝着手前后端研发流程分离,并逐步沉淀出独立的 App 端研发流程系统(研发协作流程与 App 构建流程)。在独立的 App 研发流程和系统的基础上,终端实验室以一种服务化的形式支撑 App 的研发和协作, 处理满足日常用户自动化工作外,同时还担当着持续集成、日常发布前自动验包工作等; 另外在日常发布发布提供质量数据支持,如客户端代码覆盖率统计等。 中台化阶段:伴随着终端实验室的能力不断提升优化以及测试规模的逐步扩大,服务上不仅需要满足蚂蚁金服体系 App(支付宝、口碑、网商银行等)日常测试需求,而且还需要将能力扩散覆盖到整个阿里巴巴集团的业务。 随之而来的是实验室需要面临多样化的业务方需求和定制化功能,如何在多元复杂的业务环境中,与业务方或者说上游系统完成能力共建?带着这个问题,终端实验室逐步沉淀并着手建设中台化平台:一方面让通用服务不断下沉,另一方面抽象出标准 SDK 的方式,让业务方根据自身业务特点建设特定的能力。 此外,在建设平台化的同时,终端实验室贴合支付宝业务场景的发展,构建如网络实验室、扫码实验室等一系列真实实验室的能力。 经历了几年的不断发展,终端实验室逐步完成了中台化的转变,其端上覆盖了 Android、iOS 以及 IoT 设备,服务上覆盖了通用能力、小程序准入、研发流程建设、真机租用以及用例管控等。 2. 技术生态在了解完终端实验室的历程之后,我们能够对其提供的服务有一个全面的认识。当我们去总结和分析这些服务时,可以把这些具体能力分为三大块:平台服务能力、客户端SDK 以及 实验室能力。 平台服务能力平台服务能力的目标是聚焦“如何把蚂蚁实验室构建成一个更为开放的平台”,因此我们需要考虑到如何让更多的业务方和上游系统一起参与能力共建,从而将平台的建设思路分为 2 大部分:设备实验集群和开放SDK。 1. 设备集群 蚂蚁实验室不仅包含数以千计的公用终端设备,覆盖市面绝大多数手机终端,帮助业务同学完成日常自动化测试工作,而且提供了用户自建实验室的方式:用户只需要根据自身业务场景特性进行设备采购、实验室部署,便具备在自有平台上运行自有设备的能力。 从平台的开放性与部署动态化角度看,目前设备集群能保证设备归属和业务场景做到充分隔离,保证各业务在平台使用上能相互独立。另外,面对阿里巴巴集团众多研发中心,设备集群在部署上也支持多地部署、相互隔离。 2. 开放SDK 为了给上游系统和用户提供更为开放的能力,帮助业务方根据自身需求完成能力建设。终端实验室提供开放的 SDK 能力:上游系统只需在自己服务上接入 SDK,就能够完成任务构建链路,从用例管理、设备选择、任务执行,到执行结果回调,在此基础上用户就能够根据自身业务特点将业务数据进行多维度组合,形成自己的能力输出。 ...

June 5, 2019 · 1 min · jiezi

分布式系统关注点21构建易测试系统的六脉神剑

如果第二次看到我的文章,欢迎「文末」扫码订阅我个人的公众号(跨界架构师)哟~ 每周五早8点 按时送达。当然了,也会时不时加个餐~ 这篇是「分布式系统理论」系列的第20篇。提前预告一下,后面还有一篇文章,这个系列就结束了。 在之前,核心的概念都讲的差不多了。前面Z哥带你已经聊过了「数据一致性」、「高可用」、「易扩展」、「高性能」主题下的一些实践思路。 这篇讲怎么构建一个「易测试」的系统。 作为一位开发人员,可能一听到测试就想关掉这篇文章了。那我只能说too young,too naive。 作为关注我这个号的“跨界者“们,你不能将自己的边界划的太清楚,特别在当下这个变化越来越快、适者生存的时代。要活的像“水”一样,与所处的环境结合的更紧密。 除此之外,测试工作并不是单单测试人员的事,开发人员是不是编写了一个易测试的系统也至关重要。 在Z哥我过去的几年coding经验中,总结了六点认为有助于构建出一个易测试的系统建议,在这里分享给你。 第一点,分层。分层其实除了之前聊到的「易扩展」之外,对于测试工作的进行也是有很大帮助,规模越大的系统越是如此。 脑子里想象一下,一条业务线好比一根管道,每一次的业务操作会经历整根管道的流转最终到达终点。 往往很多时候,其实我们已经定位到了问题可能产生的范围,但是由于项目没有做好分层,导致每一次的测试工作不得不“从头开始”。这是多么痛苦的一件事。 做好分层只要记住一个概念就行,「高内聚低耦合」。具体可以参考之前的文章,文末放链接。 第二点,无状态。前面的文章里说过,满足无状态的功能点意味着可以动态的进行扩容而不用考虑“状态丢失”问题。其实同时它也支持了一种测试场景,就是「容量规划」。 为了支撑业务的不断发展以及不定期举行的大型活动,我们需要清楚的知道,到底部署多少台机器为宜。 当然,你也可以选择拍脑袋的方式进行,尽量多加一些就好了。但这不是一个科学的方法,也容易造成更多的浪费。 进行容量规划的过程就好比通过水龙头装水到一组杯子里。比如,你现在的要求是1分钟装入3L水,那么通过不断的调整杯子的数量和大小,理想情况是刚刚好达到这个要求为宜。 如果此时支持无状态,那么整个过程中水龙头一直开着就好了,你只要专心调整杯子的数量和大小就行。做好无状态具体也可以参考之前的文章,文末放链接。 第三点,避免硬编码,尽量配置化。可能你一看到那些庞杂的配置项就头疼,但是不得不说,配置对于测试工作的开展是有很大帮助的。 反而用“眼不见为净”的方式,硬编码到逻辑代码中是“掩耳盗铃”的办法。 特别是以下这些用途的变量,尽量放到配置中去,否则每次配置的变更都需要重新打包编译代码,是多么麻烦的一件事情。 容量类的配置次数类的配置开关类的配置时间类的配置这些类型的配置之间的共同点是,没有永远正确、永远合理的配置。你要根据你当前的需求,不断的调整他们。 如果可以引入一个集中式的配置中心就更好了,这样可以不用一个个登陆服务器去修改配置。 第四点,依赖注入。如果你平时经常编写单元测试的话,对这个应该感受颇深。因为支持依赖注入的代码,更容易编写单元测试。 但它的价值还不止于此,随着系统规模越来越大,对于直接在生产环境进行故障演练需求越迫切,因为这才足够真实。 但是又要求不能对正常的业务数据产生影响,怎么做?那就只能单独准备演练数据,然后写入到单独的数据库中。 这个时候,依赖注入就起作用了。我们可以将载入数据源的地方设计成支持依赖注入的,如此一来,你就可以灵活的切换到不同的数据源,进行故障演练。 public interface IDataSource{ public string getName(int id);}public class DataSourceMysql implements IDataSource{ public string getName(int id){ // 从正常的数据库里中获取数据。 }}public class DataSourceDrill implements IDataSource{public string getName(int id){ // 从故障演练的数据库里中获取数据。 }}public class UserBLL{ private IDataSource _database; public UserBLL(IDataSource database){ _database = database; } public void MethodA(int id){ // do something... var name = _database.getName(id); // do something... }}//以下是调用的时候new UserBLL(new DataSourceMysql()).MethodA(id); //处理的是正常数据new UserBLL(new DataSourceDrill()).MethodA(id); //处理的是演练数据第五点,打日志。测试工作最终做的好不好,看的是数据,是结果。这就意味着,对一个系统要求是「可观测」的。 ...

May 31, 2019 · 1 min · jiezi

开源性能可视化工具FlameScope模式识别

文章翻译原文链接 FlameScope是一个新的开源性能可视化工具,它使用次秒级偏移热图和火焰图来分析周期活动、方差、扰动。我们在Netflix TechBlog上面,发表了技术文章Netflix FlameScope,以及工具的源代码。火焰图很好理解,次秒级偏移热图理解起来要困难些(我最近发明的它)。FlameScope可以该帮助你理解后者。 总而言之,次秒级偏移热图是这样的:x轴是一整秒,y轴是这一秒里的几分之一秒。这每个几分之一秒都被称作一个桶(或者说盒),表示这几分之一秒里,事件数量的聚合。盒子颜色深度表示发生的次数,颜色越深表示次数越多。 下图一个真实的CPU上的次秒级偏移热图样本: 这张图中能分析出什么信息来呢?为了能把各种不同模式区分开来展示,我在这篇文章里先画了一些人工合成的样本。实际使用FlameScope工具时,可以选择你的各个模式,还能生成火焰图,显示对应的代码路径(这里我不展示火焰图)。 周期活动1 . 一个线程,每秒一次 线程在每秒钟内的同样的偏移里醒来,做几毫秒的工作,然后回到睡眠。 2 . 一个线程,两次每秒 每500ms唤醒一次。既可能是两个线程,也可能是一个线程500ms 唤醒一次。 3 . 两个线程 看起来像两个线程均1s唤醒一次 4 . 一个忙等待线程,每秒一次 这个线程做约20ms的工作,然后睡1s。这是一个常见的模式,导致每秒钟唤醒抵消匍匐前进。 5 . 一个忙等待线程,两次每秒 每500ms唤醒一次。有可能是单线程程序,每秒唤醒两次。 6 . 一个计算较密集的忙等线程 斜率高,每秒做更多的工作,大约是80毫秒。 7 . 一个计算较不密集的忙等线程 斜率低,每秒做的工作较少,可能只有几毫秒。 8 . 一个忙等待线程,每5秒钟唤醒一次 现在5秒唤醒一次。 我们可以根据夹角和唤醒的时间间隔,计算每个唤醒的CPU繁忙时间:busy_time = (1000 ms / (热图行数 时间长度) tan(夹角)例如45°夹角的线:busy_time = (1000 ms / (50 1)) tan(45) = 20ms 方差9 . cpu利用率100% ...

May 29, 2019 · 1 min · jiezi

如何实现持续集成闲鱼把研发效率翻了个翻

阿里妹导读:业务的快速发展,需要我们更快速地响应,和更高质量产品的交付。如何从原来大(xiao)迭(pu)代(bu)的开发模式切换为精益开发模式?以 2-1-1(2周需求交付周期,1周需求开发周期,1小时集成时长)为愿景驱动改进,达到持续交付价值,响应业务要求成为我们的目标。今天,闲鱼工程师琪钰为我们分享:闲鱼是怎样朝着这一目标前进的?切换为精益开发模式后,又面临了哪些问题和挑战?名词解释:精益开发模式,团队基于看板组织协作,以持续地交付需求为目标,需求按优先级,逐步进入开发、提测。由于在项目协作中,采用看板泳道来管理需求,因此在闲鱼,同学们习惯称之为泳道模式。 1、我们面临的要求和挑战业务对交付响应时间要求越来越快。闲鱼业务正处于高速发展中,反摩尔定律告诉我们,交付越迟,商品价值打折得就越厉害。速度为王,为了满足业务快速迭代和试错对技术团队能否快速交付需求提出了更大的挑战。团队规模变大,项目沟通成本越来越高。随着闲鱼业务和技术的快速发展,交付的环境也越来越复杂,协作的角色越来越多。整个研发过程包含需求管理、开发、测试、发布、回归等关键活动,涉及aone(研发协作平台,主要是需求、bug管理等)、代码库、打包平台、自动化测试平台等多个系统,沟通协同的成本越来越高。多分支并行开发增加额外成本。项目开发切换为精益开发模式最核心的改变就是各需求是独立的互不影响,可以分别独立进行测试和集成,保持主干的稳定,随时拉发布分支进行灰度发布。但多分支并行开发,也带来了新的问题,原来打包配置、手动打包、安装测试包等人工成本,都成倍的增加。随时来的提测都能够测。之前客户端发布版本时间固定,批量开发、批量提测,测试介入比较晚。项目开发切换为精益开发模式对技术质量团队提出了更高的要求,面对多需求同时提测的情况,如何更快地响应测试。所以,构建一个贯穿从需求到代码开发,再到测试整个过程的流程,并将其工具化、自动化就显得十分必要和紧迫,而持续集成就是这一流程的重要形式体现,构建一个高效的持续集成系统摆在我们面前。这将一定程度降低开发过程中的沟通成本,流程工具化,加速自动化。 现在针对服务端的集成发布有很多可以参考的实践,但对客户端的集成发布来说,我们依然面对如下难点。 2、客户端持续集成的难点如何将研发过程中各环节关联起来,一个需求从创建到发布的关键活动如下:创建需求->创建代码分支->创建打包项目->提交代码->打包->提交测试->修复->提交集成->发布如何做到需求和代码分支关联,确保代码可追溯; 如何做到代码分支和打包项目关联,代码变更可自动触发打包; 如何做到代码范围和测试范围关联,确保测试回归范围。 多分支并行,如何有条不紊的进行集成。并行需求分支越多,意味着提交集成时,可能的冲突的概率就会越大。如何降低集成的冲突,以及集成后主干的稳定性,确保集成质量;如何做到一提交代码就触发测试,测试进一步左移;如何降低自动化测试的成本,提高测试效率;而要解决上面的这些难点,缺少一站式的工具平台支撑(集团内平台对服务端的发布有很好的支持,但对于客户端的集成发布来说,涉及平台工具比较多)。 3、怎么做客户端持续集成为了解决从需求创建到发布整个项目研发过程中协同、构建、集成和测试等遇到的问题,提高团队的持续交付能力。针对客户端集成发布,我们的整体方案的目标是首先是拉通整个需求交付流程中各个环节,简化持续交付工作,提供及时的质量反馈机制,让开发同学关注在业务的开发;有效提高集成成功率及缩短集成发布周期,让版本能够按时上线大家能够按时下班;建设可视化、自动化、智能化的持续集成流水线,让业务需求真正的可持续交付。 空谈误国,实干兴邦。在谈论更多的改进之前,我们先把基础本的流程通过工具先串起来,只有先看到整体,然后再发现问题逐步改进。 流程化 我们的核心流程是这样的,一旦创建需求分支,交付通道就已建立,直到需求发布。 首先开发按照规范创建需求分支后,自动将分支和需求进行绑定,同时创建打包项目后,自动将需求和打包地址进行绑定,这样开发同学一旦提交代码,就可以根据需求、代码提交内容等,给出影响范围,同时自动触发打包,开发和测试同学不用再担心最新的包中是否有刚提交的内容,每次变更都会触发打包;打包成功后,根据 merge request、push 定时等不同的触发方式,及分支类型,自动触发相应的测试件,进行一系列的自动化测试;测试件执行完后,执行结果将被及时反馈出来,确保每次代码变更都是经过测试验证的,测试进一步左移,并将问题在团队项目协作看板上将问题标示出来,帮助团队在项目协作中能够持续的发现问题从而提高集成质量,降低发布风险,保证业务更快更顺畅的交付。当完成了第一步,将整个流程打通之后,我们发现,在整个流程中,依然有很多是依赖人工操作的地方,这是最容易出错,并且极低地影响效率的地方,对我们来说,这是改进的机会,所以,第二步我们的重点就是做好无人化和自动化。 无人化 为了支撑持续集成流水线的运行,以无人化、自动化、可扩展为目标,及基于最小研发成本原则,我们做得事情主要分为精益开发流程协同支撑无人化及测试验证自动化两部分。 fish CI 主要是研发流程支撑,如需求绑定、监听变更、触发打包、触发测试等,fish guard 主要测试件调度、执行,结果通知,及后续测试件接入扩展部分。目前已接入的测试件主要有 UI 遍历、UI 识别、monkey、单元测试等。后续计划按照分层测试的原则,接入更多的测试件,如代码静态扫描、weex 自动化测试、服务端测试件等,增强测试件覆盖度,拓展自动化测试边界。关于这一部分,我们将在后面的文章中做更深入的分享。 数据度量 管理学之父彼得德鲁克说:“如果你不能度量它,你就无法改进它”,其实也是我们整个持续集成流水线的自检,我们到底做得怎么样,持续交付的能力如何,我们定义了如下指标用于后续统计。 指标主要分为响应能力、效率、质量三个维度,通过响应能力的这些指标,可以反应出打包变快了,质量反馈变快了,集成变快了,集成频率变高了;有效率的指标,反应出流水线工作的有效性,成功率越高说明流水线越稳定;最后质量,主要从代码质量和项目测试质量来度量,通过修改的文件数,模块分布可以反映出代码的拆分、依赖等情况;通过项目测试中 bug 的分布和库存,可以反映出项目质量情况,是否及时发现及时修复,是否达到发布标准等。 4、效果闲鱼从3月中旬开始试运行精益开发模式(持续交付模式)到现在,闲鱼所有的业务需求全部走精益开发模式,我们交付的速度,由一个月一个版本到两周一个版本。这离不开我们在流水线各个环节中的改进,如打包变快了,需求分支构建次数越来越多,集成频率越来越高,以及自动化测试验证及时反馈集成质量情况。此外,闲鱼在精益开发模式下质量获得了明显提升,如下图所示: 绿色分割线左半部分,是之前未切换到泳道模式前的一个版本,bug 趋势看,前面编码阶段,测试基本未介入,大量的代码批量集成后集中测试,在缺陷充分被移除后,才能交付,无法持续交付。绿色分割线右半部分,是某个业务线的缺陷趋势图,小批量的持续集成、及时测试和发现问题、及时修复,可以快速持续交付。 5、总结与规划简单总结下,我们做的事情,第一步是拉通整个交付过程,有一个稳定的交付过程,第二步保证交付的效率,即响应变快了,集成变快了,质量反馈变快了,第三步持续交付,关键词是“持续地”,频次上提出了更高的要求,集成的频率变高了,以前一个月集成一次,现在每天都能集成,从一个月一次,到 nightly build,再到随时集成。即相比以前,让开发同学“更”有信心集成一次变更并发布。 因此,我们的终极目标就是7*24随时发布,没有发布窗口限制,真正做到交付流水线自动化无人化和全自动化测试,降低持续构建成本,拓展自动化测试边界。 本文作者:琪钰阅读原文 本文来自云栖社区合作伙伴“阿里技术”,如需转载请联系原作者。

May 28, 2019 · 1 min · jiezi

诊断修复-TiDB-Operator-在-K8s-测试中遇到的-Linux-内核问题

作者:张文博 Kubernetes(K8s)是一个开源容器编排系统,可自动执行应用程序部署、扩展和管理。它是云原生世界的操作系统。 K8s 或操作系统中的任何缺陷都可能使用户进程存在风险。作为 PingCAP EE(效率工程)团队,我们在 K8s 中测试 TiDB Operator(一个创建和管理 TiDB 集群的工具)时,发现了两个 Linux 内核错误。这些错误已经困扰我们很长一段时间,并没有在整个 K8s 社区中彻底修复。 经过广泛的调查和诊断,我们已经确定了处理这些问题的方法。在这篇文章中,我们将与大家分享这些解决方法。不过,尽管这些方法很有用,但我们认为这只是权宜之策,相信未来会有更优雅的解决方案,也期望 K8s 社区、RHEL 和 CentOS 可以在不久的将来彻底修复这些问题。 Bug #1: 诊断修复不稳定的 Kmem Accounting关键词:SLUB: Unable to allocate memory on node -1 社区相关 Issue: https://github.com/kubernetes/kubernetes/issues/61937https://github.com/opencontainers/runc/issues/1725https://support.mesosphere.com/s/article/Critical-Issue-KMEM-MSPH-2018-0006问题起源薛定谔平台是我司开发的基于 K8s 建立的一套自动化测试框架,提供各种 Chaos 能力,同时也提供自动化的 Bench 测试,各类异常监控、告警以及自动输出测试报告等功能。我们发现 TiKV 在薛定谔平台上做 OLTP 测试时偶尔会发生 I/O 性能抖动,但从下面几项来看未发现异常: TiKV 和 RocksDB 的日志CPU 使用率内存和磁盘等负载信息只能偶尔看到 dmesg 命令执行的结果中包含一些 “SLUB: Unable to allocate memory on node -1” 信息。 问题分析我们使用 perf-tools 中的 funcslower trace 来执行较慢的内核函数并调整内核参数 hung_task_timeout_secs 阈值,抓取到了一些 TiKV 执行写操作时的内核路径信息: ...

May 27, 2019 · 3 min · jiezi

CKB-测试网-Rylai-上线之后你可以玩些什么

正如 5 月 18 日发布的《Nervos CKB 测试网正式上线》里说的一样,Rylai 经过了三十多次的迭代,我们在全球通过购买服务器,部署了真实的节点,限制了带宽,所有的测试都在真实的网络上发生。这期间的测试过程中出现了很多问题,但很幸运,我们也都解决了这些问题。(谢谢冰女保佑????) 为什么叫 Rylai?很多人问起名字的来源,终于在 Testnet Launch Party 上,Terry 做了公布: Rylai 是一个女孩子的名字。为什么是女孩子的名字?据说船在起航的时候都会用一个女孩子的名字来命名,具体什么原因,大家可以去搜一下知乎排名第一的答案(我们就不详细说啦)。 Rylai 是 Dota 里面的英雄,叫冰女(水晶室女)。Dota 文化属于 Nervos 亚文化之一,所以未来我们也会用 Dota 里面的英雄命名以后的里程碑。 另外,还有其它的解释: 项目诞生是在寒冬测试网通常会 Freeze 一些 Feature,到主网不会有太大的变化,Freeze 和冰女的气质比较相像更多画面,大家可以联想......当前的 Rylai 中包含了什么?共识(NC-Max)和 P2P 协议虽然现有的项目有成熟的共识和 P2P 协议,但我们还是决定单独将它们做出来。因为对于一个需要经受真实环境残酷考验的项目来说,现有的共识和 P2P 协议并不是那么地完善和适合我们。 Nervos 现在的共识算法叫 NC-Max,由研究员张韧设计。在测试网上线之前,我们最重要的一份工作就是证明 NC-Max 比 Bitcoin 的 Nakamoto Consensus 更好。我们对 Bitcoin 网络和 CKB 网络进行了测量,后续,我们将发布所收集的数据、RFC 以及共识协议的论文。 Rylai 三十多次的迭代过程,也是我们对 NC-Max 以及 P2P 协议参数不断调试的过程。这个参数调优就像是在给一辆汽车做零件的调试(这里请大家自行脑补,周杰伦出演的电影《头文字 D》中,其父亲藤原文太调试汽车的场景),尽可能将发动机、油门、离合器、刹车等部件调到最好的状态。现在我们找到了顺畅运行的参数组合,测试网上线,大家可以来尽情体验了。 CKB-VM我们没有用 EVM,也没有用 WebAssembly,而是基于一个完全由开放的,由社区推动的 RISC-V 指令集,打造了一个新的 CKB-VM,这很符合我们的开源理念。这里有一个很特别的技术设计就是我们把自己验签的算法跑在了 VM 里面,也可以允许用户选择自己认为合适的验证签名的方法。 ...

May 22, 2019 · 1 min · jiezi

蚂蚁金服面对亿级并发场景的组件体系设计

作者:吕丹(凝睇),2011 年加入支付宝,先后负责了支付宝 Wap、alipass 卡券、SYNC 数据同步等项目,并参与了多次双十一、双十二、春节红包大促活动,在客户端基础服务方面有一定的项目实践经验与积累。目前负责蚂蚁金服移动开发平台 mPaaS 服务端组件体系优化与架构设计。5 月 6 日,InfoQ 主办的 QCon 2019 全球软件开发大会在北京举行。蚂蚁金服技术专家吕丹(凝睇)在大会上做了《蚂蚁金服面对亿级并发场景的组件体系设计》的分享,我们根据演讲整理如下: 今天,我主要想和大家分享一下移动领域基础组件体系,内容大致可以分为四大块,第一块是标准移动研发所需的基础服务体系,第二块是支撑亿级并发的核心组件“移动接入”的架构演进过程,第三块是双十一、双十二、新春红包这种大促活动的的应付方法,最后一块是目前已经对外输出的基础服务产品。 0. 移动研发基础服务体系 首先介绍一下支付宝客户端的演进过程。之前,支付宝客户端的主要功能是转账、订单支付、交易查询等等,更像是一个工具类的 APP,在需要付钱的时候才会掏出来,用完了就放回去了。2013 年,蚂蚁金服 all in 无线之后,加入了很多服务,例如余额宝、卡券、探索发现等,基本是把支付宝网站上的功能都尽量迁移到客户端,支付宝也逐渐演化成一个平台级别的客户端。之后,随着移动互联网的快速发展,公司内部孵化出了更多的 APP,其他行业也在移动互联网圈内铺开了大量的业务,为了提升用户量、用户粘性,APP 之间也开始进行了大量的业务融合,超级 APP 也因此而诞生,APP 开始朝着生态化的模式发展。 截止到目前为止,支付宝客户端的年活跃用户数超过 8 亿,在大促场景下,同时在线量超过 3 亿,并发请求超过 1 亿,同时上线的用户数超过百万每秒。 而在这些数据的背后一定需要一套庞大、复杂、完整的支撑体系来支持支付宝的运作,移动研发基础服务体系就是其中的重要组成部分。 按照研发过程,我们把移动研发基础服务体系分成四大块:APP 研发阶段,主要包括 App 框架、基础组件、云端服务和研发工具;App 测试阶段 ,主要包括研发协作平台和真机测试平台,其中研发协作平台包含版本管理、迭代管理、安装包编译、构建和打包的能力,而真机测试主要是代替人工服务,减少人工消耗,提升测试效率; App 运维阶段 ,主要包括智能发布、日志回溯、应急管理和动态配置;App 运营阶段,主要包括舆情反馈、实时分析、离线计算和智能营销。 1. 蚂蚁移动接入架构演进 今天的主题为支撑亿级并发下的基础服务,而在亿级并发下移动接入又是最核心、最重要的一个环节。移动接入并不是单个系统,而是一整套组件的总称,包括:Spanner+ 连接管理、API 网关、PUSH 通知和 SYNC 数据同步,它是所有移动业务的流量入口,需要维持客户端的状态,支持进行统一的管控,同时还需要进行部分的业务数据处理。 其实,一开始并没有移动接入这个说法,与支付宝客户端的演进过程类似,后端移动接入也是逐步迭代演进的。最开始,各个业务服务都是自己提供 API 或者直接暴露能力给客户端,没有统一的架构,没有统一的模型,也没有统一的管控。 为了解决这个问题,在 all in 阶段我们引申出了一个 API 网关,由它来做集中式管理,同时添加了 PUSH 推送的能力。因为公司内部有很多 APP,我们希望这些能力能够复用,所以在架构上,我们支持多 APP 同构,客户端会提供多个 SDK,可以随时进行集成。 ...

May 21, 2019 · 2 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

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

构建可靠系统的原则与实践

随着阿里技术的发展,我们的技术系统越来越成为社会的基础设施,对于这些系统的可靠性要求也就越来越高。但是实际上很多的基础的产品和系统确仍然会出现一些稳定性问题,那么如何才能构建可靠的系统呢?是不是制定非常严格而细致的规则就可以做出可靠的系统呢? 航空业的教训在回答这个问题之前,我们先来看看对于系统可靠性要求非常高的航空业是怎么做的?美国的FAA是在航空安全领域事实上的权威,为了保证航空器的安全,FAA制订了非常详细而复杂的航空器认证规则,而这些规则是否就保证了航空器的安全了呢? 让我们来了解一下最近的两起空难: 2019年3月10日,埃塞俄比亚航空ET302航班在起飞六分钟后坠毁,飞机上载有149名乘客和8名机组人员。 2018年10月29日,印尼狮航JT610航班在起飞后约十分钟坠毁,飞机上载有181名乘客,和8名机组人员。 几百条鲜活生命的消逝,这是多么严重的后果啊!究竟是什么原因导致了这样的灾难呢? 这两起空难的共同点是都是波音的737MAX机型,并且都是在起飞后不久发生的空难。那么这个背后的原因是什么呢?虽然官方的调查还没有结束,但是民间的分析指向了同一个原因,那就是这款机型的设计问题。 上图展示了Boeing 737 MAX的CFM LEAP引擎,值得注意的是和一般民航飞机不同的是,引擎的上沿和机翼平面几乎齐平。为什么会这么设计呢?这是因为波音737系列最早是上世纪60年代设计的,当时的引擎的直径小很多,外形更加细长,而机翼的高度是和引擎直径相匹配的。但是随着技术的发展,更新更省油的引擎直径变得越来越大,这时候原来的机翼高度无法满足更大直径引擎的安装空间,要想调整机翼的高度则需要改变起落架的设计,改变起落架的设计则需要改变起落架收起时相关机体位置的设计,而机体设计的变化会带来更多的变化从而会被FAA认为是一款全新型号的飞机,而全新型号的飞机则需要经历完整的FAA认证流程,会带来巨大的时间和经济成本。为了避免这样的成本波音选择了将引擎前移并且提升高度,但是这样带来了另外一个问题,由于空气动力学方面的原因,飞机会变得静不稳定,特别是在起飞阶段,引擎的推力会导致飞机迎角过高进入危险的失速状态。为了回避这个问题,波音引入了一个自动控制程序MCAS,通过读取迎角传感器的数据判断飞机是否迎角过高,如果过高的话自动控制飞机降低迎角,从而保证飞机的安全。 那么这么一套保证飞行安全的系统和空难有什么关系呢?事实上MCAS系统工作得非常好,根据波音自己的统计,Boeing 737 MAX系列已经完成了数十万次的安全起降。但是问题在于当传感器工作不正常时,MCAS有可能会根据错误的迎角数据做出错误的判断和动作,也就是在不应该降低迎角的时候降低迎角,导致飞机直冲地面。 一起后果扩大的故障回到我们的工作中,前不久我们碰到了一起系统故障,其过程有一定典型的意义,为了描述方面,这里隐去一些具体细节,简单说一下故障的过程。开始的时候,由于某些原因导致缓存命中率有所下降,而缓存命中率下降导致了数据库load升高,而数据库load升高以及可能的慢SQL导致了部分请求在获取DB connection的时候超时,从而引发了exception。当exception发生的时候,为了保证系统的可用性,系统逻辑进入了一段兜底逻辑,而这段兜底逻辑在特定的条件下产生了错误的返回,从而导致线上脏数据,而这些脏数据带来了业务资损和大量的人工订正数据的成本。 这个故障处理的过程并不是重点所以不再赘述,我们要问的是为什么一个简单的exception会导致这么严重的后果呢? 两个事例的共同点如果我们仔细去观察上述两个事例,我们会发现其中有如下几个共同点: 为了好的目的而引入了非常简单的备用逻辑直接保证“效果”这些备用逻辑在绝大部分情况下都能正常工作但是在极端情况下这样的逻辑失效了,并且产生了严重的后果换句话说,系统设计者在尝试用非常简单的逻辑去解决一个实际上复杂的问题,虽然实际上并没有完全解决问题,但是因为这样的逻辑能够通过大量的测试(或者合规检查),所以系统设计者“假定”问题得到了解决,从而放心地应用到了生产环境。 那么一个非常复杂的问题是否真的能够通过一个简单巧妙的办法解决吗? 没有银弹在系统设计领域,我们通常会把问题的复杂性分为两类,分别是偶得复杂性,实质复杂性。 偶得复杂性 Accidental Complexity 所谓偶得复杂性是指由开发者自己在尝试解决问题时引入的复杂性挑战,一般而言是由解决问题的方法和手段带来的,对于特定的问题,不同的方法会带来不同的偶得复杂性。 实质复杂性 Essential Complexity 所谓实质复杂性是由事务本身所决定的,和解决方法无关。 对于偶得复杂性,通过变换解决办法是有可能用简单的办法来解决的,但是对于实质复杂性,我们是无法通过改变手段来解决的,而必须采用相应复杂的方法来解决问题。换句话说对于实质复杂的问题,不要指望有银弹。 上述事例中,实际的环境和问题是存在比较大的实质复杂性的,然而我们却试图通过一些非常简单的逻辑去解决问题,从而带来了严重的后果。 快速失败 Fail Fast那么要想防止这类问题,设计高可靠的系统要怎么做呢?这里我想介绍一条反直觉的软件设计原则,快速失败(Fail Fast): In systems design, a fail-fast system is one which immediately reports at its interface any condition that is likely to indicate a failure.这是一条反直觉的原则,大部分人听说这条原则的第一反应是这样不是让系统变得更加脆弱了吗? 实际上并不是,原因在于我们不能停留在某一次的失败(failure),而是需要观察完整的过程,如下图所示: 当问题发生时,系统立即停止工作,由人工介入找到并以合理的方式解决根本的问题,然后系统恢复运作。通过这样的选择,我们就能够更早更容易地暴露问题,每当系统发生问题之后,真正的根因会更快得以解决,所以最后我们就能得到一个更加可靠的系统。 需要说明的是,快速失败不是说系统设计不处理任何问题到处失败,而只是在面对essential complexity的时候,不要尝试用一个简单粗暴的方案去解决,要么就用一套合理的机制设计去解决它,要么就fail fast把控制权交给系统上层决策,通常来说最终可能会回归到人,由人来分析和处理问题。 ...

April 29, 2019 · 1 min · jiezi

sadf

sfsad

April 28, 2019 · 1 min · jiezi

dockerdocker

Linux常用操作 清除缓存:echo 1 > /proc/sys/vm/drop_caches杀死进程:kill -9 ps -ef |grep tomcat | awk '{print $2,$3}'Centos 7 查看防火墙状态:firewall-cmd --stateCentos 7 一次性关闭防火墙:systemctl stop firewalld.serviceyum清除缓存:yum makecache查看版本:cat /etc/redhat-release查看可以安装的软件版本(比如mysql):yum list | grep mysql同时安装多个软件(比如mysql):yum install -y mysql-server mysql mysql-devel查看软件版本(比如mysql):rpm -qi mysql-server查看已经安装的软件(比如mysql):rpm -qa | grep mysql卸载软件:rpm -e mysql // 普通删除模式卸载软件:rpm -e --nodeps mysql // 强力删除模式,如果使用上面命令删除时,提示有依赖的其它文件,则用该命令可以对其进行强力删除检查软件是否是开机启动:chkconfig --list | grep mysqld设置软件开机启动:chkconfig mysqld on在一行前新增一行数据: sed -in-place '1i nihaxxxoya' test.sh在一行后新增一行数据: sed -in-place '1a nihaxxxoya' test.sh获取当前登录的用户 echo $(whoami)查看公网ipcurl cip.cc设置免密登陆服务器 ssh-copy-id -i id_rsa.pub root@192.168.136.211curl localhost:9999/api/daizhige/article -X POST -H "Content-Type:application/json" -d '"title":"comewords","content":"articleContent"'tcpdump -A -s 0 'tcp port 9200 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0) and host 110.184.155.52' -vvLinux常用操作 ...

April 25, 2019 · 1 min · jiezi

docker

Linux常用操作 清除缓存:echo 1 > /proc/sys/vm/drop_caches杀死进程:kill -9 ps -ef |grep tomcat | awk '{print $2,$3}'Centos 7 查看防火墙状态:firewall-cmd --stateCentos 7 一次性关闭防火墙:systemctl stop firewalld.serviceyum清除缓存:yum makecache查看版本:cat /etc/redhat-release查看可以安装的软件版本(比如mysql):yum list | grep mysql同时安装多个软件(比如mysql):yum install -y mysql-server mysql mysql-devel查看软件版本(比如mysql):rpm -qi mysql-server查看已经安装的软件(比如mysql):rpm -qa | grep mysql卸载软件:rpm -e mysql // 普通删除模式卸载软件:rpm -e --nodeps mysql // 强力删除模式,如果使用上面命令删除时,提示有依赖的其它文件,则用该命令可以对其进行强力删除检查软件是否是开机启动:chkconfig --list | grep mysqld设置软件开机启动:chkconfig mysqld on在一行前新增一行数据: sed -in-place '1i nihaxxxoya' test.sh在一行后新增一行数据: sed -in-place '1a nihaxxxoya' test.sh获取当前登录的用户 echo $(whoami)查看公网ipcurl cip.cc设置免密登陆服务器 ssh-copy-id -i id_rsa.pub root@192.168.136.211curl localhost:9999/api/daizhige/article -X POST -H "Content-Type:application/json" -d '"title":"comewords","content":"articleContent"'tcpdump -A -s 0 'tcp port 9200 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0) and host 110.184.155.52' -vv

April 25, 2019 · 1 min · jiezi

MSSQL最佳实践Always-Encrypted

摘要在SQL Server安全系列专题月报分享中,往期我们已经陆续分享了:如何使用对称密钥实现SQL Server列加密技术、使用非对称密钥实现SQL Server列加密、使用混合密钥实现SQL Server列加密技术、列加密技术带来的查询性能问题以及相应解决方案、行级别安全解决方案、SQL Server 2016 dynamic data masking实现隐私数据列打码技术和使用证书做数据库备份加密这七篇文章,直接点击以上文章前往查看详情。本期月报我们分享SQL Server 2016新特性Always Encrypted技术。 问题引入在云计算大行其道的如今,有没有一种方法保证存储在云端的数据库中数据永远保持加密状态,即便是云服务提供商也看不到数据库中的明文数据,以此来保证客户云数据库中数据的绝对安全呢?答案是肯定的,就是我们今天将要谈到的SQL Server 2016引入的始终加密技术(Always Encrypted)。使用SQL Server Always Encrypted,始终保持数据处于加密状态,只有调用SQL Server的应用才能读写和操作加密数据,如此您可以避免数据库或者操作系统管理员接触到客户应用程序敏感数据。SQL Server 2016 Always Encrypted通过验证加密密钥来实现了对客户端应用的控制,该加密密钥永远不会通过网络传递给远程的SQL Server服务端。因此,最大限度保证了云数据库客户数据安全,即使是云服务提供商也无法准确获知用户数据明文。 具体实现SQL Server 2016引入的新特性Always Encrypted让用户数据在应用端加密、解密,因此在云端始终处于加密状态存储和读写,最大限制保证用户数据安全,彻底解决客户对云服务提供商的信任问题。以下是SQL Server 2016 Always Encrypted技术的详细实现步骤。 创建测试数据库为了测试方便,我们首先创建了测试数据库AlwaysEncrypted。 --Step 1 - Create MSSQL sample databaseUSE masterGOIF DB_ID('AlwaysEncrypted') IS NULL CREATE DATABASE [AlwaysEncrypted];GO-- Not 100% require, but option adviced.ALTER DATABASE [AlwaysEncrypted] COLLATE Latin1_General_BIN2;创建列主密钥其次,在AlwaysEncrypted数据库中,我们创建列主密钥(Column Master Key,简写为CMK)。 -- Step 2 - Create a column master keyUSE [AlwaysEncrypted]GOCREATE COLUMN MASTER KEY [AE_ColumnMasterKey]WITH( KEY_STORE_PROVIDER_NAME = N'MSSQL_CERTIFICATE_STORE', KEY_PATH = N'CurrentUser/My/C3C1AFCDA7F2486A9BBB16232A052A6A1431ACB0')GO创建列加密密钥然后,我们创建列加密密钥(Column Encryption Key,简写为CEK)。 ...

April 25, 2019 · 4 min · jiezi

docker

Linux常用操作清除缓存:echo 1 > /proc/sys/vm/drop_caches杀死进程:kill -9 ps -ef |grep tomcat | awk '{print $2,$3}'Centos 7 查看防火墙状态:firewall-cmd --stateCentos 7 一次性关闭防火墙:systemctl stop firewalld.serviceyum清除缓存:yum makecache查看版本:cat /etc/redhat-release查看可以安装的软件版本(比如mysql):yum list | grep mysql同时安装多个软件(比如mysql):yum install -y mysql-server mysql mysql-devel查看软件版本(比如mysql):rpm -qi mysql-server查看已经安装的软件(比如mysql):rpm -qa | grep mysql卸载软件:rpm -e mysql // 普通删除模式卸载软件:rpm -e --nodeps mysql // 强力删除模式,如果使用上面命令删除时,提示有依赖的其它文件,则用该命令可以对其进行强力删除检查软件是否是开机启动:chkconfig --list | grep mysqld设置软件开机启动:chkconfig mysqld on在一行前新增一行数据: sed -in-place '1i nihaxxxoya' test.sh在一行后新增一行数据: sed -in-place '1a nihaxxxoya' test.sh获取当前登录的用户 echo $(whoami)查看公网ipcurl cip.cc设置免密登陆服务器 ssh-copy-id -i id_rsa.pub root@192.168.136.211curl localhost:9999/api/daizhige/article -X POST -H "Content-Type:application/json" -d '"title":"comewords","content":"articleContent"'tcpdump -A -s 0 'tcp port 9200 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0) and host 110.184.155.52' -vv

April 24, 2019 · 1 min · jiezi

dockera

Linux常用操作 清除缓存:echo 1 > /proc/sys/vm/drop_caches杀死进程:kill -9 ps -ef |grep tomcat | awk '{print $2,$3}'Centos 7 查看防火墙状态:firewall-cmd --stateCentos 7 一次性关闭防火墙:systemctl stop firewalld.serviceyum清除缓存:yum makecache查看版本:cat /etc/redhat-release查看可以安装的软件版本(比如mysql):yum list | grep mysql同时安装多个软件(比如mysql):yum install -y mysql-server mysql mysql-devel查看软件版本(比如mysql):rpm -qi mysql-server查看已经安装的软件(比如mysql):rpm -qa | grep mysql卸载软件:rpm -e mysql // 普通删除模式卸载软件:rpm -e --nodeps mysql // 强力删除模式,如果使用上面命令删除时,提示有依赖的其它文件,则用该命令可以对其进行强力删除检查软件是否是开机启动:chkconfig --list | grep mysqld设置软件开机启动:chkconfig mysqld on在一行前新增一行数据: sed -in-place '1i nihaxxxoya' test.sh在一行后新增一行数据: sed -in-place '1a nihaxxxoya' test.sh获取当前登录的用户 echo $(whoami)查看公网ipcurl cip.cc设置免密登陆服务器 ssh-copy-id -i id_rsa.pub root@192.168.136.211curl localhost:9999/api/daizhige/article -X POST -H "Content-Type:application/json" -d '"title":"comewords","content":"articleContent"'tcpdump -A -s 0 'tcp port 9200 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0) and host 110.184.155.52' -vv

April 24, 2019 · 1 min · jiezi

GitOpsKubernetes多集群环境下的高效CICD实践

为了解决传统应用升级缓慢、架构臃肿、不能快速迭代、故障不能快速定位、问题无法快速解决等问题,云原生这一概念横空出世。云原生可以改进应用开发的效率,改变企业的组织结构,甚至会在文化层面上直接影响一个公司的决策,可以说,云时代的云原生应用大势已来。在容器领域内,Kubernetes已经成为了容器编排和管理的社区标准。它通过把应用服务抽象成多种资源类型,比如Deployment、Service等,提供了一个云原生应用通用的可移植模型。在这样的背景下,我们如何在云原生的环境下实践更高效的DevOps来达到更有生产力的表现就成为了一个新的课题和诉求。 与GitOps这个概念相比,大家可能对DevOps的概念已经耳熟能详了。起初DevOps是为了打破开发测试、运营这些部门之间的壁垒,通过自动化的构建、程式化的脚本,最低限度减少人工误差,一定程度上提高应用版本的迭代效率;容器技术出现以后,轻量、标准化的能力使得DevOps技术才有了突飞猛进的发展。不管技术怎样更新迭代,DevOps最主要的核心诉求是不变的,那就是提高应用迭代的频率和降低成本。GitOps就是DevOps的逻辑扩展,它的核心目标是为了更加高效和安全的应用发布。 首先我们提取出一些用户在做devops的过程中遇到的痛点进行分析。第一个问题是如何自动化推进应用在环境栈中的无差别发布.这里我列举了三种环境,测试环境、生产环境和预发环境,对于一个应用来说,我们通常的设定都是把不同分支部署到对应环境,比如master分支的源码对应的是线上环境,latest分支对应的是预发环境,其他开发分支对应地部署到测试环境;目前大多数的做法是创建不同的job,拉取不同的源码分支、部署到不同的环境,或者同一个job,通过添加不同的构建参数来决定进行怎样的构建和发布动作。 非常容易产生混乱和不便于管理。 第二个问题就是,生产环境的发布权限一般都是需要严格控制的,通常只有应用管理员或者运维管理员才有生产发布权限。我们在跟一些客户的交流中发现,一种方式是在同一套cicd环境中创建不同的job,然后通过基于角色访问控制策略来做job的隔离,只有管理员权限的人员才能看到用于发布生产的job; 更直接的一种做法就是再建一套cicd环境专门做生产环境的发布, 但这样既浪费资源又降低了应用迭代的频率。 第三个问题是说我们想要提高应用迭代的频率进而降低人力成本、时间成本、把精力放在新业务或创新业务的拓展上,但目前我们的开发测试人员在应用运行状态或测试结果的同步与反馈上有一定的隔阂,另外一个是线上业务出现问题的时候,如何快速定位、复现和回滚,这是一个我们可以重点思考的地方。以上三点只是我列举出来的我们用户在实际使用cicd的过程中的一些痛点的子集,那接下来我们就带着这些问题来看一下gitops模型的设计思路是怎样的 我们在设计gitosp发布模型的时候是有以下这些核心诉求的:第一个是版本管理,我们希望每一个发布的应用的版本号都能跟git commit id关联,这样的好处就是每一个变更都有历史记录查询、可以更快进行故障定位和修复,第二个是基线管理,这里我们一会儿会讲到分两种类型的基线,第三个是怎么做安全发布,包括发布权限管理以及安全审批的内容;最后一个是如何让开发测试人员快速获取反馈 首先gitops的核心思想就是将应用系统的声明性基础架构和应用程序存放在Git版本库中,所有对应用的操作变更都来源于Git仓库的更新,这也是gitops这个名称的由来。另外一个问题是,按照以往通用的做法,我们可能会把应用如何构建如何部署的脚本以及配置文件跟应用源码本身存放在同一个仓库里,这样带来的问题有两个,一是开发人员可能还需要维护这个部署脚本或配置文件,不能把精力集中到产品开发上,另外一个问题是部署脚本有时候会涉及环境敏感信息,安全性不够,所以我们这里一定要把应用源码仓库与构建仓库分开管理。 接下来就是基线管理,基线管理分两种,一种是环境栈基线,如图所示,我们的设定是,生产环境只能部署master分支的代码,预发环境只能部署latest分支的代码,预览环境用来部署其他开发分支,这里有个名词叫预览环境,其实也就是测试环境,但我们会在开发分支通过测试、通过验证成功合并到latest分支以后动态销毁这个测试环境,当然这在kubernetes容器集群下是非常容器做到的,在其他具体的场景下可以用不同的策略。这个基线我们可以把它称为小基线,它是用来明确管理应用在预览、预发、生产环境中的推进的。大基线是针对线上发布版本的管理的,这能保证我们在线上出现故障的时候能快速回滚到上一个稳定的版本。这在生产发布管理中是必不可少的,在gitops中我们还能快速定位故障精确到某个git commit。 然后是应用发布的权限管理和安全审批,gitops中的权限管理是通过代码合并的控制来做的,在这个模型中,普通的开发人员没有cicd环境比如jenkins的访问权限,更精确地说的话是只有日志查看的权限,在git这一端,普通开发者只有向开发测试分支推送代码的权限,并可以申请向latest分支合并代码,即提交MR/PR的权限,当普通开发者新建MR/PR后,就会触发构建把应用部署到预览环境,管理员通过查看这个新分支的构建部署是否通过一系列测试和验证来决定是否接受这个MR/PR, 只有管理员接受MR/PR的合并后,latest分支代码才会重新构建和部署到预发环境,这样就通过MR/PR的接受和拒绝来达到应用发布安全审批的目的。 最后是如何进行快速反馈和团队成员间的互动,这包括两部分内容:一个是普通开发测试人员在推送源码后,能通过邮件、钉钉、slack等工具实时地获取构建结果,对自己的应用进行高效开发测试,;另一方面是能在MR/PR的页面上查看自动化测试的反馈结果、应用预览链接、其他团队成员的comment等。 下面是使用GitOps管理应用发布到不同kubernetes集群的架构图和时序图。首先是应用源码与构建源码分离。最上面有一条虚线,虚线上面是普通开发者能看到的,或者说是有权限进行操作的部分,剩下其他的部分都是管理员才有权限做的,绿色区域是Jenkins的流水线任务。普通开发者没有Jenkins环境的创建Job和构建Job的权限,他有的只是构建Job的日志查看权限。这个普通应用是在Git仓库里,它有不同的分支,有一定设定的关系,每次有构建的时候会从另外一个Git仓库里做,比如preview-plpeline、prod-plpeline,在这里面可以存放一些信息,只有应用管理员才能看到,普通开发者没有权限看到信息。 然后我们需要设置应用发布环境栈,这在个示例中我们有预览环境、预发环境、生产环境的设置,应用在预发环境和生产环境中的发布是需要经过管理员安全审批的。 最后是一个时序图,开发人员提交新的feature,创建指向latest分支的MR,创建MR的动作会触发preview-plpeline的构建,构建会拉取preview-plpeline的构建仓库,构建仓库存放的是构建脚本以及要部署的环境信息。然后就是自动化的构建流程,首先会从应用源码仓库把应用源码拉取下来做构建,静态代码测试、单元测试,测试结果会反馈到MR上,然后打包容器镜像并把镜像推送到镜像仓库,最后会把应用通过文件部署到Kubernetes的集群里并进行功能测试,测试结果反馈到MR上,部署之后会收集应用相关信息,通过钉钉通知发送到开发群里。开发人员收到钉钉通知,可以直接点击链接查看应用状态,如果有问题,可以返回来自己重新开发,再重新进行提交,把前面的流程再走一遍,没问题就可以请求管理员进行审批,把代码合并到latest分支。latest分支和master分支有更新时,就会触发与前面的构建类似的流程把应用推进到预发环境和生产环境。 本文作者:流生阅读原文 本文为云栖社区原创内容,未经允许不得转载。

April 24, 2019 · 1 min · jiezi

Kubernetes从懵圈到熟练读懂这一篇集群节点不下线

排查完全陌生的问题,完全不熟悉的系统组件,是售后工程师的一大工作乐趣,当然也是挑战。今天借这篇文章,跟大家分析一例这样的问题。排查过程中,需要理解一些自己完全陌生的组件,比如systemd和dbus。但是排查问题的思路和方法基本上还是可以复用了,希望对大家有所帮助。 问题一直在发生I'm NotReady 阿里云有自己的Kubernetes容器集群产品。随着Kubernetes集群出货量的剧增,线上用户零星的发现,集群会非常低概率地出现节点NotReady情况。据我们观察,这个问题差不多每个月,就会有一两个客户遇到。在节点NotReady之后,集群Master没有办法对这个节点做任何控制,比如下发新的Pod,再比如抓取节点上正在运行Pod的实时信息。 需要知道的Kubernetes知识 这里我稍微补充一点Kubernetes集群的基本知识。Kubernetes集群的“硬件基础”,是以单机形态存在的集群节点。这些节点可以是物理机,也可以是虚拟机。集群节点分为Master和Worker节点。Master节点主要用来负载集群管控组件,比如调度器和控制器。而Worker节点主要用来跑业务。Kubelet是跑在各个节点上的代理,它负责与管控组件沟通,并按照管控组件的指示,直接管理Worker节点。 当集群节点进入NotReady状态的时候,我们需要做的第一件事情,肯定是检查运行在节点上的kubelet是否正常。在这个问题出现的时候,使用systemctl命令查看kubelet状态,发现它作为systemd管理的一个daemon,是运行正常的。当我们用journalctl查看kubelet日志的时候,发现下边的错误。 什么是PLEG 这个报错很清楚的告诉我们,容器runtime是不工作的,且PLEG是不健康的。这里容器runtime指的就是docker daemon。Kubelet通过直接操作docker daemon来控制容器的生命周期。而这里的PLEG,指的是pod lifecycle event generator。PLEG是kubelet用来检查容器runtime的健康检查机制。这件事情本来可以由kubelet使用polling的方式来做。但是polling有其成本上的缺陷,所以PLEG应用而生。PLEG尝试以一种“中断”的形式,来实现对容器runtime的健康检查,虽然实际上,它同时用了polling和”中断”两种机制。 基本上看到上边的报错,我们可以确认,容器runtime出了问题。在有问题的节点上,通过docker命令尝试运行新的容器,命令会没有响应。这说明上边的报错是准确的. 容器runtimeDocker Daemon调用栈分析 Docker作为阿里云Kubernetes集群使用的容器runtime,在1.11之后,被拆分成了多个组件以适应OCI标准。拆分之后,其包括docker daemon,containerd,containerd-shim以及runC。组件containerd负责集群节点上容器的生命周期管理,并向上为docker daemon提供gRPC接口。 在这个问题中,既然PLEG认为容器运行是出了问题,我们需要先从docker daemon进程看起。我们可以使用kill -USR1 <pid>命令发送USR1信号给docker daemon,而docker daemon收到信号之后,会把其所有线程调用栈输出到文件/var/run/docker文件夹里。 Docker daemon进程的调用栈相对是比较容易分析的。稍微留意,我们会发现大多数的调用栈都类似下图中的样子。通过观察栈上每个函数的名字,以及函数所在的文件(模块)名称,我们可以看到,这个调用栈下半部分,是进程接到http请求,做请求路由的过程;而上半部分则进入实际的处理函数。最终处理函数进入等待状态,等待的是一个mutex实例。 到这里,我们需要稍微看一下ContainerInspectCurrent这个函数的实现,而最重要的是,我们能搞明白,这个函数的第一个参数,就是mutex的指针。使用这个指针搜索整个调用栈文件,我们会找出,所有等在这个mutex上边的线程。同时,我们可以看到下边这个线程。 这个线程上,函数ContainerExecStart也是在处理具体请求的时候,收到了这个mutex这个参数。但不同的是,ContainerExecStart并没有在等待mutex,而是已经拿到了mutex的所有权,并把执行逻辑转向了containerd调用。关于这一点,我们可以使用代码来验证。前边我们提到过,containerd向上通过gRPC对docker daemon提供接口。此调用栈上半部分内容,正是docker daemon在通过gRPC请求来呼叫containerd。 Containerd调用栈分析 与输出docker daemon的调用栈类似,我们可以通过kill -SIGUSR1 <pid>命令来输出containerd的调用栈。不同的是,这次调用栈会直接输出到messages日志。 Containerd作为一个gRPC的服务器,它会在接到docker daemon的远程请求之后,新建一个线程去处理这次请求。关于gRPC的细节,我们这里其实不用关注太多。在这次请求的客户端调用栈上,可以看到这次调用的核心函数是Start一个进程。我们在containerd的调用栈里搜索Start,Process以及process.go等字段,很容易发现下边这个线程。 这个线程的核心任务,就是依靠runC去创建容器进程。而在容器启动之后,runC进程会退出。所以下一步,我们自然而然会想到,runC是不是有顺利完成自己的任务。查看进程列表,我们会发现,系统中有个别runC进程,还在执行,这不是预期内的行为。容器的启动,跟进程的启动,耗时应该是差不对的,系统里有正在运行的runC进程,则说明runC不能正常启动容器。 什么是DbusRunC请求Dbus 容器runtime的runC命令,是libcontainer的一个简单的封装。这个工具可以用来管理单个容器,比如容器创建,或者容器删除。在上节的最后,我们发现runC不能完成创建容器的任务。我们可以把对应的进程杀掉,然后在命令行用同样的命令尝试启动容器,同时用strace追踪整个过程。 分析发现,runC停在了向带有org.free字段的dbus写数据的地方。那什么是dbus呢?在Linux上,dbus是一种进程间进行消息通信的机制。 原因并不在Dbus 我们可以使用busctl命令列出系统现有的所有bus。如下图,在问题发生的时候,我看到客户集群节点Name的编号非常大。所以我倾向于认为,dbus某些相关的数据结构,比如Name,耗尽了引起了这个问题。 Dbus机制的实现,依赖于一个组件叫做dbus-daemon。如果真的是dbus相关数据结构耗尽,那么重启这个daemon,应该是可以解决这个问题。但不幸的是,问题并没有这么直接。重启dbus-daemon之后,问题依然存在。 在上边用strace追踪runC的截图中,我提到了,runC卡在向带有org.free字段的bus写数据的地方。在busctl输出的bus列表里,显然带有这个字段的bus,都在被systemd使用。这时,我们用systemctl daemon-reexec来重启systemd,问题消失了。所以基本上我们可以判断一个方向:问题可能跟systemd有关系。 Systemd是硬骨头Systemd是相当复杂的一个组件,尤其对没有做过相关开发工作的同学来说,比如我自己。基本上,排查systemd的问题,我用到了四个方法,(调试级别)日志,core dump,代码分析,以及live debugging。其中第一个,第三个和第四个结合起来使用,让我在经过几天的鏖战之后,找到了问题的原因。但是这里我们先从“没用”的core dump说起。 没用的Core Dump 因为重启systemd解决了问题,而这个问题本身,是runC在使用dbus和systemd通信的时候没有了响应,所以我们需要验证的第一件事情,就是systemd不是有关键线程被锁住了。查看core dump里所有线程,只有以下一个线程,此线程并没有被锁住,它在等待dbus事件,以便做出响应。 ...

April 23, 2019 · 1 min · jiezi

前端为什么要学习 Selenium

翻译:疯狂的技术宅原文:https://www.edureka.co/blog/1...本文首发微信公众号:前端先锋欢迎关注,每天都给你推送新鲜的前端技术文章 如果你正在阅读本文,那么可能希望从手动测试升级为自动化测试。你是对的,因为你需要学习 Selenium。我这样说是因为自动化测试已经风靡全球,而且业界正很缺少 Selenium 认证专家。 你可能还想知道,怎样开始测试,以及 Selenium 是否适合入门。如果你有这些疑问,那么请不要担心,因为在本文中,我将给出 10 个令人信服的理由,来说明学习 Selenium 的必要性。 学习 Selenium 的十大理由是: 开源/免费工具无操作系统/浏览器要求支持多种编程语言框架的可用性在DevOps生命周期中的强势存在与其他工具轻松集成并行和分布式测试不依赖基于 GUI 的系统设计测试用例时的灵活性对 Selenium 测试人员的需求接下来详细解释每个理由。 1. 开源/免费工具 Selenium是一个开源工具,这意味着任何人都可以免费使用它。任何组织都可以使用 Selenium 来测试他们的网站或 Web 应用,甚至独立的程序员也可以用 Selenium WebDriver 学习和练习自动化测试。 其他测试工具的问题在于,它们是需要使用许可证的工具,或者它们的功能不如 Selenium。比如 HP 的 QTP、IBM 的 RFT、TestComplete、Ranorex都是专有(许可)工具。在功能上 Selenium 是明显的赢家,无人能出于其右! 2. 没有特殊的操作系统/浏览器/硬件要求这是 Selenium 带来的另一个好处。你不需要配有 16GB 内存或大容量硬盘的系统。也不需要专门去安装 Windows、MacOS 或 Linux。用于家庭用途的操作系统足以支撑 Selenium 进行测试。 最重要的是,你可以在任何浏览器上测试网站,无论是 Chrome 还是 Firefox,Safari 或 Internet Explorer,甚至是 Opera。使用 Selenium 如此简单,甚至与将 USB 驱动器插入电脑复制数据一样。 3. 支持多种编程语言 这是程序员进入自动化测试领域的最重要因素。市场上大多数工具都需要你专注于一种特定的编程语言。诸如VBScript、Java、C# 等语言就是各种工具所需的常用脚本语言。 ...

April 21, 2019 · 1 min · jiezi

写了一个chrome插件:拦截ajax请求并修改返回结果

这个插件可以拦截页面上的 ajax 请求,并把返回结果替换成任意文本。它对 mock 数据、排查一些线上问题等会有很大帮助。(当然 chales 等抓包软件也可以做到,然而使用起来比较繁琐,做成 chrome 插件的形式会方便许多)使用示例(视频)weibo.com/tv/v/HlVZD8cR9?fid=1034:4352275389595232Chrome 商店地址地址:https://chrome.google.com/web…你也可以直接搜索 Ajax Interceptor 进行安装注意建议第一次安装完重启浏览器,或者刷新你需要使用的页面。当你不需要使用该插件时,建议把开关关上(插件icon变为灰色),以免对页面正常浏览造成影响。该插件只会在JS层面上对返回结果进行修改,即只会修改全局的XMLHTTPRequest对象和fetch方法里的返回值,进而影响页面展现。而你在chrome的devtools的network里看到的请求返回结果不会有任何变化。githubhttps://github.com/YGYOOO/aja…

April 16, 2019 · 1 min · jiezi

Dart编译技术在服务端的探索和应用

前言最近闲鱼技术团队在Flutter+Dart的多端一体化的基础上,实现了FaaS研发模式。Dart吸取了其它高级语言设计的精华,例如Smalltalk的Image技术、JVM的HotSpot和Dart编译技术又师出同门。由Dart实现的语言容器,它可以在启动速度、运行性能有不错的表现。Dart提供了AoT、JIT的编译方式,JIT拥有Kernel和AppJIT的运行模式,此外服务端应用有各自不同的运行特点,那么如何选择合理的编译方法来提升应用的性能?接下来我们用一些有典型特点的案例来引入我们在Dart编译方案的实践和思考。案例详情相应的,我们准备了短周期应用(EmptyMain & Fibonnacci & faas_tool),长周期应用(HttpServer)分别来说明不同的编译方法在各种场景下的性能表现测试环境参考#实验机1Mac OS X 10.14.3 Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz * 4 / 16GB RAM#实验机2Linux x86_64Intel(R) Xeon(R) CPU E5-2650 v2 @ 2.60GHz * 4 / 8GB RAM#Dart版本Dart Ver. 2.2.1-edge.eeb8fc8ccdcef46e835993a22b3b48c0a2ccc6f1 #Java HotSpot版本Java build 1.8.0_121-b13 Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)#GCC版本Apple LLVM version 10.0.1 (clang-1001.0.46.3)Target: x86_64-apple-darwin18.2.0Thread model: posix短周期应用Case1. EmptyMain例子是一个空函数实现,以此来评估语言平台本身的启动性能,我们使用默认参数编译一个snapshot#1.默认条件下的app-jit snapshot生成dart snapshot-kind=app-jit snapshot=empty_main.snapshot empty_main.dart测试结果作为现代高级语言Dart和Java在启动速度上在同一水平线C语言的启动速度是其它语言的20x,基本原因是C没有Java、Dart语言平台的RuntimeKernel和AppJIT方式运行有稳定的微小差异,总体AppJIT优于KernelCase2. Fibonacci数列我们分别用C、Java、Dart用递归实现Fibonacci(50)数列,来考察编译工作对性能的影响。long fibo(long n){ if(n < 2){ return n; } return fibo(n - 1) + fibo(n - 2);}AppJIT使用优化阈值实现激进优化,这样编译器在Training Run中立即获得生成Optimized代码#2.执行激进优化 dart –no-background-compilation \ –optimization-counter-threshold=1 \ –snapshot-kind=app-jit \ –snapshot=fibonacci.snapshot fibonacci.dart将Fibonacci编译成Kernel#3.生成Kernel snapshotdart –snapshot=fibonacci.snapshot fibonacci.dartAoT的Runtime不在Dart SDK里,需要自行编译AoT Runtime#4.AoT编译pkg/vm/tools/precompiler2 fibonacci.dart fibonacci.aot#5.AoT的方式执行out/ReleaseX64/dart_precompiled_runtime fibonacci.aot测试结果Dart JIT对比下,AppJIT在激进优化后性能稍好于Kernel,差距微小,编译的成本占比可以忽略不计Dart AoT模式下的性能约为JIT的1/6不到JIT运行模式下,HotSpot的执行性能最优,优于Dart AppJIT 25%以上包括C语言在内的AoT运行模式性能均低于JIT,Dart AppJIT性能优于25%问题AoT由于自身的特性(和语言无关),无法在运行时基于Profile实现代码优化,峰值性能在此场景下要差很多,但是为何Dart VM比HotSpot有25%的差距?接下来我们针对Fibonacci做进一步优化#6.编译器调优,调整递归内联深度dart –inlining_recursion_depth_threshold=5 fibonacci.snapshot 50#7.编译器调优,HotSpot调整递归内联深度java -XX:MaxRecursiveInlineLevel=5 Fabbonacci 50测试结果HotSpot VM性能全面领先于Dart VM;两者在最优情况下HotSpot VM的性能优于Dart 9%左右Dart VM 借助JIT调优,性能有大幅提升,相比默认情况有40%左右的提升Dart AppJIT 性能微弱领先Kernel也许也不难想象JVM HotSpot目前在服务器开发领域上的相对Dart成熟,相比HotSpot,DartVM的“出厂设置”比较保守,当然我们也可以大胆猜测,在服务端应用下应该还有除JIT的其它优化空间;和Case1相同,Kernel模式的性能依然低于AppJIT,主要原因是Kernel在运行前期需要把AST转换为堆数据结构、经历Compile、Compile Optimize等过程,而在适当Training run后的AppJIT snapshot在VM启动时以优化后的IL(中间代码)执行,但很快Kernel会追上App-jit,最后性能保持持平。有兴趣的读者可以参阅Vyacheslav Egorov Dart VM的文章。Case3. FaaS容器编译工具在前面我们提到过Dart版本的FaaS语言容器,为追求极致的研发体验,我们需要缩短用户Function打包到部署运行的时间。就语言容器层面而言,Dart提供的Snapshot技术可以大大提升启动速度,但是从用户Function到Snapshot(如下图)生成所产生的编译时间在不做优化的情况下超过10秒,还远远达不到极致体验的要求。我们这里通过一些测试,来寻找提升性能的途径faas_tool是一个完全用Dart编写的代码编译、生成工具。依托于faas_tool, Function的编写者不用关心如何打包、接入中间件,faas_tool提供一系列的模版及代码生成工具可以将用户的使用成本降低,此外faas_tool还提供了HotReload机制可以快速响应变更。这次我们提供了基于AoT、Kernel、AppJIT的用例来执行Function构建流程,分别记录时间消耗、中间产物大小、产物生成时间。为了验证在JIT场景下DartVM是否可通过调整Complier的行为带来性能提升,我们增加了JIT的测试分组测试结果AoT>AppJIT>kernel,其中AoT比优化后的AppJIT有3倍左右性能提升,性能是Source的1000倍JIT(Kernel, AppJIT)分组下,通过在运行时减少CompilerOptimize或暂停PGO可以提升性能很显然faas_tool最终选择了AoT编译,但是性能结果和Case2大相径庭,为了搞清楚原因我们进一步做一下CPU ProfileCPU ProfileAppJITDart App-jit模式 43%以上的时间参与编译,当然取消代码优化,可以让编译时间大幅下降,在优化情况下可以将这个比率下降到13%KernelKernel模式有61%以上的CPU时间参与编译工作, 如果关闭JIT优化代码生成,性能有15%左右提升,反之进行激进优化将有1倍左右的性能损耗AoT下的编译成本AoT模式下在运行时几乎编译和优化成本(CompileOptimized、CompileUnoptimized、CompileUnoptimized 占比为0),直接以目标平台的代码执行,因此性能要好很多。P.S. DartVM 的Profile模块在后期的版本升级更改了Tag命名, 有需要进一步了解的读者参考VM Tags附:DartVM调优和命令代码#8.模拟单核并执行激进优化 dart –no-background-compilation \ –optimization-counter-threshold=1 \ tmp/faas_tool.snapshot.kernel #9.JIT下关闭优化代码生成dart –optimization-counter-threshold=-1 \ tmp/faas_tool.snapshot.kernel #10. Appjit verbose snapshotdart –print_snapshot_sizes \ –print_snapshot_sizes_verbose \ –deterministic \ –snapshot-kind=app-jit \ –snapshot=/tmp/faas_tool.snapshot faas_tool.dart #11.Profile CPU 和 timeline dart –profiler=true \ –startup_timeline=true \ –timeline_dir=/tmp \ –enable-vm-service \ –pause-isolates-on-exit faas_tool.snapshot长周期应用HttpServer我们用一个简单的Dart版的HttpServer作为典型长周期应用的测试用例,该用例中有JsonToObject、ObjectToJson的转换,然后response输出。我们分别用Source、Kernel以及AppJIT的方式在一定的并发量下运行一段时间void processReq(HttpRequest request){ try{ final List<Map<String,dynamic>> buf = <Map<String,dynamic>>[]; final Boss boss = new Boss(numOfEmployee: 10); //Json反序列化对象 getHeadCount(max: 20).forEach((hc){ boss.hire(hc.idType, hc.docId); buf.add(hc.toJson()); }); request.response.headers.add(‘cal’,’${boss.calc()}’); //Json对象转JsonString request.response.write(jsonEncode(buf)); request.response.close() .then((v) => counter_success ++) .timeout(new Duration(seconds:3)) .catchError((e) => counter_fail ++)); } catch(e){ request.response.statusCode = 500; counter_fail ++; request.response.close(); }}测试结果 上面三种无论是何种方式启动,最终的运行时性能趋向一致,编译成本在后期可以忽略不计,这也是JIT的运行特点在AppJIT模式下在应用启动起初就有接近峰值的性能,即使在Kernel模式下也需要时间预热达到峰值性能,Source模式下VM启动需要2秒以上,因此需要相对更长时间达到峰值性能。从另一方面看应用很快完成了预热,不久达到了峰值性能P.S. 长周期的应用Optimize Compiler会经过Optimize->Deoptimize->Reoptimize的过程, 由于此案例比较简单,没体现Deoptimize到Reoptimize的表现附:VM调优脚本#12.调整当前isolate的新生代大小,默认2M最大32M的新生代大小造成频繁的YGCdart –new_gen_semi_max_size=512 \ –new_gen_semi_initial_size=512 \ http_server.dart \ –interval=2 总结和展望Dart编译方式的选择编译成本为主导的应用,应优先考虑AoT来提高应用性能长周期的应用在启动后期编译成本可忽略,应该选择JIT方式并开启Optimize Compiler,让优化器介入长周期的应用可以选择Kernel的方式来提升启动速度,通过AppJIT的方式进一步缩短warmup时间AppJIT减少了编译预热的成本,这个特性非常适合对一些高并发应用在线扩容。Kernel作为Dart编译技术的前端,其平台无关性将继续作为整个Dart编译工具链的基础。在FaaS构建方案的选择通过CPU Profile得出faas_tool是一个编译成本主导的应用,最终选择了AoT编译方案,结果大大提升了语言容器的构建的构建速度,很好满足了faas对开发效率的诉求仍需改进的地方从JIT性能表现来看,DartVM JIT的运行时性和HotSpot相比有提升余地,由于Dart语言作为服务端开发的历史不长,也许随着Dart在服务端的技术应用全面推广,相信DarVM在编译器后端技术上对服务器级的处理器架构做更多优化。本文作者:闲鱼技术-无浩阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

April 11, 2019 · 2 min · jiezi

阿里巴巴资深技术专家无相:我们能从 InteliJ IDEA 中学到什么?

本文来源于阿里巴巴资深技术专家无相在内网的分享,阿里巴巴中间件受权发布。最近因为工作的关系,要将 Eclipse 的插件升级为 IDEA 插件。升级过程中,对 IDEA 插件做了些学习和研究,希望通过本文,对“为什么收费的 InteliJ IDEA 会比免费的 Eclipse有着更好的用户口碑“这一现象,提供些个人的思考。通过这篇文章,您将了解到心流与人体工效的重要性,并使自己变得更强大,文章将从以下几点展开:InteliJ IDEA 当前的市场情况与表现;IntelliJ IDEA 成功的原因;IntelliJ IDEA 在 IDE 设计器领域胜出的两个关键点;IntelliJ IDEA 对现实工作的启示;充分利用 IntelliJ IDEA 插件,使自己变得更强大;InteliJ IDEA 的市场情况与表现是如何?2012年开始,IntelliJ IDEA 便迅速崛起,到了2016年,就占领了开发者市场的 46% 分额。直至2018的最新结果:IntelliJ IDEA 的市场份额增长到了 55.4%,显然赢得了18年 Java 领域的 IDE 王者之战,甚至还有重构的大师 Martin Fowler 在他著名的blog上,对 IntelliJ IDEA 赞誉有加。而且根据 IDEA 的产品满意度调查,其满意度竟高达 98%。从外国学者角度,分析 IntelliJ IDEA 为何成功很多国外学者也研究了 IDEA,它的成功之处归结起来有以下两点:1. 产品功能上的成功:代码的智能提示: IDEA 自主开发语言解释器,做了深度的静态分析,让编程更加智能与高效,这是一项杀手级的特色功能;没有保存按钮:每一个你想使用功能,都有快捷键;性能非常好,用户的体验及其流畅;IDEA 不仅对核心插件进行维护, 还提供了优秀的工具集,给予用户一致的UX范式体验;2. 营销策略上的成功:以产品为核心和根本,以 “更好用” 的设计理念和原则打败了很多竞争者;IDEA 没有销售团队,但是霸气的营销口号体现了其内容营销的核心逻辑:”Try it. Test it. If you feel its better, use it“ ,没有多余的营销,真正做到用产品说话;相信大部分程序员,对 IDEA 的特性已经非常熟悉了。 接下来,我们将分析其真正强大的原因。在 IDE 设计器领域里,IntelliJ IDEA 为何能胜出?IDEA 的风靡与崛起,在于它遵从了两个关键的设计哲学:1. 不打断心流Every aspect of IntelliJ IDEA is designed with ergonomics in mind. IntelliJ IDEA is built around the idea that every minute a developer spends in the flow) is a good minute, and things that break developers out of flow are bad things. Every design and implementation decision considers the possibility of interrupting developer’s flow and seeks to eliminate or minimize it.(这句话美得我不想翻译。)“Creative Flow” or just “Flow” is a state of mind where you feel evenly attuned, and focused on the task at hand.创造心流是一种思考状态,是你感觉平和或专注于手头的任务的时刻,通俗的理解就是当你沉浸一件事时,有一种忘记时间与空间的感觉。心流会让你处于你最佳的意识状态,McKinsey 在2010年的研究表明,当你处于心流状态,你的工作效率比非心流状态下的工作效率提高 500%。通过这种设计理念的学习,不禁畅想:如果越来越多的阿里产品,像IntelliJ IDEA的的产品体验一样,让人处于心流,高效完成任务,整体的生产效率将会成倍提升。2. 人体工效学IntelliJ IDEA 宣称用人体工效学来设计IDEA, 即 ”The capable and ergonomic IDE for JVM“。我们简要地科普一下人体工效学:这是一门专注研究人体舒适,通过产品设计减少疲劳、不舒适的科学。在家居设计和人体工程学上,都会重点考虑这种设计理念,让人们在使用时感受到更舒适、更高效率与更少的压力。接下来例举两个人体工效学的经典例子:1. 客服耳机如果没有佩戴式耳机的出现,客服同学估计会疯掉。1. 一撕得的包装箱一撕得的包装纸质量、胶水质量、人体工效学的拉链式设计,这三种要素在纸箱界都是业界第一。人体工效学对软件设计的提示:统一的UX风格设计;将复杂任务变成简单任务;将长任务分解成短任务;因为人类的有记忆力的限制,所以要适当提供提醒,提供关键的辅助给用户,让他们完全沉浸在核心工作中,不被打断。(更多提示,可以看参加文献的第5篇文章。)如果想体验一下人体工程学的设计,IDEA的快捷键与无保存按钮,你立刻就会明白。对我们现实工作有哪些启示?经过团队的讨论,我们认为 IDEA 的增长飞轮在于:优秀的设计理念,卓越的产品体验,InteliJ IDEA 开放的社区,免费与专业收费并行的商业模式。IDEA 不断更新迭代的路径就是用更好的产品体验,吸引到更多的用户,用户贡献了更多的利润,用来吸收更多的优秀人才,一起开发更好的产品。这给予我们现实工作的指导意义,产品要回归至以人为中心的设计理念:心流的提示:罗列出用户的任务,让用户尽可能处于心流状态,消灭导致任务低效的因素;人体工效学提示:我们在设计产品时,要充分考虑用户使用产品的场景,这是一个一体化的设计,就像星环产品设计。写代码只是软件研发的一环,需求创意提出、需求研发上线、业务商业分析这些整体环节都要考虑,让用户舒适地完成任务;智能提示:采用人工智能来提升任务的完成效率。IntelliJ IDEA的智能代码提示,是一个经典的案例;最后:我个人会将心流与人体工效学的设计思想,融入至技术产品设计过程中,期待我们团队的实践篇。小编推荐两款创造心流的 IDE 插件:1. 热替换利器:JRebel一款热部署插件,帮助开发者在项目处于运行状态下任意修改 Java 文件并动态反馈到运行的项目中。点击了解更多2. 开发测试必备部署神器:Cloud Toolkit帮助开发者更高效地开发、测试、诊断并部署应用,利用此插件,能够方便地将本地应用一键部署到任意机器(了解更多:体验链接)。参考文献https://www.javaworld.com/article/3114167/development-tools/choosing-your-java-ide.htmlhttps://www.youtube.com/watch?v=Eyy9ddRgMX8http://catalyticcolor.com/scientific-research-about-flow/https://en.wikipedia.org/wiki/Flow_(psychology))http://ergo.human.cornell.edu/ahtutorials/interface.html本文作者:无相,阿里巴巴资深技术专家,多年担任汇金平台的架构师,是阿里tbbpm工作流引擎的作者。曾在阿里云负责过云产品 0 至 1 的商业化,最近几年在负责阿里的店铺&详情等系统的中台建设。目前,新零售业务平台正在招聘优质架构师,欢迎投简历到 wuxiang#alibaba-inc.com 邮箱。本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

April 8, 2019 · 1 min · jiezi

JMeter参数化(一):CSV数据的使用

JMeter的参数化内容不少,我们分几次来讲解,今天先讲最基础的使用CSV数据。 在测试过程中我们需要批量的随机性数据,比如测试登录需要用到大量的用户名及对应的密码,我们可将需要的数据写在csv文件中,并在JMeter中读取此文件中的数据。 0.准备工作 新建csv文件,并用notepad++等专业代码编辑工具打开编辑(保证文件编码格式为utf-8),按照JMeter读取csv文件的逻辑,每次读取一行参数,一行代表一组参数。我准备的数据部分如下,其中逗号需要注意是英文符号,其中也故意参杂了一些错误的用户名和密码:添加CSV数据文件设置在需要用到参数的接口上右键点击添加,选择CSV数据文件设置。配置内容如下:变量名称:待会调用时用到的名称忽略首行:是否忽略csv文件中的首行内容分隔符:一般使用英文逗号使用变量在需要使用的接口中,将参数值改为变量 3.设置线程数及循环次数

April 8, 2019 · 1 min · jiezi

通过DataWorks数据集成归档日志服务数据至MaxCompute进行离线分析

通过DataWorks归档日志服务数据至MaxCompute官方指导文档:https://help.aliyun.com/document_detail/68322.html但是会遇到大家在分区上或者DataWorks调度参数配置问题,具体拿到真实的case模拟如下:创建数据源:步骤1、进入数据集成,点击作业数据源,进入Tab页面。步骤2、 点击右上角新增数据源,选择消息队列 loghub。步骤3、编辑LogHub数据源中的必填项,包括数据源名称、LogHubEndpoint、Project、AK信息等,并点击 测试连通性。创建目标表:步骤1、在左侧tab也中找到临时查询,并右键>新建ODPS SQL节点。步骤2、编写建表DDL。步骤3、点击执行 按钮进行创建目标表,分别为ods_client_operation_log、ods_vedio_server_log、ods_web_tracking_log。步骤4、直到日志打印成本,表示三条DDL语句执行完毕。步骤5、可以通过desc 查看创建的表。其他两张表也可以通过desc 进行查询。确认数据表的存在情况。创建数据同步任务数据源端以及在DataWorks中的数据源连通性都已经配置好,接下来就可以通过数据同步任务进行采集数据到MaxCompute上。操作步骤步骤1、点击新建业务流程 并 确认提交,名称为 直播日志采集。步骤2、在业务流程开发面板中依次创建如下依赖并命名。依次配置数据同步任务节点配置:web_tracking_log_syn、client_operation_log_syn、vedio_server_log_syn。步骤3、双击web_tracking_log_syn 进入节点配置,配置项包括数据源(数据来源和数据去向)、字段映射(源头表和目标表)、通道控制。根据采集的时间窗口自定义参数为:步骤4、可以点击高级运行进行测试。可以分别手工收入自定义参数值进行测试。步骤5、使用SQL脚本确认是否数据已经写进来。如下图所示:日志服务的日志正式的被采集入库,接下来就可以进行数据加工。比如可以通过上述来统计热门房间、地域分布和卡顿率,如下所示:具体SQL逻辑不在这里展开,可以根据具体业务需求来统计分析。依赖关系配置如上图所示。本文作者:祎休阅读原文本文为云栖社区原创内容,未经允许不得转载。

April 2, 2019 · 1 min · jiezi

六年打磨!阿里开源混沌工程工具 ChaosBlade

阿里妹导读:减少故障的最好方法就是让故障经常性的发生。通过不断重复失败过程,持续提升系统的容错和弹性能力。今天,阿里巴巴把六年来在故障演练领域的创意和实践汇浓缩而成的工具进行开源,它就是 “ChaosBlade”。如果你想要提升开发效率,不妨来了解一下。高可用架构是保障服务稳定性的核心。阿里巴巴在海量互联网服务以及历年双11场景的实践过程中,沉淀出了包括全链路压测、线上流量管控、故障演练等高可用核心技术,并通过开源和云上服务的形式对外输出,以帮助企业用户和开发者享受阿里巴巴的技术红利,提高开发效率,缩短业务的构建流程。例如,借助阿里云性能测试 PTS,高效率构建全链路压测体系,通过开源组件 Sentinel 实现限流和降级功能。这一次,经历了 6 年时间的改进和实践,累计在线上执行演练场景达数万次,我们将阿里巴巴在故障演练领域的创意和实践,浓缩成一个混沌工程工具,并将其开源,命名为 ChaosBlade。ChaosBlade 是什么?ChaosBlade 是一款遵循混沌工程实验原理,提供丰富故障场景实现,帮助分布式系统提升容错性和可恢复性的混沌工程工具,可实现底层故障的注入,特点是操作简洁、无侵入、扩展性强。ChaosBlade 基于 Apache License v2.0 开源协议,目前有 chaosblade 和 chaosblade-exe-jvm 两个仓库。chaosblade 包含 CLI 和使用 Golang 实现的基础资源、容器相关的混沌实验实施执行模块。chaosblade-exe-jvm 是对运行在 JVM 上的应用实施混沌实验的执行器。ChaosBlade 社区后续还会添加 C++、Node.js 等其他语言的混沌实验执行器。为什么要开源?很多公司已经开始关注并探索混沌工程,渐渐成为测试系统高可用,构建对系统信息不可缺少的工具。但混沌工程领域目前还处于一个快速演进的阶段,最佳实践和工具框架没有统一标准。实施混沌工程可能会带来一些潜在的业务风险,经验和工具的缺失也将进一步阻止 DevOps 人员实施混沌工程。混沌工程领域目前也有很多优秀的开源工具,分别覆盖某个领域,但这些工具的使用方式千差万别,其中有些工具上手难度大,学习成本高,混沌实验能力单一,使很多人对混沌工程领域望而却步。阿里巴巴集团在混沌工程领域已经实践多年,将混沌实验工具 ChaosBlade 开源目的,我们希望:让更多人了解并加入到混沌工程领域;缩短构建混沌工程的路径;同时依靠社区的力量,完善更多的混沌实验场景,共同推进混沌工程领域的发展。ChaosBlade 能解决哪些问题?衡量微服务的容错能力通过模拟调用延迟、服务不可用、机器资源满载等,查看发生故障的节点或实例是否被自动隔离、下线,流量调度是否正确,预案是否有效,同时观察系统整体的 QPS 或 RT 是否受影响。在此基础上可以缓慢增加故障节点范围,验证上游服务限流降级、熔断等是否有效。最终故障节点增加到请求服务超时,估算系统容错红线,衡量系统容错能力。验证容器编排配置是否合理通过模拟杀服务 Pod、杀节点、增大 Pod 资源负载,观察系统服务可用性,验证副本配置、资源限制配置以及 Pod 下部署的容器是否合理。测试 PaaS 层是否健壮通过模拟上层资源负载,验证调度系统的有效性;模拟依赖的分布式存储不可用,验证系统的容错能力;模拟调度节点不可用,测试调度任务是否自动迁移到可用节点;模拟主备节点故障,测试主备切换是否正常。验证监控告警的时效性通过对系统注入故障,验证监控指标是否准确,监控维度是否完善,告警阈值是否合理,告警是否快速,告警接收人是否正确,通知渠道是否可用等,提升监控告警的准确和时效性。定位与解决问题的应急能力通过故障突袭,随机对系统注入故障,考察相关人员对问题的应急能力,以及问题上报、处理流程是否合理,达到以战养战,锻炼人定位与解决问题的能力。功能和特点场景丰富度高ChaosBlade 支持的混沌实验场景不仅覆盖基础资源,如 CPU 满载、磁盘 IO 高、网络延迟等,还包括运行在 JVM 上的应用实验场景,如 Dubbo 调用超时和调用异常、指定方法延迟或抛异常以及返回特定值等,同时涉及容器相关的实验,如杀容器、杀 Pod。后续会持续的增加实验场景。使用简洁,易于理解ChaosBlade 通过 CLI 方式执行,具有友好的命令提示功能,可以简单快速的上手使用。命令的书写遵循阿里巴巴集团内多年故障测试和演练实践抽象出的故障注入模型,层次清晰,易于阅读和理解,降低了混沌工程实施的门槛。场景扩展方便所有的 ChaosBlade 实验执行器同样遵循上述提到的故障注入模型,使实验场景模型统一,便于开发和维护。模型本身通俗易懂,学习成本低,可以依据模型方便快捷的扩展更多的混沌实验场景。ChaosBlade 的演进史EOS(2012-2015):故障演练平台的早期版本,故障注入能力通过字节码增强方式实现,模拟常见的 RPC 故障,解决微服务的强弱依赖治理问题。MonkeyKing(2016-2018):故障演练平台的升级版本,丰富了故障场景(如:资源、容器层场景),开始在生产环境进行一些规模化的演练。AHAS(2018.9-至今):阿里云应用高可用服务,内置演练平台的全部功能,支持可编排演练、演练插件扩展等能力,并整合了架构感知和限流降级的功能。ChaosBlade(2019.3):是 MonkeyKing 平台底层故障注入的实现工具,通过对演练平台底层的故障注入能力进行抽象,定义了一套故障模型。配合用户友好的 CLI 工具进行开源,帮助云原生用户进行混沌工程测试。近期规划功能迭代:增强 JVM 演练场景,支持更多的 Java 主流框架,如 Redis,GRPC增强 Kubernetes 演练场景增加对 C++、Node.js 等应用的支持社区共建:欢迎访问 ChaosBlade@GitHub,参与社区共建,包括但不限于:架构设计模块设计代码实现Bug FixDemo样例文档、网站和翻译本文作者:中亭阅读原文本文来自云栖社区合作伙伴“ 阿里技术”,如需转载请联系原作者。 ...

March 29, 2019 · 1 min · jiezi

MSSQL实践-数据库备份加密

摘要在SQL Server安全系列专题月报分享中,我们已经分享了:如何使用对称密钥实现SQL Server列加密技术、使用非对称密钥实现SQL Server列加密、使用混合密钥实现SQL Server列加密技术、列加密技术带来的查询性能问题以及相应解决方案、行级别安全解决方案和SQL Server 2016 dynamic data masking实现隐私数据列打码技术这六篇文章,文章详情可以参见往期月报。本期月报我们分享使用证书做数据库备份加密的最佳实践。问题引入谈及数据库安全性问题,如何预防数据库备份文件泄漏,如何防止脱库安全风险,是一个非常重要的安全防范课题。这个课题的目的是万一用户数据库备份文件泄漏,也要保证用户数据的安全。在SQL Server中,2014版本之前,业界均采用的TDE技术来实现与防范脱库行为,但是TDE的原理是需要将用户所有的数据进行加密后落盘,读取时解密。这种写入时加密,读取时解密的行为,必然会导致用户查询性能的降低和CPU使用率的上升(具体对性能和CPU影响,可以参见这片测试文章SQL Server Transparent Data Encryption (TDE) Performance Comparison)。那么,我们一个很自然的问题是:有没有一种技术,既可以保证备份文件的安全,又能够兼顾到用户查询性能和CPU资源的消耗呢?这个技术就是我们今天要介绍的数据库备份加密技术,该技术是SQL Server 2014版本首次引入,企业版本和标准版支持备份加密,Web版和Express版支持备份加密文件的还原。具体实现创建测试数据库为了测试方便,我们专门创建了测试数据库BackupEncrypted。– create test databaseIF DB_ID(‘BackupEncrypted’) IS NOT NULL DROP DATABASE BackupEncryptedGOCREATE DATABASE BackupEncryptedON PRIMARY(NAME = BackupEncrypted_data, FILENAME = N’E:\SQLDATA\DATA\BackupEncrypted_data.mdf’, SIZE = 100MB, FILEGROWTH = 10MB),FILEGROUP SampleDB_MemoryOptimized_filegroup CONTAINS MEMORY_OPTIMIZED_DATA ( NAME = BackupEncrypted_MemoryOptimized, FILENAME = N’E:\SQLDATA\DATA\BackupEncrypted_MemoryOptimized’)LOG ON ( NAME = BackupEncrypted_log, FILENAME = N’E:\SQLDATA\DATA\BackupEncrypted_log.ldf’, SIZE = 100MB, FILEGROWTH = 10MB)GO创建测试表在测试数据库下,创建一张用于测试的表testTable,并插入一条随机数据。USE [BackupEncrypted]GO– create test table and insert one recordIF OBJECT_ID(‘dbo.testTable’, ‘U’) IS NOT NULL DROP TABLE dbo.testTableGOCREATE TABLE dbo.testTable( id UNIQUEIDENTIFIER default NEWID(), parent_id UNIQUEIDENTIFIER default NEWSEQUENTIALID());GOSET NOCOUNT ON;INSERT INTO dbo.testTable DEFAULT VALUES;GOSELECT * FROM dbo.testTable ORDER BY id;该条数据内容如下截图:创建Master Key和证书创建Master Key和证书,用于加密数据库备份文件。USE masterGO– If the master key is not available, create it. IF NOT EXISTS (SELECT * FROM sys.symmetric_keys WHERE name LIKE ‘%MS_DatabaseMasterKey%’) BEGIN CREATE MASTER KEY ENCRYPTION BY PASSWORD = ‘MasterKey*’; END GOUSE masterGO– create certificateCREATE CERTIFICATE MasterCert_BackupEncryptedAUTHORIZATION dboWITH SUBJECT = ‘Backup encryption master certificate’,START_DATE = ‘02/10/2017’,EXPIRY_DATE = ‘12/30/9999’GO备份证书首先,将证书和证书密钥文件备份到本地,最好它们脱机保存到第三方主机,以免主机意外宕机,导致证书文件丢失,从而造成已加密的备份文件无法还原的悲剧。USE masterGOEXEC sys.xp_create_subdir ‘C:\Tmp’– then backup it up to local pathBACKUP CERTIFICATE MasterCert_BackupEncrypted TO FILE = ‘C:\Tmp\MasterCert_BackupEncrypted.cer’WITH PRIVATE KEY ( FILE = ‘C:\Tmp\MasterCert_BackupEncrypted.key’, ENCRYPTION BY PASSWORD = ‘aa11@@AA’);加密完全备份创建完Master Key和证书文件后,我们就可以做数据库完全备份加密操作。USE master;GO– do full backup database with encryptionBACKUP DATABASE [BackupEncrypted] TO DISK = N’C:\Tmp\BackupEncrypted_FULL.bak’ WITH COMPRESSION, ENCRYPTION ( ALGORITHM = AES_256, SERVER CERTIFICATE = MasterCert_BackupEncrypted), STATS = 10;GO加密差异备份数据库差异备份加密,备份操作前,我们插入一条数据,以供后续的测试数据校验。USE [BackupEncrypted]GO– insert another recordSET NOCOUNT ON;INSERT INTO dbo.testTable DEFAULT VALUES;GOSELECT * FROM dbo.testTable ORDER BY id;USE master;GO–Differential backup with encryptionBACKUP DATABASE [BackupEncrypted]TO DISK = N’C:\Tmp\BackupEncrypted_DIFF.bak’WITH CONTINUE_AFTER_ERROR,ENCRYPTION ( ALGORITHM = AES_256, SERVER CERTIFICATE = MasterCert_BackupEncrypted), STATS = 10, DIFFERENTIAL;GO差异备份操作前,校验表中的两条数据如下图所示:加密日志备份数据库事物日志备份加密,备份前,我们照样插入一条数据,以供后续测试数据校验。USE BackupEncryptedGO– insert another recordSET NOCOUNT ON;INSERT INTO dbo.testTable DEFAULT VALUES;GOSELECT * FROM dbo.testTable ORDER BY id;USE master;GO– backup transaction log with encryptionBACKUP LOG [BackupEncrypted]TO DISK = N’C:\Tmp\BackupEncrypted_log.trn’WITH CONTINUE_AFTER_ERROR,ENCRYPTION ( ALGORITHM = AES_256, SERVER CERTIFICATE = MasterCert_BackupEncrypted), STATS = 10;GO日志备份操作前,校验表中的三条数据如下图所示:查看备份历史数据完全备份、差异备份和日志备份结束后,查看备份历史记录。use msdbGO– check backupsSELECT b.database_name, b.key_algorithm, b.encryptor_thumbprint, b.encryptor_type, b.media_set_id, m.is_encrypted, b.type, m.is_compressed, bf.physical_device_nameFROM dbo.backupset bINNER JOIN dbo.backupmediaset m ON b.media_set_id = m.media_set_idINNER JOIN dbo.backupmediafamily bf on bf.media_set_id=b.media_set_idWHERE database_name = ‘BackupEncrypted’ORDER BY b.backup_start_date DESC备份历史信息展示如下:从截图中数据我们可以看出,三种备份都采用了证书做备份加密。查看备份文件信息备份历史检查完毕后,在清理测试环境之前,检查备份文件元数据信息,可以成功查看,没有任何报错。USE masterGO– before clean environment, try to get backup files meta info, will be successRESTORE FILELISTONLY FROM DISK=‘C:\Tmp\BackupEncrypted_FULL.bak’RESTORE HEADERONLY FROM DISK=‘C:\Tmp\BackupEncrypted_FULL.bak’RESTORE FILELISTONLY FROM DISK=‘C:\Tmp\BackupEncrypted_DIFF.bak’RESTORE HEADERONLY FROM DISK=‘C:\Tmp\BackupEncrypted_DIFF.bak’RESTORE FILELISTONLY FROM DISK=‘C:\Tmp\BackupEncrypted_log.trn’RESTORE HEADERONLY FROM DISK=‘C:\Tmp\BackupEncrypted_log.trn’展示结果部分截图如下:清理环境清理环境目的是模拟在一台全新实例上还原数据库备份文件。use masterGO– let’s try to simulate a database crash, here we just drop this database.DROP DATABASE [BackupEncrypted];GO– and clean certificate and master key to simulate restore to a new instance.DROP CERTIFICATE MasterCert_BackupEncrypted;GODROP MASTER KEY;GO再次查看备份文件信息清理掉证书和Master Key后,再次查看备份文件信息,此时会报错。因为数据库备份文件已经加密。这种报错是我们所预期的,即就算我们的数据库备份文件被脱库泄漏,我们的数据也可以保证绝对安全,而不会非预期的还原回来。USE masterGO– try to get backup files meta info again after clean environment, will be not success now.RESTORE FILELISTONLY FROM DISK=‘C:\Tmp\BackupEncrypted_FULL.bak’RESTORE HEADERONLY FROM DISK=‘C:\Tmp\BackupEncrypted_FULL.bak’RESTORE FILELISTONLY FROM DISK=‘C:\Tmp\BackupEncrypted_DIFF.bak’RESTORE HEADERONLY FROM DISK=‘C:\Tmp\BackupEncrypted_DIFF.bak’RESTORE FILELISTONLY FROM DISK=‘C:\Tmp\BackupEncrypted_log.trn’RESTORE HEADERONLY FROM DISK=‘C:\Tmp\BackupEncrypted_log.trn’报错信息类似如下:Msg 33111, Level 16, State 3, Line 178Cannot find server certificate with thumbprint ‘0xA938CE32CC86DFA6EAD2AED9429814F1A4C683ED’.Msg 3013, Level 16, State 1, Line 178RESTORE FILELIST is terminating abnormally.Msg 33111, Level 16, State 3, Line 179Cannot find server certificate with thumbprint ‘0xA938CE32CC86DFA6EAD2AED9429814F1A4C683ED’.Msg 3013, Level 16, State 1, Line 179RESTORE HEADERONLY is terminating abnormally.Msg 33111, Level 16, State 3, Line 181Cannot find server certificate with thumbprint ‘0xA938CE32CC86DFA6EAD2AED9429814F1A4C683ED’.Msg 3013, Level 16, State 1, Line 181RESTORE FILELIST is terminating abnormally.Msg 33111, Level 16, State 3, Line 182Cannot find server certificate with thumbprint ‘0xA938CE32CC86DFA6EAD2AED9429814F1A4C683ED’.Msg 3013, Level 16, State 1, Line 182RESTORE HEADERONLY is terminating abnormally.Msg 33111, Level 16, State 3, Line 184Cannot find server certificate with thumbprint ‘0xA938CE32CC86DFA6EAD2AED9429814F1A4C683ED’.Msg 3013, Level 16, State 1, Line 184RESTORE FILELIST is terminating abnormally.Msg 33111, Level 16, State 3, Line 185Cannot find server certificate with thumbprint ‘0xA938CE32CC86DFA6EAD2AED9429814F1A4C683ED’.Msg 3013, Level 16, State 1, Line 185RESTORE HEADERONLY is terminating abnormally.部分错误信息截图如下:还原证书文件数据库备份加密,可以有效防止脱库泄漏的安全风险。当然,合法用户需要在新实例上成功还原加密备份文件。首先,创建Master Key;然后,从证书备份文件中,重新创建证书。USE masterGO– so we have to re-create master key, the certificate and open the IF NOT EXISTS (SELECT * FROM sys.symmetric_keys WHERE name LIKE ‘%MS_DatabaseMasterKey%’) BEGIN CREATE MASTER KEY ENCRYPTION BY PASSWORD = ‘MasterKey*’; END GOuse masterGO– re-create certificateCREATE CERTIFICATE MasterCert_BackupEncryptedFROM FILE = ‘C:\Tmp\MasterCert_BackupEncrypted.cer’WITH PRIVATE KEY (FILE = ‘C:\Tmp\MasterCert_BackupEncrypted.key’,DECRYPTION BY PASSWORD = ‘aa11@@AA’);GO检查备份文件信息校验备份文件信息,已经可以正确读取。USE masterGO– after re-create certificate, try to get backup files meta info again, will be success.RESTORE FILELISTONLY FROM DISK=‘C:\Tmp\BackupEncrypted_FULL.bak’RESTORE HEADERONLY FROM DISK=‘C:\Tmp\BackupEncrypted_FULL.bak’RESTORE FILELISTONLY FROM DISK=‘C:\Tmp\BackupEncrypted_DIFF.bak’RESTORE HEADERONLY FROM DISK=‘C:\Tmp\BackupEncrypted_DIFF.bak’RESTORE FILELISTONLY FROM DISK=‘C:\Tmp\BackupEncrypted_log.trn’RESTORE HEADERONLY FROM DISK=‘C:\Tmp\BackupEncrypted_log.trn’还原已加密完全备份文件首先,尝试还原数据库完全备份文件,成功。USE [master]– restore encrypted full backupRESTORE DATABASE [BackupEncrypted] FROM DISK = N’C:\Tmp\BackupEncrypted_FULL.bak’ WITH FILE = 1, MOVE ‘BackupEncrypted_data’ TO N’E:\SQLDATA\DATA\BackupEncrypted_data.mdf’,MOVE ‘BackupEncrypted_MemoryOptimized’ TO N’E:\SQLDATA\DATA\BackupEncrypted_MemoryOptimized’,MOVE ‘BackupEncrypted_log’ TO N’E:\SQLDATA\DATA\BackupEncrypted_log.ldf’,NOUNLOAD, STATS = 5, NORECOVERYGO还原已加密差异备份文件其次,尝试还原数据库差异备份文件,成功。– Restore encrypted diff backupRESTORE DATABASE [BackupEncrypted] FROM DISK = N’C:\Tmp\BackupEncrypted_DIFF.bak’ WITH FILE = 1, MOVE ‘BackupEncrypted_data’ TO N’E:\SQLDATA\DATA\BackupEncrypted_data.mdf’,MOVE ‘BackupEncrypted_MemoryOptimized’ TO N’E:\SQLDATA\DATA\BackupEncrypted_MemoryOptimized’,MOVE ‘BackupEncrypted_log’ TO N’E:\SQLDATA\DATA\BackupEncrypted_log.ldf’,NOUNLOAD, STATS = 5, NORECOVERYGO还原已加密日志备份文件再次,尝试还原数据库日志备份文件,成功。– restore encrypted transaction log backupRESTORE LOG [BackupEncrypted] FROM DISK = N’C:\Tmp\BackupEncrypted_log.trn’ WITH FILE = 1, MOVE ‘BackupEncrypted_data’ TO N’E:\SQLDATA\DATA\BackupEncrypted_data.mdf’,MOVE ‘BackupEncrypted_MemoryOptimized’ TO N’E:\SQLDATA\DATA\BackupEncrypted_MemoryOptimized’,MOVE ‘BackupEncrypted_log’ TO N’E:\SQLDATA\DATA\BackupEncrypted_log.ldf’,NOUNLOAD, STATS = 10GO检查测试表数据最后,检查测试表的三条测试数据。USE [BackupEncrypted]GO– double check the three recordsSELECT * FROM dbo.testTable ORDER BY id;三条校验数据一致。清理测试环境清理掉我们的测试环境。use masterGO– clean up the environmentDROP DATABASE BackupEncrypted;GODROP CERTIFICATE MasterCert_BackupEncrypted;GODROP MASTER KEY;GO最后总结本期月报我们分享了SQL Server 2014及以上版本如何使用证书实现数据库备份加密技术,在防范脱库安全风险的同时,既能够比较好的保证用户查询性能,又不会带来额外CPU资源的消耗。参考文章SQL Server Transparent Data Encryption (TDE) Performance ComparisonSQLServer · 最佳实践 · 透明数据加密TDE在SQLServer的应用开启TDE的RDS SQL Server还原到本地环境Understanding Database Backup Encryption in SQL Server本文作者:风移阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 28, 2019 · 4 min · jiezi

为每个人提供Kubernetes端到端测试

作者:Patrick Ohly(英特尔)越来越多过去是Kubernetes组件的一部分,现在搬到在Kubernetes之外开发。例如,存储驱动程序曾经被编译成Kubernetes二进制文件,然后被转移到主机上的独立Flexvolume二进制文件中,现在作为容器存储接口(Container Storage Interface,CSI)驱动程序提供,这些驱动程序部署在Kubernetes集群内部的pod中。这对于处理此类组件的开发者来说是一个挑战:如何在这样的外部组件上对Kubernetes集群进行端到端(E2E)测试?用于测试Kubernetes本身的E2E框架具有所有必要的功能。但是,尝试在Kubernetes之外使用它很困难,只有通过仔细选择大量依赖项的正确版本才能实现。在Kubernetes 1.13中,E2E测试变得更加简单。这篇博客文章总结了Kubernetes 1.13的变化。对于CSI驱动程序开发者,它将涵盖使存储测试可用于测试第三方CSI驱动程序。如何使用它们将基于两个Intel CSI驱动程序显示:开放式基础架构经理(Open Infrastructure Manager,OIM)PMEM-CSI测试这些驱动程序是大多数这些增强功能的主要动机。E2E概述E2E测试包括几个阶段:实现测试套件。这是本篇博文的主要焦点。Kubernetes E2E框架是用Go编写的。它依赖于Ginkgo来管理测试,而断言(assertion)则依赖于Gomega。这些工具支持“行为驱动开发”,它描述了“规范”中的预期行为。在这篇博客文章中,“test”用于引用个别Ginkgo.It规范。测试使用client-go与Kubernetes集群进行交互。启动测试集群。像kubetest这样的工具可以帮忙。针对该群集运行E2E测试套件。Ginkgo测试套件可以使用ginkgo工具运行,也可以使用go test进行正常的Go测试。没有任何参数,Kubernetes E2E测试套件将基于环境变量(如KUBECONFIG)连接到默认集群,与kubectl完全相同。 Kubetest还知道如何运行Kubernetes E2E套件。Kubernetes 1.13中的E2E框架增强功能所有以下增强都遵循相同的基本模式:它们使E2E框架在Kubernetes之外更有用和更容易使用,而不会改变原始Kubernetes e2e.test二进制文件的行为。拆分供应商支持使用Kubernetes <= 1.12的E2E框架很困难的主要原因是依赖于特定于提供者的SDK,这些SDK使用了大量的软件包。只是编译它已经不简单。许多这些软件包仅在某些测试中需要。例如,测试预配置卷的安装必须首先通过一些非Kubernetes API,直接与特定存储后端通信,以管理员相同的方式配置这样的卷。现在有尝试从核心Kubernetes中删除特定于云供应商的测试。在PR#68483中采用的方法可以看作是朝着这个目标迈出的一步:不是立即剥离代码并打破所有依赖它的测试,所有特定于云供应商的代码都被移动到test/e2e/framework/providers下的可选包中。然后,E2E框架通过每个供应商包单独实现的接口访问它。E2E测试套件的作者决定将哪些软件包导入测试套件。然后通过–provider命令行标志激活供应商支持。1.13和1.14中的Kubernetes e2e.test二进制文件仍然支持与1.12中相同的供应商程序。也可以不包含任何包,这意味着只有通用供应上程序可用:“skeleton”:通过Kubernetes API访问集群,没有别的“local”:跟“skeleton”差不多,但是另外kubernetes/kubernetes/cluster中的脚本可以在运行测试套件后通过ssh检索日志外部文件测试可能必须在运行时读取其他文件,例如.yaml清单。但是Kubernetes e2e.test二进制文件应该是可用的并且完全独立,因为这简化了发布和运行它。Kubernetes构建系统中的解决方案是使用go-bindata将test/e2e/testing-manifests下的所有文件链接到二进制文件中。E2E框架过去对go-bindata的输出有很强的依赖性,现在bindata支持是可选的。通过testfiles包访问文件时,将从不同的源检索文件:相对于使用–repo-root参数指定的目录零个或多个bindata块测试参数e2e.test二进制文件采用控制测试执行的附加参数。2016年,开始尝试用Viper配置文件替换所有E2E命令行参数。但是这种努力停滞不前,这使得开发者没有明确指导他们应该如何处理特定于测试的参数。v1.12中的方法是将所有标志添加到中央test/e2e/framework/test_context.go,这对于独立于框架开发的测试不行。自PR#69105以来,建议使用普通标志包在其自己的源代码中定义其参数。标记名称必须是分层的,点分隔不同的级别,例如my.test.parameter,并且必须是唯一的。标志包强制执行唯一性,第二次注册标志时会发生混乱。新的配置包简化了多个选项的定义,这些选项存储在单个结构中。总而言之,这就是现在如何处理参数:测试包中的init代码定义了测试和参数。实际参数值尚不可用,因此测试定义不能使用它们。测试套件的init代码解析参数和配置文件(可选)。测试运行并可以使用参数值。但是,最近有人指出,比较可取且有可能不将测试设置公开为命令行标志,只能通过配置文件设置它们。关于这个有一个开放的bug和一个待定的PR。Viper支持得到了增强。与供应商支持一样,它是完全可选的。它通过导入viperconfig包被拉入e2e.test二进制文件,并在解析正常的命令行标志后调用它。这已经实现,以便当标志出现在Viper配置文件中时,也可以设置所有可以通过命令行标志设置的变量。例如,Kubernetes v1.13 e2e.test二进制文件接受–viper-config=/tmp/my-config.yaml,该文件将my.test.parameter设置为具有此内容的值:my: test: parameter: value在较旧的Kubernetes版本中,该选项只能从当前目录加载文件,后缀必须省略,实际上只能通过这种方式设置几个参数。请注意Viper的一个限制仍然存在:它通过匹配已知标志的配置文件条目,而不会发出有关未知配置文件条目的警告,从而不会检测到错别字。Kubernetes的更好的配置文件解析器仍在开发中。从.yaml创建项目清单在Kubernetes 1.12中,有一些支持从.yaml文件加载单个项目,但是然后创建该项目必须通过手写代码完成。现在,框架提供新方法加载具有多个项目的.yaml文件、修补这些项目(例如,设置为当前测试创建的命名空间)以及创建它们。这目前用于为每个测试重新部署CSI驱动程序,这些驱动程序来自完全相同的.yaml文件,这些文件也用于通过kubectl进行部署。如果CSI驱动程序支持以不同的名称运行,则测试完全独立并且可以并行运行。但是,重新部署驱动程序会降低测试执行速度,并且不会涵盖针对驱动程序的并发操作。更现实的测试场景是在启动测试集群时部署驱动程序一次,然后针对该部署运行所有测试。最终,Kubernetes E2E测试将转移到该模型,一旦更清楚如何扩展测试集群的启动,包括安装CSI驱动程序等其他实体。Kubernetes 1.14推出的增强功能重用存储测试能够使用Kubernetes之外的框架可以构建自定义测试套件。但是没有测试的测试套件仍然没用。一些现有的测试,特别是用于存储的测试,可以应用于树外组件。感谢Masaki Kimura所做的工作,Kubernetes 1.13中的存储测试被定义为可以针对不同的驱动程序多次实例化它们。但历史有重复的习惯。与供应商程序一样,定义这些测试的程序包也提取了所有树内存储后端的驱动程序定义,这反过来又拉取了比所需更多的附加程序包。这在Kubernetes 1.14进行了修复。跳过不支持的测试某些存储测试依赖于群集的功能(如在支持XFS的主机上运行)或驱动程序(如支持块卷)。在测试运行时检查这些条件,导致在不满意时跳过测试。好的是这记录解释了为什么测试没有运行。开始测试很慢,特别是当它必须首先部署CSI驱动程序时,在其他情况下也差不多。在快速集群上测量为测试创建命名空间的时间为5秒,并且会产生大量噪声测试输出。本来可以解决这个问题,通过跳过不支持的测试的定义,然后报告为什么测试甚至不是测试套件的一部分变得棘手。这种方法已不被考虑,而是采用重新组织存储测试套件的方式,以便在进行更昂贵的测试设置步骤之前首先检查条件。更易读的测试定义同样的PR还将测试重写,接近传统的Ginkgo测试,测试用例及其局部变量在一个函数中。测试外部驱动程序构建自定义E2E测试套件仍然是相当多的工作。将在Kubernetes 1.14测试档案中分发的e2e.test二进制文件将能够测试已安装的存储驱动程序,而无需重建测试套件。有关详细说明,请参阅本自述文件。E2E测试套件HOWTO测试套件初始化第一步是设置定义测试套件的必要样板代码。在Kubernetes E2E中,这是在e2e.go和e2e_test.go文件中完成的。它也可以在e2e_test.go文件中完成。Kubernetes在e2e_test.go中导入所有各种供应商程序、树内测试、Viper配置支持和bindata文件。e2e.go控制实际执行,包括一些集群准备和指标收集。一个更简单的起点是来自PMEM-CSI的e2e_[test].go文件。它不使用任何供应商程序,没有Viper,没有bindata,只导入存储测试。与PMEM-CSI一样,OIM会丢弃所有额外功能,但有点复杂,因为它将自定义集群启动直接集成到测试套件中,在这种情况下非常有用,因为一些额外的组件必须在主机端运行。通过直接在E2E二进制文件中运行它们,使用dlv进行交互式调试变得更加容易。这两个CSI驱动程序都遵循Kubernetes示例,并使用test/e2e目录作为其测试套件,但也可以使用任何其他目录和其他文件名。添加E2E存储测试测试由导入测试套件的包定义。E2E测试唯一特有的是,它们使用framework.NewDefaultFramework实例化一个framework.Framework指针(通常称为f)。此变量在每个测试的BeforeEach中重新初始化,并在AfterEach中释放。它在运行时有一个f.ClientSet和f.Namespace(并且只在运行时!),可以由测试使用。PMEM-CSI存储测试导Kubernetes存储测试套件,并为必须已安装在测试集群中的PMEM-CSI驱动程序设置一个供应测试实例。存储测试套件更改存储类以使用不同的文件系统类型运行测试。由于此要求,存储类是从.yaml文件创建的。解释框架中可用的所有各种实用方法超出了本博文的范围。阅读现有测试和框架的源代码是一个很好的入门方法。提供代码即使消除了许多不必要的依赖关系,提供Kubernetes代码仍然不是一件容易的事。k8s.io/kubernetes并不意味着包含在其他项目中,也没有以dep等工具理解的方式定义其依赖关系。其他k8s.io包应包含在内,但不遵循语义版本控制或不标记任何版本(k8s.io/kube-openapi,k8s.io/utils)。PMEM-CSI使用dep。它的Gopkg.toml文件是一个很好的起点。它启用了修剪(默认情况下未在dep中启用)并将某些项目锁定到与所使用的Kubernetes版本兼容的版本上。当dep没有选择兼容的版本时,检查Kubernetes的Godeps.json有助于确定哪个版本可能是正确的版本。编译并运行测试套件go test ./test/e2e -args -help是测试测试套件编译的最快方法。一旦编译完成并且已经设置了集群,go test -timeout=0 -v ./test/e2e -ginkgo.v命令将运行所有测试。要并行运行测试,请使用ginkgo -p ./test/e2e命令。如何参与Kubernetes E2E框架由测试SIG的testing-commons子项目所有。请参阅该页面以获取联系信息。有各种任务,包括但不限于:将test/e2e/framework移动到staging仓库并重组它以使其更加模块化(#74352)。通过将更多代码移入test/e2e/framework(#74353)来简化e2e.go。从Kubernetes E2E测试套件中删除特定于供应商程序的代码(#70194)。鸣谢特别感谢本文的审阅者:Olev Kartau(https://github.com/okartau)Mary Camp(https://github.com/MCamp859)KubeCon + CloudNativeCon + Open Source Summit大会日期:会议日程通告日期:2019 年 4 月 10 日会议活动举办日期:2019 年 6 月 24 至 26 日KubeCon + CloudNativeCon + Open Source Summit赞助方案KubeCon + CloudNativeCon + Open Source Summit多元化奖学金现正接受申请KubeCon + CloudNativeCon和Open Source Summit即将首次合体落地中国KubeCon + CloudNativeCon + Open Source Summit购票窗口,立即购票!CNCF邀请你加入最终用户社区 ...

March 27, 2019 · 1 min · jiezi

阿里工程师开发了一款免费工具,提升Kubernetes应用开发效率

对于使用了Kubernetes作为应用运行环境的开发者而言,在同一个集群中我们可以使用命名空间(Namespace)快速创建多套隔离环境,在相同命名空间下,服务间使用Service的内部DNS域名进行相互访问。 基于Kubernetes强大的隔离以及服务编排能力,可以实现一套定义编排(YAML)多处部署的能力。不过,一般来说Kubernetes使用的容器网络与开发者的所在的办公网络直接并不能直接连通。 因此,如何高效的利用Kubernetes进行服务间的联调测试,成为在日常开发工作中一道绕不开的坎。本文我们就来聊一聊,如何加速基于Kubernetes的研发效率。使用自动流水线为了能够让开发者能够更快的将修改的代码部署到集群测试环境中,一般来说我们会引入持续交付流水线,将代码的编译,镜像的打包上传以及部署通过自动化的方式来解决。如下所示:从一定程度上来说,这种方式可以避免开发人员进行大量重复性的工作。但是,虽然整个过程自动化了,但是开发人员也不得不每次进行代码变更之后都需要等待流水线的运行。对于开发人员来说,每次代码变更后等待流水线运行或许已经成为整个开发任务过程中体验最糟糕的部分。打破网络限制,本地联调理想状态下是开发者可以直接在本地启动服务,并且这个服务就可以无缝的和远程的kubernetes集群中的各个其它服务实现互相调用。需要解决两个问题:我依赖了其它的服务:运行在本地的代码可以直接通过podIP,clusterIP甚至是Kubernetes集群内的DNS地址访问到部署在集群中的其它应用,如下图左;其它的服务依赖了我:运行在Kubernetes集群中的其它应用可以在不做任何改变的情况下访问我到运行的本地的代码,如下图右。要实现刚才说的两种本地联调方式,主要需要解决以下3个问题:本地网络与Kubernetes集群网络直接的连通问题在本地实现Kubernetes中内部服务的DNS解析;如果将对集群中其它Pod访问的流量转移到本地;云效开发者工具KT为了简化在Kubernetes下进行联调测试的复杂度,云效在SSH隧道网络的基础上并结合Kubernetes特性构建了一款面向开发者的免费辅助工具KT(点击前往下载),如下所示:当本地运行的服务C’希望能够直接访问集群中default命名空间下的Service A和Service B时,运行如下命令:$ ktctl -namespace=defaultKT会自动在集群中部署SSH/DNS代理容器,并构建本地到Kubernetes集群的VPN网络并通过DNS代理实现集群服务DNS域名解析,在运行KT之后,开发者的本地程序可以直接像运行在集群中的服务一样通过service名字调用集群中部署的其它应用:而如果希望集群中的其它Pod(比如图中的PodD和PodE)能够通过ServiceC访问到本地运行的程序C‘,通过如下命令,指定需要替换的目标Deployment以及指定本地服务端口:#-swap-deployment指定需要替换的目标Deployment # -expose 指定本地服务运行的端口 ktctl -swap-deployment c-deployment -expose=8080KT在构建VPN网络的同时,还会自动通过代理容器接管集群原有的PodC实例,并直接转发的本地的8080端口。实现集群应用联调本地。经过上述两个命令,开发者就可以真正的使用云原生的方式来开发调试Kubernetes中的应用了。工作原理下面解析KT的工作原理,如果你已经迫不及待的想尝试KT的功能,可以直接前往下载KT工具。KT主要由两部分组成:在本地运行的命令行工具ktctl运行在集群中的SSH/DNS代理容器。在工作原理上KT实际上是结合Kubernetes自身能力实现的一个基于SSH的VPN网络。这这部分,笔者将详细介绍云效Kubernetes开发者工具KT的工作原理:打通SSH协议通道在Kubernetes命令行工具kubectl中内置的port-forward命令可以帮助用户建立本地端口到Kubernetes集群中特定Pod实例端口间的网络转发。当我们在集群中部署一个包含sshd服务的容器后,通过port-forward可以将容器的SSH服务端口映射到本地:# 将对本地2222端口转发到kt-porxy实例的22端口 $ kubectl port-forward deployments/kt-proxy 2222:22 Forwarding from 127.0.0.1:8080 -> 8080 Forwarding from [::1]:8080 -> 8080在运行端口转发后,就可以直接通过本地的2222端口通过SSH协议进入到Kubernetes集群的kt-proxy实例中。从而打通本地与集群之间的SSH网络链路。本地动态端口转发与VPN在打通SSH网络之后,我们就可以利用SSH通道实现本地到集群的网络请求,其中最基本的方式就是使用SSH动态端口转发的能力。使用如下命令,通过本地2000运行的代理,可以将网络请求通过集群中运行的kt-proxy容器进行转发,从而实现本地到集群网络请求的转发:# ssh -D [本地网卡地址:]本地端口 name@ip -p映射到kt-proxy的22端口的本地端口 ssh -D 2000 root@127.0.0.1 -p2222在启用SSH动态端口转发后,通过设置http_proxy环境变量后,即可直接在命令行中访问集群网络:# export http_proxy=socks5://127.0.0.1:ssh动态端口转发的代理端口 export http_proxy=socks5://127.0.0.1:2000不过原生SSH动态端口转发也有一定的限制那就是无法直接使用UDP协议,这里我们选择了一个替代方案sshuttle. 如下命令所示:# export http_proxy=socks5://127.0.0.1:ssh动态端口转发的代理端口 export http_proxy=socks5://127.0.0.1:2000 sshuttle –dns –to-ns 172.16.1.36 -e ‘ssh -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null’ -r root@127.0.0.1:2222 172.16.1.0/16 172.19.1.0/16 -vvsshuttle工具在SSH协议之上构建了一个简易的VPN网络,同时支持DNS协议转发。因此,接下来的问题就是实现一个自定义的DNS服务即可,而该服务在KT中是直接内置在KT代理镜像中。远程端口转发在本地到集群的链路打通之后。 接下来需要解决的就是从集群到本地的访问链路。这部分,我们会使用到SSH的远程端口转发能力,如下所示,指定所有对kt-proxy的8080端口的网络请求都会通过SSH隧道直接转发到本地的8080端口:# ssh -R 8080:localhost:8080 root@127.0.0.1 -p2222 ssh -R 8080:localhost:8080 root@127.0.0.1 -p2222因此,在KT的实现过程之中,结合Kubernetes基于标签的松耦合能力,我们只需要克隆原有应用实例的YAML描述,并将容器替换为kt-proxy即可。从而将对集群中原有应用的请求通过SSH远程端口转发到本地。综上,通过利用Kubernetes原生能力以及适度的扩展,开发者可以快速在本地利用KT打破本地网络与Kubernetes网络之间的界限,大大提升使用Kubernetes进行联调测试的效率。小结工具承载了对特定问题的解决方案,而工程技术实践则是对其价值的放大。阿里巴巴云效平台,致力于为开发者提供一站式的企业研发与协作服务,并将阿里多年的软件工程实践以一种更加开发的形态反馈技术社区,欢迎更多的技术开发者入驻。目前,Mac用户可以前往下载并体验KT工具作者:郑云龙,阿里巴巴研发效能部高级研发工程师本文作者:云效鼓励师阅读原文本文为云栖社区原创内容,未经允许不得转载。

March 26, 2019 · 1 min · jiezi

自动化测试|录制回放效果差异检测

概述回归测试是指修改了旧代码后,重新进行测试以确认修改没有引入新的错误或导致其他的代码出现错误。传统的自动化回归测试需要手动编写脚本获得页面元素的视图树,与原有的元素视图树进行比对。当功能进行频繁迭代时,测试同学维护这些视图元素验证点比较繁杂。因此在自动化回归测试过程中,直接比较代码修改前录制的页面和代码修改后回放的页面差异,可以快速定位代码产生的缺陷,从而提高测试同学的工作效率。以闲鱼应用举例,录制和回放页面差异检测存在的一些难点包括:图像上,闲鱼页面由顶部固定区域、中间可滚动区域、底部固定区域组成,需要对页面进行版面切割,之后分别对每一个区域进行处理。另外录制和回放页面中一些图标或者图片纹理复杂的区域往往像素值分布不同,但是语义层面又是同一个物体,因此需要从语义层面进行检测识别。业务上,两张页面中间区域因为滚动带来的差异不需要检测出来,并且一些特殊的标记差异(比如一张页面某个位置有光标,另一张页面同一位置没有光标)不需要检测出来。如上图四组录制和回放页面所示,其中每组图左边为录制页面,右边是回放页面。a)中因为中间区域的滚动带来了文字和图标的差异,b)中价格组件“¥69”右边的光标带来了差异,c)中输入框里的文字不一样,d)中“库存”那一栏的图标具有语义差别。其中a)和b)中的差异不需要检测出来,c)和d)中的差异需要检测出来。方法算法流程整个算法流程如下图所示:输入的录制和回放页面图像灰度化后进行版面切割,提取顶部区域、中间区域、底部区域两张页面的中间滚动区域进行对齐两张页面的每个区域分别进行相似度计算并排除无效的差异框两张页面中分别标记最终的差异框版面切割闲鱼大部分的页面是由顶部固定区域、中间可滚动区域、底部固定区域组成,版面切割的目的是找到中间可滚动区域的上下边界,即将页面切割为三块区域,之后对每块区域分别进行处理。版面切割只针对中间区域有滚动的情况,对于中间区域无滚动的情况即可用整张图进行后续处理。考虑到录制和回放页面在滚动区域上下边界处会产生明显的差异,因此分别从图像的第一行和最后一行开始进行比较,比较对应行的结构相似度指数(SSIM),当对应行的结构相似度指数小于给定的阈值,则终止。结构相似度指数是一种衡量两幅图像相似度的指标,其从图像组成的角度将结构信息定义为独立于亮度、对比度的反映物体结构的属性,用均值作为亮度的估计,标准差作为对比度的估计,协方差作为结构相似程度的度量。具体公式如下:其中(x,y)分别为录制和回放图像,ux,uy,x2,y2,xy分别表示图像的均值、方差和协方差,c1,c2,c3为小的正常数,避免分母为零而出现不稳定,利用参数,,调整三个成分所占的比重。在实际工程中,一般设定===1,以及c3=c2/2,可以将SSIM简化为:SSIM为介于0到1之间的一个值,值越大表明两幅图越相似。下图a)b)为高2001像素、宽1125像素的录制和回放页面图像,选取SSIM阈值为0.95,计算得到的滚动区域的上边界为192,下边界为1832,如c)所示。滚动区域对齐对齐录制和回放图像的滚动区域后便可对比同一位置的差异,本文采用基于特征点的图像对齐方法, 即检测录制图像的一组稀疏特征点来匹配回放图像的一组稀疏特征点,通过两组特征点的匹配来计算一个转换矩阵,这个转换矩阵能变换对齐两幅图像的滚动区域。由于滚动区域只在垂直方向有位移,因此采用一般的刚性配准方法,如下式:录制图像坐标(x,y)通过一个3x3的位移矩阵转换到回放图像坐标(x’,y’)。位移转换矩阵可以通过对齐两幅图像的特征点来求取。在计算机视觉中,常见的特征点描述方法有SIFT、SURF、ORB等等,本文采用ORB方法,因为相比于SIFT和SURF方法,ORB在满足足够高的准确性前提下,速度更快并且使用不受专利约束。ORB全称是Oriented FAST and Rotated BRIEF,即是由FAST特征点检测和BRIEF特征点描述组成,并且在两者基础上加入了图像金字塔和图像重心方向等改进措施使得ORB对尺度和旋转不敏感。滚动区域对齐的具体步骤为:1. 输入录制和回放滚动区域。当上一步版面切割没有计算出滚动区域时,输入整幅图像的中间部分<br>2. 检测特征点。使用ORB检测两幅图像的角点。工程实现时可以设置每幅图像最多需检测出的特征点数<br>3. 匹配特征点。利用hamming距离来衡量两组特征点的相似性以及Brute Force暴力法尝试所有特征点来找到最佳匹配。基于匹配的特征点距离值进行排序,选取距离值较小的一部分特征点<br>4. 计算转换矩阵。输入两组特征点坐标位置通过最小二乘求解最优的转换矩阵<br>5. 对齐图像。应用转换矩阵将回放图像滚动区域映射对齐录制图像滚动区域<br>下图为匹配的两组特征点,计算出来的转换矩阵中tx=0,ty=-96相似度计算及后处理对录制和回放页面的顶部区域、底部区域和对齐的滚动区域分别计算结构相似度SSIM(如果图像不存在滚动区域,则对整图计算SSIM),差异的地方用矩形框标识,后续通过一定的后处理排除掉无效的差异。具体步骤如下:差异检测。对顶部区域、底部区域和对齐的滚动区域每个像素在一定的领域范围内计算SSIM,得到同等尺寸的SSIM结果图;再对SSIM结果图用大津法得到二值化的SSIM结果图,其中有差异的地方为1,没有差异的地方为0;接着提取二值化SSIM差异部分的外接轮廓;之后再计算轮廓的最小外接矩形框。得到的结果如下红框所示:后处理。上图红框显示的是对齐滚动区域带来的差异,通过判断每个框是否落到特定区域范围内来进行排除,这个特定区域在垂直方向上以滚动区域上边界起始,向下移动ty行结束或者以滚动区域下边界起始,向上移动ty行结束。另外对于一些噪声点带来的差异,可以通过限制差异框包含的面积进行排除。对上图使用以上两步后处理后,红框全部被排除。其他一些录制和回放图像中的特殊标记(比如光标)差异在业务层面需要排除,或者一些纹理丰富的图标/图像虽然像素层面存在差异,但语义层面属于同一类,即使SSIM计算不相似,也需要排除。上图a)显示光标差异模式,b)显示纹理丰富的图标/图像差异模式,c)显示其他差异模式。这些差异模式是对应位置差异框绝对差得到的结果,其中a)和b)需要排除,c)需要保留。分析这几种不同模式类型,设计了一种如下图所示CNN二分类网络。图像缩放到64x64大小,然后输入到三层conv+pooling+relu的卷积结构中,后接128个节点和2个节点的全连接层,以softmax作为类别判定输出。通过分类网络便可将业务层面或像素层面的差异进行排除。结果一些差异检测的结果如下,其中红框标示的是在录制和回放页面中存在差异的部分。a)只有中间区域滚动带来的差异,业务上不需要检测出来; b)和c)能将页面中存在语义差异的部分检测出来并且没有误检和漏检; d)两张页面不存在语义差异,但底部有个文字区域出现了误检,分析原因是页面在没有滚动的情况下,误检区域没有上下严格对齐,导致计算的SSIM值偏小,认为此处存在差异。总结本文以闲鱼自动化回归测试应用举例,实现了一种版面切割、滚动区域对齐、相似度计算及后处理的录制和回放页面差异检测方法。这种方法在使用过程中达到了检出语义差异的预期,并且方法具有普适性,对其他app的自动化回归测试具有一定的参考意义。当然本方法也存在着一些不足,对于像素分布不同但语义相同的差异模式会有一部分误检,这是由于SSIM在计算相似度时对于局部像素值的变化比较敏感,未来可对页面中的文字和图片独立提取出来进行进一步的语义分析优化。本文作者:闲鱼技术-深宇阅读原文本文为云栖社区原创内容,未经允许不得转载。

March 20, 2019 · 1 min · jiezi

关于 E2E 测试

上一篇文章发布后,竟然收获到一些同学的注意,实在是意外之喜。不过我也发现,很多同学对 E2E 测试不够了解,正好我厂的产品也没做到能作为商用版发布的程度,所以这篇再来聊聊 E2E 测试吧。本文的测试均指自动化测试。E2E,是“End to End”的缩写,可以翻译成“端到端”测试。它模仿用户,从某个入口开始,逐步执行操作,直到完成某项工作。与单元测试不同,后者通常需要测试参数、参数类型、参数值、参数数量、返回值、抛出错误等,目的在于保证特定函数能够在任何情况下都稳定可靠完成工作。单元测试假定只要所有函数都正常工作,那么整个产品就能正常工作。相对来说,E2E 测试并没有那么强调要覆盖全部使用场景,它关注的是 一个完整的操作链是否能够完成。对于 Web 前端来说,还关注 界面布局、内容信息是否符合预期。比如,登陆界面的 E2E 测试,关注用户是否能够正常输入,正常登录;登陆失败的话,是否能够正确显示错误信息。至于输入不合法的内容是否处理,没有很大的关系。Web 前端 E2E 测试的现状Web 前端 E2E 自动化测试开展得不好。在我从业的这十几年里,大部分产品的前端 E2E 测试都交给测试人员手工完成。我们稍稍分析一下,大概有三个原因:1. 测试环境不好搭单元测试也好、接口测试也好,测试环境都很容易搭建。然而 Web 前端测试如果想达到目的,需要完整的桌面操作系统和浏览器环境,这种面向普通用户的软件对自动化工具并不友好。对系统要求也比较高,很难整合到开发测试工具链档中。解决方案当然是有的,目前最流行的应该是 Selenium WebDriver。不过对于小公司、小团队来说,在并不丰富的资料中摸着石头过河实在不够经济,而且,还有接下来的两个问题。2. 测试不好写。目前的 Web 前端 E2E 测试工具局限于 XPath 技术栈。大家用选择器查找 DOM 节点,校验其属性和内容,接着进行交互。这样做导致一个必然后果:写测试的人员对页面的 DOM 结构必须了如指掌,才能用准确目标元素。同时,在这个技术环境下写就的测试用例,一旦 DOM 结构出现变化,就要大规模的修改,甚至重写。工作量很大,而且存在一些不稳定因素。跟某团队 Leader 聊天,他就很担心漫长的迭代过程中,DOM 结构变化导致测试用例失效,继而引发项目排期混乱。结果,Web 前端 E2E 测试用例的只能由前端用 JS 写,工作量大,维护负担重,且存在一些风险。大家都不愿意写。3. 有些东西不好测随着用户对产品的要求水涨船高,页面逻辑越来越复杂,功能越发依赖 Ajax,甚至和后端彻底分离,成为单页应用(SPA)。这类产品与传统的静态页服务不同,我们没法侦听 DOMReady 事件,也就难以找到合适的时间点启动测试。早些时候,我们只能依靠 setInterval() 轮询。如今,通过 Puppeteer 或者浏览器扩展都可以监听网络连接,可以根据当前保持的连接数来判断请求是否完成。不过这些做法仍然存在不小的实施难度。4. 预期收益一般我跟很多技术老大聊过。大家的回答都是:没写,没空,招测试。在加班成为常态的今天,在“看得到”的工作之外,再去做这些“看不到”的工作,实在有些吃力不讨好。另一方面,测试写得少,覆盖率跟不上,还是得招测试,人工测试。恶行循环就此产生:不想写导致没测试;那就招测试人员;有了专职测试我还写什么测试……所以大家都不写测试了。对于中等以上规模的技术团队,招几个测试也还行。对于整个公司就只有几个人的创业团队来说,大多数时候只能裸奔……更好的 Web 前端 E2E 测试工具行文至此,结论就很明显了:我们需要全新的、更高级的 Web 前端 E2E 测试工具。这个工具需要同时满足:1. 有效可以准确地描述 UI 的结构可以尽量全面的模拟用户真实操作覆盖多种操作系统、适配各种浏览器2. 使用成本低测试用例应该尽量简短,用最少的代码描述出 UI,完成交互。测试用例应该和 DOM 实现解耦,用的尽量久,能不改就不改。测试用例应该让所有人都学得会,写得通3. 提高开发效率应该提供方便易用的编写、测试环境,让用户可以轻松上手需要能够和常见的 CI 系统集成我厂的解决方案最后回到我厂。我们设计了一个全新的语言用来描述 UI,叫做 Navlang。我们可以用它描述各元素的相对位置,操作元素进行交互。它是一个描述性语言,只包含很简单的逻辑——实际上 E2E 测试也不需要多复杂的逻辑,跑不通就是挂了,跑通了就没问题。这样一来,任何人,只要经过简单的培训,都可以写出正确的测试用例。(HTML 就是最好的例子。前端很多都是页面仔出身,比如我,相信大家对这类语言的易学易用都有所了解。)因为是语言,所以它可以写成代码,可以被版本管理;因为是新的抽象,所以它不跟 DOM 实现耦合,可以被反复使用,几乎没有维护成本。(只有界面变化才需要修改测试用例,此时无论如何都要修改测试用例)目前这个工具已经在我厂的开发体系中工作将近一年,为我厂产品的稳定做出了非常重大的贡献。功能化之外,我们也在作产品化和商品化的努力。目前已经基本完成浏览器扩展功能,让普通用户可以通过浏览器扩展编写和运行测试。接下来,我们还会提供基于 Node.js 的测试工具框架,帮助大家将测试集成到现有的 CI 系统当中。未来,这款产品会服务广大小型公司和小型团队,帮助大家提升 Web UI 测试的效率。好了,敬请期待下周的 Navlang 介绍吧。 ...

March 15, 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

Go 单元测试和性能测试

测试对于互联网应用软件开发来说非常重要,它对软件可靠性保证具有重要意义,通过测试能够尽可能发现并改正软件中的错误,提高软件质量。这里我们主要讲解Go语言如何实现单元测试和性能测试。go语言中自带有一个轻量级的测试框架testing和自带的go test命令来实现单元测试和性能测试,testing框架和其他语言中的测试框架类似,你可以基于这个框架写针对相应函数的测试用例,也可以基于该框架写相应的压力测试用例,那么接下来让我们一一来看一下怎么写。单元测试创建目录test,在目录下创建add.go、add_test.go两个文件,add_test.go为单元测试文件。add_test.gopackage testimport “testing"func TestAdd(t *testing.T) { sum := Add(1, 2) if sum == 3 { t.Log(“the result is ok”) } else { t.Fatal(“the result is wrong”) }}func TestAdd1(t *testing.T) { t.Error(“the result is error”)}add.gopackage testfunc Add(a, b int) int { return a + b}然后在项目目录下运行go test -v就可以看到测试结果了=== RUN TestAdd— PASS: TestAdd (0.00s) add_test.go:8: the result is ok=== RUN TestAdd1— FAIL: TestAdd1 (0.00s) add_test.go:14: the result is errorFAILexit status 1FAIL /D/gopath/src/ados/test 0.419s如果看到PASS字样证明测试通过,FAIL字样表示测试失败。使用testing库的测试框架需要遵循以下几个规则如下:文件名必须是_test.go结尾的,这样在执行go test的时候才会执行到相应的代码你必须import testing这个包所有的测试用例函数必须是Test开头测试用例会按照源代码中写的顺序依次执行测试函数TestXxx()的参数是testing.T,我们可以使用该类型来记录错误或者是测试状态测试格式:func TestXxx (t *testing.T),Xxx部分可以为任意的字母数字的组合,但是首字母不能是小写字母[a-z],例如Testintdiv是错误的函数名。函数中通过调用testing.T的Error, Errorf, FailNow, Fatal, FatalIf方法,说明测试不通过,调用Log方法用来记录测试的信息。性能测试或压力测试压力测试用来检测函数(方法)的性能,和编写单元功能测试的方法类似,此处不再赘述,但需要注意以下几点:压力测试用例必须遵循如下格式,其中XXX可以是任意字母数字的组合,但是首字母不能是小写字母func BenchmarkXXX(b testing.B) { … }go test不会默认执行压力测试的函数,如果要执行压力测试需要带上参数-test.bench,语法:-test.bench=“test_name_regex”,例如go test -test.bench=”.“表示测试全部的压力测试函数在压力测试用例中,请记得在循环体内使用testing.B.N,以使测试可以正常的运行文件名也必须以_test.go结尾在test目录下创建 reflect_test.gopackage testimport ( “reflect” “testing”)type Student struct { Name string Age int Class string Score int}func BenchmarkReflect_New(b *testing.B) { var s *Student sv := reflect.TypeOf(Student{}) b.ResetTimer() for i := 0; i < b.N; i++ { sn := reflect.New(sv) s, _ = sn.Interface().(*Student) } _ = s}func BenchmarkDirect_New(b *testing.B) { var s *Student b.ResetTimer() for i := 0; i < b.N; i++ { s = new(Student) } _ = s}func BenchmarkReflect_Set(b *testing.B) { var s *Student sv := reflect.TypeOf(Student{}) b.ResetTimer() for i := 0; i < b.N; i++ { sn := reflect.New(sv) s = sn.Interface().(*Student) s.Name = “Jerry” s.Age = 18 s.Class = “20005” s.Score = 100 }}func BenchmarkReflect_SetFieldByName(b *testing.B) { sv := reflect.TypeOf(Student{}) b.ResetTimer() for i := 0; i < b.N; i++ { sn := reflect.New(sv).Elem() sn.FieldByName(“Name”).SetString(“Jerry”) sn.FieldByName(“Age”).SetInt(18) sn.FieldByName(“Class”).SetString(“20005”) sn.FieldByName(“Score”).SetInt(100) }}func BenchmarkReflect_SetFieldByIndex(b testing.B) { sv := reflect.TypeOf(Student{}) b.ResetTimer() for i := 0; i < b.N; i++ { sn := reflect.New(sv).Elem() sn.Field(0).SetString(“Jerry”) sn.Field(1).SetInt(18) sn.Field(2).SetString(“20005”) sn.Field(3).SetInt(100) }}func BenchmarkDirect_Set(b testing.B) { var s Student b.ResetTimer() for i := 0; i < b.N; i++ { s = new(Student) s.Name = “Jerry” s.Age = 18 s.Class = “20005” s.Score = 100 }}在test目录下,执行:go test reflect_test.go -test.bench=”.“结果如下goos: windowsgoarch: amd64BenchmarkReflect_New-4 20000000 84.9 ns/opBenchmarkDirect_New-4 30000000 50.6 ns/opBenchmarkReflect_Set-4 20000000 89.9 ns/opBenchmarkReflect_SetFieldByName-4 3000000 552 ns/opBenchmarkReflect_SetFieldByIndex-4 10000000 132 ns/opBenchmarkDirect_Set-4 30000000 53.0 ns/opPASSok command-line-arguments 10.982s上面的结果显示我们没有执行任何TestXXX的单元测试函数,显示的结果只执行了压力测试函数,以第三行为例BenchmarkReflect_New 函数执行了20000000次,每次的执行平均时间是84.9纳秒。最后一行 command-line-arguments 10.982s,代表总的执行时间为 10.982s。如果只想对某个函数测试,以BenchmarkReflect_New 为例,执行命令go test reflect_test.go -test.bench=“BenchmarkReflect_New"结果为:goos: windowsgoarch: amd64BenchmarkReflect_New-4 20000000 84.9 ns/opPASSok command-line-arguments 2.490s如果测试整个目录下的所有测试执行:go test -test.bench=”.“如果想显示内存分配的次数和大小添加 -benchmemgo test reflect_test.go -benchmem -test.bench=”.“goos: windowsgoarch: amd64BenchmarkReflect_New-4 20000000 88.3 ns/op 48 B/op 1 allocs/opBenchmarkDirect_New-4 30000000 53.8 ns/op 48 B/op 1 allocs/opBenchmarkReflect_Set-4 20000000 90.9 ns/op 48 B/op 1 allocs/opBenchmarkReflect_SetFieldByName-4 3000000 564 ns/op 80 B/op 5 allocs/opBenchmarkReflect_SetFieldByIndex-4 10000000 135 ns/op 48 B/op 1 allocs/opBenchmarkDirect_Set-4 30000000 52.4 ns/op 48 B/op 1 allocs/opPASSok command-line-arguments 12.955s后两列代表分配的内存大小和次数(48 B/op 1 allocs/op)推荐gotests它是编写Go测试的一个Golang命令行工具,可以根据目标源文件的函数和方法签名生成表驱动的测试。将自动导入测试文件中的任何新依赖项。参考:https://studygolang.com/stati…https://www.cnblogs.com/yjf51...links目录 ...

March 12, 2019 · 2 min · jiezi

RocketMQ 在平安银行的实践和应用

随着互联网金融业务和相关技术的不断发展,传统金融行业为满足业务快速发展需求,正在积极引入各类开源技术,以快速抢占市场。那么,以金融和科技作为双驱动的平安银行在开源技术的引入方面是如何评估,运用到哪些业务场景,以及面对复杂的网络环境,是如何去部署的呢?本文将以 Apache RocketMQ 为例,和您一起了解平安银行在开源技术选型方面的思考和实践。RocketMQ 在平安银行的应用场景;复杂网络环境下的部署实践;多隔离区场景下的部署情况;多 IDC 场景下的部署情况;改造实践和遇到的小插曲;RocketMQ 在平安银行的应用场景目前,平安银行通过 RocketMQ 解决了数据预加、信息通知和状态变化方面的业务需求,接下来,我们通过 App 登录、资产总览和工资理财 3 个应用场景来展开讲下。App 登录:当用户打开平安银行 App 的时候,系统会根据用户的登录 ID 去加载相应的用户数据,比如银行卡、信用卡和理财产品等,以及一些系统通知。这个场景下,我们用到了 RocketMQ 的异步处理能力,即预加载需要使用的数据,提升用户体验。资产总览:进入平安银行 App 资产总览的页面,页面显示账户余额、各类理财产品(黄金、基金和股票等),以及贷款等方面的信息。平安银行对接了各类基金、黄金和股票等来自不同金融主体、不同系统的数据,具有种类多、异构系统多和变化快的特点。我们的目标就是让资产总览数据尽可能准确,不同种类的资产变动的时候发出通知,资产系统收到通知后,根据变化的数据来计算出用户当前的资产总览。工资理财:工资理财是指每月工资下发到银行卡后,系统可以实现自动买入用户设置好的理财产品,例如买一些定投类的理财产品。这里信息的传递流程是:银行卡里的余额出现变动,系统把变动信息发送到消息引擎Consumer 端进行消费,通知用户账户已经出现变化;系统判断变动是否来源于代发工资;如果是,系统会再发送一条消息;理财的 Consumer 进行消费;判断现在是不是应该买入工资理财类的产品;如果是,自动买入用户设定好的理财产品;自动买入之后,余额再次变动,系统会再一次通知,这一次通知,判断的就是一些其他的逻辑了。那么,在这些场景中,我们对消息引擎会有哪些要求呢?A、高可用、高可靠和高性能,这是金融行业引入开源技术的基本要求;B、堆积能力,代发工资的用户很多,一个公司的员工会在某个时间点集中发放;C、顺序能力,即账户变动在先,发出通知在后;D、事务性能力,如果没有事务性,有可能会出现账户变动了,但没有触发购买理财产品的情况;E、重试和延迟消息功能,比如工资发放的时候,可能是在晚上,这时候系统会自动把购买理财的动作放在第二天白天去操作,再发出通知;F、消息回溯能力,就是出现问题的时候,我们可以把这个消息进行回溯,重新进行消息的处理,提供完整的消息轨迹;在技术选型的过程中,RocketMQ 符合我们在这些典型使用场景中对消息产品的需求,在引入的过程中,平安银行也对社区做了相应的贡献。复杂网络环境下的部署实践多测试子环境下的服务调用场景平安银行有多套测试子环境,用来对不同的feature进行测试,即图中的 FAT、FAT001、FAT002、FAT003等。传统银行系统由大型机时代向更面向互联网用户的零售时代转型过程中,不可避免微服务化,传统较集中式系统会被划分为较多的微服务,正常的情况如下图,服务 A 调用服务 B,服务 B 调用服务 C,服务 C 调用服务 D。随着业务的需求,新的 feature,我们需要对服务 A 和 B 进行改动。相比在FAT环境里去部署测试,更合适的方式是另起一套 FAT 环境,这里我们命名为 FAT001,把服务A和B部署上去,A 调用 B,B会调用原来 FAT 环境里的 C 和 D。此时,另一个新的需求,需要对服务 A 和 C 进行改动。如果直接发布到FAT 或 FAT001 肯定是不对的,会影响正在进行的测试,此时,我们会再起一套测试环境,命名为FAT002,发布服务 A 和 C。由于 FAT002 里没有服务 B,所以服务A要调用服务 B 就需要去 FAT 环境(FAT 定义为较稳定的测试子环境)。服务 B 调用服务 C 的时候,就不应该调用本环境的 C了,而是调动 FAT002 的 C 才能实现测试功能。再假设,系统同时还会有另外一个 feature 在测试 C 和 D,此时的调用逻辑也是一样的,系统调用服务 A 和 B 的时候是在 FAT,调用服务 C 和 D 的时候会到 FAT003 的环境。以上的服务调用场景是可以通过微服务框架解决的,进行全链路测试,但在生产环境中,用户的真实请求比测试环境中会更复杂一些。真实的用户请求过程我们看一下真实的用户请求。APP发起一个请求请求,进入网关,需要知道请求哪一个测试环境。通常的做法是:测试人员需要在APP上选好子环境,例如选择 FAT001,我们可以直接把请求 FAT001 的网关(每个环境网关单独部署),或者在requestheader上添加标识,让网关去区分它来源于哪一个环境(网关统一部署)。假如网关判断请求来源于 FAT002,那就会把分发给 FAT002环境进行处理。消息层面,如何处理这类用户请求以上是服务调用的请求路径,比较好理解,但到了消息队列上,问题会变得复杂些,假如一个 feature 只是更改了消费者,那如何把这条消息传递到改的消费者应用上去进行测试,而不被其它环境的消费者消费掉,这是我们需要去考虑的问题。来看下具体的情况,集群部署了 Broke A 和 Broke B,TopicA 分别部署在这两个Broker上。 此时,Producer Group A 向 Topic A 中写数据,Consumer Group A去消费,这种情况下是不会有任何问题。但如果新增一套 FAT001 的环境,要求 FAT001 发布的消息,只能由 FAT001 来消费,FAT 不能消费,这种情况下我们该怎么解决?在消息上面加一些路由、或是加一些Tag、Filter、消息的Property?这些都不能解决我们的问题。️每个子环境部署一套 RocketMQ?一方面成本太高,另一方面如果这个feture测试完成了,需要把相关的 应用再切回 FAT 进行处理,实现太过复杂。️我们想一下,多个 feature 同时进行测试,DB 会部署一套还是多套?首先一个 feature 不会更改所在的应用,一般来说 DB 是部署一套的,在数据库里面添加字段,来识别数据是来源于哪一个子环境,如果多套部署,不更改的应用取不到新部署的 DB 数据,无法进行全链路测试,所以同样的,我们也没有在每个子环境都部署一套 RocketMQ,而是部署统一部署,通过 RPC 路由把请求路由到正确的生产者集,改造消息路由算法把消息路由到正确的消费者进行处理。真实的用户请求过程在上图中生产者变更的场景下,默认的场景 FAT发送,FAT 消费 ,没有问题的,假设 FAT001 的生产者发布了,需要 FAT001 发送到MQ集群,FAT 是可以直接消费。在上图生产者和消费者同时变更的场景下,如果消费者在 FAT001也部署了应用,需要FAT消费者不能消费由FAT001产生的消息,而是由 FAT001的消费者去消费。我们的处理方式是在逻辑上把Topic A下面的Queue进行分组,相当于加了逻辑分组,如果消费者在 FAT001 有部署,我们会把 Queue 的分组扩大,在其默认设置的情况下再翻一倍,新增的 Queue 就是给到 FAT001 进行消费的。再来看看只有消费者变更的场景,如上图。假设有个feature只需要更改消费者,部署在 FAT001。也是可以通过逻辑分组的方式,实现生产者根据请求来源来发送消息到队列 FAT001 逻辑分组内的 Queue,从而只让部署在 FAT001 的消费者消费。通过以上 3 个场景,我们了解到添加逻辑分组的必要性,实现过程并不复杂。主要做了以下调整:️这个逻辑分组什么时候建立?新建 Topic 的时候,全部建好?还是 Consumer 上线/下线的时候动态的去进行调整?我们选择了动态创建的方式,这个过程中,我们添加了 Meta Server 进行元数据管理,进行动态创建:添加 Meta Service,管理的元数据包括 Producer、Consumer、Topic、Queue 和 Subenv等信息:调整 Producer,取Request Head 里面请求来源(FAT、FAT001、FAT002…),如果 Topic 对应的存在分组,选择分组的 Queue,否则发到默认分组呢的Queue;调整 Consumer,上线时判断应用部署的分组(FAT、FAT001、FAT002…),如果Topic不存在对应的分组,则新建;存在,则 rebalalce (新Consumer节点上线),下线时,判断该分组是否还存在 其它Consumer实例,若不存在,删除分组,否则 rebalalce(Consumer某一节点下线);多隔离区场景下的部署实践由于对安全上的重视,金融行业的网络环境相比其他行业会更复杂。整个隔离区分为以下几个部分:DMZ 区:外网可以直接访问,用于放置网关;Web 区:面向的是用户手机,或者网页上可以看到的功能应用;核心区:包含核心的调用逻辑功能,和交易、订单相关的核心应用,例如 DB 和存储;外联区:用于访问外网,有时候也会部署一些 Poxy 代理,因为内网不能直接访问外网,需要通过代理去访问外网;专用区域:对接基金、三方存管等外部系统。在金融行业,如果某个系统是闭环的,那必须要去做隔离;管理区:是指对整个区域的应用要进行集中管理,且数据流动是单向的,只能取数据,不能通过管理区把某区域的数据推送到另一区域。此外,从安全的角度出发,所有的区域在生产环境下都通过防火墙进行隔离,这就给我们部署 RocketMQ 带来了很大的实施难度。如果只部署一套,面对这么多的防火墙,生产者消费者到集群的的流量跨墙,会给网络带来极大的不稳定,遇到瓶颈,防火墙几乎无法在线平滑扩容;如果每个子环境都部署一套,又带来运维复杂度,而且还是存在数据同步和跨墙消费的问题。最终,我们采用的是折中的办法,即统一部署加分隔离区部署,这样做的益处是:防火墙是开大策略,保证安全的前提下,符合监管要求;针对跨隔离区消费的问题,我们采用复制的方式,模拟消费者重新写入目标集群;多IDC场景下的部署实践同城多IDC,可以认为近似局域网,比较好处理,但异地多IDC多活场景,目前我们还没有特别好的解方案,多活不可避免面临数据分片、数据合并和数据冲突的解决等问题。如果 Topic 下数据有多活需求,我们暂时通过复制方式来处理。但这类手工模拟消费者消费数据写入新集群的复制方式,会存在一些问题,即复制到另一个集群之后 offset 会改变,处理逻辑就会有偏差。我们通过 pull 的方式自定义的去根据 offset 去进行消费。当故障转移到新的集群需要去消费的时候,需要获取到新集群里面正确的offset 值。此时,这个值和之前集群里的已经不一样了,需要根据时间得到新集群里正确的offset 值,再去进行消费。在没有更好的解决方案前,治理就显得很关键了。不过,我们注意到,在 RocketMQ 最新发布的版本里,提供了 DLedger 的特性,DLedger 定位的是一个工业级的 Java Library,可以友好地嵌入各类 Java 系统中,满足其高可用、高可靠、强一致的需求。我们会尽快对这一特性进行集成和测试。改造实践和遇到的小插曲我们在对 RocketMQ 的使用过程中,添加了以下功能或特性:A. 为生产者提供消息的堆积能力。B. 将所有配置管理部署到配置中心,并做云端化处理,以满足动态修改的需求。C. 在 4.3 版本还未提供事务处理能力前,我们在本地数据库里去建一张消息表,数据库更改数据状态的时候,会同步将数据写入消息表。若发送失败,则进行补偿。并在客户端里进行封装。D. 实现统一的消息者幂等处理。E. 添加身份认证和消息认证(注:RocketMQ 4.3 版本中已经实现身份认证功能)当然,也遇到了一些小插曲,基本都是使用上的小问题,可能大家也会碰到:A. 一个应用使用多个RocketMQ集群时,未加载到正确的配置。在Client 端,如果没有对 instancename 进行配置,一个应用连多个集群会失败。B. 在大数据的场景下,内存溢出。订阅的 Topic 越多,每个 Queue 在本地缓存的 message 也会越多,默认每个 Queue 1000条,可能会把内存打爆,可根据实际情况调整。C. 删除数据时 IO 抖动,默认每天凌晨4点删除数据,量上来后出现 IO 抖动,配置了消息删除策略,默认逗号分隔开,多配几个时间删除就可以了。D. Broker上日志报延迟消息找不到数据文件。在主备切换的演练过程中,出现了延迟消息在 Broker 上处理异常的情况。当主切换到备端时,延迟消息在 Broker 上保存的文件被自动删除,再切回主,由于延时消息的元数据感觉在,会查询文件进行处理,此时已找不到文件。E. 挂 NAS 的时候,IP 获取了 NAS 的私网地址,并被提交给了服务端。以上就是我们在部署过程中遇到的一些小插曲,基本都是可以很快定位原因,解决的。总的来看,RocketMQ 对平安银行的消息系统建设的帮助是非常大的,尤其是满足了数据持久化、顺序消费和回溯的需求,此外,在消息的查询方面,也比我们之前使用的消息引擎好很多。最后再分享一点自己对中间件的一点感悟:中间件使用重在治理,规范不先行,开发两行泪。本文作者:吴建峰,GitHub ID @devilfeng,来自平安银行平台架构部基础框架团队。更多 RocketMQ 的实践案例:RocketMQ x 微众银行RocketMQ x 同程艺龙RocketMQ x 滴滴出行本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 11, 2019 · 2 min · jiezi

Dubbo Mesh 在闲鱼生产环境中的落地实践

本文作者至简曾在 2018 QCon 上海站以《Service Mesh 的本质、价值和应用探索》为题做了一次分享,其中谈到了 Dubbo Mesh 的整体发展思路是“借力开源、反哺开源”,也讲到了 Service Mesh 在阿里巴巴的发路径将经历以下三大阶段:撬动做透价值渗透实现技术换代Dubbo Mesh 在闲鱼生产环境的落地,分享的是以多语言为撬动点的阶段性总结。文章首发于「QCon」,阿里巴巴中间件授权转载。闲鱼场景的特点闲鱼采用的编程语言是 Dart,思路是通过 Flutter 和 Dart 实现 iOS、Android 两个客户端以及 Dart 服务端,以“三端一体”的思路去探索多端融合的高效软件开发模式。更多细节请参考作者同事陈新新在 2018 QCon 上海站的主题分享《Flutter & Dart 三端一体化开发》。本文将关注三端中的 Dart 服务端上运用 Dubbo Mesh 去解耦 Dubbo RPC 框架的初步实践成果。Dart 服务端是一个服务调用胶水层,收到来自接入网关发来的 HTTP 请求后,通过 C++ SDK 调用集团广泛提供的 Dubbo 服务完成业务逻辑处理后返回结果。然而,C++ SDK 的功能与 Java 的存在一定的差距,比如缺失限流降级等对于保障大促稳定性很重要的功能。从长远发展的角度,闲鱼团队希望通过 Dubbo Mesh 能屏蔽或简化不同技术栈使用中间件(比如,RPC、限流降级、监控、配置管理等)的复杂性。这一诉求的由来,是闲鱼团队通过几个月的实践,发现在 Dart 语言中通过 C++ SDK 逐个接入不同中间件存在定制和维护成本高的问题。值得说明,所谓的“定制”是因为 C++ SDK 的能力弱于 Java SDK 而要做补齐所致。Dart 服务端自身的业务逻辑很轻且在一些场景下需要调用 20 多次 Dubbo 服务,这对于 Dubbo Mesh 的技术挑战会显得更大。在 Dubbo Mesh 还没在生产环境落地过而缺乏第一手数据的情形下,其性能是否完全满足业务的要求是大家普遍关心的。架构与实现图中的虚框代表了一个Pouch容器(也可以是一台物理机或虚拟机)。左边两个容器部署了 Dubbo Mesh,剩下最右边的则没有。目前 Dubbo Mesh 主要包含 Bonder、Pilot、Envoy 三个进程,以及被轻量化的 Thin SDK。其中:Envoy 承担了数据平面的角色,所有 mesh 流量将由它完成服务发现与路由而中转。Envoy 由 Lyft 初创且目前成为了 CNCF 的毕业项目,我们在之上增加了对 Dubbo 协议的支持,并将之反哺到了开源社区(还有不少代码在等待社区 review 通过后才能进到 GitHub 的代码仓库)。Pilot 和 Bonder 共同承担控制平面的角色,实现服务注册、进程拉起与保活、集群信息和配置推送等功能。Pilot 进程的代码源于开源 Istio 的 pilot-discovery 组件,我们针对阿里巴巴集团环境做了一定的改造(比如,与Nacos进行适配去访问服务注册中心),且采用下沉到应用机器的方式进行部署,这一点与开源的集群化部署很不一样。背后的思考是,Pilot 的集群化部署对于大规模集群信息的同步是非常大的一个挑战,今天开源的 Istio 并不具备这一能力,未来需要 Nacos 团队对之进行增强,在没有完全准备好前通过下沉部署的方式能加速 Service Mesh 的探索历程。Thin SDK 是 Fat SDK 经过裁剪后只保留了对 Dubbo 协议进行编解码的能力。为了容灾,当 Thin SDK 位于 Consumer 侧时增加了一条容灾通道,细节将在文后做进一步展开。数据链路全部采用单条 TCP 长连接,这一点与非 mesh 场景是一致的。Pilot 与 Envoy 两进程间采用的是 gRPC/xDS 协议进行通讯。图中同时示例了 mesh 下的 Consumer 能同时调用 mesh 下的服务(图中以 www.mesh.com 域名做示例)和非 mesh 下的服务(图中以 www.non-mesh.com 域名做示例)。闲鱼落地的场景为了避免对 20 多个依赖服务进行改造,流量走的是 mesh 下的 Consumer 调用非 mesh 下的 Provider 这一形式,读者可以理解为图中最左边的容器部署的是 Dart 服务端,它将调用图中最右边容器所提供的服务去实现业务逻辑。容灾从 Dubbo Mesh 下的 Provider 角度,由于通常是集群化部署的,当一个 Provider 出现问题(无论是 mesh 组件引起的,还是 Provider 自身导致的)而使服务无法调通时,Consumer 侧的 Envoy 所实现的重试机制会将服务请求转发到其他 Provider。换句话说,集群化部署的 Provider 天然具备一定的容灾能力,在 mesh 场景下无需特别处理。站在 Dubbo Mesh 的 Consumer 立场,如果完全依赖 mesh 链路去调用 Provider,当 mesh 链路出现问题时则会导致所有服务都调不通,这往往会引发业务可用性问题。为此,Thin SDK 中提供了一个直连 Provider 的机制,只不过实现方式比 Fat SDK 轻量了许多。Thin SDK 会定期从 Envoy 的 Admin 接口获取所依赖服务的 Provider 的 IP 列表,以备检测到 mesh 链路存在问题时用于直连。比如,针对每一个依赖的服务获取最多 10 个 Provider 的 IP 地址,当 mesh 链路不通时以 round robin 算法向这些 Provider 直接发起调用。由于容灾是针对 mesh 链路的短暂失败而准备的,所以 IP 地址的多少并不是一个非常关键的点。Thin SDK 检测 mesh 链路的异常大致有如下场景:与 Envoy 的长连接出现中断,这是 Envoy 发生 crash 所致。所发起的服务调用收到 No Route Found、No Healthy Upstream 等错误响应。优化在闲鱼落地 Dubbo Mesh 的初期我们走了一个“弯路”。具体说来,最开始为了快速落地而采用了 Dubbo over HTTP 1.1/2 的模式,也即,将 Dubbo 协议封装在 HTTP 1.1/2 的消息体中完成服务调用。这一方案虽然能很好地享受 Envoy 已完整支持 HTTP 1.1/2 协议而带来的开发工作量少的好处,但性能测试表明其资源开销并不符合大家的预期。体现于,不仅 Consumer 侧使用 mesh 后带来更高的 CPU 开销,Provider 侧也因为要提供通过 HTTP 1.1/2 进行调用的能力而导致多出 20% 的 CPU 开销且存在改造工作。最终,我们回到让 Envoy 原生支持 Dubbo 协议的道路上来。Envoy 支持 Dubbo 协议经历了两大阶段。第一个阶段 Envoy 与上游的通讯并没有采用单条长连接,使得 Provider 的 CPU 开销因为多连接而存在不可忽视的递增。第二个阶段则完全采用单条长连接,通过多路复用的模式去除了前一阶段给 Provider 所带去的额外 CPU 开销。Dubbo Mesh 在闲鱼预发环境上线进行性能与功能验证时,我们意外地发现,Istio 原生 Pilot 的实现会将全量集群信息都推送给处于 Consumer 侧的 Envoy(Provider 侧没有这一问题),导致 Pilot 自身的 CPU 开销过大,而频繁的全量集群信息推送也使得 Envoy 不时会出现 CPU 负荷毛刺并遭受没有必要的内存开销。为此,我们针对这一问题做了集群信息按需加载的重大改造,这一优化对于更大规模与范围下运用 Dubbo Mesh 具有非常重要的意义。优化的大致思路是:Thin SDK 提供一个 API 供 Consumer 的应用在初始化时调用,周知其所需调用的服务列表。Thin SDK 通过 HTTP API 将所依赖的服务列表告诉 Bonder,Bonder 将之保存到本地文件。Envoy 启动时读取 Bonder 所保存的服务列表文件,将之当作元信息转给 Pilot。Pilot 向 Nacos 只订阅服务列表中的集群信息更新消息且只将这些消息推送给 Envoy。监控可观测性(observability)是 Service Mesh 非常重要的内容,在服务调用链路上插入了 Envoy 的情形下,愈加需要通过更强的监控措施去治理其上的所有微服务。Dubbo Mesh 的监控方案并没有使用 Istio/Mixer 这样的设计,而是沿用了阿里巴巴集团内部的方式,即信息由各进程以日志的形式输出,然后通过日志采集程序将之送到指定的服务端进行后期加工并最终展示于控制台。目前 Dubbo Mesh 通过 EagleEye 去跟踪调用链,通过ARMS去展示其他的监控信息。性能评估为了评估 Dubbo Mesh 的性能能否满足闲鱼业务的需要,我们设计了如下图所示的性能比对测试方案。其中:测试机器是阿里巴巴集团生产环境中的 3 台 4 核 8G 内存的 Pouch 容器。蓝色方框代表的是进程。测试数据全部从部署了 DartServer 和 Envoy 两进程的测试机 2 上获得。性能数据分别在非 mesh(图中红色数据流)和 mesh(图中蓝色数据流)两个场景下获得。显然,Mesh 场景下的服务流量多了 Envoy 进程所带来的一跳。DartServer 收到来自施压的 Loader 进程所发来的一个请求后,将发出 21 次到 Provider 进程的 RPC 调用。在评估 Dubbo Mesh 的性能时,这 21 次是串行发出的(下文列出的测试数据是在这一情形下收集的),实际闲鱼生产环境上线时考虑了进行并行发送去进一步降低整体调用时延(即便没有 mesh 时,闲鱼的业务也是这样实现的)。Provider 进程端并没有部署 Envoy 进程。这省去了初期引入 Dubbo Mesh 对 Provider 端的改造成本,降低了落地的工作量和难度。设计测试方案时,我们与闲鱼的同学共创了如何回答打算运用 Dubbo Mesh 的业务方一定会问的问题,即“使用 Dubbo Mesh 后对 RT(Response Time)和 CPU 负荷的影响有多大”。背后的动机是,业务方希望通过 RT 这一指标去了解 Dubbo Mesh 对用户体验的影响,基于 CPU 负荷的增长去掌握运用新技术所引发的成本。面对这一问题通常的回答是“在某某 QPS 下,RT 增加了 x%,CPU 负荷增加了 y%”,但这样的回答如果不进行具体测试是无法给出的(会出现“鸡和蛋的问题”)。因为每个业务的天然不同使得一个完整请求的 RT 会存在很大的差别(从几毫秒到几百毫秒),而实现业务逻辑所需的计算量又最终决定了机器的 CPU 负荷水平。基于此,我们设计的测试方案在于评估引入 Dubbo Mesh 后,每经过一跳 Envoy 所引入的 RT 和 CPU 增量。当这一数据出来后,业务方完全可以基于自己业务的现有数据去计算出引入 Dubbo Mesh 后的而掌握大致的影响情况。显然,背后的逻辑假设是“Envoy 对于每个 Dubbo 服务调用的计算量是一样的”,事实也确实如此。测试数据以下是 Loader 发出的请求在并发度为 100 的情形下所采集的数据。表中:Envoy 的 QPS 是 Loader 的 21 倍,原因在上面测试方案部分有交代。“单跳”的数据是从“21 跳合计”直接除以 21 所得,其严谨性值得商榷,但用于初步评估仍具参考价值(有数据比没有数据强)。“整机负荷”代表了在 mesh 场景下测试机器 2 上 DartServer 和 Envoy 两进程的 CPU 开销总和。测试表明,CPU 负荷高时 Envoy 带来的单跳 RT 增幅更大(比如表中 Loader 的 QPS 是 480 时)。给出整机负荷是为了提醒读者关注引入 mesh 前业务的正常单机水位,以便更为客观地评估运用 Dubbo Mesh 将带来的潜在影响。“CPU 负荷增幅”是指 CPU 增加的幅度。由于测试机是 4 核的,所以整机的 CPU 负荷是 400。从表中数据来看,随着机器整体负荷的增加“CPU 负荷增幅”在高段存在波动,这与 RT 在高段的持续增大存在相关,从 RT 在整体测试中完全符合线性增长来看整体数据合理。当然, 后面值得深入研究数据背后的隐藏技术细节以便深入优化。线上数据Dubbo Mesh 正式生产环境上线后,我们通过对上线前后的某接口的 RT 数据进行了全天的比对,以便大致掌握 mesh 化后的影响。2019-01-14 该接口全面切成了走 Dubbo Mesh,我们取的是 2019-01-20 日的数据。图中蓝色是 mesh 化后的 RT 表现(RT 均值 3.3),而橙色是 mesh 化前的 RT 表现(RT 均值 3.27,取的是 2019-01-13 的数据)。由于线上每天的环境都有所不同,要做绝对的比较并不可能。但通过上面的比较不难看出,mesh 化前后对于整体 RT 的影响相当的小。当整体 RT 小于 5 毫秒是如此,如果整体 RT 是几十、几百毫秒则影响就更小。为了帮助更全面地看待业务流量的波动特点,下面分别列出了两天非 mesh(2019-01-06 和 2019-01-13)和两天 mesh(2019-01-20 和 2019-01-23)的比对数据。总之,生产环境上的数据表现与前面性能评估方案下所获得的测试数据能很好地吻合。洞见Dubbo Mesh 在闲鱼生产环境的落地实践让我们收获了如下的洞见:服务发现的时效性是 Service Mesh 技术的首要关键。 以集群方式提供服务的情形下(这是分布式应用的常态),因为应用发布而导致集群中机器状态的变更如何及时准确地推送到数据平面是极具挑战的问题。对于阿里巴巴集团来说,这是 Nacos 团队致力于解决的问题。开源版本的 Istio 能否在生产环境中运用于大规模分布式应用也首先取决于这一能力。频繁的集群信息推送,将给控制平面和数据平面都带去负荷扰动,如何通过技术手段控制好扰动是需要特别关注的,对于数据平面来说编程语言的“确定性”(比如,没有 VM、没有 GC)在其中将起到不可忽视的作用。数据平面的软件实现最大程度地减少内存分配与释放将显著地改善性能。有两大举措可以考虑:逻辑与数据相分离。 以在 Envoy 中实现 Dubbo 协议为例,Envoy 每收到一个 RPC 请求都会动态地创建 fitler 去处理,一旦实现逻辑与数据相分离,filter 的创建对于每一个 worker 线程有且只有一次,通过这一个 filter 去处理所有的 RPC 请求。使用内存池。 Envoy 的实现中基本没有用到内存池,如果采用内存池对分配出来的各种 bufffer 通过链表进行缓存,这将省去大量的内存分配与释放而改善性能。再则,对于处理一个 RPC 请求而多次分散分配的动作整合成集中一次性分配也是值得运用的优化技巧。数据平面的 runtime profiling 是关键技术。 Service Mesh 虽然对业务代码没有侵入性,但对服务流量具有侵入性,如何在出现业务毛刺的情形下,快速地通过 runtime profiling 去发现问题或自证清白是非常值得关注的点。心得一年不到的探索旅程,让团队更加笃定“借力开源,反哺开源”的发展思路。随着对 Istio 和 Envoy 实现细节的更多掌握,团队很强列地感受到了走“站在巨人的肩膀上”发展的道路少走了很多弯路,除了快速跟进业界的发展步伐与思路,还将省下精力去做更有价值的事和创新。此外,Istio 和 Envoy 两个开源项目的工程质量都很高,单元测试等质量保证手段是日常开发工作中的基础环节,而我们也完全采纳了这些实践。比如,内部搭建了 CI 环境、每次代码提交将自动触发单元测试、代码经过 code review 并完成单元测试才能入库、自动化性能测试等。展望在 2019 年接下来的日子,我们将着手:与 Sentinel 团队形成合力,将 Sentinel 的能力纳入到 Dubbo Mesh 中补全对 HTTP 和 Dubbo 协议的限流、降级和熔断能力。在阿里巴巴集团大范围 Kubernetes(Sigma 3.1)落地的背景下,与兄弟团队探索更加优雅的服务流量透明拦截技术方案。迎合 Serverless 的技术发展趋势,深化通过 Dubbo Mesh 更好地轻量化应用,以及基于 Dubbo Mesh 对服务流量的天然敏感性去更好地实现 auto-scaling。在产品的易用性和工程效率方面踏实进取。未来,我们将及时与读者分享阿里巴巴集团在 Service Mesh 这一新技术领域的探索成果,也期待与大家有更多的互动交流。本文作者:至简,阿里巴巴中间件高级技术专家,是阿里巴巴集团 Service Mesh 方向的重要参与者和推动者。关于 Dubbo Mesh 的首次公开分享本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 11, 2019 · 3 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

使用 TDD 测试驱动开发来构建 Laravel REST API

TDD 以及敏捷开发的先驱者之一的 James Grenning有句名言:如果你没有进行测试驱动开发,那么你应该正在做开发后堵漏的事 - James Grenning今天我们将进行一场基于 Laravel 的测试驱动开发之旅。 我们将创建一个完整的 Laravel REST API,其中包含身份验证和 CRUD 功能,而无需打开 Postman 或浏览器。?注意:本旅程假定你已经理解了 Laravel 和 PHPUnit 的基本概念。你是否已经明晰了这个问题?那就开始吧。项目设置首先创建一个新的 Laravel 项目 composer create-project –prefer-dist laravel/laravel tdd-journey。然后,我们需要创建 用户认证 脚手架,执行 php artisan make:auth ,设置好 .env 文件中的数据库配置后,执行数据库迁移 php artisan migrate。本测试项目并不会使用到刚生成的用户认证相关的路由和视图。我们将使用 jwt-auth。所以需要继续 安装 jwt 到项目。注意:如果您在执行 jwt:generate 指令时遇到错误, 您可以参考 这里解决这个问题,直到 jwt 被正确安装到项目中。最后,您需要在 tests/Unit 和 tests/Feature 目录中删除 ExampleTest.php 文件,使我们的测试结果不被影响。编码首先将 JWT 驱动配置为 auth 配置项的默认值:<?php // config/auth.php file’defaults’ => [ ‘guard’ => ‘api’, ‘passwords’ => ‘users’,],‘guards’ => [ … ‘api’ => [ ‘driver’ => ‘jwt’, ‘provider’ => ‘users’, ],],然后将如下内容放到你的 routes/api.php 文件里:<?phpRoute::group([‘middleware’ => ‘api’, ‘prefix’ => ‘auth’], function () { Route::post(‘authenticate’, ‘AuthController@authenticate’)->name(‘api.authenticate’); Route::post(‘register’, ‘AuthController@register’)->name(‘api.register’);});现在我们已经将驱动设置完成了,如法炮制,去设置你的用户模型:<?php…class User extends Authenticatable implements JWTSubject{ … //获取将被存储在 JWT 主体 claim 中的标识 public function getJWTIdentifier() { return $this->getKey(); } // 返回一个键值对数组,包含要添加到 JWT 的任何自定义 claim public function getJWTCustomClaims() { return []; }}我们所需要做的就是实现 JWTSubject 接口然后添加相应的方法即可。接下来,我们需要增加权限认证方法到控制器中.运行 php artisan make:controller AuthController 并且添加以下方法:<?php…class AuthController extends Controller{ public function authenticate(Request $request){ // 验证字段 $this->validate($request,[’email’ => ‘required|email’,‘password’=> ‘required’]); // 测试验证 $credentials = $request->only([’email’,‘password’]); if (! $token = auth()->attempt($credentials)) { return response()->json([’error’ => ‘Incorrect credentials’], 401); } return response()->json(compact(’token’)); } public function register(Request $request){ // 表达验证 $this->validate($request,[ ’email’ => ‘required|email|max:255|unique:users’, ’name’ => ‘required|max:255’, ‘password’ => ‘required|min:8|confirmed’, ]); // 创建用户并生成 Token $user = User::create([ ’name’ => $request->input(’name’), ’email’ => $request->input(’email’), ‘password’ => Hash::make($request->input(‘password’)), ]); $token = JWTAuth::fromUser($user); return response()->json(compact(’token’)); }}这一步非常直接,我们要做的就是添加 authenticate 和 register 方法到我们的控制器中。在 authenticate 方法,我们验证了输入,尝试去登录,如果成功就返回令牌。在 register 方法,我们验证输入,然后基于此创建一个用户并且生成令牌。4. 接下来,我们进入相对简单的部分。 测试我们刚写入的内容。 使用 php artisan make:test AuthTest 生成测试类。 在新的 tests / Feature / AuthTest 中添加以下方法:<?php /** * @test * Test registration /public function testRegister(){ //创建测试用户数据 $data = [ ’email’ => ’test@gmail.com’, ’name’ => ‘Test’, ‘password’ => ‘secret1234’, ‘password_confirmation’ => ‘secret1234’, ]; //发送 post 请求 $response = $this->json(‘POST’,route(‘api.register’),$data); //判断是否发送成功 $response->assertStatus(200); //接收我们得到的 token $this->assertArrayHasKey(’token’,$response->json()); //删除数据 User::where(’email’,’test@gmail.com’)->delete();}/* * @test * Test login /public function testLogin(){ //创建用户 User::create([ ’name’ => ’test’, ’email’=>’test@gmail.com’, ‘password’ => bcrypt(‘secret1234’) ]); //模拟登陆 $response = $this->json(‘POST’,route(‘api.authenticate’),[ ’email’ => ’test@gmail.com’, ‘password’ => ‘secret1234’, ]); //判断是否登录成功并且收到了 token $response->assertStatus(200); $this->assertArrayHasKey(’token’,$response->json()); //删除用户 User::where(’email’,’test@gmail.com’)->delete();}上面代码中的几行注释概括了代码的大概作用。 您应该注意的一件事是我们如何在每个测试中创建和删除用户。 测试的全部要点是它们应该彼此独立并且应该在你的理想情况下存在数据库中的状态。如果你想全局安装它,可以运行 $ vendor / bin / phpunit 或 $ phpunit 命令。 运行后它应该会给你返回是否安装成功的数据。 如果不是这种情况,您可以浏览日志,修复并重新测试。 这就是 TDD 的美丽之处。5. 对于本教程,我们将使用『菜谱 Recipes』作为我们的 CRUD 数据模型。首先创建我们的迁移数据表 php artisan make:migration create_recipes_table 并添加以下内容:<?php …public function up(){ Schema::create(‘recipes’, function (Blueprint $table) { $table->increments(‘id’); $table->string(’title’); $table->text(‘procedure’)->nullable(); $table->tinyInteger(‘publisher_id’)->nullable(); $table->timestamps(); });}public function down(){ Schema::dropIfExists(‘recipes’);}然后运行数据迁移。 现在使用命令 php artisan make:model Recipe 来生成模型并将其添加到我们的模型中。<?php …protected $fillable = [’title’,‘procedure’];/* * 发布者 * @return \Illuminate\Database\Eloquent\Relations\BelongsTo /public function publisher(){ return $this->belongsTo(User::class);}然后将此方法添加到 user 模型。<?php… /* * 获取所有菜谱 * @return \Illuminate\Database\Eloquent\Relations\HasMany */public function recipes(){ return $this->hasMany(Recipe::class);}6. 现在我们需要最后一部分设置来完成我们的食谱管理。 首先,我们将创建控制器 php artisan make:controller RecipeController 。 接下来,编辑 routes / api.php 文件并添加 create 路由端点。<?php … Route::group([‘middleware’ => [‘api’,‘auth’],‘prefix’ => ‘recipe’],function (){ Route::post(‘create’,‘RecipeController@create’)->name(‘recipe.create’);});在控制器中,还要添加 create 方法<?php … public function create(Request $request){ //验证数据 $this->validate($request,[’title’ => ‘required’,‘procedure’ => ‘required|min:8’]); //创建配方并附加到用户 $user = Auth::user(); $recipe = Recipe::create($request->only([’title’,‘procedure’])); $user->recipes()->save($recipe); //返回 json 格式的食谱数据 return $recipe->toJson();}使用 php artisan make:test RecipeTest 生成特征测试并编辑内容,如下所示:<?php …class RecipeTest extends TestCase{ use RefreshDatabase; … //创建用户并验证用户身份 protected function authenticate(){ $user = User::create([ ’name’ => ’test’, ’email’ => ’test@gmail.com’, ‘password’ => Hash::make(‘secret1234’), ]); $token = JWTAuth::fromUser($user); return $token; } public function testCreate() { //获取 token $token = $this->authenticate(); $response = $this->withHeaders([ ‘Authorization’ => ‘Bearer ‘. $token, ])->json(‘POST’,route(‘recipe.create’),[ ’title’ => ‘Jollof Rice’, ‘procedure’ => ‘Parboil rice, get pepper and mix, and some spice and serve!’ ]); $response->assertStatus(200); }}上面的代码你可能还是不太理解。我们所做的就是创建一个用于处理用户注册和 token 生成的方法,然后在 testCreate() 方法中使用该 token 。注意使用 RefreshDatabase trait ,这个 trait 是 Laravel 在每次测试后重置数据库的便捷方式,非常适合我们漂亮的小项目。好的,所以现在,我们只要判断当前请求是否是响应状态,然后继续运行 $ vendor/bin/phpunit 。如果一切运行顺利,您应该收到错误。 ?There was 1 failure:1) TestsFeatureRecipeTest::testCreateExpected status code 200 but received 500.Failed asserting that false is true./home/user/sites/tdd-journey/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:133/home/user/sites/tdd-journey/tests/Feature/RecipeTest.php:49FAILURES!Tests: 3, Assertions: 5, Failures: 1.查看日志文件,我们可以看到罪魁祸首是 Recipe 和 User 类中的 publisher 和 recipes 的关系。 Laravel 尝试在表中找到一个字段为 user_id 的列并将其用作于外键,但在我们的迁移中,我们将publisher_id 设置为外键。 现在,将行调整为://食谱文件public function publisher(){ return $this->belongsTo(User::class,‘publisher_id’);}//用户文件public function recipes(){ return $this->hasMany(Recipe::class,‘publisher_id’);}然后重新运行测试。 如果一切顺利,我们将获得所有绿色测试!?… 3 / 3 (100%)…OK (3 tests, 5 assertions)现在我们仍然需要测试创建配方的方法。为此,我们可以判断用户的『菜谱 Recipes』计数。更新你的 testCreate 方法,如下所示:<?php…//获取 token$token = $this->authenticate();$response = $this->withHeaders([ ‘Authorization’ => ‘Bearer ‘. $token,])->json(‘POST’,route(‘recipe.create’),[ ’title’ => ‘Jollof Rice’, ‘procedure’ => ‘Parboil rice, get pepper and mix, and some spice and serve!’]);$response->assertStatus(200);//得到计数做出判断$count = User::where(’email’,’test@gmail.com’)->first()->recipes()->count();$this->assertEquals(1,$count);我们现在可以继续编写其余的方法。首先,编写我们的 routes/api.php<?php…Route::group([‘middleware’ => [‘api’,‘auth’],‘prefix’ => ‘recipe’],function (){ Route::post(‘create’,‘RecipeController@create’)->name(‘recipe.create’); Route::get(‘all’,‘RecipeController@all’)->name(‘recipe.all’); Route::post(‘update/{recipe}’,‘RecipeController@update’)->name(‘recipe.update’); Route::get(‘show/{recipe}’,‘RecipeController@show’)->name(‘recipe.show’); Route::post(‘delete/{recipe}’,‘RecipeController@delete’)->name(‘recipe.delete’);});接下来,我们将方法添加到控制器。 以下面这种方式更新 RecipeController 类。<?php ….//创建配方public function create(Request $request){ //验证 $this->validate($request,[’title’ => ‘required’,‘procedure’ => ‘required|min:8’]); //创建配方并附加到用户 $user = Auth::user(); $recipe = Recipe::create($request->only([’title’,‘procedure’])); $user->recipes()->save($recipe); //返回配方的 json 格式数据 return $recipe->toJson();}//获取所有的配方public function all(){ return Auth::user()->recipes;}//更新配方public function update(Request $request, Recipe $recipe){ //检查用户是否是配方的所有者 if($recipe->publisher_id != Auth::id()){ abort(404); return; } //更新并返回 $recipe->update($request->only(’title’,‘procedure’)); return $recipe->toJson();}//显示单个食谱的详细信息public function show(Recipe $recipe){ if($recipe->publisher_id != Auth::id()){ abort(404); return; } return $recipe->toJson();}//删除一个配方public function delete(Recipe $recipe){ if($recipe->publisher_id != Auth::id()){ abort(404); return; } $recipe->delete();}代码和注释已经很好地解释了这个逻辑。最后我们的 test/Feature/RecipeTest:<?php… use RefreshDatabase;protected $user;// 创建用户并验证他protected function authenticate(){ $user = User::create([ ’name’ => ’test’, ’email’ => ’test@gmail.com’, ‘password’ => Hash::make(‘secret1234’), ]); $this->user = $user; $token = JWTAuth::fromUser($user); return $token;}// 测试创建路由public function testCreate(){ // 获取令牌 $token = $this->authenticate(); $response = $this->withHeaders([ ‘Authorization’ => ‘Bearer ‘. $token, ])->json(‘POST’,route(‘recipe.create’),[ ’title’ => ‘Jollof Rice’, ‘procedure’ => ‘Parboil rice, get pepper and mix, and some spice and serve!’ ]); $response->assertStatus(200); // 获取计数并断言 $count = $this->user->recipes()->count(); $this->assertEquals(1,$count);}// 测试显示所有路由public function testAll(){ // 验证并将配方附加到用户 $token = $this->authenticate(); $recipe = Recipe::create([ ’title’ => ‘Jollof Rice’, ‘procedure’ => ‘Parboil rice, get pepper and mix, and some spice and serve!’ ]); $this->user->recipes()->save($recipe); // 调用路由并断言响应 $response = $this->withHeaders([ ‘Authorization’ => ‘Bearer ‘. $token, ])->json(‘GET’,route(‘recipe.all’)); $response->assertStatus(200); // 断言计数为1,第一项的标题相关 $this->assertEquals(1,count($response->json())); $this->assertEquals(‘Jollof Rice’,$response->json()[0][’title’]);}// 测试更新路由public function testUpdate(){ $token = $this->authenticate(); $recipe = Recipe::create([ ’title’ => ‘Jollof Rice’, ‘procedure’ => ‘Parboil rice, get pepper and mix, and some spice and serve!’ ]); $this->user->recipes()->save($recipe); // 调用路由并断言响应 $response = $this->withHeaders([ ‘Authorization’ => ‘Bearer ‘. $token, ])->json(‘POST’,route(‘recipe.update’,[‘recipe’ => $recipe->id]),[ ’title’ => ‘Rice’, ]); $response->assertStatus(200); // 断言标题是新标题 $this->assertEquals(‘Rice’,$this->user->recipes()->first()->title);}// 测试单一的展示路由public function testShow(){ $token = $this->authenticate(); $recipe = Recipe::create([ ’title’ => ‘Jollof Rice’, ‘procedure’ => ‘Parboil rice, get pepper and mix, and some spice and serve!’ ]); $this->user->recipes()->save($recipe); $response = $this->withHeaders([ ‘Authorization’ => ‘Bearer ‘. $token, ])->json(‘GET’,route(‘recipe.show’,[‘recipe’ => $recipe->id])); $response->assertStatus(200); // 断言标题是正确的 $this->assertEquals(‘Jollof Rice’,$response->json()[’title’]);}// 测试删除路由public function testDelete(){ $token = $this->authenticate(); $recipe = Recipe::create([ ’title’ => ‘Jollof Rice’, ‘procedure’ => ‘Parboil rice, get pepper and mix, and some spice and serve!’ ]); $this->user->recipes()->save($recipe); $response = $this->withHeaders([ ‘Authorization’ => ‘Bearer ‘. $token, ])->json(‘POST’,route(‘recipe.delete’,[‘recipe’ => $recipe->id])); $response->assertStatus(200); // 断言没有食谱 $this->assertEquals(0,$this->user->recipes()->count());}除了附加测试之外,我们还添加了一个类范围的 $user 属性。 这样,我们不止可以利用 $user 来使用 authenticate 方法不仅生成令牌,而且还为后续其他对 $user 的操作做好了准备。现在运行 $ vendor/bin/phpunit 如果操作正确,你应该进行所有绿色测试。结论希望这能让你深度了解在 TDD 在 Laravel 项目中的运行方式。 他绝对是一个比这更宽泛的概念,一个不受特地方法约束的概念。虽然这种开发方法看起来比常见的调试后期程序要耗时, 但他很适合在代码中尽早捕获错误。虽然有些情况下非 TDD 方式会更有用,但习惯于 TDD 模式开发是一种可靠的技能和习惯。本演练的全部代码可参见 Github here 仓库。请随意使用。干杯!文章转自:https://learnku.com/laravel/t… 更多文章:https://learnku.com/laravel/c… ...

March 6, 2019 · 5 min · jiezi

带你脱离视频测试的坑

本文由云+社区发表作者:腾讯云视频小编这次分享主要是视频相关的专项测试,音频相关的暂不涉及。我们直接切入正题,关于视频通话质量对比,需要一些对比项,这里是从以下5个方面进行数据对比:码率、帧率、分辨率、清晰度、时延。接下来我分别介绍一下这5个方面。▽码率数据传输时单位时间内传送的数据位数,单位是kbps,即千位每秒。码率越高对应着传输能力越强,视频精度会越高。帧率帧率是用于测量显示帧数的量度,简称fps。每秒的帧数表示处理器处理时每秒钟能够更新的次数,高的帧率可以得到更流畅、更逼真的动画。分辨率/清晰度这个两个指标代表着视频画面的清晰程度,越高的话,给用户的画面就越清晰,用户体验会越好。清晰度的单位:LW/PH时延即实时性,简单来说就是两个人通话,本端说了一句话,对端需等待一段时间才能收到。单位一般用毫秒(ms)表示。介绍完这些指标,接下来切入正题,这些数据在手机上,如何获取。首先,在双人视频通话连接好后,在非纯净态画面顶部会出现名字,在名字上点击5下,会弹出一段log,这个log是开发为了好分析问题所特意加的,这里面就包含了我们所需要的3个数据,分辨率,帧率以及码率。双人视频通话log红色框框里面的即为我们要的3个数据,需要看本端的分辨率,码率,帧率,则需要找到Enc这个字段(Enc代表编码端,即本端;Dec代表解码端,即对端),后面对应的依次为分辨率,码率和帧率。测试时,需要等待视频通话稳定一段时间,取的数据才有意义,取最大、最小值都意义不大。视频通话分别率刚开始可能会低一些,等网络稳定后视情况,应该会增加分辨率,所以取的分辨率需要等稳定后再取。帧率和码率也一样,稳定后取平均值。上面说了手机APP分辨率、码率、帧率的测试方法,接下来说一下时延和清晰度。视频清晰度,本该用一个动态的视频进行分析,这里由于条件有限,采取的是等视频稳定后,互相截图,然后用专业的清晰度计算工具,算出图片的清晰度值,我们认为这个值就是该机型视频通话的清晰度。视频专项测试方法视频清晰度测试方法具体操作如下:在音视频实验室,有专门的设备。两台手机视频通话后,一台手机切换至前摄像头,点出log后,放在架子上,另一台手机关掉本端摄像头;架子上的手机分辨率稳定后,另一端手机直接截图,这张图就是用来计算架子上的手机的分辨率的。有专门的计算工具Imatest进行计算,计算方法这里就不展开来说了。两部手机对调,就可以互相取得分辨率了。这里有个问题,即清晰度计算软件是和截图的质量也有关系,不同机型互测的时候,截图效果也是不一样的,这里是有可能会影响清晰度的最终计算结果的,这里还没有想到比较好的解决办法;但同机型互通则不存在该问题。时延测试方法电脑上打开一个在线秒表,开始计时后。两台手机固定在屏幕前,通话后,稳定一段时间后,拿起第三部手机拍照,即是时延,这里拍照15次,计算差值后取平均值,即为时延。到此,手机APP五项性能数据测试方法就全部介绍完成;接下来介绍同类型的产品视频通话,这5项数据需要如何获取。想要得到码率、帧率、分辨率这些数据只能通过一些其他方法。▽01首先是码率,这里需要抓包看。准备mac机,确保mac机上有Xcode,手机连上mac后,打开Xcode后,点击window-Device and Simulators,找到identifier,后面的设备标识复制一下,看这里02打开mac机的cmd,输入rvictl -s 手机标识,回车后即可,此时输入rvictl -l,即可查到已添加的设备。03打开Wireshare,找到rvio端口,双击后,进入rvio端口,点击Statistics-I/O Graph。04里面需要调整一下参数,就可以出现对方码率了,首先要先添加一行参数,即上图左下角的“+”号,点击“+”号后,在Enabled打上勾,然后Graph Name修改一下,Y Axis改成Bits,Interval改成1 sec。最后就要修改一下Display Filter,这个参数是用来过滤的,当你需要获取连着电脑的这部手机的码率是,你需要输入ip.src==X.X.X.X and udp;当你需要获取对端的码率时(即非连接mac的那台手机),需要输入ip.dst==X.X.X.X and udp。此文已由腾讯云+社区在各渠道发布获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号

March 4, 2019 · 1 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

如何成为优秀的技术主管?你要做到这三点

阿里妹导读:技术主管,又叫「技术经理」,英文一般是 Tech Leader ,简称 TL。随着工作经验的不断积累,能力的不断提升,每个人都有机会成为Team Leader。然而在机会到来前,我们必须提前做好准备,对TL的工作职责有一定了解。当然,这也会为当下更好地配合TL工作打下基础。今天,阿里巴巴高级技术专家云狄将结合自己多年的经验,从开发规范、开发流程、技术规划与管理三个角度出发,分享对技术TL这一角色的理解与思考,欢迎一起探讨交流。「技术主管」是开发团队中的某位程序员需要对一起创建系统的整个开发团队负责时所承担的角色。通常他既要对最终交付的软件系统负责,另外也会像一个程序员一样去开发实现系统。一个技术主管的 60% ~ 70% 的时间可能花在了开发任务分解分配、开发实践、技术架构评审、代码审核和风险识别上,而余下的 30% ~ 40% 的时间则花在为了保障系统按时交付所需的各种计划、协作、沟通、管理上。和团队管理者不同的是,技术主管的大部分管理工作都是针对具体研发任务和技术事务的。接下来基于我在技术TL这个角色上,在开发规范、开发流程、技术管理与规划等方面我的一些心路历程,和大家共勉。开发规范我当时负责的业务是集团收购一家子公司的业务,在整体技术标规范上与集团的技术标准存在很大的差异。开发规范可以说是我来到这个团队干的第一件事,我当时面对的问题是API接口格式混乱,没有标准的RPC服务化,代码没有统一标准的开发规范,技术框架组件非标准化等一系列问题,作为一名业务上的新人,我第一时间制定了一套相对标准、全面的技术开发规范,边写代码边梳理开发规范,引领团队走向统一标准化开发道路。针对团队研发规范暴露的上述问题,主要制定了如下规范:命名规范我自己非常注重搭建项目结构的起步过程,应用命名规范、模块的划分、目录(包)的命名,我觉得非常重要,如果做的足够好,别人导入项目后可能只需要10分钟就可以大概了解系统结构。具体规范包括包命名、类的命名、接口命名、方法命名、变量命名、常量命名。统一IDE代码模板约定了IDEA/Eclipse IDE代码的统一模板,代码风格一定要统一,避免不同开发人员使用不同模板带来的差异化以及代码merge成本。使用IDEA的同学可以安装Eclipse Code Formatter插件,和Eclipse统一代码模板。Maven使用规范所有二方库、三方库的版本统一定义到parent pom里,这样来所有业务应用工程统一继承parent pom里所指定的二方库、三方库的版本,统一框架与工具的版本(Spring、Apache commons工具类、日志组件、JSON处理、数据库连接池等),同时要求生产环境禁用SNAPSHOT版本。这样以来升级通用框架与工具的版本,只需要应用工程升级parent pom即可。代码Commit规范基于Angular Commit Message规范生成统一的ChangeLog,这样一来对于每次发布release tag非常清晰,Mac下都需要安装对应的插件,IDEA也有对应的插件,具体可以参考阮一峰老师的《Commit message 和 Change log 编写指南》。此刻忽然想起Linus面对pull request里的骚操作所发的飚:Get rid of it. And I don’t ever want to see that shit again. ——Linus代码的commit的规范对团队非常重要,清晰的commit信息生成的release tag,对于生产环境的故障回滚业非常关键,能够提供一些有价值的信息。统一API规范统一Rpc服务接口的返回值ResultDTO,具体代码如下:success代表接口处理响应结果成功还是失败,errorCode、errorMsg表示返回错误码和错误消息,module表示返回结果集,把ResultDTO定义到common-api顶层二方库,这样以来各个应用不需要来回转换返回结果。Http Rest接口规范约定同ResultDTO相差无几,需要额外关注一下加解密规范和签名规范、版本管理规范。异常处理规范异常处理不仅仅是狭义上遇到了Exception怎么去处理,还有各种业务逻辑遇到错误的时候我们怎么去处理。service服务层捕获的异常主要包括BusinessException(业务异常)、RetriableException (可重试异常) 到 common-api,定义一个公共异常拦截器,对业务异常、重试异常进行统一处理,对于可重试的异常调用的服务接口需要保证其幂等性。另外其他业务层有些特殊异常不需要拦截器统一处理,内部可以进行自我消化处理掉,根据场景对应的处理原则主要包括:直接返回抛出异常重试处理熔断处理降级处理这又涉及到了弹力设计的话题,我们的系统往往会对接各种依赖外部服务、Api,大部分服务都不会有SLA,即使有在大并发下我们也需要考虑外部服务不可用对自己的影响,会不会把自己拖死。我们总是希望:尽可能以小的代价通过尝试让业务可以完成;如果外部服务基本不可用,而我们又同步调用外部服务的话,我们需要进行自我保护直接熔断,否则在持续的并发的情况下自己就会垮了;如果外部服务特别重要,我们往往会考虑引入多个同类型的服务,根据价格、服务标准做路由,在出现问题的时候自动降级。推荐使用Netflix开源的hystrix容灾框架,主要解决当外部依赖出现故障时拖垮业务系统、甚至引起雪崩的问题。目前我团队也在使用,能够很好的解决异常熔断、超时熔断、基于并发数限流熔断的降级处理。分支开发规范早期的时候源码的版本管理基于 svn,后来逐步切换到 git,分支如何管理每一个公司(在Gitflow的基础上)都会略有不同。针对分支开发规范,指定如下标准:分支的定义(master、develop、release、hotfix、feature)分支命名规范checkout、merge request流程提测流程上线流程Hotfix流程虽然这个和代码质量和架构无关,按照这一套标准执行下来,能够给整个研发团队带领很大的便利:减少甚至杜绝代码管理导致的线上事故;提高开发和测试的工作效率,人多也乱;减少甚至杜绝代码管理导致的线上事故;方便运维处理发布和回滚;让项目的开发可以灵活适应多变的需求,多人协同开发。统一日志规范日志是产品必不可少的一个功能,具备可回溯性、能够抓取问题现场信息是其独一无二的优点,尤其在生产系统上问题定位等方面具有不可替代的作用。这里着重强调一下针对异常的日志规范:WARN和ERROR的选择需要好好考虑,WARN一般我倾向于记录可自恢复但值得关注的错误,ERROR代表了不能自己恢复的错误。对于业务处理遇到问题用ERROR不合理,对于catch到了异常也不是全用ERROR。记录哪些信息,最好打印一定的上下文(链路TraceId、用户Id、订单Id、外部传来的关键数据)而不仅仅是打印线程栈。记录了上下问信息,是否要考虑日志脱敏问题?可以在框架层面实现,比如自定义实现logback的ClassicConverter。正确合理的使用日志,能够指引开发人员快速查找错误、定位问题,因此约定了一套日志使用标准规范,现在可以更多的参考《阿里经济体开发规约——日志规约》。统一MYSQL开发规范表的设计和 Api 的定义类似,属于那种开头没有开好,以后改变需要花10x代价的,我知道,一开始在业务不明确的情况下,设计出良好的一步到位的表结构很困难,但是至少我们可以做的是有一个好的标准。统一工具与框架对开发过程中所用到的公共组件进行了统一抽象与封装,包括 dao 层框架mybatis、cache 组件 jetcache、httpclien t组件、common-tools (公共工具),同时抽取出全局唯一ID、分布式锁、幂等等公共组件,把以上公共组件进行集成到各个应用,进行统一升级、维护,这样以来方便大家将更多的精力集中到业务开发上。开发流程目前团队的开发模式还是基于传统的瀑布开发模式,整体开发流程涉及需求评审、测试用例评审、技术架构评审、开发与测试、验收与上线,这里主要基于TL的角度从需求管理、技术架构评审、代码评审、发布计划评审几个关键重点环节进行探讨,欢迎拍砖。需求管理美国专门从事跟踪IT项目成功或失败的权威机构 Standish Group的CHAOS Reports 报导了该公司的一项研究,该公司对多个项目作调查后发现,百分之七十四的项目是失败的,既这些项目不能按时按预算完成。其中提到最多的导致项目失败的原因就是"变更用户需求"。另外从历年的 Standish Group 报告分析看,导致项目失败的最重要原因与需求有关。Standish Group 的CHAOS 报告进一步证实了与成功项目最密切的因素是良好的需求管理,也就是项目的范围管理,特别是管理好项目的变更。产品因需求而生,在产品的整个生命周期中,产品经理会收到来自各个方面的需求,但是每一个需求的必要性、重要性和实现成本都需要经过深思熟虑的分析和计划,避免盲目的决定需求或者变更需求,这样很容易导致工作混乱,技术TL如果不能正确的对需求进行把控,会导致整个项目偏离正确的轨道。需求管理的第一步就是要梳理不同来源的需求,主要包括从产品定位出发、外部用户反馈、竞争对手情况、市场变化以及内部运营人员、客服人员、开发人员的反馈。首先技术TL对产品有足够认知和把控,简单来说就是我的产品是为了满足哪些人的哪些需求而做,产品需求一定要根植于客户的需求、根植于客户的环境。每款产品必定有其核心价值,能够为客户创造更多的价值,基于此考虑往往能得到一些核心需求,摒除价值不大的需求。需求管理中最重要的就是对发散性需求的管理,往往因此也会导致产品在执行过程中不断的变更或增加需求。由于人的思维是发散性的,所以往往在产品构思的过程中会出现各种新鲜好玩的想法,这些想法可能来自领导或者产品经理自己,但是这些想法往往都是和产品核心方向不相关的,但是由于这些想法能够在当时带来诱惑,因此这些不相关的需求会严重干扰了技术团队的精力,打乱或者延误产品原本的计划。同样技术研发同学也需要建立对产品的深度思考,不要把自己定位成产品需求的实现者,同样需要对需求负责。很多时候需求的变更或增加是因为我们面临太多选择和想要的太多,没有适当的控制自己的欲望,并以自己的喜好来决定需求,这些因素很容易导致产品没有明确的方向、团队成员疲于奔命,但是却没有实际的成果。所以技术TL一定要能够评估出重新审视产品和筛选需求的优先级,识别每一个需求的必要性、重要性和实现成本。通过深思熟虑给团队明确方向并专注,聚焦资源的支配,确保团队的精力都聚焦在产品的核心需求上。技术架构评审互联网时代,大家提倡敏捷迭代,总嫌传统方式太重,流程复杂,影响效率,什么都希望短平快,在扁平化的组织中,经常是需求火速分发到一线研发,然后就靠个人折腾去了,其实技术架构评审这同样是一个非常重要的环节。架构评审或技术方案评审的价值在于集众人的力量大家一起来分析看看方案里是否有坑,方案上线后是否会遇到不可逾越的重大技术问题,提前尽可能把一些事情先考虑到提出质疑其实对项目的健康发展有很大的好处。基于架构评审,我们的目标核心是要满足以下几点:1.设计把关,确保方案合格,各方面都考虑到了,避免缺陷和遗漏,不求方案多牛,至少不犯错。保证架构设计合理和基本一致,符合整体原则。维持对系统架构的全局认知,避免黑盒效应。通过评审发掘创新亮点,推广最佳实践。架构设计既要保证架构设计的合理性和可扩展性,又要避免过度设计。架构设计不仅仅是考虑功能实现,还有很多非功能需求,以及持续运维所需要的工作,需要工程实践经验,进行平衡和取舍。架构评审需要以下几点:技术选型:为什么选用A组件不选用B、C组件,A是开源的,开源协议是啥?基于什么语言开发的,出了问题我们自身是否能够维护?性能方面有没有压测过?这些所有问题作为技术选型我们都需要考虑清楚,才能做最终决定。高性能:产品对应的TPS、QPS和RT是多少?设计上会做到的TPS、QPS和RT是多少?而实际上我们整体随着数据量的增大系统性能会不会出现明显问题?随着业务量、数据量的上升,我们的系统的性能如何去进一步提高?系统哪个环节会是最大的瓶颈?是否有抗突发性能压力的能力,大概可以满足多少的TPS和QPS,怎么去做来实现高性能,这些问题都需要我们去思考。高可用:是否有单点的组件,非单点的组件如何做故障转移?是否考虑过多活的方案?是否有数据丢失的可能性?数据丢失如何恢复?出现系统宕机情况,对业务会造成哪些影响?有无其他补救方案?这些问题需要想清楚,有相应的解决方案。可扩展性:A和B的业务策略相差无几,后面会不会继续衍生出C的业务策略,随着业务的发展哪些环节可以做扩展,如何做扩展?架构设计上需要考虑到业务的可扩展性。可伸缩性:每个环节的服务是不是无状态的?是否都是可以快速横向扩展的?扩容需要怎么做手动还是自动?扩展后是否可以提高响应速度?这所有的问题都需要我们去思考清楚,并有对应的解决方案。弹性处理:消息重复消费、接口重复调用对应的服务是否保证幂等?是否考虑了服务降级?哪些业务支持降级?支持自动降级还是手工降级?是否考虑了服务的超时熔断、异常熔断、限流熔断?触发熔断后对客户的影响?服务是否做了隔离,单一服务故障是否影响全局?这些问题统统需要我们想清楚对应的解决方案,才会进一步保证架构设计的合理性。兼容性:上下游依赖是否梳理过,影响范围多大?怎么进行新老系统替换?新老系统能否来回切换?数据存储是否兼容老的数据处理?如果对你的上下游系统有影响,是否通知到上下游业务方?上下游依赖方进行升级的方案成本如何最小化?这些问题需要有完美的解决方案,稍有不慎会导致故障。安全性:是否彻底避免SQL注入和XSS?是否有数据泄露的可能性?是否做了风控策略?接口服务是否有防刷保护机制?数据、功能权限是否做了控制?小二后台系统是否做了日志审计?数据传输是否加密验签?应用代码中是否有明文的AK/SK、密码?这些安全细节问题需要我们统统考虑清楚,安全问题任何时候都不能轻视。可测性:测试环境和线上的差异多大?是否可以在线上做压测?线上压测怎么隔离测试数据?是否有测试白名单功能?是否支持部署多套隔离的测试环境?测试黑盒白盒工作量的比例是怎么样的?新的方案是否非常方便测试,在一定程度也需要考量。可运维性:系统是否有初始化或预热的环节?数据是否指数级别递增?业务数据是否需要定期归档处理?随着时间的推移如果压力保持不变的话系统需要怎么来巡检和维护?业务运维方面的设计也需要充分考虑到。监控与报警:对外部依赖的接口是否添加了监控与报警?应用层面系统内部是否有暴露了一些指标作监控和报警?系统层面使用的中间件和存储是否有监控报警?只有充分考虑到各个环节的监控、报警,任何问题会第一时间通知到研发,阻止故障进一步扩散。其实不同阶段的项目有不同的目标,我们不会在项目起步的时候做99.99%的可用性支持百万QPS的架构,高效完成项目的业务目标也是架构考虑的因素之一。而且随着项目的发展,随着公司中间件和容器的标准化,很多架构的工作被标准化替代,业务代码需要考虑架构方面伸缩性运维性等等的需求越来越少,慢慢的这些工作都能由架构和运维团队来接。一开始的时候我们可以花一点时间来考虑这些问题,但是不是所有的问题都需要有最终的方案。代码评审代码质量包括功能性代码质量和非功能性代码质量,功能质量大多通过测试能够去发现问题,非功能性代码质量用户不能直接体验到这种质量的好坏,代码质量不好,最直接的“受害者”是开发者或组织自身,因为代码质量好坏直接决定了软件的可维护性成本的高低。代码质量应该更多的应该从可测性,可读性,可理解性,容变性等代码可维护性维度去衡量,其中 CodeReview 是保证代码质量非常重要的一个环节,建立良好的 CodeReview 规范与习惯,对于一个技术团队是一件非常重要核心的事情,没有 CodeReview 的团队没有未来。每次项目开发自测完成后,通常会组织该小组开发人员集体进行代码 review,代码 review 一般 review 代码质量以及规范方面的问题,另外需要关注的是每一行代码变更是否与本次需求相关,如果存在搭车发布或者代码重构优化,需要自行保证测试通过,否则不予发布。CodeReview 我会重点关注如下事情:确认代码功能:代码实现的功能满足产品需求,逻辑的严谨和合理性是最基本的要求。同时需要考虑适当的扩展性,在代码的可扩展性和过度设计做出权衡,不编写无用逻辑和一些与代码功能无关的附加代码。在真正需要某些功能的时候才去实现它,而不是你预见到它将会有用。 —— RonJeffries编码规范:以集团开发规约、静态代码规约为前提,是否遵守了编码规范,遵循了最佳实践。除了形式上的要求外,更重要的是命名规范。目标是提高代码的可读性,降低代码可维护性成本。潜在的BUG:可能在最坏情况下出现问题的代码,包括常见的线程安全、业务逻辑准确性、系统边界范围、参数校验,以及存在安全漏洞(业务鉴权、灰产可利用漏洞)的代码。。文档和注释:过少(缺少必要信息)、过多(没有信息量)、过时的文档或注释,总之文档和注释要与时俱进,与最新代码保持同步。其实很多时候个人觉得良好的变量、函数命名是最好的注释,好的代码胜过注释。重复代码:当一个项目在不断开发迭代、功能累加的过程中,重复代码的出现几乎是不可避免的,通常可以通过PMD工具进行检测。类型体系之外的重复代码处理通常可以封装到对应的Util类或者Helper类中,类体系之内的重复代码通常可以通过继承、模板模式等方法来解决。复杂度:代码结构太复杂(如圈复杂度高),难以理解、测试和维护。监控与报警:基于产品的需求逻辑,需要有些指标来证明业务是正常work的,如果发生异常需要有监控、报警指标通知研发人员处理,review业务需求对应的监控与报警指标也是Code Review的重点事项。测试覆盖率:编写单元测试,特别是针对复杂代码的测试覆盖是否足够。实际上维护单元测试的成本不比开发成本低,这点团队目前做的的不到位。针对以上每次代码review所涉及到的经典案例会统一输出到文档里,大家可以共同学习避免编写出同样的Ugly Code。发布计划评审涉及到10人日以上的项目,必须有明确的发布计划,并组织项目成员统一参加项目发布计划review,发布计划主要包含如下几点:1)明确是否有外部依赖接口,如有请同步协调好业务方;2)发布前配置确认包括配置文件、数据库配置、中间件配置等各种配置,尤其各种环境下的差异化配置项;3)二方库发布顺序,是否有依赖;4)应用发布顺序;5)数据库是否有数据变更和订正,以及表结构调整;6)回滚计划,必须要有回滚计划,发布出现问题要有紧急回滚策略;7)生产环境回归测试重点Case。技术规划与管理我在带技术团队的这些年,对团队一直有一个要求,每周都要做系统健康度巡检,未雨绸缪、晴天修屋顶,避免在极端场景下某些隐藏的bug转变成了故障。系统健康度巡检为什么要把系统健康度巡检放到技术管理里,我觉得这是一个非常重要的环节。像传统的航空、电力、汽车行业都要有一定的巡检机制,保障设备系统正常运转,同样软件系统也同样需要巡检机制保障业务健康发展。随着业务的不断发展,业务量和数据量不断的上涨,系统架构的腐蚀是避免不了的,为了保障系统的健康度,需要不断的考虑对系统架构、性能进行优化。系统的监控与报警能够一定程度发现系统存在的问题,系统存在的一些隐患需要通过对系统的巡检去发现,如果优化不及时在极端情况会导致故障,巡检粒度建议每周巡检一次自己所负责的业务系统。系统巡检重点要关注如下几点:系统指标:系统CPU、负载、内存、网络、磁盘有无异常情况波动,确认是否由发布导致,还是系统调用异常。慢接口:通常rt大于3s的接口需要重点关注,极端并发场景下容易导致整个系统雪崩。慢查询:MYSQL慢查询需要重点关注,随着数据量上涨,需要对慢查询进行优化。错误日志:通过错误日志去发现系统隐藏的一些bug,避免这些bug被放大,甚至极端情况下会导致故障。技术规划技术规划通常由团队的TL负责,每个财年TL需要从大局的角度去思考每个季度的技术优化规划,去偿还技术债,技术债也是有利息的,因为利息的存在,技术债务不及时偿还的话,会在未来呈现出非线性增长,造成始料不及的损失。这里的技术规划包括如下几点:架构优化:一些结构不良、低内聚高耦合的代码则会使得哪怕是微小的需求变更或功能扩展都无从下手,修改的代价很可能超过了重写的代价。同样系统之间的耦合也需要重点去关注,遵循微服务化的原则,系统也要遵循单一职责原则,对于职责不清晰的系统去做解耦优化,进行一些模块化改造、服务隔离、公用服务抽象。性能优化:基于财年对于业务量、数据量的发展评估,根据目前系统服务的QPSRT,需要提前规划对系统性能进行一些升级策略,包括重点关注对一些慢接口、慢查询的优化。弹性与可靠性:系统提供的服务需要保障括数据一致性、幂等、防重攻击,同时也需要从熔断降级、异地多活的角度去考虑存在哪些问题,目前系统的SLA指标是否能够达到高可用,需要做哪些优化保障系统的高可用。可伸缩:应用服务是否保证无状态,关键节点发生故障能够快速转移、扩容,避免故障扩大化。总结大家不知道有没有类似的经历,某个时间段突然一些线上故障频发,各种技术债、业务债被业务方穷追猛打要求还债,如果出现这种现象很大程度这个TL已经失位了,这个团队失控了。也曾经有人跟我吐槽他的TL把活都分给他们,而TL自己什么都不干!这个技术TL真的什么都不干?曾经有一段时间我也在思考技术TL的核心职责到底是什么?技术TL应该具备哪些素质?首先技术说到底是为业务服务的,除非技术就是业务本身,必须体现它的商业价值。在很多公司里技术研发真的就成了实现其他部门需求的工具,我觉得这样的技术TL肯定是不合格的。首先它不能影响业务发展,需求提出方会经过很多转化,如果不是不假思索传递需求,整个过程会失真。第二个,我认为最最重要的是架构设计的能力,可能管理能力还次之。对于管理能力我认为最重要的是对团队的感知能力,因为一旦到了技术TL这个级别,不能脱离一线太远,业务细节可以不清楚,大的方向必须要明确。如果没有很细腻的感知能力,很多的决策会有偏差。如果他不是一个业务架构师,不是一个能给团队指明更好方向的人,他最终会沦为一个需求翻译器,产品经理说怎么做就怎么做。他更多的只是负责保证产品的质量、开发的速度,最终被肢解成一个很琐碎的人。一旦团队上了一定的规模,团队就会从单纯的需求实现走向团队运营,而运营是需要方向的,业务架构就是一个基于运营和数据的一种综合的能力。关于技术层面,技术TL需要具备如下素养:技术视野良好,解决问题能力与架构设计能力出色。技术TL要有良好的技术视野,不需要各种技术都样样精通,但是必须要所有涉猎,有所了解,对各种技术领域的发展趋势,主流非主流技术的应用场景要非常了解。知道在什么场景应用什么技术,业务发展到什么规模应该预先做哪些技术储备。产品架构的设计要有足够的弹性,既能够保证当前开发的高效率,又能够对未来产品架构的演进留出扩展的余地。动手能力要强,学习能力出色。技术TL并不需要自己亲自动手写代码,但是如有必要,自己可以随时动手参与第一线的编码工作,技术TL不能长期远离一线工作,自废武功,纸上谈兵。否则长此以往,会对技术的判断产生严重的失误。另外,技术TL也应该是一个学习能力非常出色的人,毕竟IT行业的技术更新换代速度非常快,如果没有快速学习能力,是没有资格做好技术TL的。技术TL除了管人和管事之外,其他还有很多事情要做包括建立团队研发文化、团队人才培养与建设、跨部门协调与沟通等,这样以要求技术TL也同时也需要具备良好的沟通和管理能力,以上观点仅是个人的一些思考和观点,仅供参考。本文作者:阿里技术 阅读原文本文来自云栖社区合作伙伴“ 阿里技术”,如需转载请联系原作者。 ...

February 28, 2019 · 1 min · jiezi

罗辑思维在全链路压测方面的实践和工作笔记

业务的知名度越高,其背后技术团队承受的压力就越大。一旦出现技术问题,就有可能被放大,尤其是当服务的是对知识获取体验要求颇高的用户群体。提供知识服务的罗辑思维主张“省时间的获取知识”,那么其技术团队在技术实践方面是如何践行省时间的理念的呢?本文将还原罗辑思维技术团队在全链路压测上的构建过程,为您一探究竟。全链路压测知多少保障服务的可用性和稳定性是技术团队面临的首要任务,也是技术难题之一。例如,罗辑思维提供的是知识服务,服务的是在高铁、地铁和公交车等场所利用碎片时间进行学习,在凌晨、深夜都有可能打开App,以及分布在海外的全球用户。这就需要得到App提供7*24的稳定高性能的服务和体验。在实际生产环境中,用户的访问行为一旦发生,从CDN到接入层、前端应用、后端服务、缓存、存储、中间件整个链路都面临着不确定的流量,无论是公有云、专有云、混合云还是自建IDC,全局的瓶颈识别、业务整体容量摸底和规划都需要高仿真的全链路压测来检验。这里的不确定的流量指的是某个大促活动、常规高并发时间段以及其他规划外的场景引起的不规则、大小未知的流量。众所周知,应用的服务状态除了会受到自身稳定性的影响,还会受到流量等环境因素的影响,并且影响面会继续传递到上下游,哪怕一个环节出现一点误差,误差在上下游经过几层累积后会造成什么影响谁都无法确定。因此,在生产环境里建立起一套验证机制,来验证各个生产环节都是能经受住各类流量的访问,成为保障服务的可用性和稳定性的重中之重。最佳的验证方法就是让事件提前发生,即让真实的流量来访问生产环境,实现全方位的真实业务场景模拟,确保各个环节的性能、容量和稳定性均做到万无一失,这就是全链路压测的诞生背景,也是将性能测试进行全方位的升级,使其具备“预见能力”。可见,全链路压测做得好,遇到真实环境的流量,系统仅仅只是再经历一次已经被反复验证过的场景,再考一遍做“做过的考题”,不出问题在意料之中将成为可能。压测的核心要素实施完整的业务压测,路径很重要。要达成精准衡量业务承接能力的目标,业务压测就需要做到一样的线上环境、一样的用户规模、一样的业务场景、一样的业务量级和一样的流量来源,让系统提前进行“模拟考”,从而达到精准衡量业务模型实际处理能力的目标,其核心要素是:压测环境、压测基础数据、压测流量(模型、数据)、流量发起、掌控和问题定位。生产环境上基础数据基本分为两种方式,一种是数据库层面不需要做改造,直接基于基础表里的测试账户(相关的数据完整性也要具备)进行,压测之后将相关的测试产生的流水数据清除(清除的方式可以固化SQL脚本或者落在系统上);另一种就是压测流量单独打标(如单独定义的Header),然后业务处理过程中识别这个标并传递下去,包括异步消息和中间件,最终落到数据库的影子表或者影子库中。这种方式详见阿里的全链路压测实践,我们也是选用了这种方式。此外,生产环境的压测尽量在业务低峰期进行从而避免影响生产的业务。全链路压测的构建过程目前,罗辑思维已经提供了得到APP、少年得到、和微信公众号得到里的得到商城等多个流量入口。每一年,罗辑思维都会举办跨年演讲,第一年是在优酷,有200多万人的在线观看,第二年是在深圳卫视合作直播,并在优酷等视频网站同步,直播过程中当二维码放出来的时候,我们就遇到了大量的用户请求,在同一时刻。这就意味着,如果没有对这个期间的流量做好准确的预估,评估好性能瓶颈,那么在直播过程中就有可能出现大面积服务中断。对整体系统进行全链路压测,从而有效保障每个流量入口的服务稳定性成为技术团队的当务之急。因此,我们在2017年,计划引入全链路压测。期间,也对系统做了服务化改造,对于服务化改造的内容之前在媒体上传播过,这次我们主要是讲下保障服务稳定性的核心 - 全链路压测。大致的构建过程如下:2017年10月启动全链路压测项目,完成商城的首页、详情、购物车、下单的改造方案设计、落地、基础数据准备和接入,以及得到APP首页和核心功能相关的所有读接口接入,并在进行了得到APP的第一次读接口的全链路压测。2017年11月商城核心业务和活动形态相对稳定,进入全面的压测&攻坚提升期,同时拉新、下单和支付改造开始,得到APP开始进入写改造,活动形态初具雏形,读写覆盖范围已经覆盖首页、听书、订阅、已购、拉新、知识账本,并启动用户中心侧用户部分的底层改造,包括短信等三方的业务挡板开发。2017年12月商城进行最后的支付改造补齐,然后是全链路压测&优化的整体迭代;得到APP全链路压测接入范围继续增加,覆盖全部首页范围和下侧5个tab,同时开始部分模块组合压测和性能调优的迭代;完成底层支付和用户中心配合改造,以及支付和短信所有外部调用的挡板改造;行了多轮次的全链路形态完整压测,并从中发现发现,给予问题定位,提升体系稳定性;梳理对焦了风险识别、预案和值班等等。经过3个多月的集中实施,我们将全链路压测接入链路174个,创建44个场景,压测消耗VUM1.2亿,发现各类问题200多个。如果说2017年全链路压测的设施在罗辑是从0到1,那么2018年就是从1到N了。从2018年开始,全链路压测进入比较成熟的阶段,我们的测试团队基于PTS和之前的经验,迅速地将全链路压测应用于日常活动和跨年活动中,并应用于新推出的业务「少年得到」上。目前,全链路压测已经成为保障业务稳定性的核心基础设施之一。全链路压测的构建与其说是一个项目,不如说是一项工程。仅凭我们自己的技术积累和人员配置是无法完成的,在此特别感谢阿里云PTS及其他技术团队提供的支持,帮助我们将全链路压测在罗辑思维进行落地。下面我们将落地过程中积累的经验以工作笔记的方式分享给大家。工作笔记A. 流量模型的确定:流量较大的时候可以通过日志和监控快速确定。但是往往可能日常的峰值没有那么高,但是要应对的一个活动却有很大的流量,有个方法是可以基于业务峰值的一个时间段内统计各接口的峰值,最后拼装成压测的流量模型。B. 脏数据的问题:无论是通过生产环境改造识别压测流量的方式还是在生产环境使用测试帐号的方式,都有可能出现产生脏数据的问题,最好的办法是:在仿真环境或者性能环境多校验多测试:有个仿真环境非常重要,很多问题的跟进、复现和debug不需要再上生产环境,降低风险。有多重机制保障:比如对了压测流量单独打标还需要UID有较强的区分度,关键数据及时做好备份等等。C. 监控:由于是全链路压测,目的就是全面的识别和发现问题,所以要求监控的覆盖度很高。从网络接入到数据库,从网络4层到7层和业务的,随着压测的深入,你会发现监控总是不够用。D. 压测的扩展:比如我们会用压测进行一些技术选型的比对,这个时候要确保是同样的流量模型和量级,可以通过全链路压测测试自动扩容或者是预案性质的手工扩容的速度和稳定性。在全链路压测的后期,也要进行重要的比如限流能力的检验和各种故障影响的实际检验和预案的演练。E. 网络接入:如果网络接入的节点较多,可以分别做一些DIS再压测,逐个确定能力和排除问题,然后整体enable之后再一起压测确定整体的设置和搭配上是否有能力对不齐的情况。比如,网络上使用了CDN动态加速、WAF、高防、SLB等等,如果整体压测效果不理想的时候建议屏蔽掉一些环节进行压测,收敛问题,常见的比如WAF和SLB之间的会话保持可能带来负载不匀的问题。当然这些产品本身的容量和规格也是需要压测和验证的,如SLB的CPS、QPS、带宽、连接数都有可能成为瓶颈,通过在PTS的压测场景中集成相关SLB监控可以方便地一站式查看,结合压测也可以更好的选择成本最低的使用方式。另外负载不匀除了前面说的网络接入这块的,内部做硬负载的Nginx的负载也有可能出现不匀的现象,特别是在高并发流量下有可能出现,这也是全链路、高仿真压测的价值。特别是一些重要活动的压测,建议要做一下业务中会真实出现的流量脉冲的情况。阿里云PTS是具备这个能力的,可以在逐级递增满足容量的背景下再观察下峰值脉冲的系统表现,比如验证限流能力,以及看看峰值脉冲会不会被识别为DDOS。F. 参数调优:压测之后肯定会发现大量的参数设置不合理,我们的调优主要涉及到了这些:内核网络参数调整(比如快速回收连接)、Nginx的常见参数调优、PHP-FPM的参数调整等等,这些网上相关的资料非常多。G. 缓存和数据库:重要业务是否有缓存;Redis CPU过高可以重点看下是否有模糊匹配、短连接的不合理使用、高时间复杂度的指令的使用、实时或准实时持久化操作等等。同时,可以考虑升级Redis到集群版,另外对于热点数据考虑Local Cache的优化机制(活动形态由于K-V很少,适合考虑Local Cache);重要数据库随着压测的进行和问题的发现,可能会有索引不全的情况;H. Mock服务:一般在短信下发、支付环节上会依赖第三方,压测涉及到这里的时候一般需要做一些特殊处理,比如搭建单独的Mock服务,然后将压测流量路由过来。这个Mock服务涉及了第三方服务的模拟,所以需要尽量真实,比如模拟的延迟要接近真正的三方服务。当然这个Mock服务很可能会出现瓶颈,要确保其容量和高并发下的接口延时的稳定性,毕竟一些第三方支付和短信接口的容量、限流和稳定性都是比较好的。I. 压测时系统的CPU阈值和业务SLA我们的经验是CPU的建议阈值在50到70%之间,主要是考虑到容器的环境的因素。然后由于是互联网性质的业务,所以响应时间也是将1秒作为上限,同时压测的时候也会进行同步的手工体感的实际测试检查体验。(详细的指标的解读和阈值可以点击阅读原文)J. 其他限流即使生效了,也需要在主要客户端版本上的check是否限流之后的提示和体验是否符合预期,这个很重要;全链路压测主要覆盖核心的各种接口,除此以外的接口也要有一定的保护机制,比如有默认的限流阈值,确保不会出现非核心接口由于预期外流量或者评估不足的流量导致核心系统容量不足(如果是Java技术栈可以了解下开源的Sentinel或者阿里云上免费的限流工具 AHAS)核心的应用在物理机层面要分开部署;省时间的技术理念目前,全链路压测已成为罗辑思维的核心技术设施之一,大幅提升了业务的稳定性。借助阿里云PTS,全链路压测的自动化程度得以进一步提高,加速了构建进程、降低了人力投入。我们非常关注技术团队的效率和专注度,不仅是全链路压测体系的构建,还包括其他很多业务层面的系统建设,我们都借助了合作伙伴的技术力量,在可控时间内支撑起业务的快速发展。当业务跑的快的时候,技术建设的路径和方式,是团队的基础调性决定的。本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。

February 22, 2019 · 1 min · jiezi

独家揭秘!阿里大规模数据中心的性能分析

阿里妹导读:数据中心已成为支撑大规模互联网服务的标准基础设施。随着数据中心的规模越来越大,数据中心里每一次软件(如 JVM)或硬件(如 CPU)的升级改造都会带来高昂的成本。合理的性能分析有助于数据中心的优化升级和成本节约,而错误的分析可能误导决策、甚至造成巨大的成本损耗。本文整理自阿里巴巴高级技术专家郭健美(花名:希伯)在Java相关行业会议的分享,主要介绍阿里大规模数据中心性能监控与分析的挑战与实践,希望对你有所启发。作者简介:郭健美(花名:希伯),阿里巴巴高级技术专家,目前主要从事数据中心的性能分析和软硬件结合的性能优化。CCF 系统软件专委和软件工程专委的委员。大家好,很高兴有机会与 Java 社区的开发者交流。我的研究领域在软件工程,主要集中在系统配置和性能方面。软件工程一个比较常见的活动是找 bug,当然找 bug 很重要,但后来也发现,即便 bug-free 的程序也会被人配置错,所以就衍生出了软件配置问题。很多软件需要配置化,比如 Java 程序或 JVM 启动时可以配置很多参数。通过配置,一套软件可以灵活地提供各种定制化的功能,同时,这些配置也会对软件整体性能产生不同的影响。当然这些还在软件配置方面,来了阿里以后,我有机会把这方面工作扩展到了硬件,会更多地结合硬件比如 CPU,来看系统的配置变更和升级改造对性能、可靠性以及业务上线效果的影响。今天主要谈谈我在这方面的一点工作。阿里最有代表性的事件是“双 11”。这里还是用的17年的数据,左上角是双十一的销售额,17年大概是 253 亿美金,比美国同期 Thanksgiving、Black Friday、Cyber Monday 加起来的销售额还要多。当然这是从业务层面去看数据,技术同学会比较关注右边的数据,17年双十一的交易峰值达到 32.5 万笔/秒、支付峰值达到 25.6 万笔/秒。对于企业来说,这么高的峰值性能意味着什么?意味着成本!我们之所以关注性能,就是希望通过持续的技术创新,不断地提高性能、同时节省成本。双十一零点的峰值性能不是一个简单的数字,其背后需要一个大规模数据中心来支撑。 简单来说,阿里的基础架构的上层是各种各样的应用,比如淘宝、天猫、菜鸟、钉钉,还有云计算和支付宝等,这也是阿里的一个特色,即具有丰富的业务场景。底层是上百万台机器相连的大规模数据中心,这些机器的硬件架构不同、分布地点也不同,甚至分布在世界各地。中间这部分我们称之为中台,最贴近上层应用的是数据库、存储、中间件以及计算平台,然后是资源调度、集群管理和容器,再下面是系统软件,包括操作系统、JVM 和虚拟化等。中台这部分的产品是衔接社区与企业的纽带。这两年阿里开源了很多产品,比如 Dubbo、PouchContainer 等,可以看出阿里非常重视开源社区,也非常重视跟开发者对话。现在很多人都在讲开源社区和生态,外面也有各种各样的论坛,但是像今天这样与开发者直接对话的活动并不是那么多,而推动社区发展最终还是要依赖开发者。这样大规模的基础架构服务于整个阿里经济体。从业务层面,我们可以看到 253 亿美金的销售额、32.5 万笔交易/秒这样的指标。然而,这些业务指标如何分解下来、落到基础架构的各个部分就非常复杂了。比如,我们在做 Java 中间件或 JVM 开发时,都会做性能评估。大部分技术团队开发产品后都会有个性能提升指标,比如降低了 20% 的 CPU 利用率,然而这些单个产品的性能提升放到整个交易链路、整个数据中心里面,占比多少?对数据中心整体性能提升贡献多少?这个问题很复杂,涉及面很广,包括复杂关联的软件架构和各种异构的硬件。后面会提到我们在这方面的一些思考和工作。阿里的电商应用主要是用 Java 开发的,我们也开发了自己的 AJDK,这部分对 OpenJDK 做了很多定制化开发,包括:融入更多新技术、根据业务需要及时加入一些 patches、以及提供更好的 troubleshooting 服务和工具。大家也知道,18年阿里入选并连任了 JCP EC(Java Community Process - Executive Committee) 职位,有效期两年,这对整个 Java 开发者社区、尤其是国内的 Java 生态都是一件大事。但是,不是每个人都了解这件事的影响。记得之前碰到一位同仁,提到 JCP EC 对阿里这种大业务量的公司是有帮助,对小公司就没意义了。其实不是这样的,参选 JCP EC 的时候,大公司、小公司以及一些社区开发者都有投票资格,小公司或开发者有一票,大公司也只有一票,地位是一样的。很多国外的小公司更愿意参与到社区活动,为什么?举个简单例子,由于业务需要,你在 JVM 8 上做了一个特性,费了很大的力气开发调试完成、业务上线成功,结果社区推荐升级到 JVM11 上,这时你可能又需要把该特性在 JVM 11 上重新开发调试一遍,可能还要多踩一些新的坑,这显然增加了开发代价、拉长了上线周期。但如果你能影响社区标准的制定呢?你可以提出将该特性融入社区下一个发布版本,有机会使得你的开发工作成为社区标准,也可以借助社区力量完善该特性,这样既提高了技术影响力也减少了开发成本,还是很有意义的。过去我们做性能分析主要依赖小规模的基准测试。比如,我们开发了一个 JVM 新特性, 模拟电商的场景,大家可能都会去跑 SPECjbb2015 的基准测试。再比如,测试一个新型硬件,需要比较 SPEC 或 Linpack 的基准测试指标。这些基准测试有必要性,因为我们需要一个简单、可复现的方式来衡量性能。但基准测试也有局限性,因为每一次基准测试都有其限定的运行环境和软硬件配置,这些配置设定对性能的影响可能很大,同时这些软硬件配置是否符合企业需求、是否具有代表性,都是需要考虑的问题。阿里的数据中心里有上万种不同的业务应用,也有上百万台分布在世界各地的不同服务器。当我们考虑在数据中心里升级改造软件或硬件时,一个关键问题是小规模基准测试的效果是否能扩展到数据中心里复杂的线上生产环境?举个例子,我们开发了 JVM 的一个新特性,在 SPECjbb2015 的基准测试中看到了不错的性能收益,但到线上生产环境灰度测试的时候,发现该特性可以提升一个 Java 应用的性能、但会降低另一个 Java 应用的性能。同时,我们也可能发现即便对同一个 Java 应用,在不同硬件上得到的性能结果大不相同。这些情况普遍存在,但我们不可能针对每个应用、每种硬件都跑一遍测试,因而需要一个系统化方法来估计该特性对各种应用和硬件的整体性能影响。对数据中心来说,评估每个软件或硬件升级的整体性能影响非常重要。比如,“双11”的销售额和交易峰值,业务层面可能主要关心这两个指标,那么这两个指标翻一倍的时候我们需要买多少台新机器?需要多买一倍的机器么?这是衡量技术能力提升的一个手段,也是体现“新技术”对“新商业”影响的一个途径。我们提出了很多技术创新手段,也发现了很多性能提升的机会,但需要从业务上也能看出来。为了解决上面提到的问题,我们开发了 SPEED 平台。首先是估计当前线上发生了什么,即 Estimation,通过全域监控采集数据,再进行数据分析,发现可能的优化点。比如,某些硬件整体表现比较差,可以考虑替换。然后,我们会针对软件或硬件的升级改造做线上评估,即 Evaluation。比如,硬件厂商推出了一个新硬件,他们自己肯定会做一堆评测,得到一组比较好的性能数据,但刚才也提到了,这些评测和数据都是在特定场景下跑出来的,这些场景是否适合用户的特定需求?没有直接的答案。通常,用户也不会让硬件厂商到其业务环境里去跑评测。这时候就需要用户自己拿这个新硬件做灰度测试。当然灰度规模越大评测越准确,但线上环境都直接关联业务,为了降低风险,实际中通常都是从几十台甚至几台、到上百台、上千台的逐步灰度。SPEED 平台要解决的一个问题就是即便在灰度规模很小时也能做一个较好的估计,这会节约非常多的成本。随着灰度规模增大,平台会不断提高性能分析质量,进而辅助用户决策,即 Decision。这里的决策不光是判断要不要升级新硬件或新版软件,而且需要对软硬件全栈的性能有一个很好的理解,明白什么样的软硬件架构更适合目标应用场景,这样可以考虑软硬件优化定制的方向。比如,Intel 的 CPU 从 Broadwell 到 Skylake,其架构改动很大,但这个改动的直接效果是什么?Intel 只能从基准测试中给答案,但用户可能根据自己的应用场景给出自己的答案,从而提出定制化需求,这对成本有很大影响。最后是 Validation,就是通常规模化上线后的效果来验证上述方法是否合理,同时改进方法和平台。数据中心里软硬件升级的性能分析需要一个全局的性能指标,但目前还没有统一的标准。Google 今年在 ASPLOS 上发表了一篇论文,提出了一个叫 WSMeter 的性能指标,主要是基于 CPI 来衡量性能。在 SPEED 平台里,我们也提出了一个全局性能指标,叫资源使用效率 RUE。基本思想很简单,就是衡量每个单位 Work Done 所消耗的资源。这里的 Work Done 可以是电商里完成的一个 Query,也可以是大数据处理里的一个 Task。而资源主要涵盖四大类:CPU、内存、存储和网络。通常我们会主要关注 CPU 或内存,因为目前这两部分消费了服务器大部分的成本。RUE 的思路提供了一个多角度全面衡量性能的方法。举个例子,业务方反映某台机器上应用的 response time 升高了,这时登录到机器上也看到 load 和 CPU 利用率都升高了。这时候你可能开始紧张了,担心出了一个故障,而且很可能是由于刚刚上线的一个新特性造成的。然而,这时候应该去看下 QPS 指标,如果 QPS 也升高了,那么也许是合理的,因为使用更多资源完成了更多的工作,而且这个资源使用效率的提升可能就是由新特性带来的。所以,性能需要多角度全面地衡量,否则可能会造成不合理的评价,错失真正的性能优化机会。下面具体讲几个数据中心性能分析的挑战,基本上是线上碰到过的具体问题,希望能引起大家的一些思考。首先是性能指标。可能很多人都会说性能指标我每天都在用,这有什么好说的。其实,真正理解性能指标以及系统性能本身并不是那么容易。举个例子,在数据中心里最常用的一个性能指标是 CPU 利用率,给定一个场景,数据中心里每台机器平均 CPU 利用率是 50%,假定应用需求量不会再增长、并且软件之间也不会互相干扰,那么是否可以把数据中心的现有机器数量减半呢?这样,理想情况下 CPU 利用率达到 100% 就可以充分利用资源了,是否可以这样简单地理解 CPU 利用率和数据中心的性能呢?肯定不行。就像刚才说的,数据中心除了 CPU,还有内存、存储和网络资源,机器数量减半可能很多应用都跑不起来了。再举个例子,某个技术团队升级了其负责的软件版本以后,通过线上测试看到平均 CPU 利用率下降了 10%,因而声明性能提升了 10%。这个声明没有错,但我们更关心性能提升以后是否能节省成本,比如性能提升了 10%,是否可以把该应用涉及的 10% 的机器关掉?这时候性能就不应该只看 CPU 利用率,而应该再看看对吞吐量的影响。所以,系统性能和各种性能指标,可能大家都熟悉也都在用,但还需要更全面地去理解。刚才提到 SPEED 的 Estimation 会收集线上性能数据,可是收集到的数据一定对吗?这里讲一个 Hyper-Threading 超线程的例子,可能对硬件了解的同学会比较熟悉。超线程是 Intel 的一个技术,比如我们的笔记本,一般现在都是双核的,也就是两个 hardware cores,如果支持超线程并打开以后,一个 hardware core 就会变成两个 hardware threads,即一台双核的机器会有四个逻辑 CPU。来看最上面一张图,这里有两个物理核,没有打开超线程,两边 CPU 资源都用满了,所以从任务管理器报出的整台机器平均 CPU 利用率是 100%。左下角的图也是两个物理核,打开了超线程,每个物理核上有一个 hardware thread 被用满了,整台机器平均 CPU 利用率是 50%。再看右下角的图,也是两个物理核,也打开了超线程,有一个物理核的两个hardware threads 都被用满了,整台机器平均 CPU 利用率也是 50%。左下角和右下角的 CPU 使用情况完全不同,但是如果我们只是采集整机平均 CPU 利用率,看到的数据是一样的!所以,做性能数据分析时,不要只是想着数据处理和计算,还应该注意这些数据是怎么采集的,否则可能会得到一些误导性的结果。数据中心里的硬件异构性是性能分析的一大挑战,也是性能优化的一个方向。比如这里左边的 Broadwell 架构,是 Intel 过去几年服务器 CPU 的主流架构,近几年在推右边的 Skylake 架构,包含最新的 Cascade Lake CPU。Intel 在这两个架构上做了很大的改动,比如,Broadwell 下访问内存还是保持多年的环状方式,而到了 Skylake 改为网格状方式。再比如,L2 Cache 到了Skylake 上扩大了四倍,通常来说这可以提高 L2 Cache 的命中率,但是 cache 越大也不代表性能就一定好,因为维护 cache coherence 会带来额外的开销。这些改动有利有弊,但我们需要衡量利和弊对整体性能的影响,同时结合成本来考虑是否需要将数据中心的服务器都升级到 Skylake。了解硬件的差异还是很有必要的,因为这些差异可能影响所有在其上运行的应用,并且成为硬件优化定制的方向。现代互联网服务的软件架构非常复杂,比如阿里的电商体系架构,而复杂的软件架构也是性能分析的一个主要挑战。举个简单的例子,图中右边是优惠券应用,左上角是大促主会场应用,左下角是购物车应用,这三个都是电商里常见的业务场景。从 Java 开发的角度,每个业务场景都是一个 application。电商客户既可以从大促主会场选择优惠券,也可以从购物车里选择优惠券,这是用户使用习惯的不同。从软件架构角度看,大促主会场和购物车两个应用就形成了优惠券应用的两个入口,入口不同对于优惠券应用本身的调用路径不同,性能影响也就不同。所以,在分析优惠券应用的整体性能时需要考虑其在电商业务里的各种错综复杂的架构关联和调用路径。像这种复杂多样的业务场景和调用路径是很难在基准测试中完全复现的,这也是为什么我们需要做线上性能评估。这是数据分析里著名的辛普森悖论,在社会学和医学领域有很多常见案例,我们在数据中心的性能分析里也发现了。这是线上真实的案例,具体是什么 App 我们不用追究。假设还用前面的例子,比如 App 就是优惠券应用,在大促的时候上线了一个新特性 S,灰度测试的机器占比为 1%,那么根据 RUE 指标,该特性可以提升性能 8%,挺不错的结果。但是如果优惠券应用有三个不同的分组,分组假设就是关联刚才提到的不同入口应用,那么从每个分组看,该特性都降低了应用的性能。同样一组数据、同样的性能评估指标,通过整体聚集分析得到的结果与通过各部分单独分析得到的结果正好相反,这就是辛普森悖论。既然是悖论,说明有时候应该看总体评估结果,有时间应该看部分评估结果。在这个例子里面,我们选择看部分评估、也就是分组上的评估结果,所以看起来这个新特性造成了性能下降,应该继续修改并优化性能。所以,数据中心里的性能分析还要预防各种可能的数据分析陷阱,否则可能会严重误导决策。最后,还有几分钟,简单提一下性能分析师的要求。这里通常的要求包括数学、统计方面的,也有计算机科学、编程方面的,当然还有更重要的、也需要长期积累的领域知识这一块。这里的领域知识包括对软件、硬件以及全栈性能的理解。其实,我觉得每个开发者都可以思考一下,我们不光要做功能开发,还要考虑所开发功能的性能影响,尤其是对数据中心的整体性能影响。比如,JVM 的 GC 开发,社区里比较关心 GC 暂停时间,但这个指标与 Java 应用的 response time 以及所消耗的 CPU 资源是什么关系,我们也可以有所考虑。本文作者:郭健美阅读原文本文来自云栖社区合作伙伴“ 阿里技术”,如需转载请联系原作者。 ...

February 21, 2019 · 2 min · jiezi

测试开发系类之接口自动化测试

接口定义代码角度的接口Interface定义:Java中的接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。特征:一个类可以实现多个接口,接口弥补的类不能多继承的缺点实际工作中提及的接口定义规范和约束,确保模块与模块之间,系统与系统之间通信通常工作中提及的接口分为两大类:直接调用某个类中的方法,模块接口测试通过协议(如http)调用服务端(web)或者外部系统的某个方法接口的本质即方法重要:实际工作中接口测试,需要提供详尽的接口文档,包括明确的输入(方法参数)和输出信息(方法的返回值)接口测试定义:接口测试属于单元测试的一种,它不属于真正的白盒测试(接口测试不需要关注代码内部逻辑实现)。接口测试可以理解为灰盒测试接口测试主要内容:检查接口返回的数据是否与预期结果一致检查接口的容错性,假如传递数据的类型错误时是否可以处理接口参数的边界值接口的性能,接口处理数据的时间也是测试的一个算法接口的安全性,特别是外部接口主流接口测试工具模块接口Java Junit3/4、TestNGPython unitestC++ cppunit外部接口和服务端(web)接口httpclientJmeter(主流)SoupUI(免费版和收费版)postman(chrome插件)、httprequester(Firefox插件)

February 19, 2019 · 1 min · jiezi

如何衡量研发效能?阿里资深技术专家提出了5组指标

阿里妹导读:新的一年,相信很多产品技术团队把研发效能提升列为重要的目标,甚至还有团队为此专门成立了项目组。然而,到底什么是好的研发效能,却很少有人能够表达清楚。标准不清晰,又何谈提升?今天,阿里研发效能部资深技术专家何勉老师,将与大家分享他多年的思考与观点,希望对你有所启发。本文将明确定义研发效能,并提供度量的五大指标,为研发效能的提升指明目标,并衡量提升的效果。本文也是关于研发效能提升及产品交付方法系列文章的开篇,为之后介绍的产品交付方法是否有效设立了标准。效率竖井是研发效能改进的最大问题产品交付需要前后职能(如:产品、开发、测试等)和平行部门(如:前端、后端、算法等)的协作。传统方法更多关注各个职能和部门的独立改进。然而,过度局部优化,往往导致效率竖井,反而损害整体效率。什么是效率竖井呢?上图描述了传统开发方式下,产品交付面临的普遍困境——各职能和部门局部优化带来一系列问题,如:基于局部信息的工作优先级安排,造成不同部门和职能间相互等待,让需求无法顺畅流动。比如前、中、后台对工作的优先处理不一致,进度无法对齐,让已经开始的需求不能及时交付。批量式的工作移交,带来进一步等待。为了最大化单个环节的效率,各职能往往倾向于批量接受和移交工作,如批量的集成,批量的转测等。进一步造成需求在过程中的积压和等待。跨部门的问题,经常得不到及时和有效的处理。公共环境的维护,就是一个典型的问题,是影响用户需求的顺畅交付。过程中需求跨部门的有效澄清、接口对齐、问题排查是另一些常见的公共问题,它们都会造成需求无法顺畅进展。以上只是一部分问题,它们共同作用,结果是:站在各自的视角,每个部门都觉得自己繁忙且“高效”;然而,站在全局和业务的视角,系统对外的反应却非常迟缓。这就是所谓效率竖井。效率竖井:由局部优化导致,表现为:各个环节和部门繁忙而“高效”,但总体的效率和响应速度却很低。它是研发效能提升的普遍症结所在。上图的折线反映了效率竖井下,单个需求的交付过程。绿色线表示需求正在被处理,红色线则表示需求在等待中。工作量不大的需求,交付周期却很长。因为大部分时间需求都处于等待状态。各个局部一片繁忙,外部却抱怨连连,相信很多人会感同身受。“持续快速交付价值的能力”是效能改进的核心目标要改进研发效能,我们必须走出效率竖井。为此组织必须把改进的焦点从关注各个资源环节,转向关注整个系统。上图反映了效能改进的关键——从以局部资源效率为核心,转变为价值流动效率为核心的改进。资源效率指的是,各环节的资源利用率和产出情况,如:资源的忙闲程度、使用率、代码产出和测试执行速度等。流动效率指的是,用户价值在系统中的流动速度,如:用户需求从提出到交付的时长,它越短越好;或者是过程中等待时间的占比,它越小越好。用户价值的流动是串起整个系统,促进整体优化的不二选择。为了提高价值的流动效率,组织就必须关注用户价值在系统中端到端的流动过程,改进整个系统,而不仅仅是局部环节。以此为基础,效能改进的目标是:持续快速交付价值的能力。这也是对研发效能的基本定义。持续快速交付价值的能力,是研发效能的核心定义。为此我们必须把改进的焦点从局部资源效率,转向价值流动效率,以保证全局和系统的优化。研发效能的度量——五组指标回答研发效能的根本问题以上定性的定义了研发效能。管理学之父德鲁克说:“如果你不能度量它,就无法改进它”。度量帮助我们更深刻认识研发效能,设定改进方向,并衡量改进效果。产品开发过程中会产生大量的数据,但数据不是度量。好的度量的标准是:它要为回答一个根本的问题讲述完整的故事。效能度量要回答的根本问题就是:一个组织“持续快速交付价值的能力”怎么样?为回答这个问题,应该提供怎样的一个完整故事呢?基于在天猫新零售、闲鱼、优酷、阿里健康、研发中台、阿里云等部门持续实践和探索,我们发展并验证了系统的研发效能指标体系。如上图所示,它由5组指标构成,分别是:第一:持续发布能力。具体包含两个细分指标,分别是:发布频率。 团队对外响应的速度不会大于其交付频率,发布频率约束团队对外响应和价值的流动速度。它的衡量标准是单位时间内的有效发布次数。发布前置时间(也被称为变更前置时间),也就是从代码提交到功能上线花费的时间,它体现了团队发布的基本能力。如果时间开销很大,就不合适加大发版频率。第二:需求响应周期。具体包含两个细分的指标,分别是:交付周期时间。指的是从确认用户提出的需求开始,到需求上线所经历的平均时长。它反映团队(包含业务、开发、运营等职能)对客户问题或业务机会的响应速度;开发周期时间。指的是从开发团队理解需求开始,到需求可以上线所经历的平均时长。它反映技术团队的响应能力。区分交付周期和开发周期,是为了解耦并明确问题,以做出针对性的改进。其中,交付周期是最终的目标和检验标准。第三:交付吞吐率。指的是单位时间内交付需求的数量。关于这一点,常见的问题是,个数能准确反映交付效率吗?这是个问题。所以,我们更多强调单个团队的需求吞吐率的前后对比,统计意义上它足以反映趋势和问题。第四:交付过程质量。它包含两个细分的指标,分别是:开发过程中缺陷的创建和修复时间分布。我们希望缺陷能够持续和及时地被发现,并且在发现后尽快修复;缺陷库存。我们希望在整个开发过程中控制缺陷库存量,让产品始终处于接近可发布状态,奠定持续交付的基础。交付过程质量的核心是内建质量,也就是全过程和全时段的质量。而非依赖特定的阶段,如测试阶段;或特定的时段,如项目后期。内建质量是持续交付的基础,关于其具体度量方法,下文会给出详细实例。第五:对外交付质量。 它包含两个细分的指标,分别是:1)单位时间的故障(线上问题)数;2)故障平均解决时长。这两者共同决定了系统的可用性。如上图所示,这5组指标,从流动效率、资源效率和质量三个方面讲述了一个完整的故事,回答了组织持续交付价值的能力如何这个核心问题。其中,持续发布能力和需求响应周期这两组指标反映价值的流动效率;吞吐率反映资源效率;交付过程质量和对外交付质量这两组指标共同反映质量水平。一个度量指标实例:缺陷趋势图针对这些指标,云效提供了丰富的度量图表,后续云效产品团队还会进行场景化的梳理,提高其可用性。我会及时跟进,用专门的文章介绍云效的完整度量方案。在这里我先介绍一个例子——关于过程质量的度量图表。“缺陷趋势图”是云效新设计的度量图表,它反映交付过程中缺陷发现和移除的时间分布,以及缺陷的库存趋势。如上图所示,图形的横坐标是日期,横坐标上方红色竖条代表这一天发现缺陷数量;横坐标下方绿色竖条代表当天解决的缺陷数量;橙色曲线代表缺陷存量。图中左右两个部分比较了两种交付模式。左半部分,团队属于小瀑布的开发模式。“迭代”前期,团队集中设计、编码,引入缺陷,但并未即时地集成和验证。缺陷一直掩藏在系统中,直到项目后期,团队才开始集成和测试,缺陷集中爆发。小瀑布模式下,过程质量差,带来大量的返工、延期和交付质量问题。该模式下,产品的交付时间依赖于何时缺陷能被充分移除,当然不能做到持续交付,也无法快速响应外部的需求和变化。并且,这一模式通常都导致后期的赶工,埋下交付质量隐患。右半部分,团队开始向持续交付模式演进。在整个迭代过程中,团队以小粒度的需求为单位开发,持续地集成和测试它们,即时发现和解决问题。缺陷库存得到控制,系统始终处于接近可发布状态。这一模式更接近持续发布状态,团队对外的响应能力随之增强。缺陷趋势图从一个侧面反映了团队的开发和交付模式。它引导团队持续且尽早发现缺陷并及时移除它们。控制缺陷库存,让系统始终处于接近可发布状态,保障了持续交付能力和对外响应能力。缺陷趋势图是云效研发效能度量图表中的一个。后面,我会用专门的文章系统地解读这些图表的使用。效能改进的目标设定:部分团队的2-1-1愿景以上,我们介绍了研发效能度量。基于这样的度量体系,应该设定怎样的目标呢?我们在多个团队的实施过程中,逐渐沉淀出了可供参考的目标体系,它可以总结为三个数字——“2-1-1”。“2-1-1”最初来自天猫新零售,其后在闲鱼和研发中台、阿里云等团队完善和采用。什么是“2-1-1”呢?“2"指的2周的交付周期,85%以上的需求可以在2周内交付;第一个“1”指的是1周的开发周期,85%以上的需求可以在1周内开发完成;第二个“1”指的是1小时的发布前置时间 - -提交代码后可以在1小时内完成发布。[1]达成“2-1-1”的愿景并不容易。1小时的发布前置时间,需要持续交付流水线,产品架构体系和自动测试、自动部署等能力的提升。1周的开发周期,涉及更多的能力和实践,如:需求的拆分和管理,开发团队的分工协作模式,以及持续集成和持续测试实践;最困难的则是2周的交付周期,首先它要以另外两个指标为基础,同时还涉及整个组织各职能和部门的协调一致和紧密协作;“2-1-1”的目标都是关于流动效率的,你可能会问,那资源效率和质量呢?我们专注于流动效率,是因为它是组织效能改进的抓手,能够触发深层次的和系统性的改进。就像上面分析的,为达成“2-1-1”目标,团队需要技术、管理、协作等方面的全面实践升级,而这些实践的落地,必然会带来资源效率和质量的提升,并体现到相应的度量指标上。当然,“2-1-1”是源自特定的团队,并非所有团队都要使用同样的值,比如对于涉及硬件开发的团队,两周的交付周期通常过于挑战。组织应根据自己的上下文设定恰当的目标,重要的是,它要指明改进的方向。总结本文定义了研发效能,它指的是一个组织持续快速交付价值的能力,可以从流动效率、资源效率和质量三个方面来衡量。其中流动效率是改进研发效能的核心抓手,它带来系统和全局的改进。如上图所示,研发效能最终为组织效能服务,必须体现到利润、增长、客户满意度等组织效能上;同时,研发效能的提升要落实到具体技术和管理的实践中,才可能发生。定义和度量是提升研发效能的基础,相信你更关心提升研发的具体实践和方法。后续,何勉老师将综合多个团队的实践,介绍可操作的实践体系和落地方法,还请持续关注“阿里技术”公众号,我们将尽快为你送上。本文作者:何勉阅读原文本文来自云栖社区合作伙伴“ 阿里技术”,如需转载请联系原作者。

February 18, 2019 · 1 min · jiezi

【随笔】测试外包的正确打开方式

为了节约研发成本很多组织都采用了外包的模式,尤其是测试方面的工作。带来的成本的节约确实也是真金白银的。但是财务目标是大boss们考虑的问题,基层开发团队的第一目标是产品。围绕着产品,测试外包在实施过程中,带来了一些问题,准备了一些对策。1.对业务不熟悉外包测试朋友很多都是入行不久或切换到这个业务不久,而且稳定性不足流动性较大经常换人,造成了对业务不熟悉或业务刚熟悉人跑了。不利于产品问题的发现,对灰度质量造成较大压力,增加产品团队整体的压力。对策:培训+考试,上岗前进行业务培训和考试,过关后进入团队。2.主观意识不主动由于薪酬机制等方面的原因,往往容易发生照章办事,跟着case跑,局限于case,对产品场景和如何测试产品缺乏独立思考。对策:(1)帮助它们融入团队,增强对产品参与感,荣誉感。(2)对于平时的表现业务能力等,于外包方保持沟通,定期review考核。

February 15, 2019 · 1 min · jiezi

深耕品质,腾讯WeTest《2018中国移动游戏质量白皮书》正式发布

本文由云+社区发表作者:腾讯WeTest原文链接:https://wetest.qq.com/lab/view/437.html对于游戏行业的不少人来说,2018年是一个多事之秋。放眼大局,游戏玩家中,70%用户已有3年以上的互联网经验,玩家们对游戏审美迅速提高。而相较2017年,游戏工委&伽马数据《2018年中国游戏产业报告显示》,中国游戏市场实际收入同比增长率,从23%下降至5.3%,这说明国内游戏行业增量已经转为了深耕存量的阶段。正如腾讯WeTest总经理方亮所言,游戏厂商依靠上游用户圈地的运动已经走到了尽头。但在险象环生的同时,2018年也更是充满变化与机遇的一年。2019年1月7日,腾讯WeTest《2018中国移动游戏质量白皮书》(以下简称“白皮书”)正式发布,现已于腾讯WeTest官方网站开放下载。通过腾讯大数据及其他第三方平台数据,白皮书着重从市场硬件、兼容、客户端性能、服务器性能、安全、玩家口碑、小游戏等玩家体验最敏感的质量视角进行数据采集与深入分析,客观地反应出了2018年中国移动游戏研发市场的现状与变化。图1.png【图1】《2018中国移动游戏质量白皮书》正式发布风险与机遇并存,风起云涌的2018从游戏品类出发观察,2018年间,已有不少产品在精品化与多元化上深挖起来。角色扮演类、动作类、策略类游戏仍占苹果应用商店的60%的份额。其中,角色扮演类游戏与动作类游戏的比重持续下降,但策略类游戏则依旧占比17%,风华正盛。这些头部游戏品类已进入以品质为导向的存量阶段,一并呈现出精品化、细分化、多元化的态势。图2.png【图2】App Store 游戏畅销榜Top1000游戏类型分布同时,移动游戏市场上也呈现出更为丰富的多元化特征,“战术竞技类”等涵盖多元化玩法的游戏开始登上主流舞台。而更为垂直的细分领域已衍生至14种,其中模拟游戏、家庭游戏、桌面游戏开始初露头角。另一方面,玩法轻便、即点即玩的小游戏也开始展露更多的能量。与此同时,2018年间,移动硬件设备的迭代更加速了。图3.png【图3】Android TOP100硬件配置占比统计屏幕方面,虽然16:9的设备仍是主流。此外,“刘海屏”“全面屏”等异形屏设备的覆盖人群更占据市场7%左右。这些来自用户需求与技术发展的变化,同样也为游戏开发者在游戏兼容性、客户端、服务器、安全性等一系列适配上提出了更高的要求。市场需求发展加上外部硬件的变化为游戏开发者们带来新的机遇,而想要在资源有限的情况下,于项目立项的阶段取胜,就要准确把握用户瞬息万变的审美需求。唯有掌握了曾在2018年发生的问题,拥抱变化,才能在2019年扭转乾坤。TOP 100 机型游戏综合性能数据公布,客户端问题玩家最为敏感腾讯WeTest企鹅风讯平台对抽样游戏在2018年产生的舆情调查显示,登录、卡顿、掉线问题最受玩家关注。从游戏分布来看,登录问题主要集中于动作类、策略类游戏;而卡顿问题则是动作类、角色扮演类游戏的高发问题。图4.png【图4】玩家舆情报警数量分布(客户端问题包括:登录、闪退、更新、下载、安装、启动、硬件和兼容问题)因此,针对腾讯大数据统计出的TOP 100 Android机型以及 TOP iOS机型,腾讯WeTest甄选了战术竞技、体育竞技、ARPG等游戏,一并综合生成性能评分,方便开发者在研发期间,选择性能调优阶段的参考机型。图5.png图6.png【图5】【图6】Android、iOS两大阵营主流设备性能表现首度公开腾讯手游性能大数据,战术竞技游戏服务器痛点同步揭露在2018年,根据苹果应用商店游戏畅销榜可知,多头寡头效应依然明显。而对于这些极具竞争力的产品标杆,开发者们无法详细掌握相关数据,因此更无法找到优化方向。为了助推手游行业标准建立,协助行业性能优化提升,腾讯WeTest首次公开腾讯游戏真实性能数据,战术竞技、MMORPG、体育竞技等主流品类游戏均囊括其中。图7.png【图7】腾讯Android手游性能大数据图8.png【图8】腾讯iOS手游性能大数据同时,由于站在2018年风口上的战术竞技游戏,通常采用UE4引擎机制,战斗服会承担大量同步、物理、逻辑等计算,因此对游戏的服务器性能带来众多挑战。白皮书中,全面剖析了来自服务器的技术性难点,值得开发者重点关注。图9.jpg【图9】战术竞技游戏服务器的痛点兼容性问题不容忽视,显示异常问题需要关注基于腾讯WeTest平台测试对Android产品大数据统计,平均每次测试能够发现游戏产品拥有10.1个兼容性问题,其中,显示异常、Crash问题占比超过70%。此外,功能问题亦占比14%。图10.png【图10】Android游戏兼容性问题类型分布随着Android机型内存升高,相较2017年,APK crash、安装失败等致命问题显著降低。但高配机型中异形屏的出现,导致UI异常问题频发。图11.png【图11】不同Android内存、系统机型兼容性问题分布情况同时,2018年中,Android 9 Pie面向全球发布,新的流量池正在被挖掘与重视。随着系统版本的升高与开发者的逐渐适应,Crash问题正逐步减少,但Android 8 的显示异常问题比重仍最高,需要重点关注。另一方面, iOS系统平均每次测试能够发现3-4个问题。其中,显示异常占比高达56%,需要开发者重点关注。而从iOS 10开始,安装失败与无响应问题迅速减少,基本为零。图12.png【图12】iOS游戏兼容性问题类型分布安全问题间不容发,强交互手游需重点关注外挂漏洞在移动APP中,游戏一直是安全漏洞的重灾区。腾讯WeTest对产品研发期与运营期手游安全情况进行比较,发现了最新问题:其一,强交互游戏依然是外挂漏洞的“温床”;其二,研发期游戏漏洞如果没有解决,会在运营期衍生多个外挂变种,严重影响手游平衡性;其三,动作射击类游戏由于其“强交互”特质,成为了外网外挂的“宠儿”。图13.png【图13】手游研发期与运营期间,平均单款手游安全问题占比而数据显示,最为致命的外挂问题下,“刷道具”为手游研发期最多的安全漏洞,定制外挂与通用修改器,则是手游运营期外网常见作弊方式。此外,在手游研发期存在的致命安全漏洞问题,将由于高收益在运营期会明显扩张,占比从研发期的34%上升至运营期的50%,因此开发者在手游研发期就要防微杜渐、未雨绸缪。图14.png【图14】手游研发期间,具体外挂安全问题占比全面分析小游戏测试难点,功能与适配是主流问题2018年,是小游戏爆发的元年,亦是小游戏精品化、重度化的一年。从小游戏的上线产品的比例来看,角色扮演类占据26%,休闲益智类占据24%,而在2018年下半年开始,一些策略战棋类的重度游戏类型开始出现在小游戏中,重度游戏在小游戏市场中,并非没有机会。而从产品本身观察,小游戏目前正在向精品化轻度化发展。图15.png【图15】2018年小游戏类型分布情况而相较手游通用测试模块,小游戏有着相对独立的测试内容。依托于腾讯旗下小游戏研发经验以及案例,白皮书显示,功能问题与适配问题占据小游戏测试问题主流,同时性能问题亦占据16%不容忽视。图16.png【图16】小游戏测试问题分布情况针对小游戏性能情况统计,有6%-7%的用户因游戏加载时间过长从而流失,小游戏用户流失情况与加载时长形成正比。因此,针对小游戏的加载时间,腾讯游戏根据不同机型档次,设立了加载时间上线标准。图17.png【图17】小游戏加载时间上线标准而在小游戏所面临的适配问题中,67%的问题来自显示异常,异形屏带来的UI适配问题非常明显,亟需开发者关注。 图18.png【图18】小游戏适配问题类型分布展望未来,腾讯WeTest与您同行在未来的2019年里,无论是游戏产品,亦或是互联网下的其他行业产品,统一、批量生产的简单模式定会被逐步迭代。正如WeTest总经理方亮先生所言,取胜关键必然是品质与创意,因此开发者更要做好严格的品质把控,务必珍惜来之不易的流量。在此之下,奔跑的不仅是开发者们,腾讯WeTest作为第三方平台也将与时俱进,超越品质,点亮游戏。作为一站式品质开放平台,腾讯WeTest一直致力于推动优质内容顺利产出,帮助开发者解决难题,在测试效率与质量上产生1+1>2的效果。在过去的一年间,腾讯WeTest对旗下兼容、性能、安全、舆情等服务进行了全方位品质升级,并持续输出云游戏、AI自动化测试、基于AI的同步控制系统、性能大数据分析、舆情大数据分析、舆情预警等前沿技术。而在未来,腾讯WeTest将从游戏出发,逐步将前沿的技术和服务辐射向金融、电商、视频等更多行业,助力更多精品的诞生。此文已由腾讯云+社区在各渠道发布获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号

February 14, 2019 · 1 min · jiezi

在Shell中进行独立的集成测试

翻译:疯狂的技术宅原文:https://zachholman.com/posts/…本文首发微信公众号:jingchengyideng欢迎关注,每天都给你推送新鲜的前端技术文章我在开发 during.com 时创建了一系列的微服务,它们被用来做一些同步、导入和单调繁重数据处理之类的工作。如果你对微服务不熟悉,那么它只是一个花哨的名词而已,意思就是“让我们把这些该死的业务逻辑散落的到处都是!”不管怎样,我的微服务到处都是,嗯,的确是“微”。不过我绝对不是一个逗逼,我已经多次重写了自己的web服务,从Rails中的一个目录开始,然后迁移到Ruby,接着是Crystal,之后是Go,现在又回到了Ruby。这并不是在浪费时间,这只是为了以防万一而尝试新的方法。最后我又把这些服务迁移回了Ruby。这段时间Ruby的表现真是没得说,它能很轻松的进行扩展来应对用户的请求。不过目前这个应用还没有进入beta测试阶段,在你还没有用户的时候,它的确容易扩展。实际上如果在没有用户使用的前提下,几乎任何关于软件开发的一切问题都不算什么,当然除了赚钱(当然了这也并没有成为硅谷任何一家公司的障碍)。好吧我跑题了,我一直都很享受用Shell来测试这些服务的过程。在POSIX shell环境下测试, 或者 UBIQUITOUSIX shell 环境也可以我已经用Shell脚本为这些服务编写了测试,很不错。首先,不需要为基本环境操心。无论是我的AWS实例,还是我的持续集成服务器,还有我自己的开发机上都有Shell环境。所以不需要安装任何东西,也不必运行什么Docker实例(实际上用它肯定也没什么坏处)。不过最重要的一点是,我的测试是独立的,独立于将来可能会使用的任何语言。我可以在不同的语言和框架之间进行切换,而不需要对测试脚本做任何改变。这一点非常重要,因为如果你的v1版本中有一个微妙的bug,而测试却通过了,当你开始重写v2版本的服务时,如果在无意中修正了这个bug,测试将可能失败。这意味着你暴露给其它服务的API不会因此而意外中断,你可以使用其它服务来暂时顶替,为修复bug争取时间,而不是在部署到生产环境后大吃一惊。这些测试的工具也是相当不错的,这些年我一直在用我的好友Blake Mizerany写的一个Shell环境下的小工具roundup。最近我一直在使用Sam Stephenson的 bats,现在它已经形成了一个十分活跃的社区(哈,谁能想到呢,仅仅是一个shell测试工具而已)。我的Shell测试看起来就像这样,用bats:@test “Responds with events within the given timespan” { url_params="?starts_at=2017-05-01T00:00:00-00:00&ends_at=2017-05-31T00:00:00-00:00" run curl “$URL$url_params” –silent -H “Authorization: Bearer:$bearer” assert_output –partial “Test Event 0” assert_output –partial “Test Event 2” refute_output –partial “Test Event 5” refute_output –partial “No location data” refute_output –partial “Not included in the date span”}测试非常简单,也容易理解。基本上就是运行curl然后检查输出结果,完成。整合周围的一切最后一点,这些微服务非常之小,我完全可以不用为它们写任何其它的测试,只需要写集成测试即可。全栈测试(full-stack)真的非常有趣,但是人们对此很谨慎,不知道它会成为下一个好主意还是成为世界上最差劲的想法。对于它的价值,GitHub的主旨是随时愉快地运行在零单元测试的生产环境下。总的来说我正在实践这种悬而未决的理论,不过我会悬崖勒马。如果你感兴趣的话可以阅读关于这个话题更多的文章。但是我要说的是在这种情况下,哇,一股新鲜空气袭来。我们的测试是可移植的,如果我重写了服务,不必为它们重写新的测试。我只需要通过自己的基于 shell 的测试即可。本文首发微信公众号:jingchengyideng欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章

February 13, 2019 · 1 min · jiezi

[缘起] 前端 E2E 测试的困境

与其它自动化测试不同,前端的e2e测试一直是个老大难问题,难点主要在于 如何描述测试。自动化测试的核心是检查特定输入能不能得到符合预期的结果。对于单元测试和 API 测试来说,“特定输入”就是函数或者接口的参数,结果也是当前语言的数据类型或者通用的比如 JSON,二者一方面好描述,另一方面好验证。写起来就没什么难度。比如sum(a, b) { return a + b;}要验证输入 1 和 2,返回 3,则可以写成:const assert = require(‘assert’);describe(‘sum’, function() { it(‘should equal’, function() { assert.equal(sum(1, 2), 3); });});这里输入输出都很容易描述,所以自动化测试就没什么难度。但是 UI 测试并非如此。UI 是做给用户看的,所以,一个 UI 测试应该写成这种形式(这里拿一个类似于活动行的应用来举例子):打开应用首页呈现出活动列表点击列表中的任一项进入活动详情页点击报名按钮登记个人信息点击付费按钮完成付费看到报名成功的信息这个过程当中用户的操作,很难和程序当中的抽象产物,比如按钮、列表等产生关联。操作的结果,“进入下一页”,也很难进行进一步的校验。所以在之前的生产实践中,大家喜欢用选择器来进行 E2E 测试,代表产品有 Cypress 和 Nightwatch。我觉得,这里一方面有 jQuery 带来的使用习惯延续和思维定势;另一方面,借助 XPath,找到特定元件,进行交互操作和校验元素几乎是大家唯一的选择。使用 CSS 选择器的方案并不完美,比如:选择器本身,和 UI 视图可能并没有强关联,写出来的测试可读性不强,一段时间之后回头去看,很可能会看不懂。HTML 的 DOM 结构并不稳固,随着功能增减版本迭代经常发生变化,这个时候我们就要跟着修改测试用例。DOM 结构不能反应视图的真实状态,很可能会出现虽然测试通过,但实际上在用户眼里仍然是错误的表现。那么,说了这么多不容易、其它方案的不完美,我的解决方案又是怎么样的呢?这里请容许我卖一下关子,下次再介绍由我厂 OpenResty Inc. 打造的前端自动化工具 Navlang。大家好,我是 Meathill,目前在 OpenResty Inc. 负责前端开发工作。今年我会利用业余时间,介绍我厂在前端方面的工作,包括各种垂直领域,比如自动化、DevTool protocol、插件开发、Vue、CodeMirror、组件化等等,内容包括进展、经验、心得、踩坑、产品等。欢迎大家关注本专栏,也欢迎大家光临我的博客:山维空间。如果你有任何疑问问题都可以在 SF 和我的博客上向我发问,我一定尽量答复。

February 4, 2019 · 1 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

PHPUnit实践一(初识)

本系列教程所有的PHPUnit测试基于PHPUnit6.5.9版本,Lumen 5.5框架前置日常我们的普通用到的测试:代码直接echo,debug等方法测试 -> 跟踪细节断点型测试log日志辅助测试 -> 跟踪细节断点型测试辅助工具,postman之类的做请求类测试->请求类测试浏览器直接测试->浏览器测试单元测试单元测试是针对程序的最小单元来进行正确性检验的测试工作,程序单元就是应用的最小可测试部件,一个单元可能是单个程序,类,对象,方法等单元测试是用来测试包或者程序的一部分代码或者一组代码的函数。测试的目的是确认目标代码在给定的场景下,有没有按照期望工作。一个场景是正向路经测试,就是在正常执行的情况下,保证代码不产生错误的测试。这种测试可以用来确认代码可以成功地向数据库中插入一条工作记录。另外一些单元测试可能会测试负向路径的场景,保证代码不仅会产生错误,而且是预期的错误。这种场景下的测试可能是对数据库进行查询时没有找到任何结果,或者对数据库做了无效的更新。在这两种情况下,测试都要验证确实产生了错误,且产生的是预期的错误。总之,不管如何调用或者执行代码,所写的代码行为都是可预期的优点或改善解决问题减少bug通过运行单元测试可以直接测试各个功能的正确性,有bug可以直接发现并解决,如果要等到跟其他的功能对接,进行连贯测试,测试比较麻烦,而且bug不能及早的发现并解决快速定位bug如果是web项目的某一个功能,平常我们定位bug可能是页面输入值,后台断点,一步一步的需要bug位置,如果有编写单元测试,则可以直接修改数据,运行单元测试即可,快速有限提高代码质量如果每一个部件都是完美的,那么组合起来肯定也是完美的。整体代码质量就得到了保障减少调试时间当不知问题所在的时候,可能需要各种调试与运行,而如果所有的都有编写单元测试,那么可以直接运行单元测试,就能定位问题所在位置。PHPUnitPHPUnit是一个面向PHP程序员的测试框架,这是一个xUnit的体系结构的单元测试框架。版本主版本初始版本PHP兼容性支持后台框架对应版本PHPUnit 82019年2月1日PHP 7.2, PHP 7.3, PHP 7.4在2021年2月5日结束支持 PHPUnit 72018年2月2日PHP 7.1, PHP 7.2, PHP 7.3在2020年2月7日结束支持 PHPUnit 62017年2月3日PHP 7.0, PHP 7.1, PHP 7.2在2019年2月1日结束支持*PHPUnit 52015年10月2日PHP 5.6, PHP 7.0, PHP 7.1在2018年2月2日结束支持 PHPUnit 42014年3月7日PHP 5.3, PHP 5.4, PHP 5.5, PHP 5.6在2017年2月3日结束支持 你的第一个单元测试demo目录结构tests├── ExampleTest.php 测试用例└── TestCase.php Lumen自带测试基类,继承PHPunit代码<?phpclass ExampleTest extends TestCase{ /** * 测试断言成功. * * @return void / public function testTrue() { $this->assertTrue(true); } /* * 测试断言失败 * * @return void / public function testFailure() { $this->assertTrue(false); } /* * 测试不加断言,risky. * * @return void */ public function testRisky() { }}运行../vendor/bin/phpunit ExampleTest.php输出PHPUnit 6.5.9 by Sebastian Bergmann and contributors..FR 3 / 3 (100%)Time: 902 ms, Memory: 10.00MBThere was 1 failure:1) ExampleTest::testFailureFailed asserting that false is true./web/www/wpt/gt-api/tests/ExampleTest.php:22–There was 1 risky test:1) ExampleTest::testRiskyThis test did not perform any assertionsFAILURES!Tests: 3, Assertions: 2, Failures: 1, Risky: 1.说明3个测试方法,2个断言 一个断言失败,一个测试方法无断言输出标识说明.当测试成功时输出。F当测试方法运行过程中一个断言失败时输出。E当测试方法运行过程中产生一个错误时输出。R当测试被标记为有风险时输出。S当测试被跳过时输出。I当测试被标记为不完整或未实现时输出。参考PHPUnit 6.5 官方文档 ...

January 30, 2019 · 1 min · jiezi

PHPUnit实践二(生命周期)

本系列教程所有的PHPUnit测试基于PHPUnit6.5.9版本,Lumen 5.5框架PHPUnit测试一个文件类的生命周期理解PHPUnit加载机制(Lumen版)PHPUnit自动测试文件会自动加载引入(include file)PHPUnit去启动setUp方法,Lumen里重写了setUp,加载了bootstrap/app.phpapp.php加载了composer的autoload,借此你项目所有自动加载环境都有了,不过不包含tests目录至此我们引入了我们需要构建自己的自动加载类增加tests的自动加载我们需要给tests下的测试用例创建类似下面的结构├── BaseCase.php 重写过Lumen基类的测试基类,用于我们用这个基类做测试基类,后续会说明├── bootstrap.php tests自动加载文件├── Cases 测试用例目录│ └── Demo 测试模块│ ├── logs 日志输出目录│ ├── PipeTest.php PHPUnit流程测试用例│ ├── phpunit.xml phpunit配置文件xml│ └── README.md 本模块测试用例说明├── ExampleTest.php 最原始测试demo└── TestCase.php Lumen自带的测试基类tests自动加载文件代码<?php/** * 测试框架的自动加载测试文件类 * User: qikailin /error_reporting(E_ALL ^ E_NOTICE);require DIR . ‘/../vendor/autoload.php’;define(‘MY_TESTS_DIR_BASE’, realpath(dirname(FILE)));set_include_path(implode(PATH_SEPARATOR, array( WPT_TEST_DIR_BASE, get_include_path())));spl_autoload_register(function ($class) { $classFile = MY_TESTS_DIR_BASE . DIRECTORY_SEPARATOR . str_replace([“Test\”, “/”, “\”], ["", DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR], $class) . “.php”; if (file_exists($classFile)) { include_once $classFile; }}, true, false);phpunit.xml自动加载配置bootstrap文件<?xml version=“1.0” encoding=“UTF-8”?><phpunit bootstrap="../../bootstrap.php" convertErrorsToExceptions=“true” convertNoticesToExceptions=“false” convertWarningsToExceptions=“false” colors=“true”></phpunit>流程测试代码PipeTest 流程代码<?php/* * 测试类的每个测试方法都会运行一次 setUp() 和 tearDown() 模板方法(同时,每个测试方法都是在一个全新的测试类实例上运行的)。 * 另外,setUpBeforeClass() 与 tearDownAfterClass() 模板方法将分别在测试用例类的第一个测试运行之前和测试用例类的最后一个测试运行之后调用。 * 如果有需要共享的对象或变量,可以放在setUpBeforeClass,并设置为静态属性 * User: qikailin /namespace Test\Cases\Demo;use Test\BaseCase;class PipeTest extends BaseCase{ public static function setUpBeforeClass() { fwrite(STDOUT, METHOD . “\n”); } public function setUp() { fwrite(STDOUT, METHOD . “\n”); } /* * 测试方法的前置执行,setUp之后 / protected function assertPreConditions() { fwrite(STDOUT, METHOD . “\n”); } public function testOne() { fwrite(STDOUT, METHOD . “\n”); $this->assertTrue(true); } public function testTwo() { fwrite(STDOUT, METHOD . “\n”); // 两个交换下顺序可以看下效果 // 正常执行成功assert可以继续执行,失败的会跳出方法 $this->assertArrayHasKey(’d’, [’d’=>1, ’e’=>2]); $this->assertTrue(false); } public function testThree() { fwrite(STDOUT, METHOD . “\n”); $this->assertTrue(false); } public function testFour() { fwrite(STDOUT, METHOD . “\n”); } /* * 测试方法成功后的后置执行,tearDown之前 / protected function assertPostConditions() { fwrite(STDOUT, METHOD . “\n”); } public function tearDown() { fwrite(STDOUT, METHOD . “\n”); } public static function tearDownAfterClass() { fwrite(STDOUT, METHOD . “\n”); } /* * 不成功后拦截方法 * 必须重新抛出错误,如果不抛出错误,断言会当成成功了 */ public function onNotSuccessfulTest(\Throwable $e) { fwrite(STDOUT, METHOD . “\n”); // 必须重新抛出错误,如果不抛出错误,断言会当成成功了 throw $e; }}运行# 你可以把vendor/bin加入到环境变量PATHcd tests/Demo../../../vendor/bin/phpunit运行输出PHPUnit 6.5.9 by Sebastian Bergmann and contributors.Test\Cases\Demo\PipeTest::setUpBeforeClassTest\Cases\Demo\PipeTest::setUpTest\Cases\Demo\PipeTest::assertPreConditionsTest\Cases\Demo\PipeTest::testOneTest\Cases\Demo\PipeTest::assertPostConditionsTest\Cases\Demo\PipeTest::tearDown.Test\Cases\Demo\PipeTest::setUpTest\Cases\Demo\PipeTest::assertPreConditionsTest\Cases\Demo\PipeTest::testTwoTest\Cases\Demo\PipeTest::tearDownTest\Cases\Demo\PipeTest::onNotSuccessfulTestFTest\Cases\Demo\PipeTest::setUpTest\Cases\Demo\PipeTest::assertPreConditionsTest\Cases\Demo\PipeTest::testThreeTest\Cases\Demo\PipeTest::tearDownTest\Cases\Demo\PipeTest::onNotSuccessfulTestFTest\Cases\Demo\PipeTest::setUpTest\Cases\Demo\PipeTest::assertPreConditionsTest\Cases\Demo\PipeTest::testFourTest\Cases\Demo\PipeTest::assertPostConditionsTest\Cases\Demo\PipeTest::tearDownR 4 / 4 (100%)Test\Cases\Demo\PipeTest::tearDownAfterClassTime: 1.29 seconds, Memory: 6.00MBThere were 2 failures:1) Test\Cases\Demo\PipeTest::testTwoFailed asserting that false is true./xxx/tests/Cases/Demo/PipeTest.php:472) Test\Cases\Demo\PipeTest::testThreeFailed asserting that false is true./xxx/tests/Cases/Demo/PipeTest.php:53–There was 1 risky test:1) Test\Cases\Demo\PipeTest::testFourThis test did not perform any assertionsFAILURES!Tests: 4, Assertions: 4, Failures: 2, Risky: 1.Generating code coverage report in HTML format … done整理流程输出Test\Cases\Demo\PipeTest::setUpBeforeClassTest\Cases\Demo\PipeTest::setUpTest\Cases\Demo\PipeTest::assertPreConditionsTest\Cases\Demo\PipeTest::testOneTest\Cases\Demo\PipeTest::assertPostConditionsTest\Cases\Demo\PipeTest::tearDownTest\Cases\Demo\PipeTest::setUpTest\Cases\Demo\PipeTest::assertPreConditionsTest\Cases\Demo\PipeTest::testTwoTest\Cases\Demo\PipeTest::tearDownTest\Cases\Demo\PipeTest::onNotSuccessfulTestTest\Cases\Demo\PipeTest::setUpTest\Cases\Demo\PipeTest::assertPreConditionsTest\Cases\Demo\PipeTest::testThreeTest\Cases\Demo\PipeTest::tearDownTest\Cases\Demo\PipeTest::onNotSuccessfulTestTest\Cases\Demo\PipeTest::setUpTest\Cases\Demo\PipeTest::assertPreConditionsTest\Cases\Demo\PipeTest::testFourTest\Cases\Demo\PipeTest::assertPostConditionsTest\Cases\Demo\PipeTest::tearDownTest\Cases\Demo\PipeTest::tearDownAfterClass总结一个测试类文件,从setUpBeforeClass加载,且仅此加载一次每个测试方法都会走的过程:setUp->assertPreConditions->测试方法->[assert成功执行:assertPostConditions]->tearDown->[assert执行失败:onNotSuccessfulTest,且本方法需要抛出错误]本个测试类文件执行tearDownAfterClass结束参考PHPUnit 6.5 官方文档 ...

January 30, 2019 · 2 min · jiezi

React 测试指南

前端测试金字塔对于一个 Web 应用来说,理想的测试组合应该包含大量单元测试(unit tests),部分快照测试(snapshot tests),以及少量端到端测试(e2e tests)。参考测试金字塔,我们构建了前端应用的测试金字塔。单元测试针对程序模块进行测试。模块是软件设计中的最小单位,一个函数或者一个 React 组件都可以称之为一个模块。单元测试运行快,反馈周期短,在短时间内就能够知道是否破坏了代码,因此在测试组合中占据了绝大部分。快照测试对组件的 UI 进行测试。传统的快照测试会拍摄组件的图片,并且将它和之前的图片进行对比,如果两张图片不匹配则测试失败。Jest 的快照测试不会拍摄图片,而是将 React 树序列化成字符串,通过比较两个字符串来判断 UI 是否改变。因为是纯文本的对比,所以不需要构建整个应用,运行速度自然比传统快照测试更快。E2E 测试相当于黑盒测试。测试者不需要知道程序内部是如何实现的,只需要根据业务需求,模拟用户的真实使用场景进行测试。技术选型测试种类技术选型单元测试Jest + Enzyme快照测试JestE2E 测试jest-puppeteerJest 是 Facebook 开源的测试框架。它的功能很强大,包含了测试执行器、断言库、spy、mock、snapshot 和测试覆盖率报告等。Enzyme 是 Airbnb 开源的 React 单元测试工具。它扩展了 React 官方的 TestUtils,通过类 jQuery 风格的 API 对 DOM 进行处理,减少了很多重复代码,可以很方便的对渲染出来的结果进行断言。jest-puppeteer 是一个同时包含 Jest 和 Puppeteer 的工具。Puppeteer 是谷歌官方提供的 Headless Chrome Node API,它提供了基于 DevTools Protocol 的上层 API 接口,用来控制 Chrome 或者 Chromium。有了 Puppeteer,我们可以很方便的进行端到端测试。React 测试策略测试本质上是对代码的保护,保证项目在迭代的过程中正常运行。当然,写测试也是有成本的,特别是复杂逻辑,写测试花的时间,可能不比写代码少。所以我们要制定合理的测试策略,有针对性的去写测试。至于哪些代码要测,哪些代码不测,总的来说遵循一个原则:投入低,收益高。「投入低」是指测试容易写,「收益高」是测试的价值高。换句话说,就是指测试应该优先保证核心代码逻辑,比如核心业务、基础模块、基础组件等,同时,编写测试和维护测试的成本也不宜过高。当然,这是理想情况,在实际的开发过程中还是要进行权衡。单元测试基于 React 和 Redux 项目的特点,我们制定了下面的测试策略:分类哪些要测?哪些不测?组件 有条件渲染的组件(如 if-else 分支,联动组件,权限控制组件等) 有用户交互的组件(如 Click、提交表单等)* 逻辑组件(如高阶组件和 Children Render 组件) connect 生成的容器组件 纯组合子组件的 Page 组件 纯展示的组件 组件样式Reducer有逻辑的 Reducer。如合并、删除 state。纯取值的 reducer 不测。比如(_, action) => action.payload.data Middleware全测无Action Creator无全不测方法 validators formatters* 其他公有方法私有方法公用模块全测。比如处理 API 请求的模块。无Note: 如果使用了 TypeScript,类型约束可以替代部分函数入参和返回值类型的检查。快照测试Jest 的 snapshot 测试虽然运行起来很快,也能够起到一定保护 UI 的作用。但是它维护起来很困难(大量依赖人工对比),并且有时候不稳定(UI 无变化但 className 变化仍然会导致测试失败)。因此,个人不推荐在项目中使用。但是为了应付测试覆盖率,以及「给自己信心」,也可以给以下部分添加 snapshot 测试:Page 组件:一个 page 对应一个 snapshot。纯展示的公用 UI 组件。快照测试可以等整个 Page 或者 UI 组件构建完成之后再添加,以保证稳定。E2E 测试覆盖核心的业务 flow。一个好的单元测试应该具备的条件?安全重构已有代码单元测试一个很重要的价值是为重构保驾护航。当输入不变时,当且仅当「被测业务代码功能被改动了」时,测试才应该挂掉。也就是说,无论怎么重构,测试都不应该挂掉。在写组件测试时,我们常常遇到这样的情况:用 css class 选择器选中一个节点,然后对它进行断言,那么即使业务逻辑没有发生变化,重命名这个 class 时也会使测试挂掉。理论上来说,这样的测试并不算一个「好的测试」,但是考虑到它的业务价值,我们还是会写一些这样的测试,只不过写测试的时候需要注意:使用一些不容易发生变化的选择器,比如 component name、arial-label 等。保存业务上下文我们经常说测试即文档,没错,一个好的测试往往能够非常清晰的表单业务或代码的含义。快速回归快速回归是指测试运行速度快,且稳定。要想运行速度快,很重要的一点是 mock 好外部依赖。至于怎么具体怎么 mock 外部依赖,后面会详细说明。单元测试怎么写?定义测试名称建议采用 BDD 的方式,即测试要接近自然语言,方便团队中的各个成员进行阅读。编写测试用例的时候,可以参考 AC,试着将 AC 的 Give-When-Then 转化成测试用例。GIVEN: 准备测试条件,比如渲染组件。WHEN:在某个具体的场景下,比如点击 button。THEN:断言describe(“add user”, () => { it(“when I tap add user button, expected dialog opened with 3 form fields”, () => { // Given: in profile page. // Prepare test env, like render component etc. // When: button click. // Simulate button click // Then: display add user form, which contains username, age and phone number. // Assert form fields length to equal 3 });});Mock 外部依赖单元测试的一个重要原则就是无依赖和隔离。也就是说,在测试某部分代码时,我们不期望它受到其他代码的影响。如果受到外部因素影响,测试就会变得非常复杂且不稳定。我们写单元测试时,遇到的最大问题就是:代码过于复杂。比如当页面有 API 请求、日期、定时器或 redux conent 时,写测试就变得异常困难,因为我们需要花大量时间去隔离这些外部依赖。隔离外部依赖需要用到测试替代方法,常见的有 spies、stubs 和 mocks。很多测试框架都实现了这三种方法,比如著名的 Jest 和 Sinon。这些方法可以帮助我们在测试中替换代码,减少测试编写的复杂度。spiesspies 本质上是一个函数,它可以记录目标函数的调用信息,如调用次数、传参、返回值等等,但不会改变原始函数的行为。Jest 中的 mock function 就是 spies,比如我们常用的 jest.fn() 。// Example:onSubmit() { // some other logic here this.props.dispatch(“xxx_action”);}// Example Test:it(“when form submit, expected dispatch function to be called”, () => { const mockDispatch = jest.fn(); mount(<SomeComp dispatch={mockDispatch}/>); // simlate submit event here expect(mockDispatch).toBeCalledWith(“xxx_action”); expect(mockDispatch).toBeCalledTimes(1);});spies 还可以用于替换属性方法、静态方法和原型链方法。由于这种修改会改变原始对象,使用之后必须调用 restore 方法予以还原,因此使用的时候要特别小心。// Example:const video = { play() { return true; },};// Example Test:test(‘plays video’, () => { const spy = jest.spyOn(video, ‘play’); const isPlaying = video.play(); expect(spy).toHaveBeenCalled(); expect(isPlaying).toBe(true); spy.mockRestore();});stubsstubs 跟 spies 类似,但与 spies 不同的是,stubs 会替换目标函数。也就是说,如果使用 spies,原始的函数依然会被调用,但使用 stubs,原始的函数就不会被执行了。stubs 能够保证明确的测试边界。它可以用于以下场景:替换让测试变得复杂或慢的外部函数,如 ajax。测试异常条件,如抛出异常。Jest 中也提供了类似的 API [](https://jestjs.io/docs/en/jes...[]()jest.spyOn().mockImplementation(),如下:const spy = jest.fn();const payload = [1, 2, 3];jest .spyOn(jQuery, “ajax”) .mockImplementation(({ success }) => success(payload));jQuery.ajax({ url: “https://example.api”, success: data => spy(data)});expect(spy).toHaveBeenCalledTimes(1);expect(spy).toHaveBeenCalledWith(payload);mocksmocks 是指用自定义对象代替目标对象。我们不仅可以 mock API 返回值和自定义类,还可以 mock npm 模块等等。// mock middleware apiconst mockMiddlewareAPI = { dispatch: jest.fn(), getState: jest.fn(),};// mock npm module configjest.mock(“config”, () => { return { API_BASE_URL: “http://base_url”, };});使用 mocks 时,需要注意:如果 mock 了某个模块的依赖,需要等 mock 完成了之后再 require 这个模块。有如下代码:// counter.tslet count = 0;export const get = () => count;export const inc = () => count++;export const dec = () => count–;错误做法:// counter.test.tsimport * as counter from “../counter”;describe(“counter”, () => { it(“get”, () => { jest.mock("../counter", () => ({ get: () => “mock count”, })); expect(counter.get()).toEqual(“mock count”); // 测试失败,此时的 counter 模块并非 mock 之后的模块。 });});正确做法:describe(“counter”, () => { it(“get”, () => { jest.mock("../counter", () => ({ get: () => “mock count”, })); const counter = require("../counter"); // 这里的 counter 是 mock 之后的 counter expect(counter.get()).toEqual(“mock count”); // 测试成功 });});多个测试有共享状态时,每次测试完成之后需要重置模块 jest.resetModules() 。它会清空所有 required 模块的缓存,保证模块之间的隔离。错误的做法:describe(“counter”, () => { it(“inc”, () => { const counter = require("../counter"); counter.inc(); expect(counter.get()).toEqual(1); }); it(“get”, () => { const counter = require("../counter"); // 这里的 counter 和上一个测试中的 counter 是同一份拷贝 expect(counter.get()).toEqual(0); // 测试失败 console.log(counter.get()); // ? 输出: 1 });});正确的做法:describe(“counter”, () => { afterEach(() => { jest.resetModules(); // 清空 required modules 的缓存 }); it(“inc”, () => { const counter = require("../counter"); counter.inc(); expect(counter.get()).toEqual(1); }); it(“get”, () => { const counter = require("../counter"); // 这里的 counter 和上一个测试中的 counter 是不同的拷贝 expect(counter.get()).toEqual(0); // 测试成功 console.log(counter.get()); // ? 输出: 0 });});修改代码,从一个外部模块 defaultCount 中获取 count 的默认值。// defaultCount.tsexport const defaultCount = 0;// counter.tsimport {defaultCount} from “./defaultCount”;let count = defaultCount;export const inc = () => count++;export const dec = () => count–;export const get = () => count;测试代码:import * as counter from “../counter”; // 首次导入 counter 模块console.log(counter); describe(“counter”, () => { it(“inc”, () => { jest.mock("../defaultCount", () => ({ defaultCount: 10, })); const counter1 = require("../counter"); // 再次导入 counter 模块 counter1.inc(); expect(counter1.get()).toEqual(11); // 测试失败 console.log(counter1.get()); // 输出: 1 });});再次 require counter 时,发现模块已经被 require 过了,就直接从缓存中获取,所以 counter1 使用的还是counter 的上下文,也就是 defaultCount = 0。而调用 resetModules() 会清空 cache,重新调用模块函数。在上面的代码中,注释掉 1,2 行,测试也会成功。大家可以想想为什么?编写测试组件测试渲染组件要对组件进行测试,首先要将组件渲染出来。Enzyme 提供了三种渲染方式: 浅渲染、全渲染以及静态渲染。浅渲染(Shallow Render)shallow 方法会把组件渲染成 Virtual DOM 对象,只会渲染组件中的第一层,不会渲染它的子组件,因此不需要关心 DOM 和执行环境,测试的运行速度很快。浅渲染对上层组件非常有用。上层组件往往包含很多子组件(比如 App 或 Page 组件),如果将它的子组件全部渲染出来,就意味着上层组件的测试要依赖于子组件的行为,这样不仅使测试变得更加困难,也大大降低了效率,不符合单元测试的原则。浅渲染也有天生的缺点,因为它只能渲染一级节点。如果要测试子节点,又不想全渲染怎么办呢?shallow 还提供了一个很好用的接口 .dive,通过它可以获取 wrapper 子节点的 React DOM 结构。示例代码:export const Demo = () => ( <CompA> <Container><List /></Container> </CompA>);使用 shallow 后得到如下结构:<CompA> <Container /></CompA>使用 .dive() 后得到如下结构:<div> <Container> <List /> </Container></div>全渲染(Full DOM Render)mount 方法会把组件渲染成真实的 DOM 节点。如果你的测试依赖于真实的 DOM 节点或者子组件,那就必须使用 mount 方法。特别是大量使用 Child Render 的组件,很多时候测试会依赖 Child Render 里面的内容,因此需要需要用全渲染,将子组件也渲染出来。全渲染方式需要浏览器环境,不过 Jest 已经提供了,它的默认的运行环境 jsdom ,就是一个 JavaScript 浏览器环境。需要注意的是,如果多个测试依赖了同一个 DOM,它们可能会相互影响,因此在每个测试结束之后,最好使用 .unmount() 进行清理。静态渲染(Static Render)将组件渲染成静态的 HTML 字符串,然后使用 Cheerio 对其进行解析,返回一个 Cheerio 实例对象,可以用来分析组件的 HTML 结构。测试条件渲染我们常常会用到条件渲染,也就是在满足不同条件时,渲染不同组件。比如: import React, { ReactNode } from “react”;const Container = ({ children }: { children: ReactNode }) => <div aria-label=“container”>{children}</div>;const CompA = ({ children }: { children: ReactNode }) => <div>{children}</div>;const List = () => <div>List Component</div>;interface IDemoListProps { list: string[];}export const DemoList = ({ list }: IDemoListProps) => ( <CompA> <Container>{list.length > 0 ? <List /> : null}</Container> </CompA>);对于条件渲染,这里提供了两种思路:测试是否渲染了正确节点一般的做法是将 DemoList 组件渲染出来,再根据不同的条件,去检查是否渲染出了正确的节点。describe(“DemoList”, () => { it(“when list length is more than 0, expected to render List component”, () => { const wrapper = shallow(<DemoList list={[“A”, “B”, “C”]} />); expect( wrapper .dive() .find(“List”) .exists(), ).toBe(true); }); it(“when list length is more than 0, expected to render null”, () => { const wrapper = shallow(<DemoList list={[]} />); expect( wrapper .dive() .find("[aria-label=‘container’]") .children().length, ).toBe(0); });});公用组件 + 只测判断条件我们可以抽象一个公用组件 <Show/> ,用于所有条件渲染的组件。这个组件接受一个 condition ,当满足这个 condition 时显示某个节点,不满足时显示另一个节点。<Show condition={} ifNode={} elseNode={} />我们可以为这个组件添加测试,确保在不同的条件下显示正确的节点。既然这个逻辑得已经得到了保证,使用 <Show/> 组件的地方就无需再次验证。因此我们只需要测试是否正确生成了 condition 即可。export const shouldShowBtn = (a: string, b: string, c: string) => a === b || b === c;describe(“should show button or not”, () => { it(“should show button”, () => { expect(shouldShowBtn(“x”, “x”, “x”)).toBe(true); }); it(“should hide button”, () => { expect(shouldShowBtn(“x”, “y”, “z”)).toBe(false); });});对于有权限控制的组件,一个小的配置改变也会导致整个渲染的不同,而且人工测试很难发现,这种配置多一个 prop 检查会让代码更加安全。测试用户交互常见的有点击事件、表单提交、validate 等。点击事件 click。onSubmit 。主要是测试 onSubmit 方法被调用之后是否发生了正确的行为,如 dispatch action 。validate 。 主要是测试 error message 是否按正确的顺序显示。Action Creator 测试action creator 的实现和测试都非常简单,这里就不举例了。但要注意的是,不要将计算逻辑放到 aciton creator 中。错误的方式:// action.tsexport const getList = createAction("@@list/getList", (reqParams: any) => { const params = formatReqParams({ …reqParams, page: reqParams.page + 1, startDate: formatStartDate(reqParams.startDate) endDate: formatStartDate(reqParams.endDate) }); return { url: “/api/list”, method: “GET”, params, };});正确的方式:// action.tsexport const getList = createAction("@@list/getList", (params: any) => { return { url: “/api/list”, method: “GET”, params, };});// 调用 action creator 时,先把值计算好,再传给 action creator。// utils.tsconst formatReqParams = (reqParams: any) => {return formatReqParams({ …reqParams, page: reqParams.page + 1, startDate: formatStartDate(reqParams.startDate) endDate: formatStartDate(reqParams.endDate) });};// page.tsgetFeedbackList(formatReqParams({}));Reducer 测试Reducer 测试主要是测试「根据 Action 和 State 是否生成了正确的 State」。因为 reducer 是纯函数,所以测试非常好写,这里就不细讲了。 Middleware 测试测试 middleware 最重要的就是 mock 外部依赖,其中包括 middlewareAPI 和 next 。Test Helper:class MiddlewareTestHelper { static of(middleware: any) { return new MiddlewareTestHelper(middleware); } constructor(private middleware: Middleware) {} create() { const middlewareAPI = { dispatch: jest.fn(), getState: jest.fn(), }; const next = jest.fn(); const invoke$ = (action: any) => this.middleware(middlewareAPI)(next)(action); return { middlewareAPI, next, invoke$, }; }}Example Test:it(“should handle the action”, () => { const { next, invoke$ } = MiddlewareTestHelper.of(testMiddleware()).create(); invoke$({ type: “SOME_ACTION”, payload: {}, }); expect(next).toBeCalled();});测试异步代码默认情况下,一旦到达运行上下文底部,jest测试立即结束。为了解决这个问题,我们可以使用:done() 回调函数return promiseasync/await错误的方式:test(’the data is peanut butter’, () => { function callback(data) { expect(data).toBe(‘peanut butter’); } fetchData(callback);});正确的方式:test(’the data is peanut butter’, done => { function callback(data) { expect(data).toBe(‘peanut butter’); done(); } fetchData(callback);});test(’the data is peanut butter’, () => { expect.assertions(1); return fetchData().then(data => { expect(data).toBe(‘peanut butter’); });});test(“the data is peanut butter”, async () => { const data = await fetchData(); expect(data).toBe(“peanut butter”);});执行测试采用「红 - 绿」的方式,即先让测试失败,再修改代码让测试通过,以确保断言被执行。快照测试怎么写?通过 redux-mock-store,将组件需要的全部数据准备好(给 mock store 准备 state),再进行测试。从测试的角度反思应用设计「好测试」的前提是要有「好代码」。因此我们可以从测试的角度去反思整个应用的设计,让组件的「可测试性」更高。单一职责。 一个组件只干一类事情,降低复杂度。只要每个小的部分能够被正确验证,组合起来能够完成整体功能,那么测试的时候,只需要专注于各个小的部分即可。良好的复用。 即复用逻辑的同时,也复用了测试。保证最小可用,再逐渐增加功能。 也就是我们平时所说的 TDD。…Debugconsole.log(wrapper.debug());参考文章 译-Sinon入门:利用Mocks,Spies和Stubs完成javascript测试使用Jest进行React单元测试对 React 组件进行单元测试How to Rethink Your Testing使用Enzyme测试React(Native)组件Node.js模块化机制原理探究单元测试的意义、做法、经验React 单元测试策略及落地 ...

January 30, 2019 · 6 min · jiezi

鲜为人知的混沌工程,到底哪里好?

阿里妹导读:混沌工程属于一门新兴的技术学科,行业认知和实践积累比较少,大多数IT团队对它的理解还没有上升到一个领域概念。阿里电商域在2010年左右开始尝试故障注入测试的工作,希望解决微服务架构带来的强弱依赖问题。通过本文,你将了解到:为什么需要混沌工程,阿里巴巴在该领域的实践和思考、未来的计划。一、为什么需要混沌工程?(翻译自Chaos Engineering电子书)1.1 混沌工程与故障测试的区别混沌工程是在分布式系统上进行实验的学科, 目的是建立对系统抵御生产环境中失控条件的能力以及信心,最早由Netflix及相关团队提出。故障演练是阿里巴巴在混沌工程领域的产品,目标是沉淀通用的故障模式,以可控成本在线上重放,以持续性的演练和回归方式运营来暴露问题,不断推动系统、工具、流程、人员能力的不断前进。混沌工程、故障注入和故障测试在关注点和工具中都有很大的重叠。混沌工程和其他方法之间的主要区别在于,混沌工程是一种生成新信息的实践,而故障注入是测试一种情况的一种特定方法。当想要探索复杂系统可能出现的不良行为时,注入通信延迟和错误等失败是一种很好的方法。但是我们也想探索诸如流量激增,激烈竞争,拜占庭式失败,以及消息的计划外或不常见的组合。如果一个面向消费者的网站突然因为流量激增而导致更多收入,我们很难称之为错误或失败,但我们仍然对探索系统的影响非常感兴趣。同样,故障测试以某种预想的方式破坏系统,但没有探索更多可能发生的奇怪场景,那么不可预测的事情就可能发生。测试和实验之间可以有一个重要的区别。在测试中,进行断言:给定特定条件,系统将发出特定输出。测试通常是二进制态的,并确定属性是真还是假。严格地说,这不会产生关于系统的新知识,它只是将效价分配给它的已知属性。实验产生新知识,并经常提出新的探索途径。我们认为混沌工程是一种实验形式,可以产生关于系统的新知识。它不仅仅是一种测试已知属性的方法,可以通过集成测试更轻松地进行验证。混沌实验的输入示例:模拟整个区域或数据中心的故障。部分删除各种实例上的Kafka主题。重新创建生产中发生的问题。针对特定百分比的交易服务之间注入一段预期的访问延迟。基于函数的混乱(运行时注入):随机导致抛出异常的函数。代码插入:向目标程序添加指令和允许在某些指令之前进行故障注入。时间旅行:强制系统时钟彼此不同步。在模拟I/O错误的驱动程序代码中执行例程。在 Elasticsearch 集群上最大化CPU核心。混沌工程实验的机会是无限的,可能会根据分布式系统的架构和组织的核心业务价值而有所不同。1.2 实施混沌工程的先决条件要确定是否已准备好开始采用混沌工程,需要回答一个问题:你的系统是否能够适应现实世界中的事件,例如服务故障和网络延迟峰值?如果答案是“否”,那么你还有一些工作要做。混沌工程非常适合揭露生产系统中未知的弱点,但如果确定混沌工程实验会导致系统出现严重问题,那么运行该实验就没有任何意义。先解决这个弱点,然后回到混沌工程,它将发现你不了解的其他弱点,或者它会让你发现你的系统实际上是有弹性的。混沌工程的另一个基本要素是可用于确定系统当前状态的监控系统。1.3 混沌工程原则为了具体地解决分布式系统在规模上的不确定性,可以把混沌工程看作是为了揭示系统弱点而进行的实验。破坏稳态的难度越大,我们对系统行为的信心就越强。如果发现了一个弱点,那么我们就有了一个改进目标。避免在系统规模化之后问题被放大。以下原则描述了应用混沌工程的理想方式,这些原则来实施实验过程。对这些原则的匹配程度能够增强我们在大规模分布式系统的信心。二、阿里巴巴在混沌工程领域的实践:故障演练混沌工程属于一门新兴的技术学科,行业认知和实践积累比较少,大多数IT团队对它的理解还没有上升到一个领域概念。阿里电商域在2010年左右开始尝试故障注入测试的工作,开始的目标是想解决微服务架构带来的强弱依赖问题。后来经过多个阶段的改进,最终演进到 MonkeyKing(线上故障演练平台)。从发展轨迹来看,阿里的技术演进和Netflix的技术演进基本是同时间线的,每个阶段方案的诞生都有其独特的时代背景和业务难点,也可以看到当时技术的局限性和突破。2.1 建立一个围绕稳定状态行为的假说目前阿里巴巴集团范围内的实践偏向于故障测试,即在一个具体场景下实施故障注入实验并验证预期是否得到满足。这种测试的风险相对可控,坏处是并没有通过故障注入实验探索更多的场景,暴露更多的潜在问题,测试结果比较依赖实施人的经验。当前故障测试的预期比较两级分化,要么过于关注系统的内部细节,要么对于系统的表现完全没有预期,与混沌工程定义的稳态状态行为差异比较大。引起差异的根本原因还是组织形态的不同。2014年,Netflix团队创建了一种新的角色,叫作混沌工程师(Chaos Enigneer),并开始向工程社区推广。而阿里目前并没有一个专门的职位来实施混沌工程,项目目标、业务场景、人员结构、实施方式的不同导致了对于稳定状态行为的定义不太标准。2.2 多样化真实世界的事件阿里巴巴因为多元化的业务场景、规模化的服务节点及高度复杂的系统架构,每天都会遇到各式各样的故障。这些故障信息就是最真实的混沌工程变量。为了能够更体感、有效率地描述故障,我们优先分析了P1和P2的故障(P是阿里对故障等级的描述),提出一些通用的故障场景并按照IaaS层、PaaS层、SaaS层的角度绘制了故障画像。从故障的完备性角度来看,上述画像只能粗略代表部分已出现的问题,对于未来可能会出现的新问题也需要一种手段保持兼容。在更深入的进行分析之后,我们定义了另一维度的故障画像:任何故障,一定是硬件如IaaS层,软件如PaaS或SaaS的故障。并且有个规律,硬件故障的现象,一定可以在软件故障现象上有所体现。故障一定隶属于单机或是分布式系统之一,分布式故障包含单机故障。对于单机或同机型的故障,以系统为视角,故障可能是当前进程内的故障,比如:如FullGC,CPU飙高;进程外的故障,比如其他进程突然抢占了内存,导致当前系统异常等。同时,还可能有一类故障,是人为失误,或流程失当导致,这部分我们今天不做重点讨论。从故障注入实现角度,我们也是参照上述的画像来设计的。之前我们是通过Java字节码技术和操作系统层面的工具来分别模拟进程内和进程外的故障。随着Serverless、Docker等新架构、新技术的出现,故障实现机制和承接载体也将会有一些新的变化。2.3 在生产环境中运行实验从功能性的故障测试角度来看,非生产环境去实施故障注入是可以满足预期的,所以最早的强弱依赖测试就是在日常环境中完成的。不过,因为系统行为会根据环境和流量模式有所不同,为了保证系统执行方式的真实性与当前部署系统的相关性,推荐的实施方式还是在生产环境(仿真环境、沙箱环境都不是最好的选择)。很多同学恐惧在生产环境执行实验,原因还是担心故障影响不可控。实施实验只是手段,通过实验对系统建立信心是我们的目标。关于如何减少实验带来的影响,这点在"最小化爆炸半径"部分会有阐述。2.4 持续自动化运行实验2014年,线下环境的强弱依赖测试用例是默认在每次发布后自动执行的。2015年,开始尝试在线上进行自动化回归。不过发展到最近两年,手动实验的比例逐渐变高。原因也不复杂,虽然故障注入自动化了,业务验证的成本仍然比较高。在业务高速发展、人员变化较快的环境之下,保持一套相对完善的线上回归用例集对是见非常难的事情。虽然也出现了流量录制技术,不过因为混沌工程实验本身会打破系统已有的行为,基于入口和出口的流量比对的参考度就下降许多。为了解决测试成本问题,2017年初开始推进线上微灰度环境的建设。基于业务、比例来筛选特征流量,通过真实的流量来替换原来的测试流量,通过监控&报警数据来替代测试用例结果。目前已经有部分业务基于微灰度+故障演练的模式来做演练验证(比如:盒马APOS容灾演习)。因为故障演练之前是作为一个技术组件被嵌入到常态和大促的流程中,所以在系统构建自动化的编排和分析方面的产品度并不高。演练可视化编排和能力开放会是我们团队未来的一个重点,下文中的规划部分会有所阐述。2.5 最小化爆炸半径在生产中进行试验可能会造成不必要的客户投诉,但混沌工程师的责任和义务是确保这些后续影响最小化且被考虑到。对于实验方案和目标进行充分的讨论是减少用户影响的最重要的手段。但是从实际的实施角度看,最好还是通过一些技术手段去最小化影响。Chaos Engineering和Fault Injection Test的核心区别在于:是否可以进一步减小故障的影响,比如微服务级别、请求级别甚至是用户级别。在MonkeyKing演进的中期阶段,已经可以实现请求级别的微服务故障注入。虽然那个时候演练实施的主要位置在测试环境,但初衷也是为了减少因为注入故障而导致的环境不稳定问题。除了故障注入,流量路由和数据隔离技术也是减少业务影响的有效手段。三、未来的计划线上故障演练发展到今天是第三年,随着阿里安全生产的大环境、业务方的诉求、研发迭代模式的变化,以及大家对混沌工程的接受和认识程度的提高。集团的演练领域会向着未来的几个目标发力:建立高可用专家库,结构化提高应用容错能力(解决"稳定状态定义"的问题)建设故障注入实现标准,集团内开源,提升故障模拟的广度和深度(拓宽"多样化真实世界的事件"的广度)规模化覆盖核心业务(提升"在生产环境中运行实验"的规模)以产品化、平台化思路开放演练能力(探索"自动化运行实验"的方式)四、触手可及的混沌工程MonkeyKing已经提供商业化产品,欢迎在阿里云官网搜索“AHAS”,进行免费公测。地址:https://www.aliyun.com/product/ahas本文作者:中亭阅读原文本文来自云栖社区合作伙伴“阿里技术”,如需转载请联系原作者。

January 29, 2019 · 1 min · jiezi

MTSC2018 测试开发大会视频公开(含 PPT)| 年度福利

过去的几年中,软件测试与质量保障行业悄然间发生了很大变化,TesterHome 社区作为行业见证人,通过 MTSC 大会记录了测试行业技术趋势与人才结构的变革历程。2018 年,MTSC2018 大会共邀请了 50 多位测试行业国内外顶级专家,与来自近 500 家业界领先企业的 1600 余位测试开发工程师、测试架构师与质量管理者交流分享了一线企业软件质量保障体系建设经验、测试研发新技术与最佳实践案例,助力企业技术选型、工程效率提升和业务升级转型。现在,MTSC 组委会决定公开 MTSC2018 测试开发大会视频(含 PPT 资料),作为献给 TesterHome 社区用户和测试从业人员的年度大礼,期待推进行业进步。MTSC2018 大会视频公开《腾讯手游自动化测试方案和 AI 实践》 by 蔡怡峰,腾讯 IEG 品质管理部测试开发负责人 & 李德元,腾讯 IEG 品质管理部高级测试开发工程师《基于分层快速反馈的金融 App 质量防护方案及实践》by 王小丽,TestBird 测试架构师《大疆互联网的一站式自动化测试解决方案(基于HttpRunner)》 by 李隆,大疆高级测试开发工程师《2 个人如何保证 2 亿用户 App 的质量》by 唐巧,小猿搜题产品技术负责人《ebay 自动化测试基础架构的最佳实践》 by 茹炳晟,eBay 中国研发中心测试基础架构技术主管《AirtestProject-次世代UI自动化测试解决方案》by 李翔,网易游戏测试专家《大船好调头京东测试团队从业务到技术转型实践》by 贾瑞卿,京东高级测试经理《基于用户视角的Android页面性能评测体系》by 林紫嫣,优酷 Youku 高级测试专家《玩转58场景下的自动化测试》by 吴明浩,58 集团测试架构师《探测小程序的机密》by 甄晓龙,转转高级测试工程师《千万级支付系统稳定性测试实战》by 范勋伟,美团点评智能支付测试团队负责人《大众点评APP:Mock自动化的专项应用》by 李洁,美团点评资深测试工程师《以高效共享、复用、传承为目标的测试用例管理思路&工具实践》by 顾利萍,360 QTest 团队测试主管《基于AI的APP自动化测试》by 胡平,华为资深技术专家《性能解析:互联网银行项目实践和两则实际性能定位案例》by 高楼,北京千倍科技有限公司,7D Group 创始人《汽车之家新车电商精准测试解决方案》by 闻小龙,汽车之家高级测试开发工程师大会视频及 PPT 获取方式视频资料:识别上图二维码或者访问 TesterHome 社区https://testerhome.com/topics…大会 PPT: 请访问百度网盘链接: https://pan.baidu.com/s/1ca9r… ,提取码为【apvv】。MTSC2019 启动,热点议题征集中MTSC2019 大会购票地址:https://www.bagevent.com/even…MTSC2019 大会议题申请:topic@testerhome.comMTSC2019 大会商务赞助:bd@testerhome.com关于 MTSC 大会MTSC 中国移动互联网测试开发大会(Mobile Testing Summit China)是由国内最大的测试开发技术社区 TesterHome 主办的年度技术盛会,聚焦于软件测试及应用质量保障,邀请国内外顶级技术专家分享测试行业最前沿技术创新发展、业界工程最佳实践经验等。自 2015 年举办以来,近万名有从业经验的测试开发工程师、测试经理和质量管理人员参加了 MTSC 大会,好评如潮。MTSC2019 大会议题涵盖移动端测试、服务端测试、Web 测试、IoT 智能硬件、持续集成/持续交付、DevOps 等领域,以及大数据、人工智能 AI 技术在测试领域的应用。MTSC 大会从专家邀请到内容评审都严格把关,力求务实、能落地、有深度、高质量。很多一线互联网企业如腾讯、阿里、百度、美团、华为、小米、360 等公司都专门组织研讨会来学习分享 MTSC 大会内容。2019 测试行业技术栈有哪些创新发展?如何更好的落地自动化测试技术,提升质量管理效率?人工智能 AI 与测试技术结合会产生怎样的火花?物联网测试体系搭建有哪些注意事项?在经济寒冬和行业人才结构大变革的双重背景下,测试从业人员面临怎样的职业技能挑战?… 期待与你相约 MTSC2019 大会现场(6月28-29·北京国际会议中心),共同探讨精彩议题!更多信息,访问 MTSC2019 大会官网地址:https://testerhome.com/mtsc/2019 。 ...

January 27, 2019 · 1 min · jiezi

在阿里,我们如何管理测试环境

前言阿里的许多实践看似简单,背后却蕴涵着许多思考,譬如测试环境的管理。互联网产品的服务通常是由Web应用、中间件、数据库和许多后台业务程序组成的,一套运行环境就是一个自成一体的小生态。最基本的运行环境是线上环境,部署产品的正式发布版本,为用户提供持续可靠的服务。除此以外,还有许多不对外部用户开放的运行环境,用于产品团队日常的开发和验证,统称为测试环境。正式环境的稳定性,除去软件自身的质量因素,主要与运行的主机、网络等基础设施相关,而测试环境的稳定性则更多受到人为因素影响。由于频繁的版本变更,以及部署未经充分验证的代码,测试环境出故障的情况屡见不鲜。良好的代码提交习惯、适当的变更前检查有助于减少故障的发生,但无法彻底杜绝后患。增加多套测试环境副本能够有效控制故障的影响范围,然而企业的资源终归有限,降低测试环境成本和提高测试环境稳定性成为了矛盾的两面。在这个领域里,独具匠心的阿里研发效能团队设计了一种服务级复用的虚拟化技术,称为“特性环境”,其巧妙的思路令人赞叹。本文将围绕测试环境管理的话题,聊聊这种具有阿里特色的工作方式。测试环境管理的困局测试环境的用途很广泛,常见的测试环境譬如系统集成测试环境、用户验收测试环境、预发布测试环境、灰度测试环境等,它们体现了产品的交付生命周期,也间接反映出整个团队的组织结构。小作坊型产品团队的测试环境管理起来十分简单,每个工程师本地就能启动全套软件组件进行调试,倘若不放心,再加上一个公共的集成测试环境也就足够。随着产品规模扩大,本地启动所有服务组件逐渐变得既费时又费事,工程师们只能在本地运行一部分待调试的组件,然后利用公共测试环境上的其余组件组成完整系统。与此同时,团队规模的扩张,使得每个团队成员的职责进一步细分,新的子团队被划分出来,这意味着项目的沟通成本增加,公共测试环境的稳定性开始变得难以控制。在这个过程中,测试环境管理复杂性带来的影响,不仅体现在服务联调变得繁琐,更直接反映在交付流程和资源成本的变化上。在交付流程方面,一个显著的变化是测试环境种类增多。出于不同的用途和目的,工程师们设计出了各式各样的专用测试环境。这些测试环境的组合形成了各个企业独具特色的交付流程。下图展示了一种用于大型项目的复杂交付流程。从单独服务的角度来看,环境与环境之间是由流水线相连的,再加上自动化测试或手工审批操作组成关卡,实现环境之间的传递。通常越高级别环境的部署频率越低,因此相对稳定性也越高。与之相反,在级别较低的环境上,就随时可能存在新的部署,会打扰正在使用该环境的其他人。有时为了复现某些特殊的问题场景,一些开发者不得不直接登录到服务器上面去“搞事情”,进一步影响环境的稳定性和可用性。面对随时可能崩溃的测试环境,小企业会试着去“堵”:约束服务变更时间、设立严格的变更规范,大企业则善于用“疏”:增加测试环境副本,隔离故障影响范围。显然,不堪重负的测试环境一定越“堵”越“漏”,千年以前大禹治水的故事早就揭示了的道理,刻意的管控拯救不了脆弱的测试环境。近年来,DevOps文化的兴起,端到端解放了开发者的双手,这对于测试环境的管理而言却是一把双刃剑。一方面,DevOps鼓励开发人员参与运维,了解产品的完整生命周期,有助于减少不必要的低级运维事故;另一方面,DevOps让更多的手伸向测试环境,更多的变更、更多的Hotfix出现了。这些实践从全局来看利大于弊,然而并不能缓解测试环境的动荡。单纯的流程疏通同样拯救不了脆弱的测试环境。那么该投入的还得投入。将不同团队所用的低级别测试环境各自独立,此时每个团队看到的都是线性流水线,从整体上观察,则会程现出河流汇聚的形状。由此推广,理想情况下,每位开发者都应该得到独占且稳定的测试环境,各自不受干扰的完成工作。然而由于成本因素,现实中在团队内往往只能共享有限的测试资源,不同成员在测试环境相互干扰成为影响软件开发质量的隐患。增加测试环境副本数本质上是一种提高成本换取效率的方法,然而许多试图在成本和效率之间寻找最优平衡的探索者们,似乎都在同一条不归路上越行越远。由于客观的规模和体量,上述这些测试环境管理的麻烦事儿,阿里的产品团队都无法幸免。首先是测试环境种类的管理。在阿里内部,同样有十分丰富的测试环境区分。各种测试环境的命名与其作用息息相关,虽然业界有些常用的名称,但都未形成权威的标准。实际上,环境的名称只是一种形式,关键还在于各种测试环境应当分别适配于特定应用场景,且场景之间应当或多或少存在一些差异。这种差异有些在于运行的服务种类,譬如性能测试环境很可能只需要运行与压力测试相关的那部分访问量最大的关键业务服务,其他服务运行了也是浪费资源。有些差异在于接入数据的来源,譬如开发自测的环境的数据源与正式环境肯定不一样,这样测试使用的假数据就不会污染线上用户的请求;预发布环境(或用户验收测试环境)会用与正式环境一致的数据源(或正式数据源的拷贝),以便反映新功能在真实数据上运行的情况;自动化测试相关的环境会有单独的一套测试数据库,以避测试运行过程中受到其他人为操作的干扰。还有些差异在于使用者的不同,譬如灰度和预发布环境都使用正式的数据源,但灰度环境的使用者是一小撮真实的外部用户,而预发布环境的使用者都是内部人员。总之,没必要为一个不存在业务特殊性的测试场景专门发明一种测试环境。在集团层面,阿里对流水线形式的约束相对宽松。客观的讲,只有在一线的开发团队知道最适合团队的交付流程应该是什么样子。阿里的开发平台只是规范了一些推荐的流水线模板,开发者可在此基础上进行发挥。列举几个典型的模板例子:这里出现了几种外界不太常见的环境类型名称,稍后会详细介绍。其次是测试环境成本的管理。成本管理的问题十分棘手且十分值得深究。与测试环境相关的成本主要包括管理环境所需的“人工成本”和购买基础设施所需的“资产成本”。通过自动化以及自服务化的工具可以有效降低人工相关的成本,自动化又是个很大的话题,宜另起一篇文章讨论,此处暂且收住。资产购买成本的降低依赖技术的改良和进步(排除规模化采购带来的价格变化因素),而基础设施技术的发展史包括两大领域:硬件和软件。硬件发展带来的成本大幅下降,通常来自于新的材料、新的生产工艺、以及新的硬件设计思路;软件发展带来的基础设施成本大幅下降,目前看来,大多来自于虚拟化(即资源隔离复用)技术的突破。最早的虚拟化技术是虚拟机,早在20世纪50年代,IBM就开始利用这种硬件级的虚拟化方法获得成倍的资源利用率提升。虚拟机上的不同隔离环境之间各自运行完整操作系统,具有很好的隔离性,通用性强,但对于运行业务服务的场景,显得略为笨重。2000年后,KVM、XEN等开源项目使得硬件级虚拟化广泛普及。与此同时,另一种更轻量的虚拟化技术出现了,以OpenVZ、LXC为代表的早期容器技术,实现了建立于操作系统内核之上的运行环境虚拟化,减少了独立操作系统的资源消耗,以牺牲一定隔离性为代价,获得更高的资源利用率。之后诞生的Docker以其镜像封装和单进程容器的理念,将这种内核级虚拟化技术推上百万人追捧的高度。阿里紧随技术前进的步伐,早早的就用上了虚拟机和容器,在2017年双十一时,在线业务服务的容器化比例已经达到100%。然而,接下来的挑战是,基础设施资源利用率还能做得更高吗?甩掉了虚拟机的硬件指令转换和操作系统开销,运行在容器中的程序与普通程序之间只有一层薄薄的内核Namespace隔离,完全没有运行时性能损耗,虚拟化在这个方向上似乎已经发展到了极限。唯一的可能是,抛开通用场景,专注到测试环境管理的特定场景上,继续寻找突破。终于,阿里在这个领域里发现了新的宝藏:服务级虚拟化。所谓服务级虚拟化,本质上是基于消息路由的控制,实现集群中部分服务的复用。在服务级虚拟化方式下,许多外表庞大的独立测试环境实际只需要消耗极小的额外基础设施资源,即使给每个开发者配备一套专用的测试环境集群都不再是吹牛。具体来说,在阿里的交付流程上,包含两种特殊类型的测试环境:“公共基础环境”和“特性环境”,它们形成了具有阿里特色的测试环境使用方法。公共基础环境是一个全套的服务运行环境,它通常运行一个相对稳定的服务版本,也有些团队将始终部署各服务的最新版本的低级别环境(称为“日常环境”)作为公共基础环境。特性环境是这套方法中最有意思的地方,它是虚拟的环境。从表面上看,每个特性环境都是一套独立完整的测试环境,由一系列服务组成集群,而实际上,除了个别当前使用者想要测试的服务,其余服务都是通过路由系统和消息中间件虚拟出来的,指向公共基础环境的相应服务。由于在阿里通常的开发流程中,开发任务需要经过特性分支、发布分支和诸多相关环节最后发布上线,大多数环境都从发布分支部署,唯独这种开发者自用的虚拟环境部署来自代码特性分支的版本,故可称为“特性环境”(阿里内部叫“项目环境”)。举个具体例子,某交易系统的完整部署需要由鉴权服务、交易服务、订单服务、结算服务等十几种小系统以及相应的数据库、缓存池、消息中间件等组成,那么它的公共基础环境就是这样一套具备所有服务和周边组件的完整环境。假设此时有两套特性环境在运行,一套只启动了交易服务,另一套启动了交易服务、订单服务和结算服务。对于第一套特性环境的使用者而言,虽然除交易服务外的所有服务实际上都由公共基础环境代理,但在使用时就像是自己独占一整套完整环境:可以随意部署和更新环境中交易服务的版本,并对它进行调试,不用担心会影响其他用户。对于第二套特性环境的使用者,则可以对部署在该环境中的三个服务进行联调和验证,倘若在场景中使用到了鉴权服务,则由公共基础环境的鉴权服务来响应。咋看起来,这不就是动态修改域名对应的路由地址、或者消息主题对应的投递地址么?实事并没那么简单,因为不能为了某个特性环境而修改公共基础环境的路由,所以单靠正统路由机制只能实现单向目标控制,即特性环境里的服务主动发起调用能够正确路由,若请求的发起方在公共基础环境上,就无法知道该将请求发给哪个特性环境了。对于HTTP类型的请求甚至很难处理回调的情况,当处于公共基础环境的服务进行回调时,域名解析会将目标指向公共基础环境上的同名服务。如何才能实现数据双向的正确路由和投递呢?不妨先回到这个问题的本质上来:请求应该进入哪个特性环境,是与请求的发起人相关的。因此实现双向绑定的关键在于,识别请求发起人所处的特性环境和进行端到端的路由控制。这个过程与“灰度发布”很有几分相似,可采用类似的思路解决。得益于阿里在中间件领域的技术积累,和鹰眼等路由追踪工具的广泛使用,识别请求发起人和追溯回调链路都不算难事。如此一来,路由控制也就水到渠成了。当使用特性环境时,用户需要“加入”到该环境,这个操作会将用户标识(如IP地址或用户ID)与指定的特性环境关联起来,每个用户只能同时属于一个特性环境。当数据请求经过路由中间件(消息队列、消息网关、HTTP网关等),一旦识别到请求的发起人当前处在特性环境中,就会尝试把请求路由给该环境中的服务,若该环境没有与目标一致的服务,才路由或投递到公共基础环境上。特性环境并不是孤立存在的,它可以建立在容器技术之上,从而获得更大的灵活性。正如将容器建立在虚拟机之上得到基础设施获取的便利性一样,在特性环境中,通过容器快速而动态的部署服务,意味着用户可以随时向特性环境中增加一个需要修改或调试的服务,也可以将环境中的某个服务随时销毁,让公共基础环境的自动接替它。还有一个问题是服务集群调试。配合AoneFlow的特性分支工作方式,倘若将几个服务的不同特性分支部署到同一个特性环境,就可以进行多特性的即时联调,从而将特性环境用于集成测试。不过,即使特性环境的创建成本很低,毕竟服务是部署在测试集群上的。这意味着每次修改代码都需要等待流水线的构建和部署,节约了空间开销,却没有缩短时间开销。为了进一步的降低成本、提高效率,阿里团队又捣鼓出了一种开脑洞的玩法:将本地开发机加入特性环境。在集团内部,由于开发机和测试环境都使用内网IP地址,稍加变通其实不难将特定的测试环境请求直接路由到开发机。这意味着,在特性环境的用户即使访问一个实际来自公共基础环境的服务,在后续处理链路上的一部分服务也可以来自特性环境,甚至来自本地环境。现在,调试集群中的服务变得非常简单,再也不用等待漫长的流水线构建,就像整个测试环境都运行在本地一样。DIY体验特性环境觉得服务级虚拟化太小众,离普通开发者很远?实事并非如此,我们现在就可以动手DIY个体验版的特性环境来玩。阿里的特性环境实现了包括HTTP调用、RPC调用、消息队列、消息通知等各类常用服务通信方式的双向路由服务级虚拟化。要完成这样的功能齐全的测试环境有点费劲,从通用性角度考虑,咱不妨从最符合大众口味的HTTP协议开始,做个支持单向路由的简易款。为了便于管理环境,最好得有一个能跑容器的集群,在开源社区里,功能齐全的Kubernetes是个不错的选择。在Kubernetes中有些与路由控制有关的概念,它们都以资源对象的形式展现给用户。简单介绍一下,Namespace对象能隔离服务的路由域(与容器隔离使用的内核Namespace不是一个东西,勿混淆),Service对象用来指定服务的路由目标和名称,Deployment对象对应真实部署的服务。类型是ClusterIP(以及NodePort和LoadBalancer类型,暂且忽略它们)的Service对象可路由相同Namespace内的一个真实服务,类型是ExternalName的Service对象则可作为外部服务在当前Namespace的路由代理。这些资源对象的管理都可以使用YAML格式的文件来描述,大致了解完这些,就可以开始动工了。基础设施和Kubernetes集群搭建的过程略过,下面直接进正题。先得准备路由兜底的公共基础环境,这是一个全量测试环境,包括被测系统里的所有服务和其他基础设施。暂不考虑对外访问,公共基础环境中的所有服务相应的Service对象都可以使用ClusterIP类型,假设它们对应的Namespace名称为pub-base-env。这样一来,Kubernetes会为此环境中的每个服务自动赋予Namespace内可用的域名“服务名.svc.cluster”和集群全局域名“服务名.pub-base-env.svc.cluster”。有了兜底的保障后,就可以开始创建特性环境了,最简单的特性环境可以只包含一个真实服务(例如trade-service),其余服务全部用ExternalName类型的Service对象代理到公共基础环境上。假设它使用名称为feature-env-1的Namespace,其描述的YAML如下(省略了非关键字段的信息):kind: Namespacemetadata:name: feature-env-1*kind: Servicemetadata:name: trade-servicenamespace: feature-env-1spec:type: ClusterIP…*kind: Deploymentmetadata:name: trade-servicenamespace: feature-env-1spec:…*kind: Servicemetadata:name: order-servicenamespace: feature-env-1spec:type: ExternalNameexternalName: order-service.pub-base-env.svc.cluster…*kind: Service…注意其中的order-service服务,它在当前特性环境Namespace中可以使用局部域名order-service.svc.cluster访问,请求会路由到它配置的全局域名order-service.pub-base-env.svc.cluster,即公共基础环境的同名服务上处理。处于该Namespace中的其它服务感知不到这个差异,而是会觉得这个Namespace中部署了所有相关的服务。若在特性的开发过程中,开发者对order-service服务也进行了修改,此时应该将修改过的服务版本添加到环境里来。只需修改order-service的Service对象属性(使用Kubernetes的patch操作),将其改为ClusterIP类型,同时在当前Namespace中创建一个Deployment对象与之关联即可。由于修改Service对象只对相应Namespace(即相应的特性环境)内的服务有效,无法影响从公共基础环境回调的请求,因此路由是单向的。在这种情况下,特性环境中必须包含待测调用链路的入口服务和包含回调操作的服务。例如待测的特性是由界面操作发起的,提供用户界面的服务就是入口服务。即使该服务没有修改,也应该在特性环境中部署它的主线版本。通过这种机制也不难实现把集群服务局部替换成本地服务进行调试开发的功能,倘若集群和本地主机都在内网,将ExternalName类型的Service对象指向本地的IP地址和服务端口就可以了。否则需要为本地服务增加公网路由,通过动态域名解析来实现。与此同时,云效也正在逐步完善基于Kubernetes的特性环境解决方案,届时将会提供更加全面的路由隔离支持。值得一提的是,由于公有云的特殊性,在联调时将本地主机加入云上集群是个必须克服的难题。为此云效实现了通过隧道网络+kube-proxy自身路由能力,将本地局域网主机(无需公网IP地址)加入到不在同一内网Kubernetes集群进行联调的方式。其中的技术细节也将在近期的云效公众号向大家揭晓,敬请留意。小结当许多人还在等待,在虚拟机和容器之后,下一轮虚拟化技术的风口何时到来的时候,阿里已经给出了一种答案。创业者的心态让阿里人懂得,能省必须省。其实,限制创新的往往不是技术而是想象力,服务级虚拟化的理念突破了人们对环境副本的传统认知,以独特的角度化解了测试环境成本与稳定性的矛盾。作为一种颇具特色的技术载体,特性环境的价值不仅仅在于轻量的测试环境管理体验,更在于为每位开发人员带来流畅的工作方式,实则是“简约而不简单”。实践出真知,阿里巴巴云效平台致力于解决大型项目协作、敏捷高速迭代、海量代码托管、高效测试工具、分布式秒级构建、大规模集群部署发布等世界级业务和技术难题,为阿里巴巴集团内部、生态伙伴以及云上开发者服务。诚挚欢迎业界同行与我们探讨交流。相关阅读:在阿里,我们如何管理代码分支当kubernetes应用遇到阿里分批发布模式本文作者:云效鼓励师阅读原文本文为云栖社区原创内容,未经允许不得转载。

January 25, 2019 · 1 min · jiezi

怎样针对JavaScript中的异步函数进行异常处理及测试

翻译:疯狂的技术宅原文:https://www.valentinog.com/bl…本文首发微信公众号:jingchengyideng欢迎关注,每天都给你推送新鲜的前端技术文章可以在 Javascript 的异步函数中抛出错误吗?这个话题已被反复提起过几百次,不过这次让我们从TDD(Test-Driven Development)的角度来回答它。如果你能不在Stackoverflow上搜索就能回答这个问题,会给我留下深刻的印象。如果不能的话也可以很酷。 继续往下读,你就能学到!你将学到什么通过后面的内容你将学到:如何从 Javascript 的异步函数中抛出错误如何使用 Jest 测试来自异步函数的异常要求要继续往下读你应该:对 Javascript 和 ES6 有基本的了解安装 Node.Js 和 Jest如何从 Javascript 的常规函数中抛出错误使用异常而不是返回码(清洁代码)。抛出错误是处理未知的最佳方法。同样的规则适用于各种现代语言:Java、Javascript、Python、Ruby。你可以从函数中抛出错误,可以参照以下示例:function upperCase(name) { if (typeof name !== “string”) { throw TypeError(“name must be a string”); } return name.toUpperCase();}module.exports = upperCase;这是对它的测试(使用Jest):“use strict”;const assert = require(“assert”);const upperCase = require("../function");describe(“upperCase function”, () => { test(“it throws when name is not provided”, () => { assert.throws(() => upperCase()); }); test(“it throws when name is not a string”, () => { assert.throws(() => upperCase(9)); });});也可以从 ES6 的类中抛出错误。在 Javascript 中编写类时,我总是在构造函数中输入意外值。下面是一个例子:class Person { constructor(name) { if (typeof name !== “string”) { throw TypeError(“name must be a string”); } this.name = name; } // some method here}module.exports = Person;以下是该类的测试:“use strict”;const assert = require(“assert”);const Person = require("../index");describe(“Person class”, () => { test(“it throws when name is not provided”, () => { assert.throws(() => new Person()); }); test(“it throws when name is not a string”, () => { assert.throws(() => new Person(9)); });});测试确实通过了:PASS test/index.test.js Person class ✓ it throws when name is not provided (1ms) ✓ it throws when name is not a string安排的明明白白!所以无论异常是从常规函数还是从类构造函数(或从方法)抛出的,一切都会按照预期工作。但是如果我想从异步函数中抛出错误怎么办?我可以在测试中使用assert.throws吗?各位看官请上眼!测试异常既然都看到这里了,所以你应该知道什么是 Javascript 的异步函数,对吗?先看一段代码:class Person { constructor(name) { if (typeof name !== “string”) { throw TypeError(“name must be a string”); } this.name = name; } // some method here}module.exports = Person;假设你要添加异步方法来获取有关该人的数据。这种方法需要一个网址。如果url不是字符串,就要像上一个例子中那样抛出错误。先来修改一下这个类:class Person { constructor(name) { if (typeof name !== “string”) { throw TypeError(“name must be a string”); } this.name = name; } async getData(url) { if (typeof url !== “string”) { throw TypeError(“url must be a string”); } // const response = await fetch(url) // do stuff }}module.exports = Person;如果我运行代码会怎么样?试试吧:const Person = require("../index");const valentinogagliardi = new Person(“valentinogagliardi”);valentinogagliardi.getData();结果是这样UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: name must be a stringDeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.果然不出所料,异步方法返回了一个Promise rejection,从严格意义上来讲,并没有抛出什么东西。错误被包含在了Promise rejection中。换句话说,我不能使用 assert.throws 来测试它。让我们通过测试来验证一下:“use strict”;const assert = require(“assert”);const Person = require("../index");describe(“Person methods”, () => { test(“it throws when url is not a string”, () => { const valentinogagliardi = new Person(“valentinogagliardi”); assert.throws(() => valentinogagliardi.getData()); });});测试失败了!FAIL test/index.test.js Person methods › it throws when url is not a string assert.throws(function) Expected the function to throw an error. But it didn’t throw anything. Message: Missing expected exception.有没有悟出点什么?看把你能的,来抓我啊从严格意义上讲异步函数和异步方法不会抛出错误。异步函数和异步方法总是返回一个Promise,无论它已完成还是被拒绝,你必须附上 then() 和 catch(),无论如何。(或者将方法包装在try/catch中)。被拒绝的Promise将会在堆栈中传播,除非你抓住(catch)它。至于测试代码,应该这样写:“use strict”;const assert = require(“assert”);const Person = require("../index");describe(“Person methods”, () => { test(“it rejects when url is not a string”, async () => { expect.assertions(1); const valentinogagliardi = new Person(“valentinogagliardi”); await expect(valentinogagliardi.getData()).rejects.toEqual( TypeError(“url must be a string”) ); });});我们测试的不能是普通的异常,而是带有TypeError的rejects。现在测试通过了:PASS test/index.test.js Person methods ✓ it rejects when url is not a string那代码该怎么写呢?为了能够捕获错误,你应该这样重构:const Person = require("../index");const valentinogagliardi = new Person(“valentinogagliardi”);valentinogagliardi .getData() .then(res => res) .catch(err => console.error(err));现在异常将会出现在控制台中:TypeError: url must be a string at Person.getData (/home/valentino/Documenti/articles-and-broadcasts/throw-from-async-functions-2018-04-02/index.js:12:13) at Object.<anonymous> (/home/valentino/Documenti/articles-and-broadcasts/throw-from-async-functions-2018-04-02/index.js:22:4) // …如果你想要更多的try/catch.,有一件重要的事需要注意。下面的代码不会捕获错误:const Person = require("../index");async function whatever() { try { const valentinogagliardi = new Person(“valentinogagliardi”); await valentinogagliardi.getData(); // do stuff with the eventual result and return something } catch (error) { throw Error(error); }}whatever();记住:被拒绝的Promise会在堆栈中传播,除非你抓住(catch)它。要在 try/catch 中正确捕获错误,可以像这样重构:async function whatever() { try { const valentinogagliardi = new Person(“valentinogagliardi”); await valentinogagliardi.getData(); // do stuff with the eventual result and return something } catch (error) { throw Error(error); }}whatever().catch(err => console.error(err));这就是它的工作原理。总结最后总结一下:从异步函数抛出的错误不会是“普通的异常”。异步函数和异步方法总是返回一个Promise,无论是已解决还是被拒绝。要拦截异步函数中的异常,必须使用catch()。以下是在Jest中测试异常的规则:使用 assert.throws 来测试普通函数和方法中的异常使用 expect + rejects 来测试异步函数和异步方法中的异常如果你对如何使用 Jest 测试 Koa 2 感兴趣,请查看使用Jest和Supertest进行测试的简绍这篇文章。感谢阅读!本文首发微信公众号:jingchengyideng欢迎关注,每天都给你推送新鲜的前端技术文章 ...

January 25, 2019 · 3 min · jiezi

阿里大规模数据中心性能分析

郭健美,阿里巴巴高级技术专家,目前主要从事数据中心的性能分析和软硬件结合的性能优化。CCF 系统软件专委和软件工程专委的委员。曾主持国家自然科学基金面上项目、入选上海市浦江人才计划A类、获得 ACMSIGSOFT “杰出论文奖”。担任 ICSE'18NIER、ASE'18、FSE'19 等重要会议程序委员会委员。*数据中心已成为支撑大规模互联网服务的标准基础设施。随着数据中心的规模越来越大,数据中心里每一次软件(如 JVM)或硬件(如 CPU)的升级改造都会带来高昂的成本。合理的性能分析有助于数据中心的优化升级和成本节约,而错误的分析可能误导决策、甚至造成巨大的成本损耗。本文整理自阿里巴巴高级技术专家郭健美在 2018 年 12 月 GreenTea JUG Java Meetup上的分享,主要介绍阿里大规模数据中心性能监控与分析的挑战与实践。大家好,很高兴有机会与 Java 社区的开发者交流。我的研究领域在软件工程,主要集中在系统配置和性能方面。软件工程一个比较常见的活动是找 bug,当然找 bug 很重要,但后来也发现,即便 bug-free 的程序也会被人配置错,所以就衍生出了软件配置问题。很多软件需要配置化,比如 Java 程序或 JVM 启动时可以配置很多参数。通过配置,一套软件可以灵活地提供各种定制化的功能,同时,这些配置也会对软件整体性能产生不同的影响。当然这些还在软件配置方面,来了阿里以后,我有机会把这方面工作扩展到了硬件,会更多地结合硬件比如 CPU,来看系统的配置变更和升级改造对性能、可靠性以及业务上线效果的影响。今天主要谈谈我在这方面的一点工作。阿里最有代表性的事件是“双 11”。这里还是用的去年的数据,因为今年有些数据还没出来。左上角是双十一的销售额,去年大概是 253 亿美金,比美国同期 Thanksgiving、Black Friday、Cyber Monday 加起来的销售额还要多。当然这是从业务层面去看数据,技术同学会比较关注右边的数据,去年双十一的交易峰值达到 32.5 万笔/秒、支付峰值达到 25.6 万笔/秒。对于企业来说,这么高的峰值性能意味着什么?意味着成本!我们之所以关注性能,就是希望通过持续的技术创新,不断地提高性能、同时节省成本。双十一零点的峰值性能不是一个简单的数字,其背后需要一个大规模数据中心来支撑。 简单来说,阿里的基础架构的上层是各种各样的应用,比如淘宝、天猫、菜鸟、钉钉,还有云计算和支付宝等,这也是阿里的一个特色,即具有丰富的业务场景。底层是上百万台机器相连的大规模数据中心,这些机器的硬件架构不同、分布地点也不同,甚至分布在世界各地。中间这部分我们称之为中台,最贴近上层应用的是数据库、存储、中间件以及计算平台,然后是资源调度、集群管理和容器,再下面是系统软件,包括操作系统、JVM 和虚拟化等。中台这部分的产品是衔接社区与企业的纽带。这两年阿里开源了很多产品,比如 Dubbo、PouchContainer 等,可以看出阿里非常重视开源社区,也非常重视跟开发者对话。现在很多人都在讲开源社区和生态,外面也有各种各样的论坛,但是像今天这样与开发者直接对话的活动并不是那么多,而推动社区发展最终还是要依赖开发者。这样大规模的基础架构服务于整个阿里经济体。从业务层面,我们可以看到 253 亿美金的销售额、32.5 万笔交易/秒这样的指标。然而,这些业务指标如何分解下来、落到基础架构的各个部分就非常复杂了。比如,我们在做 Java 中间件或 JVM 开发时,都会做性能评估。大部分技术团队开发产品后都会有个性能提升指标,比如降低了 20% 的 CPU 利用率,然而这些单个产品的性能提升放到整个交易链路、整个数据中心里面,占比多少?对数据中心整体性能提升贡献多少?这个问题很复杂,涉及面很广,包括复杂关联的软件架构和各种异构的硬件。后面会提到我们在这方面的一些思考和工作。阿里的电商应用主要是用 Java 开发的,我们也开发了自己的 AJDK,这部分对 OpenJDK 做了很多定制化开发,包括:融入更多新技术、根据业务需要及时加入一些 patches、以及提供更好的 troubleshooting 服务和工具。大家也知道,今年阿里入选并连任了 JCPEC 职位,有效期两年,这对整个 Java 开发者社区、尤其是国内的 Java 生态都是一件大事。但是,不是每个人都了解这件事的影响。记得之前碰到一位同仁,提到 JCPEC 对阿里这种大业务量的公司是有帮助,对小公司就没意义了。其实不是这样的,参选 JCPEC 的时候,大公司、小公司以及一些社区开发者都有投票资格,小公司或开发者有一票,大公司也只有一票,地位是一样的。很多国外的小公司更愿意参与到社区活动,为什么?举个简单例子,由于业务需要,你在 JVM 8 上做了一个特性,费了很大的力气开发调试完成、业务上线成功,结果社区推荐升级到 JVM11 上,这时你可能又需要把该特性在 JVM 11 上重新开发调试一遍,可能还要多踩一些新的坑,这显然增加了开发代价、拉长了上线周期。但如果你能影响社区标准的制定呢?你可以提出将该特性融入社区下一个发布版本,有机会使得你的开发工作成为社区标准,也可以借助社区力量完善该特性,这样既提高了技术影响力也减少了开发成本,还是很有意义的。过去我们做性能分析主要依赖小规模的基准测试。比如,我们开发了一个 JVM 新特性, 模拟电商的场景,大家可能都会去跑SPECjbb2015 的基准测试。再比如,测试一个新型硬件,需要比较 SPEC 或 Linpack 的基准测试指标。这些基准测试有必要性,因为我们需要一个简单、可复现的方式来衡量性能。但基准测试也有局限性,因为每一次基准测试都有其限定的运行环境和软硬件配置,这些配置设定对性能的影响可能很大,同时这些软硬件配置是否符合企业需求、是否具有代表性,都是需要考虑的问题。阿里的数据中心里有上万种不同的业务应用,也有上百万台分布在世界各地的不同服务器。当我们考虑在数据中心里升级改造软件或硬件时,一个关键问题是小规模基准测试的效果是否能扩展到数据中心里复杂的线上生产环境?举个例子,我们开发了 JVM 的一个新特性,在 SPECjbb2015 的基准测试中看到了不错的性能收益,但到线上生产环境灰度测试的时候,发现该特性可以提升一个 Java 应用的性能、但会降低另一个 Java 应用的性能。同时,我们也可能发现即便对同一个 Java 应用,在不同硬件上得到的性能结果大不相同。这些情况普遍存在,但我们不可能针对每个应用、每种硬件都跑一遍测试,因而需要一个系统化方法来估计该特性对各种应用和硬件的整体性能影响。对数据中心来说,评估每个软件或硬件升级的整体性能影响非常重要。比如,“双11”的销售额和交易峰值,业务层面可能主要关心这两个指标,那么这两个指标翻一倍的时候我们需要买多少台新机器?需要多买一倍的机器么?这是衡量技术能力提升的一个手段,也是体现“新技术”对“新商业”影响的一个途径。我们提出了很多技术创新手段,也发现了很多性能提升的机会,但需要从业务上也能看出来。为了解决上面提到的问题,我们开发了 SPEED 平台。首先是估计当前线上发生了什么,即 Estimation,通过全域监控采集数据,再进行数据分析,发现可能的优化点。比如,某些硬件整体表现比较差,可以考虑替换。然后,我们会针对软件或硬件的升级改造做线上评估,即 Evaluation。比如,硬件厂商推出了一个新硬件,他们自己肯定会做一堆评测,得到一组比较好的性能数据,但刚才也提到了,这些评测和数据都是在特定场景下跑出来的,这些场景是否适合用户的特定需求?没有直接的答案。通常,用户也不会让硬件厂商到其业务环境里去跑评测。这时候就需要用户自己拿这个新硬件做灰度测试。当然灰度规模越大评测越准确,但线上环境都直接关联业务,为了降低风险,实际中通常都是从几十台甚至几台、到上百台、上千台的逐步灰度。SPEED 平台要解决的一个问题就是即便在灰度规模很小时也能做一个较好的估计,这会节约非常多的成本。随着灰度规模增大,平台会不断提高性能分析质量,进而辅助用户决策,即 Decision。这里的决策不光是判断要不要升级新硬件或新版软件,而且需要对软硬件全栈的性能有一个很好的理解,明白什么样的软硬件架构更适合目标应用场景,这样可以考虑软硬件优化定制的方向。比如,Intel 的 CPU 从 Broadwell 到 Skylake,其架构改动很大,但这个改动的直接效果是什么?Intel 只能从基准测试中给答案,但用户可能根据自己的应用场景给出自己的答案,从而提出定制化需求,这对成本有很大影响。最后是 Validation,就是通常规模化上线后的效果来验证上述方法是否合理,同时改进方法和平台。数据中心里软硬件升级的性能分析需要一个全局的性能指标,但目前还没有统一的标准。Google 今年在 ASPLOS 上发表了一篇论文,提出了一个叫 WSMeter 的性能指标,主要是基于 CPI 来衡量性能。在 SPEED 平台里,我们也提出了一个全局性能指标,叫资源使用效率 RUE。基本思想很简单,就是衡量每个单位 Work Done 所消耗的资源。这里的 Work Done 可以是电商里完成的一个 Query,也可以是大数据处理里的一个 Task。而资源主要涵盖四大类:CPU、内存、存储和网络。通常我们会主要关注 CPU 或内存,因为目前这两部分消费了服务器大部分的成本。RUE 的思路提供了一个多角度全面衡量性能的方法。举个例子,业务方反映某台机器上应用的 response time 升高了,这时登录到机器上也看到 load 和 CPU 利用率都升高了。这时候你可能开始紧张了,担心出了一个故障,而且很可能是由于刚刚上线的一个新特性造成的。然而,这时候应该去看下 QPS 指标,如果 QPS 也升高了,那么也许是合理的,因为使用更多资源完成了更多的工作,而且这个资源使用效率的提升可能就是由新特性带来的。所以,性能需要多角度全面地衡量,否则可能会造成不合理的评价,错失真正的性能优化机会。下面具体讲几个数据中心性能分析的挑战,基本上是线上碰到过的具体问题,希望能引起大家的一些思考。首先是性能指标。可能很多人都会说性能指标我每天都在用,这有什么好说的。其实,真正理解性能指标以及系统性能本身并不是那么容易。举个例子,在数据中心里最常用的一个性能指标是 CPU 利用率,给定一个场景,数据中心里每台机器平均 CPU 利用率是 50%,假定应用需求量不会再增长、并且软件之间也不会互相干扰,那么是否可以把数据中心的现有机器数量减半呢?这样,理想情况下 CPU 利用率达到 100% 就可以充分利用资源了,是否可以这样简单地理解 CPU 利用率和数据中心的性能呢?肯定不行。就像刚才说的,数据中心除了 CPU,还有内存、存储和网络资源,机器数量减半可能很多应用都跑不起来了。再举个例子,某个技术团队升级了其负责的软件版本以后,通过线上测试看到平均 CPU 利用率下降了 10%,因而声明性能提升了 10%。这个声明没有错,但我们更关心性能提升以后是否能节省成本,比如性能提升了 10%,是否可以把该应用涉及的 10%的机器关掉?这时候性能就不应该只看 CPU 利用率,而应该再看看对吞吐量的影响。所以,系统性能和各种性能指标,可能大家都熟悉也都在用,但还需要更全面地去理解。刚才提到 SPEED 的 Estimation 会收集线上性能数据,可是收集到的数据一定对吗?这里讲一个 Hyper-Threading 超线程的例子,可能对硬件了解的同学会比较熟悉。超线程是 Intel 的一个技术,比如我们的笔记本,一般现在都是双核的,也就是两个hardwarecores,如果支持超线程并打开以后,一个 hardware core 就会变成两个 hardware threads,即一台双核的机器会有四个逻辑 CPU。来看最上面一张图,这里有两个物理核,没有打开超线程,两边 CPU 资源都用满了,所以从任务管理器报出的整台机器平均 CPU 利用率是 100%。左下角的图也是两个物理核,打开了超线程,每个物理核上有一个 hardwarethread 被用满了,整台机器平均 CPU 利用率是 50%。再看右下角的图,也是两个物理核,也打开了超线程,有一个物理核的两个hardware threads 都被用满了,整台机器平均 CPU 利用率也是 50%。左下角和右下角的 CPU 使用情况完全不同,但是如果我们只是采集整机平均 CPU 利用率,看到的数据是一样的!所以,做性能数据分析时,不要只是想着数据处理和计算,还应该注意这些数据是怎么采集的,否则可能会得到一些误导性的结果。数据中心里的硬件异构性是性能分析的一大挑战,也是性能优化的一个方向。比如这里左边的 Broadwell 架构,是 Intel 过去几年服务器 CPU 的主流架构,近几年在推右边的 Skylake 架构,包含最新的 Cascade Lake CPU。Intel 在这两个架构上做了很大的改动,比如,Broadwell 下访问内存还是保持多年的环状方式,而到了 Skylake 改为网格状方式。再比如,L2 Cache 到了Skylake 上扩大了四倍,通常来说这可以提高 L2 Cache 的命中率,但是 cache 越大也不代表性能就一定好,因为维护 cache coherence 会带来额外的开销。这些改动有利有弊,但我们需要衡量利和弊对整体性能的影响,同时结合成本来考虑是否需要将数据中心的服务器都升级到 Skylake。了解硬件的差异还是很有必要的,因为这些差异可能影响所有在其上运行的应用,并且成为硬件优化定制的方向。现代互联网服务的软件架构非常复杂,比如阿里的电商体系架构,而复杂的软件架构也是性能分析的一个主要挑战。举个简单的例子,图中右边是优惠券应用,左上角是大促主会场应用,右下角是购物车应用,这三个都是电商里常见的业务场景。从 Java 开发的角度,每个业务场景都是一个 application。电商客户既可以从大促主会场选择优惠券,也可以从购物车里选择优惠券,这是用户使用习惯的不同。从软件架构角度看,大促主会场和购物车两个应用就形成了优惠券应用的两个入口,入口不同对于优惠券应用本身的调用路径不同,性能影响也就不同。所以,在分析优惠券应用的整体性能时需要考虑其在电商业务里的各种错综复杂的架构关联和调用路径。像这种复杂多样的业务场景和调用路径是很难在基准测试中完全复现的,这也是为什么我们需要做线上性能评估。这是数据分析里著名的辛普森悖论,在社会学和医学领域有很多常见案例,我们在数据中心的性能分析里也发现了。这是线上真实的案例,具体是什么 App 我们不用追究。假设还用前面的例子,比如 App 就是优惠券应用,在大促的时候上线了一个新特性 S,灰度测试的机器占比为 1%,那么根据 RUE 指标,该特性可以提升性能 8%,挺不错的结果。但是如果优惠券应用有三个不同的分组,分组假设就是刚才提到的不同入口应用,那么从每个分组看,该特性都降低了应用的性能。同样一组数据、同样的性能评估指标,通过整体聚集分析得到的结果与通过各部分单独分析得到的结果正好相反,这就是辛普森悖论。既然是悖论,说明有时候应该看总体评估结果,有时间应该看部分评估结果。在这个例子里面,我们选择看部分评估、也就是分组上的评估结果,所以看起来这个新特性造成了性能下降,应该继续修改并优化性能。所以,数据中心里的性能分析还要预防各种可能的数据分析陷阱,否则可能会严重误导决策。最后,还有几分钟,简单提一下性能分析师的要求。这里通常的要求包括数学、统计方面的,也有计算机科学、编程方面的,当然还有更重要的、也需要长期积累的领域知识这一块。这里的领域知识包括对软件、硬件以及全栈性能的理解。其实,我觉得每个开发者都可以思考一下,我们不光要做功能开发,还要考虑所开发功能的性能影响,尤其是对数据中心的整体性能影响。比如,JVM 的 GC 开发,社区里比较关心 GC 暂停时间,但这个指标与 Java 应用的 response time 以及所消耗的 CPU 资源是什么关系,我们也可以有所考虑。当然,符合三块要求的候选人不好找,我们也在总结系统化的训练流程,欢迎对系统性能有兴趣的同学加入我们。本文作者:amber涂南阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

January 17, 2019 · 2 min · jiezi

testing

<div class=“cover-img” style=“background-image:url(/d/file/news/2018-11-27/25836d91061e5d9101e757f2dd65483e.png)"></div> <div class=“art-lead”> <span class=“iconfont”>&#xe621;</span> <span class=“art-lead-text”>在不久前于深圳举办的ASPENCORE全球双峰会上,全球领先的高性能模拟半导体企业ADI公司展示了一款应用于交通路况监控、实时追踪目标车辆位置等工业级场景的24GHz雷达全链路解决方案。</span> </div> <div class=“art-con article_body”> </div>

January 15, 2019 · 1 min · jiezi

利用 Postman Chrome app 和 Chrome 浏览器共享网站 cookie

背景作为一个Web工程师,最熟悉的日常工作莫过于后台接口开发与联调测试,而在接口测试上,大家最喜爱的工具清单里,必然少不了 Postman 这一利器。然而,有时接口测试需要准备好登录态,或者其他状态数据,而这些数据往往就存在浏览器 Cookie 里边。结合本文介绍的工具,便可以无缝在 Postman Chrome app (为什么强调是 Postman Chrome app,文章末尾会说明)和 Chrome 浏览器之间共享 Cookie,而这个共享过程对用户是透明的。工具清单以下工具请自行安装,我只贴下官方的软件界面截图。Chrome 浏览器Postman Chrome appPostman Interceptor使用步骤以下我们以 Github 网站为例,演示下如何实现 Cookie 共享。一、确认 Postman Interceptor 插件安装成功(如图所示)二、启动 Postman,在右上角的卫星小图标那里开启 Chrome Interceptor三、在 Chrome 浏览器里正常登陆 GitHub 网站(此步骤没什么好演示的 ╭(╯^╰)╮)四、在 Postman Chrome app 中直接模拟请求通知接口接口路径:https://github.com/notificati…也就是说,这个时候,我们虽然没有对 Postman 做特殊的 Cookie 设置,但是它的请求的登录态都被服务器验证通过了,cookie 共享成功!假如这个时候退出浏览器的登录态呢?我们先从 GitHub 退出登录,还是刚才的请求,这个时候的响应是:是的,因为 Chrome 里已经退出登录,所以 Postman 这边也自然失去登录态了,说明两边 Cookie 是同步的。Postman Interceptor 的 BonusPostman Interceptor 还有一点比较爽的是,它的 Request Capture 支持捕捉 Chrome 浏览器里的请求记录,并且自动同步到 Postman Chrome app 里边,这样的话,我们就可以方便直接在 Postman 里获取到我们需要测试的网络请求,而不是一个一个自己填写参数之类的了。缺陷遗憾的是,按照官方说明,现在 Postman Interceptor 的这个Cookie 共享还不能支持独立安装的桌面版(从官方下载而不是从 Chrome 应用市场下载)的 Postman Desktop,所以,如果你希望使用上述功能,你只能安装回 Postman Chrome app,而这个版本相对桌面版,功能自然也会少。Note: Interceptor feature is supported only in our Postman Chrome Apps and is not available in Postman Desktop Apps at the moment.另一方面,考虑到 Chrome 浏览器将会在不久的将来停掉 Chrome apps 的支持,可能这个方案也撑不了太久。如果你真心希望 Postman 将上述功能加到他们的桌面版里,可以到他们的官方GitHub issues去请愿,他们正在收集大家的意见。但是……这个请愿帖已经两年多了,而就在我表达请求之前的几个小时到几天之前,都有人陆续去请愿,所以也不知道会不会真的如愿了。总结对于确实需要获取网站 cookie 才能完成接口测试的场景,上述方法有一定的便利性,也才有必要使用我的方法,其他场景的接口测试,你们就无视我吧。参考链接Postman: Using the Interceptor to read and write cookiesPostman Help Center: How do I access Chrome’s cookies in Postman’s Chrome App?Postman Learning Center: Interceptor extensionGoogle is phasing out Chrome apps for Mac and Windows ...

January 14, 2019 · 1 min · jiezi

支付宝工程师创造出了一个可以“拷贝”支付宝的神器

摘要: “拷贝”支付宝,新版mPaaS的魔法开启了!mPaaS是源于支付宝的移动开发平台,从最初的金融级移动开发平台,逐渐演进成集开发、测试、发布、分析、运营于一体的 App 全生命周期管理平台,服务了广发银行、12306、上海地铁等标杆级客户,帮助客户完成技术升级与业务增长。“拷贝”支付宝?呵,别逗了,这不可能。但支付宝工程师们真的把这种“不可能”变成了可能。1月4日,在上海举行的蚂蚁金服ATEC城市峰会上,新一代的移动开发平台mPaaS(mobile Platform-as-a-Service)3.0正式上线。新版本围绕移动场景完成了全面智能化升级,形成分析、营销、预测、多媒体等四大 AI 能力矩阵。此外,mPaaS 3.0版本提供了一套完备的H5/小程序应用开发、运维、分析功能,并提供底层小程序业务接口扩展能力,开发者可以利用mPaaS 小程序框架自主的开放业务接口。“新版本以智能技术助力客户构建自己的超级 App,并可以基于自有 App 做技术开放,构建超级 App生态,企业可以拥有等同于支付宝的能力,包括技术、生态、业务等”,蚂蚁金服金融科技产品技术总监杨冰介绍。mPaaS的演进之路正式介绍全新一代的mPaaS之前,我们先来回顾一下这个神奇平台的发展历程。2015年,金融行业风口已至。顺应趋势、助推行业整体进化,蚂蚁金服提出互联网助推器计划,发布蚂蚁金融云。支付宝从担保支付到国民App的过程中,沉淀了大量的技术实践。但如何将支付宝多年沉淀的技术在金融行业落地,这成了当时的一个挑战。2016年上半年,蚂蚁金服副CTO胡喜拍板,秉承“技术成熟一个,开放一个”的大原则,用轻量级的方式让蚂蚁的金融科技能力落地开花,因此首选mPaaS,并将其率先实施于蚂蚁的自有业务——网商银行,取得了非常有成效的结果。随后,mPaaS在信美保险和天弘基金也进行了落地。最开始的时候,mPaaS初期主要支持内部业务,所以并没有做多租户模式,而是采用的独占的模式,让用户去买机器,在公有云上,用户购买了服务后只能自己用。但支付宝工程师要以云的方式来完成这个动作,使其成为一个资源池。mPaaS的演进开始了。支付宝工程师最先做的是,先将mPaaS组件化、共享化,即用户可以自行挑选适合自己需求的组件,而无需整体采购全套方案。紧接着,mPaaS推出了一些热点的创新功能,比如热修复、离线包等。所以,在2016年11月的时候,mPaaS推出了一个更新的版本。如果说之前的mPaaS主要落地与支付宝内部的业务;那么此时的mPaaS已经具备了对外商业化的雏形,已经是一个正式的商业化版本。与此同时,mPaaS迎来了发展过程中一个非常重要的客户——12306。基于mPaaS的底盘技术,支付宝工程师对12306做了一个大的升级,并取得了非常明显的效果。新版12306 App无论是在流畅度,还是用户体验的方面,都取得了很好的反馈。为此,铁道部还专门给mPaaS团队发了感谢信,对支付宝团队的专业精神,还有技术深度都进行了高度的赞扬。12306项目的大获成功,不但解决了实际的痛点,也坚定了支付宝技术团队的做移动技术开放的决心。要知道,这个项目是10多个人的团队在不到2个月的时间内完成的,而且平稳顺利地经受住了当年的春运亿级用户的考验,是支付宝技术在相同体量 App 中的第一次成功复制。支付宝工程师们马不停蹄,立志要解决金融行业的痛点。此时,mPaaS的第一个金融客户广发银行出现了。彼时,广发银行研发中心总经理李怀根计划对旗下的App进行优化升级,其中最主要的是进行性能优化,即App的启动速度较慢,他希望立即将其解决。支付宝工程师用了一周左右的时间,设计了一个POC(Proof of Concept),就把广发银行App首页的代码“搬到”了mPaaS上,并在行里进行了现场对比 Demo, 对比发现精彩的平均启动速度从几秒缩短到不到1秒。最终广发银行在众多厂商中选择了与源于支付宝的 mPaaS 合作。新版发现精彩上线后,李怀根更在2018年云栖大会中总结到:“发现精彩 3.0 平均启动速度达到了0.52秒,iOS 闪退率不到万分之一,发现精彩整体体验大幅度提升!”这是mPaaS在高并发,大体量金融级 App 中的又一次复制。“拷贝”支付宝,新版mPaaS的魔法mPaaS是源于支付宝的移动开发平台,现在已经演进成集开发,测试,发布,分析,运营于一体的App全生命周期管理平台。1月4号发布的mPaaS 3.0 融入了人工智能小程序技术,进行了全面的升级。魔法一:全面升级的智能化能力mPaaS 3.0全面向智能化进行升级,推出了智能投放,舆情分析,多媒体,预测4款智能化组件。同时智能预测圈人的功能,与之前发布的消息推送服务(MPS),发布服务(MDS)进行了全面整合,例如可以通过智能预测来判断接下来一周即将流失的客户,然后针对这部分用户发布一个消息 (通过MPS服务),或者通过智能投放服务发放一个营销活动(通过智能投放服务MCDP),促使这些用户能够继续留存下来。所以这次升级不仅仅是推出了智能化组件,更是整个平台的智能化升级。同时 mPaaS 3.0 解决了智能化能力落地难的问题, mPaaS 提供数据采集,智能引擎,智能化场景一体化解决方案,开箱即用,无需做任何系统对接,数据对接。同时,也提供了数据和系统的扩展能力,可以结合业务数据服务更多的场景。魔法二:通过小程序构建自主的生态系统新版的mPaaS还提供mPaaS小程序功能,mPaaS小程序源于支付宝小程序,是支付宝小程序技术的全面开放,包含了小程序开发框架、IDE、发布服务、分析服务等完整能力闭环,让客户可以以小程序的方式开放业务接口,围绕自己的App构建小程序生态。同时,基于mPaaS小程序开发的业务可以在自有App、阿里系、mPaaS生态间投放、联通、共享,壮大客户自主的业务生态。魔法三:全新组件“真机云测”面向碎片化严重的安卓市场,新版的mPaaS还推出全新组件“真机云测”,帮助App在上线前完成全面、统一的测试方案,从而彻底验证App的兼容性、功能完善与性能稳定。 “真机云测”提供了包括机柜,测试框架,任务调度平台,测试效果评估一体化解决方案,可以有效的提高测试效率,降低测试成本,提高问题发现率。目前,mPaaS真机云测已在支付宝体系内完成 50w+自动化任务,用例执行400w余次,捕获闪退 5w+次。基于以上技术创新,新版的mPaaS让“拷贝”支付宝更加便捷。毫不夸张地说,通过蚂蚁金服的移动开发平台mPaaS,企业可以拥有等同于支付宝的能力,包括技术、生态、业务等。目前,全新一代的移动开发平台mPaaS已经在蚂蚁金服金融科技官网(https://tech.antfin.com/produ…)上对外开放。本文作者:平生栗子阅读原文本文为云栖社区原创内容,未经允许不得转载。

January 11, 2019 · 1 min · jiezi

路径规划之 A* 算法

算法介绍A*(念做:A Star)算法是一种很常用的路径查找和图形遍历算法。它有较好的性能和准确度。本文在讲解算法的同时也会提供Python语言的代码实现,并会借助matplotlib库动态的展示算法的运算过程。A算法最初发表于1968年,由Stanford研究院的Peter Hart, Nils Nilsson以及Bertram Raphael发表。它可以被认为是Dijkstra算法的扩展。由于借助启发函数的引导,A算法通常拥有更好的性能。广度优先搜索为了更好的理解A算法,我们首先从广度优先(Breadth First)算法讲起。正如其名称所示,广度优先搜索以广度做为优先级进行搜索。从起点开始,首先遍历起点周围邻近的点,然后再遍历已经遍历过的点邻近的点,逐步的向外扩散,直到找到终点。这种算法就像洪水(Flood fill)一样向外扩张,算法的过程如下图所示:在上面这幅动态图中,算法遍历了图中所有的点,这通常没有必要。对于有明确终点的问题来说,一旦到达终点便可以提前终止算法,下面这幅图对比了这种情况:在执行算法的过程中,每个点需要记录达到该点的前一个点的位置 – 可以称之为父节点。这样做之后,一旦到达终点,便可以从终点开始,反过来顺着父节点的顺序找到起点,由此就构成了一条路径。Dijkstra算法Dijkstra算法是由计算机科学家Edsger W. Dijkstra在1956年提出的。Dijkstra算法用来寻找图形中节点之间的最短路径。考虑这样一种场景,在一些情况下,图形中相邻节点之间的移动代价并不相等。例如,游戏中的一幅图,既有平地也有山脉,那么游戏中的角色在平地和山脉中移动的速度通常是不相等的。在Dijkstra算法中,需要计算每一个节点距离起点的总移动代价。同时,还需要一个优先队列结构。对于所有待遍历的节点,放入优先队列中会按照代价进行排序。在算法运行的过程中,每次都从优先队列中选出代价最小的作为下一个遍历的节点。直到到达终点为止。下面对比了不考虑节点移动代价差异的广度优先搜索与考虑移动代价的Dijkstra算法的运算结果:当图形为网格图,并且每个节点之间的移动代价是相等的,那么Dijkstra算法将和广度优先算法变得一样。最佳优先搜索在一些情况下,如果我们可以预先计算出每个节点到终点的距离,则我们可以利用这个信息更快的到达终点。其原理也很简单。与Dijkstra算法类似,我们也使用一个优先队列,但此时以每个节点到达终点的距离作为优先级,每次始终选取到终点移动代价最小(离终点最近)的节点作为下一个遍历的节点。这种算法称之为最佳优先(Best First)算法。这样做可以大大加快路径的搜索速度,如下图所示:但这种算法会不会有什么缺点呢?答案是肯定的。因为,如果起点和终点之间存在障碍物,则最佳优先算法找到的很可能不是最短路径,下图描述了这种情况。A算法对比了上面几种算法,最后终于可以讲解本文的重点:A算法了。下面的描述我们将看到,A算法实际上是综合上面这些算法的特点于一身的。A算法通过下面这个函数来计算每个节点的优先级。其中:f(n)是节点n的综合优先级。当我们选择下一个要遍历的节点时,我们总会选取综合优先级最高(值最小)的节点。g(n) 是节点n距离起点的代价。h(n)是节点n距离终点的预计代价,这也就是A算法的启发函数。关于启发函数我们在下面详细讲解。A算法在运算过程中,每次从优先队列中选取f(n)值最小(优先级最高)的节点作为下一个待遍历的节点。另外,A算法使用两个集合来表示待遍历的节点,与已经遍历过的节点,这通常称之为open_set和close_set。完整的A算法描述如下: 初始化open_set和close_set;* 将起点加入open_set中,并设置优先级为0(优先级最高);* 如果open_set不为空,则从open_set中选取优先级最高的节点n: * 如果节点n为终点,则: * 从终点开始逐步追踪parent节点,一直达到起点; * 返回找到的结果路径,算法结束; * 如果节点n不是终点,则: * 将节点n从open_set中删除,并加入close_set中; * 遍历节点n所有的邻近节点: * 如果邻近节点m在close_set中,则: * 跳过,选取下一个邻近节点 * 如果邻近节点m也不在open_set中,则: * 设置节点m的parent为节点n * 计算节点m的优先级 * 将节点m加入open_set中启发函数上面已经提到,启发函数会影响A算法的行为。在极端情况下,当启发函数h(n)始终为0,则将由g(n)决定节点的优先级,此时算法就退化成了Dijkstra算法。如果h(n)始终小于等于节点n到终点的代价,则A算法保证一定能够找到最短路径。但是当h(n)的值越小,算法将遍历越多的节点,也就导致算法越慢。如果h(n)完全等于节点n到终点的代价,则A算法将找到最佳路径,并且速度很快。可惜的是,并非所有场景下都能做到这一点。因为在没有达到终点之前,我们很难确切算出距离终点还有多远。如果h(n)的值比节点n到终点的代价要大,则A算法不能保证找到最短路径,不过此时会很快。在另外一个极端情况下,如果h()n相较于g(n)大很多,则此时只有h(n)产生效果,这也就变成了最佳优先搜索。由上面这些信息我们可以知道,通过调节启发函数我们可以控制算法的速度和精确度。因为在一些情况,我们可能未必需要最短路径,而是希望能够尽快找到一个路径即可。这也是A算法比较灵活的地方。对于网格形式的图,有以下这些启发函数可以使用:如果图形中只允许朝上下左右四个方向移动,则可以使用曼哈顿距离(Manhattan distance)。如果图形中允许朝八个方向移动,则可以使用对角距离。如果图形中允许朝任何方向移动,则可以使用欧几里得距离(Euclidean distance)。关于距离曼哈顿距离如果图形中只允许朝上下左右四个方向移动,则启发函数可以使用曼哈顿距离,它的计算方法如下图所示:计算曼哈顿距离的函数如下,这里的D是指两个相邻节点之间的移动代价,通常是一个固定的常数。function heuristic(node) = dx = abs(node.x - goal.x) dy = abs(node.y - goal.y) return D * (dx + dy)对角距离如果图形中允许斜着朝邻近的节点移动,则启发函数可以使用对角距离。它的计算方法如下:计算对角距离的函数如下,这里的D2指的是两个斜着相邻节点之间的移动代价。如果所有节点都正方形,则其值就是function heuristic(node) = dx = abs(node.x - goal.x) dy = abs(node.y - goal.y) return D * (dx + dy) + (D2 - 2 * D) * min(dx, dy)欧几里得距离如果图形中允许朝任意方向移动,则可以使用欧几里得距离。欧几里得距离是指两个节点之间的直线距离,因此其计算方法也是我们比较熟悉的:其函数表示如下:function heuristic(node) = dx = abs(node.x - goal.x) dy = abs(node.y - goal.y) return D * sqrt(dx * dx + dy * dy)算法实现虽然前面介绍了很多内容,但实际上A算法并不复杂,实现起来也比较简单。下面我们给出一个Python语言的代码示例。之所以使用Python语言是因为我们可以借助matplotlib库很方便的将结果展示出来。在理解了算法之后,通过其他语言实现也并非难事。算法的源码可以到我的github上下载:paulQuei/a-star-algorithm。我们的算法演示的是在一个二维的网格图形上从起点找寻终点的求解过程。坐标点与地图首先,我们创建一个非常简单的类来描述图中的点,相关代码如下:# point.pyimport sysclass Point: def init(self, x, y): self.x = x self.y = y self.cost = sys.maxsize接着,我们实现一个描述地图结构的类。为了简化算法的描述:我们选定左下角坐标[0, 0]的点是算法起点,右上角坐标[size - 1, size - 1]的点为要找的终点。为了让算法更有趣,我们在地图的中间设置了一个障碍,并且地图中还会包含一些随机的障碍。该类的代码如下:# random_map.pyimport numpy as npimport pointclass RandomMap: def init(self, size=50): ① self.size = size self.obstacle = size//8 ② self.GenerateObstacle() ③ def GenerateObstacle(self): self.obstacle_point = [] self.obstacle_point.append(point.Point(self.size//2, self.size//2)) self.obstacle_point.append(point.Point(self.size//2, self.size//2-1)) # Generate an obstacle in the middle for i in range(self.size//2-4, self.size//2): ④ self.obstacle_point.append(point.Point(i, self.size-i)) self.obstacle_point.append(point.Point(i, self.size-i-1)) self.obstacle_point.append(point.Point(self.size-i, i)) self.obstacle_point.append(point.Point(self.size-i, i-1)) for i in range(self.obstacle-1): ⑤ x = np.random.randint(0, self.size) y = np.random.randint(0, self.size) self.obstacle_point.append(point.Point(x, y)) if (np.random.rand() > 0.5): # Random boolean ⑥ for l in range(self.size//4): self.obstacle_point.append(point.Point(x, y+l)) pass else: for l in range(self.size//4): self.obstacle_point.append(point.Point(x+l, y)) pass def IsObstacle(self, i ,j): ⑦ for p in self.obstacle_point: if i==p.x and j==p.y: return True return False这段代码说明如下:构造函数,地图的默认大小是50x50;设置障碍物的数量为地图大小除以8;调用GenerateObstacle生成随机障碍物;在地图的中间生成一个斜着的障碍物;随机生成其他几个障碍物;障碍物的方向也是随机的;定义一个方法来判断某个节点是否是障碍物;算法主体有了基本的数据结构之后,我们就可以开始实现算法主体了。这里我们通过一个类来封装我们的算法。首先实现一些算法需要的基本函数,它们如下:# a_star.pyimport sysimport timeimport numpy as npfrom matplotlib.patches import Rectangleimport pointimport random_mapclass AStar: def init(self, map): self.map=map self.open_set = [] self.close_set = [] def BaseCost(self, p): x_dis = p.x y_dis = p.y # Distance to start point return x_dis + y_dis + (np.sqrt(2) - 2) * min(x_dis, y_dis) def HeuristicCost(self, p): x_dis = self.map.size - 1 - p.x y_dis = self.map.size - 1 - p.y # Distance to end point return x_dis + y_dis + (np.sqrt(2) - 2) * min(x_dis, y_dis) def TotalCost(self, p): return self.BaseCost(p) + self.HeuristicCost(p) def IsValidPoint(self, x, y): if x < 0 or y < 0: return False if x >= self.map.size or y >= self.map.size: return False return not self.map.IsObstacle(x, y) def IsInPointList(self, p, point_list): for point in point_list: if point.x == p.x and point.y == p.y: return True return False def IsInOpenList(self, p): return self.IsInPointList(p, self.open_set) def IsInCloseList(self, p): return self.IsInPointList(p, self.close_set) def IsStartPoint(self, p): return p.x == 0 and p.y ==0 def IsEndPoint(self, p): return p.x == self.map.size-1 and p.y == self.map.size-1这里的函数说明如下:init:类的构造函数。BaseCost:节点到起点的移动代价,对应了上文的g(n)HeuristicCost:节点到终点的启发函数,对应上文的h(n)。由于我们是基于网格的图形,所以这个函数和上一个函数用的是对角距离。TotalCost:代价总和,即对应上面提到的f(n)。IsValidPoint:判断点是否有效,不在地图内部或者障碍物所在点都是无效的。IsInPointList:判断点是否在某个集合中。IsInOpenList:判断点是否在open_set中。IsInCloseList:判断点是否在close_set中。IsStartPoint:判断点是否是起点。IsEndPoint:判断点是否是终点。有了上面这些辅助函数,就可以开始实现算法主逻辑了,相关代码如下:# a_star.pydef RunAndSaveImage(self, ax, plt): start_time = time.time() start_point = point.Point(0, 0) start_point.cost = 0 self.open_set.append(start_point) while True: index = self.SelectPointInOpenList() if index < 0: print(‘No path found, algorithm failed!!!’) return p = self.open_set[index] rec = Rectangle((p.x, p.y), 1, 1, color=‘c’) ax.add_patch(rec) self.SaveImage(plt) if self.IsEndPoint(p): return self.BuildPath(p, ax, plt, start_time) del self.open_set[index] self.close_set.append(p) # Process all neighbors x = p.x y = p.y self.ProcessPoint(x-1, y+1, p) self.ProcessPoint(x-1, y, p) self.ProcessPoint(x-1, y-1, p) self.ProcessPoint(x, y-1, p) self.ProcessPoint(x+1, y-1, p) self.ProcessPoint(x+1, y, p) self.ProcessPoint(x+1, y+1, p) self.ProcessPoint(x, y+1, p)这段代码应该不需要太多解释了,它就是根据前面的算法逻辑进行实现。为了将结果展示出来,我们在算法进行的每一步,都会借助于matplotlib库将状态保存成图片。上面这个函数调用了其他几个函数代码如下:# a_star.pydef SaveImage(self, plt): millis = int(round(time.time() * 1000)) filename = ‘./’ + str(millis) + ‘.png’ plt.savefig(filename)def ProcessPoint(self, x, y, parent): if not self.IsValidPoint(x, y): return # Do nothing for invalid point p = point.Point(x, y) if self.IsInCloseList(p): return # Do nothing for visited point print(‘Process Point [’, p.x, ‘,’, p.y, ‘]’, ‘, cost: ‘, p.cost) if not self.IsInOpenList(p): p.parent = parent p.cost = self.TotalCost(p) self.open_set.append(p)def SelectPointInOpenList(self): index = 0 selected_index = -1 min_cost = sys.maxsize for p in self.open_set: cost = self.TotalCost(p) if cost < min_cost: min_cost = cost selected_index = index index += 1 return selected_indexdef BuildPath(self, p, ax, plt, start_time): path = [] while True: path.insert(0, p) # Insert first if self.IsStartPoint(p): break else: p = p.parent for p in path: rec = Rectangle((p.x, p.y), 1, 1, color=‘g’) ax.add_patch(rec) plt.draw() self.SaveImage(plt) end_time = time.time() print(’===== Algorithm finish in’, int(end_time-start_time), ’ seconds’)这三个函数应该是比较容易理解的:SaveImage:将当前状态保存到图片中,图片以当前时间命名。ProcessPoint:针对每一个节点进行处理:如果是没有处理过的节点,则计算优先级设置父节点,并且添加到open_set中。SelectPointInOpenList:从open_set中找到优先级最高的节点,返回其索引。BuildPath:从终点往回沿着parent构造结果路径。然后从起点开始绘制结果,结果使用绿色方块,每次绘制一步便保存一个图片。测试入口最后是程序的入口逻辑,使用上面写的类来查找路径:# main.pyimport numpy as npimport matplotlib.pyplot as pltfrom matplotlib.patches import Rectangleimport random_mapimport a_starplt.figure(figsize=(5, 5))map = random_map.RandomMap() ①ax = plt.gca()ax.set_xlim([0, map.size]) ②ax.set_ylim([0, map.size])for i in range(map.size): ③ for j in range(map.size): if map.IsObstacle(i,j): rec = Rectangle((i, j), width=1, height=1, color=‘gray’) ax.add_patch(rec) else: rec = Rectangle((i, j), width=1, height=1, edgecolor=‘gray’, facecolor=‘w’) ax.add_patch(rec)rec = Rectangle((0, 0), width = 1, height = 1, facecolor=‘b’)ax.add_patch(rec) ④rec = Rectangle((map.size-1, map.size-1), width = 1, height = 1, facecolor=‘r’)ax.add_patch(rec) ⑤plt.axis(’equal’) ⑥plt.axis(‘off’)plt.tight_layout()#plt.show()a_star = a_star.AStar(map)a_star.RunAndSaveImage(ax, plt) ⑦这段代码说明如下:创建一个随机地图;设置图像的内容与地图大小一致;绘制地图:对于障碍物绘制一个灰色的方块,其他区域绘制一个白色的的方块;绘制起点为蓝色方块;绘制终点为红色方块;设置图像的坐标轴比例相等并且隐藏坐标轴;调用算法来查找路径;由于我们的地图是随机的,所以每次运行的结果可能会不一样,下面是我的电脑上某次运行的结果:如果感兴趣这篇文章中的动图是如何制作的,请看我的另外一篇文章:使用Matplotlib绘制3D图形 - 制作动图。算法变种A算法有不少的变种,这里我们介绍最主要的几个。更多的内容请以访问维基百科:A Variants。ARAARA 全称是Anytime Repairing A,也称为Anytime A。与其他Anytime算法一样,它具有灵活的时间成本,即使在它结束之前被中断,也可以返回路径查找或图形遍历问题的有效解决方案。方法是在逐步优化之前生成快速,非最优的结果。在现实世界的规划问题中,问题的解决时间往往是有限的。与时间相关的规划者对这种情况都会比较熟悉:他们能够快速找到可行的解决方案,然后不断努力改进,直到时间用完为止。启发式搜索ARA算法,它根据可用的搜索时间调整其性能边界。它首先使用松散边界快速找到次优解,然后在时间允许的情况下逐渐收紧边界。如果有足够的时间,它会找到可证明的最佳解决方方案。在改进其约束的同时,ARA重复使用以前的搜索工作,因此,比其他随时搜索方法更有效。与A算法不同,Anytime A算法最重要的功能是,它们可以被停止,然后可以随时重启。该方法使用控制管理器类来处理时间限制以及停止和重新启动A算法以找到初始的,可能是次优的解决方案,然后继续搜索改进的解决方案,直到达到可证明的最佳解决方案。关于ARA的更多内容可以阅读这篇论文:ARA - Anytime A with Provable Bounds on Sub-Optimality。DD是Dynamic A的简写,其算法和A类似,不同的是,其代价的计算在算法运行过程中可能会发生变化。D包含了下面三种增量搜索算法:原始的D由Anthony Stentz发表。Focussed D由Anthony Stentz发表,是一个增量启发式搜索算法,结合了A和原始D的思想。D Lite是由Sven Koenig和Maxim Likhachev基于LPA构建的算法。所有三种搜索算法都解决了相同的基于假设的路径规划问题,包括使用自由空间假设进行规划。在这些环境中,机器人必须导航到未知地形中的给定目标坐标。它假设地形的未知部分(例如:它不包含障碍物),并在这些假设下找到从当前坐标到目标坐标的最短路径。然后机器人沿着路径行进。当它观察到新的地图信息(例如以前未知的障碍物)时,它会将信息添加到其地图中,并在必要时将新的最短路径从其当前坐标重新添加到给定的目标坐标。它会重复该过程,直到达到目标坐标或确定无法达到目标坐标。在穿越未知地形时,可能经常发现新的障碍,因此重新计划需要很快。增量(启发式)搜索算法通过使用先前问题的经验来加速搜索当前问题,从而加速搜索类似搜索问题的序列。假设目标坐标没有改变,则所有三种搜索算法都比重复的A搜索更有效。D及其变体已广泛用于移动机器人和自动车辆导航。当前系统通常基于D Lite而不是原始D或Focussed D。关于D的更多内容可以阅读这两篇文章:Project “Fast Replanning (Incremental Heuristic Search)“Real-Time Replanning in Dynamic and Unknown EnvironmentsField DField D扩展了D和D* Lite,是一种基于插值( interpolation-based )的规划算法,它使用线性插值来有效地生成低成本路径,从而消除不必要的转向。在给定线性插值假设的情况下,路径是最优的,并且在实践中非常有效。该算法目前被各种现场机器人系统使用。关于Field D的详细内容可以看下面这篇论文:Field D: An Interpolation-based Path Planner and ReplannerBlock ABlock A扩展自A,但它操作是一块(block)单元而不是单个单元。其open_set中的每个条目都是已到达但尚未扩展的块,或者需要重新扩展的块。open_set中块的优先级称为其堆值(heap value)。与A类似,Block A中的基本循环是删除具有最低堆值的条目并将其展开。在扩展期间使用LDDB来计算正在扩展的块中的边界单元的g值。LDDB是一种新型数据库,它包含了本地邻域边界点之间的距离。关于Block A的更多内容可以看下面这篇论文:Block A*: Database-Driven Search with Applications in Any-angle Path-Planning参考资料与推荐读物Stanford: Introduction to AWikipedia: A search algorithmPythonRobotics: A* algorithmARA - Anytime A with Provable Bounds on Sub-Optimality本文作者:paulquei阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

January 10, 2019 · 4 min · jiezi

用Python玩转时序数据

摘要: 本文简要介绍了如何从零开始使用Python中的时间序列。这包括对时间序列的简单定义,以及对利用pandas访问伦敦市居民智能电表所获取数据的处理。时间序列是日常生活中最常见的数据类型之一。股票价格、销售信息、气候数据、能源使用,甚至个人身高体重都是可以用来定期收集的数据样本。几乎每个数据科学家在工作中都会遇到时间序列,能够有效地处理这些数据是数据科学领域之中的一项非常重要的技能。本文简要介绍了如何从零开始使用Python中的时间序列。这包括对时间序列的简单定义,以及对利用pandas访问伦敦市居民智能电表所获取数据的处理。可以点击此处获取本文中所使用的数据。还提供了一些我认为有用的代码。让我们从基础开始,时间序列的定义是这样的:时间序列是按时间的顺序进行索引、排列或者绘制的数据点的集合。最常见的定义是,一个时间序列是在连续的相同间隔的时间点上取得的序列,因此它是一个离散时间数据的序列。时间序列数据是围绕相对确定的时间戳而组织的。因此,与随机样本相比,可能包含我们将要尝试提取的一些相关信息。加载和控制时间序列数据集让我们使用一些关于能源消耗计费的数据作为例子,以kWh(每半小时)为单位, 在2011年11月至2014年2月期间,对参与英国电力网络领导的低碳伦敦项目的伦敦居民样本数据进行分析。我们可以从绘制一些图表开始,最好了解一下样本的结构和范围,这也将允许我们寻找最终需要纠正的缺失值。对于本文的其余部分,我们只关注DateTime和kWh两列。重采样让我们从较简单的重采样技术开始。重采样涉及到更改时间序列观测的频率。特征工程可能是你对重新采样时间序列数据感兴趣的一个原因。实际上,它可以用来为监督学习模型提供额外的架构或者是对学习问题的领会角度。pandas中的重采样方法与GroupBy方法相似,因为你基本上是按照特定时间间隔进行分组的。然后指定一种方法来重新采样。让我们通过一些例子来把重采样技术描述的更具体些。我们从每周的总结开始:data.resample()方法将用于对DataFrame的kWh列数据重新取样;“W”表示我们要按每周重新取样;sum()方法用于表示在此时间段计算kWh列的总和;我们可以对每日的数据也这么做处理,并且可以使用groupby和mean函数进行按小时处理:为了进一步进行重新采样,pandas有许多内置的选项,你甚至还可以定义自己的方法。下面两个表分别显示了时间周期选项及其缩写别名和一些可能用于重采样的常用方法。其它探索这里还有一些你可以用于处理数据而进行的其它探索:用Prophet建模Facebook Prophet于2017年发布的,可用于Python,而R.Prophet是设计用于分析在不同时间间隔上显示模式的日观测时间序列。Prophet对于数据丢失情况和趋势的变化具有很强的鲁棒性,并且通常能够很好地处理异常值。它还具有高级的功能,可以模拟假日在时间序列上产生的影响并执行自定义的变更点,但我将坚持使用基本规则来启动和运行模型。我认为Prophet可能是生产快速预测结果的一个好的选择,因为它有直观的参数,并且可以由有良好领域知识背景的但缺乏预测模型的技术技能的人来进行调整。有关Prophet的更多信息,大家可以点击这里查阅官方文档。在使用Prophet之前,我们将数据里的列重新命名为正确的格式。Date列必须称为“ds”和要预测值的列为“y”。我们在下面的示例中使用了每日汇总的数据。然后我们导入Prophet,创建一个模型并与数据相匹配。在Prophet中,changepoint_prior_scale参数用于控制趋势对变化的敏感度,越高的值会更敏感,越低的值则敏感度越低。在试验了一系列值之后,我将这个参数设置为0.10,而不是默认值0.05。为了进行预测,我们需要创建一个称为未来数据框(future dataframe)的东西。我们需要指定要预测的未来时间段的数量(在我们的例子中是两个月)和预测频率(每天)。然后我们用之前创建的Prophet模型和未来数据框进行预测。非常简单!未来数据框包含了未来两个月内的预估居民使用电量。我们可以用一个图表来进行可视化预测展示:图中的黑点代表了实际值,蓝线则代表了预测值,而浅蓝色阴影区域代表不确定性。如下图所示,不确定性区域随着我们在之后的进一步变化而扩大,因为初始的不确定性随着时间的推移而扩散和增多。Prophet还可以允许我们轻松地对整体趋势和组件模式进行可视化展示:每年的模式很有趣,因为它看起来表明了居民的电量使用在秋季和冬季会增加,而在春季和夏季则会减少。直观地说,这正是我们期望要看到的。从每周的趋势来看,周日的使用量似乎比一周中其它时间都要多。最后,总体的趋势表明,使用量增长了一年,然后才缓慢地下降。需要进行进一步的调查来解释这一趋势。在下一篇文章中,我们将尝试找出是否与天气有关。LSTM(Long Short-Term Memory,长短期记忆网络)预测LSTM循环神经网络具有学习长序列观测值的前景。博客文章《了解LSTM网络》,在以一种易于理解的方式来解释底层复杂性方面做的非常出色。以下是一个描述LSTM内部单元体系结构的示意图:LSTM似乎非常适合于对时间序列的预测。让我们再次使用一下每日汇总的数据。LSTM对输入数据的大小很敏感,特别是当使用Sigmoid或Tanh这两个激活函数的时候。通常,将数据重新调整到[0,1]或[-1,1]这个范围是一个不错的实践,也称为规范化。我们可以使用scikit-learn库中的MinMaxScaler预处理类来轻松地规范化数据集。现在我们可以将已排好序的数据集拆分为训练数据集和测试数据集。下面的代码计算出了分割点的索引,并将数据拆分为多个训练数据集,其中80%的观测值可用于训练模型,剩下的20%用于测试模型。我们可以定义一个函数来创建一个新的数据集,并使用这个函数来准备用于建模的训练数据集和测试数据集。LSTM网络要求输入的数据以如下的形式提供特定的数组结构:[样本、时间间隔、特征]。数据目前都规范成了[样本,特征]的形式,我们正在为每个样本设计两个时间间隔。可以将准备好的分别用于训练和测试的输入数据转换为所期望的结构,如下所示:就是这样,现在已经准备好为示例设计和设置LSTM网络了。从下面的损失图可以看出,该模型在训练数据集和测试数据集上都具有可比较的表现。在下图中,我们看到LSTM在拟合测试数据集方面做得非常好。聚类(Clustering)最后,我们还可以使用示例的数据进行聚类。执行聚类有很多不同的方式,但一种方式是按结构层次来形成聚类。你可以通过两种方式形成一个层次结构:从顶部开始来拆分,或从底部开始来合并。我决定先看看后者。让我们从数据开始,只需简单地导入原始数据,并为某年中的某日和某日中的某一小时添加两列。Linkage和Dendrogramslinkage函数根据对象的相似性,将距离信息和对象对分组放入聚类中。这些新形成的聚类随后相互连接,以创建更大的聚类。这个过程将会进行迭代,直到在原始数据集中的所有对象在层次树中都连接在了一起。对数据进行聚类:完成了!!!这难道不是很简单吗?当然很简单了,但是上面代码中的“ward”在那里意味着什么呢?这实际上是如何执行的?正如scipy linkage文档上告诉我们的那样,“ward”是可以用来计算新形成的聚类之间距离的一个方法。关键字“ward”让linkage函数使用Ward方差最小化算法。其它常见的linkage方法,如single、complete、average,还有不同的距离度量标准,如euclidean、manhattan、hamming、cosine,如果你想玩玩的话也可以使用一下。现在让我们来看看这个称为dendogram的分层聚类图。dendogram图是聚类的层次图,其中那些条形的长度表示到下一个聚类中心的距离。如果这是你第一次看到dendrogram图,那看起来挺复杂的,但是别担心,让我们把它分解来看:在x轴上可以看到一些标签,如果你没有指定任何其它内容,那么这些标签就是X上样本的索引;·在y轴上,你可以看到那些距离长度(在我们的例子中是ward方法);水平线是聚类的合并;那些垂线告诉你哪些聚类或者标签是合并的一部分,从而形成了新的聚类;水平线的高度是用来表示需要被“桥接”以形成新聚类的距离;即使有解释说明,之前的dendogram图看起来仍然不明显。我们可以减少一点,以便能更好地查看数据。建议查找聚类文档以便能了解更多内容,并尝试使用不同的参数。本文作者:【方向】阅读原文本文为云栖社区原创内容,未经允许不得转载。

January 8, 2019 · 1 min · jiezi

2018最有用的六个机器学习项目

摘要: 用了这六个机器学习开源项目,你的项目一定进行的666!2018年又是人工智能和机器学习快速发展的一年。许多新的机器学习的项目正在以非常高的影响力影响着诸多领域,特别是医疗保健、金融、语音识别、增强现实和更复杂3D视频渲染。这一年,我们看到了更多的应用驱动研究,而不是理论研究。虽然这可能有其缺点,但它在短时间内产生了一些巨大的积极影响,产生了可以迅速转化为业务和客户创造价值的新研发,这一趋势在ML开源项目中得到了强烈反映。让我们来看看过去一年中最实用的6个ML项目。这些项目都公开发布了代码和数据集,允许个别开发人员和小型团队学习并创造价值。它们可能不是理论上最具开创性的作品,但它们很实用!Fast.aiFast.ai库的编写是为了使用现代最佳实践方法以简化且快速准确进行神经网络训练,它抽象了在实践中实施深度神经网络可能带来的所有细节工作。而且它非常易于使用,并且设计它的人有应用程序构建思维。它最初是为Fast.ai课程的学生创建的,该库以简洁易懂的方式编写在易于使用的Pytorch库之上。DetectronDetectron是Facebook AI用于物体检测和实例分割研究的研究平台,系统是用Caffe2编写。它包含各种对象检测算法的实现,包括:Mask R-CNN:使用更快的R-CNN结构的对象检测和实例分割;RetinaNet:一个基于(Feature Pyramid Network)算法的网络,具有独特的Focal Loss来处理难题;Faster R-CNN:对象检测网络最常见的结构;所有网络都可以使用以下几种可选的分类主干之一:ResNeXt {50101152};RESNET {50101152};Feature Pyramid Network(使用ResNet/ResNeXt);VGG16;更重要的是,所有上述这些模型都是带有COCO数据集上的预训练模型,因此你可以立即使用它们!他们已经在Detectron模型动物园中使用标准评估指标进行了测试。FastText这是另一个来自Facebook的研究,fastText库专为文本表示和分类而设计。它配备了预先训练的150多种语言的词向量模型,这些单词向量可用于许多任务,包括文本分类,摘要和翻译等。Auto-KerasAuto-Keras是一个用于自动机器学习(AutoML)的开源软件库。它由Texas A&M大学的DATA实验室和社区贡献者开发。AutoML的最终目标是为具有有限数据科学或机器学习背景的开发工程师提供易于访问的深度学习工具。Auto-Keras提供自动搜索深度学习模型的最佳架构和超参数的功能。DopamineDopamine是由Google基于强化学习创建的快速原型设计的研究框架,它旨在灵活且易于使用,实现标准RL算法,指标和基准。根据Dopamine的文档,他们的设计原则是:简单的测试:帮助新用户运行基准测试;灵活的开发:为新用户提供新的创新想法;可靠:为一些较旧和更流行的算法提供实现;可重复性:确保结果是可重复;vid2vidvid2vid项目是在Pytorch上实现的Nvidia最先进的视频到视频合成的模型。视频到视频合成的目标是学习从输入源视频(例如,一系列语义分割掩模)到精确描绘源视频内容的输出照片拟真视频的映射函数。这个库的好处在于它的选择:它提供了几种不同的vid2vid应用程序,包括自动驾驶/城市场景,人脸和人体姿势。它还附带了丰富的指令和功能,包括数据集加载、任务评估、训练功能和多GPU!其他一些有价值的项目:ChatterBot:用于对话引擎和创建聊天机器人的机器学习模型;Kubeflow:Kubernetes的机器学习工具包;imgaug:用于深度学习的图像增强;imbalanced-learn:scikit下的python包,专门用于修复不平衡数据集;mlflow:用于管理ML生命周期的开源平台:包括测试,可重复性和部署;AirSim:基于虚幻引擎/Unity的自动驾驶汽车模拟器,来自Microsoft AI和Research;本文作者:【方向】阅读原文本文为云栖社区原创内容,未经允许不得转载。

January 7, 2019 · 1 min · jiezi

十余位权威专家深度解读,达摩院2019十大科技趋势点燃科技热情

2019年的第一个工作日,阿里巴巴达摩院重磅发布了2019十大科技趋势,引发社会各界对未来科技的讨论和向往。这一发布同样引来科学界的普遍关注。来自包括中科院、清华大学、佛罗里达大学、杜克大学等权威学术机构的十余位专家就此发表评论,深度点评达摩院提出的观点,充分肯定达摩院在基础科研领域持续深耕的专注精神。专家普遍认为,达摩院发布的科技趋势虽然有十个方向,但都是围绕着当前科学发展的几个关键潮流,即以芯片为代表的算力、以图计算为代表的算法以及以5G为代表的连接能力。一、计算是变革的源头传统时代的计算始终在冯诺伊曼架构约束下发展,但人工智能的到来正在挑战冯诺依曼架构,而摩尔定律也接近失效,新型芯片以及新的计算机架构已经成为整个行业研究重心。达摩院认为,计算体系结构正在被重构,基于FPGA、ASIC等计算芯片的异构计算架构正在对以CPU为核心的通用计算发起冲击。“通过推高通用芯片的性能来征服一切的方式已经失效。” 中国科学院计算技术研究所研究员陈天石对此评论说,“学术界和工业界都把目光投向了更加专用的处理器架构,并且一直在期待新器件引发的新的架构演进。”杜克大学副教授、IEEE Fellow陈怡然也表示,目前学术界的研究重心在一些更为革命性的架构研究,例如内存计算、非冯诺依曼架构、神经形态计算等。而佛罗里达大学杰出教授、IEEE Fellow李涛则指出,计算体系结构的变革将主导和引领ICT领域的持续创新和发展,这将是未来产业界的核心竞争力。在人工智能领域,GPU无疑是最受企业以及开发者追捧的芯片。但达摩院认为,数据中心的AI训练场景下,计算和存储之间数据搬移已成为瓶颈,AI专用芯片将挑战GPU的绝对统治地位。“对于训练场景来说,计算量要求非常高,需要存储和处理的数据量远远大于之前常见的应用,AI专用计算架构是最佳选择。” 清华大学微纳电子系副系主任尹首一对达摩院的这一观点表示认可。根据达摩院的判断,AI专用芯片的应用将成为趋势。在2018年的杭州云栖大会上,阿里巴巴曾宣布首款AI芯片AliNPU将于2019年应用于城市大脑和自动驾驶等云端数据场景中。陈天石指出,“AI芯片可以灵活高效地支持视觉、语音和自然语言处理,甚至传统的机器学习应用,将在数据中心场景发挥重要作用。”二、算法的创新让AI更加智能1950年,人工智能之父图灵提出著名的图灵测试用以检验人工智能能力,即如果有超过30%的测试者不能确定被测试者是人还是机器人,则认为是通过测试。图灵提出的猜想可能将会很快实现。达摩院认为,在未来,人类可能无法辨别人工智能生成的语音和真人语音,具备语音交互能力的公共设施将会越来越多,甚至在一些特定对话测试中机器可以通过图灵测试。西北工业大学计算机学院教授谢磊对此表示,“声音合成技术在某些方面已经可以媲美人声,并将会拉动‘耳朵经济’的爆发,各种‘AI声优’ 将上岗,为大家提供听觉盛宴。”人工智能行业的迅速发展与深度学习带来的突破高度相关,但仅靠深度学习要实现通用人工智能仍然困难重重。达摩院认为,结合深度学习的图神经网络将让机器成为具备常识、具有理解、认知能力的AI。杜克大学统计学院终身教授David Dunson对此评论说,“结合了深度学习的图计算方法将实现推荐系统的变革性改进,为用户提供更有趣和更合适的产品,同时改善整体用户体验。”过去两年,城市大脑成为社会热词。达摩院认为,2019年,人工智能将在城市大脑技术和应用的研发中发挥更大作用,未来越来越多的城市将拥有大脑。中国城市规划设计院院长杨保军认为,“城市大脑将不再是单一领域或是单项要素的智慧,而是全局联动、多源交融的智慧。”同济大学智能交通运输系统研究中心主任杨晓光则表示,“新一代城市智能管理、智能服务与智能决策将帮助人类最大程度地预防和综合治理城市病。”三、连接万物的5G催生更多应用场景过去几年,5G的热度并不逊于人工智能。5G构建的不仅是一张人联网,它将会成为连接万物的纽带。达摩院在此次十大科技趋势中提到,5G将催生超高清视频、AR/VR等场景的成熟。中国信通院副总工、工信部信息通信经济专家委员会秘书长陈金桥对此评论说,“5G将掀开数据资源作为生产力的大幕,一个基于泛在高速连接的智能社会必将形成。” 车路协同将会是5G与人工智能两大技术交融的典型场景。达摩院认为,车路协同技术路线会加快无人驾驶的到来,并且将在固定线路公交、无人配送、园区微循环等商用场景将快速落地。单纯依靠“单车智能”的方式革新汽车存在诸多限制,例如传感器部署的成本高,感知系统以及决策系统的可靠性低等。“车路协同的优势在于,可降低单车系统在定位方案部署上的成本,并且可以实现更好的感知与决策。” 中科院自动化研究所研究员赵冬斌如此表示。本文作者:阿里云头条阅读原文本文为云栖社区原创内容,未经允许不得转载。

January 3, 2019 · 1 min · jiezi

软件测试工程师的技能树

软件测试工程师是一个历史很悠久的职位,可以说从有软件开发这个行业以来,就开始有了软件测试工程师的角色。随着时代的发展,软件测试工程师的角色和职责也在悄然发生着变化,从一开始单纯的在瀑布式开发流程中担任测试阶段的执行者,到敏捷开发流程中QA(Quality Assurance)角色,为整个团队和产品的质量负责,测试工程师的职责和边界不断的扩大。近年来互联网行业的很多测试工程师被称为是测试开发工程师,也就是要具备自动化测试和测试工具开发能力的测试工程师,可以说是对测试工程师的能力要求达到了一个新的高度。相信有过测试工作经验的同学都会深有体会,不管是瀑布式还是agile模式,测试人员的工作总是被压在产品发布的最后阶段,整个团队的压力似乎都压在测试工程师身上,没有人会理会开发过程中产生的延误,因为那已经过去,可以在retro meeting的时候diss,但是目前最重要的问题是完成产品的发布上线。所以在寻找测试工程师需要什么技能之前,测试工程师的核心问题是什么,这是我们要搞清楚的。测试工程师面临的核心问题如何以最小的投入,最大程度保证产品的质量这个问题相信大家都有所体会,商业社会追求的就是效率,甚至是极致的效率。测试工程师也不能例外,不管是叫测试工程师,QA,或者是听着高大上的测试开发工程师,其实老板们的目标是一致的,就是在尽可能少的投入,最大程度保证产品的质量。说得现实一点,你的薪资水平就取决于你能解决这个核心问题的能力。明确了我们的目标,我们所需要的能力,也是围绕着这一个目标来设定的。概述按照笔者的经验和理解,一个软件测试工程师需要具备以下的技能:测试设计能力代码能力自动化测试技术质量流程管理行业技术知识数据库业务知识测试设计作为一名测试工程师,最基础的能力应该就是根据产品来设计测试用例的能力。最基础的能力往往也是最难做到精通的能力。要设计好的测试用例,需要对产品的特性和业务非常的熟悉,对用户的使用场景有着系统化的思考。除此之外,还有一些科学的测试用例设计方法可以帮助我们设计规范化的用例,而不是仅仅根据经验或者天马行空的想法来设计用例。业界有一些经典的测试用例设计方法需要测试工程师掌握:边界值分析等价类划分因果图判定表正交实验设计上述的这些方法并不是教条,而是帮助我们理清测试用例设计的思路和提高效率的工具。代码能力在传统的思维中,对测试人员的代码能力要求似乎不是很高,在业界确实也是这样的。很多测试工程师基本上不具备代码的能力,更多是测试的执行者。但是在当今这个时代下,要想突破传统功能测试人员的天花板,代码能力是必须的。具备代码能力的测试工程师有这样两个优势:阅读开发代码如果能够具备阅读开发代码的能力,对于提高测试人员的效率是很有帮助的,它可以帮助我们做到这些一些事情通过开发修改的代码预估影响的范围,即测试的范围参加技术评审,预估测试的风险,难点,重点通过代码的逻辑设计测试用例,强化测试用例的覆盖程度对缺陷进行初步的定位其实可以做到的事情还有很多,体现在测试过程的很多细节当中自动化测试的开发自动化测试是测试发展的方向,也是提高效率的有效方法。具备了代码能力,你可以轻松的驾驭各种流行的自动化测试框架和用例开发。自动化测试接着上面关于自动化测试的讨论。在目前的热门公司的招聘中,自动化能力已经是必备的能力,也是大家很关注的一个领域。目前可以粗略的把自动化测试分为这么几类:UI自动化UI自动化实现的目标是模拟人在产品UI界面上的操作,从而观察结果来完成测试的执行。UI自动化也可以从客户端的形态上分为PC端和移动端的自动化测试,有这样一些著名的自动化工具需要我们掌握:SeleniumSelenium是一个很经典的WEB端产品的UI自动化工具,针对不同的开发语言都有很好的支持。它的原理简单来说就是通过WebDriver把脚本产生的操作指令传递到浏览器,执行我们需要的操作并且获取相应的反馈,在脚本中完成校验。Appium从这个名字就可以看出这个工具和Selenium的相似之处。其实Appium可以理解为就是移动端的Selenium。同样也是在移动端模拟人的操作来实现执行测试用例的目的。随着移动互联网时代的到来,更多的业务已经从PC的WEB端转移到了移动端,移动端的自动化测试越来越重要。其实UI的自动化实现的原理都是很类似的,基本的逻辑都是:定位元素操作元素获取反馈最后通过某种测试用例框架来管理测试用例,例如python的unittest,JAVA的TestNG,Ruby的respec等等。所以说了解了某一种UI自动化的框架和工具,很容易的就能触类旁通的学习新的框架和工具。接口自动化在目前SaaS成为主流的情况下,API,即接口,成为了支撑业务的核心部分。前端页面和App里面的业务数据都是通过各种API与服务器进行通信,从而实现业务功能。目前大多数的接口都是基于HTTP协议的,其中Restful的接口又占大多数。而很多语言,例如Python和Ruby都有很好的库来支持HTTP协议的请求,这就为我们设计接口自动化提供了很好的基础。回到我们的核心问题,投入产出比的衡量。UI的自动化无论是从实现的成本还是维护的成本来说都是巨大的,所以业界越来越把重心放到了接口层的自动化实现上。接口的自动化具备这样的优势:运行效率高开发成本低维护成本低可以与开发代码同步开发接口自动化的实现思路也是简单明了的,那就是模拟浏览器,发送HTTP请求来实现对接口的调用,然后比较返回与期望值,达到验证结果的目的。当然,要设计一套真正高效的接口自动化框架也是不容易的。这里面涉及到如何提高用例的开发效率,降低开发维护成本等关键问题。同时还可以把接口测试与性能测试结合起来,丰富接口自动化测试的内涵。质量管理流程在敏捷开发的流程中,测试工程师有了一个新的定义:Quality Assurance Engineer。而测试的执行仅仅是职责中的一部分,更为重要的是要为整个团队的产品质量负责。从整个sprint的周期来看,QA工程师都要始终如一的贯彻质量保证的意识,与开发的关系也从早期的发现bug,转变为如何帮助开发团队一起提高产品的质量。同时还要和产品团队密切的合作,在需求的分析阶段就介入,分析质量保证工作如何规划和设计,而不是在产品发布前的测试执行阶段才介入。这个里面还包含很多Soft skill的要求,包括如何与团队合作,沟通等等,这也是敏捷开发模式的关键之一。行业技术知识这一部分内容其实涵盖的内容是非常丰富的,就以互联网行业举例吧。对于一个互联网产品,测试工程师需要了解的甚至是精通的知识是很多的,从前端页面的技术栈,API的设计,后端服务器的设计,后面会专门提到的数据库,还有整个服务的架构等等,测试工程师都需要有所了解。针对这个问题,其实有一个非常好的问题可以帮助大家去梳理涉及到的知识,这就是:从在浏览器的输入框输入一个网址,到看到网页的内容,这个过程中发生了什么?回答这个问题的深度和广度,基本就能反映一个测试工程师对于互联网产品技术的掌握情况。在这里呢,我简单的罗列一些涉及到的技术和概念,这些内容对于我们测试产品,都是非常有帮助的。DNSTCP/IPHTTPSSLRestfulHTMLDOMCSSRenderXpath服务器nginxSQLCookie&SessionXSS,CSRF这里仅仅是涉及到一部分内容,具体的内容可以根据工作中遇到的场景去深入学习和了解。数据库之所以把数据库单独列出来,是因为数据库的知识对于当今的很多产品都是非常核心的内容。不管是在手动测试还是自动化测试中,都有需要到数据库进行数据校验的时候。目前主要使用的数据库可以分为两类:关系型数据库非关系型数据库关系型数据库关系型数据库是最常见的数据库类型,这类数据库通过RDBMS数据库程序来进行管理和使用,常见的有SQL Server, MySQL等等。关系型数据库中强调一个事务(Transaction)的概念。所谓事务是用户定义的一个数据库操作系列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。例如在关系数据库中,一个事务可以是一条SQL语句、一组SQL语句或整个程序。事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。持久性(Durability):一个事务一旦提交,他对数据库的修改应该永久保存在数据库中。对于实际的应用来说,SQL语言是必须要掌握的。能够通过SQL语句在数据库中找到需要的数据,是测试工程师必备的技能。SQL语句的语法大体上比较类似,在一些细节上不同的RDBMS会有些许的差别。对于自动化实现来说,在自动化测试中通过访问数据库来获得期望值也是很常见的场景。不同的语言都有访问数据库的库,整体来说应用也很简单。非关系型数据库随着互联网中大量的非结构化数据的产生,例如社交网络等等应用,用户的个人信息,社交网络,地理位置,用户生成的数据和用户操作日志已经正在以几何级数的速率增加,同时还面临大量的数据挖掘工作,传统的关系型数据库已经无法满足。所以NoSQL渐渐的发展了起来。NoSQL最突出的特点就是数据的非结构化,通俗的讲,就是数据不再是以列和行这样的形式存储的。NoSQL存储数据的方式很多:值对存储,列存储,文档存储。例如比较常见的MongoDB就是将数据存储为一个文档,数据结构由键值(key=>value)对组成。MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。RDBMS vs NoSQLRDBMS高度组织化结构化数据结构化查询语言(SQL) (SQL)数据和关系都存储在单独的表中。数据操纵语言,数据定义语言严格的一致性基础事务NoSQL代表着不仅仅是SQL没有声明性查询语言没有预定义的模式:键 - 值对存储,列存储,文档存储,图形数据库最终一致性,而非ACID属性非结构化和不可预知的数据CAP定理高性能,高可用性和可伸缩性业务知识对于测试工程师来说,所测试产品的业务知识也是非常重要的。一个测试工程师可能已经具备了上述的所有技能,但是怎么把这些技能用来解决我们最先提到的软件测试的核心问题呢?这个里面的关键,或者说中心点,就是你所测试的产品的业务。测试的方法,规划,实施方法都是多种多样的,如果在这些方法中进行选择,所依赖的正是对产品的业务的深刻理解。这里的产品业务不仅仅指产品的特性,同时还包括了产品的用户特征,用户的使用习惯,以及由此带来的对产品的流量趋势。也可以说,测试人员必须要站在用户的角度来分析产品,而不是产品开发人员的角度。测试人员还需要找到产品的核心功能和核心业务,通过这样的分析来进行测试优先级的划分,以及缺陷的定级。同时对于自动化测试的规划和架构也有着重要的影响。例如在自动化测试中要首先覆盖那些核心的业务和功能,同时根据业务的特性,用自动化的方法去模拟用户的使用场景,把有限的自动化资源投入到最关键的部分。这一块技能听起来可能很虚,好像没有什么具体的知识点,但是在不断的工作和总结中,优秀的测试工程师是能够总结出一套符合某一类产品的测试方法的,甚至还可以提炼出一些更具备通用性的best practice,用到不同的产品中。说在最后或者这样一篇短短的文章无法涵盖软件测试的内涵,但是笔者也只是想抛砖引玉,让读者能够通过这样一种不能算全面的梳理,结合自己的工作经验,对自己所从事的软件测试工作有一个更深的理解。笔者计划根据这篇文章所列出的技能树,分别写文章进行更加细致的梳理和总结,希望能够和各位同行一起学习,一起进步,同时非常欢迎大家指正我的错误和不足。

January 1, 2019 · 1 min · jiezi

使用java+TestNG进行接口回归测试

TestNG是一个开源自动化测试框架,TestNG表示下一代(Next Generation的首字母)。 TestNG类似于JUnit(特别是JUnit 4),但它不是JUnit框架的扩展,相较于Junit而言,功能更强大,使用起来更加方便,比较适合测试人员来进行集成测试或是接口回归测试。TestNG有以下几大特点:使用java和面向对象的功能;方法的名称就不必受限于某种固定的格式,可以通过注释来识别需要执行的方法;方法中需要的一些参数可以通过注释传递;注释是强类型的,所以有错误可以在编译期体现出来;支持分组测试,依赖测试,并行测试,负载测试等;支持多线程测试。TestNG常用的注释类型注释描述@Test将类或是方法标记为测试的一部分@BeforeSuite在该套件的所有测试都运行在注释的方法之前,仅运行一次。@AfterSuite在该套件的所有测试都运行在注释的方法之后,仅运行一次@BeforeClass在调用当前类的所有测试方法之前执行,注释方法仅运行一次@AfterClass在调用当前类的所有测试方法之后执行,注释方法仅运行一次@BeforeTest注释的方法将在属于<test>标签内的类的所有测试方法运行之前运行@AfterTest注释的方法将在属于<test>标签内的类的所有测试方法运行之后运行。@BeforeGroups此方法是保证在运行属于任何这些组的第一个测试方法之前,该方法被调用@AfterGroups此方法是保证运行属于任何这些组的所有测试方法执行之后,该方法被调用@BeforeMethod被注释的方法将在每个测试方法之前执行@AfterMethod被注释的方法将在每个测试方法之后执行@DataProvider被注释的方法的作用是提供测试数据,如果某个测试方法希望从这个DataProvider接收数据,就必须使用一个名字等于这个注解名字的DataProvider@Parameters介绍如何将参数传递给测试方法TestNG参数化测试TestNG提供了2种传递参数的方式。第一种: testng.xml这种方式的优点是使得代码和测试数据分离,方便维护;缺点就是如果需要传递的参数不是基本的java类型,或是需要的值只能在运行时创建,这种方法就不再适用。第二种:@DataProvider这种能够提供比较复杂的参数(也叫data-driven testing)。我们项目中使用的是第二种如图示:首先给测试方法添加值为WithdrawBatchQueryLoopData的属性dataProvider,然后提供一个name与之对应的@DataProvider方法,这个方法里的具体实现,就是从配置文件或是数据库中读取数据。TestNG分组测试分组测试是TestNG中的创新功能,分组测试使得我们可以进行各种灵活的测试,在想要并行多组不同的测试时,不需要重新进行编译。分组可以应用在方法上(一个方法可以属于一到多个分组),也可以应用在类上,应用在类上时,这个类中的所有public方法都变成测试方法,即便他们没有被注解,也可以继续在需要增加属性的方法上重复@Test注解。分组执行测试方法有多种形式:可以通过在testng.xml配置,来指定具体要执行的分组也可以通过集成到jenkins中,通过参数化构建来设置要执行的分组还可以通过运行Run Configurations时,设置要执行的分组需要注意的一点:Group标签会导致@BeforeMethod失效@BeforeMethod的作用是标明所注解的方法在每一个测试方法运行之前会执行一次。例如:@BeforeMethodPublic void beforeMehod()@TestPublic void testCase1()@TestPublic void testCase2()正常的执行的顺序为:beforeMehod—> testCase1—> beforeMehod—> testCase2但是,在将testCase放入某一个Group之后,@BeforeMethod就失效了@BeforeMethodPublic void beforeMehod()@Test(groups = “group1”)Public void testCase1()@Test(groups = “group1”)Public void testCase2()再次执行脚本,执行顺序变为testCase1—>testCase2解决方法:1、将@BeforeMethod同样加入group12、设置@BeforeMethod的属性alwaysRun=trueTestNG依赖测试我们经常会遇到要测试的多个接口之间存在依赖关系,即某一个接口的执行需要依赖上一个接口的返回结果,比如执行批付查询时,需要先执行批量代付,那么这时就使用到了TestNG的依赖测试,TestNG使用dependsOnMethods配合alwaysRun来设置测试方法之间的依赖关系,使用dependsOnGroups来设置分组之间的依赖关系强依赖:在测试方法运行之前,所有的依赖方法都必须运行并且成功,哪怕只有一个失败,测试方法都不会被调用(是skip而不是fail)软依赖(alwaysRun=true):测试方法在依赖方法运行之后总是会被执行,即便某些依赖方法运行失败。TestNG预期异常测试测试中,有时候我们期望某些代码抛出异常。TestNG通过@Test(expectedExceptions) 来判断期待的异常, 并且判断Error MessageTestNG测试报告执行完测试用例之后,会在项目的test-output(默认目录)下生成测试报告打开index.html文件可以看到测试结果摘要,包括:套件名、测试用例成功数、测试用例失败数、测试用例忽略数、执行时间和testng.xml文件。测试用例都成功的话,测试结果以绿底标志,测试用例有失败的话,测试结果以红底标志。testNG自带生成的测试报告不太美观,可以使用testng-xslt进行美化。来源:宜信技术学院 作者:王海燕

December 26, 2018 · 1 min · jiezi

以太坊构建DApps系列教程(四):Story DAO的白名单和测试

在本系列关于使用以太坊构建DApps教程的第3部分中,我们构建并将我们的代币部署到以太坊测试网络Rinkeby。在这部分中,我们将开始编写Story DAO代码。我们将使用第1部分中列出的条件来做指导。合约大纲让我们用这个骨架创建一个新的合约StoryDao.sol:pragma solidity ^0.4.24;import “../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol”;import “../node_modules/openzeppelin-solidity/contracts/ownership/Ownable.sol”;contract StoryDao is Ownable { using SafeMath for uint256; mapping(address => bool) whitelist; uint256 public whitelistedNumber = 0; mapping(address => bool) blacklist; event Whitelisted(address addr, bool status); event Blacklisted(address addr, bool status); uint256 public daofee = 100; // hundredths of a percent, i.e. 100 is 1% uint256 public whitelistfee = 10000000000000000; // in Wei, this is 0.01 ether event SubmissionCommissionChanged(uint256 newFee); event WhitelistFeeChanged(uint256 newFee); uint256 public durationDays = 21; // duration of story’s chapter in days uint256 public durationSubmissions = 1000; // duration of story’s chapter in entries function changedaofee(uint256 _fee) onlyOwner external { require(_fee < daofee, “New fee must be lower than old fee.”); daofee = _fee; emit SubmissionCommissionChanged(_fee); } function changewhitelistfee(uint256 _fee) onlyOwner external { require(_fee < whitelistfee, “New fee must be lower than old fee.”); whitelistfee = _fee; emit WhitelistFeeChanged(_fee); } function lowerSubmissionFee(uint256 _fee) onlyOwner external { require(_fee < submissionZeroFee, “New fee must be lower than old fee.”); submissionZeroFee = _fee; emit SubmissionFeeChanged(_fee); } function changeDurationDays(uint256 _days) onlyOwner external { require(_days >= 1); durationDays = _days; } function changeDurationSubmissions(uint256 _subs) onlyOwner external { require(_subs > 99); durationSubmissions = _subs; }}我们正在导入SafeMath以便再次进行安全计算,但这次我们还使用了Zeppelin的Ownable合约,该合约允许某人“拥有”故事并执行某些仅限管理员的功能。简单地说我们的StoryDao is Ownable就够了;随时检查合约,看看它是如何工作的。我们还使用此合约中的onlyOwner修饰符。函数修饰符基本上是函数扩展和插件。onlyOwner修饰符如下所示:modifier onlyOwner() { require(msg.sender == owner); ;}当onlyOwner被添加到一个函数中时,那个函数的体被粘贴到;所在的部分,并且它比其他的一切内容都先执行。因此,通过使用此修饰符,该函数会自动检查邮件发件人是否也是合约的所有者,然后照常继续(如果是)。如果没有,它会崩溃。通过在改变我们的Story DAO的费用和其他参数的函数上使用onlyOwner修饰符,我们确保只有管理员才能进行这些更改。测试让我们测试一下初始函数。如果文件夹test不存在,请创建它。然后在其中创建文件TestStoryDao.sol和TestStoryDao.js。因为在Truffle中没有本地方法来测试异常,所以也可以使用以下内容创建helpers/expectThrow.js:export default async promise => { try { await promise; } catch (error) { const invalidOpcode = error.message.search(‘invalid opcode’) >= 0; const outOfGas = error.message.search(‘out of gas’) >= 0; const revert = error.message.search(‘revert’) >= 0; assert( invalidOpcode || outOfGas || revert, ‘Expected throw, got '’ + error + ‘' instead’, ); return; } assert.fail(‘Expected throw not received’); };注意:Solidity测试通常用于测试基于合约的低级函数,即智能合约的内部。JS测试通常用于测试合约是否可以与外部进行正确的交互,这是我们最终用户将要做的事情。在TestStoryDao.sol,输入以下内容:pragma solidity ^0.4.24;import “truffle/Assert.sol”;import “truffle/DeployedAddresses.sol”;import “../contracts/StoryDao.sol”;contract TestStoryDao { function testDeploymentIsFine() public { StoryDao sd = StoryDao(DeployedAddresses.StoryDao()); uint256 daofee = 100; // hundredths of a percent, i.e. 100 is 1% uint256 whitelistfee = 10000000000000000; // in Wei, this is 0.01 ether uint256 durationDays = 21; // duration of story’s chapter in days uint256 durationSubmissions = 1000; // duration of story’s chapter in entries Assert.equal(sd.daofee(), daofee, “Initial DAO fee should be 100”); Assert.equal(sd.whitelistfee(), whitelistfee, “Initial whitelisting fee should be 0.01 ether”); Assert.equal(sd.durationDays(), durationDays, “Initial day duration should be set to 3 weeks”); Assert.equal(sd.durationSubmissions(), durationSubmissions, “Initial submission duration should be set to 1000 entries”); }}这将检查StoryDao合约是否正确部署,并提供正确的费用和持续时间。第一行确保通过从已部署地址列表中读取它来部署它,并且最后一节做了一些断言——检查声明是真还是假。在我们的例子中,我们将数字与已部署合约的初始值进行比较。每当它为“true”时,Assert.equals部分将发出一个“True”的事件,这是Truffle在测试时正在监听的事件。在TestStoryDao.js,输入以下内容:import expectThrow from ‘./helpers/expectThrow’;const StoryDao = artifacts.require(“StoryDao”);contract(‘StoryDao Test’, async (accounts) => { it(“should make sure environment is OK by checking that the first 3 accounts have over 20 eth”, async () =>{ assert.equal(web3.eth.getBalance(accounts[0]).toNumber() > 2e+19, true, “Account 0 has more than 20 eth”); assert.equal(web3.eth.getBalance(accounts[1]).toNumber() > 2e+19, true, “Account 1 has more than 20 eth”); assert.equal(web3.eth.getBalance(accounts[2]).toNumber() > 2e+19, true, “Account 2 has more than 20 eth”); }); it(“should make the deployer the owner”, async () => { let instance = await StoryDao.deployed(); assert.equal(await instance.owner(), accounts[0]); }); it(“should let owner change fee and duration”, async () => { let instance = await StoryDao.deployed(); let newDaoFee = 50; let newWhitelistFee = 1e+10; // 1 ether let newDayDuration = 42; let newSubsDuration = 1500; instance.changedaofee(newDaoFee, {from: accounts[0]}); instance.changewhitelistfee(newWhitelistFee, {from: accounts[0]}); instance.changedurationdays(newDayDuration, {from: accounts[0]}); instance.changedurationsubmissions(newSubsDuration, {from: accounts[0]}); assert.equal(await instance.daofee(), newDaoFee); assert.equal(await instance.whitelistfee(), newWhitelistFee); assert.equal(await instance.durationDays(), newDayDuration); assert.equal(await instance.durationSubmissions(), newSubsDuration); }); it(“should forbid non-owners from changing fee and duration”, async () => { let instance = await StoryDao.deployed(); let newDaoFee = 50; let newWhitelistFee = 1e+10; // 1 ether let newDayDuration = 42; let newSubsDuration = 1500; await expectThrow(instance.changedaofee(newDaoFee, {from: accounts[1]})); await expectThrow(instance.changewhitelistfee(newWhitelistFee, {from: accounts[1]})); await expectThrow(instance.changedurationdays(newDayDuration, {from: accounts[1]})); await expectThrow(instance.changedurationsubmissions(newSubsDuration, {from: accounts[1]})); }); it(“should make sure the owner can only change fees and duration to valid values”, async () =>{ let instance = await StoryDao.deployed(); let invalidDaoFee = 20000; let invalidDayDuration = 0; let invalidSubsDuration = 98; await expectThrow(instance.changedaofee(invalidDaoFee, {from: accounts[0]})); await expectThrow(instance.changedurationdays(invalidDayDuration, {from: accounts[0]})); await expectThrow(instance.changedurationsubmissions(invalidSubsDuration, {from: accounts[0]})); })});为了使我们的测试成功运行,我们还需要告诉Truffle我们想要部署StoryDao——因为它不会为我们做。因此,让我们在migrations创建3_deploy_storydao.js,其内容几乎与我们之前编写的迁移相同:var Migrations = artifacts.require("./Migrations.sol");var StoryDao = artifacts.require("./StoryDao.sol");module.exports = function(deployer, network, accounts) { if (network == “development”) { deployer.deploy(StoryDao, {from: accounts[0]}); } else { deployer.deploy(StoryDao); }};此时,我们还应该在项目文件夹的根目录中更新(或创建,如果它不存在)package.json文件,其中包含我们目前所需的依赖项,并且可能在不久的将来需要:{ “name”: “storydao”, “devDependencies”: { “babel-preset-es2015”: “^6.18.0”, “babel-preset-stage-2”: “^6.24.1”, “babel-preset-stage-3”: “^6.17.0”, “babel-polyfill”: “^6.26.0”, “babel-register”: “^6.23.0”, “dotenv”: “^6.0.0”, “truffle”: “^4.1.12”, “openzeppelin-solidity”: “^1.10.0”, “openzeppelin-solidity-metadata”: “^1.2.0”, “openzeppelin-zos”: “”, “truffle-wallet-provider”: “^0.0.5”, “ethereumjs-wallet”: “^0.6.0”, “web3”: “^1.0.0-beta.34”, “truffle-assertions”: “^0.3.1” }}和.babelrc文件的内容:{ “presets”: [“es2015”, “stage-2”, “stage-3”]}我们还需要在我们的Truffle配置中要求Babel,因此它知道它应该在编译时使用它。注意:Babel是NodeJS的一个附加组件,它允许我们在当前一代NodeJS中使用下一代JavaScript,因此我们可以编写诸如import。如果这超出了你的理解范围,只需忽略它,然后只需逐字粘贴即可。在以这种方式安装后,你可能永远不必再处理这个问题。require(‘dotenv’).config();================== ADD THESE TWO LINES ================require(‘babel-register’);require(‘babel-polyfill’);=======================================================const WalletProvider = require(“truffle-wallet-provider”);const Wallet = require(’ethereumjs-wallet’);// …现在,终于进行truffle test。输出应该类似于这个:有关测试的更多信息,请参阅本教程,该教程专门用于测试智能合约。在本课程的后续部分中,我们将跳过测试,因为输入它们会使教程太长,但请参考项目的最终源代码来检查它们。我们刚刚完成的过程已经设置了测试环境,因此你可以在进一步设置的情况下编写测试。白名单现在让我们构建一个白名单机制,让用户参与构建Story。将以下函数框架添加到StoryDao.sol:function whitelistAddress(address _add) public payable { // whitelist sender if enough money was sent}function() external payable { // if not whitelisted, whitelist if paid enough // if whitelisted, but X tokens at X price for amount}未命名的函数function()被称为回调函数,这是在没有特定指令的情况下将钱发送到此合约时被调用的函数(即,没有专门调用其他函数)。这可以让人们加入StoryDao,只需将以太发送到DAO并立即将其列入白名单,或者购买代币,具体取决于它们是否已经列入白名单。whitelistSender功能用于白名单,可以直接调用,但是如果发送方尚未列入白名单,我们将确保当收到一些以太时,后备功能会自动调用它。whitelistAddress函数被声明为public因为它也应该可以从其他合约中调用,并且回调函数是external函数,因为money将仅从外部地址转到此地址。调用此合约的合约可以直接轻松调用所需的功能。让我们首先处理回调函数。function() external payable { if (!whitelist[msg.sender]) { whitelistAddress(msg.sender); } else { // buyTokens(msg.sender, msg.value); }}我们检查发件人是否已经在白名单中,并将调用委托给whitelistAddress函数。请注意,我们已经注释掉了buyTokens函数,因为我们还没有它。接下来,让我们处理白名单。function whitelistAddress(address _add) public payable { require(!whitelist[_add], “Candidate must not be whitelisted.”); require(!blacklist[_add], “Candidate must not be blacklisted.”); require(msg.value >= whitelistfee, “Sender must send enough ether to cover the whitelisting fee.”); whitelist[_add] = true; whitelistedNumber++; emit Whitelisted(_add, true); if (msg.value > whitelistfee) { // buyTokens(_add, msg.value.sub(whitelistfee)); }}请注意,此函数接受地址作为参数,并且不从消息中提取它(来自交易)。如果有人无法承担加入DAO的费用,这还有一个额外的好处,即人们可以将其他人列入白名单。我们通过一些健壮性检查启动该功能:发件人不得列入白名单或列入黑名单(禁止),并且必须已发送足够的费用以支付费用。如果这些条件令人满意,则将地址添加到白名单中,发出白名单事件,最后,如果发送的以太数量大于覆盖白名单费用所需的以太数量,则剩余部分用于买这些代币。注意:我们使用sub而不是-来减,因为这是一个安全计算的SafeMath函数。用户现在可以将自己或其他人列入白名单,只要他们向StoryDao合约发送0.01以太或更多。结论我们在本教程中构建了DAO的初始部分,但还有很多工作要做。请继续关注:在下一部分中,我们将处理为Story添加内容的问题!======================================================================分享一些以太坊、EOS、比特币等区块链相关的交互式在线编程实战教程:java以太坊开发教程,主要是针对java和android程序员进行区块链以太坊开发的web3j详解。python以太坊,主要是针对python工程师使用web3.py进行区块链以太坊开发的详解。php以太坊,主要是介绍使用php进行智能合约开发交互,进行账号创建、交易、转账、代币开发以及过滤器和交易等内容。以太坊入门教程,主要介绍智能合约与dapp应用开发,适合入门。以太坊开发进阶教程,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。C#以太坊,主要讲解如何使用C#开发基于.Net的以太坊应用,包括账户管理、状态与交易、智能合约开发与交互、过滤器和交易等。EOS教程,本课程帮助你快速入门EOS区块链去中心化应用的开发,内容涵盖EOS工具链、账户与钱包、发行代币、智能合约开发与部署、使用代码与智能合约交互等核心知识点,最后综合运用各知识点完成一个便签DApp的开发。java比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Java代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Java工程师不可多得的比特币开发学习课程。php比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Php代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Php工程师不可多得的比特币开发学习课程。tendermint区块链开发详解,本课程适合希望使用tendermint进行区块链开发的工程师,课程内容即包括tendermint应用开发模型中的核心概念,例如ABCI接口、默克尔树、多版本状态库等,也包括代币发行等丰富的实操代码,是go语言工程师快速入门区块链开发的最佳选择。汇智网原创翻译,转载请标明出处。这里是原文以太坊构建DApps系列教程(四):Story DAO的白名单和测试 ...

December 26, 2018 · 4 min · jiezi

Dubbo压测插件的实现——基于Gatling

Dubbo 压测插件已开源,本文涉及代码详见gatling-dubboGatling 是一个开源的基于 Scala、Akka、Netty 实现的高性能压测框架,较之其他基于线程实现的压测框架,Gatling 基于 AKKA Actor 模型实现,请求由事件驱动,在系统资源消耗上低于其他压测框架(如内存、连接池等),使得单台施压机可以模拟更多的用户。此外,Gatling 提供了一套简单高效的 DSL(领域特定语言)方便我们编排业务场景,同时也具备流量控制、压力控制的能力并提供了良好的压测报告,所以有赞选择在 Gatling 基础上扩展分布式能力,开发了自己的全链路压测引擎 MAXIM。全链路压测中我们主要模拟用户实际使用场景,使用 HTTP 接口作为压测入口,但有赞目前后端服务中 Dubbo 应用比重越来越高,如果可以知道 Dubbo 应用单机水位将对我们把控系统后端服务能力大有裨益。基于 Gatling 的优势和在有赞的使用基础,我们扩展 Gatling 开发了 gatling-dubbo 压测插件。插件主要结构实现 Dubbo 压测插件,需实现以下四部分内容:Protocol 和 ProtocolBuild协议部分,这里主要定义 Dubbo 客户端相关内容,如协议、泛化调用、服务 URL、注册中心等内容,ProtocolBuild 则为 DSL 使用 Protocol 的辅助类Action 和 ActionBuild执行部分,这里的作用是发起 Dubbo 请求,校验请求结果并记录日志以便后续生成压测报告。ActionBuild 则为 DSL 使用 Action 的辅助类Check 和 CheckBuild检查部分,全链路压测中我们都使用Json Path检查请求结果,这里我们实现了一样的检查逻辑。CheckBuild 则为 DSL 使用 Check 的辅助类DSLDubbo 插件的领域特定语言,我们提供了一套简单易用的 API 方便编写 Duboo 压测脚本,风格上与原生 HTTP DSL 保持一致Protocol协议部分由 5 个属性组成,这些属性将在 Action 初始化 Dubbo 客户端时使用,分别是:protocol协议,设置为dubbogeneric泛化调用设置,Dubbo 压测插件使用泛化调用发起请求,所以这里设置为true,有赞优化了泛化调用的性能,为了使用该特性,引入了一个新值result_no_change(去掉优化前泛化调用的序列化开销以提升性能)urlDubbo 服务的地址:dubbo://IP地址:端口registryProtocolDubbo 注册中心的协议,设置为ETCD3registryAddressDubbo 注册中心的地址如果是测试 Dubbo 单机水位,则设置 url,注册中心设置为空;如果是测试 Dubbo 集群水位,则设置注册中心(目前支持 ETCD3),url 设置为空。由于目前注册中心只支持 ETCD3,插件在 Dubbo 集群上使用缺乏灵活性,所以我们又实现了客户端层面的负载均衡,如此便可抛开特定的注册中心来测试 Dubbo 集群水位。该特性目前正在内测中。object DubboProtocol { val DubboProtocolKey = new ProtocolKey { type Protocol = DubboProtocol type Components = DubboComponents def protocolClass: Class[io.gatling.core.protocol.Protocol] = classOf[DubboProtocol].asInstanceOf[Class[io.gatling.core.protocol.Protocol]] def defaultProtocolValue(configuration: GatlingConfiguration): DubboProtocol = throw new IllegalStateException(“Can’t provide a default value for DubboProtocol”) def newComponents(system: ActorSystem, coreComponents: CoreComponents): DubboProtocol => DubboComponents = { dubboProtocol => DubboComponents(dubboProtocol) } }}case class DubboProtocol( protocol: String, //dubbo generic: String, //泛化调用? url: String, //use url or registryProtocol: String, //use registry registryAddress: String //use registry) extends Protocol { type Components = DubboComponents}为了方便 Action 中使用上面这些属性,我们将其装进了 Gatling 的 ProtocolComponents:case class DubboComponents(dubboProtocol: DubboProtocol) extends ProtocolComponents { def onStart: Option[Session => Session] = None def onExit: Option[Session => Unit] = None}以上就是关于 Protocol 的定义。为了能在 DSL 中配置上述 Protocol,我们定义了 DubboProtocolBuilder,包含了 5 个方法分别设置 Protocol 的 protocol、generic、url、registryProtocol、registryAddress 5 个属性。object DubboProtocolBuilderBase { def protocol(protocol: String) = DubboProtocolBuilderGenericStep(protocol)}case class DubboProtocolBuilderGenericStep(protocol: String) { def generic(generic: String) = DubboProtocolBuilderUrlStep(protocol, generic)}case class DubboProtocolBuilderUrlStep(protocol: String, generic: String) { def url(url: String) = DubboProtocolBuilderRegistryProtocolStep(protocol, generic, url)}case class DubboProtocolBuilderRegistryProtocolStep(protocol: String, generic: String, url: String) { def registryProtocol(registryProtocol: String) = DubboProtocolBuilderRegistryAddressStep(protocol, generic, url, registryProtocol)}case class DubboProtocolBuilderRegistryAddressStep(protocol: String, generic: String, url: String, registryProtocol: String) { def registryAddress(registryAddress: String) = DubboProtocolBuilder(protocol, generic, url, registryProtocol, registryAddress)}case class DubboProtocolBuilder(protocol: String, generic: String, url: String, registryProtocol: String, registryAddress: String) { def build = DubboProtocol( protocol = protocol, generic = generic, url = url, registryProtocol = registryProtocol, registryAddress = registryAddress )}ActionDubboAction 包含了 Duboo 请求逻辑、请求结果校验逻辑以及压力控制逻辑,需要扩展 ExitableAction 并实现 execute 方法。DubboAction 类的域 argTypes、argValues 分别是泛化调用请求参数类型和请求参数值,需为 Expression[] 类型,这样当使用数据 Feeder 作为压测脚本参数输入时,可以使用类似 ${args_types}、${args_values}这样的表达式从数据 Feeder 中解析对应字段的值。execute 方法必须以异步方式执行 Dubbo 请求,这样前一个 Dubbo 请求执行后但还未等响应返回时虚拟用户就可以通过 AKKA Message 立即发起下一个请求,如此一个虚拟用户可以在很短的时间内构造大量请求。请求方式方面,相比于泛化调用,原生 API 调用需要客户端载入 Dubbo 服务相应的 API 包,但有时候却拿不到,此外,当被测 Dubbo 应用多了,客户端需要载入多个 API 包,所以出于使用上的便利性,Dubbo 压测插件使用泛化调用发起请求。异步请求响应后会执行 onComplete 方法,校验请求结果,并根据校验结果记录请求成功或失败日志,压测报告就是使用这些日志统计计算的。 为了控制压测时的 RPS,则需要实现 throttle 逻辑。实践中发现,高并发情况下,泛化调用性能远不如原生 API 调用性能,且响应时间成倍增长(如此不能表征 Dubbo 应用的真正性能),导致 Dubbo 压测插件压力控制不准,解决办法是优化泛化调用性能,使之与原生 API 调用的性能相近,请参考dubbo 泛化调用性能优化。class DubboAction( interface: String, method: String, argTypes: Expression[Array[String]], argValues: Expression[Array[Object]], genericService: GenericService, checks: List[DubboCheck], coreComponents: CoreComponents, throttled: Boolean, val objectMapper: ObjectMapper, val next: Action) extends ExitableAction with NameGen { override def statsEngine: StatsEngine = coreComponents.statsEngine override def name: String = genName(“dubboRequest”) override def execute(session: Session): Unit = recover(session) { argTypes(session) flatMap { argTypesArray => argValues(session) map { argValuesArray => val startTime = System.currentTimeMillis() val f = Future { try { genericService.$invoke(method, argTypes(session).get, argValues(session).get) } finally { } } f.onComplete { case Success(result) => val endTime = System.currentTimeMillis() val resultMap = result.asInstanceOf[JMap[String, Any]] val resultJson = objectMapper.writeValueAsString(resultMap) val (newSession, error) = Check.check(resultJson, session, checks) error match { case None => statsEngine.logResponse(session, interface + “.” + method, ResponseTimings(startTime, endTime), Status(“OK”), None, None) throttle(newSession(session)) case Some(Failure(errorMessage)) => statsEngine.logResponse(session, interface + “.” + method, ResponseTimings(startTime, endTime), Status(“KO”), None, Some(errorMessage)) throttle(newSession(session).markAsFailed) } case FuFailure(e) => val endTime = System.currentTimeMillis() statsEngine.logResponse(session, interface + “.” + method, ResponseTimings(startTime, endTime), Status(“KO”), None, Some(e.getMessage)) throttle(session.markAsFailed) } } } } private def throttle(s: Session): Unit = { if (throttled) { coreComponents.throttler.throttle(s.scenario, () => next ! s) } else { next ! s } }}DubboActionBuilder 则是获取 Protocol 属性并初始化 Dubbo 客户端:case class DubboActionBuilder(interface: String, method: String, argTypes: Expression[Array[String]], argValues: Expression[Array[Object]], checks: List[DubboCheck]) extends ActionBuilder { private def components(protocolComponentsRegistry: ProtocolComponentsRegistry): DubboComponents = protocolComponentsRegistry.components(DubboProtocol.DubboProtocolKey) override def build(ctx: ScenarioContext, next: Action): Action = { import ctx._ val protocol = components(protocolComponentsRegistry).dubboProtocol //Dubbo客户端配置 val reference = new ReferenceConfig[GenericService] val application = new ApplicationConfig application.setName(“gatling-dubbo”) reference.setApplication(application) reference.setProtocol(protocol.protocol) reference.setGeneric(protocol.generic) if (protocol.url == “”) { val registry = new RegistryConfig registry.setProtocol(protocol.registryProtocol) registry.setAddress(protocol.registryAddress) reference.setRegistry(registry) } else { reference.setUrl(protocol.url) } reference.setInterface(interface) val cache = ReferenceConfigCache.getCache val genericService = cache.get(reference) val objectMapper: ObjectMapper = new ObjectMapper() new DubboAction(interface, method, argTypes, argValues, genericService, checks, coreComponents, throttled, objectMapper, next) }}LambdaProcessBuilder 则提供了设置 Dubbo 泛化调用入参的 DSL 以及接下来要介绍的 Check 部分的 DSLcase class DubboProcessBuilder(interface: String, method: String, argTypes: Expression[Array[String]] = _ => Success(Array.empty[String]), argValues: Expression[Array[Object]] = _ => Success(Array.empty[Object]), checks: List[DubboCheck] = Nil) extends DubboCheckSupport { def argTypes(argTypes: Expression[Array[String]]): DubboProcessBuilder = copy(argTypes = argTypes) def argValues(argValues: Expression[Array[Object]]): DubboProcessBuilder = copy(argValues = argValues) def check(dubboChecks: DubboCheck*): DubboProcessBuilder = copy(checks = checks ::: dubboChecks.toList) def build(): ActionBuilder = DubboActionBuilder(interface, method, argTypes, argValues, checks)}Check全链路压测中,我们都使用Json Path校验 HTTP 请求结果,Dubbo 压测插件中,我们也实现了基于Json Path的校验。实现 Check,必须实现 Gatling check 中的 Extender 和 Preparer:package object dubbo { type DubboCheck = Check[String] val DubboStringExtender: Extender[DubboCheck, String] = (check: DubboCheck) => check val DubboStringPreparer: Preparer[String, String] = (result: String) => Success(result)}基于Json Path的校验逻辑:trait DubboJsonPathOfType { self: DubboJsonPathCheckBuilder[String] => def ofType[X: JsonFilter](implicit extractorFactory: JsonPathExtractorFactory) = new DubboJsonPathCheckBuilder[X](path, jsonParsers)}object DubboJsonPathCheckBuilder { val CharsParsingThreshold = 200 * 1000 def preparer(jsonParsers: JsonParsers): Preparer[String, Any] = response => { if (response.length() > CharsParsingThreshold || jsonParsers.preferJackson) jsonParsers.safeParseJackson(response) else jsonParsers.safeParseBoon(response) } def jsonPath(path: Expression[String])(implicit extractorFactory: JsonPathExtractorFactory, jsonParsers: JsonParsers) = new DubboJsonPathCheckBuilder[String](path, jsonParsers) with DubboJsonPathOfType}class DubboJsonPathCheckBuilder[X: JsonFilter]( private[check] val path: Expression[String], private[check] val jsonParsers: JsonParsers)(implicit extractorFactory: JsonPathExtractorFactory) extends DefaultMultipleFindCheckBuilder[DubboCheck, String, Any, X]( DubboStringExtender, DubboJsonPathCheckBuilder.preparer(jsonParsers) ) { import extractorFactory._ def findExtractor(occurrence: Int) = path.map(newSingleExtractor[X](_, occurrence)) def findAllExtractor = path.map(newMultipleExtractor[X]) def countExtractor = path.map(newCountExtractor)}DubboCheckSupport 则提供了设置 jsonPath 表达式的 DSLtrait DubboCheckSupport { def jsonPath(path: Expression[String])(implicit extractorFactory: JsonPathExtractorFactory, jsonParsers: JsonParsers) = DubboJsonPathCheckBuilder.jsonPath(path)}Dubbo 压测脚本中可以设置一个或多个 check 校验请求结果,使用 DSL check 方法*DSLtrait AwsDsl提供顶层 DSL。我们还定义了 dubboProtocolBuilder2DubboProtocol、dubboProcessBuilder2ActionBuilder 两个 Scala 隐式方法,以自动构造 DubboProtocol 和 ActionBuilder。 此外,泛化调用中使用的参数类型为 Java 类型,而我们的压测脚本使用 Scala 编写,所以这里需要做两种语言间的类型转换,所以我们定义了 transformJsonDubboData 方法trait DubboDsl extends DubboCheckSupport { val Dubbo = DubboProtocolBuilderBase def dubbo(interface: String, method: String) = DubboProcessBuilder(interface, method) implicit def dubboProtocolBuilder2DubboProtocol(builder: DubboProtocolBuilder): DubboProtocol = builder.build implicit def dubboProcessBuilder2ActionBuilder(builder: DubboProcessBuilder): ActionBuilder = builder.build() def transformJsonDubboData(argTypeName: String, argValueName: String, session: Session): Session = { session.set(argTypeName, toArray(session(argTypeName).as[JList[String]])) .set(argValueName, toArray(session(argValueName).as[JList[Any]])) } private def toArray[T:ClassTag](value: JList[T]): Array[T] = { value.asScala.toArray }}object Predef extends DubboDslDubbo 压测脚本和数据 Feeder 示例压测脚本示例:import io.gatling.core.Predef._import io.gatling.dubbo.Predef._import scala.concurrent.duration._class DubboTest extends Simulation { val dubboConfig = Dubbo .protocol(“dubbo”) .generic(“true”) //直连某台Dubbo机器,只单独压测一台机器的水位 .url(“dubbo://IP地址:端口”) //或设置注册中心,压测该Dubbo应用集群的水位,支持ETCD3注册中心 .registryProtocol("") .registryAddress("") val jsonFileFeeder = jsonFile(“data.json”).circular //数据Feeder val dubboScenario = scenario(“load test dubbo”) .forever(“repeated”) { feed(jsonFileFeeder) .exec(session => transformJsonDubboData(“args_types1”, “args_values1”, session)) .exec(dubbo(“com.xxx.xxxService”, “methodName”) .argTypes("${args_types1}") .argValues("${args_values1}") .check(jsonPath("$.code").is(“200”)) ) } setUp( dubboScenario.inject(atOnceUsers(10)) .throttle( reachRps(10) in (1 seconds), holdFor(30 seconds)) ).protocols(dubboConfig)}data.json 示例:[ { “args_types1”: [“com.xxx.xxxDTO”], “args_values1”: [{ “field1”: “111”, “field2”: “222”, “field3”: “333” }] }]Dubbo 压测报告示例我的系列博客 混沌工程 - 软件系统高可用、弹性化的必由之路 异步系统的两种测试方法我的其他测试相关开源项目 捉虫记:方便产品、开发、测试三方协同自测的管理工具招聘 有赞测试组在持续招人中,大量岗位空缺,只要你来,就能帮你点亮全栈开发技能树,有意向换工作的同学可以发简历到 sunjun【@】youzan.com ...

December 24, 2018 · 5 min · jiezi

Vtiger CRM 几处SQL注入漏洞分析,测试工程师可借鉴

本文由云+社区发表0x00 前言干白盒审计有小半年了,大部分是业务上的代码,逻辑的复杂度和功能模块结构都比较简单,干久了收获也就一般,有机会接触一个成熟的产品(vtiger CRM)进行白盒审计,从审计的技术难度上来说,都比公司内的那些业务复杂得多,而真正要提高自己技术水平,更应该看的也是这些代码。vtiger CRM是一个客户关系管理系统。0x01 分析整体结构https://www.vtiger.com/open-source-crm/download-open-source/代码下载下来,本地搭建。使用phpstorm进行审计。主目录下的vtigerversion.php可以查看当前版本。整体代码目录 其中主要得功能实现就在modules目录当中,也是我们重点审计的地方。libraries目录是使用到的第三方的一些东西,includes目录是路由加载,封装系统函数的地方。整个系统代码量确实很多,真要审计完估计没有十天半个月是不行的,看了一个礼拜,只发现几个问题。0x02 modules/Calender/actions/feed.php SQL注入分析一个成熟的产品,审计的难点就在于各种类,对象的封装和继承,A调用B,B调用C,C调用D……Vtiger_BasicAjax_Action 这个对象,是modules下vtiger目录里的,而vtiger这个也是核心的module.回到feed.php,直接定位有漏洞的代码,103行后。我图中标的,也正是注入点的位置。$fieldName参数由逗号分割成数组,如果分成后的数组值为2则进入逻辑,然后参数进入SQL语句形成注入。虽然整个系统采用了PDO的查询方式,但是如果有SQL语句存在直接拼接的话,还是有注入的风险。这里payload不能使用逗号,可以采用 (select user())a join的方法绕过。往下走的话,SQL注入漏洞更是多不胜数。也没有再看的必要了。0x03 /modules/Documents/models/ListView.php SQL注入直接看漏洞代码可以看到sortorder参数又是直接拼接。此处是order by后的注入,只能用基于时间的盲注。直接上SQLmap吧,但是sqlmap的payload会使用>,尖括号因为xss防御,已经被过滤所以需要使用绕过脚本。 –tamper greatest 绕过。poc:index.php?module=Documents&parent=&page=1&view=List&viewname=22&orderby=filename&sortorder=and/**/sleep(5)&app=MARKETING&search_params=[]&tag_params=[]&nolistcache=0&list_headers=[%22notes_title%22,%22filename%22,%22modifiedtime%22,%22assigned_user_id%22,%22filelocationtype%22,%22filestatus%22]&tag=0x04 写在最后由于时间原因,只看了前几个模块,还有好多地方没有看。漏洞都很简单,真正花费时间的是走通逻辑,验证漏洞,不停地跳转查看函数调用,和各种类对象的继承。这也是白盒审计的头疼之处,要忍着性子看开发跳来跳去,没准哪个地方就跳错了。有点难受,还没找到getshell的地方。此文已由作者授权腾讯云+社区发布

December 21, 2018 · 1 min · jiezi

网易考拉在服务化改造方面的实践

导读:网易考拉(以下简称考拉)是网易旗下以跨境业务为主的综合型电商,自2015年1月9日上线公测后,业务保持了高速增长,这背后离不开其技术团队的支撑。微服务化是电商IT架构演化的必然趋势,网易考拉的服务架构演进也经历了从单体应用走向微服务化的整个过程,以下整理自网易考拉陶杨在近期Apache Dubbo Meetup上的分享,通过该文,您将了解到:考拉架构的演进过程考拉在服务化改造方面的实践考拉在解决注册中心性能瓶颈方面的实践考拉未来的规划考拉架构的演进过程考拉在2015年初上线的时候,线上只有七个工程,商品详情页、购物车下单页等都耦合在中间这个online的工程里面。在上线之初的时候,这种架构还是比较有优势的,因为当时考拉的开发人员也不是很多,把所有的功能都耦合在一个进程里面,利于集中开发、测试和上线,是一种比较高效和节省成本的方式。但是随着业务的不断发展,包括需求的逐步增多,开发团队的不断扩容,这时候,单体架构的一些劣势就逐渐的暴露出来了,例如开发效率低:功能之间的相互耦合,不同需求的不同分支也经常会修改同一块代码,导致合代码的过程非常痛苦,而且经常会出问题。再例如上线成本高:几乎所有的发布需求都会涉及到这些应用的上线,同时不断增长的业务需求,也会使得我们的代码越来越臃肿,造成维护困难、可用性差,功能之间相互耦合,都耦合在一个进程里面,导致一旦某一个业务需求涉及的代码或者资源出现问题,那么就会影响其他的业务。比如说我们曾经在online工程里面,因为优惠券兑换热点的问题,影响了核心的下单服务。这个架构在考拉运行的4到5个月的时间里,从开发到测试再到上线,大家都特别痛苦。所以我们就开始进行了服务化拆分的工作。这个是考拉现在的分布式服务架构。伴随着服务化的拆分,我们的组织架构也进行了很多调整,出现了商品中心、用户中心和订单中心等等。拆分其实是由业务驱动的,通过业务来进行一些横向拆分或者纵向拆分,同时,拆分也会面对一个拆分粒度的问题,比如怎么才算一个服务,或者说服务拆的过细,是不是会导致我们管理成本过高,又或者说是否会带来架构上的新问题。考拉的拆分由粗到细是一个逐步演进的过程。随着服务化的拆分,使得服务架构越来越复杂,随之而来产生了各种各样的公共技术,比如说服务治理、平台配置中心、分布式事务和分布式定时任务等等。考拉的服务化实践微服务框架在服务化中起到了很重要的作用,是服务化改造的基石,经过严格的技术选型流程后,我们选用了Dubbo来作为考拉服务改造的一个重要支柱。Dubbo可以解决服务化过程中服务的定义、服务的注册与发现、服务的调用和路由等问题,此外,Dubbo也具有一些服务治理的功能和服务监控的功能。下面我将介绍考拉基于Dubbo做的一些服务化实践。首先来说一下 熔断。在进行服务化拆分之后,应用中原有的本地调用就会变成远程调用,这样就引入了更多的复杂性。比如说服务A依赖于服务B,这个过程中可能会出现网络抖动、网络异常,或者说服务B变得不可用或者不好用时,也会影响到A的服务性能,甚至可能会使得服务A占满整个线程池,导致这个应用上其它的服务也受影响,从而引发更严重的雪崩效应。因此,服务之间有这样一种依赖关系之后,需要意识到服务的依赖其实是不稳定的。此时,需要通过采取一些服务治理的措施,例如熔断、降级、限流、隔离和超时等,来保障应用不被外部的异常拖垮。Dubbo提供了降级的特性,比如可以通过mock参数来配置一些服务的失败降级或者强制降级,但是Dubbo缺少自动熔断的特性,所以我们在Dubbo上引入了Hystrix。消费者在进行服务调用的时候会经过熔断器,当服务提供者出现异常的时候,比如暂时性的不可用,熔断器就会打开,对消费端进行调用短路,此时,消费端就不会再发起远程调用,而是直接走向降级逻辑。与此同时,消费端会持续的探测服务的可用性,一旦服务恢复,熔断器就会关闭,重新恢复调用。在Dubbo的服务治理平台上,可以对Hystrix上运行的各种动态参数进行动态的配置,包括是否允许自动熔断,是否要强制熔断,熔断的失败率和时间窗口等等。下面再说一下 限流。当用户的请求量,调用超过系统可承受的并发时系统QPS会降低、出现不可用甚至存在宕机的风险。这就需要一个机制来保护我们的系统,当预期并发超过系统可承受的范围时,进行快速失败、直接返回,以保护系统。Dubbo提供了一些基础的限流特性,例如可以通过信号量的配置来限制我们消费者的调用并发,或者限制提供者的执行并发。但是这些是远远不够的,考拉自研了限流框架NFC,并基于Dubbo filter 的形式,实现了对Dubbo的支持,同时也支持对URL等其他资源的限流。通过配置中心动态获取流控规则,对于资源的请求,比如Dubbo调用会经过流控客户端,进行处理并判断是否触发限流,一旦请求超出定义的阈值,就会快速失败。同时,这些限流的结果会上报到监控平台。上图中的页面就是考拉流控平台的一个监控页面,我们在页面上可以对每一个资源(URL、Dubbo接口)进行一个阈值的配置,并对限流进行准实时监控,包括流控比率、限流次数和当前的QPS等。限流框架除了实现基本的并发限流之外,也基于令牌桶和漏桶算法实现了QPS限流,并基于Redis实现了集群级别的限流。这些措施保障系统在高流量的情况下不会被打垮。考拉在监控服务方面的改造在服务化的过程中,系统变得越来越复杂,服务数量变得越来越多,此时需要引入更多维度的监控功能,帮助快速的去定位并解决系统中的各类问题。监控主要分为这四个方面,日志、Metrics、Trace和HealthCheck。在应用程序、操作系统运行的时候,都会产生各种各样的日志,通过日志平台对这些日志进行采集、分析和展示,并支持查询和操作。Metrics反映的是系统运行的基本状态,包括瞬时值或者聚合值,例如系统的CPU使用率、磁盘使用率,以及服务调用过程中的平均延时等。Trace是对服务调用链的一个监控,例如调用过程中的耗时分析、瓶颈分析、依赖分析和异常分析等。Healthcheck可以探测应用是否准备就绪,是否健康,或者是否还存活。接下来,围绕Dubbo来介绍一下考拉在监控方面的改造实践。第一个是服务监控。Dubbo提供了服务监控功能,支持定期上报服务监控数据,通过代码增强的方式,采集Dubbo调用数据,存储到时序数据库里面,将Dubbo的调用监控功能接入到考拉自己的监控平台。上图中的页面是对Dubbo提供者的服务监控,包括对服务接口、源集群等不同维度的监控,除了全局的调用监控,还包括不同维度的监控,例如监控项里的调用次数。有时候我们更关心慢请求的情况,所以会将响应时间分为多个范围,比如说从0到10毫秒,或是从10到50毫秒等,这样就可以看到在各个范围内请求的数量,从而更好地了解服务质量。同时,也可以通过各种报警规则,对报警进行定义,当服务调用出现异常时,通过邮件、短信和电话的形式通知相关人员。监控平台也会对异常堆栈进行采集,例如说这次服务调用的异常的原因,是超时还是线程满了的,可以在监控平台上直接看到。同时生成一些监控报表,帮助我们更好地了解服务的性能,推进开发去改进。第二个是Trace。我们参考了Dapper,自研了Trace平台,并通过代码增强的方式,实现了对Dubbo调用链路的采集。相关调用链参数如TarceID,SpanID 等是通过Dubbo的隐式传参来传递的。Trace可以了解在服务调用链路中的一个耗时分析和瓶颈分析等。Trace平台上可以展示一次服务调用,经历了哪些节点,最耗时的那个节点是在哪里,从而可以有针对性的去进行性能优化。Trace还可以进行依赖分析,这些依赖是否合理,能否通过一些业务手段或者其它手段去减少一些不合理的依赖。Trace对异常链路进行监控报警,及时的探测到系统异常并帮助我们快速的定位问题,同时和日志平台做了打通,通过TraceId可以很快的获取到关联的异常日志。第三个是健康检查。健康检查也是监控中很重要的一个方面,以更优雅的方式上线应用实例。我们和自动部署平台结合,实现应用的健康检查。服务启动的时候可以通过Readiness接口判断应用依赖的各种资源,包括数据库、消息队列等等是否已经准备就绪。只有健康检查成功的时候才会触发出注册操作。同时Agent也会在程序运行的过程中定时的检查服务的运行状态。同时,也通过这些接口实现更优雅的停机,仅依赖shutdownhook,在某些情况下不一定靠谱,比如会有shutdownhook执行先后顺序的问题。应用发布的时候,首先调用offline接口,将注册服务全部从注册中心反注册,这时不再有新的流量进来,等到一段时间后,再执行停机发布操作,可以实现更加优雅的停机。考拉在服务测试方面的改造下面来介绍一下考拉在服务测试方面的实践。服务测试分为接口测试、单链路压测、全链路压测和异常测试四个维度。接口测试通过接口测试,可以来验证对外提供的Dubbo服务是否正确,因此我们也有接口测试平台,帮助QA更好的进行接口测试,包括对接口的编辑(入参、出参),用例的编辑和测试场景的执行等,单链路压测单链路的压测,主要面对单个功能的压测,比如要上线一个重要功能或者比较重要的接口之前,必须通过性能测试的指标才可以上线。全链路压测考拉作为电商平台,在大促前都会做全链路压测,用以探测系统的性能瓶颈,和对系统容量的预估。例如,探测系统的各类服务的容量是否够,需要扩容多少,以及限流的阈值要定多少合适,都可以通过全链路压测来给出一些合理的值。异常测试对服务调用链路中的一些节点进行系统异常和服务异常的注入,也可以获取他们的强度依赖关系。比如一个非常重要的接口,可以从Trace获取的调用链路,然后对调用链的依赖的各个服务节点进行异常注入。通过接口的表现,系统就会判断这个接口的强度依赖关系,以改善这些不合理的强依赖关系。考拉在API网关方面的改造随着考拉服务化的发展,我们自研了API网关,API网关可以作为外部流量的统一接口,提供了包括路由转发、流控和日志监控等一些公共的功能。考拉的API网关是通过泛化调用的方式来调用后台Dubbo的服务的。Dubbo原生的泛化调用的性能比普通Api调用要差一些,所以我们也对泛化调用性能做了一些优化,也就是去掉了泛化调用在返回结果时的一次对象转换。最终压测的结果泛化的性能甚至比正常的调用性能还要好些。考拉在多语言方面的改造考拉在业务发展的过程中产生了不少多语言的需求,例如,我们的前端团队希望可以用Node应用调用Dubbo服务。对比了易用性,选用了开源的jsonrpc 方案,然后在后端的Dubbo服务上暴露了双协议,包括Dubbo协议和json rpc协议。但在实施的过程中,也遇到了一些小问题,比如说,对于Dubbo消费者来说,不管是什么样的协议提供者,都是invoker。通过一个负载均衡策略,选取一个invoker进行调用,这个时候就会导致原来的Java客户端选用一个jsonrpc协议的提供者。这样如果他们的API版本不一致,就有可能导致序列化异常,出现调用失败的情况。所以,我们对Dubbo的一些调用逻辑做了改造,例如在Java客户端的消费者进行调用的时候,除非显示的配置,否则默认只用Dubbo协议去调用。另外,考拉也为社区的jsonrpc扩展了隐式传参的功能,因为可以用Dubbo隐式传参的功能来传递一些全链路参数。考拉在解决注册中心性能瓶颈方面的实践注册中心瓶颈可能是大部分电商企业都会遇到的问题,考拉也不例外。我们现在线上的Dubbo服务实例大概有4000多个,但是在ZooKeeper中注册的节点有一百多万个,包括服务注册的URL和消费者订阅的URL。Dubbo应用发布时的惊群效应、重复通知和消费者拉取带来的瞬时流量一下就把ZooKeeper集群的网卡打满,ZooKeeper还有另外一个问题,他的强一致性模型导致CPU的利用率不高。就算扩容,也解决不了ZooKeeper写性能的问题,ZooKeeper写是不可扩展的,并且应用发布时有大量的请求排队,从而使得接口性能急剧下降,表现出来的现象就是应用启动十分缓慢。因此,在今年年初的时候就我们决定把ZooKeeper注册中心给替换掉,对比了现有的一些开源的注册中心,包括Consul、Eruka、etcd等,觉得他们并不适合Dubbo这种单进程多服务的注册模型,同时容量能否应对未来考拉的发展,也是一个问号。于是,我们决定自研注册中心,目前正在注册中心的迁移过程当中,采用的是双注册中心的迁移方案,即服务会同时注册ZooKeeper注册中心,还有新的注册中心,这样对原有的架构不会产生太大的影响。考拉新的注册中心改造方案和现在社区的差不多,比如说也做了一个注册数据的拆分,往注册中心注册的数据只包含IP, Port 等关键数据,其它的数据都写到了Redis里面,注册中心实现使用了去中心化的一个架构,包括使用最终一致性来换取我们接口性能的一个提升。后面如果接入Dubbo,会考虑使用Nacos而不是ZooKeeper作为注册中心。未来规划考拉最近也在进行第二机房的建设,通过两个机房独立部署相同的一套系统,以实现同城双活。针对双机房的场景,Dubbo会做一定的改造,例如同机房优先调用,类似于即将发布的Dubbo2.7.0中的路由特性。在Dubbo在服务注册的时候,读取系统环境变量的环境标或者机房标,再将这些机房标注册到注册中心,然后消费端会做一个优先级路由,优先进行同机房的服务调用。容器化也是我们在规划的一个方向。随着服务化进程的演进,服务数也变得越来越多,通过容器化、DevOps可以提升测试、部署和运维效率。Service Mesh在今年非常火,通过Service Mesh将服务框架的的能力比如注册发,路由和负载均衡,服务治理等下沉到Sidecar,使用独立进程的方式来运行。对于业务工程的一个解耦,帮助我们实现一个异构系统,对多语言支持,也可以解决中间件升级推动困难以及各种依赖的冲突,业务方也可以更好的关注于业务开发,这也会是未来探索的一个方向。本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。

December 20, 2018 · 1 min · jiezi

每一个工程师都要学的安全测试,老板再也不用担心服务器被黑

本文由云+社区发表本篇包含了XSS漏洞攻击及防御详细介绍,包括漏洞基础、XSS基础、编码基础、XSS Payload、XSS攻击防御。第一部分:漏洞攻防基础知识XSS属于漏洞攻防,我们要研究它就要了解这个领域的一些行话,这样才好沟通交流。同时我建立了一个简易的攻击模型用于XSS漏洞学习。1. 漏洞术语了解一些简单术语就好。VULVulnerability漏洞,指能对系统造成损坏或能借之攻击系统的Bug。POCProof of Concept,漏洞证明;可以是可以证明漏洞存在的文字描述和截图,但更多的一般是证明漏洞存在的代码;一般不会破坏存在漏洞的系统。EXPexploit,漏洞利用;利用漏洞攻击系统的代码。Payload(有效攻击负载)是包含在你用于一次漏洞利用(exploit)中的攻击代码。PWN是一个黑客语法的俚语词 ,是指攻破设备或者系统。0DAY漏洞和0DAY攻击零日漏洞或零时差漏洞(Zero-dayexploit)通常是指还没有补丁的安全漏洞。零日攻击或零时差攻击(Zero-dayattack)则是指利用这种漏洞进行的攻击。零日漏洞不但是黑客的最爱,掌握多少零日漏洞也成为评价黑客技术水平的一个重要参数。CVE漏洞编号Common Vulnerabilities and Exposures,公共漏洞和暴露,为广泛认同的信息安全漏洞或者已经暴露出来的弱点给出一个公共的名称。可以在https://cve.mitre.org/网站根据漏洞的CVE编号搜索该漏洞的介绍。也可以在中文社区http://www.scap.org.cn/上搜索…2. 漏洞攻击模型1.png上图为一个简单的攻击模型。攻击就是将Payload通过注入点注入到执行点执行的过程。过程顺畅就表明这个漏洞被利用了。第二部分:XSS基础知识基础知识看完,现在我们可以开始接触了解XSS基础了。XSS基础不好就不用研究了,大家没用共同语言。1. 什么是XSS?XSS全称Cross-site scripting,跨站脚本攻击。攻击者通过网站注入点注入恶意客户端可执行解析的Payload,当被攻击者访问网站时Payload通过客户端执行点执行来达到某些目的,比如获取用户权限、恶意传播、钓鱼等行为。2. XSS的分类不了解分类其实很难学好XSS,大家对XSS分类有很多误解,而且很多文章上都解释错的,这里我给出一个相对好的XSS分类。2.1 按照Payload来源划分存储型XSSPayload永久存在服务器上,所以也叫永久型XSS,当浏览器请求数据时,包含Payload的数据从服务器上传回并执行。过程如图:2.png存储型XSS例子:发表帖子内容包含Payload->存入数据库->被攻击者访问包含该帖子的页面Payload被执行反射型XSS又称非持久型XSS,第一种情况:Payload来源在客户端然后在客户端直接执行。第二种情况:客户端传给服务端的临时数据,直接回显到客户端执行。过程如图:3.png反射型XSS例子 :传播一个链接,这个链接参数中包含Payload->被攻击者访问这个链接Payload在客户端被执行。在客户端搜索框输入包含payload的内容->服务端回显一个页面提示搜索内容未找到,payload就被执行了。2.2 按照Payload的位置划分DOM-based XSS由客户端JavaScript代码操作DOM或者BOM造成Payload执行的漏洞。由于主要是操作DOM造成的Payload执行,所以叫做DOM-based XSS,操作BOM同样也可以造成Payload执行,所以这个名词有些不准确,其实叫JavaScript-based XSS更好。DOM-based的Payload不在html代码中所以给自动化漏洞检测带来了困难。过程如图:4.png反射型DOM-based XSS的例子:在客户端搜索框输入包含payload的内容->服务端回显一个页面提示搜索内容未找到,payload就被执行了。存储型DOM-based XSS的例子:从服务端接口中获取包含Payload的内容->JavaScript通过操作DOM、BOM造成Payload执行HTML-based XSSPayload包含在服务端返回的HTML中,在浏览器解析HTML的时候执行。这样的漏洞易于做自动化漏洞检测,因为Payload就在HTML里面。当然HTML-based XSS也有反射型和存储型的。过程如图:5.png反射型HTML-based XSS的例子:在客户端搜索框输入包含payload的内容->服务端回显一个页面提示搜索内容未找到,payload包含在HTML被执行。存储型HTML-based XSS的例子:发表帖子内容包含Payload->存入数据库->被攻击者访问包含该帖子的页面Payload在HTML页面中被执行3. XSS的攻击目的及危害很多写出不安全代码的人都是对漏洞的危害没有清晰的认识,下图是2017 OWASP 网络威胁Top10:6_头图 自截取.jpg可以看到XSS在网络威胁中的地位举足轻重。3.1 目的cookie劫持篡改网页,进行钓鱼或者恶意传播网站重定向获取用户信息3.2 危害传播类危害系统安全威胁第三部分:XSS攻击的Payload这部分我们分析下攻击模型中的Payload,了解Payload必须了解编码,学习好JS也必须要了解好编码。要想真正做好网络安全编码是最基本的。1. 编码基础编码部分是最重要的虽然枯燥但必须要会。后面很多变形的Payload都建立在你的编码基础。这里通16进制编码工具让你彻底学会编码。1.1 编码工具16进制查看器:方便查看文件16进制编码MAC:HEx Friendwindows: HxD编辑器Sublime:可以通过Sublime将文件保存不同编码类型7.jpg1.2 ASCII定义:美国信息交换标准代码,是基于拉丁字母的一套计算机编码系统,主要用于显示现代英语和其他西欧语言。编码方式:属于单子节编码。ASCII码一共规定了128个字符的编码,只占用了一个字节的后面7位,最前面的1位统一规定为0。0~31及127(共33个)是控制字符或通信专用字符。32~126(共95个)是字符(32是空格。1.3 ISO-8859-1(Latin1)定义:Latin1是ISO-8859-1的别名,ISO-8859-1收录的字符除ASCII收录的字符外,还包括西欧语言、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号。欧元符号出现的比较晚,没有被收录在ISO-8859-1当中。编码方式:ISO-8859-1编码是单字节编码,向下兼容ASCII,其编码范围是0x00-0xFF,0x00-0x7F之间完全和ASCII一致,0x80-0x9F之间是控制字符,0xA0-0xFF之间是文字符号。注意:ISO-8859-1编码表示的字符范围很窄,无法表示中文字符。但是,由于是单字节编码,和计算机最基础的表示单位一致,所以很多时候,仍旧使用ISO-8859-1编码来表示。比如,虽然”中文”两个字不存在iso8859-1编码,以gb2312编码为例,应该是”d6d0 cec4”两个字符,使用iso8859-1编码的时候则将它拆开为4个字节来表示:”d6 d0 ce c4”(事实上,在进行存储的时候,也是以字节为单位处理的)。所以mysql中latin1可以表示任何编码的字符。Latin1与ASCII编码的关系:完全兼容ASCII。1.4 Unicode编码(UCS-2)Code Point: 码点,简单理解就是字符的数字表示。一个字符集一般可以用一张或多张由多个行和多个列所构成的二维表来表示。二维表中行与列交叉的点称之为码点,每个码点分配一个唯一的编号,称之为码点值或码点编号。BOM(Byte Order Mark):字节序,出现在文件头部,表示字节的顺序,第一个字节在前,就是”大端方式”(Big-Endian),第二个字节在前就是”小端方式”(Little-Endian)。在Unicode字符集中有一个叫做”ZERO WIDTH NO-BREAK SPACE“的字符,它的码点是FEFF。而FFFE在Unicode中是不存在的字符,所以不应该出现在实际传输中。在传输字节流前,我们可以传字符”ZERO WIDTH NO-BREAK SPACE“表示大小端,因此字符”ZERO WIDTH NO-BREAK SPACE“又被称作BOM。BOM还可以用来表示文本编码方式,Windows就是使用BOM来标记文本文件的编码方式的。Mac上文件有没有BOM都可以。例如:u00FF :00是第一个字节,FF是第二个字节。和码点表示方式一样属于大端方式。Unicode编码字符集:旨在收集全球所有的字符,为每个字符分配唯一的字符编号即代码点(Code Point),用 U+紧跟着十六进制数表示。所有字符按照使用上的频繁度划分为 17 个平面(编号为 0-16),即基本的多语言平面和增补平面。基本的多语言平面又称平面 0,收集了使用最广泛的字符,代码点从 U+0000 到 U+FFFF,每个平面有 216=65536 个码点;Unicode编码:Unicode 字符集中的字符可以有多种不同的编码方式,如 UTF-8、UTF-16、UTF-32、压缩转换等。我们通常所说的Unicode编码是UCS-2 将字符编号(同 Unicode 中的码点)直接映射为字符编码,亦即字符编号就是字符编码,中间没有经过特别的编码算法转换。是定长双字节编码:因为我们UCS-2只包括本的多语言平面(U+0000 到 U+FFFF)。UCS-2的BOM:大端模式:FEFF。小端模式:FFFE。文件保存成UTF-16 BE with BOM相当于UCS-2的大端模式,可以看到16进制开头为FEFFLatin1与Unicode编码的关系:Latin1对应于Unicode的前256个码位。8.png1.5 UTF-16定义及编码:UTF-16是Unicode的其中一个使用方式,在Unicode基本多文种平面定义的字符(无论是拉丁字母、汉字或其他文字或符号),一律使用2字节储存。而在辅助平面定义的字符,会以代理对(surrogate pair)的形式,以两个2字节的值来储存。是双字节编码。UTF-16与UCS-2的关系:UTF-16可看成是UCS-2的父集。在没有辅助平面字符(surrogate code points)前,UTF-16与UCS-2所指的是同一的意思。但当引入辅助平面字符后,就称为UTF-16了。现在若有软件声称自己支援UCS-2编码,那其实是暗指它不能支援在UTF-16中超过2bytes的字集。对于小于0x10000的UCS码,UTF-16编码就等于UCS码。UTF-16的BOM:大端模式:FEFF。小端模式:FFFE。1.6 UTF-8定义及编码:UTF-8就是在互联网上使用最广的一种Unicode的实现方式,这是为传输而设计的编码,并使编码无国界,这样就可以显示全世界上所有文化的字符了。UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用14个字节表示一个符号,根据不同的符号而变化字节长度,当字符在ASCII码的范围时,就用一个字节表示,保留了ASCII字符一个字节的编码作为它的一部分,注意的是unicode一个中文字符占2个字节,而UTF-8一个中文字符占3个字节)。从unicode到utf-8并不是直接的对应,而是要过一些算法和规则来转换。Unicode符号范围UTF-8编码方式(十六进制)0000 0000-0000 007F0xxxxxxx0000 0080-0000 07FF110xxxxx 10xxxxxx0000 0800-0000 FFFF1110xxxx 10xxxxxx 10xxxxxx0001 0000-0010 FFFF11110xxx 10xxxxxx 10xxxxxx 10xxxxxxUTF8的BOM:EFBBBF。UTF-8不存在字符序列的问题,但是可以用用BOM表示这个文件是一个UTF-8文件。文件保存成UTF-8 BE with BOM,可以看到16进制开头为EFBBBF9.png1.7 GBK/GB2312定义及编码:GB2312是最早一版的汉字编码只包含6763汉字,GB2312只支持简体字而且不全,显然不够用。GBK编码,是对GB2312编码的扩展,完全兼容GB2312标准,支持简体字繁体字,包含全部中文字符。GBK编码采用单双字节编码方案,单字节和Latin1一致,双字节是汉字部分,其编码范围:8140-FEFE,剔除xx7F码位,共23940个码位。GBK与Latin1的关系:GBK单字节编码区和Latin1编码一致。GBK与Unicode的关系:GBK与Unicode字符集编码不同但是兼容的。如"汉"的Unicode值与GBK虽然是不一样的,假设Unicode为a040,GBK为b030,但是可以对应转化的。汉字的Unicode区:4E00-u9FA5。GBK与UTF-8:GBK汉字采用双字节编码比在UTF-8中的三字节要小。但是UTF-8更通用。GBK与UTF-8转化:GBK—> Unicode —> UTF82. 前端中的编码有了编码基础就可以来认识一下前端中的编码,这样你才能真正认识Payload。我这里的应该是总结最全的。2.1 Base64Base64可以用来将binary的字节序列数据编码成ASCII字符序列构成的文本。使用时,在传输编码方式中指定Base64。使用的字符包括大小写拉丁字母各26个、数字10个、加号+和斜杠/,共64个字符及等号=用来作为后缀用途。所以总共65个字符。将3字节的数据,先后放入一个24位的缓冲区中,先来的字节占高位。数据不足3字节的话,于缓冲器中剩下的比特用0补足。每次取出6bit对原有数据用Base64字符作为编码后的输出。编码若原数据长度不是3的倍数时且剩下1个输入数据,则在编码结果后加2个=;若剩下2个输入数据,则在编码结果后加1个=。可以看出Base64编码数据大约是原来数据的3/4。标准的Base64并不适合直接放在URL里传输,因为URL编码器会把标准Base64中的/和+字符变为形如%XX的形式,而这些%号在存入数据库时还需要再进行转换,因为ANSI SQL中已将%号用作通配符。为解决此问题,可采用一种用于URL的改进Base64编码,它不在末尾填充=号,并将标准Base64中的+和/分别改成了-和_,这样就免去了在URL编解码和数据库存储时所要做的转换,避免了编码信息长度在此过程中的增加,并统一了数据库、表单等处对象标识符的格式。window.btoa/window.atob base64编码(binary to ascii)和解码仅支持Latin1字符集。2.2 JS转义字符js字符字符串中包含一些反斜杠开头的特殊转义字符,用来表示非打印符、其他用途的字符还可以转义表示unicode、Latin1字符。转义字符含义’单引号”双引号&和号\反斜杠n换行符r回车符t制表符b退格符f换页符n … nnn由一位到三位八进制数(1到377)指定的Latin-1字符xnn以16进制nn(n:0F)表示一个Latin1字符。x41表示字符Aunnnn以16进制nnnn(n:0F)表示一个Unicode字符。只限于码点在u0000uFFFF范围内u{n} … u{nnnnnn}Unicode码点值表示一个Unicode字符特别注意:换行符n在innerHTML使用只会展示一个空格并不会换行。通过n、u和x可以代表任意unicode字符和Latin1字符。通过这个可以对js加密保证js安全和进行隐蔽攻击。例子:function toUnicode(theString) { //字符串转换为unicode编码字符串,切记这个字符串是复制用的,不是让你拿来直接执行的。 var unicodeString = ‘’; for (var i = 0; i < theString.length; i++) { var theUnicode = theString.charCodeAt(i).toString(16).toUpperCase(); while (theUnicode.length < 4) { theUnicode = ‘0’ + theUnicode; } theUnicode = ‘\u’ + theUnicode; unicodeString += theUnicode; } return unicodeString;}var xssStr = “alert(‘xss’)";var xssStrUnicode = toUnicode(xssStr);//输出:"\u0061\u006C\u0065\u0072\u0074\u0028\u0027\u0078\u0073\u0073\u0027\u002"eval("\u0061\u006C\u0065\u0072\u0074\u0028\u0027\u0078\u0073\u0073\u0027\u002”); //弹出xss弹窗2.3 URL编码RFC 1738做出规定”只有字母和数字0-9a-zA-Z、一些特殊符号”$-.+!’(),”不包括双引号、以及某些保留字,才可以不经过编码直接用于URL”。所以当链接中包含中文或者其他不符合规定的字符的时候都需要经过编码的。然而由于浏览器厂商众多,对url进行编码的形式多种多样,如果不对编码进行统一处理,会对代码开发造成很大的影响,出现乱码现象。URL编码规则:需要编码的字符转换为UTF-8编码,然后在每个字节前面加上%。例如:‘牛’–>UTF-8编码E7899B–>URL编码是%E7%89%9BJS为我们提供了3个对字符串进行URL编码的方法:escape ,encodeURI,encodeURIComponentescape:由于eccape已经被建议放弃所以大家就不要用了encodeURI:encodeURI不编码的82个字符:!#$&’()+,/:;=?@-.0-9a-zA-Z,从中可以看不会对url中的保留字符进行编码,所以适合url整体编码encodeURIComponent:这个对于我们来说是最有用的一个编码函数,encodeURIComponent不编码的字符有71个:!, ‘,(,),*,-,.,_,,0-9,a-z,A-Z。可以看出对url中的保留字进行的编码,所以当传递的参数中包含这些url中的保留字(@,&,=),就可以通过这个方法编码后传输这三个方法对应的解码方法: unescape、decodeURI、decodeURIComponent2.4 HTML字符实体HTML中的预留字符必须被替换为字符实体。这样才能当成字符展示,否则会当成HTML解析。字符实体编码规则:转义字符 = +ascii码; = &实体名称;XSS字符串需要防御字符的实体转换表:10.png转化方法:function encodeHTML (a) { return String(a) .replace(/&/g, “&”) .replace(/</g, “<”) .replace(/>/g, “>”) .replace(/"/g, “”") .replace(/’/g, “’”);};2.5 页面编码页面编码设置:<meta charset=“UTF-8”><meta http-equiv=“Content-Type” content=“text/html; charset=utf-8” />脚本编码设置:<script type=“text/javascript” src=“myscripts.js” charset=“UTF-8”></script>注意:要想JS即可在UTF-8中正常使用又可以在GBK中正常使用,可以对JS中所有包含中文的字符串做字符转义。例子:alert(“网络错误”); //弹出网络错误alert("\u7f51\u7edc\u9519\u8bef"); //弹出网络错误3. Payload的分类现在可以认识Payload的了,我不得不说这里对Payload的分类可以很好的让你认识Payload。也帮助你更好的对应到执行点。3.1 原子Payload最低层级的Payload。javascript代码片段可在eval、setTimeout、setInterval中直接执行,也可通过HTML等构成高阶Payloadjavascript:javascript伪协议结构:javascript:+js代码。可以在a标签的href属性被点击和window.location.href赋值的时候执行。DATA URI协议DATA URI结构:data:, 。DATA URI数据在包含在iframe的src属性和object data属性中将会变成可执行的Payload.字符串转义变种javascript代码片段unicode或者Latin-1表示字符串。eval("\u0061\u006C\u0065\u0072\u0074\u0028\u0027\u0078\u0073\u0073\u0027\u002"); //可执行的JS3.2 纯HTMLPayload这种Payload特点不具有可执行的JS,但是存在传播风险,可以把别的站点注入到被攻击网站。包含链接跳转的HTML片段主要是传播危害<a href=“http://ha.ck”>哈哈,我来钓鱼了</a>3.3 包含原子Payload的HTML片段Payloadscript标签片段script标签片段这种Payload可以引入外部JS或者可直接执行的script。这种Payload一般不能通过直接复制给innerHTML执行,不过在IE上可以。不过通过document.write是可以执行。例子:// Payload原始值:data:text/html,<script>alert(‘xss’);</script>var inputStr ="<script>alert(‘xss’);</script>";document.write(inputStr);包含事件处理的HTML片段例如:包含img的onerror, svg的onload,input的onfocus等的HTML片段,都可以变成可执行的Payload。var inputStr ="<img src=x onerror=alert(‘xss’);>"; var inputStr ="<svg/onload=alert(‘xss’)>"; var inputStr ="<input autofocus onfocus=alert(‘xss’)>"; xssDom.innerHTML = inputStr;包含可执行JS属性的HTML片段javascript伪协议xssLink.setAttribute(“href”,“javascript:alert(‘xss’)”)//点击可触发var inputStr = “javascript:alert(‘xss’)";window.location.href = inputStr;DATA URI例子:// Payload原始值:data:text/html,<script>alert(‘xss’);</script>//var inputStr = ‘<iframe src=“data:text/html,<script>alert(“xss”);</script>"></iframe>’;// var inputStr = ‘<object data=“data:text/html;base64,ZGF0YTp0ZXh0L2h0bWwsPHNjcmlwdD5hbGVydCgneHNzJyk7PC9zY3JpcHQ+"></object>’;xssDom.innerHTML = inputStr; //弹出alert(“xss”)这里只是介绍了主要的Payload,还有很多不常见的Payload。第四部分:XSS攻击模型分析这部分我们根据漏洞攻击模型分析一下XSS的执行点和注入点。分析这两点其实就是找漏洞的过程。1. XSS漏洞执行点页面直出Dom客户端跳转链接: location.href / location.replace() / location.assign()取值写入页面:innerHTML、document.write及各种变种。这里主要会写入携带可执行Payload的HTML片段。脚本动态执行:eval、setTimeout()、setInterval()不安全属性设置:setAttribute。不安全属性前面见过:a标签的href、iframe的src、object的dataHTML5 postMessage来自不安全域名的数据。有缺陷的第三方库。2. XSS漏洞注入点看看我们可以在哪些位置注入我们的Payload服务端返回数据用户输入的数据链接参数:window.location对象三个属性href、search、search客户端存储:cookie、localStorage、sessionStorage跨域调用:postMessage数据、Referer、window.name上面内容基本包含了所有的执行点和注入点。对大家进行XSS漏洞攻防很有帮助。第五部分 XSS攻击防御策略1. 腾讯内部公共安全防御及应急响应接入公共的DOM XSS防御JS内部漏洞扫描系统扫描腾讯安全应急响应中心:安全工作者可以通过这个平台提交腾讯相关的漏洞,并根据漏洞评级获得奖励。重大故障应急响应制度。2. 安全编码2.1 执行点防御方法执行点防御页面直出Dom服务端XSS过滤客户端跳转链接域名白名单(例如:只允许qq.com域)、链接地址XSS过滤取值写入页面客户端XSS过滤脚本动态执行确保执行Js字符串来源可信|| 不安全属性设置 | 内容XSS过滤,包含链接同客户端跳转链接 ||HTML5 postMessage|origin限制来源|| 有缺陷的第三方库 | 不使用2.2 其他安全防御手段对于Cookie使用httpOnly在HTTP Header中使用Content Security Policy3. 代码审查总结XSS检查表做代码自测和检视4. 自动化检测XSS漏洞的工具手工检测XSS漏洞是一件比较费时间的事情,我们能不能写一套自动检测XSS自动检测工具。竟然我知道了注入点、执行点、Payload自动化过程是完全有可能的。XSS自动化检测的难点就在于DOM型XSS的检测。因为前端JS复杂性较高,包括静态代码分析、动态执行分析都不容易等。第六部分 总结上面内容文字比较多,看完还是很累的,总结起来就一句话:安全大于一切,不要心存侥幸,希望以上内容对您有帮助,不过以上内容仅代表个人理解,如有不对欢迎指正讨论。此文已由作者授权腾讯云+社区发布 ...

December 18, 2018 · 2 min · jiezi

如何创建一个数据科学项目?

摘要: 在一个新的数据科学项目,你应该如何组织你的项目流程?数据和代码要放在那里?应该使用什么工具?在对数据处理之前,需要考虑哪些方面?读完本文,会让你拥有一个更加科学的工作流程。假如你想要开始一个新的数据科学项目,比如对数据集进行简单的分析,或者是一个复杂的项目。你应该如何组织你的项目流程?数据和代码要放在那里?应该使用什么工具?在对数据处理之前,需要考虑哪些方面?数据科学是当前一个不太成熟的行业,每个人都各成一家。虽然我们可以在网上参照各种模板项目、文章、博客等创建一个数据科学项目,但是目前也没有教科书对这些知识做一个统一的回答。每个数据科学家都是从经验和错误中不断的探索和学习。现在,我逐渐了解到什么是典型的“数据科学项目”,应该如何构建项目?需要使用什么工具?在这篇文章中,我希望把我的经验分享给你。工作流程尽管数据科学项目的目标、规模及技术所涉及的范围很广,但其基本流程大致如下:如上图所示,项目不同,其侧重点也会有所不同:有些项目的某个过程可能特别复杂,而另一些项目可能就不需要某一过程。举个例子来说,数据科学分析项目通常就不需要“部署”(Deployment)和“监控”(Monitoring)这两个过程。现在,我们逐一来细说各个过程。源数据访问不管是你接触到人类基因组还是iris.csv,通常都会有 “原始源数据”这一概念。数据有很多种形式,可以是固定的,也可以是动态变化的,可以存储在本地或云端。其第一步都是对源数据访问,如下所示:源数据是*.csv文件集合。使用Cookiecutter工具在项目的根文件夹中创建一个data/raw/子目录,并将所有的文件存储在这里;创建docs/data.rst文件描述源数据的含义。源数据是*.csv文件集合。启动SQL服务器,创建一个raw表,将所有的CSV文件作为单独的表导入。创建docs/data.rst文件描述源数据及SQL Server位置。源数据是基因组序列文件、患者记录、excel及word文档组合等,后续还会以不可预测的方式增长。这样可以在云服务器中创建SQL数据库,将表导入。你可以在data/raw/目录存储特别大的基因组序列,在data/raw/unprocessed目录存储excel和word文件;还可以使用DVC创建Amazon S3存储器,并将data/raw/目录推送过去;也可以创建一个Python包来访问外部网站;创建docs/data.rst目录,指定SQL服务器、S3存储器和外部网站。源数据中包含不断更新的网站日志。可以使用ELK stack 并配置网站以流式传输新日志。源数据包含10万张大小为128128像素的彩色图像,所有图像的大小则为100,0001281283,将其保存在HDF5文件images.h5中。创建一个Quilt数据包并将其推送给自己的私人Quilt存储库;创建/docs/data.rst文件,为了使用数据,必须首先使用quilt install mypkg/images导入工作区,然后再使用 from quilt.data.mypkg import images导入到代码中。源数据是模拟数据集。将数据集生成实现为Python类,并在README.txt文件中记录其使用。通常来说,在设置数据源的时候可以遵循以下规则:存储数据的方式有意义,另外还要方便查询、索引。保证数据易于共享,可以使用NFS分区、Amazon S3存储器、Git-LFS存储器、Quilt包等。确保源数据是只读状态,且要备份副本。花一定的时间,记录下所有数据的含义、位置及访问过程。上面这个步骤很重要。后续项目会你可能会犯任何错误,比如源文件无效、误用方法等等,如果没有记住数据的含义、位置及访问过程,那将很麻烦。数据处理数据处理的目的是将数据转化为“干净”的数据,以便建模。在多数情况下,这种“干净”的形式就是一个特征表,因此,“数据处理”通常归结为各种形式的特征工程(feature engineering),其核心要求是:确保特征工程的逻辑可维护,目标数据集可重现,整个管道可以追溯到源数据表述。计算图(computation graph)即满足以上要求。具体例子如下:根据cookiecutter-data-science规则,使用Makefile来描述计算图。通过脚本实现每个步骤,该脚本将一些数据文件作为输入,然后输出一个新的数据文件并存储在项目的data/interim或data/processed目录中。可以使用 make -j <njobs>命令进行并行运算。使用DVC来描述和执行计算图,其过程与上面类似,此外还有共享生成文件等功能。还可以使用Luigi、Airflow或其他专用工作流管理系统来描述和执行计算图。除此之外,还可以在基于web的精美仪表板上查看计算进度。所有源数据都以表的形式存储在SQL数据库中,在SQL视图中实现所有的特征提取逻辑。此外,还可以使用SQL视图来描述对象的样本。然后,你可以根据这些特征和样本视图创建最终的模型数据集。首先,允许用户轻松的跟踪当前所定义的特征,而不用存储在大型数据表中。特征定义仅在代码运行期间有效;其次,模型从部署到生产非常简单,假设实时数据库使用相同的模式,你就只需要复制相应的视图。此外,还可以使用CTE语句将所有的特征定义编译为模型最终预测的单个查询语句。在进行数据处理时,请注意一下问题:1.重复以计算图的形式处理数据。2.考虑计算基础架构。是否进行长时间计算?是否需要并行计算还是聚类?是否可以从具有跟踪任务执行的管理UI作业中获益?3.如果想要将模型部署到生产环境中,请确保系统支持该用例。如果正在开发一个包含JAVA Android应用程序模型,但是还是想用Python开发,为了避免不必要的麻烦,就可以使用一个专门设计的DSL,然后将这个DSL转换为Java或PMML之类的中间格式。4.考虑存储特征或临时计算的元数据。可以将每个特征列保存在单独的文件中,或使用Python函数注释。建模完成数据处理和特征设计后即可开始进行建模。在一些数据科学项目中,建模可以归结为单个m.fit(X,y)或某个按钮;而在其他项目中则可能会涉及数周的迭代和实验。通常来说,你可以从“特征工程”建模开始,当模型的输出构成了很多特征时,数据处理和建模这两个过程并没有明确的界限,它们都涉及到计算。尽管如此,将建模单独列出来作为一个步骤,仍然很有意义,因为这往往会涉及到一个特殊的需求:实验管理(experiment management)。具体例子如下:如果你正在训练一个模型,用于在iris.csv数据集中对Irises进行分类。你需要尝试十个左右的标准sklearn模型,每个模型都有多个不同的参数值,并且测试不同的特征子集。如果你正在设计一个基于神经网络的图像分类模型。你可以使用ModelDB(或其他实验管理工具,如TensorBoard,Sacred,FGLab,Hyperdash,FloydHub,Comet.ML,DatMo,MLFlow,…)来记录学习曲线和实验结果,以便选择最佳的模型。使用Makefile(或DVC、工作流引擎)实现整个管道。模型训练只是计算图中的一个步骤,它输出model-<id>.pkl 文件,将模型最终AUC值附加到CSV文件,并创建 model-<id>.html报告,还有一堆用于评估的模型性能报告。实验管理/模型版本控制的UI外观如下:模型部署在实际应用中,模型最终都要部署到生产环境中,一定要有一个有效的计划,下面有些例子:建模管道输出一个训练过模型的pickle文件。所有的数据访问和特征工程代码都是由一系列Python函数实现。你需要做的就是将模型部署到Python应用程序中,创建一个包含必要函数和模型pickle文件的Python包。管建模道输出一个训练过的模型的pickle文件。部署模型需要使用Flask创建一个REST服务将其打包为一个docker容器,并通过公司的Kubernetes云服务器提供服务。训练管道生成TensorFlow模型。可以将TensorFlow服务当做REST服务。每次更新模型时,都要创建测试并运行。训练管道生成PMML文件。你可以用Java中的JPMML库来读取,一定要确保PMML导出器中要有模型测试。训练管道将模型编译为SQL查询,将SQL查询编码到应用程序中。我们对模型部署做一下总结:1.模型部署的方式有很多种。在部署之前一定要了解实际情况,并提前做计划:是否需要将模型部署到其他语言编写的代码库中?如果使用REST服务,服务的负载时多少?能否进行批量预测?如果打算购买服务,费用是多少?如果决定使用PMML,那么就要确保它能够支持你的预期预处理逻辑。如果在训练期间使用第三方数据源,那么就要考虑是否在生产中能够与它们集成,以及如何在管道导出模型中对访问信息进行编码。2.模型一旦部署到生产环境,它就转变为一行行实际的代码,所以也要满足所有需求,因此,这就需要测试。在理想情况下,部署管道应该产生用于部署的模型包以及测试时需要的所有内容。模型监控将模型成功部署到生产环境,也许训练集中的输入分布与现实不同,模型需要重新练或重新校准;也许系统性能没有达到预期。因此,你需要收集模型性能的数据并对其进行监控。这就需要你设置一个可视化仪表板,具体事例如下:将模型的输入和输出保存在logstash或数据表中,设置Metabase(或Tableau,MyDBR,Grafana等)并创建可视化模型性能和校准指标报告。进一步探索和报告在整个数据科学项目中,你还需要尝试不同的假设,以生成图标和报告。这些任务与构建管道有所不同,主要体现在两个方面:首先,大部分任务不需要可再现性,即不用包含在计算图中。另外,也没必要使用模型的可重复性,在Jupyter中手动绘制图即可。其次,这些“进一步探索”的问题往往具有不可预测性:可能需要分析性能监控日志中的一个异常值;或者测试一个新的算法。这些探索会塞满你的笔记本中,团队中的其他人可能看不懂你的记录。因此按照日期排列子项目很重要。在项目中创建project目录,子文件夹命名格式为:projects/YYYY-MM-DD -项目名称。如下所示:./2017-01-19 - Training prototype/ (README, unsorted files)./2017-01-25 - Planning slides/ (README, slides, images, notebook)./2017-02-03 - LTV estimates/ README tasks/ (another set of date-ordered subfolders)./2017-02-10 - Cleanup script/ README script.py./… 50 folders more …注意,你可以根据需要自由组织每个子项目的内部目录,因为每个子项目很可能也是一个“数据科学项目”。在任何情况下,在每个子项目中都要有个README文件夹或README.txt文件,简要列出每个子项目目录的信息。如果项目列表太长,你需要重新组织项目目录,比如压缩一部分文件移动到存档文件夹中。“探索性”的任务有两种形式,即一次性分析和可重复性使用的代码,这时候建立一些约定很有必要。服务清单数据科学项目可能会依赖一些服务,可以指定提供以下9个关键服务,来描述期望:1.文件存储。任何一个数据科学项目都必须有个存储项目的地方,且需要整个团队共享。它是网络驱动器上的一个文件夹?还是Git存储库中的一个文件夹?2.数据服务。如何存储和访问数据?这里的“数据”指的是计算机读取或输出的所有内容,包括源数据、中间结果及第三方数据集访问、元数据、模型及报告等。3.版本。代码、数据、模型、报告和文档都需要有版本控制,另外一定要备份!4.元数据和文档。如何记录项目及子项目?是否有任何机器都可读的特征、脚本、数据集或模型的元数据?5.交互式计算。在交互式计算中,你选择JupyterLab、RStudio、ROOT、Octave还是Matlab?您是否为交互式并行计算设置了一个聚类(如ipyparallel或dask)?6.作业队列和调度程序。代码如何运行?是否需要安排定期维护?7.计算图。如何描述计算图并建立可重复性?8.实验管理。如何收集、查看和分析模型培训进度和结果?使用 ModelDB、Hyperdash还是 FloydHub?9.监控仪表板。如何收集和跟踪模型在生产环境中的具体表现?使用元数据库、Tableau、 PowerBI还是Grafana?最后,我总结了一个电子表格,包含了本文提到的所有工具,可自行下载使用。本文作者:【方向】阅读原文本文为云栖社区原创内容,未经允许不得转载。

December 14, 2018 · 1 min · jiezi

智能支付稳定性测试实战

本文根据美团高级测试开发工程师勋伟在美团第43期技术沙龙“美团金融千万级交易系统质量保障之路”的演讲整理而成。主要介绍了美团智能支付业务在稳定性方向遇到的挑战,并重点介绍QA在稳定性测试中的一些方法与实践。背景美团支付承载了美团全部的交易流量,按照使用场景可以将其分为线上支付和智能支付两类业务。线上支付,支撑用户线上消费场景,处理美团所有线上交易,为团购、外卖、酒店旅游等业务线提供支付能力;智能支付,支撑用户到店消费场景,处理美团所有线下交易,通过智能POS、二维码支付、盒子支付等方式,为商家提供高效、智能化的收银解决方案。其中,智能支付作为新扩展的业务场景,去年也成为了美团增速最快的业务之一。面临的挑战而随着业务的快速增长,看似简单的支付动作,背后系统的复杂度却在持续提升。体现在:上层业务入口、底层支付渠道的不断丰富,微服务化背景下系统的纵向分层、服务的横向拆分,还有对外部系统(营销中心、会员中心、风控中心等)、内部基础设施(队列、缓存等)的依赖也越来越多,整条链路上的核心服务节点超过20个,业务复杂度可想而知。此外,技术团队在短时间内就完成了从几个人到近百人规模的扩张,这也是一个潜在的不稳定因素。曾经在一段时间内,整个系统处在“牵一发而动全身”的状态,即使自身系统不做任何发版升级,也会因为一些基础设施、上下游服务的问题,业务会毫无征兆地受到影响。痛定思痛,我们对发生过的线上问题进行复盘,分析影响服务稳定性的原因。通过数据发现,72%的严重故障集中在第三方服务和基础设施故障,对应的一些典型事故场景,比如:第三方支付通道不稳定、基础设施(如消息队列)不稳定,进而导致整个系统雪崩,当依赖方故障恢复后,我们的业务却很难立即恢复。解决方案基于这些问题,我们开展了稳定性建设专项,目的很明确:提升服务的可用性。目标是逐步将系统可用性从2个9提升到3个9,再向4个9去努力。这个过程中最核心的两个策略:柔性可用,意思是尽可能保证核心功能可用,或在有损情况下尽可能保证核心用户体验,降低影响;另一个是快速恢复,即用工具或机制保证故障的快速定位和解决,降低故障修复时间。围绕这两个策略,在稳定性建设中的常见操作:限流、熔断降级、扩容,用于打造系统的柔性可用;故障响应SOP、故障自动处理,用于故障处理时的快速恢复。而QA的工作更侧重于对这些“常见操作”进行有效性验证。基于经验,重点介绍“三把利剑”:故障演练、线上压测、持续运营体系。故障演练的由来举个真实的案例,在一次处理某支付通道不稳定的线上问题时,开发同学执行之前已经测试通过的预案(服务端关闭该通道,预期客户端将该支付通道的开关置灰,并会提示用户使用其他支付方式),但执行中却发现预案无法生效(服务端操作后,客户端该支付通道仍处于开启状态)。非故障场景下预案功能正常,故障场景下却失效了。这就是故障演练的由来,我们需要尽可能还原故障场景,才能真正验证预案的有效性。故障演练的整体方案故障演练的整体方案,主要分为三部分:负载生成模块,负责尽可能还原系统的真实运行场景(要求覆盖核心业务流程)。故障注入模块,包含故障注入工具、故障样本库(涵盖外部服务、基础组件、机房、网络等各种依赖,并重点关注超时、异常两种情况)。业务验证模块,结合自动化测试用例和各个监控大盘来进行。为了更高效地开展故障演练,我们的策略是分为两个阶段进行。首先,针对单系统进行故障演练,从故障样本库出发,全面覆盖该系统所有的保护预案;在此基础上,进行全链路故障演练,聚焦核心服务故障,验证上下游服务的容错性。故障演练的效果事实证明,故障演练确实给我们带来了很多“惊喜”,暴露了很多隐患。这里列举三类问题:数据库主从延迟影响交易;基础设施故障时,业务未做降级;依赖服务超时设置不合理、限流策略考虑不足等。线上压测的由来面对业务的指数级增长,我们必须对系统可承载的流量做到心中有数。对于QA来说,需要找到精准、高效的系统容量评估方法。我们碰到的难点包括:链路长、环节多、服务错综复杂,线下环境与线上差异大等等,基于测试有效性和测试成本考虑,我们决定要做线上压测,而且要实现全链路的线上压测。线上压测的整体方案全链路压测的实现方案,与业界主流方案没有太大区别。根据压测流程,首先,场景建模,以便更真实的还原线上系统运行场景;其次,基础数据构造,应满足数据类型以及量级的要求,避免数据热点;之后,流量构建,读写流量构造或回放,同时对压测流量进行标记和脱敏;再之后,压测执行,过程中收集链路各节点的业务运行状态、资源使用情况等;最后,生成压测报告。基于全链路线上压测方案,可以根据业务需求,灵活地进行单链路压测、分层压测等。更为重要的是,基于压测我们可以进行线上的故障演练,用于更加真实的验证系统限流、熔断等保护预案。线上压测的效果通过全链路线上压测,一方面让我们对系统容量做到心中有数,另一方面也让我们发现了线上系统运行过程中的潜在问题,而且这些问题一般都是高风险的。同样列举三类问题:基础设施优化,如机房负载不均衡、数据库主从延迟严重等;系统服务优化,如线程池配置不合理、数据库需要拆分等;故障预案优化,如限流阈值设置过低,有的甚至已经接近限流边缘而浑然不知等等。持续运营体系的由来智能支付的稳定性建设是作为一个专项在做,持续了近3个月的时间;在效果还不错的情况下,我们从智能支付延伸到整个金融服务平台,以虚拟项目组的方式再次运转了3个月的时间。通过项目方式,确实能集中解决现存的大部分稳定性问题,但业务在发展、系统在迭代,稳定性建设必然是一项长期的工作。于是,QA牵头SRE、DBA、RD,建立了初步的稳定性持续运营体系,并在持续完善。持续运营体系的整体方案下面介绍持续运营体系的三大策略:流程规范工具化,尽可能减少人为意识因素,降低人力沟通和维护成本。如:配置变更流程,将配置变更视同代码上线,以PR方式提交评审;代码规范检查落地到工具,尽可能将编码最佳实践抽取为规则,将人工检查演变为工具检查。质量度量可视化,提取指标、通过数据驱动相关问题的PDCA闭环。如:我们与SRE、DBA进行合作,将线上系统运维中与稳定性相关的指标提取出来,类似数据库慢查询次数、核心服务接口响应时长等等,并对指标数据进行实时监控,进而推进相关问题的解决。演练压测常态化,降低演练和压测成本,具备常态化执行的能力。如:通过自动化的触发演练报警,验证应急SOP在各团队实际执行中的效果。基于以上三个策略,构建稳定性持续运营体系。强调闭环,从质量度量与评价、到问题分析与解决,最终完成方法与工具的沉淀;过程中,通过平台建设来落地运营数据、完善运营工具,提升运营效率。持续运营体系的效果简单展示当前持续运营体系的运行效果,包含风险评估、质量大盘、问题跟进以及最佳实践的沉淀等。未来规划综上便是智能支付QA在稳定性建设中的重点工作。对于未来工作的想法,主要有3个方向。第一,测试有效性提升,持续去扩展故障样本库、优化演练工具和压测方案;第二,持续的平台化建设,实现操作平台化、数据平台化;第三,智能化,逐步从人工运营、自动化运营到尝试智能化运营。作者介绍勋伟,美团高级测试开发工程师,金融服务平台智能支付业务测试负责人,2015年加入美团点评。招聘如果你想学习互联网金融的技术体系,亲历互联网金融业务的爆发式增长,如果你想和我们一起,保证业务产品的高质量,欢迎加入美团金融工程质量组。有兴趣的同学可以发送简历到:fanxunwei#meituan.com。

December 14, 2018 · 1 min · jiezi

内存性能的正确解读

一台服务器,不管是物理机还是虚拟机,必不可少的就是内存,内存的性能又是如何来衡量呢。1. 内存与缓存现在比较新的CPU一般都有三级缓存,L1 Cache(32KB-256KB),L2 Cache(128KB-2MB),L3 Cache(1M-32M)。缓存逐渐变大,CPU在取数据的时候,优先从缓存去取数据,取不到才去内存取数据。2. 内存与时延显然,越靠近CPU,取数据的速度越块,通过LMBench进行了读数延迟的测试。从上图可以看出:Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz 这款CPU的L1D Cache,L1I Cache为32KB,而L2 Cache为1M,L3为32M;在对应的Cache中,时延是稳定的;不同缓存的时延呈现指数级增长;所以我们在写业务代码的时候,如果想要更快地提高效率,那么使得计算更加贴近CPU则可以获取更好的性能。但是从上图也可以看出,内存的时延都是纳秒为单位,而实际业务中都是毫秒为单位,优化的重点应该是那些以毫秒为单位的运算,而内存时延优化这块则是长尾部分。3. 内存带宽内存时延与缓存其实可谓是紧密相关,不理解透彻了,则可能测的是缓存时延。同样测试内存带宽,如果不是正确的测试,则测的是缓存带宽了。为了了解内存带宽,有必要去了解下内存与CPU的架构,早期的CPU与内存的架构还需要经过北桥总线,现在CPU与内存直接已经不需要北桥,直接通过CPU的内存控制器(IMC)进行内存读取操作:那对应的内存带宽是怎样的呢?测试内存带宽有很多很多工具,linux下一般通过stream进行测试。简单介绍下stream的算法:stream算法的原理从上图可以看出非常简单:某个内存块之间的数据读取出来,经过简单的运算放入另一个内存块。那所谓的内存带宽:内存带宽=搬运的内存大小/耗时。通过整机合理的测试,可以测出来内存控制器的带宽。下图是某云产品的内存带宽数据:————————————————————-Function Best Rate MB/s Avg time Min time Max timeCopy: 128728.5 0.134157 0.133458 0.136076Scale: 128656.4 0.134349 0.133533 0.137638Add: 144763.0 0.178851 0.178014 0.181158Triad: 144779.8 0.178717 0.177993 0.180214————————————————————-内存带宽的重要性自然不言而喻,这意味着操作内存的最大数据吞吐量。但是正确合理的测试非常重要,有几个注意事项需要关注:内存数组大小的设置,必须要远大于L3 Cache的大小,否则就是测试缓存的吞吐性能;CPU数目很有关系,一般来说,一两个核的计算能力,是远远到不了内存带宽的,整机的CPU全部运行起来,才可以有效地测试内存带宽。当然跑单核的stream测试也有意义,可以测试内存的延时。4. 其他内存与NUMA的关系:开启NUMA,可以有效地提供内存的吞吐性能,降低内存时延。stream算法的编译方法选择:通过icc编译,可以有效地提供内存带宽性能分。原因是Intel优化了CPU的指令,通过指令向量化和指令Prefetch操作,加速了数据的读写操作以及指令操作。当然其他C代码都可以通过icc编译的方法,提供指令的效率。本文作者:ecs西邪阅读原文本文为云栖社区原创内容,未经允许不得转载。

December 13, 2018 · 1 min · jiezi

实战:用Python实现随机森林

摘要: 随机森林如何实现?为什么要用随机森林?看这篇足够了!因为有Scikit-Learn这样的库,现在用Python实现任何机器学习算法都非常容易。实际上,我们现在不需要任何潜在的知识来了解模型如何工作。虽然不需要了解所有细节,但了解模型如何训练和预测对工作仍有帮助。比如:如果性能不如预期,我们可以诊断模型或当我们想要说服其他人使用我们的模型时,我们可以向他们解释模型如何做出决策的。在本文中,我们将介绍如何在Python中构建和使用Random Forest,而不是仅仅显示代码,我将尝试了解模型的工作原理。我将从一个简单的单一决策树开始,然后以解决现实世界数据科学问题的方式完成随机森林。本文的完整代码在GitHub上以Jupyter Notebook的形式提供。理解决策树决策树是随机森林的构建块,它本身就是个直观的模型。我们可以将决策树视为询问有关我们数据问题的流程图。这是一个可解释的模型,因为它决定了我们在现实生活中的做法:在最终得出决定之前,我们会询问有关数据的一系列问题。决策树的主要技术细节是如何构建有关数据的问题,决策树是通过形成能够最大限度减少基尼系数的问题而建立的。稍后我会讨论Gini Impurity,但这意味着决策树试图形成尽可能纯的节点,其中包含来自单个类的高比例样本(数据点)的节点。Gini Impurity和构建树可能有点难以理解,所以首先让我们构建一个决策树,以便可以更好的理解它。关于最简单问题的决策树我们从一个非常简单的二进制分类问题开始,如下所示:我们的数据只有两个特征(标签),且只有6个数据点。虽然这个问题很简单,但它不是线性可分的,这意味着我们不能通过数据绘制一条直线来对点进行分类。然而,我们可以绘制一系列划分类的直线,这基本上是决策树在形成一系列问题时将要做的事情。要创建决策树并在数据上训练,我们可以使用Scikit-Learn:from sklearn.tree import DecisionTreeClassifier# Make a decision tree and traintree = DecisionTreeClassifier(random_state=RSEED)tree.fit(X, y)在训练过程中,我们为模型提供特征和标签,以便学习根据特征对点进行分类。我们没有针对这个简单问题的测试集,但是在测试时,我们只给模型提供功能并让它对标签做出预测。我们可以在训练数据上测试我们模型的准确性:print(f’Model Accuracy: {tree.score(X, y)}’)Model Accuracy: 1.0我们看到它100%正确,这是我们所期望的,因为我们给了它训练的答案。可视化决策树当我们训练决策树时,实际上会发生什么?我发现了解决策树的最有用的方法是通过可视化,我们可以使用Scikit-Learn的功能(详细信息请查看笔记本或本文)。上图显示了决策树的整个结构,除叶节点(终端节点)外,所有节点都有5个部分:问题基于特征值询问数据:每个问题都有对或错的答案。根据问题的答案,数据点在树中移动。Gini:节点的Gini杂质。当我们向下移动树时,平均加权基尼系数必须减少。samples:节点中的观察数。value:每个类的样本数量。例如,顶部节点在类0中有2个样本,在类1中有4个样本。class:节点中点的多数分类。在叶节点的情况下,这是对节点中所有样本的预测。叶节点没有问题,因为这些是最终预测的地方。要对新节点进行分类,只需向下移动树,使用点的特征来回答问题,直到到达class预测的叶节点。你可以使用上面的点进行尝试,或者进行不同的预测。基尼系数在这一点上,我们应该尝试了解基尼系数。简而言之,Gini Impurity是随机选择的样本被节点中的样本分布标记错误的概率。例如,在顶部(根)节点中,有44.4%错误的可能性根据节点中样本标签的分布对随机选择的数据点进行分类。我们可以使用下面这个等式得到这个值:节点的Gini系数n是1减去每个J类的p_i平方的总和,让我们计算出根节点的基尼系数。在每个节点处,决策树在要素中搜索要拆分的值,从而最大限度地减少基尼系数。(拆分节点的替代方法是使用信息增益)。然后,它以递归过程重复此拆分过程,直到达到最大深度,或者每个节点仅包含来自一个类的样本。每层树的加权总基尼系数必须减少。在树的第二层,总加权基尼系数值为0.333:最后一层的加权总Gini系数变为0意味着每个节点都是纯粹的,并且从该节点随机选择的点不会被错误分类。虽然这似乎是好结果,但这意味着模型可能过度拟合,因为节点仅使用是通过训练数据构建的。过度拟合:为什么森林比一棵树更好你可能会想问为什么不只使用一个决策树?它似乎是完美的分类器,因为它没有犯任何错误!记住这其中的关键点是树在训练数据上没有犯错。因为我们给树提供了答案。机器学习模型的要点是很好地概括测试数据。不幸的是,当我们不限制决策树的深度时,它往往会过度拟合。当我们的模型具有高方差并且基本上记忆训练数据时,一定会发生过度拟合。这意味着它可以很好地在训练数据上,但由于测试数据不同,它将无法对测试数据做出准确的预测!我们想要的是一个能很好地学习训练数据的模型,并且可以在其他数据集上发挥作用。当我们不限制最大深度时,决策树容易过度拟合的原因是因为它具有无限的复杂性,这意味着它可以保持增长,直到它为每个单独的观察只有一个叶节点,完美地对所有这些进行分类。要理解为什么决策树具有高差异,我们可以用一个人来考虑它。想象一下,你觉得明天苹果股票会上涨,你会问一些分析师。每一位分析师都可能会有很大差异并且会严重依赖他们可以访问的数据。一位分析师可能只阅读亲苹果新闻,因此她认为价格会上涨,而另一位分析师最近从她的朋友那里听到苹果产品的质量开始下降,她可能就认为价格会下降。这些个体分析师的差异很大,因为他们的答案极其依赖于他们所看到的数据。因为每个分析师都可以访问不同的数据,所以预计个体差异会很大,但整个集合的总体方差应该减少。使用许多个体本质上是随机森林背后的想法:而不是一个决策树,使用数百或数千个树来形成一个强大的模型。(过度拟合的问题被称为偏差-方差权衡,它是机器学习中的一个基本主题)。随机森林随机森林是许多决策树组成的模型。这个模型不仅仅是一个森林,而且它还是随机的,因为有两个概念:随机抽样的数据点;基于要素子集拆分的节点;随机抽样随机森林背后的关键是每棵树在数据点的随机样本上训练。样本用替换(称为bootstrapping)绘制,这意味着一些样本将在一个树中多次训练。这个想法是通过对不同样本的每棵树进行训练,尽管每棵树相对于一组特定的训练数据可能有很大的差异,但总体而言,整个森林的方差都很小。每个学习者在数据的不同子集上学习,然后进行平均的过程被称为bagging,简称bootstrap aggregating。用于拆分节点的随机特征子集随机森林背后的另一个关键点是,只考虑所有特征的子集来分割每个决策树中的每个节点。通常,这被设置为sqrt(n_features)意味着在每个节点处,决策树考虑在特征的样本上分割总计特征总数的平方根。考虑到每个节点的所有特征,也可以训练随机森林。如果你掌握单个决策树、bagging决策树和随机特征子集,那么你就可以很好地理解随机森林的工作原理。随机森林结合了数百或数千个决策树,在略微不同的观察集上训练每个决策树,并且仅考虑有限数量的特征来分割每个树中的节点。随机森林做出的最终预测是通过平均每棵树的预测来做出的。随机森林实践与其他Scikit-Learn模型非常相似,在Python中使用随机森林只需要几行代码。我们将构建一个随机森林,但不是针对上面提到的简单问题。为了将随机森林与单个决策树的能力进行对比,我们将使用分为训练和测试的真实数据集。数据集我们要解决的问题是二进制分类任务。这些特征是个人的社会经济和生活方式特征,标签是健康状况不佳为0和身体健康为1。此数据集是由中心疾病控制和预防收集,可以在这里找到。这是一个不平衡的分类问题,因此准确性不是一个合适的指标。相反,我们将测量接收器工作特性区域曲线(ROC AUC),从0(最差)到1(最佳)的度量,随机猜测得分为0.5。我们还可以绘制ROC曲线以评估模型性能。该笔记本包含了决策树和随机森林的实现,但在这里我们只专注于随机森林。在读取数据后,我们可以实现并训练随机森林如下:from sklearn.ensemble import RandomForestClassifier# Create the model with 100 treesmodel = RandomForestClassifier(n_estimators=100, bootstrap = True, max_features = ‘sqrt’)# Fit on training datamodel.fit(train, train_labels)在训练几分钟后,准备好对测试数据进行如下预测:# Actual class predictionsrf_predictions = model.predict(test)# Probabilities for each classrf_probs = model.predict_proba(test)[:, 1]我们进行类预测(predict)以及predict_proba计算ROC AUC所需的预测概率()。一旦我们进行了预测测试,我们就可以将它们与测试标签进行比较,以计算出ROC AUC。from sklearn.metrics import roc_auc_score# Calculate roc aucroc_value = roc_auc_score(test_labels, rf_probs)结果最终的ROC AUC是随机森林为0.87,而单一决策树是0.67。如果我们查看训练分数,我们注意到两个模型都达到了1.0 ROC AUC,因为我们给这些模型提供了训练答案,并没有限制最大深度。然而,尽管随机森林过度拟合,但它能够比单一决策树更好地推广测试数据。如果我们检查模型,我们会看到单个决策树达到最大深度55,总共12327个节点。随机森林中的平均决策树的深度为46和13396个节点。即使平均节点数较多,随机森林也能更好地推广!我们还可以绘制单个决策树(顶部)和随机森林(底部)的ROC曲线。顶部和左侧的曲线是更好的模型:我们看到随机森林明显优于单一决策树。我们可以采用模型的另一个诊断措施是绘制测试预测的混淆矩阵:特征重要性(Feature Importances)随机森林中的特征重要性表示在该特征上拆分的所有节点上Gini系数减少的总和。我们可以使用这些来尝试找出随机森林最重要的预测变量,同时也可以从训练有素的随机森林中提取特征重要性,并将其放入Pandas数据框中,如下所示:import pandas as pd# Extract feature importancesfi = pd.DataFrame({‘feature’: list(train.columns), ‘importance’: model.feature_importances_}).\ sort_values(‘importance’, ascending = False)# Displayfi.head() feature importance DIFFWALK 0.036200 QLACTLM2 0.030694 EMPLOY1 0.024156 DIFFALON 0.022699 USEEQUIP 0.016922我们还可以通过删除具有0或低重要性的特征来使用特征重要性来选择特征。在森林中可视化树最后,我们可以在森林中可视化单个决策树。这次,我们必须限制树的深度,否则它将太大而无法转换为图像。为了制作我将最大深度限制为6,这仍然导致我们无法完全解析的大树!优化决策树下一步可能是通过随机搜索和Scikit-Learn中的RandomizedSearchCV来优化随机森林。优化是指在给定数据集上找到模型的最佳超参数。最佳超参数将在数据集之间变化,因此我们必须在每个数据集上单独执行优化(也称为模型调整)。我喜欢将模型调整视为寻找机器学习算法的最佳设置。有关随机森林模型优化的随机搜索的实现,请参阅Jupyter Notebook。结论在本文中,我们不仅在Python中构建和使用了随机森林,而且还对模型的进行了分析。我们首先查看了一个单独的决策树,一个随机森林的基本构建块,然后我们看到了如何在一个集合模型中组合数百个决策树。当与bagging特征一起使用和随机抽样时,该集合模型被称为随机森林。从这篇文章中理解的关键概念是:决策树:直观模型,根据询问有关特征值的问题流程图做出决策,通过过度拟合训练数据表示方差高。Gini Impurity:衡量决策树在拆分每个节点时尝试最小化的度量。表示根据节点中样本的分布对来自节点的随机选择的样本进行分类的概率。Bootstrapping:用替换的方式随机观察组进行采样。随机森林用于训练每个决策树的方法。随机的特征子集:在考虑如何在决策树中分割每个节点时选择一组随机特征。随机森林:由数百或数千个决策树组成的集合模型,使用自举,随机特征子集和平均投票来进行预测。这是一个bagging整体的例子。偏差-方差权衡:机器学习中的基本问题,描述了高复杂度模型之间的权衡,以采用最好的方式学习训练数据,代价是无法推广到测试数据以及简单的模型(高偏见)甚至无法学习训练数据。随机森林减少了单个决策树的方差,同时还准确地学习了训练数据,从而更好地预测了测试数据。希望本文为你提供了开始在项目中使用随机森林所需的信心和理解。随机森林是一种强大的机器学习模型,但这不应该阻止我们知道它是如何工作的!本文作者:【方向】阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

December 10, 2018 · 1 min · jiezi

【Keras】减少过拟合的秘诀——Dropout正则化

摘要: Dropout正则化是最简单的神经网络正则化方法。阅读完本文,你就学会了在Keras框架中,如何将深度学习神经网络Dropout正则化添加到深度学习神经网络模型里。 Dropout正则化是最简单的神经网络正则化方法。其原理非常简单粗暴:任意丢弃神经网络层中的输入,该层可以是数据样本中的输入变量或来自先前层的激活。它能够模拟具有大量不同网络结构的神经网络,并且反过来使网络中的节点更具有鲁棒性。阅读完本文,你就学会了在Keras框架中,如何将深度学习神经网络Dropout正则化添加到深度学习神经网络模型里,具体内容如下:如何使用Keras API创建Dropout层;如何使用Keras API将Dropout正则化添加到MLP、CNN和RNN层;在现有模型中,如何使用Dropout正则化减少过拟合。Keras中的Dopout正则化在Keras深度学习框架中,我们可以使用Dopout正则化,其最简单的Dopout形式是Dropout核心层。在创建Dopout正则化时,可以将 dropout rate的设为某一固定值,当dropout rate=0.8时,实际上,保留概率为0.2。下面的例子中,dropout rate=0.5。layer = Dropout(0.5)Dropout层将Dropout层添加到模型的现有层和之前的输出层之间,神经网络将这些输出反馈到后续层中。用dense()方法指定两个全连接网络层:…model.append(Dense(32))model.append(Dense(32))…在这两层中间插入一个dropout层,这样一来,第一层的输出将对第二层实现Dropout正则化,后续层与此类似。现在,我们对第二层实现了Dropout正则化。…model.append(Dense(32))model.append(Dropout(0.5))model.append(Dense(32))…Dropout也可用于可见层,如神经网络的输入。在这种情况下,就要把Dropout层作为网络的第一层,并将input_shape参数添加到层中,来制定预期输入。…model.add(Dropout(0.5, input_shape=(2,)))…下面,我们来看看Dropout正则化如何与常见的网络类型一起使用。MLP Dropout正则化在两个全连接层之间添加Dropout正则化,代码如下所示:# example of dropout between fully connected layersfrom keras.layers import Densefrom keras.layers import Dropout…model.add(Dense(32))model.add(Dropout(0.5))model.add(Dense(1))…CNN Dropout正则化我们可以在卷积层和池化层后使用Dropout正则化。一般来说,Dropout仅在池化层后使用。# example of dropout for a CNNfrom keras.layers import Densefrom keras.layers import Conv2Dfrom keras.layers import MaxPooling2Dfrom keras.layers import Dropout…model.add(Conv2D(32, (3,3)))model.add(Conv2D(32, (3,3)))model.add(MaxPooling2D())model.add(Dropout(0.5))model.add(Dense(1))…在这种情况下,我们要将Dropout应用于特征图的每个单元中。在卷积神经网络中使用Dropout正则化的另一个方法是,将卷积层中的整个特征图都丢弃,然后在池化期间也不再使用。这种方法称为空间丢弃,即Spatial Dropout。“我们创建了一个新的Dropout正则化方法,我们将其称为Spatial Dropout。在这个方法中,我们将Dropout值扩展到整个特征映射中。”——《使用卷积神经网络有效的进行对象本地化,2015》在Keras中,通过SpatialDropout2D层提供Spatial Dropout正则化。# example of spatial dropout for a CNNfrom keras.layers import Densefrom keras.layers import Conv2Dfrom keras.layers import MaxPooling2Dfrom keras.layers import SpatialDropout2D…model.add(Conv2D(32, (3,3)))model.add(Conv2D(32, (3,3)))model.add(SpatialDropout2D(0.5))model.add(MaxPooling2D())model.add(Dense(1))…RNN Dropout正则化我们在LSTM循环层和全连接层之间使用Dropout正则化,代码如下所示:# example of dropout between LSTM and fully connected layersfrom keras.layers import Densefrom keras.layers import LSTMfrom keras.layers import Dropout…model.add(LSTM(32))model.add(Dropout(0.5))model.add(Dense(1))…在这里,将Dropout应用于LSTM层的32个输出中,这样,LSTM层就作为全连接层的输入。还有一种方法可以将Dropout与LSTM之类的循环层一起使用。LSTM可以将相同的Dropout掩码用于所有的输入中。这个方法也可用于跨样本时间步长的循环输入连接。这种使用递归模型进行Dropout正则化则称为变分循环神经网络(Variational RNN)。“变分循环神经网络在每个时间步长使用相同的Dropout掩码,包括循环层。这与在RNN中实现Dropout正则化一样,在每个时间步长丢弃相同的神经网络单元,并且随意的丢弃输入、输出和循环连接。这和现有的技术形成对比,在现有的技术中,不同的神经网络单元将在不同的时间步长被丢弃,并且不会对全连接层进行丢弃。”——《循环神经网络中Dropout的基础应用,2016》Keras通过循环层上的两个参数来支持变分神经网络(输入和循环输入样本时间步长的一致性丢弃),这称为 输入“Dropout”和循环输入的“recurrent_dropout”。# example of dropout between LSTM and fully connected layersfrom keras.layers import Densefrom keras.layers import LSTMfrom keras.layers import Dropout…model.add(LSTM(32))model.add(Dropout(0.5))model.add(Dense(1))…Dropout正则化案例在本节中,我们将演示如何使用Dropout正则化来减少MLP在简单二元分类问题上的过拟合。在这里,我们提供了一个在神经网络上应用Dropout正则化的模板,你也可以将其用于分类和回归问题。二元分类问题在这里,我们使用一个标准的二元分类问题,即定义两个二维同心圆,每个类为一个圆。每个观测值都有两个输入变量,它们具有相同的比例,类输出值为0或1。这个数据集就是 “圆”数据集。我们可以使用make_circles()方法生成观测结果。我们为数据添加噪声和随机数生成器,以防每次运行代码时使用相同的样本。# generate 2d classification datasetX, y = make_circles(n_samples=100, noise=0.1, random_state=1)我们可以用x和y坐标绘制一个数据集,并将观察到的颜色定义为类值。生成和绘制数据集的代码如下:# generate two circles datasetfrom sklearn.datasets import make_circlesfrom matplotlib import pyplotfrom pandas import DataFrame# generate 2d classification datasetX, y = make_circles(n_samples=100, noise=0.1, random_state=1)# scatter plot, dots colored by class valuedf = DataFrame(dict(x=X[:,0], y=X[:,1], label=y))colors = {0:‘red’, 1:‘blue’}fig, ax = pyplot.subplots()grouped = df.groupby(’label’)for key, group in grouped: group.plot(ax=ax, kind=‘scatter’, x=‘x’, y=‘y’, label=key, color=colors[key])pyplot.show()运行以上代码,会创建一个散点图,散点图展示每个类中观察到的同心圆形状。我们可以看到,因为噪声,圆圈并不明显。这是一个特别好的测试问题,因为类不可能用一条直线表示,比如它不是线性可微分的,在这种情况下,就需要使用非线性方法来解决,比如神经网络。在这里,我们只生成了100个样本,这对于神经网络来说,样本是相当少了。但是它提供了训练数据集的过拟合现象,并且在测试数据及上的误差更大:这是使用正则化的一个特别好的例子。除此之外,这个样本集中有噪声,这就使神经网络模型有机会学习不一致样本的各个方面。多层感知器的过拟合我们可以创建一个MLP模型来解决这个二元分类问题。该模型将具有一个隐藏层,它的节点比解决该问题所需节点要多得多,从而产生过拟合。另外,我们训练模型的时间也大大超过正常训练模型所需要的时间。在定义模型之前,我们将数据集拆分为训练集和测试集:30个训练数据来训练模型和70个测试数据来评估拟合模型性能。# generate 2d classification datasetX, y = make_circles(n_samples=100, noise=0.1, random_state=1)# split into train and testn_train = 30trainX, testX = X[:n_train, :], X[n_train:, :]trainy, testy = y[:n_train], y[n_train:]接下来,我们可以定义模型。在隐藏层中使用500个节点和矫正过得线性激活函数;在输出层中使用S型激活函数预测类的值(0或1)。该模型使用二元交叉熵损失函数进行优化,这个函数适用于二元分类问题和梯度下降到有效Adam问题。# define modelmodel = Sequential()model.add(Dense(500, input_dim=2, activation=‘relu’))model.add(Dense(1, activation=‘sigmoid’))model.compile(loss=‘binary_crossentropy’, optimizer=‘adam’, metrics=[‘accuracy’])将训练数据训练4000次,默认每次训练次数为32。 然后用测试数据集验证该模型性能,代码如下。# fit modelhistory = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=4000, verbose=0)测试的方法如下。# evaluate the model_, train_acc = model.evaluate(trainX, trainy, verbose=0), test_acc = model.evaluate(testX, testy, verbose=0)print(‘Train: %.3f, Test: %.3f’ % (train_acc, test_acc))最后,在每次训练的时候绘制模型的性能。如果模型在训练数据集时的确是过拟合,那么我们训练集上的准确度线图更加准确,并且准确度随着模型学习训练数据集中的统计噪声而再次下降。# plot historypyplot.plot(history.history[‘acc’], label=‘train’)pyplot.plot(history.history[‘val_acc’], label=‘test’)pyplot.legend()pyplot.show()将以上所有代码组合起来,如下所示。# mlp overfit on the two circles datasetfrom sklearn.datasets import make_circlesfrom keras.layers import Densefrom keras.models import Sequentialfrom matplotlib import pyplot# generate 2d classification datasetX, y = make_circles(n_samples=100, noise=0.1, random_state=1)# split into train and testn_train = 30trainX, testX = X[:n_train, :], X[n_train:, :]trainy, testy = y[:n_train], y[n_train:]# define modelmodel = Sequential()model.add(Dense(500, input_dim=2, activation=‘relu’))model.add(Dense(1, activation=‘sigmoid’))model.compile(loss=‘binary_crossentropy’, optimizer=‘adam’, metrics=[‘accuracy’])# fit modelhistory = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=4000, verbose=0)# evaluate the model, train_acc = model.evaluate(trainX, trainy, verbose=0), test_acc = model.evaluate(testX, testy, verbose=0)print(‘Train: %.3f, Test: %.3f’ % (train_acc, test_acc))# plot historypyplot.plot(history.history[‘acc’], label=‘train’)pyplot.plot(history.history[‘val_acc’], label=‘test’)pyplot.legend()pyplot.show()运行以上代码,我们可以看到模型在训练和测试数据集上的性能:模型在训练数据集上的性能优于测试数据集,这是过度拟合的一个可能标志。鉴于神经网络和训练算法的随机性,模型的测试结果可能会有所不同。由于该模型严重过拟合,该模型在同一数据集上运行的结果差异并不会很大。Train: 1.000, Test: 0.757下图为模型在训练和测试集上的精度图,我们可以看到过拟合模型的预期性能,其中测试精度增加到一定值以后,再次开始减小。使用Dropout正则化减少MLP过拟合我们使用Dropout正则化更新这个示例,即在隐藏层和输出层之间插入一个新的Dropout层来实现。在这里,指定Dropout rate=0.4。# define modelmodel = Sequential()model.add(Dense(500, input_dim=2, activation=‘relu’))model.add(Dropout(0.4))model.add(Dense(1, activation=‘sigmoid’))model.compile(loss=‘binary_crossentropy’, optimizer=‘adam’, metrics=[‘accuracy’])下面列出了隐藏层后添加了dropout层的完整更新示例。# mlp with dropout on the two circles datasetfrom sklearn.datasets import make_circlesfrom keras.models import Sequentialfrom keras.layers import Densefrom keras.layers import Dropoutfrom matplotlib import pyplot# generate 2d classification datasetX, y = make_circles(n_samples=100, noise=0.1, random_state=1)# split into train and testn_train = 30trainX, testX = X[:n_train, :], X[n_train:, :]trainy, testy = y[:n_train], y[n_train:]# define modelmodel = Sequential()model.add(Dense(500, input_dim=2, activation=‘relu’))model.add(Dropout(0.4))model.add(Dense(1, activation=‘sigmoid’))model.compile(loss=‘binary_crossentropy’, optimizer=‘adam’, metrics=[‘accuracy’])# fit modelhistory = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=4000, verbose=0)# evaluate the model, train_acc = model.evaluate(trainX, trainy, verbose=0)_, test_acc = model.evaluate(testX, testy, verbose=0)print(‘Train: %.3f, Test: %.3f’ % (train_acc, test_acc))# plot historypyplot.plot(history.history[‘acc’], label=‘train’)pyplot.plot(history.history[‘val_acc’], label=‘test’)pyplot.legend()pyplot.show()运行以上代码,查看模型在训练和测试集上的性能。你所得到的结果可能会有所不同,在这种情况下,该模型具有较高的方差。在这里,我们可以看到,Dropout导致训练集的准确度有所下降,从100%降至96%,而测试集的准确度从75%提高到81%。Train: 0.967, Test: 0.814从这里我们可以看出,该模型已经不再适合训练数据集了。尽管使用Dropout正则化时会产生很多噪音,训练数据集和测试数据集的模型精度持续增加。在后续学习中,你可以进一步探索以下这些问题:1.输入Dropout。在输入变量上使用Dropout正则化,更新示例,并比较结果。2.权重约束。在隐藏层添加max-norm权重约束,更新示例,并比较结果。3.反复评估。更新示例,重复评估过拟合和Dropout模型,总结并比较平均结果。4.网格搜索率。创建Dropout概率的网格搜索,并报告Dropout rate和测试数据集准确度二者之间的关系。拓展阅读论文1.《使用卷积神经网络进行高效的对象本地化,2015》2.《递归神经网络中的理论Dropout应用,2016》博文1.基于Keras深度学习模型中的Dropout正则化2.如何使用LSTM网络的Dropout进行时间序列预测API1.Keras Regularizers API2.Keras Core Layers API3.Keras Convolutional Layers API4.Keras Recurrent Layers API5.sklearn.datasets.make_circles API总结阅读完本文,你已经了解了如何将深度学习正则化添加到深度学习神经网络模型的API中。具体来说,有以下几个内容:1.如何使用Keras API创建Dropout层。2.如何使用Keras API将Dropout正则化添加到MLP、CNN和RNN层。3.如何向现有模型中添加Dropout正则化,以此减少过拟合。本文作者:【方向】阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

December 10, 2018 · 3 min · jiezi

MQ消息队列产品测试

一、产品背景消息队列是阿里巴巴集团自主研发的专业消息中间件。 产品基于高可用分布式集群技术,提供消息订阅和发布、消息轨迹查询、定时(延时)消息、资源统计、监控报警等一系列消息云服务,是企业级互联网架构的核心产品。 MQ 目前提供 TCP 、MQTT 两种协议层面的接入方式,支持 Java、C++ 以及 .NET 不同语言,方便不同编程语言开发的应用快速接入 MQ 消息云服务。 用户可以将应用部署在阿里云 ECS、企业自建云,或者嵌入到移动端、物联网设备中与 MQ 建立连接进行消息收发,同时本地开发者也可以通过公网接入 MQ 服务进行消息收发。从官方文档中看到MQ消息队列的产品为一个提供消息服务的中间件,可以提供端到云的消息服务,这个端的覆盖面包括了移动端和IOT物联网设备,并且为了支持IOT的需要除TCP协议外提供了MQTT来支持物联网设备的消息服务,在云上的支持不止包括阿里云,可以支持用户将服务部署在企业自建云上。作为PAAS层的服务支持用户通过API的方式将消息队列服务集成在自己的平台上,目前在产品的结构上分成两部分,移动端和物联网的消息队列服务单独作为一个子产品MQ FOR IOT提供服务,这项服务和MQ主服务比主要的区别就是增加了对MQTT通讯协议的支持。从编程语言来看,因为MQ FOR IOT是面向移动端和物联网,所以需要支持的编程语言更多,包括ANDROID、IOS和PYTHON环境在消息队列服务中都已经支持。二、消息队列MQ产品测试开通服务进入控制台后看到菜单将消息队列服务清晰的分成两部分,支持MQTT的微消息服务单独列出子菜单,菜单选项按照功能分成三大部分,生产管理类子菜单,消息查询追踪类子菜单和监控报警类子菜单。TOPIC是消息队列服务中一个重要概念,用于区分消息的不同类型,比如在一次交易中,用户对于商品所下的订单和支付的订单虽然针对的是同一件事情,但是对于消息队列来说,这两种消息的功能和类型有明显的不同,可以用不同的TOPIC来区分,在TOPIC下还有个标签TAG用于二级分类,如一个用户对不同商品的购买订单可以作为不同的TAG。针对消息的配置来讲,需要定义消息的名字和消息的类型。在类型上普通消息、事务消息、定时消息、分区消息等都可以将不同类型的TOPIC根据类型区分。将TOPIC按什么类型进行分类及归入哪个分类需要用户根据实际情况进行确定。除了TOPIC外,对于一条消息,还有三个独特的属性可以为查询提供方便,生产者的编号(PRODUCT ID)、消费者的编号(CONSUMER ID)和消息编号(MESSAGE ID),加上TOPIC的配置,可以准确定义海量消息中的每一条,方便查询和监控等功能的支持。消息路由是指的在不同地域间的消息同步,需要配置源地域和TOPIC、目标地域和TOPIC,从最新写入源的消息开始进行同步。资源报表分成两个子项,生产者和消费者,可以对于消息的两个源头的情况进行查看,如果需要对于消息服务的可以在监控报警设置中进行配置,对于消息的报警项,有两个重要指标堆积量和消息延迟,分别从数量和时间对于消息服务的异常情况进行报警,通过短信方式通知用户。三、微消息队列MQ FOR IOT产品测试从微消息队列的按量付费的计费项目就可以看出物联网在消息通讯上的几个主要特征,即时连接数、订阅消息数和消息收发量。万物互联后物联网设备的消息数在这三个维度都会到达海量的程度,特别是即时连接这个特点和一般的MQ服务有很大不同,可以代表物联网中消息传递的特征。此外,微消息队列服务对于消息的分类同一般MQ服务不同的是,将TOPIC分成父TOPIC和子TOPIC的方式而不是TOPIC和TAG的分类方式,子TOPIC从属于父TOPIC,这个特点我想也是因为需要支持物联网的关系,因为传统下的消息都是针对应用比较多,但是物联网情况下,消息的类型如设备的状态、工业监测数据等会比一般情况多的多,并且消息服务的实时性要求更高,所以将TOPIC设置成父子从属关系更有利于对海量不同类型的消息进行区分。本文作者:朱祺阅读原文本文为云栖社区原创内容,未经允许不得转载。

December 6, 2018 · 1 min · jiezi

机器学习基础:(Python)训练集测试集分割与交叉验证

摘要: 本文讲述了如何用Python对训练集测试集进行分割与交叉验证。在上一篇关于Python中的线性回归的文章之后,我想再写一篇关于训练测试分割和交叉验证的文章。在数据科学和数据分析领域中,这两个概念经常被用作防止或最小化过度拟合的工具。我会解释当使用统计模型时,通常将模型拟合在训练集上,以便对未被训练的数据进行预测。在统计学和机器学习领域中,我们通常把数据分成两个子集:训练数据和测试数据,并且把模型拟合到训练数据上,以便对测试数据进行预测。当做到这一点时,可能会发生两种情况:模型的过度拟合或欠拟合。我们不希望出现这两种情况,因为这会影响模型的可预测性。我们有可能会使用具有较低准确性或不常用的模型(这意味着你不能泛化对其它数据的预测)。什么是模型的过度拟合(Overfitting)和欠拟合(Underfitting)?过度拟合过度拟合意味着模型训练得“太好”了,并且与训练数据集过于接近了。这通常发生在模型过于复杂的情况下,模型在训练数据上非常的准确,但对于未训练数据或者新数据可能会很不准确。因为这种模型不是泛化的,意味着你可以泛化结果,并且不能对其它数据进行任何推断,这大概就是你要做的。基本上,当发生这种情况时,模型学习或描述训练数据中的“噪声”,而不是数据中变量之间的实际关系。这种噪声显然不是任何新数据集的一部分,不能应用于它。欠拟合与过度拟合相反,当模型欠拟合的时候,它意味着模型不适合训练数据,因此会错过数据中的趋势特点。这也意味着该模型不能被泛化到新的数据上。你可能猜到了,这通常是模型非常简单的结果。例如,当我们将线性模型(如线性回归)拟合到非线性的数据时,也会发生这种情况。不言而喻,该模型对训练数据的预测能力差,并且还不能推广到其它的数据上。值得注意的是,欠拟合不像过度拟合那样普遍。然而,我们希望避免数据分析中的这两个问题。你可能会说,我们正在试图找到模型的欠拟合与过度拟合的中间点。像你所看到的,训练测试分割和交叉验证有助于避免过度拟合超过欠拟合。训练测试分割正如我之前所说的,我们使用的数据通常被分成训练数据和测试数据。训练集包含已知的输出,并且模型在该数据上学习,以便以后将其泛化到其它数据上。我们有测试数据集(或子集),为了测试模型在这个子集上的预测。我们将使用Scikit-Learn library,特别是其中的训练测试分割方法。我们将从导入库开始:快速地看一下导入的库:Pandas —将数据文件作为Pandas数据帧加载,并对数据进行分析;在Sklearn中,我导入了数据集模块,因此可以加载一个样本数据集和linear_model,因此可以运行线性回归;在Sklearn的子库model_selection中,我导入了train_test_split,因此可以把它分成训练集和测试集;在Matplotlib中,我导入了pyplot来绘制数据图表;好了,一切都准备就绪,让我们输入糖尿病数据集,将其转换成数据帧并定义列的名称:现在我们可以使用train_test_split函数来进行分割。函数内的test_size=0.2表明了要测试的数据的百分比,通常是80/20或70/30左右。# create training and testing varsX_train, X_test, y_train, y_test = train_test_split(df, y, test_size=0.2)print X_train.shape, y_train.shapeprint X_test.shape, y_test.shape(353, 10) (353,)(89, 10) (89,)现在我们将在训练数据上拟合模型:# fit a modellm = linear_model.LinearRegression()model = lm.fit(X_train, y_train)predictions = lm.predict(X_test)正如所看到的那样,我们在训练数据上拟合模型并尝试预测测试数据。让我们看一看都预测了什么:predictions[0:5]array([ 205.68012533, 64.58785513, 175.12880278, 169.95993301, 128.92035866])注:因为我在预测之后使用了[0:5],它只显示了前五个预测值。去掉[0:5]的限制就会使它输出我们模型创建的所有预测值。让我们来绘制模型:## The line / modelplt.scatter(y_test, predictions)plt.xlabel(“True Values”)plt.ylabel(“Predictions”)打印准确度得分:print “Score:”, model.score(X_test, y_test)Score: 0.485829586737总结:将数据分割成训练集和测试集,将回归模型拟合到训练数据,基于该数据做出预测,并在测试数据上测试预测结果。但是训练和测试的分离确实有其危险性,如果我们所做的分割不是随机的呢?如果我们数据的一个子集只包含来自某个州的人,或者具有一定收入水平但不包含其它收入水平的员工,或者只有妇女,或者只有某个年龄段的人,那该怎么办呢?这将导致过度拟合,即使我们试图避免,这就是交叉验证要派上用场了。交叉验证在前一段中,我提到了训练测试分割方法中的注意事项。为了避免这种情况,我们可以执行交叉验证。它非常类似于训练测试分割,但是被应用于更多的子集。意思是,我们将数据分割成k个子集,并训练第k-1个子集。我们要做的是,为测试保留最后一个子集。有一组交叉验证方法,我来介绍其中的两个:第一个是K-Folds Cross Validation,第二个是Leave One Out Cross Validation (LOOCV)。K-Folds 交叉验证在K-Folds交叉验证中,我们将数据分割成k个不同的子集。我们使用第k-1个子集来训练数据,并留下最后一个子集作为测试数据。然后,我们对每个子集模型计算平均值,接下来结束模型。之后,我们对测试集进行测试。这里有一个在Sklearn documentation上非常简单的K-Folds例子:fromsklearn.model_selection import KFold # import KFoldX = np.array([[1, 2], [3, 4], [1, 2], [3, 4]]) # create an arrayy = np.array([1, 2, 3, 4]) # Create another arraykf = KFold(n_splits=2) # Define the split - into 2 folds kf.get_n_splits(X) # returns the number of splitting iterations in the cross-validatorprint(kf) KFold(n_splits=2, random_state=None, shuffle=False)让我们看看结果:fortrain_index, test_index in kf.split(X): print(“TRAIN:”, train_index, “TEST:”, test_index)X_train, X_test = X[train_index], X[test_index]y_train, y_test = y[train_index], y[test_index](‘TRAIN:’, array([2, 3]), ‘TEST:’, array([0, 1]))(‘TRAIN:’, array([0, 1]), ‘TEST:’, array([2, 3]))正如看到的,函数将原始数据拆分成不同的数据子集。这是个非常简单的例子,但我认为它把概念解释的相当好。弃一法交叉验证(Leave One Out Cross Validation,LOOCV)这是另一种交叉验证的方法,弃一法交叉验证。可以在Sklearn website上查看。在这种交叉验证中,子集的数量等于我们在数据集中观察到的数量。然后,我们计算所有子集的平均数,并利用平均值建立模型。然后,对最后一个子集测试模型。因为我们会得到大量的训练集(等于样本的数量),因此这种方法的计算成本也相当高,应该在小数据集上使用。如果数据集很大,最好使用其它的方法,比如kfold。让我们看看Sklearn上的另一个例子:fromsklearn.model_selectionimportLeaveOneOutX = np.array([[1, 2], [3, 4]])y = np.array([1, 2])loo = LeaveOneOut()loo.get_n_splits(X)fortrain_index, test_indexinloo.split(X): print(“TRAIN:”, train_index, “TEST:”, test_index)X_train, X_test = X[train_index], X[test_index]y_train, y_test = y[train_index], y[test_index] print(X_train, X_test, y_train, y_test)以下是输出:(‘TRAIN:’, array([1]), ‘TEST:’, array([0]))(array([[3, 4]]), array([[1, 2]]), array([2]), array([1]))(‘TRAIN:’, array([0]), ‘TEST:’, array([1]))(array([[1, 2]]), array([[3, 4]]), array([1]), array([2]))那么,我们应该使用什么方法呢?使用多少子集呢?拥有的子集越多,我们将会由于偏差而减少误差,但会由于方差而增加误差;计算成本也会上升,显然,拥有的子集越多,计算所需的时间就越长,也将需要更多的内存。如果利用数量较少的子集,我们减少了由于方差而产生的误差,但是由于偏差引起的误差会更大。它的计算成本也更低。因此,在大数据集中,通常建议k=3。在更小的数据集中,正如我之前提到的,最好使用弃一法交叉验证。让我们看看以前用过的一个例子,这次使用的是交叉验证。我将使用cross_val_predict函数来给每个在测试切片中的数据点返回预测值。# Necessary imports: from sklearn.cross_validation import cross_val_score, cross_val_predictfrom sklearn import metrics之前,我给糖尿病数据集建立了训练测试分割,并拟合了一个模型。让我们看看在交叉验证之后的得分是多少:# Perform 6-fold cross validationscores = cross_val_score(model, df, y, cv=6)print “Cross-validated scores:”, scoresCross-validated scores: [ 0.4554861 0.46138572 0.40094084 0.55220736 0.43942775 0.56923406]正如你所看到的,最后一个子集将原始模型的得分从0.485提高到0.569。这并不是一个惊人的结果,但我们得到了想要的。现在,在进行交叉验证之后,让我们绘制新的预测图:# Make cross validated predictionspredictions = cross_val_predict(model, df, y, cv=6)plt.scatter(y, predictions)你可以看到这和原来的图有很大的不同,是原来图的点数的六倍,因为我用的cv=6。最后,让我们检查模型的R²得分(R²是一个“表示与自变量分离的可预测的因变量中方差的比例的数量”)。可以看一下我们的模型有多准确:accuracy = metrics.r2_score(y, predictions)print “Cross-Predicted Accuracy:”, accuracyCross-Predicted Accuracy: 0.490806583864本文作者:【方向】阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

November 23, 2018 · 2 min · jiezi

这一年多来,阿里Blink测试体系如何从0走向成熟?

引言Apache Flink是面向数据流处理和批处理的分布式开源计算框架,2016年阿里巴巴引入Flink框架,改造为Blink。2017年,阿里整合了所有流计算产品,决定以Blink引擎为基础,打造一款全球领先的实时计算引擎。当年双11,Blink支持了二十多个事业部/群,同时运行了上千个实时计算job,每秒处理的日志数峰值达到惊人的4.7亿。因此Blink的可靠性和稳定性保障变得极其重要,搜索事业部的质量团队为此专门成立了Blink测试小组,通过一年多的努力,建立了从代码质量到持续集成再到预发测试的全面的测试体系,帮助Blink的质量取得大幅提高。Blink测试平台介绍Blink测试团队为Blink质量量身打造Blink测试平台,内容如下图所示:Blink测试平台包含了三个测试阶段: 代码质量校验阶段,主要进行静态代码扫描、单元测试和基于minicluster的测试;集成测试阶段,主要是进行功能测试、性能测试和带有破坏性的稳定性测试;而预发测试阶段,主要是利用用户的job进行仿真测试,并在版本发布之前做最后的版本兼容性测试。平台选取部分测试集合纳入到precommit的验证中,可尽早发现代码中问题,而大规模的功能、性能、稳定性测试,通常作为dailybuild的集合。另外,Blink测试平台建立了较为完善的质量度量体系,除去对代码覆盖率的统计及变化的分析,还可一键生成测试报告,并对不同版本的质量进行横向对比。代码质量校验阶段代码质量校验阶段是整个Blink质量保障的基础。主要包含单元测试,利用aone提供的"集团代码规约扫描"工具对代码进行规范扫描,单机运行的基于minicluster的集成测试,只有这三个阶段都测试通过后才允许Blink代码提交到项目git。功能测试Blink功能测试框架使用defender,该框架是由pytest[1]改造而来,很好地支持了BlinkSql测试的特性,并支持第三方插件的引入。在测试集群中可以端到端的对某一场景进行精准测试。具体流程如下图所示,支持IDE和Jenkins两种触发模式,yarn_job、yarn_session和local三种case运行调度模式。执行结束后通过web页面或邮件的形式对结果进行展示,并对运行结果进行持久化。具有如下优势:1、case的统一调度与精细化管理:现在Blink在defender上有12个场景4000多个case,可以每天定时进行dailyrun,如果某一类别的case出现问题可单独执行,并可在页面上显示详情。2、case的三种运行模式满足了不同场景的测试需求:其中yarn_session模式对一个模块中存在sqlCase的场景较为适用,可大大减少与Yarn交互的时间。3、case灵活配置:不仅可以支持系统配置,对每个case集所需资源(slot,memory等)或集群其他配置的不同进行单独配置。4、一个case可同时支持批和流两种运行类型。5、client类型灵活扩展:可对现有数据存储和服务进行集成和扩展。现已支持多类型data store读写服务,yarn_session的启动,Blink job交互等。性能测试Blink作为实时大数据处理引擎,其对单位时间内的数据处理能力和数据处理的实时性提出了非常严苛的要求。因此,性能测试是整个Blink测试中非常重要的一环,是衡量Blink新版本能否发布的核心标准之一。Blink的性能测试主要包含Operator性能测试、SQL性能测试和runtime性能测试:Operator指构成SQL语义的一个原子操作,例如Sum,Aggregate等,是一个不能再分割的算子。Operator的性能测试主要用于监控单个算子在整个开发过程中的性能变化,以保证局部处理的优化和提高。目前,Operator的测试分成两个部分:单个算子的性能测试和算子组合的性能测试。Operator测试以Daily Run的方式反馈性能的变化。SQL性能测试主要用于监控版本开发过程中单个SQL的性能变化。TPCH和TPCDS是业界SQL标准性能测试集,分别有22和103个测试用例。测试平台将其引入到Blink性能测试中,以更全面地衡量Blink的性能变化。Runtime性能测试主要为了保障runtime层面性能不回退,主要包含端到端性能测试和模块性能测试。端到端性能测试首先根据梳理出测试场景,关注各场景job在指定数据量下的job运行时间,模块性能测试主要包含网络层性能测试,调度层性能测试,failover性能测试等,更关注在特定场景下job的处理时间。性能测试未来规划是将E2E性能测试、模块级别性能测试和参数调整整体联动起来,使其能够更好协助开发定位性能问题root cause和查看参数调优效果。稳定性测试对于支持高并发、多节点,集群物理环境复杂的分布式系统来说,类似磁盘打满、网络延迟等物理节点的异常很难避免。Blink作为一个高可用的分布式系统,必然要做到在异常情况下也能保证系统的稳定运行及数据的正常产出。“避免失败的最好方法就是不断地失败”,因此,在Blink任务运行期间将可能发生的异常模拟出来,就能够验证Blink的稳定性。我们把异常场景分为两类:一类是"黑猴子",该类场景与运行环境相关,包括机器重启、网络异常、磁盘异常、cpu异常等,这部分异常主要用shell命令来模拟;另一类异常是"白猴子",此类场景与Blink job相关,包括rpc消息超时,task异常,heart beat消息超时等,主要通过byteman[2]软件注入的方式来实现。在稳定性测试中,monkey作为调度会随机选取上述异常场景进行组合,以模拟线上可能出现的所有异常场景。考虑到Blink支持任务failover的特性和稳定性测试的自动运行,我们把稳定性测试设定为一轮轮的迭代循环,每一轮迭代都包含释放出monkey,提交任务,等待job恢复,校验四个阶段,校验主要包含checkpoint,container及slot资源等是否符合预期,校验失败就报警,校验成功后通过后进入下一轮迭代,以验证任务在长时间运行下的任务稳定性。稳定性测试架构分为四层:组件层主要包含测试Blink job,monkeys和dumper;action层包含job启动,状态校验,输出校验等;执行层包含service,monkey操作等,monkey操作时会根据ssh到具体机器,执行monkey操作;最上层是WebUI。详情如下图所示:预发测试Blink预发测试阶段主要通过克隆线上的真实任务和数据来进行复杂业务逻辑和大数据量的测试。因此,Blink 预发测试是对代码质量校验和集成测试的补充以及整个测试流程的完善,是Blink版本发布的最后一道关卡。Blink预发测试主要分为两个部分:仿真测试和兼容性测试。仿真测试仿真测试对Blink的功能、性能和稳定性等基础测试指标进行进一步地衡量,并将开发中的版本与当前的线上版本进行横向比较。因此,仿真测试能够尽早发现各种功能、性能退化和稳定性问题,从而提高上线版本的质量。仿真测试主要分为环境克隆,环境适配和测试运行三个阶段:环境克隆环境克隆是实现整个仿真测试的基础,包括线上任务的挑选、克隆和测试数据的采样。Blink的线上任务分散在多个不同的工程中,数量较多。虽然,每一个线上任务都有其内在的业务逻辑,但是,不同的任务可以根据其主要的处理逻辑进行归类,例如,以Agg操作为主的任务集合,以Sum操作为主的任务集合等,因此,Blink仿真测试需要对线上任务进行甄别,挑选出其中最具有代表性的任务。仿真测试的测试数据集是当前线上任务输入数据的采样,仅在数据规模上有差异,并且,可以根据测试需求的不同进行动态地调节,从而实现对测试目标的精确衡量。环境适配环境适配是仿真测试过程中的初始化阶段,主要进行测试用例的修改,使其能够正常运行。该过程主要包括两个步骤:更改测试数据输入源和测试结果输出地址和更新任务的资源配置。测试运行测试运行是仿真测试流程中的实际执行模块,包括测试用例的运行和结果反馈两个部分。Blink仿真测试包括功能测试、性能测试和稳定性测试等模块,不同的测试模块具有不同的衡量标准和反馈方式。这些测试模块的测试结果与代码质量校验和集成测试的结果一起构成Blink测试的结果集。性能测试和功能测试以仿真任务和采样数据作为输入,对比和分析任务在不同执行引擎上的执行过程和产出。其中,性能测试重点考察执行过程中不同执行引擎对资源的利用率、吞吐量等性能指标。功能测试则将执行的最终结果进行对比。需要特别指出的是,在功能测试中,线上版本的运行结果被假定为真,即当线上版本的执行结果与开发版本的执行结果不同时,认为开发版本的执行存在错误,需要修复开发中引入的错误。稳定性测试重点关注仿真测试任务在线上克隆环境、大数据量和长时间运行条件下的稳定性。其以Blink开发版本作为唯一的执行引擎,通过收集执行过程中的资源利用情况、吞吐量、failover等指标来进行度量。兼容性测试Blink兼容性测试主要用于发现Blink新、旧版本之间的兼容性问题,从而为线上任务升级Blink执行引擎的版本提供依据。目前,兼容性测试主要分为静态检查和动态运行两个阶段,其中,静态检查是整个兼容性测试的基础。静态检查静态检查主要用于分析线上任务在不同执行引擎下生成执行计划的不同,包括两个方面的内容:新的执行引擎生成执行计划的正确性及生成执行计划的时间长短。新、旧版本的执行引擎生成的执行计划是否兼容。在静态检查中,若新的执行引擎不能正确地生成执行计划,或者生成执行计划的时间超出预期,都可以认为静态检查失败,Blink新版本中存在异常或者缺陷,需要查找原因。当新版本能够正确地生成执行计划时,若新、旧版本的执行引擎生成的执行计划不兼容,那么,需要将对比结果反馈给开发人员以判断该执行计划的更改是否符合预期;若执行计划兼容或者执行计划的更改符合预期,则可以直接进行运行时测试。动态运行测试Blink动态运行测试利用仿真测试中的功能测试模块来进行任务的运行,是升级Blink新版本之前的最后一轮测试。若任务能够正常启动且测试结果符合预期,则认为该任务可以自动升级,反之,则需要人工介入进行手动升级。展望通过一年多的努力,Blink整体质量已经有很大幅度的提高,Blink的测试方法和工具也越来越成熟,Blink回馈社区之际,我们会逐步将测试工具一起输出,回馈更多的社区开发测试者,与此同时,随着Blink用户群的壮大,Blink业务开发者对于业务任务的质量保证需要日渐高涨,Blink测试团队未来会提供更多质量保证和开发效率工具,进一步提升Blink开发者工程效率。本文作者:溶月阅读原文本文来自云栖社区合作伙伴“阿里技术”,如需转载请联系原作者。

November 23, 2018 · 1 min · jiezi

Web 端的测试 Selenium 用法必备

大家都知道,基于Web端的测试的基础框架是需要Selenium做主要支撑的,这里边给大家介绍下Web测试核心之基于 Python 的 Selenium一、简单介绍Selenium 是用于测试 Web 应用程序用户界面 (UI) 的常用框架。它是一款用于运行端到端功能测试的超强工具。您可以使用多个编程语言编写测试,并且 Selenium 能够在一个或多个浏览器中执行这些测试。二、环境安装1.安装 python,这个。。。忽略,建议 python2.7。2.安装基于 python 的 selenium 依赖包,命令:pip install selenium==2.53.6,你懂的。3.安装浏览器驱动包,推荐使用 chrome 浏览器的 chromedriver.exe,对应 chrome 版本一定要对哦,不然运行不起来的,安装在哪?想放哪放哪,不过一般是放在 python 的根目录下。下载地址:chromedriver.storage.googleapis.com/index.html4.安装 PyCharm 2.7 左右版本,这个无脑安装~然后可自定义界面 UI 及编码风格,这个。。。忽略三、牛刀小试1.控制浏览器 #coding=utf-8from selenium import webdriverdriver = webdriver.Chrome()driver.get(“http://www.baidu.com”)driver.maximize_window() #将浏览器最大化显示driver.set_window_size(480, 800) #设置浏览器宽480、高800显示" driver.back() #后退driver.forward() #前进 driver.close() #关闭chromedriver.quit() # 退出chrome对Python开发感兴趣可以加705673780,群内会有不定期的发放免费的资料链接,这些资料都是从各个技术网站搜集、整理出来的,如果你有好的学习资料可以私聊发我,我会注明出处之后分享给大家。2.对象的定位通过 id 定位:find_element_by_id()通过 name 定位:find_element_by_name()通过 class 定位:find_element_by_class_name()通过 tag 定位:find_element_by_tag_name()通过 link 定位:find_element_by_link_text()通过 partial_link 定位:find_element_by_partial_link_text()通过 xpath 定位:find_element_by_xpath()通过 css 定位:find_element_by_css_selector()以上几种定位是常规操作,应该就基本够用了,但是有的时候就是会出现一些诡异的定位失效或者定位到了点击失效的问题,这个时候如果用js进行直接执行该事件,接下来介绍下非常规操作:id 定位:document.getElementById()name 定位:document.getElementsByName()tag 定位:document.getElementsByTagName()class 定位:document.getElementsByClassName()css 定位:document.querySelectorAll() search_js = “document.getElementsByName(‘wd’)[0].value=‘selenium’;”# 通过name定位,然后赋值“selenium” search_js2 = “document.querySelectorAll(’.s_ipt’)[0].value=‘selenium’;”# 通过css定位,然后赋值“selenium” button_js = “document.getElementById(‘su’).click();”# 通过id定位,然后执行单击操作 button_js2 = “document.getElementsByClassName(’s_btn’)[0].click()”# 通过className定位,然后执行单击操作 driver.execute_script(search_js2)#执行,execute_script(script, *args)以上几种定位是可以再度升级,可以利用 jQuery 定位一波,这里可参看之前总结的 JQ 选择器中的思维导图知识点(www.cnblogs.com/aoaoao/arti… JS,便忽略3.操作测试对象 #coding=utf-8from selenium import webdriverdriver = webdriver.Chrome()driver.get(“http://passport.kuaibo.com/login/")driver.find_element_by_id("user_name").clear() #清除输入框的默认内容driver.find_element_by_id(“user_name”).send_keys(“username”)driver.find_element_by_id(“user_pwd”).clear()driver.find_element_by_id(“user_pwd”).send_keys(“password”) #输入输入框的内容为“password”driver.find_element_by_id(“dl_an_submit”).click() #通过 submit() 来提交操作driver.find_element_by_id(“dl_an_submit”).submit()size=driver.find_element_by_id(“kw”).size #返回百度输入框的宽高 text=driver.find_element_by_id(“cp”).text #返回百度页面底部备案信息#返回元素的属性值,可以是 id、name、type 或元素拥有的其它任意属性attribute=driver.find_element_by_id(“kw”).get_attribute(’type’) #返回元素的结果是否可见,返回结果为 True 或 Falseresult=driver.find_element_by_id(“kw”).is_displayed()driver.quit() #退出4.鼠标键盘事件from selenium import webdriver #引入 Keys 类包from selenium.webdriver.common.keys import Keys #引入 ActionChains 类from selenium.webdriver.common.action_chains import ActionChains… #鼠标事件 #定位到要操作的元素right =driver.find_element_by_xpath(“xx”) #对定位到的元素执行鼠标右键操作ActionChains(driver).context_click(right).perform() #对定位到的元素执行鼠标双击操作ActionChains(driver).double_click(right).perform() #对定位到的元素执行鼠标移动到上面的操作ActionChains(driver).move_to_element(right).perform() #对定位到的元素执行鼠标左键按下的操作ActionChains(driver).click_and_hold(right).perform() #定位元素的原位置element = driver.find_element_by_name(“xxx”) #定位元素要移动到的目标位置target = driver.find_element_by_name(“xxx”) #执行元素的移动操作ActionChains(driver).drag_and_drop(element, target).perform() #键盘事件 #删除多输入的一个 值driver.find_element_by_id(“kw”).send_keys(Keys.BACK_SPACE) #输入空格键+“教程”driver.find_element_by_id(“kw”).send_keys(Keys.SPACE)driver.find_element_by_id(“kw”).send_keys(u"教程”) #ctrl+x 剪切输入框内容driver.find_element_by_id(“kw”).send_keys(Keys.CONTROL,‘x’) #其余的键盘操作类似5.等待时间 #coding=utf-8from selenium import webdriver #导入 WebDriverWait 包from selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as EC #导入 time 包import timedriver = webdriver.Chrome()driver.get(“http://www.baidu.com”) #WebDriverWait()方法使用,显示等待,WebDriverWait(driver,超时时长,调用频率,忽略异常).until(可执行方法,超时返回的信息),这里可以调用EC来实现可执行方法is_disappeared = WebDriverWait(driver, 30, 1, (ElementNotVisibleException)).until_not(lambda x: x.find_element_by_id(“kw”).is_displayed()) #until(method, message=’ ’),调用该方法提供的驱动程序作为一个参数,直到返回值不为 Falseelement.send_keys(“selenium”) #添加智能等待,隐时等待driver.implicitly_wait(30)driver.find_element_by_id(“su”).click() #添加固定休眠时间,强制等待time.sleep(5)driver.quit()6.组对象定位及层级定位,呃,忽略7.多窗口处理 #coding=utf-8from selenium import webdriver import timedriver = webdriver.Chrome()driver.get(“http://www.baidu.com/") #获得当前窗口nowhandle=driver.current_window_handle #打开注册新窗口driver.find_element_by_name(“tj_reg”).click()allhandles=driver.window_handles #循环判断窗口是否为当前窗口for handle in allhandles: if handle != nowhandle: driver.switch_to_window(handle) print ’now register window!’#切换到邮箱注册标签driver.find_element_by_id(“mailRegTab”).click()time.sleep(5)driver.close() #回到原先的窗口driver.switch_to_window(nowhandle)driver.find_element_by_id(“kw”).send_keys(u"注册成功!")time.sleep(3) #ifrome处理 #这里会自动识别id,name,如果没有则可以将元素通过选择器找到,然后输入该元素即可driver.switch_to_frame(“f1”)element = driver.find_element_by_id(“kw”)driver.switch_to_frame(element)driver.quit()&emsp;8.提示窗口处理 #coding=utf-8from selenium import webdriver import timedriver = webdriver.Chrome()driver.get(“http://www.baidu.com/") #点击打开搜索设置driver.find_element_by_name(“tj_setting”).click()driver.find_element_by_id(“SL_1”).click() #点击保存设置driver.find_element_by_xpath(”//div[@id=‘gxszButton’]/input”).click() #获取网页上的警告信息alert=driver.switch_to_alert() #接收警告信息alert.accept() #取消对话框(如果有的话)alert.dismiss() #输入值(如果有的话)alert.send_keys(“xxx”)9.控制浏览器滚动条,这个运用之前提示的jq语句即可实现10.cookie处理,主要用途在于处理验证码问题 #coding=utf-8from selenium import webdriver import timedriver = webdriver.Chrome()driver.get(“http://www.youdao.com”) #向 cookie 的 name 和 value 添加会话信息。driver.add_cookie({’name’:‘key-aaaaaaa’, ‘value’:‘value-bbbb’}) #遍历 cookies 中的 name 和 value 信息打印,当然还有上面添加的信息for cookie in driver.get_cookies(): print “%s -> %s” % (cookie[’name’], cookie[‘value’]) ##### 下面可以通过两种方式删除 cookie ##### # 删除一个特定的 cookiedriver.delete_cookie(“CookieName”) # 删除所有 cookiedriver.delete_all_cookies()time.sleep(2)driver.close()四、小结对Python开发感兴趣可以加705673780,群内会有大佬答疑,学习交流,免费学习资料可以领取。在熟悉了selenium常见的API基本操作后,这里便可以开展实际测试用例的设计了,一个良好的自动化测试用例起码符合一下五个条件:1、一个脚本是一个完整的场景,从用户登陆操作到用户退出系统关闭浏览器。2、一个脚本脚本只验证一个功能点,不要试图用户登陆系统后把所有的功能都进行验证再退出系统3、尽量只做功能中正向逻辑的验证,不要考虑太多逆向逻辑的验证,逆向逻辑的情况很多(例如手 号输错有很多种情况),验证一方面比较复杂,需要编写大量的脚本,另一方面自动化脚本本身比较脆弱, 很多非正常的逻辑的验证能力不强。(我们尽量遵循用户正常使用原则编写脚本即可)4、脚本之间不要产生关联性,也就是说编写的每一个脚本都是独立的,不能依赖或影响其他脚本。5、如果对数据进行了修改,需要对数据进行还原。6、在整个脚本中只对验证点进行验证,不要对整个脚本每一步都做验证。最后配合unittest或者testNG单元测试框架,实现分层、数据驱动、断言、截图、日志等全方位功能,得心应手的开展自动化测试工作。 ...

November 20, 2018 · 2 min · jiezi

基于Selenium + Python的web自动化框架

一、什么是Selenium?Selenium是一个基于浏览器的自动化工具,她提供了一种跨平台、跨浏览器的端到端的web自动化解决方案。Selenium主要包括三部分:Selenium IDE、Selenium WebDriver 和Selenium Grid: 1、Selenium IDE:Firefox的一个扩展,它可以进行录制回放,并可以把录制的操作以多种语言(例如java,python等)的形式导出成测试用例。 2、Selenium WebDriver:提供Web自动化所需的API,主要用作浏览器控制、页面元素选择和调试。不同的浏览器需要不同的WebDriver。 3、Selenium Grid:提供了在不同机器的不同浏览器上运行selenium测试的能力 本文使用Python结合Selenium WebDriver库进行自动化测试框架的搭建。二、自动化测试框架一个典型的自动化测试框架一般包括用例管理模块、自动化执行控制器、报表生成模块和log模块,这些模块相辅相成。接下来介绍一下各模块的逻辑单元:1、用例管理模块 用例管理模块包括新增、修改、删除等操作单元,这些单元又会涉及到用例书写模式,测试数据库的管理、可复用库等。2、自动化控制器 控制器是自动化用例执行的组织模块,主要是负责以什么方法执行我们的测试用例3、报表生成模块 主要负责执行用例后的生成报告,一般以HTML格式居多,信息主要是用例执行情况。另外还可以配置发送邮件功能。4、log模块 主要用来记录用例执行情况,以便于高效的调查用例失败信息以及追踪用例执行情况。三、自动化框架的设计和实现1、需求分析首先我们的测试对象是一个web平台,基于此平台设计的框架要包含测试用例管理、测试执行控制器、测试报告以及测试日志的生成。2、设计和实现 1)页面管理假设测试web对象为一个典型单页面应用,所以我们采用页面模式。页面模式是页面与测试用例之间的纽带,它将每个页面抽象成一个单独的类,为测试用例提供页面元素的定位和操作。 BaseClass作为父类只包含driver成员变量,用来标识Selenium中的WebDriver,以便在子类中定位页面元素。LoginClass和PageClass作为子类,可以提供页面元素的定位和操作方法。比如登录页面。从页面看,需要操作的元素分别为,登录用户名、密码、下次自动登录和登录按钮,具体实现代码如下:页面父类BaseClass.pyLoginClass继承自BaseClass,并进行登录的元素定位以及操作实现。代码中定位了username和password,并添加了设置用户名和密码的操作。2)公共库模块公共库模块是为创建测试用例服务的,主要包括常量、公共函数、日志、报告等。Common.py测试用例信息类用来标识测试用例,并且包括执行用例、执行结果信息,主要包括以下字段。日志主要用来记录测试用例执行步骤及产生的错误信息,不同的信息有不同的日志级别,比如Information,Warning,Critical和Debug。由于每个测试用例产生的日志条目比较少,所以在测试框架中只利用了最高级别的日志打印,即Debug级别,该级别也会将其他所有的日志级别的信息同样打印出来。在具体的实现中引用了Python标准库中的logging类库,以便更方便的控制日志输出。 3)测试用例仓库用例仓库主要用来组织自动化测试用例。每条测试用例都被抽象成一个独立的类,并且均继承自unittest.TestCase类。 Python中的unittest库提供了丰富的测试框架支持,包括测试用例的setUp和tearDown方法,在实现用例的过程中可以重写。依托页面管理和公共库模块实现的页面方法和公共函数,每一个测试用例脚本的书写都会非常清晰简洁。从这个测试用例中,我们可以看到Setup中定义了执行测试用例前的一些实例化工作tearDown对执行完测试做了清理和写日志文件工作测试步骤、测试数据和测试检查点非常清晰,易修改(比如用户名密码)日志级别仅有Debug,所以写日志仅需用同一Log方法3)用例执行模块执行模块主要用来控制测试用例脚本的批量执行,形成一个测试集。用例的执行引用了Python标准库中的subprocess来执行nosetests的shell命令,从而执行给定测试用例集中的用例。测试用例集是一个简单的纯文本文件,实现过程中利用了.txt文件testcases.txt用例前没有“#“标记的测试用例脚本会被执行,而有”#“标记的则会被忽略,这样可以很方便的控制测试集的执行,当然也可以创建不同的文件来执行不同的测试集。 四、需要改进的模块对于现有实现的测试框架,已经可以满足web对象的自动化需求,但还是有些可以改进提高的地方,比如:1)部分用例可以尝试数据驱动2)二次封装selenium的By函数,以便更高效定位元素3)没有进行持续化集成五、总结基于Selenium实现的web自动化框架不仅轻量级而且灵活,可以快速的开发自动化测试用例,结合本篇的框架设计和一些好的实践,希望对大家以后的web自动化框架的设计和实现有所帮助。

November 14, 2018 · 1 min · jiezi

前端调试入门

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~本文由MarsBoy发表于云+社区专栏1 控制台这里的控制台特指PC端浏览器进入开发者模式之后新打开的操作界面。常见的控制台有Chrome的控制台,Firefox的firebug。这些都能帮助我们调试前端问题。本手册将以Chrome浏览器控制台为例进行讲解。下图1为Chrome浏览器控制台,图2为Firefox控制台。图1 Chrome浏览器控制台图 2 Firefox浏览器控制台1.1脚本执行上图1中,点击tab3 进入“console”Tab页,即为脚本执行区域。在这里,可以输入任何可执行的js代码,回车即可执行并打印返回值。比如:输入“alert(‘hello,world’)”。即可在浏览器弹出弹框,显示‘hello,world’。1.2网络请求上图1中,点击tab5进入“Network”Tab页,可以看到当前的所有网络请求,包括url,请求参数,返回数据,请求cookie等详细信息。前面提到的流水号,就可以在这里通过查看具体接口返回值找到。网络请求截图,请见:图3.图3 Network截图1.3 cookie开发者工具中也可以查看cookie等本地存储相关数据(包括localStorage,sessionStorage等)。Tab6进入Application选项卡,即可查看相关数据,其中就有Cookie等详细信息。见图4示意。图4 cookie查看截图2 断点debug断点是指js代码在运行的过程中,我们需要人为中断执行,并查看此刻的运行时变量等相关信息的一种调试方法。2.1如何打断点以Chrome为例,打断点首先需要切换到Tab4 Source。找到需要打断点的文件和行,在此行之前点击一下即可。然后刷新页面或者执行其他可以让程序运行到断点位置的逻辑(比如触发一个事件,console裸调指定方法函数等)。下图5是在一个活动页面对注销逻辑打断点的示意图,图中,我们事先对login.js的913行打了一个断点,然后点击页面上的注销按钮,程序运行到断点位置暂停。快捷键:1) F8,恢复执行并跳到下一个断点2) F10,恢复执行并跳到下一个运行栈,一般为子函数。图5 打断点示意图2.2 如何查看运行时变量我们打断点是为了调查问题,程序运行到断点位置,可以直接鼠标移动到相关变量名或者方法上面查看它的值。如果想在运行到断点位置执行其它逻辑,可以直接在console区域运行相关脚本。3代理前端代理是指用本地文件替换网络文件的一个动作,代理可以用来调试问题。比如在本地文件中加上alert弹框弹出关键节点的信息,这可以有利于调试。特别是前端调试。或者当我们发现网络文件有问题的时候,需要修复问题,就可以本地改文件,然后代理,这样访问指定页面就可以看到修改后的效果。3.1常规代理常规代理指http代理,即代理http协议的各种网络请求。代理需要用到第三方代理工具:Windows上推荐用Fiddler,Mac上推荐用Charles。下面以Fiddler为例,介绍如何使用Fiddler进行常规代理。1.首先下载Fiddler,并安装。Fiddler实质上是一款基于网络抓包的Web Debuger工具。2.打开Fiddler,点击右侧的AutoResponder的Tab页,勾选图6所示的几个选项。3.在左侧网络列表中选择自己需要代理的请求,拖拽到右侧,进入下面的Rule Editor。在Rule Editor中把下面的输入框中的值设为本地文件,可以通过下拉选择“Find a File”弹出文件选择对话框进行选择。点击右下角的保存,代理设置完毕。重新刷新网页,请求即可,这时,指定的网络请求将会直接用本地的文件替代。图6 Fiddler代理配置示意3.2 https代理https是采用了SSL加密服务的Http协议,客户端与服务器间的通信是加密传输的,Fiddler如果需要做中间人,就必须拥有被代理域名的证书,可以解密远端数据,并且通过自己的证书传给浏览器。下面介绍如何配置https代理,可以参照图7.1.打开Fiddler,一次进入菜单:ToolsàFiddler Options àHTTPS2.勾选“Captrue HTTPS CONNECTs”,3.勾选“Decrypt HTTPS traffic”,选择“from all process”4.勾选“Ignore server certificate errors”5.点击右边“Actions”按钮,选择“Trust Root Certificate”选项,选择Yes安装证书。至此,Https代理已经配置完毕,如果要进行代理,剩下的步骤可以参照3.1。图7 HTTPS代理配置示意3.3手机代理移动端请求如何代理,这块也可以通过Fiddler完成。按照以下步骤完成设置:保证手机和电脑上的Fiddler在同一个网段(连接同一个Wifi,比如LabWifi)。1.打开Fiddler,进入ToolsàFiddler OptionsàConnections2.设置Port为一个固定数字,比如88883.勾选:“Allow remote computers to connect”4.勾选:“Reuse client connections“5.勾选:“Reuse server connections“6.设置手机wifi代理到PC的ip地址,并且端口设置为上面的固定数字,比如8888。此时完成设置,手机上的http请求也可以用fiddler抓取了。注:如果要进行手机上的https抓包,需要在以上步骤完成之后,手机访问PC的ip:port(和fiddler上设置的port保持一致),获取证书并安装之后才可以。4移动端debug技巧移动端调试指在安卓机/iphone/ipad等手持设备上调试前端页面逻辑的概念。这里将针对几种典型的情况给出最合适的debug方法。4.1 安卓 微信/手Q/QQ浏览器/腾讯产品APP这几种情况都有一个共性,就是app是腾讯的产品,而且在安卓手机上,我们知道安卓支持连接数据线结合Chrome内核的调试器进行inspector调试,故这几类情况我们将推荐使用TBS Studio进行调试。TBS Studio是QQ浏览器X5内核前端调试平台,支持断点调试,抓取网络请求,查看DOM结构,console控制台等,功能非常丰富。电脑上安装TBS studio之后,就可以将手机通过数据线连接到电脑,然后通过TBS Studio进行调试。具体的操作方法见 https://x5.tencent.com/4.2安卓其它环境/ios环境安卓其它环境下,或者ios环境下调试前端问题很难进行断点调试,只能采用最原始的打log,即alert弹框。这种环境下我们需要借助fiddler结合手机进行代理文件,并在代理的js文件中按需打alert。相关阅读【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识

November 8, 2018 · 1 min · jiezi