Redis 原理 及常见应用场景

 

目录

Redis 原理 及常见应用场景

redis常见应用场景

缓存雪崩

缓存穿透

缓存击穿

Redis支持的数据类型:

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

存储结构:

刚刚上面你有提到redis通讯协议(RESP ),能解释下什么是RESP?有什么特点?

Redis 有哪些架构模式?讲讲各自的特点


redis常见应用场景

1、缓存热点数据

如果你使用的是服务器端内容渲染,你又不想为每个请求重新渲染每个页面,就可以使用 Redis 把常被请求的内容缓存起来,能够大大的降低页面请求的延迟,已经有很多框架用Redis来缓存页面,这就是页面静态化的一种方式。

2.Session 存储

这可能是应用最广的点了,相比较于类似 memcache 的 session 存储,Redis 具有缓存数据持久化的能力,当缓存因出现问题而重启后,之前的缓存数据还在那儿,这个就比较实用,避免了因为session突然消失带来的用户体验问题。

3.发布和订阅

pub/sub   是 Redis 内置的一个非常强大的特性,例如可以创建一个实时的聊天系统、社交网络中的通知触发器等等。

缓存雪崩

对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机。缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据库必然扛不住,它会报一下警,然后就挂了。此时,如果没有采用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。

这就是缓存雪崩。

 

redis原理及用途 redis的原理和使用场景_缓存

 

缓存雪崩的事前事中事后的解决方案如下。

  • 事前:redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃。
  • 事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。
  • 事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。

 

缓存穿透

对于系统A,假设一秒 5000 个请求,结果其中 4000 个请求是黑客发出的恶意攻击。

黑客发出的那 4000 个攻击,缓存中查不到,每次你去数据库里查,也查不到。

举个栗子。数据库 id 是从 1 开始的,结果黑客发过来的请求 id 全部都是负数。这样的话,缓存中不会有,请求每次都“视缓存于无物”,直接查询数据库。这种恶意攻击场景的缓存穿透就会直接把数据库给打死。

redis原理及用途 redis的原理和使用场景_redis原理及用途_02

 

解决方式很简单,每次系统 A 从数据库中只要没查到,就写一个空值到缓存里去,比如 set -999 UNKNOWN。然后设置一个过期时间,这样的话,下次有相同的 key 来访问的时候,在缓存失效之前,都可以直接从缓存中取数据。

 

缓存击穿

缓存击穿,就是说某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。

解决方式也很简单,可以将热点数据设置为永远不过期;或者基于 redis or zookeeper 实现互斥锁,等待第一个请求构建完缓存之后,再释放锁,进而其它请求才能通过该 key 访问数据。

Redis支持的数据类型:

String(字符串) 、Hash(哈希)、list(列表)、set(无序集合)、zset(有序集合)

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

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

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

RDB:

rdb是Redis DataBase缩写

功能核心函数rdbSave(生成RDB文件)和rdbLoad(从文件加载内存)两个函数

redis原理及用途 redis的原理和使用场景_redis_03

 

AOF:

Aof是Append-only file缩写

redis原理及用途 redis的原理和使用场景_缓存_04

 

每当执行服务器(定时)任务或者函数时flushAppendOnlyFile 函数都会被调用, 这个函数执行以下两个工作

aof写入保存:

WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件

SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。

存储结构:

  内容是redis通讯协议(RESP )格式的命令文本存储。

比较:

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

2、aof比rdb更安全也更大

3、rdb性能比aof好

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

刚刚上面你有提到redis通讯协议(RESP ),能解释下什么是RESP?有什么特点?

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

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

 

Redis 有哪些架构模式?讲讲各自的特点

redis原理及用途 redis的原理和使用场景_Redis_05

 

 

特点:简单

问题:

1、内存容量有限 2、处理能力有限 3、无法高可用。

主从复制

 

redis原理及用途 redis的原理和使用场景_缓存_06

Redis 的复制(replication)功能允许用户根据一个 Redis 服务器来创建任意多个该服务器的复制品,其中被复制的服务器为主服务器(master),而通过复制创建出来的服务器复制品则为从服务器(slave)。 只要主从服务器之间的网络连接正常,主从服务器两者会具有相同的数据,主服务器就会一直将发生在自己身上的数据更新同步 给从服务器,从而一直保证主从服务器的数据相同。

 

 

特点:

1、master/slave 角色

2、master/slave 数据相同

3、降低 master 读压力在转交从库

问题:

无法保证高可用

没有解决 master 写的压力

哨兵

redis原理及用途 redis的原理和使用场景_缓存_07

 

Redis sentinel 是一个分布式系统中监控 redis 主从服务器,并在主服务器下线时自动进行故障转移。其中三个特性:

监控(Monitoring):    Sentinel  会不断地检查你的主服务器和从服务器是否运作正常。

提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。

自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作。

特点:

1、保证高可用

