manout's blog

Something about me

异地多活指分布在异地的多个站点同时对外提供服务的场景。异地多活是高可用架构设计的一种,与传统的灾备设计的最主要区别在于多活,即所有站点都是同时在对外提供服务的,以下整理自知乎开发者头条

CAP 理论

CAP 定理,指对于一个分布式计算系统而言,不可能满足同时满足以下三点

  • 一致性(Consistency)
    所有节点访问同一份最新的数据副本
  • 可用性(Availability)
    每次请求都能获取到非错的响应,但是不保证为最新的数据
  • 分区容错性(Partition tolerance)
    以实际效果而言,分区相当于对通信的时限要求。系统如果不能再时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在 C 和 A 之间做出选择

原理

异地多活本质上是一个分布式的 AP 方案,为了实现异地多活方案,必须要做出一部分的取舍,这个取舍就是对一致性上的取舍。

架构方案

关于数据的异地多活方案,架构通常有以下几种

  • 同城异区
  • 跨城异地
  • 跨国异地

同城异区

同城异区下,DC 之间包含以下特点

  • 延迟小于 10ms
  • 搭建高速网络
  • 极端灾难不能避免
  • 复杂度,成本,故障发生概率是应对多机房故障最优架构

跨城异地

  • 网络环境复杂,延迟不确定,大致 50ms -> 1s
  • 大概率避免极端灾难
  • 距离远,搭建专用网络通道复杂度和成本高
  • 肯定会出现数据不一致,需要保证数据短时间不一致能提供服务
  • 要求数据高度一致的服务不能跨城异地,例如支付

跨国异地

  • 为不同地区用户提供服务
  • 只读类业务多活

设计方面

三个原则

由于物理限制,实时一致性无法保证,这是要保证最终一致性,并且无法做到全部业务的异地多活,只需要实现核心业务的多活即可。并且要保证大部分用户的核心业务可用。

核心数据的异地多活

  • 物理限制,不可能做到数据实时同步
  • 减少数据不能实时同步的方案
    • 减少异地机房的距z离,搭建高速网络
    • 减少数据同步,只同步核心业务的关键数据
  • 保证最终一致性,不保证实时一致性
    • 业务不依赖与数据的同步的实时性,只要最终一致性即可

采用多种手段同步数据

  • 思维误区: 只使用存储系统的同步功能
    • MySQL 低版本的单线程复制,延迟时间长
    • Redis master slave 的全量同步,slave 不能对外服务
  • 消息队列
  • 二次读取
    • 读取本地机房失败,去另一个机房读
  • 回源读取
    • 根据路由判断数据的位置,去读取
  • 重新生成数据
    • session 重新生成,重新登录
  • 存储系统本身机制同步

在分布式数据库中首先要解决数据集按照分区的规则划分到多个节点,每个节点负责整体数据的一个子集,大致如下图。

dist_database

划分规则

目前常见的划分规则有顺序分区,哈希分区

顺序分区

数据分布于业务相关,可顺序访问,但是离散度易倾斜

哈希分区

数据分布于业务无关,不可顺序访问,离散度较好

哈希分区又可根据算法和规则的不同分为以下几种

  • 节点取余
  • 一致性哈希
  • 虚拟槽

节点取余分区

对于键值 key, 和节点数量 N ,利用公式 计算出一个位于 [0, N - 1] 区间的值用来决定放在哪个节点上

这样做的优点在于简单,但是问题在于节点数量 N 变化时(扩容或者收缩), 数据和节点之间的映射关系需要重新计算,这样的话,就需要进行数据迁移,否则之前的数据就查询不到

一致性哈希分区

在一致性哈希中 ,把所有的数据包括节点数据都放在一个哈希环上,除了需要计算要存储的 key 的 hash 之外,还要计算节点的 hash, 然后在存储是,选择一个与 key 的 hash 最接近的 hash (顺时针找到第一个 >= hash 的节点), 存储进去

优点在于加入和删除节点只影响哈希环中相邻的节点,对其他的节点无影响。
问题在于

  • 加减节点,会造成哈希环中部分数据无法命中,需要手动处理或者忽略这部分数据,因此一致性哈希常用于缓存(redis 的全内存方案)
  • 当使用少量节点时,节点变化将大范围影响哈希环中数据映射,因此不适合少量数据节点的分布式方案
  • 普通的一致性哈希分区在增加节点时需要增加一倍或减去一半节点才能保证数据和负载的均衡

