1

我正在尝试在 Azure 上托管一个长时间运行的工作流服务,但我遇到了相关问题。
我已经将 timeToUnload 和 timeToPersist 设置为 0,并且我在工作流中勾选了“发送前的持久性” - 这不是持久性的问题,它与实例键的计算方式有关。

当一个 Web 服务器启动一个工作流,而另一个 Web 服务器尝试对该工作流执行另一个操作时,它会失败并显示

System.ServiceModel.FaultException:InstancePersistenceCommand 的执行被中断,因为实例键“12e0b449-7a71-812d-977a-ab89864a272f”未与实例关联。这可能是因为实例或密钥已被清理,或者因为密钥无效。如果生成密钥的消息在错误的时间发送或包含不正确的相关数据,则密钥可能无效。

我使用 wcf 服务诊断对此进行了深入研究,发现这是因为实例密钥的计算包括网站实例名称,因此给定的工作流实例只能从实例化它的同一台机器回调(因为 Azure 设置每个角色实例上的不同网站实例名称)。

解释一下,当我创建工作流的新实例时,我有一个活动,它获取工作流实例 Guid,然后返回该 guid,并且还使用相关初始化程序来设置相关句柄。

我在 web.config 中启用了服务跟踪,因此在服务跟踪查看器中,当我实例化工作流的新实例时,我可以看到以下情况;

<ApplicationData >
    <TraceData >
        <DataItem >
            <TraceRecord Severity ="Information" Channel="Analytic " xmlns="http://schemas.microsoft.com/2004/10/E2ETraceEvent/TraceRecord ">
                <TraceIdentifier >225</ TraceIdentifier>
                <Description >Calculated correlation key '496e3207-fe9d-919f-b1df-f329c5a64934' using values 'key1:10013d62-286e-4a8f-aeb2-70582591cd7f,' in parent scope '{/NewOrbit.ExVerifier.Web_IN_2_Web/Workflow/Application/}Application_default1.xamlx'.</Description >
                <AppDomain >/LM/W3SVC/1273337584/ROOT-1-129811251826070757</AppDomain >
            </TraceRecord >
        </DataItem >
    </TraceData >
</ApplicationData >

重要的一行是这样的:

使用父范围“{/NewOrbit.ExVerifier.Web_IN_2_Web/Workflow/Application/}Application_default1.xamlx”中的值“key1:10013d62-286e-4a8f-aeb2-70582591cd7f”计算相关键“496e3207-fe9d-919f-b1df-f329c5a64934” '。

这个特定工作流实例的 Guid 是10013d62-286e-4a8f-aeb2-70582591cd7f这样的,工作流引擎从496e3207-fe9d-919f-b1df-f329c5a64934. 我可以看到带有 guid 的工作流实例,[System.Activities.DurableInstancing].[InstancesTable]我可以看到 .in 中的实例键[System.Activities.DurableInstancing].[KeysTable]。到目前为止,一切都很好,如果同一台服务器稍后调用相同的工作流程,一切正常。但是,如果其他服务器尝试访问工作流,我会收到上面提到的相关错误。再次查看诊断跟踪,我可以看到:

<TraceData >
    <DataItem >
        <TraceRecord Severity ="Information" Channel="Analytic " xmlns="http://schemas.microsoft.com/2004/10/E2ETraceEvent/TraceRecord ">
            <TraceIdentifier >225</ TraceIdentifier>
            <Description >Calculated correlation key '12e0b449-7a71-812d-977a-ab89864a272f' using values 'key1:10013d62-286e-4a8f-aeb2-70582591cd7f,' in parent scope '{/NewOrbit.ExVerifier.Web_IN_5_Web/Workflow/Application/}Application_default1.xamlx'.                     </Description >
            <AppDomain >/LM/W3SVC/1273337584/ROOT-1-129811251818669004</AppDomain >
        </TraceRecord >
    </DataItem >
</TraceData >

重要的线是

使用父范围“{/NewOrbit.ExVerifier.Web_IN_5_Web/Workflow/Application/}Application_default1.xamlx”中的值“key1:10013d62-286e-4a8f-aeb2-70582591cd7f”计算相关键“12e0b449-7a71-812d-977a-ab89864a272f” '。

如您所见,传入的是同一个 Guid,但系统在计算 Instance key 时包含了网站实例的名称,因此最终会得到一个完全不同的实例 key。

我创建了一个全新的项目来测试它并发现了完全相同的问题。我觉得我必须做一些非常简单的错误,因为我找不到其他人有同样的问题。

4

5 回答 5

4

几个月后,我找到了解决这个问题的方法。根本问题是 Azure 在每个角色实例上为网站命名不同;与“默认网站”不同,该网站被称为类似NewOrbit.ExVerifier.Web_IN_0_Web(给定您的 Web 项目 NewOrbit.ExVerifier.Web 的命名空间)。工作流使用网站名称作为用于计算实例密钥的算法的一部分,因此存在问题。

解决方案很简单,就是在角色启动期间重命名网站,以便在所有实例上调用相同的名称。解决根本问题而不是处理后果,如此明显,我第一次从未见过。

