文章目录
2021SC@SDUSC
首先再介绍一些有关Worker的知识。
然后再分析代码。
有关Worker
worker, executor,task之间的关系
worker即进程,一个worker就是一个进程,进程里面包含一个或多个线程,一个线程就是一个executor,一个线程会处理一个或多个任务,一个任务就是一个task,一个task就是一个节点类的实例对象。
storm集群的一个节点可能有一个或者多个工作进程(worker)运行在一个多个拓扑上,一个工作进程执行拓扑的一个子集。工作进程(worker)属于一个特定的拓扑,并可能为这个拓扑的一个或者多个组件(spout/bolt)运行一个或多个执行器(executor线程)。一个运行中的拓扑包括多个运行在storm集群内多个节点的进程。
1个worker进程执行的是1个topology的子集(注:不会出现1个worker为多个topology服务)。1个worker进程会启动1个或多个executor线程来执行1个topology的component(spout或bolt)。因此,1个运行中的topology就是由集群中多台物理机上的多个worker进程组成的。
executor是1个被worker进程启动的单独线程。每个executor只会运行1个topology的1个component(spout或bolt)的task(注:task可以是1个或多个,storm默认是1个component只生成1个task,executor线程里会在每次循环里顺序调用所有task实例)。
task是最终运行spout或bolt中代码的单元(注:1个task即为spout或bolt的1个实例,executor线程在执行期间会调用该task的nextTuple或execute方法)。topology启动后,1个component(spout或bolt)的task数目是固定不变的,但该component使用的executor线程数可以动态调整(例如:1个executor线程可以执行该component的1个或多个task实例)。这意味着,对于1个component存在这样的条件:#threads<=#tasks(即:线程数小于等于task数目)。默认情况下task的数目等于executor线程数目,即1个executor线程只运行1个task。
管理Worker进程的事件线程
由Supervisor来管理Worker进程的事件线程。
每个工作节点运行Supervisor守护进程,负责监听工作节点上已经分配的主机作业,启动和停止Nimbus已经分配的工作进程。
supervisor会定时从zookeeper获取拓补信息topologies、任务分配信息assignments及各类心跳信息,以此为依据进行任务分配。
在supervisor同步时,会根据新的任务分配情况来启动新的worker或者关闭旧的worker并进行负载均衡。
处理流程如下:
代码分析worker-launcher.c
setup_permissions()
static int setup_permissions(FTSENT* entry, uid_t euser, int user_write, boolean setgid_on_dir) {
if (lchown(entry->fts_path, euser, launcher_gid) != 0) {
fprintf(ERRORFILE, "ERROR: Failure to exec app initialization process - %s, fts_path=%s\n",
strerror(errno), entry->fts_path);
return -1;
}
mode_t mode = entry->fts_statp->st_mode;
// Preserve user read and execute and set group read and write.
mode_t new_mode = (mode & (S_IRUSR | S_IXUSR)) | S_IRGRP | S_IWGRP;
if (user_write) {
new_mode = new_mode | S_IWUSR;
}
// If the entry is a directory, Add group execute and setGID bits.
if ((mode & S_IFDIR) == S_IFDIR) {
new_mode = new_mode | S_IXGRP;
if (setgid_on_dir) {
new_mode = new_mode | S_ISGID;
}
}
if (chmod(entry->fts_path, new_mode) != 0) {
fprintf(ERRORFILE, "ERROR: Failure to exec app initialization process - %s, fts_path=%s\n",
strerror(errno), entry->fts_path);
return -1;
}
return 0;
}
可选地为目录设置权限,使其可由用户来写。
我们设置了权限r(w)xrws——这样文件组(应该是Storm的用户组)对目录有完全的访问权限,文件用户(拓扑所有者的用户)能够读取和执行,并在某些目录中写入。设置setGID位以确保storm的用户可以访问在该目录下创建的任何文件,以便进行清理。如果setgid_on_dir为FALSE,则目录的组权限不要设置sticky位。
mkdirs()
int mkdirs(const char* path, mode_t perm) {
struct stat sb;
char * npath;
char * p;
if (stat(path, &sb) == 0) {
return check_dir(path, sb.st_mode, perm, 1);
}
npath = strdup(path);
if (npath == NULL) {
fprintf(LOGFILE, "ERROR: Not enough memory to copy path string");
return -1;
}
/* Skip leading slashes. */
p = npath;
while (*p == '/') {
p++;
}
while (NULL != (p = strchr(p, '/'))) {
*p = '\0';
if (create_validate_dir(npath, perm, path, 0) == -1) {
free(npath);
return -1;
}
*p++ = '/'; /* restore slash */
while (*p == '/')
p++;
}
/* Create the final directory component. */
if (create_validate_dir(npath, perm, path, 1) == -1) {
free(npath);
return -1;
}
free(npath);
return 0;
}
确保给定的路径和所有父目录都以所需的权限创建。
create_validate_dir()
int create_validate_dir(const char* npath, mode_t perm, const char* path,
int finalComponent) {
struct stat sb;
if (stat(npath, &sb) != 0) {
if (mkdir(npath, perm) != 0) {
if (errno != EEXIST || stat(npath, &sb) != 0) {
fprintf(LOGFILE, "ERROR: Can't create directory %s - %s\n", npath,
strerror(errno));
return -1;
}
// The directory npath should exist.
if (check_dir(npath, sb.st_mode, perm, finalComponent) == -1) {
return -1;
}
}
} else {
if (check_dir(npath, sb.st_mode, perm, finalComponent) == -1) {
return -1;
}
}
return 0;
}
如果父目录不存在,就创建。如果发生竞争条件,就检查权限。用0或1表示这是否是最后的分量。如果是,我们需要检查权限。
setup_worker_tmp_permissions()
int setup_worker_tmp_permissions(const char *worker_dir) {
char* worker_tmp = concatenate("%s/tmp", "worker tmp dir", 1, worker_dir);
if (worker_tmp != NULL) {
int exit_code = setup_dir_permissions(worker_tmp, 1, FALSE);
if (exit_code != 0) {
fprintf(ERRORFILE, "ERROR: setup_dir_permissions on %s failed\n", worker_tmp);
fflush(ERRORFILE);
}
return exit_code;
} else {
return -1;
}
}
移除worker-id/tmp目录上的setgid以便java剖析能够工作。这对于non-container workers来说是不需要的。但最好保持一致。