关于postgresql:PostgreSQL-技术内幕五GreenplumInterconnect模块

52次阅读

共计 9167 个字符,预计需要花费 23 分钟才能阅读完成。

Greenplum 是在开源 PostgreSQL 的根底上,采纳 MPP 架构的关系型分布式数据库。Greenplum 被业界认为是最快最具性价比的数据库,具备弱小的大规模数据分析工作解决能力。
Greenplum 采纳 Shared-Nothing 架构,整个集群由多个数据节点(Segment sever)和管制节点(Master Server)组成,其中的每个数据节点上能够运行多个数据库。
简略来说,Shared-Nothing 是一个分布式的架构,每个节点绝对独立。在典型的 Shared-Nothing 中,每一个节点上所有的资源(CPU、内存、磁盘)都是独立的,每个节点都只有全副数据的一部分,也只能应用本节点的资源。
因为采纳分布式架构,Greenplum 可能将查问并行化,以充分发挥集群的劣势。Segment 外部依照规定将数据组织在一起,有助于进步数据查问性能,利于数据仓库的保护工作。
如下图所示,Greenplum 数据库是由 Master Server、Segment Server 和 Interconnect 三局部组成,Master Server 和 Segment Server 的互联通过 Interconnect 实现。

图 1:Greenplum 数据库架构示意
同时,为了最大限度地实现并行化解决,当节点间须要挪动数据时,查问打算将被宰割,而不同 Segment 间的数据挪动就由 Interconnect 模块来执行。
在上次的直播中,咱们为大家介绍了 Greenplum-Interconnect 模块技术个性和实现流程剖析,以下内容依据直播文字整顿而成。
Interconnect 概要介绍
Interconnect 是 Greenplum 数据库中负责不同节点进行外部数据传输的组件。Greenplum 数据库有一种特有的执行算子 Motion,负责查询处理在执行器节点之间替换数据,底层网络通信协定通过 Interconnect 实现。
Greenplum 数据库架构中有一些重要的概念,包含查问调度器(Query Dispatcher,简称 QD)、查问执行器(Query Executor,简称 QE)、执行算子 Motion 等。

图 2:Master-Segment 查问执行调度架构示意

QD:是指 Master 节点上负责解决用户查问申请的过程。

QE:是指 Segment 上负责执行 QD 散发来的查问工作的过程。

通常,QD 和 QE 之间有两种类型的网络连接:

libpq 是基于 TCP 的控制流协定。QD 通过 libpq 与各个 QE 间传输管制信息,包含发送查问打算、收集错误信息、解决勾销操作等。libpq 是 PostgreSQL 的标准协议,Greenplum 对该协定进行了加强,譬如新增了‘M’音讯类型 (QD 应用该音讯发送查问打算给 QE)等。

Interconnect 数据流协定:QD 和 QE、QE 和 QE 之间的表元组数据传输通过 Interconnect 实现,Greenplum 有三种 Interconnect 实现形式,一种基于 TCP 协定,一种基于 UDP 协定,还有一种是 Proxy 协定。缺省形式为 UDP Interconnect 连贯形式。

Motion:PostgreSQL 生成的查问打算只能在单节点上执行,Greenplum 须要将查问打算并行化,以充分发挥集群的劣势。为此,Greenplum 引入 Motion 算子实现查问打算的并行化。Motion 算子实现数据在不同节点间的传输,在 Gang 之间通过 Interconnect 进行数据重散布。

同时,Motion 为其余算子暗藏了 MPP 架构和单机的不同,使得其余大多数算子都能够在集群或者单机上执行。每个 Motion 算子都有发送方和接管方。

此外,Greenplum 还对某些算子进行了分布式优化,譬如汇集。Motion 算子对数据的重散布有 gather、broadcast 和 redistribute 三种操作,底层传输协定通过 Interconnect 实现。Interconnect 是一个 network abstraction layer,负责各节点之间的数据传输。

Greenplum 是采纳 Shared-Nothing 架构来存储数据的,依照某个字段哈希计算后打散到不同 Segment 节点上。当用到连贯字段之类的操作时,因为这一字段的某一个值可能在不同 Segment 下面,所以须要在不同节点上对这一字段所有的值从新哈希,而后 Segment 间通过 UDP 的形式把这些数据相互发送到对应地位,汇集到各自哈希出的 Segment 下来造成一个长期的数据块以便后续的聚合操作。

Slice:为了在查问执行期间实现最大的并行度,Greenplum 将查问打算的工作划分为 slices。Slice 是打算中能够独立进行解决的局部。查问打算会为 motion 生成 slice,motion 的每一侧都有一个 slice。正是因为 motion 算子将查问打算宰割为一个个 slice,上一层 slice 对应的过程会读取下一层各个 slice 过程播送或重散布操作,而后进行计算。

