5.Redis

1.安装redis 6.0.8

官网地址:redis

中文官网地址:redis

安全bug按照官网提示,升级成为6.0.8

image-1669466695271

2.Redis传统五大数据类型的落地应用

官网命令大全网址:command

8大类型

  • 1.String(字符类型)

  • 2.Hash(散列类型)

  • 3.List(列表类型)

  • 4.Set(集合类型)

  • 5.SortedSet(有序集合类型,简称zset)

  • 6.Bitmap(位图)

  • 7.HyperLogLog(统计)

  • 8.GEO(地理)

备注

  • 命令不区分大小写,而key是区分大小写的

  • help @类型名词

String

  • 最常用

    • set key value
    • get key
  • 同时设置/获取多个键值

    • MSET key value[key value …]
    • MGET key[key …]
  • 数值增减

    • 递增数字
      • INCR key
    • 增加指定的整数
      • INCRBY key increment
    • 递减数值
      • DECR key
    • 减少指定的整数
      • DECRBY key decrement
  • 获取字符串长度

    • STRLEN key
  • 分布式锁

image-1669466745101

  • setnx key value

  • set key value[EX seconds] [PX milliseconds] [NX] [XX]

  • 应用场景

    • 商品编号、订单号采用INCR命令生成

    image-1669466761279

    • 是否喜欢的文章

    image-1669466771868

Hash

  • 对应Java数据结构:Map<String,Map<Object,Object>>

  • 一次设置一个字段值

    • HSET key field value
  • 一次获取一个字段值

    • HGET key field
  • 一次设置多个字段值

    • HMSET key field value[field value …]
  • 一次获取多个字段值

    • HMGET key field[field …]
  • 获取所有字段值

    • hgetall key
  • 获取某个key内的全部数量

    • hlen
  • 删除一个key

    • hdel
  • 应用场景

    • 购物车早期,当前小中厂可用

      image-1669466797401
      image-1669466808590

List

  • 向列表左边添加元素

    • LPUSH key value [value …]
  • 向列表右边添加元素

    • RPUSH key value [value …]
  • 查看列表

    • LRANGE key start stop
  • 获取列表中元素的个数

    • LLEN key
  • 应用场景

    • 微信文章订阅公众号

      image-1669466828276
      image-1669466841066

Set

  • 添加元素

    • SADD key member [member …]
  • 删除元素

    • SREM key member [member …]
  • 获取集合中的所有元素

    • SMEMBERS key
  • 判断元素是否在集合中

    • SISMEMBER key member
  • 获取集合中的元素个数

    • SCARD key
  • 从集合中随机弹出一个元素,元素不删除

    • SRANDMEMBER key [数字]
  • 从集合中随机弹出一个元素,出一个删一个

    • SPOP key [数字]∪
  • 集合运算

    • 集合的差集运算A - B
      • 属于A但不属于B的的元素构成的集合
      • SDIFF key [key …]
    • 集合的交集运算A ∩ B
      • 属于A同时也属于B的共同拥有的元素构成的集合
      • SINTER key [key …]
    • 集合的并集运算A ∪ B
      • 属于A或者属于B的元素合并后的集合
      • SUNION key [key …]
  • 应用场景

    • 微信抽奖小程序

      image-1669466859025

    • 微信朋友圈点赞

      image-1669466876083

    • 微博好友关注社交关系

      image-1669466893319

      • 共同关注的人

        image-1669466905803

      • 我关注的人也关注他(大家爱好相同)

        image-1669466917827

    • QQ内推可能认识的人

      image-1669466932704

ZSet

  • 向有序集合中加入一个元素和该元素的分数

  • 添加元素

    • ZADD key score member [score member …]
  • 按照元素分数从小到大的顺序,返回索引从start到stop之间的所有元素

    • ZRANGE key start stop [WITHSCORES]
  • 获取元素的分数

    • ZSCORE key member
  • 删除元素

    • ZREM key member [member …]
  • 获取指定分数范围的元素

    • ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
  • 增加某个元素的分数

    • ZINCRBY key increment member
  • 获取集合中元素的数量

    • ZCARD key
  • 获得指定分数范围内的元素个数

    • ZCOUNT key min max
  • 按照排名范围删除元素

    • ZREMRANGEBYRANK key start stop
  • 获取元素的排名

    • 从小到大
      • ZRANK key member
    • 从大到小
      • ZREVRANK key member
  • 应用场景

    • 根据商品销售对商品进行排序显示

      image-1669466948799

    • 抖音热搜

      image-1669466967854

