美文网首页
Redis 学习

Redis 学习

作者: 水木共美 | 来源:发表于2021-06-29 13:43 被阅读0次

    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.消息队列

    相关文章

      网友评论

          本文标题:Redis 学习

          本文链接:https://www.haomeiwen.com/subject/vzloyltx.html