首页> 搜索结果页
"应该directory目录" 检索
共 4446 条结果
操作系统课设详细解答(二)
一、题目二实验二 Linux 进程管理二、实验目的通过进程的创建、撤销和运行加深对进程概念和进程并发执行的理解,明确进程和程序之间的区别。三、总体设计1.背景知识在 Linux 中创建子进程要使用 fork()函数,执行新的命令要使用 exec()系列函数,等待子进 程结束使用 wait()函数,结束终止进程使用 exit()函数。fork()原型如下:pid_t fork(void);fork 建立一个子进程,父进程继续运行,子进程在同样的位置执行同样的程序。对于父进程,fork()返回子进程的 pid, 对于子进程,fork()返回 0。出错时返回-1。2.模块介绍2-1:一个父进程,两个子进程2-2:一个父进程,一个子进程2-3:一个父进程,多个子进程3.设计步骤(1)进程的创建任务要求:编写一段程序,使用系统调用 fork()创建两个子进程。当此程序运行时,在系统 中有一个父进程和两个子进程活动。让每一个进程在屏幕上显示一个字符:父进程显示字符“a”; 两子进程分别显示字符“b”和字符“c”。步骤 1:使用 vi 或 gedit 新建一个 fork_demo.c 程序,然后拷贝清单 2-1 中的程序,使用 cc 或者gcc 编译成可执行文件 fork_demo。例如,可以使用 gcc –o fork_demo fork_demo.c 完成编译。步骤 2:在命令行输入./fork_demo 运行该程序。图2-1 进程的创建输出结果(2)子进程执行新任务任务要求:编写一段程序,使用系统调用 fork()创建一个子进程。子进程通过系统调用 exec 更换自己原有的执行代码,转去执行 Linux 命令/bin/ls (显示当前目录的列表),然后调用 exit()函 数结束。父进程则调用 waitpid()等待子进程结束,并在子进程结束后显示子进程的标识符,然后正 常结束。程序执行过程如图 2-1 所示。步骤 1:使用 vi 或 gedit 新建一个 exec_demo.c 程序,然后拷贝清单 2-2 中的程序(该程序的执 行如图 2-1 所示),使用 cc 或者 gcc 编译成可执行文件 exec_demo。例如,可以使用 gcc –o exec_demo exec_demo.c 完成编译。步骤 2:在命令行输入./exec_demo 运行该程序。步骤 3:观察该程序在屏幕上的显示结果,并分析。图2-2 子进程执行新任务输出结果(3)实现一个简单的 shell(命令行解释器) (此任务有一些难度,可选做)。任务要求:要设计的 shell 类似于 sh,bash,csh 等,必须支持以下内部命令:cd <目录>更改当前的工作目录到另一个<目录>。如果<目录>未指定,输出当前工作目录。如果<目录>不存在,应当有适当的错误信息提示。这个命令应该也能改变 PWD 的环境变量。environ 列出所有环境变量字符串的设置(类似于 Unix 系统下的 env 命令)。echo <内容 > 显示 echo 后的内容且换行help 简短概要的输出你的 shell 的使用方法和基本功能。jobs 输出 shell 当前的一系列子进程,必须提供子进程的命名和 PID 号。quit,exit,bye 退出 shell。图2-3 实现一个简单的 shell输出结果四、详细设计数据结构一个进程创建多个子进程时,则子进程之间具有兄弟关系,数据结构为链表结构,也运用了一些C++库函数。程序流程图图2-4 进程的创建流程图图2-5 子进程执行新任务流程图图2-6 实现一个简单的 shell(命令行解释器)流程图3. 关键代码2-1 创建进程#include <sys/types.h> #include <stdio.h> #include <unistd.h> int main () { int x; while((x=fork())==-1); if (x==0){ x=fork(); if(x>0) printf("b"); else printf("c"); } else printf("a"); }2-2 子进程执行新任务#include <sys/types.h> #include <stdio.h> #include <unistd.h> int main() { pid_t pid; /* fork a child process */ pid = fork(); if (pid < 0) { /* error occurred */ fprintf(stderr, "Fork Failed"); return 1; } else if (pid == 0) { /* 子进程 */ execlp("/bin/ls","ls",NULL); } else { /* 父进程 */ /* 父进程将一直等待,直到子进程运行完毕*/ wait(NULL); printf("Child Complete"); } return 0; } } return 0; }2-3 实现一个简单的 shell(命令行解释器) (选做)#include<stdio.h> #include<string.h> #include<sys/types.h> #include<unistd.h> int main() { char cmd[666]; char cata[100]; while(1) { int len,i,flag,cnt; printf("Enter commands:"); // print String scanf("%s",cmd); // Calculation String len = strlen(cmd); // for cd if(cmd[0]=='c') { flag=0; cnt=0; // Start after command for(i=3; i<len-1; i++) { // String is not null if(cmd[i]!=' ') flag=1; if(flag) { cata[cnt++] = cmd[i]; } } // String is null if(cnt==0) { printf("path error!\n"); cata[0]='.'; cata[1]='\0'; } } //for echo if(cmd[0]=='e'&&cmd[1]=='c') { flag = 0; for(i=5; i<len-1; i++) { if(cmd[i]!=' ') flag=1; if(flag) { putchar(cmd[i]); } } if(flag) putchar('\n'); } // for help if(cmd[0]=='h') { printf("/**********Method***********/\n"); printf("print cd<catalog> :find directory\n"); printf("print environ :List set\n"); printf("print echo<content> : print content\n"); printf("print help :List Method\n"); printf("print jobs :provide PID\n"); printf("print quit,exit,bye :break \n"); printf("/******Method***************/\n"); } // for quit,exit,bye if(cmd[0]=='q'||cmd[1]=='x'||cmd[0]=='b') { printf("break\n"); return 0; } else { cnt=0; // child process pid_t pid = fork(); if(pid<0) { // error occurred fprintf(stderr,"Fork Failed" ); return 1; } else if(pid==0) { //cd if(cmd[0]=='c') { execlp("/bin/ls",cata,NULL); } //jobs else if(cmd[0]=='j') { execlp("pstree","-p",NULL); } //environ else if(cmd[1]=='n') { execlp("env","",NULL); } } else { //wait child process exit wait(); } } printf("\n"); } return 0; }五、实验结果与分析实验2-1结果分析:修改后代码清单2-1后,从main()函数开始,运行父进程,通过while((x=fork())== -1)判断创建进程是否成功,如果x>0,则继续创建子进程,若成功,则此时有两个子进程和一个父进程,先创建的子进程会输出c,接下来是父进程执行完毕,输出a,后面是后创建的子进程执行完毕输出b;所以最终的输出结果是abc。实验2-2结果分析:从main()函数开始,父进程创建子进程,首先判断子进程是否创建成功,如果pid<0则创建进程失败,当pid=0时,运行子进程,输出系统当前目录。父进程将会一直等待子进程信号,只有当子进程释放信号,父进程输出“Child Complete”。实验2-3结果分析:从main()函数开始,根据下面这些关键字的标志位进行设置判断,然后再在判断之后对下面的功能进行实现:cd <目录>更改当前的工作目录到另一个<目录>。如果<目录>未指定,输出当前工作目录。如果<目录>不存在,应当有适当的错误信息提示。这个命令应该也能改变 PWD 的环境变量。environ 列出所有环境变量字符串的设置(类似于Unix 系统下的 env 命令)。echo <内容 > 显示 echo 后的内容且换行help 简短概要的输出你的 shell 的使用方法和基本功能。jobs 输出 shell 当前的一系列子进程,必须提供子进程的命名和 PID 号。quit,exit,bye 退出 shell,也就是依次终止运行的父子进程。六、小结与心得体会通过这个实验加深了我对Linux操作系统的进程概念的了解,也学会了在Linux基本运行,也使我明白了在Linux系统中子进程的创建,以及父子进程的运行过程,加深了对进程运行的理解。在Linux中利用fork建立一个子进程,父进程继续运行,子进程在同样的位置执行同样的程序。对于父进程,fork()返回子进程的 pid, 对于子进程,fork()返回 0,出错时返回-1,while((x=fork())==-1)这句话是用来判断子进程是否能创建成功,而且当x=0时运行子进程,当x>0时父进程执行,而x<0时,则进程创建不成功,通过代码确定父子进程的先后执行顺序。同时也完成实现一个简单的 shell(命令行解释器),这个是选做但我也挑战自己做这道题目,从中也收获非常多,采用了关键字这种思路去慢慢分块实现不同命令的功能,对于逻辑处理也提升很多。一、题目三实验三 互斥与同步二、实验目的(1) 回顾操作系统进程、线程的有关概念,加深对 Windows 线程的理解。(2) 了解互斥体对象,利用互斥与同步操作编写生产者-消费者问题的并发程序,加深对 P (即semWait)、V(即 semSignal)原语以及利用 P、V 原语进行进程间同步与互斥操作的理解。三、总体设计1.基本原理与算法1.1、利用的是互斥与同步中的信号量1.2、使用信号量解决有限缓冲区生产者和消费者问题2.模块介绍主要有两大模块:生产者和消费者;生产者又包括Produce(),Append(); 消费者包括Take(),Consume(); 线程的创建。3.设计步骤(1) 生产者消费者问题步骤 1:创建一个“Win32 Consol Application”工程,然后拷贝清单 3-1 中的程序,编译成可执行文件。步骤 2:在“命令提示符”窗口运行步骤 1 中生成的可执行文件,列出运行结果。步骤 3:仔细阅读源程序,找出创建线程的 WINDOWS API 函数,回答下列问题:线程的第一个执行函数是什么(从哪里开始执行)?它位于创建线程的 API 函数的第几个参数中?答:Produce()函数,位于第三个参数。步骤 4:修改清单 3-1 中的程序,调整生产者线程和消费者线程的个数,使得消费者数目大与生产者,看看结果有何不同。察看运行结果,从中你可以得出什么结论?答:当生产者个数多于消费者个数时生产速度快,生产者经常等待消费者对产品进行消费;反之,消费者经常等待生产者生产。步骤 5:修改清单 3-1 中的程序,按程序注释中的说明修改信号量 EmptySemaphore 的初始化方法,看看结果有何不同。答:结果为空,因为参数设置成可用资源为0,所以进程无法使用。步骤 6:根据步骤 4 的结果,并查看 MSDN,回答下列问题:1)CreateMutex 中有几个参数,各代表什么含义。2)CreateSemaphore 中有几个参数,各代表什么含义,信号量的初值在第几个参数中。3)程序中 P、V 原语所对应的实际 Windows API 函数是什么,写出这几条语句。4)CreateMutex 能用 CreateSemaphore 替代吗?尝试修改程序 3-1,将信号量 Mutex 完全用CreateSemaphore 及相关函数实现。写出要修改的语句。答:(1)3个;LPSECURITY_ATTRIBUTESlpMutexAttributes, // 指向安全属性的指针BOOLbInitialOwner, // 初始化互斥对象的所有者;LPCTSTRlpName // 指向互斥对象名的指针;第二个参数是FALSE,表示刚刚创建的这个Mutex不属于任何线程。(2)4个;//第一个参数:安全属性,如果为NULL则是默认安全属性 //第二个参数:信号量的初始值,要>=0且<=第三个参数 //第三个参数:信号量的最大值 //第四个参数:信号量的名称。(3)WaitForSingleObject(FullSemaphore,INFINITE); P(full);WaitForSingleObject(Mutex,INFINITE); //P(mutex);ReleaseMutex(Mutex); //V(mutex);ReleaseSemaphore(EmptySemaphore,1,NULL); //V(empty);(4)可以,Mutex=CreateSemaphore(NULL,false,false,NULL);生产者,消费者内: ReleaseMutex(Mutex);改为 ReleaseSemaphore(Mutex,1,NULL)。图3-1 生产者消费者问题输出结果(2) 读者写者问题根据实验(1)中所熟悉的 P、V 原语对应的实际 Windows API 函数,并参考教材中读者、写者问题的算法原理,尝试利用 Windows API 函数实现第一类读者写者问题(读者优先)。图3-2 读者写者问题输出结果四、详细设计数据结构应用了循环队列、数组,信号量。程序流程图图3-3 生产者消费者问题流程图图3-4 读者写者问题流程图3. 关键代码3-1 创建进程int main() { //创建各个互斥信号 //注意,互斥信号量和同步信号量的定义方法不同,互斥信号量调用的是 CreateMutex 函数,同步信号量调用的是 CreateSemaphore 函数,函数的返回值都是句柄。 Mutex = CreateMutex(NULL,FALSE,NULL); EmptySemaphore = CreateSemaphore(NULL,SIZE_OF_BUFFER,SIZE_OF_BUFFER,NULL); //将上句做如下修改,看看结果会怎样 // EmptySemaphore = CreateSemaphore(NULL,0,SIZE_OF_BUFFER-1,NULL); FullSemaphore = CreateSemaphore(NULL,0,SIZE_OF_BUFFER,NULL); //调整下面的数值,可以发现,当生产者个数多于消费者个数时, //生产速度快,生产者经常等待消费者;反之,消费者经常等待 const unsigned short PRODUCERS_COUNT = 10; //生产者的个数 const unsigned short CONSUMERS_COUNT = 1; //消费者的个数 //总的线程数 const unsigned short THREADS_COUNT = PRODUCERS_COUNT+CONSUMERS_COUNT; HANDLE hThreads[THREADS_COUNT]; //各线程的 handle DWORD producerID[PRODUCERS_COUNT]; //生产者线程的标识符 DWORD consumerID[CONSUMERS_COUNT]; //消费者线程的标识符 //创建生产者线程 for (int i=0; i<PRODUCERS_COUNT; ++i) { hThreads[i]=CreateThread(NULL,0,Producer,NULL,0,&producerID[i]); if (hThreads[i]==NULL) return -1; } //创建消费者线程 for (int i=0; i<CONSUMERS_COUNT; ++i) { hThreads[PRODUCERS_COUNT+i]=CreateThread(NULL,0,Consumer,NULL,0,&consumerID[i]); if (hThreads[i]==NULL) return -1; } while(p_ccontinue) { if(getchar()) //按回车后终止程序运行 { p_ccontinue = false; } } return 0; } //消费者 DWORD WINAPI Consumer(LPVOID lpPara) { while(p_ccontinue) { WaitForSingleObject(FullSemaphore,INFINITE); //P(full); WaitForSingleObject(Mutex,INFINITE); //P(mutex); Take(); Consume(); Sleep(1500); ReleaseMutex(Mutex); //V(mutex); ReleaseSemaphore(EmptySemaphore,1,NULL); //V(empty); } return 0; }3-2 子进程执行新任务int main() { Mutex = CreateMutex(NULL,FALSE,NULL); X = CreateMutex(NULL,FALSE,NULL); const unsigned short READERS_COUNT = 2;//创建两个读进程 const unsigned short WRITERS_COUNT = 1;//创建一个写进程 const unsigned short THREADS_COUNT = READERS_COUNT+WRITERS_COUNT; HANDLE hThreads[THREADS_COUNT]; //创建写线程 for (int i=0; i<WRITERS_COUNT; ++i) { hThreads[i]=CreateThread(NULL,0,writer,NULL,0,NULL); if (hThreads[i]==NULL) return -1; } //创建读线程 for (int i=0; i<READERS_COUNT; ++i) { hThreads[WRITERS_COUNT+i]=CreateThread(NULL,0,reader,NULL,0,NULL);//生产者线程函数Producer 线程ID&producerID[i] if (hThreads[i]==NULL) return -1; } //程序人为终止操作设计 while(p_ccontinue) { if(getchar()) //按回车后终止程序运行 { p_ccontinue = false; } } return 0; } //写者 DWORD WINAPI writer(LPVOID lpPara) { while(p_ccontinue) { WaitForSingleObject(Mutex,INFINITE); Write(); Sleep(1500); ReleaseMutex(Mutex); //V(mutex); } return 0; }五、实验结果与分析实验3-1结果分析:修改后代码清单3-1后,从main()函数开始,首先创建了生产者-消费者问题中应用到的互斥信号和同步信号以及其他基础定义,创建消费者生产者线程;最初生产者满足条件生产产品,所以先执行生产者,然后当资源有产品时,会执行消费者,生产者和消费者在代码运行过程中出现是随机的,当生产者多于消费者时,生产速度快,生产者经常等待消费者;反之,消费者经常等待;若缓冲区为空,则必定是生产者运行,缓冲区为满,则消费者运行,生产者等待,而对于结果的表示,则是调用了Append()和Consume()中的循环输出。实验3-2结果分析:这个是读写者中读者优先的问题,从main()函数开始,首先创建了生产者-消费者问题中应用到的两个互斥信号以及其他基础定义,创建消读者写者线程;最初写者先创建先运行,然后会执行读者线程,由于设置了两个互斥信号量可以将其中一个作为读者优先设置信号量,当第一个读者拿到这个互斥信号量时,写者就得等待读者释放这个信号量,而其他读者就不用就直接拿到不用判断可以运行输出。对于结果的表示,也是调用了read ()和Write()函数进行输出。六、小结与心得体会通过这个实验,我更好的了解互斥体对象,利用互斥与同步操作编写生产者-消费者问题的并发程序,加深对 P (即 semWait)、V(即 semSignal)原语以及利用 P、V 原语进行进程间同步与互斥操作的理解,生产者消费者问题是一个典型的例题,主要涉及同步与互斥,这也保证了在程序运行过程中只能有一个线程进行。然后对于3-2问题,我借鉴了《操作系统》课程书籍中的读者优先的思路,并将其实现,在这个过程中收获非常多也非常大,对于信号量以及进程的了解也更加深刻。以上只是操作系统课设部分设计内容,如果想要完整操作系统课设源代码资源有以下两种获取方式,请点击下面资源链接进行下载,希望能帮助到你!操作系统课设完整资源:点击打开下载资源操作系统课设完整资源:点击打开下载资源(注意:购买文章后,百度云盘链接大家不要直接复制链接,请手打链接否则可能打不开资源)
文章
安全  ·  算法  ·  Unix  ·  Shell  ·  Linux  ·  API  ·  C语言  ·  C++  ·  Windows
2023-01-13
Container Runtime CDI与NRI介绍
CDI介绍什么是CDICDI(Container Device Interface)是Container Runtimes支持挂载第三方设备(比如:GPU、FPGA等)机制。它引入了设备作为资源的抽象概念,这类设备由一个完全限定的名称唯一指定,该名称由设备商ID,设备类别与一个设备类别下的一个唯一名称组成,格式如下:vendor.com/class=unique_name设备商ID和设备类型(vendor.com/class)为CDI资源类型。下面是一个CDI设备名称例子:nvidia.com/gpu=all其中:nvidia.com为设备商IDgpu为类别all为CDI设备资源名称为什么需要CDI我们知道在docker中,容器如果需要使用宿主机上某个设备(或者挂载节点上某个目录或者文件),那么可以使用--device和-v完成,比如:# docker run -d --name test --device xxx:xxx -v xxx:xxx centos:7 sleep 1d但是在实际场景中这个功能远远不够,以容器中使用GPU设备为例,在安装有nvidia container toolkit的宿主机上执行如下命令:# nvidia-container-cli list /dev/nvidiactl /dev/nvidia-uvm /dev/nvidia-uvm-tools /dev/nvidia-modeset /dev/nvidia0 /usr/bin/nvidia-smi /usr/bin/nvidia-debugdump /usr/bin/nvidia-persistenced /usr/bin/nvidia-cuda-mps-control /usr/bin/nvidia-cuda-mps-server /usr/lib64/libnvidia-ml.so.460.91.03 /usr/lib64/libnvidia-cfg.so.460.91.03 /usr/lib64/libcuda.so.460.91.03 /usr/lib64/libnvidia-opencl.so.460.91.03 /usr/lib64/libnvidia-ptxjitcompiler.so.460.91.03 /usr/lib64/libnvidia-allocator.so.460.91.03 /usr/lib64/libnvidia-compiler.so.460.91.03 /usr/lib64/libnvidia-ngx.so.460.91.03 /usr/lib64/vdpau/libvdpau_nvidia.so.460.91.03 /usr/lib64/libnvidia-encode.so.460.91.03 /usr/lib64/libnvidia-opticalflow.so.460.91.03 /usr/lib64/libnvcuvid.so.460.91.03 /usr/lib64/libnvidia-eglcore.so.460.91.03 /usr/lib64/libnvidia-glcore.so.460.91.03 /usr/lib64/libnvidia-tls.so.460.91.03 /usr/lib64/libnvidia-glsi.so.460.91.03 ...... // 省略后面的部分这条命令列出了宿主机上的GPU设备以及与GPU设备相关的库文件、可执行文件等,如果一个容器需要使用GPU,那么它需要将这些文件中的全部(或者部分)挂载到容器中,该容器才能正常使用GPU。这个时候让用户使用docker run --device 和-v去挂载这些文件,肯定不现实。而且,不止GPU,其他设备挂载也有可能碰到这种问题。那么CDI思想是什么呢?仍然以GPU为例,可以把需要挂载的设备和库按照某种格式写入某一个文件中,然后在容器创建时,用户指定这个容器需要挂载刚刚定义的文件中的内容就完成了目标。Containerd如何配置CDI目前Containerd 1.7已经引入了该功能(调研该功能时,1.7正式版本还没有出,当前只有 1.7-rc.1 ReleaseNote),在containerd中配置CDI操作如下:创建两个目录:/etc/cdi和/var/run/cdi,一般/etc/cdi用于存放静态的cdi设备,如果cdi存在动态更新,那么最好放在/var/run/cdi。# mkdir -pv /etc/cdi /var/run/cdi在/etc/containerd/config.toml中添加如下配置:[plugins."io.containerd.grpc.v1.cri"]   enable_cdi = true   cdi_spec_dirs = ["/etc/cdi", "/var/run/cdi"]重启containerd服务。如何定义CDI设备下面是官方提供的例子:$ mkdir /etc/cdi $ cat > /etc/cdi/vendor.json <<EOF {   "cdiVersion": "0.5.0",   "kind": "vendor.com/device",   "devices": [     {       "name": "myDevice",       "containerEdits": {         "deviceNodes": [           {"hostPath": "/vendor/dev/card1": "path": "/dev/card1", "type": "c", "major": 25, "minor": 25, "fileMode": 384, "permissions": "rw", "uid": 1000, "gid": 1000},           {"path": "/dev/card-render1", "type": "c", "major": 25, "minor": 25, "fileMode": 384, "permissions": "rwm", "uid": 1000, "gid": 1000}         ]       }     }   ],   "containerEdits": {     "env": [       "FOO=VALID_SPEC",       "BAR=BARVALUE1"     ],     "deviceNodes": [       {"path": "/dev/vendorctl", "type": "b", "major": 25, "minor": 25, "fileMode": 384, "permissions": "rw", "uid": 1000, "gid": 1000}     ],     "mounts": [       {"hostPath": "/bin/vendorBin", "containerPath": "/bin/vendorBin"},       {"hostPath": "/usr/lib/libVendor.so.0", "containerPath": "/usr/lib/libVendor.so.0"},       {"hostPath": "tmpfs", "containerPath": "/tmp/data", "type": "tmpfs", "options": ["nosuid","strictatime","mode=755","size=65536k"]}     ],     "hooks": [       {"createContainer": {"path": "/bin/vendor-hook"} },       {"startContainer": {"path": "/usr/bin/ldconfig"} }     ]   } } EOF对于这个例子,有如下的说明:kind字段的值的格式为“VendorID/Class”,这里为“vendor.com/device”。在devices字段中,只定义了一个设备,设备名称为myDevice。containerEdits定义了该设备有哪些行为:deviceNodes表示要将宿主机上哪些设备挂载到容器中。env表示要为该容器自动添加哪些环境变量。mounts表示要将哪些文件挂载到宿主机中。hooks表示需要为容器添加哪些hooks。最外层的containerEdits(与devices字段在同一级别)为所有设备的公共行为,也就是myDevice的最终的containerEdits是两个containerEdits的并集。更详细的字段描述请参考:CDI Spec如何使用CDI设备podman中使用CDI目前支持CDI的client只有podman,用podman使用CDI设备的格式如下(以上面的vendor.com/device为例):# podman run --device vendor.com/device=myDevice ...该命令代表容器使用vendor.com/device=myDevice这个CDI设备,其中vendor.com/device与CDI Spec中的kind一致,myDevice为CDI Spec定义的设备名称(name字段)。k8s中使用CDI在k8s中为容器指定CDI设备,只需要为容器添加特定前缀的Annotation(注意是容器Annotation而不是pod Annotation),而为容器指定Annotation可以通过k8s device plugin机制实现。那么添加annotation的格式如下:AnnotationKey: cdi.k8s.io/xxxx  (xxxx名称任意) AnnotationValue: vendor.com/device=myDevice (CDI设备名称)NVIDIA对CDI的支持nvidia container runtime机制在没有CDI之前,NVIDIA对容器中使用GPU的场景提供了一套称为nvidia container runtime的方案,示意图如下:以docker为例整体流程如下:在/etc/docker/daemon.json将runtime替换为nvidia-container-runtime,然后重启docker。创建容器时,nvidia-container-runtime检查容器的oci spec中的环境变量是否有特定环境变量NVIDIA_VISIBLE_DEVICES;如果有,那么需要在容器oci spec中添加一个prestart hook(告诉runc在启动容器之前,执行这个hook),然后nvidia-container-runtime会调用真正的runc binary:{   ...... // 省略其他内容    "hooks":{       "prestart":[          {             "path":"/usr/bin/nvidia-container-runtime-hook",             "args":[                "/usr/bin/nvidia-container-runtime-hook",                "prestart"             ]          }       ]    }   ...... // 省略其他内容 }在容器启动之前,runc会调用nvidia-container-runtime-hook(该文件是nvidia-container-toolkit二进制文件的软连接),这个hook将会借助libnvidia-container等工具修改容器cgroup,把前面介绍的一些与GPU相关的文件挂载到容器中。这套机制存在一些问题:只针对GPU,如果是其他异构计算设备,那么需要重新开发在高版本的systemd环境下使用nvidia container runtime可能会存在一些问题,在高版本的systemd中会有如下行为:在执行systemctl daemon-reload、systemctl daemon-reexec等操作时,会更新cgroup相关配置,进而影响NVIDIA GPU设备在容器中的正常使用,相关社区issue请参考:1671、48。 针对这些问题,nvidia正在逐步使用CDI替换原有的这一套机制。CDI机制nvidia-container-toolkit从1.12开始实现对CDI支持。如果要使用CDI版本的nvidia-container-toolkit,那么只需要安装一个名称为nvidia-container-toolkit-base包,该包提供两个二进制文件(nvidia-container-runtime和nvidia-ctk):# rpm -ql nvidia-container-toolkit-base /etc/nvidia-container-runtime/config.toml /usr/bin/nvidia-container-runtime /usr/bin/nvidia-ctk /usr/share/licenses/nvidia-container-toolkit-base-1.12.0 /usr/share/licenses/nvidia-container-toolkit-base-1.12.0/LICENSE然后在宿主机上使用如下命令生成CDI设备:# nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml注意CDI设备将放在/etc/cdi/nvidia.yaml,由于文件内容过多,省略了mounts部分挂载众多的库文件:--- cdiVersion: 0.5.0 containerEdits:   deviceNodes:   - path: /dev/nvidia-uvm-tools   - path: /dev/nvidia-uvm   - path: /dev/nvidiactl   - path: /dev/nvidia-modeset   hooks:   - args:     - nvidia-ctk     - hook     - update-ldcache     - --folder     - /lib     - --folder     - /lib64     - --folder     - /lib/vdpau     - --folder     - /lib64/vdpau     hookName: createContainer     path: /usr/bin/nvidia-ctk   - args:     - nvidia-ctk     - hook     - chmod     - --mode     - "755"     - --path     - /dev/dri     hookName: createContainer     path: /usr/bin/nvidia-ctk   mounts:   - containerPath: /usr/bin/nvidia-smi     hostPath: /usr/bin/nvidia-smi     options:     - ro     - nosuid     - nodev     - bind ...... # 省略其他挂载的库文件 devices: - containerEdits:     deviceNodes:     - path: /dev/nvidia0     - path: /dev/dri/card1     - path: /dev/dri/renderD128   name: "0" - containerEdits:     deviceNodes:     - path: /dev/nvidia0     - path: /dev/dri/card1     - path: /dev/dri/renderD128   name: all kind: nvidia.com/gpu从生成的CDI设备中可以看到,节点上只有一个GPU,并且CDI定义了两个设备:"0"和"all",“0”代表是挂载0号GPU卡,而“all”代表是挂载节点上的所有GPU设备,这里“0”和“all”是一样的。如果要用podman创建一个使用GPU的容器,那么可以这样使用:# podman run --device nvidia.com/gpu=all ...  挂载节点上所有GPU设备 # podman run --device nvidia.com/gpu=0 ...  挂载节点上0号GPU设备可以看到,有了CDI后,NVIDIA将逐步放弃原有的nvidia container runtime方案。如何在自定义k8s device plugin中使用CDIk8s device plugin的Allocate函数在k8s device plugin机制中,每个自定义device plugin都需要实现一个名称为Allocate的函数,函数声明以及涉及到的参数和返回值如下:// Allocate函数 func Allocate(c context.Context, requests *pluginapi.AllocateRequest) (*pluginapi.AllocateResponse, error) // Kubelet传递的参数 type ContainerAllocateRequest struct { DevicesIDs           []string `protobuf:"bytes,1,rep,name=devicesIDs,proto3" json:"devicesIDs,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_sizecache        int32    `json:"-"` } type AllocateRequest struct { ContainerRequests    []*ContainerAllocateRequest `protobuf:"bytes,1,rep,name=container_requests,json=containerRequests,proto3" json:"container_requests,omitempty"` XXX_NoUnkeyedLiteral struct{}                    `json:"-"` XXX_sizecache        int32                       `json:"-"` } // device plugin需要返回的结构体 type ContainerAllocateResponse struct { // List of environment variable to be set in the container to access one of more devices. Envs map[string]string `protobuf:"bytes,1,rep,name=envs,proto3" json:"envs,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // Mounts for the container. Mounts []*Mount `protobuf:"bytes,2,rep,name=mounts,proto3" json:"mounts,omitempty"` // Devices for the container. Devices []*DeviceSpec `protobuf:"bytes,3,rep,name=devices,proto3" json:"devices,omitempty"` // Container annotations to pass to the container runtime Annotations          map[string]string `protobuf:"bytes,4,rep,name=annotations,proto3" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` XXX_NoUnkeyedLiteral struct{}          `json:"-"` XXX_sizecache        int32             `json:"-"` } type AllocateResponse struct { ContainerResponses   []*ContainerAllocateResponse `protobuf:"bytes,1,rep,name=container_responses,json=containerResponses,proto3" json:"container_responses,omitempty"` XXX_NoUnkeyedLiteral struct{}                     `json:"-"` XXX_sizecache        int32                        `json:"-"` } 对于Allocate函数,说明如下:Device Plugin启动时会向Kubelet注册一些设备ID,这些设备ID有可能是真的设备ID,也有可能是一些无实际意义的ID。Pod申请设备资源时,申请的值为device plugin向kubelet注册的设备ID集合的子集。kubelet为每个容器分配某种设备资源(比如GPU)以后,会通过GRPC协议调用该资源对应的device plugin的Allocate函数,kubelet需要传入分配的设备ID作为Allocate参数。device plugin需要根据kubelet传入的设备ID,向kubelet返回需要给容器添加的配置,例如:如果kubelet为某个容器分配设备ID为TestID1,那么需要给容器配置如下的配置:一个环境变量xxx=xxx一个Annotation xxx=xxx挂载宿主机上某个设备文件/dev/xxx到容器中挂载宿主机上某个二进制文件/usr/bin/xxx到容器中设备ID与容器配置信息之间的对应关系就是Device plugin的Allocate函数需要完成的逻辑。关于k8s device plugin机制的更详细信息请参考官方文档:device plugin。nvidia device plugin对CDI的支持在nvidia提供的k8s-device-plugin中已经开始CDI了,关键代码如下:# url: https://github.com/NVIDIA/k8s-device-plugin/ # commitId: de3ef904890eb90d0c82580bd1cc397f77bc0098 # path: cmd/nvidia-device-plugin/server.go#L332 ......  if len(devices) > 0 { var err error     // 假设kubelet为当前容器分配0号GPU卡,2号GPU卡,那么     // 最终为容器添加的AnnotationKey为"cdi.k8s.io/nvidia-device-plugin"     // 最终为容器添加的AnnotationValue为"nvidia.com/gpu=0,nvidia.com/gpu=2"     // 下面这行代码就是这个意思,当底层containerd检测到容器有这个annotation,那么会为这个     // 容器挂载对应的CDI设备 response.Annotations, err = cdiapi.UpdateAnnotations(map[string]string{}, "nvidia-device-plugin", responseID, devices) if err != nil { klog.Errorf("Failed to add CDI annotations: %v", err) } // Unset NVIDIA_VISIBLE_DEVICES envvar to ensure devices are only injected using container annotations response.Envs = plugin.apiEnvs(plugin.deviceListEnvvar, []string{"void"}) } 更高级的device plugin实现方式对于nvidia device plugin,它实现的功能是按卡的维度申请GPU资源(某个pod申请N张GPU卡),在这个场景下,调度器是弱参与GPU卡的分配,弱参与指的是,调度器知道这个节点当前有几张GPU卡可用,只要判断pod申请的值比这个值小就行,它并不关心把哪几张GPU卡分配给这个pod,而真正执行分配操作的是节点上的kubelet,它会决定分配节点上哪几张GPU卡给pod。但在某些场景中,调度器必须是强参与资源的分配,也就是调度器不仅要知道节点上某种资源的当前可用值与pod请求值的大小比较,同时还要决定将哪些资源分配给该pod,在这种场景下,kubelet将不参与分配工作(或者说它提供的资源分配方案无效)。对于调度器需要强参与的场景中,device plugin需要从其他渠道获取调度器为该pod分配的资源信息,而不再使用kubelet提供的分配方案。一种常见的操作是调度器将设备分配信息写入pod annotation中,然后device plugin在为pod的容器提供设备挂载信息时,直接从pod annotation获取,返回给kubelet。然而理想很丰满,现实很骨感。从前面的分析可以看到,当kubelet通过GRPC协议调用device plugin的Allocate函数时,仅会向device plugin传递为容器分配的设备ID,device plugin此时并不知道这些设备ID是为哪个Pod的哪个容器分配的,也就无法从pod annotation中获取调度器为容器分配的设备信息,最后也就无法确定应该为容器添加哪些配置信息。这个问题一直困扰device plugin开发人员。有了CDI机制以后,我们提出一种新的方案,解决上面困扰我们的问题。在这个方案中,Device Plugin需要实现两个函数:Allocate和PreStartContainer,Allocate函数已经在前面介绍,PreStartContainer的函数声明如下:      // PreStartContainer is called, if indicated by Device Plugin during registeration phase,       // before each container start. Device plugin can run device specific operations       // such as resetting the device before making devices available to the container.       rpc PreStartContainer(PreStartContainerRequest) returns (PreStartContainerResponse) {}可以看到官方对PreStartContainer这个函数的解释是可以在这个函数中重置设备或为容器准备一些设备。需要注意的是,kubelet向PreStartContainer传递的参数仍然只有设备ID,从参数中仍然无法知道当前这些设备ID是为哪个容器分配的。另外,kubelet调用函数的顺序是先调用Allocate函数,然后再调用PreStartContainer。Allocate函数实现逻辑如下:假设kubelet向Allocate函数传递的设备ID列表为[devId0 devId1 devId2]。对设备ID列表[devId0 devId1 devId2]做HASH计算,假设算出的HASH值为abcde(算hash是保证每个设备ID列表与一个CDI设备保持一一对应)。返回给kubelet的容器配置信息中,为容器添加一个Annotation:Annotation的key为cdi.k8s.io/<device_plugin_name>(其中这个<device_plugin_name>不是必须的,任意字符串都行,只要保证这个Annotation的key是唯一的就行,避免与其他device plugin设置CDI Annotation key冲突。)Annotation的value为CDI设备的命名规则,即<VendorId>/<class>=<unique_name>,在本方案中<class>必须为前面算出的设备ID列表的hash值。而<unique_name>只要保证唯一就行。一个示例的CDI设备名称为:alibabacloud.com/abcde=gpushare。如果底层runtime支持CDI机制,发现容器具有这个annotation后,会自动寻找这个CDI设备,并将CDI定义的配置加入到容器的配置中。注意在这一步并没有生成CDI设备文件,只是告诉底层runtime,创建容器的是需要挂载这个CDI设备,生成CDI设备需要在PreStartContainer完成。PreStartContainer函数实现的逻辑如下:假设kubelet向Allocate函数传递的设备ID列表为[devId0 devId1 devId2]。对设备ID列表[devId0 devId1 devId2]做HASH计算,假设算出的HASH值为abcde(算hash是保证每个设备ID列表与一个CDI设备保持一一对应)。根据kubelet传递的设备ID去定位pod和容器,这里需要借助访问kubelet提供的PodResources资源完成,具体代码如下: var locateInfo *LocateInfo     // deviceIds是kubelet传递的设备ID deviceList := NewDeviceList(deviceIds) klog.V(5).Infof("list total podresource %v", len(response.PodResources)) for _, pod := range response.PodResources { for _, container := range pod.Containers { resourceDeviceIds := []string{} for _, resource := range container.Devices { if resource.ResourceName == resourceName { resourceDeviceIds = append(resourceDeviceIds, resource.DeviceIds...) } } if len(resourceDeviceIds) != 0 && deviceList.Equals(NewDeviceList(resourceDeviceIds)) { locateInfo = &LocateInfo{ PodNamespace:  pod.Namespace, PodName:       pod.Name, ContainerName: container.Name, } break } } }定位到Pod Namespace和Pod Name以及Container Name后,接下来就可以获取Pod Annotation,有两种方式:方式1:借助client-go直接访问api server拿到pod annotation,这种方式不推荐,对api server有压力。方式2:借助访问kubelet 10250端口获取节点上所有pod,然后拿到目标pod的annotation,推荐使用这种方式。从pod annotation中,拿到调度器为该pod的容器分配的设备信息,生成一个CDI文件,存放在/var/run/cdi中,不过需要注意:CDI文件中kind字段必须与前面Allocate函数中返回的容器Annotation Value一致,比如:前面Allocate函数返回的annotation value为alibabacloud.com/abcde=gpushare,那么CDI文件中的kind字段必须为alibabacloud.com/abcde,并且devcies字段中,必须有一个device name为gpushare。如果一个pod被删除,那么生成的这个CDI文件也应该被删除,可以用一个goroutine定时清理,同时CDI文件中需要有信息标记该CDI文件属于哪个pod,比如给容器添加一个环境变量,该环境变量记录pod名称。PreStartContainer不需要给kubelet返回相关配置信息。可以看到PreStartContainer主要的任务就是将调度器为Pod的容器分配的设备信息写入CDI文件中。看到这里,或许有一个疑问:写入CDI文件为什么不在Allocate函数直接完成,非要在PreStartContainer函数中完成?主要原因在于某个pod进入kubelet PodResources Cache是在kubelet调用Allocate函数后进行的,所以在Allocate函数中如果希望通过传入Device Id列表在PodResources去定位pod name和container name是无法实现的。NRI介绍什么是NRINRI (Node Resource Interface)允许将用户某些自定的逻辑插入到OCI兼容的运行时中,此逻辑可以对容器进行受控更改,或在容器生命周期的某些点执行 OCI 范围之外的额外操作。例如,用于改进设备和其他容器资源的分配和管理。NRI已经在containerd 1.7.0开始支持(目前1.7.0版本还没发布,最新版本为1.7.0-rc.2),不过是一个experimental状态。当前NRI plugins与containerd交互的协议是通过GRPC完成,也就是NRI一般都是以Daemon形式存在。NRI能够订阅Pod和容器生命周期事件,下面将介绍。订阅Pod生命周期事件NRI能够订阅Pod生命周期事件,包括:creationstoppingremoval相关的API如下:// 当有pod在节点上创建时,NRI插件将收到该事件 func (p *plugin) RunPodSandbox(pod *api.PodSandbox) error { return nil } // 当有pod在节点上停止时,NRI插件将收到该事件 func (p *plugin) StopPodSandbox(pod *api.PodSandbox) error { return nil } // 当有pod在节点上移除时,NRI插件将收到该事件 func (p *plugin) RemovePodSandbox(pod *api.PodSandbox) error { return nil }订阅容器生命周期事件NRI能够订阅容器生命周期事件,包括:creation (*)post-creationstartingpost-startupdating (*)post-updatestopping (*)removal相关的API如下:// 创建容器 func (p *plugin) CreateContainer(pod *api.PodSandbox, container *api.Container) (*api.ContainerAdjustment, []*api.ContainerUpdate, error) { return nil, nil, nil } // 容器创建以后 func (p *plugin) PostCreateContainer(pod *api.PodSandbox, container *api.Container) error {   return nil } // 容器启动之前 func (p *plugin) StartContainer(pod *api.PodSandbox, container *api.Container) error { return nil } // 容器启动之后 func (p *plugin) PostStartContainer(pod *api.PodSandbox, container *api.Container) error { return nil } // 容器更新时 func (p *plugin) UpdateContainer(pod *api.PodSandbox, container *api.Container) ([]*api.ContainerUpdate, error) { return nil, nil } // 容器更新后 func (p *plugin) PostUpdateContainer(pod *api.PodSandbox, container *api.Container) error { return nil } // 容器停止时 func (p *plugin) StopContainer(pod *api.PodSandbox, container *api.Container) ([]*api.ContainerUpdate, error) { return nil, nil } // 容器移除时 func (p *plugin) RemoveContainer(pod *api.PodSandbox, container *api.Container) error { return nil }Containerd配置NRINRI功能在Containerd 1.7中被引入,如果需要开启,那么在containerd配置文件/etc/containerd/config.toml的结尾添加如下配置:  [plugins."io.containerd.nri.v1.nri"]     # Enable NRI support in containerd.     disable = false     # Allow connections from externally launched NRI plugins.     disable_connections = false     # plugin_config_path is the directory to search for plugin-specific configuration.     plugin_config_path = "/etc/nri/conf.d"     # plugin_path is the directory to search for plugins to launch on startup.     plugin_path = "/opt/nri/plugins"     # plugin_registration_timeout is the timeout for a plugin to register after connection.     plugin_registration_timeout = "5s"     # plugin_requst_timeout is the timeout for a plugin to handle an event/request.     plugin_request_timeout = "2s"     # socket_path is the path of the NRI socket to create for plugins to connect to.     socket_path = "/var/run/nri/nri.sock"配置完成以后,创建相关的目录:mkdir -pv /var/run/nri /etc/nri/conf.d /opt/nri/plugins最后重启containerd:systemctl restart containerdNRI Samples在NRI项目下有一些NRI插件的样例,这里介绍一下device-injector和logger,看看怎样实现一个NRI插件。device-injectordevice-injector是一个NRI示例插件,主要的实现的能力是:只要某个Pod的annotation中声明挂载某个或多个设备,那么devcie-injector会为pod的容器挂载对应的设备。device-injector提供了一个测试pod yaml。pod的annotation中定义了需要挂载哪些设备:apiVersion: v1 kind: Pod metadata:   name: bbdev0   labels:     app: bbdev0   annotations:     devices.nri.io/container.c0: |+       - path: /dev/nri-null         type: c         major: 1         minor: 3     devices.nri.io/container.c1: |+       - path: /dev/nri-zero         type: c         major: 1         minor: 5     mounts.nri.io/container.c2: |+       - source: /home         destination: /host-home         type: bind         options:         - bind         - ro ...... // 省略其他然后,再看插件的实现代码,device-injector订阅了容器的CreateContainer事件,也就是容器在创建时:func (p *plugin) CreateContainer(pod *api.PodSandbox, container *api.Container) (*api.ContainerAdjustment, []*api.ContainerUpdate, error) { var ( ctrName string devices []device mounts  []mount err     error ) ctrName = containerName(pod, container) if verbose { dump("CreateContainer", "pod", pod, "container", container) } adjust := &api.ContainerAdjustment{} // inject devices to container   // 从pod annotation中解析需要挂载的设备信息 devices, err = parseDevices(container.Name, pod.Annotations) if err != nil { return nil, nil, err } if len(devices) == 0 { log.Infof("%s: no devices annotated...", ctrName) } else { if verbose { dump(ctrName, "annotated devices", devices) } for _, d := range devices { adjust.AddDevice(d.toNRI()) if !verbose { log.Infof("%s: injected device %q...", ctrName, d.Path) } } } // inject mounts to container mounts, err = parseMounts(container.Name, pod.Annotations) if err != nil { return nil, nil, err } if len(mounts) == 0 { log.Infof("%s: no mounts annotated...", ctrName) } else { if verbose { dump(ctrName, "annotated mounts", mounts) } for _, m := range mounts { adjust.AddMount(m.toNRI()) if !verbose { log.Infof("%s: injected mount %q -> %q...", ctrName, m.Source, m.Destination) } } } if verbose { dump(ctrName, "ContainerAdjustment", adjust) } return adjust, nil, nil }整个函数的逻辑就是从pod annotation中解析需要挂载设备的信息,然后将该信息返回给containerd,让containerd更新容器配置。logger之所以要介绍一下logger这个NRI Sample Plugin, 主要有两点:逻辑比较简单:订阅pod和容器的生命周期事件,打印传递的参数信息。订阅所有事件:订阅了pod和容器所有的生命周期事件。logger的实现代码比较简单,以RunPodSandbox为例,只是打印PodSandox的信息:func (p *plugin) RunPodSandbox(pod *api.PodSandbox) error { dump("RunPodSandbox", "pod", pod) return nil }但是这个sample对于我们学习如何写NRI插件很有帮助。NRI与K8s device plugin机制对于前面提到的调度器强参与设备分配的情况下,如果有NRI参与,那么device plugin的Allocate函数和PreStartContainer函数可以什么都不用做。只需要实现一个NRI插件,该插件订阅容器创建的事件,直接从pod annotation中获取调度器为容器分配的设备信息,类似于前面的device-injector插件。但是这种绕开k8s device plugin机制是否为一种正确的方式,值得思考。NRI应用场景NRI虽然目前是experimental状态,实际应用场景还不太了解,不过最近发现koordinator社区的组件runtime-proxy所要做的事与NRI有很多相似之处,说明NRI的应用场景还是有的。
文章
Kubernetes  ·  API  ·  调度  ·  异构计算  ·  Docker  ·  容器  ·  Perl
2023-03-27
Python3,仅仅2段代码,就实现项目代码自动上传及部署,再也不需要Jenkins了。
1、引言小鱼:你在手动打包,要先rm - rf ,再 go away ??小屌丝:鱼哥,你在说啥嘞, 我可是正经人…小鱼:那你干啥,手动打包,没有Jenkins吗,没有自动打包工具吗?小屌丝:如果能Jenkins自动发布,我还干啥费劲的执行手动打包。小鱼:不能Jenkins?跨域了? 不能安装? 还是不会自动打包?小屌丝:一来是跨域,其次,无法在目标服务器部署Jenkins,所以,只能手动打包上传…小鱼:这样啊, 那你你应该庆幸,你遇到我了。。。。小屌丝:鱼哥,那你的意思,你有好的方法了!小鱼:必须的,今天分享一个,不用Jenkins,只用区区2段代码,就完成项目自动上传和部署。小屌丝:鱼哥, v5 ~ ~2、代码实战在实际的工作中,遇到小屌丝的那种情况,也是很常见的。发版的时候,需要部署到客户的服务器,但是,客户的服务器,对安装软件有着非常严格的要求,这个时候,大部分的同学,都是手动打包,上传,部署。为了解放同学的双手,小鱼,今天就来搞一个自动上传部署,而且还带监控的功能。2.1 模块安装我们需要依赖python的第三方库就是:watchdog老规矩,pip 安装pip install watchdog其它安装方式,直接看这两篇:《Python3,选择Python自动安装第三方库,从此跟pip说拜拜!!》《Python3:我低调的只用一行代码,就导入Python所有库!》2.2 实现思路在撸码前, 常规操作是,思考+规划 = 实现思路:创建监听器;创建事件处理对象;执行监听器,监听事件处理对象;在服务器执行脚本;有了明确的思路,我们就来事撸码。2.3 代码示例2.3.1 创建监听器代码示例# -*- coding:utf-8 -*- # @Time : 2022-09-09 # @Author : Carl_DJ import os from watchdog.observers import Observer #创建一个监听器,用于监听文件夹目录 observer = Observer() #创建2个事件处理对象 '分别用来监听 [/tmp]目录 、[/opt/project/projectName]目录'2.3.2 创建事件处理对象代码示例一:对/tmp文件进行监听# -*- coding:utf-8 -*- # @Time : 2022-09-09 # @Author : Carl_DJ from watchdog.events import * import ntpath import shutil import zipfile ''' 实现功能: 1、负责 监听 /tmp 目录, 2、事件方法:新建&修改 3、执行压缩文件移动 ''' def get_filename(filepath): """ 根据文件夹目录,获取文件名称(带后缀名) :param filepath: :return: """ return ntpath.basename(filepath) class FileMoveHandler(FileSystemEventHandler): def __init__(self): FileSystemEventHandler.__init__(self) # 文件新建 def on_created(self, event): # 新建文件夹 if event.is_directory: # print("directory created:{0}".format(event.src_path)) pass # 新建文件 else: # print("file created:{0}".format(event.src_path)) filename = get_filename(event.src_path) # 如果属于前端的4个项目压缩包,开始文件夹的操作 if filename in watch_tags: self.start(filename) def on_modified(self, event): if event.is_directory: # print("directory modified:{0}".format(event.src_path)) pass else: # print("file modified:{0}".format(event.src_path)) filename = get_filename(event.src_path) if filename in watch_tags: self.start(filename) def start_tmp(self, filename): """ 文件处理逻辑 :param filename: :return: """ try: # 文件名不带后缀 filename_without_suffix = filename.split(".")[0] # 源文件路径(压缩包文件) source_file_path = watch_folder + filename # 目标文件路径(压缩包文件) target_file_path = target_folder + filename # 目标项目文件夹(目标项目) target_project_path = target_folder + filename_without_suffix # 1、复制文件到目标文件夹 print(f"拷贝源目录{source_file_path},目标文件夹:{target_folder}") # 删除目标文件夹下的压缩文件 if os.path.exists(target_file_path): os.remove(target_file_path) # 移动文件到目标文件夹中 shutil.move(source_file_path, target_folder) # 2、清空目标文件夹中内的所有文件夹(如果存在) # 如果不存在,新建一个文件夹 if os.path.exists(target_project_path): shutil.rmtree(target_project_path, ignore_errors=True) print(f"项目{filename_without_suffix}移动成功!") except Exception as e: print("部署失败,错误原因:", str(e.args))代码示例二:对//opt/project/projectName文件进行监听# -*- coding:utf-8 -*- # @Time : 2022-09-09 # @Author : Carl_DJ ... def start_opt(self, filename): # 文件名不带后缀 filename_without_suffix = filename.split(".")[0] # 目标文件路径(压缩包文件) target_file_path = target_folder + filename # 目标项目文件夹(目标项目) target_project_path = target_folder + filename_without_suffix r = zipfile.is_zipfile(target_file_path) if r: fz = zipfile.ZipFile(target_file_path, 'r') for file in fz.namelist(): fz.extract(file, target_folder) else: print('压缩包出现问题,无法进行解压操作!')2.3.3 启动事件监听代码示例# -*- coding:utf-8 -*- # @Time : 2022-09-09 # @Author : Carl_DJ import time if __name__ == "__main__": # 待监听的文件夹目录 watch_folder = "/tmp/" # 项目目标文件夹目录 target_folder = "/opt/project/projectName/" # 监听文件夹名称,即:项目压缩包名称 watch_tags = ['project1.zip', 'project2.zip'] # 创建一个监听器,用来监听文件夹目录 observer = Observer() # 创建两个事件处理对象 move_handler = FileMoveHandler() unzip_handler = FileUnzipHandler() # 启动监控任务 # 参数分别是:观察者、监听目录、是否监听子目录 observer.schedule(move_handler, watch_folder, True) observer.schedule(unzip_handler, target_folder, True) observer.start() try: while True: time.sleep(1) except KeyboardInterrupt: observer.stop() observer.join()2.4 启动运行最后,我们就在目标服务器,启动运行脚本。文件命名:脚本文件:watchdog_folder.py日志文件:watchdog_floder.lognohup python3 -u watchdog_folder.py > watchdog_floder.log 2>&1 &3、总结看到这里,今天的分享差不多就结束了。回顾一下,今天的的内容,大体思路:先创建监听器→再创建事件处理对象→启动监听器,对事件进行监听处理→最后在服务器上启动命令执行。
文章
监控  ·  jenkins  ·  持续交付  ·  Go  ·  Python
2022-11-03
【大数据开发运维解决方案】Sqoop增量同步mysql/oracle数据到hive(merge-key/append)测试文档
上一篇文章介绍了sqoop全量同步数据到hive,本片文章将通过实验详细介绍如何增量同步数据到hive,以及sqoop job与crontab定时结合无密码登录的增量同步实现方法。一、知识储备在生产环境中,系统可能会定期从与业务相关的关系型数据库向Hadoop导入数据,导入数仓后进行后续离线分析。故我们此时不可能再将所有数据重新导一遍,此时我们就需要增量数据导入这一模式了。增量数据导入分两种,一是基于递增列的增量数据导入(Append方式)。二是基于时间列的增量数据导入(LastModified方式),增量导入使用到的核心参数主要是:–check-column 用来指定一些列,这些列在增量导入时用来检查这些数据是否作为增量数据进行导入,和关系型数据库中的自增字段及时间戳类似. 注意:这些被指定的列的类型不能使任意字符类型,如char、varchar等类型都是不可以的,同时–check-column可以去指定多个列–incremental 用来指定增量导入的模式,两种模式分别为Append和Lastmodified–last-value 指定上一次导入中检查列指定字段最大值接下来通过具体实验来详细说明1、Append模式增量导入重要参数:–incremental append基于递增列的增量导入(将递增列值大于阈值的所有数据增量导入Hadoop)–check-column递增列(int)–last-value 阈值(int)举个简单例子,在oracle库scott用户下有一张员工表(inr_app),表中有:自增主键员工编号(empno),员工名(ename),员工职位(job),员工薪资(sal)这几个属性,如下:--在oracle库scott下创建一个这样的表 create table inr_app as select rownum as empno, ename, job, sal from emp a where job is not null and rownum<=5; --查询: select * from inr_app; EMPNO ENAME JOB SAL 1 er CLERK 800.00 2 ALLEN SALESMAN 1600.00 3 WARD SALESMAN 1250.00 4 JONES MANAGER 2975.00 5 MARTIN SALESMAN 1250.00我们需要将新进员工也导入hadoop以供公司人力部门做分析,此时我们需要将这个表数据导入到hive,也就是增量导入前的一次全量导入:--在hive创建表: create table INR_APP ( empno int, ename string, job string, sal float ); hive> show tables; OK inr_app inr_emp ora_hive Time taken: 0.166 seconds, Fetched: 3 row(s) --接下来执行全量导入: [root@hadoop ~]# sqoop import --connect jdbc:oracle:thin:@192.168.1.6:1521:orcl --username scott --password tiger --table INR_APP -m 1 --hive-import --hive-database oracle --查询hive表 hive> select * from inr_app; OK 1 er CLERK 800.0 2 ALLEN SALESMAN 1600.0 3 WARD SALESMAN 1250.0 4 JONES MANAGER 2975.0 5 MARTIN SALESMAN 1250.0 Time taken: 0.179 seconds, Fetched: 5 row(s)过了一段时间后,公司又新来一批员工,我们需要将新员工也导入到hadoop供有关部门分析,此时我们只需要指定–incremental 参数为append,–last-value参数为5可。表示只从id大于5后开始导入:--先给oracle库scott.inr_app插入几条数据: insert into inr_app values(6,'zhao','DBA',100); insert into inr_app values(7,'yan','BI',100); insert into inr_app values(8,'dong','JAVA',100); commit; --执行增量导入: [root@hadoop ~]# sqoop import --connect jdbc:oracle:thin:@192.168.1.6:1521:orcl --username scott --password tiger --table INR_APP -m 1 --hive-import --hive-database oracle --incremental app end --check-column EMPNO --last-value 5 Warning: /hadoop/sqoop/../accumulo does not exist! Accumulo imports will fail. Please set $ACCUMULO_HOME to the root of your Accumulo installation. Warning: /hadoop/sqoop/../zookeeper does not exist! Accumulo imports will fail. Please set $ZOOKEEPER_HOME to the root of your Zookeeper installation. 19/03/12 19:45:55 INFO sqoop.Sqoop: Running Sqoop version: 1.4.7 19/03/12 19:45:56 WARN tool.BaseSqoopTool: Setting your password on the command-line is insecure. Consider using -P instead. 19/03/12 19:45:56 INFO tool.BaseSqoopTool: Using Hive-specific delimiters for output. You can override 19/03/12 19:45:56 INFO tool.BaseSqoopTool: delimiters with --fields-terminated-by, etc. 19/03/12 19:45:56 INFO oracle.OraOopManagerFactory: Data Connector for Oracle and Hadoop is disabled. 19/03/12 19:45:56 INFO manager.SqlManager: Using default fetchSize of 1000 19/03/12 19:45:56 INFO tool.CodeGenTool: Beginning code generation SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:file:/hadoop/share/hadoop/common/lib/slf4j-log4j12-1.7.10.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:file:/hadoop/hbase/lib/slf4j-log4j12-1.7.10.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:file:/hadoop/hive/lib/log4j-slf4j-impl-2.6.2.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation. SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory] 19/03/12 19:45:57 INFO manager.OracleManager: Time zone has been set to GMT 19/03/12 19:45:57 INFO manager.SqlManager: Executing SQL statement: SELECT t.* FROM INR_APP t WHERE 1=0 19/03/12 19:45:57 INFO orm.CompilationManager: HADOOP_MAPRED_HOME is /hadoop Note: /tmp/sqoop-root/compile/9b898359374ea580a390b32da1a37949/INR_APP.java uses or overrides a deprecated API. Note: Recompile with -Xlint:deprecation for details. 19/03/12 19:45:59 INFO orm.CompilationManager: Writing jar file: /tmp/sqoop-root/compile/9b898359374ea580a390b32da1a37949/INR_APP.jar 19/03/12 19:45:59 INFO manager.OracleManager: Time zone has been set to GMT 19/03/12 19:45:59 INFO tool.ImportTool: Maximal id query for free form incremental import: SELECT MAX(EMPNO) FROM INR_APP 19/03/12 19:45:59 INFO tool.ImportTool: Incremental import based on column EMPNO 19/03/12 19:45:59 INFO tool.ImportTool: Lower bound value: 5 19/03/12 19:45:59 INFO tool.ImportTool: Upper bound value: 8 19/03/12 19:45:59 INFO manager.OracleManager: Time zone has been set to GMT 19/03/12 19:45:59 INFO mapreduce.ImportJobBase: Beginning import of INR_APP 19/03/12 19:46:00 INFO Configuration.deprecation: mapred.jar is deprecated. Instead, use mapreduce.job.jar 19/03/12 19:46:00 INFO manager.OracleManager: Time zone has been set to GMT 19/03/12 19:46:01 INFO Configuration.deprecation: mapred.map.tasks is deprecated. Instead, use mapreduce.job.maps 19/03/12 19:46:01 INFO client.RMProxy: Connecting to ResourceManager at /192.168.1.66:8032 19/03/12 19:46:04 INFO db.DBInputFormat: Using read commited transaction isolation 19/03/12 19:46:04 INFO mapreduce.JobSubmitter: number of splits:1 19/03/12 19:46:05 INFO mapreduce.JobSubmitter: Submitting tokens for job: job_1552371714699_0010 19/03/12 19:46:05 INFO impl.YarnClientImpl: Submitted application application_1552371714699_0010 19/03/12 19:46:05 INFO mapreduce.Job: The url to track the job: http://hadoop:8088/proxy/application_1552371714699_0010/ 19/03/12 19:46:05 INFO mapreduce.Job: Running job: job_1552371714699_0010 19/03/12 19:46:13 INFO mapreduce.Job: Job job_1552371714699_0010 running in uber mode : false 19/03/12 19:46:13 INFO mapreduce.Job: map 0% reduce 0% 19/03/12 19:46:21 INFO mapreduce.Job: map 100% reduce 0% 19/03/12 19:46:21 INFO mapreduce.Job: Job job_1552371714699_0010 completed successfully 19/03/12 19:46:21 INFO mapreduce.Job: Counters: 30 File System Counters FILE: Number of bytes read=0 FILE: Number of bytes written=143702 FILE: Number of read operations=0 FILE: Number of large read operations=0 FILE: Number of write operations=0 HDFS: Number of bytes read=87 HDFS: Number of bytes written=44 HDFS: Number of read operations=4 HDFS: Number of large read operations=0 HDFS: Number of write operations=2 Job Counters Launched map tasks=1 Other local map tasks=1 Total time spent by all maps in occupied slots (ms)=4336 Total time spent by all reduces in occupied slots (ms)=0 Total time spent by all map tasks (ms)=4336 Total vcore-milliseconds taken by all map tasks=4336 Total megabyte-milliseconds taken by all map tasks=4440064 Map-Reduce Framework Map input records=3 Map output records=3 Input split bytes=87 Spilled Records=0 Failed Shuffles=0 Merged Map outputs=0 GC time elapsed (ms)=92 CPU time spent (ms)=2760 Physical memory (bytes) snapshot=211570688 Virtual memory (bytes) snapshot=2133770240 Total committed heap usage (bytes)=106954752 File Input Format Counters Bytes Read=0 File Output Format Counters Bytes Written=44 19/03/12 19:46:21 INFO mapreduce.ImportJobBase: Transferred 44 bytes in 20.3436 seconds (2.1628 bytes/sec) 19/03/12 19:46:21 INFO mapreduce.ImportJobBase: Retrieved 3 records. 19/03/12 19:46:21 INFO mapreduce.ImportJobBase: Publishing Hive/Hcat import job data to Listeners for table INR_APP 19/03/12 19:46:21 INFO util.AppendUtils: Creating missing output directory - INR_APP 19/03/12 19:46:21 INFO manager.OracleManager: Time zone has been set to GMT 19/03/12 19:46:21 INFO manager.SqlManager: Executing SQL statement: SELECT t.* FROM INR_APP t WHERE 1=0 19/03/12 19:46:21 WARN hive.TableDefWriter: Column EMPNO had to be cast to a less precise type in Hive 19/03/12 19:46:21 WARN hive.TableDefWriter: Column SAL had to be cast to a less precise type in Hive 19/03/12 19:46:21 INFO hive.HiveImport: Loading uploaded data into Hive 19/03/12 19:46:21 INFO conf.HiveConf: Found configuration file file:/hadoop/hive/conf/hive-site.xml Logging initialized using configuration in jar:file:/hadoop/hive/lib/hive-common-2.3.2.jar!/hive-log4j2.properties Async: true 19/03/12 19:46:24 INFO SessionState: Logging initialized using configuration in jar:file:/hadoop/hive/lib/hive-common-2.3.2.jar!/hive-log4j2.properties Async: true 19/03/12 19:46:24 INFO session.SessionState: Created HDFS directory: /tmp/hive/root/2968942b-30b6-49f5-b86c-d71a77963381 19/03/12 19:46:24 INFO session.SessionState: Created local directory: /hadoop/hive/tmp/root/2968942b-30b6-49f5-b86c-d71a77963381 19/03/12 19:46:24 INFO session.SessionState: Created HDFS directory: /tmp/hive/root/2968942b-30b6-49f5-b86c-d71a77963381/_tmp_space.db 19/03/12 19:46:24 INFO conf.HiveConf: Using the default value passed in for log id: 2968942b-30b6-49f5-b86c-d71a77963381 19/03/12 19:46:24 INFO session.SessionState: Updating thread name to 2968942b-30b6-49f5-b86c-d71a77963381 main 19/03/12 19:46:24 INFO conf.HiveConf: Using the default value passed in for log id: 2968942b-30b6-49f5-b86c-d71a77963381 19/03/12 19:46:24 INFO ql.Driver: Compiling command(queryId=root_20190312114624_6679c12a-4224-4bcd-a8be-f7d4ae56a139): CREATE TABLE IF NOT EXISTS `oracle`.`INR_APP` ( `EMPNO` DOUBLE, `ENAME ` STRING, `JOB` STRING, `SAL` DOUBLE) COMMENT 'Imported by sqoop on 2019/03/12 11:46:21' ROW FORMAT DELIMITED FIELDS TERMINATED BY '\001' LINES TERMINATED BY '\012' STORED AS TEXTFILE19/03/12 19:46:27 INFO hive.metastore: Trying to connect to metastore with URI thrift://192.168.1.66:9083 19/03/12 19:46:27 INFO hive.metastore: Opened a connection to metastore, current connections: 1 19/03/12 19:46:27 INFO hive.metastore: Connected to metastore. 19/03/12 19:46:27 INFO parse.CalcitePlanner: Starting Semantic Analysis 19/03/12 19:46:27 INFO parse.CalcitePlanner: Creating table oracle.INR_APP position=27 19/03/12 19:46:27 INFO ql.Driver: Semantic Analysis Completed 19/03/12 19:46:27 INFO ql.Driver: Returning Hive schema: Schema(fieldSchemas:null, properties:null) 19/03/12 19:46:27 INFO ql.Driver: Completed compiling command(queryId=root_20190312114624_6679c12a-4224-4bcd-a8be-f7d4ae56a139); Time taken: 2.876 seconds 19/03/12 19:46:27 INFO ql.Driver: Concurrency mode is disabled, not creating a lock manager 19/03/12 19:46:27 INFO ql.Driver: Executing command(queryId=root_20190312114624_6679c12a-4224-4bcd-a8be-f7d4ae56a139): CREATE TABLE IF NOT EXISTS `oracle`.`INR_APP` ( `EMPNO` DOUBLE, `ENAME ` STRING, `JOB` STRING, `SAL` DOUBLE) COMMENT 'Imported by sqoop on 2019/03/12 11:46:21' ROW FORMAT DELIMITED FIELDS TERMINATED BY '\001' LINES TERMINATED BY '\012' STORED AS TEXTFILE19/03/12 19:46:27 INFO sqlstd.SQLStdHiveAccessController: Created SQLStdHiveAccessController for session context : HiveAuthzSessionContext [sessionString=2968942b-30b6-49f5-b86c-d71a7796338 1, clientType=HIVECLI]19/03/12 19:46:27 WARN session.SessionState: METASTORE_FILTER_HOOK will be ignored, since hive.security.authorization.manager is set to instance of HiveAuthorizerFactory. 19/03/12 19:46:27 INFO hive.metastore: Mestastore configuration hive.metastore.filter.hook changed from org.apache.hadoop.hive.metastore.DefaultMetaStoreFilterHookImpl to org.apache.hadoop. hive.ql.security.authorization.plugin.AuthorizationMetaStoreFilterHook19/03/12 19:46:27 INFO hive.metastore: Closed a connection to metastore, current connections: 0 19/03/12 19:46:27 INFO hive.metastore: Trying to connect to metastore with URI thrift://192.168.1.66:9083 19/03/12 19:46:27 INFO hive.metastore: Opened a connection to metastore, current connections: 1 19/03/12 19:46:27 INFO hive.metastore: Connected to metastore. 19/03/12 19:46:27 INFO ql.Driver: Completed executing command(queryId=root_20190312114624_6679c12a-4224-4bcd-a8be-f7d4ae56a139); Time taken: 0.096 seconds OK 19/03/12 19:46:27 INFO ql.Driver: OK Time taken: 2.982 seconds 19/03/12 19:46:27 INFO CliDriver: Time taken: 2.982 seconds 19/03/12 19:46:27 INFO conf.HiveConf: Using the default value passed in for log id: 2968942b-30b6-49f5-b86c-d71a77963381 19/03/12 19:46:27 INFO session.SessionState: Resetting thread name to main 19/03/12 19:46:27 INFO conf.HiveConf: Using the default value passed in for log id: 2968942b-30b6-49f5-b86c-d71a77963381 19/03/12 19:46:27 INFO session.SessionState: Updating thread name to 2968942b-30b6-49f5-b86c-d71a77963381 main 19/03/12 19:46:27 INFO ql.Driver: Compiling command(queryId=root_20190312114627_748c136c-1446-43df-a819-728becae7df2): LOAD DATA INPATH 'hdfs://192.168.1.66:9000/user/root/INR_APP' INTO TABLE `oracle`.`INR_APP` 19/03/12 19:46:28 INFO ql.Driver: Semantic Analysis Completed 19/03/12 19:46:28 INFO ql.Driver: Returning Hive schema: Schema(fieldSchemas:null, properties:null) 19/03/12 19:46:28 INFO ql.Driver: Completed compiling command(queryId=root_20190312114627_748c136c-1446-43df-a819-728becae7df2); Time taken: 0.421 seconds 19/03/12 19:46:28 INFO ql.Driver: Concurrency mode is disabled, not creating a lock manager 19/03/12 19:46:28 INFO ql.Driver: Executing command(queryId=root_20190312114627_748c136c-1446-43df-a819-728becae7df2): LOAD DATA INPATH 'hdfs://192.168.1.66:9000/user/root/INR_APP' INTO TABLE `oracle`.`INR_APP` 19/03/12 19:46:28 INFO ql.Driver: Starting task [Stage-0:MOVE] in serial mode 19/03/12 19:46:28 INFO hive.metastore: Closed a connection to metastore, current connections: 0 Loading data to table oracle.inr_app 19/03/12 19:46:28 INFO exec.Task: Loading data to table oracle.inr_app from hdfs://192.168.1.66:9000/user/root/INR_APP 19/03/12 19:46:28 INFO hive.metastore: Trying to connect to metastore with URI thrift://192.168.1.66:9083 19/03/12 19:46:28 INFO hive.metastore: Opened a connection to metastore, current connections: 1 19/03/12 19:46:28 INFO hive.metastore: Connected to metastore. 19/03/12 19:46:28 ERROR hdfs.KeyProviderCache: Could not find uri with key [dfs.encryption.key.provider.uri] to create a keyProvider !! 19/03/12 19:46:28 INFO ql.Driver: Starting task [Stage-1:STATS] in serial mode 19/03/12 19:46:28 INFO exec.StatsTask: Executing stats task 19/03/12 19:46:28 INFO hive.metastore: Closed a connection to metastore, current connections: 0 19/03/12 19:46:28 INFO hive.metastore: Trying to connect to metastore with URI thrift://192.168.1.66:9083 19/03/12 19:46:28 INFO hive.metastore: Opened a connection to metastore, current connections: 1 19/03/12 19:46:28 INFO hive.metastore: Connected to metastore. 19/03/12 19:46:29 INFO hive.metastore: Closed a connection to metastore, current connections: 0 19/03/12 19:46:29 INFO hive.metastore: Trying to connect to metastore with URI thrift://192.168.1.66:9083 19/03/12 19:46:29 INFO hive.metastore: Opened a connection to metastore, current connections: 1 19/03/12 19:46:29 INFO hive.metastore: Connected to metastore. 19/03/12 19:46:29 INFO exec.StatsTask: Table oracle.inr_app stats: [numFiles=2, numRows=0, totalSize=146, rawDataSize=0] 19/03/12 19:46:29 INFO ql.Driver: Completed executing command(queryId=root_20190312114627_748c136c-1446-43df-a819-728becae7df2); Time taken: 0.992 seconds OK 19/03/12 19:46:29 INFO ql.Driver: OK Time taken: 1.415 seconds 19/03/12 19:46:29 INFO CliDriver: Time taken: 1.415 seconds 19/03/12 19:46:29 INFO conf.HiveConf: Using the default value passed in for log id: 2968942b-30b6-49f5-b86c-d71a77963381 19/03/12 19:46:29 INFO session.SessionState: Resetting thread name to main 19/03/12 19:46:29 INFO conf.HiveConf: Using the default value passed in for log id: 2968942b-30b6-49f5-b86c-d71a77963381 19/03/12 19:46:29 INFO session.SessionState: Deleted directory: /tmp/hive/root/2968942b-30b6-49f5-b86c-d71a77963381 on fs with scheme hdfs 19/03/12 19:46:29 INFO session.SessionState: Deleted directory: /hadoop/hive/tmp/root/2968942b-30b6-49f5-b86c-d71a77963381 on fs with scheme file 19/03/12 19:46:29 INFO hive.metastore: Closed a connection to metastore, current connections: 0 19/03/12 19:46:29 INFO hive.HiveImport: Hive import complete. 19/03/12 19:46:29 INFO hive.HiveImport: Export directory is empty, removing it. 19/03/12 19:46:29 INFO tool.ImportTool: Incremental import complete! To run another incremental import of all data following this import, supply the following arguments: 19/03/12 19:46:29 INFO tool.ImportTool: --incremental append 19/03/12 19:46:29 INFO tool.ImportTool: --check-column EMPNO 19/03/12 19:46:29 INFO tool.ImportTool: --last-value 8 19/03/12 19:46:29 INFO tool.ImportTool: (Consider saving this with 'sqoop job --create')查询hive表hive> select * from inr_app; OK 1 er CLERK 800.0 2 ALLEN SALESMAN 1600.0 3 WARD SALESMAN 1250.0 4 JONES MANAGER 2975.0 5 MARTIN SALESMAN 1250.0 6 zhao DBA 100.0 7 yan BI 100.0 8 dong JAVA 100.0 Time taken: 0.165 seconds, Fetched: 8 row(s)已经增量过来了,我们也可以使用hdfs dfs -cat查看生成的数据文件,生成的数据文件位置在之前配置hadoop环境时已经配置,读者也可以通过自己访问自己环境:IP:50070/explorer.html#/查询[root@hadoop ~]# hdfs dfs -cat /user/hive/warehouse/oracle.db/inr_app/part-m-00000_copy_1 6zhaoDBA100 7yanBI100 8dongJAVA100至于之前全量的数据,也可以看到:[root@hadoop ~]# hdfs dfs -cat /user/hive/warehouse/oracle.db/inr_app/part-m-00000 1erCLERK800 2ALLENSALESMAN1600 3WARDSALESMAN1250 4JONESMANAGER2975 5MARTINSALESMAN12502、、lastModify增量导入lastModify增量导入又分为两种模式:a、--incremental append 附加模式b、--incremental --merge-key合并模式接下来继续看实验:实验一:附加模式此方式要求原有表中有time字段,它能指定一个时间戳,让Sqoop把该时间戳之后的数据导入至Hadoop(这里为HDFS)。因为后续员工薪资可能状态会变化,变化后time字段时间戳也会变化,此时Sqoop依然会将相同状态更改后的员工信息导入HDFS,因此为导致数据重复。先在oracle库基于scott.inr_app新建一个带时间列etltime的表inr_las,初始化已有数据时间为sysdatecreate table inr_las as select a.empno, a.ename, a.job, a.sal, sysdate as etltime from inr_app a; select * from inr_las; EMPNO ENAME JOB SAL ETLTIME 1 er CLERK 800.00 2019/3/20 10:42:27 2 ALLEN SALESMAN 1600.00 2019/3/20 10:42:27 3 WARD SALESMAN 1250.00 2019/3/20 10:42:27 4 JONES MANAGER 2975.00 2019/3/20 10:42:27 5 MARTIN SALESMAN 1250.00 2019/3/20 10:42:27 6 zhao DBA 100.00 2019/3/20 10:42:27 7 yan BI 100.00 2019/3/20 10:42:27 8 dong JAVA 100.00 2019/3/20 10:42:27在hive创建表,这里统一指定列分隔符为'\t',后面导入也是以此为分隔符:create table INR_LAS ( empno int, ename string, job string, sal float, etltime string ) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t';初始化全量导入:[root@hadoop ~]# sqoop import --connect jdbc:oracle:thin:@192.168.1.6:1521:orcl --username scott --password tiger --table INR_LAS -m 1 --hive-import --hive-database oracle --fields-terminated-by '\t' --lines-terminated-by '\n'查询hive表:hive> select * from inr_las; OK 1 er CLERK 800.0 2019-03-20 10:42:27.0 2 ALLEN SALESMAN 1600.0 2019-03-20 10:42:27.0 3 WARD SALESMAN 1250.0 2019-03-20 10:42:27.0 4 JONES MANAGER 2975.0 2019-03-20 10:42:27.0 5 MARTIN SALESMAN 1250.0 2019-03-20 10:42:27.0 6 zhao DBA 100.0 2019-03-20 10:42:27.0 7 yan BI 100.0 2019-03-20 10:42:27.0 8 dong JAVA 100.0 2019-03-20 10:42:27.0 Time taken: 0.181 seconds, Fetched: 8 row(s)这次增量导入我们先使用--incremental lastmodified --last-value --append 看下效果,首先在源端对inr_las表数据做下变更:update inr_las set sal=1000,etltime=sysdate where empno=6; commit; select * from inr_las; EMPNO ENAME JOB SAL ETLTIME 1 er CLERK 800.00 2019/3/20 10:42:27 2 ALLEN SALESMAN 1600.00 2019/3/20 10:42:27 3 WARD SALESMAN 1250.00 2019/3/20 10:42:27 4 JONES MANAGER 2975.00 2019/3/20 10:42:27 5 MARTIN SALESMAN 1250.00 2019/3/20 10:42:27 6 zhao DBA 1000.00 2019/3/20 10:52:34 7 yan BI 100.00 2019/3/20 10:42:27 8 dong JAVA 100.00 2019/3/20 10:42:27接下来增量导入:[root@hadoop ~]# sqoop import --connect jdbc:oracle:thin:@192.168.1.6:1521:orcl --username scott --password tiger --table INR_LAS --fields-terminated-by '\t' --lines-terminated-by '\n' --hive-import --hive-database oracle --hive-table INR_LAS --incremental append --check-column ETLTIME --last-value '2019-03-20 10:42:27' -m 1 --null-string '\\N' --null-non-string '\\N' Warning: /hadoop/sqoop/../accumulo does not exist! Accumulo imports will fail.Please set $ACCUMULO_HOME to the root of your Accumulo installation. '2019-03Warning: /hadoop/sqoop/../zookeeper does not exist! Accumulo imports will fail. Please set $ZOOKEEPER_HOME to the root of your Zookeeper installation. 19/03/13 14:46:26 INFO sqoop.Sqoop: Running Sqoop version: 1.4.7 19/03/13 14:46:26 WARN tool.BaseSqoopTool: Setting your password on the command-line is insecure. Consider using -P instead. 19/03/13 14:46:27 INFO oracle.OraOopManagerFactory: Data Connector for Oracle and Hadoop is disabled. 19/03/13 14:46:27 INFO manager.SqlManager: Using default fetchSize of 1000 19/03/13 14:46:27 INFO tool.CodeGenTool: Beginning code generation SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:file:/hadoop/share/hadoop/common/lib/slf4j-log4j12-1.7.10.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:file:/hadoop/hbase/lib/slf4j-log4j12-1.7.10.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:file:/hadoop/hive/lib/log4j-slf4j-impl-2.6.2.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation. SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory] 19/03/13 14:46:27 INFO manager.OracleManager: Time zone has been set to GMT 19/03/13 14:46:27 INFO manager.SqlManager: Executing SQL statement: SELECT t.* FROM INR_LAS t WHERE 1=0 19/03/13 14:46:28 INFO orm.CompilationManager: HADOOP_MAPRED_HOME is /hadoop Note: /tmp/sqoop-root/compile/37cf0f81337f33bc731bf3d6fd0a3f73/INR_LAS.java uses or overrides a deprecated API. Note: Recompile with -Xlint:deprecation for details. 19/03/13 14:46:30 INFO orm.CompilationManager: Writing jar file: /tmp/sqoop-root/compile/37cf0f81337f33bc731bf3d6fd0a3f73/INR_LAS.jar 19/03/13 14:46:30 INFO manager.OracleManager: Time zone has been set to GMT 19/03/13 14:46:30 INFO tool.ImportTool: Maximal id query for free form incremental import: SELECT MAX(ETLTIME) FROM INR_LAS 19/03/13 14:46:30 INFO tool.ImportTool: Incremental import based on column ETLTIME 19/03/13 14:46:30 INFO tool.ImportTool: Lower bound value: TO_TIMESTAMP('2019-03-20 10:42:27', 'YYYY-MM-DD HH24:MI:SS.FF') 19/03/13 14:46:30 INFO tool.ImportTool: Upper bound value: TO_TIMESTAMP('2019-03-20 10:52:34.0', 'YYYY-MM-DD HH24:MI:SS.FF') 19/03/13 14:46:31 INFO manager.OracleManager: Time zone has been set to GMT 19/03/13 14:46:31 INFO mapreduce.ImportJobBase: Beginning import of INR_LAS 19/03/13 14:46:31 INFO Configuration.deprecation: mapred.jar is deprecated. Instead, use mapreduce.job.jar 19/03/13 14:46:31 INFO manager.OracleManager: Time zone has been set to GMT 19/03/13 14:46:32 INFO Configuration.deprecation: mapred.map.tasks is deprecated. Instead, use mapreduce.job.maps 19/03/13 14:46:32 INFO client.RMProxy: Connecting to ResourceManager at /192.168.1.66:8032 19/03/13 14:46:35 INFO db.DBInputFormat: Using read commited transaction isolation 19/03/13 14:46:35 INFO mapreduce.JobSubmitter: number of splits:1 19/03/13 14:46:35 INFO mapreduce.JobSubmitter: Submitting tokens for job: job_1552371714699_0031 19/03/13 14:46:36 INFO impl.YarnClientImpl: Submitted application application_1552371714699_0031 19/03/13 14:46:36 INFO mapreduce.Job: The url to track the job: http://hadoop:8088/proxy/application_1552371714699_0031/ 19/03/13 14:46:36 INFO mapreduce.Job: Running job: job_1552371714699_0031 19/03/13 14:46:45 INFO mapreduce.Job: Job job_1552371714699_0031 running in uber mode : false 19/03/13 14:46:45 INFO mapreduce.Job: map 0% reduce 0% 19/03/13 14:46:52 INFO mapreduce.Job: map 100% reduce 0% 19/03/13 14:46:53 INFO mapreduce.Job: Job job_1552371714699_0031 completed successfully 19/03/13 14:46:54 INFO mapreduce.Job: Counters: 30 File System Counters FILE: Number of bytes read=0 FILE: Number of bytes written=143840 FILE: Number of read operations=0 FILE: Number of large read operations=0 FILE: Number of write operations=0 HDFS: Number of bytes read=87 HDFS: Number of bytes written=38 HDFS: Number of read operations=4 HDFS: Number of large read operations=0 HDFS: Number of write operations=2 Job Counters Launched map tasks=1 Other local map tasks=1 Total time spent by all maps in occupied slots (ms)=4950 Total time spent by all reduces in occupied slots (ms)=0 Total time spent by all map tasks (ms)=4950 Total vcore-milliseconds taken by all map tasks=4950 Total megabyte-milliseconds taken by all map tasks=5068800 Map-Reduce Framework Map input records=1 Map output records=1 Input split bytes=87 Spilled Records=0 Failed Shuffles=0 Merged Map outputs=0 GC time elapsed (ms)=560 CPU time spent (ms)=2890 Physical memory (bytes) snapshot=189190144 Virtual memory (bytes) snapshot=2141667328 Total committed heap usage (bytes)=116391936 File Input Format Counters Bytes Read=0 File Output Format Counters Bytes Written=38 19/03/13 14:46:54 INFO mapreduce.ImportJobBase: Transferred 38 bytes in 21.7168 seconds (1.7498 bytes/sec) 19/03/13 14:46:54 INFO mapreduce.ImportJobBase: Retrieved 1 records. 19/03/13 14:46:54 INFO mapreduce.ImportJobBase: Publishing Hive/Hcat import job data to Listeners for table INR_LAS 19/03/13 14:46:54 INFO util.AppendUtils: Creating missing output directory - INR_LAS 19/03/13 14:46:54 INFO manager.OracleManager: Time zone has been set to GMT 19/03/13 14:46:54 INFO manager.SqlManager: Executing SQL statement: SELECT t.* FROM INR_LAS t WHERE 1=0 19/03/13 14:46:54 WARN hive.TableDefWriter: Column EMPNO had to be cast to a less precise type in Hive 19/03/13 14:46:54 WARN hive.TableDefWriter: Column SAL had to be cast to a less precise type in Hive 19/03/13 14:46:54 WARN hive.TableDefWriter: Column ETLTIME had to be cast to a less precise type in Hive 19/03/13 14:46:54 INFO hive.HiveImport: Loading uploaded data into Hive 19/03/13 14:46:54 INFO conf.HiveConf: Found configuration file file:/hadoop/hive/conf/hive-site.xml Logging initialized using configuration in jar:file:/hadoop/hive/lib/hive-common-2.3.2.jar!/hive-log4j2.properties Async: true 19/03/13 14:46:57 INFO SessionState: Logging initialized using configuration in jar:file:/hadoop/hive/lib/hive-common-2.3.2.jar!/hive-log4j2.properties Async: true 19/03/13 14:46:57 INFO session.SessionState: Created HDFS directory: /tmp/hive/root/dbf3aaff-4a20-426b-bc59-9117e821a2f5 19/03/13 14:46:57 INFO session.SessionState: Created local directory: /hadoop/hive/tmp/root/dbf3aaff-4a20-426b-bc59-9117e821a2f5 19/03/13 14:46:57 INFO session.SessionState: Created HDFS directory: /tmp/hive/root/dbf3aaff-4a20-426b-bc59-9117e821a2f5/_tmp_space.db 19/03/13 14:46:57 INFO conf.HiveConf: Using the default value passed in for log id: dbf3aaff-4a20-426b-bc59-9117e821a2f5 19/03/13 14:46:57 INFO session.SessionState: Updating thread name to dbf3aaff-4a20-426b-bc59-9117e821a2f5 main 19/03/13 14:46:57 INFO conf.HiveConf: Using the default value passed in for log id: dbf3aaff-4a20-426b-bc59-9117e821a2f5 19/03/13 14:46:57 INFO ql.Driver: Compiling command(queryId=root_20190313064657_78359340-8092-4093-a9ed-b5a8e82ea901): CREATE TABLE IF NOT EXISTS `oracle`.`INR_LAS` ( `EMPNO` DOUBLE, `ENAME ` STRING, `JOB` STRING, `SAL` DOUBLE, `ETLTIME` STRING) COMMENT 'Imported by sqoop on 2019/03/13 06:46:54' ROW FORMAT DELIMITED FIELDS TERMINATED BY '\011' LINES TERMINATED BY '\012' STORED AS TEXTFILE19/03/13 14:47:00 INFO hive.metastore: Trying to connect to metastore with URI thrift://192.168.1.66:9083 19/03/13 14:47:00 INFO hive.metastore: Opened a connection to metastore, current connections: 1 19/03/13 14:47:00 INFO hive.metastore: Connected to metastore. 19/03/13 14:47:00 INFO parse.CalcitePlanner: Starting Semantic Analysis 19/03/13 14:47:00 INFO parse.CalcitePlanner: Creating table oracle.INR_LAS position=27 19/03/13 14:47:00 INFO ql.Driver: Semantic Analysis Completed 19/03/13 14:47:00 INFO ql.Driver: Returning Hive schema: Schema(fieldSchemas:null, properties:null) 19/03/13 14:47:00 INFO ql.Driver: Completed compiling command(queryId=root_20190313064657_78359340-8092-4093-a9ed-b5a8e82ea901); Time taken: 3.122 seconds 19/03/13 14:47:00 INFO ql.Driver: Concurrency mode is disabled, not creating a lock manager 19/03/13 14:47:00 INFO ql.Driver: Executing command(queryId=root_20190313064657_78359340-8092-4093-a9ed-b5a8e82ea901): CREATE TABLE IF NOT EXISTS `oracle`.`INR_LAS` ( `EMPNO` DOUBLE, `ENAME ` STRING, `JOB` STRING, `SAL` DOUBLE, `ETLTIME` STRING) COMMENT 'Imported by sqoop on 2019/03/13 06:46:54' ROW FORMAT DELIMITED FIELDS TERMINATED BY '\011' LINES TERMINATED BY '\012' STORED AS TEXTFILE19/03/13 14:47:00 INFO sqlstd.SQLStdHiveAccessController: Created SQLStdHiveAccessController for session context : HiveAuthzSessionContext [sessionString=dbf3aaff-4a20-426b-bc59-9117e821a2f 5, clientType=HIVECLI]19/03/13 14:47:00 WARN session.SessionState: METASTORE_FILTER_HOOK will be ignored, since hive.security.authorization.manager is set to instance of HiveAuthorizerFactory. 19/03/13 14:47:00 INFO hive.metastore: Mestastore configuration hive.metastore.filter.hook changed from org.apache.hadoop.hive.metastore.DefaultMetaStoreFilterHookImpl to org.apache.hadoop. hive.ql.security.authorization.plugin.AuthorizationMetaStoreFilterHook19/03/13 14:47:00 INFO hive.metastore: Closed a connection to metastore, current connections: 0 19/03/13 14:47:00 INFO hive.metastore: Trying to connect to metastore with URI thrift://192.168.1.66:9083 19/03/13 14:47:00 INFO hive.metastore: Opened a connection to metastore, current connections: 1 19/03/13 14:47:00 INFO hive.metastore: Connected to metastore. 19/03/13 14:47:00 INFO ql.Driver: Completed executing command(queryId=root_20190313064657_78359340-8092-4093-a9ed-b5a8e82ea901); Time taken: 0.099 seconds OK 19/03/13 14:47:00 INFO ql.Driver: OK Time taken: 3.234 seconds 19/03/13 14:47:00 INFO CliDriver: Time taken: 3.234 seconds 19/03/13 14:47:00 INFO conf.HiveConf: Using the default value passed in for log id: dbf3aaff-4a20-426b-bc59-9117e821a2f5 19/03/13 14:47:00 INFO session.SessionState: Resetting thread name to main 19/03/13 14:47:00 INFO conf.HiveConf: Using the default value passed in for log id: dbf3aaff-4a20-426b-bc59-9117e821a2f5 19/03/13 14:47:00 INFO session.SessionState: Updating thread name to dbf3aaff-4a20-426b-bc59-9117e821a2f5 main 19/03/13 14:47:00 INFO ql.Driver: Compiling command(queryId=root_20190313064700_5af88364-6217-429d-90a0-1816e54f44d9): LOAD DATA INPATH 'hdfs://192.168.1.66:9000/user/root/INR_LAS' INTO TABLE `oracle`.`INR_LAS` 19/03/13 14:47:01 INFO ql.Driver: Semantic Analysis Completed 19/03/13 14:47:01 INFO ql.Driver: Returning Hive schema: Schema(fieldSchemas:null, properties:null) 19/03/13 14:47:01 INFO ql.Driver: Completed compiling command(queryId=root_20190313064700_5af88364-6217-429d-90a0-1816e54f44d9); Time taken: 0.443 seconds 19/03/13 14:47:01 INFO ql.Driver: Concurrency mode is disabled, not creating a lock manager 19/03/13 14:47:01 INFO ql.Driver: Executing command(queryId=root_20190313064700_5af88364-6217-429d-90a0-1816e54f44d9): LOAD DATA INPATH 'hdfs://192.168.1.66:9000/user/root/INR_LAS' INTO TABLE `oracle`.`INR_LAS` 19/03/13 14:47:01 INFO ql.Driver: Starting task [Stage-0:MOVE] in serial mode 19/03/13 14:47:01 INFO hive.metastore: Closed a connection to metastore, current connections: 0 Loading data to table oracle.inr_las 19/03/13 14:47:01 INFO exec.Task: Loading data to table oracle.inr_las from hdfs://192.168.1.66:9000/user/root/INR_LAS 19/03/13 14:47:01 INFO hive.metastore: Trying to connect to metastore with URI thrift://192.168.1.66:9083 19/03/13 14:47:01 INFO hive.metastore: Opened a connection to metastore, current connections: 1 19/03/13 14:47:01 INFO hive.metastore: Connected to metastore. 19/03/13 14:47:01 ERROR hdfs.KeyProviderCache: Could not find uri with key [dfs.encryption.key.provider.uri] to create a keyProvider !! 19/03/13 14:47:02 INFO ql.Driver: Starting task [Stage-1:STATS] in serial mode 19/03/13 14:47:02 INFO exec.StatsTask: Executing stats task 19/03/13 14:47:02 INFO hive.metastore: Closed a connection to metastore, current connections: 0 19/03/13 14:47:02 INFO hive.metastore: Trying to connect to metastore with URI thrift://192.168.1.66:9083 19/03/13 14:47:02 INFO hive.metastore: Opened a connection to metastore, current connections: 1 19/03/13 14:47:02 INFO hive.metastore: Connected to metastore. 19/03/13 14:47:02 INFO hive.metastore: Closed a connection to metastore, current connections: 0 19/03/13 14:47:02 INFO hive.metastore: Trying to connect to metastore with URI thrift://192.168.1.66:9083 19/03/13 14:47:02 INFO hive.metastore: Opened a connection to metastore, current connections: 1 19/03/13 14:47:02 INFO hive.metastore: Connected to metastore. 19/03/13 14:47:02 INFO exec.StatsTask: Table oracle.inr_las stats: [numFiles=2, numRows=0, totalSize=360, rawDataSize=0] 19/03/13 14:47:02 INFO ql.Driver: Completed executing command(queryId=root_20190313064700_5af88364-6217-429d-90a0-1816e54f44d9); Time taken: 1.211 seconds OK 19/03/13 14:47:02 INFO ql.Driver: OK Time taken: 1.654 seconds 19/03/13 14:47:02 INFO CliDriver: Time taken: 1.654 seconds 19/03/13 14:47:02 INFO conf.HiveConf: Using the default value passed in for log id: dbf3aaff-4a20-426b-bc59-9117e821a2f5 19/03/13 14:47:02 INFO session.SessionState: Resetting thread name to main 19/03/13 14:47:02 INFO conf.HiveConf: Using the default value passed in for log id: dbf3aaff-4a20-426b-bc59-9117e821a2f5 19/03/13 14:47:02 INFO session.SessionState: Deleted directory: /tmp/hive/root/dbf3aaff-4a20-426b-bc59-9117e821a2f5 on fs with scheme hdfs 19/03/13 14:47:02 INFO session.SessionState: Deleted directory: /hadoop/hive/tmp/root/dbf3aaff-4a20-426b-bc59-9117e821a2f5 on fs with scheme file 19/03/13 14:47:02 INFO hive.metastore: Closed a connection to metastore, current connections: 0 19/03/13 14:47:02 INFO hive.HiveImport: Hive import complete. 19/03/13 14:47:02 INFO hive.HiveImport: Export directory is empty, removing it. 19/03/13 14:47:02 INFO tool.ImportTool: Incremental import complete! To run another incremental import of all data following this import, supply the following arguments: 19/03/13 14:47:02 INFO tool.ImportTool: --incremental append 19/03/13 14:47:02 INFO tool.ImportTool: --check-column ETLTIME 19/03/13 14:47:02 INFO tool.ImportTool: --last-value 2019-03-20 10:52:34.0 19/03/13 14:47:02 INFO tool.ImportTool: (Consider saving this with 'sqoop job --create')查询hive表hive> select * from inr_las; OK 1 er CLERK 800.0 2019-03-20 10:42:27.0 2 ALLEN SALESMAN 1600.0 2019-03-20 10:42:27.0 3 WARD SALESMAN 1250.0 2019-03-20 10:42:27.0 4 JONES MANAGER 2975.0 2019-03-20 10:42:27.0 5 MARTIN SALESMAN 1250.0 2019-03-20 10:42:27.0 6 zhao DBA 100.0 2019-03-20 10:42:27.0 7 yan BI 100.0 2019-03-20 10:42:27.0 8 dong JAVA 100.0 2019-03-20 10:42:27.0 6 zhao DBA 1000.0 2019-03-20 10:52:34.0 Time taken: 0.171 seconds, Fetched: 9 row(s)通过上面查询结果可以看到,empno=6的这个员工薪资和etltime记录变更时间都变化后,根据上一次全量初始化后的最大时间来做增量的起始时间去源端oracle查数时候,发现了新的发生变化的数据,然后将它最新状态抽到了hive,采用的追加方式,因此hive里存了两条记录,导致了数据重复,根据时间可以取最新的状态来获取最新数据状态。实验二:合并模式接着上面实验环境继续做,这次采用合并模式来看看效果: --先看下当前的源端oracle数据: EMPNO ENAME JOB SAL ETLTIME 1 er CLERK 800.00 2019/3/20 10:42:27 2 ALLEN SALESMAN 1600.00 2019/3/20 10:42:27 3 WARD SALESMAN 1250.00 2019/3/20 10:42:27 4 JONES MANAGER 2975.00 2019/3/20 10:42:27 5 MARTIN SALESMAN 1250.00 2019/3/20 10:42:27 6 zhao DBA 1000.00 2019/3/20 10:52:34 7 yan BI 100.00 2019/3/20 10:42:27 8 dong JAVA 200.00 2019/3/21 17:12:46 先把前面的hive表给删了hive> drop table inr_las; OK Time taken: 0.195 seconds创建为外部表hive>create table INR_LAS ( empno int, ename string, job string, sal float, etltime string ) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' location '/user/hive/warehouse/exter_inr_las'; OK Time taken: 0.226 seconds注意,/user/hive/warehouse/exter_inr_las这个目录在第一次全量初始化时不要存在,它会自己创建,如果存在会报目录已存在错误:ERROR tool.ImportTool: Import failed: org.apache.hadoop.mapred.FileAlreadyExistsException: Output directory hdfs://192.168.1.66:9000/user/hive/warehouse/exter_inr_las alre ady exists这时候应该先删除一次这个目录:[root@hadoop ~]# hadoop fs -rmr /user/hive/warehouse/exter_inr_las rmr: DEPRECATED: Please use 'rm -r' instead. 19/03/13 22:05:33 INFO fs.TrashPolicyDefault: Namenode trash configuration: Deletion interval = 0 minutes, Emptier interval = 0 minutes. Deleted /user/hive/warehouse/exter_inr_las接下来全量导入一次:[root@hadoop ~]# sqoop import --connect jdbc:oracle:thin:@192.168.1.6:1521:orcl --username scott --password tiger --table INR_LAS -m 1 --target-dir /user/hive/warehouse/exter_inr_las --fiel ds-terminated-by '\t'Warning: /hadoop/sqoop/../accumulo does not exist! Accumulo imports will fail. Please set $ACCUMULO_HOME to the root of your Accumulo installation. 19/03/13 22:05:48 INFO sqoop.Sqoop: Running Sqoop version: 1.4.7 19/03/13 22:05:48 WARN tool.BaseSqoopTool: Setting your password on the command-line is insecure. Consider using -P instead. 19/03/13 22:05:48 INFO oracle.OraOopManagerFactory: Data Connector for Oracle and Hadoop is disabled. 19/03/13 22:05:48 INFO manager.SqlManager: Using default fetchSize of 1000 19/03/13 22:05:48 INFO tool.CodeGenTool: Beginning code generation SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:file:/hadoop/share/hadoop/common/lib/slf4j-log4j12-1.7.10.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:file:/hadoop/hbase/lib/slf4j-log4j12-1.7.10.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:file:/hadoop/hive/lib/log4j-slf4j-impl-2.6.2.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation. SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory] 19/03/13 22:05:49 INFO manager.OracleManager: Time zone has been set to GMT 19/03/13 22:05:49 INFO manager.SqlManager: Executing SQL statement: SELECT t.* FROM INR_LAS t WHERE 1=0 19/03/13 22:05:49 INFO orm.CompilationManager: HADOOP_MAPRED_HOME is /hadoop Note: /tmp/sqoop-root/compile/c8b2ed3172295709d819d17ca24aaf50/INR_LAS.java uses or overrides a deprecated API. Note: Recompile with -Xlint:deprecation for details. 19/03/13 22:05:52 INFO orm.CompilationManager: Writing jar file: /tmp/sqoop-root/compile/c8b2ed3172295709d819d17ca24aaf50/INR_LAS.jar 19/03/13 22:05:52 INFO manager.OracleManager: Time zone has been set to GMT 19/03/13 22:05:52 INFO manager.OracleManager: Time zone has been set to GMT 19/03/13 22:05:52 INFO mapreduce.ImportJobBase: Beginning import of INR_LAS 19/03/13 22:05:52 INFO Configuration.deprecation: mapred.jar is deprecated. Instead, use mapreduce.job.jar 19/03/13 22:05:52 INFO manager.OracleManager: Time zone has been set to GMT 19/03/13 22:05:53 INFO Configuration.deprecation: mapred.map.tasks is deprecated. Instead, use mapreduce.job.maps 19/03/13 22:05:54 INFO client.RMProxy: Connecting to ResourceManager at /192.168.1.66:8032 19/03/13 22:05:57 INFO db.DBInputFormat: Using read commited transaction isolation 19/03/13 22:05:57 INFO mapreduce.JobSubmitter: number of splits:1 19/03/13 22:05:58 INFO mapreduce.JobSubmitter: Submitting tokens for job: job_1552482402053_0006 19/03/13 22:05:58 INFO impl.YarnClientImpl: Submitted application application_1552482402053_0006 19/03/13 22:05:58 INFO mapreduce.Job: The url to track the job: http://hadoop:8088/proxy/application_1552482402053_0006/ 19/03/13 22:05:58 INFO mapreduce.Job: Running job: job_1552482402053_0006 19/03/13 22:06:07 INFO mapreduce.Job: Job job_1552482402053_0006 running in uber mode : false 19/03/13 22:06:07 INFO mapreduce.Job: map 0% reduce 0% 19/03/13 22:06:13 INFO mapreduce.Job: map 100% reduce 0% 19/03/13 22:06:15 INFO mapreduce.Job: Job job_1552482402053_0006 completed successfully 19/03/13 22:06:15 INFO mapreduce.Job: Counters: 30 File System Counters FILE: Number of bytes read=0 FILE: Number of bytes written=144058 FILE: Number of read operations=0 FILE: Number of large read operations=0 FILE: Number of write operations=0 HDFS: Number of bytes read=87 HDFS: Number of bytes written=323 HDFS: Number of read operations=4 HDFS: Number of large read operations=0 HDFS: Number of write operations=2 Job Counters Launched map tasks=1 Other local map tasks=1 Total time spent by all maps in occupied slots (ms)=4115 Total time spent by all reduces in occupied slots (ms)=0 Total time spent by all map tasks (ms)=4115 Total vcore-milliseconds taken by all map tasks=4115 Total megabyte-milliseconds taken by all map tasks=4213760 Map-Reduce Framework Map input records=8 Map output records=8 Input split bytes=87 Spilled Records=0 Failed Shuffles=0 Merged Map outputs=0 GC time elapsed (ms)=109 CPU time spent (ms)=2220 Physical memory (bytes) snapshot=187392000 Virtual memory (bytes) snapshot=2140803072 Total committed heap usage (bytes)=106430464 File Input Format Counters Bytes Read=0 File Output Format Counters Bytes Written=323 19/03/13 22:06:15 INFO mapreduce.ImportJobBase: Transferred 323 bytes in 21.3756 seconds (15.1107 bytes/sec) 19/03/13 22:06:15 INFO mapreduce.ImportJobBase: Retrieved 8 records.查看一下hdfs此文件夹下文件:[root@hadoop ~]# hdfs dfs -cat /user/hive/warehouse/exter_inr_las/part-m-00000 1 er CLERK 800 2019-03-20 10:42:27.0 2 ALLEN SALESMAN 1600 2019-03-20 10:42:27.0 3 WARD SALESMAN 1250 2019-03-20 10:42:27.0 4 JONES MANAGER 2975 2019-03-20 10:42:27.0 5 MARTIN SALESMAN 1250 2019-03-20 10:42:27.0 6 zhao DBA 1000 2019-03-20 10:52:34.0 7 yan BI 100 2019-03-20 10:42:27.0 8 dong JAVA 200 2019-03-21 17:12:46.0查一下hive表:hive> select * from inr_las; OK 1 er CLERK 800.0 2019-03-20 10:42:27.0 2 ALLEN SALESMAN 1600.0 2019-03-20 10:42:27.0 3 WARD SALESMAN 1250.0 2019-03-20 10:42:27.0 4 JONES MANAGER 2975.0 2019-03-20 10:42:27.0 5 MARTIN SALESMAN 1250.0 2019-03-20 10:42:27.0 6 zhao DBA 1000.0 2019-03-20 10:52:34.0 7 yan BI 100.0 2019-03-20 10:42:27.0 8 dong JAVA 200.0 2019-03-21 17:12:46.0 Time taken: 0.191 seconds, Fetched: 8 row(s)接下来修改一下oracle的数据:update inr_las set sal=400 ,etltime=sysdate where empno=8; commit; select * from inr_las; EMPNO ENAME JOB SAL ETLTIME 1 er CLERK 800.00 2019/3/20 10:42:27 2 ALLEN SALESMAN 1600.00 2019/3/20 10:42:27 3 WARD SALESMAN 1250.00 2019/3/20 10:42:27 4 JONES MANAGER 2975.00 2019/3/20 10:42:27 5 MARTIN SALESMAN 1250.00 2019/3/20 10:42:27 6 zhao DBA 1000.00 2019/3/20 10:52:34 7 yan BI 100.00 2019/3/20 10:42:27 8 dong JAVA 400.00 2019/3/21 17:47:03--已经更改了接下来做合并模式增量:[root@hadoop ~]# sqoop import --connect jdbc:oracle:thin:@192.168.1.6:1521:orcl --username scott --password tiger --table INR_LAS --fields-terminated-by '\t' --lines-terminated-by '\n' --t arget-dir /user/hive/warehouse/exter_inr_las -m 1 --check-column ETLTIME --incremental lastmodified --merge-key EMPNO --last-value "2019-03-21 17:12:46"Warning: /hadoop/sqoop/../accumulo does not exist! Accumulo imports will fail. Please set $ACCUMULO_HOME to the root of your Accumulo installation. 19/03/13 22:18:41 INFO sqoop.Sqoop: Running Sqoop version: 1.4.7 19/03/13 22:18:42 WARN tool.BaseSqoopTool: Setting your password on the command-line is insecure. Consider using -P instead. 19/03/13 22:18:42 INFO oracle.OraOopManagerFactory: Data Connector for Oracle and Hadoop is disabled. 19/03/13 22:18:42 INFO manager.SqlManager: Using default fetchSize of 1000 19/03/13 22:18:42 INFO tool.CodeGenTool: Beginning code generation SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:file:/hadoop/share/hadoop/common/lib/slf4j-log4j12-1.7.10.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:file:/hadoop/hbase/lib/slf4j-log4j12-1.7.10.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:file:/hadoop/hive/lib/log4j-slf4j-impl-2.6.2.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation. SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory] 19/03/13 22:18:43 INFO manager.OracleManager: Time zone has been set to GMT 19/03/13 22:18:43 INFO manager.SqlManager: Executing SQL statement: SELECT t.* FROM INR_LAS t WHERE 1=0 19/03/13 22:18:43 INFO orm.CompilationManager: HADOOP_MAPRED_HOME is /hadoop Note: /tmp/sqoop-root/compile/d4af8fb9c2b8dd33c20926713e8d23e2/INR_LAS.java uses or overrides a deprecated API. Note: Recompile with -Xlint:deprecation for details. 19/03/13 22:18:47 INFO orm.CompilationManager: Writing jar file: /tmp/sqoop-root/compile/d4af8fb9c2b8dd33c20926713e8d23e2/INR_LAS.jar 19/03/13 22:18:47 INFO manager.OracleManager: Time zone has been set to GMT 19/03/13 22:18:47 INFO manager.SqlManager: Executing SQL statement: SELECT t.* FROM INR_LAS t WHERE 1=0 19/03/13 22:18:47 INFO tool.ImportTool: Incremental import based on column ETLTIME 19/03/13 22:18:47 INFO tool.ImportTool: Lower bound value: TO_TIMESTAMP('2019-03-21 17:12:46', 'YYYY-MM-DD HH24:MI:SS.FF') 19/03/13 22:18:47 INFO tool.ImportTool: Upper bound value: TO_TIMESTAMP('2019-03-21 17:54:19.0', 'YYYY-MM-DD HH24:MI:SS.FF') 19/03/13 22:18:47 INFO manager.OracleManager: Time zone has been set to GMT 19/03/13 22:18:47 INFO mapreduce.ImportJobBase: Beginning import of INR_LAS 19/03/13 22:18:47 INFO Configuration.deprecation: mapred.jar is deprecated. Instead, use mapreduce.job.jar 19/03/13 22:18:47 INFO manager.OracleManager: Time zone has been set to GMT 19/03/13 22:18:48 INFO Configuration.deprecation: mapred.map.tasks is deprecated. Instead, use mapreduce.job.maps 19/03/13 22:18:48 INFO client.RMProxy: Connecting to ResourceManager at /192.168.1.66:8032 19/03/13 22:18:52 INFO db.DBInputFormat: Using read commited transaction isolation 19/03/13 22:18:52 INFO mapreduce.JobSubmitter: number of splits:1 19/03/13 22:18:52 INFO mapreduce.JobSubmitter: Submitting tokens for job: job_1552482402053_0009 19/03/13 22:18:53 INFO impl.YarnClientImpl: Submitted application application_1552482402053_0009 19/03/13 22:18:53 INFO mapreduce.Job: The url to track the job: http://hadoop:8088/proxy/application_1552482402053_0009/ 19/03/13 22:18:53 INFO mapreduce.Job: Running job: job_1552482402053_0009 19/03/13 22:19:02 INFO mapreduce.Job: Job job_1552482402053_0009 running in uber mode : false 19/03/13 22:19:02 INFO mapreduce.Job: map 0% reduce 0% 19/03/13 22:19:09 INFO mapreduce.Job: map 100% reduce 0% 19/03/13 22:19:10 INFO mapreduce.Job: Job job_1552482402053_0009 completed successfully 19/03/13 22:19:10 INFO mapreduce.Job: Counters: 30 File System Counters FILE: Number of bytes read=0 FILE: Number of bytes written=144379 FILE: Number of read operations=0 FILE: Number of large read operations=0 FILE: Number of write operations=0 HDFS: Number of bytes read=87 HDFS: Number of bytes written=38 HDFS: Number of read operations=4 HDFS: Number of large read operations=0 HDFS: Number of write operations=2 Job Counters Launched map tasks=1 Other local map tasks=1 Total time spent by all maps in occupied slots (ms)=4767 Total time spent by all reduces in occupied slots (ms)=0 Total time spent by all map tasks (ms)=4767 Total vcore-milliseconds taken by all map tasks=4767 Total megabyte-milliseconds taken by all map tasks=4881408 Map-Reduce Framework Map input records=1 Map output records=1 Input split bytes=87 Spilled Records=0 Failed Shuffles=0 Merged Map outputs=0 GC time elapsed (ms)=414 CPU time spent (ms)=2360 Physical memory (bytes) snapshot=189968384 Virtual memory (bytes) snapshot=2140639232 Total committed heap usage (bytes)=117440512 File Input Format Counters Bytes Read=0 File Output Format Counters Bytes Written=38 19/03/13 22:19:10 INFO mapreduce.ImportJobBase: Transferred 38 bytes in 22.4022 seconds (1.6963 bytes/sec) 19/03/13 22:19:11 INFO mapreduce.ImportJobBase: Retrieved 1 records. 19/03/13 22:19:11 INFO tool.ImportTool: Final destination exists, will run merge job. 19/03/13 22:19:11 INFO Configuration.deprecation: mapred.output.key.class is deprecated. Instead, use mapreduce.job.output.key.class 19/03/13 22:19:11 INFO client.RMProxy: Connecting to ResourceManager at /192.168.1.66:8032 19/03/13 22:19:14 INFO input.FileInputFormat: Total input paths to process : 2 19/03/13 22:19:14 INFO mapreduce.JobSubmitter: number of splits:2 19/03/13 22:19:14 INFO mapreduce.JobSubmitter: Submitting tokens for job: job_1552482402053_0010 19/03/13 22:19:14 INFO impl.YarnClientImpl: Submitted application application_1552482402053_0010 19/03/13 22:19:14 INFO mapreduce.Job: The url to track the job: http://hadoop:8088/proxy/application_1552482402053_0010/ 19/03/13 22:19:14 INFO mapreduce.Job: Running job: job_1552482402053_0010 19/03/13 22:19:25 INFO mapreduce.Job: Job job_1552482402053_0010 running in uber mode : false 19/03/13 22:19:25 INFO mapreduce.Job: map 0% reduce 0% 19/03/13 22:19:33 INFO mapreduce.Job: map 100% reduce 0% 19/03/13 22:19:40 INFO mapreduce.Job: map 100% reduce 100% 19/03/13 22:19:40 INFO mapreduce.Job: Job job_1552482402053_0010 completed successfully 19/03/13 22:19:40 INFO mapreduce.Job: Counters: 49 File System Counters FILE: Number of bytes read=614 FILE: Number of bytes written=434631 FILE: Number of read operations=0 FILE: Number of large read operations=0 FILE: Number of write operations=0 HDFS: Number of bytes read=657 HDFS: Number of bytes written=323 HDFS: Number of read operations=9 HDFS: Number of large read operations=0 HDFS: Number of write operations=2 Job Counters Launched map tasks=2 Launched reduce tasks=1 Data-local map tasks=2 Total time spent by all maps in occupied slots (ms)=9137 Total time spent by all reduces in occupied slots (ms)=4019 Total time spent by all map tasks (ms)=9137 Total time spent by all reduce tasks (ms)=4019 Total vcore-milliseconds taken by all map tasks=9137 Total vcore-milliseconds taken by all reduce tasks=4019 Total megabyte-milliseconds taken by all map tasks=9356288 Total megabyte-milliseconds taken by all reduce tasks=4115456 Map-Reduce Framework Map input records=9 Map output records=9 Map output bytes=590 Map output materialized bytes=620 Input split bytes=296 Combine input records=0 Combine output records=0 Reduce input groups=8 Reduce shuffle bytes=620 Reduce input records=9 Reduce output records=8 Spilled Records=18 Shuffled Maps =2 Failed Shuffles=0 Merged Map outputs=2 GC time elapsed (ms)=503 CPU time spent (ms)=3680 Physical memory (bytes) snapshot=704909312 Virtual memory (bytes) snapshot=6395523072 Total committed heap usage (bytes)=517996544 Shuffle Errors BAD_ID=0 CONNECTION=0 IO_ERROR=0 WRONG_LENGTH=0 WRONG_MAP=0 WRONG_REDUCE=0 File Input Format Counters Bytes Read=361 File Output Format Counters Bytes Written=323 19/03/13 22:19:40 INFO tool.ImportTool: Incremental import complete! To run another incremental import of all data following this import, supply the following arguments: 19/03/13 22:19:40 INFO tool.ImportTool: --incremental lastmodified 19/03/13 22:19:40 INFO tool.ImportTool: --check-column ETLTIME 19/03/13 22:19:40 INFO tool.ImportTool: --last-value 2019-03-21 17:54:19.0 19/03/13 22:19:40 INFO tool.ImportTool: (Consider saving this with 'sqoop job --create') 这时候去看下/user/hive/warehouse/exter_inr_las/内容,你会发现part-m-00000变成了part-r-00000,意思是做了reduce:root@hadoop ~]# hdfs dfs -cat /user/hive/warehouse/exter_inr_las/part-r-00000 1 er CLERK 800 2019-03-20 10:42:27.0 2 ALLEN SALESMAN 1600 2019-03-20 10:42:27.0 3 WARD SALESMAN 1250 2019-03-20 10:42:27.0 4 JONES MANAGER 2975 2019-03-20 10:42:27.0 5 MARTIN SALESMAN 1250 2019-03-20 10:42:27.0 6 zhao DBA 1000 2019-03-20 10:52:34.0 7 yan BI 100 2019-03-20 10:42:27.0 8 dong JAVA 400 2019-03-21 17:47:03.0发现empno=8的记录的确做了变更了,增量同步成功,去看下hive表:hive> select * from inr_las; OK 1 er CLERK 800.0 2019-03-20 10:42:27.0 2 ALLEN SALESMAN 1600.0 2019-03-20 10:42:27.0 3 WARD SALESMAN 1250.0 2019-03-20 10:42:27.0 4 JONES MANAGER 2975.0 2019-03-20 10:42:27.0 5 MARTIN SALESMAN 1250.0 2019-03-20 10:42:27.0 6 zhao DBA 1000.0 2019-03-20 10:52:34.0 7 yan BI 100.0 2019-03-20 10:42:27.0 8 dong JAVA 400.0 2019-03-21 17:47:03.0 Time taken: 0.196 seconds, Fetched: 8 row(s)没问题。由于篇幅原因,sqoop job的使用及增量脚本定时同步数据的案例写在了下一篇文章
文章
SQL  ·  分布式计算  ·  运维  ·  Oracle  ·  关系型数据库  ·  Hadoop  ·  MySQL  ·  大数据  ·  HIVE  ·  数据安全/隐私保护
2023-03-24
【大数据开发运维解决方案】Hadoop2.7.6+Spark2.4.4+Scala2.11.12+Hudi0.5.2单机伪分布式安装
Hadoop2.7.6+Spark2.4.4+Scala2.11.12+Hudi0.5.2单机伪分布式安装注意1、本文档使用的基础hadoop环境是基于本人写的另一篇文章的基础上新增的spark和hudi的安装部署文档,基础环境部署文档2、整篇文章配置相对简单,走了一些坑,没有写在文档里,为了像我一样的小白看我的文档,按着错误的路径走了,文章整体写的较为详细,按照文章整体过程来做应该不会出错,如果需要搭建基础大数据环境的,可以看上面本人写的hadoop环境部署文档,写的较为详细。3、关于spark和hudi的介绍这里不再赘述,网上和官方文档有很多的文字介绍,本文所有安装所需的介质或官方文档均已给出可以直接下载或跳转的路径,方便各位免费下载与我文章安装的一致版本的介质。4、下面是本实验安装完成后本人实验环境整体hadoop系列组件的版本情况:软件名称版本号Hadoop2.7.6Mysql5.7Hive2.3.2Hbase1.4.9Spark2.4.4Hudi0.5.2JDK1.8.0_151Scala2.11.12OGG for bigdata12.3Kylin2.4Kafka2.11-1.1.1Zookeeper3.4.6Oracle Linux6.8x64一、安装spark依赖的Scala因为其他版本的Spark都是基于2.11.版本,只有2.4.2版本的才使用Scala2.12. 版本进行开发,hudi官方用的是spark2.4.4,而spark:"Using Scala version 2.11.12 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_151)",所以这里我们下载scala2.11.12。1.1 下载和解压缩Scala下载地址:点击进入下载linux版本:在Linux服务器的opt目录下新建一个名为scala的文件夹,并将下载的压缩包上载上去:[root@hadoop opt]# cd /usr/ [root@hadoop usr]# mkdir scala [root@hadoop usr]# cd scala/ [root@hadoop scala]# pwd /usr/scala [root@hadoop scala]# ls scala-2.11.12.tgz [root@hadoop scala]# tar -zxvf scala-2.11.12.tgz [root@hadoop scala]# ls scala-2.11.12 scala-2.11.12.tgz [root@hadoop scala]# rm -rf *tgz [root@hadoop scala]# cd scala-2.11.12/ [root@hadoop scala-2.11.12]# pwd /usr/scala/scala-2.11.121.2 配置环境变量编辑/etc/profile这个文件,在文件中增加配置:export SCALA_HOME=/usr/scala/scala-2.11.12 在该文件的PATH变量中增加下面的内容: ${SCALA_HOME}/bin添加完成后,我的/etc/profile的配置如下:export JAVA_HOME=/usr/java/jdk1.8.0_151 export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar export PATH=$PATH:$JAVA_HOME/bin export HADOOP_HOME=/hadoop/ export HADOOP_CONF_DIR=${HADOOP_HOME}/etc/hadoop export HADOOP_COMMON_LIB_NATIVE_DIR=$HADOOP_HOME/lib/native export HADOOP_OPTS="-Djava.library.path=$HADOOP_HOME/lib:$HADOOP_COMMON_LIB_NATIVE_DIR" export HIVE_HOME=/hadoop/hive export HIVE_CONF_DIR=${HIVE_HOME}/conf export HCAT_HOME=$HIVE_HOME/hcatalog export HIVE_DEPENDENCY=/hadoop/hive/conf:/hadoop/hive/lib/*:/hadoop/hive/hcatalog/share/hcatalog/hive-hcatalog-pig-adapter-2.3.3.jar:/hadoop/hive/hcatalog/share/hcatalog/hive-hcatalog-core-2.3.3.jar:/hadoop/hiv e/hcatalog/share/hcatalog/hive-hcatalog-server-extensions-2.3.3.jar:/hadoop/hive/hcatalog/share/hcatalog/hive-hcatalog-streaming-2.3.3.jar:/hadoop/hive/lib/hive-exec-2.3.3.jarexport HBASE_HOME=/hadoop/hbase/ export ZOOKEEPER_HOME=/hadoop/zookeeper export KAFKA_HOME=/hadoop/kafka export KYLIN_HOME=/hadoop/kylin/ export GGHOME=/hadoop/ogg12 export SCALA_HOME=/usr/scala/scala-2.11.12 export PATH=$PATH:$JAVA_HOME/bin:$HADOOP_HOME/bin:$HADOOP_HOME/sbin:$HIVE_HOME/bin:$HCAT_HOME/bin:$HBASE_HOME/bin:$ZOOKEEPER_HOME:$KAFKA_HOME:$KYLIN_HOME/bin:${SCALA_HOME}/bin export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:${HIVE_HOME}/lib:$HBASE_HOME/lib:$KYLIN_HOME/lib export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$JAVA_HOME/jre/lib/amd64/libjsig.so:$JAVA_HOME/jre/lib/amd64/server/libjvm.so:$JAVA_HOME/jre/lib/amd64/server:$JAVA_HOME/jre/lib/amd64:$GG_HOME:/lib保存退出,source一下使环境变量生效:[root@hadoop ~]# source /etc/profile1.3 验证Scala[root@hadoop scala-2.11.12]# scala -version Scala code runner version 2.11.12 -- Copyright 2002-2017, LAMP/EPFL二、 下载和解压缩Spark2.1、下载Spark下载地址:点击进入2.2 解压缩Spark在/hadoop创建spark目录用户存放spark。[root@hadoop scala-2.11.12]# cd /hadoop/ [root@hadoop hadoop]# mkdir spark [root@hadoop hadoop]# cd spark/ 通过xftp上传安装包到spark目录 [root@hadoop spark]# tar -zxvf spark-2.4.4-bin-hadoop2.7.tgz [root@hadoop spark]# ls spark-2.4.4-bin-hadoop2.7 spark-2.4.4-bin-hadoop2.7.tgz [root@hadoop spark]# rm -rf *tgz [root@hadoop spark]# mv spark-2.4.4-bin-hadoop2.7/* . [root@hadoop spark]# ls bin conf data examples jars kubernetes LICENSE licenses NOTICE python R README.md RELEASE sbin spark-2.4.4-bin-hadoop2.7 yarn三、Spark相关的配置3.1、配置环境变量编辑/etc/profile文件,增加export SPARK_HOME=/hadoop/spark上面的变量添加完成后编辑该文件中的PATH变量,添加${SPARK_HOME}/bin修改完成后,我的/etc/profile文件内容是:export JAVA_HOME=/usr/java/jdk1.8.0_151 export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar export PATH=$PATH:$JAVA_HOME/bin export HADOOP_HOME=/hadoop/ export HADOOP_CONF_DIR=${HADOOP_HOME}/etc/hadoop export HADOOP_COMMON_LIB_NATIVE_DIR=$HADOOP_HOME/lib/native export HADOOP_OPTS="-Djava.library.path=$HADOOP_HOME/lib:$HADOOP_COMMON_LIB_NATIVE_DIR" export HIVE_HOME=/hadoop/hive export HIVE_CONF_DIR=${HIVE_HOME}/conf export HCAT_HOME=$HIVE_HOME/hcatalog export HIVE_DEPENDENCY=/hadoop/hive/conf:/hadoop/hive/lib/*:/hadoop/hive/hcatalog/share/hcatalog/hive-hcatalog-pig-adapter-2.3.3.jar:/hadoop/hive/hcatalog/share/hcatalog/hive-hcatalog-core-2.3.3.jar:/hadoop/hiv e/hcatalog/share/hcatalog/hive-hcatalog-server-extensions-2.3.3.jar:/hadoop/hive/hcatalog/share/hcatalog/hive-hcatalog-streaming-2.3.3.jar:/hadoop/hive/lib/hive-exec-2.3.3.jarexport HBASE_HOME=/hadoop/hbase/ export ZOOKEEPER_HOME=/hadoop/zookeeper export KAFKA_HOME=/hadoop/kafka export KYLIN_HOME=/hadoop/kylin/ export GGHOME=/hadoop/ogg12 export SCALA_HOME=/usr/scala/scala-2.11.12 export SPARK_HOME=/hadoop/spark export PATH=$PATH:$JAVA_HOME/bin:$HADOOP_HOME/bin:$HADOOP_HOME/sbin:$HIVE_HOME/bin:$HCAT_HOME/bin:$HBASE_HOME/bin:$ZOOKEEPER_HOME:$KAFKA_HOME:$KYLIN_HOME/bin:${SCALA_HOME}/bin:${SPARK_HOME}/bin:${SPARK_HOME}/sbin export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:${HIVE_HOME}/lib:$HBASE_HOME/lib:$KYLIN_HOME/lib export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$JAVA_HOME/jre/lib/amd64/libjsig.so:$JAVA_HOME/jre/lib/amd64/server/libjvm.so:$JAVA_HOME/jre/lib/amd64/server:$JAVA_HOME/jre/lib/amd64:$GG_HOME:/lib编辑完成后,执行命令 source /etc/profile使环境变量生效。3.2、配置参数文件进入conf目录[root@hadoop conf]# pwd /hadoop/spark/conf复制一份配置文件并重命名root@hadoop conf]# cp spark-env.sh.template spark-env.sh [root@hadoop conf]# ls docker.properties.template fairscheduler.xml.template log4j.properties.template metrics.properties.template slaves.template spark-defaults.conf.template spark-env.sh spark-env.sh.template编辑spark-env.h文件,在里面加入配置(具体路径以自己的为准):export SCALA_HOME=/usr/scala/scala-2.11.12 export JAVA_HOME=/usr/java/jdk1.8.0_151 export HADOOP_HOME=/hadoop export HADOOP_CONF_DIR=${HADOOP_HOME}/etc/hadoop export SPARK_HOME=/hadoop/spark export SPARK_MASTER_IP=192.168.1.66 export SPARK_EXECUTOR_MEMORY=1Gsource /etc/profile生效。3.3、新建slaves文件以spark为我们创建好的模板创建一个slaves文件,命令是:[root@hadoop conf]# pwd /hadoop/spark/conf [root@hadoop conf]# cp slaves.template slaves四、启动spark因为spark是依赖于hadoop提供的分布式文件系统的,所以在启动spark之前,先确保hadoop在正常运行。[root@hadoop hadoop]# jps 23408 RunJar 23249 JobHistoryServer 23297 RunJar 24049 Jps 22404 DataNode 22774 ResourceManager 23670 Kafka 22264 NameNode 22889 NodeManager 23642 QuorumPeerMain 22589 SecondaryNameNode在hadoop正常运行的情况下,在hserver1(也就是hadoop的namenode,spark的marster节点)上执行命令:[root@hadoop hadoop]# cd /hadoop/spark/sbin [root@hadoop sbin]# ./start-all.sh starting org.apache.spark.deploy.master.Master, logging to /hadoop/spark/logs/spark-root-org.apache.spark.deploy.master.Master-1-hadoop.out localhost: starting org.apache.spark.deploy.worker.Worker, logging to /hadoop/spark/logs/spark-root-org.apache.spark.deploy.worker.Worker-1-hadoop.out [root@hadoop sbin]# cat /hadoop/spark/logs/spark-root-org.apache.spark.deploy.master.Master-1-hadoop.out Spark Command: /usr/java/jdk1.8.0_151/bin/java -cp /hadoop/spark/conf/:/hadoop/spark/jars/*:/hadoop/etc/hadoop/ -Xmx1g org.apache.spark.deploy.master.Master --host hadoop --port 7077 --webui-port 8080 ======================================== 20/03/30 22:42:27 INFO master.Master: Started daemon with process name: 24079@hadoop 20/03/30 22:42:27 INFO util.SignalUtils: Registered signal handler for TERM 20/03/30 22:42:27 INFO util.SignalUtils: Registered signal handler for HUP 20/03/30 22:42:27 INFO util.SignalUtils: Registered signal handler for INT 20/03/30 22:42:27 WARN master.MasterArguments: SPARK_MASTER_IP is deprecated, please use SPARK_MASTER_HOST 20/03/30 22:42:27 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable 20/03/30 22:42:27 INFO spark.SecurityManager: Changing view acls to: root 20/03/30 22:42:27 INFO spark.SecurityManager: Changing modify acls to: root 20/03/30 22:42:27 INFO spark.SecurityManager: Changing view acls groups to: 20/03/30 22:42:27 INFO spark.SecurityManager: Changing modify acls groups to: 20/03/30 22:42:27 INFO spark.SecurityManager: SecurityManager: authentication disabled; ui acls disabled; users with view permissions: Set(root); groups with view permissions: Set(); users with modify permiss ions: Set(root); groups with modify permissions: Set()20/03/30 22:42:27 INFO util.Utils: Successfully started service 'sparkMaster' on port 7077. 20/03/30 22:42:27 INFO master.Master: Starting Spark master at spark://hadoop:7077 20/03/30 22:42:27 INFO master.Master: Running Spark version 2.4.4 20/03/30 22:42:28 INFO util.log: Logging initialized @1497ms 20/03/30 22:42:28 INFO server.Server: jetty-9.3.z-SNAPSHOT, build timestamp: unknown, git hash: unknown 20/03/30 22:42:28 INFO server.Server: Started @1560ms 20/03/30 22:42:28 INFO server.AbstractConnector: Started ServerConnector@6182300a{HTTP/1.1,[http/1.1]}{0.0.0.0:8080} 20/03/30 22:42:28 INFO util.Utils: Successfully started service 'MasterUI' on port 8080. 20/03/30 22:42:28 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@f1f0276{/app,null,AVAILABLE,@Spark} 20/03/30 22:42:28 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@f1af444{/app/json,null,AVAILABLE,@Spark} 20/03/30 22:42:28 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@259b10d3{/,null,AVAILABLE,@Spark} 20/03/30 22:42:28 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@6fc2f56f{/json,null,AVAILABLE,@Spark} 20/03/30 22:42:28 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@37a28407{/static,null,AVAILABLE,@Spark} 20/03/30 22:42:28 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@e99fa57{/app/kill,null,AVAILABLE,@Spark} 20/03/30 22:42:28 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@66be5bb8{/driver/kill,null,AVAILABLE,@Spark} 20/03/30 22:42:28 INFO ui.MasterWebUI: Bound MasterWebUI to 0.0.0.0, and started at http://hadoop:8080 20/03/30 22:42:28 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@6b2c0980{/metrics/master/json,null,AVAILABLE,@Spark} 20/03/30 22:42:28 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@4ac1749f{/metrics/applications/json,null,AVAILABLE,@Spark} 20/03/30 22:42:28 INFO master.Master: I have been elected leader! New state: ALIVE 20/03/30 22:42:31 INFO master.Master: Registering worker 192.168.1.66:39384 with 8 cores, 4.6 GB RAM [root@hadoop sbin]# cat /hadoop/spark/logs/spark-root-org.apache.spark.deploy.worker.Worker-1-hadoop.out Spark Command: /usr/java/jdk1.8.0_151/bin/java -cp /hadoop/spark/conf/:/hadoop/spark/jars/*:/hadoop/etc/hadoop/ -Xmx1g org.apache.spark.deploy.worker.Worker --webui-port 8081 spark://hadoop:7077 ======================================== 20/03/30 22:42:29 INFO worker.Worker: Started daemon with process name: 24173@hadoop 20/03/30 22:42:29 INFO util.SignalUtils: Registered signal handler for TERM 20/03/30 22:42:29 INFO util.SignalUtils: Registered signal handler for HUP 20/03/30 22:42:29 INFO util.SignalUtils: Registered signal handler for INT 20/03/30 22:42:30 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable 20/03/30 22:42:30 INFO spark.SecurityManager: Changing view acls to: root 20/03/30 22:42:30 INFO spark.SecurityManager: Changing modify acls to: root 20/03/30 22:42:30 INFO spark.SecurityManager: Changing view acls groups to: 20/03/30 22:42:30 INFO spark.SecurityManager: Changing modify acls groups to: 20/03/30 22:42:30 INFO spark.SecurityManager: SecurityManager: authentication disabled; ui acls disabled; users with view permissions: Set(root); groups with view permissions: Set(); users with modify permiss ions: Set(root); groups with modify permissions: Set()20/03/30 22:42:30 INFO util.Utils: Successfully started service 'sparkWorker' on port 39384. 20/03/30 22:42:30 INFO worker.Worker: Starting Spark worker 192.168.1.66:39384 with 8 cores, 4.6 GB RAM 20/03/30 22:42:30 INFO worker.Worker: Running Spark version 2.4.4 20/03/30 22:42:30 INFO worker.Worker: Spark home: /hadoop/spark 20/03/30 22:42:31 INFO util.log: Logging initialized @1682ms 20/03/30 22:42:31 INFO server.Server: jetty-9.3.z-SNAPSHOT, build timestamp: unknown, git hash: unknown 20/03/30 22:42:31 INFO server.Server: Started @1758ms 20/03/30 22:42:31 INFO server.AbstractConnector: Started ServerConnector@3d598dff{HTTP/1.1,[http/1.1]}{0.0.0.0:8081} 20/03/30 22:42:31 INFO util.Utils: Successfully started service 'WorkerUI' on port 8081. 20/03/30 22:42:31 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@5099c1b0{/logPage,null,AVAILABLE,@Spark} 20/03/30 22:42:31 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@64348087{/logPage/json,null,AVAILABLE,@Spark} 20/03/30 22:42:31 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@46dcda1b{/,null,AVAILABLE,@Spark} 20/03/30 22:42:31 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@1617f7cc{/json,null,AVAILABLE,@Spark} 20/03/30 22:42:31 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@56e77d31{/static,null,AVAILABLE,@Spark} 20/03/30 22:42:31 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@643123b6{/log,null,AVAILABLE,@Spark} 20/03/30 22:42:31 INFO ui.WorkerWebUI: Bound WorkerWebUI to 0.0.0.0, and started at http://hadoop:8081 20/03/30 22:42:31 INFO worker.Worker: Connecting to master hadoop:7077... 20/03/30 22:42:31 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@1cf30aaa{/metrics/json,null,AVAILABLE,@Spark} 20/03/30 22:42:31 INFO client.TransportClientFactory: Successfully created connection to hadoop/192.168.1.66:7077 after 36 ms (0 ms spent in bootstraps) 20/03/30 22:42:31 INFO worker.Worker: Successfully registered with master spark://hadoop:7077 启动没问题,访问Webui:http://192.168.1.66:8080/五、运行Spark提供的计算圆周率的示例程序这里只是简单的用local模式运行一个计算圆周率的Demo。按照下面的步骤来操作。[root@hadoop sbin]# cd /hadoop/spark/ [root@hadoop spark]# ./bin/spark-submit --class org.apache.spark.examples.SparkPi --master local examples/jars/spark-examples_2.11-2.4.4.jar 20/03/30 22:45:59 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable 20/03/30 22:45:59 INFO spark.SparkContext: Running Spark version 2.4.4 20/03/30 22:45:59 INFO spark.SparkContext: Submitted application: Spark Pi 20/03/30 22:45:59 INFO spark.SecurityManager: Changing view acls to: root 20/03/30 22:45:59 INFO spark.SecurityManager: Changing modify acls to: root 20/03/30 22:45:59 INFO spark.SecurityManager: Changing view acls groups to: 20/03/30 22:45:59 INFO spark.SecurityManager: Changing modify acls groups to: 20/03/30 22:45:59 INFO spark.SecurityManager: SecurityManager: authentication disabled; ui acls disabled; users with view permissions: Set(root); groups with view permissions: Set(); users with modify permiss ions: Set(root); groups with modify permissions: Set()20/03/30 22:45:59 INFO util.Utils: Successfully started service 'sparkDriver' on port 39352. 20/03/30 22:45:59 INFO spark.SparkEnv: Registering MapOutputTracker 20/03/30 22:45:59 INFO spark.SparkEnv: Registering BlockManagerMaster 20/03/30 22:45:59 INFO storage.BlockManagerMasterEndpoint: Using org.apache.spark.storage.DefaultTopologyMapper for getting topology information 20/03/30 22:45:59 INFO storage.BlockManagerMasterEndpoint: BlockManagerMasterEndpoint up 20/03/30 22:45:59 INFO storage.DiskBlockManager: Created local directory at /tmp/blockmgr-63bf7c92-8908-4784-8e16-4c6ef0c93dc0 20/03/30 22:45:59 INFO memory.MemoryStore: MemoryStore started with capacity 366.3 MB 20/03/30 22:45:59 INFO spark.SparkEnv: Registering OutputCommitCoordinator 20/03/30 22:46:00 INFO util.log: Logging initialized @2066ms 20/03/30 22:46:00 INFO server.Server: jetty-9.3.z-SNAPSHOT, build timestamp: unknown, git hash: unknown 20/03/30 22:46:00 INFO server.Server: Started @2179ms 20/03/30 22:46:00 INFO server.AbstractConnector: Started ServerConnector@3abd581e{HTTP/1.1,[http/1.1]}{0.0.0.0:4040} 20/03/30 22:46:00 INFO util.Utils: Successfully started service 'SparkUI' on port 4040. 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@36dce7ed{/jobs,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@6a1ebcff{/jobs/json,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@19868320{/jobs/job,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@c20be82{/jobs/job/json,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@13c612bd{/stages,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@3ef41c66{/stages/json,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@6b739528{/stages/stage,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@5f577419{/stages/stage/json,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@28fa700e{/stages/pool,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@3d526ad9{/stages/pool/json,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@e041f0c{/storage,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@6a175569{/storage/json,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@11963225{/storage/rdd,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@3f3c966c{/storage/rdd/json,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@11ee02f8{/environment,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@4102b1b1{/environment/json,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@61a5b4ae{/executors,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@3a71c100{/executors/json,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@5b69fd74{/executors/threadDump,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@f325091{/executors/threadDump/json,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@437e951d{/static,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@467f77a5{/,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@1bb9aa43{/api,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@66b72664{/jobs/job/kill,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@7a34b7b8{/stages/stage/kill,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO ui.SparkUI: Bound SparkUI to 0.0.0.0, and started at http://hadoop:4040 20/03/30 22:46:00 INFO spark.SparkContext: Added JAR file:/hadoop/spark/examples/jars/spark-examples_2.11-2.4.4.jar at spark://hadoop:39352/jars/spark-examples_2.11-2.4.4.jar with timestamp 1585579560287 20/03/30 22:46:00 INFO executor.Executor: Starting executor ID driver on host localhost 20/03/30 22:46:00 INFO util.Utils: Successfully started service 'org.apache.spark.network.netty.NettyBlockTransferService' on port 38875. 20/03/30 22:46:00 INFO netty.NettyBlockTransferService: Server created on hadoop:38875 20/03/30 22:46:00 INFO storage.BlockManager: Using org.apache.spark.storage.RandomBlockReplicationPolicy for block replication policy 20/03/30 22:46:00 INFO storage.BlockManagerMaster: Registering BlockManager BlockManagerId(driver, hadoop, 38875, None) 20/03/30 22:46:00 INFO storage.BlockManagerMasterEndpoint: Registering block manager hadoop:38875 with 366.3 MB RAM, BlockManagerId(driver, hadoop, 38875, None) 20/03/30 22:46:00 INFO storage.BlockManagerMaster: Registered BlockManager BlockManagerId(driver, hadoop, 38875, None) 20/03/30 22:46:00 INFO storage.BlockManager: Initialized BlockManager: BlockManagerId(driver, hadoop, 38875, None) 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@6f8e0cee{/metrics/json,null,AVAILABLE,@Spark} 20/03/30 22:46:01 INFO spark.SparkContext: Starting job: reduce at SparkPi.scala:38 20/03/30 22:46:01 INFO scheduler.DAGScheduler: Got job 0 (reduce at SparkPi.scala:38) with 2 output partitions 20/03/30 22:46:01 INFO scheduler.DAGScheduler: Final stage: ResultStage 0 (reduce at SparkPi.scala:38) 20/03/30 22:46:01 INFO scheduler.DAGScheduler: Parents of final stage: List() 20/03/30 22:46:01 INFO scheduler.DAGScheduler: Missing parents: List() 20/03/30 22:46:01 INFO scheduler.DAGScheduler: Submitting ResultStage 0 (MapPartitionsRDD[1] at map at SparkPi.scala:34), which has no missing parents 20/03/30 22:46:01 INFO memory.MemoryStore: Block broadcast_0 stored as values in memory (estimated size 1936.0 B, free 366.3 MB) 20/03/30 22:46:01 INFO memory.MemoryStore: Block broadcast_0_piece0 stored as bytes in memory (estimated size 1256.0 B, free 366.3 MB) 20/03/30 22:46:01 INFO storage.BlockManagerInfo: Added broadcast_0_piece0 in memory on hadoop:38875 (size: 1256.0 B, free: 366.3 MB) 20/03/30 22:46:01 INFO spark.SparkContext: Created broadcast 0 from broadcast at DAGScheduler.scala:1161 20/03/30 22:46:01 INFO scheduler.DAGScheduler: Submitting 2 missing tasks from ResultStage 0 (MapPartitionsRDD[1] at map at SparkPi.scala:34) (first 15 tasks are for partitions Vector(0, 1)) 20/03/30 22:46:01 INFO scheduler.TaskSchedulerImpl: Adding task set 0.0 with 2 tasks 20/03/30 22:46:01 INFO scheduler.TaskSetManager: Starting task 0.0 in stage 0.0 (TID 0, localhost, executor driver, partition 0, PROCESS_LOCAL, 7866 bytes) 20/03/30 22:46:01 INFO executor.Executor: Running task 0.0 in stage 0.0 (TID 0) 20/03/30 22:46:01 INFO executor.Executor: Fetching spark://hadoop:39352/jars/spark-examples_2.11-2.4.4.jar with timestamp 1585579560287 20/03/30 22:46:01 INFO client.TransportClientFactory: Successfully created connection to hadoop/192.168.1.66:39352 after 45 ms (0 ms spent in bootstraps) 20/03/30 22:46:01 INFO util.Utils: Fetching spark://hadoop:39352/jars/spark-examples_2.11-2.4.4.jar to /tmp/spark-9e0481a2-756b-436f-bc74-dd42fb5ea839/userFiles-86767584-1e78-45f2-a9ed-8ac4360ab170/fetchFileTem p2974211155688432975.tmp20/03/30 22:46:01 INFO executor.Executor: Adding file:/tmp/spark-9e0481a2-756b-436f-bc74-dd42fb5ea839/userFiles-86767584-1e78-45f2-a9ed-8ac4360ab170/spark-examples_2.11-2.4.4.jar to class loader 20/03/30 22:46:01 INFO executor.Executor: Finished task 0.0 in stage 0.0 (TID 0). 824 bytes result sent to driver 20/03/30 22:46:01 INFO scheduler.TaskSetManager: Starting task 1.0 in stage 0.0 (TID 1, localhost, executor driver, partition 1, PROCESS_LOCAL, 7866 bytes) 20/03/30 22:46:01 INFO executor.Executor: Running task 1.0 in stage 0.0 (TID 1) 20/03/30 22:46:01 INFO scheduler.TaskSetManager: Finished task 0.0 in stage 0.0 (TID 0) in 308 ms on localhost (executor driver) (1/2) 20/03/30 22:46:01 INFO executor.Executor: Finished task 1.0 in stage 0.0 (TID 1). 824 bytes result sent to driver 20/03/30 22:46:01 INFO scheduler.TaskSetManager: Finished task 1.0 in stage 0.0 (TID 1) in 31 ms on localhost (executor driver) (2/2) 20/03/30 22:46:01 INFO scheduler.TaskSchedulerImpl: Removed TaskSet 0.0, whose tasks have all completed, from pool 20/03/30 22:46:01 INFO scheduler.DAGScheduler: ResultStage 0 (reduce at SparkPi.scala:38) finished in 0.606 s 20/03/30 22:46:01 INFO scheduler.DAGScheduler: Job 0 finished: reduce at SparkPi.scala:38, took 0.703911 s Pi is roughly 3.1386756933784667 20/03/30 22:46:01 INFO server.AbstractConnector: Stopped Spark@3abd581e{HTTP/1.1,[http/1.1]}{0.0.0.0:4040} 20/03/30 22:46:01 INFO ui.SparkUI: Stopped Spark web UI at http://hadoop:4040 20/03/30 22:46:01 INFO spark.MapOutputTrackerMasterEndpoint: MapOutputTrackerMasterEndpoint stopped! 20/03/30 22:46:01 INFO memory.MemoryStore: MemoryStore cleared 20/03/30 22:46:01 INFO storage.BlockManager: BlockManager stopped 20/03/30 22:46:01 INFO storage.BlockManagerMaster: BlockManagerMaster stopped 20/03/30 22:46:01 INFO scheduler.OutputCommitCoordinator$OutputCommitCoordinatorEndpoint: OutputCommitCoordinator stopped! 20/03/30 22:46:01 INFO spark.SparkContext: Successfully stopped SparkContext 20/03/30 22:46:01 INFO util.ShutdownHookManager: Shutdown hook called 20/03/30 22:46:01 INFO util.ShutdownHookManager: Deleting directory /tmp/spark-e019897d-3160-4bb1-ab59-f391e32ec47a 20/03/30 22:46:01 INFO util.ShutdownHookManager: Deleting directory /tmp/spark-9e0481a2-756b-436f-bc74-dd42fb5ea839可以看到输出:Pi is roughly 3.137355686778434已经打印出了圆周率。上面只是使用了单机本地模式调用Demo,使用集群模式运行Demo,请继续看。六、用yarn-cluster模式执行计算程序进入到Spark的安装目录,执行命令,用yarn-cluster模式运行计算圆周率的Demo:[root@hadoop spark]# ./bin/spark-submit --class org.apache.spark.examples.SparkPi --master yarn-cluster examples/jars/spark-examples_2.11-2.4.4.jar Warning: Master yarn-cluster is deprecated since 2.0. Please use master "yarn" with specified deploy mode instead. 20/03/30 22:47:47 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable 20/03/30 22:47:48 INFO client.RMProxy: Connecting to ResourceManager at /192.168.1.66:8032 20/03/30 22:47:48 INFO yarn.Client: Requesting a new application from cluster with 1 NodeManagers 20/03/30 22:47:48 INFO yarn.Client: Verifying our application has not requested more than the maximum memory capability of the cluster (8192 MB per container) 20/03/30 22:47:48 INFO yarn.Client: Will allocate AM container, with 1408 MB memory including 384 MB overhead 20/03/30 22:47:48 INFO yarn.Client: Setting up container launch context for our AM 20/03/30 22:47:48 INFO yarn.Client: Setting up the launch environment for our AM container 20/03/30 22:47:48 INFO yarn.Client: Preparing resources for our AM container 20/03/30 22:47:48 WARN yarn.Client: Neither spark.yarn.jars nor spark.yarn.archive is set, falling back to uploading libraries under SPARK_HOME. 20/03/30 22:47:51 INFO yarn.Client: Uploading resource file:/tmp/spark-d554f7cd-c7d4-4dfa-bc86-11a340925db6/__spark_libs__3389017089811757919.zip -> hdfs://192.168.1.66:9000/user/root/.sparkStaging/application_ 1585579247054_0001/__spark_libs__3389017089811757919.zip20/03/30 22:47:59 INFO yarn.Client: Uploading resource file:/hadoop/spark/examples/jars/spark-examples_2.11-2.4.4.jar -> hdfs://192.168.1.66:9000/user/root/.sparkStaging/application_1585579247054_0001/spark-exa mples_2.11-2.4.4.jar20/03/30 22:47:59 INFO yarn.Client: Uploading resource file:/tmp/spark-d554f7cd-c7d4-4dfa-bc86-11a340925db6/__spark_conf__559264393694354636.zip -> hdfs://192.168.1.66:9000/user/root/.sparkStaging/application_1 585579247054_0001/__spark_conf__.zip20/03/30 22:47:59 INFO spark.SecurityManager: Changing view acls to: root 20/03/30 22:47:59 INFO spark.SecurityManager: Changing modify acls to: root 20/03/30 22:47:59 INFO spark.SecurityManager: Changing view acls groups to: 20/03/30 22:47:59 INFO spark.SecurityManager: Changing modify acls groups to: 20/03/30 22:47:59 INFO spark.SecurityManager: SecurityManager: authentication disabled; ui acls disabled; users with view permissions: Set(root); groups with view permissions: Set(); users with modify permiss ions: Set(root); groups with modify permissions: Set()20/03/30 22:48:01 INFO yarn.Client: Submitting application application_1585579247054_0001 to ResourceManager 20/03/30 22:48:01 INFO impl.YarnClientImpl: Submitted application application_1585579247054_0001 20/03/30 22:48:02 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:02 INFO yarn.Client: client token: N/A diagnostics: N/A ApplicationMaster host: N/A ApplicationMaster RPC port: -1 queue: default start time: 1585579681188 final status: UNDEFINED tracking URL: http://hadoop:8088/proxy/application_1585579247054_0001/ user: root 20/03/30 22:48:03 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:04 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:05 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:06 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:07 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:08 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:09 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:11 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:12 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:13 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:14 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:15 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:16 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:17 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:19 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:20 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:21 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:22 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:23 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:24 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:25 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:26 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:27 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:28 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:29 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:29 INFO yarn.Client: client token: N/A diagnostics: N/A ApplicationMaster host: hadoop ApplicationMaster RPC port: 37844 queue: default start time: 1585579681188 final status: UNDEFINED tracking URL: http://hadoop:8088/proxy/application_1585579247054_0001/ user: root 20/03/30 22:48:30 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:31 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:32 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:33 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:34 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:35 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:36 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:37 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:38 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:39 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:40 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:41 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:42 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:43 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:44 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:45 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:46 INFO yarn.Client: Application report for application_1585579247054_0001 (state: FINISHED) 20/03/30 22:48:46 INFO yarn.Client: client token: N/A diagnostics: N/A ApplicationMaster host: hadoop ApplicationMaster RPC port: 37844 queue: default start time: 1585579681188 final status: SUCCEEDED tracking URL: http://hadoop:8088/proxy/application_1585579247054_0001/ user: root 20/03/30 22:48:46 INFO util.ShutdownHookManager: Shutdown hook called 20/03/30 22:48:46 INFO util.ShutdownHookManager: Deleting directory /tmp/spark-4c243c24-9489-4c8a-a1bc-a6a9780615d6 20/03/30 22:48:46 INFO util.ShutdownHookManager: Deleting directory /tmp/spark-d554f7cd-c7d4-4dfa-bc86-11a340925db6 注意,使用yarn-cluster模式计算,结果没有输出在控制台,结果写在了Hadoop集群的日志中,如何查看计算结果?注意到刚才的输出中有地址: tracking URL: http://hadoop:8088/proxy/application_1585579247054_0001/ 进去看看: 再点进logs: 查看stdout内容: 圆周率结果已经打印出来了。这里再给出几个常用命令:启动spark ./sbin/start-all.sh 启动Hadoop以**及Spark: ./starths.sh 停止命令改成stop七、配置spark读取hive表由于在hive里面操作表是通过mapreduce的方式,效率较低,本文主要描述如何通过spark读取hive表到内存进行计算。第一步,先把$HIVE_HOME/conf/hive-site.xml放入$SPARK_HOME/conf内,使得spark能够获取hive配置[root@hadoop spark]# pwd /hadoop/spark [root@hadoop spark]# cp $HIVE_HOME/conf/hive-site.xml conf/ [root@hadoop spark]# chmod 777 conf/hive-site.xml [root@hadoop spark]# cp /hadoop/hive/lib/mysql-connector-java-5.1.47.jar jars/通过spark-shell进入交互界面[root@hadoop spark]# /hadoop/spark/bin/spark-shell 20/03/31 10:31:39 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable Setting default log level to "WARN". To adjust logging level use sc.setLogLevel(newLevel). For SparkR, use setLogLevel(newLevel). 20/03/31 10:32:41 WARN util.Utils: Service 'SparkUI' could not bind on port 4040. Attempting port 4041. 20/03/31 10:32:41 WARN util.Utils: Service 'SparkUI' could not bind on port 4041. Attempting port 4042. Spark context Web UI available at http://hadoop:4042 Spark context available as 'sc' (master = local[*], app id = local-1585621962060). Spark session available as 'spark'. Welcome to ____ __ / __/__ ___ _____/ /__ _\ \/ _ \/ _ `/ __/ '_/ /___/ .__/\_,_/_/ /_/\_\ version 2.4.4 /_/ Using Scala version 2.11.12 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_151) Type in expressions to have them evaluated. Type :help for more information. scala> import org.apache.spark.sql.hive.HiveContext import org.apache.spark.sql.hive.HiveContext scala> import org.apache.spark.sql.functions._ import org.apache.spark.sql.functions._ scala> val hiveContext = new HiveContext(sc) warning: there was one deprecation warning; re-run with -deprecation for details hiveContext: org.apache.spark.sql.hive.HiveContext = org.apache.spark.sql.hive.HiveContext@62966c9f scala> hiveContext.sql("show databases").show() 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.client.capability.check does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.aggregate.stats.false.positive.probability does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.broker.address.default does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.orc.time.counters does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.tez.task.scale.memory.reserve-fraction.min does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.orc.splits.ms.footer.cache.ppd.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.event.message.factory does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.metrics.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.tez.hs2.user.access does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.storage.storageDirectory does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.am.liveness.connection.timeout.ms does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.tez.dynamic.semijoin.reduction.threshold does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.thrift.client.connect.retry.limit does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.xmx.headroom does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.tez.dynamic.semijoin.reduction does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.allocator.direct does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.auto.enforce.stats does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.client.consistent.splits does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.tez.session.lifetime does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.timedout.txn.reaper.start does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.cache.ttl does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.management.acl does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.delegation.token.lifetime does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.authentication.ldap.guidKey does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.ats.hook.queue.capacity does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.strict.checks.large.query does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.tez.bigtable.minsize.semijoin.reduction does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.allocator.alloc.min does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.thrift.client.user does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.encode.alloc.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.wait.queue.comparator.class.name does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.output.service.port does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.orc.cache.use.soft.references does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.encode.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.tez.task.scale.memory.reserve.fraction.max does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.task.communicator.listener.thread-count does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.tez.container.max.java.heap.fraction does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.stats.column.autogather does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.am.liveness.heartbeat.interval.ms does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.decoding.metrics.percentiles.intervals does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.groupby.position.alias does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.txn.store.impl does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.spark.use.groupby.shuffle does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.object.cache.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.parallel.ops.in.session does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.groupby.limit.extrastep does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.webui.use.ssl does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.service.metrics.file.location does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.thrift.client.retry.delay.seconds does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.materializedview.fileformat does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.num.file.cleaner.threads does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.test.fail.compaction does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.blobstore.use.blobstore.as.scratchdir does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.service.metrics.class does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.allocator.mmap.path does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.download.permanent.fns does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.webui.max.historic.queries does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.vectorized.execution.reducesink.new.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.compactor.max.num.delta does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.compactor.history.retention.attempted does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.webui.port does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.compactor.initiator.failed.compacts.threshold does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.service.metrics.reporter does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.output.service.max.pending.writes does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.execution.mode does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.enable.grace.join.in.llap does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.optimize.limittranspose does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.memory.mode does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.threadpool.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.select.threshold does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.scratchdir.lock does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.webui.use.spnego does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.service.metrics.file.frequency does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.hs2.coordinator.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.task.scheduler.timeout.seconds does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.optimize.filter.stats.reduction does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.exec.orc.base.delta.ratio does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.fastpath does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.clear.dangling.scratchdir does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.test.fail.heartbeater does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.file.cleanup.delay.seconds does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.management.rpc.port does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.mapjoin.hybridgrace.bloomfilter does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.auto.enforce.tree does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.stats.ndv.tuner does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.direct.sql.max.query.length does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.compactor.history.retention.failed does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.close.session.on.disconnect does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.optimize.ppd.windowing does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.initial.metadata.count.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.webui.host does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.orc.splits.ms.footer.cache.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.optimize.point.lookup.min does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.file.metadata.threads does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.service.refresh.interval.sec does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.auto.max.output.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.driver.parallel.compilation does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.remote.token.requires.signing does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.tez.bucket.pruning does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.cache.allow.synthetic.fileid does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.hash.table.inflation.factor does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.aggr.stats.hbase.ttl does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.auto.enforce.vectorized does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.writeset.reaper.interval does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.vectorized.use.vector.serde.deserialize does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.order.columnalignment does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.output.service.send.buffer.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.exec.schema.evolution does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.direct.sql.max.elements.values.clause does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.llap.concurrent.queries does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.auto.allow.uber does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.indexer.partition.size.max does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.auto.auth does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.orc.splits.include.fileid does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.communicator.num.threads does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.orderby.position.alias does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.task.communicator.connection.sleep.between.retries.ms does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.aggregate.stats.max.partitions does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.service.metrics.hadoop2.component does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.yarn.shuffle.port does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.direct.sql.max.elements.in.clause does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.passiveWaitTimeMs does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.load.dynamic.partitions.thread does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.indexer.segments.granularity does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.thrift.http.response.header.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.conf.internal.variable.list does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.optimize.limittranspose.reductionpercentage does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.repl.cm.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.thrift.client.retry.limit does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.thrift.resultset.serialize.in.tasks does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.query.timeout.seconds does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.service.metrics.hadoop2.frequency does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.orc.splits.directory.batch.ms does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.cache.max.reader.wait does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.task.scheduler.node.reenable.max.timeout.ms does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.max.open.txns does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.auto.convert.sortmerge.join.reduce.side does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.zookeeper.publish.configs does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.auto.convert.join.hashtable.max.entries does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.tez.sessions.init.threads does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.authorization.storage.check.externaltable.drop does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.execution.mode does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.cbo.cnf.maxnodes does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.vectorized.adaptor.usage.mode does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.materializedview.rewriting does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.authentication.ldap.groupMembershipKey does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.catalog.cache.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.cbo.show.warnings does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.fshandler.threads does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.tez.max.bloom.filter.entries does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.metadata.fraction does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.materializedview.serde does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.task.scheduler.wait.queue.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.aggr.stats.cache.entries does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.txn.operational.properties does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.aggr.stats.memory.ttl does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.rpc.port does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.nonvector.wrapper.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.aggregate.stats.cache.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.vectorized.use.vectorized.input.format does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.optimize.cte.materialize.threshold does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.cache.clean.until does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.optimize.semijoin.conversion does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.port does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.spark.dynamic.partition.pruning does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.metrics.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.repl.rootdir does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.limit.partition.request does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.async.log.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.logger does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.allow.udf.load.on.demand does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.cli.tez.session.async does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.tez.bloom.filter.factor does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.am-reporter.max.threads does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.spark.use.file.size.for.mapjoin does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.strict.checks.bucketing does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.tez.bucket.pruning.compat does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.webui.spnego.principal does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.task.preemption.metrics.intervals does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.shuffle.dir.watcher.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.allocator.arena.count does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.use.SSL does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.task.communicator.connection.timeout.ms does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.transpose.aggr.join does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.maxTries does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.spark.dynamic.partition.pruning.max.data.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.metadata.base does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.aggr.stats.invalidator.frequency does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.use.lrfu does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.allocator.mmap does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.coordinator.address.default does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.thrift.resultset.max.fetch.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.conf.hidden.list does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.io.sarg.cache.max.weight.mb does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.clear.dangling.scratchdir.interval does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.sleep.time does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.vectorized.use.row.serde.deserialize does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.compile.lock.timeout does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.timedout.txn.reaper.interval does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.aggregate.stats.max.variance does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.lrfu.lambda does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.metadata.db.type does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.output.stream.timeout does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.transactional.events.mem does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.thrift.resultset.default.fetch.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.repl.cm.retain does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.merge.cardinality.check does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.authentication.ldap.groupClassKey does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.optimize.point.lookup does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.allow.permanent.fns does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.web.ssl does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.txn.manager.dump.lock.state.on.acquire.timeout does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.compactor.history.retention.succeeded does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.use.fileid.path does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.encode.slice.row.count does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.mapjoin.optimized.hashtable.probe.percent does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.select.distribute does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.am.use.fqdn does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.task.scheduler.node.reenable.min.timeout.ms does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.validate.acls does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.support.special.characters.tablename does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.mv.files.thread does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.skip.compile.udf.check does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.encode.vector.serde.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.repl.cm.interval does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.sleep.interval.between.start.attempts does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.yarn.container.mb does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.http.read.timeout does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.blobstore.optimizations.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.orc.gap.cache does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.optimize.dynamic.partition.hashjoin does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.exec.copyfile.maxnumfiles does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.encode.formats does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.http.numConnection does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.task.scheduler.enable.preemption does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.num.executors does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.cache.max.full does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.connection.class does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.tez.sessions.custom.queue.allowed does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.encode.slice.lrr does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.thrift.client.password does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.cache.max.writer.wait does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.thrift.http.request.header.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.webui.max.threads does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.optimize.limittranspose.reductiontuples does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.test.rollbacktxn does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.task.scheduler.num.schedulable.tasks.per.node does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.acl does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.memory.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.strict.checks.type.safety does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.async.exec.async.compile does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.auto.max.input.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.tez.enable.memory.manager does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.msck.repair.batch.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.blobstore.supported.schemes does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.orc.splits.allow.synthetic.fileid does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.stats.filter.in.factor does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.spark.use.op.stats does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.exec.input.listing.max.threads does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.tez.session.lifetime.jitter does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.web.port does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.strict.checks.cartesian.product does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.rpc.num.handlers does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.vcpus.per.instance does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.count.open.txns.interval does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.tez.min.bloom.filter.entries does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.optimize.partition.columns.separate does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.orc.cache.stripe.details.mem.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.txn.heartbeat.threadpool.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.task.scheduler.locality.delay does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.repl.cmrootdir does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.task.scheduler.node.disable.backoff.factor does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.am.liveness.connection.sleep.between.retries.ms does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.spark.exec.inplace.progress does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.working.directory does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.memory.per.instance.mb does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.msck.path.validation does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.tez.task.scale.memory.reserve.fraction does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.merge.nway.joins does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.compactor.history.reaper.interval does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.txn.strict.locking.mode does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.encode.vector.serde.async.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.tez.input.generate.consistent.splits does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.in.place.progress does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.indexer.memory.rownum.max does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.xsrf.filter.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.allocator.alloc.max does not exist +------------+ |databaseName| +------------+ | default| | hadoop| +------------+ scala> hiveContext.sql("show tables").show() +--------+--------------------+-----------+ |database| tableName|isTemporary| +--------+--------------------+-----------+ | default| aa| false| | default| bb| false| | default| dd| false| | default| kylin_account| false| | default| kylin_cal_dt| false| | default|kylin_category_gr...| false| | default| kylin_country| false| | default|kylin_intermediat...| false| | default|kylin_intermediat...| false| | default| kylin_sales| false| | default| test| false| | default| test_null| false| +--------+--------------------+-----------+可以看到已经查询到结果了,但是为啥上面报了一堆WARN 。比如:WARN conf.HiveConf: HiveConf of name hive.llap.skip.compile.udf.check does not exis hive-site配置文件删除掉:<property> <name>hive.llap.skip.compile.udf.check</name> <value>false</value> <description> Whether to skip the compile-time check for non-built-in UDFs when deciding whether to execute tasks in LLAP. Skipping the check allows executing UDFs from pre-localized jars in LLAP; if the jars are not pre-localized, the UDFs will simply fail to load. </description> </property>再次登录执行警告就消失了。八、配置Hudi8.1、检阅官方文档重点地方先来看下官方文档getstart首页:我之前装的hadoop环境是2.7版本的,前面之所以装spark2.4.4就是因为目前官方案例就是用的hadoop2.7+spark2.4.4,而且虽然现在hudi、spark是支持scala2.11.x/2.12.x,但是官网这里也是用的2.11,我这里为了保持和hudi官方以及spark2.4.4(Using Scala version 2.11.12 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_151))一致,也就装的2.11.12版本的scala。因为目前为止,Hudi已经出了0.5.2版本,但是Hudi官方仍然用的0.5.1的做示例,接下来,先切换到hudi0.5.1的发布文档:点击查看上面发布文档讲的意思是:版本升级 将Spark版本从2.1.0升级到2.4.4 将Avro版本从1.7.7升级到1.8.2将Parquet版本从1.8.1升级到1.10.1将Kafka版本从0.8.2.1升级到2.0.0,这是由于将spark-streaming-kafkaartifact从0.8_2.11升级到0.10_2.11/2.12间接升级 重要:Hudi0.5.1版本需要将spark的版本升级到2.4+Hudi现在支持Scala 2.11和2.12,可以参考Scala 2.12构建来使用Scala 2.12来构建Hudi,另外,hudi-spark, hudi-utilities, hudi-spark-bundle andhudi-utilities-bundle包名现已经对应变更为 hudi-spark_{scala_version},hudi-spark_{scala_version}, hudi-utilities_{scala_version},hudi-spark-bundle_{scala_version}和hudi-utilities-bundle_{scala_version}. 注意这里的scala_version为2.11或2.12。在0.5.1版本中,对于timeline元数据的操作不再使用重命名方式,这个特性在创建Hudi表时默认是打开的。对于已存在的表,这个特性默认是关闭的,在已存在表开启这个特性之前,请参考这部分(https://hudi.apache.org/docs/deployment.html#upgrading)。若开启新的Huditimeline布局方式(layout),即避免重命名,可设置写配置项hoodie.timeline.layout.version=1。当然,你也可以在CLI中使用repairoverwrite-hoodie-props命令来添加hoodie.timeline.layout.version=1至hoodie.properties文件。注意,无论使用哪种方式,在升级Writer之前请先升级HudiReader(查询引擎)版本至0.5.1版本。 CLI支持repairoverwrite-hoodie-props来指定文件来重写表的hoodie.properties文件,可以使用此命令来的更新表名或者使用新的timeline布局方式。注意当写hoodie.properties文件时(毫秒),一些查询将会暂时失败,失败后重新运行即可。DeltaStreamer用来指定表类型的参数从--storage-type变更为了--table-type,可以参考wiki来了解更多的最新变化的术语。配置Kafka ResetOffset策略的值变化了。枚举值从LARGEST变更为LATEST,SMALLEST变更为EARLIEST,对应DeltaStreamer中的配置项为auto.offset.reset。当使用spark-shell来了解Hudi时,需要提供额外的--packagesorg.apache.spark:spark-avro_2.11:2.4.4,可以参考quickstart了解更多细节。 Keygenerator(键生成器)移动到了单独的包下org.apache.hudi.keygen,如果你使用重载键生成器类(对应配置项:hoodie.datasource.write.keygenerator.class),请确保类的全路径名也对应进行变更。Hive同步工具将会为MOR注册带有_ro后缀的RO表,所以查询也请带_ro后缀,你可以使用--skip-ro-suffix配置项来保持旧的表名,即同步时不添加_ro后缀。0.5.1版本中,供presto/hive查询引擎使用的hudi-hadoop-mr-bundle包shaded了avro包,以便支持realtimequeries(实时查询)。Hudi支持可插拔的记录合并逻辑,用户只需自定义实现HoodieRecordPayload。如果你使用这个特性,你需要在你的代码中relocateavro依赖,这样可以确保你代码的行为和Hudi保持一致,你可以使用如下方式来relocation。 org.apache.avro.org.apache.hudi.org.apache.avro. DeltaStreamer更好的支持Delete,可参考blog了解更多细节。DeltaStreamer支持AWS Database Migration Service(DMS) ,可参考blog了解更多细节。支持DynamicBloomFilter(动态布隆过滤器),默认是关闭的,可以使用索引配置项hoodie.bloom.index.filter.type=DYNAMIC_V0来开启。HDFSParquetImporter支持bulkinsert,可配置--command为bulkinsert。 支持AWS WASB和WASBS云存储。8.2、错误的安装尝试好了,看完了发布文档,而且已经定下了我们的使用版本关系,那么直接切换到Hudi0.5.2最新版本的官方文档:点此跳转因为之前没用过spark和hudi,在看到hudi官网的第一眼时候,首先想到的是先下载一个hudi0.5.1对应的应用程序,然后再进行部署,部署好了之后再执行上面官网给的命令代码,比如下面我之前做的错误示范:由于官方目前案例都是用的0.5.1,所以我也下载这个版本: https://downloads.apache.org/incubator/hudi/0.5.1-incubating/hudi-0.5.1-incubating.src.tgz 将下载好的安装包,上传到/hadoop/spark目录下并解压: [root@hadoop spark]# ls bin conf data examples hudi-0.5.1-incubating.src.tgz jars kubernetes LICENSE licenses logs NOTICE python R README.md RELEASE sbin spark-2.4.4-bin-hadoop2.7 work yarn [root@hadoop spark]# tar -zxvf hudi-0.5.1-incubating.src.tgz [root@hadoop spark]# ls bin conf data examples hudi-0.5.1-incubating hudi-0.5.1-incubating.src.tgz jars kubernetes LICENSE licenses logs NOTICE python R README.md RELEASE sbin spark-2.4.4-bin-hadoop2.7 work yarn [root@hadoop spark]# rm -rf *tgz [root@hadoop ~]# /hadoop/spark/bin/spark-shell \ > --packages org.apache.hudi:hudi-spark-bundle_2.11:0.5.1-incubating,org.apache.spark:spark-avro_2.11:2.4.4 \ > --conf 'spark.serializer=org.apache.spark.serializer.KryoSerializer' Ivy Default Cache set to: /root/.ivy2/cache The jars for the packages stored in: /root/.ivy2/jars :: loading settings :: url = jar:file:/hadoop/spark/jars/ivy-2.4.0.jar!/org/apache/ivy/core/settings/ivysettings.xml org.apache.hudi#hudi-spark-bundle_2.11 added as a dependency org.apache.spark#spark-avro_2.11 added as a dependency :: resolving dependencies :: org.apache.spark#spark-submit-parent-5717aa3e-7bfb-42c4-aadd-2a884f3521d5;1.0 confs: [default] You probably access the destination server through a proxy server that is not well configured. You probably access the destination server through a proxy server that is not well configured. You probably access the destination server through a proxy server that is not well configured. You probably access the destination server through a proxy server that is not well configured. You probably access the destination server through a proxy server that is not well configured. You probably access the destination server through a proxy server that is not well configured. You probably access the destination server through a proxy server that is not well configured. You probably access the destination server through a proxy server that is not well configured. :: resolution report :: resolve 454ms :: artifacts dl 1ms :: modules in use: --------------------------------------------------------------------- | | modules || artifacts | | conf | number| search|dwnlded|evicted|| number|dwnlded| --------------------------------------------------------------------- | default | 2 | 0 | 0 | 0 || 0 | 0 | --------------------------------------------------------------------- :: problems summary :: :::: WARNINGS Host repo1.maven.org not found. url=https://repo1.maven.org/maven2/org/apache/hudi/hudi-spark-bundle_2.11/0.5.1-incubating/hudi-spark-bundle_2.11-0.5.1-incubating.pom Host repo1.maven.org not found. url=https://repo1.maven.org/maven2/org/apache/hudi/hudi-spark-bundle_2.11/0.5.1-incubating/hudi-spark-bundle_2.11-0.5.1-incubating.jar 。。。。。。。。。。 :::::::::::::::::::::::::::::::::::::::::::::: :: UNRESOLVED DEPENDENCIES :: :::::::::::::::::::::::::::::::::::::::::::::: :: org.apache.hudi#hudi-spark-bundle_2.11;0.5.1-incubating: not found :: org.apache.spark#spark-avro_2.11;2.4.4: not found :::::::::::::::::::::::::::::::::::::::::::::: :: USE VERBOSE OR DEBUG MESSAGE LEVEL FOR MORE DETAILS Exception in thread "main" java.lang.RuntimeException: [unresolved dependency: org.apache.hudi#hudi-spark-bundle_2.11;0.5.1-incubating: not found, unresolved dependency: org.apache.spark#spark-avro_2.11;2.4.4: not found] at org.apache.spark.deploy.SparkSubmitUtils$.resolveMavenCoordinates(SparkSubmit.scala:1302) at org.apache.spark.deploy.DependencyUtils$.resolveMavenDependencies(DependencyUtils.scala:54) at org.apache.spark.deploy.SparkSubmit.prepareSubmitEnvironment(SparkSubmit.scala:304) at org.apache.spark.deploy.SparkSubmit.org$apache$spark$deploy$SparkSubmit$$runMain(SparkSubmit.scala:774) at org.apache.spark.deploy.SparkSubmit.doRunMain$1(SparkSubmit.scala:161) at org.apache.spark.deploy.SparkSubmit.submit(SparkSubmit.scala:184) at org.apache.spark.deploy.SparkSubmit.doSubmit(SparkSubmit.scala:86) at org.apache.spark.deploy.SparkSubmit$$anon$2.doSubmit(SparkSubmit.scala:920) at org.apache.spark.deploy.SparkSubmit$.main(SparkSubmit.scala:929) at org.apache.spark.deploy.SparkSubmit.main(SparkSubmit.scala)8.3、正确的“安装部署”其实下载的这个应该算是个源码包,不是可直接运行的。而且spark-shell --packages是指定java包的maven地址,若不给定,则会使用该机器安装的maven默认源中下载此jar包,也就是说指定的这两个jar是需要自动下载的,我的虚拟环境一没设置外部网络,二没配置maven,这肯定会报错找不到jar包。官方这里的代码:--packages org.apache.hudi:hudi-spark-bundle_2.11:0.5.1-incubating,org.apache.spark:spark-avro_2.11:2.4.4说白了其实就是指定maven项目pom文件的依赖,翻了一下官方文档,找到了Hudi给的中央仓库地址,然后从中找到了官方案例代码中指定的两个包:直接拿出来,就是下面这两个:<dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-avro_2.11</artifactId> <version>2.4.4</version> </dependency> <dependency> <groupId>org.apache.hudi</groupId> <artifactId>hudi-spark-bundle_2.11</artifactId> <version>0.5.2-incubating</version> </dependency>好吧,那我就在这直接下载了这俩包,然后再继续看官方文档:这里说了我也可以通过自己构建hudi来快速开始, 并在spark-shell命令中使用--jars /packaging/hudi-spark-bundle/target/hudi-spark-bundle-..*-SNAPSHOT.jar, 而不是--packages org.apache.hudi:hudi-spark-bundle:0.5.2-incubating,看到这个提示,我在linux看了下 spark-shell的帮助:[root@hadoop external_jars]# /hadoop/spark/bin/spark-shell --help Usage: ./bin/spark-shell [options] Scala REPL options: -I <file> preload <file>, enforcing line-by-line interpretation Options: --master MASTER_URL spark://host:port, mesos://host:port, yarn, k8s://https://host:port, or local (Default: local[*]). --deploy-mode DEPLOY_MODE Whether to launch the driver program locally ("client") or on one of the worker machines inside the cluster ("cluster") (Default: client). --class CLASS_NAME Your application's main class (for Java / Scala apps). --name NAME A name of your application. --jars JARS Comma-separated list of jars to include on the driver and executor classpaths. --packages Comma-separated list of maven coordinates of jars to include on the driver and executor classpaths. Will search the local maven repo, then maven central and any additional remote repositories given by --repositories. The format for the coordinates should be groupId:artifactId:version. --exclude-packages Comma-separated list of groupId:artifactId, to exclude while resolving the dependencies provided in --packages to avoid dependency conflicts. --repositories Comma-separated list of additional remote repositories to search for the maven coordinates given with --packages. --py-files PY_FILES Comma-separated list of .zip, .egg, or .py files to place on the PYTHONPATH for Python apps. --files FILES Comma-separated list of files to be placed in the working directory of each executor. File paths of these files in executors can be accessed via SparkFiles.get(fileName). --conf PROP=VALUE Arbitrary Spark configuration property. --properties-file FILE Path to a file from which to load extra properties. If not specified, this will look for conf/spark-defaults.conf. --driver-memory MEM Memory for driver (e.g. 1000M, 2G) (Default: 1024M). --driver-java-options Extra Java options to pass to the driver. --driver-library-path Extra library path entries to pass to the driver. --driver-class-path Extra class path entries to pass to the driver. Note that jars added with --jars are automatically included in the classpath. --executor-memory MEM Memory per executor (e.g. 1000M, 2G) (Default: 1G). --proxy-user NAME User to impersonate when submitting the application. This argument does not work with --principal / --keytab. --help, -h Show this help message and exit. --verbose, -v Print additional debug output. --version, Print the version of current Spark. Cluster deploy mode only: --driver-cores NUM Number of cores used by the driver, only in cluster mode (Default: 1). Spark standalone or Mesos with cluster deploy mode only: --supervise If given, restarts the driver on failure. --kill SUBMISSION_ID If given, kills the driver specified. --status SUBMISSION_ID If given, requests the status of the driver specified. Spark standalone and Mesos only: --total-executor-cores NUM Total cores for all executors. Spark standalone and YARN only: --executor-cores NUM Number of cores per executor. (Default: 1 in YARN mode, or all available cores on the worker in standalone mode) YARN-only: --queue QUEUE_NAME The YARN queue to submit to (Default: "default"). --num-executors NUM Number of executors to launch (Default: 2). If dynamic allocation is enabled, the initial number of executors will be at least NUM. --archives ARCHIVES Comma separated list of archives to be extracted into the working directory of each executor. --principal PRINCIPAL Principal to be used to login to KDC, while running on secure HDFS. --keytab KEYTAB The full path to the file that contains the keytab for the principal specified above. This keytab will be copied to the node running the Application Master via the Secure Distributed Cache, for renewing the login tickets and the delegation tokens periodically.原来--jasrs是指定机器上存在的jar文件,接下来将前面下载的两个包上传到服务器:[root@hadoop spark]# mkdir external_jars [root@hadoop spark]# cd external_jars/ [root@hadoop external_jars]# pwd /hadoop/spark/external_jars 通过xftp上传jar到此目录 [root@hadoop external_jars]# ls hudi-spark-bundle_2.11-0.5.2-incubating.jar scala-library-2.11.12.jar spark-avro_2.11-2.4.4.jar spark-tags_2.11-2.4.4.jar unused-1.0.0.jar然后将官方案例代码:spark-2.4.4-bin-hadoop2.7/bin/spark-shell \ --packages org.apache.hudi:hudi-spark-bundle_2.11:0.5.2-incubating,org.apache.spark:spark-avro_2.11:2.4.4 \ --conf 'spark.serializer=org.apache.spark.serializer.KryoSerializer'修改为:[root@hadoop external_jars]# /hadoop/spark/bin/spark-shell --jars /hadoop/spark/external_jars/spark-avro_2.11-2.4.4.jar,/hadoop/spark/external_jars/hudi-spark-bundle_2.11-0.5.2-incubating.jar --conf 'spark.seri alizer=org.apache.spark.serializer.KryoSerializer '20/03/31 15:19:09 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable Setting default log level to "WARN". To adjust logging level use sc.setLogLevel(newLevel). For SparkR, use setLogLevel(newLevel). Spark context Web UI available at http://hadoop:4040 Spark context available as 'sc' (master = local[*], app id = local-1585639157881). Spark session available as 'spark'. Welcome to ____ __ / __/__ ___ _____/ /__ _\ \/ _ \/ _ `/ __/ '_/ /___/ .__/\_,_/_/ /_/\_\ version 2.4.4 /_/ Using Scala version 2.11.12 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_151) Type in expressions to have them evaluated. Type :help for more information. scala> OK!!!没有报错了,接下来开始尝试进行增删改查操作。8.4、Hudi增删改查基于上面步骤8.4.1、设置表名、基本路径和数据生成器来生成记录scala> import org.apache.hudi.QuickstartUtils._ import org.apache.hudi.QuickstartUtils._ scala> import scala.collection.JavaConversions._ import scala.collection.JavaConversions._ scala> import org.apache.spark.sql.SaveMode._ import org.apache.spark.sql.SaveMode._ scala> import org.apache.hudi.DataSourceReadOptions._ import org.apache.hudi.DataSourceReadOptions._ scala> import org.apache.hudi.DataSourceWriteOptions._ import org.apache.hudi.DataSourceWriteOptions._ scala> import org.apache.hudi.config.HoodieWriteConfig._ import org.apache.hudi.config.HoodieWriteConfig._ scala> val tableName = "hudi_cow_table" tableName: String = hudi_cow_table scala> val basePath = "file:///tmp/hudi_cow_table" basePath: String = file:///tmp/hudi_cow_table scala> val dataGen = new DataGenerator dataGen: org.apache.hudi.QuickstartUtils.DataGenerator = org.apache.hudi.QuickstartUtils$DataGenerator@4bf6bc2d数据生成器 可以基于行程样本模式 生成插入和更新的样本。8.4.2、插入数据生成一些新的行程样本,将其加载到DataFrame中,然后将DataFrame写入Hudi数据集中,如下所示。scala> val inserts = convertToStringList(dataGen.generateInserts(10)) inserts: java.util.List[String] = [{"ts": 0.0, "uuid": "81a9b76c-655b-4527-85fc-7696bdeab4fd", "rider": "rider-213", "driver": "driver-213", "begin_lat": 0.4726905879569653, "begin_lon": 0.46157858450465483, "e nd_lat": 0.754803407008858, "end_lon": 0.9671159942018241, "fare": 34.158284716382845, "partitionpath": "americas/brazil/sao_paulo"}, {"ts": 0.0, "uuid": "0d612dd2-5f10-4296-a434-b34e6558e8f1", "rider": "rider-213", "driver": "driver-213", "begin_lat": 0.6100070562136587, "begin_lon": 0.8779402295427752, "end_lat": 0.3407870505929602, "end_lon": 0.5030798142293655, "fare": 43.4923811219014, "partitionpath": "americas/brazil/sao_paulo"}, {"ts": 0.0, "uuid": "0e170de4-7eda-4ab5-8c06-e351e8b23e3d", "rider": "rider-213", "driver": "driver-213", "begin_lat": 0.5731835407930634, "begin_...scala> val df = spark.read.json(spark.sparkContext.parallelize(inserts, 2)) warning: there was one deprecation warning; re-run with -deprecation for details df: org.apache.spark.sql.DataFrame = [begin_lat: double, begin_lon: double ... 8 more fields] scala> df.write.format("org.apache.hudi"). | options(getQuickstartWriteConfigs). | option(PRECOMBINE_FIELD_OPT_KEY, "ts"). | option(RECORDKEY_FIELD_OPT_KEY, "uuid"). | option(PARTITIONPATH_FIELD_OPT_KEY, "partitionpath"). | option(TABLE_NAME, tableName). | mode(Overwrite). | save(basePath); 20/03/31 15:28:11 WARN hudi.DefaultSource: Snapshot view not supported yet via data source, for MERGE_ON_READ tables. Please query the Hive table registered using Spark SQL.mode(Overwrite)覆盖并重新创建数据集(如果已经存在)。 您可以检查在/tmp/hudi_cow_table/\<region>/\<country>/\<city>/下生成的数据。我们提供了一个记录键 (schema中的uuid),分区字段(region/county/city)和组合逻辑(schema中的ts) 以确保行程记录在每个分区中都是唯一的。更多信息请参阅 对Hudi中的数据进行建模, 有关将数据提取到Hudi中的方法的信息,请参阅写入Hudi数据集。 这里我们使用默认的写操作:插入更新。 如果您的工作负载没有更新,也可以使用更快的插入或批量插入操作。 想了解更多信息,请参阅写操作。8.4.3、查询数据将数据文件加载到DataFrame中。scala> df.write.format("org.apache.hudi"). | options(getQuickstartWriteConfigs). | option(PRECOMBINE_FIELD_OPT_KEY, "ts"). | option(RECORDKEY_FIELD_OPT_KEY, "uuid"). | option(PARTITIONPATH_FIELD_OPT_KEY, "partitionpath"). | option(TABLE_NAME, tableName). | mode(Overwrite). | save(basePath); 20/03/31 15:28:11 WARN hudi.DefaultSource: Snapshot view not supported yet via data source, for MERGE_ON_READ tables. Please query the Hive table registered using Spark SQL. scala> val roViewDF = spark. | read. | format("org.apache.hudi"). | load(basePath + "/*/*/*/*") 20/03/31 15:30:03 WARN hudi.DefaultSource: Snapshot view not supported yet via data source, for MERGE_ON_READ tables. Please query the Hive table registered using Spark SQL. roViewDF: org.apache.spark.sql.DataFrame = [_hoodie_commit_time: string, _hoodie_commit_seqno: string ... 13 more fields] scala> roViewDF.registerTempTable("hudi_ro_table") warning: there was one deprecation warning; re-run with -deprecation for details scala> spark.sql("select fare, begin_lon, begin_lat, ts from hudi_ro_table where fare > 20.0").show() +------------------+-------------------+-------------------+---+ | fare| begin_lon| begin_lat| ts| +------------------+-------------------+-------------------+---+ | 93.56018115236618|0.14285051259466197|0.21624150367601136|0.0| | 64.27696295884016| 0.4923479652912024| 0.5731835407930634|0.0| | 27.79478688582596| 0.6273212202489661|0.11488393157088261|0.0| | 33.92216483948643| 0.9694586417848392| 0.1856488085068272|0.0| |34.158284716382845|0.46157858450465483| 0.4726905879569653|0.0| | 66.62084366450246|0.03844104444445928| 0.0750588760043035|0.0| | 43.4923811219014| 0.8779402295427752| 0.6100070562136587|0.0| | 41.06290929046368| 0.8192868687714224| 0.651058505660742|0.0| +------------------+-------------------+-------------------+---+ scala> spark.sql("select _hoodie_commit_time, _hoodie_record_key, _hoodie_partition_path, rider, driver, fare from hudi_ro_table").show() +-------------------+--------------------+----------------------+---------+----------+------------------+ |_hoodie_commit_time| _hoodie_record_key|_hoodie_partition_path| rider| driver| fare| +-------------------+--------------------+----------------------+---------+----------+------------------+ | 20200331152807|264170aa-dd3f-4a7...| americas/united_s...|rider-213|driver-213| 93.56018115236618| | 20200331152807|0e170de4-7eda-4ab...| americas/united_s...|rider-213|driver-213| 64.27696295884016| | 20200331152807|fb06d140-cd00-413...| americas/united_s...|rider-213|driver-213| 27.79478688582596| | 20200331152807|eb1d495c-57b0-4b3...| americas/united_s...|rider-213|driver-213| 33.92216483948643| | 20200331152807|2b3380b7-2216-4ca...| americas/united_s...|rider-213|driver-213|19.179139106643607| | 20200331152807|81a9b76c-655b-452...| americas/brazil/s...|rider-213|driver-213|34.158284716382845| | 20200331152807|d24e8cb8-69fd-4cc...| americas/brazil/s...|rider-213|driver-213| 66.62084366450246| | 20200331152807|0d612dd2-5f10-429...| americas/brazil/s...|rider-213|driver-213| 43.4923811219014| | 20200331152807|a6a7e7ed-3559-4ee...| asia/india/chennai|rider-213|driver-213|17.851135255091155| | 20200331152807|824ee8d5-6f1f-4d5...| asia/india/chennai|rider-213|driver-213| 41.06290929046368| +-------------------+--------------------+----------------------+---------+----------+------------------+该查询提供已提取数据的读取优化视图。由于我们的分区路径(region/country/city)是嵌套的3个级别 从基本路径开始,我们使用了load(basePath + "/*/*/*/*")。 有关支持的所有存储类型和视图的更多信息,请参考存储类型和视图。8.4.4、更新数据这类似于插入新数据。使用数据生成器生成对现有行程的更新,加载到DataFrame中并将DataFrame写入hudi数据集。scala> val updates = convertToStringList(dataGen.generateUpdates(10)) updates: java.util.List[String] = [{"ts": 0.0, "uuid": "0e170de4-7eda-4ab5-8c06-e351e8b23e3d", "rider": "rider-284", "driver": "driver-284", "begin_lat": 0.7340133901254792, "begin_lon": 0.5142184937933181, "en d_lat": 0.7814655558162802, "end_lon": 0.6592596683641996, "fare": 49.527694252432056, "partitionpath": "americas/united_states/san_francisco"}, {"ts": 0.0, "uuid": "81a9b76c-655b-4527-85fc-7696bdeab4fd", "rider": "rider-284", "driver": "driver-284", "begin_lat": 0.1593867607188556, "begin_lon": 0.010872312870502165, "end_lat": 0.9808530350038475, "end_lon": 0.7963756520507014, "fare": 29.47661370147079, "partitionpath": "americas/brazil/sao_paulo"}, {"ts": 0.0, "uuid": "81a9b76c-655b-4527-85fc-7696bdeab4fd", "rider": "rider-284", "driver": "driver-284", "begin_lat": 0.71801964677...scala> val df = spark.read.json(spark.sparkContext.parallelize(updates, 2)); warning: there was one deprecation warning; re-run with -deprecation for details df: org.apache.spark.sql.DataFrame = [begin_lat: double, begin_lon: double ... 8 more fields] scala> df.write.format("org.apache.hudi"). | options(getQuickstartWriteConfigs). | option(PRECOMBINE_FIELD_OPT_KEY, "ts"). | option(RECORDKEY_FIELD_OPT_KEY, "uuid"). | option(PARTITIONPATH_FIELD_OPT_KEY, "partitionpath"). | option(TABLE_NAME, tableName). | mode(Append). | save(basePath); 20/03/31 15:32:27 WARN hudi.DefaultSource: Snapshot view not supported yet via data source, for MERGE_ON_READ tables. Please query the Hive table registered using Spark SQL.注意,保存模式现在为追加。通常,除非您是第一次尝试创建数据集,否则请始终使用追加模式。 查询现在再次查询数据将显示更新的行程。每个写操作都会生成一个新的由时间戳表示的commit 。在之前提交的相同的_hoodie_record_key中寻找_hoodie_commit_time, rider, driver字段变更。8.4.5、增量查询Hudi还提供了获取给定提交时间戳以来已更改的记录流的功能。 这可以通过使用Hudi的增量视图并提供所需更改的开始时间来实现。 如果我们需要给定提交之后的所有更改(这是常见的情况),则无需指定结束时间。scala> // reload data scala> spark. | read. | format("org.apache.hudi"). | load(basePath + "/*/*/*/*"). | createOrReplaceTempView("hudi_ro_table") 20/03/31 15:33:55 WARN hudi.DefaultSource: Snapshot view not supported yet via data source, for MERGE_ON_READ tables. Please query the Hive table registered using Spark SQL. scala> scala> val commits = spark.sql("select distinct(_hoodie_commit_time) as commitTime from hudi_ro_table order by commitTime").map(k => k.getString(0)).take(50) commits: Array[String] = Array(20200331152807, 20200331153224) scala> val beginTime = commits(commits.length - 2) // commit time we are interested in beginTime: String = 20200331152807 scala> // 增量查询数据 scala> val incViewDF = spark. | read. | format("org.apache.hudi"). | option(VIEW_TYPE_OPT_KEY, VIEW_TYPE_INCREMENTAL_OPT_VAL). | option(BEGIN_INSTANTTIME_OPT_KEY, beginTime). | load(basePath); 20/03/31 15:34:40 WARN hudi.DefaultSource: hoodie.datasource.view.type is deprecated and will be removed in a later release. Please use hoodie.datasource.query.type incViewDF: org.apache.spark.sql.DataFrame = [_hoodie_commit_time: string, _hoodie_commit_seqno: string ... 13 more fields] scala> incViewDF.registerTempTable("hudi_incr_table") warning: there was one deprecation warning; re-run with -deprecation for details scala> spark.sql("select `_hoodie_commit_time`, fare, begin_lon, begin_lat, ts from hudi_incr_table where fare > 20.0").show() +-------------------+------------------+--------------------+-------------------+---+ |_hoodie_commit_time| fare| begin_lon| begin_lat| ts| +-------------------+------------------+--------------------+-------------------+---+ | 20200331153224|49.527694252432056| 0.5142184937933181| 0.7340133901254792|0.0| | 20200331153224| 98.3428192817987| 0.3349917833248327| 0.4777395067707303|0.0| | 20200331153224| 90.9053809533154| 0.19949323322922063|0.18294079059016366|0.0| | 20200331153224| 90.25710109008239| 0.4006983139989222|0.08528650347654165|0.0| | 20200331153224| 29.47661370147079|0.010872312870502165| 0.1593867607188556|0.0| | 20200331153224| 63.72504913279929| 0.888493603696927| 0.6570857443423376|0.0| +-------------------+------------------+--------------------+-------------------+---+ 这将提供在开始时间提交之后发生的所有更改,其中包含票价大于20.0的过滤器。关于此功能的独特之处在于,它现在使您可以在批量数据上创作流式管道。8.4.6、特定时间点查询让我们看一下如何查询特定时间的数据。可以通过将结束时间指向特定的提交时间,将开始时间指向”000”(表示最早的提交时间)来表示特定时间。scala> val beginTime = "000" // Represents all commits > this time. beginTime: String = 000 scala> val endTime = commits(commits.length - 2) // commit time we are interested in endTime: String = 20200331152807 scala> scala> // 增量查询数据 scala> val incViewDF = spark.read.format("org.apache.hudi"). | option(VIEW_TYPE_OPT_KEY, VIEW_TYPE_INCREMENTAL_OPT_VAL). | option(BEGIN_INSTANTTIME_OPT_KEY, beginTime). | option(END_INSTANTTIME_OPT_KEY, endTime). | load(basePath); 20/03/31 15:36:00 WARN hudi.DefaultSource: hoodie.datasource.view.type is deprecated and will be removed in a later release. Please use hoodie.datasource.query.type incViewDF: org.apache.spark.sql.DataFrame = [_hoodie_commit_time: string, _hoodie_commit_seqno: string ... 13 more fields] scala> incViewDF.registerTempTable("hudi_incr_table") warning: there was one deprecation warning; re-run with -deprecation for details scala> spark.sql("select `_hoodie_commit_time`, fare, begin_lon, begin_lat, ts from hudi_incr_table where fare > 20.0").show() +-------------------+------------------+-------------------+-------------------+---+ |_hoodie_commit_time| fare| begin_lon| begin_lat| ts| +-------------------+------------------+-------------------+-------------------+---+ | 20200331152807| 93.56018115236618|0.14285051259466197|0.21624150367601136|0.0| | 20200331152807| 64.27696295884016| 0.4923479652912024| 0.5731835407930634|0.0| | 20200331152807| 27.79478688582596| 0.6273212202489661|0.11488393157088261|0.0| | 20200331152807| 33.92216483948643| 0.9694586417848392| 0.1856488085068272|0.0| | 20200331152807|34.158284716382845|0.46157858450465483| 0.4726905879569653|0.0| | 20200331152807| 66.62084366450246|0.03844104444445928| 0.0750588760043035|0.0| | 20200331152807| 43.4923811219014| 0.8779402295427752| 0.6100070562136587|0.0| | 20200331152807| 41.06290929046368| 0.8192868687714224| 0.651058505660742|0.0| +-------------------+------------------+-------------------+-------------------+---+
文章
SQL  ·  消息中间件  ·  分布式计算  ·  Hadoop  ·  Java  ·  Linux  ·  Scala  ·  Maven  ·  HIVE  ·  Spark
2023-03-24
Linux基础操作
1.os概念,定位 操作系统是一款管理软件,管理硬件和软件。对上提供良好、稳定和安全、高效的运行环境;对下管理好软硬件资源。2.查看Linux主机ip和使用XSHell登陆主机、XSHell下的复制黏贴查看Linux主机ip:在终端下敲 ifconfig 指令, 查看到 ip 地址。。使用XSHell登录主机:在XSHell终端写:ssh [ip]。ip 为刚才看到的 ifconfig 结果.如果网络畅通, 将会提示输入用户名密码. 输入即可正确登陆。XSHell下的复制黏贴:复制: ctrl + insert (有些同学的 insert 需要配合 fn 来按)                                                                  粘贴: shift + insert      ctrl + c / ctrl + v 是不行的3.ls指令语法: ls [选项][目录或文件]功能:对于目录,该命令列出该目录下的所有子目录与文件。对于文件,将列出文件名以及其他信息常用选项:-a 列出目录下的所有文件,包括以 . 开头的隐含文件。-d 将目录象文件一样显示,而不是显示其下的文件。 如: ls –d 指定目录-i 输出文件的 i 节点的索引信息。 如 ls –ai 指定文件-k 以 k 字节的形式表示文件的大小。 ls –alk 指定文件-l 列出文件的详细信息。-n 用数字的 UID,GID 代替名称。 (介绍 UID, GID)-F 在每个文件名后附上一个字符以说明该文件的类型, “*”表示可执行的普通文件; “/”表示目录; “@”表示符号链接; “|”表示FIFOs; “=”表示套接字(sockets)。(目录类型识别)-r 对目录反向排序。-t 以时间排序。-s 在l文件名后输出该文件的大小。(大小排序,如何找到目录下最大的文件)-R 列出所有子目录下的文件。 (递归)-1 一行只输出一个文件。这里列出今天刚刚学习到的:①ls指令:显示当前路径下的文件或者目录名称。编辑 ②ls -l指令:显示当前路径下的文件或者目录的更详细的属性信息。PS:文件 = 文件内容数据+文件属性数据。因此文件本身是需要占用空间的,即使是空文件,显示0KB,但其属性是占用空间的。文件之间(普通文件 VS  目录):普通文件就是普通的文件,目录现在可以人为是文件夹。 ③ls -a  与ls -al指令:ls -a显示目录下的所有文件,包括以.开头的隐藏文件。ls -al是ls -a和ls -l的结合,显示更详细的属性信息4.pwd指令语法: pwd功能:显示用户当前所在的目录5.cd指令 语法:cd 目录名功能:改变工作目录。将当前工作目录改变到指定的目录下。cd .. : 返回上级目录。cd /home/litao/linux/ : 绝对路径。cd ../day02/ : 相对路径。cd ~:进入用户家目。cd -:返回最近访问目录。cd指令:切换路径,进入目标路径进行操作。cd . 和cd ..  指令 :cd .是当前目录  cd ..是返回上级目录 。cd ~指令:进入家目录。root的家目录是单独的,而所有用户的家目录,都是在/home/XXX。这里显示root家目录。 cd -指令:返回最近访问目录。可以理解为在两个目录下反复横跳。 在分析绝对路径和相对路径前,先要知道Linux系统中,磁盘上的文件和目录被组成一棵目录树,每个节点都是目录或文件。其实几乎任何操作系统文件的目录组织结构是一颗多叉树。  多叉树,有叶子节点和路上节点(其实就是父节点,或非叶子节点,在Linux下这样称呼比较好理解),路上节点一定只能是目录,而叶子节点,可以是普通文件,也可以是空目录。而我们为什么喜欢用路径来表示一个文件?因为,从根目录到一个文件的路径,是唯一的!尽管某个文件有很多个,放在不同的目录里面,但是,我们可以通过路径,找到那个唯一!因此,这里分为绝对路径和相对路径。cd /home/litao/linux/ : 绝对路径。就是直接从根目录开始往下走。cd ../day02/ : 相对路径。就是,如果我们在处于某个目录中,但是想找到另外的目录中的文件,不需要返回根目录,而是可以通过这个目录,更换到目标目录中,接着找到目标文件。6.touch指令语法:touch [选项]... 文件...功能: touch命令参数可更改文档或目录的日期时间,包括存取时间和更改时间,或者新建一个不存在的文件。常用选项:-a 或--time=atime或--time=access或--time=use只更改存取时间。-c 或--no-create 不建立任何文档。-d 使用指定的日期时间,而非现在的时间。-f 此参数将忽略不予处理,仅负责解决BSD版本touch指令的兼容性问题。-m 或--time=mtime或--time=modify 只更改变动时间。-r 把指定文档或目录的日期时间,统统设成和参考文档或目录的日期时间相同。-t 使用指定的日期时间,而非现在的时间 touch指令就是在Linux下用来创建普通文件的指令7.mkdir指令语法: mkdir [选项] dirname...功能:在当前目录下创建一个名为 “dirname”的目录-p, --parents 可以是一个路径名称。此时若路径中的某些目录尚不存在,加上此选项后,系统将自动建立好那些尚不存在的目录,即一次可以建立多个目录。即在使用mkdir命令创建新的目录时,在其父目录不存在时先创建父目录的指令就是 mkdir -p XXX/XXX8.rmdir指令&&rm指令rmdir是一个与mkdir相对应的命令。 mkdir是建立目录,而rmdir是删除命令。语法: rmdir [-p][dirName]适用对象:具有当前目录操作权限的所有使用者功能:删除空目录常用选项:-p 当子目录被删除后如果父目录也变成空目录的话,就连带父目录一起删除rm命令可以同时删除文件或目录
语法: rm [-f-i-r-v][dirName/dir]适用对象:所有使用者功能:删除文件或目录(rm默认删除普通文件,加上-r,即可删除目录)常用选项:-f 即使文件属性为只读(即写保护),亦直接删除-i 删除前逐一询问确认-r 删除目录及其下所有文件 9.man指令inux的命令有很多参数,我们不可能全记住,我们可以通过查看联机手册获取帮助。也就是说:man是一个查看命令、系统调用、C接口的一个手册。man默认从1号手册开始查找,找到即停。man可以根据手册查找:man 1/2/3 命令/接口/C。访问Linux手册页的命令是:man 语法: man [选项] 命令常用选项-k 根据关键字搜索联机帮助num 只在第num章节找-a 将所有章节的都显示出来,比如 man printf 它缺省从第一章开始搜索,知道就停止,用a选项,当按下q退出,他会继续往后面搜索,直到所有章节都搜索完毕。解释一下,面手册分为8章1 是普通的命令2 是系统调用,如open,write之类的(通过这个,至少可以很方便的查到调用这个函数,需要加什么头文件)3 是库函数,如printf,fread4是特殊文件,也就是/dev下的各种设备文件5 是指文件的格式,比如passwd, 就会说明这个文件中各个字段的含义6 是给游戏留的,由各个游戏自己定义7 是附件还有一些变量,比如向environ这种全局变量在这里就有说明8 是系统管理用的命令,这些命令只能由root使用,如ifconfig10. cp指令语法: cp [选项] 源文件或目录 目标文件或目录功能: 复制文件或目录说明: cp指令用于复制文件或目录,如同时指定两个以上的文件或目录,且最后的目的地是一个已经存在的目录,则它会把前面指定的所有文件或目录复制到此目录中。若同时指定多个文件或目录,而最后的目的地并非一个已存在的目录,则会出现错误信息。cp拷贝目录或者文件,-r -f -i 同rm# ls a firstfile new_firstdir test.txt # cp test.txt a # cd a # ll drwxr-xr-x 3 root root 4096 Sep 18 10:33 b -rw-r--r-- 1 root root 168908 Sep 27 15:47 test.txt11. mv指令mv命令是move的缩写,可以用来移动文件或者将文件改名(move (rename) files),是Linux系统下常用的命令,经常用来备份文件或者目录。1.mv的功能类似剪切功能,移动目录或文件。                                                                                  2.对文件或目录重命名语法: mv [选项] 源文件或目录 目标文件或目录功能:1. 视mv命令中第二个参数类型的不同(是目标文件还是目标目录), mv命令将文件重命名或将其移至一个新的目录中。2. 当第二个参数类型是文件时, mv命令完成文件重命名,此时,源文件只能有一个(也可以是源目录名),它将所给的源文件或目录重命名为给定的目标文件名。3. 当第二个参数是已存在的目录名称时,源文件或目录参数可以有多个, mv命令将各参数指定的源文件均移至目标目录中。常用选项:-f : force 强制的意思,如果目标文件已经存在,不会询问而直接覆盖-i :若目标文件 (destination) 已经存在时,就会询问是否覆盖!# pwd /root/new_firstdir [root@VM-12-9-centos new_firstdir]# ll total 0 # touch aaa # mv aaa new_firstdir # cd new_firstdir [root@VM-12-9-centos new_firstdir]# ls aaa12. cat指令语法: cat [选项][文件]                                                                                                                    功能: 查看目标文件的内容常用选项:-b 对非空输出行编号-n 对输出的所有行编号-s 不输出多行空行# ls a firstfile new_firstdir test.txt ]# cat test.txt hello 106 [0] hello 106 [1] hello 106 [2] hello 106 [3] hello 106 [4] hello 106 [5] hello 106 [6] hello 106 [7] hello 106 [8] hello 106 [9] hello 106 [10]另外,还有一个类似的指令:tac。它的功能,也是cat一样的,打印、显示。不过,cat和tac的打印的顺序是相反的。# tac test.txt hello 106 [10000] hello 106 [9999] hello 106 [9998] hello 106 [9997] hello 106 [9996] hello 106 [9995] hello 106 [9994] hello 106 [9993] hello 106 [9992] hello 106 [9991] hello 106 [9990]13. more指令语法: more [选项][文件]功能: more命令,功能类似 cat常用选项:-n 对输出的所有行编号q 退出more对于上面的cat或tac指令,上面其实是有一万个数据,如果我们需要查找第5千个数据的时候,需要不断的往上或往下翻阅,这其实是很麻烦的,因此,more指令可以解决这样的问题。总的来说:cat和tac适合比较小的文本或者代码段,而more和接下来要说的less指令,适合大的(日志之类的).# more -5000 test.txt hello 106 [4985] hello 106 [4986] hello 106 [4987] hello 106 [4988] hello 106 [4989] hello 106 [4990] hello 106 [4991] hello 106 [4992] hello 106 [4993] hello 106 [4994] hello 106 [4995] hello 106 [4996] hello 106 [4997] hello 106 [4998] hello 106 [4999]14. less指令less 工具也是对文件或其它输出进行分页显示的工具,应该说是linux正统查看文件内容的工具,功能极其强大。less 的用法比起 more 更加的有弹性。在 more 的时候,我们并没有办法向前面翻, 只能往后面看但若使用了 less 时,就可以使用 [pageup][pagedown] 等按键的功能来往前往后翻看文件,更容易用来查看一个文件的内容!除此之外,在 less 里头可以拥有更多的搜索功能,不止可以向下搜,也可以向上搜。语法: less [参数] 文件功能:less与more类似,但使用less可以随意浏览文件,而more仅能向前移动,却不能向后移动,而且less在查看之前不会加载整个文件选项:-i 忽略搜索时的大小写-N 显示每行的行号/字符串:向下搜索“字符串”的功能?字符串:向上搜索“字符串”的功能n:重复前一个搜索(与 / 或 ? 有关)N:反向重复前一个搜索(与 / 或 ? 有关)q:quit15. head指令head 与 tail 就像它的名字一样的浅显易懂,它是用来显示开头或结尾某个数量的文字区块, head 用来显示档案的开头至标准输出中,而 tail 想当然尔就是看档案的结尾语法: head [参数]... [文件]...功能:head 用来显示档案的开头至标准输出中,默认head命令打印其相应文件的开头10行。选项:-n<行数> 显示的行数# head -10 test.txt hello 106 [0] hello 106 [1] hello 106 [2] hello 106 [3] hello 106 [4] hello 106 [5] hello 106 [6] hello 106 [7] hello 106 [8] hello 106 [9]16. tail指令ail 命令从指定点开始将文件写到标准输出.使用tail命令的-f选项可以方便的查阅正在改变的日志文件,tail -f filename会把filename里最尾部的内容显示在屏幕上,并且不但刷新,使你看到最新的文件内容语法: tail[必要参数][选择参数][文件]功能: 用于显示指定文件末尾内容,不指定文件时,作为输入信息进行处理。常用查看日志文件。选项:                                                                                                                                                -f 循环读取-n<行数> 显示行数# tail -3 test.txt hello 106 [9998] hello 106 [9999] hello 106 [10000]这里插入一个点:管道。就当提前预习:如果,我想取中间十行的数据【1000,1010】,怎么办。有两种方法:第一种,是创建一个文件来接收前1010,然后再读取这个文件的后10行,但是这样很麻烦,而已要创建文件。所以,第二种方法是利用管道:这里先浅浅地理解什么是管道:管道是传输资源的东西,一般都要有一个入口一个出口。那么:下面代码中:'  | '就是管道,可以将head看成管道的入口,tail看成管道的出口,而管道里面,先放进了前面的"head -1010 test.txt"的数据,然后tail再从管道里面取"tail -3"的数据。# head -1010 test.txt | tail -3 hello 106 [1000] hello 106 [1001] hello 106 [1002] 那么,我们将这里是数据,进行逆序,那么,再加跟管道进去就好了!# head -1010 test.txt | tail -3 | tac hello 106 [1002] hello 106 [1001] hello 106 [1000]17.时间相关的指令date显示date 指定格式显示时间: date +%Y:%m:%ddate 用法: date [OPTION]... [+FORMAT]1.在显示方面,使用者可以设定欲显示的格式,格式设定为一个加号后接数个标记,其中常用的标记列表如下
%H : 小时(00..23)%M : 分钟(00..59)%S : 秒(00..61)%X : 相当于 %H:%M:%S%d : 日 (01..31)%m : 月份 (01..12)%Y : 完整年份 (0000..9999)%F : 相当于 %Y-%m-%d2.在设定时间方面
date -s //设置当前时间,只有root权限才能设置,其他只能查看。date -s 20080523 //设置成20080523,这样会把具体时间设置成空00:00:00date -s 01:01:01 //设置具体时间,不会对日期做更改date -s “01:01:01 2008-05-23″ //这样可以设置全部时间date -s “01:01:01 20080523″ //这样可以设置全部时间date -s “2008-05-23 01:01:01″ //这样可以设置全部时间date -s “20080523 01:01:01″ //这样可以设置全部时间3.时间戳时间->时间戳: date +%s时间戳->时间: date -d@1508749502Unix时间戳(英文为Unix epoch, Unix time, POSIX time 或 Unix timestamp)是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数,不考虑闰秒18.cal指令cal命令可以用来显示公历(阳历)日历。公历是现在国际通用的历法,又称格列历,通称阳历。 “阳历”又名“太阳历”,系以地球绕行太阳一周为一年,为西方各国所通用,故又名“西历”命令格式: cal [参数][月份][年份]功能: 用于查看日历等时间信息,如只有一个参数,则表示年份(1-9999),如有两个参数,则表示月份和年份常用选项:-3 显示系统前一个月,当前月,下一个月的月历-j 显示在当年中的第几天(一年日期按天算,从1月1号算起,默认显示当前月在一年中的天数)-y 显示当前年份的日历19 find质指令:-nameLinux下find命令在目录结构中搜索文件,并执行指定的操作。Linux下find命令提供了相当多的查找条件,功能很强大。由于find具有强大的功能,所以它的选项也很多,其中大部分选项都值得我们花时间来了解一下。即使系统中含有网络文件系统( NFS), find命令在该文件系统中同样有效,只你具有相应的权限。在运行一个非常消耗资源的find命令时,很多人都倾向于把它放在后台执行,因为遍历一个大的文件系统可能会花费很长的时间(这里是指30G字节以上的文件系统)语法: find pathname -options功能: 用于在文件树种查找文件,并作出相应的处理(可能访问磁盘,进而导致效率低下)常用选项:-name 按照文件名查找文件[root@VM-12-9-centos ~]# ll total 12 a firstfile lesson4 _firstdir # find ~ -name test.txt a/test.txt a/lesson4/test.txt lesson4/test.txt # cd lesson4 lesson4]# ll total 168 test.txt lesson4]# find ~ -name firstfile /root/firstfile拓展关于搜索查找的指令:①which 用来查找命令的路径的指令# which pwd /usr/bin/pwd # which rm alias rm='rm -i' /usr/bin/rm这里有个指令:alias,它用于对一个指令进行重命名。于此同时,当我们执行ll或ls指令的时候,会发现,文件和目录的颜色不一样,那是因为alias带上了colors的指令。# which ls alias ls='ls --color=auto'②whereis:在特定的路径下,查找指定的文件名对应的指令或者文档# whereis ls ls: /usr/bin/ls /usr/share/man/man1/ls.1.gz # whereis test.txt test: /usr/bin/test /usr/share/man/man1/test.1.gz20. grep指令文本内容的过滤工具,对文本内容进行匹配,匹配成功的进行行显示语法: grep [选项] 搜寻字符串 文件功能: 在文件中搜索字符串,将找到的行打印出来常用选项:-i :忽略大小写的不同,所以大小写视为相同-n :顺便输出行号-v :反向选择,亦即显示出没有 '搜寻字符串' 内容的那一行# grep '9999' test.txt hello 106 [9999] # grep '999' test.txt hello 106 [999] hello 106 [1999] hello 106 [2999] hello 106 [3999] hello 106 [4999] hello 106 [5999] hello 106 [6999] hello 106 [7999] hello 106 [8999] hello 106 [9990] hello 106 [9991] hello 106 [9992] hello 106 [9993] hello 106 [9994] hello 106 [9995] hello 106 [9996] hello 106 [9997] hello 106 [9998] hello 106 [9999]-n带上行号:# grep -n '999' test.txt 1000:hello 106 [999] 2000:hello 106 [1999] 3000:hello 106 [2999] ......-i:# vim test.txt # grep 'abc' test.txt abc abc abc # grep -i 'abc' test.txt abc ABC aBc Abc abc abc ABC # grep 'x' test.txt x x-v:就是反向。补充1:wc -l  统计行数# grep '999' test.txt | wc -l 19补充2:sort  按照ASCII码进行排序(升序)# touch file.txt # ll total 180 a file.txt firstfile lesson4 new_firstdir test.txt # vim file.txt # cat file.txt 111111 2222 33333 44444444 66666 555555 7777 # sort file.txt 111111 2222 33333 44444444 555555 66666 7777补充3:uniq 对文本内容中,相邻,相等的,去重。# vim file.txt # cat file.txt 111111 2222 33333 44444444 66666 555555 7777 7777 7777 7777 22222 555555 555555 555555 # uniq file.txt 111111 2222 33333 44444444 66666 555555 7777 22222 555555我们发现,有一些没有相邻的,没去去重,我们可以利用管道,先进行排序,然后再去重# sort file.txt 111111 2222 22222 33333 44444444 555555 555555 555555 555555 66666 7777 7777 7777 7777 # sort file.txt | uniq 111111 2222 22222 33333 44444444 555555 66666 777721. zip/unzip指令语法: zip 压缩文件.zip 目录或文件功能: 将目录或文件压缩成zip格式常用选项:-r 递 归处理,将指定目录下的所有文件和子目录一并处理例子:将test2目录压缩: zip test2.zip test2/*解压到tmp目录: unzip test2.zip -d /tmpzip默认对一个目录进行打包压缩的时候,只会对目录文件打包压缩,也就是目录文件的内容不达标压缩。于此,需要加上-r递归。zip -r 你的压缩包(自定义) dir(要打包压缩的目录) unzip  你的压缩包(自定义)--在当前目录下进行解包解压的功能# mkdir temp # ll total 244 a file.txt firstfile lesson4 my.zip new_firstdir temp test.txt # mv my.zip temp # ll total 188 a 5 file.txt firstfile lesson4 53 new_firstdir temp test.txt # cd temp # ll total 56 my.zip # unzip my.zip Archive: my.zip creating: a/ creating: a/b/ creating: a/b/c/ creating: a/b/c/d/ inflating: a/test.txt creating: a/lesson4/ inflating: a/lesson4/test.txt inflating: a/my.tgz # ll total 60 a my.zip # tree a a |-- b | `-- c | `-- d |-- lesson4 | `-- test.txt |-- my.tgz `-- test.txt 4 directories, 3 files # pwd /root/temp # ll total 60 a my.zip # cd a # ll total 204 b lesson4 my.tgz test.txt # tree b b `-- c `-- d 2 directories, 0 files # less test.txt [3]+ Stopped less test.txt上面的是解压到当前目录,那么,接下来的指令,便是解压到指定目录中:unzip my.zip -d /home/XXX或者/root/XXX# unzip my.zip -d /root/a/b Archive: my.zip creating: /root/a/b/a/ creating: /root/a/b/a/b/ creating: /root/a/b/a/b/c/ creating: /root/a/b/a/b/c/d/ inflating: /root/a/b/a/test.txt creating: /root/a/b/a/lesson4/ inflating: /root/a/b/a/lesson4/test.txt inflating: /root/a/b/a/my.tgz22 tar指令:打包/解包,不打开,直接看内容tar [-cxtzjvf] 文件与目录 ....
参数: 
-c :建立一个压缩文件的参数指令(create 的意思);-x :解开一个压缩文件的参数指令!-t :查看 tarfile 里面的文件!-z :是否同时具有 gzip 的属性?亦即是否需要用 gzip 压缩?-j :是否同时具有 bzip2 的属性?亦即是否需要用 bzip2 压缩?-v :压缩的过程中显示文件!这个常用,但不建议用在背景执行过程!-f :使用档名,请留意,在 f 之后要立即接档名喔!不要再加参数!-C : 解压到指定目录下面代码中,分别实现了打包压缩和解包的操作:打包压缩:tar -czf oh.tgz(压缩包名字)lesson4(压缩的目录文件名)(czf:c代表创建一个压缩包,z代表使用z代表的算法,f代表文件名)。 解包:tar -xzf oh.tgz  x代表解开压缩包。如果带个v,-xzvf  -cvzf   会把过程显示出来。~]# ll total 188 a file.txt firstfile lesson4 firstdir temp test.txt ~]# tree lesson4 lesson4 `-- test.txt 0 directories, 1 file ~]# tar -czf oh.tgz lesson4 ~]# ll total 216 a file.txt firstfile lesson4 new_firstdir oh.tgz temp test.txt ~]# mv oh.tgz temp ~]# cd temp temp]# ll total 28 oh.tgz # tar -xzf oh.tgz # ll total 32 lesson4 oh.tgz # tree lesson4 lesson4 `-- test.txt 0 directories, 1 file不解压,看里面的内容:相当于windows下,点开压缩包,查看里面的东西一样。-ttemp]# tar -tf oh.tgz lesson4/ lesson4/test.txt上面的也是默认到当前目录。那么,指定路径解压,就需要  -C  指令a file.txt firstfile new_firstdir temp test.txt ~]# cd temp temp]# ll total 32 lesson4 oh.tgz temp]# tar -xzvf oh.tgz -C ~ lesson4/ lesson4/test.txt temp]# ls ~ a file.txt firstfile lesson4 new_firstdir temp test.txt23. bc指令:bc命令可以很方便的进行浮点运算。temp]# bc bc 1.06.95 30-90 -60 3.25-36.3 -33.05 ^Z [5]+ Stopped bc temp]# echo "1+2+3+4+5+6+7+8+9" 1+2+3+4+5+6+7+8+9 temp]# echo "1+2+3+4+5+6+7+8+9" | bc 4524. uname -r 指令语法: uname [选项]功能: uname用来获取电脑和操作系统的相关信息。补充说明: uname可显示linux主机所用的操作系统的版本、硬件的名称等基本信息                        常用选项: 
-a或–all 详细输出所有信息,依次为内核名称,主机名,内核版本号,内核版本,硬件名,处理器类型,硬件平台类型,操作系统名称25. 重要的几个热键[TAB],[ctrl]-c,[ctrl]-d[Tab]按键---具有『命令补全』和『档案补齐』的功能[Ctrl]-c按键---让当前的程序『停掉』[Ctrl]-d按键---通常代表着:『键盘输入结束(End Of File, EOF 戒 End OfInput)』的意思;另外他也可以用来取代exit26. 关机语法: shutdown [选项] ** 常见选项: ** -h :将系统的服务停掉后,立即关机。                                                                                               -r : 在将系统的服务器停掉之后就重新启动。                                                                                   -t sec : -t 后面加秒数,亦即『过几秒后关机』的意思27. 拓展◆安装和登录命令: login、 shutdown、 halt、 reboot、 install、 mount、 umount、 chsh、 exit、 last;◆ 文件处理命令: file、 mkdir、 grep、 dd、 find、 mv、 ls、 diff、 cat、 ln;◆ 系统管理相关命令: df、 top、 free、 quota、 at、 lp、 adduser、 groupadd、 kill、 crontab;◆ 网络操作命令: ifconfig、 ip、 ping、 netstat、 telnet、 ftp、 route、 rlogin、 rcp、 finger、 mail、 nslookup;◆ 系统安全相关命令: passwd、 su、 umask、 chgrp、 chmod、 chown、 chattr、 sudo ps、 who;◆ 其它命令: tar、 unzip、 gunzip、 unarj、 mtools、 man、 unendcode、 uudecode。查看xpu:lscpu查看内存:lsmem查看磁盘:df -h查看登录了服务器的账号,也就是用户:who 27.1 shell命令以及运行原理Linux严格意义上说的是一个操作系统,我们称之为“核心(kernel) “ ,但我们一般用户,不能直接使用kernel。而是通过kernel的“外壳”程序,也就是所谓的shell,来与kernel沟通。如何理解?为什么不能直接使用kernel?从技术角度, Shell的最简单定义:命令行解释器(command Interpreter)主要包含:将使用者的命令翻译给核心(kernel)处理。同时,将核心的处理结果翻译给使用者。对比windows GUI,我们操作windows 不是直接操作windows内核,而是通过图形接口,点击,从而完成我们的操作(比如进入D盘的操作,我们通常是双击D盘盘符.或者运行起来一个应用序)。shell 对于Linux,有相同的作用,主要是对我们的指令进行解析,解析指令给Linux内核。反馈结果在通过内核运行出结果,通过shell解析给用户。windows的图形界面,本质也是一种外壳程序。所以,Linux shell命令行外壳 和 Windows图形界面,本质是一样的。通过用户——shell——内核这样的结构,可以有效的执行很多指令。当用户传入的是非法指令,那么,shell会直接拒绝,不需要进入到内核当中,也起到了保护作用。帮助理解:如果说你是一个闷骚且害羞的程序员,那shell就像媒婆,操作系统内核就是你们村头漂亮的且有让你心动的MM小花。你看上了小花,但是有不好意思直接表白,那就让你你家人找媒婆帮你提亲,所有的事情你都直接跟媒婆沟通,由媒婆转达你的意思给小花,而我们找到媒婆姓王,所以我们叫它王婆,它对应我们常使用的bash27.2 Linux权限的概念Linux下有两种用户:超级用户(root)、普通用户。超级用户:可以再linux系统下做任何事情,不受限制普通用户:在linux下做有限的事情。超级用户的命令提示符是“#”,普通用户的命令提示符是“$命令: su [用户名]功能:切换用户。例如,要从root用户切换到普通用户user,则使用 su user。 要从普通用户user切换到root用户则使用 su root(root可以省略),此时系统会提示输入root用户的口令~]$ su - Password: ~]# pwd /root ~]# su wjmhlh ]$ pwd /root ]$ whoami wjmhlh ]$ su Password: ~]# whoami root ~]# su wjmhlh ]$ # 代表root $代表普通用户~]$ pwd /home/wjmhlh ~]$ clear ~]$ mkdir lesson5 ~]$ ll total 4 lesson5 ~]$ cd lesson5 lesson5]$ su - Password: Last login: Thu Oct 6 16:44:28 CST 2022 on pts/0 ~]# whoami root ~]# exit logout lesson5]$ whoami wjmhlh lesson5]$ su Password: lesson5]# whoami root lesson5]# su wjmhlh lesson5]$ whoami wjmhlh lesson5]$ root与普通用户之间的切换操作当我不想转换为root用户,但是需要root权限的时候,可以使用命令sudo XXX。但会有个问题,那就是,root对普通用户的信任。当root对普通用户信任的时候,使用sudo XXX后,再使用whoami的时候,发现,我们的权限更换成了root,但不信任,就会出现以下信息: ~]$ sudo whoami[sudo] password for wjmhlh: wjmhlh is not in the sudoers file.  This incident will be reported.所以,说了那么多,什么是权限?我们为什么需要权限?①什么是权限?权限是约束人的(一个人或者某群体)。而对于人,并不是真的去约束这个人或群体,而是对他的身份或者扮演的角色进行约束。就好比如,我张三,开通了某奇艺的会员,我张三能去看VIP电影,真的是因为我是张三,所以我能看吗?如果另外的一个人也是叫做张三,他能也看吗?不!仅仅是因为我在某奇艺的身份!因此:文件权限 = 人+文件属性。27.3 Linux权限管理续上:人—>角色—>权限。角色(身份)->(拥有者:owner     其他人:other    所属组:grouper)lesson5]$ touch file.txtlesson5]$ lltotal 0-rw-rw-r-- 1 wjmhlh wjmhlh 0 Oct  7 13:43 file.txt第一个红: 文件的拥有者  第二个红:文件的所属组,因为这里只有我一个人在用。对于其他人,则是,用户在访问这个文件的时候,用户名与拥有者和所属组的名字不相同的话,那么,这个用户就是其他人other。那么,为什么要存在所属组呢?文件创建者是拥有者,拥有者以外的是其他人,这个能很好的理解。而所属组,用来干嘛?所谓组,那就是一群人在一起奋斗的组别。如果有一天,一个项目中,几个团队共用一个Linux机器。我想要给我的团队小组成员看我的代码,即对他们开源,但是又不能给小组以外的人看,如果没有所属组,那么,我需要将拥有者和其他人的权限放开,这时候,只要是个人都能查看我的代码了。这样显然是绝对不行的!因此,所属组就诞生了,我只需要在所属组中,对我的小组成员开源,其他人也不能看见,这就皆大欢喜了!文件属性是啥?文件属性:r(读权限)  w(写权限)   x(执行权限)来介绍一些小知识:lesson5]$ mkdir dirlesson5]$ lltotal 4drwxrwxr-x 2 wjmhlh wjmhlh 4096 Oct  7 14:00 dir第一个红: 文件的拥有者  第二个红:文件的所属组第一个绿是文件大小。第一个蓝指的是最近修改或创建时间之前我们讲过,文件 = 内容 + 属性。所以,上面那行信息,都是属于文件的属性而橙色部分:看第一个字符:剩下的部分:所以,我们阅读权限的正确方法是:drwxrwxr-x 2 wjmhlh wjmhlh 4096 Oct  7 14:00 dir对于dir,它是目录文件,拥有者允许读权限、写权限和执行权限;所属组允许读权限、写权限和执行权限;其他人允许读权限,不允许写权限,允许执行权限。同理:-rw-rw-r-- 1 wjmhlh wjmhlh 0 Oct  7 13:43 file.txt对于file.txt,它是普通文件,拥有者允许读权限、写权限和不允许执行权限;所属组允许读权限、写权限和不允许执行权限;其他人允许读权限,不允许写权限,不允许执行权限。还有噢,其实,rwx可以使用二进制来表示,然后三组转换成八进制数字。结合以下的指令:# chmod 664 /home/abc.txt# chmod 640 /home/abc.txt接下来,我们来看看,如何操作权限?chmod
功能: 设置文件的访问权限格式: chmod [参数] 权限 文件名常用选项:R -> 递归修改目录文件的权限说明:只有文件的拥有者和root才可以改变文件的权限chmod① 用户表示符+/-=权限字符+:向权限范围增加权限代号所表示的权限-:向权限范围取消权限代号所表示的权限=:向权限范围赋予权限代号所表示的权限用户符号:u:拥有者g:拥有者同组用o:其它用户a:所有用户例子:lesson5]$ whoamiwjmhlhlesson5]$ chmod u-r file.txt      //去掉读权限  //拥有者lesson5]$ lltotal 4drwxrwxr-x 2 wjmhlh wjmhlh 4096 Oct  7 14:00 dir--w-rw-r-- 1 wjmhlh wjmhlh    0 Oct  7 13:43 file.txtlesson5]$ chmod u+x file.txt   // 增加执行权限   //拥有者lesson5]$ lltotal 4drwxrwxr-x 2 wjmhlh wjmhlh 4096 Oct  7 14:00 dir--wxrw-r-- 1 wjmhlh wjmhlh    0 Oct  7 13:43 file.txt lesson5]$ chmod u-rwx file.txt // 去掉读写执行权限   //拥有者 lesson5]$ lltotal 4drwxrwxr-x 2 wjmhlh wjmhlh 4096 Oct  7 14:00 dir----rw-r-- 1 wjmhlh wjmhlh    0 Oct  7 13:43 file.txt lesson5]$ chmod u+rw file.txt //增加读和执行权限   //拥有者lesson5]$ lltotal 4drwxrwxr-x 2 wjmhlh wjmhlh 4096 Oct  7 14:00 dir-rw-rw-r-- 1 wjmhlh wjmhlh    0 Oct  7 13:43 file.txt同时,如果我们需要同时操作拥有者、所属组和其他人的权限,可以用逗号分开。chmod u-rwx,u-rwx,g-rwx,o-rwx file.txt也可以chmod a-rwx file.txt值得注意的是,当拥有者的某个权限失效时,但是所属组拥有,我们使用拥有者来操作这个失效的权限时,依然无法执行,既然所属组拥有这个权限。因为,对于拥有者——所属组——other,是if——else if ——else的关系,拥有者没有,那么,匹配到所属组也是拥有者的名称,那么,这个权限也不能使用!还有一点的就是:root不受权限的约束!root能够删掉普通用户的任意权限,但是却可以在用户没有这个文件的权限的时候,去操作这个权限!比如,-rw-rw-r-- 1 wjmhlh wjmhlh    0 Oct  7 13:43 file.txt。普通用户只有r和w。root可以将两个也删掉,变成----rw-r--。删掉后,root依然可以对其进行读写和执行。你一点脾气都没有!所以,root一定不能丢!chown功能:修改文件的拥有者格式: chown [参数] 用户名 文件名实例:# chown user1 f1# chown -R user1 filegroup1当然,当你要将一个东西给别人的时候,需要跟别人说一声,我们可以使用sudo 来强行给。————sudo chown XXX file同时将拥有者和所属组都修改给别人,那么是这样的:sudo chown XXX:XXX filechgrp
功能:修改文件或目录的所属组格式: chgrp [参数] 用户组名 文件名常用选项: -R 递归修改文件或目录的所属组file指令:功能说明:辨识文件类型。语法: file [选项] 文件或目录常用选项-c 详细显示指令执行过程,便于排错或分析程序执行的情形。-z 尝试去解读压缩文件的内容。drwxrwxr-x 2 wjmhlh wjmhlh 4096 Oct  7 14:00 dir-rw-rw-r-- 1 wjmhlh wjmhlh    0 Oct  7 13:43 file.txt lesson5]$ file dirdir: directory lesson5]$ file file.txtfile.txt: empty关于使用sudo分配权限,后续再解析。为什么需要权限?便于系统进行安全的管理。那么,为什么在我们创建了目录或者文件后,默认的权限是我们所看到的样子,意思是说,为什么一开始不是rwx rwx rwx,而是rw- rw- rw-等等类似的样子?因为Linux规定:目录:起始权限:777文件:起始权限:666$umask0002——>000 000 010---系统会默认配置好umask 权限掩码:反是在umask出现的权限,都必须在起始权限中去掉!!!这里的去掉,不是做减法,而是按位与&最终权限 = 起始权限 & (^umask),即现对umask进行按位取反,再进行按位与,最终得到最终权限。比如:值得注意的一点是:umask是可以被修改的,也就是,默认的是0002,但我们可以修改为0444,0000.     umask 0000最后,关于rwx的一些补充:r—读权限,并不决定我们能否进入这个目录或文件,而是决定了我们能否使用ls或ll来查看里面的内容    w—写权限。决定了能否在目录里面创建文件或目录x—执行权限,决定了我们能否进入这个目录或文件所以为什么系统规定目录的默认的权限是777开始的?因为所有的目录,在创建初,一般都可以进入(x)。
文章
人工智能  ·  安全  ·  算法  ·  Unix  ·  Linux  ·  Shell  ·  程序员  ·  网络安全  ·  数据安全/隐私保护  ·  Windows
2023-03-17
Using colcon to build packages:使用colcon来构建软件包
@[toc]参考官方文档:Using colcon to build packages这是一个关于如何用colcon创建和构建ROS 2工作区(workspace)的简要教程。它是一个实用的教程,而不是为了取代核心文档。Colcon简介colcon是ROS构建工具catkin_make、catkin_make_isolated、catkin_tools和ament_tools的迭代。关于colcon设计的更多信息,请参见A universal build tool。源代码可以在GitHub组织colcon中找到。基本概念一个ROS工作区是一个具有特定结构的目录。通常有一个src子目录。在这个子目录中,ROS软件包的源代码将被放在那里。通常情况下,该目录开始时是空的。colcon进行源外构建。默认情况下,它将创建以下目录作为src目录的同级目录:build目录将是存储中间文件的地方。每一个包都将创建一个子文件夹。install目录是每个软件包将被安装到的地方。默认情况下,每个软件包将被安装到一个单独的子目录中。log目录包含关于每个colcon调用的各种日志信息。与catkin相比,没有devel目录。创建一个工作空间mkdir -p ~/ros2_ws/src cd ~/ros2_ws现在工作空间只包含一个空文件夹 src:. └── src 1 directory, 0 files添加一些源文件将示例代码下载到src目录中:git clone https://github.com/ros2/examples src/examples -b humble现在工作空间应该结构如下:. └── src └── examples ├── CONTRIBUTING.md ├── LICENSE ├── rclcpp ├── rclpy └── README.md 4 directories, 3 filesSource底层为现有的ROS 2安装设置好环境可以为工作空间提供示例软件包构建所需的依赖项,这一点很重要。 这是通过source由二进制安装或源文件安装提供的安装脚本来实现的。我们称这种环境为底层。 我们的工作区 ros2_ws 将叠加在现有 ROS 2 安装之上。 通常,建议在计划迭代少量包时使用叠加,而不是将所有包放入同一个工作区。构建工作空间在工作区的根目录中,运行 colcon build。 由于 ament_cmake 等构建类型不支持开发空间的概念并且需要安装包,因此 colcon 支持选项 --symlink-install。 这允许通过更改源空间中的文件(例如 Python 文件或其他未编译的资源)来更改已安装的文件,以加快迭代速度。colcon build --symlink-install构建完成后,我们应该看到 build 、install 和 log 目录:. ├── build ├── install ├── log └── src 4 directories, 0 files运行测试要运行我们刚刚建立的软件包的测试,请运行以下命令:colcon testSource环境当colcon成功完成构建后,其输出结果将在 install 目录中。在你使用任何已安装的可执行文件或库之前,你将需要把它们添加到你的路径和库路径中。这些文件将把所有需要的元素添加到你的路径(path)和库路径(library path)中,colcon将在安装目录中生成bash/bat文件,以帮助设置环境。这些文件将把所有需要的元素添加到你的路径和库路径中,并提供软件包所导出的任何bash或shell命令。. install/setup.bash尝试演示设置好环境后,我们可以运行由colcon构建的可执行文件。让我们运行例子中的一个订阅者节点:ros2 run examples_rclcpp_minimal_subscriber subscriber_member_function在另一个终端中,让我们运行一个发布者节点(不要忘了设置环境):ros2 run examples_rclcpp_minimal_publisher publisher_member_function您应该看到来自发布者和订阅者的消息的数量在增加。创建自己的软件包(package)colcon使用REP 149中定义的package.xml规范(也支持format 2)。colcon支持多种构建类型。推荐的构建类型是ament_cmake和ament_python。也支持纯cmake包。ament_python构建的一个例子是ament_index_python package,其中setup.py是构建的主要入口。像demo_nodes_cpp这样的包使用ament_cmake构建类型,并使用CMake作为构建工具。为了方便起见,你可以使用 ros2 pkg create 这个工具来创建一个基于模板的新软件包。对于catkin用户,这相当于catkin_create_package。设置colcon_cd和tab补全命令 colcon_cd 允许你快速地将shell的当前工作目录改为软件包的目录。举例来说,colcon_cd some_ros_package将迅速把你带到~/ros2_install/src/some_ros_package目录。echo "source /usr/share/colcon_cd/function/colcon_cd.sh" >> ~/.bashrc echo "export _colcon_cd_root=/opt/ros/humble/" >> ~/.bashrc echo "source /usr/share/colcon_argcomplete/hook/colcon-argcomplete.bash" >> ~/.bashrc对于zsh,使用echo "source /usr/share/colcon_cd/function/colcon_cd.sh" >> ~/.zshrc echo "export _colcon_cd_root=/opt/ros/humble/" >> ~/.zshrc echo "source /usr/share/colcon_argcomplete/hook/colcon-argcomplete.zsh" >> ~/.zshrc提示如果你不想构建某个特定的软件包,在这个软件包的目录中建立一个名为COLCON_IGNORE的空文件。如果您想避免在 CMake 包中配置和构建测试,您可以传递:--cmake-args -DBUILD_TESTING=0。如果你想从一个包中运行一个特定的测试,使用如下命令:colcon test --packages-select YOUR_PKG_NAME --ctest-args -R YOUR_TEST_IN_PKG
文章
存储  ·  Shell  ·  Python
2023-03-18
Creating a package:创建一个软件包
@[toc]参考官方文档:Creating a packageROS 2软件包概念简介1. ROS 2软件包是什么?一个包可以被认为是你的 ROS 2 代码的容器。 如果您希望能够安装您的代码或与他人共享,那么您需要将其组织在一个包中。 借助软件包,您可以发布您的 ROS 2 作品并允许其他人轻松构建和使用它。 ROS 2 中的包创建使用 ament 作为其构建系统,并使用 colcon 作为其构建工具。 您可以使用官方支持的 CMake 或 Python 创建包,但确实存在其他构建类型。2. ROS 2软件包是如何构成的?ROS 2 Python 和 CMake 包都有自己的最低要求内容:CMakepackage.xml:包含关于软件包的元信息的文件CMakeLists.txt:描述了如何在包内构建代码的文件最简单的包可能有一个看起来像这样的文件结构:my_package/ CMakeLists.txt package.xmlPythonpackage.xml:包含关于软件包的元信息的文件setup.py:包含如何安装软件包的说明setup.cfg:当软件包有可执行文件时,需要setup.cfg,以便ros2 run能找到它们/<package_name>:一个与你的软件包同名的目录,被ROS 2工具用来寻找你的软件包,包含__init__.py最简单的包可能有一个看起来像这样的文件结构:my_package/ setup.py package.xml resource/my_package3. 工作空间中的包一个工作区可以包含任意数量的包,每个包都在自己的文件夹中。 您还可以在一个工作区(CMake、Python 等)中包含不同构建类型的包。 你不能有嵌套的包。 最佳做法是在您的工作区中有一个 src 文件夹,并在其中创建您的包。 这使工作区的顶层保持“干净”。 一个普通的工作区可能看起来像:workspace_folder/ src/ package_1/ CMakeLists.txt package.xml package_2/ setup.py package.xml resource/package_2 ... package_n/ CMakeLists.txt package.xml1. 创建一个包进入到在前面的教程中创建的ros_ws文件夹中:cd ~/ros2_ws/src在ROS 2中,创建一个新包的命令语法是:CMake:ros2 pkg create --build-type ament_cmake <package_name>Python:ros2 pkg create --build-type ament_python <package_name>对于本教程,您将使用可选参数 --node-name 来在包中创建一个简单的 Hello World 类型可执行文件。 在您的终端中输入以下命令:CMake:ros2 pkg create --build-type ament_cmake --node-name my_node my_packagePython:ros2 pkg create --build-type ament_python --node-name my_node my_package现在你将在工作区的src目录下有一个新的文件夹,名为my_package。运行该命令后,你的终端将返回以下信息:CMake:going to create a new package package name: my_package destination directory: /home/user/ros2_ws/src package format: 3 version: 0.0.0 description: TODO: Package description maintainer: ['<name> <email>'] licenses: ['TODO: License declaration'] build type: ament_cmake dependencies: [] node_name: my_node creating folder ./my_package creating ./my_package/package.xml creating source and include folder creating folder ./my_package/src creating folder ./my_package/include/my_package creating ./my_package/CMakeLists.txt creating ./my_package/src/my_node.cppPython:going to create a new package package name: my_package destination directory: /home/user/ros2_ws/src package format: 3 version: 0.0.0 description: TODO: Package description maintainer: ['<name> <email>'] licenses: ['TODO: License declaration'] build type: ament_python dependencies: [] node_name: my_node creating folder ./my_package creating ./my_package/package.xml creating source folder creating folder ./my_package/my_package creating ./my_package/setup.py creating ./my_package/setup.cfg creating folder ./my_package/resource creating ./my_package/resource/my_package creating ./my_package/my_package/__init__.py creating folder ./my_package/test creating ./my_package/test/test_copyright.py creating ./my_package/test/test_flake8.py creating ./my_package/test/test_pep257.py creating ./my_package/my_package/my_node.py你可以看到自动生成的新软件包的文件。2. 构建软件包将包放在工作区中特别有价值,因为您可以通过在工作区根目录中运行 colcon build 来一次构建许多包。 否则,您将不得不单独构建每个包。 返回到工作区的根目录:cd ~/ros2_ws然后构建软件包:colcon build回想一下上一个教程,您的 ros2_ws 中也有 ros_tutorials 包。 您可能已经注意到运行 colcon build 也会构建 turtlesim 包。 当您的工作区中只有几个包时这很好,但是当有很多包时,colcon 构建可能需要很长时间。 下次只构建 my_package 包,你可以运行:colcon build --packages-select my_package3. source设置文件要使用你的新包和可执行文件,首先打开一个新的终端,并source你的ROS 2主安装。source /opt/ros/humble/setup.bash然后,在ros2_ws目录下,运行下面的命令,source你的工作空间:. install/local_setup.bash现在,你的工作空间已经被添加到你的路径(path)中,你将能够使用你的新包的可执行文件。4. 使用包要运行你在创建软件包时使用 --node-name 参数创建的可执行文件,请输入以下命令:ros2 run my_package my_node终端返回信息如下:CMake:hello world my_package packagePython:Hi from my_package.5. 检查软件包内容在 ros2_ws/src/my_package 里面,你会看到 ros2 pkg 自动生成的文件和文件夹:CMake:CMakeLists.txt include package.xml srcmy_node.cpp在src目录里面。这是你将来所有自定义C++节点的地方。Python:my_package package.xml resource setup.cfg setup.py testmy_node.py在my_package目录里面。这是你将来所有自定义Python节点的地方。6. 自定义 package.xml你可能已经注意到在创建你的软件包后的返回信息中,描述(description)和许可(license)字段包含了TODO注释。这是因为包的描述和许可声明不是自动设置的,但如果你想发布你的包,则需要设置。维护者(maintainer)字段可能也需要填入。在ros2_ws/src/my_package,用你喜欢的文本编辑器打开package.xml:CMake:<?xml version="1.0"?> <?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?> <package format="3"> <name>my_package</name> <version>0.0.0</version> <description>TODO: Package description</description> <maintainer email="user@todo.todo">user</maintainer> <license>TODO: License declaration</license> <buildtool_depend>ament_cmake</buildtool_depend> <test_depend>ament_lint_auto</test_depend> <test_depend>ament_lint_common</test_depend> <export> <build_type>ament_cmake</build_type> </export> </package>Python:<?xml version="1.0"?> <?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?> <package format="3"> <name>my_package</name> <version>0.0.0</version> <description>TODO: Package description</description> <maintainer email="user@todo.todo">user</maintainer> <license>TODO: License declaration</license> <test_depend>ament_copyright</test_depend> <test_depend>ament_flake8</test_depend> <test_depend>ament_pep257</test_depend> <test_depend>python3-pytest</test_depend> <export> <build_type>ament_python</build_type> </export> </package>在维护者(maintainer)一行输入你的名字和电子邮件然后,编辑描述(description)行以总结该软件包:<description>Beginner client libraries tutorials practice package</description>然后更新许可证(license)一行。你可以在这里阅读更多关于开放源代码许可证的信息。由于这个包只用于实践,所以使用任何许可证都是安全的。我们使用Apache许可证2.0(Apache License 2.0):<license>Apache License 2.0</license>完成编辑后,不要忘记保存。在许可证标签下方,您会看到一些以 _depend 结尾的标签名称。 这是你的 package.xml 将列出它对其他包的依赖关系的地方,供 colcon 搜索。 my_package 很简单,没有任何依赖关系,但您会在接下来的教程中看到这个空间的使用。Python:setup.py文件包含与package.xml相同的描述、维护者和许可字段,所以你也需要设置这些字段。它们需要在两个文件中完全匹配。版本和名称(package_name)也需要完全匹配,并且应该在两个文件中自动填充。from setuptools import setup package_name = 'my_py_pkg' setup( name=package_name, version='0.0.0', packages=[package_name], data_files=[ ('share/ament_index/resource_index/packages', ['resource/' + package_name]), ('share/' + package_name, ['package.xml']), ], install_requires=['setuptools'], zip_safe=True, maintainer='TODO', maintainer_email='TODO', description='TODO: Package description', license='TODO: License declaration', tests_require=['pytest'], entry_points={ 'console_scripts': [ 'my_node = my_py_pkg.my_node:main' ], }, )编辑维护者(maintainer)、维护者邮件(maintainer_email)和描述行(description),使之与package.xml匹配。
文章
安全  ·  Apache  ·  C++  ·  Python  ·  容器
2023-03-19
FTP:文件传输协议
RFC959:FILE TRANSFER PROTOCOL (FTP),October 1985本备忘录的状态本备忘录是文件传输协议 (File Transfer Protocol,FTP) 的官方规范。本备忘录的分发不受限制。 此版本的规范中包含以下新的可选命令:CDUP(Change to Parent Directory,更改到父目录)、SMNT(Structure Mount,结构挂载)、STOU(Store Unique,唯一存储)、RMD(Remove Directory,删除目录)、MKD(Make Directory,制作目录)、PWD(Print Directory,打印目录)和 SYST(System,系统)。请注意,此规范与以前的版本兼容。1、 介绍FTP 的目标是:1) 促进文件(计算机程序和/或数据)的共享,2) 鼓励间接或隐式(通过程序)使用远程计算机,3) 保护用户免受文件存储系统的变化影响主机,以及4) 可靠、高效地传输数据。 FTP 虽然可由用户在终端直接使用,但主要设计为供程序使用。本规范的尝试是通过简单、易于实现的协议设计来满足maxi-hosts、mini-hosts、个人工作站和TACs用户的多样化需求。本文假设您了解传输控制协议 (Transmission Control Protocol,TCP) [2] 和 Telnet 协议 [TELNET协议规范]。这些文档包含在 ARPA-Internet 协议手册 [1] 中。2、 概述在本节中,将讨论历史、术语和 FTP 模型。本节中定义的术语只是那些在 FTP 中具有特殊意义的术语。某些术语非常特定于 FTP 模型;一些读者可能希望在查看术语时转向有关 FTP 模型的部分。2.1、 历史多年来,FTP 经历了漫长的发展。附录3 是与 FTP 相关的征求意见文档的时间顺序汇编。其中包括 1971 年首次提出的文件传输机制,该机制是为在麻省理工学院的主机上实现而开发的。(RFC 114),以及 RFC 141 中的评论和讨论。RFC 172 为主机(包括终端 IMP)之间的文件传输提供了一个面向用户的协议。将此修订为 RFC 265,重申 FTP 以进行额外审查,而 RFC 281 建议进一步更改。 1982 年 1 月在 RFC 294 中提议使用“设置数据类型”事务。RFC 354 废弃了 RFC 264 和 265。文件传输协议现在被定义为 ARPANET 上主机之间文件传输的协议,FTP 的主要功能定义为在主机之间高效可靠地传输文件,并允许方便地使用远程文件存储能力。 RFC 385 进一步评论了错误、重点和协议的补充,而 RFC 414 提供了关于工作服务器和用户 FTP 的状态报告。 1973 年发布的 RFC 430(在其他 RFC 中不胜枚举)对 FTP 提出了进一步的评论。最后,一个“官方”的 FTP 文档被发布为 RFC 454。到 1973 年 7 月,与 FTP 的最后一个版本相比,发生了相当大的变化,但总体结构保持不变。 RFC 542 作为新的“官方”规范发布以反映这些更改。但是,许多基于旧规范的实现并未更新。1974 年,RFC 607 和 614 继续对 FTP 进行评论。 RFC 624 提出了进一步的设计更改和小的修改。 1975 年,名为“Leaving Well Enough Alone”的 RFC 686 讨论了所有早期和晚期 FTP 版本之间的差异。 RFC 691 提出了 RFC 686 的小修订版,涉及打印文件的主题。在从 NCP 过渡到作为底层协议的 TCP 的推动下,在 RFC 765 作为用于 TCP 的 FTP 规范的所有上述努力中诞生了凤凰。FTP 规范的当前版本旨在更正一些小的文档错误,改进对某些协议功能的解释,并添加一些新的可选命令。特别是,此版本的规范中包含了以下新的可选命令:CDUP - Change to Parent Directory,更改为父目录SMNT - Structure Mount,结构挂载STOU - Store Unique,唯一存储RMD - Remove Directory,删除目录MKD - Make Directory,制作目录PWD - Print Directory,打印目录SYST – System,系统该规范与以前的版本兼容。符合先前规范的程序应该自动符合本规范。2.2、 术语ASCIIASCII 字符集在 ARPA-Internet 协议手册中定义。在 FTP 中,ASCII 字符被定义为八位代码集的下半部分(即最高有效位为零)。访问控制访问控制(access controls)定义了用户对系统使用和该系统中文件的访问权限。访问控制是必要的,以防止未经授权或意外使用文件。调用访问控制是服务器 FTP 进程的特权。字节大小FTP 中有两种感兴趣的字节大小(byte size):文件的逻辑字节大小和用于传输数据的传输字节大小。传输字节大小始终为 8 位。传输字节大小不一定是系统中要存储数据的字节大小,也不一定是用于解释数据结构的逻辑字节大小。控制连接USER-PI 和 SERVER-PI 之间用于交换命令和回复的通信路径。此连接遵循 Telnet 协议。数据连接以指定的模式和类型传输数据的全双工连接。传输的数据可以是文件的一部分、整个文件或多个文件。该路径可以在服务器-DTP 和用户-DTP 之间,或在两个服务器-DTP 之间。数据端口被动数据传输过程在数据端口(data port)上“侦听”来自主动传输过程的连接,以便打开数据连接。DTP数据传输过程(data transfer process)建立和管理数据连接。 DTP 可以是被动的或主动的。end-of-file行尾(end-of-file)顺序定义了打印行的分隔。顺序是回车,然后是换行。EOF文件结束条件,用于定义正在传输的文件的结尾。EOR记录结束(end-of-record)条件,定义正在传输的记录的结束。错误恢复允许用户从某些错误中恢复的过程,例如主机系统或传输过程的故障。在 FTP 中,错误恢复(error recovery)可能涉及在给定检查点重新启动文件传输。FTP命令一组命令,包含从用户 FTP 流向服务器 FTP 进程的控制信息。文件一组任意长度的有序计算机数据(包括程序),由路径名唯一标识。模式通过数据连接传输数据的模式(mode)。该模式定义了传输过程中的数据格式,包括 EOR 和 EOF。 FTP 中定义的传输模式在传输模式部分中描述。NVTTelnet 协议中定义的网络虚拟终端(Network Virtual Terminal)。NVFS网络虚拟文件系统(Network Virtual File System)。使用标准命令和路径名约定定义标准网络文件系统的概念。页一个文件可以被构造为一组称为页面(page)的独立部分。 FTP 支持将不连续文件作为独立索引页面进行传输。路径名路径名(pathname)被定义为必须由用户输入文件系统以识别文件的字符串。路径名通常包含设备和/或目录名称,以及文件名规范。 FTP 尚未指定标准路径名约定。每个用户都必须遵循传输中涉及的文件系统的文件命名约定。PI协议解释器(protocol interpreter)。协议的用户端和服务器端在用户 PI 和服务器 PI 中实现了不同的角色。记录一个顺序文件可以被构造为许多称为记录(record)的连续部分。 FTP 支持记录结构,但文件不需要具有记录结构。回复回复(reply)是响应 FTP 命令从服务器通过控制连接发送给用户的确认(肯定或否定)。回复的一般形式是完成代码(包括错误代码)后跟文本字符串。代码供程序使用,文本通常供人类用户使用。服务器-DTP数据传输过程在其正常的“活动”状态下,与“侦听”数据端口建立数据连接。它设置传输和存储参数,并根据来自其 PI 的命令传输数据。 DTP 可以置于“被动”状态以进行侦听,而不是在数据端口上发起连接。服务器-FTP进程一个进程或一组进程,与用户 FTP 进程以及可能的另一个服务器协作执行文件传输功能。这些功能包括协议解释器 (protocol interpreter,PI) 和数据传输过程 (data transfer process,DTP)。服务器PI服务器协议解释器(protocol interpreter)在端口 L 上“侦听”来自用户 PI 的连接并建立控制通信连接。它从用户 PI 接收标准 FTP 命令,发送回复,并管理服务器 DTP。类型用于数据传输和存储的数据表示类型(type)。类型意味着数据存储时间和数据传输时间之间的某些转换。 FTP 中定义的表示类型在“建立数据连接”部分中描述。用户希望获得文件传输服务的个人或代表个人的进程。人类用户(user)可以直接与服务器 FTP 进程交互,但首选使用用户 FTP 进程,因为协议设计侧重于自动机。用户-DTP数据传输进程在数据端口上“侦听”来自服务器 FTP 进程的连接。如果两台服务器在它们之间传输数据,则用户 DTP 处于非活动状态。用户FTP进程一组功能,包括协议解释器、数据传输过程和用户界面,它们与一个或多个服务器-FTP 过程共同执行文件传输功能。用户界面允许在与用户的命令-回复对话中使用本地语言。用户PI用户协议解释器启动从它的端口 U 到服务器 FTP 进程的控制连接,启动 FTP 命令,并在该进程是文件传输的一部分时管理用户 DTP。2.3、 FTP模型考虑到上述定义,可以为 FTP 服务绘制以下模型(如图 1 所示)。图 1 FTP 使用模型注意: 1. 数据连接可用于任一方向。2. 数据连接不必一直存在。在图 1 描述的模型中,用户协议解释器启动控制连接。控制连接遵循 Telnet 协议。在用户启动时,标准的 FTP 命令由用户 PI 生成并通过控制连接传输到服务器进程。 (用户可以建立到服务器-FTP 的直接控制连接,例如从 TAC 终端,并独立生成标准 FTP 命令,绕过用户-FTP 过程。)标准回复从服务器-PI 发送到用户- PI 通过控制连接响应命令。FTP 命令指定数据连接的参数(数据端口、传输模式、表示类型和结构)和文件系统操作的性质(存储、检索、追加、删除等)。用户-DTP 或其指定者应“监听”指定的数据端口,服务器根据指定的参数发起数据连接和数据传输。需要注意的是,数据端口不必在通过控制连接发起 FTP 命令的同一台主机上,但用户或用户 FTP 进程必须确保在指定的数据端口上“监听”。还应注意,数据连接可用于同时发送和接收。在另一种情况下,用户可能希望在两个主机之间传输文件,这两个主机都不是本地主机。用户建立到两台服务器的控制连接,然后安排它们之间的数据连接。以这种方式,控制信息被传递给用户 PI,但数据在服务器数据传输过程之间传输。以下是此服务器-服务器交互的模型。图2该协议要求在进行数据传输时打开控制连接。用户有责任在使用完 FTP 服务后请求关闭控制连接,而采取行动的是服务器。如果在没有命令的情况下关闭控制连接,服务器可能会中止数据传输。FTP和Telnet的关系:FTP 在控制连接上使用 Telnet 协议。这可以通过两种方式实现:第一,用户PI或服务器PI可以直接在自己的程序中实现Telnet协议的规则;或者,第二,用户PI或服务器PI可以利用系统中现有的Telnet模块。易于实现、共享代码和模块化编程支持第二种方法。效率和独立性支持第一种方法。实际上,FTP 对 Telnet 协议的依赖很少,因此第一种方法不一定涉及大量代码。3、 数据传输功能文件仅通过数据连接传输。控制连接用于传输命令,这些命令描述了要执行的功能以及对这些命令的回复(参见 FTP 回复部分)。几个命令与主机之间的数据传输有关。这些数据传输命令包括指定如何传输数据位的 MODE 命令,以及用于定义数据表示方式的 STRUcture 和 TYPE 命令。传输和表示基本独立,但“Stream”传输模式取决于文件结构属性,如果使用“压缩”传输模式,则填充字节的性质取决于表示类型。3.1、 数据表示和存储数据从发送主机中的存储设备传输到接收主机中的存储设备。通常需要对数据执行某些转换,因为两个系统中的数据存储表示不同。例如,NVT-ASCII 在不同的系统中有不同的数据存储表示。 DEC TOPS-20s 通常将 NVT-ASCII 存储为五个 7 位 ASCII 字符,在 36 位字中左对齐。 IBM 大型机将 NVT-ASCII 存储为 8 位 EBCDIC 代码。 Multics 将 NVT-ASCII 存储为 36 位字中的四个 9 位字符。在不同系统之间传输文本时,最好将字符转换为标准 NVT-ASCII 表示。发送站点和接收站点必须在标准表示与其内部表示之间执行必要的转换。在具有不同字长的主机系统之间传输二进制数据(不是字符代码)时,会出现不同的表示问题。发送方应该如何发送数据以及接收方如何存储数据并不总是很清楚。例如,当将 32 位字节从 32 位字长系统传输到 36 位字长系统时,可能需要(出于效率和实用性的原因)将 32 位字节右对齐存储在后一种系统中的 36 位字中。在任何情况下,用户都应该可以选择指定数据表示和转换函数。应该注意的是,FTP 提供了非常有限的数据类型表示。超出此有限能力所需的转换应由用户直接执行。3.1.1、 数据类型数据表示由用户指定表示类型在 FTP 中处理。这种类型可以隐式(如在 ASCII 或 EBCDIC 中)或显式(如在本地字节中)定义用于解释的字节大小,称为“逻辑字节大小”。请注意,这与用于通过数据连接传输的字节大小无关,称为“传输字节大小”,不应将两者混淆。例如,NVT-ASCII 的逻辑字节大小为 8 位。如果类型是本地字节,则 TYPE 命令具有指定逻辑字节大小的强制性第二个参数。传输字节大小始终为 8 位。3.1.1.1、 ASCII 类型这是默认类型,必须被所有 FTP 实现接受。它主要用于传输文本文件,除非两个主机都发现 EBCDIC 类型更方便。发送方将数据从内部字符表示转换为标准的 8 位 NVT-ASCII 表示(请参阅 Telnet 规范)。接收者将数据从标准格式转换为他自己的内部格式。根据 NVT 标准, 序列应在必要时用于表示一行文本的结尾。 (请参阅数据表示和存储部分末尾的文件结构讨论。)使用标准 NVT-ASCII 表示意味着数据必须被解释为 8 位字节。下面讨论 ASCII 和 EBCDIC 类型的格式参数。3.1.1.2、 EBCDIC 类型此类型用于在使用 EBCDIC 作为其内部字符表示的主机之间进行有效传输。对于传输,数据表示为 8 位 EBCDIC 字符。字符代码是 EBCDIC 和 ASCII 类型的功能规范之间的唯一区别。出于表示结构的目的,行尾(与记录尾相反——参见结构的讨论)可能很少与 EBCDIC 类型一起使用,但在必要时应使用 字符。3.1.1.3、 IMAGE类型数据作为连续位发送,为了传输,这些位被打包成 8 位传输字节。接收站点必须将数据存储为连续位。存储系统的结构可能需要将文件(或每个记录,对于记录结构的文件)填充到某个方便的边界(字节、字或块)。这种填充必须全为零,只能出现在文件的末尾(或每条记录的末尾),并且必须有一种方法来识别填充位,以便在检索文件时可以将它们剥离。应该很好地宣传填充转换,以使用户能够在存储站点处理文件。图像类型用于文件的高效存储和检索以及二进制数据的传输。建议所有 FTP 实现都接受此类型。3.1.1.4、 LOCAL类型数据以逻辑字节传输,其大小由强制性的第二个参数字节大小指定。 Byte size 的值必须是十进制整数;没有默认值。逻辑字节大小不一定与传输字节大小相同。如果字节大小不同,则逻辑字节应连续打包,不考虑传输字节边界并在末尾添加任何必要的填充。当数据到达接收主机时,它将以依赖于逻辑字节大小和特定主机的方式进行转换。这种转换必须是可逆的(即,如果使用相同的参数,则可以检索到相同的文件)并且应该由 FTP 实现者很好地宣传。例如,用户向具有 32 位字的主机发送 36 位浮点数可以将该数据作为逻辑字节大小为 36 的本地字节发送。然后接收主机将需要存储逻辑字节,以便他们可以很容易地被操纵;在本例中,将 36 位逻辑字节放入 64 位双字就足够了。在另一个例子中,一对 36 位字长的主机可以使用 TYPE L 36 以字的形式相互发送数据。 数据将以 8 位传输字节打包发送,以便 9 个传输字节携带两个主机字。3.1.1.5、 格式控制ASCII 和 EBCDIC 类型也采用第二个(可选)参数;这是为了指示哪种垂直格式控件(如果有)与文件相关联。 FTP 中定义了以下数据表示类型:字符文件可以出于以下三个目的之一传输到主机:用于打印、存储和以后检索,或用于处理。如果发送文件进行打印,接收主机必须知道垂直格式控件是如何表示的。在第二种情况下,必须可以将文件存储在主机上,然后以完全相同的形式检索它。最后,应该可以将文件从一台主机移动到另一台主机并在第二台主机上处理该文件而不会出现不必要的麻烦。单一的 ASCII 或 EBCDIC 格式不能满足所有这些条件。因此,这些类型具有指定以下三种格式之一的第二个参数:3.1.1.5.1、 NON PRINT如果省略第二个(格式)参数,这是要使用的默认格式。所有 FTP 实现都必须接受非输出格式。该文件不需要包含垂直格式信息。如果将其传递给输出进程,则该进程可能会假定间距和边距的标准值。通常,这种格式将用于处理或仅用于存储的文件。3.1.1.5.2、 Telnet 格式控制该文件包含 ASCII/EBCDIC 垂直格式控件(即 、、、、),输出进程将对其进行适当解释。 ,在这个序列中,也表示行尾。3.1.1.5.3、 传输控制 (ASA)该文件包含 ASA (FORTRAN) 垂直格式控制字符。 (参见 RFC 740 附录 C;和 ACM 通信,第 7 卷,第 10 期,第 606 页,1964 年 10 月。)在根据 ASA 标准格式化的行或记录中,第一个字符不打印.相反,它应该用于确定在打印记录的其余部分之前应该发生的纸张的垂直移动。ASA 标准规定了以下控制字符:显然,输出进程必须有某种方式来区分结构实体的结尾。如果文件有记录结构(见下文),这没问题;记录将在传输和存储过程中明确标记。如果文件没有记录结构, 行尾序列用于分隔打印行,但这些格式效应器会被 ASA 控件覆盖。3.1.2、 数据结构除了不同的表示类型外,FTP 还允许指定文件的结构。 FTP中定义了三种文件结构:文件结构,这里没有内部结构,文件被认为是一个连续的数据字节序列,记录结构,其中文件由顺序记录组成,和页面结构,其中文件由独立的索引页面组成。如果未使用 STRUcture 命令,则默认采用文件结构,但所有 FTP 实现都必须接受“文本”文件(即具有 TYPE ASCII 或 EBCDIC 的文件)的文件和记录结构。文件的结构将影响文件的传输模式(参见传输模式部分)以及文件的解释和存储。文件的“自然”结构将取决于存储文件的主机。源代码文件通常以固定长度记录的形式存储在 IBM 大型机上,但在 DEC TOPS-20 上作为分隔为行的字符流(例如通过 )存储。如果要在此类不同站点之间传输文件有用,则一个站点必须有某种方式来识别另一个站点对文件的假设。由于一些站点天生面向文件,而另一些网站天生面向记录,如果将具有一种结构的文件发送到面向另一种结构的主机,则可能会出现问题。如果将带有记录结构的文本文件发送到面向文件的主机,则该主机应根据记录结构对文件应用内部转换。显然,这种转换应该是有用的,但它也必须是可逆的,以便可以使用记录结构检索相同的文件。在将具有文件结构的文件发送到面向记录的主机的情况下,存在主机应使用什么标准将文件划分为可在本地处理的记录的问题。如果此划分是必要的,则 FTP 实现应使用行尾序列, 用于 ASCII,或 用于 EBCDIC 文本文件,作为分隔符。如果 FTP 实现采用此技术,则必须准备好在使用文件结构检索文件时反转转换。3.1.2.1、 DATA结构如果未使用 STRUCture 命令,则默认采用文件结构(DATA STRUCTURES)。在文件结构中,没有内部结构,文件被认为是一个连续的数据字节序列。3.1.2.2、 记录结构所有 FTP 实现都必须接受“文本”文件(即具有 TYPE ASCII 或 EBCDIC 的文件)的记录结构(RECORD STRUCTURE)。在记录结构中,文件由顺序记录组成。3.1.2.3、 页面结构为了传输不连续的文件,FTP 定义了页面结构。这种类型的文件有时被称为“随机访问文件/random access files”,甚至被称为“多孔文件/holey files”。在这些文件中,有时还有与整个文件相关联的其他信息(例如,文件描述符),或与文件的一部分(例如,页面访问控制),或两者都有。在 FTP 中,文件的部分称为页面。为了提供各种页面大小和相关信息,每个页面都带有一个页面标题。页眉具有以下定义的字段:标题长度页头中包含该字节的逻辑字节数。最小标题长度为 4。页面索引文件此部分的逻辑页码。这不是本页的传输序号,而是用于标识文件本页的索引。数据长度页数据中的逻辑字节数。最小数据长度为 0。页面类型这是页面的类型。定义了以下页面类型:0 = 最后一页这用于指示分页结构化传输的结束。头部长度必须为4,数据长度必须为0。1 = 简单页面这是没有页面级关联控制信息的简单分页文件的正常类型。标头长度必须为 4。2 = 描述符页该类型用于传输文件整体的描述信息。3 = 访问控制页面此类型包括带有页面级访问控制信息的分页文件的附加头字段。标头长度必须为 5。可选字段更多的报头字段可用于提供每页控制信息,例如每页访问控制。所有字段的长度都是一个逻辑字节。逻辑字节大小由 TYPE 命令指定。有关更多详细信息和页面结构中的特定案例,请参阅附录1。关于参数的注意事项:如果检索的版本与最初传输的版本相同,则必须使用相同的参数存储和检索文件。相反,如果用于存储和检索文件的参数相同,则 FTP 实现必须返回与原始文件相同的文件。3.2、 建立数据连接传输数据的机制包括建立到适当端口的数据连接和选择传输参数。用户和服务器 DTP 都有一个默认数据端口。用户进程默认数据端口与控制连接端口(即 U)相同。服务器进程默认数据端口是与控制连接端口(即 L-1)相邻的端口。传输字节大小为 8 位字节。此字节大小仅与数据的实际传输相关;它与主机文件系统中的数据表示无关。被动数据传输过程(这可能是用户 DTP 或第二个服务器 DTP)应在发送传输请求命令之前“侦听”数据端口。 FTP 请求命令确定数据传输的方向。服务器在收到传输请求后,将启动与端口的数据连接。建立连接后,DTP 之间的数据传输开始,服务器 PI 向用户 PI 发送确认回复。每个 FTP 实现都必须支持使用默认数据端口,并且只有 USER-PI 可以发起对非默认端口的更改。用户可以使用 PORT 命令指定备用数据端口。用户可能希望将文件转储到 TAC 行式输出或从第三方主机检索。在后一种情况下,用户 PI 与两个服务器 PI 建立控制连接。然后(通过 FTP 命令)告诉一台服务器“侦听”另一台将启动的连接。用户 PI 向一个服务器 PI 发送一个 PORT 命令,指示另一个服务器的数据端口。最后,两者都被发送适当的传输命令。在用户控制器和服务器之间发送的命令和回复的确切顺序在 FTP 回复部分定义。通常,服务器负责维护数据连接——启动和关闭它。例外情况是当用户 DTP 以需要关闭连接以指示 EOF 的传输模式下发送数据时。服务器必须在以下条件下关闭数据连接:1、服务器已经完成发送数据的传输模式,需要close表示EOF。2、服务器收到用户的 ABORT 命令。3、端口规格由用户的命令更改。4、控制连接被合法或以其他方式关闭。5、发生不可恢复的错误情况。否则关闭是一个服务器选项,服务器必须仅通过 250 或 226 回复向用户进程指示执行该选项。3.3、 数据连接管理默认数据连接端口:所有 FTP 实现都必须支持使用默认数据连接端口,并且只有 User-PI 可以启动非默认端口的使用。协商非默认数据端口:用户 PI 可以使用 PORT 命令指定非默认用户端数据端口。 User-PI 可以通过 PASV 命令请求服务器端识别一个非默认的服务器端数据端口。由于连接是由地址对定义的,这些操作中的任何一个都足以获得不同的数据连接,但仍然允许执行这两个命令以在数据连接的两端使用新端口。数据连接的重用:当使用数据传输的流模式时,必须通过关闭连接来指示文件的结尾。如果要在会话中传输多个文件,这会导致问题,因为 TCP 需要将连接记录保留一段时间以保证可靠通信。因此无法立即重新打开连接。这个问题有两种解决方案:第一个是协商一个非默认端口。二是使用另一种传输方式。关于传输模式的评论。流传输模式本质上是不可靠的,因为人们无法确定连接是否过早关闭。其他传输模式(块、压缩)不会关闭连接以指示文件结束。它们具有足够的 FTP 编码,可以解析数据连接以确定文件的结尾。因此,使用这些模式可以让数据连接保持打开状态以进行多个文件传输。3.4、 传输模式传输数据的下一个考虑因素是选择合适的传输模式。共有三种模式:一种是格式化数据并允许重新启动程序;一种还压缩数据以实现高效传输;以及一种在很少或不进行处理的情况下传递数据的方法。在最后一种情况下,模式与结构属性交互以确定处理类型。在压缩模式下,表示类型决定了填充字节。所有数据传输都必须以文件结束符 (EOF) 完成,该文件结束符可以通过关闭数据连接来明确说明或暗示。对于具有记录结构的文件,所有记录结束标记 (EOR) 都是明确的,包括最后一个。对于以页面结构传输的文件,使用“最后一页”页面类型。注意:在本节的其余部分,字节表示“传输字节”,除非另有明确说明。为了标准化传输的目的,发送主机将其内部的行尾或记录结束表示转换为传输模式和文件结构规定的表示,而接收主机将对其内部表示进行逆转换。 IBM 大型机记录计数字段在另一台主机上可能无法识别,因此记录结束信息可能在流模式下作为两字节控制代码传输,或者作为块或压缩模式描述符中的标志位传输。没有记录结构的 ASCII 或 EBCDIC 文件中的行尾应分别由 或 指示。由于这些转换意味着某些系统需要额外的工作,因此传输非记录结构化文本文件的相同系统可能希望使用二进制表示和流模式进行传输。FTP中定义了以下传输模式:3.4.1、 流模式数据以字节流的形式传输。对使用的表示类型没有限制;记录结构是允许的。在记录结构文件中,EOR 和 EOF 将分别由一个两字节的控制代码表示。控制代码的第一个字节全是 1,即转义字符。对于 EOR,第二个字节的低位将打开,其他位置为零,而 EOF 的第二个低位将打开;也就是说,该字节的 EOR 值为 1,EOF 值为 2。 EOR 和 EOF 可以通过打开两个低位(即值 3)在传输的最后一个字节上一起指示。如果打算将全 1 的一个字节作为数据发送,则应在控制代码的第二个字节中重复该字节。如果结构是文件结构,EOF由发送主机关闭数据连接指示,所有字节都是数据字节。3.4.2、 块模式该文件作为一系列数据块传输,前面有一个或多个报头字节。头字节包含计数字段和描述符代码。计数字段以字节为单位指示数据块的总长度,从而标记下一个数据块的开始(没有填充位)。描述符代码定义:文件中的最后一个块 (EOF) 记录中的最后一个块 (EOR)、重新启动标记(请参阅错误恢复和重新启动部分)或可疑数据(即,正在传输的数据被怀疑有错误并且是不可靠的)。最后一个代码不是用于 FTP 中的错误控制。它的动机是交换某些类型的数据(例如地震或天气数据)的站点希望发送和接收所有数据,尽管本地错误(例如“磁带读取错误”),但在传输中表明某些部分是可疑的)。在这种模式下允许记录结构,并且可以使用任何表示类型。头部由三个字节组成。头信息的24位中,低16位代表字节数,高8位代表描述符代码,如下所示。区块头描述符代码由描述符字节中的位标志指示。分配了四个代码,其中每个代码编号是字节中相应位的十进制值。通过这种编码,对于特定块可能存在不止一个描述符编码条件。 可以标记尽可能多的位。重新启动标记作为整数个 8 位字节嵌入数据流中,表示控制连接上使用的语言(例如,默认值--NVT-ASCII)中的可打印字符。 (空格,在适当的语言中)不得在重新启动标记内使用。例如,要传输一个 6 个字符的标记,将发送以下内容:3.4.3、 压缩模式要发送的信息有三种:常规数据,以字节串形式发送; 压缩数据,包括复制或填充; 和控制信息,以两字节的转义序列发送。 如果发送了 n>0 个字节(最多 127 个)的常规数据,则这 n 个字节前面是一个字节,其中最左边的位设置为 0,最右边的 7 位包含数字 n。字节串:n 个数据字节的字符串 d(1),..., d(n)计数 n 必须为正。为了压缩数据字节 d 的 n 个复制的字符串,发送以下 2 个字节:复制字节:一串n个填充字节可以压缩成一个字节,填充字节随表示类型不同而不同。 如果类型为 ASCII 或 EBCDIC,则填充字节为 (空格,ASCII 代码 32,EBCDIC 代码 64)。 如果类型是图像或本地字节,则填充符为零字节。填充字符串:转义序列是一个双字节,第一个是转义字节(全为零),第二个包含块模式中定义的描述符代码。描述符代码与块模式中的含义相同,适用于后续的字节串。压缩模式对于在非常大的网络传输中以一点额外的 CPU 成本获得增加的带宽很有用。它可以最有效地用于减小输出文件的大小,例如由 RJE 主机生成的文件。3.5、 错误恢复和重启没有检测数据传输中丢失或加扰的位的规定;这一级别的错误控制由 TCP 处理。但是,提供了重新启动程序来保护用户免受严重的系统故障(包括主机、FTP 进程或底层网络的故障)。重启程序仅针对数据传输的块和压缩模式定义。它要求数据的发送方在带有一些标记信息的数据流中插入一个特殊的标记代码。标记信息仅对发送方有意义,但必须由控制连接的默认或协商语言(ASCII 或 EBCDIC)的可打印字符组成。标记可以表示位计数、记录计数或系统可以用来识别数据检查点的任何其他信息。数据接收方如果执行了重启程序,就会在接收系统中标记该标记的对应位置,并将该信息返回给用户。在系统出现故障的情况下,用户可以通过 FTP 重新启动程序识别标记点来重新启动数据传输。以下示例说明了重新启动过程的使用。数据的发送方在数据流中的方便点插入适当的标记块。接收主机在其文件系统中标记相应的数据点,并将最后已知的发送者和接收者标记信息直接或通过 110 回复中的控制连接(取决于谁是发送者)传送给用户。在系统故障的情况下,用户或控制器进程通过发送带有服务器标记代码作为参数的重新启动命令,在最后一个服务器标记处重新启动服务器。重启命令通过控制连接传输,紧随其后的是发生系统故障时正在执行的命令(如 RETR、STOR 或 LIST)。4、 文件传输功能从用户 PI 到服务器 PI 的通信通道建立为从用户到标准服务器端口的 TCP 连接。用户协议解释器负责发送FTP命令并解释收到的回复;服务器 PI 解释命令、发送回复并指示其 DTP 建立数据连接并传输数据。如果数据传输的第二方(被动传输过程)是user-DTP,那么它是通过user-FTP主机的内部协议进行管理的;如果它是第二个服务器 DTP,则它由来自用户 PI 的 PI on 命令控制。 FTP 回复将在下一节讨论。在本节中一些命令的描述中,明确说明可能的回复是有帮助的。4.1、 FTP 命令4.1.1、 访问控制命令以下命令指定访问控制标识符(命令代码显示在括号中)。用户名(USER)参数字段是标识用户的 Telnet 字符串。用户标识是服务器访问其文件系统所需的标识。此命令通常是用户在建立控制连接后发送的第一个命令(某些服务器可能需要此命令)。某些服务器也可能需要密码和/或帐户命令形式的附加标识信息。服务器可以允许在任何时候输入新的 USER 命令以更改访问控制和/或记帐信息。这具有刷新已提供的任何用户、密码和帐户信息并再次开始登录序列的效果。所有传输参数都保持不变,任何正在进行的文件传输都在旧的访问控制参数下完成。密码 (PASS)参数字段是指定用户密码的 Telnet 字符串。此命令必须紧跟在用户名命令之后,并且对于某些站点,完成用户的识别以进行访问控制。由于密码信息非常敏感,因此通常需要“屏蔽”它或禁止输入。服务器似乎没有万无一失的方法来实现这一点。因此,用户 FTP 进程有责任隐藏敏感的密码信息。帐户 (ACCT)参数字段是标识用户帐户的 Telnet 字符串。该命令不一定与 USER 命令相关,因为某些站点可能需要帐户才能登录,而其他站点仅用于特定访问,例如存储文件。在后一种情况下,命令可能随时到达。有回复代码来区分这些情况的自动化:当登录需要帐户信息时,对成功的 PASSword 命令的响应是回复代码 332。另一方面,如果登录不需要帐户信息,则回复 a成功的 PASSword 命令是 230;如果在对话中稍后发出的命令需要帐户信息,则服务器应分别返回 332 或 532 回复,具体取决于它是存储(等待接收 ACCounT 命令)还是丢弃该命令。更改工作目录 (CWD)此命令允许用户使用不同的目录或数据集进行文件存储或检索,而无需更改其登录或记帐信息。传输参数同样不变。参数是指定目录或其他系统相关文件组指示符的路径名。更改到父目录 (CDUP)此命令是 CWD 的一个特例,包含该命令是为了简化在具有不同命名父目录语法的操作系统之间传输目录树的程序的实现。回复码应与CWD 的回复码相同。有关详细信息,请参阅附录2。结构挂载 (SMNT)此命令允许用户挂载不同的文件系统数据结构,而无需更改其登录或记帐信息。传输参数同样不变。参数是指定目录或其他系统相关文件组指示符的路径名。重新初始化(REIN)此命令会终止 USER,刷新所有 I/O 和帐户信息,但允许完成正在进行的任何传输。所有参数都重置为默认设置,控制连接保持打开状态。这与用户在打开控制连接后立即发现自己的状态相同。可能会出现 USER 命令。退出(QUIT)此命令终止用户,如果文件传输不在进行中,服务器将关闭控制连接。如果文件传输正在进行,连接将保持打开状态以响应结果,然后服务器将关闭它。如果用户进程正在为多个用户传输文件,但不希望为每个用户关闭然后重新打开连接,则应使用 REIN 命令而不是 QUIT。控制连接的意外关闭将导致服务器采取有效的中止 (ABOR) 和注销 (QUIT) 操作。4.1.2、 传输参数命令所有数据传输参数都有默认值,只有当默认参数值需要改变时,才需要指定数据传输参数的命令。默认值是最后指定的值,或者如果未指定值,则标准默认值如此处所述。这意味着服务器必须“记住”适用的默认值。命令可以按任何顺序排列,但它们必须在 FTP 服务请求之前。以下命令指定数据传输参数:数据端口(PORT)参数是用于数据连接的数据端口的 HOST-PORT 规范。用户和服务器数据端口都有默认值,一般情况下不需要此命令及其回复。如果使用此命令,则参数是 32 位 Internet 主机地址和 16 位 TCP 端口地址的串联。该地址信息被分成 8 位字段,每个字段的值作为十进制数(以字符串表示)传输。字段以逗号分隔。端口命令将是:PORT h1,h2,h3,h4,p1,p2其中 h1 是 Internet 主机地址的高 8 位。被动 (PASV)此命令请求服务器 DTP 在数据端口(不是其默认数据端口)上“侦听”并等待连接而不是在接收到传输命令时启动连接。 对此命令的响应包括此服务器正在侦听的主机和端口地址。表示类型(TYPE)参数指定数据表示和存储部分中描述的表示类型。 几种类型采用第二个参数。 第一个参数由单个 Telnet 字符表示,ASCII 和 EBCDIC 的第二个 Format 参数也是如此; 本地字节的第二个参数是一个十进制整数,表示字节大小。 参数由 (空格,ASCII 代码 32)分隔。为类型分配了以下代码:默认表示类型是 ASCII 非打印。 如果更改了 Format 参数,并且稍后仅更改了第一个参数,则 Format 将返回到非打印默认值。文件结构 (STRU)参数是一个单独的 Telnet 字符代码,指定了数据表示和存储部分中描述的文件结构。为结构分配了以下代码:F - File (no record structure) R - Record structure P - Page structure默认结构是文件。传输模式(MODE)参数是单个 Telnet 字符代码,指定传输模式部分中描述的数据传输模式。为传输模式分配了以下代码:S - Stream B - Block C - Compressed默认传输模式为 Stream。4.1.3、 FTP 服务命令FTP 服务命令定义了用户请求的文件传输或文件系统功能。 FTP 服务命令的参数通常是路径名。路径名的语法必须符合服务器站点约定(适用标准默认值)和控制连接的语言约定。建议的默认处理是使用最后指定的设备、目录或文件名,或为本地用户定义的标准默认值。除了“rename from”命令后面必须跟“rename to”命令并且重启命令后面必须跟中断的服务命令(例如,STOR或RETR)之外,命令可以是任何顺序。响应 FTP 服务命令传输的数据应始终通过数据连接发送,某些信息性回复除外。以下命令指定 FTP 服务请求:检索 (RETR)此命令使服务器 DTP 将路径名中指定的文件副本传输到数据连接另一端的服务器或用户 DTP。服务器站点上文件的状态和内容不受影响。存储 (STOR)该命令使服务器-DTP 接受通过数据连接传输的数据并将数据作为文件存储在服务器站点。如果路径名中指定的文件存在于服务器站点,则其内容应由正在传输的数据替换。如果路径名中指定的文件不存在,则会在服务器站点上创建一个新文件。唯一存储 (STOU)此命令的行为与 STOR 类似,不同之处在于将在当前目录中以对该目录唯一的名称创建结果文件。 250 Transfer Started 响应必须包含生成的名称。APPEND(带创建)(APPE)该命令使服务器-DTP 接受通过数据连接传输的数据并将数据存储在服务器站点的文件中。如果路径名中指定的文件存在于服务器站点,则数据应附加到该文件中;否则将在服务器站点创建路径名中指定的文件。分配(ALLO)某些服务器可能需要此命令来保留足够的存储空间以容纳要传输的新文件。参数应是一个十进制整数,表示要为文件保留的存储的字节数(使用逻辑字节大小)。对于使用记录或页面结构发送的文件,可能还需要最大记录或页面大小(以逻辑字节为单位);这由命令的第二个参数字段中的十进制整数表示。第二个参数是可选的,但是当出现时应该用三个 Telnet 字符 R 与第一个参数分开。该命令后面应跟有 STORe 或 APPEnd 命令。 ALLO 命令应该被那些不需要事先声明文件最大大小的服务器视为 NOOP(无操作),那些只对最大记录或页面大小感兴趣的服务器应该接受第一个参数并忽略它。重启(REST)参数字段表示要重新启动文件传输的服务器标记。此命令不会导致文件传输,而是跳过文件到指定的数据检查点。该命令应紧跟在适当的 FTP 服务命令之后,该命令将使文件传输恢复。重命名自 (RNFR)此命令指定要重命名的文件的旧路径名。此命令必须紧跟在指定新文件路径名的“重命名为”命令之后。重命名为(RNTO)此命令指定在紧接在前面的“rename from”命令中指定的文件的新路径名。这两个命令一起导致文件被重命名。中止(ABOR)此命令告诉服务器中止先前的 FTP 服务命令和任何相关的数据传输。 abort 命令可能需要“特殊操作”,如 FTP 命令部分所述,以强制服务器识别。如果之前的命令已经完成(包括数据传输),则不会采取任何行动。服务器不关闭控制连接,但必须关闭数据连接。服务器收到该命令有两种情况:(1)FTP服务命令已经完成,或者(2)FTP服务命令还在进行中。在第一种情况下,服务器关闭数据连接(如果它是打开的)并以 226 回复响应,表示成功处理了 abort 命令。第二种情况,服务器中止正在进行的FTP服务并关闭数据连接,返回426回复,表示服务请求异常终止。然后服务器发送 226 回复,表示成功处理了 abort 命令。删除(DELE)此命令会导致在服务器站点删除路径名中指定的文件。如果需要额外的保护级别(例如查询“您真的要删除吗?”),则应由用户 FTP 进程提供。删除目录 (RMD)此命令会导致将路径名中指定的目录作为目录(如果路径名是绝对的)或作为当前工作目录的子目录(如果路径名是相对的)删除。见附录二。创建目录 (MKD)此命令使路径名中指定的目录创建为目录(如果路径名是绝对的)或当前工作目录的子目录(如果路径名是相对的)。见附录二。打印工作目录 (PWD)此命令导致在回复中返回当前工作目录的名称。见附录二。列表(LIST)此命令会导致从服务器向被动 DTP 发送一个列表。如果路径名指定了一个目录或其他文件组,则服务器应传输指定目录中的文件列表。如果路径名指定了一个文件,那么服务器应该发送有关该文件的当前信息。空参数表示用户当前的工作目录或默认目录。数据传输通过 ASCII 类型或 EBCDIC 类型的数据连接进行。 (用户必须确保 TYPE 是适当的 ASCII 或 EBCDIC)。由于文件上的信息可能因系统而异,因此该信息可能难以在程序中自动使用,但对人类用户可能非常有用。名称列表 (NLST)此命令导致目录列表从服务器发送到用户站点。路径名应指定目录或其他特定于系统的文件组描述符;空参数意味着当前目录。服务器将返回文件名称流,不返回其他信息。数据将以 ASCII 或 EBCDIC 类型作为由 或 分隔的有效路径名字符串通过数据连接传输。 (同样,用户必须确保 TYPE 正确。)此命令旨在返回信息,程序可以使用这些信息来自动进一步处理文件。例如,在执行“多次获取”功能时。站点参数(SITE)服务器使用此命令来提供特定于其系统的服务,这些服务对于文件传输必不可少,但不够通用,无法作为命令包含在协议中。这些服务的性质和它们的语法规范可以在对 HELP SITE 命令的回复中说明。系统 (SYST)此命令用于查找服务器上的操作系统类型。答复应将当前版本的指定编号文档 [4] 中列出的系统名称作为其第一个单词。状态 (STAT)该命令将导致状态响应以回复的形式通过控制连接发送。该命令可以在文件传输期间发送(连同 Telnet IP 和同步信号 - 请参阅有关 FTP 命令的部分)在这种情况下,服务器将以正在进行的操作状态进行响应,或者它可以在文件之间发送转让。在后一种情况下,命令可能有一个参数字段。如果参数是路径名,则该命令类似于“list”命令,不同之处在于数据应通过控制连接传输。如果给出了部分路径名,服务器可能会以与该规范相关联的文件名或属性列表进行响应。如果没有给出参数,服务器应该返回关于服务器 FTP 进程的一般状态信息。这应该包括所有传输参数的当前值和连接状态。帮助(HELP)该命令将使服务器通过控制连接向用户发送有关其实现状态的有用信息。命令可以接受一个参数(例如,任何命令名称)并返回更具体的信息作为响应。回复类型为 211 或 214。建议在输入 USER 命令之前允许 HELP。服务器可以使用此回复来指定与站点相关的参数,例如,响应 HELP SITE。NOOP (NOOP)此命令不影响任何参数或以前输入的命令。除了服务器发送 OK 回复之外,它没有指定任何动作。对于控制连接上的所有通信,文件传输协议遵循 Telnet 协议的规范。由于用于 Telnet 通信的语言可能是协商选项,因此接下来两节中的所有引用都将指向“Telnet 语言”和相应的“Telnet 行尾代码”。目前,人们可能会将这些理解为 NVT-ASCII 和 。不会引用 Telnet 协议的其他规范。FTP 命令是由“Telnet 行尾代码”终止的“Telnet 字符串”。命令代码本身是由字符 (空格)终止的字母字符,如果参数跟在后面,否则为 Telnet-EOL。本节描述了命令代码和命令的语义;命令的详细语法在命令部分指定,回复序列在命令和回复排序部分讨论,说明命令使用的场景在典型 FTP 场景部分提供。FTP 命令可以划分为指定访问控制标识符、数据传输参数或 FTP 服务请求的命令。某些命令(例如 ABOR、STAT、QUIT)可以在数据传输过程中通过控制连接发送。有些服务器可能无法同时监控控制和数据连接,在这种情况下,需要采取一些特殊措施来引起服务器的注意。暂时推荐以下有序格式:1. 用户系统在Telnet流中插入Telnet“中断进程”(IP)信号。2. 用户系统发送Telnet“Synch”信号。3. 用户系统在 Telnet 流中插入命令(例如,ABOR)。4. 服务器 PI 在接收到“IP”后,扫描 Telnet 流以查找 EXACTLY ONE FTP 命令。(对于其他服务器,这可能不是必需的,但上面列出的操作应该没有异常影响。)4.2、 FTP 回复文件传输协议命令的回复旨在确保文件传输过程中请求和动作的同步,并保证用户进程始终知道服务器的状态。每个命令必须至少生成一个回复,尽管可能不止一个;在后一种情况下,必须很容易地区分多个答复。此外,一些命令出现在顺序组中,例如 USER、PASS 和 ACCT,或 RNFR 和 RNTO。如果所有前面的命令都成功,则回复显示中间状态的存在。序列中任何一点的失败都需要从头开始重复整个序列。命令-回复序列的细节在下面的一组状态图中得到了明确的说明。FTP 回复由一个三位数字(作为三个字母数字字符传输)和一些文本组成。该数字旨在供自动机使用以确定接下来要进入的状态;该文本适用于人类用户。这三个数字包含足够的编码信息,用户进程(User-PI)不需要检查文本,并且可能会丢弃它或将其传递给用户,视情况而定。特别是,文本可能依赖于服务器,因此每个回复代码可能有不同的文本。回复被定义为包含 3 位代码,后跟空格 ,后跟一行文本(已指定某些最大行长度),并以 Telnet 行尾代码终止。但是,在某些情况下,文本比一行还长。在这些情况下,完整的文本必须用括号括起来,以便用户进程知道何时可以停止读取回复(即停止处理控制连接上的输入)并去做其他事情。这需要在第一行使用特殊格式来指示多行即将到来,并在最后一行使用另一个格式将其指定为最后一行。其中至少有一个必须包含适当的回复代码以指示事务的状态。为了满足所有派系,决定第一行和最后一行代码应该相同。因此,多行回复的格式是第一行以确切所需的回复代码开头,紧接着是连字符“-”(也称为减号),然后是文本。最后一行将以相同的代码开头,紧接着是空格 、可选的一些文本和 Telnet 行尾代码。例如:123-First line Second line 234 A line beginning with numbers 123 The last line然后,用户进程只需要在一行的开头搜索第二次出现的相同回复代码,后跟 (空格),并忽略所有中间行。如果中间线以 3 位数字开头,则服务器必须填充前面以避免混淆。该方案允许将标准系统例程用于回复信息(例如用于 STAT 回复),并附加“人工”第一行和最后一行。在极少数情况下,这些例程能够在任何行的开头生成三个数字和一个空格,每个文本行的开头应该被一些中性文本偏移,例如空格。该方案假设多行回复可能不会嵌套。回复的三位数字各有特殊意义。这旨在允许用户进程进行一系列非常简单到非常复杂的响应。第一个数字表示响应是好、坏还是不完整。(参见状态图),一个简单的用户进程将能够通过简单地检查第一个数字来确定其下一步操作(按计划进行、重做、裁员等)。想要知道发生了什么样的错误(例如文件系统错误、命令语法错误)的用户进程可以检查第二个数字,保留第三个数字用于信息的最佳分级(例如,没有前面的 RNFR 的 RNTO 命令) .回复代码的第一位数字有五个值:1yz:正面初步答复正在启动请求的操作;在继续执行新命令之前期待另一个答复。 (用户进程在完成回复之前发送另一个命令将违反协议;但服务器 FTP 进程应该将任何到达的命令排队,而前一个命令正在进行中。)这种类型的回复可用于指示命令已被接受,用户进程现在可能会关注数据连接,以实现同时监控是困难的。服务器-FTP 进程最多可以发送每个命令一个 1yz 回复。2yz:主动完成回复请求的操作已成功完成。可以发起新的请求。3yz:正面中级回复命令已被接受,但请求的操作被搁置,等待收到进一步的信息。用户应发送另一个指定此信息的命令。该回复用于命令序列组。4yz:瞬态否定完成回复未接受命令且未执行请求的操作,但错误情况是暂时的,可能会再次请求操作。用户应返回到命令序列的开头(如果有)。很难为“瞬态”赋予含义,尤其是当两个不同的站点(服务器进程和用户进程)必须就解释达成一致时。 4yz 类别中的每个回复的时间值可能略有不同,但其目的是鼓励用户进程重试。确定回复是否适合 4yz 或 5yz(永久否定)类别的经验法则是,如果命令可以重复而无需更改命令形式或用户或服务器的属性(例如,命令的拼写与使用的参数相同;用户不会更改其文件访问权限或用户名;服务器不会提供新的实现。)5yz:永久否定完成回复未接受该命令且未执行请求的操作。不鼓励用户进程重复确切的请求(以相同的顺序)。甚至一些“永久性”错误条件也可以被纠正,因此人类用户可能希望在未来的某个时刻(例如,在拼写改变后,或者用户已经改变了他的目录状态。)以下功能分组编码在第二位:x0z语法 - 这些回复指的是语法错误、不适合任何功能类别的语法正确命令、未实现或多余的命令。x1z信息 - 这些是对信息请求的回复,例如状态或帮助。x2z连接 - 参考控制和数据连接的回复。x3z身份验证和记帐 - 对登录过程和记帐程序的答复。x4z至今未明。x5z文件系统 - 这些回复指示服务器文件系统相对于请求的传输或其他文件系统操作的状态。第三个数字在每个功能类别中给出了更精细的含义等级,由第二个数字指定。下面的答复列表将说明这一点。请注意,与每个回复关联的文本是推荐的,而不是强制性的,甚至可能会根据与之关联的命令而改变。另一方面,回复代码必须严格遵循上一节中的规范;也就是说,服务器实现不应该为与这里描述的情况略有不同的情况发明新的代码,而应该调整已经定义的代码。诸如 TYPE 或 ALLO 之类的命令如果成功执行并没有为用户进程提供任何新信息,将导致返回 200 回复。如果该命令没有由特定的 Server-FTP 进程执行,因为它与该计算机系统无关,例如在 TOPS20 站点上的 ALLO,仍然需要肯定完成答复,以便简单的用户进程知道它可以继续它的行动方针。在这种情况下使用 202 回复,例如回复文本:“无需分配存储空间”。另一方面,如果该命令请求非特定于站点的操作并且未实施,则响应为 502。对此的改进是对已实施但请求未实施参数的命令的 504 回复。4.2.1 功能组回复码200 命令完成。500 语法错误,命令无法识别。这可能包括命令行太长等错误。501 参数或参数中的语法错误。202 命令未执行,在此站点上是多余的。502 命令未执行。503 错误的命令序列。504 该参数未执行命令。110 重新启动标记回复。 在这种情况下,文本是准确的,而不是留给特定的实现; 它必须是:MARK yyyy = mmmm其中 yyyy 是用户进程数据流标记,以及 mmmm 服务器的等效标记(注意标记和“=”之间的空格)。211 系统状态,或系统帮助回复。212 目录状态。213 文件状态。214 帮助信息。关于如何使用服务器或特定非标准命令的含义。此回复仅对人类用户有用。215 命名系统类型。其中 NAME 是 Assigned Numbers 文档中列表中的正式系统名称。120 服务在 nnn 分钟内准备就绪。220 为新用户准备好服务。221 服务关闭控制连接。适当时注销。421 服务不可用,正在关闭控制连接。如果服务知道它必须关闭,这可能是对任何命令的回复。125 数据连接已打开;转移开始。225 数据连接打开;没有正在进行的转移。425 无法打开数据连接。226 关闭数据连接。请求的文件操作成功(例如,文件传输或文件中止)。426 连接关闭;传输中止。227 进入被动模式 (h1,h2,h3,h4,p1,p2)。230 用户登录,继续。530 未登录。331 用户名好,需要密码。332 需要账号登录。532 需要帐户来存储文件。150 文件状态正常; 即将打开数据连接。250 请求的文件操作正常,已完成。257 “路径名”已创建。350 请求的文件操作等待进一步的信息。450 未采取请求的文件操作。 文件不可用(例如,文件忙)。550 未采取请求的操作。 文件不可用(例如,未找到文件、无法访问)。451 请求的操作已中止。 处理中的局部错误。551 请求的操作已中止。 页面类型未知。452 未采取请求的操作。 系统存储空间不足。552 请求的文件操作已中止。 超出存储分配(对于当前目录或数据集)。553 未采取请求的操作。 不允许使用文件名。4.2.2 回复码数字顺序表110 重新启动标记回复。 在这种情况下,文本是准确的,而不是留给特定的实现; 它必须是:MARK yyyy = mmmm其中 yyyy 是用户进程数据流标记,以及 mmmm 服务器的等效标记(注意标记和“=”之间的空格)。120 服务在 nnn 分钟内准备就绪。125 数据连接已打开;转移开始。150 文件状态正常;即将打开数据连接。200 命令没问题。202 命令未执行,在此站点上是多余的。211 系统状态,或系统帮助回复。212 目录状态。213 文件状态。214 帮助信息。关于如何使用服务器或特定非标准命令的含义。此回复仅对人类用户有用。215 命名系统类型。其中 NAME 是 Assigned Numbers 文档中列表中的正式系统名称。220 为新用户准备好服务。221 服务关闭控制连接。适当时注销。225 数据连接打开;没有正在进行的转移。226 关闭数据连接。请求的文件操作成功(例如,文件传输或文件中止)。227 进入被动模式 (h1,h2,h3,h4,p1,p2)。230 用户登录,继续。250 请求的文件操作正常,已完成。257 “路径名”已创建。331 用户名好,需要密码。332 需要账号登录。350 请求的文件操作等待进一步的信息。421 服务不可用,正在关闭控制连接。如果服务知道它必须关闭,这可能是对任何命令的回复。425 无法打开数据连接。426 连接关闭;传输中止。450 未采取请求的文件操作。文件不可用(例如,文件忙)。451 请求的操作中止:处理中的本地错误。452 未采取请求的操作。系统存储空间不足。500 语法错误,命令无法识别。这可能包括命令行太长等错误。501 参数或参数中的语法错误。502 命令未执行。503 错误的命令序列。504 该参数未执行命令。530 未登录。532 需要帐户来存储文件。550 未采取请求的操作。文件不可用(例如,未找到文件、无法访问)。551 请求的操作中止:页面类型未知。552 请求的文件操作已中止。超出存储分配(对于当前目录或数据集)。553 未采取请求的操作。不允许使用文件名。5. 声明性规范5.1. 最低执行为了使 FTP 能够正常工作而没有不必要的错误消息,所有服务器都需要以下最低限度的实现:TYPE - ASCII Non-print MODE - Stream STRUCTURE - File, Record COMMANDS - USER, QUIT, PORT,TYPE, MODE, STRU, 对于默认值 RETR, STOR,NOOP.传输参数的默认值为:TYPE - ASCII Non-print MODE - Stream STRU - File所有主机都必须接受上述作为标准默认值。5.2.连接服务器协议解释器应“监听”端口 L。用户或用户协议解释器应启动全双工控制连接。服务器和用户进程应遵循 ARPA-Internet 协议手册 [1] 中指定的 Telnet 协议约定。服务器没有义务提供命令行的编辑,并且可能要求在用户主机中完成。在所有传输和回复完成后,服务器应用户请求关闭控制连接。用户-DTP 必须“监听”指定的数据端口;这可能是默认用户端口 (U) 或 PORT 命令中指定的端口。服务器应使用指定的用户数据端口从他自己的默认数据端口 (L-1) 启动数据连接。传输的方向和使用的端口将由 FTP 服务命令决定。请注意,所有 FTP 实现都必须支持使用默认端口的数据传输,并且只有 USER-PI 可以启动非默认端口的使用。当数据要在两台服务器 A 和 B(参见图 2)之间传输时,用户 PI C 与两个服务器 PI 建立控制连接。其中一个服务器,比如说 A,然后会收到一个 PASV 命令,告诉他在他收到传输服务命令时“监听”他的数据端口而不是启动连接。当用户 PI 收到对 PASV 命令的确认时,其中包括主机的身份和正在侦听的端口,然后用户 PI 在 PORT 命令中将 A 的端口 a 发送到 B;返回回复。然后,用户 PI 可以向 A 和 B 发送相应的服务命令。服务器 B 启动连接并继续传输。下面列出了命令-回复序列,其中消息垂直同步但水平异步:图 3数据连接应在建立数据连接部分所述的条件下由服务器关闭。如果在不需要关闭连接来指示文件结束的数据传输之后关闭数据连接,则服务器必须立即这样做。不允许等到新的传输命令之后,因为用户进程已经测试了数据连接以查看它是否需要进行“监听”; (请记住,用户必须在发送传输请求之前“监听”关闭的数据端口)。为了防止这里的竞争条件,服务器在关闭数据连接后发送回复(226)(或者如果连接保持打开,“文件传输完成”回复(250)并且用户 PI 应该等待其中之一在发出新的传输命令之前回复)。任何时候用户或服务器看到另一端正在关闭连接,它应该立即读取连接上排队的任何剩余数据并在自己端发出关闭。5.3.命令这些命令是通过控制连接传输的 Telnet 字符串,如 FTP 命令部分所述。命令功能和语义在访问控制命令、传输参数命令、FTP 服务命令和杂项命令一节中描述。命令语法在此处指定。命令以命令代码开头,后跟参数字段。命令代码是四个或更少的字母字符。大写和小写字母字符将被同等对待。因此,以下任何一项都可能代表检索命令:RETR Retr retr ReTr rETr这也适用于表示参数值的任何符号,例如 A 或 ASCII 类型的 a。 命令代码和参数字段由一个或多个空格分隔。参数字段由可变长度字符串组成,以字符序列 (回车,换行)结尾,用于 NVT-ASCII 表示; 对于其他协商语言,可能会使用不同的行尾字符。 需要注意的是,服务器在收到行尾代码之前不采取任何行动。下面在 NVT-ASCII 中指定了语法。 参数字段中的所有字符都是 ASCII 字符,包括任何 ASCII 表示的十进制整数。 方括号表示可选参数字段。 如果不采用该选项,则隐含适当的默认值。5.3.1. FTP 命令以下是FTP命令:USER <SP> <username> <CRLF> PASS <SP> <password> <CRLF> ACCT <SP> <account-information> <CRLF> CWD <SP> <pathname> <CRLF> CDUP <CRLF> SMNT <SP> <pathname> <CRLF> QUIT <CRLF> REIN <CRLF> PORT <SP> <host-port> <CRLF> PASV <CRLF> TYPE <SP> <type-code> <CRLF> STRU <SP> <structure-code> <CRLF> MODE <SP> <mode-code> <CRLF> RETR <SP> <pathname> <CRLF> STOR <SP> <pathname> <CRLF> STOU <CRLF> APPE <SP> <pathname> <CRLF> ALLO <SP> <decimal-integer> [<SP> R <SP> <decimal-integer>] <CRLF> REST <SP> <marker> <CRLF> RNFR <SP> <pathname> <CRLF> RNTO <SP> <pathname> <CRLF> ABOR <CRLF> DELE <SP> <pathname> <CRLF> RMD <SP> <pathname> <CRLF> MKD <SP> <pathname> <CRLF> PWD <CRLF> LIST [<SP> <pathname>] <CRLF> NLST [<SP> <pathname>] <CRLF> SITE <SP> <string> <CRLF> SYST <CRLF> STAT [<SP> <pathname>] <CRLF> HELP [<SP> <string>] <CRLF> NOOP <CRLF>5.3.2. FTP 命令参数上述参数字段的语法(在适用的情况下使用 BNF 表示法)是:<username> ::= <string> <password> ::= <string> <account-information> ::= <string> <string> ::= <char> | <char><string> <char> ::= any of the 128 ASCII characters except <CR> and <LF> <marker> ::= <pr-string> <pr-string> ::= <pr-char> | <pr-char><pr-string> <pr-char> ::= printable characters, any ASCII code 33 through 126 <byte-size> ::= <number> <host-port> ::= <host-number>,<port-number> <host-number> ::= <number>,<number>,<number>,<number> <port-number> ::= <number>,<number> <number> ::= any decimal integer 1 through 255 <form-code> ::= N | T | C <type-code> ::= A [<sp> <form-code>] | E [<sp> <form-code>] | I | L <sp> <byte-size> <structure-code> ::= F | R | P <mode-code> ::= S | B | C <pathname> ::= <string> <decimal-integer> ::= any decimal integer5.4.命令和回复的排序用户和服务器之间的通信旨在进行交替对话。因此,用户发出 FTP 命令,服务器以提示的主要回复进行响应。用户应该在发送进一步的命令之前等待这个初始的主要成功或失败响应。某些命令需要用户也应该等待的第二个答复。例如,这些回复可能会报告文件传输的进度或完成情况或数据连接的关闭情况。它们是对文件传输命令的辅助回复。一组重要的信息回复是连接问候语。在正常情况下,服务器会在连接完成时发送 220 回复“等待输入”。在发送任何命令之前,用户应该等待此问候消息。如果服务器无法立即接受输入,则应立即发送 120“预期延迟”回复,并在准备好时发送 220 回复。如果有延迟,用户将知道不要挂断。自发回复有时“系统”自发地有一条消息要发送给用户(通常是所有用户)。例如,“系统将在 15 分钟内停机”。 FTP 中没有规定从服务器向用户发送此类自发信息。建议将此类信息在服务器 PI 中排队,并在下一个回复中传递给用户 PI(可能使其成为多行回复)。下表列出了每个命令的替代成功和失败回复。这些必须严格遵守;服务器可以替换回复中的文本,但不能更改代码编号和特定命令回复序列所隐含的含义和动作。命令-回复序列在本节中,将介绍命令-应答序列。列出了每个命令及其可能的回复;命令组一起列出。首先列出初步答复(其后续答复缩进并在其下方),然后是肯定和否定完成,最后是中间答复,其中包含以下序列中的其余命令。此列表构成了状态图的基础,状态图将单独呈现。Connection Establishment 120 220 220 421LoginUSER 230 530 500, 501, 421 331, 332 PASS 230 202 530 500, 501, 503, 421 332 ACCT 230 202 530 500, 501, 503, 421 CWD 250 500, 501, 502, 421, 530, 550 CDUP 200 500, 501, 502, 421, 530, 550 SMNT 202, 250 500, 501, 502, 421, 530, 550LogoutREIN 120 220 220 421 500, 502 QUIT 221 500Transfer parametersPORT 200 500, 501, 421, 530 PASV 227 500, 501, 502, 421, 530 MODE 200 500, 501, 504, 421, 530 TYPE 200 500, 501, 504, 421, 530 STRU 200 500, 501, 504, 421, 530File action commandsALLO 200 202 500, 501, 504, 421, 530 REST 500, 501, 502, 421, 530 350 STOR 125, 150 (110) 226, 250 425, 426, 451, 551, 552 532, 450, 452, 553 500, 501, 421, 530 STOU 125, 150 (110) 226, 250 425, 426, 451, 551, 552 532, 450, 452, 553 500, 501, 421, 530 RETR 125, 150 (110) 226, 250 425, 426, 451 450, 550 500, 501, 421, 530 LIST 125, 150 226, 250 425, 426, 451 450 500, 501, 502, 421, 530 NLST 125, 150 226, 250 425, 426, 451 450 500, 501, 502, 421, 530 APPE 125, 150 (110) 226, 250 425, 426, 451, 551, 552 532, 450, 550, 452, 553 500, 501, 502, 421, 530 RNFR 450, 550 500, 501, 502, 421, 530 350 RNTO 250 532, 553 500, 501, 502, 503, 421, 530 DELE 250 450, 550 500, 501, 502, 421, 530 RMD 250 500, 501, 502, 421, 530, 550 MKD 257 500, 501, 502, 421, 530, 550 PWD 257 500, 501, 502, 421, 550 ABOR 225, 226 500, 501, 502, 421Informational commandsSYST 215 500, 501, 502, 421 STAT 211, 212, 213 450 500, 501, 502, 421, 530 HELP 211, 214 500, 501, 502, 421Miscellaneous commandsSITE 200 202 500, 501, 530 NOOP 200 500 4216. 状态图​在这里,我们展示了一个非常简单的 FTP 实现的状态图。 仅使用回复代码的第一位数字。 每组 FTP 命令或命令序列都有一个状态图。命令分组是通过为每个命令构建一个模型然后将具有结构相同模型的命令收集在一起来确定的。对于每个命令或命令序列,存在三种可能的结果:成功 (S)、失败 (F) 和错误 (E)。 在下面的状态图中,我们使用符号 B 表示“开始”,使用符号 W 表示“等待回复”。我们首先展示代表最大组 FTP 命令的图表:此图为命令建模:ABOR, ALLO, DELE, CWD, CDUP, SMNT, HELP, MODE, NOOP, PASV, QUIT, SITE, PORT, SYST, STAT, RMD, MKD, PWD, STRU, 和 TYPE.另一大组命令由非常相似的图表表示:此图为命令建模:APPE, LIST, NLST, REIN, RETR, STOR, 和 STOU.请注意,第二个模型也可以用于表示第一组命令,唯一的区别是在第一组中 100 系列回复是意外的,因此被视为错误,而第二组期望(有些可能需要)100 系列 回复。 请记住,每个命令最多允许一个 100 系列回复。其余的图表模型命令序列,其中最简单的可能是重命名序列:下图是 Restart 命令的简单模型:其中“cmd”是 APPE、STOR 或 RETR。我们注意到上述三个模型是相似的。 Restart 与 Rename two 的区别仅在于第二阶段对 100 系列回复的处理,而第二组期望(有些可能需要)100 系列回复。 请记住,每个命令最多允许一个 100 系列回复。最复杂的图是登录序列:最后,我们展示了一个通用图,可用于对命令和回复交换进行建模:7. 典型的 FTP 场景主机 U 上的用户想要向主机 S 传输文件/从主机 S 传输文件:通常,用户将通过中介用户 FTP 进程与服务器通信。 以下可能是一个典型的场景。 user-FTP 提示显示在括号中,'---->' 代表主机 U 到主机 S 的命令,'<----' 代表主机 S 到主机 U 的回复。​8. 连接建立FTP控制连接是通过TCP在用户进程端口U和服务器进程端口L之间建立的。该协议被分配了服务端口21(25八进制),即L=21。附录1 - 页结构FTP 支持页面结构的需要主要源于支持在 TOPS-20 系统之间有效传输文件的需要,尤其是 NLS 使用的文件。TOPS-20 的文件系统基于页的概念。操作系统在将文件作为页面进行操作方面效率最高。操作系统为文件系统提供了一个接口,因此许多应用程序将文件视为连续的字符流。但是,一些应用程序直接使用底层页面结构,其中一些会创建有孔文件。TOPS-20 磁盘文件由四部分组成:路径名、页表、(可能为空的)页集和属性集。路径名在 RETR 或 STOR 命令中指定。它包括目录名、文件名、文件扩展名和代号。页表最多包含 2^18 个条目。每个条目可能是空的,也可能指向一个页面。如果不为空,也有一些页面特定的访问位;并非文件的所有页面都需要相同的访问保护。页是一组连续的 512 个字,每个字 36 位。文件的属性,在文件描述符块 (FDB) 中,包含诸如创建时间、写入时间、读取时间、写入器的字节大小、文件结束指针、读取和写入计数、备份系统磁带编号等内容, 等等。请注意,没有要求页表中的条目是连续的。在被占用的页表插槽之间可能有空的页表插槽。此外,文件指针的结尾只是一个数字。不要求它实际上指向文件中的“最后一个”数据。 TOPS-20 中的普通顺序 I/O 调用将导致文件尾指针在最后写入的数据之后被留下,但如果特定的编程系统需要,其他操作可能会导致它不是这样。事实上,在这两种特殊情况下,“有洞”文件和不在文件末尾的文件尾指针出现在 NLS 数据文件中。可以使用 FTP 传输参数发送 TOPS-20 分页文件:TYPE L 36、STRU P 和 MODE S(实际上,可以使用任何模式)。每页信息都有一个标题。每个作为逻辑字节的标头字段是一个 TOPS-20 字,因为 TYPE 是 L 36。标题字段是:​​Word 0: Header Length.标头长度为 5。​​Word 1: Page Index.如果数据是磁盘文件页面,则这是文件页面映射中该页面的编号。 文件中的空页(空洞)不会被发送。 请注意,一个洞与一页零不同。​​Word 2: Data Length.此页中的数据字数,跟在标题后面。 因此,传输单元的总长度是报头长度加上数据长度。​​Word 3: Page Type.这是什么类型的块的代码。 数据页是类型 3,FDB 页是类型 2。​​Word 4: Page Access Control.与文件页面映射中的页面相关联的访问位。 (这个完整的字数被程序从网络读取到磁盘放入一个 SPACS 的 AC2 中。)头部之后是数据长度数据字。 数据长度当前要么是数据页的 512,要么是 FDB 的 31。 磁盘文件页中的尾随零可能会被丢弃,在这种情况下使数据长度小于 512。附录2 - 目录命令由于 UNIX 具有树状目录结构,其中目录与普通文件一样易于操作,因此扩展这些机器上的 FTP 服务器以包含处理目录创建的命令非常有用。 由于ARPA-Internet 上还有其他主机具有树状目录(包括TOPS-20 和Multics),因此这些命令尽可能通用。FTP 添加了四个目录命令:​​MKD pathname创建一个名为“pathname”的目录。​​RMD pathname删除名为“pathname”的目录。​​PWD打印当前工作目录名称。​​CDUP切换到当前工作目录的父目录。“路径名”参数应该作为当前工作目录的子目录创建(删除),除非“路径名”字符串包含足够的信息来指定服务器,例如,“路径名”是一个绝对路径名(在 UNIX 和 Multics 中) ),或者路径名类似于 TOPS-20 的“<abso.lute.path>”。​​REPLY CODESCDUP 命令是 CWD 的一个特例,包含它是为了简化用于在具有不同命名父目录语法的操作系统之间传输目录树的程序的实现。 CDUP 的回复代码与 CWD 的回复代码相同。RMD 的回复代码与其类似文件 DELE 的回复代码相同。但是,MKD 的回复代码稍微复杂一些。 新创建的目录可能是未来 CWD 命令的对象。 不幸的是,MKD 的论据可能并不总是适合 CWD 的论据。 例如,当仅通过提供子目录名称创建 TOPS-20 子目录时,就是这种情况。 也就是说,用一个TOPS-20服务器FTP,命令序列​​MKD MYDIR CWD MYDIR将失败。 新目录只能以其“绝对”名称引用; 例如,如果上面的 MKD 命令是在连接到目录 <DFRANKLIN> 时发出的,则新的子目录只能由名称 <DFRANKLIN.MYDIR> 引用。然而,即使在 UNIX 和 Multics 上,给 MKD 的参数也可能不合适。 如果它是“相对”路径名(即相对于当前目录解释的路径名),则用户需要位于同一当前目录中才能到达子目录。 根据应用程序,这可能不方便。 在任何情况下它都不是很健壮。为了解决这些问题,在成功完成 MKD 命令后,服务器应返回以下形式的一行:​​257<space>"<directory-name>"<space><commentary>也就是说,服务器会告诉用户在引用创建的目录时使用什么字符串。 目录名可以包含任何字符; 嵌入的双引号应该被双引号转义(“双引号”约定)。例如,用户连接到目录 /usr/dm,并创建一个名为 pathname 的子目录:​CWD /usr/dm 200 directory changed to /usr/dm MKD pathname 257 "/usr/dm/pathname" directory created带有嵌入式双引号的示例:MKD foo"bar 257 "/usr/dm/foo""bar" directory created CWD /usr/dm/foo"bar 200 directory changed to /usr/dm/foo"bar同名子目录的先前存在是一个错误,在这种情况下,服务器必须返回“访问被拒绝”错误回复。​CWD /usr/dm 200 directory changed to /usr/dm MKD pathname 521-"/usr/dm/pathname" directory already exists; 521 taking no action.MKD 的失败回复类似于它的文件创建表亲 STOR。 此外,如果与子目录同名的文件名将与子目录的创建冲突(这在 UNIX 上是一个问题,但在 TOPS-20 上不应该是一个问题),则会给出“拒绝访问”返回。本质上,因为 PWD 命令返回与成功的 MKD 命令相同类型的信息,成功的 PWD 命令也使用 257 回复代码。​​SUBTLETIES因为这些命令在将子树从一台机器传输到另一台机器时最有用,所以仔细观察 MKD 的参数将被解释为当前工作目录的子目录,除非它包含足够的信息让目标主机以其他方式告知 . 在 TOPS-20 世界中使用它的一个假设示例:​CWD <some.where> 200 Working directory changed MKD overrainbow 257 "<some.where.overrainbow>" directory created CWD overrainbow 431 No such directory CWD <some.where.overrainbow> 200 Working directory changed CWD <some.where> 200 Working directory changed to <some.where> MKD <unambiguous> 257 "<unambiguous>" directory created CWD <unambiguous>请注意,第一个示例生成连接目录的子目录。 相比之下,第二个示例中的参数包含足够的信息,TOPS-20 可以判断 <unambiguous> 目录是顶级目录。 另请注意,在第一个示例中,用户通过尝试访问新创建的目录而不是 TOPS-20 返回的名称来“违反”协议。 如果存在 <overrainbow> 目录,则在这种情况下可能会导致问题; 这是某些 TOPS-20 实现中固有的歧义。 类似的考虑适用于 RMD 命令。 关键是:除非这样做会违反主机用于表示相对路径名和绝对路径名的约定,否则主机应该将 MKD 和 RMD 命令的操作数视为子目录。 MKD 命令的 257 回复必须始终包含创建的目录的绝对路径名。附录3 – 关于FTP的 RFCBhushan, Abhay, "A File Transfer Protocol", RFC 114 (NIC 5823), MIT-Project MAC, 16 April 1971. Harslem, Eric, and John Heafner, "Comments on RFC 114 (A File Transfer Protocol)", RFC 141 (NIC 6726), RAND, 29 April 1971. Bhushan, Abhay, et al, "The File Transfer Protocol", RFC 172 (NIC 6794), MIT-Project MAC, 23 June 1971. Braden, Bob, "Comments on DTP and FTP Proposals", RFC 238 (NIC 7663), UCLA/CCN, 29 September 1971. Bhushan, Abhay, et al, "The File Transfer Protocol", RFC 265 (NIC 7813), MIT-Project MAC, 17 November 1971. McKenzie, Alex, "A Suggested Addition to File Transfer Protocol", RFC 281 (NIC 8163), BBN, 8 December 1971. Bhushan, Abhay, "The Use of "Set Data Type" Transaction in File Transfer Protocol", RFC 294 (NIC 8304), MIT-Project MAC, 25 January 1972. Bhushan, Abhay, "The File Transfer Protocol", RFC 354 (NIC 10596), MIT-Project MAC, 8 July 1972. Bhushan, Abhay, "Comments on the File Transfer Protocol (RFC 354)", RFC 385 (NIC 11357), MIT-Project MAC, 18 August 1972. Hicks, Greg, "User FTP Documentation", RFC 412 (NIC 12404), Utah, 27 November 1972. Bhushan, Abhay, "File Transfer Protocol (FTP) Status and Further Comments", RFC 414 (NIC 12406), MIT-Project MAC, 20 November 1972. Braden, Bob, "Comments on File Transfer Protocol", RFC 430 (NIC 13299), UCLA/CCN, 7 February 1973. Thomas, Bob, and Bob Clements, "FTP Server-Server Interaction", RFC 438 (NIC 13770), BBN, 15 January 1973. Braden, Bob, "Print Files in FTP", RFC 448 (NIC 13299), UCLA/CCN, 27 February 1973. McKenzie, Alex, "File Transfer Protocol", RFC 454 (NIC 14333), BBN, 16 February 1973. Bressler, Bob, and Bob Thomas, "Mail Retrieval via FTP", RFC 458 (NIC 14378), BBN-NET and BBN-TENEX, 20 February 1973. Neigus, Nancy, "File Transfer Protocol", RFC 542 (NIC 17759), BBN, 12 July 1973. Krilanovich, Mark, and George Gregg, "Comments on the File Transfer Protocol", RFC 607 (NIC 21255), UCSB, 7 January 1974. Pogran, Ken, and Nancy Neigus, "Response to RFC 607 - Comments on the File Transfer Protocol", RFC 614 (NIC 21530), BBN, 28 January 1974. Krilanovich, Mark, George Gregg, Wayne Hathaway, and Jim White, "Comments on the File Transfer Protocol", RFC 624 (NIC 22054), UCSB, Ames Research Center, SRI-ARC, 28 February 1974. Bhushan, Abhay, "FTP Comments and Response to RFC 430", RFC 463 (NIC 14573), MIT-DMCG, 21 February 1973. Braden, Bob, "FTP Data Compression", RFC 468 (NIC 14742), UCLA/CCN, 8 March 1973. Bhushan, Abhay, "FTP and Network Mail System", RFC 475 (NIC 14919), MIT-DMCG, 6 March 1973. Bressler, Bob, and Bob Thomas "FTP Server-Server Interaction - II", RFC 478 (NIC 14947), BBN-NET and BBN-TENEX, 26 March 1973. White, Jim, "Use of FTP by the NIC Journal", RFC 479 (NIC 14948), SRI-ARC, 8 March 1973. White, Jim, "Host-Dependent FTP Parameters", RFC 480 (NIC 14949), SRI-ARC, 8 March 1973. Padlipsky, Mike, "An FTP Command-Naming Problem", RFC 506 (NIC 16157), MIT-Multics, 26 June 1973. Day, John, "Memo to FTP Group (Proposal for File Access Protocol)", RFC 520 (NIC 16819), Illinois, 25 June 1973. Merryman, Robert, "The UCSD-CC Server-FTP Facility", RFC 532 (NIC 17451), UCSD-CC, 22 June 1973. Braden, Bob, "TENEX FTP Problem", RFC 571 (NIC 18974), UCLA/CCN, 15 November 1973. McKenzie, Alex, and Jon Postel, "Telnet and FTP Implementation - Schedule Change", RFC 593 (NIC 20615), BBN and MITRE, 29 November 1973. Sussman, Julie, "FTP Error Code Usage for More Reliable Mail Service", RFC 630 (NIC 30237), BBN, 10 April 1974. Postel, Jon, "Revised FTP Reply Codes", RFC 640 (NIC 30843), UCLA/NMC, 5 June 1974. Harvey, Brian, "Leaving Well Enough Alone", RFC 686 (NIC 32481), SU-AI, 10 May 1975. Harvey, Brian, "One More Try on the FTP", RFC 691 (NIC 32700), SU-AI, 28 May 1975. Lieb, J., "CWD Command of FTP", RFC 697 (NIC 32963), 14 July 1975. Harrenstien, Ken, "FTP Extension: XSEN", RFC 737 (NIC 42217), SRI-KL, 31 October 1977. Harrenstien, Ken, "FTP Extension: XRSQ/XRCP", RFC 743 (NIC 42758), SRI-KL, 30 December 1977. Lebling, P. David, "Survey of FTP Mail and MLFL", RFC 751, MIT, 10 December 1978. Postel, Jon, "File Transfer Protocol Specification", RFC 765, ISI, June 1980. Mankins, David, Dan Franklin, and Buzz Owen, "Directory Oriented FTP Commands", RFC 776, BBN, December 1980. Padlipsky, Michael, "FTP Unique-Named Store Command", RFC 949, MITRE, July 1985.参考文档[1] Feinler, Elizabeth, "Internet Protocol Transition Workbook", Network Information Center, SRI International, March 1982. [2] Postel, Jon, "Transmission Control Protocol - DARPA Internet Program Protocol Specification", RFC 793, DARPA, September 1981. [3] Postel, Jon, and Joyce Reynolds, "Telnet Protocol Specification", RFC 854, ISI, May 1983. [4] Reynolds, Joyce, and Jon Postel, "Assigned Numbers", RFC 943, ISI, April 1985.
文章
存储  ·  监控  ·  网络协议  ·  Unix  ·  文件存储  ·  数据安全/隐私保护  ·  网络架构  ·  数据格式  ·  索引
2023-03-04
实践:部署Jenkins服务并开发MERN应用的CI/CD构建管道
随着您开发软件,您还必须将其与以前的代码持续集成并将其部署到服务器。手动执行此操作是一个耗时的过程,有时会导致错误。因此,我们需要以持续和自动化的方式执行此操作——这就是您将在本文中学到的内容。我们将讨论如何通过使用 Jenkins 设置 CI/CD 管道来改进 MERN(MongoDB、Express、React 和 NodeJs)应用程序开发过程。您将了解如何自动部署以实现更快、更高效的发布。先决条件对 MERN 堆栈技术的基本了解。对Docker的基本了解。从GitHub获取源代码问题考虑一下这个生产力应用程序——这是我们将在本文中使用的 MERN 项目。从构建应用程序到将其推送到 Docker 中心,我们必须完成许多步骤。首先,我们必须使用命令运行测试以确定所有测试是否通过。如果所有测试都通过,我们将构建 Docker 镜像,然后将这些镜像推送到 Docker Hub。如果您的应用程序极其复杂,您可能需要采取额外的步骤。现在,假设我们手动完成所有操作,这既费时又可能导致错误。解决方案为了解决这个问题,我们可以创建一个 CI/CD流水线。因此,每当您添加功能或修复错误时,都会触发此管道。这会自动执行从测试到部署的所有步骤。什么是 CI/CD,为什么重要?持续集成和持续部署是为自动化软件集成和部署而执行的一系列步骤。CI/CD 是 DevOps 的核心。从开发到部署,我们的 MERN 应用程序经历了四个主要阶段:测试、构建 Docker 镜像、推送到注册表以及部署到云提供商。所有这些都是通过运行各种命令手动完成的。每次添加新功能或修复错误时,我们都需要这样做。但这会显着降低开发人员的工作效率,这就是为什么 CI/CD 可以如此有助于自动化这个过程。在本文中,我们将介绍推送到注册表之前的步骤。该项目我们将在本教程中使用的项目是一个非常简单的全栈 MERN 应用程序。它包含两个微服务。前端后端这两个应用程序都包含一个 Dockerfile。什么是Jenkins?要运行 CI/CD 管道,我们需要一个 CI/CD 服务器。这是管道中编写的所有步骤运行的地方。市场上有许多可用的服务,包括 GitHub Actions、Travis CI、Circle CI、GitLab CI/CD、AWS CodePipeline、Azure DevOps 和 Google Cloud Build。Jenkins 是一种流行的 CI/CD 工具,我们将在这里使用它。如何在 Azure 上设置 Jenkins 服务器因为 Jenkins 是开源的并且它不提供云解决方案,所以我们必须在本地运行它或在云提供商上自行托管。现在,在本地运行可能很困难,尤其是对于 Windows 用户而言。因此,我选择在 Azure 上自行托管此演示。如果您想在本地运行或在 Azure 以外的地方自行托管(遵循Jenkins 的这些指南),请跳过此部分并继续阅读如何配置 Jenkins部分。首先,您需要登录您的Azure帐户(如果您还没有,请创建一个)。打开 Azure Cloud Shell。然后创建一个名为jenkins存储所有 Jenkins 配置的目录,并切换到该目录:mkdir jenkinscd jenkins创建一个名为cloud-init-jenkins.txt. 使用 nano 或 vim 打开,touch cloud-init-jenkins.txtnano cloud-init-jenkins.txt并将此代码粘贴到其中:#cloud-configpackage_upgrade: trueruncmd:  - sudo apt install openjdk-11-jre -y  - wget -qO - https://pkg.jenkins.io/debian-stable/jenkins.io.key | sudo apt-key add -  - sh -c 'echo deb https://pkg.jenkins.io/debian-stable binary/ > /etc/apt/sources.list.d/jenkins.list'  - sudo apt-get update && sudo apt-get install jenkins -y  - sudo service jenkins restart在这里,我们将在创建虚拟机后使用此文件来安装 Jenkins。首先,我们安装 openjdk,这是 Jenkins 运行所必需的。Jenkins 服务会在我们安装后重新启动。接下来,创建一个资源组。(Azure 中的资源组就像一个容器,将项目的所有相关资源保存在一个组中)az group create --name jenkins-rg --location centralindia注意:确保将位置更改为离您最近的位置。现在,创建一个虚拟机。az vm create \--resource-group jenkins-rg \--name jenkins-vm \--image UbuntuLTS \--admin-username "azureuser" \--generate-ssh-keys \--public-ip-sku Standard \--custom-data cloud-init-jenkins.txt您可以使用以下命令验证 VM 安装:az vm list -d -o table --query "[?name=='jenkins-vm']"不要混淆。此命令只是以表格格式显示 JSON 数据,以便于验证。Jenkins 服务器在 8080 port 上运行,所以我们需要在我们的 VM 上公开这个端口。你可以这样做:az vm open-port \--resource-group jenkins-rg \--name jenkins-vm  \--port 8080 --priority 1010现在我们可以使用 URL 在浏览器中访问 Jenkins 仪表板http://<your-vm-ip>:8080 。使用此命令获取 VM IP 地址:az vm show \--resource-group jenkins-rg \--name jenkins-vm -d \--query [publicIps] \--output tsv您现在可以在浏览器中看到 Jenkins 应用程序。您会注意到,Jenkins 要求我们提供一个在安装过程中自动生成的管理员密码。但首先让我们通过 SSH 进入安装了 Jenkins 的虚拟机。ssh azureuser@<ip_address>现在,输入以下命令以获取密码:sudo cat /var/lib/jenkins/secrets/initialAdminPassword复制并粘贴它。然后点击继续。如何配置Jenkins首先,您需要点击Install suggested plugins。安装所有插件需要一些时间。需要管理员用户来限制对 Jenkins 的访问。因此,继续创建一个。完成后点击保存并继续。现在您将看到 Jenkins 仪表板。第一步是安装“Blue Ocean”插件。Jenkins 有一个非常古老的界面,这可能会让一些人难以使用。这个蓝海插件为一些 Jenkins 组件(比如创建管道)提供了一个现代接口。要安装插件,请转到Manage Jenkins -> 单击System Configuration下的Manage Plugins -> Available plugins。搜索“Blue Ocean” -> 勾选方框并点击Download now and install after restart。太好了,我们都准备好了。现在让我们创建一个管道。如何编写 Jenkinsfile要创建管道,我们需要一个Jenkinsfile。该文件包含所有管道配置——阶段、步骤等。Jenkinsfile 之于 Jenkins 就像 Dockerfile 之于 Docker。Jenkinsfile 使用Groovy语法。语法非常简单。看一眼就能明白一切。让我们开始写:pipeline {}agent一词应该是您在管道中提到的第一件事。代理类似于运行作业的容器或环境。您可以使用多个代理并行运行作业。pipeline { agent any}在这里,我们告诉 Jenkins 使用任何可用的代理。我们的流水线共有 5 个阶段:第 1 阶段:下载代码不同的 CI/CD 工具使用不同的命名约定。在 Jenkins 中,这些被称为阶段。在每个阶段,我们编写不同的步骤。我们的第一个阶段是从源代码管理系统(在我们的例子中是 GitHub)检出代码。pipeline { agent any stages {  stage('Checkout') {   steps {    checkout scm   }  } }}提交更改并推送到您的 GitHub 存储库。由于我们还没有创建任何管道,现在就开始吧。在开始之前,我们必须确保 Git 已安装在我们的系统上。如果您按照我之前的步骤在 Azure VM 上安装 Jenkins,则 Git 已经安装。您可以通过运行以下命令对其进行测试(使您仍然通过 SSH 连接到 VM):git --version## install gitsudo apt install git打开蓝海。单击创建新管道。然后选择您的源代码管理系统。如果您选择 GitHub,则必须为 Jenkins 提供访问令牌以访问您的存储库。我建议在此处单击创建访问令牌,因为它是一个具有所有必要权限的模板。然后点击连接。之后,将创建一个管道。由于我们的存储库已经包含一个 Jenkinsfile,Jenkins 会自动检测它并运行我们在管道中提到的阶段和步骤。如果一切顺利,整个页面将变为绿色。(其他颜色:蓝色表示管道正在运行,红色表示管道出现问题,灰色表示我们停止了管道。)第 2 阶段:运行前端测试通常,所有 CI/CD 管道都包含一些需要在部署之前运行的测试。所以我在前端和后端都添加了简单的测试。让我们从前端测试开始。stage('Client Tests') { steps {  dir('client') {   sh 'npm install'   sh 'npm test'  } }}我们正在将目录更改为,client/因为那是前端代码所在的位置。然后安装依赖项并在 shell 中npm install运行测试。npm test同样,在我们重新启动管道之前,我们必须确保安装了节点和 npm。在虚拟机中使用这些命令安装节点和 npm:curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash -sudo apt-get install -y nodejs现在,提交代码并重新启动管道。第 3 阶段:运行后端测试现在对后端测试做同样的事情。但是在我们继续之前,我们需要做一件事。如果您看一下代码库activity.test.js,我们会使用一些环境变量。因此,让我们在 Jenkins 中添加这些环境变量。如何在 Jenkins 中添加环境变量要添加环境变量,请转到Manage Jenkins -> 单击“Security”下的Manage Credentials -> System -> Global credentials (unrestricted) -> 单击+ Add Credentials。对于Kind选择“Secret text”,将Scope保留为默认值,对于Secret写入秘密值和ID。这就是我们在 Jenkinsfile 中使用这些环境变量时所使用的。添加以下环境变量:然后在 Jenkinsfile 中,使用这些环境变量:environment { MONGODB_URI = credentials('mongodb-uri') TOKEN_KEY = credentials('token-key') EMAIL = credentials('email') PASSWORD = credentials('password')}添加一个阶段来安装依赖项,在 Jenkins 环境中设置这些变量,然后运行测试:stage('Server Tests') { steps {  dir('server') {   sh 'npm install'   sh 'export MONGODB_URI=$MONGODB_URI'   sh 'export TOKEN_KEY=$TOKEN_KEY'   sh 'export EMAIL=$EMAIL'   sh 'export PASSWORD=$PASSWORD'   sh 'npm test'  } }}再次提交代码并重新启动管道。第 4 阶段:构建 Docker 镜像现在,我们必须指定一个步骤来从 Dockerfiles 构建 Docker 镜像。在我们继续之前,请在 VM 中安装 Docker(如果您尚未安装它)。安装 Docker:sudo apt install docker.io将用户添加jenkins到docker组中,以便 Jenkins 可以访问 Docker 守护进程——否则您将收到权限被拒绝的错误。sudo usermod -a -G docker jenkins然后重启jenkins服务。sudo systemctl restart jenkins在 Jenkinsfile 中添加一个阶段。stage('Build Images') { steps {  sh 'docker build -t rakeshpotnuru/productivity-app:client-latest client'  sh 'docker build -t rakeshpotnuru/productivity-app:server-latest server' }}提交代码并重新启动管道。阶段 5:将图像推送到注册表作为最后阶段,我们会将图像推送到 Docker hub。在此之前,将您的 docker hub 用户名和密码添加到 Jenkins 凭据管理器,但对于种类选择“用户名和密码”。添加我们登录并将图像推送到 Docker hub 的最后阶段。stage('Push Images to DockerHub') { steps {  withCredentials([usernamePassword(credentialsId: 'dockerhub', passwordVariable: 'DOCKER_PASSWORD', usernameVariable: 'DOCKER_USERNAME')]) {   sh 'docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD'   sh 'docker push rakeshpotnuru/productivity-app:client-latest'   sh 'docker push rakeshpotnuru/productivity-app:server-latest'  } }}完整的Jenkinsfile// This is a Jenkinsfile. It is a script that Jenkins will run when a build is triggered.pipeline {    // Telling Jenkins to run the pipeline on any available agent.    agent any    // Setting environment variables for the build.    environment {        MONGODB_URI = credentials('mongodb-uri')        TOKEN_KEY = credentials('token-key')        EMAIL = credentials('email')        PASSWORD = credentials('password')    }    // This is the pipeline. It is a series of stages that Jenkins will run.    stages {        // This state is telling Jenkins to checkout the source code from the source control management system.        stage('Checkout') {            steps {                checkout scm            }        }                // This stage is telling Jenkins to run the tests in the client directory.        stage('Client Tests') {            steps {                dir('client') {                    sh 'npm install'                    sh 'npm test'                }            }        }                // This stage is telling Jenkins to run the tests in the server directory.        stage('Server Tests') {            steps {                dir('server') {                    sh 'npm install'                    sh 'export MONGODB_URI=$MONGODB_URI'                    sh 'export TOKEN_KEY=$TOKEN_KEY'                    sh 'export EMAIL=$EMAIL'                    sh 'export PASSWORD=$PASSWORD'                    sh 'npm test'                }            }        }                // This stage is telling Jenkins to build the images for the client and server.        stage('Build Images') {            steps {                sh 'docker build -t rakeshpotnuru/productivity-app:client-latest client'                sh 'docker build -t rakeshpotnuru/productivity-app:server-latest server'            }        }                // This stage is telling Jenkins to push the images to DockerHub.        stage('Push Images to DockerHub') {            steps {                withCredentials([usernamePassword(credentialsId: 'dockerhub', passwordVariable: 'DOCKER_PASSWORD', usernameVariable: 'DOCKER_USERNAME')]) {                    sh 'docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD'                    sh 'docker push rakeshpotnuru/productivity-app:client-latest'                    sh 'docker push rakeshpotnuru/productivity-app:server-latest'                }            }        }    }}结论总之,让我们回顾一下我们所涵盖的内容:我们探讨了在软件开发中实施持续集成和持续部署 (CI/CD) 的重要性。我们深入研究了 Jenkins 的基础知识,并获得了有关如何在 Azure 云平台上部署 Jenkins 服务器的知识。我们定制了 Jenkins 以满足我们的特定要求。最后,我们编写了一个 Jenkinsfile 并利用 Jenkins Blue Ocean 的用户友好界面构建了一个管道。
文章
存储  ·  前端开发  ·  jenkins  ·  Shell  ·  持续交付  ·  开发工具  ·  数据安全/隐私保护  ·  git  ·  Docker  ·  容器
2023-03-12
...
跳转至:
开发与运维
5773 人关注 | 133347 讨论 | 319056 内容
+ 订阅
  • PHP的分布式缓存是干什么的?具体如何实现?底层原理是什么?
  • PHP分布式系统的实现方法是怎样的?具体步骤是怎样的?底层原理是什么?
  • PHP的分布式系统的设计原理是什么?底层原理是什么?
查看更多 >
安全
1243 人关注 | 24130 讨论 | 85719 内容
+ 订阅
  • 阿里云ACP改革后考试内容是什么?
  • yii2.0验证码的使用方法一共有几种?底层原理是什么?
  • 一个人提升PHP技能水平的路径有哪些?底层原理是什么?
查看更多 >
云原生
234322 人关注 | 11602 讨论 | 47345 内容
+ 订阅
  • PHP架构师的学习路线图是怎样的?具体步骤是怎样的?底层原理是什么?
  • Laravel框架的设计思想是什么?底层原理是什么?
  • [oeasy]python0117 文字的演化_埃及圣书体_象形文字_楔形文字
查看更多 >
大数据
188708 人关注 | 30820 讨论 | 83842 内容
+ 订阅
  • PHP分布式系统的实现方法是怎样的?具体步骤是怎样的?底层原理是什么?
  • 阿里云ACP改革后考试内容是什么?
  • Yii2.0里面一共有多少种队列的实现方式?底层原理是什么?
查看更多 >
微服务
23045 人关注 | 11718 讨论 | 35073 内容
+ 订阅
  • PHP分布式系统的实现方法是怎样的?具体步骤是怎样的?底层原理是什么?
  • PHP的分布式系统的设计原理是什么?底层原理是什么?
  • Yii2.0里面一共有多少种队列的实现方式?底层原理是什么?
查看更多 >