Gang:属于同一个 slice 然而运行在不同的 segment 上的过程,称为 Gang。如上图 2 所示,图中有两个 QE 节点,一个 QD 节点,QD 节点被划分为三个 slice。依照雷同的 slice 在不同 QE 下面运行称一个组件的 Gang,所以上图共有三个 Gang。

Interconnect 初始化流程
在做好根底筹备工作之后,会有一系列处理函数,将某个节点或所有节点的数据收集上来。在数据传输的过程中,会有 buffer 治理的机制,在肯定的机会,将 buffer 内的数据刷出,这种机制能够无效地升高存储和网络的开销。以下是初始化一些重要的数据结构阐明。

  1. Interconnect 初始化外围构造
Go
typedef enum GpVars_Interconnect_Type
{
Interconnect_TYPE_TCP = 0,
Interconnect_TYPE_UDPIFC,
Interconnect_TYPE_PROXY,
} GpVars_Interconnect_Type;

typedef struct ChunkTransportState 
{ 
/* array of per-motion-node chunk transport state */ 
int size;// 来自宏定义 CTS_INITIAL_SIZE 
ChunkTransportStateEntry *states;// 上一个成员变量定义的 size 个数 
ChunkTransportStateEntry 
/* keeps track of if we've"activated" connections via SetupInterconnect(). 
*/ 
bool activated; 
bool aggressiveRetry; 
/* whether we've logged when network timeout happens */ 
bool networkTimeoutIsLogged;// 缺省 false,在 ic_udp 中才用到 
bool teardownActive; 
List *incompleteConns; 
/* slice table stuff. */ 
struct SliceTable *sliceTable; 
int sliceId;// 以后执行 slice 的索引号 
/* Estate pointer for this statement */ 
struct EState *estate; 
/* Function pointers to our send/receive functions */ 
bool (*SendChunk)(struct ChunkTransportState *transportStates, 
ChunkTransportStateEntry *pEntry, MotionConn *conn, TupleChunkListItem tcItem, 
int16 motionId); 
TupleChunkListItem (*RecvTupleChunkFrom)(struct ChunkTransportState 
*transportStates, int16 motNodeID, int16 srcRoute); 
TupleChunkListItem (*RecvTupleChunkFromAny)(struct ChunkTransportState 
*transportStates, int16 motNodeID, int16 *srcRoute); 
void (*doSendStopMessage)(struct ChunkTransportState *transportStates, int16 
motNodeID); 
void (*SendEos)(struct ChunkTransportState *transportStates, int motNodeID, 
TupleChunkListItem tcItem); 
/* ic_proxy backend context */ 
struct ICProxyBackendContext *proxyContext; 
} ChunkTransportState;
  1. Interconnect 初始化逻辑接口
    初始化的流程会调用 setup in Interconnect,而后依据数据类型抉择连贯协定。默认会抉择 UDP,用户也能够配置成 TCP。在 TCP 的流程外面,会通过 GUC 宏来判断走纯 TCP 协定还是走 proxy 协定。
Go
void
SetupInterconnect(EState *estate)
{
        Interconnect_handle_t *h;
        h = allocate_Interconnect_handle();

        Assert(InterconnectContext != NULL);
        oldContext = MemoryContextSwitchTo(InterconnectContext);

        if (Gp_Interconnect_type == Interconnect_TYPE_UDPIFC)
                SetupUDPIFCInterconnect(estate);    #here udp 初始化流程
        else if (Gp_Interconnect_type == Interconnect_TYPE_TCP ||
                         Gp_Interconnect_type == Interconnect_TYPE_PROXY)
                SetupTCPInterconnect(estate);#here tcp & proxy
        else
                elog(ERROR, "unsupported expected Interconnect type");

        MemoryContextSwitchTo(oldContext);

        h->Interconnect_context = estate->Interconnect_context;
}
SetupUDPIFCInterconnect_Internal 初始化一些列相干构造, 包含 Interconnect_context 初始化、以及 transportStates->states 成员 createChunkTransportState 的初始化,以及 rx_buffer_queue 相干成员的初始化。/* rx_buffer_queue */
// 缓冲区相干初始化重要参数
conn->pkt_q_capacity = Gp_Interconnect_queue_depth;
conn->pkt_q_size = 0;
conn->pkt_q_head = 0;
conn->pkt_q_tail = 0;
conn->pkt_q = (uint8 **) palloc0(conn->pkt_q_capacity * sizeof(uint8 *));

/* update the max buffer count of our rx buffer pool.  */
rx_buffer_pool.maxCount += conn->pkt_q_capacity;

3.  Interconnect 初始化回调接口
当初始化的时候,接口回调函数都是对立的。当真正初始化执行时,会给上对应的函数撑持。

