redis学习(单机集群安装、三大问题、持久化)

本文介绍了Redis的特性,包括其作为内存数据库的基础信息,详细阐述了Linux环境下单机版的安装步骤,以及如何搭建三主三从的集群。同时,深入探讨了Redis的持久化机制,包括RDB和AOF两种方式,以及它们的优缺点。此外,还讨论了缓存的重要性和可能面临的问题,如缓存粒度和缓存穿透,并提出了Bloom Filter和Redis实现的布隆过滤器作为解决方案。
摘要由CSDN通过智能技术生成

是什么?

开源的,C语言写的,单线程的,高性能的,key-value的内存数据库,基于内存运行并支持持久化的nosql数据库

安装

下载:官网/github/yml

linux单机版安装步骤:

1、下载tar.gz   (https://github.com/antirez/redis/releases)

2、tar -zxvf 解压

3、yum install gcc 安装gcc环境,因为redis是c语言写的,所以需要环境

4、先 cd redis  再   make  

5、先cd src   再 make install

6、修改redis.conf  (bind、保护模式、dir设置持久化保存的目录、守护线程、aof配置、rdb配置,密码)

7、服务端:redis-server redis.conf

     客户端: 可视化或者命令行: redis-cli -h ip -p 6379

 

集群搭建:

1、配置三主三从的redis节点 建立6个文件夹,分别放置不同的redis.conf

2、修改下列配置:注意port dir 等基础配置,不要有文件冲突

port 7000
daemonize yes
pidfile /var/run/redis_7000.pid
cluster-enabled yes
cluster-node-timeout 15000
cluster-config-file  nodes-7000.conf 

3、第一个修改好了,将7000的conf复制给其他节点

        sed 's/7000/7001/g' redis7000/redis.conf > redis7001/redis.conf

4、分别启动所有的节点  redis-server redis-cluster/redis7005/redis.conf

5、--cluster help查看帮助   redis-cli --cluster help

6、create创建集群,自动分片 

redis-cli  create ip:port ip2:port2 ... ipN:portN --cluster-replicas 1 -a 123456     1代表主从比例1:1设置

 

 持久化理解:

   持久化的本质就是将内存中数据,写到磁盘中进行保存,以提高数据的安全性,尽量减少因宕机等造成数据丢失的风险

   redis的持久化分为rdb和aof

   rdb:

       原理: redis会单独fork一个子进程,来进行数据写入磁盘的io操作。这个子进程的所有数据和主进程保持一致。子进程会创建一个临时文件,将数据写入,等数据写入完成后,将上一次备份的rbd文件替换。rdb操作时,会阻塞主进程的io操作      

      rdb位置:redis.conf 中的  dir配置,默认 ./

      什么时候创建子进程,进行rbd操作:

          手动触发:客户端调用save(同步) bgsave(异步)   shoutdown(没有开启aof操作时)  flushall

          自动触发:redis.conf 配置的save达到触发条件时  默认下列配置,生产环境不需要这么启动的这么频繁 

                            save 900 1   save 300 10  save 60 10000   save 时间(秒)  修改的数据量

aof:

   原理:将redis的操作日志(事物处理),以追加的方式写入文件,读操作不记录。

   触发机制:redis.conf 中设置   appendonly yes 开启aof   (apend only file)

                    规则:# appendfsync  no(操作系统控制,快,不可控)/  always(时时,一条写操作追加一次 慢 安全)/  everysec (sec:秒  每秒执行一次  均衡)

                    重写规则(瘦身压缩,将规则数据转换为二进制文件): auto-aof-rewrite-percentage 100 (重写比例)
                                       auto-aof-rewrite-min-size 64mb  (重写最小文件大小)

思考: 以前系统开了rdb,保存了部分数据,后面开启了aof,数据是否可以加载进来?

缓存起到了什么作用:

   1、缓存查询比较快,提高接口响应速度

   2、减少了查询数据库,调用接口的次数,对服务进行降压

 

缓存的问题:

 1、缓存粒度问题

      通俗的说,缓存粒度问题,就是在缓存数据时,是缓存全部数据,还是缓存部分数据。

     缓存全部数据,粒度粗,通用性好,可用性强,代码相对简单,但是会有大量的无用数据,造成空间浪费,网络带宽浪费。需要综合考虑,设置缓存的粒度

 

2、缓存穿透问题

      个人理解就是,访问一个根本不存在的数据,请求穿过缓存,还是直接访问到了数据库

       当恶意攻击时,请求没办法在缓存中找到对应数据,并且调到了数据库或者接口请求中,缓存完全没有达到设计之初希望的作用,所以需要处理

     解决方案:

           一、缓存空对象,请求缓存查不到,请求数据库,请求数据库查不到的时候,设置空对象进缓存,下次访问可以拦截到该请求,避免重复调用  

                   存在的问题: 1、缓存了不存在的数据,空间浪费,可以设置失效时间,但是失效时间过期后,还是存在穿透问题

                                         2、一直切换不一样的,随机的不存在的数据,还是会重复调用到底层内部,并且造成空间的浪费,只能                                                解决部分问题,穿透问题没有完全解决

           二、goolgle的guava包下的BloomFilter

                  解决的问题: 先往bloomFilter内部插入所有正确的值,查询缓存前先判断是否在bloomFilter内部,存在的话,就放行查询底层,否则可以直接返回

                 存在的问题: 内存中设置数据,处理数据,空间利用大,不支持大数据量处理

                                      在分布式环境中,都在本地内存中,在集群环境不好维护处理

                                      重启就失效了,利用率低

                  设置缓存的代码如下:

/**
  * funnel:存储数据类型  expectedInsertions:数据存储容量 fpp:误判率(误判率影响整体性能
  */         
 BloomFilter bf =BloomFilter.create(Funnels.integerFunnel(),10000,0.01);

        // 初始化1000000条数据到过滤器中
        for (int i = 0; i < 10000; i++) {
            bf.put(i);
        }

        //是否有匹配不上的
        for (int i = 0; i < 10000; i++) {
            if (!bf.mightContain(i)) {
                System.out.println("有坏人逃脱了~~~");
            }
        }

        // 匹配不在过滤器中的10000个值,有多少匹配出来
        int count = 0;
        for (int i = 10000; i < 10000 + 10000; i++) {
            if (bf.mightContain(i)) {
                count++;
            }
        }
结果: 没有存在匹配不上的,0.01个误判的,在缓存穿透问题上,误判仅是指能够查询到数据库中,所以不影响功能,业务上可以接受

实现原理:
    根据误判率,通过内部算法,设置一个长度大小合适的数组,hash函数合适的个数,来实现针对某一个key,利用多个hash函数散列,同时设置值,当判断是否存在布隆过滤器判断时,根据这个key,同时判断所有的hash函数位置上均有值,就算命中。
    源码如下,虽然我也没看懂,但是可以拷贝借鉴:
    long numBits = optimalNumOfBits(expectedInsertions, fpp);
    int numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, numBits);


