Redis概述
Redis是一个使用ANSI C编写的开源、包含多种数据结构、支持网络、基于内存、可选持久性的键值对存储数据库。
特性:
- 基于内存运行,性能高效
- 支持分布式,理论上可以无限扩展
- key-value存储系统
- 开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API
NoSQL(NoSQL = Not Only SQL),意即“不仅仅是SQL”,泛指非关系型的数据库。随着互联网web2.0 网站的兴起,传统的关系数据库在应付特别是超大规模和高并发类型纯动态网站已经显得力不从心,暴露了很多难以克服的问题。
结构化数据和非结构化数据
- 结构化数据指的是由二维表结构来逻辑表达和实现的数据,严格遵循数据格式与长度规范,也称作为行数据。
- 非结构化数据,指的是数据结构不规则或不完整,没有任何预定义的数据模型,不方便用二维逻辑表来表现的数据,例如办公文档(Word)、文本、图片、HTML、各类报表、视频音频等。
NoSQL的四大分类
KV型NoSql(redis)
KV型NoSql顾名思义就是以键值对形式存储的非关系型数据库KV型NoSql最大的优点就是高性能,利用Redis自带的BenchMark做基准测试,TPS可达到10万的级别,性能非常强劲。
- 数据基于内存,读写效率高
- KV型数据,时间复杂度为O(1),查询速度快
列式NoSql(HBase)
列式NoSql,大数据时代最具代表性的技术之一了,以HBase为代表。
- 查询时只有指定的列会被读取,不会读取所有列
- 列数据被组织到一起,一次磁盘IO可以将一列数据一次性读取到内存中
文档型NoSql(MongoDB)
文档型NoSql指的是将半结构化数据存储为文档的一种NoSql,文档型NoSql通常以JSON或者XML格式存储数据。关系型数据库是按部就班地每个字段一列存,在MongDB里面就是一个JSON字符串存储
搜索型NoSql(ElasticSearch)
传统关系型数据库主要通过索引来达到快速查询的目的,但是在全文搜索的场景下,索引是无能为力的,like查询一来无法满足所有模糊匹配需求,二来使用限制太大且使用不当容易造成慢查询,搜索型NoSql的诞生正是为了解决关系型数据库全文搜索能力较弱的问题,ElasticSearch是搜索型NoSql的代表产品
关系型和非关系区别
关系型
优点:
- 易于维护:都是使用表结构,格式一致;
- 使用方便:SQL语言通用,可用于复杂查询;
- 复杂操作:支持SQL,可用于一个表以及多个表之间非常复杂的查询。
缺点:
- 读写性能比较差,尤其是海量数据的高效率读写;
- 固定的表结构,灵活度稍欠
非关系型
关系型数据库最典型的数据结构是表,由二维表及其之间的联系所组成的一个数据组织
优点:
- 格式灵活:存储数据的格式可以是key,value形式、文档形式、图片形式等等,文档形式、图片形式等等,使用灵活,应用场景广泛,而关系型数据库则只支持基础类型。
- 速度快:nosql可以使用硬盘或者随机存储器作为载体,而关系型数据库只能使用硬盘;
- 高扩展性;
- 成本低:nosql数据库部署简单,基本都是开源软件。
缺点:
- 不提供sql支持,学习和使用成本较高;
- 无事务处理;
- 数据结构相对复杂,复杂查询方面稍欠
Redis入门
Redis是一个字典结构的存储服务器,一个Redis实例提供了多个用来存储数据的字典,客户端可以指定将数据存储在哪个字典中。Redis默认支持16个数据库,可以通过调整Redis的配置文件redis/redis.conf中的databases来修改这一个值,设置完毕后重启Redis便完成配置。
Redis 使用的到底是多线程还是单线程?
因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。redis 采用网络IO多路复用技术来保证在多连接的时候, 系统的高吞吐量
IO多路复用技术
Redis数据类型
String
String是Redis最基本的类型,一个key对应一个value。String是二进制安全的,意味着String可以包含
任何数据,比如序列化对象或者一张图片。String最多可以放512M的数据。
使用场景:
- value 除了是字符串以外还可以是数字。
- 计数器
- 统计多单位的数量
- 粉丝数
- 对象缓存存储
- 分布式锁
List
List是简单的字符串列表,按照插入顺序排序,添加一个元素到列表的头部(左边)后者尾部(右边)。底层是一个双向链表,对两端的操作性能极高,通过索引操作中间的节点性能较差。
一个List最多可以包含2的32次方-1个元素(每个列表超过40亿个元素)
使用场景:
- 消息队列
- 排行榜
- 最新列表
SET
和List功能类似,但是Set是自动重排的,如果是存储一个列表数据,不希望出现重复数据时,Set是一个很好的选择。Set是String类型的无序集合,它底层是一个value为null的hash表,所以添加、删除、查找的时间复杂度都是O(1).
使用场景:
- 黑白名单
- 随机展示
- 好友
- 关注人
- 粉丝
- 感兴趣的人集合
Hash
Hash是一个键值对的集合,Hash是一个String类型的Field(字段)和Value(值),Hash特别适用于存储对象。
Hash结构优化:
- 如果field数量较少,存储结构优化为类型数组
- 如果field数量较多,存储结构优化为HashMap结构
使用场景:
- 购物车
- 存储对象
ZSet
ZSet和Set很相似,是一个没有重复元素的String集合,不同之处是ZSet的每个元素都关联了一个分数(Score),这个分数被用来按照从低分到高分的方式排序集合中的元素,集合的元素是唯一的,但是分数可以重复。
使用场景 :
- 延时队列
- 排行榜
- 限流
BitMaps
BitMaps本身就不是一种数据结构,实际上就是字符串,但是它可以对字符串的位进行操作。“合理地使用位能够有效的提高内存使用率和开发效率”。
使用场景:
- 活跃天数
- 打卡天数
- 登录天数
- 用户签到
- 统计活跃用户
- 统计用户是否在线
- 实现布隆过滤器
Geospatia
GEO,Geographic,地理信息,该类型就是元素的二维坐标,在地图上就是经纬度。Redis基于该类型提供了经纬度设置、查询、范围查询、距离查询、经纬度Hash等常见操作。
使用场景:
- 附近的电影院
- 附近的好友
- 离最近的火锅店
Redis配置文件
units
配置大小单位,开头定义基本度量单位,只支持bytes,大小写不敏感
INCLUDES
Redis只有一个配置文件,如果多个人进行开发维护,那么就需要多个这样的配置文件,这时候多个配置文件就可以在此通过 include /path/to/local.conf 配置进来,而原本的 redis.conf 配置文件就作为一个总闸。
NETWORK
参数:
- bind:绑定redis服务器网卡IP,默认为127.0.0.1,即本地回环地址。这样的话,访问redis服务只能通过本机的客户端连接,而无法通过远程连接。如果bind选项为空的话,那会接受所有来自于可用网络接口的连接。
- port:指定redis运行的端口,默认是6379。由于Redis是单线程模型,因此单机开多个Redis进程的时候会修改端口。
- timeout:设置客户端连接时的超时时间,单位为秒。当客户端在这段时间内没有发出任何指令,那么关闭该连接。默认值为0,表示不关闭。
- tcp-keepalive :单位是秒,表示将周期性的使用SO_KEEPALIVE检测客户端是否还处于健康状态,避免服务器一直阻塞,官方给出的建议值是300s,如果设置为0,则不会周期性的检测
GENERAL
详解:
- daemonize:设置为yes表示指定Redis以守护进程的方式启动(后台启动)。默认值为 no
- pidfile:配置PID文件路径,当redis作为守护进程运行的时候,它会把 pid 默认写到 /var/redis/run/redis_6379.pid 文件里面
- loglevel :定义日志级别。默认值为notice,有如下4种取值:
- debug(记录大量日志信息,适用于开发、测试阶段)
- verbose(较多日志信息)
- notice(适量日志信息,使用于生产环境)
- warning(仅有部分重要、关键信息才会被记录)
- logfile :配置log文件地址,默认打印在命令行终端的窗口上
- databases:设置数据库的数目。默认的数据库是DB 0 ,可以在每个连接上使用select 命令选择一个不同的数据库,dbid是一个介于0到databases - 1 之间的数值。默认值是 16,也就是说默认Redis有16个数据库。
SNAPSHOTTING
参数:
- save:这里是用来配置触发 Redis的持久化条件,也就是什么时候将内存中的数据保存到硬盘
- save 900 1:表示900 秒内如果至少有 1 个 key 的值变化,则保存
- save 300 10:表示300 秒内如果至少有 10 个 key 的值变化,则保存
- save 60 10000:表示60 秒内如果至少有 10000 个 key 的值变化,则保存
REPLICATION
参数:
- slave-serve-stale-data:默认值为yes。当一个 slave 与 master 失去联系,或者复制正在进行的时候, slave 可能会有两种表现:
- 如果为 yes ,slave 仍然会应答客户端请求,但返回的数据可能是过时,或者数据可能是空的在第一次同步的时候
- 如果为 no ,在你执行除了 info he salveof 之外的其他命令时,slave 都将返回一个 “SYNC with master in progress” 的错误
- slave-read-only:配置Redis的Slave实例是否接受写操作,即Slave是否为只读Redis。默认值为yes。
- repl-diskless-sync:主从数据复制是否使用无硬盘复制功能。默认值为no。
- repl-diskless-sync-delay:当启用无硬盘备份,服务器等待一段时间后才会通过套接字向从站传送RDB文件,这个等待时间是可配置的。
- repl-disable-tcp-nodelay:同步之后是否禁用从站上的TCP_NODELAY 如果你选择yes,redis会使用较少量的TCP包和带宽向从站发送数据。
CLIENTS
参数:
maxclients :设置客户端最大并发连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件。 描述符数-32(redis server自身会使用一些),如果设置maxclients为0 。表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息
MEMORY MANAGEMENT
参数:
-
maxmemory:设置Redis的最大内存,如果设置为0 。表示不作限制。通常是配合下面介绍的maxmemory-policy参数一起使用。
-
maxmemory-policy :当内存使用达到maxmemory设置的最大值时,redis使用的内存清除策略。有以下几种可以选择:
-
- 1)volatile-lru 利用LRU算法移除设置过过期时间的key (LRU:最近使用 Least Recently
- Used )
- 2)allkeys-lru 利用LRU算法移除任何key
- 3)volatile-random 移除设置过过期时间的随机key
- 4)allkeys-random 移除随机ke
- 5)volatile-ttl 移除即将过期的key(minor TTL)
- 6)noeviction noeviction 不移除任何key,只是返回一个写错误 ,默认选项
-
maxmemory-samples :LRU 和 minimal TTL 算法都不是精准的算法,但是相对精确的算法(为了节省内存)。随意你可以选择样本大小进行检,redis默认选择3个样本进行检测,可以通过maxmemory-samples进行设置样本数。
APPEND ONLY MODE
参数:
- appendonly:默认redis使用的是rdb方式持久化,这种方式在许多应用中已经足够用了。但是redis如果中途宕机,会导致可能有几分钟的数据丢失,根据save来策略进行持久化,Append Only File是另一种持久化方式, 可以提供更好的持久化特性。Redis会把每次写入的数据在接收后都写入appendonly.aof文件,每次启动时Redis都会先把这个文件的数据读入内存里,先忽略RDB文件。默认值为no。
- appendfilename :aof文件名,默认是"appendonly.aof"
- appendfsync:aof持久化策略的配置;no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快;always表示每次写入都执行fsync,以保证数据同步到磁盘;everysec表示每秒执行一次fsync,可能会导致丢失这1s数据
LUA SCRIPTING
lua-time-limit:一个lua脚本执行的最大时间,单位为ms。默认值为5000.
REDIS CLUSTER
参数:
- cluster-enabled:集群开关,默认是不开启集群模式。
- cluster-config-file:集群配置文件的名称。
- cluster-node-timeout :可以配置值为15000。节点互连超时的阀值,集群节点超时毫秒数
- cluster-slave-validity-factor :可以配置值为10。
SECURITY
requirepass:设置redis连接密码。
Redis发布和订阅
Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。
//订阅语法格式
subcribe 主题名字
//示例:
127.0.0.1:6379> SUBSCRIBE channel-1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel-1"
3) (integer) 1
//发布命令
publish channel-1 hello
//示例:(打开另一个客户端,给channel1发布消息hello)
127.0.0.1:6379> PUBLISH channel-1 hello
(integer) 1 //返回的1是订阅者数量。
//打开第一个客户端可以看到发送的消息
127.0.0.1:6379> SUBSCRIBE channel-1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel-1"
3) (integer) 1
1) "message"
2) "channel-1"
3) "hello"
//发布的消息没有持久化,如果在订阅的客户端收不到hello,只能收到订阅后发布的消息。
Redis慢查询
Redis命令执行的过程
- 慢查询发生在第3阶段
- 客户端超时不一定慢查询,但慢查询是客户端超时的一个可能因素
- 慢查询日志是存放在Redis内存列表中
慢查询日志
慢查询日志是Redis服务端在命令执行前后计算每条命令的执行时长,当超过某个阈值是记录下来的日志。日志中记录了慢查询发生的时间,还有执行时长、具体什么命令等信息,它可以用来帮助开发和运维人员定位系统中存在的慢查询。
获取慢查询日志
可以使用 slowlog get 命令获取慢查询日志,在 slowlog get 后面还可以加一个数字,用于指定获取慢查询日志的条数,比如,获取3条慢查询日志:
配置慢查询日志
- 命令执行时长的指定阈值 slowlog-log-slower-than。slowlog-log-slower-than的作用是指定命令执行时长的阈值,执行命令的时长超过这个阈值时就会被记录下来。
- 存放慢查询日志的条数 slowlog-max-len。slowlog-max-len的作用是指定慢查询日志最多存储的条数。实际上,Redis使用了一个列表存放慢查询 日志,slowlog-max-len就是这个列表的最大长度。
查看慢查询日志
127.0.0.1:0>config get slow*
1) "slowlog-max-len"
2) "128"
3) "slowlog-log-slower-than"
4) "10000"
10000阈值,单位微秒,此处为10毫秒,128慢日志记录保存数量的阈值,此处保存128条。
修改Redis配置文件
比如,把slowlog-log-slower-than设置为1000,slowlog-max-len设置为1200:
- slowlog-log-slower-than 1000
- slowlog-max-len 1200
实践建议
slowlog-max-len****配置建议
- 线上建议调大慢查询列表,记录慢查询时Redis会对长命令做截断操作,并不会占用大量内存。
- 增大慢查询列表可以减缓慢查询被剔除的可能,例如线上可设置为1000以上。
slowlog-log-slower-than****配置建议
- 默认值超过10毫秒判定为慢查询,需要根据Redis并发量调整该值。
- 由于Redis采用单线程响应命令,对于高流量的场景,如果命令执行时间在1毫秒以上,那么Redis最多可支撑OPS不到1000。因此对于高OPS场景的Redis建议设置为1毫秒。
流水线pipeline
经历了 1次pipeline(n条命令) = 1次网络时间 + n次命令时间,这大大减少了网络时间的开销,这就是流
水线。
一次网络命令通信模型
经历一次时间=一次网络时间+一次命令时间
批量网络命令通信模型
经历n次时间=n次网络时间+n次命令时间
//引入jedis依赖包:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency
//没有pipeline命令
Jedis jedis - new Jedis("127.0.0.1",6379);
for ( int i = 0 ; i < 10000 ; i ++ ){
jedis.hset("hashkey:" + i , "field" + i , "value" + i);
}
/**
在不使用pipeline的情况下,使用for循环进行每次一条命令的执行操作,耗费的时间可能达到 1w
条插入命令的耗时为50s
*/
//使用pipeline
Jedis jedis = new Jedis("127.0.0.1",6379);
for ( int i = 0; i < 100 ; i++) {
Pipeline pipeline = jedis.ppipelined();
for (int j = i * 100 ; j < (i + 1) * 100 ; j++) {
pipeline.hset("hashkey:" + j,"field" + j, "value" + j);
}
pipeline.syncAndReturnAll();
}
Redis持久化机制
由于redis的数据都放在内存中,如果没有配置持久化,Redis重启后数据就全部丢失了,于是重启Redis需要开启持久化功能。将数据保存到磁盘上,当Redis重启后,可以从磁盘中恢复数据。“对于Redis而言,持久化机制是指把内存中的数据存为硬盘文件, 这样当Redis重启或服务器故障时能根据持久化后的硬盘文件恢复数 据。”
redis持久化的意义,在于故障恢复。比如部署了一个redis,作为cache缓存,同时也可以保存一些比较重要的数据。
Redis****提供了两个不同形式的持久化方式
RDB 持久化是全量备份,比较耗时,所以Redis就提供了一种更为高效地AOF(Append Only-file)持久化方案,简单描述它的工作原理:AOF日志存储的是Redis服务器指令序列,AOF只记录对内存进行修改的指令记录。
- RDB(Redis DataBase)
- AOF(Append Only File)
RDB持久化机制
RDB:指的是在指定的时间间隔内将内存的数据集快照写入磁盘,恢复时直接将快照读取到内存里。“快照-经过压缩的二进制文件”。
配置dump.rdb
RDB保存的文件,在redis.conf中配置文件名称,默认为dump.rdb。(rdb文件的保存位置,也可以修改。默认在Redis启动时命令行所在的目录下。)
rdb文件的保存路径,也可以修改。默认为Redis启动时命令行所在的目录下
触发机制-主要三种方式
RDB配置
快照默认配置:
- save 3600 1:表示3600秒内(一小时)如果至少有1个key的值变化,则保存。
- save 300 100:表示300秒内(五分钟)如果至少有100个 key 的值变化,则保存。
- save 60 10000:表示60秒内如果至少有 10000个key的值变化,则保存。
配置新的保存规则
给redis.conf添加新的快照策略,30秒内如果有5次key的变化,则触发快照。配置修改后,需要重启
Redis服务。
- save 3600 1
- save 300 100
- save 60 10000
- save 30 5
flushall
执行flushall命令,也会触发rdb规则。
save与bgsave
手动触发Redis进行RDB持久化的命令有两种:
\1. save 该命令会阻塞当前Redis服务器,执行save命令期间,Redis不能处理其他命令,直到RDB过程完成为止,不建议使用。
\2. bgsave执行该命令时,Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。
高级配置
stop-writes-on-bgsave-error
默认值是yes。当Redis无法写入磁盘的话,直接关闭Redis的写操作。
rdbcompression
默认值是yes。对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用LZF算
法进行压缩。如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能,但是存储在磁盘上的快照
会比较大。
rdbchecksum
默认值是yes。在存储快照后,我们还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加
大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。
恢复数据
只需要将rdb文件放在Redis的启动目录,Redis启动时会自动加载dump.rdb并恢复数据。
优缺点
适合大规模的数据恢复
对数据完整性和一致性要求不高更适合使用
节省磁盘空间
恢复速度快
在备份周期在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会丢失最后一次快照
后的所有修改。
AOF持久化机制
以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来。AOF默认不开启可以在redis.conf中配置文件名称,默认为appendonly.aof。
AOF文件的保存路径,同RDB的路径一致,如果AOF和RDB同时启动,Redis默认读取AOF的数
据。
AOF启动/修复/恢复
设置Yes:修改默认的appendonly no,改为yes
AOF同步频率设置
参数:
\1. appendfsync always 始终同步,每次Redis的写入都会立刻记入日志,性能较差但数据完整性比较好。
\2. appendfsync everysec 每秒同步,每秒记入日志一次,如果宕机,本秒的数据可能丢失。
\3. appendfsync no redis不主动进行同步,把同步时机交给操作系统。
优缺点
优点
备份机制更稳健,丢失数据概率更低。
可读的日志文本,通过操作AOF稳健,可以处理误操作。
缺点
比起RDB占用更多的磁盘空间。
恢复备份速度要慢。
每次读写都同步的话,有一定的性能压力。
选用持久化方式
不要仅仅使用****RDB
RDB数据快照文件,都是每隔5分钟,或者更长时间生成一次,这个时候就得接受一旦redis进程宕机,
那么会丢失最近5分钟的数据。
也不要仅仅使用****AOF
\1. 你通过AOF做冷备,没有RDB做冷备,来的恢复速度更快。
\2. RDB每次简单粗暴生成数据快照,更加健壮,可以避免AOF这种复杂的备份和恢复机制的bug。
综合使用AOF和RDB两种持久化机制
用AOF来保证数据不丢失,作为数据恢复的第一选择,用RDB来做不同程度的冷备,在AOF文件都丢失
或损坏不可用的时候,还可以使用RDB来进行快速的数据恢复。
RDB的优点:
1.体积更小:相同的数据量rdb数据比aof的小,因为rdb是紧凑型文件
2.恢复更快:因为rdb是数据的快照,基本上就是数据的复制,不用重新读取再写入内存
3.性能更高:父进程在保存rdb时候只需要fork一个子进程,无需父进程的进行其他io操作,也保证了服务器的性能。
缺点:
1.故障丢失:因为rdb是全量的,一般是使用shell脚本实现30分钟或者1小时或者每天对redis进行rdb备份,(注,也可以是用自带的策略),但是最少也要5分钟进行一次的备份,所以当服务死掉后,最少也要丢失5分钟的数据。
2.耐久性差:相对aof的异步策略来说,因为rdb的复制是全量的,即使是fork的子进程来进行备份,当数据量很大的时候对磁盘的消耗也是不可忽视的,尤其在访问量很高的时候,fork的时间也会延长,导致cpu吃紧,耐久性相对较差。
aof的优点
1.数据保证:可以设置fsync策略,一般默认是everysec,也可以设置每次写入追加,所以即使服务死掉了,也最多丢失一秒数据
2.自动缩小:当aof文件大小到达一定程度的时候,后台会自动的去执行aof重写,此过程不会影响主进程,重写完成后,新的写入将会写到新的aof中,旧的就会被删除掉。但是此条如果拿出来对比rdb的话还是没有必要算成优点,只是官网显示成优点而已。
缺点:
1.性能相对较差:它的操作模式决定了它会对redis的性能有所损耗
2.体积相对更大:尽管是将aof文件重写了,但是毕竟是操作过程和操作结果仍然有很大的差别,体积也更大。
Redis事务
Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。
Redis事务没有隔离级别的概念
批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行,也就不存在事务内的查询要看到事务里的更新,事务外查询不能看到。
Redis不保证原子性
Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。
Redis事务的三个阶段
- 开始事务
- 命令入队
- 执行事务
Redis事务相关命令
watch key1 key2 ... : 监视一或多个key,如果在事务执行之前,
被监视的key被其他命令改动,则事务被打断 ( 类似乐观锁 )
multi : 标记一个事务块的开始( queued )
exec : 执行所有事务块的命令 ( 一旦执行exec后,之前加的监控锁都会被取消掉 )
discard : 取消事务,放弃事务块中的所有命令
unwatch : 取消watch对所有key的监控
原子性、一致性、隔离性和持久性
- 原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被恢复(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
- 一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
- 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执 行时由 于 交 叉 执 行而导致数据 的不一致 。 事务隔离分为不同级别,包括读未 提 交 ( Readuncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
- 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
主从复制
为什么要使用主从复制?
机器故障:部署一台Redis,当服务器发生故障时需要迁移到另外一台服务器而且要保证数据的同步。
容量瓶颈:当有需求要扩容redis内存时,16G->64G,单机一般无法满足。
解决办法
将数据复制多个副本到其它节点进行复制,实现Redis的高可用,实现对数据的冗余备份保证数据和服务的高可用
主从复制:指的是将一台redis服务器的数据,复制到其它的redis服务器,前者称为主节点(master),后者称为从节点(slave),数据的复制是单向的,只能由主节点到从节点。
主从复制作用
- 数据冗余:实现数据的热备份,吃持久化之外的一种数据冗余方式。
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;(实际是一种服务的冗余)
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务。(即写redis数据时应用连接主节点,读redis数据时应用连接从节点),分担服务器负载;
- 高可用:主从复制还可以是哨兵和集群能够实施的基础。
哨兵模式:反客为主的自动版,能够自动监控master是否发生故障,如果故障了会根据投票数从slave中挑选一个作为master,其他的slave会自动转向同步新的master,实现故障自动转义。
主从复制原理
主从复制分三个阶段六个过程
三个阶段
- 连接建设阶段
- 数据同步阶段
- 命令传播阶段
六个过程
- 保存主节点(master)信息
- 主从建立:从节点(slave)内部通过每秒运行的定时任务维护复制相关逻辑,当定时任务发现存在新的主节点后,会尝试与该节点建立网络连接。从节点会建立一个socket套接字,从节点建立了一个端口为51234的套接字,用于接收主节点发送的复制命令。
- 发送ping命令:连接建立成功后从节点发送ping请求进行首次通信
-
- 检查主从之间网络套接字是否可用
- 检测主节点当前是否可以接收命令
- 权限验证:如果主节点设置了 requirepass 参数,则需要密码验证,从节点必须配置 masterauth 参数保证与主节点相同的密码才能通过验证;如果验证失败复制将终止,从节点重新发起复制流程。
- 同步数据集:主从复制连接正常通信后,对于首次建立复制的场景,主节点会把持有的数据全部发送给从节点,这部分操作是耗时最长的步骤
-
- 主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。slave在任何时候都可以发起全量同步,redis策略是,不管什么情况,首先会尝试进行增量同步,如果失败,要求从机进行全量同步。
- 命令持续复制
-
- 当主节点把当前的数据同步给从节点后,便完成了复制的建立流程。接下来主节点会持续地把写命令发送给从节点,保证主从数据一致性。
哨兵监控
当主机Master宕机后,需要手动切换,重新选取主节点
主从切换
当主服务器宕机后,需要将一台从服务器切换为主服务器,需要人工干预,不仅麻烦,还会导致服务一段时间不可用。所有就有了哨兵模式。
哨兵模式
哨兵模式是一种特殊的模式,redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,可以独立运行。原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
哨兵作用
- 集群监控:负责监控redis master和slave进程是否正常工作
- 消息通知:如果某个redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员
- 故障转移:如果master node挂掉了,会自动转移到slave node上
- 配置中心:如果故障转移发生了,通知client客户端新的master地址
工作原理
监控阶段
- sentinel(哨兵1)----->向master(主)和slave(从)发起info,拿到全信息。
- sentinel(哨兵2)----->向master(主)发起info,就知道已经存在的sentinel(哨兵1)的信息,并且 连接slave(从)。
- sentinel(哨兵2)----->向sentinel(哨兵1)发起subscribe(订阅)。
通知阶段
sentinel不断的向master和slave发起通知,收集信息。
故障转移阶段
通知阶段sentinel发送的通知没得到master的回应,就会把master标记为SRI_S_DOWN,并且把master的状态发给各个sentinel,其他sentinel听到master挂了,说我不信,我也去看看,并把结果共享给各个sentinel,当有一半的sentinel都认为master挂了的时候,就会把master标记为SRI_0_DOWN。
投票方式
最先接到哪个sentinel的竞选通知就会把票投给它
故障转移
- 哨兵系统的主从节点,与普通的主从节点并没有什么区别,故障发现和转移是和哨兵来控制和完成的。
- 哨兵节点本质上是redis节点。
- 每个哨兵节点,只需要配置监控主节点,便可以自动发现其它的哨兵节点和从节点。
- 在哨兵节点启动和故障转移阶段,各个节点的配置文件会被从写。
哨兵模式缺点
- 当master挂掉时,sentinel会选举一个新的master,选举时无法访问Redis,会存在瞬断的情况。
- 哨兵模式,对外只有master节点可以写,slave节点只能用于读,尽管redis单节点最多支持10W的QPS,但是在节日大促时,写数据的压力都在master上。
- Redis单节点内存不能设置过大,如果数据过大在主从同步时会变得很慢,在节点启动时,时间会很长。
集群Cluster模式
redis集群有三种模式(主从模式、Sentinel模式、Cluster模式),Redis集群是一个由多个主从节点群组成的分布式服务集群,它具有复制,高可用和分片特性。
优点
- 有多个master,可以减小访问瞬断问题的影响
- 有多个master,可以提供更高的并发量
- 可以分片存储,可以存储更多的数据
Redis集群最少需要三个节点
原理
Redis Cluster将所有数据划分为16384个slots(槽位),每个节点负责其中一部分槽位。槽位的信息存于每个节点中,只有master节点会被分配槽位,slave节点不会分配槽位。
槽位定位算法:k1=127001
Cluster 默认会对key值使用crc16算法进行hash得到一个整数值,然后用这个整数值对16384进行取模来得到具体槽位
HASH_SLOT = CRC16(key)%16384
Redis脑裂
Redis的集群脑裂是指因为网络问题,导致Redis Master节点跟redis slave节点和Sentinel 集群处于不同的网络分区,此时因为sentinel集群无法感知到master的存在,所以将slave节点提升为master节点。
此时存在两个不同的master节点,就像一个大脑分裂成两个,集群脑裂问题中,如果客户端还基于原来的master节点继续写入数据,那么新的Master节点将无法同步这些数据,当网络问题解决之后,sentinel集群将原先的Master节点降为slave节点,此时再从新的master中同步数据。
//redis.conf
min-replicas-to-write 1
min-replicas-max-lag 5
//第一个参数表示最少的slave节点为1个
//第二个参数表示数据复制和同步的延迟不能超过5秒
//配置了这两个参数:如果发生脑裂原Master会在客户端写入操作的时候拒绝请求。这样可以避免大量数据丢失。
Redis缓存预热
新启动的系统中没有缓存数据,在缓存重建数据的过程中,系统性能和数据库负载都不太好,所以最好就是在系统上线之前就要把缓存的热点数据加载在缓存中,这种缓存预加载就是缓存预热。缓存预热解决数据库裸奔后的宕机问题
缓存冷启动
缓存中没有数据,由于缓存冷启动没有数据,如果直接对外提供服务,当并发量上来时,MySQL就会挂掉了
解决方法
- 提前给redis中灌入部分数据,再提供服务。
- 如果数据量非常大,不能讲所有数据都写入redis,因为数据量太大了会耗费大量时间,redis也容纳不下这么多数据
- 需要根据当天的具体访问情况,实时统计出访问频率较高的热数据,然后将这些热数据写入redis中
- 在热数据比较多的情况下可以用多个服务并行读取数据去写,并行的分布式的缓存预热
缓存穿透
缓存穿透指的是缓存和数据库中都没有的数据,而用户不断发起请求,如发起id为“-1”的数据或者id特别大的数据。这个时候的用户请求很可能是攻击者,攻击会导致数据库压力过大。
**操作过程:**用户查询的数据在数据库中没有,自然缓存也不会有,这样就导致用户查询的时候,在缓存中查询不到数据,每次都要去数据库中再查询一遍,然后返回null(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查询数据库(缓存命中率问题)。
解决方法
- 对空值缓存:如果请求查询返回的数据为空(不管数据是否存在)仍然把这个空结果缓存,设置这个空结果的过期时间较短,最长不超过5分钟。
- 布隆过滤器:如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有的元素保存起来,然后通过比较确定。
布隆过滤器
布隆过滤器是一种数据结构,比较巧妙的概率型数据结构(probability data structure),特点是高效的插入和查询。(布隆过滤器是一种数据库结构,底层是bit数组)
//引入hutool包
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.17</version>
</dependency>
//java代码实现
// 初始化 构造方法的参数大小10 决定了布隆过滤器BitMap的大小
BitMapBloomFilter filter = new BitMapBloomFilter(10); filter.add("123");
filter.add("abc");
filter.add("ddd");
boolean abc = filter.contains("abc");
System.out.println(abc);
缓存击穿
某一个热点key,在缓存过期的一瞬间,同时有大量的请求打进来,由于此时缓存过期了,所有请求最终会走到数据库,造成数据库瞬时请求量和压力骤增(缓存中没有的数据,数据库中有)。
解决方案
- 互斥锁:在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询操作,其他线程拿不到锁就阻塞等待,等第一个线程将数据写入缓存后,其他线程直接查询缓存。
- 热点数据不过期:直接将缓存设置为不过期,然后由定时任务去异步加载数据,更新缓存。
public String get(String key) throws InterruptedException {
String value = jedis.get(key);
// 缓存过期
if (value == null){
// 设置3分钟超时,防止删除操作失败的时候 下一次缓存不能load db
Long setnx = jedis.setnx(key + "mutex", "1");
jedis.pexpire(key + "mutex", 3 * 60);
// 代表设置成功
if (setnx == 1){
// 数据库查询
// value = db.get(key);
// 保存缓存 jedis.setex(key,3*60,"");
jedis.del(key + "mutex");
return value; }
else {
// 这个时候代表同时操作的其他线程已经load db并设置缓存了。 需要重新重新获取 缓存
Thread.sleep(50);
return get(key);
}
}else {
return value;
}
}
缓存雪崩
缓存雪崩是指我们设置缓存时采用了相同的过期时间,导致缓存在某一个时刻同时失效,请求全部转发到DB,DB瞬时压力过重导致雪崩
缓存正常从Redis中获取数据
缓存失效
解决方法
- 过期时间打散:给缓存过期时间加上一个随机时间,使得每个key的过期时间分布开来,不会集中在同一时刻失效。
- 热点数据不过期:该方式和缓存击穿一样,也是要着重考虑刷新的时间和数据库异常如何处理的情况
- 加互斥锁:该方式和缓存击穿一样,按key维度加锁,对于同一个key,只允许一个线程去计算其他线程原地阻塞等待第一个线程的计算结果,然后直接缓存即可。
public Object GetProductListNew(String cacheKey) {
int cacheTime = 30;
String lockKey = cacheKey;
// 获取key的缓存
String cacheValue = jedis.get(cacheKey);
// 缓存未失效返回缓存
if (cacheValue != null) {
return cacheValue;
} else {
// 加锁
synchronized(lockKey) {
// 获取key的value值
cacheValue = jedis.get(cacheKey);
if (cacheValue != null) {
return cacheValue;
} else {
//这里一般是sql查询数据db.set(key)
// 添加缓存
jedis.set(cacheKey,"");
}
}
return cacheValue;
}
}
//加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量。