通过回调来对应处理函数,在 PG 外面是一种常见形式。比方,对于 TCP 的流程对应 RecvTupleChunkFromTCP。

对于 UDP 的流程,对应 TupleChunkFromUDP。相应函数的尾缀法则与 TCP 或是 UDP 对应。

Go
TCP & proxy :
    Interconnect_context->RecvTupleChunkFrom = RecvTupleChunkFromTCP;
        Interconnect_context->RecvTupleChunkFromAny = RecvTupleChunkFromAnyTCP;
        Interconnect_context->SendEos = SendEosTCP;
        Interconnect_context->SendChunk = SendChunkTCP;
        Interconnect_context->doSendStopMessage = doSendStopMessageTCP;

UDP:        
        Interconnect_context->RecvTupleChunkFrom = RecvTupleChunkFromUDPIFC;
        Interconnect_context->RecvTupleChunkFromAny = RecvTupleChunkFromAnyUDPIFC;
        Interconnect_context->SendEos = SendEosUDPIFC;
        Interconnect_context->SendChunk = SendChunkUDPIFC;
        Interconnect_context->doSendStopMessage = doSendStopMessageUDPIFC;

Ic_udp 流程剖析
1. Ic_udp 流程剖析之缓冲区外围构造

Go
MotionConn:核心成员变量剖析:/* send side queue for packets to be sent */
ICBufferList sndQueue;
//buff 来自 conn->curBuff,间接来自 snd_buffer_pool
ICBuffer *curBuff;

//snd_buffer_pool 在 motionconn 初始化的时候,别离获取 buffer,放在 curBuff

uint8           *pBuff;
//pBuff 初始化后指向其 curBuff->pkt

/*
依赖 aSlice->primaryProcesses 获取过程 proc 构造进行初始化结构,过程 id、IP、端口等信息
*/
struct icpkthdr                conn_info;
// 全局 &ic_control_info.connHtab
        
  struct CdbProcess  *cdbProc;// 来自 aSlice->primaryProcesses        

uint8                **pkt_q;
/*pkt_q 是数组充当环形缓冲区,其中容量求模计算下标操作,Rx 线程接管的数据包 pkt 搁置在 conn->pkt_q[pos] = (uint8 *) pkt 中。而 IcBuffer 中的 pkt 赋值给 motioncon 中的 pBuff,而 pBuff 又会在调用 prepareRxConnForRead 时,被赋值 pkt_q 对应指针指向的数据区,conn->pBuff = conn->pkt_q[conn->pkt_q_head]; 从而造成数据链路关系。*/

motion:ICBufferList sndQueue、ICBuffer *curBuff、ICBufferList unackQueue、uint8         *pBuff、uint8                **pkt_q;
ICBuffer
        pkt
static SendBufferPool snd_buffer_pool;

第一层:snd_buffer_pool 在 motionconn 初始化的时候,别离获取 buffer,放在 curBuff,并初始化 pBuff。第二层:了解 sndQueue 逻辑
中转站,buff 来自 conn->curBuff,间接来自 snd_buffer_pool  
第三层:了解 data buffer 和 pkt_q
启用数据缓冲区:pkt_q 是数组充当环形缓冲区,其中容量求模计算下标操作,Rx 线程接管的数据包 pkt 搁置在 conn->pkt_q[pos] = (uint8 *) pkt 中。而 IcBuffer 中的 pkt 赋值给 motioncon 中的 pBuff,而 pBuff 又会在调用 prepareRxConnForRead 时,被赋值 pkt_q 对应指针指向的数据区,conn->pBuff = conn->pkt_q[conn->pkt_q_head]; 从而造成数据链路关系。

2. Ic_udp 流程剖析之缓冲区流程剖析

Go
Ic_udp 流程剖析缓冲区初始化:SetupUDPIFCInterconnect_Internal 调用 initSndBufferPool(&snd_buffer_pool)进行初始化。Ic_udp 流程剖析缓冲获取:调用接口 getSndBuffer 获取缓冲区 buffer,在初始化流程 SetupUDPIFCInterconnect_Internal->startOutgoingUDPConnections, 为每个 con 获取一个 buffer,并且填充 MotionConn 中的 curBuff
static ICBuffer * getSndBuffer(MotionConn *conn)

Ic_udp 流程剖析缓冲开释:通过调用 icBufferListReturn 接口,开释 buffer 进去 snd_buffer_pool.freeList
static void
icBufferListReturn(ICBufferList *list, bool inExpirationQueue)    
{icBufferListAppend(&snd_buffer_pool.freeList, buf);# here 0
}
清理:cleanSndBufferPool(&snd_buffer_pool); 下面开释回去后接着清理 buff。handleAckedPacket 逻辑对于 unackQueue 也会登程开释。

