《Reids 设计与实现》第十六章 集群(下)_为什么不直接向节点广播publish命令-程序员宅基地

技术标签: Redis  《Redis 设计与实现》  

《Reids 设计与实现》第十六章 集群(下)

七、复制与故障转移

Redis 集群中的节点分为主节点和从节点,其中主节点用于处理槽,而从节点则用于复制某个主节点,并在被复制的主节点下线时,代替下线主节点继续处理命令请求

举个例子,对于包含 7000、7001、7002、7003 四个主节点的集群来说,我们可以将 7004、7005 两个节点添加到集群里面,并将这两个节点设定为节点 7000 的从节点,如图 17-32 所示(图中以双圆形表示主节点,单圆形表示从节点)

在这里插入图片描述

表 17-1 记录了集群各个节点的当前状态,以及它们正在做的工作

在这里插入图片描述

如果这时,节点 7000 进入下线状态,那么集群中仍在正常运作的几个主节点将在节点 7000 的两个从节点 —— 节点 7004 和节点 7005 中选出一个节点作为新的主节点,这个新的主节点将接管原来节点 7000 负责处理的槽,并继续处理客户端发送的命令请求

例如,如果节点 7004 被选中为新的主节点,那么节点 7004 将接管原来由节点 7000 负责处理的槽 0 至槽 5000,节点 7005 也会从原来的复制节点 7000,改为复制节点 7004,如图 17-33 所示(图中用虚线包围的节点为已下线节点)

在这里插入图片描述

表 17-2 记录了在对节点 7000 进行故障转移之后,集群各个节点的当前状态,以及它们正在做的工作

在这里插入图片描述

如果在故障转移完成之后,下线的节点 7000 重新上线,那么它键成为节点 7004 的从节点,如图 17-34 所示

在这里插入图片描述

表 17-3 展示了节点 7000 复制节点 7004 之后,集群中各个节点的状态

在这里插入图片描述

1.设置从节点

向一个节点发送命令:

CLUSTER REPLICATION <node_id>

可以让接收命令的节点成为 node_id 所指定节点的从节点,并开始对主节点进行复制:

  • 接收到该命令的节点首先会在自己的 clusterState.nodes 字典中找到 node_id 所对应节点的 clusterNode 结构,并将自己的 clusterState.myself.slaveof 指针指向这个结构,以此来记录这个节点正在复制的主节点:
struct clusterNode{
    
	//...
	//如果这是一个从节点,那么指向主节点
	struct clusterNode *slaveof
	//...
};
  • 然后节点会修改自己在 clusterState.myself.flags 中的属性,关闭原本的 REDIS_NODE_MASTER 标识,打开 REDIS_NODE_SLAVE 标识,标识这个节点已经由原来的主节点变成了从节点
  • 最后,节点会调用复制代码,并根据 clusterState.myself.slaveof 指向的 clusterNode 结构所保存的 IP 地址和端口号,对主节点进行复制。因为节点的复制功能和单机 Redis 服务器的复制功能使用了相同的代码,所以让从节点复制主节点相当于向从节点发送命令 SLAVEOF <master_ip> <master_port>

在这里插入图片描述

图 17-35 展示了节点 7004 在复制节点 7000 时的 clusterState 结构:

  • clusterState.myself.flags 属性的值为 REDIS_NODE_SLAVE,表示节点 7004 是一个从节点
  • clusterState.myself.slaveof 指针指向代表节点 7000 的结构,表示节点 7004 正在复制的主节点为节点 7000

一个节点成为从节点,并开始复制某个主节点这一信息会通过消息发送给集群中的其他节点,最终集群中的所有节点都会知道某个从节点正在复制某个主节点

集群中的所有节点都会在代表主节点的 clusterNode 结构的 slaves 属性和 numslaves 属性中记录正在复制这个主节点的从节点名单:

struct clusterNode{
    
	//...
	//正在复制这个主节点的从节点数量
	int numslaves;

