如何在零停机时间下部署ASP.NET应用程序

135
为了部署我们的网站新版本,我们需要执行以下步骤:
  1. 将新代码压缩成zip文件,并上传至服务器。
  2. 在生产服务器上,删除IIS网站目录中所有现有的代码。
  3. 将新代码zip文件解压到现在为空的IIS目录中。
这个过程已经被脚本化,执行速度相当快,但是在删除旧文件和部署新文件的过程中,仍然可能会出现10-20秒的停机时间。
是否有任何建议可以实现0秒停机的方法?

这个问题不应该在ServerFault上吗? - Daniel Rodriguez
52
也许是这样,但是 ServerFault 在2008年9月还不存在。 - Karl Glennon
3
IIS可以指向符号链接文件夹吗?更改符号链接是否会导致IIS进程重新启动? - Neil McGuigan
有没有完整的源代码样例来作为最终解决方案? - Kiquenet
有没有可能拥有多个应用程序池,并将流量从一个应用程序池切换到另一个? - Luke
13个回答

87
您需要2个服务器和一台负载均衡器。以下是步骤:
  1. 将所有流量切换到服务器2
  2. 在服务器1上部署
  3. 测试服务器1
  4. 将所有流量切换到服务器1
  5. 在服务器2上部署
  6. 测试服务器2
  7. 将流量分配到两个服务器上
问题是,即使在这种情况下,如果您正在使用“粘性会话”,仍然会有应用程序重启和会话丢失。如果您拥有数据库会话或状态服务器,则一切都应该没问题。

4
您还可以配置负载均衡器,使其为给定服务器服务现有会话,但不接受新的会话。这允许您避免丢失会话。然而,这种技术需要等待会话结束,并且通常需要编写脚本来完成此操作。 - user41871
44
当代码卷具有对数据库的结构性更改时,此方法往往会失败。一旦您升级了服务器1的数据库,服务器2将会崩溃。现在,您可以备份/还原服务器1上的数据库进行测试,但是然后您就需要解决在并行复制运行时生产数据库中发生更改的数据整理问题。 - EBarr
13
@EBarr说,无论如何,在ASP.NET应用程序上技术上仍然没有停机时间——问题不是“如何在SQL Server数据库上进行零停机时间部署”。 - Sklivvz
10
关键是以一种不会破坏性地开发方式进行sql更改。通常情况下,你需要在后续版本中进行任何破坏性的sql更改,一旦它不再被使用。这不难,只要经过实践就行了。 - Bealer
1
@Bealer,这个问题在任何配置中都是相关的:如果您依赖于多个组件,则每个更改都应向后兼容以与其余部分进行交互。 - Sklivvz
显示剩余8条评论

60

Microsoft Web Deployment Tool 在某种程度上支持此功能:

启用Windows事务性文件系统(TxF)支持。当启用TxF支持时,文件操作是原子的;也就是说,它们要么完全成功,要么完全失败。这确保了数据完整性并防止数据或文件处于“半途”或损坏状态。在MS Deploy中,默认情况下禁用TxF。

看起来交易是针对整个同步进行的。另外,TxF是Windows Server 2008的一个功能,因此此事务功能将无法与早期版本一起使用。

我相信可以通过使用文件夹作为版本和IIS元数据库修改脚本实现0停机时间:

  • 对于现有的路径/url:
  • 将新的(或修改后的)网站复制到服务器上
    • \web\app\v2.1\
  • 修改IIS元数据库以更改网站路径
    • \web\app\2.0\
    • \web\app\v2.1\

此方法提供以下好处:

  • 如果新版本有问题,您可以轻松回滚到v2.0
  • 要部署到多个物理或虚拟服务器,您可以使用文件部署的脚本。一旦所有服务器都具有新版本,就可以同时使用Microsoft Web Deployment Tool更改所有服务器的元数据库。

5
我已经通过改编我们的PowerShell部署脚本来实施这种方法。您可以在此处查看更改IIS网站文件夹的脚本部分: https://dev59.com/xEXRa4cB1Zd3GeqPveWr感谢指引。 - Karl Glennon
17
很遗憾,这种方法无法考虑数据库结构的更改。一旦您将数据库升级到v2.1,那么v2.0就会出现问题。 - EBarr
8
在我看来,使用TxF在这里有点过度了。同时在文件系统中拥有v2.0和v2.1不会对任何事情造成影响。当v2.1上线时才会发生重大变化,而此时TxF事务已经提交。真正的零停机时间是由于IIS从旧AppPool转移到新AppPool的方式,而不是因为TxF。 - RickNZ
5
这样做的问题在于,如果大量用户数据存储在应用程序文件夹的子文件夹中。 - Kenny Evitt
5
这不是0秒部署,因为新应用程序需要启动。 - usr
显示剩余7条评论

