关于request:微服务的全链路请求RequestContextHolder

我的项目开发过程中,有没有很想定义一个全局变量,作用域针对于单次 request申请,在整个申请过程中都能够随时获取。当应用feign、dubbo等做服务调用时,如果该变量的作用域还能传递到整个微服务链路,那就更好了。这就是本文想实现的成果,刚工作时基于 Oracle ADF 开发,就能够定义基于 RequestScope 作用域的变量。 在后面《微服务的全链路日志(Sleuth+MDC)》文章中,咱们实现了日志的全链路,原理是基于 spring cloud sleuth 和 MDC 的框架来实现 traceId 等值的全程传递。本文算是姊妹篇,但一些实现的框架有所不同,本文是基于 spring 自带的 RequestContextHolder,和 servlet 的 HttpServletRequest 来实现的。 1. 单服务单线程实现如果只心愿实现单个服务内的作用域,而且整个API的逻辑内都是单线程,那么最容易想到的计划就是 ThreadLocal。定义一个 ThreadLocal 变量,在每一个API申请的时候赋值,在申请完结后革除,咱们很多框架在AOP中解决这段逻辑。 但当初更容易,Spring框架自带的 RequestContextHolder 人造反对这么做。寄存变量总要有提供 Getter/Setter 办法的容器吧,上面就介绍 HttpServletRequest。 1.1. HttpServletRequestHttpServletRequest 大家应该都不生疏,一次 API 申请中,所有客户端的申请都被封装成一个 HttpServletRequest 对象里。这个对象在 API 申请时创立,响应实现后销毁,就很适宜作为 Request 作用域的容器。 1、Attribute 和 Header、Parameter而往容器中投放和获取变量的办法,则能够用 HttpServletRequest 对象的 setAttribute/getAttribute 办法来实现。现在大家可能都对 Attribute比拟生疏,它在晚期 JSP 开发时用的比拟多,用于 Servlet之间的值传递,不过用于以后场景也非常符合。 有人说那为啥不必 Header、Parameter 呢?它们也是 Request 作用域内的容器。简略有两点: Header、Parameter 设计之初就不是用于做服务端容器的,所以它们通常只能在客户端赋值,在服务端 HttpServletRequest 也只提供了 Getter接口,而没有 Setter接口。但 Attribute 就同时提供了 Getter/Setter 接口。Header、Parameter 存储对象的 Value 都是 String 字符串,也是不便客户端数据基于 HTTP 协定传输时不便。但 Attribute 存储对象的 Value 是 Object,也就更适宜寄存各种类型的对象。那么在Web开发中,咱们日常是如何获取 HttpServletRequest 对象的呢? ...

April 18, 2022 · 4 min · jiezi

关于request:requestresponse

一、Request 1.Request 继承体系 2.Request 获取申请数据 3.Request 申请参数中文乱码解决 4.Request 申请转发 二、Response 1.Response 设置响应数据性能介绍 2.Response 实现重定向 2.Response

April 4, 2022 · 1 min · jiezi

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

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

October 8, 2019 · 2 min · jiezi

阿里云人脸识别公测使用说明