	//一个数组
	//每个数据项指向一个正在复制这个主节点的 clusterNode 结构
	struct clusterNode **slaves;
	//...
};

在这里插入图片描述

举个例子,图 17-36 记录了节点 7004 和节点 7005 成为节点 7000 的从节点之后,集群中的各个节点为节点 7000 创建的 clusterNode 结构的样子:

  • 代表节点 7000 的 clusterNode 结构的 numslaves 属性的值为 2,这说明有两个从节点正在复制节点 7000
  • 代表节点 7000 的 clusterNode 结构的 slaves 数组的两个项分别指向代表节点 7004 和代表节点 7005 的 clusterNode 结构,这说明节点 7000 的两个从节点分别是节点 7004 和节点 7005

2.故障检测

集群中的每个节点都会定期地向集群中的其他节点发送 PING 消息,以此来检测对方是否在线,如果接收 PING 消息的节点没有在规定的时间内,向发送 PING 消息的节点返回 PONG 消息,那么发送 PING 消息的节点就会将接收 PING 消息的节点标记为疑似下线(probable fail,PFAIL)

举个例子,如果节点 7001 向节点 7000 发送一条 PING 消息,但是节点 7000 没有在规定的时间内,向节点 7001 返回一条 PONG 消息,那么节点 7001 就会在自己的 clusterState.nodes 字典中找到节点 7000 所对应的 clusterNode 结构,并在结构的 flags 属性中打开 REDIS_NODE_PFAIL 标识,以此表示节点 7000 进入了疑似下线状态,如图 17-27 所示

在这里插入图片描述

集群中的各个节点会通过互相发送消息的方式来交换集群中各个节点的状态信息,例如某个节点是处于在线状态、疑似下线状态(PFAIL),还是已下线状态(FAIL)

当一个主节点 A 通过消息得知主节点 B 认为主节点 C 进入了疑似下线状态时,主节点 A 会在自己的 clusterState.nodes 字典中找到主节点 C 所对应的 clusterNode 结构,并将主节点 B 的下线报告(failure report)添加到 clusterNode 结构的 fail_reports 链表里面:

struct clusterNode{
    
	//...
	//一个链表,记录了所有其他节点对该节点的下线报告
	list *fail_reports;
	//...
};

每个下线报告由一个 clusterNodeFailReport 结构表示:

struct clusterNodeFailReport{
    
	//报告目标节点已经下线的节点
	struct clusterNode *node;

	//最后一次从 node 节点收到下线报告的时间
	//程序使用这个时间戳来检查下线报告是否过期
	//(与当前时间相差太久的下线报告会被删除)
	mstime_t time;
}typedef clusterNodeFailReport;

举个例子,如果主节点 7001 在收到主节点 7002、主节点 7003 发送的消息后得知,主节点 7002 和主节点 7003 都认为主节点 7000 进入了疑似下线状态,那么主节点 7001 将为主节点 7000 创建图 17-38 所示的下线报告

在这里插入图片描述

如果在一个集群里面,半数以上负责处理槽的主节点都将某个主节点 x 报告为疑似下线,那么这个主节点 x 将被标记为已下线(FAIL),将主节点 x 标记为已下线的节点回向集群广播一条关于主节点 x 的 FAIL 消息,所有收到这条 FAIL 消息的节点都会立即将主节点 x 标记为已下线

举个例子,对于图 17-38 所示的下线报告来说,主节点 7002 和主节点 7003 都认为主节点 7000 进入了下线状态,并且主节点 7001 也认为主节点 7000 进入了疑似下线状态(代表主节点 7000 的结构打开了 REDIS_NODE_PFAIL 标识),综合i起来,在集群四个负责处理槽的主节点里面,有三个都将主节点 7000 标记为下线,数量已经超过了半数,所以主节点 7001 会将主节点 7000 标记为已下线,并向集群广播一条关于主节点 7000 的 FAIL 消息

在这里插入图片描述

3.故障转移

当一个从节点发现自己正在复制的主节点进入了已下线状态时,从节点将开始对下线主节点进行故障转移,以下是故障转移的执行步骤;

  • 复制下线主节点的所有从节点里面,会有一个从节点被选中
  • 被选中的从节点会执行 SLAVEOF no one 命令,成为新的主节点
  • 新的主节点会撤销所有对已下线主节点的槽指派,并将这些槽全部指派给自己
  • 新的主节点向集群广播一条 PONG 消息,这条 PONG 消息可以让集群中的其他节点立即知道这个节点已经由从节点变成了主节点,并且这个主节点已经接管了原本由已下线节点复制处理的槽‘
  • 新的主节点开始接收和自己负责处理的槽有关的命令请求,故障转移完成

4.选举新的主节点

新的主节点是通过选举产生的

以下是集群选举新的主节点的方法:

  1. 集群的配置纪元是一个自增计数器,它的初始值为 0
  2. 当集群里的某个节点开始一次故障转移操作时,集群配置纪元的值会被增一
  3. 对于每个配置纪元,集群里每个负责处理槽的主节点都有一次投票的机会,而第一个向主节点要求投票的从节点将获得主节点的投票
  4. 当从节点发现自己正在复制的主节点进入已下线状态时,从节点会向集群广播一条 CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST 消息,要求所有收到这条消息、并且具有投票权的主节点向这个从节点投票
  5. 如果一个主节点具有投票权(它正在负责处理槽),并且这个主节点尚未投票给其他从节点,那么主节点将向要求投票的从节点返回一条 CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK 消息,表示这个主节点支持从节点成为新的主节点
  6. 每个参与选举的从节点都会接收 CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK 消息,并根据自己收到了多少条这种消息来统计自己获得了多少主节点的支持
  7. 如果集群里有 N 个具有投票权的主节点,那么当一个从节点收集到大于等于 N/2+1 张支持票时,这个从节点就会当选为新的主节点
  8. 因为在每一个配置纪元里面,每个具有投票权的主节点只投一次票,所以如果有 N 个主节点进行投票,那么具有大于等 N/2+1 张支持票的从节点只会有一个,这确保了新的主节点只会有一个
  9. 如果在一个配置纪元里面没有从节点能收集到足够多的支持票,那么集群进入一个新的配置纪元,并再次进行选举,知道选出新的主节点为止

这个选举新主节点的方法和选举领头 Sentinel 的方法非常相似,因为两者都是基于 Raft 算法的领头选举(leader election)方法来实现的

八、消息

集群中的各个节点通过发送和接收消息(message)来进行通信,我们称发送消息的节点为发送者(sender),接收消息的节点为接收者(receiver)

节点发送的消息主要有以下五种:

  • MEET 消息:当发送者接到客户端发送的 CLUSTER MEET 命令时,发送者会向接收者发送 MEET 消息,请求接收者加入到发送者当前所处的集群里面
  • PING 消息:集群里的每个节点默认每隔一秒钟就会从已知节点列表中随机选出五个节点,然后对这五个节点中最长时间没有发送过 PING 消息的节点发送 PING 消息,以此来检测被选中的节点是否在线。除此之外,如果节点 A 最后一次收到节点 B 发送的 PONG 消息的时间,距离当前时间已经超过了节点 A 的 cluster-node-timeout 选项设置时长的一半,那么节点 A 也会向节点 B 发送 PING 消息,这可以防止节点 A 因为长时间没有随机选中节点 B 作为 PING 消息的发送对象而导致对节点 B 的信息更新滞后
  • PONG 消息:当接收者收到发送者发来的 MEET 消息或者 PING 消息时,为了向发送者确认这条 MEET 消息或者 PING 消息已到达,接收者会向发送者返回一条 PONG 消息。另外,一个节点也可以通过向集群广播自己的 PONG 消息来让集群中的其他节点立即刷新关于这个节点的认识,例如当一次故障转移操作成功执行之后,新的主节点会向集群广播一条 PONG 消息,以此来让集群中的其他节点立即知道这个节点已经变成了主节点,并且接管了已下线节点负责的槽
  • FAIL 消息:当一个主节点 A 判断另一个主节点 B 已经进入 FAIL 状态时,节点 A 会向集群广播一条关于节点 B 的 FAIL 消息,所有收到这条消息的节点都会立即将节点 B 标记为已下线
  • PUBLISH 消息:当节点接收到一个 PUBLISH 命令时,节点会执行这个命令,并向集群广播一条 PUBLISH 消息,所有接收到这条 PUBLISH 消息的节点都会执行相同的 PUBLISH 命令

