1. 基础
从数据结构、操作和持久化方式做了一个简单的总结
1.1 数据结构
string
字符串可以保存三种类型数据:
自增和自减命令:
字符串、整数、浮点数- incr key 自增1
- decr key 自减1
- incrby key 要增加的数字 增加指定的数字
- decrby key 要减去的数字 减去指定的数字
- append key-name value 追加value
- getrange key-name start end 相当于substr对原值没有影响
list
一个列表结构可以有序的存储多个字符串.
指令:- LPUSH/RPUSH 将给定的值推入列表的左/右端
- LRANGE 获取列表在给定范围上的所有值,0 -1可以查看所有
- LINDEX 获取列表在给定位置上的单个元素
[LPOP/RPOP 集合名] 从列表的左/右弹出一个值,并返回被弹出的值(被弹出的值,不存在于列表中)
- ltrim key-name start end 修剪列表,保留start-end的长度
- blpop key-name timout 从非空列表弹出最左侧元素或等待timeout秒阻塞并等待弹出元素 出现
brpop类似blpop
-rpoplpush source-key dest-key 从source-key弹出最右侧元素推入dest-key的最左侧元素
-brpoplpush 可阻塞的 rpoplpush
set
和Java的set相同,唯一无序.
一些简单的命令:- [SADD 集合名 添加的元素] 添加元素
- [SREM 集合名 移除的元素] 移除元素
- [SISMEMBER 集合名 查找的元素] 查找元素
- [SMEMBERS 集合名] 查找所有的元素,数据量多可能会很慢
SINTER、SUNION、SDIFF 交集计算、并集计算和差集计算等等指令
hashmap
可以存储多个键值对之间的映射.
散列存储的值既可以是字符串也可以是数字.
用户同样可以对散列存储的数字执行自增或自减操作
指令:
- hset 集合名 键 值
- hget 集合名 键
- hdel 集合名 键
- hgetall 集合名
zset
特点:- 和散列一样,存储键值对
- 键称为成员(member)
- 值称为分值,必须为浮点数
- 唯一一个既可以根据成员访问元素没有可以根据分值以及分值的排列顺序来访问元素的结构
命令:
- ZADD
- ZRANGE
- ZRANGEBYSCORE
- ZREM
- withscores 末尾追加这个命令,按分值进行排序
1.2 操作
事务
通过watch、multi和exec可以制作一个事务流水线. 减少应用和redis的交互,提高性能.
watch会监控键,如果在执行exec之前表新增、修改、删除的话,那么exec的时候就会返回错误信息,可以重试交易
watch
redis的cas操作.
在watch期间监视的值发生变动,事务失败:
WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXECmulti
原子执行事务的开始点
exec
原子执行事务的结束点
不支持回滚
流水线
使用conn.pipelined()
如果有true这个参数就是使用事务,不写或者false参数就是单纯的流水线.性能会进一步得到提高
过期
1.3 发布与订阅
数据会丢失
数据传输的可靠性,如果客户端在执行订阅操作的过程中断线,断线期间发送的消息会丢失
如果能承担一部分数据的丢失可以使用.
1.4 持久化方式
- 快照
snapshotting
保存某一时间点的所有信息.
适用场景: 快照要时没照完系统就崩溃了,就会丢失最后一次成功快照后的信息,所以在能承受丢失部分信息的时候,才使用这个.
不适用场景: 很大的数据占用很大的内存,创建子进程会造成很长的停顿时间.有些应用无法接受
灵活: 可在dbfilename文件配置输出的文件名,还可配置输出目录
相关指令:
bgsave Redis创建一个子进程后台保存快照.
save 拒绝接受请求,专一的执行快照,所以这个不常用
在配置文件设置 save 60 10000 表示在60秒内有10000个写操作的时候,保存数据,如果有多个配置,满足一个就执行
shutdown 或标准的term指令,停止接受新命令,执行save命令,然后关闭redis
一个Redis对另一个Redis 服务器发起SYNC命令,如果没在执行BGSAVE就执行BGSAVE
- 数据丢失
- 影响redis性能
- 系统故障,数据无法修复
只追加文件
将修改数据库的命令写入 只追加(append-only)文件里面. 只追加写入设置可以设置为
从不同步、每秒同步一次或每写入一个命令就同步一次
Redis提供主从复制功能
append only file
持久化的数据追加到之前的文件的末尾.
三个同步指令:
appendfsync always 同步每个指令,频繁的写指令系统会崩溃
appendfsync everysec 最多丢失一秒的数据
appendfsync no 会丢失不可控的数据,过多的写操作还会降低redis的存储速度
缺点: 持久化后的文件太大了
可对文件进行压缩
压缩需要使用子进程,Redis过大创建子进程的时间会更多,问题和之前相同.
可以灵活的设置压缩文件的指令,超过某大小或超过上一次压缩的大小的几倍,就可以执行压缩命令,提供后台压缩命令
1.5 性能测试
redis-benchmark -c l -q
-c l 使用单机测试
-q 简化测试
注意: 实际的生产环境性能只有测试效果的50%-60%,因为测试不会处理返回结果
2. 高级
2.1 扩展读性能
主从复制
- 从服务器树帮助负担写的压力
- 对传输文件进行压缩,减少传输量
- 挑选正确的数据结构
- 存入前压缩
- 存入后压缩
使用流
2.2 扩展写性能和内存容量
使用redis集群扩展写性能和内存容量,下面是集群的具体介绍
3. redis-cluster
总结了redis集群的特点、配置和实战
3.1 特点
分片
redis集群中有16384个散列槽.
集群至少拥有三个节点:
节点A包含从0到5500的散列槽。
节点B包含从5501到11000的散列槽。
节点C包含从11001到16383的散列槽新增节点,从三个节点分出一部分数据给D
删除节点把节点的数据平均移动给其他节点
主从复制
每个主节点,必须有一个从节点,保证数据高可用.主节点崩溃,从节点替换,从节点上升为主节点.主节点恢复后变为从节点
弱一致性
需要在性能和一致性之间进行权衡。
为性能牺牲一致性
3.2 配置端口
- 服务端口
集群总线端口
- 故障检测
- 配置更新
- 故障转移授权
3.3 实战
同一个客户端,不同配置文件
使用相同的内存
手动故障转移
Redis Cluster使用CLUSTER FAILOVER 命令支持手动故障转移,该命令必须在要故障转移的主站的一个从站中执行。
副本迁移
在Redis群集中,只需使用以下命令,就可以随时使用不同的主服务器重新配置从属服务器进行复制:
CLUSTER REPLICATE
增加/删除新节点
新增节点:
redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000准确指定要使用新副本定位的主控制器:
redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000 --cluster-slave --cluster-master-id xxxxx
要删除从节点,只需使用del-noderedis-cli命令:
redis-cli --cluster reshard 127.0.0.1:7000
写上所有插槽的数量
写上转移的id
写上自己的id
done
redis-cli --cluster del-node 127.0.0.1:7000
无法使用multi
[] threw exception [Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: MULTI is currently not supported in cluster mode.] with root cause
4. redis-sentlnel
Redis Sentinel是Redis的官方高可用性解决方案。总结了sentinel的一些功能和使用方式
4.1 功能
监控
Sentinel会不断检查主实例和从属实例是否按预期工作。
通知
Sentinel可以通过API通知系统管理员,另一台计算机程序,其中一个受监控的Redis实例出现问题。
自动故障转移
如果主服务器未按预期工作,Sentinel可以启动故障转移过程,其中从服务器升级为主服务器,其他其他服务器重新配置为使用新主服务器,并且使用Redis服务器的应用程序通知有关新服务器的地址连接。
配置提供
Sentinel充当客户端服务发现的权限来源:客户端连接到Sentinels,以便询问负责给定服务的当前Redis主服务器的地址。如果发生故障转移,Sentinels将报告新地址。
4.2 实战使用多个sentinel优势
- 减少误报
- 高可用
4.3 使用
修改sentinel.conf
sentinel monitor mymaster 127.0.0.1 6379 2
第一行用于告诉Redis监视一个名为mymaster的主服务器,它位于地址127.0.0.1和端口6379,仲裁数为2.
仲裁数用于检测故障的哨兵数量.最终选举一个sentinel进行故障转移
sentinel down-after-milliseconds mymaster 60000
down-after-milliseconds 对于Sentinel开始认为它已关闭的时间,实例不应该可以到达的时间(无论是不回复我们的PING还是回复错误)都是以毫秒为单位的时间。
sentinel failover-timeout mymaster 180000
失效转移超时时间,投票后的哨兵进行故障转移其他哨兵在此时间后可进行重试的时间
sentinel parallel-syncs mymaster 1
同步数据给从服务器的数量,这个数越大,同步数据的时间越少但是不可用的服务器越多.数量越少同步时间越多,但是可用的服务器越多.
应该将此选项设置为值1来确保一次只能访问一个从站。
使用方式
始终在三个不同的服务器中至少部署三个Sentinels
测试结果:
服务器A拥有主服务M1和哨兵S1,
服务器B拥有从服务R1和哨兵S2.在M1崩溃时可以进行故障转移,但是如果服务器A整体崩溃,只有一个哨兵S2则无法进行故障转移.
增加服务器C哨兵S3. 在服务器A整体崩溃后,也可进行故障转移
解决从服务器丢失数据问题
Redis实例作为主服务器时,如果无法写入至少1个从服务器,将停止接受写入。由于复制是异步的,因此无法实际写入意味着从属设备已断开连接,或者未向我们发送超过指定max-lag秒数的异步确认。
- min-slaves-to-write 1
- min-slaves-max-lag 10
5. Redisson
Redisson是一个Redis的Java客户端,Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。
5.1 分布式锁
可重入锁(Reentrant Lock)
基于Redis的Redisson分布式可重入锁RLock Java 对象实现了java.util.concurrent.locks.Lock接口。同时还提供了异步(Async)、反射式(Reactive)和RxJava2标准的接口。
RLock lock = redisson.getLock("anyLock"); // 最常见的使用方法 lock.lock();
自动解锁的方式:
// 加锁以后10秒钟自动解锁 // 无需调用unlock方法手动解锁 lock.lock(10, TimeUnit.SECONDS); // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁 boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS); if (res) { try { ... } finally { lock.unlock(); } }
公平锁(Fair Lock)
基于Redis的Redisson分布式可重入公平锁也是实现了java.util.concurrent.locks.Lock接口的一种RLock对象。同时还提供了异步(Async)、反射式(Reactive)和RxJava2标准的接口。它保证了当多个Redisson客户端线程同时请求加锁时,优先分配给先发出请求的线程。所有请求线程会在一个队列中排队,当某个线程出现宕机时,Redisson会等待5秒后继续下一个线程,也就是说如果前面有5个线程都处于等待状态,那么后面的线程会等待至少25秒。
RLock fairLock = redisson.getFairLock("anyLock"); // 最常见的使用方法 fairLock.lock();
联锁(MultiLock)
基于Redis的Redisson分布式联锁RedissonMultiLock对象可以将多个RLock对象关联为一个联锁,每个RLock对象实例可以来自于不同的Redisson实例。
RLock lock1 = redissonInstance1.getLock("lock1"); RLock lock2 = redissonInstance2.getLock("lock2"); RLock lock3 = redissonInstance3.getLock("lock3"); RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3); // 同时加锁:lock1 lock2 lock3 // 所有的锁都上锁成功才算成功。 lock.lock(); ... lock.unlock();
红锁(RedLock)
基于Redis的Redisson红锁RedissonRedLock对象实现了Redlock介绍的加锁算法。该对象也可以用来将多个RLock对象关联为一个红锁,每个RLock对象实例可以来自于不同的Redisson实例。
红锁是一个钟更安全的锁.
RLock lock1 = redissonInstance1.getLock("lock1"); RLock lock2 = redissonInstance2.getLock("lock2"); RLock lock3 = redissonInstance3.getLock("lock3"); RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3); // 同时加锁:lock1 lock2 lock3 // 红锁在大部分节点上加锁成功就算成功。 lock.lock(); ... lock.unlock();
独享
独享(相互排斥)。在任意一个时刻,只有一个客户端持有锁。
无死锁
无死锁。即便持有锁的客户端崩溃(crashed)或者网络被分裂(gets partitioned),锁仍然可以被获取。
容错
容错。 只要大部分Redis节点都活着,客户端就可以获取和释放锁.
读写锁(ReadWriteLock)
基于Redis的Redisson分布式可重入读写锁RReadWriteLock Java对象实现了java.util.concurrent.locks.ReadWriteLock接口。其中读锁和写锁都继承了RLock接口。
分布式可重入读写锁允许同时有多个读锁和一个写锁处于加锁状态
RReadWriteLock rwlock = redisson.getReadWriteLock("anyRWLock"); // 最常见的使用方法 rwlock.readLock().lock(); // 或 rwlock.writeLock().lock();
信号量(Semaphore)
基于Redis的Redisson的分布式信号量(Semaphore)Java对象RSemaphore采用了与java.util.concurrent.Semaphore相似的接口和用法。同时还提供了异步(Async)、反射式(Reactive)和RxJava2标准的接口。
RSemaphore semaphore = redisson.getSemaphore("semaphore");
semaphore.acquire();
//或
semaphore.acquireAsync();
semaphore.acquire(23);
semaphore.tryAcquire();
//或
semaphore.tryAcquireAsync();
semaphore.tryAcquire(23, TimeUnit.SECONDS);
//或
semaphore.tryAcquireAsync(23, TimeUnit.SECONDS);
semaphore.release(10);
semaphore.release();
//或
semaphore.releaseAsync();
可过期性信号量(PermitExpirableSemaphore)
基于Redis的Redisson可过期性信号量(PermitExpirableSemaphore)是在RSemaphore对象的基础上,为每个信号增加了一个过期时间。每个信号可以通过独立的ID来辨识,释放时只能通过提交这个ID才能释放
RPermitExpirableSemaphore semaphore = redisson.getPermitExpirableSemaphore("mySemaphore"); String permitId = semaphore.acquire(); // 获取一个信号,有效期只有2秒钟。 String permitId = semaphore.acquire(2, TimeUnit.SECONDS); // ... semaphore.release(permitId);
闭锁(CountDownLatch)
基于Redisson的Redisson分布式闭锁(CountDownLatch)Java对象RCountDownLatch采用了与java.util.concurrent.CountDownLatch相似的接口和用法。