消息中间件——Redis—入门(二)

前言

Redis 是 C 语言开发的一个开源的高性能键值对(key-value)的NoSQL内存数据库,可以用作数据库、缓存、消息中间件等。

优点:

速度快(每秒10万次读写)、多种数据结构、支持多种客户端语言、功能丰富、支持集群分布式。

缺点:

成本高昂(消耗cpu、内存)、持久化影响性能。

使用场景

  1. 缓存系统

缓存是Redis最常见的应用场景,之所有这么使用,主要是因为Redis读写性能优异。而且逐渐有取代memcached,成为首选服务端缓存的组件。而且,Redis内部是支持事务的,在使用时候能有效保证数据的一致性。

  1. 丰富的数据格式性能更高,应用场景丰富

Redis相比其他缓存,有一个非常大的优势,就是支持多种数据类型。

  • string——适合最简单的k-v存储,类似于memcached的存储结构,短信验证码,配置信息等,就用这种类型来存储。
  • hash——一般key为ID或者唯一标示,value对应的就是详情了。如商品详情,个人信息详情,新闻详情等。
  • list——因为list是有序的,比较适合存储一些有序且数据相对固定的数据。如省市区表、字典表等。因为list是有序的,适合根据写入的时间来排序,如:最新的***,消息队列等。
  • set——可以简单的理解为ID-List的模式,如微博中一个人有哪些好友,set最牛的地方在于,可以对两个set提供交集、并集、差集操作。例如:查找两个人共同的好友等。
  • Sorted Set——是set的增强版本,增加了一个score参数,自动会根据score的值进行排序。比较适合类似于top 10等不根据插入的时间来排序的数据。
  1. 作为分布式锁

redis支持setnx操作(即key不存在的情况下设置value),这种特性常用来做分布式锁的实现。

  1. 过期时间设置

最常见的就是:短信验证码、具有时间性的商品展示等。无需像数据库还要去查时间进行对比。因为使用比较简单,就不赘述了。

通信

  1. TCP协议

    Redis底层网络通信协议其实是通过TCP来完成的。

  2. RESP协议。

    Redis客户端和服务器端使用的序列化协议,它是特意为Redis设计的,同时也可以用于其他软件工程。

    RESP可以序列化多种不同的数据类型,比如:整型、字符串、数组。错误是一种特定的类型。Redis客户端把参数用数组来表示。回复的是一种特殊的数据格式。

  3. pipeline管道

    pipeline管道就是解决执行大量命令时、会产生大量数据来回次数而导致延迟的技术。其实原理比较简单,pipeline是把所有的命令一次发过去,避免频繁的发送、接收带来的网络开销,redis在打包接收到一堆命令后,依次执行,然后把结果再打包返回给客户端。

  4. 单线程

    redis虽然是单线程,却是直接操作内存,同时使用了IO多路复用,异步非阻塞,效率依然可观。

请求处理过程

消息中间件——Redis—基础(二)_2020-10-29-13-54-02.png

可以看到整个请求过程操作都离不开文本事件处理器

  1. 当Redis启动时,在所监听的 socket 上 创建文件事件处理器,监听 socket 建立连接的事件。

  2. 当用户在客户端中键入一个命令请求时, 客户端会将这个命令请求转换成协议格式(RESP), 然后建立到服务器的socket,建立client,注册socket读取事件处理器,将协议格式的命令请求发送给服务器(读取事件处理器)。

  3. 服务器端读取套接字中协议格式的命令请求, 并将其保存到客户端状态的输入缓冲区里面。

  4. 对输入缓冲区中的命令请求进行分析, 提取出命令请求中包含的命令参数, 以及命令参数的个数, 然后分别将参数和参数个数保存到客户端状态的 argv 属性和 argc 属性里面。

  5. 调用命令执行器, 执行客户端指定的命令,命令实现函数会将命令回复保存到客户端的输出缓冲区里面。

  6. 注册socket写入事件处理器,从输入缓冲区写回数据到socket,关闭client,清空缓冲区。

IO多路复用模型

多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。

在 I/O 多路复用模型中,最重要的函数调用就是 select,该方法的能够同时监控多个文件描述符的可读可写情况,当其中的某些文件描述符可读或者可写时,select 方法就会返回可读以及可写的文件描述符个数。

Redis 服务采用 Reactor 的方式来实现文件事件处理器(每一个网络连接其实都对应一个文件描述符)

消息中间件——Redis—基础(二)_2020-10-29-14-09-01.png

文件事件处理器使用 I/O 多路复用模块同时监听多个 FD,当 accept、read、write 和 close 文件事件产生时,文件事件处理器就会回调 FD 绑定的事件处理器。