一条消息由消息头(header)和消息正文(data)组成,接下来的内容将首先介绍消息头,然后再分别介绍上面提到的五种不同类型的消息正文

1.消息头

节点发送的所有消息都由一个消息头包裹,消息头除了包含消息正文之外,还记录了消息发送者自身的一些信息,因为这些信息也会被消息接收者用到,所以严格来讲,我们可以认为消息头本身也是消息的一部分

每个消息头都由一个 cluster.h/clusterMsg 结构表示:

typedef struct{
    
	//消息的长度(包括这个消息头的长度和消息正文的长度)
	uint32_t totlen;

	//消息的类型
	uint16_t type;

	//消息正文包含的节点信息数量
	//只在发送 MEET、PING、PONG 这三种 Gossip 协议消息时使用
	uint16_t configEpoch;

	//发送者的名字(ID)
	char sender[REDIS_CLUSTER_NAMELEN];

	//发送者目前的槽指派信息
	unsigned char myslots[REDIS_CLUSTER_SLOTS/8];

	//如果发送者是一个从节点,那么这里记录的是发送者正在复制的主节点的名字
	//如果发送者是一个主节点,那么这里记录的是 REDIS_NODE_NULL_NAME
	//(一个 40 字节长,值全为 0 的字节数组)
	char slaveof[REDIS_CLUSTER_NAMELEN];

	//发送者的端口号
	uint16_t port;

	//发送者的标识值
	uint16_t flags;

	//发送者所处集群的状态
	unsigned char state;

	//消息的正文(或者说,内容)
	union clusterMsgData data;
}clusterMsg;

clusterMsg.data 属性指向联合 cluster.h/clusterMsgData,这个联合就是消息的正文:

union clusterMsgData{
    
	//MEET、PING、PONG 消息的正文
	struct{
    
		//每条 MEET、PING、PONG 消息都包含两个
		//clusterMsgDataGossip 结构
		clusterMsgDataGossip gossip[1];
	}ping;

	//FAIL 消息的正文
	struct{
    
		clusterMsgDataFail about;
	}fail;

	//PUBLISH 消息的正文
	struct{
    
		clusterMsgDataPublish msg;
	}publish;

	//其他消息的正文...
};

clusterMsg 结构的 currentEpoch、sender、myslots 等属性记录了发送者自身的节点信息,接收者会根据这些信息,在自己的 clusterState.nodes 字典里找到发送者对应的 clusterNode 结构,并对结构进行更新

举个例子,通过对比接收者为发送者记录的槽指派信息,以及发送者在消息头的 myslots 属性记录的槽指派信息,接收者可以知道发送者的槽指派信息是否发生了变化

又或者说,通过对比接收者为发送者记录的标识值,以及发送者在消息头的 flags 属性记录的标识值,接收者可以知道发送者的状态和角色是否发生了变化,例如节点状态由原来的在线变成了下线,或者由主节点变成了从节点等等

2.MEET、PING、PONG 消息的实现

Redis 集群中的各个节点通过 Gossip 协议来交换各自关于不同节点的状态信息,其中 Gossip 协议由 MEET、PING、PONG 三种消息实现,这三种消息的正文都由两个 cluster.h/clusterMsgDataGossip 结构组成:

union clusterMsgData{
    
