在开发中,我们通常会需要一个唯一ID来标识数据,如果是单体架构,我们可以通过数据库的主键,或直接在内存中维护一个自增数字来作为ID都是
可以的,但对于一个分布式系统,就会有可能会出现ID冲突,此时有一下解决方案:
1.uuid,这种方案复杂度最低,但是会影响存储空间和性能
2.利用单机数据库的自增主键,作为分布式ID的生成器,复杂度适中,ID长度较之uuid更短,但是受到单机数据库性能的限制,并发量大的时候,
此方案也不是最优方案
3.利用redis、zookeeper的特性来生成id,比如redis的自增命令、zookeeper的顺序节点,这种方案和单机数据库((mysql)相比,性能有所提高,可
以适当选用
4.雪花算法,一切问题如果能直接用算法解决,那就是最合适的,利用雪花算法也可以生成分布式ID,底层原理就是通过某台机器在某一毫秒内对
某一个数字自增,这种方案也能保证分布式架构中的系统id唯一,但是只能保证趋势递增。业界存在tinyid、leaf等开源中间件实现了雪花算法。
分布式锁所要解决的问题的本质是:能够对分布在多台机器中的线程对共享资源的互斥访问。在这个原理上可以有很多的实现方式:
1.基于Mysql,分布式环境中的线程连接同一个数据库,利用数据库中的行锁来达到互斥访问,但是Mysql的加锁和释放锁的性能会比较低,不适合真正的实际生产环境2.基于Zookeeper,Zookeeper中的数据是存在内存的,所以相对于Mysql/性能上是适合实际环境的,并且基于Zookeeper的顺序节点、临时节点、Watch机制能非常好的来实现的分布式锁
3.基于Redis,Redis中的数据也是在内存,基于Redis的消费订阅功能、数据超时时间,lua脚本等功能,也能很好的实现的分布式锁
误区:分布式事务=Seata
分布式事务:就是要将不同节点上的事务操作,提供操作原子性保证。同时成功或者同时失败。
分布式事务第一个要点就是要在原本没有直接关联的事务之间建立联系。
1、HTTP连接: 最大努力通知。-- 事后补偿。
2、MQ:事务消息机制。
3、Redis:也可以定制出分布式事务机制。
4、Seata:是通过TC来在多个事务之间建立联系。
两阶段:AT XA就在于要锁资源。
三阶段:ICC在两阶段的基础上增加一个准备阶段。在准备阶段是不锁资源的。
SAGA模式:: 类似于熔断。 业务自己实现正向操作和补偿操作的逻辑。
基于XA协议的:两阶段提交和三阶段提交,需要数据库层面支持
基于事务补偿机制的:TCC,基于业务层面实现
本地消息表:基于本地数据库+mq,维护本地状态(进行中),通过mq调用服务,完成后响应一条消息回调,将
状态改成完成。需要配合定时任务扫表、重新发送消息调用服务,需要保证幂等
基于事务消息:mq
在单体架构中,多个线程都是属于同一个进程的,所以在线程并发执行时,遇到资源竞争时,可以利用ReentrantLock、synchronized等技术来作为
锁,来控制共享资源的使用。
而在分布式架构中,多个线程是可能处于不同进程中的,而这些线程并发执行遇到资源竞争时,利用ReentrantLock、synchronized等技术是没办法来控制多个进程中的线程的,所以需要分布式锁,意思就是,需要一个分布式锁生成器,分布式系统中的应用程序都可以来使用这个生成器所提供的锁,从而达到多个进程中的线程使用同一把锁。
目前主流的分布式锁的实现方案有两种:
1.zookeeper:利用的是zookeeper的临时节点、顺序节点、watch机制来实现的,zookeeper分布式锁的特点是高一致性,因为zookeeper保证的
是CP,所以由它实现的分布式锁更可靠,不会出现混乱
2.redis:利用redis的setnx、lua脚本、消费订阅等机制来实现的,redis分布式锁的特点是高可用,因为redis保证的是AP,所以由它实现的分布式
锁可能不可靠,不稳定(一旦redis中的数据出现了不一致),可能会出现多个客户端同时加到锁的情况
什么是分布式事务?有哪些实现方案?
在分布式系统中,一次业务处理可能需要多个应用来实现,比如用户发送一次下单请求,就涉及到订单系统创建订单、库存系统减库存,而对于一次下单,订单创建与减库存应该是要同时成功或同时失败的,但在分布式系统中,如果不做处理,就很有可能出现订单创建成功,但是减库存失败,那么解决这类问题,就需要用到分布式事务。常用解决方案有:
1.本地消息表:创建订单时,将减库存消息加入在本地事务中,一起提交到数据库存入本地消息表,然后调用库存系统,如果调用成功则修改本地
消息状态为成功,如果调用库存系统生败,则由后台定时任务从本地消息表中取出未成功的消息,重试调用虚存系统
分布式锁,即分布式系统中的锁。在单体应用中我们通过锁解决的是控制共享资源访问的问题,而分布式锁,就是解决了分布式系统中控制共
享资源访问的问题。与单体应用不同的是,分布式系统中竞争共享资源的最小粒度从线程升级成了进程。
分布式锁应该具备哪些条件:
。分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行
高可用的获取锁与释放锁
高性能的获取锁与释放锁
具备可重入特性(可理解为重新进入,由多于一个任务并发使用,而不必担心数据错误)
具备锁失效机制,即自动解锁,防止死锁
具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败
在分布式场景中我们避免不了使用分布式锁对资源进行锁定,通常我们使用Redis 或者Zookeeper 来实现。
这里说下redis,Redis 我们知道执行命令是单线程的,我们可以通过lua脚本的方式提交命令,以保证原子性操作。虽说是能够实现加锁的基本功能,但是中间还是会遇到很多其他的问题,比如说锁超时如何释放问题,我们可以设置过期时间,那么setnx命令和expire命令并不是原子操作了,那就要使用lua脚本组合提交执行,那如果业务执行时间大于我们设置的过期时间,那就需要锁续期机制,这一切我们可以直接使用RedissonLock来帮我们完成。里面已经是封装的比较完善了。
那么我们在采用Redis作为分布式锁的时候我们会遇到一个高可用的问题,如果选择主从架构就可能会存在锁出问题的场景。虽说Redis也是
提供了RedLock但是,它的使用成本会比较高,而且在理论层面还存在争议性。
看业务需求吧,如果能够接受极端场景下互斥失败问题,并且对性能要求比较高的时候我们可以采用RedissonLock,做好兜底方案就行。如果不能接受就直接用ZooKeeper吧!
为什么要进行系统拆分?
1)应用间耦合严重。系统内各个应用之间不通,同样一个功能在各个应用中都有实现,后果就是改一处功能,需要同时改系统中的所有应用。
这种情况多存在于历史较长的系统,因各种原因,系统内的各个应用都形成了自己的严务小闭环;
2)业务扩展性差。数据模型从设计之初就只支持某一类的业务,来了新类型的业务后又得重新写代码实现,结果就是项目延期,大大影响业务
的接入速度;
3)代码老旧,难以维护。各种随意的if else、写死逻辑散落在应用的各个角落,处处是坑,开发维护起来战战兢兢;
4)系统扩展性差。系统支撑现有业务已是颤颤巍巍,不论是应用还是DB都已经无法承受业务快速发展带来的压力;
5)新坑越挖越多,恶性循环。不改变的话,最终的结果就是把系统做死了。
如何进行系统拆分?
多维度把握业务复杂度
一个老生常谈的问题,系统与业务的关系?
我们最期望的理想情况是第一种关系(车辆与人),业务觉得不合适,可以马上换一辆新的。但现实的情况是更像心脏起搏器与人之间的关
系,不是说换就能换。一个系统接的业务越多,耦合越紧密。如果在没有真正把握住业务复杂度之前贸然行动,最终的结局就是把心脏带飞
如何把握住业务复杂度?需要多维度的思考、实践。
一个是技术层面,通过与 pd以及开发的讨论,熟悉现有各个应用的领域模型,以及优缺点,这种讨论只能让人有个大概,电多的细节加代尴
架构等需要通过做需求、改造、优化这些实践来掌握。
各个应用熟悉之后,需要从系统层面来构思,我们想打造平台型的产品,那么最重要也是最难的一点就是功能集中管控,打破各个应用的业务小闭环,统一收拢,这个决心更多的是开发、产品、业务方、各个团队之间达成的共识,可以参考《微服务(Microservice)那点事》一文,“按照业务或者客户需求组织资源”。
此外也要与业务方保持功能沟通、计划沟通,确保应用拆分出来后符合使用需求、扩展需求,获取他们的支持。
定义边界,原则:高内聚,低耦合,单一职责!
定义边界,原则:高内聚,低耦合,单一职责!
业务复杂度把握后,需要开始定义各个应用的服务边界。怎么才算是好的边界?像葫芦娃兄弟一样的应用就是好的!
举个例子,葫芦娃兄弟(应用)间的技能是相互独立的,遵循单一职责原则,
比如水娃只能喷水,火娃只会喷火,隐形娃不会喷水喷火但能隐身。更为关键的是,葫芦娃兄弟最终可以合体为金刚葫芦娃,即这些应用虽然功能彼此独立,但又相互打通,最后合体在一起就成了我们的平台。
这里很多人会有疑惑,拆分粒度怎么控制?很难有一个明确的结论,只能说是结合业务场景、目标、进度的一个折中。但总体的原则是先从一
个大的服务边界开始,不要太细,因为随着架构、业务的演进,应用自然而然会再次拆分,让正确的事情自然发生才最合理。
确定拆分后的应用目标
一旦系统的宏观应用拆分图出来后,就要落实到某一具体的应用拆分上了。
首先要确定的就是某一应用拆分后的目标。拆分优化是没有底的,可能越做越深,越做越没结果,继而又影响自己和团队的士气。比如说可以
定这期的目标就是将db、应用分拆出去,数据模型的重新设计可以在第二期。
确定当前要拆分应用的架构状态、代码情况、依赖状况,并推演可能的各种异常。
动手前的思考成本远远低于动手后遇到问题的解决成本。应用拆分最怕的是中途说“他*的,这块不能动,原来当时这样设计是有原因的,得想别的路子!”这时的压力可想而知,整个节奏不符合预期后,很可能会接二连三遇到同样的问题,这时不仅同事们士气下降,自己也会丧失信心,继而可能导致拆分失败。
给自己留个锦囊,“有备无患”。
锦囊就四个字“有备无患”,可以贴在桌面或者手机上。在以后具体实施过程中,多思考下“方案是否有多种可以选择?复杂问题能否拆解?实际操作时是否有预案?”,应用拆分在具体实践过程中比拼得就是细致二字,多一份方案,多一份预案,不仅能提升成功概率,更给自己信心。
什么是微服务?
微服务架构是一个分布式系统,
按照业务进行划分成为不同的服务单元.
解决单体系统性能等不足
微服务架构(通常简称为微服务)是指开发应用所用的一种架构形式或者称之为一种架构风格。通过微服务,可将大型应用分解成多个独立的组件,其中每个组件都有各自的责任领域,每一个组件都能够单独部署,组件之间是松耦合的。在处理一个用户请求时,基于微服务的应用可能会调用许多内部微服务来共同生成其响应。
微服务的优势
敏捷性
微服务促进若干小型独立团队形成一个组织,这些团队负责自己的服务。各团队在小型且易于理解的环境中行事,并且可以更独立、更快速地
工作。这缩短了开发周期时间。您可以从组织的总吞吐量中显著获益。
灵活扩展
通过微服务,您可以独立扩展各项服务以满足其支持的应用程序功能的需求。这使团队能够适当调整基础设施需求,准确衡量功能成本,并在
服务需求激增时保持可用性。
轻松部署
微服务支持持续集成和持续交付,可以轻松尝试新想法,并可以在无法正常运行时回滚。由于故障成本较低,因此可以大胆试验,更轻松地更
新代码,并缩短新功能的上市时间。
技术自由
微服务架构不遵循“一刀切”的方法。团队可以自由选择最佳工具来解决他们的具体问题。因此,构建微服务的团队可以为每项作业选择最佳工
具。
可重复使用的代码
将软件划分为小型且明确定义的模块,让团队可以将功能用于多种目的。专为某项功能编写的服务可以用作另一项功能的构建块。这样应用程
序就可以自行引导,因为开发人员可以创建新功能,而无需从头开始编写代码。
弹性
服务独立性增加了应用程序应对故障的弹性。在整体式架构中,如果一个组件出现故障,可能导致整个应用程序无法运行。通过微服务,应用
程序可以通过降低功能而不导致整个应用程序崩溃来处理总体服务故障。
同步
RESTHTTP协议
REST请求在微服务中是最为常用的一种通讯方式,它依赖于 HTTP\HTTPS 协议。RESTFUL的特点是:
1.每一个URI代表1种资源
2.客户端使用GET、POST、PUT、DELETE4个表示操作方式的动词对服务端资源进行操作:GET用来获取资源,POST用来新建资源(也
可以用于更新资源),PUT用来更新资源,DELETE 用来删除资源
3.通过操作资源的表现形式来操作资源
4.资源的表现形式是XML或者HTML
5.客户端与服务端之间的交互在请求之间是无状态的,从客户端到服务端的每个请求都必须包含理解请求所必需的信息
RPCTCP协议
RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请求另一个节点提供的服务。它的工作流程是这样的:
1.执行客户端调用语句,传送参数
2.调用本地系统发送网络消息
3.消息传送到远程主机
4.服务器得到消息并取得参数
5.根据调用请求以及参数执行远程过程(服务)
6.执行过程完毕,将结果返回服务器句柄
7.服务器句柄返回结果,调用远程主机的系统网络服务发送结果
8.消息传回本地主机
9. 客户端句柄由本地主机的网络服务接收消息
异步
消息中间件
常见的消息中间件有Kafka、ActiveMQ、RabbitMQ、RocketMQ,常见的协议有AMQP、MQTTP、STOMP、XMPP。这里不对消息队列进行
拓展了,具体如何使用还是请移步官网。
面向微服务的技术(SpringCloud)
Spring Cloud 抛弃了Dubbo的RPC通信,采用的是基于HTTP的REST方式。严格来说,这两种方式各有优劣。虽然从一定程度上来说,后者牺牲了服务调用的性能,但也避免了上面提到的原生RPC带来的问题。而且REST相比 RPC更为灵活,服务提供方和调用方,不存在代码级别的强依赖,这在强调快速演化的微服务环境下显得更加合适。
最大的区别:
Dubbo 底层是使用Netty 这样的NIO 框架,是基于TCP 协议传输的,配合以Hession 序列化完成RPC通信;
而SpringCloud是基于Http协议+rest 接口调用远程过程的通信,相对来说,Http请求会有更大的报文,占的带宽也会更多。但是REST相比RPC更为灵活,服务提供方和调用方的依赖只依靠一纸契约,不存在代码级别的强依赖,这在强调快速演化的微服务环境下,显得更为合适,至于注重通信速度还是方便灵活性,具体情况具体考虑。
定位区别
Dubbo 是SOA时代的产物,它的关注点主要在于服务的调用,流量分发、流量监控和熔断;而Spring Cloud 诞生于微服务架构时代,考虑的是
微服务治理的方方面面,另外由于依托 Spirng、Spirng Boot 的优势之上,两个框架在开始目标就不一致,Dubbo 定位服务治理、Spirng Sl0u0 是一T生态。因此可以大胆地判断,Dubbo 未来会在服务治理方面更为出色,而 Sprinacloud 在微服务治理上面无人能敌.
模块区别
1、Dubbo主要分为服务注册中心,服务提供者,服务消费者,还有管控中心;
2、相比起Dubbo简单的四个模块,SpringCloud则是一个完整的分布式一站式框架,他有着一样的服务注册中心,服务提供者,服务消费
者,管控台,断路器,分布式配置服务,消息总线,以及服务追踪等;
Spring Boot
Spring Boot 这家伙简直就是对Java企业级应用开发进行了一场浩浩荡荡的革命。如果稍微有几年工作经验的老油条,应该都记得以前的Java Web开发模式:Tomcat+WAR包。WEB 项目基于spring framework,项目目录一定要是标准的WEB-INF+ classes + lib,而且大量的xml配置。如果说,以前搭建一个SSH架构的Web项目需要1个小时,那么现在应该10分钟就可以了。
比如我们要创建一个web项目,使用Spring的朋友都知道,在使用Spring 的时候,需要在pom文件中添加多个依赖,而Spring Boot 则会
帮助开发着快速启动一个web容器,在SpringBoot中,我们只需要在pom文件中添加如下一个starter-web依赖即可。
Spring 虽然使Java EE轻量级框架,但由于其繁琐的配置,一度被人认为是“配置地狱”。各种XML、Annotation配置会让人眼花缭乱,而且
配置多的话,如果出错了也很难找出原因。Spring Boot 更多的是采用Java Config的方式,对Spring进行配置。
Spring Boot 能够让你非常容易的创建一个单机版本、生产级别的基于spring framework的应用。然后,“just run"即可。SpringBoot 默认集
成了很多第三方包,以便你能以最小的代价开始一个项目。
在使用Spring时,项目部署时需要我们在服务器上部署 tomcat,然后把项目打成war 包扔到 tomcat里,在使用Spring Boot后,我们不需要
在服务器上去部署tomcat,因为Spring Boot 内嵌了tomcat,我们只需要将项目打成jar包,使用java-jar xxx.jar一键式启动项目。
我们可以引入spring-boot-start-actuator 依赖,直接使用REST方式来获取进程的运行期性能参数,从而达到监控的目的,比较方便。但是Spring Boot只是个微框架,没有提供相应的服务发现与注册的配套功能,没有外围监控集成方案,没有外围安全管理方案,所以在微服务架构中,还需要Spring Cloud 来配合一起使用。
我们看看官方对Spring Boot 的定义:
Spring Boot is designed to get you up and running as quickly as possible, with minimal upfront configuration of Spring. Spring Boot takes
an opinionated view of building production-ready applications.
即Spring Boot 为快速启动且最小化配置的spring应用而设计,并且它具有用于构建生产级别应用的一套固化的视图。这里的固化的视图,笔
者认为可以理解成SpringBoot的约定,因为Spring Boot的设计是约定大于实现的。
Spring Cloud
最后就是大名鼎鼎的Spring Cloud了,Spring Cloud 事实上是一整套基于Spring Boot的微服务解决方案。它为开发者提供了很多工具,用于快速构建分布式系统的一些通用模式,例如:配置管理、注册中心、服务发现、限流、网关、链路追踪等。
如下图所示,很好的说明了Spring Boot 和Spring Cloud的关系,Spring Boot 是build anything,而Spring Cloud是coordinate anything,
Spring Cloud的每一个微服务解决方案都是基于Spring Boot 构建的.
从这个问题开始,面试官就已经进入了实际的生产问题的面试了。
一个分布式系统中的某个接口,该如何保证幂等性?这个事儿其实是你做分布式系统的时候必须要考虑的一个生产环境的技术问题。啥意思
呢?
你看,假如你有个服务提供一些接口供外部调用,这个服务部署在了5台机器上,接着有个接口就是付款接口。然后人家用户在前端上操作的时候,不知道为啥,总之就是一个订单不小心发起了两次支付请求,然后这俩请求分散在了这个服务部署的不同的机器上,好了,结果一个订单扣款扣两次。
或者是订单系统调用支付系统进行支付,结果不小心因为网络超时了,然后订单系统走了前面我们看到的那个重试机制,咔嚓给你重试了一
把,好,支付系统收到一个支付请求两次,而且因为负载均衡算法落在了不同的机器上,尴尬了。。。
所以你肯定得知道这事儿,否则你做出来的分布式系统恐怕容易埋坑。
这个不是技术问题,这个没有通用的一个方法,这个应该结合业务来保证幂等性。
所谓幂等性,就是说一个接口,多次发起同一个请求,你这个接口得保证结果是准确的,比如不能多扣款、不能多插入一条数据、不能将统计
值多加了1。这就是幂等性。
其实保证幂等性主要是三点:
对于每个请求必须有一个唯一的标识,举个栗子:订单支付请求,肯定得包含订单id,一个订单id最多支付一次,对吧。
每次处理完请求之后,必须有一个记录标识这个请求处理过了。常见的方案是在 mysql中记录个状态啥的,比如支付之前记录一条这个订
单的支付流水。
每次接收请求需要进行判断,判断之前是否处理过。比如说,如果有一个订单已经支付了,就已经有了一条支付流水,那么如果重复发送
这个请求,则此时先插入支付流水,orderld已经存在了,唯一键约束生效,报错插入不进去的。然后你就不用再扣款了。
实际运作过程中,你要结合自己的业务来,比如说利用Redis,用orderld作为唯一键。只有成功插入这个支付流水,才可以执行实际的支付扣
款。
要求是支付一个订单,必须插入一条支付流水,order_id建一个唯一键 unique key。你在支付一个订单之前,先插入一条支付流水,
order_id 就已经进去了。你就可以写一个标识到Redis 里面去,set order_id payed ,下一次重复请求过来了,先查 Redis 的 order_id对应的value,如果是payed就说明已经支付过了,你就别重复支付了。
限流可以认为服务降级的一种,限流就是限制系统的输入和输出流量已达到保护系统的目的。一般来说系统的吞吐量是可以被测算的,为了保
证系统的稳定运行,一旦达到的需要限制的阈值,就需要限制流量并采取一些措施以完成限制流量的目的。比如:延迟处理,拒绝处理,或者部分拒绝处理等等。
1、计数器算法
采用计数器实现限流有点简单粗暴,一般我们会限制一秒钟的能够通过的请求数,比如限流qps100,算法的实现思路就是从第一个请求进来开始计时,在接下去的1s内,每来一个请求,就把计数加1,如果累加的数字达到了100,那么后续的请求就会被全部拒绝。等到1s结束后,把计数恢复成0,重新开始计数。
具体的实现可以是这样的:对于每次服务调用,可以通过 AtomicLong#incrementAndGet()方法来给计数器加1并返回最新值,通过这个
最新值和阈值进行比较。
这种实现方式,相信大家都知道有一个弊端:如果我在单位时间 1s内的前10ms,已经通过了 100个请求,那后面的990ms,只能眼巴巴的
把请求拒绝,我们把这种现象称为“突刺现象”
2、漏桶算法
为了消除“突刺现象”,可以采用漏桶算法实现限流,漏桶算法这个名字就很形象,算法内部有一个容器,类似生活用到的漏斗,当请求进来
时,相当于水倒入漏斗,然后从下端小口慢慢匀速的流出。不管上面流量多大,下面流出的速度始终保持不变。
不管服务调用方多么不稳定,通过漏桶算法进行限流,每10毫秒处理一次请求。因为处理的速度是固定的,请求进来的速度是未知的,可能
突然进来很多请求,没来得及处理的请求就先放在桶里,既然是个桶,肯定是有容量上限,如果桶满了,那么新进来的请求就丢弃。
在算法实现方面,可以准备一个队列,用来保存请求,另外通过一个线程池定期从队列中获取请求并执行,可以一次性获取多个并发执行。
这种算法,在使用过后也存在弊端:无法应对短时间的突发流量。
3、令牌桶算法
从某种意义上讲,令牌桶算法是对漏桶算法的一种改进,桶算法能够限制请求调用的速率,而令牌桶算法能够在限制调用的平均速率的同时还
允许一定程度的突发调用。
在令牌桶算法中,存在一个桶,用来存放固定数量的令牌。算法中存在一种机制,以一定的速率往桶中放令牌。每次请求调用需要先获取令
只有拿到令牌,才有机会继续执行,否则选择选择等待可用的令牌、或者直接拒绝放令牌这个动作是持续不断的进行,如果桶中令牌数达到上限,就丢弃令牌,所以就存在这种情况,桶中一直有大量的可用令牌,这时进来的请求就可以直接拿到令牌执行,比如设置qps为100,那么限流器初始化完成一秒后,桶中就已经有100个令牌了,这时服务还没完全启动好,等启动完成对外提供服务时,该限流器可以抵挡瞬时的100个请求。所以,只有桶中没有令牌时,请求才会进行等待,最后相当于以一定的速率执行。
4、集群限流
前面讨论的几种算法都属于单机限流的范畴,但是业务需求五花八门,简单的单机限流,根本无法满足他们。
比如为了限制某个资源被每个用户或者商户的访问次数,5s只能访问2次,或者一天只能调用1000次,这种需求,单机限流是无法实现
的,这时就需要通过集群限流进行实现。
如何实现?为了控制访问次数,肯定需要一个计数器,而且这个计数器只能保存在第三方服务,比如redis。
大概思路:每次有相关操作的时候,就向redis 服务器发送一个incr命令,比如需要限制某个用户访问/index接口的次数,只需要拼接用户 id和接口名生成redis的key,每次该用户访问此接口时,只需要对这个 key 执行incr命令,在这个key带上过期时间,就可以实现指定时间的访问频率。
工作中的使用
面试官问了你一堆Dubbo 是怎么玩儿的,你会玩儿Dubbo就可以把单块系统弄成分布式系统,然后分布式之后接踵而来的就是一堆问题,最
大的问题就是分布式事务、接口幂等性、分布式锁,还有最后一个就是分布式Session。
当然了,分布式系统中的问题何止这么一点,非常之多,复杂度很高,这里只是说一下常见的几个问题,也是面试的时候常问的几个。
Session 是啥?浏览器有个Cookie,在一段时间内这个Cookie 都存在,然后每次发请求过来都带上一个特殊的
根据这个东西,在服务端可以维护一个对应的Session域,里面可以放点数据。
jsessionid cookie,就
一般的话只要你没关掉浏览器,Cookie还在,那么对应的那个Session就在,但是如果Cookie没了,Session也就没了。常见于什么购物车
之类的东西,还有登录状态保存之类的。
这个不多说了,懂Java的都该知道这个。
单块系统的时候这么玩儿Session没问题,但是你要是分布式系统呢,那么多的服务,Session状态在哪儿维护啊?
其实方法很多,但是常见常用的是以下几种:
完全不用Session
使用JWTToken储存用户身份,然后再从数据库或者cache中获取其他的信息。这样无论请求分配到哪个服务器都无所谓。
Tomcat + Redis
这个其实还挺方便的,就是使用Session的代码,跟以前一样,还是基于Tomcat 原生的Session支持即可,然后就是用一个叫做
RedisSessionManager的东西,让所有我们部署的Tomcat 都将Session数据存储到Redis即可。
在Tomcat的配置文件中配置:
然后指定Redis 的host和port就ok了。
Spring Session + Redis
上面所说的第二种方式会与Tomcat容器重耦合,如果我要将Web容器迁移成Jetty,难道还要重新把Jetty 都配置一遍?
因为上面那种Tomcat+Redis的方式好用,但是会严重依赖于Web容器,不好将代码移植到其他Web容器上去,尤其是你要是换了技术栈
咋整?比如换成了Spring Cloud或者是SpringBoot之类的呢?
所以现在比较好的还是基于Java一站式解决方案,也就是Spring。人家Spring基本上承包了大部分我们需要使用的框架,Spirng Cloud 做
微服务,Spring Boot 做脚手架,所以用Spring Session是一个很好的选择。
在pom.xml中配置:
org.springframework.sessionspring-session-data-redis1.2.1.RELEASE
redis.clientsjedis2.8.1
在Spring配置文件中配置:
在web.xml中配置:
springSessionRepositoryFilter
org.springframework.web.filter.DelegatingFilterProxy
springSessionRepositoryFilter
/*
Java
上面的代码就是ok的,给Spring Session 配置基于Redis 来存储Session数据,然后配置了一个Spring Session的过滤器,这样的话,Session 相关操作都会交给Spring Session 来管了。接着在代码中,就用原生的Session 操作,就是直接基于Spring Session从Redis中获取数据了。
实现分布式的会话有很多种方式,我说的只不过是比较常见的几种方式,Tomcat+ Redis早期比较常用,但是会重耦合到Tomcat中;近些
年,通过Spring Session来实现。
Zookeeper 保证了CP(C:一致性,P:分区容错性),Eureka保证了AP(A:高可用)
1.当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的信息,但不能容忍直接down掉不可用。也就是说,服务注册功能对高可用性要求比较高,但zk会出现这样一种情况,当master 节点因为网络故障与其他节点失去联系时,剩余节点会重新选leader。问题在于,选取leader时间过长,30~120s,且选取期间zk集群都不可用,这样就会导致选取期间注册服务瘫痪。在云部署的环境下,因网络问题使得zk集群失去master 节点是较大概率会发生的事,虽然服务能够恢复,但是漫长的选取时间导致的注册长期不可用是不能容忍的。
2.Eureka保证了可用性,Eureka各个节点是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点仍然可以提供注册和查询服务。而Eureka的客户端向某个Eureka注册或发现时发生连接失败,则会自动切换到其他节点,只有一台Eureka还在,就能保证注册服务可用,只是查到的信息可能不是最新的。除此之外,Eureka还有自我保护机制,如果在15分钟内超过85%的节点没有正常的心跳,那么Eureka就认为客户端与注册中心发生了网络故障,此时会出现以下几种情况:①、Eureka不在从注册列表中移除因为长时间没有收到心跳而应该过期的服务。②、Eureka 仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点上(即保证当前节点仍然可用)④、当网络稳定时,当前实例新的注册信息会被同步到其他节点。
因此,Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像Zookeeper 那样使整个微服务瘫痪。
你们线上的微服务是怎么部署的?
部署一个单体式应用意味运行大型应用的多个副本,典型的提供若干个(N)服务器(物理或者虚拟),运行若干个(M)个应用实例。部署
单体式应用不会很直接,但是肯定比部署微服务应用简单些。
一个微服务应用由上百个服务构成,服务可以采用不同语言和框架分别写就。每个服务都是一个单一应用,可以有自己的部署、资源、扩展和监控需求。例如,可以根据服务需求运行若干个服务实例,除此之外,每个实例必须有自己的CPU,内存和|/O资源。尽管很复杂,但是更挑战的是服务部署必须快速、可靠和性价比高。
有一些微服务部署的模式,先讨论一下每个主机多服务实例的模式。
单主机多服务实例模式
部署微服务的一种方法就是单主机多服务实例模式,使用这种模式,需要提供若干台物理或者虚拟机,每台机器上运行多个服务实例。很多情
说实话,如果面试官问你这个题目,那么你必须要使出全身吃奶劲了。为啥?因为你没看到现在很多公司招聘的JD里都是说啥,有高并发经
验者优先。
如果你确实有真才实学,在互联网公司里干过高并发系统,那你确实拿offer基本如探囊取物,没啥问题。面试官也绝对不会这样来问你,否
则他就是蠢。
假设你在某知名电商公司干过高并发系统,用户上亿,一天流量几十亿,高峰期并发量上万,甚至是十万。那么人家一定会仔细盘问你的系统架构,你们系统啥架构?怎么部署的?部署了多少台机器?缓存咋用的?MQ咋用的?数据库咋用的?就是深挖你到底是如何扛住高并发的。因为真正干过高并发的人一定知道,脱离了业务的系统架构都是在纸上谈兵,真正在复杂业务场景而且还高并发的时候,那系统架构一定不是那么简单的,用个redis,用mq就能搞定?当然不是,真实的系统架构搭配上业务之后,会比这种简单的所谓“高并发架构”要复杂很多倍。如果有面试官问你个问题说,如何设计一个高并发系统?那么不好意思,一定是因为你实际上没干过高并发系统。面试官看你简历就没啥出彩的,感觉就不咋地,所以就会问问你,如何设计一个高并发系统?其实说白了本质就是看看你有没有自己研究过,有没有一定的知识积累。
最好的当然是招聘个真正干过高并发的哥儿们咯,但是这种哥儿们人数稀缺,不好招。所以可能次一点的就是招一个自己研究过的哥儿们,总
比招一个啥也不会的哥儿们好吧!
所以这个时候你必须得做一把个人秀了,秀出你所有关于高并发的知识!
其实所谓的高并发,如果你要理解这个问题呢,其实就得从高并发的根源出发,为啥会有高并发?为啥高并发就很牛逼?
我说的浅显一点,很简单,就是因为刚开始系统都是连接数据库的,但是要知道数据库支撑到每秒并发两三千的时候,基本就快完了。所以才
有说,很多公司,刚开始干的时候,技术比较low,结果业务发展太快,有的时候系统扛不住压力就挂了。
当然会挂了,凭什么不挂?你数据库如果瞬间承载每秒5000/8000,甚至上万的并发,一定会宕机,因为比如mysql就压根儿扛不住这么高
的并发量。
所以为啥高并发牛逼?就是因为现在用互联网的人越来越多,很多app、网站、系统承载的都是高并发请求,可能高峰期每秒并发量几千,很
正常的。如果是什么双十一之类的,每秒并发几万几十万都有可能。
那么如此之高的并发量,加上原本就如此之复杂的业务,咋玩儿?真正厉害的,一定是在复杂业务系统里玩儿过高并发架构的人,但是你没
有,那么我给你说一下你该怎么回答这个问题:
可以分为以下6点:
可以分为以下6点:
系统拆分
·缓存
MQ
分库分表
读写分离
ElasticSearch
系统拆分
将一个系统拆分为多个子系统,用dubbo来搞。然后每个系统连一个数据库,这样本来就一个库,现在多个数据库,不也可以扛高并发么。
缓存
缓存,必须得用缓存。大部分的高并发场景,都是读多写少,那你完全可以在数据库和缓存里都写一份,然后读的时候大量走缓存不就得了。
毕竟人家redis 轻轻松松单机几万的并发。所以你可以考虑考虑你的项目里,那些承载主要请求的读场景,怎么用缓存来抗高并发。
MQ
MQ,必须得用MQ。可能你还是会出现高并发写的场景,比如说一个业务操作里要频繁搞数据库几十次,增删改增删改,疯了。那高并发绝对搞挂你的系统,你要是用redis 来承载写那肯定不行,人家是缓存,数据随时就被LRU了,数据格式还无比简单,没有事务支持。所以该用mysal还得用mysql啊。那你咋办?用MQ吧,大量的写请求灌入MQ里,排队慢慢玩儿,后边系统消费后慢慢写,控制在 mysql承载范围之内。所以你得考虑考虑你的项目里,那些承载复杂写业务逻辑的场景里,如何用MQ来异步写,提升并发性。MQ单机抗几万并发也是ok的,这个之前还特意说过。
分库分表
分库分表,可能到了最后数据库层面还是免不了抗高并发的要求,好吧,那么就将一个数据库拆分为多个库,多个库来扛更高的并发;然后将
一个表拆分为多个表,每个表的数据量保持少一点,提高 sql 跑的性能。
读写分离
读写分离,这个就是说大部分时候数据库可能也是读多写少,没必要所有请求都集中在一个库上吧,可以搞个主从架构,主库写入,从库读
取,搞一个读写分离。读流量太多的时候,还可以加更多的从库。
ElasticSearch
Elasticsearch,简称es。es是分布式的,可以随便扩容,分布式天然就可以支撑高并发,因为动不动就可以扩容加机器来扛更高的并发。那
么一些比较简单的查询、统计类的操作,可以考虑用es来承载,还有一些全文搜索类的操作,也可以考虑用es来承载。
上面的6点,基本就是高并发系统肯定要干的一些事儿,大家可以仔细结合之前讲过的知识考虑一下,到时候你可以系统的把这块阐述一下
然后每个部分要注意哪些问题,之前都讲过了,你都可以阐述阐述,表明你对这块是有点积累的。
说句实话,毕竟你真正厉害的一点,不是在于弄明白一些技术,或者大概知道一个高并发系统应该长什么样?其实实际上在真正的复杂的业务系统里,做高并发要远远比上面提到的点要复杂几十倍到上百倍。你需要考虑:哪些需要分库分表,哪些不需要分库分表,单库单表跟分库分表如何join,哪些数据要放到缓存里去,放哪些数据才可以扛住高并发的请求,你需要完成对一个复杂业务系统的分析之后,然后逐步逐步的加入高并发的系统架构的改造,这个过程是无比复杂的,一旦做过一次,并且做好了,你在这个市场上就会非常的吃香。
其实大部分公司,真正看重的,不是说你掌握高并发相关的一些基本的架构知识,架构中的一些技术,RocketMQ、Kafka、Redis、
Elasticsearch,高并发这一块,你了解了,也只能是次一等的人才。对一个有几十万行代码的复杂的分布式系统,一步一步架构、设计以及实
践过高并发架构的人,这个经验是难能可贵的。
C10K问题是一个优化网络套接字以同时处理大量客户端连接的问题。C10K表示处理10000个并发连接。注意这里的并发连接和每秒请求数不同,虽然它们是相似的: 每秒处理许多请求需要很高的吞吐量(快速处理它们),但是更大的数量并发连接需要高效的连接调度。C10K问题最早是在1999年提出来的。
Dan Kegel
目前工作在美国的洛杉矶,当前受雇于Google公司。从1978年起开始接触计算机编程,是Winetricks的作者、也是Wine 1.0的管理员,同时也是Crosstool(一个让gcc/glibc编译器更易用的工具套件)的作者。发表了著名的《The C10K problem》技术文章,是Java JSR-51规范的提交者并参与编写了Java平台的NIO和文件锁,同时参与了RFC 5128标准中有关NAT 穿越(P2P打洞)技术的描述和定义。
C10K问题的由来
Web1.0
时代大约从1991年持续到2004年,此时的互联网是一个只读网络,所有网站
的内容都由运营者提供,用户只能观看,是静态互联网。
Web 3
A Goosle ④S msnw Web 1.0
Web 2.0
Centraliznd
Web2.0
这就是表格发生变化的地方,在这个阶段,我们能够参与到网络中(就像你有你的Facebook账户,在网络上发布内容一样简单),所以这就是你能够参与到网络中的动态地方,但是这里有一个问题,你所有的数据都保存在一个集中的系统中,这意味着如果主服务器/组织出现故障,你所有的交易/服务都会停止。
uuid
1,当前日期和时间
时间戳
2,时钟序列。
计数器
3,全局唯一的IEEE机器识别号,如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得。
优点:代码简单,性能好(本地生成,没有网络消耗),保证唯一(相对而言,重复概率极低可以忽略)
缺点:
每次生成的ID都是无序的,而且不是全数字,且无法保证趋势递增。
UUID生成的是字符串,字符串存储性能差,查询效率慢,写的时候由于不能产生顺序的append操作,需要进行insert操作,导致频繁的页分裂,这种操作在记录占用空间比较大的情况下,性能下降比较大,还会增加读取磁盘次数
UUID长度过长,不适用于存储,耗费数据库性能。
ID无一定业务含义,可读性差。
有信息安全问题,有可能泄露mac地址
数据库自增序列
单机模式:
优点:
实现简单,依靠数据库即可,成本小。
ID数字化,单调自增,满足数据库存储和查询性能。
具有一定的业务可读性。(结合业务code)
强依赖DB,存在单点问题,如果数据库宕机,则业务不可用。DB生成ID性能有限,单点数据库压力大,无法扛高并发场景。信息安全问题,比如暴露订单量,ur1查询改一下id查到别人的订单
缺点:
数据库高可用:多主模式做负载,基于序列的起始值和步长设置,不同的初始值,相同的步长,步长大于节点数
优点:
缺点:
解决了ID生成的单点问题,同时平衡了负载。
系统扩容困难:系统定义好步长之后,增加机器之后调整步长困难。
数据库压力大:每次获取一个ID都必须读写一次数据库。
主从同步的时候:电商下单->支付insert master db select数据,因为数据同步延迟导致查不到这个数
据。加cache(不是最好的解决方式)数据要求比较严谨的话查master主库。
Leaf-segment
采用每次获取一个ID区间段的方式来解决,区间段用完之后再去数据库获取新的号段,这样一来可以大大减轻数据库
的压力
核心字段:biz_tag,max_id,step
biz_tag用来区分业务,max_id表示该biz_tag目前所被分配的ID号段的最大值,step表示每次分配的号段长度,原来每次获取ID都要访问数据库,现在只需要把Step设置的足够合理如1000,那么现在可以在1000个ID用完之后再去访问数据库
优点:
扩张灵活,性能强能够撑起大部分业务场景。
ID号码是趋势递增的,满足数据库存储和查询性能要求。
可用性高,即使ID生成服务器不可用,也能够使得业务在短时间内可用,为排查问题争取时间。
缺点:
可能存在多个节点同时请求ID区间的情况,依赖DB
双buffer:将获取一个号段的方式优化成获取两个号段,在一个号段用完之后不用立马去更新号段,还有一个缓存号段备用,这样能够有效解决这种冲突问题,而且采用双buffer的方式,在当前号段消耗了10%的时候就去检查下一个号段有没有准备好,如果没有准备好就去更新下一个号段,当当前号段用完了就切换到下一个已经缓存好的号段去使用,同时在下一个号段消耗到10%的时候,又去检测下一个号段有没有准备好,如此往复。
优点:
基于JVM存储双buffer的号段,减少了数据库查询,减少了网络依赖,效率更高。
缺点:
segment号段长度是固定的,业务量大时可能会频繁更新号段,因为原本分配的号段会一下用完
如果号段长度设置的过长,但凡缓存中有号段没有消耗完,其他节点重新获取的号段与之前相比可能跨度会很
大。动态调整Step
基于redis、mongodb、zk等中间件生成
雪花算法
生成一个64bit的整性数字
第一位符号位固定为0,41位时间戳,10位workId,12位序列号
位数可以有不同实现
优点:
每个毫秒值包含的ID值很多,不够可以变动位数来增加,性能佳(依赖workId的实现)。
时间戳值在高位,中间是固定的机器码,自增的序列在低位,整个ID是趋势递增的。
能够根据业务场景数据库节点布置灵活挑战bit位划分,灵活度高。
强依赖于机器时钟,如果时钟回拨,会导致重复的ID生成,所以一般基于此的算法发现时钟回拨,都会抛异常处
理,阻止ID生成,这可能导致服务不可用。
分布式锁解决方案
需要这个锁独立于每一个服务之外,而不是在服务里面。
数据库:利用主键冲突控制一次只有一个线程能获取锁,非阻塞、不可重入、单点、失效时间
Zookeeper分布式锁:
zk通过临时节点,解决了死锁的问题,一旦客户端获取到锁之后突然挂掉(Session连接断开),那么这个临时节点就会
自动删除掉,其他客户端自动获取锁。临时顺序节点解决惊群效应
Redis分布式锁:setNX,单线程处理网络请求,不需要考虑并发安全性
所有服务节点设置相同的key,返回为0、则锁获取失败
setnx
问题:
1、早期版本没有超时参数,需要单独设置,存在死锁问题(中途宕机)
2、后期版本提供加锁与设置时间原子操作,但是存在任务超时,锁自动释放,导致并发问题,加锁与释放锁不是同一
线程问题
删除锁:判断线程唯一标志,再删除
可重入性及锁续期没有实现,通过redisson解决(类似AQS的实现,看门狗监听机制)
redlock:意思的机制都只操作单节点、即使Redis通过sentinel保证高可用,如果这个master节点由于某些原因发
生了主从切换,那么就会出现锁丢失的情况(redis同步设置可能数据丢失)
。redlock从多个节点申清锁,当一半以上节点获取成功、锁才算获取成功,redission有相应的实现
目前大部分的微服务框架都是应用级注册,比如SpringCloud,包括K8S也是应用注册,应用级注册代表是把整个应用作为一个微服务注册到注册中心,注册中心保存的数据格式为:
应用名:
实例1的ip和port实例2的ip和port实例3的ip和port
而接口级注册,代表是把应用中的某个接口作为一个微服务注册到注册中心,注册中心保存的数据格式为:
接口1:
实例1的ip和port实例2的ip和port实例3的ip和port接口2:
实例1的ip和port实例2的ip和port实例3的ip和port
目前Dubbo3.0之前的版本采取的就是接口级注册,Dubbo3.0已经支持了应用级注册。
接口级注册的好处在于,对于消费者而言,可以直接面向接口,消费者想要便用哪个接口,就可以直接从注册中心根据接口名找到接口所在的地址,然后直接调用,而不用关心接口在哪个应用。而
对于应用级注册,对于消费者而言,需要知道想要使用的接口在哪个应用,然后获取应用实例地址,再去调用接口,相比较接口级注册而言就稍微麻烦了一点。
接口级注册的缺点在于,注册中心的存储的冗余信息较多,注册中心的压力更大,注册中心存储的数据如果越多,那么数据发生改变的频率也就越高,对于消费者而言就需要消耗资源来进行同步
也增加了消费者应用的压力。
需要这个锁独立于每一个服务之外,而不是在服务里面。
数据库:利用主键冲突控制一次只有一个线程能获取锁,非阻塞、不可重入、单点、失效时间
Zookeeper分布式锁:
zk通过临时节点,解决了死锁的问题,一旦客户端获取到锁之后突然挂掉(Session连接断开),那么这个临时节点就会
自动删除掉,其他客户端自动获取锁。临时顺序节点解决惊群效应
Redis分布式锁:setNX,单线程处理网络请求,不需要考虑并发安全性
所有服务节点设置相同的key,返回为0、则锁获取失败
setnx
问题:
1、早期版本没有超时参数,需要单独设置,存在死锁问题(中途宕机)
2、后期版本提供加锁与设置时间原子操作,但是存在任务超时,锁自动释放,导致并发问题,加锁与释放锁不是同一
线程问题
删除锁:判断线程唯一标志,再删除
可重入性及锁续期没有实现,通过redisson解决(类似AQS的实现,看门狗监听机制)
redlock:意思的机制都只操作单节点、即使Redis通过sentinel保证高可用,如果这个master节点由于某些原因发
生了主从切换,那么就会出现锁丢失的情况(redis同步设置可能数据丢失)
。redlock从多个节点申清锁,当一半以上节点获取成功、锁才算获取成功,redission有相应的实现
可扩展性:通过对服务、存储的扩展,来提高系统的处理能力,通过对多台服务器协同工作,来完成单台服务器无
法处理的任务,尤其是高并发或者大数据量的任务。
高可用:单点不影响整体,单点故障指系统中某个组件一旦失效,会让整个系统无法工作
无状态:无状态的服务才能满足部分机器宕机不影响全部,可以随时进行扩展的需求。
可管理:便于运维,出问题能不能及时发现定位
高可靠:同样的请求返回同样的数据;更新能够持久化;数据不会丢失
1、轮询法
将请求按顺序轮流地分配到后端服务器上,它均衡地对待后端的每一台服务器,而不关心服务器实际的连接数和当
前的系统负载。
2、加权轮询法
不同的后端服务器可能机器的配置和当前系统的负载并不相同,因此它们的抗压能力也不相同。给配置高、负载低的机器配置更高的权重,让其处理更多的请;而配置低、负载高的机器,给其分配较低的权重,降低其系统负载,加权轮询能很好地处理这一问题,并将请求顺序且按照权重分配到后端。
3、随机法
通过系统的随机算法,根据后端服务器的列表大小值来随机选取其中的一台服务器进行访问。由概率统计理论可以得知,随着客户端调用服务端的次数增多,其实际效果越来越接近于平均分配调用量到后端的每一台服务器,也就是轮询的结果。
4、加权随机法
与加权轮询法一样,加权随机法也根据后端机器的配置,系统的负载分配不同的权重。不同的是,它是按照权重随
机请求后端服务器,而非顺序。
5、源地址哈希法
源地址哈希的思想是根据获取客户端的IP地址,通过哈希函数计算得到的一个数值,用该数值对服务器列表的大小进行取模运算,得到的结果便是客服端要访问服务器的序号。采用源地址哈希法进行负载均衡,同一IP地址的客方端,当后端服务器列表不变时,它每次都会映射到同一台后端服务器进行访问。
6、最小连接数法
最小连接数算法比较灵活和智能,由于后端服务器的配置不尽相同,对于请求的处理有快有慢,它是根据后端服务器当前的连接情况,动态地选取其中当前积压连接数最少的一台服务器来处理当前的请求,尽可能地提高后端服的利用效率,将负责合理地分流到每一台服务器。
1.分布式架构是指将单体架构中的各个部分拆分,然后部署不同的机器或进程中去,SOA和
务基本上都是分布式架构的
2.SOA是一种面向服务的架构,系统的所有服务都注册在总线上,当调用服务时,从总线上查找服务信息,然后调用
3.微服务是一种更彻底的面向服务的架构,将系统中各个功能个体抽成一个个小的应用程序,基本保持一个应用对应的一个服务的架构
拆分微服务的时候,为了尽量保证微服务的稳定,会有一些基本的准则:
1.微服务之间尽量不要有业务交叉。
2.微服务之前只能通过接口进行服务调用,而不能绕过接口直接访问对方的数据。
3.高内聚,低耦合。
高内聚低耦合,是一种从上而下指导微服务设计的方法。实现高内聚低耦合的工具主要有同步的接口调用和 异步的事件驱动两种方式。
什么是DDD:在2004年,由Eric Evans提出了,DDD是面对软件复杂之道。Domain-Driven- Design-Tackling Complexity in the Heart of Software
大泥团:不利于微服务的拆分。大泥团结构拆分出来的微服务依然是泥团机构,当服务业务逐渐复杂,这个泥团又会膨胀成为大泥团。
DDD只是一种方法论,没有一个稳定的技术框架。DDD要求领域是跟技术无关、跟存储无关、跟通信无关。
什么是中台?
所谓中台,就是将各个业务线中可以复用的一些功能抽取出来,剥离个性,提取共性,形成一些可复用的组件。
大体上,中台可以分为三类业务中台、数据中台和技术中台。大数据杀熟-数据中台
中台跟DDD结合: DDD会通过限界上下文将系统拆分成一个一个的领域,而这种限界上下文,天生就成了中台之间的逻辑屏障。
·hash算法:根据key进行hash函数运算、结果对分片数取模,确定分片 适合固定分片数的场景,扩展分片或者减少分片时,所有数据都需要重新计算分片、存储·一致性hash:将整个hash值得区间组织成一个闭合的圆环,计算每台服务器的hash值、映射到圆环中。使用相同的hash算法计算数据的hash值,映射到圆环,顺时针寻找,找到的第一个服务器就是数据存储的服务器。新增及减少节点时只会影响节点到他逆时针最近的一个服务器之间的值 存在hash环倾斜的问题,即服务器分布不均匀,可以通过虚拟节点解决
·hashslot:将数据与服务器隔离开,数据与slot映射,slot与服务器映射,数据进行hash决定存放的slot,新增及删除节点时,将slot进行迁移即可
Spring Cloud是一个微服务框架,提供了微服务领域中的很多功能组件,Dubbo一开始是一个RPC调用框架,核心是解决服务调用间的问题,Spring Cloud是一个大而全的框架,Dubbo则更侧重于服务调用,所以Dubbo所提供的功能没有Spring Cloud全面,但是Dubbo的服务调用性能比Spring cloud高,不过Spring Clou蒸和Dubbo并不是对立的,是可以结合起来一起使用的。
1.当服务A调用服务B,服务B调用C,此时大量请求突然请求服务A,假如服务A本身能抗住这些请求,但是如果服务C抗不住,导致服务C请求堆积,从而服务B请求堆
积,从而服务A不可用,这就是服务雪崩,解决方式就是服务降级和服务熔断。
2.服务限流是指在高并发请求下,为了保护系统,可以对访问服务的请求进行数量上的限制,从而防止系统不被大量请求压垮,在秒杀中,限流是非常重要的。
1.服务熔断是指,当服务A调用的某个服务B不可用时,上游服务A为了保证自己不受影响,从而不再调用服务B,直接返回一个结果,减轻服务A和服务B的压力,直到服
务B恢复。
2.服务降级是指,当发现系统压力过载时,可以通过关闭某个服务,或限流某个服务来减轻系统压力,这就是服务降级。
1.Eureka:注册中心
2.Nacos:注册中心、配置中心3.Consul:注册中心、配置中心4. Spring Cloud Config:配置中心5. Feign/OpenFeign:RPC调用6.
Kong:服务网关
7.Zuul:服务网关
8.Spring Cloud Gateway:服务网关
9. Ribbon:负载均衡
10. Spring CLoud Sleuth:链路追踪
11. Zipkin:链路追踪
12.Seata:分布式事务
13.Dubbo:RPC调用
14.Sentinel:服务熔断
15.Hystrix:服务熔断
zk:CP设计(强一致性),目标是一个分布式的协调系统,用于进行资源的统一管理。
当节点crash后,需要进行leader的选举,在这个期间内,zk服务是不可用的。
eureka:AP设计(高可用),目标是一个服务注册发现系统,专门用于微服务的服务发现注册。
Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册时如果发现连接失
败,会自动切换至其他节点,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)
同时当eureka的服务端发现85%以上的服务都没有心跳的话,它就会认为自己的网络出了问题,就不会从服务列表中删除这些失去心跳的服务,同时eureka的客户端也会缓
存服务信息。eureka对于服务注册发现来说是非常好的选择。
·UUID:简单、性能好,没有顺序,没有业务含义,存在泄漏mac地址的风险
·数据库主键:实现简单,单调递增,具有一定的业务可读性,强依赖db、存在性能瓶颈,存在暴露业务 信息的风险
• redis,mongodb,zk等中间件:增加了系统的复杂度和稳定性
·雪花算法
限流一般需要结合容量规划和压测来进行。当外部请求接近或者达到系统的最大阈值时,触发限流,采取其他的手
段进行降级,保护系统不被压垮。常见的降级策略包括延迟处理、拒绝服务、随机拒绝等。
计数器法:
1、将时间划分为固定的窗口大小,例如1s
2、在窗口时间段内,每来一个请求,对计数器加1。
3、当计数器达到设定限制后,该窗口时间内的之后的请求都被丢弃处理。
4、该窗口时间结束后,计数器清零,从新开始计数。
滑动窗口计数法:
1.将时间划分为细粒度的区间,每个区间维持一个计数器,每进入一个请求则将计数器加一。
2.多个区间组成一个时间窗口,每流逝一个区间时间后,则抛弃最老的一个区间,纳入新区间。如图中示例的窗口T1变
为窗口T2
3.若当前窗口的区间计数器总和超过设定的限制数量,则本窗口内的后续请求都被丢弃。
漏桶算法:如果外部请求超出当前阈值,则会在容易里积蓄,一直到溢出,系统并不关心溢出的流量。从出口处限制请求速率,并不存在计数器法的临界问题,请求曲线始终是平滑的。无法应对突发流量,相当于一个空桶+固定处理线程
令牌桶算法:假设一个大小恒定的桶,这个桶的容量和设定的阈值有关,桶里放着很多令牌,通过一个固定的速
率,往里边放入令牌,如果桶满了,就把令牌丢掉,最后桶中可以保存的最大令牌数永远不会超过桶的大小。当有请求进入时,就尝试从桶里取走一个令牌,如果桶里是空的,那么这个请求就会被拒绝。