概述之前阿里云人脸识别只提供人脸检测,人脸属性及人脸对比三个API接口,关于这方面的介绍及使用细节,可以参考阿里云人脸识别使用流程简介,之前使用的服务地址为:dtplus-cn-shanghai.data.aliyuncs.com。目前新版本加入了1:N人脸查找的功能,新版本还处于公测阶段,服务地址:face.cn-shanghai.aliyuncs.com。下面主要介绍如何使用新版本的地址调用之前的三个API的功能。使用流程1、服务开通及1:N人脸识别使用阿里云人脸识别 1:N 使用简明示例 2、接口调用Code示例 import com.aliyuncs.CommonRequest;import com.aliyuncs.CommonResponse;import com.aliyuncs.DefaultAcsClient;import com.aliyuncs.exceptions.ClientException;import com.aliyuncs.http.MethodType;import com.aliyuncs.profile.DefaultProfile;public class CommomDemo { //DefaultProfile.getProfile的参数分别是地域,access_key_id, access_key_secret public static DefaultProfile profile = DefaultProfile.getProfile("cn-shanghai", "********", "********"); public static DefaultAcsClient client = new DefaultAcsClient(profile); public static void main(String[] args) { String imageUrl_1 = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1559655604341&di=3d6995f6dee1c4795d1827e754a00452&imgtype=0&src=http%3A%2F%2Fimg0.ph.126.net%2F90u9atgu46nnziAm1NMAGw%3D%3D%2F6631853916514183512.jpg"; String imageUrl_2 = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1559655604338&di=ee3d8fb39f6e14a21852a4ac3f2c5a14&imgtype=0&src=http%3A%2F%2Fc4.haibao.cn%2Fimg%2F600_0_100_0%2F1473652712.0005%2F87c7805c10e60e9a6db94f86d6014de8.jpg"; // 人类检测定位 DetectFace(imageUrl_1); // 人类属性识别 GetFaceAttribute(imageUrl_1); // 人脸对比 VerifyFace(imageUrl_1,imageUrl_2); } /** * DetectFace API 人脸检测定位 * * @param imageUrl 检测人脸图片的URL */ public static void DetectFace(String imageUrl) { CommonRequest request = new CommonRequest(); request.setMethod(MethodType.POST); request.setDomain("face.cn-shanghai.aliyuncs.com"); request.setVersion("2018-12-03"); request.setAction("DetectFace"); request.putBodyParameter("ImageUrl", imageUrl);// request.putBodyParameter("Content", "/9j/4AAQSkZJRgABA..."); //检测图片的内容,Base64编码 CommonResponse response = null; try { response = client.getCommonResponse(request); } catch (ClientException e) { e.printStackTrace(); } System.out.println(response.getData()); } /** * GetFaceAttribute API 人脸属性识别 * * @param imageUrl 检测人脸图片的URL */ public static void GetFaceAttribute(String imageUrl) { CommonRequest request = new CommonRequest(); request.setMethod(MethodType.POST); request.setDomain("face.cn-shanghai.aliyuncs.com"); request.setVersion("2018-12-03"); request.setAction("GetFaceAttribute"); request.putBodyParameter("ImageUrl", imageUrl);// request.putBodyParameter("Content", "/9j/4AAQSkZJRgABA..."); //检测图片的内容,Base64编码 CommonResponse response = null; try { response = client.getCommonResponse(request); } catch (ClientException e) { e.printStackTrace(); } System.out.println(response.getData()); } /** * VerifyFace API 人脸比对 * * @param imageUrl_1 对比人脸图片1 * @param imageUrl_2 对比人脸图片2 */ public static void VerifyFace(String imageUrl_1, String imageUrl_2) { CommonRequest request = new CommonRequest(); request.setMethod(MethodType.POST); request.setDomain("face.cn-shanghai.aliyuncs.com"); request.setVersion("2018-12-03"); request.setAction("VerifyFace"); request.putBodyParameter("ImageUrl1", imageUrl_1); request.putBodyParameter("ImageUrl2", imageUrl_2);// request.putBodyParameter("Content", "/9j/4AAQSkZJRgABA..."); //检测图片的内容,Base64编码 CommonResponse response = null; try { response = client.getCommonResponse(request); } catch (ClientException e) { e.printStackTrace(); } System.out.println(response.getData()); }}3、测试结果 ...

July 8, 2019 · 1 min · jiezi

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

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

June 25, 2019 · 1 min · jiezi

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

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

June 19, 2019 · 2 min · jiezi

手把手教你使用TF服务将TensorFlow模型部署到生产环境

摘要: 训练好的模型不知道如何布置到生产环境?快来学习一下吧!介绍将机器学习(ML)模型应用于生产环境已成为一个火热的的话题,许多框架提供了旨在解决此问题的不同解决方案。为解决这一问题,谷歌发布了TensorFlow(TF)服务,以期待解决将ML模型部署到生产中的问题。本文提供了一个关于服务于预先训练的卷积语义分割网络的实践教程。阅读本文后,你将能够使用TF服务来部署和向TF训练的深度CNN发出请求等操作。另外,本文将概述TF服务的API及其工作原理。如果你想学习本教程并在计算机上运行示例,请完整了解本文。但是,如果你只想了解TensorFlow服务,你可以专注于前两部分。TensorFlow服务库-概述首先我们需要花一些时间来了解TF Serving如何处理ML模型的整个生命周期。在这里,我们将介绍TF服务的主要构建块,本部分的目标是提供TF服务API的介绍。如需深入了解,请访问TF服务文档页面。TensorFlow服务由一些抽象组成,这些抽象类用于不同任务的API,其中最重要的是Servable,Loader,Source和Manager,让我们来看看他们之间是如何互动的:简单来说,当TF Serving识别磁盘上的模型时,Source组件就开始工作啦,整个服务生命周期也算开始了,Source组件负责识别应加载的新模型。实际上,它会密切关注文件系统,以确定新模型版本何时到达磁盘。当它看到新版本模型时,它会为该特定版本的模型创建一个Loader。总之,Loader几乎了解模型的所有内容,包括如何加载以及如何估计模型所需的资源,例如请求的RAM和GPU内存。Loader还有一个指向磁盘上模型的指针以及用于加载它的所有必要的元数据。但是有一个问题:加载器不允许加载模型。创建Loader后,Source会将其作为Aspired Version发送给Manager。收到模型的Aspired Version后,Manager继续执行服务过程。这里有两种可能性,一个是推送第一个模型版本进行部署,在这种情况下,Manager将确保所需的资源可用,完成后,Manager会授予Loader加载模型的权限;第二是我们推出现有模型的新版本,在这种情况下,管理员必须先咨询版本策略插件,然后再继续操作,版本策略确定如何进行加载新模型版本的过程。具体来说,在第一种情况下,我们可以确保我们的系统始终可用于传入客户的请求。此时,我们同时加载了两个模型版本,只有在加载完成后,Manager才会卸载旧版本,并且可以安全地在模型之间切换。另一方面,如果我们想通过不使用额外缓冲区来节省资源,我们可以选择保留数据。最后,当客户端请求模型的handle时,管理器返回Servable的handle。在接下来的部分中,我们将介绍如何使用TF服务提供卷积神经网络(CNN)。导出服务模型为TensorFlow构建的ML模型提供服务的第一步是确保它的格式正确,为此,TensorFlow提供了SavedModel类。SavedModel是TensorFlow模型的通用序列化格式,如果你熟悉TF,则可以使用TensorFlow Saver来保留模型的变量。TensorFlow Saver提供了将模型的检查点文件保存到磁盘或从磁盘恢复的功能。实际上,SavedModel包装了TensorFlow Saver,它是导出TF模型进行服务的标准方式。SavedModel object有一些很好的功能。首先,它允许你将多个元图保存到单个SavedModel对象,换句话说,它允许我们为不同的任务提供不同的图表。例如,假设你刚刚完成了模型的训练。在大多数情况下,要执行推理,你的图表不需要某些特定于训练的操作。这些操作可能包括优化器的变量,学习速率调度张量,额外的预处理操作等。此外,你可能希望为移动部署提供量化版本的图形。在此环境中,SavedModel允许你使用不同的配置保存图形。在我们的例子中,我们有三个不同的图形和相应的标签,如“训练”、“推理”和“移动”。此外,这三个图形为了提升内存效率还共享相同的变量集。就在不久前,如果我们想在移动设备上部署TF模型时,我们需要知道输入和输出张量的名称,以便向模型提供数据或从模型获取数据。这需要强制程序员在图的所有张量中搜索他们所需的张量。如果张量没有正确命名,那么任务可能非常繁琐。为了简化操作,SavedModel提供对SignatureDefs的支持,SignatureDefs定义了TensorFlow支持的计算的签名。它确定了计算图的正确输入和输出张量,也就是说使用这些签名,你可以指定用于输入和输出的确切节点。要使用其内置的服务API,TF Serving要求模型包含一个或多个SignatureDefs。要创建此类签名,我们需要提供输入,输出和所需方法名称的定义,输入和输出表示从字符串到TensorInfo对象的映射。在这里,我们定义了默认张量,用于向图表输入数据和从图表接收数据。目前,有三种服务API:分类,预测和回归。每个签名定义都与特定的RPC API相匹配,Classification SegnatureDef用于Classify RPC API,Predict SegnatureDef用于Predict RPC API等等依此类推。对于分类签名,必须有输入张量(接收数据)和两个可能的输出张量中的至少一个:类或分数。Regression SignatureDef只需要一个张量用于输入,另一个用于输出。最后,Predict signature允许动态数量的输入和输出张量。此外,SavedModel支持数据存储,以用于ops初始化依赖于外部文件的情况,它还具有在创建SavedModel之前清除设备的机制。现在,让我们看看我们如何在实践中做到这一点。设置环境在开始之前,我们需要从Github克隆此TensorFlow DeepLab-v3。DeepLab是谷歌最好的语义分割ConvNet,网络可以将图像作为输入并输出类似掩模的图像,该图像将某些对象与背景分开。该版本的DeepLab在Pascal VOC分段数据集上进行了训练,因此,它可以分割和识别多达20个类。如果你想了解有关语义分段和DeepLab-v3的更多信息,请查看深入深度卷积语义分段网络和Deeplab_V3。与服务相关的所有文件都存在于:./deeplab_v3/serving/。在那里,你会发现两个重要的文件:deeplab_saved_model.py和deeplab_client.ipynb。在进一步研究之前,请务必下载Deeplab-v3预训练模型。前往上面的GitHub存储库,单击checkpoints链接,你应该有一个名为tboard_logs /的文件夹,其中包含16645 /文件夹。现在,我们需要创建两个Python虚拟环境,一个用于Python 3,另一个用于Python 2,请确保安装必要的依赖项。你可以在serving_requirements.txt和client_requirements.txt文件中找到它们。你可能很好奇为什么需要两个Python env,因为我们的模型DeepLab-v3是在Python 3下开发的,而TensorFlow Serving Python API仅针对Python 2发布。因此,要导出模型并运行TF服务,我们使用Python 3 env 。请注意,你可以使用bazel中的Serving API放弃Python 2 env。有关更多详细信息,请参阅TF服务实例。完成这一步后,让我们从真正重要的事情开始吧。实例教程TensorFlow提供了一个易于使用的高级实用程序类使用SavedModel,类名为SavedModelBuilder。SavedModelBuilder类提供了保存多个元图,关联变量和数据的功能。让我们来看一个如何导出Deep Segmentation CNN模型进行服务的运行示例。如上所述,要导出模型,我们使用啦SavedModelBuilder类。它将生成SavedModel协议缓冲区文件以及模型的变量和资源。让我们剖析一下代码:# Create SavedModelBuilder class# defines where the model will be exportedexport_path_base = FLAGS.export_model_direxport_path = os.path.join( tf.compat.as_bytes(export_path_base), tf.compat.as_bytes(str(FLAGS.model_version)))print(‘Exporting trained model to’, export_path)builder = tf.saved_model.builder.SavedModelBuilder(export_path)SavedModelBuilder接收(作为输入)保存模型数据的目录。这里,export_path变量是为了连接export_path_base和model_version。因此,不同的模型版本将保存在export_path_base文件夹内的单独目录中。假设我们在生产中有我们模型的基础版本,但我们想要部署它的新版本。因为我们已经提高了模型的准确性,并希望为我们的客户提供这个新版本。要导出同一模型的不同版本,我们只需将FLAGS.model_version设置为更高的整数值即可。然后将在export_path_base文件夹中创建一个不同的文件夹(保存我们模型的新版本)。现在,我们需要指定模型的输入和输出Tensors。为此,我们使用SignatureDefs,签名定义了我们要导出的模型类型。它提供了从字符串(逻辑Tensor名称)到TensorInfo对象的映射。我们的想法是,客户端可以引用签名定义的逻辑名称,而不是引用输入/输出的实际张量名称。为了服务语义分段CNN,我们将创建一个预测签名。请注意,build_signature_def()函数采用输入和输出张量的映射以及所需的API。SignatureDef需要指定:输入,输出和方法名称,我们期望输入有三个值:一图像,另外两个张量指定其尺寸(高度和宽度)。对于输出,我们只定义了一个结果-分段输出掩码。# Creates the TensorInfo protobuf objects that encapsulates the input/output tensorstensor_info_input = tf.saved_model.utils.build_tensor_info(input_tensor)tensor_info_height = tf.saved_model.utils.build_tensor_info(image_height_tensor)tensor_info_width = tf.saved_model.utils.build_tensor_info(image_width_tensor)# output tensor infotensor_info_output = tf.saved_model.utils.build_tensor_info(predictions_tf)# Defines the DeepLab signatures, uses the TF Predict API# It receives an image and its dimensions and output the segmentation maskprediction_signature = ( tf.saved_model.signature_def_utils.build_signature_def( inputs={‘images’: tensor_info_input, ‘height’: tensor_info_height, ‘width’: tensor_info_width}, outputs={‘segmentation_map’: tensor_info_output}, method_name=tf.saved_model.signature_constants.PREDICT_METHOD_NAME))请注意,字符串‘image’,‘height’,‘width’和‘segmentation_map’不是张量。相反,它们是引用实际张量input_tensor,image_height_tensor和image_width_tensor的逻辑名称。因此,它们可以是你喜欢的任何唯一字符串。此外,SignatureDefs中的映射与TensorInfo protobuf对象有关,而与实际张量无关。要创建TensorInfo对象,我们使用实用程序函数:tf.saved_model.utils.build_tensor_info(tensor)。现在我们调用add_meta_graph_and_variables()函数来构建SavedModel协议缓冲区对象,然后我们运行save()方法,它会将模型的快照保存到包含模型变量和资源的磁盘。builder.add_meta_graph_and_variables( sess, [tf.saved_model.tag_constants.SERVING], signature_def_map={ ‘predict_images’: prediction_signature, })# export the modelbuilder.save(as_text=True)print(‘Done exporting!’)现在我们可以运行deeplab_saved_model.py来导出我们的模型。如果一切顺利,你将看到文件夹./serving/versions/1,请注意,“1”表示模型的当前版本。在每个版本子目录中,你将看到以下文件:·saved_model.pb或saved_model.pbtxt,这是序列化的SavedModel文件。它包括模型的一个或多个图形定义,以及签名定义。·变量,该文件夹包含图形的序列化变量。现在,我们已准备好启动我们的模型服务器。为此,请运行:$ tensorflow_model_server –port=9000 –model_name=deeplab –model_base_path=<full/path/to/serving/versions/>该model_base_path指的是输出模型保存,另外,我们不在路径中指定版本文件夹,模型版本控制由TF服务处理。生成客户端请求客户端代码非常简单,看一下:deeplab_client.ipynb。首先,我们读取要发送到服务器的图像并将其转换为正确的格式。接下来,我们创建一个gRPC存根,存根允许我们调用远程服务器的方法。为此,我们将实例化prediction_service_pb2模块的beta_create_PredictionService_stub类。此时,存根保持调用远程过程的必要逻辑,就像它们是本地的一样。现在,我们需要创建和设置请求对象。由于我们的服务器实现了TensorFlow Predict API,因此我们需要解析Predict请求。要发出Predict请求,首先,我们从predict_pb2模块中实例化PredictRequest类。我们还需要指定model_spec.name和model_spec.signature_name参数。该名称参数是当我们推出的服务器定义的“模型名称”的说法,而signature_name是指分配给逻辑名称signature_def_map()的参数add_meta_graph()函数。# create the RPC stubchannel = implementations.insecure_channel(host, int(port))stub = prediction_service_pb2.beta_create_PredictionService_stub(channel)# create the request object and set the name and signature_name paramsrequest = predict_pb2.PredictRequest()request.model_spec.name = ‘deeplab’request.model_spec.signature_name = ‘predict_images’# fill in the request object with the necessary datarequest.inputs[‘images’].CopyFrom( tf.contrib.util.make_tensor_proto(image.astype(dtype=np.float32), shape=[1, height, width, 3]))request.inputs[‘height’].CopyFrom(tf.contrib.util.make_tensor_proto(height, shape=[1]))request.inputs[‘width’].CopyFrom(tf.contrib.util.make_tensor_proto(width, shape=[1]))接下来,我们必须提供服务器签名中定义的输入数据。请记住,在服务器中,我们定义了一个Predict API来预期图像以及两个标量(图像的高度和宽度)。为了将输入数据提供给请求对象,TensorFlow提供了实用程序tf.make_tensor_proto(),此方法是从Python/numpy创建的TensorProto对象,我们可以使用它将图像及其尺寸提供给请求对象。看起来我们已经准备好调用服务器了。为此,我们调用Predict()方法(使用存根)并将请求对象作为参数传递。gRPC支持:同步和异步调用。因此,如果你在处理请求时想要做一些工作,我们可以调用Predict.future()而不是Predict()。# sync requestsresult_future = stub.Predict(request, 30.)# For async requests# result_future = stub.Predict.future(request, 10.)# Do some work…# result_future = result_future.result()现在我们可以获取并享受结果。本文作者:【方向】阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 27, 2019 · 1 min · jiezi

集成源码深度剖析:Fescar x Spring Cloud

Fescar 简介常见的分布式事务方式有基于 2PC 的 XA (e.g. atomikos),从业务层入手的 TCC( e.g. byteTCC)、事务消息 ( e.g. RocketMQ Half Message) 等等。XA 是需要本地数据库支持的分布式事务的协议,资源锁在数据库层面导致性能较差,而支付宝作为布道师引入的 TCC 模式需要大量的业务代码保证,开发维护成本较高。分布式事务是业界比较关注的领域,这也是短短时间 Fescar 能收获6k Star的原因之一。Fescar 名字取自 Fast & Easy Commit And Rollback ,简单来说Fescar通过对本地 RDBMS 分支事务的协调来驱动完成全局事务,是工作在应用层的中间件。主要优点是相对于XA模式是性能较好不长时间占用连接资源,相对于 TCC 方式开发成本和业务侵入性较低。类似于 XA,Fescar 将角色分为 TC、RM、TM,事务整体过程模型如下:1. TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID。2. XID 在微服务调用链路的上下文中传播。3. RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖。4. TM 向 TC 发起针对 XID 的全局提交或回滚决议。5. TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。其中在目前的实现版本中 TC 是独立部署的进程,维护全局事务的操作记录和全局锁记录,负责协调并驱动全局事务的提交或回滚。TM RM 则与应用程序工作在同一应用进程。RM对 JDBC 数据源采用代理的方式对底层数据库做管理,利用语法解析,在执行事务保留快照,并生成 undo log。大概的流程和模型划分就介绍到这里,下面开始对 Fescar 事务传播机制的分析。Fescar 事务传播机制Fescar 事务传播包括应用内事务嵌套调用和跨服务调用的事务传播。Fescar 事务是怎么在微服务调用链中传播的呢?Fescar 提供了事务 API 允许用户手动绑定事务的 XID 并加入到全局事务中,所以我们根据不同的服务框架机制,将 XID 在链路中传递即可实现事务的传播。RPC 请求过程分为调用方与被调用方两部分,我们将 XID 在请求与响应时做相应的处理即可。大致过程为:调用方即请求方将当前事务上下文中的 XID 取出,通过RPC协议传递给被调用方;被调用方从请求中的将 XID 取出,并绑定到自己的事务上下文中,纳入全局事务。微服务框架一般都有相应的 Filter 和 Interceptor 机制,我们来分析下 Spring Cloud 与Fescar 的整合过程。Fescar 与 Spring Cloud Alibaba 集成部分源码解析本部分源码全部来自于 spring-cloud-alibaba-fescar. 源码解析部分主要包括AutoConfiguration、微服务被调用方和微服务调用方三大部分。对于微服务调用方方式具体分为 RestTemplate 和 Feign,对于 Feign 请求方式又进一步细分为结合 Hystrix 和 Sentinel 的使用模式。Fescar AutoConfiguration对于 AutoConfiguration 部分的解析此处只介绍与 Fescar 启动相关的部分,其他部分的解析将穿插于【微服务被调用方】和【微服务调用方】章节进行介绍。Fescar 的启动需要配置 GlobalTransactionScanner,GlobalTransactionScanner 负责初始化 Fescar 的 RM client、TM client 和 自动代理标注 GlobalTransactional 注解的类。GlobalTransactionScanner bean 的启动通过 GlobalTransactionAutoConfiguration 加载并注入FescarProperties。FescarProperties 包含了 Fescar的重要属性 txServiceGroup ,此属性的可通过 application.properties 文件中的 key: spring.cloud.alibaba.fescar.txServiceGroup 读取,默认值为 ${spring.application.name}-fescar-service-group 。txServiceGroup 表示Fescar 的逻辑事务分组名,此分组名通过配置中心(目前支持文件、Apollo)获取逻辑事务分组名对应的 TC 集群名称,进一步通过集群名称构造出 TC 集群的服务名,通过注册中心(目前支持Nacos、Redis、ZooKeeper和Eureka)和服务名找到可用的 TC 服务节点,然后 RM client、TM client 与 TC 进行 RPC 交互。微服务被调用方由于调用方的逻辑比较多一点,我们先分析被调用方的逻辑。针对于 Spring Cloud 项目,默认采用的 RPC 传输协议时 HTTP 协议,所以使用了 HandlerInterceptor 机制来对HTTP的请求做拦截。HandlerInterceptor 是 Spring 提供的接口, 它有以下三个方法可以被覆写。 /** * Intercept the execution of a handler. Called after HandlerMapping determined * an appropriate handler object, but before HandlerAdapter invokes the handler. / default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } /* * Intercept the execution of a handler. Called after HandlerAdapter actually * invoked the handler, but before the DispatcherServlet renders the view. * Can expose additional model objects to the view via the given ModelAndView. / default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } /* * Callback after completion of request processing, that is, after rendering * the view. Will be called on any outcome of handler execution, thus allows * for proper resource cleanup. / default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { }根据注释,我们可以很明确的看到各个方法的作用时间和常用用途。对于 Fescar 集成来讲,它需要重写了 preHandle、afterCompletion 方法。FescarHandlerInterceptor 的作用是将服务链路传递过来的 XID,绑定到服务节点的事务上下文中,并且在请求完成后清理相关资源。FescarHandlerInterceptorConfiguration 中配置了所有的 url 均进行拦截,对所有的请求过来均会执行该拦截器,进行 XID 的转换与事务绑定。/* * @author xiaojing * * Fescar HandlerInterceptor, Convert Fescar information into * @see com.alibaba.fescar.core.context.RootContext from http request’s header in * {@link org.springframework.web.servlet.HandlerInterceptor#preHandle(HttpServletRequest , HttpServletResponse , Object )}, * And clean up Fescar information after servlet method invocation in * {@link org.springframework.web.servlet.HandlerInterceptor#afterCompletion(HttpServletRequest, HttpServletResponse, Object, Exception)} /public class FescarHandlerInterceptor implements HandlerInterceptor { private static final Logger log = LoggerFactory .getLogger(FescarHandlerInterceptor.class); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String xid = RootContext.getXID(); String rpcXid = request.getHeader(RootContext.KEY_XID); if (log.isDebugEnabled()) { log.debug(“xid in RootContext {} xid in RpcContext {}”, xid, rpcXid); } if (xid == null && rpcXid != null) { RootContext.bind(rpcXid); if (log.isDebugEnabled()) { log.debug(“bind {} to RootContext”, rpcXid); } } return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) throws Exception { String rpcXid = request.getHeader(RootContext.KEY_XID); if (StringUtils.isEmpty(rpcXid)) { return; } String unbindXid = RootContext.unbind(); if (log.isDebugEnabled()) { log.debug(“unbind {} from RootContext”, unbindXid); } if (!rpcXid.equalsIgnoreCase(unbindXid)) { log.warn(“xid in change during RPC from {} to {}”, rpcXid, unbindXid); if (unbindXid != null) { RootContext.bind(unbindXid); log.warn(“bind {} back to RootContext”, unbindXid); } } }}preHandle 在请求执行前被调用,xid 为当前事务上下文已经绑定的全局事务的唯一标识,rpcXid 为请求通过 HTTP Header 传递过来需要绑定的全局事务标识。preHandle 方法中判断如果当前事务上下文中没有 XID,且 rpcXid 不为空,那么就将 rpcXid 绑定到当前的事务上下文。afterCompletion 在请求完成后被调用,该方法用来执行资源的相关清理动作。Fescar 通过 RootContext.unbind() 方法对事务上下文涉及到的 XID 进行解绑。下面 if 中的逻辑是为了代码的健壮性考虑,如果遇到 rpcXid和 unbindXid 不相等的情况,再将 unbindXid 重新绑定回去。对于 Spring Cloud 来讲,默认采用的 RPC 方式是 HTTP 的方式,所以对被调用方来讲,它的请求拦截方式不用做任何区分,只需要从 Header 中将 XID 就可以取出绑定到自己的事务上下文中即可。但是对于调用方由于请求组件的多样化,包括熔断隔离机制,所以要区分不同的情况做处理,后面我们来具体分析一下。微服务调用方Fescar 将请求方式分为:RestTemplate、Feign、Feign+Hystrix 和 Feign+Sentinel 。不同的组件通过 Spring Boot 的 Auto Configuration 来完成自动的配置,具体的配置类可以看 spring.factories ,下文也会介绍相关的配置类。RestTemplate先来看下如果调用方如果是是基于 RestTemplate 的请求,Fescar 是怎么传递 XID 的。public class FescarRestTemplateInterceptor implements ClientHttpRequestInterceptor { @Override public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException { HttpRequestWrapper requestWrapper = new HttpRequestWrapper(httpRequest); String xid = RootContext.getXID(); if (!StringUtils.isEmpty(xid)) { requestWrapper.getHeaders().add(RootContext.KEY_XID, xid); } return clientHttpRequestExecution.execute(requestWrapper, bytes); }}FescarRestTemplateInterceptor 实现了 ClientHttpRequestInterceptor 接口的 intercept 方法,对调用的请求做了包装,在发送请求时若存在 Fescar 事务上下文 XID 则取出并放到 HTTP Header 中。FescarRestTemplateInterceptor 通过 FescarRestTemplateAutoConfiguration 实现将 FescarRestTemplateInterceptor 配置到 RestTemplate 中去。@Configurationpublic class FescarRestTemplateAutoConfiguration { @Bean public FescarRestTemplateInterceptor fescarRestTemplateInterceptor() { return new FescarRestTemplateInterceptor(); } @Autowired(required = false) private Collection<RestTemplate> restTemplates; @Autowired private FescarRestTemplateInterceptor fescarRestTemplateInterceptor; @PostConstruct public void init() { if (this.restTemplates != null) { for (RestTemplate restTemplate : restTemplates) { List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>( restTemplate.getInterceptors()); interceptors.add(this.fescarRestTemplateInterceptor); restTemplate.setInterceptors(interceptors); } } }}init 方法遍历所有的 restTemplate ,并将原来 restTemplate 中的拦截器取出,增加 fescarRestTemplateInterceptor 后置入并重排序。Feign接下来看下 Feign 的相关代码,该包下面的类还是比较多的,我们先从其 AutoConfiguration 入手。@Configuration@ConditionalOnClass(Client.class)@AutoConfigureBefore(FeignAutoConfiguration.class)public class FescarFeignClientAutoConfiguration { @Bean @Scope(“prototype”) @ConditionalOnClass(name = “com.netflix.hystrix.HystrixCommand”) @ConditionalOnProperty(name = “feign.hystrix.enabled”, havingValue = “true”) Feign.Builder feignHystrixBuilder(BeanFactory beanFactory) { return FescarHystrixFeignBuilder.builder(beanFactory); } @Bean @Scope(“prototype”) @ConditionalOnClass(name = “com.alibaba.csp.sentinel.SphU”) @ConditionalOnProperty(name = “feign.sentinel.enabled”, havingValue = “true”) Feign.Builder feignSentinelBuilder(BeanFactory beanFactory) { return FescarSentinelFeignBuilder.builder(beanFactory); } @Bean @ConditionalOnMissingBean @Scope(“prototype”) Feign.Builder feignBuilder(BeanFactory beanFactory) { return FescarFeignBuilder.builder(beanFactory); } @Configuration protected static class FeignBeanPostProcessorConfiguration { @Bean FescarBeanPostProcessor fescarBeanPostProcessor( FescarFeignObjectWrapper fescarFeignObjectWrapper) { return new FescarBeanPostProcessor(fescarFeignObjectWrapper); } @Bean FescarContextBeanPostProcessor fescarContextBeanPostProcessor( BeanFactory beanFactory) { return new FescarContextBeanPostProcessor(beanFactory); } @Bean FescarFeignObjectWrapper fescarFeignObjectWrapper(BeanFactory beanFactory) { return new FescarFeignObjectWrapper(beanFactory); } }}FescarFeignClientAutoConfiguration 在存在 Client.class 时生效,且要求作用在 FeignAutoConfiguration 之前。由于FeignClientsConfiguration 是在 FeignAutoConfiguration 生成 FeignContext 生效的,所以根据依赖关系, FescarFeignClientAutoConfiguration 同样早于 FeignClientsConfiguration。FescarFeignClientAutoConfiguration 自定义了 Feign.Builder,针对于 feign.sentinel,feign.hystrix 和 feign 的情况做了适配,目的是自定义 feign 中 Client 的真正实现为 FescarFeignClient。HystrixFeign.builder().retryer(Retryer.NEVER_RETRY) .client(new FescarFeignClient(beanFactory))SentinelFeign.builder().retryer(Retryer.NEVER_RETRY) .client(new FescarFeignClient(beanFactory));Feign.builder().client(new FescarFeignClient(beanFactory));FescarFeignClient 是对原来的 Feign 客户端代理增强,具体代码见下图:public class FescarFeignClient implements Client { private final Client delegate; private final BeanFactory beanFactory; FescarFeignClient(BeanFactory beanFactory) { this.beanFactory = beanFactory; this.delegate = new Client.Default(null, null); } FescarFeignClient(BeanFactory beanFactory, Client delegate) { this.delegate = delegate; this.beanFactory = beanFactory; } @Override public Response execute(Request request, Request.Options options) throws IOException { Request modifiedRequest = getModifyRequest(request); try { return this.delegate.execute(modifiedRequest, options); } finally { } } private Request getModifyRequest(Request request) { String xid = RootContext.getXID(); if (StringUtils.isEmpty(xid)) { return request; } Map<String, Collection<String>> headers = new HashMap<>(); headers.putAll(request.headers()); List<String> fescarXid = new ArrayList<>(); fescarXid.add(xid); headers.put(RootContext.KEY_XID, fescarXid); return Request.create(request.method(), request.url(), headers, request.body(), request.charset()); }上面的过程中我们可以看到,FescarFeignClient 对原来的 Request 做了修改,它首先将 XID 从当前的事务上下文中取出,如果 XID 不为空的情况下,将 XID 放到了 Header 中。FeignBeanPostProcessorConfiguration 定义了3个bean:FescarContextBeanPostProcessor、FescarBeanPostProcessor 和 FescarFeignObjectWrapper。其中 FescarContextBeanPostProcessor FescarBeanPostProcessor 实现了Spring BeanPostProcessor 接口。以下为 FescarContextBeanPostProcessor 实现。 @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof FeignContext && !(bean instanceof FescarFeignContext)) { return new FescarFeignContext(getFescarFeignObjectWrapper(), (FeignContext) bean); } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; }BeanPostProcessor 中的两个方法可以对 Spring 容器中的 Bean 做前后处理,postProcessBeforeInitialization 处理时机是初始化之前,postProcessAfterInitialization 的处理时机是初始化之后,这2个方法的返回值可以是原先生成的实例 bean,或者使用 wrapper 包装后的实例。FescarContextBeanPostProcessor 将 FeignContext 包装成 FescarFeignContext。FescarBeanPostProcessor 将 FeignClient 根据是否继承了LoadBalancerFeignClient 包装成 FescarLoadBalancerFeignClient 和 FescarFeignClient。FeignAutoConfiguration 中的 FeignContext 并没有加 ConditionalOnXXX 的条件,所以 Fescar 采用预置处理的方式将 FeignContext 包装成 FescarFeignContext。 @Bean public FeignContext feignContext() { FeignContext context = new FeignContext(); context.setConfigurations(this.configurations); return context; }而对于 Feign Client,FeignClientFactoryBean 中会获取 FeignContext 的实例对象。对于开发者采用 @Configuration 注解的自定义配置的 Feign Client 对象,这里会被配置到 builder,导致 FescarFeignBuilder 中增强后的 FescarFeignCliet 失效。FeignClientFactoryBean 中关键代码如下: /* * @param <T> the target type of the Feign client * @return a {@link Feign} client created with the specified data and the context information */ <T> T getTarget() { FeignContext context = applicationContext.getBean(FeignContext.class); Feign.Builder builder = feign(context); if (!StringUtils.hasText(this.url)) { if (!this.name.startsWith(“http”)) { url = “http://” + this.name; } else { url = this.name; } url += cleanPath(); return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type, this.name, url)); } if (StringUtils.hasText(this.url) && !this.url.startsWith(“http”)) { this.url = “http://” + this.url; } String url = this.url + cleanPath(); Client client = getOptional(context, Client.class); if (client != null) { if (client instanceof LoadBalancerFeignClient) { // not load balancing because we have a url, // but ribbon is on the classpath, so unwrap client = ((LoadBalancerFeignClient)client).getDelegate(); } builder.client(client); } Targeter targeter = get(context, Targeter.class); return (T) targeter.target(this, builder, context, new HardCodedTarget<>( this.type, this.name, url)); }上述代码根据是否指定了注解参数中的 URL 来选择直接调用 URL 还是走负载均衡,targeter.target 通过动态代理创建对象。大致过程为:将解析出的feign方法放入map,再通过将其作为参数传入生成InvocationHandler,进而生成动态代理对象。FescarContextBeanPostProcessor 的存在,即使开发者对 FeignClient 自定义操作,依旧可以完成 Fescar 所需的全局事务的增强。对于 FescarFeignObjectWrapper,我们重点关注下Wrapper方法: Object wrap(Object bean) { if (bean instanceof Client && !(bean instanceof FescarFeignClient)) { if (bean instanceof LoadBalancerFeignClient) { LoadBalancerFeignClient client = ((LoadBalancerFeignClient) bean); return new FescarLoadBalancerFeignClient(client.getDelegate(), factory(), clientFactory(), this.beanFactory); } return new FescarFeignClient(this.beanFactory, (Client) bean); } return bean; }wrap 方法中,如果 bean 是 LoadBalancerFeignClient 的实例对象,那么首先通过 client.getDelegate() 方法将 LoadBalancerFeignClient 代理的实际 Client 对象取出后包装成 FescarFeignClient,再生成 LoadBalancerFeignClient 的子类 FescarLoadBalancerFeignClient 对象。如果 bean 是 Client 的实例对象且不是 FescarFeignClient LoadBalancerFeignClient,那么 bean 会直接包装生成 FescarFeignClient。上面的流程设计还是比较巧妙的,首先根据 Spring boot 的 Auto Configuration 控制了配置的先后顺序,同时自定义了 Feign Builder的Bean,保证了 Client 均是经过增强后的 FescarFeignClient 。再通过 BeanPostProcessor 对Spring 容器中的 Bean 做了一遍包装,保证容器内的Bean均是增强后 FescarFeignClient ,避免 FeignClientFactoryBean getTarget 方法的替换动作。Hystrix 隔离下面我们再来看下 Hystrix 部分,为什么要单独把 Hystrix 拆出来看呢,而且 Fescar 代码也单独实现了个策略类。目前事务上下文 RootContext 的默认实现是基于 ThreadLocal 方式的 ThreadLocalContextCore,也就是上下文其实是和线程绑定的。Hystrix 本身有两种隔离状态的模式,基于信号量或者基于线程池进行隔离。Hystrix 官方建议是采取线程池的方式来充分隔离,也是一般情况下在采用的模式:Thread or SemaphoreThe default, and the recommended setting, is to run HystrixCommands using thread isolation (THREAD) and HystrixObservableCommands using semaphore isolation (SEMAPHORE).Commands executed in threads have an extra layer of protection against latencies beyond what network timeouts can offer.Generally the only time you should use semaphore isolation for HystrixCommands is when the call is so high volume (hundreds per second, per instance) that the overhead of separate threads is too high; this typically only applies to non-network calls.service 层的业务代码和请求发出的线程肯定不是同一个,那么 ThreadLocal 的方式就没办法将 XID 传递给 Hystrix 的线程并传递给被调用方的。怎么处理这件事情呢,Hystrix 提供了个机制让开发者去自定义并发策略,只需要继承 HystrixConcurrencyStrategy 重写 wrapCallable 方法即可。public class FescarHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy { private HystrixConcurrencyStrategy delegate; public FescarHystrixConcurrencyStrategy() { this.delegate = HystrixPlugins.getInstance().getConcurrencyStrategy(); HystrixPlugins.reset(); HystrixPlugins.getInstance().registerConcurrencyStrategy(this); } @Override public <K> Callable<K> wrapCallable(Callable<K> c) { if (c instanceof FescarContextCallable) { return c; } Callable<K> wrappedCallable; if (this.delegate != null) { wrappedCallable = this.delegate.wrapCallable(c); } else { wrappedCallable = c; } if (wrappedCallable instanceof FescarContextCallable) { return wrappedCallable; } return new FescarContextCallable<>(wrappedCallable); } private static class FescarContextCallable<K> implements Callable<K> { private final Callable<K> actual; private final String xid; FescarContextCallable(Callable<K> actual) { this.actual = actual; this.xid = RootContext.getXID(); } @Override public K call() throws Exception { try { RootContext.bind(xid); return actual.call(); } finally { RootContext.unbind(); } } }}Fescar 也提供一个 FescarHystrixAutoConfiguration,在存在 HystrixCommand 的时候生成FescarHystrixConcurrencyStrategy。@Configuration@ConditionalOnClass(HystrixCommand.class)public class FescarHystrixAutoConfiguration { @Bean FescarHystrixConcurrencyStrategy fescarHystrixConcurrencyStrategy() { return new FescarHystrixConcurrencyStrategy(); }}参考资料Fescar: https://github.com/alibaba/fescarSpring Cloud Alibaba: https://github.com/spring-cloud-incubator/spring-cloud-alibabaspring-cloud-openfeign: https://github.com/spring-cloud/spring-cloud-openfeign本文作者郭树抗,社区昵称 ywind,曾就职于华为终端云,现搜狐智能媒体中心Java工程师,目前主要负责搜狐号相关开发,对分布式事务、分布式系统和微服务架构有异常浓厚的兴趣。季敏(清铭),社区昵称 slievrly,Fescar 开源项目负责人,阿里巴巴中件间 TXC/GTS 核心研发成员,长期从事于分布式中间件核心研发工作,在分布式事务领域有着较丰富的技术积累。延伸阅读微服务架构下,解决数据一致性问题的实践本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 8, 2019 · 7 min · jiezi

转发和重定向的区别

实际发生位置不同,地址栏不同转发是发生在服务器的转发是由服务器进行跳转的,细心的朋友会发现,在转发的时候,浏览器的地址栏是没有发生变化的,在我访问Servlet111的时候,即使跳转到了Servlet222的页面,浏览器的地址还是Servlet111的。也就是说浏览器是不知道该跳转的动作,转发是对浏览器透明的。通过上面的转发时序图我们也可以发现,实现转发只是一次的http请求,一次转发中request和response对象都是同一个。这也解释了,为什么可以使用request作为域对象进行Servlet之间的通讯。重定向是发生在浏览器的重定向是由浏览器进行跳转的,进行重定向跳转的时候,浏览器的地址会发生变化的。曾经介绍过:实现重定向的原理是由response的状态码和Location头组合而实现的。这是由浏览器进行的页面跳转实现重定向会发出两个http请求,request域对象是无效的,因为它不是同一个request对象转发和重定向的区别实际发生位置不同,地址栏不同转发是发生在服务器的转发是由服务器进行跳转的,细心的朋友会发现,在转发的时候,浏览器的地址栏是没有发生变化的,在我访问Servlet111的时候,即使跳转到了Servlet222的页面,浏览器的地址还是Servlet111的。也就是说浏览器是不知道该跳转的动作,转发是对浏览器透明的。通过上面的转发时序图我们也可以发现,实现转发只是一次的http请求,一次转发中request和response对象都是同一个。这也解释了,为什么可以使用request作为域对象进行Servlet之间的通讯。重定向是发生在浏览器的重定向是由浏览器进行跳转的,进行重定向跳转的时候,浏览器的地址会发生变化的。曾经介绍过:实现重定向的原理是由response的状态码和Location头组合而实现的。这是由浏览器进行的页面跳转实现重定向会发出两个http请求,request域对象是无效的,因为它不是同一个request对象转发和重定向使用哪一个?根据上面说明了转发和重定向的区别也可以很容易概括出来。转发是带着转发前的请求的参数的。重定向是新的请求。典型的应用场景:转发: 访问 Servlet 处理业务逻辑,然后 forward 到 jsp 显示处理结果,浏览器里 URL 不变重定向: 提交表单,处理成功后 redirect 到另一个 jsp,防止表单重复提交,浏览器里 URL 变了

February 18, 2019 · 1 min · jiezi

Kubernetes 弹性伸缩全场景解析 (一)- 概念延伸与组件布局

传统弹性伸缩的困境弹性伸缩是Kubernetes中被大家关注的一大亮点,在讨论相关的组件和实现方案之前。首先想先给大家扩充下弹性伸缩的边界与定义,传统意义上来讲,弹性伸缩主要解决的问题是容量规划与实际负载的矛盾。如上图所示,蓝色的水位线表示集群的容量随着负载的提高不断的增长,红色的曲线表示集群的实际的负载真实的变化。而弹性伸缩要解决的就是当实际负载出现激增,而容量规划没有来得及反应的场景。常规的弹性伸缩是基于阈值的,通过设置一个资源缓冲水位来保障资源的充盈,通常15%-30%左右的资源预留是比较常见的选择。换言之就是通过一个具备缓冲能力的资源池用资源的冗余换取集群的可用。这种方式表面上看是没有什么问题的,确实在很多的解决方案或者开源组件中也是按照这种方式进行实现的,但是当我们深入的思考这种实现方案的时候会发现,这种方式存在如下三个经典问题。1. 百分比碎片难题在一个Kubernetes集群中,通常不只包含一种规格的机器,针对不同的场景、不同的需求,机器的配置、容量可能会有非常大的差异,那么集群伸缩时的百分比就具备非常大的迷惑性。假设我们的集群中存在4C8G的机器与16C32G的机器两种不同规格,对于10%的资源预留,这两种规格是所代表的意义是完全不同的。特别是在缩容的场景下,通常为了保证缩容后的集群不处在震荡状态,我们会一个节点一个节点或者二分法来缩容节点,那么如何根据百分比来判断当前节点是处在缩容状态就尤为重要,此时如果大规格机器有较低的利用率被判断缩容,那么很有可能会造成节点缩容后,容器重新调度后的争抢饥饿。如果添加判断条件,优先缩容小配置的节点,则有可能造成缩容后资源的大量冗余,最终集群中可能会只剩下所有的巨石节点。2. 容量的规划炸弹还记得在没有使用容器前,是如何做容量规划的吗?一般会按照应用来进行机器的分配,例如,应用A需要2台4C8G的机器,应用B需要4台8C16G的机器,应用A的机器与应用B的机器是独立的,相互不干扰。到了容器的场景中,大部分的开发者无需关心底层的资源了,那么这个时候容量规划哪里去了呢?在Kubernetes中是通过Request和Limit的方式进行设置,Request表示资源的申请值,Limit表示资源的限制值。既然Request和Limit才是容量规划的对等概念,那么这就代表着资源的实际计算规则要根据Request和Limit才更加准确。而对于每个节点预留资源阈值而言,很有可能会造成小节点的预留无法满足调度,大节点的预留又调度不完的场景。3. 资源利用率困境集群的资源利用率是否可以真的代表当前的集群状态呢?当一个Pod的资源利用率很低的时候,不代表就可以侵占他所申请的资源。在大部分的生产集群中,资源利用率都不会保持在一个非常高的水位,但从调度来讲,资源的调度水位应该保持在一个比较高的水位。这样才能既保证集群的稳定可用,又不过于浪费资源。如果没有设置Request与Limit,而集群的整体资源利用率很高这意味着什么?这表示所有的Pod都在被以真实负载为单元进行调度,相互之间存在非常严重的争抢,而且简单的加入节点也丝毫无法解决问题,因为对于一个已调度的Pod而言,除了手动调度与驱逐没有任何方式可以将这个Pod从高负载的节点中移走。那如果我们设置了Request与Limit而节点的资源利用率又非常高的时候说明了什么呢?很可惜这在大部分的场景下都是不可能的,因为不同的应用不同的负载在不同的时刻资源的利用率也会有所差异,大概率的情况是集群还没有触发设置的阈值就已经无法调度Pod了。弹性伸缩概念的延伸既然基于资源利用率的弹性伸缩有上述已知的三个问题,有什么办法可以来解决呢?随着应用类型的多样性发展,不同类型的应用的资源要求也存在越来越大的差异。弹性伸缩的概念和意义也在变化,传统理解上弹性伸缩是为了解决容量规划和在线负载的矛盾,而现在是资源成本与可用性之间的博弈。如果将常见的应用进行规约分类,可以分为如下四种不同类型:在线任务类型比较常见的是网站、API服务、微服务等常见的互联网业务型应用,这种应用的特点是对常规资源消耗较高,比如CPU、内存、网络IO、磁盘IO等,对于业务中断容忍性差。离线任务类型例如大数据离线计算、边缘计算等,这种应用的特点是对可靠性的要求较低,也没有明确的时效性要求,更多的关注点是成本如何降低。定时任务类型定时运行一些批量计算任务是这种应用的比较常见形态,成本节约与调度能力是重点关注的部分。特殊任务类型例如闲时计算的场景、IOT类业务、网格计算、超算等,这类场景对于资源利用率都有比较高的要求。单纯的基于资源利用率的弹性伸缩大部分是用来解决第一种类型的应用而产生的,对于其他三种类型的应用并不是很合适,那么Kubernetes是如何解决这个问题的呢?kubernetes的弹性伸缩布局Kubernetes将弹性伸缩的本质进行了抽象,如果抛开实现的方式,对于不同应用的弹性伸缩而言,该如何统一模型呢?Kubernetes的设计思路是将弹性伸缩划分为保障应用负载处在容量规划之内与保障资源池大小满足整体容量规划两个层面。简单理解,当需要弹性伸缩时,优先变化的应该是负载的容量规划,当集群的资源池无法满足负载的容量规划时,再调整资源池的水位保证可用性。而两者相结合的方式是无法调度的Pod来实现的,这样开发者就可以在集群资源水位较低的时候使用HPA、VPA等处理容量规划的组件实现实时极致的弹性,资源不足的时候通过Cluster-Autoscaler进行集群资源水位的调整,重新调度,实现伸缩的补偿。两者相互解耦又相互结合,实现极致的弹性。在Kubernetes的生态中,在多个维度、多个层次提供了不同的组件来满足不同的伸缩场景。如果我们从伸缩对象与伸缩方向两个方面来解读Kubernetes的弹性伸缩的话,可以得到如下的弹性伸缩矩阵。cluster-autoscaler:kubernetes社区中负责节点水平伸缩的组件,目前处在GA阶段(General Availability,即正式发布的版本)。HPA:kubernetes社区中负责Pod水平伸缩的组件,是所有伸缩组件中历史最悠久的,目前支持autoscaling/v1、autoscaling/v2beta1与autoscaling/v2beta2,其中autoscaling/v1只支持CPU一种伸缩指标,在autoscaling/v2beta1中增加支持custom metrics,在autoscaling/v2beta2中增加支持external metrics。cluster-proportional-autoscaler:根据集群的节点数目,水平调整Pod数目的组件,目前处在GA阶段。vetical-pod-autoscaler:根据Pod的资源利用率、历史数据、异常事件,来动态调整负载的Request值的组件,主要关注在有状态服务、单体应用的资源伸缩场景,目前处在beta阶段。addon-resizer:根据集群中节点的数目,纵向调整负载的Request的组件,目前处在beta阶段。在这五个组件中,cluster-autoscaler、HPA、cluster-proportional-autoscaler是目前比较稳定的组件,建议有相关需求的开发者进行选用。对于百分之八十以上的场景,我们建议客户通过HPA结合cluster-autoscaler的方式进行集群的弹性伸缩管理,HPA负责负载的容量规划管理而cluster-autoscaler负责资源池的扩容与缩容。最后在本文中,和大家主要讨论的是在云原生时代下弹性伸缩概念的延伸,以及Kubernetes社区是如何通过解耦的方式通过多个转职的组件实现了两个维度的弹性伸缩,在本系列后面的文章中会为一一解析每个弹性伸缩组件的相关原理与用法。本文作者:莫源阅读原文本文为云栖社区原创内容,未经允许不得转载。

November 23, 2018 · 1 min · jiezi