4. Redis常见面试问题与API使用

1 面试常见问题

1.1 击穿
  1. 服务常见架构

    1. 通常请求需要查询数据,先尝试从redis中查询,如果能取到直接返回
    2. 如果取不到,改为从数据库中取,取到数据后更新缓存并返回结果
    3. 如果数据库中也取不到,直接返回空结果

在这里插入图片描述

  1. redis作为缓存时,其key可能会由于过期、lru、lfu算法而被清理。高并发地访问恰好被清理掉的某个key对应的数据,就会导致高并发访问数据库,这就是缓存的击穿

  2. 解决缓存击穿的客户端代码

    1. get k1:先尝试从缓存中查找k1
    2. setnx k1:如果缓存中没有k1,设置k1的值,且该命令会为k1上锁,且redis是单进程单实例,因此一定会有一个客户端先获取到k1的锁
      1. 上锁成功:访问数据库,将结果更新缓存
      2. 上锁失败:后续的客户端会上锁失败,上锁失败后sleep一段时间,再重复整个流程。先get k1,此时能从缓存获取到k1,就不会再访问数据库
  3. 如果第一个进程在获取到锁后,挂掉,会导致锁永远无法释放,因此可以为锁设置过期时间

  4. 假设过期时间1s,如果确实第一个客户端获取锁后,访问后段数据库需要10s,此时锁被自动释放,后续的客户端进程再次击穿缓存,访问到后端数据库。因此可以考虑为获取到锁的客户端启动一个守护进程,守护进程去监控客户端状态,如果发现客户端并没有死,只是访问数据库时间较长,可以延长锁的过期时间

1.2 穿透
  1. 可以使用布隆过滤器
    1. client包含布隆算法和数据
    2. 客户端持有算法,redis存放数据
    3. redis集成布隆(redis bloom)
  2. 布隆过滤器无法删除
    1. 可以使用布谷鸟这种可以删除的过滤器替代
    2. 从数据库删除后,在redis中存放一个value为空的key,客户端发现该产品的value为空,就知道根本没有该产品信息
1.3 雪崩
  1. 大量的key同时失效,从而间接导致高并发到达数据库,称为雪崩
  2. 解决方案
    1. key必须在某时间点过期(例如0点必须过期)
      1. 参考击穿的解决方案
      2. 业务层增加判断,当0点时,服务器上进行延时,比如先sleep一会再继续访问
    2. key的过期与时点无关:将过期时间设置为随机
1.4 分布式锁

