IT基础设施及服务器ODM服务商
|
微服务(概念篇):什么是微服务?深入浅出搞清楚微服务 二维码
33
发表时间:2019-10-17 16:47 前言 一、微服务介绍 微,狭义来讲就是体积小、著名的"2 pizza 团队"很好的诠释了这一解释(2 pizza 团队最早是亚马逊 CEO Bezos提出来的,意思是说单个服务的设计,所有参与人从设计、开发、测试、运维所有人加起来 只需要2个披萨就够了 )。 而所谓服务,一定要区别于系统,服务一个或者一组相对较小且独立的功能单元,是用户可以感知最小功能集。 2. 微服务由来 3. 为什么需要微服务? 3.1 早期的单体架构带来的问题 1.复杂性逐渐变高 比如有的项目有几十万行代码,各个模块之间区别比较模糊,逻辑比较混乱,代码越多复杂性越高,越难解决遇到的问题。 2.技术债务逐渐上升 公司的人员流动是再正常不过的事情,有的员工在离职之前,疏于代码质量的自我管束,导致留下来很多坑,由于单体项目代码量庞大的惊人,留下的坑很难被发觉,这就给新来的员工带来很大的烦恼,人员流动越大所留下的坑越多,也就是所谓的技术债务越来越多。 3.部署速度逐渐变慢 这个就很好理解了,单体架构模块非常多,代码量非常庞大,导致部署项目所花费的时间越来越多,曾经有的项目启动就要一二十分钟,这是多么恐怖的事情啊,启动几次项目一天的时间就过去了,留给开发者开发的时间就非常少了。 4.阻碍技术创新 比如以前的某个项目使用struts2写的,由于各个模块之间有着千丝万缕的联系,代码量大,逻辑不够清楚,如果现在想用spring mvc来重构这个项目将是非常困难的,付出的成本将非常大,所以更多的时候公司不得不硬着头皮继续使用老的struts架构,这就阻碍了技术的创新。 5.无法按需伸缩 比如说电影模块是CPU密集型的模块,而订单模块是IO密集型的模块,假如我们要提升订单模块的性能,比如加大内存、增加硬盘,但是由于所有的模块都在一个架构下,因此我们在扩展订单模块的性能时不得不考虑其它模块的因素,因为我们不能因为扩展某个模块的性能而损害其它模块的性能,从而无法按需进行伸缩。 3.2 微服务与单体架构区别 单体架构所有的模块都共用一个数据库,存储方式比较单一,微服务每个模块都可以使用不同的存储方式(比如有的用redis,有的用mysql等),数据库也是单个模块对应自己的数据库。 单体架构所有的模块开发所使用的技术一样,微服务每个模块都可以使用不同的开发技术,开发模式更灵活。 3.3 微服务与SOA区别 4. 微服务本质 能不能做成微服务,取决于四个要素: 小:微服务体积小,2 pizza 团队。 独:能够独立的部署和运行。 轻:使用轻量级的通信机制和架构。 松:为服务之间是松耦合的。 6. 微服务折分与设计 拆分的大原则是当一块业务不依赖或极少依赖其它服务,有独立的业务语义,为超过2个的其他服务或客户端提供数据,那么它就应该被拆分成一个独立的服务模块。 6.1 微服务设计原则 意思是每个微服务只需要实现自己的业务逻辑就可以了,比如订单管理模块,它只需要处理订单的业务逻辑就可以了,其它的不必考虑。 服务自治原则 意思是每个微服务从开发、测试、运维等都是独立的,包括存储的数据库也都是独立的,自己就有一套完整的流程,我们完全可以把它当成一个项目来对待。不必依赖于其它模块。 轻量级通信原则 首先是通信的语言非常的轻量,第二,该通信方式需要是跨语言、跨平台的,之所以要跨平台、跨语言就是为了让每个微服务都有足够的独立性,可以不受技术的钳制。 接口明确原则 由于微服务之间可能存在着调用关系,为了尽量避免以后由于某个微服务的接口变化而导致其它微服务都做调整,在设计之初就要考虑到所有情况,让接口尽量做的更通用,更灵活,从而尽量避免其它模块也做调整。 7. 微服务优势与缺点 一系列独立运行的微服务共同构建起了整个系统; 每个服务为独立的业务开发,一个微服务一般完成某个特定的功能,比如:订单管理,用户管理等; 微服务之间通过一些轻量级的通信机制进行通信,例如通过REST API或者RPC的方式进行调用。 7.2 特点 由于微服务单个模块就相当于一个项目,开发这个模块我们就只需关心这个模块的逻辑即可,代码量和逻辑复杂度都会降低,从而易于开发和维护。 启动较快 这是相对单个微服务来讲的,相比于启动单体架构的整个项目,启动某个模块的服务速度明显是要快很多的。 局部修改容易部署 在开发中发现了一个问题,如果是单体架构的话,我们就需要重新发布并启动整个项目,非常耗时间,但是微服务则不同,哪个模块出现了bug我们只需要解决那个模块的bug就可以了,解决完bug之后,我们只需要重启这个模块的服务即可,部署相对简单,不必重启整个项目从而大大节约时间。 技术栈不受限 比如订单微服务和电影微服务原来都是用java写的,现在我们想把电影微服务改成nodeJs技术,这是完全可以的,而且由于所关注的只是电影的逻辑而已,因此技术更换的成本也就会少很多。 按需伸缩 我们上面说了单体架构在想扩展某个模块的性能时不得不考虑到其它模块的性能会不会受影响,对于我们微服务来讲,完全不是问题,电影模块通过什么方式来提升性能不必考虑其它模块的情况。 7.3 缺点 对于单体架构来讲,我们只需要维护好这一个项目就可以了,但是对于微服务架构来讲,由于项目是由多个微服务构成的,每个模块出现问题都会造成整个项目运行出现异常,想要知道是哪个模块造成的问题往往是不容易的,因为我们无法一步一步通过debug的方式来跟踪,这就对运维人员提出了很高的要求。 分布式的复杂性 对于单体架构来讲,我们可以不使用分布式,但是对于微服务架构来说,分布式几乎是必会用的技术,由于分布式本身的复杂性,导致微服务架构也变得复杂起来。 接口调整成本高 比如,用户微服务是要被订单微服务和电影微服务所调用的,一旦用户微服务的接口发生大的变动,那么所有依赖它的微服务都要做相应的调整,由于微服务可能非常多,那么调整接口所造成的成本将会明显提高。 重复劳动 对于单体架构来讲,如果某段业务被多个模块所共同使用,我们便可以抽象成一个工具类,被所有模块直接调用,但是微服务却无法这样做,因为这个微服务的工具类是不能被其它微服务所直接调用的,从而我们便不得不在每个微服务上都建这么一个工具类,从而导致代码的重复。 8. 微服务开发框架 Spring Cloud:http://projects.spring.io/spring-cloud(现在非常流行的微服务架构) Dubbo:http://dubbo.io Dropwizard:http://www.dropwizard.io (关注单个微服务的开发) Consul、etcd&etc.(微服务的模块) 9. Sprint cloud 和 Sprint boot区别 旨在简化创建产品级的Spring应用和服务,简化了配置文件,使用嵌入式web服务器,含有诸多开箱即用微服务功能,可以和spring cloud联合部署。 Spring Cloud: 微服务工具包,为开发者提供了在分布式系统的配置管理、服务发现、断路器、智能路由、微代理、控制总线等开发工具包。 具体区别可以点击:https://blog.csdn.net/Soinice/article/details/83793722 二、微服务实践先知 所以,一般在后台N个服务和UI之间会有一个代理或者叫API Gateway,他的作用包括 提供统一服务入口,让微服务对前台透明 聚合后台的服务,节省流量,提升性能 提供安全,过滤,流控等API管理功能 我的理解其实这个API Gateway可以有很多广义的实现办法,可以是一个软硬一体的盒子,也可以是一个简单的MVC框架,甚至是一个Node.js的服务端。他们最重要的作用是为前台(通常是移动应用)提供后台服务的聚合,提供一个统一的服务出口,解除他们之间的耦合,不过API Gateway也有可能成为单点故障点或者性能的瓶颈。 2. 服务之间如何通信?(服务调用) REST(JAX-RS,Spring Boot) RPC(Thrift, Dubbo) 异步消息调用(Kafka, Notify) 一般同步调用比较简单,一致性强,但是容易出调用问题,性能体验上也会差些,特别是调用层次多的时候。RESTful和RPC的比较也是一个很有意思的话题。一般REST基于HTTP,更容易实现,更容易被接受,服务端实现技术也更灵活些,各个语言都能支持,同时能跨客户端,对客户端没有特殊的要求,只要封装了HTTP的SDK就能调用,所以相对使用的广一些。RPC也有自己的优点,传输协议更高效,安全更可控,特别在一个公司内部,如果有统一个的开发规范和统一的服务框架时,他的开发效率优势更明显些。就看各自的技术积累实际条件,自己的选择了。 而异步消息的方式在分布式系统中有特别广泛的应用,他既能减低调用服务之间的耦合,又能成为调用之间的缓冲,确保消息积压不会冲垮被调用方,同时能保证调用方的服务体验,继续干自己该干的活,不至于被后台性能拖慢。不过需要付出的代价是一致性的减弱,需要接受数据最终一致性;还有就是后台服务一般要 实现幂等性,因为消息发送出于性能的考虑一般会有重复(保证消息的被收到且仅收到一次对性能是很大的考验);最后就是必须引入一个独立的broker,如果公司内部没有技术积累,对broker分布式管理也是一个很大的挑战。 3. 这么多服务怎么查找?(服务发现) 客户端做: 优点是架构简单,扩展灵活,只对服务注册器依赖。缺点是客户端要维护所有调用服务的地址,有技术难度,一般大公司都有成熟的内部框架支持,比如Dubbo。 服务端做: 优点是简单,所有服务对于前台调用方透明,一般在小公司在云服务上部署的应用采用的比较多。 4. 服务挂了怎么办? 重试机制 限流 熔断机制 负载均衡 降级(本地缓存) 这些方法基本上都很明确通用,就不详细说明了。比如Netflix的Hystrix:https://github.com/Netflix/Hystrix 5. 微服务需要考虑的问题 API Gateway 服务间调用 服务发现 服务容错 服务部署 数据调用 三、微服务重要部件
服务注册中心是服务发现的核心。它保存了各个可用服务实例的网络地址(IPAddress和Port)。服务注册中心必须要有高可用性和实时更新功能。上面提到的 Netflix Eureka 就是一个服务注册中心。它提供了服务注册和查询服务信息的REST API。服务通过使用POST请求注册自己的IPAddress和Port。每30秒发送一个PUT请求刷新注册信息。通过DELETE请求注销服务。客户端通过GET请求获取可用的服务实例信息。 Netflix的高可用(Netflix achieves high availability )是通过在Amazon EC2运行多个实例来实现的,每一个Eureka服务都有一个弹性IP Address。当Eureka服务启动时,有DNS服务器动态的分配。Eureka客户端通过查询 DNS来获取Eureka的网络地址(IP Address和Port)。一般情况下,都是返回和客户端在同一个可用区Eureka服务器地址。 其他能够作为服务注册中心的有: etcd:高可用,分布式,强一致性的,key-value,Kubernetes和Cloud Foundry都是使用了etcd。 consul:一个用于discovering和configuring的工具。它提供了允许客户端注册和发现服务的API。Consul可以进行服务健康检查,以确定服务的可用性。 zookeeper:在分布式应用中被广泛使用,高性能的协调服务。 Apache Zookeeper 最初为Hadoop的一个子项目,但现在是一个顶级项目。 2.1 zookeeper服务注册和发现 具体来说,zookeeper就是个分布式文件系统,每当一个服务提供者部署后都要将自己的服务注册到zookeeper的某一路径上: /{service}/{version}/{ip:port},比如我们的HelloWorldService部署到两台机器,那么zookeeper上就会创建两条目录: /HelloWorldService/1.0.0/100.19.20.01:16888 /HelloWorldService/1.0.0/100.19.20.02:16888 zookeeper提供了“心跳检测”功能,它会定时向各个服务提供者发送一个请求(实际上建立的是一个 socket 长连接),如果长期没有响应,服务中心就认为该服务提供者已经“挂了”,并将其剔除,比如100.19.20.02这台机器如果宕机了,那么zookeeper上的路径就会只剩/HelloWorldService/1.0.0/100.19.20.01:16888。 服务消费者会去监听相应路径(/HelloWorldService/1.0.0),一旦路径上的数据有任务变化(增加或减少),zookeeper都会通知服务消费方服务提供者地址列表已经发生改变,从而进行更新。 更为重要的是zookeeper 与生俱来的容错容灾能力(比如leader选举),可以确保服务注册表的高可用性。 3. 负载均衡 3.1 负载均衡的常见策略 把来自网络的请求随机分配给内部中的多个服务器。 3.1.2 轮询 每一个来自网络中的请求,轮流分配给内部的服务器,从1到N然后重新开始。此种负载均衡算法适合服务器组内部的服务器都具有相同的配置并且平均服务请求相对均衡的情况。 3.1.3 加权轮询 根据服务器的不同处理能力,给每个服务器分配不同的权值,使其能够接受相应权值数的服务请求。例如:服务器A的权值被设计成1,B的权值是3,C的权值是6,则服务器A、B、C将分别接受到10%、30%、60%的服务请求。此种均衡算法能确保高性能的服务器得到更多的使用率,避免低性能的服务器负载过重。 3.1.4 IP Hash 这种方式通过生成请求源IP的哈希值,并通过这个哈希值来找到正确的真实服务器。这意味着对于同一主机来说他对应的服务器总是相同。使用这种方式,你不需要保存任何源IP。但是需要注意,这种方式可能导致服务器负载不平衡。 3.1.5 最少连接数 客户端的每一次请求服务在服务器停留的时间可能会有较大的差异,随着工作时间加长,如果采用简单的轮循或随机均衡算法,每一台服务器上的连接进程可能会产生极大的不同,并没有达到真正的负载均衡。最少连接数均衡算法对内部中需负载的每一台服务器都有一个数据记录,记录当前该服务器正在处理的连接数量,当有新的服务连接请求时,将把当前请求分配给连接数最少的服务器,使均衡更加符合实际情况,负载更加均衡。此种均衡算法适合长时处理的请求服务,如FTP。 4. 容错 在调用服务集群时,如果一个微服务调用异常,如超时,连接异常,网络异常等,则根据容错策略进行服务容错。目前支持的服务容错策略有快速失败,失效切换。如果连续失败多次则直接熔断,不再发起调用。这样可以避免一个服务异常拖垮所有依赖于他的服务。 4.1 容错策略 服务只发起一次待用,失败立即报错。通常用于非幂等下性的写操作。 4.1.2 失效切换 服务发起调用,当出现失败后,重试其他服务器。通常用于读操作,但重试会带来更长时间的延迟。重试的次数通常是可以设置的。 4.1.3 失败安全 失败安全, 当服务调用出现异常时,直接忽略。通常用于写入日志等操作。 4.1.4 失败自动恢复 当服务调用出现异常时,记录失败请求,定时重发。通常用于消息通知。 4.1.5 forking Cluster 并行调用多个服务器,只要有一个成功,即返回。通常用于实时性较高的读操作。可以通过forks=n来设置最大并行数。 4.1.6 广播调用 广播调用所有提供者,逐个调用,任何一台失败则失败。通常用于通知所有提供者更新缓存或日志等本地资源信息。 5. 熔断 我们在处理异常的时候,要根据具体的业务情况来决定处理方式,比如我们调用商品接口,对方只是临时做了降级处理,那么作为网关调用就要切到可替换的服务上来执行或者获取托底数据,给用户友好提示。还有要区分异常的类型,比如依赖的服务崩溃了,这个可能需要花费比较久的时间来解决。也可能是由于服务器负载临时过高导致超时。作为熔断器应该能够甄别这种异常类型,从而根据具体的错误类型调整熔断策略。增加手动设置,在失败的服务恢复时间不确定的情况下,管理员可以手动强制切换熔断状态。最后,熔断器的使用场景是调用可能失败的远程服务程序或者共享资源。如果是本地缓存本地私有资源,使用熔断器则会增加系统的额外开销。还要注意,熔断器不能作为应用程序中业务逻辑的异常处理替代品。 有一些异常比较顽固,突然发生,无法预测,而且很难恢复,并且还会导致级联失败(举个例子,假设一个服务集群的负载非常高,如果这时候集群的一部分挂掉了,还占了很大一部分资源,整个集群都有可能遭殃)。如果我们这时还是不断进行重试的话,结果大多都是失败的。因此,此时我们的应用需要立即进入失败状态(fast-fail),并采取合适的方法进行恢复。 我们可以用状态机来实现CircuitBreaker,它有以下三种状态: 关闭( Closed ):默认情况下Circuit Breaker是关闭的,此时允许操作执行。CircuitBreaker内部记录着最近失败的次数,如果对应的操作执行失败,次数就会续一次。如果在某个时间段内,失败次数(或者失败比率)达到阈值,CircuitBreaker会转换到开启( Open )状态。在开启状态中,Circuit Breaker会启用一个超时计时器,设这个计时器的目的是给集群相应的时间来恢复故障。当计时器时间到的时候,CircuitBreaker会转换到半开启( Half-Open )状态。 开启( Open ):在此状态下,执行对应的操作将会立即失败并且立即抛出异常。 半开启( Half-Open ):在此状态下,Circuit Breaker会允许执行一定数量的操作。如果所有操作全部成功,CircuitBreaker就会假定故障已经恢复,它就会转换到关闭状态,并且重置失败次数。如果其中 任意一次 操作失败了,Circuit Breaker就会认为故障仍然存在,所以它会转换到开启状态并再次开启计时器(再给系统一些时间使其从失败中恢复) 6. 限流和降级 7. SLA 分配给客户的最小带宽; 客户带宽极限; 能同时服务的客户数目; 在可能影响用户行为的网络变化之前的通知安排; 拨入访问可用性; 运用统计学; 服务供应商支持的最小网络利用性能,如99.9%有效工作时间或每天最多为1分钟的停机时间; 各类客户的流量优先权; 客户技术支持和服务; 惩罚规定,为服务供应商不能满足 SLA需求所指定。 8. API网关 9. 多级缓存 10. 超时和重试 11. 线程池隔离 12. 降级和限流 13. 网关监控和统计
|