Redis相关

Redis集群方案使用

标签(空格分隔): 分享


Server端

非集群的方式

  • 主从分离
  • 分库
  • 使用hash
  • hash的缺点
    • 物理节点的增删,会导致所有的key都重新分布。

Redis官方集群基本原理 详细介绍



Redis集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis 先对key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点

使用去中心化的思想,使用hash slot方式,将16348个hash slot,覆盖到所有节点上 对于存储的每个key值,使用CRC16(KEY)%16348=slot得到他对应的hash slot,并在访问key时就去找他的hash slot在哪一个节点上,然后由当前访问节点从实际被分配了这个hash slot的节点去取数据 节点之间使用轻量协议通信 减少带宽占用 性能很高 自动实现负载均衡与高可用,自动实现failover,并且支持动态扩展,官方已经玩到可以1000个节点,实现的复杂度低。

  • hash slot 方式
    • 虚拟槽位与物理节点解耦,不受节点数量影响
    • slot-物理节点映射方式更灵活
  • 投票机制
    -所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽
  • 灵活的增加或减少节点(动态的槽位分配)
  • 高可用
    • 主从
    • 动态的给节点分配槽
    • 部分节点fail,不影响其他节点
  • 什么时候节点不可用
    • 投票过程是集群中所有master参与,如果半数以上master节点与master节点通信超时(cluster-node-timeout),认为当前master节点挂掉。
  • 什么时候整个集群不可用(cluster_state:fail)
    • 如果集群任意master挂掉,且当前master没有slave.集群进入fail状态,也可以理解成集群的slot映射[0-16383]不完整时进入fail状态.
    • 如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态.

集群管理

  • 集群信息

  • 集群启动与关闭
# 启动
/data/redis-4.0.1/src/redis-server /data/redis-4.0.1/cluster/30305/redis.conf > /data/redis-4.0.1/cluster/30305/redis-30305.log 2>&1 &
# 关闭
/data/redis-4.0.1/src/redis-cli  -p 30305 -h 172.16.116.51 -a 123456 shutdown
  • 连接集群

redis-cli -c -p 30301 -h 172.16.116.51 -a 123456

  • 集群(cluster)
CLUSTER INFO 打印集群的信息  
CLUSTER NODES 列出集群当前已知的所有节点(node),以及这些节点的相关信息。   
  • 节点(node)
CLUSTER MEET <ip> <port> 将 ip 和 port 所指定的节点添加到集群当中,让它成为集群的一份子。  
CLUSTER FORGET <node_id> 从集群中移除 node_id 指定的节点。  
CLUSTER REPLICATE <node_id> 将当前节点设置为 node_id 指定的节点的从节点。  
CLUSTER SAVECONFIG 将节点的配置文件保存到硬盘里面。   
  • 槽(slot)
CLUSTER ADDSLOTS <slot> [slot ...] 将一个或多个槽(slot)指派(assign)给当前节点。  
CLUSTER DELSLOTS <slot> [slot ...] 移除一个或多个槽对当前节点的指派。  
CLUSTER FLUSHSLOTS 移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点。  
CLUSTER SETSLOT <slot> NODE <node_id> 将槽 slot 指派给 node_id 指定的节点,如果槽已经指派给另一个节点,那么先让另一个节点删除该槽>,然后再进行指派。  
CLUSTER SETSLOT <slot> MIGRATING <node_id> 将本节点的槽 slot 迁移到 node_id 指定的节点中。  
CLUSTER SETSLOT <slot> IMPORTING <node_id> 从 node_id 指定的节点中导入槽 slot 到本节点。  
CLUSTER SETSLOT <slot> STABLE 取消对槽 slot 的导入(import)或者迁移(migrate)。   

//键 (key)  
CLUSTER KEYSLOT <key> 计算键 key 应该被放置在哪个槽上。  
CLUSTER COUNTKEYSINSLOT <slot> 返回槽 slot 目前包含的键值对数量。  
CLUSTER GETKEYSINSLOT <slot> <count> 返回 count 个 slot

新增一个节点