3.你知道分布式锁吗?有哪些实现方案?你谈谈对Redis分布式锁的理解,删key的时候有什么问题?

  • 粉丝反馈回来的题目

    • Redis除了拿来做缓存,你还见过基于Redis的什么用法
    • Redis做分布式锁的时候有哪些需要注意的问题
    • 如果Redis是单点部署的,会带来什么问题
      • 那你准备怎么解决单点问题呢
    • 集群模式下,比如主从模式,有没有什么问题
    • 那你简单的介绍一下Redlock吧,你简历上写redission,你谈谈
    • Redis分布式锁如何续期?看门狗知道么?
  • Base案例(boot+redis)

    • 使用场景

      • 多个服务间+保证同一时刻内+同一用户只能有一个请求(防止关键业务出现数据冲突和并发错误)
    • 建Module(除了端口号和项目名,其他都一样)

      • boot_redis01
      • boot_redis02
    • 改POM

      • boot_redis01

        <?xml version="1.0" encoding="UTF-8"?>
        <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
            <modelVersion>4.0.0</modelVersion>
            <parent>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-parent</artifactId>
                <version>2.3.3.RELEASE</version>
                <relativePath/> <!-- lookup parent from repository -->
            </parent>
            <groupId>cn.wangzengqiang</groupId>
            <artifactId>boot_redis01</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <name>boot_redis01</name>
            <description>boot_redis01</description>
            <properties>
                <java.version>1.8</java.version>
            </properties>
            <dependencies>
                <!--web+actuator-->
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-web</artifactId>
                </dependency>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-actuator</artifactId>
                </dependency>
                <!--springboot与Redis整合依赖-->
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-data-redis</artifactId>
                </dependency>
                <dependency>
                    <groupId>org.apache.commons</groupId>
                    <artifactId>commons-pool2</artifactId>
                </dependency>
                <!--jedis-->
                <dependency>
                    <groupId>redis.clients</groupId>
                    <artifactId>jedis</artifactId>
                    <version>3.1.0</version>
                </dependency>
                <!--springboot-aop技术-->
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-aop</artifactId>
                </dependency>
                <!--redisson-->
                <dependency>
                    <groupId>org.redisson</groupId>
                    <artifactId>redisson</artifactId>
                    <version>3.13.4</version>
                </dependency>
                <!--一般通用基础配置-->
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-devtools</artifactId>
                    <scope>runtime</scope>
                    <optional>true</optional>
                </dependency>
                <dependency>
                    <groupId>org.projectlombok</groupId>
                    <artifactId>lombok</artifactId>
                    <optional>true</optional>
                </dependency>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-test</artifactId>
                    <scope>test</scope>
                </dependency>
            </dependencies>
        
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-maven-plugin</artifactId>
                        <configuration>
                            <excludes>
                                <exclude>
                                    <groupId>org.projectlombok</groupId>
                                    <artifactId>lombok</artifactId>
                                </exclude>
                            </excludes>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        
        </project>
        
        
        • boot_redis02【除了项目名,其他同boot_redis01】
    • 写YML

      • boot_redis01

          server.port=1111
          #   ================ redis相关配置 ====================
          # redis数据库索引(默认为0)
          spring.redis.database=0
          # redis服务器地址
          spring.redis.host=localhost
          # redis服务器连接端口
          spring.redis.port=6379
          # redis服务器连接密码(默认为空)
          spring.redis.password=
          # 连接池最大连接数(使用负值表示没有限制) 默认8
          spring.redis.lettuce.pool.max-active=8
          # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认-1
          spring.redis.lettuce.pool.max-wait=-1
          # 连接池中的最大空闲连接 默认8
          spring.redis.lettuce.pool.max-idle=8
          # 连接池中的最小空闲连接 默认0
          spring.redis.lettuce.pool.min-idle=0
        
      • boot_redis02【除了port不同,其他同boot_redis01】

    • 主启动

    • 业务类

      • config【boot_redis01和boot_redis02一样】

        package cn.wangzengqiang.boot_redis01.config;
        
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
        import org.springframework.data.redis.core.RedisTemplate;
        import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
        import org.springframework.data.redis.serializer.StringRedisSerializer;
        
        import java.io.Serializable;
        
        @Configuration
        public class RedisConfig {
            @Bean
            public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory connectionFactory) {
                RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();
                redisTemplate.setConnectionFactory(connectionFactory);
                redisTemplate.setKeySerializer(new StringRedisSerializer());
                redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
                return redisTemplate;
            }
        }
        
        
      • controller【boot_redis01和boot_redis02一样】

        package cn.wangzengqiang.boot_redis01.controller;
        
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.beans.factory.annotation.Value;
        import org.springframework.data.redis.core.StringRedisTemplate;
        import org.springframework.web.bind.annotation.GetMapping;
        import org.springframework.web.bind.annotation.RestController;
        
        @RestController
        public class GoodController {
            @Autowired
            private StringRedisTemplate stringRedisTemplate;
        
            @Value("${server.port}")
            private String serverPort;
        
            @GetMapping("buy_goods")
            public String buy_Goods() {
                String result = stringRedisTemplate.opsForValue().get("goods:001");// get key ========== 看看库存的数量够不够
                int goodsNumber = result == null ? 0 : Integer.parseInt(result);
                if (goodsNumber > 0) {
                    int realNumber = goodsNumber - 1;
                    stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
                    System.out.println("成功买到商品,库存还剩下: " + realNumber + " 件" + "\t 服务提供端口: " + serverPort);
                    return "成功买到商品,库存还剩下: " + realNumber + " 件" + "\t 服务提供端口: " + serverPort;
                } else {
                    System.out.println("商品已经售完/活动结束/调用超时,欢迎下次光临 " + "\t 服务提供端口: " + serverPort);
                }
                return "商品已经售完/活动结束/调用超时,欢迎下次光临 " + "\t 服务提供端口: " + serverPort;
        
        
            }
        }
        
        
    • 小测试

  • 大家来找茬

    • 上面测试通过求吐槽

      • 1.单机版没加锁

        • 问题

          • 没有加锁,并发下数字不对,出现超卖现象
        • 思考

          • 加synchronized
          • 加ReentrantLock
          • 还是都可以?
          • 分析
        • 解决

          • 修改为2.0版

            image-1669467002084

          • 解释

      • 2.nginx分布式微服务架构

        • 问题

          • 分布式部署后,单机锁还是出现超卖现象,需要分布式锁

            image-1669467017668

            image-1669467027624

            • redis cluster
          • nginx配置负载均衡

            • 启动nginx并测试通过

            • /usr/local/nginx/conf

              • nginx.conf

                image-1669467043527

            • 重启

              • ./nginx -s reload
            • /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf

            • ./nginx -c /usr/local/nginx/conf/nginx.conf

            • 1

              • 启动:/usr/local/nginx/sbin
              • 关闭:/usr/local/nginx/sbin > ./nginx -s stop
              • 重启:./nginx -s reload
          • 启动两个微服务

          • 上面手点,下面高并发模拟

            • image-1669467073619
          • 线程组redis

            • http请求
          • jmeter压测

      • 解决

        • 问题:出异常的话,可能无法释放锁,必须要在代码层面finally释放锁

        • 解决:加锁解锁,lock/unlock必须同时出现并保证调用

          • 修改为4.0版

            @GetMapping("buy_goods3")
                public String buy_Goods3() {
                    String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
                    try {
                        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value);//setNX
                        if (!flag) {
                            return "抢锁失败";
                        }
            
            
                        String result = stringRedisTemplate.opsForValue().get("goods:001");// get key ========== 看看库存的数量够不够
                        int goodsNumber = result == null ? 0 : Integer.parseInt(result);
                        if (goodsNumber > 0) {
                            int realNumber = goodsNumber - 1;
                            stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
                            System.out.println("成功买到商品,库存还剩下: " + realNumber + " 件" + "\t 服务提供端口: " + serverPort);
            
                            return "成功买到商品,库存还剩下: " + realNumber + " 件" + "\t 服务提供端口: " + serverPort;
                        } else {
                            System.out.println("商品已经售完/活动结束/调用超时,欢迎下次光临 " + "\t 服务提供端口: " + serverPort);
                        }
                        return "商品已经售完/活动结束/调用超时,欢迎下次光临 " + "\t 服务提供端口: " + serverPort;
                    } finally {
                        stringRedisTemplate.delete(REDIS_LOCK);
                    }
            
            
                }
            
      • 4.宕机了

        • 问题:部署了微服务jar包的机器挂了,代码层面根本没有走到finally这块,没办法保证解锁,这个key没有被删除,需要加入一个过期时间限定key

        • 解决

          • 需要对lockKey有过期时间的设定

          • 修改为 5.0版

            @GetMapping("buy_goods4")
                public String buy_Goods4() {
                    String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
                    try {
                        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value);//setNX
                        stringRedisTemplate.expire(REDIS_LOCK, 10L, TimeUnit.SECONDS);
                        if (!flag) {
                            return "抢锁失败";
                        }
            
            
                        String result = stringRedisTemplate.opsForValue().get("goods:001");// get key ========== 看看库存的数量够不够
                        int goodsNumber = result == null ? 0 : Integer.parseInt(result);
                        if (goodsNumber > 0) {
                            int realNumber = goodsNumber - 1;
                            stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
                            System.out.println("成功买到商品,库存还剩下: " + realNumber + " 件" + "\t 服务提供端口: " + serverPort);
            
                            return "成功买到商品,库存还剩下: " + realNumber + " 件" + "\t 服务提供端口: " + serverPort;
                        } else {
                            System.out.println("商品已经售完/活动结束/调用超时,欢迎下次光临 " + "\t 服务提供端口: " + serverPort);
                        }
                        return "商品已经售完/活动结束/调用超时,欢迎下次光临 " + "\t 服务提供端口: " + serverPort;
                    } finally {
                        stringRedisTemplate.delete(REDIS_LOCK);
                    }
            
            
                }
            
        • 问题:设置key+过期时间分开了,必须要合并成一行,具备原子性

        • 解决:修改为6.0版

          @GetMapping("buy_goods5")
              public String buy_Goods5() {
                  String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
                  try {
                      Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value, 10L, TimeUnit.SECONDS);//setNX
                      stringRedisTemplate.expire(REDIS_LOCK, 10L, TimeUnit.SECONDS);
                      if (!flag) {
                          return "抢锁失败";
                      }
          
          
                      String result = stringRedisTemplate.opsForValue().get("goods:001");// get key ========== 看看库存的数量够不够
                      int goodsNumber = result == null ? 0 : Integer.parseInt(result);
                      if (goodsNumber > 0) {
                          int realNumber = goodsNumber - 1;
                          stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
                          System.out.println("成功买到商品,库存还剩下: " + realNumber + " 件" + "\t 服务提供端口: " + serverPort);
          
                          return "成功买到商品,库存还剩下: " + realNumber + " 件" + "\t 服务提供端口: " + serverPort;
                      } else {
                          System.out.println("商品已经售完/活动结束/调用超时,欢迎下次光临 " + "\t 服务提供端口: " + serverPort);
                      }
                      return "商品已经售完/活动结束/调用超时,欢迎下次光临 " + "\t 服务提供端口: " + serverPort;
                  } finally {
                      stringRedisTemplate.delete(REDIS_LOCK);
                  }
          
          
              }
          
      • 6

        • 问题:张冠李戴,删除了别人的锁

          image-1669467117097

        • 解决:

          • 只能自己删除自己的,不许动别人的

          • 修改为7.0版

            @GetMapping("buy_goods6")
                public String buy_Goods6() {
                    String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
                    try {
                        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value, 10L, TimeUnit.SECONDS);//setNX
                        stringRedisTemplate.expire(REDIS_LOCK, 10L, TimeUnit.SECONDS);
                        if (!flag) {
                            return "抢锁失败";
                        }
            
            
                        String result = stringRedisTemplate.opsForValue().get("goods:001");// get key ========== 看看库存的数量够不够
                        int goodsNumber = result == null ? 0 : Integer.parseInt(result);
                        if (goodsNumber > 0) {
                            int realNumber = goodsNumber - 1;
                            stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
                            System.out.println("成功买到商品,库存还剩下: " + realNumber + " 件" + "\t 服务提供端口: " + serverPort);
            
                            return "成功买到商品,库存还剩下: " + realNumber + " 件" + "\t 服务提供端口: " + serverPort;
                        } else {
                            System.out.println("商品已经售完/活动结束/调用超时,欢迎下次光临 " + "\t 服务提供端口: " + serverPort);
                        }
                        return "商品已经售完/活动结束/调用超时,欢迎下次光临 " + "\t 服务提供端口: " + serverPort;
                    } finally {
                        if (stringRedisTemplate.opsForValue().get(REDIS_LOCK).equalsIgnoreCase(value)) {
                            stringRedisTemplate.delete(REDIS_LOCK);
                        }
            
                    }
            
            
                }
            
        • 问题

          image-1669467132418

          • finally块的判断+del删除操作不是原子性的
        • 解决

          • 用redis自身的事务

            • 命令复习

              image-1669467152483

            • 修改为8.1版

              @GetMapping("buy_goods7_1")
                  public String buy_Goods7_1() {
                      String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
                      try {
                          Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value, 10L, TimeUnit.SECONDS);//setNX
                          stringRedisTemplate.expire(REDIS_LOCK, 10L, TimeUnit.SECONDS);
                          if (!flag) {
                              return "抢锁失败";
                          }
              
              
                          String result = stringRedisTemplate.opsForValue().get("goods:001");// get key ========== 看看库存的数量够不够
                          int goodsNumber = result == null ? 0 : Integer.parseInt(result);
                          if (goodsNumber > 0) {
                              int realNumber = goodsNumber - 1;
                              stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
                              System.out.println("成功买到商品,库存还剩下: " + realNumber + " 件" + "\t 服务提供端口: " + serverPort);
              
                              return "成功买到商品,库存还剩下: " + realNumber + " 件" + "\t 服务提供端口: " + serverPort;
                          } else {
                              System.out.println("商品已经售完/活动结束/调用超时,欢迎下次光临 " + "\t 服务提供端口: " + serverPort);
                          }
                          return "商品已经售完/活动结束/调用超时,欢迎下次光临 " + "\t 服务提供端口: " + serverPort;
                      } finally {
                          while (true) {
                              stringRedisTemplate.watch(REDIS_LOCK);
                              if (stringRedisTemplate.opsForValue().get(REDIS_LOCK).equalsIgnoreCase(value)) {
                                  stringRedisTemplate.setEnableTransactionSupport(true);
                                  stringRedisTemplate.multi();
                                  stringRedisTemplate.delete(REDIS_LOCK);
                                  List<Object> list = stringRedisTemplate.exec();
                                  if (list == null) {
                                      continue;
                                  }
                              }
                              stringRedisTemplate.unwatch();
                              break;
                          }
              
                      }
              
              
                  }
              
          • 用lua脚本

            • Redis调用Lua脚本通过eval命令保证代码执行的原子性

            • 修改为8.2版

              @GetMapping("buy_goods7_2")
                  public String buy_Goods7_2() throws Exception {
                      String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
                      try {
                          Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value, 10L, TimeUnit.SECONDS);//setNX
                          stringRedisTemplate.expire(REDIS_LOCK, 10L, TimeUnit.SECONDS);
                          if (!flag) {
                              return "抢锁失败";
                          }
              
              
                          String result = stringRedisTemplate.opsForValue().get("goods:001");// get key ========== 看看库存的数量够不够
                          int goodsNumber = result == null ? 0 : Integer.parseInt(result);
                          if (goodsNumber > 0) {
                              int realNumber = goodsNumber - 1;
                              stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
                              System.out.println("成功买到商品,库存还剩下: " + realNumber + " 件" + "\t 服务提供端口: " + serverPort);
              
                              return "成功买到商品,库存还剩下: " + realNumber + " 件" + "\t 服务提供端口: " + serverPort;
                          } else {
                              System.out.println("商品已经售完/活动结束/调用超时,欢迎下次光临 " + "\t 服务提供端口: " + serverPort);
                          }
                          return "商品已经售完/活动结束/调用超时,欢迎下次光临 " + "\t 服务提供端口: " + serverPort;
                      } finally {
                          Jedis jedis = RedisUtils.getJedis();
                          String script = "if redis.call('get',KEYS[1]) == ARGV[1] " +
                                  "then " +
                                  "    return redis.call('del',KEYS[1]) " +
                                  "else " +
                                  "    return 0 " +
                                  "end";//这段lua脚本来自于官网:https://redis.io/commands/set/
                          try {
                              Object o = jedis.eval(script, Collections.singletonList(REDIS_LOCK), Collections.singletonList(value));
                              if ("1".equals(o.toString())) {
                                  System.out.println("----- del redis lock ok");
                              } else {
                                  System.out.println("----- del redis lock error");
                              }
              
                          } finally {
                              if (null != jedis) {
                                  jedis.close();
                              }
                          }
              
              
                      }
              
              
                  }
              
              • RedisUtils

                package cn.wangzengqiang.boot_redis02.util;
                
                import redis.clients.jedis.Jedis;
                import redis.clients.jedis.JedisPool;
                import redis.clients.jedis.JedisPoolConfig;
                
                public class RedisUtils {
                    private static JedisPool jedisPool;
                
                    static {
                        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
                        jedisPoolConfig.setMaxTotal(20);
                        jedisPoolConfig.setMaxIdle(10);
                        jedisPool = new JedisPool(jedisPoolConfig, "127.0.0.1", 6379);
                
                    }
                
                    public static Jedis getJedis() throws Exception {
                        if (null != jedisPool) {
                            return jedisPool.getResource();
                        }
                        throw new Exception("JedisPool is not ok");
                    }
                }
                
                
              • code

        • 确保redisLock过期时间大于业务执行时间的问题

          • Redis分布式锁如何续期(续命)
        • 集群+CAP对比zookeeper

          • 对比Zookeeper,重点

            • CAP

              • Redis

                • AP:Redis异步复制造成的锁丢失,比如:主节点没来的及把刚刚set进来这条数据给从节点,就挂了
              • Zookeeper

                • CP
      • 9.综合上述

        • redis集群环境下,我们自己写的也不ok,直接上RedLock之Redisson落地实现

        • 上Redisson

          • RedisConfig

          • 业务代码修改为9.0版

            @GetMapping("buy_goods8")
                public String buy_Goods8() throws Exception {
                    String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
                    RLock redissonLock = redisson.getLock(REDIS_LOCK);
                    redissonLock.lock();
            
                    try {
            
            
                        String result = stringRedisTemplate.opsForValue().get("goods:001");// get key ========== 看看库存的数量够不够
                        int goodsNumber = result == null ? 0 : Integer.parseInt(result);
                        if (goodsNumber > 0) {
                            int realNumber = goodsNumber - 1;
                            stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
                            System.out.println("成功买到商品,库存还剩下: " + realNumber + " 件" + "\t 服务提供端口: " + serverPort);
            
                            return "成功买到商品,库存还剩下: " + realNumber + " 件" + "\t 服务提供端口: " + serverPort;
                        } else {
                            System.out.println("商品已经售完/活动结束/调用超时,欢迎下次光临 " + "\t 服务提供端口: " + serverPort);
                        }
                        return "商品已经售完/活动结束/调用超时,欢迎下次光临 " + "\t 服务提供端口: " + serverPort;
                    } finally {
                        redissonLock.unlock();
            
                    }
            
            
                }
            
        • 9.0版bug及完善到9.1

          • image-1669467181172

          • 业务代码修改为9.1版

             @GetMapping("buy_goods8_1")
                public String buy_Goods8_1() throws Exception {
                    String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
                    RLock redissonLock = redisson.getLock(REDIS_LOCK);
                    redissonLock.lock();
            
                    try {
            
            
                        String result = stringRedisTemplate.opsForValue().get("goods:001");// get key ========== 看看库存的数量够不够
                        int goodsNumber = result == null ? 0 : Integer.parseInt(result);
                        if (goodsNumber > 0) {
                            int realNumber = goodsNumber - 1;
                            stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
                            System.out.println("成功买到商品,库存还剩下: " + realNumber + " 件" + "\t 服务提供端口: " + serverPort);
            
                            return "成功买到商品,库存还剩下: " + realNumber + " 件" + "\t 服务提供端口: " + serverPort;
                        } else {
                            System.out.println("商品已经售完/活动结束/调用超时,欢迎下次光临 " + "\t 服务提供端口: " + serverPort);
                        }
                        return "商品已经售完/活动结束/调用超时,欢迎下次光临 " + "\t 服务提供端口: " + serverPort;
                    } finally {
                        if (redissonLock.isLocked()) {
                            if (redissonLock.isHeldByCurrentThread()) {
                                redissonLock.unlock();
            
                            }
                        }
            
                    }
            
            
                }
            
    • 总结【层层递进,不断完善】

      • synchronized单机版ok,上分布式
        • nginx分布式微服务,单机锁不行
          • 取消单机锁,上redis分布式锁setnx
            • 只加了锁,没有释放锁,出异常的话,可能无法释放锁,必须要在代码层面finally释放锁
              • 宕机了,部署了微服务代码层面根本没有走到finally这块,没办法保证解锁,这个key没有被删除,需要有lockKey的过期时间设定
                • 为redis的分布式锁key,增加过期时间,此外,还必须要setnx+过期时间必须同一行
                  • 必须规定只能自己删除自己的锁,你不能把别人的锁删除了,防止张冠李戴,1删2,2删3
                    • redis集群环境下,我们自己写的也不ok,直接上RedLock之Redisson落地实现