2、监控各个节点

3、自动故障迁移

缺点:主从模式,切换需要时间丢数据

没有解决 master 写的压力

集群(直连型):

 

redis原理及用途 redis的原理和使用场景_缓存_08

从redis 3.0之后版本支持redis-cluster集群,Redis-Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。

特点:

1、无中心架构(不存在哪个节点影响性能瓶颈),少了 proxy 层。

2、数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布。

3、可扩展性,可线性扩展到 1000 个节点,节点可动态添加或删除。

4、高可用性,部分节点不可用时,集群仍可用。通过增加 Slave 做备份数据副本

5、实现故障自动 failover,节点之间通过 gossip 协议交换状态信息,用投票机制完成 Slave到 Master 的角色提升。

缺点:

1、资源隔离性较差,容易出现相互影响的情况。

2、数据通过异步复制,不保证数据的强一致性

Java实现Redis发布订阅

上代码:

想使用jedis先引入依赖

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>
复制代码

建立一个Publisher (发布者)

public class Publisher extends Thread{
    private final JedisPool jedisPool;
    public Publisher(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }
    
    @Override
    public void run() {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        Jedis jedis = jedisPool.getResource();   //连接池中取出一个连接
        while (true) {
            String line = null;
            try {
                line = reader.readLine();
                if (!"quit".equals(line)) {
                    jedis.publish("mychannel", line);   //从 mychannel 的频道上推送消息
                } else {
                    break;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
复制代码

 

再建立一个订阅者

public class Subscriber extends JedisPubSub {
    public Subscriber(){}
    @Override
    public void onMessage(String channel, String message) {       //收到消息会调用
        System.out.println(String.format("receive redis published message, channel %s, message %s", channel, message));
    }
    @Override
    public void onSubscribe(String channel, int subscribedChannels) {    //订阅了频道会调用
        System.out.println(String.format("subscribe redis channel success, channel %s, subscribedChannels %d",
                channel, subscribedChannels));
    }
    @Override
    public void onUnsubscribe(String channel, int subscribedChannels) {   //取消订阅 会调用
        System.out.println(String.format("unsubscribe redis channel, channel %s, subscribedChannels %d",
                channel, subscribedChannels));
    }
}
复制代码

这里订阅者需要继承JedisPubSub,来重写它的三个方法。用途 注释上已经写了,很简单。

我们这里只是定义了一个订阅者,下面去订阅频道。

public class SubThread extends Thread {
    private final JedisPool jedisPool;
    private final Subscriber subscriber = new Subscriber();
    private final String channel = "mychannel";
    public SubThread(JedisPool jedisPool) {
        super("SubThread");
        this.jedisPool = jedisPool;
    }
    @Override
    public void run() {
        System.out.println(String.format("subscribe redis, channel %s, thread will be blocked", channel));
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();   //取出一个连接
            jedis.subscribe(subscriber, channel);    //通过subscribe 的api去订阅,入参是订阅者和频道名
        } catch (Exception e) {
            System.out.println(String.format("subsrcibe channel error, %s", e));
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }
}
复制代码

最后,再写一个测试类去跑一下。键盘输入消息,订阅者就会触发onMessage方法

public class PubSubDemo {
    public static void main( String[] args )
    {
        // 连接redis服务端
        JedisPool jedisPool = new JedisPool(new JedisPoolConfig(), "127.0.0.1", 6379);
        
        System.out.println(String.format("redis pool is starting, redis ip %s, redis port %d", "127.0.0.1", 6379));
        SubThread subThread = new SubThread(jedisPool);  //订阅者
        subThread.start();
        Publisher publisher = new Publisher(jedisPool);    //发布者
        publisher.start();
    }
}
复制代码

总结:缓存雪崩是指缓存机器意外发生了全盘宕机,大量请求全部落在数据库上,数据库扛不住挂掉。重启数据库后,数据库又被新的流量打死。

缓存雪崩的事前事中事后的解决方案如下。

  • 事前:redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃。
  • 事中:本地 ehcache 缓存 + hystrix (海思吹克思)限流&降级,避免 MySQL 被打死。
  • 事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。

缓存穿透是指大量的请求在缓存中查不到,直接查询数据库,造成数据库宕机。

解决方法:每次系统从数据库中只要没查到,就写一个控制到缓存里去,如果再有相同的 key 来访问的时候,在缓存失效之前,都可以直接从缓存中取数据。

缓存击穿是指某个key非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个key失效的瞬间,大量的请求就击穿了缓存,直接请求数据库。

解决方法:可以将热点数据设置为永不过期,或者基于redis or zookeeper 实现互斥锁,等待第一个请求构建完缓存之后,再释放锁,进而其它请求才能通过该 key 访问数据。

redis支持的数据类型:String(字符串) 、Hash(哈希)、list(列表)、set(无序集合)、zset(有序集合)

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

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