虚拟槽分区

虚拟槽分区为了解决一致性哈希分区的不足而创建的。
虚拟槽分区使用分散度良好的 hash 函数,将 key 映射到固定范围内的整数集合中,整数定义为槽(slot).
这个范围一般远远大于节点数,比如 redis Cluster 槽的范围是 0 - 16383. 一共 16384 个槽

槽概念

redis cluster 中有一个长度为 16384 槽概念,他们的编号为0, 1, 2 ,3 … 16382, 16383. 作为哈希槽使用。槽是集群内数据管理和迁移的基本单位,采用大范围槽的主要目的是为了方便数据拆分和集群扩展。
正常工作的时候,redis cluster 中的每个 master 节点都会负责一部分的槽,当某个 key 映射到某个 Master 负责的槽,那么这个 Master 负责为这个 key 提供服务。
可以有用户指定 Master 节点与槽的对应关系,也可以在初始化时由脚本(redis-trib.rb) 自动生成。
只有 Master 才拥有槽的所有权,如果是某个 Master 才拥有槽的所有权,如果是某个 Master 的 Salve, 这个槽只负责槽的使用,但是没有所有权。

数据分片

在 Redis Cluster 中,拥有 16384 个 slot, 这个数是固定的,存储在 Redis Cluster 中的所有的键都会被映射到这些 slot 中。数据库中的每个键都属于这 16384 个哈希槽中的一个。
集群用公式 CRC16(key) % 16384 计算 key 数于哪个槽。其中 CRC16(key) 语句用于计算 key 的 CRC16 校验。集群中的每个节点负责处理一部分哈希槽。

节点的槽指派信息

clusterNode 结构的 slots 和 numSlots 属性记录了节点负责接待哪些槽

1
2
3
4
struct clusterNode {
//
unsigned char slots[16384/8];
};

slots 为一个二进制位数组,共包含 2048 个 byte, 16348 个 bit. 如果拥有哪个槽,那么槽序号对应的 bit 就为 1, 事件复杂度为O(1)

请求重定向

由于每个节点只负责部分 slot, 以及 slot 可能从一个节点迁移到另一个节点,造成客户端有可能会向错误的节点发起请求。
此时需要一种机制对其进行发现和修正,这就是请求重定向,有两种不同的重定向场景

MOVED 错误

  • 请求的 key 对应的槽不在该节点上,节点将查看自身内部所保存的哈希槽到节点 ID 的映射记录,节点回复一个 MOVED 错误.
  • 需要客户端进行再次重试

ASKED 错误

  • 请求的 key 对应的槽目前处于 MIGRATING 状态,并且当前节点找不到这个 key 了,节点回复 ASK 错误。ASK 会把对应槽的 IMPORTING 节点返回,告知客户端到 IMPORTING 的节点查询
  • 客户端进行重试首先发送 ASKING 命令,节点将为客户端设置一个一次性的标志(flag), 使得客户端可以执行一次针对 IMPORTING 状态的槽的命令请求,然后再发送真正的命令请求。
  • 不必更新客户端所记录的槽到节点的映射

golang 内置对协程的支持,十分便于开发高并发程序,现在深入了解下 goroutine 的实现

goroutine 的使用

使用 golang 的协程支持只需要使用 go 关键字即可,示例如下

1
2
3
4
5
6
7
8
9
10
// Create a new goroutine and execute the method
go DoSomeThing()
// Create a new anonymous method and run it in a goroutine
go func () {

}()
// Create a goroutine directly and execute the code block in the goroutine
go {

}

配置 runtime.GOMAXPROCS

可以使用 runtime.GOMAXPROCS 确定进程占用的核数

并行与并发的不同

进程,线程与处理器

现代 OS 中, 线程是 CPU 调度和执行的基本单位。进程是资源分配的基本单位。
每个进程包括私有虚拟地址空间,代码段,数据和其他的系统资源。
线程是一个进程中的一个执行单元。
一个进程至少会有一个线程。

并行与并发

对于并发和并行,应该从进程和线程的角度理解

并发

在一个时间段内,同时有线程在执行,但是在一个特定的时间点内,只有一个线程在执行。
多个线程通过调度算法比如(时间片)由 OS 调度执行

并行

在同一个时间点,有多个线程在执行。
并行程序需要硬件的支持,通常需要运行在多核处理器上

多个不同的多线程模型

用户态和内核态