Ic_Proxy 流程剖析

  1. 简要介绍
    TCP 流程 buf 设计较为简单,在这里不做具体赘述。Proxy 代理服务是基于 TCP 革新而来,次要用来应答在大规模集群外面网络连接数微小的状况。

Ic_Proxy 只须要一个网络连接在每两个网端之间,相比拟于 IC-Tcp 模式,它耗费的连贯总量和端口更少。同时,与 IC-Udp 模式相比,在高提早网络具备更好的体现。

TCP 是一种点对点的有连贯传输协定,一个有 N 个 QE 节点的 Motion 的连接数是 N^2,一个有 k 个 Motion 的查问将产生 k *N^2 个连贯。

举例来讲,如果一个蕴含 500 个 Segment 的集群,运行一个蕴含 10 个 Motion 的查问,那么这个查问就须要建设 10*500^2 = 2,500,000 个 TCP 连贯。即便不思考最大连接数限度,建设如此多的 TCP 连贯也是十分低效的。

Ic_Proxy 是用 LIBUV 开发的,默认状况下禁用 IC 代理,咱们能够应用./configure –enable-ic-proxy。

装置实现后,咱们还须要设置 ic 代理网络,它实现了通过设置 GUC。例如,如果集群具备一个主节点、一个备用主节点、一个主分段和一个镜像分段,咱们能够像上面这样设置它:

Go
gp_Interconnect_proxy_addresses 
gpconfig --skipvalidation -c gp_Interconnect_proxy_addresses -v "'1:-1:localhost:2000,2:0:localhost:2002,3:0:localhost:2003,4:-1:localhost:2001'"

它蕴含所有主服务器、备用服务器、以及主服务器和镜像服务器的信息段,语法如下:dbid:segid:hostname:port[,dbid:segid:ip:port]。这里要留神,将值指定为单引号字符串很重要,否则将被解析为格局有效的中间体。
2.  Ic_proxy 逻辑连贯

Go
在 Ic-Tcp 模式下,QE 之间存在 TCP 连贯(包含 QD), 以一个收集动作举例:┌    ┐
│    │  <=====  [QE1]
│ QD │
│    │  <=====  [QE2]
└    ┘
在 Ic-Udp 模式下,没有 TCP 连贯,但仍有逻辑连贯:如果两个 QE 互相通信,则存在逻辑连贯:┌    ┐
│    │  <-----  [QE1]
│ QD │
│    │  <-----  [QE2]
└    ┘
在 Ic_Proxy 模式下,咱们依然应用逻辑连贯的概念:┌    ┐          ┌       ┐
│    │          │       │  <====>  [proxy]  <~~~~>  [QE1]
│ QD │  <~~~~>  │ proxy │
│    │          │       │  <====>  [proxy]  <~~~~>  [QE2]
└    ┘          └       ┘
在 N:1 汇合静止中,有 N 个逻辑连贯;
在 N:N 重新分配 / 播送静止中存在逻辑连接数 N *N

3.  Ic_Proxy 逻辑连贯标识符
为了辨认逻辑连贯,咱们须要晓得谁是发送者,谁是接收者。在 Ic_Proxy 中,咱们不辨别逻辑的方向连贯,咱们应用名称本地和近程作为端点。起点至多由 segindex 和 PID 标识,因而逻辑连贯能够通过以下形式标识:seg1,p1->seg2,p2

然而,这还不足以辨别不同查问中的子打算。咱们还必须将发送方和接管方切片索引放入思考:slice[a->b] seg1,p1->seg2,p2

此外,思考到后端过程可用于不同的查问会话及其生命周期不是严格同步的,咱们还必须将命令 ID 放入标识符中:cmd1,slice[a->b] seg1,p1->seg2,p2

出于调试目标,咱们还将会话 ID 放在标识符中。在思考镜像或备用时,咱们必须意识到与 SEG1 主节点的连贯和与 SEG1 镜像的连贯不同,所以咱们还须要将 dbid 放入标识符中:cmd1,slice[a->b] seg1,dbid3,p1->seg2,dbid5,p2Ic_Proxy

数据转发流程介绍
数据转发是 Ic_Proxy 流程最简单的局部,依照不同的流程,会产生三种转发类型:
第一种是 Loopback,即循环本地;
第二种是 proxy client,通过代理去 client;
第三种是 proxy to proxy,从一个代理发到另一个代理。
而后,依照上述的三种类型再调用对应的 route,把数据转发进来,这样就造成了一个残缺的数据转发流程。

图 3:Ic_proxy 数据包转发解决流程图

图 4:Ic_Proxy 流程时序图

明天咱们为大家带来 Greenplum-Interconnect 模块的解析,心愿可能帮忙大家更好地了解模块的技术个性和实现解决流程。

正文完
 0