21
通过在不同端口上的两个本地IIS站点之间使用IIS中的应用程序请求路由作为软件负载均衡器,可以实现单服务器的零停机部署。这被称为蓝绿部署策略,在任何给定时间只有其中一个站点可用于负载均衡器。将部署到“停止”状态的站点上,对其进行预热,并将其引入负载均衡器(通常通过传递应用程序请求路由健康检查来完成),然后使最初处于活动状态的站点失效并将其从“池”中删除(再次通过使其健康检查失败)。完整的教程可以在此找到。

9
我最近遇到了这个问题,我想到的解决方案是在IIS中设置两个站点,并在它们之间切换。
对于我的配置,我有一个A站点和B站点的Web目录,如下所示: c:\Intranet\Live A\Interface c:\Intranet\Live B\Interface 在IIS中,我有两个完全相同的站点(相同的端口、身份验证等),每个站点都有自己的应用程序池。其中一个站点正在运行(A),另一个站点被停止(B),活动站点也有活动主机标头。
当要部署到生产环境时,我只需将发布到已停止站点的位置。因为我可以使用其端口访问B站点,所以我可以预热该站点,以便第一个用户不会导致应用程序启动。然后使用批处理文件将活动主机标头复制到B,停止A并启动B。

1
这有助于减少由文件复制引起的停机时间,但与@Sklivvz一样存在同样的问题——一旦代码卷具有数据库结构更改,站点就会崩溃。 - EBarr
3
如果你需要删除一列,请在下一个版本中进行操作,等到此列不再被 A 或 B 使用时再执行,避免引入破坏性的 SQL 更改。 - Bealer
@Bealer -- 同意(但有附带条件)。关于“代码角色期间停机时间”的问题,已经有一整个系列的讨论了。但我还没有找到一个真正讨论数据库模式演变现实的。附带条件 - 两阶段模式更改会带来各种复杂性。例如 - 如果表定义与ORM理解的定义不同(新列或缺失列),则许多ORM都会出错。 - EBarr
2
@Rob,如果网站停止了,你如何进行“预热”? - Andrew Gee
2
@Rob "将实时主机标头复制到B" 你能解释一下吗? - Justin J Stark
显示剩余2条评论

8

好的,既然大家都在downvote我2008年写的答案,那么我来告诉你我们现在2014年是如何做的。因为我们现在使用的是ASP.NET MVC,所以我们不再使用Web站点。

我们绝对不需要负载均衡器和两台服务器来完成这项任务,如果您每个网站都要维护3台服务器,那就没问题了,但对于大多数网站来说,这样做是完全过度的。

此外,我们不依赖于Microsoft的最新技术 - 太慢了,还有太多的隐藏魔法,而且容易改变名称。

我们是这样做的:

  1. 我们有一个发布后生成的DLL文件将其复制到“bin-pub”文件夹的步骤。

  2. 我们使用Beyond Compare(非常好用**)验证并同步更改的文件(通过FTP进行,因为广泛支持)上传到生产服务器上

  3. 我们在网站上有一个安全URL,其中包含一个按钮,可以将'bin-pub'中的所有内容复制到'bin'中(首先备份以便快速回滚)。在这一点上,应用程序会自动重新启动。然后我们的ORM检查是否需要添加任何表或列,并创建它们。

这只需要几毫秒的停机时间。应用程序重新启动可能需要一两秒钟,但在重新启动期间,请求被缓冲,因此实际上没有停机时间。

整个部署过程需要5秒到30分钟不等,具体取决于更改了多少文件和需要审查的更改数量。

这样做,您就无需将整个网站复制到另一个目录,只需复制bin文件夹即可。您还可以完全控制该过程并确定正在发生什么变化。

**我们总是快速检查要部署的更改 - 作为最后一次双重检查,以便我们知道要测试什么以及是否出现任何故障。我们使用Beyond Compare,因为它可以轻松比较FTP上的文件差异。如果不使用Beyond Compare,我绝对不会这样做,因为你根本不知道你正在覆盖什么。

