GreenPlum数据库网络层——集群节点状态信息CdbComponents

cdb_component_dbs是进程私有的集群节点状态信息CdbComponents,初始时其为NULL。cdbcomponent_updateCdbComponents和cdbcomponent_getCdbComponents两个函数用于给cdb_component_dbs变量赋值。而cdbcomponent_destroyCdbComponents函数用于将cdb_component_dbs指针重置为NULL。

static CdbComponentDatabases *cdb_component_dbs = NULL;

获取集群节点状态信息

cdbcomponent_getCdbComponents函数用于获取集群节点状态信息,其函数逻辑如下:

  • cdb_component_dbs不为NULL,说明已经初始化并获取过集群节点状态信息,直接将cdb_component_dbs返回
  • cdb_component_dbs为NULL,需要调用getCdbComponentInfo函数获取集群节点状态信息
  • cdb_component_dbs为NULL,且获取集群节点状态信息失败,如果GP_role节点角色为GP_ROLE_DISPATCH,调用FtsNotifyProber函数通知FTS进程探测集群状态
CdbComponentDatabases *cdbcomponent_getCdbComponents(){
	PG_TRY();
	{
		if (cdb_component_dbs == NULL) { 
			cdb_component_dbs = getCdbComponentInfo();
			cdb_component_dbs->fts_version = getFtsVersion();
			cdb_component_dbs->expand_version = GetGpExpandVersion();
		}
	}
	PG_CATCH();
	{
		if (Gp_role == GP_ROLE_DISPATCH) FtsNotifyProber();
		PG_RE_THROW();
	}
	PG_END_TRY();
	return cdb_component_dbs;
}

cdbcomponent_updateCdbComponents函数和cdbcomponent_getCdbComponents函数不一致的情况在于处理fts_version和expand_version不一致时对cdb_component_dbs进行更新。
FTS负责更新gp_segment_configuration,在每个fts探测周期,FTS首先获取当前配置的副本,然后基于它探测segments,最后释放副本。 在探测阶段,FTS 可能会多次启动/关闭事务,因此 FTS 不应在启动新事务时更新 gp_segment_configuration 的当前副本。

void cdbcomponent_updateCdbComponents(void) {
	uint8 ftsVersion= getFtsVersion();
	int expandVersion = GetGpExpandVersion();
	/* FTS takes responsibility for updating gp_segment_configuration, in each fts probe cycle, FTS firstly gets a copy of current configuration, then probe the segments based on it and finally free the copy in the end. In the probe stage, FTS might start/close transactions many times, so FTS should not update current copy of gp_segment_configuration when a new transaction is started. */
	if (am_ftsprobe) return;

	PG_TRY();
	{
		if (cdb_component_dbs == NULL) {
			cdb_component_dbs = getCdbComponentInfo();
			cdb_component_dbs->fts_version = ftsVersion;
			cdb_component_dbs->expand_version = GetGpExpandVersion();
		} else if ((cdb_component_dbs->fts_version != ftsVersion || cdb_component_dbs->expand_version != expandVersion)) {
			if (TempNamespaceOidIsValid()) {
				/* Do not update here, otherwise, temp files will be lost  in segments; */
			}else{
				ELOG_DISPATCHER_DEBUG("FTS rescanned, get new component databases info.");
				cdbcomponent_destroyCdbComponents();
				cdb_component_dbs = getCdbComponentInfo();
				cdb_component_dbs->fts_version = ftsVersion;
				cdb_component_dbs->expand_version = expandVersion;
			}
		}
	}
	PG_CATCH();
	{
		FtsNotifyProber();
		PG_RE_THROW();
	}
	PG_END_TRY();
	Assert(cdb_component_dbs->numActiveQEs == 0);
}

什么是GpExpandVersion?

GreenPlum集群支持在线扩容,cdb_component_dbs->expand_version用于指示是否正在进行扩容操作。一旦 gpexpand 扩容脚本启动新segment并更新 gp_segment_configuration,gpexpand扩容脚本使用gp_expand_bump_version来提升gpexpand版本。 gpexpand 版本更改还可以防止在 gpexpand 期间对目录进行并发更改(请参阅 gp_expand_lock_catalog)。在cdbcomponent_updateCdbComponents函数中如果发现cdb_component_dbs->expand_version不一致会更新cdb_component_dbs,从而发现新加入的segments。

