Java复习之Redis解读

转载于三太子敖丙!!!

redis单线程为什么执行速度这么快?

(1):纯内存操作,避免大量访问数据库,减少直接读取磁盘数据,redis将数据储存在内存里面,读写数据的时候都不会受到硬盘 I/O 速度的限制,所以速度快

(2):单线程操作,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗

(3):采用了非阻塞I/O多路复用机制

Redis数据结构底层实现

String:

(1)Simple dynamic string(SDS)的数据结构

struct sdshdr{
 //记录buf数组中已使用字节的数量
 //等于 SDS 保存字符串的长度
 int len;
 //记录 buf 数组中未使用字节的数量
 int free;
 //字节数组,用于保存字符串
 char buf[];
}

它的优点:(1)不会出现字符串变更造成的内存溢出问题

(2)获取字符串长度时间复杂度为1

(3)空间预分配, 惰性空间释放free字段,会默认留够一定的空间防止多次重分配内存

应用场景:String 缓存结构体用户信息,计数

Hash:

数组+链表的基础上,进行了一些rehash优化;1.Reids的Hash采用链地址法来处理冲突,然后它没有使用红黑树优化。

2.哈希表节点采用单链表结构。

3.rehash优化 (采用分而治之的思想,将庞大的迁移工作量划分到每一次CURD中,避免了服务繁忙)

应用场景:保存结构体信息可部分获取不用序列化所有字段

List:

应用场景:(1):比如twitter的关注列表,粉丝列表等都可以用Redis的list结构来实现

(2):list的实现为一个双向链表,即可以支持反向查找和遍历

Set:

内部实现是一个 value为null的HashMap,实际就是通过计算hash的方式来快速排重的,这也是set能提供判断一个成员 是否在集合内的原因。应用场景:去重的场景,交集(sinter)、并集(sunion)、差集(sdiff),实现如共同关注、共同喜好、二度好友等功能

Zset:

内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。跳表:每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的 应用场景:实现延时队列

redis事务

(1):Multi开启事务

(2):Exec执行事务块内命令

(3):Discard 取消事务

(4):Watch 监视一个或多个key,如果事务执行前key被改动,事务将打断

redis事务的实现特征

(1):所有命令都将会被串行化的顺序执行,事务执行期间,Redis不会再为其它客户端的请求提供任何服务,从而保证了事物中的所有命令被原子的执行

(2):Redis事务中如果有某一条命令执行失败,其后的命令仍然会被继续执行

(3):在事务开启之前,如果客户端与服务器之间出现通讯故障并导致网络断开,其后所有待执行的语句都将不会被服务器执行。然而如果网络中断事件是发生在客户端执行EXEC命令之后,那么该事务中的所有命令都会被服务器执行

(4):当使用Append-Only模式时,Redis会通过调用系统函数write将该事务内的所有写操作在本次调用中全部写入磁盘。

然而如果在写入的过程中出现系统崩溃,如电源故障导致的宕机,那么此时也许只有部分数据被写入到磁盘,而另外一部分数据却已经丢失。

Redis服务器会在重新启动时执行一系列必要的一致性检测,一旦发现类似问题,就会立即退出并给出相应的错误提示。此时,我们就要充分利用Redis工具包中提供的redis-check-aof工具,该工具可以帮助我们定位到数据不一致的错误,并将已经写入的部分数据进行回滚。修复之后我们就可以再次重新启动Redis服务器了

Redis的同步机制?

(1):全量拷贝, 1.slave第一次启动时,连接Master,发送PSYNC命令,