在这里插入图片描述

  1. 同一个客户端用户,同时发出了多个请求,分布式环境下,多台机器上部署的多个service进行了并发操作,导致插入了冗余数据

  2. 修改逻辑为service需要先去抢锁,抢到锁的service,才去数据库操作,这个锁就是分布式锁

  3. 分布式锁的实现

    1. Redis
    2. Zookeeper
    3. Memcached
    4. Chubby
  4. redis实现分布式锁

    //1. 加锁
    //a. 不使用setnx(lock_sale_商品ID,1)+expire(lock_sale_商品ID, 30)是为了防止加锁后,还未设置过期时间这段时间内进程挂掉,导致的死锁
    //b. 如果线程A执行了30s没执行完,但此时由于key到期,线程B进入,而此时A处理完成,del时,可能误将B的锁删除,因此需要将value设置为线程id,且当当前线程id和加锁时的线程id相同,才允许删除
    //c. 但无法解决B可能会和A访问同一段代码的问题
    //d. 可以为A开启一个守护线程,守护线程从A进行了29s后开始执行,之后每20s执行一次,如果发现A未处理完成,使用expire命令为A的锁延长20s,当A结束后,关闭其守护进程。如果节点1忽然断电,由于线程A和守护线程在同一个进程,守护线程也会停下。这把锁到了超时的时候,没人给它续命,也就自动释放了
    String threadId = Thread.currentThread().getId()
    set(key,threadId ,30,NX)
    
    //2. 解锁
    if(threadId .equals(redisClient.get(key)){
        del(key)
    }
    

2 API

  1. spring官网–Projects–Spring Data–Spring Data Redis–LEARN–Reference Doc.
  2. java中可以使用lettuce、Jedis、Redisson等开源api访问redis,spring中也集成了它们,但现在默认使用lettuce对redis进行连接
  3. Jedis和lettuce的简单使用:可以参考各自github中的文档,都是需要先通过maven引入,然后输入ip和port来创建Jedis或lettuce实例,最后使用具体方法发送命令给redis
  4. Jedis线程不安全,多线程使用同一个Jedis实例会有问题,通常采用一个称为JedisPool的连接池,为每个线程分配一个连接,但这样会导致大量socket连接在服务端上,成本较高
  5. RedisConnectionFactory:用于建立与Redis的连接
  6. RedisTemplate:用于真正发送指令给redis,由RedisConnectionFactory创建,可以使用RedisTemplate这种高阶的API来操作redis,也可以通过RedisTemplate来获取一个RedisConnection对象,从而调用lettuce、Jedis、Redisson等API的原生方法(低阶别API)
  7. Serializers:由于Redis是二进制安全的,该类可以用于指定如何将数据转为二进制码,比如是直接通过java序列化转为二进制码,还是先转为一个jason格式的字符串,再转为二进制码。如果写和读使用的二进制转换的机制不同(例如写入使用java序列化,读取使用python的逻辑),可能会导致客户端无法正确解析读取到的二进制码
2.1 使用spring boot连接redis
  1. File–New–Project–Spring Initializr–com.msb.spring.redis–NoSQL–Spring Data Redis(Access+Driver)

  2. application.properties

    spring.redis.host=172.128.246.128
    spring.redis.port=6379
    
  3. TestRedis

    package com.msb.spring.redis.demo;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Component;
    
    @Component
    public class TestRedis {
        //1. spring boot会自动根据application.properties中配置的内容,创建RedisConnectionFactory,并通过RedisConnectionFactory创建RedisTemplate
        //2. 之后根据Autowired注释,将容器中的RedisTemplate对象自动注入到redisTemplate中
        @Autowired
        RedisTemplate redisTemplate;
        public void testRedis(){
            //3. 表示获取redis的string类型的命令
            redisTemplate.opsForValue().set("hello","china");
            System.out.println(redisTemplate.opsForValue().get("hello"));
        }
    }
    
  4. DemoApplication

    package com.msb.spring.redis.demo;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.ConfigurableApplicationContext;
    
    @SpringBootApplication
    public class DemoApplication {
        public static void main(String[] args) {
            //1. 用于初始化spring容器
            ConfigurableApplicationContext ctx = SpringApplication.run(DemoApplication.class, args);
            TestRedis redis = ctx.getBean(TestRedis.class);
            redis.testRedis();
        }
    
    }
    
  5. redis服务默认的安全策略是禁止远端访问,且绑定必须通过127.0.0.1访问,即telnet 192.168.246.128 6379不通。需要通过修改配置文件protected-mode no开启远端访问,#bind 127.0.0.1取消ip绑定。也可以通过本地客户端连接后执行config set protected-mode no命令临时开启远端访问。config get *可以查看当前redis服务的所有配置参数

  6. 执行DemoApplication后,发现执行成功,正确打印china,但使用redis-cli直接连接redis服务时,发现存放的key和value并不是hello和china,而是一堆乱码。这是因为在使用redisTemplate将key和value转为二进制码时,默认使用的是java的序列化机制,也就是将一个String对象序列到了redis中,而不是将字符串本身直接转为二进制码

2.2 不使用java序列化
  1. TestRedis

    //StringRedisTemplate要求key和value都必须为string类型,同时会将String对象,当作字符串转为二进制码,这样存放到redis中的key和value就不再是乱码
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    public void testRedis(){
      stringRedisTemplate.opsForValue().set("hello","china");
      System.out.println(stringRedisTemplate.opsForValue().get("hello"));
    }
    
  2. 使用低阶API操作redis

    //redis中存放的key和value也是正常的
    RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
    connection.set("hello02".getBytes(),"mashibing".getBytes());
    System.out.println(new String(connection.get("hello02".getBytes())));
    
2.3 hash操作
  1. 操作hash

    HashOperations<String, Object, Object> hash = stringRedisTemplate.opsForHash();
    hash.put("sean","name","zhouzhilei");
    //由于使用了stringRedisTemplate,因此无法使用12这个整型作为value,必须使用string类型作为value
    hash.put("sean","age","12");
    //打印:{name=zhouzhilei, age=12}
    System.out.println(hash.entries("sean"));
    
  2. 使用Jackson2HashMapper将java对象转为Map类型,从而存放到redis的hash类型变量中

    1. 引入pom依赖

      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-json</artifactId>
      </dependency>
      
    2. Person

      package com.msb.spring.redis.demo;
      
      public class Person {
          private String name;
          private Integer age;
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          public Integer getAge() {
              return age;
          }
      
          public void setAge(Integer age) {
              this.age = age;
          }
      }
      
    3. TestRedis

      Person p = new Person();
      p.setName("zhangsan");
      p.setAge(16);
      //1. 选择true时,会采用扁平化的方式转为jason
      //2. 不采用扁平化时,jason内容为
      //{"firstname" : "Jon", "lastname" : "Snow", "address" : { "city" : "Castle Black", "country" : "The North" }, "date" : "1561543964015", "localDateTime" : "2018-01-02T12:13:14"}
      //3. 采用扁平化处理时,jason内容为
      //{"firstname" : "Jon", "lastname" : "Snow", "address.city" : "Castle Black", "address.country" : "The North", "date" : "1561543964015", "localDateTime" : "2018-01-02T12:13:14"}
      //public class Person {
      //    String firstname;
      //    String lastname;
      //    Address address;
      //    Date date;
      //    LocalDateTime localDateTime;
      //}
      //
      //public class Address {
      //    String city;
      //    String country;
      //}
      Person p = new Person();
      p.setName("zhangsan");
      p.setAge(16);
      Jackson2HashMapper jm = new Jackson2HashMapper(objectMapper, false);
      redisTemplate.opsForHash().putAll("sean01",jm.toHash(p));
      Map map = redisTemplate.opsForHash().entries("sean01");
      Person per = objectMapper.convertValue(map,Person.class);
      System.out.println(per.getName());
      
  3. 由于使用了RedisTemplate,所以会对Key和Value进行java序列化,导致存入redis的字节码只能用java程序读取。但如果改为使用StringRedisTemplate,由于Person中存在Integer类型的age,Person转为的Map中的value为Integer类型,会导致放入redis失败

  4. 可以为RedisTemplate或StringRedisTemplate设置hash中value的序列化器,StringRedisTemplate默认使用的就是StringRedisSerializer,因此可以将String对象以字符串的方式转为二进制码

  5. MyTemplate

    package com.msb.spring.redis.demo;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
    
    @Configuration
    public class MyTemplate {
      	//1. 之前spring管理了一个StringRedisTemplate类型的bean,id为stringRedisTemplate,现在加上一个id为ooxx的StringRedisTemplate类型的bean,下面StringRedisTemplate类型的变量使用新的bean进行注入
        @Bean
        public StringRedisTemplate ooxx(RedisConnectionFactory fc){
            StringRedisTemplate tp = new StringRedisTemplate(fc);
          	//2. 可以分别为key、value、hash中的key、hash中的value设置序列化器,由于stringRedisTemplate已经规定了key、value、hash中的key都以字符串的形式转为二进制码,因此只需要对hash的value进行特殊设置即可
            tp.setHashValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
            return tp;
        }
    }
    
  6. TestRedis

    @Autowired
    @Qualifier("ooxx")
    StringRedisTemplate stringRedisTemplate;
    
    ...
      
    Person p = new Person();
    p.setName("zhangsan");
    p.setAge(16);
    Jackson2HashMapper jm = new Jackson2HashMapper(objectMapper, false);
    stringRedisTemplate.opsForHash().putAll("sean01",jm.toHash(p));
    Map map = stringRedisTemplate.opsForHash().entries("sean01");
    Person per = objectMapper.convertValue(map,Person.class);
    System.out.println(per.getName());
    
2.4 实现聊天室
RedisConnection cc = stringRedisTemplate.getConnectionFactory().getConnection();
//	订阅频道,此时使用redis-cli连接一个客户端,执行publish ooxx "xiaohong: Hello",订阅中就会收到该消息,收到后触发onMessage,就会打印消息
cc.subscribe(new MessageListener() {
  @Override
  public void onMessage(Message message, byte[] pattern) {
    byte[] body = message.getBody();
    System.out.println(new String(body));
  }
}, "ooxx".getBytes());

while(true){
  // 向频道发布消息
  stringRedisTemplate.convertAndSend("ooxx","我: hello  from wo zi ji ");
  try {
    Thread.sleep(3000);
  } catch (InterruptedException e) {
    e.printStackTrace();
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
图像识别技术在病虫害检测中的应用是一个快速发展的领域,它结合了计算机视觉和机器学习算法来自动识别和分类植物上的病虫害。以下是这一技术的一些关键步骤和组成部分: 1. **数据收集**:首先需要收集大量的植物图像数据,这些数据包括健康植物的图像以及受不同病虫害影响的植物图像。 2. **图像预处理**:对收集到的图像进行处理,以提高后续分析的准确性。这可能包括调整亮度、对比度、去噪、裁剪、缩放等。 3. **特征提取**:从图像中提取有助于识别病虫害的特征。这些特征可能包括颜色、纹理、形状、边缘等。 4. **模型训练**:使用机器学习算法(如支持向量机、随机森林、卷积神经网络等)来训练模型。训练过程中,算法会学习如何根据提取的特征来识别不同的病虫害。 5. **模型验证和测试**:在独立的测试集上验证模型的性能,以确保其准确性和泛化能力。 6. **部署和应用**:将训练好的模型部署到实际的病虫害检测系统中,可以是移动应用、网页服务或集成到智能农业设备中。 7. **实时监测**:在实际应用中,系统可以实时接收植物图像,并快速给出病虫害的检测结果。 8. **持续学习**:随着时间的推移,系统可以不断学习新的病虫害样本,以提高其识别能力。 9. **用户界面**:为了方便用户使用,通常会有一个用户友好的界面,显示检测结果,并提供进一步的指导或建议。 这项技术的优势在于它可以快速、准确地识别出病虫害,甚至在早期阶段就能发现问题,从而及时采取措施。此外,它还可以减少对化学农药的依赖,支持可持续农业发展。随着技术的不断进步,图像识别在病虫害检测中的应用将越来越广泛。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值