Redis
什么是NoSQL
先来看看百度百科对NoSQL的描述。
NoSQL,泛指非关系型的数据库。随着互联网web2.0网站的兴起,传统的关系数据库在处理web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,出现了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,特别是大数据应用难题。
NoSQL维基百科称其为Not Only SQL
,在当今的大数据年代,仅仅是关系型数据库已经不能满足当前一些流量较大的Web应用,所以NoSQL就应运而生,就是用来解决数据量较大的问题。
NoSQL可以分成四个大类。
键值存储数据库
列存储数据库
文档型数据库
图形数据库
这里我就用百度百科上的表格,来简单了解一下这四个分类。
分类 | Examples举例 | 典型应用场景 | 数据模型 | 优点 | 缺点 |
---|---|---|---|---|---|
键值存储数据库 | Tokyo Cabinet/Tyrant, Redis, Voldemort, Oracle BDB | 内容缓存,主要用于处理大量数据的高访问负载,也用于一些日志系统等等。 | Key 指向 Value 的键值对,通常用hash table来实现 | 查找速度快 | 数据无结构化,通常只被当作字符串或者二进制数据 |
列存储数据库 | Cassandra, HBase, Riak | 分布式的文件系统 | 以列簇式存储,将同一列数据存在一起 | 查找速度快,可扩展性强,更容易进行分布式扩展 | 功能相对局限 |
文档型数据库 | CouchDB, MongoDb | Web应用(与Key-Value类似,Value是结构化的,不同的是数据库能够了解Value的内容) | Key-Value对应的键值对,Value为结构化数据 | 数据结构要求不严格,表结构可变,不需要像关系型数据库一样需要预先定义表结构 | 查询性能不高,而且缺乏统一的查询语法。 |
图形(Graph)数据库 | Neo4J, InfoGrid, Infinite Graph | 社交网络,推荐系统等。专注于构建关系图谱 | 图结构 | 利用图结构相关算法。比如最短路径寻址,N度关系查找等 | 很多时候需要对整个图做计算才能得出需要的信息,而且这种结构不太好做分布式的集群方案。 |
这次学习的就是键值存储数据库中的Redis。
什么是Redis
Redis是Remote Dictionary Server
的缩写,翻译为远程字典调用,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
安装
windows
windows的只需要下载他们提供的压缩包使用就好了。
打开服务后就能看到服务的信息,默认端口为6379
接下来来确认一下服务是否真的开启了,打开客户端,使用ping
命令来测试,出现PONG
表示已经连接到了Redis的服务。
windows的版本活跃度很低,而且官网也不推荐使用windows,所以还是尽量使用Linux,之后的使用我也是使用Linux来测试。
Linux
去到redis的github就能下载到压缩包使用。
因为Redis是使用C/C++写的,所以要想使用,还是需要先安装一下C/C++的环境。
1 | # 安装环境 |
要是make命令时出现了很多的error,大概率就是因为redis6版本是需要gcc版本是5以上的才行,所以需要先升级gcc的版本,这里是升级到了9。
1 | yum -y install centos-release-scl |
Redis默认会安装到/usr/local/bin
的目录里。
Redis不是默认启动的,为了方便学习,要设置成默认启动的,所以去修改redis.conf
这个文件。
1 | # 将这个选项设置为yes,默认为no |
启动Redis的服务。
1 | # 以哪个配置文件来启动redis的服务 |
关闭Redis的服务
1 | # 在客户端连接6379端口中 |
性能测试
Redis的目录下还会有一个命令redis-benchmark
。
这个命令是官方自带的性能测试的命令,用来测试Redis的一些基础命令读写的速度,以下是命令的参数。
参数选项 | 描述 | 默认值 |
---|---|---|
-h | 指定服务器主机名 | 127.0.0.1 |
-p | 指定服务器端口 | 6379 |
-s | 指定服务器 socket | |
-c | 指定并发连接数 | 50 |
-n | 指定请求数 | 10000 |
-d | 以字节的形式指定 SET/GET 值的数据大小 | 2 |
-k | 1=keep alive 0=reconnect | 1 |
-r | SET/GET/INCR 使用随机 key, SADD 使用随机值 | |
-P | 通过管道传输 |
1 |
-q | 强制退出 redis。仅显示 query/sec 值 | |
—csv | 以 CSV 格式输出 | |
-l | 生成循环,永久执行测试 | |
-t | 仅运行以逗号分隔的测试命令列表 | |
-I | Idle 模式。仅打开 N 个 idle 连接并等待。 |
数据类型
Redis支持的数据类型有很多,官网也写的很清楚了。
Redis provides data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes, and streams.
String(字符串)
String一般使用是用来计数,比如用户的访问次数,热点文章的点击数,转发数等。
1 | # 值为一个字符串 |
List(列表)
List一般用来发布与订阅或者说消息队列、慢查询等场景。
1 | # 值为类似一个双向的链表 |
Set(集合)
Set的使用场景一般为,需要存放的数据不能重复以及需要获取多个数据源交集和并集等场景
1 | # set不能重复添加相同的元素,类似于Java的HashSet,是一种无序集合 |
Hash(哈希)
Hash一般是用来进行对象的存储
1 | # hash的结构类似于Java8时候的HashMap |
Zset(有序的集合)
Zset一般使用在那些需要排序的场景。比如礼物排行榜,弹幕消息等。
1 | # 在Set的基础上增加了权重参数,从而可以通过权重来进行排序 |
geospatial(地理位置)
1 | # 用来表示地理位置,经纬度,实现底层就是Zset,所以一些命令也适用 |
Hyperloglog(基数统计)
1 | # 基数,表示不重复元素的个数,存在误差 |
Bitmaps(位图)
一般用来用户签到,统计活跃的用户和用户在线情况。
1 | # 使用二进制来表示状态,用于那些只有两个状态 |
总结
对Redis的数据类型进行一个小的总结。
Redis的数据类型其实大概分为两类。
五大数据类型
- String
- Set
- Hash
- List
- Zset
三个特殊的数据类型
- geospatial
- hyperloglog
- bitmaps
事务
- Redis的事务其实就是一组命令的队列
- Redis事务没有隔离级别的概念
- 一次性,顺序性,排他性
- Redis的单条命令存在原子性,但是事务是多条命令的集合,所以事务不具备原子性
Redis的事务分为三个阶段
- 开启事务
- 命令入队
- 执行事务
在开启事务后,输入的命令会进入队列,但是没有直接执行,要等所有命令入队后,发出执行命令才会一次性,依次执行
1 | # 开启事务 |
- 若是事务中存在错误的命令,那么整个事务的命令都不会被执行
- 若是事务中只是命令语法有问题,那么该命令会抛出异常,但是其他命令会正常执行
监视
首先了解悲观锁和乐观锁这两个概念。
悲观锁
悲观锁,顾名思义,什么时候都很悲观,每次访问公共数据,都会觉得别人会进行修改,所以每次获取该数据的时候,都会对数据加锁
乐观锁
乐观锁,顾名思义,什么时候都很乐观,每次访问公共数据,都觉得别人不会进行修改,所以每次都不会去上锁,只有在更新该数据后会判断一下使用期间其他线程有没有修改该数据。可以使用版本号来实现。每次更新数据后,判断该数据的版本号是否发生变更,发生了变更,就会导致修改失效,若是没有就会修改成功并修改版本号。
1 | # Redis的监控就是使用了乐观锁的操作 |
持久化
存储在内存中的数据,一旦机器出现问题,那么内存中的数据就会丢失,所以为了尽量避免这个问题,Redis提供了两种持久化的方法,接下来来了解一下这两种持久化的方式。
RDB
RDB持久化机制是将某个时刻的数据快照写入磁盘,也就是将某个时刻的数据保存下来,等到Redis服务启动,就会自动加载这个快照文件进行数据恢复。
手动触发
save命令(不建议使用)
save是一个同步的命令,也就是说执行save命令,会让Redis的服务器发生阻塞,直到RDB持久化完成,其他命令才能正常进行。
bgsave命令
bgsave是一个异步的命令,执行bgsave命令,Redis会
fork
一个子进程来进行RDB持久化,只有fork时才会阻塞,其他时间Redis正常运行。
自动触发
满足配置文件中save配置的文件,默认配置如下,可以自己配置
1
2
3
4
5
6# 当900秒内至少有1个键值对发生变动,触发持久化
save 900 1
# 当300秒内至少有10个键值对发生变动,触发持久化
save 300 10
# 当60秒内至少有10000个键值对发生变动,触发持久化
save 60 10000执行
flushall
命令清空数据库时,触发持久化执行
shutdown
命令等手段退出Redis时,触发持久化
RDB文件
从配置文件可以看到配置文件的默认路径和默认名。
1 | # rdb文件的默认文件名 |
AOF
AOF持久化全称Append Only File
,当我们执行的改变数据的操作时就会将该命令追加到一个AOF文件的末尾,当Redis服务重新启动的时候,就会重新执行AOF文件内的命令,用来同步数据。
AOF不是默认的持久化方式,故默认关闭的,需要去配置文件手动开启。
AOF触发策略
1 | # appendfsync always |
always
每次有新的修改数据的命令,就会将缓冲区内的命令同步追加到AOF文件,十分安全,但是效率低
everysec
默认的策略,每秒将缓冲区内的命令同步追加到AOF文件,但是无法做到实时持久化,还是会可能丢失一秒的数据
no
交给操作系统来决定什么时候去同步追加数据
重写机制
当命令不断被追加到AOF文件内,文件会越来越大,这对使用来说很不好,所以Redis提供了一个AOF的重写机制来解决这个问题,将AOF文件内的命令优化,重写为可以恢复到当前数据的最小指令集,从而减少文件的大小,达到压缩AOF文件的目的。
触发流程如下。
手动触发
手动输入bgrewriteaof
命令触发重写机制。
自动触发
自动触发就需要自行修改配置文件内AOF重写的配置。
1 | # 重写触发条件 |
持久化流程
过期Key
为什么要设置过期Key
之前有提到过设置过期时间,那么就会有一个问题,为什么我们要多此一举去设置一个过期时间呢?
简而言之那就是,内存有限。
如果所有的Key都存在内存,数据量一大就会内存溢出了。
还有一个原因是用户希望存一个有过期时间的Key,就比如在一些场景下,过期Key就会显示很方便。Token的状态登陆或者是验证码的使用,都可以在Redis存储一个过期时间从而达到过期了就无法使用的目的。
不适用过期时间又想要实现这个功能,那就只能查询数据库获得记录,然后在判断是否过期,那也会有点麻烦。
如何判断Key过期
Redis 通过一个叫做过期字典(可以看作是hash表)来保存数据过期的时间。过期字典的键指向Redis数据库中的某个key(键),过期字典的值是一个long long
类型的整数,这个整数保存了key所指向的数据库键的过期时间(毫秒精度的UNIX时间戳)。
过期Key的删除策略
定期删除
每隔一段时间就会抽取一批Key来进行过期检查,过期了就进行删除。
惰性删除
每次从数据库获取Key时会判断该Key是否过期,要是过期了就会进行删除,没有过期就会返回数据给用户。
定时删除
在设置key的过期时间的同时,为该key创建一个定时器,让定时器在key的过期时间来临时,对key进行删除。
Redis默认使用的就是定期删除和惰性删除的配合。
Redis的内存淘汰策略
刚才有提到过过期Key,从Redis的过期Key删除策略中可以看出这些删除策略并不能将每一个过期的Key都删掉,所以在数据量大的时候又加上过期的Key,就会出现内存满了的情况,那这种时候就需要淘汰掉一些内存数据了。
Redis提供有8种淘汰的策略。
- volatile-lru:淘汰掉设置过期时间的数据集里最近最少使用的数据
- volatile-ttl:淘汰掉设置过期时间的数据集里的将要过期的数据
- volatile-random:随机选择设置过期时间的数据集里的数据来进行淘汰
- volatile-lfu:淘汰掉设置过期时间的数据集里最不经常使用的数据
- allkeys-lru:移除空间里最近最少使用的 key(这个是最常用的)
- allkeys-random:从空间中任意选择数据来进行淘汰
- allkeys-lfu:移除空间里最不经常使用的数据
- no-eviction:不允许淘汰数据,当内存满了之后在进行新数据的写入就会报错
发布订阅
发布订阅是一种消息通信模式。
发送者发送消息,订阅者接受发送者的消息。
接下来进行一个简单的测试。
订阅一个频道,这里订阅
yww
这个频道1
2
3
4
5127.0.0.1:6379> subscribe yww
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "yww"
3) (integer) 1在打开一个Redis的客户端,然后往
yww
频道发送两条消息1
2
3
4
5127.0.0.1:6379> publish yww "Hello Redis"
(integer) 1
127.0.0.1:6379> publish yww "Hello World"
(integer) 1
127.0.0.1:6379>在订阅频道的订阅者的客户端就能接收到频道的消息了
1
2
3
4
5
6
7
8
9
10
11127.0.0.1:6379> subscribe yww
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "yww"
3) (integer) 1
1) "message"
2) "yww"
3) "Hello Redis"
1) "message"
2) "yww"
3) "Hello World"
以下是一些常用的API
1 | # 订阅一个或多个符和给定模式的频道 |
主从复制,读写分离
当数据量过大的时候,服务器的压力就会提高,为了解决这个问题,Redis提供了主从复制,读写分离
的方案,因为大部分的压力是读操作,所以可以搭建一个Redis集群(最低要求三台,一主二从),主节点负责写操作,从节点用来提供读的服务,这样就能减少服务器的压力了。
当然还可以有以下这种情况
节点是既可以当主节点,又可以当子节点的。
Redis的主从复制表示只有主节点能进行写操作,从节点是不能进行写操作的,只能进行读操作,主节点的数据会同步到子节点,达到整个集群的数据一致。
同步机制
上边说到的同步方式根据是否是全量来分为全量同步
和增量同步
。
全量同步
全量同步的数据复制一般只会发生在
slave从节点
连接主节点的时候,这时从节点会将主节点中的所有数据都复制到从节点中,从而从节点连接主节点后数据通同步。期间发生的具体步骤如下。
- 从节点连接主节点后,向主节点发出
SYNC
命令 - 主节点收到
SYNC
命令后,开始执行BGSAVE
生成RDB文件,因为是异步的操作,所以主节点继续处理命令,并将被执行的命令放入缓冲区 - 生成RDB文件后,就会向从节点发送快照文件
- 从节点收到主节点发送的RDB文件后,就会放弃原来存在的旧数据,然后载入RDB文件同步数据
- 触发增量同步
这样主节点的数据旧全量同步复制到了从节点上。
- 从节点连接主节点后,向主节点发出
增量同步
除了首次从节点的全量同步,一般数据的同步都是使用增量同步的方式
- 主节点将缓冲区中的命令发送给从节点
- 从节点接收到命令请求,就会执行这些命令,完成部分写命令带来的数据同步
模拟Redis集群
这里使用同一个服务器,不同端口搭建的Redis集群进行学习。
当然你有多台服务器可以使用不同服务器来搭建集群来学习。
再不然可以直接开三个容器来搭建Redis集群来学习。
首先是了解一个基本的命令。
1 | # 查看当前服务的信息 |
创建三个配置文件,用来开启三个服务。
1 | redis6379.conf redis6380.conf redis6381.conf |
然后配置这三个文件。
1 | # redis6379.conf 主节点的配置 |
然后是建立主从联系,这里使用6379的服务当成主节点,建立联系有两种方法。
在从节点中使用命令连接主机,这是简单的连接,要是从节点服务关闭了,之后重启旧连接不到主节点了,可以说是一次性的
1
SLAVEOF [host] [port]
使用配置设置,这种情况,从节点服务重启后就会继续连接主机
1
2
3
4
5
6
7################################# REPLICATION #################################
# 在这下面配置主节点服务地址就好了,样例已经给出
# replicaof <masterip> <masterport>
# 要是主节点服务有密码可以在这里配置
# masterauth <master-password>
这里连接6379当成主节点。
1 | # 配置6380从节点 |
然后在主机设置的数据,可以在从机中获取数据。
哨兵模式(Sentinel)
通过配置连接的子节点出现故障,重启服务后,因为配置连接的缘故那还是子节点,所以子节点出现故障的情况很简单。
要是这个子节点有子节点,那么主机故障后,子节点的role依旧是salve,所以还是不能进行写操作。
接下来重点了解一下主节点出现故障的情况。
当主节点出现故障后,它的子节点身份是不会变的,当主节点的服务重新启动后,集群依旧存在。
当我们不清楚主节点何时恢复,总不能一直不进行写操作,所以就需要重新推出一个主节点。
1 | # 令从节点恢复master |
这是手动配置主节点,这其实还是挺麻烦的,所以为了解决这个问题,Redis从2.8的版本后就提供了哨兵模式
这个方案。
哨兵模式其实就是自动推选主节点的一种方案。
先来简单了解一下哨兵模式。
哨兵是一个独立的进程,在Redis的命令中也能看到它redis-sentinel
。
该进程通过向各个节点发送命令,通过节点返回的信息来监控节点的使用情况。
当主节点宕机了,哨兵向主节点发送命令却得不到返回信息,过段时间哨兵确认主节点宕机后,就会随机给从节点随机投票,获得投票的节点就会当选为主节点,然后通过发布订阅模式通知其他的从节点修改配置,让它们切换主节点对象,从而实现集群正常服务。
那要是哨兵宕机了怎么办呢?这样的设置还是会出现问题。所以哨兵不能只有一台,哨兵也要形成一个集群(最好三个起步),于是最终的解决方案就如下图。
多个哨兵进行监控,当有一个哨兵检测到主节点宕机,并不会马上切换,因为有可能是哨兵的问题,所以哨兵只会主观认为主节点宕机(这种情况叫主观下线)。
当一定个数(在哨兵的配置文件可以配置)的哨兵都认为主节点宕机后,那大概率就不是哨兵的问题了,那么哨兵之前就会对剩余的从节点投票,投票结束后,随机一个哨兵进行failover
操作(这种情况叫客观下线)。
failover
又称故障转移,它会从从节点中挑选一个作为Redis集群中的新的主节点。
- 选择票数高的从节点当为主节点,若是不存在(同票的情况),就继续判断。
- 选择主从复制,同步数据最完整的节点成为主节点,若是不存在就继续判断。
- 选择启动最早的子节点当为新的主节点。
对选出来的主节点执行slaveof no one
将身份转换成主节点,然后向其他的从节点发送订阅模式通知,各个哨兵就会让他们的节点切换主节点的对象为新的主节点。最后更新之前宕掉的节点的身份为从节点,当宕机恢复后,就自动成为该集群的从节点。
哨兵的启动需要先配置哨兵启动的配置文件sentinel.conf
。
1 | # Example sentinel.conf |
1 | # 开启哨兵线程 |
redis.conf
1 | ################################## INCLUDES ################################### |
一些参考链接
- https://zhuanlan.zhihu.com/p/105587132
- https://www.bilibili.com/video/BV1S54y1R7SB?p=1
- https://baijiahao.baidu.com/s?id=1654694618189745916&wfr=spider&for=pc
- https://blog.csdn.net/weixin_39040059/article/details/79120444
- https://www.runoob.com/redis/redis-tutorial.html
- https://www.cnblogs.com/daofaziran/p/10978628.html
- https://www.jianshu.com/p/06ab9daf921d