标签(空格分隔): 分享
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个节点,实现的复杂度低。
略
# 启动
/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 INFO 打印集群的信息
CLUSTER NODES 列出集群当前已知的所有节点(node),以及这些节点的相关信息。
CLUSTER MEET <ip> <port> 将 ip 和 port 所指定的节点添加到集群当中,让它成为集群的一份子。
CLUSTER FORGET <node_id> 从集群中移除 node_id 指定的节点。
CLUSTER REPLICATE <node_id> 将当前节点设置为 node_id 指定的节点的从节点。
CLUSTER SAVECONFIG 将节点的配置文件保存到硬盘里面。
CLUSTER ADDSLOTS [slot ...] 将一个或多个槽(slot)指派(assign)给当前节点。
CLUSTER DELSLOTS [slot ...] 移除一个或多个槽对当前节点的指派。
CLUSTER FLUSHSLOTS 移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点。
CLUSTER SETSLOT NODE 将槽 slot 指派给 node_id 指定的节点,如果槽已经指派给另一个节点,那么先让另一个节点删除该槽>,然后再进行指派。
CLUSTER SETSLOT MIGRATING 将本节点的槽 slot 迁移到 node_id 指定的节点中。
CLUSTER SETSLOT IMPORTING 从 node_id 指定的节点中导入槽 slot 到本节点。
CLUSTER SETSLOT STABLE 取消对槽 slot 的导入(import)或者迁移(migrate)。
//键 (key)
CLUSTER KEYSLOT 计算键 key 应该被放置在哪个槽上。
CLUSTER COUNTKEYSINSLOT 返回槽 slot 目前包含的键值对数量。
CLUSTER GETKEYSINSLOT 返回 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
与Mysql 表的映射关系
例子: 用户表 user , 转换为key-value存储
userid | username | passworde | |
---|---|---|---|
9 | Lisi | 1111111 | [email protected] |
set user:userid:9:username lisi
set user:userid:9:password 111111
set user:userid:9:email [email protected]
keys user:userid:9[username|password|email]
<context:property-placeholder location="classpath:redis.properties"/>
<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.propertiesvalue>
property>
bean>
<bean id="redisClusterTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<property name="connectionFactory" ref="jedisClusterConnectionFactory" />
bean>
@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 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 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 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 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 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 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 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 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
top -p $(pgrep -f -d, redis)
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 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
由于使用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之间分布得很均匀,因此选了这个值。