# 启动新实例(一主、一丛)
/data/redis-4.0.1/src/redis-server /data/redis-4.0.1/cluster/30305/redis.conf > /data/redis-4.0.1/cluster/30305/redis-30305.log 2>&1 &
# 连接任何一个节点
redis-cli -c -p 30301 -h 172.16.116.52 -a 123456
# 查看集群信息
cluster info
#查看节点信息
cluster nodes
# 添加主节点到集群
/data/redis-4.0.1/src/redis-trib.rb add-node 172.16.116.52:30305 172.16.116.52:30304
# 添加从节点到集群
/data/redis-4.0.1/src/redis-trib.rb add-node --slave --master-id 73911503bbf5347bdab1f973d90f3b29409ca567 172.16.116.51:30305  172.16.116.52:30304

# 重新分配slot
/data/redis-4.0.1/src/redis-trib.rb reshard 172.16.116.52:30301

删除一个节点

/data/redis-4.0.1/src/redis-trib.rb del-node  172.16.116.52:30301 'hash'

cluster forget hash

视频教程


链接: https://pan.baidu.com/s/1s-nKXGR9hC2cP-hw0tEKFw 密码: chd9

Client端

Redis key 设计技巧

  • 分组(redis,命名空间)

  • 与Mysql 表的映射关系

    • 把表名转换为key前缀 如, tag:
    • 第2段放置用于区分区key的字段–对应mysql中的主键的列名,如userid
    • 第3段放置主键值,如2,3,4…., a , b ,c
    • 第4段,写要存储的列名
  • 例子: 用户表 user , 转换为key-value存储

useridusernamepasswordeemail
9Lisi1111111lisi@163.com

set user:userid:9:username lisi
set user:userid:9:password 111111
set user:userid:9:email lisi@163.com

keys user:userid:9[username|password|email]

集群与单点共存的配置


 <!--读取配置-->
    <context:property-placeholder location="classpath:redis.properties"/>
    <!--redis 密码-->
    <bean name="password" class="org.springframework.data.redis.connection.RedisPassword">
        <constructor-arg value="${redis.cluster.password}"/>
    </bean>
    <!--单点配置-->
    <bean id="sentinelConfiguration" class="org.springframework.data.redis.connection.RedisSentinelConfiguration">
        <constructor-arg name="master" value="${redis.masterName}"/>
        <constructor-arg name="sentinelHostAndPorts">
            <set>
                <value>${redis.sentinels}</value>
                <value>${redis.sentinels2}</value>
                <value>${redis.sentinels3}</value>
            </set>
        </constructor-arg>
        <property name="password" ref="password"/>
    </bean>

    <!--集群配置-->
    <bean id="clusterConfiguration" class="org.springframework.data.redis.connection.RedisClusterConfiguration">
        <constructor-arg name="clusterNodes">
            <set>
                <value>${redis.cluster.node1}</value>
                <value>${redis.cluster.node2}</value>
                <value>${redis.cluster.node3}</value>
                <value>${redis.cluster.node4}</value>
            </set>
        </constructor-arg>
        <property name="maxRedirects" value="${redis.cluster.redirect}" />
        <property name="password" ref="password"/>
    </bean>


    <!-- 集群工厂 -->
    <bean id="jedisClusterConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <constructor-arg index="0" ref="clusterConfiguration"/>
    </bean>

    <!-- 单点工厂 -->
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <constructor-arg index="0" ref="sentinelConfiguration"/>
    </bean>


    <!-- 单点客户端 -->
    <bean id="myRedisClient" class="com.shangde.greatbear.redis.MyRedisClient" init-method="springInit">
        <property name="redisPath">
            <value>redis.properties</value>
        </property>
    </bean>

    <!--集群客户端-->
    <bean id="redisClusterTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
        <property name="connectionFactory" ref="jedisClusterConnectionFactory" />
    </bean>

StringRedisTemplate 使用 参见单元测试

@Autowired
@Qualifier("redisClusterTemplate")
StringRedisTemplate clusterTemplate;

@Autowired
@Qualifier("redisStandaloneTemplate")
StringRedisTemplate standaloneTemplate;

package com.greatbear.test.common;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.connection.RedisClusterNode;
import org.springframework.data.redis.connection.RedisZSetCommands;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(locations = {
        "classpath:test/test-redis-cluster.xml"
})
/**
 * lettuce 集群 单元测试
 */
public class TestRedisCluster {

    @Autowired
    //@Qualifier("redisStandaloneTemplate")
    @Qualifier("redisClusterTemplate")
    StringRedisTemplate clusterTemplate;


