本文主要介绍redis的基本原理和特性

更新于 2021-04-18


什么是Redis

Redis是C语言编写的开源的基于键值对的存储服务系统,支持多种数据结构的一种高性能,功能丰富的NoSQL数据库。

Redis很像memcache,整个数据都是放在内存中进行操作的,定期通过异步方式持久化到硬盘上。

Redis有时候也会用户缓存,相比于memcache,如果是单纯的缓存场景,memcache性能是优于redis的;


Redis的特点

  • 速度快:单线程模型,数据存储在内存,最高可达10W OPS;
  • 支持持久化:提供AOF和RDB方式的持久化;
  • 支持多种数据结构:支持字符串、哈希、列表、集合、有序集合等数据结构;
  • 支持多种编程语言:提供TCP接口,支持Python,Java,Lua等;
  • 功能丰富:支持发布订阅、lua脚本、事物、pipeline等功能;
  • 代码简单,使用简单:核心代码量2W行,个性化定制方便;不依赖外部的库;
  • 支持主从复制
  • 支持高可用和分布式:2.8版本后提供Sentinel功能以支持高可用;3.0版本后支持分布式;
  • 原子性:redis所有操作都是原子的(要么成功要么失败);

redis支持的数据结构有:字符串(string)、哈希(hash)、字符串列表(list)、字符串集合(set)、有序字符串集合(sorted set)等


Redis应用场景

  • 缓存系统:使用redis在server层和存储层中间构建存储层,加速请求的响应,减轻后端压力;
  • 计数器:一条微博的转发数、评论数、点赞数;
  • 消息队列系统
  • 排行榜功能
  • 社交网络:粉丝数、关注数、共同关注等;
  • 实时系统:垃圾邮件系统、过滤器;

Redis和memcache比较

redis和memcache都是内存数据库,在缓存上应用都比较多,它们在以下的方面会有一些区别:

线程模型

memcache采用多线程模型,基于IO多路复用技术,主线程接收到请求后分配给子线程进行处理。

  • 优点:
    • 这样当某个请求耗时较长,不会影响到其他的请求。
  • 缺点:
    • CPU多线程切换带来性能损耗;
    • 多线程访问共享资源必定加锁,导致性能损耗;

redis采用IO多路复用技术,但处理请求是单线程模型,接收请求到处理请求都在一个线程中完成。

  • 优点:
    • 减少了CPU上下文切换的损耗;
    • 没有多线程访问共享资源的加锁竞争;
  • 缺点:
    • 一个请求处理时间较长,会影响后面的请求;
    • 无法利用cpu多核特性;

redis使用应避免执行复杂的耗时操作,如果key的数据量较大,则可能memcache的性能会更好一些。

数据结构

memcache仅支持string类型的操作,且value的大小必须在1MB以下,过期时间不能超过30天。

redis支持多种数据类型,例如:stringsetlisthash等。

淘汰策略

memcahched必须设置实例的内存上限,达到上限后会触发LRU淘汰机制,不常使用的冷数据会被优先淘汰。

redis没有设置内存上限,只要内存够用就会使用最大的内存,同时支持多种淘汰策略:

  • volatile-lru:从过期key中按LRU机制淘汰
  • allkeys-lru:在所有key中按LRU机制淘汰
  • volatile-random:在过期key中随机淘汰key
  • allkeys-random:在所有key中随机淘汰key
  • volatile-ttl:优先淘汰最近要过期的key
  • volatile-lfu:在所有key中按LFU机制淘汰
  • allkeys-lfu:在过期key中按LFU机制淘汰

管道与事务

Redis还支持管道功能,客户端一次性打包发送多条命令到服务端,服务端依次处理客户端发来的命令。这样可以减少来回往来的网络IO次数,提供高访问性能。

另外它还支持事务,这里所说的事务并不是MySQL那样严格的事务模型,这种事务模型是Redis特有的。

一般事务会配合管道一块使用,客户端一次性打包发送多条命令到服务端,并且标识这些命令必须严格按顺序执行,不能被其他客户端打断。同时执行事务之前,客户端可以告诉服务端某个key稍后会进行相关操作,如果这个客户端在操作这个key之前,有其他客户端对这个key进行更改,那么当前客户端在执行这些命令时会放弃整个事务操作,保证一致性。

持久化

memcache不支持持久化数据,如果服务器宕机,则会丢失全部的数据;

redis支持aof和rdb两种方式的持久化数据的方式,可以避免宕机带来的数据丢失的问题;

高可用

memcached没有主从复制的架构,仅支持单机部署。

redis支持主从复制架构,两个节点组成主从架构,从可以实时同步主的数据,提高整个Redis服务的可用性。同时Redis还提供了哨兵节点,在主节点宕机时,主动把从节点提升为主节点,继续提供服务。主从两个节点还可以提供读写分离功能,进一步提高程序访问的性能。

集群

Memcached的集群化是在客户端采用一致性哈希算法向指定节点发送数据,当一个节点宕机时,其他节点会分担这个节点的请求。