int GetGpExpandVersion(void) { return *gp_expand_version; }
Datum gp_expand_bump_version(PG_FUNCTION_ARGS) { //用于提升gpexpand版本的函数
	*gp_expand_version += 1;
	PG_RETURN_VOID();
}

getCdbComponentInfo

getCdbComponentInfo函数的主要逻辑如下所示:
如果没有创建CdbComponentsContext内存上下文,先创建cdb components Context,然后切换到该内存上下文。创建用于统计同一主机上安装segment数量的HTAB hostSegsHash。通过readGpSegConfigFromCatalog或readGpSegConfigFromFTSFiles函数获取所有segment(包括master、standby、primary、mirror)的Config组成GpSegConfigEntry数组,如下图中的虚线框所示。为CdbComponentDatabases分配内存,并初始化相应的成员。

static CdbComponentDatabases *getCdbComponentInfo(void) {
	MemoryContext oldContext;
	CdbComponentDatabaseInfo *cdbInfo;
	CdbComponentDatabases *component_databases = NULL;
	GpSegConfigEntry *configs;
	int			i, x = 0, total_dbs = 0;
	bool		found;
	HostSegsEntry *hsEntry;

	if (!CdbComponentsContext) CdbComponentsContext = AllocSetContextCreate(TopMemoryContext, "cdb components Context", ALLOCSET_DEFAULT_MINSIZE,ALLOCSET_DEFAULT_INITSIZE,ALLOCSET_DEFAULT_MAXSIZE);
	oldContext = MemoryContextSwitchTo(CdbComponentsContext);

	HTAB	   *hostSegsHash = hostSegsHashTableInit();

	if (IsTransactionState()) configs = readGpSegConfigFromCatalog(&total_dbs);
	else configs = readGpSegConfigFromFTSFiles(&total_dbs);

	component_databases = palloc0(sizeof(CdbComponentDatabases));
	component_databases->numActiveQEs = 0;
	component_databases->numIdleQEs = 0;
	component_databases->qeCounter = 0;
	component_databases->freeCounterList = NIL;
	component_databases->segment_db_info =
		(CdbComponentDatabaseInfo *) palloc0(sizeof(CdbComponentDatabaseInfo) * total_dbs);
	component_databases->entry_db_info =
		(CdbComponentDatabaseInfo *) palloc0(sizeof(CdbComponentDatabaseInfo) * 2);

遍历GpSegConfigEntry数组,首先确保primary segment的hostip获取出来的必须合法(mirror的hostip其实可以忽略不处理)。如果处理的Config是master、standby,则将其加入到entry_db_info数组;如果处理的Config是primary或mirror,则将其加入到segment_db_info数组。如果处理的Config是primary,则更新hostSegsHash中相应的segmentCount数量,以统计相同host上安装的segment数量。

	for (i = 0; i < total_dbs; i++) {
		CdbComponentDatabaseInfo	*pRow;
		GpSegConfigEntry	*config = &configs[i];
		
		config->hostip= NULL; /* lookup hostip/hostaddrs cache */
		getAddressesForDBid(config, !am_ftsprobe? ERROR : LOG);

		/* We make sure we get a valid hostip for primary here, if hostip for mirrors can not be get, ignore the error. */
		if (config->hostaddrs[0] == NULL && config->role == GP_SEGMENT_CONFIGURATION_ROLE_PRIMARY)
			ereport(!am_ftsprobe ? ERROR : LOG,(errcode(ERRCODE_CONNECTION_FAILURE),errmsg("cannot resolve network address for dbid=%d", config->dbid)));

		if (config->hostaddrs[0] != NULL) config->hostip = pstrdup(config->hostaddrs[0]);
		AssertImply(config->hostip, strlen(config->hostip) <= INET6_ADDRSTRLEN);

		/* Determine which array to place this rows data in: entry or segment, based on the content field. */
		if (config->segindex >= 0) {
			pRow = &component_databases->segment_db_info[component_databases->total_segment_dbs];
			component_databases->total_segment_dbs++;
		}else{
			pRow = &component_databases->entry_db_info[component_databases->total_entry_dbs];
			component_databases->total_entry_dbs++;
		}

		pRow->cdbs = component_databases;
		pRow->config = config;
		pRow->freelist = NIL;
		pRow->numIdleQEs = 0;
		pRow->numActiveQEs = 0;

		if (config->role != GP_SEGMENT_CONFIGURATION_ROLE_PRIMARY || config->hostip == NULL)
			continue;

		hsEntry = (HostSegsEntry *) hash_search(hostSegsHash, config->hostip, HASH_ENTER, &found);
		if (found) hsEntry->segmentCount++;
		else hsEntry->segmentCount = 1;
	}

	/* Validate that there exists at least one entry and one segment database in the configuration */
	if (component_databases->total_segment_dbs == 0){
		ereport(ERROR,(errcode(ERRCODE_CARDINALITY_VIOLATION),errmsg("number of segment databases cannot be 0")));
	}
	if (component_databases->total_entry_dbs == 0){
		ereport(ERROR,(errcode(ERRCODE_CARDINALITY_VIOLATION), errmsg("number of entry databases cannot be 0")));
	}

以segindex作为比较键进行排序segment_db_info和entry_db_info,求取segments的数量。确保执行该函数时,后台进程的GpIdentity.dbid和GpIdentity.segindex在entry_db_info中,也就是该进程是集群节点的子进程。校验segment segindexes( 0 - numsegments-1)。通过hostSegsHash更新cdbInfo->hostSegs,也就是确定host上安装的segment数量。删除hostSegsHash HTAB。

	/* Now sort the data by segindex, isprimary desc */
	qsort(component_databases->segment_db_info, component_databases->total_segment_dbs, sizeof(CdbComponentDatabaseInfo), CdbComponentDatabaseInfoCompare);
	qsort(component_databases->entry_db_info, component_databases->total_entry_dbs, sizeof(CdbComponentDatabaseInfo), CdbComponentDatabaseInfoCompare);

	/* Now count the number of distinct segindexes. Since it's sorted, this is easy. */
	for (i = 0; i < component_databases->total_segment_dbs; i++){
		if (i == 0 || (component_databases->segment_db_info[i].config->segindex != component_databases->segment_db_info[i - 1].config->segindex)) {
			component_databases->total_segments++;
		}
	}

	/* Now validate that our identity is present in the entry databases */
	for (i = 0; i < component_databases->total_entry_dbs; i++){
		cdbInfo = &component_databases->entry_db_info[i];
		if (cdbInfo->config->dbid == GpIdentity.dbid && cdbInfo->config->segindex == GpIdentity.segindex){
			break;
		}
	}
	if (i == component_databases->total_entry_dbs) {
		ereport(ERROR,(errcode(ERRCODE_DATA_EXCEPTION),errmsg("cannot locate entry database"),errdetail("Entry database represented by this db in gp_segment_configuration: dbid %d content %d",GpIdentity.dbid, GpIdentity.segindex)));
	}

	/* Now validate that the segindexes for the segment databases are between 0 and (numsegments - 1) inclusive, and that we hit them all. Since it's sorted, this is relatively easy. */
	x = 0;
	for (i = 0; i < component_databases->total_segments; i++) {
		int			this_segindex = -1;
		while (x < component_databases->total_segment_dbs) {
			this_segindex = component_databases->segment_db_info[x].config->segindex;
			if (this_segindex < i) x++;
			else if (this_segindex == i) break;
			else if (this_segindex > i) {
				ereport(ERROR,(errcode(ERRCODE_DATA_EXCEPTION),errmsg("content values not valid in %s table",GpSegmentConfigRelationName),errdetail("Content values must be in the range 0 to %d inclusive.",component_databases->total_segments - 1)));
			}
		}
		if (this_segindex != i){
			ereport(ERROR,(errcode(ERRCODE_DATA_EXCEPTION),errmsg("content values not valid in %s table",GpSegmentConfigRelationName), errdetail("Content values must be in the range 0 to %d inclusive",component_databases->total_segments - 1)));
		}
	}

	for (i = 0; i < component_databases->total_segment_dbs; i++){
		cdbInfo = &component_databases->segment_db_info[i];
		if (cdbInfo->config->role != GP_SEGMENT_CONFIGURATION_ROLE_PRIMARY || cdbInfo->config->hostip == NULL)
			continue;
		hsEntry = (HostSegsEntry *) hash_search(hostSegsHash, cdbInfo->config->hostip, HASH_FIND, &found);
		Assert(found);
		cdbInfo->hostSegs = hsEntry->segmentCount;
	}

	for (i = 0; i < component_databases->total_entry_dbs; i++) {
		cdbInfo = &component_databases->entry_db_info[i];
		if (cdbInfo->config->role != GP_SEGMENT_CONFIGURATION_ROLE_PRIMARY || cdbInfo->config->hostip == NULL)
			continue;
		hsEntry = (HostSegsEntry *) hash_search(hostSegsHash, cdbInfo->config->hostip, HASH_FIND, &found);
		Assert(found);
		cdbInfo->hostSegs = hsEntry->segmentCount;
	}

	hash_destroy(hostSegsHash);
	MemoryContextSwitchTo(oldContext);
	return component_databases;
}

