通常我们都把数据存到关系型数据库中,但为了提升应用的性能,我们应该把访频率高且不会经常变动的数据缓存到内存中。Redis 没有像 MySQL 这类关系型数据库那样强大的查询功能,需要考虑如何把关系型数据库中的数据,合理的对应到缓存的 key-value 数据结构中。

Redis特点

  • Redis 支持AOF 和 RDB 两种数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载使用
  • Redis 不仅仅支持简单的 key-value 数据存储,同时还提供了 list(列表)、set(集合)、zset()、hash(哈希)等数据结构的存储
  • Redis支持数据备份,即 master-slave 模式的数据备份

Redis优势

  • 高性能:Redis 读的速度是110000次/s,写的速度是81000/s。
  • 丰富的数据类型:如上特点
  • 原子性:所有操作都是原子性,还支持对几个操作合并后的原子性操作,并且支持事务
  • 丰富的特效:支持发布与订阅
  • 超大key和value:Redis 的 key 和 String 类型的 value 大小可达 500M

String 数据类型

String 类型是 Redis 最常见、最基本的数据类型。String类型是二进制安全的,可以把图片,视频文件保存到String中

应用场景

(1)存储数据库中某个字段的值

Redis::set(sprintf('user_ticket_%s', $userId), $userTicket);
// 原生
set user_ticket:1 "********"

(2)生成自增id

当 Redis 的 String 类型的值为整数形式时,Redis 可以把它当做整数一样进行自增(incr)、自减(decr)操作。由于 Redis 所有操作都是原子性的,所以不必担心多客户端连接时出现的事物问题

hash 数据类型

hash 类型像关系型数据库的一张表,hash 的 key 是唯一值,value 部分是 hashmap 的结构。hash 数据类型在存储类似用户对象时比 String 数据类型更快、更灵活、内存开销更小,而且不需要对数据进行转换和解析。数据模型如下。

应用场景

存储对象类型数据,hash 的结构可以任意添加或删除上图的 field

$user = User::find(1);
Redis::hmset(sprintf('agent_dashboard_summary_%s', $user->id), $user);
// 原生
hmset user:1 name "李三" age 18 birthday "20010101" 

list 数据类型

list 是按照插入顺序排序的字符串链表,可以再头部和尾部插入新的元素(双向链表实现,两端添加元素的时间复杂度为 O(1))。插入元素是,如果 key 不存在,Redis 会为该 key 创建一个新的链表,如果链表中所有的元素都被移除,该 key 也会从 Redis 中移除。

常见操作时用 lpush 命令在 list 头部插入元素,用 rpop 命令在 list尾取出数据

应用场景

(1)消息队列

Redis 的 list 数据类型对于大部分使用者来说,是实现队列服务最经济、最简单的方式。

(2)最新内容

因为 list 结构数据是查询两端及附近数据性能非常高,不受链表长度影响;但如果在链表中间部分插入数据,性能会越来越差。

(3) 列表旋转

利用 RPOPLPUSH source destination 可以原子性的返回并移除存储在 source 的列表的尾部元素,并把该元素存储在 destination 列表的头部。如果 source 不存在,那么会返回 nil 值,并且不会执行任何操作。如果 source 和 destination 相同,那么这个操作等同于移除列表尾部元素并把该元素放入列表头部。

  1. 一个典型的例子就是服务器上的监控程序:它们需要在尽可能短的时间内,并行地检查一批网站,确保它们的可访问性。下面例子每次 RPOPLPUSH 后都会拿到本次的尾部元素
127.0.0.1:6379> LPUSH ips 192.168.0.10
(integer) 1
127.0.0.1:6379> LPUSH ips 192.168.0.11
(integer) 2
127.0.0.1:6379> LPUSH ips 192.168.0.12
(integer) 3
127.0.0.1:6379> LPUSH ips 192.168.0.13
(integer) 4
127.0.0.1:6379> LPUSH ips 192.168.0.14
(integer) 5
127.0.0.1:6379> RPOPLPUSH ips ips
"192.168.0.10"
127.0.0.1:6379> RPOPLPUSH ips ips
"192.168.0.11"
127.0.0.1:6379> RPOPLPUSH ips ips
"192.168.0.12"
127.0.0.1:6379> RPOPLPUSH ips ips
"192.168.0.13"
127.0.0.1:6379> RPOPLPUSH ips ips
127.0.0.1:6379> LRANGE ips 0 5
1) "192.168.0.14"
2) "192.168.0.13"
3) "192.168.0.12"
4) "192.168.0.11"
5) "192.168.0.10"

set 数据类型

set 数据类型是一个集合(没有排序,不重复),可以对 set 类型的数据进行添加、删除、判断是否存在等操作,时间复杂度 O(1)。set 类型提供多个 set 之间的集合运算,如求交集、并集、补集,在 Redis 内部操作效率很高。

应用场景

set 类型的特点是不重复且无序的一组数据,并具有丰富的计算功能,在一些特定场景中可以高效的解决一般关系型数据库不方便的工作。

(1) 共同的好友列表

社交类产品中,获取两个人或者多个人的共同好友,两个人或者多个人共同关注的微博类似这样的功能,用 MySQL 的话操作比较复杂,可以把每个人的好友 id 存到集合中,获取共同好友的操作就可以简单到一个取交集的命令搞定。

set 的内部实现是一个 vulue 永远为 null 的 hashmap,实际就是通过计算 hash 的方式来快速重排,这也是 set 能提供判断一个成员是否在集合内的原因

eg.


127.0.0.1:6379> sadd user:mike tom lucy face kobe
(integer) 4
127.0.0.1:6379> sadd user:tom mike lucy face kobe
(integer) 4
127.0.0.1:6379> sadd user:face mike tom lucy kobe
(integer) 4
127.0.0.1:6379> sadd user:lucy mike tom face kobe
(integer) 4
# 查看 mike 和 tom 的共同好友
127.0.0.1:6379> sinter user:mike user:tom
1) "kobe"
2) "face"
3) "lucy"
127.0.0.1:6379>

sorted set 数据类型

在 set 数据类型的基础上每个元素关联了一个分数,往有序集合中插入数据时自动根据这个分数排序

应用场景

(1)比如根据好友的亲密度排序显示好友列表

# face 和五位好友的王者亲密度
127.0.0.1:6379> zadd user:face 10 top 30 lucy 20 mike 50 dev
(integer) 4

# face 和好友亲密度排序
127.0.0.1:6379> zrevrange user:face 0 -1
1) "dev"
2) "lucy"
3) "mike"
4) "top"

# 增加 face 和 top 的亲密度
127.0.0.1:6379> zincrby user:face 50 top
"60"
127.0.0.1:6379> zrevrange user:face 0 -1
1) "top"
2) "dev"
3) "lucy"
4) "mike"

类似需求还会出现在根据文章阅读量或者点赞对文章进行排序、学生考试成绩、精准清理数据库中过期数据等