NoSQL数据库简介
用NoSQL打破了传统的关系型数据库,以业务逻辑为依据处理存储模式,针对不同数据结构的类型改变以性能为最优先的存储方式,目的就是提高性能
技术的分类:
1.解决功能性的问题(实现一个功能用的这些技术):Java、Jsp、RDBMS、Tomcat、HTML、Linux、JDBC、SVN
2.解决扩展性的问题(加入、修改功能需要用到的技术):Struts、Spring、SpringMVC、Hibrenate、Mybatis
3.解决性能的问题(解决用户量竞争访问,解决性能需要用到的技术):NoSQL、Java线程、Hadoop、Nginx、MQ、ElasticSearch
session的共享问题:
第一种方案:存储到客户端中,或是放在cookie中,好处:每次请求都会带有cookie中,里面都会有用户信息,能保证session共享,缺点:存到客户端中,安全问题
第二种方案:session复制,在第一台服务器登录了,产生了session对象,然后把session对象复制了多份到其他台服务器中,能保证session是同步的,缺点:session是复制的,复制的意思表示session对象是一样的,造成空间极大的浪费,每个里面都存储相同的对象
第三种方案:NoSQL数据库,可以吧用户信息存储到NoSQL数据库中,比如第一次登录后存储,第二次访问就看开NoSQL有没有信息,有的话就登陆,没有就不能登录,好处:不需要经过IO操作,数据完全存到内存中,读的速度更快,解决CPU和内存的方案
NoSQL解决IO压力
NoSQL数据库
NoSQL数据库概述
NoSQL(NoSQL = Not Only SQL),意思是“不仅仅是SQL”,泛指非关系型数据库
NoSQL不依赖业务逻辑方式存储,而以简单的key-value模式存储,因此大大的增加了数据库的扩展能力
- 不遵循SQL标准
- 不支持ACID(原子性、可见性、隔离性、有序性)
- 远超于SQL的性能
NoSQL适用场景
- 对数据高并发的读写
- 海量数据的读写
- 对数据高可扩展性
NoSQL不适用场景
- 需要事务支持
- 基于sql的结构化查询存储吗,处理复杂的关系,需要即席查询
(用不着sql的和用不了sql也不行的情况,请考虑用NoSQL)
行式存储数据库
行式数据库
列式数据库
Redis概述
配合关系型数据库做高速缓存
- 高频次,热门访问的数据,降低数据库IO
- 分布式架构,做session共享
多样的数据结构存储持久化数据
Redis安装
1.首先利用Xshell进入linux
2.安装c语言编译环境,gcc版本
yum install gcc
3.查询版本
gcc --version
4.进入opt文件夹(这里我是放在这个文件夹的)
5.解压redis压缩包
tar -zxvf 文件名
6.进入解压后的文件
7.使用make编译安装
注意:如果没有C语言编译环境,make会报错
进入src目录下执行make install
8.安装后进入 usr/local/bin 目录下有这么几个文件:
分别是:
前台启动:(不推荐)
redis-server
后台启动:(推荐)
- 回到 解压后 redis的文件夹
- 复制redis.conf文件到etc目录下
cp redis.conf /etc/redis.conf
- 后台启动设置daemonize no 改成 yes
vim redis.conf
进入文件夹
找到daemonize no改成yes
再次启动redis服务,并指定启动服务配置文件
redis-server /etc/redis.conf
启动成功!
比如吧窗口关掉了,redis还在启动,因为是后台启动
- redis-cli 通过后端连接redis关闭,使用shutdown命令
Redis相关知识
端口6379从何而来:Alessia Merz(九键6379的对位)
Redis:单线程+多线IO复用
常用五大数据类型
Redis键(Key)
- keys * 查看当前库所有key
- exists key 判断某个key是否存在 返回1就表示有 返回0就是没有
- type key 查看你的key是什么数据类型
- del key 删除指定的key数据
- unlink key 根绝value选择非阻塞删除
- set key键 value值 增加操作
- expire key 10 为指定的key设置过期时间
- ttl key 查看还有多少s过期 -1永不过期 -2表示已过期
- select 切换库
- dbsize 查看当前库里又多少key
常用数据类型 字符串(String)
常用命令
- set key value 添加键值对(设置相同的key就把之前的覆盖了)
- get key 查询对应键值对
- append key 将给的value值追加到原值的末尾
- strlen key 获得值的长度
- setnx key value 只有在key不存在时 设置key的值
- incr key 将key中存储的数字值增1 只能对数字值操作,如果为空,新增值为1
- decr key 将key中存储的数字值捡1 只能对数字值操作,如果为空,新增值为-1
- incrby decrby key 步长 将key中存储的数字值增减 自定义步长 只能对数字值操作(步长:自定义数字)
原子操作
- mset key1 key2… 同时设置一个或者多个key-value对
- mget 同时获取一个或者多个key-value对
- msetnx 同时设置一个或多个key-value对 仅当所有给定key都不存在
原子性,有一个失效,都会失效 - getrange key 起始位置 结束位置;获取值的范围(含头含尾)
- setrange key 位置 覆盖的值 ;覆写key所存储的字符串值
- setex key 过期时间 value值
- getset key value 以新值换旧值
String数据结构
什么叫动态字符串?
就可以修改字符串的值
Redis 列表 (List)
常用命令
- lpus/rpush key value1 value2 ;从左边/右边插入一个或多个值
v1本来是在第一个格子,然后v2进来吧v1挤到第二个格子,v2就在第一个格子 以此类推
Redis列表(List)
常用命令
数据结构
quickList
List数据结构快速链表 quickList
首先在列表元素较少的情况会使用一块连续的内存存储,这个结构是ziplist,也即是压缩列表
它将所有的元素紧挨着一起存储,分配的是一块连续的内存
当数据量比较多的时候才会改成quickList
因为普通的链表需要的附加指针空间太大,会比较浪费空间,比如这个列表里存的只是int类型的数据,结构上还需要两个额外的指针prev和next
Redis将链表和ziplist结合起来组成了quicklist,也就是将多个ziplist使用双向指针串起来使用,这样既满足了快速的插入删除性能,又不会出现太大的空间冗余
Redis集合(Set)
Redis Set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的
Redis的set是string类型的无序集合,它底层其实是一个value为null的hash表,所以添加、删除、查找的复杂度是0(1)
一个算法,随着数据的增加,执行时间的长短,如果是0(1),数据增加,查找数据的时间不变
常用命令
- sadd key value 将一个或者多个member元素加入到集合key中,已经存在的member元素将被忽略
- smenbers key 取出该集合的所有值
- sismember key value 判断集合key是否为含有该value值 有1 没有0
- scard key 返回该集合的元素个数
- srem key value 删除集合中的某个元素
- spop key 随机从该集合中吐出一个值
- srandmember key n 随机从该集合中取出n个值,不会从集合中删除
- smove source destination value 吧集合中一个值从一个集合移动到另一个集合
- sinter key1 key2 返回两个集合的交集*(相同的)元素
- sunion key1 key2 返回两个集合的并集(所有的)元素
set数据结构
Java中HashSet内部实现使用的是HashMap,只不过所有的value都指向同一个对象,Redis的set结构也是一样,它的内部也使用hash结构,所有的value都指向同一个内部值
Redis哈希(Hash)
Redis hash是一个键值对集合
Redis hash是一个string类型的field合value的映射表,hash特别适合用于存储对象类似java里面的Map<String,Object>
用户ID为查找的key,存储的value用户对象包含姓名,年龄,生日等信息,如果用普通的key/value结构赖存储
主要有以下两种存储方式:
最好用hash存储方式:
通过key(用户ID)+field(属性标签)就可以操作对应属性数据了,既不需要重复存储数据,也不会带来序列化和并发修改控制的问题
常用命令
- hset key filed value 给key集合中的field键赋值value值
- hget key field 从key1集合field取出value
- hmset key1 field1 value1 field2 value2 批量设置hash的值
- hexists ke1 field 查看哈希表key中,给定域field是否存在,真返回1 假返回0
- hkeys key 列出该hash集合所有的field
- hvals key 列出该hash集合所有的value
- hincrby key field increment 为哈希表key中的域field的值加上增量1,加值
- hsetnx key field value 将哈希表中的域field值设置为value 当且仅当域field不存在,field值有就不能加,没有就可以加
数据结构
Hash类型对应的数据结构是两种:ziplist(压缩列表),hashtable(哈希表),当field-value长度较短且个数较少时,使用ziplist,否则使用hashtable
配置文件
单位设置方式:
只支持bytes,不支持bit
网络相关配置:
bind
默认情况bind=127.0.0.1只能接受本机的访问请求
不写的情况下,无限制接受任何ip地址的访问
生产环境肯定要写你应用服务器的地址:服务器是需要远程访问的,所以需要将其注释掉
如果开起来protected-mode,那么在没有设定bind ip且没有设密码的情况下,Redis只允许接受本机的响应
开启保护模式:
改成no就是关闭保护模式,支持远程访问
tcp-backlog
设置tcp的backlog,backlog其实是一个链接队列,backlog队列总和=未完成三次握手队列+已经完成三次握手队列
在高并发环境下你需要意个高backlog值来避免客户端连接问题
监测心跳:
设置日志的输入路径
默认的库有多少 个
LIMITS限制
设置redis同时可以与多少个客户端进行连接
默认情况下为10000个客户端
如果达到了此限制,redis则会拒绝新的连接请求,并且向这些连接请求方发出max number of client reached 已做回应
Redis发布和订阅
什么是发布和订阅
Redis发布订阅(pub/sub)是一种消息通信 模式:发送者(pub)发送消息,订阅者(sub)接收消息
Redis客户端可以订阅任意数量的频道
发布订阅命令行实现
1.打开一个客户端订阅channe11
SUBSCRIBE channe11
2.打开另一个客户端,给channe11发布消息hello
publish channe11 hello
3.打开第一个客户端可以看到发送的消息
新数据类型-Bitmaps
简介:
Redis提供了Bitmaps这个“数据类型”可以实现对位的操作:
(1)Bitmaps本身不是一种数据类型,实际上它就是字符串(Key-value),但是它可以对字符串的位进行操作
(2)Bitmaps单独提供了一套命令,所以在Redis中使用Bitmaps和使用字符串的方法不太相同,可以吧Bitmaps想象成一个以位为单位的数组,数组的每个单元只能存储0和1,数组的下标在Bitmaps中叫做偏移量
offset:偏移量
格式:
setbit key offset value 设置Bitmaps 中某个偏移量的值(0或1)1设置被访问,0未访问
偏移量从0开始
getbit key offset 获取偏移量是否被访问过
bitcount
统计字符串被设置为1的bit数,一般情况下,给定的整个字符串都会被进行计数,通过指定额外的start或end参数,可以让计数只在特定的位上进行,start和end参数的设置,都可以使用负数值:比如-1 表示最后一个位,而-2表示倒数第二个位,start、end是指bit组的字节的下表述,二者皆包含
bitcount key 统计字符串start字节到end字节的比特值为1(被访问)的数量
bitop and unique:users:and:20201104_03
unique:users:20201103unique:users:20201104
Bitmaps与set对比
用户量大就用Bitmaps:
用户量小用set集合
新数据类型-HyperLogLog
统计页面访问量
在工作当中,我们经常会遇到与统计相关的功能需求,比如统计网站PV(PageView页面访问量),可以使用Redis的incr、incrby轻松实现
但像UV(UniqueVisitor,独立访客)、独立ip数、搜索记录数等需要去重和计数的问题如何解决?这种求集合中不重复元素个数的问题称为基数问题
解决基数问题有很多种方案:
(1)数据存储在MySQL表中,使用distinct count计算不重复个数
(2)使用Redis提供的hash、set、bitmaps等数据结构来处理
以上的方案结果精确,但随着数据不断增加,导致占用空间越来越大,对于非常大的数据集是不切实际的
是否能降低一定的精度来平衡存储空间?Redis推出了HyperLogLog
Redis HyperLogLog是用来做基数统计的算法,HyperLogLog的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的
在Redis里面,每个HyperLogLog键只需要花费12kb内存,就可以计算接近2^64个不同元素的基数,这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比
但是因为HyperLogLog只会根据输入元素来计算基数,而不会储存输入元素本身,所以HyperLogLog不能像集合那样,返回输入的各个元素
什么是基数
比如数据集{1,3,5,7,5,7,8,},那么这个数据集的基数集为{1,3,5,7,8}基数(不重复元素)为5,基数估计就是在误差可接受的范围内,快速计算基数
命令
pfadd key “elemnt” 添加指定元素到HeyperLogLog中, 成功1,失败0
pfcount key 统计基数
pfmerge destkey sourcekey 将一个或多个HLL合并后的结果存储在另一个HLL中,比如每月活跃用户可以使用每天的活跃用户合并计算可得
新数据类型-Geospatial
简介
Redis 3.2中增加了对GEO类型的支持,GEO,Geographic,地理信息的缩写,该类型,就是元素的2维坐标,在地图上就是经纬度,redis基于该类型,提供了经纬度设置,查询,范围查询,距离查询,经纬度hash等常见操作
有效的经度从-180 到 180度 纬度从-85.05112878到85.05112878
当坐标位置超出指定范围,该命令会返回一个错误,已经添加的数据,是无法再往里面添加的
命令
geoadd key longitude latitude member 添加地理位置
geopos key longitude 获取地理位置
geodist key member1 member2 获取两个位置之间的直线距离
单位: m 米 km 千米 mi 英里 ft 英尺
Redis客户端工具:Jedis操作-测试
Jedis所需要的jar包
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
在Linux中防火墙:
systemctl status firewalld
关闭:
systemctl stop firewalld
操作key,得到redis里面所有的数据
@Test
public void demo1() {
Jedis jedis = new Jedis("192.168.255.100", 6379);
Set<String> keys = jedis.keys("*");
for (String s : keys) {
System.out.println(s);
}
}
添加key
//添加
@Test
public void demo2() {
Jedis jedis = new Jedis("192.168.255.100", 6379);
//添加
jedis.set("name", "lucy");
//获取
String name = jedis.get("name");
System.out.println(name);
Set<String> keys = jedis.keys("*");
for (String s : keys) {
System.out.println(s);
}
}
Jedis-API:String
//设置多个key-value
jedis.mset("k1", "v1", "k2", "v2");
List<String> mget = jedis.mget("k1", "k2");
System.out.println(mget);
Jedis-API:List
//操作list
@Test
public void demo3() {
Jedis jedis = new Jedis("192.168.255.100", 6379);
jedis.lpush("key1", "lucy", "mary", "jack");
List<String> key1 = jedis.lrange("key1", 0, -1);
System.out.println(key1);
}
Jedis-API:Set
//操作set
@Test
public void demo4() {
Jedis jedis = new Jedis("192.168.255.100", 6379);
jedis.sadd("names", "lucy");
jedis.sadd("names", "mary");
Set<String> name = jedis.smembers("names");
System.out.println(name);
}
Jedis-API:Hash
//操作hash
@Test
public void demo5() {
Jedis jedis = new Jedis("192.168.255.100", 6379);
jedis.hset("users", "age", "20");
String hget = jedis.hget("users", "age");
System.out.println(hget);
}
事务和锁机制-基本操作
Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序的执行,事务在执行的过程中,不会被其他客户端发送来的命令请求所打断
Redis事务的主要作用就是串联多个命令防止别的命令插队
Multi、Exec、discard
从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行
组队的过程中可以通过discard来放弃组队
事务的基本操作
表示已经开启了事务
新建队列
执行
放弃
事务的错误处理
如果执行某个阶段命令报了错误,则只有报错的命令不会被执行,其他的命令都会执行,不会回滚
组队不会报错,执行会报错,因为incr是自增+1,组队就放进去了,但是v1不是数字就不能加1,所以执行的时候会报错
事务和锁机制-事务冲突(悲观锁和乐观锁)
为什么要做成事务
想想一个场景:有很多人有你的账户,同时去参加双十一抢购
事务冲突的问题
悲观锁
悲观锁(Pessimistic Lock),顾名思义,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁,传统的关系型数据库里面就用到了很多这样的锁机制,比如行锁、表锁等,读锁、写锁等,都是在操作之前先上锁
乐观锁
通过版本号进行操作
乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制,乐观锁适用于多读的应用类型,这样可以提高吞吐量,Redis就是利用这种check-and-set机制实现事务的
演示乐观锁和事务特性
WATCH key[key…]
在执行multi之前,先执行waych key1[key2],可以监视一个(或多个)key,如果在事务执行之前这个(或这些)key被其他命令所改动,那么事务将被打断
第一个:
第二个:
UNWATCH
取消WATCH命令对所有key的监视
如果在执行WATCH命令之后,EXEC命令或DISCARD命令先被执行了的话,那么就不需要在执行UNWATCH了
Redis事务三特性
- 单独的隔离操作
- 事务中的所有命令都会序列化、按顺序执行,事务在执行的过程中,不会被其他客户端发送来的命令请求所打断
- 没有隔离级别的概念
- 队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行
- 不保证原子性
- 事务中如果有一条命令执行失败,其后的命令仍然会被执行,不会回滚
Redis主从复制
主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主
Redis主从复制-搭建一主多从
先在根目录下创建一个文件夹并进入:
复制redis.conf配置文件到文件夹中
配置一主两从的,创建三个配置文件
命名为:redis6379.conf、edis6380.conf、edis6381.conf
从之前conf文件中把appendonly yes改为no
在myredis下创建文件vi redis.conf
在配置文件中写入内容
用同样的方式创建6380、6381
启动三台redis服务器
查看进程的方式
ps -ef | grep redis
三台服务器连接6379 80 81
查看三台主机运行情况
info replication
配从不配主
slaveof ip port
在6380 6381上执行:slaveof 127.0.0.1 6379
加上后就能作为当前6379里面的从机操作
注意:从机里面不能做写操作,只能做读操作
复制原理的一主二从
主机挂掉了,从机也只认这一个主机
主机重新启动还是主服务器
主从复制-薪火相传和反客为主
薪火相传
上一个slave可以是下一个slave的master,slave同样可以接收其他slaves的连接和同步请求,那么该slave作为了链条中下一个master可以有效减轻master的写压力,去中心化降低风险
用slaveof ip port
中途变更转向:会清除之前的数据,重新建立拷贝最新的
风险是一旦某个slave宕机,后面的slave都没法备份
主机挂了,从机还是从机,无法写数据了
从机之间连接
反客为主
用slaveof on one 将从机变成主机
哨兵模式
反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库(相当于一个谍报人员,从库加一个哨兵到主机,如果主机挂掉了,这个从库立马变为主机)
还原一主二从的搭建
自定义在/myredis 目录下新建sentinel.conf文件 名字绝不能错
sentinel monitor mymaster 127.0.0.1 6379 1
启动配置文件
redis-sentinel sentinel.conf
当主机挂掉,从机选举中产生新的主机
先挂掉主机
然后哨兵这边自动显示
故障恢复
偏移量最大的替代被挂掉的主机,偏移量就是主机挂掉之前和某一个从机同步量最高的
集群
Redis集群
容量不够,redis如何进行扩容?
并发写操作吗,redis如何分摊?
另外主从模式,薪火相传模式,主机宕机,导致ip地址发生变化,应用程序中配置需要修改对应的主机地址、端口等信息
之前通过代理主机来解决,但是redis3.0中提供了解决方案,就是无中心化集群配置
任何一台服务器都可以作为集群的入口,比如通过订单进入集群,正好访问的不是用户数据,那么订单该怎么做?订单吧请求转移给用户去操作,比如现在有个操作也不是用户做的,那么用户就转移给商品,商品转移给订单,可以互相进行访问,这就是无中心化集群
什么是集群
Redis集群实现了对Redis的水平扩容,既启动N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N
Redis集群通过分区(Partition)来提供一定程度的可用性;即使集群中有一部分节点失效或者无法进行通讯,集群也可以继续处理命令请求
搭建Redis集群
删除带有dump63的文件
rm -rf dump63*
更改redis6379的配置文件
复制出同样的几份分别为6380 6381 6389 6390 6391,并在文件里面做响应的修改
启动6379 6380 6381 6389 6390 6391配置文件
查询进程
ps -ef | grep redis
将六个节点合成一个集群
组合之前确保所有redis启动,nodex-xxxx.conf文件都生成正常
进入 /opt/redis/src目录下查找一个集成文件
在src目录下使用命令
redis-cli --cluster create --cluster-replicas 1 192.168.255.100:6379 192.168.255.100:6380 192.168.255.100:6381 192.168.255.100:6389 192.168.255.100:6390 192.168.255.100:6391
此处不要用127.0.0.1 请用真实ip地址
–replicas 1 采用最简单的方式配置集群,一个主机,一个从机,正好三组
默认分配三组各组的主机
回答yes
-c采用集群策略连接 设置数据会自动切换到相应的写主机
任何一个节点可以作为中心的入口,他们之间可以互相访问
cluster nodes查看节点信息
集群操作和故障恢复
redis cluster如何分配六个节点
一个集群至少要有三个主节点
选项 --cluster-replicas 1 表示我们希望为集群中的每个主节点创建一个从节点
分配原则尽量保证每个主数据库运行在不同的ip地址,每个从库和 主库不在一个ip地址上
什么是slots
一个Redis集群包含16384个插槽(hash slots),数据库中的每个键都属于这16384个插槽的其中一个
集群使用公式CRC16(key)%16384来计算键key属于那个槽,其中CRC16(key)语句用于计算键key的CRC16检验和
如刚刚集群搭建成功给了个插槽:16384 slots covered.
比如刚刚创建的集群就有提示,三个主机每个主机都给了提示的插槽范围
比如set了一个键值
其实插槽就是吧每个值分配到每个插槽里面分摊,让他平均分摊压力
例如随机进入一个主机,新增一个值:
set k1 v1那么她计算后有个提示,【12706】,但是不属于6379插槽范围内,所以他自动切换到6381中,然后往里面添加数据由他来完成
不能一次性添加多个值,他会无法添加
如果想添加多个值,就分成组的形式
查询集群中的值
cluster keyslot k1
查询插槽中有几个键
cluster countkeysinslot 4847
用值返回count个slot槽中的键
getkeysinslot 449 1
故障恢复
主机挂机后,从机替换主机成为主机
先挂掉主机
随意进一台其他的主机
查看状态(6379挂掉了替代的是6381)
重新打开一个服务器重启6379(可以看到取而代之的主机是6381,6379变为了从机)
Redis缓存穿透和雪崩
Redis缓存使用,极大提升了应用程序的性能和效率,特别是数据查询方面,但同时,它也带来了一些问题,其中,最要害的问题,就是数据的一致性问题,从严格意义上讲,这个问题无解,如果对数据的一致性要求很高,那么就不能使用缓存
另外的一些典型问题就是,缓存穿透、缓存雪崩和缓存击穿,目前,业界都有比较流行的解决方案
缓存穿透
概念
缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询,发现也没有,于是本次查询失败,当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库,这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透
解决方案
(1)布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力
(2)缓存空对象
当存储层不命中后,即使返回的空对象也将其缓存起来,同时还会设置一个过期时间,之后在访问这个数据将会从缓存中获取,保护了后端数据源
但是这两个方法也会存在两个问题:
1.如果空值能够被缓存起来,这就意味着需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键
2.即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口不一致,这对于需要保持一致性的业务会有影响
缓存击穿(量太大,缓存过期)
这里需要注意和缓存击穿的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏幕上凿开了一个洞
当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导致数据库瞬间压力过大
解决方案
设置热点数据永不过期
从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后产生的问题
加互斥锁
分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可,这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大
缓存雪崩
概念
缓存雪崩,是指在某一个时间段,缓存集中过期失效,Redis宕机
产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品比较集中的放入了缓存,假设缓存一个小时,那么到了凌晨一点钟的时候,这批商品的缓存就都过期了,而对这批商品的查询访问,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰,于是所有的请求都会达到存储层,存储层的调用量就会暴增,造成存储层也会挂掉的情况
其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网,因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的,无非就是对数据库产生周期性的压力而已,而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮
解决方案
Redis高可用
这个思想的含义是,既然Redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群
限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量,比如对某个key只允许一个线程查询和写缓存,其他线程等待
数据预热
数据加热的含义就是在正式部署之前,先把可能的数据先预先访问一边,这样部分可能大量访问的数据就会加载到缓存中,在即将发生大并发访问前手动触发加载缓存不同的kye,设置不同的过期时间,让缓存失效的时间点尽量均匀
Redis消息队列
为什么要用消息队列
消息队列的本质:
消息队列一种“先进先出”的数据结构
常见的应用场景:解耦、异步、削峰
解耦
系统的耦合性越高,容错性就越抵,以电商应用为例,用户创建订单后,如果耦合调用库存系统、物流系统、支付系统,任何一个子系统出了故障或者因为升级等原因暂时不可用,都会造成下单操作异常,影响用户使用体验
如何处理
使用MQ进行解耦,系统的耦合性就会提高了,比如物流系统发生故障,需要几分钟修复,这段时间内,物流系统要处理的数据被缓存到消息队列中,用户的下单操作正常完成,当物流系统回复后,补充处理存在消息队列中的订单消息即可,终端系统感知不到物流系统发生过几分钟故障
异步
A系统接收一个请求,需要在自己本地写库,还需要在B、C、D三个系统写库,自己本地写库要3ms,B、C、D三个系统分别写库要300ms、400ms、200ms,最终请求总延时是3+300+400+200=903ms,接近1s,用户非常不好,一般互联网类的企业,对于用户直接的操作,一般要求是每个请求都必须在200ms以内完成,对用户几乎是无感知的,如果用户通过浏览器发起请求,等待个1s,这几乎是不可接受的
如何处理
如果使用MQ,那么A系统连续发送3条消息到MQ队列中,假如耗时5ms,A系统从接受一个请求到返回响应给用户,总时长是3+5=8ms,对于用户而言,响应速度大大提升了,改善了用户的体验
流量削峰
秒杀,应用系统如果遇到系统请求流量瞬间猛增,有可能会将系统压垮,有了消息队列可以将大量请求缓存起来,分散到很长一段时间处理,这样可以大大提高系统的稳定性和用户体验
如何处理
一般情况,为了保证系统的稳定性,如果系统负载超过阈值,就会阻止用户请求,这会影响用户体验,而如果使用消息队列将请求缓存起来,等待系统处理完毕后通知用户下单完毕,这样总不能下单体验要好
redis中的订阅与发布
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息,redis客户端可以订阅任意数量的频道
1.pub/sub模式的消息通知,发布/订阅模式:
通过两个命令来实现pub/sub模式的消息通知
1.publish 主题名称 消息内容 (向指定主题中发送一条消息)
2.subscribe 主题名称 (订阅某一个主题)