    /**
     * get set del has getAndSet getIfAbsent increment expire expireAt
     */
    @Test
    public void redisKeyCommandTest() {
        ValueOperations<String, String> ops = clusterTemplate.opsForValue();
        // del
        clusterTemplate.delete("getAndSet");

        //dump
        ops.set("dumpKey", "dumpValue");
        ops.get("dumpKey");

        //exist
        clusterTemplate.hasKey("dumpKey");

        // expire
        clusterTemplate.expire("dumpKey", 10, TimeUnit.MINUTES);
        clusterTemplate.getExpire("dumpKey", TimeUnit.SECONDS);

        //getAndSet
        ops.getAndSet("getAndSet", getRandom());
        clusterTemplate.randomKey();
        // keys type ** 慎用 **
        clusterTemplate.keys("*").forEach(k -> System.out.println(clusterTemplate.type(k)));
    }

    /**
     * String Command
     */
    @Test
    public void redisStringCommandTest() {
        ValueOperations<String, String> ops = clusterTemplate.opsForValue();
        String key = "opsForValueSimpleKeyValueTest";
        //set
        ops.set(key, getRandom());
        //get
        System.out.println(ops.get(key));
        //getRange
        System.out.println(ops.get(key, 1, 6));
        //getSet
        ops.getAndSet(key, getRandom());
        //getBit setBite
        ops.getBit(key, 1);
        ops.setBit(key, 1, true);
        //mget mset
        ops.multiSet(getMap());
        ops.multiGet(Arrays.asList("a", "b"));
        //setex
        ops.set(key, "1", 1, TimeUnit.SECONDS);
        //setnx
        ops.setIfAbsent(key, getRandom());
        //setRange
        ops.set(key, "asdasd", 1);
        //strlen
        ops.size(key);
        //msetnx
        ops.multiSetIfAbsent(getMap());
        //incr
        ops.set(key, "1");

        ops.increment(key, 1d);
        //append
        ops.append(key, "suffix");
    }


    /**
     * HASH
     * 支持多个键值对的操作
     */
    @Test
    public void redisHashTest() {
        HashOperations<String, Object, Object> ops = clusterTemplate.opsForHash();
        String key = "redisHashTest";
        //HMSET
        ops.putAll(key, getMap());
        //HEDL
        ops.delete(key, "key1", "key2");
        //HEXISTS
        ops.hasKey(key, "key1");
        //HGETALL
        ops.entries(key);
        //HINCRBY
        ops.increment(key, "key1", 1f);
        //HKEYS
        ops.keys(key);
        //HLEN
        ops.size(key);
        //HMGET
        ops.multiGet(key, Arrays.asList("key1", "key2"));
        //HVALS
        ops.values(key);

    }

    /**
     * 链表
     * 支持阻塞操作
     */
    @Test
    public void redisListTest() {
        ListOperations<String, String> ops = clusterTemplate.opsForList();
        String key = "redisListTest1";
        String key2 = "redisListTest2123123123";


        //BLPOP 阻塞
        ops.leftPop(key, 1, TimeUnit.SECONDS);
        //BRPOP
        ops.rightPop(key, 1, TimeUnit.SECONDS);
        //BLPOPRPUSH
        ops.rightPopAndLeftPush(key, key2, 1, TimeUnit.SECONDS);
        // LINDEX
        ops.index(key, 1);
        // LINSERT
        ops.leftPush(key, "1");
        ops.set(key, 0, "insertValue");
        //LLEN
        ops.size(key);
        //LPOP
        ops.leftPop(key);
        //LPUSH
        ops.leftPush(key, "1");
        //LPUSH BEFORE
        ops.leftPush(key, "1", "2");
        //LPUSH 多个
        ops.leftPushAll(key, "v1", "v2", "v3");
        //LPUSHX 存在才PUSH
        ops.leftPushIfPresent(key, "4");
        //LRANGE
        ops.range(key, 0, 1);
        //LREM
        ops.remove(key, 1, "4");
        //LTRIM
        ops.trim(key, 0, 3);
        //RPOP
        ops.rightPop(key);
    }

