围炉网

一行代码,一篇日志,一个梦想,一个世界

架构师系列——中间件

  • RabbitMQ VS Rocket MQ

  • rabbitmq

Exchange分发消息时根据类型的不同分发策略有区别,目前共四种类型:direct、fanout、topic、headers 。headers 匹配 AMQP 消息的 header 而不是路由键,此外 headers 交换器和 direct 交换器完全一致,但性能差很多,目前几乎用不到了

mirror 镜像队列,目的是为了保证 rabbitMQ 数据的高可靠性解决方案,主要就是实现数据的同步,一般来讲是 2 – 3 个节点实现数据同步。对于 100% 数据可靠性解决方案,一般是采用 3 个节点。

依赖 rabbitMQ 的 federation 插件,可以实现持续的,可靠的 AMQP 数据通信,多活模式在实际配置与应用非常的简单。

rabbitMQ 部署架构采用双中心模式(多中心),那么在两套(或多套)数据中心各部署一套 rabbitMQ 集群,各中心的rabbitMQ 服务除了需要为业务提供正常的消息服务外,中心之间还需要实现部分队列消息共享。

  • rocketmq

多 master 多 slave 异步复制模式

在多 master 模式的基础上,每个 master 节点都有至少一个对应的 slave。master

节点可读可写,但是 slave 只能读不能写,类似于 mysql 的主备模式。

优点: 在 master 宕机时,消费者可以从 slave 读取消息,消息的实时性不会受影响,性能几乎和多 master 一样。

缺点:使用异步复制的同步方式有可能会有消息丢失的问题。

多 master 多 slave 同步双写模式

同多 master 多 slave 异步复制模式类似,区别在于 master 和 slave 之间的数据同步方式。

优点:同步双写的同步模式能保证数据不丢失。

缺点:发送单个消息 RT 会略长,性能相比异步复制低10%左右。

刷盘策略:同步刷盘和异步刷盘(指的是节点自身数据是同步还是异步存储)

同步方式:同步双写和异步复制(指的一组 master 和 slave 之间数据的同步)

注意:要保证数据可靠,需采用同步刷盘和同步双写的方式,但性能会较其他方式低。

  • 到底什么时候该用 Topic,什么时候该用 Tag?

建议您从以下几个方面进行判断:

消息类型是否一致:如普通消息、事务消息、定时(延时)消息、顺序消息,不同的消息类型使用不同的 Topic,无法通过 Tag 进行区分。

业务是否相关联:没有直接关联的消息,如淘宝交易消息,京东物流消息使用不同的 Topic 进行区分;而同样是天猫交易消息,电器类订单、女装类订单、化妆品类订单的消息可以用 Tag 进行区分。

消息优先级是否一致:如同样是物流消息,盒马必须小时内送达,天猫超市 24 小时内送达,淘宝物流则相对会慢一些,不同优先级的消息用不同的 Topic 进行区分。

消息量级是否相当:有些业务消息虽然量小但是实时性要求高,如果跟某些万亿量级的消息使用同一个 Topic,则有可能会因为过长的等待时间而“饿死”,此时需要将不同量级的消息进行拆分,使用不同的 Topic。

总的来说,针对消息分类,您可以选择创建多个 Topic,或者在同一个 Topic 下创建多个 Tag。但通常情况下,不同的 Topic 之间的消息没有必然的联系,而 Tag 则用来区分同一个 Topic 下相互关联的消息,例如全集和子集的关系、流程先后的关系。

集群消费:当使用集群消费模式时,消息队列RocketMQ版认为任意一条消息只需要被集群内的任意一个消费者处理即可。

广播消费:当使用广播消费模式时,消息队列RocketMQ版会将每条消息推送给集群内所有注册过的消费者,保证消息至少被每个消费者消费一次。

注意事项

广播消费模式下不支持顺序消息。

广播消费模式下不支持重置消费位点(重置消费位点是为了跳过之前堆积的消息)

每条消息都需要被相同订阅逻辑的多台机器处理。

消费进度在客户端维护,出现重复消费的概率稍大于集群模式。

广播模式下,消息队列RocketMQ版保证每条消息至少被每台客户端消费一次,但是并不会重投消费失败的消息,因此业务方需要关注消费失败的情况。

广播模式下,客户端每一次重启都会从最新消息消费。客户端在被停止期间发送至服务端的消息将会被自动跳过,请谨慎选择。

广播模式下,每条消息都会被大量的客户端重复处理,因此推荐尽可能使用集群模式。

广播模式下服务端不维护消费进度,所以消息队列RocketMQ版控制台不支持消息堆积查询、消息堆积报警和订阅关系查询功能。

更多信息

  • RocketMQ Broker中的消息被消费后会立即删除吗?

不会,每条消息都会持久化到CommitLog中,每个Consumer连接到Broker后会维持消费进度信息,当有消息消费后只是当前Consumer的消费进度(CommitLog的offset)更新了。

追问:那么消息会堆积吗?什么时候清理过期消息?

4.6版本默认48小时后会删除不再使用的CommitLog文件

检查这个文件最后访问时间

判断是否大于过期时间

指定时间删除,默认凌晨4点