4.Redis缓存过期淘汰策略

  • 粉丝反馈的面试题

    • 生产上你们的redis内存设置多少
    • 如何配置、修改redis的内存大小
    • 如果内存满了,你怎么办
    • redis清理内存的方式?定期删除和惰性删除了解过么
    • redis缓存淘汰策略
    • redis的LRU了解过么?可否手写一个LRU算法
    • 。。。。
  • Redis内存满了怎么办

    • redis默认内存多少?在哪里查看?如何设置修改?

      • 查看redis最大占用内存

        image-1669467201493

      • redis默认内存多少可以用

        image-1669467230512

      • 一般生产上你如何配置

        • 一般推荐redis设置内存为最大物理内存大的3/4
      • 如何修改redis内存设置

        • 通过修改文件配置

          image-1669467242568

        • 通过命令修改

        image-1669467259312

      • 什么命令查看redis内存使用情况

        • info memory【默认info是全部信息,加memory只看与内存有关的信息】
    • 真要打满了会怎么样?

    • 如果redis内存使用超出了设置的最大值会怎么样?

      • 改改配置,故意把最大值设为1个byte试试

        image-1669467274028

    • 结论

      • 设置了maxmemory的选项,假如redis内存使用达到上限,没有加上过期时间就会导致数据写满maxmemory,为了避免类似情况,引出下一章内存淘汰策略
  • redis缓存淘汰策略

    • 往redis里写的数据是怎么没了的

      • redis过期键的删除策略

        image-1669467285415

      • 三种不同的删除策略

        • 定时删除

          image-1669467299404

          • 总结:对CPU不友好,用处理器性能换取存储空间(拿时间换空间)
        • 惰性删除

          image-1669467315579

          • 总结:对memory不友好,用存储空间换取处理器性能(拿空间换时间)
        • 上面两种方案都走极端

          • 定期删除
          • image-1669467327782
            • 定期抽样key,判断是否过期
            • 漏网之鱼
      • 上述步骤都过堂了,还有漏洞吗

        image-1669467342522

      • 内存淘汰策略登场

    • 有哪些(redis6.0.8版本)

      • noeviction:不会驱逐任何key
      • allkeys-lru:对所有key使用LRU算法进行删除
      • volatile-lru:对所有设置了过期时间的key使用LRU算法进行删除
      • allkeys-random:对所有key随机删除
      • volatile-random:对所有设置了过期时间的key随机删除
      • volatile-ttl:删除马上要过期的key
      • allkeys-lfu:对所有key使用LFU算法进行删除
      • volatile-lfu:对所有设置了过期时间的key使用LFU算法进行删除
      • 上面总结:
        • 2*4得8
        • 2个维度
          • 过期键中筛选
          • 所有键中筛选
        • 4个方面
          • LRU(Least Recently Used)
          • LFU(Least Frequently Used)
          • random
          • ttl
        • 8个选项
    • 你平时用哪一种

      • 一般用:allkeys-lru
    • 如何配置,修改

      • 命令

        image-1669467358586

      • 配置文件

        image-1669467372858