多线程的实现可分为用户态线程和内核态线程

  • 用户态线程
    由用户的代码实现
  • 内核态线程
    由 OS 的支持

多线程模型

多对一模型模型

多个用户态的线程可对应于一个内核态的线程,线程的调度完全在用户空间实现。
用户态线程对 OS 不可见

由于多个用户态线程对应于一个内核态线程实例,如果该线程陷入内核态比如 I/O 请求,并且内核态线程在阻塞在 I/O 请求,所有的用户态线程都会阻塞,用户态线程可以使用非阻塞的 I/O 方法,但是同样会有性能和复杂性的问题

一对一模型

每个用户态线程都映射到内核态线程。

多对多模型

用户态线程和内核态线程的个数比为 M:N.
这样的模型需要内核态调度和用户态调度的交互,这样当发生线程的上下文切换时大部分发生在用户态,这样能够提高程序的性能,并且充分利用多核处理器的计算资源

goroutine 机制的调度实现

goroutine 实现了上面的 M:N 模型,goroutine 机制的底层是使用了协程来实现。golang 内置的调度器能够允许协程在多核 CPU 上的执行。

调度器的工作方式

在 golang 内置的调度器中有四种结构,分别为 M, G, P 和 Sched
M, G, P 生命在 runtime.h, Sched 声明在 proc.h

M 结构

表示系统内核态线程,并有 OS 管理。goroutine 在 M 上运行。
M 结构包含了很多底层相关信息,比如小对象缓存,当前正在执行的 goroutine, 随机数生成器

P 结构

代表 CPU, 主要是为了执行 goroutine, 这个结构维护一个 goroutine 队列,保证 Sched 能够支持 N:1 到 M:N 不同模型的调度

G 结构

是 goroutine 实现的核心结构。包括运行栈,指令指针 和其他调度 goroutine 的信息,比如阻塞的通道

Sched 结构

调度器,维护一个存储 M 和 G 的队列以及其他调度器的状态

调度方式

在一个 M 结构中,会保存当前的上下文(P),并且运行一个 goroutine 实例(G).
多个 goroutine 会绑定在对应的 P 中,同一时刻只有 goroutine 在运行,可是其他的 goroutine 会处于可调用或者阻塞的状态。

系统调用

当一个内核态线程陷入系统调用并且阻塞时,调度器会将绑定在这个 P 上的其他 goroutine 转移到其他 M 上。

steal work

当在 context runqueue 不平衡时,调度器会将合适 M 上的处于等待状态 goroutine 实例转移到空闲的 M 上。

汇编层面的实现

示例代码

1
2
3
4
5
6
7
func test(i int) int {
return i + 1
}

func demo() {
go test(1)
}

重点关注 go 语句的汇编实现

1
2
3
4
5
6
7
8
9
pcdata  $2, $0
pcdata $0, $0
movq $1, 16(SP)
movl $16, (SP)
pcdata $2, $1
leaq "".test·f(SB), AX
pcdata $2, $0
movq AX, 8(SP)
call runtime.newproc(SB)

pcdata 指令用来包含一些垃圾收集器需要的信息,由编译器产生

主要逻辑为操作数入栈后调用 runtime.newproc 生成新的 goroutine。

kitc(kite 的客户端代码) 中的 kitc_client.go 源码有 500+ 行

代码组成

接口

源码中包含以下接口

  • discoverChangeHandler
  • userErrCBChangeHandler
  • NewClient
  • NewWithThriftClient
  • newWithThrfitClient
  • SetChain
  • initMWChain
  • Call
  • initRPCInfo
  • Name
  • instanceCBChangeHandler
  • Options
  • RemoteConfigs
  • ServiceInstances
  • RecentEvents
  • ServiceCircuitbreaker
  • InstanceCircuitbreaker
  • pushEvent
  • serviceCBChangeHandler
  • GetRPCConfig

结构

  • KitcClient

KitcClient

结构组成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type KitcClient struct {
meshMode bool
name string
opts *Options
client Client

userErrBreaker *circuit.Pannel
serviceBreaker *circuit.Panel
instanceBreaker *circuit.Panel
pool *connpool.ConnPool
discoverer *kitcDiscoverer
remoteConfiger *remoteConfiger
eventQueue *kitevent.Queue
loadbalancer loadbalancer.LoadBalancer

chain endpoint.Middleware
once sync.Once
}

成员解读

client

client 是一个接口,声明如下

1
2
3
type Client interface {
New(kc *KitcClient) Caller
}

