2.Redis高阶篇

redis是单线程主要是指redis的网络IO和键值对读写是由一个线程来完成的,redis在处理客户端的请求时包括获取(socket读)、解析、执行、内容返回(socket写)等都由一个顺序串行的主线程处理,这就是所谓的“单线程”。这也是redis对外提供键值存储服务的主要流程。redis采用Reactor模式的网络模型,对于一个客户端请求,主线程负责一个完整的处理过程如下:

  1. 读取Socket
  2. 解析请求
  3. 执行操作
  4. 写入Socket

但redis的其他功能,比如持久化RDB、AOF、异步删除、集群数据同步等,其实是由额外的线程执行的;redis命令工作线程是单线程的,但是,整个redis来说,是多线程的

对于redis主要的性能瓶颈是内存或者网络带宽而非CPU

主线程和IO线程协作完成请求处理:

  1. 阶段一:服务端和客户端建立socket连接,并分配处理线程
    1. 首先,主线程负责接收建立请求。当有客户端请求和实例建立socket连接时,主线程会创建和客户端的连接,并把socket放入全局等待队列中,紧接着,主线程通过轮询方法把socket连接分配给IO线程
  2. 阶段二:IO线程读取并解析请求
    1. 主线程一旦把socket分配给IO线程,就会进入阻塞状态,等待IO线程完成客户端请求读取和解析,因为有多个IO线程在并行处理,所以,这个过程很快就可以完成
  3. 阶段三:主线程执行请求操作
    1. 等到IO线程解析完请求,主线程还是会以单线程的方式执行这些命令操作
  4. 阶段四:IO线程回写socket和主线程清空全局队列
    1. 当主线程执行完请求操作后,会把需要返回的结果写入缓冲区,然后,主线程会阻塞等待IO线程,把这些结果回写到socket中,并返回给客户端。和IO线程读取和解析请求一样,IO线程回写socket时,也是有多个线程在并发执行,所以回写socket的速度也很快,等到IO线程回写socket完毕,主线程会清空全局队列,等待客户端的后续请求

Unix网络编程中的五种IO模型:

  1. Blocking IO:阻塞IO
  2. NoneBlocking IO:非阻塞IO
  3. IO multiplexing IO:IO多路复用
    1. 概念:一种同步的IO模型,实现一个线程监视多个文件句柄,一旦某个文件句柄就绪就能够通知到对应应用程序进行相应的读写操作,没有文件句柄就绪时就会阻塞应用程序,从而释放CPU资源
    2. I/O:网络I/O,尤其在操作系统层面指数据在内核态和用户态之间的读写操作
    3. 多路:多个客户端连接(连接就是套接字描述符,即socket或者channel)
    4. 复用:复用一个或几个线程
    5. 也就是说一个服务端进程可以同时处理多个套接字描述符
    6. 实现IO多路复用的模型有3种:
      1. select
      2. poll
      3. epoll
  4. signal driven IO:信号驱动IO
  5. asynchronous IO:异步IO

redis6以后开启多线程需要在redis.conf配置文件中开启:io-threads 6 和 设置io-threads-do-reads为yes,表示启动多线程

在redis.conf配置文件中禁用keys *,flushAll,flushDB等命令:rename-command keys “” rename-command flushdb “”

rename-command flushall “”

防止bigkey:

  1. string类型控制在10kb以内,hash、list、set、zset元素个数不要超过5000
  2. 非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除(因为自动删除会触发del操作,造成阻塞)

如何发现bigkey:

  1. redis-cli --bigkeys:redis-cli -h 127.0.0.1 -p 6379 -a 123456 --bigkeys
  2. MEMORY USAGE key:计算key的值所占的字节数
  3. 调优:在redis.conf中设置以下属性为yes:
    1. lazyfree-lazy-server-del yes
    2. replica-lazy-flush yes
    3. lazyfree-lazy-user-del yes

redis和mysql双写一致性:

双检加锁策略避免缓存击穿

