一、Nosql概述
为什么要使用NoSQL
用户的个人信息,社交网络,地理位置。用户自己产生的数据,用户日志等等爆发式增长!
这时候我们就需要使用NoSQL数据库,NoSQL可以很好的处理以上的情况
什么是NoSQL
NoSQL
NoSQL = Not Only SQL(不仅仅是SQL)
泛指非关系型数据库,
关系型数据库:列+行,同一个表下数据的结构是一样的。
非关系型数据库:数据存储没有固定的格式,并且可以进行横向扩展。
NoSQL特点
解耦
1、方便扩展(数据之间没有关系,很好扩展)
2、大数据量高性能(Redis一秒写8万次,读取11万,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高)
3、数据类型是多样型的(不需要事先设计数据库)
了解:3V + 3高
大数据时代的3V :主要是描述问题的
- 海量Velume
- 多样Variety
- 实时Velocity
大数据时代的3高 : 主要是对程序的要求
- 高并发
- 高可扩
- 高性能
NoSQL的四大分类
KV键值对
Redis
文档型数据库(bson格式和json一样)
- MongoDB
- MongoDB是一个基于分布式文件存储的数据库,C++编写的,主要用来处理大量的文档
- MongoDB是一个介于关系型数据库和非关系型数据库中间的产品。MongoDB是非关系型数据库中功能最丰富的,NoSQL中最像关系型数据库的数据库。
列存储数据库
- HBase(大数据必学)
- 分布式文件系统
图关系数据库
- Neo4j
分类 | 举例 | 应用场景 | 数据模型 | 优点 | 缺点 |
---|---|---|---|---|---|
KV键值对 | Tokyo Cabinet/Tyrant, Redis, Voldemort, Oracle BDB | 内容缓存,主要用于处理大量数据的高访问负载,也用于一些日志系统等等。 | Key 指向 Value 的键值对,通常用hash table来实现 | 查找速度快 | 数据无结构化,通常只被当作字符串或者二进制数据 |
列存储数据库 | Cassandra, HBase, Riak | 分布式的文件系统 | 以列簇式存储,将同一列数据存在一起 | 查找速度快,可扩展性强,更容易进行分布式扩展 | 功能相对局限 |
文档型数据库 | CouchDB, MongoDb | Web应用(与Key-Value类似,Value是结构化的,不同的是数据库能够了解Value的内容) | Key-Value对应的键值对,Value为结构化数据 | 数据结构要求不严格,表结构可变,不需要像关系型数据库一样需要预先定义表结构 | 查询性能不高,而且缺乏统一的查询语法。 |
图关系数据库 | Neo4J, InfoGrid, Infinite Graph | 社交网络,推荐系统等。专注于构建关系图谱 | 图结构 | 利用图结构相关算法。比如最短路径寻址,N度关系查找等 | 很多时候需要对整个图做计算才能得出需要的信息,而且这种结构不太好做分布式的集群 |
二、Redis入门
概述
Redis是什么
Redis(Remote Dictionary Server),即远程字典服务。
是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
Redis能干什么
- 内存存储、持久化,内存是断电即失的,所以需要持久化(RDB、AOF)
- 高效率、用于高速缓冲
- 发布订阅系统
- 地图信息分析
- 计时器、计数器
特性
- 多样的数据类型
- 持久化
- 集群
- 事务
环境搭建
推荐使用Linux服务器学习
windows版本的Redis已经停更很久了
Windows安装
1、下载安装包 https://github.com/microsoftarchive/redis/releases/tag/win-3.2.100
2、下载完毕得到压缩包
3、解压到自己电脑的环境目录下就可以了(只有5M)
4、开启redis,双击启动redis-server
这里如果闪退的话,可以先创建一个start.bat文件,然后在文件中写入
redis-server.exe redis.windows.conf
然后启动这个文件即可
5、使用redis客户端来连接redis(双击启动cli客户端)
基础知识
redis默认有16个数据库(DB0~DB15),并且默认使用的第0个数据库,在redis.windows.conf文件中可以看出
命令
set key valus:设定一个k-v键值对值
get key:通过key取出value
type key:查看key的类型
select n:切换到DB n数据库
keys *:查看当前数据库中所有的key
dbsize:查看当前数据库大小,与key的数量相关
flushdb:清空当前数据库的键值对
flushall:清空所有数据库的键值对
exists key:判断key是否存在,如果存在,则返回1,否则返回0
move key db:把key移动到别的数据库
expire key seconds:设置key多少秒后过期(删除)
ttl key:查看key还剩多少秒过期,
-
如果返回一个正数,则是还剩多少时间过期;
-
如果返回-1,则没有设置过期时间;
-
如果返回-2,则已过期(ttl=time to live)
rename key newkey:修改key的名称为newkey,如果newkey存在,会被覆盖
renamenx key newkey:仅当newkey不存在时,修改key的名称为newkey,newkey存在会返回0,不存在会返回1
redis的命令有很多,可以去官网查看更多的命令,官网
三、五大数据类型
Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel提供高可用,通过Redis Cluster提供自动分区。
String(字符串)
append key “sfsf”:追加字符串,如果当前key不存在,就相当于set key
strlen key:获取字符串的长度
incr key:key+1,如果key不存在,则set key,返回修改后的值
decr key:key-1,如果key不存在,则set key,返回修改后的值
incrby key 10:key+10,如果key不存在,则set key,返回修改后的值
decrby key 10:key-10,如果key不存在,则set key,返回修改后的值
getrange key 0 3:截取下标0到3的字符串,左闭右闭,如果是0到-1,则截取所有的字符串
setrange key 下标 替换的值:从某个值开始替换
将bc替换成xx
setex key seconds value:设置一个key,再数秒后过期
setnx key value:不存在再设置
返回1,则设置成功
返回0,则设置失败
mset key value…:一次性设置多个key,原子性操作,mset后也可以和上面一样加ex或者nx
mget key…:一次性取多个value
getset key value:先get再set,get的是之前的值,返回的是get的值,组合命令,具有原子性
List(列表)
可以添加一个元素到列表的头部或者尾部,列表中允许设定重复的value
只有push和pop时才分左右,其余命令的l均为list的意思
lpush key value1…:在key列表头部插入一个或多个值
rpush key value1…:在key列表尾部插入一个或多个值
lrange key start end:获取key列表start到end索引下的元素(左闭右闭 0—-1表示获取所有元素)
lpop key:移除key列表的头部元素
rpop key:移除key列表的尾部元素
llen key:查看key列表的长度
lrem key count value:移除key列表中count个value
-
count>0:从头往尾移除count个值为value的元素
-
count=0:移除所有的值为value的元素
-
count<0:从尾往头移除|count|个值为value的元素
ltrim key start stop:让key列表只保留下标在start到stop的元素
lset key index value:将key列表下标为index的值替换为value
linsert key before|after pivot value:在key列表中存在的pivot值的前面或者后面插入value
Set(集合)
set中的值不能重复,并且是无序的
sadd key member…:往key集合中存放一个或多个member值
smembers key:查看key集合中的值
sismember key member:判断key集合中是否存在member值
- 返回1则存在
- 返回0则不存在
scard key:查看key集合中的元素个数
srem key member…:移除key集合中的值为member元素
srandmember key count:从key集合中随机抽取count元素出来,如果不写count,则默认抽一个
spop key:随机移除key集合中的一个元素
smove key1 key2 value:将key1集合中的value值移动到key2集合中
差集、交集、并集
sdiff key1 key2:以key1为准的差集
sinter key1 key2:交集
sunion key1 key2:并集
Hash(哈希表)
key-Map集合,值是一个map集合,map中的field不能重复,value可以重复,hash适合用于存储对象,
hset key field value:在key哈希表中,设定一个名为field,值为value的kv键值对
hget key field:查看key哈希表中名为field的值
hgetall key:查看key哈希表中所有的值(以kv键值对的方式)
hdel key field:删除key哈希表中名为field的值
hlen key:获取key哈希表的长度
hexists key field:判断key哈希表中名为field的字段是否存在
hkeys key:获取key哈希表中所有的键
hvals key:获取key哈希表中所有的值
Zset(有序集合)
在set的基础上,增加了一个score值,set k1 v1 zset k1 score1 v1,所以Zset是有序不重复的,score可以重复
zadd key score member:往key集合中添加一个member值,score为排序标志(数字)
zrange key start stop:查看key集合中,下标start到stop的值(0到-1是所有的值)默认按照score从小到大排
zrevrange key start stop withscores:跟上面一样 但排序是从大到小排,并且会显示score的值
zrangebyscore key min max:在key集合中查看score在min到max中的值(从小到大) inf:无穷
zrevrangebyscore key max min:在key集合中查看score在max到mix中的值(从大到小)
zrem key member:移除key集合中值为member的元素
zcard key:获取key集合的个数
zcount key min max:获得key集合score在min到max之间的数量
四、三种特殊数据类型
Geospatial(地理位置)
geoadd(添加地理位置)
我们一般会下载城市数据,直接通过java程序一次性导入
有效经度从-180到180度,有效的纬度从-85.05到85.05
用的是一个有序集合zset保存,所以所有的zset命令都可以使用
geoadd key longitude latitude member:geoadd 名字 经度 维度 ,往一个key集合中中添加一个或者多个member数据
geoadd china:city 116.40 39.90 beijing
geoadd china:city 121.47 31.23 shanghai
geoadd china:city 106.50 29.53 chongqing 114.05 22.52 shengzhen
geoadd china:city 120.16 30.24 hangzhou 108.96 34.26 xian
geopos(获取指定的地理位置)
geopos key member…:获取key集合中一个或者多个member的地理位置
geodist(两个元素之间的距离)
geodist key member1 member2:获取key集合中member1和member2的直线距离 默认为米
geodist key member1 member2 km:修改为千米
georadius(查看周围的元素)
以给定的经纬度为中心,返回与中心距离不超过给定最大距离的所有元素
georadius key longitude latitude radius m|km|ft|mi:radius范围,key集合
georadius china:city 110 30 500 km:寻找以110,30这个经纬度为中心,方圆500km的城市
georadius china:city 110 30 500 km withdist:附加了一个到中心的距离
georadius china:city 110 30 500 km withcoord:附加了城市的定位信息
georadiusbymember(查看指定元素周围的元素)
georadiusbymember key member radius m|km|ft|mi:查看key集合中member元素radius范围内的元素
geohash(返回一个或者多个元素的hash)
返回11个字符的geohash字符串
geohash key member…:返回一个或者多个元素的位置hash
原理
geo底层的实现原理是使用了zset,我们可以使用zset命令来操作
Hyperloglog(基数统计)
基数是数据集中不重复的元素个数
优点:占用的内存是固定的
缺点:无法知道具体的内容
底层是string
pfadd key element…:添加一个或多个element元素到key中
pfcount key…:统计一个或者多个key的返回基数(多个key的情况下会合并统计)
pfmerge destkey sourcekey…:合并多个sourcekey成为destkey
如果允许容错,那么一定可以使用Hyperloglog !
如果不允许容错,就使用set或者自己的数据类型即可 !
应用场景:网页的访问数
Bitmap(位图)
位存储
只有 0和1两个状态
应用场景:签到统计、状态统计
setbit key offset value:往key里存储一个offset元素,值为value,value只能为0或1
getbit key offset:查看key中offset;元素的值
bitcount key:统计key中所有值为1的数量
五、事务
redis里的事务比起mysql的事务更像是批处理
Redis里单条命令是保证原子性的,但是事务不保证原子性
Redis事务没有隔离级别的概念
Redis事务本质:一组命令的集合,一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行!
一次性、顺序性、排他性!执行一系列的命令
redis的事务:
- 开启事务(multi)
- 命令入队(…)
- 执行事务(exec)
multi:开启事务
discard:取消事务
exec:执行事务
编译型异常
命令有错误,事务中的所有的命令都不会执行
运行时异常
命令没错误,但逻辑存在错误,部分事务能运行
监控
悲观锁
- 认为什么时候都会出问题,无论做什么都会加锁
乐观锁
- 认为什么时候都不会出问题,所以不会上锁!更新数据的时候判断一下,在此期间是否有人修改过数据
- 更新的时候比较version
watch key:监控指定的一个或者多个key,相当于乐观锁
unwatch:解锁
在执行事务后,无论事务是否成功,都会自动解锁
例子:
1、先在一个客户端里监控并写事务,但先不执行
2、然后在另一个客户端里修改key的值
3、这时再在第一个客户端中执行事务
发现事务执行失败了,这就是使用了watch监控
六、Jedis
什么是Jedis?
官方推荐的java连接开发工具,使用java操作redis的中间件
1、导入对应的依赖
<!--导入jedis的包-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
2、编码测试
- 连接数据库
- 操作命令
- 断开连接
public static void main(String[] args) {
// 1. new Jedis对象
Jedis jedis = new Jedis("127.0.0.1",6379);
// 2. Jedis所有的命令就是之前学习的命令
System.out.println(jedis.ping());
}
输出
常用的API
String
List
Set
Hash
Zset
所有的API命令就是上面学习的指令
事务
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello","world");
jsonObject.put("name","wyl");
// 开启事务
Transaction multi = jedis.multi();
String s = jsonObject.toJSONString();
try {
multi.set("user1",s);
multi.set("user2",s);
multi.exec();
} catch (Exception e) {
multi.discard(); // 放弃事务
e.printStackTrace();
} finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
jedis.close(); // 关闭连接
}
}
七、SpringBoot整合
在springboot 2.x之后,原来使用的jedis被替换成了lettuce
jedis:采用的直连,多个线程操作的话是不安全的
lettuce:采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况
测试
1、导入依赖
<!--操作redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、配置连接
# springboot 所有的配置类,都有一个自动配置类 RedisAutoConfiguration
# 自动配置类都会绑定一个 properties配置文件 RedisProperties
spring.redis.host=127.0.0.1
spring.redis.port=6379
3、测试
@SpringBootTest
class Redis02SpringbootApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
// redisTemplate操作不同的数据类型,api和我们的指令一样
// opsForValue 操作字符串 类似string
// opsForList 操作list 类似list
redisTemplate.opsForValue().set("mykey","沐三");
System.out.println(redisTemplate.opsForValue().get("mykey"));
}
}
这里有一个问题,因为redis未序列化,导致在redis里查看数据时是乱码
自定义RedisTemplate
编写一个自己的redisTemplate来替代默认的
@Configuration
public class RedisConfig {
// 编写我们自己的redisTemplate
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 连接工厂,不必修改
template.setConnectionFactory(redisConnectionFactory);
/*
* 序列化设置
*/
// key、hash的key 采用 String序列化方式
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
// value、hash的value 采用 Jackson 序列化方式
template.setValueSerializer(RedisSerializer.json());
template.setHashValueSerializer(RedisSerializer.json());
template.afterPropertiesSet();
return template;
}
}
在实体类中实现序列化,就不会乱码了
@Component
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User implements Serializable {
private String name;
private int age;
}
但在命令行中还是无法显示中文
控制台显示中文
解决方案:
先进入redis的安装目录所在的命令行,输入redis-cli --raw
如果还是无法显示中文,那就先把命令行的编码方式改成65001(UTF-8)
通过去修改注册表可以修改代码页,去注册表该路径下HKEY_CURRENT_USER\Console%SystemRoot%_System32_cmd.exe修改CodePage的值为65001
工具类
可以去网上找工具类来简化开发,通过封装使开发更加简单
八、Redis.conf详解
查看redis.windows.conf配置文件
容量单位不区分大小写,g和gb有区别
使用include组合多个配置
网络
绑定的ip
端口号和保护模式
通用GENERAL
daemonize yes # 以守护进程的方式运行,默认是no,我们需要自己开启为yes,并且在windows上是不支持的,在linux上可以修改
pidfile /var/run/redis.pid # 如果以后台的方式运行,我们需要指定一个pid文件,同样不支持windows
日志
日志的文件位置名
数据库的数量,默认是16个
快照
持久化,在规定的时间内,执行了多少操作,则会持久化到文件中
redis是内存数据库,如果没有持久化,那么数据断电即失
save 900 1 # 在900秒后,如果有一个key修改过,那么进行持久化操作
磁盘出现错误时会停止工作
压缩rdb文件,但会消耗一些cpu资源
保存rdb文件时,进行错误校验
RDB的文件名和保存目录
REPLICATION 主从复制
里面可以修改主从复制的一些参数
SECURITY
能设置redis的密码
requirepass 123456:设置为123456
限制
设置能连接上redis的最大客户端数量
设置redis最大的内存容量
内存达到上限的处理策略
maxmemory-policy 六种方式
1、volatile-lru:只对设置了过期时间的key进行LRU(默认值)
2、allkeys-lru : 删除lru算法的key
3、volatile-random:随机删除即将过期key
4、allkeys-random:随机删除
5、volatile-ttl : 删除即将过期的
6、noeviction : 永不过期,返回错误
APPEND ONLY MODE aof配置
默认是不开启aof模式,默认使用rdb方式持久化,大部分情况下,rdb够用
持久化文件的名字
appendfsync always # 每次修改都会 sync
appendfsync everysec # 每秒执行一次 sync
appendfsync no # 不执行 sync
九、Redis持久化
Redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。所以Redis提供了持久化功能
RDB(Redis DataBase)
默认情况下,Redis将数据库快照保存在名字为dump.rdb的二进制文件中。文件名可以在配置文件中自定义
工作原理
在进行RDB的时候,redis的主线程不会做io操作的,主线程会创建(fork)一个子线程来完成该操作
- Redis调用forks。同时拥有父进程和子进程
- 子进程将数据集写入一个临时的RDB文件中
- 当子进程完成对新RDB文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件。
这种工作方式使得 Redis 可以从写时复制(copy-on-write)机制中获益(因为是使用子进程进行写操作,而父进程依然可以接收来自客户端的请求。)
触发机制
- save规则满足的情况下,会自动触发
- 执行flushall命令,也会触发rdb原则
- 退出redis,也会自动产生rdb文件
在配置文件中,有默认的save规则
经过多少秒检测一次,如果有多少修改,则进行持久化操作
save 900 1 # 在900秒后,如果有一个key修改过,那么进行持久化操作
当触发save时,会立刻对当前内存中的数据进行持久化,但是会阻塞,因为save是同步命令
优缺点
优点:
- 适合大规模的数据恢复
- 对数据的完整性要求不高
缺点
- 需要一定的时间间隔进行操作,如果宕机了,那么最后一次修改的数据就没了
- fork进程的时候,会占用一定的内存空间
AOF(Append Only File)
什么是AOF
快照功能(RDB)并不是非常耐久(durable): 如果 Redis 因为某些原因而造成故障停机, 那么服务器将丢失最近写入、以及未保存到快照中的那些数据。 从 1.1 版本开始, Redis 增加了一种完全耐久的持久化方式: AOF 持久化。
AOF文件会记录我们的写入操作(不会记录读操作)
默认是不开启的,如果要使用,需要修改配置文件,把appendonly改为yes
appendfsync always # 每次修改都会 sync
appendfsync everysec # 每秒执行一次 sync 默认
appendfsync no # 不执行 sync
当我们执行了set k1 v1时,aof文件记录了我们的set操作
redis-check-aof.exe修复工具
如果aof文件中有一些错误的操作,这时是无法启动redis的
我们需要使用redis-check-aof来修复aof文件,进入命令行输入redis-check-aof.exe --fix appendonly.aof来修复aof文件
再次打开aof文件,发现错误的操作已被清除,redis也能启动了
文件重写
因为aof文件记录的都是命令,所以就可能有一些冗余的命令导致aof文件越来越大,redis引入了aof重写机制,以便压缩aof体积
重写aof后为什么能变小
- 清除了一些无效命令
- 进程内超时的数据不再写入aof文件
- 多条写命令可以合并成批量写命令
当大于64mb时执行重写机制,文件增长比例默认为100
优缺点
优点:
- 每次修改都会同步,文件的完整性会更好
- 每秒同步一次,最多只会丢失这一秒的数据
缺点
- aof运行效率比rdb慢
- aof文件体积大于rdb文件体积
十、Redis主从复制
概念
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower), 数据的复制是单向的!只能由主节点复制到从节点(主节点以写为主、从节点以读为主)。
默认情况下,每台Redis服务器都是主节点,一个主节点可以有0个或者多个从节点,但每个从节点只能由一个主节点。
作用
1、数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式。
2、故障恢复:当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式
3、负载均衡:在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作,分担服务器的负载;尤其是在多读少写的场景下,通过多个从节点分担负载,提高并发量。
4、高可用基石:主从复制还是哨兵和集群能够实施的基础。
为什么使用集群
- 单台服务器难以负载大量的请求
- 单台服务器故障率高,系统崩坏概率大
- 单台服务器内存容量有限。
十一、哨兵模式
主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。
概述
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
用文字描述一下故障切换(failover)的过程。假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。
优缺点
优点:
- 哨兵集群,基于主从复制模式,所有主从复制的优点,它都有
- 主从可以切换,故障可以转移,系统的可用性更好
- 哨兵模式是主从模式的升级,手动到自动,更加健壮
缺点:
- Redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦
- 实现哨兵模式的配置其实是很麻烦的,里面有很多配置项
十二、缓存穿透与雪崩
缓存穿透(查不到数据)
概念
在默认情况下,用户请求数据时,会先在缓存(Redis)中查找,若没找到即缓存未命中,再在数据库中进行查找,数量少可能问题不大,可是一旦大量的请求数据(例如秒杀场景)缓存都没有命中的话,就会全部转移到数据库上,造成数据库极大的压力,就有可能导致数据库崩溃。网络安全中也有人恶意使用这种手段进行攻击被称为洪水攻击。
解决方案
使用布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的压力
缓存空对象
当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源
这样做有一个缺陷:存储空对象也需要空间,大量的空对象会耗费一定的空间,存储效率并不高。解决这个缺陷的方式就是设置较短过期时间
即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。
缓存击穿(并发查同一条数据,缓存过期)
概念
这里需要注意和缓存击穿的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点就行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导致数据库瞬间压力过大
解决方案
1、设置热点数据永不过期
这样就不会出现热点数据过期的情况,但是当Redis内存空间满的时候也会清理部分数据,而且此种方案会占用空间,一旦热点数据多了起来,就会占用部分空间。
2、加互斥锁(分布式锁)
在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。保证同时刻只有一个线程访问。这样对锁的要求就十分高。
缓存雪崩(不同的数据过期了)
概念
大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。
解决方案
redis高可用
这个思想的含义是,既然redis有可能挂掉,那我多增加几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群
限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制都数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待
数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