tech
Netty Mina
-
高性能和高可伸缩性网络应用程序的网络应用框架
-
传统io缺点
- NIO的类库和API还是有点复杂,比如Buffer的使用
- Selector编写复杂,如果对某个事件注册后,业务代码过于耦合
- 需要了解很多多线程的知识,熟悉网络编程
- 面对断连重连、保丢失、粘包等,处理复杂
- NIO存在BUG,根据网上言论说是selector空轮训导致CPU飙升
-
创建步骤
-
即包含一个接收连接的线程池(也有可能是单个线程,boss线程池)以及一个处理连接的线程池(worker线程池)。boss负责接收连接,并进行IO监听;worker负责后续的处理。
-
流程
- 1、创建两个NIO线程组,一个专门用来网络事件处理(接受客户端连接),另一个则进行网络通讯读写
- 2、创建一个ServerBootstrap对象,配置Netty的一系列参数,例如接受传入数据的缓存大小等。
- 3、创建一个实际处理数据的类ChannelInitializer,进行初始化的准备工作,比如设置传入数据的字符集,格式,实现实际处理数据的接口。
- 4、绑定端口,执行同步阻塞方法等待服务器启动即可。
-
-
-
通讯方式
-
使用长连接通道不断开的形式进行通信,也就是服务器和客户端的通道一直处于开启状态,如果服务器的性能比较好,而且客户端的数量也不多的情况下,可以考虑这种方式
-
一次性批量提交数据,采用短连接的方式,也就是我们把数据保存在本地临时缓冲区或者临时表中,
当达到临界值时进行一次性批量提交,又或者根据定时任务轮询提交,这种情况下弊端是做不到
实时性传输,在实时性要求不高的程序中可以采用 -
采用一种特殊的长连接,在指定某一段时间之内,服务端和某台客户端没有任何通讯,则断开连接,下次如果客户端要向服务端发送数据时,再次建立连接。
-
-
组件
-
Bootstrap:netty的组件容器,用于把其他各个部分连接起来;如果是TCP的Server端,则为ServerBootstrap.
-
Channel:代表一个Socket的连接
-
EventLoopGroup:一个Group包含多个EventLoop,可以理解为线程池
-
EventLoop:处理具体的Channel,一个EventLoop可以处理多个Channel
-
ChannelPipeline:每个Channel绑定一个pipeline,在上面注册处理逻辑handler
-
Handler:具体的对消息或连接的处理,有两种类型,Inbound和Outbound。分别代表消息接收的处理和消息发送的处理。
- 当接收消息的时候,会从链表的表头开始遍历,如果是inbound就调用对应的方法;如果发送消息则从链表的尾巴开始遍历
-
ChannelFuture:注解回调方法
-
-
dubbo
-
概述
-
流程图
- 图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口。
- 图中从下至上分为十层,各层均为单向依赖,右边的黑色箭头代表层之间的依赖关系,每一层都可以剥离上层被复用,其中,Service 和 Config 层为 API,其它各层均为 SPI。
- 图中绿色小块的为扩展接口,蓝色小块为实现类,图中只显示用于关联各层的实现类。
- 图中蓝色虚线为初始化过程,即启动时组装链,红色实线为方法调用过程,即运行时调时链,紫色三角箭头为继承,可以把子类看作父类的同一个节点,线上的文字为调用的方法。
-
-
各层说明
- config 配置层:对外配置接口,以 ServiceConfig, ReferenceConfig 为中心,可以直接初始化配置类,也可以通过 spring 解析配置生成配置类
- proxy 服务代理层:服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton, 以 ServiceProxy 为中心,扩展接口为 ProxyFactory
- registry 注册中心层:封装服务地址的注册与发现,以服务 URL 为中心,扩展接口为 RegistryFactory, Registry, RegistryService
- cluster 路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以 Invoker 为中心,扩展接口为 Cluster, Directory, Router, LoadBalance
- monitor 监控层:RPC 调用次数和调用时间监控,以 Statistics 为中心,扩展接口为 MonitorFactory, Monitor, MonitorService
- protocol 远程调用层:封装 RPC 调用,以 Invocation, Result 为中心,扩展接口为 Protocol, Invoker, Exporter
- exchange 信息交换层:封装请求响应模式,同步转异步,以 Request, Response 为中心,扩展接口为 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer
- transport 网络传输层:抽象 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为 Channel, Transporter, Client, Server, Codec
- serialize 数据序列化层:可复用的一些工具,扩展接口为 Serialization, ObjectInput, ObjectOutput, ThreadPool
-
关系说明
- 在 RPC 中,Protocol 是核心层,也就是只要有 Protocol + Invoker + Exporter 就可以完成非透明的 RPC 调用,然后在 Invoker 的主过程上 Filter 拦截点。
- 图中的 Consumer 和 Provider 是抽象概念,只是想让看图者更直观的了解哪些类分属于客户端与服务器端,不用 Client 和 Server 的原因是 Dubbo 在很多场景下都使用 Provider, Consumer, Registry, Monitor 划分逻辑拓普节点,保持统一概念。
- 而 Cluster 是外围概念,所以 Cluster 的目的是将多个 Invoker 伪装成一个 Invoker,这样其它人只要关注 Protocol 层 Invoker 即可,加上 Cluster 或者去掉 Cluster 对其它层都不会造成影响,因为只有一个提供者时,是不需要 Cluster 的。
- Proxy 层封装了所有接口的透明化代理,而在其它层都以 Invoker 为中心,只有到了暴露给用户使用时,才用 Proxy 将 Invoker 转成接口,或将接口实现转成 Invoker,也就是去掉 Proxy 层 RPC 是可以 Run 的,只是不那么透明,不那么看起来像调本地服务一样调远程服务。
- 而 Remoting 实现是 Dubbo 协议的实现,如果你选择 RMI 协议,整个 Remoting 都不会用上,Remoting 内部再划为 Transport 传输层和 Exchange 信息交换层,Transport 层只负责单向消息传输,是对 Mina, Netty, Grizzly 的抽象,它也可以扩展 UDP 传输,而 Exchange 层是在传输层之上封装了 Request-Response 语义。
- Registry 和 Monitor 实际上不算一层,而是一个独立的节点,只是为了全局概览,用层的方式画在一起。
-
模块分包
-
结构
-
dubbo-common 公共逻辑模块:包括 Util 类和通用模型。
-
dubbo-remoting 远程通讯模块:相当于 Dubbo 协议的实现,如果 RPC 用 RMI协议则不需要使用此包。
-
dubbo-rpc 远程调用模块:抽象各种协议,以及动态代理,只包含一对一的调用,不关心集群的管理。
-
dubbo-cluster 集群模块:将多个服务提供方伪装为一个提供方,包括:负载均衡, 容错,路由等,集群的地址列表可以是静态配置的,也可以是由注册中心下发。
-
dubbo-registry 注册中心模块:基于注册中心下发地址的集群方式,以及对各种注册中心的抽象。
-
dubbo-monitor 监控模块:统计服务调用次数,调用时间的,调用链跟踪的服务。
-
dubbo-config 配置模块:是 Dubbo 对外的 API,用户通过 Config 使用D ubbo,隐藏 Dubbo 所有细节。
-
dubbo-container 容器模块:是一个 Standlone 的容器,以简单的 Main 加载 Spring 启动,因为服务通常不需要 Tomcat/JBoss 等 Web 容器的特性,没必要用 Web 容器去加载服务。
-
整体上按照分层结构进行分包,与分层的不同点在于:
- container 为服务容器,用于部署运行服务,没有在层中画出。
- protocol 层和 proxy 层都放在 rpc 模块中,这两层是 rpc 的核心,在不需要集群也就是只有一个提供者时,可以只使用这两层完成 rpc 调用。
- transport 层和 exchange 层都放在 remoting 模块中,为 rpc 调用的通讯基础。
- serialize 层放在 common 模块中,以便更大程度复用。
-
-
依赖关系
-
示意图
- 图中小方块 Protocol, Cluster, Proxy, Service, Container, Registry, Monitor 代表层或模块,蓝色的表示与业务有交互,绿色的表示只对 Dubbo 内部交互。
- 图中背景方块 Consumer, Provider, Registry, Monitor 代表部署逻辑拓扑节点。
- 图中蓝色虚线为初始化时调用,红色虚线为运行时异步调用,红色实线为运行时同步调用。
- 图中只包含 RPC 的层,不包含 Remoting 的层,Remoting 整体都隐含在 Protocol 中。
-
-
调用链
- 示意图
-
http://dubbo.apache.org/zh-cn/docs/dev/design.html
thrift是Apache的一个跨语言的高性能的服务框架
服务治理
lucene、Solr、ElasticSearch
Spring Quartz xxlJob
kafka
-
学习网站
- http://orchome.com/kafka/index
-
优势
- kafka是用于构建实时数据管道和流应用程序。具有横向扩展,容错,wicked fast(变态快)等优点,并已在成千上万家公司运行。
-
链接:http://orchome.com/5
-
我们认为,一个流处理平台具有三个关键能力:
-
发布和订阅消息(流),在这方面,它类似于一个消息队列或企业消息系统。
-
Kafka的流与传统企业消息系统相比的概念如何?
- 传统的消息有两种模式:队列和发布订阅。 在队列模式中,消费者池从服务器读取消息(每个消息只被其中一个读取); 发布订阅模式:消息广播给所有的消费者。这两种模式都有优缺点,队列的优点是允许多个消费者瓜分处理数据,这样可以扩展处理。但是,队列不像多个订阅者,一旦消息者进程读取后故障了,那么消息就丢了。而发布和订阅允许你广播数据到多个消费者,由于每个订阅者都订阅了消息,所以没办法缩放处理。
- kafka中消费者组有两个概念:队列:消费者组(consumer group)允许同名的消费者组成员瓜分处理。发布订阅:允许你广播消息给多个消费者组(不同名)。
- kafka的每个topic都具有这两种模式。
-
kafka有比传统的消息系统更强的顺序保证。
- 传统的消息系统按顺序保存数据,如果多个消费者从队列消费,则服务器按存储的顺序发送消息,但是,尽管服务器按顺序发送,消息异步传递到消费者,因此消息可能乱序到达消费者。这意味着消息存在并行消费的情况,顺序就无法保证。消息系统常常通过仅设1个消费者来解决这个问题,但是这意味着没用到并行处理。
- kafka做的更好。通过并行topic的parition —— kafka提供了顺序保证和负载均衡。每个partition仅由同一个消费者组中的一个消费者消费到。并确保消费者是该partition的唯一消费者,并按顺序消费数据。每个topic有多个分区,则需要对多个消费者做负载均衡,但请注意,相同的消费者组中不能有比分区更多的消费者,否则多出的消费者一直处于空等待,不会收到消息。
-
-
以容错的方式存储消息(流)。
- 所有发布消息到消息队列和消费分离的系统,实际上都充当了一个存储系统(发布的消息先存储起来)。Kafka比别的系统的优势是它是一个非常高性能的存储系统。
- 写入到kafka的数据将写到磁盘并复制到集群中保证容错性。并允许生产者等待消息应答,直到消息完全写入。
- kafka的磁盘结构 - 无论你服务器上有50KB或50TB,执行是相同的。
- client来控制读取数据的位置。你还可以认为kafka是一种专用于高性能,低延迟,提交日志存储,复制,和传播特殊用途的分布式文件系统。
-
在消息流发生时处理它们。
- 仅仅读,写和存储是不够的,kafka的目标是实时的流处理。
- 在kafka中,流处理持续获取输入topic的数据,进行处理加工,然后写入输出topic。例如,一个零售APP,接收销售和出货的输入流,统计数量或调整价格后输出。
- 可以直接使用producer和consumer API进行简单的处理。对于复杂的转换,Kafka提供了更强大的Streams API。可构建聚合计算或连接流到一起的复杂应用程序。
- 助于解决此类应用面临的硬性问题:处理无序的数据,代码更改的再处理,执行状态计算等。
- Sterams API在Kafka中的核心:使用producer和consumer API作为输入,利用Kafka做状态存储,使用相同的组机制在stream处理器实例之间进行容错保障。
-
-
什么是kakfa的优势?
-
它应用于2大类应用:
- 构建实时的流数据管道,可靠地获取系统和应用程序之间的数据。
- 构建实时流的应用程序,对数据流进行转换或反应。
-
要了解kafka是如何做这些事情的,让我们从下到上深入探讨kafka的能力。
-
首先几个概念:
- kafka作为一个集群运行在一个或多个服务器上。
- kafka集群存储的消息是以topic为类别记录的。
- 每个消息(也叫记录record,我习惯叫消息)是由一个key,一个value和时间戳构成。
-
kafka有四个核心API:
- 应用程序使用 Producer API 发布消息到1个或多个topic(主题)。
- 应用程序使用 Consumer API 来订阅一个或多个topic,并处理产生的消息。
- 应用程序使用 Streams API 充当一个流处理器,从1个或多个topic消费输入流,并生产一个输出流到1个或多个输出topic,有效地将输入流转换到输出流。
- Connector API允许构建或运行可重复使用的生产者或消费者,将topic连接到现有的应用程序或数据系统。例如,一个关系数据库的连接器可捕获每一个变化。
-
协议
-
tcp
- 简单,高性能,和开发协议无关
-
-
概念
-
topic
-
topic1-part1
-
topic
- 主题名称
-
partion
- 分区编号
-
leader
- 当前分区负责读写的节点
-
replicas
- 分区的复制节点,与主题的副本数量有关
-
isr
- 同步状态中的副本,是replicas的子集,必须是存活的,并且都能赶上主副本
-
-
topic1-part2
-
topic1-part3
-
topic1-part4
-
topic2-part1
-
topic2-part2
-
-
broker
-
setver1
-
topic1-part1
-
topic1-part2
- leader
-
topic1-part3
-
topic1-part4
- leader
-
-
server2
-
topic1-part1
- leader
-
topic1-part2
-
topic1-part3
- leader
-
topic1-part4
-
-
-
consumer
-
通常来讲,消息模型可以分为两种, 队列和发布-订阅式。 队列的处理方式是 一组消费者从服务器读取消息,一条消息只有其中的一个消费者来处理。在发布-订阅模型中,消息被广播给所有的消费者,接收到消息的消费者都可以处理此消息。Kafka为这两种模型提供了单一的消费者抽象模型: 消费者组 (consumer group)。 消费者用一个消费者组名标记自己。 一个发布在Topic上消息被分发给此消费者组中的一个消费者。 假如所有的消费者都在一个组中,那么这就变成了queue模型。 假如所有的消费者都在不同的组中,那么就完全变成了发布-订阅模型。 更通用的, 我们可以创建一些消费者组作为逻辑上的订阅者。每个组包含数目不等的消费者, 一个组内多个消费者可以用来扩展性能和容错。正如下图所示:
- 2个kafka集群托管4个分区(P0-P3),2个消费者组,消费组A有2个消费者实例,消费组B有4个。
- 正像传统的消息系统一样,Kafka保证消息的顺序不变。 再详细扯几句。传统的队列模型保持消息,并且保证它们的先后顺序不变。但是, 尽管服务器保证了消息的顺序,消息还是异步的发送给各个消费者,消费者收到消息的先后顺序不能保证了。这也意味着并行消费将不能保证消息的先后顺序。用过传统的消息系统的同学肯定清楚,消息的顺序处理很让人头痛。如果只让一个消费者处理消息,又违背了并行处理的初衷。 在这一点上Kafka做的更好,尽管并没有完全解决上述问题。 Kafka采用了一种分而治之的策略:分区。 因为Topic分区中消息只能由消费者组中的唯一一个消费者处理,所以消息肯定是按照先后顺序进行处理的。但是它也仅仅是保证Topic的一个分区顺序处理,不能保证跨分区的消息先后处理顺序。 所以,如果你想要顺序的处理Topic的所有消息,那就只提供一个分区。
-
-
producer
- 生产者往某个Topic上发布消息。生产者也负责选择发布到Topic上的哪一个分区。最简单的方式从分区列表中轮流选择。也可以根据某种算法依照权重选择分区。开发者负责如何选择分区的算法。
-
分布式
- Log的分区被分布到集群中的多个服务器上。每个服务器处理它分到的分区。 根据配置每个分区还可以复制到其它服务器作为备份容错。 每个分区有一个leader,零或多个follower。Leader处理此分区的所有的读写请求,而follower被动的复制数据。如果leader宕机,其它的一个follower会被推举为新的leader。 一台服务器可能同时是一个分区的leader,另一个分区的follower。 这样可以平衡负载,避免所有的请求都只让一台或者某几台服务器处理。
-
-
Kafka的保证(Guarantees)
- 生产者发送到一个特定的Topic的分区上,消息将会按照它们发送的顺序依次加入,也就是说,如果一个消息M1和M2使用相同的producer发送,M1先发送,那么M1将比M2的offset低,并且优先的出现在日志中。
- 消费者收到的消息也是此顺序。
- 如果一个Topic配置了复制因子(replication factor)为N, 那么可以允许N-1服务器宕机而不丢失任何已经提交(committed)的消息。
-
-
使用场景
-
消息
- kafka更好的替换传统的消息系统,消息系统被用于各种场景(解耦数据生产者,缓存未处理的消息,等),与大多数消息系统比较,kafka有更好的吞吐量,内置分区,副本和故障转移,这有利于处理大规模的消息。
- 根据我们的经验,消息往往用于较低的吞吐量,但需要低的端到端延迟,并需要提供强大的耐用性的保证。
- 在这一领域的kafka比得上传统的消息系统,如的ActiveMQ或RabbitMQ的。
-
网站活动追踪
- kafka原本的使用场景:用户的活动追踪,网站的活动(网页游览,搜索或其他用户的操作信息)发布到不同的话题中心,这些消息可实时处理,实时监测,也可加载到Hadoop或离线处理数据仓库。
- 每个用户页面视图都会产生非常高的量。
-
指标
- kafka也常常用于监测数据。分布式应用程序生成的统计数据集中聚合。
-
日志聚合
- 使用kafka代替一个日志聚合的解决方案。
-
流处理
- kafka消息处理包含多个阶段。其中原始输入数据是从kafka主题消费的,然后汇总,丰富,或者以其他的方式处理转化为新主题,例如,一个推荐新闻文章,文章内容可能从“articles”主题获取;然后进一步处理内容,得到一个处理后的新内容,最后推荐给用户。这种处理是基于单个主题的实时数据流。从0.10.0.0开始,轻量,但功能强大的流处理,就进行这样的数据处理了。
- 除了Kafka Streams,还有Apache Storm和Apache Samza可选择。
-
事件采集
- 事件采集是一种应用程序的设计风格,其中状态的变化根据时间的顺序记录下来,kafka支持这种非常大的存储日志数据的场景。
-
提交日志
- kafka可以作为一种分布式的外部提交日志,日志帮助节点之间复制数据,并作为失败的节点来恢复数据重新同步,kafka的日志压缩功能很好的支持这种用法,这种用法类似于Apacha BookKeeper项目
-
-
设计
-
持久
-
文件系统
-
储存结构
- 目的:提高磁盘利用率和消息处理性能。
-
- 在kafka文件系统中,同一个topic下有多个不同partition,每个partition创建一个目录。即topic下有分区的子目录。
-
- 每个partion相当于一个巨型文件被平均分配到多个大小相等的多个segment(段)文件中。但每个段segment file消息数量不一定相等,这种特性方便old segment file快速被删除。即分区目录下log文件大小一样。而且一个分区段文件(log文件)对应一个索引文件(index文件)
-
- 每个partiton只需要支持顺序读写就行了,segment文件生命周期由服务端配置参数决定。
-
- index为稀疏索引结构,并不存储每条记录的元数据信息
-
如何在partition中快速定位segment file
- 同一个topic下有不同分区,每个分区下面会划分为多个(段)文件,只有一个当前文件在写(一个分区对应一个消费者),其他文件只读。当写满一个文件(写满的意思是达到设定值)则切换文件,新建一个当前文件用来写,老的当前文件切换为只读。文件的命名以起始偏移量来命名。删除文件时,使用了写时复制技术。
- 当消费者要拉取某个消息起始偏移量位置的数据变的相当简单,只要根据传上来的offset⼆分查找文件列表,定位到具体文件,然后根据索引文件⼆分搜索,定位到index中的offset,读取log文件的偏移量,定位到log,即可开始传输数据。
-
高效文件系统特点
- 1.一个大文件分成多个小文件段。
- 2.多个小文件段,容易定时清除或删除已经消费完文件,减少磁盘占用
- 3.index,log全部映射到memory直接操作,使用零拷贝加页缓存技术,避免segment file被交换到磁盘增加IO操作次数。
- 4.根据索引元数据信息,可以确定consumer每次批量拉取最大msg chunk数量。
- 5.索引文件元数据存储用的是相对前个segment file的 offset存储,节省空间⼤小
- https://blog.csdn.net/u010159842/article/details/80192534
-
-
不要害怕文件系统
-
高效的文件系统存储结构可以和网速一样快
-
磁盘线性读取和随机读取性能差千倍
- 用内存做缓存减少差异
-
jvm内存
- 存对象消耗内存为本身大小两倍
- 堆数据增多以后,回收内存变得繁琐和缓慢
-
由于这些因素,使用文件系统并依赖pagecache(页缓存)将优于缓存在内存中或其他的结构 - 我们通过自动访问所有可用的内存将使得可用的内存至少提高一倍。并可能通过存储紧凑型字节结构再次提高一倍。这将使得32G机器上高达28-32GB的缓存,并无需GC。此外,即使服务重新启动,该缓存保持可用,而进程内的缓存则需要在内存中重建(10GB缓存需要10分钟),否则将需要启动完全冷却的缓存(这意味着可怕的初始化性能)。这也大大简化了代码,因为在缓存和文件系统之间维持的一致性的所有逻辑现在都在OS中,这比一次性进程更加有效和更正确。如果你的磁盘支持线性的读取,那么预读取将有效地将每个磁盘中有用的数据预填充此缓存。
-
这带来一个非常简单的设计:当内存空间耗尽时,将它全部flush到文件系统中,而不是尽可能把数据维持在内存中。我们反过来看,所有的数据直接写入到文件系统的持久化日志中,无需flush到磁盘上。实际上这只是意味着它被转移到内核的页缓存中。
-
常数时间
- 在消息系统中使用的持久数据结构常常具有相关联的BTree或其他通过随机访问数据结构的每个消费者队列,以维护关于消息的元数据。BTrees是可用的最通用的数据结构,可以在消息系统中支持各种各样的事务和非事务性语义。尽管,Btree的操作是O(log N),但它们的成本相当高。通常O(log N)O(log N)基本上等同于恒定时间,但是磁盘操作不是这样,磁盘寻找在10ms的pop,每个磁盘一次只能做一次寻找,所以并行性受限制。因此,即使是少量的磁盘搜索导致非常高的开销。由于存储系统将非常快速的缓存操作与非常慢的物理磁盘操作相结合,因为数据随固定缓存而增加,所以观察到的树结构的性能通常是超线性的。- 即,你的数据翻倍则使得事情慢两倍还多。
- 直观上,持久队列可以建立在简单的读取和附加到文件上,就像日志解决方案的情况一样。 这种结构的优点是所有操作都是O(1),并且读取不会阻塞写入或彼此。 这具有明显的性能优势,因为性能与数据大小完全分离 - 服务器现在可以充分利用这点,低转速 1+TB SATA驱动器。虽然这些驱动器的搜索性能不佳,但是对于大量的读写而言,这些驱动器具有可接受的性能,并且价格是1/3,能力为3倍。
-
-
-
效率
-
问题
- 大量的小io操作
- 过度的字节复制
-
方案
-
消息集合在一起,减少往返
-
生产者消费者共享一套二进制数据消息格式,避免转换
-
零拷贝
-
传统
- 操作系统将数据从磁盘读入到内核空间的页缓存
- 应用程序将数据从内核空间读入到用户空间缓存中
- 应用程序将数据写回到内核空间到socket缓存中
- 操作系统将数据从socket缓冲区复制到网卡缓冲区,以便将数据经网络发出
-
改进
- 这样做明显是低效的,这里有四次拷贝,两次系统调用。如果使用sendfile,再次拷贝可以被避免:允许操作系统将数据直接从页缓存发送到网络上。所以在这个优化的路径中,只有最后一步将数据拷贝到网卡缓存中是需要的
- 们假设一个topic有多个消费者的情况。 并使用上面的零拷贝优化,数据被复制到页缓存中一次,并在每个消费上重复使用,而不是存储在存储器中,也不在每次读取时复制到用户空间。 这使得以接近网络连接限制的速度消费消息
- 这种页缓存和sendfile组合,意味着Kafka集群的消费者大多数都完全从缓存消费消息,而磁盘没有任何读取活动。
-
-
端对端批量压缩
-
传统
- 直接压缩,压缩比低,消息冗余
-
解决
- 批量压缩,只由消费者解压缩
-
-
-
-
生产者
-
负载平衡
- 生产者将数据直接发送到分区leader的broker上(没有任何干预的路由层)。为了帮助producer做到这一点,Kafka所有节点都可应答给producer哪些服务器是正常的,哪些topic分区的leader允许producer在给定的时间内可以直接请求。
- 客户端控制消息发布到哪个parition,可以随机,实现一种的随机负载平衡,或者也可以通过语义分区函数,我们暴露接口,以允许用户通过key去指定分区和使用使用hash来指向分区(如果需要,可重写分区函数)。例如:如果选择的key是用户ID,那么对给定的用户ID的所有数据将被发送到相同分区。反过来,消费者有能指定消费那个分区,这种设计风格,让消费者可以对敏感性的消息进行局部处理。
-
异步发送
- 批处理是效率的一大驱动力,kafka生产者使用批处理试图在内存中积累数据,在单个请求发送累积的大批量数据,可以配置批处理积累的不大于一定的消息数,并等待时间不超过配置的延迟(64k 或 10毫秒)。这将累积更多消息 用于少数较大的I/O操作上,为了更好的吞吐量,这种缓存是可配置,并给出一种来权衡极少量的额外的延迟的机制。
-
-
消费者
-
kafka消费者通过向broker的leader分区发起“提取”请求。消费者指定每次请求日志的偏移量并收到那一块日志的起始位置。因此,消费者可以重新指定位置,重新消费。
-
push VS pull
-
push
- 最快保证数据传递,消费者可能来不及消费,造成拥堵
-
pull
-
适应消费者消费速率,可以使用批处理,增加传输效率,无信息拉取是会造成空轮训
- 使用long poll阻塞,并设置定时拉取时间
-
-
-
消费者定位
-
我们的topic被分为一组完全有序的分区,每个分区在任何给定的时间都由每个订阅消费者组中的一个消费者消费。 这意味着消费者在每个分区中的位置只是一个整数,下一个消息消费的偏移量。 这使得关于已消费到哪里的状态变得非常的小,每个分区只有一个数字。 可以定期检查此状态。 这使得等同于消息应答并更轻量。
- 消费者可以回到旧的偏移量重新消费
-
-
-
消息传递保障
-
消息传递保证
- at most once 最多一次
- at least once 至少一次
- exact once 刚好一次
-
Kafka生产者支持幂等传递选项,保证重新发送不会导致日志中重复。 broker为每个生产者分配一个ID,并通过生产者发送的序列号为每个消息进行去重。从0.11.0.0开始,生产者支持使用类似事务的语义将消息发送到多个topic分区的能力:即所有消息都被成功写入,或者没有。这个主要用于Kafka topic之间“正好一次“处理(如下所述)。
-
默认是保证“至少一次”传递,并允许用户通过禁止生产者重试和处理一批消息前提交它的偏移量来实现 “最多一次”传递。而“正好一次”传递需要与目标存储系统合作
-
当写入到外部系统时,需要将消费者的位置与实际存储为输出的位置进行协调。实现这一目标的典型方法是在消费者位置的存储和消费者输出的存储之间引入两阶段的”提交“。但是,这可以更简单,通过让消费者将其offset存储在与其输出相同的位置。这样最好,因为大多数的输出系统不支持两阶段”提交“。作为一个例子,考虑一个Kafka Connect连接器,它填充HDFS中的数据以及它读取的数据的offset,以保证数据和offset都被更新,或者都不更新。 对于需要这些更强大语义的许多其他数据系统,我们遵循类似的模式,并且消息不具有允许重复数据删除的主键。
-
-
副本和选举
-
follow上存储的数据
-
存活
-
和zk保持连接
-
不能落后太多
-
如果一个follower死掉,卡住,或落后,leader将从同步副本列表中移除它。落后是通过replica.lag.max.messages配置控制,卡住是通过replica.lag.time.max.ms配置控制的。
-
所有副本都死了
- 等待在ISR中的副本起死回生并选择该副本作为leader(希望它仍有所有数据)。
- 选择第一个副本 (不一定在 ISR),作为leader。
-
-
当该分区的所有同步副本已经写入到其日志中时漫该消息视为“已提交”。只有“已提交”的消息才会给到消费者
-
复制日志:Quorums,ISR,和状态机制
-
Quorum:法定人数,原指为了处理事务、拥有做出决定的权力而必须出席的众议员或参议员的数量(一般指半数以上)。
-
副本日志模拟了对一系列值顺序进入的过程(通常日志编号是 0,1,2,……)。有很多方法可以实现这一点,但最简单和最快的是leader提供选择的排序值,只要leader活着,所有的followers只需要复制和排序。
-
Leader选举
- 如果某个分区所在的服务器除了问题,不可用,kafka会从该分区的其他的副本中选择一个作为新的Leader。之后所有的读写就会转移到这个新的Leader上。现在的问题是应当选择哪个作为新的Leader。显然,只有那些跟Leader保持同步的Follower才应该被选作新的Leader。
- Kafka会在Zookeeper上针对每个Topic维护一个称为ISR(in-sync replica,已同步的副本)的集合,该集合中是一些分区的副本。只有当这些副本都跟Leader中的副本同步了之后,kafka才会认为消息已提交,并反馈给消息的生产者。如果这个集合有增减,kafka会更新zookeeper上的记录。
- 如果某个分区的Leader不可用,Kafka就会从ISR集合中选择一个副本作为新的Leader。
- 显然通过ISR,kafka需要的冗余度较低,可以容忍的失败数比较高。假设某个topic有f+1个副本,kafka可以容忍f个服务器不可用。
-
为什么不用少数服从多数的方法
- 少数服从多数是一种比较常见的一致性算法和Leader选举法。它的含义是只有超过半数的副本同步了,系统才会认为数据已同步;选择Leader时也是从超过半数的同步的副本中选择。这种算法需要较高的冗余度。譬如只允许一台机器失败,需要有三个副本;而如果只容忍两台机器失败,则需要五个副本。而kafka的ISR集合方法,分别只需要两个和三个副本。
-
如果所有的ISR副本都失败了怎么办
- 此时有两种方法可选,一种是等待ISR集合中的副本复活,一种是选择任何一个立即可用的副本,而这个副本不一定是在ISR集合中。这两种方法各有利弊,实际生产中按需选择。
- 如果要等待ISR副本复活,虽然可以保证一致性,但可能需要很长时间。而如果选择立即可用的副本,则很可能该副本并不一致。
-
-
-
日志压缩
-
nginx
- openresty
服务器
- docker tomcat jetty
中间件
-
Storm
-
nimbus(主节点) 监控节点运行,分配给从节点具体任务
-
Nimbus的主要工作是运行Storm拓扑。Nimbus分析拓扑并收集要执行的任务。然后,它将任务分配给可用的supervisor。Supervisor将有一个或多个工作进程。Supervisor将任务委派给工作进程。工作进程将根据需要产生尽可能多的执行器并运行任务。Apache Storm使用内部分布式消息传递系统来进行Nimbus和管理程序之间的通信。
- 子主题 1
-
-
supervisor(从节点) 拥有一个或者多个工作进程,将任务分配给工作进程
-
workerProgress(工作进程) 工作进程执行与特定拓扑相关的任务,创建特定执行器执行任务,一个工作进程拥有多个执行器
-
excutor(执行者) 工作进程产生的单个线程,运行一个或者多个任务,用于特定spout或者bolt
-
task(任务) 实际处理数据,为bolt或者spout
-
zk
-
其它
-
Topology拓扑
- Spout和Bolt构成
-
Tuple 元组
- 最小数据单元
-
Spout 流源
- 发出tuple
-
Bolts(逻辑处理单元)
- Bolts可以接收数据并发射到一个或多个Bolts
-
Stream
- 流是元组的无序序列。
-
子主题 6
- 子主题 1
-
子主题 7
-
-
-
zk
-
文件系统
-
实例
- 每个子目录项如 NameService 都被称作为 znode,这个 znode 是被它所在的路径唯一标识,如 Server1 这个 znode 的标识为 /NameService/Server1
- znode 可以有子节点目录,并且每个 znode 可以存储数据,注意 EPHEMERAL 类型的目录节点不能有子节点目录
- znode 是有版本的,每个 znode 中存储的数据可以有多个版本,也就是一个访问路径中可以存储多份数据
- znode 可以是临时节点,一旦创建这个 znode 的客户端与服务器失去联系,这个 znode 也将自动删除,Zookeeper 的客户端和服务器通信采用长连接方式,每个客户端和服务器通过心跳来保持连接,这个连接状态称为 session,如果 znode 是临时节点,这个 session 失效,znode 也就删除了
- znode 的目录名可以自动编号,如 App1 已经存在,再创建的话,将会自动命名为 App2
- znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个是 Zookeeper 的核心特性,Zookeeper 的很多功能都是基于这个特性实现的,后面在典型的应用场景中会有实例介绍
-
-
znode
- 持久,临时,是否顺序
-
通知机制
- 节点发生变化,会通知客户端
-
使用
-
命名服务
- 在zookeeper的文件系统里创建一个目录,即有唯一的path
-
配置管理
-
集群管理
-
分布式锁
- 保持独占(创建相同节点抢占)
- 控制时序(创建临时节点,编号最小获得锁)
-
队列管理
-
-
角色
-
client
-
leader(投票发起和决议,更新系统状态)
-
选主
-
轮询,获取最大zxid,超过半数投票为leadaer
-
为什么zookeeper集群的数目,一般为奇数个?
- •Leader选举算法采用了Paxos协议;
- •Paxos核心思想:当多数Server写成功,则任务数据写成功如果有3个Server,则两个写成功即可;如果有4或5个Server,则三个写成功即可。
- •Server数目一般为奇数(3、5、7)如果有3个Server,则最多允许1个Server挂掉;如果有4个Server,则同样最多允许1个Server挂掉由此,
- 我们看出3台服务器和4台服务器的的容灾能力是一样的,所以为了节省服务器资源,一般我们采用奇数个数,作为服务器部署个数。
-
-
选举机制
-
服务器ID
- 比如有三台服务器,编号分别是1,2,3。
-
-
-
-
-
编号越大在选择算法中的权重越大。
- 数据ID
- 服务器中存放的最大数据ID.
- 值越大说明数据越新,在选举算法中数据越新权重越大。
- 逻辑时钟
- 或者叫投票的次数,同一轮投票过程中的逻辑时钟值是相同的。每投完一次票这个数据就会增加,然后与接收到的其它服务器返回的投票信息中的数值相比,根据不同的值做出不同的判断。
- 选举状态
- LOOKING,竞选状态。
- FOLLOWING,随从状态,同步leader状态,参与投票。
- OBSERVING,观察状态,同步leader状态,不参与投票。
- LEADING,领导者状态。
- 选举消息内容
- 在投票完成后,需要将投票信息发送给集群中的所有服务器,它包含如下内容。
- 服务器ID
- 数据ID
- 逻辑时钟
- 选举状态
- 流程
- 总流程
- 状态变化,描述Leader选择过程中的状态变化,这是假设全部实例中均没有数据,假设服务器启动顺序分别为:A,B,C。
- 选举流程简述
- 目前有5台服务器,每台服务器均没有数据,它们的编号分别是1,2,3,4,5,按编号依次启动,它们的选择举过程如下:
- 服务器1启动,给自己投票,然后发投票信息,由于其它机器还没有启动所以它收不到反馈信息,服务器1的状态一直属于Looking。
- 服务器2启动,给自己投票,同时与之前启动的服务器1交换结果,由于服务器2的编号大所以服务器2胜出,但此时投票数没有大于半数,所以两个服务器的状态依然是LOOKING。
- 服务器3启动,给自己投票,同时与之前启动的服务器1,2交换信息,由于服务器3的编号最大所以服务器3胜出,此时投票数正好大于半数,所以服务器3成为领导者,服务器1,2成为小弟。
- 服务器4启动,给自己投票,同时与之前启动的服务器1,2,3交换信息,尽管服务器4的编号大,但之前服务器3已经胜出,所以服务器4只能成为小弟。
- 服务器5启动,后面的逻辑同服务器4成为小弟。
- https://www.cnblogs.com/ASPNET2008/p/6421571.html
- follower(参与投票)
- 状态
- looking
- leading
- following
- 同步leader
- zxid(64位)
- epoch用来标识leader关系是否改变(32),每次一个leader被选出来,它都会有一个新的epoch,标识当前属于那个leader的统治时期
- 递增计数(32)
- 状态的每一次改变, 都对应着一个递增的Transaction id, 该id称为zxid. 由于zxid的递增性质, 如果zxid1小于zxid2, 那么zxid1肯定先于zxid2发生.
- 创建任意节点, 或者更新任意节点的数据, 或者删除任意节点, 都会导致Zookeeper状态发生改变, 从而导致zxid的值增加.
- observer (不参与投投票)
- 常见命令
- 启动
- Windows环境
- 双击zkServer.cmd脚本即可启动ZooKeeper或者根目录输入zkserver
- Linux环境
- 使用zkServer.sh脚本,如下:
- 启动ZK服务: sh bin/zkServer.sh start
- 查看ZK服务状态: sh bin/zkServer.sh status
- 停止ZK服务: sh bin/zkServer.sh stop
- 重启ZK服务: sh bin/zkServer.sh restart
- 连接
- 启动ZooKeeper服务之后,我们可以使用如下命令连接到 ZooKeeper 服务:
- zookeeper-3.4.8\bin>zkCli.cmd -server 127.0.0.1:2181
- 子主题 1
- 子主题 1
- Linux环境下:
- > zkCli.sh -server 127.0.0.1:2181
- 原文:https://blog.csdn.net/top_code/article/details/51377632
- 操作
- ls
- 使用 ls 命令来查看某个目录包含的所有文件,例如:
- [zk: 127.0.0.1:2181(CONNECTED) 1] ls /
- ls2
- 使用 ls2 命令来查看某个目录包含的所有文件,与ls不同的是它查看到time、version等信息
- [zk: 127.0.0.1:2181(CONNECTED) 1] ls2 /
- create
- 创建znode,并设置初始内容,例如
- [zk: 127.0.0.1:2181(CONNECTED) 1] create /test "hello"
- 创建一个新的 znode节点“ test ”以及与它关联的字符串
- get
- get
- 获取znode的数据,如下:
- [zk: 127.0.0.1:2181(CONNECTED) 1] get /test
- set
- set
- 修改znode内容,例如:
- [zk: 127.0.0.1:2181(CONNECTED) 1] set /test "ricky"
- delete
- delete
- 删除znode
- [zk: 127.0.0.1:2181(CONNECTED) 1] delete /test
- quit
-
mycat
-
原理
- 主要根据对sql的拦截,然后经过一定规则的分片解析、路由分析、读写分离分析、缓存分析等,然后将SQL发给后端真实的数据块,并将返回的结果做适当处理返回给客户端。
- 子主题 2
-
分片
-
拆分
-
水平拆分
-
要把一个表按照某种规则把数据划分到不同表或数据库里
-
常用规则
- *ID
- *日期
- *特定字段取模
-
优点
- *拆分规则抽象好,join操作基本可以数据库内完成
- *不存在单库大数据,高并发的性能瓶颈
- *应用端改造少
- *提高了系统稳定性和负载能力
-
缺点
- *拆分规则难以抽象
- *分片事务一致性难以解决
- *数据多次扩展难度跟维护量极大
- *跨库join性能较差
-
-
垂直拆分
-
表按模块划分到不同数据库表中
-
一般按照业务表进行分类,划分为不同的业务、模块库,耦合度越低,越容易做垂直拆分,
-
跨库Join,采用共享数据源或分库接口调用,根据资源和数据规模、负载而定
-
优点
- *拆分后业务清晰,拆分规则明确
- *系统之间整合或扩展容易
- *数据库维护简单
-
缺点
- *部分业务表无法Join,只能通过接口方式解决,提高了系统复杂度
- *受每种业务不同的限制存在单库性能瓶颈,不容易扩展跟性能提高
- *事务处理复杂
-
-
-
总结
-
c.水平拆分和垂直拆分共同缺点
- *分布式事务处理困难
- *跨节点join困难
- *扩数据源管理复杂
-
d.切分总则
- *能不切分的尽量不切分
- *如果要切分,选择合适的切分规则,提前规划好
- *数据库切分尽量通过数据冗余或表分组来降低跨库join
- *业务尽量使用少的多表join
-
-
-
使用场景
- (1)单纯读写分离,此时配置最为简单,支持读写分离、主从切换
- (2)分库分表,对记录超过1000万的表进行水平拆分,最大支持1000亿单表水平拆分
- (3)多租户应用,每个应用一个数据库,但程序只需连接MyCAT,程序不改变,实现多租户化
- (4)报表系统,借住MyCAT分表能力,处理大规模的报表统计
- (5)替代Hbase,分析大数据
- (6)海量实时数据查询
-
概念
-
逻辑库(schema)
-
逻辑库是mycat中间件层配置的对应实际一个或多个业务数据库集群构成
- 子主题 1
-
-
逻辑表(table)
- a.逻辑表是mycat切分到多个数据库或者不切分对应用程序显示的统一的表。
- b.分片表是原有的大表,经过分片,分布在不同数据库、相同数据库的保留相同表结构,但数据不同的表。
- c.非分片表是未做切分的表。
- d.ER表基于E-R关系分片策略,子表记录与所关联的父表记录存放在同一个数据分片上,即子表依赖于父表,通过表分组保证数据join不会跨库操作。
- e.全局表,业务系统中变化不大、数据量不大(十万以下),但又需要经常关联的表,mycat采用冗余在各个节点一个份来完成。
-
分片节点(dataNode)
- 数据库分片后,一个大表被切分到不同的分片数据库上,每个表分片所在的数据库就是分片节点。
-
分片主机(dataHost)
- 分片节点所在的服务器,数据切分后,每个分片节点不一定都会独占一台服务器,同一个分片服务器可能存储多个分片节点,尽量使读写压力高的分片节点均衡的放在不同的节点主机上。
-
分片规则(rule)
- 按照某种业务规则把数据分到某个分片节点上的规则,就是分片规则。(分片规则非常重要,直接决定后续数据处理复杂度)
-
全局序列号(sequence)
- 当数据库分片后,原有的主键约束在分布式条件下无法使用,因此需要引入外部机制保证数据唯一表示,这种保证全局的数据唯一表示机制就是全局序列号(sequence)。
-
多租户
- 多用户的环境共用相同的系统、程序组件,并且确保各用户间数据的隔离性。
- a.一个用户一个数据库,隔离级别最高、安全性最好,费用最高
- b.共享数据库,隔离数据架构,每个用户一个schema
- c.共享数据库,共享数据架构,共享database、schema,通过表tenantID区分租户数据
-
schema
-
table1
-
dataNode
-
datanode
-
datanode1
-
datahost
- writehost
- readhost
-
database
-
-
-
tablename
-
tableprimarykey
-
teblerule
-
-
table2
-
schema.xml
- 子主题 1
-
-
-
-
disconf
-
组成
- 安装Mysql(Ver 14.12 Distrib 5.0.45, for unknown-linux-gnu (x86_64) using EditLine wrapper)
- 安装Tomcat(apache-tomcat-7.0.50)
- 安装Nginx(nginx/1.5.3)
- 安装 zookeeeper (zookeeper-3.3.0)
- 安装 Redis (2.4.5)
-
架构
-
-
消息队列
-
常见消息队列
-
对比
- Kafka在于分布式架构,RabbitMQ基于AMQP协议来实现,RocketMQ/思路来源于kafka,改成了主从结构,在事务性可靠性方面做了优化。广泛来说,电商、金融等对事务性要求很高的,可以考虑RabbitMQ和RocketMQ,对性能要求高的可考虑Kafka。
- 其实对于这些消息队列的产品,每一种都在某一领域占有一席,虽然ActiveMQ目前在社区已经不是很活跃,但是其下一代产品Apollo已经问世。ZeroMQ小而美,RabbitMQ大而稳,Kakfa和RocketMQ快而强劲。RocketMQ虽然目前还很多不完善,但是一旦在Apache孵化成为顶级项目,全球程序猿开始贡献,前途也是不可限量的
- 原文:https://blog.csdn.net/liuxinghao/article/details/60875715
-
http://blog.51cto.com/caczjz/2141194?source=dra
-
-
架构
-
面向服务的架构(SOA)
-
架构演变
-
初始阶段架构
-
LAMP
- 初始阶段 的小型系统 应用程序、数据库、文件等所有的资源都在一台服务器上通俗称为LAMP
- 应用程序、数据库、文件等所有的资源都在一台服务器上。
- 通常服务器操作系统使用linux,应用程序使用PHP开发,然后部署在Apache上,数据库使用Mysql,汇集各种免费开源软件以及一台廉价服务器就可以开始系统的发展之路了。
-
-
应用服务和数据分离
- 好景不长,发现随着系统访问量的再度增加,webserver机器的压力在高峰期会上升到比较高,这个时候开始考虑增加一台webserver - 应用程序、数据库、文件分别部署在独立的资源上。 - 数据量增加,单台服务器性能及存储空间不足,需要将应用和数据分离,并发处理能力和数据存储空间得到了很大改善。
-
使用缓存改善性能
- 数据库中访问较集中的一小部分数据存储在缓存服务器中,减少数据库的访问次数,降低数据库的访问压力。 - 系统访问特点遵循二八定律,即80%的业务访问集中在20%的数据上。 - 缓存分为本地缓存和远程分布式缓存,本地缓存访问速度更快但缓存数据量有限,同时存在与应用程序争用内存的情况。
-
应用服务器集群
- 在做完分库分表这些工作后,数据库上的压力已经降到比较低了,又开始过着每天看着访问量暴增的幸福生活了,突然有一天,发现系统的访问又开始有变慢的趋势了,这个时候首先查看数据库,压力一切正常,之后查看webserver,发现apache阻塞了很多的请求,而应用服务器对每个请求也是比较快的,看来 是请求数太高导致需要排队等待,响应速度变慢 - 多台服务器通过负载均衡同时向外部提供服务,解决单台服务器处理能力和存储空间上限的问题。 - 使用集群是系统解决高并发、海量数据问题的常用手段。通过向集群中追加资源,提升系统的并发处理能力,使得服务器的负载压力不再成为整个系统的瓶颈。
-
数据库读写分离
- 享受了一段时间的系统访问量高速增长的幸福后,发现系统又开始变慢了,这次又是什么状况呢,经过查找,发现数据库写入、更新的这些操作的部分数据库连接的资源竞争非常激烈,导致了系统变慢 - 多台服务器通过负载均衡同时向外部提供服务,解决单台服务器处理能力和存储空间上限的问题。 - 使用集群是系统解决高并发、海量数据问题的常用手段。通过向集群中追加资源,使得服务器的负载压力不在成为整个系统的瓶颈。
-
反向代理和CDN加速
- 采用CDN和反向代理加快系统的 访问速度。 - 为了应付复杂的网络环境和不同地区用户的访问,通过CDN和反向代理加快用户访问的速度,同时减轻后端服务器的负载压力。CDN与反向代理的基本原理都是缓存。
-
分布式文件系统和分布式数据库
- 随着系统的不断运行,数据量开始大幅度增长,这个时候发现分库后查询仍然会有些慢,于是按照分库的思想开始做分表的工作 - 数据库采用分布式数据库,文件系统采用分布式文件系统。 - 任何强大的单一服务器都满足不了大型系统持续增长的业务需求,数据库读写分离随着业务的发展最终也将无法满足需求,需要使用分布式数据库及分布式文件系统来支撑。 - 分布式数据库是系统数据库拆分的最后方法,只有在单表数据规模非常庞大的时候才使用,更常用的数据库拆分手段是业务分库,将不同的业务数据库部署在不同的物理服务器上。
-
使用NoSQL和搜索引擎
- 系统引入NoSQL数据库及搜索引擎。 - 随着业务越来越复杂,对数据存储和检索的需求也越来越复杂,系统需要采用一些非关系型数据库如NoSQL和分数据库查询技术如搜索引擎。应用服务器通过统一数据访问模块访问各种数据,减轻应用程序管理诸多数据源的麻烦。
-
业务拆分
- 系统上按照业务进行拆分改造,应用服务器按照业务区分进行分别部署。 - 为了应对日益复杂的业务场景,通常使用分而治之的手段将整个系统业务分成不同的产品线,应用之间通过超链接建立关系,也可以通过消息队列进行数据分发,当然更多的还是通过访问同一个数据存储系统来构成一个关联的完整系统。 - 纵向拆分: - 将一个大应用拆分为多个小应用,如果新业务较为独立,那么就直接将其设计部署为一个独立的Web应用系统 - 纵向拆分相对较为简单,通过梳理业务,将较少相关的业务剥离即可。 - 横向拆分:将复用的业务拆分出来,独立部署为分布式服务,新增业务只需要调用这些分布式服务 - 横向拆分需要识别可复用的业务,设计服务接口,规范服务依赖关系。
-
分布式服务
- 公共的应用模块被提取出来,部署在分布式服务器上供应用服务器调用。 - 随着业务越拆越小,应用系统整体复杂程度呈指数级上升,由于所有应用要和所有数据库系统连接,最终导致数据库连接资源不足,拒绝服务。 - 问题 - Q:分布式服务应用会面临哪些问题? - A: - (1) 当服务越来越多时,服务URL配置管理变得非常困难,F5硬件负载均衡器的单点压力也越来越大。 - (2) 当进一步发展,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。 - (3) 接着,服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要多少机器支撑?什么时候该加机器? - (4) 服务多了,沟通成本也开始上升,调某个服务失败该找谁?服务的参数都有什么约定? - (5) 一个服务有多个业务消费者,如何确保服务质量? - (6) 随着服务的不停升级,总有些意想不到的事发生,比如cache写错了导致内存溢出,故障不可避免,每次核心服务一挂,影响一大片,人心慌慌,如何控制故障的影响面?服务是否可以功能降级?或者资源劣化?
-
Java分布式应用技术基础
-
分布式服务下的关键技术:消息队列架构
-
分布式服务下的关键技术:消息队列原理
- 1
-
分布式服务下的关键技术:服务框架架构
- 服务框架通过接口分解系统耦合性,不同子系统通过相同的接口描述进行服务启用 - 服务框架是一个点对点模型 - 服务框架面向同构系统 - 适合:移动应用、互联网应用、外部系统
-
分布式服务下的关键技术:服务框架原理
-
分布式服务下的关键技术:服务总线架构
- 服务总线同服务框架一样,均是通过接口分解系统耦合性,不同子系统通过相同的接口描述进行服务启用 - 服务总线是一个总线式的模型 - 服务总线面向同构、异构系统 - 适合:内部系统 - 分布式架构下系统间交互的5种通信模式 - request/response模式(同步模式):客户端发起请求一直阻塞到服务端返回请求为止。 - Callback(异步模式):客户端发送一个RPC请求给服务器,服务端处理后再发送一个消息给消息发送端提供的callback端点,此类情况非常合适以下场景:A组件发送RPC请求给B,B处理完成后,需要通知A组件做后续处理。 - Future模式:客户端发送完请求后,继续做自己的事情,返回一个包含消息结果的Future对象。客户端需要使用返回结果时,使用Future对象的.get(),如果此时没有结果返回的话,会一直阻塞到有结果返回为止。 - Oneway模式:客户端调用完继续执行,不管接收端是否成功。 - Reliable模式:为保证通信可靠,将借助于消息中心来实现消息的可靠送达,请求将做持久化存储,在接收方在线时做送达,并由消息中心保证异常重试。
-
-
-
-
road
-
https://www.w3cschool.cn/architectroad/architectroad-optimization-of-seckilling-system.html
-
方法论
-
细聊分布式ID生成方法
- 常见方法一:使用数据库的 auto_increment 来生成全局唯一递增ID
- 常见方法二:单点批量ID生成服务
- 常见方法三:uuid
- 常见方法四:取当前毫秒数
- 常见方法五:类snowflake算法
-
互联网架构,如何进行容量设计
- 【步骤一:评估总访问量】
- ->询问业务、产品、运营
- 【步骤二:评估平均访问量QPS】
- ->除以时间,一天算4w秒
- 【步骤三:评估高峰QPS】
- ->根据业务曲线图来
- 【步骤四:评估系统、单机极限QPS】
- ->压测很重要
- 【步骤五:根据线上冗余度回答两个问题】
- -> 估计冗余度与线上冗余度差值
-
架构 秒杀系统优化思路
- (1)尽量将请求拦截在系统上游(越上游越好);
- (2)读多写少的常用多使用缓存(缓存抗读压力);
- 浏览器和APP:做限速
- 站点层:按照uid做限速,做页面缓存
- 服务层:按照业务做写请求队列控制流量,做数据缓存
- 数据层:闲庭信步
- 并且:结合业务做优化
-
线程数究竟设多少合理
- N核服务器,通过执行业务的单线程分析出本地计算时间为x,等待时间为y,则工作线程数(线程池线程数)设置为 N*(x+y)/x,能让CPU的利用率最大化。
-
单点系统架构的可用性与性能优化
- (1)单点系统存在的问题:可用性问题,性能瓶颈问题
- (2)shadow-master是一种常见的解决单点系统可用性问题的方案
- (3)减少与单点的交互,是存在单点的系统优化的核心方向,常见方法有批量写,客户端缓存
- (4)水平扩展也是提升单点系统性能的好方案
-
负载均衡
- (1)【客户端层】到【反向代理层】的负载均衡,是通过“DNS轮询”实现的
- (2)【反向代理层】到【站点层】的负载均衡,是通过“nginx”实现的
- (3)【站点层】到【服务层】的负载均衡,是通过“服务连接池”实现的
- (4)【数据层】的负载均衡,要考虑“数据的均衡”与“请求的均衡”两个点,常见的方式有“按照范围水平切分”与“hash水平切分”
-
DNS轮询
- 1)接入层架构要考虑的问题域为:高可用、扩展性、反向代理+扩展均衡
- 2)nginx、keepalived、lvs、f5可以很好的解决高可用、扩展性、反向代理+扩展均衡的问题
- 3)水平扩展scale out是解决扩展性问题的根本方案,DNS轮询是不能完全被nginx/lvs/f5所替代的
-
异构服务器
- 1)service的负载均衡、故障转移、超时处理通常是RPC-client连接池层面来实施的
- 2)异构服务器负载均衡,最简单的方式是静态权重法,缺点是无法自适应动态调整
- 3)动态权重法,可以动态的根据service的处理能力来分配负载,需要有连接池层面的微小改动
- 4)过载保护,是在负载过高时,service为了保护自己,保证一定处理能力的一种自救方法
- 5)动态权重法,还可以用做service的过载保护
-
高并发
-
高并发(High Concurrency)是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够同时并行处理很多请求。
- 高并发相关常用的一些指标有响应时间(Response Time),吞吐量(Throughput),每秒查询率QPS(Query Per Second),并发用户数等。
- 响应时间:系统对请求做出响应的时间。例如系统处理一个HTTP请求需要200ms,这个200ms就是系统的响应时间。
- 吞吐量:单位时间内处理的请求数量。
- QPS:每秒响应请求数。在互联网领域,这个指标和吞吐量区分的没有这么明显。
- 并发用户数:同时承载正常使用系统功能的用户数量。例如一个即时通讯系统,同时在线量一定程度上代表了系统的并发用户数。
-
提高系统并发能力的方式,方法论上主要有两种:垂直扩展(Scale Up)与水平扩展(Scale Out)。前者垂直扩展可以通过提升单机硬件性能,或者提升单机架构性能,来提高并发性,但单机性能总是有极限的,互联网分布式架构设计高并发终极解决方案还是后者:水平扩展。
-
互联网分层架构中,各层次水平扩展的实践又有所不同:
-
(1)反向代理层可以通过“DNS轮询”的方式来进行水平扩展;
-
(2)站点层可以通过nginx来进行水平扩展;
-
(3)服务层可以通过服务连接池来进行水平扩展;
-
(4)数据库可以按照数据范围,或者数据哈希的方式来进行水平扩展;
-
各层实施水平扩展后,能够通过增加服务器数量的方式来提升系统的性能,做到理论上的性能无限。
-
-
高可用
- 高可用HA(High Availability)是分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计减少系统不能提供服务的时间。
- 方法论上,高可用是通过冗余+自动故障转移来实现的。
- 整个互联网分层系统架构的高可用,又是通过每一层的冗余+自动故障转移来综合实现的,具体的:
- (1)【客户端层】到【反向代理层】的高可用,是通过反向代理层的冗余实现的,常见实践是keepalived + virtual IP自动故障转移
- (2)【反向代理层】到【站点层】的高可用,是通过站点层的冗余实现的,常见实践是nginx与web-server之间的存活性探测与自动故障转移
- (3)【站点层】到【服务层】的高可用,是通过服务层的冗余实现的,常见实践是通过service-connection-pool来保证自动故障转移
- (4)【服务层】到【缓存层】的高可用,是通过缓存数据的冗余实现的,常见实践是缓存客户端双读双写,或者利用缓存集群的主从数据同步与sentinel保活与自动故障转移;更多的业务场景,对缓存没有高可用要求,可以使用缓存服务化来对调用方屏蔽底层复杂性
- (5)【服务层】到【数据库“读”】的高可用,是通过读库的冗余实现的,常见实践是通过db-connection-pool来保证自动故障转移
- (6)【服务层】到【数据库“写”】的高可用,是通过写库的冗余实现的,常见实践是keepalived + virtual IP自动故障转移
-
反向依赖解耦
- 如何发现系统架构中不合理的“反向依赖”设计?
- 回答:
- (1)变动方是A,配合方却是BCDE
- (2)需求方是A,改动方确是BCDE
- 想想“换IP的是你,配合重启的却是我”,此时往往架构上可以进行解耦优化。
- 常见反向依赖及优化方案?
- (1)公共库导致耦合
- 优化一:如果公共库是业务特性代码,进行公共库垂直拆分
- 优化二:如果公共库是业务共性代码,进行服务化下沉抽象
- (2)服务化不彻底导致耦合
- 特征:服务中包含大量“根据不同业务,执行不同个性分支”的代码
- 优化方案:个性代码放到业务层实现,将服务化更彻底更纯粹
- (3)notify的不合理实现导致的耦合
- 特征:调用方不关注执行结果,以调用的方式去实现通知,新增订阅者,修改代码的是发布者
- 优化方案:通过MQ解耦
- (4)配置中的ip导致上下游耦合
- 特征:多个上游需要修改配置重启
- 优化方案:使用内网域名替代内网ip,通过“修改DNS指向,统一切断旧连接”的方式来上游无感切换
- (5)下游扩容导致上下游耦合
- 特性:多个上游需要修改配置重启
-
数据库设计
- •业务初期用单库
- •读压力大,读高可用,用分组
- •数据量大,写线性扩容,用分片
- •属性短,访问频度高的属性,垂直拆分到一起
-
-
典型架构实践
-
TCP接入层的负载均衡、高可用、扩展性架构
- web-server如何实施负载均衡?
- 利用nginx反向代理来轮询、随机、ip-hash。
- tcp-server怎么快速保证请求一致性?
- 单机。
- 如何保证高可用?
- 客户配置多个tcp-server的域名。
- 如何防止DNS劫持,以及加速?
- IP直通车,客户端配置多个tcp-server的IP。
- 如何保证扩展性?
- 服务端提供get-tcp-ip接口,向client屏屏蔽负载均衡策略,并实施便捷扩容。
- 如何保证高可用?
- tcp-server“推”状态给get-tcp-ip接口,
- or
- get-tcp-ip接口“拉”tcp-server状态。
-
配置中心架构设计
- 解决什么问题?
- 配置导致系统耦合,架构反向依赖。
- 什么痛点?
- 上游痛:扩容的是下游,改配置重启的是上游
- 下游痛:不知道谁依赖于自己
- 配置架构如何演进?
- 一、配置私藏
- 二、全局配置文件
- 三、配置中心
-
跨公网调用的大坑与架构优化方案
-
计数
- 小小的计数,在数据量大,并发量大的时候,其架构实践思路为:
- •计数外置:由“count计数法”升级为“计数外置法”
- •读多写少,甚至写多但一致性要求不高的计数,需要进行缓存优化,降低数据库压力
- •缓存kv设计优化,可以由[key:type]->[count],优化为[key]->[c1:c2:c3]
- •数据库扩展性优化,可以由列扩展优化为行扩展
-
数据库与缓存
-
-
搜索架构
-
简介
-
(1)全网搜索引擎系统由spider, search&index, rank三个子系统构成
-
(2)站内搜索引擎与全网搜索引擎的差异在于,少了一个spider子系统
-
(3)spider和search&index系统是两个工程系统,rank系统的优化却需要长时间的调优和积累
-
(4)正排索引(forward index)是由网页url_id快速找到分词后网页内容list的过程
-
(5)倒排索引(inverted index)是由分词item快速寻找包含这个分词的网页list<url_id>的过程
-
(6)用户检索的过程,是先分词,再找到每个item对应的list<url_id>,最后进行集合求交集的过程
-
(7)有序集合求交集的方法有
-
a)二重for循环法,时间复杂度O(n*n)
-
b)拉链法,时间复杂度O(n)
-
c)水平分桶,多线程并行
-
d)bitmap,大大提高运算并行度,时间复杂度O(n)
-
e)跳表,时间复杂度为O(log(n))
-
-
-
搜索架构一般会经历这么几个阶段
- (1)原始阶段-LIKE
- (2)初级阶段-全文索引
- (3)中级阶段-开源外置索引
- (4)高级阶段-自研搜索引擎
-
-
5W2H2R
- 5W-> why, who, when, where, what: 为什么要做,希望谁,在什么时间,什么地方,完成什么事情
- 2H-> how, how much: 希望怎么做,做到什么程度
- 2R-> resource, result: 有什么资源支持,希望获得什么结果
-
-
分布式事务
-
分布式事务的基础
-
ACID
-
A:原子性(Atomicity)
- 一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
- 就像你买东西要么交钱收货一起都执行,要么要是发不出货,就退钱。
-
C:一致性(Consistency)
- 事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。
-
I:隔离性(Isolation)
- 指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
- 打个比方,你买东西这个事情,是不影响其他人的。
-
D:持久性(Durability)
- 指的是只要事务成功结束,它对数据库所做的更新就必须永久保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。
- 打个比方,你买东西的时候需要记录在账本上,即使老板忘记了那也有据可查。
-
-
cap
- C (一致性):对某个指定的客户端来说,读操作能返回最新的写操作。对于数据分布在不同节点上的数据上来说,如果在某个节点更新了数据,那么在其他节点如果都能读取到这个最新的数据,那么就称为强一致,如果有某个节点没有读取到,那就是分布式不一致。
- A (可用性):非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)。可用性的两个关键一个是合理的时间,一个是合理的响应。合理的时间指的是请求不能无限被阻塞,应该在合理的时间给出返回。合理的响应指的是系统应该明确返回结果并且结果是正确的,这里的正确指的是比如应该返回50,而不是返回40。
- P (分区容错性):当出现网络分区后,系统能够继续工作。打个比方,这里个集群有多台机器,有台机器网络出现了问题,但是这个集群仍然可以正常工作。
-
BASE
- BASE 是 Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent (最终一致性)三个短语的缩写。是对CAP中AP的一个扩展
- 基本可用:分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。
- 软状态:允许系统中存在中间状态,这个状态不影响系统可用性,这里指的是CAP中的不一致。
- 最终一致:最终一致是指经过一段时间后,所有节点数据都将会达到一致。
- BASE解决了CAP中理论没有网络延迟,在BASE中用软状态和最终一致,保证了延迟后的一致性。BASE和 ACID 是相反的,它完全不同于ACID的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。
-
innodb事务
- InnoDB日志和锁来保证ACID - 原子性和一致性通过Undo log来实现 - UndoLog的原理很简单,为了满足事务的原子性,在操作任何数据之前,首先将数据备份到一个地方(这个存储数据备份的地方称为UndoLog)。然后进行数据的修改。如果出现了错误或者用户执行了ROLLBACK语句,系统可以利用Undo Log中的备份将数据恢复到事务开始之前的状态。 - 持久性通过redo log(重做日志)来实现 - 和Undo Log相反,RedoLog记录的是新数据的备份。在事务提交前,只要将RedoLog持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是RedoLog已经持久化。系统可以根据RedoLog的内容,将所有数据恢复到最新的状态。
-
其它概念
-
幂等
- 同一操作反复执行结果不变
-
危险期
- 请求没有成功发送到服务器
- 服务器处理完成后的回应无法回传给请求方
-
-
CAP,BASE以及ACID的关系
- CAP描述了对于一个分布式系统而言重要的三要素:数据一致性,可用性,分区容错性之间的制约关系,当你选择了其中的两个时,就不得不对剩下的一个做一定程度的牺牲。BASE和ACID都可以看做是对CAP三要素进行取舍后的某种特殊情况
- BASE强调可用性和分区容错性,放弃强一致性,这是大部分分布式系统的选择,比如NoSQL系统,微服务架构下的分布式系统
- ACID是单机数据的事务特性,因为不是分布式系统无需考虑分区容错,故而是选择了可用性和强一致性后的结果。
- https://maimai.cn/article/detail?fid=1078911828&efid=YBb3icG7HSsovxQUkpPaPA
-
分布式概念
- 系统由物理上不同分布的多个机器节点组成
- 系统的多个节点通过网络进行通信,协调彼此之间的工作。
- 系统作为整体统一对外提供服务,其分布式细节对客户端透明。
-
-
xa
-
XA
- 事务管理器和本地资源管理器。其中本地资源管理器往往由数据库实现,比如Oracle、DB2这些商业数据库都实现了XA接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。XA实现分布式事务的原理如下:
-
两阶段提交(2PC)
-
2PC
-
成功
-
第一阶段
- 在XA分布式事务的第一阶段,作为事务协调者的节点会首先向所有的参与者节点发送Prepare请求。
- 在接到Prepare请求之后,每一个参与者节点会各自执行与事务有关的数据更新,写入Undo Log和Redo Log。如果参与者执行成功,暂时不提交事务,而是向事务协调节点返回“完成”消息。
- 当事务协调者接到了所有参与者的返回消息,整个分布式事务将会进入第二阶段。
- 原文:https://blog.csdn.net/bjweimengshu/article/details/79607522
-
第二阶段
- 接到Commit请求之后,事务参与者节点会各自进行本地的事务提交,并释放锁资源。当本地事务完成提交后,将会向事务协调者返回“完成”消息。
- 当事务协调者接收到所有事务参与者的“完成”反馈,整个分布式事务完成
-
-
失败
-
第一阶段
-
第二阶段
- 在XA的第一阶段,如果某个事务参与者反馈失败消息,说明该节点的本地事务执行不成功,必须回滚。
- 于是在第二阶段,事务协调节点向所有的事务参与者发送Abort请求。接收到Abort请求之后,各个事务参与者节点需要在本地进行事务的回滚操作,回滚操作依照Undo Log来进行。
-
-
LPO
-
最末参与者优化
- 子主题 1
- 子主题 1
-
-
不足
-
1.性能问题
- XA协议遵循强一致性。在事务执行过程中,各个节点占用着数据库资源,只有当所有节点准备完毕,事务协调者才会通知提交,参与者提交后释放资源。这样的过程有着非常明显的性能问题。
-
2.协调者单点故障问题
- 事务协调者是整个XA模型的核心,一旦事务协调者节点挂掉,参与者收不到提交或是回滚通知,参与者会一直处于中间状态无法完成事务。
-
3.丢失消息导致的不一致问题。
- 在XA协议的第二个阶段,如果发生局部网络问题,一部分事务参与者收到了提交消息,另一部分事务参与者没收到提交消息,那么就导致了节点之间数据的不一致。
-
-
-
2PC改进
-
1.XA三阶段提交
- XA三阶段提交在两阶段提交的基础上增加了CanCommit阶段,并且引入了超时机制。一旦事物参与者迟迟没有接到协调者的commit请求,会自动进行本地commit。这样有效解决了协调者单点故障的问题。但是性能问题和不一致的问题仍然没有根本解决。
-
2.MQ事务
-
利用消息中间件来异步完成事务的后一半更新,实现系统的最终一致性。这个方式避免了像XA协议那样的性能问题。
-
第一阶段Prepared消息,会拿到消息的地址。
-
第二阶段执行本地事务。
-
第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。消息接受者就能使用这个消息。
-
demo
-
基于消息中间件的两阶段提交往往用在高并发场景下,将一个分布式事务拆成一个消息事务(A系统的本地操作+发消息)+B系统的本地操作,其中B系统的操作由消息驱动,只要消息事务成功,那么A操作一定成功,消息也一定发出来了,这时候B会收到消息去执行本地操作,如果本地操作失败,消息会重投,直到B操作成功,这样就变相地实现了A与B的分布式事务。
- 1、A系统向消息中间件发送一条预备消息 - 2、消息中间件保存预备消息并返回成功 - 3、A执行本地事务 - 4、A发送提交消息给消息中间件 - 通过以上4步完成了一个消息事务。对于以上的4个步骤,每个步骤都可能产生错误,下面一一分析: - 步骤一出错,则整个事务失败,不会执行A的本地操作 - 步骤二出错,则整个事务失败,不会执行A的本地操作 - 步骤三出错,这时候需要回滚预备消息,怎么回滚?答案是A系统实现一个消息中间件的回调接口,消息中间件会去不断执行回调接口,检查A事务执行是否执行成功,如果失败则回滚预备消息 - 步骤四出错,这时候A的本地事务是成功的,那么消息中间件要回滚A吗?答案是不需要,其实通过回调接口,消息中间件能够检查到A执行成功了,这时候其实不需要A发提交消息了,消息中间件可以自己对消息进行提交,从而完成整个消息事务
-
-
-
-
3.TCC事务
-
TCC事务是Try、Commit、Cancel三种指令的缩写,其逻辑模式类似于XA两阶段提交,但是实现方式是在代码层面来人为实现
-
实现
-
2PC协议比较:
- 没有单独的Prepare阶段,降低协议成本
- 系统故障容忍度高,恢复简单
-
栗子
-
Try阶段:
-
完成所有业务检查(一致性),预留业务资源(准隔离性)
-
回顾上面航班预定案例的阶段1,机票就是业务资源,所有的资源提供者(航空公司)预留都成功,try阶段才算陈宫
-
-
Confirm阶段:
-
确认执行业务操作,不做任何业务检查, 只使用Try阶段预留的业务资源。回顾上面航班预定案例的阶段2,美团APP确认两个航空公司机票都预留成功,因此向两个航空公司分别发送确认购买的请求。
-
-
Cancel阶段:
-
取消Try阶段预留的业务资源。回顾上面航班预定案例的阶段2,如果某个业务方的业务资源没有预留成功,则取消所有业务资源预留请求。
-
-
-
对比
- XA
-
-
-
本地消息表
-
Saga事务
-
每个操作有对应的撤销操作,后面的步骤失败,则前面的操作进行撤销
- T1=扣100元 T2=给用户加一瓶水 T3=减库存一瓶水
- C1=加100元 C2=给用户减一瓶水 C3=给库存加一瓶水
-
-
柔性事务
-
日志和补偿机制
-
可靠消息传递
-
无锁实现
- 避免事务回滚
- 服务业务变化明细表
- 乐观锁
-
-
-
-
-
概念
- 分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
-
总结
- 分布式事务,本质上是对多个数据库的事务进行统一控制,按照控制力度可以分为:不控制、部分控制和完全控制。不控制就是不引入分布式事务,部分控制就是各种变种的两阶段提交,包括上面提到的消息事务+最终一致性、TCC模式,而完全控制就是完全实现两阶段提交。部分控制的好处是并发量和性能很好,缺点是数据一致性减弱了,完全控制则是牺牲了性能,保障了一致性,具体用哪种方式,最终还是取决于业务场景。作为技术人员,一定不能忘了技术是为业务服务的,不要为了技术而技术,针对不同业务进行技术选型也是一种很重要的能力!
-
分布式锁
-
基于数据库
-
思路
-
通过表中的记录的存在情况确定当前是否有锁存在
-
先insert一把锁(使用索引,插入相同字段),后delete
-
缺点
-
这把锁强依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。
- 数据库是单点?搞两个数据库,数据之前双向同步。一旦挂掉快速切换到备库上。
-
这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁。
- 没有失效时间?只要做一个定时任务,每隔一定时间把数据库中的超时数据清理一遍。
-
这把锁只能是非阻塞的,因为数据的insert操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作。
- 非阻塞的?搞一个while循环,直到insert成功再返回成功。
-
这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了。
- 非重入的?在数据库表中加个字段,记录当前获得锁的机器的主机信息和线程信息,那么下次再获取锁的时候先查询数据库,如果当前机器的主机信息和线程信息在数据库可以查到的话,直接把锁分配给他就可以了。
-
这把锁是非公平锁,所有等待锁的线程凭运气去争夺锁。
- 非公平的?再建一张中间表,将等待锁的线程全记录下来,并根据创建时间排序,只有最先创建的允许获取锁
-
-
-
思路二
- 通过数据库的排他锁来实现分布式锁。
-
-
基于缓存
-
思路
-
SET
-
http://redisdoc.com/string/set.html
-
https://www.linuxidc.com/Linux/2018-05/152458.htm
-
加锁
-
取消锁
-
查询是否上锁
-
-
步骤
- 1,线程请求访问前先调用加锁的方法,加锁就去里生成一个随机数同时保存在线程本地变量和redis的某key中,此key设有效期为200ms,具体值根据业务执行时间自行调整,加锁成功;
- 2.其它线程试着访问拿出它本地变量与redis中某key进行比较,如果不一致,则说明有锁,此线程休眠一段时间,再试着加锁;
- 3.加锁成功的线程在操作结束后删掉它持有锁(用lua实现,保证原子性,在它比对和删除锁的过程中,其它线程不会加锁成功),让其它线程再次加锁以执行任务;
-
缺陷
-
1、单点问题。
-
现在主流的缓存服务都支持集群部署,通过集群来解决单点问题。
-
Redlock算法
-
实现
- 1、客户端获取当前时间,以毫秒为单位。
- 2、客户端尝试获取N个节点的锁,(每个节点获取锁的方式和前面说的缓存锁一样),N个节点以相同的key和value获取锁。客户端需要设置接口访问超时,接口超时时间需要远远小于锁超时时间,比如锁自动释放的时间是10s,那么接口超时大概设置5-50ms。这样可以在有redis节点宕机后,访问该节点时能尽快超时,而减小锁的正常使用。
- 3、客户端计算在获得锁的时候花费了多少时间,方法是用当前时间减去在步骤一获取的时间,只有客户端获得了超过3个节点的锁,而且获取锁的时间小于锁的超时时间,客户端才获得了分布式锁。
- 4、客户端获取的锁的时间为设置的锁超时时间减去步骤三计算出的获取锁花费时间。
- 5、如果客户端获取锁失败了,客户端会依次删除所有的锁。
- 使用Redlock算法,可以保证在挂掉最多2个节点的时候,分布式锁服务仍然能工作,这相比之前的数据库锁和缓存锁大大提高了可用性,由于redis的高效性能,分布式缓存锁性能并不比数据库锁差。
- 作者:南北雪树
- 原文:https://blog.csdn.net/u010963948/article/details/79006572
-
缺点
- 首先必须部署5个节点才能让Redlock的可靠性更强。
- 然后需要请求5个节点才能获取到锁,通过Future的方式,先并发向5个节点请求,再一起获得响应结果,能缩短响应时间,不过还是比单节点redis锁要耗费更多时间。
- 然后由于必须获取到5个节点中的3个以上,所以可能出现获取锁冲突,即大家都获得了1-2把锁,结果谁也不能获取到锁,这个问题,redis作者借鉴了raft算法的精髓,通过冲突后在随机时间开始,可以大大降低冲突时间,但是这问题并不能很好的避免,特别是在第一次获取锁的时候,所以获取锁的时间成本增加了。
- 如果5个节点有2个宕机,此时锁的可用性会极大降低,首先必须等待这两个宕机节点的结果超时才能返回,另外只有3个节点,客户端必须获取到这全部3个节点的锁才能拥有锁,难度也加大了。
- 如果出现网络分区,那么可能出现客户端永远也无法获取锁的情况。
-
-
-
2、这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在redis中,其他线程无法再获得到锁。
- 没有失效时间?redis的setExpire方法支持传入失效时间,到达时间之后数据会自动删除。
-
3、这把锁只能是非阻塞的,无论成功还是失败都直接返回。
- 非阻塞?while重复执行。
-
4、这把锁是非重入的,一个线程获得锁之后,在释放锁之前,无法再次获得该锁,因为使用到的key在redis中已经存在。无法再执行setNX操作。
- 非可重入?在一个线程获取到锁之后,把当前主机信息和线程信息保存起来,下次再获取之前先检查自己是不是当前锁的拥有者。
-
5、这把锁是非公平的,所有等待的线程同时去发起setNX操作,运气好的线程能获取锁。
- 非公平?在线程获取锁之前先把所有等待的线程放入一个队列中,然后按先进先出原则获取锁。
-
原文:https://blog.csdn.net/u010963948/article/details/79006572
-
-
-
-
基于zk
-
原文:https://blog.csdn.net/u010963948/article/details/79006572
-
思路
- 大致思想即为:每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。
-
优点
- 来看下Zookeeper能不能解决前面提到的问题。
- 锁无法释放?使用Zookeeper可以有效的解决锁无法释放的问题,因为在创建锁的时候,客户端会在ZK中创建一个临时节点,一旦客户端获取到锁之后突然挂掉(Session连接断开),那么这个临时节点就会自动删除掉。其他客户端就可以再次获得锁。
- 非阻塞锁?使用Zookeeper可以实现阻塞的锁,客户端可以通过在ZK中创建顺序节点,并且在节点上绑定监听器,一旦节点有变化,Zookeeper会通知客户端,客户端可以检查自己创建的节点是不是当前所有节点中序号最小的,如果是,那么自己就获取到锁,便可以执行业务逻辑了。
- 不可重入?使用Zookeeper也可以有效的解决不可重入的问题,客户端在创建节点的时候,把当前客户端的主机信息和线程信息直接写入到节点中,下次想要获取锁的时候和当前最小的节点中的数据比对一下就可以了。如果和自己的信息一样,那么自己直接获取到锁,如果不一样就再创建一个临时的顺序节点,参与排队。
- 单点问题?使用Zookeeper可以有效的解决单点问题,ZK是集群部署的,只要集群中有半数以上的机器存活,就可以对外提供服务。
- 公平问题?使用Zookeeper可以解决公平锁问题,客户端在ZK中创建的临时节点是有序的,每次锁被释放时,ZK可以通知最小节点来获取锁,保证了公平。
-
缺点
- Zookeeper实现的分布式锁其实存在一个缺点,那就是性能上可能并没有缓存服务那么高。因为每次在创建锁和释放锁的过程中,都要动态创建、销毁瞬时节点来实现锁功能。ZK中创建和删除节点只能通过Leader服务器来执行,然后将数据同不到所有的Follower机器上。
-
一致性
-
原文:https://blog.csdn.net/u010963948/article/details/79006572
-
强一致性
- 在分布式环境下,满足强一致性的数据储存基本不存在,它要求在更新一个节点的数据,需要同步更新所有的节点。这种同步策略出现在主从同步复制的数据库中。但是这种同步策略,对写性能的影响太大而很少见于实践。因为Zookeeper是同步写N/2+1个节点,还有N/2个节点没有同步更新,所以Zookeeper不是强一致性的。
-
因果一致性
- 们在基于Zookeeper实现分布式锁的时候,应该使用满足因果一致性的做法,即等待锁的线程都监听Zookeeper上锁的变化,在锁被释放的时候,Zookeeper会将锁变化的通知告诉满足公平锁条件的等待线程。
-
-
-
总结比较
- 从理解的难易程度角度(从低到高)
- 数据库 > 缓存 > Zookeeper
- 从实现的复杂性角度(从低到高)
- Zookeeper > 缓存 > 数据库
- 从性能角度(从高到低)
- 缓存 > Zookeeper >= 数据库
- 从可靠性角度(从高到低)
- Zookeeper > 缓存 > 数据库\
-
-
-
IT架构转型之道
-
中台战略
-
烟囱
- 重复建设和维护带来的投资成本
- 烟囱系统间交互的集成和协作成本
- 不利于业务沉淀和持续发展
-
SOA
- 面向服务的分布式计算
- 服务间耦合低
- 服务可组装
- 服务注册和自动发现
- 用协议定义规范交互方式
-
分布式
-
优势
- 降低协同成本,快速响应业务
- 降低系统间耦合度以及整体负责度
- 减小个别系统出错对整体的影响
- 解决数据库连接瓶颈,更灵活的分库分表以及扩容
- 对业务根据场景调整容量
-
中心化
-
ECB
- 扩展
- 雪崩
-
-
去中心化
-
dubbo
-
HSF
-
原理图
- 整体实现方式
- 原理图
-
相关技术
- netty
- hessien
-
-
-
-
-
Disruptor
-
概述
-
子主题 1
- 子主题 1
-
子主题 2
-
-
使用
-
原理
-
1.建Event类(数据对象)
-
2.建立一个生产数据的工厂类,EventFactory,用于生产数据;
-
3.监听事件类(处理Event数据)
-
4.实例化Disruptor,配置参数,绑定事件;
-
5.建存放数据的核心 RingBuffer,生产的数据放入 RungBuffer。
-
ringbuffer
-
它是一个环(首尾相接的环),你可以把它用做在不同上下文(线程)间传递数据的buffer。
-
ring buffer和大家常用的队列之间的区别是,我们不删除buffer中的数据,也就是说这些数据一直存放在buffer中,直到新的数据覆盖他们。
-
why fast
- 是因为它在可靠消息传递方面有很好的性能
- 首先,因为它是数组,所以要比链表快,而且有一个容易预测的访问模式。(译者注:数组内元素的内存地址的连续性存储的)。这是对CPU缓存友好的—也就是说,在硬件级别,数组中的元素是会被预加载的,因此在ringbuffer当中,cpu无需时不时去主存加载数组中的下一个元素。(校对注:因为只要一个元素被加载到缓存行,其他相邻的几个元素也会被加载进同一个缓存行)
- 其次,你可以为数组预先分配内存,使得数组对象一直存在(除非程序终止)。这就意味着不需要花大量的时间用于垃圾回收。此外,不像链表那样,需要为每一个添加到其上面的对象创造节点对象—对应的,当删除节点时,需要执行相应的内存清理操作。
-
inner
-
子主题 1
-
ring buffer维护两个指针,“next”和“cursor”。
-
填充数据
- 假设有一个线程负责将字母“D”写进ring buffer中。将会从ring buffer中获取一个区块(slot),这个操作是一个基于CAS的“get-and-increment”操作,将“next”指针进行自增。这样,当前线程(我们可以叫做线程D)进行了get-and-increment操作后,
-
-
-
-
-
-
-
指向了位置4,然后返回3。这样,线程D就获得了位置3的操作权限。
- 接着,另一个线程E做类似以上的操作
- 提交写入
- 以上,线程D和线程E都可以同时线程安全的往各自负责的区块(或位置,slots)写入数据。但是,我们可以讨论一下线程E先完成任务的场景…
线程E尝试提交写入数据。在一个繁忙的循环中有若干的CAS提交操作。线程E持有位置4,它将会做一个CAS的waiting操作,直到 “cursor”变成3,然后将“cursor”变成4。
再次强调,这是一个原子性的操作。因此,现在的ring buffer中,“cursor”现在是2,线程E将会进入长期等待并重试操作,直到 “cursor”变成3。
然后,线程D开始提交。线程E用CAS操作将“cursor”设置为3(线程E持有的区块位置)当且仅当“cursor”位置是2.“cursor”当前是2,所以CAS操作成功和提交也成功了。
这时候,“cursor”已经更新成3,然后所有和3相关的数据变成可读。
这是一个关键点。知道ring buffer填充了多少 – 即写了多少数据,那一个序列数写入最高等等,是游标的一些简单的功能。“next”指针是为了保证写入的事务特性
- 子主题 1
- 最后的疑惑是线程E的写入可见,线程E一直重试,尝试将“cursor”从3更新成4,经过线程D操作后已经更新成3,那么下一次重试就可以成功了。
- https://blog.csdn.net/chen_fly2011/article/details/54929468
- 原理
- 1.Ring buffer是由一个大数组组成的。
- 2.所有Ring buffer的“指针”(也称为序列或游标)是Java long类型的(64位有符号数),指针采用往上计数自增的方式。(不用担心越界,即使每秒1,000,000条消息,也要消耗300,000年才可以用完)。
- 3.对Ring buffer中的指针进行按Ring buffer的size取模找出数组的下标来定位入口(类似于HashMap的entry)。为了提高性能,我们通常将ring buffer的size大小设置成实际使用的2倍,这样我们可以通过位运算(bit-mask )的方式计算出数组的下标。
Resteasy
NAS
-
Network Attached Storage:网络附属存储
-
功能
- 数据存储
- 下载机
- 多媒体服务器
- 外网访问
- Time Machine备份
- https://blog.csdn.net/hangtianxia/article/details/81880666
-
产品
-
FreeNAS
- FreeNAS基于FreeBSD系统开发,是现在最受欢迎的开源NAS系统之一,官方文档很全而且中文教程也很多。不过FreeNAS基于ZFS文件系统,只支持64位CPU,最小内存要求8GB,对硬件的要求比较高,主要面向企业级
-
NAS4Free
- NAS4Free是基于FreeNAS开发的,可以算是FessNAS的一个分支,最近更名为了 XigmaNAS。它对硬件的要求比较低,适合家用。不过文档和教程不多。
-
OpenMediaVault
- OMV是基于Debian的开源NAS系统。我比较熟悉Linux,所以更倾向于OMV。它本身就有很多插件,还有很多第三方的插件可以安装。如果想自己扩展其它功能(比如Aria2)的话也很方便,和其它Linux系统一样。OMV还有树莓派的版本,感兴趣的话可以下载试试。
-
https://blog.csdn.net/hangtianxia/article/details/81990578
-
mybatis
-
&和$区别
- 1 #是将传入的值当做字符串的形式,eg:select id,name,age from student where id =#{id},当前端把id值1,传入到后台的时候,就相当于 select id,name,age from student where id =‘1’.
- 2 是 将 传 入 的 数 据 直 接 显 示 生 成 s q l 语 句 , e g : s e l e c t i d , n a m e , a g e f r o m s t u d e n t w h e r e i d = 是将传入的数据直接显示生成sql语句,eg:select id,name,age from student where id = 是将传入的数据直接显示生成sql语句,eg:selectid,name,agefromstudentwhereid={id},当前端把id值1,传入到后台的时候,就相当于 select id,name,age from student where id = 1.
- 3 使用#可以很大程度上防止sql注入。(语句的拼接)
- 4 但是如果使用在order by 中就需要使用 $.
- 5 在大多数情况下还是经常使用#,但在不同情况下必须使用$.
- 我觉得#与的区别最大在于:#{} 传入值时,sql解析时,参数是带引号的,而的区别最大在于:#{} 传入值时,sql解析时,参数是带引号的,而{}穿入值,sql解析时,参数是不带引号的。
- 一 : 理解mybatis中 $与#
-
在mybatis中的$与#都是在sql中动态的传入参数。
-
eg:select id,name,age from student where name=#{name} 这个name是动态的,可变的。当你传入什么样的值,就会根据你传入的值执行sql语句。
- 二:使用$与#
- #{}: 解析为一个 JDBC 预编译语句(prepared statement)的参数标记符,一个 #{ } 被解析为一个参数占位符 。
- ${}: 仅仅为一个纯碎的 string 替换,在动态 SQL 解析阶段将会进行变量替换。
- name–>cy
- eg: select id,name,age from student where name=#{name} – name=‘cy’
-
select id,name,age from student where name=${name} -- name=cy
XMind: ZEN - Trial Version