JAVA面试总结
2019-12-09 11:04:47 4 举报
AI智能生成
java面试知识点总结,不断更新完善中。
作者其他创作
大纲/内容
框架
Spring
AOP
用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块。<br>可用于权限认证、日志、事务处理。
代理方式
静态代理<br>
AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
动态代理(Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理)
Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
IoC
IOC就是控制反转,是指创建对象的控制权的转移,以前创建对象的主动权和时机是由自己把控的,而现在这种权力转移到Spring容器中,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松散耦合,也利于功能的复用。DI依赖注入,和控制反转是同一个概念的不同角度的描述,即 应用程序在运行时依赖IoC容器来动态注入对象需要的外部资源。 <br>
三种注入方式
构造器注入、setter方法注入、根据注解注入。
IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。
优点<br>
(1)spring属于低侵入式设计,代码的污染极低;<br><br>(2)spring的DI机制将对象之间的依赖关系交由框架处理,减低组件的耦合性;<br><br>(3)Spring提供了AOP技术,支持将一些通用任务,如安全、事务、日志、权限等进行集中式管理,从而提供更好的复用。<br><br>(4)spring对于主流的应用框架提供了集成支持。
BeanFactory和ApplicationContext有什么区别?
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。
SpringMvc
Mybatis
SpringBoot
SpringCloud
算法
分布式
什么是分布式
单机
一个系统业务量很小的时候所有的代码都放在一个项目中就好了,然后这个项目部署在一台服务器上就好了。整个项目所有的服务都由这台服务器提供。这就是单机结构。
分布式
分布式结构就是将一个完整的系统,按照业务功能,拆分成一个个独立的子系统,在分布式结构中,每个子系统就被称为“服务”。这些子系统能够独立运行在web容器中,它们之间通过远程调用的方式通信。分布式的每个服务都可集群化部署
集群
单机处理到达瓶颈的时候,你就把单机复制几份,这样就构成了一个“集群”。集群中每台服务器就叫做这个集群的一个“节点”,所有节点构成了一个集群。每个节点都提供相同的服务
分布式事务
分布式锁
分布式全局唯一序列号
linux
前端
工具
Git
Maven
基础
字符串
String
特点:
字符串是常量,一旦被赋值就无法改变<br>
本质是一个字符数组(底层用char[]存储)
创建方式:
使用构造方法创建字符串对象
String s = new String ("abc");
存放在堆内存中
直接赋值创建字符串对象
String s = "abc"
存放在方法区中的常量池内
常用方法:
判断功能
boolean equals(Object obj):比较字符串的内容是否相同<br>
boolean equalsIgnoreCase(String str):比较字符串的内容是否相同,忽略大小写
boolean startsWith(String str):判断字符串对象是否以指定的str开头<br>
boolean endsWith(String str):判断字符串对象是否以指定的str结尾
获取功能
int length():获取字符串的长度,其实也就是字符个数<br>
char charAt(int index):获取指定索引处的字符<br>
int indexOf(String str):获取str在字符串对象中第一次出现的索引
String substring(int start):从start开始截取字符串<br>
String substring(int start,int end):从start开始,到end结束截取字符串。包括start,不包括end
转换功能<br>
char[] toCharArray():把字符串转换为字符数组<br>
String toLowerCase():把字符串转换为小写字符串<br>
String toUpperCase():把字符串转换为大写字符串<br>
其它功能
String trim() 去除字符串两端空格<br>
String[] split(String str) 按照指定符号分割字符串
StringBuilder
特点:
可变字符串,但是线程不安全
创建方式:
无参
StringBuilder sb = new StringBuilder();
传入字符串
String s = "helloworld";<br> StringBuilder sb = new StringBuilder(s);
常用方法:
public int capacity():返回当前容量 (理论值)<br>
public int length():返回长度(已经存储的字符个数)<br>
public StringBuilder append(任意类型):添加数据,并返回自身对象<br>
public StringBuilder reverse():反转功能<br>
String toString():通过toString()就可以实现把StringBuilder转成String
StringBuilder和String的相互转换:
StringBuilder -- String<br>
public String toString():通过toString()就可以实现把StringBuilder转成String<br>
String -- StringBuilder<br>
StringBuilder(String str):通过构造方法就可以实现把String转成StringBuilder
StringBuffer
特点:
可变字符串且线程安全
创建方式:
无参
StringBuffer sb = new StringBuffer();
带参
指定初始化长度
StringBuffer sb = new StringBuffer(16);
指定初始化内容
StringBuffer sb = new StringBuffer(“abc”);
常用方法:
append():追加字符串<br>
如何保证线程安全:
在方法上加synchronized锁
集合
Collection
List
ArrayList
特点:
排列有序,可重复
底层使用数组,查询快,增删慢<br>
线程不安全<br>
扩容时:当前容量*1.5+1<br>
Vector
特点:
排列有序,可重复
底层使用数组实现,查询快,增删慢
线程安全,效率低
扩容时默认是当前容量的1倍
LinkedList
特点:
排列有序,可重复
底层使用双向循环链表,增删快,查询慢<br>
线程不安全
Set
HashSet
特点:
排列无序,不可重复<br>
底层使用hash表实现<br>
存取速度快
内部是HashMap
TreeSet
特点:
排列无序,不可重复
底层使用二叉树实现<br>
按自然顺序存储
LinkedHashSet
特点:
排列有序,不可重复
采用Hash表存储,并用双向链表记录插入顺序<br>
内部采用LinkedHashMap
将值作为key,value为null进行存储
Queue
特点:
先进先出的队列,可以在前面删除,后边增加
可以用List、数组和链表实现
Map
HashMap
特点:
键不可重复,值可以重复<br>
底层是hash表
数组+单向链表+红黑树
1.7
数组+链表
查找
根据hashcode值快速定位到数组下标,然后遍历该数组下的链表<br>
时间复杂度取决于链表长度,为O(n)
1.8
数组+链表+红黑树
当链表中的元素超过8个后自动转为红黑树
查找
时间复杂度为 O(logN)
扩容
capacity:<br>
当前数组容量,始终保持 2^n,可以扩容,扩容后数组大小为当前的 2 倍。初始值为16
loadFactor:<br>
负载因子,默认为 0.75。
threshold:
扩容的阈值,等于 capacity * loadFactor
每个Entry包含四项元素:
key<br>
value
hash<br>
next<br>
用于单向链表
线程不安全<br>
key和value均可以为null
TreeMap<br>
特点:
键不可重复,值可以重复
底层用二叉树实现
HashTable
特点:
键不可以重复,值可以重复<br>
底层hash表
线程安全<br>
key和value均不可为null
新代码中不推荐使用,如果需要保证线程安全使用CurrentHashMap
JVM
类加载机制
①加载
在内存中生成一个代表这个类的class对象,作为方法区这个类各种数据的入口<br>
获取
class文件
zip包中读取<br>
jar包<br>
war包
运行时计算生成(动态代理)<br>
由其它文件生成(如jsp文件转换成对应的class类)<br>
②连接
验证
这一阶段的主要目的是为了确保 Class 文件的字节流中包含的信息是否符合当前虚拟机的要求,并<br>且不会危害虚拟机自身的安全。
准备
准备阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使<br>用的内存空间。
解析
虚拟机将常量池中的符号引用替换为直接引用的过程
符号引用
符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟<br>机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引<br>用的字面量形式明确定义在 Java 虚拟机规范的 Class 文件格式中。
直接引用
直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有<br>了直接引用,那引用的目标必定已经在内存中存在。
③初始化
初始化阶段是类加载最后一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类加载<br>器以外,其它操作都由 JVM 主导。到了初始阶段,才开始真正执行类中定义的 Java 程序代码。
不会执行初始化的情况
1. 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。<br>
2. 定义对象数组,不会触发该类的初始化。<br>
3. 常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触<br>发定义常量所在的类。<br>
4. 通过类名获取 Class 对象,不会触发类的初始化。<br>
5. 通过 Class.forName 加载指定类时,如果指定参数 initialize 为 false 时,也不会触发类初<br>始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。<br>
6. 通过 ClassLoader 默认的 loadClass 方法,也不会触发初始化动作。
④使用
⑤卸载<br>
类加载器
1. 启动类加载器(Bootstrap ClassLoader):负责加载 JAVA_HOME\lib 目录中的,或通过-<br>Xbootclasspath 参数指定路径中的,且被虚拟机认可(按文件名识别,如 rt.jar)的类。
2. 扩展类加载器(Extension ClassLoader):负责加载 JAVA_HOME\lib\ext 目录中的,或通<br>过 java.ext.dirs 系统变量指定路径中的类库。<br>
3. 应用程序类加载器(Application ClassLoader):负责加载用户路径(classpath)上的类<br>库。
4.JVM 通过双亲委派模型进行类的加载,当然我们也可以通过继承 java.lang.ClassLoader<br>实现自定义的类加载器。
双亲委派
当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父<br>类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,<br>只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的<br>Class),子类加载器才会尝试自己去加载。
java代码的执行
代码编译为.class文件<br>
javac
装载.class<br>
classLoader
执行class
解释执行
编译执行
内存管理
内存空间
方法区(共享)
即我们常说的永久代(Permanent Generation), 用于存储被 JVM 加载的类信息、常量、静<br>态变量、即时编译器编译后的代码等数据
永久代
内存回收的主要目标是针对常量池的回收和类型<br>的卸载, 因此收益一般很小
指内存的永久保存区域,主要存放 Class 和 Meta(元数据)的信息,Class 在被加载的时候被<br>放入永久区域. 它和和存放实例的区域不同,GC 不会在主程序运行期对永久区域进行清理。所以这<br>也导致了永久代的区域会随着加载的 Class 的增多而胀满,最终抛出 OOM 异常。
在 Java8 中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。元空间<br>的本质和永久代类似,元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用<br>本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入 native <br>memory, 字符串池和类的静态变量放入 java 堆中. 这样可以加载多少类的元数据就不再由<br>MaxPermSize 控制, 而由系统的实际可用空间来控制.
堆(共享)
是被线程共享的一块内存区域,创建的对象和数组都保存在 Java 堆内存中,也是垃圾收集器进行<br>垃圾收集的最重要的内存区域。
新生代
Eden 区:
Java 新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。当 Eden 区内存不够的时候就会触发 MinorGC,对新<br>生代区进行一次垃圾回收。
ServivorTo:
保留了一次 MinorGC 过程中的幸存者。
ServivorFrom:
上一次 GC 的幸存者,作为这一次 GC 的被扫描者。
老年代
主要存放应用程序中生命周期长的内存对象。
老年代的对象比较稳定,所以 MajorGC 不会频繁执行。在进行 MajorGC 前一般都先进行<br>了一次 MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足<br>够大的连续空间分配给新创建的较大对象时也会提前触发一次 MajorGC 进行垃圾回收腾出空间。
MajorGC 采用标记清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没<br>有标记的对象。MajorGC 的耗时比较长,因为要扫描再回收。MajorGC 会产生内存碎片,为了减<br>少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的<br>时候,就会抛出 OOM(Out of Memory)异常。
虚拟机栈(私有)
是描述java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)
局部变量表<br>
操作数栈<br>
动态链接<br>
方法出口<br>
每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。<br>
栈帧( Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接<br>(Dynamic Linking) 、 方 法 返 回 值 和 异 常 分 派 ( Dispatch Exception ) 。<br>栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出了<br>在方法内未被捕获的异常)都算作方法结束。
本地方法栈(私有)
本地方法栈为 Native 方法服务
程序计数器(私有)
指向虚拟机字节码指令的位置,唯一一个无oom的区域
示意图
子主题
内存回收
gc要做的三件事<br>
哪些内存需要回收?
什么时候回收?
怎么回收?
哪些对象已经死亡?
引用计数器
在 Java 中,引用和对象是有关联的。如果要操作对象则必须用引用进行。因此,很显然一个简单<br>的办法是通过引用计数来判断一个对象是否可以回收。简单说,即一个对象如果没有任何与之关<br>联的引用,即他们的引用计数都不为 0,则说明对象不太可能再被用到,那么这个对象就是可回收<br>对象。
存在循环引用问题,现已废弃
可达性分析法
为了解决引用计数法的循环引用问题,Java 使用了可达性分析的方法。通过一系列的“GC roots”对象作为起点搜索。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,则将面临回收。
垃圾收集算法
标记清除算法
最基础的垃圾回收算法,分为两个阶段,标注和清除。标记阶段标记出所有需要回收的对象,清<br>除阶段回收被标记的对象所占用的空间
该算法最大的问题是内存碎片化严重,后续可能发生大对象不能找到可<br>利用空间的问题。
复制算法<br>
为了解决 Mark-Sweep 算法内存碎片化的缺陷而被提出的算法。按内存容量将内存划分为等大小<br>的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用<br>的内存清掉
这种算法虽然实现简单,内存效率高,不易产生碎片,但是最大的问题是可用内存被压缩到了原<br>本的一半。且存活对象增多的话,Copying 算法的效率会大大降低。
标记整理算法<br>
标记阶段和 Mark-Sweep 算法相同,标记后不是清<br>理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象。
分代收集算法
分代收集法是目前大部分 JVM 所采用的方法,其核心思想是根据对象存活的不同生命周期将内存<br>划分为不同的域,一般情况下将 GC 堆划分为老生代(Tenured/Old Generation)和新生代(Young <br>Generation)。老生代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃<br>圾回收时都有大量垃圾需要被回收,因此可以根据不同区域选择不同的算法。
java的四种引用类型
强引用
在 Java 中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引<br>用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即<br>使该对象以后永远都不会被用到 JVM 也不会回收。因此强引用是造成 Java 内存泄漏的主要原因之<br>一。
软引用
软引用需要用 SoftReference 类来实现,对于只有软引用的对象来说,当系统内存足够时它<br>不会被回收,当系统内存空间不足时它会被回收。软引用通常用在对内存敏感的程序中。
弱引用
弱引用需要用 WeakReference 类来实现,它比软引用的生存期更短,对于只有弱引用的对象<br>来说,只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,总会回收该对象占用的内存。
虚引用
虚引用需要 PhantomReference 类来实现,它不能单独使用,必须和引用队列联合使用。虚<br>引用的主要作用是跟踪对象被垃圾回收的状态。
多线程
中间件
Redis
描述
redis是一个单线程的基于内存的key-value数据库,并且支持数据的持久化。
数据类型
string(字符串)
string 类型的值最大能存储 512MB。
string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象。
hash(哈希)
每个 hash 可以存储 2^32 -1 键值对(40多亿)。
hash 是一个键值(key=>value)对集合,适合存储对象
list(列表)
列表最多可存储 2^32 - 1 元素 (4294967295, 每个列表可存储40多亿)。
列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。
set(集合)
集合中最大的成员数为 2^32 - 1(4294967295, 每个集合可存储40多亿个成员)。
Set 是 string 类型的无序集合。
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
sadd 命令<br>添加一个 string 元素到 key 对应的 set 集合中,成功返回 1,如果元素已经在集合中返回 0。
zset(sorted set:有序集合)
zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。<br>
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
zset的成员是唯一的,但分数(score)却可以重复。
zadd 命令<br>添加元素到集合,元素在集合中存在则更新对应score
持久化方式
RDB
描述
RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。
优点
1). 一旦采用该方式,那么你的整个Redis数据库将只包含一个文件,这对于文件备份而言是非常完美的。比如,你可能打算每个小时归档一次最近24小时的数 据,同时还要每天归档一次最近30天的数据。通过这样的备份策略,一旦系统出现灾难性故障,我们可以非常容易的进行恢复。
2). 对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上。
3). 性能最大化。对于Redis的服务进程而言,在开始持久化时,它唯一需要做的只是fork出子进程,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行IO操作了。
4). 相比于AOF机制,如果数据集很大,RDB的启动效率会更高。
缺点
1). 如果你想保证数据的高可用性,即最大限度的避免数据丢失,那么RDB将不是一个很好的选择。因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。
2). 由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。
配置
Redis会将数据集的快照dump到dump.rdb文件中。此外,我们也可以通过配置文件来修改Redis服务器dump快照的频率,在打开6379.conf文件之后,我们搜索save,可以看到下面的配置信息:<br><br>save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,则dump内存快照。<br><br>save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,则dump内存快照。<br><br>save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,则dump内存快照。
AOF
描述
AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。
优点
1). 该机制可以带来更高的数据安全性,即数据持久性。Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其 效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变 化都会被立即记录到磁盘中。可以预见,这种方式在效率上是最低的。
2). 由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。然而如果我们本次操 作只是写入了一半数据就出现了系统崩溃问题,不用担心,在Redis下一次启动之前,我们可以通过redis-check-aof工具来帮助我们解决数据 一致性的问题。
3). 如果日志过大,Redis可以自动启用rewrite机制。即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时Redis还会创 建一个新的文件用于记录此期间有哪些修改命令被执行。因此在进行rewrite切换时可以更好的保证数据安全性。
4). AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建。
缺点
1). 对于相同数量的数据集而言,AOF文件通常要大于RDB文件。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
2). 根据同步策略的不同,AOF在运行效率上往往会慢于RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效。
配置
在Redis的配置文件中存在三种同步方式,它们分别是:<br><br>appendfsync always #每次有数据修改发生时都会写入AOF文件。<br><br>appendfsync everysec #每秒钟同步一次,该策略为AOF的缺省策略。<br><br>appendfsync no #从不同步。高效但是数据不会被持久化。
redis使用场景
缓存<br>
消息列表<br>
用到list
排行榜
用到zset,使用redis的ZREVRANGE方法获取排行榜
计数器
Redis的命令都是原子性的,你可以轻松地利用INCR、DECR命令进行原子性操作
实时反垃圾系统<br>
反垃圾系统通常都是基于关键词的,使用Redis储存关系词,能够利用Redis的高性能,为监控系统提供稳定及精确的实时监控功能,典型的案例如,邮件系统、评论系统等
可以发布、订阅的实时消息系统
Redis中Pub/Sub系统可以构建实时的消息系统,比如,很多使用Pub/Sub构建的实时聊天应用。<br><br>设计思路:<br><br> 服务端发送消息(含标题,内容),标题按照一定规则存入redis,同时标题(以最少的信息量)推送到客户端,客户点击标题时,获取相应的内容阅读.<br><br> 如果未读取,可以提示多少条未读,redis能够很快记数<br><br> 根据一定时间清理缓存<br>
队列应用
抢红包
分布式全局唯一id
利用incr命令,每次加一
分布式锁
setnx key value,当key不存在时,将 key 的值设为 value ,返回1<br>若给定的 key 已经存在,则setnx不做任何动作,返回0。<br><br>当setnx返回1时,表示获取锁,做完操作以后del key,表示释放锁,如果setnx返回0表示获取锁失败<br>为避免死锁,要多setnx的值设置过期时间
缓存三连
缓存雪崩
描述
缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。
解决方法
在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,避免大量缓存同时失效
缓存穿透
描述
查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。
解决方法
采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
缓存击穿
描述
缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
解决方法
使用redis分布式锁,保证只有一个请求到达后端并构建数据,待缓存数据构建完毕再将等待查询缓存的请求放入缓存
Redis的局限性
Redis只能使用单线程,性能受限于CPU性能
支持简单的事务需求,但业界使用场景很少,并不成熟
Redis的优势
(1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
(2) 支持丰富数据类型,支持string,list,set,sorted set,hash
(3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
(4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除
Redis数据淘汰策略
策略
(1) voltile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
(2) volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
(3) volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
(4) allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
(5) allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
(6) no-enviction(驱逐):禁止驱逐数据
配置方法
1.在线更新配置 /apps/svr/redis/bin/redis-cli -p 6921 config set maxmemory-policy volatile-lru
2.修改配置文件 maxmemory-policy volatile-lru #默认是noeviction,即不进行数据淘汰
作用
保证缓存中都是热点数据
Redis搭建
单机
特点
简单
缺点
内存容量有限,处理能力有限,无法高可用
主从复制
特点
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点。
作用
数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
缺点
无法保证高可用,没有解决主节点的写的压力
哨兵
特点
sentinel可以让redis实现主从复制,当一个集群中的master失效之后,sentinel可以选举出一个新的master用于自动接替master的工作,集群中的其他redis服务器自动指向新的master同步数据。一般建议sentinel采取奇数台,防止某一台sentinel无法连接到master导致误切换。
缺点
主从切换数据,需要时间会丢数据
cluster集群方案
特点
redis 3.0之后版本支持redis-cluster集群,它是Redis官方提出的解决方案,Redis-Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。所有的 redis 节点彼此互联(PING-PONG 机制),内部使用二进制协议优化传输速度和带宽.redis集群一般可扩展性的master主节点,同时每一个master一般配置一个slave从节点。所有的master占满redis集群的16384个哈希槽。而master与slave采用主从机制,master采用单独的后台线程以发送快照的方式与slave节点同步数据,所以不会影响整个redis的性能。
maste节点故障判断
集群中所有master参与,如果半数以上master节点与故障节点通信超过(cluster-node-timeout),认为该节点故障。那么该节点得slave从节点就升级为master节点
Dubbo
Zookeeper
RabbitMq
描述
RabbitMQ是一个由erlang开发的基于AMQP(Advanced Message Queue )协议的开源实现。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面都非常的优秀。是当前最主流的消息中间件之一。
使用过程
(1)客户端连接到消息队列服务器,打开一个channel。
(2)客户端声明一个exchange,并设置相关属性。
(3)客户端声明一个queue,并设置相关属性。
(4)客户端使用routing key,在exchange和queue之间建立好绑定关系。
(5) 客户端投递消息到exchange。exchange接收到消息后,就根据消息的key和已经设置的binding,进行消息路由,将消息投递到一个或多个队列里。
Exchange 模式
Fanout Exchange
所有发送到Fanout Exchange的消息都会被转发到与该Exchange 绑定(Binding)的所有Queue上。
Direct Exchange
所有发送到Direct Exchange的消息被转发到RouteKey中指定的Queue。
Topic Exchange
所有发送到Topic Exchange的消息被转发到所有关心RouteKey中指定Topic的Queue上,Exchange 将RouteKey 和某Topic 进行模糊匹配。
如何防止消息丢失
生产者发送消息失败
事务
影响性能,不推荐使用
普通confirm
producer每发送一条消息后,调用waitForConfirms()方法,等待broker端confirm,如果服务器端返回false或者超时时间内未返回,客户端进行消息重传
在配置文件中开启
批量confirm
批量confirm模式:producer每发送一批消息后,调用waitForConfirms()方法,等待broker端confirm。
如果失败需要批量重发消息,效果不好
异步confirm
提供一个回调方法,broker confirm了一条或者多条消息后producer端会回调这个方法。 我们分别来看看这三种confirm模式
发送消息前按顺序向redis中存入消息,序号作为key,发送后监听交换机的返回值,如果失败则重新发送。返回值:deliveryTag表示消息序号,multiple表示消息是否成功。
交换机没有匹配的队列
confirm机制确保的是消息能够正确的发送至RabbitMQ,这里的“发送至RabbitMQ”的含义是指消息被正确的发往至RabbitMQ的交换器,如果此交换器没有匹配的队列的话,那么消息也将会丢失
1. 使用mandatory 设置true
子主题
2. 利用备份交换机(alternate-exchange):实现没有路由到队列的消息
mq消息丢失
消息被投递到RabbitMQ的内存中, 还没投递到消费者实例之前宕机了, 消息不就丢失了
可以进行消息持久化, 将Exchange、queue和message都持久化到硬盘, 这样, RabbitMQ重启时, 会把持久化的Exchange、queue和message从硬盘重新加载出来, 重新投递消息
Exchange的持久化, 声明交换机时指定持久化参数为true即可
queue的持久化, 声明队列时指定持久化参数为true即可
message的持久化, 是通过配置deliveryMode实现的, 生产者投递时, 指定deliveryMode为MessageDeliveryMode.PERSISTENT即可实现消息的持久化, 投递和消费都需要通过Message对象进行交互, 为了不每次都写配置转换的代码, 我们写一个消息帮助类MessageHelper:
消费者消费消息失败
描述
消费者在收到消息后在处理过程中出现异常,导致消息消费失败
解决方法
消费者处理消息失败的时候,重试一定的次数。比如重试3次,如果重试3次之后还是失败,则把这条消息发送到死信队列
思路
首先,将消息携带routtingkey的消息发送到正常转发器exchange@normal,exchange@normal将消息发送到正常队列queue@normal,queue@normal得到消息后进行处理,如果处理成功,则给rabbitmq发送应答。如果消息处理失败,判断消息失败的次数:如果失败次数小于3次,则将消息发送到重试转发器exchange@retry,exchange@retry得到消息后,发送到重试队列queue@retry,queue@retry10s后,将该条消息再次发送到正常转发器exchange@normal进行正常的消费;如果失败次数超过3次,则将消息发送到失败转发器exchange@filed,exchange@filed将失败了的消息发送给失败队列queue@filed,然后可以根据业务需求处理失败了的数据。比如保存到失败文件或者数据库等,也可以人工处理后,重新发送给exchange@normal。
如何防止消息重复消费
消费方接口保证幂等性
如何解决消息积压问题
描述
unack消息的积压问题, 简单来说就是消费者处理能力有限, 无法一下将MQ投递过来的所有消息消费完, 如果MQ推送消息过多, 比如可能有几千上万条消息积压在某个消费者实例内存中, 此时这些积压的消息就处于unack状态, 如果一直积压, 就有可能导致消费者服务实例内存溢出、内存消耗过大、甚至内存泄露
解决方法
你可以通过 “channel.basicQos(10)” 这个方法来设置当前channel的prefetch count。也可以通过配置文件设置: spring.rabbitmq.listener.simple.prefetch=10
ElasticSearch
设计模式
数据库
MySql
Oracle
0 条评论
下一页