// 上一篇:管道(pipeline)

// 下一篇:Continuation-passing_style(CPS)


发现问题

在一个正式项目的开发周期中,除了源代码版本控制外,还存在着项目的配置/编译/打包/发布等各种高频但非“核心”的脚本代码。职业程序员常常在写项目正式代码的时候,有着良好的习惯,包括编码规范/模块化/...等等。然而,当场景切换到配置、编译、打包、发布等脚本代码时,往往会写出蹩脚的代码。例如:全局变量满天飞、路径随便拼接、没有函数封装的裸奔代码、无任何注释和文档...

在这个过程中,“破窗效应”常常悄无声息在起作用。一个典型的现象是随着脚本的拷贝粘贴,项目根目录下会出现各种单一功能的脚本,例如:

  • pub.sh
  • pubGroup1.sh
  • pubCI.sh
  • pub.cmd
  • pubCI.cmd
  • clean.sh
  • clean.cmd
  • pubAndClean.cmd
  • start.sh
  • start.cmd
  • stop.sh
  • stop.cmd
  • startAll.sh
  • startCI.sh
  • ....

小心!当这些“一键搞定”的“便利脚本”出现在项目的根目录下时,就应该引起你的警觉!软件项目在这些复制粘贴的脚本出现的时候,代码的规模也在大量膨胀;工程师发现和解决的BUG也在一波一波的来袭;工程师之间的骂声也在升级:“这个脚本怎么用?”“为什么你那边可以,我这边不可以”“不行,还是挂了”...

需求分析

那么,这个问题应该怎么解决呢?通过设计解决问题。不过,在设计之前要仔细分析下这些脚本的特征。我们看下pub.sh会做的事情:

node deploy/deploy.js stop -config config.json -group group1
node deploy/deploy.js stop -config config.json -group group2
node deploy/deploy.js stop -config config.json -group group3
node deploy/deploy.js pub -config config.json -group group1
node deploy/deploy.js pub -config config.json -group group2
node deploy/deploy.js pub -config config.json -group group3
node deploy/deploy.js start -config config.json -group group1
node deploy/deploy.js start -config config.json -group group2
node deploy/deploy.js start -config config.json -group group3

结论1:

发布脚本可能会针对多个组分别做stop,pub,start动作,并且这个pub的名字被不恰当的用来做为整个脚本的名字。

接下来来看pubGroup1.sh做的事情:

node deploy/deploy.js stop -config config.json -group group1
node deploy/deploy.js pub -config config.json -group group1
node deploy/deploy.js start -config config.json -group group1

结论2:

发布脚本需要支持针对不同的分组操作。

然后,我们看下pubCI.sh做的事情:

node deploy/makeconfig.js machinelist_ci.json
node deploy/deploy.js stop -config config.json -group group1
node deploy/deploy.js stop -config config.json -group group2
...

结论3:

发布脚本会针对不同的集群从机器列表生成对应集群所需的配置

再分析下脚本的不同类型,有的是做pub的,有的是做stop的,有的是做start的。从而有

结论4:

发布脚本针对一个环境的指定分组有不同的操作需求。

程序设计

经过上述分析,我们基本搞清楚了这些看上去每个都是“一键”搞定的一个事情的脚本背后,有着一组复合的需求。在理清了这些需求之后,我们首先改变这些想法:“这是一次性脚本”;“这个脚本一键操作很方便”;“我复制一份改一下”。

在此之前,经过一番思考,并参考设计精良的命令行范本:git,我们重新确定目标:

  1. 合并为单一脚本。
  2. 通过简洁的命令行参数满足不同的需求,命令行参数以操作做分组依据。
  3. 设计出简洁的配置指定不同的集群、不同的分组。
  4. 自由地忽略不需要的操作。
  5. 安全的预览即将执行的命令,避免误操作。

我们把这个单一脚本命名为dev.js。文件的后缀名说明了我们将使用nodejs作为脚本的组织语言。