误判率的解释:
    bloomFilter的误判率:bloomFilter本质是通过设置的误判率来设置对应的数组容量大小和hash函数个数,实现低空间,高利用的目的,但是会出现,判断数组中的数据全部被其他key值设置为1,恰巧不存在的key,所hash的所有的数组位置均为1,产生误判情况

 

         三、利用redis来实现布隆过滤器

                1、利用redis的bit来实现一个布隆过滤器,api: setbit key index value      getbit key index     bitcount key

                2、代码涉及逻辑:  借鉴google的计算bit数组大小,hash函数个数的算法,来控制redis的使用空间大小和错误率

                                                init初始化,获取所有的正确数据,利用setbit,根据key的hash值,取余,得到多个index,往指定                                                  的key设置数据为1,设置到redis中

                                                查询前先利用getbit 查询一把redis

                解决问题: 1、将本地内存的大小,迁移出了业务模块,放在中间件处理,解耦,可扩展

                                  2、 分布式环境可以使用

                                  3、重启数据不会丢失  

                 存在的问题: 原来都是在内存处理,现在需要额外的网络io, 性能要低于google的

   

redis分布式锁:

  大概看了下redisson实现redis lock的代码

 RLock lock = redisson.getLock();
 lock.lock();
 lock.unlock();

public void lock() {
        try {
            lockInterruptibly();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
//        try {
//            return tryLockAsync(waitTime, leaseTime, unit).get();
//        } catch (ExecutionException e) {
//            throw new IllegalStateException(e);
//        }
        long newLeaseTime = -1;
        if (leaseTime != -1) {
            newLeaseTime = unit.toMillis(waitTime)*2;
        }
        
        long time = System.currentTimeMillis();
        long remainTime = -1;
        if (waitTime != -1) {
            remainTime = unit.toMillis(waitTime);
        }
        long lockWaitTime = calcLockWaitTime(remainTime);
        
        int failedLocksLimit = failedLocksLimit();
        List<RLock> acquiredLocks = new ArrayList<>(locks.size());
        for (ListIterator<RLock> iterator = locks.listIterator(); iterator.hasNext();) {
            RLock lock = iterator.next();
            boolean lockAcquired;
            try {
                if (waitTime == -1 && leaseTime == -1) {
                    lockAcquired = lock.tryLock();
                } else {
                    long awaitTime = Math.min(lockWaitTime, remainTime);
                    lockAcquired = lock.tryLock(awaitTime, newLeaseTime, TimeUnit.MILLISECONDS);
                }
            } catch (RedisResponseTimeoutException e) {
                unlockInner(Arrays.asList(lock));
                lockAcquired = false;
            } catch (Exception e) {
                lockAcquired = false;
            }
            
            if (lockAcquired) {
                acquiredLocks.add(lock);
            } else {
                if (locks.size() - acquiredLocks.size() == failedLocksLimit()) {
                    break;
                }

                if (failedLocksLimit == 0) {
                    unlockInner(acquiredLocks);
                    if (waitTime == -1 && leaseTime == -1) {
                        return false;
                    }
                    failedLocksLimit = failedLocksLimit();
                    acquiredLocks.clear();
                    // reset iterator
                    while (iterator.hasPrevious()) {
                        iterator.previous();
                    }
                } else {
                    failedLocksLimit--;
                }
            }
            
            if (remainTime != -1) {
                remainTime -= System.currentTimeMillis() - time;
                time = System.currentTimeMillis();
                if (remainTime <= 0) {
                    unlockInner(acquiredLocks);
                    return false;
                }
            }
        }

        if (leaseTime != -1) {
            List<RFuture<Boolean>> futures = new ArrayList<>(acquiredLocks.size());
            for (RLock rLock : acquiredLocks) {
                RFuture<Boolean> future = ((RedissonLock) rLock).expireAsync(unit.toMillis(leaseTime), TimeUnit.MILLISECONDS);
                futures.add(future);
            }
            
            for (RFuture<Boolean> rFuture : futures) {
                rFuture.syncUninterruptibly();
            }
        }
        
        return true;
    }

...........中间获取方法省略,看关键方法
 <T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        internalLockLeaseTime = unit.toMillis(leaseTime);
        // 直接使用LUA脚本,获取数据
        // KEYS[1] 传递过来创建锁的key
        // ARGV[1] 过期时间
        // ARGV[2] 客户端id
        /**
         * 第一个if,判断key1 是不是exist =0 不存在,直接hset 并设置expire时间
         * 第二个if,判断key1 和客户端id是不是都匹配上,匹配上加1 并且设置失效时间
         * 否则 直接返回剩余的过期时间
         */
        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
                  "if (redis.call('exists', KEYS[1]) == 0) then " +
                      "redis.call('hset', KEYS[1], ARGV[2], 1); " +
                      "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                      "return nil; " +
                  "end; " +
                  "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                      "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                      "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                      "return nil; " +
                  "end; " +
                  "return redis.call('pttl', KEYS[1]);",
                    Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
    }


