标签 程序员 下的文章

Transaction模式的C实现

提到Transaction模式(即事务模式),很多人会感到陌生。这并不奇怪,在大名鼎鼎的GoF的《Design Pattern》一书中,它仅仅是Command模式的别名罢了。不过在实际的开发中,我们却经常会遇到可以应用事务模式的场景。本文可以理解成Command模式在事务领域的应用,但这样说有些麻烦,我们莫不如直接称之为Transaction模式。

与前几篇设计模式C实现系列文章一样,这篇文章也源于对实际问题的思考和总结。这次的问题是这样的:我们的业务系统实现了一个ftp上传文件的功能,其v1版代码的结构简化后大致如下:

int ftp_upload_file(const char *filename, const remote_server_desc *desc) {
    int ret;

    ret = upload_local_file(filename, desc);
    if (ret)
        return ret;

    ret = remove_local_file(filename);
    if (ret)
        return ret;

    return rename_remote_file(filename, desc);
};

代码的大致流程是这样的:
1、首先调用upload_local_file,将本地文件(比如foo.txt)上传到远程主机(上传后名字为foo.txt.tmp)
2、然后调用remove_local_file,删除本地文件(如foo.txt)
3、最后调用rename_remote_file,对远程主机上的文件进行改名操作(如将foo.txt.tmp改为foo.txt)

正常情况下,这版代码工作的也很好,以下是正常情况下的输出:
upload [foo.txt.tmp] to host [10.10.12.123, incoming/txt] Ok!
remove localfile [foo.txt] Ok!
rename [foo.txt.tmp] to [foo.txt] Ok!

但明眼人都可以看出v1版本代码的问题,那就是对业务异常的处理不够理想,下面列举一些可能出现异常的环节:
1、upload_local_file可能出现异常,返回失败
这时文件也许已经上传成功,我们在退出整个上传流程之前,应该尝试调用remove_remote_file,删除远程主机上的文件,恢复系统状态到上传前状态;

2、remove_local_file可能出现异常,返回失败
此时文件已经上传成功,若不做任何处理而直接退出的话,会导致下次重复上传同名文件而出现覆盖异常。为了防止这一问题的发生,我们在退出整个上传流程之前,应该尝试调用remove_remote_file,删除远程主机上的文件,恢复系统状态;

3、rename_remote_file也可能出现异常,返回失败
此时文件已经上传成功,且本地文件已经被删除,若改名失败而不做任何处理,将会导致已经上传到远程主机上的文件永远不会被处理(因为后缀名为.tmp,远程主机上的处理程序无法识别)。为了应对这一异常,我们应该在退出整个上传流程之前,恢复本地文件,并删除已经上传到远程主机上的文件,以恢复系统状态。

于是,我们就有了v2版代码,见下面:

int ftp_upload_file(const char *filename, const remote_server_desc *desc) {
    int ret;

    ret = upload_local_file(filename, desc);
    if (ret) {
        (void)remove_remote_file(filename, desc);
        return ret;
    }

    ret = remove_local_file(filename);
    if (ret) {
        (void)remove_remote_file(filename, desc);
        return ret;
    }

    ret = rename_remote_file(filename, desc);
    if (ret) {
        (void)remove_remote_file(filename, desc);
        (void)recovery_local_file(filename);
        return ret;
    }

    return ret;
};

这样修改后,若rename出现异常,则执行结果会变为:
upload [foo.txt.tmp] to host [10.10.12.123, incoming/txt] Ok!
remove localfile [foo.txt] Ok!
rename [foo.txt.tmp] to [foo.txt] Failed!
remove [foo.txt.tmp] from host [10.10.12.123, incoming/txt] Ok!
recover localfile [foo.txt] Ok!

程序在出现异常后将系统状态恢复到未操作前,并会在下一次操作中重新尝试。可以看出这是一个典型的事务场景,即整个上传过程是一个不可分割的整体,其中包括的诸多操作要么都做,要么都不做。