    /**
     * SET操作
     */
    @Test
    public void redisSetTest() {
        SetOperations<String, String> ops = clusterTemplate.opsForSet();
        String key = "redisSetTest";
        String key2 = "redisSetTest2";
        //SADD 多个
        ops.add(key, "v1", "v2");
        ops.add(key2, "v1", "v3");
        //SCARD
        ops.size(key);
        //SDIFF
        ops.difference(key, key2);
        //SDIFFSTORE
        ops.differenceAndStore(key, key2, "temp");
        //SINNER
        ops.intersect(key, key2);
        //SINNERSTORE
        ops.intersectAndStore(key, key2, "temp2");
        //SISMEMBER
        ops.isMember(key, "v1");
        //SMEMBERS ** 慎用 **
        ops.members(key);
        //SMOVE
        ops.move(key, "v1", key2);
        //SPOP
        ops.pop(key);
        //SRANDOMMEMBER
        ops.randomMember(key);
        //SREM
        ops.remove(key, key2);
        //SUNION
        ops.union(key, key2);
        //SUNIONSTORE
        ops.add(key, "v1", "v2", "v3");
        ops.add(key2, "v11", "v22", "v33");
        ops.unionAndStore(key, key2, "temp2");
        //SCAN
        ops.add(key, "v1", "v2", "v3");
        ScanOptions options = ScanOptions.scanOptions().build();
        Cursor<String> scan = ops.scan(key, options);

        while (scan.hasNext()) {
            System.out.println(scan.next());
        }
    }

    /**
     * ZSET 操作
     */
    @Test
    public void redisZsetTest() {
        String key = "redisZsetTest";
        String key2 = "redisZsetTest2";
        ZSetOperations<String, String> ops = clusterTemplate.opsForZSet();
        //ZADD
        ops.add(key, "v1", 1d);
        ops.add(key, "v2", 2d);
        ops.add(key, "v3", 3d);
        ops.add(key, "v4", 4d);
        ops.add(key2, "v1", 1d);
        ops.add(key2, "v3", 3d);
        ops.add(key2, "v5", 5d);
        //ZCARD
        ops.zCard(key);
        //ZCOUNT
        ops.count(key, 1d, 2d);
        //ZINCRBY
        ops.incrementScore(key, "v4", 1.5d);
        //ZLEXCOUNT 字母序
        //ZRANGE 位置
        ops.range(key, 1, 3);
        //ZRANGEBYLEX 字母序
        ops.rangeByLex(key, RedisZSetCommands.Range.range().gt("v1").lte("v2"));
        //ZRANGEBYSCORE  分数
        ops.rangeByScore(key, 1d, 4d);
        //ZRANK 排名
        ops.rank(key, "v1");
        //ZREM
        ops.remove(key, "v1");
        //ZREMRANGEBYLEX
        //ZREMRANGEBYSCORE
        ops.removeRangeByScore(key, 2d, 3d);
        //ZREVRANGE
        ops.reverseRange(key, 0, -1);
        //ZREVRANGEBYSCORE
        ops.removeRangeByScore(key, 1d, 5d);
        //ZREVRANK
        ops.reverseRank(key, "v1");
        //ZSCORE
        ops.score(key, "v1");
        //ZZSCAN
        ops.scan(key, ScanOptions.scanOptions().build());
    }

    /**
     * 基数统计结构
     */
    @Test
    public void redisHyperLogLogTest() {
        HyperLogLogOperations<String, String> ops = clusterTemplate.opsForHyperLogLog();
        String key = "redisHyperLogLogTestKey";
        String key2 = "redisHyperLogLogTestKey2";
        ops.add(key, "asd");
        ops.add(key2, "asdasd");
        ops.size(key);
        ops.union("redisHyperLogLogTestTemp", key, key2);
    }

    /**
     * 发布
     */
    @Test
    public void redisPublishTest() {
        String channle = "testChannel";
        clusterTemplate.execute((RedisCallback<Object>) connection -> {
            connection.publish(channle.getBytes(), "message1".getBytes());
            return null;
        });

    }

    /**
     * 订阅
     */
    @Test
    public void redisSubscribeTest() {
        String channle = "testChannel";
        clusterTemplate.execute((RedisCallback<Object>) connection -> {
            connection.subscribe((message, pattern) -> {
                System.out.println(new String(message.getBody()));
            }, channle.getBytes());
            return null;
        });
    }


    /**
     * 事务 集群不支持
     * - 批量操作在发送 EXEC 命令前被放入队列缓存。
     * - 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
     * - 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。
     */
    @Test
    public void redisTransactionTest() {
        clusterTemplate.setEnableTransactionSupport(true);
        //开启事务
        clusterTemplate.multi();
        redisKeyCommandTest();
        redisStringCommandTest();
        //提交事务
        clusterTemplate.exec();
        //取消事务
        //clusterTemplate.discard();
        //监视key
        clusterTemplate.watch("watchKey");
        //取消监视
        clusterTemplate.unwatch();
    }

    /**
     * 脚本
     * 集群不支持脚本
     */
    @Test
    public void redisScriptTest() {
        String lua = "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}";
        DefaultRedisScript<List> script = new DefaultRedisScript<>(lua, List.class);

        String sha1 = script.getSha1();
        System.out.println(sha1);
        System.out.println(clusterTemplate.execute(script, Arrays.asList("key1", "key2"), "arg1", "arg2"));
    }

    private String getRandom() {
        return String.valueOf(Math.random());
    }

    private Map<String, String> getMap() {
        Map<String, String> map = new HashMap<>();
        map.put("a", "1");
        map.put("b", "2");
        map.put("c", "3");
        map.put("d", "4");
        return map;
    }

    @Test
    public void test() {
        clusterTemplate.opsForZSet().intersectAndStore("key1", "key2", "zSetTempKey");

    }

    /**
     * 不支持
     */
    @Test
    public void testTransaction() {
        // 事务
        clusterTemplate.multi();
        redisKeyCommandTest();
        redisStringCommandTest();
        clusterTemplate.exec();
    }

    /**
     * 不支持
     */
    @Test
    public void testMove() {
        clusterTemplate.opsForValue().set("a", "1");
        //move
        clusterTemplate.move("a", 1);
    }

    /**
     * 不支持
     */
    @Test
    public void testScan() {
        //scan
        clusterTemplate.execute((RedisCallback<Object>) connection -> {
            Cursor<byte[]> scan = connection.scan(ScanOptions.scanOptions().build());
            while (scan.hasNext()) {
                System.out.println(scan.next());
            }
            return null;
        });
    }

    /**
     * 不支持
     */
    @Test
    public void testHypLogLogUnion() {
        //hyploglog union
        clusterTemplate.opsForHyperLogLog().union("redisHyperLogLogTestTemp", "key", "key2");
    }

    /**
     * 不支持
     */
    @Test
    public void testZsetInner() {
        //ZINTERSTORE 交集分数相加
        clusterTemplate.opsForZSet().intersectAndStore("key1", "key2", "zSetTempKey");
    }

    /**
     * 不支持
     */
    @Test
    public void testZSetunion() {
        //ZUNIONSTORE
        clusterTemplate.opsForZSet().unionAndStore("key", "key2", "zSetTempKey2");
    }


}

集群不支持的命令

  • 事务
  • pipeline
  • 脚本
  • move
  • scan
  • 多key操作
    • HypLogLogUnion
    • ZINTERSTORE
    • ZUNIONSTORE

性能测试

  • 不再建议使用 MyRedisClient
  • 监控 redis
    top -p $(pgrep -f -d, redis)

谨慎使用的命令

  • keys *
  • SMEMBERS

扩展

持久化 与Redis的rehash

https://blog.csdn.net/a600423444/article/details/8944601

https://blog.csdn.net/jsjwk/article/details/7964108

关于 缓存穿透、缓存雪崩、缓存更新、缓存预热、缓存降级

传送门

  • 分布式锁

public class RedisLockUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(RedisLockUtil.class);
    private static final String UNLOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

    /**
     * @Description 获取锁并设置失效时间(毫秒),返回锁uuid,null表示获取锁失败
     * @Author shenwei
     * @Date 2018/6/26 18:24
     */
    public static String tryLock(String key, long milliseconds) {
        return tryLock(key, milliseconds, getUUID());
    }

    /**
     * @Description 获取锁并设置失效时间(毫秒),返回锁uuid,null表示获取锁失败
     * @Author shenwei
     * @Date 2018/8/15 14:05
     */
    public static String tryLock(String key, long milliseconds, String value) {
        if (StringUtils.isBlank(key) || StringUtils.isBlank(value)) {
            return null;
        }
        boolean isLock = false;
        try {
            isLock = MyRedisClient.getClient().set(key, value, "nx", "px", milliseconds) != null;
        } catch (Exception e) {
            LOGGER.error("tryLock fail !", e);
        }
        if (isLock) {
            return value;
        } else {
            return null;
        }
    }

    /**
     * @Description 释放锁(私有锁),需要传入获取锁时拿到的uuid,由此操作释放的锁,则返回true,否则返回false
     * @Author shenwei
     * @Date 2018/6/26 18:26
     */
    public static boolean unlock(String key, String uuid) {
        if (StringUtils.isBlank(key) || StringUtils.isBlank(uuid)) {
            return false;
        }
        List<String> params = new ArrayList<>();
        params.add(uuid);
        try {
            Long result = (Long) MyRedisClient.getClient().eval(UNLOCK_SCRIPT, key, params);
            return result != null && result == 1;
        } catch (Exception e) {
            LOGGER.error("unlock fail !", e);
            return false;
        }
    }

    /**
     * @Description 生成全局唯一UUID
     * @Author shenwei
     * @Date 2018/6/26 18:22
     */
    private static String getUUID() {
        return UUID.randomUUID().toString();
    }

}

集群的其他实现方式

(1)Twitter开发的twemproxy

(2)豌豆荚开发的codis

为什么哈希槽的数量固定为16384


由于使用CRC16算法,该算法可以产生2^16-1=65535个值,可是为什么哈希槽的数量设置成了16384?

        Normal heartbeat packets carry the full configuration of a node, that can be replaced in an idempotent way with the old in order to update an old config. This means they contain the slots configuration for a node, in raw form, that uses 2k of space with 16k slots, but would use a prohibitive 8k of space using 65k slots.
        At the same time it is unlikely that Redis Cluster would scale to more than 1000 mater nodes because of other design tradeoffs.

    So 16k was in the right range to ensure enough slots per master with a max of 1000 maters, but a small enough number to propagate the slot configuration as a raw bitmap easily. Note that in small clusters the bitmap would be hard to compress because when N is small the bitmap would have slots/N bits set that is a large percentage of bits set.

总结一下:
1、redis的一个节点的心跳信息中需要携带该节点的所有配置信息,而16K大小的槽数量所需要耗费的内存为2K,但如果使用65K个槽,这部分空间将达到8K,心跳信息就会很庞大。

2、Redis集群中主节点的数量基本不可能超过1000个。

3、Redis主节点的配置信息中,它所负责的哈希槽是通过一张bitmap的形式来保存的,在传输过程中,会对bitmap进行压缩,但是如果bitmap的填充率slots / N很高的话,bitmap的压缩率就很低,所以N表示节点数,如果节点数很少,而哈希槽数量很多的话,bitmap的压缩率就很低。而16K个槽当主节点为1000的时候,是刚好比较合理的,既保证了每个节点有足够的哈希槽,又可以很好的利用bitmap。

4、选取了16384是因为crc16会输出16bit的结果,可以看作是一个分布在0-2^16-1之间的数,redis的作者测试发现这个数对2^14求模的会将key在0-2^14-1之间分布得很均匀,因此选了这个值。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redis相关的面试题涵盖了以下几个方面: 1. Redis的基本概念和特性:面试官可能会问到你对Redis的理解和熟悉程度。你可以回答Redis是一个开源的内存键值存储系统,具有高性能和持久化能力。它支持多种数据结构,如字符串、哈希表、列表、集合和有序集合等。 2. Redis的数据持久化方式:Redis提供了两种方式来将数据持久化到磁盘上,分别是RDB(Redis Database)和AOF(Append Only File)。RDB是一种快照方式,可以将数据以二进制形式保存到硬盘上,而AOF则是将每个写操作追加到文件末尾。你可以解释一下这两种方式的优缺点,并说明在不同场景下应该选择哪种方式。 3. Redis的线程模型:在Redis 6.0之前,Redis是单线程的。而在Redis 6.0之后开始支持多线程。Redis内部使用基于epoll的多路复用来处理网络IO,而执行命令的核心模块仍然是单线程的。你可以简要介绍一下Redis的线程模型以及引入多线程的原因。 4. Redis的扩展模块:Redis支持通过扩展模块来增加额外的功能。例如,BloomFilter、RedisSearch和Redis-ML等扩展模块可以用于实现不同的功能需求。你可以提到一些常用的Redis扩展模块,并解释一下它们的作用和用途。 总结起来,面试中关于Redis的问题主要包括对Redis的基本概念和特性的理解、数据持久化方式、线程模型以及扩展模块的使用等方面。通过对这些问题的了解和回答,可以展示出你对Redis的熟悉程度和实际应用能力。同时,你还可以结合自己的经验和实际项目,给出一些实际的应用场景和解决方案,从而更好地回答面试官的问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [面试 Redis 没底?这 40 道面试题让你不再慌(附答案)](https://blog.csdn.net/xmt1139057136/article/details/115423283)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [redis面试题总结(附答案)](https://blog.csdn.net/guorui_java/article/details/117194603)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值