在这里插入图片描述

Dns Cache

getAddressesForDBid函数用于获取segment的DNS记录。GpSegConfigEntry的address成员是gp_segment_configuration中的address字段,GpSegConfigEntry的hostname成员是gp_segment_configuration中的hostname字段。

static void getAddressesForDBid(GpSegConfigEntry *c, int elevel) {
	char	   *name;	
	memset(c->hostaddrs, 0, COMPONENT_DBS_MAX_ADDRS * sizeof(char *)); /* Use hostname */
	/* add an entry, using the first the "address" and then the "hostname" as fallback. */
	name = getDnsCachedAddress(c->address, c->port, elevel, true);
	if (name) {
		c->hostaddrs[0] = pstrdup(name);
		return;
	}
	
	name = getDnsCachedAddress(c->hostname, c->port, elevel, true); /* now the hostname. */
	if (name) {
		c->hostaddrs[0] = pstrdup(name);
	} else {
		c->hostaddrs[0] = NULL;
	}
	return;
}

getDnsCachedAddress函数可以通过segment_ip_cache_htab HTAB缓冲来获取DNS记录,如果指定use_cache则从HTAB缓冲中获取DNS记录。如果不指定use_cache或HTAB缓冲中没有相应的记录,使用pg_getaddrinfo_all函数获取DNS记录。如果指定了use_cache,则将该记录加入到segment_ip_cache_htab HTAB缓冲中。
IPv6 可能会正常工作,我们只需要确保所有数据结构都足够大以容纳 IPv6 地址。 在某些损坏的系统上,您可以获得 IPv6 地址,但无法绑定到它,因为内核中禁用或缺少 IPv6,所以如果没有 IPv4 地址,我们只想使用 IPv6 地址 . 我们真正需要做的就是对此进行测试。

static char *getDnsCachedAddress(char *name, int port, int elevel, bool use_cache) {
	SegIpEntry	   *e = NULL;
	char			hostinfo[NI_MAXHOST];
	if (use_cache) {
		if (segment_ip_cache_htab == NULL) {
			HASHCTL		hash_ctl;
			MemSet(&hash_ctl, 0, sizeof(hash_ctl));
			hash_ctl.keysize = NAMEDATALEN + 1;
			hash_ctl.entrysize = sizeof(SegIpEntry);
			segment_ip_cache_htab = hash_create("segment_dns_cache", 256, &hash_ctl, HASH_ELEM);
		}else{
			e = (SegIpEntry *) hash_search(segment_ip_cache_htab, name, HASH_FIND, NULL);
			if (e != NULL) return e->hostinfo;
		}
	}

	/* The name is either not in our cache, or we've been instructed to not use the cache. Perform the name lookup. */
	if (!use_cache || (use_cache && e == NULL)) {
		MemoryContext oldContext = NULL;
		int			ret;
		char		portNumberStr[32];
		char	   *service;
		struct addrinfo *addrs = NULL, *addr;
		struct addrinfo hint;

		/* Initialize hint structure */
		MemSet(&hint, 0, sizeof(hint));
		hint.ai_socktype = SOCK_STREAM;
		hint.ai_family = AF_UNSPEC;
		snprintf(portNumberStr, sizeof(portNumberStr), "%d", port);
		service = portNumberStr;

		ret = pg_getaddrinfo_all(name, service, &hint, &addrs);
		if (ret || !addrs) {
			if (addrs) pg_freeaddrinfo_all(hint.ai_family, addrs);

			/* If a host name is unknown, whether it is an error depends on its role 如果一个主机名是未知的,是否是错误取决于它的角色:
			 * - if it is a primary then it's an error;
			 * - if it is a mirror then it's just a warning;
			 * but we do not know the role information here, so always treat it as a warning, the callers should check the role and decide what to do.
			 */
			if (ret != EAI_FAIL && elevel == ERROR) elevel = WARNING;
			ereport(elevel,(errmsg("could not translate host name \"%s\", port \"%d\" to address: %s",name, port, gai_strerror(ret))));
			return NULL;
		}

		/* save in the cache context 将记录保存到缓冲区中 */
		if (use_cache) oldContext = MemoryContextSwitchTo(TopMemoryContext);
		hostinfo[0] = '\0';
		for (addr = addrs; addr; addr = addr->ai_next){
#ifdef HAVE_UNIX_SOCKETS
			/* Ignore AF_UNIX sockets, if any are returned. */
			if (addr->ai_family == AF_UNIX) continue;
#endif
			if (addr->ai_family == AF_INET) /* IPv4 address */ {
				memset(hostinfo, 0, sizeof(hostinfo));
				pg_getnameinfo_all((struct sockaddr_storage *) addr->ai_addr, addr->ai_addrlen,hostinfo, sizeof(hostinfo),NULL, 0, NI_NUMERICHOST);
				if (use_cache){
					/* Insert into our cache htab */
					e = (SegIpEntry *) hash_search(segment_ip_cache_htab,name, HASH_ENTER, NULL);
					memcpy(e->hostinfo, hostinfo, sizeof(hostinfo));
				}
				break;
			}
		}

#ifdef HAVE_IPV6
		/*
		 * IPv6 probably would work fine, we'd just need to make sure all the
		 * data structures are big enough for the IPv6 address.  And on some
		 * broken systems, you can get an IPv6 address, but not be able to
		 * bind to it because IPv6 is disabled or missing in the kernel, so
		 * we'd only want to use the IPv6 address if there isn't an IPv4
		 * address.  All we really need to do is test this.
		 */
		if (((!use_cache && !hostinfo[0]) || (use_cache && e == NULL)) && addrs->ai_family == AF_INET6) {
			addr = addrs;
			/* Get a text representation of the IP address */
			pg_getnameinfo_all((struct sockaddr_storage *) addr->ai_addr, addr->ai_addrlen,hostinfo, sizeof(hostinfo), NULL, 0, NI_NUMERICHOST);
			if (use_cache){			
				e = (SegIpEntry *) hash_search(segment_ip_cache_htab, name, HASH_ENTER, NULL); /* Insert into our cache htab */
				memcpy(e->hostinfo, hostinfo, sizeof(hostinfo));
			}
		}
#endif
		if (use_cache) MemoryContextSwitchTo(oldContext);
		pg_freeaddrinfo_all(hint.ai_family, addrs);
	}
	/* return a pointer to our cache. */
	if (use_cache) return e->hostinfo;
	return pstrdup(hostinfo);
}

pg_getnameinfo_all函数用于获取 Unix、IPv4 和 IPv6 套接字的名称信息。该例程的 API 在两个方面与标准 getnameinfo() 定义不同:首先,addr 参数声明为 sockaddr_storage 而不是 struct sockaddr,其次,即使返回失败,节点和服务字段也保证填充某些内容。GreenPlum数据库对DNS记录比较敏感,比如/etc/hosts中配置出错,大概率可以导致数据库不可用,或者集群存在多个网络平面,使用gpcopy等工具同步两个集群数据时,需要将两个集群使用的数据平面打通,并确保/etc/hosts或DNS服务中同配置了集群所有节点的DNS记录。

int pg_getnameinfo_all(const struct sockaddr_storage * addr, int salen, char *node, int nodelen, char *service, int servicelen, int flags) {
	int			rc;
#ifdef HAVE_UNIX_SOCKETS
	if (addr && addr->ss_family == AF_UNIX)
		rc = getnameinfo_unix((const struct sockaddr_un *) addr, salen,node, nodelen, service, servicelen, flags);
	else
#endif
		rc = getnameinfo((const struct sockaddr *) addr, salen, node, nodelen, service, servicelen, flags);
	if (rc != 0){
		if (node) strlcpy(node, "???", nodelen);
		if (service) strlcpy(service, "???", servicelen);
	}
	return rc;
}