一切初看上去都很美!但用优雅设计的尺度细致考量,我们就会发现一些问题:
首先,如果一个事务场景包含的操作序列很多,那代码中的异常处理将是很痛苦的事情,以最后一步操作为例,一旦异常出错,我们就需要显式做N步回退处理,代码必然显得十分繁琐。另外大量的错误码判断,也会引入诸多if,势必使得代码味道较差;
其次,事务操作的具体实现都暴露给调用者,这在调用者与事务实现之间引入耦合,不利于代码的单元测试与调试;
最后,类似的事务场景在系统中存在很多,如果按v2版本的实现方式,那么系统中将会存在大量类似结构的代码,也算是一种重复吧。

我们的解决手段无非还是面向接口和封装变化,于是我们就有了充分参考了Transaction模式解决方法的v3版代码。

/* 通用事务接口 transaction_unit.h */
struct transaction_unit_t {
    int (*execute)(struct transaction_unit_t *this, void *arg); /* alias: commit */
    int (*unexecute)(struct transaction_unit_t *this, void *arg); /* alias: rollback */
};

/* upload_request.h */
struct upload_request {
    char filename[PATH_MAX];
    char ip[16];
    char path[PATH_MAX];
};

/* ftp_upload_transaction_unit.h */
struct transaction_unit_t* ftp_upload_transaction_unit_new();
void ftp_upload_transaction_unit_destroy(struct transaction_unit_t **tu);

/* ftp_upload_transaction_unit.c */

typedef struct operation_pair operation_pair;
typedef APR_RING_HEAD(operation_pair_head_t, operation_pair) operation_pair_head_t;

struct operation_pair {
    APR_RING_ENTRY(operation_pair) link;
    int (*do_func)(struct upload_request* r);
    int (*undo_func)(struct upload_request* r);
};

struct ftp_upload_transaction_unit_t {
    struct transaction_unit_t tu;
    operation_pair_head_t     ops;
    operation_pair            *op; /* 记录操作异常所在单元 */
};

struct transaction_unit_t* ftp_upload_transaction_unit_new() {
    struct ftp_upload_transaction_unit_t *tu;
    tu  = (struct ftp_upload_transaction_unit_t*)malloc(sizeof(*tu));
    if (!tu) return NULL;

    memset(tu, 0, sizeof(tu));
    tu->tu.execute = ftp_upload_transaction_execute;
    tu->tu.unexecute = ftp_upload_transaction_unexecute;
    APR_RING_INIT(&(tu->ops), operation_pair, link);

    operation_pair *op = (operation_pair*)malloc(sizeof(*op)); /* 这里省略一些异常处理,下面也是如此 */
    op->do_func = upload_local_file;
    op->undo_func = remove_remote_file;
    APR_RING_ELEM_INIT(op, link);
    APR_RING_INSERT_TAIL(&(tu->ops), op, operation_pair, link);

    op = (operation_pair*)malloc(sizeof(*op));
    op->do_func = remove_local_file;
    op->undo_func = recover_local_file;
    APR_RING_ELEM_INIT(op, link);
    APR_RING_INSERT_TAIL(&(tu->ops), op, operation_pair, link);

    op = (operation_pair*)malloc(sizeof(*op));
    op->do_func = rename_remote_file;
    op->undo_func = NULL;
    APR_RING_ELEM_INIT(op, link);
    APR_RING_INSERT_TAIL(&(tu->ops), op, operation_pair, link);

    return (struct transaction_unit_t*)tu;
}

static int ftp_upload_transaction_execute(struct transaction_unit_t *tu, void *arg) {
    struct ftp_upload_transaction_unit_t *this = (struct ftp_upload_transaction_unit_t*)tu;

    operation_pair *op = NULL;
    int ret = 0;

    APR_RING_FOREACH(op, &(this->ops), operation_pair, link) {
        if (op) {
            if (op->do_func) {
                ret = op->do_func(arg);
                if (ret) {
                    this->op = op;
                    return ret;
                }
            }
        }
    }

    return ret;
}