应由 kitool 工具自动生成

Call

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
func (kc *KitcClient) Call(method string, ctx context.Context, request interface{}) {
metricsClient.EmitCounter("kite.requset.throughput", 1, "", nil)

kc.once.Do(kc.initMWChain)

if !ServiceMeshMode {
if kc.opts.LoadBalancer != nil || kc.opts.IDCHashFunc != nil {
ctx = context.WithValue(ctx, lbkey, request)
}
}

if ServiceMeshMode {
if consist, ok := kc.opts.Loadbalancer.(*loadbalancer.ConsistBalancer); ok {
if ringhashKey, err := consist.GetKey(ctx, request); err == nil && len(ringhashKey) > 0 {
ctx = context.WithValue(ctx, RingHashKeyType(":CH"), ringhashkey)
}
}
}

ctx, err := kc.infoRPCInfo(method, ctx)
if err != nil {
return nil, err
}

caller := kc.client.New(kc)
next, request := caller.Call(method, request)
if next == nil || request == nil {
return nil, fmt.Errorf("service=%s method=%s unknow method return nil", kc.name, method)
}

ctx = context.WithValue(ctx, KITECLIENTKEY, kc)
resp, err := kc.chain(next)(ctx, request)
if _, ok := resp.(endpoint.KitcCallResponse); ! ok {
return nil, err
}
return resp.(endpoint.KitcCallResponse), err
}

这部分代码中的具体的实现依赖于 kitool 实现的客户端部分。
该方法的实际调用者为由 thrift 生成的中的继承自 kitcClient 的实例, 中间 caller.Call 接口是由 thrift 文件中生成的。

这个方法是 client 向 RPCServer 发起 RPC 请求,根据 method 参数确认要调用的方法

逻辑分析

  • 在监控中增加计数
  • 初始化 RPC info
  • caller 调用方法
  • 调用 middleWare
  • 返回 response

kite.go 源码大概有 200+ 行

代码组成

kite.go 包含以下公共接口和模块内部成员

  • SetIDL
  • NewAccessLogger
  • NewCallLogger
  • GetLocalIp
  • Init
  • Run
  • WaitSignal
  • MethodContext
  • DefineProcessor
  • Use
  • KiteMW
  • AddMethodMW

内部成员

  • userMW endpoint.Middleware
  • mMap map[string]endpoint.Middleware
  • RPCServer *RPCServer
  • IDLs map[string]string
  • Processor thrfit.TProcessor
  • KiteVersion const string

Init

代码逻辑

  • 如果存在环境变量 GOMAXPROCSMY_CPU_LIMIT 设置 runtime.GOMAXPROCS
    runtime.GOMAXPROCS 为 go 程序所能占用的最大核数,也可以理解为线程数
  • 读取配置,配置优先级
    配置文件 > 环境变量 > 参数
  • 初始化 init Data bus config
  • 初始化 access logger
  • 初始化 kitc 的 call logger
  • 创建 RPCServer 的实例

Run

源码

1
2
3
4
5
6
7
func Run() error {
errch := make(chan error, 1)
go func () {
errch <- server
}()
return waitSignal(errch)
}

逻辑

  • 启动一个 goroutine, 在其中启动 RPCServer.Serve() 服务
  • 等待系统中断信号并退出

waitSignal

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func waitSignal(errCh chan error) error {
signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGINT, syscall.SIGHUP, syscall.SIGTERM)

defer trace.Close()
WaitSignal:
for {
select {
case sig := <- signals:
switch sig {
case syscall.SIGTERM:
StopRegister()
return error.New(sig.String())
case syscall.SIGHUP, syscall.SIGINT:
StopRegister()
RPCServer.Stop()
break WaitSignal
}
case err := <- errch:
StopRegister()
return err
}
}
err := <- errch
if err != nil {
logs.Fatal("KITE: AcceptLoop error: %s\n", err)
}
return err
}

代码逻辑

  • 设置 os 的中断信号由 signals 通道接受
  • 等待系统的中断并处理

redis 关键技术

  • 事件循环
  • 过期 & 逐出
  • 持久化
  • 主从复制
  • pipeline & mget
  • redis 集群

事件循环

redis 服务在启动后会陷入一个巨大的 while 循环,不停地处理文件事件和时间时间

  • 文件事件
    诸如连接上的请求和关闭连接等
  • 时间事件
    定时任务

文件事件

