对于编程中琳琅满目的算法,本人向来是不善此道也不精于此的,而说起排序算法,也只是会冒泡排序。还记得当初刚做开发工作面试第一家公司时,面试官便让手写冒泡排序(入职之后才知道,这面试官就是一个冒泡排序"病态"爱好者,逢面试必考冒泡排序-__-)。后来看吴军的一些文章,提到提高效率的关键就是少做事情不做无用功,便对这不起眼的排序算法有了兴趣。刚好今天周末有闲,遂研究一二,与各位道友共享。 冒泡排序时间之所以效率低,就是因为将所有数都一视同仁不做区分挨个比较,这是最普通的做事方法,所以效率也是最普通的,时间复杂度为N的平方;而归并排序效率高,则是采用了分治的思想,将一个整体分成多个小份,每个小份排好序之后再互相比较,这样就比冒泡快了不少,时间复杂度为NlogN;快速排序的平均时间复杂度也是NlogN,但是实际的耗费时间会比归并排序快两三倍(当然快排在最坏的情况下时间复杂度还是N的平方,比归并排序大),它的平均执行时间能比归并更快一些是因为它每次分组时不是随机分组而是相对有序的分组,即先从数组中随机取一个数作为基数,然后将数据移动,使得基数一边的数都比它小,另一边的数都比它大,再在两边各取一个基数进行相同的移动、分组操作,递归下去,这样每个细分的小组都在整体的大数组中有个位置,合并时直接按从小到大将各个分组合并起来即可,所以一般情况下会比归并快一些。 了解了思想之后,再用代码实现相对就会容易很多。此处就再借用一个直观一点的例子来说明归并与快排二者的区别。假设有1000个学生,想对他们的成绩进行排序。方法1借用归并排序的思想,具体这样做:将这1000个人分成10组,将每组的100人进行排序,排完之后再在各组之间从小到大依次进行比较,最后得到整个的成绩排名。方法2借用快速排序的思想,具体需这样做:将1000个人也是分成10组,但是是按分数段分,0-10分的放在一组,10-20分的放在一组,20-30分的放在一组,依次类推,分完组之后再在各个小组中进行排序,而当你合并各个小组时,只需将其按从小到大的顺序直接合并就行,无需跟方法1一样将各小组中的数据取出来跟其他小组中的数据挨个比较。看到这里,想必各位道友对快排比归并排序还要快一些的原因就有了解了。 算法可以理解成做事的技巧或者说套路,我们对其的理解可以不止于编程,完全可以推广出去。比如归并的分治法,将一个大事情拆解成多个小事件,解决起来就会方便很多。闲话扯了一大堆,下面就将我自己写的排序给大家贴出来,附带上注释讲解,如果有之前不了解的道友,相信看完之后便会念头通透,原地飞升 >_< 。 正文 [erji]归并排序[/erji] // 归并排序 public static void mergeSort (int[] arr) { // 建一个临时数据来存放数据 int[] temp = new int[arr.length]; mergeSort(arr, 0, arr.length - 1, temp); } private static void mergeSort(int[] arr, int left, int right, int[] temp) { if (left < right) { // 如果起始下标跟结束下标差值小于1,则不进行操作 int mid = (left + right) / 2; mergeSort(arr, left, mid, temp); // 分组,将左边分为一组,递归调用进行排序 mergeSort(arr, mid+1, right, temp); // 将右边分为一组 merge(arr, left, mid, right, temp); //将左右分组合并 } } private static void merge(int[] arr, int left, int mid, int right, int[] temp) { int i = left; // 定义左指针 int j = mid + 1; // 定义右指针 int t = 0; // 给temp临时数组用的指针 while (i <= mid && j <= right) { // 设置左右指针的移动边界 if (arr[i] <= arr[j]) { // 此处是升序,故谁小谁先赋给临时数组 temp[t++] = arr[i++]; } else { temp[t++] = arr[j++]; } } while (i <= mid) { // 如果左边有剩余,则放在temp中 temp[t++] = arr[i++]; } while (j <= right) { // 如果右边有剩余,依次放入temp中 temp[t++] = arr[j++]; } t = 0; // 此时temp中已经是arr数组中下标从left到right之间排好序的数据了,因为temp每次都是从0开始赋值,所以需将排好序的数放回arr的对应位置 while (left <= right) { // 将left到right之间排好序的数据放回arr中,此时left到right之间的数就是最终排好序的数 arr[left++] = temp[t++]; } } 快速排序 // 快速排序 public static void quickSort (int[] arr, int left, int right) { // 先将异常情况处理掉 if (arr == null || arr.length < 2) { return; } if (right <= left) { return; } if (right - left == 1 && arr[left] <= arr[right]) { return; } // 取第一个数为基准数(基数取哪个都行,此处是为了方便) int index = arr[left]; int i = left + 1; // 左指针 int j = right; // 右指针 while (i < j && i < right && j > left) { // 设置指针的移动边界 while (arr[j] > index && j > left) {j--;} // 找到从右边数第一个比index小的数 while (arr[i] < index && i < right) {i++;} // 找到从左边数第一个比index大的数 if (i < j) { // 交换这两个数 如果i == j,说明二者定位到了同一个位置,则不用交换;如果i > j,说明二者已经相遇然后背向而行了,也不交换 int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } // 执行完上面循环后,arr已经是左边比index小,右边比index大的数组了,只是基准数仍在基准位置left处,需放到它应该在的位置 if (j != left && arr[j] != arr[left]) { // j最后停留位置的数,肯定是一个小于等于index的值,所以如果不是同一个位置的话,直接将二者调换一下位置即可 int temp = arr[j]; arr[j] = arr[left]; arr[left] = temp; } quickSort(arr, left, j-1); // 将基准数左边排序 quickSort(arr, j+1, right); // 将基准数右边排序 } 本文地址: https://www.linuxprobe.com/amount-pre-line.html 摘要:本文将就Observability中的日志聚合、分布式跟踪及具体应用中结合使用进行展开说明。 一、Observability Observability是一个最近几年开始在监控社区流行的术语。本文将Observability视为一种理念,一种监控的超集,包括监控、日志聚合、分布式跟踪,可以实时更深入地观察系统。本文将就其中的日志聚合、分布式跟踪及具体应用中结合使用进行展开说明。 二、日志与调用链的探索式查询 微服务、云和容器化架构的出现,改变了我们构建系统的方式。应用程序是分布式的,而且瞬息万变。加之底层的基础设施和网络服务愈加健壮,日常系统运维的大部分工作将来自应用程序层或者是不同应用程序之间的复杂交互调用。 对于复杂的跨系统调用,一次请求可能需要后台几台或上百台节点的支持。此时具体到一次请求已经很难通过人力确认其处理的完整流程,此时最能反映每次请求处理过程的应该是分布式追踪(下文简称调用链)。 调用链是从一次具体请求的全局角度看待问题,当细粒具体到一个节点时,应用系统自身打印的日志最能说明当前节点处理逻辑。 下面通过一张简图来说明调用链和日志聚合做的事情: 调用链的作用是将一次请求所经过的所有节点和关键操作进行记录并汇总展示出来,就像图中绿色箭头,能够提供一个全局的视角去看待一次请求。 日志聚合的作用是将所有节点和系统产生的日志进行汇总整理,并提供给用户一个有效并友好的查询能力。 但是我们在具体使用过程中往往是这样的: 从调用链进来以后发现了一个问题,然后切换到日志聚合去根据特定属性查询对应的日志信息,通过排查日志信息发现还需要再次去查询与之关联的调用链信息……如此往返多次。 日志与调用链的探索式查询对于这种经典场景提供了一种新的闭环处理问题模式: 从调用链入口进入,可以根据调用链关联到具体应用的与当前调用链相关的日志,根据日志也可以关联到具体一条调用链;从日志入口进入,可以根据日志关联到与当前日志相关的具体一条调用链,根据一条调用链又可以关联到与当前调用链相关联的日志。而且两种模式可以相互切换。 三、举个栗子 用户小明通过日志聚合搜索发现有A系统一段日志有异常信息,此时他可以通过此条日志关联找出对应的调用过程a。通过观察a这条调用链小明发现,是由于a上的节点a[2]超时导致。此时小明可以从调用链关联到与节点a[2]相关的日志内容从而确定问题所在(具体效果见下文)。 四、整体架构设计 4.1 数据抓取: 应用集群中的机器上部署的agent用于数据收集和上送,探针内嵌在容器(tomcat等)用于为应用画像和收集应用信息 4.2 数据传输: agent将处理过后的日志通过mq上送到监控服务器 4.3 数据处理及存储: 监控服务器将采集上来的数据进行处理并将其存入ES,方便用户通过特定特征快速定位 4.4 数据展示: 将数据进行可视化展示,并提供方便的可视化自定义查询服务 五、具体实现 在介绍调用链和日志聚合具体实现之前需要明确的几个概念: 5.1 中间件劫持技术 通过在中间件启动时动态将我们自己的代码行为植入到中间件的各种行为中的技术。比如在tomcat启动时动态在tomcat处理请求的开始位置添加代码劫持,则能够实现在tomcat执行处理请求逻辑之前进行服务调用画像等功能。更多能力和实现方式可以参考: http://chuansong.me/n/603660351655 5.2 traceId 通过中间件劫持技术在服务调用最前端产生且能够唯一确定一条调用链的id。 主要实现逻辑: 在应用容器启动时,使用中间件劫持技术在服务调用入口和应用日志写文件入口位置添加劫持点 在发生服务调用时生成调用链元数据和上下文 当应用写日志时通过写文件入口劫持点获取当前调用的调用链上下文,将traceId与应用日志一同写入应用日志文件 日志归集将生成的日志文件聚合整理上送到监控服务器 监控服务器将收集到的日志信息,进行处理并存入es web页面将存储在es中的数据进行展示 核心逻辑如下图: 六、调用链和日志聚合实现 调用链部分分为:模型设计、服务端信息收集(轻/重)、方法级信息收集(轻/重)、客户端信息收集(轻/重)、调用链协议设计(轻/重)、调用链上下文传递、调用信息记录及传递、调用数据统计处理几个关键过程。关键技术为中间件劫持增强框架、调用模型设计和调用链上下文传递。 应用日志部分分为:日志归集,日志内容处理传输,服务端日志处理及存储等几个关键步骤。关键技术为:服务画像技术、日志归集。 七、效果展示 7.1 调用链入口 通过特定条件搜素出关心的具体一次调用过程,点击进入调用的详细过程界面。 点击右侧的关联按钮可快速定位到与之相关联日志。 7.2 日志入口 通过特定特征(图中为按照Hello关键字进行搜索)搜索出符合条件的日志。 通过点击具体日志即可进入对应的调用过程。 参考资料 更多详情: https://uavorg.github.io/documents/uavdoc_architecture/moniorframework/diao-yong-lian-shu-ju-sheng-cheng.html 下载UAVStack的源码( https://github.com/uavorg) 下载AllInOne开发演示版( https://uavorg.github.io/main/) 作者:李崇 来源:宜信技术学院 注:kuberntes版本为1.15 什么是 Ingress Ingress 是一个提供对外服务的路由和负载均衡器,其本质是个nginx控制器服务。 k8s文档上Ingress经典数据链路图: internet | [ Ingress ] --|-----|-- [ Services ] 对博客进行改造 构建Dockefile 先容器化整个Hexo项目,构建 Dockefile ,这里采用nginx + 静态资源的形式部署(主要为了节约内存CPU): FROM nginx:1.13.0-alpine LABEL maintainer="hexo-shikanon-blog " # 装载编译后的文件对外访问 COPY ./public /usr/share/nginx/html 构建Deployment 构建一个 Deployment 服务将其部署上kubernetes: apiVersion: apps/v1 kind: Deployment metadata: name: nginx-hexo-blog-delopyment labels: webtype: staticblog spec: replicas: 2 selector: matchLabels: webtype: staticblog template: metadata: labels: webtype: staticblog function: blog spec: containers: - name: hexo-blog image: nginx-hexo-blog:0.0.1 ports: - containerPort: 80 构建Service暴露服务端口 构建一个 Service 暴露统一的服务端口: apiVersion: v1 kind: Service metadata: name: static-blog spec: selector: webtype: staticblog ports: - protocol: TCP port: 80 targetPort: 80 # deployment的端口, 这里创建一个名称为 "static-blog" 的 Service 对象,它会将请求代理到使用 TCP 端口 targetPort,并且具有标签 "webtype: staticblog" 的 Pod 上。 查看端口信息: $ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.13.0.1 443/TCP 10d static-blog ClusterIP 10.13.83.44 80/TCP 8h 测试端口是否可以访问: $ curl -I 10.13.83.44 HTTP/1.1 200 OK Server: nginx/1.13.0 Date: Wed, 16 Oct 2019 16:51:13 GMT Content-Type: text/html Content-Length: 71636 Last-Modified: Mon, 29 Jul 2019 19:25:29 GMT Connection: keep-alive ETag: "5d3f4829-117d4" Accept-Ranges: bytes 构建Ingress服务 最后一步,构建 Ingress 服务对外部提供服务和反向代理: apiVersion: extensions/v1beta1 kind: Ingress metadata: name: reverse-proxy annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: rules: - host: www.shikanon.com http: paths: - backend: serviceName: static-blog servicePort: 80 完成! 构建HTTPS网站 用secret类型对象保存密钥数据 Secret 对象类型用来保存敏感信息,例如密码、OAuth 令牌和 ssh key,其中 ssh key 就是一个经典的应用。 Secret 参数用例: kubectl create secret -h Create a secret using specified subcommand. Available Commands: docker-registry Create a secret for use with a Docker registry generic Create a secret from a local file, directory or literal value tls Create a TLS secret Usage: kubectl create secret [flags] [options] 创建 Secret 加密对象: kubectl create secret tls shikanon-ssh-key-secret --cert=/home/shikanon/web/www/ssl/cert.pem --key=/home/shikanon/web/www/ssl/private.key 修改Ingress: apiVersion: extensions/v1beta1 kind: Ingress metadata: name: reverse-proxy annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: rules: - host: www.shikanon.com http: paths: - backend: serviceName: static-blog servicePort: 80 tls: - hosts: - www.shikanon.com secretName: shikanon-ssh-key-secret 注:一个Ingress只能支持一个tls 使用国内镜像 mv /etc/apt/sources.list /etc/apt/sources.list.bak echo "deb http://mirrors.163.com/debian/ jessie main non-free contrib" >> /etc/apt/sources.list echo "deb http://mirrors.163.com/debian/ jessie-proposed-updates main non-free contrib" >>/etc/apt/sources.list echo "deb-src http://mirrors.163.com/debian/ jessie main non-free contrib" >>/etc/apt/sources.list echo "deb-src http://mirrors.163.com/debian/ jessie-proposed-updates main non-free contrib" >>/etc/apt/sources.list 更新 apt-get 指令 apt-get update 安装 yum 命令 apt-get install vim 添加 ls 命令 vim ~/.bashrc 在最后一行添加 alias ll='ls $LS_OPTIONS -l' 使之生效 source ~/.bashrc node_modules/x-pack/plugins/translations/translations/zh-CN.json kbn.discoverTitle kbn.home.welcomeTitle xpack.security.loginPage.welcomeTitle 首先 :谷歌浏览器右键打开属性,在箭头所指的位置复制粘贴 -no-sandbox。(需要空一格再写入 -no-sandbox) 其次 :你打开谷歌浏览器可以看到如下提醒,提醒你,稳定性和安全性会有所下降,但表担心,只要能打开浏览器就很惊喜了! 剩下的问题就很好解决了 !!点击右上角的 三个点 ---》点击 设置 ---》拖到最下边 高级设置 ---》再拖到最下边 重置 ---》完美 最后,打开浏览器就可以用了。 ps:2019/10/15补充 谷歌浏览器不知道怎么了,最近打开都是崩溃,如果你们的网随便下载,最好重新下一个吧!我现在几乎不重置了,直接在--no-sandbox下使用!! 1、什么是ansible? ansible是一个部署一群远程主机的自动化运维工具,基于Python开发,集合了众多运维工具(puppet、cfengine、chef、func、fabric)的优点,它不需要安装客户端,通过SSH协议实现远程节点和管理节点之间的通信,实现了批量系统配置、批量程序部署、批量运行命令等功能。 ansible是基于模块工作的,模块可以由任何语言开发,它不仅支持命令行使用模块,也支持编写yaml格式的playbook。 2、ansible的安装 两台主机,jin-10,jin-11,我们只需要在jin-10上安装ansible即可。 [root@jin-10 ~]# yum install -y ansible 然后把jin-10上的公钥文件id_rsa.pub复制到jin-11上的/root/.ssh/authorized_keys里,同时,对本机也进行密钥认证(把/root/.ssh/id_rsa.pub文件的内容复制到authorized_keys里)。 [root@jin-10 ~]# ssh jin-10 Last login: Tue Sep 3 12:27:50 2019 from jin-11 [root@jin-10 ~]# ssh jin-11 Last login: Tue Sep 3 12:24:22 2019 from jin-10 可以看到,jin-10用ssh的方式连接本机和jin-11都不用密码和密钥认证。 然后我们设置一个主机组,用来告诉Ansible需要管理哪些主机。编辑文件vim /etc/ansible/hosts,内容如下: [testhost] 127.0.0.1 jin-11 其中,testhost为自定义的主机组名字,然后是两个主机的IP(或主机名)。 3、ansible的使用 ansible模块Module bash无论在命令行上执行,还是bash脚本中,都需要调用cd、ls、copy、yum等命令;module就是ansible的“命令”,module是ansible命令行和脚本中都需要调用的。常用的ansible module有yum、copy、template等。 在bash,调用命令时可以跟不同的参数,每个命令的参数都是该命令自定义的;同样,ansible中调用module也可以跟不同的参数,每个module的参数也都是由module自定义的。 在命令行中,-m后面接调用module的名字,-a后面接调用module的参数: [root@jin-10 ~]# ansible all -m copy -a "src=/etc/hosts dest=/tmp/hosts" jin-11 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "checksum": "843eede91ec66c16e38ddb67dee1bb173ea7fe9c", "dest": "/tmp/hosts", "gid": 0, "group": "root", "mode": "0644", "owner": "root", "path": "/tmp/hosts", "secontext": "unconfined_u:object_r:admin_home_t:s0", "size": 182, "state": "file", "uid": 0 } 127.0.0.1 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "checksum": "843eede91ec66c16e38ddb67dee1bb173ea7fe9c", "dest": "/tmp/hosts", "gid": 0, "group": "root", "mode": "0644", "owner": "root", "path": "/tmp/hosts", "secontext": "unconfined_u:object_r:admin_home_t:s0", "size": 182, "state": "file", "uid": 0 } 在playbook脚本中,tasks中的每一个action都是对module的一次调用。在每个action中:冒号前面是module的名字,冒号后面是调用module的参数: --- tasks: - name: ensure apache is at the latest version yum: pkg=httpd state=latest - name: write the apache config file template: src=templates/httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf - name: ensure apache is running service: name=httpd state=started 下面是一些常用的module: 调试和测试类的module: ping - ping一下你的远程主机,如果可以通过ansible成功连接,那么返回pong debug - 用于调试的module,只是简单打印一些消息,有点像linux的echo命令。 文件类的module: copy - 从本地拷贝文件到远程节点 template - 从本地拷贝文件到远程节点,并进行变量的替换 file - 设置文件的属性 linux上常用的操作: user - 管理用户账户 yum - redhat系linux上的安装包管理 service - 管理服务 firewalld - 管理防火墙中的服务和端口 执行Shell命令: shell - 在节点上执行shell命令,支持$HOME和"<", ">", "|", ";" and "&" command - 在远程节点上面执行命令,不支持$HOME和"<", ">", "|", ";" and "&" 3.1 ansible远程执行命令 例:ansible testhost -m command -a 'w' 此处的testhost为主机组名(也可以直接写一个ip,针对某一台机器来执行命令),-m后边是模块名字,-a后面是命令 [root@jin-10 ~]# ansible testhost -m command -a 'w' jin-11 | CHANGED | rc=0 >> 09:07:34 up 21:59, 2 users, load average: 0.00, 0.01, 0.05 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT root pts/0 192.168.154.1 二11 20:39m 0.33s 0.33s -bash root pts/1 jin-10 09:07 1.00s 0.28s 0.02s w 127.0.0.1 | CHANGED | rc=0 >> 09:07:34 up 23:03, 3 users, load average: 0.27, 0.14, 0.19 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT root pts/0 192.168.154.1 二10 6.00s 6.63s 5.48s ssh jin-10 root pts/1 fe80::d601:fb6f: 二12 6.00s 7.09s 0.00s ssh -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/21f0e6a9ae -tt 127.0.0.1 /bin/sh -c '/usr/bin/python /root/.ansible/tmp/ansible-tmp-1567559253.0-170559931011383/AnsiballZ_command.py && sleep 0' root pts/4 localhost 09:07 1.00s 0.51s 0.01s w 针对某一台机器来执行命令: [root@jin-10 ~]# ansible jin-11 -m command -a 'hostname' jin-11 | CHANGED | rc=0 >> jin-11 模块shell也可以同样实现: [root@jin-10 ~]# ansible testhost -m shell -a 'hostname' jin-11 | CHANGED | rc=0 >> jin-11 127.0.0.1 | CHANGED | rc=0 >> jin-10 3.2 ansible拷贝文件或目录 ansible拷贝目录时,如果目标指定的目录不存在,它会自动创建: [root@jin-10 ~]# ansible jin-11 -m copy -a "src=/etc/ansible dest=/tmp/ansible_test owner=root group=root mode=0755" jin-11 | CHANGED => { "changed": true, "dest": "/tmp/ansible_test/", "src": "/etc/ansible" } [root@jin-11 ~]# ll /tmp/ansible_test 总用量 0 drwxr-xr-x. 3 root root 51 9月 4 09:24 ansible 可以看到目标目录被创建且源目录在目标目录下面。 操作时,要注意src和dest是文件还是目录,拷贝文件时,如果目标目录已经存在同名目录,那么会把该文件放到同名目录里面: [root@jin-11 /tmp]# ll -t 总用量 4 drwxr-xr-x. 2 root root 6 9月 4 09:32 m_test [root@jin-10 ~]# ansible jin-11 -m copy -a "src=/etc/passwd dest=/tmp/m_test" jin-11 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "checksum": "1db31b171f911344344ceb7cf8621d31be83ec07", "dest": "/tmp/m_test/passwd", "gid": 0, "group": "root", "md5sum": "e1a0ce587167dc3587ee5f1b357dc3c4", "mode": "0644", "owner": "root", "secontext": "unconfined_u:object_r:admin_home_t:s0", "size": 1299, "src": "/root/.ansible/tmp/ansible-tmp-1567560870.7-110358489604181/source", "state": "file", "uid": 0 } [root@jin-11 /tmp]# ls m_test/ passwd 3.3 ansible远程执行脚本 在jin-10上编写一个test_ansible.sh的脚本,内容如下: #!/bin/bash echo `date` >/tmp/test_ansible.txt 然后分发到主机组testhost: [root@jin-10 ~]# ansible testhost -m copy -a "src=/tmp/test_ansible.sh dest=/tmp/test.sh mode=0755" jin-11 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "checksum": "0dc6471ba363889d83d3bf98c4354d874c86bd19", "dest": "/tmp/test.sh", "gid": 0, "group": "root", "md5sum": "bfee9509a7066ca2e822dbe48b21fbe7", "mode": "0755", "owner": "root", "secontext": "unconfined_u:object_r:admin_home_t:s0", "size": 48, "src": "/root/.ansible/tmp/ansible-tmp-1567561704.9-93797175448931/source", "state": "file", "uid": 0 } 127.0.0.1 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "checksum": "0dc6471ba363889d83d3bf98c4354d874c86bd19", "dest": "/tmp/test.sh", "gid": 0, "group": "root", "md5sum": "bfee9509a7066ca2e822dbe48b21fbe7", "mode": "0755", "owner": "root", "secontext": "unconfined_u:object_r:admin_home_t:s0", "size": 48, "src": "/root/.ansible/tmp/ansible-tmp-1567561704.82-59120276764010/source", "state": "file", "uid": 0 } 然后批量执行该脚本: [root@jin-10 ~]# ansible testhost -m shell -a "/tmp/test.sh" jin-11 | CHANGED | rc=0 >> 127.0.0.1 | CHANGED | rc=0 >> [root@jin-10 ~]# ls /tmp/test_ansible.txt /tmp/test_ansible.txt [root@jin-10 ~]# cat !$ cat /tmp/test_ansible.txt 2019年 09月 04日 星期三 09:50:23 CST [root@jin-11 /tmp]# ls /tmp/test_ansible.txt /tmp/test_ansible.txt [root@jin-11 /tmp]# cat !$ cat /tmp/test_ansible.txt 2019年 09月 04日 星期三 09:50:23 CST 3.4 ansible管理任务计划 使用cron模块可以创建计划任务: [root@jin-10 ~]# ansible jin-11 -m cron -a "name='test cron' job='/bin/touch /tmp/test_cron' weekday=6" jin-11 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "envs": [], "jobs": [ "test cron" ] } [root@jin-11 /tmp]# crontab -l #Ansible: test cron * * * * 6 /bin/touch /tmp/test_cron 使用cron模块删除计划任务: [root@jin-10 ~]# ansible jin-11 -m cron -a "name='test cron' state=absent" jin-11 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "envs": [], "jobs": [] } [root@jin-11 /tmp]# crontab -l 可以看到,之前创建的计划任务已经被删除了。 3.5 ansible安装包和管理服务 ansible的yum模块可以安装包和管理服务(可以进行安装包的安装、卸载和远程启动等)。 我们在jin-11上安装httpd为例: [root@jin-11 ~]# rpm -qa httpd [root@jin-11 ~]# 可以看到,jin-11是没有安装httpd的。 在jin-10上执行如下命令: [root@jin-10 ~]# ansible jin-11 -m yum -a "name=httpd state=installed" jin-11 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "changes": { "installed": [ "httpd" ] }, "msg": "", "rc": 0, "results": [ "Loaded plugins: fastestmirror\nLoading mirror speeds from cached hostfile\n * base: mirrors.tuna.tsinghua.edu.cn\n * epel: hkg.mirror.rackspace.com\n * extras: mirrors.aliyun.com\n * updates: mirrors.tuna.tsinghua.edu.cn\nResolving Dependencies\n--> Running transaction check\n---> Package httpd.x86_64 0:2.4.6-89.el7.centos.1 will be installed\n--> Processing Dependency: httpd-tools = 2.4.6-89.el7.centos.1 for package: httpd-2.4.6-89.el7.centos.1.x86_64\n--> Processing Dependency: /etc/mime.types for package: httpd-2.4.6-89.el7.centos.1.x86_64\n--> Processing Dependency: libaprutil-1.so.0()(64bit) for package: httpd-2.4.6-89.el7.centos.1.x86_64\n--> Processing Dependency: libapr-1.so.0()(64bit) for package: httpd-2.4.6-89.el7.centos.1.x86_64\n--> Running transaction check\n---> Package apr.x86_64 0:1.4.8-3.el7_4.1 will be installed\n---> Package apr-util.x86_64 0:1.5.2-6.el7 will be installed\n---> Package httpd-tools.x86_64 0:2.4.6-89.el7.centos.1 will be installed\n---> Package mailcap.noarch 0:2.1.41-2.el7 will be installed\n--> Finished Dependency Resolution\n\nDependencies Resolved\n\n================================================================================\n Package Arch Version Repository Size\n================================================================================\nInstalling:\n httpd x86_64 2.4.6-89.el7.centos.1 updates 2.7 M\nInstalling for dependencies:\n apr x86_64 1.4.8-3.el7_4.1 base 103 k\n apr-util x86_64 1.5.2-6.el7 base 92 k\n httpd-tools x86_64 2.4.6-89.el7.centos.1 updates 91 k\n mailcap noarch 2.1.41-2.el7 base 31 k\n\nTransaction Summary\n================================================================================\nInstall 1 Package (+4 Dependent packages)\n\nTotal download size: 3.0 M\nInstalled size: 10 M\nDownloading packages:\n--------------------------------------------------------------------------------\nTotal 260 kB/s | 3.0 MB 00:11 \nRunning transaction check\nRunning transaction test\nTransaction test succeeded\nRunning transaction\n Installing : apr-1.4.8-3.el7_4.1.x86_64 1/5 \n Installing : apr-util-1.5.2-6.el7.x86_64 2/5 \n Installing : httpd-tools-2.4.6-89.el7.centos.1.x86_64 3/5 \n Installing : mailcap-2.1.41-2.el7.noarch 4/5 \n Installing : httpd-2.4.6-89.el7.centos.1.x86_64 5/5 \n Verifying : httpd-2.4.6-89.el7.centos.1.x86_64 1/5 \n Verifying : mailcap-2.1.41-2.el7.noarch 2/5 \n Verifying : httpd-tools-2.4.6-89.el7.centos.1.x86_64 3/5 \n Verifying : apr-util-1.5.2-6.el7.x86_64 4/5 \n Verifying : apr-1.4.8-3.el7_4.1.x86_64 5/5 \n\nInstalled:\n httpd.x86_64 0:2.4.6-89.el7.centos.1 \n\nDependency Installed:\n apr.x86_64 0:1.4.8-3.el7_4.1 apr-util.x86_64 0:1.5.2-6.el7 \n httpd-tools.x86_64 0:2.4.6-89.el7.centos.1 mailcap.noarch 0:2.1.41-2.el7 \n\nComplete!\n" ] } 再在jin-11上查看,已经安装了httpd包,并且没有启动: [root@jin-11 ~]# rpm -qa httpd httpd-2.4.6-89.el7.centos.1.x86_64 [root@jin-11 ~]# ps aux |grep httpd root 4256 0.0 0.1 112724 996 pts/0 S+ 11:29 0:00 grep --color=auto httpd 我们再远程启动httpd: [root@jin-10 ~]# ansible jin-11 -m service -a "name=httpd state=started enabled=no" 再查看jin-11是否成功启动httpd服务: [root@jin-11 ~]# ps aux |grep httpd root 4355 19.0 0.5 230408 5184 ? Ss 11:31 0:00 /usr/sbin/httpd -DFOREGROUND apache 4356 0.0 0.3 230408 3004 ? S 11:31 0:00 /usr/sbin/httpd -DFOREGROUND apache 4357 0.0 0.3 230408 3004 ? S 11:31 0:00 /usr/sbin/httpd -DFOREGROUND apache 4358 0.0 0.3 230408 3004 ? S 11:31 0:00 /usr/sbin/httpd -DFOREGROUND apache 4360 0.0 0.3 230408 3004 ? S 11:31 0:00 /usr/sbin/httpd -DFOREGROUND apache 4361 0.0 0.3 230408 3004 ? S 11:31 0:00 /usr/sbin/httpd -DFOREGROUND root 4370 0.0 0.1 112724 996 pts/0 R+ 11:31 0:00 grep --color=auto httpd 可以看到,httpd服务已经成功启动。 4、ansible playbook playbook是指一个可被ansible执行的yml文件,最基本的playbook脚本分为三个部分: 1、在什么机器上以什么身份执行: hosts:为主机的IP,或者主机组名,或者关键字all。 users:在远程以哪个用户身份执行。 ... 2、执行的任务有哪些: tasks tasks是从上到下顺序执行,如果中间发生错误,那么整个playbook会中止。我们可以改修文件后,再重新执行。 每一个task都是对module的一次调用,只是使用不同的参数和变量而已。 每一个task最好有name属性(可选),这个是供人读的,没有实际的操作。然后会在命令行里面输出,提示用户执行情况。 task中每个action会调用一个module,在module中会去检查当前系统状态是否需要重新执行。 如果本次执行了,那么action会得到返回值changed。 如果不需要执行,那么action得到返回值ok。 3、善后的任务有哪些: handlers handler也是对module的一次调用,与tasks不同,tasks会默认的按定义顺序执行每一个task,handlers则不会,它需要在tasks中被调用,才有可能被执行。 handler可以理解为playbook的event,在ansible中,只在task的执行状态为changed的时候,才会执行该task调用的handler,这也是handler与普通的event机制不同的地方。 如果有多个task notify同一个handler,那么只执行一次。 handler的使用场景:例如执行task之后,服务器状态发生变化之后要执行的一些操作,比如我们修改了配置文件后,需要重启一下服务,这时候就可以用handler模块。 下面我们来编写一个简单的playbook,vim /etc/ansible/test.yml: --- - hosts: jin-11 remote_user: root tasks: - name: test_playbook shell: touch /tmp/test_playbook.txt 然后执行: [root@jin-10 /etc/ansible]# ansible-playbook /etc/ansible/test.yml PLAY [jin-11] ************************************************************************ TASK [Gathering Facts] *************************************************************** ok: [jin-11] TASK [test_playbook] ***************************************************************** [WARNING]: Consider using the file module with state=touch rather than running 'touch'. If you need to use command because file is insufficient you can add 'warn: false' to this command task or set 'command_warnings=False' in ansible.cfg to get rid of this message. changed: [jin-11] PLAY RECAP *************************************************************************** jin-11 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 [root@jin-11 ~]# ll -t /tmp/ 总用量 16 -rw-r--r--. 1 root root 0 9月 4 14:49 test_playbook.txt 可以看到,在jin-11的/tmp/下生成了文件test_playbook.txt。 4.1 playbook的变量 自定义变量 在playbook中,通过vars关键字自定义变量,使用时用{{ }}引用以来即可。 我们以创建用户为例,编辑vim /etc/ansible/create_user.yml,内容如下: --- - name: create_user hosts: jin-11 user: root gather_facts: false vars: - user: "test" tasks: - name: create user user: name="{{ user }}" 然后执行: [root@jin-10 /etc/ansible]# ansible-playbook /etc/ansible/create_user.yml PLAY [create_user] ******************************************************************* TASK [create user] ******************************************************************* changed: [jin-11] PLAY RECAP *************************************************************************** jin-11 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 在jin-11上查看: [root@jin-11 ~]# id test uid=1002(test) gid=1002(test) 组=1002(test) 主机的系统变量(facts) ansible会通过module setup来收集主机的系统信息,这些收集到的系统信息叫做facts,这些facts信息可以直接以变量的形式使用。 [root@jin-10 ~]# ansible all -m setup -u root jin-11 | SUCCESS => { "ansible_facts": { "ansible_all_ipv4_addresses": [ "192.168.154.11" ], "ansible_all_ipv6_addresses": [ "fe80::c9a8:961e:9b39:d180" ], "ansible_apparmor": { "status": "disabled" }, "ansible_architecture": "x86_64", ... 文件模板中使用的变量 在playbook中定义的变量,可以直接在template中使用,facts变量也可以直接在template中使用,包含在inventory里面定义的host和group变量。只要是在playbook中可以访问的变量,都可以在template文件中使用。 把运行结果当做变量使用-注册变量 把task的执行结果也可以作为一个变量值。这个时候就需要用到“注册变量”,将执行结果注册到一个变量中,待后面的action使用,注册变量经常和debug module一起使用,这样可以得到更多action的输出信息,帮助用户调试。 --- - hosts: all tasks: - shell: ls register: result ignore_errors: True - shell: echo "{{ result.stdout }}" when: result.rc == 5 - debug: msg="{{ result.stdout }}" 用命令行传递参数 为了使Playbook更灵活、通用性更强,允许用户在执行的时候传入变量的值,这个时候就需要用到“额外变量”。 在playbook中定义的变量,需要从命令行传递变量值。 下面是在命令行里面传值得的方法: [root@jin-10 ~]# ansible-playbook for_var_test.yml --extra-vars "hosts=jin-11 user=root" 可以用json格式传递参数: [root@jin-10 ~]# ansible-playbook for_var_test.yml --extra-vars "{ 'hosts': 'jin-11', 'user': 'root' }" 还可以把参数放在文件里面: [root@jin-10 ~]# ansible-playbook for_var_test.yml --extra-vars "@vars.json" 4.2 playbook中的条件判断 when: 类似于编程语言中的if语句 编辑文件vim /etc/ansible/when.yml,内容如下: --- - hosts: testhost user: root gather_facts: True tasks: - name: use when shell: touch /tmp/when.txt when: ansible_ens32.ipv4.address == "192.168.154.11" 执行: [root@jin-10 ~]# ansible-playbook /etc/ansible/when.yml PLAY [testhost] ********************************************************************** TASK [Gathering Facts] *************************************************************** ok: [jin-11] ok: [127.0.0.1] TASK [use when] ********************************************************************** skipping: [127.0.0.1] [WARNING]: Consider using the file module with state=touch rather than running 'touch'. If you need to use command because file is insufficient you can add 'warn: false' to this command task or set 'command_warnings=False' in ansible.cfg to get rid of this message. changed: [jin-11] PLAY RECAP *************************************************************************** 127.0.0.1 : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 jin-11 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 查看结果: [root@jin-10 ~]# ll -t /tmp/ 总用量 12 -rw-r--r--. 1 root root 43 9月 4 09:50 test_ansible.txt -rwxr-xr-x. 1 root root 48 9月 4 09:48 test.sh [root@jin-11 ~]# ll -t /tmp/ 总用量 16 -rw-r--r--. 1 root root 0 9月 5 09:33 when.txt -rw-r--r--. 1 root root 0 9月 4 14:49 test_playbook.txt 可以看到jin-10没有生成when.txt文件,而jin-11则生成了。 4.3 playbook循环 loop: 类似于编程语言中的while语句,在playbook脚本中一般写作with_... 编辑文件vim /etc/ansible/while.yml,内容如下: --- - hosts: testhost user: root tasks: - name: change mode for files file: path=/tmp/{{ item }} state=touch mode=600 with_items: - while1.txt - while2.txt - while3.txt 执行: [root@jin-10 ~]# ansible-playbook /etc/ansible/while.yml PLAY [testhost] ********************************************************************** TASK [Gathering Facts] *************************************************************** ok: [jin-11] ok: [127.0.0.1] TASK [change mode for files] ********************************************************* changed: [jin-11] => (item=while1.txt) changed: [127.0.0.1] => (item=while1.txt) changed: [jin-11] => (item=while2.txt) changed: [jin-11] => (item=while3.txt) changed: [127.0.0.1] => (item=while2.txt) changed: [127.0.0.1] => (item=while3.txt) PLAY RECAP *************************************************************************** 127.0.0.1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 jin-11 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 查看结果: [root@jin-10 ~]# ll -t /tmp/ 总用量 12 -rw-------. 1 root root 0 9月 5 09:45 while3.txt -rw-------. 1 root root 0 9月 5 09:45 while2.txt -rw-------. 1 root root 0 9月 5 09:45 while1.txt -rw-r--r--. 1 root root 43 9月 4 09:50 test_ansible.txt [root@jin-11 ~]# ll -t /tmp/ 总用量 16 -rw-------. 1 root root 0 9月 5 09:45 while3.txt -rw-------. 1 root root 0 9月 5 09:45 while2.txt -rw-------. 1 root root 0 9月 5 09:45 while1.txt -rw-r--r--. 1 root root 0 9月 5 09:33 when.txt 4.4 用playbook安装nginx 思路:先在一台机器上编译安装好nginx、打包,然后再用ansible去下发。 - 创建相关的目录 进入ansible配置文件目录, 创建一个nginx_install的目录,方便管理,最终目录如下: [root@jin-10 /var/lib/docker]# cd /etc/ansible/ [root@jin-10 /etc/ansible]# mkdir nginx_install [root@jin-10 /etc/ansible]# cd !$ cd nginx_install [root@jin-10 /etc/ansible/nginx_install]# mkdir -p roles/{common,install}/{handlers,files,meta,tasks,templates,vars} [root@jin-10 /etc/ansible/nginx_install]# tree . └── roles ├── common │ ├── files │ ├── handlers │ ├── meta │ ├── tasks │ ├── templates │ └── vars └── install ├── files ├── handlers ├── meta ├── tasks ├── templates └── vars 说明:roles目录下有两个角色,common为一些准备操作,install为安装nginx的操作。 每个角色下面又有几个目录,handlers下面是当发生改变时要执行的操作,通常用在配置文件发生改变,重启服务。files为安装时用到的一些文件,meta为说明信息,说明角色依赖等信息,tasks里面是核心的配置文件,templates通常存一些配置文件,启动脚本等模板文件,vars下为定义的变量。 准备相关的文件 之前已经在jin-10上编译安装了nginx,相关目录文件如下: nginx的目录: [root@jin-10 /etc/ansible/nginx_install]# ls /usr/local/nginx/ client_body_temp fastcgi_temp logs sbin uwsgi_temp conf html proxy_temp scgi_temp nginx的启动脚本: [root@jin-10 /etc/ansible/nginx_install]# ls /etc/init.d/nginx /etc/init.d/nginx nginx的配置文件: [root@jin-10 /etc/ansible/nginx_install]# ls /usr/local/nginx/conf/nginx.conf /usr/local/nginx/conf/nginx.conf 把nginx的目录打包,并复制文件到之前创建的目录中: [root@jin-10 /usr/local]# tar czf nginx.tar.gz --exclude "nginx.conf" --exclude "vhost" nginx/ [root@jin-10 /usr/local]# mv nginx.tar.gz /etc/ansible/nginx_install/roles/install/files/ [root@jin-10 /usr/local]# cp nginx/conf/nginx.conf /etc/ansible/nginx_install/roles/install/templates/ [root@jin-10 /usr/local]# cp /etc/init.d/nginx /etc/ansible/nginx_install/roles/install/templates/ 定义common的tasks,安装nginx需要的一些依赖包: 进入/etc/ansible/nginx_install/roles/目录,并编写/etc/ansible/nginx_install/roles/common/tasks/main.yml文件,内容如下: [root@jin-10 /etc/ansible/nginx_install/roles]# vim /etc/ansible/nginx_install/roles/common/tasks/main.yml - name: Install initializtion require software yum: name={{ item }} state=installed with_items: - zlib-devel - pcre-devel 再编写一个定义变量的文件vim /etc/ansible/nginx_install/roles/install/vars/main.yml,内容如下: nginx_user: www nginx_port: 80 nginx_basedir: /usr/local/nginx 然后再创建一个配置文件vim /etc/ansible/nginx_install/roles/install/tasks/copy.yml ,用于拷贝文件到目标机器: - name: Copy nginx software copy: src=nginx.tar.gz dest=/tmp/nginx.tar.gz owner=root group=root - name: Uncompression nginx software shell: tar zxf /tmp/nginx.tar.gz -C /usr/local/ - name: Copy nginx start script template: src=nginx dest=/etc/init.d/nginx owner=root group=root mode=0755 - name: Copy nginx config template: src=nginx.conf dest={{ nginx_basedir }}/conf/ owner=root group=root mode=06 44 再创建一个文件vim /etc/ansible/nginx_install/roles/install/tasks/install.yml ,用于建立用户,启动服务,删除压缩包: - name: Create nginx user user: name={{ nginx_user }} state=present createhome=no shell=/sbin/nologin - name: Start nginx service shell: /etc/init.d/nginx start - name: Add boot start nginx service shell: chkconfig --level 345 nginx on - name: Delete nginx compression files shell: rm -rf /tmp/nginx.tar.gz 再创建一个调用copy和install的文件 /etc/ansible/nginx_install/roles/install/tasks/main.yml,内容如下: - include: copy.yml - include: install.yml 最后,我们再定义一个入口配置文件/etc/ansible/nginx_install/install.yml,内容如下: --- - hosts: jin-11 remote_user: root gather_facts: True roles: - common - install 最终目录文件如下: [root@jin-10 /etc/ansible/nginx_install]# tree . ├── install.yml └── roles ├── common │ ├── files │ ├── handlers │ ├── meta │ ├── tasks │ │ └── main.yml │ ├── templates │ └── vars └── install ├── files │ └── nginx.tar.gz ├── handlers ├── meta ├── tasks │ ├── copy.yml │ ├── install.yml │ └── main.yml ├── templates │ ├── nginx │ └── nginx.conf └── vars └── main.yml 执行脚本: [root@jin-11 ~]# yum remove nginx 已加载插件:fastestmirror 参数 nginx 没有匹配 不删除任何软件包 如果目标机器上已安装了nginx,先用命令yum remove nginx删除已安装的nginx。 然后执行playbook脚本: [root@jin-10 /etc/ansible/nginx_install]# ansible-playbook /etc/ansible/nginx_install/install.yml PLAY [jin-11] ************************************************************************ TASK [Gathering Facts] *************************************************************** ok: [jin-11] TASK [common : Install initializtion require software] ******************************* [DEPRECATION WARNING]: Invoking "yum" only once while using a loop via squash_actions is deprecated. Instead of using a loop to supply multiple items and specifying `name: "{{ item }}"`, please use `name: ['zlib-devel', 'pcre-devel']` and remove the loop. This feature will be removed in version 2.11. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg. ok: [jin-11] => (item=[u'zlib-devel', u'pcre-devel']) TASK [install : Copy nginx software] ************************************************* changed: [jin-11] TASK [install : Uncompression nginx software] **************************************** [WARNING]: Consider using the unarchive module rather than running 'tar'. If you need to use command because unarchive is insufficient you can add 'warn: false' to this command task or set 'command_warnings=False' in ansible.cfg to get rid of this message. changed: [jin-11] TASK [install : Copy nginx start script] ********************************************* ok: [jin-11] TASK [install : Copy nginx config] *************************************************** ok: [jin-11] TASK [install : Create nginx user] *************************************************** changed: [jin-11] TASK [install : Start nginx service] ************************************************* changed: [jin-11] TASK [install : Add boot start nginx service] **************************************** changed: [jin-11] TASK [install : Delete nginx compression files] ************************************** [WARNING]: Consider using the file module with state=absent rather than running 'rm'. If you need to use command because file is insufficient you can add 'warn: false' to this command task or set 'command_warnings=False' in ansible.cfg to get rid of this message. changed: [jin-11] PLAY RECAP *************************************************************************** jin-11 : ok=10 changed=6 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 在jin-11主机上查看是否有nginx的进程: [root@jin-11 ~]# ps aux|grep nginx root 4040 0.0 0.0 20540 628 ? Ss 08:32 0:00 nginx: master process /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf nobody 4042 0.0 0.3 22984 3192 ? S 08:32 0:00 nginx: worker process nobody 4043 0.0 0.3 22984 3192 ? S 08:32 0:00 nginx: worker process root 4192 0.0 0.1 112724 996 pts/0 S+ 09:45 0:00 grep --color=auto nginx 4.5 playbook管理配置文件 安装软件一般是在初始化环境的时候,而在生产环境中,我们大多数是要管理配置文件。 下面我们来写一个管理nginx配置文件的playbook。 首先先创建所需要的目录: [root@jin-10 ~]# mkdir -p /etc/ansible/nginx_config/roles/{new,old}/{files,handlers,vars,tasks} 最终目录如下: [root@jin-10 ~]# tree /etc/ansible/nginx_config/ /etc/ansible/nginx_config/ └── roles ├── new │ ├── files │ ├── handlers │ ├── tasks │ └── vars └── old ├── files ├── handlers ├── tasks └── vars 其中,new目录更新时用到,old回滚时用到,files下面为nginx.conf,handlers为重启nginx服务的命令。 关于回滚,需要在执行playbook之前先备份旧的配置,所以对于旧的配置文件的管理一定要谨慎,千万不要随便去修改线上机器的配置,而且要保证new/files下面的配置和线上的配置保持一致。 拷贝nginx.conf文件到/etc/ansible/nginx_config/roles/new/files/目录下: [root@jin-10 /usr/local/nginx/conf]# cp -r nginx.conf /etc/ansible/nginx_config/roles/new/files/ 编辑一个定义变量的文件vim /etc/ansible/nginx_config/roles/new/vars/main.yml,内容如下: nginx_basedir: /usr/local/nginx 再编写一个重新加载nginx服务的文件vim /etc/ansible/nginx_config/roles/new/handlers/main.yml,内容如下: - name: restart nginx shell: /etc/init.d/nginx reload 再编写一个核心任务的文件vim /etc/ansible/nginx_config/roles/new/tasks/main.yml ,内容如下: - name: copy conf file copy: src={{ item.src }} dest={{ nginx_basedir }}/{{ item.dest }} backup=yes owner=r oot group=root mode=0644 with_items: - { src: nginx.conf, dest: conf/nginx.conf } notify: restart nginx 最后,我们定义一个总入口配置文件vim /etc/ansible/nginx_config/update.yml,内容如下: --- - hosts: testhost user: root roles: - new 执行update.yml [root@jin-10 ~]# ansible-playbook /etc/ansible/nginx_config/update.yml PLAY [testhost] ********************************************************************** TASK [Gathering Facts] *************************************************************** ok: [jin-11] ok: [127.0.0.1] TASK [new : copy conf file] ********************************************************** ok: [jin-11] => (item={u'dest': u'conf/nginx.conf', u'src': u'nginx.conf'}) ok: [127.0.0.1] => (item={u'dest': u'conf/nginx.conf', u'src': u'nginx.conf'}) PLAY RECAP *************************************************************************** 127.0.0.1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 jin-11 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 可以看到,nginx服务已经重新加载了: [root@jin-11 ~]# ps aux|grep nginx root 4040 0.0 0.0 20540 628 ? Ss 08:32 0:00 nginx: master process /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf nobody 4042 0.0 0.3 22984 3192 ? S 08:32 0:00 nginx: worker process nobody 4043 0.0 0.3 22984 3192 ? S 08:32 0:00 nginx: worker process root 4570 0.0 0.1 112724 996 pts/0 S+ 10:43 0:00 grep --color=auto nginx [root@jin-11 ~]# date 2019年 09月 09日 星期一 10:44:12 CST 我们编辑nginx.conf,加上一行注释内容:# test for the rollback,然后再执行update.yml脚本: [root@jin-10 ~]# cat /etc/ansible/nginx_config/roles/new/files/nginx.conf|grep test # test for the rollback [root@jin-10 ~]# ansible-playbook /etc/ansible/nginx_config/update.yml PLAY [testhost] ********************************************************************** TASK [Gathering Facts] *************************************************************** ok: [jin-11] ok: [127.0.0.1] TASK [new : copy conf file] ********************************************************** ok: [jin-11] => (item={u'dest': u'conf/nginx.conf', u'src': u'nginx.conf'}) ok: [127.0.0.1] => (item={u'dest': u'conf/nginx.conf', u'src': u'nginx.conf'}) PLAY RECAP *************************************************************************** 127.0.0.1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 jin-11 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 在jin-11上查看nginx.conf是否备份成功: [root@jin-11 ~]# cat /usr/local/nginx/conf/nginx.conf|grep test # test for the rollback 以上的操作是针对于更新、修改配置文件的,下面来介绍一下回滚操作: 回滚操作就是把旧的配置覆盖,然后重新加载nginx服务, 每次改动nginx配置文件之前先备份到old里,对应目录为/etc/ansible/nginx_config/roles/old/files。 拷贝new目录里的文件到old目录里,相当于把当前nginx配置文件备份到old里,如需回滚就将备份还原。 [root@jin-10 ~]# rsync -av /etc/ansible/nginx_config/roles/new/ /etc/ansible/nginx_config/roles/old/ sending incremental file list files/ files/nginx.conf handlers/ handlers/main.yml tasks/ tasks/main.yml vars/ vars/main.yml sent 2,535 bytes received 108 bytes 5,286.00 bytes/sec total size is 2,080 speedup is 0.79 编写一个总入口配置文件vim /etc/ansible/nginx_config/rollback.yml,内容如下: --- - hosts: jin-11 user: root roles: - old 下面我们来进行一个简单的回滚操作: 1、先将配置文件同步到old目录下: [root@jin-10 ~]# rsync -av /etc/ansible/nginx_config/roles/new/files/nginx.conf /etc/ansible/nginx_config/roles/old/files/nginx.conf sending incremental file list sent 49 bytes received 12 bytes 122.00 bytes/sec total size is 1,772 speedup is 29.05 2、修改配置文件/etc/ansible/nginx_config/roles/new/files/nginx.conf,文件末尾增加一行注释内容: #test the ansible playbook rollback 3、执行update.yml文件向客户端更新文件: [root@jin-10 ~]# ansible-playbook /etc/ansible/nginx_config/update.yml PLAY [testhost] ********************************************************************** TASK [Gathering Facts] *************************************************************** ok: [jin-11] ok: [127.0.0.1] TASK [new : copy conf file] ********************************************************** changed: [jin-11] => (item={u'dest': u'conf/nginx.conf', u'src': u'nginx.conf'}) changed: [127.0.0.1] => (item={u'dest': u'conf/nginx.conf', u'src': u'nginx.conf'}) RUNNING HANDLER [new : restart nginx] ************************************************ changed: [jin-11] changed: [127.0.0.1] PLAY RECAP *************************************************************************** 127.0.0.1 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 jin-11 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 4、在jin-11上查看,文件是否同步: [root@jin-11 ~]# tail -n1 /usr/local/nginx/conf/nginx.conf #test the ansible playbook rollback 5、可以看到,同步成功,然后我们执行rollback.yml文件进行回滚操作: [root@jin-10 ~]# ansible-playbook /etc/ansible/nginx_config/rollback.yml PLAY [jin-11] ************************************************************************ TASK [Gathering Facts] *************************************************************** ok: [jin-11] TASK [old : copy conf file] ********************************************************** changed: [jin-11] => (item={u'dest': u'conf/nginx.conf', u'src': u'nginx.conf'}) RUNNING HANDLER [old : restart nginx] ************************************************ changed: [jin-11] PLAY RECAP *************************************************************************** jin-11 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 6、在jin-11上查看是否已恢复: [root@jin-11 ~]# tail -n1 /usr/local/nginx/conf/nginx.conf } [root@jin-11 ~]# 可以看到,之前文件末尾增加的那一行没有了。 所以,所谓的回滚就是在操作改动前,先把文件备份一份,如果出现误操作,就将之前备份的文件还原回去,这样就起到了一个回滚的效果。 手动设置系统时间: sudo date -s "2019-10-12 22:31:00" 将硬件时间改为当地时间,并将系统时间同步到硬件时钟上: sudo hwclock --localtime --systohc Docker Docker是一个为开发者和运维工程师(系统管理员)以容器的方式构建,分享和运行应用的平台.使用容器进行应用部署的方式,我们成为容器化. 容器化应用具有一下特性,使得容器化日益流行: 灵活: 再复杂的应用都可以进行容器化. 轻量: 容器使用使用和共享主机的内核,在系统资源的利用比虚拟机更加高效. 可移植: 容器可以本地构建,部署到云上,运行在任何地方. 松耦合: 容器是高度自封装的,可以在不影响其他容器的情况下替换和升级容器. 可扩展: 可以在整个数据中心里增加和自动分发容器副本. 安全: 容器约束和隔离应用进程,而无需用户进行任何配置. 镜像和容器 其实,容器就是运行的进程,附带一些封装的特性,使其与主机上和其他容器的进程隔离.每个容器都只访问它自己私有的文件系统,这是容器隔离很重要的一方面.而Docker镜像就提供了这个文件系统,一个镜像包含运行该应用所有需求 - 代码或者二进制文件,运行时,依赖库,以及其他需要的文件系统对象 通过与虚拟机对比,虚拟机(VM)通过一个虚拟机管理(Hypervisor)运行了完整的操作系统来访问主机资源.通常虚拟机会产生大量的开销,超过了应用本身所需要的开销. 容器编排 容器化过程的可移植性和可重复性意味着我们有机会跨云和数据中心移动和扩展容器化的应用程序,容器有效地保证应用程序可以在任何地方以相同的方式运行,这使我们可以快速有效地利用所有这些环境.当我们扩展我们的应用,我们需要一些工具来帮助自动维护这些应用,在容器的生命周期里,可以自动替换失败的容器,管理滚动升级,以及重新配置. 容器编排器(Orchestrator)就是管理,扩展和维护容器化应用的工具,当前最常见的例子就是 Kubernetes 和 Docke Swarm . Docker Desktop 工具可以在开发环境提供这两个编排工具.当前, Docker Desktop 仅支持在 Windows 和 OSX 系统上安装,本文接下来主要介绍如何在 Linux 上安装Docker,以及运行一个容器. 安装Docker 如果你使用的是Windows或者Mac OS系统,请参考上面的链接安装和使用 Docker Desktop ,下面我们将已 Ubuntu 18.04 系统为例来安装Docker的社区版本(docker-ce). 配置软件源 更新 apt 包索引 $ sudo apt update 安装需要的软件包 $ sudo apt install \ apt-transport-https \ ca-certificates \ curl \ gnupg-agent \ software-properties-common 添加Docker官方的GPG信息 $ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - 使用如下命令添加Docker的安装源 $ sudo add-apt-repository \ "deb [arch=amd64] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) \ stable" stable 意思为添加的是稳定版本的源. 安装 更新软件源 $ sudo apt update 安装最新版本的Docker引擎和containerd $ sudo apt install docker-ce docker-ce-cli containerd.io 验证安装 $ sudo docker run hello-world 该命令会下载一个测试镜像并运行一个容器,该容器会打印一些信息后退出. 补充 : 为了运行 docker 命令不需要使用 sudo ,可以将当前用户加入 docker $ sudo usermod -aG "docker" 在重新登录用户后,可以直接使用 docker 命令进行操作. 运行容器 在上面安装 docker 引擎的最后一个验证步骤中,其实我们已经运行了一个容器,这里再示例如何运行一个 ubuntu 操作系统的基础容器. 以下命令假设已经将当前用户添加到了 docker 组. 拉取(Pull)镜像 $ docker image pull ubuntu:18.04 该命令将从Docker官方的镜像仓库( Docker Hub )上下载一个官方维护的Ubuntu的基础镜像,标签为 18.04 . 运行容器 $ docker container run --rm -it ubuntu:18.04 root@451a1f6ed7c9:/# 上面的命令,将用 ubuntu:18.04 的镜像运行一个容器, --rm 选项指示在退出或停止容器时自动删除该容器, -it 选项表示创建一个tty并与其交互,因为该容器默认运行的进程是 bash ,这样我们可直接在容器里进行交互. 在容器运行时,可以打开另一个终端,使用如下命令查看当前运行在该主机上的容器 docker container ls CONTAINER ID NAMES IMAGE CREATED ago STATUS PORTS COMMAND 451a1f6ed7c9 musing_diffie ubuntu:18.04 4 minutes ago ago Up 4 minutes "/bin/bash" 当我们在容器的只能高端里执行 exit ,或者在主机的另一个终端里执行 docker container stop musing_diffie ,都将停止并删除该容器. 总结 这里我们介绍了容器的基本概念,以及在 Ubuntu 系统上安装Docker,运行了一个简单的容器.该系列接下来的文章中我会介绍如何容器化我们的应用程序. | QinQ 是 802.1Q in 802.1Q 的简称,是基于 IEEE 802.1Q 技术的一种比较简单的二层 VPN 协议。 QinQ简介 IEEE 802.1Q 定义的 VLAN ID 域有 12 个比特,最多可以提供 4094 个 VLAN。但在实际应用中,尤其是在城域网中,需要大量的 VLAN 来隔离用户,4094 个 VLAN 远远不能满足需求。QinQ 使整个网络最多可以提供 4094×4094 个 VLAN,满足了城域网对 VLAN 数量的需求。 QinQ 是 802.1Q in 802.1Q 的简称,是基于 IEEE 802.1Q 技术的一种比较简单的二层 VPN 协议。 通过将一层 VLAN Tag 封装到私网报文上,使其携带两层 VLAN Tag 穿越运营商的骨干网络(又称公网),从而使运营商能够利用一个 VLAN 为包含多个 VLAN 的用户网络提供服务。 QinQ 具备以下优点 缓解公网 VLAN 资源日益紧缺的问题。 用户可以规划自己的私网 VLAN,不会导致与公网 VLAN 冲突。 为用户提供了一种简单、灵活的二层 VPN 解决方案。 当运营商进行 VLAN 规划时,用户网络不必更改原有配置,使用户网络具有了较强的独立性。 QinQ的工作原理 QinQ报文在运营商网络中传输时带有双层VLAN Tag: 内层 VLAN Tag:为用户的私网 VLAN Tag,Customer VLAN Tag (简称 CVLAN)。设备依靠该 Tag 在私网中传送报文。 外层 VLAN Tag:为运营商分配给用户的公网 VLAN Tag, Service VLAN Tag(简称 SVLAN)。设备依靠该 Tag 在公网中传送 QinQ 报文。 在公网的传输过程中,设备只根据外层 VLAN Tag 转发报文,而内层 VLAN Tag 将被当作报文的数据部分进行传输。 QinQ网络拓扑 用户网络A和B的私网VLAN分别为VLAN 1~10 和VLAN 1~20。运营商为用户网络A和B分配的公网VLAN分别为VLAN 3 和VLAN 4。 当用户网络 A 和 B 中带私网 VLAN Tag 的报文进入运营商网络时,报文外面就会被分别封装上 VLAN 3 和 VLAN 4 的公网 VLAN Tag。 来自不同用户网络的报文在运营商网络中传输时被隔离,即使这些用户网络各自的 VLAN 范围存在重叠,因为分配到的公网 VLAN 不同,在运营商网络中传输时也不会产生冲突。 当报文穿过运营商网络,到达运营商网络另一侧 PE(Provider Edge,服务提供商网络边缘)设备后,报文被剥离公网 VLAN Tag,然后再传送给用户网络的 CE(Customer Edge,用户网络边缘)设备。 QinQ的实现方式 当端口上配置了 QinQ 功能后,不论从该端口收到的报文是否带有 VLAN Tag,设备都会为该报文添加本端口缺省 VLAN 的 Tag: 如果收到的是带有 VLAN Tag 的报文,该报文就成为带两层 Tag 的报文; 如果收到的是不带 VLAN Tag 的报文,该报文就成为带有本端口缺省 VLAN Tag 的报文。 本文地址: https://www.linuxprobe.com/qinq-vlan-tag.html | 这篇文章主要介绍了php pdo连接数据库操作,结合实例形式分析了PHP使用pdo连接数据库并执行事务相关操作技巧,需要的朋友可以参考下 pdo连接数据库的有点是能实现不同数据库之间的转换,而且有事务功能的回滚,更有pdo::prepare();pdo:::execute()函数的预处理查询,所以我个人认为pdo的功能还是比较强大的,所有这篇日志只为我自己而写,希望看到这篇日志的兄弟们能对你们有所帮助。 要用php连接数据库首先要要实例化pdo的类,并且要有数据源,服务器账号,服务器密码 数据源是数据库类型,服务器名称,数据库名称的一个集合。 query("set names gbk");//设置从数据库里面传递过来的数据的编码格式 ?> 事务介绍:事务介绍我就通过我自己的理解来讲解一遍吧,就是先要关闭数据库的自动提交功能(什么是自动提交功能?就是当我们写完一个sql语句后,按回车键执行不起,而要经过特殊的代码处理才能提交上去,后面我会介绍的) 然后写出你要执行的sql语句并将返回的结果赋给两个不同的变量,之后提交,如果在执行的时候其中1个或多个发生了错误,就进行事务回滚,即使回归初始状态(也就是前面在事务处理代码中的插入或改变或删除或查询的语句全部作废),还有一个优点是不会因为进入其他网页,或执行其他sql语句而影响到事务处理的进程 //以下是事务回滚的代码简介 query("set names gbk");//设置从数据库里面传递过来的数据的编码格式 $pdo->begintransaction();//在这里关闭mysql的自动提交功能 $a=$pdo->query("insert into tongxue values('130042106','谭勇','男'); $b=$pdo->query("insert into tongxue values('130042100','猪八戒','男')"); if($a==true && $b==true){ $pdo->commit();//提交事务 } else{ $pdo->rollback();//事务回滚 } ?> 用mysql_num_rows()函数能数出数据库返回结果集的行数,以此来判断该用户输入的用户名和密码是否正确,那么在pdo中我们如何实现这个功能呢? 在pdo中有一个函数pdo::fetchall(),他的作用是将从数据库返回的一个结果集全部赋给获取它的值,之后再用count()函数数出行数具体事例代码如下 query("set names gbk");//设置从数据库里面传递过来的数据的编码格式 $sql="select * from tongxue where id='130042106'"; $shuju=pdo->prepare($sql);//这就是我们所说的预处理 $shuju->execute();//执行预处理的结果; $jg=$shuju->fetchall(PDO::FETCH_ASSOC);//将返回的结果集以数组的方式全部返回给变量$jg $hangshu=count($jg);//数出结果集的行数 if($hangshu>0){ echo '查询出来是有这个人的'; } else{ echo '查询出来是没有这个人的'; } ?> 当我们的页面运行的sql语句较多时,可以用pdo当中的预处理,来减缓服务器的压力,这对于那些要做大型网站的项目来说是一个不错的选择,因为大型网站一天的浏览量是几万或十几万的. 下面我们来看看pdo的预处理 query("set names gbk");//设置从数据库里面传递过来的数据的编码格式 $sql="insert into tongxue values('130042100','老师','男')";//我们要执行的sql语句 $shuju=$pdo->prepare($sql); //预处理 $shuju->execute();//执行预处理的sql语句 if($shuju){ echo '执行成功'; } else{ echo '执行失败'; } ?> 本文地址: https://www.linuxprobe.com/php-pdo-linux.html | nignx的并发连接数,一般优化后,峰值能保持在 1~3w 左右。
Nginx 的进程模型 Nginx 服务器,正常运行过程中 1.多进程:一个 Master 进程、多个 Worker 进程 2.Master 进程:管理 Worker 进程 3.对外接口:接收外部的操作(信号) 4.对内转发:根据外部的操作的不同,通过信号管理 Worker 5.监控:监控 worker 进程的运行状态,worker 进程异常终止后,自动重启 worker 进程 6.Worker 进程:所有 Worker 进程都是平等的 7.实际处理:网络请求,由 Worker 进程处理; 8.Worker 进程数量:在 nginx.conf 中配置,一般设置为核心数,充分利用 CPU 资源,同时,避免进程数量过多,避免进程竞争 CPU 资源,增加上下文切换的损耗。 思考: 1.请求是连接到 Nginx,Master 进程负责处理和转发? 2.如何选定哪个 Worker 进程处理请求?请求的处理结果,是否还要经过 Master 进程? HTTP 连接建立和请求处理过程 1.Nginx 启动时,Master 进程,加载配置文件 2.Master 进程,初始化监听的 socket 3.Master 进程,fork 出多个 Worker 进程 4.Worker 进程,竞争新的连接,获胜方通过三次握手,建立 Socket 连接,并处理请求 Nginx 高性能、高并发 1.Nginx 采用:多进程 + 异步非阻塞方式(IO 多路复用 epoll) 2.请求的完整过程: 3.建立连接 4.读取请求:解析请求 5.处理请求 6.响应请求 7.请求的完整过程,对应到底层,就是:读写 socket 事件 Nginx 的事件处理模型 request:Nginx 中 http 请求。 基本的 HTTP Web Server 工作模式: 1.接收请求:逐行读取请求行和请求头,判断段有请求体后,读取请求体 2.处理请求 3.返回响应:根据处理结果,生成相应的 HTTP 请求(响应行、响应头、响应体) Nginx 也是这个套路,整体流程一致。 模块化体系结构 nginx的模块根据其功能基本上可以分为以下几种类型: 1.event module: 搭建了独立于操作系统的事件处理机制的框架,及提供了各具体事件的处理。包括ngx_events_module, ngx_event_core_module和ngx_epoll_module等。nginx具体使用何种事件处理模块,这依赖于具体的操作系统和编译选项。 2.phase handler: 此类型的模块也被直接称为handler模块。主要负责处理客户端请求并产生待响应内容,比如ngx_http_static_module模块,负责客户端的静态页面请求处理并将对应的磁盘文件准备为响应内容输出。 3.output filter: 也称为filter模块,主要是负责对输出的内容进行处理,可以对输出进行修改。例如,可以实现对输出的所有html页面增加预定义的footbar一类的工作,或者对输出的图片的URL进行替换之类的工作。 4.upstream: upstream模块实现反向代理的功能,将真正的请求转发到后端服务器上,并从后端服务器上读取响应,发回客户端。upstream模块是一种特殊的handler,只不过响应内容不是真正由自己产生的,而是从后端服务器上读取的。 5.load-balancer: 负载均衡模块,实现特定的算法,在众多的后端服务器中,选择一个服务器出来作为某个请求的转发服务器。 常见问题剖析 Nginx vs. Apache 网络 IO 模型: 1.nginx:IO 多路复用,epoll(freebsd 上是 kqueue ) 2.高性能 3.高并发 4.占用系统资源少 5.apache:阻塞 + 多进程/多线程 6.更稳定,bug 少 7.模块更丰富 场景: 处理多个请求时,可以采用:IO 多路复用 或者 阻塞 IO +多线程 IO 多路服用:一个 线程,跟踪多个 socket 状态,哪个就绪,就读写哪个; 阻塞 IO + 多线程:每一个请求,新建一个服务线程 思考:IO 多路复用 和 多线程 的适用场景? IO 多路复用:单个连接的请求处理速度没有优势,适合 IO 密集型 场景,事件驱动 大并发量:只使用一个线程,处理大量的并发请求,降低上下文环境切换损耗,也不需要考虑并发问题,相对可以处理更多的请求; 消耗更少的系统资源(不需要线程调度开销) 适用于长连接的情况(多线程模式长连接容易造成线程过多,造成频繁调度) 阻塞IO + 多线程:实现简单,可以不依赖系统调用,适合 CPU 密集型 场景 每个线程,都需要时间和空间; 线程数量增长时,线程调度开销指数增长 Nginx 最大连接数 基础背景: 1.Nginx 是多进程模型,Worker 进程用于处理请求; 2.单个进程的连接数(文件描述符 fd),有上限(nofile):ulimit -n 3.Nginx 上配置单个 worker 进程的最大连接数:worker_connections 上限为 nofile 4.Nginx 上配置 worker 进程的数量:worker_processes 因此,Nginx 的最大连接数: 1.Nginx 的最大连接数:Worker 进程数量 x 单个 Worker 进程的最大连接数 2.上面是 Nginx 作为通用服务器时,最大的连接数 3.Nginx 作为反向代理服务器时,能够服务的最大连接数:(Worker 进程数量 x 单个 Worker 进程的最大连接数)/ 2。 4.Nginx 反向代理时,会建立 Client 的连接和后端 Web Server 的连接,占用 2 个连接 思考: 每打开一个 socket 占用一个 fd 为什么,一个进程能够打开的 fd 数量有限制? IO 模型 场景: 处理多个请求时,可以采用:IO 多路复用 或者 阻塞 IO +多线程 IO 多路复用:一个 线程,跟踪多个 socket 状态,哪个就绪,就读写哪个; 阻塞 IO + 多线程:每一个请求,新建一个服务线程 思考: IO 多路复用 和 多线程 的适用场景? IO 多路复用:单个连接的请求处理速度没有优势 大并发量:只使用一个线程,处理大量的并发请求,降低上下文环境切换损耗,也不需要考虑并发问题,相对可以处理更多的请求; 消耗更少的系统资源(不需要线程调度开销) 适用于长连接的情况(多线程模式长连接容易造成线程过多,造成频繁调度) 阻塞IO + 多线程:实现简单,可以不依赖系统调用。 每个线程,都需要时间和空间; 线程数量增长时,线程调度开销指数增长 select/poll 和 epoll 比较 详细内容,参考: select poll epoll三者之间的比较 select/poll 系统调用: // select 系统调用 int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout); // poll 系统调用 int poll(struct pollfd fds[], nfds_t nfds, int timeout); select: 查询 fd_set 中,是否有就绪的 fd,可以设定一个超时时间,当有 fd (File descripter) 就绪或超时返回; fd_set 是一个位集合,大小是在编译内核时的常量,默认大小为 1024 特点: 连接数限制,fd_set 可表示的 fd 数量太小了; 线性扫描:判断 fd 是否就绪,需要遍历一边 fd_set; 数据复制:用户空间和内核空间,复制连接就绪状态信息 poll: 解决了连接数限制: poll 中将 select 中的 fd_set 替换成了一个 pollfd 数组 解决 fd 数量过小的问题 数据复制:用户空间和内核空间,复制连接就绪状态信息 epoll:event 事件驱动 epoll:event 事件驱动 事件机制:避免线性扫描 为每个 fd,注册一个监听事件 fd 变更为就绪时,将 fd 添加到就绪链表 fd 数量:无限制(OS 级别的限制,单个进程能打开多少个 fd) select,poll,epoll: 1.I/O多路复用的机制; 2.I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。 3.监视多个文件描述符 4.但select,poll,epoll本质上都是同步I/O: 5.用户进程负责读写(从内核空间拷贝到用户空间),读写过程中,用户进程是阻塞的; 6.异步 IO,无需用户进程负责读写,异步IO,会负责从内核空间拷贝到用户空间; Nginx 的并发处理能力 关于 Nginx 的并发处理能力 并发连接数,一般优化后,峰值能保持在 1~3w 左右。(内存和 CPU 核心数不同,会有进一步优化空间) 本文地址: https://www.linuxprobe.com/nginx.html 最近荣耀魔法本( MagicBook Pro , 锐龙R5 3550H)装了Fedora31系统,虽然没有像Deepin 15.11那样出现亮度不能调节和网卡不能用的问题,但小问题也有不少: 触摸板有时启动后失效,需要重启。sudo modprobe -r i2c_hid && sudo modprobe i2c_hid 触摸板的右键失效。【可设置双指单击显示菜单】 待机后进入桌面有时卡死。【连接鼠标点击或者往上拖动屏幕可以显示登陆界面】 外接显示器刷新频率不能高于30赫兹,否则显示错乱。 外接显示器调整分辨率后,桌面卡住,只能长按电源键关机。 蓝牙3鼠标连接正常,但是蓝牙4的连接不上。 chrome下载完文件后,点击“在文件夹中显示”,文件夹很久才打开。 直接双击下载的rpm软件包,图形安装界面不能自动解决依赖,需要sudo dnf install ./xxx.rpm 有时启动后输入法不启动,需要命令行执行ibus restart 🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿 常用命令 #查看人机交互信息 dmesg dmesg | grep error #重启模块 sudo modprobe -r i2c_hid && sudo modprobe i2c_hid 🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿 官方渠道可以升级内核到5.4.13啦🤪 🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿 安装gnome扩展后,设置主题和优化配置,系统变得很友好。😁
有很多个远程桌面,很多台服务器的大佬看过来,我介绍一款流弊的远程桌面批量管理工具 IIS7远程桌面批量管理工具: http://yczm.iis7.com/?lxmd 其功能有以下: 1、批量管理WIN系列服务器,VPS,电脑; 2、批量导入服务器的IP,端口,账号和密码 3、批量打开N个服务器的远程桌面 ;4、远程桌面后,远程窗口右上角会出现 服务器备注的信息 5、远程桌面后,不影响任务栏显示。可以及时看其他窗口;6、自定义远程桌面窗口分辨率 7、定时监测服务器是否正常 ;8、服务器到期提醒 ;9、可选择是否加载本地硬盘、硬盘映射 10、可选择是否加载服务器的声音,远程声卡读取 ;11、可选择是否禁用本地复制到远程的功能 12、可选择标签式或窗口式批量远程 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 一个TCC事务框架需要解决的当然是分布式事务的管理。关于TCC事务机制的介绍,可以参考TCC事务机制简介。 |
---|
一个TCC事务框架需要解决的当然是分布式事务的管理。关于TCC事务机制的介绍,可以参考TCC事务机制简介。 TCC事务模型虽然说起来简单,然而要基于TCC实现一个通用的分布式事务框架,却比它看上去要复杂的多,不只是简单的调用一下Confirm/Cancel业务就可以了的。 本文将以Spring容器为例,试图分析一下,实现一个通用的TCC分布式事务框架需要注意的一些问题。 一、TCC全局事务必须基于RM本地事务来实现 TCC服务是由Try/Confirm/Cancel业务构成的,其Try/Confirm/Cancel业务在执行时,会访问资源管理器(Resource Manager,下文简称RM)来存取数据。 这些存取操作,必须要参与RM本地事务,以使其更改的数据要么都commit,要么都rollback。 这一点不难理解,考虑一下如下场景: 假设图中的服务B没有基于RM本地事务(以RDBS为例,可通过设置auto-commit为true来模拟),那么一旦[B:Try]操作中途执行失败,TCC事务框架后续决定回滚全局事务时,该[B:Cancel]则需要判断[B:Try]中哪些操作已经写到DB、哪些操作还没有写到DB. 假设[B:Try]业务有5个写库操作,[B:Cancel]业务则需要逐个判断这5个操作是否生效,并将生效的操作执行反向操作。 不幸的是,由于[B:Cancel]业务也有n(0<=n<=5)个反向的写库操作,此时一旦[B:Cancel]也中途出错,则后续的[B:Cancel]执行任务更加繁重。 因为相比第一次[B:Cancel]操作,后续的[B:Cancel]操作还需要判断先前的[B:Cancel]操作的n(0<=n<=5)个写库中哪几个已经执行、哪几个还没有执行. 这就涉及到了幂等性问题,而对幂等性的保障,又很可能还需要涉及额外的写库操作,该写库操作又会因为没有RM本地事务的支持而存在类似问题。。。 可想而知,如果不基于RM本地事务,TCC事务框架是无法有效的管理TCC全局事务的。 反之,基于RM本地事务的TCC事务,这种情况则会很容易处理。 [B:Try]操作中途执行失败,TCC事务框架将其参与RM本地事务直接rollback即可。后续TCC事务框架决定回滚全局事务时,在知道“[B:Try]操作涉及的RM本地事务已经rollback”的情况下,根本无需执行[B:Cancel]操作。 换句话说,基于RM本地事务实现TCC事务框架时,一个TCC型服务的cancel业务要么执行,要么不执行,不需要考虑部分执行的情况。 二、TCC事务框架应该接管Spring容器的TransactionManager 基于RM本地事务的TCC事务框架,可以将各Try/Confirm/Cancel业务看成一个原子服务:一个RM本地事务提交,参与该RM本地事务的所有Try/Confirm/Cancel业务操作都生效;反之,则都不生效。 掌握每个RM本地事务的状态以及它们与Try/Confirm/Cancel业务方法之间的对应关系,以此为基础,TCC事务框架才能有效的构建TCC全局事务。 TCC服务的Try/Confirm/Cancel业务方法在RM上的数据存取操作,其RM本地事务是由Spring容器的PlatformTransactionManager来commit/rollback的,TCC事务框架想要了解RM本地事务的状态,只能通过接管Spring的事务管理器功能。 2.1. 为什么TCC事务框架需要掌握RM本地事务的状态? 首先,根据TCC机制的定义,TCC事务是通过执行Cancel业务来达到回滚效果的。仔细分析一下,这里暗含一个事实:只有生效的Try业务操作才需要执行对应的Cancel业务操作。 换句话说,只有Try业务操作所参与的RM本地事务被commit了,后续TCC全局事务回滚时才需要执行其对应的Cancel业务操作 否则,如果Try业务操作所参与的RM本地事务被rollback了,后续TCC全局事务回滚时就不能执行其Cancel业务,此时若盲目执行Cancel业务反而会导致数据不一致。 其次,Confirm/Cancel业务操作必须保证生效。Confirm/Cancel业务操作也会涉及RM数据存取操作,其参与的RM本地事务也必须被commit。 TCC事务框架需要在确切的知道所有Confirm/Cancel业务操作参与的RM本地事务都被成功commit后,才能将标记该TCC全局事务为完成。 如果TCC事务框架误判了Confirm/Cancel业务参与RM本地事务的状态,就会造成全局事务不一致。 最后,未完成的TCC全局,TCC事务框架必须重新尝试提交/回滚操作。重试时会再次调用各TCC服务的Confirm/Cancel业务操作。 如果某个服务的Confirm/Cancel业务之前已经生效(其参与的RM本地事务已经提交),重试时就不应该再次被调用。否则,其Confirm/Cancel业务被多次调用,就会有“服务幂等性”的问题。 2.2. 拦截TCC服务的Try/Confirm/Cancel业务方法的执行,根据其异常信息可否知道其RM本地事务是否commit/rollback了呢? 基本上很难做到,为什么这么说? 第一,事务是可以在多个(本地/远程)服务之间互相传播其事务上下文的,一个业务方法(Try/Confirm/Cancel)执行完毕并不一定会触发当前事务的commit/rollback操作。 比如,被传播事务上下文的业务方法,在它开始执行时,容器并不会为其创建新的事务,而是它的调用方参与的事务,使得二者操作在同一个事务中;同样,在它执行完毕时,容器也不会提交/回滚它参与的事务的。 因此,这类业务方法上的异常情况并不能反映他们是否生效。不接管Spring的TransactionManager,就无法了解事务于何时被创建,也无法了解它于何时被提交/回滚。 第二、一个业务方法可能会包含多个RM本地事务的情况。 比如:A(REQUIRED)->B(REQUIRES_NEW)->C(REQUIRED),这种情况下,A服务所参与的RM本地事务被提交时,B服务和C服务参与的RM本地事务则可能会被回滚。 第三、并不是抛出了异常的业务方法,其参与的事务就回滚了。 Spring容器的声明式事务定义了两类异常,其事务完成方向都不一样:系统异常(一般为Unchecked异常,默认事务完成方向是rollback)、应用异常(一般为Checked异常,默认事务完成方向是commit)。 二者的事务完成方向又可以通过@Transactional配置显式的指定,如rollbackFor/noRollbackFor等。 第四、Spring容器还支持使用setRollbackOnly的方式显式的控制事务完成方向; 最后,自行拦截业务方法的拦截器和Spring的事务处理的拦截器还会存在执行先后、拦截范围不同等问题。 例如,如果自行拦截器执行在前,就会出现业务方法虽然已经执行完毕但此时其参与的RM本地事务还没有commit/rollback。 TCC事务框架的定位应该是一个TransactionManager,其职责是负责commit/rollback事务。 而一个事务应该commit、还是rollback,则应该是由Spring容器来决定的: Spring决定提交事务时,会调用TransactionManager来完成commit操作;Spring决定回滚事务时,会调用TransactionManager来完成rollback操作。 接管Spring容器的TransactionManager,TCC事务框架可以明确的得到Spring的事务性指令,并管理Spring容器中各服务的RM本地事务。 否则,如果通过自行拦截的机制,则使得业务系统存在TCC事务处理、RM本地事务处理两套事务处理逻辑,二者互不通信,各行其是。 这种情况下要协调TCC全局事务,基本上可以说是缘木求鱼,本地事务尚且无法管理,更何谈管理分布式事务? 三、TCC事务框架应该具备故障恢复机制 一个TCC事务框架,若是没有故障恢复的保障,是不成其为分布式事务框架的。 分布式事务管理框架的职责,不是做出全局事务提交/回滚的指令,而是管理全局事务提交/回滚的过程。 它需要能够协调多个RM资源、多个节点的分支事务,保证它们按全局事务的完成方向各自完成自己的分支事务。 这一点,是不容易做到的。因为,实际应用中,会有各种故障出现,很多都会造成事务的中断,从而使得统一提交/回滚全局事务的目标不能达到,甚至出现”一部分分支事务已经提交,而另一部分分支事务则已回滚”的情况。 比较常见的故障,比如:业务系统服务器宕机、重启;数据库服务器宕机、重启;网络故障;断电等。这些故障可能单独发生,也可能会同时发生。 作为分布式事务框架,应该具备相应的故障恢复机制,无视这些故障的影响是不负责任的做法。 一个完整的分布式事务框架,应该保障即使在最严苛的条件下也能保证全局事务的一致性,而不是只能在最理想的环境下才能提供这种保障。退一步说,如果能有所谓“理想的环境”,那也无需使用分布式事务了。 TCC事务框架要支持故障恢复,就必须记录相应的事务日志。事务日志是故障恢复的基础和前提,它记录了事务的各项数据。 TCC事务框架做故障恢复时,可以根据事务日志的数据将中断的事务恢复至正确的状态,并在此基础上继续执行先前未完成的提交/回滚操作。关注微信公众号:Java技术栈,在后台回复:架构,可以获取我整理的 N 篇架构教程,都是干货。 四、TCC事务框架应该提供Confirm/Cancel服务的幂等性保障 一般认为,服务的幂等性,是指针对同一个服务的多次(n>1)请求和对它的单次(n=1)请求,二者具有相同的副作用。 在TCC事务模型中,Confirm/Cancel业务可能会被重复调用,其原因很多。 比如,全局事务在提交/回滚时会调用各TCC服务的Confirm/Cancel业务逻辑。执行这些Confirm/Cancel业务时,可能会出现如网络中断的故障而使得全局事务不能完成。 因此,故障恢复机制后续仍然会重新提交/回滚这些未完成的全局事务,这样就会再次调用参与该全局事务的各TCC服务的Confirm/Cancel业务逻辑。 既然Confirm/Cancel业务可能会被多次调用,就需要保障其幂等性。 那么,应该由TCC事务框架来提供幂等性保障?还是应该由业务系统自行来保障幂等性呢? 个人认为,应该是由TCC事务框架来提供幂等性保障。如果仅仅只是极个别服务存在这个问题的话,那么由业务系统来负责也是可以的; 然而,这是一类公共问题,毫无疑问,所有TCC服务的Confirm/Cancel业务存在幂等性问题。TCC服务的公共问题应该由TCC事务框架来解决; 而且,考虑一下由业务系统来负责幂等性需要考虑的问题,就会发现,这无疑增大了业务系统的复杂度。 五、TCC事务框架不能盲目的依赖Cancel业务来回滚事务 前文以及提到过,TCC事务通过Cancel业务来对Try业务进行回撤的机制暗含了一个事实:Try操作已经生效。 也就是说,只有Try操作所参与的RM本地事务已经提交的情况下,才需要执行其Cancel操作进行回撤。没有执行、或者执行了但是其RM本地事务被rollback的Try业务,是一定不能执行其Cancel业务进行回撤的。 因此,TCC事务框架在全局事务回滚时,应该根据TCC服务的Try业务的执行情况选择合适的处理机制。而不能盲目的执行Cancel业务,否则就会导致数据不一致。 一个TCC服务的Try操作是否生效,这是TCC事务框架应该知道的,因为其Try业务所参与的RM事务也是由TCC事务框架所commit/rollbac的(前提是TCC事务框架接管了Spring的事务管理器)。 所以,TCC事务回滚时,TCC事务框架可考虑如下处理策略: 如果TCC事务框架发现某个服务的Try操作的本地事务尚未提交,应该直接将其回滚,而后就不必再执行该服务的cancel业务; 如果TCC事务框架发现某个服务的Try操作的本地事务已经回滚,则不必再执行该服务的cancel业务; 如果TCC事务框架发现某个服务的Try操作尚未被执行过,那么,也不必再执行该服务的cancel业务。 总之,TCC事务框架应该保障: 已生效的Try操作应该被其Cancel操作所回撤; 尚未生效的Try操作,则不应该执行其Cancel操作。这一点,不是幂等性所能解决的问题。如上文所述,幂等性是指服务被执行一次和被执行n(n>0)次所产生的影响相同。但是,未被执行和被执行过,二者效果肯定是不一样的,这不属于幂等性的范畴。 六、Cancel业务与Try业务并行,甚至先于Try操作完成 这应该算TCC事务机制特有的一个不可思议的陷阱。 一般来说,一个特定的TCC服务,其Try操作的执行,是应该在其Confirm/Cancel操作之前的。 Try操作执行完毕之后,Spring容器再根据Try操作的执行情况,指示TCC事务框架提交/回滚全局事务。然后,TCC事务框架再去逐个调用各TCC服务的Confirm/Cancel操作。 然而,超时、网络故障、服务器的重启等故障的存在,使得这个顺序会被打乱。比如: 上图中,假设[B:Try]操作执行过程中,网络闪断,[A:Try]会收到一个RPC远程调用异常。 A不处理该异常,导致全局事务决定回滚,TCC事务框架就会去调用[B:Cancel],而此刻A、B之间网络刚好已经恢复。如果[B:Try]操作耗时较长(网络阻塞/数据库操作阻塞),就会出现[B:Try]和[B:Cancel]二者并行处理的现象,甚至[B:Cancel]先完成的现象。 这种情况下,由于[B:Cancel]执行时,[B:Try]尚未生效(其RM本地事务尚未提交),因此,[B:Cancel]是不能执行的,至少是不能生效(执行了其RM本地事务也要rollback)的。 然而,当[B:Cancel]处理完毕(跳过执行、或者执行后rollback其RM本地事务)后,[B:Try]操作完成又生效了(其RM本地事务成功提交),这就会使得[B:Cancel]虽然提供了,但却没有起到回撤[B:Try]的作用,导致数据的不一致。 所以,TCC框架在这种情况下,需要: 将[B:Try]的本地事务标注为rollbackOnly,阻止其后续生效; 禁止其再次将事务上下文传递给其他远程分支,否则该问题将在其他分支上出现; 相应地,[B:Cancel]也不必执行,至少不能生效。 当然,TCC事务框架也可以简单的选择阻塞[B:Cancel]的处理,待[B:Try]执行完毕后,再根据它的执行情况判断是否需要执行[B:Cancel]。 不过,这种处理方式因为需要等待,所以,处理效率上会有所不及。 同样的情况也会出现在confirm业务上,只不过,发生在Confirm业务上的处理逻辑与发生在Cancel业务上的处理逻辑会不一样。 TCC框架必须保证: Confirm业务在Try业务之后执行,若发现并行,则只能阻塞相应的Confirm业务操作; 在进入Confirm执行阶段之后,也不可以再提交同一全局事务内的新的Try操作的RM本地事务。 七、TCC服务复用性是不是相对较差? TCC事务机制的定义,决定了一个服务需要提供三个业务实现:Try业务、Confirm业务、Cancel业务。 可能会有人因此认为TCC服务的复用性较差。怎么说呢,要是将 Try/Confirm/Cancel业务逻辑单独拿出来复用,其复用性当然是不好的。 Try/Confirm/Cancel 逻辑作为TCC型服务中的一部分,是不能单独作为一个组件来复用的。Try、Confirm、Cancel业务共同才构成一个组件,如果要复用,应该是复用整个TCC服务组件,而不是单独的Try/Confirm/Cancel业务。 八、TCC服务是否需要对外暴露三个服务接口? 不需要。TCC服务与普通的服务一样,只需要暴露一个接口,也就是它的Try业务。 Confirm/Cancel业务逻辑,只是因为全局事务提交/回滚的需要才提供的,因此Confirm/Cancel业务只需要被TCC事务框架发现即可,不需要被调用它的其他业务服务所感知。 换句话说,业务系统的其他服务在需要调用TCC服务时,根本不需要知道它是否为TCC型服务。 因为,TCC服务能被其他业务服务调用的也仅仅是其Try业务,Confirm/Cancel业务是不能被其他业务服务直接调用的。 九、TCC服务A的Confirm/Cancel业务中能否调用它依赖的TCC服务B的Confirm/Cancel业务? 最好不要这样做。 首先,没有必要。TCC服务A依赖TCC服务B,那么[A:Try]已经将事务上下文传播给[B:Try]了,后续由TCC事务框架来调用各自的Confirm/Cancel业务即可; 其次,Confirm/Cancel业务如果被允许调用其他服务,那么它就有可能再次发起新的TCC全局事务。如此递归下去,将会导致全局事务关系混乱且不可控。 TCC全局事务,应该尽量在Try操作阶段传播事务上下文。Confirm/Cancel操作阶段仅需要完成各自Try业务操作的确认操作/补偿操作即可,不适合再做远程调用,更不能再对外传播事务上下文。 综上所述,本文倾向于认为,实现一个通用的TCC分布式事务管理框架,还是相对比较复杂的。一般业务系统如果需要使用TCC事务机制,并不推荐自行设计实现。 这里,给大家推荐一款开源的TCC分布式事务管理器ByteTCChttps://github.com/liuyangming/ByteTCC ByteTCC基于Try/Confirm/Cancel机制实现,可与Spring容器无缝集成,兼容Spring的声明式事务管理。提供对dubbo框架、Spring Cloud的开箱即用的支持,可满足多数据源、跨应用、跨服务器等各种分布式事务场景的需求。 本文地址: https://www.linuxprobe.com/distributed-transaction-framework.html 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 26.6 jenkins邮件设置 26.7 插件email-ext 26.8 pojie jenkins管理员密码 26.6 jenkins邮件设置 上一章节,我们配置完php发布。然后还要配置发邮件,失败了或成功了都是要通知到人的 1.系统管理 -> 系统设置 -> Jenkins Location 先设置系统管理员邮件地址,这里的邮件地址需要和后续要设置的发邮件地址一致。 2.往下拉,找到“邮件通知”那一栏 3.填写SMTP服务器,点右侧的高级,打勾“使用SMTP认证”,填写用户名密码,如果使用的是ssl协议,还需要在这里打勾,smtp端口默认为25 4.可以打勾“通过发送测试邮件测试配置”,然后填写接收邮件的地址,点右侧的Test configuration 5.然后就是到已经构建过的job里去配置接收邮件的地址了。在最下面“构建后操作”那一栏,找到E-mail Notification,Recipients填写收邮件人 6.这个收件人只会在job构建失败时才能收到邮件 实例: 然后我们测试一下,能不能发送邮件。我们认为的给他搞错一下,从而让他发邮件 [root@axinlinux-01 ~]# rm -rf /tmp/jenkins_test/* #我们先把之前的构建的内容全部删除 [root@axinlinux-01 ~]# chattr +i /tmp/jenkins_test/ #然后把这个额目录加个i权限(增加该属性 表示文件不能删除 重命名 设定链接 写入以及新增数据) 我们看控制台输出,也能看到已经发送了。再去163邮箱去查看一下 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 26.7 插件email-ext 上一章,我们配置了jenkins的内置邮箱。但是他只有在构建失败(发送)的时候,才会发邮件。但是我们想让他在构建成功的时候也发送邮件。就用到了email-ext这个插件 1.插件名字Email Extension Plugin,默认已经安装 2.系统管理->系统设置->Extended E-mail Notification 填写SMTP server,点击Use SMTP Authentication,填写用户名、密码、SMTP port等 3.还需到对应构建任务中去配置一下,下拉到“构建后操作” 4.点击“增加构建后操作步骤”,选择“Editable Email Notification”,其中Project Recipient List为接收邮件的收件人,可以在默认内容后面增加额外的收件人邮箱,用逗号分隔 5.点击右下角的“Advanced settings”,定位到“Triggers”,然后点击下方的“Add Trigger”,可以增加发邮件的条件。 6.参考文章http://www.cnblogs.com/zz0412/p/jenkins_jj_01.html #如有其它需求,可以参考这篇文章。基本都包括了 实例: 以上,插件的邮箱配置完成。可以重新构建一下,测试是否发邮件 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 26.8 pojie jenkins管理员密码 忘记jenkins管理员密码 cd /var/lib/jenkins/users/admin vim config.xml//定位到 那一行 删除改行,改为 #jbcrypt:$2a$10$pre7I4liZFdF6ZE05QntTOoKoKa5pCUumaFO/mMzMoH09bNBzyj6O 重启一下jenkins 新密码为aminglinux.com 实例: [root@axinlinux-01 ~]# cd /var/lib/jenkins/users/admin_625763853940219016/ [root@axinlinux-01 admin_625763853940219016]# ls config.xml [root@axinlinux-01 admin_625763853940219016]# vim config.xml #因为jenkins没有用到数据库,他所有的配置都存放于这样的.xml配置文件里。包括用户密码 #jbcrypt:$2a$10$pre7I4liZFdF6ZE05QntTOoKoKa5pCUumaFO/mMzMoH09bNBzyj6O #这一串字符串是加密的,所代表的密码就是aminglinux.com 然后重启一下服务就可以浏览器上登录了 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 它们是包含文件和元数据的档案文件。当安装或卸载 RPM 时,此元数据告诉 RPM 在哪里创建或删除文件。正如你将在上一篇文章中记住的,元数据还包含有关“依赖项”的信息,它可以是“运行时”或“构建时”的依赖信息。 例如,让我们来看看 fpaste。你可以使用 dnf 下载该 RPM。这将下载 Fedora 存储库中可用的 fpaste 最新版本。在 Fedora 30 上,当前版本为 0.3.9.2: $ dnf download fpaste ... fpaste-0.3.9.2-2.fc30.noarch.rpm 由于这是个构建 RPM,因此它仅包含使用 fpaste 所需的文件: $ rpm -qpl ./fpaste-0.3.9.2-2.fc30.noarch.rpm /usr/bin/fpaste /usr/share/doc/fpaste /usr/share/doc/fpaste/README.rst /usr/share/doc/fpaste/TODO /usr/share/licenses/fpaste /usr/share/licenses/fpaste/COPYING /usr/share/man/man1/fpaste.1.gz 源 RPM 在此链条中的下一个环节是源 RPM。Fedora 中的所有软件都必须从其源代码构建。我们不包括预构建的二进制文件。因此,要制作一个 RPM 文件,RPM(工具)需要: 给出必须要安装的文件, 例如,如果要编译出这些文件,则告诉它们如何生成这些文件, 告知必须在何处安装这些文件, 该特定软件需要其他哪些依赖才能正常工作。 源 RPM 拥有所有这些信息。源 RPM 与构建 RPM 相似,但顾名思义,它们不包含已构建的二进制文件,而是包含某个软件的源文件。让我们下载 fpaste 的源 RPM: $ dnf download fpaste --source ... fpaste-0.3.9.2-2.fc30.src.rpm 注意文件的结尾是 src.rpm。所有的 RPM 都是从源 RPM 构建的。你也可以使用 dnf 轻松检查“二进制” RPM 的源 RPM: $ dnf repoquery --qf "%{SOURCERPM}" fpaste fpaste-0.3.9.2-2.fc30.src.rpm 另外,由于这是源 RPM,因此它不包含构建的文件。相反,它包含有关如何从中构建 RPM 的源代码和指令: $ rpm -qpl ./fpaste-0.3.9.2-2.fc30.src.rpm fpaste-0.3.9.2.tar.gz fpaste.spec 这里,第一个文件只是 fpaste 的源代码。第二个是 spec 文件。spec 文件是个配方,可告诉 RPM(工具)如何使用源 RPM 中包含的源代码创建 RPM(档案文件)— 它包含 RPM(工具)构建 RPM(档案文件)所需的所有信息。在 spec 文件中。当我们软件包维护人员添加软件到 Fedora 中时,我们大部分时间都花在编写和完善 spec 文件上。当软件包需要更新时,我们会回过头来调整 spec 文件。你可以在 https://src.fedoraproject.org/browse/projects/ 的源代码存储库中查看 Fedora 中所有软件包的 spec 文件。 请注意,一个源 RPM 可能包含构建多个 RPM 的说明。fpaste 是一款非常简单的软件,一个源 RPM 生成一个“二进制” RPM。而 Python 则更复杂。虽然只有一个源 RPM,但它会生成多个二进制 RPM: $ sudo dnf repoquery --qf "%{SOURCERPM}" python3 python3-3.7.3-1.fc30.src.rpm python3-3.7.4-1.fc30.src.rpm $ sudo dnf repoquery --qf "%{SOURCERPM}" python3-devel python3-3.7.3-1.fc30.src.rpm python3-3.7.4-1.fc30.src.rpm $ sudo dnf repoquery --qf "%{SOURCERPM}" python3-libs python3-3.7.3-1.fc30.src.rpm python3-3.7.4-1.fc30.src.rpm $ sudo dnf repoquery --qf "%{SOURCERPM}" python3-idle python3-3.7.3-1.fc30.src.rpm python3-3.7.4-1.fc30.src.rpm $ sudo dnf repoquery --qf "%{SOURCERPM}" python3-tkinter python3-3.7.3-1.fc30.src.rpm python3-3.7.4-1.fc30.src.rpm 用 RPM 行话来讲,“python3” 是“主包”,因此该 spec 文件将称为 python3.spec。所有其他软件包均为“子软件包”。你可以下载 python3 的源 RPM,并查看其中的内容。(提示:补丁也是源代码的一部分): $ dnf download --source python3 python3-3.7.4-1.fc30.src.rpm $ rpm -qpl ./python3-3.7.4-1.fc30.src.rpm 00001-rpath.patch 00102-lib64.patch 00111-no-static-lib.patch 00155-avoid-ctypes-thunks.patch 00170-gc-assertions.patch 00178-dont-duplicate-flags-in-sysconfig.patch 00189-use-rpm-wheels.patch 00205-make-libpl-respect-lib64.patch 00251-change-user-install-location.patch 00274-fix-arch-names.patch 00316-mark-bdist_wininst-unsupported.patch Python-3.7.4.tar.xz check-pyc-timestamps.py idle3.appdata.xml idle3.desktop python3.spec 从源 RPM 构建 RPM 现在我们有了源 RPM,并且其中有什么内容,我们可以从中重建 RPM。但是,在执行此操作之前,我们应该设置系统以构建 RPM。首先,我们安装必需的工具: $ sudo dnf install fedora-packager 这将安装 rpmbuild 工具。rpmbuild 需要一个默认布局,以便它知道源 RPM 中每个必需组件的位置。让我们看看它们是什么: $ rpm -E %{_specdir} /home/asinha/rpmbuild/SPECS $ rpm -E %{_sourcedir} /home/asinha/rpmbuild/SOURCES $ rpm -E %{_builddir} /home/asinha/rpmbuild/BUILD $ rpm -E %{_buildrootdir} /home/asinha/rpmbuild/BUILDROOT $ rpm -E %{_srcrpmdir} /home/asinha/rpmbuild/SRPMS $ rpm -E %{_rpmdir} /home/asinha/rpmbuild/RPMS 我已经在系统上设置了所有这些目录: $ cd $ tree -L 1 rpmbuild/ rpmbuild/ ├── BUILD ├── BUILDROOT ├── RPMS ├── SOURCES ├── SPECS └── SRPMS 6 directories, 0 files RPM 还提供了一个为你全部设置好的工具: $ rpmdev-setuptree 然后,确保已安装 fpaste 的所有构建依赖项: sudo dnf builddep fpaste-0.3.9.2-3.fc30.src.rpm 对于 fpaste,你只需要 Python,并且它肯定已经安装在你的系统上(dnf 也使用 Python)。还可以给 builddep 命令 一个 spec 文件,而不是源 RPM。在手册页中了解更多信息: $ man dnf.plugin.builddep 现在我们有了所需的一切,从源 RPM 构建一个 RPM 就像这样简单: $ rpmbuild --rebuild fpaste-0.3.9.2-3.fc30.src.rpm .. .. $ tree ~/rpmbuild/RPMS/noarch/ /home/asinha/rpmbuild/RPMS/noarch/ └── fpaste-0.3.9.2-3.fc30.noarch.rpm 0 directories, 1 file rpmbuild 将安装源 RPM 并从中构建你的 RPM。现在,你可以使用 dnf 安装 RPM 以使用它。当然,如前所述,如果你想在 RPM 中进行任何更改,则必须修改 spec 文件,我们将在下一篇文章中介绍 spec 文件。 总结 总结一下这篇文章有两点: 我们通常安装使用的 RPM 是包含软件的构建版本的 “二进制” RPM 构建 RPM 来自于源 RPM,源 RPM 包括用于生成二进制 RPM 所需的源代码和规范文件。 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 重定向 系统设定的输入输出 默认输出设备:标准输出,STDOUT,1 默认输入设备:标准输入,STDIN,0 标准错误输出:STDERR,2 标准输入:键盘 标准输出和错误输出:显示器 linux中的I/O重定向 标准输入 (stdin): 代码为0,使用<或<<; 标准输出 (stdout): 代码为1,使用>或>>; 标准错误输出(stderr): 代码为2,使用2>或2>>; ">" ">>" >: 覆盖输出 >> :追加输出 # set -C 禁止对已经存在文件使用覆盖重定向;强制覆盖输出,则使用 >| # set +C 关闭上述功能 "2>" "2>>" 2>: 重定向错误输出 2>>: 追加方式 将正确的与错误的分别存入不同的文件中 # ls / /varr > /tmp/var3. out 2> /tmp/ err . out # ls /varr > /tmp/var4. out 2> /tmp/var4. out /dev/null垃圾桶黑洞装置 &>: 重定向标准输出或错误输出至同一个文件(或者2>&1) "<" 输出重定向(将原来需要由键盘输入的数据,改由文件内容来取代) # tr 'a-z' 'A-Z' < /etc/fstab "<<" Here Document(代表结束的输入字符) # cat << END # cat >> /tmp/myfile.txt << EOF /dev/ null , 软件设备,bit bucket,数据黑洞,将内容输出定向到该设备下无任何返回内容 管道 管道是将前一个 命令 的输出作为后一个 命令 的输入 命令1 | 命令2 | 命令3 | ...... # echo "hello world." | tr 'a-z' 'A-Z' # echo "redhat" | passwd --stdin hive # cut -d: -f1 /etc/passwd | sort | tr 'a-z' 'A-Z' # echo "Hello,World." | tee /tmp/hello.out # tee: 显示内容并将内容保存在文件中 # wc -l /etc/passwd | cut -d ' ' -f1 练习: 1 、统计/usr/bin/目录下的文件个数; 2 、取出当前系统上所有用户的 shell ,要求,每种shell只显示一次,并且按顺序进行显示; 3 、思考:如何显示/var/log目录下每个文件的内容类型? 4 、取出/etc/inittab文件的第 6 行; 5 、取出/etc/passwd文件中倒数第 9 个用户的用户名和shell,显示到屏幕上并将其保存至/tmp/users文件中; 6 、显示/etc目录下所有以pa开头的文件,并统计其个数; 7 、不使用文本编辑器,将alias cls=clear一行内容添加至当前用户的.bashrc文件中; 本文地址: https://www.linuxprobe.com/refre-gd-he.html |