5.Redis的LRU算法简介

  • redis的LRU了解过么?可否手写一个LRU算法

    • 是什么

      image-1669467386163

      • 手机case【手机同时打开多个应用在后端进程可以来回切换】

        image-1669467399067

    • 算法来源

      image-1669467413808

    • 设计思想

      image-1669467426120

      • LRU的算法核心是哈希链表

        • 本质就是HashMap+DoubleLinkedList,时间复杂度是o(1),哈希表+双向链表的结合体
      • 动画说明

        image-1669467448806

        image-1669467459886

    • 编码手写如何实现LRU

      • 案例01

        • 参考LinkedHashMap

          image-1669467472928

        • 依赖JDK

          package cn.wangzengqiang.interview.atguigu2019.interview3.lru;
          
          import java.util.LinkedHashMap;
          import java.util.Map;
          
          public class LRUCacheDemo<K, V> extends LinkedHashMap<K, V> {
              private int capacity;//缓存坑位
          
              /**
               * accessOrder     the ordering mode
               * <tt>true</tt> for access-order,
               * <tt>false</tt> for insertion-order
               *
               * @param capacity
               */
              public LRUCacheDemo(int capacity) {
                  //super(capacity, 0.75F, true);
                  super(capacity, 0.75F, false);
                  this.capacity = capacity;
              }
          
              @Override
              protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
                  return super.size() > capacity;
              }
          
              public static void main(String[] args) {
                  LRUCacheDemo lruCacheDemo = new LRUCacheDemo(3);
                  lruCacheDemo.put(1, "a");
                  lruCacheDemo.put(2, "b");
                  lruCacheDemo.put(3, "c");
                  System.out.println(lruCacheDemo.keySet());
                  System.out.println();
          
                  lruCacheDemo.put(4, "d");
                  System.out.println(lruCacheDemo.keySet());
                  System.out.println();
          
                  lruCacheDemo.put(3, "c");
                  System.out.println(lruCacheDemo.keySet());
                  lruCacheDemo.put(3, "c");
                  System.out.println(lruCacheDemo.keySet());
                  lruCacheDemo.put(3, "c");
                  System.out.println(lruCacheDemo.keySet());
                  System.out.println();
                  lruCacheDemo.put(5, "x");
                  System.out.println(lruCacheDemo.keySet());
          
          
              }
          }
          /**
           * accessOrder = true运行结果:
           * [1, 2, 3]
           * <p>
           * [2, 3, 4]
           * <p>
           * [2, 4, 3]
           * [2, 4, 3]
           * [2, 4, 3]
           * <p>
           * [4, 3, 5]
           *
           * accessOrder = false运行结果:
           * [1, 2, 3]
           *
           * [2, 3, 4]
           *
           * [2, 3, 4]
           * [2, 3, 4]
           * [2, 3, 4]
           *
           * [3, 4, 5]
           */
          
      • 案例02

        • 不依赖JDK(参考AQS思想+LinkedHashMap实现思想)

          package cn.wangzengqiang.interview.atguigu2019.interview3.lru;
          
          import java.util.HashMap;
          import java.util.Map;
          
          public class LRUCacheDemo1 {
          
              // map负责查找,构建一个虚拟的双向链表,它里面安装的就是一个个Node节点,作为数据载体
          
              //1.构造一个Node节点,作为数据载体
              class Node<K, V> {//参考AbstractQueuedSynchronizer
                  K key;
                  V value;
                  Node<K, V> prev;
                  Node<K, V> next;
          
                  public Node() {
                      this.prev = this.next = null;
                  }
          
                  public Node(K key, V value) {
                      this.key = key;
                      this.value = value;
                      this.prev = this.next = null;
                  }
              }
          
              //2.构建一个虚拟的双向链表,里面安放的就是我们的Node
              class DoubleLinkedList<K, V> {
                  Node<K, V> head;
                  Node<K, V> tail;
          
                  //2.1 构造方法
          
                  public DoubleLinkedList() {
                      head = new Node<>();
                      tail = new Node<>();
                      head.next = tail;
                      tail.prev = head;
                  }
          
                  //2.2 添加到头
                  public void addHead(Node<K, V> node) {
                      node.next = head.next;
                      node.prev = head;
                      head.next.prev = node;
                      head.next = node;
                  }
          
                  //2.3 删除节点
                  public void removeNode(Node<K, V> node) {
                      node.next.prev = node.prev;
                      node.prev.next = node.next;
                      node.prev = null;
                      node.next = null;
                  }
          
                  //2.4获得最后一个节点
                  public Node getLast() {
                      return tail.prev;
                  }
              }
          
              private int cacheSize;
              Map<Integer, Node<Integer, Integer>> map;
              DoubleLinkedList<Integer, Integer> doubleLinkedList;
          
              public LRUCacheDemo1(int cacheSize) {
                  this.cacheSize = cacheSize;//坑位
                  map = new HashMap<>();//查找
                  doubleLinkedList = new DoubleLinkedList<>();
          
              }
          
              public int get(int key) {
                  if (!map.containsKey(key)) {
                      return -1;
                  }
                  Node<Integer, Integer> node = map.get(key);
                  doubleLinkedList.removeNode(node);
                  doubleLinkedList.addHead(node);
          
                  return node.value;
          
              }
          
              //saveOrUpdate method
              public void put(int key, int value) {
                  if (map.containsKey(key)) {//update
                      Node<Integer, Integer> node = map.get(key);
                      node.value = value;
                      map.put(key, node);
                      doubleLinkedList.removeNode(node);
                      doubleLinkedList.addHead(node);
          
                  } else {
                      if (map.size() == cacheSize) {//坑位满了
                          Node<Integer, Integer> lastNode = doubleLinkedList.getLast();
          
                          map.remove(lastNode.key);
                          doubleLinkedList.removeNode(lastNode);
                      }
                      //才是新增
                      Node<Integer, Integer> newNode = new Node<>(key, value);
                      map.put(key, newNode);
                      doubleLinkedList.addHead(newNode);
          
                  }
          
              }
          
              public static void main(String[] args) {
                  LRUCacheDemo1 lruCacheDemo = new LRUCacheDemo1(3);
                  lruCacheDemo.put(1, 1);
                  lruCacheDemo.put(2, 2);
                  lruCacheDemo.put(3, 3);
                  System.out.println(lruCacheDemo.map.keySet());
                  System.out.println();
          
                  lruCacheDemo.put(4, 1);
                  System.out.println(lruCacheDemo.map.keySet());
                  System.out.println();
          
                  lruCacheDemo.put(3, 1);
                  System.out.println(lruCacheDemo.map.keySet());
                  lruCacheDemo.put(3, 1);
                  System.out.println(lruCacheDemo.map.keySet());
                  lruCacheDemo.put(3, 1);
                  System.out.println(lruCacheDemo.map.keySet());
                  System.out.println();
                  lruCacheDemo.put(5, 1);
                  System.out.println(lruCacheDemo.map.keySet());
          
          
              }
          }
          /**
           * 运行结果:
           * [1, 2, 3]
           *
           * [2, 3, 4]
           *
           * [2, 3, 4]
           * [2, 3, 4]
           * [2, 3, 4]
           *
           * [3, 4, 5]
           */
          

