NoSQL概述
什么是NoSQL
NoSQL,泛指非关系型的数据库。随着互联网web2.0网站的兴起,传统的关系数据库在处理web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网动态网站已经显得力不从心,出现了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,特别是大数据应用难题
NoSQL的特点
- 方便扩展(数据之间没有关系,方便扩展)
- 大数据量高性能(Redis一秒写8W次,读取11W次,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高)
- 数据类型是多样性的且不需要事先设计数据库,随取随用
- 传统RDBMS (关系型数据库)和NoSQL的区别
传统RDBMS
- 结构化组织
- SQL
- 数据和关系都存在单独的表中
- 操作,数据定义语言
- 严格的一致性
- 基础的事务ACID
NoSQL
- 不仅仅是数据库
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库(社交关系)
- 最终一致性
- CAP定理和BASE (异地多活)
- 高性能,高可用,高可扩
了解:3V+3高
大数据时代的3V:主要是描述问题
- 海量
- 多样
- 实时
大数据时代的3高:对程序的要求
- 高并发
- 高可扩(支持水平拆分)
- 高性能
企业实践:NoSQL+RDBMS
NoSQL的四大分类
KV键值对:
- 新浪:Redis
- 美团:Redis + Tair
- 阿里、百度:Redis + memcacahe
文档型数据库:
- MongoDB
- MongoDB是一个基于分布式文件存储的数据库,由C++编写,主要用于处理大量的文档
- MongoDB是一个介于关系型数据库和非关系型数据库的中间产品,是非关系型数据库中功能最丰富,最像关系型数据库的。
- CouchDB
列存储数据库:
- HBase
- 分布式文件系统
图关系型数据库(不是用来存图的):
- Neo4j
- InfoGrid
区别:
Redis
什么是redis
Redis,即远程字典服务
开源、使用ANSI C语言编写、支持网络、可基于内存,亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。被人们称之为结构化的数据库。
redis能做什么
- 内存存储,持久化(rdb、aof)
- 效率高,可以用于高速缓存
- 发布订阅系统
- 地图信息分析
- 计时器、计数器(网站访问浏览量)
- 等等。。。。。。
特性
- 多样化的数据类型
- 持久化
- 集群
- 事务
- 等等。。。。。。。。。。
注意
Windows系统直接在GitHub上下载,但是不建议使用,停更时间较长,
Redis推荐在Linux服务器上搭建环境
Redis安装
Windows环境下操作手册
- 下载安装包
windows版本的安装包官方没有,但是微软有
https://github.com/microsoftarchive/redis/releases
- 解压
- 开启redis,双击运行redis-server.exe 默认端口号为6379
- 使用redis客户端来连接redis
Linux环境下操作手册
- 下载安装包
- 解压
- 配置文件为redis.config
- 基本的环境安装
#进入文件夹
yum install gcc-c++
make
make install
redis默认的安装路径
usr/local/bin
-
将配置文件复制到
usr/local/bin/[自己建立]
的目录下 -
启动Reids
redis默认不是后台启动的,如果要后台启动,需要修改配置文件
#后台启动设置 vim redis.conf #将daemoize 属性改为yes #启动redis服务 #进入`usr/local/bin`目录下 redis-server [自建的目录]/redis.config
-
使用客户端连接redis服务
redis-cli -p 6379
查看redis的相关进程信息
ps -ef|grep redis
-
关闭redis服务
#使用redis-cli连接redis服务 redis-cli -p 6379 #执行shutdown命令关闭redis服务 shutdown #执行exit退出客户端 exit #再次查看进程信息是否存在 ps -ef|grep redis
Redis的性能测试
Redis-bencmaker是一个压力测试工具
- redis-benchmark命令参数(参考菜鸟教程):
Redis基础知识与操作
redis有16个database,在redis.conf文件中可以找到database这一数据
默认使用第0个数据库
- 可以使用
select
命令来切换数据库
# 进入redis客户端
# 切库
select [0≤num≤15]
#查看数据库大小
dbsize
# 查看数据库所有的Key
Key *
# 清空当前数据库的数据
flushdb
# 清空所有数据库的数据
flushall
Redis是单线程的==,官方表示,Redis是基于内存操作,CPU不是Redis的性能瓶颈,Redis的性能瓶颈来源于==机器的内存和网络的带宽
-
Redis-Key的操作
# 存储数据 set key value # 取出数据 get key # 查看所有的key key * # 判断库中是否含有key的数据,有返回1,没有返回0 Exists key # 移除key 1:表示本库的key move key 1 # 设置过期时间,可以通过ttl key来查看此key还剩多少时间 Expire key [time(second)] # 查看value的数据类型 type key
Redis-五大数据类型
只列了常用,全部命令查阅官方文档
String类型
# 添加一个String类型的数据
set key1 v1
# 追加字符串,返回追加后的字符串长度
Append key1 "hello"
#统计字符串的长度
Strlen key1
# 截取字符串
getrange key [起始位置] [结束位置(置为-1时代表所有字符串)]
#替换字符串
setrange [key] [起始位置] [替换的值]
# setex (set with exire) #设置过期时间
set [key] [time(second)] [value]
# setnx (set if not exist) #若key存在,则不改变值,分布式锁中常常使用
setnx [key] [value]
setnx [key] [value2] #不会改变value的值
# 批量设置值 mset msetnx(原子性操作,只要有存在的key就会失败)
mset [key] [value] [key2] [value2] [key3] [value3] ......
# 批量获取值 mget
mget [key] [key2] [key3] ......
# 添加对象
# 法一:
set user:1 {name:zhangsan,age:3} #设置一个user:1 对象的值为一个JOSN字符串
#法二:
mset user:1:name zhangsan user:1:age 3
# 先GET,再set数据 如果不存在值则返回null并set新值,存在则返回旧值再set新值
getset key value
# 自增1
incr key [1(默认步长,可不写)]
# 自减1
decr key [1(默认步长,可不写)]
List类型
在Redis中,List可以做成栈、队列。
# list从左侧增加数据
lpush list [数据]
# list从右侧增加数据
rpush list [数据]
# list从左侧取出数据
lrange list [起始下标(从0开始)] [结束下标(-1表示所有)]
# list从左边移除数据
lpop list
# list从右边移除数据
rpop list
# 获取某一个下标的key
lindex list [index]
# 获取list的长度
llen list
# 移除指定的值(精确匹配)
lrem list [count(需要移除的数量)] [value]
# list截断
ltrim [key] [起始下标] [结束下标]
# 移除list的最后一个元素,并添加到一个新的list中
rpoplpush [key1] [key2]
# 判断当前list是否存在值
Exists [key]
# 往指定下标存值(前提必须有此key)[指定下标位置有值会更新此值]
lset [key] [index] [value]
# 往某一个key的某一个值的前面或者后面插入值
linsert [key] [before/after] ["key中的vlaue"] ["要插入的value"]
小结
- list实际上是一个链表,
- 如果key 不存在,创建新列表
- 如果key存在,新增内容
- 如果移除了所有值,空链表,也代表不存在
- 在两边插入或者改动值的时候,效率最高
Set类型
set中的值不能重复,存储无序
# set的存值
sadd [key] [value]
# set的取值
smembers [key]
# set中是否存在某值
sismember [key] [value]
# 统计set中元素的个数
scard [key]
# 移除set中的值
srem [key] [value]
# 在set集合中随机抽取一个值(抽奖)
srandmember [key] [count](抽几个元素)
# 随机移除set中的某个元素
spop [key]
# 将一个指定的值移到另一个set中
smove [source key] [destination key] [value]
# 查看set中与其他set不同的值(差集)
sdiff [key] [key1] [key2] [......]
# 查看set中与其他集合相同的值(交集)
sinter [key] [key1] [key2] [......]
# 查看set与其它set所有的值(并集)
sunion [key] [key1] [key2] [......]
Hash类型
当作Map 集合
# hash的存值
# 返回hash的值的数量
hset [key] [field] [value] [field] [value] ......
# 存值成功返回OK
hmset [key] [field] [value] [field] [value] ......
# hash的取值
# 获得单个值
hget [key] [field]
#获得多个值
hmget [key] [field] [field1] [field2] ......
#获得全部值
hgetall [key]
# 删除hash中的某个值(通过删除其field来实现)
hdel [key] [field] [field2] [......]
# 查看hash中有多少值
hlen [key]
# 判断hash中是否存在某值(通过查找其field来实现),存在返回1
hexists [key] [field]
# 只获得hash中所有的field
hkeys [key]
# 只获得hash中所有的value
hvals [key]
# 值自增
hincrby [key] [field] [increment(自增步长)]
# 值自减
hincrby [key] [field] [increment(自增步长为负数即可)]
# hsetnx 同setnx
hsetnx [key] [field] [value]
ZSet(有序集合)
在set基础上,增加了一个值
set k1 v1 ===> zset k1 score1 v1
# zset中添加值
zadd [key] [score(数值,按此排序)] [member] [score] [member] .....
# zset中取全部值
zrange [key] 0 -1
# 排序的实现
zrangebyscore [key] [min(-inf:负无穷)] [max(+inf:正无穷)]
[withscores(带score显示)]
# 移除一个元素
zrem [key] [member]
# 查看zset中的值的数量
zcard [key]
# 统计zset中score在某范围内的值的数量
zcount [key] [min(包含)] [max(包含)]
# 使用案列
存储班级成绩表 工资表等
排行榜实现
Redis -三种特殊数据类型
geospatial 地理位置
应用:定位、附近的人、打车距离计算等
# geo只有六个命令
* geoadd #添加地理位置
* geodist #两个地理位置之间的距离(直线距离)
* geohash #返回一个或多个位置元素的 Geohash 表示(将经纬信息变成11位的字符串)
* geopos #获得经纬度信息
* georadius #以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
* georadiusbymember #和georadius类似,只是此方法的中心点由给定的位置元素决定,而不是以给定的经纬度决定
geoadd
# 参数 :key 经度 纬度 member
# 有效的经度介于 -180 度至 180 度之间
# 有效的纬度介于 -85.05112878 度至 85.05112878 度之间
geoadd china:city 116.40 39.90 beijing
geopos
# 参数 :key member
geopos china:city beijing
geodist
# 参数 : key member1 member2 [单位]
#单位
m 表示单位为米。
km 表示单位为千米。
mi 表示单位为英里。
ft 表示单位为英尺。
geodist china beijing hefei km
georadius
# 参数 key 经度 纬度 半径范围 单位 [withdist(带距离返回)] [withcoord(带经纬度信息返回)] [count number(只查number个)]
#单位
m 表示单位为米。
km 表示单位为千米。
mi 表示单位为英里。
ft 表示单位为英尺。
georadius china:city 115 35 1000 KM withdist withcoord count 1
georadiusbymember
# 参数 :key member 半径范围 单位
georadius china:city beijing 1000 km
geohash
# 参数 : key member [member] [......]
geohash china:city beijing hefei
Geo 底层实现原理是ZSet有序集合,我们可以使用ZSet命令来操作Geo
# 官方给的geo只有6个命令,不包含删除某个位置信息,如果我们要删除某个位置信息(模仿关闭附近的人场景),可以使用ZSet命令操作
zrem china:city member
Hyperloglog基数统计
用来统计集合不重复的元素
注意:Hyperloglog有大约0.81%的容错,若业务需求不允许容错,则不能使用
应用场景:统计网页的UV,即访问量,每个用户无论访问多少次都只算一次。
传统方式:用set来去重用户id保存访问量,再统计总数。缺点:内存占用大
# 添加集合元素
PFadd key [element .......]
# 统计集合不重复元素的数量
PFcount key
#统计集合间不重复的元素的数量
PFcount key [key2 ...]
# 合并两个集合元素 sourcekey ------> destkey (destkey可以是一个未创建的集合,用以创建新的合并后的集合)
PFmerge destkey sourcekey [sourcekey ...]
BitMaps
位存储
需求场 景:数据只有两个状态时可以使用bitmaps存储,如打卡状态,登录状态,用户活跃状态等
# 位存储
setbit key offset value
# 例子:一年365天的签到信息,0代表未签到,1代表签到
setbit sign 0 1
setbit sign 1 0
setbit sign 2 1
setbit sign 3 0
......
# 查看某一位的位信息
getbit key offset
# 统计"1"位的信息
bitcount key
Redis -事务
Redis单条命令是保证原子性的,要么同时成功,要么同时失败。但是Redis的事务不保证原子性
Redis事务的本质:一组命令的集合,一个事务中所有的命令都会被序列化,在事务执行的过程中,会按照顺序执行
事务的特性:一次性,顺序性,排他性。Redis事务没有隔离级别的概念,所有的命令在事务中斌没有直接执行,而是等到发起执行命令时才会一次性按序逐个执行。
事务是按组的,一组事务执行结束该事务即结束,如再想使用事务,则需要再开启事务—>命令—>执行
------> 队列 set set set ... 执行 ------>
Redis的事务:
-
开启事务
Mulit
-
命令入队
需要执行的命令
-
放弃事务
discard
-
执行事务
exec
事务的演示
127.0.0.1:6379> multi # 事务开启
OK
127.0.0.1:6379(TX)> set k1 v1 # 命令1
QUEUED # 入队
127.0.0.1:6379(TX)> set k2 v2 # 命令2
QUEUED # 入队
127.0.0.1:6379(TX)> get k1 ...
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> exec # 执行事务
1) OK # 返回每条命令的执行状态
2) OK ...
3) "v1"
4) "v2"
5) OK
事务的放弃
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k1
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> discard # 放弃事务
OK
127.0.0.1:6379> get k1
(nil)
127.0.0.1:6379> get k2
(nil)
事务的错误命令处理
-
编译型异常(代码有问题,命令有错)
事务中所有的命令都不会执行
-
运行时异常(逻辑错误)
有错误的命令不执行,其他命令照常执行,错误命令抛出异常
Redis - 悲观锁&乐观锁
使用Redis的监控机制完成
悲观锁
悲观锁的概念:对代码逻辑很悲观,认为在多线程并发的情况下什么时候都会出问题,无论什么地方都加锁,十分影响性能
乐观锁
乐观锁的概念:对代码逻辑很乐观,认为在多线程并发的情况下什么时候都不会出问题,所以不会上锁,更新数据的时候取一个字段(如Version)判断一下,在此期间是否有人修改过这个数据。
MySQL中实现乐观锁的步骤
- 获取version
- 更新的时候比较version
Redis实现乐观锁(Watch机制)
正常执行流程
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set spend 0
OK
127.0.0.1:6379> watch money # 监视money对象
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 20
QUEUED
127.0.0.1:6379(TX)> incrby spend 20
QUEUED
127.0.0.1:6379(TX)> exec # money的值不变,事务正常执行
1) (integer) 80
2) (integer) 20
因为锁导致事务执行失败流程
- client-1
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set spend 0
OK
127.0.0.1:6379> watch money # 监视money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 20
QUEUED
127.0.0.1:6379(TX)> incrby spend 20
QUEUED
127.0.0.1:6379(TX)> exec # client-2中因为在执行前修改了money的值,事务执行失败
(nil)
- client-2(client-2中的命令必须在client-1中的事务执行前执行)
127.0.0.1:6379> incrby money 100 # 将money增加100
(integer) 200
解决因为锁导致事务执行失败
# 先解锁
unwatch
# 再加锁
watch money
#再执行事务
...
Jedis操作Redis
代码地址:git@github.com:yu2676555716/RedisStudy.git
什么是Jedis
-
Jedis是Redis官方推荐的Java连接开发开发工具
-
是Java操作Redis 的一个中间件
测试Jedis操作Redis
- 详见IDEA工程[redis-jedis]
SpringBoot整合Redis
测试SpringBoot整合Redis
代码地址:git@github.com:yu2676555716/RedisStudy.git
- 详见IDEA工程[redis-springboot]
在SpringBoot2.x之后,原来使用的jedis被替换为了lettuce
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
<!-- 此依赖被包含在spring-boot-starter-data-redis中-->
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>6.1.5.RELEASE</version>
<scope>compile</scope>
</dependency>
区别:
-
jedis:
- 采用直连,多个线程操作的话,是不安全的,想要避免安全性问题则需要jedis pool连接池,像BIO模式。
-
lettuce:
- 采用netty(高性能的通信框架),实例可以在多个线程中共享,不存在不安全的情况,可以减少线程数量,像NIO模式。
源码分析
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
//RedisProperties.class 中定义了一系列的可配置选项
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
//如果存在自己的redisTemplate,则此redisTemplate失效
@ConditionalOnMissingBean(name = "redisTemplate")
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
//默认的redisTemplate没有过多的设置,因为redis对象都是需要序列化的
//两个泛型都是Object类型,不适用,之后使用需要强制转换为<String,Object>,也可以自定义 redisTemplate
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
// 由于String类型在redis中常用,所以此处多添加了一个stringRedisTemplate
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
return new StringRedisTemplate(redisConnectionFactory);
}
}
序列化
用redisTemplate存数据时,若直接存未序列化的实体类,会报SerializationFailedException和IllegalArgumentException异常
在RedisTemplate的源码中,定义了有关序列化的配置
...
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer keySerializer = null;
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer valueSerializer = null;
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer hashKeySerializer = null;
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer hashValueSerializer = null;
private RedisSerializer<String> stringSerializer = RedisSerializer.string();
...
if (defaultSerializer == null) {
//默认使用的是JDK的序列化方式
//默认使用JDK序列化会转义字符,可能导致乱码,所以有时候需要我们自己定义JSON的序列化方式
defaultSerializer = new JdkSerializationRedisSerializer(
classLoader != null ? classLoader : this.getClass().getClassLoader());
}
...
自己定义redisTemplate来实现自定义配置
- 详见IDEA工程[redis-springboot]
Redis.conf详解
# redis 配置文件的介绍部分
# Redis configuration file example.
...
# 单位 大小写不敏感
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
...
################################## INCLUDES ###################################
...
# 能包含别的配置文件
# include /path/to/local.conf
# include /path/to/other.conf
#网络配置
################################## NETWORK #####################################
...
# 绑定能访问的IP配置
bind 127.0.0.1 -::1
...
#保护模式 一般都是开启
protected-mode yes
#访问的端口 默认6379
port 6379
...
# 通用配置
################################# GENERAL #####################################
daemonize no #后台启动,默认no,需要改为yes
...
pidfile /var/run/redis_6379.pid # 配置文件的PID
...
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)
# warning (only very important / critical messages are logged)
loglevel notice # 日志级别
...
logfile "" # 日志文件位置
...
databases 16 # 默认数据库的数量
...
always-show-logo no #是否显示redis的log
# 快照 在规定的时间内,执行了多少次操作,则会持久化到.rdb或者.aof文件
################################ SNAPSHOTTING ################################
...
# 如果900秒内至少有1个Key进行了修改,就执行一次持久化操作
save 900 1
# 如果300秒内至少有10个Key进行了修改,就执行一次持久化操作
save 300 10
# 如果60秒内至少有10000个Key进行了修改,就执行一次持久化操作【高并发】
save 60 10000
...
stop-writes-on-bgsave-error yes # 持久化出错后是否继续工作
...
rdbcompression yes # 是否压缩.RBD文件【占用CPU资源】
...
rdbchecksum yes # 保存rdb文件的时候,是否进行错误校验
...
dir ./ # rdb 文件保存的目录
...
# 安全配置
################################## SECURITY ###################################
...
# The requirepass is not compatable with aclfile option and the ACL LOAD
# command, these will cause requirepass to be ignored.
# 通过设置requirepass 来设置redis登录密码,一般通过命令行的模式设置
# requirepass foobared
# 通过命令行来设置密码
config set requirepass xxx
# 登录使用命令
auth xxx
# 客户端配置
################################### CLIENTS ####################################
maxclients 10000 # redis最大连接数
# 内存配置
############################## MEMORY MANAGEMENT ################################
maxmemory <bytes> # redis最大内存配置
...
# volatile-lru -> Evict using approximated LRU, only keys with an expire set.
# allkeys-lru -> Evict any key using approximated LRU.
# volatile-lfu -> Evict using approximated LFU, only keys with an expire set.
# allkeys-lfu -> Evict any key using approximated LFU.
# volatile-random -> Remove a random key having an expire set.
# allkeys-random -> Remove a random key, any key.
# volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
# noeviction -> Don't evict anything, just return an error on write operations.[默认]
maxmemory-policy noeviction # 内存满了处理策略
# AOF配置
############################## APPEND ONLY MODE ###############################
appendonly no # 默认不开启AOF持久化方式,默认使用RDB方式
...
appendfilename "appendonly.aof" # AOF持久化的文件名
...
# appendfsync always # 每次修改都同步,消耗性能,慢
appendfsync everysec # 默认,每秒执行一次同步,可能会丢失这一秒的数据
# appendfsync no # 不执行同步,由操作系统自己同步数据,速度最快,但一般也不用
Redis持久化
Redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,一旦服务器进程退出结束,数据库中的数据也会随之消失,所以Redis提供了持久化功能
RDB(Redis Database)
在指定的时间间隔内将内存中的数据集快照写入磁盘,恢复时将快照直接读进内存。
- Redis会单独创建(Fork)一个子进程来执行持久化的操作
- 先将数据写入到一个临时文件中,待持久化进程结束,再用此文件替换上次持久化好的文件
整个持久化过程中,主进程是不进行任何IO操作的,所以确保了Redis的极高性能。如果需要进行大规模的数据恢复,且对数据恢复的完整性不敏感,那么RDB的方式要比AOF方式更加高效
- RDB的缺点就是最后一次持久化的数据可能会丢失,但Redis依旧默认使用RDB方式来持久化
- RDB持久化的文件是
dump.rdb
文件
触发机制(产生RDB文件)
- save的规则满足的情况下,会自动触发
- 执行flushall命令也会触发
- 退出redis
如何恢复RDB文件
-
只需要将RDB文件放在redis启动目录下即可
-
# 启动目录查询 命令行下 config get dir # 此目录下如果有dump.rdb文件,redis会自己扫描
RDB 的优点与缺点
- 优点
- 适合大规模数据恢复
- 对数据的完整性要求不高
- 缺点
- 需要一定的时间间隔进程操作,如果redis出现宕机,则最后一次修改的数据就会丢失
- fork进程的时候,会占用一定的资源
AOF(APPEND ONLY FILE)
AOF 也是Fork一个子进程以日志的形式来记录每个写操作,将Redis所有执行过的记录保存下来(读操作不记录),只允许追加文件,不可以修改文件,Redis启动时,会将所有执行过的记录再执行一遍,完成数据的恢复工作
-
默认不开启,如需开启,只需要将配置文件中的appendonly 改为yes即可
-
如果AOF文件有错误,redis是启动不起来的,redis提供了redis-check-aof工具来修复AOF 文件
-
# Bin目录下执行 (会丢失有错误的数据) redis-check-aof --fix appendonly.aof
-
-
AOF持久化的文件是
appendonly.aof
文件
优点
- 每一次修改都会同步,文件的完整性会更好
缺点
- 相对于数据文件来说,AOF文件远远大于RDB文件,修复速度也慢于RDB文件
- AOF运行效率比RDB慢
Redis-发布订阅
Redis发布订阅(pub/sub)是一种消息通信模式:发送者发送消息,订阅者接收消息
- Redis客户端可以订阅任意数量的频道
命令
# 将信息 message 发送到指定的频道 channe
PUBLISH channel message
# 订阅给定的一个或多个频道的信息
SUBSCRIBE channel [channel …]
# 订阅一个或多个符合给定模式的频道。
PSUBSCRIBE pattern [pattern …]
# 指示客户端退订给定的频道。
UNSUBSCRIBE [channel [channel …]] # 如果没有频道被指定,也即是,一个无参数的 UNSUBSCRIBE 调用被执行,那么客户端使用 SUBSCRIBE 命令订阅的所有频道都会被退订。在这种情况下,命令会返回一个信息,告知客户端所有被退订的频道。
# 指示客户端退订所有给定模式。
PUNSUBSCRIBE [pattern [pattern …]] # 如果没有模式被指定,也即是,一个无参数的 PUNSUBSCRIBE 调用被执行,那么客户端使用 PSUBSCRIBE pattern [pattern …] 命令订阅的所有模式都会被退订。在这种情况下,命令会返回一个信息,告知客户端所有被退订的模式。
# PUBSUB 是一个查看订阅与发布系统状态的内省命令, 它由数个不同格式的子命令组成, 以下将分别对这些子命令进行介绍
PUBSUB <subcommand> [argument [argument …]]
PUBSUB CHANNELS [pattern] # 列出当前的活跃频道。
PUBSUB NUMSUB [channel-1 … channel-N] # 返回给定频道的订阅者数量, 订阅模式的客户端不计算在内。
PUBSUB NUMPAT # 返回订阅模式的数量。
示例
- 打开一个redis 客户端
- 执行
SUBSCRIBE ycy
命令,订阅ycy频道
- 执行
- 再打开一个redis客户端
- 执行
PUBLISH ycy "hello yu"
命令,往ycy
频道推送hello yu
的消息
- 执行
- 第一个客户端自动接收
hello yu
消息
原理
通过SUBSCRIBE
命令订阅某频道后,redis-server
里维护了一个字典,字典的键就是一个一个的频道,而字典的值则是一个链表,链表中保存了所有订阅这个channel的客户端,SUBSCRIBE
命令的关键就是将客户端添加到给定的channel订阅链表中。
通过PUBLISH
命令向订阅者发送消息,redis-server
会使用给定的频道作为键,在它所维护的channel字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者。
Redis-集群搭建
Redis-主从复制(概念)
概念
- 主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器的过程,
- 前者称为主(
master/leader
),后者称为从(slave/follower
); - 数据的复制是单向的,只能由主节点往从节点复制。其中,主节点(
master/leader
)以写为主,从节点(slave/follower
)以读为主 - 搭建集群时,最低需要一主二从来满足哨兵模式,即三台Redis服务器。
作用
- 数据冗余:主从复制实现了数据的备份,是持久化之外的一种数据冗余方式
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现故障快速恢复,本质上是一种服务的冗余
- 高可用的基石:主从复制是哨兵和集群能够实施的基础。
- 主从复制,读写分离,减缓服务器的压力
项目中使用单台Redis的局限性
- 结构上来说,单个Redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较大。
- 从容量上来说,单个Redis服务器的内存容量有限,一般来说,单台Reedis最大的使用内存不应该超过20G
注意
- 默认情况下,每台Reids服务器都是主节点
- 一个主节点可以没有从节点,可以有多个从节点,但是一个从节点只能有一个主节点
环境搭建-多台Redis服务器
只需配置从节点,无需配置主节点。
# 查看当前Redis服务器的集群详情
127.0.0.1:6379> info replication
# Replication
role:master # 当前Redis服务器角色
connected_slaves:0 # 已连接的从机角色
...
# copy一下配置文件[有几个从节点就拷贝几个从节点的配置文件]
[root@localhost redis-conf]# cp redis.conf redisMaster.conf
[root@localhost redis-conf]# cp redis.conf redisSlaver01.conf
[root@localhost redis-conf]# cp redis.conf redisSlaver02.conf
# 分别修改从节点的配置文件
# 端口修改
port 6380
# pid修改
pidfile /var/run/redis_6380.pid
# 日志文件名
logfile "redisSlaver01.log"
# RBD持久化文件名
dbfilename redisSlaver01.rdb
# 端口修改
port 6381
# pid修改
pidfile /var/run/redis_6381.pid
# 日志文件名
logfile "redisSlaver02.log"
# RBD持久化文件名
dbfilename redisSlaver02.rdb
# 分别启动三台Redis服务器
# 查看redis进程信息
[root@localhost bin]# ps -ef|grep redis
root 121325 1 0 15:55 ? 00:00:00 redis-server 127.0.0.1:6379
root 121390 1 0 15:57 ? 00:00:00 redis-server 127.0.0.1:6380
root 121415 1 0 15:57 ? 00:00:00 redis-server 127.0.0.1:6381
root 121451 120100 0 15:58 pts/3 00:00:00 grep --color=auto redis
环境搭建-一主二从(命令搭建)
只需配置从机
# redisSlaver01
127.0.0.1:6380> slaveof 127.0.0.1 6379 # 认主
OK
127.0.0.1:6380> info replication # 查看信息
# Replication
role:slave # 角色:从机
master_host:127.0.0.1 # 主机Ip
master_port:6379 # 主机端口
master_link_status:up # 主机链接状态
...
# redisSlaver02
127.0.0.1:6381> slaveof 127.0.0.1 6379 # 认主
OK
127.0.0.1:6381> info replication # 查看信息
# Replication
role:slave # 角色:从机
master_host:127.0.0.1 # 主机Ip
master_port:6379 # 主机端口
master_link_status:up # 主机链接状
# 回头再看主机信息
127.0.0.1:6379> info replication
# Replication
role:master # 角色:主机
connected_slaves:2 # 从机数量:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=406,lag=1 # 从机0 信息
slave1:ip=127.0.0.1,port=6381,state=online,offset=406,lag=0 # 从机1 信息
重点:
slaveof
命令详解
SLAVEOF
命令用于在 Redis 运行时动态地修改复制(replication)功能的行为。通过执行SLAVEOF host port
命令,可以将当前服务器转变为指定服务器的从属服务器(slave server)。- 如果当前服务器已经是某个主服务器(master server)的从属服务器,那么执行
SLAVEOF host port
将使当前服务器停止对旧主服务器的同步,丢弃旧数据集,转而开始对新主服务器进行同步。 - 对一个从属服务器执行命令
SLAVEOF NO ONE
将使得这个从属服务器关闭复制功能,并从从属服务器转变回主服务器,原来同步所得的数据集不会被丢弃。
拓展
ROLE
命令可以根据当前不同角色主机返回不同的数组- 主服务器将返回属下从服务器的 IP 地址和端口。
- 从服务器将返回自己正在复制的主服务器的 IP 地址、端口、连接状态以及复制偏移量。
- Sentinel 将返回自己正在监视的主服务器列表。
注意
-
主机可以写。从机不能写,只能读!
-
通过这种命令形式的配置只是暂时有效,永久有效应通过配置文件配置
环境搭建-一主二从(配置文件搭建)
配置文件解析
# 主从复制配置
################################# REPLICATION #################################
# 从机配置文件中修改以下行信息
# 去掉注释“#”号
# <masterip> 主机IP <masterport> 主机端口
...
# replicaof <masterip> <masterport>
...
# 此行是主机密码验证配置,可能会用上
# masterauth <master-assword>
...
问题
测试
- 模拟主机宕机(关闭主机服务)
- 从机依旧是从机模式,不做任何改变
- 从机拥有主机宕机前的所有数据
- 主机重新上线时,从机仍可继续同步主机的数据
- 模拟从机宕机(关闭一个从机服务)【注:采用命令行模式配置主从复制,宕机后重启Redis服务,会丢失主从配置信息,变为主机】
- 其它服务器状态无变化
- 重启Redis服务后,自动同步主机所有数据(全量复制)
- 主机有新的数据也会继续同步(增量复制)
主机宕机后,整个集群不可写操作,只能读
哨兵模式
解决主机宕机后,整个集群不可写操作,只能读的问题
- 主机宕机后自动选举主机,需要开启一个独立的哨兵进程
开启
- Redis的bin目录下有
redis-sentinel
进程服务
单个哨兵亦不能满足可靠性(单个哨兵进程挂了),所以一般也需要搭建哨兵集群来监控Redis集群
哨兵集群选举新主机过程
- 主节点服务宕机
- 单个或者低数量哨兵首先检测到【此时并不会立刻重新选举】
- 单个或者低数量哨兵认为主节点服务不可用【主服务机器主观下线】
- 当达到一定数量的哨兵检测到主节点服务宕机
- 哨兵集群由随机的单个哨兵发起投票,选举新的主节点服务(票多者胜任)【主服务机器客观下线】
哨兵模式搭建
# 新建一个配置文件
vim sentinel.conf
# 写入配置信息
# sentinel monitor 需要监控的主机名称[随意] 主机IP 主机Port 1:主机宕机的选举配置
sentinel monitor redisMaster 127.0.0.1 6379 1
# 注意,文件名和配置信息中的关键单词不能有错
# 通过配置启动哨兵
redis-sentinel redis-conf/sentinel.conf
优点
- 哨兵集群,基于主从复制模式,所有的主从配置优点它都包含
- 主从可以切换,故障可以转移,系统的可用性就会更好
- 哨兵模式就是主从模式的升级,能够自动在主机服务下线后自动选举新的主机服务
缺点
- Redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦
- 实现的方式很繁琐(配置繁琐)
# 哨兵模式的相关配置
# 哨兵运行的端口 默认: 26379
port 26379 # 若配置哨兵集群,需要配置每一个哨兵的端口[写多个配置文件,多进程启动哨兵,类似主从复制]
# 哨兵的工作目录(生成文件)
dir /tmp
# 哨兵sentinel监控的redis主节点的ip port
# sentinel monitor 需要监控的主机名称[随意] 主机IP 主机Port 1:达到决定主机客观下线的哨兵数
sentinel monitor redisMaster 127.0.0.1 63790 1
# 当在Redis实例中开启了requirepass foobared 授权密码,这样连接Redis实例的客户端都需要提供密码
# 设置哨兵sentinel连接主从的密码,注意必须为主从设置一样的验证密码
# sentinel auth-pass <master-name> <password>
#指定多少毫秒之后,主节点没有应答哨兵,哨兵主观认为主节点下线,默认30秒
sentinel down-after-milliseconds <master-name> 30000
...
Redis-缓存穿透和雪崩
查询模型
用户查询请求------------> 缓存数据库(如Redis)------------>MySQL
缓存穿透
概念
用户想要查询一个数据,发现缓存数据库Redis中没有(缓存未命中),于是向持久层数据库(如MySQL)中查询,也查不到,于是本次查询失败。当用户特别多的时候,缓存都没有命中,于是都去持久层数据库查询,导致持久层压力过大,出现缓存穿透现象。
解决方案
-
布隆过滤器
- 布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免对持久化层数据库的查询压力
- 模型
客户端---------->BloomFilter–>缓存数据库--------------->持久化层数据库
-
缓存空数据
- 当持久化层 不命中,即使返回的空对象也缓存起来,同时设置过期时间,之后访问此数据将会从缓存中拿取
- 问题
- 浪费空间
- 即使设置了过期时间,还是会导致缓存和持久化层数据在一段时间内不一致的现象。
缓存击穿
概述
缓存击穿,是指一个热点key,在不停的扛着大并发请求,大并发集中对这一个点进行访问,当这个Key在失效的瞬间,持续的大并发瞬间通过缓存穿透,击穿缓存,直接请求数据库的过程。与缓存穿透还是有差别的。
解决方案
- 设置热点数据永不过期
- 加互斥锁
- 分布式锁:使用分布式锁,保证对于每个Key同时只有一个线程去查询后端服务,其它线程没有获得分布式锁的权限,等待即可。这种方式将高并发的压力由缓存转移到了分布式锁上,因此对分布式锁的考验极大。
缓存雪崩
概念
缓存雪崩,指某一时间段,缓存集中过期失效。Redis宕机。
解决方案
- Redis高可用:多加机器(异地多活)
- 限流降级
- 数据预热