*向下滚动到底:( BTW,我不再推荐Web Sites,因为构建速度更慢,而且可能会由于一半已编译的临时文件而崩溃。我们过去使用它们是因为它们允许逐个文件部署。修复小问题非常快速,您可以看到您正在部署的内容(如果使用Beyond Compare,否则就忘了吧)。


但是,由于应用程序池的回收,您仍然会遇到停机时间。 - testpattern
没有问题,应用程序重启期间请求会被 IIS 自动缓冲,因此不会有停机时间。 - mike nelson

7
使用 Microsoft.Web.Administration 的 ServerManager 类,您可以开发自己的部署代理程序。 关键是更改 VirtualDirectory 的 PhysicalPath,这会在旧和新 Web 应用程序之间进行在线原子切换。 请注意,这可能导致旧的和新的 AppDomains 并行执行! 问题在于如何同步对数据库等的更改。 通过轮询具有旧或新 PhysicalPaths 的 AppDomains 是否存在,可以检测旧的 AppDomain(s) 何时终止,以及新的 AppDomain(s) 是否已启动。 要强制启动 AppDomain,必须发出 HTTP 请求(IIS 7.5 支持 Autostart 功能) 现在需要一种阻止对新 AppDomain 的请求的方法。 我使用一个命名互斥体-由部署代理创建和拥有,在新 Web 应用程序的 Application_Start 中等待,然后由部署代理释放一旦完成了数据库更新。 (我在 Web 应用程序中使用标记文件来启用互斥体等待行为) 新的 Web 应用程序运行后,我删除标记文件。

5
我唯一能想到的零停机方法是使用至少2台服务器进行托管。

1

我想稍微修改一下George的答案,对于单个服务器,可以按照以下步骤进行:

  1. 使用Web Deployment Project将站点预编译为单个DLL
  2. 将新站点压缩,并上传到服务器
  3. 将其解压缩到位于具有站点正确权限的文件夹中的新文件夹中,以便解压缩的文件正确继承权限(例如e:\web,带有子文件夹v20090901、v20090916等)
  4. 使用IIS Manager更改包含站点的文件夹名称
  5. 保留旧文件夹一段时间,以便在出现问题时可以回退到旧版本

第4步会导致IIS工作进程重新启动。

如果您不使用InProc会话,则此方法只能实现零停机时间;如果可能,请改用SQL模式(更好的方法是完全避免使用会话状态)。

当然,如果涉及多个服务器和/或数据库更改,则需要更多操作。


1
和@Sklivvz一样的问题--一旦代码卷对数据库进行结构性更改,该方法就会失效。 - EBarr
4
这就是为什么我说当数据库发生变化时,情况会更加复杂。推出具有结构性变化的代码并不仅仅是一个部署问题; 还需要在代码中以及可能还需在数据库中提供支持。 - RickNZ

1
To expand on sklivvz's answer, which relied on having some kind of load balancer (or just a standby copy on the same server)
  1. 将所有流量直接导向站点/服务器2
  2. 可选择等待一段时间,以确保尽可能少的用户在部署版本上有待处理的工作流程
  3. 部署到站点/服务器1并尽可能预热
  4. 事务性地执行数据库迁移(力求实现此目标)
  5. 立即将所有流量导向站点/服务器1
  6. 部署到站点/服务器2
  7. 将流量导向两个站点/服务器

可以通过创建数据库快照/副本引入一些冒烟测试,但这并不总是可行的。

如果可能且需要,请使用“路由差异”,例如不同的租户URL(customerX.myapp.net)或不同的用户,首先向一个不知情的小组进行部署。如果没有失败,就向所有人发布。

由于涉及数据库迁移,因此回滚到先前版本通常是不可能的。

有一些方法可以让应用程序在这些情况下更友好地运行,例如使用事件队列和播放机制,但由于我们正在谈论对正在使用的内容进行更改的部署,因此真正没有绝对可靠的方法。


1
这是我的做法:
绝对最低系统要求:
1 台服务器,带有
  • 1 个运行在80端口的负载均衡器/反向代理(例如nginx)
  • 2 个ASP.NET-Core / mono反向代理 / fastcgi chroot-jails或docker容器,监听2个不同的TCP端口
    (甚至只需在2个不同的TCP端口上运行两个反向代理应用程序,而无需任何沙盒)
工作流程:
开始交易myupdate
try
    Web-Service: Tell all applications on all web-servers to go into primary read-only mode 
    Application switch to primary read-only mode, and responds 
    Web sockets begin notifying all clients 
    Wait for all applications to respond

    wait (custom short interval)

    Web-Service: Tell all applications on all web-servers to go into secondary read-only mode 
    Application switch to secondary read-only mode (data-entry fuse)
    Updatedb - secondary read-only mode (switches database to read-only)

    Web-Service: Create backup of database 
    Web-Service: Restore backup to new database
    Web-Service: Update new database with new schema 

    Deploy new application to apt-repository 
    (for windows, you will have to write your own custom deployment web-service)
    ssh into every machine in array_of_new_webapps
    run apt-get update
    then either 
    apt-get dist-upgrade
    OR
    apt-get install <packagename>
    OR 
    apt-get install --only-upgrade <packagename>
    depending on what you need
    -- This deploys the new application to all new chroots (or servers/VMs)

    Test: Test new application under test.domain.xxx
    -- everything that fails should throw an exception here
    commit myupdate;

    Web-Service: Tell all applications to send web-socket request to reload the pages to all clients at time x (+/- random number)
    @client: notify of reload and that this causes loss of unsafed data, with option to abort 

    @ time x:  Switch load balancer from array_of_old_webapps to array_of_new_webapps 
    Decomission/Recycle array_of_old_webapps, etc.

catch
        rollback myupdate 
        switch to read-write mode
        Web-Service: Tell all applications to send web-socket request to unblock read-only mode
end try 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接