在一个Consumer Group内,Queue和Consumer之间的对应关系是一对多的关系:一个Queue最多只能分配给一个Consumer,一个Cosumer可以分配得到多个Queue。这样的分配规则,每个Queue只有一个消费者,可以避免消费过程中的多线程处理和资源锁定,有效提高各Consumer消费的并行度和处理效率。

  • kafka

kafka没有重试机制不支持消息重试,也没有死信队列

Kafka 只是分为一个或多个分区的主题的集合。Kafka 分区是消息的线性有序序列,其中每个消息由它们的索引(称为偏移)来标识。Kafka 集群中的所有数据都是不相连的分区联合。 传入消息写在分区的末尾,消息由消费者顺序读取。 通过将消息复制到不同的代理提供持久性。

kafka高可用性的设计也是进行Replication。为了尽量做好负载均衡和容错能力,需要将同一个Partition的Replica尽量分散到不同的机器。

kafka选取了一个折中的方式:ISR(in-sync replicas)。producer每次发送消息,将消息发送给leader,leader将消息同步给他“信任”的“小弟们”就算成功,巧妙的均衡了确保数据不丢失以及吞吐率。具体的,

在所有的Replica中,leader会维护一个与其基本保持同步的Replica列表,该列表称为ISR(in-sync Replica),每个Partition都会有一个ISR,而且是由leader动态维护。

如果一个replica落后leader太多,leader会将其剔除。如果另外的replica跟上脚步,leader会将其加入。

同步:leader向ISR中的所有replica同步消息,当收到所有ISR中replica的ack之后,leader才commit。

异步:收到同步消息的ISR中的replica,异步将消息同步给ISR集合外的replica。

Kafka的Leader选举是通过在zookeeper上创建/controller临时节点来实现leader选举

  • 推模式有什么缺点?

推送速率难以适应消费速率,推模式的目标就是以最快的速度推送消息,当生产者往 Broker 发送消息的速率大于消费者消费消息的速率时,随着时间的增长消费者那边可能就“爆仓”了,因为根本消费不过来啊。

所以说推模式难以根据消费者的状态控制推送速率,适用于消息量不大、消费能力强要求实时性高的情况下。

拉模式主动权就在消费者身上了,消费者可以根据自身的情况来发起拉取消息的请求。假设当前消费者觉得自己消费不过来了,它可以根据一定的策略停止拉取

  • 拉模式有什么缺点?

消息延迟,毕竟是消费者去拉取消息,但是消费者怎么知道消息到了呢?所以它只能不断地拉取,但是又不能很频繁地请求,太频繁了就变成消费者在攻击 Broker 了。因此需要降低请求的频率,比如隔个 2 秒请求一次,你看着消息就很有可能延迟 2 秒了。

RocketMQ 和 Kafka 都选择了拉模式,当然业界也有基于推模式的消息队列如 ActiveMQ。

RocketMQ 和 Kafka 都是利用“长轮询”来实现拉模式

  • 搜索引擎

Lucene引擎支持比如索引时的分词,查询时的切面(Facet),高亮,拼写检查,搜索建议,相似页面等等

  • SolrCloud是基于Solr和Zookeeper的分布式搜索方案

Collection

Collection在SolrCloud集群中是一个逻辑意义上的完整的索引结构。它常常被划分为一个或多个Shard(分片),它们使用相同的配置信息。

比如:针对商品信息搜索可以创建一个collection。

collection=shard1+shard2+….+shardX

Core

每个Core是Solr中一个独立运行单位,提供索引和搜索服务。一个shard需要由一个Core或多个Core组成。由于collection由多个shard组成所以collection一般由多个core组成。

Master或Slave

Master是master-slave结构中的主结点(通常说主服务器),Slave是master-slave结构中的从结点(通常说从服务器或备服务器)。同一个Shard下master和slave存储的数据是一致的,这是为了达到高可用目的。

Shard

Collection的逻辑分片。每个Shard被化成一个或者多个replication,通过选举确定哪个是Leader。

总的来说,我感觉,ElasticSerach更像一个商业产品,而Solr更像一个软件。Solr的定制能力更强,几乎什么都可以配置。对于开发者来说,要实现一个新的功能,可以不用动Solr核心代码,而给Solr增加一些Processer和Component,然后通过xml配置服务器的行为。同时,Solr还提供了BlobStore,可以上传Jar代码来在Solr集群中部署这些新的插件(像不像HBase的Coprocessor?)。并且Solr独家的On HDFS能力为Solr提供了存储计算分离的便捷性,可以做到shard的自由移动而不用搬迁数据。Solr还支持分裂shard,这些能力都对运维友好,能够在扩容、宕机恢复方面会有更大的灵活性。而ElasticSearch竭尽全力地降低用户的使用门槛,用户可以非常快的上手。同时坚持不引入像Zookeeper这样的额外组件,也是为了降低部署难度。毕竟,ElasticSearch后面有一家强大的商业公司,从客户需求出发,在ElasticSearch上做了非常多丰富的功能,建立起了非常完整的生态,并拥有众多客户案例。对于一般的客户来说,一般会选择一个大而全的产品去满足他们的需求,因为在技术栈中引入一个新产品,本身学习成本和运维成本都会比较大。而ElasticSearch更加地契合了这些用户的需求,ElasticSearch入门简单,不仅可以搜索,还可以分析,同时可以处理时序数据,还在X-Pack中支持机器学习等等功能。我想,这也是目前ElasticSearch更火的原因吧。

  • Redis