	//...
	//MEET、PING 和 PONG 消息的正文
	struct{
    
		//每条 MEET、PING、PONG 消息都包含两个
		//clusterMsgDataGossip 结构
		clusterMsgDataGossip gossip[1];
	}ping;
	//其他消息的正文...
};

因为 MEET、PING、PONG 三种消息都使用相同的消息正文,所以节点通过消息头的 type 属性来判断一条消息是 MEET 消息、PING 消息还是 PONG 消息

每次发送 MEET、PING、PONG 消息时,发送者都从自己的已知节点列表中随机选出两个节点(可以是主节点或者从节点),并将这两个被选中节点的信息分别保存到两个 clusterMsgDataGossip 结构里面

clusterMsgDataGossip 结构记录了被选中节点的名字,发送者与被选中节点最后一次发送和接收 PING 消息和 PONG 消息的时间戳,被选中节点的 IP 地址和端口号,以及被选中节点的标识值:

typedef struct{
    
	//节点的名字
	char nodename[REDIS_CLUSTER_NAMELEN];

	//最后一次向该节点发送 PING 消息的时间戳
	uint32_t pong_received;

	//节点的 IP 地址
	char ip[16];

	//节点的端口号
	uint16_t port;

	//节点的标识值
	uint16_t flags;
}clusterMsgDataGossip;

当接收者收到 MEET、PING、PONG 消息时,接收者会访问消息正文中的两个 clusterMsgDataGossip 结构,并根据自己是否认实 clusterMsgDataGossip 结构中记录的被选中节点来选择进行哪种操作:

  • 如果被选中节点不存在于接收者的已知节点列表,那么说明接收者是第一次接触到被选中节点,接收者将根据结构中记录的 IP 地址和端口号等信息,与被选中节点进行握手
  • 如果被选中节点已经存在于接收者的已知节点列表,那么说明接收者之前已经与被选中节点进行过接触,接收者将根据 clusterMsgDataGossip 结构记录的信息,对被选中节点所对应的 clusterNode 结构进行更新

举个发送 PING 消息和返回 PONG 消息的例子,假设在一个包含 A、B、C、D、E、F 六个节点的集群里:

  • 节点 A 向节点 D发送 PING 消息,并且消息里面包含了节点 B 和节点 C 的信息,当节点 D 收到这条 PING 消息时,它将更新自己对节点 B 和节点 C 的认识
  • 之后,节点 D 将向节点 A 返回一条 PONG 消息,并且消息里面包含了节点 E 和 节点 F 的消息,当节点 A 收到这条 PONG 消息时,它将更新自己对节点 E 和 节点 F 的认识

整个通信过程如图 17-41 所示

在这里插入图片描述

3.FAIL 消息的实现

当集群里的主节点 A 将主节点 B 标记为已下线(FAIL)时,主节点 A 将向集群广播一条关于主节点 B 的 FAIL 消息,所有接收到这条 FAIL 消息的节点都会将主节点 B 标记为已下线

在集群的节点数量比较大的情况下,单纯使用 Gossip 协议来传播节点的已下线信息会给节点的信息更新带来一定延迟,因为 Gossip 协议消息通常需要一段时间才能传播至整个集群,而发送 FAIL 消息可以让集群里的所有节点立即知道某个主节点已下线,从而尽快判断是否需要将集群标记为下线,又或者对下线主节点进行故障转移

FAIL 消息的正文由 cluster.h/clusterMsgDataFail 结构表示,这个结构只包含一个 nodename 属性,该属性记录了已下线节点的名字:

typedef struct{
    
	char nodename[REDIS_CLUSTER_NAMELEN];
}clusterMsgDataFail;

因为集群里的所有节点都一个独一无二的名字,所以 FAIL 消息里面只需要保存下线节点的名字,接收到消息的节点就可以根据这个名字来判断是哪个节点下线了