Redis集群化采用的是每个节点维护一部分虚拟槽位,通过key的哈希计算,将key映射到具体的虚拟槽位上,这个槽位再映射到具体的Redis节点。同时每个Redis节点都包含至少一个从节点,组成主从架构,进一步提高每个节点的高可用能力。当增加或下线节点时,需要手动触发数据迁移,重新进行哈希槽位映射。

怎么选择

如果你的业务需要各种数据结构给予支撑,同时要求数据的高可用保障,那么选择Redis是比较合适的。

如果你的业务非常简单,只是简单的set/get,并且对于内存使用并不高,那么使用简单的Memcached足够。


Redis数据结构

字符串

字符串(string)是redis最简单的数据结构,其内部表示就是一个字符数组。字符串常用于缓存信息,例如将用户信息使用json序列换为字符串,存入redis进行缓存。字符串最大长度为512MB。

列表

列表(list)在redis中插入和删除操作非常快,时间复杂度为O(1),但是索引定位很慢,时间复杂度为O(n),它相当于一个链表,每个元素都是用双向指针顺序,可同时支持前向和后向遍历。

当列表弹出一个元素后,该数据节后会被自动删除,内存会被回收。列表常用来做异步队列使用,将需要延后处理的任务结构体序列化成字符串塞进redis的列表,另一个线程从这个列表中轮训数据进行处理。

hash(字典)

redis的hash内部存储了很多键值对,结构和hashmap一样。redis字典的值只能是字符串。

set(集合)

redis的set相当于java中的hashset,内部的键值对是无序的、唯一的。内部相当于是一个特殊的字典,字典中所有的value都是NULL。

当集合中最后一个元素被移除后,数据结构会被删除,内存会被回收;set常被用来哦存储在某次活动中中奖的用户ID,因为有去重功能所以不会出现重复的用户;

zset(有序列表)

zset是redis最具特色的数据结构,它一方面具有set保证内部value唯一性的特点,另一方面给每个value赋予了一个score,代表value的排序权重。

zset中最后一个元素被移除后,数据结构会被删除,内存会被回收;zset常用来存储粉丝列表,value是粉丝的用户id,score是关注事件,可以根据关注事件进行排序;也用来存储学生成绩,value是学生id,score是学生考试成绩,可以根据score进行排序得到名次;

数据结构的特点

  1. list、set、zset、hash成为容器型数据结构,遵循create if not existsdrop if no elements规则;
  2. 所有数据结构都可以设置过期时间,时间到了就会删除对象;
  3. 一个字符串如果调用set方法修改了它,那么设置的过期时间就会消失;

持久化方案

Redis提供了两种数据持久化的方式:AOF和RDB,用于防止服务器宕机导致数据丢失。

RDB

rdb是Redis DataBase缩写,核心函数为rdbSave(生成RDB文件)和rdbLoad(从文件加载内存)两个函数。

img

AOF

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

  1. WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件;
  2. SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中;

img

两种方式比较

  • aof文件比rdb更新频率高,优先使用aof还原数据;
  • aof比rdb更安全也更大;
  • rdb性能比aof好;
  • 如果两个都配了优先加载AOF;

Redis特性

多数据库特性

一个redis实例可以包含多个数据库,一个客户端可以指定连接redis实例其中的一个数据库。

一个redis实例可以提供16个数据库,编号从0 到 15。客户端默认是连接0号数据库。

连接指定的数据库

可以通过select指令加上数据库编号来连接数据库,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
127.0.0.1:6379> select 1
OK

127.0.0.1:6379[1]> keys *
(empty list or set)

127.0.0.1:6379[1]> select 0
OK

127.0.0.1:6379> keys *
1) "mya3"
2) "mya1"
3) "qq"
...

数据库间移动key

可以将一个key从一个库移动到另一个库,使用move指令,例如:

1
2
3
4
5
6
7
8
9
// 将0库中的myset这个key移动到1库中
127.0.0.1:6379> move myset 1
(integer) 1

127.0.0.1:6379> select 1
OK

127.0.0.1:6379[1]> keys *
1) "myset"

事务特性

事务执行将被串行执行,执行期间redis将不会为其他客户端提供服务,从而保证事务中的指令原子化执行。redis中实现事务特性使用multi、exec、discard指令。

  • multi:将会创建一个事务,其后的参数将被视为事务中的命令;
  • exec:exec将会执行事务;
  • discard:事务回滚

如果在exec之前出现网络问题连接不上redis,则事务中的命令不会被执行;如果exec之后出现网络问题连接不上redis,则事务将继续执行。

multi

multi将会开启一个事务,其后的指令将会存在事务命令队列中,直到执行。

1
2
3
4
5
6
7
8
9
10
11
127.0.0.1:6379> set num 2
OK

127.0.0.1:6379> get num
"2"

127.0.0.1:6379> incr num
QUEUED

127.0.0.1:6379> incr num
QUEUED

可以看到,事务命令已经被存在队列中。

exec

exec将会执行事务命令队列中的命令,相当于mysql中的commit。

1
2
3
127.0.0.1:6379> exec 
1) (integer) 3
2) (integer) 4

discard

discard是回滚操作,相当于mysql中的rollback。

1
2
3
4
5
6
7
8
9
10
11
12
127.0.0.1:6379> set user tom
OK
127.0.0.1:6379> get user
"tom"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set user jerry
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> get user
"tom"