Codis 是一个分布式 Redis 解决方案, 对于上层的应用来说, 连接到 Codis Proxy 和连接原生的 Redis Server 没有明显的区别 (不支持的命令列表), 上层应用可以像使用单机的 Redis 一样使用, Codis 底层会处理请求的转发, 不停机的数据迁移等工作, 所有后边的一切事情, 对于前面的客户端来说是透明的, 可以简单的认为后边连接的是一个内存无限大的 Redis 服务.

可以启动多个codis实例来支撑,每个codis节点都是对等的,这样可以增加整体的QPS需求,还能起到容灾功能。

槽位关系

codis根据key直接hash到1024个槽位,每个槽位对应后面的一个redis。计算出key的槽位就能将key转发到正确的redis实例。

槽位关系一旦变化,就会涉及所有redis实例,codis用zookeeper来存储槽位关系(集群配置中心),还提供了一个dashboard模块来观察和修改槽位关系。

槽位关系发生变化时,codis会用一个新的命令来查找指定槽位中的所有key,然后迁移每个key到正确的槽位。当请求打在正在迁移的槽位上时,codis会强制对该key进行迁移。

codis有自动均衡功能,在系统空闲时观察每个redis实例对应的槽位数量,不均衡时就会自动迁移。

cluster是redis官方提供的集群化方案,它与codis不同,它是去中心化的,集群的每个节点负责一部分数据,相互连接形成一个对等的集群,它们之间通过一种特殊的二进制协议相互交互集群信息。

cluster的槽位划分更细,槽位的信息存储于每个节点上,不需要另外的分布式存储来存节点的信息。客户端也存有关于槽位的信息,它可以直接定位到目标节点。

槽位和迁移

cluster允许用户强制将某个key挂在特定槽位上。

当客户端向一个节点发送指令,但是节点已经没有这个key了,此时就会返回-moved,告知客户端应该去这个节点查找,同时客户端修改本地槽位映射表。

迁移时按槽迁移,一次性获取槽位所有key,然后一个key一个key迁移,每个key的迁移过程都是以原节点作为目标节点的客户端,key是先序列化发送,然后接收端再反序列化存入内存,然后返回ok,最后原节点删除该key。

迁移的过程是同步的,原节点的线程一直阻塞,直到迁移成功。

Redis集群预分好16384个桶,当需要在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) mod 16384的值,决定将一个key放到哪个桶中。

redis cluster 为了保证数据的高可用性,加入了主从模式,一个主节点对应一个或多个从节点,主节点提供数据存取,从节点则是从主节点拉取数据备份,当这个主节点挂掉后,就会有这个从节点选取一个来充当主节点,从而保证集群不会挂掉

因为Codis在Redis的基础上的改造,所以在Codis上是不支持事务的,同时也会有一些命令行不支持,在官方的文档上有(Codis不支持的命令)

Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。 

Redis事务相关命令:

  watch key1 key2 … : 监视一或多个key,如果在事务执行之前,被监视的key被其他命令改动,则事务被打断 ( 类似乐观锁 )

  multi : 标记一个事务块的开始( queued )

  exec : 执行所有事务块的命令 ( 一旦执行exec后,之前加的监控锁都会被取消掉 ) 

  discard : 取消事务,放弃事务块中的所有命令

  unwatch : 取消watch对所有key的监控 

Redis Cluster集群架构,不同的key是有可能分配在不同的Redis节点上的,在这种情况下Redis的事务机制是不生效的。其次,Redis事务不支持回滚操作,简直是鸡肋!

在事务运行期间,虽然Redis命令可能会执行失败,但是Redis仍然会执行事务中余下的其他命令,而不会执行回滚操作

Redis支持多个数据库,并且每个数据库的数据是隔离的不能共享,单机下的redis可以支持16个数据库(db0 ~ db15)。在Redis Cluster集群架构下只有一个数据库空间,即db0。

不做读写分离。我们用的是Redis Cluster的架构,是属于分片集群的架构。而redis本身在内存上操作,不会涉及IO吞吐,即使读写分离也不会提升太多性能,Redis在生产上的主要问题是考虑容量,单机最多10-20G,key太多降低redis性能.因此采用分片集群结构,已经能保证了我们的性能。其次,用上了读写分离后,还要考虑主从一致性,主从延迟等问题,徒增业务复杂度。

  • MySQL的高可用方案

