.Net Core微服务架构
一、前言
大家一直都在谈论微服务架构,园子里面也有很多关于微服务的文章,前几天也有一些园子的朋友问我微服务架构的一些技术,我这里就整理了微服务架构的技术栈路线图,这里就分享出来和大家一起探讨学习,同时让新手对微服务相关技术有一个更深入的了解。
二、技术栈
2.1 工欲善其事,必先利其器
现在互联网盛行的年代,互联网产品也层出不穷,受欢迎的互联网产品都有一个比较牛的技术团队,我这里分享下.net 微服务架构技术栈图如下:
俗话说得好:工欲善其事,必先利其器。一个优秀的工程师应该善于使用框架和工具,在微服务这一块的技术选型并非一蹴而就,需要经过日积月累和落地的项目才能完善。下文我会一一分享技术栈中的主要框架和工具的使用场景,这篇文章就不一一分享实战例子。
2.2 微服务
微服务如何“微”?
微服务,当然核心是主题是“微”,怎么微呢?应该如何微呢?在我刚来杭州的时候接触过一个电商系统的单体架构
,系统比较庞大,结合了各种电商该拥有的业务逻辑和场景, 代码也比较难于维护,前前后后接手的人也比较多,代码耦合度太高,改个业务逻辑基本上是牵一发而动全身,跟我上个月分享的关于Asp.Net Core 中IdentityServer4 授权中心之应用实战的文章中的电商系统最初的架构图类似,如下:
那针对这个架构就有可“微”之谈了。
这里针对该单体架构
可以做如下几个原则上进行“微”服务:
- 根据业务来进行拆分,一个业务一个服务原则进行拆分,做到通用性业务服务模块,这样业务之间可以做到高内聚低耦合。后面随意改动哪一块业务,只需要去改动这一块的业务微服务即可,其他业务不会受到影响。
- 一个业务模块一个独立的数据库为原则,相互平行的业务做到不需要相互依赖调用。
- 外层API网关进行业务逻辑的整合。
- 一个业务数据库一个微服务为原则。
- 结合分布式服务,可以快速版本迭代,发布平滑发布,不受时间影响,每时每刻都可以发布,无需半夜等到12点进行发布。(比较痛苦的发布,犹如三日凌空般(有点夸张),曾经有段时间是每周都有那么几个晚上痛苦的发布,一发布就可能是凌晨4,5点,很多时候发布完,还要经过各种测试,最后发现问题还得线上改bug,我们回去的时候别的同事已经来上班了;当时我们的技术大佬说过这么一句话:“他连续一周都没看到过他的儿子,回去的时候,他儿子早就睡着了,起来上班的时候,他儿子已经去学校了”,大家一定也有过这样的发布经历。)
按照上面的原则后,原来的电商单体架构微服务后架构图如下:
架构图粗略的画了下,能够表明意思即可,微服务、Docker
、k8s
那一块简要的概括,没有详细画出具体的图。
微服务集群
微服务已经“微”好了,那需要一个服务发现的数据中心,这里就该用到Consul
了,Consul
主要用来注册服务,及服务发现,以及服务的健康检查,我们可以根据需要针对某些业务服务进行自动扩容,添加服务器及扩张服务集群,一台服务挂了,Consul会自动选择可用的服务节点进行连接使用,这样整体电商系统稳定性大大增大。需要了解Consul
更加详细的特性和搭建,可以点击5分钟看懂微服务架构下的Consul 特性及搭建 一文阅读。
微服务如何保证数据的一致性
以前单体架构应用,对于业务之间的耦合是通过事务保证数据的一致性的,那对于微服务而言怎么做到数据的一致性呢?上门也说了,微服务应该做到业务之间没有依赖关系,每一个业务都是独立的一个服务,那这样怎么保证业务与之间的数据的一致性也存在很大的一个问题,也是业界对微服务争议比较大的一个话题,那到底该如何保证数据的一致性?
在分布式系统架构中有一个CAP理论
:任何分布式系统只可同时满足一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)中的两点,没法三者兼顾。对于分布式系统来说,分区容错性是基本要求,否则就失去了价值。因此,就只能在可用性和一致性之间做出选择。如果选择提供一致性需要付出在满足一致性之前阻塞其他并发访问的代价。这可能持续一个不确定的时间,尤其是在系统已经表现出高延迟时或者网络故障导致失去连接时。依据目前的成功经验,可用性一般是更好的选择,但是在服务和数据库之间维护数据一致性是非常根本的需求,微服务架构中选择满足最终一致性。
最终一致性是指系统中的所有数据副本经过一段时间后,最终能够达到一致的状态。这里所说的一段时间,也要是用户可接受范围内的一段时间。
从一致性的本质来看,就是在一个业务逻辑中包含的所有服务要么都成功,要么都失败。那我们又该如何选择方向,来保证成功还是保证失败呢?就是就需要根据业务模式做出选择。实现最终一致性有三种模式:可靠事件模式、业务补偿模式、TCC模式,这里就不再延伸,后面有机会再来分享学习。
2.3 微服务开源框架
我这里微服务架构使用的是开源微服务框架 core-grpc
开源框架源代码地址:https://github.com/overtly/core-grpc 前面我分享过一篇关于微服务架构应用实战中简单描述了微服务的基本概念和利弊,这里就不再分享,具体应用也可以点击【.net core】电商平台升级之微服务架构应用实战(core-grpc) 阅读
2.4 ORM框架
微服务中使用的ORM Dapper ,而使用的的第三方开源组件是core-data
,开源作者对dapper 进行了一次封装,开源框架源代码地址:https://github.com/overtly/core-data
core-data
主要优势:
- 官方建议使用DDD 领域驱动设计思想开发
- 支持多种数据库,简单配置添加链接的配置即可
- 多数据库的支持
- 支持分表操作,自定义分表策略的支持
- 支持表达式方式编写,减少写Sql语句机械性工作
- 可对Dapper 进行扩展
- 性能依赖于Dapper 本身的性能,Dapper 本身是轻量级ORM ,官方测试性能都强于其他的ORM
2.5 分布式跟踪系统
随着微服务架构的流行,一些微服务架构下的问题也会越来越突出,比如一个请求会涉及多个服务,而服务本身可能也会依赖其他服务,整个请求路径就构成了一个网状的调用链,而在整个调用链中一旦某个节点发生异常,整个调用链的稳定性就会受到影响,所以会深深的感受到 “银弹” 这个词是不存在的,每种架构都有其优缺点 。
对以上情况, 我们就需要一些可以帮助理解系统行为、用于分析性能问题的工具,以便发生故障的时候,能够快速定位和解决问题,这时候 APM(应用性能管理)工具就该闪亮登场了。目前主要的一些 APM 工具有: Cat、Zipkin、Pinpoint、SkyWalking,这里主要介绍 SkyWalking ,它是一款优秀的国产 APM 工具,包括了分布式追踪、性能指标分析、应用和服务依赖分析等。
2.6 系统日志集成
庞大的系统中离不开日志系统,排查问题,记录相关敏感信息等都需要一个日志系统,这里选择使用ExceptionLess 日志系统,日志写入到ES中,并支持可视化UI进行日志管理,查询,平常遇到问题,直接通过日志管理后台进行排查。
2.7 消息队列
消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题。实现高性能、高可用、可伸缩和最终一致性架构。使用较多的消息队列有ActiveMQ、RabbitMQ、ZeroMQ、Kafka、MetaMQ、RocketMQ。
2.8 任务调度
这里主要使用的是Quartz.Net 进行作业任务调度,任务调用有什么用处呢?,比如我们需要统计一个数据,但是实时统计需要一大堆的连表查询,并且比较损耗数据库的性能,因此可以选择使用任务调度的方案进行数据统计作业,半夜某个时间点去统计前一天的数据。
2.9 NoSql
Nosql 主要是非关系型数据库,比如MongDB、 Redis、Memcache等,可以用来在API网关和数据库层面做一层数据缓存,访问一些不是经常更新的数据,把它缓存起来,每次网络请求过来就可以先通过从分布式缓存中进行数据读取,减少对数据库的查询压力,提高系统的吞吐量。
2.10 可视化数据管理及分析(Kibana)
Kibana 是为 Elasticsearch设计的开源分析和可视化平台。你可以使用 Kibana 来搜索,查看存储在 Elasticsearch 索引中的数据并与之交互。你可以很容易实现高级的数据分析和可视化,以图标的形式展现出来。Kibana 的使用场景,应该集中在两方面:
实时监控 通过 histogram 面板,配合不同条件的多个 queries 可以对一个事件走很多个维度组合出不同的时间序列走势。时间序列数据是最常见的监控报警了。问题分析
关于 elk 的用途,可以参照其对应的商业产品 splunk 的场景:使用 Splunk 的意义在于使信息收集和处理智能化。而其操作智能化表现在:搜索,通过下钻数据排查问题,通过分析根本原因来解决问题;实时可见性,可以将对系统的检测和警报结合在一起,便于跟踪 SLA 和性能问题;历史分析,可以从中找出趋势和历史模式,行为基线和阈值,生成一致性报告。
2.11 Prometheus
Prometheus是一套开源的系统监控报警框架。Prometheus作为新一代的云原生监控系统,相比传统监控监控系统(Nagios或者Zabbix)拥有如下优点。
优势
- 易于管理
- 轻易获取服务内部状态
- 高效灵活的查询语句
- 支持本地和远程存储
- 采用http协议,默认pull模式拉取数据,也可以通过中间网关push数据
- 支持自动发现
- 可扩展
- 易集成
好了到了这里,大多已经介绍完了,其他几个大家都是比较常见常使用的技术,就不一一介绍了。
2.12 .Net Core 虚拟化
.Net Core 新一代的.Net Core 跨平台开发框架,可以脱离windows 环境,搭建在linux等平台上,那怎样搭建呢?当然可以使用当前比较流行的Docker容器,把.net core 项目虚拟化 搭建在Docker 容器中运行,不依赖于任何平台和环境,只需要通过命令制作好镜像即可,同时也可以借助K8s
来进行多容器应用部署、编排、更新等。
什么是k8s呢?
Kubernetes是一个开源的,用于管理云平台中多个主机上的容器化的应用,Kubernetes的目标是让部署容器化的应用简单并且高效(powerful),Kubernetes提供了应用部署,规划,更新,维护的一种机制。
Kubernetes一个核心的特点就是能够自主的管理容器来保证云平台中的容器按照用户的期望状态运行着(比如用户想让apache一直运行,用户不需要关心怎么去做,Kubernetes会自动去监控,然后去重启,新建,总之,让apache一直提供服务),管理员可以加载一个微型服务,让规划器来找到合适的位置,同时,Kubernetes也系统提升工具以及人性化方面,让用户能够方便的部署自己的应用(就像canary deployments)。
现在Kubernetes着重于不间断的服务状态(比如web服务器或者缓存服务器)和原生云平台应用(Nosql),在不久的将来会支持各种生产云平台中的各种服务,例如,分批,工作流,以及传统数据库。
在Kubenetes中,所有的容器均在Pod中运行,一个Pod可以承载一个或者多个相关的容器,在后边的案例中,同一个Pod中的容器会部署在同一个物理机器上并且能够共享资源。一个Pod也可以包含O个或者多个磁盘卷组(volumes),这些卷组将会以目录的形式提供给一个容器,或者被所有Pod中的容器共享,对于用户创建的每个Pod,系统会自动选择那个健康并且有足够容量的机器,然后创建类似容器的容器,当容器创建失败的时候,容器会被node agent自动的重启,这个node agent叫kubelet,但是,如果是Pod失败或者机器,它不会自动的转移并且启动,除非用户定义了 replication controller。
用户可以自己创建并管理Pod,Kubernetes将这些操作简化为两个操作:基于相同的Pod配置文件部署多个Pod复制品;创建可替代的Pod当一个Pod挂了或者机器挂了的时候。而Kubernetes API中负责来重新启动,迁移等行为的部分叫做“replication controller”,它根据一个模板生成了一个Pod,然后系统就根据用户的需求创建了许多冗余,这些冗余的Pod组成了一个整个应用,或者服务,或者服务中的一层。一旦一个Pod被创建,系统就会不停的监控Pod的健康情况以及Pod所在主机的健康情况,如果这个Pod因为软件原因挂掉了或者所在的机器挂掉了,replication controller 会自动在一个健康的机器上创建一个一摸一样的Pod,来维持原来的Pod冗余状态不变,一个应用的多个Pod可以共享一个机器。
我们经常需要选中一组Pod,例如,我们要限制一组Pod的某些操作,或者查询某组Pod的状态,作为Kubernetes的基本机制,用户可以给Kubernetes Api中的任何对象贴上一组 key:value的标签,然后,我们就可以通过标签来选择一组相关的Kubernetes Api 对象,然后去执行一些特定的操作,每个资源额外拥有一组(很多) keys 和 values,然后外部的工具可以使用这些keys和vlues值进行对象的检索,这些Map叫做annotations(注释)。
Kubernetes支持一种特殊的网络模型,Kubernetes创建了一个地址空间,并且不动态的分配端口,它可以允许用户选择任何想使用的端口,为了实现这个功能,它为每个Pod分配IP地址。
现代互联网应用一般都会包含多层服务构成,比如web前台空间与用来存储键值对的内存服务器以及对应的存储服务,为了更好的服务于这样的架构,Kubernetes提供了服务的抽象,并提供了固定的IP地址和DNS名称,而这些与一系列Pod进行动态关联,这些都通过之前提到的标签进行关联,所以我们可以关联任何我们想关联的Pod,当一个Pod中的容器访问这个地址的时候,这个请求会被转发到本地代理(kube proxy),每台机器上均有一个本地代理,然后被转发到相应的后端容器。Kubernetes通过一种轮训机制选择相应的后端容器,这些动态的Pod被替换的时候,Kube proxy时刻追踪着,所以,服务的 IP地址(dns名称),从来不变。
所有Kubernetes中的资源,比如Pod,都通过一个叫URI的东西来区分,这个URI有一个UID,URI的重要组成部分是:对象的类型(比如pod),对象的名字,对象的命名空间,对于特殊的对象类型,在同一个命名空间内,所有的名字都是不同的,在对象只提供名称,不提供命名空间的情况下,这种情况是假定是默认的命名空间。UID是时间和空间上的唯一。
2.13 自动化集成部署
为什么需要自动化集成部署?
我从以下几点来分析为什么需要自动化集成部署:
- 你要相信的是所有的人工部署、发布、更新都是不可靠的,自动化智能部署可以减少事故率。
- 人为备份、发布更新都是效率非常低的。
- 如果某个项目需要更新,但是这个微服务有十几台负载,那你人为一台一台服务器更新发布是不是很繁琐,更加容易出事故呢?
什么是自动化集成部署?
通过jenkins
、gitlab
、docker
等工具,以及依赖事先写好的脚本监听代码提交动态、自动化构造项目镜像、推送镜像到镜像仓库、Docker 拉起镜像、启动项目等系列自动化脚本处理,可以平滑的一台一台服务停止并且更新;一切操作无需人为的干预,甚至出现问题可以一键回滚操作。
自动化集成部署有哪些优势
- 一切自动化,无需人为干预,提高效率,专业的人做专业的事情,开发做好开发的事情即可,运维做好运维的事情。
- 发布可追溯
- 随时人为干预回滚(通过脚本回顾上一步自动化备份的项目镜像)
- 平滑发布,不影响用户体验,一台一台服务器切断,发布更新。