使用哈希= C#ASP.NET Core 3.0和Docker,StackExchange.Redis的运行速度非常慢

我目前正在开发一种Web API,在存储和发送数据时必须快速高效。首先,我尝试了Entity Framework Core,然后尝试了ADO.NET,这大大提高了速度。

为了提高速度,我尝试设置Redis。但是,与ADO.NET相比,它的运行速度让我感到震惊。具体来说,ADO.NET 541msRedis直至2.5s都可以通过邮递员检索具有32属性的5对象!

以下是我为优化Redis而做的一些事情:

  • 使我所有的HashGet异步
  • 关闭持久性存储(尽管无论如何都已关闭)
  • save中的config设置为""
  • 创建了一个IDatabase的全局实例,因此仅使用了一个连接
  • 在启动时创建了我的所有哈希(请参见RedisConnectionHelper.Instance.Hashes

我代表每个数据行的实际哈希(RedisConnectionHelper.Instance.Hashes)从device1:input1device32:input12,共384

这是我的CRUD部分课程:

namespace StateAPI.RedisContext
{
    public class RedisCRUD : IRedisCRUD
    {
        // ^^^ store and re-use this!!!
        IDatabase db = RedisConnectionHelper.Instance.Connectionmultiplexer.GetDatabase();

        public async Task<IEnumerable<StateRedis>> AllStatesAsync()
        {
            List<StateRedis> returnedDevices = new List<StateRedis>();

            foreach (string h in RedisConnectionHelper.Instance.Hashes)
            {
                StateRedis state = new StateRedis
                {
                    Id = await db.HashGetasync(h,"id"),InputState = await db.HashGetasync(h,"state"),OnPhrase = await db.HashGetasync(h,"onphrase"),OffPhrase = await db.HashGetasync(h,"offphrase"),When = await db.HashGetasync(h,"when")
                };

                returnedDevices.Add(state);
            }

            return returnedDevices;
        }
    }
}

这是我的api控制器:

    // GET api/getallstates
    [HttpGet]
    [Route("getallstates")]
    public async Task<IEnumerable<StateRedis>> GetallStates()
    {
        return await _redisCRUD.AllStatesAsync(); // DI injected in
    }

这是我的StateRedis模型:

namespace StateAPI.Models
{
    public class StateRedis
    {
        public string Id { get; set; }
        public string InputState { get; set; }
        public string OnPhrase { get; set; }
        public string OffPhrase { get; set; }
        public string When { get; set; }
    }
}

请注意,这是Docker的Redis容器,在内部运行Docker的linux服务器上运行。 SQL Server也在同一Docker服务器上运行,通常该服务器可以很好地运行我们所有的容器。请注意,我在这里读到,当某人的Web项目与Redis位于同一位置时,他们的问题就消失了吗?

时间似乎在1秒和2.5秒之间波动,因此显然某个地方存在主要瓶颈。

更新

我的Redis连接助手:

namespace StateAPI.Helpers
{
    public class RedisConnectionHelper
    {
        private static RedisConnectionHelper _instance = null;

        public Connectionmultiplexer Connectionmultiplexer { get; set; }

        public List<string> Hashes { get; set;}

        public static RedisConnectionHelper Instance
        {
            get
            {
                if (_instance == null) _instance = new RedisConnectionHelper();
                return _instance;
            }
            protected set
            {
                _instance = value;
            }
        }

        public RedisConnectionHelper()
        {
            // ^^^ store and re-use this!!!
            Connectionmultiplexer = Connectionmultiplexer.Connect("dell-docker"); // Note that Connectionmultiplexer implements IDisposable and can be disposed when no longer required

            // create hashes for each device and input e.g. device1:input12
            Hashes = new List<string>();

            int deviceCount = 1;
            int inputCount = 1;

            int numOfInputs = 12;
            int numOfDevices = 32;

            for (int i = 0; i < (numOfDevices * numOfInputs); i++)
            {
                if (i % numOfInputs == 0 && i != 0)
                {
                    inputCount = 1;
                    deviceCount = deviceCount + 1;
                }

                string deviceHash = $"device{deviceCount}:input{inputCount}";

                inputCount = inputCount + 1;

                Hashes.Add(deviceHash);
            }
        }
    }
}
dahuaabcd123 回答:使用哈希= C#ASP.NET Core 3.0和Docker,StackExchange.Redis的运行速度非常慢

问题

您发布的代码使Redis进行了1920次往返(Hashes集合中的384个项目x每个请求5个请求),并且它们是按顺序进行的:

foreach (string h in RedisConnectionHelper.Instance.Hashes)
{
    StateRedis state = new StateRedis
    {
        Id = await db.HashGetAsync(h,"id"),// Execution stops until Redis returns
        InputState = await db.HashGetAsync(h,"state"),// Execution stops until Redis returns
        OnPhrase = await db.HashGetAsync(h,"onphrase"),// Execution stops until Redis returns
        OffPhrase = await db.HashGetAsync(h,"offphrase"),// Execution stops until Redis returns
        When = await db.HashGetAsync(h,"when")           // Execution stops until Redis returns
    };

    returnedDevices.Add(state);
}

对于每个哈希,此代码将发出5个请求,并等待每个返回,然后再发出下一个请求。即使Redis的等待时间仅为1ms,也就是等待接收每个响应的第一个字节也就是1920ms。

(在这里使用async / await并没有解决此问题。尽管线程被阻塞以服务 other 传入请求,但是await意味着对db.HashGetAsync()的每次调用都可以直到上一个完成才开始。)

这类似于进行如下1920个SQL查询:

SELECT id        FROM data WHERE deviceID = 1 AND inputID = 1
SELECT state     FROM data WHERE deviceID = 1 AND inputID = 1
SELECT onphrase  FROM data WHERE deviceID = 1 AND inputID = 1
SELECT offphrase FROM data WHERE deviceID = 1 AND inputID = 1
SELECT when      FROM data WHERE deviceID = 1 AND inputID = 1

SELECT id        FROM data WHERE deviceID = 1 AND inputID = 2
SELECT state     FROM data WHERE deviceID = 1 AND inputID = 2
SELECT onphrase  FROM data WHERE deviceID = 1 AND inputID = 2
SELECT offphrase FROM data WHERE deviceID = 1 AND inputID = 2
SELECT when      FROM data WHERE deviceID = 1 AND inputID = 2
...

SELECT id        FROM data WHERE deviceID = 32 AND inputID = 12
SELECT state     FROM data WHERE deviceID = 32 AND inputID = 12
SELECT onphrase  FROM data WHERE deviceID = 32 AND inputID = 12
SELECT offphrase FROM data WHERE deviceID = 32 AND inputID = 12
SELECT when      FROM data WHERE deviceID = 32 AND inputID = 12

快速改进

一种将请求数量减少5倍的快速方法是检索一个请求中所需的所有哈希值:

foreach (string h in RedisConnectionHelper.Instance.Hashes)
{
    var keys = await db.HashGetAsync(h,new RedisValue[]{ "id","state","onphrase","offphrase","when" })
    StateRedis state = new StateRedis
    {
        Id = keys[0],InputState = keys[1],OnPhrase = keys[2],OffPhrase = keys[3],When = keys[4],};

    returnedDevices.Add(state);
}

此代码每个哈希将发出1个请求,即384个请求。这可能仍然比对一个操作的请求要多,但是可能会使Redis实现与SQL的实现可比。

可能更好的解决方案

管道请求

Redis可以非常快速地响应请求,但是现在代码正在等待上一个响应,然后再发送下一个请求。相反,您可以pipeline the requests:先将它们全部发送,然后接收响应。

public async Task<IEnumerable<StateRedis>> AllStatesAsync()
{
    List<StateRedis> returnedDevices = new List<StateRedis>();

    // Start every request without awaiting the responses
    List<Task<StateRedis>> stateTasks =
        RedisConnectionHelper.Instance.Hashes
        .Select(hashKey => GetStateRedisAsync(hashKey))
        .ToList();

    // Wait for the responses
    StateRedis[] states = await Task.WhenAll(stateTasks);
    return states;

    async Task<StateRedis> GetStateRedisAsync(RedisKey key)
    {
        var keys = await db.HashGetAsync(h,"when" })
        return new StateRedis
        {
            Id = keys[0],};
    }
}