Keepalived+mysql双主:两台MySQL互为主从关系,通过Keepalived配置虚 拟IP,实现当其中的一台MySQL数据库宕机后,应用能够自动切换到另外一台MySQL数据库,保证系统的高可 用。 注:master1和master2只有server-id不同和 auto-increment-offset不同。 mysql中有自增长字段,在做数据库的主主同步时需要设置自增长的两个相关配置:auto_increment_offset和 auto_increment_increment。 auto-increment-increment表示自增长字段每次递增的量,其默认值是1。它的值 应设为整个结构中服务器的总数,本案例用到两台服务器,所以值设为2。 auto-increment-offset是用来设定数 据库中自动增长的起点(即初始值),因为这两能服务器都设定了一次自动增长值2,所以它们的起点必须得不同,这样才能避免两台服务器数据同步时出现主键冲突。

keepalived是集群管理中保证集群高可用的一个软件解决方案,其功 能类似于heartbeat,用来防止单点故障 keepalived是以VRRP协议为实现基础的,VRRP全称Virtual Router Redundancy Protocol,即虚拟路由冗余协议。 虚拟路由冗余协议,可以认为是实现路由器高可用的协议,即 将N台提供相同功能的路由器组成一个路由器组,这个组里面有一个master和多个backup,master上面有一个 对外提供服务的vip,master会发组播(组播地址为224.0.0.18),当backup收不到vrrp包时就认为master宕掉 了,这时就需要根据VRRP的优先级来选举一个backup当master。这样的话就可以保证路由器的高可用了。 keepalived主要有三个模块,分别是core 、check和vrrp。core模块为keepalived的核心,负责主进程的启动、 维护以及全局配置文件的加载和解析。check负责健康检查,包括常见的各种检查方式。vrrp模块是来实现 VRRP协议的。

  • 主从复制、读写分离

从机是通过binlog日志从master同步数据的,如果在网络延迟的情况,从机就会出现数据延迟。那么就有可能出现master写入数据后,slave读取数据不一定能马上读出来。有没有事务问题呢?框架进行了处理:同一线程且同一数据库连接内,如有写入操作,以后的读操作均从主库读取,用于保证数据一致性。

druid+ShardingSphere-JDBC

  • MongoDB

Mongodb的三种集群方式的搭建:Replica Set / Sharding / Master-Slaver。

Replica Set(副本集)

其实简单来说就是集群当中包含了多份数据,保证主节点挂掉了,备节点能继续提供数据服务,提供的前提就是数据需要和主节点一致。

Mongodb(M)表示主节点,Mongodb(S)表示备节点,Mongodb(A)表示仲裁节点。主备节点存储数据,仲裁节点不存储数据。客户端同时连接主节点与备节点,不连接仲裁节点。

默认设置下,主节点提供所有增删查改服务,备节点不提供任何服务。但是可以通过设置使备节点提供查询服务,这样就可以减少主节点的压力,当客户端进行数据查询时,请求自动转到备节点上。这个设置叫做Read Preference Modes,同时Java客户端提供了简单的配置方式,可以不必直接对数据库进行操作。

仲裁节点是一种特殊的节点,它本身并不存储数据,主要的作用是决定哪一个备节点在主节点挂掉之后提升为主节点,所以客户端不需要连接此节点。这里虽然只有一个备节点,但是仍然需要一个仲裁节点来提升备节点级别。我开始也不相信必须要有仲裁节点,但是自己也试过没仲裁节点的话,主节点挂了备节点还是备节点,所以咱们还是需要它的。

Sharding

和Replica Set类似,都需要一个仲裁节点,但是Sharding还需要配置节点和路由节点。就三种集群搭建方式来说,这种是最复杂的。部署图如下:

Master-Slaver

这个是最简答的集群搭建,不过准确说也不能算是集群,只能说是主备。并且官方已经不推荐这种方式。

综述

Master-Slaver官方已经不推荐这种方式了。

Sharding比较复杂

Replica Set集群、高可用,部署也简单

mongodb内存管理

mongodb会把所有内存全部用光。

mongodb使用内存映射存储引擎,它会把磁盘IO转化为内存操作。读数据时,直接从内存读取数据;写数据时,将随机写转化为顺序写。mongodb不干涉内存管理,将内存管理工作交给操作系统去处理。在使用时必须随时监测内存使用情况,因为mongodb会把所有能用的内存都用完。

MongoDB数据库单列索引可以不在乎方向,如对无索引字段排序需要控制数据量级(32M)

MongoDB数据库复合索引在使用中一定要注意其方向,要完全理解其逻辑,避免索引失效

  • 内存映射

内存映射是一个文件到一块内存的映射,使应用程序可以通过内存指针对磁盘上的文件进行访问。其过程就像对加载了文件的内存访问。

在映射过程中,并没有实际的数据拷贝,文件并没有被放入内存,只是逻辑上放入了内存。具体到代码,就是建立并初始化了具体的数据结构。当要操作数据的时候,才会将文件数据从硬盘读入内存,如果物理内存不够用,则会使用swap空间。

内存映射比普通文件操作效率高,原因是普通文件读取执行了两次数据拷贝(硬盘–>内核空间–>用户空间),内存映射只进行了一次数据拷贝(硬盘–>用户空间)。而且普通IO操作从用户态转为内核态的不断切换十分耗时,内存映射省去了这个转换。

不推荐mongodb热数据超过物理内存大小。如果超过,操作系统会在物理内存和磁盘swap之间频繁交换数据,影响性能。

  • mongodb存储

