redis数据结构:
- String 可以是字符串也可以是数字,以及浮点数
- List,一个链表,链表上每一个节点都包含一个字符串
- set 包含字符串的无序手机其,特点是每一个字符都是唯一的
- hash,包含键值对的无序散列,类似map
- ZSet,字符串成员,在set的基础上是顺序的,元素的顺序由分值来决定
- redis的发布与订阅的特性(重点):
- subscribe channel命令 订阅给定的一个或者多个频道
- pushlish channel message 给指定频道发送消息,
- 通过这两个命令redis可以实现发布与订阅功能
- redis的基本事务功能
- redis通过multi和exec命令来实现事务的特性,在执行multi命令之后,redis会将这个客户端之后发送的所有命令存储在一个队列中,直到接受到exec命令为止。然后会在不被打断的情况下执行这些操作
- redis键过期时间:使用expiration设置key存活的时间,之后自动删除
重要的数据结构
- hyperLogLog:基数计算概念:通常用来统计一个集合中不重复元素个数,比如用来统计网站的UV,或者网站搜索关键字的数量。最简单的办法是用给一个集合做统计,判断新词是否存在,然后添加存在两个问题,数据量会变很大,判断成本会变得很高。bitMap存储1 亿个数据大概要12M内存,redis中HyperLogLog(HLL)中只需要1K内存就能做到
- hyperLogLog原理(简析):将需要统计的数据进行HASH,得到每个数据对应二进制串,统计得到的二进制串中从低位开始第一次出现1的情况并且进行统计,统计每个key的这种1 的位置,让后用概率和统计的方法推到出统计和数据量之间的关系,HyperLogLog核心在于集合中每个实体对应的Hash串,通过这种统计来预估整体的量,可以大大减少内存的耗费,所以这种方式并没有存储所有的样本,而是通过样本估算的方式推算出总体大小。(数学原理 伯努力分布)
- GEO redis3.2中的新特性用来存储和获取经纬度,以及地理位置,还有位置距离计算等内容 如下操作
//设置信息
GEOADD beijing 113.2099647 23.593675 五道口 //GEOADD 地址 经度 纬度 地区
GEOADD beijing 113.2099643 23.593674 上地
//获取信息
GEOPOS beijing 上地
//获取指定信息地点 900m范围的地点
GEORADIUS beijing 113.2099647 23.593675 900 m
- redis modules:redis4.0 曾加了自定义模块的开发,类似于3.X版本中lua脚本的兼容,但是lua有一定学习量,在4.0中提供了一系列模块源码: https://github.com/RedisLabsModules/redex ,可以通过命令将对应的模块代码导入到redis中,就可以使用对应自定义命令
SADD admins Alice Bob xiaorui.cc
- BloomFilter:主要功能是用来做去重,我们可以用来对访问UV和购买UV的实时去重,具体原理如下:
- BloomFilter过滤器会将key做一次Hash,将hash后得到的int分布到一个大的Bit数组中对应的位,判断一个Key是否存在,只要看Hash对应的bit数组位置是否是1,1则被占用,0不被占用
- 而且redis2.0 以后就支持bigMap的操作,以及Bit操作中的AND,OR,NOT,XOR操作,bitMap支持232 大小,也就是512M,下标最大232-1,可以存2亿左右数据
- Redis Search:基于redis的搜索插件,可以用来做全局key值的模糊查询,并且可以兼容一部分通配符
- Redis-ML :用来做机器学习的
redis数据安全与性能保障
- redis提供两种不同持久化方式来将数据存储到硬盘:
- 快照模式(RDB)可以将存在某一个时间点上所有数据写入硬盘
- RDB模式通过配置信息来启动,并且设置触发的配置,比如save 60 10000 在最近一次快照算起,60秒以内有10000次写操作,自动执行一次BGSAVE命令做备份
- 快照生成一个rdb文件,可以拷贝,但是在心快照创建完毕之前,redis,系统,硬件有一个出现问题,那么redis将会丢失最近一次快照之后的所有写入数据。
- 可以手动创建快照:通过BGSAVE命令,redis会调用fork来创建一个子进程,他来完成写入,父子进程共享内存,直到父进程有写入操作的时候共享结束
- 只适合哪些可以容忍少部分数据丢失的业务场景
- rdb方式需要找到save配置的点,因为如果过于频繁会浪费资源,过于稀少可能丢失大量数据,我们一般做法是在测试机器上尽量模拟线上环境
- 执行BGSAVE会使redis停顿一定时间取决于系统以及硬件
- 追加文件模式(AOF),在执行写命令时候将这条命令复制到硬盘
- AOF方式也可以通过配置方式打开,他会将所有写命令追加到aof文件末尾,redis只需要执行一次aof文件就可以恢复所有记录。
- 配置写入频率:always:每个写入都同步aof文件到硬盘,效率极低。everysec:每一秒同步一次 no:让操作系统来决定何时同步。
- 一般使用everysec,每秒执行一次和不适用任何持久化的性能比差不多,这样可以保证做多丢失一秒的数据
- aof文件可能会变得很大甚至沾满硬盘,这时候redis重启需要执行AOF文件中所有命令这个执行时间会很长
- 可以通过向redis发送BGREWRITEAOF命令来移除荣誉的命令,来重写aof文件,但是如果文件太大,删除一个十几G的文件redis可能会处于挂起状态数秒
- 方式可以同时使用也可以单独,也可以不用
redis过期策略
- 惰性删除:当读/写一个已经过期的Key的时候,会发生惰性删除策略,直接删除这个Key,很明显,太被动了
- 定期删除:由于惰性删除策略无法保证冷数据被几及时删除,所以redis会定期主动淘汰一批已经过期的Key
- 主动删除:当前已有的数据内存超过MaxMemory的时候,触发自动清策略,该策略由启动参数的配置决定
- volatile-lru:从已经设置过期时间的数据集合中通过LRU算法淘汰数据,3.0以前的默认策略:
- volatile-ttl:从过期数据集合中挑选过期时间最小的数据删掉
- volatile-random:从已过期集合中所见删除数据
- allkey-lru:从所有数据聚合中通过lru删掉
- allkey-random:从所有数据集合中随机删除
- noenviction:禁止从内存中删除数据 3.0默认策略
- redis过期策略只在主库中进行所有很容易在从库中读到脏数据:以下方式避免
- 通过sca命令扫库,相当于访问这个key,充分发挥惰性删除,但是确定在于会在扫库的时候给数据库带来一定压力,所有需要找好时间点去执行。
- 升级redis到3.0版本,在3.0 以后的版本中修复了这个问题,可以在源码中看到,曾加了对key值的主从库的判断,然后key为空,择返回null
redis缓存遇到的问题
- 缓存穿透:一般缓存系统按照key去缓存查询,value不存在,就去后端系统查找,如果key对应的value不存在,并且这个key访问量巨大,这样会给后端DB造成很大的压力,这就叫缓存穿透:
- 解决方案一:对查询结果为空的情况也进行缓存一个null,之后如果insert了之后在清理这个null的缓存
- 对一定不存在的key进行过滤,放到一个足够大的bitMap中,不存在数据会被这个bitMap拦截
- 缓存雪崩:如果我们设置缓存时候在同一个时刻过期,导致某一个时刻缓存几乎失效,请求都装法DB,DB瞬时压力过大雪崩
- 解决方案:如果需要集中社会缓存过期时间,那么我们一般会在缓存失效时间的计算上加一个随机数,这样缓存到期时间就可以分散开
- 缓存击穿:对于设置了过期时间的key,如果这些key在某些时间点被高超并发访问,这个时候必须考虑呗击穿的问题,和雪崩区别在于这是某一个key,正好过期的时候高并发访问一个key
- 解决方案一:使用互斥锁,mutex key,就是在缓存失效的时候并不立马去load db,而是先使用缓存工具中某一些带成功操作返回值的操作,比如 redis的setNx实现的互斥锁,他去set一个mutex key,当操作返回成功之后在去load db并且设置到缓存中,如果返回失败,重试get方法,这样就可以只有一个请求到DB层,代码如下
public String get(String key) {
String value = redis.get(key);
if (value == null) { //代表缓存值过期
//设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db
if (redis.setnx(key_mutex, 1, 3 * 60) == 1) { //代表设置成功
value = db.get(key);
redis.set(key, value, expire_secs);
redis.del(key_mutex);
} else {
//这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可
sleep(50);
get(key); //重试
}
} else {
return value;
}
}
- 解决方案二:可以设置无过期时间,做逻辑过期,在redis上不做过期的设置,就不会出现这种情况,但是在value中设置一个过期的时间,在获取value之后判断是否过期,过期择重新load到cache,这样可能会有其他线程读到过期的数据,但是应该影响不大
redis 分布式锁
- redis中有一个命令 setNx(key存在时候设置值),成功返回1,可以用setNx来争夺锁,在通过expire来设置过期时间防止忘记释放,也可以直接通过redis.setnx(key_mutex, 1, 3 * 60),同时设置值与时间,如下jedisclient源码:
while(true) {
result = this.jedisCluster.set(lockKey, lockKey, "NX", "EX", 30L);
if("OK".equals(result)) {
return true;
}
try {
Thread.sleep(1000L);
} catch (InterruptedException var4) {
;
}
}
redis可能有的问题
- redis中有1亿个key,其中10万个是一样的前缀开头,如何全部找出来:
- 可以用keys命令,因为redis是单线程的,如果用keys会造成服务停顿
- 用scan遍历所有key,可以无阻塞的提取指定模式的列表,但是可能会有重复,时间更长
- Redis做异步消息队列:
- 用list结构可以,通过repush生产消息,lpop消费消息,没有消息可以sleep之后再做,或者用blpop命令,他会阻塞直到获取消息。
- 可以用pub/sub命令做消息队列并且可以实现一次消息消费多次 1:N
- 延迟队列可以用sort命令对一个set进行排序,排期的对象是时间戳,这样我们可以通过逻辑去匹配一定时间之前的消息。
- 大量的key需要设置同一时间过期一般需要注意缓存雪崩效应:
- 所有值统一时刻过期,导致直接到DB查询,可以通过随机值,分散过期的时间
- redis如何做数据持久化:
- rdb模式我们可以通过bgsave方式主动去触发,同时也做aof做持久化,bgsave会持续一段时间,不够实时性,如果机器有问题可能会丢比较多的数据,aof是记录写命令,这个丢失的数据比较少,在重启的时候,会用bgsave的文件进行数据恢复,然后在用aof来恢复没有备份到文件中的数据,这样做到最小丢失。
- rdb原理如上:redis通过fork出一个子进程来做bgsave操作,父子进程共享内存直到父进程有写操作,共享结束。
- redis同步机制:
- redis一般是主从同步,从从同步,master在同步时候,先bgsave一次,期间所有写入都在缓存buffer,然后将rdb文件同步到从节点,从节点loader到内存,通知主节点将缓存中的写入同步。
redis集群
- redis3.0 的新特性就是自带集群功能,3.0 下的集群没有中心节点,RedisCluster 将所有的key映射到16385个slot中,每一个redis实例负责一部分最少三个实例一个集群,业务程序通过任务一个redisCluster都可以访问所有的数据,如果数据不再这个节点,那么这个节点会引导客户端去对应节点获取信息,redisCluster的管理实通过节点之间的通讯完成,定期交互,定期更新
- 每一个节点都可以配置一个从节点,集群中三个redisCluster某个挂了,从节点将顶替他的位置,,主从复制的功能 和单机的时候一直,所以叫他基于主从配置的redis集群。