Java面试 —— Redis相关
Posted JohnnyLin00
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java面试 —— Redis相关相关的知识,希望对你有一定的参考价值。
Redis 底层数据结构
字符串底层存储
在Redis 中,字符串有三种存储编码方式: int编码 、embstr编码和raw编码
int编码
当value是一个整数且值大小不超过8个字节,就会是哟红int编码,ptr直接存储数值
embstr 编码
embstr对象用于存储比较短的字符串,embstr编码中RedisObject结构与ptr指向的SDS 结构在内存中是连续的,内存分配次数和内存释放次数均是一次。
raw编码会分别调用两次内存分配函数来分别创建RedisObject结构个SDS结构。
hash对象
Redis中,hash类型的value可以是一个hash表,底层编码可以是ziplist,也可以是hashtable。
默认情况下,当元素个数小于512个时,底层使用ziplist存储数据。
ziplist
元素保存的字符串长度较短且元素个数较少。(长度小于64字节,个数小于512),出于节约内存考虑,hash表会使用ziplist作为底层实现,ziplist是一块连续的内存,里面每一个节点保存了对应的key和value,然后每个节点很紧凑地存储在一起。
优点是: 没有冗余空间
缺点是: 插入新元素需要调用realloc扩展内存,这可能会导致内存重分配
hashtable
元素比较多是就会使用hashtable编码作为底层实现。此时RedisObject的ptr指针指向一个dict结构,dict结构中的ht数组有两个元素h[0]和h[1]。通常h[0]保存键值对,h[1]只在渐进式rehash时使用,hashtable是通过链地址法来解决冲突的。
Zset
Zset在存储时会将元素按照score从低到高排列,底层是通过跳表实现的。
ziplist
当元素较少时(元素长度小于64字节,且元素个数小于128),Zset的底层编码使用ziplist实现,所有元素按照score从低到高排序。
skiplist + dict
当元素较多时,使用skiplist + dict来实现。skiplist存储元素的值和score,并且将所有元素按照分值有序排列。便于以O(logN)的时间复杂度插入,删除,更新,及根据Score进行范围性查找。
dict存储元素的值和Score的映射关系,便于以O(1)的时间复杂度查找元素对应的分值。
跳表
跳表就是层次化的链表结构,它由多个链表组成。只有底层的链表保存节点数据,一般来说每两个节点选出一个节点作为下一级索引的节点,让下一级 索引的节点数量为本机索引节点数量的一半。依次类推直至最顶层索引节点数为1。
原理是每次查找数据时,先在最上层查找,然后再定位到下一层,层层定位,直至最终找到目标数据。这种方式不用遍历整个链表,而是跳跃着差,这样就使得查找时间复杂度退化到了logn
为什么不使用List、红黑树或平衡二叉树呢?
List是顺序㽾,访问速度很快,但是添加和删除操作是O(N)操作。至于红黑树和平衡二叉树,每次更新redis的值,都要消耗O(logn)的复杂度调整树结构,而跳跃表只需要调整局部链表结构就行,显然跳跃表更适合。
跳跃表支持平均O(logN)、最坏O(N)的复杂度进行节点查找,还可以通过顺序性操作来批量处理节点。
在大部分情况下,跳跃表的效率与平衡树媲美,但是跳跃表的实现要比平衡树要来得更为简单。
缓存雪崩
重复排队、并发超卖、数据不一致
redis分布式锁
- 单机多线程下存在并发问题。 – 在JVM层面加锁,如synchronized或ReentrantLock
- 分布式部署下存在超卖问题。 --使用Redis分布式锁,加解锁。
String value = UUID.random().toString();
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value); //加锁
stringRedisTemplate.delte(REDIS_LOCK)
- 秒杀业务逻辑代码块出现异常时可能无法释放掉锁。 — 增加try -catch 语句块 在finally代码块释放锁
- 部署了秒杀服务的服务器宕机 – 对lockkey 增加过期时间的设定
5. 原子性考虑。 加锁和设置过期时间非原子操作
EX seconds – Set the specified expire time, in seconds.
PX milliseconds – Set the specified expire time, in milliseconds.
- 误删其他事务的锁。 必须在删除锁之前判断是否是自己的加的锁。
- 判断是否是自己加的锁 与 解锁非原子性操作,那么会出现判断加锁与解锁不是同一个客户端导致误解锁
-
利用LUA脚本
-
使用Redis的事务
仍然存在 redisLock 过期时间小于业务执行时间的问题,也就是如何实现分布式锁的续期问题。
再者在集群环境下还存在Redis的主从不一致
Redison
- 在超高并发下,可能出现IllegalMonitorStateException
select … for update后,对所在行加了互斥锁,而你使用select …在Innodb里是快照读,是不涉及到锁的问题的,如果想要验证加锁是否成功,需要对查询加共享锁 lock in share mode或互斥锁for update
注意需要两个会话都开启事务:
select * from tmp_file_bk limit 0,10 for update;
之后会话2,使用
select * from tmp_file_bk where id = 106 lock in share mode ;
也无法访问到
以上是关于Java面试 —— Redis相关的主要内容,如果未能解决你的问题,请参考以下文章