mongodb将数据存放在磁盘文件上,一个数据文件有多个events组成,每个event可以保存集合数据或者索引数据(集合和索引数据不会区分保存)。每个events只保存一个集合的数据,不同集合的数据分散在不同的events中,一个集合的数据也被拆分在不同的events中。每个event保存的是单个文档数据。索引数据也是如此。collection的events信息被保存在命名空间文件中,只保存第一个event的信息,然后event之间通过双向链表组成events。event有额外的冗余空间padding,以便在数据更改的时候不用移动数据。

关于数据文件大小,从64M开始,每次新增数据文件,大小是上一个文件的两倍,直到2G为止。而且mongodb使用预分配,在当前数据文件快要用完时提前创建新的数据文件,并用0填充。这样可以使mongodb始终保持充足的空间,防止因为临时分配磁盘空间导致阻塞。

关于命名空间文件,后缀名为.ns,是数据库中最早生成的文件,负责保存数据库中的集合索引的元数据。默认情况下,.ns文件大小为16MB,可以存储24000个命名空间,也就是说数据库集合和索引的总数不超过24000。可以自行修改。

  • mongodb持久化

在传统数据库中,存在一个Redo Log的机制用来应对因为系统崩溃或掉电引发的数据丢失问题。原理是将操作写入日志,然后执行操作,然后操作完成后删除日志;如果发生系统崩溃,重启后首先会从Redo Log中重新执行操作。MongoDB 的journal就是实现这个目的的一种WAL日志。

不止传统数据库,Redo Log机制广泛存在于各种框架系统中。

mongodb的读写全部针对内存进行。如果没有journal,mongodb写入数据仅仅是到内存中,由操作系统决定什么时候将数据持久化到硬盘上,mongodb仅仅提供60s一次的强制写入硬盘。如果出现系统崩溃,那么从上一次刷盘到现在的数据就会丢失,一次丢失60s的数据。

从2.0开始,mongodb默认开启journal。数据写入首先会写入Journal Buffer,然后再写入内存,Journal Buffer每隔100ms写入磁盘,提交为单事务,全部成功或全部失败。这样就算系统崩溃,mongodb重启后会根据Journal文件恢复数据,最多丢失100ms数据。这个提交时间可以修改,从2ms到300ms之间。

如果journal开启,内存大小差不多翻番

如果不能忍受100ms的数据丢失,可以通过设定写关注来调整写入的可靠性。这里是通过放弃部分写入性能来提高写入的可靠性。

{w: 0} Unacknowledged:对每个写入操作,mongodb不会返回一个是否成功的状态文档。通过放弃一切安全性换来了最好的性能。在这个级别下,向mongodb写入操作如果失败,客户端也不会有任何提示。很多使用mongodb保存日志文件使用这个级别,数据量大,就算丢失一两条数据也无所谓。

{w: 1} Acknowledged:对每个写入操作,mongodb会确认数据写入主节点内存的情况。可以看重复主键, 网络错误,系统故障或者是无效数据等错误。从2.4开始,这个级别是默认级别。如果写入过程中发生了系统崩溃,只会损失内存中的100ms数据。

{j:1} Journaled:对于每个写入操作,mongodb会确认数据通过journal落盘后才会返回。并不是每一次写操作都会刷新硬盘,mongodb在每次写操作后最多等待30ms,把30ms内的数据顺序写入硬盘。在这30ms内客户端处于等待状态。对于单个操作会有额外的延迟,但是对于高并发情况下,延迟和吞吐并不会有多大影响。如果存储系统针对顺序写进行优化,整个延迟会被降到最低。

这里,如果mongodb是单机,那么以上级别已经足够安全。在集群情况下,可以使用下一个级别。

{w: “majority”}:对于每个写入操作,写入到多数节点。mongodb的复制集一般为奇数节点。这个级别可以保障数据在集群环境中的一致性。

  • HDFS

hadoop 2.x默认块大小为128M. hadoop3.x默认块大小为256M

建议设置块大小为接近你硬盘的输出速率的2的N次方。比如你的硬盘平均输出速率为200M/s.那么就设置成256M。如果是400M/s,就设置成512M.

  • Oceanbase

充分利用大内存,2M大数据块(oracle, mysql块大小可以设置为8K,16K等几种),高效压缩(利用数据字典进行压缩,支付宝某业务从数据由100TB压缩到33TB)

分布式事务会使用两阶段提交并做了一些优化

用paxos协议保证数据副本一致,故障时自动切主

多种容灾模式(延迟指commit时城市间数据同步的延迟)(五副本是因为要以多数副本为准)

  • tpmc

流量指标(tpmC):描述了系统在执行 Payment,Order-Status,Delivery,Stock-level 这四种交易的同时,每分钟可以处理的 New-Order 交易的数量。

tpm 是 transactions per minute 的简称;C 指 TPC 中的 C 基准程序。它的定义是每分钟内系统处理的新订单个数。要注意的是,在处理新订单的同时,系统还要按图 1 的要求处理其 它 4 类事务 请求。从图 1 可以看出,新订单请求不可能超出全部事务请求的 45%,因此,当一个 系统的性能为 1000tpmC 时,它每分钟实际处理的请求数是 2000 多个。

  • Spark Streaming与Storm比较

