分布式

Posted by 盈盈冲哥 on March 5, 2020

微服务

https://www.zhihu.com/question/65502802

完全拆分后各个服务可以采用异构的技术。比如数据分析服务可以使用数据仓库作为持久化层,以便于高效地做一些统计计算;商品服务和促销服务访问频率比较大,因此加入了缓存机制等。

微服务架构还有一个技术外的好处,它使整个系统的分工更加明确,责任更加清晰,每个人专心负责为其他人提供更好的服务。

微服务架构整个应用分散成多个服务,定位故障点非常困难。在微服务架构中,一个服务故障可能会产生雪崩效用,导致整个系统故障。

Spring Cloud

https://snailclimb.gitee.io/javaguide/#/docs/system-design/micro-service/spring-cloud

构建分布式系统不需要复杂和容易出错。Spring Cloud 为最常见的分布式系统模式提供了一种简单且易于接受的编程模型,帮助开发人员构建有弹性的、可靠的、协调的应用程序。Spring Cloud 构建于 Spring Boot 之上,使得开发者很容易入手并快速应用于生产中。

我所理解的 Spring Cloud 就是微服务系统架构的一站式解决方案,在平时我们构建微服务的过程中需要做如 服务发现注册 、配置中心 、消息总线 、负载均衡断路器 、数据监控 等操作,而 Spring Cloud 为我们提供了一套简易的编程模型,使我们能在 Spring Boot 的基础上轻松地实现微服务项目的构建。

大白话入门 Spring Cloud

Spring Cloud抽象了一套通用的开发模式,依赖于RPC、网关、服务发现、配置管理、限流熔断、分布式链路跟踪的具体实现。

  • Eureka 服务发现框架

    服务发现:其实就是一个“中介”,整个过程中有三个角色:服务提供者(出租房子的)、服务消费者(租客)、服务中介(房屋中介)。

    服务提供者: 就是提供一些自己能够执行的一些服务给外界。

    服务消费者: 就是需要使用一些服务的“用户”。

    服务中介: 其实就是服务提供者和服务消费者之间的“桥梁”,服务提供者可以把自己注册到服务中介那里,而服务消费者如需要消费一些服务(使用一些功能)就可以在服务中介中寻找注册在服务中介的服务提供者。

    服务注册 Register:

    官方解释:当 Eureka 客户端向 Eureka Server 注册时,它提供自身的元数据,比如IP地址、端口,运行状况指示符URL,主页等。

    结合中介理解:房东 (提供者 Eureka Client Provider)在中介 (服务器 Eureka Server) 那里登记房屋的信息,比如面积,价格,地段等等(元数据 metaData)。

    服务续约 Renew:

    官方解释:Eureka 客户会每隔30秒(默认情况下)发送一次心跳来续约。 通过续约来告知 Eureka Server 该 Eureka 客户仍然存在,没有出现问题。 正常情况下,如果 Eureka Server 在90秒没有收到 Eureka 客户的续约,它会将实例从其注册表中删除。

    结合中介理解:房东 (提供者 Eureka Client Provider) 定期告诉中介 (服务器 Eureka Server) 我的房子还租(续约) ,中介 (服务器Eureka Server) 收到之后继续保留房屋的信息。

    获取注册列表信息 Fetch Registries:

    官方解释:Eureka 客户端从服务器获取注册表信息,并将其缓存在本地。客户端会使用该信息查找其他服务,从而进行远程调用。该注册列表信息定期(每30秒钟)更新一次。每次返回注册列表信息可能与 Eureka 客户端的缓存信息不同, Eureka 客户端自动处理。如果由于某种原因导致注册列表信息不能及时匹配,Eureka 客户端则会重新获取整个注册表信息。 Eureka 服务器缓存注册列表信息,整个注册表以及每个应用程序的信息进行了压缩,压缩内容和没有压缩的内容完全相同。Eureka 客户端和 Eureka 服务器可以使用JSON / XML格式进行通讯。在默认的情况下 Eureka 客户端使用压缩 JSON 格式来获取注册列表的信息。

    结合中介理解:租客(消费者 Eureka Client Consumer) 去中介 (服务器 Eureka Server) 那里获取所有的房屋信息列表 (客户端列表 Eureka Client List) ,而且租客为了获取最新的信息会定期向中介 (服务器 Eureka Server) 那里获取并更新本地列表。

    服务下线 Cancel:

    官方解释:Eureka客户端在程序关闭时向Eureka服务器发送取消请求。 发送请求后,该客户端实例信息将从服务器的实例注册表中删除。该下线请求不会自动完成,它需要调用以下内容:DiscoveryManager.getInstance().shutdownComponent();

    结合中介理解:房东 (提供者 Eureka Client Provider) 告诉中介 (服务器 Eureka Server) 我的房子不租了,中介之后就将注册的房屋信息从列表中剔除。

    服务剔除 Eviction:

    官方解释:在默认的情况下,当Eureka客户端连续90秒(3个续约周期)没有向Eureka服务器发送服务续约,即心跳,Eureka服务器会将该服务实例从服务注册列表删除,即服务剔除。

    结合中介理解:房东(提供者 Eureka Client Provider) 会定期联系 中介 (服务器 Eureka Server) 告诉他我的房子还租(续约),如果中介 (服务器 Eureka Server) 长时间没收到提供者的信息,那么中介会将他的房屋信息给下架(服务剔除)。

  • Ribbon 进程内负载均衡器

    Ribbon 是运行在消费者端的负载均衡器,其工作原理就是 Consumer 端获取到了所有的服务列表之后,在其内部使用负载均衡算法,进行对多个系统的调用。

    Nginx 和 Ribbon 的对比

    和 Ribbon 不同的是,Nignx是一种集中式的负载均衡器。

    何为集中式呢?简单理解就是 将所有请求都集中起来,然后再进行负载均衡。

    在 Nginx 中请求是先进入负载均衡器,而在 Ribbon 中是先在客户端进行负载均衡才进行请求的。

    Ribbon 的几种负载均衡算法

    负载均衡,不管 Nginx 还是 Ribbon 都需要其算法的支持,如果我没记错的话 Nginx 使用的是 轮询和加权轮询算法。而在 Ribbon 中有更多的负载均衡调度算法,其默认是使用的 RoundRobinRule 轮询策略。

    • RoundRobinRule:轮询策略。Ribbon 默认采用的策略。若经过一轮轮询没有找到可用的 provider,其最多轮询 10 轮。若最终还没有找到,则返回 null。
    • RandomRule: 随机策略,从所有可用的 provider 中随机选择一个。
    • RetryRule: 重试策略。先按照 RoundRobinRule 策略获取 provider,若获取失败,则在指定的时限内重试。默认的时限为 500 毫秒。
  • Open Feign 服务调用映射

  • Hystrix 服务降级熔断器

    所谓 熔断 就是服务雪崩的一种有效解决方案。当指定时间窗内的请求失败率达到设定阈值时,系统将通过 断路器 直接将此请求链路断开。

    也就是我们上面服务B调用服务C在指定时间窗内,调用的失败率到达了一定的值,那么 Hystrix 则会自动将 服务B与C 之间的请求都断了,以免导致服务雪崩现象。

    降级是为了更好的用户体验,当一个方法调用异常时,通过执行另一种代码逻辑来给用户友好的回复。

  • Zuul 微服务网关

    在上面我们学习了 Eureka 之后我们知道了 服务提供者 是 消费者 通过 Eureka Server 进行访问的,即 Eureka Server 是 服务提供者 的统一入口。那么整个应用中存在那么多 消费者 需要用户进行调用,这个时候用户该怎样访问这些 消费者工程 呢?当然可以像之前那样直接访问这些工程。但这种方式没有统一的消费者工程调用入口,不便于访问与管理,而 Zuul 就是这样的一个对于 消费者 的统一入口。

    网关是系统唯一对外的入口,介于客户端与服务器端之间,用于对请求进行鉴权、限流、 路由、监控等功能。网关有的功能,Zuul 基本都有。而 Zuul 中最关键的就是 路由和过滤器 了。

    Zuul 的路由功能

    Consumer 向 Eureka Server 进行注册,网关只要注册就能拿到所有 Consumer 的信息,拿到信息就可以获取所有的 Consumer 的元数据(名称,ip,端口),拿到这些元数据就可以直接可以做路由映射。包括统一前缀、路由策略配置、服务名屏蔽、路径屏蔽、敏感请求头屏蔽。

    Zuul 的过滤功能

    如果说,路由功能是 Zuul 的基操的话,那么过滤器就是 Zuul的利器了。毕竟所有请求都经过网关(Zuul),那么我们可以进行各种过滤,这样我们就能实现 限流,灰度发布,权限控制 等等。

    过滤器类型:Pre、Routing、Post。前置Pre就是在请求之前进行过滤,Routing路由过滤器就是我们上面所讲的路由策略,而Post后置过滤器就是在 Response 之前进行过滤的过滤器。

    令牌桶限流

    首先我们会有个桶,如果里面没有满那么就会以一定 固定的速率 会往里面放令牌,一个请求过来首先要从桶中获取令牌,如果没有获取到,那么这个请求就拒绝,如果获取到那么就放行。

    Zuul 的过滤器还可以实现 权限校验,包括上面提到的 灰度发布 等等。

  • Config 微服务统一配置中心

    当我们的微服务系统开始慢慢地庞大起来,那么多 Consumer 、Provider 、Eureka Server 、Zuul 系统都会持有自己的配置,这个时候我们在项目运行的时候可能需要更改某些应用的配置,如果我们不进行配置的统一管理,我们只能去每个应用下一个一个寻找配置文件然后修改配置文件再重启应用。

    首先对于分布式系统而言我们就不应该去每个应用下去分别修改配置文件,再者对于重启应用来说,服务无法访问所以直接抛弃了可用性,这是我们更不愿见到的。

    那么有没有一种方法既能对配置文件统一地进行管理,又能在项目运行时动态修改配置文件呢?

    Spring Cloud Config 就是能将各个 应用/系统/模块 的配置文件存放到 统一的地方然后进行管理(Git 或者 SVN)。

    Spring Cloud Config 就暴露出一个接口给启动应用来获取它所想要的配置文件,应用获取到配置文件然后再进行它的初始化工作。

    一般我们会使用 Bus 消息总线 + Spring Cloud Config 进行配置的动态刷新。

  • Bus 消息总线

    Spring Cloud Bus 的作用就是管理和广播分布式系统中的消息,也就是消息引擎系统中的广播模式。

SOA

通俗易懂地解释什么是SOA

把系统按照实际业务,拆分成刚刚好大小的、合适的、独立部署的模块,每个模块之间相互独立。

当服务越来越多,调用方也越来越多的时候,它们之间的关系就变得非常混乱,需要对这些关系进行管理。

所以这个时候就需要能进行服务治理的框架,比如dubbo+zookeeper,比如SpringCloud,有了服务治理功能,我们就能清晰地看到服务被谁谁谁调用,谁谁谁调用了哪些服务,哪些服务是热点服务需要配置服务器集群,而对这个服务集群的负载均衡也是服务治理可以完成的重要功能之一。

当然,还可以更进一步,加上服务监控跟踪等等等等之类的。

Dubbo

什么是分布式?

分布式或者说 SOA 分布式重要的就是面向服务,说简单的分布式就是我们把整个系统拆分成不同的服务然后将这些服务放在不同的服务器上减轻单体服务的压力提高并发量和性能。比如电商系统可以简单地拆分成订单系统、商品系统、登录系统等等,拆分之后的每个服务可以部署在不同的机器上,如果某一个服务的访问量比较大的话也可以将这个服务同时部署在多台机器上。

为什么要分布式?

从开发角度来讲单体应用的代码都集中在一起,而分布式系统的代码根据业务被拆分。所以,每个团队可以负责一个服务的开发,这样提升了开发效率。另外,代码根据业务拆分之后更加便于维护和扩展。

另外,我觉得将系统拆分成分布式之后不光便于系统扩展和维护,更能提高整个系统的性能。你想一想嘛?把整个系统拆分成不同的服务/系统,然后每个服务/系统 单独部署在一台服务器上,是不是很大程度上提高了系统性能呢?

