1、Redis 是啥
C语言编写,开源的高性能Key-Value非关系缓存数据库。支持的数据类型有string,list,set,zset,hash。基于内存,速度很快,能达到10w的qps。支持数据持久化到硬盘,和从硬盘数据恢复。
2、Redis为什么这么快
- redis 使用内存存储
- redis 处理数据单线程,避免线程切换开销
- redis 使用非阻塞IO,IO多路复用技术 poll epoll kqueue
- redis 自建数据结构,
3、Redis的IO模型
image.png单线程版的Reactor模型 单线程注册各种socket IO事件(读,写,连接),epoll_wait阻塞到有事件触发,线程根据不同事件类型,触发不同的handler,处理结束后继续阻塞到epoll_wait。
多路复用体现在epoll,可以同时监听大量套接字事件,即单线程处理多个连接。
4、Redis的持久化机制
- RDB 定时进行数据集的时间点快照, 同步save模式和异步bgsave模式,快照可以压缩
save 300 10 //标识300s内,有超过10个key更改,则触发RDB保存
RDB保存过程:
1、redis主进程 fork子进程
2、父进程继续处理新的请求,包含写入。子进程负责将内存数据写入临时文件,(os的父子进程共享物理内存,当有一方有写操作要改变共享页时,会触发copy-on-write写时复制),到父进程进行写操作时,会创建内存页的副本,所以子进程写入的数据是fork时的内存的一个快照
3、子进程写完临时文件,会将RDB文件替换久的,子进程退出
优点:
适合备份和容灾恢复,
恢复速度快,
备份可使用fork子进程方式,父进程不需要进行磁盘IO操作
缺陷:
故障停机后,备份间隙的数据无法找回
数据量过大时,内存吃紧,如果用到虚拟内存,fork会很耗时,redis主进程也会被阻塞
- AOF 记录每次写记录 通过回放记录进行数据恢复 类似binlog
appendonly yes // 开启AOF
#appendfsync always // 每次收到写请求都进行写盘
appendfsync everysec // 每秒写盘一次,是效率和持久化的折中
#appendfsync no //依赖os自己进行刷盘,效率最高
解决文件过大,重写机制:bgrewriteaof
1、redis fork子进程
2、子进程根据内存中的数据快照,生成重建数据库的命令,写入临时文件
3、父进程继续处理client请求,写命令继续往原aof文件写入。同时写命令做缓存,
4、子进程快照重建完,临时文件写完后,通知父进程,父进程把缓存的命令写入到该临时文件
5、父进程替换临时文件重命名
优点:
可设置fsync备份频率,设置为1s一次效率任然很高,所以故障时丢数据少
追加写文件末尾,速度快
重写机制
每次命令的记录,可重放,可恢复指定错误命令前的数据
缺陷:
文件比RDB文件大
写入速度略慢与RDB
5、Redis 单线程相关问题
Redis的核心模块是单线程模型
Redis是基于Reactor IO网络事件处理模型 - 多个套接字,IO多路复用器,文件事件派发器,事件处理器。而redis4.0之前这些都是单线程处理的,所以事件队列也是单线程消费,所以Redis是单线程。
Redis的瓶颈不在cpu,而在内存和网络。
Redis4.0开始使用多线程,如后台删除对象或通过redis实现的阻塞命令
Redis6.0多线程用于网络IO,用于网络IO的读写和协议的解析
6、Redis集群
redis集群支持3中模式
- 主从复制模式
replicaof 127.0.0.1 6379
从设置主
1、从启动,发送sync命令到主
2、主执行bgsave,并缓冲区记录后续执行的命令
3、bgsave完成后,主发送快照.rdb文件给从,从载入快照
4、从载入快照结束后,开始接收写命令,主将缓冲区的命令全写到从。从初始化完成
5、从开始接收主的增量写命令
优点:
支持主从读写分离,缓解主压力
从机也能接受其他从机的备份请求,缓解主压力
同步过程非阻塞,主机任然可以提供读写
缺点:
不具备自动容错和恢复功能,宕机后客户端直接失败,需要重新启动或者客户端手工换地址
主从数据可能不一致
多个slave不能同时启动,不然都向master发送sync,master io会满,引起宕机
不支持在线扩容
- 哨兵模式
#哨兵的配置文件
port 26379
sentinel monitor mymaster 127.0.0.1 6379 1
哨兵的启动命令
redis-sentinel sentinel1.conf
主从复制的基础上,解决自动切换master
哨兵是一个独立的进程,会监听master和各个slave的运行状态,当master宕机后,选出一个slave修改为master,并通过订阅方式通知其他slave修改配置。 哨兵自己也通过集群的方式互相监听,做到高可用。
master故障切换过程:
sentinel监听到master宕机,会先自己标记为主观下线,等待其他哨兵也监听到master宕机,当主观下线的sentinel达到设定值时,哨兵会发起一次投票,failover 切换master,向订阅的slave通知master的切换,从机更改配置,实现客观下线。
sentinel工作模式:
sentinel每秒向所有的master,slave,sentinel发送ping命令,如果最后一次ping成功的时间和当前间隔超过设定值down-after-milliseconds
时,标记为主观下线
如果超过设定数量的哨兵都标记某master为主观下线,则标记为客观下线
如果没到设定数量,主观下线的哨兵会在后续ping通后,将master的下线状态移除
优点:解决的自动切换主从
缺点:难扩容
- cluster模式
Redis 3.0 开始提供Redis cluster
cluster中每个节点互连,通过对key进行hash,不同的key分配带不同的节点中
没有使用一致性hash算法,而是使用了hash槽?
一致性hash算法:环形0-2^32 每个节点收上一个节点值到当前节点值的hash 优点是增加和删除节点只影响顺时针的那一个节点 缺点是 数据易倾斜,增加删除节点数据需要全部重新hash重新分配
计算hash值后,顺时针查找,第一个节点
hash槽:定义0-2^14个槽位,给定每个节点分配的槽。
计算hash值后,直接判断节点中是否有该槽。增加删除节点需要重新分配 实现简单方便
多slave备份master,挂了slave顶上,
# 端口
port 7001
# 启用集群模式
cluster-enabled yes
# 根据你启用的节点来命名,最好和端口保持一致,这个是用来保存其他节点的名称,状态等信息的
cluster-config-file nodes_7001.conf
# 超时时间
cluster-node-timeout 5000
appendonly yes
# 后台运行
daemonize yes
# 非保护模式
protected-mode no
pidfile /var/run/redis_7001.pid
redis-server redis7001.conf
7、Redis 客户端
redisson java客户端 与jedis对标
redisson基于netty,采用非阻塞IO,性能高 ; 支持读写分离;支持cluster下的pipelining
https://www.javadoc.io/doc/org.redisson/redisson/3.10.6/index.html java官方文档
1、redisson实现了很多java类的分布式 实现
- RAutomicLong 做分布式计数,id生成等功能 还有AtomicDouble
redisson.getAtomicLong("myAtomicLong")
atomicLong.compareAndSet
atomicLong.getAndIncrement
- RLongAdder 累加器 DoubleAdder - 累加器比AutomicLong高并发性能更好
RLongAdder atomicLong = redisson.getLongAdder("myLongAdder");
atomicLong.add(12);
atomicLong.increment();
atomicLong.decrement();
atomicLong.sum();
累加器不用了需要手工销毁
atomicLong.destroy();
- RTopic 话题分发订阅功能 消息队列 RPatternTopic 模糊主题订阅
RTopic topic = redisson.getTopic("anyTopic");
topic.addListener(SomeObject.class, new MessageListener<SomeObject>() {
@Override
public void onMessage(String channel, SomeObject message) {
//...
}
});
// 在其他线程或JVM节点
RTopic topic = redisson.getTopic("anyTopic");
long clientsReceivedMessage = topic.publish(new SomeObject());
- RBloomFilter 布隆过滤器 判断key是否存在,不存key 最大比特数2^32
- RRateLimit 限流器
boolean trySetRate(RateType var1, long var2, long var4, RateIntervalUnit var6);
//设置访问速率,var2为访问数,var4为单位时间,var6为时间单位
void acquire(); //访问数据 默认令牌数1 阻塞
void acquire(long var1); //访问数据,占据的令牌数 阻塞
boolean tryAcquire(); //尝试访问数据 非阻塞,立马返回
boolean tryAcquire(long permits); //
boolean tryAcquire(long var1, TimeUnit var3); // //尝试访问数据 阻塞,等待给定时间
boolean tryAcquire(long var1, long var3, TimeUnit var5);
RateLimiterConfig getConfig();
// 使用
RRateLimiter rateLimiter=client.getRateLimiter("rate_limiter");
rateLimiter.trySetRate(RateType.PER_CLIENT,5,2, RateIntervalUnit.MINUTES);
ExecutorService executorService= Executors.newFixedThreadPool(10);
for (int i=0;i<10;i++){
executorService.submit(()->{
try{
//阻塞等待
rateLimiter.acquire();
}catch (Exception e){
e.printStackTrace();
}
});
}
}
- RMap 提供本地缓存功能,元素淘汰功能,集群时的数据分片功能,监听功能
RMap<String, SomeObject> map = redisson.getMap("anyMap");
map.put("123", new SomeObject());
map.remove("123");
- RSet RSortedSet
- RList
- RQueue RBlockingQueue
2、redisson 分布式锁
原理:
单机Redis
上锁 my_random_value保证每个客户端设置的值都不一样
SET resource_name my_random_value NX PX 30000
解锁 使用上述获取到锁的value对比,保证只能由获取锁的进程删除锁
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
集群Redis RedLock算法
相同的key和value,一次向多个redis实例发送获取锁,需要设置一个超时时间(该时间需要小于锁的自动失效时间),如果有超过一半的实例获取成功,且没超时,则获取锁成功。 如果获取失败,则需要将成功的都解锁
- RLock
如果上锁的master宕机,无法解锁,会导致锁死。使用了看门狗 - 无限延长锁的有效期。
只有拥有锁的进程才能解锁。 如果想让其他进程也能解开,可以使用Semaphore
// 默认获取的是非公平锁
RLock lock = redisson.getLock("anyLock");
// 公平锁
RLock fairLock = redisson.getFairLock("anyLock");
// 拿锁失败时会不停的重试
// 具有Watch Dog 自动延期机制 默认续30s 每隔30/3=10 秒续到30s
lock.lock();
// 尝试拿锁10s后停止重试,返回false
// 具有Watch Dog 自动延期机制 默认续30s
boolean res1 = lock.tryLock(10, TimeUnit.SECONDS);
// 拿锁失败时会不停的重试
// 没有Watch Dog ,10s后自动释放
lock.lock(10, TimeUnit.SECONDS);
// 尝试拿锁100s后停止重试,返回false
// 没有Watch Dog ,10s后自动释放
boolean res2 = lock.tryLock(100, 10, TimeUnit.SECONDS);
//2. 公平锁 保证 Redisson 客户端线程将以其请求的顺序获得锁
RLock fairLock = redissonClient.getFairLock("fairLock");
//3. 读写锁 没错与JDK中ReentrantLock的读写锁效果一样
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("readWriteLock");
readWriteLock.readLock().lock();
readWriteLock.writeLock().lock();
- RedLock
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();
- RSemaphore 信号量
与java的信号量类似,不过提供了异步的方法
boolean trySetPermits(int var1); //设置permits数
void reducePermits(int var1); //减少permit数
void acquire() throws InterruptedException; //获得一个permit
void acquire(int var1) throws InterruptedException; //获得var1个permits
boolean tryAcquire(); //尝试获得permit
boolean tryAcquire(int var1); //尝试获得var1个permit
boolean tryAcquire(long var1, TimeUnit var3) throws InterruptedException; //尝试获得permit,等待时间var1
boolean tryAcquire(int var1, long var2, TimeUnit var4) throws InterruptedException;
//尝试获得var1个permit,等待时间var2
void release(); //释放permit
void release(int var1); //释放var1个permits
int availablePermits(); //信号量的permits数
int drainPermits(); //清空permits
semaphore.tryAcquireAsync(23, TimeUnit.SECONDS);
semaphore.releaseAsync();
- RCountDownLatch 倒计数 闭锁
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.trySetCount(1);
latch.await();
// 在其他线程或其他JVM里
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.countDown();
3、远程服务调用 RPC
注册服务
RRemoteService remoteService = redisson.getRemoteService();
SomeServiceImpl someServiceImpl = new SomeServiceImpl();
// 在调用远程方法以前,应该首先注册远程服务
// 只注册了一个服务端工作者实例,只能同时执行一个并发调用
remoteService.register(SomeServiceInterface.class, someServiceImpl);
// 注册了12个服务端工作者实例,可以同时执行12个并发调用
remoteService.register(SomeServiceInterface.class, someServiceImpl, 12);
调用服务
RRemoteService remoteService = redisson.getRemoteService();
SomeServiceInterface service = remoteService.get(SomeServiceInterface.class);
String result = service.doSomeStuff(1L, "secondParam", new AnyParam());
8、Redis应用
1.session,token缓存
2.阅读数,点赞数等的缓存
3.分布式锁
4.bitmap做签到,日活等统计
5.高并发访问数据库
6.消息队列
网友评论