【问题标题】:Using jedis how to write to a specific slot/node in redis cluster使用jedis如何写入redis集群中的特定插槽/节点
【发布时间】:2018-03-08 18:09:18
【问题描述】:

我正在尝试提高将数据写入 redis 集群的性能。 我们计划从 redi-sentinel 迁移到集群模式以实现可扩展性。

但是,写操作的性能相比 redis-sentinel 是非常低的。我们在 redis-sentinel 中使用了管道,但集群模式不支持管道。

所以,我正在考虑将进入同一节点的所有密钥分组,并使用管道将批处理发送到该特定节点。

所以,我想知道如何知道/计算(在写入集群之前)特定密钥将写入哪个节点/插槽?

【问题讨论】:

    标签: redis jedis redis-sentinel redis-cluster


    【解决方案1】:

    解决方案 1:
    找到了一种解决方案来识别密钥将进入的插槽。 JedisCluster 有一些 API 可以获取它。

    int slotNum = JedisClusterCRC16.getSlot(key); - 提供密钥的槽号。

    Set<HostAndPort> redisClusterNode = new HashSet<HostAndPort>();
    redisClusterNode.add(new HostAndPort(hostItem, port));
    
    JedisSlotBasedConnectionHandler connHandler = new 
                  JedisSlotBasedConnectionHandler(redisClusterNode, poolConfig, 60);
    
    Jedis jedis = connHandler.getConnectionFromSlot(slotNum);
    

    这为集群中的特定节点提供了 jedis 对象(来自 Jedispool 内部)。
    现在有了上面的 jedis 对象,所有命令都可以轻松地为特定节点(在集群中)流水线化

    Pipeline pipeline = jedis.pipelined();
    pipeline.multi();
    for(Entry<String, Map<String, String>> kvf : kvfs.entrySet()) {
       pipeline.hmset(kvf.getKey(), kvf.getValue());
    }
    pipeline.exec();
    

    尽管这种方法(使用 JedisCluster)为密钥提供了适当的节点,但这并没有为我提供预期的性能,我认为这是由于了解插槽号和节点(插槽的)所涉及的过程。
    每次我们尝试获取包含插槽号的实际节点(绝地)时,上述过程似乎都会建立与节点(集群中)的物理连接。因此,如果我们有数百万个密钥,这会阻碍性能。
    因此,使用 Lettuce 包的另一种方法(如下)帮助我克服了这个问题。


    解决方案 2:
    使用支持集群模式发送批量命令的Lettuce包。

         <groupId>biz.paluch.redis</groupId>
         <artifactId>lettuce</artifactId>
         <version>4.4.3.Final</version>
    

    代码sn-p:

    RedisClusterClient client = RedisClusterClient.create(RedisURI.create("hostname", "port"));
    StatefulRedisClusterConnection<String, String> connection = client.connect();
    
    RedisAdvancedClusterAsyncCommands<String, String> commands = connection.async();
    // Disabling auto-flushing
    commands.setAutoFlushCommands(false);
    
    List<RedisFuture<?>> futures = new ArrayList<>();
    // kvf is of type Map<String, Map<String, String>>
    for (Entry<> e : kvf.entrySet())
    {
       futures.add(commands.hmset( (String) e.getKey(), (Map<String, String>) e.getValue()));
    }
    // write all commands to the transport layer
    commands.flushCommands();
    // synchronization example: Wait until all futures complete
    LettuceFutures.awaitAll(10, TimeUnit.SECONDS,
    futures.toArray(new RedisFuture[futures.size()]));
    

    参考:https://github.com/lettuce-io/lettuce-core/wiki/Pipelining-and-command-flushing


    【讨论】:

      【解决方案2】:

      但集群模式不支持流水线

      错了!

      使用单个管道,您只能将多个命令发送到同一个连接到同一个节点。与此节点是单个实例还是Redis Cluster的成员无关。

      所以你的问题应该是使用单个管道,我们不能发送多个命令,其键分布在多个插槽上。为了解决这个问题,您希望这些键位于同一个插槽中。我们怎样才能做到这一点?

      如何知道/计算(在写入集群之前)特定密钥将写入哪个节点/插槽

      您无需自己计算。您可以使用Hash Tags 强制多个键成为同一个哈希槽的一部分。

      因此,您只需使用相同的Hash Tags 重命名您希望位于同一插槽中的那些键。例如将 user-nameuser-age 重命名为 {user-id}user-name{user-id}user-age

      详情请见Hash Tagsdoc

      【讨论】:

      • 为什么你认为cluster mode doesn't support pipeline 是错误的?
      • Redis 集群和单实例 Redis,当且仅当键属于同一个节点/槽时才支持管道。
      • 是的,但是对于 JedisCLuster 的用户,JedisCluster 不支持管道(因为它没有给你一个管道对象)。
        流水线意味着在单个连接上发送多个命令。
        * 我们在集群模式下是否获得单连接? - 是的,我们有。
        * 但是,JedisCluster 模式 API 是否为您提供了单一连接的管道? - 不,它没有。
        所以,从用户的角度来看,JedisCluster 模式不支持 Pipeline。
        但是,肯定有一些方法(变通方法)可以在集群模式下实现管道(有一些注意事项)。
      • HashTags 在这里对我没有帮助。我在这里的用例不仅仅是将密钥(具有相同前缀)发送到同一个节点。但是,我的用例是知道去同一个节点的所有键,以便我可以将所有命令(在所有键上)通过管道传输到该特定节点。
      • 任何时候我们谈论使用哈希标签,我们应该提到哈希标签应该谨慎使用,因为它强制存储到同一个哈希槽(同一个节点)。如果存储不是分布式的,这会随着时间的推移降低性能。 redis.io/topics/cluster-spec
      猜你喜欢
      • 2015-10-13
      • 2016-12-10
      • 2017-12-27
      • 2016-09-21
      • 1970-01-01
      • 2017-06-17
      • 2018-12-04
      • 2022-01-03
      • 1970-01-01
      相关资源
      最近更新 更多