Dapr是一个由微软主导的云原生开源项目,国内云计算巨头阿里云也积极参与其中,2019年10月首次发布,到今年2月正式发布V1.0版本。在不到一年半的时间内,github star数达到了1.2万,超过同期的kubernetes、istio、knative等,发展势头迅猛,业界关注度非常高。
什么是云原生
云原生技术有利于各组织在公有云、私有云和混合云等新型动态环境中,构建和运行可弹性扩展的应用。云原生的代表技术包括容器、服务网格、微服务、不可变基础设施和声明式API。
这些技术能够构建容错性好、易于管理和便于观察的松耦合系统。结合可靠的自动化手段,云原生技术使工程师能够轻松地对系统作出频繁和可预测的重大变更。
云原生计算基金会(CNCF)致力于培育和维护一个厂商中立的开源生态系统,来推广云原生技术。我们通过将最前沿的模式民主化,让这些创新为大众所用。
什么是Dapr
Dapr(Distributed Application Runtime)
分布式应用运行时,是一个可移植的、事件驱动的运行时,它使任何开发人员能够轻松构建出弹性的、无状态和有状态的应用程序,并可运行在云平台或边缘计算中,它同时也支持多种编程语言和开发框架。
Dapr 愿景
可移植,事件驱动,弹性,有状态和无状态,云和边端,语言无关,框架无关。
Any language, Any framework, Anywhere
其核心是要提供一个有标准,可配置,包含各种分布式能力的运行时。
如今,我们正经历着上云浪潮。开发人员对Web+数据库应用结构(例如经典3层设计)非常熟悉,并且使用得手,但对本身能支持分布式的微服务应用结构却感觉陌生。成为分布式系统专家很难,并且你也不需要这么做。开发人员希望专注于业务逻辑,同时希望平台为其提供可伸缩的、弹性的、可维护性和云原生架构的其他功能。
这就是Dapr所要解决的。Dapr将构建微服务应用的最佳实践设计成开放、独立和模块化的方式,让你能够选择的任意开发语言和框架构建可移植应用程序。每个构建块都是完全独立的,您可以采用其中一个或多个或全部来构建你的应用。
此外,Dapr是和平台无关的,这意味着您可以在本地、Kubernetes群集或者其它集成Dapr的托管环境中运行应用程序。这使得您能够在云平台和边缘计算中运行微服务应用。
使用Dapr,您可以使用任何语言、任何框架轻松构建微服务应用,并运行在任何地方。
Dapr 发展历程
- 2019年10月:微软在GitHub上开源了Dapr,发布0.1.0版本
- 2021年2月:Daprv1.0版本发布
阿里巴巴深度参与Dapr项目,不仅仅以终端用户的身份成为Dapr的早期采用者,也通过全面参与Dapr的开源开发和代码贡献成为目前Dapr项目中的主要贡献公司之一,仅次于微软:
- 2020年中:阿里巴巴开始参与Dapr项目,在内部试用功能并进行代码开发
- 2020年底:阿里巴巴内部小规模试点Dapr,目前已经十几个应用在使用Dapr。
Dapr 引领云原生的未来
分布式应用所需:
- 生命周期:包括部署,健康检查,水平扩展,配置管理等,目前这些需求的最佳实践,都陆续在kubernetes上有了落地。
- 网络:网络方面的需求是service Mesh的主战场,比如istio可以满足这里绝大部分需求,除了pub/sub。
- 状态:包括数据的读写,状态其实是非常难以管理的,涉及幂等,缓存,数据流等等。
- 绑定:主要是指和系统外部资源的交互。
Multi runtime是由RedHat首席架构师Bilgin Ibryam提出的,实际上multi runtime和dapr并没有直接的关系,multi runtime的提出是在dapr开源之后。作者的文章重点对当今分布式应用的需求做了归类,并且分析了当前流行的云原生项目是如何满足这些分布式需求,包括kubernetes,istio,dapr等,最后,作者对分布式应用和中间件的未来发展,做了推导和预测,这就是multiruntime。
过去,我们对云原生的全景:
有了Dapr之后,我们对云原生的全景:
应用的期望就是中间件的方向:
- 应用可以使用任意喜爱而适合的语言编写,可以快速开发和快速迭代。
- 应用需要的能力都可以通过标准的API提供,无需关心底层具体实现。
- 应用可以部署到任意的云端,不管是公有云、私有云还是混合云,没有平台和厂商限制,无需代码改造。
- 应用可以根据流量弹性伸缩,顶住波峰的压力,也能在空闲时释放资源。
Service Mesh探索了Sidecar模式,Dapr将Sidecar模式推广到更大的领域:
- 完善的多语言支持和应用轻量化的需求推动中间件将更多的能力从应用中分离出来
- Sidecar模式会推广到更大的领域,越来越多的中间件产品会开始Mesh化,整合到Runtime。
- 对厂商锁定的天然厌恶和规避,会加剧对可移植性的追求,从而进一步促使为下沉到Runtime中的分布式能力提供标准而业界通用的API。
- API的标准化和社区认可,将成为Runtime普及的最大挑战,但同时也将推动各种中间件产品改进自身实现,实现中间件产品和社区标准API之间的磨合与完善。
预测未来:
Dapr 与Istio、Linkerd或OSM等服务网格相比如何?
Dapr不是一个服务网格。虽然服务网侧重于细粒度网络控制,但Dapr专注于帮助开发人员构建分布式应用程序。Dapr和服务网都使用sidecar模式,并随应用程序一起运行,它们确实具有一些重叠的功能,但也提供独特的优势。
虽然Dapr和服务网格确实提供了一些重叠功能,但dapr不是服务网格,其中服务网格被定义为网络的服务网格。与专注于网络问题的服务网格不同,Dapr专注于提供构建基块,使开发人员更容易将应用程序构建为微服务。Dapr以开发人员为中心,而服务网格以基础设施为中心。
在大多数情况下,开发人员不需要意识到他们正在构建的应用程序将部署在包括服务网格在内的环境中,因为服务网格会拦截网络流量。服务网格主要由系统操作员管理和部署。但是,Dapr构建块API旨在供开发人员在其代码中明确使用。
Dapr与服务网格共享的一些常见功能包括:
- 使用mTLS加密实现安全的服务到服务通信
- 服务到服务指标集合
- 服务到服务分布式跟踪
- 通过重试获得可恢复能力
重要的是,Dapr通过以开发人员为中心的关注点提供服务发现和调用。这意味着,通过Dapr的服务调用API,开发人员在服务名称上调用一种方法,而服务网格则处理网络概念,如IP和DNS地址。但是,Dapr不为路由或流量拆分等流量行为提供功能。流量路由通常使用应用程序的入口代理处理,并且不必使用服务网格。此外,Dapr还为状态管理、发布/订阅、Actor等提供了其他应用级别的构建块。
Dapr和服务网之间的另一个区别是可观察性(跟踪和指标)。服务网格在网络级别运行,并跟踪服务之间的网络调用。Dapr通过服务调用来达到此操作,但Dapr也使用写入Cloud Events信封的跟踪Id在发布/订阅调用上提供可观察性(跟踪和指标)。这意味着Dapr的指标和跟踪比使用服务到服务调用和发布/订阅进行通信的应用程序的服务网格更为广泛。
下图捕获Dapr和服务网格提供的重叠功能和独特功能:
Dapr 三驾马车
Dapr的设计是典型的分层架构,其核心理念,是利用抽象层来实现应用关注点的分离,用以降低分布式应用的复杂性。
在Dapr的架构中,核心的三个组成部分:API
,Building Blocks
和Components
。
Dapr API
Dapr提供两种API,HTTP1.1/REST和HTTP2/gRPC,两者在功能上是对等的。
应用如何能使用到这些分布式能力,这是Dapr最核心的设计,也是dapr应用和非dapr应用最关键的区别: dapr利用标准API暴露各种分布式能力。API定义了应用所需的分布式能力。dapr提供两种API: HTTP1.1/REST
和HTTP2/gRPC
,两者在功能上是等价的。这些API是平台无关的,或者说是实现无关的,这是dapr能否流行的一个关键。
应用只需要按照API规范发起,不管是服务访问,还是存储,还是发布消息到队列里,都是HTTP接口。不管是操作redis还是mysql都是一样的API。在应用看来,一切所需的能力,都可以用HTTP协议来表示,这些能力的获取是标准化的,只要应用需要的分布式能力不变,那应用的代码就不需要改变。
将「分布式原语」映射到HttpAPI上,极大地减少了程序员心智的开销。在应用代码中不再需要引入相关的组件调用库,不需要去封装组件的具体调用方式,不需要对不同的实现做区分。
另外在用户应用侧,dapr还提供了多种语言的SDK,这些SDK的目的是用更便捷的方式来暴露buildingBlocks的API,用更加语义化的方法调用,来封装Http/gRPC的调用。
Dapr Building Blocks(构建块)
翻译为构建块,这是Dapr对外提供能力的基本单元,每个构建块对外提供一种分布式能力。
Dapr对外提供能力的基本单元,是对分布式能力的抽象和归类,包括以下几大类:
- service-to-service invocation
- State management
- Publish and subscribe
- Resource bindings
- Actors
- Observability
- Secrets
Dapr目前已有的构建块和他们提供的能力的简单描述:
Building Block
构建块是可以从您的代码中调用的HTTP或gRPC API,并且由一个或多个Dapr组件组成。
构建块解决了构建弹性微服务应用程序中的常见挑战,并编纂了最佳实践和模式。Dapr由一组构建块组成,并且具有可扩展性以添加新的构建块。
下图显示了构建块如何公开了可被代码调用的公共API,并使用组件来实现构建块的能力。
以下是Dapr提供的构建块类型:
每个构建块都是独立的,这意味着您可以采用其中一个或多个或全部来构建应用。在当前Dapr的初始版本中,提供了以下构建块:
构建块 | 描述 |
服务间调用 | 弹性的服务间调用能在远程服务上进行方法调用(包括检索),无论它们是否位于受支持的托管环境中的。 |
状态管理 | 对于存储键/值对的状态管理,长时间运行,高可用性,有状态服务可轻松写入应用程序中的无状态服务。状态存储是可插拔的,可以包括Azure Cosmos DB,Azure SQL Server,Postgre SQL,AWS Dynamo DB或Redis等。 |
发布订阅 | 发布活动并订阅主题 |
资源绑定 | 带触发器的资源绑定通过接收和发送事件到任何外部源(如数据库、队列、文件系统等)来进一步构建事件驱动架构,以实现扩展性和弹性。 |
Actors | 一种用于有状态和无状态对象的模式,通过方法和状态的封装让并发变得简单。Dapr在其actor运行时提供了很多能力,包括并发,状态管理,用于actor激活/停用的生命周期管理,以及唤醒actor的计时器和提醒器。 |
可观测性 | Dapr可以发出度量,日志和跟踪以调试和监控Dapr和用户应用程序。Dapr支持分布式跟踪,通过使用W3C跟踪上下文标准和OpenTelemetry发送到不同的监控工具,以方便诊断和服务于生产中的服务间调用。 |
秘密 | Dapr提供秘密管理,并与公有云和本地秘密存储集成,以检索秘密,用于应用代码。 |
Dapr Components(组件)
组件层,这是Dapr的能力实现层,每个组件都会实现特定构建块的能力。
Components提供和各种分布式实现的对接,包括自建的,云上的,边缘等等。
理论上building block可以组合使用任意的components,一个component也可以被不同的building block使用。比如actor和state都会使用state component;另一个例子,service invocation会使用namere solution和middleware component,而且不同的场景下,可以选择不同的component实现。
Component类型和实现:在实现层面,每一种component类型定义了一系列接口(interface definition),每一种component类型有多种component实现,他们都实现了component类型要求的接口(interface)。
三驾马车的关系
- Dapr Building Blocks提供“能力”
- Dapr API提供对分布式能力的“抽象”,对外暴露Building Block的能力
- Dapr Components是Building Block能力的具体“实现”
Dapr API如何实现
比如一个电商系统,需要持久化存储,传统的做法是,我们要先决策使用什么存储,mysql或者redis等,我们需要在代码里引入相应的SDK,编写各异的实现,未来如果应用想要切换存储类型,或者从本地存储迁移到云上,改动非常大。
假设这个系统的特征是读多写少,那我们倾向于用乐观锁来更新数据。业务提出来的「用乐观锁控制并发写入」这就是一个典型的分布式需求,而这种需求的实现在不同的存储系统中不尽相同,比如mysql是需要用户显式指定一个字段作为版本信息,用户写操作是需要把版本信息传回服务器,而redis乐观锁需要用户指定在redis server端watch某个key。类似的需求还有数据库一致性,是使用最终一致性还是强一致性,各种存储实现也不同。
如上图所示,如果接入使用dapr runtime,应用发起存储调用非常简单,不需要在应用代码里引入redis或者mysql的SDK,也不用关心实际存储使用是什么通信协议,应用代码里只需要使用分布式原语和dapr runtime通信,通信的协议是简单的Http或者gRPC,dapr runtime去实现这些分布式能力。
Dapr 服务发现(Service Invocation)
主要能力:
- 服务发现
- 通信安全
- 失败重试
- 可观测性
在kubernetes中使用dapr,dapr会为每个服务生成一个新的service(以-dapr结尾),sidecar之间的通信都是gRPC,每个应用需要指定一个app-id用于服务发现,应用需要显示的发起对runtimeAPI的调用,没有类似mesh的iptables透明拦截。
大家可以脑洞一下,如果dapr这种模式能大规模流行,那市面上大部分RPC是不是都不再需要了,如今大部分RPC虽然各有专长,但是大部分功能都是类似的,服务发现、编解码、网络传输,有的RPC框架还带服务治理的能力。大部分能力目前都可以由mesh或者dapr这类runtime来提供,这也是一个明显的趋势。
Dapr 状态管理(State Management)
主要能力:
- CRUD,包括批量操作
- 事务
- 并发:
- first-write-wins、last-write-wins
- 一致性:
- 最终一致、强一致性
- 可插拔(Pluggablestatestores)
State提供一致的键值对存储抽象,这里不包括关系型或者其他类型的存储。总的来说,在云原生领域(以kubernetes和etcd为代表),键值对存储的适用范围更广。另外相比其他存储类型,键值对存储引擎的接口抽象更容易实现,即使是关系型数据库,也能轻松的实现对键值对API的支持。
但仍然不是所有的存储引擎都能提供等价的键值对存储能力。为了保证应用程序的可移植性,这里的确是需要一些适配工作。比如像Memcached,Cassandra这些是不支持事务的,而很多数据库也不能提供基于ETag的乐观锁能力。
对于并发控制,在API层,Dapr利用HTTP ETags
来实现并发控制,类似kubernetes对象的resource version
,具体地:Dapr在返回数据时,会带上Etag属性。如果用户需要使用乐观锁做更新操作,请求中需要带回Etag,只有当Etag和服务器上数据的相同时,更新操作才会成功。如果更新操作没有带上Etag,那并发模式将是last-write-wins
。
Dapr 发布和订阅(Publish And Subscribe)
使用发布和订阅模式,微服务间可以充分的解耦。
主要能力:
- 统一的消息格式:
- CloudEvents
- At-Least-Onceguarantees(消息绝不会丢,但可能会重复传递)
- 支持消息过期时间(permessageTTL)
- 支持topic可见性配置
Runtime不仅可以做能力的对接适配,还可以做增强,这是一个例子:如果消息组件原生支持消息有效期,那Runtime直接转发TTL相关操作,过期的行为由组件直接控制,而对于那些不支持消息有效期的组件,Dapr会在Runtime中补齐相关的过期功能。(Cloud Event里有Expiration)
Dapr 绑定(Bindings)
Bindings其实和之前的pub/sub非常类似,也是利用异步通信传递消息。它俩主要的区别是:pub/sub主要面向的是dapr内部应用,而bindings主要解决的和外部依赖系统的输入输出。
实际上它俩下层的components有很多是重叠的,比如说kafka,redis既可以作为内部消息传递,也可以作为外部消息传递。pub/sub基本可以等同于消息队列,但bindings主要是处理事件(trigger handler),比如twitter关键字事件,比如github webhooks等。
Dapr 并发模型(Actor)
- 最基本的计算单元,封装了可以执行的行为和私有状态
- 通过信箱异步通信
- 内部单线程
- 虚拟的:不需要显示创建,自动GC
Actor是一种并发编程的模型,Actor表示的是一个最基本的计算单元,封装了可以执行的行为和私有状态。actor之间相互隔离,它们并不互相共享内存,也就是说,一个actor能维持一个私有的状态,并且这个状态不可能被另一个actor所改变。在actor模型里每个actor都有地址(信箱),所以它们才能够相互发送消息。每个actor只能顺序地处理消息。单个actor不考虑并发。
Dapr中actor是虚拟的,它们并不一定要常驻内存。它们不需要显式创建或销毁。dapr actor runtime在第一次接收到该actor ID的请求时自动激活actor。如果该actor在一段时间内未被使用,那么runtime将回收内存对象。如果以后需要重新启动,它还将还原actor的一切原有数据。
Actor placement service为系统提供了actor分发和管理,placement会跟踪actor类型和所有实例的分区,并将这些分区信息同步到每个dapr实例中,并跟踪他们的创建和销毁。
Dapr 中间件原则(Middleware Pipelines)
注意middleware pipelines是一个component类型,而不是building block。
Dapr官方提供流量管控的能力比较弱,和istio相比的话,目前dapr只有重试,加密等少数的管控能力,但dapr提供一个扩展的方式:这就是middleware pipelines,用户可以按需编写不同的实现,并把他们级联起来使用。
其实这种方式在各种编程语言web框架中非常常见,只是叫法不同,有的叫装饰者模型,有的叫洋葱模型,其实模式都是一样:请求在路由到用户代码之前,会先按序执行middleware pipelines,请求经过应用处理后,再按相反顺序执行上述middleware pipeline。通常在前序中对request做相应的增强处理,在后续中对response做增强处理。
咋一看这可能是一个不太起眼的功能,但和传统web框架的middleware不一样,dapr runtime本身是在应用进程之外,所以不存在语言限制的问题。这使得middleware提供的功能可以跨语言共享。比如dapr原生没有提供限流和自定义鉴权的功能(呼声很高的2个场景),我们可以遵循middleware的接口按需实现,然后植入dapr运行时中。
Dapr 部署模式(托管方式)
Dapr可以托管在多种环境中,包括用于本地开发的自托管,或部署到一组VM、Kubernetes和边缘环境(如Azure IoT Edge)。
Dapr使用sidecar模式来暴露building blocks的能力,这里的sidecar除了包括sidecar container外,还可以是sidecar process。
在非容器化环境中,用户应用和dapr runtime都是独立的进程;而在kubernetes这种容器化环境中,dapr runtime作为sidecar container注入到业务pod中,这和service mesh sidecar模式是一致的。
自托管
在自托管模式下,Dapr作为单独的sidecar进程运行,服务代码可以通过HTTP或gRPC调用该进程。在自托管模式下,您还可以将Dapr部署到一组VM上。
Dapr可以配置为在开发人员本地计算机上以自托管模式运行。每个运行的服务都有一个Dapr运行时进程(或sidecar),配置为使用状态存储,pub/sub,绑定组件和其他构建块。
您可以使用Dapr CLI在本地机器上运行启用了Dapr的应用程序。
Kubernetes托管
在容器托管环境(如Kubernetes)中,Dapr作为sidecar容器运行,和应用程序容器在同一个pod中。
Dapr可以配置为在任何Kubernetes集群上运行。在Kubernetes中,dapr-sidecar-injector和dapr-operator服务提供一流的集成,以将Dapr作为sidecar容器启动在与服务容器相同的pod中,并为在集群中部署的Dapr组件提供更新通知。
dapr-sentry服务是一个认证中心,它允许Dapr sidecar实例之间的相互TLS进行安全数据加密。
Dapr 仪表盘(控制面板)
整个控制面还是一个微服务。和istio早期有点类似。
- Sidecarinjector:利用kubernetes mutating webhook给业务pod注入dapr runtime sidecar容器,以及运行所需的环境变量,启动参数等。包括连接控制面operator的地址(control-plane-address)等。
- Operator:会list watch用户定义的Component资源,并下发给数据面的dapr runtime。数据面runtime会持有一个Operator Client去连接控制面Operator。
- Sentry: 为dapr系统中的工作负载提供基于mtls的安全通信。mtls能强制通信双方进行身份认证,同时在认证之后保证通信都走加密通道。Sentry的功能很类似istio里的Citadel(目前已经合并到istiod)。在整个过程中,sentry充当证书颁发机构(CA),处理dapr sidecar发起的签署证书请求,另外还要负责证书的轮转。除了dapr sidecar之间的自动mTLS之外,sidecar和dapr控制面服务之间也是强制性的mTLS。
- Placement:用于跟踪actor的类型和实例分布,并同步给数据面的runtime。
- Dapr Dashboard
Dapr Dashboard
是一个WebUI,可帮助您可视化本地计算机或Kubernetes上运行的Dapr实例的信息。
Dapr 性能
sidecar模式会带来额外的性能开销。以我们使用service mesh的经验来看,这种模式的性能开销主要是2个方面,一个是流量经过sidecar的拦截、流量管控和转发损耗,另一个是sidecar需要从控制面同步管理数据,sidecar需要存储和处理这些数据,这可能会给数据面内存和CPU带来压力,特别是大规模场景下。
在官方对daprV1.0的性能测试数据看:在不开启mtls和遥测的情况下,延迟P90大概增加1.4ms,在开启mtls和0.1 tracingrate情况下,P90数据大概还会增加了3ms左右。
这个数据要比istio好,dapr sidecar没有太多的流量管控和修改的功能,也没有使用iptables拦截,开销相对较小。为了尽可能提高通信效率,dapr sidecar之间的通信固定使用gRPC协议。而且dapr从数据面同步的数据量也非常少,所以也不会有类似istio场景下频繁reload xDS的问题。
但相比service mesh,dapr sidecar管控了更多的流量类型,比如状态存储,应用系统对这类流量的延迟变化更加敏感,用户在接入dapr之前需要慎重评估。
Dapr 开发工具支持
DotNet Core SDK
虽然Dapr独立于任何编程语言,但它可以轻松地集成到具有特定语言的SDK应用程序中。随着我们看到越来越多的开发人员使用Dapr,我们对这些SDK的投资已增加,以帮助简化Dapr集成。Dapr为Java、Python、PHP、.NET、Go以及最近添加的Rust和C++提供SDK。此外,根据社区反馈,Java SDK中增加了许多改进,包括虚拟角色、键入类和与Spring Boot Web框架的集成。对于.NET,还改进了类型类别,并与ASP.NET核心Web框架集成。作为未来路线图的一部分,计划为其他SDK提供类似的支持。
- Dapr SDK for .NET
- dapr.io by Nuget
Visual Studio Code扩展
能够在没有任何云依赖的情况下开发本地机器上的应用程序,这对生产力和成本非常重要,也是Dapr的关键目标。Dapr视觉工作室代码(VS代码)预览扩展可帮助开发人员使用Dapr调试应用程序,与Dapr运行时间进行交互,并与DaprCLI合并。
- Dapr for Visual Studio Code
Dapr 入门教程(阿里知行动手实验室)
十分钟快速领略开源分布式运行时Dapr应用的开发、部署过程