举个例子,对于包含 7000、7001、7002、7003 四个主节点的集群来说;

  • 如果主节点 7001 发现主节点 7000 已下线,那么主节点 7001 将向主节点 7002 和主节点 7003 发送 FAIL 消息,其中 FAIL 消息中包含的节点名字为主节点 7000 的名字,以此来表示主节点 7000 已下线
  • 当主节点 7002 和主节点 7003 都接收到主节点 7001 发送的 FAIL 消息时,它们也会将主节点 7000 标记为已下线
  • 因为这时集群已经有超过一半的主节点认为主节点 7000 已下线,所以集群剩下的几个主节点可以判断是否需要将集群标记为下线,又或者开始对主节点 7000 进行故障转移

图 17-42 至图 17-44 展示了节点发送和接收 FAIL 消息的整个过程

在这里插入图片描述

4.PUBLISH 消息的实现

当客户端向集群中的某个节点发送命令:

PUBLISH <channel> <message>

的时候,接收到 PUBLISH 命令的节点不仅会向 channel 频道发送消息 message,它还会向集群广播一条 PUBLISH 消息,所有接收到这条 PUBLISH 消息的节点都会向 channel 频道发送 message 消息

换句话说,向集群中的某个节点发送命令;

PUBLISH <channel> <message>

将导致集群中的所有节点都向 channel 频道发送 message 消息

举个例子,对于包含 7000、7001、7002、7003 四个节点的集群来说,如果节点 7000 收到了客户端发送的 PUBLISH 命令,那么节点 7000 将向 7001、7002、7003 三个节点发送 PUBLISH 消息,如图 17-45 所示

在这里插入图片描述

PUBLISH 消息的正文由 cluster.h/clusterMsgDataPublish 结构表示;

typedef struct{
    
	uint32_t channel_len;
	uint32_t message_len;

	//定义为 8 字节只是为了对齐其他消息结构
	//实际的长度由保存的内容决定
	unsigned char bulk_data[8];
}clusterMsgDataPublish;

clusterMsgDataPublish 结构的 bulk_data 属性是一个字节数组,这个字节数组保存了客户端通过 PUBLISH 命令发送给节点的 channel 参数和 message 参数,而结构的 channel_len 和 message_len 则分别保存了 channel 参数的长度和 message 参数的长度:

  • 其中 bulk_data 的 0 字节至 channel_len - 1 字节保存的是 channel 参数
  • 而 bulk_data 的 channel_len 字节至 channel_len + messsage_len - 1 字节保存的则是 message 参数

举个例子,如果节点收到的 PUBLISH 命令为:

PUBLISH "news.it" "hello"

那么节点发送的 PUBLISH 消息的 clusterMsgDataPublish 结构将如图 17-46 所示:

在这里插入图片描述

其中 bulk_data 数组的前七个字节保存了 channel 参数的值 “news.it”,而 bulk_data 数组的后五个字节则保存了 message 参数的值 “hello”

为什么不直接向所有节点广播 PUBLISH 命令

实际上,要让集群的所有节点都执行相同的 PUBLISH 命令,最简单的方法就是向所有节点广播相同的 PUBLISH 命令,这也是 Redis 在复制 PUBLISH 命令时所使用的方法,不过因为这种做法并不符合 Redis 集群的 “各个节点通过发送和接收消息来进行通信” 这一规则,所以节点没有采取广播 PUBLISH 命令的做法

