基于TableStore的海量气象格点数据解决方案实战

前言气象数据是一类典型的大数据,具有数据量大、时效性高、数据种类丰富等特点。气象数据中大量的数据是时空数据,记录了时间和空间范围内各个点的各个物理量的观测量或者模拟量,每天产生的数据量常在几十TB到上百TB的规模,且在爆发性增长。如何存储和高效的查询这些气象数据越来越成为一个难题。传统的方案常常采用关系型数据库加文件系统的方式实现这类气象数据的存储和实时查询,这种方案在可扩展性、可维护性和性能上都有一些缺陷,随着数据规模的增大,缺点越来越明显。最近几年,业界开始越来越多的基于分布式NoSQL来解决这一问题,比如基于TableStore来实现气象格点数据的存储和查询。TableStore是一款阿里自研的分布式NoSQL服务,可以提供超大规模的存储容量,支撑超大规模的并发访问和低延迟的性能,可以很好的解决气象数据的规模和查询性能问题。我们之前也写过相关的解决方案文章《基于云上分布式NoSQL的海量气象数据存储和查询方案》,也有一些客户基于这个方案进行了开发。出于减少客户开发难度,提供通用的实现的想法,我们最近开发了一个TableStore-Grid的Library,基于这个Library用户可以非常方便的实现气象格点数据的存储、查询和管理。本文作为一个实战文章,主要讲解这一解决方案的设计以及使用方式。背景格点数据的特点格点数据具有明显的多维特点,以模式系统每次产生的数据为例,一般包含以下五个维度:物理量,或者称为要素:温度、湿度、风向、风速等等。预报时效:未来3小时、6小时、9小时、72小时等等。高度。经度。纬度。当我们固定某一要素某一预报时效,那么高度、经度、纬度就构成一个三维网格数据,如下图所示(图片来自互联网)。每个格点代表了一个三维空间上的点,上面的数值为该点在某一预报时效(比如未来三小时)下,某一物理量(比如温度)的预报值。假设一个三维格点空间包含10个不同高度的平面,每个平面为一个2880 x 570的格点,每个格点保存一个4字节数据,那么这三维的数据量为2880 x 570 x 4 x 10, 大约64MB。这仅仅是某个模式系统对某个物理量某一时效下的一次预报,可见模式数据的总量是非常大的。格点数据的查询方式预报员会通过页面的形式浏览各种模式数据(格点数据),并进行数值模式预报。这个页面需要提供多种模式数据的查询方式,比如:查询一个经纬度平面的格点数据:比如未来三小时全球地面温度的格点数据,或者未来三小时浙江省地面温度数据。查询某个格点的时间序列数据:比如阿里云公司所在地未来3小时、未来6小时、一直到未来72小时的温度。查询不同物理量的数据:比如查询某一预报时效、某一高度、某一点的全部物理量的预报数据。查询不同模式系统产生的数据:比如同时查询欧洲中心的某一模式数据和中国气象机构产生的对应数据等。上面提到,一个格点数据集一般是一个五维结构,各种查询方式实际上就是对这个五维数据进行切分,比如查询某个平面,每个剖面,某个点序列,某个三维、四维子空间等等。而我们的方案设计要保证在各种查询条件的查询性能,这是数据查询方面的主要技术难点。基于TableStore的方案设计标准化格点数据模型首先,我们定义一个规整的五维网格数据为一个GridDataSet,表示一个格点数据集,按照维度顺序,其五维分别为:variable:变量,比如各种物理量。time:时间维度。z: z轴,一般表示空间高度x: x轴,一般表示经度或纬度。y:y轴,一般表示经度或纬度。GridDataSet = F(variable, time, z, x, y)。一个GridDataSet除了包含五维数据,以及各个维度的长度等外,还包含一些其他信息:GridDataSetId:唯一标记这个GridDataSet的Id。Attributes:自定义属性信息,比如该数据的产生时间、数据来源、预报类型等等。用户可以自由定义自定义属性,也可以给某些属性建立索引,建立索引后就可以通过各种组合条件来查询符合条件的数据集。举个例子来说,假设某种气象预报,每次预报未来72小时的每个整点的各个高度、各个经纬度的各种物理量,则这次预报就是一个标准的五维数据,是一个单独的数据集(GridDataSet),下一次相同的预报则是另一个数据集,这两个数据集需要有不同的GridDataSetId。这两个数据集比较类似,只是起报时间不同,但是因为起报时间不在五维模型中(五维内的时间为一次预报中的未来不同时刻),所以属于不同的数据集,起报时间可以作为数据集的自定义属性。本方案中,也支持对自定义属性设置条件进行检索。数据存储方案我们设计了两张表分别存储数据集(GridDataSet)的meta和data,meta表示这个数据集的各种元数据,比如GridDataSetId、各维度长度、自定义属性等等,data表示这个数据集里实际的网格数据。data相比meta在数据大小上要大很多。为什么要分为meta和data两张表分开存储,主要是出于这样的考虑:用户会有根据多种条件查询数据集的要求,比如查询最近有哪些数据集已经完成入库,或者查询表中有哪些某种类型的数据集等。传统方案中主要是通过MySQL等关系型数据库来存储,在本方案中我们通过单独的meta表来存储,并通过TableStore的多元索引功能来实现多条件的组合查询和多种排序方式,相比传统方案更加易用。在查询格点数据之前,一般要知道格点数据中各维度的长度等信息,这些信息就是存储在meta表中的,即需要先查询meta表,再查询data表。因为meta数据一般都很小,因此查询效率相比查询data要高,多一次查询并不会明显增加延迟。meta表设计meta表的设计比较简单,主键只有一列,记录GridDataSetId,因为GridDataSetId就可以唯一标记一个GridDataSet。各种系统属性和自定义属性保存在meta表的属性列中。查询meta表有两种方式,一种是通过GridDataSetId直接查询,另外一种是通过多元索引,可以根据多种属性条件组合进行查询,比如筛选某种类型的数据,按照入库时间从新到老返回等。data表设计data表的设计要解决五维数据在不同的切分模式下的查询效率问题,不能简单直接的对数据进行存储。首先,为了查询效率最高,我们要尽量减少一次查询需要扫描的数据量。一个数据集的数据量可能在几GB的级别,但是一次查询往往只需要其中的几MB的数据,如果无法高效的定位要查询的数据,那么就要扫描全部的几GB的数据,从中筛选出符合某个范围的数据,显然效率是很低的。那么怎么才能做到高效的定位到需要的数据之中呢?我们首先设计一种表结构设计方式,我们使用四列主键列,分别为:GridDataSetId:数据集Id,唯一标记这个数据集。Variable:变量名,即五维模型中的第一维。Time:时间,即五维模型中的第二维。Z:高度,即五维模型中的第三维。这四列主键列标记一行TableStore中的数据,这行数据需要保存后两维的数据,即一个格点平面。这种设计下,对于五维中的前三维,我们都可以通过主键列的值来定位,即对于前三维的每一种情况,都对应TableStore中的一行。因为前三维分别代表变量、时间和高度,一般而言不会特别的多,每个维度在几个到几十个的级别,我们可以通过一些并行查询的方法来加速查询速度。剩下的问题就在于后两维数据如何存储和查询。首先后两维代表了一个水平的平面,一般是一个经纬度网格,这两维的大小是比前三维要大很多的,每维在几百到几千的级别,随着数值预报越来越精细化,这个网格的大小还会成倍增加。这样的一个稠密的网格数据,我们不能把每个格点都用一列来保存,这样列的数量会非常多,存储效率也会非常的低。另一方面,如果我们把一个平面的格点数据存储到一列中,在整读整取时效率比较高,但是如果只读取某个点,就会读取很多的无效数据,效率又会变得比较低。因此我们采取一种折中的方案,对平面的二维数据再次进行切分,切分成更小的平面数据块,这样就可以做到只读取部分数据块,而不总是读取整个平面,因此极大的提高了查询性能。方案实现基于上面的存储方案,我们实现了一个TableStore-Grid的library,提供以下接口:public interface GridStore { /** * 创建相关的meta、data表,数据录入前调用。 * @throws Exception / void createStore() throws Exception; /* * 写入gridDataSet的meta信息。 * @param meta * @throws Exception / void putDataSetMeta(GridDataSetMeta meta) throws Exception; /* * 更新meta信息。 * @param meta * @throws Exception / void updateDataSetMeta(GridDataSetMeta meta) throws Exception; /* * 通过gridDataSetId获取meta。 * @param dataSetId * @return * @throws Exception / GridDataSetMeta getDataSetMeta(String dataSetId) throws Exception; /* * // 创建meta表的多元索引。 * @param indexName * @param indexSchema * @throws Exception / void createMetaIndex(String indexName, IndexSchema indexSchema) throws Exception; /* * 通过多种查询条件来查询符合条件的数据集。 * @param indexName 多元索引名。 * @param query 查询条件,可以通过QueryBuilder构建。 * @param queryParams 查询相关参数,包括offset、limit、sort等。 * @return * @throws Exception / QueryGridDataSetResult queryDataSets(String indexName, Query query, QueryParams queryParams) throws Exception; /* * 获取GridDataWriter用于写入数据。 * @param meta * @return / GridDataWriter getDataWriter(GridDataSetMeta meta); /* * 获取GridDataFetcher用于读取数据。 * @param meta * @return / GridDataFetcher getDataFetcher(GridDataSetMeta meta); /* * 释放资源。 / void close();}public interface GridDataWriter { /* * 写入一个二维平面。 * @param variable 变量名。 * @param t 时间维的值。 * @param z 高度维的值。 * @param grid2D 平面数据。 * @throws Exception / void writeGrid2D(String variable, int t, int z, Grid2D grid2D) throws Exception;}public interface GridDataFetcher { /* * 设置要查询的变量。 * @param variables * @return / GridDataFetcher setVariablesToGet(Collection<String> variables); /* * 设置要读取的各维度起始点和大小。 * @param origin 各维度起始点。 * @param shape 各维度大小。 * @return / GridDataFetcher setOriginShape(int[] origin, int[] shape); /* * 获取数据。 * @return * @throws Exception */ GridDataSet fetch() throws Exception;}下面我们分别给出数据录入、数据查询、数据集检索方面的示例。数据录入数据录入流程可以分为三部分:写入putDataSetMeta接口写入数据集的meta信息。通过GridDataWriter录入整个数据集的数据。通过updateDataSetMeta接口更新数据集的meta信息,标记数据已经录入完成。下面的例子中,我们读取一个NetCDF(气象格点数据常用的格式)文件,然后将其中的数据通过GridDataWriter录入到TableStore中。通过GridDataWriter每次写入时,只能写入一个二维平面,所以我们需要在外层进行3层循环,分别枚举变量维、时间维、高度维的值,然后读取对应的二维平面的数据进行录入。public void importFromNcFile(GridDataSetMeta meta, String ncFileName) throws Exception { GridDataWriter writer = tableStoreGrid.getDataWriter(meta); NetcdfFile ncFile = NetcdfFile.open(ncFileName); List<Variable> variables = ncFile.getVariables(); for (Variable variable : variables) { if (meta.getVariables().contains(variable.getShortName())) { for (int t = 0; t < meta.gettSize(); t++) { for (int z = 0; z < meta.getzSize(); z++) { Array array = variable.read(new int[]{t, z, 0, 0}, new int[]{1, 1, meta.getxSize(), meta.getySize()}); Grid2D grid2D = new Grid2D(array.getDataAsByteBuffer(), variable.getDataType(), new int[] {0, 0}, new int[] {meta.getxSize(), meta.getySize()}); writer.writeGrid2D(variable.getShortName(), t, z, grid2D); } } } }}数据查询GridDataFetcher支持对五维数据进行任意维度的查询。第一维是变量维,通过setVariablesToGet接口设置要读取哪些变量,其余四维通过设置起始点(origin)和读取的大小(shape)就可以实现任意维度读取。public Array queryByTableStore(String dataSetId, String variable, int[] origin, int[] shape) throws Exception { GridDataFetcher fetcher = this.tableStoreGrid.getDataFetcher(this.tableStoreGrid.getDataSetMeta(dataSetId)); fetcher.setVariablesToGet(Arrays.asList(variable)); fetcher.setOriginShape(origin, shape); Grid4D grid4D = fetcher.fetch().getVariable(variable); return grid4D.toArray();}多条件检索数据集本方案中,对Meta表建立多元索引后,可以支持通过各种组合条件来进行数据集检索,查询出符合条件的数据集,这个功能对于气象管理系统来说非常重要。下面举一个例子,假设我们要查询已经完成入库的,创建时间为最近一天的,来源为ECMWF(欧洲中期天气预报中心)或者NMC(全国气象中心),精度为1KM的气象预报,并按照创建时间从新到老排序,可以用以下代码实现:查询条件: (status == DONE) and (create_time > System.currentTimeMillis - 86400000) and (source == “ECMWF” or source == “NMC”) and (accuracy == “1km”)QueryGridDataSetResult result = tableStoreGrid.queryDataSets( ExampleConfig.GRID_META_INDEX_NAME, QueryBuilder.and() .equal(“status”, “DONE”) .greaterThan(“create_time”, System.currentTimeMillis() - 86400000) .equal(“accuracy”, “1km”) .query(QueryBuilder.or() .equal(“source”, “ECMWF”) .equal(“source”, “NMC”) .build()) .build(), new QueryParams(0, 10, new Sort(Arrays.<Sort.Sorter>asList(new FieldSort(“create_time”, SortOrder.DESC)))));是不是非常简单?这一部分功能利用了TableStore的多元索引,多元索引可以实现多字段组合查询、模糊查询、全文检索、排序、范围查询、嵌套查询、空间查询等功能,给元数据管理场景提供了强大的底层能力。相关代码的获取可以在github上获取TableStore-Grid的实现代码和示例代码,欢迎大家体验、使用以及给我们提出建议。代码链接: https://github.com/aliyun/tablestore-examples/tree/master/demos/TableStore-Grid本文作者:亦征阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

April 16, 2019 · 2 min · jiezi

近万字长文,设计分布式系统需要考虑因素的都在这里!

本周三晚20:30,Kubernetes Master Class在线培训第三期《Kubernetes应用商店:Harbor与Istio》即将开播,点击http://live.vhall.com/231749318 即可免费预约注册!介 绍今天的应用程序可以说是分布式系统开发中的一项奇迹。基于不同的系统架构,构成应用程序的每个功能或服务可能在不同的系统上执行,而系统位于不同的地理位置,使用不同的计算机语言编写。应用程序的组件可能托管在一个功能强大的系统上,该系统由用户自己携带,并且可以和世界各地的应用程序组件或服务进行通信(他们都是数据中心的副本)。而令人惊讶的是,使用这些应用程序的用户通常并不会对复杂环境的请求作出响应。这样的请求包含了像本地时间、本地天气或前往酒店的方向等等。让我们慢慢开始介绍,看看使这一切成为可能的工业魔法,并思考开发人员在处理这种复杂性时应该牢记哪些思想和规则。系统设计的演变史图1:系统设计的历史演变从程序员编写应用程序,手工将它们编译成他们正在使用的机器的语言,然后使用切换开关将单个机器指令和数据直接输入到计算机的内存开始,应用程序开发已经走过了漫长的道路。随着处理器越来越强大,系统内存和在线存储容量增加,计算机网络能力显著增强,开发方法也产生了变化。现在,数据可以从地球的一段传递到另一端,而速度比早期电脑将数据从系统内存转移到处理器本身的速度还要快!让我们看看这一惊人转变中的一些亮点。单体设计早期的计算机程序都是基于单体设计的,所有的应用程序组件都被设计成在一台机器上执行。这意味着像用户界面(如果用户实际能与程序交互)、应用程序处理规则、数据管理、存储管理和网络管理(如果计算机连接到计算机网路上)等功能都包含在了程序中。这些虽然编写起来简单,但这些程序会变得越来越复杂,越来越难以形成文档,也难以更新和更改。这时,机器本身对企业来说就成了最大的开销,因此应用程序的设计是为了尽最大可能使用机器。客户端/服务器架构随着处理器越来越强大,系统和在线存储容量增加,数据通信更快、更经济,应用程序的设计也随之发展,以适应发展的速度。应用程序逻辑被重构或分解,允许每个应用程序在不同的机器上执行,并且在组件之间插入了不断改进的网络。这使得一些特性可以迁移到当前可用的成本最低的计算环境中。这一演变经历了一下几个阶段:终端和终端模拟早期的分布式计算依赖于特别用途的用户访问设备——终端。应用程序必须理解它们使用的通信协议,并直接向设备发出命令。当廉价的个人计算机(PC)出现时,终端被运行终端模拟程序的PC所取代。此时,应用程序的所有组件仍然驻留在单个大型机或小型计算机上轻量客户端随着PC的功能越来越强大,支持更大的内部和在线存储,网络性能进一步提高,企业对其应用程序进行了细分或分解,以便在本地PC上提取和执行用户界面。应用程序的其余部分则继续在数据中心的系统上执行。这些PC通常比它们所替代的终端便宜,并且它们还有额外的优点。这些PC是多功能设备,它们可以运行在它们所替换的终端上无法运行的、却能提高办公效率的应用程序。这种组合促使企业在更新或刷新应用程序时,开始倾向于客户端/服务器应用体系结构。中型客户端PC的发展仍在快速进行。一旦出现了更强大、存储容量更大的系统,企业就会使用它们,将更多的处理操作从数据中心昂贵的系统迁移到便宜的用户办公桌上。这时,用户界面和一些计算任务就迁移到了本地的PC上。这样大型机和小型计算机(现在成为服务器)就有了更长的使用寿命,从而降低了企业总体的计算成本。重型客户端随着PC变得越来越强大,更多的应用程序度可以从后端服务器迁移过来。在这里,除了数据和存储管理功能之外的所有功能都已迁移。进入互联网和万维网这时,公共互联网和万维网出现了。客户端/服务器计算的方式仍然在使用。为了降低总体成本,一些企业开始重新架构它们的分布式应用程序,便于使用标准的internet协议进行通信,并使用Web浏览器代替之前定制的用户界面功能。后来,一些应用程序的特性通过Javascript语言重新编写,这样它们就可以在客户端的计算机上本地执行。服务器的改进行业创新并不仅仅关注客户端侧的通信链路,对服务器也做了很大的改进。企业开始利用许多更小、更便宜的符合行业标准的服务器,通过它们强大的功能来支持部分或者全部原来基于大型机的功能。这样它们可以减少需要部署的昂贵主机系统的数量。接着,远程PC就可以和许多服务器通信,每个服务器都支持自己的应用程序组件。在此环境中使用了专用的数据库和文件服务器。之后,再将其他应用程序功能迁移到应用程序服务器。网络是另一个业界高度关注的领域。企业开始使用提供防火墙以及其他安全功能的专用网络服务器、文件缓存功能来加速应用程序的数据访问,电子邮件服务器、web服务器、web应用程序服务器、分布式命名服务器这些服务器跟踪和控制用户凭证,用于访问数据和应用程序。封装在设备服务器中的网络服务列表一直在增长。基于对象的开发PC和服务器功能的快速变化,加上处理能力、内存和网络这三者的价格的大幅下降,这些都对应用程序开发产生了重大影响。IT领域中最大的成本开销不再是硬件和软件,而是变成了通信、IT服务(员工)、电力以及冷却系统。软件开发、软件维护和IT操作出现了新的重要性,开发过程也发生了变化以迎合新的形势,即系统便宜,而人员、通信和电力越来越贵。图2:全球IT花费预测企业希望通过改进数据和应用程序架构来充分发挥员工的价值。其结果就是面向对象的应用程序和开发方法。许多编程语言,例如下面的语言,都支持这种方式:C++、C#、COBOL、Java、PHP、Python、Ruby在定义和记录数据结构时,应用程序开发者的编写变得更加系统化来适应变化。这种方式还使得维护和改进应用程序更加容易。开源软件Opensource.com为开源软件提供了以下定义:“开源软件是带有源代码的软件,任何人都可以检查、修改和增强代码。”“而有些软件的源代码只有创建它的个人、团队或组织才能修改——并且保有对它的独占控制。人们称这种软件为‘专有’或‘闭源’软件。”只有专有软件的原始作者才能合法地复制、检查和修改该软件。为了使用专有软件,计算机用户必须同意(通常通过接受首次运行该软件时显示的许可证),如果软件作者没有明确允许的话,他们不会对软件做任何的修改。微软Office和Adobe Photoshop都是专有软件的例子。虽然开源软件在计算机早期就已经存在,但直到20世纪90年代,当完整的开源操作系统、虚拟化技术、开发工具、数据库引擎和其他重要功能出现时,它才走到了前台。开源技术通常是基于web和分布式计算的关键组件。其中,以下类别的开源软件很受欢迎:开发工具应用支持数据库(flat文件,SQL,No-SQL,以及内存)分布式文件系统消息传输/队列操作系统聚类分布式计算强大的系统、快速的网络以及复杂软件可用性的结合,已经将主要的应用程序开发从单一转向了更加分布式的形式。然而企业已经意识到,有时候从头开始比尝试重构或分解旧的应用程序要更好。当企业进行创建分布式应用程序的工作时,常常会发现一些有趣的副产品。一个设计得当的应用程序,它已经分解成单独的功能或服务,可以由单独的团队并行开发。快速应用程序开发和部署(也称为DevOps)就是一种利用新环境的方法。面向服务的架构随着行业从客户端/服务器的计算模式,发展到更加分布式的方法,“面向服务的架构”一词出现了。这种方法基于分布式系统的概念、消息队列和交付中的标准,以及将XML消息传递作为共享数据和数据定义的标准方法。各个应用程序的功能被打包成面向网络的服务,这些服务接收一条消息,请求它们执行特定的服务,在它们执行服务之后,将响应发送回请求该服务的函数。这种方法还提供了另一个好处,即可以将给定的服务托管在网络的多个位置。这既提高了整体性能,又增强了可靠性。除此之外,现在还有很多工作负载管理工具,它用于接收服务请求、检查可用容量、将请求转发给具有最大可用容量的服务,然后将响应发送回请求者。如果特定的服务器没有及时响应,工作负载管理器会简单地向服务转发另一个实例。它还会将没有响应的服务标记为失败,并且在它收到一条表明服务仍在运行的消息之前,不会向它发送额外的请求。设计分布式系统的重要考虑因素现在我们已经走过了50多年的计算机历史,下面让我们来看看分布式系统开发人员的一些经验法则。需要考虑的东西很多,因为分布式解决方案可能有组件和服务在许多地方、不同类型的系统中运行,而且必须来回传递消息才能执行工作。要想成功创建这些解决方案,谨慎思考是必须的。除此之外还必须为所使用的每种主机系统、开发工具和消息传递系统提供专门的知识。确定需要做什么我们首先要考虑的事情,是我们究竟需要完成什么。虽然这听起来很简单,但却非常重要。令人惊讶的是,许多开发人员在知道具体需要什么之前就开始构建东西。通常情况下,这意味着他们构建了不必要的功能,浪费了时间。引用Yogi Berra的话就是:“如果你不知道自己要去哪里,你最终会去往别的地方”。首先需要知道要做什么,已经有哪些工具和服务可用,以及使用最终解决方案的人应该看到什么。交互和批处理快速响应和低延迟常常是我们的需求,因此比较明智的做法是考虑在用户等待时应该做什么,以及可以将什么放入批处理过程中,而这些批处理执行在事件驱动或时间驱动的计划中。在考虑了功能的初始分割之后,比较好的做法是计划何时需要执行后台、批处理进程、这些功能操作哪些数据、以及如何确保这些功能是可靠的、何时可用以及如何防止数据丢失。功能应该托管在哪里只有在详细计划了“完成什么”之后,才应该考虑“在哪里”以及“如何做”。开发人员有各自最喜欢的工具和方法,并且经常会调用它们,即使可能不是最佳的选择。Bernard Baruch说过:“如果你只有一把锤子,那么所有东西看起来都像钉子”。了解企业开发的企业标准也很重要。仅仅因为工具目前很流行就选择它是不明智的。这个工具可以完成这些工作,但是需要记住的是,它构建的所有东西都需要维护。如果你构建了一些只有自己才能理解或者维护的东西,那么在你职业生涯的剩下时间中,你可能已经把自己束缚在这一功能上了。我自己也有过这种经历,自认为自己创建的功能工作正常、轻量而且可靠。但在我离开那家公司后的十年里,我不断地收到关于这些功能的电话,因为后来的开发人员无法理解这些功能是如何实现的,而我写的文件又早就被丢掉了。在分布式解决方案中,每个功能或服务都应该分别考虑。该功能应该在企业数据中心?还是使用云服务提供商?还是两者兼有?另外还要考虑到在某些行业中存在法规要求,这些要求指导我们选择需要在何处以及如何维护和存储数据。其他需要考虑的东西还包括:该功能的主机应该是什么类型的系统。有没有系统架构更合适该功能?系统应该基于ARM、x86、SPARC、Precision、Power,还是大型机?会有某种特定的操作系统为该功能提供了更好的计算环境吗?Linux、Windows、UNIX、System I,或是System Z会是更好的平台吗?某特定的开发语言是否更适合该功能?它是一种特定类型的数据管理工具吗?该用Flat文件、SQL数据库还是No-SQL数据库?还是说非结构化的存储机制更好?功能应该托管在虚拟机中还是容器中,方便迁移、自动化以及编排吗?在本世纪初,运行Windows或Linux虚拟机往往是首选。虽然它们为方法提供了重要的隔离,并且在必要时很容易重启或移动他们,但是他们的处理、内存以及存储要求相当高。容器是处理虚拟化的另一种方式,它提供了类似的隔离级别,能够重新启动和迁移方法,而且消耗的处理能力、内存或存储都要小得多。性能性能是另一个重要的考虑因素。在定义组成解决方案的功能或服务时,开发人员应该注意它们是否有重要处理、内存或存储需求。仔细研究这些问题非常重要,这样才能知道是否可以进一步细分或分解这些功能。进一步的分割会允许并行处理的增加,这样能很大可能地提供性能上的改进。当然,这样做的代价是,它也增加了复杂性,可能会更加难以管理和保证安全。可靠性在高风险的企业环境中,解决方案的可靠性至关重要。开发人员必须考虑何时可以要求人们重新输入数据、重新运行功能,或者何时功能将不可用。数据库开发人员在20世纪60年代就遇到了这个问题,并开发了原子功能的概念。也就是说,功能必须完成或者部分的更新必须回滚,以使得数据处于功能开始前的状态。分布式系统也需要这种思维方式,确保即使在出现服务故障和事物中断的情况下也能保证数据完整性。例如,在关键消息传递系统中,在确认消息已经被接收方收到之前,消息必须被一直存储好。如果消息没能成功收到,则必须重新发送原始消息,并向系统管理报告故障。可管理性尽管没有核心应用程序功能那么有趣,但可管理性仍然是保证应用程序正常运转的关键因素。所有分布式功能都必须得到充分的检测,让管理员了解每个功能的当前状态,并在需要时更改功能的参数。毕竟,分布式系统是由比它们所替代的单片系统更多的活动部件组成的。开发人员必须时刻注意让这个分布式计算环境易于使用和维护。这给我们带来了一个绝对的要求,即必须对所有分布式功能进行充分的工具化,让管理员了解到它们的当前状态。毕竟,分布式系统本质上比它们所替代的单片系统更加复杂,并且有更多的活动部件。安全性确保分布式系统安全性,比单片环境中安全问题的难度高了一个数量级。每个功能都必须单独保密,功能之间的通信连接也必须保密。随着网络规模和复杂性的增长,开发人员必须考虑如何控制对功能的访问,如何确保只有授权用户才能访问这些功能,以及如何将服务与其他服务隔离开来。安全性是一个关键元素,必须添加到每个功能中而不是之后才加入。必须避免和汇报对功能和数据的未经授权访问。隐私性关于隐私性的话题有越来越多的规范。对与每个面向客户系统的开发人员来说,欧盟的GDPR以及美国的HIPPA法规都是重要的考虑因素。掌控复杂性开发人员必须花时间考虑如何将复杂计算环境中全部的内容组合在一起。服务应该被封装成一个单一的功能 ,或者少量紧密相关的功能,想要维护这样的规则非常困难。如果一个功能在多个地方实现,那么想要维护和更新就会很困难。当一个功能的实例没有更新会怎样?这个问题非常具有挑战性。这就意味着,对于复杂应用程序的开发人员来说,维护一个用于显示每个功能所在位置的可视化模型就非常有用了,这样,如果规则或业务需求发生变化,就可以对其进行更新。通常情况下,开发人员就必须花时间记录他们做了什么,什么时候做了更改,以及这些更改的目的是什么,这样其他人员就不必为了了解一个功能在哪里或者它是如何工作的而费心思去理解成堆的代码。要成为分布式系统的架构师,开发人员就必须要掌握复杂性。开发人员必须掌握的方法开发人员必须掌握分解和重构应用程序体系结构,从团队的角度思考问题,并提高他们在快速应用程序开发和部署(DevOps)方法方面的技能。毕竟,他们必须能够系统地思考哪些功能彼此独立,哪些功能依赖于其他功能的输出来工作。依赖于其他功能的这部分功能最好作为单个服务来实现。将它们作为独立的功能实现,可能会产生不必要的复杂性,导致应用程序性能低下,并且给网络带来不必要的负担。虚拟化技术涵盖了许多基础虚拟化是一个比虚拟机软件或容器更大的类别。这两个功能都被认为是虚拟化技术。在目前的应用程序中,至少有7种不同类型的虚拟化技术在使用。虚拟化技术可用于增强用户访问应用程序的方式、应用程序在何处以及如何执行、处理在何处以及如何执行、网络功能怎么样、数据在哪里以及如何存储、安全性如何实现以及管理功能如何实现。下面的虚拟化技术模型可能有助于开发人员理解虚拟化的概念。图3:虚拟化系统的架构从软件定义的解决方案角度考虑对于开发人员来说,从“软件定义的”解决方案的角度来考虑也是非常重要的。这也就是说,将控制从实际的处理中分割出来,这样功能就可以被自动化以及编排了。有哪些工具和策略可供使用当开发人员步入这个复杂的世界时,他们不应该觉得自己是独立的。供应商和开源社区提供了许多强大的工具。各种形式的虚拟化技术都可以成为开发人员最好的朋友。虚拟化技术是你最好的朋友容器让轻松地开发功能成为可能,这些功能可以在不互相干扰的情况下执行,并且可以根据工作负载需求从一个系统迁移到另一个系统。编排技术让控制多个功能成为可能,确保它们运行良好且可靠。它还可以在失败的情况下重启或移动它们。支持增量开发:功能可以并行开发,并在准备好时部署。它们还可以用新特性进行更新,而不需要在其他地方进行更改。支持高度分布式系统:既可以在企业数据中心本地部署功能,也可以在云服务提供商的数据中心远程部署功能。从服务的角度考虑这意味着开发人员必须考虑服务以及服务之间如何通信。定义良好的API定义良好的API可以让多个团队更好地协同工作,并且确保一切都能按照计划组合在一起。通常情况下这意味着要做更多的前期工作,但最终是非常值得的。为什么?因为整体开发可以更快。而且它还可以减少文档工作的工作量。支持快速应用程序开发这种方法对于快速应用程序开发和快速原型开发(即DevOps)来说也是完美的。如果执行得当,DevOps还可以只需很短的部署时间。从标准的角度思考分布式系统的开发人员应该充分考虑多供应商的国际标准,而不是单单依赖于一个供应商。这种方法避免了厂商的锁定,并且可以找到在不同领域最出彩的那个供应商。总 结值得注意的一点是,快速应用程序开发和分布式系统部署的指南,是从“慢慢来”开始的。最明智的做法就是先计划好你要去哪里,你要做什么,否则你很可能最终没能达成目标、开发预算耗尽并且毫无成果。

April 16, 2019 · 1 min · jiezi

什么是ZooKeeper?

前言只有光头才能变强。文本已收录至我的GitHub仓库,欢迎Star:https://github.com/ZhongFuCheng3y/3y上次写了一篇 什么是消息队列?以后,本来想入门一下Kafka的(装一下环境、看看Kafka一些概念啥的)。后来发现Kafka用到了ZooKeeper,而我又对ZooKeeper不了解,所以想先来学学什么是ZooKeeper,再去看看什么是Kafka。ZooKeeper相信大家已经听过这个词了,不知道大家对他了解多少呢?我第一次听到ZooKeeper的时候是在学Eureka的时候(外行人都能看懂的SpringCloud,错过了血亏!),同样ZooKeeper也可以作为注册中心。后面听到ZooKeeper的时候,是因为ZooKeeper可以作为分布式锁的一种实现。直至在了解Kafka的时候,发现Kafka也需要依赖ZooKeeper。Kafka使用ZooKeeper管理自己的元数据配置。这篇文章来写写我学习ZooKeeper的笔记,如果有错的地方希望大家可以在评论区指出。一、什么是ZooKeeper从上面我们也可以发现,好像哪都有ZooKeeper的身影,那什么是ZooKeeper呢?我们先去官网看看介绍:官网还有另一段话:ZooKeeper: A Distributed Coordination Service for Distributed Applications相比于官网的介绍,我其实更喜欢Wiki中对ZooKeeper的介绍:(留下不懂英语的泪水)我简单概括一下:ZooKeeper主要服务于分布式系统,可以用ZooKeeper来做:统一配置管理、统一命名服务、分布式锁、集群管理。使用分布式系统就无法避免对节点管理的问题(需要实时感知节点的状态、对节点进行统一管理等等),而由于这些问题处理起来可能相对麻烦和提高了系统的复杂性,ZooKeeper作为一个能够通用解决这些问题的中间件就应运而生了。二、为什么ZooKeeper能干这么多?从上面我们可以知道,可以用ZooKeeper来做:统一配置管理、统一命名服务、分布式锁、集群管理。这里我们先不管统一配置管理、统一命名服务、分布式锁、集群管理每个具体的含义(后面会讲)那为什么ZooKeeper可以干那么多事?来看看ZooKeeper究竟是何方神物,在Wiki中其实也有提到:ZooKeeper nodes store their data in a hierarchical name space, much like a file system or a tree) data structureZooKeeper的数据结构,跟Unix文件系统非常类似,可以看做是一颗树,每个节点叫做ZNode。每一个节点可以通过路径来标识,结构图如下:那ZooKeeper这颗"树"有什么特点呢??ZooKeeper的节点我们称之为Znode,Znode分为两种类型:短暂/临时(Ephemeral):当客户端和服务端断开连接后,所创建的Znode(节点)会自动删除持久(Persistent):当客户端和服务端断开连接后,所创建的Znode(节点)不会删除ZooKeeper和Redis一样,也是C/S结构(分成客户端和服务端)2.1 监听器在上面我们已经简单知道了ZooKeeper的数据结构了,ZooKeeper还配合了监听器才能够做那么多事的。常见的监听场景有以下两项:监听Znode节点的数据变化监听子节点的增减变化没错,通过监听+Znode节点(持久/短暂[临时]),ZooKeeper就可以玩出这么多花样了。三、ZooKeeper是怎么做到的?下面我们来看看用ZooKeeper怎么来做:统一配置管理、统一命名服务、分布式锁、集群管理。3.1 统一配置管理比如我们现在有三个系统A、B、C,他们有三份配置,分别是ASystem.yml、BSystem.yml、CSystem.yml,然后,这三份配置又非常类似,很多的配置项几乎都一样。此时,如果我们要改变其中一份配置项的信息,很可能其他两份都要改。并且,改变了配置项的信息很可能就要重启系统于是,我们希望把ASystem.yml、BSystem.yml、CSystem.yml相同的配置项抽取出来成一份公用的配置common.yml,并且即便common.yml改了,也不需要系统A、B、C重启。做法:我们可以将common.yml这份配置放在ZooKeeper的Znode节点中,系统A、B、C监听着这个Znode节点有无变更,如果变更了,及时响应。参考资料:基于zookeeper实现统一配置管理https://blog.csdn.net/u011320740/article/details/787426253.2 统一命名服务统一命名服务的理解其实跟域名一样,是我们为这某一部分的资源给它取一个名字,别人通过这个名字就可以拿到对应的资源。比如说,现在我有一个域名www.java3y.com,但我这个域名下有多台机器:192.168.1.1192.168.1.2192.168.1.3192.168.1.4别人访问www.java3y.com即可访问到我的机器,而不是通过IP去访问。3.3 分布式锁锁的概念在这我就不说了,如果对锁概念还不太了解的同学,可参考下面的文章Java锁?分布式锁?乐观锁?行锁?我们可以使用ZooKeeper来实现分布式锁,那是怎么做的呢??下面来看看:系统A、B、C都去访问/locks节点访问的时候会创建带顺序号的临时/短暂(EPHEMERAL_SEQUENTIAL)节点,比如,系统A创建了id_000000节点,系统B创建了id_000002节点,系统C创建了id_000001节点。接着,拿到/locks节点下的所有子节点(id_000000,id_000001,id_000002),判断自己创建的是不是最小的那个节点如果是,则拿到锁。释放锁:执行完操作后,把创建的节点给删掉如果不是,则监听比自己要小1的节点变化举个例子:系统A拿到/locks节点下的所有子节点,经过比较,发现自己(id_000000),是所有子节点最小的。所以得到锁系统B拿到/locks节点下的所有子节点,经过比较,发现自己(id_000002),不是所有子节点最小的。所以监听比自己小1的节点id_000001的状态系统C拿到/locks节点下的所有子节点,经过比较,发现自己(id_000001),不是所有子节点最小的。所以监听比自己小1的节点id_000000的状态……等到系统A执行完操作以后,将自己创建的节点删除(id_000000)。通过监听,系统C发现id_000000节点已经删除了,发现自己已经是最小的节点了,于是顺利拿到锁….系统B如上3.4集群状态经过上面几个例子,我相信大家也很容易想到ZooKeeper是怎么"感知"节点的动态新增或者删除的了。还是以我们三个系统A、B、C为例,在ZooKeeper中创建临时节点即可:只要系统A挂了,那/groupMember/A这个节点就会删除,通过监听groupMember下的子节点,系统B和C就能够感知到系统A已经挂了。(新增也是同理)除了能够感知节点的上下线变化,ZooKeeper还可以实现动态选举Master的功能。(如果集群是主从架构模式下)原理也很简单,如果想要实现动态选举Master的功能,Znode节点的类型是带顺序号的临时节点(EPHEMERAL_SEQUENTIAL)就好了。Zookeeper会每次选举最小编号的作为Master,如果Master挂了,自然对应的Znode节点就会删除。然后让新的最小编号作为Master,这样就可以实现动态选举的功能了。最后这篇文章主要讲解了ZooKeeper的入门相关的知识,ZooKeeper通过Znode的节点类型+监听机制就实现那么多好用的功能了!当然了,ZooKeeper要考虑的事没那么简单的,后面有机会深入的话,我还会继续分享,希望这篇文章对大家有所帮助~参考资料:分布式服务框架 Zookeeperhttps://www.ibm.com/developerworks/cn/opensource/os-cn-zookeeper/index.htmlZooKeeper初识整理(老酒装新瓶)https://lxkaka.wang/2017/12/21/zookeeper/ZooKeeperhttps://www.cnblogs.com/sunshine-long/p/9057191.htmlZooKeeper 的应用场景https://zhuanlan.zhihu.com/p/59669985乐于输出干货的Java技术公众号:Java3y。公众号内有200多篇原创技术文章、海量视频资源、精美脑图,不妨来关注一下!觉得我的文章写得不错,不妨点一下赞!

April 15, 2019 · 1 min · jiezi

分布式系统关注点——先写DB还是「缓存」?

如果第二次看到我的文章,欢迎文末扫码订阅我个人的公众号(跨界架构师)哟~ 本文长度为4209字,建议阅读12分钟。坚持原创,每一篇都是用心之作~在前一篇《360°全方位解读「缓存」》中,我们聊了运用缓存的三种思路,以及在一个完整的系统中可以设立缓存的几个位置,并且分享了关于浏览器缓存、CDN缓存、网关(代理)缓存的一些使用经验。这次Z哥将深入到实际场景中,来看一下「进程内缓存」、「进程外缓存」运用时的一些最佳实践。由于篇幅原因,这次先聊三个问题。首当其冲的就是“先写DB还是缓存?”。我想,只要你开始运用缓存,这会是你第一个要好好思考的问题,否则在前方等待你的就是灾难。。。先写DB还是缓存?一个程序可以没有缓存,但是一定要有数据库。这是大家的普遍观点,所以数据库的重要性在你的潜意识里总是被放在了第一位。先DB再缓存如果不细想的话你可能会觉得,数据库操作失败了,自然缓存也不用操作了;数据库操作成功了,再操作缓存,没毛病。但是数据库操作成功,缓存操作的失败的情况该怎么解?(主要在用到redis,memcached这种进程外缓存的时候,由于网络因素,失败的可能性大增)办法也是有的,在操作数据库的时候带一个事务,如果缓存操作失败则事务回滚。大致的代码意思如下:begin trans var isDbSuccess = write db; if(isDbSuccess){ var isCacheSuccess = write cache; if(isCacheSuccess){ return success; } else{ rollback db; return fail; } } else{ return fail; } catch(Exception ex){ rollback db; }end trans如此一来就万无一失了吗?并不是。除了由于事务的引入,增加了数据库的压力之外,在极端场景下可能会出现rollback db失败的情况。是不是很头疼?解决这个问题的方式就是write cache的时候做delete操作,而不是set操作。如此一来,用多一次cache miss的代价来换rollback db失败的问题。就像图上所示,哪怕rollback失败了,通过一次cache miss重新从db中载入旧值。题外话:其实这种做法有一种专业的叫法——Cache Aside Pattern。为了便于记忆,你可以和分布式系统的CAP定理同时记忆,叫「缓存的CAP模式」。是不是看上去妥了?可以开始潇洒了?▲图片来源于网络,版权归原作者所有如果你的数据库没有做高可用的话,的确可以妥了。但是如果数据库做了高可用,就会涉及到主从数据库的数据同步,这就有新问题了。题外话:所以大家不要过度追求技术的酷炫,可能会得不偿失,自找麻烦。什么问题呢?就是如果在数据还未同步到「从库」的时候,由于cache miss去「从库」取到了未同步前的旧值。解决它的第一个方式很简单,也很粗暴。就是定时去「从库」读数据,发现数据和缓存不一样了就set到缓存里去。但是这个方式有点“治标不治本”。不断的从数据库定时读取,对资源的消耗大不说,这个间隔频率也不好定义一个比较合适的统一标准,太短吧,会导致重复读取的次数加大,太长吧,又会导致缓存和数据库不一致的时间变长。所以这个方案仅适用于项目中只有2、3处需要做这种处理的场景,并且还不能是数据会频繁修改的情况。因为在数据修改频次较高的场景,甚至可能还会出现这个定时机制所消耗的资源反而大于主程序的情况。一般情况下,另一种更普适性的方案是采用接下去聊的这种更底层的方式进行,就是“哪里有问题处理哪里”,当「从库」完成同步的时候再额外做一次delete cache或者set cache的操作。如此,虽说也没有100%解决短暂的数据不一致问题,但是已经将脏数据所存在的时长降到了最低(最终由主从同步的耗时决定),并且大大减少了无谓的资源消耗。可能你会说,“不行,这么一点时间也不能忍”怎么办?办法是有,但是会增加「主库」的压力。就是在产生数据库写入动作后的一小段时间内强制读「主库」来加载缓存。怎么实现呢?先得依赖一个共享存储,可以借助数据库或者也可以是我们现在正在聊的分布式缓存。然后,你在事务提交之后往共享存储中临时存一个{ key = dbname + tablename + id,value = null,expire = 3s }这样的数据,并且再做一次delete cache的操作。begin trans var isDbSuccess = write db; if(isDbSuccess){ var isCacheSuccess = delete cache; if(isCacheSuccess){ return success; } else{ rollback db; return fail; } } else{ return fail; } catch(Exception ex){ rollback db; }end trans//在这里做这个临时存储,{key,value,expire}。delete cache;如此一来,当「读数据」的时候发生cache miss,先判断是否存在这个临时数据,只要在3秒内就会强制走「主库」取数据。可以看到,不同的方案各有利弊,需要根据具体的场景仔细权衡。先缓存再DB你工作中的大部分场景对数据准确性肯定是低容忍的,所以一般不建议选择「先缓存再DB」的方案,因为内存是易失性的。一旦遇到操作缓存成功,操作DB失败的情况,问题就来了。在这个时候最新的数据只有缓存里有,怎么办?单独起个线程不断的重试往数据库写?这个方案在一定程度上可行,但不适合用于对数据准确性有高要求的场景,因为缓存一旦挂了,数据就丢了!题外话:哪怕选择了这个方案,重试线程应确保只有1个,否则会存在“ABBA”的「并发写」问题。可能你会说用delete cache不就没问题了?可以是可以,但是要有个前提条件,访问缓存的程序不会产生并发。因为只要你的程序是多线程运行的,一旦出现并发就有可能出现「读」的线程由于cache miss从数据库取的时候,「写」的线程还没将数据写到数据库的情况。所以,哪怕用delete cache的方式,要么带lock(多客户端情况下还得上分布式锁),要么必然出现数据不一致。值得注意的是,如果数据库同样做了高可用,哪怕带了lock,也还需要考虑和上面提到的「先DB再缓存」中一样的由于主从同步的时间差可能会产生的问题。当然了,「先缓存再DB」也不是一文不值。当对写入速度有极致要求,而对数据准确性没那么高要求的场景下就非常好使,其实就是前一篇(《360°全方位解读「缓存」》)提到的「延迟写」机制。小结一下,相比缓存来说,数据库的「高可用」一般会在系统发展的后期才会引入,所以在没有引入数据库「高可用」的情况下,Z哥建议你使用「先DB再缓存」的方式,并且缓存操作用delete而不是set,这样基本就可以高枕无忧了。但是如果数据库做了「高可用」,那么团队必然也形成一定规模了,这个时候就老老实实的做数据库变更记录(binlog)的订阅吧。到这里可能有的小伙伴要问了,“如果上了分布式缓存,还需要本地缓存吗?”。本地缓存还要不要?在解答这个问题之前我们先来思考一个问题,一个分布式系统最重要的价值是什么?是「无限扩展」,只要堆硬件就能应对业务增长。要达到这点的背后需要满足一个特性,就是程序要「无状态」。那么既想引入缓存来加速,又要达到「无状态」,靠的就是分布式缓存。所以,能用分布式缓存解决的问题就尽量不要引入本地缓存。否则引入分布式缓存的作用就小了很多。但是在少数场景下,本地缓存还是可以发挥其价值的,但是我们需要仔细识别出来。主要是三个场景:不经常变更的数据。(比如一天甚至好几天更新一次的那种)需要支撑非常高的并发。(比如秒杀)对数据准确性能容忍的场景。(比如浏览量,评论数等)不过,我还是建议你,除了第二种场景,否则还是尽量不要引入本地缓存。原因我们下面来说说。其实这个原因的根本问题就是在引入了本地缓存后,本地缓存(进程内缓存)、分布式缓存(进程外缓存)、数据库这三者之间的数据一致性该怎么进行呢?本地缓存、分布式缓存、db之间的数据一致性如果是个单点应用程序的话,很简单,将本地缓存的操作放在最后就好了。可能你会说本地缓存修改失败怎么办?比如重复key啊什么的异常。那你可以反思一下为这种数据为什么可以成功的写进数据库。。。但是,本地缓存带来的一个巨大问题就是:虽然一个节点没问题,但是多个本地缓存节点之间的数据如何同步?解决这个问题的方式中有两种和之前我们聊过的Session问题(《做了「负载均衡」就可以随便加机器了吗?》)是类似的。要么是由接收修改的节点通知其它节点变更(通过rpc或者mq皆可),要么借助一致性hash让同一个来源的请求固定落到一个节点上。后者可以让不同节点上的本地缓存数据都不重复,从源头上避免了这个问题。但是这两个方案走的都是极端,前者变更成本太高,比如需要通知上千个节点的话,这个成本难以接受。而后者的话对资源的消耗太高,而且还容易出现压力分摊不均匀的问题。所以,一般系统规模小的时候可以考虑前者,而规模越大越会选择后者。还有一种相对中庸一些的,以降低数据的准确性来换成本的方案。就是设置缓存定时过期或者定时往下游的分布式缓存拉取最新数据。这和前面「先DB再缓存」中提到的定时机制是一样的逻辑,胜在简单,缺点就是会存在更长时间的数据不一致。小结一下,本地缓存的数据一致性解决方案,能彻底解决的是借助一致性hash的方案,但是成本比较高。所以,如非必要还是慎重决定要不要做本地缓存。总结好了,我们一起总结一下。这次呢,Z哥先花了大量的篇幅和你讨论「先写DB还是缓存」的问题,并且带你层层深入,通过一点一点的演进来阐述不同的解决方案。然后与你讨论了「本地缓存」的意义以及如何在「分布式缓存」和「数据库」的基础上做好数据一致性,这其中主要是多个本地缓存节点之间的数据同步问题。希望对你有所启发。这次的缓存实践是一个非常好的例子,从中我们可以看到一件事情的精细化所带来的复杂度需要更加的精细化去解决,但是又会带来新的复杂度。所以作为技术人的你,需要无时无刻考虑该怎么权衡,而不是人云亦云。相关文章:分布式系统关注点——360°全方位解读「缓存」分布式系统关注点——做了「负载均衡」就可以随便加机器了吗?作者:Zachary出处:https://www.cnblogs.com/Zacha…如果你喜欢这篇文章,可以点一下文末的「赞」。这样可以给我一点反馈。: )谢谢你的举手之劳。▶关于作者:张帆(Zachary,个人微信号:Zachary-ZF)。坚持用心打磨每一篇高质量原创。欢迎扫描下方的二维码~。定期发表原创内容:架构设计丨分布式系统丨产品丨运营丨一些思考。如果你是初级程序员,想提升但不知道如何下手。又或者做程序员多年,陷入了一些瓶颈想拓宽一下视野。欢迎关注我的公众号「跨界架构师」,回复「技术」,送你一份我长期收集和整理的思维导图。如果你是运营,面对不断变化的市场束手无策。又或者想了解主流的运营策略,以丰富自己的“仓库”。欢迎关注我的公众号「跨界架构师」,回复「运营」,送你一份我长期收集和整理的思维导图。 ...

April 11, 2019 · 1 min · jiezi

mysql及proxy

整体架构/流程主流程:主函数在sql/amin.cc中调用Mysqld.cc中mysqld_main首先载入篇日志,信号注册,plugin_register (mysql是插件式存储引擎设计,innodb,myisam等都是插件,在这里注册),核心为mysqld_socket_acceptor->connection_event_loop();监听处理循环poll。 process_new_connection处理handler有三种,线程池方式只用于商业,一个线程处理所有请求,一个连接一个线程(大多数选择Per_thread_connection_handler)。若thread_cache中有空闲直接获取,否则创建新的用户线程。进入用户线程的handle_connection 3.1 mysql网络通信一共有这几层:THD | Protocol | NET | VIO | SOCKET,protocol对数据的协议格式化,NET封装了net buf读写刷到网络的操作,VIO是对所有连接类型网络操作的一层封装(TCP/IP, Socket, Name Pipe, SSL, SHARED MEMORY),handle_connection初始化THD(线程),开始do_command (关于THD,有个很好的图:http://mysql.taobao.org/month…) 3.2.do_command=>dispatch_comand=>mysql_parse=》【检查query_cache有缓存直接返回否则=》】parse_sql=》mysql_execute_cmd判断insert等调用mysql_insert,每条记录调用write_record,这个是各个引擎的基类,根据操作表类型调用引擎层的函数=》写binlog日志=》提交/回滚。注意大家可能都以为是有query_cache的。但是从8.0开启废弃了query_cache。第二正会讲一下除了用户线程和主线程,在启动时,还创建了timer_notify线程。由于为了解决DDL无法做到atomic等,从MySQL8.0开始取消了FRM文件及其他server层的元数据文件(frm, par, trn, trg, isl,db.opt),所有的元数据都用InnoDB引擎进行存储, 另外一些诸如权限表之类的系统表也改用InnoDB引擎。因此在加载这些表时,创建了innodb用到的一系列线程。从插入流程开始外层 handle_connection=>do_commannd=>dispatch_command=>mysql_parse=>mysql_execute_commannd=>sql_cmd_dml::execute=> execute_inner while{对每条记录执行write_record} =>ha_write_row【返回到这里不出错记录binlog】,调用引擎table->file->ha_write_row(table->record[0])引擎层:row_insert_for_mysql_using_ins_graph开始,有开启事务的操作,trx_start_low。首先,需要分配回滚段,因为会修改数据,就需要找地方把老版本的数据给记录下来,其次,需要通过全局事务id产生器产生一个事务id,最后,把读写事务加入到全局读写事务链表(trx_sys->rw_trx_list),把事务id加入到活跃读写事务数组中(trx_sys->descriptors)在InnoDB看来所有的事务在启动时候都是只读状态,只有接受到修改数据的SQL后(InnoDB接收到才行。因为在start transaction read only模式下,DML/DDL都被Serve层挡掉了)才调用trx_set_rw_mode函数把只读事务提升为读写事务。整体流程图如下:必须有这些步骤的原因:[1]为了快,所有数据先写入内存,再刷脏[2]为了防止数据页写过程中崩溃数据的持久性=》先写redo保证重启后可以恢复。日志写不成功不操作,日志是顺序写,内容少,可以同步等。(最好是物理重做)。[3]异常回滚=》物理回滚反解复杂,需要一个逻辑日志。 基于undo log又实现了MVCC unlog等也要保证操作持久化原子化。[4]为了删除不每次整理页,只标记,为了真正删除/undo不需要的清除=》purge[5]flush对一个pageid多次操作合并在一起减少随机操作=》二级索引非唯一change buff[6]Flush过程中一个页部分写成功就崩溃,无法正确后恢复=》二次写[7]为完整的主链路。[8]为异步的刷盘链路性能磁盘,B+树先来个准确的B+比B为何更适合:区别两点,一个是B树搜索是可以止于非页节点的,包含数据(包含数据在磁盘中页的位置),且数据只出现在树中一次。另一点是叶子节点有双向链表。第一点使得节点可以包含更多路(因为不存数据在磁盘中页的位置,只包含下一层的指针页位置,B树这两个都要包含),层高会更少;只能到页节点搜索结束,性能稳定。第二点为了扫描和范围索引。内存buffer所有数据页。都走这套。包括undo等所有的数据页都存储在一个LRU链表上,修改过的block被加到flush_list上,解压的数据页被放到unzip_LRU链表上。namedescbuf_pool_t::page_hashpage_hash用于存储已经或正在读入内存的page。根据<space_id, page_no>快速查找。当不在page hash时,才会去尝试从文件读取buf_pool_t::LRULRU上维持了所有从磁盘读入的数据页,该LRU上又在链表尾部开始大约3/8处将链表划分为两部分,新读入的page被加入到这个位置;当我们设置了innodb_old_blocks_time,若两次访问page的时间超过该阀值,则将其挪动到LRU头部;这就避免了类似一次性的全表扫描操作导致buffer pool污染buf_pool_t::free存储了当前空闲可分配的blockbuf_pool_t::flush_list存储了被修改过的page,根据oldest_modification(即载入内存后第一次修改该page时的Redo LSN)排序buf_pool_t::flush_rbt在崩溃恢复阶段在flush list上建立的红黑数,用于将apply redo后的page快速的插入到flush list上,以保证其有序buf_pool_t::unzip_LRU压缩表上解压后的page被存储到unzip_LRU。 buf_block_t::frame存储解压后的数据,buf_block_t::page->zip.data指向原始压缩数据。buf_pool_t::zip_free[BUF_BUDDY_SIZES_MAX]用于管理压缩页产生的空闲碎片page。压缩页占用的内存采用buddy allocator算法进行分配。page_hash查找。LRU只是用于淘汰。一份block。指针保存在hash和lru上当另外一个线程也想请求相同page时,首先根据space id 和page no找到对应的buffer pool instance。然后查询page hash。如果看到page hash中已经有对应的block了,说明page已经或正在被读入buffer pool,如果io_fix为BUF_IO_READ,说明正在进行IO,就通过加X锁的方式做一次sync(buf_wait_for_read),确保IO完成。如果没有则表示需要从磁盘读取。在读盘前首先我们需要为即将读入内存的数据页分配一个空闲的block。当free list上存在空闲的block时,可以直接从free list上摘取;如果没有,就需要从unzip_lru 或者 lru上驱逐page。先unzip lru。再lru是否有可替换page,直接释放,否则可能是脏页多,再线程在LRU上做脏页刷新。后台线程也会定期做脏页刷新。一个流程对buffer的操作步骤:索引:聚簇索引见上二级索引 change buffer对非唯一二级索引页,delete_mark,delete,insert顺序插入缓冲区,合并减少随机IO。物理:ibdata第4个page B+ Tree(key:spaceid,offset,counter)内存:ibuf,B+树内容: space,offset,发生change的数据写入:1 不会导致空page:delete时只有一条记录 拒绝2 不会导致分裂,insert时检查IBUF BITMAP标识剩余空间大小,超出触发merge 拒绝merge:(在很多情况都需要把ibuf里的页进行合并)1.辅助索引页被读取到缓冲池时2.插入时预估page no空间不足3.ibuf空间不足4.插入ibuf可能产生ibuf Tree的索引分裂5.Master (IDLE ,ACTIVE,SHUTDOWN)……Purge操作和insert在ibuf并发问题在purge模式下,用ibuf同时将watch插入到hash table中,如果都在内存里,会给同一份page加锁,没问题,但是要两个线程都写入ibuf_insert时,是没办法控制顺序的(本来就允许这种无序,因为非唯一)。所以需要一个进入后,另一个就放弃,不能都写入ibuf。在purge模式下,用ibuf同时将watch插入到hash table中,insert就不会再放入ibuf中了其他读取清除这个buf.功能——事务A undologC(一个事务中间状态可见性) MVCCI (多个事物之间可见性/操作不干扰) MVCCD redolog内存总体流程undolog物理:回滚段,rseg0在ibdata第6个page,132临时表空间,33128独立表空间或ibdata,存储在ibdata,临时表空间或单独表空间。每个表空间可以包含若干个段。每个段有1024个控制页slot和历史表。每个slot对应一个undo log对象,有一个undo log header.内存:全局trx_sys->rseg_array。每个事务trx->rsegs内容: 逻辑日志 Insert undo日志记录插入的唯一键值的len和value。 Update undo日志在insert undo基础上,同时记录了旧记录事务id,以及被更新字段的旧数据写入 入口函数:btr_cur_ins_lock_and_undoa) 从chached_list或分配一个空闲slot创建undo页b) 顺序写undo log header和记录c) 在事务提交阶段,加入到history list或释放【见事务提交】Undo log的写入在一个单独的mtr中,受redo log的保护,先讲一个子事务mtr。Mtr是InnoDB对物理数据文件操作的最小原子单元,保证持久性,用于管理对Page加锁、修改、释放、以及日志提交到公共buffer等工作。 开启时初始化m_impl,比如mlog用于存储redo log记录 提交时需要将本地产生的日志拷贝到公共缓冲区,将修改的脏页放到flush list上。回滚: 入口函数:row_indo_step 解析老版本记录,做逆向操作事务提交时undolog 入口函数:trx_commit_low–>trx_write_serialisation_history 事务提交整体流程,注意看下大概顺序,后面会具体讲写完redo就可以提交了。生成事务no。如果有update类的undo日志 。加入到purge_queue(清理垃圾),history链表(维护历史版本)子事务提交。Redo log写到公共缓存释放MVCC的readview;insert的undo日志释放(可cache重用,否则全部释放包括page页)刷日志在该函数中,需要将该事务包含的Undo都设置为完成状态,先设置insert undo,再设置update undo(trx_undo_set_state_at_finish),完成状态包含三种: 如果当前的undo log只占一个page,且占用的header page大小使用不足其3/4时(TRX_UNDO_PAGE_REUSE_LIMIT),则状态设置为TRX_UNDO_CACHED,该undo对象会随后加入到undo cache list上; 如果是Insert_undo(undo类型为TRX_UNDO_INSERT),则状态设置为TRX_UNDO_TO_FREE; 如果不满足a和b,则表明该undo可能需要Purge线程去执行清理操作,状态设置为TRX_UNDO_TO_PURGE。对于undate undo需要调用trx_undo_update_cleanup进行清理操作。注意上面只清理了update_undo,insert_undo直到事务释放记录锁、从读写事务链表清除、以及关闭read view后才进行,这里的slot,undo page ,history关系:每个rseg控制页有1024个slot和history。undo page释放后或者移到history list后,就可以把slot清空、undo page转为cache不释放则不动slotpurge:删除(更新数据的真正删除),清除过期undo。入口函数srv_do_purge作用: 对于用户删除的数据,InnoDB并不是立刻删除,而是标记一下,后台线程批量的真正删除。类似的还有InnoDB的二级索引的更新操作,不是直接对索引进行更新,而是标记一下,然后产生一条新的。这个线程就是后台的Purge线程。此外,清除过期的undo,histroy list,指的是undo不需要被用来构建之前的版本,也不需要用来回滚事务。我们先来分析一下Purge Coordinator的流程。启动线程后,会进入一个大的循环,循环的终止条件是数据库关闭。在循环内部,首先是自适应的sleep,然后才会进入核心Purge逻辑。sleep时间与全局历史链表有关系,如果历史链表没有增长,且总数小于5000,则进入sleep,等待事务提交的时候被唤醒(srv_purge_coordinator_suspend)。退出循环后,也就是数据库进入关闭的流程,这个时候就需要依据参数innodb_fast_shutdown来确定在关闭前是否需要把所有记录给清除。接下来,介绍一下核心Purge逻辑。 srv_do_purge1)首先依据当前的系统负载来确定需要使用的Purge线程数(srv_do_purge),即如果压力小,只用一个Purge Cooridinator线程就可以了。如果压力大,就多唤醒几个线程一起做清理记录的操作。如果全局历史链表在增加,或者全局历史链表已经超过innodb_max_purge_lag,则认为压力大,需要增加处理的线程数。如果数据库处于不活跃状态(srv_check_activity),则减少处理的线程数。2)如果历史链表很长,超过innodb_max_purge_lag,则需要重新计算delay时间(不超过innodb_max_purge_lag_delay)。如果计算结果大于0,则在后续的DML中需要先sleep,保证不会太快产生undo(row_mysql_delay_if_needed)。3)从全局视图链表中,克隆最老的readview(快照、拿视图为了拿事务id.undo日志中upadte记了事务id),所有在这个readview开启之前提交的事务所产生的undo都被认为是可以清理的。克隆之后,还需要把最老视图的创建者的id加入到view->descriptors中,因为这个事务修改产生的undo,暂时还不能删除(read_view_purge_open)。4)从undo segment的最小堆中(堆存放每个段未被purge的最老的undo页),找出最早提交事务的undolog(trx_purge_get_rseg_with_min_trx_id),如果undolog标记过delete_mark(表示有记录删除操作),则把先关undopage信息暂存在purge_sys_t中(trx_purge_read_undo_rec)。5)依据purge_sys_t中的信息,读取出相应的undo,同时把相关信息加入到任务队列中。同时更新扫描过的指针,方便后续truncate undolog。6)循环第4步和第5步,直到为空,或者接下到view->low_limit_no,即最老视图创建时已经提交的事务,或者已经解析的page数量超过innodb_purge_batch_size。(把delete和Undopage分别存放,detele给工作线程删除)7)把所有的任务都放入队列后,就可以通知所有Purge Worker线程(如果有的话)去执行记录删除操作了。删除记录的核心逻辑在函数row_purge_record_func中。有两种情况,一种是数据记录被删除了,那么需要删除所有的聚集索引和二级索引(row_purge_del_mark),另外一种是二级索引被更新了(总是先删除+插入新记录),所以需要去执行清理操作。8)在所有提交的任务都已经被执行完后,就可以调用函数trx_purge_truncate去删除update undo(insert undo在事务提交后就被清理了)。每个undo segment分别清理,从自己的histrory list中取出最早的一个undo,进行truncate(trx_purge_truncate_rseg_history)。truncate中,最终会调用fseg_free_page来清理磁盘上的空间。MVCCundo+read view 写时并发读ReadView::id 创建该视图的事务ID;m_ids 创建ReadView时,活跃的读写事务ID数组,有序存储;记录trx_id不在m_ids中可见m_low_limit_id 当前最大事务ID;记录rx_id>=ReadView::m_low_limit_id,则说明该事务是创建readview之后开启的,不可见Rem_up_limit_id ;m_ids 集合中的最小值;记录trx_id< m_up_limit_id该事务在创建ReadView时已经提交了,可见二级索引回聚簇索引中。若不可见,则通过undo构建老版本记录。redolog物理:log文件,ib_logfile 覆盖写内存:log buffer log_sys(记了日志在磁盘和内存中用到的信息,比如总大小,一些需要刷盘的阈值等)内容:记录物理位置spaceid,page,offset上要操作的逻辑日志写入 每个子事务的操作都会写入log(mtr.m_impl.m_log中) mlog_open_and_write_index=》memcpy=》mlog_close提交 子事务提交写入缓冲区 将本次的表空间和文件信息加入到一个内存链表上 (去除恢复中对数据字典的依赖) 提交时,准备log内容,提交到公共buffer中,并将对应的脏页加到flush list上 Step 1: mtr_t::Command::prepare_write() 1.若当前mtr的模式为MTR_LOG_NO_REDO 或者MTR_LOG_NONE,则获取log_sys->mutex,从函数返回 2.若当前要写入的redo log记录的大小超过log buffer的二分之一,则去扩大log buffer,大小约为原来的两倍。 3.持有log_sys->mutex 4.调用函数log_margin_checkpoint_age检查本次写入:如果本次产生的redo log size的两倍超过redo log文件capacity,则打印一条错误信息;若本次写入可能覆盖检查点,还需要去强制做一次同步chekpoint 5.检查本次修改的表空间是否是上次checkpoint后第一次修改(fil_names_write_if_was_clean) 如果space->max_lsn = 0,表示自上次checkpoint后第一次修改该表空间: a. 修改space->max_lsn为当前log_sys->lsn; b. 调用fil_names_dirty_and_write将该tablespace加入到fil_system->named_spaces链表上; c. 调用fil_names_write写入一条类型为MLOG_FILE_NAME的日志,写入类型、spaceid, page no(0)、文件路径长度、以及文件路径名。 在mtr日志末尾追加一个字节的MLOG_MULTI_REC_END类型的标记,表示这是多个日志类型的mtr。 如果不是从上一次checkpoint后第一次修改该表,则根据mtr中log的个数,或标识日志头最高位为MLOG_SINGLE_REC_FLAG,或附加一个1字节的MLOG_MULTI_REC_END日志。 Step 2: 拷贝 若日志不够,log_wait_for_space_after_reserving Step 3:如果本次修改产生了脏页,获取log_sys->log_flush_order_mutex,随后释放log_sys->mutex。 Step 4. 将当前Mtr修改的脏页加入到flush list上,脏页上记录的lsn为当前mtr写入的结束点lsn。基于上述加锁逻辑,能够保证flush list上的脏页总是以LSN排序。 Step 5. 释放log_sys->log_flush_order_mutex锁 Step 6. 释放当前mtr持有的锁(主要是page latch)及分配的内存,mtr完成提交。刷盘 整个事务的提交 trx_commit. 参数innodb_flush_log_at_trx_commit当设置该值为1时,每次事务提交都要做一次fsync,这是最安全的配置,即使宕机也不会丢失事务 当设置为2时,则在事务提交时只做write操作,只保证写到系统的page cache,因此实例crash不会丢失事务,但宕机则可能丢失事务 当设置为0时,事务提交不会触发redo写操作,而是留给后台线程每秒一次的刷盘操作,因此实例crash将最多丢失1秒钟内的事务,写入一条MLOG_FILE_NAME刷脏 刷脏会在以下情形被触发 启动和关闭时会唤醒刷脏线程 redo log可能覆盖写时,调用单独线程把未提交LSN对应的redo log刷盘 LRU LIST在未能自己释放时,先自己刷脏一页,不行再 唤醒刷脏线程刷脏线程innodb_page_cleaners设置为4,那么就是一个协调线程(本身也是工作线程),加3个工作线程,工作方式为生产者-消费者。工作队列长度为buffer pool instance的个数,使用一个全局slot数组表示。buf_flush_page_cleaner_coordinator协调线程主循环主线程以最多1s的间隔或者收到buf_flush_event事件就会触发进行一轮的刷脏。协调线程首先会调用pc_request()函数,这个函数的作用就是为每个slot代表的缓冲池实例计算要刷脏多少页,然后把每个slot的state设置PAGE_CLEANER_STATE_REQUESTED, 唤醒等待的工作线程。由于协调线程也会和工作线程一样做具体的刷脏操作,所以它在唤醒工作线程之后,会调用pc_flush_slot(),和其它的工作线程并行去做刷脏页操作。一但它做完自己的刷脏操作,就会调用pc_wait_finished()等待所有的工作线程完成刷脏操作。完成这一轮的刷脏之后,协调线程会收集一些统计信息,比如这轮刷脏所用的时间,以及对LRU和flush_list队列刷脏的页数等。然后会根据当前的负载计算应该sleep的时间、以及下次刷脏的页数,为下一轮的刷脏做准备。buf_flush_page_cleaner_worker工作线程主循环启动后就等在page_cleaner_t的is_requested事件上,一旦协调线程通过is_requested唤醒所有等待的工作线程,工作线程就调用pc_flush_slot()函数去完成刷脏动作。pc_flush_slot:先找到一个空间的slot,page_cleaner->n_slots_requested–; // 表明这个slot开始被处理,将未被处理的slot数减1 page_cleaner->n_slots_flushing++; //这个slot开始刷脏,将flushing加1 slot->state = PAGE_CLEANER_STATE_FLUSHING;刷LRU,FLUSH LISTpage_cleaner->n_slots_flushing–; // 刷脏工作线程完成次轮刷脏后,将flushing减1 page_cleaner->n_slots_finished++; //刷脏工作线程完成次轮刷脏后,将完成的slot加一 slot->state = PAGE_CLEANER_STATE_FINISHED; // 设置此slot的状态为FINISHED若是最后一个,os_event_set(page_cleaner->is_finished)pc_wait_finished:os_event_wait(page_cleaner->is_finished);统计等每次刷多少srv_max_buf_pool_modified_pct决定2. log_checkpoint在主线程,合并insertbuffer,等需要redo log空间的以及事务提交后等都会调用log_free_check_wait =》调用单独的log checkpoint进程 这里如果page cleaner在ACTIVE唤醒buf_flush_event(buf_flush_page_coordinator_thread监听),否则自己buf_flush_sync_all_buf_pools。redo checkpoint的入口函数为log_checkpoint,其执行流程如下:Step1. 持有log_sys->mutex锁,并获取buffer pool的flush list链表尾的block上的lsn,这个lsn是buffer pool中未写入数据文件的最老lsn,在该lsn之前的数据都保证已经写入了磁盘。Step 2. 调用函数fil_names_clear扫描fil_system->named_spaces上的fil_space_t对象,如果表空间fil_space_t->max_lsn小于当前准备做checkpoint的Lsn,则从链表上移除并将max_lsn重置为0。同时为每个被修改的表空间构建MLOG_FILE_NAME类型的redo记录。(这一步未来可能会移除,只要跟踪第一次修改该表空间的min_lsn,并且min_lsn大于当前checkpoint的lsn,就可以忽略调用fil_names_write)写入一个MLOG_CHECKPOINT类型的CHECKPOINT REDO记录,并记入当前的checkpoint LSNStep3 . fsync redo log到当前的lsn 调用fil_flush_file_spaces(关闭时srv_shutdown_log,崩溃恢复……)Step4. 写入checkpoint信息函数:log_write_checkpoint_info –> log_group_checkpointcheckpoint信息被写入到了第一个iblogfile的头部,但写入的文件偏移位置比较有意思,当log_sys->next_checkpoint_no为奇数时,写入到LOG_CHECKPOINT_2(3 *512字节)位置,为偶数时,写入到LOG_CHECKPOINT_1(512字节)位置。在crash recover重启时,会读取记录在checkpoint中的lsn信息,然后从该lsn开始扫描redo日志。Checkpoint操作由异步IO线程执行写入操作,当完成写入后,会调用函数log_io_complete执行如下操作:fsync 被修改的redo log文件更新相关变量:log_sys->next_checkpoint_no++log_sys->last_checkpoint_lsn = log_sys->next_checkpoint_lsn释放log_sys->checkpoint_lock锁崩溃恢复1.从第一个iblogfile的头部定位要扫描的LSN(数据落盘点) 2.扫描redo log 1) 第一次redo log的扫描,主要是查找MLOG_CHECKPOINT,不进行redo log的解析, 2) 第二次扫描是在第一次找到MLOG_CHECKPOINT(获取表和路径)基础之上进行的,该次扫描会把redo log解析到哈希表中,如果扫描完整个文件,哈希表还没有被填满,则不需要第三次扫描,直接进行recovery就结束 3)第二次扫描把哈希表填满后,还有redo log剩余,则需要循环进行扫描,哈希表满后立即进行recovery,直到所有的redo log被apply完为止。 3.具体redo log的恢复 MLOG_UNDO_HDR_CREATE:解析事务ID,为其重建undo log头; MLOG_REC_INSERT 解析出索引信息(mlog_parse_index)和记录信息( page_cur_parse_insert_rec)等 在完成修复page后,需要将脏页加入到buffer pool的flush list上;查找红黑树找到合适的插入位置 MLOG_FILE_NAME用于记录在checkpoint之后,所有被修改过的信息(space, filepath); MLOG_CHECKPOINT用于标志MLOG_FILE_NAME的结束。在恢复过程中,只需要打开这些ibd文件即可,当然由于space和filepath的对应关系通过redo存了下来,恢复的时候也不再依赖数据字典。在恢复数据页的过程中不产生新的redo 日志;二次写 redo会记spaceid,pageno,偏移量内的逻辑日志只记录:’这是一个插入操作’和’这行数据的内容‘。 为了省地方。但是这样就有个问题。在redo log恢复执行时,如果执行逻辑时一半断电了,比如已经某地址加了一个记录数。但又断电了,重新灰度执行又会执行一遍。就会在地址后又加一条。写坏了。这就需要在脏页落盘时采取二次写。数据写入ibd前先顺序写入ibdata.在崩溃恢复时,先检验checksum.不合法载入ibdata的数据。Redo为了保证原子性,要求一块一写。不够的话要先读旧的然后改然后写。以512字节(最小扇区)对其方式写入,不需要二次写。设置一个值innodb_log_write_ahead_size,不需要这个过程,超过该值补0到一块直接插入server与innodb的事务保证server和引擎层事务的界限 1.开启事务。server只会调用引擎层。 server层如果不以命令,是不会显示开启事务的。在SQLCOM_BEGIN等命令会调用trans_begin 分布式事务会调trans_begin(跟踪下) 证明是正确的,在外层trans_begin并没有调用。并不研究了 提交会在server层调用各个引擎的事务提交。 下面说下innodb层的trx 2.提交事务。根据是否开启binlog和是否有多个引擎执行不同。比如开了Binlog且使用了事务引擎,用Mysql_bin_log的两阶段和组提交。如果没有用事务引擎,直接记log等就可以 3.事务回滚:分为真正xa回滚还是普通回滚。普通回滚调用引擎层回滚 4.崩溃恢复:没有server层的崩溃恢复开启 分配回滚段,获取事务id,加入事务链表提交 入口: MYSQL_BIN_LOG::commit,如果是分布式事务,用xa,两阶段。prepare和commit。我们先研究普通的提交。XA不作为重点。但是由于server层和Innodb层两个日志,需要保证顺序,也按照XA的两阶段设计。也叫内部xa 1) xa两阶段 Prepare undo log写入xid,设置状态为PREPARED Commit Flush Stage:由leader依次为别的线程对flush redo log到LSN,再写binlog文件 Sync Stage:如果sync_binlog计数超过配置值,以组为维度文件fsync Commit Stage:队列中的事务依次进行innodb commit, 修改undo头的状态为完成;并释放事务锁,清理读写事务链表、readview等一系列操作。2) 原因 两阶段是为了保证binlog和redo log一致性。server和备库用binlog来恢复同步。innodb用undo和redo恢复。 1落undo 2flush redo 3 flush binlog 4fsync binlog 5fsync redo [ps:sync可能只是内核缓冲放入磁盘队列,fsync只保证放入磁盘,都是同步] 2要在3前 因为要先保证redo写了才算是事务执行了,否则无法回滚就记了binlog 4要在5前 提交成功,redo才会落盘成功,如果先redo再binlog, 但是binlog落盘失败,不会回滚,binlog少记录 但是如果按照4到5的顺序,有binlog落盘成功, 如果此时正常,5失败会回滚,按照undo的内容逆着执行一次,binlog记录了重新执行的,可以正常恢复数据; 如果此时异常宕机,无法回滚,就会出问题。binlog已经落盘,redo没有落盘。此时数据同步导致主从不一致。因此加两阶段。在第一阶段在写入xid,undo设置为prepare。如果在宕机恢复时对于prepare中的发现binlog的xid没有被执行,重新执行一遍。 prepare阶段的redo log可以先不写,在commit阶段一起写。只要保证flush redo在flush binlog前就可以 两阶段其实也是undo标识一下binlog和redolog还不一致。3) 组提交:两阶段提交,在并发时无法保证顺序一致,用ordered_commit控制如图在Online backup taken时,那么事务 T1 在备机恢复 MySQL 数据库时,发现 T1 未在存储引擎内提交,那么在恢复时,T1 事务就会被回滚。数据不一致。如果关闭binlog_order_commits。事务各自提交。这时候没有设置不写redo log。不能保证Innodb commit顺序和Binlog写入顺序一直,但是不会影响数据一致性。只是物理备份数据不一致。但是依赖于事务页上记录binlog恢复的,比如xtrabackup就会发生备份数据不一致的情况。回滚两阶段:正常应该根据undo回滚,但看到undo为iprepare且binlog有,就不回滚当由于各种原因(例如死锁,或者显式ROLLBACK)需要将事务回滚时,ha_rollback_trans=》ha_rollback_low,进而调用InnoDB函数trx_rollback_for_mysql来回滚事务。对于innodb回滚的方式是提取undo日志,做逆向操作。提交失败会回滚。走的非xa,调用trx_rollback_for_mysql。原来一直纠结binlog会不会删除。。。跟踪了好久也没看出来,其实是undo中的在提交时重新写一下binlog。这里在子事务里会介绍。分布式分布式事务主从复制三种日志模式1.基于行的复制 row优点:符合幂等性,高度保障数据一致。缺点:数据量大2.基于语句的复制 statement优点:日志量少缺点:特定功能函数导致主从数据不一致,重复执行时无法保证幂等3.混合类型的复制 mixed (默认语句,语句无法精准复制,则基于行)主从同步过程其中1. Slave 上面的IO线程连接上 Master,并请求从指定日志文件的指定位置(或者从最开始的日志)之后的日志内容;重放过程和master一样,也redologGTID MySQL 5.6引入全局事务ID的首要目的,是保证Slave在复制的时候不会重复执行相同的事务操作;其次,是用全局事务IDs代替由文件名和物理偏移量组成的复制位点,定位Slave需要复制的binlog内容。因此,MySQL必须在写binlog时记录每个事务的全局GTID,保证Master / Slave可以根据这些GTID忽略或者执行相应的事务。GTID的组成部分: 前面是server_uuid:后面是一个串行号 例如:server_uuid:sequence number 7800a22c-95ae-11e4-983d-080027de205a:10 UUID:每个mysql实例的唯一ID,由于会传递到slave,所以也可以理解为源ID。 Sequence number:在每台MySQL服务器上都是从1开始自增长的串行,一个数值对应一个事务。 当事务提交时,不管是STATEMENT还是ROW格式的binlog,都会添加一个XID_EVENT事件作为事务的结束。该事件记录了该事务的id。同步方案 同步复制 所谓的同步复制,意思是master的变化,必须等待slave-1,slave-2,…,slave-n完成后才能返回。 异步复制 master只需要完成自己的数据库操作即可。至于slaves是否收到二进制日志,是否完成操作,不用关心 半同步复制 master只保证slaves中的一个操作成功,就返回,其他slave不管。master既要负责写操作,还的维护N个线程,负担会很重。可以这样,slave-1是master的从,slave-1又是slave-2,slave-3,…的主,同时slave-1不再负责select。slave-1将master的复制线程的负担,转移到自己的身上。这就是所谓的多级复制的概念。 这里会有个一致性的问题。 开始提交事务 =>write binlog => sync binlog => engine commit => send events =>返回 send_events失败会导致master有slave没有 开始提交事务 =>write binlog => sync binlog => send events => engine commit =>返回 send_events失败若sync binlog未同步,导致XA不会重做,slave领先并行复制定期checkout-point将队列中执行结束的删除。记录checkpoint后每个worker是否执行过的bitmap。崩溃恢复时执行Bitmap未执行的部分。按db分粒度大可以换成table扩展性当主库支撑不了。水平扩展。拆表。可靠性需要proxy保证一致性同步策略影响。XA分为外部和内部。对于外部。要应用程序或proxy作为协调者。(二阶段提交协调者判断所有prepare后commit)。对于内部,binlog控制。proxy功能共享式的缓存读写分离路由可靠性的保证,主从切换,故障发现与定位XA一致性的实现过滤加注释例如:https://github.com/mariadb-co… ...

April 9, 2019 · 2 min · jiezi

干货|Spring Cloud Stream 体系及原理介绍

Spring Cloud Stream 在 Spring Cloud 体系内用于构建高度可扩展的基于事件驱动的微服务,其目的是为了简化消息在 Spring Cloud 应用程序中的开发。Spring Cloud Stream (后面以 SCS 代替 Spring Cloud Stream) 本身内容很多,而且它还有很多外部的依赖,想要熟悉 SCS,必须要先了解 Spring Messaging 和 Spring Integration 这两个项目,接下来文章将从以下几点跟大家进行介绍:什么是 Spring Messaging;什么是 Spring Integration;什么是 SCS及其功能;Spring MessagingSpring Messaging 是 Spring Framework 中的一个模块,其作用就是统一消息的编程模型。比如消息 Messaging 对应的模型就包括一个消息体 Payload 和消息头 Header:package org.springframework.messaging;public interface Message<T> { T getPayload(); MessageHeaders getHeaders();}消息通道 MessageChannel 用于接收消息,调用 send 方法可以将消息发送至该消息通道中 :@FunctionalInterfacepublic interface MessageChannel { long INDEFINITE_TIMEOUT = -1; default boolean send(Message<?> message) { return send(message, INDEFINITE_TIMEOUT); } boolean send(Message<?> message, long timeout);}消息通道里的消息如何被消费呢?由消息通道的子接口可订阅的消息通道 SubscribableChannel 实现,被 MessageHandler 消息处理器所订阅:public interface SubscribableChannel extends MessageChannel { boolean subscribe(MessageHandler handler); boolean unsubscribe(MessageHandler handler);}由MessageHandler 真正地消费/处理消息:@FunctionalInterfacepublic interface MessageHandler { void handleMessage(Message<?> message) throws MessagingException;}Spring Messaging 内部在消息模型的基础上衍生出了其它的一些功能,如:消息接收参数及返回值处理:消息接收参数处理器 HandlerMethodArgumentResolver 配合 @Header, @Payload 等注解使用;消息接收后的返回值处理器 HandlerMethodReturnValueHandler 配合 @SendTo 注解使用;消息体内容转换器 MessageConverter;统一抽象的消息发送模板 AbstractMessageSendingTemplate;消息通道拦截器 ChannelInterceptor;Spring IntegrationSpring Integration 提供了 Spring 编程模型的扩展用来支持企业集成模式(Enterprise Integration Patterns),是对 Spring Messaging 的扩展。它提出了不少新的概念,包括消息的路由 MessageRoute、消息的分发 MessageDispatcher、消息的过滤 Filter、消息的转换 Transformer、消息的聚合 Aggregator、消息的分割 Splitter 等等。同时还提供了 MessageChannel 和MessageHandler 的实现,分别包括 DirectChannel、ExecutorChannel、PublishSubscribeChannel 和MessageFilter、ServiceActivatingHandler、MethodInvokingSplitter 等内容。首先为大家介绍几种消息的处理方式:消息的分割:消息的聚合:消息的过滤:消息的分发:接下来,我们以一个最简单的例子来尝试一下 Spring Integration:SubscribableChannel messageChannel = new DirectChannel(); // 1messageChannel.subscribe(msg -> { // 2 System.out.println(“receive: " + msg.getPayload());});messageChannel.send(MessageBuilder.withPayload(“msg from alibaba”).build()); // 3构造一个可订阅的消息通道 messageChannel;使用 MessageHandler 去消费这个消息通道里的消息;发送一条消息到这个消息通道,消息最终被消息通道里的 MessageHandler 所消费,最后控制台打印出: receive: msg from alibaba;DirectChannel 内部有个 UnicastingDispatcher 类型的消息分发器,会分发到对应的消息通道 MessageChannel 中,从名字也可以看出来,UnicastingDispatcher 是个单播的分发器,只能选择一个消息通道。那么如何选择呢? 内部提供了 LoadBalancingStrategy 负载均衡策略,默认只有轮询的实现,可以进行扩展。我们对上段代码做一点修改,使用多个 MessageHandler 去处理消息:SubscribableChannel messageChannel = new DirectChannel();messageChannel.subscribe(msg -> { System.out.println(“receive1: " + msg.getPayload());});messageChannel.subscribe(msg -> { System.out.println(“receive2: " + msg.getPayload());});messageChannel.send(MessageBuilder.withPayload(“msg from alibaba”).build());messageChannel.send(MessageBuilder.withPayload(“msg from alibaba”).build());由于 DirectChannel 内部的消息分发器是 UnicastingDispatcher 单播的方式,并且采用轮询的负载均衡策略,所以这里两次的消费分别对应这两个 MessageHandler。控制台打印出:receive1: msg from alibabareceive2: msg from alibaba既然存在单播的消息分发器 UnicastingDispatcher,必然也会存在广播的消息分发器,那就是 BroadcastingDispatcher,它被 PublishSubscribeChannel 这个消息通道所使用。广播消息分发器会把消息分发给所有的 MessageHandler:SubscribableChannel messageChannel = new PublishSubscribeChannel();messageChannel.subscribe(msg -> { System.out.println(“receive1: " + msg.getPayload());});messageChannel.subscribe(msg -> { System.out.println(“receive2: " + msg.getPayload());});messageChannel.send(MessageBuilder.withPayload(“msg from alibaba”).build());messageChannel.send(MessageBuilder.withPayload(“msg from alibaba”).build());发送两个消息,都被所有的 MessageHandler 所消费。控制台打印:receive1: msg from alibabareceive2: msg from alibabareceive1: msg from alibabareceive2: msg from alibabaSpring Cloud StreamSCS与各模块之间的关系是:SCS 在 Spring Integration 的基础上进行了封装,提出了 Binder, Binding, @EnableBinding, @StreamListener 等概念;SCS 与 Spring Boot Actuator 整合,提供了 /bindings, /channels endpoint;SCS 与 Spring Boot Externalized Configuration 整合,提供了 BindingProperties, BinderProperties 等外部化配置类;SCS 增强了消息发送失败的和消费失败情况下的处理逻辑等功能。SCS 是 Spring Integration 的加强,同时与 Spring Boot 体系进行了融合,也是 Spring Cloud Bus 的基础。它屏蔽了底层消息中间件的实现细节,希望以统一的一套 API 来进行消息的发送/消费,底层消息中间件的实现细节由各消息中间件的 Binder 完成。Binder 是提供与外部消息中间件集成的组件,为构造 Binding提供了 2 个方法,分别是 bindConsumer 和 bindProducer ,它们分别用于构造生产者和消费者。目前官方的实现有 Rabbit Binder 和 Kafka Binder, Spring Cloud Alibaba 内部已经实现了 RocketMQ Binder。从图中可以看出,Binding 是连接应用程序跟消息中间件的桥梁,用于消息的消费和生产。我们来看一个最简单的使用 RocketMQ Binder 的例子,然后分析一下它的底层处理原理:启动类及消息的发送:@SpringBootApplication@EnableBinding({ Source.class, Sink.class }) // 1public class SendAndReceiveApplication { public static void main(String[] args) { SpringApplication.run(SendAndReceiveApplication.class, args); } @Bean // 2 public CustomRunner customRunner() { return new CustomRunner(); } public static class CustomRunner implements CommandLineRunner { @Autowired private Source source; @Override public void run(String… args) throws Exception { int count = 5; for (int index = 1; index <= count; index++) { source.output().send(MessageBuilder.withPayload(“msg-” + index).build()); // 3 } } }}消息的接收:@Servicepublic class StreamListenerReceiveService { @StreamListener(Sink.INPUT) // 4 public void receiveByStreamListener1(String receiveMsg) { System.out.println(“receiveByStreamListener: " + receiveMsg); }}这段代码很简单,没有涉及到 RocketMQ 相关的代码,消息的发送和接收都是基于 SCS 体系完成的。如果想切换成 RabbitMQ 或 kafka,只需修改配置文件即可,代码无需修改。我们分析这段代码的原理:@EnableBinding 对应的两个接口属性 Source 和 Sink 是 SCS 内部提供的。SCS 内部会基于 Source 和 Sink 构造 BindableProxyFactory,且对应的 output 和 input 方法返回的 MessageChannel 是 DirectChannel。output 和 input 方法修饰的注解对应的 value 是配置文件中 binding 的 name。public interface Source { String OUTPUT = “output”; @Output(Source.OUTPUT) MessageChannel output();}public interface Sink { String INPUT = “input”; @Input(Sink.INPUT) SubscribableChannel input();}配置文件里 bindings 的 name 为 output 和 input,对应 Source 和 Sink 接口的方法上的注解里的 value:spring.cloud.stream.bindings.output.destination=test-topicspring.cloud.stream.bindings.output.content-type=text/plainspring.cloud.stream.rocketmq.bindings.output.producer.group=demo-groupspring.cloud.stream.bindings.input.destination=test-topicspring.cloud.stream.bindings.input.content-type=text/plainspring.cloud.stream.bindings.input.group=test-group1构造 CommandLineRunner,程序启动的时候会执行 CustomRunner 的 run 方法。调用 Source 接口里的 output 方法获取 DirectChannel,并发送消息到这个消息通道中。这里跟之前 Spring Integration 章节里的代码一致。Source 里的 output 发送消息到 DirectChannel 消息通道之后会被 AbstractMessageChannelBinder#SendingHandler 这个 MessageHandler 处理,然后它会委托给 AbstractMessageChannelBinder#createProducerMessageHandler 创建的 MessageHandler 处理(该方法由不同的消息中间件实现);不同的消息中间件对应的 AbstractMessageChannelBinder#createProducerMessageHandler 方法返回的 MessageHandler 内部会把 Spring Message 转换成对应中间件的 Message 模型并发送到对应中间件的 broker;使用 @StreamListener 进行消息的订阅。请注意,注解里的 Sink.input 对应的值是 “input”,会根据配置文件里 binding 对应的 name 为 input 的值进行配置:不同的消息中间件对应的 AbstractMessageChannelBinder#createConsumerEndpoint 方法会使用 Consumer 订阅消息,订阅到消息后内部会把中间件对应的 Message 模型转换成 Spring Message;消息转换之后会把 Spring Message 发送至 name 为 input 的消息通道中;@StreamListener 对应的 StreamListenerMessageHandler 订阅了 name 为 input 的消息通道,进行了消息的消费;这个过程文字描述有点啰嗦,用一张图总结一下(黄色部分涉及到各消息中间件的 Binder 实现以及 MQ 基本的订阅发布功能):SCS 章节的最后,我们来看一段 SCS 关于消息的处理方式的一段代码:@StreamListener(value = Sink.INPUT, condition = “headers[‘index’]==‘1’")public void receiveByHeader(Message msg) { System.out.println(“receive by headers[‘index’]==‘1’: " + msg);}@StreamListener(value = Sink.INPUT, condition = “headers[‘index’]==‘9999’")public void receivePerson(@Payload Person person) { System.out.println(“receive Person: " + person);}@StreamListener(value = Sink.INPUT)public void receiveAllMsg(String msg) { System.out.println(“receive allMsg by StreamListener. content: " + msg);}@StreamListener(value = Sink.INPUT)public void receiveHeaderAndMsg(@Header(“index”) String index, Message msg) { System.out.println(“receive by HeaderAndMsg by StreamListener. content: " + msg);}有没有发现这段代码跟 Spring MVC Controller 中接收请求的代码很像? 实际上他们的架构都是类似的,Spring MVC 对于 Controller 中参数和返回值的处理类分别是 org.springframework.web.method.support.HandlerMethodArgumentResolver、 org.springframework.web.method.support.HandlerMethodReturnValueHandler。Spring Messaging 中对于参数和返回值的处理类之前也提到过,分别是 org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver、org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler。它们的类名一模一样,甚至内部的方法名也一样。总结上图是 SCS 体系相关类说明的总结,关于 SCS 以及 RocketMQ Binder 更多相关的示例,可以参考 RocketMQ Binder Demos,包含了消息的聚合、分割、过滤;消息异常处理;消息标签、sql过滤;同步、异步消费等等。下一篇文章,我们将分析消息总线(Spring Cloud Bus) 在 Spring Cloud 体系中的作用,并逐步展开,分析 Spring Cloud Alibaba 中的 RocketMQ Binder 是如何实现 Spring Cloud Stream 标准的。本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

April 9, 2019 · 4 min · jiezi

学习笔记--间接通信

什么是间接通信?间接通信被定义为在分布式系统中实体通过中介者进行通信,没有发送者和接收者之间的耦合。间接通信具有两个特性:(1)空间解耦:发送者不需要知道接收者是谁。(2)时间解耦:发送者和接收者可以有独立的生命周期。间接通信的范型1.组通信(1)概念组通信实现了组播通信,通过一个操作,消息被发送到组内的所有成员,与系统中的所有成员通信。组通信的一个重要特征就是一个进程事项发起一个组播操作,而不是发起多个发送操作到每个进程。(2)分类a.封闭组和开放组封闭组:对封闭组的组成员来说,只有组成员才能组播给它。开发组:对开放组的组成员来说,组外的成员也能组播给它。b.重叠组和非重叠组重叠组:实体(对象或进程)可以成为多个组的成员。非重叠组:实体(对象或进程)至多只能属于一个组。(3)实现JGroups等。2.发布-订阅系统(1)概念在发布-订阅系统中,发布者发布事件到事件服务中,订阅者通过订阅表示对特定事件感兴趣。发布-订阅系统的任务是把订阅和发布的事件匹配,保证事件通知正确传递。一个给定的事件会传递给多个订阅者,所以发布-订阅系统是一个一对多的通信范型。(2)订阅模型a.基于渠道发布者发布事件到命名的渠道,订阅者订阅其中一个已命名的渠道,并接收所有发送到那个渠道的事件。b.基于主题每个事件通知可以定义多个主题,订阅者根据自己感兴趣的主题订阅。c.基于内容基于内容的订阅是基于主题订阅的一般化。d.基于类型在基于对象的方法中,对象有指定的类型。在基于类型的方法中,订阅根据事件类型来定义,根据给定的事件类型或者子类型来定义。(3)发布-订阅系统的体系结构3.消息队列(1)概念生产者进程发送消息到特定队列,消费者进程从队列中接收消息。有以下三种接收方式:a.堵塞接收:保持堵塞直到有合适的消息可用。b.非堵塞接收(轮询):检查队列的标志,返回可用的消息,或是一个不可用的指示。c.通知操作:当队列中有可用的消息时,向消费者发送事件通知。(2)JMS(Java消息服务)4.分布式共享内存(1)概念分布式共享内存(DSM)是一种抽象,用于给不共享内存的物理机共享数据。分布式共享内存使得不同计算机的进程能访问不同进程的数据,就像访问自己进程的数据一样。(2)与消息传递的比较比较内容分布式共享内存消息传递序列化相关进程直接共享变量,不需要序列化。变量在发送进程序列化,在接收进程反序列化。安全性共享分布式内存的进程可能错误的变更数据而导致其他进程失效。支持消息传递的进程因为拥有各自的私有地址空间而得到保护。持久性通过DSM通信的进程可以在非重叠生命周期进行。通过消息传递的进程需要在同一时刻进行消息传递。同步通过共享内存编程的常规组成成分,例如锁和信号量实现的。通过消息原语进行进程之间的同步。(3)实现Apollo Domain文件系统等。5.元组空间(1)概念进程通过在元组空间放置元组间接进行通信,其他进程可以从元组空间中读或者删除元组。元组由一个或多个带类型的数据域组成。(2)实现JavaSpaces等等。间接通信风格总结比较内容组发布-订阅系统消息队列分布式共享内存空间组通信空间解耦是是是是是时间解耦可能可能是是是服务风格基于通信的基于通信的基于通信的基于状态的基于状态的通信模式一对多一对多一对一一对多一对一或一对多主要目的可靠的分布式计算信息分发或EAI;移动和无处不在系统信息分发或EAI;商业事务处理并行和分布式计算并行和分布式计算;移动和无处不在系统可伸缩性有限的有限的可能可能有限的关联性无基于内容的发布-订阅无无有參考资料《分布式系统概念与设计》

April 8, 2019 · 1 min · jiezi

对话 CTO | 健身新物种,超级猩猩带来了哪些改变?

专栏介绍「对话 CTO」是极客公园的一档最新专栏,以技术人的视角聊聊研发管理者的发展和成长。本专栏由ONES 的创始人&CEO 王颖奇作为特邀访谈者。王颖奇曾参与金山软件 WPS、金山毒霸等大型软件的核心开发工作;2011 年创立了正点科技,旗下产品正点闹钟、正点日历在全球用户过亿;2014 年,王颖奇在知名美元基金晨兴资本任 EIR,并以个人身份参与十余家公司的管理咨询工作;2015 年,王颖奇创立 ONES,致力于提供企业级研发管理解决方案。摘要刚刚加入超级猩猩时,刻奇就在思考,如何解决大则几千平的传统健身房需要承担的高昂租金成本问题。从健身房选址到摆脱大面积店面束缚,通过技术实现了对健身房进行化整为零和精细化运营的改造,互联网出身的他将分布式概念带入了实体行业。刻奇说,完全不同于传统健身房,超级猩猩省去了前台、销售的角色,将这些行为线上化,通过得到的运营数据去赋能各个部门。依靠按次收费、小程序约课、游戏化的线上运营手段……健身房、健身教练和用户之间的关系被重新「定义」了。通过 IT 和技术实现打破健身房租金和选址的经营铁律,穿过坪效天花板,关于超级猩猩如何将线上化、数据化、智能化落地,利用技术和运营手段提高课程购买率和复购率,刻奇聊了聊他的想法。在他看来,(健身房)IT 化必然成为未来的行业标准。化零为整,分布式概念建店颖奇:非常感谢超级猩猩联合创始人刻奇接受我们今天的采访。外界看到超级猩猩可能是连锁的健身房,或者更多的是在线下的健身场景,同时也有订课小程序。作为产品或者技术负责人,您在超级猩猩里担任的角色是怎样?除了我们见到的部分,超级猩猩还有哪些东西是我们没有理解到的呢?刻奇:对于外界来说,大家可能第一反应,超级猩猩是一个健身房。但从我们的角度,超级猩猩和过往的健身房是一个完全不同的定义。举个例子,传统健身房一定要有销售这些角色,但是在我们这里却没有。我们把所有的前台、销售都砍掉了,取而代之的是回归到健身的本质,回归到课程本身、内容本身。用技术方案取代了健身房传统的必要人员,这样做之后,我们才能把所有的支付行为、交易行为、浏览行为都线上化,线上化之后才能数据化。这是我们自己的一个三步概念,首先线上化,第二数据化,第三智能化。颖奇:这三方面真正落地,会是什么样子的?刻奇:首先是线上化。我们可能是最早的一批做小程序的团队,超级猩猩全国接近一百家门店,做到了真的没有前台,所有的用户都是通过微信端进入到门店。第二点是数据化。很早我们就内部成立了自己的数据平台组这样一个中台部门。所有的运营数据都能以各种自定义的报表形式呈现给不同的部门。比如,传统健身房教练是通过人来评定的,我们认为很主观也不公平。超级猩猩未来几百个几千个教练团队,我们直接通过自己的算法系统,基于一个核心指标:满员率,给到教练及时的反馈。第三是还有一些我们在尝试的智能化的应用,比如完全自动化的排课。颖奇:大家会说传统健身房是地产的生意,我觉得更多是生意难扩大,利润空间薄。超级猩猩利用技术化的手段让效率变高了,核心会变吗?刻奇:核心也发生很大的变化。传统健身房的模式是这样一个商业模型,通过建几千平的大店要到一个相对低的租金。租金早期是一个诱惑,但是中期会上涨,因为无法在周围随便找到另一个相同面积的场地,所以间接被地产绑架了。传统健身房运营了七年八年,短则甚至两年三年,就会陷入这个问题。所以健身房几乎无法走到上市的环节,因为无法在财务上说服股东,保证租金成本不上涨。但是我们一开始就强调大量拆散。我是互联网出身,所以我会将分布式这种概念应用到实业行业。拆散之后有两个好处,从用户角度来看,门店距离用户更近了。同时我们不受地产的控制,一两百平相对灵活,更换一个地方成本相对小。我可以在周围开很多家,客流是没有损失的。颖奇:这应该是一个非常核心的逻辑了,我们把店变小变多,就更加需要 IT 的能力,否则的话我们也没法去管理。刻奇:对,这个是跟技术相关的、支持超级猩猩从大变小的一个非常核心的逻辑。如果没有 IT 的支持,店面的增加会直接导致管理成本急剧增加。大的健身房管理上面就一个店,相对管理成本会变低一点,但是你分散的话,管理成本就会变高,但是通过提高 IT 能力,即便我们有很多店,我们依然没有前台,没有额外的管理成本,还是能够开这种小的店,能够保证同样的管理效率。举个例子,可能十年、二十年前大超市是主力。而现在有越来越多的社区店、便利店,虽然它们很长时间内还是会并存,但社区店和便利店还是在蚕食大超市的生存空间。大健身房也面临同样的问题,租金水平不断上涨,小的健身房在蚕食市场。而且在这个过程中,小的依然离用户更近。这个也是我觉得很核心的一个逻辑。颖奇:如果有些健身房没有太强的 IT 能力,可能就会买 SaaS 系统或者做外包,去做一些程序并进行一些运营,这个会不会形成一种行业标准?刻奇:我觉得 IT 化一定会成为行业标准的。我们这个行业也就几家有自己的开发能力,另外很多小型公司确实都是买 SaaS,通过 SaaS 的形式去做这个东西。迭代决策系统,数据化建模选址颖奇:IT 的这些能力是怎样帮助你们的?信息传递速度、服务能力上是否有进步?支撑起那么多小的店,提高聚集份额的速度等等这些事情上是否有起到辅助作用?刻奇:是的,基于刚才提到的分布化,加上 IT 能力,标准化能力,我们门店标准的选址可能只需要 1-2 个月,甚至更快。2 个月门店就可以开起来,管理成本的其中一些部分都是 IT 化的,硬件一装,门店空间的硬件控制系统一装,课程也是标准化的,经过培训和招募优秀的教练,一个标准化的门店就直接下去了,是非常快捷的一个过程。颖奇:我有个非常好奇的点,超级猩猩是如何选址进行快速扩张的,是否有一些数据建模的决策机制,有没有一些东西可以和大家聊一聊。刻奇:首先我们自己有内部的一套系统,是基于过往运营情况,加上你对于当下城市,就是门店选址的地方,进行数据对比,然后你就能够大概有一个维度指标了。同时,我们也需要把一个区位的维度指标收集的更全。比如说它旁边的租金水平,旁边有多少个相关的消费品类,人流量等等。这套体系其实做得比较好的是海底捞,他们也是通过这种方式选址。当然,我觉得算法比重会越来越大,现在可能五五吧。一部分是基于算法的能力去理解这个地方合不合适,包括未来适合什么样的运营的方式、策略、排什么课;然后还有一半是能在过程中主观进行感知。颖奇:数据选址是有别于传统的高效率选址方法。刻奇:是的,我觉得数据维度越来越全面之后,人的维度会越来越小,未来我们可能 80% 的决策来自于数据的量化指标,20% 是由人的感性的直觉和经验去做这个决定,这是一个过程。当然我们选址其实也有一个自己的流程系统,这个流程系统使得内部的决策环节更科学更高效,并且一定是全公司一起参与进来响应。其他的部门都要参与到这个角色里面去,提供各自相应的专业分析,市场有自己市场的考量,门店有门店运营的考量,教练有教练的考量。如何把内部这套系统做的既科学又不要太繁琐。我们自己迭代了 80 多个版本去做这套内部的决策系统,并且保证高效,保持我们的科学性,而且持续的进行迭代。颖奇:现在超级猩猩在全国有多少门店?刻奇:差不多九个城市,一百家门店。颖奇:今年大概会有一个什么样的目标?刻奇:今年预计是 100 到 150 家的新增门店数。颖奇:另一个问题,可能是跟所有健身房都有关系的。「健身」本身是个「反人性」的行为,超级猩猩是怎么解决「反人性」这个事情上的用户心理和体验呢?这里面会有哪些是通过我们的技术手段或者运营手段呢?刻奇:有。所有的用户能够做到按次付费,也是线上技术支持之后能达到的一个效果。因为按次付费,所以来的人消费决策是认真的,投入是不一样的,和在传统健身房上免费团课的感觉是不一样的。传统健身房按年收费,超级猩猩按次收费,我们的教练也知道课教得不好不仅会影响满员率,还会影响下次的直接收入。学员和教练的投入会营造一个氛围,一个人练会觉得「反人性」,但是当有了氛围之后,学员也觉得挺开心的。颖奇:这解决了单次来的投入问题,那怎么解决让他每次都来的问题呢?刻奇:对,这也是我们线上部分去做的,线上化运营之后可以引入很多游戏化的运营手段。比如我们去年做了「猩章」成长体系,现在很多人跟随我们也在做了。颁发游戏等级徽章,按不同课程类型细化,还有解锁,名人堂,累计排名等等。核心是这样设计之后给学员带来的是「多重的反馈」。学员会觉得「我不只是流了汗,我自己开心」,当然我们后面也在做新的尝试,比如做硬件相关的新的产品。就好像游戏能刺激玩家玩下去的原因一样,游戏中的反馈很丰富很及时,这也是提高用户粘性的一个重要方式。另外,我们也给教练提供一些能力帮助他们去提高课程的体验。教练端产品会看到每一节课学员能力的分布,有多少学生是第一次来超级猩猩,有多少同学是第一次上这个教练的课,然后教练就能快速理解这节课他该怎么样动态地调整的授课方式和交流方式。「规模」中打开边界,重新「发明」健身房颖奇:接下来聊聊您自己,首先可以讲一下您的名字「刻奇」是怎么来的?刻奇:我在青年时代喜欢米兰·昆德拉的《生命中不能承受之轻》,里面比较重要的一个概念就是「刻奇」。大家对这个的翻译有很多讨论,书里面可能会比较常规的翻译是「媚俗」这种概念去讲的,我个人觉得它是用一个词概括了一种社会心理状态。像喝醉酒了之后会说很多的话,所有的情绪都带入进去,同时希望全世界都要陷入到这种情绪之中。这是在社会心理学里面非常常见的一种从众状态,比如一次灾难性事件,所有人都要在朋友圈去点蜡烛,类似这样的心理状态。「刻奇」这个词当时引发了我很深的思考,就作为我自己名字了。颖奇:那是什么契机让您加入了超级猩猩呢?您在来超级猩猩之前,大概一些工作的履历是什么样的呢?刻奇:我最早是在迅雷。虽然那时候是产品经理,但是之前也是技术出身。在迅雷的时候也比较有幸参与了迅雷当时可能是最盈利的部门——会员事业部,也经历了从 PC 到移动互联网迁移的过程。后面短暂的去了一下支付宝,在支付宝无线端负责账号和安全这部分的产品业务。然后就又回到了深圳参与超级猩猩的创业过程。颖奇:最后您可以分享几本最近看的、认为比较好的书给大家。刻奇:我最近看的一本比较好的是《规模》,有一章我印象很深,为什么在发展过程中大多数公司会越做越衰败,但城市会越来越大。为什么同样是在规模增长过程中会发生不同的命运,里面提到一个很重要的概念是维度性。公司随着增长它的维度是越来越低的,因为公司越来越大,里面的沟通效率、反馈效率在不停的降低,这几乎是常态,所有的公司慢慢的效率都会变低,沟通反馈机制会越来越慢。而城市不一样,城市随着规模扩大,它会不停的引进新的人、新的技术,并且边界不断放大,它永远是充满生命的这种活力,它的维度是随着它的规模增大在变高的。颖奇:这更多可能还是你自己的规模在变大,实际上量变走向质变了。刻奇:对,可能是发生质变了。以健身行业为例,为什么健身连锁在世界范围里都没有一个能够走到那么高的位置,它是不是在过程中有某种原因导致的。而比如说可口可乐公司能够增长如此快,能够跨越时间周期惯例周期,这个过程中是不是由他们不同的增长模式导致的?颖奇:对,我觉得有些东西可能是设计出来的,有些东西可能是他早期基因决定的。包括现在我们作为观察者来看超级猩猩,也是看到超级猩猩在一点点改变,也不是完全设计出来的。我觉得你是在参与到重新发明健身房,包括重新发明健身房、用户和健身教练之间的关系之中的。刻奇:所以我也觉得你们 ONES 很重要,因为所有的公司都应该是互联网公司,都一定要有自己的研发能力。现在如果哪个公司没有自己的研发团队,我都不太看好公司的前景,它可能只是一个短期的小生意。如果一个公司真的是要做一个企业长期发展,它一定会需要自己内部的技术基因。如果以前没有,现在一定要有,不然就会被时代淘汰。颖奇:近期有一些传统行业例如家具厂商、海鲜企业也来采购我们的研发管理工具,就说明他们内部都有软件的部门了,这个发展是比我们之前想象的要快很多的。今天非常感谢您的分享。想要更好更快地发布产品?请扫描二维码了解更多。或访问http://ones.ai。

April 8, 2019 · 1 min · jiezi

如何免费创建云端爬虫集群

移步 GitHub

April 4, 2019 · 1 min · jiezi

Kubernetes 中如何保证优雅地停止 Pod

作者:吴叶磊一直以来我对优雅地停止 Pod 这件事理解得很单纯:不就利用是 PreStop hook 做优雅退出吗?但最近发现很多场景下 PreStop Hook 并不能很好地完成需求,这篇文章就简单分析一下“优雅地停止 Pod”这回事儿。何谓优雅停止?优雅停止(Graceful shutdown)这个说法来自于操作系统,我们执行关机之后都得 OS 先完成一些清理操作,而与之相对的就是硬中止(Hard shutdown),比如拔电源。到了分布式系统中,优雅停止就不仅仅是单机上进程自己的事了,往往还要与系统中的其它组件打交道。比如说我们起一个微服务,网关把一部分流量分给我们,这时:假如我们一声不吭直接把进程杀了,那这部分流量就无法得到正确处理,部分用户受到影响。不过还好,通常来说网关或者服务注册中心会和我们的服务保持一个心跳,过了心跳超时之后系统会自动摘除我们的服务,问题也就解决了;这是硬中止,虽然我们整个系统写得不错能够自愈,但还是会产生一些抖动甚至错误。假如我们先告诉网关或服务注册中心我们要下线,等对方完成服务摘除操作再中止进程,那不会有任何流量受到影响;这是优雅停止,将单个组件的启停对整个系统影响最小化。按照惯例,SIGKILL 是硬终止的信号,而 SIGTERM 是通知进程优雅退出的信号,因此很多微服务框架会监听 SIGTERM 信号,收到之后去做反注册等清理操作,实现优雅退出。PreStop Hook回到 Kubernetes(下称 K8s),当我们想干掉一个 Pod 的时候,理想状况当然是 K8s 从对应的 Service(假如有的话)把这个 Pod 摘掉,同时给 Pod 发 SIGTERM 信号让 Pod 中的各个容器优雅退出就行了。但实际上 Pod 有可能犯各种幺蛾子:已经卡死了,处理不了优雅退出的代码逻辑或需要很久才能处理完成。优雅退出的逻辑有 BUG,自己死循环了。代码写得野,根本不理会 SIGTERM。因此,K8s 的 Pod 终止流程中还有一个“最多可以容忍的时间”,即 grace period(在 Pod 的 .spec.terminationGracePeriodSeconds 字段中定义),这个值默认是 30 秒,我们在执行 kubectl delete 的时候也可通过 –grace-period 参数显式指定一个优雅退出时间来覆盖 Pod 中的配置。而当 grace period 超出之后,K8s 就只能选择 SIGKILL 强制干掉 Pod 了。很多场景下,除了把 Pod 从 K8s 的 Service 上摘下来以及进程内部的优雅退出之外,我们还必须做一些额外的事情,比如说从 K8s 外部的服务注册中心上反注册。这时就要用到 PreStop Hook 了,K8s 目前提供了 Exec 和 HTTP 两种 PreStop Hook,实际用的时候,需要通过 Pod 的 .spec.containers[].lifecycle.preStop 字段为 Pod 中的每个容器单独配置,比如:spec: contaienrs: - name: my-awesome-container lifecycle: preStop: exec: command: ["/bin/sh","-c","/pre-stop.sh"]/pre-stop.sh 脚本里就可以写我们自己的清理逻辑。最后我们串起来再整个表述一下 Pod 退出的流程(官方文档里更严谨哦):用户删除 Pod。2.1. Pod 进入 Terminating 状态。2.2. 与此同时,K8s 会将 Pod 从对应的 service 上摘除。2.3. 与此同时,针对有 PreStop Hook 的容器,kubelet 会调用每个容器的 PreStop Hook,假如 PreStop Hook 的运行时间超出了 grace period,kubelet 会发送 SIGTERM 并再等 2 秒。2.4. 与此同时,针对没有 PreStop Hook 的容器,kubelet 发送 SIGTERM。grace period 超出之后,kubelet 发送 SIGKILL 干掉尚未退出的容器。这个过程很不错,但它存在一个问题就是我们无法预测 Pod 会在多久之内完成优雅退出,也无法优雅地应对“优雅退出”失败的情况。而在我们的产品 TiDB Operator 中,这就是一个无法接受的事情。有状态分布式应用的挑战为什么说无法接受这个流程呢?其实这个流程对无状态应用来说通常是 OK 的,但下面这个场景就稍微复杂一点:TiDB 中有一个核心的分布式 KV 存储层 TiKV。TiKV 内部基于 Multi-Raft 做一致性存储,这个架构比较复杂,这里我们可以简化描述为一主多从的架构,Leader 写入,Follower 同步。而我们的场景是要对 TiKV 做计划性的运维操作,比如滚动升级,迁移节点。在这个场景下,尽管系统可以接受小于半数的节点宕机,但对于预期性的停机,我们要尽量做到优雅停止。这是因为数据库场景本身就是非常严苛的,基本上都处于整个架构的核心部分,因此我们要把抖动做到越小越好。要做到这点,就得做不少清理工作,比如说我们要在停机前将当前节点上的 Leader 全部迁移到其它节点上。得益于系统的良好设计,大多数时候这类操作都很快,然而分布式系统中异常是家常便饭,优雅退出耗时过长甚至失败的场景是我们必须要考虑的。假如类似的事情发生了,为了业务稳定和数据安全,我们就不能强制关闭 Pod,而应该停止操作过程,通知工程师介入。 这时,上面所说的 Pod 退出流程就不再适用了。小心翼翼:手动控制所有流程这个问题其实 K8s 本身没有开箱即用的解决方案,于是我们在自己的 Controller 中(TiDB 对象本身就是一个 CRD)与非常细致地控制了各种操作场景下的服务启停逻辑。抛开细节不谈,最后的大致逻辑是在每次停服务前,由 Controller 通知集群进行节点下线前的各种迁移操作,操作完成后,才真正下线节点,并进行下一个节点的操作。而假如集群无法正常完成迁移等操作或耗时过久,我们也能“守住底线”,不会强行把节点干掉,这就保证了诸如滚动升级,节点迁移之类操作的安全性。但这种办法存在一个问题就是实现起来比较复杂,我们需要自己实现一个控制器,在其中实现细粒度的控制逻辑并且在 Controller 的控制循环中不断去检查能否安全停止 Pod。另辟蹊径:解耦 Pod 删除的控制流复杂的逻辑总是没有简单的逻辑好维护,同时写 CRD 和 Controller 的开发量也不小,能不能有一种更简洁,更通用的逻辑,能实现“保证优雅关闭(否则不关闭)”的需求呢?有,办法就是 ValidatingAdmissionWebhook。这里先介绍一点点背景知识,Kubernetes 的 apiserver 一开始就有 AdmissionController 的设计,这个设计和各类 Web 框架中的 Filter 或 Middleware 很像,就是一个插件化的责任链,责任链中的每个插件针对 apiserver 收到的请求做一些操作或校验。举两个插件的例子:DefaultStorageClass,为没有声明 storageClass 的 PVC 自动设置 storageClass。ResourceQuota,校验 Pod 的资源使用是否超出了对应 Namespace 的 Quota。虽然说这是插件化的,但在 1.7 之前,所有的 plugin 都需要写到 apiserver 的代码中一起编译,很不灵活。而在 1.7 中 K8s 就引入了 Dynamic Admission Control 机制,允许用户向 apiserver 注册 webhook,而 apiserver 则通过 webhook 调用外部 server 来实现 filter 逻辑。1.9 中,这个特性进一步做了优化,把 webhook 分成了两类: MutatingAdmissionWebhook 和 ValidatingAdmissionWebhook,顾名思义,前者就是操作 api 对象的,比如上文例子中的 DefaultStroageClass,而后者是校验 api 对象的,比如 ResourceQuota。拆分之后,apiserver 就能保证在校验(Validating)之前先做完所有的修改(Mutating),下面这个示意图非常清晰:而我们的办法就是,利用 ValidatingAdmissionWebhook,在重要的 Pod 收到删除请求时,先在 webhook server 上请求集群进行下线前的清理和准备工作,并直接返回拒绝。这时候重点来了,Control Loop 为了达到目标状态(比如说升级到新版本),会不断地进行 reconcile,尝试删除 Pod,而我们的 webhook 则会不断拒绝,除非集群已经完成了所有的清理和准备工作。下面是这个流程的分步描述:用户更新资源对象。controller-manager watch 到对象变更。controller-manager 开始同步对象状态,尝试删除第一个 Pod。apiserver 调用外部 webhook。webhook server 请求集群做 tikv-1 节点下线前的准备工作(这个请求是幂等的),并查询准备工作是否完成,假如准备完成,允许删除,假如没有完成,则拒绝,整个流程会因为 controller manager 的控制循环回到第 2 步。好像一下子所有东西都清晰了,这个 webhook 的逻辑很清晰,就是要保证所有相关的 Pod 删除操作都要先完成优雅退出前的准备,完全不用关心外部的控制循环是怎么跑的,也因此它非常容易编写和测试,非常优雅地满足了我们“保证优雅关闭(否则不关闭)”的需求,目前我们正在考虑用这种方式替换线上的旧方案。后记其实 Dynamic Admission Control 的应用很广,比如 Istio 就是用 MutatingAdmissionWebhook 来实现 envoy 容器的注入的。从上面的例子中我们也可以看到它的扩展能力很强,而且常常能站在一个正交的视角上,非常干净地解决问题,与其它逻辑做到很好的解耦。当然了,Kubernetes 中还有 非常多的扩展点,从 kubectl 到 apiserver,scheduler,kubelet(device plugin,flexvolume),自定义 Controller 再到集群层面的网络(CNI),存储(CSI)可以说是处处可以做事情。以前做一些常规的微服务部署对这些并不熟悉也没用过,而现在面对 TiDB 这样复杂的分布式系统,尤其在 Kubernetes 对有状态应用和本地存储的支持还不够好的情况下,得在每一个扩展点上去悉心考量,做起来非常有意思,因此后续可能还有一些 TiDB Operator 中思考过的解决方案分享。 ...

April 2, 2019 · 2 min · jiezi

信用算力基于 RocketMQ 实现金融级数据服务的实践

导读:微服务架构已成为了互联网的热门话题之一,而这也是互联网技术发展的必然阶段。然而,微服务概念的提出者 Martin Fowler 却强调:分布式调用的第一原则就是不要分布式。纵观微服务实施过程中的弊端,可以推断出作者的意图,就是希望系统架构者能够谨慎地对待分布式调用,这是分布式系统自身存在的缺陷所致。但无论是 RPC 框架,还是 REST 框架,都因为驻留在不同进程空间的分布式组件,而引入了额外的复杂度。因而可能对系统的效率、可靠性、可预测性等诸多方面带来负面影响。信用算力自2016年开始实施微服务改造,通过消息队列(Message Queue),后文简称MQ,来规避微服务存在的缺陷,实现金融级数据服务。以下是一些使用场景和心得。为什么需要 MQ一、案例介绍先来看一个当前的真实业务场景。对于通过信息流获客的企业而言,当用户注册时,因业务需求会调用用户服务,然后执行一系列操作,注册 -> 初始化账户信息 -> 邀友奖励发放 -> 发放优惠券 -> … -> 信息流数据上报。用户服务的开发人员压力非常大,因为需要调用非常多的服务,业务耦合严重。如果当时账户服务正在执行发版操作,那么初始化账户动作会失败。然而平台经过不断的迭代更新,后续又新增了一个签到业务,新注册用户默认签到一次。这就需要修改用户服务,增加调用签到服务的接口。每当遇到此种情况,开发用户服务的同学就非常不爽了,为什么总是我?新增签到业务和用户服务又有什么关系?为解决此类重度依赖的问题,我们在架构层面引入了 MQ,用来规避微服务之间重度耦合调用的弊端。新架构如下图:用户完成注册动作后,只需要往 MQ 发送一个用户注册的通知消息,下游业务如需要依赖注册相关的数据,订阅注册消息的 topic 即可,从而实现了业务的解耦。看完上述真实的案例后,大家可能产生疑惑,到底什么是 MQ,使用 MQ 又有什么好处?适合使用 MQ 的场景和不适合使用 MQ 的场景有哪些不同?二、什么是 MQ?简单来说,MQ(MessageQueue)是一种跨进程的通信机制,用于上下游传递消息。适合使用 MQ 的场景有:1、上游不关心下游执行结果,例如上述案例中用户注册后,我们并不关心账户是否初始化,是否上报了信息流等;2、异步返回执行时间长:例如上述案例中,当邀友奖励发放,需要经历很多风控规则,执行时间比较长,但是用户并不关注奖励何时发放。不适合使用MQ场景调用方实时关注执行结果,例如用户发起注册动作后,需要立刻知道,注册结果是成功还是失败,这种需要实时知道最终执行结果的场景,就不适合使用MQ。三、使用MQ的好处:1、解耦2、可靠投递3、广播4、最终一致性5、流量削峰6、消息投递保证7、异步通信(支持同步)8、提高系统吞吐、健壮性MQ 的技术选型目前业内比较主流的 MQ 包括 RocketMQ、ActiveMQ、RabbitMQ、Kafka等,关于性能、存储、社区活跃度等各方面的技术对比已经很多,本文不再重复。但我们发现通过简单的选型对比,很难抉择到底选择哪款MQ产品。因为金融行业对于数据一致性以及服务可用性的要求非常高,所以任何关于技术的选项都显得尤为重要。经调研,如微众银行、民生银行、平安银行等国内知名的互联网银行和直销银行代表,都在使用 RocketMQ,且 RocketMQ 出生在阿里系,经受过各种生产压力的考验,非常稳定。并且,目前此项技术已经捐增给 Apache 社区,社区活跃度非常高。另外 RocketMQ 开发语言是Java,开发同学遇到解决不了的问题点,或者不清楚的概念,可以直接 Debug 源码。经过多方面的比较,我们选择 RocketMQ 作为规避微服务弊端的利器。MQ 在微服务下的使用场景MQ 是一种跨进程的通信机制,用于上下游传递消息,目前信用算力将 RocketMQ 应用于解耦、流量削峰、分布式事务的处理等几个场景。一、解耦通常解耦的做法是生产者发送消息到 MQ,下游订阅 MQ 的特定 topic,当下游接收到消息后开始处理业务逻辑。那么,消息发送方到底应该是由谁来承担?是服务提供者在处理完RPC请求后,根据业务需求开始发送消息吗?但此刻开发人员就会抱怨为什么总是我?为什么处理完业务后需要发送 MQ?为此,在解耦的过程中通过订阅数据库的 BinLog 日志,开发了一套 BinLog 日志解析模块,专门解析日志,然后生成 JSON 字符串后发送消息到 MQ,下游订阅 MQ 即可。流程如下:目前所有需要依赖下游服务的业务线,其数据变动都采用此方案。方案优缺点:优点:1、服务之间依赖完全解耦,任何基于注册行为的业务变更,都无需依赖上游,只需订阅MQ即可;2、系统的稳定性和吞吐量增加了,用户注册的响应时间缩短了;缺点:1、引入MQ后系统复杂性增加,维护成本增加;2、从注册开始到全部数据初始化结束的整体时间增加了;二、流量削峰每逢遇到会员日的时候,平台会发送大量的会员福利活动通知,以短信、站内信、PUSH 消息的方式通知注册用户。所有的消息会在很短的时间全部推送到消息中心,同时正常的业务通知任然有大量业务消息推送到消息中心。为保障平台的稳定性和可靠性,在消息中心前置了多种 topic,如短信、推送、站内提醒。消息中心接收到消息后会全部写入不同 topic 的 MQ,多个消费者来消费并把信息推送给终端用户。三、分布式事务用户在平台上支付他订购某种业务的时候,需要涉及到支付服务、账户服务、优惠券服务、积分服务,在单体模式下这种业务非常容易实现,通过事务即可完成,伪代码如下:然而,在微服务的情况下,原本通过简单事务处理的却变得非常复杂,若引入两阶段提交(2PC)或者补偿事务(TCC)方案,则系统的复杂程度会增加。信用算力的做法是通过本地事务 + MQ 消息的方式来解决, 虽然 RocketMQ 也支持事务消息,但是其他主流 MQ 并没有此项功能,所以综合考虑采用如下方案:消息上游:需要额外建一个tc_message表,并记录消息发送状态。消息表和业务数据在同一个数据库里面,而且要在一个事务里提交。然后消息会经过MQ发送到消息的消费方。如果消息发送失败,会进行重试发送;消息上游:开启定时任务扫描tc_message表,如果超过设置的时间内状态没有变更,会再次发送消息到MQ,如重试次数达到上限则发起告警操作;消息下游:需要处理这个消息,并完成自己的业务逻辑。此时如果本地事务处理成功,表明已经处理完成了,需要发起业务回调通知业务方;方案优缺点:优点:1、用最小的代价实现分布式事物,以达到数据最终一致性;2、方案非常灵活,任何环节都可以人为控制;缺点:1、复杂性增加了,业务操作的时候需要写入 tc_message 表以及发送 MQ,同时还需要考虑状态超时未变更的补发机制以及告警处理机制;2、用户看到的数据,存在有短暂不一致的情况;心得体会使用 RocketMQ 3年多了,总体来说运行的非常稳定,基本上没有发生过生产事故,下面说说这几年使用下来的心得体会:1、一个应用尽可能用一个 Topic,消息子类型用 tags 来标识。Topic 名称和 Tags 名称可以自行设置。Producer,Consumer都需要规范,要做到见名知意。发送消息时候必须携带 Tags,消费方在订阅消息时,才可以利用 Tags 在 Broker 做消息过滤。2、每条消息在业务层面有唯一标识码,方便在系统出现异常的情况,可以通过业务维度查询。举个栗子,当用户在平台注册成功后,会以 Topic 和 UserID 作为唯一标识码(topic_user_10011),服务器会为每个消息创建索引,该消息会持久化入库,以防将来定位消息丢失等问题。下游收到消息后会以 Topic+Key 方式来记录消费行为,包括消息日期、当前机器IP地址、处理结果等;也可以通过 Topic+Key 的方式来查询这条消息内容,包括消息被谁消费,以及这条 MQ 在每个环节的处理状态。3、消息发送成功或者失败,都需要记录 log 日志,且必须打印 sendresult、MsgID、唯一标识码。4、由于上游会做消息重试机制,所以下游消息必须要做幂等处理。5、需要封装 MQ 的 API 在封装后,API 需屏蔽底层 MQ 的特性,开发人员无需关注到底是用的哪个 MQ 来支持本地分布式事物、MQ 消息自动入库、自动打印日志,减少开发人员操作成本。总的来说,MQ 是一个互联网架构中常见的解耦利器,在这3年中,信用算力在微服务中一直使用 MQ 来为金融客户提供高质量的数据服务。虽然 MQ 不是唯一方案,但是从目前阶段来看,的确是一种非常不错的解决方案。本文作者:潘志伟(信用算力技术总监,QCon 演讲嘉宾,十多年 Java 从业经验,精通微服务架构,精通大数据。拥有亿级用户平台架构经验,万级并发的API网关经验。)本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 29, 2019 · 1 min · jiezi

一篇读懂分布式架构下的负载均衡

一篇读懂分布式架构下的负载均衡微信公众号:IT一刻钟大型现实非严肃主义现场一刻钟与你分享优质技术架构与见闻,做一个有剧情的程序员关注可第一时间了解更多精彩内容,定期有福利相送哟。什么是负载均衡?百度词条里的解释是:负载均衡,英文叫Load Balance,意思就是将请求或者数据分摊到多个操作单元上进行执行,共同完成工作任务。它的目的就通过调度集群,达到最佳化资源使用,最大化吞吐率,最小化响应时间,避免单点过载的问题。负载均衡分类负载均衡可以根据网络协议的层数进行分类,我们这里以ISO模型为准,从下到上分为:物理层,数据链路层,网络层,传输层,会话层,表示层,应用层。当客户端发起请求,会经过层层的封装,发给服务器,服务器收到请求后经过层层的解析,获取到对应的内容。二层负载均衡二层负债均衡是基于数据链路层的负债均衡,即让负债均衡服务器和业务服务器绑定同一个虚拟IP(即VIP),客户端直接通过这个VIP进行请求,那么如何区分相同IP下的不同机器呢?没错,通过MAC物理地址,每台机器的MAC物理地址都不一样,当负载均衡服务器接收到请求之后,通过改写HTTP报文中以太网首部的MAC地址,按照某种算法将请求转发到目标机器上,实现负载均衡。这种方式负载方式虽然控制粒度比较粗,但是优点是负载均衡服务器的压力会比较小,负载均衡服务器只负责请求的进入,不负责请求的响应(响应是有后端业务服务器直接响应给客户端),吞吐量会比较高。三层负载均衡三层负载均衡是基于网络层的负载均衡,通俗的说就是按照不同机器不同IP地址进行转发请求到不同的机器上。这种方式虽然比二层负载多了一层,但从控制的颗粒度上看,并没有比二层负载均衡更有优势,并且,由于请求的进出都要经过负载均衡服务器,会对其造成比较大的压力,性能也比二层负载均衡要差。四层负载均衡四层负载均衡是基于传输层的负载均衡,传输层的代表协议就是TCP/UDP协议,除了包含IP之外,还有区分了端口号,通俗的说就是基于IP+端口号进行请求的转发。相对于上面两种,控制力度缩小到了端口,可以针对同一机器上的不用服务进行负载。这一层以LVS为代表。七层负载均衡七层负载均衡是基于应用层的负载均衡,应用层的代表协议有HTTP,DNS等,可以根据请求的url进行转发负载,比起四层负载,会更加的灵活,所控制到的粒度也是最细的,使得整个网络更"智能化"。例如访问一个网站的用户流量,可以通过七层的方式,将对图片类的请求转发到特定的图片服务器并可以使用缓存技术;将对文字类的请求可以转发到特定的文字服务器并可以使用压缩技术。可以说功能是非常强大的负载。这一层以Nginx为代表。在普通的应用架构中,使用Nginx完全可以满足需求,对于一些大型应用,一般会采用DNS+LVS+Nginx的方式进行多层次负债均衡,以上这些说明都是基于软件层面的负载均衡,在一些超大型的应用中,还会在前面多加一层物理负载均衡,比如知名的F5。负载均衡算法负载均衡算法分为两类:一种是静态负载均衡,一种是动态负载均衡。静态均衡算法:1、轮询法将请求按顺序轮流地分配到每个节点上,不关心每个节点实际的连接数和当前的系统负载。优点:简单高效,易于水平扩展,每个节点满足字面意义上的均衡;缺点:没有考虑机器的性能问题,根据木桶最短木板理论,集群性能瓶颈更多的会受性能差的服务器影响。2、随机法将请求随机分配到各个节点。由概率统计理论得知,随着客户端调用服务端的次数增多,其实际效果越来越接近于平均分配,也就是轮询的结果。优缺点和轮询相似。3、源地址哈希法源地址哈希的思想是根据客户端的IP地址,通过哈希函数计算得到一个数值,用该数值对服务器节点数进行取模,得到的结果便是要访问节点序号。采用源地址哈希法进行负载均衡,同一IP地址的客户端,当后端服务器列表不变时,它每次都会落到到同一台服务器进行访问。优点:相同的IP每次落在同一个节点,可以人为干预客户端请求方向,例如灰度发布;缺点:如果某个节点出现故障,会导致这个节点上的客户端无法使用,无法保证高可用。当某一用户成为热点用户,那么会有巨大的流量涌向这个节点,导致冷热分布不均衡,无法有效利用起集群的性能。所以当热点事件出现时,一般会将源地址哈希法切换成轮询法。4、加权轮询法不同的后端服务器可能机器的配置和当前系统的负载并不相同,因此它们的抗压能力也不相同。给配置高、负载低的机器配置更高的权重,让其处理更多的请;而配置低、负载高的机器,给其分配较低的权重,降低其系统负载,加权轮询能很好地处理这一问题,并将请求顺序且按照权重分配到后端。加权轮询算法要生成一个服务器序列,该序列中包含n个服务器。n是所有服务器的权重之和。在该序列中,每个服务器的出现的次数,等于其权重值。并且,生成的序列中,服务器的分布应该尽可能的均匀。比如序列{a, a, a, a, a, b, c}中,前五个请求都会分配给服务器a,这就是一种不均匀的分配方法,更好的序列应该是:{a, a, b, a, c, a, a}。优点:可以将不同机器的性能问题纳入到考量范围,集群性能最优最大化;缺点:生产环境复杂多变,服务器抗压能力也无法精确估算,静态算法导致无法实时动态调整节点权重,只能粗糙优化。5、加权随机法与加权轮询法一样,加权随机法也根据后端机器的配置,系统的负载分配不同的权重。不同的是,它是按照权重随机请求后端服务器,而非顺序。6、键值范围法根据键的范围进行负债,比如0到10万的用户请求走第一个节点服务器,10万到20万的用户请求走第二个节点服务器……以此类推。优点:容易水平扩展,随着用户量增加,可以增加节点而不影响旧数据;缺点:容易负债不均衡,比如新注册的用户活跃度高,旧用户活跃度低,那么压力就全在新增的服务节点上,旧服务节点性能浪费。而且也容易单点故障,无法满足高可用。(注:以上所提到的单点故障,都可以用主从方式来解决,从节点监听主节点心跳,当发现主节点死亡,从节点切换成主节点顶替上去。这里可以思考一个问题,怎么设计集群主从可以最大程度上降低成本)动态负债均衡算法:1、最小连接数法根据每个节点当前的连接情况,动态地选取其中当前积压连接数最少的一个节点处理当前请求,尽可能地提高后端服务的利用效率,将请求合理地分流到每一台服务器。俗称闲的人不能闲着,大家一起动起来。优点:动态,根据节点状况实时变化;缺点:提高了复杂度,每次连接断开需要进行计数;实现:将连接数的倒数当权重值。2、最快响应速度法根据请求的响应时间,来动态调整每个节点的权重,将响应速度快的服务节点分配更多的请求,响应速度慢的服务节点分配更少的请求,俗称能者多劳,扶贫救弱。优点:动态,实时变化,控制的粒度更细,跟灵敏;缺点:复杂度更高,每次需要计算请求的响应速度;实现:可以根据响应时间进行打分,计算权重。3、观察模式法观察者模式是综合了最小连接数和最快响应度,同时考量这两个指标数,进行一个权重的分配。说在后面话还有哪些负载均衡的算法,或者有更好的想法或问题,欢迎留言交流!

March 29, 2019 · 1 min · jiezi

对话巨杉核心研发团队:分布式数据库自研之路

一直以来,数据库的核心研发团队都十分神秘,作为隐藏在幕后的隐士高人,他们对数据库发展以及数据库研发团队的看法是什么呢?本文我们就由巨杉数据库核心技术研发团队的“老司机”,向大家分享他分布式数据库的自研之路。Q:作为数据库行业的“老司机”,您能否先介绍一下自己?A:我叫Danny,是巨杉数据库核心研发团队的成员,是一名数据库资深工程师和架构师,有超过20年的数据库核心研发经验,曾经作为DB2 内核研发团队成员参与了DB2 ,DPF等产品的架构设计和研发工作。目前,我们北美研发实验室的团队已经有很多数据库的专家“老司机”加入,全部来自DB2 的核心技术团队。虽然我们团队很多都是来自IBM、华为的“传统企业级IT人”,不太喜欢抛头露面。但是现在是技术圈一个变革的新时代,我们的产品已经开源了,所以我们之后也会让我们团队的技术大牛们多多参与社区活动,分享一下我们做数据库核心研发的心得,同时也和大家一起进步。Q:作为“老IBM”,您认为像IBM这样历史悠久的IT企业,他们的核心研发团队是怎么样的呢?您对此感受最深的是什么?A:IBM是最早提出“关系型数据库”这一概念和理论体系的公司,从技术上看,传统三大关系型数据库在发展过程中,其实已经具有很深远的技术储备了。DB2是三大传统关系型数据库中唯一的分布式产品,因此我们团队在分布式技术方面的积累是一脉相承的。我在DB2的十几年里,感受最深的就是技术底蕴和沉淀。比如说,在Unix真正支持线程机制之前,针对多线程模型,甚至是针对不同的硬件设备,他们早已使用汇编语言实现了逻辑线程的切换和调用,这些机制在当时其实是相当领先的。说到研发团队,IBM的实验室也是卧虎藏龙。从最初使用汇编语言开始的技术专家们,一直在参与数据库、操作系统和编译器底层的研发工作,可以说正是他们创造了最早的关系型数据库的概念,也是他们真正把数据库打造成为一个通用的软件平台。Q:像数据库这样的基础软件,技术上的难度是什么?A:数据库软件,特别是一款真正企业级ready的产品,并没有大家想象的,只是开发一款软件那么简单。从技术上来说,数据库既需要有技术基因的传承,又需要创新。数据库技术到现在已经发展了40多年了。在技术的发展中,数据库软件/平台已经成为一个功能复杂,架构庞大,安全要求很高的庞大软件产品体系。因此,技术上既需要技术的积累,也需要新的创新。同时,在应用端这边,由于用户都是银行、政府等这些30年前就开始使用数据库的老客户,他们通常无法承担全盘迁移的风险,因此在业务技术架构上,难免保留了各个时代的历史遗留,比如说,北美一些银行的核心IT系统,直到目前仍然运行在40年前的技术平台之上。这也要求企业级ready的数据库基础软件需要有很强的兼容能力,不但可以保证旧业务的运行,还可以不断地推陈出新。这种创新是必须的,但在技术上却又是最难的。Q:以您近20年的数据库行业经验,您认为数据库核心团队应该是怎么样的?A:我认为数据库核心研发团队的基因很重要,就比如说IBM的DB2团队,就是以多位数据库领域的“老炮儿”为核心,搭配有技术实力的资深工程师,而不像现在很多的开源新产品,他们都是以年轻的创新团队为主。就像我上面提到的技术复杂度和产品历史跨度的问题,数据库产品如果要在大型企业内使用,技术团队必须要有传统数据库的开发经验,这也就是技术老炮儿存在的作用。简单说,数据库基础软件就是创新技术和技术经验积累的融合体。Q:海内外基础软件研发有什么不同?A:相对来说,海外拥有技术人才的基础,也有像IBM Oracle这样的体系的沿袭,培养出了一批批技术人才和团队。所以现在北美很多新一代基础软件产品团队其实还是围绕了老一辈的“老司机”构建的。国内基础软件的人才积累还不够,因此基础软件领域还没有完全形成基础软件领域的武林门派,这也是近年来基础软件和AI领域国内企业疯狂往外招人的原因。但是数据库由于历史原因,国内无论是互联网还是科研团队想要形成独特的门派,还需要时间。巨杉这边我们的团队拥有以王涛为代表的很多DB2 团队的核心技术专家,以及来自华为的技术核心团队成员,是技术基因和技术创新很好的结合。Q:数据库开发和其他软件有什么不同?A:因为刚才提到的这些特点,基础软件特别是数据库的研发,和其他应用软件有很大的不同。其中最大的一个不同点就是开发语言和开发模式。从计算机的发展来看,C是最面向机器语言(汇编代码)的,原则上每一行C代码都可以很精准地映射到一些汇编指令上,因此从对操作系统底层的操控来看最为精准。C++则是在C之上发展起来的面向对象语言。在底层编程中,C++的高级特性被使用的非常少,但是其设计模式对于模块化开发很有帮助。因此使用C++既可以兼顾对操作系统底层最精准的把控,也可以将一些面向对象的理念融入代码中,在复杂系统构建时起到重要作用。如今新的一些新型开发语言则不是面向对象,因此在设计模式上不适合大型复杂系统的开发。同时,这些语言语言简化了很多C/C++里最为重要的指针概念,使其对内存的精准操作变得不可能完成。指针这个概念用好了是神器,用差了是垃圾,大部分能力不高的程序员,或者没有非常完善测试框架的项目很难完美把握指针这类高级特性,使得大型项目开发里面内存泄露和崩溃漏洞遍地都是。但是对于我们巨杉来说,有着DB2数据库内核的研发经验,从人员能力,到代码质量管理,到测试框架的完善都能够完美驾驭这类高级特性,最大程度挖掘出操作系统和数据库底层的性能与处理能力。Q:分布式数据库方向是什么?A:根据Gartner和我们CTO王涛的共同观点,真正特别大使得传统关系型数据库存不下的表相对来讲数量都是可控的。因此有很多workaround都可以搞定这个问题,这也是为什么传统以来大家用分库分表虽然麻烦,但也不是解决不了应用问题。数据库其实真正面临的痛点是“微服务”下,数据服务的资源池化。应用程序从传统烟囱式构建,向微服务转型的过程中,在每一个微服务上都放一个独立的数据库已经是不可能的事情了。这种情况下,数据服务资源池需要直接面向上层成百上千个,来自不同开发商、不同团队的,开发能力不一、应用类型不同、SLA安全级别不同等等的各类需求。因此,资源池必须拥有弹性扩张、资源隔离、多租户、可配置一致性、多模式(支持各类SQL协议)、集群内可配置容灾策略等一系列功能,同时每个数据库实例的计算和存储能力需要做到能够无限扩张,毕竟有些微服务可能会涉及到极多的流水数据,不能限定每个数据库实例使用的资源仅局限于一台物理设备。所以说,单纯为了分布式的OLTP只是解决了不构成刚需的问题(分库分表早可以解决),但是在微服务应用开发的环境下,数据库更是要从资源池化的角度对上层提供服务,同时资源池中的每个数据库实例内部也要支持分布式交易等一系列特性,做到与传统数据库的全兼容。Q:SequoiaDB自从发布3.0版本以来,在社区和市场得到的反馈都很好,能否透露一下产品的一些新动向?A:近期,我们会发布一个新的版本,其中OLTP场景选性能会有大的提升,同时对于SQL处理能力也会有很大提升。在分布式的交易型业务下,整体性能提升将比现在版本有23倍的提升,对比同类产品性能将高出56倍以上。这些在本周的活动我们也会做一个简单的分享和介绍。3月30号,本周末我们巨杉Techday的第二期活动也会在北京举办,我们也会带来一些深度的技术分享,届时也会有现场的视频直播,希望大家也能多多关注和参与!未来我们也会有更多“神秘”的数据库“老司机”给大家带来技术、趋势、见闻的分享~

March 28, 2019 · 1 min · jiezi

400+节点的 Elasticsearch 集群运维

本文首发于InfoQ https://www.infoq.cn/article/… 作者:Anton Hägerstrand翻译:杨振涛目录:数据量版本节点配置索引结构性能Meltwater每天要处理数百万量级的帖子数据,因此需要一种能处理该量级数据的存储和检索技术。从0.11.X 版本开始我们就已经是Elasticsearch的忠实用户了。在经历了一些波折之后,最终我们认为做出了正确的技术选型。Elasticsearch 用于支持我们的主要媒体监控应用,客户通过该应用可以检索和分析媒体数据,比如新闻文章、(公开的)Facebook帖子、Instagram帖子、博客和微博。我们通过使用一个混合API来收集这些内容,并爬取和稍作加工,使得它们可被 Elasticsearch 检索到。本文将分享我们所学到的经验、如何调优 Elasticsearch,以及要绕过的一些陷阱。如果想了解更多关于我们在Elasticsearch方面的点滴,可参考之前博文中的 numad issues 和 batch percolator。1.数据量每天都有数量相当庞大的新闻和微博产生;在高峰期需要索引大约300多万社论文章,和近1亿条社交帖子数据。其中社论数据长期保存以供检索(可回溯到2009年),社交帖子数据保存近15个月的。当前的主分片数据使用了大约200 TB的磁盘空间,副本数据大约600 TB。我们的业务每分钟有3千次请求。所有的请求通过一个叫做 “search-service” 的服务,该服务会依次完成所有与 Elasticsearch 集群的交互。大部分检索规则比较复杂,包括在面板和新闻流中。比如,一个客户可能对 Tesla 和 Elon Musk 感兴趣,但希望排除所有关于 SpaceX 或 PayPal 的信息。用户可以使用一种与 Lucene 查询语法类似的灵活语法,如下:Tesla AND “Elon Musk” NOT (SpaceX OR PayPal)我们最长的此类查询有60多页。重点是:除了每分钟3千次请求以外,没有一个查询是像在 Google 里查询 “Barack Obama” 这么简单的;这简直就是可怕的野兽,但ES节点必须努力找出一个匹配的文档集。2.版本我们运行的是一个基于 Elasticsearch 1.7.6 的定制版本。该版本与1.7.6 主干版本的唯一区别是,我们向后移植(backport)了 roaring bitsets/bitmaps 作为缓存。该功能是从 Lucene 5 移植到 Lucene 4 的,对应移植到了 ES 1.X 版本。Elasticsearch 1.X 中使用默认的 bitset 作为缓存,对于稀疏结果来说开销非常大,不过在 Elasticsearch 2.X 中已经做了优化。为何不使用较新版本的 Elasticsearch 呢?主要原因是升级困难。在主版本间滚动升级只适用于从ES 5到6(从ES 2到5应该也支持滚动升级,但没有试过)。因此,我们只能通过重启整个集群来升级。宕机对我们来说几乎不可接受,但或许可以应对一次重启所带来的大约30-60分钟宕机时间;而真正令人担心的,是一旦发生故障并没有真正的回滚过程。截止目前我们选择了不升级集群。当然我们希望可以升级,但目前有更为紧迫的任务。实际上该如何实施升级尚未有定论,很可能选择创建另一个新的集群,而不是升级现有的。3.节点配置我们自2017年6月开始在AWS上运行主集群,使用i3.2xlarge实例作为数据节点。之前我们在COLO(Co-located Data Center)里运行集群,但后续迁移到了AWS云,以便在新机器宕机时能赢得时间,使得我们在扩容和缩容时更加弹性。我们在不同的可用区运行3个候选 master 节点,并设置 discovery.zen.minimum_master_nodes 为2。这是避免脑裂问题 split-brain problem 非常通用的策略。我们的数据集在存储方面,要求80%容量和3个以上的副本,这使得我们运行了430个数据节点。起初打算使用不同层级的数据,在较慢的磁盘上存储较旧的数据,但是由于我们只有相关的较低量级旧于15个月的数据(只有编辑数据,因为我们丢弃了旧的社交数据),然而这并未奏效。每个月的硬件开销远大于运行在COLO中,但是云服务支持扩容集群到2倍,而几乎不用花费多少时间。你可能会问,为何选择自己管理维护ES集群。其实我们考虑过托管方案,但最后还是选择自己安装,理由是: AWS Elasticsearch Service暴露给用户的可控性太差了,Elastic Cloud 的成本比直接在EC2上运行集群要高2-3倍。为了在某个可用区宕机时保护我们自身,节点分散于eu-west-1的所有3个可用区。我们使用 AWS plugin 来完成该项配置。它提供了一个叫做aws_availability_zone 的节点属性,我们把 cluster.routing.allocation.awareness.attributes 设置为 aws_availability_zone。这保证了ES的副本尽可能地存储在不同的可用区,而查询尽可能被路由到相同可用区的节点。这些实例运行的是 Amazon Linux,临时挂载为 ext4,有约64GB的内存。我们分配了26GB用于ES节点的堆内存,剩下的用于磁盘缓存。为何是26GB?因为 JVM 是在一个黑魔法之上构建的 。我们同时使用 Terraform 自动扩容组来提供实例,并使用 Puppet 完成一切安装配置。4.索引结构因为我们的数据和查询都是基于时间序列的,所以使用了 time-based indexing ,类似于ELK (elasticsearch, logstash, kibana) stack。同时也让不同类型的数据保存在不同的索引库中,以便诸如社论文档和社交文档类数据最终位于不同的每日索引库中。这样可以在需要的时候只丢弃社交索引,并增加一些查询优化。每个日索引运行在两个分片中的一个。该项设置产生了大量的分片(接近40k)。有了这么多的分片和节点,集群操作有时变得更特殊。比如,删除索引似乎成为集群master的能力瓶颈,它需要把集群状态信息推送给所有节点。我们的集群状态数据约100 MB,但通过TCP压缩可减少到3 MB(可以通过 curl localhost:9200/_cluster/state/_all 查看你自己集群的状态数据)。Master 节点仍然需要在每次变更时推送1.3 GB数据(430 节点 x 3 MB 状态大小)。除了这1.3 GB数据外,还有约860 MB必须在可用区(比如 最基本的通过公共互联网)之间传输。这会比较耗时,尤其是在删除数百个索引时。我们希望新版本的 Elasticsearch 能优化这一点,首先从 ES 2.0支持仅发送集群状态的差分数据 这一特性开始。5.性能如前所述,我们的ES集群为了满足客户的检索需求,需要处理一些非常复杂的查询。为应对查询负载,过去几年我们在性能方面做了大量的工作。我们必须尝试公平分享ES集群的性能测试,从下列引文就可以看出。不幸的是,当集群宕机的时候,不到三分之一的查询能成功完成。我们相信测试本身导致了集群宕机。 —— 摘录自使用真实查询在新ES集群平台上的第一次性能测试为了控制查询执行过程,我们开发了一个插件,实现了一系列自定义查询类型。通过使用这些查询类型来提供Elasticsearch官方版本不支持的功能和性能优化。比如,我们实现了 phrases 中的 wildcard 查询,支持在 SpanNear 查询中执行;另一个优化是支持“*”代替 match-all-query ;还有其他一系列特性。Elasticsearch 和 Lucene 的性能高度依赖于具体的查询和数据,没有银弹。即便如此,仍可给出一些从基础到进阶的参考:限制你的检索范围,仅涉及相关数据。比如,对于每日索引库,只按相关日期范围检索。对于检索范围中间的索引,避免使用范围查询/过滤器。使用wildcards时忽略前缀wildcards - 除非你能对term建立倒排索引。双端wildcards难以优化。关注资源消耗的相关迹象 数据节点的CPU占用持续飙高吗?IQ等待走高吗?看看GC统计。这些可以从profilers工具或者通过 JMX 代理获得。如果 ParNewGC 消耗了超过15%的时间,去检查下内存日志。如果有任何的 SerialGC 停顿,你可能真的遇到问题了。不太了解这些内容?没关系,这个系列博文很好地介绍了JVM性能 。记住,ES和G1垃圾回收器一起并非最佳 。如果遇到垃圾回收问题,请不要尝试调整GC设置。这一点经常发生,因为默认设置已经很合理了。相反,应该聚焦在减少内存分配上。具体怎么做?参考下文。如果遇到内存问题,但没有时间解决,可考虑查询Azul Zing。这是一个很贵的产品,但仅仅使用它们的JVM就可以提升2倍的吞吐量。不过最终我们并没有使用它,因为我们无法证明物有所值。考虑使用缓存,包括 Elasticsearch 外缓存和 Lucene 级别的缓存。在 Elasticsearch 1.X 中可以通过使用 filter 来控制缓存。之后的版本中看起来更难一些,但貌似可以实现自己用于缓存的查询类型。我们在未来升级到2.X的时候可能会做类似的工作。查看是否有热点数据(比如某个节点承担了所有的负载)。可以尝试均衡负载,使用分片分配过滤策略 shard allocation filtering ,或者尝试通过集群重新路由 cluster rerouting 来自行迁移分片。我们已经使用线性优化自动重新路由,但使用简单的自动化策略也大有帮助。搭建测试环境(我更喜欢笔记本)可从线上环境加载一部分代表性的数据(建议至少有一个分片)。使用线上的查询回放加压(较难)。使用本地设置来测试请求的资源消耗。综合以上各点,在 Elasticsearch 进程上启用一个 profiler。这是本列表中最重要的一条。我们同时通过Java Mission Control 和 VisualVM 使用飞行记录器。在性能问题上尝试投机(包括付费顾问/技术支持)的人是在浪费他们(以及你自己)的时间。排查下 JVM 哪部分消耗了时间和内存,然后探索下 Elasticsearch/Lucene 源代码,检查是哪部分代码在执行或者分配内存。一旦搞清楚是请求的哪一部分导致了响应变慢,你就可以通过尝试修改请求来优化(比如,修改term聚合的执行提示 ,或者切换查询类型)。修改查询类型或者查询顺序,可以有较大影响。如果不凑效,还可以尝试优化 ES/Lucene 代码。这看起来太夸张,却可以为我们降低3到4倍的CPU消耗和4到8倍的内存使用。某些修改很细微(比如 indices query ),但其他人可能要求我们完全重写查询执行。最终的代码严重依赖于我们的查询模式,所以可能适合也可能不适合他人使用。因此目前为止我们并没有开源这部分代码。不过这可能是下一篇博文的好素材。图表说明:响应时间。有/没有 重写 Lucene 查询执行。同时也表明不再有节点每天多次发生内存不足。顺便说明下,因为我知道会面临一个问题:从上一次性能测试我们知道通过升级到 ES 2.X 能小幅提升性能,但是并不能改变什么。话虽如此,但如果你已经从 ES 1.X 集群迁移到了 ES 2.X,我们很乐意听取关于你如何完成迁移的实践经验。如果读到了这里,说明你对 Elasticsearch 是真爱啊(或者至少你是真的需要它)。我们很乐意学习你的经验,以及任何可以分享的内容。欢迎在评论区分享你的反馈和问题。英文原文链接:http://underthehood.meltwater…更多内容敬请关注vivo互联网技术微信公众号。注:转载文章请先与微信号:labs2020 联系。 ...

March 28, 2019 · 1 min · jiezi

数据科学家为什要用Git?怎么用?

摘要:也许你在别的地方听说过Git。也许有人告诉过你,Git只适合软件开发人员。如果你是数据科学家,那么Git其实对你很重要。本文作者希望能够通过经验分享让你了解Git的重要性,以及如何在你的数据科学工作中使用它。什么是Git?Git是一个分布式版本控制系统,用于在软件开发期间跟踪源代码的更改。看看维基百科给出的这个定义,好像Git专门是为软件开发人员而设计的。实际上,Git是当今世界上使用最广泛的现代版本控制系统,它是以分布式的协作方式为项目(开源或商业)做出了伟大的贡献。除了分布式版本控制系统之外,Git的还考虑了性能、安全性和灵活性。现在你已经了解了Git是什么,但是你脑海中的问题可能是,“如果我是做数据科学项目的人,它与我的工作有什么关系?”以前我也一样不能理解Git的重要性,直到我开始在现实工作环境中,我才发现它时如此重要!为什么是Git?我们来谈谈为什么?一年前,我决定学习Git。我在Github上分享并发布了我的代码,这是我在CERN的论文项目。虽然很难理解Git中常用的术语(git-add、commit、push、pull等),但我知道这在数据科学领域很重要,这使我的数据科学工作比以往任何时候都更加充实。所以我保持学习状态,并坚持“committing”。当我加入我目前的公司时,我在Git方面的经验就派上了用场,因为Git是跨不同团队进行代码开发和协作的主要方式。更重要的是,当你的组织遵循敏捷软件开发框架时,Git尤其有用,在该框架中,Git的分布式版本控制使整个开发工作流更加高效、快速且易于适应变化。那么什么是版本控制呢?版本控制是一个系统记录一个文件或一组文件随时间的变化,以便你以后可以调用特定的版本。比如说,你是一个数据科学家,与一个团队合作,在这个团队中你和另一个数据科学家在构建机器学习模型的时候,对同一个特征进行工作。如果你对该特征做了一些更改并上传到远程存储库,并且这些更改与主分支合并,那么你的项目现在变成了1.1版本。另一位数据科学家也对版本1.1的相同功能进行了一些更改,新的更改现在与主分支合并。模型就变成1.2版本。在任何时候,如果你的团队发现版本1.2在发布期间有一些错误,他们随时可以调用以前的版本1.1,这就是版本控制的美妙之处。作为数据科学家如何使用Git?我们已经讨论过什么是Git及其重要性。现在的问题归结为:作为数据科学家如何使用Git?作为数据科学家,你不需要成为一个Git领域的专家。关键是要理解Git技术的工作流程以及如何在日常工作中使用Git。准确地说,我在这里使用的是Git Feature Branch Workflow,它通常被开源和商业项目使用。如果你想更多地了解这里使用的术语,点击这里进行了解。Git Feature Branch WorkflowFeature Branch Workflow像一个中央存储库,master分支代表正式的项目历史记录。开发人员每次开始处理一个新特性时,都会创建一个新的分支,而不是直接提交到他们的本地主分支上。新的分支可以(也应该)推送到中央存储库。在这种情况下,可以在不修改master分支的情况下与其他开发人员共享一个该分支。在开始执行任何操作之前,请键入git remote -v以确保工作区指向要使用的远程存储库。1、从主分支开始,创建一个新分支git checkout mastergit pullgit checkout -b branch-name如果总是维护和更新主分支,则切换到本地主分支,并将最新的提交和代码提取到本地主分支。假设你希望创建一个本地分支,向代码中添加一个新功能,并稍后上传到远程存储库。一旦你将最新的代码更新到本地master分支,我们就创建并checkout出一个名为branch-name的新分支,所有的更改都将在此本地分支上进行。这意味着你本地的master分支不会受到任何影响。2、更新、添加、提交并将更改推送到远程存储库git statusgit add <your-files>git commit -m ‘your message’git push -u origin branch-name上面我们做了很多操作,让我们详细了解它。一旦发生了一些更新,就将新的操作add到本地分支,并且希望将该操作上传到远程分支,以便合并到远程主分支。git status将输出你对文件的所有更改(跟踪或未跟踪)。在使用git commit-m“your message”提交消息更改之前,你将使用git add <your files>决定要暂存哪些文件。在此阶段,你的更改仅显示在本地分支中。为了使你的更改显示在BitBucket上的远程分支中,你需要使用git push -u origin branch-name命令进行提交。此命令将该分支推送到中央存储库,并且-u表示将其添加为远程跟踪分支。在设置了跟踪分支之后,可以在没有任何参数的情况下调用git push,以自动将新的功能分支推送到BitBucket上的中央存储库。3、创建pull请求现在你已经成功地添加了一个新功能并推送到远程分支。你为自己的贡献感到骄傲,你希望在将远程分支与远程主分支合并之前得到团队成员的反馈。在该分支合并到主分支之前,让其他团队成员有机会对其进行审查。你可以在BitBucket上创建pull请求。现在,你的团队成员已经查看了你的代码,并决定在代码可以合并到主代码库-master分支之前,需要你进行一些其他更改。git statusgit add <your-files>git commit -m ‘your message’git push现在,你可以按照与之前相同的步骤进行更改、提交并最终将更新推送到中央存储库。一旦使用了git push,你的更新将自动显示在pull请求中。如果其他人已将目标更改为你所接触的同一代码,则会发生合并冲突,这在工作中很常见。你可以在这里看到如何解决合并冲突。一旦一切顺利完成,这些功能将会合并到master分支中。当我第一次开始学习Git时,我感到非常沮丧,因为我仍然没有真正理解工作流。这也是写这篇文章的主要原因之一,它真正分解并在更高层次的理解上向你解释工作流程。因为我相信,对工作流程中发生的事情有一个清晰的了解将使学习过程更加有效。本文作者:【方向】阅读原文本文为云栖社区原创内容,未经允许不得转载。

March 25, 2019 · 1 min · jiezi

阿里巴巴的微服务开源之路

侠之大者,为国为民。在金庸小说中,郭靖和黄蓉是“侠之大者,为国为民”的典范,他们以布衣之身帮助宋军守护襄阳十余年。技术的世界里,并无大小之分。但当一群程序员由服务公司内部转变为社会的程序员,将技术以开源的方式与社区的开发者一同协作、改进和使用时,他们便被赋予了更大的责任和期待。阿里云智能中间件的程序员们正和社区的开发者们一起,用键盘敲下国内微服务开源项目的过去和未来。国内首个非 Hadoop 生态体系的 Apache 社区顶级项目2016年的那届双11,RocketMQ 团队首次将低延迟存储解决方案应用于双11的支撑,经受住了流量的大考,整个大促期间,99.996%的延迟落在了10ms以内,完成了保障交易稳定的既定目标。对于读写比例几乎均衡的分布式消息引擎来说,这一技术上的突破,即便是放在全球范围内,也绝对是值得称赞的。另一边,在历时3个月的开源重塑后,RocketMQ 团队启动了向 Apache 软件基金会的捐赠之路,但迈出这一步并不容易。“当时国内的开源氛围还没有现在那么活跃,开源之后,很多设计文档、代码质量,以及社区建设还不够理想。我们一直期待,国内的开源项目和开源社区也可以在世界的开源舞台上发挥让人瞩目的价值,希望更多“中国智造”的开源项目成为世界级的开源项目。”阿里云智能高级技术专家冯嘉回忆道。经过近一年的努力,在2017年9月25日,Apache软件基金会官方宣布,阿里巴巴捐赠给 Apache 社区的开源项目 RocketMQ 从 Apache社区正式毕业,成为 Apache 顶级项目(TLP),这是国内首个非 Hadoop 生态体系的 Apache 社区顶级项目。值得一提的是,根据项目毕业前的统计,RocketMQ 有百分八十的新特性与生态集成来自于社区的贡献。毕业一年多后,RocketMQ 已经覆盖互联网金融等领域60%以上的消息场景,并被应用到金融、电力、物流、游戏、电子商务、共享出行等十几个行业。然而,随着云计算、大数据、人工智能等技术在全球范围的深入推进,催生出了如IoT、区块链、AI、边缘计算等新的应用场景,架构上如何进一步演进以更好的适应新的场景,服务好下一个十年,这是即将到来的 RocketMQ 5.0 要解决的问题。消息领域的里程碑事件RocketMQ 从 Apache 社区正式毕业的同时,消息领域出现了另一件里程碑事件,分布式消息领域的国际标准 OpenMessaging 开源项目正式入驻Linux基金会,这是国内首个在全球范围发起的分布式计算领域的国际标准。消息通信已经成为现代数据驱动架构的关键环节,但在全球范围内,消息领域仍然存在两大问题:一是,缺乏供应商中立的行业标准,导致各种消息中间件的高复杂性和不兼容性,相应地造成了公司的产品低效、混乱和供应商锁定等问题。二是,目前已有的方案框架并不能很好地适配云架构,即非云原生架构,因此无法有效地对大数据、流计算和物联网等新兴业务需求提供技术支持。这也是 RocketMQ 开源过程中,开发者和合作伙伴经常会提到的问题:“在消息领域,市场上出现了各类不同的开源解决方案,这导致了用户更高的接入和维护成本,为了确保各个消息引擎间能正常通信,还要投入大量的精力去做兼容。”这时候,建立一套供应商中立,和语言无关的消息领域的事实标准,成为各社区成员共同的诉求。此后,阿里巴巴发起 OpenMessaging 项目,并邀请了雅虎、滴滴出行、Streamlio 共同参与,一年后,参与OpenMessaging 开源标准社区的企业达20家之多,包括阿里巴巴、Datapipeline、滴滴出行、浩鲸科技、京东商城、科大讯飞、青云QingCloud、Streamlio、VIPKID、微众银行、Yahoo、中国移动苏州研发中心等(按首字母排序),此外,还获得了 Apache RocketMQ、Apache Pulsar 等顶级消息开源厂商的支持。相比于开源一个分布式消息项目,一套开源标准能被各家厂商所接受,对整个国内开源领域而言,是更具有里程碑意义的事件。从微服务框架到微服务生态Dubbo 是阿里巴巴于2012年开源的分布式服务治理框架,是国内影响力最大、使用最广泛的开源服务框架之一。在2016年、2017、2018年开源中国发起的最受欢迎的中国开源软件评选中,连续三年进入 Top10 名单。2019年2月 Dubbo 发布了2.7.0,这一版本将用于 Apache 基金会的正式毕业。(已进入 Near Graduation 阶段)从 Apache 孵化器毕业,除了有个名誉,对项目之后的维护、发展有什么影响?“从孵化器毕业是一种荣誉,但这并不是结束,而是另一种开始。这有点像求学,毕业并不意味着学习上的中断,而是发挥更大社会价值的开始。毕业也更像是一个成人礼,意味着Dubbo 团队已经符合Apache对一个成熟开源项目的要求,并开始具备独立发展的能力。”阿里云智能高级技术专家北纬在接受媒体采访时回答道。截至目前,Dubbo 已收获 2.5w+ star,在 GitHub 所有 Java 项目中排名前十,并有越来也多的企业用户选择 Dubbo 作为自己的微服务治理框架。但是,随着微服务化的逐渐深入,Dubbo 提供的能力逐渐无法满足微服务各个方面的需求。阿里云智能技术专家望陶在一次直播中分享道:“Dubbo 是一个微服务框架,帮助开发者快速构建高性能的微服务应用。但在 API Gateway,熔断限流,分布式监控,分布式事务等方面,缺乏一套比较完整的围绕 Dubbo 的解决方案,基本上是各个公司自研,或者需要调研外面开源的各种框架进行调研选型,花费了比较大的时间和精力在这上面,却无法形成一套体系化的方案。”因此,我们做了进一步的演进,即从微服务框架演进到微服务生态。通过和成熟的开源方案做集成,形成一个完整的微服务生态,组成 Dubbo Ecosystem,开发者无需为现有的系统做出过多的修改,就能快速开发微服务应用。Dubbo Ecosystem 的概念得以提出,离不开 2018 年夏天开源的两大微服务组件。技术人的仲夏之夜2018年夏天,国内开源领域,迎来了两位新成员。作为微服务和云原生生态下的两款重要开源组件,Nacos 主打云原生应用中的动态服务发现、配置和服务管理,Sentinel 则是聚焦在限流和降级两个方面。Nacos 和 Sentinel 均是在阿里近10年的核心业务场景下沉淀所产生的,他们的开源是对微服务和元原生领域开源技术方案的有效补充,同时也非常强调融入开源生态,除了兼容 Dubbo,也支持 SpringCloud 和 Kubenetes 等生态,以增强自身的生命力。“阿里巴巴早在 2007 年进行从 IOE 集中式应用架构升级为互联网分布式服务化架构的时候,就意识到在分布式环境中,诸如分布式服务治理,数据源容灾切换、异地多活、预案和限流规则等场景下的配置变更难题,因为在一个大型的分布式系统中,你没有办法把整个分布式系统停下来,去做一个软件、硬件或者系统的升级。”阿里云智能高级技术专家坤宇在 QCon 的现场分享道。相比其他服务配置中心开源方案,Nacos 的起步虽然晚了点,但除了配置中心,他还提供了动态服务发现、服务共享与管理的功能,在大规模场景下具备更优秀的性能,在易用性上更便捷,分布式部署上更灵活。Nacos 支持多种启动模式,用户可以根据业务场景自由选择,将各个功能模块,如注册中心和配置中心,分开部署或者合并部署,从而能够完整支持小型创业公司成长到大型企业,微服务全生命周期的演进。截止到目前,已经有40多家企业将 Nacos 部署到生产环境中,例如 虎牙直播 就是最早一批将 Nacos 大规模引入到生产环境的典型用户。“虎牙关注 Nacos 是从v0.2 开始的,我们也参与了社区的建设,可以说是比较早期的企业用户。引入Nacos前,我们也对比了Spring Cloud Config Server、ZooKeeper 和 ectd ,总体评估下来,基于我们微服务体系现状以及业务场景,决定使用 Nacos 作为服务化改造中服务注册和服务发现的方案。使用过程中,我们发现,随着社区版本的不断更新和虎牙的深入实践,Nacos 的优势远比调研过程中发现的多。”虎牙基础保障部中间件团队负责人张波在一次开发者活动上分享道。巧的是,一边是 Nacos宣布开源,并被列入 CNCF 云原生全景图,另一边是 Spring Cloud 生态下的服务注册和发现组件 Netflix Eureka 宣布停止开源投入,勇敢者的游戏充满了变数,但在 Nacos 团队看来,这场游戏自己可以走到最后,因为我们并不是一个人在战斗,Nacos 只是阿里众多开源项目中的一员,随后还会有更多的开源项目反哺给社区,形成生态,例如轻量级限流降级组件 Sentinel。2018年7月29日,AliwareOpen Source•深圳站现场,只能容纳400人的场地,来了700多位开发者。阿里云智能高级技术专家子矜在现场宣布了轻量级限流降级组件 Sentinel 的开源。Sentinel 经历了10年双11的考验,覆盖了阿里的所有核心场景,也因此积累了大量的流量归整场景以及生产实践。Sentinel 的出现,离不开阿里历届高可用架构团队的共同努力。“在双11备战中,容量规划是最重要也是最具挑战的环节之一。从第一年开始,双11的0点时刻就代表了我们的历史最高业务访问量,它通常是日常流量的几十倍甚至上百倍。因此,如何让一个技术和业务持续复杂的分布式站点去更平稳支撑好这突如其来的流量冲击,是我们这10年来一直在解的题。”阿里云智能高可用架构团队资深技术专家游骥在一次双11备战结束后分享道。这10年,容量规划经历了人工估算、线下压测、线上压测、全链路压测、全链路压测和隔离环境、弹性伸缩相结合的5个阶段。2013年双11结束后,全链路压测的诞生解决了容量的确定性问题。作为一项划时代的技术,全链路压测的实现,对整个集团而言,都是一件里程碑事件。![2014年,高可用架构团队获得集团 CTO 大奖](https://upload-images.jianshu…随后,基于全链路压测为核心,打造了一系列容量规划相关的配套生态,提升能力的同时,降低了整个环节的成本、提升效率。随着容量规划技术的不断演进,2018年起,高可用架构团队希望可以把这些年在生成环境下的实践,贡献给社区,之后便有了 Sentinel 的开源。Sentinel 开源后仅两个月,便被列入云原生全景图谱,位于编排和管理模块象限中,同时被列入云原生全景图谱的还有提供应用架构自动探测、故障注入式高可用能力演练和一键应用限流降级等功能的应用高可用服务 AHAS。近期,Sentinel 贡献的spring-cloud-circuitbreaker-sentinel模块正式被社区合并至Spring Cloud Circuit Breaker,由此,Sentinel 也加入了 Spring Cloud Circuit Breaker 俱乐部,成为 Spring Cloud 官方的主流推荐选择之一。Spring Cloud 官方推荐的微服务方案不止 Sentinel 一个,还有 Spring Cloud Alibaba.2018年,中国的 Java 圈发生了一件大事。Spring Cloud 联合创始人 Spencer Gibb 在 Spring 官网的博客页面宣布:阿里巴巴开源 Spring Cloud Alibaba,并发布了首个预览版本。随后,Spring Cloud 官方 Twitter 也发布了此消息。可能是受到 Spring Cloud Netflix 减少开源投入的影响,Spring Cloud Alibaba 开源后的热度超出了阿里巴巴高级技术专家姬望的预期。在接受开源中国采访的过程中,姬望认为“Spring Cloud Alibaba 是中国 Java 开发者的福音,弥补了 Spring Cloud 原生实现在大规模集群场景上的局限性。Spring Cloud 规范的实现目前有很多,比如 Netflix 有自己的一整套体系,Consul 支持服务注册和配置管理,ZooKeeper 支持服务注册等。但每套实现或多或少都有各自的优缺点,或许大多数 Spring Cloud 用户很难体会到 Netflix OSS 等实现的局限性,无论是服务发现、分布式配置,还是服务调用和熔断都不太适合大规模集群场景,比如我们内部也遇到 Eureka 性能问题。因此,我们将自身的超大规模集群经验与强大的 SpringCloud 生态整合,实现强强联合,希望能对业界会产生一些积极的化学变化。”夏天过后,开源的热度仍在延续效率的好处在于,我们可以把自己的注意力和时间聚焦在更需要创造力的事情上,做更有成就感的事情。对于工作在工程领域的开发者们而言,他们的效率意识更强。2018年9月,阿里将内部广泛使用的 Java 线上诊断工具进行开源,取名 Arthas (阿尔萨斯)。也许是击中了开发者线上排查问题的痛点,Arthas 在距离开源后的第一个 Release 版发布仅 147 天,就获得了超过 1w 的 star 数,并有40多位 Contributors 参与开源贡献。从中,我们不仅看到 Arthas 在开发者群体中的受欢迎程度,也发现越来越多的国内开发者开始擅于使用开源技术加速业务发展,更是不禁畅想起将来会有更多国内的优质开源项目获得全球开发者的关注和喜爱。技术领域,一切 里程碑 的达成,都源于一份技术情怀。阿里云智能技术专家断岭回忆到:“Arthas 在阿里巴巴内部起源于2015年,当时微服务方兴未艾,我们团队一方面专注 Spring Boot 的落地,提高开发效率。另外一方面,希望可以提高技术团队线上排查问题的能力和效率。当时,我们经过选型讨论,选择基于 Greys (Greys 是阿里巴巴杜琨@oldmanpushcart 开发的),一款 Java 开源在线问题诊断工具来开发,以提供更好的应用诊断体验。”我们在用户体验上做了大量的改进:彩色UI、Web Console 和内网一键诊断等。慢慢的,Arthas 成为阿里巴巴很多技术同事线上诊断问题的必备工具。尽管 Arthas 在阿里内部广受好评,但只是一个自用的工具。取之开源,用之开源,因此我们在2018年9月28日,正式开源了 Arthas,希望可以帮助 Java 开发人员提升诊断效率。随着越来越多的开发者开始使用 Arthas,众多开发者效率工具将 Arthas 内置到自己的产品中,丰富了 Arthas 的接入和打开方式,例如 IDE 插件 Cloud Toolkit。时间来到2019年。阿里云智能高级开发工程师煊檍在内网分享到:分布式事式问题一直是应用开发过程中的技术痒点。不敢说是痛点,因为长久以来,大家普遍对分布式事务问题的应对策略还是:能不用就不用,尽量绕开。但在微服务架构普遍落地的今天,分布式事务问题越来越绕不开,解决方案不是没有,但要么性能差,要么侵入性高,不容易落地。总之,是有点“不爽”。而这种“不爽”集中反映在了分布式事务开源中间件 Fescar 上。当阿里云智能高级开发工程师清铭在2019年1月 RocketMQ Meetup 上宣布分布式事务中间件 Fescar 正式开源后的一周内,Fescar 便收获了3000+ star,社区讨论的 issue 达58个。随后,Fescar 项目组整理并回答了开发者们集中关心的13个问题,例如 Fescar 的诞生背景、适用场景,和其他开源分布式事务方案之间的差别等。阿里巴巴中间件团队于2014年发布 TXC(Taobao Transaction Constructor),开始为集团内应用提供分布式事务服务。2016年,TXC 经过产品化改造,以 GTS(Global TransactionService)的身份上线阿里云,成为当时业界唯一一款云上分布式事务产品,以阿里云公有云和专有云解决方案的形式,交付给众多外部客户,并得到了客户的一致认可。2019 年,基于 TXC 和 GTS 的技术积累,中间件团队发起了开源项目 Fescar(Fast & EaSy Commit And Rollback, FESCAR),和社区一起共建分布式事务解决方案。TXC/GTS/Fescar 一脉相承,为解决微服务架构下的分布式事务问题交出了一份与众不同的答卷。而Fescar 的愿景是让分布式事务的使用像本地事务的使用一样简单和高效。最终的目标是希望可以让 Fescar 适用于所有的分布式事务场景。阿里巴巴的开源之路仍在延续。恰逢其时,阿里云峰会·北京的开发者专场现场,阿里云智能资深技术专家李三红宣布,阿里开源 Open JDK 长期支持版本 Alibaba Dragonwell,作为 JCP 最高执行委员会唯一的中国企业,将更主动的参与到 Java 生态的维护工作中。Dragonwell 意为龙井,象征着中国的茶文化。本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 25, 2019 · 2 min · jiezi

手把手教你实现一个基于Redis的分布式锁

简介分布式锁在分布式系统中非常常见,比如对公共资源进行操作,如卖车票,同一时刻只能有一个节点将某个特定座位的票卖出去;如避免缓存失效带来的大量请求访问数据库的问题设计这非常像一道面试题:如何实现一个分布式锁?在简介中,基本上已经对这个分布式工具提出了一些需求,你可以不着急看下面的答案,自己思考一下分布式锁应该如何实现?首先我们需要一个简单的答题套路:需求分析、系统设计、实现方式、缺点不足需求分析1.能够在高并发的分布式的系统中应用2.需要实现锁的基本特性:一旦某个锁被分配出去,那么其他的节点无法再进入这个锁所管辖范围内的资源;失效机制避免无限时长的锁与死锁3.进一步实现锁的高级特性和JUC并发工具类似功能更好:可重入、阻塞与非阻塞、公平与非公平、JUC的并发工具(Semaphore,CountDownLatch, CyclicBarrier)系统设计转换成设计是如下几个要求:1.对加锁、解锁的过程需要是高性能、原子性的 2.需要在某个分布式节点都能访问到的公共平台上进行锁状态的操作所以,我们分析出系统的构成应该要有锁状态存储模块、连接存储模块的连接池模块、锁内部逻辑模块锁状态存储模块分布式锁的存储有三种常见实现,因为能满足实现锁的这些条件:高性能加锁解锁、操作的原子性、是分布式系统中不同节点都可以访问的公共平台:1.数据库(利用主键唯一规则、MySQL行锁)2.基于Redis的NX、EX参数3.Zookeeper临时有序节点由于锁常常是在高并发的情况下才会使用到的分布式控制工具,所以使用数据库实现会对数据库造成一定的压力,连接池爆满问题,所以不推荐数据库实现;我们还需要维护Zookeeper集群,实现起来还是比较复杂的。如果不是原有系统就依赖Zookeeper,同时压力不大的情况下。一般不使用Zookeeper实现分布式锁。所以缓存实现分布式锁还是比较常见的,因为缓存比较轻量、缓存的响应快、吞吐高、还有自动失效的机制保证锁一定能释放。连接池模块可使用JedisPool实现,如果后期性能不佳,可考虑参照HikariCP自己实现锁内部逻辑模块基本功能:加锁、解锁、超时释放高级功能:可重入、阻塞与非阻塞、公平与非公平、JUC并发工具功能实现方式存储模块使用Redis,连接池模块暂时使用JedisPool,锁的内部逻辑将从基本功能开始,逐步实现高级功能,下面就是各种功能实现的具体思路与代码了。加锁、超时释放NX是Redis提供的一个原子操作,如果指定key存在,那么NX失败,如果不存在会进行set操作并返回成功。我们可以利用这个来实现一个分布式的锁,主要思路就是,set成功表示获取锁,set失败表示获取失败,失败后需要重试。再加上EX参数可以让该key在超时之后自动删除。下面是一个阻塞锁的加锁操作,将循环去掉并返回执行结果就能写出非阻塞锁(就不粘出来了): public void lock(String key, String request, int timeout) throws InterruptedException { Jedis jedis = jedisPool.getResource(); while (timeout >= 0) { String result = jedis.set(LOCK_PREFIX + key, request, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, DEFAULT_EXPIRE_TIME); if (LOCK_MSG.equals(result)) { jedis.close(); return; } Thread.sleep(DEFAULT_SLEEP_TIME); timeout -= DEFAULT_SLEEP_TIME; }}但超时时间这个参数会引发一个问题,如果超过超时时间但是业务还没执行完会导致并发问题,其他进程就会执行业务代码,至于如何改进,下文会讲到。解锁最常见的解锁代码就是直接使用jedis.del()方法删除锁,这种不先判断锁的拥有者而直接解锁的方式,会导致任何客户端都可以随时进行解锁,即使这把锁不是它的。比如可能存在这样的情况:客户端A加锁,一段时间之后客户端A解锁,在执行jedis.del()之前,锁突然过期了,此时客户端B尝试加锁成功,然后客户端A再执行del()方法,则将客户端B的锁给解除了。所以我们需要一个具有原子性的方法来解锁,并且要同时判断这把锁是不是自己的。由于Lua脚本在Redis中执行是原子性的,所以可以写成下面这样:public boolean unlock(String key, String value) { Jedis jedis = jedisPool.getResource(); String script = “if redis.call(‘get’, KEYS[1]) == ARGV[1] then return redis.call(‘del’, KEYS[1]) else return 0 end”; Object result = jedis.eval(script, Collections.singletonList(LOCK_PREFIX + key), Collections.singletonList(value)); jedis.close(); return UNLOCK_MSG.equals(result);}来用测试梭一把此时我们可以来写个测试来试试有没有达到我们想要的效果,上面的代码都写在src/main/java下的RedisLock里,下面的测试代码需要写在src/test/java里,因为单元测试只是测试代码的逻辑,无法测试真实连接Redis之后的表现,也没办法体验到被锁住带来的紧张又刺激的快感,所以本项目中主要以集成测试为主,如果你想试试带Mock的单元测试,可以看看这篇文章。那么集成测试会需要依赖一个Redis实例,为了避免你在本地去装个Redis来跑测试,我用到了一个嵌入式的Redis工具以及如下代码来帮我们New一个Redis实例,尽情去连接吧 ~ 代码可参看EmbeddedRedis类。另外,集成测试使用到了Spring,是不是倍感亲切?相当于也提供了一个集成Spring的例子。@Configurationpublic class EmbeddedRedis implements ApplicationRunner { private static RedisServer redisServer; @PreDestroy public void stopRedis() { redisServer.stop(); } @Override public void run(ApplicationArguments applicationArguments) { redisServer = RedisServer.builder().setting(“bind 127.0.0.1”).setting(“requirepass test”).build(); redisServer.start(); }}对于需要考虑并发的代码下的测试是比较难且比较难以达到检测代码质量的目的的,因为测试用例会用到多线程的环境,不一定能百分百通过且难以重现,但本项目的分布式锁是一个比较简单的并发场景,所以我会尽可能保证测试是有意义的。我第一个测试用例是想测试一下锁的互斥能力,能否在A拿到锁之后,B就无法立即拿到锁:@Testpublic void shouldWaitWhenOneUsingLockAndTheOtherOneWantToUse() throws InterruptedException { Thread t = new Thread(() -> { try { redisLock.lock(lock1Key, UUID.randomUUID().toString()); } catch (InterruptedException e) { e.printStackTrace(); } }); t.start(); t.join(); long startTime = System.currentTimeMillis(); redisLock.lock(lock1Key, UUID.randomUUID().toString(), 3000); assertThat(System.currentTimeMillis() - startTime).isBetween(2500L, 3500L);}但这仅仅测试了加锁操作时候的互斥性,但是没有测试解锁是否会成功以及解锁之后原来等待锁的进程会继续进行,所以你可以参看一下testLockAndUnlock方法是如何测试的。不要觉得写测试很简单,想清楚测试的各种情况,设计测试情景并实现并不容易。然而以后写的测试不会单独拿出来讲,毕竟本文想关注的还是分布式锁的实现嘛。超时释放导致的并发问题问题:如果A拿到锁之后设置了超时时长,但是业务执行的时长超过了超时时长,导致A还在执行业务但是锁已经被释放,此时其他进程就会拿到锁从而执行相同的业务,此时因为并发导致分布式锁失去了意义。如果可以通过在key快要过期的时候判断下任务有没有执行完毕,如果还没有那就自动延长过期时间,那么确实可以解决并发的问题,但是超时时长也就失去了意义。所以个人认为最好的解决方式是在锁超时的时候通知服务器去停掉超时任务,但是结合上Redis的消息通知机制不免有些过重了所以这个问题上,分布式锁的Redis实现并不靠谱。本人在Redisson中也没有找到解决方式。或者使用Zookepper将超时消息发送给客户端去执行超时情况下的业务逻辑。单点故障导致的并发问题建立主从复制架构,但是还是会由于主节点挂掉导致某些数据还没同步就已经丢失,所以推荐多主架构,有N个独立的master服务器,客户端会向所有的服务器发送获取锁的操作。可以继续优化的地方实现类似JUC中的Semaphore、CountDownLatch、公平锁非公平锁、读写锁功能,可参考Redisson的实现参考RedLock方案,提供多主配置方式与加锁解锁实现使用订阅解锁消息与Semaphore代替Thread.sleep()避免时间浪费,可参考Redisson中RedissonLock的lockInterruptibly方法 ...

March 19, 2019 · 1 min · jiezi

阿里巴巴微服务开源项目盘点(持续更新)

大前端、微服务、数据库、更多精彩,尽在开发者分会场【Apache Dubbo】Apache Dubbo 是一款高性能、轻量级的开源Java RPC框架,是国内影响力最大、使用最广泛的开源服务框架之一,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。在2016年、2017、2018年开源中国发起的最受欢迎的中国开源软件评选中,连续三年进入Top10名单。2019年2月Dubbo发布了2.7.0,这一版本将用于Apache 基金会的正式毕业。项目地址:https://github.com/apache/incubator-dubbo最新动态:探秘 Dubbo 的度量统计基础设施 - Dubbo MetricsDubbo 生态添新兵,Dubbo Admin 发布 v0.1谁正在用:https://github.com/apache/incubator-dubbo/issues/1012媒体报道:Dubbo作者亲述:那些辉煌、沉寂与重生的故事【Apache RocketMQ】Apache RocketMQ 是一款分布式消息引擎,具备「低延迟」、「高性能」、「高可靠性」等特点,可满足兆级容量和可扩展性的需求。它是国内首个非 Hadoop 生态体系的Apache 社区顶级项目,根据项目毕业前的统计,RocketMQ有百分八十的新特性与生态集成来自于社区的贡献。项目地址:https://github.com/apache/rocketmq最新动态:Apache RocketMQ 发布 v4.4.0,新添权限控制和消息轨迹特性谁正在用:RocketMQ 在平安银行的实践和应用滴滴出行基于RocketMQ构建企业级消息队列服务的实践【OpenMessaging】OpenMessaging开源项目于2017年正式入驻Linux基金会,是国内首个在全球范围发起的分布式计算领域的国际标准。OpenMessaging开源标准社区的企业达10家之多,包括阿里巴巴、Datapipeline、滴滴出行、浩鲸科技、京东商城、青云QingCloud、Streamlio、微众银行、Yahoo、中国移动苏州研发中心(按首字母排序),此外,还获得了RocketMQ、RabbitMQ和Pulsar 3个顶级消息开源厂商的支持。项目地址:https://github.com/openmessaging/openmessaging-java首个由国内发起的分布式消息领域的国际标准OpenMessaging一周年回顾【Nacos】Nacos 是一个更易于帮助构建云原生应用的动态服务发现、配置和服务管理平台,提供「注册中心」、「配置中心」和「动态DNS服务」三大功能。项目地址:https://github.com/alibaba/nacos谁正在用:https://github.com/alibaba/nacos/issues/273虎牙直播在微服务改造方面的实践和总结最新动态:Nacos Committers 团队首亮相,发布 0.9.0 版本背后的故事最佳实践阿里巴巴基于 Nacos 实现环境隔离的实践如何打通CMDB,实现就近访问【Sentinel】Sentinel 承接阿里巴巴近10年双十一大促流量的核心场景,以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。其提供丰富的应用场景支持、完备的监控能力、易用的拓展点。项目地址:https://github.com/alibaba/sentinel最新动态:https://dwz.cn/faH9eKHH背后的故事媒体专访:限流熔断技术选型:从Hystrix到Sentinel最佳实践快速体验 Sentinel 集群限流功能,只需简单几步【Arthas】Arthas 是一款 Java 线上诊断工具,可有效解决开发过程中遇到的各类诊断难题。2018年9月,阿里将内部广泛使用的java线上诊断工具进行开源,取名arthas(阿尔萨斯)。也许是击中了开发者线上排查问题的痛点,Arthas在距离开源后的第一个Release 版发布仅 147 天,就获得了超过1w的star数,并有40多位contributors参与开源贡献。项目地址:https://github.com/alibaba/arthas最新动态:新的开始 | Arthas GitHub Star 破万后的回顾和展望【Spring Cloud Alibaba】Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案,提供包括「服务限流降级」、「服务注册与发现」、「分布式配置管理」和「阿里云对象存储」等主要功能。项目地址:https://github.com/spring-cloud-incubator/spring-cloud-alibaba最新动态:Spring Cloud Alibaba发布第二个版本,Spring 发来贺电媒体报道:Spring Cloud Alibaba,中国 Javaer 的福音,为微服务续上 18 年【Fescar】Fescar 是阿里巴巴2019年1月开源的一套分布式事务中间件,Fescar 的愿景是让分布式事务的使用像现在本地事务的使用一样简单、高效,最终的目标是希望可以适用于所有的分布式事务场景。项目地址:https://github.com/alibaba/Fescar• 最佳实践集成源码深度剖析:Fescar x Spring Cloud分布式事务中间件 Fescar—RM 模块源码解读*阿里热门开源项目↓↓↓↓↓海量资源点击领取更有kindle、技术图书抽奖活动,百分百中奖本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。

March 19, 2019 · 1 min · jiezi

揭秘:蚂蚁金服bPaaS究竟是什么?

摘要: 分布式金融核心套件,蚂蚁金服bPaaS究竟是什么东东?去年9月,蚂蚁金服在杭州云栖ATEC发布了分布式金融核心套件bPaaS( Business Platform As a Service ),对外开放自身沉淀的“产品合约”、“资产交换”、“资产核心”、“会计核算”、“计价” 等金融核心组件,而这款号称源自于蚂蚁金服十余年业务和技术积累的bPaaS,也被视为是2018年初蚂蚁金服决定将分布式金融核心能力对外输出后,蚂蚁金服推出的第一款重量级的产品。今年3月,蚂蚁金服与南京润和软件联合推出基于分布式金融核心套件bPaaS能力的“新一代分布式金融业务核心平台”。这也意味着蚂蚁金服科技开放进入新阶段,不仅有自主研发的技术输出,也推出了生态互集成模式的新型产品。那么,这款据称能够最快在3个月内复制蚂蚁金服核心技术能力的分布式核心金融套件bPaaS到底都有何神奇之处?它又能给金融行业带来哪些改变?对于蚂蚁金服又意味着什么?这些恐怕还都得从bPaaS的初衷说起。为什么会有bPaaS?蚂蚁金服高级技术专家李玄表示,bPaaS的初衷是为了加速金融行业客户的数字化转型,如果没有bPaaS,金融行业客户要从零开始摸索和开发自己的分布式业务系统,通常会是一个漫长的过程,因为其中要涉及很多分布式的关键技术,以及金融业务模型的抽象等数字化转型中的问题和痛点,非常的复杂和具有挑战性。而bPaaS则重新定义了金融业务领域模型,尽可能规避了分布式技术在核心业务中的落地复杂度,集成了分布式应用场景下的一系列支撑性能力,如全链路核对,业务监控信息标准,全链路压测等,最终形成一个打包方案,开放给金融行业的客户使用。更为重要的是,bPaaS中整合的是蚂蚁金服在十几年的金融业务实践中经过无数次的实际应用验证和检验的切实可行的技术和解决方案,说是“复制蚂蚁金服的核心技术能力”,其实并不夸张。简言之,不同金融机构存在着差异和特色,一套“通用”产品已经不能满足金融行业用户不同的业务需求。而bPaaS能够提供可复用、可运营的共享金融业务处理能力。在保持银行传统核心稳定的前提下,可以根据不同银行差异化的业务场景快速定制新业务场景,支撑银行业务快速发展,敏捷创新。bPaaS的本质说起bPaaS,实际上可以先从bPaaS在软件分层中所处的位置看起,bPaaS实际上是处于SaaS层和PaaS层之间的一个服务,它集成了资产、客户、产品、支付、账务等多个金融业务领域核心引擎组件,整合了金融业务核心领域服务能力,形成一个高度聚合的金融核心能力引擎,赋予了金融行业用户将业务能力引擎与分布式架构平台融于一体,向下能屏蔽分布式事务、底层数据库、中间件等分布式架构平台技术复杂性,向上能支撑银行客户运营和服务创新需求,标准化、可重用的金融核心领域服务能力。李玄表示,bPaaS本质上是一种为用户赋能的服务模式。其核心是将业务中公共的、通用的业务功能沉淀出来形成能力,避免功能的重复建设和维护,更合理地利用技术资源。实际上,bPaaS的精髓就在于,以非常强大的可编排、可组合、可配置、可扩展的技术服务能力,来支撑业务的快速敏捷和灵活多变。为什么要用bPaaS?在谈及为何要使用bPaaS时,李玄认为,金融行业数字化转型的敏捷诉求,是促使金融行业采用分布式金融核心套件的最主要驱动力,而bPaaS具备的三大特点,恰恰能够满足金融行业用户加速数字化转型的需求。首先,bPaaS可以为用户提供业务敏捷能力,所谓业务敏捷能力是指,bPaaS可以非常快速的支撑对业务的创新,它将底层的业务能力进行了抽象和组合编排,并且对于上层的产品是透明的,这使得用户的业务创新可以更加快速敏捷。其次,bPaaS整合了蚂蚁金服的大数据能力。众所周知,蚂蚁金服的众多业务都需要大数据进行赋能,也有众多的业务系统需要用到实时计算和离线计算等大数据技术,而蚂蚁金服将这套业务应用与大数据完美结合的技术预置到了bPaaS之中。此外,bPaaS屏蔽了整个分布式服务的复杂性。例如,蚂蚁金融云上有很多PaaS组件、中间件,单独使用并没有太高门槛,但如果想要把它们整合,去构建一个高效、敏捷、灵活的业务应用最佳实践的话,还是具有相当的难度的。而bPaaS则屏蔽了这样的复杂度,还携带了一些技术风险工具在其中,并内置了蚂蚁金服的各种规范标准,业务监控、技术监控的分析识别,基本上企业用户可以达成“拿来即用”,而如果没有bPaaS,用户可能需要走很多弯路。基于这些特点,金融行业用户通过bPaaS搭建新的新一代分布式金融核心系统,花费时间将可从过去的三年甚至更长时间缩短至3到6个月,并快速配齐弹性伸缩、敏捷开发、秒级容灾等云原生分布式能力,从而大大加速金融行业用户的数字化转型。bPaaS,金融科技开放承载者蚂蚁金服副总裁刘伟光曾如此阐述实行金融科技开放战略的初衷,“我们希望蚂蚁金服的技术开放能够和金融机构的顶层战略相结合,将我们的技术应用到客户最重要或者更创新的场景当中,让科技真正推动业务的腾飞,加速金融机构数字化转型的进程。”而作为承载蚂蚁金服金融科技开放战略的一款拳头产品,bPaaS的意义远非仅仅是一款产品那样简单,也不仅仅是蚂蚁金服金融科技开放战略的实际成果,它更大程度上展示的是蚂蚁金服要长期坚决执行金融科技开放战略的意志和决心,而此次与南京润和的合作也是蚂蚁金服将金融科技开放战略继续深入推进、共建金融行业生态的标志。“这次合作只是一个开始,我们将通过合作与更多生态伙伴一起,共同探索百花齐放、有竞争力的金融科技产品与服务。”刘伟光说。关于蚂蚁金服bPaaS:https://tech.antfin.com/products/DFAS?chInfo=zx一站式开发者服务,海量学习资源0元起!阿里热门开源项目、机器学习干货、开发者课程/工具、小微项目、移动研发等海量资源;更有开发者福利Kindle、技术图书幸运抽奖,100%中–》https://www.aliyun.com/acts/product-section-2019/developer?utm_content=g_1000047140本文作者:华蒙阅读原文本文为云栖社区原创内容,未经允许不得转载。

March 19, 2019 · 1 min · jiezi

蚂蚁金服开源 SOFAJRaft:生产级 Java Raft 算法库

什么是 SOFAJRaft?SOFAJRaft 是一个基于 Raft 一致性算法的生产级高性能 Java 实现,支持 MULTI-RAFT-GROUP,适用于高负载低延迟的场景。 使用 SOFAJRaft 你可以专注于自己的业务领域,由 SOFAJRaft 负责处理所有与 Raft 相关的技术难题,并且 SOFAJRaft 非常易于使用,你可以通过几个示例在很短的时间内掌握它。SOFAJRaft 是从百度的 braft 移植而来,做了一些优化和改进,感谢百度 braft 团队开源了如此优秀的 C++ Raft 实现。基础知识:分布式共识算法 (Consensus Algorithm)如何理解分布式共识?多个参与者某一件事一致 :一件事,一个结论已达成一致的结论,不可推翻有哪些分布式共识算法?Paxos:被认为是分布式共识算法的根本,其他都是其变种,但是 Paxos 论文中只给出了单个提案的过程,并没有给出复制状态机中需要的 multi-paxos 的相关细节的描述,实现 Paxos 具有很高的工程复杂度(如多点可写,允许日志空洞等)。Zab:被应用在 Zookeeper 中,业界使用广泛,但没有抽象成通用的 library。Raft:以容易理解著称,业界也涌现出很多 Raft 实现,比如大名鼎鼎的 etcd, braft, tikv 等。什么是 Raft?Raft 是一种更易于理解的分布式共识算法,核心协议本质上还是师承 Paxos 的精髓,不同的是依靠 Raft 模块化的拆分以及更加简化的设计,Raft 协议相对更容易实现。模块化的拆分主要体现在:Raft 把一致性协议划分为 Leader 选举、MemberShip 变更、日志复制、Snapshot 等几个几乎完全解耦的模块。更加简化的设计则体现在:Raft 不允许类似 Paxos 中的乱序提交、简化系统中的角色状态(只有 Leader、Follower、Candidate 三种角色)、限制仅 Leader 可写入、使用随机化的超时时间来设计 Leader Election 等等。特点:Strong Leader系统中必须存在且同一时刻只能有一个 Leader,只有 Leader 可以接受 Clients 发过来的请求;Leader 负责主动与所有 Followers 通信,负责将“提案”发送给所有 Followers,同时收集多数派的 Followers 应答;Leader 还需向所有 Followers 主动发送心跳维持领导地位(保持存在感)。一句话总结 Strong Leader: “你们不要 BB! 按我说的做,做完了向我汇报!"。另外,身为 Leader 必须保持一直 BB(heartbeat) 的状态,否则就会有别人跳出来想要 BB 。Raft 中的基本概念篇幅有限,这里只对 Raft 中的几个概念做一个简单介绍,详细请参考 Raft paper。Raft-node 的 3 种角色/状态Follower:完全被动,不能发送任何请求,只接受并响应来自 Leader 和 Candidate 的 Message,每个节点启动后的初始状态一定是 Follower;Leader:处理所有来自客户端的请求,以及复制 Log 到所有 Followers;Candidate:用来竞选一个新 Leader (Candidate 由 Follower 触发超时而来)。Message 的 3 种类型RequestVote RPC:由 Candidate 发出,用于发送投票请求;AppendEntries (Heartbeat) RPC:由 Leader 发出,用于 Leader 向 Followers 复制日志条目,也会用作 Heartbeat (日志条目为空即为 Heartbeat);InstallSnapshot RPC:由 Leader 发出,用于快照传输,虽然多数情况都是每个服务器独立创建快照,但是Leader 有时候必须发送快照给一些落后太多的 Follower,这通常发生在 Leader 已经丢弃了下一条要发给该Follower 的日志条目(Log Compaction 时清除掉了) 的情况下。任期逻辑时钟时间被划分为一个个任期 (term),term id 按时间轴单调递增;每一个任期的开始都是 Leader 选举,选举成功之后,Leader 在任期内管理整个集群,也就是 “选举 + 常规操作”;每个任期最多一个 Leader,可能没有 Leader (spilt-vote 导致)。本图出自《Raft: A Consensus Algorithm for Replicated Logs》什么是 SOFAJRaft?SOFAJRaft 是一个基于 Raft 一致性算法的生产级高性能 Java 实现,支持 MULTI-RAFT-GROUP,适用于高负载低延迟的场景。 使用 SOFAJRaft 你可以专注于自己的业务领域,由 SOFAJRaft 负责处理所有与 Raft 相关的技术难题,并且 SOFAJRaft 非常易于使用,你可以通过几个示例在很短的时间内掌握它。SOFAJRaft 是从百度的 braft 移植而来,做了一些优化和改进,感谢百度 braft 团队开源了如此优秀的 C++ Raft 实现。SOFAJRaft 整体功能&性能优化功能支持1.Leader election:Leader 选举,这个不多说,上面已介绍过 Raft 中的 Leader 机制。2.Log replication and recovery:日志复制和日志恢复。Log replication 就是要保证已经被 commit 的数据一定不会丢失,即一定要成功复制到多数派。Log recovery 包含两个方面:Current term 日志恢复:主要针对一些 Follower 节点重启加入集群或者是新增 Follower 节点后如何追日志;Prev term 日志恢复:主要针对 Leader 切换前后的日志一致性。3.Snapshot and log compaction:定时生成 snapshot,实现 log compaction 加速启动和恢复,以及 InstallSnapshot 给 Followers 拷贝数据,如下图:本图出自《In Search of an Understandable Consensus Algorithm》4.Membership change:用于集群线上配置变更,比如增加节点、删除节点、替换节点等。5.Transfer leader:主动变更 leader,用于重启维护,leader 负载平衡等。6.Symmetric network partition tolerance:对称网络分区容忍性。如上图 S1 为当前 leader,网络分区造成 S2 不断增加本地 term,为了避免网络恢复后 S2 发起选举导致正在良心 工作的 leader step-down,从而导致整个集群重新发起选举,SOFAJRaft 中增加了 pre-vote 来避免这个问题的发生。SOFAJRaft 中在 request-vote 之前会先进行 pre-vote(currentTerm + 1, lastLogIndex, lastLogTerm),多数派成功后才会转换状态为 candidate 发起真正的 request-vote,所以分区后的节点,pre-vote 不会成功,也就不会导致集群一段时间内无法正常提供服务。7.Asymmetric network partition tolerance:非对称网络分区容忍性。如上图 S1 为当前 leader,S2 不断超时触发选主,S3 提升 term 打断当前 lease,从而拒绝 leader 的更新。在 SOFAJRaft 中增加了一个 tick 的检查,每个 follower 维护一个时间戳记录下收到 leader 上数据更新的时间(也包括心跳),只有超过 election timeout 之后才允许接受 request-vote 请求。8.Fault tolerance:容错性,少数派故障不影响系统整体可用性,包括但不限于:机器掉电强杀应用慢节点(GC, OOM 等)网络故障其他各种奇葩原因导致 raft 节点无法正常工作9.Workaround when quorate peers are dead:多数派故障时,整个 grop 已不具备可用性,安全的做法是等待多数节点恢复,只有这样才能保证数据安全;但是如果业务更加追求系统可用性,可以放弃数据一致性的话,SOFAJRaft 提供了手动触发 reset_peers 的指令以迅速重建整个集群,恢复集群可用。10.Metrics:SOFAJRaft 内置了基于 Metrics 类库的性能指标统计,具有丰富的性能统计指标,利用这些指标数据可以帮助用户更容易找出系统性能瓶颈。11.Jepsen:除了几百个单元测试以及部分 chaos 测试之外, SOFAJRaft 还使用 jepsen 这个分布式验证和故障注入测试框架模拟了很多种情况,都已验证通过:随机分区,一大一小两个网络分区随机增加和移除节点随机停止和启动节点随机 kill -9 和启动节点随机划分为两组,互通一个中间节点,模拟分区情况随机划分为不同的 majority 分组性能优化除了功能上的完整性,SOFAJRaft 还做了很多性能方面的优化,这里有一份 KV 场景(get/put)的 Benchmark 数据, 在小数据包,读写比例为 9:1,保证线性一致读的场景下,三副本最高可以达到 40w+ 的 ops。这里挑重点介绍几个优化点:Batch: 我们知道互联网两大优化法宝便是 Cache 和 Batch,SOFAJRaft 在 Batch 上花了较大心思,整个链路几乎都是 Batch 的,依靠 disruptor 的 MPSC 模型批量消费,对整体性能有着极大的提升,包括但不限于:批量提交 task批量网络发送本地 IO batch 写入要保证日志不丢,一般每条 log entry 都要进行 fsync 同步刷盘,比较耗时,SOFAJRaft 中做了合并写入的优化。批量应用到状态机需要说明的是,虽然 SOFAJRaft 中大量使用了 Batch 技巧,但对单个请求的延时并无任何影响,SOFAJRaft 中不会对请求做延时的攒批处理。Replication pipeline:流水线复制,通常 Leader 跟 Followers 节点的 Log 同步是串行 Batch 的方式,每个 Batch 发送之后需要等待 Batch 同步完成之后才能继续发送下一批(ping-pong),这样会导致较长的延迟。SOFAJRaft 中通过 Leader 跟 Followers 节点之间的 pipeline 复制来改进,非常有效降低了数据同步的延迟, 提高吞吐。经我们测试,开启 pipeline 可以将吞吐提升 30% 以上,详细数据请参照 Benchmark。Append log in parallel:在 SOFAJRaft 中 Leader 持久化 log entries 和向 Followers 发送 log entries 是并行的。Fully concurrent replication:Leader 向所有 Follwers 发送 Log 也是完全相互独立和并发的。Asynchronous:SOFAJRaft 中整个链路几乎没有任何阻塞,完全异步的,是一个完全的 callback 编程模型。ReadIndex:优化 Raft read 走 Raft log 的性能问题,每次 read,仅记录 commitIndex,然后发送所有 peers heartbeat 来确认 Leader 身份,如果 Leader 身份确认成功,等到 appliedIndex >= commitIndex,就可以返回 Client read 了,基于 ReadIndex Follower 也可以很方便的提供线性一致读,不过 commitIndex 是需要从 Leader 那里获取,多了一轮 RPC;关于线性一致读文章后面会详细分析。Lease Read:SOFAJRaft 还支持通过租约 (lease) 保证 Leader 的身份,从而省去了 ReadIndex 每次 heartbeat 确认 Leader 身份,性能更好,但是通过时钟维护 lease 本身并不是绝对的安全(时钟漂移问题,所以 SOFAJRaft 中默认配置是 ReadIndex,因为通常情况下 ReadIndex 性能已足够好。SOFAJRaft 设计Node:Raft 分组中的一个节点,连接封装底层的所有服务,用户看到的主要服务接口,特别是 apply(task)用于向 raft group 组成的复制状态机集群提交新任务应用到业务状态机。存储:上图靠下的部分均为存储相关。Log 存储,记录 Raft 用户提交任务的日志,将日志从 Leader 复制到其他节点上。LogStorage 是存储实现,默认实现基于 RocksDB 存储,你也可以很容易扩展自己的日志存储实现;LogManager 负责对底层存储的调用,对调用做缓存、批量提交、必要的检查和优化。Metadata 存储,元信息存储,记录 Raft 实现的内部状态,比如当前 term、投票给哪个节点等信息。Snapshot 存储,用于存放用户的状态机 snapshot 及元信息,可选:SnapshotStorage 用于 snapshot 存储实现;SnapshotExecutor 用于 snapshot 实际存储、远程安装、复制的管理。状态机StateMachine:用户核心逻辑的实现,核心是 onApply(Iterator) 方法, 应用通过 Node#apply(task) 提交的日志到业务状态机;FSMCaller:封装对业务 StateMachine 的状态转换的调用以及日志的写入等,一个有限状态机的实现,做必要的检查、请求合并提交和并发处理等。复制Replicator:用于 Leader 向 Followers 复制日志,也就是 Raft 中的 AppendEntries 调用,包括心跳存活检查等;ReplicatorGroup:用于单个 Raft group 管理所有的 replicator,必要的权限检查和派发。RPC:RPC 模块用于节点之间的网络通讯RPC Server:内置于 Node 内的 RPC 服务器,接收其他节点或者客户端发过来的请求,转交给对应服务处理;RPC Client:用于向其他节点发起请求,例如投票、复制日志、心跳等。KV Store:KV Store 是各种 Raft 实现的一个典型应用场景,SOFAJRaft 中包含了一个嵌入式的分布式 KV 存储实现(SOFAJRaft-RheaKV)。SOFAJRaft Group单个节点的 SOFAJRaft-node 是没什么实际意义的,下面是三副本的 SOFAJRaft 架构图:SOFAJRaft Multi Group单个 Raft group 是无法解决大流量的读写瓶颈的,SOFAJRaft 自然也要支持 multi-raft-group。SOFAJRaft 实现细节解析之高效的线性一致读什么是线性一致读? 所谓线性一致读,一个简单的例子就是在 t1 的时刻我们写入了一个值,那么在 t1 之后,我们一定能读到这个值,不可能读到 t1 之前的旧值 (想想 Java 中的 volatile 关键字,说白了线性一致读就是在分布式系统中实现 Java volatile 语义)。如上图 Client A、B、C、D 均符合线性一致读,其中 D 看起来是 stale read,其实并不是,D 请求横跨了 3 个阶段,而读可能发生在任意时刻,所以读到 1 或 2 都行。重要:接下来的讨论均基于一个大前提,就是业务状态机的实现必须是满足线性一致性的,简单说就是也要具有 Java volatile 的语义。要实现线性一致读,首先我们简单直接一些,是否可以直接从当前 Leader 节点读?仔细一想,这显然行不通,因为你无法确定这一刻当前的 “Leader” 真的是 Leader,比如在网络分区的情况下,它可能已经被推翻王朝却不自知。最简单易懂的实现方式:同 “写” 请求一样,“读” 请求也走一遍 Raft 协议 (Raft Log)。本图出自《Raft: A Consensus Algorithm for Replicated Logs》这一定是可以的,但性能上显然不会太出色,走 Raft Log 不仅仅有日志落盘的开销,还有日志复制的网络开销,另外还有一堆的 Raft “读日志” 造成的磁盘占用开销,这在读比重很大的系统中通常是无法被接受的。ReadIndex Read这是 Raft 论文中提到的一种优化方案,具体来说:Leader 将自己当前 Log 的 commitIndex 记录到一个 Local 变量 ReadIndex 里面;接着向 Followers 发起一轮 heartbeat,如果半数以上节点返回了对应的 heartbeat response,那么 Leader 就能够确定现在自己仍然是 Leader (证明了自己是自己);Leader 等待自己的状态机执行,直到 applyIndex 超过了 ReadIndex,这样就能够安全的提供 Linearizable Read 了,也不必管读的时刻是否 Leader 已飘走 (思考:为什么等到 applyIndex 超过了 ReadIndex 就可以执行读请求?);Leader 执行 read 请求,将结果返回给 Client。通过ReadIndex,也可以很容易在 Followers 节点上提供线性一致读:Follower 节点向 Leader 请求最新的 ReadIndex;Leader 执行上面前 3 步的过程(确定自己真的是 Leader),并返回 ReadIndex 给 Follower;Follower 等待自己的 applyIndex 超过了 ReadIndex;Follower 执行 read 请求,将结果返回给 Client。(SOFAJRaft 中可配置是否从 Follower 读取,默认不打开)ReadIndex小结:相比较于走 Raft Log 的方式,ReadIndex 省去了磁盘的开销,能大幅度提升吞吐,结合 SOFAJRaft 的 batch + pipeline ack + 全异步机制,三副本的情况下 Leader 读的吞吐可以接近于 RPC 的吞吐上限;延迟取决于多数派中最慢的一个 heartbeat response,理论上对于降低延时的效果不会非常显著。Lease ReadLease Read 与 ReadIndex 类似,但更进一步,不仅省去了 Log,还省去了网络交互。它可以大幅提升读的吞吐也能显著降低延时。基本的思路是 Leader 取一个比 election timeout 小的租期(最好小一个数量级),在租约期内不会发生选举,这就确保了 Leader 不会变,所以可以跳过 ReadIndex 的第二步,也就降低了延时。可以看到 Lease Read 的正确性和时间是挂钩的,因此时间的实现至关重要,如果时钟漂移严重,这套机制就会有问题。实现方式:定时 heartbeat 获得多数派响应,确认 Leader 的有效性 (在 SOFAJRaft 中默认的 heartbeat 间隔是 election timeout 的十分之一);在租约有效时间内,可以认为当前 Leader 是 Raft Group 内的唯一有效 Leader,可忽略 ReadIndex 中的 heartbeat 确认步骤(2);Leader 等待自己的状态机执行,直到 applyIndex 超过了 ReadIndex,这样就能够安全的提供 Linearizable Read 了 。在 SOFAJRaft 中发起一次线性一致读请求的代码展示:// KV 存储实现线性一致读public void readFromQuorum(String key, AsyncContext asyncContext) { // 请求 ID 作为请求上下文传入 byte[] reqContext = new byte[4]; Bits.putInt(reqContext, 0, requestId.incrementAndGet()); // 调用 readIndex 方法, 等待回调执行 this.node.readIndex(reqContext, new ReadIndexClosure() { @Override public void run(Status status, long index, byte[] reqCtx) { if (status.isOk()) { try { // ReadIndexClosure 回调成功,可以从状态机读取最新数据返回 // 如果你的状态实现有版本概念,可以根据传入的日志 index 编号做读取 asyncContext.sendResponse(new ValueCommand(fsm.getValue(key))); } catch (KeyNotFoundException e) { asyncContext.sendResponse(GetCommandProcessor.createKeyNotFoundResponse()); } } else { // 特定情况下,比如发生选举,该读请求将失败 asyncContext.sendResponse(new BooleanCommand(false, status.getErrorMsg())); } } });}应用场景Leader 选举;分布式锁服务,比如 Zookeeper,在 SOFAJRaft 中的 RheaKV 模块提供了完整的分布式锁实现;高可靠的元信息管理,可直接基于 SOFAJRaft-RheaKV 存储;分布式存储系统,如分布式消息队列、分布式文件系统、分布式块系统等等。使用案例RheaKV:基于 SOFAJRaft 实现的嵌入式、分布式、高可用、强一致的 KV 存储类库。AntQ Streams QCoordinator:使用 SOFAJRaft 在 Coordinator 集群内做选举、使用 SOFAJRaft-RheaKV 做元信息存储等功能。Schema Registry:高可靠 schema 管理服务,类似 kafka schema registry,存储部分基于 SOFAJRaft-RheaKV。SOFA 服务注册中心元信息管理模块:IP 数据信息注册,要求写数据达到各个节点一致,并且在少数派节点挂掉时保证不影响数据正常存储。实践一、基于 SOFAJRaft 设计一个简单的 KV Store二、基于 SOFAJRaft 的 RheaKV 的设计功能名词PD全局的中心总控节点,负责整个集群的调度,不需要自管理的集群可不启用 PD (一个 PD 可管理多个集群,基于 clusterId 隔离)。Store集群中的一个物理存储节点,一个 Store 包含一个或多个 Region。Region最小的 KV 数据单元,每个 Region 都有一个左闭右开的区间 [startKey, endKey), 可根据请求流量/负载/数据量大小等指标自动分裂以及自动副本搬迁。特点嵌入式强一致性自驱动自诊断, 自优化, 自决策以上几点(尤其2、3) 基本都是依托于 SOFAJRaft 自身的功能来实现,详细介绍请参考 SOFAJRaft 文档 。致谢感谢 braft、etcd、tikv 贡献了优秀的 Raft 实现,SOFAJRaft 受益良多。招聘蚂蚁金服中间件团队持续在寻找对于基础中间件(如消息、数据中间件以及分布式计算等)以及下一代高性能面向实时分析的时序数据库等方向充满热情的小伙伴加入,有意者请联系 boyan@antfin.com。参考资料SOFAJRaft 源码SOFAJRaft 详细文档RaftRaft paperRaft: A Consensus Algorithm for Replicated LogsPaxos/Raft:分布式一致性算法原理剖析及其在实战中的应用braft 文档线性一致性和 RaftStrong consistency modelsetcd raft 设计与实现《一》MetricsjepsenBenchmark本文作者:s潘潘阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 15, 2019 · 5 min · jiezi

为什么在PBFT中最优的节点总数3f+1

在使用PBFT的系统中,假设最多允许 f 个节点发生异常,则正常节点数 k需满足k>f, 节点总数是 n 需满足n>2f。假如 f 个异常节点没有响应,在不知情的情况下,我们一般认为响应的节点里可能有 f 个坏节点,则节点总数 n>3f,所以节点总数最少为3f+1,可以保证最多有 f 节点发生异常。参考论文原文如下,看了好几遍加上查阅资料才看懂。

March 14, 2019 · 1 min · jiezi

重磅预告 | 今晚直播:MyCat的坑如何在分布式中间件DBLE上改善

上周,DBLE团队历时3个月准备的开源MySQL分布式中间件DBLE系列公开课发布了,为使社区同学能够更好的评估课程内容、质量以及对DBLE有更清晰深入的认知,我们联合IT168将在第二节课程发布前开放一期直播,跟大家聊聊DBLE与MyCat错综复杂的故事。直播时间:3月14日(今晚)20:00PM分享嘉宾:DBLE项目负责人 蓝寅分享主题:《MyCat的坑如何在分布式中间件DBLE上改善》课程亮点跟你分享MyCat不为人知的一面线上的复杂查询其实结果不对?被MyCat默默吃掉的那些SQL!MyCat不太支持的那些语法!不严谨代码导致的那些bug!内容简介分享将从DBA与研发两个角度进行说明,对于DBA同学最关心的SQL语言实现以及运维管理应当如何评估;开发者同学十分关注的bug修复质量,代码质量及代码科学管理如何判断,通过拆解两方对中间件的需求来吐槽MyCat到底有多少坑以及DBLE是如何避免的,为大家在MySQL的“读写分离”、“分库分表”等架构选型方面提供指引。课程提纲一.DBA角度SQL语言实现 数据查询: 简单查询 / 聚合函数查询 / 函数嵌套查询 / union 查询 / 子查询 / Join查询 / 跨表Join查询 数据操作:Insert / 全局序列 上下文设置:上下文变量运维管理 用户权限:管理端用户权限二.开发角度1.bug修复质量2.代码半成品残留3.代码灌水4.伪造实现直播课程参与方式关注「爱可生开源社区」,回复关键字 “ 直播 ”即可成功报名啦!今晚20:00,一起相约微课堂,关于DBLE公开课或者DBLE的使用问题反馈一键直达DBLE研发团队。开源分布式中间件DBLE社区官网:https://opensource.actionsky….GitHub主页:https://github.com/actiontech…技术交流群:669663113开源数据传输中间件DTLE社区官网:https://opensource.actionsky….GitHub主页:https://github.com/actiontech…技术交流群:852990221

March 14, 2019 · 1 min · jiezi

NoSQL最新现状和趋势:云NoSQL数据库将成重要增长引擎

NoSQL最早起源于1998年,但从2009年开始,NoSQL真正开始逐渐兴起和发展。回望历史应该说NoSQL数据库的兴起,完全是十年来伴随互联网技术,大数据数据的兴起和发展,NoSQL在面临大数据场景下相对于关系型数据库运用,这一概念无疑是一种全新思维的注入。接下来本文重点梳理下NoSQL领域最新发展趋势以及阿里云NoSQL最新现状,以飨读者。云NoSQL数据库成为数据库领域重要增长引擎云化趋势不可避免,根据Gartner的报告,2017年超过73%的DBMS增长来自云厂商,Gartner象限里面AWS在领导者象限上升趋势明确,老牌厂商下滑严重。在2018年Gartner报告中,阿里云数据库更是中国唯一首次入围远见者象限。而在众多云厂商里面增长最快的又是NoSQL数据库,云NoSQL成为数据库领域重要增长引擎。阿里云覆盖了主流的NoSQL引擎阿里云集团是国内最早提出数据战略,本身也拥有最大体量的数据,是最早投入NoSQL数据库技术研发,目前也拥有国内最大的专家团队。在持续十年技术加持下,阿里云NoSQL目前覆盖了所有主流的NoSQL数据库,如Redis/mongodb/HBase/图等等。下表是目前阿里云目前覆盖的主流的NoSQL数据库。NoSQL数据库存储类型典型场景Redis/MemcacheKey/Value缓存,搭配所有数据库使用;直播、视频等各种在线场景。MongoDB文档非常适合游戏、地图、基于位置的数据查询等等场景。HBase Wide Column风控、画像、用户行为数据,用户详单,对象存储等等。ElasticSearch/Solr倒排索引文本模糊查询JanusGraph/GDB图关系分析和挖掘,金融、风控阿里云NoSQL数据库多项独家关键技术领先竞争对手,是国内当之无愧的NoSQL数据库排头兵。拥有业界最全的产品的形态以Redis为例,全系覆盖全系列支持2.X, 3.X 4.X, 支持从适合开发者的单节点到多中心的各种产品形态,架构任意变换, 自动容灾, 全面监控。通过各种产品形态,阿里云有能力服务从开发者、中小企业、大企业各种客户,满足不同客户对性价比,SLA,可靠性,管控的各种需求。在最新版本跟进上,阿里云NoSQL也一直在走云厂商前列,18年6月6日全球首发HBase 2.0,同年10月份又全球第一家云厂商支持MongoDB 4.0版本。另外预告一下,这个月我们会推出全新redis 5.0版本,满足众多企业对新版本的诉求,敬请期待!把数据可靠性作为重中之重对大多数公司来说数据的安全性以及可靠性是非常重要的,如何保障数据的安全以及数据的可靠是大多数数据库必须考虑的。2016 IDC的报告表示数据的备份(data-protection)和数据恢复(retention)是NoSQL的最基础的需求之一,阿里云NoSQL数据库也一直把怎么保障客户的数据安全放在首位。以云HBase为例,传统数据库备份恢复的能力都是TB级别,在交易等场景下面是足够的,但面向大数据场景就捉襟见肘了。云HBase通过垂直整合高压缩、内核级优化等能力,将备份恢复的量级成功推高百倍以上,做到 百TB级别甚至更高 ,让客户在大数据量场景下也无后顾之忧。云HBase支持全量(备份集)备份、全量(备份集)恢复、增量(实时)备份、增量(时间点)恢复完整备份恢复能力。全球分布式能力助力企业解决业务出海、地域级灾备、全球同服/多活、跨域数据迁移等关键业务云厂商之所以有这么强大的活力,除了在数据库内核本身长足的进步之外,结合云服务整体生态的创新是重要的一环。阿里云NoSQL数据库持续发力和覆盖了全球分布式能力,助力企业参与全球业务竞争。是否具备全球扩展和分布式的能力,是云NoSQL数据库重要的入门门槛。以Redis为例,我们再18年8月份全球云厂商首家推出Redis全球多活,解决多媒体、游戏、电商等多行业客户业务出海、地域级灾备、全球同服/多活、跨域数据迁移的诉求。多项关键能力独步领域内:全球多活:内核级别支持,实现多节点全球容灾。高可用:同步通道支持断点续传,容忍天级别的隔断,子实例HA自动切换
高性能:单通道10万TPS,高于Redis处理速度;延迟低、洲际内百毫秒延迟
支持数据最终一致性:CRDT冲突解决方案+数据一致性检测,基于Redis内核改造,原生Redis内核无此功能。融合多模和多负载能力,提供一站式低成本数据处理平台2018年12月13日第8届中国云计算标准和应用大会隆重发布X-Pack,在云HBase基础上新增多模型和多负载支持。
多模型支持:同时支持KV、时序、时空、图、文档等多种数据模型,内置丰富处理能力,让业务开发效率提升百倍。多负载支持:ApsaraDB HBase在在线能力的基础上,融合流处理、批处理、OLAP,OLTP、高速对象存储,全文检索等能力,提供客户业务开箱即用的能力。通过多模型和多负载的支持,基于Apache HBase及HBase生态构建的 低成本、一站式 数据处理平台,支持Spark、二级索引、全文查询、图、时序、时空、分析等能力,是物联网、风控推荐、对象存储、AI、Feeds等场景首选数据库。多项独家企业级能力,领域内领先,不负重托客户选择NoSQL数据库服务,一个非常看重的关键点是就是企业级能力,解决企业生产开发过程中,管理,维护各个方案的瓶颈。NoSQL数据库服务深度结合企业应用场景,经过长期的发展,目前具备了多项独家企业级能力,以MongoDB为例:跨域双活:数据双向同步,相比现有通道产品提升100%效率,业界领先。首创物理+逻辑备份双能力:物理备份,相比开源版本备份效率提升100%,恢复效率提升300%。创新性提供逻辑snapshot能力,解决政企等定期大批量数据更新需求,同架构下性价比提升100%。秒级监控及智能诊断能力:提供每秒粒度的监控数据采集,监控精度提升数十倍。结合监控、审计等数据提供智能诊断,分析系统运行瓶颈并自动化提供优化建议。下面是云MongoDB和自建的社区版本的一个对比,云服务再搭建、安全、优化、运维、定制等各个方面全面胜出,全面降低客户的TCO。即将重磅发布CloudNative图数据库随着企业的数据越来越多,业务越来越复杂,客户提出了很多实时在线的高性能图数据存储和查询服务的诉求。我们也将再本月重磅发布阿里云首款Cloud Native专业的图数据库。新的图数据库将会支持丰富的能力,并在易用性,高可用站上一个新的高度,非常适合拥有社交网络、金融欺诈检测、实时推荐引擎、知识图谱、网络/IT运营方面诉求的客户。展望未来,阿里云NoSQL数据库也会持续围绕客户业务,成本,运维各个方面进行优化和创新,成就客户,争做最好的NoSQL数据库而持续努力!最后,阿里即将召开2019阿里云峰会 北京站有专门的数据库专场《数据库专场:云时代的企业级数据库架构与实践》,进一步分享数据库的最新发展趋势,欢迎大家报名:
https://www.yunqi.org/goPage?page=bj_signup_mb&activeId=2&ticketTypeId=39&channelId=12本文作者:所在jason阅读原文本文为云栖社区原创内容,未经允许不得转载。

March 12, 2019 · 1 min · jiezi

DBLE核心研发主讲:MySQL分布式中间件公开课开课啦

DBLE年龄:2岁半爱好:开源技能:数据水平拆分、兼容MySQL、高可用性、SQL支持、复杂查询优化、分布式事务支持特长:兼容性、复杂查询、分布式事务的深入改进优化为了使社区同学能够更清晰的了解开源分布式中间件DBLE的功能特性,DBLE核心研发团队历时3个月推出DBLE系列公开课。DBLE公开课简介课程目录DBLE系列课程分为4章,共12节3月15日起,每周五中午11:30前更新课程咨询:DBLE技术交流群(669663113)每天8:30-20:30即时疑问解答适用人群 正在使用DBLE或MyCat的用户当前中间件无法满足业务需求的用户运维大体量数据库,需要考虑分库分表的DBA为数据库架构选型发愁的研发人员讲师介绍▽Attention▽为了让大家更好的了解课程内容以及评估课程质量,本期预热已发布第一期课程,大家可以先感受一下一股强烈的分布式旋风扑面而来是多么清爽。第一章 DBLE的基本使用1.1 DBLE概述课程观看传送门:方式1:打开「爱可生开源社区」官网,点击技术博客,选择DBLE系列公开课即可观看方式2:点击“阅读原文”直达播放现场,一秒开始听课~如何获取课程DBLE系列公开课自3月15日起将按照每周一节课程发布在「爱可生开源社区官网」,点击官网(http://opensource.actionsky.com)博客专栏,即可查看“DBLE系列公开课”。课程免费,公开,我们的最终目标是能有更多的社区同学了解认可并使用DBLE。除此之外,我们为心急的同学提供了新的打开方式:敲黑板:VIP提前解锁新课程朋友圈分享本文并截图给小编(WeChat:mg116611)即可提前2周获取课程,DBLE系列公开课更新结束前分享均有效。

March 9, 2019 · 1 min · jiezi

深度分析 | MyCat与DBLE的对比性能调优

作者简介蓝寅,开源分布式中间件DBLE项目负责人;持续专注于数据库方面的技术, 始终在一线从事开发;对数据复制,读写分离,分库分表的有深入的理解与实践。问题起因:用benchmarksql_for_mysql对原生MyCat-1.6.1和DBLE-2.17.07版做性能测试对比,发现DBLE性能只到原生版MyCat的70%左右。问题分析过程:分析过程主要有以下内容:包括现象,收集数据,分析猜测原因,验证猜测的方式来进行。开源分布式中间件DBLE: 社区官网,获取DBLE快速入门指南及最新资讯: https://opensource.actionsky.com GitHub主页,查看官方文档: https://github.com/actiontech… 社区技术交流群,迅速获取官方支持: QQ群:6696631131.分析瓶颈1.1 先对两者进行一个CPU占用的堆栈分析通过对CPU火焰图的比较,发现DBLE用在纯排序上的CPU占用在15%以上,而MyCat在排序上没有看到明显的CPU占用。( 复盘时的思考:这里有明显的可疑之处,应当及早观察两者是否公平)1.2 首先猜测可能的原因a.由于MyCat对以下这条用例实现有bug:具体方式是直接原句下发SQL到节点,收到各个节点的结果后直接做加法;而DBLE则是改写为select distinct s_i_id 收集全部结果集,然后在中间件做去重和统计的工作。所以两者在这个case上的对比是不公平的。b.排序本身算法选择的问题1.3 对猜测原因的验证a.去除有bug的case,并未看到性能有提升,而且考虑这条用例在所有用例出现的概率只有4%,涉及到的数据也不多,所以应该不是性能问题的主因。b.去除有排序的case,看到两者性能接近,确定是排序的问题。2.猜测原因2.1 猜测一:源码实现原因2.1.1 猜测描述梳理DBLE源码排序逻辑的实现细节,是多路归并的排序,理论上是最优选择。实际上具体的实现上有可优化的空间,如下图, N个数的K路排序的初始化值理论最优复杂度是O(N),而这里变成了O(NlogK2) 。2.1.2 验证猜测为了快速将排序的因素排除,将cmp函数直接返回1再次做测试。结果提升了10%的性能,所以虽然cmp是有性能问题,但导致性能如此大还有其他原因。(复盘:新版本已优化此处10%的性能差异)2.2 猜测二:回到排序SQL查看B-SQL源码,有3个排序SQL,其中有2个排序SQL的排序列不在select 项中,这本来应该引发MyCat的bug的,但我们在返回集和抓包中都没有发现。再仔细阅读源码,原来B-SQL通过hard code的方式使得压力永远跑不到这两个代码路径上,这样我们又排除了2个干扰因素,问题集中到剩下的那个排序上了。将排序除去,64数据量,64并发,DBLE的性能是MyCat的96%。证明确实和排序有关。3.分析多并发压力排序性能的原因3.1 猜测排序算法在特殊场景下的适用性3.1.1 猜测描述由于MyCat排序采用的是timsort, 时间复杂度的可能最优是O(n)。而DBLE的多路归并排序在B-SQL这个场景下时间复杂度最差情况是O(n*(k-1)).猜测timSort排序在B-SQL多并发场景下可能会优于多路归并。3.1.2 验证猜测用B-SQL压测并统计函数调用次数。结论:在B-SQL场景下:两者平均每个排序调用的cmp函数的次数并没有发生明显的异化每个排序cmp次数虽然没有大的差异,但总的调用次数却相差很大,DBLE大约是MyCat的5倍4. 分析DBLE排序时cmp函数次数调用多的原因问题集中在了为什么DBLE会有更多次的比较函数调用。4.1 验证压力下发的SQL是否与cmp函数调用相符是否下发的SQL就不公平4.1.1收集数据用抓包的方式分别抓取B-SQL发给MyCat和DBLE的包,结果发现 DBLE的所有SQL中排序这条SQL的发生次数是MyCat的10倍左右。再次用yourkit查看调用次数和CPU分布验证,发现调用次数确实符合抓包的结论,CPU分布也是DBLE分了大量的时间用于排序,而MyCat对排序的分配几乎可以忽略。这也与最一开始的火焰图结论一样。用wireshark分析DBLE抓包结果,发现某些连接执行一段时间之后大量的重复出现排序+delete的query请求直到压力结束,举例如下图。4.1.2 分析原因分析B-SQL源码这里发现只有delete的数据为0才会引发死循环。4.1.3 验证测试在引发死循环的原因找到之前,先修改代码验证测试。无论result是否是0都设置newOrderRemoved=true使得B-SQL跳出死循环。验证测试,DBLE性能终于符合预期,变为MyCat的105%。至此,B-SQL有排序引发DBLE性能下降的原因找到了,某种场景下B-SQL对DBLE执行delete,影响行数为0,导致此时会有死循环,发送了大量排序请求,严重降低了DBLE性能,并且并发压力越大越容易出现,但也有一定几率不会触发。5.分析哪种场景下delete行数为05.1隔离级别测试因为对隔离级别并不熟悉,花了很长时间才想到原因,在MySQL上做了一个实验:也就是说,在并发情况下确实有可能有死循环出现。5.2 分析为什么只有在DBLE上有这个问题而在MyCat上没有这个问题原因是DBLE和MyCat的默认隔离级别都是REPEATED_READ,但MyCat的实现有bug,除非客户端显式使用set语句,MyCat后端连接使用的隔离级别都是下属结点上的默认隔离级别;而DBLE会在获取后端连接后同步上下文,使得session级别的隔离级别和DBLE配置相同。而后端的四个结点中除了1台是REPEATED_READ,其他三个结点都是READ_COMMITTED。这样同样的并发条件,DBLE100%会触发,而MyCat只有25%的概率触发。5.3 验证测试将DBLE上的配置添加<property name=“txIsolation”>2</property>(默认是3)与默认做对比:性能比是1:0.75.符合期望,性能原因全部找到。6. 吐槽最后吐槽一下B-SQL,找了官方的B-SQL4.1版和5.0版,4.1版并未对此情况做任何改进,仍有可能陷入死循环影响测试。而5.0的对应代码处有这么一段注释,不知道PGSQL是否这里真的会触发异常,但MySQL并不会触发异常,仍有可能陷入死循环。7.性能原因回顾1.cmp函数时候初始化值的问题,影响部分性能,非主要性能瓶颈,新版本已改进。2.同时触发了MyCat和B-SQL的两个bug,导致测试的性能数据负负得正;Mycat bug:配置的隔离级别不生效问题B-SQL bug:RR隔离级别下,delete死循环问题需要将MySQL结点都改为READ_COMMITED,再将配置改为<property name=“txIsolation”>2</property>,避开上述的bug。8.收获1.测试环境的搭建无论是配置参数还是各个节点的状态都要同步,保证公平。2.性能分析工具的使用。3.性能测试可能一次的结果具有偶然性,需要多次验证。4.当有矛盾的结论时候,可能就快接近问题的真相了,需要持续关注。开源分布式中间件DBLE社区官网: https://opensource.actionsky….GitHub主页: https://github.com/actiontech…技术交流群:669663113开源数据传输中间件DTLE社区官网: https://opensource.actionsky….GitHub主页: https://github.com/actiontech…技术交流群:852990221

March 9, 2019 · 1 min · jiezi

分布式理论与场景浅谈

基本理论FLP/CAP/BASE/ACIDFLP不可能原理在异步模型中,分布式系统中只要有一个进程不可用,就可能无法达成整体的共识.在工程中的分布式系统实现中, 通过解决活锁等问题, 来使系统在一定时间内可以达到一致性.上图里CP还少一个常见的: zookeeperACID对应刚性事务, 追求强一致性, 以MySql等RDBMS为代表;BASE对应柔性事务, 牺牲强一致性来换取一定的可用性, 过种中会存在中间状态, 但会达到最终一致性, 以Spanner等分布式系统为代表.CAP理论分布式系统中C、A、P三者不能同时满足,最多只能满足其中两个.通常来说, 使用网络通信的分布式系统,无法舍弃P性质, 会根据选择的不同去达到AP或CP.不过三者选二的情况很容易发生误解,分区很少发生,那么在系统不存在分区的情况下没什么理由牺牲C或A。A与C的取舍可以在同一系统内反复发生, A与C的程度都有很多的层次, 比如可用性的变化和一致性等级的变化等所以实际使用中可根据应用场景进行适当取舍以zookeeper为例, 它的CAP分别为:C: 最终一致性, 一般十几秒内可以Sync到各个节点A: 数据总是可用, 超过一半的节点的数据是最新的, 但想保证读到最新的数据需手动调用Sync函数P: 一是节点多了会导致写数据时同步延时非常大, 二是节点多了Leader选举非常耗时, 可引入 observer节点缓解zookeeper是一个CP系统, 因为在任何时刻对它的访问请求能得到一致的数据结果, 但不保证每次服务请求的可用性(比如发生网络分区时), 所以其实它做服务发现并不如AP的eureka合适分布式事务实现最终一致性有三种模式可靠事件模式, MQ配合本地或外部事件表业务补偿模式, 用于业务/技术异常时补偿TCC模式, 应用层的2PC分布式事务的需求, 主要是因为数据库的水平拆分以及应用SOA化, 服务调用中会使用到跨库事务.从某种角度来说, 只有拥有复杂业务(如金融), 全球性服务(如谷歌), 和拥有大数据量(分库)的公司才会有这种需求的场景. 常见的一致性协议ZABRaftViewstamped ReplicationQuorumGossip本质都是Paxos协议的变种常见的类Paxos分布式事务实现MySQL的XATCC, Try-Confirm-Cancel很好的一篇核心金融场景分布式事务分布式锁通常的选择单机Redis或官方分布式锁RedLockZookeeper可根据不同的业务场景来使用, 如果对锁的要求不是很高的话, 比如1%的重复加锁可接受, 使用单机Redis最简单方便分布式的实际业务场景微信 PaxosStore阿里 AliSQL X-ClusterTiDB (multi raft group)谷歌Spanner等AWS aurora, dynamo

March 8, 2019 · 1 min · jiezi

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

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

March 5, 2019 · 2 min · jiezi

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

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

March 4, 2019 · 1 min · jiezi

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

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

March 1, 2019 · 1 min · jiezi

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

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

February 28, 2019 · 1 min · jiezi

回顾 | 开源分布式中间件DBLE社区分享活动总结

1月24日,我们发布了为期30天的「如何获取全国 25场 MySQL 主题大会免费入场券」有奖社区分享活动,希望社区同学能够分享测试或生产环境中DBLE使用上的难题,困惑,创新或收获,分享与DBLE相关的社区故事。在经历春节在内的1个月中我们收到共了3位社区同学的4篇投稿,内容涉及DBLE的基础配置系列,DBLE的进阶使用系列,DBLE实际应用案例分析,每篇内容都是社区同学自己在实际应用中的总结与整理,相信这些内容除了与大家分享外更重要的是对产品的具体使用与认知上的沉淀。案例类 社区投稿 | DBLE和MyCat跨分片查询结果不一致案例分析投稿:杨严豪这篇文章的背景是社区同学刚好发现一条跨节点 join 查询在 DBLE、Mycat 的查询得到的结果不一致这样一个比较有意思的场景,于是准备测试环境进行分析比对并最终验证出了正确的数据来源。Tips:故障及案例分析思路可参考,同时提醒同学们重视数据的正确性基础知识类社区投稿 | DBLE rule.xml 配置解析投稿:余朝飞rule.xml 定义实际用到的拆分算法,熟悉各种拆分区算法的详细配置及其适用场景,方便我们在众多数据拆分场景选择并配置合适的拆分规则,同时这也是试用分库分表中间件的第一步。将表的详细拆分算法写在配置中,这是一种很"傻"的方式,但是这也是万不得已的一种选择,如果不通过配置文件的方式告诉中间件这些信息,那么中间件就无从得知底层具体的数据分布情况,也就达不到我们最终想要的目的了。社区投稿 | DBLE Server.xml 配置解析投稿:余朝飞本文简单介绍了Server.xml中的三个重要的配置段落,分别是DBLE的系统配置,用户配置以及黑白名单功能,针对用户配置则介绍了实际应用场景下的配置以及对应的DML权限配置,并详细介绍了DBLE黑白名单配置实践。Tips:以上两篇文章是社区同学在使用DBLE时对具体配置的理解,虽说是基础知识,但认知偏差或粗心大意很可能会为后期埋雷,建议详读,打牢基础。进阶使用类 社区投稿 | DBLE 自定义拆分算法 投稿:钟悦DBLE默认支持数十种数据拆分算法,基本能满足大部分的社区用户的使用需求;为了满足更广的业务场景,DBLE还支持更加灵活的自定义拆分算法;本文对面向有类似需求的DBLE开发者,提供了一个如何开发和部署自定义的拆分规则的一个指引。Tips:此文属于DBLE的进阶使用,为社区提供了DBLE如何开发和部署自定义的拆分规则的一个指引,非常适用于有类似需求的开发者。▽奖励回顾符合活动规则的投稿即可获得 100 RMB 京东E卡经 DBLE 团队审核优质文章可再额外获得:100 RMB 京东E卡在「爱可生开源社区」官微、官网及多个官方自媒体平台的推广展示全国 25场 MySQL主题大会免费入场券(仅限作者本人,不可转让)甲骨文官方MySQL技术交流大会姜承尧老师的IMG社区嘉年华叶金荣&吴炳锡老师的「3306」(包含但不仅限于以上社区活动)▽奖品展示1.「爱可生开源社区」推送的全国25场社区活动免费入场券请3位同学私信小编(wechat:mg116611)领取2.京东E卡(长这样子~)关于以上3位同学200元的京东E卡已发送,预计2个工作日即到;MySQL主题大会免费入场券请私信领取哦。后期我们会继续开放邀请分享,错过的同学请关注下期活动,稿子可以先攒起来~往期精选| 使用指南开源分布式中间件 DBLE 快速入门指南DBLE 自定义拆分算法DBLE Server.xml 配置解析DBLE Schema.xml 配置解析DBLE rule.xml 配置解析| 案例分析DBLE和Mycat跨分片查询结果不一致案例分析| 社区活动如何获取全国 25场 MySQL 主题大会免费入场券开源分布式中间件DBLE GitHub主页:https://github.com/actiontech…技术交流群:669663113开源数据传输中间件DTLEGitHub主页:https://github.com/actiontech…技术交流群:852990221

February 28, 2019 · 1 min · jiezi

刚开始Jaeger和分布式追踪?这个简介可以帮到你(视频+幻灯片)

这是来自Uber的Yuri Shkuro和红帽的Pavol Loffay在几个月前KubeCon + CloudNativeCon北美有关Jaeger和分布式跟踪的介绍。内容包括对当前的Jaeger功能做一个简短的演示,讨论即将到来的一年的路线图,并完成问答。在完成这个介绍之后,应该能更好地了解Jaeger如何适应云原生应用程序的可观察性:腾讯视频幻灯片有关该项目的更多信息,欢迎大家继续观赏他们的“深入了解:Jaeger”的内容:腾讯视频幻灯片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购票窗口,立即购票!

February 28, 2019 · 1 min · jiezi

分布式系统的Raft算法

Raft作为Paxos的简化版本,在工程领域有着更加广泛的应用。本文转载的几篇文章对Raft的工作原理、实现方式进行了详细的介绍。分布式系统的Raft算法总结:目前几乎所有语言都已经有支持Raft算法的库包,具体可参考:raftconsensus.github.io英文动画演示RaftCAP原理和BASE思想分布式Paxos算法分布式事务 => 分布式系统事务一致性解决方案

February 27, 2019 · 1 min · jiezi

分布式事务中间件 Fescar - 全局写排它锁解读

前言一般,数据库事务的隔离级别会被设置成 读已提交,已满足业务需求,这样对应在Fescar中的分支(本地)事务的隔离级别就是 读已提交,那么Fescar中对于全局事务的隔离级别又是什么呢?如果认真阅读了 分布式事务中间件Txc/Fescar-RM模块源码解读 的同学应该能推断出来:Fescar将全局事务的默认隔离定义成读未提交。对于读未提交隔离级别对业务的影响,想必大家都比较清楚,会读到脏数据,经典的就是银行转账例子,出现数据不一致的问题。而对于Fescar,如果没有采取任何其它技术手段,那会出现很严重的问题,比如:如上图所示,问最终全局事务A对资源R1应该回滚到哪种状态?很明显,如果再根据UndoLog去做回滚,就会发生严重问题:覆盖了全局事务B对资源R1的变更。那Fescar是如何解决这个问题呢?答案就是 Fescar的全局写排它锁解决方案,在全局事务A执行过程中全局事务B会因为获取不到全局锁而处于等待状态。对于Fescar的隔离级别,引用官方的一段话来作说明:全局事务的隔离性是建立在分支事务的本地隔离级别基础之上的。在数据库本地隔离级别 读已提交 或以上的前提下,Fescar 设计了由事务协调器维护的 全局写排他锁,来保证事务间的 写隔离,将全局事务默认定义在 读未提交 的隔离级别上。我们对隔离级别的共识是:绝大部分应用在 读已提交 的隔离级别下工作是没有问题的。而实际上,这当中又有绝大多数的应用场景,实际上工作在 读未提交 的隔离级别下同样没有问题。在极端场景下,应用如果需要达到全局的 读已提交,Fescar 也提供了相应的机制来达到目的。默认,Fescar 是工作在 读未提交 的隔离级别下,保证绝大多数场景的高效性。下面,本文将深入到源码层面对Fescar全局写排它锁实现方案进行解读。Fescar全局写排它锁实现方案在TC(Transaction Coordinator)模块维护,RM(Resource Manager)模块会在需要锁获取全局锁的地方请求TC模块以保证事务间的写隔离,下面就分成两个部分介绍:TC-全局写排它锁实现方案、RM-全局写排它锁使用一、TC—全局写排它锁实现方案首先看一下TC模块与外部交互的入口,下图是TC模块的main函数:上图中看出RpcServer处理通信协议相关逻辑,而对于TC模块真实处理器是DefaultCoordiantor,里面包含了所有TC对外暴露的功能,比如doGlobalBegin(全局事务创建)、doGlobalCommit(全局事务提交)、doGlobalRollback(全局事务回滚)、doBranchReport(分支事务状态上报)、doBranchRegister(分支事务注册)、doLockCheck(全局写排它锁校验)等,其中doBranchRegister、doLockCheck、doGlobalCommit就是全局写排它锁实现方案的入口。/*** 分支事务注册,在注册过程中会获取分支事务的全局锁资源*/@Overrideprotected void doBranchRegister(BranchRegisterRequest request, BranchRegisterResponse response, RpcContext rpcContext) throws TransactionException { response.setTransactionId(request.getTransactionId()); response.setBranchId(core.branchRegister(request.getBranchType(), request.getResourceId(), rpcContext.getClientId(), XID.generateXID(request.getTransactionId()), request.getLockKey()));}/*** 校验全局锁能否被获取到*/@Overrideprotected void doLockCheck(GlobalLockQueryRequest request, GlobalLockQueryResponse response, RpcContext rpcContext) throws TransactionException { response.setLockable(core.lockQuery(request.getBranchType(), request.getResourceId(), XID.generateXID(request.getTransactionId()), request.getLockKey()));}/*** 全局事务提交,会将全局事务下的所有分支事务的锁占用记录释放*/@Overrideprotected void doGlobalCommit(GlobalCommitRequest request, GlobalCommitResponse response, RpcContext rpcContext)throws TransactionException { response.setGlobalStatus(core.commit(XID.generateXID(request.getTransactionId())));}上述代码逻辑最后会被代理到DefualtCore去做执行如上图,不管是获取锁还是校验锁状态逻辑,最终都会被LockManger所接管,而LockManager的逻辑由DefaultLockManagerImpl实现,所有与全局写排它锁的设计都在DefaultLockManagerImpl中维护。首先,就先来看一下全局写排它锁的结构:private static final ConcurrentHashMap<String, ConcurrentHashMap<String, ConcurrentHashMap<Integer, Map<String, Long>>>> LOCK_MAP = new ConcurrentHashMap<~>();整体上,锁结构采用Map进行设计,前半段采用ConcurrentHashMap,后半段采用HashMap,最终其实就是做一个锁占用标记:在某个ResourceId(数据库源ID)上某个Tabel中的某个主键对应的行记录的全局写排它锁被哪个全局事务占用。下面,我们来看一下具体获取锁的源码:如上图注释,整个acquireLock逻辑还是很清晰的,对于分支事务需要的锁资源,要么是一次性全部成功获取,要么全部失败,不存在部分成功部分失败的情况。通过上面的解释,可能会有两个疑问:1. 为什么锁结构前半部分采用ConcurrentHashMap,后半部分采用HashMap?前半部分采用ConcurrentHashMap好理解:为了支持更好的并发处理;疑问的是后半部分为什么不直接采用ConcurrentHashMap,而采用HashMap呢?可能原因是因为后半部分需要去判断当前全局事务有没有占用PK对应的锁资源,是一个复合操作,即使采用ConcurrentHashMap还是避免不了要使用Synchronized加锁进行判断,还不如直接使用更轻量级的HashMap。2. 为什么BranchSession要存储持有的锁资源这个比较简单,在整个锁的结构中未体现分支事务占用了哪些锁记录,这样如果全局事务提交时,分支事务怎么去释放所占用的锁资源呢?所以在BranchSession保存了分支事务占用的锁资源。下图展示校验全局锁资源能否被获取逻辑:下图展示分支事务释放全局锁资源逻辑以上就是TC模块中全局写排它锁的实现原理:在分支事务注册时,RM会将当前分支事务所需要的锁资源一并传递过来,TC获取负责全局锁资源的获取(要么一次性全部成功,要么全部失败,不存在部分成功部分失败);在全局事务提交时,TC模块自动将全局事务下的所有分支事务持有的锁资源进行释放;同时,为减少全局写排它锁获取失败概率,TC模块对外暴露了校验锁资源能否被获取接口,RM模块可以在在适当位置加以校验,以减少分支事务注册时失败概率。二、RM-全局写排它锁使用在RM模块中,主要使用了TC模块全局锁的两个功能,一个是校验全局锁能否被获取,一个是分支事务注册去占用全局锁,全局锁释放跟RM无关,由TC模块在全局事务提交时自动释放。分支事务注册前,都会去做全局锁状态校验逻辑,以保证分支注册不会发生锁冲突。在执行Update、Insert、Delete语句时,都会在sql执行前后生成数据快照以组织成UndoLog,而生成快照的方式基本上都是采用Select…For Update形式,RM尝试校验全局锁能否被获取的逻辑就在执行该语句的执行器中:SelectForUpdateExecutor,具体如下图:基本逻辑如下:执行Select … For update语句,这样本地事务就占用了数据库对应行锁,其它本地事务由于无法抢占本地数据库行锁,进而也不会去抢占全局锁。循环掌握校验全局锁能否被获取,由于全局锁可能会被先于当前的全局事务获取,因此需要等之前的全局事务释放全局锁资源;如果这里校验能获取到全局锁,那么由于步骤1的原因,在当前本地事务结束前,其它本地事务是不会去获取全局锁的,进而保证了在当前本地事务提交前的分支事务注册不会因为全局锁冲突而失败。注:细心的同学可能会发现,对于Update、Delete语句对应的UpdateExecutor、DeleteExecutor中会因获取beforeImage而执行Select..For Update语句,进而会去校验全局锁资源状态,而对于Insert语句对应的InsertExecutor却没有相关全局锁校验逻辑,原因可能是:因为是Insert,那么对应插入行PK是新增的,全局锁资源必定未被占用,进而在本地事务提交前的分支事务注册时对应的全局锁资源肯定是能够获取得到的。接下来我们再来看看分支事务如何提交,对于分支事务中需要占用的全局锁资源如何生成和保存的。首先,在执行SQL完业务SQL后,会根据beforeImage和afterImage生成UndoLog,与此同时,当前本地事务所需要占用的全局锁资源标识也会一同生成,保存在ContentoionProxy的ConnectionContext中,如下图所示。在ContentoionProxy.commit中,分支事务注册时会将ConnectionProxy中的context内保存的需要占用的全局锁标识一同传递给TC进行全局锁的获取。以上,就是RM模块中对全局写排它锁的使用逻辑,因在真正执行获取全局锁资源前会去循环校验全局锁资源状态,保证在实际获取锁资源时不会因为锁冲突而失败,但这样其实坏处也很明显:在锁冲突比较严重时,会增加本地事务数据库锁占用时长,进而给业务接口带来一定的性能损耗。三、总结本文详细介绍了Fescar为在 读未提交 隔离级别下做到 写隔离 而实现的全局写排它锁,包括TC模块内的全局写排它锁的实现原理以及RM模块内如何对全局写排它锁的使用逻辑。在了解源码过程中,笔者也遗留了两个问题:1. 全局写排它锁数据结构保存在内存中,如果服务器重启/宕机了怎么办,即TC模块的高可用方案是什么呢?2. 一个Fescar管理的全局事务和一个非Fescar管理的本地事务之间发生锁冲突怎么办?具体问题如下图,问题是:全局事务A如何回滚?对于问题1有待继续研究;对于问题2目前已有答案,但Fescar目前暂未实现,具体就是全局事务A回滚时会报错,全局事务A内的分支事务A1回滚时会校验afterImage与当前表中对应行数据是否一致,如果一致才允许回滚,不一致则回滚失败并报警通知对应业务方,由业务方自行处理。参考Fescar官方介绍fescar锁设计和隔离级别的理解姊妹篇:分布式事务中间件TXC/Fescar—RM模块源码解读本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

February 27, 2019 · 1 min · jiezi

深度揭秘“蚂蚁双链通”

摘要: 目前,市场上基于区块链的供应链金融基本上是从应收账款切入的。蚂蚁区块链做到了应收账款确认、流转、融资、清分的全生命周期上链,资产的确权和流转以链上为准,而不是简单的业务存证。今年年初,蚂蚁金服ATEC城市峰会在上海举行。在ATEC区块链行业研讨会分论坛上,蚂蚁金服区块链高级产品专家杨俊带来了主题为《供应链金融,不止于金融:蚂蚁双链通——基于区块链的供应链协作网络》的精彩分享。区块链与应收流转目前,市场上基于区块链的供应链金融基本上是从应收账款切入的。应收账款是最容易数字化的资产,其业务模式比较简单:核心企业核心供应商进行应付账款确认,将应收账款拆分转让,再将应收账款转入金融机构进行融资,金融机构之间或金融机构与投资者间用ABS的方式再次进行资产的转让或融资。无论是合同转让、融资产品还是ABS产品,均为已经成为成熟的业务产品,区块链的使用让这个旧有的业务模式变得更加可靠并容易实施。蚂蚁金服做到了应收账款确认、流转、融资、清分的全生命周期上链,资产的确权和流转以链上为准,而不是简单的业务存证。区块链的以下四个特点使其在金融领域发挥出了极大价值:1.多方性:区块链无需依靠中心化系统即可帮助很多企业进行多方协作,多方是区块链中非常重要的特征。2.可信性:区块链模式的分布式特性、多方共识特性、不可篡改特性保证了整个数据资产的可信性。3.高效性:区块链通过可信的方式进行数字化,使其成为高效的可在线上完成的工作。4.智能性:在区块链上附加智能合约可编程的资产,通过资产的流转来实现管理上的优化及其他业务领域的创新。区块链技术虽然没有创造新的业务,但却依靠其特性为金融领域、供应链领域及更多相关领域创造出了新的价值。区块链模式对企业的价值对于企业而言区块链模式有如下的一些价值:1.对于核心企业而言,区块链模式可以很好的改善其子企业的资金使用效率。通过区块链模式核心企业可以用自身内部资产流转提升整个集团的资金使用效率,以此提升企业运作效率。对于额外有金融板块的公司来说,区块链技术可以为这样的公司带来额外的收益,为核心企业在财务领域创造不小的价值。2.通过区块链的方式,企业与供应商间可以建立密切的协作关系。蚂蚁金服的区块链技术可以做到对供应链进行多级分析并进行穿透式的管理,对于很多对供应链管控要求严格的行业是一个很大的助力。通过区块链技术可以在企业与供应商间更快更好地建立良性的协作与管理关系。3.通过区块链模式可以建立一种新的协作方式。供应链的中间渠道将变得更顺畅,很大程度上方便了核心企业的管理。对于原材料企业与上游大企业来说,在提升企业效率的同时,还可以降低企业的金融风险并把绩效上的融资业务做得更好。4.对于蚂蚁金服而言,通过区块链的方式可以让供应链上的小微企业得到更好的融资服务。除此以外,对于很多中型企业来说,通过区块链方式可以很好的协调管理应收应付这项复杂繁琐的工作。对于应收账款过多而造成很大经营风险的企业而言,通过区块链的方式可以得到信誉良好大企业的应收账款凭证,对于企业经营稳定性的提高有很大的帮助。对于需要融资的企业来说,通过区块链的方式可以使用应收凭证很便捷地在线进行融资。区块链模式探索中的难题对每个企业而言,多种多样的选择带来不同的价值与代价。企业构建区块链也是如此,企业在利用区块链方式构建自己生态的同时,往往被多种多样的可选择路径困扰。这时一个十分重要的考量标准为:企业技术上、风险管控上的投入与所建立的生态产出是否匹配。如上图所示为企业在选择发展自身区块链模式时的可选路径:1.对于加入其他企业构建的平台的企业,无法完全信任平台对其独立性、企业隐私的保证。2.加入金融机构所构建平台的企业,往往受到金融机构类型平台的限制,很难向除开金融以外的方向进一步发展。3.不同企业、合作方等多方协作构建的区块链联盟由于经验与技术的不足无法产生很好的协作关系。4.一些企业选择放弃发展区块链。5.技术选型上的困扰。区块链技术虽然很有发展,但目前尚处于不成熟的阶段,无论公司使用公链技术、联盟链技术或是传统的中心化技术,都会面临各自的问题。如下图所示,蚂蚁金服绘制了企业所关心的雷达图,图中将区块链的优劣分为以下几大考量维度:高可靠性合法合规性分布式的开放隐私安全可运营性 在上图所示的雷达图中,黄色线条代表的公链技术在分布式开放与高可信度方面做的很好,而在合法合规性、隐私与安全方面,公链模式还差的很多,一旦应用实名后在隐私方面将很难得到保障。在雷达图中的深蓝色线条代表了联盟链。传统的联盟链中,机构与机构之间,企业与企业之间完成相互的业务,合法合规性是很高的,高可信性也比较可观。在隐私安全方面联盟链远远强于公链,在可运营性方面同样表现不俗。而在分布式开放方面,传统的联盟链基本上仅为机构间的业务交流所用,分布式开放能力比较不足。蚂蚁金服双链通对于上述种种选择区块链时面临的难题,蚂蚁金服的新品牌双链通可以很好的进行解决,双链的涵义分别指区块链与供应链。如下图所示,双链通可分为联盟网络、企业运营服务、金融基础业务设施这三大部分:蚂蚁金服会与所有合作伙伴一起构建区块链的联盟网络。蚂蚁金服将作为这个联盟网络最主要的技术运营方及开发方。蚂蚁金服拥有完全自主可控的核心技术,可以解决很多开发中的问题及技术难点,并通过全球的技术网络无缝支持联盟网络的开发与运营。联盟网络作为企业级、金融级商业应用,安全性与隐私性将十分可靠。蚂蚁金服落地了全球第一个硬件级的隐私保护方案,将隐私保护从硬件层面做起,排除掉一切不安全的因素。在开放性方面,蚂蚁金服将会为企业级应用提供配套的基础设施以帮助合作伙伴成功构建起自己的业务。在应收账款及其他功能性领域,例如应收账款的确权和转让等业务,业务安全性要求高,风险很大,必须要用可靠的措施来完成。在这一准金融领域,蚂蚁金服拥有多年的沉淀与丰富的资源,可以为企业提供安全可靠、合法合规的融资与金融通道。在供应链的协作方面,如何让客户企业发现潜在目标,如何让客户企业真正获得价值是一大难题。蚂蚁金服在B类客户特别中小微企业上面,拥有多年合作经验并通过大量相关基础设施作为配套开发了符合这样中小公司利益的双通链网络。最核心的区块链联盟网络、金融业务的基础设施配套、面向企业运营服务能力的配套三者组合成为了蚂蚁金服的双链通服务,这是一个可以用多种方式组装的合作蓝图,一个由多方组成的协作网络。蚂蚁双链通未来的协作生态面向未来,蚂蚁金服计划构建区块链的协作生态。如下图所示,图的中央部分代表蚂蚁金服发起建设的基础设施,周围部分是蚂蚁金服的合作伙伴:1.核心企业是蚂蚁金服最重要的合作伙伴。在这些合作伙伴当中,不仅有与蚂蚁金服合作的核心企业,还可以包括其下属的财务公司、保理公司、信托公司甚至自有的银行等。这些下属公司也可以成为蚂蚁金服区块链的联盟成员,并把核心企业的业务进行打包。2.对于不具备自己开发业务系统的链上中小企业来说。蚂蚁金服提供了一个轻量的业务平台,帮助中小企业直接上链进行交易。蚂蚁金服有很高的网络公信力,同时也正在引入审计与监管机构的参与,值得中小企业的信赖。同时行业的组织也可成为行业联盟的成员之一。3.银行与金融机构均可成为区块链联盟成员,可以在平台上进行网银、身份核实、账务甚至资金结算等业务。对于更高层级的服务商、合作伙伴来说,依托核心能力,可以为蚂蚁金服合作的客户提供更多的供应链协作、供应链管理相关的能力。通过这种方式,双链通可以和所有的客户及合作伙伴建立一个良好的协作生态。本文作者:华蒙阅读原文本文为云栖社区原创内容,未经允许不得转载。

February 27, 2019 · 1 min · jiezi

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

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

February 25, 2019 · 3 min · jiezi

Nacos系列:Nacos的三种部署模式

三种部署模式Nacos支持三种部署模式1、单机模式:可用于测试和单机使用,生产环境切忌使用单机模式(满足不了高可用)2、集群模式:可用于生产环境,确保高可用3、多集群模式:可用于多数据中心场景单机模式启动 Nacos ServerLinux:sh startup.sh -m standaloneWindows:cmd startup.cmd -m standalone 或 双击 startup.cmd 启动关闭 Nacos ServerLinux:sh shutdown.shWindows:cmd shutdown.cmd 或 双击 shutdown.cmd 启动在0.7版本之前,Nacos使用的是嵌入式数据库Derby (Apache Derby)来存储数据;0.7版本,增加了对mysql数据源的支持。Derby数据源内嵌的数据库,通过命令直接启动即可,无需额外安装。startup.cmd -m standaloneMySQL数据源步骤一:安装MySQL数据,版本要求:5.6.5+步骤二:初始化数据库创建数据库create database if not exists nacos default charset utf8 collate utf8_general_ci;初始化数据库在nacos server解压目录conf下,找到 nacos-mysql.sql 文件,直接执行,执行完成后,用Navicat客户端查看步骤三:修改conf/application.properties文件,添加如下信息## mysql datasourcespring.datasource.platform=mysql db.num=1db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=truedb.user=rootdb.password=123456注意:spring.datasource.platform=mysql别漏了,要不然还是使用Derby数据库。步骤四:启动 Nacos Server启动成功后,我们使用上一篇博客:《Nacos系列:Nacos的Java SDK使用》中的案例来验证,运行NacosConfig,观察数据表的内容变化,示例知行后,config_info表和his_config_info表都会有和配置相关的数据,如下图所示:似乎Nacos的MySQL数据源只存储了配置数据,服务列表和注册的服务实例信息并不会出现在数据表中(通过运行示例中的NacosDiscovery类main()方法,就可以观察到该现象)集群模式资源有限,我直接在Windows上模拟部署搭建“伪集群”, 新建一个文件目录NacosCluster,将Nacos Server解压三份到该目录下,分别命名为nasosSlave0、nasosSlave1、nasosSlave2,分配端口:8845、8846、8847修改nasosSlave0/conf/application.properties,server.port=8845,并添加## mysql datasourcespring.datasource.platform=mysql db.num=1db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=truedb.user=rootdb.password=123456这里先使用一个MySQL库演示功能,实际生产上至少要使用主备模式,例如:db.num=2db.url.0=jdbc:mysql://127.0.0.1:3306/nacos1?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=truedb.url.1=jdbc:mysql://127.0.0.1:3306/nacos2?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=truedb.user=rootdb.password=123456拷贝nasosSlave0/conf/cluster.conf.example为cluster.conf,修改内容如下:127.0.0.1:8845127.0.0.1:8846127.0.0.1:8847nasosSlave1、nasosSlave2 和 nasosSlave0 除了端口不同外,其它配置保持一致。分别启动每台 Nacos Serverstartup.cmd -m cluster注意:在Windows下,这个时候不能再双击startup.cmd启动了,如果这样启动仍然是以单机模式运行,因为在bin/startup.cmd中有下面这段代码:if not “%2” == “cluster” ( set “JAVA_OPT=%JAVA_OPT% -Xms512m -Xmx512m -Xmn256m” set “JAVA_OPT=%JAVA_OPT% -Dnacos.standalone=true” ) else ( set “JAVA_OPT=%JAVA_OPT% -server -Xms2g -Xmx2g -Xmn1g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m” set “JAVA_OPT=%JAVA_OPT% -XX:-OmitStackTraceInFastThrow XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=%BASE_DIR%\logs\java_heapdump.hprof” set “JAVA_OPT=%JAVA_OPT% -XX:-UseLargePages” )观察控制台,启动成功后,在控制台可以看到如下信息:E:\Software\Nacos\nacosCluster\nacosSlave0\bin>startup.cmd -m cluster ,–. ,–.’| ,–,: : | Nacos 0.8.0,--.'| ’ : ,—. Running in cluster mode| : : | | ’ ,’\ .–.–. Port: 8845: | \ | : ,–.–. ,—. / / | / / ’ Pid: 6568| : ’ ‘; | / \ / . ; ,. :| : /./ Console: http://192.168.1.102:8845/nacos/index.html' ' ;. ;.--. .-. | / / '' | |: :| : ;_| | | \ | \__\/: . .. ' / ' | .; : \ \ . https://nacos.io’ : | ; .’ ," .–.; |’ ; :__| : | ----. \| | '–’ / / ,. |’ | ‘.’|\ \ / / /--' /' : | ; : .' \ : : —-’ ‘–’. /; |.’ | , .-./\ \ / --'---''---' –---' —-‘2019-02-20 23:06:13,059 INFO The server IP list of Nacos is [127.0.0.1:8845, 127.0.0.1:8846, 127.0.0.1:8847]2019-02-20 23:06:14,185 INFO Nacos is starting…2019-02-20 23:06:15,409 INFO Nacos is starting…2019-02-20 23:06:16,512 INFO Nacos is starting…2019-02-20 23:06:17,605 INFO Nacos is starting…2019-02-20 23:06:18,736 INFO Nacos is starting…2019-02-20 23:06:19,860 INFO Nacos is starting…2019-02-20 23:06:21,021 INFO Nacos is starting…2019-02-20 23:06:22,230 INFO Nacos is starting…2019-02-20 23:06:23,390 INFO Nacos is starting…2019-02-20 23:06:24,605 INFO Nacos is starting…2019-02-20 23:06:25,991 INFO Nacos is starting…2019-02-20 23:06:26,993 INFO Nacos is starting…2019-02-20 23:06:28,197 INFO Nacos is starting…2019-02-20 23:06:29,264 INFO Nacos is starting…2019-02-20 23:06:30,515 INFO Nacos is starting…2019-02-20 23:06:31,810 INFO Nacos is starting…2019-02-20 23:06:32,934 INFO Nacos is starting…2019-02-20 23:06:33,976 INFO Nacos is starting…2019-02-20 23:06:35,044 INFO Nacos is starting…2019-02-20 23:06:36,153 INFO Nacos is starting…2019-02-20 23:06:37,290 INFO Nacos is starting…2019-02-20 23:06:38,616 INFO Nacos is starting…2019-02-20 23:06:39,736 INFO Nacos is starting…2019-02-20 23:06:40,824 INFO Nacos is starting…2019-02-20 23:06:41,757 INFO Nacos Log files: E:\Software\Nacos\nacosCluster\nacosSlave0/logs/2019-02-20 23:06:41,768 INFO Nacos Conf files: E:\Software\Nacos\nacosCluster\nacosSlave0/conf/2019-02-20 23:06:41,771 INFO Nacos Data files: E:\Software\Nacos\nacosCluster\nacosSlave0/data/2019-02-20 23:06:41,774 INFO Nacos started successfully in cluster mode.在浏览器分别访问如下路径http://localhost:8845/nacoshttp://localhost:8846/nacoshttp://localhost:8847/nacos如果都能访问成功,证明集群模式部署成功。多集群模式Nacos支持NameServer路由请求模式,通过它您可以设计一个有用的映射规则来控制请求转发到相应的集群,在映射规则中您可以按命名空间或租户等分片请求(From Nacos官网)其他说明据Nacos官方宣称,Nacos v0.8.0 Pre-GA版本已经可以用于生产环境,在此之前的版本,请勿在生产上使用。参考资料部署手册集群部署说明推荐阅读Nacos系列:欢迎来到Nacos的世界!Nacos系列:基于Nacos的注册中心Nacos系列:基于Nacos的配置中心Nacos系列:Nacos的Java SDK使用 ...

February 23, 2019 · 2 min · jiezi

Nacos系列:Nacos的Java SDK使用

Maven依赖Nacos提供完整的Java SDK,便于配置管理和服务发现及管理,以 Nacos-0.8.0 版本为例添加Maven依赖:<dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> <version>0.8.0</version></dependency>仅仅引入nacos-client是不够的,否则启动时会出现如下错误:sun.misc.Launcher$AppClassLoader@18b4aac2 JM.Log:WARN Init JM logger with NopLoggerFactory, pay attention. sun.misc.Launcher$AppClassLoader@18b4aac2java.lang.ClassNotFoundException: org.apache.logging.log4j.core.Logger at java.net.URLClassLoader.findClass(URLClassLoader.java:381) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:264) at com.alibaba.nacos.client.logger.log4j2.Log4j2LoggerFactory.<init>(Log4j2LoggerFactory.java:33) at com.alibaba.nacos.client.logger.LoggerFactory.<clinit>(LoggerFactory.java:59) at com.alibaba.nacos.client.config.utils.LogUtils.<clinit>(LogUtils.java:49) at com.alibaba.nacos.client.config.NacosConfigService.<clinit>(NacosConfigService.java:55) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:264) at com.alibaba.nacos.api.config.ConfigFactory.createConfigService(ConfigFactory.java:40) at com.alibaba.nacos.api.config.ConfigFactory.createConfigService(ConfigFactory.java:59) at com.alibaba.nacos.api.NacosFactory.createConfigService(NacosFactory.java:52) at com.learn.nacos.config.NacosConfig.main(NacosConfig.java:12)根据错误提示,应该还需要添加log4j相关依赖,官网的文档并没有对此说明,我在pom.xml添加了下面这些依赖才不报错<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.1.11</version></dependency><dependency> <groupId>org.logback-extensions</groupId> <artifactId>logback-ext-spring</artifactId> <version>0.1.4</version></dependency><dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>1.7.25</version></dependency><dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version></dependency><dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.25</version></dependency>配置管理创建ConfigService,可以通过 NacosFactory.createConfigService() 或 ConfigFactory.createConfigService() 来创建,后者是前者的底层实现方式,这两种方式都包含如下两个方法:createConfigService(serverAddr)createConfigService(properties)创建示例:// 方式一String serverAddr = “127.0.0.1:8848”;ConfigService configService = ConfigFactory.createConfigService(serverAddr);// 方式二ConfigService configService = ConfigFactory.createConfigService(properties)Properties properties = new Properties();properties.put(“serverAddr”, serverAddr);查看ConfigService源码,它提供了如下方法:获取 Nacos Server 当前状态:String getServerStatus()底层源码:public String getServerStatus() { if (worker.isHealthServer()) { return “UP”; } else { return “DOWN”; }}根据源码注释,该状态应该是指 Nacos Server 的状态,我把 Nacos Server 关闭之后,再次运行示例,得到的结果仍然是UP,不知道这是不是一个BUG。发布配置:boolean publishConfig(String dataId, String group, String content) throws NacosException支持程序自动发布Nacos配置,创建和修改配置使用同一个方法,配置不存在则创建;配置已存在则更新。底层源码:try { result = agent.httpPost(url, headers, params, encode, POST_TIMEOUT);} catch (IOException ioe) { log.warn(“NACOS-0006”, LoggerHelper.getErrorCodeStr(“NACOS”, “NACOS-0006”, “环境问题”, “[publish-single] exception”)); log.warn(agent.getName(), “[publish-single] exception, dataId={}, group={}, msg={}”, dataId, group, ioe.toString()); return false;}发布配置后,如果马上用getConfig()读取配置,有时候会读不到,设置了足够的等待时长后才可保证每次正常读取,看了源码才知道Nacos的配置管理(发布、读取、移除)都是通过HTTP接口完成的,但发布配置的时延是多少,官网似乎没有说明?几秒钟的时延在一些对实时性要求很高的场景会不会存在影响呢?读取配置:String getConfig(String dataId, String group, long timeoutMs) throws NacosExceptiontimeoutMs指读取配置超时时间,官网推荐设置为3000ms底层源码:// 优先使用本地配置String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);if (content != null) { log.warn(agent.getName(), “[get-config] get failover ok, dataId={}, group={}, tenant={}, config={}”, dataId, group, tenant, ContentUtils.truncateContent(content)); cr.setContent(content); configFilterChainManager.doFilter(null, cr); content = cr.getContent(); return content;}try { content = worker.getServerConfig(dataId, group, tenant, timeoutMs); cr.setContent(content); configFilterChainManager.doFilter(null, cr); content = cr.getContent(); return content;} catch (NacosException ioe) { if (NacosException.NO_RIGHT == ioe.getErrCode()) { throw ioe; } log.warn(“NACOS-0003”, LoggerHelper.getErrorCodeStr(“NACOS”, “NACOS-0003”, “环境问题”, “get from server error”)); log.warn(agent.getName(), “[get-config] get from server error, dataId={}, group={}, tenant={}, msg={}”, dataId, group, tenant, ioe.toString());}从源码上看,配置会先从本地缓存文件读取,如果没读取到,才会去请求Nacos Server的配置,这个缓存文件在哪呢?就在当前用户的nacos目录下生成的缓存文件:nacos/config/fixed-127.0.0.1_8848_nacos/snapshot/DEFAULT_GROUP/nacos-sdk-java-config,配置内容和发布到Nacos Server的配置内容是一致的。移除配置:boolean removeConfig(String dataId, String group) throws NacosException支持程序自动发布Nacos配置,配置不存在时会直接返回成功,移除配置后,本地的缓存文件也会被删除底层源码:try { result = agent.httpDelete(url, null, params, encode, POST_TIMEOUT);} catch (IOException ioe) { log.warn("[remove] error, " + dataId + “, " + group + “, " + tenant + “, msg: " + ioe.toString()); return false;}移除配置同发布配置一样,如果移除后马上查询,有可能还能将刚移除的配置查出来,也存在一定的时延,需要设置等待时间读取。添加配置监听:void addListener(String dataId, String group, Listener listener) throws NacosException支持动态监听配置的变化,运行示例源码,在Nacos控制台把配置内容修改为sdk-java-config:change from nacos console,此时观看IDE控制台,你会看到如下打印信息:当前线程:com.alibaba.nacos.client.Worker.longPollingfixed-127.0.0.1_8848 ,监听到配置内容变化:sdk-java-config:change from nacos console移除配置监听:void removeListener(String dataId, String group, Listener listener)移除监听后,配置的变化不会再监听启动完整示例,运行结果如下,请注意配置监听线程和配置管理线程不是同一个线程当前线程:main ,服务状态:UP添加监听添加监听成功发布配置发布配置成功当前线程:com.alibaba.nacos.client.Worker.longPollingfixed-127.0.0.1_8848 ,监听到配置内容变化:nacos-sdk-java-config:init当前线程:main ,发布配置后获取配置内容:nacos-sdk-java-config:init重新发布配置重新发布配置成功当前线程:main ,重新发布配置后获取配置内容:sdk-java-config:update当前线程:com.alibaba.nacos.client.Worker.longPollingfixed-127.0.0.1_8848 ,监听到配置内容变化:sdk-java-config:update当前线程:com.alibaba.nacos.client.Worker.longPollingfixed-127.0.0.1_8848 ,监听到配置内容变化:sdk-java-config:change from nacos console移除配置移除配置成功当前线程:main ,移除配置后获取配置内容:null取消监听取消监听成功服务管理创建NamingService,可以通过 NacosFactory.createNamingService() 或 NamingFactory.createNamingService() 来创建,后者是前者的底层实现方式,这两种方式都包含如下两个方法:createNamingService(serverAddr)createNamingService(properties)创建示例:// 方式一String serverAddr = “127.0.0.1:8848”;NamingService namingService = NamingFactory.createNamingService(serverAddr);// 方式二NamingService namingService = NamingFactory.createNamingService(properties)Properties properties = new Properties();properties.put(“serverAddr”, serverAddr);查看NamingService类源码,它提供了如下方法:获取 Nacos Server 当前状态:String getServerStatus()注册服务实例:void registerInstance(多个参数)方式一:String serverIp = “127.0.0.1”;int serverPort = 8848;String serverAddr = serverIp + “:” + serverPort;String serviceName = “nacos-sdk-java-discovery”;NamingService namingService = NamingFactory.createNamingService(serverAddr);namingService.registerInstance(serviceName, serverIp, serverPort);方式二:Instance instance = new Instance();instance.setIp(serverIp);//IPinstance.setPort(serverPort);//端口instance.setServiceName(serviceName);//服务名instance.setEnabled(true);//true: 上线 false: 下线instance.setHealthy(healthy);//健康状态instance.setWeight(1.0);//权重instance.addMetadata(“nacos-sdk-java-discovery”, “true”);//元数据NamingService namingService = NamingFactory.createNamingService(serverAddr);namingService.registerInstance(serviceName, instance);注册后,本地会生成缓存文件1、在Nacos安装目录data目录下:data/naming/data/public/com.alibaba.nacos.naming.domains.meta.public##nacos-sdk-java-discovery2、当前用户的nacos目录下:/nacos/naming/public/failover/nacos-sdk-java-discovery3、当前用户的nacos目录下:/nacos/naming/public/nacos-sdk-java-discovery即使删除服务实例,上面三个缓存文件也不会被删除,Nacos控制台服务列表中该服务也还存在着(但服务实例数会变成0);删除Nacos控制台的该服务,安全目录data目录下的缓存文件会被删除,但当前用户的nacos目录下的文件不会被删除,这里面是什么机制,我暂时还没整明白,等后面整明白了再来补充。删除服务实例:void deregisterInstance(多个参数)获取所有服务实例:List<Instance> getAllInstances(多个参数)获取所有健康或不健康的服务实例:List<Instance> selectInstances(多个参数)随机获取一个健康实例(根据负载均衡算法):Instance selectOneHealthyInstance(多个参数)添加服务实例监听:void subscribe(多个参数)添加服务实例监听:void unsubscribe(多个参数)分页获取所有服务实例:ListView<String> getServicesOfServer(多个参数)获取所有监听的服务实例:List<ServiceInfo> getSubscribeServices()启动完整示例,运行结果如下,请注意服务实例监听线程和服务实例管理线程不是同一个线程当前线程:main ,服务状态:UP注册实例注册实例成功添加监听添加监听成功当前线程:main ,注册实例后获取所有实例:[{“clusterName”:“DEFAULT”,“enabled”:true,“instanceId”:“127.0.0.1#8848#DEFAULT#nacos-sdk-java-discovery”,“ip”:“127.0.0.1”,“metadata”:{},“port”:8848,“serviceName”:“nacos-sdk-java-discovery”,“valid”:true,“weight”:1.0}]当前线程:main ,注册实例后获取所有健康实例:[{“clusterName”:“DEFAULT”,“enabled”:true,“instanceId”:“127.0.0.1#8848#DEFAULT#nacos-sdk-java-discovery”,“ip”:“127.0.0.1”,“metadata”:{},“port”:8848,“serviceName”:“nacos-sdk-java-discovery”,“valid”:true,“weight”:1.0}]当前线程:com.alibaba.nacos.naming.client.listener ,监听到实例名称:nacos-sdk-java-discovery当前线程:com.alibaba.nacos.naming.client.listener ,监听到实例内容:[{“clusterName”:“DEFAULT”,“enabled”:true,“instanceId”:“127.0.0.1#8848#DEFAULT#nacos-sdk-java-discovery”,“ip”:“127.0.0.1”,“metadata”:{},“port”:8848,“serviceName”:“nacos-sdk-java-discovery”,“valid”:true,“weight”:1.0}]当前线程:main ,注册实例后获取一个健康实例:{“clusterName”:“DEFAULT”,“enabled”:true,“instanceId”:“127.0.0.1#8848#DEFAULT#nacos-sdk-java-discovery”,“ip”:“127.0.0.1”,“metadata”:{},“port”:8848,“serviceName”:“nacos-sdk-java-discovery”,“valid”:true,“weight”:1.0}当前线程:com.alibaba.nacos.naming.client.listener ,监听到实例名称:nacos-sdk-java-discovery当前线程:com.alibaba.nacos.naming.client.listener ,监听到实例内容:[{“clusterName”:“DEFAULT”,“enabled”:true,“instanceId”:“127.0.0.1#8848#DEFAULT#nacos-sdk-java-discovery”,“ip”:“127.0.0.1”,“metadata”:{“change”:“true;”},“port”:8848,“serviceName”:“nacos-sdk-java-discovery”,“valid”:true,“weight”:2.0}]取消监听取消监听成功删除实例删除实例成功Exception in thread “main” java.lang.IllegalStateException: no host to srv for serviceInfo: nacos-sdk-java-discovery at com.alibaba.nacos.client.naming.core.Balancer$RandomByWeight.selectAll(Balancer.java:45) at com.alibaba.nacos.client.naming.core.Balancer$RandomByWeight.selectHost(Balancer.java:53) at com.alibaba.nacos.client.naming.NacosNamingService.selectOneHealthyInstance(NacosNamingService.java:270) at com.alibaba.nacos.client.naming.NacosNamingService.selectOneHealthyInstance(NacosNamingService.java:263) at com.alibaba.nacos.client.naming.NacosNamingService.selectOneHealthyInstance(NacosNamingService.java:253) at com.learn.nacos.discovery.NacosDiscovery.main(NacosDiscovery.java:121)当前线程:main ,删除实例后获取所有实例:[]当前线程:main ,删除实例后获取所有健康实例:[]以上就是 Nacos Java SDK 配置管理和服务管理功能的介绍,请参考示例源码学习。示例源码项目:learn-nacos-sdk-java代码已上传至码云和Github上,欢迎下载学习GiteeGithub参考资料Nacos用户指南:Java的SDK推荐阅读Nacos系列:欢迎来到Nacos的世界!Nacos系列:基于Nacos的注册中心Nacos系列:基于Nacos的配置中心 ...

February 22, 2019 · 2 min · jiezi

Tensorflow源码解析2 -- 前后端连接的桥梁 - Session

1 Session概述Session是TensorFlow前后端连接的桥梁。用户利用session使得client能够与master的执行引擎建立连接,并通过session.run()来触发一次计算。它建立了一套上下文环境,封装了operation计算以及tensor求值的环境。session创建时,系统会分配一些资源,比如graph引用、要连接的计算引擎的名称等。故计算完毕后,需要使用session.close()关闭session,避免引起内存泄漏,特别是graph无法释放的问题。可以显式调用session.close(),或利用with上下文管理器,或者直接使用InteractiveSession。session之间采用共享graph的方式来提高运行效率。一个session只能运行一个graph实例,但一个graph可以运行在多个session中。一般情况下,创建session时如果不指定Graph实例,则会使用系统默认Graph。常见情况下,我们都是使用一个graph,即默认graph。当session创建时,不会重新创建graph实例,而是默认graph引用计数加1。当session close时,引用计数减1。只有引用计数为0时,graph才会被回收。这种graph共享的方式,大大减少了graph创建和回收的资源消耗,优化了TensorFlow运行效率。2 默认sessionop运算和tensor求值时,如果没有指定运行在哪个session中,则会运行在默认session中。通过session.as_default()可以将自己设置为默认session。但个人建议最好还是通过session.run(operator)和session.run(tensor)来进行op运算和tensor求值。operation.run()operation.run()等价于tf.get_default_session().run(operation)@tf_export(“Operation”)class Operation(object): # 通过operation.run()调用,进行operation计算 def run(self, feed_dict=None, session=None): _run_using_default_session(self, feed_dict, self.graph, session) def _run_using_default_session(operation, feed_dict, graph, session=None): # 没有指定session,则获取默认session if session is None: session = get_default_session() # 最终还是通过session.run()进行运行的。tf中任何运算,都是通过session来run的。 # 通过session来建立client和master的连接,并将graph发送给master,master再进行执行 session.run(operation, feed_dict)tensor.eval()tensor.eval()等价于tf.get_default_session().run(tensor), 如下@tf_export(“Tensor”)class Tensor(_TensorLike): # 通过tensor.eval()调用,进行tensor运算 def eval(self, feed_dict=None, session=None): return _eval_using_default_session(self, feed_dict, self.graph, session) def _eval_using_default_session(tensors, feed_dict, graph, session=None): # 如果没有指定session,则获取默认session if session is None: session = get_default_session() return session.run(tensors, feed_dict)默认session的管理tf通过运行时维护的session本地线程栈,来管理默认session。故不同的线程会有不同的默认session,默认session是线程作用域的。# session栈_default_session_stack = _DefaultStack()# 获取默认session的接口@tf_export(“get_default_session”)def get_default_session(): return _default_session_stack.get_default()# _DefaultStack默认session栈是线程相关的class _DefaultStack(threading.local): # 默认session栈的创建,其实就是一个list def init(self): super(_DefaultStack, self).init() self._enforce_nesting = True self.stack = [] # 获取默认session def get_default(self): return self.stack[-1] if len(self.stack) >= 1 else None3 前端Session类型session类图会话Session的UML类图如下分为两种类型,普通Session和交互式InteractiveSession。InteractiveSession和Session基本相同,区别在于InteractiveSession创建后,会将自己替换为默认session。使得之后operation.run()和tensor.eval()的执行通过这个默认session来进行。特别适合Python交互式环境。InteractiveSession自带with上下文管理器。它在创建时和关闭时会调用上下文管理器的enter和exit方法,从而进行资源的申请和释放,避免内存泄漏问题。这同样很适合Python交互式环境。Session和InteractiveSession的代码逻辑不多,主要逻辑均在其父类BaseSession中。主要代码如下@tf_export(‘Session’)class Session(BaseSession): def init(self, target=’’, graph=None, config=None): # session创建的主要逻辑都在其父类BaseSession中 super(Session, self).init(target, graph, config=config) self._default_graph_context_manager = None self._default_session_context_manager = None@tf_export(‘InteractiveSession’)class InteractiveSession(BaseSession): def init(self, target=’’, graph=None, config=None): self._explicitly_closed = False # 将自己设置为default session self.default_session = self.as_default() self.default_session.enforce_nesting = False # 自动调用上下文管理器的__enter()方法 self.default_session.enter() self.explicit_graph = graph def close(self): super(InteractiveSession, self).close() ## 省略无关代码 ## 自动调用上下文管理器的__exit()方法,避免内存泄漏 self._default_session.exit(None, None, None) self._default_session = NoneBaseSessionBaseSession基本包含了所有的会话实现逻辑。包括会话的整个生命周期,也就是创建 执行 关闭和销毁四个阶段。生命周期后面详细分析。BaseSession包含的主要成员变量有graph引用,序列化的graph_def, 要连接的tf引擎target,session配置信息config等。4 后端Session类型在后端master中,根据前端client调用tf.Session(target=’’, graph=None, config=None)时指定的target,来创建不同的Session。target为要连接的tf后端执行引擎,默认为空字符串。Session创建采用了抽象工厂模式,如果为空字符串,则创建本地DirectSession,如果以grpc://开头,则创建分布式GrpcSession。类图如下DirectSession只能利用本地设备,将任务创建到本地的CPU GPU上。而GrpcSession则可以利用远端分布式设备,将任务创建到其他机器的CPU GPU上,然后通过grpc协议进行通信。grpc协议是谷歌发明并开源的远程通信协议。5 Session生命周期Session作为前后端连接的桥梁,以及上下文运行环境,其生命周期尤其关键。大致分为4个阶段创建:通过tf.Session()创建session实例,进行系统资源分配,特别是graph引用计数加1运行:通过session.run()触发计算的执行,client会将整图graph传递给master,由master进行执行关闭:通过session.close()来关闭,会进行系统资源的回收,特别是graph引用计数减1.销毁:Python垃圾回收器进行GC时,调用session.del()进行回收。生命周期方法入口基本都在前端Python的BaseSession中,它会通过swig自动生成的函数符号映射关系,调用C层的实现。5.1 创建先从BaseSession类的init方法看起,只保留了主要代码。def init(self, target=’’, graph=None, config=None): # graph表示构建的图。TensorFlow的一个session会对应一个图。这个图包含了所有涉及到的算子 # graph如果没有设置(通常都不会设置),则使用默认graph if graph is None: self._graph = ops.get_default_graph() else: self._graph = graph self._opened = False self._closed = False self._current_version = 0 self._extend_lock = threading.Lock() # target为要连接的tf执行引擎 if target is not None: self._target = compat.as_bytes(target) else: self._target = None self._delete_lock = threading.Lock() self._dead_handles = [] # config为session的配置信息 if config is not None: self._config = config self._add_shapes = config.graph_options.infer_shapes else: self._config = None self.add_shapes = False self.created_with_new_api = ops.USE_C_API # 调用C层来创建session self.session = None opts = tf_session.TF_NewSessionOptions(target=self.target, config=config) self.session = tf_session.TF_NewSession(self.graph.c_graph, opts, status)BaseSession先进行成员变量的赋值,然后调用TF_NewSession来创建session。TF_NewSession()方法由swig自动生成,在bazel-bin/tensorflow/python/pywrap_tensorflow_internal.py中def TF_NewSession(graph, opts, status): return pywrap_tensorflow_internal.TF_NewSession(graph, opts, status)pywrap_tensorflow_internal包含了C层函数的符号表。在swig模块import时,会加载pywrap_tensorflow_internal.so动态链接库,从而得到符号表。在pywrap_tensorflow_internal.cc中,注册了供Python调用的函数的符号表,从而实现Python到C的函数映射和调用。// c++函数调用的符号表,Python通过它可以调用到C层代码。符号表和动态链接库由swig自动生成static PyMethodDef SwigMethods[] = { // .. 省略其他函数定义 // TF_NewSession的符号表,通过这个映射,Python中就可以调用到C层代码了。 { (char )“TF_NewSession”, _wrap_TF_NewSession, METH_VARARGS, NULL}, // … 省略其他函数定义}最终调用到c_api.c中的TF_NewSession()// TF_NewSession创建session的新实现,在C层后端代码中TF_Session TF_NewSession(TF_Graph* graph, const TF_SessionOptions* opt, TF_Status* status) { Session* session; // 创建session status->status = NewSession(opt->options, &session); if (status->status.ok()) { TF_Session* new_session = new TF_Session(session, graph); if (graph != nullptr) { // 采用了引用计数方式,多个session共享一个图实例,效率更高。 // session创建时,引用计数加1。session close时引用计数减1。引用计数为0时,graph才会被回收。 mutex_lock l(graph->mu); graph->sessions[new_session] = Status::OK(); } return new_session; } else { DCHECK_EQ(nullptr, session); return nullptr; }}session创建时,并创建graph,而是采用共享方式,只是引用计数加1了。这种方式减少了session创建和关闭时的资源消耗,提高了运行效率。NewSession()根据前端传递的target,使用sessionFactory创建对应的TensorFlow::Session实例。Status NewSession(const SessionOptions& options, Session** out_session) { SessionFactory* factory; const Status s = SessionFactory::GetFactory(options, &factory); // 通过sessionFactory创建多态的Session。本地session为DirectSession,分布式为GRPCSession out_session = factory->NewSession(options); if (!out_session) { return errors::Internal(“Failed to create session.”); } return Status::OK();}创建session采用了抽象工厂模式。根据client传递的target,来创建不同的session。如果target为空字符串,则创建本地DirectSession。如果以grpc://开头,则创建分布式GrpcSession。TensorFlow包含本地运行时和分布式运行时两种运行模式。下面来看DirectSessionFactory的NewSession()方法class DirectSessionFactory : public SessionFactory { public: Session NewSession(const SessionOptions& options) override { std::vector<Device> devices; // job在本地执行 const Status s = DeviceFactory::AddDevices( options, “/job:localhost/replica:0/task:0”, &devices); if (!s.ok()) { LOG(ERROR) << s; return nullptr; } DirectSession* session = new DirectSession(options, new DeviceMgr(devices), this); { mutex_lock l(sessions_lock); sessions.push_back(session); } return session; }GrpcSessionFactory的NewSession()方法就不详细分析了,它会将job任务创建在分布式设备上,各job通过grpc协议通信。5.2 运行通过session.run()可以启动graph的执行。入口在BaseSession的run()方法中, 同样只列出关键代码class BaseSession(SessionInterface): def run(self, fetches, feed_dict=None, options=None, run_metadata=None): # fetches可以为单个变量,或者数组,或者元组。它是图的一部分,可以是操作operation,也可以是数据tensor,或者他们的名字String # feed_dict为对应placeholder的实际训练数据,它的类型为字典 result = self.run(None, fetches, feed_dict, options_ptr,run_metadata_ptr) return result def run(self, handle, fetches, feed_dict, options, run_metadata): # 创建fetch处理器fetch_handler fetch_handler = FetchHandler( self.graph, fetches, feed_dict_tensor, feed_handles=feed_handles) # 经过不同类型的fetch_handler处理,得到最终的fetches和targets # targets为要执行的operation,fetches为要执行的tensor _ = self.update_with_movers(feed_dict_tensor, feed_map) final_fetches = fetch_handler.fetches() final_targets = fetch_handler.targets() # 开始运行 if final_fetches or final_targets or (handle and feed_dict_tensor): results = self.do_run(handle, final_targets, final_fetches, feed_dict_tensor, options, run_metadata) else: results = [] # 输出结果到results中 return fetch_handler.build_results(self, results) def do_run(self, handle, target_list, fetch_list, feed_dict, options, run_metadata): # 将要运行的operation添加到graph中 self.extend_graph() # 执行一次运行run,会调用底层C来实现 return tf_session.TF_SessionPRunSetup_wrapper( session, feed_list, fetch_list, target_list, status) # 将要运行的operation添加到graph中 def extend_graph(self): with self.extend_lock: if self.graph.version > self.current_version: # 生成graph_def对象,它是graph的序列化表示 graph_def, self.current_version = self.graph.as_graph_def( from_version=self.current_version, add_shapes=self.add_shapes) # 通过TF_ExtendGraph将序列化后的graph,也就是graph_def传递给后端 with errors.raise_exception_on_not_ok_status() as status: tf_session.TF_ExtendGraph(self.session, graph_def.SerializeToString(), status) self.opened = True逻辑还是十分复杂的,主要有一下几步入参处理,创建fetch处理器fetch_handler,得到最终要执行的operation和tensor对graph进行序列化,生成graph_def对象将序列化后的grap_def对象传递给后端master。通过后端master来run。我们分别来看extend和run。5.2.1 extend添加节点到graph中TF_ExtendGraph()会调用到c_api中,这个逻辑同样通过swig工具自动生成。下面看c_api.cc中的TF_ExtendGraph()方法// 增加节点到graph中,proto为序列化后的graphvoid TF_ExtendGraph(TF_DeprecatedSession* s, const void* proto, size_t proto_len, TF_Status* status) { GraphDef g; // 先将proto反序列化,得到client传递的graph,放入g中 if (!tensorflow::ParseProtoUnlimited(&g, proto, proto_len)) { status->status = InvalidArgument(“Invalid GraphDef”); return; } // 再调用session的extend方法。根据创建的不同session类型,多态调用不同方法。 status->status = s->session->Extend(g);}后端系统根据生成的Session类型,多态的调用Extend方法。如果是本地session,则调用DirectSession的Extend()方法。如果是分布式session,则调用GrpcSession的相关方法。下面来看GrpcSession的Extend方法。Status GrpcSession::Extend(const GraphDef& graph) { CallOptions call_options; call_options.SetTimeout(options.config.operation_timeout_in_ms()); return ExtendImpl(&call_options, graph);}Status GrpcSession::ExtendImpl(CallOptions* call_options, const GraphDef& graph) { bool handle_is_empty; { mutex_lock l(mu); handle_is_empty = handle.empty(); } if (handle_is_empty) { // 如果graph句柄为空,则表明graph还没有创建好,此时extend就等同于create return Create(graph); } mutex_lock l(mu); ExtendSessionRequest req; req.set_session_handle(handle); *req.mutable_graph_def() = graph; req.set_current_graph_version(current_graph_version); ExtendSessionResponse resp; // 调用底层实现,来添加节点到graph中 Status s = master->ExtendSession(call_options, &req, &resp); if (s.ok()) { current_graph_version = resp.new_graph_version(); } return s;}Extend()方法中要注意的一点是,如果是首次执行Extend(), 则要先调用Create()方法进行graph的注册。否则才是执行添加节点到graph中。5.2.2 run执行图的计算同样,Python通过swig自动生成的代码,来实现对C API的调用。C层实现在c_api.cc的TF_Run()中。// session.run()的C层实现void TF_Run(TF_DeprecatedSession* s, const TF_Buffer* run_options, // Input tensors,输入的数据tensor const char** c_input_names, TF_Tensor** c_inputs, int ninputs, // Output tensors,运行计算后输出的数据tensor const char** c_output_names, TF_Tensor** c_outputs, int noutputs, // Target nodes,要运行的节点 const char** c_target_oper_names, int ntargets, TF_Buffer* run_metadata, TF_Status* status) { // 省略一段代码 TF_Run_Helper(s->session, nullptr, run_options, input_pairs, output_names, c_outputs, target_oper_names, run_metadata, status);}// 真正的实现了session.run()static void TF_Run_Helper() { RunMetadata run_metadata_proto; // 调用不同的session实现类的run方法,来执行 result = session->Run(run_options_proto, input_pairs, output_tensor_names, target_oper_names, &outputs, &run_metadata_proto); // 省略代码}最终会调用创建的session来执行run方法。DirectSession和GrpcSession的Run()方法会有所不同。后面很复杂,就不接着分析了。5.3 关闭session通过session.close()来关闭session,释放相关资源,防止内存泄漏。class BaseSession(SessionInterface): def close(self): tf_session.TF_CloseSession(self.session, status)会调用到C API的TF_CloseSession()方法。void TF_CloseSession(TF_Session* s, TF_Status* status) { status->status = s->session->Close();}最终根据创建的session,多态的调用其Close()方法。同样分为DirectSession和GrpcSession两种。::tensorflow::Status DirectSession::Close() { cancellation_manager->StartCancel(); { mutex_lock l(closed_lock); if (closed) return ::tensorflow::Status::OK(); closed = true; } // 注销session if (factory != nullptr) factory->Deregister(this); return ::tensorflow::Status::OK();}DirectSessionFactory中的Deregister()方法如下void Deregister(const DirectSession* session) { mutex_lock l(sessions_lock); // 释放相关资源 sessions.erase(std::remove(sessions.begin(), sessions.end(), session), sessions.end()); }5.4 销毁sessionsession的销毁是由Python的GC自动执行的。python通过引用计数方法来判断是否回收对象。当对象的引用计数为0,且虚拟机触发了GC时,会调用对象的__del()方法来销毁对象。引用计数法有个很致命的问题,就是无法解决循环引用问题,故会存在内存泄漏。Java虚拟机采用了调用链分析的方式来决定哪些对象会被回收。class BaseSession(SessionInterface): def del(self): # 先close,防止用户没有调用close() try: self.close() # 再调用c api的TF_DeleteSession来销毁session if self.session is not None: try: status = c_api_util.ScopedTFStatus() if self.created_with_new_api: tf_session.TF_DeleteSession(self.session, status)c_api.cc中的相关逻辑如下void TF_DeleteSession(TF_Session* s, TF_Status* status) { status->status = Status::OK(); TF_Graph* const graph = s->graph; if (graph != nullptr) { graph->mu.lock(); graph->sessions.erase(s); // 如果graph的引用计数为0,也就是graph没有被任何session持有,则考虑销毁graph对象 const bool del = graph->delete_requested && graph->sessions.empty(); graph->mu.unlock(); // 销毁graph对象 if (del) delete graph; } // 销毁session和TF_Session delete s->session; delete s;}TF_DeleteSession()会判断graph的引用计数是否为0,如果为0,则会销毁graph。然后销毁session和TF_Session对象。通过Session实现类的析构函数,来销毁session,释放线程池Executor,资源管理器ResourceManager等资源。DirectSession::~DirectSession() { for (auto& it : partial_runs) { it.second.reset(nullptr); } // 释放线程池Executor for (auto& it : executors) { it.second.reset(); } for (auto d : device_mgr->ListDevices()) { d->op_segment()->RemoveHold(session_handle); } // 释放ResourceManager for (auto d : device_mgr->ListDevices()) { d->ClearResourceMgr(); } // 释放CancellationManager实例 functions.clear(); delete cancellation_manager; // 释放ThreadPool for (const auto& p_and_owned : thread_pools) { if (p_and_owned.second) delete p_and_owned.first; } execution_state.reset(nullptr); flib_def.reset(nullptr);}6 总结Session是TensorFlow的client和master连接的桥梁,client任何运算也是通过session来run。它是client端最重要的对象。在Python层和C++层,均有不同的session实现。session生命周期会经历四个阶段,create run close和del。四个阶段均由Python前端开始,最终调用到C层后端实现。由此也可以看到,TensorFlow框架的前后端分离和模块化设计是多么的精巧。本文作者:扬易阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

February 21, 2019 · 5 min · jiezi

开源分布式中间件 DBLE Server.xml 配置解析

DBLE是基于开源项目MyCat发展的企业级开源分布式中间件,适用于高并发及TB级海量数据处理场景;江湖人送外号 “MyCat Plus”;其简单稳定,持续维护,良好的社区环境和广大的群众基础使DBLE得到了社区的大力支持。DBLE项目介绍DBLE 官方项目: https://github.com/actiontech…如对源码有兴趣或者需要定制的功能的可以通过源码编译安装DBLE 下载地址:https://github.com/actiontech…DBLE 官方社区交流群:669663113DBLE的主要配置文件前两篇文章"DBLE Rule.xml 配置解析"、“DBLE Schema.xml 配置解析” 分别介绍DBLE中Rule.xml和Schema.xml的配置,今天继续介绍DBLE中Server.xml文件的配置,希望通过本篇的介绍,能对Server.xml文件的整体结构和内容有较为清晰的认识,方便大家能快速地入门。DBLE的配置文件都在conf目录里面,常用的几个配置文件如下:DBLE是一个JAVA分库分表中间件,既然是JAVA应用肯定会有JVM相关的配置,在DBLE中我们选择wrapper来作为管理DBLE进程的工具,配置文件在conf/wrapper.conf中,关于JVM的详细介绍在wrapp.conf。Server.xml配置解析Server.xml是DBLE的重要配置文件之一, 整体配置可以分为三段,<system>段,<user>段和<firewall>段,分别定义了DBLE软件的相关配置,用户配置和针对用户的权限控制功能。<dble:server> <system></system> <user></user> <firewall> <whitehost></whitehost> </firewall></dble:server>关于配置文件,如果在DBLE运行途中修改了配置,要想配置动态生效,只需要使用reload @@config命令,但是需要提醒的是reload @@config对Server.mxl中的<system>段中的内容无法生效,即如果修改了server.xml的system的配置,需要重新启动DBLE使其生效 ,更多关于DBLE管理命令请参考DBLE管理端命令同样以思维导图的方式归纳如下,其中个别参数配置只是粗略地将最重要的参数罗列出来,详细的参数还请参考官方文档。system配置DBLE通过Server.xml配置文件来定义相关管理行为,如监听端口,sql统计和控制连接行为等功能,DBLE除了支持分库分表功能外,还实现了类似MySQL的慢查询日志功能,并且支持使用pt-query-digest这样的工具进行慢查询SQL分析,极大地方便了DBA的SQL性能分析与问题SQL定位,以下简单列出了一些最重要的也是最基础的功能配置。用户配置用户配置在<user>段进行配置,因为在Schema.xml中允许多个schema的存在,因此业务用户也是允许多个用户同时存在,并且还可以给这些用户进行更小粒度的权限划分。以实际场景来举例,比如当前配置了两个逻辑库adv和motor,分别是汽车和广告业务,这两个库直接没有任何的关联,因此需要分别配置两个用户来使用这两个schema,一个是adv_user,另一个是motor_user,这两个用户登录上去DBLE能看到只有自己的schema,其他schema不可见,两个用户的配置如下:<user name=“adv_user”> <property name=“password”>adv_user</property> <property name=“schemas”>adv</property> <property name=“readOnly”>false</property> </user> <user name=“motor_user”> <property name=“password”>motor_user</property> <property name=“schemas”>motor</property> <property name=“readOnly”>false</property> </user>但是你可能会想万一这两个库之中的表有关联查询呢?对应场景是:我们需要有个用户叫adv_motor_user,它对motor和adv库都需要有权限访问,别担心,DBLE提供了对单个用户可以访问多个schema的配置方式,我们可以在schemas中指定多个schema,之间用逗号分隔,配置生效后使用这个用户就能登录看到两个schema。<user name=“adv_motor_user”> <property name=“password”>adv_motor_user</property> <property name=“schemas”>motor,adv</property> <!–多个逻辑库之间使用逗号分隔,这些逻辑库必须在Schema.xml中定义–> <property name=“readOnly”>false</property> <!–用来做只读用户–> </user>你可能还会想,这样的权限粒度依然不够细,我们需要更细粒度的权限控制,比如需要adv_user的权限仅限于增删改查权限,即需要将权限细分到dml语句, DBLE仍然提供这样的配置,我们可以继续增加privileges的配置,如下图示:<user name=“adv_user”> <property name=“password”>adv_user</property> <property name=“schemas”>adv</property> <property name=“readOnly”>false</property> <privileges check=“true”> <schema name=“adv” dml=“1110” > <!– 默认库中所有逻辑表的继承权限 –> <table name=“tb01” dml=“1111”></table> <!– 单独指定表权限 –> </schema> </privileges></user>privileges的check参数作用于是否对用户权限进行检查,默认是不检查,dml的权限是分别是INSERT UPDATE SELECT DELETE四种权限,用4个数字0或1的组合来表示是否开启, 1表示开启,0表示关闭。在上图中, adv_user对adv库中所有表的默认权限是'1110’, 即只有insert, update和select权限, 即如果没有像tb01那样详细地列出来的情况,所有表的权限继承权限1110,上面例子中,ta01作为特殊指定的逻辑表的权限,adv_user对该表的权限是1111。我们可以看到DBLE可以将权限划分到DML,并且是可以精确到某一逻辑表级别,对于只需要DML权限的场景是足够了,但是这种权限控制还是很粗略,比如如果想让adv_user除了有dml权限之外,还需要有drop table权限,这时候在用DBLE的privileges权限控制就显得无能为力了,因此可行的建议是:控制连接后端MySQL的用户的权限,将DBLE侧的权限完全放开,换句话说只严格限制schema.xml的writehost中配置的连接用户的权限。 使用后面要讲的黑名单提供的权限控制。黑白名单配置针对上的权限粒度略显粗略的限制(事实上大多数场景下DML的权限也已经足够了),DBLE提供黑白名单的功能,白名单就是只允许白名单的连接,而黑名单则是详细的针对已经通过白名单的连接的权限控制,黑名单更类似于SQL审计, 黑名单配置以firewall分隔开来, 典型配置如下:<firewall> <whitehost> <host host=“127.0.0.1” user=“adv_user”/> <host host=“127.0.0.1” user=“admin”/> </whitehost> <blacklist check=“true”><property name=“selelctAllow”>false</property> <!– 允许select–><property name=“insertAllow”>false</property> <!– 允许insert–><property name=“updateAllow”>false</property>…… </blacklist></firewall>对于黑白名单,需要注意:DBLE针对来源IP和用户名进行限制,放行白名单连接,对于不在白名单列之中的连接,统统拒绝而无法登陆。白名单中的来源ip,只能指定固定IP, 暂不支持MySQL “%‘‘类似的ip通配符。 想象一种场景,需要像MySQL一样指定一个ip范围能允许连接DBLE,这时只能一行一行增加允许ip了,此处略显笨拙。如果配置了黑名单,则再根据黑名单中的权限property来进一步限制白名单中的用户权限,例如是否允许查询,是否允许INSERT,是否允许UPDATE。可以看出DBLE中黑名单更像是对用户的权限审计,DBLE在黑名单中将上面只能粗糙地限制到DML权限的用户配置做了较多较细的扩展,这样权限粒度更小,从实际场景来说,这更符合生产需要,由此我们可以针对性地去掉一些危险的SQL。总结本文简单介绍了Server.xml中的三个重要的配置段落,分别是DBLE的系统配置,用户配置以及黑白名单功能,针对用户配置则介绍了实际应用场景下的配置以及对应的DML权限配置,并详细介绍了DBLE黑白名单配置实践。往期精选| 社区投稿DBLE和Mycat跨分片查询结果不一致案例分析DBLE 自定义拆分算法DBLE rule.xml 配置解析| 使用指南开源分布式中间件 DBLE 快速入门指南DBLE Schema.xml 配置解析| 社区活动如何获取全国 25场 MySQL 主题大会免费入场券 ...

February 20, 2019 · 1 min · jiezi

Tensorflow源码解析1 -- 内核架构和源码结构

1 主流深度学习框架对比当今的软件开发基本都是分层化和模块化的,应用层开发会基于框架层。比如开发Linux Driver会基于Linux kernel,开发Android app会基于Android Framework。深度学习也不例外,框架层为上层模型开发提供了强大的多语言接口、稳定的运行时、高效的算子,以及完备的通信层和设备层管理层。因此,各大公司早早的就开始了深度学习框架的研发,以便能占领市场。当前的框架有数十种之多,主流的如下(截止到2018年11月)显然TensorFlow是独一无二的王者。第二名Keras,它是对TensorFlow或Theano接口的二次封装,严格意义上并不是一个独立的深度学习框架。TensorFlow目前也已经集成了Keras,使得安装了TensorFlow的用户就可以直接使用Keras了。TensorFlow之所以能够从数十种框架中脱颖而出,主要优点有出身高贵,是谷歌出品的。但其他很多框架出身也不差,例如PyTorch之于Facebook,MXNET之于Amazon2015年就开源了,比较早的俘获了一大批开发者。这个确实是tf的一大先发优势,但PyTorch的前身Caffe,以及MXNET开源时间都不晚,而且Caffe流行时间比tf早,后来才被赶超的。更有Theano这样的绝对老前辈。由此可见,软件开源是多么重要。目前流行的深度学习框架也基本都开源了。支持的开发语言多,支持Python Java Go C++等多种流行语言。相比某些框架,确实是优势很大。相比MXNET则小巫见大巫了。MXNET早期发展的一个主要方向就是前端多语言的支持,连MATLAB R Julia等语言都支持了。运行效率高。早期的时候,其实tf的运行效率比很多框架都要低一些的。安装容易,用户上手快,文档齐全,社区活跃。这个是tf的一个较大优势,特别是社区方面,也就是我们常说的生态优势。互联网头部集中效应十分明显,体现在开源软件上也是一样。这也是我认为最大的一个优势。总结起来,TensorFlow虽然每个方面都不是绝对领先的优势,但贵在每个方面都做的不错,因此最终能够一骑绝尘,独领风骚。学习Tensorflow框架内核,可以理解前端接口语言的支持,session生命周期,graph的构建、分裂和执行,operation的注册和运行,模块间数据通信,本地运行和分布式运行模式,以及CPU GPU TPU等异构设备的封装支持等。学习这些,对于模型的压缩 加速 优化等都是大有裨益的。2 TensorFlow系统架构TensorFlow设计十分精巧,基于分层和模块化的设计思想进行开发的。框架如下图整个框架以C API为界,分为前端和后端两大部分。前端:提供编程模型,多语言的接口支持,比如Python Java C++等。通过C API建立前后端的连接,后面详细讲解。后端:提供运行环境,完成计算图的执行。进一步分为4层运行时:分为分布式运行时和本地运行时,负责计算图的接收,构造,编排等。计算层:提供各op算子的内核实现,例如conv2d, relu等通信层:实现组件间数据通信,基于GRPC和RDMA两种通信方式设备层:提供多种异构设备的支持,如CPU GPU TPU FPGA等模型构造和执行流程TensorFlow的一大特点是,图的构造和执行相分离。用户添加完算子,构建好整图后,才开始进行训练和执行,也就是图的执行。大体流程如下图构建:用户在client中基于TensorFlow的多语言编程接口,添加算子,完成计算图的构造。图传递:client开启session,通过它建立和master之间的连接。执行session.run()时,将构造好的graph序列化为graphDef后,以protobuf的格式传递给master。图剪枝:master根据session.run()传递的fetches和feeds列表,反向遍历全图full graph,实施剪枝,得到最小依赖子图图分裂:master将最小子图分裂为多个Graph Partition,并注册到多个worker上。一个worker对应一个Graph Partition。图二次分裂:worker根据当前可用硬件资源,如CPU GPU,将Graph Partition按照op算子设备约束规范(例如tf.device(’/cpu:0’),二次分裂到不同设备上。每个计算设备对应一个Graph Partition。图运行:对于每一个计算设备,worker依照op在kernel中的实现,完成op的运算。设备间数据通信可以使用send/recv节点,而worker间通信,则使用GRPC或RDMA协议。3 前端多语言实现 - swig包装器TensorFlow提供了很多种语言的前端接口,使得用户可以通过多种语言来完成模型的训练和推断。其中Python支持得最好。这也是TensorFlow之所以受欢迎的一大原因。前端多语言是怎么实现的呢?这要归功于swig包装器。swig是个帮助使用C或者C++编写的软件能与其它各种高级编程语言进行嵌入联接的开发工具。在TensorFlow使用bazel编译时,swig会生成两个wrapper文件pywrap_tensorflow_internal.py:对接上层Python调用pywrap_tensorflow_internal.cc:对接底层C API调用。pywrap_tensorflow_internal.py 模块被导入时,会加载_pywrap_tensorflow_internal.so动态链接库,它里面包含了所有运行时接口的符号。而pywrap_tensorflow_internal.cc中,则注册了一个函数符号表,实现Python接口和C接口的映射。运行时,就可以通过映射表,找到Python接口在C层的实现了。4 tensorflow 源码结构TensorFlow源码基本也是按照框架分层来组织文件的。如下其中core为tf的核心,它的源码结构如下5 总结TensorFlow框架设计精巧,代码量也很大,我们可以从以下部分逐步学习TensorFlow内核架构和源码结构。先从全局上对框架进行理解。前后端连接的桥梁–Session,重点理解session的生命周期,并通过相关源码可以加深理解Python前端如何调用底层C实现。TensorFlow核心对象—Graph。图graph是TensorFlow最核心的对象,基本都是围绕着它来进行的。graph的节点为算子operation,边为数据tensor。TensorFlow图的节点 – Operation。operation是图graph的节点,承载了计算算子。TensorFlow图的边 – Tensor。Tensor是图graph的边,承载了计算的数据。TensorFlow本地运行时。TensorFlow分布式运行时。和本地运行时有一些共用的接口,但区别也很大。TensorFlow设备层。主要了解设备层的定义规范,以及实现。TensorFlow队列和并行运算。TensorFlow断点检查checkpoint,模型保存Saver,以及可视化tensorboard。这三个为TensorFlow主要的工具。本文作者:扬易阅读原文本文为云栖社区原创内容,未经允许不得转载。

February 20, 2019 · 1 min · jiezi

Perseus-BERT——业内性能极致优化的BERT训练方案【阿里云弹性人工智能】

一,背景——横空出世的BERT全面超越人类2018年在自然语言处理(NLP)领域最具爆炸性的一朵“蘑菇云”莫过于Google Research提出的BERT(Bidirectional Encoder Representations from Transformers)模型。作为一种新型的语言表示模型,BERT以“摧枯拉朽”之势横扫包括语言问答、理解、预测等各项NLP锦标的桂冠,见图1和图2。【图1】SQuAD是基于Wikipedia文章的标准问答数据库的NLP锦标。目前SQuAD2.0排名前十名均为基于BERT的模型(图中列出前五名),前20名有16席均是出自BERT 【图2】GLUE是一项通用语言理解评估的benchmark,包含11项NLP任务。BERT自诞生日起长期压倒性霸占榜首(目前BERT排名第二,第一为Microsoft提交的BIGBIRD模型,由于没有URL链接无从知晓模型细节,网传BIGBIRD的名称上有借鉴BERT BIG模型之嫌) 业内将BERT在自然语言处理的地位比作ResNet之于计算机视觉领域的里程碑地位。在BERT横空出世之后,所有的自然语言处理任务都可以基于BERT模型为基础展开。一言以蔽之,现如今,作为NLP的研究者,如果不了解BERT,那就是落后的科技工作者;作为以自然语言处理为重要依托的科技公司,如果不落地BERT,那就是落后生产力的代表。二,痛点——算力成为BERT落地的拦路虎BERT强大的原因在哪里?让我们拂去云霭,窥探下硝烟下的奥秘。BERT模型分为预训练模型(Pretrain)和精调模型(Finetune)。Pretrain模型为通用的语言模型。Finetune只需要在Pretrain的基础上增加一层适配层就可以服务于从问答到语言推理等各类任务,无需为具体任务修改整体模型架构,如图3所示。这种设计方便BERT预处理模型适配于各类具体NLP模型(类似于CV领域基于ImageNet训练的各种Backbone模型)。【图3】左图基于BERT pretrain的模型用于语句问答任务(SQuAD)的finetune模型,右图为用于句对分类(Sentence Pair Classification Tasks)的finetune模型。他们均是在BERT Pretrain模型的基础上增加了一层具体任务的适配层因此,BERT的强大主要归功于精确度和鲁棒性俱佳的Pretrain语言模型。大部分的计算量也出自Pretrain模型。其主要运用了以下两项技术,都是极其耗费计算资源的模块。1. 双向Transformer架构图4可见,与其他pre-training的模型架构不同,BERT从左到右和从右到左地同时对语料进行transformer处理。这种双向技术能充分提取语料的时域相关性,但同时也大大增加了计算资源的负担。【关于Transformer是Google 17年在NLP上的大作,其用全Attention机制取代NLP常用的RNN及其变体LSTM等的常用架构,大大改善了NLP的预测准确度。本文不展开,该兴趣的同学可以自行搜索一下】。【图4】Pretrain架构对比。其中OpenAI GPT采用从左到右的Transformer架构,ELMo采用部分从左到右和部分从右到左的LSTM的级联方式。BERT采用同时从左到右和从右到左的双向Transformer架构。1. 词/句双任务随机预测BERT预训练模型在迭代计算中会同时进行单词预测和语句预测两项非监督预测任务。其一,单词预测任务对语料进行随机MASK操作(Masked LM)。在所有语料中随机选取15%的单词作为Mask数据。被选中Mask的语料单词在迭代计算过程中80%时间会被掩码覆盖用于预测、10%时间保持不变、10%时间随机替换为其他单词,如图5所示。其二,语句预测任务(Next Sentence Prediction)。对选中的前后句A和B,在整个迭代预测过程中,50%的时间B作为A的真实后续语句(Label=IsNext),另外50%的时间则从语料库里随机选取其他语句作为A的后续语句(Label=NotNext),如图5所示【图5】词/句双任务随机预测输入语料实例。蓝框和红框为同一个语料输入在不同时刻的随机状态。对单词预测任务,蓝框中的“went”为真实数据,到了红框则被[MASK],红框中的“the” 则相反;对于语句预测任务,蓝框中的句组为真实的前后句,而红框中的句组则为随机的组合。这种随机选取的单词/语句预测方式在功能上实现了非监督数据的输入的功能,有效防止模型的过拟合。但是按比例随机选取需要大大增加对语料库的迭代次数才能消化所有的语料数据,这给计算资源带来了极大的压力。综上,BERT预处理模型功能需要建立在极强的计算力基础之上。BERT论文显示,训练BERT BASE 预训练模型(L=12, H=768, A=12, Total Parameters=110M, 1000,000次迭代)需要1台Cloud TPU工作16天;而作为目前深度学习主流的Nvidia GPU加速卡面对如此海量的计算量更是力不从心。即使是目前主流最强劲的Nvidia V100加速卡,训练一个BERT-Base Pretrain模型需要一两个月的时间。而训练Large模型,需要花至少四五个月的时间。花几个月训练一个模型,对于绝大部分在GPU上训练BERT的用户来说真是伤不起。三,救星——擎天云加速框架为BERT披荆斩棘阿里云弹性人工智能团队依托阿里云强大的基础设施资源打磨业内极具竞争力的人工智能创新方案。基于BERT的训练痛点,团队打造了擎天优化版的Perseus-BERT, 极大地提升了BERT pretrain模型的训练速度。在云上一台V100 8卡实例上,只需4天不到即可训练一份BERT模型。Perseus-BERT是如何打造云上最佳的BERT训练实践?以下干货为您揭秘Perseus-BERT的独门绝技。1. Perseus 统一分布式通信框架 —— 赋予BERT分布式训练的轻功Perseus(擎天)统一分布式通信框架是团队针对人工智能云端训练的痛点,针对阿里云基础设施极致优化的分布式训练框架。其可轻便地嵌入主流人工智能框架的单机训练代码,在保证训练精度的同时高效地提升训练的多机扩展性。擎天分布式框架的干货介绍详见团队另一篇文章《Perseus(擎天):统一深度学习分布式通信框架》。针对tensorflow代码的BERT,Perseus提供horovod的python api方便嵌入BERT预训练代码。基本流程如下:让每块GPU对应一个Perseus rank进程;对global step和warmup step做基于rank数的校准;对训练数据根据rank-id做划分;给Optimizer增加DistributeOptimizer的wrapper。值得注意的是,BERT源码用的自定义的Optimizer,在计算梯度时采用了以下apigrads = tf.gradients(loss, tvars)Perseus的DistributeOptimizer继承标准的Optimizer实现,并在compute_gradients api 上实现分布式的梯度更新计算。因此对grads获取做了如下微调grads_and_vars = optimizer.compute_gradients(loss, tvars)grads = list()for grad, var in grads_and_vars: grads.append(grad)2. 混合精度训练和XLA编译优化——提升BERT单机性能的内功混合精度在深度学习中,混合精度训练指的是float32和float16混合的训练方式,一般的混合精度模式如图6所示【图6】混合精度训练示例。在Forward+Backward计算过程中用float16做计算,在梯度更新时转换为float32做梯度更新。混合梯度对Bert训练带来如下好处,增大训练时的batch size和sequence_size以保证模型训练的精度。 目前阿里云上提供的主流的Nvidia显卡的显存最大为16GB,对一个BERT-Base模型在float32模式只能最高设置为sequence_size=256,batch_size=26。BERT的随机预测模型设计对sequence_size和batch_size的大小有一定要求。为保证匹配BERT的原生训练精度,需要保证sequece_size=512的情况下batch_size不小于16。Float16的混合精度可以保证如上需求。混合精度能充分利用硬件的加速资源。 NVidia从Volta架构开始增加了Tensor Core资源,这是专门做4x4矩阵乘法的fp16/fp32混合精度的ASIC加速器,一块V100能提供125T的Tensor Core计算能力,只有在混合精度下计算才能利用上这一块强大的算力。 受限于float16的表示精度,混合精度训练的代码需要额外的编写,NVidia提供了在Tensorflow下做混合精度训练的教程 。其主要思路是通过tf.variable_scope的custom_getter 参数保证存储的参数为float32并用float16做计算。 在BERT预训练模型中,为了保证训练的精度,Perseus-BERT没有简单的利用custom_getter参数,而是显式指定训地参数中哪些可以利用float16不会影响精度,哪些必须用float32已保证精度。我们的经验如下:Embedding部分要保证float32精度;Attetion部分可以利用float16加速;Gradients相关的更新和验证需要保证float32精度;非线性激活等模块需要保证float32精度。XLA编译器优化XLA是Tensorflow新近提出的模型编译器,其可以将Graph编译成IR表示,Fuse冗余Ops,并对Ops做了性能优化、适配硬件资源。然而官方的Tensorflow release并不支持xla的分布式训练,为了保证分布式训练可以正常进行和精度,我们自己编译了带有额外patch的tensorflow来支持分布式训练,Perseus-BERT 通过启用XLA编译优化加速训练过程并增加了Batch size大小。3. 数据集预处理的加速Perseus BERT 同时对文本预处理做的word embedding和语句划分做了并行化的优化。这里就不展开说明。四,性能——计算时间单位从月降低到天图7展示了Perseus BERT在P100实例上的性能,与开源主流的horovod相比,Peseus-BERT双机16卡的分布式性能是前者的5倍之多。目前某大客户已在阿里云P100集群上大规模上线了Perseus BERT,用10台4卡P100只需要2.5天即可训练完成业务模型,如果用开源的horovod(Tensorflow分布式性能优化版)大概需要1个月的时间。【图7】Bert在阿里云上P100实例的对比(实验环境Bert on P100; Batch size: 22 ;Max seq length: 256 ;Data type:float32; Tensorflow 1.12; Perseus: 0.9.1;Horovod: 0.15.2)为了和Google TPU做对比,我们量化了TPU的性能,性能依据如图8。一个Cloud TPU可计算的BERT-Base性能 256 (1000000/4/4/24/60/60) = 185 exmaples/s。 而一台阿里云上的V100 单机八卡实例在相同的sequence_size=512下, 通过Perseus-BERT优化的Base模型训练可以做到 680 examples/s,接近一台Cloud TPU的4倍性能。对一台Cloud TPU花费16天才能训练完的BERT模型,一台阿里云的V100 8卡实例只需要4天不到便可训练完毕。【图8】BERT Pretain在Google Cloud TPU上的性能依据五,总结——基于阿里云基础设施的AI极致性能优化弹性人工智能团队一直致力基于阿里云基础设施的AI极致性能优化的创新方案。Perseus-BERT就是一个非常典型的案例,我们在框架层面上基于阿里云的基础设施做深度优化,充分释放阿里云上基础资源的计算能力,让阿里云的客户充分享受云上的AI计算优势,让天下没有难算的AI。本文作者:笋江阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

February 19, 2019 · 1 min · jiezi

Nacos系列:基于Nacos的注册中心

前言所谓注册中心,其实是分布式架构演进过程中的产物,在系统中充当一个协调者的角色。但是,为什么需要这样一个协调者的角色呢?我们先来看一个例子,以便理解为什么分布式架构中需要有注册中心。案例小明和小新住在同一家沃尔玛超市附近,他俩都办了会员,经常关注超市的一些优惠活动,元宵节快到了,沃尔玛准备搞一个元宵节特惠活动,需要通知到附近的住户。对于沃尔玛来说,可以安排工作人员电话通知到小明和小新;而对于小明和小新来说,可以去超市咨询相关信息。那么问题来了,住在超市附近的,不只有小明和小新两个消费者,如果每个人都打电话去通知就显得太麻烦了,小明和小新提前在超市了解了相关信息,可是不巧的是,由于各种原因,沃尔玛元宵特惠活动要从上午改到下午才开始,他们又该从何得知呢?其实,沃尔玛关心的是通知能不能传达到附近的住户,小明和小新关心的是沃尔玛优惠活动的详情动态。沃尔玛不必给每个住户挨个电话通知,它只需要在它的微信公众号上推送一条消息即可,小明和小新也不用去超市咨询,只要随时关注沃尔玛公众号的推送消息即可。在上面这个例子中,沃尔玛就是服务提供者,小明和小新是服务消费者,微信公众号类似于注册中心,通过微信公众号,沃尔玛(服务方)和小明、小新(消费方)就“解耦”了。用这个例子来解释注册中心未必恰当,毕竟系统中的服务既可以是服务提供者(Provider),也可以是服务消费者(Consumer),但我想的是以一种更加通俗的方式来解释它,技术日新月异,各种技术、术语层出不穷,容易让人头晕眼花,但万变不离其宗,技术源于现实世界,亦服务于现实世界,在现实世界中,我们思考如何解决问题,技术也必然以同样的思路去解决问题。关于注册中心,更技术层面的解释,大家可以看一下这篇文章:《服务注册中心架构演进》在现有的技术方案中,注册中心主要分为两类,一类是CP类注册中心,另一类是AP类注册中心,Nacos属于后者,为什么会有 CP 和 AP 两种不同类型的注册中心呢?这就不得不提到分布式的一个理论:CAP理论。它是由加州大学的计算机科学家 Eric Brewer 提出,在一个分布式系统中,Consistency(一致性)、Availability(可用性)、Partition tolerance(分区容错性)无法同时满足,正所谓“鱼和熊掌与虾不可兼得也”。CP类注册中心更强调一致性,而AP类注册中心更强调可用性,它们之间的区别,推荐阅读阿里中间件博客的文章:《阿里巴巴为什么不用 ZooKeeper 做服务发现?》, 这篇文章我看了好几遍,虽然不能完全理解,但也能明白十之八九。如果你看完文章后,得到的结论是以后再也不把 Dubbo 和 Zookeeper 结合起来使用了,那么你便错了。因为,对于绝大多数公司的绝大多数系统,无论是 Dubbo + Zookeeper,还是 Dubbo + Nacos,都能够满足需求,有的公司甚至都不需要使用Dubbo,所以,一定要结合实际的业务场景来分析判断。不过,我们作为技术开发人员,了解技术原理是很重要的,唯有了解其底层逻辑,才知道如何做技术选型以及解决疑难杂症。好了,让我们回到Nacos本身,下面将从代码层面分别介绍 Nacos + Spring 和 Nacos + Spring Boot 的使用,我的案例都是基于 Nacos 官网的示例(毕竟官网是最好的学习资料嘛)。Nacos 结合 Spring先来看 Nacos + Spring 的使用:添加 maven 依赖:<dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-spring-context</artifactId> <version>0.2.2-RC1</version></dependency>使用 @EnableNacosDiscovery 开启 Nacos Spring 的服务发现功能@Configuration@EnableNacosDiscovery(globalProperties = @NacosProperties(serverAddr = “127.0.0.1:8848”))public class NacosDiscovery {}使用 @NacosInjected 注入 Nacos 的NamingService实例,通过NamingService的registerInstance() 向 Nacos Server 注册一个名称为applicationName的服务,当然,你也可以通过 Nacos Open API 方式注册:curl -X PUT ‘http://127.0.0.1:8848/nacos/v1/ns/instance?serviceName=XXX&ip=XXX&port=XXX’,这里我们介绍使用代码的方式。@Configuration@EnableNacosDiscovery(globalProperties = @NacosProperties(serverAddr = “127.0.0.1:8848”))public class NacosDiscovery { @NacosInjected private NamingService namingService; @Value("${server.port}") private int serverPort; @Value("${spring.application.name}") private String applicationName; @PostConstruct public void registerInstance() throws NacosException { namingService.registerInstance(applicationName, “127.0.0.1”, serverPort); }}再写一个Controller来验证服务是否再 Nacos Server 上注册了,代码如下:@RestController@RequestMapping(value = “discovery”)public class NacosDiscoveryController { @NacosInjected private NamingService namingService; @RequestMapping(value = “/get”, method = GET) @ResponseBody public List<Instance> getInstance(@RequestParam String serviceName) throws NacosException { return namingService.getAllInstances(serviceName); }}启动 Nacos Server,安装及启动方式请参考:《欢迎来到Nacos的世界!》然后启动Tomcat,我们先来看看Nacos控制台有什么变化在控制台上,我们可以看到名为nacos-spring-discovery服务实例,点击详情按钮查看实例的详细信息:在浏览器上访问:http://127.0.0.1:8080/discovery/get?serviceName=nacos-spring-discovery,返回结果如下:[{ “instanceId”: “127.0.0.1#8080#{"defaultCheckPort":80,"defaultPort":80,"healthChecker":{"type":"TCP"},"metadata":{},"name":"","useIPPort4Check":true}#nacos-spring-discovery”, “ip”: “127.0.0.1”, “port”: 8080, “weight”: 1.0, “healthy”: true, “cluster”: { “serviceName”: null, “name”: “”, “healthChecker”: { “type”: “TCP” }, “defaultPort”: 80, “defaultCheckPort”: 80, “useIPPort4Check”: true, “metadata”: {} }, “service”: null, “metadata”: {}}]和我们刚才在控制台看到的数据是一致的。以上就是 Nacos 结合 Spring 的实现方式,那么 Nacos 结合 Spring Boot 呢?其实没什么太大区别。Nacos 结合 Spring Boot添加 Starter 依赖:<dependency> <groupId>com.alibaba.boot</groupId> <artifactId>nacos-discovery-spring-boot-starter</artifactId> <version>0.2.1</version></dependency>注意:版本 0.2.x.RELEASE 对应的是 Spring Boot 2.x 版本,版本 0.1.x.RELEASE 对应的是 Spring Boot 1.x 版本。在application.properties中添加如下配置信息:server.port=8080spring.application.name=nacos-springboot-discoverynacos.discovery.server-addr=127.0.0.1:8848添加NacosDiscoveryApplication启动类,使用@NacosInjected注入 Nacos 的 NamingService实例,通过NamingService的registerInstance()向 Nacos Server 注册一个名称为applicationName的服务:@SpringBootApplicationpublic class NacosDiscoveryApplication { @NacosInjected private NamingService namingService; @Value("${server.port}") private int serverPort; @Value("${spring.application.name}") private String applicationName; @PostConstruct public void registerInstance() throws NacosException { namingService.registerInstance(applicationName, “127.0.0.1”, serverPort); } public static void main(String[] args) { SpringApplication.run(NacosDiscoveryApplication.class, args); }}添加NacosDiscoveryController类:@RestController@RequestMapping(value = “discovery”)public class NacosDiscoveryController { @NacosInjected private NamingService namingService; @RequestMapping(value = “/get”, method = GET) @ResponseBody public List<Instance> getInstance(@RequestParam String serviceName) throws NacosException { return namingService.getAllInstances(serviceName); }}启动NacosDiscoveryApplication,观察Nacos控制台在浏览器上访问:http://127.0.0.1:8080/discovery/get?serviceName=nacos-springboot-discovery,返回结果如下:[{ “instanceId”: “127.0.0.1#8080#{"defaultCheckPort":80,"defaultPort":80,"healthChecker":{"type":"TCP"},"metadata":{},"name":"","useIPPort4Check":true}#nacos-springboot-discovery”, “ip”: “127.0.0.1”, “port”: 8080, “weight”: 1.0, “healthy”: true, “cluster”: { “serviceName”: null, “name”: “”, “healthChecker”: { “type”: “TCP” }, “defaultPort”: 80, “defaultCheckPort”: 80, “useIPPort4Check”: true, “metadata”: {} }, “service”: null, “metadata”: {}}]以上两个示例的源码请从这里下载learn-nacos-spring-discoverylearn-nacos-springboot-discovery好了,关于 Nacos 作为注册中心的话题先聊到这里,下一期将介绍 Nacos 作为配置中心的使用,敬请期待!参考资料1、服务注册中心架构演进2、阿里巴巴为什么不用 ZooKeeper 做服务发现?3、Nacos Spring 快速开始4、Nacos Spring Boot 快速开始5、SpringBoot使用Nacos服务发现6、Spring Cloud Alibaba基础教程:使用Nacos实现服务注册与发现 ...

February 18, 2019 · 2 min · jiezi

社区投稿 | DBLE rule.xml 配置解析

文章来源:爱可生云数据库作者:余朝飞DBLE项目介绍DBLE官方网站:https://opensource.actionsky.com可以详细了解DBLE的背景和应用场景,本文不涉及到的细节都可在官方文档获得更细节都信息;对于刚了解到同学,可以以本文为快速入门基础DBLE官方项目:https://github.com/actiontech…如对源码有兴趣或者需要定制的功能的可以通过源码编译DBLE下载地址:https://github.com/actiontech…建议下载最新的releases版本,下载tar压缩包即可,如有源码编译需求的,可以下载源码包DBLE的主要配置文件上一篇"DBLE Schema.xml 配置解析"详细介绍了DBLE之中关于Scema.xml的配置,本篇文章将继续为大家讲解一下DBLE中Rule.xml文件的配置。DBLE的配置文件都在conf目录里面,常用的几个配置文件如下:文件说明server.xmlDBLE server相关参数定义,包括dble性能,定时任务,端口,用户配置等;本文主要涉及到访问用户的配置schema.xmlDBLE具体分片定义,规定table和schema以及dataNode之间的关系,指定每个表格使用哪种类型的分片方法,定义每个dataNode的连接信息等rule.xmlDBLE实际用到的分片算法的配置rule.xml配置解析其中rule.xml是日常配置分片算法的时候最常用到的配置文件,我们通过思维导图的方式给大家整理了DBLE的rule.xml的配置,需要注意的是思维导图不能代替看文档,导图只能起着概括归纳的作用,详细的细节还请参考官方文档。rule.xml举例从分片的数据在各个数据节点分布来看,分片可分为连续分片和离散分片,连续分片就是将一定范围内的数据全部分布在某一DataNode, 离散分布则是通过hash取模等方法将数据打散较为均匀地分布在各个DataNode。分片连续分片离散分片优点并发访问能力有限,扩容迁移代价小并发访问能力增强 范围条件查询性能提升缺点存在数据热点的可能性,并发访问能力受限于单一或少量DataNode .数据扩容比较困难,需要对整体数据做重新分布。举例date,numberrangehash, stringhash, patternrange注:hash,patternrange分片方式如果配置分片区间足够宽的话也是可以当做连续分片的。以下我以PatternRange算法为例,讲解一下如何配置该拆分算法,比如当前有一张表tasK_log已经有1000万的数据,这张表又因为需要和其他表进行关联查询,单表太大进行关联时异常缓慢,因此我们需要对这张表做拆分, 将这张表分别放在三个分片上,dn1,dn2,dn3。schema.xml中的配置如下 <table name=“tasK_log” dataNode=“dn1,dn2,dn3” rule=“three_node_range” />rule.xml中配置如下:<?xml version=“1.0”?><!DOCTYPE dble:rule SYSTEM “rule.dtd”><dble:rule xmlns:dble=“http://dble.cloud/"> <tableRule name=“three_node_range”> <rule> <columns>id</columns> <algorithm>three_node_range</algorithm> </rule> </tableRule> <function name=“three_node_range” class=“PatternRange”> <property name=“mapFile”>partition.txt</property> <property name=“patternValue”>1024</property> <property name=“defaultNode”>0</property> </function></dble:rule>mapfile partition.txt定义如下:[root@localhost ~]# cat partition.txt 0-255=0256-511=1512-1024=2查找路由时,将id字段与patternValue取模,即计算M = id % patternValue,如果M在0-255之间,在数据落在dn1分片。如果M在256-511之间,在数据落在dn2分片。如果M在512-1024之间,则数据落在dn3分片。如果都匹配不上,则落在默认节点defaultNode dn1分片(理论上在这个例子中是不可能匹配不上的)关于每一种拆分算法的详细介绍请参加官方文档介绍。总结rule.xml定义实际用到的拆分算法以及该拆分算法对应到的逻辑库使用使用算法,熟悉各种拆分算法的详细配置及其适用场景,才方便我们在众多数据拆分场景选择并配置合适的拆分规则,同时这也是适用分库分表中间件的第一步,并且实地演示了一个小小的拆分例子,使用到了patternrange算法。将表的详细拆分规则写在配置中,这是一种很"傻"的方式,但是这也是万不得已的一种选择,如果不通过配置文件的方式告诉中间件这些信息,那么中间件就无从得知底层具体的数据分布情况,也就达不到我们最终想要分库分表的目的了。

February 18, 2019 · 1 min · jiezi

分布式系统「伸缩性」大招之——「弹性架构」详解

如果第二次看到我的文章,欢迎下方扫码订阅我的个人公众号(跨界架构师)哟本文长度为3633字,建议阅读10分钟。坚持原创,每一篇都是用心之作~如果我们的开发工作真的就如搭积木一般就好了,轮廓分明,个个分开,坏了哪块积木换掉哪块就好了。但是,实际我们的工作中所面临的可能只有一块积木,而且还是一大块,要换得一起换,要修得一起修。Z哥在之前《分布式系统关注点(13)——「高内聚低耦合」详解》中提到的分层架构它可以让我们有意识的去做一些切分,但是换和修的难度还是根据切分的粒度大小来决定的。有更好的方式吗?这是显然的。事件驱动架构我们来换一个思维看待这个问题。不管是平时的系统升级也好、修复bug也好、扩容也好,其实就是一场“手术”。通过这场“手术”来解决当前面临的一些问题。那么分层架构好比只是将一个人的手、脚、嘴、鼻等分的清清楚楚,但是整体还是紧密的耦合在一起。怎么耦合的呢?我们人是靠“血液”的流动连接起来的。这就好比在分布式系统中通过rpc框架连接起不同的节点一样。但是软件与人不同,有2种不同的连接方式,除了「同步」的方式之外还有「异步」的方式。因为有些时候你不需要知道其他系统的执行结果,只要确保自己将其需要的数据传递给它了即可。恰巧有一种架构是这种模式的典型——事件驱动架构(简称EDA,Event Driven Architecture)。平时常见的MQ、本地消息表等运用于数据传递的中转环节,就是事件驱动架构的思想体现。事件驱动架构又细分为两种典型的实现方式,与Z哥之前在《分布式系统关注点(3)——「共识」的兄弟「事务」》中提到的Saga模式的2种实现方式类似,一种是中心化的、一种是去中心化的。下面Z哥来举个例子,让你看起来更容易理解一些。(例子仅为了阐述是怎么工作的,真正的实施中还需要考虑如何保证数据一致性等问题,这部分可以参考之前发表的系列文章,文末带传送门)传统的电商场景中,用户从购物车中点击“提交”按钮后,至少需要做这几件事:生成一笔订单、生成一笔支付记录、给订单匹配发货的快递公司。在这个场景下,中心化和去中心化有什么不同呢?中心化这种模式拥有一个“上帝”。但是“上帝”不会处理也不知道任何业务逻辑,它只编排事件。除了中心化之外,它还有什么特点呢?Z哥给它的定义是“3+2结构”。这种模式中存在3种类型的主体:事件生产者、“上帝”(调停者)、事件处理者。然后中间夹着两层队列,以此结构就能解耦。就像这样:事件生产者 –> 队列 –> “上帝”(调停者) –> 队列 –> 事件处理者。那么回上面的这个例子中,事件生产者CartService发出了一个“订单创建”事件,通过队列传递给调停者。然后调停者根据事先制定好的编排规则对事件进行相应的转换,也通过队列做二次分发,传递给事件处理者。可能你会问,这些好理解。但是,我之前也经常看到什么编排编排的,到底编排该怎么做呢?其实编排主要做两件事:「事件转换」和「事件发送」(对应「服务编排」类框架的「调用」)。「事件转换」实质就是给将要发送的事件对象的参数进行赋值。赋值的数据来源于哪呢?除了事件产生的源头带入的参数,还有持续累积的「上下文」,就如下图中这样的一个共享存储空间。可能你又会问,我怎么将多个事件处理者之间组合成一个上下文呢?通过一个全局唯一的标识即可,每次向“上帝”丢事件的时候把这个全局唯一标识带过去。题外话:一般来说,还会在一个全局唯一标识之下带一个内部唯一的「子流水号」,为了配合做接下去要讲到的「事件发送」。一是为了后续排查问题的时候清晰的知道这次调用产生的异常是从哪个上游系统来的。二是为了便于观测整个调用的逻辑是否符合编排时的预期。怎么做呢?通过一个x.x.x.x格式的序号。比如,串行就是1,2,3。分支和并行就是2.1,2.2。分支+串行的结合就是1,2,2.1,2.2,3。「事件发送」实质就是负责事件流转的逻辑控制,然后发往「事件处理者」去处理。它决定了是按顺序还是分支进行?是串行还是并行?到这就不再展开了,要不然就跑题了,我们下次再细聊这部分内容。再强调一下,「事件转换」和「事件发送」是你在实现“上帝”(调停者)功能的时候需要满足的最基本的两个功能哦。中心化最大的优势是让流程更加的“可见”了,同时也更容易去做一些监控类的东西,系统规模越大,这个优势产生的效果越明显。但是一个最基本的“上帝”(调停者)实现起来还需要考虑数据一致性问题,所以,会大大增加它的实现复杂度。因此,如果你面对的场景,业务没有特别庞大,并且是比较稳定的,或许用去中心化的方式也是不错的选择。去中心化这个模式由于没有了“上帝”,因此每个事件处理者需要知道自己的下一个事件处理器是什么?需要哪些参数?以及队列是哪个之类的东西。但是整体结构会变得简单很多,从“3+2结构”变成了“2+1结构”。结构简化背后的复杂度都跑到事件处理者开发人员编写的业务代码中去了。因为他需要自己去负责「事件转换」和「事件发送」这两个事情。嗯,改造成事件驱动架构之后,通过「队列」的解耦和异步的事件流转,系统的运转的确会更顺畅。但是有时候你可能想进行更细粒度的控制,因为一般情况下,一个service中会处理很多业务环节,不太会只存在一个对外接口、一条业务逻辑。在这样的情况下,很多时候你可能需要修改的地方仅仅是其中的一个接口。能不能只修改其中的一部分代码并且进行「热更新」呢?微内核架构(插件架构)就适合来解决这个问题。微内核架构顾名思义,微内核架构的关键是内核。所以需要先找到并明确内核是什么?然后将其它部分都视作“可拆卸”的部件。好比我们一个人,大脑就是内核,其它的什么都可以换,换完之后你还是你,但是大脑换了就不是你了。微内核架构整体上由两部分组成:核心系统和插件模块。核心系统内又包含了微内核、插件模块,以及内置的一些同样以插件形式提供的默认功能。其中,微内核主要负责插件的生命周期管理和控制插件模块。插件模块则负责插件的加载、替换和卸载。外部的插件如果要能够接入进来并顺利运行,前提先要有一个满足标准接口规范的实现。一个插件的标准接口至少会有这样的2个方法需要具体的插件来实现:public interface IPlugin{ /// <summary> /// 初始化配置 /// </summary> void InitializeConfig(Dictionary<string,string> configs); /// <summary> /// 运行 /// </summary> void Run(); …}最后,插件之间彼此独立,但核心系统知道哪里可以找到它们以及如何运行它们。 最佳实践知道了这两种具有“弹性”的架构模式,你该如何判断什么情况下需要搬出来用呢?Z哥带你来分析一下每一种架构的优缺点,就能发现它适用的场景。事件驱动架构它的优点是:通过「队列」进行解耦,使得面对快速变化的需求可以即时上线,而不影响上游系统。由于「事件」是一个独立存在的“标准化”沟通载体,可以利用这个特点衔接各种跨平台、多语言的程序。如果再进行额外的持久化,还能便于后续的问题排查。同时也可以对「事件」进行反复的「重放」,对处理者的吞吐量进行更真实的压力测试。更“动态”、容错性好。可以很容易,低成本地集成、再集成、再配置新的和已经存在的事件处理者,也可以很容易的移除事件处理者。轻松的做扩容和缩容。在“上帝”模式下,对业务能有一个“可见”的掌控,更容易发现流程不合理或者被忽略的问题。同时能标准化一些技术细节,如「数据一致性」的实现方式等。它的缺点是:面对不稳定的网络问题、各种异常,想要处理好这些以确保一致性,需要比同步调用花费很大的精力和成本。无法像同步调用一般,操作成功后即代表可以看到最新的数据,需要容忍延迟或者对延迟做一些用户体验上的额外处理。那么,它所适用的场景就是:对实时性要求不高的场景。系统中存在大量的跨平台、多语言的异构环境。以尽可能提高程序复用度为目的的场景。业务灵活多变的场景。需要经常扩容缩容的场景。微内核架构它的优点是:为递进设计和增量开发提供了方便。可以先实现一个稳固的核心系统,然后逐渐地增加功能和特性。和事件驱动架构一样,也可避免单一组件失效,而造成整个系统崩溃,容错性好。内核只需要重新启动这个组件,不致于影响其他功能。它的缺点是:由于主要的微内核很小,所以无法对整体进行优化。每个插件都各自管各自的,甚至可能是由不同团队负责维护。一般来说,为了避免在单个应用程序中的复杂度爆炸,很少会启用插件嵌套插件的模式,所以插件中的代码复用度会差一些。那么,它所适用的场景就是:可以嵌入或者作为其它架构模式的一部分。例如事件驱动架构中,“上帝”的「事件转换」就可以使用微内核架构实现。业务逻辑虽然不同,但是运行逻辑相同的场景。比如,定期任务和作业调度类应用。具有清晰的增量开发预期的场景。总结好了,我们总结一下。这次呢,Z哥向你介绍了「事件驱动架构」的两种实现模式和实现思路,以及「微内核架构」的实现思路。并且奉上了对这两种架构模式的优缺点与适用场景分析的最佳实践。希望对你有所启发。相关文章:分布式系统关注点(1)——初识数据一致性分布式系统关注点(2)——通过“共识”达成数据一致性分布式系统关注点(3)——「共识」的兄弟「事务」作者:Zachary出处:https://www.cnblogs.com/Zacha…如果你喜欢这篇文章,可以点一下文末的「赞」。这样可以给我一点反馈。: )谢谢你的举手之劳。▶关于作者:张帆(Zachary,个人微信号:Zachary-ZF)。坚持用心打磨每一篇高质量原创。欢迎扫描下方的二维码。定期发表原创内容:架构设计丨分布式系统丨产品丨运营丨一些思考。如果你是初级程序员,想提升但不知道如何下手。又或者做程序员多年,陷入了一些瓶颈想拓宽一下视野。欢迎关注我的公众号「跨界架构师」,回复「技术」,送你一份我长期收集和整理的思维导图。如果你是运营,面对不断变化的市场束手无策。又或者想了解主流的运营策略,以丰富自己的“仓库”。欢迎关注我的公众号「跨界架构师」,回复「运营」,送你一份我长期收集和整理的思维导图。

February 18, 2019 · 1 min · jiezi

虎牙直播在微服务改造方面的实践和总结

相比文字和图片,直播提供了人与人之间更丰富的沟通形式,其对平台稳定性的考验很大,那么倡导“以技术驱动娱乐”的虎牙直播(以下简称“虎牙”)是如何在技术上赋能娱乐,本文将为您介绍虎牙在DNS、服务注册、CMDB和服务配置中心等方面的实践。文章整理自虎牙基础保障部中间件团队负责人张波(社区ID:zhangjimmy)在Dubbo Meetup 广州站沙龙上的分享,阿里巴巴中间件授权发布,分享议题如下:为什么选用NacosDNS-F的技术价值和应用场景服务注册的实践CMDB的应用和实践服务配置的实践为什么选用 Nacos虎牙关注 Nacos 是从v0.2 开始的(最新版本:Pre-GA v0.8),我们也参与了社区的建设,可以说是比较早期的企业用户。Nacos 是一个更易于帮助构建云原生应用的动态服务发现、配置和服务管理平台,提供「注册中心」、「配置中心」和「动态DNS服务」三大功能。首先,在虎牙的微服务场景中,起初有多个注册中心,每一个注册中心服务于某一部分微服务,缺少一个能融合多个注册中心,并把他们逐一打通,然后实现一个能管理整个微服务体系的大的注册中心。以下内容摘自我们考虑引入Nacos时,在服务注册中心方案上的选型对比:Nacos提供 DNS-F功能, 可以与K8S、Spring Cloud和Dubbo等多个开源产品进行集成,实现服务的注册功能。其次,在服务配置中心方案的选型过程中,我们希望配置中心和注册中心能够打通,这样可以省去我们在微服务治理方面的一些投入。因此,我们也同步比较了一些服务配置中心的开源方案:例如Spring Cloud Config Server、Zookeeper和ETCD,总体评估下来,基于我们微服务体系现状以及业务场景,我们决定使用Nacos作为我们的服务注册和服务发现的方案。使用过程中,我们发现,随着社区版本的不断更新和虎牙的深入实践,Nacos的优势远比我们调研过程中发现的更多,接下来,我将围绕DNS-F、Nacos-Sync、 CMDB和负载均衡4方面来分享虎牙的实践。DNS - F 的技术价值Nacos 提供的DNS-F功能的第一个技术价值在于,弥补了我们内部微服务没有一个全局动态调度能力的空白。刚才提到,虎牙有多个微服务体系,但并没有一个微服务具备全局动态调度的能力,因为它们各自都是独立的。目前,我们通过Nacos已经融合了四个微服务体系的注册中心,最终目标是把所有的微服务都融合在一起,实现全局动态调动的能力。第二,DNS-F解决了服务端端到端面临的挑战,即延时大、解析不准、故障牵引慢的问题。如何去理解呢?当内部有多个微服务体系的时候,每一个体系的成熟度是不同的。例如,有一些微服务框架对同机房或CMDB路由是不支持的,当一个服务注册到了多个IDC中心,去调用它的服务的时候,即便是同机房,也可能调用到一个不是同机房的节点。这样就会无端的造成服务的延时和解析不准。即使我们基于DNS做一些解析的优化,但仍然无法完全解决服务的延时和解析不准。这是因为DNS都是IP策略的就近解析,无法根据服务的物理状态、物理信息进行路由。此外,当一个核心服务出现问题,如果缺少一个融合了多个调用方和被调用方的信息的统一的注册中心,就很难去准确判断如何去牵引,从而导致故障牵引慢。有了Nacos后,就可以接入一个统一的注册中心以及配置中心,去解决这些问题。(目前,虎牙还在微服务体系的改造过程中,未完全实现统一的注册中心)第三,提供专线流量牵引能力。虎牙的核心机房的流量互通,是使用专线来实现的。专线的特性就是物理建设的,而且我们的专线建设可能不像BAT那么大,例如我们专线容量的冗余只有50%,假设某个直播异常火爆,突发流量高于平常的两百倍,超过了专线的建设能力,这时候一个服务就有可能会导致全网故障。但是,通过全局的注册中心和调动能力,我们就可以把流量牵引到其他地方,例如迁移到公网,甚至牵引到一个不存在的地址,来平衡一下。即便某个服务出现问题,也不会影响我们的全局服务。第四,支持服务端的多种调度需求,包括同机房路由、同机器路由,以及同机架路由,Nacos都可以去做适配。此外,基于Nacos 的DNS-F功能,我们还实现了加速外部域名解析和服务故障牵引秒级生效。DNS - F 的应用场景这张图是Nacos DNS-F的一个具体实现,实际上是拦截了OS层的DNS请求。如果经过DNS的域名是内部服务,它就会从Nacos Server 获取结果,如果不是,就会转发到其它的LocalDNS进行解析。以数据库高可用的应用场景为例,我们的数据库切换效率比较低,依赖业务方修改配置,时效不确定,通常需要10分钟以上(备注:我们的数据库实际上已经实现了主备的功能,但当一个主服务出现问题的时候,总是要去切换IP。)切换IP的过程中,依赖运维和开发的协作,这是一个比较长的过程。引入DNS后,当主出现问题的时候,就可以很快的用另外一个主的IP来进行替换,屏蔽故障,而且节点的故障检测和故障切换都可以自动完成,并不依赖运维和开发的协作,节省了时间。当然,这个场景的解法有很多,比如说使用MySQL - Proxy也可以去解这个问题,但我们的MySQL - Proxy还在建设中,想尽快的把这个问题解决,所以采用了DNS的方式。下面我们再着重分享下基于DNS-F对LocalDNS的优化。虎牙还没有去建设自己的LocalDNS,大部分使用的是一些公共的DNS,大致有以下这些组成。这种组成方式会存在一个问题。假设服务突然一下崩溃后,之后服务又马上正常了,这种情况我们无法重现去找到崩溃原因。因为很多场景下,是一个公共DNS的请求超时导致的,甚至一个解析失败导致的,在那一刻,因为无法保留现场的,所以就发现不了问题。以我们的监测数据来看,DNS解析错误的比例达到1‰左右,超时比例将更高。意思是在使用公共DNS的情况下,服务有1‰的几率是会超时或失败,如果服务没有做好容错,就会出现异常。同时,一些公共DNS解析的延时都是不定的,比如在亚马逊上一些比较不好的节点,它的延时会比较高,平均超过三四十毫秒。然后我们基于DNS-F对LocalDNS做了一些优化。优化结果如下:平均解析时间从之前的超过两百毫秒降低到两毫秒以下;缓存命中率从92%提升到了99%以上;解析失败率之前是1‰,现在基本上没有了。优化的效果也体现在我们的风控服务上,平均延迟下降10ms,服务超时比例下降25%,降低了因延迟或服务超时导致的用户上传的图片或文字违规但未被审核到的风险。服务注册的实践虎牙的核心业务是跑在Tars上的。Tars:腾讯开源的一款微服务框架。Tars主要是支持C++,但对Java、PHP等开发语言的支持力度比较差,这就使得我们非C++的业务方去调用它就会很别扭。引入Nacos以后,我们通过Nacos支持的DNS协议来实现服务发现过程中对全语言的支持。当然,Nacos不只是一个注册中心,它具备了融合多个数据中心的能力,支持多数据源的同步,例如,我们目前已经支持了Taf(虎牙内部的一个重要微服务体系)、Nacos自身、ZooKeeper、以及K8S上一些服务注册的同步。同时,基于Nacos集群的双向同步功能(Nacos-Sync),我们实现了国内的两个可用区,以及国外的多个可用区之间的数据值同步,最终实现了一处注册、多地可读。Nacos-Sync是事件机制,即同步任务通过事件触发,可以灵活地开启和关闭你要同步的任务,然后根据服务变化事件触发监听,保证实时性,最后通过定时的全量突发同步事件,保证服务数据的最终一致。同时,Nacos-Sync也支持服务心跳维持,即多个数据中心的心跳,可以使用Nacos-Sync代理要来实现远端同步。此外,也支持心跳与同步任务绑定,便于灵活控制。由于Taf上有数万个注册服务,同步的量特别大,所以我们在Nacos-Sync做了一些改造,通过任务分片来实现数万服务同步的可用性保障。改造步骤是先以服务为粒度定义任务,然后在多个分片上分散任务负载,最后以单分片多副本来保证任务可用性。对接 CMDB,实现就近访问在服务进行多机房或者多地域部署时,跨地域的服务访问往往延迟较高,一个城市内的机房间的典型网络延迟在1ms左右,而跨城市的网络延迟,例如上海到北京大概为30ms。此时自然而然的一个想法就是能不能让服务消费者和服务提供者进行同地域访问。Nacos定义了一个SPI接口,里面包含了与第三方CMDB约定的一些方法。用户依照约定实现了相应的SPI接口后,将实现打成Jar包放置到Nacos安装目录下,重启Nacos即可让Nacos与CMDB的数据打通。在实际的落地过程中,我们是在DNS-F接入Taf,在DNS-F上实现Taf的中控接口,无缝对接Taf的sdk。DNS-F提供缓存负载均衡和实例信息,Nacos则提供负载均衡信息的查询接口。服务配置的实践虎牙的域名(www.huya.com)会接入华南、华中、华北多个IDC机房,每个机房都会建设一个Nginx去做负载均衡,经过负载均衡的流量会通过专线返回到我们的后端服务器上。在这个过程中,如果我们去修改一个在中间的配置,需要下发到多个机房的上百个负责负载均衡的机器上,如果出现配置下发不及时,或下发配置失败,极大可能会出现故障,同时,负责均衡服务的机器对弹性能力的要求较高,在业务高峰如果不能快速扩容,容易出现全网故障。传统的配置下发方式是通过服务端下发文件更新配置,更新配置生效时间长,由于需要预先知道负责均衡集群的机器信息,扩缩容需要等元信息同步以后才能接入流量,扩容流量的接入时间较长。引入Nacos后,我们采用了配置中心监听方式,通过客户端主动监听配置更新,配置便可秒级生效,新扩容服务主动拉取全量配置,流量接入时长缩短3分钟+。虎牙对 Nacos 改造和升级的总结引入Nacos的过程中,我们所做的改造和升级总结如下。一是在DNS-F上,我们增加了对外部域名的预缓存的支持,Agent的监控数据对接到公司的内部监控,日志输出也对接到内部的日志服务,然后和公司的CMDB对接,并实现了DNS-F Cluster集群。我们之所以去构建一个DNS-FCluster集群,是为了避免内存、硬盘或版本问题导致的DNS服务无效,有了DNS-F Cluster集群,当本地Agent出现问题的时候,就可以通过集群去代理和解析DNS请求。二是在Nacos-Sync上,我们对接了TAF注册服务和K8S注册服务,以及解决了多数据中心环形同步的问题。三是在Nacos CMDB上,我们对Nacos CMDB进行了扩展,对接了虎牙自己的CMDB,并对接了内部的负载均衡策略。本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。

February 15, 2019 · 1 min · jiezi

阿里开源分布式事务解决方案 Fescar 全解析

广为人知的阿里分布式事务解决方案:GTS(Global Transaction Service),已正式推出开源版本,取名为“Fescar”,希望帮助业界解决微服务架构下的分布式事务问题,今天我们一起来深入了解。FESCAR on GitHubhttps://github.com/alibaba/fe…微服务倡导将复杂的单体应用拆分为若干个功能简单、松耦合的服务,这样可以降低开发难度、增强扩展性、便于敏捷开发。当前被越来越多的开发者推崇,系统微服务化后,一个看似简单的功能,内部可能需要调用多个服务并操作多个数据库实现,服务调用的分布式事务问题变的非常突出。分布式事务已经成为微服务落地最大的阻碍,也是最具挑战性的一个技术难题。 1. 什么是微服务化带来的分布式事务问题?首先,设想一个传统的单体应用(Monolithic App),通过 3 个 Module,在同一个数据源上更新数据来完成一项业务。很自然的,整个业务过程的数据一致性由本地事务来保证。随着业务需求和架构的变化,单体应用被拆分为微服务:原来的 3 个 Module 被拆分为 3 个独立的服务,分别使用独立的数据源(Pattern: Database per service)。业务过程将由 3 个服务的调用来完成。此时,每一个服务内部的数据一致性仍有本地事务来保证。而整个业务层面的全局数据一致性要如何保障呢?这就是微服务架构下面临的,典型的分布式事务需求:我们需要一个分布式事务的解决方案保障业务全局的数据一致性。2. Fescar 的发展历程阿里是国内最早一批进行应用分布式(微服务化)改造的企业,所以很早就遇到微服务架构下的分布式事务问题。2014 年,阿里中间件团队发布 TXC(Taobao Transaction Constructor),为集团内应用提供分布式事务服务。2016 年,TXC 经过产品化改造,以 GTS(Global Transaction Service)的身份登陆阿里云,成为当时业界唯一一款云上分布式事务产品,在阿云里的公有云、专有云解决方案中,开始服务于众多外部客户。2019 年起,基于 TXC 和 GTS 的技术积累,阿里中间件团队发起了开源项目 Fescar(Fast & EaSy Commit And Rollback, FESCAR),和社区一起建设这个分布式事务解决方案。TXC/GTS/Fescar 一脉相承,为解决微服务架构下的分布式事务问题交出了一份与众不同的答卷。2.1 设计初衷高速增长的互联网时代,快速试错的能力对业务来说是至关重要的:一方面,不应该因为技术架构上的微服务化和分布式事务支持的引入,给业务层面带来额外的研发负担。另一方面,引入分布式事务支持的业务应该基本保持在同一量级上的性能表现,不能因为事务机制显著拖慢业务。基于这两点,我们设计之初的最重要的考量就在于:对业务无侵入:这里的“侵入”是指,因为分布式事务这个技术问题的制约,要求应用在业务层面进行设计和改造。这种设计和改造往往会给应用带来很高的研发和维护成本。我们希望把分布式事务问题在 中间件 这个层次解决掉,不要求应用在业务层面做额外的工作。高性能:引入分布式事务的保障,必然会有额外的开销,引起性能的下降。我们希望把分布式事务引入的性能损耗降到非常低的水平,让应用不因为分布式事务的引入导致业务的可用性受影响。2.2 既有的解决方案为什么不满足?既有的分布式事务解决方案按照对业务侵入性分为两类,即:对业务无侵入的和对业务有侵入的。业务无侵入的方案既有的主流分布式事务解决方案中,对业务无侵入的只有基于 XA 的方案,但应用 XA 方案存在 3 个方面的问题:要求数据库提供对 XA 的支持。如果遇到不支持 XA(或支持得不好,比如 MySQL 5.7 以前的版本)的数据库,则不能使用。受协议本身的约束,事务资源的锁定周期长。长周期的资源锁定从业务层面来看,往往是不必要的,而因为事务资源的管理器是数据库本身,应用层无法插手。这样形成的局面就是,基于 XA 的应用往往性能会比较差,而且很难优化。已经落地的基于 XA 的分布式解决方案,都依托于重量级的应用服务器(Tuxedo/WebLogic/WebSphere 等),这是不适用于微服务架构的。侵入业务的方案实际上,最初分布式事务只有 XA 这个唯一方案。XA 是完备的,但在实践过程中,由于种种原因(包含但不限于上面提到的 3 点)往往不得不放弃,转而从业务层面着手来解决分布式事务问题。比如:基于可靠消息的最终一致性方案TCCSaga都属于这一类。这些方案的具体机制在这里不做展开,网上这方面的论述文章非常多。总之,这些方案都要求在应用的业务层面把分布式事务技术约束考虑到设计中,通常每一个服务都需要设计实现正向和反向的幂等接口。这样的设计约束,往往会导致很高的研发和维护成本。2.3 理想的方案应该是什么样子?不可否认,侵入业务的分布式事务方案都经过大量实践验证,能有效解决问题,在各种行业的业务应用系统中起着重要作用。但回到原点来思考,这些方案的采用实际上都是迫于无奈。设想,如果基于 XA 的方案能够不那么重,并且能保证业务的性能需求,相信不会有人愿意把分布式事务问题拿到业务层面来解决。一个理想的分布式事务解决方案应该:像使用本地事务一样简单,业务逻辑只关注业务层面的需求,不需要考虑事务机制上的约束。3. 原理和设计我们要设计一个对业务无侵入的方案,所以从业务无侵入的 XA 方案来思考:是否可以在 XA 的基础上演进,解决掉 XA 方案面临的问题呢?3.1 如何定义一个分布式事务?首先,很自然的,我们可以把一个分布式事务理解成一个包含了若干分支事务的全局事务。全局事务的职责是协调其下管辖的 分支事务 达成一致,要么一起成功提交,要么一起失败回滚。此外,通常分支事务本身就是一个满足 ACID 的本地事务。这是我们对分布式事务结构的基本认识,与 XA 是一致的。其次,与 XA 的模型类似,我们定义 3 个组件来协议分布式事务的处理过程。Transaction Coordinator (TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。Transaction Manager (TM):控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。Resource Manager (RM):控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。一个典型的分布式事务过程:TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID。XID 在微服务调用链路的上下文中传播。RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖。TM 向 TC 发起针对 XID 的全局提交或回滚决议。TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。至此,Fescar 的协议机制总体上看与 XA 是一致的。3.2 与 XA 的差别在什么地方?架构层次XA 方案的 RM 实际上是在数据库层,RM 本质上就是数据库自身(通过提供支持 XA 的驱动程序来供应用使用)。而 Fescar 的 RM 是以二方包的形式作为中间件层部署在应用程序这一侧的,不依赖与数据库本身对协议的支持,当然也不需要数据库支持 XA 协议。这点对于微服务化的架构来说是非常重要的:应用层不需要为本地事务和分布式事务两类不同场景来适配两套不同的数据库驱动。这个设计,剥离了分布式事务方案对数据库在 协议支持 上的要求。两阶段提交先来看一下 XA 的 2PC 过程。无论 Phase2 的决议是 commit 还是 rollback,事务性资源的锁都要保持到 Phase2 完成才释放。设想一个正常运行的业务,大概率是 90% 以上的事务最终应该是成功提交的,我们是否可以在 Phase1 就将本地事务提交呢?这样 90% 以上的情况下,可以省去 Phase2 持锁的时间,整体提高效率。这个设计,在绝大多数场景减少了事务持锁时间,从而提高了事务的并发度。当然,你肯定会问:Phase1 即提交的情况下,Phase2 如何回滚呢?3.3 分支事务如何提交和回滚?首先,应用需要使用 Fescar 的 JDBC 数据源代理,也就是 Fescar 的 RM。Phase1:Fescar 的 JDBC 数据源代理通过对业务 SQL 的解析,把业务数据在更新前后的数据镜像组织成回滚日志,利用本地事务 的 ACID 特性,将业务数据的更新和回滚日志的写入在同一个 本地事务中提交。这样,可以保证:任何提交的业务数据的更新一定有相应的回滚日志存在。基于这样的机制,分支的本地事务便可以在全局事务的 Phase1 提交,马上释放本地事务锁定的资源。Phase2:如果决议是全局提交,此时分支事务此时已经完成提交,不需要同步协调处理(只需要异步清理回滚日志),Phase2 可以非常快速地完成。如果决议是全局回滚,RM 收到协调器发来的回滚请求,通过 XID 和 Branch ID 找到相应的回滚日志记录,通过回滚记录生成反向的更新 SQL 并执行,以完成分支的回滚。3.4 事务传播机制XID 是一个全局事务的唯一标识,事务传播机制要做的就是把 XID 在服务调用链路中传递下去,并绑定到服务的事务上下文中,这样,服务链路中的数据库更新操作,就都会向该 XID 代表的全局事务注册分支,纳入同一个全局事务的管辖。基于这个机制,Fescar 是可以支持任何微服务 RPC 框架的。只要在特定框架中找到可以透明传播 XID 的机制即可,比如,Dubbo 的 Filter + RpcContext。对应到 Java EE 规范和 Spring 定义的事务传播属性,Fescar 的支持如下:PROPAGATION_REQUIRED:默认支持PROPAGATION_SUPPORTS:默认支持PROPAGATION_MANDATORY:应用通过 API 来实现PROPAGATION_REQUIRES_NEW:应用通过 API 来实现PROPAGATION_NOT_SUPPORTED:应用通过 API 来实现PROPAGATION_NEVER:应用通过 API 来实现PROPAGATION_REQUIRED_NESTED:不支持3.5 隔离性全局事务的隔离性是建立在分支事务的本地隔离级别基础之上的。在数据库本地隔离级别读已提交或以上的前提下,Fescar 设计了由事务协调器维护的 全局写排他锁,来保证事务间的写隔离,将全局事务默认定义在读未提交的隔离级别上。我们对隔离级别的共识是:绝大部分应用在 读已提交 的隔离级别下工作是没有问题的。而实际上,这当中又有绝大多数的应用场景,实际上工作在读未提交的隔离级别下同样没有问题。在极端场景下,应用如果需要达到全局的 读已提交,Fescar 也提供了相应的机制来达到目的。默认,Fescar 是工作在 读无提交 的隔离级别下,保证绝大多数场景的高效性。事务的 ACID 属性在 Fescar 中的体现是一个比较复杂的话题,我们会有专门的文章来深入分析,这里不做进一步展开。4. 适用场景分析前文所述的 Fescar 的核心原理中有一个重要前提:分支事务中涉及的资源,必须是支持ACID 事务的 关系型数据库。分支的提交和回滚机制,都依赖于本地事务的保障。所以,如果应用使用的数据库是不支持事务的,或根本不是关系型数据库,就不适用。另外,目前 Fescar 的实现还存在一些局限,比如:事务隔离级别最高支持到读已提交的水平,SQL 的解析还不能涵盖全部的语法等。为了覆盖 Fescar 原生机制暂时不能支持应用场景,我们定义了另外一种工作模式。上面介绍的 Fescar 原生工作模式称为 AT(Automatic Transaction)模式,这种模式是对业务无侵入的。与之相应的另外一种工作模式称为 MT(Manual Transaction)模式,这种模式下,分支事务需要应用自己来定义业务本身及提交和回滚的逻辑。4.1 分支的基本行为模式作为全局事务一部分的分支事务,除本身的业务逻辑外,都包含 4 个与协调器交互的行为:分支注册:在分支事务的数据操作进行之前,需要向协调器注册,把即将进行的分支事务数据操作,纳入一个已经开启的全局事务的管理中去,在分支注册成功后,才可以进行数据操作。状态上报:在分支事务的数据操作完成后,需要向事务协调器上报其执行结果。分支提交:响应协调器发出的分支事务提交的请求,完成分支提交。分支回滚:响应协调器发出的分支事务回滚的请求,完成分支回滚。4.2 AT 模式分支的行为模式业务逻辑不需要关注事务机制,分支与全局事务的交互过程自动进行。4.3 MT 模式分支的行为模式业务逻辑需要被分解为 Prepare/Commit/Rollback 3 部分,形成一个 MT 分支,加入全局事务。MT 模式一方面是 AT 模式的补充。另外,更重要的价值在于,通过 MT 模式可以把众多非事务性资源纳入全局事务的管理中。4.4 混合模式因为 AT 和 MT 模式的分支从根本上行为模式是一致的,所以可以完全兼容,即,一个全局事务中,可以同时存在 AT 和 MT 的分支。这样就可以达到全面覆盖业务场景的目的:AT 模式可以支持的,使用 AT 模式;AT 模式暂时支持不了的,用 MT 模式来替代。另外,自然的,MT 模式管理的非事务性资源也可以和支持事务的关系型数据库资源一起,纳入同一个分布式事务的管理中。4.5 应用场景的远景回到我们设计的初衷:一个理想的分布式事务解决方案是不应该侵入业务的。MT 模式是在 AT 模式暂时不能完全覆盖所有场景的情况下,一个比较自然的补充方案。我们希望通过 AT 模式的不断演进增强,逐步扩大所支持的场景,MT 模式逐步收敛。未来,我们会纳入对 XA 的原生支持,用 XA 这种无侵入的方式来覆盖 AT 模式无法触达的场景。5. 扩展点5.1 微服务框架的支持事务上下文在微服务间的传播需要根据微服务框架本身的机制,订制最优的,对应用层透明的解决方案。有兴趣在这方面共建的开发者可以参考内置的对 Dubbo 的支持方案,来实现对其他微服务框架的支持。5.2 所支持的数据库类型因为 AT 涉及 SQL 的解析,所以在不同类型的数据库上工作,会有一些特定的适配。有兴趣在这方面共建的开发者可以参考内置的对 MySQL 的支持方案,来实现对其他数据库的支持。5.3 配置和服务注册发现支持接入不同的配置和服务注册发现解决方案。比如:Nacos、Eureka、ZooKeeper 等。5.4 MT 模式的场景拓展MT 模式的一个重要作用就是,可以把非关系型数据库的资源,通过 MT 模式分支的包装,纳入到全局事务的管辖中来。比如,Redis、HBase、RocketMQ 的事务消息等。有兴趣在这方面共建的开发者可以在这里贡献一系列相关生态的适配方案。5.5 事务协调器的分布式高可用方案针对不同场景,支持不同的方式作为事务协调器 Server 端的高可用方案。比如,针对事务状态的持久化,可以是基于文件的实现方案,也可以是基于数据库的实现方案;集群间的状态同步,可以是基于 RPC 通信的方案,也可以是基于高可用 KV 存储的方案。6. Roadmap蓝图绿色部分是已经开源发布出来的,黄色 部分是将在后续版本中由阿里发布出来的,蓝色部分是我们和社区共建生态部分:对不同数据库的支持,开发者可以参考 MySQL 的实现。对不同微服务框架的支持,开发者可以参考 Dubbo 的实现。对 MQ、NoSQL 的支持,开发者可以参考 TCC 的实现。配置和服务注册发现:开发者通过少量的工作可以接入任何可以提供这类服务的框架。当然,非 蓝色 的部分也非常欢迎社区参与进来,贡献更优的解决方案。另外,XA 作为分布式事务的标准,是一个完备的分布式事务解决方案不可或缺的,远景的规划中,我们一定需要把 XA 的支持加入进来。初步的版本规划v0.1.0:微服务框架支持: Dubbo数据库支持: MySQL基于 Spring AOP 的 Annotation事务协调器: 单机版本v0.5.x:微服务框架支持: Spring CloudMT 模式支持 TCC 模式事务的适配动态配置和服务发现事务协调器: 高可用集群版本v0.8.x:Metrics控制台: 监控/部署/升级/扩缩容v1.0.0:General Availability: 生产环境适用v1.5.x:数据库支持: Oracle/PostgreSQL/OceanBase不依赖 Spring AOP 的 Annotation热点数据的优化处理机制RocketMQ 事务消息纳入全局事务管理NoSQL 纳入全局事务管理的适配机制支持 HBase支持 Redisv2.0.0:支持 XA当然,项目迭代演进的过程,我们最重视的是社区的声音,路线图会和社区充分交流及时进行调整。相关链接:FESCAR on GitHub:https://github.com/alibaba/fe… GTS on Aliyun:https://help.aliyun.com/produ…本文作者:amber涂南阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

January 29, 2019 · 2 min · jiezi

阿里资深技术专家:优秀的数据库存储引擎应具备哪些能力?

摘要: 作为数据库的底盘,一个成熟的存储引擎如何实现高效数据存取?导读本文作者是阿里巴巴OLTP数据库团队资深技术专家——曲山。作为自研高性能、低成本存储引擎X-Engine的负责人,曲山眼中的优秀关系型数据库存储引擎应该具备哪些能力呢?正文数据库内核按层次来分,就是两层:SQL & Storage。SQL Layer负责将你输入的SQL statement通过一系列步骤(parse/resolve/rewrite/optimize…)转换成物理执行计划,同时负责计划的执行,执行计划通常是一颗树的形式,其中树的叶子节点(执行器算子)部分往往负责单表的数据操作,这些操作算子就要在storage layer来执行了。因此,一个数据库存储引擎的主要工作,简单来讲就是存取数据,但是前提是保证数据库的ACID(atomicity/consistency/isolation/durability)语义。存储引擎对外提供的接口其实比较简单,主要就是数据写入/修改/查询,事务处理(start transaction/commit/rollback…),修改schema对象/数据字典(可选), 数据统计,还有一些周边的运维或数据导入导出功能。仅仅从功能上来说,要实现一个存储引擎似乎并不困难,如今也有很多Key-Value Store摇身一变就成为了数据库存储引擎,无非是加上一套事务处理机制罢了。但是作为数据库的底盘,一个成熟的存储引擎必须要考虑效率,如何高效(性能/成本最大化)的实现数据存取则成了在设计上做出种种权衡的主要考量。可以从存储引擎的几个主要组件来讨论:数据组织数据在内存和磁盘中的组织方式很大程度上决定了存取的效率,不同的应用场景选择也不同,典型的如:数据按行存储(NSM),对事务处理比较友好,因为事务数据总是完整行写进来, 多用于OLTP场景。按列存储(DSM),把tuples中相同的列值物理上存储在一起,这样只需要读取需要的列,在大规模数据扫描时减少大量I/O。另外列存做压缩的效果更好,适合OLAP场景,但是事务处理就不那么方便,需要做行转列。所以大部分AP数据库事务处理效率都不怎么高,某些甚至只支持批量导入。混合存储(FSM),行列混合布局,有把数据先按行分组(Segment, SubPage),组内使用DSM组织,如PAX, RCFile,也有先按列分组(Column Group),组内指定的列按NSM组织,如Peloton的Tile。此种格式试图结合NSM和DSM两者的优点,达到处理混合负载(HTAP)的目的,但是同时也继承了两者的缺点。所以做存储引擎,一开始就要面临选择何种存储格式的问题。即便选定了大类,每种格式中也有无数的细节需要考虑,每种数据类型的字段如何编码(Encoding),行存中null/not null如何存储,是否需要列索引加快project operation,是否需要对列值进行重排,列存如何进行数据压缩,等等,都要存储空间和存取速度中做平衡。现代数据库为了应对复杂的应用场景,往往使用不只一种存储格式,比如Oracle有In-memory Column Store在内存中将行存的页面转换为列存方式的page,用来加速复杂查询。当数据选定存储格式以后,还要选择数据在磁盘和内存中的聚集方式。以按行存储为例,大部分存储引擎使用固定大小的页面(page)来存储连续的若干行。当然,数据行如何连续排列,有堆表(随机)和索引组织表(按索引序)两种,现在较为流行的LSM-Like的存储引擎使用不定大小的页面(称为DataBlock),只支持按主键索引序聚集;这两种方式主要区别在于前者被设计为可更新的,每个page中会留有空间,后者是只读的,数据紧密存储不带padding,便于压缩。两者的区别实际上是因为事务处理机制有较大的区别导致的,后面再论。对于In-Memory Database来说,数据组织的方式会有较大区别,因为不需要在内存和持久化存储中交换数据,内存中一般不会使用page形式,而是直接使用索引存储结构(比如B+Tree)直接索引到记录(tuples),无需page这一层间接引用,减少cpu cache miss。缓存管理缓存的粒度一般是page,关键在于缓存替换算法。目前用的比较广泛的LRU,LFU,ARC..以及各种变种的算法都有在数据库中使用。另外还有一个是如何更有效的管理内存的问题,这点上,定长的page会比不定长的更有优势。当然还要考虑各种query pattern对cache的影响,如果单行查询较多,选用更细粒度(比如row)的cache会更有效率,但是淘汰的策略会更复杂,很多新的研究开始尝试引入机器学习的方法来优化cache淘汰算法,以及有效的管理cache.事务处理存储引擎之核心,保证数据库的ACID。要保证D,大家的做法差不多,都是写WAL(Write Ahead Log)来做recovery,关键是如何高效的实现ACI,也就是所谓的多版本并发控制(MVCC)机制。MVCC的完整实现比较复杂,暂不详细阐述,这里面的关键在于如何处理并发执行过程中的数据冲突(data race),包括写写冲突,读写冲突;因为数据库的负载一般是读多写少的,要做到高效,只读事务不能被读写事务阻塞,这就要求我们的写不能直接去更新当前的数据,而是要有一套维护多版本数据的能力,当前的存储引擎管理多版本数据的办法无非两种:写入数据原地更新,被更新的旧版本写到undo链中,写入代价大,事务处理复杂, 但是回收旧版本数据高效。写入数据不直接更新原来的数据,而是追加为新版本,写入代价小,但是读,尤其是扫描需要读取层次较多,更为严重的问题是回收旧版本的数据需要做compact,代价很大。前一种称为ARIES算法比大多数主流数据库存储引擎使用,后一种称为LSM-Tree的结构也被很多新存储引擎使用,受到越来越多的关注。Catalog与KV store有区别的是,数据库是有严格的schema的,所以多数存储引擎中的记录都是有结构的,很多KV store在作为数据库存储引擎时,都是在中间做一层转换,将上层处理的tuples以特定的编码方式转换为binary key-value,写入KVStore,并在读取到上层后,依靠schema解释为tuples格式供上层处理。这种方法当然可以工作,但是诸多优化无法实施:a. 数据迭代必须是整行,即便只需要其中一列,序列化/反序列化开销是免不了的。b. project和filter的工作无法下放到存储层内部进行处理; c. 没有列信息,做按列编码,压缩也不可能。d. schema change只能暴力重整数据… 因此要做到真正的高效,越来越多的存储引擎选择完全感知schema,存储细微结构。总结以上所探讨的,还只是单机数据库的存储引擎几个大的问题,而现代数据库对存储引擎提出了更高的要求,可扩展,高可用已经成为标配,现在要考虑的是如何给你的存储引擎加上分布式的能力,而这又涉及到高可用一致性保证,自动扩展,分布式事务等一系列更为复杂的问题,这已远超出本文的范畴,需要另开篇章。最后介绍下我们正在开发的阿里自研分布式数据库X-DB,其中的存储引擎就使用了我们自研的X-Engine。X-Engine使用了一种对数据进行分层的存储架构,因为目标是面向大规模的海量数据存储,提供高并发事务处理能力和尽可能降低成本。我们根据数据访问频度(冷热)的不同将数据划分为多个层次,针对每个层次数据的访问特点,设计对应的存储结构,写入合适的存储设备。X-Engine使用了LSM-Tree作为分层存储的架构基础,并在这之上进行了重新设计。简单来讲,热数据层和数据更新使用内存存储,利用了大量内存数据库的技术(Lock-Free index structure/append only)提高事务处理的性能,我们设计了一套事务处理流水线处理机制,把事务处理的几个阶段并行起来,极大提升了吞吐。而访问频度低的冷(温)数据逐渐淘汰或是合并到持久化的存储层次中,结合当前丰富的存储设备层次体系(NVM/SSD/HDD)进行存储。我们对性能影响比较大的compaction过程做了大量优化,主要是拆分数据存储粒度,利用数据更新热点较为集中的特征,尽可能的在合并过程中复用数据,精细化控制LSM的形状,减少I/O和计算代价,并同时极大的减少了合并过程中的空间放大。同时使用更细粒度的访问控制和缓存机制,优化读的性能。当然优化是无止境的,得益于丰富的应用场景,我们在其中获得了大量的工程经验。X-Engine现在已经不只一个单机数据库存储引擎,结合我们的X-Paxos(分布式强一致高可用框架), GMS(分布式管理服务), 和X-Trx(分布式事务处理框架),已经演变为一个分布式数据库存储系统。本文作者:七幕阅读原文本文为云栖社区原创内容,未经允许不得转载。

January 29, 2019 · 1 min · jiezi

一位技术校招生在支付宝的成长笔记

哪有那么多的“逆袭”,唯有努力与坚持,机会就会在前方。鲁直,1989年生,本科毕业于浙江工业大学,之后被校招进阿里巴巴。虽然,今年刚刚30岁,但他已是蚂蚁金服SOFA中间件开源负责人。看到这个开头,是不是觉得我们要向大家讲述一个普通程序员励志“逆袭”的故事?不,并不是这样。前4年,他的人生剧本和别人并没有什么不同但机会总是留给有准备的人“当时就是不想考研究生,而刚好阿里给的offer又能让我在杭州‘活’下去。”鲁直推了推眼镜,淡淡地说。2009年,鲁直报名参加了阿里和浙江工业大学校企合作的实习项目,经过1年的实习期,他在毕业季里成功拿下了B2B团队的offer。最初的时候,鲁直对于业务架构根本谈不上了解,只是每天重复着很普通,甚至是略微枯燥的代码工作。在当时,鲁直的工作就是做产品的研发以及业务系统的开发。每天想着的是建模和现在的业务模型是否匹配,IE6下能不能兼容……和大多数踏出校门、初入职场的大学生没什么不同。鲁直的生活和职业几乎谈不上计划,更谈不上梦想。但是,努力和幸运,让鲁直的人生轨迹逐渐发生变化。“当时的主管对我们说,因为我们刚进公司不久,在技术方面还需要更多的提升。”于是,鲁直就一头扎进开源代码的研究与分析中。在那时,业界的开源意识并不像现在这般普及,但鲁直他们组织的“半民间”开源兴趣小组却坚持了近2年的时间,一帮技术新人相互陪伴着学习开源,看代码,互相指出不足。不断的学习让鲁直对于中间件的兴趣日渐浓厚,他很想在这一领域进行尝试。终于,机会来了!有个同事提议推出一个研发效率提升工具,并被当时的技术主管知道了,他给了鲁直和这个同事一个月的时间把这个工具做出来,而且先不用管业务的事儿。于是,两个人用了一个月的时间,最终拿到结果:一款研发效率提升工具。从看书自学,到组成小团队一起研究代码,再到这次的实操,鲁直在B2B团队3年时间,想清楚了自己究竟要什么。“当时,就认定了自己想要去做中间件”,鲁直说,“而且阿里也有完备的人员流动机制。”于是,鲁直作出了一个重要的决定——从B2B团队转岗到蚂蚁金服中间件SOFA团队。那个属于鲁直的机会终于来了。在SOFA中间件团队5年的挑战与成长学习使人进步如愿以偿,鲁直进入了蚂蚁金服中间件SOFA团队,但这并不意味着是一片坦途。“更忙了,也更充实了;更有趣了,但挑战也更大了。”鲁直略带兴奋地告诉笔者,转岗后,他感受最明显的是角色发生了变化。“之前在业务团队的时候,我只需要具备业务视角即可。但是中间件不一样,需要充分考虑用户的感受。中间件的用户都是研发人员,我需要考量他们的使用场景和习惯等,甚至是在单词拼写以及命名规范等细节。”鲁直说,他必须要较真,因为程序员很多时间都花在变量命名上。随着对中间件的深入,鲁直发现,自己进入了“Hard”模式,之前那些认知看上去都不太管用,甚至有一些可笑。正是基于这种警醒,鲁直知道,不断学习才是自己唯一可选的路。于是,鲁直一头扎进书海,到处找中间件相关的书籍,从最底层的基础理论学起;然后将这些理论知识应用于实际的工作中。为此,鲁直主动要求做很多的支持工作。一段时间之后,鲁直很快就了解了所负责的中间件产品的细节,并快速地积累了解决问题的经验。“这段经历还是蛮有意思的。如果当时只是一味地读死书,而没有将其用于工作中,我想我可能没有那么大的提升。”鲁直感慨到。如果说学习让鲁直感到了愉悦,那么在中间件团队工作期间,收获的“痛苦”又是什么?——“你不知道,项目进度带来的压力真心大。”鲁直说。2016年双11,鲁直所在的SOFA团队负责弹性架构的改造,但其中一个非常老的协议却成了弹性架构下的“bug”。“都知道双11那种紧张的气氛,跟打仗没什么区别。”鲁直说,“架构改造的工作当时因为这个‘bug’而停滞了,整个团队不仅周末连续加班,身体疲惫不已;心理的压力更大。”然而,除了迎难而上,别无它途。鲁直和小伙伴们一起不断对协议进行深入的分析,不断地定制针对性的修复方案,终于让业务顺利升级中间件,平稳地支持了双11。“当时真的是身心俱疲,可以说是非常痛苦了。但最终,我们还是完成了任务。”鲁直说着,镜片后闪过一丝坚定的眼神。在鲁直看来,在越困难的时期,越需要逼自己一把,所谓破釜沉舟,大概如此吧。“SOFA这个名字的来历还挺有意思的,是我们的CTO鲁肃取的名字,里面包含两层意思,一是按照当时的技术趋势,要做面向服务的架构,即Service Oriented Architecture,但加入了金融业务,所以是ServiceOriented Fabric Architecture;二是希望能够像‘沙发’一样,让工程师可以非常爽地工作。”2018年4月,可以让工程师们非常爽的SOFA正式开源了!“现在,SOFA在蚂蚁金服有将近2000个应用,是在蚂蚁业务场景下被不断验证和锤炼的一套框架。”鲁直表示,“把SOFA开源出去,让更多的人使用,对于SOFA未来的发展极具意义。”鲁直告诉笔者,开源的意义就是给技术的发展装上轮子。9年阿里轨迹,一个普通又特别的“码农”越自律越自由程序员的头发,一直是一个不太好玩的老梗。尽管鲁直的头发仍然浓密,但还是能看到在危险边缘疯狂试探的发际线。尽管团队的小伙伴称其为“鲁大师”,但鲁直一直强调自己不过是个平凡的“码农”。“如果哪天,我在阿里的成长完全停滞了,那也就是我离开的时候了。”鲁直悠悠地说到。从大学毕业就在这家公司,9年,是一份执着的坚持。他认为,自己之所以能在阿里巴巴有所成,是因为自己很幸运,在工作中找到了自己的热爱,于是,所有的辛苦都不再是前行的负重,而是助推力。对于中间件的喜欢,以及阿里巴巴和蚂蚁金服提供的阔大舞台,让自己不断面对挑战,不断去学习,不断地成长。鲁直喜欢跑步,即便是天气再冷也会跑个5公里;而且他也喜欢马拉松,陆续坚持了8年之久。在作息时间方面,鲁直也有着“严格”的标准,他要求自己尽量在12点之前睡觉。“熬夜对身体真的不好,而且我跑步也是为了锻炼身体,但这些都其实是我对自己的一些要求。”鲁直说。不管作息规律也好,跑步也好,都可以视作是鲁直对自己的严格自律。鲁直在用自己的行动诠释“越自律越自由”。那些对开源有兴趣的小伙伴们,鲁直给出了自己的建议。“参与开源,一个错别字也是开始。根据对项目了解的深入程度,可以从找错别字、命名规范等找错开始,由浅入深,再去提出Issue、提交Bug。相信所有的开源项目维护者都会非常地欢迎大家一起参与、多提一些意见。”最后,鲁直引用他最喜欢的程序员Jamie Zanwinski的一句话与大家共勉:痛苦造就性格。在舒适的状态下,很多的人表现是差不多的,但是在逆境中,一些人内心非常深处的想法和力量才能被充分发挥出来。SOFA是什么?SOFA(Scalable OpenFinancial Architecture),蚂蚁金服自主研发的金融级分布式中间件,包含了构建金融级云原生架构所需的各个组件,包括微服务研发框架,RPC 框架,服务注册中心,分布式定时任务,限流/熔断框架,分布式链路追踪,分布式高可用消息队列,分布式事务框架等组件。简单来说,SOFA就是包含一整套组件的金融级分布式中间件。诞生于支付宝第2代技术系统的服务化,最开始只有一套框架,后来逐渐形成了一整套完整组件。SOFA和传统金融架构的区别1、传统的金融IT架构一般采取集中式,通过购入大型机小型机解决数据问题,拓展性弱且机器成本高昂。2、SOFA则采取分布式的架构,在高并发交易处理能力、强一致性、秒级容灾和弹性伸缩上都有突出的表现。譬如面对双11流量洪峰时,完全可以准备PC级的服务器去支撑,弹性伸缩。本文作者:越自律越自由阅读原文本文来自云栖社区合作伙伴“阿里技术”,如需转载请联系原作者。

January 25, 2019 · 1 min · jiezi

分布式理论之BASE理论

系列文章 -> 分布式理论分布式理论之CAP定理(布鲁尔定理)分布式理论之BASE理论什么是BASE理论如前文中说CAP定理是三个单词的缩写,BASE也是一样,是由Basically Available(基本可用),Soft state(软状态),和 Eventually consistent(最终一致性)三个短语的缩写。为什么会出现BASE理论我们在CAP理论讨论过CAP三者之间的权衡,而BASE理论是ebay架构师来源于对大型互联网分布式实践的总结,对于互联网来说,分区容错性(P)必须满足,我们权衡的是一致性(C)和可用性(A),而我们又希望在满足P的前提下能让C和A都满足,我们该怎么处理?核心思想虽然无法做到强一致性(Strong consistency),但可以根据业务自身的特点,采用适当的方式来达到最终一致性(Eventual consistency)名词解释Basically Available(基本可用)基本可用是相对于正常的系统来说的,常见如下情况响应时间上的损失:正常情况下的搜索引擎0.5秒即返回给用户结果,而基本可用看的搜索结果可能要1秒,2秒甚至3秒(超过3秒用户就接受不了了)功能上的损失:在一个电商网站上,正常情况下,用户可以顺利完成每一笔订单,但是到了促销时间,可能为了应对并发,保护购物系统的稳定性,部分用户会被引导到一个降级页面Soft state(软状态)软状态是相对原子性来说的原子性(硬状态) -> 要求多个节点的数据副本都是一致的,这是一种"硬状态"软状态(弱状态) -> 允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延迟Eventually consistent(最终一致性)弱一致性 和强一致性相对 系统并不保证连续进程或者线程的访问都会返回最新的更新过的值。系统在数据写入成功之后,不承诺立即可以读到最新写入的值,也不会具体的承诺多久之后可以读到。但会尽可能保证在某个时间级别(比如秒级别)之后,可以让数据达到一致性状态。最终一致性是弱一致性的特定形式亚马逊CTO Werner Vogels在于2008年发表的一篇文章中对最终一致性进行了非常详细的介绍【英文】All Things Distributed【译】最终一致性官方解释 系统能够保证在没有其他新的更新操作的情况下,数据最终一定能够达到一致的状态,因此所有客户端对系统的数据访问最终都能够获取到最新的值。对于软状态,我们允许中间状态存在,但不可能一直是中间状态,必须要有个期限,系统保证在没有后续更新的前提下,在这个期限后,系统最终返回上一次更新操作的值,从而达到数据的最终一致性,这个容忍期限(不一致窗口的事件)取决于通信延迟,系统负载,数据复制方案设计,复制副本个数等,DNS是一个典型的最终一致性系统。最终一致性模型变种因果一致性(Causal consistency)如果节点A在更新完某个数据后通知了节点B,那么节点B的访问修改操作都是基于A更新后的值,同时,和节点A没有因果关系的C的数据访问则没有这样的限制读己之所写(Read your writes)因果一致性的特定形式,一个节点A总可以读到自己更新的数据会话一致性(Session consistency)访问存储系统同一个有效的会话,系统应保证该进程读己之所写单调读一致性(Monotonic read consistency)一个节点从系统中读取一个特定值之后,那么该节点从系统中不会读取到该值以前的任何值单调写一致性(Monotonic write consistency)一个系统要能够保证来自同一个节点的写操作被顺序执行(保证写操作串行化)实践中,往往5个系统进行排列组合,当然,不只是分布式系统使用最终一致性,关系型数据库在某个功能上,也是使用最终一致性的,比如备份,数据库的复制过程是需要时间的,这个复制过程中,业务读取到的值就是旧的。当然,最终还是达成了数据一致性。这也算是一个最终一致性的经典案例BASE和ACID的区别与联系参考ACID维基百科 ACID,是指数据库管理系统(DBMS)在写入或更新资料的过程中,为保证事务(transaction)是正确可靠的,所必须具备的四个特性:原子性(atomicity,或称不可分割性)、一致性(consistency)、隔离性(isolation,又称独立性)、持久性(durability)原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。隔离性:数据库允许多个并发事务同时对齐数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。ACID是传统数据库常用的设计理念, 追求强一致性模型。BASE支持的是大型分布式系统,提出通过牺牲强一致性获得高可用性。ACID和BASE代表了两种截然相反的设计哲学。总的来说,BASE 理论面向大型高可用可扩展的分布式系统,与ACID这种强一致性模型不同,常常是牺牲强一致性来获得可用性,并允许数据在一段时间是不一致的。虽然两者处于【一致性-可用性】分布图的两级,但两者并不是孤立的,对于分布式系统来说,往往依据业务的不同和使用的系统组件不同,而需要灵活的调整一致性要求,也因此,常常会组合使用ACID和BASE。参考All Things Distributed[译]最终一致性CAP原理、一致性模型、BASE理论和ACID特性分布式理论(二) - BASE理论ACID从分布式一致性谈到CAP理论、BASE理论

January 25, 2019 · 1 min · jiezi

分布式理论之CAP定理(布鲁尔定理)

定义在理论计算机科学中,CAP定理(CAP theorem),又被称作布鲁尔定理(Brewer’s theorem),它指出对于一个分布式计算系统来说,不可能同时满足以下三点选项具体意义一致性(Consistency)所有节点访问同一份最新的数据副本可用性(Availability)每次请求都能获取到非错的响应,但是不保证获取的数据为最新数据分区容错性(Partition tolerance)分布式系统在遇到任何网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务,除非整个网络环境都发生了故障而CAP指的就是上述三个指标的首字母分别解释每个指标一致性(Consistency)在写操作完成后开始的任何读操作都必须返回该值,或者后续写操作的结果也就是说,在一致性系统中,一旦客户端将值写入任何一台服务器并获得响应,那么之后client从其他任何服务器读取的都是刚写入的数据用如下系统进行解释客户端向G1写入数据v1,并等待响应此时,G1服务器的数据为v1,而G2服务器的数据为v0,两者不一致接着,在返回响应给客户端之前,G2服务器会自动同步G1服务器的数据,使得G2服务器的数据也是v1一致性保证了不管向哪台服务器(比如这边向G1)写入数据,其他的服务器(G2)能实时同步数据G2已经同步了G1的数据,会告诉G1,我已经同步了G1接收了所有同步服务器的已同步的报告,才将“写入成功”信息响应给clientclient再发起请求,读取G2的数据此时得到的响应是v1,即使client从未写入数据到G2可用性(Availability)系统中非故障节点收到的每个请求都必须有响应在可用系统中,如果我们的客户端向服务器发送请求,并且服务器未崩溃,则服务器必须最终响应客户端,不允许服务器忽略客户的请求分区容错性(Partition tolerance)允许网络丢失从一个节点发送到另一个节点的任意多条消息,即不同步也就是说,G1和G2发送给对方的任何消息都是可以放弃的,也就是说G1和G2可能因为各种意外情况,导致无法成功进行同步,分布式系统要能容忍这种情况。CAP三者不可能同时满足假设确实存在三者能同时满足的系统那么我们要做的第一件事就是分区我们的系统,由于满足分区容错性,也就是说可能因为通信不佳等情况,G1和G2之间是没有同步接下来,我们的客户端将v1写入G1,但G1和G2之间是不同步的,所以如下G1是v1数据,G2是v0数据。由于要满足可用性,即一定要返回数据,所以G1必须在数据没有同步给G2的前提下返回数据给client,如下接下去,client请求的是G2服务器,由于G2服务器的数据是v0,所以client得到的数据是v0很明显,G1返回的是v1数据,G2返回的是v0数据,两者不一致。其余情况也有类似推导,也就是说CAP三者不能同时出现。CAP三者如何权衡我们权衡三者的关键点取决于业务放弃了一致性,满足分区容错,那么节点之间就有可能失去联系,为了高可用,每个节点只能用本地数据提供服务,而这样会容易导致全局数据不一致性。对于互联网应用来说(如新浪,网易),机器数量庞大,节点分散,网络故障再正常不过了,那么此时就是保障AP,放弃C的场景,而从实际中理解,像门户网站这种偶尔没有一致性是能接受的,但不能访问问题就非常大了。对于银行来说,就是必须保证强一致性,也就是说C必须存在,那么就只用CA和CP两种情况,当保障强一致性和可用性(CA),那么一旦出现通信故障,系统将完全不可用。另一方面,如果保障了强一致性和分区容错(CP),那么久具备了部分可用性。实际究竟应该选择什么,是需要通过业务场景进行权衡的(并不是所有情况都是CP好于CA,只能查看信息但不能更新信息有时候还不如直接拒绝服务)参考CAP原则(CAP定理)、BASE理论分布式理论(一) - CAP定理CAP定理An Illustrated Proof of the CAP Theorem

January 24, 2019 · 1 min · jiezi

.NET环境大规模使用OpenTracing

作者:Austin ParkerOpenTracing的最大优势之一是围绕它构建的社区,涵盖各种语言和技术。考虑到这一点,我很高兴今天在OpenTracing博客上发表一篇由Aaron Stannard撰写的客座文章。Aaron Stannard是Petabridge的创始人兼首席执行官,Petabridge是一家帮助.NET公司构建大规模分布式系统的创业公司。他也是Akka.NET项目的联合创始人。你可以在Twitter上找到他,网址是https://twitter.com/Aarononth…在过去的五年里,我一直担任Akka.NET开源项目的维护者和联合创始人之一,该项目是最初在Scala开发,极受欢迎的Akka项目的C#和F#移植。我最初开始这个项目,是因为.NET生态系统缺乏用于构建实时大型应用程序类型的工具和框架,就像那时我在MarkedUp开发的那种类型,MarkedUp是我运行的营销自动化和分析的初创公司。在关闭MarkedUp后,我继续创建了Petabridge,这是一家致力于在.NET中支持和开发Akka.NET,和其他分布式系统技术的开源公司。我很高兴地报告说,现在.NET社区有一个更强大的开源生态系统,并且有更多的工具选择,可用于构建我在2013-14年工作的.NET中的大规模应用程序类型。随着.NET Core的出现,整个.NET生态系统正在发生巨大变化,.NET Core是一种高性能,轻量级和100%跨平台的.NET运行时(runtime)的新实现。这为.NET开发者开辟了一个新的可能性,而这之前根本就没有。使用Akka.NET和Actor模型的大规模.NETAkka和Akka.NET,如果你还没有听说过,是在通用虚拟机(分别是JVM和CLR)之上构建的actor模型的实现。演员(actor)模型是一个可追溯到早期20世纪70年代的旧概念,但近年来它重新焕发活力,因为它提供了一种易于在大型数据中心或公共云环境中分发,可理解的计算模型。你问,“可理解的计算模型”做什么?具体来说,actor模型为需要构建可扩展实时系统的开发者,找到了一个家,例如:多人视频游戏;分析(Analytics);营销自动化;医疗/医疗物联网;物流、交通和运输;能源;金融;和实时交易处理(ACH、支付处理器等)所有这些应用程序的共同点是,它们履行了对客户和利益相关者的义务,他们必须能够以一致的快速(实时)方式完成工作,而不管系统的总量(可扩展)。为了使这些应用程序满足这两个目标,它们必须是有状态的,这意味着真实的来源来自应用程序内存,而不是外部数据库。为了使有状态应用既具有容错性,和高可用性,它们也必须分散(decentralized),状态不能集中在一个区域,否则系统容易受到单点瓶颈和单点故障限制的影响。这是actor模型允许开发者做的事情:构建高度分散、容错、有状态的应用程序,其中每个工作(actor)单元都是自包含的私有状态,不能直接从外部修改。修改actor的状态的唯一方法,是通过向该actor发送一条消息,该actor最终将处理该消息,从而可能导致更新actor的状态。在.NET中,Akka.NET是构建这些类型应用程序的主要actor模型实现,它被数百家公司使用,包括戴尔、美国银行、波音、S&P Global、Becton Dickinson、美国能源部,Zynga等等。然而,演员模型为试图大规模采用它的软件团队提出了一些重大挑战,其中最痛苦的一个是大规模诊断和调试编程错误和网络相关问题。这就是OpenTracing和分布式跟踪的登场时间。使用OpenTracing以低成本了解复杂性Akka.NET和大规模分布式演员的问题在于,在任何特定时间,你的系统每秒都可以进行数千万次交互,看起来与此太相似:Akka.NET ActorSystem中的每个actor通常都有一些少量的自包含状态,一些消息处理代码执行其实际工作,以及一些对它经常与之通信的其他actor的引用。演员通过来回传递消息来相互通信。默认情况下,在actor模型中传递的消息100%是异步的,actors一直按照它们被发送的顺序处理消息,但是一个actor可能必须处理来自许多其他actor的消息。Actor可以跨进程和网络边界透明地相互通信,因此,发送到一个进程内的单个actor的消息可能最终传播到多个进程。其中存在的问题是:这种位置透明性,使得演员如此擅长以可扩展的方式分配工作,这可能会使他们在生产中出现问题时进行调试时非常令人沮丧:知道出现问题的地点和时间变成一个非凡问题,尤其是当你有数百万次这样的操作一直在发生时。这是我们发现OpenTracing特别有用的地方。Akka.NET应用程序不作为单线程,单体进程存在,它们是高度并发且通常是分布式的进程。因此.NET中常见的传统跟踪工具,如Intellitrace,通常无法帮助我们回答系统内部“出了什么问题?”。我们需要的是分布式跟踪工具,它们可以从多个进程收集上下文,将它们关联在一起,并从分布式系统的角度讲述完整的故事。我们需要能够回答诸如“akka.tcp://ClusterSys@10.11.22.248:1100/user/actorA/child2收到msg1后,发送给akka.tcp://ClusterSys@10.11.22.249:1100/user/processB/child1的是什么?”,只有在这两个进程上运行的分布式跟踪工具,才能有效地回答这个问题,而这正是我们在Petabridge上使用OpenTracing的原因。OpenTracing实施和优势Petabridge专业支持大规模采用Akka.NET的用户,这意味着我们必须提供各种工具来帮助他们的生活更轻松。这就是为什么我们开始创建Phobos,这是Akka.NET的监控和跟踪解决方案。我们希望通过开发某种分布式跟踪实现,帮助我们的用户解决这个Akka.NET可观察性问题,这些实现可以轻松地包含在他们的应用程序代码。但我们遇到了一个小问题:我们的客户无法接受单一供应商的解决方案作应用程序性能监视,他们肯定不会接受只适用于Akka.NET,而不适用于其他重要的.NET技术,如ASP.NET Core和SignalR。OpenTracing为我们优雅而简单地解决了这个问题:通过瞄准OpenTracing标准,而不是任何单一的销售解决方案,如Zipkin或Jaeger,我们可以为我们的客户打开门口,让他们选择他们想要的任何跟踪解决方案。我们也知道,我们很可能会为.NET用户创建一些兼容OpenTracing的驱动程序,他们希望能够使用我们和其他依赖该标准的产品。因此,我们针对优秀的OpenTracing C#库构建了Phobos的跟踪功能,并设计了Zipkin和Jaeger等工具基于OpenTracing绑定的第一方集成。这大大降低了我们的开发成本,增加了用户享受的选择自由。每次演员发送或接收消息时,我们都会创建一个新的Span,并将跟踪标识符传播到我们在演员之间传递的每条消息中,包括通过网络传递。我们能够构建所有这些,因此它在幕后工作,而不需要太多的手动仪器(instrumentation)。可以肯定的是,OpenTracing允许我们使用Jaeger制作像这样的可理解的图形:在这种情况下,我们正在建模一个“扇出”(“fan out”)调用,其中一个节点通过网络向许多其他节点发出呼叫,使用传统工具难以捕获的东西,因为它涉及多个节点上的大量并发处理和每个人之间的异步沟通。但是使用OpenTracing的标准,我们很容易使用像Jaeger这样的工具来实现这一点,Jaeger在C#中有一个很好的OpenTracing兼容驱动程序。在.NET中创建OpenTracing驱动程序一旦Phobos完全支持OpenTracing,作为我们最终用户的集成点,我们就知道任何拥有内部或第三方跟踪解决方案,但本身不支持OpenTracing的Akka.NET用户最终都可以找到一种方法使用OpenTracing库来将事情联系在一起。但是,我们决定加倍努力,采用一些已经在.NET社区中流行的现有工具,或者通过为这些产品推出第一方OpenTracing驱动程序和适配器来降低进入门槛。我们建造的第一个是Petabridge.Tracing.Zipkin,一个用于Zipkin的高性能OpenTracing兼容驱动程序;我们想在内部使用Zipkin,并希望原生支持像Kafka的传送选项。在许多.NET用户的要求下,我们构建的第二个也是更有趣的是Microsoft Application Insights OpenTracing适配器,用于我们的Akka.NET跟踪产品。对Azure上运行的用户,我们希望能够支持Application Insights作为的跟踪目标,但是没有用于将Application Insights插入OpenTracing的内置解决方案。因此,我们遵循了Microsoft团队编写的标准文档,该文档允许我们在OpenTracing的词典之上映射Application Insights常规,并且能够创建一个开源软件包Petabridge.Tracing.ApplicationInsights,它弥合了这两者之间的差距技术,使Application Insights在大型Akka.NET应用程序中完美可行。我们在发布软件包之后发现,即便是微软本身也在使用OpenTracing和我们的Application Insights驱动程序来内部测试他们自己的一些云应用程序。对于整个.NET生态系统中的每个人来说,这是一件好事:随着OpenTracing继续获得牵引力,它将有助于推动其作为行业标准实践的使用。随着我们继续推动大规模.NET系统的规模和速度的界限,像我们这样的组织将继续投资OpenTracing等技术,以及其有前途的监控对手OpenMetrics,以限制运行这些系统的运营和管理成本。到目前为止,OpenTracing已经为我们的公司和整个Akka.NET项目带来了惊人的表现,我们期待在未来看到更多。-Aaron Stannard,Petabridge首席执行官Akka.NET项目联合创始人2019年KubeCon + CloudNativeCon中国论坛提案征集(CFP)现已开放KubeCon + CloudNativeCon 论坛让用户、开发人员、从业人员汇聚一堂,面对面进行交流合作。与会人员有 Kubernetes、Prometheus 及其他云原生计算基金会 (CNCF) 主办项目的领导,和我们一同探讨云原生生态系统发展方向。2019年中国开源峰会提案征集(CFP)现已开放在中国开源峰会上,与会者将共同合作及共享信息,了解最新和最有趣的开源技术,包括 Linux、容器、云技术、网络、微服务等;并获得如何在开源社区中导向和引领的信息。大会日期:提案征集截止日期:太平洋标准时间 2 月 15 日,星期五,晚上 11:59提案征集通知日期:2019 年 4 月 1 日会议日程通告日期:2019 年 4 月 3 日幻灯片提交截止日期:6 月 17 日,星期一会议活动举办日期:2019 年 6 月 24 至 26 日2019年KubeCon + CloudNativeCon + Open Source Summit China赞助方案出炉啦

January 22, 2019 · 1 min · jiezi

关于开源分布式事务中间件Fescar,我们总结了开发者关心的13个问题

开源分布式事务中间件 Fescar 自1月10日上线v0.1版本以来,受到了开发者们的极大关注(watch249,star3005,fork649,社区讨论的issue58,数据统计于1月17日14:00),可见,天下苦分布式事务久矣。为此,我们收集了大家在社区(Github)和社群(钉钉群&微信群)关注的核心问题,总结如下,并给出回复。Q1:Fescar 的发展经历了哪些历程?和阿里云全局事务服务GTS之间是什么关系?A1:阿里巴巴是国内最早一批进行应用分布式(微服务化)改造的企业,所以很早就遇到微服务架构下的分布式事务问题。2014 年阿里巴巴中间件团队发布TXC(Taobao Transaction Constructor),为集团内应用提供分布式事务服务。2016 年TXC 经过产品化改造,以GTS(Global TransactionService)的身份上线阿里云,成为当时业界唯一一款云上分布式事务产品,以阿里云公有云或专有云解决方案的形式,交付给众多外部客户。2019 年基于 TXC 和 GTS 的技术积累,阿里巴巴中间件团队发起了开源项目Fescar(Fast & EaSy Commit And Rollback, FESCAR),和社区一起建设这个分布式事务解决方案。TXC/GTS/Fescar一脉相承,为解决微服务架构下的分布式事务问题交出了一份与众不同的答卷。Q2:Fescar 有哪些适用场景?A2:Fescar 的愿景是让分布式事务的使用像现在本地事务的使用一样简单、高效,最终的目标是希望可以适用于所有的分布式事务场景。目前,核心的 AT 模式适用于构建于支持本地 ACID 事务的关系型数据库。非关系型数据库类资源的管理,通过 MT 模式来支持。AT 模式与 MT 模式完全兼容,所以可以在同一个分布式事务中,同时管理两类资源。Q3:目前有已经有一些其他的分布式事务开源方案,Fescar 和他们之间有哪些区别?和JTA支持分布式事务有哪些区别?A3:既有的分布式事务解决方案按照对业务侵入性分为两类,即:对业务无侵入的和对业务有侵入的。业务无侵入的方案既有的主流分布式事务解决方案中,对业务无侵入的只有基于 XA 的方案(注:问题中提到的 JTA 是XA 方案的 Java 版本),但应用XA 方案存在 3 个方面的问题:1、要求数据库提供对 XA 的支持。如果遇到不支持 XA(或支持得不好,比如 MySQL 5.7 以前的版本)的数据库,则不能使用。2、受协议本身的约束,事务资源(数据记录、数据库连接)的锁定周期长。长周期的资源锁定从业务层面来看,往往是不必要的,而因为事务资源的管理器是数据库本身,应用层无法插手。这样形成的局面就是,基于 XA 的应用往往性能会比较差,而且很难优化。3、已经落地的基于 XA 的分布式解决方案,都依托于重量级的应用服务器(Tuxedo/WebLogic/WebSphere 等),这是不适用于微服务架构的。侵入业务的方案实际上,最初分布式事务只有 XA 这个唯一方案。XA 是完备的,但在实践过程中,由于种种原因(包含但不限于上面提到的3 点)往往不得不放弃,转而从业务层面着手来解决分布式事务问题。比如:基于可靠消息的最终一致性方案TCCSaga都属于这一类。这些方案的具体机制在这里不做展开,网上这方面的论述文章非常多。总之,这些方案都要求在应用的业务层面把分布式事务技术约束考虑到设计中,通常每一个服务都需要设计实现正向和反向的幂等接口。这样的设计约束,往往会导致很高的研发和维护成本。不可否认,侵入业务的分布式事务方案都经过大量实践验证,能有效解决问题,在各行种业的业务应用系统中起着重要作用。但回到原点来思考,这些方案的采用实际上都是迫于无奈。回到问题:与基于消息的最终一致、TCC、Saga等业务逻辑侵入方案的不同在于,Fescar 的设计初衷就是保持对业务的非侵入性,不要求业务层面按照分布式事务的特定场景来设计正向和反向的两套(甚至多套)业务逻辑。这方面的差别就不展开了。与 XA 的区别在于,设计了一套不同与 XA 的两阶段协议,在保持对业务不侵入的前提下,保证良好的性能,也避免了对底层数据库协议支持的要求。可以看作是一套轻量级的XA 机制。具体的差别如下:架构层次XA方案的 RM 实际上是在数据库层,RM本质上就是数据库自身(通过提供支持 XA 的驱动程序来供应用使用)。而 Fescar 的RM 是以二方包的形式作为中间件层部署在应用程序这一侧的,不依赖与数据库本身对协议的支持,当然也不需要数据库支持XA 协议。这点对于微服务化的架构来说是非常重要的:应用层不需要为本地事务和分布式事务两类不同场景来适配两套不同的数据库驱动。这个设计,剥离了分布式事务方案对数据库在协议支持上的要求。两阶段提交先来看一下 XA 的2PC 过程。无论 Phase2 的决议是commit 还是 rollback,事务性资源的锁都要保持到Phase2 完成才释放。再看 Fescar 的2PC 过程。分支事务中数据的 本地锁 由本地事务管理,在分支事务 Phase1 结束时释放。同时,随着本地事务结束,连接 也得以释放。分支事务中数据的 全局锁 在事务协调器侧管理,在决议 Phase2 全局提交时,全局锁马上可以释放。只有在决议全局回滚的情况下,全局锁 才被持有至分支的 Phase2 结束。这个设计,极大地减少了分支事务对资源(数据和连接)的锁定时间,给整体并发和吞吐的提升提供了基础。Q4:Fescar 支持 Dubbo 的哪些版本?A4:所有版本。Q5:Fescar 支持 Spring Cloud么?A5:Fescar 与微服务框架的接口点在于,需要把事务的唯一标识 XID(一个字符串)通过微服务框架的服务调用间调用的机制中,透明地传递,并通过 Fescar 的 API 来绑定(或解绑)到应用的线程上下文中。(机制可以参考内置的对 Dubbo 支持的实现 com.alibaba.fescar.dubbo.TransactionPropagationFilter)所以,本质上这个问题不是支不支持 Spring Cloud,而是如何支持 Spring Cloud 中选用的服务调用机制。目前正在和 Spring Cloud Alibaba 的同学合作,准备在v0.5.x版本(或更早)发布对 Spring Cloud默认的支持。同时,非常欢迎社区的朋友参与进来,贡献包括 Spring Cloud 在内的各类微服务框架的支持。Q6:Fescar 是否支持本地跨库多数据源?除了关系型数据库,是否还支持NoSQL数据库?A6:本地跨多数据源同样是支持的,在 Fescar 的架构中,同一个服务中的多个数据源与跨服务的多个数据源,没有本质区别。AT 模式目前仅限于对关系型数据库的支持(本身具备ACID 事务支持),后面会发布出来的 MT 模式可以支持 NoSQL 这类本身不具备本地事务支持的资源。Q7:Fescar 现在开源的是AT模式,MT模式暂时不支持,什么时候会开源?A7:当前 0.1.0 版本只是把 Fescar 最核心的 AT 模式的最小集发布出来,一方面是按开源的规划和架构的重构进展,另一方面也是希望通过最小集版本,让用户和开发者社区更容易理解到我们核心的设计思路,让更多人比较容易地参与进来建设,而不是完全由阿里巴巴主导,仅仅把我们的整套方案开源出来给大家用而已。阿里巴巴在分布式事务上的技术积累,我们会通过 Fescar 项目毫无保留地贡献给社区,所有功能特性都会按规划和社区的反馈陆续开源出来。MT 按初步的计划,会在 0.5.x 版本发布。Q8:Fescar 什么时候提供HA cluster,单节点的server的瓶颈如何处理?A8:按初步的计划,HA Cluster 会在 0.5.x 版本发布,解决单机部署的单点问题。Q9:因网络中断、网张闪断、节点宕机和超时等引起的异常,Fescar会提供相应的补偿措施么?A9:这些异常情况的处理是分布式事务解决方案的基本要求,Fescar 同样也是提供了整套方案来处理各类异常场景。这方面的具体机制会在 HA Cluster 版本发布时,给出全面的分析介绍。Q10:Fescar框架中,如何监控分布式事务?A10:监控是非常重要的一块儿内容。TXC 和 GTS 的监控在阿里巴巴内部使用了很多基础设施的辅助。而在开源版本中,我们还没有一个现成的监控方案。大体上,监控的基础是两个方面:一方面是日志,通过日志的采集和处理,可以形成一个完整的事务链路,这些数据对于业务层面的分析和调优是重要的参考依据。另一方面是 API,Fescar 会提供一套管控 API,用于对运行时事务的管理。我们后续会把这两方面的数据格式、部署形态及接口整理出来,希望和社区来共建监控这个重要的方面。Q11:Fescar 的roadmap 有了么?A11:目前最新的roadmap如下:v0.1.0微服务框架支持: Dubbo数据库支持: MySQL基于 Spring AOP 的 Annotation事务协调器: 单机版本v0.5.x微服务框架支持: Spring CloudMT 模式支持 TCC 模式事务的适配动态配置和服务发现事务协调器: 高可用集群版本v0.8.xMetrics控制台: 监控/部署/升级/扩缩容v1.0.0General Availability: 生产环境适用v1.5.x数据库支持: Oracle/PostgreSQL/OceanBase不依赖 Spring AOP 的 Annotation热点数据的优化处理机制RocketMQ 事务消息纳入全局事务管理NoSQL 纳入全局事务管理的适配机制支持 HBase支持 Redisv2.0.0支持 XA当然,项目迭代演进的过程,我们最重视的是社区的声音,路线图会和社区充分交流及时进行调整。Q12:Fescar 官网什么时候上线?A12:Fescar 官方域名已经注册,官网将采用静态开源站点搭建工具Docsite「传送门」进行搭建,logo 已经设计并将于近期公布。Q13:如何加入 Fescar 社区,进行贡献,已经摩拳擦掌了。A13:我们非常欢迎大家通过各种形式参与到我们项目的建设中,包括但不限于:架构设计模块设计代码实现Bug FixDemo样例文档、网站和翻译具体的参与方法可以参见我们项目中的CONTRIBUTING 指引,或与 @eternaltingting@163.com 联系。实际上,我们并不拘泥于贡献的形式,开发者提出的每一个 issue,无论是Bug Report、改进建议或者甚至是问题咨询都代表着对项目的关注和帮助。希望 Fescar 项目和社区一起健康成长,成为分布式事务领域一个优秀的解决方案。本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

January 22, 2019 · 1 min · jiezi

Euler 今日问世!国内首个工业级的图深度学习开源框架,阿里妈妈造

阿里妹导读:千呼万唤始出来!阿里妈妈正式公布重磅开源项目——图深度学习框架Euler。这是国内首个在核心业务大规模应用后开源的图深度学习框架。此次开源,Euler内置了大量的算法供用户直接使用,相关代码已经可在GitHub上进行下载。图学习和深度学习都是人工智能的一个分支,作为阿里巴巴旗下的大数据营销平台,阿里妈妈创新性地将图学习与深度学习进行结合,推出了Euler,可帮助大幅度提升营销效率。Euler已在阿里妈妈核心业务场景进行了锤炼和验证,同时,在金融、电信、医疗等涉及到复杂网络分析的场景中也具有很高的应用价值。例如,用户可以利用Euler对基于用户交易等金融数据构建的复杂异构图进行学习与推理,进而应用于金融反欺诈等场景。下面让我们一起走进Euler的世界。Euler 开源地址 : https://github.com/alibaba/euler1. 概述过去几年随着数据规模和硬件计算力的迅速增长,深度学习技术在工业界被广泛应用并产生了巨大的技术红利。当前应用已经相对成熟,下一步的技术红利在哪里还在积极探索之中。图神经网络将端到端学习与归纳推理相结合,有望解决深度学习无法处理的关系推理、可解释性等一系列问题。对结构知识的表达、计算和组合泛化是实现具备human-like AI的关键,图神经网络有希望在这些方面形成突破,使得机器能力进一步提升,因此对图神经网络的深入应用有希望形成下一波技术红利。图作为表达能力很强的通用的数据结构,可以用来刻画现实世界中的很多问题,例如社交场景的用户网络、电商场景的用户和商品网络、电信场景的通信网络、金融场景的交易网络和医疗场景的药物分子网络等等。相比文本、语音和图像领域的数据比较容易处理成欧式空间的Grid-like类型,适合现有的深度学习模型处理,图是一种非欧空间下的数据,并不能直接应用现有方法,需要专门设计的图神经网络系统。1.1Euler的核心能力1)大规模图的分布式学习工业界的图往往具有数十亿节点和数百亿边,有些场景甚至可以到数百亿节点和数千亿边,在这样规模的图上单机训练是不可行的。Euler支持图分割和高效稳定的分布式训练,可以轻松支撑数十亿点、数百亿边的计算规模。2)支持复杂异构图的表征工业界的图关系大都错综复杂,体现在节点异构、边关系异构,另外节点和边上可能有非常丰富的属性,这使得一些常见的图神经网络很难学到有效的表达。Euler在图结构存储和图计算的抽象上均良好的支持异构点、异构边类型的操作,并支持丰富的异构属性,可以很容易的在图学习算法中进行异构图的表征学习。3)图学习与深度学习的结合工业界有很多经典场景,例如搜索/推荐/广告场景,传统的深度学习方法有不错效果,如何把图学习和传统方法结合起来,进一步提升模型能力是很值得探索的。Euler支持基于深度学习样本的mini-batch训练,把图表征直接输入到深度学习网络中联合训练。4)分层抽象与灵活扩展Euler系统抽象为图引擎层、图操作算子层、算法实现层三个层次,可以快速地在高层扩展一个图学习算法。实际上,Euler也内置了大量的算法实现供大家直接使用。1.2 Euler内置的算法实现考虑到框架的易用性,我们内置了多种知名算法以及几种我们内部的创新算法。所有实现,我们仔细进行了测试,保证了算法运行效率,且算法效果与原论文对齐。用户无需进行开发,注入数据到平台后,可以直接使用。我们内置的算法列表见下表。有关我们内部算法的详细信息请见2.3节。2. 系统设计Euler系统整体可以分为三层:最底层的分布式图引擎,中间层图语义的算子,高层的图表示学习算法。下边我们分开描述各个层次的核心功能。2.1分布式图引擎为了支持我们的业务,我们不仅面临超大规模图存储与计算的挑战,还需要处理由多种不同类型的点,边及其属性构成异构图的复杂性。我们的分布式图引擎针对海量图存储,分布式并行图计算及异构图进行了优化设计,确保了工业场景下的有效应用。首先为了存储超大规模图(数十亿点,数百亿边),Euler必须突破单机的限制,从而采用了分布式的存储架构。在图加载时,整张图在引擎内部被切分为多个子图,每个计算节点被分配1个或几个子图进行加载。为了充分利用各个计算节点的能力,在进行图的操作时,顶层操作被分解为多个对子图的操作由各个节点并行执行。这样随着更多节点的加入,我们可以得到更好的服务能力。其次,我们引入了多replica的支持。从而用户可以灵活平衡shard与replica的数量,取得更佳的服务能力。最后,我们针对图表示学习优化了底层的图存储数据结构与操作算法,单机的图操作性能获得了数倍的提升。多种不同类型的边,点与属性所组成的异构图,对很多复杂的业务场景必不可少。为了支持异构图计算能力,底层存储按照不同的节点与边的类型分别组织。这样我们可以高效支持异构的图操作。2.2中间图操作算子由于图学习算法的多样性以及业务的复杂性,固定的某几种甚至几十种算法实现无法满足客户的所有需求。所以在Euler设计中,我们围绕底层系统的核心能力着重设计了灵活强大的图操作算子,且所有算子均支持异构图操作语义。用户可以利用它来快速搭建自己的算法变体,满足独特的业务需求。首先,Euler分布式图引擎提供了C++的API来提供所有图操作。基于这个API,我们可以方便的基于某个深度学习框架添加图操作的算子,从而利用Euler C++接口访问底层图引擎的能力。我们支持广泛使用的深度学习框架,比如阿里巴巴的X-DeepLearning与流行的TensorFlow。后继我们也会考虑支持其它的深度学习框架,比如PyTorch。利用灵活的图操作算子,机器学习框架可以在每个mini-batch与Euler交互,动态扩充与组织训练样本。这样,Euler不仅支持传统的以图为中心的学习模式,且可以把图学习的能力注入传统的学习任务,实现端到端训练。按照功能分类,我们的核心系统提供的API可以分类如下:全局带权采样点和边的能力。主要用于mini-batch样本的随机生成以及Negative Sampling。基于给定节点的邻居操作。这个是图计算的核心能力包括邻居带权采样,取Top权重的邻居等。点/边的属性查找。这个能力使得算法可以使用更丰富的特征,而不仅限于点/边的ID特征。2.3高层算法实现如1.2节所述,除了LINE算法以外,我们实现的算法可以分为随机游走与邻居汇聚两大类算法。有关外部算法的详细信息,请参见1.2节提供的论文链接。下面我们详细介绍内部的三个创新算法,相关论文的链接我们会在github上给出。Scalable-GCN它是一种高效的GCN训练算法。GCN以及更一般的Graph Neural Network (GNN)类的方法由于能有效的提取图结构信息,在许多任务上均取得了超过以往方法的效果。但是GCN的模型会引入巨大的计算量,导致模型的训练时间不可接受。Scalable-GCN在保证优秀效果的前提下,把mini-batch GCN的计算复杂度从层数的指数函数压到线性。这使得在阿里妈妈的海量数据下应用三层GCN成为可能,广告匹配的效果获得了显著提升。LsHNELsHNE是我们结合阿里妈妈搜索广告场景创新地提出一种无监督的大规模异构网络embedding学习方法。区别于DeepWalk类算法,LsHNE的特点包括:a) 采用深度神经网络学习表达,可以有效融合Attribute信息;b)考虑embedding表示的距离敏感需求,提出两个负采样原则:分布一致性原则和弱相关性原则;c)支持异构网络。LasGNNLasGNN是一种半监督的大规模异构图卷积神经网络学习方法, 它有效融合了图结构知识信息和海量用户行为信息,大幅提升了模型精度,是工业界广告场景下首次应用半监督图方法。该方法有多处创新,例如将metapath的思想应用于图卷积网络中,并提出了metapathGCN模型,有效解决了异构网络的卷积问题;提出了metapathSAGE模型,在模型中我们设计高效的邻居采样的方法,使得大规模的多层邻居卷积成为可能。3. 应用实例Euler平台已经在阿里妈妈搜索广告的多个场景下广泛实用,并取得了出色的业务效果,例如检索匹配场景、CTR预估场景、营销工具场景和反作弊场景等。我们以匹配场景的为例来看下Euler的应用。广告匹配的任务是给定用户搜索请求,匹配模块通过理解用户意图,快速准确地从海量广告中找到高质量的小规模候选广告集,输送给下游的排序模块进行排序。我们首先使用一些传统的挖掘算法,从用户行为日志、内容属性等维度挖掘出Query(查询词), Item(商品)和Ad(广告)的多种关系,然后利用Euler平台的LsHNE方法学习图中节点的embedding,这里节点embedding后的空间距离刻画了原来图中的关系,对于在线过来的请求通过计算用户查询词向量、前置行为中节点向量和广告节点向量之间的距离进行高效的向量化最近邻检索,可以快速匹配到符合用户意图的广告。图2展示了LsHNE方法的离线和在线流程。具体图3展示了样本构造和网络结构示意。本文作者:让你久等了阅读原文本文来自云栖社区合作伙伴“阿里技术”,如需转载请联系原作者。

January 21, 2019 · 1 min · jiezi

阿里重磅开源首款自研科学计算引擎Mars,揭秘超大规模科学计算

摘要: 由阿里巴巴统一大数据计算平台MaxCompute研发团队,历经1年多研发,打破大数据、科学计算领域边界,完成第一个版本并开源。 Mars,一个基于张量的统一分布式计算框架。使用 Mars 进行科学计算,不仅使得完成大规模科学计算任务从MapReduce实现上千行代码降低到Mars数行代码,更在性能上有大幅提升。日前,阿里巴巴正式对外发布了分布式科学计算引擎 Mars 的开源代码地址,开发者们可以在pypi上自主下载安装,或在Github上获取源代码并参与开发。此前,早在2018年9月的杭州云栖大会上,阿里巴巴就公布了这项开源计划。Mars 突破了现有大数据计算引擎的关系代数为主的计算模型,将分布式技术引入科学计算/数值计算领域,极大地扩展了科学计算的计算规模和效率。目前已应用于阿里巴巴及其云上客户的业务和生产场景。本文将为大家详细介绍Mars的设计初衷和技术架构。*概述科学计算即数值计算,是指应用计算机处理科学研究和工程技术中所遇到的数学计算问题。比如图像处理、机器学习、深度学习等很多领域都会用到科学计算。有很多语言和库都提供了科学计算工具。这其中,Numpy以其简洁易用的语法和强大的性能成为佼佼者,并以此为基础形成了庞大的技术栈。(下图所示)Numpy的核心概念多维数组是各种上层工具的基础。多维数组也被称为张量,相较于二维表/矩阵,张量具有更强大的表达能力。因此,现在流行的深度学习框架也都广泛的基于张量的数据结构。随着机器学习/深度学习的热潮,张量的概念已逐渐为人所熟知,对张量进行通用计算的规模需求也与日俱增。但现实是如Numpy这样优秀的科学计算库仍旧停留在单机时代,无法突破规模瓶颈。当下流行的分布式计算引擎也并非为科学计算而生,上层接口不匹配导致科学计算任务很难用传统的SQL/MapReduce编写,执行引擎本身没有针对科学计算优化更使得计算效率难以令人满意。基于以上科学计算现状,由阿里巴巴统一大数据计算平台MaxCompute研发团队,历经1年多研发,打破大数据、科学计算领域边界,完成第一个版本并开源。 Mars,一个基于张量的统一分布式计算框架。使用 Mars 进行科学计算,不仅使得完成大规模科学计算任务从MapReduce实现上千行代码降低到Mars数行代码,更在性能上有大幅提升。目前,Mars 实现了 tensor 的部分,即numpy 分布式化, 实现了 70% 常见的 numpy 接口。后续,在 Mars 0.2 的版本中, 正在将 pandas 分布式化,即将提供完全兼容 pandas 的接口,以构建整个生态。 Mars作为新一代超大规模科学计算引擎,不仅普惠科学计算进入分布式时代,更让大数据进行高效的科学计算成为可能。Mars的核心能力符合使用习惯的接口Mars 通过 tensor 模块提供兼容 Numpy 的接口,用户可以将已有的基于 Numpy 编写的代码,只需替换 import,就可将代码逻辑移植到 Mars,并直接获得比原来大数万倍规模,同时处理能力提高数十倍的能力。目前,Mars 实现了大约 70% 的常见 Numpy 接口。充分利用GPU加速除此之外,Mars 还扩展了 Numpy,充分利用了GPU在科学计算领域的已有成果。创建张量时,通过指定 gpu=True 就可以让后续计算在GPU上执行。比如:a = mt.random.rand(1000, 2000, gpu=True) # 指定在 GPU 上创建(a + 1).sum(axis=1).execute()稀疏矩阵Mars 还支持二维稀疏矩阵,创建稀疏矩阵的时候,通过指定 sparse=True 即可。以eye 接口为例,它创建了一个单位对角矩阵,这个矩阵只有对角线上有值,其他位置上都是 0,所以,我们可以用稀疏的方式存储。a = mt.eye(1000, sparse=True) # 指定创建稀疏矩阵(a + 1).sum(axis=1).execute()系统设计接下来介绍 Mars 的系统设计,让大家了解 Mars 是如何让科学计算任务自动并行化并拥有强大的性能。分而治之—tileMars 通常对科学计算任务采用分而治之的方式。给定一个张量,Mars 会自动将其在各个维度上切分成小的 Chunk 来分别处理。对于 Mars 实现的所有的算子,都支持自动切分任务并行。这个自动切分的过程在Mars里被称为 tile。比如,给定一个 1000 2000 的张量,如果每个维度上的 chunk 大小为 500,那么这个张量就会被 tile 成 2 4 一共 8 个 chunk。对于后续的算子,比如加法(Add)和求和(SUM),也都会自动执行 tile 操作。一个张量的运算的 tile 过程如下图所示。延迟执行和 Fusion 优化目前 Mars 编写的代码需要显式调用 execute 触发,这是基于 Mars 的延迟执行机制。用户在写中间代码时,并不会需要任何的实际数据计算。这样的好处是可以对中间过程做更多优化,让整个任务的执行更优。目前 Mars 里主要用到了 fusion 优化,即把多个操作合并成一个执行。对于前面一个图的例子,在 tile 完成之后,Mars 会对细粒度的 Chunk 级别图进行 fusion 优化,比如8个 RAND+ADD+SUM,每个可以被分别合并成一个节点,一方面可以通过调用如 numexpr 库来生成加速代码,另一方面,减少实际运行节点的数量也可以有效减少调度执行图的开销。多种调度方式Mars 支持多种调度方式:| 多线程模式:Mars 可以使用多线程来在本地调度执行 Chunk 级别的图。对于 Numpy 来说,大部分算子都是使用单线程执行,仅使用这种调度方式,也可以使得 Mars 在单机即可获得 tile 化的执行图的能力,突破 Numpy 的单机内存限制,同时充分利用单机所有 CPU/GPU 资源,获得比 Numpy 快数倍的性能。| 单机集群模式: Mars 可以在单机启动整个分布式运行时,利用多进程来加速任务的执行;这种模式适合模拟面向分布式环境的开发调试。| 分布式 : Mars 可以启动一个或者多个 scheduler,以及多个 worker,scheduler 会调度 Chunk 级别的算子到各个 worker 去执行。下图是 Mars 分布式的执行架构:Mars 分布式执行时会启动多个 scheduler 和 多个 worker,图中是3个 scheduler 和5个 worker,这些 scheduler 组成一致性哈希环。用户在客户端显式或隐式创建一个 session,会根据一致性哈希在其中一个 scheduler 上分配 SessionActor,然后用户通过 execute 提交了一个张量的计算,会创建 GraphActor 来管理这个张量的执行,这个张量会在 GraphActor 中被 tile 成 chunk 级别的图。这里假设有3个 chunk,那么会在 scheduler 上创建3个 OperandActor 分别对应。这些 OperandActor 会根据自己的依赖是否完成、以及集群资源是否足够来提交到各个 worker 上执行。在所有 OperandActor 都完成后会通知 GraphActor 任务完成,然后客户端就可以拉取数据来展示或者绘图。向内和向外伸缩Mars 灵活的 tile 化执行图配合多种调度模式,可以使得相同的 Mars 编写的代码随意向内(scale in)和向外(scale out)伸缩。向内伸缩到单机,可以利用多核来并行执行科学计算任务;向外伸缩到分布式集群,可以支持到上千台 worker 规模来完成单机无论如何都难以完成的任务。Benchmark在一个真实的场景中,我们遇到了巨型矩阵乘法的计算需求,需要完成两个均为千亿元素,大小约为2.25T的矩阵相乘。Mars通过5行代码,使用1600 CU(200个 worker,每 worker 为 8核 32G内存),在2个半小时内完成计算。在此之前,同类计算只能使用 MapReduce 编写千余行代码模拟进行,完成同样的任务需要动用 9000 CU 并耗时10个小时。让我们再看两个对比。下图是对36亿数据矩阵的每个元素加一再乘以二,红色的叉表示 Numpy 的计算时间,绿色的实线是 Mars 的计算时间,蓝色虚线是理论计算时间。可以看到单机 Mars 就比 Numpy 快数倍,随着 Worker 的增加,可以获得几乎线性的加速比。下图是进一步扩大计算规模,把数据扩大到144亿元素,对这些元素加一乘以二以后再求和。这时候输入数据就有 115G,单机的 Numpy 已经无法完成运算,Mars 依然可以完成运算,且随着机器的增多可以获得还不错的加速比。开源地址Mars 已经在 Github 开源:https://github.com/mars-project/mars ,且后续会全部在 Github 上使用标准开源软件的方式来进行开发,欢迎大家使用 Mars,并成为 Mars 的 contributor。Mars科学计算引擎产品发布会发布直播回放>>发布活动页>> 大数据计算服务MaxCompute官网>>MaxCompute试用申请页面>>聚能聊>>本文作者:晋恒阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

January 17, 2019 · 2 min · jiezi

TiDB 在转转的业务实战

作者:陈维,转转优品技术部 RD。开篇世界级的开源分布式数据库 TiDB 自 2016 年 12 月正式发布第一个版本以来,业内诸多公司逐步引入使用,并取得广泛认可。对于互联网公司,数据存储的重要性不言而喻。在 NewSQL 数据库出现之前,一般采用单机数据库(比如 MySQL)作为存储,随着数据量的增加,“分库分表”是早晚面临的问题,即使有诸如 MyCat、ShardingJDBC 等优秀的中间件,“分库分表”还是给 RD 和 DBA 带来较高的成本;NewSQL 数据库出现后,由于它不仅有 NoSQL 对海量数据的管理存储能力、还支持传统关系数据库的 ACID 和 SQL,所以对业务开发来说,存储问题已经变得更加简单友好,进而可以更专注于业务本身。而 TiDB,正是 NewSQL 的一个杰出代表!站在业务开发的视角,TiDB 最吸引人的几大特性是:支持 MySQL 协议(开发接入成本低);100% 支持事务(数据一致性实现简单、可靠);无限水平拓展(不必考虑分库分表)。基于这几大特性,TiDB 在业务开发中是值得推广和实践的,但是,它毕竟不是传统的关系型数据库,以致我们对关系型数据库的一些使用经验和积累,在 TiDB 中是存在差异的,现主要阐述“事务”和“查询”两方面的差异。TiDB 事务和 MySQL 事务的差异MySQL 事务和 TiDB 事务对比在 TiDB 中执行的事务 b,返回影响条数是 1(认为已经修改成功),但是提交后查询,status 却不是事务 b 修改的值,而是事务 a 修改的值。可见,MySQL 事务和 TiDB 事务存在这样的差异:MySQL 事务中,可以通过影响条数,作为写入(或修改)是否成功的依据;而在 TiDB 中,这却是不可行的!作为开发者我们需要考虑下面的问题:同步 RPC 调用中,如果需要严格依赖影响条数以确认返回值,那将如何是好?多表操作中,如果需要严格依赖某个主表数据更新结果,作为是否更新(或写入)其他表的判断依据,那又将如何是好?原因分析及解决方案对于 MySQL,当更新某条记录时,会先获取该记录对应的行级锁(排他锁),获取成功则进行后续的事务操作,获取失败则阻塞等待。对于 TiDB,使用 Percolator 事务模型:可以理解为乐观锁实现,事务开启、事务中都不会加锁,而是在提交时才加锁。参见 这篇文章(TiDB 事务算法)。其简要流程如下:在事务提交的 PreWrite 阶段,当“锁检查”失败时:如果开启冲突重试,事务提交将会进行重试;如果未开启冲突重试,将会抛出写入冲突异常。可见,对于 MySQL,由于在写入操作时加上了排他锁,变相将并行事务从逻辑上串行化;而对于 TiDB,属于乐观锁模型,在事务提交时才加锁,并使用事务开启时获取的“全局时间戳”作为“锁检查”的依据。所以,在业务层面避免 TiDB 事务差异的本质在于避免锁冲突,即,当前事务执行时,不产生别的事务时间戳(无其他事务并行)。处理方式为事务串行化。TiDB 事务串行化在业务层,可以借助分布式锁,实现串行化处理,如下:基于 Spring 和分布式锁的事务管理器拓展在 Spring 生态下,spring-tx 中定义了统一的事务管理器接口:PlatformTransactionManager,其中有获取事务(getTransaction)、提交(commit)、回滚(rollback)三个基本方法;使用装饰器模式,事务串行化组件可做如下设计:其中,关键点有:超时时间:为避免死锁,锁必须有超时时间;为避免锁超时导致事务并行,事务必须有超时时间,而且锁超时时间必须大于事务超时时间(时间差最好在秒级)。加锁时机:TiDB 中“锁检查”的依据是事务开启时获取的“全局时间戳”,所以加锁时机必须在事务开启前。事务模板接口设计隐藏复杂的事务重写逻辑,暴露简单友好的 API:TiDB 查询和 MySQL 的差异在 TiDB 使用过程中,偶尔会有这样的情况,某几个字段建立了索引,但是查询过程还是很慢,甚至不经过索引检索。索引混淆型(举例)表结构:CREATE TABLE t_test ( id bigint(20) NOT NULL DEFAULT ‘0’ COMMENT ‘主键id’, a int(11) NOT NULL DEFAULT ‘0’ COMMENT ‘a’, b int(11) NOT NULL DEFAULT ‘0’ COMMENT ‘b’, c int(11) NOT NULL DEFAULT ‘0’ COMMENT ‘c’, PRIMARY KEY (id), KEY idx_a_b (a,b), KEY idx_c (c) ) ENGINE=InnoDB;查询:如果需要查询 (a=1 且 b=1)或 c=2 的数据,在 MySQL 中,sql 可以写为:SELECT id from t_test where (a=1 and b=1) or (c=2);,MySQL 做查询优化时,会检索到 idx_a_b 和 idx_c 两个索引;但是在 TiDB(v2.0.8-9)中,这个 sql 会成为一个慢 SQL,需要改写为:SELECT id from t_test where (a=1 and b=1) UNION SELECT id from t_test where (c=2);小结:导致该问题的原因,可以理解为 TiDB 的 sql 解析还有优化空间。冷热数据型(举例)表结构:CREATE TABLE t_job_record ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT ‘主键id’, job_code varchar(255) NOT NULL DEFAULT ’’ COMMENT ‘任务code’, record_id bigint(20) NOT NULL DEFAULT ‘0’ COMMENT ‘记录id’, status tinyint(3) NOT NULL DEFAULT ‘0’ COMMENT ‘执行状态:0 待处理’, execute_time bigint(20) NOT NULL DEFAULT ‘0’ COMMENT ‘执行时间(毫秒)’, PRIMARY KEY (id), KEY idx_status_execute_time (status,execute_time), KEY idx_record_id (record_id) ) ENGINE=InnoDB COMMENT=‘异步任务job’数据说明:a. 冷数据,status=1 的数据(已经处理过的数据);b. 热数据,status=0 并且 execute_time<= 当前时间 的数据。慢查询:对于热数据,数据量一般不大,但是查询频度很高,假设当前(毫秒级)时间为:1546361579646,则在 MySQL 中,查询 sql 为:SELECT * FROM t_job_record where status=0 and execute_time<= 1546361579646这个在 MySQL 中很高效的查询,在 TiDB 中虽然也可从索引检索,但其耗时却不尽人意(百万级数据量,耗时百毫秒级)。原因分析:在 TiDB 中,底层索引结构为 LSM-Tree,如下图:当从内存级的 C0 层查询不到数据时,会逐层扫描硬盘中各层;且 merge 操作为异步操作,索引数据更新会存在一定的延迟,可能存在无效索引。由于逐层扫描和异步 merge,使得查询效率较低。优化方式:尽可能缩小过滤范围,比如结合异步 job 获取记录频率,在保证不遗漏数据的前提下,合理设置 execute_time 筛选区间,例如 1 小时,sql 改写为:SELECT * FROM t_job_record where status=0 and execute_time>1546357979646 and execute_time<= 1546361579646优化效果:耗时 10 毫秒级别(以下)。关于查询的启发在基于 TiDB 的业务开发中,先摒弃传统关系型数据库带来的对 sql 先入为主的理解或经验,谨慎设计每一个 sql,如 DBA 所提倡:设计 sql 时务必关注执行计划,必要时请教 DBA。和 MySQL 相比,TiDB 的底层存储和结构决定了其特殊性和差异性;但是,TiDB 支持 MySQL 协议,它们也存在一些共同之处,比如在 TiDB 中使用“预编译”和“批处理”,同样可以获得一定的性能提升。服务端预编译在 MySQL 中,可以使用 PREPARE stmt_name FROM preparable_stm 对 sql 语句进行预编译,然后使用 EXECUTE stmt_name [USING @var_name [, @var_name] …] 执行预编译语句。如此,同一 sql 的多次操作,可以获得比常规 sql 更高的性能。mysql-jdbc 源码中,实现了标准的 Statement 和 PreparedStatement 的同时,还有一个ServerPreparedStatement 实现,ServerPreparedStatement 属于PreparedStatement的拓展,三者对比如下:容易发现,PreparedStatement 和 Statement 的区别主要区别在于参数处理,而对于发送数据包,调用服务端的处理逻辑是一样(或类似)的;经测试,二者速度相当。其实,PreparedStatement 并不是服务端预处理的;ServerPreparedStatement 才是真正的服务端预处理,速度也较 PreparedStatement 快;其使用场景一般是:频繁的数据库访问,sql 数量有限(有缓存淘汰策略,使用不宜会导致两次 IO)。批处理对于多条数据写入,常用 sql 为 insert … values (…),(…);而对于多条数据更新,亦可以使用 update … case … when… then… end 来减少 IO 次数。但它们都有一个特点,数据条数越多,sql 越加复杂,sql 解析成本也更高,耗时增长可能高于线性增长。而批处理,可以复用一条简单 sql,实现批量数据的写入或更新,为系统带来更低、更稳定的耗时。对于批处理,作为客户端,java.sql.Statement 主要定义了两个接口方法,addBatch 和 executeBatch 来支持批处理。批处理的简要流程说明如下:经业务中实践,使用批处理方式的写入(或更新),比常规 insert … values(…),(…)(或 update … case … when… then… end)性能更稳定,耗时也更低。 ...

January 17, 2019 · 2 min · jiezi

扎心一问:分库分表就能无限扩容吗

前言像我这样的菜鸟,总会有各种疑问,刚开始是对 JDK API 的疑问,对 NIO 的疑问,对 JVM 的疑问,当工作几年后,对服务的可用性,可扩展性也有了新的疑问,什么疑问呢?其实是老生常谈的话题:服务的扩容问题。正常情况下的服务演化之路让我们从最初开始。单体应用每个创业公司基本都是从类似 SSM 和 SSH 这种架构起来的,没什么好讲的,基本每个程序员都经历过。RPC 应用当业务越来越大,我们需要对服务进行水平扩容,扩容很简单,只要保证服务是无状态的就可以了,如下图:当业务又越来越大,我们的服务关系错综复杂,同时,有很多服务访问都是不需要连接 DB 的,只需要连接缓存即可,那么就可以做成分离的,减少 DB 宝贵的连接。如下图:我相信大部分公司都是在这个阶段。Dubbo 就是为了解决这个问题而生的。分库分表如果你的公司产品很受欢迎,业务继续高速发展,数据越来越多,SQL 操作越来越慢,那么数据库就会成为瓶颈,那么你肯定会想到分库分表,不论通过 ID hash 或者 range 的方式都可以。如下图:这下应该没问题了吧。任凭你用户再多,并发再高,我只要无限扩容数据库,无限扩容应用,就可以了。这也是本文的标题,分库分表就能解决无限扩容吗?实际上,像上面的架构,并不能解决。其实,这个问题和 RPC 的问题有点类似:数据库连接过多!!!通常,我们的 RPC 应用由于是使用中间件进行访问数据库,应用实际上是不知道到底要访问哪个数据库的,访问数据库的规则由中间件决定,例如 sharding JDBC。这就导致,这个应用必须和所有的数据库连接,就像我们上面的架构图一样,一个 RPC 应用需要和 3 个 mysql 连接,如果是 30 个 RPC 应用,每个 RPC 的数据库连接池大小是8 ,每个 mysql 需要维护 240 个连接,我们知道,mysql 默认连接数是 100,最大连接数是 16384,也就是说,假设每个应用的连接池大小是 8 ,超过 2048 个应用就无法再继续连接了,也就无法继续扩容了。注意,由于每个物理库有很多逻辑库,再加上微服务运动如火如荼, 2048 并没有看起来那么大。也许你说,我可以通过前面加一个 proxy 来解决连接数的问题,实际上,代理的性能也会成为问题,为什么?代理的连接数也是不能超过 16384 的,如果并发超过 16384,变成 163840,那么 proxy 也解决不了问题。怎么办?让我们再看看上面的架构图:我们发现,问题是出在“每个 RPC 应用都要连所有的库”,导致扩容应用的同时,每个数据库连接数就要增加。就算增加数据库,也不能解决连接数的问题。那怎么办呢?单元化单元化,听起来高大上,通常在一些 XXX 大会上,分享“关于两地三中心”,“三地五中心”,“异地多活”等等牛逼的名词的时候,单元化也会一起出现。这里我们不讨论那么牛逼的,就只说“数据库连接数过多” 的问题。实际上,思路很简单:我们不让应用连接所有的数据库就可以了。假设我们根据 range 分成了 10 个库,现在有 10 个应用,我们让每个应用只连一个库,当应用增多变成 20个,数据库的连接不够用了,我们就将 10 个库分成 20 个库,这样,无论你应用扩容到多少个,都可以解决数据库连接数过多的问题。注意:做这件事的前提是:你必须保证,访问你这个应用的 request 请求的数据库一定是在这个应用的。s换个说法,当用户从 DNS 那里进来的时候,就知道自己要去那个应用了,所以,规则在 DNS 之前就定好了,虽然这有点夸张,但肯定在进应用之前就知道要去哪个库了。所以,这通常需要一个规则,例如通过用户 ID hash,由配置中心广播 hash 规则。这样,所有的组件都能保持一致的规则,从而正确的访问到数据库。如下图:到这里,我们终于解决了无限扩容的问题。最后本文从单体应用开始,逐步讲述了一个正常后台的演进历程,知道了分库分表并不能解决“无限扩容” 的问题,只有单元化才能解决这问题。而单元化则带来更多的复杂性。但是好处不言而喻。单元化带来的更多的思路。有了单元化,解决了无限扩容的问题,但是我们还没有考虑单点的问题,即服务的可用性。要知道,我们这里的数据库都是单点的。这就是另一个话题 —— 异地多活。限于篇幅,下次再聊。 ...

January 16, 2019 · 1 min · jiezi

SSL加密与分布式IM系统-InChat1.1.3版本试用说明

本文首发于本博客 猫叔的博客,转载请申明出处2019年1月15号-InChat发布V1.1.3版本InChat一个轻量级、高效率的支持多端(应用与硬件Iot)的异步网络应用通讯框架v1.1.3版本使用说明v1.1.0-alpha版本使用说明InChatV1.1.2版本使用说明历史更新说明1.1.2版本捕获未注册链接断开异常,完善异常处理修改项目启动流程,仿Selector启动模式添加HTTP接口三个:发送通知消息,获取在线用户数,获取在线用户列表,暂不支持用户自定义HTTP接口(对于传统web API我们希望用户用自己的框架与流程)服务端发送通知接口改为enum处理1.1.3版本添加SSL加密,实现https与wss功能接受用户自定义证书(浏览器信任与不信任均可以访问)InChat配置可改为分布式或着单机版引入Redis,处理集群信息与消息互通如果有生产需要或者个别需求,发现BUG,欢迎留言,项目会更新新的API关于InChat的Maven依赖fastjson 》 1.2.53gson 》 2.8.5netty 》 4.1.32.Finalcommons-lang 》 3.5slf4j-log4j12 》 1.7.25jedis 》 3.0.1创建项目创建一个空的Maven项目,并引入InChatMaven包,(注意,请不要使用与本项目相同的包目录)。可能你只需要这样的Maven依赖即可<dependencies> <dependency> <groupId>com.github.UncleCatMySelf</groupId> <artifactId>InChat</artifactId> <version>1.1.3</version> </dependency></dependencies>对接InChat的接口与实现InChat启动参数可以自配置你只需要继承InChat的默认配置类InitNetty即可,如下public class MyInit extends InitNetty { /** 自定义启动监听端口 / @Override public int getWebport() { return 8090; } /* 是否启动分布式 true-启动、false-不启动 / @Override public Boolean getDistributed() { return true; } /* 是否启动加密功能 */ @Override public boolean isSsl() { return true; }}请注意,分布式为测试版,所以暂不支持SSL加密,如果启动分布式请关闭SSL加密功能如何自定义证书?#keytool -genkey -keysize 2048 -validity 365 -keyalg RSA -dnam e “CN=in-chat.cn” -keypass 123456 -storepass 123456 -keystore inchat.jkskeytool为JDK提供的生成证书工具keysize 2048 密钥长度2048位(这个长度的密钥目前可认为无法被暴力破解)validity 365 证书有效期365天keyalg RSA 使用RSA非对称加密算法dname “CN=gornix.com” 设置Common Name为gornix.com,这是我的域名keypass 654321 密钥的访问密码为123456storepass 123456 密钥库的访问密码为123456(其实这两个密码也可以设置一样,通常都设置一样,方便记)keystore gornix.jks 指定生成的密钥库文件为inchat.jks如果你试着自己创建了自己的证书,那么你需要去重写InitNetty中的几个信息:jksFile,jksStorePassword,jksCertificatePassword。你的jks文件只需要放到resources目录下就好,两个密码就是你之前设定相同的密码。本项目已经提供了默认的inchat.jks,请用户在Maven包中复制并粘贴到自己的项目中的resources文件夹中即可。获取聊天消息数据此接口与原先一样,仅修改了方法名public class DataBaseServiceImpl implements InChatToDataBaseService { @Override public Boolean writeMessage(InChatMessage message) { System.out.println(message.toString()); return true; }}登录校验与群聊消息此接口没有做过多的修改public class VerifyServiceImpl implements InChatVerifyService { @Override public boolean verifyToken(String token) { return true; } @Override public JSONArray getArrayByGroupId(String groupId) { JSONArray jsonArray = JSONArray.parseArray("["1111","2222","3333"]"); return jsonArray; }}服务端发送通知消息枚举类此接口具有Demo模板,用户需要继承InChat框架的FromServerService接口,同时该接口注释也有实例demo,我们需要实现一个自定义的枚举,你可以这样写:public enum FromServerServiceImpl implements FromServerService { //你可以自定义自己的系统消息,请以Integer-String的形式 TYPE1(1,"【系统通知】您的账号存在异常,请注意安全保密信息。"), TYPE2(2,"【系统通知】恭喜您连续登录超过5天,奖励5积分。"); private Integer code; private String message; FromServerServiceImpl(Integer code, String message){ this.code = code; this.message = message; } public Integer getCode() { return code; } //实现接口的方法,遍历本枚举的code,获取对应的消息,作为系统消息发送 public String findByCode(Object code) { Integer codes = (Integer)code; for (FromServerServiceImpl item: FromServerServiceImpl.values()) { if (item.code == codes){ return item.message; } } return null; } public void setCode(Integer code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; }}启动项目1.1.3版本的启动项目变得异常的简单,你只需要配置启动的配置工厂即可。但是如果我们启动了分布式的话,我们还需要配置redis信息。public class application { public static void main(String[] args) { //配置你的自定义配置 ConfigFactory.initNetty = new MyInit(); //配置校验类 ConfigFactory.inChatVerifyService = new VerifyServiceImpl(); //配置消息接收处理类 ConfigFactory.inChatToDataBaseService = new DataBaseServiceImpl(); //配置服务端系统消息枚举,这里的值无所谓 TYPE1或者TYPE2或者TYPEN均可以 ConfigFactory.fromServerService = FromServerServiceImpl.TYPE1; //配置分布式Redis地址,端口目前默认的,下一版可以支持修改。 ConfigFactory.RedisIP = “192.168.192.132”; //启动InChat InitServer.open(); }}项目效果启动成功DEBUG - -Dio.netty.threadLocalDirectBufferSize: 0DEBUG - -Dio.netty.maxThreadLocalCharBufferSize: 16384 INFO - 服务端启动成功【192.168.56.1:8090】 INFO - [RedisConfig.getJedis]:连接成功,测试连接PING->PONG如果你开通了分布式,那么你可以试着启动两个应用程序。DEBUG - -Dio.netty.threadLocalDirectBufferSize: 0DEBUG - -Dio.netty.maxThreadLocalCharBufferSize: 16384 INFO - 服务端启动成功【192.168.56.1:8070】 INFO - [RedisConfig.getJedis]:连接成功,测试连接PING->PONG读者可以到项目中使用原来的两个前端页面。分别登录两个用户在两个应用程序,并进行互相通信即可。启动分布式请关闭SSL,分布式为测试版,暂不支持SSL目前,分布式版本接通了点对点与群聊的功能,大家可以试试。下一版本会添加一个分布式的组件用来统一数据与接口功能。关于加密的,请提前让电脑认同信任证书关于分布式的操作效果关于HTTP接口的,目前与分布式无关原先的自我发送,点对点发送,群聊,异常处理,HTTP接口均与原来一样原先的接口说明可以看上一版本: InChatV1.1.2版本使用说明前端相关这里你可以来到InChat的Front-End-Testing文档夹中的chat.html。你可以直接使用,你进需要修改对应的对接IP即可。如果你开了SSL加密,你的IP开头记得改为:wss://192.168.1.121:8090/ws !!!前端可以看原来的版本: InChatV1.1.2版本使用说明关于数据库设计当前一版不会固定大家的数据库设计,大家可以自己自由设计,同时搭上自己的项目,构建一个附带IM的自项目。公众号:Java猫说现架构设计(码农)兼创业技术顾问,不羁平庸,热爱开源,杂谈程序人生与不定期干货。 ...

January 15, 2019 · 2 min · jiezi

高可用、弹性动态的金融级移动架构在蚂蚁金服的演进之路

摘要: 支付宝作为国民级应用,当前全球用户已经超过 10 亿,提供了超过 200 项以上的服务,而崩溃率始终维持在万分之五以下,而且每天支付宝都上线新的功能和改进。做到今天这样的成绩,并不容易,是经过长时间的实践经验积累下来的。导语本文基于重岳在 2018 年 Arch Summit 北京站的分享内容进行总结,希望通过本篇文章介绍近些年来支付宝在移动端架构的上演进和思考,期冀能给读者们带来些许帮助。支付宝作为国民级应用,当前全球用户已经超过 10 亿,提供了超过 200 项以上的服务,而崩溃率始终维持在万分之五以下,而且每天支付宝都上线新的功能和改进。做到今天这样的成绩,并不容易,是经过长时间的实践经验积累下来的。支付宝的架构演进主要经历了三个阶段,如果用比喻的话,可以分为独木舟、战列舰和航空母舰三个阶段。独木舟时代支付宝刚推出移动端时,它的结构非常之简单,除了一些工具组件被划分为模块,业务代码都是糅合在一起。刚开始并没有太大问题,但是当我们的研发人员迅速增长时,问题开始变得棘手起来,仅仅举几个例子便可见一斑。研发同学晚上提交的可以运行的代码,到第二天早上来更新一下就完全不能用,原因是其他不相干团队提交代码覆盖或者污染了自己的代码。临近发布点的时候,通常是最忙的,但不是忙着赶功能,而是忙着解决合并代码产生的各种问题,不仅浪费时间,还耽误测试同学的宝贵时间。即使最后勉强发布了,稳定性和性能也是非常糟糕的,因为各个模块只管自己的,没有统一的规范,也缺乏统一的监控。最令 Android 开发头痛的是 65535 的问题,彼时 Google 还没有推出 multi-dex 的方案。这些严重的问题让我们的产品研发迭代变得无法持续下去,因此我们决定来一次彻底的重构,于是步入了战列舰时代。战列舰时代当设计新一代的客户端架构时,我们从三个方向进行思考:团队协作、研发效率、性能与稳定。团队协作方面,我们希望整个架构分层合理,基础层面,将通用能力下沉,为更多的上层业务服务,避免重复创造轮子;业务层面,各个业务团队能够独立开发管理,不会对不相关的业务造成影响。基于这个初衷,我们形成了下图这样的架构:整个客户端架构总共分成四层:业务层、服务层、组件层、框架层。业务层:只需专注于业务逻辑与界面的实现,当需要调用如支付这样的通用能力时,研发同学直接使用下层提供的服务能力,不需自己开发,如此能够保证核心能力有收口,方便监控。服务层:常用模块,如登录、支付、营销等,它们不仅自己是业务,也向其他业务提供自己的服务,我们将此类模块归类到服务层。组件层:这一层提供的是客户端通用能力,如安全、网络、多媒体、存储这些,它们提供稳定的接口给上层使用者,同时不断优化自身内部的性能和稳定性,作为客户端的基石,它们至关重要。框架层:最为关键的部分,包括容器、微应用、服务框架以及 Pipeline,客户端的微应用化、启动管理都依赖框架层的运作。我们将服务层、组件层和框架层合称为 mPaaS,即移动端上的 PaaS 服务。这些 PaaS 服务可以复用,我们不仅在支付宝里使用它们,也在其他集团应用,如蚂蚁财富、网商银行等中使用。业务分治要实现业务分治,最好的方式就在代码上能够进行隔离,大家不必在同一个 Codebase 中开发,避免代码合并冲突的现象,这个通常在 Android 上通常可以 aar 的方式来实现,但是可惜的是我们重构的时候 aar 还没出来,而且即使有 aar,也存在打包时间随代码体积增大线性增长的问题。我们的解决方案借鉴 OSGi 的概念,将整个客户端以 Bundle 为单位划分,每个 Bundle 可以包含自己的代码、页面和资源。读者可能会想,这究竟和 aar 有什么分别呢?其实区别很大!首先,Bundle 里的代码部分是已编译的 dex,当编译 apk 时,我们只需要合并 dex 即可,不需要像 aar 那样将 class 编译成 dex 再进行合并,这样大大节省了打包时间;其次,Bundle 是可以独立运行于自己的 ClassLoader 中的,并且我们可以通过壳代理的方式加载 Activity 等基础组件,使得动态下发业务成为可能;最后,Bundle 里还包含微应用、服务和 Pipeline 相关的配置信息,框架会根据这些信息启动相应的组件。mPaaS 的服务,即 Service 类似于 Spring 框架中的 Service,它对外提供接口服务,而使用者不需要知道如何初始化服务的实例以及生命周期管理,这些完全由框架来托管。使用者只需要知道目标服务接口类的方法参数即可,调用时通过框架提供的 API 来获取实例。对于服务的发布者来说,他在自己的 bundle 中声明接口类以及实现接口类派生的实例类,并注册相关信息到 bundle 的 manifest 文件中。这种做法的本质思想是 Inversion of Control,减少类之间的复杂依赖,避免繁琐的初始化工作。以依赖接口的方式进行开发,能够解除服务使用者对服务提供者的依赖,在服务提供者尚未完全开发完成时,使用者可以完全以 mock 的方式来模拟服务,而不需要修改自己的业务代码,当然,前提是双方协商好服务接口的协议。支付宝中的页面非常多,直接启动 Activity 或者 ViewController 对我们来说远远不够,我们选择在它们上面增加 MicroApp,即微应用的概念。微应用具备唯一的应用 ID,在框架中标识自己的存在。微应用具有统一的入口,根据使用方传入的字典参数来管理 Activity 或 ViewController。这样能够带来很多好处:只要应用 ID 和参数协议不变,使用方不需担心目标应用内部重构带来的影响,直接使用 Activity 或者 ViewController 类名造成的引用泛滥的问题不复存在。微应用的 ID 和字典参数特性,很容易生成 URL,从而实现外部应用使用 URL 跳转应用内页面。从数据的角度,我们可以按业务维度来统计用户行为数据。微应用的概念不仅适用于原生页面,同样也适用于 H5 和小程序。注册为 H5 或者小程序类型的应用 ID,框架会自动将启动过程 delegate 给 H5 或者小程序容器,而使用者完全不必关心应用 ID 对应的应用类型。综上所述,微应用化和服务接口所赋予的特性极大提高团队间协作效率,各研发小组之间的依赖更加简单,可以各行其道,更关注于自身服务的打造建设。性能优化我们一方面在架构上作出重大改变来提高研发效率,另一方面也在不断的进行性能优化,改善用户体验。我们主要从三个层面来着手:框架层面制定统一开发规范,业务方使用统一的线程池、存储、网络等组件,并按需进行加载,避免不必要的启动和耗时操作。引入 Pipeline 机制,业务模块如需在应用启动时进行初始化工作,必须使用 Pipeline。框架依据业务优先级确定业务初始化实际。利用 AOP 切面,对常用路径进行耗时统计,追踪性能瓶颈。基础指标对于常用指标,如闪退、ANR、内存、存储、电量、流量等,进行长期追踪。我们能够明确获悉每个版本之间这些指标上的差异,并进行采样分析,定位并解决问题。向下突破我们不仅仅在应用层面进行优化,同时也向下探索性能提升的可能性。在这方面,我们也收获颇丰,比如在 Android 上某些系统版本,通过在启动阶段禁用 GC 的方式获得 20%~30% 的启动时间缩减;在 iOS 上,利用系统本身的 Background Fetch 机制提高进程活跃时间,实现应用秒起。航母时代随着移动支付的不断普及,面对海量的用户和业务需求,高可用、弹性动态成为支付宝客户端更为艰巨的挑战。支付宝作为集支付、金融、生活为一体的服务平台,需要能够快速稳定的发布服务和引入第三方服务,同时对于用户的反馈和诉求必须能够积极迅速的响应。动态研发模式我们在研发模式上作出改变以业务快速迭代的要求,业务逐步由原生页面向 Web 混合页面迁移。原有的研发模式能够很好的满足团队协作的要求,但是随着业务规模的不断增大,代码量相应膨胀导致安装包太大,在iOS上一度超过代码段上限,无法通过 AppStore 审核,另外基于集中时间点的迭代发布,通常是一个月发布一个版本,远不能满足业务的更新速度要求。相较于原生应用开发,Web应用的优势非常明显:只需要一套代码,Web 应用可以在 iOS 和 Android 客户端中运行,能够相对减少人员的投入。每个用户日常使用的功能仅仅是支付宝庞大平台中的一小部分,H5 应用可以做到动态下发,因此可以消除冗余的存储,降低包大小。近些年来 React Native,Weex 等动态渲染引擎在社区非常活跃,但经过小范围的应用以及考虑到 Web 技术的不断发展以及其在业界公认的地位,我们最终还是选择 Web 技术作为动态研发模式的基础。Web 应用迭代摆脱了客户端集中时间点发布的束缚,各业务线迭代计划变得自主可控。打磨 Web 体验尽管 Web 应用优势明显,但在移动端上的短板也是显而易见的,它提供的用户体验、性能以及能力上的限制与原生应用有相当大的差距。为了弥补这些差距,我们做了大量的改进,主要在以下几个方面:前后端分离,我们将页面资源离线化,这样节省了资源请求消耗的时间,使得页面打开速度提升明显,解决了在网络环境较差下容易出现白屏的问题。同时,数据请求使用 native 网络通道,可优化的空间更大,安全性更高。差量更新,客户端更新某业务应用版本时,不需下载完整的新版本资源包,而是下载由发布平台根据客户端本地安装版本计算生成的体积更小的差量包,这样不仅能够节省带宽和流量,也提升了业务更新的速度。推拉结合,解决业务最新版本覆盖率的问题,每次发布新版本时,业务可主动触发消息到客户端,客户端收到通知后会更新该业务应用版本。同时,客户端会定时去检查服务端是否有版本发布,这样能够保证版本发布后大多数用户在短时间内获得最新的应用。容错补偿,客户端可能由于网络、安全或者存储权限等原因,不能使用或者及时获得离线包,这种情况我们也考虑进来了。我们在发布离线资源时,发布平台会自动生成对应的在线 URL 并配置到应用信息中,当客户端加载 Web 应用时发现离线包不可用,会立刻启用该url加载内容,能够最大程度保证业务可用性。Android 独立浏览器内核,Android 碎片化的问题自其诞生之初业已存在,而且目前看上去没有得以解决的迹象。不同系统、不同厂商中的浏览器内核同样存在差异,这导致层出不穷的兼容性问题令研发同学头疼不已,这也违背 Web 一统天下的愿景。为了彻底解决并掌控这些问题,我们引入了独立的 UC 浏览器内核并集成在应用中,这样所有的问题都集中到UC团队解决,变得非常可控,根据数据统计,使用 UC 浏览器内核后浏览器相关的闪退和 ANR 有明显的下降。同时,安全上出现的漏洞,我们可以在第一时间修复并发布,远比厂商升级更有效率。Web 应用全方位监控,资源加载异常、JS执行异常、白屏、加载耗时等性能数据会被收集上报至后台,可以及时发现异常。小程序我们不仅自身提供各种各样的服务,也需要引入第三方服务来服务更多的人群,以往我们只能引入简单的第三方 H5 页面,它们只能使用支付宝提供的少量功能,而且开发人员能力的差异导致用户体验不是很理想。小程序将支付宝的能力全面开放出来,从开发到测试皆有完整的 IDE 等工具链支持,同时 DSL 简单易用,对于第三方来说,能够快速开发上线一款体验和功能比以往更强大的小程序。本文作者:josephjin阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

January 15, 2019 · 1 min · jiezi

编写你的第一个 Java 版 Raft 分布式 KV 存储

前言本文旨在讲述如何使用 Java 语言实现基于 Raft 算法的,分布式的,KV 结构的存储项目。该项目的背景是为了深入理解 Raft 算法,从而深刻理解分布式环境下数据强一致性该如何实现;该项目的目标是:在复杂的分布式环境中,多个存储节点能够保证数据强一致性。项目地址:https://github.com/stateIs0/l…欢迎 star :)什么是 Java 版 Raft 分布式 KV 存储Raft 算法大部分人都已经了解,也有很多实现,从 GitHub 上来看,似乎 Golang 语言实现的较多,比较有名的,例如 etcd。而 Java 版本的,在生产环境大规模使用的实现则较少;同时,他们的设计目标大部分都是命名服务,即服务注册发现,也就是说,他们通常都是基于 AP 实现,就像 DNS,DNS 是一个命名服务,同时也不是一个强一致性的服务。比较不同的是 Zookeeper,ZK 常被大家用来做命名服务,但他更多的是一个分布式服务协调者。而上面的这些都不是存储服务,虽然也都可以做一些存储工作。甚至像 kafka,可以利用 ZK 实现分布式存储。回到我们这边。此次我们语言部分使用 Java,RPC 网络通信框架使用的是蚂蚁金服 SOFA-Bolt,底层 KV 存储使用的是 RocksDB,其中核心的 Raft 则由我们自己实现(如果不自己实现,那这个项目没有意义)。 注意,该项目将舍弃一部分性能和可用性,以追求尽可能的强一致性。为什么要费尽心力重复造轮子小时候,我们阅读关于高可用的文章时,最后都会提到一个问题:服务挂了怎么办?通常有 2 种回答:如果是无状态服务,那么毫不影响使用。如果是有状态服务,可以将状态保存到一个别的地方,例如 Redis。如果 Redis 挂了怎么办?那就放到 ZK。很多中间件,都会使用 ZK 来保证状态一致,例如 codis,kafka。因为使用 ZK 能够帮我们节省大量的时间。但有的时候,中间件的用户觉得引入第三方中间件很麻烦,那么中间件开发者会尝试自己实现一致性,例如 Redis Cluster, TiDB 等。而通常自己实现,都会使用 Raft 算法,那有人问,为什么不使用"更牛逼的" paxos 算法?对不起,这个有点难,至少目前开源的、生产环境大规模使用的 paxos 算法实现还没有出现,只听过 Google 或者 alibaba 在其内部实现过,具体是什么样子的,这里我们就不讨论了。回到我们的话题,为什么重复造轮子?从 3 个方面来回答:有的时候 ZK 和 etcd 并不能解决我们的问题,或者像上面说的,引入其他的中间件部署起来太麻烦也太重。完全处于好奇,好奇为什么 Raft 可以保证一致性(这通常可以通过汗牛充栋的文章来得到解答)?但是到底该怎么实现?分布式开发的要求,作为开发分布式系统的程序员,如果能够更深刻的理解分布式系统的核心算法,那么对如何合理设计一个分布式系统将大有益处。好,有了以上 3 个原因,我们就有足够的动力来造轮子了,接下来就是如何造的问题了。编写前的 Raft 理论基础任何实践都是理论先行。如果你对 Raft 理论已经非常熟悉,那么可以跳过此节,直接看实现的步骤。Raft 为了算法的可理解性,将算法分成了 4 个部分。leader 选举日志复制成员变更日志压缩同 zk 一样,leader 都是必须的,所有的写操作都是由 leader 发起,从而保证数据流向足够简单。而 leader 的选举则通过比较每个节点的逻辑时间(term)大小,以及日志下标(index)的大小。刚刚说 leader 选举涉及日志下标,那么就要讲日志复制。日志复制可以说是 Raft 核心的核心,说简单点,Raft 就是为了保证多节点之间日志的一致。当日志一致,我们可以认为整个系统的状态是一致的。这个日志你可以理解成 mysql 的 binlog。Raft 通过各种补丁,保证了日志复制的正确性。Raft leader 节点会将客户端的请求都封装成日志,发送到各个 follower 中,如果集群中超过一半的 follower 回复成功,那么这个日志就可以被提交(commit),这个 commit 可以理解为 ACID 的 D ,即持久化。当日志被持久化到磁盘,后面的事情就好办了。而第三点则是为了节点的扩展性。第四点是为了性能。相比较 leader 选举和 日志复制,不是那么的重要,可以说,如果没有成员变更和日志压缩,也可以搞出一个可用的 Raft 分布式系统,但没有 leader 选举和日志复制,是万万不能的。因此,本文和本项目将重点放在 leader 选举和日志复制。以上,就简单说明了 Raft 的算法,关于 Raft 算法更多的文章,请参考本人博客中的其他文章(包含官方各个版本论文和 PPT & 动画 & 其他博客文章),博客地址:thinkinjava.cn实现的步骤实现目标:基于 Raft 论文实现 Raft 核心功能,即 Leader 选举 & 日志复制。Raft 核心组件包括:一致性模块,RPC 通信,日志模块,状态机。技术选型:一致性模块,是 Raft 算法的核心实现,通过一致性模块,保证 Raft 集群节点数据的一致性。这里我们需要自己根据论文描述去实现。RPC 通信,可以使用 HTTP 短连接,也可以直接使用 TCP 长连接,考虑到集群各个节点频繁通信,同时节点通常都在一个局域网内,因此我们选用 TCP 长连接。而 Java 社区长连接框架首选 Netty,这里我们选用蚂蚁金服网络通信框架 SOFA-Bolt(基于 Netty),便于快速开发。日志模块,Raft 算法中,日志实现是基础,考虑到时间因素,我们选用 RocksDB 作为日志存储。状态机,可以是任何实现,其实质就是将日志中的内容进行处理。可以理解为 Mysql binlog 中的具体数据。由于我们是要实现一个 KV 存储,那么可以直接使用日志模块的 RocksDB 组件。以上。我们可以看到,得益于开源世界,我们开发一个 Raft 存储,只需要编写一个“一致性模块”就行了,其他模块都有现成的轮子可以使用,真是美滋滋。接口设计:上面我们说了 Raft 的几个核心功能,事实上,就可以理解为接口。所以我们定义以下几个接口:Consensus, 一致性模块接口LogModule,日志模块接口StateMachine, 状态机接口RpcServer & RpcClient, RPC 接口Node,同时,为了聚合上面的几个接口,我们需要定义一个 Node 接口,即节点,Raft 抽象的机器节点。LifeCycle, 最后,我们需要管理以上组件的生命周期,因此需要一个 LifeCycle 接口。接下来,我们需要详细定义核心接口 Consensus。我们根据论文定义了 2 个核心接口: /** * 请求投票 RPC * * 接收者实现: * * 如果term < currentTerm返回 false (5.2 节) * 如果 votedFor 为空或者就是 candidateId,并且候选人的日志至少和自己一样新,那么就投票给他(5.2 节,5.4 节) / RvoteResult requestVote(RvoteParam param); /* * 附加日志(多个日志,为了提高效率) RPC * * 接收者实现: * * 如果 term < currentTerm 就返回 false (5.1 节) * 如果日志在 prevLogIndex 位置处的日志条目的任期号和 prevLogTerm 不匹配,则返回 false (5.3 节) * 如果已经存在的日志条目和新的产生冲突(索引值相同但是任期号不同),删除这一条和之后所有的 (5.3 节) * 附加任何在已有的日志中不存在的条目 * 如果 leaderCommit > commitIndex,令 commitIndex 等于 leaderCommit 和 新日志条目索引值中较小的一个 / AentryResult appendEntries(AentryParam param);请求投票 & 附加日志。也就是我们的 Raft 节点的核心功能,leader 选举和 日志复制。实现这两个接口是 Raft 的关键所在。然后再看 LogModule 接口,这个自由发挥,考虑日志的特点,我定义了以下几个接口:void write(LogEntry logEntry);LogEntry read(Long index);void removeOnStartIndex(Long startIndex);LogEntry getLast();Long getLastIndex();分别是写,读,删,最后是两个关于 Last 的接口,在 Raft 中,Last 是一个非常关键的东西,因此我这里单独定义了 2个方法,虽然看起来不是很好看 :)状态机接口,在 Raft 论文中,将数据保存到状态机,作者称之为应用,那么我们也这么命名,说白了,就是将已成功提交的日志应用到状态机中: /* * 将数据应用到状态机. * * 原则上,只需这一个方法(apply). 其他的方法是为了更方便的使用状态机. * @param logEntry 日志中的数据. / void apply(LogEntry logEntry); LogEntry get(String key); String getString(String key); void setString(String key, String value); void delString(String… key); 第一个 apply 方法,就是 Raft 论文常常提及的方法,即将日志应用到状态机中,后面的几个方法,都是我为了方便获取数据设计的,可以不用在意,甚至于,这几个方法不存在也不影响 Raft 的实现,但影响 KV 存储的实现,试想:一个系统只有保存功能,没有获取功能,要你何用?。RpcClient 和 RPCServer 没什么好讲的,其实就是 send 和 receive。然后是 Node 接口,Node 接口也是 Raft 没有定义的,我们依靠自己的理解定义了几个接口: /* * 设置配置文件. * * @param config / void setConfig(NodeConfig config); /* * 处理请求投票 RPC. * * @param param * @return / RvoteResult handlerRequestVote(RvoteParam param); /* * 处理附加日志请求. * * @param param * @return / AentryResult handlerAppendEntries(AentryParam param); /* * 处理客户端请求. * * @param request * @return / ClientKVAck handlerClientRequest(ClientKVReq request); /* * 转发给 leader 节点. * @param request * @return */ ClientKVAck redirect(ClientKVReq request);首先,一个 Node 肯定需要配置文件,所以有一个 setConfig 接口,然后,肯定需要处理“请求投票”和“附加日志”,同时,还需要接收用户,也就是客户端的请求(不然数据从哪来?),所以有 handlerClientRequest 接口,最后,考虑到灵活性,我们让每个节点都可以接收客户端的请求,但 follower 节点并不能处理请求,所以需要重定向到 leader 节点,因此,我们需要一个重定向接口。最后是生命周期接口,这里我们简单定义了 2 个,有需要的话,再另外加上组合接口: void init() throws Throwable; void destroy() throws Throwable;好,基本的接口定义完了,后面就是实现了。实现才是关键。Leader 选举的实现选举,其实就是一个定时器,根据 Raft 论文描述,如果超时了就需要重新选举,我们使用 Java 的定时任务线程池进行实现,实现之前,需要确定几个点:选举者必须不是 leader。必须超时了才能选举,具体超时时间根据你的设计而定,注意,每个节点的超时时间不能相同,应当使用随机算法错开(Raft 关键实现),避免无谓的死锁。选举者优先选举自己,将自己变成 candidate。选举的第一步就是把自己的 term 加一。然后像其他节点发送请求投票 RPC,请求参数参照论文,包括自身的 term,自身的 lastIndex,以及日志的 lastTerm。同时,请求投票 RPC 应该是并行请求的。等待投票结果应该有超时控制,如果超时了,就不等待了。最后,如果有超过半数的响应为 success,那么就需要立即变成 leader ,并发送心跳阻止其他选举。如果失败了,就需要重新选举。注意,这个期间,如果有其他节点发送心跳,也需要立刻变成 follower,否则,将死循环。具体代码,可参见 https://github.com/stateIs0/l…上面说的,其实是 Leader 选举中,请求者的实现,那么接收者如何实现呢?接收者在收到“请求投票” RPC 后,需要做以下事情:注意,选举操作应该是串行的,因为涉及到状态修改,并发操作将导致数据错乱。也就是说,如果抢锁失败,应当立即返回错误。首先判断对方的 term 是否小于自己,如果小于自己,直接返回失败。如果当前节点没有投票给任何人,或者投的正好是对方,那么就可以比较日志的大小,反之,返回失败。如果对方日志没有自己大,返回失败。反之,投票给对方,并变成 follower。变成 follower 的同时,异步的选举任务在最后从 condidate 变成 leader 之前,会判断是否是 follower,如果是 follower,就放弃成为 leader。这是一个兜底的措施。具体代码参见 https://github.com/stateIs0/l…到这里,基本就能够实现 Raft Leader 选举的逻辑。注意,我们上面涉及到的 LastIndex 等参数,还没有实现,但不影响我们编写伪代码,毕竟日志复制比 leader 选举要复杂的多,我们的原则是从易到难。:)日志复制的实现日志复制是 Raft 实现一致性的核心。日志复制有 2 种形式,1种是心跳,一种是真正的日志,心跳的日志内容是空的,其他部分基本相同,也就是说,接收方在收到日志时,如果发现是空的,那么他就是心跳。心跳既然是心跳,肯定就是个定时任务,和选举一样。在我们的实现中,我们每 5 秒发送一次心跳。注意点:首先自己必须是 leader 才能发送心跳。必须满足 5 秒的时间间隔。并发的向其他 follower 节点发送心跳。心跳参数包括自身的 ID,自身的 term,以便让对方检查 term,防止网络分区导致的脑裂。如果任意 follower 的返回值的 term 大于自身,说明自己分区了,那么需要变成 follower,并更新自己的 term。然后重新发起选举。具体代码查看:https://github.com/stateIs0/l…然后是心跳接收者的实现,这个就比较简单了,接收者需要做几件事情:无论成功失败首先设置返回值,也就是将自己的 term 返回给 leader。判断对方的 term 是否大于自身,如果大于自身,变成 follower,防止异步的选举任务误操作。同时更新选举时间和心跳时间。如果对方 term 小于自身,返回失败。不更新选举时间和心跳时间。以便触发选举。具体代码参见:https://github.com/stateIs0/l…说完了心跳,再说说真正的日志附加。简单来说,当用户向 Leader 发送一个 KV 数据,那么 Leader 需要将 KV数据封装成日志,并行的发送到其他的 follower 节点,只要在指定的超时时间内,有过半几点返回成功,那么久提交(持久化)这条日志,返回客户端成功,否者返回失败。因此,Leader 节点会有一个 ClientKVAck handlerClientRequest(ClientKVReq request) 接口,用于接收用户的 KV 数据,同时,会并行向其他节点复制数据,具体步骤如下:每个节点都可能会接收到客户端的请求,但只有 leader 能处理,所以如果自身不是 leader,则需要转发给 leader。然后将用户的 KV 数据封装成日志结构,包括 term,index,command,预提交到本地。并行的向其他节点发送数据,也就是日志复制。如果在指定的时间内,过半节点返回成功,那么就提交这条日志。最后,更新自己的 commitIndex,lastApplied 等信息。注意,复制不仅仅是简单的将这条日志发送到其他节点,这可能比我们想象的复杂,为了保证复杂网络环境下的一致性,Raft 保存了每个节点的成功复制过的日志的 index,即 nextIndex ,因此,如果对方之前一段时间宕机了,那么,从宕机那一刻开始,到当前这段时间的所有日志,都要发送给对方。甚至于,如果对方觉得你发送的日志还是太大,那么就要递减的减小 nextIndex,复制更多的日志给对方。注意:这里是 Raft 实现分布式一致性的关键所在。具体代码参见:https://github.com/stateIs0/l…再来看看日志接收者的实现步骤:和心跳一样,要先检查对方 term,如果 term 都不对,那么就没什么好说的了。如果日志不匹配,那么返回 leader,告诉他,减小 nextIndex 重试。如果本地存在的日志和 leader 的日志冲突了,以 leader 的为准,删除自身的。最后,将日志应用到状态机,更新本地的 commitIndex,返回 leader 成功。具体代码参见:https://github.com/stateIs0/l…到这里,日志复制的部分就讲完了。注意,实现日志复制的前提是,必须有一个正确的日志存储系统,即我们的 RocksDB,我们在 RocksDB 的基础上,使用一种机制,维护了 每个节点 的LastIndex,无论何时何地,都能够得到正确的 LastIndex,这是实现日志复制不可获取的一部分。验证“Leader 选举”和“日志复制”写完了程序,如何验证是否正确呢?当然是写验证程序。我们首先验证 “Leader 选举”。其实这个比较好测试。在 idea 中配置 5 个 application 启动项,配置 main 类为 RaftNodeBootStrap 类, 加入 -DserverPort=8775 -DserverPort=8776 -DserverPort=8777 -DserverPort=8778 -DserverPort=8779 系统配置, 表示分布式环境下的 5 个机器节点.依次启动 5 个 RaftNodeBootStrap 节点, 端口分别是 8775,8776, 8777, 8778, 8779.观察控制台, 约 6 秒后, 会发生选举事件,此时,会产生一个 leader. 而 leader 会立刻发送心跳维持自己的地位.如果leader 的端口是 8775, 使用 idea 关闭 8775 端口,模拟节点挂掉, 大约 15 秒后, 会重新开始选举, 并且会在剩余的 4 个节点中,产生一个新的 leader. 并开始发送心跳日志。然后验证 日志复制,分为 2 种情况:正常状态下在 idea 中配置 5 个 application 启动项,配置 main 类为 RaftNodeBootStrap 类, 加入 -DserverPort=8775 -DserverPort=8776 -DserverPort=8777 -DserverPort=8778 -DserverPort=8779依次启动 5 个 RaftNodeBootStrap 节点, 端口分别是 8775,8776, 8777, 8778, 8779.使用客户端写入 kv 数据.杀掉所有节点, 使用 junit test 读取每个 rocksDB 的值, 验证每个节点的数据是否一致.非正常状态下在 idea 中配置 5 个 application 启动项,配置 main 类为 RaftNodeBootStrap 类, 加入 -DserverPort=8775 -DserverPort=8776 -DserverPort=8777 -DserverPort=8778 -DserverPort=8779依次启动 5 个 RaftNodeBootStrap 节点, 端口分别是 8775,8776, 8777, 8778, 8779.使用客户端写入 kv 数据.杀掉 leader (假设是 8775).再次写入数据.重启 8775.关闭所有节点, 读取 RocksDB 验证数据一致性.Summary本文并没有贴很多代码,如果要贴代码的话,阅读体验将不会很好,并且代码也不能说明什么,如果想看具体实现,可以到 github 上看看,顺便给个 star :)该项目 Java 代码约 2500 行,核心代码估计也就 1000 多行。你甚至可以说,这是个玩具代码,但我相信毕玄大师所说,玩具代码经过优化后,也是可以变成可在商业系统中真正健壮运行的代码(http://hellojava.info/?p=508) :)回到我们的初衷,我们并不奢望这段代码能够运行在生产环境中,就像我的另一个项目 Lu-RPC 一样。但,经历了一次编写可正确运行的玩具代码的经历,下次再次编写工程化的代码,应该会更加容易些。这点我深有体会。可以稍微展开讲一下,在写完 Lu-RPC 项目后,我就接到了开发生产环境运行的限流熔断框架任务,此时,开发 Lu-RPC 的经历让我在开发该框架时,更加的从容和自如:)再回到 Raft 上面来,虽然上面的测试用例跑过了,程序也经过了我反反复复的测试,但不代表这个程序就是 100% 正确的,特别是在复杂的分布式环境下。如果你对 Raft 有兴趣,欢迎一起交流沟通 :)项目地址:https://github.com/stateIs0/l… ...

January 12, 2019 · 4 min · jiezi

Mars 如何分布式地执行

先前,我们已经介绍过 Mars 是什么。如今 Mars 已在 Github 开源并对内上线试用,本文将介绍 Mars 已实现的分布式执行架构,欢迎大家提出意见。架构Mars 提供了一套分布式执行 Tensor 的库。该库使用 mars.actors 实现的 Actor 模型编写,包含 Scheduler、Worker 和 Web 服务。用户向 Mars Web Service 提交的是由 Tensor 组成的 Graph。Web Service 接收这些图并提交到一台 Scheduler。在提交作业到各个 Worker 之前,Mars Scheduler 先将 Tensor 图编译成一张由 Chunk 和 Operand 组成的图,此后对图进行分析和切分。此后,Scheduler 在所有 Scheduler 中根据一致性哈希创建一系列控制单个 Operand 执行的 OperandActor。Operand 以符合拓扑序的顺序进行调度,当所有 Operand 完成执行,整张图将被标记为已完成,客户端能够从 Web 中拉取结果。整个执行过程如下图所述。作业提交用户端通过 RESTful API 向 Mars 服务提交作业。用户通过编写 Tensor 上的代码,此后通过 session.run(tensor) 将 Tensor 操作转换为 Tensor 构成的 Graph 并提交到 Web API。此后,Web API 将作业提交到 SessionActor 并在集群中创建一个 GraphActor 用于图的分析和管理。用户端则开始查询图的执行状态,直至执行结束。在 GraphActor 中,我们首先根据 chunks 设置将 Tensor 图转换为 Operand 和 Chunk 组成的图,这一过程使得图可以被进一步拆分并能够并行执行。此后,我们在图上进行一系列的分析以获得 Operand 的优先级,同时向起始 Operand 指派 Worker,关于这一部分的细节可以参考 准备执行图 章节。此后,每个 Operand 均建立一个 OperandActor 用于控制该 Operand 的具体执行。当 Operand 处于 READY状态(如同在 Operand 状态 章节描述的那样),Scheduler 将会为 Operand 选择目标 Worker,随后作业被提交 Worker 进行实际的执行。执行控制当一个 Operand 被提交到 Worker,OperandActor 等待 Worker 上的回调。如果 Operand 执行成功,Operand 的后继将被调度。如果 Operand 执行失败,OperandActor 将会尝试数次,如果仍失败则将此次执行标记为失败。取消作业用户端可以使用 RESTful API 取消运行中的作业。取消请求将被写入 Graph 的状态存储中,同时 GraphActor 上的取消接口将被调用。如果作业在准备阶段,它将在检测到停止请求后立即结束,否则请求将被下发到每个 OperandActor,并设置状态为 CANCELLING。如果此时 Operand 没有运行,Operand 状态将被直接置为 CANCELLED。如果 Operand 正在运行,停止请求将被下发到 Worker 中并导致一个 ExecutionInterrupted 错误,该错误将返回给 OperandActor,此时 Operand 的状态将被标记为 CANCELLED。准备执行图当一个 Tensor 图被提交到 Mars Scheduler,一张包含更细粒度的,由 Operand 和 Chunk 构成的图将根据数据源中包含的 chunks 参数被生成。图压缩当完成 Chunk 图的生成后,我们将会通过合并图中相邻的节点来减小图的规模,这一合并也能让我们充分利用 numexpr 这样的加速库来加速计算过程。目前 Mars 仅会合并形成单条链的 Operand。例如,当执行下面的代码import mars.tensor as mta = mt.random.rand(100, chunks=100)b = mt.random.rand(100, chunks=100)c = (a + b).sum()Mars 将会合并 Operand ADD 和 SUM 成为 FUSE 节点。RAND Operand 不会被合并,因为它们并没有和 ADD 及 SUM 组成一条简单的直线。初始 Worker 分配为 Operand 分配 Worker 对于图执行的性能而言至关重要。随机分配初始 Operand 可能导致巨大的网络开销,并有可能导致不同 Worker 间作业分配的不平衡。因为非初始节点的分配能够根据其前驱生成数据的物理分布及各个 Worker 的空闲情况方便地确定,在执行图准备阶段,我们只考虑初始 Operand 的分配问题。初始 Worker 分配需要遵循几个准则。首先,分配给每个 Worker 执行的 Operand 需要尽量保持平衡满,这能够使计算集群在整个执行阶段都有较高的利用率,这在执行的最后阶段显得尤其重要。其次,初始节点分配需要使后续节点执行时的网络”传输尽量小。也就是说,初始点分配需要充分遵循局部性原则。需要注意的是,上述准则在某些情况下会彼此冲突。一个网络传输量最小的分配方案可能会非常偏斜。我们开发了一套启发式算法来获取两个目标的平衡,该算法描述如下:选择列表中的第一个初始节点和第一台机器;从 Operand 图转换出的无向图中自该点开始进行深度优先搜索;如果另一个未被分配的初始节点被访问到,我们将其分配给步骤1中选择的机器;当访问到的 Operand 总数大于平均每个 Worker 接受的 Operand 个数时,停止分配;前往步骤1,如果仍有 Worker 未被分配 Operand,否则结束。调度策略当一个 Operand 组成的 Graph 执行时,合适的执行顺序会减少集群中暂存的数据总量,从而减小数据被 Spill 到磁盘的可能性。合适的 Worker 能够减少执行时网络传输的总量。Operand 选择策略合适的执行顺序能够显著减小集群中暂存的数据总量。下图中展示了 Tree Reduction 的例子,圆形代表 Operand,方形代表 Chunk,红色代表 Operand 正在执行,蓝色代表 Operand 可被执行,绿色代表 Operand 产生的 Chunk 已被存储,灰色代表 Operand 及其相关数据已被释放。假设我们有两台 Worker,并且每个 Operand 的资源使用量均相等,每张图展示的是不同策略下经过5个时间单元的执行后的状态。左图展示的是节点依照层次分别执行,而右图展示的是依照接近深度优先的顺序执行。左图中,有6个 Chunk 的数据需要暂存,右图只有2个。因为我们的目标是减少存储在集群中的数据总数,我们为进入 READY 状态的 Operand 设定了一套优先级策略:深度更大的 Operand 需要被优先执行;被更深的 Operand 依赖的 Operand 需要被优先执行;输出规模更小的节点需要被优先执行。Worker 选择策略当 Scheduler 准备执行图时,初始 Operand 的 Worker 已被确定。我们选择后续 Operand 分配 Worker 的依据是输入数据所在的 Worker。如果某个 Worker 拥有的输入数据大小最大,则该 Worker 将被选择用于执行后续 Operand。如果这样的 Worker 有多个,则各个候选 Worker 的资源状况将起到决定作用。Operand 状态Mars 中的每一个操作符都被一个 OperandActor 单独调度。执行的过程是一个状态转移的过程。在 OperandActor 中,我们为每一个状态的进入过程定义一个状态转移函数。起始 Operand 在初始化时位于 READY 状态,非起始 Operand 在初始化时则位于 UNSCHEDULED 状态。当给定的条件满足,Operand 将转移到另一个状态并执行相应的操作。状态转移的流程可以参考下图:我们在下面描述每个状态的含义及 Mats 在这些状态下执行的操作。UNSCHEDUED:一个 Operand 位于此状态,当它的上游数据没有准备好。READY:一个 Operand 位于此状态,当所有上游输入数据均已准备完毕。在进入这一状态时,OperandActor 向 AssignerActor 中选择的所有 Worker 提交作业。如果某一 Worker 准备运行作业,它将向 Scheduler 发送消息,Scheduler 将向其他 Worker 发送停止运行的消息,此后向该 Worker 发送消息以启动作业执行。RUNNING:一个 Operand 位于此状态,当它的执行已经启动。在进入此状态时,OperandActor 会检查作业是否已经提交。如果尚未提交,OperandActor 将构造一个由 FetchChunk Operand 和当前 Operand 组成的图,并将其提交到 Worker 中。此后,OperandActor 会在 Worker 中注册一个回调来获取作业执行完成的消息。FINISHED:一个 Operand 位于此状态,当作业执行已完成。当 Operand 进入此状态,且 Operand 无后继,一个消息将被发送到 GraphActor 以决定是否整个 Graph 的执行都已结束。与此同时,OperandActor 向它的前驱和后继发送执行完成的消息。如果一个前驱收到此消息,它将检查是否所有的后继都已执行完成。如是,当前 Operand 上的数据可以被释放。如果一个后继收到此消息,它将检查是否所有的前驱已完成。如是,该后继的状态可以转移到 READY。FREED:一个 Operand 位于此状态,当其上所有数据都已被释放。CANCELLED:一个 Operand 位于此状态,当所有重新执行的尝试均告失败。当 Operand 进入此状态,它将把相同状态传递到后继节点。CANCELLING:一个 Operand 位于此状态,当它正在被取消执行。如果此前作业正在执行,一个取消执行的请求会被发送到 Worker 上。CANCELLED:一个 Operand 位于此状态,当执行已被取消并停止运行。如果执行进入这一状态,OperandActor 会尝试将书友的后继都转为 CANCELLING。Worker 中的执行细节一个 Mars Worker 包含多个进程,以减少全局解释器锁(GIL)对执行的影响。具体的执行在独立的进程中完成。为减少不必要的内存拷贝和进程间通讯,Mars Worker 使用共享内存来存储执行结果。当一个作业被提交到 Worker,它将首先被置于队列中等待分配内存。当内存被分配后,其他 Worker 上的数据,或者当前 Worker 上已被 spill 到磁盘的数据将会被重新载入内存中。此时,所有计算需要的数据已经都在内存中,真正的计算过程将启动。当计算完成,Worker 将会把作业放到共享存储空间中。这四种执行状态的转换关系见下图。执行控制Mars Worker 通过 ExecutionActor 控制所有 Operand 在 Worker 中的执行。该 Actor 本身并不参与实际运算或者数据传输,只是向其他 Actor 提交任务。Scheduler 中的 OperandActor 通过 ExecutionActor 上的 enqueue_graph 调用向 Worker 提交作业。Worker 接受 Operand 提交并且将其换存在队列中。当作业可以执行时,ExecutionActor 将会向 Scheduler 发送消息,Scheduler 将确定是否将执行该操作。当 Scheduler 确定在当前 Worker 上执行 Operand,它将调用 start_execution 方法,并通过 add_finish_callback注册一个回调。这一设计允许执行结果被多个位置接收,这对故障恢复有价值。ExecutionActor 使用 mars.promise 模块来同时处理多个 Operand 的执行请求。具体的执行步骤通过 Promise 类的 then 方法相串联。当最终的执行结果被存储,之前注册的回调将被触发。如果在之前的任意执行步骤中发生错误,该错误会被传导到最后 catch 方法注册的处理函数中并得到处理。Operand 的排序所有在 READY 状态的 Operand 都被提交到 Scheduler 选择的 Worker 中。因此,在执行的绝大多数时间里,提交到 Operand 的 Worker 个数通常都高于单个 Worker 能够处理的 Operand 总数。因此,Worker 需要对 Operand 进行排序,此后选择一部分 Worker 来执行。这一排序过程在 TaskQueueActor 中进行,该 Actor 中维护一个优先队列,其中存储 Operand 的相关信息。与此同时,TaskQueueActor 定时运行一个作业分配任务,对处于优先队列头部的 Operand 分配执行资源直至没有多余的资源来运行 Operand,这一分配过程也会在新 Operand 提交或者 Operand 执行完成时触发。内存管理Mars Worker 管理两部分内存。第一部分是每个 Worker 进程私有的内存空间,由每个进程自己持有。第二部分是所有进程共享的内存空间,由 Apache Arrow 中的 plasma_store 持有。为了避免进程内存溢出,我们引入了 Worker 级别的 QuotaActor,用于分配进程内存。当一个 Operand 开始执行前,它将为输入和输出 Chunk 向 QuotaActor 发送批量内存请求。如果剩余的内存空间可以满足请求,该请求会被 QuotaActor 接受。否则,请求将排队等待空闲资源。当相关内存使用被释放,请求的资源会被释放,此时,QuotaActor 能够为其他 Operand 分配资源。共享内存由 plasma_store 管理,通常会占据整个内存的 50%。由于不存在溢出的可能,这部分内存无需经过 QuotaActor 而是直接通过 plasma_store 的相关方法进行分配。当共享内存使用殆尽,Mars Worker 会尝试将一部分不在使用的 Chunk spill 到磁盘中,以腾出空间容纳新的 Chunk。从共享内存 spill 到磁盘的 Chunk 数据可能会被未来的 Operand 重新使用,而从磁盘重新载入共享内存的操作可能会非常耗费 IO 资源,尤其在共享内存已经耗尽,需要 spill 其他 Chunk 到磁盘以容纳载入的 Chunk 时。因此,当数据共享并不需要时,例如该 Chunk 只会被一个 Operand 使用,我们会将 Chunk 直接载入进程私有内存中,而不是共享内存,这可以显著减少作业总执行时间。未来工作Mars 目前正在快速迭代,近期将考虑实现 Worker 级别的 failover 及 shuffle 支持,Scheduler 级别的 failover 也在计划中。本文作者:继盛阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

January 11, 2019 · 3 min · jiezi

Kubernetes API 与 Operator,不为人知的开发者战争

如果我问你,如何把一个 etcd 集群部署在 Google Cloud 或者阿里云上,你一定会不假思索的给出答案:当然是用 etcd Operator!实际上,几乎在一夜之间,Kubernetes Operator 这个新生事物,就成了开发和部署分布式应用的一项事实标准。时至今日,无论是 etcd、TiDB、Redis,还是 Kafka、RocketMQ、Spark、TensorFlow,几乎每一个你能叫上名字来的分布式项目,都由官方维护着各自的 Kubernetes Operator。而 Operator 官方库里,也一直维护着一个知名分布式项目的 Operator 汇总。https://github.com/operator-framework/awesome-operators短短一年多时间,这个列表的长度已经增长了几十倍。 而且更有意思的是,如果你仔细翻阅这个 Operator 列表,你就不难发现这样一个有趣的事实:现今 Kubernetes Operator 的意义,恐怕已经远远超过了“分布式应用部署”的这个原始的范畴,而已然成为了容器化时代应用开发与发布的一个全新途径。所以,你才会在这个列表里看到,Android SDK 的开发者们,正在使用 Operator “一键”生成和更新 Android 开发环境;而 Linux 系统工程师们,则在使用Operator “一键”重现性能测试集群。如果说,Docker 镜像的提出,完成了应用静态描述的标准化。那么 Kubernetes Operator 的出现,终于为应用的动态描述提出了一套行之有效的实现规范。更为重要的是,对于 TiDB、Kafka、RocketMQ 等分布式应用的开发者来说,这些应用运行起来之后的动态描述,才是对一个分布式应用真正有意义的信息。而在此之前,用户如果要想将 TiDB、Kafka 这样的分布式应用很好的使用起来,就不得不去尝试编写一套复杂的管理脚本,甚至为此学习大量与项目本身无关的运维知识。更为麻烦的是,这些脚本、知识、和经验,并没有一个很好的办法能够有效的沉淀下来。而任何一种技术的传授,如果严重依赖于口口相传而不是固化的代码和逻辑的话,那么它的维护成本和使用门槛,就可以说是“灾难级”的。所以说,Kubernetes Operator 发布之初最大的意义,就在于它将分布式应用的使用门槛直接降到了最低。那么这个门槛具体有多低呢?一般来说,无论这个分布式应用项目有多复杂,只要它为用户提供了 Operator,那么这个项目的使用就只需要两条命令即可搞定,以 Kafka 为例:这两条命令执行完成后,一个 Kafka 集群运行所需的节点,以及它们所依赖的 ZooKeeper 节点,就会以容器的方式自动出现在你的 Kubernetes 集群里了。不过,简化运维和部署,其实只是 Operator 在用户层面的表象。而在更底层的技术层面,Operator 最大的价值,在于它为“容器究竟能不能管理有状态应用”这个颇具争议话题,画上了一个优雅的句号。要知道,在2014-2015年的时候,伴随着 Docker 公司和 Docker 项目的走红,整个云计算生态几乎都陷入了名为“容器”的狂热当中。然而,相比于 “容器化”浪潮的如火如荼,这个圈子却始终对“有状态应用”讳莫如深。事实上,有状态应用(比如, 前面提到的Kafka )跟无状态应用(比如,一个简单的Jave Web网站)的不同之处,就在于前者对某些外部资源有着绑定性的依赖,比如远程存储,或者网络设备,以及,有状态应用的多个示例之间往往有着拓扑关系。这两种设计,在软件工程的世界里可以说再普通不过了,而且我们几乎可以下这样一个结论:所有的分布式应用都是有状态应用。但是,在容器的世界里,分布式应用却成了一个“异类”。我们知道,容器的本质,其实就是一个被限制了“世界观”的进程。在这种隔离和限制的大基调下,容器技术本身的“人格基因”,就是对外部世界(即:宿主机)的“视而不见”和“充耳不闻”。所以我们经常说,容器的“状态”一定是“易失”的。其实,容器对它的“小世界”之外的状态和数据漠不关心,正是这种“隔离性”的主要体现。但状态“易失”并不能说是容器的缺陷:我们既然对容器可以重现完整的应用执行环境的“一致性”拍手称赞,那就必然要对这种能力背后的限制了然于心。这种默契,也正是早期的 Docker 公司所向披靡的重要背景:在这个阶段,相比于“容器化”的巨大吸引力,开发者是可以暂时接受一部分应用不能运行在容器里的。而分布式应用容器化的困境,其实就在于它成为了这种“容器化”默契的“终极破坏者”。一个应用本身可以拥有多个可扩展的实例,这本来是容器化应用令人津津乐道的一个优势。但是一旦这些实例像分布式应用这样具有了拓扑关系,以及,这些实例本身不完全等价的时候,容器化的解决方案就再次变得“丑陋”起来:这种情况下,应用开发者们不仅又要为这些容器实例编写一套难以维护的管理脚本,还必须要想办法应对容器重启后状态丢失的难题。而这些容器状态的维护,实际上往往需要打破容器的隔离性、让容器对外部世界有所感知才能做到,这就使得容器化与有状态,成为了两种完全相悖的需求。不过,从上面的叙述中相信你也应该已经察觉到,分布式应用容器化的难点,并不在于容器本身有什么重大缺陷,而在于我们一直以来缺乏一种对“状态”的合理的抽象与描述,使得状态可以和容器进程本身解耦开来。这也就解释了为什么,在 Kubernetes 这样的外部编排框架逐渐成熟起了之后,业界才逐渐对有状态应用管理开始有了比较清晰的理解和认识。而我们知道, Kubernetes 项目最具价值的理念,就是它围绕 etcd 构建出来的一套“面向终态”编排体系,这套体系在开源社区里,就是大名鼎鼎的“声明式 API”。“声明式 API”的核心原理,就是当用户向 Kubernetes 提交了一个 API 对象的描述之后,Kubernetes 会负责为你保证整个集群里各项资源的状态,都与你的 API 对象描述的需求相一致。更重要的是,这个保证是一项“无条件的”、“没有期限”的承诺:对于每个保存在 etcd 里的 API 对象,Kubernetes 都通过启动一种叫做“控制器模式”(Controller Pattern)的无限循环,不断检查,然后调谐,最后确保整个集群的状态与这个 API 对象的描述一致。比如,你提交的 API 对象是一个应用,描述的是这个应用必须有三个实例,那么无论接下来你的 API 对象发生任何“风吹草动”,控制器都会检查一遍这个集群里是不是真的有三个应用实例在运行。并且,它会根据这次检查的结果来决定,是不是需要对集群做某些操作来完成这次“调谐”过程。当然,这里控制器正是依靠 etcd 的 Watch API 来实现对 API 对象变化的感知的。在整个过程中,你提交的 API 对象就是 Kubernetes 控制器眼中的“金科玉律”,是接下来控制器执行调谐逻辑要达到的唯一状态。这就是我们所说的“终态”的含义。而 Operator 的设计,其实就是把这个“控制器”模式的思想,贯彻的更加彻底。在 Operator 里,你提交的 API 对象不再是一个单体应用的描述,而是一个完整的分布式应用集群的描述。这里的区别在于,整个分布式应用集群的状态和定义,都成了Kubernetes 控制器需要保证的“终态”。比如,这个应用有几个实例,实例间的关系如何处理,实例需要把数据存储在哪里,如何对实例数据进行备份和恢复,都是这个控制器需要根据 API 对象的变化进行处理的逻辑。从上述叙述中,你就应该能够明白, Operator 其实就是一段代码,这段代码 Watch 了 etcd 里一个描述分布式应用集群的API 对象,然后这段代码通过实现 Kubernetes 的控制器模式,来保证这个集群始终跟用户的定义完全相同。而在这个过程中,Operator 也有能力利用 Kubernetes 的存储、网络插件等外部资源,协同的为应用状态的保持提供帮助。所以说,Operator 本身在实现上,其实是在 Kubernetes 声明式 API 基础上的一种“微创新”。它合理的利用了 Kubernetes API 可以添加自定义 API 类型的能力,然后又巧妙的通过 Kubernetes 原生的“控制器模式”,完成了一个面向分布式应用终态的调谐过程。而 Operator 本身在用法上,则是一个需要用户大量编写代码的的开发者工具。 不过,这个编写代码的过程,并没有像很多人当初料想的那样导致 Operator 项目走向小众,反而在短短三年的时间里, Operator 就迅速成为了容器化分布式应用管理的事实标准。时至今日,Operator 项目的生态地位已经毋庸置疑。就在刚刚结束的2018年 KubeCon 北美峰会上,Operator 项目和大量的用户案例一次又一次出现在聚光灯前,不断的印证着这个小小的“微创新”对整个云计算社区所产生的深远影响。不过,在 Operator 项目引人瞩目的成长经历背后,你是否考虑过这样一个问题:Kubernetes 项目一直以来,其实都内置着一个管理有状态应用的能力叫作 StatefulSet。而如果你稍微了解 Kubernetes 项目的话就不难发现,Operator 和 StatefulSet,虽然在对应用状态的抽象上有所不同,但它们的设计原理,几乎是完全一致的,即:这两种机制的本质,都是围绕Kubernetes API 对象的“终态”进行调谐的一个控制器(Controller)而已。可是,为什么在一个开源社区里,会同时存在这样的两个核心原理完全一致、设计目标也几乎相同的有状态应用管理方案呢?作为 CoreOS 公司后来广为人知的“左膀右臂”之一(即:etcd 和 Operator),Operator 项目能够在 Kubernetes 生态里争取到今天的位置,是不是也是 CoreOS 公司的开源战略使然呢?事实上,Operator 项目并没有像很多人想象的那样出生就含着金钥匙。只不过,在当时的确没有人能想到,当 CoreOS 的两名工程师带着一个业余项目从一间平淡无奇的公寓走出后不久,一场围绕着 Kubernetes API 生态、以争夺“分布式应用开发者”为核心的的重量级角逐,就徐徐拉开了序幕。*2016 年秋天,原 CoreOS 公司的工程师邓洪超像往常一样,来到了同事位于福斯特城(Foster City)的公寓进行结对编程。每周四相约在这里结对,是这两位工程师多年来约定俗成的惯例。不过,与以往不同的是,相比于往常天马行空般的头脑风暴,这一次,这两位工程师的脑子里正在琢磨着的,是一个非常“接地气”的小项目。我们知道,Kubernetes 项目实现“容器编排”的核心,在于一个叫做“控制器模式”的机制,即:通过对 etcd 里的 API 对象的变化进行监视(Watch),Kubernetes 项目就可以在一个叫做 Controller 的组件里对这些变化进行响应。而无论是 Pod 等应用对象,还是 iptables、存储设备等服务对象,任何一个 API 对象发生变化,那么 Kubernetes 接下来需要执行的响应逻辑,就是对应的 Controller 里定义的编排动作。所以,一个自然而然的想法就是,作为 Kubernetes 项目的用户,我能不能自己编写一个 Controller 来定义我所期望的编排动作呢?比如:当一个 Pod 对象被更新的时候,我的 Controller 可以在“原地”对 Pod 进行“重启”,而不是像 Deployment 那样必须先删除 Pod,然后再创建 Pod。这个想法,其实是很多应用开发者以及 PaaS 用户的强烈需求,也是一直以来萦绕在 CoreOS 公司 CEO Alex Polvi 脑海里的一个念头。而在一次简单的内部讨论提及之后,这个念头很快就激发出了两位工程师的技术灵感,成为了周四结对编程的新主题。而这一次,他们决定把这个小项目,起名叫做:Operator。所以顾名思义,Operator 这个项目最开始的初衷,是用来帮助开发者实现运维(Operate)能力的。但 Operator 的核心思想,却并不是“替开发者做运维工作”,而是“让开发者自己编写运维工具”。更有意思的是,这个运维工具的编写标准,或者说,编写 Operator 代码可以参考的模板,正是 Kubernetes 的“控制器模式(Controller Pattern)”。前面已经说过, Kubernetes 的“控制器模式”,是围绕着比如 Pod 这样的 API 对象,在 Controller 通过响应它的增删改查来定义对 Pod 的编排动作。而 Operator 的设计思路,就是允许开发者在 Kubernetes 里添加一个新的 API 对象,用来描述一个分布式应用的集群。然后,在这个 API 对象的 Controller 里,开发者就可以定义对这个分布式应用集群的运维动作了。举个例子, 假设下面这个 YAML 文件定义的,是一个 3 节点 etcd 集群的描述:有了这样一个 etcdCluster 对象,那么开发者接下来要做的事情,就是编写一个 etcdCluster Controller,使得当任何用户提交这样一个 YAML 文件给 Kubernetes 之后,我们自己编写的 Controller 就会响应 etcdCluster “增加”事件,为用户创建出 3 个节点的 etcd 集群出来。然后,它还会按照我们在 Controller 编写的事件响应逻辑,自动的对这个集群的节点更新、删除等事件做出处理,执行我定义的其他运维功能。像这样一个 etcdCluster Controller,就是 etcd Operator 的核心组成部分了。而作为 etcd 的开发者,CoreOS 的两位工程师把对 etcd 集群的运维工作编写成 Go 语言代码,一点都不困难。可是,要完成这个 Operator 真正困难在于:Kubernetes 只认识 Pod、Node、Service 等这些 Kubernetes 自己原生的 API 对象,它怎么可能认识开发者自己定义的这个 etcdCluster 对象呢?在当时, Kubernetes 项目允许用户自己添加 API 对象的插件能力,叫做 Third Party Resource,简称:TPR。TPR 允许你提交一个 YAML 文件,来定义你想要的的新 API 对象的名字,比如:etcdCluster;也允许你定义这个对象允许的合法的属性,比如:int 格式的 size 字段, string 格式的 version 字段。然后,你就可以提交一个具体的 etcdCluster 对象的描述文件给 Kubernetes,等待该对应的 Controller 进行处理。而这个 Controller,就是 Operator 的主干代码了。所以接下来,CoreOS 的两位工程师轻车熟路,在 Operator 里对 etcdCluster 对象的增、删、改事件的响应位置,写上了创建、删除、更新 etcd 节点的操作逻辑。然后,调试运行,看着一个 etcd 集群按照 YAML 文件里的描述被创建起来。大功告成!就这样,在一个普通的周四下午,世界上第一个 Operator 诞生在了湾区的一所公寓当中。而对于 CoreOS 的两位工程师来说,编写这个小工具的主要目的,就是借助 Kubernetes 的核心原理来自动化的管理 etcd 集群,更重要的是,不需要使用 Kubernetes 里自带的 StatefulSet。你可能已经知道,Kubernetes 里本身就内置了一个叫做 StatefulSet 的功能,是专门用来管理有状态应用的。而 StatefulSet 的核心原理,其实是对分布式应用的两种状态进行了保持:分布式应用的拓扑状态,或者说,节点之间的启动顺序;分布式应用的存储状态,或者说,每个节点依赖的持久化数据。可是,为了能够实现上述两种状态的保持机制,StatefulSet 的设计就给应用开发者带来了额外的束缚。比如,etcd 集群各节点之间的拓扑关系,并不依赖于节点名字或者角色(比如 Master 或者 Slave)来确定,而是记录在每个 etcd 节点的启动参数当中。这使得 StatefulSet 通过“为节点分配有序的 DNS 名字”的拓扑保持方式,实际上没有了用武之地,反而还得要求开发者在节点的启动命令里添加大量的逻辑来生成正确的启动命令,非常不优雅。类似的,对于存储状态来说,etcd 集群对数据的备份和恢复方法,也跟 StatefulSet 依赖的的远程持久化数据卷方案并没有太大关系。不难看到, StatefulSet 其实比较适用于应用本身节点管理能力不完善的项目,比如 MySQL。而对于 etcd 这种已经借助 Raft 实现了自管理的分布式应用来说, StatefulSet 的使用方法和带来的各种限制,其实是非常别扭的。而带着工程师特有的较真儿精神,邓洪超和他的同事借助 Kubernetes 原生的扩展机制实现的,正是一个比 StatefulSet 更加灵活、能够把控制权重新交还给开发者的分布式应用管理工具。他们把这个工具起名叫做 Operator,并在几个月后的 KubeCon 上进行了一次 Demo ,推荐大家尝试使用 Operator 来部署 etcd 集群。没有人能想到的是,这个当时还处于 PoC 状态的小项目一经公布,就立刻激发起了整个社区的模仿和学习的热潮。很快,大量的应用开发者纷纷涌进 Kubernetes 社区,争先恐后的宣布自己的分布式项目可以通过 Operator 运行起来。而敏锐的公有云提供商们很快看出了这其中的端倪:Operator 这个小框架,已然成为了分布式应用和有状态应用“上云”的必经之路。Prometheus,Rook,伴随着越来越多的、以往在容器里运行起来困难重重的应用,通过 Operator 走上了 Kubernetes 之后,Kubernetes 项目第一次出现在了开发者生态的核心位置。这个局面,已经远远超出了邓洪超甚至 CoreOS 公司自己的预期。更重要的是,不同于 StatefulSet 等 Kubernetes 原生的编排概念,Operator 依赖的 Kubernetes 能力,只有最核心的声明式 API 与控制器模式;Operator 具体的实现逻辑,则编写在自定义 Controller 的代码中。这种设计给开发者赋予了极高的自由度,这在整个云计算和 PaaS 领域的发展过程中,都是非常罕见的。此外,相比于 Helm、Docker Compose 等描述应用静态关系的编排工具,Operator 定义的乃是应用运行起来后整个集群的动态逻辑。得益于 Kubernetes 项目良好的声明式 API 的设计和开发者友好的 API 编程范式,Operator 在保证上述自由度的同时,又可以始终如一的展现出清晰的架构和设计逻辑,使得应用的开发者们,可以通过复制粘贴就快速搭建出一个 Operator 的框架,然后专注于填写自己的业务逻辑。在向来讲究“用脚投票”的开发者生态当中,Operator 这样一个编程友好、架构清晰、方便代码复制粘贴的小工具,本身就已经具备了某些成功的特质。然而,Operator 的意外走红,并没有让 CoreOS 公司“一夜成名”,反而差点将这个初出茅庐的项目,扼杀在萌芽状态。在当时的 Kubernetes 社区里,跟应用开发者打交道并不是一个非常常见的事情。而 Operator 项目的诞生,却把 Kubernetes 项目第一次拉近到了开发者的面前,这让整个社区感觉了不适应。而作为 Kubernetes 项目 API 治理的负责人,Google 团队对这种冲突的感受最为明显。对于 Google 团队来说,Controller 以及控制器模式,应该是一个隐藏在 Kubernetes 内部实现里的核心机制,并不适合直接开放给开发者来使用。退一步说,即使开放出去,这个 Controller 的设计和用法,也应该按照 Kubernetes 现有的 API 层规范来进行,最好能成为 Kubernetes 内置 Controller Manager 管理下的一部分。可是, Operator 却把直接编写 Controller 代码的自由度完全交给了开发者,成为了一个游离于 Kubernetes Controller Manager 之外的外部组件。带着这个想法,社区里的很多团队从 Operator 项目诞生一开始,就对它的设计和演进方向提出了质疑,甚至建议将 Operator 的名字修改为 Custom Kubernetes Controller。而无巧不成书,就在 Google 和 CoreOS 在 Controller 的话语权上争执不下的时候, Kubernetes 项目的发起人之一 Brendan Burns 突然宣布加入了微软,这让 Google 团队和 Operator 项目的关系一下子跌倒了冰点。你可能会有些困惑:Brendan Burns 与 Kubernetes 的关系我是清楚的,但这跟 Operator 又有什么瓜葛吗?实际上,你可能很难想到,Brendan Burns 和他的团队,才是 TPR (Third Party Resource)这个特性最初的发起人。所以,几乎在一夜之间,Operator 项目链路上的每一个环节,都与 Google 团队完美的擦肩而过。眼睁睁的看着这个正冉冉升起的开发者工具突然就跟自己完全没了关系,这个中滋味,确实不太好受。于是,在 2017年初,Google 团队和 RedHat 公司开始主动在社区推广 UAS(User Aggregated APIServer),也就是后来 APIServer Aggregator 的雏形。APIServer Aggregator 的设计思路是允许用户编写一个自定义的 APIServer,在这里面添加自定义 API。然后,这个 APIServer 就可以跟 Kubernetes 原生的 APIServer 绑定部署在一起统一提供服务了。不难看到,这个设计与 Google 团队认为自定义 API 必须在 Kubernetes 现有框架下进行管理的想法还是比较一致的。紧接着,RedHat 和 Google 联盟开始游说社区使用 UAS 机制取代 TPR,并且建议直接从 Kubernetes 项目里废弃 TPR 这个功能。一时间,社区里谣言四起,不少已经通过 TPR 实现的项目,也开始转而使用 UAS 来重构以求自保。 而 Operator 这个严重依赖于 TPR 的小项目,还没来得及发展壮大,就被推向了关闭的边缘。面对几乎要与社区背道而驰的困境,CoreOS 公司的 CTO Brandon Philips 做出了一个大胆的决定:让社区里的所有开发者发声,挽救 TPR 和 Operator。2017 年 2月,Brandon Philips 在 GitHub 上开了一个帖子(Gist), 号召所有使用 TPR 或者 Operator 项目的开发者在这里留下的自己的项目链接或者描述。这个帖子,迅速的成为了当年容器技术圈最热门的事件之一,登上了 HackerNews 的头条。有趣的是,这个帖子直到今天也仍然健在,甚至还在被更新,你可以点击这个链接去感受一下当时的盛况。https://gist.github.com/philips/a97a143546c87b86b870a82a753db14c而伴随着 Kubernetes 项目的迅速崛起,短短一年时间不到,夹缝中求生存的 Operator 项目,开始对公有云市场产生了不可逆转的影响,也逐步改变了开发者们对“云”以及云上应用开发模式的基本认知。甚至就连 Google Cloud 自己最大的客户之一 Snapchat ,也成为了 Operator 项目的忠实用户。在来自社区的巨大压力下,在这个由成千上万开发者们自发维护起来的 Operator 生态面前,Google 和 RedHat 公司最终选择了反省和退让。有意思的是,这个退让的结果,再一次为这次闹剧增添了几分戏剧性。就在 Brandon Phillips 的开发者搜集帖发布了不到三个月后,RedHat 和 Google 公司的工程师突然在 Kubernetes 社区里宣布:TPR 即将被废弃,取而代之的是一个名叫 CRD,Custom Resource Definition 的东西。于是,开发者们开始忧心忡忡的按照文档,将原本使用 TPR 的代码都升级成 CRD。而就在这时,他们却惊奇的发现,这两种机制除了名字之外,好像并没有任何不同。所谓的升级工作,其实就是将代码里的 TPR 字样全局替换成 CRD 而已。难道,这只是虚惊一场?其实,很少有人注意到,在 TPR 被替换成 CRD 之后,Brendan Burns 和微软团队就再也没有出现在“自定义 API”这个至关重要的领域里了。而 CRD 现在的负责人,都是来自 Google 和 RedHat 的工程师。在这次升级事件之后不久,CoreOS 公司在它的官方网站上发布了一篇叫做:TPR Is Dead! Kubernetes 1.7 Turns to CRD 的博客(https://coreos.com/blog/custom-resource-kubernetes-v17),旨在指导用户从 TRP 升级成 CRD。不过,现在回头再看一眼这篇文章,平淡无奇的讲述背后,你能否感受到当年这场“开发者战争”的蛛丝马迹呢?其实,Operator 并不平坦的晋级之路,只是 Kubernetes API 生态风起云涌的冰山一角。几乎在每个星期,甚至每一天,都有太多围绕着 Kubernetes 开发者生态的角逐,在这个无比繁荣的社区背后,以不为人知的方式开始或者谢幕。而这一切纷争的根本原因却无比直白。Kubernetes 项目,已经被广泛认可为云计算时代应用开发者们的终端入口。这正是为何,无论是 Google、微软,还是 CoreOS 以及 Heptio,所有这个生态里的大小玩家,都在不遗余力的在 Kubernetes API 层上捍卫着自己的话语权,以期在这个未来云时代的开发者入口上,争取到自己的一席之地。而在完成了对收 CoreOS 的收购之后,RedHat 终于在这一领域拿到了可以跟 Google 和微软一较高低的关键位置。2018年,RedHat 不失时机的发布了 Operator Framework,希望通过 Operator 周边工具和生态的进一步完善,把 Operator 确立成为分布式应用开发与管理的关键依赖。而伴随着 Operator 越来越多的介入到应用开发和部署流程之后, Kubernetes API 一定会继续向上演进,进一步影响开发者的认知和编程习惯。这,已经成为了云计算生态继续发展下去的必然趋势。而作为这个趋势坚定不移的贯彻者,无论是 Istio,还是 Knative,都在用同样的经历告诉我们这样的道理:只有构建在 Kubernetes 这个云时代基础设施事实标准上的开发者工具,才有可能成为下一个开发者领域的 “Operator” 。本文作者:amber涂南阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

January 11, 2019 · 4 min · jiezi

适合 分布式系统工程师 的 分布式系统理论 中英对照

Distributed systems theory for the distributed systems engineer适合 分布式系统工程师 的 分布式系统理论Gwen Shapira, who at the time was an engineer at Cloudera and now is spreading the Kafka gospel, asked a question on Twitter that got me thinking.Gwen Shapira曾在Cloudera做工程师,现在宣传Kafka,他在Twitter问了以下问题,使我有所思考。I need to improve my proficiency in distributed systems theory. Where do I start? Any recommended books?我想在分布式理论上有所提升。应该从哪开始?有推荐的书?— Gwen (Chen) Shapira (@gwenshap) August 7, 2014My response of old might have been “well, here’s the FLP paper, and here’s the Paxos paper, and here’s the Byzantine generals paper…”, 我第一反应是“可以看:FLP论文、paxos论文、Byzantine将军论文”,and I’d have prescribed a laundry list of primary source material which would have taken at least six months to get through if you rushed. 我推荐的主要阅读材料,如果你贸然去读,你至少要阅读6个月才会有感觉。But I’ve come to thinking that recommending a ton of theoretical papers is often precisely the wrong way to go about learning distributed systems theory (unless you are in a PhD program). 由此可知,推荐一吨的理论论文让你阅读,这是了解分布式系统的错误的方式。(除非你在读博士)Papers are usually deep, usually complex, and require both serious study, and usually significant experience to glean their important contributions and to place them in context. 论文一般是深奥、复杂的,而且需要一系列学习和丰富的经验才能感觉到其贡献、才能其放到对应的场景(以理解和应用)。What good is requiring that level of expertise of engineers?工程师了解分布式理论有什么好处?And yet, unfortunately, there’s a paucity of good ‘bridge’ material that summarises, distills and contextualises the important results and ideas in distributed systems theory; 很不幸,几乎没有好的引导文章,来总结、提炼、场景化 分布式系统理论中的重要结论和想法;particularly material that does so without condescending. 特别是 通俗易懂的引导文章 更没有。Considering that gap lead me to another interesting question:考虑这样的空白区域,让我想问另一个问题:What distributed systems theory should a distributed systems engineer know?一个分布式系统工程师应该了解什么样的分布式系统理论?A little theory is, in this case, not such a dangerous thing. 这种情况下,了解一点点理论并不是坏事。So I tried to come up with a list of what I consider the basic concepts that are applicable to my every-day job as a distributed systems engineer. 我日常工作是一个分布式系统工程师,我认为适合我的基本概念,下面会给出这些基本概念。Let me know what you think I missed!你认为我缺失的请告知我!First steps 准备These four readings do a pretty good job of explaining what about building distributed systems is challenging. 下面四个读物解释了构建分布式系统会遇到的困难。Collectively they outline a set of abstract but technical difficulties that the distributed systems engineer has to overcome, and set the stage for the more detailed investigation in later sections这些读物都勾勒了一些列 抽象而非技术 的困难,分布式系统工程师必须要克服这些困难。这些读物的后面章节有更详细的研究。Distributed Systems for Fun and Profit is a short book which tries to cover some of the basic issues in distributed systems including the role of time and different strategies for replication.Distributed Systems for Fun and Profit 是一本小书,它想覆盖分布式系统中的一些基本问题,包括 时钟所起的作用、不同策略的复制。Notes on distributed systems for young bloods - not theory, but a good practical counterbalance to keep the rest of your reading grounded.Notes on distributed systems for young bloods - 非理论,而是一个很好的实践,以让你落到实处。A Note on Distributed Systems - a classic paper on why you can’t just pretend all remote interactions are like local objects.A Note on Distributed Systems - 一个经典论文,关于 为什么你不能假装所有远程交互像本地对象一样。The fallacies of distributed computing - 8 fallacies of distributed computing that set the stage for the kinds of things system designers forget.The fallacies of distributed computing 分布式计算的8个错误的推论,以提醒系统设计者。You should know about safety and liveness properties:你应该知道 安全 和 活力:safety properties say that nothing bad will ever happen. For example, the property of never returning an inconsistent value is a safety property, as is never electing two leaders at the same time.安全 说的是 永远不会发生坏事。比如,不返回不一致的值 是 一种 安全, 同一时刻不会选出两个 主节点 也是 一种 安全。liveness properties say that something good will eventually happen. For example, saying that a system will eventually return a result to every API call is a liveness property, as is guaranteeing that a write to disk always eventually completes.活力 说的是 好事情终究会发生。比如,对于每个api调用,一个系统终究会返回一个结果,这是一种 活力;保证一次写磁盘最终总能结束,这是一种 活力。Failure and Time 失败和时钟Many difficulties that the distributed systems engineer faces can be blamed on two underlying causes:分布式系统工程师面对的许多困难可以归结为以下两个原因:Processes may fail进程可能失败There is no good way to tell that they have done soThere is a very deep relationship between what, if anything, processes share about their knowledge of time, what failure scenarios are possible to detect, and what algorithms and primitives may be correctly implemented. 进程间怎么共用时钟、什么样的失败可以检测、什么样的算法和原语可以被正确实现,这三者之间有很深的联系。Most of the time, we assume that two different nodes have absolutely no shared knowledge of what time it is, or how quickly time passes.一般情况下,我们假设不同节点绝对无法共用时钟(时刻值或流过了多少时间)You should know:你应该知道:The (partial) hierarchy of failure modes: crash stop -> omission -> Byzantine. You should understand that what is possible at the top of the hierarchy must be possible at lower levels, and what is impossible at lower levels must be impossible at higher levels.失败模型的层次:节点崩溃后关机 -> 节点崩溃后死机(经过无限长时间后才响应) -> 恶意节点 (不遵守约定的规则) 。 各个层次间逐渐将限制放松,你应该知道这些限制.How you decide whether an event happened before another event in the absence of any shared clock. This means Lamport clocks and their generalisation to Vector clocks, but also see the Dynamo paper.两个节点之间,没有任何共用时钟,你怎么确定一个节点上的一个事件和另一个节点上的另一个事件之间的先后顺序. 这就要阅读Lamport时钟和更一般化的Vector时钟, 也可以阅读Dynamo论文.How big an impact the possibility of even a single failure can actually have on our ability to implement correct distributed systems (see my notes on the FLP result below).允许单节点失败对实现正确的分布式系统有多大的冲击?(见下面FLP结论处)Different models of time: synchronous, partially synchronous and asynchronous时钟的不同模型:同步、部分同部 、 异步That detecting failures is a fundamental problem, one that trades off accuracy and completeness - yet another safety vs liveness conflict. The paper that really set out failure detection as a theoretical problem is Chandra and Toueg’s ‘Unreliable Failure Detectors for Reliable Distributed Systems’. But there are several shorter summaries around - I quite like this random one from Stanford.失败检测是一个基本问题,失败检测可以平衡准确度和完成度(如果能检测到失败了,则可以容许不那么准确、没完全做完),失败检测也可以解决安全和活力间的冲突。把失败检测作为理论来研究的论文是 Chandra and Toueg’s ‘Unreliable Failure Detectors for Reliable Distributed Systems’. 不过也有一些简短的总结-我特别喜欢this random one from Stanford.The basic tension of fault tolerance 容错导致的基本矛盾A system that tolerates some faults without degrading must be able to act as though those faults had not occurred. 一个系统容忍一些错误而没有降级 必须能当成 就像这些错误没有发生过一样。This means usually that parts of the system must do work redundantly, but doing more work than is absolutely necessary typically carries a cost both in performance and resource consumption. 这意味着系统的一部分要冗余地工作(同样的功能部署多个节点),冗余是绝对必要的,冗余一般会带来性能和资源的消耗。This is the basic tension of adding fault tolerance to a system.这就是给一个系统添加冗余的基本矛盾。You should know:你应该知道:The quorum technique for ensuring single-copy serialisability. See Skeen’s original paper, but perhaps better is Wikipedia’s entry).确保串行单复制的多数派技术. 见 Skeen的原始论文, 不过或许更好的是 Wikipedia’s entry).(多数派中有一个是主节点,其余为从节点,以主节点接收到的写请求序列为准[串行],主节点单方面的要求从们接受字节的写请求序列[从节点不得反抗、不得有异议:从节点是非恶意的、遵守全局规则的、非拜占庭的])About 2-phase-commit, 3-phase-commit and Paxos, and why they have different fault-tolerance properties.两步提交、 三步提交 、Paxos, 以及为什么他们不同于容错.How eventual consistency, and other techniques, seek to avoid this tension at the cost of weaker guarantees about system behaviour. The Dynamo paper is a great place to start, but also Pat Helland’s classic Life Beyond Transactions is a must-read.最终一致性、其他技术 以 对系统行为做更弱的保证 为代价 来 设法避开 此矛盾 . 可以看 Dynamo 论文 , 不过 必须要读 Pat Helland的论文 经典 Life Beyond Transactions .Basic primitives 基本原语There are few agreed-upon basic building blocks in distributed systems, but more are beginning to emerge. You should know what the following problems are, and where to find a solution for them:在分布式系统中,很少有约定的基本构建块,更多的是处于形成中的基本构建块。有应该知道下面的问题是什么,并且从哪能找到他们的解决方案:Leader election (e.g. the Bully algorithm)主节点选举 (例如 Bully 算法)Consistent snapshotting (e.g. this classic paper from Chandy and Lamport)一致快照 (比如 这个来自 Chandy and Lamport的经典论文 )一致性 (见上面 2PC 、 Paxos 处)Distributed state machine replication (Wikipedia is ok, Lampson’s paper is canonical but dry).分布式状态机复制 (看Wikipedia 就行, Lampson的 论文 是权威但是太枯燥了).Broadcast - delivering messages to more than one node at once广播 - 同时发送消息给集群Atomic broadcast - can you deliver a message to all nodes in a group, or none?原子广播 - 你能发送消息给一集群,使得要么集群中的所有节点都收到了这条信息、要么集群中全部节点都没收到此消息?(这就是原子广播)Gossip (the classic paper)Gossip (经典论文)Causal multicast (but also consider the enjoyable back-and-forth between Birman and Cheriton).因果广播 (也可以看看 Birman和forth ).Chain replication (a neat way of ensuring consistency and ordering of writes by organizing nodes into a virtual linked list).链式复制 (将节点们放进一个虚拟链表中,从而可以干净的确保写请求的一致性和顺序 ).The original paper原始论文一系列改良 for read-mostly workloads对读请求占绝大多数的一系列改良An experiential report by @slfritchie@slfritchie给出的 一个经验报告Fundamental Results 基础结论Some facts just need to be internalised. There are more than this, naturally, but here’s a flavour:有些事实只需要主观理解(不需要关注证明).You can’t implement consistent storage and respond to all requests if you might drop messages between processes. This is the CAP theorem.如果节点间可能丢失消息[:P],那么你不可能 既 实现一致性存储[:C] 又 响应所有时刻的请求[:A]. 这就是 CAP理论.Consensus is impossible to implement in such a way that it both a) is always correct and b) always terminates if even one machine might fail in an asynchronous system with crash-* stop failures (the FLP result). The first slides - before the proof gets going - of my Papers We Love SF talk do a reasonable job of explaining the result, I hope. Suggestion: there’s no real need to understand the proof.在一个异步系统中,一致性不可能以这样一个途径实现:既a) 总是正确的 ; 又b) 总是能结束 即使只有一个节点可能以 崩溃-*停止 失败 (FLP结论). 在看证明之前,看下我以简明的方式解释FLP结论的论文 Papers We Love SF talk . 建议: 没有理解证明的需求.(一个异步系统中,假设节点崩溃后停止而不是奔溃后又恢复;1、要确保结果总是正确的,2、每次写请求能够在有限时间内返回结果。这两点没法同时满足:这就是FLP结论)Consensus is impossible to solve in fewer than 2 rounds of messages in general.一般地,只进行少于2轮的消息传递,不可能达成一致性 .Atomic broadcast is exactly as hard as consensus - in a precise sense, if you solve atomic broadcast, you solve consensus, and vice versa. Chandra and Toueg prove this, but you just need to know that it’s true.原子广播和一致性,二者的难度精确的相等。更直白的说,如果你能解原子广播,那么你也能解一致性,反之亦然。 Chandra 和 Toueg 证明了这一点, 但是你只需要知道这个论断是成立的。Real systems 真实系统The most important exercise to repeat is to read descriptions of new, real systems, and to critique their design decisions. Do this over and over again. Some suggestions:最重要的、应该不断重复的实践是:读新的、真实的系统的描述,并评价他们设计的决定。 下面是建议的系统:Google:GFSSpannerF1ChubbyBigTableMillWheelOmegaDapperPaxos Made LiveThe Tail At ScaleNot Google:DryadCassandraCephRAMCloudHyperDexPNUTSAzure Data Lake StorePostscript 结尾If you tame all the concepts and techniques on this list, I’d like to talk to you about engineering positions working with the menagerie of distributed systems we curate at Cloudera.如果你驯服了这个列表中的所有概念和技术,我很乐意和你聊聊Cloudera的分布式系统工程师职位。 ...

January 10, 2019 · 7 min · jiezi

CAP 定理 —— 可用性 (Availability) 和分区容忍 (Partition tolerance)

分布式系统现在变得越来越重要,CAP 定理描述了分布式系统的固有特性,能帮助我们更好地理解分布式系统文章翻译自stackoverflow高赞回答,有能力的同学建议看看原文https://stackoverflow.com/que…Consistency 一致性集群中各个结点的数据总是一致的,因此你可以向任意结点读写数据,并总是能得到相同的数据Availability 可用性可用性表示你总是能够访问集群,即使集群中的某个结点宕机了Partition tolerance 分区容忍容忍集群持续运行,即使他们中存在分区(两个分区中的结点都是好的,只是分区之间不能通信)为了能同时达到可用性和分区容忍,你必须放弃一致性。假设你的集群中有两个结点 X 和 Y,现在他们之间网络通信中断了,因此他们不再能够同步数据,这时你可以作出如下选择:允许结点之间不同步 (保可用性,放弃一致性),或者认为集群不可用 (保一致性,放弃可用性)CAP不能同时成立,所以会出现以下三种组合Consistency & Availability:只要所有结点都在线,那么结点中的数据都是一致的。你可以向任意结点读写数据,并确信他们的数据都是相同的。但只要分区出现,数据将不再同步 (P不成立,即不能容忍分区出现)Consistency & Partition tolerance:要求所有结点的数据都是一致的。容忍分区出现,但当有部分结点 down 掉后 (分区出现时集群会认为是部分结点down掉),为了避免数据不同步,集群会变成不可用Availability & Partition tolerance:所有结点保持在线,即使他们之间存在分区不能相互通信,当分区问题解决后再重新同步数据。所以你不能保证所有结点的数据都是一致的 (在分区出现期间或之后)你可以认为 CA 系统实际上不存在,因为分区问题总是会出现在分布式系统中

January 10, 2019 · 1 min · jiezi

Mars 是什么、能做什么、如何做的——记 Mars 在 PyCon China 2018 上的分享

最近,在 PyCon China 2018 的北京主会场、成都和杭州分会场都分享了我们最新的工作 Mars,基于矩阵的统一计算框架。本文会以文字的形式对 PyCon 中国上的分享再进行一次阐述。听到 Mars,很多第一次听说的同学都会灵魂三问:Mars 是什么,能做什么,怎么做的。今天我们就会从背景,以及一个例子出发,来回答这几个问题。背景首先是 scipy 技术栈的全景图,numpy 是基础,它提供了多维数组的数据结构,并提供了它上面的各种计算。再往上,重要的有 scipy,主要面向各种科学计算的操作;pandas,其中核心的概念是 DataFrame,他提供对表类型数据的处理、清洗等功能。往上一层,比较经典的库,有 scikit-learn,它是最知名的机器学习框架之一。最上面一层,是各种垂直领域的库,如 astropy 主要面向天文,biopython 面向生物领域等。从 scipy 技术栈可以看出,numpy 是一个核心的地位,大量上层的库都使用了 numpy 的数据结构和计算。我们真实世界的数据,并不只是表这种二维类型数据那么简单,很多时候,我们要面对的往往是多维数据,比如我们常见的图片处理,首先我们有图片的个数,然后有图片的长宽,以及 RGBA 通道,这就是四维的数据;这样的例子不胜枚举。有这样多维的处理能力,就有处理各种更加复杂,甚至是科学领域的能力;同时,由于多维数据本身包含二维数据,所以,我们也因此具备表类型数据的处理能力。另外,如果我们需要探究数据的内在,光靠对表数据进行一些统计等操作是绝对不够的,我们需要更深层的“数学” 的方法,比如运用矩阵乘法、傅里叶变换等等的能力,来对数据进行更深层次的分析。而 numpy 由于是数值计算的库,加上各种上层的库,我们认为它们很适合用来提供这方面的能力。为什么要做 Mars,从一个例子开始那么,为什么要做 Mars 这个项目呢?我们不妨从一个例子来看。我们试图用蒙特卡洛方法来求解 pi,蒙特卡洛方法其实很简单,就是用随机数的方法来解决特定的问题。如图,这里我们有个半径为1的圆和边长为2的正方形,我们生成很多随机点的方式,通过右下角的公式,我们就可以计算出 pi 的值为 4 乘以落在圆里点的个数除以总的点个数。随机生成的点越多,计算出来的 pi 就越精确。用纯 Python 实现非常简单,我们只要遍历 N 次,生成 x 和 y 点,计算是不是落在圆内即可。运行1千万个点,需要超过10秒的时间。Cython 是常见加速 Python 代码的方式,Cython 定义了 Python 语言的超集,把这个语言翻译到 c/c++,然后再进行编译来加速执行。这里,我们增加了几个变量的类型,可以看到比纯 Python 提升了 40% 的性能。Cython 现在已经成为 Python 项目的标配,核心的 Python 三方库基本都使用 Cython 来加速 Python 代码的性能。我们这个例子里的数据都是一个类型,我们可以想到用专门的数值计算的库,通过矢量化的方式,能极快加速这个任务的性能。numpy 就是当仁不让的选择了,使用 numpy,我们需要的是面向 array 的思维方式,我们应当减少使用循环。这里先用 numpy.random.uniform 来生成 N2 的一个二维数组,然后 data ** 2 会对该数组里的所有数据做平方操作,然后 sum(axis=1) ,会对 axis=1 也就是行方向上求和,这个时候,得到的是长度为 N 的 vector,然后我们用 numpy.sqrt 来对这个 vector 的每一个值求开方,<1 会得到一个布尔值的 vector,即每个点是不是都是落在圆里,最后接一个 sum,就可以求出来总共的点的个数。初次上手 numpy 可能会不太习惯,但是用多了以后,就会发现这种写法的方便,它其实是非常符合直觉的。可以看到,通过使用 numpy,我们写出了更简单的代码,但是性能确大幅提升,比纯 Python 的写法性能提升超过 10 倍。那么 numpy 的代码还能够优化么,答案是肯定的,我们通过一个叫 numexpr 的库,来将 numpy 的多个操作合并成一个操作执行,来加速 numpy 的执行。可以看到,通过 numexpr 优化的代码,性能比纯 Python 代码提升超过 25 倍。此时的代码运行已经相当快了,如果我们手上有 GPU,那么我们可以利用硬件来加速任务执行。这里必须要安利一个库,叫 cupy,他提供了和 numpy 一致的 API,通过简单的 import 替换,就能让 numpy 代码跑在英伟达的显卡之上。这时可以看到,性能大幅提升超过 270 倍。真的非常夸张了。为了让蒙特卡洛方法计算的结果更加精确,我们把计算量扩大 1000 倍。会碰到什么情况呢?没错,这就是大家不时碰到的,OutOfMemory,内存溢出。更惨的是,在 jupyter 里,有时候内存溢出导致进程被杀,甚至会导致之前跑的全部结果都丢失。蒙特卡洛方法还是比较容易处理的,我把问题分解成 1000 个,每个求解1千万数据就好了嘛,写个循环,做个汇总。但此时,整个计算的时间来到了12分钟多,太慢了。此时我们会发现,整个运行过程中,其实只有一个 CPU 在干活,其他核都在原地吆喝。那么,我们怎么让 numpy 并行化呢?首先,numpy 里有一些操作是能并行的,比如 tensordot 来做矩阵乘法,其他大部分操作都不能利用多核。那么,要将 numpy 并行化,我们可以:采用多线程/多进程编写任务分布式化蒙特卡洛方法算 pi 改写成多线程和多进程实现还是非常容易的,我们写一个函数来处理1千万的数据,我们把这个函数通过 concurrent.futures 的 ThreadPoolExecutor 和 ProcessPoolExecutor 来分别提交函数 1000 遍用多线程和多进程执行即可。可以看到性能能提升到 2倍和3倍。但是呢,蒙特卡洛求解 pi 本身就很容易手写并行,考虑更复杂的情况。import numpy as npa = np.random.rand(100000, 100000)(a.dot(a.T) - a).std()这里创建了 10万10万的矩阵 a,输入就有大概 75G,我们拿 a 矩阵乘 a 的转置,再减去 a 本身,最终求标准差。这个任务的输入数据就很难塞进内存,后续的手写并行就更加困难。这里问题就引出来了,我们需要什么样框架呢?提供熟悉的接口,像 cupy 这样,通过简单的 import 替换,就能让原来 numpy 写的代码并行。具备可扩展性。小到单机,也可以利用多核并行;大到一个很大的集群,支持上千台机器的规模来一起分布式处理任务。支持硬件加速,支持用 GPU 等硬件来加速任务执行。支持各种优化,比如操作合并,能利用到一些库来加速执行合并的操作。我们虽然是内存计算的,但不希望单机或者集群内存不足,任务就会失败。我们应当让暂时用不到的数据 spill 到磁盘等等存储,来保证即使内存不够,也能完成整个计算。Mars 是什么,能做什么事Mars 就是这样一个框架,它的目标就是解决这几个问题。目前 Mars 包括了 tensor :分布式的多维矩阵计算。100亿大小的蒙特卡洛求解 pi的问题规模是 150G,它会导致 OOM。通过 Mars tensor API,只需要将 import numpy as np 替换成 import mars.tensor as mt,后续的计算完全一致。不过有一个不同,mars tensor 需要通过 execute 触发执行,这样做的好处是能够对整个中间过程做尽量多的优化,比如操作合并等等。不过这种方式对 debug 不太友好,后续我们会提供 eager mode,来对每一步操作都触发计算,这样就和 numpy 代码完全一致了。可以看到这个计算时间和手写并行时间相当,峰值内存使用也只是 1个多G,因此可以看到 Mars tensor 既能充分并行,又能节省内存的使用 。目前,Mars 实现了 70% 的常见 numpy 接口,完整列表见 这里。我们一致在努力提供更多 numpy 和 scipy 的接口,我们最近刚刚完成了对逆矩阵计算的支持。Mars tensor 也提供了对 GPU 和稀疏矩阵的支持。eye 是创建单位对角矩阵,它只有对角线上有值为1,如果用稠密的方式存储会浪费存储。不过目前,Mars tensor 还只支持二维稀疏矩阵。Mars 怎么做到并行和更省内存和所有的 dataflow 的框架一样,Mars 本身也有计算图的概念,不同的是,Mars 包含粗粒度图和细粒度图的概念,用户写的代码在客户端生成粗粒度图,在提交到服务端后,会有 tile 的过程,将粗粒度图 tile 成细粒度图,然后我们会调度细粒度图执行。这里,用户写下的代码,在内存里会表达成 Tensor 和 Operand 构成的粗粒度图。当用户调用 execute 方法时,粗粒度的图会被序列化到服务端,反序列化后,我们会把这个图 tile 成细粒度图。对于输入 10002000 的矩阵,假设指定每个维度上的 chunk 大小都是 500,那它会被 tile 成 24 一共 8 个chunk。后续,我们会对每个我们实现的 operand 也就是算子提供 tile 的操作,将一个粗粒度的图 tile 成细粒度图。这时,我们可以看到,在单机,如果有8个核,那么我们就可以并行执行整个细粒度图;另外给定 1/8 大小的内存,我们就可以完成整个图的计算。不过,我们在真正执行前,会对整个图进行 fuse 也就是操作合并的优化,这里的三个操作真正执行的时候,会被合并成一个算子。针对执行目标的不同,我们会使用 numexpr 和 cupy 的 fuse 支持来分别对 CPU 和 GPU 进行操作合并执行。上面的例子,都是我们造出来很容易并行的任务。如我们先前提到的例子,通过 tile 之后生成的细粒度图其实是非常复杂的。真实世界的计算场景,这样的任务其实是很多的。为了将这些复杂的细粒度图能够充分调度执行,我们必须要满足一些基本的准则,才能让执行足够高效。首先,初始节点的分配非常重要。比如上图,假设我们有两个 worker,如果我们把 1和3 分配到一个 worker,而将 2和4 分配到另一个 worker,这时当 5 或者 6 被调度的时候,他们就需要触发远程数据拉取,这样执行效率会大打折扣。如果我们一开始将 1和2 分配到一个 worker,将 3和4 分配到另一个 worker,这时执行就会非常高效。初始节点的分配对整体的执行影响是很大的,这就需要我们对整个细粒度的图有个全局的掌握,我们才能做到比较好的初始节点分配。另外,深度优先执行的策略也是相当重要的。假设这时,我们只有一个 worker,执行完 1和2 后,我们调度了 3 的话,就会导致 1和2 的内存不能释放,因为 5 此时还没有被触发执行。但是,如果我们执行完 1和2 后,调度了 5 执行,那么当 5 执行完后,1和2 的内存就可以释放,这样整个执行过程中的内存就会是最省的。所以,初始节点分配,以及深度优先执行是两个最基本的准则,光有这两点是远远不够的,mars 的整个执行调度中有很多具有挑战的任务,这也是我们需要长期优化的对象。Mars 分布式所以,Mars 本质上其实是一个细粒度的,异构图的调度系统。我们把细粒度的算子调度到各个机器上,在真正执行的时候其实是调用 numpy、cupy、numexpr 等等的库。我们充分利用了成熟的、高度优化的单机库,而不是重复在这些领域造轮子。在这个过程中,我们会遇到一些难点:因为我们是 master-slave 架构,我们 master 如何避免单点?我们的 worker 如何避免 Python 的 GIL(全局解释器锁)的限制?Master 的控制逻辑交错复杂,我们很容易写出来高耦合的,又臭又长的代码,我们如何将代码解耦?我们的解法是使用 Actor model。Actor模型定义了并行的方式,也就是一切皆 Actor,每个 Actor 维护一个内部状态,它们都持有邮箱,Actor 之间通过消息传递,消息收到会放在邮箱中,Actor 从邮箱中取消息进行处理,一个 Actor 同时只能处理一个消息。Actor 就是一个最小的并行单元,由于一个 Actor 同时只能处理一个消息,你完全不需要担心并发的问题,并发应当是 Actor 框架来处理的。而所有 Actor 是不是在同一台机器上,这在 Actor 模型里也变得不重要,Actor 在不同机器上,只要能完成消息的传递就可以了,这样 Actor 模型也天然支持分布式系统。因为 Actor 是最小的并行单元,我们在写代码的时候,可以将整个系统分解成很多 Actor,每个 Actor 是单一职责的,这有点类似面向对象的思想,这样让我们的代码得以解耦。另外,Master 解耦成 Actor 之后,我们可以让这些 Actor 分布在不同的机器上,这样就让 Master 不再成为单点。同时,我们让这些 Actor 根据一致性哈希来进行分配,后续如果有 scheduler 机器挂掉, Actor 可以根据一致性哈希重新分配并重新创建来达到容错的目的。最后,我们的 actors 是跑在多进程上的,每个进程里是很多的协程,这样,我们的 worker 也不会受到 GIL 的限制。像 Scala 或者 Java 这些 JVM 语言 可以使用 akka 这个 Actor 框架,对于 Python 来说,我们并没有什么标准做法,我们认为我们只是需要一个轻量的 Actor 框架就可以满足我们使用,我们不需要 akka 里面一些高阶的功能。因此,我们开发了 Mars actors,一个轻量的 Actor 框架,我们 Mars 整个分布式的 schedulers 和 workers 都在 Mars actors 层之上。这是我们 Mars actors 的架构图,在启动 Actor pool 的时候,我们子进程会根据并发启动若干子进程。主进程上有 socket handler 来接受远程 socket 连接传递消息,另外主进程有个 Dispatcher 对象,用来根据消息的目的地来进行分发。我们所有的 Actor 都在子进程上创建,当 Actor 收到一个消息处理时,我们会通过协程调用 Actor.on_receive(message) 方法。一个 Actor 发送消息到另一个 Actor,分三种情况。它们在同一个进程,那么直接通过协程调用即可。它们在一台机器不同进程,这个消息会被序列化通过管道送到主进程的 Dispatcher,dispatcher 通过解开二进制的头部信息得到目标的进程 ID,通过对应的管道送到对应子进程,子进程通过协程触发相应 Actor 的消息处理即可。它们在不同机器,那么当前子进程会通过 socket 把序列化的消息发送到对应机器的主进程,该机器再通过 Dispatcher 把消息送到对应子进程。由于使用协程作为子进程内的并行方式,而协程本身在 IO 处理上有很强的性能,所以,我们的 Actor 框架在 IO 方面也会有很好的性能。上图是裸用 Mars actors 来求解蒙特卡洛方法算 pi。这里定义两个 Actor,一个 Actor 是 ChunkInside,它接受一个 chunk 的大小,来计算落在圆内点的个数;另外一个 Actor 是 PiCaculator,它负责接受总的点个数,来创建 ChunkInside,这个例子就是直接创建 1000 个 ChunkInside,然后通过发送消息来触发他们计算。create_actor 时指定 address 可以让 Actor 分配在不同的机器上。这里可以看到,我们裸用 Mars actors 的性能是要快过多进程版本的。这里我们总结一下,通过使用 Mars actors,我们能不受 GIL 限制,编写分布式代码变得非常容易,它让我们 IO 变得高效,此外,因为 Actor 解耦,代码也变得更容易维护。现在让我们看下 Mars 分布式的完整执行过程。现在有1个 client,3个 scheduler 和 5个worker。用户创建一个 session,在服务端会创建一个 SessionActor 对象,通过一致性哈希,分配到 scheduler1 上。此时,用户运行了一个 tensor,首先 SessionActor 会创建一个 GraphActor,它会 tile 粗粒度图,图上假设有三个节点,则会创建三个 OperandActor,分别分配到不同的 scheduler 上。每个 OperandActor 会控制 operand 的提交、任务状态的监督和内存的释放等操作。此时 1 和 2 的 OperandActor 发现没有依赖,并且集群资源充足,那么他们会把任务提交到相应的 worker 执行,在执行完成后,向 3 通知任务完成,3 发现 1和2 都执行完成后,因为数据在不同 worker 执行,决定好执行 worker 后,先触发数据拉取操作,然后再执行。客户端这边,通过轮询 GraphActor 得知任务完成,则会触发数据拉取到本地的操作。整个任务就完成了。我们对 Mars 分布式做了两个 benchmark,第一个是对 36 亿数据的每个元素加一再乘以2,图中红色叉是 numpy 的执行时间,可以看到,我们比 numpy 有数倍提升,蓝色的虚线是理论运行时间,可以看到我们真实加速非常接近理论时间加速。第二个 benchmark,我们增加了数据量,来到 144 亿数据,对每个元素加1乘以2后,再求和,可以看到单机 numpy 已经不能完成任务了,此时,针对这个任务,我们也可以取得不错的加速比。未来计划Mars 已经在 Github 上源代码,让更多同学来一起参与共建 Mars:https://github.com/mars-project/mars 。在后续 Mars 的开发计划上,如上文说,我们会支持 eager mode,让每一步触发执行,提升对性能不敏感的任务开发以及 debug 时的使用体验;我们会支持更多 numpy 和 scipy 接口;后续很重要的一个是,我们会提供 100% 兼容 pandas 的接口,由于利用了 mars tensor 作为基础,我们也可以提供 GPU 的支持;我们会提供兼容 scikit-learn 的机器学习的支持;我们还会提供在细粒度图上调度自定义函数和自定义类的功能,增强灵活性;最后,因为我们客户端其实并不依赖 Python,任意语言都可以序列化粗粒度图,所以我们完全可以提供多语言的客户端版本,不过这点,我们会视需求而定。总之,开源对我们是很重要的,庞大的 scipy 技术栈的并行化,光靠我们的力量是不够的,需要大家来一起帮我们来共建。现场图片最后再 po 一点现场图片吧,现场观众对 Mars 的问题还是蛮多的。我大致总结下:Mars 在一些特定计算的性能,比如 SVD 分解,这里我们有和用户合作项目的一些测试数据,输入数据是 8亿*32的矩阵做 SVD 分解,分解完再矩阵乘起来和原矩阵做对比,这整个计算过程使用 100个 worker(8核),用7分钟时间算完Mars 何时开源,我们已经开源:https://github.com/mars-project/marsMars 开源后会不会闭源,答:不会Mars actors 的详细工作原理Mars 是静态图还是动态图,目前是静态图,eager mode 做完后可以支持动态图Mars 会不会涉及深度学习,答:目前不会本文作者:继盛阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

January 10, 2019 · 3 min · jiezi

如何基于OceanBase构建应用和数据库的异地多活

摘要: OceanBase是一个通用的分布式的关系型数据库,有很多独特的特点。比如数据库的多租户、高可用、极致弹性伸缩能力。如果把OceanBase当作单库使用,就没有把OceanBase的分布式优势发挥到极致。前言OceanBase是一个通用的分布式的关系型数据库,有很多独特的特点。比如数据库的多租户、高可用、极致弹性伸缩能力。如果把OceanBase当作单库使用,就没有把OceanBase的分布式优势发挥到极致。本文主要分享一个基于分布式架构的应用把OceanBase数据库的分布式优势发挥到极致所需要了解的OceanBase基础,这也是理解蚂蚁金服的基于OceanBase构建的三地五中心异地多活架构的基础。分布式数据库开发相关问题好的性能首先是设计出来的,应用如果追求极致的性能,就需要关注OceanBase里数据的相关事情。如:数据如何分布?数据如何读写?存储容量瓶颈怎么办?访问性能瓶颈怎么办?数据库出故障时数据可用性和可靠性具体怎样?应用需要做什么特殊处理么?数据库扩展时应用需要迁移数据么?数据迁移的时候对应用有什么影响?这些问题对理解OceanBase的分布式特点很有帮助。后面我们逐步看看OceanBase是如何应对。OceanBase集群外观首先简介一下OceanBase集群的外观。OceanBase是以集群形式运行的,由一堆服务器组成。上图是「三副本」部署,机器会分为三组,每组一个区域(称为Zone),各个机器通过网络互相访问。没有光纤交换机、共享存储以及直连网线等。服务器通常建议CPU、内存和磁盘尽可能的大,磁盘建议用普通SSD盘。普通服务器的好处是便宜,劣势是可靠性和性能可能不如小型机那么高。也就是说OceanBase可以部署在一组可靠性和性能不是特别高的普通服务器上,却提供了高性能、高可用和高可靠、弹性伸缩等多项能力。以上是一个OceanBase集群的外观和能力,但是提供给业务的并不是这个集群的全部资源和能力,而是其子集,即租户(Tenant)。OceanBase多租户特性OceanBase定义了一些基本的资源规格(Resource unit config,如4CPU8Gmem500Gdisk等),然后选取某类资源规格创建一组资源池(Resource Pool),此时集群资源就有一部分被分配出去了。最后将这个资源池关联到一个新建租户,则租户就可以使用这个资源池的能力。OceanBase默认有个sys租户,管理整个集群。用户租户必须在sys租户内部创建。如下示例就是创建租户的过程。#sys租户登录方法$mysql -hxxx.xx.11.11 -uroot@sys#obdemo -P2883 -proot oceanbase -A#资源规格(UnitConfig)create resourceunit S0_uc max_cpu=2,max_memory=‘5G’,…资源单元(Unit)create resourcepool Pool_01 unit=‘S0_uc’,unit_num=2,…租户(Tenant)create tenant test_ins resource_pool_list= (‘Pool_01’),…OceanBase兼容了大部分MySQL连接协议和语法,租户的使用体验跟MySQL实例很像。研发可以在租户里创建数据库(Database)、表(Table)。还包括分区表等。OceanBase里描述数据的最小粒度是分区。普通的表(非分区表)就是一个分区,分区表则包含多个分区。租户的示意图如下。租户之间数据是绝对隔离,资源有一定程度隔离。研发可以将业务先垂直拆分为多个独立的子业务,分别使用不同的租户或者集群。OceanBase资源单元租户里并不知道数据具体在哪个机器上,也可以说没必要知道。只是租户的性能还取决于运维为租户规划的资源池分布情况,所以了解一下资源单元的分布特点对性能规划也是有意义的。资源池(Resource Pool)是由一组资源单元(Resource Unit)组成。资源单元数量默认跟Zone的数量一致或者是它的倍数(可以配置具体分布在哪些Zone以及每个Zone里的Unit数量)。如下图资源单元具备一定的资源能力,是数据的容器。租户拥有的资源单元规格和数量决定了这个租户最大性能。资源单元可以在同一个Zone的不同节点之间自由迁移,OceanBase借此来维持各个节点的资源利用率尽可能维持一个均衡状态。OceanBase拆分设计数据库拆分数据库拆分有两种。一是垂直拆分。即按业务模块拆分到不同的实例或库里。为了模块之间互不影响,拆分到不同的实例比较好。在OceanBase里实现时可以是拆分到同一个集群里不同租户或者不同集群里的租户都可以,取决于业务规模和数据库集群规模。垂直拆分很好理解,后面不再赘述。一是水平拆分。即按某个业务维度将数据拆分到多个分片。这些分片可以是在一个库或者不同库或者不同实例的不同库下。水平拆分实现又有两类常用的选择。如下:分库分表。将一个业务表拆分到N个相同结构的物理表中。中间件做业务表(逻辑表)到分表(物理表)的映射路由以及其他相关操作(如结果聚合计算)等。这个N个物理表可以在不同实例的不同分库中。分库的维度和分表的维度可以不一样,比较灵活。分区表。将一个物理表设计为分区表,拆分到N个分区。分区表的各个分区结构是数据库内部保证一致。OceanBase选择的是分区表的水平拆分方式,并且支持二级分区表(即有2个不同的拆分维度叠加使用)。水平拆分示意图如下:上图是分库分表和分区表同时结合使用。业务表order先经过中间件拆分为100个分表(存在10个分库里),每个分表在OceanBase内部又是一个分区表(100个分区)。分库分表的维度和分区表分区的维度都是一致的,根据用户ID。分库分表和分区各有利弊。分库分表的好处是各个分表的结构一致性是在中间件层保证,比较好控制,比较适合灰度变更(允许部分分表结构不一致,最终必须全部一致)。此外更大的好处是,分库分表是实现异地多活单元话架构的必不可少的条件。缺点是中间件的SQL支持范围有限。分区的好处是在数据库内部解决了拆分问题。针对分区表的SQL功能是数据库SQL引擎的本质工作,相关特性(全局索引、二级分区等)会持续开发完善。分区分库分表架构设计,需要确定机器数、实例数、分库数和分表数的拓扑,性能理论上限取决于主实例所处的机器节点数。此后要做扩展就要调整这四个元素的数目及其联系。这种扩展很可能涉及到分表数据的迁移,需要借助外部工具或产品实现。分区架构设计,研发确定分区策略和分区数,运维确定租户的资源单元数量,OceanBase确定资源单元(Unit)在哪些机器节点上以及分区(Partition)在哪些资源单元里。同一个分区不能跨节点存储。如下图。此后要做扩展就是调整资源单元的规格、数量。OceanBase在确定Unit里的分区的位置时会尽量让每个节点的负载维持均衡。这个负载的计算方式比较复杂,会综合考虑OB节点内部CPU、内存和空间利用率等。分区随意分布对应用性能很可能有负面影响。当业务上有联系的两个表的分区分布在不同的资源单元里(同时也分布在不同的节点里),这两个表的连接就难以避免跨节点请求数据,网络上的延时会影响这个连接的性能。注: t1(p0) 表示表t1的0号分区。每个分区在集群里数据实际有三份,即三副本(Replica)。图中忽略了Zone2和Zone3的细节。三副本之间的数据同步靠把Leader副本的事务日志同步到其他Follower副本中。Paxos协议会保障这个事务日志传输的可靠性(事务日志在一半以上成员里落盘,剩余成员最终也会落盘),同时还有个以分区为粒度的选举机制,保障Leader副本不可用的时候,能快速从现有两个Follower副本里选举出新的Leader副本,并且数据还绝对不丢。这里就体现了故障切换时两个重要指标:RPO=0, RTO<30s。Locality图5中t0和t1业务上是有联系的表(如主表和详情表),两者都是分区表,分区策略和分片数都相同,OceanBase提供了一个表属性“表分组”(TableGroup)。设置为同一个表分组的不同表的分区数一定一样,并且同号分区组成一个“分区分组”(PartitionGroup)。同一个分区分组的分区一定会分配在同一个资源单元(Unit)内部(也就是会在同一个节点内部),彼此的连接逻辑就避免了跨节点请求。另外一个效果是如果一个事务同时修改两个有业务关联的分区,使用分区分组也可以规避跨节点的分布式事务。这个表分组属性的设置就是OceanBase的Locality特性之一——影响相关分区的分布。实际上每个分区都有三副本(Replica, 本文例子),图5中省略了t0(p0)和t1(p0)的其他两个副本都分别会在同一个Unit里分配。不仅如此,每个分区的三副本里都会有leader副本默认提供读写服务。leader副本是选举出来的。t0(p0)和t1(p0)的leader副本也一定会在同一个Unit里(即在同一个Zone里)。这样才彻底的避免了连接的时候跨节点请求。OceanBase的Locality特性还可以指定租户/数据库/表的默认Zone,这样下面的表的leader副本会优先被选举为这个Zone里副本。如下面例子,数据库和表会继承租户的默认设置,当然也可以自己修改primary_zone或者locality属性覆盖上层设置。:create tenant obtrans0primary_zone=‘hz1’;create table item (…)locality = ‘F@hz1, F@hz2, F@hz3,R{all_server}@hz1, R{all_server}@hz2, R{all_server}@hz3’注:F表示全功能副本,R表示只读副本。设置primary_zone后单个租户的所有表的读写都集中到一个Zone里,该租户在其他zone里的Unit就没有读写压力。通常这样做是源于业务应用的要求。如应用的请求都是来自于这个Zone,为了规避应用跨Zone读写数据性能下降。不过primary_zone更大的意义在于当集群里有很多租户的时候,可以将不同业务租户的读写压力分摊到不同Zone的机器,这样集群整体资源利用率提升,所有应用的总体性能也得到提升。后面要介绍的异地多活架构就充分利用OceanBase这个特性,在数据库层面将拆分的表的业务读写点尽可能分散到所有Zone的所有机器上。除了表与表之间可能有联系,业务模块之间也可能有联系。一个业务过程可能会横跨多个业务模块,前面这些模块的数据被垂直拆分到多个租户里。OceanBase的Locality特性“租户分组”(TenantGroup)还可以设置不同租户之间的联系。如下租户交易订单和支付订单在业务上是先后发生。create tenantgroup tgtrade tenant_array=(‘obtrade0’, ‘obpay0’);租户分组的意义依然是为了在分布式架构下尽可能将一个业务流程内多次数据库请求都约束在同一个Zone或者Region(注:OceanBase将地域相邻的Zone定义为一个Region)里。OceanBase异地多活架构异地多活概念异地多活的概念一直都有,只是内涵不断变化。以双机房多活为例,应用通常都是无状态的,可以多地部署。数据库有状态,传统数据库只有主库可以提供读写,备库最多只能提供只读服务(如ORACLE的Active Dataguard):1. 应用双活,数据库A地读写,B地不可读写。这种只有应用多活,数据库是异地备份容灾(无并发)。2. 应用双活,数据库A地读写,B地只读。这种也是应用双活,数据库读写分离(实例级并发)。3. 应用双活,数据库双活,两地应用同时读写不同表。这种数据库双向同步,应用同时错开写不同的数据(表级并发)。4. 应用双活,数据库双活,两地应用同时读写相同表不同记录。这种数据库双向同步,应用同时错开写不同的数据(行级并发)。5. 应用双活,数据库双活,两地应用同时读写相同表相同记录。这种数据库双向同步,应用同时写相同的数据,最终会因为冲突一方事务回滚(行级并发写冲突)上面第1种情形,B地应用是跨地域远程读写数据库。两地距离较大的时候性能会很不好。2的B地应用是本地访问数据库。3,4,5三种情形两地数据库都提供读写服务,对应用而言是本地访问数据库,但到分布式数据库内部,其要读写的数据是否正好在本地就取决于业务和数据库的拆分设计。有这么一种情形,B地应用访问B地数据库实例,请求的数据的写入点恰好是A地,这样会走分布式数据库内部路由远程A地实例中拿到数据,性能上会有些下降,而业务可能不知道为什么。OceanBase水平拆分方案为了避免在分布式数据库OceanBase内部发生跨Zone请求,应用的请求流量的水平拆分规则和数据的水平拆分规则要保持一致并联动,就可以实现真正的应用向本地实例请求读写的数据恰好就是在本地实例种。这就是Locality的用途所在。首先业务架构层面能根据用户ID(uid)做拆分,将用户拆分为100分。x和y是用户直接相关的表,都根据uid拆分为100个分表,分布在5个不同的租户里。x[00-19]表示20个分表。每个租户的Leader分别分布在不同的Zone。uid:00-19表示 分到这20个分片的用户数据和用户流量。应用层面在某个环节能根据UID将用户请求转发到对应的机房(Zone),应用服务之间的请求信息都有这个UID信息,后面应用请求都在本机房流转,并且访问数据库时通过分库分表中间件(DBP)和OceanBase的反向代理(OBProxy)就路由到本机房的业务租户上。实际使用这个架构时,有几个隐含的前提,Zone1和Zone2是同城双机房,Zone3和Zone4是同城双机房,两个城市靠的比较近,Zone5 实际很远,所以一般不提供写入。为节省空间,Zone5里的副本放的是日志副本。应用异地多活架构上面要实现OceanBase在每个Zone的应用写入都是恰好是本地访问,关键点就是应用流量水平拆分规则跟数据水平拆分规则保持一致。而应用要维持这样一个规则,需要很多产品都要支持水平拆分等。如下图图中流量接入层的“负载均衡”环节负责调整用户流量到对应的机房(Zone),此后的应用之间的请求就都是本地访问。能使用这个解决方案的业务都是能按用户维度做水平拆分。有些业务的拆分维度跟用户维度是冲突的,或者有些业务的数据只支持集中写入等,不能做到上面这种多活,但也可以使用OceanBase,实现单点写入,多点读取的功能。OceanBase在异地容灾和多活架构方案中的价值就是支持水平拆分规则的定义、解决了多个机房间数据同步和一致性问题、始终具备高可用和弹性伸缩能力等。参考OceanBase负载均衡的魅力蚂蚁金服异地多活的微服务体系本文作者:mq4096阅读原文本文为云栖社区原创内容,未经允许不得转载。

January 9, 2019 · 1 min · jiezi

Mars 算法实践——人脸识别

Mars 是一个基于矩阵的统一分布式计算框架,在之前的文章中已经介绍了 Mars 是什么, 以及 Mars 分布式执行 ,而且 Mars 已经在 GitHub 中开源。当你看完 Mars 的介绍可能会问它能做什么,这几乎取决于你想做什么,因为 Mars 作为底层运算库,实现了 numpy 70% 的常用接口。这篇文章将会介绍如何使用 Mars 完成你想做的事情。奇异值分解 (SVD)在处理纷繁的数据时,作为数据处理者,首先想到的就是降维,SVD 就是其中一种比较常见的降维方法,在 numpy.linalg 模块中就有 svd 方法,当我们有20000个100维的数据需要处理,调用 SVD 接口:In [1]: import numpy as npIn [2]: a = np.random.rand(20000, 100)In [3]: %time U, s, V = np.linalg.svd(a)CPU times: user 4min 3s, sys: 10.2 s, total: 4min 13sWall time: 1min 18s可以看到即使 Numpy 使用了 mkl 加速,也需要1分多钟的运行时间,当数据量更大时,单机的内存已经无法处理。Mars 也实现了 SVD ,但是它比 Numpy 有更快的速度,因为利用矩阵分块计算的算法,能够并行计算:In [1]: import mars.tensor as mtIn [2]: a = mt.random.rand(20000, 100, chunk_size=100)In [3]: %time U, s, V = mt.linalg.svd(a).execute()CPU times: user 5.42 s, sys: 1.49 s, total: 6.91 sWall time: 1.87 s可以看到在相同数据量情况下,Mars 有几十倍速度的提升,仅仅需要1秒多钟就可以解决20000数据量的降维问题。想象一下淘宝用户数据做矩阵分解时,分布式的矩阵运算就显现出其价值。主成分分析 (PCA)提到降维,主成分分析也是一种重要的手段。PCA 会选取包含信息量最多的方向对数据进行投影,其投影方向可以从最大化方差或者最小化投影误差两个角度理解。也就是通过低维表征的向量和特征向量矩阵,可以基本重构出所对应的原始高维向量。其最主要的公式如下所示:xi为每个样本的数据,j为新的投影方向,我们的目标就是使得投影方差最大化,从而找到主特征。上面式子中的矩阵C在数学中可以用协方差矩阵表示,当然首先要对输入的样本做中心化调整。我们可以用随机产生的数组看一下 Numpy 是如何实现 PCA 降维操作:import numpy as npa = np.random.randint(0, 256, size=(10000, 100))a_mean = a.mean(axis=1, keepdims=True)a_new = a - a_meancov_a = (a_new.dot(a_new.T)) / (a.shape[1] - 1)#利用SVD求协方差矩阵前20个特征值U, s, V = np.linalg.svd(cov_a)V = V.Tvecs = V[:, :20]#用低纬度的特征向量表示原数据a_transformed = a.dot(vecs)由于随机产生的数据本身就没有太强的特征,所以在100维数据中象征性的取出前20维,一般可以用特征值的比例取总和的前99%之类的数值。再看一下 Mars 是如何实现的:import mars.tensor as mta = mt.random.randint(0, 256, size=(10000, 100))a_mean = a.mean(axis=1, keepdims=True)a_new = a - a_meancov_a = (a_new.dot(a_new.T)) / (a.shape[1] - 1)#利用SVD求协方差矩阵前20个特征值U, s, V = mt.linalg.svd(cov_a)V = V.Tvecs = V[:, :20]#用低纬度的特征向量表示原数据a_transformed = a.dot(vecs).execute()可以看到除了 import 的不同,再者就是对最后需要数据的变量调用 execute 方法,甚至在未来我们做完 eager 模式后, execute 都可以省去,以前用 Numpy 写的算法可以几乎无缝转化成多进程以及分布式的程序,再也不用自己手动去写MapReduce。人脸识别当 Mars 实现了基础算法时,便可以使用到实际的算法场景中。PCA最著名的应用就是人脸特征提取以及人脸识别,单个人脸图片的维度很大,分类器很难处理,早起比较知名的人脸识别 Eigenface 算法就是采用PCA算法。本文以一个简单的人脸识别程序作为例子,看看 Mars 是如何实现该算法的。本文的人脸数据库用的是ORL face database,有40个不同的人共400张人脸图片,每张图片为 92112 像素的灰度图片。这里选取每组图片的第一张人脸图片作为测试图片,其余九张图片作为训练集。首先利用 python 的 OpenCV 的库将所有图片读取成一个大矩阵,也就是 36010304大小的矩阵,每一行是每个人脸的灰度值,一共有360张训练样本。利用 PCA 训练数据,data_mat 就是输入的矩阵,k 是需要保留的维度。import mars.tensor as mtfrom mars.session import new_sessionsession = new_session()def cov(x): x_new = x - x.mean(axis=1, keepdims=True) return x_new.dot(x_new.T) / (x_new.shape[1] - 1)def pca_compress(data_mat, k): data_mean = mt.mean(data_mat, axis=0, keepdims=True) data_new = data_mat - data_mean cov_data = cov(data_new) U, s, V = mt.linalg.svd(cov_data) V = V.T vecs = V[:, :k] data_transformed = vecs.T.dot(data_new) return session.run(data_transformed, data_mean, vecs)由于后续做预测识别,所以除了转化成低维度的数据,还需要返回平均值以及低维度空间向量。可以看到中间过程平均脸的样子,前几年比较火的各地的平均脸就可以通过这种方式获取,当然这里的维度以及样本比较少,大概只能看出个人脸的样子。其实 data_transformed 中保存的特征脸按照像素排列之后也能看出特征脸的形状。图中有15个特征脸,足以用来做一个人脸分类器。另外在函数 PCA 中用了 session.run 这个函数,这是由于三个需要返回的结果并不是相互独立的,目前的延迟执行模式下提交三次运算会增加运算量,同一次提交则不会,当然立即执行模式以及运算过的部分图的剪枝工作我们也在进行中。当训练完成之后,就可以利用降维后的数据做人脸识别了。将之前非训练样本的图片输入,转化成降维后的维度表示,在这里我们就用简单的欧式距离判断与之前训练样本中每个人脸数据的差距,距离最小的就是识别出的人脸,当然也可以设置某个阈值,最小值超过阈值的判断为识别失败。最终在这个数据集下跑出来的准确率为 92.5%,意味着一个简单的人脸识别算法搭建完成。# 计算欧氏距离def compare(vec1, vec2): distance = mt.dot(vec1, vec2) / (mt.linalg.norm(vec1) * mt.linalg.norm(vec2)) return distance.execute()未来上文展示了如何利用 Mars 一步一步地完成人脸识别小算法的过程,可以看到 Mars 类 Numpy 的接口对算法开发人员十分友好,算法规模超出单机能力时,不再需要关注如果扩展到分布式环境,Mars 帮你处理背后所有的并行逻辑。当然,Mars 还有很多可以改进的地方,比如在 PCA 中对协方差矩阵的分解,可以用特征值、特征向量计算,计算量会远小于 SVD 方法,不过目前线性代数模块还没有实现计算特征向量的方法,这些特性我们会一步步完善,包括 SciPy 里各种上层算法接口的实现。大家有需求的可以在 GitHub 上提 issue 或者帮助我们共建 Mars。Mars 作为一个刚刚开源的项目,十分欢迎提出其他任何想法与建议,我们需要大家的加入,让 Mars 越来越好。本文作者:继盛阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

January 9, 2019 · 2 min · jiezi

OceanBase迁移服务:向分布式架构升级的直接路径

2019年1月4日,OceanBase迁移服务解决方案在ATEC城市峰会中正式发布。蚂蚁金服资深技术专家师文汇和技术专家韩谷悦共同分享了OceanBase迁移服务的重要特性和业务实践。蚂蚁数据库架构的三代升级史在过去的十多年时间里,蚂蚁在整个基础数据库架构上一共经历了三代升级。第一代数据架构是构建在IOE的基础之上——IBM的小型机、Oracle的商业数据库,还有EMC的共享存储。基于第一代IOE架构的运维成本是非常高的,同时稳定性的挑战也是非常大的。随着业务的快速发展,这套架构已经完全没有办法适应业务发展的增速。随之诞生的是第二代架构,第二代架构的主体是OE——也就是Oracle和EMC,加上蚂蚁自身的分布式中间件,解决了业务的水平和垂直的弹性能力。这一代架构其实伴随着蚂蚁走了很多年。随着4G、5G时代的到来和金融的普及化,人们的生活越来越离不开移动支付,业务井喷式的发展给底层的数据库提出了更高的要求。这些要求包括更高的稳定性,快速恢复能力和极致的弹性能力等。于是最终演进到了我们如今的第三代架构。第三代架构是由OceanBase为代表的金融级云数据库和分布式中间件所构成。数据库架构升级的挑战伴随着整个蚂蚁的发展,整个数据库的架构也仅仅演进了三代。这其中一个很重要的原因就是对于任何企业而言,整个数据库的架构升级都是一件非常有挑战的事情。蚂蚁金服资深技术专家师文汇说道,“用一个我们内部经常说的比喻,就是数据库的架构升级就好像是在给一个高速运行的飞机更换引擎。”更换引擎的目的是为了拥有更好的动力,做更多技术上的创新。但是横亘在眼前的问题是,如何才能做到稳妥创新,保证驾驶中的飞机平稳顺利的运行,这其实是有非常大的挑战。在过去三代架构的演进中我们可以看到,本质上每一代架构的迭代基本上都是以两到三年为周期,这其中会有非常高的人力投入和成本开销。第二个挑战就是从传统的商业数据库迁移到OceanBase数据库之上,我们如何保证迁移过程中以及迁移以后的稳定性。另外一个非常大的挑战就是数据质量,在金融企业里,数据承载的不仅只是钱,更承载了数以亿计用户的信任。所以数据一条不能丢,一条不能错,这是我们做数据库的底线。当然,包括兼容性问题和性能风险也给数据库的架构升级带来重重挑战。OceanBase迁移服务:向分布式架构升级的直接路径基于上述问题和挑战,同时经过蚂蚁十年数据库架构升级的先进经验,蚂蚁金服为客户打造了这款一站式数据迁移解决方案——OceanBase迁移服务(OceanBaseMigration Service,简称OMS)。OMS的发展演进OMS的演进是以业务为驱动,并且与OceanBase的架构升级和不断发展密不可分。早在2014-2015年期间,蚂蚁主站上的一些核心业务,包括大家熟知的交易业务,支付业务和会员业务等,需要从Oracle迁移到OceanBase上。当时的OMS还是以一个工具类、模块化的形态支撑着这些项目。所以在2015年我们开始对OMS的方案进行全面的调研,力求沉淀出通用的系统化的解决方案。在2016年,OMS已经有了平台化的架构,引入了大规模编排的思想,将整个迁移特别是切换过程中繁琐易错的环节全部集成到平台。这一时期,OceanBase也完成了从0.5版本到1.0版本的架构升级,这一年OMS还支撑了网商银行、印度PayTM以及主站的核心业务升级到OceanBase 1.0版本。到了2018年的时候,无论在基础功能层面还是任务编排层面,OMS都已经被打磨得日趋完善。今年OMS已经支持了蚂蚁森林,蚂蚁商户平台以及众多大量核心及非核心的业务从MySQL迁移到OceanBase之上。与此同时,在外部业务包括很多已经上线OceanBase的商业银行,也已经验证了使用OMS一键迁移到OceanBase的能力。OMS的方案优势OceanBase迁移服务其实主要解决了五个重要的问题。1.负载回放验证:其中第一个核心的问题就是负载回放验证,通过采集源端数据库的SQL流量,在目标库OceanBase上回放,可以验证其在OceanBase上的功能是否兼容、性能是否出现问题等。同时基于蚂蚁DBA十多年的经验沉淀,OMS会为客户提供性能等方面的调优建议。2.秒级数据校验:第二点就是数据校验,OMS有三层数据校验,可以做到秒级的延迟。举一个例子,比如说我们想把传统商业数据库替换成OceanBase,如果在迁移过程中任何一条数据出现了错误,在一秒钟内就可以快速发现。校验的延迟可以完全保证在一秒以内,根据蚂蚁线上的经验,大概在100-200毫秒之间。3.分钟级即时回滚:第三点也是最重要的一点,就是OMS有随时回滚的能力,而且回滚是无损的。这也是我们前面所强调的稳妥创新的基石。4.多种数据库类型支持:目前OMS支持源端数据库类型有Oracle、MySQL、OceanBase等等,支持全量迁移和增量数据同步。5.一键完成迁移:整个数据迁移链路和回滚机制的搭建基本上都是通过一键操作完成,使用简便。OMS的技术架构OMS的核心方案其实非常简单,我们把OceanBase变成Oracle/MySQL的一个备库。传统的商业数据库一般都是有主库和备库的:主库承担写的流量,如果主库出现问题,我们会把数据切到备库,然后通过OMS提供的一整套虚拟主备库的解决方案完成切换。比如原来Oracle有一个主库一个备库,然后OceanBase其实变成了一个虚拟的备库。整个数据库架构的升级也会变得异常简单,简单到只是做了一个主备切换。回滚也会变得非常简单,其实也是做了一次主备切换。从OMS的整体架构来看,其实一个非常关键的点就是,我们在传统的商业数据库和OceanBase之间建立了一套虚拟的主备链路,整个OMS里用到的所有组件,其实都是在蚂蚁和阿里有很多年技术沉淀的,也都是基于真实场景所产生的。OMS的迁移流程OceanBase迁移服务的整体迁移流程其实只有七步。1.评估:首先第一步是通过负载回放工具做兼容性分析;2.PoC:接下来OceanBase云平台可以帮助客户部署一套PoC集群;3.预迁移:然后OMS把线上的Oracle的数据预迁移到一个测试库里;4.验证:在这个测试库里用负载回放工具去回放这些SQL,然后找到SQL里不兼容,性能或者数据质量不满足预期的部分,并提供优化建议;5.正式迁移:前四步做完了以后,业务需要调整或者需要优化的SQL已经完成优化,然后就可以正式迁移了。首先把原有的全量数据迁过来,然后再把增量变化的那部分数据实时同步过来;6.校验:等到所有的数据准备好以后,然后我们继续完成三级校验;7.切换和回滚:等到所有的校验都完成以后,可以一键完成切换和回滚功能。通过这七步就可以轻松完成从传统商业数据库到分布式数据库的完整迁移。蚂蚁商户平台基于OMS的业务实践蚂蚁商户平台承载着商户档案数据信息,订购关系、签约信息的数据和相应的服务能力。其中一部分业务使用的是MySQL数据库,还有一部分核心业务使用的是Oracle数据库。随着商户的快速增长以及业务场景的不断丰富,商户平台数据增长迅速,数据规模相当庞大。尤其是MySQL的单表瓶颈日益明显,DDL变更、DML更新的性能与风险已经无法承担。蚂蚁金服技术专家韩谷悦介绍道,“OceanBase能够支持数据的无限扩展,满足商户业务的容量与性能需求。那么如果我们换一种数据库底盘,其实所要面对的性能、稳定性和数据质量的风险同样不可避免。”从蚂蚁商户平台的业务实践来看,使用OMS迁移与传统迁移进行对比,我们可以看到:· 业务评估和改造过去通常一个业务少则花费1-2个月的时间去做改造和适配;那么基于OMS自动化的SQL兼容性评估和负载回放的能力,蚂蚁商务平台业务的改造大概只用了一个星期的时间。· 数据迁移和校验客观来讲,迁移的总时长主要取决于业务数据模型,数据量和网络环境。在提高迁移效率方面,OMS目前增量迁移的延迟仅为毫秒级,跨城情况下最长只需要3秒。并且针对校验出的数据差异提供补齐的SQL和订正方案,使得迁移和校验的整体效率有了大幅度的提升。· 业务切换其实在切换之前,往往需要制定严密的切流方案和Failover方案,整个切换过程中需要检查与校验的细节非常繁琐,任何一步疏忽都有可能造成数据不一致的问题。那么OMS通过引入大规模编排的思想,把所有繁琐复杂的环节通通落到平台当中。所以从原来业务切换需要用时1-2周时间, 使用OMS后蚂蚁商户平台业务无论是切读还是切写的过程中都只用了几分钟的时间。· 业务回滚在过去,迁移之后的业务回滚要担负重大的决策风险,OMS使得业务回滚就像一次主备切换,可以瞬间完成并且不丢数据,所以让业务回滚不再成为难题。商户业务整体迁移的过程中也发生过业务抖动,使用OMS回滚的时候从登陆系统到完成回滚也只用了几分钟的时间。所以全程下来蚂蚁商户平台这个业务的迁移时间大概在三个多星期的时间完成,那么无论从人力成本还是时间成本上,OMS都极大地提升了数据库的整体迁移效率。最后,韩谷悦为大家展示了OMS一键迁移的demo演示。当前, 越来越多的企业已经认识到分布式架构在实现业务灵活扩展以及敏捷开发等方面的巨大价值。OceanBase不断通过产品端的革新,为传统企业输送“互联网基因”,帮助更多客户向分布式架构转型。同时OceanBase也在不断提高服务客户的深度和广度。深度意味着在同样的业务场景下,随着业务的发展和体量的壮大,帮助更多企业承担起业务所带来的极致压力。广度则针对的是随着新型技术形态和业务场景的出现,帮助更多企业快速响应,通过技术创新而适应变化所带来的新的市场契机。OceanBase致力于将蚂蚁自身业务多年沉淀下来的最浓缩,最经典和最普世的方法论输出给广大的企业客户,同时做到深度和广度并存,真正帮助客户实现稳妥创新。

January 9, 2019 · 1 min · jiezi

Mars——基于矩阵的统一分布式计算框架

很高兴在这里宣布我们的新项目:Mars,一个基于矩阵的统一分布式计算框架。我们已经在 Github 开源:https://github.com/mars-project/mars 。背景PythonPython 是一门相当古老的语言了,如今,在数据科学计算、机器学习、以及深度学习领域,Python 越来越受欢迎。大数据领域,由于 hadoop 和 spark 等,Java 等还是占据着比较核心的位置,但是在 spark 上也可以看到,pyspark 的用户占据很大一部分。深度学习领域,绝大部分的库(tensorflow、pytorch、mxnet、chainer)都支持 Python 语言,且 Python 语言也是这些库上使用最广泛的语言。对 MaxCompute 来说,Python 用户也是一股重要力量。PyData(numpy、scipy、pandas、scikit-learn、matplotlib)Python 在数据科学领域,有非常丰富的包可以选择,下图展示了整个 Python 数据科学技术栈。可以看到 numpy 作为基础,在其上,有 scipy 面向科学家,pandas 面向数据分析,scikit-learn 则是最著名的机器学习库,matplotlib 专注于可视化。对 numpy 来说,其中最核心的概念就是 ndarray——多维数组,pandas、scikit-learn 等库都构建于这个数据结构基础之上。问题虽然 Python 在这些领域越来越流行,PyData 技术栈给数据科学家们提供了多维矩阵、DataFrame 上的分析和计算能力、基于二维矩阵的机器学习算法,但这些库都仅仅受限于单机运算,在大数据时代,数据量一大,这些库的处理能力都显得捉襟见肘。虽然大数据时代,有各种各样基于 SQL 的计算引擎,但对科学计算领域,这些引擎都不太适合用来进行大规模的多维矩阵的运算操作。而且,相当一部分用户,尤其是数据科学家们,习惯于使用各种成熟的单机库,他们不希望改变自己的使用习惯,去学习一些新的库和语法。此外,在深度学习领域,ndarray/tensor 也是最基本的数据结构,但它们仅仅限制在深度学习上,也不适合大规模的多维矩阵运算。基于这些考量,我们开发了 Mars,一个基于 tensor 的统一分布式计算框架,前期我们关注怎么将 tensor 这层做到极致。我们的工作Mars 的核心用 python 实现,这样做的好处是能利用到现有的 Python 社区的工作,我们能充分利用 numpy、cupy、pandas 等来作为我们小的计算单元,我们能快速稳定构建我们整个系统;其次,Python 本身能轻松和 c/c++ 做继承,我们也不必担心 Python 语言本身的性能问题,我们可以对性能热点模块轻松用 c/cython 重写。接下来,主要集中介绍 Mars tensor,即多维矩阵计算的部分。Numpy APINumpy 成功的一个原因,就是其简单易用的 API。Mars tensor 在这块可以直接利用其作为我们的接口。所以在 numpy API 的基础上,用户可以写出灵活的代码,进行数据处理,甚至是实现各种算法。下面是两段代码,分别是用 numpy 和 Mars tensor 来实现一个功能。import numpy as npa = np.random.rand(1000, 2000)(a + 1).sum(axis=1)import mars.tensor as mta = mt.random.rand(1000, 2000)(a + 1).sum(axis=1).execute()这里,创建了一个 1000x2000 的随机数矩阵,对其中每个元素加1,并在 axis=1(行)上求和。目前,Mars 实现了大约 70% 的 Numpy 常用接口。可以看到,除了 import 做了替换,用户只需要通过调用 execute 来显式触发计算。通过 execute 显式触发计算的好处是,我们能对中间过程做更多的优化,来更高效地执行计算。不过,静态图的坏处是牺牲了灵活性,增加了 debug 的难度。下个版本,我们会提供 instant/eager mode,来对每一步操作触发计算,这样,用户能更有效地进行 debug,且能利用到 Python 语言来做循环,当然性能也会有所损失。使用 GPU 计算Mars tensor 也支持使用 GPU 计算。对于某些矩阵创建的接口,我们提供了 gpu=True 的选项,来指定分配到 GPU,后续这个矩阵上的计算将会在 GPU 上进行。import mars.tensor as mta = mt.random.rand(1000, 2000, gpu=True)(a + 1).sum(axis=1).execute()这里 a 是分配在 GPU 上,因此后续的计算在 GPU 上进行。稀疏矩阵Mars tensor 支持创建稀疏矩阵,不过目前 Mars tensor 还只支持二维稀疏矩阵。比如,我们可以创建一个稀疏的单位矩阵,通过指定 sparse=True 即可。import mars.tensor as mta = mt.eye(1000, sparse=True, gpu=True)b = (a + 1).sum(axis=1)这里看到,gpu 和 sparse 选项可以同时指定。基于 Mars tensor 的上层建筑这部分在 Mars 里尚未实现,这里提下我们希望在 Mars 上构建的各个组件。DataFrame相信有部分同学也知道 PyODPS DataFrame,这个库是我们之前的一个项目,它能让用户写出类似 pandas 类似的语法,让运算在 ODPS 上进行。但 PyODPS DataFrame 由于 ODPS 本身的限制,并不能完全实现 pandas 的全部功能(如 index 等),而且语法也有不同。基于 Mars tensor,我们提供 100% 兼容 pandas 语法的 DataFrame。使用 mars DataFrame,不会受限于单个机器的内存。这个是我们下个版本的最主要工作之一。机器学习scikit-learn 的一些算法的输入就是二维的 numpy ndarray。我们也会在 Mars 上提供分布式的机器学习算法。我们大致有以下三条路:scikit-learn 有些算法支持 partial_fit,因此,我们直接在每个 worker 上调用 sklearn 的算法。提供基于 Mars 的 joblib 后端。由于 sklearn 使用 joblib 来做并行,因此,我们可以通过实现 joblib 的 backend,来让 scikit-learn 直接跑在 Mars 的分布式环境。但是,这个方法的输入仍然是 numpy ndarray,因此,总的输入数据还是受限于内存。在 Mars tensor 的基础上实现机器学习算法,这个方法需要的工作量是最高的,但是,好处是,这些算法就能利用 Mars tensor 的能力,比如 GPU 计算。以后,我们需要更多的同学来帮我们贡献代码,共建 Mars 生态。细粒度的函数和类Mars 的核心,其实是一个基于 Actor 的细粒度的调度引擎。因此,实际上,用户可以写一些并行的 Python 函数和类,来进行细粒度的控制。我们可能会提供以下几种接口。函数用户能写普通的 Python 函数,通过 mars.remote.spawn 来将函数调度到 Mars 上来分布式运行import mars.remote as mrdef add(x, y): return x + ydata = [ (1, 2), (3, 4)]for item in data: mr.spawn(add, item[0], item[1])利用 mr.spawn,用户能轻松构建分布式程序。在函数里,用户也可以使用 mr.spawn,这样,用户可以写出非常精细的分布式执行程序。类有时候,用户需要一些有状态的类,来进行更新状态等操作,这些类在 Mars 上被称为 RemoteClass。import mars.remote as mrclass Counter(mr.RemoteClass): def init(self): self.value = 0 def inc(self, n=1): self.value += ncounter = mr.spawn(Counter)counter.inc()目前,这些函数和类的部分尚未实现,只是在构想中,所以届时接口可能会做调整。内部实现这里,我简单介绍下 Mars tensor 的内部原理。客户端在客户端,我们不会做任何真正的运算操作,用户写下代码,我们只会在内存里用图记录用户的操作。对于 Mars tensor 来说,我们有两个重要的概念,operand 和 tensor,分别如下图的蓝色圆和粉色方块所示。Operand 表示算子,tensor 表示生成的多维数组。比如,下图,用户写下这些代码,我们会依次在图上生成对应的 operand 和 tensor。当用户显式调用 execute 的时候,我们会将这个图提交到 Mars 的分布式执行环境。我们客户端部分,并不会对语言有任何依赖,只需要有相同的 tensor graph 序列化,因此可以用任何语言实现。下个版本我们要不要提供 Java 版本的 Mars tensor,我们还要看是不是有用户需要。分布式执行环境Mars 本质上是一个对细粒度图的执行调度系统。对于 Mars tensor 来说,我们接收到了客户端的 tensor 级别的图(粗粒度),我们要尝试将其转化成 chunk 级别的图(细粒度)。每个 chunk 以及其输入,在执行时,都应当能被内存放下。我们称这个过程叫做 tile。在拿到细粒度的 chunk 级别的图后,我们会将这个图上的 Operand 分配到各个 worker 上去执行。总结Mars 在九月份的云栖大会发布,目前我们已经在 Github 开源:https://github.com/mars-project/mars。我们项目完全以开源的方式运作,而不是简单把代码放出来。期待有更多的同学能参与 Mars,共建 Mars。努力了很久,我们不会甘于做一个平庸的项目,我们期待对世界做出一点微小的贡献——我们的征途是星辰大海!本文作者:继盛阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

January 9, 2019 · 2 min · jiezi

性能诊断利器 JProfiler 快速入门和最佳实践

背景性能诊断是软件工程师在日常工作中需要经常面对和解决的问题,在用户体验至上的今天,解决好应用的性能问题能带来非常大的收益。Java 作为最流行的编程语言之一,其应用性能诊断一直受到业界广泛关注。可能造成 Java 应用出现性能问题的因素非常多,例如线程控制、磁盘读写、数据库访问、网络I/O、垃圾收集等。想要了定位这些问题,一款优秀的性能诊断工具必不可少。本文将介绍 Java 性能诊断过程中的常用工具,并重点介绍其中的优秀代表 JProfiler 的基本原理和最佳实践(本文所做的调研基于jprofiler10.1.4)。Java 性能诊断工具简介在 Java 的世界里,有许多诊断工具可供选择,既包括像 jmap、jstat 这样的简单命令行工具,又包括 JVisualvm、JProfiler 等图形化综合诊断工具,同时还有 SkyWalking、ARMS 这样的针对分布式应用的性能监控系统。下面分别对其进行介绍。简单命令行工具JDK 内置了许多命令行工具,它们可用来获取目标 JVM 不同方面、不同层次的信息。jinfo - 用于实时查看和调整目标 JVM 的各项参数。jstack - 用于获取目标 Java 进程内的线程堆栈信息,可用来检测死锁、定位死循环等。jmap - 用于获取目标 Java 进程的内存相关信息,包括 Java 堆各区域的使用情况、堆中对象的统计信息、类加载信息等。jstat - 一款轻量级多功能监控工具,可用于获取目标 Java 进程的类加载、JIT 编译、垃圾收集、内存使用等信息。jcmd - 相比 jstat 功能更为全面的工具,可用于获取目标 Java 进程的性能统计、JFR、内存使用、垃圾收集、线程堆栈、JVM 运行时间等信息。图形化综合诊断工具使用上述命令行工具或组合能帮您获取目标 Java 应用性能相关的基础信息,但它们存在下列局限:无法获取方法级别的分析数据,如方法间的调用关系、各方法的调用次数和调用时间等(这对定位应用性能瓶颈至关重要)。要求用户登录到目标 Java 应用所在的宿主机上,使用起来不是很方便。分析数据通过终端输出,结果展示不够直观。下面介绍几款图形化的综合性能诊断工具。JVisualvmJVisualvm 是 JDK 内置的可视化性能诊断工具,它通过 JMX、jstatd、Attach API 等方式获取目标 JVM 的分析数据,包括 CPU 使用率、内存使用量、线程堆栈信息等。此外,它还能直观地展示 Java 堆中各对象的数量和大小、各 Java 方法的调用次数和执行时间等。JProfilerJProfiler 是由 ej-technologies 公司开发的一款 Java 应用性能诊断工具。它聚焦于四个重要主题上。方法调用 - 对方法调用的分析可以帮助您了解应用程序正在做什么,并找到提高其性能的方法。内存分配 - 通过分析堆上对象、引用链和垃圾收集能帮您修复内存泄漏问题,优化内存使用。线程和锁 - JProfiler 提供多种针对线程和锁的分析视图助您发现多线程问题。高级子系统 - 许多性能问题都发生在更高的语义级别上。例如,对于JDBC调用,您可能希望找出执行最慢的 SQL 语句。JProfiler 支持对这些子系统进行集成分析。分布式应用性能诊断如果只需要诊断单机 Java 应用的性能瓶颈,上面介绍的诊断工具就已经够用了。但随着现代系统架构逐渐从单体转变为分布式、微服务,单纯使用上述工具往往无法满足需求,这时就需要借助 Jaeger、ARMS、SkyWalking 这些分布式追踪系统提供的全链路追踪功能。分布式追踪系统种类繁多,但实现原理都大同小异,它们通过代码埋点的方式记录 tracing 信息,通过 SDK 或 agent 将记录的数据传输至中央处理系统,最后提供 query 接口对结果进行展示和分析,想了解更多分布式追踪系统的原理可参考文章开放分布式追踪(OpenTracing)入门与 Jaeger 实现。JProfiler 简介核心组件JProfiler 包含用于采集目标 JVM 分析数据的 JProfiler agent、用于可视化分析数据的 JProfiler UI、提供各种功能的命令行工具,它们之间的关系如下图所示。JProfiler agentJProfiler agent 是一个本地库,它可以在 JVM 启动时通过参数-agentpath:<path to native library>进行加载或者在程序运行时通过 JVM Attach 机制进行加载。Agent 被成功加载后,会设置 JVMTI 环境,监听虚拟机产生的事件,如类加载、线程创建等。例如,当它监听到类加载事件后,会给这些类注入用于执行度量操作的字节码。JProfiler UIJProfiler UI 是一个可独立部署的组件,它通过 socket 和 agent 建立连接。这意味着不论目标 JVM 运行在本地还是远端,JProfiler UI 和 agent 间的通信机制都是一样的。JProfiler UI 的主要功能是展示通过 agent 采集上来的分析数据,此外还可以通过它控制 agent 的采集行为,将快照保存至磁盘,展示保存的快照。命令行工具JProfiler 提供了一系列命令行工具以实现不同的功能。jpcontroller - 用于控制 agent 的采集行为。它通过 agent 注册的 JProfiler MBean 向 agent 传递命令。jpenable - 用于将 agent 加载到一个正在运行的 JVM 上。jpdump - 用于获取正在运行的 JVM 的堆快照。jpexport & jpcompare - 用于从保存的快照中提取数据并创建 HTML 报告。安装配置JProfiler 同时支持诊断本地和远程 Java 应用的性能。如果您需要实时采集并展示远程 JVM 的分析数据,需要完成以步骤:在本地安装 JProfiler UI。在远程宿主机上安装 JProfiler agent 并让其被目标 JVM 加载。配置 UI 到 agent 的连接。具体步骤可参考文档 Installing JProfiler 和 Profiling A JVM。最佳实践本章将以高性能写 LogHub 类库 Aliyun LOG Java Producer 为原型,带您了解如何使用 JProfiler 剖析它的性能。如果您的应用或者您在使用 producer 的过程中遇到了性能问题,也可以用类似的方式定位问题根因。如果您还不了解 producer 的功能,建议先阅读文章日志上云利器 - Aliyun LOG Java Producer。本章使用的样例代码参见 SamplePerformance.java。JProfiler 设置数据采集模式JProfier 提供两种数据采集模式 Sampling 和 Instrumentation。Sampling - 适合于不要求数据完全精确的场景。优点是对系统性能的影响较小,缺点是某些特性不支持(如方法级别的统计信息)。Instrumentation - 完整功能模式,统计信息也是精确的。缺点是如果需要分析的类比较多,对应用性能影响较大。为了降低影响,往往需要和 Filter 一起使用。由于我们需要获取方法级别的统计信息,这里选择了 Instrumentation 模式。同时配置了 Filter,让 agent 只记录位于 Java 包com.aliyun.openservices.aliyun.log.producer下的类和类com.aliyun.openservices.log.Client的 CPU 分析数据。应用启动模式通过为 JProfiler agent 指定不同的参数可以控制应用的启动模式。等待模式 - 只有在 Jprofiler GUI 和 agent 建立连接并完成分析配置设置后,应用才会真正启动。在这种模式下,您能够获取应用启动时期的分析数据。对应的命令为-agentpath:<path to native library>=port=8849。立即启动模式 - 应用会立即启动,Jprofiler GUI 会在需要时和 agent 建立连接并设置分析配置。这种模式相对灵活,但会丢失应用启动初期的分析数据。对应的命令为-agentpath:<path to native library>=port=8849,nowait。离线模式 - 通过触发器记录数据、保存快照供事后分析。对应的命令为-agentpath:<path to native library>=offline,id=xxx,config=/config.xml。因为是在测试环境,同时对应用启动初期的性能也比较关注,这里选择了默认的等待模式。使用 JProfiler 诊断性能在完成 JProfiler 的设置后,便可以对 Producer 的性能进行诊断。Overview在概览页我们可以清晰的看到内存使用量、垃圾收集活动、类加载数量、线程个数和状态、CPU 使用率等指标随时间变化的趋势。通过此图,我们可以作出如下基本判断:程序在运行过程中会产生大量对象,但这些对象生命周期极短,大部分都能被垃圾收集器及时回收,不会造成内存无限增长。加载类的数量在程序初始时增长较快,随后保持平稳,符合预期。在程序运行过程中,有大量线程处于阻塞状态,需要重点关注。在程序刚启动时,CPU 使用率较高,需要进一步探究其原因。CPU viewsCPU views 下的各个子视图展示了应用中各方法的执行次数、执行时间、调用关系等信息,能帮我们定位对应用性能影响最大的方法。Call TreeCall tree 通过树形图清晰地展现了方法间的层次调用关系。同时,JProfiler 将子方法按照它们的执行总时间由大到小排序,这能让您快速定位关键方法。对于 Producer 而言,方法SendProducerBatchTask.run()耗时最多,继续向下查看会发现该方法的主要时间消耗在了执行方法Client.PutLogs()上。Hot Spots如果您的应用方法很多,且很多子方法的执行时间比较接近,使用 hot spots 视图往往能助您更快地定位问题。该视图能根据方法的单独执行时间、总执行时间、平均执行时间、调用次数等属性对它们排序。其中,单独执行时间等于该方法的总执行时间减去所有子方法的总执行时间。在该视图下,可以看到Client.PutLogs(),LogGroup.toByteArray(),SamplePerformance$1.run()是单独执行时间耗时最多的三个方法。Call Graph找到了关键方法后,call graph 视图能为您呈现与该方法直接关联的所有方法。这有助于我们对症下药,制定合适的性能优化策略。这里,我们观察到方法Client.PutLogs()执行的主要时间花费在了对象序列化上,因此性能优化的关键是提供执行效率更高的序列化方法。Live memoryLive memory 下的各个子视图能让您掌握内存的具体分配和使用情况,助您判断是否存在内存泄漏问题。All ObjectsAll Objects 视图展示了当前堆中各种对象的数量和总大小。由图可知,程序在运行过程中构造出了大量 LogContent 对象。Allocation Call TreeAllocation Call Tree 以树形图的形式展示了各方法分配的内存大小。可以看到,SamplePerformance$1.run()和SendProducerBatchTask.run()是内存分配大户。Allocation Hot Spots如果方法比较多,您还可以通过 Allocation Hot Spots 视图快速找出分配对象最多的方法。Thread History线程历史记录视图直观地展示了各线程在不同时间点的状态。不同线程执行的任务不同,所展现的状态特征也不同。线程pool-1-thread-<M>会循环调用producer.send()方法异步发送数据,它们在程序刚启动时一直处于运行状态,但随后在大部分时间里处于阻塞状态。这是因为 producer 发送数据的速率低于数据的产生速率,且单个 producer 实例能缓存的数据大小有限。在程序运行初始,producer 有足够空间缓存待发送数据,所以pool-1-thread-<M>一直处于运行状态,这也就解释了为何程序在刚启动时 CPU 使用率较高。随着时间的推移,producer 的缓存被逐渐耗尽,pool-1-thread-<M>必须等到 producer “释放”出足够的空间才有机会继续运行,这也是为什么我们会观察到大量线程处于阻塞状态。aliyun-log-producer-0-mover负责将超时 batch 投递到发送线程池中。由于发送速率较快,batch 会因缓存的数据达到了上限被pool-1-thread-<M>直接投递到发送线程池中,因此 mover 线程在大部分时间里都处于等待状态。aliyun-log-producer-0-io-thread-<N>作为真正执行数据发送任务的线程有一部分时间花在了网络 I/O 状态。aliyun-log-producer-0-success-batch-handler用于处理发送成功的 batch。由于回调函数比较简单,执行时间短,它在大部分时间里都处于等待状态。aliyun-log-producer-0-failure-batch-handler用于处理发送失败的 batch。由于没有数据发送失败,它一直处于等待状态。通过上述分析可知,这些线程的状态特征都是符合预期的。Overhead Hot Spots Detected当程序运行结束后,JProfiler 会弹出一个对话框展示那些频繁被调用,但执行时间又很短的方法。在下次诊断时,您可以让 JProfiler agent 在分析过程中忽略掉这些方法以减轻对应用性能的影响。小结通过 JProfiler 的诊断可知应用不存在大的性能问题,也不存在内存泄漏。下一步的优化方向是提升对象的序列化效率。参考资料深入浅出JProfilerJprofiler Introduction本文作者:吴波bruce_wu阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

January 7, 2019 · 2 min · jiezi

分布式任务框架之celery

架构Broker消息代理,作为临时储存任务的中间媒介,为 Celery 提供了队列服务。生产者将任务发送到 Broker,消费者再从 Broker 获取任务。Celery目前支持RabbitMQ、Redis、MongoDB、Beanstalk、SQLAlchemy、Zookeeper等 作为消息代理,但适用于生产环境的只有RabbitMQ和Redis,至于其他的方式,一是支持有限, 二是可能得不到更好的技术支持。 Celery官方推荐的是RabbitMQ,Celery的作者Ask Solem Hoel最初在VMware就是为RabbitMQ工作的,Celer最初的设计就是基于RabbitMQ,所以使用 RabbitMQ会非常稳定,成功案例很多。如果使用Redis,则有可能发生突然断电之类的问题 造成Redis突然终止后的数据丢失等后果。Beat任务调度器,负责调度并触发 Celery 定时周期任务。Beat 进程读取 CeleryConfig 中自定义的定时周期任务列表,将到期需要执行的定时任务发送到任务队列中。Worker任务执行单元,实际负责执行任务的服务进程,每一个 Worker 都有一个并发池(Prefork/Eventlet/Gevent/Thread)来支持多并发。Worker 会监听订阅的任务队列,当队列中有任务时,就会获取任务并执行。Result Backend/Store任务执行状态和结果存储,Celery 支持任务实时处理,也就是说 Celery 可以把任务执行的实时状态和最终结果回传生产者。这种回传也需要通过中间存储媒介。web监控管理添加管理任务任务的监控

January 3, 2019 · 1 min · jiezi

【从零开始学架构】学习笔记(一)

1.1 什么是架构1.1.1 架构简述【优秀架构具备的特点】:优秀的 TPS 承载力优秀的性能故障影响降到最小投入产出最优方案1.1.2 架构师职责明确需求系统能力分解技术选型制定架构说明书及主导执行落地1.2 架构设计分层1.2.1 为什么要分层分而治之各司其职有条不紊的结合1.2.2 常见的分层设计计算机网络 OSI 七层模型Web 系统 MVC 模型分层基于领域模型的分层1.2.3 分层模型演进一、Servlet JSP 时代(V0.1)Servlet + Tomcat 容器完成 Web 接入使用 JavaBean + JDBC 完成数据层接入使用 JSP 完成页面展示二、MVC(V1.0)【V 1.0 时代 典型代表 SSH】Structs 解决接入及表示层。(ActionServlet 重)Spring 解决业务服务、事务处理、会话管理。Hibernate 解决数据存储接入问题。(特殊的SQL处理繁琐;SET 联动数据库问题)三、SSM 时代(V1.5)SpringMVC 解决接入及表示层Spring 解决业务服务、事务处理、会话管理等问题MyBatis 解决数据接入层四、SpringBoot all in one(V2.0)整合了所有 Spring 的框架功能提供了简单的配置及注解的接入方式提供 All in one 的服务【V2.0 存在的问题】:解决了单一应用内的软件分层,却没有解决整体应用的分层单一应用的性能瓶颈,无法支撑亿级流量团队协作问题五、分布式分层(V3.0)1、WEB概念层2、业务概念层3、数据访问记存储层

December 29, 2018 · 1 min · jiezi

Spring Cloud Alibaba发布第二个版本,Spring 发来贺电

还是熟悉的面孔,还是熟悉的味道,不同的是,这次的配方升级了。今年10月底,Spring Cloud联合创始人Spencer Gibb在Spring官网的博客页面宣布:阿里巴巴开源 Spring Cloud Alibaba,并发布了首个预览版本。随后,Spring Cloud 官方Twitter也发布了此消息。- 传送门时隔 51天,Spencer Gibb再次在Spring官网的博客页面宣布:Spring Cloud Alibaba发布了其开源后的第二个版本0.2.1,随后,Spring Cloud 官方Twitter也转发了此消息。圣诞节的前一周,Josh Long向他的老朋友许晓斌发来贺电:小编翻译:听闻阿里巴巴官方宣布使用Spring Cloud,我开心的一晚上没睡着,下周三我会在Spring Tips小视频里介绍Spring Cloud Alibaba。圣诞快乐,老哥!爱你视频地址新版本概要增加了两个新的模块, spring-cloud-alibaba-schedulerx 和 spring-cloud-stream-binder-rocketmq 。在 spring-cloud-alibaba-nacos 和 spring-cloud-alibaba-sentinel 中增加了一些新的 feature 。修复了之前版本的一些 bug 。注意: 版本 0.2.1.RELEASE 对应的是 Spring Cloud Finchley 的版本。这次发布也包含了一个适配 Spring Cloud Edgware 的版本 0.1.1.RELEASE,版本 0.1.1.RELEASE 也包含了 0.2.1.RELEASE 中新增的组件和特性。Spring Cloud Alibaba RocketMQ适配了 spring cloud stream 对于 message 抽象的 API。支持事务消息。Consumer 端支持以 tags、SQL表达式过滤消息。支持顺序、并发以及广播消费模式。Spring Cloud Alibaba SchedulerX提供了秒级、精准、高可靠、高可用的定时任务调度服务。提供了丰富的任务执行模型,包括单机执行,广播执行,以及子任务的分布式执行。Spring Cloud Alibaba Nacos Config将Nacos Client 的版本升级到 0.6.2 版本。支持从多个 dataid 和 groupid 中获取和监听配置,并支持优先级指定。优化了 动态监听的逻辑,只有配置了动态刷新的配置项才会实时刷新。Spring Cloud Alibaba Nacos Discovery将Nacos Client 的版本升级到 0.6.2 版本。支持在 Nacos Console 端将服务实例设置成不可用状态,服务发现会自动过滤此节点。支持服务发现在初始化时不使用本地缓存。Spring Cloud Alibaba Sentinel支持 Feign,兼容了 @FeignClient 所有的属性,包括 fallback、fallbackFactory。支持 热点参数限流和集群限流。重构了 ReadableDataSource 的设计,提供了更友好的配置 Sentinel 规则持久化的方式。优化了 Sentinel 对于 RestTemplate 的降级后的处理。调整并添加了一些 Sentinel 配置信息对应的属性,如日志目录,日志文件名等。新版本背后的思考Spring Cloud Alibaba Nacos DiscoveryNacos Discovery 在这个版本最大的更新就是支持在初始化的时候不使用本地文件缓存,目前初始化的时候已经默认不使用本地文件缓存。为什么要有缓存?首先我们来了解一下本地缓存的概念,为什么需要这个本地缓存?我们都知道,服务注册与发现应该只是服务调用中的辅助性的一个环节,而不是一个关键的环节。一个良好的服务注册与发现的设计,需要保证以下两点。服务注册与发现只是旁路,不应该参与具体的调用过程。在服务的运行过程中,即使服务注册中心偶然发生异常宕机后,也尽量不影响正常的业务调用。要实现以上两点,缓存就不可或缺,而为了适应不同的场景,缓存又可以分成内存缓存和本地文件缓存,他们的概念和适用场景如下。内存中的缓存将服务提供者列表维护在内存中,每次调用时直接从内存中的列表获取节点即可。内存缓存通过定时任务更新,或者在收到服务注册中心的推送之后再更新。确保了即使在服务注册中心宕机的情况下,也能保证服务仍能正常调用。本地文件缓存将上述提到的内存中的缓存,保留在本地的某个文件中,这样在调用服务注册中心失败的时候,可以从本机的文件缓存中获取服务提供者列表。这样就保证了在服务注册中心宕机的情况下,应用在重启后也能找到服务提供者。为什么要关闭有了以上背景知识后,读者可能会有疑问,既然缓存这么好,你们为什么默认要把它置为默认关闭呢?我们在发布出第一个版本之后,很多用户反馈,为什么我服务下线之后还是有节点,仍旧可以被查询到呢?这样导致我这个监控数据完全不准,你们这个有 bug,完全不对。其实这是阿里巴巴在多年业务积累的经验,对于服务发现来说,一个即使是已经过时的节点,也比没有任何数据好。而且还有可能其实这个服务只是和服务注册中心失去了心跳,但是应用本身是正常的。当然,这也暴露了我们设计中存在的一些问题,没有把服务发现本身,以及缓存的分层给做好,两者糅合在一块。所以在这一次的版本更新中我们还是选择适配开源标准,默认关闭了本地文件缓存,留了一个开关给用户自由选择。Spring Cloud Alibaba Nacos ConfigNacos Config 在这个版本中有两个大的特性,支持了“共享配置”,修正了动态刷新的语义和行为。共享配置的实现在第一个版本还没发布到时候,社区里对配置管理中心的讨论就没停止过,其中听到最多的反馈应该就是支持多个应用共享一个配置。我们也通过 github issue 的方式,征集了很多意见,详情见 #12 , #141。后来我们将这个模型抽象了一下,认清这些需求本质是一个应用可以从多个 DataID 和 GroupID 组合中获取配置,并且这些配置还可以单独指定优先级和是否动态刷新。最后我们推荐了这种使用方式,既保证了使用场景的灵活性,又保证了业务语义的明确性。更多详情可以参考 WIKI。# config external configuration# 1、Data Id 在默认的组 DEFAULT_GROUP,不支持配置的动态刷新spring.cloud.nacos.config.ext-config[0].data-id=ext-config-common01.properties# 2、Data Id 不在默认的组,不支持动态刷新spring.cloud.nacos.config.ext-config[1].data-id=ext-config-common02.propertiesspring.cloud.nacos.config.ext-config[1].group=GLOBAL_GROUP# 3、Data Id 既不在默认的组,也支持动态刷新spring.cloud.nacos.config.ext-config[2].data-id=ext-config-common03.propertiesspring.cloud.nacos.config.ext-config[2].group=REFRESH_GROUPspring.cloud.nacos.config.ext-config[2].refresh=true#优先级关系: spring.cloud.nacos.config.ext-config[n].data-id 其中 n 的值越大,优先级越高。注意 data-id 的值必须带文件扩展名,文件扩展名支持 properties、yaml 和 yml。通过这种自定义扩展的配置项,既可以支持一个应用从多个配置项中获取数据,也解决多个应用间配置共享的问题。头脑风暴,@fudali 同学还提出了更加灵活的一种方式 #161,就是可以通过一个配置项来配置所有的 DataID 的信息,然后可以通过修改这个配置项,可以修改整体配置项的逻辑。这是一个非常好的想法,只不过这一期中我们没有实现,原因是这种方式太灵活了。我们认为配置管理其实是一件很严肃的事情,太灵活导致生产中变更比较不可控。虽然目前的逻辑也可以支持这种用法,但是我们并没有把这种模式当做推荐模式,后续如果社区有更多的反馈认为这是一个强烈的需求,欢迎提 PR。动态刷新的修正简单好用、实时可监控的动态刷新也许是 Nacos Config 相对于其他开源中间件相比最核心的优势了,不同于 Spring Cloud Config Server 必须使用 Spring Cloud Bus 才能实现动态刷新,Nacos Config 无需依赖其他任何中间件就可以实现实时动态刷新,而且推送成功与否和刷新历史还支持实时查询。但是我们发现在第一个版本中的实现发现两个问题:动态刷新的实现方式不对:不应该直接调用 Context.refresh() 方法,而是应该 publish 一个 RefreshEvent。是否动态刷新的语义有误:对于那些被标记为不动态刷新的配置项来说,只是修改他们的时候不会触发动态刷新。但是当其他支持动态刷新的配置项触发了动态刷新时,应用的 Context 仍旧会去获取那些被标记为不动态刷新的配置项的内容,也就意味着这些配置项有可能被连带刷新了。在这个版本中,我们修复了这两个问题。首先,动态刷新不再是直接去调用 ContextRefresher.refresh() 方法,而是 publish 了一个 RefreshEvent,让 spring-cloud-commons 里的 RefreshEventListener 去触发这个 ContextRefresher.refresh() 方法。其次,我们修正了动态刷新的语义后,这一次是真正做到了,只有 refresh 属性为 true 的配置项,才会在运行的过程中变更为新的值,refresh 属性为 false 的配置项再也不用担心应用在运行的过程中发生莫名其妙的变更了。更深入一点,在上个月 SpringOne Tour 中,我们和 Spring Cloud 的创始人 Spencer 聊到 Spring Cloud 的 Context.refresh() 成本太高,会刷新整个 Spring Context。他反复强调了两次 Context.refresh() 只对 @RefreshScope 和 @ConfigurationProperties 有效,成本一点也不高。之前我们接收到很多社区的反馈都是 Nacos Config 动态刷新支不支持 xxxx,支不支持 xxxx。之前我们都是回答只支持 @RefreshScope 和 @ConfigurationProperties ,如果他内置没有支持,那就得自己加上相应的注解。今天我们可以很愉快地回复,他监听了 RefreshEvent 也能直接支持。而且如果添加 @RefreshScope 和 @ConfigurationProperties 都不满足你的需求时,可以通过实现自己的 RefreshEventListener 更多高级的玩法。Spring Cloud Alibaba SentinelSentinel 在这个版本中有三个大的特性:全面支持 FeignClient ,完善了 RestTemplate 的支持,添加了热点限流、集群限流。FeignClient 集成 Sentinel其实在这之前,Sentinel 支持 FeignClient 已经设计了很久了,之前我们的想法做一个兼容性较强的方案,支持 Sentinel 和 Hystrix 在 FeignClient 中同时使用,尽量做到对原有应用的侵入性做到最小。这个方案我们也思考调研了很久,但是实现难度确实比较大,需要修改 FeignClient 的代码才能实现两者共存。正好前段时间在 Spring Cloud 届最大的新闻就是 Hystrix 宣布不在维护了,于是我们就换了一个思路,直接使用 Sentinel 替代 Hystrix,不再去追求支持两者共存。我们实现了自己的 Feign.Builder,在构建的 FeignClient 执行调用的过程中,通过 SentinelInvocationHandler 完成 Sentinel 的流量统计和保护的动作。如果想使用 Sentinel 为 FeignClient 限流降级,首先需要引入 sentinel-starter 的依赖,然后打开 Sentinel 限流降级的开关 feign.sentinel.enabled=true ,就完成了 Sentinel 的接入。如果需要使用更加定制化的功能,则需要在 @FeignClient 添加 fallback 和 configuration 这些属性的配置。注意 @FeignClient 注解中的所有属性,Sentinel 都做了兼容。RestTemplate 集成 SentinelSpring Cloud Alibaba Sentinel 支持对 RestTemplate 的服务调用使用 Sentinel 进行保护,补全了 Hystrix 这一块的空白。接入的方式也不复杂,在构造 RestTemplate bean 的时候需要加上 @SentinelRestTemplate 注解,然后在控制台配置相应的规则即可。在触发了限流降级时,默认的处理方式是返回 RestTemplate request block by sentinel 信息。如果想自定义被限流之后的处理方式,还可以添加 blockHandlerClass,blockHandler 分别定制被限流后的处理类以及对于的处理方法。如果想自定义被降级之后的处理方式,还可以添加 fallback,fallbackClass 分别定制被降级后的处理类以及对于的处理方法。RestTemplate 的限流降级 ?Sentinel 也承包了!热点参数限流和集群限流首先解释一下什么是热点参数限流和集群限流。热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。集群流控主要解决的问题是:当我们需要控制整个集群流量总量,但是单机流量可能会不均匀,如果是单机维度去限制的话会无法精确地限制总体流量,因此需要引入集群维度的流量控制。Sentinel v1.4.0 的 新功能 ,也能第一时间愉快地在 Spring Cloud Alibaba 上使用了。新组件Spring Cloud Alibaba RocketMQSpring Cloud Stream 是一个用于构建基于消息的微服务应用框架,它基于 SpringBoot 来创建具有生产级别的单机 Spring 应用,并且使用 Spring Integration 与 Broker 进行连接。它提供了消息中间件的统一抽象,推出了 publish-subscribe、consumer groups、partition 这些统一的概念。RocketMQ 是一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。具有以下特点:能够保证严格的消息顺序、提供丰富的消息拉取模式、高效的订阅者水平扩展能力、实时的消息订阅机制、亿级消息堆积能力。在这次的 Spring Cloud Stream Binder RocketMQ 的实现中,我们适配了 Spring Cloud Stream 对于 message 抽象的 API,支持了 RocketMQ 的事务消息。消息订阅时支持以 tags、SQL 表达式过滤消息,同时还支持顺序、并发、延迟以及广播消费模式。Spring Cloud Alibaba SchedulerXSchedulerX 是阿里中间件团队开发的一款分布式任务调度产品,提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务,同时提供分布式的任务执行模型,如网格任务,网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行。简单易用的轻量分布式任务调度您不需要关心调度逻辑,只需要在在 JobProcessor 接口的实现中添加业务逻辑即可,然后在自主运维控制台配置上一个 Job 即可完成使用。高可用的分布式任务不管是 SchedulerX 服务端还是客户端都是分布式架构设计,任务可以在多台客户端机器里的任何一台机器执行,如果客户端出现宕机的情况,服务端会自动选择正常运行的客户端去执行 Job,每个 Job 在服务端的不同机器均有备份,SchedulerX 服务端任意宕掉部分机器仍能保证 Job 正常调度。友好的用户界面SchedulerX 提供了非常友好的页面方便您创建、删除或修改 Job,提供了立即触发执行一次的功能,方便您测试以及关键时刻手动立即执行一次,还提供了历史执行记录查询的功能,您可以看到任何一个 Job 过去 100 次的历史执行记录。功能强大提供了秒级、精准的定时任务调度服务,且提供了丰富的任务执行模型,包括单机执行,广播执行,以及子任务的分布式执行。What’s Next?Spring Cloud Alibaba Cloud SLS 针对日志类数据的一站式服务,在阿⾥巴巴集团经历大量大数据场景锤炼⽽成。您⽆需开发就能快捷地完成日志数据采集、消费、投递以及查询分析等功能,提升运维、运营效率,建立 DT 时代海量日志处理能力。Spring Cloud Alibaba Dubbo Dubbo 是一款流行的开源 RPC 框架,我们会把 Dubbo 整合到 Spring Cloud Alibaba 中,让大家在开发 Dubbo 时也能享受到 Spring Cloud 带来的便利。致谢Spring Cloud Alibaba 从开源建设以来,受到了很多社区同学的关注。社区的每一个 issue ,每一个 PR,都是对整个项目的帮助,都在为建设更好用的 Spring Cloud 添砖加瓦。我们真心地感谢为这个项目提出过 Issue 和 PR 的同学,特别是这些 contributor:HaojunRen、xiejiashuai、mengxiangrui007我们希望有更多社区的同学加入进来,一起把项目做好。还要特别感谢文档团队的倾芸,她帮忙将我们所有的 Reference Doc 翻译了英文,为Spring Cloud Alibaba 的国际化进程铺平了道路。亦盏,阿里巴巴中间件高级开发工程师,长期关注微服务领域,主要负责 Spring Cloud Alibaba 项目的开发和社区维护。本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

December 29, 2018 · 3 min · jiezi

趣味集算:wordcount

WordCount 差不多是最常用的分布式系统练习程序了,Hadoop 就经常用这个当例子。我们来看用集算器怎么做 wordcount。先从单线程开始。例如,D:\files\novel 目录中,有一些小说文档,现在,需要统计这些小说中哪些单词最为常用:在集算器中,如果不嫌写得长,只要一句代码就可以搞定了: A1=directory@p(“D:/files/novel”).(file().read().words().groups(lower():Word;count():Count)).merge(Word).groups@o(Word;sum(Count):Count).sort@z(Count)怎么样,是不是超级简单?计算后,A1 中得到的结果如下:不过,这句确实有点长,为了便于理解,我们可以把它拆成多步来执行: ABC1=directory@p(“D:/files/novel”)[]=now()2for A1=file(A2).read().words() 3 =B2.groups(lower():Word;count():Count) 4 >B1=B1[B3] 5=B1.merge(Word)=A5.groups@o(Word;sum(Count):Count).sort@z(Count)=interval@ms(C1,now())在 A1 中,列出目录中的各个文件:第 24 行循环统计每个文件中的单词。B2 中读取文件中的文本并拆分成单词:B3 中统计出当前文档中每个单词出现的次数,统计时将单词转换为小写字母,以避免大小写变化的影响。结果将按照单词的字典顺序排列:在每个文档统计完成后,在 B4 中将结果记录到 B1 中,所有文档统计后,B1 中结果如下:在 A5 中,将这些结果按照每个单词归并起来,结果如下:B5 中,将归并后的统计结果按每个单词聚合计算,再将结果按 Count 降序排列,可以得到和前面单条语句时相同的结果:在 C1 和 C5 中,通过记录执行开始前后的时刻,估算出计算所需的毫秒数如下:可见,执行效率还是非常高的。搞完单线程,我们再来试试多线程的搞法。并行计算会不会麻烦很多啊?看看代码吧: ABC1=directory@p(“D:/files/novel”) =now()2fork A1=file(A2).read().words() 3 =B2.groups(lower():Word;count():Count) 4=A2.merge(Word)=A4.groups@o(Word;sum(Count):Count).sort@z(Count)=interval@ms(C1,now())嗯,好象差不多嘛,就是把 A2 的那个 for 换成了 fork,其它代码基本没什么变化。看看 C4 中的计时情况果然快了,并行真地起了作用(俺的笔记本只有双核,有这个性能提高也就差不多了)。这个 fork 语句就会自动把本来单线程串行执行的 for 循环变成多线程并行循环计算,程序员完全不用操心线程管理的事,是不是很简单?搞完多线程,现来搞集群。懒得真搞多个服务器来,就用一台机器启动多个进程模拟一下吧。在集算器安装目录的 esProc\bin 路径下,可以找到 esprocs.exe 文件,可以直接运行它来启动或配置服务器:在首次用 Start 按键启动服务器之前,可以先点击 Config 配置并行服务器的相关信息,如在 Unit 页面中配置本机中所要启动的服务器 ip 及端口:配置完成后,就可以回到服务器主窗口启动服务器。重复执行 esprocs.exe 可以再启动两个服务器,这 3 个服务器将依次使用配置中设置的 ip 和端口。这样单机上的服务器集群就准备完毕了。下面准备统计 4 个路径中所有文档的单词,由于这里使用单机模拟服务器集群的,所以每个服务器都是共用相同路径的,如果是远程服务器,设定时路径可能也会有所差异。 ABC1[192.168.10.229:4001,192.168.10.229:4004,192.168.10.229:4007][D:/files/novel1,D:/files/novel2, D:/files/novel3,D:/files/novel4] 2fork B1;A1=directory@p(A2) 3 fork B2=file(B3).read().words()4 =C3.groups(lower():Word;count():Count)5 return B3.merge(Word) 6=A2.merge(Word)=A6.groups@o(Word;sum(Count):Count).sort@z(Count) 在计算时,用 4 个文件路径作为参数,需要执行 4 个子任务分别计算某个路径中文件的单词数。只需要在 fork 后面加上各个服务器节点的地址,这些子任务就可以由集算器自动分派给服务器节点去计算并将结果汇总,程序员根本不用操心这些小问题。最后在 B6 中计算出结果如下:从各个服务器窗口中,可以看到集群计算任务的分配执行情况:怎么样?很简单吧,Hadoop 还没搭建起来的时间,咱已经把活干完了。 ...

December 28, 2018 · 1 min · jiezi

TableStore实战:DLA+SQL实时分析TableStore

一、实战背景什么是DLA(DataLake Analytics数据湖)?他是无服务器化(Serverless)的云上交互式查询分析服务。作为分布式交互式分析服务,是表格存储计算生态的重要组成之一。为了使用户更好的了解DLA的功能、使用方式,创建了这一实战样例。基于DLA可以不用做任何ETL、数据搬迁等前置过程, 实现跨各种异构数据源进行大数据关联分析,并且支持数据回流到各个异构数据源,从而极大的节省成本、 降低延时和提升用户体验。基于JDBC,表格存储的控制台将SQL查询直接做了集成,数据为公共实例,用户不用开通服务也可免费体验表格存储的实时SQL分析、查询功能,样例如下所示:__官网控制台地址:__项目样例需求场景:黑五交易数据本实战案例中,我们从 https://www.kaggle.com/mehdidag/black-friday 上获取数据, 存储到TableStore,然后基于DLA做分析,带你切身感受下数据的价值!“Black Friday”,即“黑色星期五”,是美国人一年中购物最疯狂的日子,类似于中国的“双十一”购物狂欢节。一般黑色星期五的活动主要在线下,但逐渐也有往线上发展的趋势,比如Amazon就有针对黑色星期五做的线上销售活动, 与天猫双十一很相似。同样的,这样的活动会产生大量有意义的商业数据。我们在DLA中定义了一个叫blackfriday50w的表,映射到TableStore中的一个表,用来描述用户购买商品的。如下为示例数据的表结构、与真实数据截图二、表格存储(TableStore)方案准备工作若您对于DLA实时在线分析TableStore的功能感兴趣,希望开始自己系统的搭建之旅,只需按照如下步骤便可以着手搭建了:1、开通表格存储通过控制台开通表格存储服务,表格存储即开即用(后付费),采用按量付费方式,已为用户提供足够功能测试的免费额度。表格存储官网控制台、免费额度说明。2、创建实例通过控制台创建表格存储实例。3、导入数据该数据共有53.8万行,12个列,我们通过SDK将全量数据存储在TableStore的表。用户可通过控制台插入2条测试数据;开通DLA服务DLA服务开通用户进入产品介绍页,选择开通服务:https://www.aliyun.com/product/datalakeanalytics通过DLA控制台开通TableStore数据源开通数据源后创建服务访问点(择经典网络,若已有vpc,可选择vpc)登录CMS(账密会在开通服务后发送站内消息,消息中查看)创建DLA外表1)创建自己的DLA库(相关信息从上述过程中查找):mysql> create database hangzhou_ots_test with dbproperties ( catalog = ‘ots’, location = ‘https://instanceName.cn-hangzhou.ots-internal.aliyuncs.com’, instance = ‘instanceName’);Query OK, 0 rows affected (0.23 sec)#hangzhou_ots_test —请注意库名,允许字母、数字、下划线#catalog = ‘ots’, —指定为ots,是为了区分其他数据源,比如oss、rds等#location = ‘https://xxx’ —ots的endpoint,从实例上可以看到#instance = ‘hz-tpch-1x-vol'2)查看自己创建的库:mysql> show databases;+——————————+| Database |+——————————+| hangzhou_ots_test |+——————————+1 rows in set (0.22 sec)3)查看自己的DLA表:mysql> use hangzhou_ots_test;Database changedmysql> show tables;Empty set (0.30 sec)4)创建DLA表,映射到OTS的表:mysql> CREATE EXTERNAL TABLE tableName ( pk1 varchar(100) not NULL , pk2 int not NULL , col1 varchar(100) NULL , col2 varchar(100) NULL , PRIMARY KEY (pk1, pk2));Query OK, 0 rows affected (0.36 sec)## tableName —- TableStore中对应的表名(dla中会转换成小写后做映射)## pk2 int not NULL —- 如果是主键的话,必须要not null## PRIMARY KEY (pk1, pk2) —- 务必与ots中的主键顺序相同;名称的话也要对应5)查看自己创建的表和相关的DDL语句:mysql> show tables;+————+| Table_Name |+————+| tablename |+————+1 row in set (0.35 sec)6)开始查询和分析(用户可以分析自己的数据,符合mysql的语法)mysql> select count(*) from tablename;+——-+| _col0 |+——-+| 25 |+——-+1 row in set (1.19 sec)这样,一个TableStore在DLA中的关联外表创建成功,用户便可以通过JDBC、或者CMS控制台,根据自己的需求实时分析自己的TableStore表了。三、表格存储控制台展示如下为控制提供的SQL场景,用户可以仿照控制台中实例自己写一些需求SQL,开来尝试吧!最畅销的top10产品和销售量中高端产品占总体GMV的比例不同年龄段的消费客单价趋势高消费人群的性别和年龄趋势本文作者:潭潭 阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

December 28, 2018 · 1 min · jiezi

【分布式服务架构原理、设计与实战】读书笔记 (一)分布式微服务架构设计原理

1.1 从传统单体架构到服务化架构1.1.1 JEE 架构JEE 将企业级软件架构分为三个层级:Web 层:负责与用户交互或者对外提供接口。业务逻辑层:为实现业务逻辑而设计的流程处理和计算处理模块。数据存取层:将业务逻辑层处理的结果持久化以待后续查询,并维护领域模型中对象的生命周期。JEE 平台将不同的模块化组件聚合后运行在通用的应用服务器上,例如:WebLogic、WebSphere、JBoss、Tomcat图1-1 JEE 时代的典型架构1.1.2 SSH 架构Struts、Spring、Hibernate视图、模型、控制器(Struts MVC)图1-3 MVC 模型Spring IoC –> 【Spring思维导图,让Spring不再难懂(ioc篇)】Spring AOP –> 【Spring思维导图,让Spring不再难懂(AOP 篇)】图1-4 SSH 时代的架构1.1.3 服务化架构从 JEE 到 SSH ,服务的特点仍然是单体化、服务的粒度抽象为模块化组件,所有组件耦合在一个项目中,并配置和运行在一个 JVM 进程中。为解决上述问题,SOA 出现了:SOA 代表面向服务的架构,俗称服务化。SOA 将应用程序的模块化组件通过【定义明确的接口和契约】联系起来,接口采用中立的方式进行定义,独立于某种语言、硬件和操作系统。SOA 通常通过网络通信来完成,但不局限于某种网络协议,可以是底层的 TCP/IP,也可以是应用层的 HTTP,也可以是消息队列协议,甚至可以是约定的某种数据库存储形式。SOA 服务化的发展:一、Web Service SOA 服务化的一种实现方式,使得运行在不同机器及操作系统上的服务互相发现和调用成为可能,并可以通过某种协议交换数据。图1-5 Web Service 的工作原理图服务提供者 Web Service 2 和 Web Service 3 通过 UDDI 协议将服务注册到 Web Service 目录服务中。服务消费者 Web Service 1 通过 UDDI 协议从 Web Service 目录中查询服务,并获得服务的 WSDL 服务描述文件。服务消费者 Web Service 1 通过 WSDL 语言远程调用和消费 Web Service 2 和 Web Service 3 提供的服务。二、ESB ESB 是企业服务总线的简称,用户设计和实现网络化服务交互和通信的软件模型,是 SOA 的另外一种实现方式,主要用于企业信息化系统的集成服务场景中。 ESB 也适用于事件处理、数据转换、映射、消息和事件异步队列顺序处理、安全和异常处理、协议转换、保证通信服务的质量等场景。图1-6 ESB 架构图ESB 服务密友中心化的服务节点,每个服务提供者都是通过总线的模式插入系统,总线根据流程的编排负责将服务的输出进行转换并发送给流程要求的下一个服务进行处理。ESB 的核心在于企业服务总线的功能和职责:监控和控制服务之间的消息路由。控制可拔插的服务化的功能和版本。解析服务之间交互和通信的内容和格式。通过组合服务、资源和消息处理器来统一编排业务需要的信息处理流程。使用冗余来提供服务的备份能力。1.2 从服务化到微服务图1-7 微服务架构1.3 微服务架构的核心要点和实现原理1.3.1 微服务架构中职能团队划分【康威定律】1.3.2 微服务的去中心化治理1.3.3 微服务的交互模式1、读者容错模式(Tolerant Reader)微服务中服务提供者和消费者之间如何对接口的改变进行容错。在服务消费者处理服务提供者返回的消息过程中,需要对服务返回的消息进行过滤,提取自己需要的内容,对多余或位置内容抛弃,不是硬生生的抛错。推荐宽松的校验策略,只有无法识别信息,继续处理流程时,才能抛错。2、消费者驱动契约模式 消费者驱动契约模式用来定义服务化中服务之间交互接口改变的最佳规则。服务契约分为:提供者契约、消费者契约、消费者驱动契约,它从期望与约束的角度描述了服务提供者与消费者之间的联动关系。提供者契约:以提供者为中心,消费者无条件遵守消费者契约:对某个消费者的需求进行更为精确的描述,可以用来标识现有的提供者契约,也可以用来发现一个尚未明确的提供者契约。消费者驱动的契约:代表服务提供者向其所有当前消费者承诺遵守的约束。一旦各消费者把具体的期望告知提供者,则提供者无论在什么时间和场景下,都不应该打破契约。图1-10 服务之间的交互需要使用的三种服务契约3、去数据共享模式 1.3.4 微服务的分解和组合模式1、服务代理模式根据业务的需求选择调用后端的某个服务,在返回给使用端前,可以对后端服务的输出进行加工,也可以直接把后端服务的返回结果返回给使用端。图1-12 服务代理模式【典型案例:平滑的系统迁移】在新老系统上双写。迁移双写之前的历史遗留数据。将读请求切换到新系统『服务代理模式』。下调双写逻辑,只写新系统。第 3 步,一般只会对读请求切换设计一个开关,开关打开时查询新系统,开关关闭时查询老系统。图1-13 迁移案例中开关的逻辑2、聚合服务模式模式最常用的服务组合模式,根据业务流程处理的需要,以一定的顺序调用依赖的多个微服务,对依赖的微服务返回的数据进行组合、加工和转换,返回给使用方。图1-14 服务聚合模式的架构3、服务串联模式类似工作流,最前面的服务1负责接收请求和相应使用方,串联服务后再与服务1交互,随后服务 1与服务2交互,最后,从服务2产生的结果经过服务1和串联服务逐个处理后返回给使用方。图1-17 服务串联模式的架构● 使用 RESTful 风格的远程调用实现; ● 采用同步调用模式,在串联服务没有完成返回之前,所有服务都会阻塞和等待; ● 一个请求会占用一个线程来处理; ● 不建议层级太多,如果能用服务聚合模式,优先使用服务聚合模式; ● 串联链路上增加节点,只要不是在正后方,串联服务无感知图1-18 在串联服务中调用链的最后增加无感知的架构图1-19 服务串联模式案例的架构图4、服务分支模式● 服务分支模式是服务代理模式、服务聚合模式和服务串联模式相结合的产物。 ● 分支服务可以拥有自己的存储,调用多个后端的服务或者服务串联链,然后将结果进行组合处理再返回给客户端。 ● 分支服务也可以使用代理模式,简单地调用后端的某个服务或者服务链,然后将返回的数值直接返回给使用方。图1-20 服务分支模式的架构图调用链上有多个层次重复调用某基础服务,导致基础服务挂掉时影响的流量有累加效果:假设基础服务资源池中的机器个数为 i,一次挂掉的机器个数为 j,一个调用链中调用 x 次基础服务,那么正确处理的流量的计算公式为: 成功率 = ((i-j) / i) x方分支模式放大了服务的依赖关系,在现实设计中尽量保持服务调用级别的简单,在使用服务组合和服务代理模式时,不要使用服务串联模式和服务分支模式,以保持服务依赖关系的清晰明了。5、服务异步消息模式核心的系统服务使用同步调用,核心链路以外的服务可以使用异步消息队列进行异步化。图1-20 服务异步消息模式的架构5、服务共享数据模式其实是反模式图1-25 服务共享数据模式【在下面两种场景中,仍然需要数据共享模式】:一、单元化架构对性能要求高。图1-26 单元化架构的示意图二、遗留的整体服务在重构微服务的过程中,发现单体服务依赖的数据库表耦合在一起,对其拆分需要进行反规范化的处理,可能造成数据一致性问题。1.3.4 微服务的容错模式1、舱壁隔离模式1)微服务容器分组将微服务的每个节点服务池分为三组:准生产环境;灰度环境;生产环境。图1-27 服务分组2)线程池隔离图1-28 线程池隔离2、熔断模式用电路保险开关来比如熔断模式。对微服务系统,当服务的输入负载迅速增加,如果没有有效的措施对负载进行熔断,则会使服务迅速压垮。图1-29 熔断模式3、限流模式【有如下几种主流的方法实现限流】:1)计数器2)令牌桶图1-32 令牌桶结构3)信号量类似于生活中的漏洞示例:public class SemaphoreExample { private ExecutorService exec = Executors.newCachedThreadPool(); public static void main(String[] args) { final Semaphore sem = new Semaphore(5); for (int index = 0; index < 20; index++) { Runnable run = new Runnable() { public void run() { try { // 获得许可 sem.acquire(); // 同时只有 5 个请求可以到达这里 Thread.sleep((long) (Math.random())); // 释放许可 sem.release() System.out.println(“剩余许可:” + sem.availablePermits()); } catch(InterruptedException e) { e.printStackTrace(); } } }; exec.execute(run); } exec.shutdown(); }}4、实现转移模式【如果微服务架构发生了熔断和限流,则该如何处理被拒绝的请求】采用【快速失败】,直接返回使用方错误是否有【备份】,如果有,则迅速切换到备份服务【failover策略】,采用重试的方法来解决,这种方法要求服务提供者的服务实现幂等性1.4 Java 平台微服务架构的项目组织形式1.4.1 微服务项目的依赖关系在微服务领域,jar 包被分为:一方库:本服务在 JVM 进程内依赖的 Jar 包。二房库:在服务外通过网络通信或 RPC 调用的服务的 Jar 包。三方库:所依赖的其他公司或者组织提1.4.2 微服务项目的层级结构Java 微服务项目一般分为:服务导出层、接口层、逻辑实现层服务导出层 : 最后会打包成一个 War 包,包含服务的实现 Jar 包、接口 Jar 包,以及 Web 项目导出 RPC 服务所需的配置文件等。服务接口层 : 包含业务接口、依赖的 DTO 及需要的枚举类等,最后打包成 Jar 包,发布到 Maven 服务器,也包含在服务导出层的 War 包中。服务实现层 : 包含业务逻辑实现类、依赖的第三方服务的包装类,以及下层数据库访问的 DAO 类,最后打包成 Jar。【服务实现层的架构图】【服务实现层的反模式架构图】1.4.3 微服务项目的持续发布微服务项目需要实现自动化的CI/CD,包括:代码管理自动编译发布 OA自动化测试性能测试准生产部署和测试生成环境发布1.5 服务化管理和治理框架的技术选型1.5.1 RPC1、JDK RMIJava 到 Java 的分布式调用框架,一个 Java 进程内的服务可以调用其他 Java 进程内的服务,使用 JDK 内置的序列化和反序列化协议。序列化协议:JDK自带的专用序列化协议,不能跨语言网络传输协议:底层网络协议2、Hessian & Burlap序列化协议:Hessian 序列化为二进制协议;Burlap 序列化为 XML 数据网络传输协议:HTTP 协议3、Spring HTTP Invoker序列化协议:Hessian 序列化为二进制协议;Burlap 序列化为 XML 数据网络传输协议:HTTP 协议1.5.21、Dubbo提供高性能、透明化的 RPC 远程服务调用,还提供了基本的服务监控、服务质量和服务调度支持多种序列化协议和通信编码协议,默认使用 Dubbo 协议传输 Hessian 序列化的数据使用 ZooKeeper 作为注册中心来注册和发现服务通过客户端负载均衡来路由请求,负载均衡算法包括:随机、轮询、最少活跃调用数、一致性哈希等。2、HSFHigh Speed Framework3、Thrift采用中间的接口描述语言定义并创建服务支持跨语言服务开发和调用,并包含中间的接口描述语言与代码生成和转换工具采用二进制序列化传输数据4、AXIS源于 IBM “SOAP4J”,使用 SOAP 协议5、Mule ESB基于 Java 语言的企业服务总线产品,可以把多个异构系统通过总线模式集成在一起1.5.3 微服务1、Spring Boot【图1-37 JEE时代,应用包含在容器内的架构图】Spring Boot 相反,它将容器嵌入自启动的 Jar 包中,在 Spring Boot 应用启动时,内部启动嵌入的容器【Spring Boot 的容器包含在应用内的架构图】Spring Boot 这种设计在微服务架构下有如下明显有点:可以创建独立、自启动的应用程序无需构建 War 包并发布到容器中,构建和维护 War 包、容器的配置和管理也需要成本通过 Maven 的定制化标签,可以快速构建 Spring Boot 的应用程序可以最大化的自动化配置 Spring,而无需人工配置各项参数提供了产品话特点,如:性能分析、健康检查和外部化配置无 XML 配置是 Spring Cloud 构建微服务架构的重要基础2、Netflix提供服务发现、断路器和监控、智能路由、客户端负载均衡、易用的 REST 客户端等服务化必须的功能3、Spring cloud Netflix服务发现组件 Eureka容错性组件 Hystrix智能路由组件 Zuul客户端负载均衡组件 Ribbon【图 1-39 Spring Cloud Netfix 架构图】【Netflix 交互流程】服务在 Eureka 服务器实例注册Zuul 作为一个特殊的服务在 Eureka 上注册并发现服务Zuul 作为网关,将发现的服务导出给客户端RestTemplate 和 FeignClient 使用简单的服务调用方法调用服务1、服务2【Netflix 特点】服务在 Eureka 注册,由 Spring 管理的 Bean 来发现和调用通过配置的方式可以启动嵌入式的 Eureka 服务器Feign 客户端通过声明的方式即可导入服务代理Zuul 使用 Ribbon 服务实现客户端负载均衡通过声明的方式即可插入 Hystrix 的客户端通过配置的方式即可启动 Hystrix 面板服务器在 Spring 环境中可以直接配置 Netflix 组件Zuul 可以自动注册过滤器和路由器,形成一个反向代理服务器Hystrix 面板可以对服务的状态进行监控,并提供容错机制 ...

December 28, 2018 · 2 min · jiezi

2018年阿里云NoSQL数据库大事盘点

NoSQL一词最早出现在1998年。2009年Last.fm的Johan Oskarsson发起了一次关于分布式开源数据库的讨论,来自Rackspace的Eric Evans再次提出了NoSQL概念,这时的NoSQL主要是指非关系型、分布式、不提供数据库设计模式。从2009年开始,NoSQL开始逐渐兴起和发展。回望历史应该说NoSQL数据库的兴起,完全是十年来伴随互联网技术,大数据数据的兴起和发展,NoSQL在面临大数据场景下相对于关系型数据库运用,这一概念无疑是一种全新思维的注入。正值2018年底之际,让我们来看看NoSQL数据库领域发生了哪些深刻的变化和阿里云NoSQL的关键技术,尝试探究未来十年云计算时代新趋势。云NoSQL数据库成为数据库领域重要增长引擎云化趋势不可避免,根据Gartner的报告,2017年超过73%的DBMS增长来自云厂商,Gartner象限里面AWS在领导者象限上升趋势明确,老牌厂商下滑严重。在2018年Gartner报告中,阿里云数据库更是中国唯一首次入围远见者象限。而在众多云厂商里面增长最快的又是NoSQL数据库,云NoSQL成为数据库领域重要增长引擎。NoSQL融合和跨界创新成为新趋势2018年对SQL/NoSQL是一个重要的一年。MySQL 8.0重点增强了json支持,整行是一个文档,并提供了专门的接口用于访问,类似MongoDB的能力。MongoDB release了4.0版本,NoSQL领域里面第一个开始支持事务,在刚刚举行的2018年的AWS:reivent大会上,dynamodb也宣布支持事务。传统SQL/NoSQL不再界限分明,NoSQL和SQL数据库融合和跨界创新逐渐成为新趋势,事务不再是关系型数据库独门秘籍。多模成为事实标准如果去年Gartner在2017年数据库厂商推荐报告中还在谈多模是发展趋势的话,今年再看看各大数据库厂商都已经纷纷支持了多模,如Azure的cosmosDB早就支持K/V,列簇,文档,图。AWS的DynamoDB支持KV和文档,阿里云新发布X-Pack更是将多模推上新高度,KV的基础上,同时支持时序、时空、图、文档等多种数据模型。全球分布式成为重量级玩家的入门门槛云厂商之所以有这么强大的活力,除了在数据库内核本身长足的进步之外,结合云服务整体生态的创新是重要的一环。云厂商3A玩家的主流NoSQL数据库都已经全面覆盖了全球分布式能力,助力企业参与全球业务竞争。是否具备全球扩展和分布式的能力,是云NoSQL数据库重要的入门门槛。阿里云集团是国内最早提出数据战略,本身也拥有最大体量的数据,是最早投入NoSQL数据库技术研发,目前也拥有国内最大的专家团队。在持续十年技术加持下,阿里云NoSQL目前覆盖了所有主流的NoSQL数据库,如Redis/mongodb/HBase等等。阿里云NoSQL数据库多项独家关键技术领先竞争对手,是国内当之无愧的NoSQL数据库排头兵。关键技术一:8月份推出Redis独家全球多活8月份阿里云Redis,全球云厂商首家推出Redis全球多活,解决多媒体、游戏、电商等多行业客户业务出海、地域级灾备、全球同服/多活、跨域数据迁移的诉求。多项关键能力独步领域内:全球多活:内核级别支持,实现多节点全球容灾。高可用:同步通道支持断点续传,容忍天级别的隔断,子实例HA自动切换高性能:单通道10万TPS,高于Redis处理速度;延迟低、洲际内百毫秒延迟支持数据最终一致性:CRDT冲突解决方案+数据一致性检测,基于Redis内核改造,原生Redis内核无此功能。关键技术二:10月份全球第一家云厂商支持MongoDB 4.0版本,多项独家企业级能力,领域内领先跨域双活:数据双向同步,相比现有通道产品提升100%效率,业界领先。首创物理+逻辑备份双能力:物理备份,相比开源版本备份效率提升100%,恢复效率提升300%。创新性提供逻辑snapshot能力,解决政企等定期大批量数据更新需求,同架构下性价比提升100%。秒级监控及智能诊断能力:提供每秒粒度的监控数据采集,监控精度提升数十倍。结合监控、审计等数据提供智能诊断,分析系统运行瓶颈并自动化提供优化建议。关键技术三:HBase发布全新X-Pack,支持多模和融合负载能力,定义云HBase业界新标准12月13日第8届中国云计算标准和应用大会隆重发布X-Pack,新增多模型和融合负载支持。多模型支持:同时支持KV、时序、时空、图、文档等多种数据模型,内置丰富处理能力,让业务开发效率提升百倍。融合负载支持:ApsaraDB HBase在在线能力的基础上,融合流处理、批处理、OLAP,OLTP、高速对象存储,全文检索等能力,提供客户业务开箱即用的能力。多模型和融合负载是云HBase领域第一个具备此能力的云服务。阿里云6月6日全球首发2.0,12.13日又全新推出X-Pack,持续领先的背后是高强度,持续的研发投入,阿里巴巴集团早在2010开始研究并把HBase投入生产环境使用,从最初的淘宝历史交易记录,到蚂蚁安全风控数据存储。持续8年的投入,历经8年双十一锻炼。4个PMC,6个committer,造就了国内最大最专业的HBase技术团队,其中HBase内核中超过200+重要的feature是阿里贡献。集团内部超过万台的规模,单集群超过千台,全球领先。云HBase的能力也得到了客户的高度认可,短短1年间,就覆盖了社交、金融、政企、车联网、交通、物流、零售、电商等数十个个行业,帮单用户顶住千万级QPS的业务压力,以及百PB级数据高效存储和处理。站在2018/2019交替之际,展望未来,阿里云NoSQL数据库也会持续围绕客户业务,成本,运维各个方面进行优化和创新,成就客户,争做最好的NoSQL数据库而持续努力!本文作者:所在jason阅读原文本文为云栖社区原创内容,未经允许不得转载。

December 25, 2018 · 1 min · jiezi

深入探访支付宝双11十年路,技术凿穿焦虑与想象极限

摘要: 支付宝与欲望、想象力的博弈乃至搏斗,10年来不曾停歇。小蚂蚁说:双11十年间,交易规模的指数级增长不断挑战人们的想象力,而对蚂蚁技术团队来说,这不仅是一场消费盛宴,而是无数次濒临压力和焦虑极限的体验,更是技术的练兵场。如今双11对蚂蚁金服而言,已经绝不仅限于一个技术项目,而更像是一个社会化工程,可以叫做「连贯的,社会化的技术大协作」。支付宝团队不正像那尊红漆雕塑一样?一面对技术保持着敬畏、谦逊,一面又不得不玩命狂奔。「双11」就在眼下了,但蚂蚁金服的新园区里气氛明朗,人群也没往年那么匆忙。进园区时,出租车司机左手扶稳方向盘,右手比划着说,秋天是杭州最好的季节,当然啦,春天也不赖。阳光猛烈,洒在园区的楼群上,映得金栗色玻璃深邃又清亮。这座新园区里尚有很多事不为人所熟知。每3分钟会有1人在2号楼门口左手边垃圾桶上捻灭烟头,吱呀作响;访客大厅的姑娘每天用胖大海跟人参片泡4壶茶,12个玻璃杯倒扣,杯子把统一偏右30度;园区身着橙色外套的保洁员不间歇地扫落叶,她们每天工作8小时,3班倒,总在推车上预备3个喷壶,以及1个保温杯;每个花坛里,通常能用竹质夹子够出三个烟头或纸片。这里的秋天昼夜温差只有6摄氏度,但早晚都有人衣着单薄;穿冲锋衣的外卖员打手机时,话筒离开嘴边20公分,嗓门平均70分贝;下午时分,很多餐馆的员工们蹲在门口抽烟,只有星巴克客流不断,这里的大蛋糕与迷你蛋糕预定时间都是3天,收银员也偶尔会用墨色水笔给姓董的先生标注Mr Wang;员工餐厅每天分四次供餐,楼群间额外排列着18家餐饮门店。楼内有超过1000平米的免费健身房,私教价格仅为外边的一半,穿耐克跑鞋的姑娘每天会带着她的柯基犬来同时使用两台跑步机,尽管她的身型已没什么可挑剔。当你在下沉广场跟第四个人搭话后,套着夹克衫的保安会盯着看,在你发毛之前问及身份,噢,是记者,别介意,履行职责嘛。这是造价超过11亿元,面积18万平方米的蚂蚁金服新总部,设施功能齐全,堪堪媲美小型城镇,是 NBBJ 建筑事务所的手笔,也被叫做「蚂蚁Z空间」。功能强大的综合体建筑容纳了这里的杭州人与新杭州人,注视着他们的每一单生意,每一次创新,这里承载上万人的财富与梦想,也记录着每个个体的骄傲与焦虑。双11十年间,交易规模的指数级增长不断挑战人们的想象力,而急速扩张背后,对技术团队来说,是无数次濒临压力和焦虑极限的体验。想象力和焦虑最初给蚂蚁金服技术团队结出了一张网,又织就成细密厚实的茧壳。从2010年开始的三四年里,人们总会在双11的消费前端感受到一些使用体验的卡顿、不舒适,而内里则是这批工程师与欲望、想象力的博弈乃至搏斗,并在很多个逼近焦虑极限的瞬间,不断打破桎梏。「为了几十秒,值吗?」杭州入秋的早晨,凉得很,黄勇(花名展一)起个大早,跟几位同事结伴跑了趟灵隐寺。这千年古刹在深山,向来香火旺盛。这几年,寺庙时兴环保,免费发清香,他请了三炷,点上,拜拜。采访时,我问拜的哪位菩萨,黄勇皱皱眉头,乐了,「还真不认识」。烧香的心可是诚的,况且,来许愿的人,没几个比他的愿望还大,作为今年双11支付保障PM(项目经理),他得事无巨细地操办这个事关几亿人的项目。每逢双11,蚂蚁金服的项目组成员们总要供上关二爷,穿上红内裤,换上红战袍,存几瓶红酒,烧几炉香。按支付宝双11保障团团长陈亮(花名俊义,技术风险部研究员)的话来说,这是对技术的敬畏。可事实上,要敬畏的绝不仅仅是技术这一件事,双11作为枝节空前庞杂的项目,每个事物的细节上都有无数个随机的可能性,早已超出了人能控制的边界。黄勇能做的就是制定「容灾」机制,尽力去逼近那个不可能到达的「确定性」。举个例子来说,在采访当天,黄勇刚刚给所有11月10号晚上要进光明顶(支付宝双11作战室)的成员发了邮件,仔细交代了「如果当晚茶杯在电脑上打翻了怎么办」这个主题。2012年,负责支付宝双11项目的PM同事从西安请回一尊皮影关公像,大伙觉得新鲜,纷纷敬上香烟、酸奶跟水果。自打那会开始,每逢重要的项目启动,总有人提前往公司请关二爷。创业邦这次拜访蚂蚁金服时,作战室里就供着一尊二爷铜像,该上的供也早都摆上了。请二爷似乎也开始带来好运,那位请铜像的同学,前年双11还在公司里抽到一次大奖。某年双11,马云带几位合作伙伴在西溪园区参观,登上光明顶(支付宝双11作战室)的时候,一位女性投资人吃惊地问,你们工程师居然时兴拜关公?俊义就笑,还是那个说辞,敬畏。信仰也好,敬畏也罢,双11显然都值得。十年里,从最初几乎不太被人感知的促销活动,由欲望、情绪、责任感和创造力混合驱动着增长,长成一个不断突破想象力极限的庞然大物。2009年,首届双11购物节的单日成交额是5000多万元,一个对比是,当年支付宝的日交易额最高突破了12亿元。「记得有几十个品牌参与,当时对它的感觉就是,淘宝做了个活动」,支付宝事业群总裁倪行军(花名苗人凤)回忆称。但他没有预料到,所有人都没预料到,从第二年,双11就开始刷新所有人的想象力上限,如今回头端详增长曲线,它在某些年份里维持着数字量级的增速,那线条着实显得陡峭,但想想吧,处在那个当下,未知和增长给人们心理带来的是更加强烈的冲击感。在蚂蚁金服CTO程立(花名鲁肃)的记忆里,2010年之后的几年双11,对支付宝技术团队来说,是像电影《2012》一般的巨大考验,「你把一个船放在那里,上面有个大浪,没人知道能不能扛住,扛住就扛住了,扛不住就没了。」这艘大船只能提前按既有的想象力建造,但在应对巨浪时,必须临时补救随机出现的漏洞,随机意味着不确定性,巨大的随机和不确定性就进一步施加给团队更庞大的压力。程立记得,现任阿里云副总裁李津当时在阿里巴巴集团负责双11项目,「受不了的时候,李津要开车到龙井山上,打开窗户睡一宿,他说压力太大了,要吸氧。」2010年,第二次迎接双11的支付宝经历了一次后来广为人知的「4秒惊魂」。11日的23时59分30秒,双11结束前半分钟,支付宝核心账务系统突然报警,资源行将耗尽。当时整个支付宝的账务数据库没有进行过任何拆分,一旦系统崩溃,所有业务都会挂掉,对淘宝和支付宝都会造成灾难性损失。在工程师将一个会计系统的应用关掉,释放出来资源时,离数据库崩溃只剩4秒。单就技术本身,在当时就已经是一笔永远测算不清楚的账。2012年双11之前,支付宝技术组已经把能想象到的压力测试做了个遍,但当晚高峰期还是出了岔子,运维工程师巩杰(花名袁越)记得,当时后台一条数据通道设置的阈值太低,导致短暂宕机,但系统认定为无法响应,于是自动将其剔除了,随后服务器一台接一台地挂掉,「跟雪崩似的,导致几十分钟里交易一直在抖动」,直到做了降级,切掉一部分流量之后,系统才恢复正常交易——按程立的说法是,那根保险钨丝被高频交易熔断了,临时搭上一根铜线才应付过去。此时,过于庞大复杂的系统,人力已经无法完成全面有效的测试了。巩杰说,因为有前两年数据库无法承压的情况,2012年已经在应用和DBA层面做了大量的压力测试,但最终出问题的,恰恰是前面还没压到的「路口」。采访中,俊义苦笑道,当时每年双11都信心满满,每年又都过得提心吊胆。在双11压力最大的那几年,整个支付宝技术团队每年要花费几个月乃至半年时间来「练兵」,做各种技术结构调整,系统测试。俊义最初产生过疑问,整个团队花费出的绝大部分时间精力,只是为了贡献给双11最高峰的那几秒。「非得这样吗?」「值吗?」但时间会赋予所有原本未知事物以终极的意义,双11正是这样一个把意义逐渐延展开的时代产物。「在当时,淘宝是我们最大的客户,我们必须服务好」,俊义说。按照马云早年的讲法,在客户关系之外,淘宝天猫和支付宝更像是夫妻关系,也正是在淘宝天猫的业务倒逼下,支付宝团队的技术能力被空前地激发,一位今年入职的工程师毫不讳言,他入职蚂蚁金服的核心吸引力就是双11,「对工程师来说,再没有比双11更值得挑战的项目了。」巩杰也是后来才意识到,某信用卡团队早先在实验室环境里实现的数万笔每秒的交易峰值,早就被支付宝在实战里远远抛在身后。2017年双11,支付宝的交易峰值就达到了25.6万笔/秒。按照资深技术专家李铮(花名祢衡)的说法,技术团队最近几年已经把双11两天48小时的工作量做了很细致的拆分,“我们做了非常详尽的作战手册,它有很多的步骤,按不同的时间点,你要去执行。”技术之外,双11是个在更广泛的范围内牵扯着不同部门,不同团队,不同企业的庞大协作系统。蚂蚁金服集团副总裁陈亮(花名关胜,品牌与公众沟通部门负责人)记得,某一年的双11当晚十点钟前后,一家国有大行银行的交易系统内的一百万个单号发光了,后续单子无法生成,于是当晚最后两个小时,所有源自该银行的支付订单都无法执行。「总会有你无法预想的问题出现,我们做好所有准备,剩下只能兵来将挡水来土囤了。」想想啊,就好比火箭升空一样,倪行军敲敲桌子说。多少软硬件技术环节,多少个零件组装拆卸,在设计制造的过程中,只能穷尽所有人脑可以企及的可能性去做测试,但在点火那一刹那,等待它的是圆满功成还是原地爆炸,你只能束手以待了。倪行军觉得,无论是技术人员拜关公、烧香还是公关团队的预案,都证明了蚂蚁金服团队对双11的敬畏心。2013年5月,支付宝下线了最后一台IBM小型机,随后逐渐以自主研发的OceanBase数据库替代了Oracle,完成了去IOE工程。如今双11对蚂蚁金服来说,已经绝不仅限于一个技术项目,而更像是一个社会化工程。程立说,如果为它定义一个清晰的组织概念,可以叫做「连贯的,社会化的技术大协作」。一面敬畏,一面狂奔蚂蚁Z空间的楼群维持着古怪的几何形状,像个「撅着屁股」的Z字,又像个扭动起舞的水泥巨人。但与外部怪异的建筑设计、杂乱的人流相反,在楼宇内部密布着闸机与证件机器,构建起坚固的秩序和准入流程。室外,巨大的红色人形雕塑朝着人流入口鞠躬,姿态谦逊,气势却浑然不可当。支付宝团队不正像那尊雕塑一样?一面对技术保持着敬畏、谦逊,一面又不得不玩命狂奔。这十年间,在双11之外,他们也有很多焦虑要去消解。被问及在支付宝工作十几年间最难忘的瞬间,倪行军和陈亮的首选都是那次年会。2010年1月21日,支付宝公司年会,此前内部并没有太多源自自觉的危机感。遥遥领先的市场份额与灼灼亮眼的业务数据,一切看起来十分顺利。但年会一开场,人们就发现气氛就有些怪异。会场高音喇叭里首先传来指责、抱怨、无奈与批评,这些声音是来自客服电话录音里的客户投诉。但现场事态发展,完全不只是「反思」而已。陈亮到了会场,才收到马云等阿里集团组织部的高管们将要到场的消息。随后,客户满意中心的代表上台,表达了「我们的体验如何糟糕,用户如何承受着折磨」;BD团队则指出「合作伙伴是如何对支付宝的高期望,同时又是如何的失望和无奈」。马云现场发火了。「烂,太烂,烂到极点」。陈亮记得,这是他多年来唯一一次在公开场合看到马云发脾气。马云毫不客气地指出,支付宝在很多问题上太过保守,如果不重视用户体验,「将慢慢死去」。这显然跟支付宝团队自我评价的结论相去甚远,事实上,在那个时点上,如果横向对比来看,支付宝的产品设计和市场占有率表现绝不算差,团队甚至把2009年定义为「用户体验年」。但回头看,当时在PC端的产品体验确实很不理想,每次支付都需要解决控件、插件、外接U盾一堆问题。 时任阿里巴巴CTO的王坚也给了一句非常严厉的评价,「自娱自乐」。这甚至使倪行军当下有点懵,他记得在年会之后一段时间里,一度陷入严重的自我怀疑,「搞了这么多年技术,怎么变成自娱自乐了?是不是我们对技术的认知出了问题?」后来他反应过来,差池是出现在从技术到产品、到业务、再到客户之间的对话环节。做客户体验,单由使命与愿景来驱动不够。他原本认为的应该如何运作,与用户的现实期待之间,鸿沟已现。整个中国的支付行业按照支付方式演变可以分成三个阶段:2009年-2013年,从网银支付到快捷支付;2014年-2016年,移动支付崛起;2017年-2018年,则是指纹和刷脸支付渐成主流。如今回头看,那次年会对整个蚂蚁金服公司来说都是个至关重要的节点,在此次转型的推动下,支付宝从网银支付迈进了快捷支付时代。「生生被逼出来的」,俊义回忆道,「如果那时候没有快捷支付,整个中国移动互联网的进程至少会落后两三年」。微信支付加入之前,支付宝曾有十年时间只能自我调试,寻找发展坐标。而当前者入局,支付宝团队的反应是:哇!我们有竞争对手了。「我们从没有遇过像这样的竞争对手,竞争是很正常的事情,但结局取决于竞争对手的能量,微信支付是非常值得尊敬的一个竞争对手。」陈亮如是说。微信支付出现,促使蚂蚁金服又一次推进意识形态的提升。如今说来云淡风轻,当时可是风起云涌,情绪百般垂丧。时间回到2014年1月26日,腾讯推出微信红包,后者立刻以病毒式传播的方式活跃在微信群内,并在除夕夜全面爆发。数据显示,除夕当天到初八,超800万用户参与了红包活动,超4000万个红包被领取。与微信红包这面的热火朝天形成明显反差的是,支付宝的「讨彩头」反响平平。后者推出于23日,还早了3天。「微信一个红包就超过支付宝8年干的事。」这句话很快流传起来,马云后来则用「珍珠港偷袭」评价腾讯推出微信红包一举。陈亮对这件事情对记忆尤其深刻,他参与了支付宝红包的产品讨论。因为也在广东工作过,知道当地有讨红包的习俗,于是他给出了做「讨红包」的建议。但微信做的是「发红包」,陈亮回想,当时讨论过程中,似乎也有人提出这一点,但产品设计最终并未将其采纳。 其实,即便支付宝当时采用了发红包的设计,在那一阵上也未必有胜算——没有关系链,没有社群,没有从交易体系到账户体系的整体准备。但陈亮仍然感到懊悔,控制不住的懊悔,甚至责怪自己技不如人。眼看着媒体群里纷纷扬扬的红包雨和赞扬声,陈亮都不想上微信了,「不想说话了,不敢说话了」。 他想去友人处寻得开解,想驳斥那句一个红包顶八年的说法,但他刚开口就沉默下去,市场反应已然说明一切。可他还是在心里翻来覆去地想,怎么我们没有想到人家那个点子,怎么就没有呢? 但事情过去也就过去了。尽管公司层面的焦虑一直延续到2016年,但陈亮已经学会将焦虑情绪摒除在自己的生活之外。焦虑毫无用处这件事已被证明——前两年的焦虑除了让他自己难受紧张、动作变形外没有产生任何意义。其实,接受这种量级的竞争,或许某种意义上也是在接受命运馈赠。陈亮后来总是被年轻同事认为对困难事物的感受很迟钝,他自己觉得原因在于再没有过境况更加艰难的时刻了。再碰到困难时,总有一种消解的情绪在,「最难的时候都过来了,这些算什么?」而支付产业则更加受益于两家顶级公司的竞争推动,中国支付技术在国际上一骑绝尘。2017年年末,西班牙《世界报》刊文表达了对中国支付产业的看法,给出的结论叫做:「中国的支付革命堪称中国史上最大的技术革新之一。」技术的价值观其实从2010年双11的「4秒惊魂」之前,支付宝技术人员就意识到,使用IOE商用设备(IBM-服务器提供商,Oracle-数据库软件提供商,EMC-存储设备提供商,三者构成了从软件到硬件的企业数据库系统)与开源软件,已经不能适用于双11交易量指数级增长对技术支持的要求,尤其是在谁也不能完全预设到当晚状况的时候。即使能支撑,成本也将是天文数字。支付宝决定去IOE,自主研发分布式数据库,转云计算,OceanBase项目随即启动。俊义记得,他在支付宝做的第一个技术改造项目是拆分数据库。当时还不是因为双11,单纯是因为支付宝网站交易量涨得很快,数据库扛不住了,不拆,业务就无法增加。这是在2008年。2010年,俊义又拆了一次数据库。这次,他将上次拆出的两个数据库中的交易数据库,拆成10个小型机。这时已差不多算是为去IOE铺下基础。但很快,10个小型机也不够用了。 2011年的双11结束后,应用服务器与数据库的连接已到瓶颈,容量没办法再增加,换句话说,IOE集中式强大单点无法满足阿里特别是当时淘宝爆炸式业务增长应用的模式,同时也限制了技术潜力的发挥,另外,由于IOE是专用设备,对机架、电力、网络存在单独设计的要求,成本压力也已经非常大。 从2010年1月启动,到2011年7月完成商品库的去IOE(经历读写分离、去小型机、去Oracle和EMC),再到交易等其他核心系统的去IOE,2013年,支付宝最后一台小型机下线,IOE中的I和E都已经被中国自主研发的技术取代,上云完成阶段性进展,这就像造发动机,意味着双11的交易量不会再受到技术制约。不过在第一阶段,每年双11能否顺利通过,还是有点碰运气。从2014年开始,支付宝开始研发和施行全链路压测技术,这就有点像造飞机时候的风洞,造一个实验室,完全模拟当天峰值所有的真实环境,对系统进行压力测试。据2018年大促保障副队长巩杰说,全链路压测对真实用户请求的模拟可以达到与双11当天请求90%以上的一致度。这样一来,到了双11当天,平稳度过的概率就极高了,团队因不确定而产生的焦虑大幅降低。全链路压测作为消除不确定性的“大杀器”,已经成为目前测试系统的常规手段,随着系统的升级,使用频率也在降低,李铮记得,全链路压测技术刚刚研发使用的时候,“恨不得每天都做一遍测试”,而今年的双11准备工作里,每周定期做1-2次压力测试已经足够了。支付宝的双11已经是一个巨大的系统工程,已经无法再完全依赖人脑思考解决所有条线上的问题。所以,李铮觉得,“智能化”是另一个关键词。对系统工程的把控,也正是要辅以智能化全链路压测这类技术手段,才能更加精准高效地解决问题。 11月2日,大促保障团组织了最后一次模拟的全链路压测,万事俱备,只欠东风,就等10日24点一过。对支付技术来说,稳定压倒一切,稳定也意味着一切。一如往年,第10年双11,稳定的重要性依然处于第一位置。稳定之外,支付宝技术团队还有更多追求。在2018年的双11技术保障上,人工干预已经越来越少,因为整个保障系统的智能化程度越来越高。比如,往年筹备双11时,该配置多少计算资源,如何达到最优化的配置,都需要非常有经验的工程师进行严密计算,并进行反复的压力测试,不断调优。但现在,机器可以自动地进行计算和调优。程立打了个比方,双11的支付保障会越来越朝着「自动驾驶」的目标迈进,该往哪开,在哪停,如何躲避风险,保障安全,都是智能的。新的变化还体现在生物识别支付和区块链技术的应用。 在倪行军的谈论中,支付宝对支付的理解,倾向于支付脱媒,到最后,支付时不需要任何载体,人体本身即为最大媒介,当然,脱媒不可完全脱离,但生物识别技术是IoT时代用户参与到数字化场景的敲门砖,任何的场景系统都要首先确定一个所谓的数字身份的问题,而人本身就是最棒的载体,不需要其它的媒介做二次切换。由此,生物识别是可以重塑体验的技术。据倪行军透露,平日应用场景中的生物识别(包括指纹输入、面部扫描等)支付比例已经超过一半,这反映出整体人群对生物识别技术所对应的新支付体验的接受程度,这信号让他觉得,手机应用之外其他生活场景中,扩展生物识别技术用户的时机,已经到来。今年上半年,生物识别技术真正走向规模化商业化,倪行军的预期是先实现规模化,在终端设备达到百万级规模的基础上,根据用户行为与各商业场景连接的磨合情况,再考虑后续的商业诉求。未来,新技术的应用势必重新定义整个商业流程,新的百万级的商业机会将在此诞生。今年天猫双11用区块链技术为1.5亿跨境商品提供原产地溯源,包括比利时钻石交易所的钻石这类大额商品。 变化背后是蚂蚁金服的BASIC技术战略演进及开放,Blockchain (区块链)、Aritificial intelligence(人工智能)、Security(安全)、IoT(物联网)和Computing(计算)这五条线索构成对未来更加清晰的想象力。十年间,蚂蚁金服整个公司都在从中心化向分布式持续变化。人员能力变得更加均衡。俊义记得,早年在双11和很多技术攻关的关键时刻,总会有几位技术大牛同事站出来,在当下拿出过人的洞察与能力,最终顺利过关。但如今,蚂蚁金服公司的整个技术结构益发庞杂,必须形成全局、众人的工程化作战。IT架构从IOE变成分布式,再演化出「离在线混部」。去年有25%是自有服务器处理,55%在云上,20%是离线资源;今年这个比例则会更新到60%在云上,在线与离线分别20%,其间,性能较差的离线机房也能执行在线处理,核心在于资源的进一步合理分配。分布式趋势渐成大势:机房越来越多,从杭州拓展到全国各地;应用系统与数据库越扩越多;团队从支付宝技术团队扩至各个产品线,集团运作从前尚可靠寥寥能力拔尖者把握,如今则需层层分解,整体组织协同作战。「从中心化到分布式」是互联网发展过程中,近年形成的社会关系形态和内容的一大特征。如果将其视作一种价值观的话,作为一家工程师员工占比超过51%的互联网金融企业,它正在被深深影响、驱动并改变着,企业里大量人、事、物,都在明确地呈现这这种趋势导向,这家价值上千亿美金的企业,也正在成为一个由技术价值观驱动业务、团队革新与发展的经典范本。本文作者:平生栗子阅读原文本文为云栖社区原创内容,未经允许不得转载。

December 25, 2018 · 1 min · jiezi

【Dubbo源码阅读系列】之 Dubbo SPI 机制

最近抽空开始了 Dubbo 源码的阅读之旅,希望可以通过写文章的方式记录和分享自己对 Dubbo 的理解。如果在本文出现一些纰漏或者错误之处,也希望大家不吝指出。Dubbo SPI 介绍Java SPI在阅读本文之前可能需要你对 Java SPI(Service Provider Interface) 机制有过简单的了解。这里简单介绍下:在面向对象的设计中,我们提倡模块之间基于接口编程。不同模块可能会有不同的具体实现,但是为了避免模块的之间的耦合过大,我们需要一种有效的服务(服务实现)发现机制来选择具体模块。SPI 就是这样一种基于接口编程+策略模式+配置文件,同时可供使用者根据自己的实际需要启用/替换模块具体实现的方案。Dubbo SPI 的改进点以下内容摘录自 https://dubbo.gitbooks.io/dub… Dubbo 的扩展点加载从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来。JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点在 Dubbo 中,如果某个 interface 接口标记了 @SPI 注解,那么我们认为它是 Dubbo 中的一个扩展点。扩展点是 Dubbo SPI 的核心,下面我们就扩展点加载、扩展点自动包装、扩展点自动装配几方面来聊聊具体实现。Dubbo SPI 机制详解Dubbo 扩展点的加载在阅读本文前,如果你阅读过Java SPI 相关内容,大概能回忆起来有 /META-INF/services 这样一个目录。在这个目录下有一个以接口命名的文件,文件的内容为接口具体实现类的全限定名。在 Dubbo 中我们也能找到类似的设计。META-INF/services/(兼容JAVA SPI)META-INF/dubbo/(自定义扩展点实现)META-INF/dubbo/internal/(Dubbo内部扩展点实现)非常好~我们现在已经知道了从哪里加载扩展点了,再回忆一下,JAVA SPI是如何加载的。ServiceLoader<DubboService> spiLoader = ServiceLoader.load(XXX.class);类似的,在 Dubbo 中也有这样一个用于加载扩展点的类 ExtensionLoader。这一章节,我们会着重了解一下这个类到底是如何帮助我们加载扩展点的。我们先来看一段简短的代码。Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();在 Dubbo 的实现里面用到了大量类似的代码片段,我们只需要提供一个 type ,即可获取该 type 的自适应(关于自适应的理解在后文会提到)扩展类。在获取对应自适应扩展类时,我们首先获取该类型的 ExtensionLoader。看到这里我们应该下意识的感觉到对于每个 type 来说,都应该有一个对应的 ExtensionLoader 对象。我们先来看看 ExtensionLoader 是如何获取的。getExtensionLoader()public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { if (type == null) { throw new IllegalArgumentException(“Extension type == null”); } if (!type.isInterface()) { throw new IllegalArgumentException(“Extension type(” + type + “) is not interface!”); } // 是否被 SPI 注解标识 if (!withExtensionAnnotation(type)) { throw new IllegalArgumentException(“Extension type(” + type + “) is not extension, because WITHOUT @” + SPI.class.getSimpleName() + " Annotation!"); } //EXTENSION_LOADERS 为一个 ConcurrentMap集合,key 为 Class 对象,value 为ExtenLoader 对象 ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); if (loader == null) { EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); } return loader;}private ExtensionLoader(Class<?> type) { this.type = type; objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());}上面这一段的代码比较简单,根据 type 从 EXTENSION_LOADERS 集合中获取 loader ,如果返回的值为 null 则新建一个 ExtensionLoader 对象。这里的 objectFactory 获取也用到了类似的方法,获取到了 ExtensionFactory 的扩展自适应类。getAdaptiveExtension()public T getAdaptiveExtension() { //cachedAdaptiveInstance用于缓存自适应扩展类实例 Object instance = cachedAdaptiveInstance.get(); if (instance == null) { if (createAdaptiveInstanceError == null) { synchronized (cachedAdaptiveInstance) { instance = cachedAdaptiveInstance.get(); if (instance == null) { try { instance = createAdaptiveExtension(); cachedAdaptiveInstance.set(instance); } catch (Throwable t) { // … } } } } return (T) instance;}getAdaptiveExtension()方法用于获取当前自适应扩展类实例,首先会从 cachedAdaptiveInstance 对象中获取,如果值为 null 同时 createAdaptiveInstanceError 为空,则调用 createAdaptiveExtension 方法创建扩展类实例。创建完后更新 cachedAdaptiveInstance 。createAdaptiveExtension()private T createAdaptiveExtension() { try { return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { // 省略异常 }}这里有两个方法值得我们关注,injectExtension() 和 getAdaptiveExtensionClass()。injectExtension() 看名字像是一个实现了注入功能的方法,而 getAdaptiveExtensionClass() 则用于获取具体的自适应扩展类。我们依次看下这两个方法。injectExtension()private T injectExtension(T instance) { try { if (objectFactory != null) { for (Method method : instance.getClass().getMethods()) { if (method.getName().startsWith(“set”) && method.getParameterTypes().length == 1 && Modifier.isPublic(method.getModifiers())) { //如果存在 DisableInject 注解则跳过 if (method.getAnnotation(DisableInject.class) != null) { continue; } //获取 method 第一个参数的类型 Class<?> pt = method.getParameterTypes()[0]; try { String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : “”; Object object = objectFactory.getExtension(pt, property); if (object != null) { method.invoke(instance, object); } } catch (Exception e) { logger.error(“fail to inject via method " + method.getName() + " of interface " + type.getName() + “: " + e.getMessage(), e); } } } } } catch (Exception e) { logger.error(e.getMessage(), e); } return instance;}简单的总结下这个方法做了什么:遍历当前实例的 set 方法,以 set 方法第四位开始至末尾的字符串为关键字,尝试通过 objectFactory 来获取对应的 扩展类实现。如果存在对应扩展类,通过反射注入到当前实例中。这个方法相当于完成了一个简单的依赖注入功能,我们常说 Dubbo 中的 IOC 实际上也是在这里体现的。getAdaptiveExtensionClass()private Class<?> getAdaptiveExtensionClass() { getExtensionClasses(); if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } return cachedAdaptiveClass = createAdaptiveExtensionClass();}接着看 getAdaptiveExtensionClass() 方法。首先调用 getExtensionClasses() 方法,如果 cachedAdaptiveClass() 不为 null 则返回,如果为 null 则调用 createAdaptiveExtensionClass() 方法。依次看下这两个方法。getExtensionClasses()private Map<String, Class<?>> getExtensionClasses() { Map<String, Class<?>> classes = cachedClasses.get(); if (classes == null) { synchronized (cachedClasses) { classes = cachedClasses.get(); if (classes == null) { classes = loadExtensionClasses(); cachedClasses.set(classes); } } } return classes;}内容比较简单,我们直接看 loadExtensionClasses() 方法。private Map<String, Class<?>> loadExtensionClasses() { final SPI defaultAnnotation = type.getAnnotation(SPI.class); if (defaultAnnotation != null) { String value = defaultAnnotation.value(); if ((value = value.trim()).length() > 0) { String[] names = NAME_SEPARATOR.split(value); // 只能有一个默认扩展实例 if (names.length > 1) { throw new IllegalStateException(“more than 1 default extension name on extension " + type.getName() + “: " + Arrays.toString(names)); } if (names.length == 1) { cachedDefaultName = names[0]; } } } Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>(); loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName()); loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace(“org.apache”, “com.alibaba”)); loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName()); loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace(“org.apache”, “com.alibaba”)); loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName()); loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace(“org.apache”, “com.alibaba”)); return extensionClasses;}绕来绕去这么久,终于要进入主题了。为什么说进入主题了呢?看看这几个变量的值DUBBO_INTERNAL_DIRECTORY:META-INF/dubbo/internal/DUBBO_DIRECTORY:META-INF/dubbo/SERVICES_DIRECTORY:META-INF/services/熟悉的配方熟悉的料。。没错了,我们马上就要开始读取这三个目录下的文件,然后开始加载我们的扩展点了。loadDirectory()private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) { String fileName = dir + type; try { Enumeration<java.net.URL> urls; ClassLoader classLoader = findClassLoader(); if (classLoader != null) { urls = classLoader.getResources(fileName); } else { urls = ClassLoader.getSystemResources(fileName); } if (urls != null) { while (urls.hasMoreElements()) { java.net.URL resourceURL = urls.nextElement(); loadResource(extensionClasses, classLoader, resourceURL); } } } catch (Throwable t) { // … }}private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) { try { BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), “utf-8”)); try { String line; while ((line = reader.readLine()) != null) { //#对应注释位置,剔除存在的注释 final int ci = line.indexOf(’#’); if (ci >= 0) { line = line.substring(0, ci); } line = line.trim(); if (line.length() > 0) { try { String name = null; //文件中的内容以 key=value 的形式保存,拆分 key 和 vlaue int i = line.indexOf(’=’); if (i > 0) { name = line.substring(0, i).trim(); line = line.substring(i + 1).trim(); } if (line.length() > 0) { loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name); } } catch (Throwable t) { // … } } } } finally { reader.close(); } } catch (Throwable t) { // … }}private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException { // 用于判断 class 是不是 type 接口的实现类 if (!type.isAssignableFrom(clazz)) { throw new IllegalStateException(“Error when load extension class(interface: " + type + “, class line: " + clazz.getName() + “), class " + clazz.getName() + “is not subtype of interface.”); } // 如果当前 class 被 @Adaptive 注解标记,更新 cachedAdaptiveClass 缓存对象 if (clazz.isAnnotationPresent(Adaptive.class)) { if (cachedAdaptiveClass == null) { cachedAdaptiveClass = clazz; } else if (!cachedAdaptiveClass.equals(clazz)) { // 省略异常 } } else if (isWrapperClass(clazz)) { // 这里涉及到了 Dubbo 扩展点的另一个机制:包装,在后文介绍 Set<Class<?>> wrappers = cachedWrapperClasses; if (wrappers == null) { cachedWrapperClasses = new ConcurrentHashSet<Class<?>>(); wrappers = cachedWrapperClasses; } wrappers.add(clazz); } else { clazz.getConstructor(); // 如果 name 为空,调用 findAnnotationName() 方法。如果当前类有 @Extension 注解,直接返回 @Extension 注解value; // 若没有 @Extension 注解,但是类名类似 xxxType(Type 代表 type 的类名),返回值为小写的 xxx if (name == null || name.length() == 0) { name = findAnnotationName(clazz); if (name.length() == 0) { throw new IllegalStateException(“No such extension name for the class " + clazz.getName() + " in the config " + resourceURL); } } String[] names = NAME_SEPARATOR.split(name); if (names != null && names.length > 0) { // @Activate 注解用于配置扩展被自动激活条件 // 如果当前 class 包含 @Activate ,加入到缓存中 Activate activate = clazz.getAnnotation(Activate.class); if (activate != null) { cachedActivates.put(names[0], activate); } else { // support com.alibaba.dubbo.common.extension.Activate com.alibaba.dubbo.common.extension.Activate oldActivate = clazz.getAnnotation(com.alibaba.dubbo.common.extension.Activate.class); if (oldActivate != null) { cachedActivates.put(names[0], oldActivate); } } for (String n : names) { if (!cachedNames.containsKey(clazz)) { cachedNames.put(clazz, n); } Class<?> c = extensionClasses.get(n); if (c == null) { // 还记得文件内容长啥样吗?(name = calssvalue),我们最后将其保存到了 extensionClasses 集合中 extensionClasses.put(n, clazz); } else if (c != clazz) { // … } } } }}这一段代码真的相当长啊。。梳理下之后发现其实他做的事情也很简单:拼接生成文件名:dir + type,读取该文件读取文件内容,将文件内容拆分为 name 和 class 字符串如果 clazz 类中包含 @Adaptive 注解,将其加入到 cachedAdaptiveClass 缓存中 如果 clazz 类中为包装类,添加到 wrappers 中 如果文件不为 key=class 形式,会尝试通过 @Extension 注解获取 name 如果 clazz 包含 @Activate 注解(兼容 com.alibaba.dubbo.common.extension.Activate 注解),将其添加到 cachedActivates 缓存中最后以 name 为 key ,clazz 为 vlaue,将其添加到 extensionClasses 集合中并返回获取自适应扩展类getAdaptiveExtensionClass()private Class<?> getAdaptiveExtensionClass() { getExtensionClasses(); if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } return cachedAdaptiveClass = createAdaptiveExtensionClass();}Ok,我们已经分析了 getExtensionClasses 方法,并且已经将扩展点实现加载到了缓存中。这个方法是由 getAdaptiveExtensionClass() 方法引出来的,它看起来是像是创建自适应扩展类的。这里会先判断缓存对象 cachedAdaptiveClass 是否会空,cachedAdaptiveClass 是什么时候被初始化的呢?回顾一下之前的代码:private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException { // 省略… if (clazz.isAnnotationPresent(Adaptive.class)) { if (cachedAdaptiveClass == null) { cachedAdaptiveClass = clazz; } else if (!cachedAdaptiveClass.equals(clazz)) { // 省略… } }}在 loadClass() 方法中如果发现当前 clazz 包含 @Adaptive 注解,则将当前 clazz 作为缓存自适应类保存。例如在 AdaptiveExtensionFactory 类中就有这么用,我们会将 AdaptiveExtensionFactory 类作为 ExtensionFactory 类型的自适应类缓存起来。@Adaptivepublic class AdaptiveExtensionFactory implements ExtensionFactory我们继续分析该方法的后部分。如果 cachedAdaptiveClass 为 null,则会调用 createAdaptiveExtensionClass() 方法动态生成一个自适应扩展类。private Class<?> createAdaptiveExtensionClass() { String code = createAdaptiveExtensionClassCode(); System.out.println(code); ClassLoader classLoader = findClassLoader(); org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); return compiler.compile(code, classLoader);}这一段代码在本次分享中不打算重点叙述,可以简单的理解为 dubbo 帮我生成了一个自适应类。我摘取了生成的一段代码,如下所示:package org.apache.dubbo.rpc;import org.apache.dubbo.common.extension.ExtensionLoader;public class ProxyFactory$Adaptive implements org.apache.dubbo.rpc.ProxyFactory { private static final org.apache.dubbo.common.logger.Logger logger = org.apache.dubbo.common.logger.LoggerFactory.getLogger(ExtensionLoader.class); private java.util.concurrent.atomic.AtomicInteger count = new java.util.concurrent.atomic.AtomicInteger(0); public org.apache.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, org.apache.dubbo.common.URL arg2) throws org.apache.dubbo.rpc.RpcException { if (arg2 == null) throw new IllegalArgumentException(“url == null”); org.apache.dubbo.common.URL url = arg2; String extName = url.getParameter(“proxy”, “javassist”); if(extName == null) throw new IllegalStateException(“Fail to get extension(org.apache.dubbo.rpc.ProxyFactory) name from url(” + url.toString() + “) use keys([proxy])”); org.apache.dubbo.rpc.ProxyFactory extension = null; try { extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName); }catch(Exception e){ if (count.incrementAndGet() == 1) { logger.warn(“Failed to find extension named " + extName + " for type org.apache.dubbo.rpc.ProxyFactory, will use default extension javassist instead.”, e); } extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(“javassist”); } return extension.getInvoker(arg0, arg1, arg2); } public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0, boolean arg1) throws org.apache.dubbo.rpc.RpcException { if (arg0 == null) throw new IllegalArgumentException(“org.apache.dubbo.rpc.Invoker argument == null”); if (arg0.getUrl() == null) throw new IllegalArgumentException(“org.apache.dubbo.rpc.Invoker argument getUrl() == null”);org.apache.dubbo.common.URL url = arg0.getUrl(); String extName = url.getParameter(“proxy”, “javassist”); if(extName == null) throw new IllegalStateException(“Fail to get extension(org.apache.dubbo.rpc.ProxyFactory) name from url(” + url.toString() + “) use keys([proxy])”); org.apache.dubbo.rpc.ProxyFactory extension = null; try { extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName); }catch(Exception e){ if (count.incrementAndGet() == 1) { logger.warn(“Failed to find extension named " + extName + " for type org.apache.dubbo.rpc.ProxyFactory, will use default extension javassist instead.”, e); } extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(“javassist”); } return extension.getProxy(arg0, arg1); } public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException { if (arg0 == null) throw new IllegalArgumentException(“org.apache.dubbo.rpc.Invoker argument == null”); if (arg0.getUrl() == null) throw new IllegalArgumentException(“org.apache.dubbo.rpc.Invoker argument getUrl() == null”);org.apache.dubbo.common.URL url = arg0.getUrl(); String extName = url.getParameter(“proxy”, “javassist”); if(extName == null) throw new IllegalStateException(“Fail to get extension(org.apache.dubbo.rpc.ProxyFactory) name from url(” + url.toString() + “) use keys([proxy])”); org.apache.dubbo.rpc.ProxyFactory extension = null; try { extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName); }catch(Exception e){ if (count.incrementAndGet() == 1) { logger.warn(“Failed to find extension named " + extName + " for type org.apache.dubbo.rpc.ProxyFactory, will use default extension javassist instead.”, e); } extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(“javassist”); } return extension.getProxy(arg0); }}extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);这一段代码实际上才是自适应适配类的精髓,看看 extName 是怎么来的?String extName = url.getParameter(“proxy”, “javassist”);extName 又是从 url 中取得的,实际上 url 对于 Dubbo 来说是一种非常重要的上下文传输载体,在后续系列文章中大家会逐步感受到。public T getExtension(String name) { if (name == null || name.length() == 0) { throw new IllegalArgumentException(“Extension name == null”); } if (“true”.equals(name)) { return getDefaultExtension(); } // 从缓存中读取扩展实现类 Holder<Object> holder = cachedInstances.get(name); if (holder == null) { cachedInstances.putIfAbsent(name, new Holder<Object>()); holder = cachedInstances.get(name); } Object instance = holder.get(); if (instance == null) { synchronized (holder) { instance = holder.get(); if (instance == null) { instance = createExtension(name); holder.set(instance); } } } return (T) instance;}上面的逻辑比较简单,这里也不赘述了,直接看 createExtension() 方法。private T createExtension(String name) { Class<?> clazz = getExtensionClasses().get(name); if (clazz == null) { throw findException(name); } try { T instance = (T) EXTENSION_INSTANCES.get(clazz); if (instance == null) { EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } injectExtension(instance); Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (wrapperClasses != null && !wrapperClasses.isEmpty()) { for (Class<?> wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } return instance; } catch (Throwable t) { throw new IllegalStateException(“Extension instance(name: " + name + “, class: " + type + “) could not be instantiated: " + t.getMessage(), t); }}getExtensionClasses() 方法在前文已经分析过了,但是需要注意的是:getExtensionClasses 返回给我们的不过是使用 Class.forName() 加载过的类而已,充其量执行了里面的静态代码段,而并非得到了真正的实例。真正的实例对象仍需要调用 class.newInstance() 方法才能获取。 了解了这些之后我们继续看,我们通过 getExtensionClasses() 尝试获取系统已经加载的 class 对象,通过 class 对象再去扩展实例缓存中取。如果扩展实例为 null,调用 newInstance() 方法初始化实例,并放到 EXTENSION_INSTANCES 缓存中。之后再调用 injectExtension() 方法进行依赖注入。最后一段涉及到包装类的用法,下一个章节进行介绍。扩展类的包装在 createExtension() 方法中有如下一段代码:private T createExtension(String name) { // ···省略··· Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (wrapperClasses != null && !wrapperClasses.isEmpty()) { for (Class<?> wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } return instance; // ···省略···}还记得 wrapperClasses 在什么地方被初始化的吗?在前文中的 loadClass() 方法中我们已经有介绍过。再回顾一下:private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException { // ···省略··· if (isWrapperClass(clazz)) { Set<Class<?>> wrappers = cachedWrapperClasses; if (wrappers == null) { cachedWrapperClasses = new ConcurrentHashSet<Class<?>>(); wrappers = cachedWrapperClasses; } wrappers.add(clazz); } // ···省略···}private boolean isWrapperClass(Class<?> clazz) { try { clazz.getConstructor(type); return true; } catch (NoSuchMethodException e) { return false; }}在看这个方法前我们先了解下 Dubbo 中 wrapper 类的定义。举个例子:class A { private A a; public A(A a){ this.a = a; }}我们可以看到 A 类有一个以 A 为参数的构造方法,我们称它为复制构造方法。有这样构造方法的类在 Dubbo 中我们称它为 Wrapper 类。继续看 isWrapperClass() 方法,这个方法比较简单,尝试获取 clazz 中以 type 为参数的构造方法,如果可以获取到,则认为 clazz 则是当前 type 类的包装类。再结合上面的代码,我们会发现在加载扩展点时,我们将对应 type 的包装类缓存起来。private T createExtension(String name) { // ···省略··· T instance = (T) EXTENSION_INSTANCES.get(clazz); if (instance == null) { EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } injectExtension(instance); Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (wrapperClasses != null && !wrapperClasses.isEmpty()) { for (Class<?> wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } return instance; // ···省略···}为了更好的理解这段代码,我们假设当前 type 值为 Protocol.class ,我们可以在 org.apache.dubbo.rpc.Protocol 文件中找到 Protocol 接口的包装类 ProtocolFilterWrapper 和 ProtocolListenerWrapper,他们会依次被添加到 cachedWrapperClasses 集合中。依次遍历 cachedWrapperClasses 集合,比如第一次取到的是 ProtocolFilterWrapper 类,则会以调用 ProtocolFilterWrapper 的复制构造方法将 instance 包装起来。创建完 ProtocolFilterWrapper 对象实例后,调用 injectExtension() 进行依赖注入。此时 instance 已经为 ProtocolFilterWrapper 的实例,继续循环,会将 ProtocolFilterWrapper 类包装在 ProtocolListenerWrapper 类中。因此我们最后返回的是一个 ProtocolListenerWrapper 实例。最后调用时,仍会通过一层一层的调用,最后调用原始 instance 的方法。这里的包装类有点类似 AOP 思想,我们可以通过一层一层的包装,在调用扩展实现之前添加一些日志打印、监控等自定义的操作。Dubbo 中的 IOC 机制上文中我们已经讨论过 Dubbo 中利用反射机制实现一个类 IOC 功能。在这一章节中,我们再回顾一下 injectExtension() 方法,仔细的来看看 Dubbo 中 IOC 功能的实现。createExtension()instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));private T injectExtension(T instance) { // ··· Class<?> pt = method.getParameterTypes()[0]; try { String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : “”; Object object = objectFactory.getExtension(pt, property); if (object != null) { method.invoke(instance, object); } } // ···}public class StubProxyFactoryWrapper implements ProxyFactory { // … private Protocol protocol; public void setProtocol(Protocol protocol) { this.protocol = protocol; } //…}在上一章节中我们已经讲过 wrapper 类,在这里我们举个例子说明一下。比如我们当前的 wrapperClass 类为 StubProxyFactoryWrapper,那么代码执行逻辑大致如下所示:创建 StubProxyFactoryWrapper 实例;获取流程1创建的实例作为 injectExtension() 的参数,执行;injectExtension() 方法循环遍历到 StubProxyFactoryWrapper 的 setProtocol()方法(此时 pt=Protocol.class,property=protocol),执行 objectFactory.getExtension(pt,property) 方法。objectFactory 在 ExtensionLoader 的构造方法中被初始化,在这里获取到自适应扩展类为 AdaptiveExtensionFactory。执行 AdaptiveExtensionFactory.getExtension()。AdaptiveExtensionFactory 类中有一个集合变量 factories。factories 在 AdaptiveExtensionFactory 的构造方法中被初始化,包含了两个工厂类:SpiExtensionFactory、SpringExtensionFactory。执行 AdaptiveExtensionFactory 类的 getExtension() 方法会依次调用 SpiExtensionFactory 和 SpringExtensionFactory 类的 getExtension() 方法。执行 SpiExtensionFactory 的 getExtension() 方法。上面有说到此时的 type=Procotol.class,property=protocol,从下面的代码我们可以发现 Protocol 是一个接口类,同时标注了 @SPI 注解,此时会获取 Protocol 类型的 ExtensionLoader 对象,最后又去调用 loader 的 getAdaptiveExtension() 方法。最终获取到的自适应类为 Protocol$Adaptive 动态类。objectFactory.getExtension(pt, property); 最后得到的类为 Protocol$Adaptive 类,最后利用反射机制将其注入到 StubProxyFactoryWrapper 实例中。@SPI(“dubbo”)public interface Protocol {}public class SpiExtensionFactory implements ExtensionFactory { @Override public <T> T getExtension(Class<T> type, String name) { if (type.isInterface() && type.isAnnotationPresent(SPI.class)) { ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type); if (!loader.getSupportedExtensions().isEmpty()) { return loader.getAdaptiveExtension(); } } return null; }}END在最后,我们再回顾下开头关于 Dubbo SPI 基于 JAVA SPI 改进的那段话:Dubbo 的扩展点加载从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来。JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。总结如下:Dubbo SPI 在加载扩展点,会以 key-value 的形式将扩展类保存在缓存中,但此时的扩展类只是调用 Class.forName() 加载的类,并没有实例化。扩展类会在调用 getExtension() 方法时被实例化。Dubbo 通过工厂模式和反射机制实现了依赖注入功能。Dubbo 中通过包装类实现了 AOP 机制,方便我们添加监控和打印日志。本BLOG上原创文章未经本人许可,不得用于商业用途及传统媒体。网络媒体转载请注明出处,否则属于侵权行为。https://juejin.im/post/5c0cd7… ...

December 25, 2018 · 10 min · jiezi

微服务配置中心实战:Spring + MyBatis + Druid + Nacos

在结合场景谈服务发现和配置中我们讲述了 Nacos 配置中心的三个典型的应用场景,包括如何在 Spring Boot 中使用 Nacos 配置中心将数据库连接信息管控起来,而在“原生”的 Spring 中可以怎么使用 Nacos 配置中心呢?很多基于 Spring MVC 框架的 Web 开发中,Spring + MyBatis + Druid 是一个黄金组合,在此基础上融入 Nacos 配置中心,将会发生什么特别的变化呢?本文将通过一个用户信息查询示例,演示在 Spring Web 项目中如何将数据库连接池的配置存放到 Nacos 中,统一运维管控,达到配置治理与降低数据泄露风险的目的。数据表在测试数据库中新建 user 表,其中包含用户名称等字段,与接下来的 User model 类相对应。CREATE TABLE user ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, name varchar(10) NOT NULL DEFAULT ’’ COMMENT ‘名字’, create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT ‘创建时间’, update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT ‘更新时间’, PRIMARY KEY (id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=‘用户表’;添加一行测试数据:INSERT INTO user (name, create_time, update_time) VALUES (‘Nacos’, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);Spring该示例是 Spring 常规的 Web 项目,项目结构如下:pom.xml引入 Nacos Spring 的依赖包 nacos-spring-context:<dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-spring-context</artifactId> <version>${latest.version}</version></dependency>笔者在撰写本文时,nacos-spring-context 的最新版本为:0.2.2-RC1dispatcher-servlet.xmldispatcher-servlet.xml 为示例中 Spring MVC 的入口配置,在其中通过 import 引入了 Nacos、Druid、MyBatis 的配置,其内容如下:<?xml version=“1.0” encoding=“UTF-8”?><beans xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance" xmlns:context=“http://www.springframework.org/schema/context" xmlns:mvc=“http://www.springframework.org/schema/mvc" xmlns=“http://www.springframework.org/schema/beans" xsi:schemaLocation=“http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <mvc:annotation-driven/> <context:annotation-config/> <context:component-scan base-package=“com.alibaba.nacos.example.spring”/> <import resource=“nacos.xml”/> <import resource=“datasource.xml”/> <import resource=“spring-config-mybatis.xml”/></beans>nacos.xml关键看 nacos.xml ,nacos-spring-context 的扩展了 Spring 的 XML Schema 机制,自定义了 <nacos:property-source/> 等元素,详细配置内容如下:<?xml version=“1.0” encoding=“UTF-8”?><beans xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance" xmlns=“http://www.springframework.org/schema/beans" xmlns:nacos=“http://nacos.io/schema/nacos" xsi:schemaLocation=“http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://nacos.io/schema/nacos http://nacos.io/schema/nacos.xsd"> <nacos:global-properties server-addr=“127.0.0.1:8848” /> <nacos:property-source data-id=“datasource.properties”/></beans>其中通过 <nacos:global-properties /> 设置 Nacos Server 的连接地址,通过 <nacos:property-source /> 从 Nacos 配置中心加载了 dataId 为 datasource.properties 的配置,nacos-spring-context 会解析获取到的配置内容并添加到 Spring Environment 的 PropertySources 中,使得后续初始化 Druid 连接池的时候能获取到数据库连接地址、账号密码、初始连接池大小等信息。这就是 Nacos 配置中心与 Spring 结合的关键点。datasource.xml这是数据库连接池的配置,初始化了 DruidDataSource 的 Spring Bean,并将其通过 DataSourceTransactionManager 设置为 Spring 的数据库连接池。<?xml version=“1.0” encoding=“UTF-8”?><beans xmlns=“http://www.springframework.org/schema/beans" xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance" xmlns:tx=“http://www.springframework.org/schema/tx" xsi:schemaLocation=“http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id=“dataSource” class=“com.alibaba.druid.pool.DruidDataSource” init-method=“init” destroy-method=“close”> <property name=“url” value="${datasource.url}”/> <property name=“username” value="${datasource.username}”/> <property name=“password” value="${datasource.password}”/> <property name=“initialSize” value="${datasource.initial-size}”/> <property name=“maxActive” value="${datasource.max-active}”/> </bean> <bean id=“txManager” class=“org.springframework.jdbc.datasource.DataSourceTransactionManager”> <property name=“dataSource” ref=“dataSource”/> </bean> <tx:annotation-driven transaction-manager=“txManager”/></beans>从上面的配置内容看一看到,数据库连接池不需要因为引入 Nacos 配置中做任何特殊的改变。其他User 的 Model、Service 等也跟不使用 Nacos 配置中心时完全一致,这里就不一一贴出,完整示例代码可以在 nacos-examples 获取:https://github.com/nacos-group/nacos-examples/tree/master/nacos-spring-example/nacos-spring-config-datasource-exampleNacos参照 Nacos 官网的快速开始:https://nacos.io/zh-cn/docs/quick-start.html从 Nacos 的 github 上下载最新稳定版本的 nacos-server:https://github.com/alibaba/nacos/releases解压后到 Nacos 的 bin 目录下,执行 startup 启动脚本以单机模式启动 Nacos Server, Windows 请使用 cmd startup.cmd ,Linux/Unix/Mac 环境请使用 sh startup.sh -m standalone 。启动后,浏览器访问:http://localhost:8848/nacos/index.html 就可以来到 Nacos 的控制台,新增 dataId 为 datasource.properties 的配置,对应上面 Spring 的 nacos.xml 中的 dataId。配置内容则与 Spring 的 datasource.xml 中的连接池属性一一对应,示例如下:datasource.url=jdbc:mysql://localhost:3306/testdatasource.username=rootdatasource.password=rootdatasource.initial-size=1datasource.max-active=20运行示例中是 UserController#get() 通过 UserServce 调用 Mybatis 的 Mapper 类(UserMapper)从数据库中查询指定 ID 的用户信息,假设该示例是运行在端口为 8080 的 Tomcat 上,访问:http://localhost:8080/users?id=1 地址将返回:{ “id”: 1, “name”: “Nacos”}总结本文通过一个示例演示在“原生” Spring 中如何使用 Nacos 配置中心,从示例可以看出,对原有的 Spring 项目基本没有任何侵入,只需在 pom.xml 中添加 nacos-spring-context 的依赖,然后再定义并引入 nacos.xml 配置,就可以将数据库连接池信息管控起来,做到统一运维,并降低数据泄露的风险。试想,如果你有多个项目连接同一个数据库或一个项目部署很多实例,当数据库密码修改时,你不需要去修改每个项目的 datasource.properties 文件,再走一次应用的部署发布流程,而是到 Nacos 的控制台上修改一个配置项,再去重启应用实例即可。当然,如果你是自己管理数据库连接池,则可以做到连“重启应用实例”都不需要了,只需在监听到 Nacos 配置变化时重新初始化数据库连接池即可。将 Spring 配置放置到 Nacos 配置中,还能用上“动态推送”、“版本管理”、“快速回滚”、“监听查询”,以及后续的 “灰度发布”、“配置加密”、“权限管控”等功能,为 Spring + MyBatis + Druid 插上“飞翔”的翅膀。作为 Nacos 配置中心的阿里云免费产品 ACM:https://www.aliyun.com/product/acm,已经提供了上面所有的功能,如果您不想自己部署运维 Nacos Server 或者想使用“推送轨迹”、“细粒度权限控制”、“ECS 实例 RAM 角色”等高级特性,不妨尝试下免费的 ACM。完整示例代码:Nacos: https://github.com/nacos-group/nacos-examples/tree/master/nacos-spring-example/nacos-spring-config-datasource-exampleACM: https://github.com/nacos-group/nacos-examples/tree/acm/nacos-spring-example/nacos-spring-config-datasource-example本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

December 24, 2018 · 2 min · jiezi

云栖专辑 | 阿里开发者们的第3个感悟:从身边开源开始学习,用过才能更好理解代码

2015年12月20日,云栖社区上线。2018年12月20日,云栖社区3岁。阿里巴巴常说“晴天修屋顶”。在我们看来,寒冬中,最值得投资的是学习,是增厚的知识储备。所以社区特别制作了这个专辑——分享给开发者们20个弥足珍贵的成长感悟,50本书单。多年以后,再回首2018-19年,留给我们自己的,除了寒冷,还有不断上升的技术能力与拼搏后的成就感。*12月24日,从身边熟悉的开源系统开始,用过才能更好理解代码。这是我们送给开发者的第3个感悟。正研,社区HBase社群大V。在他的博文中,可以清晰看到经验的积累。正研:开源改变世界杨文龙(正研)阿里巴巴存储技术事业部技术专家Ali-HBase内核研发负责人ApacheHBase社区Committer&PMC成员对分布式存储系统的设计、实践具备丰富的大规模生产的经验有些人一直想去学习热门开源软件的代码,但其实不如从身边熟悉的开源系统开始;因为只有用过,才能更好地理解代码,只有带着实际生产的问题去看,才能明白为什么要这样去设计架构。反过来,只有从源码级理解这个系统,才能在使用过程中避免采坑。推荐的书单《深入理解计算机系统》《HeadFirst设计模式》*12月21日,使命感与开放心态,是我们送给开发者的第2个感悟。德歌:公益是一辈子的事,I’m digoal, just do it德歌,江湖人称德哥。PG大神,在社区拥有6500+位粉丝。三年来,他沉淀在社区的博文超过2000+篇。还记得社区刚成立时,有位开发者在博文后留言“我一直认为PG是小众数据库,没想到社区有这么多干货。” 三年过去,PG的地位一直在上升,云栖社区PG钉群也已经超过1000位开发者在一起交流讨论。*周正中(德歌)PostgreSQL 中国社区发起人之一,PostgreSQL 象牙塔发起人之一,DBA+社群联合发起人之一,10余项数据库相关专利,现就职于阿里云数据库内核技术组。学习第一要有使命感,第二要有开放的心态。使命感是技术为业务服务,结合业务一起创造社会价值,有使命感才能让你坚持下去,遇到困难时不容易被打倒。开放是在扎实的技术功底之上,跳出纯粹的技术从生态进行思考,要埋头苦干也要抬头看路。比如行业生态中重叠部分,盟友与竞争关系,问题及补齐办法等,同时也要密切关注国家和国际形势,分析背后原因,在未来技术方向决策上避免逆流行舟。推荐的书单:《PostgreSQL实战》*12月20日,场景中学习,这是我们送给开发者的第1个感悟。阿里毕玄:程序员的成长路线在这篇《程序员的成长路线》里,阿里基础设施负责人毕玄结合自己的经历跟大家讲述了他在各个角色上成长的感受。在他的职业经历中,在成长方面经历了技术能力的成长、架构能力的成长,以及现在作为一个在修炼中的技术 Leader 的成长。其中技术能力和架构能力的成长是所有程序员都很需要的,值得所有正为职业发展而迷茫的技术同学细细品味。*林昊(毕玄)阿里基础设施负责人阿里巴巴HSF、T4创始人,HBase负责人主导阿里电商分布式应用架构、异地多活架构、资源弹性架构升级程序员,要寻找甚至创造场景来学习相应的技术能力。正如学Java通讯框架,尝试基于BIO/NIO写一个,然后对比Mina/Netty,看看为什么不一样;学Java的内存管理,尝试写程序去控制GC的行为。书籍外,更建议翻看源码,结合场景才能真正理解和学会。我的职业经历是技术能力成长、架构能力成长和正在修炼中的技术Leader的成长,三条路线都可发展,没有孰优孰劣,兴趣、个人优势仍是最重要的。出版的图书:《OSGi原理与最佳实践》《分布式Java应用:基础与实践》推荐的书单:《硅谷之谜》《智能时代:大数据与智能革命重新定义未来》预计更新到1月20日,欢迎收藏。本文作者:云篆阅读原文本文为云栖社区原创内容,未经允许不得转载。

December 24, 2018 · 1 min · jiezi

XXL-CONF v1.6.1,分布式配置管理平台

Release Notes1、在未设置accessToken情况下,非法请求恶意构造配置Key可遍历读取文件漏洞修复;(From:360代码卫士团队)2、项目名正则校验问题修复,项目名中划线分隔,配置点分隔;3、底层HTTP工具类优化;4、RESTFUL 接口格式调整,改为POST请求,兼容大数据量配置请求;5、配置Key合法性校验逻辑优化,非法Key服务端自动过滤,避免阻塞正常配置的查询加载;6、升级pom依赖至较新版本;简介XXL-CONF 是一个轻量级分布式配置管理平台,拥有"轻量级、秒级动态推送、多环境、跨语言、跨机房、配置监听、权限控制、版本回滚"等特性。现已开放源代码,开箱即用。特性1、简单易用: 接入灵活方便,一分钟上手;2、轻量级: 部署简单,不依赖第三方服务,一分钟上手;3、配置中心HA:配置中心支持集群部署,提升配置中心系统容灾和可用性。4、在线管理: 提供配置中心, 通过Web界面在线操作配置数据,直观高效;5、多环境支持:单个配置中心集群,支持自定义多套环境,管理多个环境的的配置数据;环境之间相互隔离;6、多数据类型配置:支持多种数据类型配置,如:String、Boolean、Short、Integer、Long、Float、Double 等;7、跨语言:底层通过http服务(long-polling)拉取配置数据并实时感知配置变更,从而实现多语言支持。8、跨机房:得益于配置中心集群关系对等特性,集群各节点提供幂等的配置服务;因此,异地跨机房部署时,只需要请求本机房配置中心即可,实现异地多活;9、高性能:得益于配置中心的 “磁盘配置” 与客户端的 “LocalCache”,因此配置服务性能非常高;单机可承担大量配置请求;10、实时性: 秒级动态推送;配置更新后, 实时推送配置信息, 项目中配置数据会实时更新并生效, 不需要重启线上机器;11、配置变更监听功能:可开发Listener逻辑,监听配置变更事件,可据此动态刷新JDBC连接池等高级功能;12、最终一致性:底层借助内置广播机制,保障配置数据的最终一致性,从而保证配置数据的同步;13、配置备份: 配置数据同时在磁盘与MySQL中存储和备份,并定期同步, 提高配置数据的安全性;14、多种获取配置方式:支持 “API、 注解、XML占位符” 等多种方式获取配置,可灵活选择使用;15、兼容Spring原生配置:兼容Spring原生配置方式 “@Value”、"${}" 加载本地配置功能;与分布式配置获取方式隔离,互不干扰;16、分布式: 支持多业务线接入并统一管理配置信息,支撑分布式业务场景;17、项目隔离: 以项目为维度管理配置, 方便隔离不同业务线配置;18、高性能: 通过LocalCache对配置数据做缓存, 提高性能;19、客户端断线重连强化:设置守护线程,周期性检测客户端连接、配置同步,提高异常情况下配置稳定性和时效性;20、空配置处理:主动缓存null或不存在类型配置,避免配置请求穿透到远程配置Server引发雪崩问题;21、用户管理:支持在线添加和维护用户,包括普通用户和管理员两种类型用户;22、配置权限控制;以项目为维度进行配置权限控制,管理员拥有全部项目权限,普通用户只有分配才拥有项目下配置的查看和管理权限;23、历史版本回滚:记录配置变更历史,方便历史配置版本回溯,默认记录10个历史版本;24、配置快照:客户端从配置中心获取到的配置数据后,会周期性缓存到本地快照文件中,当从配置中心获取配置失败时,将会使用使用本地快照文件中的配置数据;提高系统可用性;25、访问令牌(accessToken):为提升系统安全性,配置中心和客户端进行安全性校验,双方AccessToken匹配才允许通讯;文档地址中文文档技术交流社区交流

December 21, 2018 · 1 min · jiezi

云栖专辑|阿里开发者们的第二个感悟:PG大V德哥的使命感与开放心态

摘要: 2018年12月20日,云栖社区3岁。阿里巴巴常说“晴天修屋顶”,所以我们特别制作了这个专辑——分享给开发者们20个阿里故事,50本书籍。2015年12月20日,云栖社区上线。2018年12月20日,云栖社区3岁。阿里巴巴常说“晴天修屋顶”。在我们看来,寒冬中,最值得投资的是学习,是增厚的知识储备。所以社区特别制作了这个专辑——分享给开发者们20个弥足珍贵的成长感悟,50本书单。多年以后,再回首2018-19年,留给我们自己的,除了寒冷,还有不断上升的技术能力与拼搏后的成就感。*12月21日,使命感与开放心态,是我们送给开发者的第2个感悟。德歌:公益是一辈子的事,I’m digoal, just do it德歌,江湖人称德哥。PG大神,在社区拥有6500+位粉丝。三年来,他沉淀在社区的博文超过2000+篇。还记得社区刚成立时,有位开发者在博文后留言“我一直认为PG是小众数据库,没想到社区有这么多干货。” 三年过去,PG的地位一直在上升,云栖社区PG钉群也已经超过1000位开发者在一起交流讨论。*周正中(德歌)PostgreSQL 中国社区发起人之一,PostgreSQL 象牙塔发起人之一,DBA+社群联合发起人之一,10余项数据库相关专利,现就职于阿里云数据库内核技术组。学习第一要有使命感,第二要有开放的心态。使命感是技术为业务服务,结合业务一起创造社会价值,有使命感才能让你坚持下去,遇到困难时不容易被打倒。开放是在扎实的技术功底之上,跳出纯粹的技术从生态进行思考,要埋头苦干也要抬头看路。比如行业生态中重叠部分,盟友与竞争关系,问题及补齐办法等,同时也要密切关注国家和国际形势,分析背后原因,在未来技术方向决策上避免逆流行舟。推荐的书单:《PostgreSQL实战》本文作者:云篆阅读原文本文为云栖社区原创内容,未经允许不得转载。

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

阿里毕玄:程序员的成长路线

摘要: 阿里基础设施负责人毕玄结合自己的经历跟大家讲述了他在各个角色上成长的感受,值得所有正为职业发展而迷茫的技术同学细细品味。【编者按】2018年12月20日,云栖社区3周岁生日。阿里巴巴常说“晴天修屋顶”,所以我们特别策划了这个专辑——分享给开发者们20个阿里故事,50本书籍。第一位是林昊(毕玄)。在这篇《程序员的成长路线》里,阿里基础设施负责人毕玄结合自己的经历跟大家讲述了他在各个角色上成长的感受。在他的职业经历中,在成长方面经历了技术能力的成长、架构能力的成长,以及现在作为一个在修炼中的技术 Leader 的成长。其中技术能力和架构能力的成长是所有程序员都很需要的,值得所有正为职业发展而迷茫的技术同学细细品味。技术能力成长我大学读的是生物系,缺少了专业的训练,这个使得我在技术能力上其实欠缺的更多。回头想想,在工作的前5年,更多的都是在拓宽技术面,刚毕业的时候只会 ASP,工作前两年学会了 VB、Delphi这些神器,到工作的第三、四年比较专注的做了工作流领域。技术能力的成长主要还是在 2007 年加入阿里以后,在加入阿里前,我是一个连日均访问量 1万 PV 都没见过的人,到了阿里后,做的第一件事竟然就是写 HSF,并且在客服的 CRM 系统上线,访问量大概是每天上百万的服务调用,无知者无畏,当时也就那么上线了,更神奇的是竟然没出现什么问题,于是继续把HSF上线到当时的交易中心,当时交易中心每天的服务调用量大概是亿级,结果上线当天就回滚了,而且还不知道到底是什么原因,这次的回滚是对我触动最大的一次(当然,触动大也有可能是后面要是解决不了,就该从淘宝滚蛋了)。回滚后开始仔细查问题,最后发现是当时 HSF 所使用的 jboss-remoting 默认的超时参数 60s 的问题,自从这个问题后,才明白要支撑好到了一定量级的系统,最重要的是对整个技术栈的精通,否则出问题都不知道该怎么解决或临时查,于是才开始仔细学习 Java 的 BIO/NIO,Mina,反射,并发编程等,尽管这些东西很多在加入阿里前也看过一些书、资料学过,但到了这个时候才发现自己其实不怎么懂,那段时间密集的开始更细致的看书,翻看用到的 Mina、甚至是 Java 的各种 API 背后的源码,是自己的 Java 技能提升最快的一段时间,在回滚的两个月后,基于 Mina 完全重写了 HSF,再次上线终于一切顺利。在那之后,随着 HSF 应用的场景越来越多,以及加上后来自己在淘宝消防队查比较多的问题,Java 方面的技能也得到了不少成长,而同时也发现了很多的 Java 问题还得对 JVM、操作系统层面有一定掌握才行,尤其是 JVM,于是当时和还在阿里的撒迦经常一起周末跑到公司来结对看 JVM 代码,:)。在撒迦的帮助下对 JVM 的掌握终于也越来越好,那段时光会让自己明白很多东西只有看了代码,并且有相应的使用机会才能真正的掌握。在 HSF 之后,去做 HBase,学习了很多在存储方面的技能,这也是我之前完全不懂的领域,在HBase之后,开始做第一代容器产品 T4(寓意是第四代淘宝技术),进入彻底不懂的领域,虚拟化、Cgroup 等等都是那个时候才开始学习,但因为没详细研究过代码,并自己去做改造,其实到今天也就是点皮毛而已。对于程序员而言,技术能力的成长显然是最重要的(程序员行当里最赞的一句就是:Talk is cheap, show me the code!),我自己其实很多都属于被逼的成长,当然这样通常反而也是最快的,很多同学会觉得自己没碰到这样的机会,所以成长就比较慢,我会非常建议的是可以尝试自己去创造一些场景(当然,如果本来就是工作需要就更好了),来学相应的技术能力(例如学 Java 的通讯框架,可以尝试自己基于 BIO/NIO 写一个,然后对比 Mina/Netty 这些成熟的,看看为什么写的不太一样,又例如学 Java 的内存管理,可以尝试自己写程序去控制 GC 的行为,例如先来一次 ygc,再来两次 fgc,再来 5 次 ygc,再来一次 fgc之类的,学的时候除了一些入门的书外,我非常建议去翻看源码,最后你会发现所有的书都不如源码),这样才能真正的理解和学会,否则其实很容易忘。架构能力成长说起架构,在我刚工作的第三年负责工作流系统的时候也做过,但直到后来在阿里做 T4、异地多活,我才有了真正更强烈的感受,对架构师也有更深的一些理解。架构呢,我现在的理解基本是一个结构图,当然有不同视角的结构,但这个图里的部分呢是多个团队来做的,甚至是跨多个专业的团队。在做 T4 的时候,由于 T4 涉及到了标准的一个 Java WebConsole,一堆的运维体系,容器技术等,这是一个至少要跨三个团队的结构,无论是从研发视角还是部署视角都是如此,因此作为 T4 的架构师,怎么设计好整个的结构,各自的边界、接口是我当时最大的感受,让跨专业的多个团队能更好的协作,在这个阶段中最重要的要考虑的是怎么根据整个项目的优先级来调整每个部分,以及作为一个不是全懂的架构师怎么更好的确保结果,我自己的感受是 T4 让我学会了从一个只做自己专业系统的架构师成长为了能做跨专业的系统的架构师。在做异地多活的时候,感受就更加强烈,因为这个跨的专业数、整个参与的人数完全是上升到了一个非常大的程度,各个专业、系统的人都需要看整个架构才能知道自己应该做什么,扮演的角色,在做异地多活整个项目过程中,作为总的架构师,我自己感觉的是最重要的职责是怎么控制项目的风险,或者说作为架构师,你觉得一个项目中最重要的要掌控住的是,并且从架构上怎么设计这个部分,这也是后来我在问很多架构师时最喜欢问的问题,一份架构文档不是说按照模板写就可以(很多的架构设计文档都是千篇一律,通常看到的都是什么都考虑,但从架构设计上并没体现这些考虑的地方是怎么做的),而是要根据实际的项目/产品情况来突出重点,确保最重要的几个问题是从架构设计上就去掌控的,尤其是跨多个专业团队的大型项目,这种项目准确的说是大架构师带着一堆的专业领域的架构师来做的,例如异地多活项目从架构设计上来说除了正常的结构、边界以外,最重要的是数据正确性的设计,我自己最强的感受就是异地多活才让我明白了一个大型系统的架构师是怎么样的。所以就我自己的感受而言,架构师对知识的宽要求非常广,并且要能非常好的进行抽象,来做结构、边界的设计,分析出当前阶段系统的重点,并从架构层面做好设计来确保重点的实现,这个相对技术能力的成长而言我觉得更需要机会,但同样在机会前需要有足够的积累(例如写一个系统的时候,是不是主动的去了解过上下游的系统设计,是不是了解过具体的部署结构,对相应的知识点有没有简单的了解等,我自己在做 T4 前,LVS、机房/网络结构等完全搞不懂是怎么回事)。技术Leader修炼技术 Leader 我比较倾向于有前面两步积累的同学,技术 Leader 非常重要的一点是对技术趋势的感知和判断能力,这其实是个非常综合的能力,一到两个领域的技术深度,大的架构能力,对技术历程的理解、技术发展的思考能力,作为技术 Leader 是很需要的,然后是其他的一些作为 Leader 方面的比较综合的一些能力(例如组织搭建、建设方面的能力等,不过这些能力呢通常对技术的人来说确实会欠缺的更多一些),这个我自己还在修炼和学习中,就不讲太多了。总结总结来说呢,我认为程序员可发展的路线还是很多的,上面写的这三条其实都是可发展的路线,没有孰优孰劣,谁高一等之类的,兴趣、个人优势仍然是最重要的。*作为《OSGi原理与最佳实践》和《分布式Java应用:基础与实践》的作者,毕玄推荐了他的书单给到我们:《硅谷之谜》《智能时代:大数据与智能革命重新定义未来》本文作者:云篆阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

December 20, 2018 · 1 min · jiezi

新手也能看懂,消息队列其实很简单

该文已加入开源项目:JavaGuide(一份涵盖大部分Java程序员所需要掌握的核心知识的文档类项目,Star 数接近 16k)。地址:https://github.com/Snailclimb/JavaGuide.本文内容思维导图:消息队列其实很简单 “RabbitMQ?”“Kafka?”“RocketMQ?”…在日常学习与开发过程中,我们常常听到消息队列这个关键词。我也在我的多篇文章中提到了这个概念。可能你是熟练使用消息队列的老手,又或者你是不懂消息队列的新手,不论你了不了解消息队列,本文都将带你搞懂消息队列的一些基本理论。如果你是老手,你可能从本文学到你之前不曾注意的一些关于消息队列的重要概念,如果你是新手,相信本文将是你打开消息队列大门的一板砖。一 什么是消息队列 我们可以把消息队列比作是一个存放消息的容器,当我们需要使用消息的时候可以取出消息供自己使用。消息队列是分布式系统中重要的组件,使用消息队列主要是为了通过异步处理提高系统性能和削峰、降低系统耦合性。目前使用较多的消息队列有ActiveMQ,RabbitMQ,Kafka,RocketMQ,我们后面会一一对比这些消息队列。 另外,我们知道队列 Queue 是一种先进先出的数据结构,所以消费消息时也是按照顺序来消费的。比如生产者发送消息1,2,3…对于消费者就会按照1,2,3…的顺序来消费。但是偶尔也会出现消息被消费的顺序不对的情况,比如某个消息消费失败又或者一个 queue 多个consumer 也会导致消息被消费的顺序不对,我们一定要保证消息被消费的顺序正确。 除了上面说的消息消费顺序的问题,使用消息队列,我们还要考虑如何保证消息不被重复消费?如何保证消息的可靠性传输(如何处理消息丢失的问题)?……等等问题。所以说使用消息队列也不是十全十美的,使用它也会让系统可用性降低、复杂度提高,另外需要我们保障一致性等问题。二 为什么要用消息队列 我觉得使用消息队列主要有两点好处:1.通过异步处理提高系统性能(削峰、减少响应所需时间);2.降低系统耦合性。如果在面试的时候你被面试官问到这个问题的话,一般情况是你在你的简历上涉及到消息队列这方面的内容,这个时候推荐你结合你自己的项目来回答。 《大型网站技术架构》第四章和第七章均有提到消息队列对应用性能及扩展性的提升。(1) 通过异步处理提高系统性能(削峰、减少响应所需时间) 如上图,在不使用消息队列服务器的时候,用户的请求数据直接写入数据库,在高并发的情况下数据库压力剧增,使得响应速度变慢。但是在使用消息队列之后,用户的请求数据发送给消息队列之后立即 返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库。由于消息队列服务器处理速度快于数据库(消息队列也比数据库有更好的伸缩性),因此响应速度得到大幅改善。 通过以上分析我们可以得出消息队列具有很好的削峰作用的功能——即通过异步处理,将短时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。 举例:在电子商务一些秒杀、促销活动中,合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。如下图所示: 因为用户请求数据写入消息队列之后就立即返回给用户了,但是请求数据在后续的业务校验、写数据库等操作中可能失败。因此使用消息队列进行异步处理之后,需要适当修改业务流程进行配合,比如用户在提交订单之后,订单数据写入消息队列,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,甚至出库后,再通过电子邮件或短信通知用户订单成功,以免交易纠纷。这就类似我们平时手机订火车票和电影票。(2) 降低系统耦合性 我们知道如果模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响较小,这样系统的可扩展性无疑更好一些。 我们最常见的事件驱动架构类似生产者消费者模式,在大型网站中通常用利用消息队列实现事件驱动结构。如下图所示: 消息队列使利用发布-订阅模式工作,消息发送者(生产者)发布消息,一个或多个消息接受者(消费者)订阅消息。 从上图可以看到消息发送者(生产者)和消息接受者(消费者)之间没有直接耦合,消息发送者将消息发送至分布式消息队列即结束对消息的处理,消息接受者从分布式消息队列获取该消息后进行后续处理,并不需要知道该消息从何而来。对新增业务,只要对该类消息感兴趣,即可订阅该消息,对原有系统和业务没有任何影响,从而实现网站业务的可扩展性设计。 消息接受者对消息进行过滤、处理、包装后,构造成一个新的消息类型,将消息继续发送出去,等待其他消息接受者订阅该消息。因此基于事件(消息对象)驱动的业务架构可以是一系列流程。 另外为了避免消息队列服务器宕机造成消息丢失,会将成功发送到消息队列的消息存储在消息生产者服务器上,等消息真正被消费者服务器处理后才删除消息。在消息队列服务器宕机后,生产者服务器会选择分布式消息队列服务器集群中的其他服务器发布消息。备注: 不要认为消息队列只能利用发布-订阅模式工作,只不过在解耦这个特定业务环境下是使用发布-订阅模式的。除了发布-订阅模式,还有点对点订阅模式(一个消息只有一个消费者),我们比较常用的是发布-订阅模式。 另外,这两种消息模型是 JMS 提供的,AMQP 协议还提供了 5 种消息模型。三 使用消息队列带来的一些问题系统可用性降低: 系统可用性在某种程度上降低,为什么这样说呢?在加入MQ之前,你不用考虑消息丢失或者说MQ挂掉等等的情况,但是,引入MQ之后你就需要去考虑了!系统复杂性提高: 加入MQ之后,你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等等问题!一致性问题: 我上面讲了消息队列可以实现异步,消息队列带来的异步确实可以提高系统响应速度。但是,万一消息的真正消费者并没有正确消费消息怎么办?这样就会导致数据不一致的情况了!四 JMS VS AMQP4.1 JMS4.1.1 JMS 简介 JMS(JAVA Message Service,java消息服务)是java的消息服务,JMS的客户端之间可以通过JMS服务进行异步的消息传输。JMS(JAVA Message Service,Java消息服务)API是一个消息服务的标准或者说是规范,允许应用程序组件基于JavaEE平台创建、发送、接收和读取消息。它使分布式通信耦合度更低,消息服务更加可靠以及异步性。ActiveMQ 就是基于 JMS 规范实现的。4.1.2 JMS两种消息模型①点到点(P2P)模型 使用队列(Queue)作为消息通信载体;满足生产者与消费者模式,一条消息只能被一个消费者使用,未被消费的消息在队列中保留直到被消费或超时。比如:我们生产者发送100条消息的话,两个消费者来消费一般情况下两个消费者会按照消息发送的顺序各自消费一半(也就是你一个我一个的消费。)② 发布/订阅(Pub/Sub)模型 发布订阅模型(Pub/Sub) 使用主题(Topic)作为消息通信载体,类似于广播模式;发布者发布一条消息,该消息通过主题传递给所有的订阅者,在一条消息广播之后才订阅的用户则是收不到该条消息的。4.1.3 JMS 五种不同的消息正文格式 JMS定义了五种不同的消息正文格式,以及调用的消息类型,允许你发送并接收以一些不同形式的数据,提供现有消息格式的一些级别的兼容性。StreamMessage – Java原始值的数据流MapMessage–一套名称-值对TextMessage–一个字符串对象ObjectMessage–一个序列化的 Java对象BytesMessage–一个字节的数据流4.2 AMQP AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准 高级消息队列协议(二进制应用层协议),是应用层协议的一个开放标准,为面向消息的中间件设计,兼容 JMS。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件同产品,不同的开发语言等条件的限制。RabbitMQ 就是基于 AMQP 协议实现的。4.3 JMS vs AMQP对比方向JMSAMQP定义Java API协议跨语言否是跨平台否是支持消息类型提供两种消息模型:①Peer-2-Peer;②Pub/sub提供了五种消息模型:①direct exchange;②fanout exchange;③topic change;④headers exchange;⑤system exchange。本质来讲,后四种和JMS的pub/sub模型没有太大差别,仅是在路由机制上做了更详细的划分;支持消息类型支持多种消息类型 ,我们在上面提到过byte[](二进制)总结:AMQP 为消息定义了线路层(wire-level protocol)的协议,而JMS所定义的是API规范。在 Java 体系中,多个client均可以通过JMS进行交互,不需要应用修改代码,但是其对跨平台的支持较差。而AMQP天然具有跨平台、跨语言特性。JMS 支持TextMessage、MapMessage 等复杂的消息类型;而 AMQP 仅支持 byte[] 消息类型(复杂的类型可序列化后发送)。由于Exchange 提供的路由算法,AMQP可以提供多样化的路由方式来传递消息到消息队列,而 JMS 仅支持 队列 和 主题/订阅 方式两种。五 常见的消息队列对比对比方向概要吞吐量万级的 ActiveMQ 和 RabbitMQ 的吞吐量(ActiveMQ 的性能最差)要比 十万级甚至是百万级的 RocketMQ 和 Kafka 低一个数量级。可用性都可以实现高可用。ActiveMQ 和 RabbitMQ 都是基于主从架构实现高可用性。RocketMQ 基于分布式架构。 kafka 也是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用时效性RabbitMQ 基于erlang开发,所以并发能力很强,性能极其好,延时很低,达到微秒级。其他三个都是 ms 级。功能支持除了 Kafka,其他三个功能都较为完备。 Kafka 功能较为简单,主要支持简单的MQ功能,在大数据领域的实时计算以及日志采集被大规模使用,是事实上的标准消息丢失ActiveMQ 和 RabbitMQ 丢失的可能性非常低, RocketMQ 和 Kafka 理论上不会丢失。总结:ActiveMQ 的社区算是比较成熟,但是较目前来说,ActiveMQ 的性能比较差,而且版本迭代很慢,不推荐使用。RabbitMQ 在吞吐量方面虽然稍逊于 Kafka 和 RocketMQ ,但是由于它基于 erlang 开发,所以并发能力很强,性能极其好,延时很低,达到微秒级。但是也因为 RabbitMQ 基于 erlang 开发,所以国内很少有公司有实力做erlang源码级别的研究和定制。如果业务场景对并发量要求不是太高(十万级、百万级),那这四种消息队列中,RabbitMQ 一定是你的首选。如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的,绝对没问题,社区活跃度很高,绝对不会黄,何况几乎是全世界这个领域的事实性规范。RocketMQ 阿里出品,Java 系开源项目,源代码我们可以直接阅读,然后可以定制自己公司的MQ,并且 RocketMQ 有阿里巴巴的实际业务场景的实战考验。RocketMQ 社区活跃度相对较为一般,不过也还可以,文档相对来说简单一些,然后接口这块不是按照标准 JMS 规范走的有些系统要迁移需要修改大量代码。还有就是阿里出台的技术,你得做好这个技术万一被抛弃,社区黄掉的风险,那如果你们公司有技术实力我觉得用RocketMQ 挺好的kafka 的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms 级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。同时 kafka 最好是支撑较少的 topic 数量即可,保证其超高吞吐量。kafka 唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集。参考:《Java工程师面试突击第1季-中华石杉老师》本文作者:snailclimb阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

December 19, 2018 · 1 min · jiezi

阿里云开发者工具上手体验

近期参加了阿里云官方的一个开发者工具有奖评测活动,感兴趣的朋友可以去官网了解下,以下是我对这款工具的使用体验,欢迎各位朋友进行交流和斧正。Alibaba Cloud Toolkit(以下简称工具集)是阿里中间件团队发布的一款面向阿里云服务的开发工具集,用来提升开发者使用阿里云产品时的用户体验。本文将对使用该工具集部署EDAS应用的各种能力进行一番评测。Alibaba Cloud Toolkit主要包含三种类型的工具,分别是IDE插件(目前仅支持Eclipse,IDEA插件据说正在开发中,很快会发布)、Maven插件和命令行工具。在对这三种类型的工具进行详细评测之前,我们先来了解一下使用Web界面部署普通EDAS应用的过程。第一步,需要先申请一些ECS并将这些ECS导入到已经创建的集群中。图1. 集群和集群中的ECS第二步,创建应用。在创建应用的时候选择使用该集群,并选择集群中的ECS作为部署目标。同时打开“立即部署”选项,再选择应用部署方式、应用运行环境和Java环境等,然后上传部署包,在确定了版本以及批次和分批方式后,就可以创建并部署应用了(图2)。图2. 创建并部署应用这个过程看似简单,却有如下一些问题。首先,界面是基于Web的,虽然简单易用,但是自动化能力不强,需要人为干预才能工作,效率不高;其次,用户需要自己打包和上传部署文件,导致开发和部署过程割裂。有了上面一些对EDAS传统部署过程的了解,我们再分别使用工具集中的不同工具来尝试重新部署应用,看看会有什么不同。IDE插件通常而言,开发人员都会使用IDE来提升软件的开发效率,因此Alibaba Cloud Toolkit提供基于主流IDE的扩展插件就是顺其自然的事情,拿Eclipse为例,在Eclipse市场中就能搜索到Alibaba Cloud Toolkit,该插不仅支持EDAS,也有很多其他的功能,但这里我们主要关注面向EDAS的集成能力。安装好插件以后,导入一个项目(可以是原生Dubbo,原生Spring Cloud或HSF项目),然后在Project Explore窗口右键点击该项目,会出现Alibaba Cloud上下文菜单,选择该菜单项后会出现Deploy to EDAS…子菜单项(图3)。图3. Alibaba Cloud菜单及子菜单如果是第一次使用该插件,会弹出一个设置窗口(图4)用来设置访问账户所需的Access Key ID和Access Key Secret,设置完成以后就会出现Deploy to EDAS窗口(图5)。图4. 设置访问账户的Access Key ID和Access Key Secret图5. 部署应用该窗口的主要功能一目了然,选择Region、Namespace、Application和Group后,点击Deploy即开始部署。该插件的优势正如前文所言,解决了开发到部署过程上下文切换的问题,可以在一个场景下完成这两种工作。而劣势(或者说功能的不足)是其能力还不能完全与传统的基于Web部署过程对齐:比如无法选择批次、分配方式,以及在第一次部署的时候无法选择容器和Java版本等等,希望未来的版本能够加以改进。Maven插件使用Java的开发人员多数会使用Apache Maven来管理依赖、编译、测试、打包和发布等过程,因此Alibaba Cloud Toolkit也提供了与Maven的集成能力,即edas-maven-plugin。有关配置和使用方法,在官网的《通过 edas-maven-plugin 插件自动化部署应用》文档中已经有详细说明,就不在此赘述。比起IDE插件来说,Maven插件能够更有效的解决自动化问题,尤其是跟持续集成与持续交付系统对接的时候更为有用。在一个配置完善的自动化系统中,开发人员将开发完成的代码提交到代码仓库,此时可以自动触发应用的构建、单元测试与打包,打包好的交付物会通过edas-maven-plugin部署到测试环境,运行一遍冒烟测试之后,再被部署到预发环境供测试人员测试。整个过程可以做到完全无人值守。该Maven插件也同样存在很多不尽如人意的地方,比如配置项中需要使用应用或分组的ID来指定部署目标,虽然在Web界面上提供有相关的配置可以复制粘贴,但是依然不如直接使用名称来得方便。另外在执行mvn edas:deploy命令的时候必须要在package阶段之后(即命令必须为mvn package edas:deploy),否则无法找到要部署的包。最后同IDE插件遇到的问题相同,如果只有一个空应用,而没有指定过该应用的容器和Java版本的话,那么使用Maven插件部署的时候可能会出现问题。希望这些不足能够在未来的版本中得到解决。命令行工具命令行工具在整个Alibaba Cloud Toolkit里面是功能最强大的,当然也是使用门槛最高的,我们来分别了解一下。说其功能强大,是因为命令行工具是对EDAS Open API的封装,因此只要是接口中提供的功能,都可以使用命令行工具来调用。这个功能并不算新颖,以前在没有提供命令行工具的时候,一些用户也会通过使用Python脚本调用Open API的方式来达到相同的目的,只是官方提供了这个能力之后,对于大部分的开发者而言减少了这部分的工作量。再者,该命令行工具是与aliyun这个命令集成到一起的,因此如果企业对自动化运维有很高要求的话,使用aliyun edas这个子命令可以避免绝大多数的手动操作,包括但不限于创建命名空间、创建集群、集群导入、创建应用以及部署应用、创建分组、应用生命周期管理等等。增强自动化且提高运维效率的同时,更多的可以避免因人为操作失误而导致的安全生产风险,间接提高了产品质量。有关命令行工具的具体配置和使用方法请参考文档《使用CLI快速部署EDAS应用》。说其使用门槛高,主要在于单一命令只能完成一个原子操作,且操作过程大多是异步的,这就要求实现复杂功能的时候需要编写脚本来完成执行结果的轮询与命令串联。虽然这些工作对于开发人员来说算不上什么问题,但是总之是有可改进空间的。以上便是Alibaba Cloud Toolkit提供的针对EDAS产品的全部功能的试用体验以及对其优点和现有不足的分析,鉴于笔者水平有限,提出的问题点并不一定完全准确。总而言之,该工具集的出现确实在很大程度上提升了开发人员的工作效率,也让各种CI/CD以及自动化运维等能力成为可能,确实是一个值得企业用户尤其是开发者用户去尝试的优秀工具集。

December 18, 2018 · 1 min · jiezi

分布式缓存Redis使用心得

一、缓存在系统中用来做什么?少量数据存储,高速读写访问。通过数据全部in-momery 的方式来保证高速访问,同时提供数据落地的功能,实际这正是Redis最主要的适用场景。海量数据存储,分布式系统支持,数据一致性保证,方便的集群节点添加/删除。Redis3.0以后开始支持集群,实现了半自动化的数据分片,不过需要smart-client的支持。二、从不同的角度来详细介绍redis网络模型:Redis使用单线程的IO复用模型,自己封装了一个简单的AeEvent事件处理框架,主要实现了epoll、kqueue和select,对于单纯只有IO操作来说,单线程可以将速度优势发挥到最大,但是Redis也提供了一些简单的计算功能,比如排序、聚合等,对于这些操作,单线程模型实际会严重影响整体吞吐量,CPU计算过程中,整个IO调度都是被阻塞住的。内存管理:Redis使用现场申请内存的方式来存储数据,并且很少使用free-list等方式来优化内存分配,会在一定程度上存在内存碎片,Redis跟据存储命令参数,会把带过期时间的数据单独存放在一起,并把它们称为临时数据,非临时数据是永远不会被剔除的,即便物理内存不够,导致swap也不会剔除任何非临时数据(但会尝试剔除部分临时数据),这点上Redis更适合作为存储而不是cache。数据一致性问题:在一致性问题上,个人感觉redis没有memcached实现的好,Memcached提供了cas命令,可以保证多个并发访问操作同一份数据的一致性问题。 Redis没有提供cas 命令,并不能保证这点,不过Redis提供了事务的功能,可以保证一串命令的原子性,中间不会被任何操作打断。支持的KEY类型:Redis除key/value之外,还支持list,set,sorted set,hash等众多数据结构,提供了KEYS进行枚举操作,但不能在线上使用,如果需要枚举线上数据,Redis提供了工具可以直接扫描其dump文件,枚举出所有数据,Redis还同时提供了持久化和复制等功能。客户端支持:redis官方提供了丰富的客户端支持,包括了绝大多数编程语言的客户端,比如我此次测试就选择了官方推荐了Java客户端Jedis.里面提供了丰富的接口、方法使得开发人员无需关系内部的数据分片、读取数据的路由等,只需简单的调用即可,非常方便。数据复制:从2.8开始,Slave会周期性(每秒一次)发起一个Ack确认复制流(replication stream)被处理进度, Redis复制工作原理详细过程如下:如果设置了一个Slave,无论是第一次连接还是重连到Master,它都会发出一个SYNC命令;当Master收到SYNC命令之后,会做两件事:a) Master执行BGSAVE:后台写数据到磁盘(rdb快照);b) Master同时将新收到的写入和修改数据集的命令存入缓冲区(非查询类);当Master在后台把数据保存到快照文件完成之后,Master会把这个快照文件传送给Slave,而Slave则把内存清空后,加载该文件到内存中;而Master也会把此前收集到缓冲区中的命令,通过Reids命令协议形式转发给Slave,Slave执行这些命令,实现和Master的同步;Master/Slave此后会不断通过异步方式进行命令的同步,达到最终数据的同步一致;需要注意的是Master和Slave之间一旦发生重连都会引发全量同步操作。但在2.8之后,也可能是部分同步操作。2.8开始,当Master和Slave之间的连接断开之后,他们之间可以采用持续复制处理方式代替采用全量同步。Master端为复制流维护一个内存缓冲区(in-memory backlog),记录最近发送的复制流命令;同时,Master和Slave之间都维护一个复制偏移量(replication offset)和当前Master服务器ID(Masterrun id)。当网络断开,Slave尝试重连时:a. 如果MasterID相同(即仍是断网前的Master服务器),并且从断开时到当前时刻的历史命令依然在Master的内存缓冲区中存在,则Master会将缺失的这段时间的所有命令发送给Slave执行,然后复制工作就可以继续执行了;b. 否则,依然需要全量复制操作。读写分离:redis支持读写分离,而且使用简单,只需在配置文件中把redis读服务器和写服务器进行配置,多个服务器使用逗号分开如下:水平动态扩展:历时三年之久,终于等来了期待已由的Redis 3.0。新版本主要是实现了Cluster的功能,增删集群节点后会自动的进行数据迁移。实现 Redis 集群在线重配置的核心就是将槽从一个节点移动到另一个节点的能力。因为一个哈希槽实际上就是一些键的集合, 所以 Redis 集群在重哈希(rehash)时真正要做的,就是将一些键从一个节点移动到另一个节点。数据淘汰策略:redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。redis 提供 6种数据淘汰策略:volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰no-enviction(驱逐):禁止驱逐数据三、集群(即分布式)下面详细介绍一下redis的集群功能,从3.0以后的版本开始支持集群功能,也就是正真意义上实现了分布式。Redis 集群是一个分布式(distributed)、容错(fault-tolerant)的 Redis 实现, 集群可以使用的功能是普通单机 Redis 所能使用的功能的一个子集(subset)。Redis 集群中不存在中心(central)节点或者代理(proxy)节点, 集群的其中一个主要设计目标是达到线性可扩展性(linear scalability)。Redis 集群为了保证一致性(consistency)而牺牲了一部分容错性: 系统会在保证对网络断线(netsplit)和节点失效(node failure)具有有限(limited)抵抗力的前提下,尽可能地保持数据的一致性。集群特性:(1)所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。(2)节点的fail是通过集群中超过半数的节点检测失效时才生效。(3)客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。(4)redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->valueRedis 集群实现的功能子集:Redis集群实现了单机 Redis 中, 所有处理单个数据库键的命令。针对多个数据库键的复杂计算操作, 比如集合的并集操作、合集操作没有被实现,那些理论上需要使用多个节点的多个数据库键才能完成的命令也没有被实现。在将来, 用户也许可以通过 MIGRATE COPY 命令,在集群的计算节点(computation node)中执行针对多个数据库键的只读操作, 但集群本身不会去实现那些需要将多个数据库键在多个节点中移来移去的复杂多键命令。Redis 集群不像单机Redis 那样支持多数据库功能, 集群只使用默认的 0 号数据库, 并且不能使用 SELECT 命令。Redis 集群协议中的客户端和服务器:Redis 集群中的节点有以下责任:持有键值对数据。记录集群的状态,包括键到正确节点的映射(mappingkeys to right nodes)。自动发现其他节点,识别工作不正常的节点,并在有需要时,在从节点中选举出新的主节点。为了执行以上列出的任务, 集群中的每个节点都与其他节点建立起了“集群连接(cluster bus)”, 该连接是一个 TCP 连接, 使用二进制协议进行通讯。节点之间使用Gossip 协议 来进行以下工作:传播(propagate)关于集群的信息,以此来发现新的节点。向其他节点发送 PING 数据包,以此来检查目标节点是否正常运作。在特定事件发生时,发送集群信息。除此之外, 集群连接还用于在集群中发布或订阅信息。因为集群节点不能代理(proxy)命令请求, 所以客户端应该在节点返回 -MOVED 或者 -ASK 转向(redirection)错误时,自行将命令请求转发至其他节点。因为客户端可以自由地向集群中的任何一个节点发送命令请求, 并可以在有需要时, 根据转向错误所提供的信息, 将命令转发至正确的节点,所以在理论上来说, 客户端是无须保存集群状态信息的。不过, 如果客户端可以将键和节点之间的映射信息保存起来, 可以有效地减少可能出现的转向次数, 籍此提升命令执行的效率。键分布模型Redis 集群的键空间被分割为 16384 个槽(slot), 集群的最大节点数量也是 16384 个。推荐的最大节点数量为 1000 个左右。每个主节点都负责处理 16384 个哈希槽的其中一部分。当我们说一个集群处于“稳定”(stable)状态时, 指的是集群没有在执行重配(reconfiguration)操作,每个哈希槽都只由一个节点进行处理。重配置指的是将某个/某些槽从一个节点移动到另一个节点。一个主节点可以有任意多个从节点,这些从节点用于在主节点发生网络断线或者节点失效时, 对主节点进行替换。集群节点属性:每个节点在集群中都有一个独一无二的 ID , 该 ID 是一个十六进制表示的 160 位随机数, 在节点第一次启动时由 /dev/urandom 生成。节点会将它的 ID 保存到配置文件, 只要这个配置文件不被删除,节点就会一直沿用这个 ID 。节点 ID 用于标识集群中的每个节点。一个节点可以改变它的 IP 和端口号, 而不改变节点 ID 。集群可以自动识别出 IP/端口号的变化, 并将这一信息通过 Gossip 协议广播给其他节点知道。以下是每个节点都有的关联信息, 并且节点会将这些信息发送给其他节点:节点所使用的 IP 地址和 TCP 端口号。节点的标志(flags)。节点负责处理的哈希槽。节点最近一次使用集群连接发送 PING 数据包(packet)的时间。节点最近一次在回复中接收到 PONG 数据包的时间。集群将该节点标记为下线的时间。该节点的从节点数量。如果该节点是从节点的话,那么它会记录主节点的节点 ID 。如果这是一个主节点的话,那么主节点 ID 这一栏的值为 0000000 。以上信息的其中一部分可以通过向集群中的任意节点(主节点或者从节点都可以)发送 CLUSTER NODES 命令来获得。节点握手:节点总是应答(accept)来自集群连接端口的连接请求,并对接收到的 PING 数据包进行回复, 即使这个 PING 数据包来自不可信的节点。然而,除了 PING 之外, 节点会拒绝其他所有并非来自集群节点的数据包。要让一个节点承认另一个节点同属于一个集群,只有以下两种方法:一个节点可以通过向另一个节点发送 MEET 信息,来强制让接收信息的节点承认发送信息的节点为集群中的一份子。 一个节点仅在管理员显式地向它发送CLUSTER MEET ipport 命令时, 才会向另一个节点发送 MEET 信息。如果一个可信节点向另一个节点传播第三者节点的信息, 那么接收信息的那个节点也会将第三者节点识别为集群中的一份子。也即是说, 如果 A 认识 B , B 认识 C , 并且 B 向 A 传播关于 C 的信息, 那么 A 也会将 C 识别为集群中的一份子, 并尝试连接 C 。这意味着如果我们将一个/一些新节点添加到一个集群中, 那么这个/这些新节点最终会和集群中已有的其他所有节点连接起来。这说明只要管理员使用 CLUSTER MEET 命令显式地指定了可信关系,集群就可以自动发现其他节点。这种节点识别机制通过防止不同的 Redis 集群因为 IP 地址变更或者其他网络事件的发生而产生意料之外的联合(mix), 从而使得集群更具健壮性。当节点的网络连接断开时,它会主动连接其他已知的节点。MOVED 转向:一个 Redis 客户端可以向集群中的任意节点(包括从节点)发送命令请求。节点会对命令请求进行分析, 如果该命令是集群可以执行的命令, 那么节点会查找这个命令所要处理的键所在的槽。如果要查找的哈希槽正好就由接收到命令的节点负责处理,那么节点就直接执行这个命令。另一方面, 如果所查找的槽不是由该节点处理的话, 节点将查看自身内部所保存的哈希槽到节点 ID 的映射记录,并向客户端回复一个 MOVED 错误。即使客户端在重新发送 GET 命令之前, 等待了非常久的时间,以至于集群又再次更改了配置, 使得节点 127.0.0.1:6381 已经不再处理槽 3999 , 那么当客户端向节点 127.0.0.1:6381 发送 GET 命令的时候, 节点将再次向客户端返回 MOVED 错误, 指示现在负责处理槽 3999 的节点。虽然我们用 ID 来标识集群中的节点, 但是为了让客户端的转向操作尽可能地简单,,节点在 MOVED 错误中直接返回目标节点的 IP 和端口号,而不是目标节点的 ID 。但一个客户端应该记录(memorize)下“槽 3999 由节点 127.0.0.1:6381 负责处理“这一信息, 这样当再次有命令需要对槽 3999 执行时, 客户端就可以加快寻找正确节点的速度。注意, 当集群处于稳定状态时, 所有客户端最终都会保存有一个哈希槽至节点的映射记录(map of hash slots to nodes), 使得集群非常高效: 客户端可以直接向正确的节点发送命令请求, 无须转向、代理或者其他任何可能发生单点故障(single point failure)的实体(entiy)。除了 MOVED转向错误之外, 一个客户端还应该可以处理稍后介绍的 ASK 转向错误。集群在线重配置:Redis 集群支持在集群运行的过程中添加或者移除节点。实际上, 节点的添加操作和节点的删除操作可以抽象成同一个操作,那就是, 将哈希槽从一个节点移动到另一个节点:添加一个新节点到集群, 等于将其他已存在节点的槽移动到一个空白的新节点里面。从集群中移除一个节点, 等于将被移除节点的所有槽移动到集群的其他节点上面去。因此, 实现Redis 集群在线重配置的核心就是将槽从一个节点移动到另一个节点的能力。 因为一个哈希槽实际上就是一些键的集合, 所以 Redis 集群在重哈希(rehash)时真正要做的, 就是将一些键从一个节点移动到另一个节点。要理解Redis 集群如何将槽从一个节点移动到另一个节点, 我们需要对 CLUSTER 命令的各个子命令进行介绍,这些命理负责管理集群节点的槽转换表(slots translation table)。以下是CLUSTER 命令可用的子命令:最开头的两条命令ADDSLOTS 和 DELSLOTS 分别用于向节点指派(assign)或者移除节点,当槽被指派或者移除之后, 节点会将这一信息通过 Gossip 协议传播到整个集群。 ADDSLOTS 命令通常在新创建集群时, 作为一种快速地将各个槽指派给各个节点的手段来使用。CLUSTERSETSLOT slot NODE node 子命令可以将指定的槽 slot 指派给节点node 。至于CLUSTER SETSLOT slot MIGRATING node 命令和 CLUSTER SETSLOTslot IMPORTING node 命令, 前者用于将给定节点 node 中的槽 slot 迁移出节点, 而后者用于将给定槽 slot导入到节点 node :当一个槽被设置为MIGRATING 状态时, 原来持有这个槽的节点仍然会继续接受关于这个槽的命令请求, 但只有命令所处理的键仍然存在于节点时, 节点才会处理这个命令请求。如果命令所使用的键不存在与该节点, 那么节点将向客户端返回一个 -ASK 转向(redirection)错误, 告知客户端, 要将命令请求发送到槽的迁移目标节点。当一个槽被设置为IMPORTING 状态时, 节点仅在接收到 ASKING 命令之后, 才会接受关于这个槽的命令请求。如果客户端没有向节点发送 ASKING 命令, 那么节点会使用 -MOVED 转向错误将命令请求转向至真正负责处理这个槽的节点。上面关于MIGRATING 和 IMPORTING 的说明有些难懂, 让我们用一个实际的实例来说明一下。假设现在, 我们有 A 和 B 两个节点, 并且我们想将槽8 从节点 A 移动到节点 B , 于是我们:向节点 B 发送命令 CLUSTER SETSLOT 8 IMPORTING A向节点 A 发送命令 CLUSTER SETSLOT 8 MIGRATING B每当客户端向其他节点发送关于哈希槽 8 的命令请求时, 这些节点都会向客户端返回指向节点 A 的转向信息:如果命令要处理的键已经存在于槽 8 里面, 那么这个命令将由节点 A 处理。如果命令要处理的键未存在于槽 8 里面(比如说,要向槽添加一个新的键), 那么这个命令由节点 B 处理。这种机制将使得节点 A 不再创建关于槽 8 的任何新键。与此同时, 一个特殊的客户端 redis-trib 以及 Redis 集群配置程序(configuration utility)会将节点 A 中槽 8 里面的键移动到节点 B 。键的移动操作由以下两个命令执行:CLUSTERGETKEYSINSLOT slot count上面的命令会让节点返回 count 个 slot 槽中的键, 对于命令所返回的每个键, redis-trib 都会向节点 A 发送一条 MIGRATE 命令, 该命令会将所指定的键原子地(atomic)从节点 A 移动到节点 B (在移动键期间,两个节点都会处于阻塞状态,以免出现竞争条件)。以下为MIGRATE 命令的运作原理:MIGRATEtarget_host target_port key target_database id timeout执行MIGRATE 命令的节点会连接到 target 节点, 并将序列化后的 key 数据发送给 target , 一旦 target 返回 OK , 节点就将自己的 key 从数据库中删除。从一个外部客户端的视角来看, 在某个时间点上, 键 key 要么存在于节点 A , 要么存在于节点 B , 但不会同时存在于节点 A 和节点 B 。因为 Redis集群只使用 0 号数据库, 所以当 MIGRATE 命令被用于执行集群操作时, target_database 的值总是 0 。target_database参数的存在是为了让 MIGRATE 命令成为一个通用命令, 从而可以作用于集群以外的其他功能。我们对MIGRATE 命令做了优化, 使得它即使在传输包含多个元素的列表键这样的复杂数据时, 也可以保持高效。不过, 尽管MIGRATE 非常高效, 对一个键非常多、并且键的数据量非常大的集群来说, 集群重配置还是会占用大量的时间, 可能会导致集群没办法适应那些对于响应时间有严格要求的应用程序。ASK 转向:在之前介绍 MOVED 转向的时候, 我们说除了 MOVED 转向之外, 还有另一种 ASK 转向。当节点需要让一个客户端长期地(permanently)将针对某个槽的命令请求发送至另一个节点时,节点向客户端返回 MOVED 转向。另一方面, 当节点需要让客户端仅仅在下一个命令请求中转向至另一个节点时, 节点向客户端返回 ASK 转向。比如说, 在我们上一节列举的槽 8 的例子中, 因为槽 8 所包含的各个键分散在节点 A 和节点 B 中, 所以当客户端在节点 A 中没找到某个键时, 它应该转向到节点 B 中去寻找, 但是这种转向应该仅仅影响一次命令查询,而不是让客户端每次都直接去查找节点 B : 在节点 A 所持有的属于槽 8 的键没有全部被迁移到节点 B 之前, 客户端应该先访问节点 A , 然后再访问节点 B 。因为这种转向只针对 16384 个槽中的其中一个槽, 所以转向对集群造成的性能损耗属于可接受的范围。因为上述原因, 如果我们要在查找节点 A 之后, 继续查找节点 B , 那么客户端在向节点 B 发送命令请求之前, 应该先发送一个 ASKING 命令, 否则这个针对带有IMPORTING 状态的槽的命令请求将被节点 B 拒绝执行。接收到客户端 ASKING 命令的节点将为客户端设置一个一次性的标志(flag), 使得客户端可以执行一次针对 IMPORTING 状态的槽的命令请求。从客户端的角度来看, ASK 转向的完整语义(semantics)如下:如果客户端接收到 ASK 转向, 那么将命令请求的发送对象调整为转向所指定的节点。先发送一个 ASKING 命令,然后再发送真正的命令请求。不必更新客户端所记录的槽 8 至节点的映射: 槽 8 应该仍然映射到节点 A , 而不是节点 B 。一旦节点 A 针对槽 8 的迁移工作完成, 节点 A 在再次收到针对槽 8 的命令请求时, 就会向客户端返回 MOVED 转向, 将关于槽 8 的命令请求长期地转向到节点 B 。注意, 即使客户端出现 Bug , 过早地将槽 8 映射到了节点 B 上面, 但只要这个客户端不发送 ASKING 命令, 客户端发送命令请求的时候就会遇上 MOVED 错误, 并将它转向回节点 A 。容错:节点失效检测,以下是节点失效检查的实现方法:当一个节点向另一个节点发送 PING 命令, 但是目标节点未能在给定的时限内返回 PING 命令的回复时, 那么发送命令的节点会将目标节点标记为 PFAIL(possible failure,可能已失效)。等待 PING 命令回复的时限称为“节点超时时限(node timeout)”, 是一个节点选项(node-wise setting)。每次当节点对其他节点发送 PING 命令的时候,它都会随机地广播三个它所知道的节点的信息, 这些信息里面的其中一项就是说明节点是否已经被标记为 PFAIL或者 FAIL 。当节点接收到其他节点发来的信息时, 它会记下那些被其他节点标记为失效的节点。这称为失效报告(failure report)。如果节点已经将某个节点标记为 PFAIL , 并且根据节点所收到的失效报告显式,集群中的大部分其他主节点也认为那个节点进入了失效状态, 那么节点会将那个失效节点的状态标记为 FAIL 。一旦某个节点被标记为 FAIL , 关于这个节点已失效的信息就会被广播到整个集群,所有接收到这条信息的节点都会将失效节点标记为 FAIL 。简单来说, 一个节点要将另一个节点标记为失效, 必须先询问其他节点的意见, 并且得到大部分主节点的同意才行。因为过期的失效报告会被移除,所以主节点要将某个节点标记为 FAIL 的话, 必须以最近接收到的失效报告作为根据。从节点选举:一旦某个主节点进入 FAIL 状态, 如果这个主节点有一个或多个从节点存在,那么其中一个从节点会被升级为新的主节点, 而其他从节点则会开始对这个新的主节点进行复制。新的主节点由已下线主节点属下的所有从节点中自行选举产生,以下是选举的条件:这个节点是已下线主节点的从节点。已下线主节点负责处理的槽数量非空。从节点的数据被认为是可靠的, 也即是, 主从节点之间的复制连接(replication link)的断线时长不能超过节点超时时限(nodetimeout)乘以REDIS_CLUSTER_SLAVE_VALIDITY_MULT 常量得出的积。如果一个从节点满足了以上的所有条件, 那么这个从节点将向集群中的其他主节点发送授权请求, 询问它们,是否允许自己(从节点)升级为新的主节点。如果发送授权请求的从节点满足以下属性, 那么主节点将向从节点返FAILOVER_AUTH_GRANTED 授权, 同意从节点的升级要求:发送授权请求的是一个从节点, 并且它所属的主节点处于 FAIL状态。在已下线主节点的所有从节点中, 这个从节点的节点 ID 在排序中是最小的。这个从节点处于正常的运行状态: 它没有被标记为 FAIL 状态,也没有被标记为 PFAIL 状态。一旦某个从节点在给定的时限内得到大部分主节点的授权,它就会开始执行以下故障转移操作:通过 PONG 数据包(packet)告知其他节点, 这个节点现在是主节点了。通过 PONG 数据包告知其他节点, 这个节点是一个已升级的从节点(promoted slave)。接管(claiming)所有由已下线主节点负责处理的哈希槽。显式地向所有节点广播一个 PONG 数据包, 加速其他节点识别这个节点的进度,而不是等待定时的 PING / PONG 数据包。所有其他节点都会根据新的主节点对配置进行相应的更新:所有被新的主节点接管的槽会被更新。已下线主节点的所有从节点会察觉到 PROMOTED 标志,并开始对新的主节点进行复制。如果已下线的主节点重新回到上线状态, 那么它会察觉到PROMOTED 标志, 并将自身调整为现任主节点的从节点。在集群的生命周期中, 如果一个带有 PROMOTED 标识的主节点因为某些原因转变成了从节点,那么该节点将丢失它所带有的 PROMOTED 标识。 ...

December 18, 2018 · 4 min · jiezi

生物智能与AI——关乎创造、关乎理解(上)

摘要: 原来人工智能跟人类智能有那么深的联系!几百万年前,第一次人类智能的星火出现在非洲大陆,并且持续发展,最终在大约10万年前在智人的大脑中达到顶峰。作为现代人类,我们只能想象我们的古代祖先在窥视夜空时所经历的事情,以思考物理现实的本质,以及从内心窥视自己心理现实的本质。在过去的几百年里,我们的物种通过发现控制空间、时间、物质和能量的基本数学定律。在发展对物理现实的精确理解方面取得了巨大的智力进步,现在已经在量子力学的大框架中被编纂。然而,我们正处于探索心理现实本质的最初阶段。尤其是人类智能是如何从100亿个突触连接的1000亿个神经元的生物湿件中产生的?神经科学,心理学和认知科学等现代学科在过去100年中取得了重要进展,为解决这一重大问题奠定了基础。但是,当涉及到我们的心智能力时,对于现代人来说,仅仅理解它们是不够的,我们非常希望在无生命系统中重现这些功能。本质上,人类作为进化的产物,有时也渴望扮演创造者的角色。这种向往渗透在人类文学的作品,事实上,人工智能(AI)这个新兴领域,通常与神经科学,心理学和认知科学领域合作,在创造具有类似人类能力的机器方面取得了巨大进步。在这篇文章中,我将进一步探讨人工智能,神经科学,心理学和认知科学以及数学,物理和社会科学中的联合学科在过去和未来将继续如何共同努力追求交织在一起的理解和创造智能系统的过程。生物学与人工智能之间的富有成效的合作在过去的60多年中,AI的发展受到了神经科学和心理学的深刻影响,其中也受到了神经科学和心理学的启发。在早期的几十年中,许多AI从业者在神经科学和心理学方面进行了很好的研究。在这里,我提供了神经科学,心理学和AI之间过去的相互作用:这种相对简单的元素(神经元)的分布式网络能够实现源于神经科学的人类智能的显着计算,并且现在以神经网络的形式渗透到AI系统中。这个想法并不总是显而易见的,在大约一百年前,在高尔基和卡哈尔之间的著名辩论之后,它才变得坚定。包括多维尺度和因子分析在内的各种降维技术最初是在心理测量学研究的背景下开发的。著名的神经科学家霍勒斯·巴洛(Horace Barlow)发明了分解代码的概念,这反过来启发了独立成分分析(ICA)和当前的AI研究,旨在解开数据变异的独立因素。托尔曼在认知图上的工作提供了方向,使得我们可以使用这些模型进行规划和导航。这巩固了内部模型形成作为动物智能的关键组成部分的思想,这部分目前处于人工智能研究的前沿。Hopfield网络是理论神经科学的一个模型,为思考分布式、可寻址的存储器和检索提供了一个统一的框架,也启发了Boltzmann机器,这反过来又为证明深度神经网络模型的成功提供了关键的第一步。它还启发了许多弱约束的分布式以满足作为AI计算模型的想法。目前主导机器视觉的深层卷积网络的关键核心直接受到大脑的启发。其中包括腹侧流中的分层视觉处理,它表明深度的重要性;视网膜的发现是整个视觉皮层的组织原理,导致卷积的出现;发现简单和复杂的细胞激发了最大池化等操作。关于稀疏编码的研究工作是为了理解初级视觉皮层中定向边缘检测器,导致稀疏编码成为现代AI系统中的基本构建块。时序差分学习等算法现在是强化学习领域的基础,它受到经典条件反射的动物实验的启发。反过来,强化学习对基底神经节功能的解释具有显着影响,其中多巴胺能为基底神经节提供了非常重要的奖励预测误差信号,该信号也驱动许多强化学习算法。大脑中存储系统的模块化启发了现代记忆神经网络,其在一定程度上将存储器存储和执行控制电路的操作分开,其决定何时从存储器读取和写入。人类注意力系统激发了注意力机制和神经网络的结合,这些神经网络可以被训练以动态地注意力或忽略其状态和输入的不同方面以进行未来的计算决策。语言学和认知科学中正式生成语法的发展导致概率语法的发展和CS的解析。Dropout等现代正则化技术的灵感来自于神经动力学的内在随机性。人工智能未来的生物学启示尽管当前人工智能系统在监督模式识别任务方面取得了显著的商业成功,但仿真人类智能仍然有很长的路要走。在这里,我将概述一些个人观点,其中我认为生物学和人工智能领域可以携手前进。1、生物学上可信的信用分配(plausible credit assignment)信用分配问题可能是神经科学和人工智能领域最大的开放性问题之一。很明显,假设你正在打网球而且你没有击中球。你的100万亿个突触中有哪一个应该受到指责?大脑如何在你的运动系统中专门找到并纠正突触组,尤其是在错误发生后几百毫秒内通过视觉系统传递错误时?在AI中,这种信用分配问题在许多情况下通过多层计算的反向传播来解决。然而,目前尚不清楚大脑如何解决这个问题。真实的情况是,大脑使用本地学习规则解决它:即每个突触仅使用物理上可用的信息来调整其强度,例如,由突触连接的两个神经元的电活动来奖励和惩罚的任何神经调节输入。解释这些本地突触规则是什么以及它们如何工作可能会对AI产生巨大影响,这可以一定程度上减少反向传播的通信开销。但更一般地说,解决困扰神经科学和人工智能的常见未解决问题应该通过将突触生理学家,计算神经科学家和AI从业者聚集在一起来集体解决生物学上可信的信用分配问题来推动进步。2、融合突触复杂性生物和人工神经模型之间的主要区别在于我们模拟连接神经元的突触的方式。在人工网络中,突触由单个标量值建模,反映乘法增益因子,转换神经元的输入如何影响神经元的输出。相反,每个生物突触都隐藏在极其复杂的分子信号通路中。例如,我们对最近事件记忆的海马突触各自包含数百种不同类型分子的化学反应网络,同时它具有整个复杂时间处理能力的动力系统。在看到这种复杂性后,理论家或工程师可能会试图简单地将其视为生物学上的混乱,而这种混乱就是一种进化的偶然事件。然而,理论研究表明,这种突触复杂性可能确实对学习和记忆至关重要。事实上,在突触具有有限动态范围的记忆网络模型中,这样的突触本身就要求是具有复杂时间滤波特性的动态系统,以实现合理的网络存储容量。此外,最近在AI中正在利用更智能的突触作为解决灾难性遗忘问题的一种方法,其中训练学习两个任务的网络只能学习第二个任务,因为学习第二个任务会改变突触权重以这种方式消除从学习第一项任务中获得的知识。一般地说,我们的人工智能系统很可能通过忽略生物突触的动态复杂性而取得重大的性能提升。正如我们为我们的网络添加空间深度以实现复杂的层次表示一样,我们可能还需要为突触添加动态深度以实现复杂的时间学习功能。从系统级模块化大脑架构中获取灵感通常,当前的商业AI系统涉及具有相对均匀的分层或循环架构的训练网络,其从随机权重开始。但是,对于更复杂的任务来说,这可能是一个难以解决的问题。事实上,生物进化的道路却截然不同。所有脊椎动物的最后共同祖先生活在5亿年前。从那以后,它的基本大脑一直在发展,导致大约1亿年前出现哺乳动物大脑,以及几百万年前的人类大脑。这种不间断的进化链导致了一个错综复杂的大脑结构,具有高度保守的计算元素和巨大的系统级模块化。事实上,我们目前缺乏工程设计原则,来解释像大脑一样复杂的传感,通信,控制和记忆网络可以在5亿年的时间内不断扩大规模和复杂性,同时永远不会失去在动态环境中自适应运行的能力。因此,AI从大脑的系统级结构中获取灵感可能非常有趣。一个关键的系统属性是功能和结构的模块化。大脑不像我们目前的AI架构是同质的,而是有不同的模块,如海马(保留情节记忆和导航),基底神经节(潜在的强化学习和动作选择)和小脑(自动化的运动控制和通过监督学习获得更高层次的认知)。此外,人脑中的记忆系统(习惯记忆,运动技能,短期记忆,长期记忆,情景记忆,语义记忆)也是功能模块化的。此外,在运动系统中,嵌套反馈环架构占主导地位,通过简单的快速循环在20毫秒内实现自动运动校正,稍慢的智能循环通过运动皮层在50毫秒内实现更复杂的运动校正,最后经过整个大脑的视觉反馈实现对运动错误的有意识的校正。最后,所有哺乳动物大脑的一个主要特征是由大量相似的6层皮质柱组成的新皮层,所有这些都被认为是在单个规范计算模块上实现的变异。总体而言,现代哺乳动物大脑具有显著的模块性,通过1亿年的进化保存下来,表明这种系统级模块化可能有利于在AI系统中实施。目前从白板上训练神经网络的方法是不可能走向更普遍的人类智能的途径。实际上,系统级模块化的组合带来的不同类型的纠错嵌套循环和动态复杂的突触可能都是解决生物学上可信的信用分配的关键因素。本文作者:【方向】阅读原文本文为云栖社区原创内容,未经允许不得转载。

December 17, 2018 · 1 min · jiezi

蚂蚁的开放:想办法摸到10米的篮筐

摘要: 只有不停的去挑战自己,永不停歇的思考和不断颠覆自我,才可能保持一点点微小的领先。小蚂蚁说:从“互联网推进器计划”到“成熟一个开放一个”,再到蚂蚁金融科技全面开放战略的公布,这一路走来,蚂蚁金服不断创造技术的里程碑,同时,也缔造出一个又一个的业界里程碑。然而,谁又想到,“蚂蚁的科技开放究竟要怎么做?”今天,我们将分享一个“蚂蚁金服科技开放的故事”。文 | 央行观察作为 “空降” 蚂蚁金服的高管之一,刘伟光刚来的时候颇不适应。刘伟光是蚂蚁金融科技开放的负责人。2017年12月底,在杭州某酒店的一次公司内部会上,刚来公司不久的他,向同事们讲了下他对蚂蚁金融技术开放的战略。那个时候他正在苦苦思索未来的开放格局不得其解,当天人处在正发高烧的状态,状态差到极点。“很多当时的想法连自己都没有说服自己,更不要说别人了”,后来,每次想起这次 “失败” 的分享,刘伟光都觉得挺惭愧的,那就是当时的困境所在。那时的蚂蚁金服,正渐入开放的深水区,从 “成熟一个,开放一个” 决意走向全面开放。如何真正全面、从哪入手等问题都扑面而来。说开放难做,是因为这意味着很多技术能力要从服务C端,跨越到B端和C端兼顾,这中间有一个巨大的能力跨越和需要不断论证的过程。 放眼全球,真正能做到的公司屈指可数,而蚂蚁金服所处的金融行业特殊性,又进一步增加了这个难度:首先,IOE 传统架构下的流程和模式已经在行业根深蒂固,新来者必须提供更有价值的服务;其次,蚂蚁金服自身也有金融业务,合作方难免会有顾忌。因此,如果提供不了更好的解决方案、真正给用户带来价值的话,蚂蚁技术开放的战略就将只是一厢情愿。蚂蚁的科技开放究竟要怎么做?刘伟光和同事们绞尽脑汁地想了几个月,无数个夜晚在黄龙国际的办公室里和旁边的夜宵馆子里带着团队苦苦的进行 “脑暴”。一、“想办法摸到放置在十米高的篮筐”在加入蚂蚁金服之前,刘伟光曾在 Oracle、EMC、Pivotal 等全球顶尖软件公司工作过多年。到蚂蚁后,这是一个全新的行业,且行业在巨变,在老东家的那套玩法已经行不通了。外企最看重的是业务规模和短线收入,但是现在如果他再说自己做了几个 million、几个 billion 的生意的话,在蚂蚁没有人会在意,公司需要的是开拓一种全新的服务模式和用技术来促进甚至驱动生态的做法。这中间需要巨大的脑洞和付出。就像一个篮球运动员,如果让他从3.05米高的篮筐摸到3.20米,只要勤加锻炼就可以达到,这是一种线性的思维和增长,一个不一定恰当的比喻就是:如果篮筐放到10米高让他去摸,怎么办?靠之前的方法就完全不行,必须换一种新的思路和打法。对于刘伟光来说也是如此,要想进一步推动蚂蚁金融科技,就必须有一套新的打法。首先就要颠覆自己,改变自己近20年的纯粹 IT 思维模式,这本是就是一个巨大的挑战。2018年春节后的一个下午,刘伟光要和公司的 CTO 程立以及副CTO 胡喜讨论headcount(人员招聘)的问题,刘伟光在会议的开始说了一句,“我这里还有些关于业务上的最新的打法可以聊聊”。“那就先说打法吧”,程立打断了刘伟光。程立曾经是支付宝最早的程序员,在蚂蚁金服的十几年中,他经历过 “双11” 的洗礼、“账目三期” 的历练,熟知这家企业在技术上所走的每一步。在2017年杭州云栖 ATEC 大会上,程立首次披露了蚂蚁金服面向未来的技术布局——“BASIC” 战略(“BASIC:Blockchain(区块链)、AI(人工智能)、Security(安全)、IoT(物联网)和 Computing(计算)”)。也正是从那时起,科技 (开放)、普惠和全球化一道,成为了蚂蚁金服的三大核心战略。事实上,蚂蚁金服的科技开放战略早在几年前就有。2015年9月,时任蚂蚁金服总裁的井贤栋就宣布启动互联网推进器计划。随后的几年里,开放战略不断升级,直到成为公司最重要的核心战略之一,这背后有着特殊的时代背景:一是金融系统去IOE、自主可控化的趋势日益明显,二是金融科技的发展体现出深度融合的趋势,银行、新金融机构之间的技术和业务合作正呈现出加速的趋势。在和程立、胡喜的那次谈话中,刘伟光就从赋予金融行业前中后台新的定义、重构未来数字金融这个角度说了自己的想法。他认为,好的科技服务,应该切中用户的真实需求,应该先从服务银行与用户交互最多的移动端发力,重新定义移动端金融的能力,然后走向中台,建立敏捷能力中心,最后再助力银行瘦身后台系统。通过分布式技术平台重构后台核心系统,全新的架构更加强调对业务和产品迭代周期的缩短,增强对新型业务的快速支撑,包括对线上线下业务融合的思考,从而适应中国金融行业发展的趋势。在很多 IT 专业人士眼中,前中后台的提法算不是上什么新词,但是在那个黑板上画出的架构图却是完全不同的定义,但这却是刘伟光和同事在几个月思索后的一些阶段性想法。银行 App 的用户体验,一直是客户诟病的痛点,如果看看手机应用商店下的评论,我们就知道银行在这方面需要做多么大的提升,而这也正是蚂蚁金服能够帮助到银行的地方。无独有偶,蚂蚁的竞争对手腾讯,也在帮商业银行做用户体验大调研,以期从移动端切入用户。讲到最后,程立在白板上写下了自己的愿望,“用技术实现真正的数字金融”。可以说这番谈话开启了后来的很多故事。实现这个业务目标的第一步,是要倾听银行伙伴们在移动端上的真实需求。2018年3月底的一个晚上,在支付宝大厦楼下的一个小饭馆里,刘伟光向同事和盘托出了想从移动端入手的思路。他说,“我们要办一场专注在移动金融的会,请100家银行来,倾听他们的想法,验证我们的思路”。当时刘伟光在喊出100家银行的时候其实心里是很虚的。这些思路和打法到底能否真正得到市场的认可还是个问号。这就是在蚂蚁金融科技开放历程中非常著名的 “511大会”。整个会议的筹备没有用供应商,蚂蚁的人自己来负责会议的通知、接机、组织和所有的会务工作,全员上阵,连 HR 和研发的专家们都在现场做会务支持。通过主动联系和人传人的方式,最后有250家银行报名参加,西子宾馆的大会议室里超过500人云集。在这次会上,刘伟光代表公司将蚂蚁在移动端的服务战略和思路分享给了银行客户,也对蚂蚁未来的前中后台战略做了展望,还请到了银行客户上台发言。当天的会议进行到下午5~6点钟的时候,依然有80%左右的上座率。“我们用蚂蚁的精神办了一场会”,刘伟光说。二、从解决方案入手在加入蚂蚁一年多的多时间里,刘伟光和团队一起拜访了中国数百家家金融机构,每到一地,他都要和当地银行的高管做深入的沟通。在这个过程中,他感受到了银行面向科技、面向移动、面向数字化转型的迫切需求。1、“为什么我们银行做了这么多APP,没有一个app日活量大?”2、“为什么别人是千人千面,我们是千人一面的APP?”3、 “我们花了一个多亿人民币构建一个数据仓库,为什么现在还一张数据报表都跑不出来?”4、“金融科技到底是什么?不是给科技部自用,而是应该给全行所有人包括客户都用的起来的工具才有意义,人人都可以使用大数据系统做 BI,而不是仅限于非常少数的专业人员。“5、“分布式架构就是银行的未来,我们科技部把职业生涯赌在这场改造上,你们蚂蚁金服敢不敢跟我们一起赌?”6、“农村金融是世界性难题,金融科技如果能解决最后一公里的触达,才真正有意义。”7、“中国的城商商业银行如果没有自己的核心客户群,没有核心竞争力的产品,没有差异化竞争,未来的出路在哪里?”这些客户心声给了蚂蚁技术团队非常大的触动,这些问题充满着困惑、焦虑但又极具启发,也促使蚂蚁金服对科技开放的模式进行更深层的思考,传统金融 IT 服务公司讲究的是卖系统、卖软件,鲜少从战略和业务提升的角度入手。然而,今天所有的需求归结到最本质的一点就是,金融科技的开放怎样才能真正给客户带来价值,科技到底能不能和业务的增长产生方程式般的关联度?正如美国柯林斯在其畅销书《从优秀到卓越》一书中所阐述的,技术变革从来不是实现从平庸到伟大的关键因素,在柯林斯眼中,技术是加速器而非驱动企业实现变革的第一推动力。同样,今天金融机构面向数字化的转型,是从之前的 IT 模式,过渡到互联网服务的模式,蚂蚁金服不仅需从自身实践的角度看问题,还要站在客户的视角去找到解决的办法,和他们一起跨越业务和技术之间的鸿沟。数字金融创新,不只是业务导流和线上风控,也不是单一的科技产品应用,而是抵达核心业务系统的转型创新,构建自身的核心数据资产。金融机构需要的是成本低、见效快、可用性高、符合金融级别安全标准、又能够真正带来业务价值的科技输出服务,帮助其进行快速敏捷的产品开发。金融机构的需求就是蚂蚁金服努力的方向。刘伟光和同事在重新梳理产品体系之后,从业务的视角切入,将金融最核心的三个元素抽象出来,推出了 “分布式金融核心套件” 这款新产品。简言之,“分布式金融核心套件” 是一个业务视角入手的产品。金融机构使用之后,既可以针对业务需求进行敏捷开发,快速获取客户,还能将产品工厂、资金交换、核算等金融机构核心业务技术能力封装成业务组件,以支撑各个业务线条的快速调用。蚂蚁金服将很多与此相关的技术能力封装在 “分布式金融核心套件” 之中,与生态 ISV 一起就能够将传统的业务能力叠加进来,从而快速支撑每一项业务的发展,将金融机构过去多个竖井式的核心系统架构改变为适应金融业务和产品快速迭代的分层领域架构,让银行的业务核心能力无处不在。创新与改变,一直是驱动着蚂蚁技术开放团队的源动力。2017年,南京银行引入蚂蚁金服分布式架构 SOFAStack、分布式数据库 Oceanbase 以及大数据平台能力,构建新的互联网核心。同年11月上线互联网金融平台 “鑫云+”,“鑫云+” 一端对接互联网平台的金融需求,另一端对接实际的金融产品和服务,通过使用金融级互联网架构设计模式、敏捷工具和微服务平台,“鑫云+” 从架构设计到上线投产仅用了5个月。上线之后,正好赶上了消费金融的大发展,在截至2018年6月底的最近8个月中,“鑫云+” 平台新客户数达到390万,每日贷款额从1万人民币上升至10亿人民币。“我们用一年时间干了过去十年的业务量”,南京银行信息科技部副总经理李勇感慨的说。三、蚂蚁技术发展历程开放的前提是强大的技术实力。解决实际问题、促进业务发展,一直是蚂蚁金服技术创新的底色。从支付宝最初的担保交易,到后来的快捷支付、余额宝,乃至今天的借呗、花呗、相互宝,蚂蚁金服的发展受益于技术的支撑。在这个过程中,公司内部也形成了独特技术部门组织架构:负责统一架构的事业部,以及各个业务单元里的应用技术事业部,平台技术与应用技术并行不悖,高度融合。为了展示蚂蚁的技术实力,2018年9月20日,杭州云栖大会 ATEC 主论坛现场上演了一场特别的技术秀。蚂蚁金服副CTO 胡喜现场模拟挖断支付宝近一半服务器的光缆。结果只过了26秒,模拟环境中的支付宝就完全恢复了正常。类似的灾备演练在蚂蚁金服内部经常举行,但是在一个公开的场合对外展示却并不容易。“几乎是我们所有人脱了一层皮”,胡喜这样说道,观众看到的是四根线剪掉了,然而由于数据和交易分布在不同的机房,其中牵涉不同的分布式架构,要调动底层数量众多的数据库系统一起协同,难度非常大。现在,蚂蚁金服的机房架构已经做到了 “三地五中心”,即在三座城市部署五个机房,一旦其中一个或两个机房发生故障,支付宝的底层技术系统会将故障城市的流量全部切换到运行正常的机房,并且能做到数据保持一致且零丢失。这样的技术架构,让蚂蚁金服的系统持续可用性因此达到了 “6个9”,即便用金融级别的标准来衡量的话,这个水平在全球也是首屈一指的。“这次是演习。而在真实环境下,如果支付宝部署在两个城市的两个机房同时出问题,跑在这两个机房上的支付宝账户恢复正常的速度是分钟级”,胡喜这样说道。胡喜2007年加入支付宝,37岁的他是阿里巴巴集团最年轻的合伙人之一,早些年,胡喜和他的同事,主要面临两个问题:一是让系统容量可以无限增长;二是希望系统持续可用。如果说分布式架构解决了第一个问题的话,那么 “异地多活” 技术的成熟,就意味着第二个问题已经不再是问题。回顾蚂蚁金服技术的发展历程,大致可以分为三个阶段,在第一个阶段,支付宝需要应对 “双十一” 大促海量并发的系统需求,支付宝自身的系统架构从原来的烟囱式改成了分布式,这个阶段支付宝完全是在自我探索;在支付宝掌握了分布式架构技术之后,又将这种技术应用在网商银行等场景,成熟后又通过互联网推进器计划向外输出,成功服务了南京银行等客户;在个案的探索之后,蚂蚁金服将自身的金融科技能力通过 “分布式金融核心套件” 的产品开放出来,将金融机构获取科技服务的门槛大大降低。“打穿打透” 的极致性技术追求,让胡喜和他的同事,将蚂蚁的技术带到了全球 Fintech 领域的最高点。也正是在那次会议上,胡喜宣布,蚂蚁金融云升级成为蚂蚁金融科技并全面开放,目的就是为行业提供完整的数字金融解决方案,而刘伟光就是这个项目的直接负责人。今天,站在金融科技这个赛道上,刘伟光和他的同事深知,任何公司和个人都要适应这个时代的快速转型,只有不停的去挑战自己,永不停歇的思考和不断颠覆自我,才可能保持一点点微小的领先。而这份微小的领先,也只有在开放共享,携手合作中才能焕发真正的能量。蚂蚁金服最希望成为这个赛道上,和众多的金融科技伙伴,始终携手同行的那一个。本文作者:平生栗子阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

December 12, 2018 · 1 min · jiezi

2018年阿里巴巴重要开源项目汇总(持续更新中)

摘要: 云栖社区特在2018年年末,将阿里巴巴的一些重要的开源项目进行整理,希望对大家有所帮助。开源展示了人类共同协作,成果分享的魅力,每一次技术发展都是站在巨人的肩膀上,技术诸多创新和发展往往就是基于开源发展起来的,没有任何一家网络公司可以不使用开源技术,仅靠自身技术而发展起来。阿里巴巴各个团队都是发自内心地将踩过的坑和总结的经验融入到开源项目中,供业界所有人使用,希望帮助他人解决问题。云栖社区特在2018年年末,将阿里巴巴的一些重要的开源项目进行整理,希望对大家有所帮助。(以下项目排名不分先后)这些优质的开源项目即将成立便于开发者交流的讨论钉钉群。社区将不断更新,欢迎大家加入讨论。前端1.数据驱动的高交互可视化图形语法 AntV - G2G2 是一套基于可视化编码的图形语法,以数据驱动,具有高度的易用性和扩展性,用户无需关注各种繁琐的实现细节,一条语句即可构建出各种各样的可交互的统计图表。同时,G2 也是 AntV 最重要的组成,始于《The Grammar of Graphics》一书描述的视觉编码语法系统(这也是 G2 项目命名的由来)。项目地址:https://github.com/antvis/g22.企业级中后台 UI 解决方案 Fusion DesignFusion Design 是一种旨在提升设计与开发之间 UI 构建效率的工作方式。通过建设基于 DPL 模式的,设计、前端之间的标准协议与工作流,来快速构建符合业务诉求的 DPL,提升 DPL 的构建效率和应用效率,帮助业务快速实现 UI 构建。项目地址:https://fusion.design/3.设计语言 & 前端框架 Ant DesignAnt Design 是蚂蚁金服开发和正在使用的一套企业级的前端设计语言和基于 React 的前端框架实现。它的特性:企业级金融产品的交互语言和视觉体系;丰富实用的 React UI 组件;基于 React 的组件化开发模式;背靠 npm 生态圈;基于 webpack 的调试构建方案,支持 ES6。地址:https://github.com/ant-design/ant-design4.基于 G2 封装的 React 图表库 BizChartsBizCharts 是一个基于 G2 封装的 React 图表库,具有 G2、React 的全部优点,可以让用户以组件的形式组合出无数种图表;并且集成了大量的统计工具,支持多种坐标系绘制,交互定制,动画定制以及图形定制等等。相信 BizCharts 定能成为您的数据可视化项目的强力助手。地址:https://github.com/alibaba/BizCharts5.企业级 Node.js 框架 Egg阿里开源的企业级 Node.js 框架。Egg.js 的插件机制有很高的可扩展性,一个插件只做一件事。Egg.js 通过框架聚合这些插件,并根据自己的业务场景定制配置,这样应用的开发成本就变得很低。地址:https://github.com/eggjs/egg6.基于 React 的前端框架 umiumi 是可插拔的企业级 React 框架,类似 Next.js。地址:https://github.com/umijs/umi7.前端构建和工程化工具 DawnDawn 取「黎明、破晓」之意,原为「阿里云·业务运营团队」内部的前端构建和工程化工具,现已完全开源。它通过 pipeline 和 middleware 将开发过程抽象为相对固定的阶段和有限的操作,简化并统一了开发人员的日常构建与开发相关的工作。地址:https://alibaba.github.io/dawn/8.页面逻辑原型 Ant UX这是一套制作页面逻辑的素材库,当你在产品设计初期,可以用它来绘制页面与页面之间的逻辑图,帮助你了解产品架构,业务流程。配合工具: OmniGraffle ,SketchOmniGraffle 可以用来绘制图表,流程图,组织结构图以及插图,也可以用来组织头脑中思考的信息,组织头脑风暴的结果,绘制心智图,作为样式管理器,或设计网页或PDF文档的原型。Sketch 是一个矢量设计软件,可以帮助你快速投入UI设计当中。地址: http://ux.ant.design/9.跨容器的渲染引擎 RaxRax 是阿里开源的一个通用的 JavaScript 库,主要有 React 兼容的 API 。 使用 React 的就应该已经知道如何使用 Rax。地址:https://alibaba.github.io/rax/10.基于 WebGL 的 Javascript 3D 绘图引擎 G3DG3D 是阿里巴巴开源的一款基于 WebGL 的 javascript 3D 绘图引擎。与其他的 WebGL 3D 引擎相比,G3D 是更加「纯粹」的渲染引擎,也就是说,它完全不依赖任何 DOM API,而是仅仅依赖一个 canvas 对象(或者类 canvas 对象)。该特性使得 G3D 不仅能够运行在浏览器环境中,也能够运行在一些非浏览器的 js 终端环境下(hybrid 环境),比如 GCanvas 环境(基于 Weex 或 ReactNative)。地址:https://alibaba.github.io/G3D/11.跨平台移动开发工具 WeexWeex 是开源的跨平台移动开发工具,能够完美兼顾性能与动态性,让移动开发者通过简捷的前端语法写出Native级别的性能体验,并支持iOS、安卓、YunOS及Web等多端部署。地址:https://github.com/apache/incubator-weex/12.基于 Weex 的 UI 组件库 Weex UiWeex Ui 是一个基于 Weex 的富交互、轻量级、高性能的 UI 组件库。地址:https://github.com/alibaba/weex-ui13.动效语言& React 框架动效解决方案 Ant MotionAnt Motion 是从蚂蚁金服的 Ant Design 中提炼出来的动效语言。它不仅仅是动效语言,同时也是一套 React 框架动效解决方案,提供了单项,组合动画,以及整套解决方案,帮助开发者更容易的在项目中使用动效。地址:https://motion.ant.design/14.多页面切换场景统一解决方案 UltraViewPagerUltraViewPager 是阿里开源的一个封装多种特性的 ViewPager ,主要是为多页面切换场景提供统一解决方案。地址:https://github.com/alibaba/UltraViewPager15.模块化 UI 界面方案 Tangram UITangram,七巧板,是天猫团队开源的跨平台模块化 UI 界面方案。Tangram 不仅仅是一个 Native(iOS & Android)的界面开发框架,而是从日常工作中沉淀出的一套界面解决方案,涵盖了 Native SDK、GUI操作台、后端逻辑容器、组件库机制的一整套方案。地址:http://tangram.pingguohe.net/16.自动化测试解决方案 MacacaMacaca是一套完整的自动化测试解决方案,支持移动端和PC端,支持Native, Hybrid, H5 等多种应用类型,并且提供客户端工具和持续集成服务。地址:https://github.com/alibaba/macaca17.基于 React 的中后台应用解决方案 飞冰 ICE飞冰(ICE) 是一套基于 React 的中后台应用解决方案,ICE 包含了一条从设计端到开发端的完整链路,帮助用户快速搭建属于自己的中后台应用。面向设计者端,ICE 提供了 ICE Design 设计语言,来给 UI 界面提供专业的视觉指导。面向开发者端,ICE 提供了 Iceworks 工具,这是一个图形化界面的开发平台,它承载了 ICE 的物料体系和开发体验。地址:https://alibaba.github.io/ice/18.HTML5 互动游戏引擎 HiloHilo 是阿里巴巴集团开发的一款HTML5跨终端游戏解决方案,ta可以帮助开发者快速创建HTML5游戏。地址:https://github.com/hiloteam/Hilo19.JavaScript 模板引擎 Velocity.jsVelocity.js 是velocity模板语法的javascript实现。Velocity 是基于Java的模板引擎,广泛应用在阿里集 体各个子公司。Velocity模板适用于大量模板使用的场景,支持复杂的逻辑运算,包含 基本数据类型、变量赋值和函数等功能。Velocity.js 支持 Node.js 和浏览器环境。地址:https://github.com/shepherdwind/velocity.js20. 非侵入式运行期 AOP 框架 DexposedDexposed 是阿里巴巴无线事业部第一个重量级 Andorid 开源软件,基于 ROOT 社区著名开源项目 Xposed 改造剥离了 ROOT 部分,演化为服务于所在应用自身的 AOP 框架。它支撑了阿里大部分 App 的在线分钟级客户端 bugfix 和线上调试能力。地址:https://github.com/alibaba/dexposed21. 自动化测试解决方案 UI RecorderUI Recorder 是一款零成本的整体自动化测试解决方案,一次自测等于多次测试,测一个浏览器等于测多个浏览器!地址:https://github.com/alibaba/uirecorder22. Android 应用热修复工具 AndFixAndFix 是阿里巴巴开源的 Android 应用热修复工具,帮助 Anroid 开发者修复应用的线上问题。Andfix 是 “Android hot-fix” 的缩写。地址:https://github.com/alibaba/AndFix23. 淘宝 React 框架 React Web淘宝前端团队开源项目React Web,通过与React Native一致的API构建Web应用。地址:https://github.com/taobaofed/react-web24.基于 Node.js 的自动化持续集成 ReliableReliable 是分布式架构的持续集成系统,由Macaca 团队的成员开发。适用于集成构建、集成构建等场景。她是典型的主从结构,分为reliable-master 与 reliable-slave 两部分。地址:https://github.com/reliablejs/reliable-master25. 模拟数据生成器 Mock.jsMock.js 是一款模拟数据生成器,旨在帮助前端攻城师独立于后端进行开发,帮助编写单元测试。提供了以下模拟功能:根据数据模板生成模拟数据,模拟 Ajax 请求,生成并返回模拟数据,基于 HTML 模板生成模拟数据。地址:https://github.com/nuysoft/Mock26. React 应用的服务器渲染框架 BeidouBeidou(北斗) 是 NodeJS & React 同构框架,基于Egg.js开发。地址:https://github.com/alibaba/beidouJava1.分布式应用服务开发的一站式解决方案 Spring Cloud AlibabaSpring Cloud Alibaba 致力于提供分布式应用服务开发的一站式解决方案。此项目包含开发分布式应用服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里分布式应用解决方案,通过阿里中间件来迅速搭建分布式应用系统。地址:https://github.com/spring-cloud-incubator/spring-cloud-alibaba2. JDBC 连接池、监控组件 DruidDruid是一个 JDBC 组件。监控数据库访问性能。提供了一个高效、功能强大、可扩展性好的数据库连接池。数据库密码加密。SQL执行日志。地址:https://github.com/alibaba/druid3. Java 的 JSON 处理器 fastjsonfastjson 是一个性能很好的 Java 语言实现的 JSON 解析器和生成器,来自阿里巴巴的工程师开发。主要特点:快速FAST (比其它任何基于Java的解析器和生成器更快,包括jackson);强大(支持普通JDK类包括任意Java Bean Class、Collection、Map、Date或enum);零依赖(没有依赖其它任何类库除了JDK)。地址:https://github.com/alibaba/fastjson4. 服务框架 DubboApache Dubbo (incubating) |是阿里巴巴的一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。地址:https://github.com/alibaba/dubbo5. 企业级流式计算引擎 JStormJStorm 是参考 Apache Storm 实现的实时流式计算框架,在网络IO、线程模型、资源调度、可用性及稳定性上做了持续改进,已被越来越多企业使用。JStorm 可以看作是 storm 的 java 增强版本,除了内核用纯java实现外,还包括了thrift、python、facet ui。从架构上看,其本质是一个基于 zk 的分布式调度系统。地址:https://github.com/alibaba/jstorm6. apns4japns4j 是 Apple Push Notification Service 的 Java 实现!地址:https://github.com/teaey/apns4j7. 分布式数据层 TDDLTDDL 是一个基于集中式配置的 jdbc datasource实现,具有主备,读写分离,动态数据库配置等功能。地址:https://github.com/alibaba/tb_tddl8. 轻量级分布式数据访问层 CobarClientCobar Client是一个轻量级分布式数据访问层(DAL)基于iBatis(已更名为MyBatis)和Spring框架实现。地址:https://github.com/alibaba/cobarclient9. 淘宝定制 JVM:TaobaoJVMTaobaoJVM 基于 OpenJDK HotSpot VM,是国内第一个优化、定制且开源的服务器版Java虚拟机。目前已经在淘宝、天猫上线,全部替换了Oracle官方JVM版本,在性能,功能上都初步体现了它的价值。地址:http://jvm.taobao.org10. Java 图片处理类库 SimpleImageSimpleImage是阿里巴巴的一个Java图片处理的类库,可以实现图片缩略、水印等处理。地址:https://github.com/alibaba/simpleimage11. redis 的 java 客户端 TedisTedis 是另一个 redis 的 java 客户端。Tedis 的目标是打造一个可在生产环境直接使用的高可用 Redis 解决方案。地址:https://github.com/justified/tedis12.开源 Java 诊断工具 ArthasArthas(阿尔萨斯)是阿里巴巴开源的 Java 诊断工具,深受开发者喜爱。Arthas 采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。地址:https://alibaba.github.io/arthas/13.动态服务发现、配置和服务管理平台 NacosNacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您实现动态服务发现、服务配置管理、服务及流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以“服务”为中心的现代应用架构(例如微服务范式、云原生范式)的服务基础设施。地址:https://nacos.io/en-us/14.Java 解析 Excel 工具 easyexcelJava 解析、生成 Excel 比较有名的框架有 Apache poi、jxl 。但他们都存在一个严重的问题就是非常的耗内存,poi 有一套 SAX 模式的 API 可以一定程度的解决一些内存溢出的问题,但 POI 还是有一些缺陷,比如 07 版 Excel 解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel 重写了 poi 对 07 版 Excel 的解析,能够原本一个 3M 的 excel 用 POI sax 依然需要 100M 左右内存降低到 KB 级别,并且再大的 excel 不会出现内存溢出,03 版依赖 POI 的 sax 模式。在上层做了模型转换的封装,让使用者更加简单方便。地址:https://github.com/alibaba/easyexcel15.高可用流量管理框架 SentinelSentinel 是面向微服务的轻量级流量控制框架,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。地址:https://github.com/alibaba/Sentinel16.基于多维度 Metrics 的系统度量和监控中间件 SOFALookoutLookout 是一个利用多维度的 metrics 对目标系统进行度量和监控的项目。Lookout 的多维度 metrics 参考 Metrics 2.0 标准。Lookout 项目分为客户端部分与服务器端部分。客户端是一个 Java 的类库,可以将它植入您的应用代码中采集 metrics 信息,客户端更多详情。服务端代码部分,将于下一版本提供。通过 LOOKOUT 的服务,可以对 metrics 数据进行收集、加工、存储和查询等处理,另外结合 grafana,可做数据可视化展示。地址:https://github.com/alipay/sofa-lookout17.基于 Spring Boot 的研发框架 SOFABootSOFABoot 是蚂蚁金服开源的基于 Spring Boot 的研发框架,它在 Spring Boot 的基础上,提供了诸如 Readiness Check,类隔离,日志空间隔离等等能力。在增强了 Spring Boot 的同时,SOFABoot 提供了让用户可以在 Spring Boot 中非常方便地使用 SOFAStack 相关中间件的能力。地址:https://github.com/alipay/sofa-boot18.轻量级 Java 类隔离容器 SOFAArkSOFAArk 是一款基于 Java 实现的轻量级类隔离容器,由蚂蚁金服公司开源贡献;主要为应用程序提供类隔离和依赖包隔离的能力;基于 Fat Jar 技术,应用可以被打包成一个自包含可运行的 Fat Jar,应用既可以是简单的单模块 Java 应用也可以是 Spring Boot 应用。可访问网址进入快速开始并获取更多详细信息。地址:https://alipay.github.io/sofastack.github.io/19.分布式链路追踪中间件 SOFATracerSOFATracer 是一个用于分布式系统调用跟踪的组件,通过统一的 traceId 将调用链路中的各种网络调用情况以日志的方式记录下来,以达到透视化网络调用的目的。这些日志可用于故障的快速发现,服务治理等。地址:https://github.com/alipay/sofa-tracer20.高性能 Java RPC 框架 SOFARPCSOFARPC 是一个高可扩展性、高性能、生产级的 Java RPC 框架。在蚂蚁金服 SOFARPC 已经经历了十多年及五代版本的发展。SOFARPC 致力于简化应用之间的 RPC 调用,为应用提供方便透明、稳定高效的点对点远程服务调用方案。为了用户和开发者方便的进行功能扩展,SOFARPC 提供了丰富的模型抽象和可扩展接口,包括过滤器、路由、负载均衡等等。同时围绕 SOFARPC 框架及其周边组件提供丰富的微服务治理方案。地址:https://github.com/alipay/sofa-rpc21.基于 Netty 的网络通信框架 SOFABoltSOFABolt 是蚂蚁金融服务集团开发的一套基于 Netty 实现的网络通信框架。为了让 Java 程序员能将更多的精力放在基于网络通信的业务逻辑实现上,而不是过多的纠结于网络底层 NIO 的实现以及处理难以调试的网络问题,Netty 应运而生。为了让中间件开发者能将更多的精力放在产品功能特性实现上,而不是重复地一遍遍制造通信框架的轮子,SOFABolt 应运而生。地址:https://github.com/alipay/sofa-bolt22.动态非侵入 AOP 解决方案 JVM-SandboxJVM-Sandbox,JVM 沙箱容器,一种基于 JVM 的非侵入式运行期 AOP 解决方案。地址:https://github.com/alibaba/jvm-sandbox23.面向云的分布式消息领域标准 OpenMessagingOpenMessaging 是由阿里巴巴发起,与雅虎、滴滴出行、Streamlio 公司共同参与创立,旨在创立厂商无关、平台无关的分布式消息及流处理领域的应用开发标准。地址:https://github.com/openmessaging/openmessaging-java24.P2P 文件分发系统 DragonflyDragonfly(蜻蜓)是阿里自研的 P2P 文件分发系统,用于解决大规模文件分发场景下分发耗时、成功率低、带宽浪费等难题。大幅提升发布部署、数据预热、大规模容器镜像分发等业务能力。开源版的 Dragonfly 可用于 P2P 文件分发、容器镜像分发、局部限速、磁盘容量预检等。它支持多种容器技术,对容器本身无需做任何改造,镜像分发比 natvie 方式提速可高达 57 倍,Registry 网络出流量降低99.5%以上。地址:https://github.com/alibaba/Dragonfly25.LayoutManager 定制化布局方案 vlayoutVirtualLayout是一个针对RecyclerView的LayoutManager扩展, 主要提供一整套布局方案和布局间的组件复用的问题。地址:https://github.com/alibaba/vlayout26.Java 代码规约扫描插件 P3C项目包含三部分:PMD 实现、IntelliJ IDEA 插件、Eclipse 插件地址:https://github.com/alibaba/p3c27.Android 容器化框架 AtlasAtlas 是由阿里巴巴移动团队自研的手机淘宝安卓客户端容器化框架,以容器化思路解决大规模团队协作问题,实现并行开发、快速迭代和动态部署,适用于 Android 4.x 以上系统版本的大小型 App 开发。地址:http://atlas.taobao.org/数据库1. 开源数据库 AliSQLAliSQL 是基于 MySQL 官方版本的一个分支,由阿里云数据库团队维护,目前也应用于阿里巴巴集团业务以及阿里云数据库服务。该版本在社区版的基础上做了大量的性能与功能的优化改进。尤其适合电商、云计算以及金融等行业环境。地址:https://github.com/alibaba/AliSQL2. 分布式数据库 OceanBaseOceanBase是一个支持海量数据的高性能分布式数据库系统,实现了数千亿条记录、数百TB数据上的跨行跨表事务,由淘宝核心系统研发部、运维、DBA、广告、应用研发等部门共同完成。地址:https://github.com/alibaba/oceanbase3. 分布式数据库同步系统 otterotter 基于数据库增量日志解析,准实时同步到本机房或异地机房的mysql/oracle数据库. 一个分布式数据库同步系统。地址:https://github.com/alibaba/otter4. Oracle 数据迁移同步工具 yugongyugong 是阿里巴巴推出的去 Oracle 数据迁移同步工具(全量 + 增量,目标支持 MySQL/DRDS)。地址:https://github.com/alibaba/yugong5. 分布式 SQL 引擎 LealoneLealone 为 HBase 提供一个分布式SQL引擎,尝试将BigTable(HBase)和 RDBMS (H2数据库) 结合的项目。地址:https://github.com/lealone/Lealone6. 关系型数据的分布式处理系统 CobarCobar 是关系型数据的分布式处理系统,它可以在分布式的环境下像传统数据库一样为您提供海量数据服务。以下是快速启动场景:地址:https://github.com/alibaba/cobar7. 分布式关系数据库 Alibaba WaspWasp 是类Google MegaStore & F1的分布式关系数据库。地址:https://github.com/alibaba/wasp系统1. 分布式文件系统 FastDFSFastDFS 是一个开源的分布式文件系统,她对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。特别适合以文件为载体的在线服务,如相册网站、视频网站等等。地址:https://github.com/happyfish100/fastdfs2. 消息中间件 Apache RocketMQRocketMQ 是一款分布式、队列模型的消息中间件,具有以下特点:能够保证严格的消息顺序提供丰富的消息拉取模式高效的订阅者水平扩展能力实时的消息订阅机制亿级消息堆积能力地址:https://github.com/apache/rocketmq/3. 分布式 key/value 存储系统 TairTair是一个Key/Value结构数据的解决方案,它默认支持基于内存和文件的两种存储方式,分别和我们通常所说的缓存和持久化存储对应。地址:https://github.com/alibaba/tair4. 系统信息采集和监控工具 TsarTsar是淘宝的采集工具,主要用来收集服务器的系统信息(如cpu,io,mem,tcp等)以及应用数据(如squid haproxy nginx等),tsar支持实时查看和历史查看,方便了解应用和服务器的信息!地址:https://github.com/alibaba/tsar5. 分布式消息中间件 Metamorphosis91560100541c91d5c14aedad0031b44492cbc892Metamorphosis (MetaQ) 是一个高性能、高可用、可扩展的分布式消息中间件,类似于LinkedIn的Kafka,具有消息存储顺序写、吞吐量大和支持本地和XA事务等特性,适用于大吞吐量、顺序消息、广播和日志数据传输等场景。地址:https://github.com/killme2008/Metamorphosis6. 淘宝Web服务器 TengineTengine是由淘宝网发起的Web服务器项目。它在Nginx的基础上,针对大访问量网站的需求,添加了很多高级功能和特性。Tengine的性能和稳定性已经在大型的网站如淘宝网,天猫商城等得到了很好的检验。它的最终目标是打造一个高效、稳定、安全、易用的Web平台。地址:https://github.com/alibaba/tengine7. 分布式文件系统 TFSTFS(Taobao FileSystem)是一个高可扩展、高可用、高性能、面向互联网服务的分布式文件系统,其设计目标是支持海量的非结构化数据。地址:https://github.com/alibaba/tfs8. 异步任务处理系统 TBScheduletbschedule 是一个简洁的分布式任务调度引擎,目的是让一种批量任务或者不断变化的任务,能够被动态的分配到多个主机的JVM中,不同的线程组中并行执行。所有的任务能够被不重复,不遗漏的快速处理。地址:https://github.com/taobao/TBSchedule9. 容器引擎 Pouch04a2ed22d016dde2b1e2f1a75dd6773b9edcd6aaPouch 是一款轻量级的容器引擎,拥有快速高效、可移植性高、资源占用少等特性,主要帮助阿里更快的做到内部业务的交付,同时提高超大规模下数据中心的物理资源利用率。地址:https://github.com/alibaba/pouch10. 在线分析查询系统 mdrillmdrill是一个分布式的在线分析查询系统,基于hadoop,lucene,solr,jstorm等开源系统作为实现,基于SQL的查询语法。 mdrill是一个能够对大量数据进行分布式处理的软件框架。mdrill是快速的高性能的,他的底层因使用了索引、列式存储、以及内存cache等技 术,使得数据扫描的速度大为增加。mdrill是分布式的,它以并行的方式工作,通过并行处理加快处理速度。地址:https://github.com/alibaba/mdrill教程1. 七天学会NodeJS文档七天学会 NodeJS 是阿里巴巴国际站前端技术部编写的开源文档,用于学习 Node.js。地址:https://github.com/nqdeng/7-days-nodejs2. Nginx开发从入门到精通nginx由于出色的性能,在世界范围内受到了越来越多人的关注,在淘宝内部它更是被广泛的使用,众多的开发以及运维同学都迫切的想要了解nginx模块的开发以及它的内部原理,但是国内却没有一本关于这方面的书,源于此我们决定自己来写一本。本书的作者为淘宝核心系统服务器平台组的成员,本书写作的思路是从模块开发逐渐过渡到nginx原理剖析。地址:https://github.com/taobao/nginx-book以上为阿里巴巴重点开源项目的分享,希望对大家有所帮助!更多技术干货开源,请关注云栖社区github:https://github.com/alibaba-developer本文作者:山哥在这里阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

December 12, 2018 · 4 min · jiezi

构筑敏捷能力中心,打造下一代数字银行“操作系统”!

摘要: 构筑敏捷能力中心,打造下一代数字银行“操作系统”!小蚂蚁说:近年来,越来越多国内外领先银行全力投入数字化转型。数字化转型是技术与商业模式的深度融合,最终结果是商业模式的变革。银行数字化转型是一个逐步递进的旅程,敏捷能力中心的打造如同建设下一代数字银行“操作系统”,在银行数字化之旅中具有里程碑意义。银行业已进入全面数字化时代,数字化转型将从改善客户连接的1.0时代,进入到以敏捷能力为核心的数字化2.0时代。蚂蚁金服认为,打破过去独立分散的系统架构,铸造以分布式平台为支撑的金融敏捷能力中心,将成为银行机构全面数字化转型、提升客户服务能力的重要引擎。2018年11月29至30日,由《当代金融家》全媒体集团、鸿儒金融教育基金、蚂蚁金服主办的2018年(第七届)中国中小银行发展高峰论坛在广州举办,论坛围绕新时代中小银行金融科技与风险防控展开,来自监管层、股份制银行、国内城商行、农商行代表及相关机构领约300位专业人士出席峰会。在演讲中指出,银行数字化转型是一个逐步递进的旅程,敏捷能力中心的打造如同建设下一代数字银行“操作系统”,在银行数字化之旅中具有里程碑意义。他解释道,“敏捷能力中心从根本上解决企业的管理效率,以及服务终端、前台创新的问题,可助力银行不断快速响应、探索、挖掘、引领用户的需求,提升用户体验,降低服务成本,在全面数字化时代领先行业。”曾经,金融行业由于对可靠性和风险管理的要求高于一般行业,而后台修改的成本和风险较⾼,所以驱使他们会尽量选择保持后台系统稳定性的IT建设模式,把大量的业务逻辑(业务能力)直接部署在前台系统中,形成厚重的前台系统。这样便形成业务联系彼此割裂的状态,并未能很好地支撑前台快速创新响应用户的需求,降低了用户体验。随着IT技术的不断发展,曾经微服务、DevOps等技术也给出了技术上实现敏捷能力的“银弹”。但局部的方案未能从根本上解决全行级敏捷问题,银行的业务部门、研发中心、数据运维中心各自都是拿着拼图的一部分寻找方向。峰会上,蚂蚁金服结合自己的实践,从自身的实践总结出覆盖技术、数据、业务多个维度的敏捷能力中心,与广大银行业伙伴共享,以助力各家银行机构打造自身的“数字化操作系统”。据介绍,数字化时代敏捷能力中心包括以下几个方面:敏捷技术中心:把分布式架构体系涉及到的研发、测试、治理、运维、风险形成完整的支持全行应用的技术平台。除此之外还有三地五中心的灾备架构、免疫架构、防御资金安全和不断地、主动地发现故障等。这些技术能力体现蚂蚁技术敏捷中心不断追求的在分布式架构下的敏捷与自愈能力,将“不确定性变为确定性”。敏捷智能中心:把数据智能计算、数据研发、数据资产有机组合形成全行统一数据平台和金融智能应用,例如针对全行风险八大领域,构建全行级风险平台,实现风险管理统一化、风险技术平台化、风险数据规划化。敏捷业务中心:沉淀分布式架构下组件化、服务化的业务核心能力,向上提供平台化领域服务、开放式的编程接口来帮助快速构建存贷汇、互金等各类业务产品;同时封装了金融级分布式架构中的技术复杂性,降低分布式转型的学习曲线,提升分布式转型速度。纵观银行数字化发展历程,数字化1.0从移动端开始,着力于改善客户连接,提升业务触达能力。而刘伟光分析道,到数字化2.0时代,银行等企业机构重点在于通过分布式架构,获得数字化原生企业的标准技术栈能力,来建立与数字化原生企业相对标的三大核心竞争力:技术敏捷能力、数据智能驱动能力、业务敏捷能力,打造面向下一代数字化转型的数字银行“操作系统”。基于这些能力,银行可在未来的数字化3.0时代,建设多方位的生态合作新型银行模式:对内进行业务场景接入,对外通过API Bank向企业开放、建设基于API的应用生态市场模式,构建基于金融组件化操作系统的开放生态银行。本文作者:平生栗子阅读原文本文为云栖社区原创内容,未经允许不得转载。

December 5, 2018 · 1 min · jiezi

跨境物流链路怎么做?菜鸟工程师打造了全球通关“神器”

阿里妹导读:2018天猫双11物流订单量创新高,突破10亿件,这是一次史无前例的物流洪峰。天猫双11十年来,见证了物流业从手写地址、人工分拣,到电子面单、机器人分拣。无论是物流园区、干线运输,还是秒级通关、末端配送,都通过技术高效连接,智能物流骨干网正在加快实现行业数字化、智能化升级。因此,阿里技术推出《菜鸟智慧新物流》专题,邀请菜鸟技术人,为你揭秘物流核心技术。今天第一期,让我们一起走近神秘的“神鲸网络全球通关平台”,全方位了解新技术时代下的跨境智能关务。前言“跨境”,这是在当今行业一个非常 fashion的名词。从2014年起,海关总署陆续颁发多项跨境贸易政策,给跨境进出口业务带来了诸多红利。2014年也被很多业内人士称为跨境进口电商元年。也许你会听到这样一段对话:A:Hey,兄dei,你是做什么的?B:哦,我搞跨境物流的。A:是嘛,跨境这几年很火啊!政府在大力扶持这一块,我看很多公司都在做一块,有前途!B:哪有?道路坎坷,很艰辛的,不过累并快乐着,我也挺看好这块的!在整个跨境物流链路中,会涉及到多个角色:仓、干线、关务、快递等。关务是跨境物流链路最核心的环节,需要协同海关,国检等政府部门完成整个进出口国的通关操作。这块不仅业务复杂,而且存在诸多不确定性。为此,我们搭建了“神鲸网络全球通关平台”。旨在对接海关,协同CP(cainiao partner),从线下到线上,以全球化、数据化、智能化为方向,以快速、轻量、多态为核心目标,为跨境电商客户提供全球一体化的通关解决方案!痛点和挑战如下是整个通关全链路业务流程,包含资质备案、风控、出入区、跨境通关、税汇等核心领域。整个链路交互节点繁多,不同国家,甚至不同监管区在申报模式,交互方式,通关能力上都存在很大差别,另外由于申报链路冗长,任何一个节点出现抖动都有可能导致整个通关发生异常,进而导致申报时效拖长,为保障用户能够正常通关,我们往往需要投入更多的成本去解决申报链路过程发生的种种问题。所以,如何有效使用海关通关能力,给到跨境商户稳定的、高效的、统一的一站式通关解决方案?这是我们需要攻克的核心难题。应对策略通关异常繁多,申报时效冗长,大促成本飙高是大部分跨境通关企业碰到的问题。如何去异常,打时效,降成本,保障通关丝般顺滑?基于此,在神鲸网络通关平台中,我们做了诸多举措,有一体化监控的星宫大盘,阳关道批量申报,政企协同全链路摸高压测以及守护神智能辅助系统等等。今年的双十一大促上,因为有这些强有力的后盾,加上海关通关能力再创新高,大家在喝茶+聊天中度过了一个轻松愉快的双十一。今天,我们重点来交流下星宫大盘、批量申报和智能辅助系统。星宫大盘,一体化监控整个通关链路长,依赖系统多,如果有一个全链路的监控系统进行护卫,不仅可以实时窥视整个链路流转情况,还可以做到异常的实时跟踪处理。为此,我们搭建了关务数据中心,承载关务所有数据,并依托它构建了整个星宫大盘产品,将业务监控,指标监控,系统监控一体化,真正实现360度无死角主动监控。如下图(注:下图中数据非真实数据,仅做示例):数据中心数据中心涵盖了整个关务生态的数据,通过实时+离线两种方式,很好的支撑了实时业务监控和指标监控等核心业务。如下是整个数据中心核心架构,包含消息接入,指标计算,数据存储等核心模块。作为一个数据产品,最基本的的诉求就是能保证数据实时性、准确性,那怎么在大促情况下能够做到99.99%数据准确性?这是数据中心面临的最大的一个挑战。实时性(秒级生效)业务系统通过消息埋点的方式记录各个链路节点数据,通过阿里消息中间件消息异步推送给数据中心。数据中心拥有一个支持水平扩展的庞大的服务器集群,具有强大的消息处理能力,保证消息的实时消费。通过缓存+异步存储的方式提升整体消息处理能力; 存储之前先往缓存存一份,后面热点查询优先从缓存获取数据,提高查询效率,数据插入如果超时或者失败立即创建调度任务进行异步重试插入。准确性(99.99%)由于关务业务特殊性,星宫需要保证监控数据的准确性,传统的方案一般是通过流计算的方式把数据统计出来,这种方案统计和详情数据是分开的,可能会导致数据统计和真实数据存在误差的情况,这对于星宫来说是不可接受的。为此,项目组另辟蹊径采用实时详情数据聚合的方案,这里,我们引入了ES中间件,阿里中间件团队针对ES做了非常多的优化,具有高性能的聚合能力,支持海量级数据的实时聚合。另外我们在数据结构存储上面做了多层优化,比如:热点查询条件用int来逻辑映射,字段存储底层采用列存储。为了加快检索,存储树形结构把目录加载到缓存,犹如数据字典一样。另外为了保证数据消费不丢失,在客户端启动了多层重试机制,保证数据的最终一致性。今年大促上,数据中心表现出色,双11当天QPS达到40000+ 平均耗时11ms,正是这种强大的数据消费能力保证了星宫数据实时性,另外亿级别数据多维度聚合统计基本上都是秒级返回,真正做到了100%可用。批量申报,独木桥变阳关道由于海关各个节点大多采用MQ+FTP的技术架构,文件数的个数会影响整体通道消费能力。另外总署的56号公告要求四单申报进行加签操作,随之带来的将是验签成本的增加。为减轻总署通道压力,并提升验签能力,我们采用了批量申报的策略,简而言之就是将多个订单聚合到一起进行申报,一次加签操作,一次申报动作。如下:批量申报调度模块自研了一个轻量的批量调度框架实现,通过一个任务池汇总所有任务,按照不同业务规则聚合同类型的任务然后进行消费。如下:记得当时该项目刚上线时,还有一个小插曲,压测下来发现整体性能远远达不到要求,这可急坏了整个项目组。任务消费过程大体分为:分页捞取任务-》锁定任务-》消费任务。其中捞取任务和锁定任务过程是通过抢占分布式锁的方式来防止并发,避免同一个任务被多个线程捞取并消费。正式由于这个分布式锁的限制以及单库单表的DB瓶颈,导致整体性能一直上不去。经过讨论,最终我们采用了分布式锁池+DB散列方案。即既然单个分布式锁无法满足要求,那么设计成锁池好了;既然单库单表存在瓶颈,那按照业务关键字进行散列。分布式锁池我们使用的是redis的Set数据结构+spop和sadd命令实现的,应用启动时初始化指定个数的锁放到Set数据结构中,然后通过spop随机获取一个模值捞取任务,任务锁定后再通过sadd返还锁,插入任务时也是通过锁个数进行随机散列到多个库多个表中。通过该机制改造后,整体性能大大提升,数据库压力也降低了好几倍。这次微小的调整,却带来了巨大的性能提升,在今年双十一大促上,批量申报也是大放异彩,整个通关审单时效大大降低,申报能力相比往年也有质的提升。如下是17年和18年双十一某一属地海关的平均审单时效对比,相比17年,今年的平均审单时效非常稳定,基本保持在20分钟以内,海关上行和下行通道毫无压力。如下是某海关30分钟内审单完成率情况,相比往年,今年审单能力有巨大的飞跃,基本上是零堆积,申报速度跟审单速度几乎持平。智能辅助系统,关务守护神今年是关务的智能化元年,在正向申报链路上,我们推出了智能限流与智能hold单产品,自适应保护自身与海关系统。在人工成本降低的同时,保证了海关系统的最大吞吐能力。在异常处理上,我们基于规则引擎上线了异常智能处理系统,通过不断丰富异常处理规则,系统变得越来越聪明,基本上可以自动处理大部分海关异常。同时,作为关务智能大脑,还为关务数据中心提供数据分析服务。智能系统包含产品如下:智能限流整个智能限流的设计不仅支持集群环境下任意接口秒级与分钟级精准限流,还能根据接口的RT与失败率等指标对接口流量进行动态调节。1.技术架构:智能限流分三个主要模块:资源监控(对资源的请求量精准统计)、限流策略(请求量达到阈值后的操作)、智能调控(依据一定的规则与算法调节)。智能限流整体采用了pipeline的设计模式,目前实现了单机限流、集群限流和自适应限流三个阀门,只有全部通过阀门,请求才能被放行,否则就会被拦截。这种设计便于维护以及后期限流策略的扩展,例如在双十一之前紧急增加的集群分钟级限流(开发测试仅半个人日)。单机限流和集群限流都是固定限流,即人为提前设定好接口的限流值,如果请求量超过这个值,便会被流控。人工限流的关键是对请求量的精准统计。动态限流则会依赖一些指标进行实时计算和分析,系统按照一定规则自行判断是否需要限流,这个限流是将接口能力分为档位进行调整,既会下调,也会自动上调恢复接口的能力。2.资源监控资源监控是指统计某个接口的各种指标,包含请求量/失败量/限流量等,这些指标基本上是在单台机器上的统计。但是在限流场景,单机限流仅仅能保护机器本身,对于下游的保护,还是需要集群限流功能,因此还需要对集群环境下的资源访问统计。单机指标统计单机指标主要是基于滑动窗口的原理进行统计,比如QPS(每秒的请求数)的统计是将1秒分为5个时间窗口,每个窗口统计200ms内的请求,最后做累加。单机指标监控主要是做单机限流,单机限流的最大好处是能够保障单台机器不会被上游压垮。而对下游而言,单机限流可用性较低,对集群数据来说准确性不能保障。集群指标统计我们不仅仅要保护自己,还要保护下游系统,因此需要保证集群环境下给到下游的量是精确可控制的。集群指标统计需要借助分布式缓存实现,通过使用incr原子累加功能,实现在分布式环境下对请求量的统计。针对QPS的统计,缓存的key由接口名称+秒级时间戳(yyyyMMddmmSS)组成,例如:xxxx_20181118012012。集群统计的准确性依赖两个点:一个是分布式缓存的性能,另一个是分布式环境下每台机器的时间一致性(NTP网络可以保证)。智能限流的缓存使用的是阿里集团的tair(MDB),单次读写平均在5ms以内,对于并发量不是特别大的业务系统来说误差完全可以接受。3.限流策略通过对资源的准确监控,人工固定限流比较容易实现,只要比较下当前的实际qps值和设定的qps值大小即可,达到设定的限流值该请求就会被终止(目前通过抛出指定类型的异常)。无论是秒级还是分钟级限流,只是监控的粒度不同,即统计的key的时间戳的区别,对于限流的逻辑完全一致。在限流时,我们还会进行主动通知,便于人工干预。异常智能处理异常智能处理主要是在日常和大促后的扫尾阶段发挥重要作用。它以关务知识大脑为核心,协同CP、小二、系统共同高效解决业务异常。处理的异常越多,沉淀的就越多,系统也会越来越智能。关务异常存在繁、杂、多、变等特点,如果靠人工去处理每一个异常订单,需要投入巨大的成本,时效也无法得到保障,在大促期间,异常订单量更是以百倍级别增长。技术是第一生产力,我们需要机器代替人工处理这些异常,系统自动处理也就应运而生。然而,经过不断的实践证明,短期单纯依赖系统自动处理所有的异常是不现实的,有部分还是需要人工介入(比如备案问题),然后再利用系统进行重新申报。因此,异常智能处理系统的目标是:搭建人机交互闭环机制,沉淀底层知识大脑,快速提高异常处理的智能化程度。在底层实现上,我们搭建了一套规则库用于知识沉淀,上层实时监听海关异常回执,实现大部分异常秒级处理;同时,启动定时异常处理任务,每天定点捞取遗漏订单进行处理;最后,为小二和CP推送需要人工介入的异常订单,处理完后再推送到系统,由系统接着处理后续流程。展望未来在全球化的道路上,我们任重而道远,AI智能,大数据协同是未来的方向,基于人工智能方式实现全球通关的丝般顺滑,让全球通关更简单!这是我们的宗旨和目标,我们会一步一个脚印一直走下去!未来的路还很长,我们迫切需要有更多的能人异士加入,一起谱写旷世不灭的传奇。如果你足够优秀,如果你不甘平庸,来吧,让我们一起风骚前行!本文作者:啸鹏阅读原文本文来自云栖社区合作伙伴“阿里技术”,如需转载请联系原作者。

December 4, 2018 · 1 min · jiezi

如何在一分钟内实现微服务系统下的架构可视化

为什么需要架构可视化随着企业进行微服务架构改造,系统架构复杂度越来越高,架构变化日益频繁,微服务改造后的实际架构模型可能与预期已经产生了巨大差异,架构师或系统运维人员很难准确记忆所有资源实例的构成和交互情况;其次,系统架构在动态演化过程中可能引入了一些不可靠的因素,比如弱依赖变强依赖、局部容量不足、系统耦合过重等,给系统的稳定性带了极大的安全隐患。所以我们每次在面对系统改造、业务大促以及稳定性治理工作之前,都会通过梳理架构图的方式,呈现系统架构中个组件之间的交互方式,架构可视化能够清晰的协助我们识别架构中存在的问题以及建立高可用的系统。架构可视化后,可以给我们带来以下几点但不局限于此的优势:确定系统边界一张好的架构图,应该明确系统所包含的各个组件以及各个组件之间的核心调用关系,这些组件的集合就是系统的处理边界,系统架构的边界在一定程度上也反映了业务域的边界。架构问题识别基于高可用的架构准则,结合可视化的架构图,可以评估架构可能存在的安全风险,比如系统在容灾、隔离以及自愈维度下的健壮性。其次,一些架构链路可视化工具(比如鹰眼)在实际工作中确实大大提高了开发者排查与定位问题的效率。提高系统可用性有了系统架构的上下游依赖关系图,在故障发生时,开发人员可以借助依赖数据快速定位到问题的来源,极大缩短问题修复时间(MTTR)。借助架构图,我们还可以梳理出系统中存在的强弱依赖,在业务高峰期对弱依赖进行降级,或者针对系统依赖的各个组件进行故障模拟,以评测系统整体在面对局部故障的可靠性。常见架构可视化的做法我们熟知的架构图是静态的停留在PPT上的,很多时候我们的架构已经发生了非常大的变化,但是我们还在使用那张看上去很经典却早已过时的架构图。长时间使用与实际架构不符的架构图对线上架构的认知的危害是巨大的,我们需要在脑海中不断更新对系统架构的视图,以保持对系统架构的敏感度。每年的大促或者重大系统改造成为我们梳理系统架构、对架构进行重新认知的机会,此刻我们需要通过各种工具查看系统的各个组件分布以及不同组件的内部与外部的依赖关系,这种梳理架构图的方法是最常用的方式,权且称之为“手工绘制法”。手工经常干的事情,就有追求效率的同学使用计算机系统带来的自动化手段帮助自己做这件事情,比如我们常常看到的基于数据埋点的微服务可视化解决方案,这类架构可视化手段通常在分布式追踪、APM等监控领域使用较多,下图为某APM产品提供的应用维度架构可视化方案:我们称这种可视化方式为“埋点式感知法”,架构组件的识别是依赖关键的核心类检测与埋点,此种方案存在以下弊端:语言相关性:只要是系统埋点,与语言相关的特征基本就拜托不了,需要针对不同语言提供不同的依赖包;不易维护:因为是对核心类的检测,当组件包做了重大变更时,需要同步变更;不易扩展:因为是客户端识别方案,客户端一旦开放出去,新组件的支持只能等待用户更新组件;规模受限:客户端识别的另一个缺点是算法受限,服务端进行识别,可以借助大数据分析等手段更有效准确的识别;还有一种自动化架构感可视化方法,我们称之为“无界架构感知”,是一种语言无关性的架构识别方案,其采用采集用户主机上的进程和容器的元数据、监控数以及网路数据的最最基础的数据,在服务端构建架构图。架构可视化还能怎么做为了最大限度上降低用户进行架构可视化的成本,我们采用了应用无侵入的方式微服务进行可视化,通过采集进程数据与网络调用数据,构建进程间的网络调用关系,构建微服务的架构信息。用户只需要安装我们AHAS Agent探针,即可完成架构可视化操作;对于阿里云云原生系统,我们提供了自动化安装方式,而无需登录机器。如何让架构可视化更有效我们同样认为架构可视化的有效性跟人的认知层次有关,架构可视化的重点是确定该工具是否更好的支持自顶向下方法、自下而上方法或者两者的结合。开发者更关心应用维度上的架构,架构师或者管理者更关心整体系统架构。所以需要针对不用的使用者提供不同层次的架构可视化视角。理想的架构图需要支持宏观维度以及不断下钻下的微观视角,我们对架构进行了分层设计,目前分为进程层、容器层和主机层,后期我们可能会继续上扩或者下钻支持地域层或者服务层。下图为一台阿里云ECS部署了微服务应用安装AHAS探针之后的可视化三层架构界面:应对架构的可变性没有哪家科技公司的系统架构是一成不变的,系统架构会随着系统的版本迭代不断进行演化。所以对架构可视化操作,还需要具备随着时间的推移可对架构信息进行自动更新已经回溯的能力。在我们提供的架构感知产品中默认架构图会随着时间自动刷新,同时支持对历史的回溯,你可以选择历史中的某一刻查看架构信息,比如,重大版本的变更时,发布前与发布后的系统架构是否发生了违背一些高可用原则的问题,抑或排查是否出现了不该有的依赖问题。架构可视化的核心软件架构可视化的核心点是寻找在软件体系结构中有意义和有效的元素视图以及这些元素之间的关系。我们认为一款优秀的软件架构可视化产品应该帮助用户排除掉不重要的信息,给用户呈现出对他们有价值的视图,特别是在微服务架构下庞大而复杂的调用关系链场景中。这里面的核心点是有意义和有效,要做到这两点,首先需要识别什么是有意义和有效的元素和关系,我们在此领域做的事情归纳起来就是“识别”,识别机器上的每个进程是什么,发生的网络调用远端是什么,唯有知晓了这些元素是什么我们才有理由和依据来判断是否对用户有意义以及其在用户架构中的重要程度。架构可视化中的元素识别在梳理了大量架构图,我们发现用户关心的架构元素主要分为三类:1. 自己的应用服务;2. 应用对外部的资源依赖;3. 服务器本身的信息。 应用对外部资源的依赖通常以其它应用和通用中间件或者存储服务两种形式存在。故我们将需要识别的进程分为:应用服务和常见的组件服务(比如Redis、MySQL等),这些组件服务又分为用户自建的服务和使用公有云提供的服务,特别是对于Cloud Native应用来说,云服务的识别显得格外重要。目前,我们提供了20种阿里云云服务的识别以及包含MySQL、Redis、Tomcat等常见的21种三方服务组件,此组件库还在不断扩张中,目的就是最大限度的知晓架构中的元素到底是什么。图中展示了通过识别服务识别出来的Nginx、Redis组件以及阿里云中的Mysql服务和AHAS服务有了架构可视化还能做什么架构可视化不是目的,只是实现系统高可用性的手段。借助架构感知采集到的架构数据,在识别了用户使用的组件(我们对MySQL、Redis、MQ等的统称)后,我们借助这些组件以及与组件匹配的故障库,可以给用户自动推荐这些组件可能遇到的故障,配合我们提供的评测服务让用户更方便地对组件进行各种故障的模拟与演练,以提高系统的健壮性。其次,通过架构感知识别Java Application 应用,如果发现其负载较高,配合我们提供的限流降级(阿里巴巴开源的Sentinel商业版)功能,为服务的持续可用性保驾护航。借助架构感知进行系统限流配置我们对AHAS的定位是一款数据分析型的高可用保障产品,帮助云原生架构系统实现高可用能力的提升。架构可视化是我们给用户提供的高效运维和管控的窗口,我们期望通过丰富的云原生数据体系配合架构图的可视化以及可操作性,建立起以应用为中心的运维一体化平台。在未来,我们会加强与其它云服务的集成,比如监控、容器服务,以丰富架构感知的数据维度;其次,会在数据的深度挖掘和智能化消费上投入更多精力,真正让数据成为企业的核心价值,让数据成为保障业务的稳定性的利器。本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。

November 30, 2018 · 1 min · jiezi

始于阿里,回归社区|阿里巴巴的开源之路

破土而出的生命力,源自理想主义者心底对技术的信念。开源曾经帮助 Redhat 在传统软件市场奠定了其行业地位。无独有偶,作为云计算时代的赶超者,谷歌也拿起了开源的武器,试图打乱 AWS 和 Azure 的节奏。目前,这一策略似乎正在奏效。如今云原生技术正席卷全球,云原生基金会在去年 KubeCon +CloudNativeCon NA 的现场宣布:其正在孵化的项目已达 14 个,入驻的厂家或产品已超过 300 家,并吸引了 2.2 万开发者参与项目代码贡献,其明星产品 Kubenetes 的 GitHub 上 Authors 和 Issues 量已排行开源领域的第二名。而 Kubenetes 正是 Google 开源的一个容器编排引擎。今年,KubeCon + CloudNativeCon 首次来到中国。在2018 KubeCon + CloudNativeCon的现场,阿里云研究员伯瑜向在场的开发者们宣布,CNCF 已将阿里巴巴云原生镜像分发系统 Dragonfly 接纳为其沙箱项目(Sandbox),并有机会成为国内首个从 CNCF 毕业的开源项目。目前已经毕业的两个项目,一个是 Kubernetes,另一个是 Prometheus。据悉,目前阿里巴巴已经有 8 个项目进入 CNCF 云原生全景图,分别是分布式服务治理框架 Dubbo、分布式消息引擎 RocketMQ、流量控制组件Sentinel、企业级富容器技术 PouchContainer、服务发现和管理 Nacos、分布式消息标准 OpenMessaging、云原生镜像分发系统 Dragonfly 和高可用服务 AHAS。时间回到 2016 年2016 年的那届双11,RocketMQ 创始人冯嘉和他的团队首次将低延迟存储解决方案应用于双11的支撑,经受住了流量的大考,整个大促期间,99.996%的延迟落在了 10ms 以内,完成了保障交易稳定的既定目标。对于读写比例几乎均衡的分布式消息引擎来说,这一技术上的突破,即便是放在全球范围内,也绝对是值得称赞的。另一边,在历时3个月的开源重塑后,冯嘉和他的团队启动了 RocketMQ 向Apache 软件基金会的捐赠之路,但迈出这一步并不容易。“当时国内的开源氛围还没有现在那么活跃,开源之后,很多设计、源码和文档的维护工作还不够理想,但我们就是想证明国内的开源项目和开源社区也可以在世界的开源舞台上发挥价值。”经过近一年的努力,在2017年9月25日,Apache 软件基金会官方宣布,阿里巴巴捐赠给 Apache 社区的开源项目 RocketMQ 从 Apache 社区正式毕业,成为 Apache 顶级项目(TLP),这是国内首个非 Hadoop 生态体系的Apache 社区顶级项目。值得一提的是,根据项目毕业前的统计,RocketMQ 有百分八十的新特性与生态集成来自于社区的贡献。2017 年,消息领域出现一件里程碑事件。分布式消息领域的国际标准 OpenMessaging 开源项目正式入驻 Linux 基金会,这是国内首个在全球范围发起的分布式计算领域的国际标准。消息通讯已经成为现代数据驱动架构的关键环节,但在全球范围内,消息领域仍然存在两大问题:一是缺乏供应商中立的行业标准,导致各种消息中间件的高复杂性和不兼容性,相应地造成了公司的产品低效、混乱和供应商锁定等问题。二是目前已有的方案框架并不能很好地适配云架构,即非云原生架构,因此无法有效地对大数据、流计算和物联网等新兴业务需求提供技术支持。这也是冯嘉和他的团队在开源 RocketMQ 过程中,开发者和合作伙伴经常会提到的问题:“在消息领域,市场上出现了各类不同的开源解决方案,这导致了用户更高的接入和维护成本,为了确保各个消息引擎间能正常通信,还要投入大量的精力去做兼容。”这时候,建立一套供应商中立,和语言无关的消息领域的事实标准,成为各社区成员共同的诉求。此后,在 2017 年 9 月,阿里巴巴发起 OpenMessaging 项目,并邀请了雅虎、滴滴出行、Streamlio共同参与,一年后,参与 OpenMessaging 开源标准社区的企业达10家之多,包括阿里巴巴、Datapipeline、滴滴出行、浩鲸科技、京东商城、青云 QingCloud、Streamlio、微众银行、Yahoo、中国移动苏州研发中心(按首字母排序),此外,还获得了 RocketMQ、RabbitMQ 和 Pulsar 3 个顶级消息开源厂商的支持。相比于开源一个分布式消息项目,一套开源标准能被各家厂商所接受,对整个国内开源领域而言,是更具有里程碑意义的事件。2017 年 9 月,Dubbo 重启开源。Dubbo 是阿里巴巴于 2012 年开源的分布式服务治理框架,是国内影响力最大、使用最广泛的开源服务框架之一,在 2016 年、2017 年开源中国发起的最受欢迎的中国开源软件评选中,连续两年进入 Top10 名单。2017 年 9 月 7 日,在 Github 将版本更新至 2.5.4,重点升级所依赖的 JDK 及其组件,随后连续发布了 11 个版本。并于2018年2月捐献给 Apache 软件基金会,希望借助社区的力量来发展 Dubbo,打消大家对于 Dubbo 未来的顾虑。项目重启半年后,Dubbo 项目负责人阿里巴巴高级技术专家北纬在接受媒体采访的时候,从战略、社区、生态和回馈四个方面谈了 Dubbo 重启开源背后的原因。“集团近几年开始将开源提到了新的战略高度,这次投入资源重启开源,核心是希望让开源发挥更大的社会价值,并和广大开发者一起,建立一个繁荣的Dubbo 生态,普惠所有使用 Dubbo 的人和 Dubbo 本身。”Dubbo项目组成员朱勇在今年上海的技术沙龙上分享Dubbo未来发展的过程中提到,其后续的规划是要解决好两个问题。第一个问题是重点关注技术趋势,例如云原生对Dubbo开源现状的影响。第二个问题是 Dubbo 本身定位的问题,除了保持技术上的领先性,还需要围绕 Dubbo 核心发展生态,和社区成员一起将 Dubbo 发展成一个服务化改造的整体解决方案。2017 年 11 月,阿里自研容器技术 PouchContainer 开源。在开源不到一年的时间里,PouchContainer 1.0 GA 版本发布,达到可生产级别。今年 8 月,PouchContainer 被纳入开源社区开放容器计划 OCI;9 月,被收录进高校教材《云计算导论》;11 月,PouchContainer 团队携蚂蚁金服容器团队、阿里云 ACS 团队,与容器生态 Containerd 社区 Maintainer进行技术交流,有望发展成 Containerd 社区 Maintainer 席位,代表国内企业在世界容器技术领域发声。PouchContainer 发展速度之快,超出了宏亮的想象。宏亮是 Docker Swarm 容器集群项目的核心代码维护者(Maintainer),并于2015 年 8 月出版了《Docker 源码分析》一书,对 Docker 架构和源代码进行了深入的讲解,该书在 Docker 领域迅速成为畅销书籍。2017 年,宏亮承担起阿里自有容器技术的对内支持和对外技术布道的工作,秉承初心,希望在竞争激烈的容器开源领域能抢下属于国内容器技术的一席之地。在团队的努力下,阿里集团内部已实现 100%的容器化,并已经开始涉及离线业务,实现在、离线业务的混合调度与部署。整个集团能实现 100%的容器化,离不开阿里内部自研的 P2P 分发技术,该项目取名为 Dragonfly,寓意点与点之间的文件分发能如蜻蜓般轻盈和迅速,解决传统文件发布系统中的大规模下载、远距离传输、带宽成本和安全传输的问题。日前,Dragonfly 正式进入 CNCF, 并成为国内第三个被列为沙箱级别(Sandbox Level Project)的开源项目,可见,CNCF 在其云原生的技术版图中正希望借助Dragonfly等优秀的镜像分发技术,以提升企业微服务架构下应用的交付效率。始于阿里,回归社区。今年夏天,国内开源领域,迎来了两位新成员。作为微服务和云原生生态下的两款重要开源框架/组件,Nacos主打云原生应用中的动态服务发现、配置和服务管理,Sentinel 则是聚焦在限流和降级两个方面。Nacos 和 Sentinel 均是在阿里近 10 年的核心业务场景下沉淀所产生的,他们的开源是对微服务和元原生领域开源技术方案的有效补充,同时也非常强调融入开源生态,除了兼容 Dubbo和Sentinel,也支持对Spring Cloud 和 Kubenetes 等生态,以增强自身的生命力。“阿里巴巴早在 2007 年进行从 IOE 集中式应用架构升级为互联网分布式服务化架构的时候,就意识到在分布式环境中,诸如分布式服务治理,数据源容灾切换、异地多活、预案和限流规则等场景下的配置变更难题,因为在一个大型的分布式系统中,你没有办法把整个分布式系统停下来,去做一个软件、硬件或者系统的升级。”阿里巴巴高级技术专家坤宇在 2017 QCon 的现场分享到。在配置变更领域,我们从2008年的无 ConfigServer 时代,借用硬件负载设备 F5 提供的 VIP 功能,通过域名方式来实现服务提供方和调用方之间的通信,逐步经历了 ConfigServer 单机版、集群版的多次迭代,不断提高其稳定性。曾写下支付宝钱包服务端第一行代码的阿里高级技术专家慕义,在今年深圳的技术沙龙现场回忆了阿里注册中心自研的 10 年路:“这期间,集团业务经历了跨越式的发展,每年翻番的服务规模,不断的给 ConfigServer 的技术架构演进带来更高的要求和挑战,使得我们有更多的机会在生产环境发现和解决一个个问题的过程中,实现架构的一代代升级。Nacos 便是在这样的背景下,经过几代技术人的技术攻坚所产生的。”我们希望 Nacos 可以帮助开发者获得有别于原生或其他第三方服务发现和动态配置管理解决方案所提供的能力,满足开发者们在微服务落地过程当中对工业级注册中心的诉求,缩短想法到实现的路径。巧的是,一边是 Nacos宣布开源,另一边是 Spring Cloud 生态下的服务注册和发现组件 Netflix Eureka 宣布闭源,勇敢者的游戏充满了变数,但在坤宇和他的团队看来,这场游戏自己可以走到最后,因为我们并不是一个人在战斗,Nacos 只是阿里众多开源项目中的一员,随后还会有更多的开源项目反哺给社区,形成生态,例如轻量级限流降级组件 Sentinel。7月29日,Aliware Open Source•深圳站现场,只能容纳 400 人的场地,来了 700 多位开发者。阿里巴巴高级技术专家子矜在现场宣布了轻量级限流降级组件 Sentinel 的开源。作为阿里巴巴“大中台、小前台”架构中的基础模块,Sentinel 经历了 10 年双 11 的考验覆盖了阿里的所有核心场景,也因此积累了大量的流量归整场景以及生产实践。Sentinel 的出现,离不开阿里历届高可用架构团队的共同努力。“在双11备战中,容量规划是最重要也是最具挑战的环节之一。从第一年开始,双11的 0 点时刻就代表了我们的历史最高业务访问量,它通常是日常流量的几十倍甚至上百倍。因此,如何让一个技术和业务持续复杂的分布式站点去更平稳支撑好这突如其来的流量冲击,是我们这 10 年来一直在解的题。”阿里巴巴高可用架构团队资深技术专家游骥在今年的双11结束后分享道。这 10 年,容量规划经历了人工估算、线下压测、线上压测、全链路压测、全链路压测和隔离环境、弹性伸缩相结合的 5 个阶段。2013 年双11结束后,全链路压测的诞生解决了容量的确定性问题。作为一项划时代的技术,全链路压测的实现,对整个集团而言,都是一件里程碑事件。随后,基于全链路压测为核心,打造了一系列容量规划相关的配套生态,提升能力的同时,降低了整个环节的成本、提升效率。随着容量规划技术的不断演进,2018 年起,高可用架构团队希望可以把这些年在生成环境下的实践,贡献给社区,之后便有了 Sentinel 的开源。一边是作为发起者。将自己生产环境实践下沉淀出来的架构和技术贡献给社区。另一边是作为参与者。基于一些开源项目或云平台,输出可以解决开发者当前工作中存在的痛点的解决方案,例如近期新开源的项目 Spring Cloud Alibaba 和 开发者工具 Alibaba Cloud Toolkit。相同的是,技术理想主义者都希望技术可以为让世界变得更好,这才是技术人的兴奋点。“让世界的技术因为阿里巴巴而变得更美好一点点”。这是阿里巴巴系统软件、中间件、研发效能事业部负责人毕玄邮件签名中的一句话。他正和一群技术理想主义者,与太平洋另一边的技术高手们正面PK,在这场躲不开的战役中,一起认真一把。本文作者:amber涂南阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

November 22, 2018 · 2 min · jiezi

始于阿里,回归社区:阿里8个项目进入CNCF云原生全景图

摘要: 一群技术理想主义者,与太平洋另一边的技术高手们正面PK,在这场躲不开的战役中,一起认真一把。破土而出的生命力,源自理想主义者心底对技术的信念。云原生技术正席卷全球,云原生基金会在去年KubeCon +CloudNativeCon NA的现场宣布:其正在孵化的项目已达14个,入驻的厂家或产品已超过300家,并吸引了2.2万开发者参与项目代码贡献,其明星产品Kubenetes 的GitHub 上Authors 和 Issues 量已排行开源领域的第二名。今年,KubeCon + CloudNativeCon 首次来到中国。在2018 KubeCon + CloudNativeCon的现场,阿里云研究员伯瑜向在场的开发者们宣布,CNCF已将阿里巴巴云原生镜像分发系统Dragonfly接纳为其沙箱项目(Sandbox),并有机会成为国内首个从CNCF毕业的开源项目。目前已经毕业的两个项目,一个是Kubernetes,另一个是Prometheus。据悉,目前阿里巴巴已经有8个项目进入CNCF云原生全景图,分别是分布式服务治理框架Dubbo、分布式消息引擎RocketMQ、流量控制组件Sentinel、企业级富容器技术PouchContainer、服务发现和管理Nacos、分布式消息标准OpenMessaging、云原生镜像分发系统Dragonfly和高可用服务AHAS。时间回到2016年2016年的那届双11,RocketMQ创始人冯嘉和他的团队首次将低延迟存储解决方案应用于双11的支撑,经受住了流量的大考,整个大促期间,99.996%的延迟落在了10ms以内,完成了保障交易稳定的既定目标。对于读写比例几乎均衡的分布式消息引擎来说,这一技术上的突破,即便是放在全球范围内,也绝对是值得称赞的。另一边,在历时3个月的开源重塑后,冯嘉和他的团队启动了RocketMQ向Apache 软件基金会的捐赠之路,但迈出这一步并不容易。“当时国内的开源氛围还没有现在那么活跃,开源之后,很多设计、源码和文档的维护工作还不够理想,但我们就是想证明国内的开源项目和开源社区也可以在世界的开源舞台上发挥价值。”经过近一年的努力,在2017年9月25日,Apache 软件基金会官方宣布,阿里巴巴捐赠给 Apache 社区的开源项目 RocketMQ 从 Apache 社区正式毕业,成为 Apache 顶级项目(TLP),这是国内首个非 Hadoop 生态体系的Apache 社区顶级项目。值得一提的是,根据项目毕业前的统计,RocketMQ有百分八十的新特性与生态集成来自于社区的贡献。2017年,消息领域出现一件里程碑事件分布式消息领域的国际标准OpenMessaging开源项目正式入驻Linux基金会,这是国内首个在全球范围发起的分布式计算领域的国际标准。消息通讯已经成为现代数据驱动架构的关键环节,但在全球范围内,消息领域仍然存在两大问题:一是缺乏供应商中立的行业标准,导致各种消息中间件的高复杂性和不兼容性,相应地造成了公司的产品低效、混乱和供应商锁定等问题。二是目前已有的方案框架并不能很好地适配云架构,即非云原生架构,因此无法有效地对大数据、流计算和物联网等新兴业务需求提供技术支持。这也是冯嘉和他的团队在开源RocketMQ过程中,开发者和合作伙伴经常会提到的问题:“在消息领域,市场上出现了各类不同的开源解决方案,这导致了用户更高的接入和维护成本,为了确保各个消息引擎间能正常通信,还要投入大量的精力去做兼容。”这时候,建立一套供应商中立,和语言无关的消息领域的事实标准,成为各社区成员共同的诉求。此后,在2017年9月,阿里巴巴发起OpenMessaging项目,并邀请了雅虎、滴滴出行、Streamlio共同参与,一年后,参与OpenMessaging开源标准社区的企业达10家之多,包括阿里巴巴、Datapipeline、滴滴出行、浩鲸科技、京东商城、青云QingCloud、Streamlio、微众银行、Yahoo、中国移动苏州研发中心(按首字母排序),此外,还获得了RocketMQ、RabbitMQ和Pulsar 3个顶级消息开源厂商的支持。相比于开源一个分布式消息项目,一套开源标准能被各家厂商所接受,对整个国内开源领域而言,是更具有里程碑意义的事件。2017年9月,Dubbo重启开源Dubbo 是阿里巴巴于2012年开源的分布式服务治理框架,是国内影响力最大、使用最广泛的开源服务框架之一,在2016年、2017年开源中国发起的最受欢迎的中国开源软件评选中,连续两年进入Top10名单。2017年9月7日,在Github将版本更新至2.5.4,重点升级所依赖的JDK及其组件,随后连续发布了11个版本。并于2018年2月捐献给 Apache 软件基金会,希望借助社区的力量来发展 Dubbo,打消大家对于 Dubbo 未来的顾虑。项目重启半年后,Dubbo 项目负责人阿里巴巴高级技术专家北纬在接受媒体采访的时候,从战略、社区、生态和回馈四个方面谈了Dubbo重启开源背后的原因。“集团近几年开始将开源提到了新的战略高度,这次投入资源重启开源,核心是希望让开源发挥更大的社会价值,并和广大开发者一起,建立一个繁荣的Dubbo生态,普惠所有使用 Dubbo 的人和Dubbo本身。”Dubbo项目组成员朱勇在今年上海的技术沙龙上分享Dubbo未来发展的过程中提到,其后续的规划是要解决好两个问题。第一个问题是重点关注技术趋势,例如云原生对Dubbo开源现状的影响。第二个问题是 Dubbo 本身定位的问题,除了保持技术上的领先性,还需要围绕 Dubbo 核心发展生态,和社区成员一起将 Dubbo 发展成一个服务化改造的整体解决方案。2017年11月,阿里自研容器技术PouchContainer开源在开源不到一年的时间里,PouchContainer 1.0 GA 版本发布,达到可生产级别。今年8月,PouchContainer 被纳入开源社区开放容器计划OCI;9月,被收录进高校教材《云计算导论》;11月,Pouch团队携蚂蚁金服容器团队、阿里云ACS团队,与容器生态 Containerd社区 Maintainer进行技术交流,有望发展成 Containerd 社区 Maintainer 席位,代表国内企业在世界容器技术领域发声。PouchContainer发展速度之快,超出了宏亮的想象。宏亮是 Docker Swarm 容器集群项目的核心代码维护者(Maintainer),并于2015年8月出版了《Docker 源码分析》一书,对 Docker 架构和源代码进行了深入的讲解,该书在Docker领域迅速成为畅销书籍。2017年,宏亮承担起阿里自有容器技术的对内支持和对外推广工作,秉承初心,希望在竞争激烈的容器开源领域能抢下属于国内容器技术的一席之地。在他和团队的努力下,阿里集团内部已实现100%的容器化,并已经开始涉及离线业务,实现在、离线业务的混合调度与部署。整个集团能实现100%的容器化,离不开阿里内部自研的P2P分发技术,该项目取名为蜻蜓 Dragonfly,寓意点与点之间的文件分发能如蜻蜓般轻盈和迅速,解决传统文件发布系统中的大规模下载、远距离传输、带宽成本和安全传输的问题。日前,Dragonfly 正式进入 CNCF, 并成为国内第三个被列为沙箱级别(Sandbox Level Project)的开源项目,可见,CNCF 在其云原生的技术版图中正希望借助蜻蜓等优秀的镜像分发技术,以提升企业微服务架构下应用的交付效率。始于阿里,回归社区。今年夏天,国内开源领域,迎来了两位新成员。作为微服务和云原生生态下的两款重要开源框架/组件,Nacos主打云原生应用中的动态服务发现、配置和服务管理,Sentinle则是聚焦在限流和降级两个方面。Nacos和Sentinel均是在阿里近10年的核心业务场景下沉淀所产生的,他们的开源是对微服务和元原生领域开源技术方案的有效补充,同时也非常强调融入开源生态,除了兼容Dubbo和Sentinel,也支持对Spring Cloud 和 Kubenetes等生态,以增强自身的生命力。“阿里巴巴早在 2007 年进行从 IOE 集中式应用架构升级为互联网分布式服务化架构的时候,就意识到在分布式环境中,诸如分布式服务治理,数据源容灾切换、异地多活、预案和限流规则等场景下的配置变更难题,因为在一个大型的分布式系统中,你没有办法把整个分布式系统停下来,去做一个软件、硬件或者系统的升级。”阿里巴巴高级技术专家坤宇在2017 QCon的现场分享到。在配置变更领域,我们从2008年的无 ConfigServer 时代,借用硬件负载设备F5提供的VIP功能,通过域名方式来实现服务提供方和调用方之间的通信,逐步经历了ConfigServer单机版、集群版的多次迭代,不断提高其稳定性。曾写下支付宝钱包服务端第一行代码的阿里高级技术专家慕义,在今年深圳的技术沙龙现场回忆了阿里注册中心自研的10年路:“这期间,集团业务经历了跨越式的发展,每年翻番的服务规模,不断的给ConfigServer的技术架构演进带来更高的要求和挑战,使得我们有更多的机会在生产环境发现和解决一个个问题的过程中,实现架构的一代代升级。Nacos便是在这样的背景下,经过几代技术人的技术攻坚所产生的。”我们希望Nacos可以帮助开发者获得有别于原生或其他第三方服务发现和动态配置管理解决方案所提供的能力,满足开发者们在微服务落地过程当中对工业级注册中心的诉求,缩短想法到实现的路径。巧的是,一边是 Nacos宣布开源,另一边是Spring Cloud生态下的服务注册和发现组件Netflix Eureka宣布闭源,勇敢者的游戏充满了变数,但在坤宇和他的团队看来,这场游戏自己可以走到最后,因为我们并不是一个人在战斗,Nacos只是阿里众多开源项目中的一员,随后还会有更多的开源项目反哺给社区,形成生态,例如轻量级限流降级组件 Sentinel。7月29日,Aliware Open Source•深圳站现场,只能容纳400人的场地,来了700多位开发者。阿里巴巴高级技术专家子矜在现场宣布了轻量级限流降级组件Sentinel的开源。作为阿里巴巴“大中台、小前台”架构中的基础模块,Sentinel经历了10年双11的考验覆盖了阿里的所有核心场景,也因此积累了大量的流量归整场景以及生产实践。Sentinel的出现,离不开阿里历届高可用架构团队的共同努力。“在双11备战中,容量规划是最重要也是最具挑战的环节之一。从第一年开始,双11的0点时刻就代表了我们的历史最高业务访问量,它通常是日常流量的几十倍甚至上百倍。因此,如何让一个技术和业务持续复杂的分布式站点去更平稳支撑好这突如其来的流量冲击,是我们这10年来一直在解的题。”阿里巴巴高可用架构团队资深技术专家游骥在今年的双11结束后分享道。这10年,容量规划经历了人工估算、线下压测、线上压测、全链路压测、全链路压测和隔离环境、弹性伸缩相结合的5个阶段。2013年双11结束后,全链路压测的诞生解决了容量的确定性问题。作为一项划时代的技术,全链路压测的实现,对整个集团而言,都是一件里程碑事件。随后,基于全链路压测为核心,打造了一系列容量规划相关的配套生态,提升能力的同时,降低了整个环节的成本、提升效率。随着容量规划技术的不断演进,2018年起,高可用架构团队希望可以把这些年在生成环境下的实践,贡献给社区,之后便有了Sentinel的开源。一边是作为发起者。将自己生产环境实践下沉淀出来的架构和技术贡献给社区。另一边是作为参与者。基于一些开源项目或云平台,输出可以解决开发者当前工作中存在的痛点的解决方案,例如近期新开源的项目Spring Cloud Alibaba 和 开发者工具 Alibaba Cloud Toolkit。相同的是,技术理想主义者都希望技术可以为让世界变得更好,这才是技术人的兴奋点。“让世界的技术因为阿里巴巴而变得更美好一点点”。这是阿里巴巴毕玄邮件签名中的一句话。他正和一群技术理想主义者,与太平洋另一边的技术高手们正面PK,在这场躲不开的战役中,一起认真一把。本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

November 20, 2018 · 1 min · jiezi

蚂蚁金融科技全面开放战略背后的技术布局

导读:蚂蚁金融科技全面开放战略的公布,意味着蚂蚁金融科技正式进入全新的3.0时代。蚂蚁金融科技15年来的演进,在其发展史上不断留下了技术里程碑,同时,也缔造出一个又一个的业界“奇迹”。本文将深度揭秘蚂蚁金服的技术战略及布局。11月7日,第五届世界互联网大会在浙江乌镇开幕。当天下午,代表着行业最高水准的“世界互联网领先科技成果”发布,蚂蚁金服自主可控的金融级商用区块链平台等十余项先进技术获得此项殊荣。11月8日,世界互联网大会上由中国人民银行科技司主办的“金融科技与信用社会建设”主题论坛,蚂蚁金服宣布基于网络黑灰产防控治理的“天朗计划”全面升级,将蚂蚁风险大脑对传销、非法集资、金融诈骗等金融风险防控融入其中,同时,蚂蚁风险大脑也将会全面赋能合作伙伴,为其业务发展保驾护航,为投资者和消费者提供一个天朗气清的网络空间。而在同期举行的 “互联网之光”博览会上,蚂蚁金服不仅展示了15年来的技术演进之路,而且重点展示了区块链技术和蚂蚁风险大脑等相关产品及解决方案。蚂蚁金服15年技术演进之路经过15年的发展,蚂蚁金服已经成长为中国最重要的金融科技平台。除了支付、信贷、保险、理财、征信等众多业务,在金融技术的研发、投入及开放上,也已深耕不辍了多年。最初的蚂蚁金融云逐渐演变,在积累了众多云计算能力和技术组件的基础上,早已超越了传统金融云的概念,而囊括了云计算、大数据、AI、IoT、区块链等一整套技术体系。我们势必需要重新认识蚂蚁金服,将它的立体与多维展现出来,让金融科技的价值被真正重视!全面开放:数字金融技术整体解决方案当下,以支付宝为代表的蚂蚁金服的产品从一开始就要接受来自于网上的各种检验、钓鱼、攻击、窃取。面对一个开放的系统,没有办法关起门来,需要去研发各种技术,这对技术能力是非常大的考验。3年前,蚂蚁金服启动了“互联网推进器”计划,该计划表明,蚂蚁金服将在5年内助力超过1000家金融机构向新金融转型升级,在平台、数据和技术等方面实施能力全面对外开放。从2015年蚂蚁金融云发布到2016年GeaBase在支付场景上线,从2017年OceanBase 三地五中心集群部署到2018年“蚂蚁风险大脑”上线,蚂蚁金融科技开放一直在往纵深方向发展。到了今天,蚂蚁金服已经逐渐形成了“点线面”相结合的技术解决方案体系,包含了海量金融交易技术、金融智能技术、新一代金融交互技术、金融安全、区块链、综合技术等。在蚂蚁金融科技的大版图上,强调“技术开放”,而不是“技术输出”,或许这不仅仅是喊的口号不同,更是因为两者所导致的结果是真正差异化的:“开放”更具开源精神,它助推着金融科技的融合与创新,让新金融变成了“更好的金融”。在此前云栖ATEC主论坛上,蚂蚁金服副CTO胡喜就宣布,蚂蚁金服的金融科技正式全面开放,为行业提供完整的数字金融解决方案。包括容灾系统在内的多项核心技术和解决方案,如金融安全、蚂蚁风险大脑、区块链等都将对合作伙伴开放。五大关键技术详解:蚂蚁金服的“完整答卷”除了让金融机构摆脱传统IT架构的束缚外,蚂蚁金服也成就了自己世界级的技术能力。在多条重要技术主线上,无数的金融场景都能被一一对应,蚂蚁金服也由此不断进入“新领地”,在技术前沿展开探索。因此,主要梳理的技术主线有如下5条:海量金融交易技术交易技术是支付宝创造“人间奇迹”的起点。2017年的“双11”,支付宝凭借多项新纪录成为当天的主角:11秒钟破亿,28秒钟破10亿,3分01秒破100亿,6分05秒钟破200亿。根据支付宝官方数据,第5分22秒,双11的支付峰值达到25.6万笔/秒,同时蚂蚁金服自主研发的数据库处理峰值达到4200万次/秒,双双创下新纪录。而海量金融交易技术的背后,其实是分布式架构所带来的创新优势,敏捷迭代、容灾安全、弹性伸缩构成了分布式架构转型升级的三大驱动力。其中,OceanBase分布式数据库、SOFA ware分布式中间件、CAF?容器云平台三大技术构成了蚂蚁金服金融级的分布式架构。金融交易技术最关键的目标是“数据不丢失,业务不停机”。目前,TRaaS 技术风险防控平台(Technological Risk-defence as a Service)已一跃成为技术风险领域最为成熟的产品,在高可用架构(异地多活、全链路压测)、资损防控(交易实时核对、自动决策)、智能运维(AIOps故障自愈)等功能上做到了极致。而金融级分布式架构SOFAStack(Scalable Open Financial Architecture Stack)专注为金融用户提供安全、敏捷的基础架构能力,解决传统集中式架构转型的困难,将传统集中式架构转变为分布式系统架构。以人保健康为例,借助SOFAStack,其互联网保险云核心业务处理能力提升了上千倍,并支持弹性扩容,出单时间达到每秒1000单,外部渠道产品接入效率提升6倍,新产品上线时间缩短80%以上。经过四代架构演进,八年“双十一”的考验,现在SOFA已经从中间件这一层开始,逐渐对外进行开放和开源。金融安全技术安全是蚂蚁金服BASIC五大技术开放战略之一(Blockchain区块链、Artificial intelligence金融智能、Security安全、IoT物联网和Computing计算),事实上十多年以来,在业务场景的迫切需求的驱动下,蚂蚁金服的风控技术也经历了多次升级迭代,才发展成一套以AI智能算法、生物核身为基础的多层级立体闭环风控系统——蚂蚁风险大脑(Risk Brian)。人脸识别、指纹识别、虹膜识别、活体检测等技术加上智能设备终端、传感器识别等共同组成了蚂蚁风险大脑数字核身解决方案。面对日益复杂的新金融场景监管,地方金融监管机构正在承担越来越多的责任,同时监管痛点也逐步显现出来。而当下,蚂蚁风险大脑集风险感知、风险识别、智能进化、自动策略调整4大功能为一身,已形成了“金融消费者教育预警”、“7+4行业监测”、“涉众金融风险防控”、“金融风险联动处置”、“投资人信访登记”等完整的防控链路。目前,蚂蚁风险大脑已与北京、天津、广州等多个地方金融监管部门建立合作,共同保护消费者合法权益。全面、多样化的数字身份认证体系也是安全技术一大亮点。运用ZOLOZ(生物认证),对人脸识别的准确率高达99.99%, 覆盖2亿+互联网金融用户,确保20亿+次交易安全;运用IFAA(本地生物认证框架),实现覆盖终端设备12亿台,支持380款Android手机,TEE级别、高安全性,设备接入轻量快速低成本,周期从4个月降至1周内。除此之外,AlphaRisk (智能风控引擎)在风险感知、风险识别、智能进化、自动驾驶上也有着诸多应用,向着自动化、自学习、高准确率、高计算性能、自适应的方向进化。金融智能决策技术蚂蚁金服的金融智能决策技术与旗下网商银行独创的“310”模式息息相关。“310”即“3分钟申请、1秒钟到账、0人工干预”的服务标准,至今服务了1000万中小微企业的贷款。2018年6月,蚂蚁金服董事长兼CEO井贤栋透露,网商银行将启动“凡星计划”:未来三年,网商银行将与1000家各类金融机构合作,服务3000万家小微企业和个体经营户。在过去,发放一笔小微企业贷款的平均人力成本在2000元,而网商银行通过技术支撑的“310”模式,让每笔贷款的平均运营成本降低至2.3元,其中2元是计算和存储硬件等技术投入费用。可以说,技术降低了金融服务的成本,实现了商业上的可持续发展。具体运行方式上,金融智能决策技术涉4大步骤,分别是数据的采集与计算、AB试验体系和BI深度分析、统一指标与策略管理、模拟训练和预测平台等,至今已积累10万余项指标体系、3000多种风控策略,仅行业化风控模型就建立100多个。与此同时,在2017双11支付宝 25.6万笔支付每秒也需要其迅速做出大量高效的决策,最难得的是其资损率一直小于百万分之0.5的水平,交易资金的安全性得到了高度保障。新一代金融交互技术在新一代金融交互技术上,蚂蚁金服有着坚实的中台能力,新一代mPaaS平台,让客户端运行更为高效与稳定;小程序的组件和API则将支付宝特色能力与系统原生能力做了紧密地结合,实现了一次开发多端投放,并可以无缝迁移支付宝小程序到自己的App中;Ant Design让用户研究、交互模式、设计语言等形成了良好的循环圈,将终端用户的体验提升到了极致。具体来看,开发者能够利用蚂蚁金服移动开发平台mPaaS做好移动App的开发、管理、发布,并做好App全生命周期的管理,其中包括了开发期的研发测试、打包构建、发布管理,还有发布之后的用户行为分析、闪退分析等。2018年9月27日,支付宝小程序一站式云服务正式开放公测,为小程序开发者提供了完整的云端支持,让开发者无需自己搭建服务器,即可实现支付宝小程序的快速上线和迭代,大大节省开发成本、加快开发速度。如果说PaaS平台是对企业后台服务的生命周期的管理,包括研发、发布、监控这一套流程,那么mPaaS就是对移动应用App一整套全生命周期的管理服务,能有效降低技术门槛、减少研发成本、提升开发效率,协助金融机构快速搭建稳定高质量的移动应用。区块链技术蚂蚁金服自研可控的金融级区块链平台已经在多个社会和商业应用场景实现多机构、多国全球部署,提供面向政府、企业和普通百姓的各类数字服务。同时,蚂蚁区块链解决了很多区块链产业面临的技术挑战,在性能、安全性以及跨链交互等多个技术难点的研究与攻关进展方面均处于世界前列,已经具备金融级平台所需要的高性能、高可靠和高安全的技术特点。蚂蚁金服区块链总体定位是做一个信任连接的基础设施,与信美人寿相互保险社的合作是其区块链技术的试水,而在天猫跨境电商溯源、茅台溯源这两个可信的溯源服务上,区块链技术日臻成熟。2017年11月,蚂蚁金服正式上线关于天猫境外商品的跨境溯源的服务。在这个场景中,支付宝用户从天猫针对来自澳洲、新西兰26个商品、奶制品提供了关于每一瓶奶制品的身份证的溯源码服务。目前,蚂蚁金服在区块链领域申请了160多件专利,国家专利局公开授权的有65件,在知识产权产业媒体IPRdaily最新发布的《2018年全球区块链专利企业排行榜》排名第一。目前,蚂蚁区块链的专利方向主要集中于底层技术,并将这些技术首先应用在公益慈善、食品安全、跨境汇款、房屋租赁等更具社会价值的民生领域。2018年6月25日,蚂蚁金服宣布全球首个基于区块链技术的电子钱包跨境汇款服务在香港上线。在香港工作22年的菲律宾人Grace幸运地第一个尝鲜,整个汇款过程耗时仅3秒,而在以前需要短则10分钟、长则几天。在区块链技术的支持下,支付宝香港钱包可以为在港菲律宾劳工提供7×24小时不间断的跨境汇款服务,实现3-6秒汇款到账服务体验。同时,大大降低了多方对账成本。据了解,蚂蚁金服依次攻克了符合各国监管要求的隐私保护设计、区块链节点跨境多地多机房部署、低延时智能合约交易确认等技术难题。“BASIC”综合技术实力跻身一线可以说,除了上文介绍的数字金融五大关键技术,蚂蚁金服的综合技术实力在经历了“原始积累”之后,已经跻身一线技术厂商。“BASIC”是现在的蚂蚁金服最常提到的核心技术能力,即Blockchain(区块链)、AI(金融智能)、Security(安全)、IoT(物联网)、Computing(计算),未来所有的金融科技都围绕着这些技术来展开并实施开放开源。此外,蚂蚁金服完全自主研发的OceanBase 是高性能、高可扩展、数据强一致的金融级分布式关系型数据库,具备丰富的关系数据库功能,支持完整的 ACID 特性,首创的“三地五中心”城市级故障无损容灾方案正成为越来越多金融机构核心系统高可用的选择。OceanBase还高度兼容 MySQL,让用户能够以最小的迁移成本使用高性能、可扩展、持续可用的分布式数据库服务,同时对用户数据提供金融级可靠性的保障。而在最近几年被业界广泛关注的图数据库领域,蚂蚁金服经过3年多的探索,也自主研发出多项指标领先业界的金融级分布式图数据库GeaBase。GeaBase具备高性能、高可用、高扩展性及可移植性强等多重特性,支撑着蚂蚁金服旗下支付的风险控制、反洗钱、反欺诈、反刷单、反套现、金融案件审理、知识图谱、会员拉新、好友推荐、理财资讯推荐等众多的业务和应用。目前,GeaBase不仅广泛应用于蚂蚁金服的生态体系内,而且已经技术对外开放,正与多家银行等企业开展合作。从开放到开源,蚂蚁金服的“暖科技”在探索新的空间蚂蚁金服是最早提出技术开放的企业之一。在2015-2018三年间,从“互联网推进器计划”到“成熟一个开放一个”,蚂蚁金服公布的产品数量从5个增长到了80个,解决方案从3个发展到了50个,未来将以“蚂蚁金融科技”为技术输出品牌。现在,蚂蚁金融科技正式进入了3.0时代:支付宝对内延续“BASIC”战略,对外开放的技术越来越完整、越来越核心,是成建制、有体系的全面开放,并实现了技术商业化。业务上,实现了余额宝开放、借呗开放、花呗开放、小微企业贷款开放、蚂蚁财富平台、蚂蚁保险平台、蚂蚁森林等的开放……能力上,实现了小程序、生活号、实名核身能力、信用能力、风控能力、会员运营能力的开放……技术上,实现了区块链、金融智能、金融安全、金融分布式框架、移动开发、金融分布式数据库等100%开放……此外,在开源这条路上,蚂蚁金服一直保持着自己的节奏。2016年5月,企业级产品的设计体系Ant Design 1.0正式发布;2016年9月,企业级Node.js框架Egg宣布开源;2017年11月,专业的数据可视化解决方案AntV 3.0发布;2018年1月,首届蚂蚁金服体验科技大会在杭州蚂蚁Z空间成功举办;2018年4月,Ant Design成为国内公司star数最多的开源项目;2018年4月,蚂蚁金服启动分布式中间件开源计划,用于快速构建金融级云原生架构。……蚂蚁金服副CTO胡喜此前表示,蚂蚁金服内部建立了一个开源共建的体制,代码完全开放,其他业务的垂直BU自己也可以实现定制化需求。蚂蚁金服CTO程立曾说过,蚂蚁金服不是为了做技术本身而做技术,而希望用技术来解决社会当下和未来的问题。如果说用金字塔结构来描绘数字金融的社会价值,在塔顶的就是数字金融能在全球范围内带来更多平等的机会。同样,面向未来的技术探索与挑战,对“数据不丢失,业务不停机”保持极致追求,让整个数字世界变得安全可信,给全世界每一个人可信数字身份,在IoT的海量数据之上实时安全计算,蚂蚁金服责无旁贷。本文作者:华蒙阅读原文本文为云栖社区原创内容,未经允许不得转载。

November 13, 2018 · 1 min · jiezi

一份针对于新手的多线程实践--进阶篇

前言在上文《一份针对于新手的多线程实践》留下了一个问题:这只是多线程其中的一个用法,相信看到这里的朋友应该多它的理解更进一步了。再给大家留个阅后练习,场景也是类似的:在 Redis 或者其他存储介质中存放有上千万的手机号码数据,每个号码都是唯一的,需要在最快的时间内把这些号码全部都遍历一遍。有想法感兴趣的朋友欢迎在文末留言参与讨论????????。网友们的方案<!–more–>我在公众号以及其他一些平台收到了大家的回复,果然是众人拾柴火焰高啊。感谢每一位参与的朋友。其实看了大家的方案大多都想到了数据肯定要分段,因为大量的数据肯定没法一次性 load 到内存。但怎么加载就要考虑清楚了,有些人说放在数据库中通过分页的方式进行加载,然后将每页的数据丢到一个线程里去做遍历。其实想法挺不错的,但有个问题就是:这样肯定会导致有一个主线程去遍历所有的号码,即便是分页查询的那也得全部查询一遍,效率还是很低。即便是分页加载号码用多线程,那就会涉及到锁的问题,怎么保证每个线程读取的数据是互不冲突的。但如果存储换成 Redis 的 String 结构这样就更行不通了。遍历数据方案有没有一种利用多线程加载效率高,并且线程之间互相不需要竞争锁的方案呢?下面来看看这个方案:首先在存储这千万号码的时候我们把它的号段单独提出来并冗余存储一次。比如有个号码是 18523981123 那么就还需要存储一个号段:1852398。这样当我们有以下这些号码时:18523981123 18523981124 18523981125 13123874321 13123874322 13123874323我们就还会维护一个号段数据为:1852398 1312387这样我想大家应该明白下一步应当怎么做了吧。在需要遍历时:通过主线程先把所有的号段加载到内存,即便是千万的号码号段也顶多几千条数据。遍历这个号段,将每个号段提交到一个 task 线程中。由这个线程通过号段再去查询真正的号码进行遍历。最后所有的号段都提交完毕再等待所有的线程执行完毕即可遍历所有的号码。这样做的根本原因其实是避免了线程之间加锁,通过号段可以让每个线程只取自己那一部分数据。可能会有人说,如果号码持续增多导致号段的数据也达到了上万甚至几十万这怎么办呢?那其实也是同样的思路,可以再把号段进行拆分。比如之前是 1852398 的号段,那我继续拆分为 1852 。这样只需要在之前的基础上再启动一个线程去查询子号段即可,有点 fork/join 的味道。这样的思路其实也和 JDK1.7 中的 ConcurrentHashMap 类似,定位一个真正的数据需要两次定位。分布式方案上面的方案也是由局限性的,毕竟说到底还是一个单机应用。没法扩展;处理的数据始终是有上限。这个上限就和服务器的配置以及线程数这些相关,说的高大上一点其实就是垂直扩展增加单机的处理性能。因此随着数据量的提升我们肯定得需要通过水平扩展的方式才能达到最好的性能,这就是分布式的方案。假设我现在有上亿的数据需要遍历,但我当前的服务器配置只能支撑一个应用启动 N 个线程 5 分钟跑5000W 的数据。于是我水平扩展,在三台服务器上启动了三个独立的进程。假设一个应用能跑 5000W ,那么理论上来说三个应用就可以跑1.5亿的数据了。但这个的前提还是和上文一样:每个应用只能处理自己的数据,不能出现加锁的情况(这样会大大的降低性能)。所以我们得对刚才的号段进行分组。先通过一张图来直观的表示这个逻辑:假设现在我有 9 个号段,那么我就得按照图中的方式把数据隔离开来。第一个数据给应用0,第二个数据给应用1,第三个数据给应用2。后面的数据以此类推(就是一个简单的取模运算)。这样就可以将号段均匀的分配给不同的应用来进行处理,然后每个应用再按照上文提到的将分配给自己的号段丢到线程池中由具体的线程去查询、遍历即可。分布式带来的问题这样看似没啥问题,但一旦引入了分布式之后就不可避免的会出现 CAP 的取舍,这里不做过多讨论,感兴趣的朋友可以自行搜索。首先要解决的一个问题就是:这三个应用怎么知道它自己应该取哪些号段的数据呢?比如 0 号应用就取 0 3 6(这个相当于号段的下标),难道在配置文件里配置嘛?那如果数据量又增大了,对应的机器数也增加到了 5 台,那自然 0 号应用就不是取 0 3 6 了(取模之后数据会变)。所以我们得需要一个统一的调度来分配各个应用他们应当取哪些号段,这也就是数据分片。假设我这里有一个统一的分配中心,他知道现在有多少个应用来处理数据。还是假设上文的三个应用吧。在真正开始遍历数据的时候,这个分配中心就会去告诉这三个应用:你们要开始工作了啊,0 号应用你的工作内容是 0 3 6,1 号应用你的工作内容是 1 4 7,2 号应用你的工作内容是 2 5 8。这样各个应用就知道他们所应当处理的数据了。当我们新增了一个应用来处理数据时也很简单,同样这个分配中心知道现在有多少台应用会工作。他会再拿着现有的号段对 4(3+1台应用) 进行取模然后对数据进行重新分配,这样就可以再次保证数据分配均匀了。只是分配中心如何知道有多少应用呢,其实也简单,只要中心和应用之间通信就可以了。比如启动的时候调用分配中心的接口即可。上面提到的这个分配中心其实就是一个常见的定时任务的分布式调度中心,由它来统一发起调度,当然分片只是它其中的一个功能而已(关于调度中心之后有兴趣再细说)。总结本次探讨了多线程的更多应用方式,如要是如何高效的运行。最主要的一点其实就是尽量的避免加锁。同时对分布式水平扩展谈了一些处理建议,本次也是难得的一行代码都没贴,大家感兴趣的话在后面更新相关代码。也欢迎大家留言讨论。????你的点赞与转发是最大的支持。

November 1, 2018 · 1 min · jiezi

如何搭建一个MySQL分布式集群

1、准备集群搭建环境使用6台虚拟机来搭建 MySQL分布式集群 ,相应的实验环境与对应的MySQL节点之间的对应关系如下图所示:管理节点(MGM):这类节点的作用是管理MySQLCluster内的其他节点,如提供配置数据,并停止节点,运行备份等。由于这类节点负责管理其他节点的配置,应该在启动其他节点之前启动这类节点。MGM节点是用命令“ndb_mgmd”启动的;数据节点(NDB):这类节点用于保存Cluster的数据,数据节点的数目与副本的数目相关,是片段的倍数。例如,对于两个副本,每个副本有两个片段,那么就有4个数据节点,没有必要设定过多的副本,在NDB中数据会尽量的保存在内存中。数据节点使用命令“ndb”启动的;SQL节点:这是用来访问Cluster数据的节点,对于MySQL Cluster,客户端节点是使用NDB Cluster存储引擎的传统MySQL服务器。通常,SQL节点使用命令“mysqld-ndbcluster”启动的;2、准备安装包在官网上下载mysql的安装包: mysql-cluster-gpl-7.4.11-Linux-glibc2.5-x86_64.tar.gz,并进行解压。3、集群搭建流程1]将上述安装包解压出来的文件都移到/usr/local/mysql下;2]运行script目录下的mysql-install-db.sh脚本,运行命令为./mysql-install-db.sh –user=root–basedir =/usr/local/mysql –datadir=/usr/local/mysql;注意其中用户为root的名称需要跟配置文件my.cnf中的相同;在管理节点,数据节点,SQL节点上都执行上述安装命令,从而完成对mysql的安装;4、集群配置与启动1]在管理节点上需要完成对于集群整体的配置配置:在/var/lib/mysql-cluster/config.ini中实现如下的配置信息:2]在数据节点中需要在my.cnf中完成对于数据节点的相关配置信息,如下:需要指明配置的数据节点的根目录,数据目录,socket连接配置,用户配置,以及对应的管理节点的ip地址配置;将配置完成的配置文件移动到/etc/my.cnf,完成;3]在SQL节点上完成对于SQL节点的配置信息,同样的是在my.cnf中完成相应配置信息,并将配置文件移动到/etc/my.cnf中,相应的配置信息的设定如下所示:完成以上配置后,就可以启动集群中的各个节点了。5、集群启动在启动mysql集群的时候,注意首先要启动管理节点,并依次启动其他等若干个节点,相应的启动步骤如下:1]在管理节点上,切换到/usr/local/mysql/bin目录下,执行ndb_mgmd -f /var/lib/mysql-cluster/config.ini命令,完成管理节点的启动;2]在各个数据节点上,切换到/usr/local/mysql/bin目录下,执行ndbd –initial(第一次启动时,否则执行ndbd即可),完成对数据节点的启动;3]在各个SQL节点上,同样切换到/usr/local/mysql/bin目录下,执行mysqld_safe –user=root完成启动;4]在管理节点上运行ndb_mgm命令,进入数据库管理的客户端,输入show命令,查看与之相连接的各个节点的状态;5]在SQL节点上分别进入系统的安全状态,并完成对root用户的密码修改,运行以下指令,进行密码修改:A use mysql,切换到mysql数据库;B UPDATE user SET Password = PASSWORD(‘123456’)WHERE user = ‘root’;从而实现对root密码的修改;C flush privilege,完成修改;6]修改使得任意主机都能连得上mysql,进行如下修改,同样安装第5步进入安全模式,并完成相应的修改,如下:grant all on‘.’to ‘root@’%’ identified by ‘123456’;这样就可以使得任意一个主机都可以通过root用户来登录mysql了;6、集群测试在集群上的一个SQL节点上执行创建数据库,并创建一张表,并完成相应的数据插入,如下:A create database ctest; //创建数据库B create table test(id int primarykey; //创建一张表);C insert into test (id)values(1); //完成数据插入登录另外一个SQL节点,并执行SQL查询操作,看数据库中是否已经有数据,如下:select * from ctest;如果有数据,表示数据插入成功;7、关闭集群1]首先关闭管理节点和数据节点,需要在管理节点上执行命令,如下:./ndb_mgm -e shutdown;2]然后关闭SQL节点,在SQL节点上执行命令/usr/local/mysql/support-fies/mysql.server stop(其中/usr/local/mysql/是mysql的安装目录).从而关闭SQL节点;

October 29, 2018 · 1 min · jiezi

TDSQL“相似查询工具MSQL+”入选VLDB论文

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~本文由腾讯云数据库 TencentDB发表于云+社区专栏作者介绍:王晓宇,腾讯数据库TDSQL团队成员,目前参与TDSQL数据库内核研发工作。腾讯与高校合作的论文入选数据库顶会腾讯TDSQL团队携手中国人民大学信息学院、武汉大学计算机学院合作的DEMO论文“MSQL+: a Plugin Toolkit for Similarity Search under Metric Spaces in Distributed Relational Database Systems”被国际数据库顶级会议VLDB 2018录取。该论文设计了一款基于RDBMS的插件式近似查询工具MSQL+。MSQL+遵循SQL标准,支持面向度量空间(一种比文本空间、向量空间等更为简洁和通用的表达方式)的近似查询,依托分布式数据库系统TDSQL,实现了通用、易用、高效的相似查询处理技术。会议期间,团队展示了基于腾讯分布式数据库TDSQL实现的相似查询工具MSQL+,用于在分布式系统TDSQL中实现相似查询。在TDSQL数据库内部集成更多的计算功能,赋予数据库更为丰富的计算能力。原论文请见http://www.vldb.org/pvldb/vol…。论文信息如下:Wei Lu, Xinyi Zhang, Zhiyu Shui, Zhe Peng, Xiao Zhang, Xiaoyong Du, Hao Huang, Xiaoyu Wang, Anqun Pan, Haixiang Li: MSQL+: a Plugin Toolkit for Similarity Search under Metric Spaces in Distributed Relational Database Systems. VLDB 2018 Demonstration如果您想了解更多技术细节,请参考如下内容(如下重点介绍MSQL+的产生背景、功能、架构、设计):论文解读以下重点介绍MSQL+的产生背景、功能、架构、设计,原论文请见http://www.vldb.org/pvldb/vol…。MSQL+产生背景相似查询是诸多数据库应用的基本操作。举例来说,相似查询在文本检索、拼写检查、指纹认证、人脸识别等场景作用显著。那么这些应用是如何进行相似查询的?给出对象q和集合R,返回R中与q相似度相差不超过的元素。最直接地,遍历r∈R,计算r与q的相似度,可想而知,此方法效率很低。各领域已发展出多种方式对上述相似查询方式针对优化,但仍存在以下问题:1与现有数据库系统剥离:现有的相似查询方法,大量建立新系统或新式索引提高效率,如M-Tree、D-Index、kd-tree等,虽说性能得以提升,但很难将其融合到现有RDBMS。另有一些方法基于B+-tree实现相似查询,但要在现有RDBMS上开发新API,而且效率表现不佳。这些方法缺少统一标准、兼容性差,每接触新方法,都要付出额外的学习成本。2数据空间有限,普适性差:众多应用场景对“相似”的定义不同,衡量维度、数据类型不同,难以建立通用的相似查询模型。借助于定制化的剪枝规则,特定场景相似查询性能得到提升,但几乎不可能移植到其他应用场景。作为基本操作,相似查询应该具有普适性,在不同RDBMS应用中都能保证良好表现。3仅适用中心化系统,难应对“大数据”场景:大数据时代下,借助于分布式系统维护日益增长的数据是大势所趋。遗憾的是现有的相似查询方式,并未良好地支持分布式系统。为避免上述问题,MSQL+被设计为:基于RDBMS,遵循SQL标准,借力分布式数据库,以实现通用、易用、高效。在实际生产系统中,MSQL+以腾讯公司的分布式数据库TDSQL为依托,高效地实现了论文提出的思想和功能。MSQL+主要功能MSQL+由两大模块组成:1构建索引:MSQL+为每个数据对象生成可比较的签名(Signature),并在签名上建立B+-tree索引,签名值位于相似度范围内的对象,作为相似查询的候选项;2查询处理:用户提交SELECT-FROM-WHERE语句,该语句须提供两个约束条件,分别为:a) 用户定义的相似度函数,b) 相似度范围,条件b)初步筛选候选项,条件a)精炼候选项、返回相似结果集。相比于现有的相似查询方式,MSQL+具备以下优势:1基于RDBMS现有功能实现,使用B+-tree索引数据,使用SELECT-FROM-WHERE语句相似查询;2支持广泛的数据空间:任意类型数据可被合理地索引(见下文设计),经由统一接口相似查询;3可运行于单机和分布式RDBMS,依托分布式关系数据库系统TDSQL,能够加快预处理及相似查询进程。MSQL+设计方案本节将对MSQL+近似查询方案做简单介绍,细节请见原论文。1. Similarity Search in Metric SpacesMSQL+采用分治策略,将完整的数据集划分成多个独立的分片,每个分片筛选出若干较为相似的候选者,这些候选者后续会被二次精选。MSQL+如何划分数据集?论文说明,数据集内一些对象被选为pivot(下节介绍选举pivot的策略),剩余的数据对象按某种策略分配到唯一的pivot(比如,与之最近的pivot),这些pivot和分配至此的数据对象构成了一个分片。如此,完整的数据集被划分成多个互不相交的小数据集,然后在各分片内筛选较为相似的候选者。筛选候选者的规则是什么呢?我们从一个例子入手:给出对象q和数据集R,相似查询返回R中与q相差不超过的数据对象。对分区Pi而言,筛选r∈Pi ,且|q,r|≦的对象r作为候选者。定理 1:对于分区Pi(其pivot为Pi),∀r∈Pi ,|q,r|≦的必要条件是:LBi=|pi, q|-≦|pi, r|≦|pi, q|+ = UBiPivot的挑选,是上述过程的基础,那么,MSQL+如何挑选pivot?2. Pivot Selection选择合适的pivot,可以加快筛选候选者及精选结果集的过程,论文提出了四种pivot选择方式:1Random:从集合R中随机挑选对象作为pivot;2MaxVariance:从集合R中挑选方差最大的一组对象作为pivots;3MaxProb:pivot需满足,预期筛选出的候选项的个数最少;4Heuristic:采取类似于k-means的启发式算法,整体上看,各分区中的元素尽量靠近pivot。到此,已经可以筛选较为相近的候选者,那么,如何从中精选出更相近的结果集呢?3. Processing similarity queries in RDBMS为了快速精选出结果集,MSQL+在数据集上构建B+-tree索引,以下分两步,介绍该索引如何构建、如何使用。论文做出定义:某表存储了数据集R,表上有M个属性(即M列),部分属性作为相似度的度量,记作A:{ A1, A2, …, An } n≦M ,对于r∈R ,r[A]表示数据r属性{A1, A2, …, An}的值。3.1 Index Building在A上建立B+-tree索引,有两个条件:a) 域{ A1, A2, …, An }都是可比较的,b) 只需比较A各域的值即可精选候选项。借助于此索引,可方便地实现相似查询。那么,如何构建这样的索引?论文做出这样的设计:对于r∈R,一张“签名表”(Signature generation schema)记录了r的签名S(r[A]),S(r[A])=<i, |r, Pi|>,其中i是分区ID,|r, Pi|是分区内数据对象r和pivot Pi的差距,签名的比较规则为:原表(存储数据集R)上新增一列I记录签名<i, |r, Pi|>,并在I上建立B+-tree索引,此索引满足“可比较”和“比较索引可确定候选项”两要素,故可借助此索引方便地近似查询。3.2 Query Processing至此,已经构建了合适的B+-tree索引,如何利用该索引精选候选者?MSQL+支持用户自定义相似度函数DIST(r[A], q[A], ),此函数判断r[A]和q[A]的距离未超过,用户定义相似度函数这一设计,扩展了MSQL+支持的数据空间和类型。有了DIST函数,用户输入SELECT-FROM-WHERE语句形如:SELECT R.A1,…,R.AnFROM RWHERE DIST(r[A], q[A], )上面这条SQL,直接从数据集R中精确筛选结果集,效率堪忧。候选者此时派上用场,定理1(见Similarity Search in Metric Spaces)描述了如何筛选候选者,减少精确筛选的数据量,加速精拣过程。结合定理1和DIST函数,用户输入SELECT-FROM-WHERE语句形如:SELECT R.A1,…,R.AnFROM R, PivotsRangeSet PRSWHERE I BETWEEN PRS.LB and PRS.UB ANDDIST(r[A], q[A], )其中,临时表PivotsRangeSet维护了各pivot的LU和UB。因为PivotsRangeSet规模很小,查询优化器总会先索引扫描得到候选项,然后DIST函数精炼结果集。MSQL+分布式架构MSQL+既可在本地RDBMS上工作,又可部署在分布式RDBMS。论文给出MSQL+基于TDSQL的架构。1. System Architecture1.1 TDSQL介绍TDSQL是腾讯针对金融联机交易场景推出的高一致性,分布式数据库集群解决方案,能够保证强一致下的高可用,拥有灵活的全球部署架构,实现了倍数性能提升,增强了MySQL原生的安全机制,能够在水平方向上分布式扩展,具有自动化的运营体系和完善的配套设施。TDSQL由以下关键组件构成:1Routing Node:负载均衡;2ZooKeeprt:维护系统元信息,如表、索引、分区等;3Global Executor:接收SQL、下发local executor、汇集本地结果、生成执行计划等;4Local executor:本地数据存、取、计算等。1.2 TDSQL增益MSQL+是一款由用户自定义函数、存储过程实现的插件式工具,可以无缝融入TDSQL。MSQL+如何在TDSQL上工作呢?ZooKeeper维护MSQL+特需的元信息,并同步至各local executors;Global executor接收相似查询请求,分发至各local executors执行,汇集最终结果并给出执行计划;Local executor完成本地分片相似查询,返回执行结果。TDSQL又能给MSQL+带来什么样的增益?首先是可靠、可用性,TDSQL实现了多副本强一致性,最大程度地保障MSQL+所需的大量样本数据的安全、可用、可靠。其次,TDSQL支持水平方向分布式扩展,免除单机存储容量不足的后顾之忧,无论MSQL+样本数据多大,TDSQL都可轻松应对。TDSQL在安全机制做出的优化,很大程度保证MSQL+样本数据的安全和机密。我们最关心的性能问题,从分布式角度看,TDSQL多个本地节点并行查询,全局相似查询效率大幅度提升;具体到本地节点,TDSQL在数据库内核方面做出大量优化,使得单节点效率也有很大提升。2. Index BuildingZooKeeper维护了全部pivot信息,并由Global executor将pivot信息下发至local executors。 Global executor协调local executors构建索引,每个local executor维护一定数量的分片,也就维护对应的pivots,基于这些pivots,local executor生成签名S(r[A]),进而构建起索引。3. Query Processing用户发起相似查询请求时,routing node选择一个global executor,global executor协调local executors并行执行相似查询,汇集本地执行结果并生成执行计划。MSQL+界面展示论文展示的操作界面如下,MSQL+支持相似查询、索引构建、客户端连接、集群管理、数据导入、查询状态显示、执行计划可视化等功能。结论:MSQL+是一款基于RDBMS的插件式近似查询工具,基于腾讯TDSQL实现,具有通用、易用、高效的特点:统一接口支持多种数据空间;遵循SQL标准,发起SELECT-FROM-WHERE命令即可完成相似查询任务;MSQL+依托腾讯分布式数据库TDSQL,实现了负载均衡、多点并行,可高效地完成相似查询。问答PHP + MSQL +函数在函数中使用请求相关阅读TDSQL参加全球数据库顶级盛会 VLDB 2018回顾腾讯云数据库MySQL游戏行业数据安全实践分享MySQL 8.0 版本功能变更介绍 【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识 ...

October 19, 2018 · 1 min · jiezi