赞
踩
1.微服务监控系统
在微服务架构中,监控系统按照原理和作用大致可以分为三类:
日志监控(Log)
调用链调用监控(Tracing)
度量监控(Metrics)
1.1 日志监控
日志类比较常见,我们的框架代码、系统环境、以及业务逻辑中一般都会产出一些日志,这些日志我们通常把它记录后统一收集起来,方便在跟踪和丁文问题的需要时进行查询;
日志类记录的信息一般是一些事件、非结构化的一些文本内容;
日志的输出和处理的解决方案常用的有 ELK Stack 方案,用于实时搜索,分析和可视化日志数据;
开源实时日志分析 ELK 平台能够完美的解决我们上述的问题,ELK 由ElasticSearch、Logstash和Kiabana
三个开源工具组成。
组件介绍
Elasticsearch 是个开源分布式搜索引擎,它具备分布式、零配置、自动发现、索引自动分片、索引副本机制、RESTful
风格接口、多数据源、自动搜索负载等特性;
Logstash 是一个完全开源的工具,它可以对你的日志进行收集、过滤,并将其存储供以后使用(如搜索);
Kibana 也是一个开源和免费的工具,可以为 Logstash 和 ElasticSearch 提供的日志分析友好的 Web
界面,可以帮助您汇总、分析和搜索重要数据日志;
Kafka 用来接收用户日志的消息队列。
工作流程图
Logstash 收集 AppServer 产生的日志记录 Log;
将日志 log 存放到 ElasticSearch 集群中,而 Kibana 则从 ES 集群中查询数据生成图表;
生成的日志图表返回给 Browser 进行渲染显示,分别支持各种终端进行显示。
1.2 调用链监控
调用链监控是用来追踪微服务之前依赖的路径和问题定位;
主要原理就是子节点会记录父节点的 id 信息。
一个请求从开始进入,在微服务中调用不同的服务节点后,再返回给客户端,在这个过程中通过调用链参数来追寻全链路的调用行程。通过这个方式可以很方便的知道请求在哪个环节出了故障,系统的瓶颈出现在哪一个环节,定位出优化点。
1.2.1 调用链监控的作用
生成项目网络拓扑图
快速定位问题
优化系统
1.2.2 调用链监控的原理
主要原理就是子节点会记录父节点的 id 信息,要理解好三个核心的概念 Trace、Span 和 Annotation。
Trace
Trace 是指一次请求调用的链路过程,trace id 是指这次请求调用的 ID;
在一次请求中,会在网络的最开始生成一个全局唯一的用于标识此次请求的 trace id。这个 trace id 在这次请求调用过程中无论经过多少个节点都会保持不变,并且在随着每一层的调用不停的传递;
最终,可以通过 trace id 将这一次用户请求在系统中的路径全部串起来。
Span
Span 是指一个模块的调用过程,一般用 span id 来标识。在一次请求的过程中会调用不同的节点、模块、服务,每一次调用都会生成一个新的 span id 来记录;
这样就可以通过 span id 来定位当前请求在整个系统调用链中所处的位置,以及它的上下游节点分别是什么。
Annotation
指附属信息,可以用于附属在每一个 Span 上自定义的数据。
具体流程:
1.2.3 调用链监控开源应用
CAT
CAT 是由大众点评开源的一款调用链监控系统,基于JAVA开发,有很多互联网企业在使用,热度非常高;
它有一个非常强大和丰富的可视化报表界面,这一点其实对于一款调用链监控系统而来非常的重要;
在 CAT 提供的报表界面中有非常多的功能,几乎能看到你想要的任何维度的报表数据;
CAT 有个很大的优势就是处理的实时性,CAT 里大部分系统是分钟级统计。
Open Zipkin
Zipkin 由 Twitter 开源,支持的语言非常多。它基于 Google Dapper 的论文设计而来,国内外很多公司都在用,文档资料也很丰富。
用于收集服务的定时数据,以解决微服务架构中的延迟问题,包括数据的收集、存储、查找和展现。
Pinpoint
Pinpoint 中的服务关系依赖图做得非常棒,超出市面上任何一款产品;
Pinpoint 运用 JavaAgent 字节码增强技术,只需要加启动参数即可。因为采用字节码增强方式去埋点,所以在埋点的时候是不需要修改业务代码的,是非侵入式的。非常适合项目已经完成之后再增加调用链监控的时候去使用的方案;
但是也是由于采用字节码增强的方式,所以它目前仅支持 Java 语言。
方案选型比较
1.3 微服务监控体系
监控是微服务治理的重要环节,架构采用分层监控,一般分为以下监控层次。如下图所示:
系统层
系统层主要是指 CPU、磁盘、内存、网络等服务器层面的监控,这些一般也是运维同学比较关注的对象。
应用层
应用层指的是服务角度的监控,比如接口、框架、某个服务的健康状态等,一般是服务开发或框架开发人员关注的对象。
用户层
这一层主要是与用户、与业务相关的一些监控,属于功能层面的,大多数是项目经理或产品经理会比较关注的对象。
监控指标
2. 微服务容错隔离
2.1 什么是容错隔离
单体应用的架构下一旦程序发生了故障,那么整个应用可能就没法使用了,所以我们要把单体应用拆分成具有多个服务的微服务架构,来减少故障的影响范围。
但是在微服务架构下,有一个新的问题就是,由于服务数变多了,假设单个服务的故障率是不变的,那么整体微服务系统的故障率其实是提高了的。
假设单个服务的故障率是 0.01%,也就是可用性是 99.99%,如果我们总共有 10 个微服务,那么我们整体的可用性就是 99.99% 的十次方,得到的就是 99.90% 的可用性(也就是故障率为 0.1%)。可见,相对于之前的单体应用,整个系统可能发生故障的风险大幅提升。
当某个服务出现故障,我们要做的就是最大限度的隔离单个服务的风险,也就是「 容错隔离 」的方法。不仅保证了微服务架构的正常运行,也保证了系统的可用性和健壮性。
2.2 常见的可用性风险有哪些
单机可用性风险
单机房可用性风险
这种风险的概率比单机器的要低很多,但是也不是完全不可能发生,在实际情况中,还是有一定概率的。比如最为常见的就是通往机房的光纤被挖断,造成机房提供不了服务;
如果我们的服务全部都部署在单个机房,而机房又出故障了,但是现在大多数中大型项目都会采用多机房部署的方案,比如同城双活、异地多活等;
一旦某个机房出现了故障不可用了,立即采用切换路由的方式,把这个机房的流量切到其它机房里就能正常提供服务了。
跨机房集群可用性风险
2.3 容错隔离的方案有哪些
超时
限流
降级
延迟异步处理
熔断
断路器其实就是一个状态机原理,有三种状态:
2.4 开源容错隔离应用
Hystrix 原理图
当我们使用了 Hystrix 之后,请求会被封装到 HystrixCommand 中,这也就是第一步;
第二步就是开始执行请求,Hystrix 支持同步执行(图中 .execute 方法)、异步执行(图中 .queue方法)和响应式执行(图中 .observer);
第三步判断缓存,如果存在与缓存中,则直接返回缓存结果;
如果不在缓存中,则走第四步,判断断路器的状态是否为开启,如果是开启状态,也就是短路了,那就进行失败返回,跳到第八步;
第八步需要对失败返回的处理也需要再做一次判断,要么正常失败返回,返回相应信息,要么根本没有实现失败返回的处理逻辑,就直接报错;
如果断路器不是开启状态,那请求就继续走,进行第五步,判断线程、队列是否满了。如果满了,那么同样跳到第八步;
如果线程没满,则走到第六步,执行远程调用逻辑,然后判断远程调用是否成功。调用发生异常了就挑到第八步,调用正常就挑到第九步正常返回信息。
图中的第七步,非常牛逼的一个模块,是来收集 Hystrix 流程中的各种信息来对系统做监控判断的。
Hystrix 断路器的原理图
Hystrix 通过滑动时间窗口算法来实现断路器的,是以秒为单位的滑桶式统计。它总共包含 10个桶,每秒钟一个生成一个新的桶,往前推移,旧的桶就废弃掉;
每一个桶中记录了所有服务调用的状态,调用次数、是否成功等信息,断路器的开关就是把这 10 个桶进行聚合计算后,来判断当前是应该开启还是闭合的。
3. 微服务的访问安全
3.1 什么是访问安全
访问安全就是要保证符合系统要求的请求才可以正常访问服务来响应数据,避免非正常服务对系统进行攻击和破坏;
微服务会进行服务的拆分,服务也会随业务分为内部服务和外部服务,同时需要保证哪些服务可以直接访问,哪些不可以;
总的来说,访问安全本质上就是要作为访问的认证。
3.2 传统单机服务的访问安全机制
传统单体应用的访问示意图
3.3 微服务如何实现访问安全
在微服务架构下,一般有以下三种方案:
3.3.1 网关鉴权模式(API Gateway)
3.3.3 API Token 模式(OAuth2.0)
如图,这是一种采用基于令牌 Token 的授权方式。在这个模式下,是由授权服务器(图中 Authorization Server)、API 网关(图中 API Gateway)、内部的微服务节点几个模块组成。
流程如下:
客户端应用首先使用账号密码或者其它身份信息去访问授权服务器(Authorization Server)获取 访问令牌(Access Token);
拿到访问令牌(Access Token)后带着它再去访问API网关(图中 API Gateway),API Gateway 自己是无法判断这个 Access Token 是否合法,所以走第 3 步;
API Gateway 去调用 Authorization Server 校验 Access Token 的合法性;
如果验证完 Access Token 是合法的,那 API Gateway 就将 Access Token 换成 JWT 令牌返回;
注意:此处也可以不换成 JWT,而是直接返回原 Access Token。但是换成 JWT 更好,因为 Access Token 是一串不可读无意义的字符串,每次验证 Access Token 是否合法都需要去访问 Authorization Server才知道。但是 JWT 令牌是一个包含 JSON 对象,有用户信息和其它数据的一个字符串,后面微服务节点拿到 JWT之后,自己就可以做校验,减少了交互次数。
API Gateway 有了JWT之后,就将请求向后端微服务节点进行转发,同时会带上这个 JWT;
微服务节点收到请求后,读取里面的 JWT,然后通过加密算法验证这个 JWT,验证通过后,就处理请求逻辑。
这里面就使用到了 OAuth2.0 的原理,不过这只是 OAuth2.0 各类模式中的一种。
3.4 OAuth2.0 的访问安全
3.4.1 什么是 OAuth2.0
OAuth2.0 是一种访问授权协议框架。它是基于 Token 令牌的授权方式,在不暴露用户密码的情况下,使应用方能够获取到用户数据的访问权限。
例如:你开发了一个视频网站,可以采用第三方微信登陆,那么只要用户在微信上对这个网站授权了,那这个网站就可以在无需用户密码的情况下获取用户在微信上的头像。
OAuth2.0 的流程如下图:
3.4.2 OAuth2.0 主要名词解释
3.4.3 OAuth2.0 有四种授权模式
授权码(Authorization Code)
授权码模式是指客户端应用先去申请一个授权码,然后再拿着这个授权码去获取令牌的模式。这也是目前最为常用的一种模式,安全性比较高,适用于我们常用的前后端分离项目。通过前端跳转的方式去访问授权服务器获取授权码,然后后端再用这个授权码访问授权服务器以获取访问令牌。
工作流程图
第一步,客户端的前端页面(图中 UserAgent)将用户跳转到授权服务器(Authorization Server)里进行授权,授权完成后,返回授权码(Authorization Code)。
第二步,客户端的后端服务(图中 Client)携带授权码(Authorization Code)去访问 授权服务器,然后获得正式的访问令牌(Access Token)。
面的前端和后端分别做不同的逻辑,前端接触不到 Access Token,保证了 Access Token 的安全性。
简化模式(Implicit)
工作流程图
第一步,应用(纯前端的应用)将用户跳转到授权服务器(Authorization Server)里进行授权。授权完成后,授权服务器直接将 Access Token 返回给 前端应用,令牌存储在前端页面。
第二步,应用(纯前端的应用)携带访问令牌(Access Token)去访问资源,获取资源。
在整个过程中,虽然令牌是在前端 URL 中直接传递,但令牌不是放在 HTTP 协议中 URL 参数字段中的,而是放在 URL 锚点里。因为锚点数据不会被浏览器发到服务器,因此有一定的安全保障。
用户名密码(Resource Owner Credentials)
这种方式最容易理解了,直接使用用户名、密码作为授权方式去访问授权服务器,从而获取 Access Token。
这个方式因为需要用户给出自己的密码,所以非常的不安全性。一般仅在客户端应用与授权服务器、资源服务器是归属统一公司、团队,互相非常信任的情况下采用。
客户端凭证(Client Credentials)
这是适用于服务器间通信的场景。客户端应用拿一个用户凭证去找授权服务器获取Access Token。
4. 容器技术
4.1 为什么需要容器技术
传统的 PaaS 技术虽然也可以一键将本地应用部署到云上,并且也是采用隔离环境的形式去部署,但是其兼容性非常的不好。
因为其主要原理就是将本地应用程序和启停脚本一同打包,然后上传到云服务器上,然后再在云服务器里通过脚本启动这个应用程序。
这样的做法,看起来很理想。但是在实际情况下,由于本地与云端的环境差异,导致上传到云端的应用运行的时候经常各种报错,需要各种修改配置和参数来做兼容服务环境。甚至在项目迭代过程中不同的版本代码都需要重新去做适配,非常耗费精力。
然而以Docker为代表的容器技术却通过一个小创新完美的解决了这个问题。
在 Docker 的方案中,它不仅打包了本地应用程序,而且还将本地环境(操作系统的一部分)也打包了,组成一个叫做「 Docker镜像 」的文件包。所以这个「 Docker镜像 」就包含了应用运行所需的全部依赖,我们可以直接基于这个「 Docker镜像 」在本地进行开发与测试,完成之后,再直接将这个「 Docker镜像 」一键上传到云端运行即可。
Docker 实现了本地与云端的环境完全一致,做到了真正的一次开发随处运行,避免了类似“我在本地正常运行,传到云端就不可以了”的说辞。
4.2 什么是容器
先来看一下容器与虚拟机的对比区别:
虚拟机是在宿主机上基于 Hypervisor 软件虚拟出一套操作系统所需的硬件设备,再在这些虚拟硬件上安装操作系统 Guest OS,然后不同的应用程序就可以运行在不同的 Guest OS 上,应用之间也就相互独立、资源隔离。但是由于需要 Hypervisor 来创建虚拟机,且每个虚拟机里需要完整的运行一套操作系统 Guest OS,因此这个方式会带来很多额外资源的开销。
Docker容器中却没有 Hypervisor 这一层,虽然它需要在宿主机中运行 Docker Engine,但它的原理却完全不同于 Hypervisor,它并没有虚拟出硬件设备,更没有独立部署全套的操作系统 Guest OS。
Docker容器没有那么复杂的实现原理,它其实就是一个普通进程而已,只不过它是一种经过特殊处理过的普通进程。我们启动容器的时候(docker run …),Docker Engine 只不过是启动了一个进程,这个进程就运行着我们容器里的应用。
但 Docker Engine 对这个进程做了一些特殊处理,通过这些特殊处理之后,这个进程所看到的外部环境就不再是宿主机的环境(它看不到宿主机中的其它进程了,以为自己是当前操作系统唯一一个进程),并且 Docker Engine 还对这个进程所使用得资源进行了限制,防止它对宿主机资源的无限使用。
对比下来就是,容器比虚拟机更加轻量级,花销也更小,更好地利用好主机的资源。
4.3 容器如何做到资源隔离和限制
Docker 容器对这个进程的隔离主要采用两个核心技术点 Namespace 和 Cgroups。
总结来说就是,Namespace 为容器进程开辟隔离进程,Cgroups 限制容器进程之间抢夺资源,从此保证了容器之间独立运行和隔离。
Namespace 技术
Namespace 是 Linux 操作系统默认提供的 API,包括 PID Namespace、Mount Namespace、IPC Namespace、Network Namespace 等。
以 PID Namespace 举例,它的功能是可以让我们在创建进程的时候,告诉Linux系统,我们要创建的进程需要一个新的独立的进程空间,并且这个进程在这个新的进程空间里的 PID=1。也就是说这个进程只看得到这个新进程空间里的东西,看不到外面宿主机环境里的东西,也看不到其它进程。
不过这只是一个虚拟空间,事实上这个进程在宿主机里 PID 该是啥还是啥,没有变化,只不过在这个进程空间里,该进程以为自己的 PID=1。
打个比方,就像是一个班级,每个人在这个班里都有一个编号。班里有 90 人,然后来了一位新同学,那他在班里的编号就是 91。可是老师为了给这位同学特别照顾,所以在班里开辟了一块独立的看不到外面的小隔间,并告诉这个同学他的编号是 1。由于这位同学在这个小空间里隔离着,所以他真的以为自己就是班上的第一位同学且编号为 1。当然了,事实上这位同学在班上的编号依然是 91。
Network Namespace 也是类似的技术原理,让这个进程只能看到当前 Namespace 空间里的网络设备,看不到宿主机真实情况。同理,其它 Mount、IPC 等 Namespace 也是这样。Namespace 技术其实就是修改了应用进程的视觉范围,但应用进程的本质却没有变化。
不过,Docker容器里虽然带有一部分操作系统(文件系统相关),但它并没有内核,因此多个容器之间是共用宿主机的操作系统内核的。这一点与虚拟机的原理是完全不一样的。
Cgroups 技术
Cgroup 全称是 Control Group,其功能就是限制进程组所使用的最大资源(这些资源可以是 CPU、内存、磁盘等等)。
既然 Namespace 技术 只能改变一下进程组的视觉范围,并不能真实的对资源做出限制。那么为了防止容器(进程)之间互相抢资源,甚至某个容器把宿主机资源全部用完导致其它容器也宕掉的情况发生。因此,必须采用 Cgroup 技术对容器的资源进行限制。
Cgroup 技术也是 Linux 默认提供的功能,在 Linux 系统的 /sys/fs/cgroup 下面有一些子目录 cpu、memory 等。Cgroup 技术提供的功能就是可以基于这些目录实现对这些资源进行限制。
例如,在 /sys/fs/cgroup/cpu 下面创建一个 dockerContainer 子目录,系统就会自动在这个新建的目录下面生成一些配置文件,这些配置文件就是用来控制资源使用量的。例如可以在这些配置文件里面设置某个进程ID对CPU的最大使用率。
Cgroup 对其它内存、磁盘等资源也是采用同样原理做限制。
4.4 什么是容器镜像
一个基础的容器镜像其实就是一个 rootfs,它包含操作系统的文件系统(文件和目录),但并不包含操作系统的内核。rootfs 是在容器里根目录上挂载的一个全新的文件系统,此文件系统与宿主机的文件系统无关,是一个完全独立的,用于给容器进行提供环境的文件系统。
对于一个Docker容器而言,需要基于 pivot_root 指令,将容器内的系统根目录切换到rootfs上。这样,有了这个 rootfs,容器就能够为进程构建出一个完整的文件系统,且实现了与宿主机的环境隔离。也正是有了rootfs,才能实现基于容器的本地应用与云端应用运行环境的一致。
另外,为了方便镜像的复用,Docker 在镜像中引入了层(Layer)的概念,可以将不同的镜像一层一层的迭在一起。这样,如果我们要做一个新的镜像,就可以基于之前已经做好的某个镜像的基础上继续做。
如上图,这个例子中最底层是操作系统引导。往上一层就是基础镜像层(Linux 的文件系统),再往上就是我们需要的各种应用镜像,Docker 会把这些镜像联合挂载在一个挂载点上,这些镜像层都是只读的。只有最上面的容器层是可读可写的。
这种分层的方案其实是基于联合文件系统 UnionFS(Union File System)的技术实现的。它可以将不同的目录全部挂载在同一个目录下。
举个例子,假如有文件夹 test1 和 test2 ,这两个文件夹里面有相同的文件,也有不同的文件。然后我们可以采用联合挂载的方式,将这两个文件夹挂载到 test3 上,那么 test3 目录里就有了 test1 和 test2 的所有文件(相同的文件有去重,不同的文件都保留)。
这个原理应用在 Docker 镜像中。比如有 2 个同学,同学 A 已经做好了一个基于 Linux 的 Java 环境的镜像,同学 S 想搭建一个 Java Web 环境,那么他就不必再去做 Java 环境的镜像了,可以直接基于同学 A 的镜像在上面增加 Tomcat 后生成新镜像即可。
4.5 容器技术在微服务的实践
随着微服务的流行,容器技术也相应的被大家重视起来。容器技术主要解决了以下两个问题。
环境一致性问题
例如 Java 的 jar/war 包部署会依赖于环境的问题(操着系统的版本,JDK 版本问题)。
镜像部署问题
例如 Java、Ruby、NodeJS 等等的发布系统是不一样的,每个环境都得很麻烦的部署一遍,采用 Docker 镜像,就屏蔽了这类问题。
部署实践
下图是 Docker 容器部署的一个完整过程:
更重要的是,拥有如此多服务的集群环境迁移、复制也非常轻松,只需选择好各服务对应的 Docker 服务镜像、配置好相互之间访问地址就能很快搭建出一份完全一样的新集群。
4.6 容器调度
目前基于容器的调度平台有 Kubernetes(K8S)、Mesos、Omega。
总结
本文主要介绍了微服务架构下的服务监控、容错隔离、访问安全以及结合容器技术实现服务发布和部署。初窥了微服务架构的模块,相信对微服务架构会有所帮助。再深入就需要有针对性的技术实践才能加深了解。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。