在昨天慕尼黑的预SubConf的Subversion Workshop中,我展示了一点Subversion
1.5的新特性 - WebDAV通过代理写支持。这个特性允许基于Apache 2.2.x和mod_proxy的Subversion服务器使用一个主服务器和关联的版本库,一个或多个副服务器可以处理读操作,同时通过(代理)将写操作转给主服务器。在这种部署场景中,每个副服务器都使用某种进程与主版本库拷贝同步,通常是通过主版本库本身钩子脚本驱动。
为什么人们需要这样的安排?相对于写操作(例如commit, revision property修改和lock/unlock请求),人们会更多地使用读操作(例如checkouts,
updates, status checks, log历史请求, diff计算等),你一定希望使用多个服务器来分担处理读请求(压力测试的场景)的压力,或者你有一个世界级的组织(例如CollabNet的),办公室分布在United
States, Eastern Europe和India,如果你部署一个跨整个组织的中央服务器,你一定会紧张用户对于Subversion服务器性能的要求,WebDAV通过代理写将会允许你为每个地区配置地域副服务器,普遍提高用户读请求的性能。
为了我的描述,我们来描述一个WebDAV通过代理写的简单场景,包含一个副服务器,使用svnsync机制来从主服务器传播修改。如下描述了怎样做。
首先,你需要在主副服务器上安装Apache的HTTP服务器,而且副服务器的版本必须是高于 2.2.0,并开启了mod_proxy。现在,你需要配置你的主服务器来发布你的版本库。假定你的Subversion版本库位于/opt/svn/project,你一定会在httpd.conf添加如下的代码:
<Location /svn/project>
DAV svn
SVNPath /opt/svn/project
</Location>
你的副服务器最终是主服务器版本库的完全复制品(这是我们需要小心的),但是此刻我们需要一些httpd.conf的魔法来发布他们的复制(再次,我们假定位于/opt/svn/project):
<Location /svn/project>
DAV svn
SVNPath /opt/svn/project
SVNMasterURI http://IP-ADDR-OF-MASTER/svn/project/
</Location>
注意SVNMasterURI指示 - 只有这一点是Subversion
1.5的新东西。它告诉副服务器将写类型操作代理到主服务器,并提供了主服务器版本库的URL。
现在,在每个副服务器上的httpd.conf文件中还有第二点需要修改的地方,记住我们使用svnsync将修改从主服务器推到副服务器上,svnsync使用普通的提交同步,如果你将提交代理回主服务器,那不会工作。所以我们需要另一个Location块,用来发布服务器的版本库,但不是写操作代理的对象,只是允许从主服务器IP地址的提交:
<Location /svn/project-proxy-sync>
DAV svn
SVNPath /opt/svn/project
Order deny,allow
Deny from all
Allow from IP-ADDR-OF-MASTER
<Location /svn/project>
好的,让我们讨论一下实际的版本库,就像我提到的,每个副版本库需要一个主版本库的复制品,而且根据我们的目的,复制品必须是主版本库只读镜像。(见此文)你将会从主服务器运行svnsync
init,为了性能的缘故(在这个场景中非常重要),你将会对主服务器使用file:///的访问。需要小心的事情还有很多,包括在每个副服务器上使用svnadmin
create(或Subversion第三方工具等价的命令)创建新的版本库,创建允许pre-revprop-change的许可钩子,然后使用svnsync
init http://IP-ADDR-OF-SLAVE/svn/project-proxy-sync file:///opt/svn/project
(注意我们使用了特别的sync URL),最后我们使用svnsync sync http://IP-ADDR-OF-SLAVE/svn/project-proxy-sync
将每个修订版本拷贝到副服务器。喜欢冒险的你一定会在每个方法中找到捷径,从只创建一个副服务器准备好同步的版本库,然后将其拷贝到其他副服务器,到手工修改主版本库版本库的修订版本0的属性来优化副服务器的同步代价,我会把这些技巧留给读者作为练习。
让我们看看我们在哪里。我们已经有了配置完成的Apache HTTP服务器,我们已经放置好了每个机器上的版本库,所有的副版本库已经准备好了与主版本库的同步,我们现在需要自动机制来保持镜像的同步,我们使用主版本库的钩子子系统来实现。如果你计划允许修订版本属性修改,你需要在版本库存放
pre-revprop-change钩子脚本,然后可能还有post-revprop-change钩子来告诉svnsync来重新拷贝修订版本属性:
#!/bin/sh
REVISION=${2}
# Launch (backgrounded) sync jobs for each slave server.
svnsync copy-revprops http://IP-ADDR-OF-SLAVE1/svn/project-proxy-sync ${REVISION} &
svnsync copy-revprops http://IP-ADDR-OF-SLAVE2/svn/project-proxy-sync ${REVISION} &
svnsync copy-revprops http://IP-ADDR-OF-SLAVE3/svn/project-proxy-sync ${REVISION} &
…
你也需要一个post-commit钩子来将修订版本传递到新的版本库:
#!/bin/sh
# Launch (backgrounded) sync jobs for each slave server.
svnsync sync http://IP-ADDR-OF-SLAVE1/svn/project-proxy-sync &
svnsync sync http://IP-ADDR-OF-SLAVE2/svn/project-proxy-sync &
svnsync sync http://IP-ADDR-OF-SLAVE3/svn/project-proxy-sync &
…
为什么我们要将svnsync进程放到后台?就像我们提到的,性能在这里非常关键,如果有用户执行提交或属性修改,我们副服务器同步时,他可能会立刻执行读操作(例如svn
update),如果他这这样做,他会得到修订版本在服务器不存在的错误,虽然并不致命,但是很让人困惑。
此刻,我们可以使用我们的服务器工作了,几乎所有的事情都工作了。用户可以从某个存在的s副服务器检出工作拷贝,当他们执行读操作时,服务器将会使用主版本库的复制品返回数据。当他们提交或修改修订版本属性时,他们的副服务器会将之交给主服务器,完成真正的动作,同时将修改传播会所有的副服务器,但是在继续使用之前你还有一些额外的事情要做。
首先,我们没有为我们的用户添加任何认证和授权信息,有趣的是,你需要匹配所有服务器的认证事务(也需要一个方法保持这些东西同步),但你只需要副服务器的读授权,所有的读和写授权事务都在主服务器。(可能只是保持所有服务器上的的所有事情同步是最简单的。)
第二,我们没有做任何处理lock/unlock客户端请求事情,为此我们可以在主版本库实现post-lock和post-unlock钩子脚本,然后将lock/unlock执行到副服务器,就像他们被执行过一样。
这是一项复杂的工作,幸运的是,如果省去此步,开发场景的锁定改进依然可以工作,只是锁定查询(询问”锁定什么了,谁干的?”)会一直为空。
最后,我们没有办法处理主服务器和副服务器之间连接中断时的问题,如果连接是在某个客户端提交操作进行的时候,那样的话 - 提交不会在主服务器上结束,而他的副服务器会提示一些错误,最坏的情况就是主服务器的提交已经完成,而用户客户端永远不会知道这一点。(同样的事情也会发生在独立服务器配置情况下,连接在服务器尝试返回最终的MERGE请求时中断的时候)如果连接在提交之后的svnsync阶段中断,副服务器可以继续工作,但是在下次提交之前会一直提示过时,你可以在主服务器实现一个cron任务来偶尔执行所有副版本库的同步,来减少过期的时间。svnsync在修订版本属性修改后失败会怎样?那样会更加复杂
- 你或许需要实现一种进程的包裹,可以可靠的跟踪成功和失败,并提供一个重试机制。对于传递lock/unlock状态到副服务器时也会出现类似的问题。
就像你看到的,当前的技术发展水平不像你拨动开关那样立刻开始使用一个主服务器到多个副服务器的版本库复制部署场景,这是一件需要技巧的事情,充满了犯错误的机会,并有许多问题没有被发现。但是这是Subversion
1.5提供的新特性,提供了基础需求,如果你希望看一下直到完成的复杂性。
|