
异步社区(www.epubit.com)是人民邮电出版社旗下IT专业图书旗舰社区,也是国内领先的IT专业图书社区,致力于优质学习内容的出版和分享,实现了纸书电子书的同步上架,于2015年8月上线运营。公众号【异步图书】,每日赠送异步新书。
2017 年年初,我所在的公司开始对整个业务系统进行重构和微服务化,替换掉因业务发展而不堪重负的、运行了 10 年的庞大的单体应用。我有幸作为小组技术负责人,负责部分业务的微服务架构的设计和开发工作。 随着微服务迁移工作的深入,服务化过程中遇到的问题越来越多,痛点也越加明显。当我们的业务被拆分成若干个服务时,不可避免地要进行服务之间的交互,很多时候需要多个服务共同协作才能完成一个完整的业务流程。在这种情况下,服务间的通信问题也暴露得更加明显。我开始思考如何实现分布式系统的弹性设计,以及解决容错、监控等问题。 我偶然通过阅读“What's a service mesh? And why do I need one?”这篇文章接触到服务网格概念,并了解到它是解决微服务通信问题的好帮手。与此同时,Istio 也发布了 1.0 版本。在仔细了解了 Istio 的整体技术架构后,我深深地被这种优雅的设计所折服,各组件职责清晰、松散耦合,数据平面可替换,Mixer 的适配器模式又提供了强大的可扩展性。加之 Google、IBM 和 Lyft 的支持,我预感 Istio 会和 Kubernetes一样,成为又一个明星级的产品。 服务网格是一个新颖的概念,Istio 作为它的一个实现产品,诞生也不到两年的时间,网络上很难找到相关的学习资源,主要的学习资料就是 Istio 官方提供的文档。这份文档虽然十分详尽地介绍了 Istio 的方方面面,但语言较为晦涩,内容组织也不适合初学者。今天这本《Istio实战指南》恰好满足了初学者的需求。 Istio实战指南 01 什么是服务网格 什么是服务网格?简单来说,就是在微服务架构下管理服务间网络通信的基础设施。为了让大家更容易理解,我们用现实中的社交网络来进行类比。 把时间退回到现代科技出现之前。那时候人和人之间如果需要沟通和交流,可能就只能亲自登门拜访了,以面对面的方式建立联系。后来出现了书信,这是通讯的一大进步,至少为了说几句话,我不用亲自跑一趟了。但书信的时效性还是太低,于是就出现了电话。这下沟通的时效性问题也解决了,只要有电话网络覆盖的地方,随时都可以通过电话进行交流。但是电话仍然有局限性,就是它只能是点对点的沟通,如果我想和三五个好友一起聊天,就没法实现了。最终,社交网络的出现解决了这个问题。 无论是以前IM工具的群组功能,还是Facebook、人人网这样的SNS网站,再到现在的朋友圈,都可以认为是不同形态的社交网络。我们可以随时随地不受限制的和联系人进行一对一、多对多的沟通。那么社交网络和服务网格有哪些相似性呢? 首先,社交网络中出现的主体是联系人,这就好像服务网格中的主体是一个个的微服务。联系人之间是通过注册在社交网络的账号进行交互的,包括消息的发送和接收。这个账号的载体可以是电脑或者手机。这就好像网格里的Sidecar代理,服务间的通信都由它们完成。它们就像是微服务的账号和手机一样,负责与不同的联系人(微服务)进行沟通。 如果说服务网格是Sidecar代理组成的网络拓扑,那么人际关系就是社交网络的拓扑。最后,社交网络的最终效果是使得人与人之间无阻碍的沟通,使人和地域、时间解耦;类似的,服务网格最终的目的是使得微服务(即业务)和网络通信功能解耦。 相信现在你已经大致了解了服务网格的基本概念,本质上它将网络通信下沉到基础设施层,让微服务只需要关注业务本身;就好像社交网络将人际关系的网络拓扑抽取出来,你只需要关注沟通的内容本身,而不需要关心沟通的方式。 02 什么是Istio 作为服务网格的实现产品,Istio一经推出就备受瞩目,成为各大厂商和开发者争相追逐的“香馍馍”。我个人认为Istio会成为继Kubernetes之后的又一个明星级产品。Istio的官方网站这样定义自己。 它是一个完全开源的服务网格,以透明层的方式构建在现有分布式应用中。它也是一个提供了各种API的平台,可以与任何日志平台、监控系统或策略系统集成。Istio的多样化特性可以让你高效地运行分布式微服务架构,并提供一种统一的方式来保护、连接和监控微服务。 从上面的定义中可以了解到,Istio为微服务应用提供了一个完整的解决方案,可以以统一的方式去检测和管理微服务。同时,它还提供了管理流量、实施访问策略、收集数据等功能,而所有这些功能都对业务代码透明,即不需要修改业务代码就能实现。 有了Istio,就几乎可以不需要其他的微服务框架,也不需要自己去实现服务治理等功能,只要把网络层委托给Istio,它就能帮助完成这一系列的功能。简单来说,Istio就是一个提供了服务治理能力的服务网格。 03 Istio的架构 对服务网格来讲,业务代码无侵入和网络层的全权代理是其重要的优势。我们来了解一下Istio的架构,看一看它是如何做到这两点的,并了解架构中的各个组件是如何协同工作并完成网络层功能的。 Istio的架构从逻辑上分成数据平面(Data Plane)和控制平面(Control Plane)。是否觉得似曾相识?没错,Kubernetes的架构也具有相似的结构,分为控制节点和计算节点。毫无疑问,这样的设计可以很好地解耦各个功能组件。 数据平面:由一组和业务服务成对出现的Sidecar代理(Envoy)构成,它的主要功能是接管服务的进出流量,传递并控制服务和Mixer组件的所有网络通信(Mixer是一个策略和遥测数据的收集器,稍后会介绍)。 控制平面:主要包括了Pilot、Mixer、Citadel和Galley共4个组件,主要功能是通过配置和管理Sidecar代理来进行流量控制,并配置Mixer去执行策略和收集遥测数据(Telemetry)。 图1展示了由这些组件组成的Istio架构。 从Istio的架构中可以看出,Istio追求尽可能的透明,通过各种解耦设计让系统对内对外都没有依赖。同时,它还提供了高度的扩展性。Istio认为随着应用的增长和服务的增多,扩展策略系统是最主要的需求,因此它被设计为以增量的方式进行扩展。可移植也是Istio在设计中充分考虑的因素,它被设计为支持多种平台,以便服务可以被方便地迁移到不同的云环境中(在撰写本书的过程中,Istio仍然深度依赖于Kubernetes平台)。 通过数据平面和控制平面的分离,各个组件都成为插件,这种开放和包容的设计思路相当具有前瞻性,我想这也就是其他服务网格产品都放弃了和它竞争而选择合作的重要原因。 下面对架构中的各组件做进一步介绍。 图1 Istio架构 04 Istio的核心控件 4.1 Envoy 从架构图可以看出,Istio的数据平面就是指代理。Istio选择Envoy作为Sidecar代理,Envoy本质上是一个为面向服务的架构而设计的7层代理和通信总线。Envoy基于C++11开发而成,性能出色。除了具有强大的网络控制能力外,Envoy还可以将流量行为和数据提取出来发送给Mixer组件,用以进行监控。 Envoy在网络控制方面的主要功能如下。 HTTP 7层路由。 支持gRPC、HTTP/2。 服务发现和动态配置。 健康检查。 高级负载均衡。 我们知道,在Kubernetes环境中,同一个Pod内的不同容器间共享网络栈,这一特性使得Sidecar可以接管进出这些容器的网络流量,这就是Sidecar模式的实现基础。Envoy是目前Istio默认的数据平面,实际上因为Istio灵活的架构,完全可以选择其他兼容的产品作为Sidecar。目前很多服务网格产品都可以作为Istio的数据平面并提供集成。 4.2 Pilot Pilot 是Istio实现流量管理的核心组件,它主要的作用是配置和管理Envoy代理。比如可以为代理之间设置特定的流量规则,或者配置超时、重试、熔断这样的弹性能力。Pilot会将控制流量行为的路由规则转换为Envoy的配置,并在运行时将它们广播到Envoy。另外,Pilot还能够把服务发现机制抽象出来并转换成API分发给Envoy,使得后者具有服务发现的能力。 简单来说,Pilot的主要任务有两个。 从平台(如Kubernetes)获取服务信息,完成服务发现。 获取Istio的各项配置,转换成Envoy代理可读的格式并分发。 图2展示了Pilot架构。Pilot维护了一套独立于平台的服务规则,并提供了 图2 Pilot架构 一个平台适配器,以便接入各种不同的平台。Rules API对运维人员开放,使得他们可以设置想要的流量规则,Pilot会把这些配置好的规则通过Envoy API分发给Envoy代理,以使其执行指定的规则。 Pilot还公开了用于服务发现并且可以动态更新负载均衡和路由表的API。 4.3 Mixer Mixer的主要功能是提供策略控制,并从Envoy代理收集遥测数据。每次网络通信时Envoy代理都会向Mixer发出预检要求,用来检测调用者的合法性。调用之后Envoy代理会发送遥测数据供Mixer收集。一般情况下Sidecar代理可以缓存这些数据,不需要频繁地调用Mixer。 适配器是Mixer的重要组成部分,它本质上是一个插件模型,每个插件叫作适配器。这项特性使得Mixer可以接入几乎任意的(只要定义好接口)后端基础设施。比如可以选择接入不同的日志收集器、监控工具和授权工具等;可以在运行时切换不同的适配器或者是打开(关闭)它们;还可以自定义适配器以满足特定需求。适配器极大地提高了Mixer的扩展性,它让Istio的功能拥有了更多可能性。图3展示了Mixer的架构图并展示了它和Envoy的交互方式。 图3 Mixer架构 4.4 Citadel Citadel是与安全相关的组件,主要负责密钥和证书的管理。它可以提供服务间和终端用户的身份认证,还可以加密服务网格中的流量。在后面介绍安全主题的第8章中,我们会详细说明它是如何和其他组件协同工作的。 4.5 Galley 在2019年3月份发布的1.1版本中,Galley作为一个独立的组件被添加到了架构当中(在此之前的版本中Galley并未独立出现),它现在是Istio主要的配置管理组件,负责配置的获取、处理和分发。Galley使用了一种叫作MCP(Mesh Configuration Protocol,网格配置协议)的协议与其他组件进行通信。 05 Istio的主要功能 下面详细地介绍一下Istio的4个主要功能和实现原理。 5.1 流量管理 第1章介绍过,微服务应用最大的痛点就是处理服务间的通信,而这一问题的核心其实就是流量管理。首先来看一看传统的微服务应用在没有服务网格介入的情况下,如何完成诸如金丝雀发布这样的动态路由。假设不借助任何现成的第三方框架,一个简单的实现方法是,在服务间添加一个负载均衡(如Nginx)做代理,通过修改配置的权重来分配流量。这种方式将对流量的管理和基础设施(云服务器、虚拟机、实体机等)绑定在了一起,难以维护。 而使用Istio就可以轻松地实现各种维度的流量控制。图4展示了两种不同的金丝雀发布策略。第一种是根据权重把5%的流量路由给新版本;第二种是根据请求的头信息User-Agent把使用iPhone的用户流量路由到新版本。 图4 Istio的流量管理 Istio的流量管理是通过Pilot和Envoy这两个组件实现的,将流量和基础设施进行了解耦。Pilot负责配置规则,并把规则分发到Envoy代理去实施;而Envoy按照规则执行各种流量管理的功能,比如动态请求路由,超时、重试和熔断,还可以通过故障注入来测试服务之间的容错能力。下面对这些具体的功能进行逐一介绍。 1.请求路由 Istio为了控制服务请求,引入了服务版本(Version)的概念,可以通过版本这一标签将服务进行区分。版本的设置是非常灵活的,可以根据服务的迭代编号进行定义(如v1、v2版本);也可以根据部署环境进行定义(如Dev、Staging和Production);或者是自定义任何用于区分服务的标记。通过版本标签,Istio就可以定义灵活的路由规则以控制流量,上面提到的金丝雀发布这类应用场景就很容易实现了。 图5展示了使用服务版本实现路由分配的例子。服务版本定义了版本号(v1.5、v2.0-alpha)和环境(us-prod、us-staging)两种信息。服务B包含了4个Pod,其中3个是部署在生产环境的v1.5版本,而Pod4是部署在预生产环境的v2.0-alpha版本。运维人员根据服务版本指定路由规则,通过Pilot同步给Envoy代理,使得99%的流量流向v1.5版本的生产环境,而1%的流量进入v2.0-alpha版本的预生产环境。 图5 服务版本控制 2.入口网关(Ingress)和出口网关(Egress) 服务间通信是通过Envoy代理进行的。同样,我们也可以在整个系统的入口和出口处部署代理,使得所有流入和流出的流量都由代理进行转发,而这两个负责入口和出口的代理就叫作入口网关和出口网关。它们相当于整个微服务应用的边界代理,把守着进入和流出服务网格的流量。图6展示了Ingress和Egress在请求流中的位置,通过设置Envoy代理,出入服务网格的流量也得到了控制。 图6 请求流中的Ingress和Egress 3.服务发现和负载均衡 服务发现的前提条件是具有服务注册的能力。目前Kubernetes这类容器编排平台也提供了服务注册的能力。Istio基于平台实现服务发现和负载均衡时,需要通过Pilot和Envoy协作完成,如图7所示。Pilot组件会从平台获取服务的注册信息,并提供服务发现的接口,Envoy获得这些信息并更新到自己的负载均衡池。Envoy会定期地对池中的实例进行健康检查,剔除离线的实例,保证服务信息的实时性。 图7 服务发现和负载均衡 4.故障处理 Istio的故障处理都由Envoy代理完成。Envoy提供了一整套现成的故障处理机制,比如超时、重试、限流和熔断等。这些功能都能够以规则的形式进行动态配置,并且执行运行时修改。这使得服务具有更好的容错能力和弹性,并保证服务的稳定性。 5.故障注入 简单来说,故障注入就是在系统中人为地设置一些故障,来测试系统的稳定性和系统恢复的能力。比如为某服务设置一个延迟,使其长时间无响应,然后检测调用方是否能处理这种超时问题而自身不受影响(如及时终止对故障发生方的调用,避免自己受到影响且使故障扩展)。 Isito支持注入两种类型的故障:延迟和中断。延迟是模拟网络延迟或服务过载的情况;中断是模拟上游服务崩溃的情况,表现为HTTP的错误码和TCP连接失败。 5.2 策略和遥测 1.策略 在微服务应用中,除了流量管理以外,常常还需要进行一些额外的控制,比如限流(对调用频率、速率进行限制)、设置白名单和黑名单等。 Istio中的策略控制是依靠Mixer完成的。Envoy代理在每次网络请求时,都会调用Mixer进行预先检查,确定是否满足对应的策略。同时,Mixer又可以根据这些来自流量的数据,进行指标数据的采集和汇总,这就是遥测功能。 2.遥测(Telemetry) 遥测是工业上常用的一种技术,它是指从远程设备中收集数据,并传输到接收设备进行监测。在软件开发中,遥测的含义引申为对各种指标(metric)数据进行收集,并监控、分析这些指标,比如我们经常听到的BI数据分析。 Mixer的一大主要功能就是遥测。前面已经说过,Envoy代理会发送数据给Mixer,这就使得Mixer具有了数据收集的能力。在本章对Mixer的介绍中读者已经了解到Mixer的插件模型,也就是适配器。Mixer可以接入不同的后端设施作为适配器,来处理收集到的指标数据,比如日志分析系统、监控系统等。 5.3 可视化 在微服务应用越来越复杂的情况下,对整个系统的状态进行监控和追踪变得尤为重要。试想如果一个包含上百个服务的系统发生了故障却无法准确定位问题的根源,或者系统压力已经到了承受的临界值而运维人员却浑然不知,这是多么可怕的事情。没有完备的、可观察的监控系统就无法保障系统的稳定性。 Istio可以很方便地和各种监控、追踪工具集成,以便我们以可视化的方式(网页)直观地查看整个系统的运行状态。比如可以集成Prometheus来进行指标数据的收集,然后将收集的数据放在Grafana监控工具中展示;还可以集成Jaeger作为追踪系统,帮助我们对请求的调用链进行跟踪,在故障发生时分析出现问题的根源;或者将请求日志记录到Kibana系统,以图表的方式进行数据分析。 以上提到的这些可视化工具都会在第7章被集成到Istio,并得到详细的介绍。 5.4 安全 Istio 中的安全架构是由多个组件协同完成的。Citadel是负责安全的主要组件,用于密钥和证书的管理;Pilot会将授权策略等信息分发给Envoy代理;Envoy根据策略实现服务间的安全通信;Mixer负责管理授权等工作。图8展示了Istio的安全架构和运作流程。 图8 Istio安全架构 1.认证 Istio提供如下两种类型的身份认证。 传输认证:也叫作服务到服务认证。这种方式的认证是通过双向TLS(mTLS)来实现的,即客户端和服务端(或者是调用者和被调用者)都要验证彼此的合法性。 来源认证:也叫作最终用户认证,用于验证终端用户或设备。Istio使用目前业界流行的JWT(JSON Web Token)作为实现方案(在配置项上Istio提供了扩展性,但在撰写本书时仍然只支持JWT)。 这两种认证的工作原理类似,都是将来自平台的认证策略存储起来,然后通过Pilot分发给Envoy代理,如图9所示。 图9 认证架构 2.授权 Istio的授权功能沿用了Kubernetes中的授权方式:RBAC(Role-Based Access Control,基于角色的访问控制)。它可以为网格中的服务提供不同级别的访问控制。比如命名空间级别、服务级别和方法级别。 图10显示了授权的工作方式。运维人员编写授权策略的清单文件并将其部署到平台(Kubernetes)。Pilot组件会获取策略信息并将其保存到自己的配置存储中,同时监听授权策略的变更情况,以便及时更新。然后Pilot会把授权信息分发给Envoy代理。Envoy在请求到达的时候,评估当前的请求是否合法并作出相应的返回。 图10 授权架构 与安全相关的配置涉及很多细节,我们会在后面的练习章节有针对性地进行具体介绍,以便读者可以通过演练加深理解。 06 小 结 本章主要介绍了Istio的理论知识。Istio作为一个开源的服务网格产品,提供了统一的方式去管理流量、设置安全和监控等服务治理的能力。 Istio的架构分为数据平面和控制平面,这种优雅的设计使得各个组件充分解耦,各司其职,这就是很多人将它称为第二代服务网格产品的原因。数据平面即Envoy代理,负责流量的接管;控制平面包含了Pilot、Mixer、Citadel和Galley,它们分别负责流量控制、策略控制、安全加固和数据收集。通过这些组件的协同工作,Istio顺利地完成了流量管理、策略和遥测、可视化和安全这4大功能。 第3章将进入实践阶段,搭建Istio的开发环境并完成它的安装和部署。 Istio实战指南 作者:马若飞 推荐理由: Service Mesher社区成员,作为Service Mesh的布道者; 该社区成员对于Istio的了解和认知都具备一定的权威性。 。 本书是Istio服务网格技术的入门图书。全书共分为10章,深入浅出地介绍了Istio的相关知识,结合大量的示例,清晰而详细的阐述了Istio的4大特性:连接、策略、可视化和安全。本书的第1章介绍了服务网格的起源和发展,第2~4章介绍了Istio的基本概念和安装。从第5章起通过实例练习的方式介绍了Istio的流量管理等内容,并把Istio应用到真实的项目开发中,帮助读者进一步理解概念。 - END -
《数据科学实战手册(第2版)》 [印度]普拉罕•塔塔(Prabhanjan Tattar) [美]托尼•奥赫达(Tony Ojeda) 肖恩•帕特里克•墨菲(Sean Patrick Murphy) 本杰明•本福特(Benjamin Bengfort) 阿比吉特•达斯古普塔(Abhijit Dasgupta) 著 本书的主要内容有:学习数据科学项目的流程,并使用它来获取、清洗、分析和可视化数据;在多个实战项目中理解数据科学的关键概念;通过一步一步的代码实例来掌握R和Python强大的数据编程能力。无论你是数据科学的新手,还是有经验的专业人士,你都可以从本书提供的89个实际案例中掌握数据科学的关键概念和利用R、Python进行数据编程的能力。 《持续交付2.0:业务引领的DevOps精要》 乔梁 著 持续交付是重要软件开发方法之一。本书分三个部分:第一部分作者根据自己近十年的工作及咨询经历,对原有的持续交付进行了修正,将持续交付重新定义为实现组织战略目标的能力,并引入持续交付的能力模型;第二部分阐述组织打造持续交付能力所需遵守的原则,包括基础原则、组织原则和架构原则;第三部分通过多个案例解读,阐述如何根据组织的当前状况和应用原则,并对最佳实践进行取舍,快速达到组织能力目标。 《Python编程无师自通——专业程序员的养成》 [美] 科里•奥尔索夫(Cory Althoff) 著 从Python 3 中开始学习编程,并构建第一个程序;通过学习“面向对象的程序设计”并创建一个强大的Python程序来让读者掌握技能; 学习使用Git、Bash、正则表达式和数据库等工具,然后使用学习的新编程技能来构建一个网络爬虫;学习计算机科学的基本原理,包括计算机架构、数据结构、算法和网络编程;学习为项目编程:涵盖了软件开发过程、测试和最佳编码实践;了解如何完成团队合作,并获得编程工作。 《机器人爱好者 第8辑》 美国SERVO杂志 著 Servo的内容主题范围很广,从可编程逻辑,到步进马达基础、无线电通信基础、机器人基础知识、自助操作、闭环系统、语音识别、视觉添加等,到产品和图书评论,构建自己的Bot和系统项目等等。诸如DARPA、机器人世界杯足球赛、FIRST、ComBots、水下机器人挑战赛等等活动的报道。
《算法详解(卷1)——算法基础》 Tim Roughgarden 著 算法详解系列图书共有4卷,本书是第一卷——基础算法。本书共有6章,主要介绍了4个主题,它们分别是渐进性分析和大O表示法、分冶算法和主方法、随机化算法以及排序和选择。附录A和附录B简单介绍了数据归纳法和离散概率的相关知识。本书的每一章均有小测验、章末习题和编程题,这为读者的自我检查以及进一步学习提供了较多的便利。 《PHP和MySQL Web开发学习指南》 [澳] 汤姆·巴特勒(Tom Butler)凯文·雅克(Kevin Yank) 著 全书共包括12章。第1章介绍了PHP和MySQL的安装;第2章和第3章,分别简单介绍了MySQL和PHP;第4章将创建一些Web页面;第5章介绍了关系数据库理论;第6章介绍了PHP编程基础;第7章扩展了第6章的主题:第8章讨论正则表达式的应用;第9章探讨了cookie和会话;第10章介绍了MySQL的管理技术;第11章探讨了高级SQL查询技巧;第12章介绍了二进制数据的应用和处理。 《Wireshark网络分析实战(第2版)》 [印度]甘德拉·库马尔·纳纳(Nagendra Kumar Nainar) 著 Wireshark是最流行的一款网络嗅探软件。本书以示例方式详细讲解了如何使用Wireshark进行网络分析。通过本书的学习,读者可以掌握如何安装、配置Wireshark,如何使用Wireshark捕获数据,如何来对捕获的数据进行分析,以解决常见的网络问题等。 《量化交易学习指南——基于R语言》 [印度]帕勒姆·吉特(Param Jeet) 普拉桑特·瓦次(Prashant Vats) 著 本书包括9章内容,分别从R编程基础、统计建模、计量经济学、小波分析、时序分析、算法交易、机器学习在交易中的应用、风险管理、优化、衍生品定价等内容。几乎每一个小标题都是一个热点,非常值得相关领域的人员学习阅读。 《全程软件测试(第3版)》 朱少民 著 本书系统地总结了过去十年中软件测试发生的变化,浓缩了作者许多宝贵的软件测试经验。本书首先介绍对于软件测试的不同看法,全程软件测试的思想,软件测试的基础设施与TA框架、团队能力建设;然后逐步深入到测试的计划、设计、执行、持续反馈和改进;接着,讨论全程测试的思想,包括全程静态测试、全程性能测试、全程安全性、全程建模、全程可视化。本书最后展望了软件测试的未来。 本书适合软件测试人员阅读,也可作为相关专业人士的参考指南。 《RISC-V架构与嵌入式开发快速入门》 胡振波 著 本书是一本介绍RISC-V架构嵌入式开发的入门书籍,以通俗的语言系统介绍了嵌入式开发的基础知识和RISC-V架构的内容,力求帮助读者快速掌握RISC-V架构的嵌入式开发技巧。本书共分为两部分。第一部分为第1~14章,基本涵盖了使用RISC-V架构进行嵌入式开发所需的所有关键知识。第二部分为附录部分,详细介绍了RISC-V指令集架构,辅以作者加入的背景知识解读和注解,以便于读者理解。
《Web前端开发精品课 JavaScript基础教程》 莫振杰 著 全书共分为2大部分,第 1部分是JavaScript基础知识,主要学习JavaScript基础概念如变量、运算符、表达式等。第 二部分是JavaScript进阶知识,主要学习DOM、事件操作以及各种开发技术。除了知识讲解,教程还融入了大量的开发技巧,并且更加注重编程思维的培养,使得学习者能有顺畅的学习思路,这一点是极其重要的。 《Python金融实战》 【美】Yuxing Yan(严玉星) 著 Python凭借其简单、易读、可扩展性以及拥有巨大而活跃的科学计算社区,在需要数据分析和处理大量数据的金融领域得到了广泛而迅速的应用,并且成为越来越多专业人士首 选的编程语言之一。 本书通过12章内容介绍了Python在金融领域的应用,从Python的安装、基础语法,再到一系列简单的编程示例,本书循序渐进地引导读者学习Python。同时,本书还结合Python的各个模块以及金融领域中的期权价格、金融图形绘制、时间序列、期权定价模型、期权定价等内容,深度揭示了Python在金融行业中的应用技巧。 本书适合金融、会计等相关专业的高校师生阅读,也适合金融领域的研究人员和从业人员参考学习。对于有一定计算机编程基础,但想要从事金融行业的读者,本书也是不错的参考用书。 《Go语言程序设计》 【英】Mark Summerfield 著 本书既是一本实用的Go语言教程,又是一本Go语言参考手册。书中从如何获取和安装Go语言环境,以及如何建立和运行Go程序开始,逐步介绍了Go语言的语法、特性以及一些标准库,内置数据类型、语句和控制结构,然后讲解了如何在Go语言中进行面向对象编程,Go语言的并发特性,如何导入和使用标准库包、自定义包及第三方软件包,提供了评价Go语言、以Go语言思考以及用Go语言编写高性能软件所需的所有知识。
随着互联网的飞速发展,各行各业对互联网服务的要求也越来越高,服务架构能撑起多大的业务数据?服务响应的速度能不能达到要求?我们的架构师每天都在思考这些问题。 对于数据库或者对象存储等服务来说,它们受限于自己先天的设计目标,往往不能具有很好的性能,响应时间通常是秒级。此时就需要高性能的缓存来为我们的服务提速了,缓存服务的响应时间通常是毫秒级,甚至小于1ms。 缓存服务需要被设置在其他服务的前端,客户端首先访问缓存,查询自己的数据,仅当客户端需要的数据不存在于缓存中时,才去访问实际的服务。从实际的服务中获取到的数据会被放在缓存中,以备下次使用。 缓存的设计目标就是尽可能地快,但它引起了其他的问题。比如目前业界使用较多的缓存服务有Memcached和Redis等,它们都是内存内缓存,单节点最大的容量不能超过整个系统的内存。 且一旦服务器重启,对于Memcached来说就是内容彻底丢失;Redis稍好一点,但也要花费不少时间从磁盘上的数据文件中重新读入内存。 当我们决定要用Go语言编写一个缓存服务的时候,首先想到的就是HTTP服务。因为用Go语言写基于HTTP的缓存服务真的是太方便了,我们只需要一个map来保存数据,写一个handler负责处理请求,然后调用http.ListenAndServe,最后用go run运行。一切就是这么简单,你不需要去考虑复杂的并发问题,也不需要自己设计一套网络协议,Go语言的HTTP服务框架会帮你处理好底层的一切。 我们在本文将要实现的是一个简单的内存缓存服务,所有的缓存数据都存储在服务器的内存中。一旦服务器重启,所有的数据都将被清零。 缓存服务的接口 1.1.1 REST接口 本章的接口支持缓存的设置(Set)、获取(Get)和删除(Del)这3个基本操作,同时还支持对缓存服务状态的查询。Set操作用于将一对键值对(key value pair)设置进缓存服务器,它通过HTTP的PUT方法进行;Get操作用于查询某个键并获取其值,它通过HTTP的GET方法进行;Del操作用于从缓存中删除某个键,它通过HTTP的DELETE方法进行。我们可以查询的缓存服务状态包括当前缓存了多少对键值对,所有的键一共占据了多少字节,所有的值一共占据了多少字节。 客户端通过HTTP的PUT方法将一对键值对设置进缓存服务器,服务器将该键值对保存在内存堆上创建的map里。 这里/cache/是一个URL,它标识了缓存的值(value)所在的位置。URL是Uniform Resource Locator的缩写,它是一个网络地址,用于引用某个网络资源在网络上的位置。HTTP的请求正文(request body)里包含了该key对应的value的内容。 客户端通过HTTP的GET方法从缓存服务器上获取key对应的value,服务器在map中查找该key,如果key不存在,服务器返回HTTP错误代码404 NOT FOUND;如果key存在,则服务器在HTTP响应正文(response body)中返回相应的value。 客户端通过HTTP的GET方法从缓存服务器上获取key对应的value,服务器在map中查找该key,如果key不存在,服务器返回HTTP错误代码404 NOT FOUND;如果key存在,则服务器在HTTP响应正文(response body)中返回相应的value。 客户端通过HTTP的DELETE方法将key从缓存中删除。无论之前该key是否存在,之后它都将不存在,服务器始终返回HTTP错误代码200 OK。 客户端通过这个接口获取缓存服务的状态,在HTTP响应正文中返回的状态是以JSON格式编码的一个cache.Stat结构体(见例1-3)。 1.1.2 缓存Set流程 我们可以用一张简单的图来概括Set流程,见图1-1。 图1-1 in memory缓存的Set流程 客户端的PUT请求提供了key和value。cacheHandler实现了http.Handler接口,其ServeHTTP方法对HTTP请求进行解析,并调用cache.Cache接口的Set方法。 在cache模块中,inMemoryCache结构体实现Cache接口,其Set方法最终将键值对保存在内存的map中。cacheHandler最后会返回客户端一个HTTP错误号来表示结果,如果成功则返回的是200 OK,否则返回500 Internal Server Error。 Go语言中的map的含义和用法跟大多数现代编程语言中的map一样,map是一种用于保存键值对的散列表数据结构,可以通过中括号 [ ] 进行key的查询和设置。 由于程序会对key进行散列和掩码运算以直接获取存储key的偏移量,所以能获得近乎O(1)的查询和设置复杂度。之所以说近乎O(1)是因为两个key在经过散列和掩码运算后有可能会具有相同的偏移量,此时将不得不继续进行线性搜索,不过发生这种不幸情况的概率很小。 1.1.3 缓存Get流程 缓存Get流程见图1-2。 图1-2 in memory缓存的Get流程 客户端的Get请求提供了key。cacheHandler的ServeHTTP方法对HTTP请求进行解析,并调用cache.Cache接口的Get方法。inMemoryCache结构体的Get方法在map中查询key对应的value并返回。cacheHandler会将value写入HTTP响应正文并返回200 OK,如果cache.Cache.Get方法返回错误,cacheHandler会返回500 Internal Server Error。如果value长度为0,说明该key不存在,cacheHandler会返回404 Not Found。 1.1.4 缓存Del流程 缓存Del流程见图1-3。 图1-3 in memory缓存的Del流程 客户端的DELETE请求提供了key。cacheHandler的ServeHTTP方法对HTTP请求进行解析,并调用cache.Cache接口的Del方法。inMemoryCache结构体的Del方法在map中查询key是否存在,如果存在则调用delete函数删除该key。如果cache.Cache.Del方法返回错误,cacheHandler会返回500 Internal Server Error,否则返回200 OK。 REST接口和处理流程介绍完了,接下来我们来看看如何实现。 Go语言实现 1.2.1 main包的实现 缓存服务的main包只有一个函数,就是main函数。在Go语言中,如果某个项目需要被编译为可执行程序,那么它的源码需要有一个main包,其中需要有一个main函数,它用来作为可执行程序的入口函数。如果某个项目不需要被编译为可执行程序,只是实现一个库,则可以没有main包和main函数。我们的缓存服务需要被编译成一个可执行程序,所以需要提供main包和main函数。main函数的实现见例1-1: 例1-1 main函数 我们的main函数非常简单,它需要做的只是调用cache.New函数创建一个新的cache.Cache接口的实例c,然后以c为参数调用http.New函数创建一个指向http.Server结构体的指针并调用其Listen方法。 cache.New这样的写法则是指定我们调用的New函数属于cache包。Go语言调用同一个包内的函数不需要在函数前面带上包名,Go编译器会默认在当前包内查找。调用另一个包中的函数则需要指定包名,让Go编译器知道去哪里查找这个函数。这里我们是在main包中调用cache包的New函数,所以需要指定包名。 1.2.2 cache包的实现 我们在cache包中实现服务的缓存功能。在cache包内,我们首先声明了一个Cache接口,见例1-2。 例1-2 Cache接口 在Go语言中,接口和实现是完全分开的。接口甚至拥有它自己的类型(type interface)。开发者可以自由声明一个接口,然后以一种或多种方式去实现这个接口。在例1-2中,我们看到的就是一个名为Cache的接口声明。 在接口内,我们会声明一些方法,一个接口就是该接口内所有方法的集合。任何结构体只要实现了某个接口声明的所有方法,我们就认为该结构体实现了该接口。实现某个接口的结构体可以不止一个,这意味着同样的接口实现的方式可以有很多种,Go语言就是用这种方式来实现多态。 我们的Cache接口一共声明了4个方法,分别是Set、Get、Del和GetStat。 Set方法用于将键值对设置进缓存,它接收两个参数,类型分别是string和[ ]byte,其中string是key的类型,而[ ]byte则是value的类型,byte前面的中括号意味着它的类型是字节(byte)的切片(slice)。Go语言中切片的内部实现可以被认为是一个指向切片第一个元素的地址和该切片的长度。切片和数组(Array)的区别在于数组的长度是固定的,而切片则是底层数组的一个视图,其长度可以动态调整。Set方法的返回值只有一个。若返回值的类型是error,则用于返回Set操作的错误,当Set操作成功时,返回nil。 Get方法根据key从缓存中获取value,所以它接收一个string类型的参数,返回值则是两个,分别是 [ ]byte和error。在Go语言中,当函数具有多个返回值时,需要用小括号()将它们括在一起。 Del方法从缓存中删除key,所以它只有一个string类型的参数和一个error类型的返回值。 GetStat方法用于获取缓存的状态,它没有参数,只有一个Stat类型的返回值。Stat是一种结构体,见例1-3。 例1-3 Stat结构体相关实现 Go语言编程仅仅声明接口类型(type interface)是没用的,还必须实现接口。而接口的实现需要依附于某个结构体类型(type struct)。Stat就是一个结构体,它的内部有3个字段,Count用于表示缓存目前保存的键值对数量,KeySize和ValueSize分别表示key和value占据的总字节数。 结构体也可以包含方法,和接口不同的地方在于结构体必须实现这些方法,而接口只需要声明。Stat结构体实现了add和del两个方法,这两个方法分别用于新加键值对和删除键值对时改变缓存的状态。 在了解完整个Cache接口之后,我们就可以去看看New函数的实现了,见例1-4。 例1-4 New函数实现 cache包的New函数用来创建并返回一个Cache接口,它接收一个string类型的参数typ,typ用于指定需要创建的Cache接口的具体结构体类型。 我们在函数体的第一行声明了一个类型为Cache接口的变量c,当typ字符串等于“inmemory”时,我们将newInMemoryCache函数的返回值赋值给c。如果c为nil,我们调用panic报错并退出整个程序,否则我们打印一条日志通知缓存开始服务并将c返回。 本文实现的缓存服务是一种内存缓存(in memory),实现Cache接口的结构体名为inMemoryCache,见例1-5。 例1-5 inMemoryCache相关代码 inMemoryCache结构体包含一个成员c,类型是以string为key、以 [ ]byte为value的map,用来保存键值对;一个mutex,类型是sync.RWMutex,用来对map的并发访问提供读写锁保护;一个Stat,用来记录缓存状态。 Go语言的map可以支持多个goroutine同时读,但不能支持多个goroutine同时写或同时既读又写,所以我们必须用一个读写锁保护map的并发读写,当多个goroutine同时读时,它们会调用mutex.RLock(),互不影响。 当有至少一个goroutine需要写时,它会调用mutex.Lock(),此时它会等待所有其他读写锁释放,然后自己加锁,在它加锁后其他goroutine需要加锁则必须等待它先解锁。读写锁mutex的类型是sync.RWMutex,sync是Go语言自带的一个标准包,它提供了包括Mutex、RWMutex在内的多种互斥锁的实现。 需要特别注意的是Stat,它的类型是Stat结构体,但是它没有提供成员名字,这种写法在Go语言中被称为内嵌。结构体可以内嵌多个结构体和接口,接则只能内嵌多个接口。 Go语言通过内嵌来实现继承,内嵌结构体/接口可以被认为是外层结构体/接口的父类。一个内嵌结构体/接口的所有成员/方法都可以通过外层结构体/接口直接访问,那些成员/方法的首字母不需要大写。(通常我们从一个结构体外部只能访问其首字母大写的成员/方法,访问自己的内嵌成员的成员/方法不受此限制。)当我们需要访问某个内嵌成员本身时,我们可以直接用它的类型指代它,就如同我们在inMemoryCache.GetStat函数中做的那样。 1.2.3 HTTP包的实现 HTTP包用来实现我们的HTTP服务功能。由于不需要使用多态,我们在HTTP包里并没有声明接口,而是直接声明了一个Server结构体,见例1-6。 例1-6 Server相关实现 Server结构体中内嵌了cache.Cache,cache.Cache就是之前介绍的cache包的Cache接口。HTTP包的Server结构体内嵌该接口意味着http.Server也实现了cache.Cache接口,而实现的方式则由实际的内嵌结构体决定。 接下来我们看到Server的Listen方法会调用http.Handle函数,它会注册两个Handler分别用来处理/cache/和/status这两个HTTP协议的端点。 这里需要注意的是http.Handle函数并不属于我们的HTTP包,而是Go语言自己的net/http标准包。还记得吗?Server结构体自身就处于我们的HTTP包里,引用自己包内的名字无需指定包名,所以当我们指定HTTP包名时,Go语言编译器会知道去net/http包中查找名字。 Server.cacheHandler方法返回的是一个http.Handler接口,它用来处理HTTP端点/cache/的请求,也就是缓存的Set、Get、Del这3个基本操作,见例1-7。 例1-7 cacheHandler相关实现 cacheHandler结构体内嵌了一个Server结构体的指针,并实现了ServeHTTP方法,实现该方法就意味着实现了http.Handler接口。例1-8展示了Go语言标准包net/http对Handler接口的定义。 例1-8 Go标准包net/http中Handler接口的定义 cacheHandler的ServeHTTP方法解析URL以获取key,并根据HTTP请求的3种方式PUT/GET/DELETE决定调用cache.Cache的Set/Get/Del方法。 这里我们看到了Go语言内嵌的高阶使用方式——多重内嵌:cacheHandler内嵌了Server结构体指针,而Server内嵌了cache.Cache接口。于是cacheHandler就可以直接访问cache.Cache的方法了。 Server.statusHandler方法同样返回一个http.Handler接口,其实现见例1-9。 例1-9 statusHandler相关实现 和cacheHandler一样,statusHandler内嵌Server结构体指针并实现ServeHTTP方法。该方法调用cache.Cache的GetStat方法并将返回的cache.Stat结构体用JSON格式编码成字节切片b,写入HTTP的响应正文。 如果你是一位程序员,看到这里你的心里可能会有一个疑问。我们这样实现会不会太复杂了?为了处理两个HTTP端点的请求,我们需要实现两个Handler结构体并分别实现它们的ServeHTTP方法,能不能直接在Server结构体上实现ServeHTTP方法并根据URL区分不同的HTTP请求? 从实现上来说是可行的,但是那意味着Server的ServeHTTP需要承担两个不同的职责,处理两类HTTP请求。将这两类请求分开到不同的结构体内实现符合SOLID的单一职责原则。 Go语言的实现介绍完了,接下来我们需要把程序运行起来,并进行功能测试来验证我们的实现。 《分布式缓存——原理、架构及Go语言实现》 胡世杰 著 点击此处购买纸书 本书共分3个部分,每个部分都有3章。第1部分为基本功能的实现,主要介绍基于HTTP的in memory缓存服务、HTTP/REST协议、TCP等。第2部分介绍性能相关的内容,我们将集中全力讲解从各方面提升缓存服务性能的方法,主要包括pipeline的原理、RocksDB批量写入等。最后一个部分则HE 分布式缓存服务集群有关,主要介绍分布式缓存集群、节点的再平衡功能等。