在多个客户端中实现多路复用(epoll 实现),接受发来的命令请求,并将命令的执行结果返回客户端

  • read 事件
    新建连接,接受请求
  • write 事件
    返回结果

逻辑如下

redis_file_event

时间事件

要在指定时间点运行的事件,多个时间时间以无序链表的形式保存在服务器状态中

redis_time_event

常有的时间事件如下

  • 更新服务器各类统计信息,如时间,内存占用等
  • 数据库后台操作,key 过期清理,数据库 rehash 等
  • 关闭,清理失效的客户端连接
  • 检查是否需要 RDB dump, AOF 重写
  • 主节点,对从节点定期同步
  • 集群模式,集群定期同步信息和连接测试

beforesleep

在进入事件循环前执行

  • cluster 集群状态检查
  • 处理被 block 卡住的 client, 如一些阻塞请求 BLPOP 等
  • 将 AOF buffer 持久化到 AOF 文件

流程

beforeSleep -> epollwait -> 处理请求 -> 定时事件 -> …

在流程中最好时间的步骤为 key hash, 所以减少 key 的大小,最好不要超过 1 MB, 减少耗时命令。

逐出

当执行 write 但是内存到达上限时,强制将一些 key 删除

  • allkeys 所有 keys
  • volatile 设置了过期的 key
  • LRU 最近未使用
  • random 随机
  • ttl 最快过期的

逐出特点

  • 不是精准算法,而是抽样比对
  • 每次写入前判断
  • 逐出是阻塞请求的

当 逐出 qps 过高时,会影响正常请求处理

过期

当某个 key 到达了 ttl 时间,认为该 key 已经失效

两种方式删除

  • 惰性删除
    读写之前判断 ttl, 过期则删除
  • 定期删除
    在 redis 定时时间中随机抽取部分 key 判断 ttl

过期特点

  • 并不一定是按设置时间准时地过期
  • 定期删除的时候会判断过期比例,达到阈值才推出

建议

建议打散 key 的过期时间,避免大量 key 在同一时间点过期

持久化

将内存中的数据 dump 到磁盘文件

RDB 持久化

  • 经过压缩的二进制格式
  • fork 子进程 dump 可能会造成瞬间卡顿

AOF 持久化

  • 保存所有修改数据库的命令
  • 先写 AOF 缓存,再同步到 AOF 文件
  • AOF 重写,达到阈值时触发,减小问价大小

应用

应使用 AOF 文件备灾,将数据恢复到最近 3 天的任意小时粒度

主从复制

主从模式

  • 主从节点都可以挂在从节点
  • 最终一致性

全量同步

  • 传递 RDB 文件 & restore 命令重建 kv
  • 传递在 RDB dump 过程中的写入数据

部分同步

根据 offset 传递积压缓存中的部分数据

redis_master_slave_copy

主从复制的流程

master_slave_sync

pipline

client 端

将多个命令缓存起来,缓冲区满了就发送

redis

处理一个 tcp 连接发来的多个命令,处理完一个发送一个

twemproxy

既要处理一个 client 连接发来的多个命令,又要捋到同一个下游 redis server 的命令缓存起来一起发送

优点

  • 节省了往返时间
  • 减少了 proxy, redis server 的 IO 次数

redis_pipline

mget

client

client 使用 mget 命令将多个命令放到一个命令中

redis

一个命令中处理多个 key, 等所有 key 处理完后组装回复一起发送

twemproxy

拆分 key 分发到不同 redis server, 需要等待,缓存 mget 中全部回复

优点

节省往返时间

缺点

  • proxy 缓存 mget 结果
  • mget 延时是最后一个 key 回复时间,前面的 key 需要等待

建议

使用 pipline 代替 mget, 且控制一次请求的命令数量

redis_mget

redis 集群

在 redis 中有两种模式建立集群

  • 一致性 hash
  • redis cluster

一致性hash

proxy 作为中间管理节点,分配命令

问题

实例宕机,加节点容易造成数据丢失

redis cluster

节点之间两两通讯维护拓扑信息,有节点数量限制

redis_cluster

redis 官网对 redis 的介绍.

Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes with radius queries and streams. Redis has built-in replication, Lua scripting, LRU eviction, transactions and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster.

前言

Redis 是一个开源的使用 ANSI C 语言编写,支持网络,可基于内存亦可持久化的日志型,key-value 的数据库,并提供多种语言的 API, 源代码见于 github