这是您如何执行此操作的方法(基于此:http: //blogs.msdn.com/b/tomholl/archive/2011/06/28/hosting-services-with-was-and-iis-on-windows -azure.aspx )

将 powershell 配置为具有提升的访问权限,以便在配置 IIS 后进行更改:

ServiceDefinition.csdef添加一个启动任务:

<ServiceDefinition name="WasInAzure" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">
  <WebRole name="WebRole1">
      ...
      <Startup>
          <Task commandLine="setup\startup.cmd" executionContext="elevated" />
      </Startup>
  </WebRole>
</ServiceDefinition>

Setup\Startup.cmd应该有这个内容:

powershell -command "set-executionpolicy Unrestricted" >> out.txt

将角色 OnStart 配置为具有管理员权限

ServiceDefinition.csdef添加这个:

<ServiceDefinition name="WasInAzure" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">
  <WebRole name="WebRole1">
  ...
    <Runtime executionContext="elevated" />
  </WebRole>
</ServiceDefinition>

创建一个 powershell 脚本来重命名网站

创建一个setup\RoleStart.ps1文件:

write-host "Begin RoleStart.ps1"
import-module WebAdministration
$siteName = "*" + $args[0] + "*"
Get-WebSite $siteName | Foreach-Object { 
    $site = $_;
    $siteref = "IIS:/Sites/" + $site.Name;
    try {
        Rename-Item $siteref 'MyWebSite'
        write-host $siteName + " was renamed"
    }
    catch
    {
       write-host "Failed to rename " + $siteName + " : " + $error[0]
    }
}
write-host "End RoleStart.ps1"

(将 MyWebSite 替换为您希望在所有服务器上调用该网站的任何内容)。

在角色启动时运行 RoleStart.ps1:

在您的网站项目的根目录中创建或编辑 WebRole.cs 并添加以下代码:

public class WebRole : RoleEntryPoint
{
    public override bool OnStart()
    {
        var startInfo = new ProcessStartInfo()
        {
            FileName = "powershell.exe",
            Arguments = @".\setup\rolestart.ps1",
            RedirectStandardOutput = true,
            UseShellExecute=false,
        };
        var writer = new StreamWriter("out.txt");
        var process = Process.Start(startInfo);
        process.WaitForExit();
        writer.Write(process.StandardOutput.ReadToEnd());
        writer.Close();
        return base.OnStart();
    }
}

应该就是这样。如果您启动多个 Web 角色实例并使用 RDP 连接到它们,您现在应该能够看到该网站在所有实例上都被称为相同,因此工作流持久性有效。

于 2012-08-19T20:50:16.027 回答
1

看来这是在 Web 角色中运行工作流服务的问题。看起来解决方法是在没有相同问题的工作角色中运行您的工作流服务。

于 2012-05-13T18:13:04.860 回答
0

我不熟悉工作流持久性。但是其他人报告说他们已经成功地使 SQL Azure 与 WF 持久性一起工作,我建议您检查 http://social.msdn.microsoft.com/Forums/en-US/ssdsgetstarted/thread/2dac9194-0067- 4e16-8e95-c15a72cb0069/http://www.theworkflowelement.com/2011/05/wf-persistence-on-sql-azure.html看看他们是否有帮助。

最好的祝福,

明旭。

于 2012-05-11T08:46:29.937 回答
0

我们是 WF 和 WCF 的新手,但想知道您是否可以构建自己的实例存储。

这应该使您能够覆盖 InstanceKey 从而您可以计算自己的。

网上有很多例子。

于 2012-05-15T21:25:51.563 回答
0

经过大量反编译和搜索,我终于找到了密钥的生成位置。

在 System.ServiceModel.Channels.CorrelationKey 类中,System.ServiceModel

GenerateKeyString方法可以解决问题。现在我必须找到一种方法来覆盖此方法并制作我自己的生成密钥算法,以便同一个实例可以在多个具有不同名称的 Web 服务器中运行。

private static Guid GenerateKey(string keyString)
{
  return new Guid(HashHelper.ComputeHash(Encoding.Unicode.GetBytes(keyString)));
}

private static string GenerateKeyString(ReadOnlyDictionaryInternal<string, string> keyData, string scopeName, string provider)
{
  if (string.IsNullOrEmpty(scopeName))
    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("scopeName", System.ServiceModel.SR.GetString("ScopeNameMustBeSpecified"));
  if (provider.Length == 0)
    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("provider", System.ServiceModel.SR.GetString("ProviderCannotBeEmptyString"));
  StringBuilder stringBuilder1 = new StringBuilder();
  StringBuilder stringBuilder2 = new StringBuilder();
  SortedList<string, string> sortedList = new SortedList<string, string>((IDictionary<string, string>) keyData, (IComparer<string>) StringComparer.Ordinal);
  stringBuilder2.Append(sortedList.Count.ToString((IFormatProvider) NumberFormatInfo.InvariantInfo));
  stringBuilder2.Append('.');
  for (int index = 0; index < sortedList.Count; ++index)
  {
    if (index > 0)
      stringBuilder1.Append('&');
    stringBuilder1.Append(sortedList.Keys[index]);
    stringBuilder1.Append('=');
    stringBuilder1.Append(sortedList.Values[index]);
    stringBuilder2.Append(sortedList.Keys[index].Length.ToString((IFormatProvider) NumberFormatInfo.InvariantInfo));
    stringBuilder2.Append('.');
    stringBuilder2.Append(sortedList.Values[index].Length.ToString((IFormatProvider) NumberFormatInfo.InvariantInfo));
    stringBuilder2.Append('.');
  }
  if (sortedList.Count > 0)
    stringBuilder1.Append(',');
  stringBuilder1.Append(scopeName);
  stringBuilder1.Append(',');
  stringBuilder1.Append(provider);
  stringBuilder2.Append(scopeName.Length.ToString((IFormatProvider) NumberFormatInfo.InvariantInfo));
  stringBuilder2.Append('.');
  stringBuilder2.Append(provider.Length.ToString((IFormatProvider) NumberFormatInfo.InvariantInfo));
  stringBuilder1.Append('|');
  stringBuilder1.Append((object) stringBuilder2);
  return ((object) stringBuilder1).ToString();
}
于 2013-08-30T21:09:50.033 回答