Storm处理的是每次传入的一个事件,而Spark Streaming是处理某个时间段窗口内的事件流。因此,Storm处理一个事件可以达到亚秒级的延迟,而Spark Streaming则有秒级的延迟。

在Storm中,当每条单独的记录通过系统时必须被跟踪,所以Storm能够至少保证每条记录将被处理一次,但是在从错误中恢复过来时候允许出现重复记录,这意味着可变状态可能不正确地被更新两次。而Spark Streaming只需要在批处理级别对记录进行跟踪处理,因此可以有效地保证每条记录将完全被处理一次,即便一个节点发生故障。虽然Storm的 Trident library库也提供了完全一次处理的功能。但是它依赖于事务更新状态,而这个过程是很慢的,并且通常必须由用户实现。

简而言之,如果你需要亚秒级的延迟,Storm是一个不错的选择,而且没有数据丢失。如果你需要有状态的计算,而且要完全保证每个事件只被处理一次,Spark Streaming则更好。Spark Streaming编程逻辑也可能更容易,因为它类似于批处理程序,特别是在你使用批次(尽管是很小的)时。

  • CNCF 云原生 Landscape路线图

整个路线图分成了十个步骤,每个步骤都是用户或平台开发者将云原生技术在实际环境中落地时,需要循序渐进思考和处理的问题:

1. 容器化。目前最流行的容器化技术是Docker,你可以将任意大小的应用程序和依赖项,甚至在模拟器上运行的一些程序,都进行容器化。随着时间的推移,你还可以对应用程序进行分割,并将未来的功能编写为微服务。

2. CI/CD(持续集成和持续发布)。创建CI/CD环境,从而使源代码上的任意修改,都能够自动通过容器进行编译、测试,并被部署到预生产甚至生产环境中。

3. 应用编排。Kubernetes是目前市场上应用编排领域被最广泛应用的工具,Helm Charts可以用来帮助应用开发和发布者用于升级Kubernetes上运行的应用。

4. 监控和分析。在这一步中,用户需要为平台选择监控、日志以及跟踪的相关工具,例如将Prometheus用于监控、Fluentd用于日志、Jaeger用于整个应用调用链的跟踪。

5. 服务代理、发现和治理。CoreDNS、Envoy和LInkerd可以分别用于服务发现和服务治理,提供服务的健康检查、请求路由、和负载均衡等功能。

6. 网络。Calico、Flannel以及Weave Net等软件用于提供更灵活的网络功能。

7. 分布式数据库和存储。分布式数据库可以提供更好的弹性和伸缩性能,但同时需要专业的容器存储予以支持。

8. 流和消息处理。当应用需要比JSON-REST这个模式更高的性能时,可以考虑使用gRPC或者NATS。gRPC是一个通用的RPC(远程调用)框架(类似各种框架中的RPC调用),NATS是一个发布/订阅和负载均衡的消息队列系统。

9. 容器镜像库和运行环境。Harbor是目前最受欢迎的容器镜像库,同时,你也可以选择使用不同的容器运行环境用于运行容器程序。

10. 软件发布。最后可以借助Notary等软件用于软件的安全发布。

企业服务总线技术架构:资源申请慢,复用性差,高可用性差

虚拟机依赖人工调度,共享存储限制规模

高可用依赖底层FT(Fault Tolerant), HA (High Availability), DR (Disaster Recovery) 

全面依赖运维,大量的审批

多种多样的中间件,每个团队独立选型中间件,没有统一的维护,没有统一的知识积累,无法统一保障SLA

服务化加速业务创新

架构腐化问题、架构耦合问题,技术债务问题。解决方案:

可理解性:工程简洁,职责单一,易修改,易review

可测试性:单元测试覆盖度高,集成测试容易开展

可观测性:成立架构委员会,制定规范,实时监测

  • 2PC

两阶段提交协议为了保证分布在不同节点上的分布式事务的一致性,我们需要引入一个协调者来管理所有的节点,负责各个本地资源的提交和回滚,并确保这些节点正确提交操作结果,若提交失败则放弃事务。

2PC问题

同步阻塞问题:二阶段提交算法在执行过程中,所有参与节点都是事务阻塞型的。也就是说,当本地资源管理器占有临界资源时,其他资源管理器如果要访问同一临界资源,会处于阻塞状态。

协调者单点故障导致参与者长期阻塞问题:基于 XA 的二阶段提交算法类似于集中式算法,一旦事务管理器发生故障,整个系统都处于停滞状态。尤其是在提交阶段,一旦事务管理器发生故障,资源管理器会由于等待管理器的消息,而一直锁定事务资源,导致整个系统被阻塞。

数据不一致问题:在提交阶段,当协调者向参与者发送 DoCommit 请求之后,如果发生了局部网络异常,或者在发送提交请求的过程中协调者发生了故障,就会导致只有一部分参与者接收到了提交请求并执行提交操作,但其他未接到提交请求的那部分参与者则无法执行事务提交。于是整个分布式系统便出现了数据不一致的问题。