最终一致性:给缓存设置过期时间,定期清理缓存并回写,是保证最终一致性的解决方案。所有的写操作以数据库为主,如果数据库写成功,缓存更新失败,那么只要达到过期时间,则后面的读请求自然会从数据库中读取新值然后回填到缓存,达到一致性

延时双删策略:一开始A线程就删除redis的数据,然后更新mysql数据,最后再把redis的数据删除(因为可能在更新msyql数据期间有其他线程来读取脏数据回写到redis),最后如果有B线程来读取redis,发现redis没有数据就会读取mysql最新的数据,并把数据回写到redis,这样就保证了redis和mysql的数据一致性

优先使用先更新数据库,再删除缓存方案:

  1. 先删除缓存再更新数据库,有可能导致请求因缓存缺失而访问mysql,给数据库带来压力
  2. 如果业务应用中读取数据库和写缓存的时间不好估算,那么延迟双删的等待时间不好设置

canal实现了mysql数据变动立刻同步给redis:

  1. 查看mysql版本号:select version();

  2. 查看当前主机的二进制日志:show master status

  3. show variables like ‘log_bin’,如果为off,执行下面步骤:

  4. 开启mysql的binlog写入功能:在my.ini配置文件中:在[mysqld]下添加:

    1. log-bin=mysql-bin #开启binlog
    2. binlog-format=ROW #选择ROW模式
    3. server_id=1 #配置mysql replaction需要定义,不要和canal的slaveId重复
    4. 重启mysql
  5. 再次show variables like ‘log_bin’,此时为ON

  6. select * from mysql.‘user’

  7. 默认没有canal账户:授权canal的mysql账户

    1. DROP user if exists 'canal'@'%';
      create user 'canal'@'%' identified by 'canal';
      grant all privileges on *.* to 'canal'@'%' identified by 'canal';
      flush privileges;
      
  8. canal配置:

    1. 下载:http://github.com/alibaba/canal

    2. 解压:tar -zxvf canal.deployer.tar.gz

    3. 配置:修改在canal安装目录下的conf/example路径下instance.properties文件:

      1. # 换成mysql的主机和端口
        canal.instance.master.address=127.0.0.1:3306
        
    4. 启动:canal安装目录下的bin目录执行./startup.sh

    5. 查看是否启动:在canal安装目录下的log目录下查看日志canal.log文件

    6. java配置canal,详情查看b站视频

缓存穿透:一般情况下,是先查询缓存redis是否有该条数据,缓存中没有时,再查询数据库。当数据库也不存在该条数据时,每次查询都要访问数据库,这就是缓存穿透。带来的问题就是,当有大量请求查询数据库不存在的数据时,就会给数据库带来压力。可以使用布隆过滤器解决缓存穿透问题:

  1. 把已存在数据的key存在布隆过滤器中,相当于redis前面挡着一个布隆过滤器
  2. 当有新的请求时,先到布隆过滤器中查询是否存在
  3. 如果布隆过滤器中不存在该条数据则直接返回
  4. 如果布隆过滤器中已存在,才去查询缓存redis,如果redis里没有查询到则再去查询mysql

缓存预热:

缓存问题产生原因解决方案
缓存更新方式数据变更、缓存时效性同步更新、失效更新、异步更新、定时更新
缓存不一致同步更新失败、异步更新增加重试、补偿任务
缓存穿透恶意攻击空对象缓存、布隆过滤器
缓存击穿热点key失效互斥更新、随机退避、差异失效时间
缓存雪崩缓存挂掉快速失败熔断、主从模式、集群模式

redis分布式锁:

锁的种类:

  1. 单机版同一个JVM虚拟机内,synchronized或者Lock接口
  2. 分布式多个不同JVM虚拟机,单机的线程锁机制不再起作用,资源类在不同的服务器之间共享了
  3. setnx key value
  4. set key value [EX seconds] [PX milliseconds] [NX] [XX]
  5. 使用hset实现分布式锁

