【java总结】数据库-Redis

Redis基础

什么是 Redis?

Redis 是一款非常受欢迎的NoSQL数据库,他是使用C语言编写而成的 开源免费的,一个高性能的 key-value 数据库。整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据 flush 到硬盘 上进行保存。

缺点:由于他受到物理内存的限制,因此无法用作海量数据的高性能读写

为什么 Redis 需要把所有数据放到内存中?

  • Redis 为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘,所以 Redis 具有高速读写和数据持久化的特征
  • 如果程序直接与磁盘交互,磁盘 IO 速度会严重影响 Redis 的性能
  • 内存的硬件成本降低,使得 Redis 更受欢迎

Redis 能用来做什么?

会话缓存,排行榜,计数器,发布,订阅,地图信息分析(附近的人)

  1. 记录帖子的点赞数、评论数和点击数 (hash)。
  2. 记录用户的帖子 ID 列表 (排序),便于快速显示用户的帖子列表 (zset)。
  3. 记录帖子的标题、摘要、作者和封面信息,用于列表页展示 (hash)。
  4. 记录帖子的点赞用户 ID 列表,评论 ID 列表,用于显示和去重计数 (zset)。
  5. 缓存近期热帖内容 (帖子内容空间占用比较大),减少数据库压力 (hash)。
  6. 记录帖子的相关文章 ID,根据内容推荐相关帖子 (list)。
  7. 如果帖子 ID 是整数自增的,可以使用 Redis 来分配帖子 ID(计数器)。
  8. 收藏集和帖子之间的关系 (zset)。
  9. 记录热榜帖子 ID 列表,总热榜和分类热榜 (zset)。
  10. 缓存用户行为历史,进行恶意行为过滤 (zset,hash)。

Redis 安装

Docker 方式

# 拉取 redis 镜像
> docker pull redis
# 运行 redis 容器
> docker run --name myredis -d -p6379:6379 redis
# 执行容器中的 redis-cli,可以直接使用命令行操作 redis
> docker exec -it myredis redis-cli

Github 源码编译方式

# 下载源码
> git clone --branch 2.8 --depth 1 git@github.com:antirez/redis.git
> cd redis
# 编译
> make
> cd src
# 运行服务器,daemonize表示在后台运行
> ./redis-server --daemonize yes
# 运行命令行
> ./redis-cli

直接安装方式

# mac
> brew install redis
# ubuntu
> apt-get install redis
# redhat
> yum install redis
# 运行客户端
> redis-cli

网页直接测试

常用命令

> set name jimu98   #设置值
> get name    #获取值
"jimu98"
> exists name   #是否存在
> del name   #删除
> flushdb  #清除自己数据库
> flushall #清除所有数据库
> expire name 10 #10s后过期
> ttl name #查看剩余过期时间
> PERSIST name   # 移除 name 的过期时间
> type name #查看类型
> mset name1 boy name2 girl #批量设置
> mget name1 name2  #批量接收
其他自己百度吧。

Reids数据类型

常用5种数据类型:string,list,set,zset,hash

一个字符串类型的值能存储最大容量是多少?512M

三种特殊数据类型

geospatial:

(地理位置)

hyperloglog:

(基础统计,统计一个集合中不重复的数据,使用位运算的方式,但是存在0.81%错误率)

bitmap(位图,只记录0,1)

Redis是单进程单线程的?

Redis是单进程单线程的,Redis利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销。

多线程处理会涉及到锁,而且多线程处理会涉及到线程切换而消耗CPU。因为CPU不是Redis 的瓶颈,Redis的瓶颈最有可能是机器内存或者网络带宽。

单线程无法发挥多核CPU性能,不 过可以通过在单机开多个Redis实例来解决。另外在5月2号发布了全新的redis6.0,正式支持多线程

Redis 数据淘汰策略

