当前位置: 首页>后端>正文

Redis 学习

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模型

Redis 学习,第1张

单线程版的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缓存
3.分布式锁
4.bitmap做签到,日活等统计
5.高并发访问数据库
6.消息队列


https://www.xamrdz.com/backend/3ck1931630.html

相关文章: