IT技术博客大学习 共学习 共进步
全部 移动开发 后端 数据库 AI 算法 安全 DevOps 前端 设计 开发者

自动增量升级方案的设计及实现

UC技术博客 2013-07-29 22:54:55 累计浏览 2,020 次
本机暂存

   问题背景:

1. 能否以某种简便甚至自动化的方式,将修改过的文件以增量的方式同步到线上而不影响应用的正常运行。
2. 除了文件同步外,能否自定义某些脚本,在升级时自动执行。
3. 如果发现升级后的版本有问题,能否快速回滚到原来的版本。
写作目的:
1. 以SVN为例子,学会基于版本库的自动增量升级。
2. 无需依赖任何文件同步工具,只需简单的几个shell脚本便可完成从自动增量打包到自动增量升级的整个过程。
适合阅读对象:
1. 想从繁琐乏味的升级工作中解放出来的运维人员。
2. 担心因修改文件数目多而可能导致升级遗漏的开发人员(尤其是web项目开发者)。
3. 想了解自动增量升级设计思想的人员。
不涉及的内容:
1. 暂未包含基于hg或git版本库的增量升级的实现,但思路与SVN是类似的。
2. 本文侧重业务应用的增量升级,不涉及个人软件或者系统软件的增量升级,尽管这两者存在一些相似的地方。
脚本下载地址:
https://github.com/joerong666/auto_patch.git
正文:
增量升级这个名词相信已经不是什么新鲜事物了,甚至我们每天在不经意间也已经做了该事。比如windows的自动更新,就是增量更新的一种。先撇开系统软件的升级不说,作为一个业务应用开发者,笔者这里就自己所了解,介绍一下业务应用在做增量升级方面都采用哪些方法。
升级方法比较:
方法一:逐个文件拷贝覆盖
这个是最原始的增量升级方式,虽然简单,但繁琐且易漏易错,相信稍有点规模的应用都不会采用这种方式。想想如果有分布在不同子目录中的上百个文件被修改过了,你得执行拷贝覆盖多少次,更悲催的是,你花了九牛二虎之力才把文件拷贝好,结果却发现覆盖错了。
方法二:使用类似rsync的文件同步服务器
相对来说,使用rsync可以确保文件不会被误覆盖,也能保持目录结构的一致性。但个人觉得这只是一种文件同步,跟升级还是有一定的区别,如果我们的升级仅仅是涉及文件的覆盖或删除,或许采用rsync是一个不错的选择。但如果想在升级的同时自动执行某些定制的程序或脚本,那么rsync就显得有点爱莫能助了。另外rsync的引入,也需要额外的成本去搭建和监控它,自然也就需要额外的维护成本。而且rsync与版本库的无缝结合也是个值得考验的问题,能否由rsync直接从版本库中获取增量的部分,还是得额外写个脚本将增量部分写到rsync的同步目录中,再由rsync客户端来获取?
方法三:由开发人员手工编写升级脚本
一般来说,采用这种方法,首先需要开发人员记录下哪些文件涉及改动,当然可通过查看版本历史的方式来判断哪些文件需要做更新;然后再针对这些文件,逐条编写相应的用于覆盖或删除线上应用的copy或rm语句;最后将包含这些语句的脚本随增量文件一起打包给运维人员,让运维人员到线上执行。从可靠性的方面考虑,这种方式还不如第二种方法,但相对来说,这种方式的可控性比较灵活,可以灵活自定义升级脚本,而不是简单的同步文件。比如可以定义在升级的同时执行某个特定的程序,最常见的就是在升级的同时重启原来的某些服务。
方法四:自动增量升级
该方法是对方法三的一种增强,一方面是升级脚本是根据版本历史自动生成的,不再需要人工编写;另一方面,支持自定义升级扩展,也就说除了生成简单的增量覆盖升级脚本外,还支持以可插拔式的方式将自定制的程序让升级脚本自动执行。这是笔者选择的升级方法,也是本文的讲述重点。下面针对这种方法具体展开描述。
自动增量升级:
下面先从流程方面来大概介绍一下自动增量升级的整个思路,然后再从程序的角度解释一下本文中所涉及的脚本的作用。
升级流程:
升级流程分两个阶段,第一阶段为增量打包操作,一般由开发人员执行;第二阶段为升级操作,可由运维人员执行。
增量打包流程:
流程描述:更新版本库 ===> 生成增量清单文件 ===> 检查并修剪清单文件(以及编写自定义升级程序) ===> 生成增量补丁包
对应命令或脚本:svn update ===> gen_manifest.sh(需将结果重定向到PATCH_MANIFEST清单文件) ===> 添加或删除清单文件(PATCH_MANIFEST)中无需做更新的记录(同时根据需要编写想让升级程序自动执行的脚本,如demo_custom_script.sh) ===> gen_patch.sh
升级流程:
流程描述:获取增量包 ===> 执行升级操作并生成增量包的回滚备份包
对应命令或脚本:可通过wget获取增量包 ===> patch.sh(执行完后会生成增量回滚包,如patch_recover_20130626113450)
程序解释:
生成清单文件gen_manifest.sh:
$ ./gen_manifest.sh
Usage: ./gen_manifest.sh revision1[:revision2] [sub_dir]
该脚本用于生成增量清单文件,既然是增量,就需要有一个相对版本,所以至少需要传入revision1这个版本参数,其值为上次程序发布时的版本库版本号加1。默认情况,该脚本会生成当前目录及其所有子目录下(递归)的增量清单,如果只需要针对某个子目录,则可指定sub_dir。因一般情况下该脚本生成出来的清单都需做一定的修剪,为了可直接供管道使用,这里并没有让它输出到文件,而是直接输出到标准输出,使用者可根据需要直接重定向到PATCH_MANIFEST(这个文件名是gen_patch.sh脚本约定好使用的,不能为其它名字)文件。
清单文件PATCH_MANIFEST,大体如下:
#revision:A/D/C:src_file[:dest_dir]
r2061:A:sub_dir/dir1/a.java
r2065:A:sub_dir/dir2/b.sh
r2069:A:script/dir3/c.ini:sub_dir/script/dir3
#r2111:D:sub_dir/test/kkk/tt.txt
#r2112:A:sub_dir/test/kkk2
#r2112:D:sub_dir/test/kkk
#r2113:A:sub_dir/test/kkk2/tt2.txt
r2124:A:sub_dir/conf/log4j.properties
#r2197:A:sub_dir/conf
#r2197:A:sub_dir/conf/config.conf
xxxxx:A:sub_dir/db/patch.sql
xxxxx:C:patch_script/demo_custom_script.sh:sub_dir/autoscript

   这里分几点来解释一下这个清单文件:

   1. 行首带#号的为注释行,gen_patch.sh不会解析。由于有些配置文件(如这里的config.conf)我们在版本库上虽做了修改,但我们不能更新到线上应用去,毕竟配置不一样,故直接把相应的行注释掉即可(注意记得把相应的目录也注释掉,如这里的”#r2197:A:sub_dir/conf“行)。

   2. 第一行是一个样例,标识每行记录需遵守的格式,一行最多只有4项,每项用冒号分隔。revision为版本号;A/D/C分别表示为“添加或修改(Add)/删除(Delete)/自定义命令(Comand)“;src_file为涉及文件在版本库中的目录结构;dest_dir(这里是目录,而不是文件)为src_file在发布包中的目录结构,如果没指定则表示涉及的文件在版本库和发布包中的目录结构一样。

