快速了解什么是:微服务
Posted 做一道光
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了快速了解什么是:微服务相关的知识,希望对你有一定的参考价值。
1. 微服务简介 随着互联网技术的飞速发展,目前全球超过半的人口在使用互联网,人们的生活随着互联网的发展,发生了翻天覆地的变化。各行各业都在应用互联网 国家政策也在大力支持互联网的发 展。随着越来越多的用户参与,业务场景越来越复杂,传统的单体架构己经很难满足互联网技 术的发展要求。这主要体现在两方面, 是随着业务复杂度的提高,代码的可维护性、扩展性 和可读性在降低; 是维护系统的成本、修改系统的成本在提高。所以,改变单体应用架构己 经势在必行。另外,随着云计算、大数据、人工智能的飞速发展,对系统架构也提出了越来越 高的要求。 微服务(不是一个框架 而是一种架构思想),是著名的 oo (面向对象, ObjectOriented )专家 Martin Fowler 提出来的,它是用来描述将软件应用程序设计为独立部署 的服务的种特殊方式。最近两年,微服务在各大技术会议、文章、书籍上出现的频率已经让人 意识到它对于软件领域所带来的影响力。 微服务架构的系统是个 分布式系统 ,按业务领域划分 为 独立的服务单元 ,有自动化运维、容错、快速演进的特点,它能够解决传统单体架构系统的 痛点,同时也能满足越来越复杂的业务需求 。 1.1 单体架构不足 在应用的初始阶段,单体架构无论是在开发速度、运维难度上,还是服务器的成本上都有着显著的优势。在一个产品的前景不明确的初始阶段,用单体架构是非常明智的选择。随着应 用业务的发展和业务复杂度的提高,这种架构明显存在很多的不足,主要体现在以下 3 个方面: 1. 业务越来越复杂,单体应用的代码量越来越大,代码的可读性、可维护性和可扩展性下降,新人接手代码所需的时间成倍增加,业务扩展带来的代价越来越大。 2. 随着用户越来越多,程序承受的并发越来越高,单体应用的并发能力有限。 3. 测试的难度越来越大,单体应用的业务都在同个程序中,随着业务的扩张、复杂度的增加,单体应用修改业务或者增加业务或许会给其他业务带来定的影响,导致测试难度增加。 1.2 到底什么是微服务 什么是微服务呢? 就是将一个大的应用,拆分成多个小的模块,每个模块都有自己的功能和职责,每个模块可以进行交互,这就是微服务 对于微服务,业界没有严格统一的定义,但是作为“微服务”这名词的发明人,Martin Fowler对微服务的定义似乎更具有权威性和指导意义,他的理解如下: 简而言之,微服务架构的风格,就是将单一程序开发成一个微服务,每个微服务运行在自己的进程中,并使用轻量级通信机制,通常是 HTTP RESTFUL API 。这些服务围绕业务能力来划分构建的,并通 过完全自动化部署机制来独立部署这些服务可以使用不同的编程语 言,以及不同数据存储技术,以保证最低限度的集中式管理。 1.2.1 总结出微服务的特点 1. 按业务(功能)划分为一个独立运行的程序,即服务单元。 2. 服务之间通过 HTTP 协议相互通信。 http 是一个万能的协议 (web 应用都支持的模式) 3. 自动化部署。 4. 可以用不同的编程语言。 5. 可以用不同的存储技术。 6. 服务集中化管理。 7. 微服务是一个分布式系统。 1.3 微服务特点的具体阐述 1.3.1 微服务单元按业务来划分(不是绝对的) 微服务的“微”到底需要定义到什么样的程度,这是个非常难以界定的概念,可以从以个方面来界定: 是根据代码量来定义,根据代码的多少来判断程序的大小: 是根据开发时间的长短 来判断: 是根据业务的大小来划分。根据 Martin Fowler 的定义,微服务的“微”是按照 业务来划分的 。一个大的业务可以拆分成若干小的业务, 个小的业务又可以拆分成若干更小 的业务,业务到底怎么拆分才算合适,这需要开发人员自己去决定。例如微博最常见的功能是微博内容、关注和粉丝,而其中微博内容又有点赞、评论等,如何将微博这个复杂的程序划分 为单个的服务,需要由开发团队去决定。按业务划分的微服务单元独立部署,运行在独立的 程中 这些微服务单元是高度组件化的模块,并提供了稳定的模块边界,服务与服务之间没有 任何的相合 有非常好的扩展性和复用性。传统的软件开发模式通常由 UI 团队、服务端团队、 数据库和运维团队构成,相应地将软件按照职能划分为 、服务端、数据库和运维等模块。通 常这些开发 员各司其职 很少有人跨职能去工作。 如果按照业务来划分服务,每个服务都需 要独立的 UI 、服务端、数据库和运维。也就是说, 个小的业务的微服务需要动用 个团队的 人去协作,这显然增加了团队与团队之间交流协作的成本。所以产生了跨职能团 队,这个团 队负责一个服务的所有工作,包括 UI 、服务端和数据库。当这个团队只有 个人的时候,就 对开发人员提出了更高的要求。 1.3.2 微服务通过 HTTP 来互相通信 按照业务划分的微服务单元独立部署 并运行在各自的进程中。微服务单元之间的通信方般倾向于使用 HTTP 这种简单的通信机制,更多的时候是使用 RESTfulAPI 。这种接受请求、处 理业务逻辑、返回数据的 HTTP 模式非常高效,并且这种通机制与平台和语言无关。例如用 Java 写的服务可以消费用 Go 语言写的服务。 1.3.3 微服务的数据库独立 在单体架构中,所有的业务都共用个数据库。随着业务量的增加,数据库的表的数量越来越多,难以管理和维护,并且数据量的增 会导致查询速度越来越慢。 例如个应用有这样几个业务: 用户的信息、用户的账户、用户的购物 、数据报 服务等。典型的单体架构如微服务的 个特 点就是按业务划分服务,服务与服务之间无稠合,就连数据库也是独立的个典型的微服务的架 构就是每个微服务都有自己独立的数据库,数据库之间没有何联系这样做的好处在于,随着业 务的不断扩张,服务与服务不需要提供数据库集成,而是提供 API 接口相互调用:还有 个好 处是数据库独立,单业务的数据盆少,易于维护,数据库性能有着明显的优势,数据库的迁移 也很方便。另外,随着存储技术的发展,数据库的存储方式不再仅仅是关系型数据库,非关系 数据库的应用也非常广泛,例如 MongoDB ,它们有着良好的读 性能,因此越来越受欢迎 个典型的微服务的系统。 1.3.4 微服务的自动化部署(CI /CD)(持续集成 持续交付) 在微服务架构中,系统会被拆分为若干个微服务,每个微服务又是一个独立的应用程序。单体架构的应用程序只需要部署一次,而微服务架构有多少个服务就需要部署多少次。随着服务数 量的增加,如果微服务按照单体架构的部署方式,部署的难度会呈指数增加。业务的粒度划分 得越细,微服务的数量就越多,这时需要更稳定的部署机制。随着技术的发展,尤其是 Docker 容器技术的推进,以及自动化部署工具(例如开源组件 Jenkins)的出现,自动化部署变得越 来越简单。 自动化部署可以提高部署的效率,减少人为的控制,部署过程中出现错误的概率降低,部署过程的每一步自动化,提高软件的质量。构建一个自动化部署的系统,虽然在前期需要开发人员 或者运维人员的学习,但是对于整个软件系统来说是一个全新的概念。在软件系统的整个生命 周期之中,每一步是由程序控制的,而不是人为控制,软件的质量提高到了一个新的高度。随 着 DevOps 这种全新概念的推进,自动化部署必然会成为微服务部署 的一种方式。 1.3.5 服务集中化管理 微服务系统是按业务单元来划分服务的,服务数量越多,管理起来就越复杂,因此微服务必须使用集中化管理。目前流行的微服务框架中,例如 Spring Cloud 采用 Eureka 来注册服务和 发现服务,另外, Zookeeper、 Consul 等都是非常优秀的服务集中化管理框架。 1.3.6 分布式架构 分布式系统是集群部署的,由很多计算机相互协作共同构成,它能够处理海量的用户请求。当分布式系统对外提供服务时,用户是毫不知情的,还以为是一台服务器在提供服务。分布式系 统的复杂任务通过计算机之间的相互协作来完成,当然简单的任务也可以在一台计算机上完 成。分布式系统通过网络协议来通信,所以分布式系统在空间上没有任何限制,即分布式服务 器可以部署不同的机房和不同的地区。微服务架构是分布式架构,分布式系统比单体系统更加 复杂,主要体现在服务的独立性和服务相互调用的可靠性,以及分布式事务、全局锁、全局 Id 等,而单体系统不需要考虑这些复杂性。 另外,分布式系统的应用都是集群化部署,会给数据一致性带来困难。分布式系统中的服务通 信依赖于网络,网络不好,必然会对分布式系统带来很大的影响。在分布式系统中,服务之间 相互依赖,如果一个服务出现了故障或者是网络延迟,在高并发的情况下,会导致线程阻塞, 在很短的时间内该服务的线程资源会消耗殆尽,最终使得该服务不可用。由于服务的相互依赖, 可能会导致整个系统的不可用,这就是“雪崩效应”。为了防止此类事件的发生,分布式系统 必然要采取相应的措施,例如“熔断机制”。 1.3.7 熔断机制 Hystrix 为了防止“雪崩效应”事件的发生,分布式系统采用了熔断机制。在用 SpringCloud 构建的微服务系统中,采用了熔断器(即 Hystrix 令 组件的 C ircuit Breaker)去做熔断。例如在微 服务系统中,有 a、 b、 c、 d、 e、 如果此时服务 b 出现故障或者网络延迟,服务 b 会出现大量的线程阻塞,有可能在很短的时间内线程资源就被消耗完了,导致服务 b 的不可用。如果服务 b 为较 底层的服务,会影响 到其他服务,导致其他服务会一直等待服务 b 的处理。如果服务 b 迟迟不处理,大量的网络 请求不仅仅堆积在服务 b,而且会堆积到依赖于服务 b 的其他服务。而因服务 b 出现故障影 响的服务,也会影响到依赖于因服务 b 出现故障影响的服务的其他服务,从而由 b 开始,影 响到整个系统,导致整个系统的不可用。这是一件非常可怕的事,因为服务器运营商的不可靠, 必然会导致服务的不可靠,而网络服务商的不可靠性,也会导致服务的不可靠。在高并发的场 景下,稍微有点不可靠,由于故障的传播性,会导致大量的服务不可用,甚至导致整个系统崩 溃。 为了解决这一难题,微服务架构引入了熔断机制。当服务 b 出现故障,请求失败次数超过设定的阀值之后,服务 b 就会开启熔断器,之后服务 b 不进行任何的业务逻辑操作,执行快速 失败,直接返回请求失败的信息。其他依赖于 b 的服务就不会因为得不到响应而线程阻塞, 这时除了服务 b 和依赖于服务 b 的部分功能不可用外,其他功能正常。 1.4 微服务的优势 相对于单体服务来说,微服务具有很多的优势,主要体现在以下方面。 1. 将一个复杂的业务分解成若干小的业务,每个业务拆分成一个服务,服务的边界明确,将复杂的问题简单化。服务按照业务拆分,编码也是按照业务来拆分,代码的可读性和可扩展 性增加。新人加入团队,不需要了解所有的业务代码,只需要了解他所接管的服务的代码,新 人学习时间成本减少。 2. 由于微服务系统是分布式系统,服务与服务之间没有任何的祸合。随着业务的增加,可以根据业务再拆分服务,具有极强的横向扩展能力。随着应用的用户量的增加,井发量增加, 可以将微服务集群化部署,从而增加系统的负载能力。简而言之,微服务系统的微服务单元具 有很强的横向扩展能力。 3. 服务与服务之问通过 HTTP 网络通信协议来通信,单个微服务内部高度祸合,服务与服务之间完全独立,无调合。这使得微服务可以采用任何的开发语言和技术来实现。开发人员不 再被强迫使用公司以前的技术或者已经过时的技术,而是可以自由选择最适合业务场景的或者 最适合自己的开发语言和技术,提高开发效率、降低开发成本。 4. 如果是一个单体的应用,由于业务的复杂性、代码的祸合性,以及可能存在的历史问题。在重写一个单体应用时,要求重写的应用的人员了解所有的业务,所以重写单体应用是非常困 难的,并且重写风险也较高。如果是微服务系统,由于微服务系统是按照业务的进行拆分的, 并且有坚实的服务边界,所以重写某个服务就相当于重写某一个业务的代码,非常简单。 5. 微服务的每个服务单元都是独立部署的,即独立运行在某个进程里。微服务的修改和部署对其他服务没有影响。试想,假设一个应用只有一个简单的修改,如果是单体架构,需要测 试和部署整个应用;而如果采用微服务架构,只需要测试并部署被修改的那个服务,这就大大 减少了测试和部署的时间。 6. 微服务在 CAP 理论中采用的是 AP 架构,即具有高可用和分区容错的特点。高可用主要体现在系统 7 x 24 小时不间断的服务,它要求系统有大量的服务器集群,从而提高了系统 的负载能力。另外,分区容错也使得系统更加健壮。 1.5 微服务的不足 ( 正视它的不足 ) 凡事都有两面性,微服务也不例外,微服务相对于单体应用来说具有很多的优势,当然也有它的不足,主要体现在如下方面: 1. 微服务的复杂度 2. 分布式事务问题 3. 服务的划分(按照功能划分 还是按照组件来划分呢) 分工 4. 服务的部署(不用自动化部署 自动化部署) 1.6 微服务架构的设计原则(项目起步搭建) 开闭原则 单一原则 6 大设计原则 架构设计和代码设计思路一样的软件设计就好比建筑设计。 Architect 这个词在建筑学中是“建筑师”的意思,而在软件领 域里则是“架构师”的意思,可见它们确实有相似之处。无论是建筑师还是架构师,他们都希 望把作品设计出自己的特色,并且更愿意把创造出的东西被称为艺术品。然而现实却是,建筑 设计和软件设计有非常大的区别。建筑师设计并建造出来的建筑往往很难有变化,除非拆了重 建。而架构师设计出来的软件系统,为了满足产品的业务发展,在它的整个生命周期中,每一 个版本都有很多的变化。 软件设计每一个版本都在变化,所以软件设计应该是渐进式发展。软件从一开始就不应该被设计成微服务架构,微服务架构固然有优势,但是它需要更多的资源,包括服务器资源、技术人 员等。追求大公司所带来的技术解决方案,刻意地追求某个新技术,企图使用技术解决所有的 问题,这些都是软件设计的误区。 技术应该是随着业务的发展而发展的,任何脱离业务的技术是不能产生价值的。在初创公司、业务很单一时,如果在 LAMP 单体构架够用的情况下,就应该用 LAMP ,因为它开发速度快, 性价比高。随着业务的发展,用户量的增加,可以考虑将数据库读写分离、加缓存、加复杂均 衡服务器、将应用程序集群化部署等。如果业务还在不断发展,这时可以考虑使用分布式系统, 例如微服务架构的系统。不管使用什么样的架构,驱动架构的发展一定是业务的发展,只有当 前架构不再适合当前业务的发展,才考虑更换架构。 在微服务架构中,有三大难题,那就是 ** 服务故障的传播性 ( 熔断 ) 、服务的划分和分布式事务 ** 。在微服务设计时,一定要考虑清楚这三个难题,从而选择合适的框架。目前比较流行的 微服务框架有 Spring 社区的 Spring Cloud 、 Google 公司的 Kubemetes 等。不管使用哪 一种框架或者工具,都需要考虑这三大难题。为了解决服务故障的传播性,一般的微服务框架 都有熔断机制组件。另外,服务的划分没有具体的划分方法,一般来说根据业务来划分服务, 领域驱动设计具有指导作用。最后,分布式事务一般的解决办法就是两阶段提交或者三阶段提 交,不管使用哪一种都存在事务失败,导致数据不一致的情况,关键时刻还得人工去恢复数据。 总之,微服务的设计一定是渐进式的,并且是随着业务的发展而发展的。
你了解微服务的超时传递吗?
为什么需要超时控制?
很多连锁故障的场景下的一个常见问题是服务器正在消耗大量资源处理那些早已经超过客户端截止时间的请求,这样的结果是,服务器消耗大量资源没有做任何有价值的工作,回复已经超时的请求是没有任何意义的。
超时控制可以说是保证服务稳定性的一道重要的防线,它的本质是快速失败(fail fast),良好的超时控制策略可以尽快清空高延迟的请求,尽快释放资源避免请求的堆积。
服务间超时传递
如果一个请求有多个阶段,比如由一系列 RPC 调用组成,那么我们的服务应该在每个阶段开始前检查截止时间以避免做无用功,也就是要检查是否还有足够的剩余时间处理请求。
一个常见的错误实现方式是在每个 RPC 服务设置一个固定的超时时间,我们应该在每个服务间传递超时时间,超时时间可以在服务调用的最上层设置,由初始请求触发的整个 RPC 树会设置同样的绝对截止时间。例如,在服务请求的最上层设置超时时间为3s,服务A请求服务B,服务B执行耗时为1s,服务B再请求服务C这时超时时间剩余2s,服务C执行耗时为1s,这时服务C再请求服务D,服务D执行耗时为500ms,以此类推,理想情况下在整个调用链里都采用相同的超时传递机制。
如果不采用超时传递机制,那么就会出现如下情况:
- 服务A给服务B发送一个请求,设置的超时时间为3s
- 服务B处理请求耗时为2s,并且继续请求服务C
- 如果使用了超时传递那么服务C的超时时间应该为1s,但这里没有采用超时传递所以超时时间为在配置中写死的3s
- 服务C继续执行耗时为2s,其实这时候最上层设置的超时时间已截止,如下的请求无意义
- 继续请求服务D
如果服务B采用了超时传递机制,那么在服务C就应该立刻放弃该请求,因为已经到了截止时间,客户端可能已经报错。我们在设置超时传递的时候一般会将传递出去的截止时间减少一点,比如100毫秒,以便将网络传输时间和客户端收到回复之后的处理时间考虑在内。
进程内超时传递
不光服务间需要超时传递进程内同样需要进行超时传递,比如在一个进程内串行的调用了Mysql、Redis和服务B,设置总的请求时间为3s,请求Mysql耗时1s后再次请求Redis这时的超时时间为2s,Redis执行耗时500ms再请求服务B这时候超时时间为1.5s,因为我们的每个中间件或者服务都会在配置文件中设置一个固定的超时时间,我们需要取剩余时间和设置时间中的最小值。
context实现超时传递
context原理非常简单,但功能却非常强大,go的标准库也都已实现了对context的支持,各种开源的框架也实现了对context的支持,context已然成为了标准,超时传递也依赖context来实现。
我们一般在服务的最上层通过设置初始context进行超时控制传递,比如设置超时时间为3s
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()
当进行context传递的时候,比如上图中请求Redis,那么通过如下方式获取剩余时间,然后对比Redis设置的超时时间取较小的时间
dl, ok := ctx.Deadline()
timeout := time.Now().Add(time.Second * 3)
if ok := dl.Before(timeout); ok {
timeout = dl
}
服务间超时传递主要是指 RPC 调用时候的超时传递,对于 gRPC 来说并不需要要我们做额外的处理,gRPC 本身就支持超时传递,原理和上面差不多,是通过 metadata 进行传递,最终会被转化为 grpc-timeout 的值,如下代码所示 grpc-go/internal/transport/handler_server.go:79
if v := r.Header.Get("grpc-timeout"); v != "" {
to, err := decodeTimeout(v)
if err != nil {
return nil, status.Errorf(codes.Internal, "malformed time-out: %v", err)
}
st.timeoutSet = true
st.timeout = to
}
超时传递是保证服务稳定性的一道重要防线,原理和实现都非常简单,你们的框架中实现了超时传递了吗?如果没有的话就赶紧动起手来吧。
go-zero 中的超时传递
go-zero 中可以通过配置文件中的 Timeout
配置 api gateway
和 rpc
服务的超时,并且会在服务间自动传递。
之前的 一文搞懂如何实现 Go 超时控制 里面有讲解超时控制如何使用。
参考
《SRE:Google运维解密》
项目地址
https://github.com/zeromicro/go-zero
欢迎使用 go-zero
并 star/fork 支持我们!
微信交流群
关注『微服务实践』公众号并点击 交流群 获取社区群二维码。
以上是关于快速了解什么是:微服务的主要内容,如果未能解决你的问题,请参考以下文章