目录
本文记录了redis学习过程中一些笔记以及redis学习的线路,从入门->进阶->高级->实践->放弃。好的记性不如烂的笔记,健忘的时候瞄两眼,加深理解。
redis特性
- 内存数据库,读写速度快. 读:81000/s, 写:110000/s;
- 支持持久化操作:可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用;
- 支持数据备份:master-slave模式的数据备份;
- 支持事务(MULTI /EXEC):对几个操作合并后的原子性执行;
- 支持多种数据类型:String,list,hash,set,sortedset;
6 所有操作都是原子性的-底层采用了单线程模型;
常用数据类型及其命令
在redis内部所支持的操作命令都存储在 struct redisCommand redisCommandTable[];
string:
strlen/append/setrange/getrange
string 是可以修改的,是动态字符串(Simple Dynamic String 简称 SDS)他的内部结构更像是一个 ArrayList,维护一个字节数组并预分配冗余空间以减少内存的频繁分配.当字符串的长度小于 1MB时,每次扩容都是加倍现有的空间,如果字符串长度超过 1MB 时,每次扩容时只会扩展 1MB 的空间.
ps:字符串长度为最大长度 512MB.
list:
lpush/rpush/lpop/rpop/linsert/lset/ltrim/lindex
hash:
hset/hmset/hget/hmget/hincrby/hkeys/hvals/hgetall/
set:
SADD/SISMEMBER/SCARD(返回个数)/SPOP(随机删除N个元素)/SRANDMEMBER(随机返回个N个元素)/SINTER/SUNION/SDIFF/
sortedset:
zadd/zrange/zrevrange/zrank/zrevrank/zcount返回集合中给定区间的数量/zcard返回集合中所有元素的个数/zremrangebyscore删除集合中在给定排序区间的元素
使用场景
https://www.cnblogs.com/markhe/articles/5689102.html
分布式锁
static const std::string LOCK_SCRIPT=“if redis.call(“GET”,KEYS[1]) == ARGV[1] then\n”
" return “”\n"
“else\n”
" return redis.call( “SET”, KEYS[1], ARGV[1], “EX”, ARGV[2], “NX”)\n"
“end”;
static const std::string UNLOCK_SCRIPT = "if redis.call(\"GET\",KEYS[1]) == ARGV[1] then\n" \
" return redis.call(\"DEL\",KEYS[1])\n" \
"else\n" \
" return 0\n" \
"end";
** 统计用户任意时间窗口内的登陆次数**
可以使用bitmap实现用户在任意时间窗口内的登录次数,下面以用户在一年内有多少天登录了系统。
100 为该年的第100天,1表示登录了系统。
setbit user 100 1
setbit user 200 1
setbit user 364 1
bitcount user 0 -1
输出3 表示一年内有三天登录了系统
strlen user
46 表示该user在redis中占用的字节数.
统计任意时间窗口内活跃的用户数
10000 和 10001 为用户id
setbit 20200401 10000 1
setbit 20200401 10001 1
setbit 20200402 10001 1
bitop or res 20200401 20200402
bitcout res 0 -1
输出为2
取最新N个数据的操作
lpush key val
//保持链表只有N位
ltrim key 0 N-1 (都是下标,下标从0开始)
//获得前N个最新数据
lrange key 0 N-1
消息队列
使用list可以构建队列系统,使用sorted set甚至可以构建有优先级的队列系统。
//生产者
rpush msg_queue message
//消费者
lpop msg_queue
blpop msg_queue seconds
微博和微信公众号消息流
假如我关注了Mactalk ,晓说
1) Mactalk 发微博 消息ID为10000
LPUSH msg:myid 10000
2)晓说发微博 10001
LPUSH msg:myid 10001
3)查看最新微博消息
LRANGE msg:myid 0 10
电商购物车
Hash
1) 以用户ID为key
2) 以商品ID为filed
3) 以商品数量为val
微信抽奖小程序
SET
1) 点击参与抽奖
SADD key USERID
2)查看抽奖用户
SMEMBERS key
3)抽取count名中奖者
SRANDMEMBER key count/SPOP key count
微信微博点赞,收藏,标签
SET
1) 点赞
SADD like:msgid userid
2)取消点赞
SREM like:msgid
3)检查用户是否已经点赞过
SISMEMBER like:msgid USERID
4)获取点赞用户列表
SMEMBERS like:msgid
5)获取点赞用户数
SCARD like:msgid
集合操作实现微博微信关注模型
共同关注:SINTER I A
可能认识的人:SDIFF A I
我关注的人也关注他: SISMEMBER C a
集合操作实现商品品牌筛选
排行榜应用,取TOP N操作
//初始化操作
zadd login_times 1 a
zadd login_times 1 b
zadd login_times 1 c
当点击次数增加时,对该应用次数自增1
ZINCRBY login_times 1 c
//逆序排列取得排名前N的应用
ZREVRANGE login_times 0 3 WITHSCORES
//获取某应用的排行
ZRANK login_times a
Pub/Sub构建实时消息系统
[默认连接个数]
maxclients : CONFIG_DEFAULT_MAX_CLIENTS 10000
在创建 epoll_fd aeEventLoop 时会保留的 32 + 96(128) 个fd ,所以总的连接数可以为 10128
[分布式锁]
实现锁的锁需要注意的细节
- 互斥性
- 可重入性
- 只能释放自己持有的锁
static const std::string LOCK_SCRIPT=“if redis.call(“GET”,KEYS[1]) == ARGV[1] then\n”
" return “”\n"
“else\n”
" return redis.call( “SET”, KEYS[1], ARGV[1], “EX”, ARGV[2], “NX”)\n"
“end”;
static const std::string UNLOCK_SCRIPT = “if redis.call(“GET”,KEYS[1]) == ARGV[1] then\n”
" return redis.call(“DEL”,KEYS[1])\n"
“else\n”
" return 0\n"
“end”;
static const std::string REFRESH_LOCK_SCRIPT = “if redis.call(“GET”,KEYS[1]) == ARGV[1] then\n”
" return redis.call(“SET”,KEYS[1], ARGV[1], “EX”, ARGV[2])\n"
“else\n”
" return “”\n"
“end”;
【限流】
local key1 = KEYS[1]
local times = ARGV[1]
local expire = ARGV[2]
local val = redis.call(“incr”, key1)
local ttl = redis.call(“ttl”, key1)
if val == 1 then
redis.call(“expire”, key1, tonumber(expire))
else
if ttl == -1 then
redis.call(“expire”, key1, tonumber(expire))
end
end
if val > tonumber(times) then
return 0
end
return 1
Redis客户端和连接池
以JAVA客户端jedis为例:
添加依赖:
‘redis.clients:jedis:2.9.0’
== 客户端不是线程安全 ==
Jedis jedis = new Jedis("192.168.1.8", 6379);
jedis.set("name", "Tom");
System.out.println(jedis.get("name"));
jedis.close(); ==记得关闭资源,以免造成 fd 未关闭 ==
== 连接池 ==
为了避免频繁的创建和关闭redis的连接,程序中客户端建议使用redis 连接池。
JedisPoolConfig poolConfig = new JedisPoolConfig();
//连接池中的最大空闲连接数
poolConfig.setMaxIdle(5);
//连接池中的最大连接数
poolConfig.setMaxTotal(8);
//从连接池中获取连接资源的最大阻塞等待时间(使用负值表示没有限制) - 默认为无限等待
poolConfig.setMaxWaitMillis(500);
//创建一连接池对象
JedisPool pool = new JedisPool(poolConfig, "192.168.1.8", 6379);
Jedis jedis = pool.getResource();
jedis.set(Thread.currentThread().getName(), Thread.currentThread().getName());
jedis.close();//把连接资源归还到连接池
redis与spring boot结合
1 在 application.properties 文件中设置redis连接相关属性
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=192.168.1.8
# Redis服务器连接端口
spring.redis.port=6379
# 连接超时时间(毫秒)
spring.redis.timeout=300
# Redis服务器连接密码(默认为空)
#spring.redis.password=123456
#连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=8
# 从连接池中获取连接资源的最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=0
2 创建redis配置类
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 配置连接工厂
template.setConnectionFactory(factory);
//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSeial.setObjectMapper(om);
// 值采用json序列化
template.setValueSerializer(jacksonSeial);
//使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
// 设置hash key 和value序列化模式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(jacksonSeial);
template.afterPropertiesSet();
System.out.println("RedisConfig::redisTemplate()");
return template;
}
}
3 redis 工具类
@Component
public class RedisUtil {
@Autowired
private RedisTemplate<String, Object> objectRedisTemplate;
/**
* 普通缓存获取
* @param key 键
* @return 值
*/
public Object get(String key){
return key==null?null: objectRedisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key,Object value) {
try {
objectRedisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
4 创建实体类(可序列化)
public class UserEntity implements Serializable {
private Long id;
private String guid;
private String name;
private String age;
private Date createTime;
//添加get/set方法
}
5 测试
@SpringBootTest
@RunWith(SpringRunner.class)
public class JedisTest {
@Autowired
RedisUtil redisUtil;
@Test
public void testJedis() {
UserEntity userEntity =new UserEntity();
userEntity.setId(Long.valueOf(1));
userEntity.setGuid(String.valueOf(1));
userEntity.setName("zhangsan");
userEntity.setAge(String.valueOf(20));
userEntity.setCreateTime(new Date());
redisUtil.set("zhangsan",userEntity);
userEntity = (UserEntity) redisUtil.get("zhangsan");
System.out.println(userEntity);
}
}
持久化
** redis持久化方式分为两种:快照(RDB 文件) 和 追加式文件(AOF 文件) **
1 RDB持久化方式能够在指定的时间间隔对数据进行快照存储;
2 AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据.AOF命令以redis协议追加保 存每次写的操作到文件末尾;
3 Redis的持久化是可以禁用的,就是说你可以让数据的生命周期只存在于服务器的运行时间里;
4 两种方式的持久化是可以同时存在的,但是当Redis重启时,AOF文件会被优先用于重建数据
** RDB **
*** 优点 ***
1 RDB文件是紧凑的单文件,它保存了某个时间点的Redis数据,很适合用于做数据备份。
2 RDB的性能很好,需要进行持久化时,主进程会fork一个子进程出来,然后把持久化的工作交给子进程,自己不会有相关的I/O操作,所以RDB持久化方式可以最大化redis的性能;
3 比起AOF,在数据量比较大的情况下,RDB的启动速度更快。
***缺点 ***
1 RDB容易造成数据的丢失。假设每5分钟保存一次快照,如果Redis因为某些原因不能正常工作,那么从上次产生快照到Redis出现问题这段时间的数据就会丢失了。
2 RDB 需要经常fork子进程来保存数据集到硬盘上,当数据集比较大的时候,fork的过程是非常耗时的,可能会导致Redis在一些毫秒级内不能响应客户端的请求.AOF也需要fork,但是你可以调节重写日志文件的频率来提高数据集的耐久度.
** 工作原理**
当 Redis 需要保存 dump.rdb 文件时, 服务器执行以下操作:
1 Redis 调用forks. 同时拥有父进程和子进程。
2 子进程将数据集写入到一个临时 RDB 文件中。
3 当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件。
这种工作方式使得 Redis 可以从写时复制(copy-on-write)机制中获益。
** 开启 RDB**
#RDB文件名,默认为dump.rdb
dbfilename dump.rdb
#文件存放的目录,RDB/AOF都存放在此目录下。默认为当前工作目录。
dir ./
#你可以配置保存点,使Redis如果在每N秒后数据发生了M次改变就保存快照文件。
#比如说, 以下设置会让 Redis 在满足“ 60 秒内有至少有 1000 个键被改动”这一条件时, 自动保存一次数据集:
save 60 1000
** 禁用 RDB**
save “”
或者注释掉所以的 save 选项
** 手动生成快照 **
Redis提供了两个命令用于手动生成快照:
SAVE :
命令会使用同步的方式生成RDB快照文件,这意味着在这个过程中会阻塞所有其他客户端的请求。因此不建议在生产环境使用这个命令
BGSAVE:
使用后台的方式保存RDB文件,调用此命令后,会立刻返回OK返回码。Redis会产生一个子进程进行处理并立刻恢复对客户端的服务。在客户端我们可以使用LASTSAVE命令查看操作是否成功。
** AOF **
快照并不是很可靠。如果你的电脑突然宕机了,或者电源断了,又或者不小心杀掉了进程,那么最新的数据就会丢失。而AOF文件则提供了一种更为可靠的持久化方式。每当Redis接收到写操作命令时,就会把命令追加到AOF文件里,当你重启Redis时,AOF里的命令会被重新执行一次,重建数据。
*** 优点 ***
1.比RDB可靠。你可以制定不同的fsync策略:不进行fsync、每秒fsync一次和每次进行fsync。默认是每秒fsync一次。这意味着你最多丢失一秒钟的数据。
2. AOF是一个只进行追加的日志文件。就算是遇到突然停电的情况,也不会出现日志的定位或者损坏问题。甚至如果因为某些原因(例如磁盘满了)命令只写了一半到日志文件里,我们也可以用redis-check-aof这个工具很简单的进行修复;
3. 当AOF文件太大时,Redis会自动在后台进行重写。重写很安全,因为重写是在一个新的文件上进行,同时Redis会继续往旧的文件追加数据。新文件上会写入能重建当前数据集的最小操作命令的集合。当新文件重写完,Redis会把新旧文件进行切换,然后开始把数据写到新文件上。
4. AOF把操作命令以简单易懂的格式一条接一条的保存在文件里,很容易导出来用于恢复数据;
***缺点 ***
1.对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积
6. 在某些fsync策略下,AOF的速度会比RDB慢。通常fsync设置为每秒一次就能获得比较高的性能,而在禁止fsync的情况下速度可以达到RDB的水平。
. ** 打开 AOF **
appendonly yes
#fsync的频率
appendfsync everysec
** 日志重写 **
1 redis执行fork,产生一个子进程;
7. 子进程把重建当前数据集的最小操作命令集合 写入到一个临时文件;
8. 对于新的写操作,redis会把它写入一个缓存里,同时写入现有的aof文件里。这样即使在重写的中途发生停机,现有的 AOF 文件也还是安全的 ;
9. 当子进程完成文件的重写后,会向主进程发送信号,然后主进程会将缓存中的数据写入新的AOF文件,最后用这个新的文件替换旧的文件。
我们可以通过配置设置日志重写的条件:
#Redis会记住自从上一次重写后AOF文件的大小(如果自Redis启动后还没重写过,则记住启动时使用的AOF文件的大小)。
#如果当前的文件大小比起记住的那个大小超过指定的百分比,则会触发重写。
#同时需要设置一个文件大小最小值,只有大于这个值文件才会重写,以防文件很小,但是已经达到百分比的情况。
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
要禁用自动的日志重写功能,我们可以把百分比设置为0:
auto-aof-rewrite-percentage 0
事务支持
Redis的事务支持主要是通过 multi 和exec 操作来实现的:multi表示事务开启,exec表示执行multi到exec之间的语句块。
Redis收到 multi 语句后,会将其后的命令添加到命令队列中去,直到遇到exec时一并执行且不会被中断。
在multi开启的事务过程中,如果其它线程也修改了该key的值,那么,multi事务依旧会被执行,且multi中设定的该key的值会覆盖掉其它线程设定的值
客户A ====== 客户B
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set name Jack ====== 此时另外一个客户端设置了该key值 127.0.0.1:6379> set name Andy
QUEUED ====== 127.0.0.1:6379> get name
127.0.0.1:6379> set age 12 ====== " Andy"
QUEUED
127.0.0.1:6379> get name
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) "Jack" 可以看出A覆盖了B设置的name值
要解决这个问题,就必须在事务开启前,添加watch关键字对一个或多个待更新的key进行监视。一旦key的值被其他线程所修改,则事务将不会执行。watch关键字的本质是一个乐观锁.
客户A ======= 客户B
127.0.0.1:6379> WATCH name age
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set name Jim ======= 127.0.0.1:6379> set name Lily
QUEUED ======= OK
127.0.0.1:6379> set age 23 ======= 127.0.0.1:6379> get name
QUEUED ======= "Lily"
127.0.0.1:6379> exec
(nil) //可以看出事务执行失败
127.0.0.1:6379> get name
"Lily"
127.0.0.1:6379> get age
"12"
只要事务中被监听的key被其它线程修改,那整个事务都将不会被执行。原因是服务端在监听到key被修改的事件后,便执行了discard命令,它会丢弃掉该事务中的所有语句块和watch的关键字。
== 在redis事务语句块中,如果有部分语句执行失败,那也不影响其它的语句执行==
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> get name
QUEUED
127.0.0.1:6379> INCR name
QUEUED
127.0.0.1:6379> set name Cat
QUEUED
127.0.0.1:6379> set age 100
QUEUED
127.0.0.1:6379> get name
QUEUED
127.0.0.1:6379> get age
QUEUED
127.0.0.1:6379> EXEC
- “Jim”
- (error) ERR value is not an integer or out of range
- OK
- OK
- “Cat” //其他语句仍然可以正常执行
- “100”
命令错误:如果我们在使用事务时,如果执行命令有错误,则会造成事务无法提交
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set name KEY
QUEUED
127.0.0.1:6379> ZADD1 aa a
(error) ERR unknown command ‘ZADD1’
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
集群不支持事务
127.0.0.1:8001> MULTI
OK
127.0.0.1:8001> set name aaa
(error) MOVED 5798 192.168.1.8:8002
127.0.0.1:8001> set age 11
QUEUED
127.0.0.1:8001> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:8001> get name
(error) MOVED 5798 192.168.1.8:8002
127.0.0.1:8001> get age
(nil)
Redis 哨兵
哨兵模式基于主从复制模式,只是引入了哨兵来监控与自动处理故障。
Redis 的 Sentinel 系统用于管理多个 Redis 服务器(instance), 该系统执行以下三个任务:
监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。
Redis Sentinel 是一个分布式系统, 你可以在一个架构中运行多个 Sentinel 进程(progress), 这些进程使用流言协议(gossip protocols)来接收关于主服务器是否下线的信息, 并使用投票协议(agreement protocols)来决定是否执行自动故障迁移, 以及选择哪个从服务器作为新的主服务器。
你无须为运行的每个 Sentinel 分别设置其他 Sentinel 的地址, 因为== Sentinel 可以通过发布与订阅功能来自动发现正在监视相同主服务器的其他 Sentinel , 这一功能是通过向频道 sentinel:hello 发送信息来实现的==。
在默认情况下, Sentinel 使用 TCP 端口 26379 (普通 Redis 服务器使用的是 6379 )。
下面在单机上实践 redis 哨兵:
1 哨兵是基于主从复制,所以得配置好 redis 主从服务器:
master: 192.168.1.8 6379
slave 1: 192.168.1.8 6377
slave 2: 192.168.1.8 6378
2 Redis Sentinel 实例:
Sentinel 1 192.168.1.8 26379
Sentinel 2 192.168.1.8 26378
Sentinel 3 192.168.1.8 26377
哨兵的具体配置:
vim /usr/local/redis-cluster/26379/conf/redis.conf
bind 192.168.1.8
port 26379
daemonize yes
logfile /usr/local/redis-cluster/26379/logs/26379.log
dir /usr/local/redis-cluster/26379/data
#mymaster 主节点别名.主节点IP 和 端口. 由两个sentine节点同意,判断主节点失败
sentinel monitor mymaster 192.168.1.8 6379 2
#认为服务器已经断线所需的毫秒数
sentinel down-after-milliseconds mymaster 3000
#发生故障转移时,最多可以有多少个从服务器同时对新的主服务器进行同步,1轮询发起复制
sentinel parallel-syncs mymaster 1
#故障转移超时时间
sentinel failover-timeout mymaster 18000
其他两个只要修改 端口号就可以了
sed -i “s#6379#6378#g” redis.conf
sed -i “s#6379#6377#g” redis.conf
3 启动redis主从服务器
redis-server /usr/local/redis-cluster/6379/redis.conf
redis-server /usr/local/redis-cluster/6378/redis.conf
redis-cli -h 192.168.1.8 -p 6378
192.168.1.8:6378> SLAVEOF 192.168.1.8 6379
redis-server /usr/local/redis-cluster/6377/redis.conf
redis-cli -h 192.168.1.8 -p 6377
192.168.1.8:6378> SLAVEOF 192.168.1.8 6379
4 启动哨兵
redis-sintinel /usr/local/redis-cluster/26379/conf/redis.conf
redis-sintinel /usr/local/redis-cluster/26378/conf/redis.conf
redis-sintinel /usr/local/redis-cluster/26377/conf/redis.conf
当哨兵启动后,配置会发生变化;
1: sentinel parallel-syncs 配置被去掉
2:添加了配置纪元相关的参数:
sentinel config-epoch mymaster 2
Generated by CONFIG REWRITE
sentinel leader-epoch mymaster 0
sentinel known-slave mymaster 192.168.1.8 6378
sentinel known-slave mymaster 192.168.1.8 6377
sentinel known-sentinel mymaster 192.168.1.8 26378 374db32babcc186f35020f0b41144b2d3587897c
sentinel known-sentinel mymaster 192.168.1.8 26379 708e388c35df02d44345e4f7f07397edd32c6464
sentinel current-epoch 2
优点:
哨兵模式基于主从复制模式,所以主从复制模式有的优点,哨兵模式也有
哨兵模式下,master挂掉可以自动进行切换,系统可用性更高
缺点:
同样也继承了主从模式难以在线扩容的缺点,Redis的容量受限于单机配置
需要额外的资源来启动sentinel进程,实现相对复杂一点,同时slave节点作为备份节点不提供服务
集群
Redis 集群是一个提供在多个Redis节点间共享数据的程序集。哨兵模式解决了主从复制不能自动故障转移,达不到高可用的问题,但还是存在难以在线扩容,Redis容量受限于单机配置的问题。Cluster模式实现了Redis的分布式存储,即每台节点存储不同的内容,来解决在线扩容的问题。
Redis集群并不支持处理多个keys的命令,因为这需要在不同的节点间移动数据,从而达不到像Redis那样的性能,在高负载的情况下可能会导致不可预料的错误.
Redis 集群通过分区来提供一定程度的可用性,在实际环境中当某个节点宕机或者不可达的情况下继续处理命令. Redis 集群的优势:
自动分割数据到不同的节点上;
整个集群的部分节点失败或者不可达的情况下能够继续处理命令;
Redis 集群采用Gossip 协议,节点间彼此不断交换信息,一段时间后所有的节点都会知道集群完整信息。
Gossip 协议职责是信息交换,信息交换的载体就是节点间彼此发送Gossip消息(PING,PONG, MEET, FAIL)
集群中的每一个节点都会单独开辟一个Tcp 通道,用于节点之间彼此通信,通信端口在基础端口上加10000;
Note:
127.0.0.1:8005> slaveof 192.168.0.18 8002
(error) ERR SLAVEOF not allowed in cluster mode.
创建集群
mkdir -p /usr/local/redis/etc
mkdir -p /usr/local/redis/bin
https://yq.aliyun.com/articles/704603?spm=a2c4e.11155472.0.0.b3972ce9s6SOoZ //设置yum 源
apt-get -y install ruby
apt-get -y install rubygems
gem install redis --version 5.0.5
./redis-trib.rb create --replicas 1 192.168.1.5:8001 192.168.1.5:8002 192.168.1.5:8003 192.168.1.5:8004 192.168.1.5:8005 192.168.1.5:8006
1 为主/从节点的比值
前面的为主,后面的为从
集群不支持 KEY不存在同一个 SLOT上的MGET操作
6台redis才能建立一起一个集群
优点:
无中心架构,数据按照slot分布在多个节点。
集群中的每个节点都是平等的关系,每个节点都保存各自的数据和整个集群的状态。每个节点都和其他所有节点连接,而且这些连接保持活跃,这样就保证了我们只需要连接集群中的任意一个节点,就可以获取到其他节点的数据。
可线性扩展到1000多个节点,节点可动态添加或删除
能够实现自动故障转移,节点之间通过gossip协议交换状态信息,用投票机制完成slave到master的角色转换
缺点:
节点会因为某些原因发生阻塞(阻塞时间大于 cluster-node-timeout)被判断下线,这种failover是没有必要的
数据通过异步复制,不保证数据的强一致性
slave充当“冷备”,不能缓解读压力
批量操作限制,目前只支持具有相同slot值的key执行批量操作,对mset、mget、sunion等操作支持不友好
key事务操作支持有限,只支持多key在同一节点的事务操作,多key分布不同节点时无法使用事务功能
不支持多数据库空间,单机redis可以支持16个db,集群模式下只能使用一个,即db 0
如何解决哨兵在线扩容,集群不支持批量及事务操作的缺点
上面提到了哨兵模式的在线扩容受限,集群批量操作限制,key事务操作支持有限以及不支持多数据库空间的问题,下面介绍一种新的架构方式,即基于业务类型来访问不同的Redis节点:
在应用中可以通过配置给每种业务指定一个Redis节点。在访问的时候首先获取节点信息,接着按照哨兵模式的方式来访问具体的redis节点。这样具备了哨兵模式和集群模式有点。