3.后面的版本号为xxxxx的行是一些自定义操作行,这些操作并没有或者无法在版本库中体现。如
xxxxx:A:sub_dir/db/patch.sql  ===> patch.sql文件在指定的版本号范围内并没有相应的更改历史,也许它根本就不在版本库内,但我们又想让它随本次的增量包拷贝到线上应用去,所以采用这种定制的方式委婉的将它纳入增量包中。
4.值得注意的是操作为"C"的行,如这里的最后一行:
xxxxx:C:patch_script/demo_custom_script.sh:sub_dir/autoscript ===>C操作的行并非简单的表示将src_file拷贝到dest_dir,事实上它并没有被拷贝到线上应用中去,而只是仅仅打进增量包中,然后作为一种自动化的程序,在运维人员执行升级(patch.sh)操作时被自动执行的程序,有点被回调的味道。该程序一般需要开发人员根据实际需要编写,一般是一些执行服务的操作,如重启某些服务。如这里提到的demo_custom_script.sh:
自定义升级脚本demo_custom_script.sh:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/env bash
 
src_dir=$1  ### 由升级程序patch.sh传递进来,表示增量包在线上被解压的临时目录
dest_dir=$2 ### 由升级程序patch.sh传递进来,表示线上应用所在的目录(即patch.sh所在的目录)
 