将数据存储为Redis字符串

有几种方法可以进一步加快速度,但是最好的方法可能是重新考虑如何在Redis中存储数据。现在,您将这些数据存储在384个散列中,因此,要获取所有散列中的数据,您必须发出多个Redis请求,因为没有命令会从多个散列中返回数据。

您是否需要将这些作为哈希存储在Redis中?我不知道每个哈希中还存储哪些其他数据,但是如果有意义,您可以将这些数据存储为JSON序列化的Redis字符串:

db.StringSet("device1:input1",JsonConvert.SerializeObject(new StateRedis { id = 1,state = "foobar",... }))
db.StringSet("device1:input2",JsonConvert.SerializeObject(new StateRedis { id = 2,... }))
...
db.StringSet("device32:input12",JsonConvert.SerializeObject(new StateRedis { id = 384,... }))

使用这种格式,您可以一次查询所有字符串:

var values = db.StringGet(new RedisKey[] { "device1:input1","device1:input2",... });
foreach (RedisValue value in values)
{
    var stateRedis = JsonConvert.DeserializeObject<StateRedis>(value);

    // stateRedis == new StateRedis { id = 1,... }
}

如果每个deviceN:inputM键中的数据量较小,则可以接受。如果您在每个对象中存储一个大的二进制对象,那么您可以将该blob存储在一个名为deviceN:inputM:raw_data之类的单独字符串键中。

每个设备/输入的组合哈希

如果将应用程序中的数据存储在Redis哈希中确实有意义,那么您可以将设备的所有输入合并为一个哈希:

db.HashSet("device1",new [] {
    new HashEntry("input1:id",1),new HashEntry("input1:state","foobar"),...
    new HashEntry("input12:id",12),new HashEntry("input12:state",});

使用这种结构,您仍然需要进行32个请求才能获取所有设备的数据,但是如果哈希中的一项是较大的二进制blob,则可以避免不必要地从Redis进行传输。

本文链接:https://www.f2er.com/3165692.html

大家都在问