文章目录
1.非关系型数据库基础
1)什么是NoSQL?
解决高并发和磁盘读写慢的问题,成千上万的读写操作,
2)NoSQL的分类
a) 基于键值列
b) 列存储数据库
c) 图形数据库
d) 文档型数据库
2.redis简介
1)简介:
开源、c语言编写、高性能的数据存储结构。
2)redis的特性
1)支持数据的持久化存储
2)redis不仅支持简单的key-value类型的数据,还支持list,set,zset,hash等数据类型的存储
3)支持数据的备份,即master-slave模式的数据备份
4)默认支持16个数据库
1)redis的优点
1.读写性能极高
2.丰富的数据类型,支持二进制案例的string
3.原子性
4.丰富的特性,支持publish/subscribe,通知,key过期等特性
2) 常用的命令
a) set k1 100 //设置key-value
get ki //获得key对应的value值
// “100” 打印出来
b) del k1 //删除key值k1
1 //表示删除成功
0 //表示删除失败
c) key * 可以把当前数据库的key值都打出来
d) dbsize
//6 返回当前数据库key的数量
e) key k?
打印k1,k2,k3,k8,k7 ,?表示通配符一个字符
f) flu TAB补全命令
FLUSHdb 把当前数据库key值清零
flushall 清空所有数据库
g) move key [num] 把key从当前数据库移动到目标数据库
h) 设置过期时间
expire k1 10
ttl k1 看时间,-2表示过期,数据没有了;-1表示永不过期
j) type k3 看key的类型
//string
3) 常用数据类型
stl底层结构用红黑树,这里的set底层用哈希表
a) string
//k1 string1
getrange k1 0 2 //要是end是-1,则表示到结尾
“str”
setrange k1 0 he
hering1 //st被覆盖
setrange k1 0 helloworldwuhan
“helloworldwuhan”
mget k1 k2 //获取多个key对应的value值
“hellowordwuhan”
100
getset k1 12345 //把k1对应的value值改为12345,之前为100,打印一次
“100”
setex k1 10 100
get k1
“100”
ttl k1
4
ttl k1
-2
incr k1 //把value值自增1
11
INCRBY k1 100 //value值加100,value值必须是整数integer
115
b )list
lpush头插法,rpush尾插法
lset 可以通过下标设置value值
lrem 去掉list的5个2(去除多个重复的元素)
ltrim 修建list,范围内的数据保存下来
linsert根据value值前后插入元素
c ) set
smembers 遍历打印set
scard返回元素个数
smove把集合1的7移动到集合2
srandmember set1 2 //从集合中随机抽两个元素
用于抽奖很简单
SPOP随机移除集合中的元素
SDIFF key1 key2 出现在key1中,但是不在key2中的元素
sinter取交集,sunion取并集
d) sorted set
20是权重
(表示开区间,(10,20 大于10小于20
zrange打印
[表示大于等于str1,小于等于str2,要有边界条件
limit 0表示从第一个开始,1表示几个数据
-表示正无穷,+表示正无穷
zscore zset str1 返回权重
zrevrangebyscore 反序排列,前面填最大,后面填最小的
e) hash
设置键值对
获取性别等信息
hgetall打印所有key和value
只打印key值和只打印value值
f)配置信息
cd /etc/redis/
cd redis/
#或–注释
进入6379.conf
启动redis启动界面(默认启动config)
sudo redis-server /tec/redis/6379.conf
3.redis持久化方式
0)概念:
定期将redis中的数据以某种形式(数据或命令)存到硬盘
1)RDB:将当前数据保存到硬盘
2)AOF:将每次执行的写命令保存到硬盘
与RDB互为优缺点,对数据的完整性和一致性高
配置文件更新,更新到dump.rdb文件
60秒内执行了1W次,自动触发一次save操作(两个条件要求同时达到)
AOF配置修改:
appendonly yes表示开启同步
appendfsync everysec比appendfsync alwaysCPU利用率降低很好多,同步到硬盘
AOF会将相同的命令转化
set k1 1
incr k1 incr k1 incr k1 …100次
AOF优化成——》set k1 101 ,就是重写,以命令的形式往后面写
两个条件:大于64M且是上一次的两倍大小,两个条件同时满足才能触发重写
64表示64字节
2)redis持久化方式选择
两种都存在,优先使用aof方式
问题:Redis 的持久化机制是什么?各自的优缺点?
1)概述:redis提供两种持久化机制RDB(默认)和AOF
2)RDB:按照一定时间将内粗拿到数据以快照的形式保存到硬盘中,对应产生的数据为dump.rdb。通过配置文件中的save参数来定义快照的周期
优点:
- 1.只有一个文件dump.rdb,方便持久化。
- 2.容灾性好,一个文件可以保存到安全的磁盘。
- 3.性能最大化,fork子进程来完成写操作,让主进程继续处理命令,所以是IO最大化。使用单独子进程来进行持久化,主进程不会进行任何IO操作,保证了redis的高性能
- 4.相对于大数据集时,比AOF的启动效率更高
3)AOF:持久化,将redis执行的每次写命令记录到单独的日志文件中,当重启redis会重新将持久化的日志中文件恢复数据
优点:
- 1.数据安全,aof持久化可以配置appendfsync属性,有always,每进行一次命令操作就记录到aof文件中一次。
- 2.通过append模式写文件,计时中途服务器宕机,可以通过redis-check-aof工具解决数据一致性问题
- 3.AOF机制的rewrite模式。AOF文件没被rewrite之间(文件过大会对命令去重后合并)
缺点:
- 1、AOF文件比RDB文件大,且恢复速度慢
- 2、数据集大的时候,比rdb启动效率低
4)AOF和RDB区别
- AOF文件比RDB更新频率更高,优先使用AOF还原数据
- AOF比RDB更安全但占用空间更大
- RDB性能比AOF好
- 两个都装配了,优先加载AOF恢复
4.redis事务
mysql的事务具有原子性,redis不具有原子性,不具有语法上面的错误
看不出来运行时出错,但能看的出来编译时出错,redis事务本身不具有原子性
watch监视数据,打断事务
乐观锁和悲观锁
比较版本号,相同就加1
5.redis主从复制
1)redis不存在主主复制,其他和mysql一致
2)配置
①把6379.conf里面的6379改成你要改变的端口
②启动配置文件
sudo redis-server /etc/redis/6379.conf
ps -elf | grep 6379
分别启动三个配置文件
③redis-cli
redis-cli -p 6380 //进入6380和6381
redis-cli -p 6381
默认情况下以认为自己为主机启动
④寻找主机
slaveof host port
例如:SLAVEOF 127.0.0.1 6379
6380的主机为6379
6.哨兵模式(Sentinel)
问题解决:解决主redis故障后,无人当master的问题
主机故障后,有一个从机能够成为新的主机,
1)配置命令
//监控6379,1个哨兵
sentinel monitor master6379 127.0.0.1 6379 1
2)哨兵启动
7.redis常见面试问题
1)缓存雪崩
a) 原因:大量热点数据请求ttl失效,内存找不到,就去磁盘里面去找,服务器就会崩
b) 业务场景举例:假设电商首页的key失效时间都是12小时,中午12点刷新的,我零点有个秒杀活动,假设当时每秒6000个请求,本来缓存可以抗住每秒5000个请求,但是缓存当时所有的key都失效了。此时1秒6000个请求全部落在数据库,数据库可能没反应过来就挂了,DBA重启数据库,但是数据库立马又被新的流量打死了
c) 解决:
①如果Redis是集群部署,将热点数据均匀分布在不同的Redis库中也能避免全部失效的问题,不过本渣我在生产环境中操作集群的时候,单个服务都是对应的单个Redis分片,是为了方便数据的管理,但是也同样有了可能会失效这样的弊端,失效时间随机是个好策略;
②在批量往Redis存数据的时候,把每个key的失效时间都加个随机值就好了,这样可以保证少数据不会在同一时间大面积失效;
代码:
setRedis(Key,value,time + Math.random() * 10000);
③设置热点数据永远不过期,有更新操作就更新缓存就好了(比如运维更新了首页商品,那你刷下缓存就完事了,不要设置过期时间),电商首页的数据也可以用这个操作,保险
2)缓存击穿
原因:当某一个热点非常热点,不停的扛着大并发,大并发集中对这个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库
解决方案:
①延长热点key的过期时间或设置永不过期,如排行榜,首页等;
②利用互斥锁保证同一时刻只有一个客户端可以查询底层数据的这个数据,如果获取失败则休眠一段时间(200ms)再去获取,如果获取成功则释放锁读redis,避免其他大量请求同时穿过Redis访问底层数据库
代码
/**
* 获取数据
* @param Key 查询参数
* @return data 数据
* @throws InterruptedException 异常
* @author 敖丙
*/
public static String getData(String Key) throws InterruptedException {
// 从redis查询数据
String result = getDataByKV(Key);
// 参数校验
if (StringUtils.isBlank(result)) {
// 获取锁
if (reenLock.tryLock()) {
// 去数据库查询
result = getDataByDB(Key);
// 校验
if (StringUtils.isNotBlank(result)) {
// 搞进缓存
setDataToKV(Key, result);
}
// !!!释放锁 正常会在finally里面释放
reenLock.unLock();
} else {
// 睡一会再拿
Thread.sleep(100L);
result = getData(Key);
}
}
return result;
}
// 这里面的锁都是单机玩玩,分布式锁还是得靠lua脚本这样的
3)缓存穿透
a) 原因:要查询的数据不存在,缓存无法命中,硬盘数据库也没有要查询的数据,也就不会记录到缓存中,这样每次对该数据的查询都会穿过缓存区查询一次数据库
b) 业务场景举例:
而用户不断发起请求,我们数据库的 id 都是1开始自增上去的,如发起为id值为 -1 的数据或 id 为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大,严重会击垮数据库
c) 解决方案:
①参数做校验,数据库id都是大于0的,对查询做一些校验和过滤,不合法的参数或查询,直接返回
②缓存空对象,如果数据库不存在这个数据,我们在缓存中也保存这个key,只是把redis的val值记录为“不存在,空”,也就是<key,nil>并设置过期时间,下次访问就不会做无用的查找了
③可以预先将数据库的key都存在一个map里面,然后用布隆过滤器过滤掉哪些不存在的key,不存在你return就好了,存在你就去查了DB刷新KV再return,可以明确知道某个key一定不存在,那些key可能存在
技术总结
一般避免以上情况发生我们从三个时间段去分析下:
1)事前:Redis 高可用,主从+哨兵,Redis cluster,避免全盘崩溃。
2)事中:本地 ehcache 缓存 + Hystrix 限流+降级,避免MySQL 被打死。
3)事后:Redis 持久化 RDB+AOF,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。
上面的几点我会在吊打系列Redis篇全部讲一下这个月应该可以吧Redis更完,限流组件,可以设置每秒的请求,有多少能通过组件,剩余的未通过的请求,怎么办?走降级!可以返回一些默认的值,或者友情提示,或者空白的值。