export LANG=en_US.UTF-8
 
target_dir=my_target/bin
target_proc=my_service
 
function err_out()
{
echo "Reload my_service failed"    >&2
    exit 1
}
 
cd $dest_dir/$target_dir && \
./my_service reload  ### 升级时(一般是执行完增量清单中的其它操作之后)重启我的服务
 
if [ $? -ne 0 ]; then
err_out
fi
 
exit 0

   生成增量包gen_patch.sh:

$ ./gen_patch.sh
Usage: ./gen_patch.sh <OUTPUT>
<OUTPUT>: patch file like <OUTPUT>-patch20130607.tar.gz will be generated
Attention: a manifest file named 'PATCH_MANIFEST' is needed under current directory

   gen_patch.sh的功能很简单,就是根据之前生成的清单文件PATCH_MANIFEST生成增量包,所以前提是确保清单文件是准确的,当然也可以通过查看生成后的增量包是否符合实际要求来进一步确认。

执行升级操作patch.sh:
$ ./patch.sh
Usage: ./patch.sh patch.tar.gz
顾名思义,该脚本的作用就是用于给当前的应用打上补丁,一般由线上的运维人员执行。该脚本应该在程序第一次发布的时候包含进去,且应该与放置应用代码的根目录同级。如下
myapp-1.1.0-rc5-bin-x86_64
|- approot
|- app-patch20130624.tar.gz
|- patch.sh
|- patch_recover_20130613182709 ===> 升级时生成的增量回滚快照
|- patch_20130626.log ===> 升级时执行的日志
其中的app-patch20130624.tar.gz即为前面通过gen_patch.sh生成的增量升级包,而patch_recover_20130613182709目录是执行升级前自动生成的增量备份,如果发现升级有误的话,可立即通过将该备份目录下的内容拷回原来的位置,即可完成回滚。
结束:
虽然前面洋洋洒洒写了好多,感觉好像很复杂的样子,其实想要表达意思的很简单,就下面三个步骤:
生成增量包:
$ ./gen_manifest.sh 2061 >PATCH_MANIFEST
$ ./gen_patch.sh app ===>生成app-patch20130624.tar.gz
执行升级:
$ ./patch.sh app-patch20130624.tar.gz

同分类推荐文章

  1. 从零重建 macOS 开发机:可复现的环境初始化流程 (2026-06-14 20:36:00)
  2. 百度物理网络监控工具开源第二弹:毫秒级监控工具 baize,让你的网络问题无处遁形 (2026-06-11 08:10:28)
  3. How to Set Up Homebrew Tap for Private CLI Tools: A Complete Guide (2026-05-27 02:13:03)

查看更多 DevOps 文章 →

建议继续学习

  1. 让邮件飞一会儿 (累计阅读 5,709)
  2. 从Code Review 谈如何做技术 (累计阅读 5,217)
  3. Git安装使用手记 (累计阅读 5,052)
  4. 为何改用Git (累计阅读 4,980)
  5. 利用tortoiseSVN在两个版本库间merge code (累计阅读 4,952)
  6. GIT分支管理是一门艺术 (累计阅读 4,641)
  7. IT人员的必经之路(图解) (累计阅读 4,331)
  8. 软件开发中的火车模型发布模式 (累计阅读 4,035)
  9. 创业公司该如何应对竞争对手的抄袭? (累计阅读 3,568)
  10. 程序员的18个有趣的事实 (累计阅读 3,505)