Stackexchange.redis: Como inserir várias chaves?

Criado em 11 mar. 2019  ·  4Comentários  ·  Fonte: StackExchange/StackExchange.Redis

Oi
precisamos implementar a inserção de várias chaves da maneira mais rápida possível
Eu li o seguinte problema:
https://github.com/StackExchange/StackExchange.Redis/issues/432

mas isso não me ajudou

qual é a prática recomendada para inserir N(onde N> 1000, por exemplo)
Espero que haja uma maneira melhor do que chamar StringSet () N vezes

obrigado

Comentários muito úteis

O "algum motivo" é porque: isso não é suportado pelo próprio redis - veja MSET - falta qualquer tipo de validade (o parâmetro when oscila entre MSET e MSETNX ).

Se você precisa de tempos de espera, bem como, duas opções de saltar à mente:

  • a abordagem assíncrona em pipeline usando SETEX (discutido anteriormente)
  • uma solução baseada em Lua via EVAL / EVALSHA ( ScriptEvaluate em SE.Redis)

Aqui está um exemplo completo da segunda opção que assume que um único vencimento pode ser usado para todas as inserções:

`` `c #
usando StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Diagnostics;
usando System.Linq;

classe estática P
{
static void Main ()
{
// AVISO: exemplo limpa o banco de dados como parte da execução

    const int DB = 0;
    var muxer = ConnectionMultiplexer.Connect(new ConfigurationOptions {
        EndPoints = { "127.0.0.1" },
        AllowAdmin = true, // enables FLUSHDB for our example
    });
    var db = muxer.GetDatabase(DB);
    var server = muxer.GetServer(muxer.GetEndPoints().Single());

    Console.WriteLine($"FLUSHING DATABASE {DB}...");
    server.FlushDatabase(DB);

    Console.WriteLine($"size before: {server.DatabaseSize(DB)}");

    const int BatchSize = 100, ExpirySeconds = 120;
    List<RedisKey> keys = new List<RedisKey>(BatchSize);
    List<RedisValue> values = new List<RedisValue>(BatchSize + 1);

    // note that ARGV[1] is the expiry, so all the values are
    // off-by-one compared to their keys
    const string lua = @"

expiração local = tonumber (ARGV [1])
contagem local = 0
para i, chave em ipairs (KEYS) faça
redis.call ('SETEX', chave, expiração, ARGV [i + 1])
contagem = contagem + 1
fim
contagem de retorno
";
values.Add (ExpirySeconds);
foreach (par var em InventData (1024))
{
keys.Add (pair.key);
valores.Adicionar (par.valor);

        if(keys.Count == BatchSize)
        {
            // execute
            Console.WriteLine($"sending batch of {keys.Count}...");
            var count = (int)db.ScriptEvaluate(lua, keys.ToArray(), values.ToArray());
            Debug.Assert(count == keys.Count); // check expectation

            // reset for next batch
            keys.Clear();
            values.Clear();
            values.Add(ExpirySeconds);
        }
    }
    if (keys.Count != 0)
    {
        // execute final batch
        Console.WriteLine($"sending batch of {keys.Count}...");
        var count = (int)db.ScriptEvaluate(lua, keys.ToArray(), values.ToArray());
        Debug.Assert(count == keys.Count); // check expectation
    }

    Console.WriteLine($"size after: {server.DatabaseSize(DB)}");
}

static IEnumerable<(string key, string value)>
    InventData(int count)
{
    var rand = new Random();
    unsafe string Invent(int len)
    {
        string alphabet = "0123456789 abcdefghijklmnopqrstuvwxyz";
        string s = new string('\0', len);
        fixed(char* ptr = s)
        {
            for (int i = 0; i < len; i++)
                ptr[i] = alphabet[rand.Next(alphabet.Length)];
        }
        return s;
    }
    for(int i = 0; i < count; i++)
    {
        yield return (Invent(20), Invent(50));
    }
}

}
`` `

Todos 4 comentários

A maneira mais simples de fazer isso provavelmente seria fazer um loop pegando "algum número" (100? 250? 500? - depende do tamanho das chaves e dos valores, da largura de banda da sua rede, etc.) e emitir um número de MSET operações, que você pode fazer por meio de StringSet(KeyValuePair<RedisKey, RedisValue>[]) . Talvez, por exemplo:

c# const int BatchSize = 100; // play with var batch = new List<KeyValuePair<RedisKey, RedisValue>>(BatchSize); foreach(var pair in yourSourceData) { batch.Add(new KeyValuePair<RedisKey, RedisValue>(pair.Key, pair.Value)); if (batch.Count == BatchSize) { db.StringSet(batch.ToArray()); batch.Clear(); } } if (batch.Count != 0) // final batch db.StringSet(batch.ToArray());