// 未获取到锁,会自旋等待拿锁,一直重试
 while (true) {
                long currentTime = System.currentTimeMillis();
                ttl = tryAcquire(leaseTime, unit, threadId);
                // lock acquired
                if (ttl == null) {
                    return true;
                }

                time -= System.currentTimeMillis() - currentTime;
                if (time <= 0) {
                    acquireFailed(threadId);
                    return false;
                }

                // waiting for message
                currentTime = System.currentTimeMillis();
                if (ttl >= 0 && ttl < time) {
                    getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                } else {
                    getEntry(threadId).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
                }

                time -= System.currentTimeMillis() - currentTime;
                if (time <= 0) {
                    acquireFailed(threadId);
                    return false;
                }
            }



// unlock的lua脚本
protected RFuture<Boolean> unlockInnerAsync(long threadId) {
        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
                    "return nil;" +
                "end; " +
                "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
                "if (counter > 0) then " +
                    "redis.call('pexpire', KEYS[1], ARGV[2]); " +
                    "return 0; " +
                "else " +
                    "redis.call('del', KEYS[1]); " +
                    "redis.call('publish', KEYS[2], ARGV[1]); " +
                    "return 1; "+
                "end; " +
                "return nil;",
                Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));

    }

 

                   

redis的事务:

 使用事务的方式: 开启事务:MULTI    提交事务:EXEC

 事务执行内部,如果语法不对,直接报错,那么exec提交时,会全部回滚

 其中一个如果语法编译通过,执行报错。exec提交时,会回滚有问题的那个,其他正常提交

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值