01
微服务与分布式
什么是分布式?
首先,我们对上图提到的部分关键词进行讲解。
单体,是指一个进程完成全部的后端处理;水平拆分,是同一个后端多环境部署,他们都处理相同的内容,使用反向代理来均衡负载,这种也叫集群;垂直拆分,则是把不同业务拆分为各个节点,反向代理通过路由将请求分发给每个业务节点上。
分布式,就是不同的组件或者同一个组件在多个机器节点上部署,关键在于是否通过交换信息的方式进行协作,也就是说,分布式是指通过网络连接的多个组件,通过交换信息协作而形成的系统。而集群,是指同一种组件的多个实例,形成的逻辑上的整体,它们之间不会互相通信。
分布式和微服务有何不同?
简单来说,微服务是很小的服务,一个服务可能负责几个功能或者一个业务,是一种面向SOA架构的,服务之间也是通过RPC来交互或者是webservice来交互的,这个服务可以单独部署运行,每个微服务都是由独立的小团队开发,测试,部署,上线,负责它的整个生命周期。
再说分布式系统,其是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统分布式服务顾名思义服务是分散部署在不同的机器上的,微服务架构是面向逻辑的架构。逻辑架构设计完后就该做物理架构设计,系统应用部署在超过一台服务器或虚拟机上,且各分开部署的部分彼此通过各种通讯协议交互信息,就可算作分布式部署,生产环境下的微服务肯定是分布式部署的,分布式部署的应用不一定是微服务架构的,比如集群部署,它是把相同应用复制到不同服务器上,但是逻辑功能上还是单体应用。
分布式和微服的架构很相似,只是部署的方式不一样。假设去大饭店吃饭就是一个完整的业务的话, 饭店的厨师、洗碗阿姨、服务员就是分布式,特点是各司其职。厨师、洗碗阿姨和服务员都不止一个人,这就是集群。分布式就是微服务的一种表现形式,分布式是部署层面,微服务是设计层面。
为什么要分布式部署?
分布式部署优点:
分布式计算可以把大型计算分布到多台计算机上进行,它可以根据不同的任务和场景来配置不同数量的计算资源,满足所需要的快速响应时间,提供更高的性能。
分布式计算系统可以根据需要,增加更多的计算机来满足技术需求
分布式计算因为采用很多计算机来完成计算,一台服务器的崩溃并不影响到其余的服务器,失败的任务也会被调度到其他服务器上重新执行,不影响总体任务的完成
分布式部署缺点:
而要定位具体的故障机器及原因,并进行故障调试就存在着很多的问题。引起故障的原因也是多方面的,可能是网络问题、硬件问题、权限问题、同步问题等,要进行问题的重现和跟踪诊断远不如一台服务器或是一个集中的运行环境来得方便。
经常会遇到网络基础设施的问题,如传输问题、网络拥堵、信息丢失等,需要在应用层面处理所有这些故障,造成比较大的开销
开发过程中需要考虑到分布式可能带来的数据,安全等的问题,需要开发来解决,增加工作难度。
02
分布式系统挑战
挑战
分布式系统需要大量机器协作,面临诸多的挑战:
#1
异构的机器与网络。
分布式系统中的机器,配置不一样,其上运行的服务也可能由不同的语言、架构实现,因此处理能力也不一样;节点间通过网络连接,而不同网络运营商提供的网络的带宽、延时、丢包率又不一样。怎么保证大家齐头并进,共同完成目标,这四个不小的挑战。
普遍的节点故障。
虽然单个节点的故障概率较低,但节点数目达到一定规模,出故障的概率就变高了。分布式系统需要保证故障发生的时候,系统仍然是可用的,这就需要监控节点的状态,在节点故障的情况下将该节点负责的计算、存储任务转移到其他节点。
#2
#3
不可靠的网络。
节点间通过网络通信,而网络是不可靠的。可能的网络问题包括:网络分割、延时、丢包、乱序。
相比单机过程调用,网络通信最让人头疼的是超时:节点 A 向节点 B 发出请求,在约定的时间内没有收到节点 B 的响应,那么 B 是否处理了请求,这个是不确定的,这个不确定会带来诸多问题,最简单的,是否要重试请求,节点 B 会不会多次处理同一个请求。
分布式系统CAP+Base
CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。
在分布式系统的设计中,没有一种设计可以同时满足一致性,可用性,分区容错性 3个特性 :
为什么在分布式系统的设计中,没有一种设计可以同时满足一致性,可用性,分区容错性 3个特性?
分布式系统CP案例 (保证强一致性zookeeper)
知识点预热(ZooKeeper 集群中的角色简介):
当Follower发现Leader节点挂了之后,Follower会拒绝现有的客户端连接,参与主从选举,在成功选举出leader之前,zk将不可用;这个时候如果ZK作为注册中心,注册中心将不可用。
分布式系统AP案例 (保证高可用性)
从上图中可看出,Eureka注册中心采用集群部署,满足高可用的特性,当订单服务在扩容的时候,新增订单服务实例4,数据注册到Eureka A节点,在其余的两个节点没有订单服务4这个实例信息。这种情况下,用户服务获取服务列表,可能获取不到订单实例4,这就是最简单的数据不一致情况。但是对于Eureka集群本身,各个节点都处于平等的地位,完全是为了冗余,这时有其中一个节点挂掉并不影响Eureka的使用,从这个角度来说保证了分布式的高可用。
分布式系统BASE理论
BASE 理论中的最终一致性,是指分布式系统中所有的数据副本在经过一段时间的同步后,最终都会达到一个一致的状态。也就是说,在数据副本在达到一致之前,会存在一断延迟。
03
实战案例
场景一:websocket实现分布式方案
问题描述:websocket相关简介
业务场景:前端发起流水线执行的HTTP请求,点击执行日志与后端建立WS链接,后端能够实时的把日志主动推送到前端页面。
问题描述:websocket分布式会遇到的问题
当后端服务器有多台时,用户A发起流水线执行的HTTP请求发送到B服务器,点击查看实时日志与服务器A建立WS长链接,无法接收到实时日志的推送,而B用户刷新当前流水线可以看到实时日志。
期待效果:
用户A发起流水线执行的HTTP请求发送到B服务器。用户A、用户B点击实时日志查看,均可以查看到流水线执行的情况。
解决方案:
用户A发起流水线执行的HTTP请求发送到B服务器。B服务器把流水线执行日志实时推送到Redis中,服务器A和B监听订阅Redis的日志数据,一旦监听到数据,服务器A、B通过WS长链接把日志推送给与当前服务器建立长链接的用户A、用户B。这样就起到了解耦的作用,无论用户的请求发送到哪一台服务器上,无论用户的长链接是与哪一台服务器建立连接,都可以查看实时日志。
场景二:定时线程池分布式问题
问题描述
在项目中我们经常会使用定时线程池来实现一个定时的异步任务,线程池的创建和销毁也是JVM层面的,如果分布式多机器部署的场景使用到定时线程池又会遇到什么问题,又要怎么去解决?
问题一:用户在A服务器创建了一个任务加入到A服务器的线程池中,然后用户B负载到B服务器上去取消这个定时任务?无法取消?
思考:是不是可以对创建的任务做一个Hash,有关某个任务的所有的操作都负载到同一个服务器上
结合问题及思考进行如下改造:
问题二:如果突然A服务器挂了,这时候A服务器上面定时线程池中所存的任务就会消失?该怎么办?
思考:这里和上面websocket场景还不一样,上面实时查看日志,如果服务器挂掉用户刷新一下页面,用户可以重新负载到其他服务器继续查看日志。这里我创建完成一个任务之后,就完全交给JVM线程池了,服务器挂掉之后任务用户自己创建的任务就消失了。怎么解决?
结合问题及思考进行如下改造:
方案:我们把任务放在线程池中,并且缓存在Redis中做缓存备份,借鉴微服务注册中心的概念,我们把服务注册到注册中心,当注册中心检测到B服务器挂掉了,就通知A服务器就把挂掉的B服务器线程池中的任务同步到A服务器中同步创建一份继续执行。
细节考虑:如果有一台C服务器挂掉了,A和B同时监听到,会不会A和B分别在本地都创建了B的任务,那同一时刻A和C分别会执行B的任务,同一时刻任务会执行两遍?
深度思考:分布式带来了什么问题?系统的复杂度是不是大大的增加了。维护起来变得更复杂了。
案例总结
04
分布式常见方案
分布式事物
本地事物的ACID:
分布式事务,就是指不是在单个服务或单个数据库架构下,产生的事务,例如:在数据库水平拆分、服务垂直拆分之后,一个业务操作通常要跨多个数据库、服务才能完成。例如电商行业中比较常见的下单付款案例,包括下面几个行为:
完成上面的操作需要访问三个不同的微服务和三个不同的数据库。订单的创建、库存的扣减、账户扣款在每一个服务和数据库内是一个本地事务,可以保证ACID原则。但是当我们把三件事情看做一个'业务',要满足保证“业务”的原子性,要么所有操作全部成功,要么全部失败,不允许出现部分成功部分失败的现象,这就是分布式系统下的事务。
分布式事物解决方案:Seata
Seata事务管理中有三个重要的角色:
分布式锁
什么情况下需要使用锁?
在单机环境下,也就是单个JVM环境下多线程对共享资源的并发更新处理,我们可以简单地使用JDK提供的ReentrantLock。
如果是在微服务架构多实例的环境下,每一个服务都有多个节点,我们如果还是按照之前的方式来做,就会出现如下图这样的情况:这个时候再用ReentrantLock就没办法控制了,因为这时候这些任务是跨JVM的,不再是简单的单体应用了,需要协同多个节点信息,共同获取锁的竞争情况。
分布式锁解决方案:Redis
基于数据库实现的分布式锁。
实现逻辑:在数据库中创建一个表,表中包含方法名、类名等字段,并在方法名字段上创建唯一索引,当执行某个方法时,就使用这个方法名向表中插入数据,插入成功就相当于获取了锁,执行完成后删除对应的行数据释放锁。
但是要注意以下几点要求:
基于redis实现的分布式锁。
实现逻辑:获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
Redission基于redis实现的分布式锁RedLock。
Redission是基于redis实现的用于解决分布式问题的框架,其中RedLock是用来解决分布式锁的常见方案。使用也比较简单。
分布式ID
分布式 ID 是分布式系统下的 ID。例如:单机 MySQL 需要进行分库分表。在分库之后, 数据遍布在不同服务器上的数据库,数据库的自增主键已经没办法满足生成的主键唯一了。分布式ID就是为不同的数据节点生成全局唯一主键ID。
分布式 ID 需要满足哪些要求?
分布式 ID 常见的方案
方案一:Redis的Incr
NoSQL 方案使用 Redis 多一些。我们通过 Redis 的 incr 命令即可实现对 id 原子顺序递增。
方案二:UUID
UUID 可以保证唯一性,因为其生成规则包括 MAC 地址、时间戳、名字空间(Namespace)、随机或伪随机数、时序等元素,计算机基于这些规则生成的 UUID 是肯定不会重复的。
方案三:雪花算法
Snowflake 是 Twitter 开源的分布式 ID 生成算法。Snowflake 由 64 bit 的二进制数字组成,这 64bit 的二进制被分成了几部分,每一部分存储的数据都有特定的含义:
分布式调度
分布式调度定义(分布式任务调度有两层含义):
分布式方案一:Elastic-Job
Elastic_Job 是当当网开源的一个分布式调度解决方案,基于 Quartz 二次开发的,功能非常丰富强大,采用 zookeeper 实现分布式调度,实现任务分片以及高可用。目前由两个相互独立的子项目 Elatstic-Job-Lite 和 Elastic-Job-Cloud 组成。目前说的是 Elastic-Job-Lite 的轻量级解决方案,使用 jar 的形式提供分布式任务的调度服务,而 Elastic-Job-Cloud 是结合 Mesos 以及 Docker 在云环境下使用。
分布式调度方案二:XXL-JOB
XXL-JOB是一个轻量级分布式任务调度平台,有以下特性:
最后,工具推荐Redisson,是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Data Grid)。充分的利用了Redis键值数据库提供的一系列优势,基于Java实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度。同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作。
https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95
联系客服