所以我以不同的方式解决了这个问题。我真的不喜欢反映这种私有静态方法来转储缓存的想法,因为您并不真正知道这样做会带来什么;您基本上是在规避封装,这可能会导致无法预料的问题。但实际上,我担心我转储缓存的竞争条件,在我发送请求之前,一些其他线程进入并建立一个新会话,因此我的第一个线程无意中劫持了该会话。坏消息......无论如何,这就是我所做的。
我停下来思考是否有办法隔离进程,然后我的一位 Android 同事回忆起 AppDomains 的可用性。我们都同意旋转一个应该允许 Tcp/Ssl 调用运行,与其他一切隔离。这将允许缓存逻辑保持不变,而不会导致 SSL 会话之间发生冲突。
基本上,我最初将我的 SSL 客户端编写为一个单独的库的内部。然后在那个图书馆里,我有一个公共服务充当那个客户的代理/调解人。在应用程序层,我希望能够根据硬件类型在服务(在我的例子中是 HSM 服务)之间切换,所以我将它包装到一个适配器中,并将它与工厂接口使用。好的,那有什么关系呢?好吧,它只是让干净地做这个 AppDomain 事情变得更容易,而不会强迫任何其他公共服务消费者(我谈到的代理/中介)的这种行为。你不必遵循这个抽象,我只想在找到抽象的好例子时分享它们:)
现在,在适配器中,我基本上不是直接调用服务,而是创建域。这是演员:
public VCRklServiceAdapter(
string hostname,
int port,
IHsmLogger logger)
{
Ensure.IsNotNullOrEmpty(hostname, nameof(hostname));
Ensure.IsNotDefault(port, nameof(port), failureMessage: $"It does not appear that the port number was actually set (port: {port})");
Ensure.IsNotNull(logger, nameof(logger));
ClientId = Guid.NewGuid();
_logger = logger;
_hostname = hostname;
_port = port;
// configure the domain
_instanceDomain = AppDomain.CreateDomain(
$"vcrypt_rkl_instance_{ClientId}",
null,
AppDomain.CurrentDomain.SetupInformation);
// using the configured domain, grab a command instance from which we can
// marshall in some data
_rklServiceRuntime = (IRklServiceRuntime)_instanceDomain.CreateInstanceAndUnwrap(
typeof(VCServiceRuntime).Assembly.FullName,
typeof(VCServiceRuntime).FullName);
}
所有这一切都是创建一个命名域,我的实际服务将从该域中独立运行。现在,我遇到的大多数关于如何在域中实际执行的文章都过度简化了它的工作方式。这些示例通常涉及调用myDomain.DoCallback(() => ...);,这并没有错,但是尝试将数据进出该域可能会出现问题,因为序列化可能会阻止您死在轨道上。简单地说,在DoCallback() 之外实例化的对象在从DoCallback 内部调用时不是同一个对象,因为它们是在此域之外创建的(请参阅对象编组)。所以你很可能会遇到各种序列化错误。如果运行整个操作、输入和输出以及所有操作都可以从myDomain.DoCallback() 内部发生,这不是问题,但如果您需要使用外部参数并将此 AppDomain 中的某些内容返回到原始域,则会出现问题。
我在 SO 上遇到了一种不同的模式,它对我有用并解决了这个问题。在我的示例 ctor 中查看 _rklServiceRuntime =。这实际上是要求域实例化一个对象,以便您充当该域的代理。这将允许您编组一些对象进出它。这是我对IRklServiceRuntime的实现:
public interface IRklServiceRuntime
{
RklResponse Run(RklRequest request, string hostname, int port, Guid clientId, IHsmLogger logger);
}
public class VCServiceRuntime : MarshalByRefObject, IRklServiceRuntime
{
public RklResponse Run(
RklRequest request,
string hostname,
int port,
Guid clientId,
IHsmLogger logger)
{
Ensure.IsNotNull(request, nameof(request));
Ensure.IsNotNullOrEmpty(hostname, nameof(hostname));
Ensure.IsNotDefault(port, nameof(port), failureMessage: $"It does not appear that the port number was actually set (port: {port})");
Ensure.IsNotNull(logger, nameof(logger));
// these are set here instead of passed in because they are not
// serializable
var clientCert = ApplicationValues.VCClientCertificate;
var clientCerts = new X509Certificate2Collection(clientCert);
using (var client = new VCServiceClient(hostname, port, clientCerts, clientId, logger))
{
var response = client.RetrieveDeviceKeys(request);
return response;
}
}
}
这继承自 MarshallByRefObject 允许它跨越 AppDomain 边界,并且有一个方法可以获取您的外部参数并从实例化它的域中执行您的逻辑。
现在回到服务适配器:服务适配器现在要做的就是调用_rklServiceRuntime.Run(...) 并输入必要的、可序列化的参数。现在,我只需根据需要创建尽可能多的服务适配器实例,它们都在自己的域中运行。这对我有用,因为我的 SSL 调用很小而且很简短,并且这些请求是在内部 Web 服务内部发出的,其中像这样的实例化请求非常重要。这是完整的适配器:
public class VCRklServiceAdapter : IRklService
{
private readonly string _hostname;
private readonly int _port;
private readonly IHsmLogger _logger;
private readonly AppDomain _instanceDomain;
private readonly IRklServiceRuntime _rklServiceRuntime;
public Guid ClientId { get; }
public VCRklServiceAdapter(
string hostname,
int port,
IHsmLogger logger)
{
Ensure.IsNotNullOrEmpty(hostname, nameof(hostname));
Ensure.IsNotDefault(port, nameof(port), failureMessage: $"It does not appear that the port number was actually set (port: {port})");
Ensure.IsNotNull(logger, nameof(logger));
ClientId = Guid.NewGuid();
_logger = logger;
_hostname = hostname;
_port = port;
// configure the domain
_instanceDomain = AppDomain.CreateDomain(
$"vc_rkl_instance_{ClientId}",
null,
AppDomain.CurrentDomain.SetupInformation);
// using the configured domain, grab a command instance from which we can
// marshall in some data
_rklServiceRuntime = (IRklServiceRuntime)_instanceDomain.CreateInstanceAndUnwrap(
typeof(VCServiceRuntime).Assembly.FullName,
typeof(VCServiceRuntime).FullName);
}
public RklResponse GetKeys(RklRequest rklRequest)
{
Ensure.IsNotNull(rklRequest, nameof(rklRequest));
var response = _rklServiceRuntime.Run(
rklRequest,
_hostname,
_port,
ClientId,
_logger);
return response;
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
public void Dispose()
{
AppDomain.Unload(_instanceDomain);
}
}
注意 dispose 方法。不要忘记卸载域。该服务实现了实现 IDisposable 的 IRklService,因此当我使用它时,它与 using 语句一起使用。
这似乎有点做作,但实际上并非如此,现在逻辑将在其自己的域上独立运行,因此缓存逻辑保持不变但没有问题。比干预 SSLSessionCache 要好得多!
请原谅任何命名不一致的地方,因为我在写完帖子后正在快速清理实际名称。我希望这对某人有所帮助!