RPC

什么是 RPC?RPC原理是什么?

RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。比如两个不同的服务 A、B 部署在两台不同的机器上,那么服务 A 如果想要调用服务 B 中的某个方法该怎么办呢?使用 HTTP请求 当然可以,但是可能会比较慢而且一些优化做的并不好。 RPC 的出现就是为了解决这个问题。

RPC原理是什么?

img

  1. 服务消费方(client)调用以本地调用方式调用服务;
  2. client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;
  3. client stub找到服务地址,并将消息发送到服务端;
  4. server stub收到消息后进行解码;
  5. server stub根据解码结果调用本地的服务;
  6. 本地服务执行并将结果返回给server stub;
  7. server stub将返回结果打包成消息并发送至消费方;
  8. client stub接收到消息,并进行解码;
  9. 服务消费方得到最终结果。

RPC 解决了什么问题?

从上面对 RPC 介绍的内容中,概括来讲RPC 主要解决了:让分布式或者微服务系统中不同服务之间的调用像本地调用一样简单。

常见的 RPC 框架总结?

  • RMI(JDK自带): JDK自带的RPC,有很多局限性,不推荐使用。

  • Dubbo: Dubbo是 阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring框架无缝集成。目前 Dubbo 已经成为 Spring Cloud Alibaba 中的官方组件。

  • gRPC :gRPC是可以在任何环境中运行的现代开源高性能RPC框架。它可以通过可插拔的支持来有效地连接数据中心内和跨数据中心的服务,以实现负载平衡,跟踪,运行状况检查和身份验证。它也适用于分布式计算的最后一英里,以将设备,移动应用程序和浏览器连接到后端服务。

  • Hessian: Hessian是一个轻量级的remotingonhttp工具,使用简单的方法提供了RMI的功能。 相比WebService,Hessian更简单、快捷。采用的是二进制RPC协议,因为采用的是二进制协议,所以它很适合于发送二进制数据。

  • Thrift: Apache Thrift是Facebook开源的跨语言的RPC通信框架,目前已经捐献给Apache基金会管理,由于其跨语言特性和出色的性能,在很多互联网公司得到应用,有能力的公司甚至会基于thrift研发一套分布式服务框架,增加诸如服务注册、服务发现等功能。

既有 HTTP ,为啥用 RPC 进行服务调用?

RPC 只是一种概念、一种设计,就是为了解决 不同服务之间的调用问题, 它一般会包含有 传输协议序列化协议 这两个。

但是,HTTP 是一种协议,RPC框架可以使用 HTTP协议作为传输协议或者直接使用TCP作为传输协议,使用不同的协议一般也是为了适应不同的场景。

RPC框架功能更齐全:成熟的 RPC框架还提供好了“服务自动注册与发现”、”智能负载均衡”、“可视化的服务治理和运维”、“运行期流量调度”等等功能,这些也算是选择 RPC 进行服务注册和发现的一方面原因吧!

Dubbo

dubbo

Apache Dubbo (incubating) ˈdʌbəʊ 是一款高性能、轻量级的开源Java RPC 框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。简单来说 Dubbo 是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。

Dubbo 的诞生和 SOA 分布式架构的流行有着莫大的关系。SOA 面向服务的架构(Service Oriented Architecture),也就是把工程按照业务逻辑拆分成服务层、表现层两个工程。服务层中包含业务逻辑,只需要对外提供服务即可。表现层只需要处理和页面的交互,业务逻辑都是调用服务层的服务来实现。SOA架构中有两个主要角色:服务提供者(Provider)和服务使用者(Consumer)。

Dubbo的架构

节点简单说明:

  • Provider: 暴露服务的服务提供方
  • Consumer: 调用远程服务的服务消费方
  • Registry: 服务注册与发现的注册中心
  • Monitor: 统计服务的调用次数和调用时间的监控中心
  • Container: 服务运行容器

调用关系说明:

  • 服务容器负责启动,加载,运行服务提供者。
  • 服务提供者在启动时,向注册中心注册自己提供的服务。
  • 服务消费者在启动时,向注册中心订阅自己所需的服务。
  • 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
  • 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
  • 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

