Java面试——Redis

为什么Redis能这么快

  • 完全基于内存,绝大部分请求是纯粹的内存操作,执行效率高
  • 数据结构简单,对数据操作也简单
  • 采用单线程,单线程也能处理高并发请求
  • 由于采用单线程,在高并发情况下避免了使用锁
  • 使用多路I/O复用模型,非阻塞IO

Redis供用户使用的数据类型

String

最基本的数据类型,二进制安全(二进制安全意味着可以保存任何数据,比如图片,最大512M)

set name "aaa"	// ok
get name		// "aaa"

127.0.0.1:6379> set count 1
OK
127.0.0.1:6379> incr count
(integer) 2
127.0.0.1:6379> get count
"2"
127.0.0.1:6379> del count
(integer) 1

Hash

String元素组成的字典,适合用于存储对象

127.0.0.1:6379> hmset ant name "ant" age 23
OK
127.0.0.1:6379> hget ant age
"23"
127.0.0.1:6379> hset ant name ant666
(integer) 0
127.0.0.1:6379> hget ant
(error) ERR wrong number of arguments for 'hget' command
127.0.0.1:6379> hgetall ant
1) "name"
2) "ant666"
3) "age"
4) "23"

List

列表,按照String元素插入顺序排序

127.0.0.1:6379> lpush mylist aaa
(integer) 1
127.0.0.1:6379> lpush mylist bbb
(integer) 2
127.0.0.1:6379> lpush mylist ccc
(integer) 3
127.0.0.1:6379> lrange mylist 0 10
1) "ccc"
2) "bbb"
3) "aaa"
127.0.0.1:6379> rpush mylist ddd
(integer) 4
127.0.0.1:6379> lrange mylist 0 10
1) "ccc"
2) "bbb"
3) "aaa"
4) "ddd"

你可以添加一个元素到列表的头部(左边)或者尾部(右边)

利用lpush可以实现后进先出的栈结构

Set

String元素组成的无序集合,通过哈希表实现,不允许重复

Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

127.0.0.1:6379> sadd myset 111
(integer) 1
127.0.0.1:6379> sadd myset 222
(integer) 1
127.0.0.1:6379> sadd myset 333
(integer) 1
127.0.0.1:6379> sadd myset 333
(integer) 0
127.0.0.1:6379> smembers myset
1) "111"
2) "222"
3) "333"

Sorted Set

Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。

不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。

有序集合的成员是唯一的,但分数(score)却可以重复。

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。

127.0.0.1:6379> zadd myzset 3 abc
(integer) 1
127.0.0.1:6379> zadd myzset 2 aaa
(integer) 1
127.0.0.1:6379> zadd myzset 1 abd
(integer) 1
127.0.0.1:6379> zadd myzset 2 abd
(integer) 0
127.0.0.1:6379> zrange myzset 0 10
1) "aaa"
2) "abd"
3) "abc"
127.0.0.1:6379> zrange myzset 0 10 withscores
1) "aaa"
2) "2"
3) "abd"
4) "2"
5) "abc"
6) "3"

从海量Key里查询出某一固定前缀的Key

SCAN cursor [MATCH pattern] [COUNT count]

以0作为游标开始一次新的迭代,知道命令返回游标0完成一次遍历

127.0.0.1:6379> scan 0 match my* count 10
1) "6"
2) 1) "myzset"
   2) "mystring"

如何使用Redis实现分布式锁

分布式锁需要解决的问题

  • 互斥性
  • 安全性
  • 死锁
  • 容错

SETNX

如果key不存在,则创建并赋值

  • 时间复杂度:O(1)
  • 返回值:设置成功,返回1;设置失败,返回0
127.0.0.1:6379> setnx locknx test
(integer) 1
127.0.0.1:6379> setnx locknx aaa
(integer) 0
127.0.0.1:6379> get locknx
"test"

EXPIRE 解决SETNX长期有效的问题

  • 设置key的生存时间,当key过期时,会被自动删除
127.0.0.1:6379> expire locknx 2
(integer) 1
127.0.0.1:6379> get locknx
(nil)
127.0.0.1:6379> setnx locknx aaa
(integer) 1

但是这样操作会有缺点,原子性得不到满足。当setnx之后还没来得及执行EXPIRE时程序挂掉了,就会导致锁一直不会被释放。于是便有了SET key value [EX seconds] [PX milliseconds] [NX|XX]

SET的同时设置EXPIRE

SET key value [EX seconds] [PX milliseconds] [NX|XX]

  • EX second:设置键的过期时间为xx秒
  • PX millisecond: 设置键的过期时间为xx毫秒
  • NX:只在键不存在时,才对键进行设置操作
  • XX: 只在键已经存在时,才对键进行设置操作
  • SET操作成功完成时,返回OK,否则返回nil
