redis
redis持久化
redis开机的时候--->加载持久化文件(第一次开启的时候没有)--->启动了--->会写入一些数据
--->redis会在某一时刻把内存的数据写入磁盘(生成持久化文件)
1.RDB持久化原理
原理是redis会单独创建(fork)一个和当前线程一模一样的子进程来进行持久化,这个子进程的所有数据
(变量、环境变量、程序计数器等)和原进程一模一样,会先将数据写入到一个临时文件中,待持久化结束,
再用这个临时文件替换上次持久化好的文件,整个过程中,主进程不进行任何io操作,这就确保了较高的性能。
- 这个持久化文件在哪里?
- dir ./(默认情况下会根据启动的位置为准,最好配置一下)
- 产生dump.rdb文件
- 他什么时候fork子进程,或者什么时候出发rdb持久化机制?
- 1.shutdown时,如果没有开启aof,会触发。
- 2.配置文件中默认的快照配置
- save 900 1 (定时器900秒,有一个更改,会触发)
- save 300 100
- save 60 10000 (优化方案,将300、60的删掉;集群环境下,rdb是删不了的)
- 3.执行save(主进程执行,会阻塞),bgsave(fork的进程,后台异步执行快照)
- 4.执行flushall,把内存数据清空了,但里面是空的,无意义。(把磁盘的数据也清空,否则会丢失数据)
rdb缺点:
1.出现意外宕机,会出现数据丢失。因为无法触发rdb持久化机制。
2.AOF持久化原理
原理是将redis的操作日志以追加的方式写入文件,读操作是不记录的。
- 这持久化文件在哪里?
- appendonly yes 表示开启aof
- dir ./(默认情况下会根据启动的位置为准,最好配置一下)
- 产生appendonly.aof文件
- 触发机制(根据配置文件配置项)
- appendfsync no 表示等操作系统进行数据同步到磁盘(批量操作)(效率快,持久化没保证)
- appendfsync always 同步持久化,每次发生数据变更时,立即记录到磁盘(效率慢,安全)
- appendfsync everysec 表示每秒同步一次(默认值,很快,但可能会丢失一秒以内的数据)
- 数据走向流程:主进程 ---> 缓冲区 ---> aof文件(磁盘)
- 数据会先从主进程进入到缓冲区,最后才会写入到aof文件。
- no 表示数据进入到缓冲区,会批量等待,才会写入到aof文件
- always 表示数据进入到缓冲区,立马就写入到aof文件
- everysec 表示数据进入到缓冲区,每秒执行一次,写入到aof文件
- aof重写机制(解决aof不断变大问题)
- 重写,会fork子进程。根据当前内存数据,生成aof快照。
- redis4.0开启混合持久化,是对重写的进一步优化
- aof-use-rdb-preamble yes(是否开启重写aof文件机制)
- 手动执行命令:bgrewriteaof,(以rdb的方式的数据格式保存到aof文件中)
- 自动触发重写:
# 增长比例(100%)
auto-aof-rewrite-percentage 100
# 当aof文件增长到一定大小时,redis会调用bgrewriteaof对日志文件进行重写。
auto-aof-rewrite-min-size 64mb(优化点,生产环境必改,一般5G以上)
例如:
第一次触发:64mb ,第二次触发:64mb + 64mb * 100% = 128mb
总结
类型 | 是否fork子进程 | 缺点 | 优点 |
---|---|---|---|
rdb | 会fork子进程 | 可能会丢失最后一次写入的数据 | 启动redis时,从磁盘加载持久化数据非常快 |
aof | 不会fork子进程 | 最多丢失不会超过2秒的数据 | 启动redis时,从磁盘加载持久化数据不如rdb |
aof重写 | 会fork子进程 |
redis缓存问题
1.缓存穿透
指查询数据库和缓存中都没有存在的数据
解决方法:
- 1.缓存空对象 (代码简单、效果不好)
- 缺点:
- 1.只能针对相同的key进行限制,无法对大量的key进行限制。
- 2.会导致redis中,存在大量的空数据问题,占用redis内存。
//从缓存中获取
obj = redis.getkey(id);
if(obj != null){
//(缓存空对象)
if(obj instanceOf 空对象){
return "查无数据";
}
return "查询成功"+obj;
}
try{
//从数据库中获取
obj = dao.select(id);
if(obj != null){
redis.setKey(id, obj);
return "查询成功"+obj;
}else{
//(缓存空对象)
redis.setKey(id, 空对象);
}
}
return "查无数据";
- 2.布隆过滤器 (代码复杂、效果很好)
//布隆过滤器
if (!bloomFilter.mightContain(id)) {
//不存在
return "查无数据";
}
//从缓存中获取
obj = redis.getkey(id);
if(obj != null){
return "查询成功"+obj;
}
try{
//从数据库中获取
obj = dao.select(id);
if(obj != null){
//加入布隆过滤器
bloomFilter.put(id);
return "查询成功"+obj;
}
}
return "查无数据";
- 缺点
- 1.布隆过滤器维护比较麻烦,不能删除,只能重构一个新的。
- 2.布隆过滤器需要先初始化,不然第一次查询都不存在了。
- 原理
一个bit位数组,1个key会通过多个hash算法得到hash值,存到bit位数组中;那就可能出现当1个不存在的key,通过
多个hash算法得到的hash值,对应的index上面有数据,那就可能出现误判的情况。
- 分布式布隆过滤器
//guava依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>27.0.1-jre</version>
</dependency>
private static int size = 1000000;
/**
* size 预计要插入多少数据
* fpp 容错率--->出现误判的概率是多少
* list 创建的是object数组
* bit 数组
*
* 位数组 21亿 JVM内存 数据不会进行持久化 256M
* redis 42亿 redis内存 redis的持久化数据 512M
*/
private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, 0.001);
public static void main(String[] args) {
for (int i = 1; i <= size; i++) {
bloomFilter.put(i);
}
List<Integer> list = new ArrayList<>(10000);
//故意取10000个不存在过滤里的值,看看有多少个会被认为在过滤器里
for (int i = size + 10000; i < size + 20000; i++) {
if (bloomFilter.mightContain(i)) {
//误判(明明不存在,误判为存在)
list.add(i);
}
}
System.out.println("误判数量:" + list.size());
}
2.缓存穿透
指数据库中有数据,缓存没有(这条数据没人访问过;数据刚好失效)。并发访问会导致所有查询都访问数据库
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
我们知道,使用缓存,如果获取不到,才会去数据库里获取。但是如果是热点 key,访问量非常的大,数据库在重建缓存的时候,会出现很多线程同时重建的情况。因为高并发导致的大量热点的 key 在重建还没完成的时候,不断被重建缓存的过程,由于大量线程都去做重建缓存工作,导致服务器拖慢的情况。
解决方法:
- 1.互斥锁
第一次获取缓存的时候,加一个锁,然后查询数据库,接着是重建缓存。这个时候,另外一个请求又过来获取缓存,发现有个锁,这个时候就去等待,之后都是一次等待的过程,直到重建完成以后,锁解除后再次获取缓存命中。
public String getKey(String key){
String value = redis.get(key);
if(value == null){
String mutexKey = "mutex:key:"+key; //设置互斥锁的key
if(redis.set(mutexKey,"1","ex 180","nx")){ //给这个key上一把锁,ex表示只有一个线程能执行,过期时间为180秒
value = db.get(key);
redis.set(key,value);
redis.delete(mutexKety);
}else{
// 其他的线程休息100毫秒后重试
Thread.sleep(100);
getKey(key);
}
}
return value;
}
互斥锁的优点是思路非常简单,具有一致性,但是互斥锁也有一定的问题,就是大量线程在等待的问题。存在死锁的可能性。
- 2.分布式锁
可以使用redisson框架提供的redis分布式锁
3.缓存雪崩问题
缓存雪崩是指机器宕机或在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。
解决方法:
- 1.在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
- 2.做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期
- 3.不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
- 4.如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
4.缓存与数据库数据一致性问题
5.缓存粒度控制
通俗来讲,缓存粒度问题就是我们在使用缓存时,是将所有数据缓存还是缓存部分数据?
数据类型 | 通用性 | 空间占用(内存空间+网络码率) | 代码维护 |
---|---|---|---|
全部数据 | 高 | 大 | 简单 |
部分数据 | 低 | 小 | 较为复杂 |
缓存粒度问题是一个容易被忽视的问题,如果使用不当,可能会造成很多无用空间的浪费,可能会造成网络带宽的浪费,可能会造成代码通用性较差等情况,必须学会综合数据通用性、空间占用比、代码维护性 三点评估取舍因素权衡使用。
redis集群搭建
1.安装好redis到/opt/myredis/redis下面
2.新建redis7000,redis7001,redis7002,redis7003,redis7004,redis7005文件夹
3.将redis.conf文件复制在redis7000下
4.分别修改个目录下的redis.conf文件
vi redis7000/redis.conf
1.bind 127.0.0.1 指定本机ip(内网ip)
2.port 7000
3.daemonize yes
4.pidfile /opt/myredis/redis7000/redis_7000.pid
5.logfile /opt/myredis/redis7000/redis_7000.log
6.dir /opt/myredis/redis7000
7.requirepass 123456
(集群环境参数)
8.cluster-enabled yes(启动集群模式)
9.cluster-config-file nodes-7000.conf(这里800x最好和port对应上)
10.cluster-node-timeout 15000
11.appendonly yes
4.把redis7000/redis.conf文件复制到redis7001,redis7002,redis7003,redis7004,redis7005下
将redis7000/redis.conf文件复制到redis7001下,并同时将7000字符批量替换成70001
sed 's/7000/7001/g' redis7000/redis.conf > redis7001/redis.conf
sed 's/7000/7002/g' redis7000/redis.conf > redis7002/redis.conf
sed 's/7000/7003/g' redis7000/redis.conf > redis7003/redis.conf
sed 's/7000/7004/g' redis7000/redis.conf > redis7004/redis.conf
sed 's/7000/7005/g' redis7000/redis.conf > redis7005/redis.conf
5.分别启动7000,7001,7002,7003,7004,7005实例
/opt/myredis/redis/bin/redis-server /opt/myredis/redis7000/redis.conf
/opt/myredis/redis/bin/redis-server /opt/myredis/redis7001/redis.conf
/opt/myredis/redis/bin/redis-server /opt/myredis/redis7002/redis.conf
/opt/myredis/redis/bin/redis-server /opt/myredis/redis7003/redis.conf
/opt/myredis/redis/bin/redis-server /opt/myredis/redis7004/redis.conf
/opt/myredis/redis/bin/redis-server /opt/myredis/redis7005/redis.conf
#查看是否启动成功
ps -ef | grep redis
6.构建集群,主从分配并指派槽位
/opt/myredis/redis/bin/redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003
127.0.0.1:7004 127.0.0.1:7005 --cluster-replicas 1
这里有6个ip,表示前3个ip为主节点,后面3个为从节点
1,表示主从比例为1:1(3:3)
2,表示主从比例为2:4(2:4),但是主节点必须要有3个以上,那这里就会报错
这里的槽位是平均分配:16384
7.验证集群
1.连接任意一个客户端即可:(-c是redis-cli可以帮我们自动计算槽位,并进行重定向到对应的redis)
/opt/myredis/redis/bin/redis-cli -c -h 127.0.0.1 -p 7000 (-c表示集群模式,指定ip地址和端口号)
2.进行验证:
cluster info(查看集群信息)、cluster nodes(查看节点列表)
3.进行数据操作验证
set key llsydn
4.关闭集群则需要逐个进行关闭,使用命令:
/opt/myredis/redis/bin/redis-cli -c -h 127.0.0.1 -p 700* shutdown
redis内容扩展
1.Pipeline
注意:使用Pipeline的操作是非原子操作
2.GEO
GEOADD locations 116.419217 39.921133 beijin
GEOPOS locations beijin
GEODIST locations tianjin beijin km 计算距离
GEORADIUSBYMEMBER locations beijin 150 km 通过距离计算城市
注意:没有删除命令 它的本质是zset (type locations)
所以可以使用zrem key member 删除元素
zrange key 0 -1 表示所有 返回指定集合中所有value
3.hyperLogLog
Redis 在 2.8.9 版本添加了 HyperLogLog 结构。
Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
PFADD 2017_03_06:taibai 'yes' 'yes' 'yes' 'yes' 'no'
PFCOUNT 2017_03_06:taibai 统计有多少不同的值
1.PFADD 2017_09_08:taibai uuid9 uuid10 uu11
2.PFMERGE 2016_03_06:taibai 2017_09_08:taibai 合并
注意:本质还是字符串 ,有容错率,官方数据是0.81%
4.bitmaps
setbit taibai 5000 0 (将key为taibai的5000bit位设置为0)
getbit taibai 5000 (将key为taibai的5000bit位的数据)
bitcount taibai (统计有多少个1)
Bitmap本质是string,是一串连续的2进制数字(0或1),每一位所在的位置为偏移(offset)。
string(Bitmap)最大长度是512 MB,所以它们可以表示2 ^ 32=4294967296个不同的位。