2.master会执行bgsave命令来生成rdb文件,期间的所有写命令将被写入缓冲区。

  1. master bgsave执行完毕,向slave发送rdb文件

  2. slave收到rdb文件,丢弃所有旧数据,开始载入rdb文件

  3. rdb文件同步结束之后,slave执行从master缓冲区发送过来的所以写命令。

  4. 此后 master 每执行一个写命令,就向slave发送相同的写命令。

    (2):增量拷贝 如果出现网络闪断或者命令丢失等异常情况,从节点之前保存了自身已复制的偏移量和主节点的运行ID

  5. 主节点根据偏移量把复制积压缓冲区里的数据发送给从节点,保证主从复制进入正常状态。

    redis集群模式性能优化

    (1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件

    (2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次

    (3) 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内

    (4) 尽量避免在压力很大的主库上增加从库

    (5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。

    Redis集群方案

    (1):官方cluster方案

    (2):twemproxy

    代理方案twemproxy是一个单点,很容易对其造成很大的压力,所以通常会结合keepalived来实twemproy的高可用

    (3):codis 基于客户端来进行分片

集群不可用场景

(1):master挂掉,且当前master没有slave

(2):集群超过半数以上master挂掉,无论是否有slave集群进入fail状态

redis 最适合的场景

(1):会话缓存session cache

(2):排行榜/计数器ZRANGE

(3):发布/订阅

缓存淘汰策略

(1):先进先出算法(FIFO)

(2):最近使用最少Least Frequently Used(LFU)

(3):最长时间未被使用的Least Recently Used(LRU)

当存在热点数据时,LRU的效率很好,但偶发性的、周期性的批量操作会导致LRU命中率急剧下降,缓存污染情况比较严重

redis过期key删除策略

(1):惰性删除,cpu友好,但是浪费cpu资源

(2):定时删除(不常用)

(3):定期删除,cpu友好,节省空间

缓存雪崩以及处理办法

同一时刻大量缓存失效;

处理方法:

(1):缓存数据增加过期标记

(2):设置不同的缓存失效时间

(3):双层缓存策略C1为短期,C2为长期

(4):定时更新策略

缓存击穿原因以及处理办法

频繁请求查询系统中不存在的数据导致;

处理方法:

(1):cache null策略,查询反馈结果为null仍然缓存这个null结果,设置不超过5分钟过期时间

(2):布隆过滤器,所有可能存在的数据映射到足够大的bitmap中 google布隆过滤器:基于内存,重启失效不支持大数据量,无法在分布式场景 redis布隆过滤器:可扩展性,不存在重启失效问题,需要网络io,性能低于google

redis阻塞原因

(1):数据结构使用不合理bigkey

(2):CPU饱和

(3):持久化阻塞,rdb fork子线程,aof每秒刷盘等

hot key出现造成集群访问量倾斜解决办法

(1):使用本地缓存

(2):利用分片算法的特性,对key进行打散处理(给hot key加上前缀或者后缀,把一个hotkey 的数量变成 redis 实例个数N的倍数M,从而由访问一个 redis key 变成访问 N * M 个redis key)

Redis分布式锁

2.6版本以后lua脚本保证setnx跟setex进行原子性(setnx之后,未setex,服务挂了,锁不释放) a获取锁,超过过期时间,自动释放锁,b获取到锁执行,a代码执行完remove锁,a和b是一样的key,导致a释放了b的锁。解决办法:remove之前判断value(高并发下value可能被修改,应该用lua来保证原子性)

Redis如何做持久化

bgsave做镜像全量持久化,aof做增量持久化。因为bgsave会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据 ,所以需要aof来配合使用。在redis实例重启时,会使用bgsave持久化文件重新构建内存,再使用aof重放近期的操作指令来 实 现完整恢复重启之前的状态。

对方追问那如果突然机器掉电会怎样?

取决于aof日志sync属性的配置,如果不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据。但是在高性能的要求下每次都sync是不现实的,一般都使用定时sync,比如1s1次,这个时候最多就会丢失1s的数据.

redis锁续租问题?

(1):基于redis的redission分布式可重入锁RLock,以及配合java集合中lock;

(2):Redission 内部提供了一个监控锁的看门狗,不断延长锁的有效期,默认检查锁的超时时间是30秒

(3):此方案的问题:如果你对某个redis master实例,写入了myLock这种锁key的value,此时会异步复制给对应的master ,slave实例。但是这个过程中一旦发生redis master宕机,主备切换,redis slave变为了redis master。

接着就会导致,客户端2来尝试加锁的时候,在新的redis master上完成了加锁,而客户端1也以为自己成功加了锁。此时就会导致多个客户端对一个分布式锁完成了加锁 解决办法:只需要将新的redis实例,在一个TTL时间内,对客户端不可用即可,在这个时间内,所有客户端锁将被失效或者自动释放.

bgsave的原理是什么?

fork和cow。fork是指redis通过创建子进程来进行bgsave操作,cow指的是copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写进的页面数据会逐渐和子进程分离开来。

RDB与AOF区别

(1):R文件格式紧凑,方便数据恢复,保存rdb文件时父进程会fork出子进程由其完成具体持久化工作,最大化redis性能,恢复大数据集速度更快,只有手动提交save命令或关闭命令时才触发备份操作;

(2):A记录对服务器的每次写操作(默认1s写入一次),保存数据更完整,在redis重启是会重放这些命令来恢复数据,操作效率高,故障丢失数据更少,但是文件体积更大;

1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来?

使用keys指令可以扫出指定模式的key列表。如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了 ,但是整体所花费的时间会比直接用keys指令长。

如何使用Redis做异步队列?

一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。

可不可以不用sleep呢?

list还有个指令叫blpop,在没有消息的时候,它会阻塞住直到消息到来。

能不能生产一次消费多次呢?

使用pub/sub主题订阅者模式,可以实现1:N的消息队列。

pub/sub有什么缺点?

在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq等。

redis如何实现延时队列?

使用sortedset,想要执行时间的时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。

为啥redis zset使用跳跃链表而不用红黑树实现?

(1):skiplist的复杂度和红黑树一样,而且实现起来更简单。

(2):在并发环境下红黑树在插入和删除时需要rebalance,性能不如跳表。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值