1.请问什么是缓存穿透、缓存雪崩、缓存击穿,怎么避免它们的发生
1.缓存穿透:
请求查询不存在的数据,由于缓存不命中,每次请求都会直接查询数据库,导致数据库压力过大;
避免办法:
缓存空对象,将查询为空的结果缓存一段时间,避免多次查询数据库
使用布隆过滤器,快速判断一个元素是否存在于一个集合中,可以在缓存就过滤掉请求不存在的数据
2.缓存雪崩:
指缓存大量数据同时失效,导致大量请求直接访问数据库,导致数据库压力过大
避免办法:
将不同的缓存数据设置不同的过期时间
使用分布式缓存,将缓存分散在多台缓存服务器上,避免单点故障导致大规模缓存失效
3. 缓存击穿:
指一个热点数据失效,导致大量请求同时涌入数据库
避免办法:
使用互斥锁,在缓存失效的瞬间,只允许一个请求去查询数据库,其他请求等待
使用热点数据永不过期,或设置较长的过期时间
2.假设redis只能存储10000条数据,那么怎么保证redis中存储的数据都是热点数据
当redis使用的内存超过了设置的最大内存,会触发redis的淘汰机制,而将机制设置为allkeys-lru或allksys-lfu时能保证redis中存储的数据都是热点数据
CONFIG SET maxmemory-policy allkeys-lru
3.请讲一下redis常见的内存淘汰策略
LRU(Least Recently Used,最近一段时间没有访问过) 、TTL(过期时间)、LFU(Least Frequently Used,最近一段时间最少访问)
1. noeviction:当内存使用超过配置的时候会返回错误,不会驱逐任何键
2. allkeys-lru:加入键的时候,如果过限,首先通过LRU算法驱逐最久没有使用的键
3. volatile-lru:加入键的时候如果过限,首先从设置了过期时间的键集合中驱逐最久没有使用的键
4. allkeys-random:加入键的时候如果过限,从所有key随机删除
5. volatile-random:加入键的时候如果过限,从过期键的集合中随机驱逐
6. volatile-ttl:从配置了过期时间的键中驱逐马上就要过期的键
7. volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键
8. allkeys-lfu:从所有键中驱逐使用频率最少的键
4.请问什么是一致性哈希算法,谈一谈你的理解
当我们在使用分布式缓存时可能引发以下问题:
同一份数据可能在多个redis数据库造成数据冗余
无法保证对相同key的所有访问都发送到相同redis中
为了解决上述问题,最初我们使用hash算法,用hash值来指定数据保存在哪个redis数据库中,但是又存在着扩展性和容错性的问题,当服务器增加或减少时都需要重新计算hash值
容错性是指当系统中的某个服务出现问题时,不能影响其他系统;扩展性是指当加入新的服务器后,整个系统能正确高效运行。
一致性哈希就是将整个哈希空间组织成一个圆环,服务器在上面是一个个节点,数据也在环上,然后数据会顺时针存到距离最近的节点,一致性哈希的可扩展性和容错性都比较好,但是存在资源倾斜的问题,可以用过映射虚拟节点解决
5.请问什么是哈希槽
哈希槽是一种数据分片的方法,用于分布式储存系统中实现负载均衡和高可用性,其基本思想是将整个哈希值空间划分为固定数量的槽,每个槽对应一个服务器节点,当需要储存或者查找一个键值对时,先对键值对进行哈希运算,根据哈希值找到对应的槽,再访问槽上的服务器节点
6.同步与异步的区别
同步:同步就是顺序执行,执行完一个再执行下一个,需要等待、协调运行。
异步:异步和同步是相对的,异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这一事件完成后再工作。
同步就可以想成去实体店买东西,不给货一直等着;异步就像网购,订完就可以干别的去,不用一直等着;
7.线程的创建方式
1.编写一个类继承自 Thread 类,重写 run() 方法。用 start() 方法启动线程
2.编写一个类实现 Runnable 接口,实现 run() 方法。用 new Thread(Runnable target).start() 方法来启动线程
3.实现Callable接口,匿名类继承Thread
4.线程池创建
线程池实例,及其七个参数
RejectedExecutionHandler rejectedHandler = new ThreadPoolExecutor.AbortPolicy();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // 核心线程数
10, // 最大线程数
60, // 存活时间
TimeUnit.SECONDS, // 时间单位
new LinkedBlockingQueue<>(100), // 工作队列
threadFactory, // 线程工厂
rejectedHandler, // 拒绝策略
);
线程的状态及转化
初始状态:线程刚创建还未调用start()方法
就绪状态:调用start()方法,还未执行run()方法,等待cpu资源(进入该状态:sleep(long)时间到、线程拿到对象锁..)
运行状态:获得cpu资源,开始执行run()方法(变为就绪状态:线程失去cpu资源、调用yield()静态方法)
阻塞状态:线程正等待监视器锁时,线程等待进入sychronized同步方法/代码块会进入该状态,当取得锁会变就绪
等待状态:需要等待其他线程完成操作;运行->等待(其他线程调用join()方法、当前线程调用wait方法、LockSupport.park()出于线程调度禁用当前线程);等待->就绪(等待的线程被其他线程对象唤醒,notify()和notifyAll() 、LockSupport.unpark(Thread)给出许可证解除等待状态)
超时等待状态:与等待状态类似但是可以在指定时间自行返回;运行->超时等待(调用Thread.sleep(long)、线程调用wait(long)、其他线程调用join(long)、LockSupport.parkNanos()、LockSupport.parkUntil() )
8.什么是排序二叉树以及排序二叉树的遍历方式
排序二叉树每个节点的值大于其非空左子树所有节点的值并小于其非空右子树所有节点的值
前序遍历:根左右
中序遍历:左根右
后序遍历:左右根
// 中序遍历
public void inorderTraversal(TreeNode root) {
if (root == null) {
return;
}
inorderTraversal(root.left);
System.out.print(root.val + " ");
inorderTraversal(root.right);
}
// 前序遍历
public void preorderTraversal(TreeNode root) {
if (root == null) {
return;
}
System.out.print(root.val + " ");
preorderTraversal(root.left);
preorderTraversal(root.right);
}
// 后序遍历
public void postorderTraversal(TreeNode root) {
if (root == null) {
return;
}
postorderTraversal(root.left);
postorderTraversal(root.right);
System.out.print(root.val + " ");
}
9.请解释什么是脏读、幻读、不可重复读、第一类丢失更新以及第二类丢失更新
脏读:
一个事务读取到了另一个未提交的事务所做的修改,从而导致读取到了不一致或错误的数据
幻读:
是在同一个事务中,多次执行相同的查询语句,但得到的结果集却不同。
不可重复读:
在同一个事务中,多次读取同一条数据时,得到的结果不一致
第一类丢失更新:
撤销一个事务的时候,把其它事务已提交的更新数据覆盖了;(回滚丢失)
第二类丢失更新:
当两个事务同时更新同一数据时,后提交的事务的更新覆盖了先提交的事务的更新,从而导致了数据的丢失
10.事务隔离级别有哪些
READ_UNCOMMITTED:读未提交,什么问题都无法解决,效率最高
READ_COMMITTED:读已提交,可以解决脏读问题
REPEATABLE_READ:可重复读,可以解决脏读、不可重复读的问题
SERIALIZABLE:可序列化,最高级别,解决所有问题(脏读、幻读、不可重复读),但是效率最低
11.wait与sleep的区别
定义位置不同:sleep() 是线程类(Thread)的方法;wait() 是 Object 的方法;
调用地方不同:sleep 方法可以在任何地方使用;wait 方法则只能在同步方法或同步块中使用;
锁资源释放方式不同:
sleep 方法只让出了CPU,没有释放同步资源锁! wait方法则是当前线程让自己暂时退让出同步资源锁,以便其他正在等待该资源的线程得到该资源进而运行,只有调用了notify方法,之前调用wait()的线程才会解除wait状态,可以去参与竞争同步资源锁,进而得到执行。
恢复方式不同:
sleep调用后停止运行期间仍持有同步锁,所以到时间会继续执行;wait调用会放弃对象锁,进入等待队列,待调用notify()/notifyAll()唤醒指定的线程或者所有线程,才会进入锁池,再次获得对象锁后才会进入运行状态,在没有获取对象锁之前不会继续执行;
异常捕获:sleep需要捕获或者抛出异常,而wait/notify/notifyAll则不需要。