redis的value具有类型且有一些本地方法,所以相比memcached可以调用本地方法,移动计算不用移动数据。redis的value有类型且有操作方法,所以可以将redis的数据类型作为集合操作,将java的操作移动到redis,实现java无状态。
Redis是单线程的,5.0之后worker线程时单线程的;但是io线程可以多线程的
五大数据类型
1、String
可以存字符串,进行数值计算,还可以存二进制位 bitmap(setbit),进行二进制计算(bitop),统计二进制数(bitcount);
getbit key offset,setbit key offset value,
bitop(“AND”, ‘new_name’, ‘n1’, ‘n2’, ‘n3’) 获取n1、n2、n3进行与计算结果保存在new_name
bitcount(‘key’) 统计字符串的二进制码有多少个1(可以指定offset)
incr( name, amount=1) 自增 name 对应的值,当 name 不存在时,则创建 name=amount,否则,则自增。可用于页面点击计数等功能
2、List
list里面是一个循环链表有lpush,rpush,lpop,rpop命令,可以组成栈和队列。还可以根据下标取元素(lindex、rindex),可以当数组用。还可以截取元素(ltrim)
3、hash
存储map,用来存sparkstreaming中用来存kafka某个topic某个groupid对应的多个partition的偏移量。hset、hget(存取一个key),hmset,hmget(存取多个key),hkeys,hvals
4、set
集合,无序;可以用来去重或者做多个结合的交并差集。
5、zset
集合,有序。内部实现是ziplist或者skiplist;
ZADD key score1 member1 [score2 member2]
-
当元素数量小于128个或者所有member的长度都小于64字节的时候使用ziplist,其他情况使用跳表排序(skiplist)
每插入一个元素的时候我们通过一个随机函数获得该元素的层高,然后将元素插入
-
ziplist:类似于数组,开辟一个连续的空间存储数据。但是,不同的是ziplist的每个节点大小不一样,按照数据大小分配节点空间,节约了内存。数据节点包括三个部分,分别是前一个节点的长度prev_entry_len,当前数据类型和编码格式encoding,具体数据指针value。
6、Stream(5.x之后新增的)
用于实现消息队列,redis实现消息队列也可以通过PUB/SUB,订阅/发布模式、基于List的 LPUSH+BRPOP 的实现。
CAP原则
分布式系统中,一致性(Consistency),可用性(Availability),分区容错性(Partition tolerance)不可能全部保障,最多只能保证两个
从客户端的角度看
强一致性:读取到的数据总是最新的。
弱一致性:读取到的数据不一定是最新的。
最终一致性:属于弱一致性,不同的是,最终一致性的系统会在后台异步地更新所有的备份,所以最终所有的备份都会是最新的数据。
分布式事务 2PC 3PC TCC
- 2PC:分为准备、提交两个阶段。准备阶段协调者会给各参与者发送准备命令,你可以把准备命令理解成除了提交事务之外啥事都做完了。当接收到所有参与者的确认信息之后,发生提交命令。如果有失败的,就发送回滚命令。
存在的问题:协调者在发送提交命令之前就挂了,这会造成参与者长时间阻塞。
- 3PC:准备阶段、预提交、提交。相比于 2PC 它在参与者中也引入了超时机制,并且新增了一个阶段使得参与者可以利用这一个阶段统一各自的状态。由于引入了准备阶段(询问阶段)和超时机制(协调者、参与者),极大避免了2PC协调者、参与者长时间的阻塞问题。
超时机制:协调组和参与者都有超时机制;协调组一段时间没有收到参与者的确认就会回滚,而3pc的参与者在提交阶段如果超时没有收到协调者的确认的话,可以直接提交事务。因为,3pc的准备阶段已经成功,所以可以认为所有参与者都准备好了的,所以第三阶段如果没接到协调者的Commit命令可以认为自己就是没收到,其他人收到了,大家都会提交(这是一个概率问题,并不是说参与者此时提交一定是正确的,只是高概率是正确的)。 - TCC:TCC本质就是2PC在应用层上的实现,解决跨应用的业务操作原子性要求。比如支付操作,支付系统可能会调用余额系统和红包系统两个系统,要求余额系统和红包系统同时成功或者同时失败。
1、Try:尝试执行业务。 完成所有业务检查(一致性)预留必须业务资源(准隔离性)
2、Confirm:确认执行业务。 真正执行业务 不做任何业务检查 只使用Try阶段预留的业务资源
3、Cancel:取消执行业务 释放Try阶段预留的业务资源
两阶段提交协议中所存在的长时间阻塞状态发生的几率还是非常低的,所以虽然三阶段提交协议相对于两阶段提交协议对于数据强一致性更有保障,但是因为效率问题,两阶段提交协议在实际系统中反而更加受宠。
redis持久化
两种方式:RDB快照,AOF日志文件,默认开启RDB;
RDB快照:对当前时间点全量数据缓存,之后恢复速度快;
redis会创建一个fork子进程,将数据写到临时文件中,持久化结束在用这个临时文件替代上次持久化好的文件,整个过程主进程不进行任何的io操作,确保了性能。
RDB执行时机
1、在shutdown时如果没有开启AOF就会启动fork进程进行RDB持久化
2、通过配置文件指定在多长时间内操作了多少次修改就会触发
3.、执行save命令或者bgsave命令。save命令是主线程执行持久化,会阻塞客户端请求;bgsave后台fork进程持久化,不阻塞客户端请求。
AOF日志文件:增量自读文件,开启AOF后,恢复数据只读AOF日志文件,且会降低redis的效率;之前版本,AOF是自读的增量文件,所以久了文件会很大,redis会对日志文件重写,将多条命令合并成一条等价命令;4.x版本之后,改为了混合持久化,AOF文件开启的时候会把RDB一起写到AOF日志文件中。
混合持久化:通过bgrewriteaof命令完成,fork子进程将内存中的副本全量以RDB的形式写入aof文件,之后再将父进程的增量命令以aof方式写入到文件。
优点:由于绝大部分都是RDB格式,加载速度跟快,同时结合aof,数据丢失更少
AOF触发时机
AOF重写触发时机(bgrewriteaof)
1、通过配置文件配置aof文件增长率大于某一值触发
2、通过配置文件配置aof文件文件大小大于某一值触发
Redis缓存淘汰机制
当redis内存超出物理内存限制时,内存数据会频繁和磁盘交互(swap)。交换会让redis的性能急剧下降。
通过设置maxmemory
参数限制redis使用内存超出期望值。当实际内存超出maxmemory
参数限制时,redis会根据以下几种机制进行缓存淘汰。
- noeviction 可以响应读请求,不会响应写请求,可以保证数据不丢失,默认淘汰策略。
- volatile-lru 通过LRU算法淘汰设置了过期时间的key中最近最少使用的key,不会淘汰没有设置过期时间的key,保证持久化的数据不会突然丢失
- volatile-ttl 根据key 的剩余寿命 ttl 的值,ttl (剩余时间)越小越优先被淘汰(只淘汰设置了过期时间的key)。
- volatile-random 随机淘汰设置了过期时间的key
- allkeys-lru 对所有key使用LRU进行淘汰
- allkeys-random 对所有key随机淘汰
过期Key清理策略
三种策略:被动清理、主动清理、使用内存超过maxmemory时触发主动清理。redis根据这三种策略删除过期的key。
- 被动清理:当读写一个过期的key的时候,再进行清理
- 主动清理:定期抽样淘汰一批过期的key。设置
hz
参数指定定期清理频率,默认每秒触发10次。每次随机测试100个设置了过期时间的key,删除发现的已过期的key,如果删除数量超过25个则重复执行。 - 内存使用操出maxmemory限制时,触发主动清理策略
redis主从复制
redis是单机程序,所以存在单点故障和压力过大的问题,通过主从复制搭建多台从机,解决了单点故障问题(哨兵负责故障切换);同时主机负责写操作从机负责读操作(读写分离),降低了redis压力。
强一致性:主机收到数据后,将数据转发到从机,当收到所有从机的响应后再向提交客户端。数据不会丢失,但是可用性会大大降低
弱一致性:主机收到数据后就像客户端进行响应,之后再想从机同步数据。数据可能会丢失,但是可用性得到了保障。redis默认使用弱一致性,所以redis的分布式锁并不完全可靠。
最终一致性:
redis扩容
将key散列到多太redis上(既将原来的数据进行了切片)。每个redis都维护一个槽位和redis的映射关系,每一台redis上都已一个算法决定key应该放到那个槽位(分区)上。当客户端请求到一台redis,计算出key对应的槽位(crc16哈希算法),就将数据存储到槽位对应的redis上,实现了redis的扩容。每一个redis又可以搭建一台从机提高容灾性。
当redis再次扩容的时候,就重新分配槽位(分区),将分区数据迁移到新的redis上。(如果10个槽位-分区,开始两台redis,各种维护5个槽位,假设加入一台后,新的redis维护3,7分区,就只迁出3,7分区的数据到新的redis上)
所以向redis存储的时候尽量不要使用很大的key,因为一个大key在扩容的时候也不能被打散到多个槽位上。尽量将大key转化为多个小key,这样扩容的时候可以分散到多台redis上。
Redis中的一致性hash
为了维护槽位和各个redis实例之间的映射关系,redis引入了一致性hash算法。
一致性hash算法,相对于普通的hash算法,减少了集群扩缩容时的影响。
通过将各个redis实例的IP地址hash后,得到给每个实例在hash环上的位置,hash值后的位置都属于当前节点的槽位。当集群扩容缩容的时候,只会影响到该节点附近的一个节点,对其他节点的hash结果不影响。
搭建集群模式
配置文件改为集群模式,启动多个redis进程,使用命令 ./redis-cli --cluster create host1:port1 host1:port1 --cluster-replicas n 构建集群,分配主从,分配槽位(16384固定的)
redis集群故障转移:当一个节点A超过一定时间(
cluster-node-timeout
)未收到节点B对ping包的响应时,节点A会将ping包标记为pfail。当大多数的master节点确认B为pfail,就会将B标记为fail。fail状态的节点需要进行主从切换。
连接集群:
客户端提供了连接集群的命令,加上-c使用集群模式连接,set可以重定向,jedis没有重定向功能
扩容:加入新redisnode,分配槽位
缓存穿透:指查询一条数据库没有,缓存也没有的数据。查询就会绕过缓存,多次打到数据库上。
解决方案:1、缓存空对象 (代码简单,效果不太好)2、布隆过滤器(代码维护复杂,效果好)
布隆过滤器:用于快速判某个元素是否存在于redis中,底层是一个bit[],只能表示0/1(不保存真实数据,所以过滤器很小)。put值的时候使用多个hash函数散列,将对于位置设为1。存在一定的误判率。
可以使用redis的bitmap实现布隆功率器
缓存击穿
指并发查询一条数据库有,缓存没有的数据(没人访问这条数据或者 刚好缓存过期) 热点数据
解决方案:对查询数据库之前加锁,将并发访问串行化。加锁之后再次查询缓存(双重判断缓存),降低了数据库的访问量。虽然加了锁,但是并发访问只会查询一次数据库,效率很快。
缓存雪崩
redis宕机 大部分数据失效
规避雪崩:redis高可用集群,错开数据过期时间
如果雪崩出现:熔断 限流
Redis去重方式
- 利用redis的set结构进行去重。缺点:需要保存大量数据,消耗内存
- 利用redis的bitmap实现bloomFilter实现去重(bitset,bitget)。
- 通过redis的hll数据结构进行去重(pdadd,pfcount)。redis HLL数据结构进行近似去重
Redis大key解决方案
由于redis是一个work线程,大key会导致长时间阻塞其他请求,降低redis的性能。
大key的解决思路就是将一个大key,拆分成多个key。这样可以将读写压力分散到多个redis实例上。
单key 大value的情况,可以将单key拆分为多个key,然后使用mutilGet的方式读写。
set、hash、list类的大key,可以在客户端对key进行拆分。
newHashKey = hashKey + ( hash(field) % 10000); hset (newHashKey, field, value) ; hget(newHashKey, field)
redis pipeLine操作
pipeLine操作在批量进行大量读写请求的时候,提高redis响应速度。
原理就是将批量命令打包发送给redis,减少了网络请求的消耗。但是批量发送不能保证事务,既不能保证命令的先后执行顺序,以及是否执行成功。
redis pipeLine