子系统配置设计

上述5个目标中,第3个是首先需要搞定的,如果配置的结构清晰,程序只是对配置所决定的结构的执行。配置的结构如下:

{
system = {
basic: {
push:[
{group:"zookeeper":, action:"stop"},
{group:"sleep":, action:"10"}, {group:"zookeeper":, action:"start"},
{group:"sleep":, action:"10"}, {group:"zookeeper":, action:"create"},
{group:"sleep":, action:"10"}
],
stop:[
{group:"zookeeper":, action:"stop"},
{group:"sleep":, action:"10"}
],
start:[
{group:"zookeeper":, action:"start"},
{group:"sleep":, action:"10"}
],
check:[
{group:"zookeeper":, action:"check"}
]
},
docker_image:{
push:[
{ group: "docker_image", action: "stop" },
{ group: "docker_image", action: "start" },
],
start:[
{ group: "docker_image", action: "start" },
],
stop:[
{ group: "docker_image", action: "stop" },
],
check:[
{ group: "docker_image", action: "check" },
]
},
servers:[
push:[
{ group: "server_1", action: "stop" },
{ group: "server_2", action: "stop" },
{ group: "server_3", action: "stop" }, { group: "server_1", action: "pub" },
{ group: "server_2", action: "pub" },
{ group: "server_3", action: "pub" }, { group: "server_1", action: "start" },
{ group: "server_2", action: "start" },
{ group: "server_3", action: "start" },
],
start:[
{ group: "server_1", action: "start" },
{ group: "server_2", action: "start" },
{ group: "server_3", action: "start" }
],
stop:[
{ group: "server_1", action: "stop" },
{ group: "server_2", action: "stop" },
{ group: "server_3", action: "stop" },
],
check:[
{ group: "server_1", action: "check" },
{ group: "server_2", action: "check" },
{ group: "server_3", action: "check" }
]
]
}
}

在这个配置结构里,有两层设计:

  1. 可以配置不同的子系统, 并且根据常见需求预定义内置的子系统,当某些情况下不满强需求时,可以增加子系统。
  2. 每个子系统下定义四种操作:push,start,stop,check

为什么要定义出一个叫做push的操作呢?这是因为经过分析,下层的脚本提供了四个基本的原子操作:

  • pub:将一个group拷贝到目标机器上。
  • stop: 停止目标机器上的一个group
  • start:开始目标机器上的一个group
  • check:执行测试脚本检测一个group在目标机器上的基本运行情况。

那么,我们实际在部署的过程中,stop,start,check 三个操作都相对来说比较单一,不会有太多副作用。但是,pub动作一般来说就是部署新版本的服务上去,此时:

  1. 它需要和其他的操作组合使用。
  2. 多个group之间会有相互依赖关系。

因此,我们将这样的一组复杂操作组合起来,统一用一个新的概念:push来命名。对于子系统这样的层级,只提供push,而不提供单独的pub操作。

设计了子系统配置之后,我们就可以满足不同的分组配置需求,这里的一个关键地方在于每个子系统的每个操作那边具体执行那些原子指令是可以通过配置进行编排

命令行参数设计

定义了上述子系统配置之后,接下来考虑命令行参数的设计,根据设计目标,学习git的命令行参数,我们以操作为中心组织options。

node dev.js push -system basic
node dev.js start -system basic
node dev.js stop -system basic
node dev.js check -system basic

上述命令已经可以这对子系统执行四个不同的操作。但是有时候我们希望只针对某个单一group做操作,因此,命令行将支持直接针对group的操作:

node dev.js push -group server1
node dev.js pub -group server1
node dev.js start -group server1
node dev.js stop -group server1
node dev.js check -group server1

在针对group的操作中,将group的pub操作也暴露出来,提供给精细控制的情景使用。

进而,考虑第4个需求:在针对子系统的操作里应该能临时忽略某些子操作。因此,我们增加-i选项:

node dev.js push -system basic -i start,stop

通过-i op1,op2,op3 这样的方式,我们可以忽略某些子操作。有时候,我们也希望忽略子系统的某些分组。那么可以增加一个新的忽略分组的命令行参数,但是,鉴于分组(group)的名字肯定不会和操作(op)的名字重合,我们可以直接让-i选项里能忽略分组:

node dev.js push -system server -i stop,server2

到此为止,还缺了一个部件,那就是针对不同的集群切换配置的选项。这可以轻松增加:

node dev.js push -system server -env ci

增加了的env参数变成一个必填参数,这样增加了可靠性:操作者必须知道自己针对哪个集群上部署,针对一些关键的集群,增加密码输入的要求。

预览的重要性

在上面的设计里,为了满足针对不同集群,忽略某些操作,子系统执行序列的配置。当用户敲入命令行,输入回车的瞬间,到底哪些命令会被执行?

为了解决这个问题,dev.js的执行部分设计两种不同的模式:

function exe(script,mode){
if(mode==='e'){
// execute the script...
else{
console.log(script);
}
}

而且,默认模式设置为预览模式,所以默认情况下,当用户敲入回车时,只会看到即将执行的命令预览:

scripts:

node deploy/deploy.js stop -config config.json -group group1
node deploy/deploy.js stop -config config.json -group group2
node deploy/deploy.js stop -config config.json -group group3
node deploy/deploy.js pub -config config.json -group group1
node deploy/deploy.js pub -config config.json -group group2
node deploy/deploy.js pub -config config.json -group group3
node deploy/deploy.js start -config config.json -group group1
node deploy/deploy.js start -config config.json -group group2
node deploy/deploy.js start -config config.json -group group3 please use `-e` to execute it.

只有当用户输入-e选项时,才会真正执行,这样所有对自己的操作有疑惑的用户都可以放心的预览目标指令序列。

尾声

关于指令序列,有点像汇编的过程:

  1. 提供原子的指令。
  2. 提供组合原子指令的机制(例如分组)。

在程序设计的其他场景,例如测试用例的组织,异步链式逻辑的组织里,指令序列都有使用的机会。有N个原子的概念(核心概念),以及上层的一组对这些原子概念的组合(策略)。然后,人们常常只能看到上层的组合策略,因此会在组合策略的角度讨论A好还是B好。可是,因为这些组合策略不是原子的概念,所以无论说A好,还是说B好,都不会有结果,因为它们不是本质的。A和B策略,最终翻译后会变成底层的一组a,b,c操作序列,如果你能有一个探针,在底层监控a、b、c序列的执行,你把它们打印出来,你可以分析A、B上面出问题的时候,在底层序列里是哪里出问题了,例如可能是某个b正确执行所需要的条件不满足。

参考

[1] [tracing a packet journey using linux tracepoints](http://netsplit.com/tracing-on-linux

https://blog.yadutaf.fr/2017/07/28/tracing-a-packet-journey-using-linux-tracepoints-perf-ebpf/s)

[2] linux perf

控制结构(10) 指令序列(opcode)的更多相关文章

  1. 控制结构(10): 指令序列(opcode)

    // 上一篇:管道(pipeline) // 下一篇:Continuation-passing_style(CPS) 发现问题 在一个正式项目的开发周期中,除了源代码版本控制外,还存在着项目的配置/编 ...

  2. 循环-10. 求序列前N项和*

    /* * Main.c * C10-循环-10. 求序列前N项和 * Created on: 2014年7月30日 * Author: Boomkeeper *******部分通过******* */ ...

  3. MariaDB 10.3 序列

    在MariaDB .3版本中sequence是特殊的表,和表使用相同的namespace,因此表和序列的名字不能相同. MariaDB [wuhan]> select version(); +- ...

  4. wannafly 练习赛10 f 序列查询(莫队,分块预处理,链表存已有次数)

    链接:https://www.nowcoder.net/acm/contest/58/F 时间限制:C/C++ 5秒,其他语言10秒 空间限制:C/C++ 262144K,其他语言524288K 64 ...

  5. 【AngularJS】—— 10 指令的复用

    前面练习了如何自定义指令,这里练习一下指令在不同的控制器中如何复用. —— 来自<慕课网 指令3> 首先看一下一个小例子,通过自定义指令,捕获鼠标事件,并触发控制器中的方法. 单个控制器的 ...

  6. Velocity(10)——指令的转义

    引用的转义使用"\",指令的转义也是使用"\".但是,指令的转义要比引用的转义复杂很多.例如: #if($foo) Go! #end $foo为true,输出G ...

  7. 【python cookbook】【数据结构与算法】10.从序列中移除重复项且保持元素间顺序不变

    问题:从序列中移除重复的元素,但仍然保持剩下的元素顺序不变 解决方案: 1.如果序列中的值时可哈希(hashable)的,可以通过使用集合和生成器解决.

  8. msvc2010生成的指令序列有问题,可能跟pgo有关

    正常序列 有问题序列 这段代码程序启动是执行,会导致崩溃 工程使用ltcg pgo,也就是说,第一次编译连接完成后,会跑一次profile,再执行连接器代码生成优化. 构建记录显示,ltcg已跑完,说 ...

  9. 循环-10. 求序列前N项和(15)

    #include<iostream>#include<iomanip>using namespace std;int main(){    double i,n,t,a,b;  ...

随机推荐

  1. 如何搭建自己的Maven远程私仓

    1.首先,配置好Maven,jdk等必备环境 2.配置好环境后,下载最新版本的nexus 下载地址:http://www.sonatype.org/nexus/go 3.打开目录nexus-***\b ...

  2. win7建立FTP服务器

    1.在控制面板->程序和功能->打开或关闭windows功能->安装IIS服务 2.控制面板->管理工具->IIS管理器 浏览器地址栏:ftp://zc:1234@192 ...

  3. Linux - 简明Shell编程08 - 函数(Function)

    脚本地址 https://github.com/anliven/L-Shell/tree/master/Shell-Basics 示例脚本及注释 #!/bin/bash function Check( ...

  4. QLineEdit IP地址校验

    QLineEdit IP地址校验 原文出处:[上善若静水] 1.通过自定义类方式实现IP4地址范围限制输入: //--------------------TLineEditIP.h---------- ...

  5. spring boot整合jsp的那些坑(spring boot 学习笔记之三)

    Spring Boot 整合 Jsp 步骤: 1.新建一个spring boot项目 2.修改pom文件 <dependency>            <groupId>or ...

  6. Kotlin代理属性--官方文档翻译

    代理属性 Delegated Properties 本文为个人翻译的Kotlin官方文档, 原文连接: Delegated Properties 一些特定的常见类型的属性, 尽管我们可以在每次需要的时 ...

  7. latex插图续

    LaTeX中一般只直接支持插入eps(Encapsulated PostScript)格式的图形文件, 因此在图片插入latex文档之前应先设法得到图片的eps格式的文件.  UNIX下的各种应用软件 ...

  8. 从yum提示空间不足到根分区扩容

    记录一次安装软件的报错 --1261065212@qq.com         1.系统版本(VMware 虚拟机) [root@ansible-admin ~]# cat /etc/redhat-r ...

  9. poj2901 Hotel

    Hotel Time Limit: 10000MS   Memory Limit: 65536K Total Submissions: 859   Accepted: 280 Description ...

  10. thinkjs学习-this.assign传递数据和ajax调用后台接口

    在页面加载时,就需要显示在页面上的数据,可以在后台使用this.assign赋值,在前台通过ejs等模板获取:用户点击按钮,或者触发某些事件和后台进行交互时,就需要用到ajax调用后台接口.本文通过一 ...