彩蛋

1.查看redis版本

  • linux命令行:redis-server -v
  • redis命令行:info

2.SSM->SSR(其中R是Redis)

3.单机锁与分布式锁

image-1669467507796

4.redis安装

5.git嵌套提交

adding embedded git repository: boot_redis01

解决:https://blog.csdn.net/m0_48945359/article/details/125007430

6.git回退与重新提交:

回退: git reset --hard HEAD^

重新提交: git push --force

7.程序先完成,再完美

先完成,后完美(先功能,后性能)

8.锁之不见不散与过期不候

  • 不见不散-synchronized
  • 过期不候-ReentrantLock

9.压测工具:apache jmeter

apache jmeter

10.nginx windows版安装

nginx windows

start nginx
tasklist /fi "imagename eq nginx.exe"
  • nginx访问:http://localhost/

    • 报错:使用 命令>start nginx导致,点击nginx.exe也不行,估计是改配置文件改坏了

      An error occurred.
      Sorry, the page you are looking for is currently unavailable.
      Please try again later.
      
      If you are the system administrator of this resource then you should check the error log for details.
      
      Faithfully yours, nginx.
      
    • 看nginx的error.log

      2022/10/29 21:42:01 [error] 19004#17912: *1 connect() failed (10061: No connection could be made because the target machine actively refused it) while connecting to upstream, client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.1", upstream: "http://127.0.0.1:1111/", host: "localhost"
      
      
  • nginx进程杀死不了:是因为kill的不是master进程

11.天上飞的理念必然有落地的实现

12.redis不是mysql,不是什么数据都存储,一般存储热点数据