Você pode preferir usar await StringSetAsync e / ou CommandFlags.FireAndForget , dependendo de suas necessidades. Se você estava usando a API assíncrona, também poderia adiar cada await até um pouco antes do próximo lote, de modo que continue criando o próximo lote enquanto o primeiro é processado, mas isso fica mais complexo.

Na extremidade extrema: você poderia escrever um tubo de rolagem de comprimento fixo de aguardáveis ​​e emitir cada item individualmente, mas ... eu suspeito que isso teria mais sobrecarga.

Qualquer uso?

Oi
Obrigado pela resposta detalhada
Parece uma boa solução, vou testá-la para ver se isso ajuda

**EDITAR:
a única coisa que é um problema é o fato de que a sobrecarga de função que leva um array não permite que você defina um tempo de expiração por algum motivo

bool StringSet(KeyValuePair<RedisKey, RedisValue>[] values, When when = When.Always, CommandFlags flags = CommandFlags.None);

O "algum motivo" é porque: isso não é suportado pelo próprio redis - veja MSET - falta qualquer tipo de validade (o parâmetro when oscila entre MSET e MSETNX ).

Se você precisa de tempos de espera, bem como, duas opções de saltar à mente:

  • a abordagem assíncrona em pipeline usando SETEX (discutido anteriormente)
  • uma solução baseada em Lua via EVAL / EVALSHA ( ScriptEvaluate em SE.Redis)

Aqui está um exemplo completo da segunda opção que assume que um único vencimento pode ser usado para todas as inserções:

`` `c #
usando StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Diagnostics;
usando System.Linq;

classe estática P
{
static void Main ()
{
// AVISO: exemplo limpa o banco de dados como parte da execução

    const int DB = 0;
    var muxer = ConnectionMultiplexer.Connect(new ConfigurationOptions {
        EndPoints = { "127.0.0.1" },
        AllowAdmin = true, // enables FLUSHDB for our example
    });
    var db = muxer.GetDatabase(DB);
    var server = muxer.GetServer(muxer.GetEndPoints().Single());

    Console.WriteLine($"FLUSHING DATABASE {DB}...");
    server.FlushDatabase(DB);

    Console.WriteLine($"size before: {server.DatabaseSize(DB)}");

    const int BatchSize = 100, ExpirySeconds = 120;
    List<RedisKey> keys = new List<RedisKey>(BatchSize);
    List<RedisValue> values = new List<RedisValue>(BatchSize + 1);

    // note that ARGV[1] is the expiry, so all the values are
    // off-by-one compared to their keys
    const string lua = @"

expiração local = tonumber (ARGV [1])
contagem local = 0
para i, chave em ipairs (KEYS) faça
redis.call ('SETEX', chave, expiração, ARGV [i + 1])
contagem = contagem + 1
fim
contagem de retorno
";
values.Add (ExpirySeconds);
foreach (par var em InventData (1024))
{
keys.Add (pair.key);
valores.Adicionar (par.valor);

        if(keys.Count == BatchSize)
        {
            // execute
            Console.WriteLine($"sending batch of {keys.Count}...");
            var count = (int)db.ScriptEvaluate(lua, keys.ToArray(), values.ToArray());
            Debug.Assert(count == keys.Count); // check expectation

            // reset for next batch
            keys.Clear();
            values.Clear();
            values.Add(ExpirySeconds);
        }
    }
    if (keys.Count != 0)
    {
        // execute final batch
        Console.WriteLine($"sending batch of {keys.Count}...");
        var count = (int)db.ScriptEvaluate(lua, keys.ToArray(), values.ToArray());
        Debug.Assert(count == keys.Count); // check expectation
    }

    Console.WriteLine($"size after: {server.DatabaseSize(DB)}");
}

static IEnumerable<(string key, string value)>
    InventData(int count)
{
    var rand = new Random();
    unsafe string Invent(int len)
    {
        string alphabet = "0123456789 abcdefghijklmnopqrstuvwxyz";
        string s = new string('\0', len);
        fixed(char* ptr = s)
        {
            for (int i = 0; i < len; i++)
                ptr[i] = alphabet[rand.Next(alphabet.Length)];
        }
        return s;
    }
    for(int i = 0; i < count; i++)
    {
        yield return (Invent(20), Invent(50));
    }
}

}
`` `

muito obrigado
ambas as soluções parecem válidas para mim. vamos tentar

Esta página foi útil?
0 / 5 - 0 avaliações