标签 博客 下的文章

由一个软件库存问题想到的

近期产品线出现这样一个“怪现象”:许多已经完成编码并具备提交给测试组的版本没有测试人员对应。测试部那边给出的策略是:按版本优先级从高到低依次测 试。这样一来一些重要版本需要到3个月甚至更长时间之后才能开始测试。可以肯定这种现象是生产环节的一个问题,但用什么理论去解释和分析这个问题呢?我想 到了“库存” – 软件库存。

Joel说软件》的那个Joel曾写过一篇名为《软件库存》的文章,也正是看了那篇文章后,我才第一次了解到软件库存这个概念。库存似乎是传统制造业中 的一个概念,但软件开发其实也是广义产品生产的一种,虽然有其特殊性,因此有些产品制造方面的理论是可以应用于软件生产过程中的,软件库存就是其中之一。

传统制造业的库存较为容易理解,零件、原材料、半成品以及未卖出去的最终产品这些都可以理解为库存。而软件生产中的库存都包含哪些内容和环节呢?一旦形成库存,利弊又有哪些呢?

* 未纳入开发的需求/特性

这里所说的需求/特性是经过决策后将来要开发的且能带来价值的,而不是为了大而全而滥芋充数的那种。这类需求/特性可理解为已经采购并存放在库中尚未投入 生产的原料库存。这类库存如果变得很大,则很可能说明产品市场场景甚好,但也可能是现有的生产能力出现了不足;如果这块库存过小或没有,则会导致后期开工 不足,或者说产品的持续成长前景黯淡,市场对其的需求也不乐观了,组织对此情况应做出迅速反应。

* 已经开发完毕但未经测试的半成品

开发人员开始投入,根据需求/特性疯狂编码、单元测试,生产出未经专业测试的半成品。这些半成品因其中潜在的许多缺陷而不能作为最终商品投放市场,因此无 法收回成本并赚取利润。一旦这个环节形成库存,则说明后续质量验证环节的生产能力与软件开发环节的生产能力出现了不匹配,这将导致版本中的问题不能尽快地 暴露,使得问题流向其他版本或其他系统中,直到测试开始后才能发现,后续要花费更多的工作量做关联的修正。这也是我所在产品线所遇到的棘手问题,唯一的方 法就是提高质量验证环节的生产能力,至于如何提高,因地制宜,这里不表。而较小的库存或这个环节无库存,也会导致后续质量验证环节的开工不足,或衔接出现 节奏性的问题。

* 未上市或未卖出的成品

当产品顺利通过质量检测部门的测试后,便可正式发布,变成最终产品- 成品,交付到最终用户那里。但如果这个环节出现库存,问题将变得严重得多。要么是前期市场估计过于乐观,要么是行销人员不给力,要么则是生产过剩,没有做 好库存管理等等。如果这个环节库存过小,则最终客户依旧无法及时得到产品,不利于保持客户粘性,客户很可能退而求其次,选择其他厂商的产品了。

以上简要分析既提到了不能忽视库存的存在,也说到了无库存的危害。传统制造行业一直在追求着一种“零库存”的概念,所谓“零库存”,是指物料(包括原材 料、半成品和产成品等) 在采购、生产、销售、配送等一个或几个经营环节中,不以仓库存储的形式存在,而均是处于周转的状态。它并不是指以仓库储存形式的某种或某 些物品的储存数量真正为零,而是通过实施特定的库存控制策略,实现库存量的最小化。在软件生产领域也可借鉴这一概念,在各个环节努力维持合理且适当的库 存,这对整个生产过程是大有裨益的。

可以看出“零库存”的一个精要就是及时收回产品的投资,获得利润,保持生产过程的持续有效进行。这让我想到了当今软件过程的演化:从最初的瀑布过程到目前 流行的迭代和敏捷等过程,以及流行的持续交付等概念,它们似乎都是在追求“零库存”,追求快速回流价值。或者说库存理论潜在地催生了敏捷、持续交付等概念 的迅速发展,并让人们看到其中的裨益所在。

Go中的系统Signal处理

我们在生产环境下运行的系统要求优雅退出,即程序接收退出通知后,会有机会先执行一段清理代码,将收尾工作做完后再真正退出。我们采用系统Signal来 通知系统退出,即kill pragram-pid。我们在程序中针对一些系统信号设置了处理函数,当收到信号后,会执行相关清理程序或通知各个子进程做自清理。kill -9强制杀掉程序是不能被接受的,那样会导致某些处理过程被强制中断,留下无法恢复的现场,导致消息被破坏,影响下次系统启动运行。

最近用Golang实现的一个代理程序也需要优雅退出,因此我尝试了解了一下Golang中对系统Signal的处理方式,这里和大家分享。Golang 的系统信号处理主要涉及os包、os.signal包以及syscall包。其中最主要的函数是signal包中的Notify函数:

func Notify(c chan<- os.Signal, sig …os.Signal)

该函数会将进程收到的系统Signal转发给channel c。转发哪些信号由该函数的可变参数决定,如果你没有传入sig参数,那么Notify会将系统收到的所有信号转发给c。如果你像下面这样调用Notify:

