Redis 前言

7/15/2021 Redis

# 一:前言

Redis 是互联网技术领域使用最为广泛的存储中间件,它是「Remote Dictionary Service」的首字母缩写,也就是「远程字典服务」。

Redis 会将所有数据都存放在内存中,所以读写性能非常惊人。不仅如此,Redis 还可以将内存的数据利用快照(RDB)和日志(AOF)的形式保存在硬盘上。

Redis提供了键过期、发布订阅、事务、流水线、Lua脚本等附加功能。

面试经历

下面这段面试经历摘自《Redis深度历险:核心原理和应用实践 - 钱文品》

在面试后端工程师 Redis 技能的时候,面试官通常问的第一个问题就是"Redis 能用来做什么?",第一个回答往往都会是「缓存」。缓存确实是 Redis 使用最多的领域,它相比Memcache 而言更加易于理解、使用和控制。

可是如果再进一步问"还有呢?",大多数同学就会开始皱眉头,只有一小部分人会回答「分布式锁」。如果你就分布式锁再深入问下去,他们基本就会开始摇头:我们项目里面Redis 的锁方法都是别人(应该是架构师)封装好的,拿过来直接使用,内部细节没有去了解过,也没有必要了解。

对类似的场景,我深有体会。因为关于 Redis 的面试题,之前准备了很多,但是真正能用上的却很少。当面试的同学频繁地回复「不知道、没用过」的时候,再继续深入追问已经毫无意义,这时候就需要切换话题了。偶尔遇上几个能持续很多回合的同学,他们总能使人眼前一亮。如果再拓展一下周边知识点,就会发现这些人往往也会有所涉猎,这时我在心中已经暗暗地对这位同学伸出了大拇指。

这样的面试经历事后也让我深刻反思:架构师的技能很高,对提升团队研发效率很有帮助,我们非常钦佩和羡慕。但是普通开发者如果习惯于在架构师封装好的东西之上,只专注于做业务开发,那久而久之,在技术理解和成长上就会变得迟钝甚至麻木。从这个角度看,架构师也可能成为普通开发者的"敌人",他的强大能力会让大家变成"温室的花朵",一旦遇到环境变化就会不知所措。

其实很多业务场景,如果仅仅是会使用某项技术、框架,那是再简单不过了。但随着业务发展,系统的用户量、并发量涨上来之后,现有系统的问题就会层出不穷地暴露出来。如果不能深入地了解系统、技术和框架背后的深层原理,很多问题根本无法理解到本质,更谈不上解决,临时抱佛脚也于事无补。

# 二:Redis 特性

  1. 速度快
    1. C语言
    2. 单线程架构,预防了多线程可能产生的竞争问题
  2. 基于键值对的数据结构服务器
    • 字符串
      • 位图(Bitmaps)
      • HyperLogLog
    • 哈希
    • 列表
    • 集合
    • 有序集合
    • GEO(地理信息位置)
  3. 丰富的功能
    • 提供了键过期功能,可以用来实现缓存;
    • 提供了发布订阅功能,可以用来实现消息系统;
    • 支持 Lua 脚本功能,可以利用 Lua 创造出新的 Redis 命令;
    • 提供了简单的事务功能,能在一定程度上保证事务特性;
    • 提供了流水线(Pipeline)功能,这样客户端能将一批命令一次性传到 Redis,减少了网络的开销。
  4. 简单稳定
  5. 客户端语言多
  6. 持久化
    • RDB
    • AOF
  7. 主从复制
  8. 高可用的分布式

# 三: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,可以设置超时时间。

  • blpopblpop key1 timeout 移除并获取列表的第一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
  • brpopbrpop key1 timeout 移除并获取列表的最后一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

6. 数据共享分布式

因为Redis是分布式的独立服务,可以在多个应用之间共享。例如:分布式Session

<dependency> 
    <groupId>org.springframework.session</groupId> 
    <artifactId>spring-session-data-redis</artifactId> 
</dependency>
1
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);
}
1
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),可以用来做大数据量的统计
1
2
3
4
5
6
7
8
9

例如:在线用户统计,留存用户统计

> setbit onlineusers 01 
> setbit onlineusers 11 
> setbit onlineusers 20
1
2
3

支持按位与、或、非、异或等等操作

计算出7天都在线的用户

BITOP "AND" "7_days_both_online_users" "day_1_online_users" "day_2_online_users" ...  "day_7_online_users"
1

11. 购物车

String或hash。所有String可以做的hash都可以做

  • key:用户id;field:商品id;value:商品数量
  • +1:hincr;-1:hdecr;删除:hdel;全选:hgetall;商品数:hlen

12. 用户消息时间线timeline

list,双向链表,直接作为timeline就好了。插入有序

13. 抽奖

自带一个随机获取值

spop myset
1

14. 商品标签

例如,用 tags:i5001 来维护商品所有的标签

  • sadd tags:i5001 画面清晰细腻
  • sadd tags:i5001 真彩清晰显示屏
  • sadd tags:i5001 流程至极

15. 商品筛选

## 获取差集
> sdiff set1 set2
## 获取交集(intersection )
> sinter set1 set2
## 获取并集
> sunion set1 set2
1
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
1
2
3
4

赛选商品,苹果的、ios的、屏幕在6.0-6.24之间的,屏幕材质是LCD屏幕

sinter brand:apple brand:ios screensize:6.0-6.24 screentype:lcd
1

16. 用户关注、推荐模型

follow 关注 fans 粉丝

相互关注:

sadd 1:follow 2
sadd 2:fans 1
sadd 1:fans 2
sadd 2:follow 1
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。

# 六:其他链接

# 七:参考文献

最后更新: 12/18/2023, 3:49:59 PM