控制结构(10): 指令序列(opcode)
// 上一篇:管道(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
,我们重新确定目标:
- 合并为单一脚本。
- 通过简洁的命令行参数满足不同的需求,命令行参数以操作做分组依据。
- 设计出简洁的配置指定不同的集群、不同的分组。
- 自由地忽略不需要的操作。
- 安全的预览即将执行的命令,避免误操作。
我们把这个单一脚本命名为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" }
]
]
}
}
在这个配置结构里,有两层设计:
- 可以配置不同的
子系统
, 并且根据常见需求预定义内置的子系统,当某些情况下不满强需求时,可以增加子系统。 - 每个子系统下定义四种操作:
push
,start
,stop
,check
。
为什么要定义出一个叫做push
的操作呢?这是因为经过分析,下层的脚本提供了四个基本的原子操作:
- pub:将一个group拷贝到目标机器上。
- stop: 停止目标机器上的一个group
- start:开始目标机器上的一个group
- check:执行测试脚本检测一个group在目标机器上的基本运行情况。
那么,我们实际在部署的过程中,stop
,start
,check
三个操作都相对来说比较单一,不会有太多副作用。但是,pub
动作一般来说就是部署新版本的服务上去,此时:
- 它需要和其他的操作组合使用。
- 多个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
选项时,才会真正执行,这样所有对自己的操作有疑惑的用户都可以放心的预览目标指令序列。
尾声
关于指令序列,有点像汇编的过程:
- 提供原子的指令。
- 提供组合原子指令的机制(例如分组)。
在程序设计的其他场景,例如测试用例的组织,异步链式逻辑的组织里,指令序列都有使用的机会。有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)的更多相关文章
- 控制结构(10) 指令序列(opcode)
// 上一篇:管道(pipeline) 发现问题 在一个正式项目的开发周期中,除了源代码版本控制外,还存在着项目的配置/编译/打包/发布等各种高频但非"核心"的脚本代码.职业程序员 ...
- 循环-10. 求序列前N项和*
/* * Main.c * C10-循环-10. 求序列前N项和 * Created on: 2014年7月30日 * Author: Boomkeeper *******部分通过******* */ ...
- MariaDB 10.3 序列
在MariaDB .3版本中sequence是特殊的表,和表使用相同的namespace,因此表和序列的名字不能相同. MariaDB [wuhan]> select version(); +- ...
- wannafly 练习赛10 f 序列查询(莫队,分块预处理,链表存已有次数)
链接:https://www.nowcoder.net/acm/contest/58/F 时间限制:C/C++ 5秒,其他语言10秒 空间限制:C/C++ 262144K,其他语言524288K 64 ...
- 【AngularJS】—— 10 指令的复用
前面练习了如何自定义指令,这里练习一下指令在不同的控制器中如何复用. —— 来自<慕课网 指令3> 首先看一下一个小例子,通过自定义指令,捕获鼠标事件,并触发控制器中的方法. 单个控制器的 ...
- Velocity(10)——指令的转义
引用的转义使用"\",指令的转义也是使用"\".但是,指令的转义要比引用的转义复杂很多.例如: #if($foo) Go! #end $foo为true,输出G ...
- 【python cookbook】【数据结构与算法】10.从序列中移除重复项且保持元素间顺序不变
问题:从序列中移除重复的元素,但仍然保持剩下的元素顺序不变 解决方案: 1.如果序列中的值时可哈希(hashable)的,可以通过使用集合和生成器解决.
- msvc2010生成的指令序列有问题,可能跟pgo有关
正常序列 有问题序列 这段代码程序启动是执行,会导致崩溃 工程使用ltcg pgo,也就是说,第一次编译连接完成后,会跑一次profile,再执行连接器代码生成优化. 构建记录显示,ltcg已跑完,说 ...
- 循环-10. 求序列前N项和(15)
#include<iostream>#include<iomanip>using namespace std;int main(){ double i,n,t,a,b; ...
随机推荐
- Spring Cloud Alibaba Nacos 入门
概览 阿里巴巴在2018年7月份发布Nacos, Nacos是一个更易于构建云原生应用的动态服务发现.配置管理和服务管理平台.并表示在6-8个月完成到生产可用的0.8版本,目前版本是0.9版本. Na ...
- USSD 杂记
Android Oreo允许应用程序读取来自运营商的USSD消息. 利用emoney执行话费充值,需要执行USSD代码,尝试编写apk执行ussd代码进行充值. 尝试在Android8的系统上进行US ...
- js生成随机颜色
var shine=0.8; var arrays = ['[255,182,193,0.8]','[144,238,144,0.8]','[255,235,205,0.8]','[240,128,1 ...
- 小tips:node起一个简单服务,打开本地项目或文件浏览
1.安装nodejs 2.在项目文件夹目录下创建一个js文件,命名server.js(自定义名称),内容如下 var http = require('http'); var fs = require( ...
- ps -ef |grep java
一.ps -ef |grep java 查看包含“java”的所有进程 二.涉及命令详解 ps命令将某个进程显示出来(是LINUX下最常用的也是非常强大的进程查看命令) grep命令是查找(是一种强大 ...
- Android BroadcastReceiver 接收收到短信的广播
一.知识介绍 1.broadcastReceiver是广播接受者,四大组件之一. 2.Android中内置了很多系统级别的广播,可以在应用程序中得到各种系统的状态信息. 3.使用场景: ①当手机没有电 ...
- Linux如何查找某个时间点后生成的空文件
今天遇到一个特殊需求,需要找到某天(例如2017-04-13)以及这之后生成的空文件.那么这个要怎么处理呢?这个当然是用find命令来解决.如下所示, -mtime -5 表示查找距现在 5*24H ...
- iOS MVVM架构总结
为什么使用MVVM iOS中,我们使用的大部分都是MVC架构.虽然MVC的层次明确,但是由于功能日益的增加.代码的维护,使得更多的代码被写在了Controller中,这样Controller就显得非常 ...
- 如何让EasyUI的Tree或者ComboTree节点不显示图标?
版本:jQuery EasyUI 1.3.2 通过测试,只需把节点的state属性设置为null即可使EasyUI的Tree或者ComboTree控件的节点不显示图标.
- zabbix监控Oracle
可监控项 使用zabbix监控oracle数据库需要借助第三方的插件,目前使用较多的是orabbix.目前维护到了1.2.3版本.关于oracle自带的监控项目有以下几个: DB Version (i ...