getnameinfo

gethostbyname, gethostbyaddr是不可重入函数;已经被getaddrinfo, getnameinfo替代。可重入函数主要用于多任务环境中,一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入OS调度下去执行另外一段。不可重入,意味着不可被并行调度,否则会产生不可预料的结果,这些函数提内一般使用了静态(static)的数据结构,使用了malloc()或者free()函数,使用了标准I/O函数等等。getaddrinfo 将主机和服务转换到socket地址,融合了函数getipnodebyname, getipnodebyaddr, getservbyname, getservbyport的功能,是可重入的。getnameinfo 功能与getaddrinfo相反,它将socket地址转换到主机和服务,融合了函数gethostbyaddr、getservbyport的功能,也是可重入的。
函数原型:

int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);
int getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags);

函数参数说明:
node或service参数最多可能有一个为NULL。
node 要么为点分式地址(IPv4-点分十进制, IPv6-十六进制) 要么为主机名。如果hints.ai_flags包含了AI_NUMERICHOST标志,则node必须为数字地址。
service为端口号或者端口名。如果不为空,为端口名,则必须可以解析,通过/etc/services。
如果hints.ai_flags指定了AI_CANONNAME标志,则返回的结构体列表中第一个addrinfo结构体的ai_canonname域指向了主机的正式名字
如果hints.ai_flags指定了AI_PASSIVE标志,而且node为NULL,则返回的地址包含INADDR_ANY或IN6ADDR_ANY_INIT适合用来bind将要accept连接的socket。
如果hints.ai_flags指定了AI_PASSIVE标志,而且node不为NULL,则忽略AI_PASSIVE。
如果hints.ai_flags未指定AI_PASSIVE标志,则返回的地址适合用来connect,sendto,sendmsg。
如果hints.ai_flags未指定AI_PASSIVE标志,而且node为NULL, 则返回回环地址,用于本地服务。
根据node和service查找到对应的addr info。如果此信息被connect调用,则表示这是被监听对象;而监听者可以通过node里面设置。node如果是fe80这种IP V6地址,必须接%eth来指定接口,则监听者就是这个local IP;如果是raw socket,则监听端口就是hints里面的 ai_protocol。 getaddrinfo() supports the address%scope-id notation for specifying the IPv6 scope-ID.

IPv4中使用gethostbyname()函数完成主机名到地址解析,这个函数仅仅支持IPv4,且不允许调用者指定所需地址类型的任何信息,返回的结构只包含了用于存储IPv4地址的空间。
IPv6中引入了新的API getaddrinfo(),它是协议无关的,既可用于IPv4也可用于IPv6。getaddrinfo() 函数能够处理名字到地址以及服务到端口这两种转换,返回的是一个 struct addrinfo 的结构体(列表)指针而不是一个地址清单。这些 struct addrinfo 结构体随后可由套接口函数直接使用。如此以来,getaddrinfo()函数把协议相关性安全隐藏在这个库函数内部。应用程序只要处理由getaddrinfo()函数填写的套接口地址结构。

Parameter

  1. nodename
    主机名(“www.baidu.com”)或者是数字化的地址字符串(IPv4的点分十进制串(“192.168.1.100”)或者IPv6的16进制串(“2000::1:2345:6789:abcd”)),如果 ai_flags 中设置了AI_NUMERICHOST 标志,那么该参数只能是数字化的地址字符串,不能是域名,该标志的作用就是阻止进行域名解析。nodename 和 servname 可以设置为NULL,但是同时只能有一个为NUL。

  2. servname
    服务名可以是十进制的端口号(“8080”)字符串,也可以是已定义的服务名称,如"ftp"、"http"等,详细请查看/etc/services 文件,最后翻译成对应服务的端口号。如果此参数设置为NULL,那么返回的socket地址中的端口号不会被设置。如果 ai_flags 设置了AI_NUMERICSERV 标志并且该参数未设置为NULL,那么该参数必须是一个指向10进制的端口号字符串,不能设定成服务名,该标志就是用来阻止服务名解析。

  3. hints
    该参数指向用户设定的 struct addrinfo 结构体,只能设定该结构体中 ai_family、ai_socktype、ai_protocol 和 ai_flags 四个域,其他域必须设置为0 或者 NULL, 通常是申请 结构体变量后使用memset()初始化再设定指定的四个域。该参数可以设置为NULL,等价于 ai_socktype = 0, ai_protocol = 0,ai_family = AF_UNSPEC, ai_flags = 0 (在GNU Linux中ai_flag = AI_V4MAPPED | AI_ADDRCONFIG,可以看作是GNU的改进)。
     ① ai_family 指定返回地址的协议簇,取值范围:AF_INET(IPv4)、AF_INET6(IPv6)、AF_UNSPEC(IPv4 and IPv6)
    ② ai_socktype 具体类型请查看struct addrinfo 中的 enum __socket_type 类型,用于设定返回地址的socket类型,常用的有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW, 设置为0表示所有类型都可以。
     ③ ai_protocol 具体取值范围请查看 Ip Protocol ,常用的有 IPPROTO_TCP、IPPROTO_UDP 等,设置为0表示所有协议。
     ④ ai_flags 附加选项,多个选项可以使用或操作进行结合,具体取值范围请查看struct addrinfo , 常用的标志如下:

AI_PASSIVE 如果设置了 AI_PASSIVE 标志,并且 nodename 是 NULL, 那么返回的socket地址可以用于的bind()函数,返回的地址是通配符地址(wildcard address, IPv4时是INADDR_ANY,IPv6时是IN6ADDR_ANY_INIT),这样应用程序(典型是server)就可以使用这个通配符地址用来接收任何请求主机地址的连接,如果 nodename 不是NULL,那么 AI_PASSIVE 标志被忽略;如果未设置AI_PASSIVE标志,返回的socket地址可以用于connect(), sendto(), 或者 sendmsg()函数。如果 nodename 是NULL,那么网络地址会被设置为lookback接口地址(IPv4时是INADDR_LOOPBACK,IPv6时是IN6ADDR_LOOPBACK_INIT),这种情况下,应用是想与运行在同一个主机上另一个应用通信。

AI_CANONNAME 请求canonical(主机的official name)名字。如果设置了该标志,那么 res 返回的第一个 struct addrinfo 中的 ai_canonname 域会存储official name的指针。

AI_NUMERICHOST 阻止域名解析,具体见 nodename 中的说明。

AI_NUMERICSERV 阻止服务名解析,具体见 servname 中的说明。

