// 上一篇:管道(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. RabbitMQ 使用(一)

    RabbitMQ中的使用 这篇文章将会介绍关于RabbbitMQ的使用,并且使用的是kombo(客户端的Python实现)来实现: 安装 如果使用的是mac安装的话,可以先安装到指定的位置,接着配置命 ...

  2. python——序列 & 集合 & 映射

    列表 & 元组 & 字典 & 集合 序列 序列: 每个元素可以是任何类型(也可以是序列),每个元素被分配一个序号(从0开始)(序号,也叫索引,表示元素的位置) Python中的 ...

  3. Qt日常备注(函数/接口实现)

    1.判断QString是否为纯数字 2.查找自身最长重复子字符串 3.树形列表复选框操作 4.更改文件权限 5.判断系统64位 6.文件生成md5值 7.版本号比较(字符串比较) //-----实现- ...

  4. JavaScript入门(一)

    一.什么叫JS? 1.JS的概念JS,是JavaScript的缩写形式,而JavaScript是一种基于对象和事件驱动并且具有相对安全性的客户端脚本语言.它在Web开发中JS占据着举足轻重的地位,所有 ...

  5. HTML-CSS相关(1)

    一  浮动 1.标准流中的块级盒子,宽度将会自动伸展为100%而浮动的块级盒子,宽度不会自动伸展,而是由内容撑开. 2.当一个盒子浮动,标准流中未浮动的其他盒子,将视浮动盒子不存在而占据浮动盒子原来的 ...

  6. 【Tomcat】batch获得war包

    功能: 将maven项目打包复制到tomcat/webapps set git=C:\Users\zhengwenqiang\git set tomcat=e:\tomcat7.0.64 c: cd ...

  7. cygwin环境c语言开发

    . 在windows上开发c语言,使用sublime编辑器 在工具栏tools-->run 结果报错,原因是没有在GNU环境下使用sublime text2 在 cygwin环境下启动subli ...

  8. ubuntu安装新版QQ

    我一直无法解决Ubuntu QQ问题,而最近我重装ubuntu之后在网络上找到与QQ相关的内容,网上有大神开发出了新版的wineQQ,解决了我们对QQ的需求.经过尝试,完成了QQ安装 如图 安装的是w ...

  9. 剑指Offer第36题—Java版

    本题使用归并排序的思想,结合归并排序,写出的算法解. //数组中的逆序对 public static int InversePairs(int[] array){ if(array==null||ar ...

  10. [读书笔记] 四、SpringBoot中使用JPA 进行快速CRUD操作

    通过Spring提供的JPA Hibernate实现,进行快速CRUD操作的一个栗子~. 视图用到了SpringBoot推荐的thymeleaf来解析,数据库使用的Mysql,代码详细我会贴在下面文章 ...