【问题标题】:How to deploy an ASP.NET Application with zero downtime如何以零停机时间部署 ASP.NET 应用程序
【发布时间】:2010-09-13 23:13:56
【问题描述】:

要部署我们网站的新版本,我们执行以下操作:

  1. 压缩新代码并将其上传到服务器。
  2. 在直播服务器上,从 IIS 网站目录中删除所有直播代码。
  3. 将新的代码压缩文件解压到现在为空的 IIS 目录中

这个过程都是脚本化的,而且发生得很快,但是当旧文件被删除和新文件被部署时,仍然可能有 10-20 秒的停机时间。

关于 0 秒停机方法有什么建议吗?

【问题讨论】:

  • 这不应该在ServerFault上吗?
  • 或许,但 ServerFault 在 08 年 9 月不存在
  • IIS 可以指向符号链接文件夹吗?更改符号链接会导致 IIS 进程回收吗?
  • 有完整源代码脚本示例的最终解决方案吗?
  • 不可以有多个应用池,把流量从一个应用池切换到另一个吗?

标签: asp.net iis deployment redundancy


【解决方案1】:

您需要 2 台服务器和一个负载平衡器。步骤如下:

  1. 打开服务器 2 上的所有流量
  2. 在服务器 1 上部署
  3. 测试服务器 1
  4. 打开服务器 1 上的所有流量
  5. 在服务器 2 上部署
  6. 测试服务器 2
  7. 打开两台服务器上的流量

问题是,即使在这种情况下,如果您使用“粘性会话”,您仍然会出现应用程序重新启动和会话丢失的情况。如果您有数据库会话或状态服务器,那么一切都应该没问题。

【讨论】:

  • 您还可以配置负载均衡器,使其为给定服务器的现有会话提供服务,但不接受新会话。这使您可以避免丢弃会话。然而,这种技术需要等待会话结束,通常您需要编写脚本。
  • 当代码卷对数据库进行结构更改时,此方法往往会失败。一旦升级服务器 1 的数据库,服务器 2 就会爆炸。现在您可以备份/恢复数据库以在服务器 1 上进行测试,但是您会遇到在并行副本运行时整理实时数据库中更改的数据的问题。
  • @AndreiRinea -- 你认为这将如何在大容量 OLTP 系统中工作?要么系统不同步,并且在切换时丢失数据,要么您需要暂停数据输入并编写脚本来识别临时数据并将其迁移到新的数据库结构。
  • @EBarr: 无论如何技术上你在 ASP.NET 应用程序上的停机时间仍然为零——问题不是“如何部署到 sql server db零停机时间”。
  • 他们的关键是以一种不破坏 sql 更改的方式进行开发。一旦不再使用,您通常必须在以下版本中进行任何破坏性的 sql 更改。练习起来并不难。
【解决方案2】:

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 部署工具同时更改所有服务器的元数据库。

【讨论】:

  • 我已经通过调整我们的 powershell 部署脚本来实现这种方法。您可以在此处查看更改 IIS 站点文件夹的脚本部分:stackoverflow.com/questions/330608/… 感谢您的指点。
  • 不幸的是,这种方法没有考虑到数据库的结构变化。升级 v2.1 的数据库后,v.2.0 就会爆炸。
  • IMO 在这里使用 TxF 太过分了。在文件系统中同时拥有 v2.0 和 v2.1 并没有什么坏处。重大变化发生在 v2.1 上线时,到那时,TxF 事务已经提交。真正的零停机时间是因为 IIS 从旧 AppPool 迁移到新 AppPool 的方式,而不是因为 TxF。
  • 另一个问题是如果大量用户数据存储在应用文件夹的子文件夹中。
  • 这不是 0 秒部署,因为新应用需要启动。
【解决方案3】:

通过将 IIS 中的应用程序请求路由用作不同端口上两个本地 IIS 站点之间的软件负载平衡器,您可以在单个服务器上实现零停机时间部署。这被称为蓝绿色部署策略,在任何给定时间,负载均衡器中只有两个站点中的一个可用。部署到“关闭”的站点,对其进行预热,然后将其带入负载均衡器(通常通过应用程序请求路由健康检查),然后将启动的原始站点从“池”中取出(再次通过使其健康检查失败)。

A full tutorial can be found here.

【讨论】:

    【解决方案4】:

    我最近经历了这个,我想出的解决方案是在 IIS 中设置两个站点并在它们之间切换。

    对于我的配置,我为每个 A 和 B 站点都有一个 web 目录,如下所示: c:\内网\直播A\接口 c:\内网\直播B\接口

    在 IIS 中,我有两个相同的站点(相同的端口、身份验证等),每个站点都有自己的应用程序池。其中一个站点正在运行 (A),另一个站点已停止 (B)。直播的也有直播的主机头。

    当涉及到实时部署时,我只是发布到 STOPPED 站点的位置。因为我可以使用它的端口访问 B 站点,所以我可以预热站点,这样第一个用户就不会导致应用程序启动。然后使用批处理文件将实时主机标头复制到 B,停止 A 并启动 B。

    【讨论】:

    • 这有助于减少因文件复制而导致的停机,但与@Sklivvz 存在相同的问题——只要代码卷对数据库进行结构更改,网站就会蓬勃发展。
    • 这对我来说似乎也是一种直观的方式,但为什么没有一种简单的内置方式来做到这一点?
    • @Ebarr 然后不要推出破坏性的 sql 更改。例如,如果您需要删除某个列,请在 A 或 B 不再使用的下一个版本中执行此操作。
    • @Rob 如果站点停止,您如何“预热”站点?
    • @Rob "将直播主机头复制到 B" 你能解释一下吗?
    【解决方案5】:

    使用 Microsoft.Web.Administration 的 ServerManager 类,您可以开发自己的部署代理。

    诀窍是更改 VirtualDirectory 的 PhysicalPath,从而在新旧 Web 应用程序之间进行在线原子切换。

    请注意,这可能会导致新旧 AppDomain 并行执行!

    问题是如何将更改同步到数据库等。

    通过使用旧的或新的 PhysicalPaths 轮询 AppDomain 的存在,可以检测旧的 AppDomain 何时终止,以及新的 AppDomain 是否已启动。

    要强制启动 AppDomain,您必须发出 HTTP 请求(IIS 7.5 支持自动启动功能)

    现在您需要一种方法来阻止对新 AppDomain 的请求。 我使用了一个命名互斥体 - 它由部署代理创建和拥有,由新 Web 应用的 Application_Start 等待,然后在数据库更新完成后由部署代理释放。

    (我在网络应用程序中使用标记文件来启用互斥等待行为) 新的网络应用程序运行后,我会删除标记文件。

    【讨论】:

      【解决方案6】:

      好吧,既然每个人都反对我早在 2008 年写的答案*...

      我会在 2014 年告诉你我们现在是如何做到的。我们不再使用网站,因为我们现在使用的是 ASP.NET MVC。

      我们当然不需要负载平衡器和两台服务器来完成这项工作,如果您为每个维护的网站配备 3 台服务器,那很好,但对于大多数网站来说,这完全是矫枉过正。

      此外,我们不依赖 Microsoft 的最新向导 - 太慢,隐藏的魔法太多,而且太容易更改名称。

      我们是这样做的:

      1. 我们有一个后期构建步骤,将生成的 DLL 复制到“bin-pub”文件夹中。

      2. 我们使用 Beyond Compare(非常棒**)来验证更改的文件并将其同步到生产服务器(通过 FTP,因为它得到了广泛的支持)

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

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

      整个部署过程需要 5 秒到 30 分钟不等,具体取决于更改了多少文件以及要查看多少更改。

      这样,您不必将整个网站复制到不同的目录,只需将 bin 文件夹复制到其他目录即可。您还可以完全控制流程并准确了解发生了什么变化。

      **我们总是快速关注我们正在部署的更改 - 作为最后一分钟的仔细检查,因此我们知道要测试什么,如果有任何问题我们准备好了。我们使用 Beyond Compare 是因为它可以让您通过 FTP 轻松区分文件。如果没有 BC,我永远不会这样做,你不知道你在覆盖什么。

      *滚动到底部查看它:(顺便说一句,我不再推荐网站,因为它们构建速度较慢,并且在编译一半的临时文件时可能会严重崩溃。我们过去使用它们是因为它们允许更灵活的文件-按文件部署。非常快速地解决了一个小问题,您可以准确地看到您正在部署的内容(当然如果使用 Beyond Compare - 否则忘记它)。

      【讨论】:

      • 但是,您仍然会因为应用程序池回收而停机。
      • 不,没有停机时间,因为请求在应用重启期间由 IIS 自动缓冲
      【解决方案7】:

      我能想到的唯一零停机方法涉及在至少 2 台服务器上托管。

      【讨论】:

        【解决方案8】:

        对于单个服务器,我会稍微改进一下 George 的答案,如下所示:

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

        第 4 步将导致 IIS 工作进程回收。

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

        当然,当有多个服务器和/或数据库更改时,它会涉及更多......

        【讨论】:

        • 与@Sklivvz 相同的问题 -- 一旦代码卷对数据库进行结构更改,此方法就会失效。
        • 这就是为什么我说当有数据库更改时它会更多地参与...推出带有数据库结构更改的代码不仅仅是部署问题;代码中也必须有支持,可能在数据库中也是如此。
        【解决方案9】:

        扩展 sklivvz 的答案,它依赖于某种负载平衡器(或只是同一服务器上的备用副本)

        1. 将所有流量定向到站点/服务器 2
        2. (可选)稍等片刻,以确保尽可能少的用户在已部署的版本上有待处理的工作流
        3. 部署到站点/服务器 1 并尽可能预热
        4. 以事务方式执行数据库迁移(努力做到这一点)
        5. 立即将所有流量定向到站点/服务器 1
        6. 部署到站点/服务器 2
        7. 两个站点/服务器的直接流量

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

        如果可能和需要使用“路由差异”,例如不同的租户 URL:s (customerX.myapp.net) 或不同的用户,首先部署到不知情的豚鼠组。如果没有任何问题,请发布给所有人。

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

        有一些方法可以让应用程序在这些场景中运行得更好,例如使用事件队列和回放机制,但由于我们正在讨论将更改部署到正在使用的东西上,所以真的没有万无一失的方法。

        【讨论】:

          【解决方案10】:

          这就是我的做法:

          绝对最低系统要求:
          1 台服务器与

          • 1 个负载均衡器/反向代理(例如 nginx)在端口 80 上运行
          • 2 ASP.NET-Core/mono reverse-proxy/fastcgi chroot-jails 或 docker-containers 监听 2 个不同的 TCP 端口
            (或者甚至只是两个反向代理应用程序在 2 个不同的 TCP 端口上,没有任何沙箱)

          工作流程:

          开始事务我的更新

          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 
          

          【讨论】:

            【解决方案11】:

            我经常使用无停机时间的解决方法是:

            1. 将正在运行的 .NET 核心应用程序 dll 重命名为 filename.dll.backup

            2. 上传新的 .dll(网络应用程序可用并在文件上传时为请求提供服务)

            3. 上传完成后回收应用程序池。需要 RDP 访问服务器或功能以在您的主机控制面板中回收应用程序池。

            IIS 在回收时会与应用程序池重叠,因此回收期间通常不会有任何停机时间。因此,请求仍然会在不知道应用程序池已被回收的情况下进入,并且请求可以无缝地提供服务而不会停机。

            我仍在寻找比这更好的方法..!! :)

            【讨论】:

              【解决方案12】:

              我建议保留旧文件并简单地覆盖它们。这样,停机时间仅限于单个文件覆盖时间,并且一次只丢失一个文件。

              虽然不确定这对“网络应用程序”有帮助(我认为您说的是您正在使用的),这就是我们总是使用“网站”的原因。此外,“网站”部署不会重新启动您的站点并删除所有用户会话。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2011-05-26
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-10-06
                • 2016-07-30
                相关资源
                最近更新 更多