Redis 提供 6种内存淘汰策略

  1. Volatile-lru:从设置了过期时间的数据集中,选择最近最少使用的数据释放。
  2. Allkeys-lru:从数据集中(包括设置过期时间以及未设置过期时间的数据集中),选择最近最少使用的数据释放。
  3. Volatile-random:从设置了过期时间的数据集中,随机选择一个数据进行释放。
  4. Allkeys-random:从数据集中(包括了设置过期时间以及未设置过期时间的数据集)随机选择一个数据进行入释放。
  5. Volatile-ttl:从设置了过期时间的数据集中,选择马上就要过期的数据进行释放。
  6. Noeviction:不删除任意数据(但Redis还会根据引用计数器进行释放),这时如果内存不够时,会直接返回错误。

Redis单点吞吐量

单点TPS(每秒钟最大能处理的请求数)达到8万/秒,QPS(每秒钟最大能接受的用户访问量)达到10万/秒,补充下TPS和QPS的概念

1.QPS: 应用系统每秒钟最大能接受的用户访问量 每秒钟处理完请求的次数,注意这里是处理完,具体是指发出请求到服务器处理完成功返回 结果。可以理解在server中有个counter,每处理一个请求加1,1秒后counter=QPS。

2.TPS: 每秒钟最大能处理的请求数 每秒钟处理完的事务次数,一个应用系统1s能完成多少事务处理,一个事务在分布式处理 中,可能会对应多个请求,对于衡量单个接口服务的处理能力,用QPS比较合理。

Redis配置详解

Redis 支持的 Java 客户端都有哪些?官方推荐用哪个

Redisson、Jedis、lettuce 等等,官方推荐使用 Redisson。

Jedis 与 Redisson 对比有什么优缺点?

Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持;
Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作, 不支持排序、事务、管道、分区等Redis特性。Redisson的宗旨是促进使用者对Redis的关注分离,从 而让使用者能够将精力更集中地放在处理业务逻辑上。

Redis持久化

Redis提供了哪几种持久化方式?

Redis主要提供了两种持久化机制:RDB和AOF;

RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。

image-20200803210747126

AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。

image-20200803210817902

RDB 优缺点

  • RDB 会生成多个数据文件,每个数据文件都代表了某一个时刻中 redis 的数据,这种多个数据文件的方式,非常适合做冷备,可以将这种完整的数据文件发送到一些远程的安全存储上去,比如说 Amazon 的 S3 云服务上去,在国内可以是阿里云的 ODPS 分布式存储上,以预定好的备份策略来定期备份 redis 中的数据。
  • RDB 对 redis 对外提供的读写服务,影响非常小,可以让 redis 保持高性能,因为 redis 主进程只需要 fork 一个子进程,让子进程执行磁盘 IO 操作来进行 RDB 持久化即可。
  • 相对于 AOF 持久化机制来说,直接基于 RDB 数据文件来重启和恢复 redis 进程,更加快速。
  • 如果想要在 redis 故障时,尽可能少的丢失数据,那么 RDB 没有 AOF 好。一般来说,RDB 数据快照文件,都是每隔 5 分钟,或者更长时间生成一次,这个时候就得接受一旦 redis 进程宕机,那么会丢失最近 5 分钟的数据。
  • RDB 每次在 fork 子进程来执行 RDB 快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒。

AOF 优缺点

该机制可以带来更高的数据安全性,即数据持久性。Redis中提供了三种同步策略,

即每秒同步、每修改同步和不同步。

  • AOF 可以更好的保护数据不丢失,一般 AOF 会每隔 1 秒,通过一个后台线程执行一次fsync操作,最多丢失 1 秒钟的数据。
  • AOF 日志文件以 append-only 模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修复。
  • AOF 日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。因为在 rewrite log 的时候,会对其中的指令进行压缩,创建出一份需要恢复数据的最小日志出来。在创建新日志文件的时候,老的日志文件还是照常写入。当新的 merge 后的日志文件 ready 的时候,再交换新老日志文件即可。
  • AOF 日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用 flushall 命令清空了所有数据,只要这个时候后台 rewrite 还没有发生,那么就可以立即拷贝 AOF 文件,将最后一条 flushall 命令给删了,然后再将该 AOF 文件放回去,就可以通过恢复机制,自动恢复所有数据。
  • 对于同一份数据来说,AOF 日志文件通常比 RDB 数据快照文件更大。
  • AOF 开启后,支持的写 QPS 会比 RDB 支持的写 QPS 低,因为 AOF 一般会配置成每秒 fsync 一次日志文件,当然,每秒一次 fsync,性能也还是很高的。(如果实时写入,那么 QPS 会大降,redis 性能会大大降低)
  • 以前 AOF 发生过 bug,就是通过 AOF 记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。所以说,类似 AOF 这种较为复杂的基于命令日志 / merge / 回放的方式,比基于 RDB 每次持久化一份完整的数据快照文件的方式,更加脆弱一些,容易有 bug。不过 AOF 就是为了避免 rewrite 过程导致的 bug,因此每次 rewrite 并不是基于旧的指令日志进行 merge 的,而是基于当时内存中的数据进行指令的重新构建,这样健壮性会好很多。

RDB 和 AOF 到底该如何选择

  • 不要仅仅使用 RDB,因为那样会导致你丢失很多数据;
  • 也不要仅仅使用 AOF,因为那样有两个问题:第一,你通过 AOF 做冷备,没有 RDB 做冷备来的恢复速度更快;第二,RDB 每次简单粗暴生成数据快照,更加健壮,可以避免 AOF 这种复杂的备份和恢复机制的 bug;
  • redis 支持同时开启开启两种持久化方式,我们可以综合使用 AOF 和 RDB 两种持久化机制,用 AOF 来保证数据不丢失,作为数据恢复的第一选择; 用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复。

Redis事务操作

怎么理解 Redis 事务?

事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行,事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。

redis对事务的支持目前还比较简单。redis只能保证一个client发起的事务中的命令可以连续的执行,而中间不会插入其他client的命令。 由于redis是单线程来处理所有client的请求的所以做到这点是很容易的。

redis没有回滚的概念,已经执行的动作不能undo。当事务在执行EXEC命令之前出现错误,则redis会清空事务队列,放弃执行事务。在EXEC之后redis批量执行命令时产生的错误会被忽略,其他命令继续执行(这是全部执行的真正含义)。在执行的过程中,redis命令只会因为错误的语法而失败,因此,事务无罪。

Redis事务的命令

MULTI、EXEC、DISCARD和WATCH命令是Redis事务功能的基础。

MULTI命令

MULTI命令用来开启一个事务,当事务开启之后,客户端可以继续向服务器发送多条命令,这些命令会缓存在队列里面,而非执行(这点很重要,这代表着在multi开启事务以后无法根据get到的数据做逻辑判断),只有当执行EXEC命令时,这些命令才会执行。

DISCARD命令

DISCARD命令可以清空事务队列,放弃执行事务。

如果使用了WATCH命令,DISCARD命令会将当前连接监控的所有键取消监控。

EXEC命令

EXEC命令会触发执行事务中的所有命令。当使用WATCH命令时,只有当受监控的键没有被修改时,EXEC命令才会执行事务中的命令,这种方式利用了检查再设置(CAS)的机制。

Redis内存

Redis如何做内存优化?

尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小, 所以你应该尽可能的将你的数据模型抽象到一个散列表里面。比如你的web系统中有一个用 户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的key,而是应该把这个用户的 所有信息存储到一张散列表里面.

Redis回收进程如何工作的?

  • 一个客户端运行了新的命令,添加了新的数据。
  • Redis检查内存使用情况,如果大于maxmemory的限制, 则根据设定好的策略进行回收。
  • 一个新的命令被执行,等等。
  • 所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。

Redis使用的是近似的LRU算法,通过少量keys进行取样,然后回收其中一个最好的key(被访问时间较早的)。从redis3.0以后,redis改进了回收键的候选池,也就是改善了算法的性能,使得更加近似真的LRU算法。

redis过期键的删除策略?

(1)定时删除:在设置键的过期时间的同时,创建一个定时器 timer). 让定时器在键的过期时间来临时,立即执行对键的删除操作。

(2)惰性删除:放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过 期,如果过期的话,就删除该键;如果没有过期,就返回该键。

(3)定期删除:每隔一段时间程序就对数据库进行一次检查,删除里面的过期键。至于要删除多少过期键,以及要检查多少个数据库,则由算法决定。

Redis高级功能

Redis主要有哪些功能?

哨兵(Sentinel)和复制(Replication)

Redis服务器毫无征兆的罢工是个麻烦事,如何保证备份的机器是原始服务器的完整备份呢?这时候就需要哨兵和复制。

Sentinel可以管理多个Redis服务器,它提供了监控,提醒以及自动的故障转移的功能,Replication则是负责让一个Redis服务器可以配备多个备份的服务器。

Redis也是利用这两个功能来保证Redis的高可用的

事务

很多情况下我们需要一次执行不止一个命令,而且需要其同时成功或者失败。redis对事务的支持也是源自于这部分需求,即支持一次性按顺序执行多个命令的能力,并保证其原子性。

LUA脚本

在事务的基础上,如果我们需要在服务端一次性的执行更复杂的操作(包含一些逻辑判断),则lua就可以排上用场了

持久化

redis的持久化指的是redis会把内存的中的数据写入到硬盘中,在redis重新启动的时候加载这些数据,从而最大限度的降低缓存丢失带来的影响。

集群(Cluster)

单台服务器资源的总是有上限的,CPU资源和IO资源我们可以通过主从复制,进行读写分离,把一部分CPU和IO的压力转移到从服务器上,这也有点类似mysql数据库的主从同步。

在Redis官方的分布式方案出来之前,有twemproxy和codis两种方案,这两个方案总体上来说都是依赖proxy来进行分布式的。

Pipeline 有什么好处,为什么要用 pipeline?

可以将多次 IO 往返的时间缩减为一次

使用过 Redis 分布式锁么,它是什么回事?

先拿 setnx 来争抢锁,抢到之后,再用 expire 给锁加一个过期时间防止锁忘记了 释放。

如果在 setnx 之后执行 expire 之前进程意外 crash 或者要重启维护了,那会怎么样?

set 指令有非常复杂的参数,这个应该是可以同时把 setnx 和 expire 合成一条指令来用的!

Redis集群方案

Redis集群方案

官方cluser方案

从redis 3.0版本开始支持redis-cluster集群,redis-cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他节点连接。redis-cluster是一种服务端分片技术。

redis-cluster架构图

image-20200803222701385

Redis Cluster中,Sharding采用slot(槽)的概念,一共分成16384个槽,这有点儿类pre sharding思路。对于每个进入Redis的键值对,根据key进行散列,分配到这16384个slot中的某一个中。使用的hash算法也比较简单,就是CRC16后16384取模。

Redis集群中的每个node(节点)负责分摊这16384个slot中的一部分,也就是说,每个slot都对应一个node负责处理。当动态添加或减少node节点时,需要将16384个槽做个再分配,槽中的键值也要迁移。当然,这一过程,在目前实现中,还处于半自动状态,需要人工介入。

Redis集群,要保证16384个槽对应的node都正常工作,如果某个node发生故障,那它负责的slots也就失效,整个集群将不能工作。

为了增加集群的可访问性,官方推荐的方案是将node配置成主从结构,即一个master主节点,挂n个slave从节点。这时,如果主节点失效,Redis Cluster会根据选举算法从slave节点中选择一个上升为主节点,整个集群继续对外提供服务。这非常类似前篇文章提到的Redis Sharding场景下服务器节点通过Sentinel监控架构成主从结构,只是Redis Cluster本身提供了故障转移容错的能力。

Redis Cluster的新节点识别能力、故障判断及故障转移能力是通过集群中的每个node都在和其它nodes进行通信,这被称为集群总线(cluster bus)。它们使用特殊的端口号,即对外服务端口号加10000。例如如果某个node的端口号是6379,那么它与其它nodes通信的端口号是16379。nodes之间的通信采用特殊的二进制协议。