虽然整个文件事件处理器是在单线程上运行的,但是通过 I/O 多路复用模块的引入,实现了同时对多个 FD 读写的监控,提高了网络通信模型的性能,同时也可以保证整个 Redis 服务实现的简单。

基础数据结构

redis提供了多种数据结构,我们主要了解其中五种基础数据结构:String、Hash、List、Set、Sorted Set。

Redis 的 key 是字符串类型,但是 key 中不能包括边界字符,由于 key 不是 binary safe
的字符串,所以像”my key”和”mykey\n”这样包含空格和换行的 key 是不允许的。

首先redis内部使用一个redisObject对象来表示所有的key和value.

1
2
3
4
5
6
7
typedef struct redisObject { 
unsigned [type] 4;
unsigned [encoding] 4;
unsigned [lru] REDIS_LRU_BITS;
int refcount;
void *ptr;
} robj;

简单介绍一下这几个字段:

  • type:数据类型,就是我们熟悉的string、hash、list等。
  • encoding:内部编码,其实就是本文要介绍的数据结构。指的是当前这个value底层是用的什么数据结构。因为同一个数据类型底层也有多种数据结构的实现,所以这里需要指定数据结构。
  • REDIS_LRU_BITS:当前对象可以保留的时长。这个我们在后面讲键的过期策略的时候讲。
  • refcount:对象引用计数,用于GC。
  • ptr:指针,指向以encoding的方式实现这个对象的实际地址。
  1. String(字符串)

    应用场景:

    存储key-value键值对,这个比较简单不细说了

  2. list(列表)

    应用场景:

    由于list它是一个按照插入顺序排序的列表,所以应用场景相对还较多的,例如:

    • 消息队列:lpop和rpush(或者反过来,lpush和rpop)能实现队列的功能
    • 朋友圈的点赞列表、评论列表、排行榜:lpush命令和lrange命令能实现最新列表的功能,每次通过lpush命令往列表里插入新的元素,然后通过lrange命令读取最新的元素列表。
  3. hash (字典)

    应用场景:

    • 购物车:hset [key] [field] [value] 命令, 可以实现以用户Id,商品Id为field,商品数量为value,恰好构成了购物车的3个要素。
    • 存储对象:hash类型的(key, field, value)的结构与对象的(对象id, 属性, 值)的结构相似,也可以用来存储对象。
  4. set(集合)

    应用场景:

    • 好友、关注、粉丝、感兴趣的人集合:
    • 首页展示随机:美团首页有很多推荐商家,但是并不能全部展示,set类型适合存放所有需要展示的内容,而srandmember命令则可以从中随机获取几个。
    • 存储某活动中中奖的用户ID ,因为有去重功能,可以保证同一个用户不会中奖两次。
  5. zset(有序集合)

    应用场景:

    • zset 可以用做排行榜,但是和list不同的是zset它能够实现动态的排序,例如: 可以用来存储粉丝列表,value 值是粉丝的用户 ID,score 是关注时间,我们可以对粉丝列表按关注时间进行排序。
    • zset 还可以用来存储学生的成绩, value 值是学生的 ID, score 是他的考试成绩。 我们对成绩按分数进行排序就可以得到他的名次。

客户端

目前主流的客户端有三种,Jedis、Lettuce、Redisson,我们从几个方面比较以下它们。

  1. 性能

    Jedis的性能比lettuce和Redisson都要差一点,三者的主要差异在于以下:

    • Jedis使用同步和阻塞IO的方式,不支持异步;lettuce和Redisson支持异步,底层是基于netty框架的事件驱动作为通信层。

    • Jedis设计上就是基于线程不安全来设计,一个连接只能被一个线程使用,但是可以结合连接池来提高其性能;lettuce和Redis基于线程安全来设计的,一个连接是被共享使用的,但是也提供了连接池,主要用于事务以及阻塞操作的命令。

    • lettuce和Redisson支持异步流的方式。

  2. 功能

    • Jedis: 提供比较全面的redis原生指令的支持,上层封装比较弱,集群特性支持度非常低,高级特性几乎没有。

    • lettuce: 高级redis客户端,支持各种模式的redis连接和操作,高级特性几乎没有。

    • Redisson: 高级redis客户端,支持各种模式的redis连接和操作,同时提供一大堆的实用功能。

  3. Redisson

    Redisson支持了很多高级功能:分布式锁、事务、RPC、分布式任务调度等;

    实际上单是使用Redisson作为Spring的客户端就足够了。但是Redisson实际上对字符串操作支持性差。

    个人倾向lettuce,如果需要分布式锁、分布式集合等分布式高级特性可以结合 Redisson 使用。

文章目录
  1. 1. 前言
  2. 2. 使用场景
  3. 3. 通信
  4. 4. 请求处理过程
  5. 5. IO多路复用模型
  6. 6. 基础数据结构
  7. 7. 客户端
|