通过Dapr实现一个简单的基于.net的微服务电商系统

1周前

本来想在Dpar 1.0GA时发布这篇文章,由于其他事情耽搁了放到现在。时下微服务和云原生技术如何如荼,微软也不甘示弱的和阿里一起适时推出了Dapr(https://dapr.io/),园子里关于dapr的文章不太多,所以今天就借这篇文章分享一下如何通过dapr跑起来一个简易的电商系统,让大家通过这个系统来观察dapr如何运作的,权当抛砖引玉。

首先第一个话题,什么是dapr?

要说dapr,首先我们得了解什么是服务网格,而要说服务网格还是得先讲讲微服务。微服务的概念相信现在大家已经耳熟能详了。在微服务体系中,开发者通过拆分设计不同的服务,通过服务间协同作业的方式聚合业务实现相关的功能。

服务与服务之间涉及服务调用、事件传播、状态流转等等概念,再细分相关功能会涉及到服务调用时限流、重试、降级,事件传播时确保一致性,对整个服务系统的拓扑追踪、监控等等功能。以java为例,一般是采用dubbo或者springcloud这样的侵入性框架,通过开发人员手动集成到项目里。并在外部搭建诸如zookeeper、eureka这样的分布式协调器来实现服务的注册、发现。通过网关如zuul、kong实现对外部对内部服务的调用,通过feign ribbon hystrix这样或那样的组件实现服务间通讯时负载均衡、熔断、限流、降级。通过设计eventbus来实现事件在服务间流转。

说了这么一大堆,和服务网格有什么关系呢?服务网格本质上就是微服务架构在云原生基础上对网络通讯相关的功能做了解耦和下沉。让运行于云原生之上的应用(一般呈现方式主要是容器)可以不再关心服务间通讯相关的一大堆技术实现。通过对每一个应用附加一个独立进程的代理(也叫sidecar)实现。

这样开发人员只关心应用如何实现具体的业务,而不用去关心具体的服务间治理,服务网格帮应用完成服务注册、发现、负载均衡、重试、限流等等相关功能。

所以dapr是什么就一目了然了,dapr就是服务网格的一种实现方式。只不过相比传统的服务网格关注的可能是流量治理来讲dapr更关注服务间状态变化,用官方的原话来讲“Dapr 是一个可移植的、事件驱动的运行时,它使任何开发人员能够轻松构建出弹性的、无状态和有状态的应用程序,并可运行在云平台或边缘计算中,它同时也支持多种编程语言和开发框架。”

什么是事件驱动?

什么是事件驱动?我们知道在一个分布式系统里,当某个服务需要其他服务协同作业时,有两种办法,一种就是通过强一致性的方式调用,比如RPC call或者restapi。这种方式有一个弊端,就是必须确保下游服务必须可用,否则可能会导致同步调用时调用过程超时、或者下游服务不响应导致的失败。

在分布式系统里由于网络IO不可靠等等因素,我们往往很难100%确保同步调用能够将一个业务在多个服务间协同完成,所以一般会采用订阅-发布的方式。也就是大家比较熟悉的事件总线这样的异步模型,通过将我们的请求以发布事件的方式灌入消息队列后不等待立即返回,通过订阅方订阅消息完成后续操作,若需要回滚同样发布事件,由发送方订阅失败消息进行补偿操作即可。

通过这样的方式我们可以构建一个以事件来驱动的异步的,最终一致性的响应式系统。而dapr则是将事件驱动在云原生层面发扬光大,通过对不同中间件的集成屏蔽了构建事件驱动架构的各种复杂性,让开发人员几乎不写或者少写代码的情况下完成一个事件驱动架构的分布式系统。

Dapr如何助力微服务设计落地的?

Dapr提供了哪些功能来助力我们微服务落地呢?可以从上图看到有7,8种功能,我们从左至右一个一个来讲。

第一个就是服务间调用,也就是常见的同步调用。dapr在服务间调用封装了服务注册发现、ssl、自动重试、熔断、限流(需单独配置支撑)。一般当应用请求下游服务时daprd会发起一个https请求、若超时会重试数次,若下游服务下线不可用则会返回一个友好的json格式的50x供上游服务做异常冗余处理。

第二个是状态管理,提供了对于存储键/值对的状态管理,同时对大部分主流的状态存储中间件进行了支持,而无需开发者去对特定中间件做相应的sdk集成。

第三个是订阅发布,通过该api可以轻松实现一个异构的语言无关的事件总线,同时和状态管理一样,它的订阅发布中间件是可插拔的。

第四个是资源绑定,带触发器的资源绑定通过接收和发送事件到任何外部源(如数据库、队列、文件系统等)来进一步构建事件驱动架构,以实现扩展性和弹性,此特性有一点Serverless的思想。

第五个是大名鼎鼎的actor模型,很多没接触过actor的同学可能会一头雾水的问actor模型是什么呢?简单来讲就是一个分布式的并发的无锁线程同步对象。举一个简单的例子它可以解决在并发下商品超卖的问题,假设我们有一个api可以通过访问来扣商品库存,在无锁无事务的情况下,由于读写数据库时间差的问题,一定会导致商品超卖,即便是我们将商品库存放在对象上通过内存保存,如果不引入原子操作,也一样会有线程同步导致的数据不一致问题,而actor则可以在不引入任何内部或外部api/sdk的情况下实现多线程访问下数据的完全一致性。简单来讲就是每一个actor是通过对mailbox队列来阻塞消费实现多线程访问下数据一致性的,具体大家可以多了解一下这个模型。而Dapr 在其 actor 运行时提供了很多能力,包括并发,状态管理,用于 actor 激活/停用的生命周期管理,以及唤醒 actor 的计时器和提醒器。

第六个是可观测性,dapr通过一些三方组件提供了诸如链路追踪和应用监控等等相关观测手段来方便开发人员更加直观的定位网络问题等等。

第七个是安全性,默认dapr之间调用不管是同步调用还是actor调用或者订阅发布,均会通过双向https的方式加密通讯,避免明文传递消息。

最后一个是丰富的扩展组件,熟悉dapr的开发者可以自定义各种组件通过中间件的方式插入到sidecar的pipline中去实现自定义功能的扩展。

另外需要关注的是dapr对上层应用提供了两种请求模式,一种是http api一种是grpc api,通过这种语言无关的方式我们可以轻易的在不同语言之间通过dapr搭建起一个异构的分布式系统而不用关心不同语言之间的差异。

Dapr与其他服务网格的区别

目前市场上的服务网格框架基本都被诸如linkerd、istio这样的老牌服务网格占据。这些服务网格的关注点和dapr有一定区别。如果非要说相同或者相似点的话那就是他们的架构都是由数据平面和控制平面组成,其中数据平面主要是由集群内的各种sidecar组成,而控制平面就是调度中心。另外一个相同点就是他们都实现了对应用程序的代码无侵入性(dapr提供了简单的sdk,只是对dapr api的简易封装,不是必选项),但是从功能层面来讲,两者的关注点则完全不一样了,例如service mesh霸主istio他提供了对流量切分、流量镜像、监控、智能路由、故障注入,自动化的度量指标、日志以及追踪等等功能,可以看到它更关心流量代理这部分逻辑。而dapr在这部分目前来讲还稍弱,但是dapr提供了其他服务网格几乎没有关心的状态服务、事件、actor模型等等功能,两者可以说是互补的(dapr是可以和istio这样的服务网格集成工作的,未来可期)

更多了解dapr

访问https://dapr.io了解更多

talk is cheap, show me the code!

说了那么多概念,最终还是需要落地到具体的系统设计上,我们就从这个电商系统开始吧。

技术概要、设计规范

整个电商系统分为两个具体的repo

https://github.com/sd797994/Oxygen-Dapr

该repo是针对Dapr通讯相关的API统一了编程模型封装实现的一个rpc框架,类似于dapr提供的.net SDK,该repo我已经将打包到了nuget,所以电商系统不需要依赖该repo的源代码,有兴趣的朋友可以copy下来,可以的话请star一下。该框架基于.net5

https://github.com/sd797994/Oxygen-Dapr.EshopSample

该repo为本次演示电商系统源代码,其结构如下:

Depoly主要是一些yaml和bat方便读者朋友通过k8s快速启动之用。

Public包含一些领域业务的抽象(DomainBase)和工具层(InfrastructureBase)以及RPC的接口层(Remote-IApplicationService)以及前端(WebPage-www)及后端(WebPage-Admin)页面

Services文件夹包含后端的微服务,分为账户服务、商品服务、商城公共服务以及交易服务,另外包含两个通用支撑服务:图片服务以及作业服务。

业务微服务类主要是以清洁架构分层,清洁架构是领域驱动设计的一个概念:

整个代码结构是以Domain为核心,外部依次是应用层、基础设施,是一个从内及外的设计。

Domain包含了整个服务所需的具体的业务聚合,Domain的核心数聚合,包含聚合根、实体以及值对象,剩余部分则是围绕聚合形成的规约、DTO、领域服务、仓储抽象等等。
应用层通过读写分离的方式来实现对领域层的操作和对查询业务的操作,另外包含事件订阅器用于接收其他应用发起的领域事件。其结构如下:

用例(UseCaseService)类型的应用服务主要作用就是聚合操作当前服务的领域,同时调用基础设施层实现持久化以及事务,同时可以发送领域事件亦或是调用远程RPC。

查询(QueryService)类型的应用服务主要是对各种客户端发起的业务指令调用基础设施层的ORM或es或者dapr的statemanager或者远程RPC进行具体的操作查询。

事件订阅器(EventHandler)类型的应用服务主要是接收事件并进行业务操作,其操作逻辑和用例类似。

基础设施层则包含了对上层的一系列支撑,包括各种通用组件、工具、ORM、持久化实体、仓储实现等等,其中用到的外部持久化设备有postgres、elasticsearch以及redis,所有的业务表存储主要依赖于postgres,elasticsearch主要是包含移动端的商品列表查询,redis则主要是对dapr以及作业系统的持久化支撑。此处不再赘述。

Host作为服务启动的入口,主要是启动通用主机注入依赖注入框架,注入Oxygen框架初始化各种配置、AOP、鉴权服务等等来启动RPC服务。不再赘述

部署网络拓扑图

Tips:如何部署可以参考repo的readme.md

整个系统前、后端以及各种通用服务都是以容器化的方式运行在k8s之上的。其中在最前端是k8s的ingress-controller,由于这个场景相对比较简单,不需要各种复杂的流量切分逻辑,所以我目前选型的是k8s官方推荐的nginx。当请求从客户端发起的时候,流量最先流入ingress-controller,通过已配置好的ingress规则会再次发送到各个k8s service再流入具体的pod进行作业。

其中对www.dapreshop.com的访问会直接请求到后端页面pod、对m.dapreshop.com的访问会请求到移动端页面pod,这两个页面上发起的api.dapreshop.com则会统一先流入到一个叫apigateway的pod上,该pod附加了一个dapr的sidecar,该pod只是一个包含路由重写规则的nginx,它的作用是将源地址请求重新组装为dapr可识别的api地址并调用sidecar,这样通过sidecar和内网的其他挂载了sidecar的各种子服务进行相应的互操作。

如何运行它?

Tips:如何运行可以参考repo的readme.md

通过kubectl查询两个命名空间看到如下情况则代码系统已经完全启动完毕。

这个时候访问admin.dapreshop.com会进入该页面:

当初始化后会自动创建一个管理员、一个用于模拟下单的用户以及相关的权限、角色。

同时会自动创建商城的基础设置、商品分类、商品,以及随机创建几个商品的折扣活动。

此时访问m.dapreshop.com就能看到一个包含商品的下单页面了

随意选择几个商品,选择结算后,后端即可创建一个订单,后台就可以模拟订单的剩余流程、注意该订单会在5分钟内被作业系统取消,库存会回滚。

观察

当我们在前后端做了各种操作后,登录zipkin.dapreshop.com即可观察到流量的变化

可以观察到相应的请求和链路细节

以前端下订单为例:

流量通过网关路由到交易服务,交易服务会调用账户服务获取一个mock account、然后会调用商品RPC查询商品基础信息,接下来调用商品Actor对象做具体的库存减扣,最后发布事件,同时交易服务的交易记录订阅器和作业订阅器会订阅该事件做后续相关操作。由于所有的请求都通过daprd这个sidecar完成,所以所有的请求和流量都能通过zipkin直观的观察到。

最后我们可以在daprcli中通过dapr dashborad -k 来观测dapr目前控制平面的基本情况,此部分就不赘述了,大家可以登录dapr的官网多了解

 

以上就是本次关于dapr如何落地一个电商demo的入门级的相关分享,Dapr本身包含了太多的内容由于时间关系无法一一呈现还需要读者朋友们在实际使用中去挖掘,如果喜欢的话请给repo一个star,欢迎转发fork~

(0)

相关推荐