signal.Notify(c, syscall.SIGINT, syscall.SIGUSR1, syscall.SIGUSR2)

则Go只会关注你传入的Signal类型,其他Signal将会按照默认方式处理,大多都是进程退出。因此你需要在Notify中传入你要关注和处理的Signal类型,也就是拦截它们,提供自定义处理函数来改变它们的行为。

下面是一个较为完整的例子:

//signal.go

package main

import "fmt"
import "time"
import "os"
import "os/signal"
import "syscall"

type signalHandler func(s os.Signal, arg interface{})

type signalSet struct {
    m map[os.Signal]signalHandler
}

func signalSetNew()(*signalSet){
    ss := new(signalSet)
    ss.m = make(map[os.Signal]signalHandler)
    return ss
}

func (set *signalSet) register(s os.Signal, handler signalHandler) {
    if _, found := set.m[s]; !found {
        set.m[s] =  handler
    }
}

func (set *signalSet) handle(sig os.Signal, arg interface{})(err error) {
    if _, found := set.m[sig]; found {
        set.m[sig](sig, arg)
        return nil
    } else {
        return fmt.Errorf("No handler available for signal %v", sig)
    }

    panic("won't reach here")
}

func main() {
    go sysSignalHandleDemo()
    time.Sleep(time.Hour) // make the main goroutine wait!
}

func sysSignalHandleDemo() {
    ss := signalSetNew()
    handler := func(s os.Signal, arg interface{}) {
        fmt.Printf("handle signal: %v\n", s)
    }

    ss.register(syscall.SIGINT, handler)
    ss.register(syscall.SIGUSR1, handler)
    ss.register(syscall.SIGUSR2, handler)

    for {
        c := make(chan os.Signal)
        var sigs []os.Signal
        for sig := range ss.m {
            sigs = append(sigs, sig)
        }
        signal.Notify(c)
        sig := <-c

        err := ss.handle(sig, nil)
        if (err != nil) {
            fmt.Printf("unknown signal received: %v\n", sig)
            os.Exit(1)
        }
    }
}

上例中Notify函数只有一个参数,没有传入要关注的sig,因此程序会将收到的所有类型Signal都转发到channel c中。build该源文件并执行程序:

$> go build signal.go
$> signal

在另外一个窗口下执行如下命令:
$> ps -ef|grep signal
tonybai  25271  1087  0 16:27 pts/1    00:00:00 signal
$> kill -n 2 25271
$> kill -n 12 25271
$> kill 25271

我们在第一个窗口会看到如下输出:
$> signal
handle signal: interrupt
handle signal: user defined signal 2
unknown signal received: terminated

在sysSignalHandleDemo中我们也可以为Notify传入我们所关注的Signal集合:

signal.Notify(c, sigs…)

这样只有在该集合中的信号我们才能捕获,收到未在集合中的信号时,程序多直接退出。上面只是一个Demo,只是说明了我们可以捕捉到我们所关注的信号,并未体现程序如何优雅退出,不同程序的退出方式不同,这里没有通用方法,就不细说了,你的程序需要你专门的设计。

另外我们生产环境下的程序多是以Daemon守护进程的形式运行的。我们用C实现的程序多参考“Unix高级编程”中的方法将程序转为Daemon Process,但在Go中目前尚提供相关方式,网上有一些实现,但据说都不理想。更多的Go开发者建议不要在代码中实现Daemon转换,建议直接利用 第三方工具。比如在Ubuntu下我们可以使用start-stop-daemon这个小程序轻松将你的程序转换为Daemon:

$> start-stop-daemon –start –pidfile ./signal.pid –startas /home/tonybai/test/go/signal –background -m
$> start-stop-daemon –stop –pidfile ./signal.pid –startas /home/tonybai/test/go/signal

这里注意:只有加上-m选项,pidfile才能成功创建。

start-stop-daemon在Debian系的Linux发行版中都是默认自带的。但在Redhat系Linux发行版中却没有该工具,我们可以自行安装:

wget -c http://developer.axis.com/download/distribution/apps-sys-utils-start-stop-daemon-IR1_9_18-2.tar.gz
tar -xzf apps-sys-utils-start-stop-daemon-IR1_9_18-2.tar.gz
cd apps/sys-utils/start-stop-daemon-IR1_9_18-2
gcc start-stop-daemon.c -o start-stop-daemon

切换到root下
cp start-stop-daemon /sbin/
chmod +x /sbin/start-stop-daemon

另外Go 1.0.2提供的二进制安装包直接在Redhat 5.6(Linux tonybai 2.6.18-238.el5 #1 SMP Sun Dec 19 14:22:44 EST 2010 x86_64 x86_64 x86_64 GNU/Linux)下面运行出错,提示无法找到GLIBC 2.7版本。目前解决这一问题的方法似乎只有从源码编译安装。进入到$GOROOT/src下,执行./all.bash即可。重现编译链接后的go可执 行程序则运行一切正常。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats