Redis详解
# NOSQL简述
NOSQL(NOSQL= Not Only SQL ),意即“不仅仅是SQL”,泛指非关系型的数据库
随着互联网 WEB2.0 网站的兴起,传统的关系数据库在应付 WEB2.0 网站,特别是超大规模和高并发的SNS类型的 WEB2.0 纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。
不遵循SQL标准。
不支持ACID(原子性、一致性、隔离性和持久性)。
远超于SQL的性能。
# NoSQL数据库常见分类
键值(Key-Value)存储数据库
这一类数据库主要会使用到一个哈希表,这个表中有一个特定的键和一个指针指向特定的数据。Key/value模型的优势在于简单、易部署。但是如果DBA只对部分值进行查询或更新的时候,Key/value就显得效率低下了。
例如:Tokyo CabinetTyrant, Redis, Voldemort, Oracle BDB.
列存储数据库
这部分数据库通常是用来应对分布式存储的海量数据。键仍然存在,但是它们的特点是指向了多个列。这些列是由列家族来安排的。
例如:Cassandra, HBase, Riak.
文档型数据库
文档型数据库的灵感是来自于Lotus Notes办公软件的,而且它同第一种键值存储相类似。该类型的数据模型是版本化的文档,半结构化的文档以特定的格式存储,比如JSON。文档型数据库可 以看作是键值数据库的升级版,允许之间嵌套键值。而且文档型数据库比键值数据库的查询效率更高。
例如:CouchDB, MongoDb. 国内也有文档型数据库SequoiaDB,已经开源。
图形(Graph)数据库
图形结构的数据库同其他行列以及刚性结构的SQL数据库不同,它是使用灵活的图形模型,并且能够扩展到多个服务器上。NoSQL数据库没有标准的查询语言(SQL),因此进行数据库查询需要制定数据模型。许多NoSQL数据库都有REST式的数据接口或者查询API。
例如:Neo4J, InfoGrid, Infinite Graph.
因此,综上所述 NoSQL 数据库在以下的这几种情况下比较适用:
数据模型比较简单
对数据高可扩展性的
对数据库性能要求较高
不需要高度的数据一致性
对于给定key,比较容易映射复杂值的环境
# Redis简述
基本概念
Redis是一个开源的、使用C语言编写的、支持网络交互的、可基于内存也可持久化的Key-Value数据库(非关系型数据库)。
优点
速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1),Redis能读的速度是110000次/s,写的速度是81000次/s 。
支持丰富数据类型,支持string,list,set,sorted set,hash
支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除,Redis还支持 publish/subscribe, 通知等特性。
缺点
持久化。Redis直接将数据存储到内存中,要将数据保存到磁盘上,Redis可以使用两种方式实现持久化过程。定时快照(snapshot):每隔一段时间将整个数据库写到磁盘上,每次均是写全部数据,代价非常高。第二种方式基于语句追加(aof):只追踪变化的数据,但是追加的log可能过大,同时所有的操作均重新执行一遍,恢复速度慢。
耗内存,占用内存过高。
# 应用场景
缓存
对于一些要返回给前端数据的缓存,当有大量数据库sql操作时候,为了避免每次接口请求都要去查询数据库,可以把一些数据缓存到Redis中,这样是直接从内存中获取数据,速度回增快很多。
WEB 端用户,用于登陆缓存session数据,登陆的一些信息存到sessioen中,缓存到Redis中
登录session缓存:WEB 端用户,用于登陆缓存session数据,登陆的一些信息存到session中,缓存到Redis中,没次用户再次登录判断Redis只能够是否存在或者已过期。
购物车缓存:每个用户的购物车是一个哈希表,用户id作为key,存储了 itemId 与 商品加车数量之间的关系。购物车提供数量设置,购物车不随用户登录退出删除。
队列
Redis中提供了list接口,这个list提供了lpush和rpop,这两个方法具有原子性,可以插入队列元素和弹出队列元素。
数据存储
Redis是非关系型数据库,可以把Redis直接用于数据存储,提供了增删改查等操作,因为Redis有良好的硬盘持久化机制,Redis数据就可以定期持久化到硬盘中,保证了Redis数据的完整性和安全性。
Redis锁实现防刷机制
Redis锁可以处理并发问题,Redis数据类型中有一个set类型,set类型在存储数据的时候是无序的,而且每个值是不一样的,不能重复,这样就可以快速的查找元素中某个值是否存在,精确的进行增加删除操作。
# 下载和安装
Redis官网:https://redis.io (opens new window)
Redis中文网:http://www.redis.cn (opens new window)
# Windows
以前在redis官网挂着这么一段话,可惜现在没了
The Redis project does not officially support Windows. However, the Microsoft Open Tech group develops and maintains this Windows port targeting Win64.
简单的说,redis官方不支持windows编译,但是微软维护了一个windows版本。
微软维护的地址:https://github.com/microsoftarchive/redis/releases
Redis服务相关命令(不建议使用)
安装服务:
redis-server.exe --service-install redis.windows.conf --loglevel verbose
卸载服务:
redis-server --service-uninstall
开启服务:
redis-server --service-start
停止服务:
redis-server --service-stop
# Linux(Centos7)
PS:Centos选择的是最小化(mini)安装
离线软件安装目录存放约定
- /opt/software:压缩包和安装包存放位置
- /opt/apps:软件的安装目录
# 安装C语言编译环境
yum -y install cpp binutils glibc glibc-kernheaders glibc-common glibc-devel gcc make gcc-c++ libstdc++-devel tcl
2
3
上传 redis安装包,并编译安装
# 解压
tar -zxvf redis-6.0.6.tar.gz
# 切换至解压目录
cd /opt/software/redis-6.0.6
#编译成功后开始执行安装命令,我没有安装到默认目录,我自定义的目录,执行命令
make PREFIX=/opt/apps/redis install
2
3
4
5
6
7
8
出现以下提示信息表示安装成功
Hint: It's a good idea to run 'make test' ;)
INSTALL install
INSTALL install
INSTALL install
INSTALL install
INSTALL install
make[1]: 离开目录“/opt/software/redis-6.0.6/src”
2
3
4
5
6
7
8
# 文件说明
redis-benchmark:性能测试工具
redis-check-aof:修复有问题的AOF文件,rdb和aof后面讲
redis-check-dump:修复有问题的dump.rdb文件
redis-sentinel:Redis集群使用
redis-server:Redis服务器启动命令
redis-cli:客户端,操作入口
修改基础配置
#安装成功后,创建data文件夹,用于存放redis的数据,
mkdir /opt/apps/redis/data
#然后复制redis中的redis.conf文件到安装目录下,
cp /opt/software/redis-6.0.6/redis.conf /opt/apps/redis/bin
#通过vim命令编辑该配置文件
vim redis.conf
#去掉#号,意为开启保护模式,可以选择绑定ip,也可以指定密码
#protected-mode yes
#修改为dir /opt/apps/redis/data 意为把指定redis的数据存放目录
dir ./
#然后保存,退出编辑。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 服务启停
连接远程服务器:Redis-cli –h IP地址 –p 端口 –a 密码
#启动redis-server --通过配置文件启动redis-server
/opt/apps/redis/bin/redis-server /opt/apps/redis/bin/redis.conf
# 不通过配置文件启动redis-server,会使用默认配置(即在bin目录下的redis.conf)
/opt/apps/redis/bin/redis-server
#单实例关闭:
redis-cli shutdown
#多实例关闭,指定端口关闭
redis-cli -p 6379 shutdow
2
3
4
5
6
7
8
9
10
11
redis默认不转义中文,如果需要转义中文的话,需要在redis-cli后面追加-–raw
# 数据类型和命令
Redis是一种高级的key-value非关系型数据库。,其中value支持五种数据类型:string、List、set、hash、sore set
# 键(key)命令
# 字符串(String)命令
String是Redis最基本的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value。
String类型是二进制安全的。意味着Redis的string可以包含任何数据。比如jpg图片或者序列化的对象。
String类型是Redis最基本的数据类型,一个Redis中字符串value最多可以是512M
String的数据结构为简单动态字符串(Simple Dynamic String,缩写SDS)。是可以修改的字符串,内部结构实现上类似于Java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配.
如图中所示,内部为当前字符串实际分配的空间capacity一般要高于实际字符串长度len。当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间。需要注意的是字符串最大长度为512M。
# 列表(List)命令
列表(list)类型是用来存储多个有序的字符串,列表中的每个字符串称为元素(element),一个列表最多可以存储2的32次方-1(40多亿)。在Redis中,可以对列表两端压入(push)和弹出(pop)
它的底层实际是个双向链表,对两端的操作性能很高,通过索引下标操作中间的节点性能会较差。
在Redis3.2版本以前列表类型的内部编码有两种。
- 压缩列表(ziplist):在列表元素较少的情况下(当列表的元素个数小于list-max-ziplist-entries配置,默认512个。同时列表中每个元素的值都小于list-max-ziplist-value配置时默认64字节)会使用一块连续的内存存储,这个结构是ziplist也就是压缩列表
- linkedlist(链表):当列表类型无法满足ziplist的条件时,Redis会使用linkedlist作为列表的内部实现。
Redis3.2版本开始对列表数据结构进行了改造,使用 quicklist 代替了 ziplist 和 linkedlist
- 快速列表(quicklist):将多个ziplist使用双向指针串起来使用。这样既满足了快速的插入删除性能,又不会出现太大的空间冗余
命令 | 描述 |
---|---|
Redis Lindex 命令 (opens new window) | 通过索引获取列表中的元素 |
Redis Rpush 命令 (opens new window) | 在列表中添加一个或多个值 |
Redis Lrange 命令 (opens new window) | 获取列表指定范围内的元素 |
Redis Rpoplpush 命令 (opens new window) | 移除列表的最后一个元素,并将该元素添加到另一个列表并返回 |
Redis Blpop 命令 (opens new window) | 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
Redis Brpop 命令 (opens new window) | 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
Redis Brpoplpush命令 (opens new window) | 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
Redis Lrem 命令 (opens new window) | 移除列表元素 |
Redis Llen 命令 (opens new window) | 获取列表长度 |
Redis Ltrim 命令 (opens new window) | 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。 |
Redis Lpop 命令 (opens new window) | 移出并获取列表的第一个元素 |
Redis Lpushx 命令 (opens new window) | 将一个或多个值插入到已存在的列表头部 |
Redis Linsert 命令 (opens new window) | 在列表的元素前或者后插入元素 |
Redis Rpop 命令 (opens new window) | 移除并获取列表最后一个元素 |
Redis Lset 命令 (opens new window) | 通过索引设置列表元素的值 |
Redis Lpush 命令 (opens new window) | 将一个或多个值插入到列表头部 |
Redis Rpushx 命令 (opens new window) | 为已存在的列表添加值 |
# 集合(Set)命令
Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的
- 元素不可以重复
- 元素为无序
当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择
Set数据结构是dict字典,字典是用哈希表实现的。
Java中HashSet的内部实现使用的是HashMap,只不过所有的value都指向同一个对象。
Redis的set结构也是一样,它的内部也使用hash结构,所有的value都指向同一个内部值。
命令 | 描述 |
---|---|
Redis Sunion 命令 (opens new window) | 返回所有给定集合的并集 |
Redis Scard 命令 (opens new window) | 获取集合的成员数 |
Redis Srandmember 命令 (opens new window) | 返回集合中一个或多个随机数 |
Redis Smembers 命令 (opens new window) | 返回集合中的所有成员 |
Redis Sinter 命令 (opens new window) | 返回给定所有集合的交集 |
Redis Srem 命令 (opens new window) | 移除集合中一个或多个成员 |
Redis Smove 命令 (opens new window) | 将 member 元素从 source 集合移动到 destination 集合 |
Redis Sadd 命令 (opens new window) | 向集合添加一个或多个成员 |
Redis Sismember 命令 (opens new window) | 判断 member 元素是否是集合 key 的成员 |
Redis Sdiffstore 命令 (opens new window) | 返回给定所有集合的差集并存储在 destination 中 |
Redis Sdiff 命令 (opens new window) | 返回给定所有集合的差集 |
Redis Sscan 命令 (opens new window) | 迭代集合中的元素 |
Redis Sinterstore 命令 (opens new window) | 返回给定所有集合的交集并存储在 destination 中 |
Redis Sunionstore 命令 (opens new window) | 所有给定集合的并集存储在 destination 集合中 |
Redis Spop 命令 (opens new window) | 移除并返回集合中的一个随机元素 |
# 哈希(Hash)命令
hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。类似Java里面的Map<String,Object>
需求:用户ID为查找的key,存储的value用户对象包含姓名,年龄,生日等信息
相比较于使用Redis字符串存储,其有以下几个优缺点:
原生字符串每个属性一个键
set user:1:name Tom
set user:1:age 15
2
优点:简单直观,每个属性都支持更新操作。
缺点:占用过多的键,内存占用量较大,同时用户信息内聚性比较差,所以此种方案一般不会在生产环境使用。
序列化字符串后,将用户信息序列化后用一个键保存
set user:1 serialize(userInfo)
- 优点:简化编程,如果合理的使用序列化可以提高内存的使用效率。
- 缺点:序列化和反序列化有一定的开销,同时每次更新属性都需要把全部数据取出进行反序列化,更新后再序列化到Redis中。
Hash类型对应的数据结构是两种:ziplist(压缩列表),hashtable(哈希表)。当field-value长度较短且个数较少时,使用ziplist,否则使用hashtable。
- 当哈希类型元素个数小于hash-max-ziplist-entries配置(默认512个)
- 所有值都小于hash-max-ziplist-value配置(默认64字节)
命令 | 描述 |
---|---|
Redis Hmset 命令 (opens new window) | 同时将多个 field-value (域-值)对设置到哈希表 key 中。 |
Redis Hmget 命令 (opens new window) | 获取所有给定字段的值 |
Redis Hset 命令 (opens new window) | 将哈希表 key 中的字段 field 的值设为 value 。 |
Redis Hgetall 命令 (opens new window) | 获取在哈希表中指定 key 的所有字段和值 |
Redis Hget 命令 (opens new window) | 获取存储在哈希表中指定字段的值 |
Redis Hexists 命令 (opens new window) | 查看哈希表 key 中,指定的字段是否存在。 |
Redis Hincrby 命令 (opens new window) | 为哈希表 key 中的指定字段的整数值加上增量 increment 。 |
Redis Hlen 命令 (opens new window) | 获取哈希表中字段的数量 |
Redis Hdel 命令 (opens new window) | 删除一个或多个哈希表字段 |
Redis Hvals 命令 (opens new window) | 获取哈希表中所有值 |
Redis Hincrbyfloat 命令 (opens new window) | 为哈希表 key 中的指定字段的浮点数值加上增量 increment 。 |
Redis Hkeys 命令 (opens new window) | 获取所有哈希表中的字段 |
Redis Hsetnx 命令 (opens new window) | 只有在字段 field 不存在时,设置哈希表字段的值。 |
# 有序集合(sorted set)命令
有序集合zset与普通集合set非常相似,是一个没有重复元素的字符串集合。
不同之处是有序集合的每个成员都关联了一个评分(score),这个评分(score)被用来按照从最低分到最高分的方式排序集合中的成员。
集合的成员是唯一的,但是评分可以是重复了 。
因为元素是有序的, 所以你也可以很快的根据评分(score)或者次序(position)来获取一个范围的元素。
访问有序集合的中间元素也是非常快的,因此你能够使用有序集合作为一个没有重复成员的智能列表。
zset底层使用了两个数据结构
hash:hash的作用就是关联元素value和权重score,保障元素value的唯一性,可以通过元素value找到相应的score值。
跳跃表:跳跃表的目的在于给元素value排序,根据score的范围获取元素列表。
# 连接命令
命令 | 描述 |
---|---|
Redis Echo 命令 (opens new window) | 打印字符串 |
Redis Select 命令 (opens new window) | 切换到指定的数据库 |
Redis Ping 命令 (opens new window) | 查看服务是否运行 |
Redis Quit 命令 (opens new window) | 关闭当前连接 |
Redis Auth 命令 (opens new window) | 验证密码是否正确 |
# 服务器命令
# 脚本命令
命令 | 描述 |
---|---|
Redis Script kill 命令 (opens new window) | 杀死当前正在运行的 Lua 脚本。 |
Redis Script Load 命令 (opens new window) | 将脚本 script 添加到脚本缓存中,但并不立即执行这个脚本。 |
Redis Eval 命令 (opens new window) | 执行 Lua 脚本。 |
Redis Evalsha 命令 (opens new window) | 执行 Lua 脚本。 |
Redis Script Exists 命令 (opens new window) | 查看指定的脚本是否已经被保存在缓存当中。 |
Redis Script Flush 命令 (opens new window) | 从脚本缓存中移除所有脚本。 |
# 事务命令
Redis事务的主要作用就是串联多个命令防止别的命令插队。
Redis事务三特性
单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
没有隔离级别的概念:队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行
不保证原子性:事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚
输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行。
组队的过程中可以通过discard来放弃组队
组队中某个命令出现了报告错误(入队命令错误(语法错误;严重错误导致服务器不能正常工作(例如内存不足))),执行时整个的所有队列都会被取消
如果执行阶段某个命令报出了错误(命令执行错误,事务提交),则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚
命令 | 描述 |
---|---|
Redis Exec 命令 (opens new window) | 执行所有事务块内的命令。 |
Redis Watch 命令 (opens new window) | 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。 |
Redis Discard 命令 (opens new window) | 取消事务,放弃执行事务块内的所有命令。 |
Redis Unwatch 命令 (opens new window) | 取消 WATCH 命令对所有 key 的监视。 |
Redis Multi 命令 (opens new window) | 标记一个事务块的开始。 |
乐观锁和悲观锁
悲观锁(Pessimistic Lock):顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁,读锁,写锁等,都是在做操作之前先上锁。
乐观锁(Optimistic Lock):顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的
# 发布订阅命令
Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。
客户端可以订阅频道如下图
发布的消息没有持久化,如果在订阅的客户端收不到hello,只能收到订阅后发布的消息
命令 | 描述 |
---|---|
Redis Unsubscribe 命令 (opens new window) | 指退订给定的频道。 |
Redis Subscribe 命令 (opens new window) | 订阅给定的一个或多个频道的信息。 |
Redis Pubsub 命令 (opens new window) | 查看订阅与发布系统状态。 |
Redis Punsubscribe 命令 (opens new window) | 退订所有给定模式的频道。 |
Redis Publish 命令 (opens new window) | 将信息发送到指定的频道。 |
Redis Psubscribe 命令 (opens new window) | 订阅一个或多个符合给定模式的频道。 |
# 多数据库
说明:一个Redis实例可以包含多个数据库,客户端可以指定连接某个数据库(与MySql客户端我们创建多个数据库类似)一个Redis实例最多可以提供16个数据库,下标是从0到15,默认连接的是第0号数据库。
代码相关演示:
127.0.0.1:6379> select 1 //选择数据库1
OK
127.0.0.1:6379[1]> keys *
(empty list or set)
127.0.0.1:6379[1]> select 0
OK
127.0.0.1:6379> set string1 2
OK
127.0.0.1:6379> keys * //查询所有的key
1) "hsah1"
2) "list1"
3) "set1"
4) "list2"
5) "string1"
6) "hash1"
7) "string2"
127.0.0.1:6379> move list1 1 //移动list1到数据库1
(integer) 1
127.0.0.1:6379> move set1 1
(integer) 1
127.0.0.1:6379> keys *
1) "hsah1"
2) "list2"
3) "string1"
4) "hash1"
5) "string2"
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> keys *
1) "list1"
2) "set1"
127.0.0.1:6379[1]> type set1 //获取数据类型
set
127.0.0.1:6379[1]>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 内存维护策略
Redis作为优秀的中间缓存件,时常会存储大量的数据,即使采取了集群部署来动态扩容,也应该及时的整理内存,维持系统性能。
在Redis中有两种解决方案
为数据设置超时时间
采用LRU(least recently used 最新最近使用)算法动态将不用的数据删除
maxmemory配置指令用于配置Redis存储数据时指定限制的内存大小。通过redis.conf可以设置该指令,或者之后使用CONFIG SET命令来进行运行时配置。
设置maxmemory为0代表没有内存限制。对于64位的系统这是个默认值,对于32位的系统默认内存限制为3GB。
当maxmemory限制达到的时候Redis会使用的行为由 Redis的maxmemory-policy配置指令来进行配置
- noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
- allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
- volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
- allkeys-random: 回收随机的键使得新添加的数据有空间存放。
- volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
- volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
- Redis4.0后新加了两个:
- volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键
- allkeys-lfu:从所有键中驱逐使用频率最少的键
# 数据持久化策略
安装Redis之后默认选择的是RDB方式,还有在修改过Redis-config之后注意要重新启动Redis服务才能生效 。
# RDB数据持久化方式
RDB是指在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。
Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等)数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程。
优势
Redis数据库会只包含一个文件存储在硬盘中,对于文件备份会简单很多。
对于灾难恢复,RDB是更好的选择,因为一个文件可以直接拷贝走,拷贝回来。
性能最大化,Redis开始持久化的时候只分出一些子进程,之后这些子进程会完成持久化工作,避免了服务器进程执行 IO 的操作。数据集很大的时候,启动效率会更高。
缺点
最大限度的避免数据丢失,RDB做的不是特别好,系统一定在定时持久化之前出现一些档期的情况,还没有来得及往硬盘上写,数据已经丢失掉。
因为RDB是通过开启子进程的方式来进行持久化操作的,因此当数据集比较大的时候,这个过程可能会导致服务器停止一定时间,几十毫秒甚至1秒。
配置
linux目录中/usr/local/Redis/Redis-conf目录中找到这样的几行代码
save 900 1
save 300 10
save 60 10000
2
3
4
第一行代码表示:900秒,也就是15分钟至少又一个key发生变化就会持久化一次。
第二行代码表示:300秒,至少有10个key发生变化就会往硬盘中持久化一次。
第三行代码表示:60秒,至少有10000个key发生变化就会往硬盘中持久化一次。
dbfilename dump.rdb
配置中继续往下看,看到这样一行代码,这个dump是数据库的名字。
默认是dump.rdb文件,这是默认的文件,可以自己修改文件名
往下一段代码
dir ./
保存路径位置,就是当前目录下的上面的名字,就是持久化的数据库
恢复:将备份文件 (dump.rdb) 移动到 redis 启动目录并启动服务即可可以通过config get dir获取目录。
关闭RDB
- 修改配置文件,设置save ""
- 临时关闭,config set save ""
# AOF数据持久化方式
AOF是指以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。更新时间默认是1秒
AOF默认把操作保存在appendonly.aof文件中,生成的文件会放在启动redis的目录
优势
可以带来更高的数据安全性,这种数据持久化方式有三种同步策略,每秒同步,每修改同步(每一次发生数据的变化都会立即被记录到磁盘中,效率最低但是最安全),不同步。
日志的写入操作是采用append追加的模式,在写入过程中即使出现服务器宕机问题,也不会破坏日志文件中已经写入的内容。
如果日志过大,Redis可以自动启动重写机制,Redis会不断的将修改的数据写入到老的磁盘当中,同时Redis会创建一个新的文件来记录此期间产生了哪些修改命令被执行了。
AOF包含一个格式非常清晰易于理解的日志文件,用于记录所有的修改操作。通过这个文件就可以完成数据的重建。
劣势
对于相同的数据集文件,AOF要比RDB文件大。
根据同步策略的不同,AOF在运行效率上往往低于RDB,AOF每修改就同步到硬盘上效率肯定是没有RDB高的。
配置
linux目录中/usr/local/Redis/Redis-conf目录中找到这样的几行代码
appendonly no
# The name of the append only file(default:"appendonly.aof")
appendfilename "appendonly.aof"
2
3
4
5
6
如果使用AOF的持久还方式,需要把appendonly 后面的属性变为yes,默认为no
appendonly.aof是用来记录所有修改操作的文件,这个文件还可以用来进行数据的恢复等,例如一条删除操作成功后,我们在appendonly.aof文件中把删除命令去掉,重新运行Redis,之前的数据又都会恢复
# appendfsync always
appendfsync everysec
# appendfsync no
2
3
4
这段代码是关于同步策略的一个设置,第一条是每修改就同步持久化,第二条是每秒同步持久化一次,第三条是不同步持久化。
相关配置
- appendonly 是否启动aof持久化,默认为no,关闭,需要的时候改成yes
- appendfilename aof文件名,默认是appendonly.aof,建议不要更改
- appendfsync 同步策略,有三个取值
- ayways (每修改同步): 同步持久化,每次发生数据变更会被立即记录到磁盘 性能较差但数据完整性比较好
- everysec (异步操作):每秒记录,如果一秒内宕机,有数据丢失
- no(从不同步)
损坏 AOF 文件之后怎么处理
AOF 文件损坏后,如文件后面追加了一些错误的信息,启动redis服务的时候会出现错误。可以使用redis-check-aof --fix [aof文件名] 进行修复。
rewrite(重写)
AOF采用文件追加方式,文件会越来越大为避免出现此种情况,新增了重写机制,当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集.可以使用命令bgrewriteaof
重写原理
AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似。
触发时机
Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发,但是企业中一般会配置的比较大,甚至高达5G
相关配置
- no-appendfsync-on-rewrite 同步机制,一般使用no,保证数据完整性
- auto-aof-rewrite-percentage 超过多少的时候,开始重写,默认是100,就是文件的大小是之前的两倍的时候开始重写。
- auto-aof-rewrite-min-size 大小超过多少的时候开始复制,默认是64mb,但是企业一般会比较大,如5G
# 如何选择
Ok, so what should I use?
The general indication is that you should use both persistence methods if you want a degree of data safety comparable to what PostgreSQL can provide you.
If you care a lot about your data, but still can live with a few minutes of data loss in case of disasters, you can simply use RDB alone.
There are many users using AOF alone, but we discourage it since to have an RDB snapshot from time to time is a great idea for doing database backups, for faster restarts, and in the event of bugs in the AOF engine.
Note: for all these reasons we'll likely end up unifying AOF and RDB into a single persistence model in the future (long term plan).
The following sections will illustrate a few more details about the two persistence models.
以上摘自官方文档:https://redis.io/topics/persistence#redis-persistence
dump.rdb和appendonly.aof可以同时存在,启动的时候会先加载appendonly.aof
AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大
只做缓存:如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式
同时开启两种持久化方式:在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整
RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件。那要不要只使用AOF呢?建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有AOF可能潜在的bug,留着作为一个万一的手段。
- 建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则。
- AOF重写的基础大小默认值64M太小了,可以设到5G以上。默认超过原大小100%大小时重写可以改到适当的数值
# 主从复制
主机数据更新后根据配置和策略, 自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主
在redis中设置主从有2种方式:
在redis.conf中设置slaveof
slaveof <masterip> <masterport>
使用redis-cli客户端连接到redis服务,执行slaveof命令
slaveof <masterip> <masterport>
第二种方式在重启后将失去主从复制关系。查看主从信息:INFO replication
缺点:
一旦负责写入数据的Redis节点宕机之后,那么我们的程序将无法继续写入
# 复制原理
Slave启动成功连接到master后会发送一个sync命令
Master接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令, 在后台进程执行完毕之后,master将传送整个数据文件到slave,以完成一次完全同步
全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步
但是只要是重新连接master,一次完全同步(全量复制)将被自动执行
# 具体配置
将redis的目录复制三分,分别命名为6379、6380、6381
pidfile /opt/apps/6379/redis_6379.pid
port 6379
dbfilename dump6379.rdb
2
3
4
5
6
- slave-priority 10:设置从机的优先级,值越小,优先级越高,用于选举主机时使用。默认100
启动三个Redis实例,在6380和6381上执行: slaveof 192.168.233.102 6379
可以将配置增加到文件中。永久生效。
查看系统进程
[root@centos-redis ~]# ps -ef | grep redis
root 1715 1213 0 16:38 pts/0 00:00:00 bin/redis-server 0.0.0.0:6379
root 1761 1597 0 16:42 pts/2 00:00:00 bin/redis-server 127.0.0.1:6381
root 1768 1502 0 16:42 pts/1 00:00:00 bin/redis-server 127.0.0.1:6380
root 1870 1774 0 16:43 pts/3 00:00:00 grep --color=auto redis
2
3
4
5
6
查看主从复制的相关信息
[root@centos-redis ~]# cd /opt/apps/6379/
[root@centos-redis 6379]# bin/redis-cli
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6381,state=online,offset=210,lag=1
slave1:ip=127.0.0.1,port=6380,state=online,offset=210,lag=1
master_replid:b9722ce52a748e08673cfca12b18a3abb23baa48
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:210
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:210
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 哨兵机制
为什么要有哨兵机制?
- 哨兵机制的出现是为了解决主从复制的缺点的
# 哨兵进程的作用
提醒(Notification):当被监控的某个Redis节点出现问题时, 哨兵(sentinel) 可以通过 API向管理员或者其他应用程序发送通知。
监控(Monitoring): 哨兵(sentinel) 会不断地检查你的Master和Slave是否运作正常。
自动故障迁移(Automatic failover):
当一个Master不能正常工作时,哨兵(sentinel)会开始一次自动故障迁移操作,它会将失效Master的其中一个Slave升级为新的Master,并让失效Master的其他Slave改为复制新的Master;
当客户端试图连接失效的Master时,集群也会向客户端返回新Master的地址,使得集群可以使用现在的Master替换失效Master。Master和Slave服务器切换后,Master的redis.conf、Slave的redis.conf和sentinel.conf的配置文件的内容都会发生相应的改变,即,Master主服务器的redis.conf配置文件中会多一行slaveof的配置,sentinel.conf的监控目标会随之调换。
# 哨兵进程的工作方式
每个Sentinel(哨兵)进程以每秒钟一次的频率向整个集群中的Master主服务器、Slave从服务器以及其他Sentinel(哨兵)进程发送一个 PING 命令。
如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel(哨兵)进程标记为主观下线(SDOWN)。
如果一个Master主服务器被标记为主观下线(SDOWN),则正在监视这个Master主服务器的所有 Sentinel(哨兵)进程要以每秒一次的频率确认Master主服务器的确进入了主观下线状态。
当有足够数量的 Sentinel(哨兵)进程(大于等于配置文件指定的值)在指定的时间范围内确认Master主服务器进入了主观下线状态(SDOWN), 则Master主服务器会被标记为客观下线(ODOWN)。
在一般情况下, 每个 Sentinel(哨兵)进程会以每 10 秒一次的频率向集群中的所有Master主服务器、Slave从服务器发送 INFO 命令。
当Master主服务器被 Sentinel(哨兵)进程标记为客观下线(ODOWN)时,Sentinel(哨兵)进程向下线的 Master主服务器的所有 Slave从服务器发送 INFO 命令的频率会从 10 秒一次改为每秒一次。
若没有足够数量的 Sentinel(哨兵)进程同意 Master主服务器下线, Master主服务器的客观下线状态就会被移除。若 Master主服务器重新向 Sentinel(哨兵)进程发送 PING 命令返回有效回复,Master主服务器的主观下线状态就会被移除。
总结
哨兵的个数为奇数
哨兵通过每秒钟一次的频率向整个集群发送PING的命令来检测其中的节点是否挂掉
哨兵会通过节点选举的方式来判断某个节点是否挂掉,如果超过半数的哨兵认为该节点挂掉,那么该节点就是挂掉了
节点的优先级
优先级在redis.conf中默认:slave-priority 100,值越小优先级越高
偏移量是指获得原主机数据最全的
每个redis实例启动后都会随机生成一个40位的runid
# 具体配置
新建sentinel.conf文件。配置哨兵,填写内容
#其中mymaster为监控对象起的服务器名称, 1 为至少有多少个哨兵同意迁移的数量。
sentinel monitor mymaster 127.0.0.1 6379 1
# 启动哨兵
redis-sentinel ./sentinel.conf
2
3
4
5
启动哨兵:redis-server ./sentinel-001.conf --sentinel
# 集群(Cluster)
即使有了主从复制,每个数据库都要保存整个集群中的所有数据,容易形成木桶效应
Redis 集群实现了对Redis的水平扩容,即启动N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N。
# 搭建集群
将rdb,aof文件都删除掉
制作6个实例,6379,6380,6381,6389,6390,6391
配置基本信息
cluster-enabled yes:打开集群模式
cluster-config-file nodes-6379.conf:设定节点配置文件名
cluster-node-timeout 15000:设定节点失联时间,超过该时间(毫秒),集群自动进行主从切换。
[root@hadoop102 etc]# vim 6379.conf
port 6379
pidfile /opt/apps/redis/logs/redis_6379.pid
logfile "/opt/apps/redis/logs/redis_6379.log"
dbfilename dump6379.rdb
appendonly no
daemonize yes
dir "/opt/apps/redis/data"
cluster-enabled yes
cluster-config-file nodes-6379.conf
cluster-node-timeout 15000
protected-mode no
2
3
4
5
6
7
8
9
10
11
12
PS:这仅仅是6379节点的配置,其他节点类似,除了端口、dbfilename、pidfile、cluster-config-file名字不一样
节点合体
bin/redis-cli --cluster create --cluster-replicas 1 192.168.233.102:6379 192.168.233.102:6380 192.168.233.102:6381 192.168.233.102:6389 192.168.233.102:6390 192.168.233.102:6391
此处不要用127.0.0.1, 请用真实IP地址
--replicas 1 采用最简单的方式配置集群,一台主机,一台从机,正好三组(先是3个主节点,然后是3个从节点)
看到以下提示信息表示集群启动成功
>>> Performing Cluster Check (using node 192.168.233.102:6379)
M: acb968e7d94da5327b155a500bb4664966a37c17 192.168.233.102:6379
slots:[0-5460] (5461 slots) master
1 additional replica(s)
S: 9ffee8ac41ce206f52e265c118b437d6f658e714 192.168.233.102:6389
slots: (0 slots) slave
replicates d70dfe2d66804df027c245ee7e76f2f1876f5624
M: cc3590d8b176bde6d31ff0155bbc2536d77ec520 192.168.233.102:6380
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
S: 7517dc91d67f362e05ba8a2b69e50867d2ec9122 192.168.233.102:6390
slots: (0 slots) slave
replicates acb968e7d94da5327b155a500bb4664966a37c17
M: d70dfe2d66804df027c245ee7e76f2f1876f5624 192.168.233.102:6381
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
S: 3aa8e53fa7f43ad81a46db1f50b8bafb29e2f253 192.168.233.102:6391
slots: (0 slots) slave
replicates cc3590d8b176bde6d31ff0155bbc2536d77ec520
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 集群验证
普通方式登录:直接进入读主机,存储数据时,会出现MOVED重定向操作
[root@hadoop102 redis]# ./bin/redis-cli -p 6389
127.0.0.1:6389> set n 100
(error) MOVED 3432 192.168.233.102:6379
2
3
4
采用集群策略连接,设置数据会自动切换到相应的写主机
[root@hadoop102 redis]# ./bin/redis-cli -c -p 6379
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set name xiaohei
-> Redirected to slot [5798] located at 192.168.233.102:6380
OK
2
3
4
5
6
通过 cluster nodes 命令查看集群信息
127.0.0.1:6379> cluster nodes
9ffee8ac41ce206f52e265c118b437d6f658e714 192.168.233.102:6389@16389 slave d70dfe2d66804df027c245ee7e76f2f1876f5624 0 1632980403000 3 connected
cc3590d8b176bde6d31ff0155bbc2536d77ec520 192.168.233.102:6380@16380 master - 0 1632980402783 2 connected 5461-10922
7517dc91d67f362e05ba8a2b69e50867d2ec9122 192.168.233.102:6390@16390 slave acb968e7d94da5327b155a500bb4664966a37c17 0 1632980400000 1 connected
acb968e7d94da5327b155a500bb4664966a37c17 192.168.233.102:6379@16379 myself,master - 0 1632980401000 1 connected 0-5460
d70dfe2d66804df027c245ee7e76f2f1876f5624 192.168.233.102:6381@16381 master - 0 1632980400000 3 connected 10923-16383
3aa8e53fa7f43ad81a46db1f50b8bafb29e2f253 192.168.233.102:6391@16391 slave cc3590d8b176bde6d31ff0155bbc2536d77ec520 0 1632980403811 2 connected
2
3
4
5
6
7
# 注意事项
关于Redis集群的创建,在官方文档有这么一段描述,摘录如下
If you are using Redis 5 or higher, this is very easy to accomplish as we are helped by the Redis Cluster command line utility embedded into redis-cli, that can be used to create new clusters, check or reshard an existing cluster, and so forth.
For Redis version 3 or 4, there is the older tool called redis-trib.rb which is very similar. You can find it in the src directory of the Redis source code distribution. You need to install redis gem to be able to run redis-trib.
gem install redis
地址:https://redis.io/topics/cluster-tutorial
也正如文中所述,如果使用Redis5以下的版本需要ruby的支持,而尴尬的是centos7中ruby的默认版本为2.0.0,执行上述命令需要升级ruby版本
防火墙相关
- systemctl status firewalld.service
- systemctl stop firewalld.service
- systemctl disable firewalld.service
# Windows安装cluster
下载Ruby
redis-trib是一个 Ruby 程序,所以要安装Ruby,我下载rubyinstaller-2.7.0-1-x64.exe
下载地址:https://rubyinstaller.org/downloads/
gem install redis
成功的打印结果:
Fetching redis-4.1.3.gem
Successfully installed redis-4.1.3
Parsing documentation for redis-4.1.3
Installing ri documentation for redis-4.1.3
Done installing documentation for redis after 1 seconds
1 gem installed
下载redis-trib.rb文件
下载地址:https://github.com/beebol/redis-trib.rb
通过ruby安装redis集群
ruby redis-trib.rb\redis-trib.rb create --replicas 1 127.0.0.1:6370 127.0.0.1:6380 127.0.0.1:6390 127.0.0.1:6371 127.0.0.1:6381 127.0.0.1:6391
# slots(插槽)
在集群启动成功的时候我们可以看到如下提示
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
2
3
4
官方文档有一段关于插槽的描述,其原文摘录如下
There are 16384 hash slots in Redis Cluster, and to compute what is the hash slot of a given key, we simply take the CRC16 of the key modulo 16384.
Every node in a Redis Cluster is responsible for a subset of the hash slots, so for example you may have a cluster with 3 nodes, where:
- Node A contains hash slots from 0 to 5500.
- Node B contains hash slots from 5501 to 11000.
- Node C contains hash slots from 11001 to 16383.
地址:https://redis.io/topics/cluster-tutorial
一个 Redis 集群包含 16384 个插槽(hash slot), 数据库中的每个键都属于这 16384 个插槽的其中一个
集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和
集群中的每个节点负责处理一部分插槽。 举个例子, 如果一个集群3个节点, 其中
- 节点 A 负责处理 0 号至 5460 号插槽。
- 节点 B 负责处理 5461 号至 10922 号插槽。
- 节点 C 负责处理 10923 号至 16383 号插槽
# 集群中录入值
在redis-cli每次录入、查询键值,redis都会计算出该key应该送往的插槽,如果不是该客户端对应服务器的插槽,redis会报错,并告知应前往的redis实例地址和端口
redis-cli客户端提供了 –c 参数实现自动重定向。
如 redis-cli -c –p 6379 登入后,再录入、查询键值对可以自动重定向。不在一个slot下的键值,是不能使用mget,mset等多键操作
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3
(error) CROSSSLOT Keys in request don't hash to the same slot
2
3
4
可以通过{}来定义组的概念,从而使key中{}内相同内容的键值对放到一个slot中去。
[root@hadoop102 redis]# ./bin/redis-cli -c -p 6379
127.0.0.1:6379> mset k1{cust} v1 k2{cust} v2 k3{cust} v3
OK
2
3
# 集群中查询值
基础语法:
CLUSTER GETKEYSINSLOT<slot><count>
返回 count 个 slot 槽中的键。
127.0.0.1:6379> cluster keyslot cust
(integer) 4847
127.0.0.1:6379> cluster countkeysinslot 4847
(integer) 3
127.0.0.1:6379> cluster getkeysinslot 4847 10
1) "k1{cust}"
2) "k2{cust}"
3) "k3{cust}"
2
3
4
5
6
7
8
# 故障恢复
如果主节点下线?从节点能否自动升为主节点?注意:15秒超时
手动结束6380端口的进程之后查询集群节点信息
127.0.0.1:6379> cluster nodes
9ffee8ac41ce206f52e265c118b437d6f658e714 192.168.233.102:6389@16389 slave d70dfe2d66804df027c245ee7e76f2f1876f5624 0 1632982193000 3 connected
cc3590d8b176bde6d31ff0155bbc2536d77ec520 192.168.233.102:6380@16380 master,fail - 1632982141896 1632982139862 2 disconnected
7517dc91d67f362e05ba8a2b69e50867d2ec9122 192.168.233.102:6390@16390 slave acb968e7d94da5327b155a500bb4664966a37c17 0 1632982193762 1 connected
acb968e7d94da5327b155a500bb4664966a37c17 192.168.233.102:6379@16379 myself,master - 0 1632982192000 1 connected 0-5460
d70dfe2d66804df027c245ee7e76f2f1876f5624 192.168.233.102:6381@16381 master - 0 1632982192000 3 connected 10923-16383
3aa8e53fa7f43ad81a46db1f50b8bafb29e2f253 192.168.233.102:6391@16391 master - 0 1632982193000 7 connected 5461-10922
2
3
4
5
6
7
主节点(6380)恢复后,主从关系会如何?主节点回来变成从机。
127.0.0.1:6379> cluster nodes
9ffee8ac41ce206f52e265c118b437d6f658e714 192.168.233.102:6389@16389 slave d70dfe2d66804df027c245ee7e76f2f1876f5624 0 1632982306288 3 connected
cc3590d8b176bde6d31ff0155bbc2536d77ec520 192.168.233.102:6380@16380 slave 3aa8e53fa7f43ad81a46db1f50b8bafb29e2f253 0 1632982307307 7 connected
7517dc91d67f362e05ba8a2b69e50867d2ec9122 192.168.233.102:6390@16390 slave acb968e7d94da5327b155a500bb4664966a37c17 0 1632982304244 1 connected
acb968e7d94da5327b155a500bb4664966a37c17 192.168.233.102:6379@16379 myself,master - 0 1632982305000 1 connected 0-5460
d70dfe2d66804df027c245ee7e76f2f1876f5624 192.168.233.102:6381@16381 master - 0 1632982304000 3 connected 10923-16383
3aa8e53fa7f43ad81a46db1f50b8bafb29e2f253 192.168.233.102:6391@16391 master - 0 1632982305264 7 connected 5461-10922
2
3
4
5
6
7
如果所有某一段插槽的主从节点都宕掉,redis服务是否还能继续?
如果某一段插槽的主从都挂掉,而cluster-require-full-coverage 为yes ,那么 ,整个集群都挂掉
如果某一段插槽的主从都挂掉,而cluster-require-full-coverage 为no ,那么,该插槽数据全都不能使用,也无法存储。
# Java集成Redis
依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>
2
3
4
5
6
// 创建一个 Jedis 的连接
Jedis jedis = new Jedis(“127.0.0.1”, 6379);
// 密码认证 如果设置了密码,就需要进行认证
jedis.auth(“offcn123”);
// 执行 redis 命令
jedis.set("mytest", "hello world, this is jedis client!");
2
3
4
5
6
7
# Spring环境
依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
2
3
4
5
6
7
8
9
10
11
12
@ComponentScan(basePackages = "demos")
@Configuration
public class Application{
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate template=new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
template.setDefaultSerializer(new StringRedisSerializer());
return template;
}
@Bean
public RedisConnectionFactory redisConnectionFactory(){
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
JedisConnectionFactory factory=new JedisConnectionFactory(configuration);
return factory;
}
public static void main(String args[]){
ApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
RedisTemplate bean = context.getBean(RedisTemplate.class);
bean.opsForValue().set("11", "222");
System.out.println( bean.opsForValue().get("11"));;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# Redis序列化
当我们使用RedisTemplte对数据进行操作的时候它会采用序列化的方式将我们的数据存入redis
RedisTemplte默认使用序列化方式是我们的JDK序列化
区别:
JdkSerializationRedisSerializer序列化后长度最小,Jackson2JsonRedisSerializer效率最高。
如果综合考虑效率和可读性,牺牲部分空间,推荐key使用StringRedisSerializer,保持的key简明易读;value可以使用Jackson2JsonRedisSerializer
如果空间比较敏感,效率要求不高,推荐key使用StringRedisSerializer,保持的key简明易读;value可以使用JdkSerializationRedisSerializer
总结:
Redis支持的序列化方式
JdkSerializationRedisSerializer:默认值
Jackson2JsonRedisSerializer
StringRedisSerializer:推荐
# SpringBoot环境
jedis和Lettuce都是Redis的客户端,它们都可以连接Redis服务器,但是在SpringBoot2.0之后默认都是使用的Lettuce这个客户端连接Redis服务器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--SpringBoot2.X集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
2
3
4
5
6
7
8
9
10
11
# 应用问题
Redis在实际应用中在面临各种场景时的一些解决方案
# 缓存穿透
问题描述
key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会压到数据源,从而可能压垮数据源。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。
解决方案
一个一定不存在缓存及查询不到的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。
- 对空值缓存:
如果一个查询返回的数据为空(不管是数据是否不存在),我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过五分钟
- 设置可访问的名单(白名单)
使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。
- 采用布隆过滤器
布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。)将所有可能存在的数据哈希到一个足够大的bitmaps中,一个一定不存在的数据会被 这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力。
- 进行实时监控
当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务
# 缓存击穿
问题描述
key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
解决方案
key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题
- 预先设置热门数据
在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长
- 实时调整
现场监控哪些数据热门,实时调整key的过期时长
- 使用锁
就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db。
先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX)去set一个mutex key
当操作返回成功时,再进行load db的操作,并回设缓存,最后删除mutex key;
当操作返回失败,证明有线程在load db,当前线程睡眠一段时间再重试整个get缓存的方法。
# 缓存雪崩
问题描述
key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
缓存雪崩与缓存击穿的区别在于这里针对很多key缓存,前者则是某一个key正常访问
解决方案
缓存失效时的雪崩效应对底层系统的冲击非常可怕!
- 构建多级缓存架构
nginx缓存 + redis缓存 +其他缓存(ehcache等)
- 使用锁或队列
用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。不适用高并发情况
- 设置过期标志更新缓存
记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际key的缓存
- 将缓存失效时间分散开
比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件
# 分布式锁
问题描述
随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的Java API并不能提供分布式锁的能力。为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!
分布式锁主流的实现方案:
- 基于数据库实现分布式锁
- 基于缓存(Redis等)
- 基于Zookeeper
解决方案
redis:命令
# set sku:1:info “OK” NX PX 10000
EX second :设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。
PX millisecond :设置键的过期时间为 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX key millisecond value 。
NX :只在键不存在时,才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。
XX :只在键已经存在时,才对键进行设置操作。
# 常见错误
问题描述
*** ERROR: Invalid configuration for cluster creation.
*** Redis Cluster requires at least 3 master nodes.
*** This is not possible with 4 nodes and 1 replicas per node.
*** At least 6 nodes are required.
2
3
4
解决方案
- Redis集群至少需要3个master节点
- 增加节点数量
问题描述
[err]: pending querybuf: check size of pending_querybuf after set a big value in tests/unit/pendingquerybuf.tcl
the used_memory of replica is much larger than master. Master:43877872 Replica:69042640
2
3
解决方案
增加内存
关闭其他程序,多执行几遍make test
问题描述
make[1]: *** [server.o] 错误 1
make[1]: 离开目录“/usr/redis-6.0.1/src”
make: *** [all] 错误 2
server.c:2402:11: 错误:‘struct redisServer’没有名为‘assert_file’的成员
server.assert_file = "<no file>";
^
server.c:2403:11: 错误:‘struct redisServer’没有名为‘assert_line’的成员
server.assert_line = 0;
^
server.c:2404:11: 错误:‘struct redisServer’没有名为‘bug_report_start’的成员
server.bug_report_start = 0;
^
server.c:2405:11: 错误:‘struct redisServer’没有名为‘watchdog_period’的成员
server.watchdog_period = 0;
^
server.c:2411:11: 错误:‘struct redisServer’没有名为‘lua_always_replicate_commands’的成员
server.lua_always_replicate_commands = 1;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
解决方案
# 升级到9.1版本
yum -y install centos-release-scl
yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
#临时启用
scl enable devtoolset-9 bash
2
3
4
5
6
问题描述
[root@redis-server redis-6.0.1]# make test
cd src && make test
make[1]: 进入目录“/usr/redis-6.0.1/src”
CC Makefile.dep
make[1]: 离开目录“/usr/redis-6.0.1/src”
make[1]: 进入目录“/usr/redis-6.0.1/src”
You need tcl 8.5 or newer in order to run the Redis test
make[1]: *** [test] 错误 1
make[1]: 离开目录“/usr/redis-6.0.1/src”
make: *** [test] 错误 2
2
3
4
5
6
7
8
9
10
解决方案
yum install tcl
make test
2