对客户端来说,整个cluster被看做是一个整体,客户端可以连接任意一个node进行操作,就像操作单一Redis实例一样,当客户端操作的key没有分配到该node上时,Redis会返回转向指令,指向正确的node,这有点儿像浏览器页面的302 redirect跳转。

codis

codis由3大组件构成:

  • codis-server : 修改过源码的redis, 支持slot,扩容迁移等
  • codis-proxy : 支持多线程,go语言实现的内核
  • codis Dashboard : 集群管理工具

提供web图形界面管理集群。 集群元数据存在在zookeeper或etcd。 提供独立的组件codis-ha负责redis节点主备切换。 基于proxy的codis,客户端对路由表变化无感知。客户端需要从codis dashhoard调用list proxy命令获取所有proxy列表,并根据自身的轮询策略决定访问哪个proxy节点以实现负载均衡。

Redis Sharding集群

Sharding架构图:

image-20200803223335581

Redis 3正式推出了官方集群技术,解决了多Redis实例协同服务问题。Redis Cluster可以说是服务端Sharding分片技术的体现,即将键值按照一定算法合理分配到各个实例分片上,同时各个实例节点协调沟通,共同对外承担一致服务。

多Redis实例服务,比单Redis实例要复杂的多,这涉及到定位、协同、容错、扩容等技术难题。这里,我们介绍一种轻量级的客户端Redis Sharding技术。

Redis Sharding可以说是Redis Cluster出来之前,业界普遍使用的多Redis实例集群方法。其主要思想是采用哈希算法将Redis数据的key进行散列,通过hash函数,特定的key会映射到特定的Redis节点上。这样,客户端就知道该向哪个Redis节点操作数据。Sharding架构如图:

还有阿里的之类的

Redis Sentinal 着眼于高可用,在 master 宕机时会自动将 slave 提升为 master,继续提供服务。

Redis Cluster 着眼于扩展性,在单个 redis 内存不足时,使用 Cluster 进行 分片存储。

说说 Redis 哈希槽的概念?

Redis 集群没有使用一致性 hash,而是引入了哈希槽的概念,Redis 集群有 16384 个哈希槽,每个 key 通 过 CRC16 校验后对 16384 取模来决定放置哪个槽,集群的每个节点负责一部分 hash 槽。

Redis集群最大节点个数是多少?

16384 个

Redis集群的主从复制模型是怎样的?

为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主 从复制模型,每个节点都会有N-1个复制品.

Redis 集群会有写操作丢失吗?为什么?

Redis 并不能保证数据的强一致性,这意味这在实际中集群在特定的条件下可能会丢失写操作。

Redis 集群之间是如何复制的?

异步复制

Redis 集群如何选择数据库?

Redis 集群目前无法做数据库选择,默认在 0 数据库。

Redis 集群方案什么情况下会导致整个集群不可用?

有 A,B,C 三个节点的集群,在没有复制模型的情况下,如果节点 B 失败了, 那么整个集群就会以为缺少 5501-11000 这个范围的槽而不可用。

Redis场景题

假如 Redis 里面有 1 亿个 key,其中有 10w 个 key 是以 某个固定的已知的前缀开头的,如果将它们全部找出来?

使用 keys 指令可以扫出指定模式的 key 列表。

如果这个 redis 正在给线上的业务提供服务,那使用 keys 指令会 有什么问题?

redis 的单线程的。keys 指令会导致线 程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时 候可以使用 scan 指令,scan 指令可以无阻塞的提取出指定模式的 key 列表,但 是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间 会比直接用 keys 指令长。

如果有大量的 key 需要设置同一时间过期,一般需要注意 什么?

如果大量的 key 过期时间设置的过于集中,到过期的那个时间点,redis 可能 会出现短暂的卡顿现象。一般需要在时间上加一个随机值,使得过期时间分散一 些。

为啥 Redis 单线程模型也能效率这么高?

纯内存操作。
核心是基于非阻塞的 IO 多路复用机制。
C 语言实现,一般来说,C 语言实现的程序“距离”操作系统更近,执行速度相对会更快。
单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题。