重要知识点总结:

  • 注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小
  • 监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示
  • 注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外
  • 注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者
  • 注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表
  • 注册中心和监控中心都是可选的,服务消费者可以直连服务提供者
  • 服务提供者无状态,任意一台宕掉后,不影响使用
  • 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复

从下至上分为十层,各层均为单向依赖,右边的黑色箭头代表层之间的依赖关系,每一层都可以剥离上层被复用,其中,Service 和 Config 层为 API,其它各层均为 SPI。

各层说明:

  • 第一层:service层,接口层,给服务提供者和消费者来实现的
  • 第二层:config层,配置层,主要是对dubbo进行各种配置的
  • 第三层:proxy层,服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton
  • 第四层:registry层,服务注册层,负责服务的注册与发现
  • 第五层:cluster层,集群层,封装多个服务提供者的路由以及负载均衡,将多个实例组合成一个服务
  • 第六层:monitor层,监控层,对rpc接口的调用次数和调用时间进行监控
  • 第七层:protocol层,远程调用层,封装rpc调用
  • 第八层:exchange层,信息交换层,封装请求响应模式,同步转异步
  • 第九层:transport层,网络传输层,抽象mina和netty为统一接口
  • 第十层:serialize层,数据序列化层,网络传输需要

Dubbo 提供的负载均衡策略

  • Random LoadBalance:默认,基于权重的随机负载均衡机制
  • RoundRobin LoadBalance:不推荐,基于权重的轮询负载均衡机制
  • LeastActive LoadBalance:最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
  • ConsistentHash LoadBalance:一致性 Hash,相同参数的请求总是发到同一提供者。

zookeeper宕机与dubbo直连的情况

在实际生产中,假如zookeeper注册中心宕掉,一段时间内服务消费方还是能够调用提供方的服务的,实际上它使用的本地缓存进行通讯,这只是dubbo健壮性的一种体现。

dubbo的健壮性表现:

  • 监控中心宕掉不影响使用,只是丢失部分采样数据
  • 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
  • 注册中心对等集群,任意一台宕掉后,将自动切换到另一台
  • 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
  • 服务提供者无状态,任意一台宕掉后,不影响使用
  • 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复

我们前面提到过:注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小。所以,我们可以完全可以绕过注册中心——采用 dubbo 直连 ,即在服务消费方配置服务提供方的位置信息。

Thrift

Thrift RPC详解

Thrift RPC详解

为了实现RPC步骤,许多RPC工具被研发出来。这些RPC工具大多使用“接口描述语言” —— interface description language (IDL) 来提供跨平台跨语言的服务调用。现在生产中用的最多的IDL是Google开源的protobuf。

在日常开发中通常有两种形式来使用RPC,一种是团队内部完全实现上述RPC的6个步骤,自己序列化数据,然后自己利用socket或者http传输数据,最常见的就是游戏开发了。另一种就是利用现成的RPC工具,这些RPC工具实现了底层的数据通信,开发人员只需要利用IDL定义实现自己的服务即可而不用关心数据是如何通信的,最常见的RPC工具是Facebook开源的Thrift RPC框架。本文将重点讲解Thrift RPC。

Thrift实际上是实现了C/S模式,通过代码生成工具将接口定义文件生成服务器端和客户端代码(可以为不同语言),从而实现服务端和客户端跨语言的支持。用户在Thirft描述文件中声明自己的服务,这些服务经过编译后会生成相应语言的代码文件,然后用户实现服务(客户端调用服务,服务器端提服务)便可以了。其中protocol(协议层, 定义数据传输格式,可以为二进制或者XML等)和transport(传输层,定义数据传输方式,可以为TCP/IP传输,内存共享或者文件共享等)被用作运行时库。

Thrift的协议栈如下图所示:

![img](https://upload-images.jianshu.io/upload_images/6302559-75e0caadd951d14d.png?imageMogr2/auto-orient/strip imageView2/2/w/424/format/webp)

在Client和Server的最顶层都是用户自定义的处理逻辑,也就是说用户只需要编写用户逻辑,就可以完成整套的RPC调用流程。用户逻辑的下一层是Thrift自动生成的代码,这些代码主要用于结构化数据的解析,发送和接收,同时服务器端的自动生成代码中还包含了RPC请求的转发(Client的A调用转发到Server A函数进行处理)。

协议栈的其他模块都是Thrift的运行时模块:

  • 底层IO模块,负责实际的数据传输,包括Socket,文件,或者压缩数据流等。

  • TTransport负责以字节流方式发送和接收Message,是底层IO模块在Thrift框架中的实现,每一个底层IO模块都会有一个对应TTransport来负责Thrift的字节流(Byte Stream)数据在该IO模块上的传输。例如TSocket对应Socket传输,TFileTransport对应文件传输。

  • TProtocol主要负责结构化数据组装成Message,或者从Message结构中读出结构化数据。TProtocol将一个有类型的数据转化为字节流以交给TTransport进行传输,或者从TTransport中读取一定长度的字节数据转化为特定类型的数据。如int32会被TBinaryProtocol Encode为一个四字节的字节数据,或者TBinaryProtocol从TTransport中取出四个字节的数据Decode为int32。

  • TServer负责接收Client的请求,并将请求转发到Processor进行处理。TServer主要任务就是高效的接受Client的请求,特别是在高并发请求的情况下快速完成请求。

  • Processor(或者TProcessor)负责对Client的请求做出相应,包括RPC请求转发,调用参数解析和用户逻辑调用,返回值写回等处理步骤。Processor是服务器端从Thrift框架转入用户逻辑的关键流程。Processor同时也负责向Message结构中写入数据或者读出数据。

利用Thrift用户只需要做三件事:

(1). 利用IDL定义数据结构及服务 (2). 利用代码生成工具将(1)中的IDL编译成对应语言(如C++、JAVA),编译后得到基本的框架代码 (3). 在(2)中框架代码基础上完成完整代码(纯C++代码、JAVA代码等)

数据类型

  • Base Types(基本类型)
  • Structs(结构体)
  • enum(枚举)
  • Containers(容器)
  • Exceptions(异常)
  • Services(服务)

protobuf为什么快

protobuf为什么那么快

每个字段我们都用tag value的方式来存储的,在tag当中记录两种信息,一个是value对应的字段的编号,另一个是value的数据类型(比如是整形还是字符串等),因为tag中有字段编号信息,所以即使没有传递height字段的value值,根据编号也能正确的配对。

protobuf当中定义了Varint这种数据类型,可以以不同的长度来存储整数,将数据进一步的进行了压缩。

为了能够快速解析字符串类型的数据,protobuf在存储的时候,做了特殊的处理,分成了三部分:tag leg value,其中的leg记录了字符串的长度,同样使用了varint来存储,一般一个字节就能搞定,然后程序从leg后截取leg个字节的数据作为value,解析速度非常快。

分布式id

分布式id生成方案总结

  • UUID:不适合作为主键,因为太长了,并且无序不可读,查询效率低。比较适合用于生成唯一的名字的标示比如文件的名字。
  • 数据库自增 id:业务系统每次需要一个ID时,都需要请求数据库获取,性能低,并且如果此数据库实例下线了,那么将影响所有的业务系统。
  • 数据库多主模式:两台数据库分别设置不同起始值和相同步长,生成不重复ID的策略来实现高可用。问题是无法新增实例。
  • 利用redis生成id
  • 号端模式

    1
    2
    3
    4
    5
    6
    
    CREATE TABLE id_generator (
      id int(10) NOT NULL,
      current_max_id bigint(20) NOT NULL COMMENT '当前最大id',
      increment_step int(10) NOT NULL COMMENT '号段的长度',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    1
    
    update id_generator set current_max_id=#{newMaxId}, version=version+1 where version = #{version}
    
  • twitter snowflake雪花算法:时间戳、工作机器id、序列号
  • 美团Leaf

限流

限流的算法有哪些?

  • 固定窗口计数器算法:规定我们单位时间处理的请求数量。
  • 滑动窗口计数器算法:例如我们的接口限流每分钟处理60个请求,我们可以把 1 分钟分为60个窗口。每隔1秒移动一次, 如果当前窗口的请求计数总和超过了限制的数量的话就不再处理其他请求。
  • 漏桶算法:往桶中以任意速率流入水,以一定速率流出水。当水超过桶流量则丢弃,因为桶容量是不变的,保证了整体的速率。
  • 令牌桶算法:首先我们会有个桶,如果里面没有满那么就会以一定 固定的速率 会往里面放令牌,一个请求过来首先要从桶中获取令牌,如果没有获取到,那么这个请求就拒绝,如果获取到那么就放行。

Databus

读数据:读缓存,读数据库,写缓存

写数据:淘汰缓存,写数据库,(写缓存)

问题:写数据库后,写缓存失败怎么办?读数据后写缓存时,数据库又更新了怎么办?

解决:Databus,强一致协议(比如两阶段提交,paxos等)

秒杀、超卖

  • 秒杀活动的技术挑战

    • 对现有网站业务造成冲击
    • 高并发下的应用、数据库负载
    • 突然增加的网络及服务器带宽
    • 得到下单URL可以直接下单
  • 秒杀系统的应对策略

    • 秒杀系统独立部署
    • 秒杀商品页面静态化
    • 租借秒杀活动网络带宽
    • 动态生成随机下单页面URL
  • 秒杀系统架构设计

    • 如何控制秒杀商品页面购买按钮的点亮

      刷新秒杀商品页面从CDN服务器获取静态页面,在静态页面中加入一个JavaScript的文件引用。每次刷新秒杀商品页面都从JavaScript服务器集群加载一次JavaScript文件,该JavaScript文件中加入秒杀是否开始的标志和下单页面URL的随机数参数。秒杀开始前定时任务服务器生成新的JavaScript文件并推送到JavaScript服务器。

    • 如何只允许第一个提交的订单被发送到订单子系统

      为了减轻下单页面服务器的负载压力,可以控制进入下单页面的入口,只有少数用户能进入下单页面,其他用户直接进入秒杀结束页面。假设下单服务器集群有10台服务器,每台服务器只接受最多10个下单请求。活动开始用户点击购买按钮,请求发送至下单服务器,下单服务器检查本机已处理下单请求数目,如果已经超过10,直接显示秒杀活动已结束页面;如果未超过10,用户填写订单点击提交按钮,检查全局已提交订单数目,如果已超过商品数目,显示秒杀活动已结束页面,否则提交到订单子系统。

  • 超卖

    • 单点用线程锁,集群用分布式锁
    • 库存放在Redis里,秒杀结束再异步修改数据库中的库存
    • 请求放到Redis队列或者放到消息队列中,再利用一个线程去执行秒杀任务

[大型网站技术架构]

  • 秒杀活动的技术挑战

    • 对现有网站业务造成冲击
    • 高并发下的应用、数据库负载
    • 突然增加的网络及服务器带宽
    • 得到下单URL可以直接下单
  • 秒杀系统的应对策略

    • 秒杀系统独立部署
    • 秒杀商品页面静态化
    • 租借秒杀活动网络带宽
    • 动态生成随机下单页面URL
  • 秒杀系统架构设计

    • 如何控制秒杀商品页面购买按钮的点亮

      刷新秒杀商品页面从CDN服务器获取静态页面,在静态页面中加入一个JavaScript的文件引用。每次刷新秒杀商品页面都从JavaScript服务器集群加载一次JavaScript文件,该JavaScript文件中加入秒杀是否开始的标志和下单页面URL的随机数参数。秒杀开始前定时任务服务器生成新的JavaScript文件并推送到JavaScript服务器。

    • 如何只允许第一个提交的订单被发送到订单子系统

      为了减轻下单页面服务器的负载压力,可以控制进入下单页面的入口,只有少数用户能进入下单页面,其他用户直接进入秒杀结束页面。假设下单服务器集群有10台服务器,每台服务器只接受最多10个下单请求。活动开始用户点击购买按钮,请求发送至下单服务器,下单服务器检查本机已处理下单请求数目,如果已经超过10,直接显示秒杀活动已结束页面;如果未超过10,用户填写订单点击提交按钮,检查全局已提交订单数目,如果已超过商品数目,显示秒杀活动已结束页面,否则提交到订单子系统。

秒杀业务/超卖的几种解决思路

  • 秒杀背景分析

    • 高并发
    • 超卖
    • 恶意请求
    • 链接暴露
    • 数据库宕机
  • 问题逐个击破

    • 服务单一职责原则
    • 秒杀链接加盐
    • Redis集群/库存预热
    • Nginx
    • 资源静态化
    • 按钮控制
    • 限流&降级&熔断&隔离
    • 削峰填谷
  • 针对超卖问题的几种解决思路

    1. 加锁

      (1) 单个IIS(即单进程),通过在代码里lock/monitor线程锁即可。

      (2) 如果是IIS集群,多个秒杀集群同时请求DB,那么lock线程锁就锁不住了,这里就需要引入Redis分布式锁,所有请求先去Redis加锁→秒杀→解锁,实际上多个IIS请求最终到数据库 依次先后进行的。

    2. 库存放redis里

      思路:我们要开始秒杀前你通过定时任务或者运维同学提前把商品的库存加载到Redis中去,让整个流程都在Redis里面去做,然后等秒杀介绍了,再异步的去修改库存就好了。

      分析:

      (1) 单体Redis没问题,提前把商品的库存加载到Redis中去,让整个流程都在Redis里面去做(利用Redis单线程),然后等秒杀介绍了,再异步的去修改库存就好了。

      (2) 集群Redis乍一看有问题(比如1主多从)。

      比如现在库存只剩下1个了,我们高并发嘛,4个redis服务器一起查询了发现都是还有1个,那大家都觉得是自己抢到了,就都去扣库存,那结果就变成了-3。

      我们可以这样解决(比较高级):

      Lua脚本是类似Redis事务,有一定的原子性,不会被其他命令插队,可以完成一些Redis事务性的操作。

    1
    
    Lua 脚本功能是 Reids在 2.6 版本的最大亮点, 通过内嵌对 Lua 环境的支持, Redis 解决了长久以来不能高效地处理 CAS (check-and-set)命令的缺点, 并且可以通过组合使用多个命令, 轻松实现以前很难实现或者不能高效实现的模式。
    
    1. 请求消息队列里

    (1) 单体或集群Redis

    把所有的请求放到Redis队列中,然后开启一个线程去出队执行秒杀业务(秒杀业务执行完,下一个才出队),实际上到数据库还是 一个接着一个进行。(仅这一种场景可以)

    注意:

    A. 如果是开多个线程出队执行秒杀业务, 或者 即使单线程出队,然后秒杀 ,出队和秒杀是分开执行, 同样会存在上一个秒杀业务没有执行完,下一个又进来了。

    B. 如果秒杀业务是集群,即使是依次出队,打到DB上还是会存在上述问题,且没法保证原先队列中的先后顺序了。

    (2) 据说小米的解决方案:(不立即操作)

    大家先发起抢的请求,服务器端把请求按先到先存规则放进消息队列,当消息队列到了最大值就说明是理论上的抢完了, 这时再处理消息队列生成订单, 大家过几分钟就能发现有没有抢得到, 抢到的让他们继续付款。

  • 学妹的整理

    秒杀问题(超卖问题)

    2020-7-7 李鹏程分享

    https://km.sankuai.com/page/330500196

    关于超卖

    在之前面试阿里的时候,面试官问我关于促销活动等。这种情况,对于商品应该使用什么锁,他告诉我应该用悲观锁,因为卖出的不能超过库存,但是为啥这里的解决方案是乐观锁呢?

    answer:这个问题可以通过悲观锁解决也可以通过乐观锁解决,具体参考↓(这三个讲的基本上差不多),lpc上面的文档里也写到了乐观锁的具体的sql解决方案

    https://blog.csdn.net/glamour2015/article/details/105179738/

    https://hacpai.com/article/1536335417613

    https://www.jianshu.com/p/39b3a95240c4