通常, Redis 将数据存于内存中,或者配置使用虚拟内存。通过两种方式可以实现数据的持久化

  • 快照
    将内存中的数据不断写入磁盘
  • 日志
    记录每次更新的日志

前者性能较高,但是可能会引起一定程度的数据丢失
后者相反

默认端口 6379

特点

支持数据的持久化,可将内存的数据保存到磁盘中,重启的时候方便再次加载使用
支持数据的备份,即 master-slave 模式的数据的备份
性能极高, Redis 读的速度为 110000 次/s, 写的速度为 81000 次/s
所有操作都是原子的

数据对象

String

可以包含任何类型,本质上就是字节序列,比如 jpg 图片或者序列化后的对象,亦可 key 最多可以存储 512 MB

Hash

一个 String 类型的 field 和 value 的映射表
每个 hash 可以存储 键值对

list

简单的字符串列表,可以从列表头部(左边) 或者尾部(右边) 添加元素

set

String 类型的无序组合。通过 hash 实现,所以 添加,删除,查找的复杂度都是 O(1)

zset

和 set 一样也是 String 类型元素的集合,且不孕允许重复的成员,不同的是每个元素都会关联一个 double 类型的 score.
redis 正是通过 score 来为集合中的成员进行从小到大的排序。添加元素到集合,元素在集合中存在则更新对应的 score

操作

关于 redis 的命令可以详见官网文档

Service Mesh(服务网格), 是一种微服务架构的形式。

维基中的定义

在 wiki 中解释如下

In a service mesh, each service instance is paired with an instance of a reverse proxy server, called a service proxy, sidecar proxy, or sidecar. The service instance and sidecar proxy share a container, and the containers are managed by a container orchestration tool such as Kubernetes. The service proxies are responsible for communication with other service instances and can support capabilities such as service (instance) discovery, load balancing, authentication and authorization, secure communications, and others.
In a service mesh, the service instances and their sidecar proxy are said to make up the data plane, which includes not only data management but also request processing and response. The service mesh also includes a control plane for managing the interaction between services, mediated by their sidecar proxies. There are several options for service mesh architecture: Istio (a joint project among Google, IBM, and Lyft), Buoyant[31] & others

在 Service Mesh 中,每个微服务的实例与一个反向代理服务器绑定,被称为 service proxy, sidecar proxy 或者 sidecar. service instance 和 sidecar proxy 共享一个容器,此容器由一个容器编排工具(比如 Kubernetes) 管理。这些 service proxy 负责与其他 service instance 通信,并且具有以下能力

  • 服务发现
  • 负载均衡
  • 访问控制和鉴权
  • 安全通信

在 Service Mesh 中,service instance 和对应的 service proxy 组成 data plane, 这不仅包含了数据管理同样也包含了处理请求和发送响应。Service Mesh 同样包括一个控制面板来控制服务间的交互,并由 sidecar proxy 调解.

目前较为通用的 Service Mesh 架构有 Istio, Linkerd 等

较为通俗的解释

A service mesh is a configurable infrasturcture layer for a microservices application.

Service mesh 是微服务应用中可配置的基础设施层,可以理解为 TCP/IP 之上的抽象层。

Service Mesh 有如下几个特点:

  1. 应用程序间通讯的中间层
  2. 轻量级网络代理
  3. 应用程序无感知
  4. 解耦应用程序的重试,超时,监控,追踪和服务发现

要理解消息中间件首先要理解两个概念,即什么是分布式系统,什么又是中间件

分布式系统的特点

  • 组件分布在网络计算机上
  • 组件之间通过消息来协调行动

中间件

中间件在 wiki 上描述为

提供系统软件和应用软件之间连接的软件,以便于软件各部件之间的沟通,特别是应用软件对于系统软件的集中的逻辑。

分布式消息中间件

wiki 中的定义为

Message-oriented middleware is software or hardware infrastructure supporting and receiving messages between distributed systems

消息中间件是支持在分布式系统中发送和接受消息的硬件或软件基础设施。
消息中间件本身也是一个分布式系统。

消息中间件解决的是分布式系统之间消息传递的问题。

消息中间件的应用场景

  • 业务解耦
    各模块之间只需要发布消息,无需知晓服务的存在
  • 削峰填谷
    当上游系统的吞吐能力高于下游系统时,消息中间件可以在峰值时堆积消息,在峰值过去后下游系统慢慢消费消息解决流量洪峰的问题
  • 事件驱动
    系统与系统之间通过消息传递的形式驱动业务,以流式的模型处理
0%