127.0.0.1:6379> set locktest test EX 10 NX
OK
127.0.0.1:6379> set locktest test1 EX 10 NX
(nil)
127.0.0.1:6379> set locktest test1 EX 10 NX
OK

如何使用Redis实现异步队列

使用List作为队列,LPUSH/RPUSH生产消息,RPOP/LPOP消费消息

127.0.0.1:6379> lpush mylist aaa
(integer) 1
127.0.0.1:6379> lpush mylist bbb
(integer) 2
127.0.0.1:6379> lpush mylist ccc
(integer) 3
127.0.0.1:6379> rpop mylist
"aaa"
127.0.0.1:6379> rpop mylist
"bbb"
127.0.0.1:6379> rpop mylist
"ccc"
127.0.0.1:6379> rpop mylist
(nil)

缺点:没有等待队列里有值,就直接消费

弥补:通过应用层引入Sleep机制循环调用POP重试

这里引入一种不需要Sleep的方法,BLPOP/BRPOP key [key ...] timeout

BLPOP/BRPOP

BLPOP/BRPOP key [key ...] timeout阻塞直到队列有消息或者超时

这种方式实现队列依旧有缺点,只能供一个消费者消费。解决方法是引入pub/sub主题订阅者模式

pub/sub 主题订阅者模式

  • 发送者(pub)发送消息,订阅者(sub)接收消息
  • 订阅者可以订阅任意数量的频道
    在这里插入图片描述
127.0.0.1:6379> SUBSCRIBE topic

Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "topic"
3) (integer) 1

重新开启个 redis 客户端,然后在同一个频道 redisChat 发布两次消息,订阅者就能接收到消息。

127.0.0.1:6379> PUBLISH topic "hello"

(integer) 1

127.0.0.1:6379> PUBLISH topic "i love you"

(integer) 1

订阅者的客户端会显示如下消息

1) "message"
2) "topic"
3) "hello"
1) "message"
2) "topic"
3) "i love you"

缺点:消息的发布是无状态的,无法保证可达。解决这类问题就需要用专业的消息队列了,Rabbitmq等

Redis如何做持久化

RDB(快照)持久化

可通过配置文件配置redis自动备份,也可通过命令手动备份

  • SAVE 阻塞Redis的服务器进程,知道RDB文件被创建完毕
  • BGSAVE Fork出一个子进程来创建RDB文件,不阻塞服务器进程

以上命令将在 redis 安装目录中创建dump.rdb文件,如果需要恢复数据,只需将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可。

缺点:

  • 内存数据的全量同步,数据量大会由于I/O而严重影响性能
  • 可能会因为Redis挂掉而丢失从当前至最近一次快照期间的备份

AOF(Append-Only-File)持久化:保存写状态

  • 记录下除了查询以外的所有变更数据库状态的指令
  • 以append的形式追加保存到AOF文件中(增量)

日志重写可以解决AOF文件大小不断增大的问题

RDB和AOF的优缺点

  • RDB优点:全量数据快照,文件小,恢复快
  • RDB缺点:无法保存最近一次快照之后的数据
  • AOF优点:可读性高,适合保存增量数据,数据不易丢失
  • AOF缺点:文件体积大,恢复时间长

RDB-AOF混合模式

  • BGSAVE做镜像全量持久化,AOF做增量持久化

Redis管道技术

Redis是一种基于客户端-服务端模型以及请求/响应协议的TCP服务。这意味着通常情况下一个请求会遵循以下步骤:

  • 客户端向服务端发送一个查询请求,并监听Socket返回,通常是以阻塞模式,等待服务端响应。
  • 服务端处理命令,并将结果返回给客户端。

Redis 管道技术可以在服务端未响应时,客户端可以继续向服务端发送请求,并最终一次性读取所有服务端的响应。

Pipeline批量执行指令,节省多次IO往返时间,提高了Redis服务性能。

Redis 全同步过程

  • slave发送sync命令到master
  • master启动一个后台进程,将Redis中的数据快照保存到文件中
  • master将保存数据快照期间接收到的写命令缓存起来
  • master完成写文件操作后,将该文件发送给slave
  • 使用新的AOF文件替换掉旧的AOF文件
  • master将这期间收集的增量写命令发送给slave端

Redis 增量同步过程

  • master接收到用户的操作指令,判断是否需要传播到slave
  • 将操作记录追加到AOF文件
  • 将操作传播到其他slave
  • 将缓存中的数据发送给slave

Redis Sentinel

Redis主从结构如果master挂了那么整个集群会处于不可用状态,Redis哨兵就是为了解决这个问题

  • 监控:检查主从服务器是否运行正常
  • 提醒:通过API向管理员或其他应用程序发送故障通知
  • 自动故障迁移:主从切换

主流Redis应用

主从复制 + Redis哨兵 + 一致性Hash算法

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AntChenxi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值