lua脚本:

  1. eval “redis.call(‘set’,‘k1’,‘v1’) redis.call(‘expire’,‘k1’,‘30’) return redis.call(‘get’,‘k1’)” 0
  2. eval “return redis.call(‘mset’,KEYS[1],ARGV[1],KEYS[2],ARGV[2])” 2 k1 k2 v1 v2
  3. eval “if redis.cal(‘get’,KEYS[1]) == ARGV[1] then return redis.call(‘del’,KEYS[1]) else return 0 end” 1 k1 v1

可重入锁:指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(锁对象是同一个),不会因为之前已经获取过还没释放而阻塞

分布式锁需要的条件和刚需:

  1. 独占性:任何时刻有且只有一个线程持有这个锁

  2. 高可用:若redis集群环境下,不能因为某一个节点挂了而出现获取锁和释放锁失败的情况
    高并发请求下,依旧性能很好

  3. 防死锁:不能出现死锁问题,必须有超时重试机制或者设置过期时间撤销操作,有个终止跳出的途径

  4. 不乱抢:防止张冠李戴,只能解锁自己的锁,不能把别人的锁给释放了

  5. 重入性:同一节点的同一线程如果获得锁之后,他可以再次获取这个锁

  6. import cn.hutool.core.util.IdUtil;
    import com.xfcy.mylock.DistributedLockFactory;
    import lombok.extern.slf4j.Slf4j;
    import org.redisson.Redisson;
    import org.redisson.api.RLock;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.data.redis.core.script.DefaultRedisScript;
    import org.springframework.stereotype.Service;
    
    import java.util.Arrays;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * @author
     * @date 2023/4/1 10:34
     */
    @Service
    @Slf4j
    public class InventoryService {
    
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
        @Value("${server.port}")
        private String port;
    
        /**
         *  v6.0       使用 Lua 脚本   将 final的判断 + del 弄成原子操作
         *                  问题: 兼顾锁的可重入性
         *             但是基本 v6.0 版本已经够用
         * @return
         */
        public String sale() {
            String retMessage = "";
            String key = "xfcyRedisLock";
            String value = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();
            while (!stringRedisTemplate.opsForValue().setIfAbsent(key, value, 30L, TimeUnit.SECONDS)) {
                // 暂停20毫秒,进行递归重试
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 抢锁成功的请求线程,进行正常的业务逻辑操作,扣减库存
            try {
                //1 查询库存信息
                String result = stringRedisTemplate.opsForValue().get("inventory001");
                //2 判断库存是否足够
                Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);
                //3 扣减库存
                if (inventoryNumber > 0) {
                    stringRedisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNumber));
                    retMessage = "成功卖出一个商品,库存剩余: " + inventoryNumber;
                    System.out.println(retMessage);
                } else {
                    retMessage = "商品卖完了,o(╥﹏╥)o";
                }
            }finally {
                // 改进点,修改为Lua脚本的Redis分布式锁调用,必须保证原子性
                String luaScript =
                        "if (redis.call('get',KEYS[1]) == ARGV[1]) then " +
                                "return redis.call('del',KEYS[1]) " +
                                "else " +
                                "return 0 " +
                                "end";
                stringRedisTemplate.execute(new DefaultRedisScript<>(luaScript, Boolean.class), Arrays.asList(key), value);
            }
            return retMessage + "\t" + "服务端口号:" + port;
        }
    
    
    
        /**
         * v5.0        存在问题: final的判断 + del 不是一行原子操作,需要lua脚本进行修改
         * @return
         */
    //    public String sale() {
    //        String retMessage = "";
    //        String key = "xfcyRedisLock";
    //        String value = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();
    //        while (!stringRedisTemplate.opsForValue().setIfAbsent(key, value, 30L, TimeUnit.SECONDS)) {
    //            // 暂停20毫秒,进行递归重试
    //            try {
    //                Thread.sleep(20);
    //            } catch (InterruptedException e) {
    //                e.printStackTrace();
    //            }
    //        }
    //        // 抢锁成功的请求线程,进行正常的业务逻辑操作,扣减库存
    //        try {
    //            //1 查询库存信息
    //            String result = stringRedisTemplate.opsForValue().get("inventory001");
    //            //2 判断库存是否足够
    //            Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);
    //            //3 扣减库存
    //            if (inventoryNumber > 0) {
    //                stringRedisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNumber));
    //                retMessage = "成功卖出一个商品,库存剩余: " + inventoryNumber;
    //                System.out.println(retMessage);
    //            } else {
    //                retMessage = "商品卖完了,o(╥﹏╥)o";
    //            }
    //        }finally {
    //            // 改进点,只能删除自己的key,而不是别的客户端
    //            // 问题:不能保证原子操作
    //            if (stringRedisTemplate.opsForValue().get(key).equalsIgnoreCase(value)) {
    //                stringRedisTemplate.delete(key);
    //            }
    //        }
    //        return retMessage + "\t" + "服务端口号:" + port;
    //    }
    
    
        /**
         *  v4.0      加了过期时间 stringRedisTemplate.opsForValue().setIfAbsent(key, value, 30L, TimeUnit.SECONDS)
         *              问题:误删锁,如果线程A运行时间超出了过期时间
         *                      在线程A运行时,xfcyRedisLock这个key过期,另一个线程B进来加了key
         *                      线程A结束后,把线程B的锁删了
         *           stringRedisTemplate.delete(key);  只能自己删除自己的,需要添加判断是否是自己的锁
         * @return
         */
    //    public String sale() {
    //        String retMessage = "";
    //        String key = "xfcyRedisLock";
    //        String value = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();
    //
    //        // 不用递归了,高并发下容易出错,我们用自旋替代递归方法调用;也不用if,用while来替代
    //        while (!stringRedisTemplate.opsForValue().setIfAbsent(key, value, 30L, TimeUnit.SECONDS)) {
    //            // 暂停20毫秒,进行递归重试
    //            try {
    //                Thread.sleep(20);
    //            } catch (InterruptedException e) {
    //                e.printStackTrace();
    //            }
    //        }
    //        // 无法保证原子性
            stringRedisTemplate.expire(key, 30L, TimeUnit.SECONDS);
    //
    //        // 抢锁成功的请求线程,进行正常的业务逻辑操作,扣减库存
    //        try {
    //            //1 查询库存信息
    //            String result = stringRedisTemplate.opsForValue().get("inventory001");
    //            //2 判断库存是否足够
    //            Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);
    //            //3 扣减库存
    //            if (inventoryNumber > 0) {
    //                stringRedisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNumber));
    //                retMessage = "成功卖出一个商品,库存剩余: " + inventoryNumber;
    //                System.out.println(retMessage);
    //            } else {
    //                retMessage = "商品卖完了,o(╥﹏╥)o";
    //            }
    //        }finally {
    //            stringRedisTemplate.delete(key);
    //        }
    //        return retMessage + "\t" + "服务端口号:" + port;
    //    }
    
    
        /**
         * 3.2版  setnx  用while判断
         *          问题: setnx过后,正在进行业务逻辑操作时,没有走到finally之前,整个微服务down机了,导致锁一直存在
         *                      不是程序出了问题,如果程序问题,最后还是会执行finally
         *                没办法保证解锁(没有过期时间,该key一直存在),需要加入过期时间限定key
         */
    //    public String sale() {
    //        String retMessage = "";
    //        String key = "xfcyRedisLock";
    //        String value = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();
    //
    //        // 不用递归了,高并发下容易出错,我们用自旋替代递归方法调用;也不用if,用while来替代
    //        while (!stringRedisTemplate.opsForValue().setIfAbsent(key, value)) {
    //            // 暂停20毫秒,进行递归重试
    //            try {
    //                Thread.sleep(20);
    //            } catch (InterruptedException e) {
    //                e.printStackTrace();
    //            }
    //        }
    //        // 抢锁成功的请求线程,进行正常的业务逻辑操作,扣减库存
    //        try {
    //            //1 查询库存信息
    //            String result = stringRedisTemplate.opsForValue().get("inventory001");
    //            //2 判断库存是否足够
    //            Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);
    //            //3 扣减库存
    //            if (inventoryNumber > 0) {
    //                stringRedisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNumber));
    //                retMessage = "成功卖出一个商品,库存剩余: " + inventoryNumber;
    //                System.out.println(retMessage);
    //            } else {
    //                retMessage = "商品卖完了,o(╥﹏╥)o";
    //            }
    //        }finally {
    //            stringRedisTemplate.delete(key);
    //        }
    //        return retMessage + "\t" + "服务端口号:" + port;
    //    }
    
    
        /**
         * 3.1版   setnx 递归调用 容易导致 StackOverflowError
         *          高并发唤醒后推荐用while判断而不是if
         * @return
         */
    //    public String sale() {
    //        String retMessage = "";
    //        String key = "xfcyRedisLock";
    //        String value = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();
    //
    //        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, value);
    //        // flag = false 抢不到的线程要继续重试 。。。
    //        if (!flag) {
    //            // 暂停20毫秒,进行递归重试
    //            try {
    //                Thread.sleep(20);
    //            } catch (InterruptedException e) {
    //                e.printStackTrace();
    //            }
    //            sale();
    //        } else {
    //            try {
    //                //1 查询库存信息
    //                String result = stringRedisTemplate.opsForValue().get("inventory001");
    //                //2 判断库存是否足够
    //                Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);
    //                //3 扣减库存
    //                if (inventoryNumber > 0) {
    //                    stringRedisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNumber));
    //                    retMessage = "成功卖出一个商品,库存剩余: " + inventoryNumber;
    //                    System.out.println(retMessage);
    //                } else {
    //                    retMessage = "商品卖完了,o(╥﹏╥)o";
    //                }
    //            } finally {
    //                stringRedisTemplate.delete(key);
    //            }
    //        }
    //        return retMessage + "\t" + "服务端口号:" + port;
    //    }
    
    
    /**
     * v2.0 单机版加锁配合nginx和jmeter压测后,不满足高并发分布式锁的性能要求,出现超卖
     */
    //    private Lock lock = new ReentrantLock();
    //
    //    public String sale() {
    //        String retMessage = "";
    //        lock.lock();
    //        try {
    //            //1 查询库存信息
    //            String result = stringRedisTemplate.opsForValue().get("inventory001");
    //            //2 判断库存是否足够
    //            Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);
    //            //3 扣减库存
    //            if (inventoryNumber > 0) {
    //                stringRedisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNumber));
    //                retMessage = "成功卖出一个商品,库存剩余: " + inventoryNumber;
    //                System.out.println(retMessage);
    //            } else {
    //                retMessage = "商品卖完了,o(╥﹏╥)o";
    //            }
    //        } finally {
    //            lock.unlock();
    //        }
    //        return retMessage + "\t" + "服务端口号:" + port;
    //    }
    }
    
    
  7.     @Autowired
        private DistributedLockFactory distributedLockFactory;
    
    
        /**
         * v8.0  实现自动续期功能的完善,后台自定义扫描程序,如果规定时间内没有完成业务逻辑,会调用加钟自动续期的脚本
         *
         * @return
         */
        public String sale() {
            String retMessage = "";
    
            Lock redisLock = distributedLockFactory.getDistributedLock("redis");
            redisLock.lock();
            try {
                //1 查询库存信息
                String result = stringRedisTemplate.opsForValue().get("inventory001");
                //2 判断库存是否足够
                Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);
                //3 扣减库存
                if (inventoryNumber > 0) {
                    stringRedisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNumber));
                    retMessage = "成功卖出一个商品,库存剩余: " + inventoryNumber;
    
                    // 演示自动续期的的功能
    //                try {
    //                    TimeUnit.SECONDS.sleep(120);
    //                } catch (InterruptedException e) {
    //                    e.printStackTrace();
    //                }
                } else {
                    retMessage = "商品卖完了,o(╥﹏╥)o";
                }
            } finally {
                redisLock.unlock();
            }
            return retMessage + "\t" + "服务端口号:" + port;
        }
    
    
        /**
         * v7.0     兼顾锁的可重入性   setnx不满足了,需要hash结构的hset
         * 上锁和解锁都用 Lua 脚本实现原子性
         * 引入工厂模式 DistributedLockFactory    实现Lock接口 ,实现 redis的可重入锁
         *
         * @return
         */
    //    //private Lock redisDistributedLock = new RedisDistributedLock(stringRedisTemplate, "xfcyRedisLock");
    //
    //    public String sale() {
    //        String retMessage = "";
    //
    //        Lock redisLock = distributedLockFactory.getDistributedLock("redis");
    //        redisLock.lock();
    //
    //        //redisDistributedLock.lock();
    //        try {
    //            //1 查询库存信息
    //            String result = stringRedisTemplate.opsForValue().get("inventory001");
    //            //2 判断库存是否足够
    //            Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);
    //            //3 扣减库存
    //            if (inventoryNumber > 0) {
    //                stringRedisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNumber));
    //                retMessage = "成功卖出一个商品,库存剩余: " + inventoryNumber;
    //                System.out.println(retMessage);
    //
    //                // 测试可重入性
    //                //testReEntry();
    //
    //            } else {
    //                retMessage = "商品卖完了,o(╥﹏╥)o";
    //            }
    //        } finally {
    //            redisLock.unlock();
    //            //redisDistributedLock.unlock();
    //        }
    //        return retMessage + "\t" + "服务端口号:" + port;
    //    }
    //
    //    private void testReEntry() {
    //        Lock redisLock = distributedLockFactory.getDistributedLock("redis");
    //        redisLock.lock();
    //
    //        //redisDistributedLock.lock();
    //        try {
    //            System.out.println("测试可重入锁");
    //        } finally {
    //            redisLock.unlock();
    //            //redisDistributedLock.unlock();
    //        }
    //    }
    
  8. package com.xfcy.mylock;
    
    import cn.hutool.core.util.IdUtil;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.stereotype.Component;
    
    import java.util.concurrent.locks.Lock;
    
    /**
     * @author 晓风残月Lx
     * @date 2023/4/1 22:14
     */
    @Component
    public class DistributedLockFactory {
    
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
        private String lockName;
        private String uuidValue;
    
        public DistributedLockFactory() {
            this.uuidValue = IdUtil.simpleUUID();
        }
    
        public Lock getDistributedLock(String lockType) {
            if (lockType == null) {
                return null;
            }
            if (lockType.equalsIgnoreCase("REDIS")) {
                this.lockName = "xfcyRedisLock";
                return new RedisDistributedLock(stringRedisTemplate, lockName, uuidValue);
            }else if (lockType.equalsIgnoreCase("ZOOKEEPER")) {
                this.lockName = "xfcyZookeeperLock";
                // TODO zoookeeper 版本的分布式锁
                return null;
            }else if (lockType.equalsIgnoreCase("MYSQL")){
                this.lockName = "xfcyMysqlLock";
                // TODO MYSQL 版本的分布式锁
                return null;
            }
            return null;
        }
    }
    
  9. package com.xfcy.mylock;
    
    import cn.hutool.core.util.IdUtil;
    import com.sun.org.apache.xpath.internal.operations.Bool;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.data.redis.core.script.DefaultRedisScript;
    
    import java.util.Arrays;
    import java.util.Timer;
    import java.util.TimerTask;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    
    /**
     * @author 晓风残月Lx
     * @date 2023/4/1 21:38
     * 自研的redis分布式锁,实现 Lock 接口
     */
    // @Component 引入DistributedLockFactory工厂模式,从工厂获得即可
    public class RedisDistributedLock implements Lock {
    
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
    
        private String lockName;    // KEYS[1]
        private String uuidValue;   // ARGV[1]
        private long expireTime;    // ARGV[2]
    
        public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName, String uuidValue) {
            this.stringRedisTemplate = stringRedisTemplate;
            this.lockName = lockName;
            this.uuidValue = uuidValue + ":" + Thread.currentThread().getId();
            this.expireTime = 30L;
        }
    
        @Override
        public void lock() {
            tryLock();
        }
    
        @Override
        public boolean tryLock() {
            try {
                tryLock(-1L, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
            return false;
        }
    
        @Override
        public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
            if (time == -1L) {
                String script = "if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 then " +
                        "redis.call('hincrby',KEYS[1],ARGV[1],1)  " +
                        "redis.call('expire',KEYS[1],ARGV[2]) " +
                        "return 1 " +
                        "else " +
                        "return 0 " +
                        "end";
                System.out.println("lockName = " + lockName +"\t" + "uuidValue = " + uuidValue);
                while (!stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime))) {
                    // 暂停 60ms
                    Thread.sleep(60);
                }
                // 新建一个后台扫描程序,来监视key目前的ttl,是否到我们规定的 1/2 1/3 来实现续期
                resetExpire();
                return true;
            }
            return false;
        }
    
        @Override
        public void unlock() {
            String script = "if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 0 then " +
                    "return nil " +
                    "elseif redis.call('HINCRBY',KEYS[1],ARGV[1],-1) == 0 then  " +
                    "return redis.call('del',KEYS[1]) " +
                    "else  " +
                    "return 0 " +
                    "end";
            // nil = false  1 = true  0 = false
            Long flag = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime));
            if (null == flag) {
                throw new RuntimeException("this lock doesn't exists0");
            }
        }
    
        private void resetExpire() {
            String script = "if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 1 then " +
                    "return redis.call('expire',KEYS[1],ARGV[2]) " +
                    "else " +
                    "return 0 " +
                    "end";
            new Timer().schedule(new TimerTask() {
                @Override
                public void run() {
                    if (stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime))) {
                        resetExpire();
                    }
                }
            }, (this.expireTime * 1000) / 3);
        }
    
    
        // 下面两个用不上
        // 下面两个用不上
        // 下面两个用不上
    
        @Override
        public void lockInterruptibly() throws InterruptedException {
    
        }
    
        @Override
        public Condition newCondition() {
            return null;
        }
    }
    