二阶段无法解决的问题:协调者再发出DoCommit 消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。

  • 3PC

三阶段提交引入了超时机制和准备阶段。

超时机制

同时在协调者和参与者中引入超时机制。如果协调者或参与者在规定的时间内没有接收到来自其他节点的响应,就会根据当前的状态选择提交或者终止整个事务。

准备阶段

在第一阶段和第二阶段中间引入了一个准备阶段,也就是在提交阶段之前,加入了一个预提交阶段。在预提交阶段排除一些不一致的情况,保证在最后提交之前各参与节点的状态是一致的。

由于存在超时机制,即使协调者发生故障,参与者无法及时收到来自协调者的信息之后,他会默认执行commit。避免参与者长期阻塞。

3PC会在2阶段到3阶段间阻塞,2PC会在1阶段到2阶段整个事务过程中阻塞,因而总体来说3PC并不能不阻塞,只是最大限度减少了阻塞的时间。

3PC和2PC都无法解决数据一致的问题,不过3PC存在超时会通过超时保证协调者和参与者在提交阶段无法通信过程中最终一致,而不需人工介入。

  • Seata

Seata 还支持 TCC 和 Saga 模式,但支持的主要方式是 AT。

AT 模式基于 支持本地 ACID 事务 的 关系型数据库:

一阶段 prepare 行为:在本地事务中,一并提交业务数据更新和相应回滚日志记录。

二阶段 commit 行为:马上成功结束,自动 异步批量清理回滚日志。

二阶段 rollback 行为:通过回滚日志,自动 生成补偿操作,完成数据回滚。

相应的,TCC 模式,不依赖于底层数据资源的事务支持:

一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。

二阶段 commit 行为:调用 自定义 的 commit 逻辑。

二阶段 rollback 行为:调用 自定义 的 rollback 逻辑。

所谓 TCC 模式,是指支持把 自定义 的分支事务纳入到全局事务的管理中。

缺乏隔离性的应对

由于 Saga 事务不保证隔离性, 在极端情况下可能由于脏写无法完成回滚操作, 比如举一个极端的例子, 分布式事务内先给用户A充值, 然后给用户B扣减余额, 如果在给A用户充值成功, 在事务提交以前, A用户把余额消费掉了, 如果事务发生回滚, 这时则没有办法进行补偿了。这就是缺乏隔离性造成的典型的问题, 实践中一般的应对方法是:

业务流程设计时遵循“宁可长款, 不可短款”的原则, 长款意思是客户少了钱机构多了钱, 以机构信誉可以给客户退款, 反之则是短款, 少的钱可能追不回来了。所以在业务流程设计上一定是先扣款。

有些业务场景可以允许让业务最终成功, 在回滚不了的情况下可以继续重试完成后面的流程, 所以状态机引擎除了提供“回滚”能力还需要提供“向前”恢复上下文继续执行的能力, 让业务最终执行成功, 达到最终一致性的目的。

  • dubbo核心能力:

面向接口的远程方法调用,为开发者屏蔽远程调用底层细节

智能容错和负载均衡

服务自动注册和发现:Dubbo目前支持4种注册中心,(multicast zookeeper redis simple)推荐使用Zookeeper注册中心

高度可扩展能力:遵循微内核+插件的设计原则,所有核心能力如Protocol、Transport、Serialization被设计为扩展点,平等对待内置实现和第三方实现。

运行期流量调度:内置条件、脚本等路由策略,通过配置不同的路由规则,轻松实现灰度发布,同机房优先等功能。

可视化的服务治理与运维,提供丰富服务治理、运维工具:随时查询服务元数据、服务健康状态及调用统计,实时下发路由策略、调整配置参数。

  • SPI(Service Provider Interface)

JDK内置的⼀个服务发现机制,它使得接⼝和具体实现完全解耦。我们只声明接⼝,具体的实现类在配置中选择。具体的就是你定义了⼀个接⼝,然后在META-INF/services ⽬录下放置⼀个与接⼝同名的⽂本⽂件,⽂件的内容为接⼝的实现类,多个实现类⽤换⾏符分隔。这样就通过配置来决定具体⽤哪个实现!

  • Dubbo SPI

Java SPI 在查找扩展实现类的时候遍历 SPI 的配置文件并且将实现类全部实例化,假设一个实现类初始化过程比较消耗资源且耗时,但是你的代码里面又用不上它,这就产生了资源的浪费。所以说 Java SPI 无法按需加载实现类。

Dubbo 通过一个代理机制实现了自适应扩展,简单的说就是为你想扩展的接口生成一个代理类,可以通过JDK 或者 javassist 编译你生成的代理类代码,然后通过反射创建实例。根据请求的参数,即 URL 得到具体要调用的实现类名,然后再调用 getExtension 获取。

  • 面向对象设计原则

单一职责原则(Single Responsibility Principle – SRP)一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分

开放封闭原则(Open Closed Principle – OCP)对于扩展应该是开放的,但对于修改应该是封闭的。当应用的需求改变时,在不修改软件实体的源代码或者二进制代码的前提下,可以扩展模块的功能,使其满足新的需求。可以通过“抽象约束、封装变化”来实现开闭原则,即通过接口或者抽象类为软件实体定义一个相对稳定的抽象层,而将相同的可变因素封装在相同的具体实现类中。

里氏替换原则(Liskov Substitution Principle – LSP)在代码中可以将父类全部替换为子类,程序不会报错,也不会在运行时出现任何异常,但反过来却不一定成立。子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。

最少知识原则(Least Knowledge Principle – LKP)如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。

尽量减少对象之间的交互,从而减小类之间的耦合。简言之,一定要做到:低耦合,高内聚。

接口隔离原则(Interface Segregation Principle – ISP)不要对外暴露没有实际意义的接口。客户端不应该被迫依赖于它不使用的方法。单一职责原则注重的是职责,而接口隔离原则注重的是对接口依赖的隔离。单一职责原则主要是约束类,它针对的是程序中的实现和细节;接口隔离原则主要约束接口,主要针对抽象和程序整体框架的构建。

依赖倒置原则(Dependence Inversion Principle – DIP)高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。要面向接口编程,不要面向实现编程。依赖倒置原则是实现开闭原则的重要途径之一

六大原则的英文首字母拼在一起就是 SOLID(稳定的),所以也称之为 SOLID 原则

合成复用原则又叫组合/聚合复用原则:它要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。

  • 3种常见的微服务编排方式

1、Orchestration(编制)

Orchestration面向可执行的流程:通过一个可执行的流程来协同内部及外部的服务交互,通过流程来控制总体的目标、涉及的操作、服务调用顺序。Orchestration实现方案多是同步的。

优点:

流程控制服务时时刻刻都知道每一笔业务究竟进行到了什么地步,监控业务成了相对简单的事情。

缺点:

1)流程控制服务很容易控制了太多的业务逻辑,耦合度过高,变得臃肿。

2)各个微服务退化为单纯的增删改查,失去自身价值。

2、Choreography(编排)

Choreography面向协作:通过消息的交互序列来控制各个部分资源的交互,参与交互的资源都是对等的,没有集中的控制。

Choreography可以看作一种消息驱动模式,或者说是订阅发布模式,每笔业务到来后,各个监听改事件的服务,会主动获取消息,处理,并可以按需发布自己的消息。可以把不同队列看作不同种类的消息,微服务看作消息处理函数。

Choreography实现方案多是异步的。

优点:耦合度低,每个服务都可以各司其职。

缺点:

1)业务流程是通过订阅的方式来体现的,很难直接监控每笔业务的处理,因此难于调试。

2)由于没有预定义流程,所以很难在事前保证流程正确性,基本靠事后分析数据来判断。

3)当一个业务流程会嵌入到多个服务中时,维护会很困难。

建议:

1)小粒度的服务需要组合,服务的粒度越小,越需要组合。

2)增加相应的监控系统,来保证业务顺畅进行。

3、API网关

API网关可以看作一种简单的接口聚合/拆分的方式:每笔业务到来后先到达网关,网关调用各微服务,并最终聚合/拆分需反馈的结果。

API网关其实就是一个适配网关,比如对于Web端,可以一个页面同时发起几十个请求,而对于移动端,最好是一个页面就几个请求。而采用API网关,后面的微服务可以是相同的。

优点:

对外接口相对稳定。

缺点:

只适合业务逻辑较为简单的场景,业务逻辑过于复杂时,网关接口耦合度及复杂度会急剧升高,变得臃肿。

  • activiti工作流引擎

实现业务流程变化后,程序代码不需要改动。常见应用流域:行政管理、人事管理

业务流程规范化,通过流程设计器activiti designer配置生成xml文件

BPMN(Business Process Model And Notation)业务流程模型和符号

Activiti流程设计器在IDEA或Eclipse中都有工具插件

可以通过运行java代码的方式(包里定义了很多相关的service)创建25张工作表、部署流程定义(可以是zip文件或者xml文件)、获取流程实例、每个用户可以通过流程定义的Key获得任务列表

任务完成后任务的完成时间会被更新并插入一条新的待完成任务且其结束时间为null

当正在执行的流程没有完全审批结束的时候,如果要删除流程定义信息就会失败。如果要强制删除可以采用级联删除

  • 服务治理:

  1. 服务设计:采用分层、分治的架构设计理念;各个模块之间应该是高内聚、低耦合

  2. 高可用:通过注册中心实现服务的自动注册和发现;针对故障,防止系统崩溃,还设计了服务熔断和服务降级

  3. 可扩展:技术层面,通过注册中心获得了横向扩展的能力;业务层面,通过微服务的架构设计,使得业务也获得了扩展的能力

  4. 高并发:通过服务限流、

  5. 大数据:垂直拆库,水平拆表;拆库拆表   proxy代理:mycat, atlas,sharding-proxy (好处是跨语言); 直连:sharding-jdbc (在jar包中判断写的请求发到master, 读的请求发到slave,可以指定规则比如根据userid进行分库,orderid进行分表)

    @HystrixCommand(commandProperties = {

            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),

            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000")

    })

    @HystrixCommand(fallbackMethod = "hiError")

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

沪ICP备15009335号-2