ServiceStack.Redis multi-threading collision

2019-08-01 07:05发布

问题:

I have a ServiceStack.Redis multi-threading collision. I have a kind of non-standard usage case for ServiceStack where I capture all routes in one "FallbackRoute". This means there is one service for that one route. Here is the DTO:

[FallbackRoute("/{Path*}")]
public class S3Request  : IRequiresRequestStream{ 
  public string Path{ get; set; }
  public Stream RequestStream { get; set; }
}

The Service is:

public class S3Service : Service
{
    public RedisUsersCredentials RedisUsersCredentials { get; set; }
    // S3 Request Mutual Exclusion Objects:
    static readonly object syncBucket = new object();
    static readonly object syncObject = new object();

public object Get(S3Request request){
  IRedisClient Redis = RedisUsersCredentials.RedisClient;
  // Do a bunch of stuff with Redis
}

public object Put(S3Request request){
  IRedisClient Redis = RedisUsersCredentials.RedisClient;
  // Do a bunch of stuff with Redis
}

Then in AppHost.cs in the Configure block I have autowired redis:

container.Register<ServiceStack.Redis.IRedisClientsManager>(c =>
    new ServiceStack.Redis.BasicRedisClientManager("localhost:6379"));

    container.RegisterAutoWired<RedisUsersCredentials>();

I got this from posts on how to properly use the BasicRedisClientManager with multi-threading. But I get exceptions like:

multi-request: 581, sPort: 40900, LastCommand: SMEMBERS            TenantOwnerSet:s3devel</Message><StackTrace>[S3Request: 4/1/2015 6:36:50 PM]:
[REQUEST: {}]
ServiceStack.Redis.RedisResponseException: Unknown reply on multi-request:     581, sPort: 40900, LastCommand: SMEMBERS TenantOwnerSet:s3devel
at ServiceStack.Redis.RedisNativeClient.CreateResponseError (string) &lt;IL     0x0003a, 0x00153&gt;
at ServiceStack.Redis.RedisNativeClient.ReadMultiData () &lt;IL 0x000e5,     0x0060f&gt;
at ServiceStack.Redis.RedisNativeClient.SendExpectMultiData (byte[][])     &lt;IL 0x00037, 0x001db&gt;
at ServiceStack.Redis.RedisNativeClient.SMembers (string) &lt;IL 0x0001a,     0x000cf&gt;
at    ServiceStack.Redis.Generic.RedisTypedClient`1&lt;string&gt;.GetAllItemsFromSet    (ServiceStack.Redis.Generic.IRedisSet`1&lt;string&gt;) &lt;0x00083&gt;
at ServiceStack.Redis.Generic.RedisClientSet`1&lt;string&gt;.GetAll ()     &lt;0x0006f&gt;
at S3OL.TenantManager.getTenantOwner () [0x0001e] in     /home/admin/devgit/ols3/mono/src/TenantManager.cs:87
at S3OL.TenantManager..ctor (ServiceStack.Redis.IRedisClient,string)     [0x00084] in /home/admin/devgit/ols3/mono/src/TenantManager.cs:60
at S3OL.BucketManager..ctor (ServiceStack.Redis.IRedisClient,string,string)    [0x00016] in /home/admin/devgit/ols3/mono/src/BucketManager.cs:120
at S3OL.S3Service.Delete (S3OL.S3Request) [0x00195] in     /home/admin/devgit/ols3/mono/Interface.cs:570
at (wrapper dynamic-method) object.lambda_method     (System.Runtime.CompilerServices.Closure,object,object) &lt;IL 0x0000c,    0x000a3&gt;
at ServiceStack.Host.ServiceRunner`1&lt;S3OL.S3Request&gt;.Execute   (ServiceStack.Web.IRequest,object,S3OL.S3Request) &lt;0x00642&gt;
</StackTrace><Errors /></ResponseStatus></ErrorResponse>

Only happens when I have more than one client. I can hit it repeatedly with one client, and very fast. If I add a client at the same time it dies in one of these Redis exceptions.

回答1:

The IRedisClient instances are not ThreadSafe and should not be shared across multiple threads. This looks like you're reusing the same RedisClient instance:

IRedisClient Redis = RedisUsersCredentials.RedisClient;

You should be accessing and releasing a new IRedisClient for each request which you can do by accessing the Redis client within the Service, e.g:

public object Put(S3Request request){
    base.Redis;
    // Do a bunch of stuff with Redis
}

Which works because it creates a new instance for that Service if it doesn't exist:

private IRedisClient redis;
public virtual IRedisClient Redis
{
    get { return redis ?? (redis = RedisManager.GetClient()); }
}

Which also gets disposed if it's created after the Service has executed:

public virtual void Dispose()
{
    ...
    if (redis != null)
        redis.Dispose();
}

If you don't want to use the base.Redis instance that gets created from the configured IRedisClientsManager than you must create your own instance within your Service and dispose of it yourself which you would typically create and dispose in a using block, e.g:

public object Get(S3Request request)
{
    using (var redis = RedisUsersCredentials.CreateRedisClient())
    {
        // Do a bunch of stuff with Redis
    }
}

By resolving and releasing a new IRedisClient from a IRedisClientsManager each time your service will be using a new client instance that's not shared by any other threads.