redis淘汰策略:

redis设置内存:配置文件中设置maxmemory,一般推荐设置内存为最大物理内存的四分之三

定期删除策略:每隔一段时间执行一次删除过期键操作并通过限制删除操作执行时长和频率来减少删除操作对CPU时间的影响

LRU:最近最少使用页面置换算法,淘汰最长时间未被使用的页面,看页面最后一次被使用到发生调度的时间长短,首先淘汰最长时间未被使用的页面

LFU:最近最不常用页面置换算法,淘汰一定时期内被访问次数最少的页面,看一定时间段内页面被使用的频率,淘汰一定时期内被访问次数最少的页面

LRU关键是看页面最后一次被使用到发生调度的时间长短,LFU关键是看一定时间段内页面被使用的频率

八种缓存淘汰策略:

  1. noevication : 不会驱逐任何key,表示即使内存达到上限也不进行置换,所有能引起内存增加的命令都返回 error(默认)
  2. allkeys-lru: 对所有key使用 LRU算法进行删除,优先删除掉最近不经常使用的key,用以保存新数据
  3. volatie-lru : 对所有设置了过期时间的key使用LRU 算法删除
  4. allkeys-random :对所有key随机删除
  5. volatie-random : 对所有设置了过期时间的key随机删除
  6. volatie-ttl :对所有设置了过期时间的key随即删除
  7. allkeys-lfu:对所有key使用LFU算法进行删除
  8. volatile-lfu:对所有设置了过期时间的key使用LFU算法进行删除

IO多路复用:

  1. IO:网络I/O
  2. 多路:多个客户端连接(连接就是套接字描述符,即socket或者channel),指的是多条TCP
  3. 复用:用一个进程来处理多条的连接,使用单进程就能够实现同时处理多个客户端的连接

redis利用epoll来实现IO多路复用,将连接信息和事件放到队列中,一次放到文件事件分派器,事件分派器将事件分发给事件处理器

所谓的IO多路复用机制,就是说通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或写就绪),能够通知程序进行相应的读写操作。这种机制的使用需要select、poll、epoll来配合。多个连接共用一个阻塞对象,应用程序只需要在一个阻塞对象上等待,无需阻塞等待所有连接。当某条连接有新的数据可以处理时,操作系统通知应用程序,线程从阻塞状态返回,开始进行业务处理

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值