# 一:前言
Redis 是互联网技术领域使用最为广泛的存储中间件,它是「Remote Dictionary Service」的首字母缩写,也就是「远程字典服务」。
Redis 会将所有数据都存放在内存中,所以读写性能非常惊人。不仅如此,Redis 还可以将内存的数据利用快照(RDB)和日志(AOF)的形式保存在硬盘上。
Redis提供了键过期、发布订阅、事务、流水线、Lua脚本等附加功能。
面试经历
下面这段面试经历摘自《Redis深度历险:核心原理和应用实践 - 钱文品》
在面试后端工程师 Redis 技能的时候,面试官通常问的第一个问题就是"Redis 能用来做什么?",第一个回答往往都会是「缓存」。缓存确实是 Redis 使用最多的领域,它相比Memcache 而言更加易于理解、使用和控制。
可是如果再进一步问"还有呢?",大多数同学就会开始皱眉头,只有一小部分人会回答「分布式锁」。如果你就分布式锁再深入问下去,他们基本就会开始摇头:我们项目里面Redis 的锁方法都是别人(应该是架构师)封装好的,拿过来直接使用,内部细节没有去了解过,也没有必要了解。
对类似的场景,我深有体会。因为关于 Redis 的面试题,之前准备了很多,但是真正能用上的却很少。当面试的同学频繁地回复「不知道、没用过」的时候,再继续深入追问已经毫无意义,这时候就需要切换话题了。偶尔遇上几个能持续很多回合的同学,他们总能使人眼前一亮。如果再拓展一下周边知识点,就会发现这些人往往也会有所涉猎,这时我在心中已经暗暗地对这位同学伸出了大拇指。
这样的面试经历事后也让我深刻反思:架构师的技能很高,对提升团队研发效率很有帮助,我们非常钦佩和羡慕。但是普通开发者如果习惯于在架构师封装好的东西之上,只专注于做业务开发,那久而久之,在技术理解和成长上就会变得迟钝甚至麻木。从这个角度看,架构师也可能成为普通开发者的"敌人",他的强大能力会让大家变成"温室的花朵",一旦遇到环境变化就会不知所措。
其实很多业务场景,如果仅仅是会使用某项技术、框架,那是再简单不过了。但随着业务发展,系统的用户量、并发量涨上来之后,现有系统的问题就会层出不穷地暴露出来。如果不能深入地了解系统、技术和框架背后的深层原理,很多问题根本无法理解到本质,更谈不上解决,临时抱佛脚也于事无补。
# 二:Redis 特性
- 速度快
- C语言
- 单线程架构,预防了多线程可能产生的竞争问题
- 基于键值对的数据结构服务器
- 字符串
- 位图(Bitmaps)
- HyperLogLog
- 哈希
- 列表
- 集合
- 有序集合
- GEO(地理信息位置)
- 字符串
- 丰富的功能
- 提供了键过期功能,可以用来实现缓存;
- 提供了发布订阅功能,可以用来实现消息系统;
- 支持 Lua 脚本功能,可以利用 Lua 创造出新的 Redis 命令;
- 提供了简单的事务功能,能在一定程度上保证事务特性;
- 提供了流水线(Pipeline)功能,这样客户端能将一批命令一次性传到 Redis,减少了网络的开销。
- 简单稳定
- 客户端语言多
- 持久化
- RDB
- AOF
- 主从复制
- 高可用的分布式
# 三:Redis 使用场景
1. 缓存
降低后端数据源的压力。同时提供了灵活控制最大内存和内存溢出后的淘汰策略。一个合理的缓存设计能够为一个网站的稳定保驾护航。常用String类型,例如:热点数据缓存(例如报表、明星出轨),对象缓存、全页缓存、可以提升热点数据的访问数据。
2. 排行榜系统
Redis提供了列表和有序集合数据结构,合理地使用这些数据结构可以很方便地构建各种排行榜系统。
id 为 60001 的新闻点击数加1:zincrby hotNews:20190926 1 n6001
获取今天点击最多的15条:zrevrange hotNews:20190926 0 15 withscores
3. 计数器应用
视频网站的播放数、电商网站浏览数。如果并发量很大对传统关系型数据的性能是一种挑战。Redis天然支持计数功能。常常使用 int 类型,incr 方法。允许一定的延迟,先写入Redis再定时同步到数据库。
4. 社交网络
赞 / 踩、粉丝、共同好友 / 喜好、推送、下拉刷新等。由于社交网站访问量通常比较大,而且传统的关系型数据不太适合保存这种类型的数据,Redis提供的数据结构可以相对比较容易地实现这些功能。
例如某条微博ID是 t1001,用户ID是 u3001
- 点赞了这条微博:
sadd like:t1001 u3001
- 取消点赞:
srem like:t1001 u3001
- 是否点赞:
sismember like:t1001 u3001
- 点赞的所有用户:
smembers like:t1001
- 点赞数:
scard like:t1001
5. 消息队列系统
Redis 提供了发布订阅和阻塞队列的功能,虽然和专业的消息队列比还不够足够强大,但是对于一般的消息队列功能基本可以满足。
List 提供了两个阻塞的弹出操作:blpop/brpop
,可以设置超时时间。
- blpop:
blpop key1 timeout
移除并获取列表的第一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 - brpop:
brpop key1 timeout
移除并获取列表的最后一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
6. 数据共享分布式
因为Redis是分布式的独立服务,可以在多个应用之间共享。例如:分布式Session
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
2
3
4
7. 分布式锁
String 类型 setnx
方法,只有不存在时才能添加成功,返回true
public static boolean getLock(String key) {
Long flag = jedis.setnx(key, "1");
if (flag == 1) {
jedis.expire(key, 10);
}
return flag == 1;
}
public static void releaseLock(String key) {
jedis.del(key);
}
2
3
4
5
6
7
8
9
10
11
8. 全局ID
int类型,使用 incrby
方法,利用原子性,incrby userid 1000
分库分表的场景,一次性拿一段。
9. 限流
以访问者的ip和其他信息作为key,访问一次增加一次计数,超过次数则放回false。
10. 位统计
String 类型的bitcount。
字符是以8位二进制存储的
> set k1 a
> setbit k1 6 1
> setbit k1 7 0
> get k1
## 6 7 代表的a的二进制位的修改
# a 对应的ASCII码是97,转换为二进制数据是01100001
# b 对应的ASCII码是98,转换为二进制数据是01100010
## 因为bit非常节省空间(1 MB=8388608 bit),可以用来做大数据量的统计
2
3
4
5
6
7
8
9
例如:在线用户统计,留存用户统计
> setbit onlineusers 01
> setbit onlineusers 11
> setbit onlineusers 20
2
3
支持按位与、或、非、异或等等操作
计算出7天都在线的用户
BITOP "AND" "7_days_both_online_users" "day_1_online_users" "day_2_online_users" ... "day_7_online_users"
11. 购物车
String或hash。所有String可以做的hash都可以做
- key:用户id;field:商品id;value:商品数量
- +1:hincr;-1:hdecr;删除:hdel;全选:hgetall;商品数:hlen
12. 用户消息时间线timeline
list,双向链表,直接作为timeline就好了。插入有序
13. 抽奖
自带一个随机获取值
spop myset
14. 商品标签
例如,用 tags:i5001 来维护商品所有的标签
sadd tags:i5001
画面清晰细腻sadd tags:i5001
真彩清晰显示屏sadd tags:i5001
流程至极
15. 商品筛选
## 获取差集
> sdiff set1 set2
## 获取交集(intersection )
> sinter set1 set2
## 获取并集
> sunion set1 set2
2
3
4
5
6
假设:iPhone20 上市了
> sadd brand:apple iPhone11
> sadd brand:ios iPhone11
> sad screensize:6.0-6.24 iPhone11
> sad screentype:lcd iPhone 11
2
3
4
赛选商品,苹果的、ios的、屏幕在6.0-6.24之间的,屏幕材质是LCD屏幕
sinter brand:apple brand:ios screensize:6.0-6.24 screentype:lcd
16. 用户关注、推荐模型
follow 关注 fans 粉丝
相互关注:
sadd 1:follow 2
sadd 2:fans 1
sadd 1:fans 2
sadd 2:follow 1
2
3
4
我关注的人也关注了他(取交集):sinter 1:follow 2:fans
可能认识的人:
- 用户1可能认识的人(差集):
sdiff 2:follow 1:follow
- 用户2可能认识的人:
sdiff 1:follow 2:follow
# 四:Redis不可以做什么
从 数据规模 的角度看:数据可以分为大规模数据和小规模数据,Redis 的数据是存放在内存中的,如果数据量非常大,例如每天有几亿的用户行为数据,使用 Redis 来存储的话,基本上是个无底洞,经济成本相当的高。
从 数据冷热 的角度看:数据分为 热数据 和 冷数据,热数据通常是指需要频繁操作的数据,反之为冷数据,例如对于视频网站来说,视频基本信息 基本上在各个业务线都是经常要操作的数据,而用户的观看记录不一定是经常需要访问的数据,这里暂且不讨论两者数据规模的差异,单纯站在数据冷热的角度上看,视频信息属于热数据,用户观看记录属于冷数据。如果将这些冷数据放在 Redis 中,基本上是对于内存的一种浪费,但是对于一些热数据可以放在 Redis 中加速读写,也可以减轻后端存储的负载,可以说是事半功倍。
# 五:Redis建议
# 1. 切勿当作黑盒使用,开发与运维同样重要
在实际运维和使用Redis的过程中发现,很多线上的故障和问题都是由于完全把Redis当做黑盒造成的,如果不了解Redis的单线程模型,有些开发者会在有上千万个键的Redis上执行 keys *
操作,如果不了解持久化的相关原理,会在一个写操作量很大的Redis上配置自动保存RDB。而且在很多公司内只有专职的关系型数据库DBA,并没有NoSQL的相关运维人员,也就是说开发者很有可能会自己运维Redis,对于Redis的开发者来说既是好事又是坏事。站在好的方面看,开发人员可以通过运维Redis真正了解Redis的一些原理,不单纯停留在开发上。站在坏的方面看,Redis的开发人员不仅要支持开发,还要承担运维的责任,而且由于运维经验不足可能会造成线上故障。但是从实际经验来看,运维足够规模的Redis会对用好Redis更加有帮助。
# 2. 阅读源码
Redis是开源项目,Redis的代码量相对于许多NoSQL数据库来说是非常小的,也就意味着作为普通的开发和运维人员也是可以"吃透"Redis的。通过阅读优秀的源码,不仅能够加深对于Redis的理解,而且还能提高自身的编码水平,甚至可以对Redis做定制化,也就是说可以修改Redis的源码来满足自身的需求,例如新浪微博在Redis的早期版本上做了很多的定制化来满足自身的需求,豌豆荚也开源基于Proxy的Redis分布式实现Codis。
# 六:其他链接
# 七:参考文献
- 《Redis 深度历险:核心原理和应用实践 - 钱文品》
- 《Redis 开发与运维 - 付磊、张益军》
- 16个 Redis 常见使用场景 (opens new window)
- 官方文档 (opens new window)