九、重点回顾

  • 节点通过握手来将其他节点添加到自己所处的集群当中
  • 集群中的 16384 个槽可以分别指派给集群中的各个节点,每个节点都会记录哪些槽指派给了自己,而哪些槽又被指派给了其他节点
  • 节点在接到一个命令请求时,会先检查这个命令请求要处理的键所在的槽是否自己负责,如果不是的话,节点将向客户端返回一个 MOVED 错误,MOVED 错误携带的信息可以指引客户端转向至正在负责相关槽的节点
  • 对 Redis 集群的重新分片工作是由 redis-trib 负责执行的,重新分片的关键是将属于某个槽的所有键值对从一个节点转移至另一个节点
  • 如果节点 A 正在迁移槽 i 至节点 B,那么当节点 A 没能在自己的数据库中找到命令指定的数据库键时,节点 A 会向客户端返回一个 ASK 错误,指引客户端到节点 B 继续查找指定的数据库键
  • MOVED 错误表示槽的负责权已经从一个节点转移到了另一个节点,而 ASK 错误只是两个节点在迁移槽的过程中使用的一种临时措施
  • 集群里的从节点用于复制主节点,并在主节点下线时,代替主节点继续处理命令请求
  • 集群中的节点通过发送和接收消息来进行通信,常见的消息包括 MEET、PING、PONG、PUBLISH、FAIL 五种
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_45593575/article/details/122084839

智能推荐

手把手教你安装Eclipse最新版本的详细教程 (非常详细,非常实用)_eclipse安装教程-程序员宅基地

文章浏览阅读4.4k次,点赞2次,收藏16次。写这篇文章的由来是因为后边要用这个工具,但是由于某些原因有部分小伙伴和童鞋们可能不会安装此工具,为了方便小伙伴们和童鞋们的后续学习和不打击他们的积极性,因为80%的人都是死在工具的安装这第一道门槛上,这门槛说高也不高说低也不是太低。所以就抽时间水了这一篇文章。_eclipse安装教程

分享11个web前端开发实战项目案例+源码_前端项目实战案例-程序员宅基地

文章浏览阅读4.1w次,点赞12次,收藏193次。小编为大家收集了11个web前端开发,大企业实战项目案例+5W行源码!拿走玩去吧!1)小米官网项目描述:首先选择小米官网为第一个实战案例,是因为刚开始入门,有个参考点,另外站点比较偏向目前的卡片式设计,实现常见效果。目的为学者练习编写小米官网,熟悉div+css布局。学习资料的话可以加下web前端开发学习裙:600加上610再加上151自己去群里下载下。项目技术:HTML+CSS+Div布局2)迅雷官网项目描述:此站点特效较多,所以通过练习编写次站点,学生可以更多练习CSS3的新特性过渡与动画的实_前端项目实战案例

计算质数-埃里克森筛法(间隔黄金武器)-程序员宅基地

文章浏览阅读73次。素数,不同的质数,各种各样的问题总是遇到的素数。以下我们来说一下求素数的一种比較有效的算法。就是筛法。由于这个要求得1-n区间的素数仅仅须要O(nloglogn)的时间复杂度。以下来说一下它的思路。思路:如今又1-n的数字。素数嘛就是除了1和本身之外没有其它的约数。所以有约数的都不是素数。我们从2開始往后遍历,是2的倍数的都不是素数。所以我们把他们划掉然后如...

探索Keras DCGAN:深度学习中的创新图像生成-程序员宅基地

文章浏览阅读532次,点赞9次,收藏14次。探索Keras DCGAN:深度学习中的创新图像生成项目地址:https://gitcode.com/jacobgil/keras-dcgan在数据驱动的时代,图像生成模型已经成为人工智能的一个重要领域。其中,Keras DCGAN 是一个基于 Keras 的实现,用于构建和训练 Deep Convolutional Generative Adversarial Networks(深度卷积生...

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found):_spring-could org.apache.ibatis.binding.bindingexce-程序员宅基地

文章浏览阅读116次。今天在搭建springcloud项目时,发现如上错误,顺便整理一下这个异常:1. mapper.xml的命名空间(namespace)是否跟mapper的接口路径一致<mapper namespace="com.baicun.springcloudprovider.mapper.SysUserMapper">2.mapper.xml接口名是否和mapper.java接..._spring-could org.apache.ibatis.binding.bindingexception: invalid bound state

四种高效数据库设计思想——提高查询效率_数据库为什么能提高效率-程序员宅基地