static int ftp_upload_transaction_unexecute(struct transaction_unit_t *tu, void *arg) {
    struct ftp_upload_transaction_unit_t *this = (struct ftp_upload_transaction_unit_t*)tu;

    operation_pair *op = this->op;
    if (!op)
        return 0;

    do {
        if (op->undo_func) {
            op->undo_func(arg);
        }
        op = APR_RING_PREV(op, link);
    } while(op && (op != APR_RING_SENTINEL(&(this->ops), operation_pair, link)));

    return 0;
}

/* main.c */
int ftp_upload_file(struct upload_request *r) {
    int ret;
    struct transaction_unit_t *tu = ftp_upload_transaction_unit_new();

    /* 事务开始 */
    ret = tu->execute(tu, (void*)r);
    if (ret)
        tu->unexecute(tu, (void*)r);
    /* 事务结束 */

    return ret;
};

int main(int argc, const char *argv[])
{
    struct upload_request r = {"foo.txt", "10.10.12.123", "incoming/txt"};
    return ftp_upload_file(&r);
}

代码有些长,所以省略了destroy等一些非关键性的实现代码。这里将事务模式的基本接口抽象为transaction_unit,而ftp_upload_transaction_unit则是transaction_unit接口的一个实现,它通过一个环形链表来组织由事务处理函数(do_func)以及对应事务回滚函数(undo_func)组成的操作单元。沿着链表正向遍历,即执行事务处理操作集合;一旦某个事务操作出现异常,便改为沿着链表反向遍历,即执行事务回滚操作集合,这样也就实现了一种具体的事务模式。

注意:这里仅是一种事务模式的实现思路,但其实现是否符合事务的要求还不一定,要给出一个完备的事务实现可并非易事,实现FTP上传事务更非易事。

原本设计模式的C实现系列文章在上一篇《Chain of Responsibility模式的C实现》之后就应该嘎然而止的,但变化总比计划快,于是就有了这篇文章。

提高效率不是口号

当前任何一个组织 — 无论是私企,还是国企,无论是政府还是民间组织,无论是在国内还是在国外 — 都在强调提高效率。但"提高效率"不简单是一句口号,还需要脚踏实地的真正去做。

说到"提高效率",大家首先就会想到工作的行为主体-人!促进人员能力的提升是提升个体工作效率的一个很好的办法。在软件开发领域也有一个公认的事实,那就是一个顶尖程序员的效率可以十倍甚至百倍于一个普通程序员。为此,很多组织都投入巨资引入各种针对个体能力提升的培训和咨询,试图以此提升全体人员的效率。我们不能否认这种方法的效果,但从实际情况来看,似乎效果有限,特别是对组织整体效率的提升效果有限,因为组织内不可能都是由领域顶尖人员组成的,即使接受培训,大部分人员也不会成为顶尖级,其水平还是处于平均线的。这就是摆在我们面前的一个现实,我所在的组织同样也面临着同样的问题,这也促使我进行了一些较为深入的思考。

那么除了个体能力提升之外,我们还应当如何提升组织的整体效率呢?

提高效率,首先要做好"认知",找到方向。即知道什么工作决策和行为是有利于提高效率的,什么样的决策和行为是无助于提高效率或是导致无用功。以现阶段比较火的物流快递行业为例,科学地选择送货路线无疑是提高整体效率的一个好方法,即能节省时间,还能节省交通燃油成本,缩短送货时间,提高服务质量。如果你作为快递公司的老总,你的决策应该是:建立一套高智能的选路系统,以帮助每个快递员快速完成送货任务。在软件开发领域,人们一直以来都在做着探索,努力地瞄准正确的方向前行。特别是近些年来,软件开发借鉴其他传统产业的生产经验,试图通过消除开发过程中的浪费来提升整体开发效率。什么是软件开发领域的浪费呢?广义的讲,对开发团队而言,不能卖钱的工作都是浪费;对于客户而言,不能给客户带来价值的工作都是浪费。传统行业(比如汽车制造业)用事实证明,浪费现象只能尽量减少,但无法完全杜绝。但浪费是否是完全没有意义的呢?有些"浪费“对组织的发展壮大还是有裨益的,比如建立企业内部知识库等。OK,我们似乎找到了软件领域提升组织整体效率的方向,那就是要尽量减少那些既不能给客户带来直接价值,也无法给我们自身带来经济利益,对组织的发展壮大也无多大贡献的浪费。

提高效率,还要了解个体的行为特征。我们应该了解所在行业从业人员的行为效率曲线。理想的情况是让大家始终都能处于自己的高效率区间。俗话说,术业有专攻。快递员在挨家串户送货时效率是最高的;软件开发人员在设计、编码和调试时效率是最高的;文案人员在编写文档时的效率是最高的,等等。如果让每个快递员自己优化送货路线、让每个软件开发人员自己做集成构建,环境搭建、调试和测试、让文案人员自己负责后期的装订和印刷,那低效率就不可避免。说白了就是让大家都去做且一直在做自己最擅长的事情,这样才能尽量发挥出个体的效率,才有益于组织整体效率的提升。

有了上面两个方面作为前提,我们就可以得到一种切实可行的提升组织整体效率的方法,那就是推进组织内部服务的建设。什么是"内部服务"?简单来说,就是组织内部一部分人员的工作就是为其他人提供服务,至于服务的内容因行业的不同而不同。"内部服务"可以从全局角度主动地推动个体效率提升。从个体行为上来看,内部服务可以剥离大多数个体所不擅长的事情,让大多数个体长期处在高效率区间,后期将这些大家不擅长的事情以服务或基础设施的形式科学地且用户界面友好地提供给大家,让大家可以高效地或自动化地使用。这就是"内部服务"的原理所在。注意,与在培训和咨询等过程中,组织内人员需主动学习和主动提高效率不同,通过内部服务的建设和提升,组织内人员是在不知不觉中"被提升了效率",也避免了因人员能力参差不齐,效果因人而异的问题,这是其最大魅力。

个人觉得实施内部服务建设有以下几个关键要素:
- 细分工作,识别出基础服务。即找出可以作为服务的工作内容。还是以软件开发领域为例,系统管理小组和版本构建小组的工作内容就可以作为提供给其他人的基础服务。

- 按工作内容调整团队组织。即将组织划分为若干基础服务团队,以及其他非提供内部服务的产品团队;明确内部服务团队的职责范围以及与其他团队的接口方式。

- 基于内部服务改造工作流程。将内部服务放入工作流程中,让成果物在各个团队间高效流动,甚至可以在一定程度上简化原有工作流程。

在人员有限的前提下,内部服务团队也可以是虚拟团队,不过这样做就会导致内部服务工作的优先级在工作极其繁忙时被降低,可能会导致浪费现象在后续显现出来。内部服务的概念也是分工细化思想的延伸,也许它并不适合一些人员规模较小的startup,但却适合那些规模已经扩大但效率却没有明显提升的组织。

在软件开发领域,我们很容易识别出很多基础服务,诸如服务器/虚拟化环境支持服务、公共库开发团队、测试工具的开发服务、构建与持续集成服务、自动化测试支持服务、文案服务等。越来越多的自动化框架和工具(比如puppetjenkinsbuildbot等)也让内部服务团队可以不必具有很大规模,而且可以使得服务团队自身的工作也颇为高效。另外内部服务团队还便于将一些致力于消除浪费、提高效率的思想(诸如持续交付DevOps)快速地转化为具体的工作方法和实践。

总之,提高效率是一个极其重要的事,甚至是直接关乎于真金白银的事。一个组织应着眼于全局思考、规划和落实提高效率的具体措施。如果一个组织现在依然将提高效率停留在喊口号的层次上,那若干年后,这个组织剩下的也就仅是那句口号罢了。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! 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