• redis面试题


    String数据类型、List 数据类型、Hash数据类型(散列类型)、set数据类型(无序集合)、Sorted Set数据类型 (zset、有序集合)。

    1、String是 redis 最基本的类型,最大能存储 512MB 的数据,String类型是二进制安全的,即可以存储任何数据、比如数字、图片、序列化对象等。INCR key: key值递增加1 ( key值必须为整数)、DECR key: key值递增减1 (key值必须为整数)、GETSET key value: 获取key值并返回,同时给key设置新值。

    2、Hash数据类型(散列类型)、hash用于存储对象。可以采用这样的命名方式:对象类别和ID构成键名,使用字段表示对象的属性,而字段值则存储属性值。 如:存储 ID 为 2 的汽车对象。如果Hash中包含很少的字段,那么该类型的数据也将仅占用很少的磁盘空间。每一个Hash可以存储4294967295个键值对。

    3、Sorted Set数据类型,有序集合,元素类型为Sting,元素具有唯一性, 不能重复每个元素都会关联–个double类型的分数score(表示权重),可以通过权重的大小排序,元素的score可以相同应用范围:可以用于一个大型在线游戏的积分排行榜。每当玩家的分数发生变化时,可以执行ZADD命 令更新玩家的分数,此后再通过ZRANGE命令获取积分TOP10的用户信息。

    4、Redis内部使用一个redisObject对象来表示所有的key和value,type代表一个value对象具体是何种数据类型,encoding是不同数据类型在redis内部的存储方式,比如:type=string代表value存储的是一个普通字符串,那么对应的encoding可以是raw或者是int,如果是int则代表实际redis内部是按数值型类存储和表示这个字符串的,当然前提是这个字符串本身可以用数值表示,比如:"123" "456"这样的字符串。

    问:redis为什么快

    1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);

    2、数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;

    3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;

    4、使用多路I/O复用模型,非阻塞IO;

    5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;

    什么是Redis持久化?Redis有哪几种持久化方式?优缺点是什么?

    持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。

    Redis 提供了两种持久化方式:RDB(默认) 和AOF

    比较:

    1、aof文件比rdb更新频率高,优先使用aof还原数据。

    2、aof比rdb更安全也更大

    3、rdb性能比aof好

    4、如果两个都配了优先加载AOF

    redis通讯协议(RESP ),能解释下什么是RESP?有什么特点?

    RESP 是redis客户端和服务端之前使用的一种通讯协议;

    RESP 的特点:实现简单、快速解析、可读性好

    什么是缓存穿透?如何避免?什么是缓存雪崩?何如避免?

    缓存穿透

    一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。

    如何避免?

    1:对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。

    2:对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。

    缓存雪崩

    当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃。

    如何避免?

    1:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。

    2:做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期

    3:不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

    三、redis的命中率

    用户在访问缓存中的数据时,并不是100%会返回的,可能有不返回的情况,这种情况下就得去DB查询数据,为了提高系统的性能,我们需要提高缓存的命中率。redis中info命令可以查询到基本信息。

    命中率 = keyspace_hits / (keyspace_misses + keyspace_hits)

    合理的redis配置可以提高命中率

    如何保证mysql和redis数据一致性

    1.延迟双删   在写库前后都进行【redis.del(key)】操作,并且设定合理的超时时间;

    2.异步更新缓存(基于订阅binlog的同步机制) 

    技术整体思路:

    MySQL binlog增量订阅消费+消息队列+增量数据更新到redis

    1)读Redis:热数据基本都在Redis

    2)写MySQL:增删改都是操作MySQL

    3)更新Redis数据:MySQ的数据操作binlog,来更新到Redis

    Redis更新

    (1)数据操作主要分为两大块:

    一个是全量(将全部数据一次写入到redis)一个是增量(实时更新)

    这里说的是增量,指的是mysql的update、insert、delate变更数据。

    (2)读取binlog后分析 ,利用消息队列,推送更新各台的redis缓存数据。

    这样一旦MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新。

    其实这种机制,很类似MySQL的主从备份机制,因为MySQL的主备也是通过binlog来实现的数据一致性。

    这里可以结合使用canal(阿里的一款开源框架),通过该框架可以对MySQL的binlog进行订阅,而canal正是模仿了mysql的slave数据库的备份请求,使得Redis的数据更新达到了相同的效果。

    当然,这里的消息推送工具你也可以采用别的第三方:kafka、rabbitMQ等来实现推送更新Redis

     Redis 的数据淘汰策略有哪些

    a、noeviction:返回错误当内存限制达到,并且客户端尝试执行会让更多内存被使用的命令。

    b、allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。

    c、volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。

    d、allkeys-random: 回收随机的键使得新添加的数据有空间存放。

    e、volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键

    f、volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。

    redis实现延时队列的两种方式

    一,redis的过期key监控

      1.开启过期key监听  在redis的配置里把这个注释去掉  notify-keyspace-events Ex   然后重启redis

      2. 使用redis过期监听实现延迟队列 继承KeyExpirationEventMessageListener类,实现父类的方法,就可以监听key过期时间了。当有key过期,就会执行这里。这里就把需要的key过滤出来,然后发送给消息队列。

      注意:尽量单机运行,因为多台机器都会执行,浪费cpu,增加数据库负担。二是,机器频繁部署的时候,如果有时间间隔,会出现数据的漏处理。

    一,redis的zset实现延迟队列

      1.生产者很简单,其实就是利用zset的特性,给一个zset添加元素而已,而时间就是它的score。

    1 public void produce(Integer taskId, long exeTime) {
    2   System.out.println("加入任务, taskId: " + taskId + ", exeTime: " + exeTime + ", 当前时间:" + LocalDateTime.now());
    3 
    4   RedisOps.getJedis().zadd(RedisOps.key, exeTime, String.valueOf(taskId));
    5 }

      2.消费者代码,把已经过期的zset中的元素给删除掉,然后处理数据。

     1 public void consumer() {
     2   Executors.newSingleThreadExecutor().submit(new Runnable() {
     3     @Override
     4     public void run() {
     5       while (true) {
     6         Set<String> taskIdSet = RedisOps.getJedis().zrangeByScore(RedisOps.key, 0, System.currentTimeMillis(), 0, 1);
     7         if (taskIdSet == null || taskIdSet.isEmpty()) {
     8           System.out.println("没有任务");
     9   
    10         } else {
    11           taskIdSet.forEach(id -> {
    12             long result = RedisOps.getJedis().zrem(RedisOps.key, id);
    13             if (result == 1L) {
    14               System.out.println("从延时队列中获取到任务,taskId:" + id + " , 当前时间:" + LocalDateTime.now());
    15             }
    16           });
    17         }
    18         try {
    19           TimeUnit.MILLISECONDS.sleep(100);
    20         } catch (InterruptedException e) {
    21           e.printStackTrace();
    22         }
    23       }
    24     }
    25   });
    26 }
  • 相关阅读:
    Netty入门——客户端与服务端通信
    使用配置文件自定义Ribbon配置
    使用Java代码自定义Ribbon配置
    Spring Cloud Ribbon入门
    负载均衡简介
    常见的几种负载均衡算法
    Eureka编程
    Eureka多机高可用
    Maven项目打包成可执行Jar文件
    Eureka单机高可用伪集群配置
  • 原文地址:https://www.cnblogs.com/chdchd/p/14242063.html
Copyright © 2020-2023  润新知