文章浏览阅读1.1k次。四种高效数据库设计思想——提高查询效率:设计数据库表结构时,我们首先要按照数据库的三大范式进行建立数据。1. 1NF每列不可拆分2. 2NF确保每个表只做一件事情3. 3NF满足2NF,消除表中的依赖传递。三大范式的出现是在上世纪70年代,由于内存资源比较昂贵,所以严格按照三大范式进行数据库设计。而如今内存变得越来越廉价,在考虑效率和内存的基础上我们可以做出最优选择以达到最高效率。_数据库为什么能提高效率

随便推点

什么是配置_基于配置是什么意思-程序员宅基地

文章浏览阅读1.6k次。应用程序在启动和运行的时候往往需要读取一些配置信息,配置基本上伴随着应用程序的整个生命周期,比如:数 据库连接参数、启动参数等。配置主要有以下几个特点:配置是独立于程序的只读变量配置对于程序是只读的,程序通过读取配置来改变自己的行为,但是程序不应该去改变配置配置伴随应用的整个生命周期配置贯穿于应用的整个生命周期,应用在启动时通过读取配置来初始化,在运行时根据配置调整行为。比如:启动时需要读取服务的端口号、系统在运行过程中需要读取定时策略执行定时任务等。配置可以有多种加载方式常见的有程序内部_基于配置是什么意思

二、使用GObject——一个简单类的实现-程序员宅基地

文章浏览阅读170次。Glib库实现了一个非常重要的基础类--GObject,这个类中封装了许多我们在定义和实现类时经常用到的机制: 引用计数式的内存管理 对象的构造与析构 通用的属性(Property)机制 Signal的简单使用方式 很多使用GObject..._

golang 定时任务处理-程序员宅基地

文章浏览阅读6.3k次,点赞2次,收藏9次。在 golang 中若写定时脚本,有两种实现。一、基于原生语法组装func DocSyncTaskCronJob() { ticker := time.NewTicker(time.Minute * 5) // 每分钟执行一次 for range ticker.C { ProcTask() }}func ProcTask() { log.Println("hello world")}二、基于 github 中封装的 cron 库实现package taskimport (_golang 定时任务

VC获取精确时间的方法_vc 通过线程和 sleep 获取精准时间-程序员宅基地

文章浏览阅读2.1k次。 来源:http://blog.csdn.net/clever101/archive/2008/10/18/3096049.aspx 声明:本文章是我整合网上的资料而成的,其中的大部分文字不是我所为的,我所起的作用只是归纳整理并添加我的一些看法。非常感谢引用到的文字的作者的辛勤劳动,所参考的文献在文章最后我已一一列出。 对关注性能的程序开发人员而言,一个好的计时部件既是益友,也_vc 通过线程和 sleep 获取精准时间

wml入门-程序员宅基地

文章浏览阅读58次。公司突然说要进行wap开发了,以前从没了解过,但我却异常的兴奋,因为可以学习新东西了,呵呵,我们大家一起努力吧。首先说说环境的搭建。可以把.wml的文件看做是另一种的html进行信息的展示,但并不是所有的浏览器都支持,好用的有Opera,还有WinWap。编写wml文件语法比较严格,不好的是我还没有找到好的提示工具,就先用纯文本吧。我找到了一个很好的学习网站:http://w3sc..._winwap学习

计算机考研怎么给老师发邮件,考研复试前,手把手教你怎么给导师发邮件!4点要注意...-程序员宅基地

文章浏览阅读504次。考研成绩出来后,第一件事是干什么?当然不只是高兴,而是马上给心仪的导师发邮件,先露个“名字熟”。不要以为初试考了高分或者过线了,一切都稳妥了,一时得意忘形,居然没联系导师,等想起时,导师已经属于他人了。对于一些大佬,热门导师一定要趁早发邮件咨询,一是表示尊重;二是这类老师可能已经没有统招名额,所以越早知道,越有利于下一步计划。但是,在给导师发邮件中,要注意以下4点,不求一步成功,但求先留下个好印象..._跨考计算机怎么给导师发邮件