AI_V4MAPPED 当 ai_family 指定为AF_INT6(IPv6)时,如果没有找到IPv6地址,那么会返回IPv4-mapped IPv6 地址,也就是说如果没有找到AAAA record(用来将域名解析到IPv6地址的DNS记录),那么就查询A record(IPv4),将找到的IPv4地址映射到IPv6地址, IPv4-mapped IPv6 地址其实是IPv6内嵌IPv4的一种方式,地址的形式为"0::FFFF:a.b.c.d",例如"::ffff:192.168.89.9"(混合格式)这个地址仍然是一个IPv6地址, 只是"0000:0000:0000:0000:0000:ffff:c0a8:5909"(16机制格式)的另外一种写法罢了。当 ai_family 不是AF_INT6(IPv6)时,该标志被忽略。

AI_ALL 查询IPv4和IPv6地址

AI_ADDRCONFIG 只有当主机配置了IPv4地址才进行查询IPv4地址;只有当主机配置了IPv6地址才进行查询IPv6地址.

  1. res
    该参数获取一个指向存储结果的 struct addrinfo 结构体列表,使用完成后调用 freeaddrinfo() 释放存储结果空间。

Return Value
如果 getaddrinfo() 函数执行成功,返回值为 0 , 其他情况返回值表示错误种别。使用函数gai_strerror() 可以获取可读性的错误信息,用法用strerror()相同,
错误种别如下:
EAI_ADDRFAMILY 指定的主机上没有请求的address family对应的网络地址.
EAI_AGAIN DNS(name server)返回临时性错误. 可以稍后重试.
EAI_BADFLAGS hints.ai_flags 包含了无效的标志; 或者 hints.ai_flags 包含了 AI_CANONNAME 标志但是 name 是 NULL.
EAI_FAIL DNS(name server)返回永久性错误
EAI_FAMILY 不支持的 address family(hints.ai_family).
EAI_MEMORY 内存耗尽.
EAI_NODATA 指定的网络主机存在,但是其未定义任何网络地址.
EAI_NONAME nodename 或者 servname 未知;或者两者都设置为NULL;或者设置了 AI_NUMERICSERV 标志但是 servname 不是一个数字化的端口名字符串。
EAI_SERVICE 请求的socket类型不支持请求的服务类型.例如服务类型是 “shell” (基于流的socket服务),但是 hints.ai_protocol 是 IPPROTO_UDP 或者hints.ai_socktype 是 SOCK_DGRAM;或者 servname 不是NULL 但是 hints.ai_socktype 是 SOCK_RAW (原始套接字不支持服务的概念).
EAI_SOCKTYPE 不支持请求的socket类型. 例如, hints.ai_socktype 和 hints.ai_protocol 冲突 (例如分别是SOCK_DGRAM、IPPROTO_TCP).
EAI_SYSTEM 系统调用错误,检查 errno.

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Greenplum数据库是一种高性能的分布式数据库系统,它基于开源的PostgreSQL数据库,专为大规模数据分析和处理而设计。Greenplum具备横向扩展能力,可以在多个节点上分布式存储和处理数据,以提供更好的性能和可扩展性。 Greenplum数据库的特点包括: 1. 并行处理:Greenplum可以将大数据集分成多个片段,然后在多个节点上同时处理和分析,从而提高处理速度。 2. 列存储:Greenplum使用列存储技术来优化查询性能。它将每个列存储在独立的文件中,可以更快地访问和查询特定的列数据。 3. 数据压缩:Greenplum可以对数据进行压缩,以减少存储空间,并提高查询性能。 4. 数据分片:Greenplum将数据集分成多个片段,并在不同的节点上存储,以实现数据的并行处理。 5. 多维数据分析:Greenplum内置了许多用于多维数据分析的功能和工具,使得用户可以轻松地进行复杂的数据查询和分析操作。 Greenplum数据库适用于大规模数据分析和处理的场景,如数据仓库、商业智能、大数据分析等。它可以处理PB级别的数据,并且提供了强大的查询和分析能力。同时,Greenplum还提供了丰富的功能和工具,以帮助用户进行数据的导入、导出和转换,使得数据的处理变得更加简单和高效。 总之,Greenplum数据库是一种强大的分布式数据库系统,它通过并行处理、列存储等技术,提供了高性能和可扩展的数据分析和处理能力。如果您需要处理大规模数据集并进行复杂的数据分析操作,Greenplum数据库是一个值得考虑的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值