大佬教程收集整理的这篇文章主要介绍了Java后端社招面试个人总结,大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。
近期面了一些后端的内容c;准备先对其中一些内容进行总结下c;便于个人加深理解。行文可能仅支持个人能看懂理解就行。如有理解不到位的地方请谅解。终结面试后再来一一回顾整理。当然c;友情提示下:面试前最好是提前做好项目亮点和难点、个人优点和缺点、为什么找工作的介绍准备c;在面试中不断总结提炼c;形成较好的表述。本文会持续更新到换工作结束c;再分模块整理。(由于文字较多c;可能存在没被发现的错别字c;欢迎及时指出c;感谢)
JVM内存区域是什么样的?各有什么内容?🌟🌟🌟🌟
JVM内存区域包含以下几部分:
程序计数器:线程私有。当前线程所执行字节码的行号指示器c;便于线程切换后回到正确的执行位置c;无OOM情况;
Java虚拟机栈:线程私有。方法执行时同步创建栈帧c;存储局部变量表、操作数栈、动态连接和方法出口等信息。方法执行过程就是入栈到出栈的一个过程。(补充:局部变量表中有基本数据类型、对象引用、返回地址类型c;数据类型以局部变量槽来存储c;long/double两个槽位c;其余的只有一个c;编译时确定c;运行时不会发生改变)线程请求栈深度大于虚拟机所允许深度c;将抛出StackOverflowErrorc;如虚拟机栈容量可动态扩展c;则当栈扩展时无法申请足够内存时抛出OOM。
本地方法栈:线程私有。为虚拟机使用到的本地方法服务。也有可能StackOverflowError或OOM。
Java堆:线程共享。内存管理最大的一块。存放对象实例c;垃圾收集器管理的内存区域。物理上可不连续c;但在逻辑上是连续的。可根据-Xmx和-Xms设定固定大小或可扩展的。无空间完成对象实例分配且无法再扩展时c;将抛出OOM。
方法区:线程共享。存储被虚拟机加载的类型信息、常量、静态变量、即时编译器后的代码缓存等数据。如果方法区无法满足新内存分配需求时c;也会抛出OOMc;一般垃圾回收器不对其进行处理。
new对象的原理/生命周期 🌟
a、检查类是否加载c;没有则先加载类。(懒加载c;会在堆区有class对象c;方法区会有类的相关元数据信息)
b、分配内存。jvm根据大小分配内存空间;空闲列表(空间不规整c;容易形成碎片空间)和指针碰撞方式(空间比较规整c;默认使用)。并发问题c;用CAS+重试机制或本地线程分配缓冲(每个线程预先分配一块堆内存)。
c、初始化。实例赋零值或null等操作。
d、设置对象头。hashcode、分代年龄、锁状态等信息。
e、执行初始化方法。对实例设置程序指定的初始值c;并执行构造方法。
当对象不再被使用时c;就需要进行垃圾回收c;为其他对象腾出空间。
一般分配在eden区c;不够用时会触发minor gcc;存活对象被移动到survivor区c;eden:survivor:survivor=8:1:1c;如果survivor满了或年龄到了15后则被移动到老年代。
大对象和长期存活的对象都将进入老年代。如果老年代满了则会触发full gcc;回收整个堆。
垃圾回收算法有哪些?新生代用的是哪种?哪种容易引起full GC?TLAB是什么? 🌟🌟
标记清除:从gc root链上标记所有被引用的对象;遍历整个堆c;把未标记的对象清除。(需暂停整个应用c;并会产生内存碎片)。缺点:执行效率不稳定c;会因为对象数量增长c;效率变低;标记清除后会有大量的不连续的内存碎片c;空间碎片太多就会导致无法分配较大对象c;无法找到足够大的连续内存c;而发生gc;
标记整理:算法分为”标记-整理-清除“阶段c;首先需要先标记出存活的对象c;然后把他们整理到一边c;最后把存活边界外的内存空间都清除一遍。这个算法的好处就是不会产生内存碎片c;但是由于整理阶段移动了对象c;所以需要更新对象的引用。
复制:复制算法把内存空间划为两个相等的区域c;每次只使用其中一个区域。垃圾回收时c;遍历当前使用区域c;把正在使用中的对象复制到另外一个区域中。缺点:可用内存缩短一半c;浪费空间。
尽管看起来问题很大c;但分代理论说大多数对象生命周期短c;这种情况下标记复制就很适合了(复制的存活对象少)。至于内存消耗太大的问题c;java虚拟机通过将新生代分为一个Eden区与2个Survivo区c;其中一个Survivo区用来复制c;这样一来极大得提高了内存空间利用率。
所以新生代用的是标记复制算法。
容易引起Full GC的是标记清除算法c;因为空间碎片太多导致无法分配大对象。
线程本地局部缓存TLAB(Thread Local AlLOCATIOn Buffer)c;JVM为了提升对象内存分配的效率c;对于所创建的线程都会分配一块独立的空间TLABc; 其大小由JVM根据运行的情况计算而得c;在TLAB上分配对象时不需要加锁c;因此JVM在给线程的对象分配内存时会尽量的在TLAB上分配c; 在这种情况下JVM中分配对象内存的性能和C基本是一样高效的c;但如果对象过大的话则仍然是直接使用堆空间分配。
TLAB仅作用于新生代的Eden Spacec;所以通常多个小的对象比大的对象分配起来更加高效。
虽然总体来说堆是线程共享的c;但是在堆的年轻代中的Eden区可以分配给专属于线程的局部缓存区TLABc;也可以用来存放对象。相当于线程私有的对象。所以这块内存分配是线程独占的c;读取、使用和回收是线程共享的。
垃圾对象的判定方式?哪些可以做gc root对象 🌟
引用计数器:被引用一次+1c;为0时表示无引用c;可以被回收了。但相互引用但外部无引用的情况下不会被回收c;容易造成内存泄漏。
可达性分析:从gc root开始扫描堆中的对象c;被扫描到的都是存活对象。没有扫到的则需要被回收。gc root对象:虚拟机栈中的引用对象、方法区中的静态属性引用对象、方法区中的常量引用对象和本地方法栈中JNI引用对象、被锁持有的对象、虚拟机内部引用对象等。
引用有哪些?各有什么特点?🌟
强引用(strong reference)
强引用就是我们最常见的普通对象引用(如new 一个对象)c;只要还有强引用指向一个对象c;就表明此对象还“活着”。在强引用面前c;即使JVM内存空间不足c;JVM宁愿抛出OutOfMemoryError运行时错误(OOM)c;让程序异常终止c;也不会靠回收强引用对象来解决内存不足的问题。(不符合垃圾收集)对于一个普通的对象c;如果没有其他的引用关系c;只要超过了引用的作用域或者显式地将相应(强)引用赋值为nullc;就意味着此对象可以被垃圾收集了。但要注意的是c;并不是赋值为null后就立马被垃圾回收c;具体的回收时机还是要看垃圾收集策略的。
软引用(soft reference)
软引用通过SoftReference类实现。 软引用的生命周期比强引用短一些。只有当 JVM 认为内存不足时c;才会去试图回收软引用指向的对象c;即JVM 会确保在抛出 OutOfMemoryError 之前c;清理软引用指向的对象。(垃圾收集可能会执行c;但会作为最后的选择)
软引用可以和一个引用队列(ReferenceQueue)联合使用c;如果软引用所引用的对象被垃圾回收器回收c;Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
后续c;我们可以调用ReferenceQueue的poll()方法来检查是否有它所关心的对象被回收。如果队列为空c;将返回一个null,否则该方法返回队列中前面的一个Reference对象。
应用场景:软引用通常用来实现内存敏感的缓存。如果还有空闲内存c;就可以暂时保留缓存c;当内存不足时清理掉c;这样就保证了使用缓存的同时c;不会耗尽内存。
在使用软引用的时候必须检查引用是否为null。因为垃圾收集器可能在任意时刻回收软引用c;如果不做是否null的判断c;可能会出现NullPointerException的异常。
弱引用(weak reference)
弱引用通过WeakReference类实现。 弱引用的生命周期比软引用短。弱引用指向的对象是一种十分临近finalize状态的情况c;当弱引用被清除的时候c;就符合finalize的条件了。弱引用与软引用最大的区别就是弱引用比软引用的生命周期更短暂。垃圾回收器会扫描它所管辖的内存区域的过程中c;只要发现弱引用的对象c;不管内存空间是否有空闲c;都会立刻回收它。(符合垃圾收集)具体的回收时机还是要看垃圾回收策略的c;因此那些弱引用的对象并不是说只要达到弱引用状态就会立马被回收。
弱引用可以和一个引用队列(ReferenceQueue)联合使用c;如果弱引用所引用的对象被垃圾回收c;Java虚拟机就会把这个弱引用加入到与之关联的引用队列。这一点和软引用一样。
虚引用(phantom reference)
虚引用并不会决定对象的生命周期。即如果一个对象仅持有虚引用c;就相当于没有任何引用一样c;在任何时候都可能被垃圾回收器回收。(符合垃圾收集)不能通过它访问对象c;虚引用仅仅是提供了一种确保对象被finalize以后c;做某些事情的机制(如做所谓的Post-Mortem清理机制)c;也有人利用虚引用监控对象的创建和销毁。
虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时c;如果发现它还有虚引用c;就会在回收对象的内存之前c;把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用c;来了解被引用的对象是否将要被垃圾回收。
利用软引用和弱引用c;我们可以将访问到的对象c;重新指向强引用c;也就是人为的改变了对象的可达性状态。所以对于软引用、弱引用之类c;垃圾收集器可能会存在二次确认的问题c;以确保处于弱引用状态的对象没有改变为强引用。
但是有个问题c;如果我们错误的保持了强引用(比如c;赋值给了static变量)c;那么对象可能就没有机会变回类似弱引用的可达性状态了c;就会产生内存泄露。所以c;检查弱引用指向对象是否被垃圾收集c;也是诊断是否有特定内存泄露的一个思路c;我们的框架使用到弱引用又怀疑有内存泄露c;就可以从这个角度检查。
什么是内存溢出c;什么是内存泄漏 🌟
内存溢出是指程序在申请内存时c;没有足够的空间供其使用。而内存泄漏是指在程序在申请空间后c;无法释放已申请的空间。一次泄漏不会造成什么影响c;但内存泄漏堆积会耗尽所有内存。
OOM如何排查?如何调优gc? 🌟🌟🌟(没经验的话可以说自己会按照什么方式去排查)
1、根据top命令查看各个进程的使用情况c;通过ctrl+m找到消耗最高的几个进程;根据pid查看性能消耗较高的是什么服务在运行;
2、通过jstat虚拟机统计信息命令行工具查看进程的类加载、内存、垃圾收集、即时编译等运行数据。
如 -gc 进程号 间隔时间 输出条数来监视Java堆情况;-gcutil 进程号查看空间占比;
3、jmap内存映射工具来生成堆转储快照c;即dump文件c;jmap -heap 进程号。也可通过-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/export/log/risk-manager-magpie.hprof配置在OOM时自动生成。
调优:堆大小的设置;新生代老年代的比例设置;回收器设置;(仅做了解c;没实践过)
用的是什么jdk版本?什么垃圾回收机制?G1和CMS的回收流程与算法是什么样的?有啥优缺点?🌟🌟🌟🌟(一定要提前了解线上用的是哪种c;面试官会根据这个来引入)
jdk1.8c;用的是G1收集器。
获取最短停顿时间为目标的收集器。基于标记-清除算法实现c;老年代收集器。步骤为:
1、初始标记(CPU停顿c;很短):标记gcroot直连对象c;速度很快;
2、并发标记(收集垃圾和用户程序一起执行):进行GC root对象图遍历的过程;
3、重新标记(CPU停顿c;比初始标记稍长c;但比并发标记短):修正并发标记中因用户线程继续运作而导致标记变更的记录;
4、并发清除-清除算法(不需移动存活对象c;与用户线程同时):清除已死亡对象。
缺点:对CPU资源敏感c;总吞吐量下降;无法处理浮动垃圾;空间碎片对对象分配不利;
面向服务端应用的垃圾收集器c;基于region的堆内存布局c;化整为零c;大小相等的region根据需要作为新生代或老年代c;不同的region采用不同的策略处理c;分代收集器。步骤:
1、初始标记(CPU停顿):标记直连对象;停顿很短c;利用minorGC完成c;实际上并没有额外停顿;
2、并发标记(与用户线程并发执行):可达性分析c;找出要回收的对象;耗时长;
3、最终标记(CPU停顿):处理原始快照SATB中并发阶段结束后的遗留记录;
4、筛选回收:(可根据用户期望的GC停顿时间回收):对各个region的回收价值和成本排序c;基于用户所期望停顿的时间来回收对应的内存。
优点:并行与并发c;多CPU下可通过并发继续执行从而缩短停顿时间;分代收集:不需其他收集器配合就能独立管理整个GC堆;空间整合:整体基于标记-整理c;局部复制来实现;可预测的停顿;
Spring如何实现自定义注解 🌟
Java注解是附加在代码中的一些元信息c;用于一些工具在编译时、运行时进行解析和使用c;起到说明、配置的功能。其本质是继承了Annotation的特殊接口c;其具体实现类是Java运行时生成的动态代理类。反射获取注解时c;返回的是java运行时生成的动态代理对象$Proxy1。
1、创建一个自定义注解和创建一个接口类似。但自定义注解需要使用@interface;
2、添加元注解信息;
3、注解方法不能带有参数;
4、注解方法返回值为基本类型、String、Enums、Annotation或其数组;
5、注解可以有默认值;
@Target(FIELD)
@Retention(RUNTIME)
@Documented
public @interface CarName {
String value() default "";
}
@Target:注解用于什么地方:ElementType.ConstructorFieldlocal_variablemethodpackageparametertype(类或接口或枚举声明);
@Document:注解是否会包含在javadoc中;
@Retention:什么时候用该注解。sourcE(编译阶段就丢弃)/CLASS(类加载时丢弃)/RUNTIME(始终不会丢弃);
@Inherited:定义该注解与子类的关系。子类是否能使用。
Spring beanFactory和Factory Bean的区别? 🌟
BeanFactory:所有SpringBean的容器根接口c;定义了Spring容器的规范c;如getBeanisSingleton等方法;实现类诸如XmlBeanFactory、AbstructBeanFactory;
FactoryBean:Spring容器创建Bean的一种形式c;可让用户通过实现该接口来自定义该Bean接口的实例化过程;让调用者无需关心具体实现细节。方法有getObject/getObjectType/isSingleton;常用类有ProxyFactoryBean(AOP代理Bean).
SpringBean的循环依赖如何处理的?有几级缓存? 🌟🌟🌟
循环依赖c;即A依赖Bc;B又依赖A;或者ABC三者的依赖关系。
解决循环依赖:主要是针对单例Bean对象而言的。原型的会抛出异常提示。
1、创建原始Bean对象
instanceWrapper = createBeanInstance(beAnname, mbd, args);
final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
假设BeanA先被创建。创建后的原始对象是BeanA1。上述代码中的bean即BeanA1。
2、暴露早期引用
addSingletonFactory(beAnname, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
return getEarlyBeanReference(beAnname, mbd, bean);
}
});
通过暴露早期引用c;BeanA指向的原始对象BeanA1创建好后c;就会把原始对象的引用通过ObjectFactory暴露出去c;在getObject的时候c;其getEarlyBeanReference第三个参数就是原始对象暴露的bean。
3、解析依赖
populateBean(beAnname, mbd, instanceWrapper);
解析依赖阶段c;会先对BeanA对象进行属性填充c;当检测到BeanA依赖于BeanB时c;就会先去实例化B。而BeanB也会在此处解析自己的依赖。就可以直接调用BeanFactory.getBean("beanA")方法获取beanA;
4、获取早期引用
protected Object getSingleton(String beAnname, Boolean allowEarlyReferencE) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 从缓存中获取早期引用
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReferencE) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 从 SingletonFactory 中获取早期引用
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beAnname, singletonObject);
this.singletonFactories.remove(beanName);
}}}}
return (singletonObject != NULL_OBjeCT ? singletonObject : null);
}
在上一步中c;getBean("beanA")会先调用getSingleton("beanA")c;尝试从缓存中获取;由于beanA 还未实例化好c;则返回的是nullc;接着getEarlySingletonObject也返回空c;因为早期引用还没有放入缓存中。因此调用singletonFactory.getObjectc;由于已经有了早期引用c;则实际上指向了BeanA1。beanB获取了这个原始对象的引用c;就可以顺利完成实例化c;这样beanA也就能顺利完成实例化了。由于beanB.beanA和beanA指向的是同一个对象beanA1c;所以beanB中的beanA也处于可用状态了。
Spring有三级缓存。处于最上层的缓存是singletonObjectsc;它其中存储的对象是完全创建好c;可以正常使用的beanc;二级缓存叫做earlySingletonObjectsc;它其中存储的bean是仅执行了第一步通过构造方法实例化c;并没有填充属性和初始化c;第三级缓存singletonFactories存储的是对应bean的一个工场。
/** 一级缓存c;保存singletonBean实例: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
/** 二级缓存c;保存早期未完全创建的Singleton实例: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
/** 三级缓存c;保存singletonBean生产工厂: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
Spring尝试获取单例bean时c;首先会在三级缓存中查找。
protected Object getSingleton(String beAnname, Boolean allowEarlyReferencE) {
// 查询一级缓存
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
//若一级缓存内不存在c;查询二级缓存
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReferencE) {
//若二级缓存内不存在c;查询三级缓存
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//若三级缓存中的c;则通过工厂获得对象c;并清除三级缓存c;提升至二级缓存
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beAnname, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBjeCT ? singletonObject : null);
}
当Spring容器试图获得单例bean时c;首先会在三层缓存中查找。查找位置从一级缓存至三级缓存c;注意若三级缓存查找成功c;其返回的bean对象并不一定是完全体c;而可能是仅完成实例化c;还未完成属性装填的提前暴露引用。当三级缓存内都未找到目标c;getSingleton方法则会返回nullc;之后Spring将会执行一系列逻辑c;最终将调用以下方法新创建bean对象:
protected Object doCreateBean(final String beAnname, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
//此处略过 做某些事
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecyclE interfaces like BeanFactoryAware.
// 早期缓存单例对象以解决循环引用问题
// 即使问题是在如BeanFactoryAware的生命周期阶段接口处发生的
// 允许早期暴露参数
Boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposurE) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beAnname +
"' to allow for resolving potential circular references");
}
// 将实例化完成但还未填装属性的bean引用暴露出来c;方法为将beAnname和对应singletonFactory加入第三级缓存Map
addSingletonFactory(beAnname, () -> getEarlyBeanReference(beAnname, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
// 此处省略部分代码
//填装属性c;在此方法内尝试获得循环引用的被引用beanc;方法与自身bean获得流程一致
populateBean(beAnname, mbd, instanceWrapper);
exposedObject = initializeBean(beAnname, exposedObject, mbd);
// 此处省略部分代码
使用三级缓存而非二级缓存并不是因为只有三级缓存才能解决循环引用问题c;其实二级缓存同样也能很好解决循环引用问题。使用三级而非二级缓存并非出于IOC的考虑c;而是出于AOP的考虑c;即若使用二级缓存c;在AOP情形下c;注入到其他bean的c;不是最终的代理对象c;而是原始对象。
AOP的原理、使用场景、是否可以更改入出参?如何实现一个AOP?cglib和jdk代理的区别?🌟🌟🌟(如果有时间c;可以自己手动实践下)
AOPc;面向切面编程c;可以对业务逻辑的各个部分进行隔离c;从而使得业务逻辑各部分之间的耦合度降低c;提高程序的可重用性。SpringAop基于动态代理实现。
①JDK动态代理只提供接口的代理c;不支持类的代理。核心InvocationHandler接口和Proxy类c;InvocationHandler 通过invoke()方法反射来调用目标类中的代码c;动态地将横切逻辑和业务编织在一起;接着c;Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
②如果代理类没有实现 InvocationHandler 接口c;那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library)c;是一个代码生成的类库c;可以在运行时动态的生成指定类的一个子类对象c;并覆盖其中特定方法并添加增强代码c;从而实现AOP。CGLIB是通过继承的方式做的动态代理c;因此如果某个类被标记为finalc;那么它是无法使用CGLIB做动态代理的。
可以通过ProceedingJoinPoint.getArgs()获取方法调用参数c;对其进行修改c;然后通过ProceedingJoinPoint.proceed(Object[] args)来传入修改过的参数继续调用。
AOP的通知类型
前置通知(Before):在目标方法被调用之前调用通知功能后置通知(After):在目标方法完成之后调用通知c;此时不会关心方法的输出是什么返回通知(After-returning):在目标方法成功执行之后调用通知异常通知(After-throwing):在目标方法抛出异常后调用通知环绕通知(Around):通知包裹了被通知的方法c;在被通知的方法调用之前和之后执行自定义的行为。
后置通知和返回通知的区别是c;后置通知是不管方法是否有异常c;都会执行该通知;而返回通知是方法正常结束时才会执行。
对于基础数据类型的话c;==比较的是值;对于引用数据类型c;==比较的是引用的地址;
equals如果是Object默认的c;则是比较地址值;否则是根据重写的方法来比较相应的内容。
字符串的equals比较就是比较的内容。
因为在-127-128的范围内c;Integer会自动拆箱c;所以比较的是值。
订单ID如何生成以保证唯一? 🌟🌟(这个和电商业务的项目关联比较大)
可以采用自增IDc;也可以采用雪花算法。
雪花算法是Twitter开源的分布式ID生成算法c;以64bit的Long型作为全局唯一IDc;引入了时间戳c;基本上保持自增。
第一部分:1位c;为0c;无意义c;保持自增ID是正数;
第二部分:41位c;时间戳;多达2^41-1个毫秒值c;大概69年;
第四部分:5位c;机器IDc;每个机房有2^5个机器c;所以第三第四部分代表1024个机器;
第五部分:12位c;序号c;某机房某台机器这一毫秒内生成的ID序号c;共2^12-1个(4096)c;如是第12个请求c;就以12作为最后几位的数字。
优点:高性能高可用c;生成时不依赖数据库c;完全内存中生成;容量大c;每秒可生成百万的自增IDc;ID自增存入数据库c;索引效率高;
缺点:依赖与系统时间的一致性c;如系统被回调时间c;则可能造成ID重复。
jsf架构用的是什么?rpc的哪种?rpc接口和http接口的区别?🌟(了解即可)
rpc框架。常用的rpc框架有thrift、dubbo、SpringCloud等。我们用的应该是dubbo.
RPC接口与http对比
1、传输协议。RPC:可以基于TCP协议c;也可以基于http协议;http:基于http协议
2、传输效率。RPC:使用自定义的TCP协议c;可以让请求报文体积更小c;或者使用http2协议c;也可以很好的减少报文的体积c;提高传输效率;http:如果是基于http1.1的协议c;请求中会包含很多无用的内容c;如果是基于http2.0c;那么简单的封装下是可以作为一个RPC来使用的。
3、性能消耗。RPC:可以基于thrift实现高效的二进制传输;http:大部分是通过json来实现的c;字节大小和序列化耗时都比thrift要更消耗性能。
4、负载均衡。RPC:基本都自带了负载均衡策略;http:需要配置Nginxc;HAProxy来实现。
5、服务治理。RPC:能做到自动通知c;不影响上游;http:需要事先通知c;修改Nginx/HAProxy配置。
网络有了解么?有哪些层?各是干啥的?IP在哪一层?TCP/UDP在哪一层?🌟 (除此之外还有三次握手四次挥手c;知道过程c;知道为什么不能两次为什么不能四次等)
第一层:应用层。定义了用于在网络中进行通信和传输数据的接口; (五层是表示层和会话层合并到了应用层)http 第二层:表示层。定义不同的系统中数据的传输格式c;编码和解码规范等; 第三层:会话层。管理用户的会话c;控制用户间逻辑连接的建立和中断; 第四层:传输层。管理着网络中的端到端的数据传输; TCP/UDP 第五层:网络层。定义网络设备间如何传输数据; IP 第六层:链路层。将上面的网络层的数据包封装成数据帧c;便于物理层传输; 第七层:物理层。这一层@R_489_11380@是传输这些二进制数据。
多线程时如何保证一个变量的可见性? 🌟🌟🌟🌟
synchronized关键字
synchronized语义规范:
1.进入同步块前c;先清空工作内存中的共享变量c;从主内存中重新加载;
2.解锁前必须把修改的共享变量同步回主内存。
synchronized如何做到线程安全的?
1.锁机制保护共享资源c;只有获得锁的线程才可操作共享资源;
2.synchronized语义规范保证了修改共享资源后c;会同步回主存c;就做到了线程安全。
volatile关键字(比synchronized更轻量级c;性能更好)
volatile语义规范:
1.使用volatile变量时c;必须重新从主内存加载c;并且read 、load是连续的。
2.修改volatile变量后c;必须立马同步回主内存c;并且store、 write是连续的。
volatile能做到线程安全吗?
1.不能c;因为它没有锁机制c;线程可并发操作共享资源。除非对变量的写操作不依赖于当前值。
final关键字:修饰类、方法、变量c;类不能被继承、方法不能重写、变量只能赋值一次
synchronized如何解决并发问题的? 🌟🌟🌟🌟
类锁:所有对象共用一个锁;对象锁:一个对象一把锁c;多个对象多把锁。
synchronized修饰实例方法c;对当前实例对象加锁;修饰静态方法c;对当前类加锁;修饰代码块c;对synchronized括号内的对象/类加锁。
方法块的同步是隐式的c;JVM通过方法常量池中的方法表结构中的ACC_SYNCHRONIZED访问标识区分一个方法是否是同步方法c;如果是c;执行线程先持有monitorc;执行完再释放monitor。
代码块同步是利用monitorenter和monitorexit这两个字节码指令来时间的。当前线程试图获取monitor对象的所有权c;未加锁或已持有c;则锁计数器+1c;否则-1c;为0时释放c;获取失败则进入阻塞c;等待其他线程释放锁。
synchronized修饰静态变量会怎样?🌟🌟🌟
无论synchronized关键字加在方法上还是对象上c;它取得的锁都是对象c;而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。
所以加在静态变量上就是获取类锁。
锁升级是什么?有锁降级么?为什么是重量级锁?是否存在内核态切换? 🌟🌟🌟🌟
线程的阻塞和唤醒需要CPU从用户态转为内核态。为了减少获得锁和释放锁带来的性能消耗c;JDK1.6引入了偏向锁和轻量级锁c;一共有4种状态c;从低到高依次为:无锁状态、偏向锁状态、轻量级锁、重量级锁状态c;会随着竞争情况逐渐升级。锁升级但不能降级c;是为了提高锁获得和释放的效率。
偏向锁:无锁竞争情况下为减少锁竞争的资源开销c;引入偏向锁。线程获得了锁c;则进入偏向模式c;会在对象头中记录对应的线程IDc;再次请求锁时c;则无需任何同步操作c;只需检查对象头的线程ID是否匹配c;省去大量有关锁申请的操作。偏向锁不会主动释放锁。如果不匹配c;则需看对象头的线程是否存活c;若不存活c;则锁对象变成无锁状态c;其他线程可以竞争变为偏向锁;如存活c;则需看线程1当前栈帧c;如果还需要继续持有该锁对象c;则暂停线程1c;撤销偏向锁c;升级为轻量级锁;否则变成无锁状态重新偏向。
轻量级锁:偏向锁升级而来。会先把锁对象的对象头复制一份到当前线程的栈帧中的锁空间内c;然后CAS把对象头重的内容替换为当前线程存储的锁记录的地址;如果此时另一个线程的CAS失败c;则尝试自旋等待1释放锁(CPU消耗)。若自旋10次货100次还未等到c;或又有线程来竞争c;则轻量级锁就会膨胀成重量级锁。
重量级锁:除了拥有锁的线程外都将阻塞c;防止CPU空转。
为啥重量级?JDK1.6以前c;重量级锁是需要依靠操作系统来实现互斥锁的c;这导致大量上下文切换c;消耗大量CPUc;影响性能。阻塞和唤醒操作又涉及到了上下文操作c;大量消耗CPUc;降低性能。1.6引入锁升级。
ReentrantLock什么场景下使用?和synchronized有什么区别? 🌟🌟🌟🌟
就是因为Synchronized性能低c;有人就开发了ReentrantLock可重入锁c;大大提高了性能。
1、底层实现:synchronized是JVM层面的锁c;Java关键字c;同步块或同步方法中调用c;涉及锁升级;ReenTrantLock是JDK1.5以后的JUC提供的API层面的锁c;利用ASQ实现的c;使用改进的CLH队列c;实现CAS自旋+阻塞+唤醒。
2、synchronized不需主动释放c;而ReenTrantLock需要手动释放c;否则会出现死锁;lock/unlock配合try/finally来完成c;使用释放更灵活;
3、synchronized不可中断c;ReenTrantLock可中断;
4、synchronized为非公平锁。ReenTrantLock默认非公平c;也可以通过构造函数设置为公平锁;trylock可设置尝试等待;
5、synchronized不可绑定条件Conditionc;而ReenTrantLock可以c;结合await/singal实现线程的精确唤醒。
6、synchronzied锁的是对象c;锁是保存在对象头里面的c;根据对象头数据来标识是否有线程获得锁/争抢锁;ReentrantLock锁的是线程c;根据进入的线程和int类型的state标识锁的获得/争抢。
场景:1、若发现某操作已经在执行c;则trylock(5,SECOND)尝试等待c;等待超时则不执行;2、公平锁c;等待着一个个执行;3、发现某操作在执行中c;则不再执行trylock();4、lockInterruptibly中断正在执行的操作立刻释放锁进入下一个操作。
线程池参数有哪些?分别代表什么含义?如何进行调优?线程复用是怎么实现的?队列中无线程时的核心线程处于什么状态? 🌟🌟🌟(最好自己实践下c;怎么写的c;有哪些分类c;参数等处理)
参数及其含义
1、corePoolSize:核心线程数。核心线程会一直存活c;即使没有任务需要执行。当线程数小于核心线程数时c;即使有线程空闲c;线程池也会优先创建新线程处理;设置allowCoreThreadTimeout=true(默认false)时c;核心线程会超时关闭。
2、workQueue:存放待执行任务的队列:当提交的任务数超过核心线程数大小后c;再提交的任务就存放在这里。它仅仅用来存放被 execute 方法提交的 Runnable 任务。
3、@H_763_3@maximumPoolSize:最大线程数。当线程数>=corePoolSizec;且任务队列已满时。线程池会创建新线程来处理任务;当线程数=@H_763_3@maximumPoolSizec;且任务队列已满时c;线程池会拒绝处理任务而抛出异常
4、 keepAliveTime:线程空闲时间。当线程空闲时间达到keepAliveTime时c;线程会退出c;直到线程数量=corePoolSize。如果allowCoreThreadTimeout=truec;则会直到线程数量=0;
5、allowCoreThreadTimeout:允许核心线程超时;
6、rejectedExecutionHandler:任务拒绝处理器;两种情况会拒绝处理任务:当线程数已经达到@H_763_3@maximumPoolSizec;切队列已满c;会拒绝新任务;当线程池被调用shutdown()后c;会等待线程池里的任务执行完毕c;再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务c;会拒绝新任务。线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置默认是AbortPolicyc;会抛出异常。ThreadPoolExecutor类有几个内部实现类来处理这类情况: - AbortPolicy 丢弃任务c;抛运行时异常 - CallerRunsPolicy 执行任务 - DiscardPolicy 忽视c;什么都不会发生 - DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务 实现RejectedExecutionHandler接口c;可自定义处理器
新提交一个任务时的处理流程
1、如果当前线程池的线程数还没有达到基本大小(poolSize < corePoolSizE)c;无论是否有空闲的线程新增一个线程处理新提交的任务;
2、如果当前线程池的线程数大于或等于基本大小(poolSize >= corePoolSizE) 且任务队列未满时c;就将新提交的任务提交到阻塞队列排队c;等候处理workQueue.offer(command);
3、如果当前线程池的线程数大于或等于基本大小(poolSize >= corePoolSizE) 且任务队列满时;
3.1、当前poolSize<maximumPoolSizec;那么就新增线程来处理任务;
3.2、当前poolSize=maximumPoolSizec;那么意味着线程池的处理能力已经达到了极限c;此时需要线程池的饱和策略RejectedExecutionHandler来拒绝新增加的任务。
优化时主要针对corePoolSize、maximumPoolSize、workQueue这三个参数。
corePoolSize:CPU密集型CPU数+1(偶尔的内存页失效等额外的线程也能确保CPU时钟周期不会被浪费)c;IO密集型CPU*2(一个等待IO时其他的还可以继续执行);最大线程数=CPU*25;
实现线程复用:核心线程会一直处于阻塞状态c;等待任务来时被唤醒使用。
为什么不建议使用Executors创建线程池的处理? 🌟🌟
不允许使用Executors去创建c;而是通过ThreadPoolExecutor方式c;一是明确线程池运行原理c;二是规避资源耗尽风险c;Executors的弊端在于其等待队列长度最大是Integer.max_valuec;可能会堆积大量的线程/请求而导致OOM。
@H_45_11@mySQL慢查询/SQL优化是怎么做的?联合索引(a,b,C)中为何使用c不走索引?为什么不使用多个单列索引? 🌟🌟🌟🌟
慢SQL优化:1、检查索引是否生效c;是否需要重建索引;2、是否数据量太大c;考虑分批次读区;3、是否返回了不必要的字段c;尽量不要用SELEct *;4、查询语句是否合理;
最左前缀匹配原则c;R_476_11845@ysql会一直从左向右匹配直到遇到范围查询(>、<、between、likE)就停止匹配c;比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引c;d是用不到索引的c;如果建立(a,b,d,C)的索引则都可以用到c;a,b,d的顺序可以任意调整c;由优化器进行优化;
联合索引(a,b,C)实际上是(a)/(a,b)/(a,b,C)。所以c是无法走索引的。
对于多个单列索引来说c;优化器会计算成本c;选择成本最小的索引c;而不是走所有的索引。
脏读:脏读指的是读到了其他事务未提交的数据c;未提交意味着这些数据可能会回滚c;也就是可能最终不会存到数据库中c;也就是不存在的数据。读到了并一定最终存在的数据c;这就是脏读。
可重复读可重复读指的是在一个事务内c;最开始读到的数据和事务结束前的任意时刻读到的同一批数据都是一致的。通常针对数据更新(updatE)操作。
不可重复读:对比可重复读c;不可重复读指的是在同一事务内c;不同的时刻读到的同一批数据可能是不一样的c;可能会受到其他事务的影响c;比如其他事务改了这批数据并提交了。通常针对数据更新(updatE)操作。
幻读:幻读是针对数据插入(INSERT)操作来说的。假设事务A对某些行的内容作了更改c;但是还未提交c;此时事务B插入了与事务A更改前的记录相同的记录行c;并且在事务A提交之前先提交了c;而这时c;在事务A中查询c;会发现好像刚刚的更改对于某些数据未起作用c;但其实是事务B刚插入进来的c;让用户感觉很魔幻c;感觉出现了幻觉c;这就叫幻读。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 | 可能 | 可能 | 可能 |
读已提交(默认) | 不可能 | 可能 | 可能 |
可重复读 | 不可能 | 不可能 | 可能 |
串行化 | 不可能 | 不可能 | 不可能 |
SQL如何保证并发的?讲一讲MVCC。MVCC能否读到另一个事务提交的数据?两个事务更新时如何进行版本控制的? 🌟🌟🌟🌟
读未提交c;它是性能最好c;也最野蛮c;因为它压根儿就不加锁c;所以根本谈不上什么隔离效果c;可以理解为没有隔离。
再来说串行化。读的时候加共享锁c;其他事务可以并发读c;但是不能写。写的时候加排它锁c;其他事务不能并发写也不能并发读。
实现可重复读c;采用MVCC多版本并发控制。可重复读是在事务开始的时候生成一个当前事务全局性的快照c;而读提交则是每次执行语句的时候都重新生成一次快照。
对于一个快照来说c;它能够读到那些版本数据c;要遵循以下规则:当前事务内的更新c;可以读到;版本未提交c;不能读到;版本已提交c;但是却在快照创建后提交的c;不能读到;版本已提交c;且是在快照创建前提交的c;可以读到;
并发写问题:存在这的情况c;两个事务c;对同一条数据做修改。最后结果应该是时间靠后的那个。并且更新之前要先读数据c;这里所说的读和上面说到的读不一样c;更新之前的读叫做“当前读”c;总是当前版本的数据c;也就是多版本中最新一次提交的那版。假设事务A执行 update 操作c; update 的时候要对所修改的行加行锁c;这个行锁会在提交之后才释放。而在事务A提交之前c;事务B也想 update 这行数据c;于是申请行锁c;但是由于已经被事务A占有c;事务B是申请不到的c;此时c;事务B就会一直处于等待状态c;直到事务A提交c;事务B才能继续执行c;如果事务A的时间太长c;那么事务B很有可能出现超时异常。
解决幻读:解决幻读用的也是锁c;叫做间隙锁c;R_476_11845@ySQL 把行锁和间隙锁合并在一起c;解决了并发写和幻读的问题c;这个锁叫做 Next-Key锁。
假设现在表中有两条记录c;并且 age 字段已经添加了索引c;两条记录 age 的值分别为 10 和 30。
在事务A提交之前c;事务B的插入操作只能等待c;这就是间隙锁起得作用。当事务A执行update user set name='风筝2号’ where age = 10;
的时候c;由于条件 where age = 10 c;数据库不仅在 age =10 的行上添加了行锁c;而且在这条记录的两边c;也就是(负无穷,10]、(10,30]这两个区间加了间隙锁c;从而导致事务B插入操作无法完成c;只能等待事务A提交。不仅插入 age = 10 的记录需要等待事务A提交c;age<10、10<age<30 的记录页无法完成c;而大于等于30的记录则不受影响c;这足以解决幻读问题了。这是有索引的情况c;如果 age 不是索引列c;那么数据库会为整个表加上间隙锁。所以c;如果是没有索引的话c;不管 age 是否大于等于30c;都要等待事务A提交才可以成功插入。
在不加事务的情况下c;更新数据会加锁么?🌟
更新时得看是否走索引。如果走索引的话就锁行。如果不走索引c;R_476_11845@ySQL 无法直接定位到这行数据。MySQL 会为这张表中所有行加行锁c;没错c;是所有行。但在加上行锁后c;R_476_11845@ySQL 会进行一遍过滤c;发现不满足的行就释放锁c;最终只留下符合条件的行。虽然最终只为符合条件的行加了锁c;但是这一锁一释放的过程对性能也是影响极大的。所以c;如果是大表的话c;建议合理设计索引c;如果真的出现这种情况c;那很难保证并发度。
Innodb自增ID的原理?是否会加锁?重启后ID如何处理的? 🌟🌟
AutoIncrement最新值的获取涉及到锁c;有三种锁模式c;对应 innodb_autoinc_lock_mode 的值c; 0 c;1c;2. MySQL 8.0 之后c;默认为 2c; 在这之前c;默认为 1。innodb_autoinc_lock_mode=0(traditional lock mode):传统的auto_increment机制c;这种模式下所有针对auto_increment列的插入操作都会加表级别的AUTO-INC锁c;在语句执行结束则会释放c;分配的值也是一个个分配c;是连续的c;正常情况下也不会有间隙(当然如果事务rollBACk了这个auto_increment值就会浪费掉c;从而造成间隙)。
innodb_autoinc_lock_mode=1(consecutive lock mode):这种情况下c;针对未知数量批量插入(例如INSERT ... SELECT, replaCE ... SELECT和LOAD DATA)才会采用AUTO-INC锁这种方式c;而针对已知数量的普通插入c;则采用了一种新的轻量级的互斥锁来分配auto_increment列的值。这种锁c;只会持续到获取一定数量的 idc;不会等待语句执行结束在释放。也就是拿轻量级锁提前分配好所需数量的 id 之后释放锁c;再执行语句。当然c;如果其他事务已经持有了AUTO-INC锁c;则simple inserts需要等待。当然c;这种情况下c;可能产生的间隙更多。
innodb_autoinc_lock_mode=2(interleaved lock mode):这种模式下任何类型的inserts都不会采用AUTO-INC锁c;性能最好c;但是在同一条语句内部产生auto_increment值间隙。其实这个就是所有语句对于同一个值进行 Compare-And-Set 更新c;类似于乐观锁。这个锁模式对statement-based Replication的主从同步都有一定问题。因为同步传输的是语句c;而不是行值c;语句执行后的差异导致主从可能主键不一致。 AutoIncrement 计数器在 @H_763_3@mySQL 8.0 之前c;存储在内存中c;每次启动时通过以下语句初始化:SELECT MAX(ai_col) FROM table_name FOR updatE;在 MySQL 8.0 之后c;持久化存储到磁盘。通过每次更新写入 Redo Logc;并在检查点刷入 innodb 引擎表中记录下来。所以c;在MySQL 8.0 之前c;如果 rollBACk 导致某些值没有使用c;重启后c;这些值还是会使用。但是在 MySQL 8.0 之后就不会了。
一般情况下c;一张表最大能放多少数据量级? 🌟🌟🌟🌟
InnoDB存储引擎也有自己的最小储存单元--页(Page)c;一个页的大小是16K。页可以用于存放数据也可以用于存放键值+指针c;在B+树中叶子节点存放数据c;非叶子节点存放键值+指针。
假设一行数据的大小是1kc;那么一个页可以存放16行这样的数据。
假设键值是Long型的自增IDc;则为8字节c;指针默认是6字节c;这样非叶子节点的一个键值+指针为14字节c;一页可存放16K/14=1170。
假设B+数高度是Nc;则叶子节点数= 1170^(n-1);所以数据行数=叶子节点数*16=16*1170^(N-1);
一般层高都是3-5层c;按照3层算c;数据行数=16*1170*1170=21902400c;2千万量级。
性别适合索引么? 🌟
访问索引需要付出额外的IO开销c;性别不适合做聚簇索引。但普通索引的话相当于拿到的是对应的IDc;还需回表找数据。假如要从表的100万行数据中取几个数据c;那么利用索引迅速定位c;访问索引的这IO开销就非常值了。但如果是从100万行数据中取50万行数据c;就比如性别字段c;那相对需要访问50万次索引c;再访问50万次表c;加起来的开销并不会比直接对表进行一次完整扫描小。
为什么不建议使用SELEct *? 🌟
1c; 业务方面
a.假设某一天修改了表结构c;如果用SELEct *c;返回的数据必然会会变化c;客户端是否对数据库变化作适配c;是否所有地方都做了适配c;这都是问题。
b. 可能会存在不需要的列c;传输过程中有不必要的性能损耗;
c. 客户端解析查询结果也需要更多损耗
2c;数据库原理方面原因(此处以MySql为例):
a. 使用了SELEct c;必然导致数据库需要先解析代表哪写字段c;从数据字段中将*转化为具体的字段含义c;存在性能开销;
b. 不可能对所有字段建索引c;在索引优化必然会有局限性c;导致查询时性能差;
es如何做到高并发读写与同步的?如何保证数据的一致性? 🌟🌟🌟🌟
es分为一个主节点和多个数据节点/候选主节点、协调节点c;每个节点中存储数据c;参与索引与搜索功能。每个节点包含主分片和副本c;且同一主分片与其副本不在同一节点上。在进行数据写入时c;每个节点都可以写c;但根据路由规则计算后只写入相应的主分片c;并并发同步到副本当中c;同步完成后才通知成功。为了保证写入速度c;采用延迟写策略c;先写内存c;每隔1s写入文件缓存c;这时就可以被读取到了c;再等5s写磁盘。读取时是随机读取。同时es集群可扩展上百台的服务器c;所以能够做到高并发的读写与同步。
为了保证数据的一致性c;首先数据同步时要副本都成功后才返回成功;其次c;添加事务日志translog记录还未写入到磁盘的数据c;translog先写入os cache的c;默认每隔5秒刷一次到磁盘中去c;因为日志每隔5秒从文件缓存系统flush一次到磁盘c;所以最多会丢5秒的数据。
es的使用场景有哪些?有哪些关键字c;作用是什么? 🌟
检索、搜索、统计数据。
1、term查询用来查询某个关键字在文档里是否存在c;所以Term需要是文档切分的一个关键字;
2、terms查询用来查询某几个个关键字在文档里是否存在c;Terms可以同时对一个字段检索多个关键字;
3、match查询和queryString有点类似c;就是先对查询内容做分词c;然后再去进行匹配;
4、match_all的查询方式简单粗暴c;就是匹配所有c;不需要传递任何参数;
5、match_phrase属于短语匹配c;能保证分词间的邻近关系c;相当于对文档的关键词进行重组以匹配查询内容c;对于匹配了短语"森 小 林"的文档c;下面的条件必须为true:森 、小、 林必须全部出现在某个字段中;小的位置必须比森的位置大1;林的位置必须比森的位置大2; 6、multi_match表示多字段匹配关键词c;我们试着在Name和sex里找c;只要包含男的我们就返回该数据;
7、range查询c;顾明思意就是范围查询c;例如我们这里要查询年龄在19到28的人的数据;gt: > 大于(greater than)lt: < 小于(less than)gte: >= 大于或等于(greater than or equal to);lte: <= 小于或等于(less than or equal to);
8、exists允许你过滤文档c;只查找那些在特定字段有值的文档c;无论其值是多少c;为了验证c;需要注意c;这里的有值即使是空值也算有值c;只要不是null;
9、wildcardc;通配符查询c;其中【?】代表任意一个字符【*】代表任意的一个或多个字符c;例如我们想查名字结尾为林的文档:
10、prefixc;前缀查询c;我们为了找到所有姓名以森开头的文档c;可以使用这种方式:
11、regexpc;正则匹配c;ES兼容了正则的查询方式
12、fuzzyc;纠错检索c;让输入条件有容错性c;例如我要检索性别为woman的数据c;但是我拼错了c;输入的是wmanc;用fuzzy照样可以检索到;
13、filter:只过滤符合条件的文档c;与must唯一的区别是:不计算相关系得分c;但因为有缓存c;所以性能高;
14、must:用must连接的多个条件必须都满足c;是and的关系c;逻辑&与的关系;
15、should:用should连接的多个条件只要满足一个即可c;是or的关系c;逻辑||或的关系;
16、must_not:用must_not绑定的条件表示一定不能满足该条件c;是not的关系c;逻辑^非的关系。用这些条件的连接词将多个查询条件连接起来就能进行复杂的复合查询了。Boolean在同时有must和should的时候c;should就被过滤掉了c;因为Should表示有也可以没有也可以c;所以我们常把must放到should字句里c;确保should的子句能执行;
es的分片和副本数设置的是多少?16:1?有几个主节点?分片副本的作用? 🌟🌟
Elasticsearch提供了将索引划分成多份的能力c;这些份就叫做分片。每个分片本身也是一个功能完善并且独立的“索引”c;这个“索引”可以被放置到集群中的任何节点上c;允许水平分割/扩展内容容量c;在分片之上进行分布式的、并行的操作来提高性能/吞吐量c;提供了高可扩展及高并发能力。
分片故障时@R_223_10772@机制非常必要。Elasticsearch允许创建分片的一份或多份拷贝c;这些拷贝叫做复制分片。在分片/节点失败的情况下c;提供了高可用性。因为这个原因c;注意到复制分片从不与原/主要(original/priMary)分片置于同一节点上是非常重要的c;同时复制分片还能提提高并发量。所以复制分片的作用是高可用高并发c;副本越多消耗越大c;也越保险c;集群的可用性就越高。
准实时的原因是什么? 🌟🌟🌟🌟
为了保证写入速度c;采用延迟写策略c;先写内存c;此时不可被读取c;每隔1s写入文件缓存形成新的段文件c;这时就可以被读取到了c;所以会有1s的时间差c;因此被称为准实时。
es的脑裂现象是什么?如何解决的? 🌟🌟🌟🌟
脑裂现象是指在选举过程中出现多个master竞争时c;主分片和副本的识别也发生了分歧c;对一些分歧中的分片标识为了坏片c;更新的时候造成数据混乱或其它非预期结果。其实按照选举规则c;能选举出一个确定的master是一定的c;就算clusterstateVersion一样c;也不可能有两个节点id一致c;总会有大有小c;按照此规则c;所有节点其实是能达成共识的。
“脑裂”问题可能有以下几个原因造成:
网络问题:集群间的网络延迟导致一些节点访问不到masterc;认为master挂掉了从而选举出新的masterc;并对master上的分片和副本标红c;分配新的主分片;
节点负载:主节点的角色既为master又为datac;访问量较大时可能会导致ES停止响应(假死状态)造成大面积延迟c;此时其他节点得不到主节点的响应认为主节点挂掉了c;会重新选取主节点。
内存回收:主节点的角色既为master又为datac;当data节点上的ES进程占用的内存较大c;引发JVM的大规模内存回收c;造成ES进程失去响应。
为了避免脑裂现象的发生c;我们可以从根源着手通过以下几个方面来做出优化措施:
适当调大响应时间c;减少误判:通过参数discovery.zen.ping_timeout设置节点状态的响应时间c;默认为3sc;可以适当调大c;如果master在该响应时间的范围内没有做出响应应答c;判断该节点已经挂掉了。调大参数(如6sc;discovery.zen.ping_timeout:6)c;可适当减少误判。
角色分离:即是上面我们提到的候选主节点和数据节点进行角色分离c;这样可以减轻主节点的负担c;防止主节点的假死状态发生c;减少对主节点“已死”的误判。
选举触发:在候选集群中的节点的配置文件中设置参数discovery.zen.munimum_master_nodes的值c;这个参数表示在选举主节点时需要参与选举的候选主节点的节点数c;默认值是1c;官方建议取值(master_eligibel_nodes/2) + 1c;这样做既能防止脑裂现象的发生c;也能最大限度地提升集群的高可用性c;因为只要不少于discovery.zen.munimum_master_nodes个候选节点存活c;选举工作就能正常进行。当小于这个值的时候c;无法触发选举行为c;集群无法使用c;不会造成分片混乱的情况。
什么是倒排索引? 🌟🌟
传统方法是根据文件找到该文件的内容c;在文件内容中匹配搜索关键字c;这种方法是顺序扫描方法c;数据量大、搜索慢。倒排索引结构是根据内容(词语)找文档。词典就是term的集合c;每个term【域和关键词的组合】会索引一连串满足条件的文档idc;检索时通过term检索可以找到这串idc;进而找到满足条件的文档集合
redis的优缺点是什么? 🌟🌟
优点
1、高性能的key-value内存数据库 – 读速度是 110000 次/s,写速度是 81000 次/s 。
2、丰富的数据类型 – String, List, Hash, Set 及zset 数据类型操作。
3、原子 – redis 的所有操作都是原子性的c;意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务c;即原子性c;通过 MULTI 和 EXEC指令包起来。
4、丰富的特性 – 可用于缓存c;消息c;按 key 设置过期时间c;过期后将会自动删除。
5、redis 运行在内存中但是可以持久化到磁盘c;所以在对不同数据集进行高速读写时需要权衡内存c;因为数据量不能大于硬件内存。在内存数据库方面的另一个优点是c;相比在磁盘上相同的复杂的数据结构c;在内存中操作起来非常简单c;这样 redis可以做很多内部复杂性很强的事情。同时c;在磁盘格式方面他们是紧凑的以追加的方式产生的c;因为他们并不需要进行随机访问。
缺点
1、数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此redis适合的场景主要局限在较小数据量的高性能操作和运算上。
2、主机宕机c;宕机前有部分数据未能及时同步到从机c;切换IP后还会引入数据不一致的问题c;降低了系统的可用性。
3、可能会存在缓存击穿、缓存雪崩、缓存穿透等问题;
redis为什么这么快? 🌟🌟🌟🌟🌟
1、redis是基于内存的c;内存的读写速度非常快c; 数据存放在内存中c;内存的响应时间大约是 100纳秒 c;这是redis每秒万亿级别访问的重要基础。
2、redis是单线程的c;省去了很多上下文切换线程的时间c;避免了线程切换和竞态产生的消耗;
3、redis使用多路复用技术c;可以处理并发的连接c;多个socket连接复用redis的单个存取线程。IO多路复用内部实现采用epollc;采用了epoll+自己实现的简单的事件框架。epoll中的读、写、关闭、连接都转化成了事件c;然后利用epoll的多路复用特性实现了对多个事件的监控c;但在处理时通过队列一个个单线程处理c;绝不在io上浪费一点时间。
4、数据结构简单c;操作节省时间。
高可用有哪些方式?当存储不了数据时怎么处理? 🌟🌟(熟记淘汰策略)
redis 实现高并发主要依靠主从架构c;一主多从c;一般来说c;很多项目其实就足够了c;单主用来写入数据c;单机几万 QPSc;多从用来查询数据c;多个从实例可以提供每秒 10w 的 QPS。如果想要在实现高并发的同时c;容纳大量的数据c;那么就需要 redis 集群c;使用 redis 集群之后c;可以提供每秒几十万的读写并发。redis 高可用c;如果是做主从架构部署c;那么加上哨兵就可以了c;就可以实现c;任何一个实例宕机c;可以进行主备切换。
如果达到设置的上限c;redis 的写命令会返回错误信息(但是读命令还可以正常返回。)或者你可以将 redis 当缓存来使用配置淘汰机制c;当 redis 达到内存上限时会冲刷掉旧的内容。
redis 提供 6 种数据淘汰策略:
volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:从数据集(server.db[i].Dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db[i].Dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据
分布式锁是怎么用的?是否存在缺点/问题?如果使用setNx命令有什么问题?🌟🌟🌟🌟
redis的set的带过期时间的命令来使用的c;在删除时需要先判断是否是自己来操作的。
缺点
1、客户端1得到了锁c;因为网络问题或者GC等原因导致长时间阻塞c;然后业务程序还没执行完锁就过期了c;这时候客户端2也能正常拿到锁c;可能会导致线程安全的问题;(可在一定时间内延长过期时间)
2、redis服务器时钟漂移问题:如果redis服务器的机器时钟发生了向前跳跃c;就会导致这个key过早超时失效c;比如说客户端1拿到锁后c;key的过期时间是12:02分c;但redis服务器本身的时钟比客户端快了2分钟c;导致key在12:00的时候就失效了c;这时候c;如果客户端1还没有释放锁的话c;就可能导致多个客户端同时持有同一把锁的问题。
3、单点实例安全问题:如果redis是单master模式的c;当这台机宕机的时候c;那么所有的客户端都获取不到锁了c;为了提高可用性c;可能就会给这个master加一个slavec;但是因为redis的主从同步是异步进行的c;可能会出现客户端1设置完锁后c;R_476_11845@aster挂掉c;slave提升为masterc;因为异步复制的特性c;客户端1设置的锁丢失了c;这时候客户端2设置锁也能够成功c;导致客户端1和客户端2同时拥有锁。该问题可以通过redLock算法解决。
使用setNx命令加锁则需手动加过期时间c;并发时会出现未写入过期时间而导致无法释放锁。
还知道其他哪些分布式锁?如何选型? 🌟🌟🌟🌟🌟
基于数据库实现分布式锁
在数据库中创建一个表c;表中包含方法名等字段c;并在方法名字段上创建唯一索引c;想要执行某个方法c;就使用这个方法名向表中插入数据c;成功插入则获取锁c;执行后删除对应的行数据释放锁。
缺点:不具备可重入性;不具备阻塞锁特性c;需手动循环;没有锁失效机制;
基于缓存(redis等)实现分布式锁
优点:redis有很高的性能;redis命令对此支持较好c;实现起来比较方便。
setNx/expire/delete key;也可以用SEt命令。2.6.9的版本就支持了。
Rdis只保证最终一致性c;副本间的数据复制是异步进行(Set是写c;Get是读c;REIDs集群一般是读写分离架构c;存在主从同步延迟情况)c;主从切换之后可能有部分数据没有复制过去可能会丢失锁情况c;故强一致性要求的业务不推荐使用REIDsc;推荐使用zk。redis集群各方法的响应时间均为最低。随着并发量和业务数量的提升其响应时间会有明显上升(公有集群影响因素偏大)c;但是极限qps可以达到最大且基本无异常。
基于Zookeeper实现分布式锁
ZooKeeper是一个为分布式应用提供一致性服务的开源组件c;它内部是一个分层的文件系统目录树结构c;规定同一个目录下只能有一个唯一文件名。基于ZooKeeper实现分布式锁的步骤如下:
1、创建一个目录mylock;
2、线程A想获取锁就在mylock目录下创建临时顺序节点;
3、获取mylock目录下所有的子节点c;然后获取比自己小的兄弟节点c;如果不存在c;则说明当前线程顺序号最小c;获得锁;
4、线程B获取所有节点c;判断自己不是最小节点c;设置监听比自己次小的节点;
5、线程A处理完c;删除自己的节点c;线程B监听到变更事件c;判断自己是不是最小的节点c;如果是则获得锁。
优点:具备高可用、可重入、阻塞锁特性c;可解决失效死锁问题。
缺点:因为需要频繁的创建和删除节点c;性能上不如redis方式。
使用ZooKeeper集群c;锁原理是使用ZooKeeper的临时节点c;临时节点的生命周期在Client与集群的Session结束时结束。因此如果某个Client节点存在网络问题c;与ZooKeeper集群断开连接c;Session超时同样会导致锁被错误的释放(导致被其他线程错误地持有)c;因此ZooKeeper也无法保证完全一致。
ZK具有较好的稳定性;响应时间抖动很小c;没有出现异常。但是随着并发量和业务数量的提升其响应时间和qps会明显下降。
单机的 redisc;能够承载的 QPS 大概就在上万到几万不等。对于缓存来说c;一般都是用来支撑读高并发的。因此架构做成主从(master-slavE)架构c;一主多从c;主负责写c;并且将数据复制到其它的 slave 节点c;从节点负责读。所有的读请求全部走从节点。这样也可以很轻松实现水平扩容c;支撑读高并发。
脑裂c;也就是说c;某个 master 所在机器突然脱离了正常的网络c;跟其他 slave 机器不能连接c;但是实际上 master 还运行着。此时哨兵可能就会认为 master 宕机了c;然后开启选举c;将其他 slave 切换成了 master。这个时候c;集群里就会有两个 master c;也就是所谓的脑裂。此时虽然某个 slave 被切换成了masterc;但是可能 client 还没来得及切换到新的 masterc;还继续向旧 master 写数据。因此旧 master 再次恢复的时候c;会被作为一个 slave 挂到新的 master 上去c;自己的数据会清空c;重新从新的 master 复制数据。而新的 master 并没有后来 client 写入的数据c;因此c;这部分数据也就丢失了。
解决方式:两个参数(根据需要配置):min-slaves-to-write 1/min-slaves-max-lag 10
要求至少有 1 个 slavec;数据复制和同步的延迟不能超过 10 秒。如果说一旦所有的 slavec;数据复制和同步的延迟都超过了 10 秒钟c;那么这个时候c;R_476_11845@aster 就不会再接收任何请求了。
@H_121_2@mySQL用于持久化的存储数据到硬盘,功能强大,但是速度较慢。redis用于存储使用较为频繁的数据到缓存中,读取速度快。redis适合放一些频繁使用,比较热的数据,因为是放在内存中,读写速度都非常快,一般会应用在下面一些场景:排行榜、计数器、消息队列推送、好友关注、粉丝。
1、采用延迟双删策略保证redis与数据库的一致性:1、先淘汰缓存;2、再写数据库;3、休眠1秒c;再次淘汰缓存。这么做的目的c;就是确保读请求结束c;写请求可以删除读请求造成的缓存脏数据。
2、删除缓存重试机制:1、写请求更新数据库;2、删除缓存c;但可能删除失败;3、删除失败的key放入消息队列c;消费消息获取要删除的key;4、重试删除缓存的操作;
3、同步biglog异步删除缓存:以mysql为例 可以使用阿里的canal将binlog日志采集发送到MQ队列里面c;然后编写一个简单的缓存删除消息者订阅binlog日志c;根据更新log删除缓存c;并且通过ACK机制确认处理这条更新logc;保证数据缓存一致性;
哨兵模式大概讲一讲。 🌟🌟
哨兵用于实现 redis 集群的高可用c;本身也是分布式的c;作为一个哨兵集群去运行c;互相协同工作。
1、@R_223_10772@时c;判断一个 master node 是否宕机了c;需要大部分的哨兵都同意才行c;涉及到了分布式选举的问题。
2、即使部分哨兵节点挂掉了c;哨兵集群还是能正常工作的。
redis 的持久化机制是什么?各自的优缺点? 🌟🌟
redis提供两种持久化机制 RDB 和 AOF 机制:
RDB(redis DataBasE):是指用数据集快照的方式半持久化模式记录 redis 数据库的所有键值对,在某个时间点将数据写入一个临时文件c;持久化结束后c;用这个临时文件替换上次持久化的文件c;达到数据恢复。
优点:1、只有一个文件 dump.rdbc;方便持久化。2、容灾性好c;一个文件可以保存到安全的磁盘。3、性能最大化c;fork 子进程来完成写操作c;让主进程继续处理命令c;所以是 IO最大化。使用单独子进程来进行持久化c;主进程不会进行任何 IO 操作c;保证了 redis的高性能;4、相对于数据集大时c;比 AOF 的启动效率更高。
缺点:数据安全性低。RDB 是间隔一段时间(每隔五分钟)进行持久化c;如果持久化之间 redis 发生故障c;会发生数据丢失。所以这种方式更适合数据要求不严谨的时候。
AOF(Append-only filE):是指所有的命令行记录以 redis 命令请求协议的格式完全持久化存储保存为 aof 文件。数据日志c;记录操作命令。
优点:1、数据安全c;aof 持久化可以配置 appendfsync 属性c;有 alwaysc;每进行一次命令操作就记录到 aof 文件中一次。everysec每秒同步一次;2、通过 append 模式写文件c;即使中途服务器宕机c;可以通过 redis-check-aof工具解决数据一致性问题。3、AOF 机制的 rewrite 模式。AOF 文件没被 rewrite 之前(文件过大时会对命令进行合并重写)c;可以删除其中的某些命令(比如误操作的 flushall)
缺点:AOF 文件比 RDB 文件大c;且恢复速度慢。数据集大的时候c;比 rdb 启动效率低。
@H_121_2@mQ/kafka的基本原理/使用场景。 🌟🌟
@H_763_3@mQ
消息队列作为高并发系统的核心组件之一c;能够帮助业务系统解构提升开发效率和系统稳定性。主要具有以下优势:
使用场景:异步、解藕、削峰;
kafka基本原理
Kafka是一个分布式、支持分区的(partition)、多副本的(replica)c;基于zookeeper协调的分布式消息系统c;它的最大的特性就是可以实时的处理大量数据以满足各种需求场景:比如基于hadoop的批处理系统、低延迟的实时系统、storm/Spark流式处理引擎c;web/nginx日志、访问日志c;消息服务等等c;用scala语言编写。
Kafka中发布订阅的对象是topic。我们可以为每类数据创建一个topicc;把向topic发布消息的客户端称作producerc;从topic订阅消息的客户端称作consumer。Producers和consumers可以同时从多个topic读写数据。一个kafka集群由一个或多个broker服务器组成c;它负责持久化和备份具体的kafka消息。
kafka使用场景
1、日志收集:一个公司可以用Kafka可以收集各种服务的logc;通过kafka以统一接口服务的方式开放给各种consumerc;例如hadoop、Hbase、Solr等。
2、消息系统:解耦和生产者和消费者、缓存消息等。
3、用户活动跟踪:Kafka经常被用来记录web用户或者app用户的各种活动c;如浏览网页、搜索、点击等活动c;这些活动信息被各个服务器发布到kafka的topic中c;然后订阅者通过订阅这些topic来做实时的监控分析c;或者装载到hadoop、数据仓库中做离线分析和挖掘。
4、运营指标:Kafka也经常用来记录运营监控数据。包括收集各种分布式应用的数据c;生产各种操作的集中反馈c;比如报警和报告。
5、流式处理:比如spark streaming和storm
6、事件源
kafka特性
1、高吞吐量、低延迟:kafka每秒可以处理几十万条消息c;它的延迟最低只有几毫秒c;每个topic可以分多个partition, consumer group 对partition进行consume操作;
2、可扩展性:kafka集群支持热扩展;
3、持久性、可靠性:消息被持久化到本地磁盘c;并且支持数据备份防止数据丢失;
4、容错性:允许集群中节点失败(若副本数量为n,则允许n-1个节点失败);
5、高并发:支持数千个客户端同时读写;
6、支持实时在线处理和离线处理:可以使用Storm这种实时流处理系统对消息进行实时进行处理c;同时还可以使用Hadoop这种批处理系统进行离线处理;
Zookeeper在kafka的作用 1、无论是kafka集群c;还是producer和consumer都依赖于zookeeper来保证系统可用性集群保存一些meta信息。 2、Kafka使用zookeeper作为其分布式协调框架c;很好的将消息生产、消息存储、消息消费的过程结合在一起。 3、同时借助zookeeperc;kafka能够生产者、消费者和broker在内的所有组件在无状态的情况下c;建立起生产者和消费者的订阅关系c;并实现生产者与消费者的负载均衡。
@H_121_2@mQ/Kafka的区别 🌟🌟🌟🌟
在Kafka中c;是1个topic有多个partitionc;每个partition有1个master + 多个slave。
在RocketMQ里面c;1台机器只能要么是Masterc;要么是Slave。这个在初始的机器配置里面c;就定死了。
@H_366_53@master/Slave/Broker概念@H_763_3@master/Slave概念
Kafka: Master/Slave是个逻辑概念c;1台机器c;同时具有Master角色和Slave角色。
RocketMQ: Master/Slave是个物理概念c;1台机器c;只能是Master或者Slave。在集群初始配置的时候c;指定死的。其中Master的broker id = 0c;Slave的broker id > 0。
Broker概念:
Kafka: Broker是个物理概念c;1个broker就对应1台机器。
RocketMQ:Broker是个逻辑概念c;1个broker = 1个master + 多个slave。所以才有master broker, slave broker这样的概念。Kafka是先有Brokerc;然后产生出Master/Slave;RokCETR_476_11845@Q是先定义Master/Slavec;然后组合出Broker。
@H_366_53@master/Slave/Broker特性在Kafka里面c;R_476_11845@aser/Slave是选举出来的!RocketMQ不需要选举!
具体来说c;在Kafka里面c;R_476_11845@aster/Slave的选举c;有2步:第1步c;先通过ZK在所有机器中c;选举出一个KafkaController;第2步c;再由这个Controllerc;决定每个partition的Master是谁c;Slave是谁。这里的Master/Slave是动态的c;也就是说:当Master挂了之后c;会有1个Slave切换成Master。
而在RocketMQ中c;不需要选举c;R_476_11845@aster/Slave的角色也是固定的。当一个Master挂了之后c;你可以写到其他Master上c;但不会说一个Slave切换成Master。这种简化c;使得RocketMQ可以不依赖ZK就很好的管理Topic/queue和物理机器的映射关系了c;也实现了高可用。
在Kafka里面c;一个partition必须与1个Master有严格映射关系c;这个Master挂了c;就要从其他Slave里面选举出一个Master;而在RocketMQ里面c;这个限制放开了c;一个queue对应的Master挂了c;它会切到其他Masterc;而不是选举出来一个。
总结c;RocketMQ不需要像Kafka那样有很重的选举逻辑c;它把这个问题简化了。剩下的就是topic/queue的路由信息c;那用个简单的NameServer就搞定了c;很轻量c;还无状态c;可靠性也能得到很好保证。
RocketMQ支持异步实时刷盘c;同步刷盘c;同步Replicationc;异步Replication Kafka使用异步刷盘方式c;异步Replication/同步Replication 总结:RocketMQ的同步刷盘在单机可靠性上比Kafka更高c;不会因为操作系统Crashc;导致数据丢失。
Kafka单机写入TPS约在百万条/秒c;消息大小10个字节 RocketMQ单机写入TPS单实例约7万条/秒c;单机部署3个Brokerc;可以跑到最高12万条/秒c;消息大小10个字节 总结:Kafka的TPS跑到单机百万c;主要是由于Producer端将多个小消息合并c;批量发向Broker。消息投递实时性
Kafka使用短轮询方式c;实时性取决于轮询间隔时间c;0.8以后版本支持长轮询。 RocketMQ使用长轮询c;同Push方式实时性一致c;消息的投递延时通常在几个毫秒。 消费失败重试 Kafka消费失败不支持重试。 RocketMQ消费失败支持定时重试c;每次重试间隔时间顺延 总结:例如充值类应用c;当前时刻调用运营商网关c;充值失败c;可能是对方压力过多c;稍后再调用就会成功c;如支付宝到银行扣款也是类似需求。
这里的重试需要可靠的重试c;即失败重试的消息不因为Consumer宕机导致丢失。
Kafka支持消息顺序c;但是一台Broker宕机后c;就会产生消息乱序 RocketMQ支持严格的消息顺序c;在顺序消息场景下c;一台Broker宕机后c;发送消息会失败c;但是不会乱序 Mysql Binlog分发需要严格的消息顺序
Kafka理论上可以按照Offset来回溯消息 RocketMQ支持按照时间来回溯消息c;精度毫秒c;例如从一天之前的某时某分某秒开始重新消费消息 总结:典型业务场景如consumer做订单分析c;但是由于程序逻辑或者依赖的系统发生故障等原因c;导致今天消费的消息全部无效c;需要重新从昨天零点开始消费c;那么以时间为起点的消息重放功能对于业务非常有帮助。消息堆积能力
理论上Kafka要比RocketMQ的堆积能力更强c;不过RocketMQ单机也可以支持亿级的消息堆积能力c;我们认为这个堆积能力已经完全可以满足业务需求。
@H_121_2@mQ出现消息积压了怎么办? 🌟🌟
假设一个 MQ 消费者可以一秒处理 1000 条消息c;三个 MQ 消费者可以一秒处理 3000 条消息c;那么一分钟的处理量是 18 万条。如果 MQ 中积压了几百万到上千万的数据c;即使消费者恢复了c;也需要大概很长的时间才能恢复过来。
对于产线环境来说c;漫长的等待是不可接受的c;所以面临这种窘境时c;只能临时紧急扩容以应对了c;具体操作步骤和思路如下:
kafka写超时怎么处理? 🌟🌟
1、报错:: java.util.concurrent.ExecutionException: org.apache.kafka.common.errors.NotLeaderForPartitionException: This server is not the leader for that topic-partition.
报错原因:producer在向kafka broker写的时候c;刚好发生选举c;本来是向broker0上写的c;选举之后broker1成为leaderc;所以无法写成功c;就抛异常了。
解决办法:修改producer的重试参数retries参数c;默认是0c; 一般设置为3c; 我在生产环境配置的retries=10
2、java.util.concurrent.ExecutionException: org.apache.kafka.common.errors.TimeoutException: Expiring 1 record(s) for binlogCsbbroker-2 due to 30026 ms has passed since batch creation plus linger time
报错原因:具体原因我自己还没有找到c;但是网友们都说是因为kafka在批量写的时候c;这一批次的数据没有在30s内还处理完c;(30s为request.timeout.ms默认值)c;这一批次的数据就过期了c;所以抛出异常
解决办法:增大request.timeout.msc; 我在生产环境配置的是request.timeout.ms=60000 // 由原来默认的30s改成60s
3、java.util.concurrent.ExecutionException: org.apache.kafka.common.errors.NetworkException: The server disconnected before a response was received.
报错原因:kafka client与broker断开连接了
解决办法:重启服务
@H_121_2@mQ/Kafka会丢消息么?如何保证数据一致性? 🌟🌟
从Producer端看:
Kafka是这么处理的c;当一个消息被发送后c;Producer会等待broker成功接收到消息的反馈(可通过参数控制等待时间)c;如果消息在途中丢失或是其中一个broker挂掉c;Producer会重新发送(Kafka有备份机制c;可通过参数控制是否等待所有备份节点都收到消息)。 从Consumer端看:
broker端记录了partition中的一个offset值c;这个值指向Consumer下一个即将消费message。当Consumer收到了消息c;但却在处理过程中挂掉c;此时Consumer可以通过这个offset值重新找到上一个消息再进行处理。Consumer还有权限控制这个offset值c;对持久化到broker端的消息做任意处理。
是否会出现脑裂现象?🌟🌟
如果controller Broker 挂掉了c;Kafka集群必须找到可以替代的controllerc;集群将不能正常运转。这里面存在一个问题c;很难确定Broker是挂掉了c;还是仅仅只是短暂性的故障。但是c;集群为了正常运转c;必须选出新的controller。如果之前被取代的controller又正常了c;他并不知道自己已经被取代了c;那么此时集群中会出现两台controller。
其实这种情况是很容易发生。比如c;某个controller由于GC而被认为已经挂掉c;并选择了一个新的controller。在GC的情况下c;在最初的controller眼中c;并没有改变任何东西c;该Broker甚至不知道它已经暂停了。因此c;它将继续充当当前controllerc;这是分布式系统中的常见情况c;称为脑裂。
Kafka是通过使用epoch number(纪元编号c;也称为隔离令牌)来完成的。epoch number只是单调递增的数字c;第一次选出Controller时c;epoch number值为1c;如果再次选出新的Controllerc;则epoch number将为2c;依次单调递增。
每个新选出的controller通过Zookeeper 的条件递增操作获得一个全新的、数值更大的epoch number 。其他Broker 在知道当前epoch number 后c;如果收到由controller发出的包含较旧(较小)epoch number的消息c;就会忽略它们c;即Broker根据最大的epoch number来区分当前最新的controller。
ArrayList的扩容机制说一说。🌟🌟(准备了hashmap没准备它c;尴尬了)
如果通过无参构造的话c;初始数组容量为0c;当真正对数组进行添加时(即添加第一个元素时)c;才真正分配容量c;默认分配容量为10;当容量不足时(容量为sizec;添加第size+1个元素时)c;先判断按照1.5倍(位运算)的比例扩容能否满足最低容量要求c;若能c;则以1.5倍扩容c;否则以最低容量要求进行扩容。
执行add方法时c;先判断ArrayList当前容量是否满足size+1的容量;在判断是否满足size+1的容量时c;先判断ArrayList是否为空c;若为空c;则先初始化ArrayList初始容量为10c;再判断初始容量是否满足最低容量要求;若不为空c;则直接判断当前容量是否满足最低容量要求;若满足最低容量要求c;则直接添加;若不满足c;则先扩容c;再添加。
ArrayList的最大容量为Integer.max_value。
说一说hashMap put的详细过程c;越详细越好。
这个就需要自行去研究下源码c;在理解的基础上来阐述了。1.7和1.8要分别说。最好是熟悉put/get/扩容机制等。
可借鉴一篇文章 https://www.cnblogs.com/cxuanBlog/p/13181746.html
介绍一下hashMapc;1.7和1.8的区别是什么?🌟🌟🌟🌟🌟
(1)JDK1.7的时候使用的是数组+ 单链表的数据结构。但是在JDK1.8及之后时c;使用的是数组+链表+红黑树的数据结构(当链表的深度达到8的时候(泊松分布c;概率非常小)c;也就是默认阈值c;就会自动扩容把链表转成红黑树的数据结构来把时间复杂度从O(n)变成O(logN)提高了效率)
(2)JDK1.7用的是头插法c;而JDK1.8及之后使用的都是尾插法。因为JDK1.7是用单链表进行的纵向延伸c;当采用头插法时会容易出现逆序且环形链表死循环问题。但是在JDK1.8之后是因为加入了红黑树使用尾插法c;能够避免出现逆序且链表死循环的问题。
(3)扩容后数据存储位置的计算方式也不一样:1. 在JDK1.7的时候是直接用hash值和需要扩容的二进制数进行&(hash值 & length-1);而在JDK1.8的时候直接用了JDK1.7的时候计算的规律c;也就是扩容前的原始位置+扩容的大小值=JDK1.8的计算方式。
(4)在JDK1.7的时候是先扩容后插入的c;这样就会导致无论这一次插入是不是发生hash冲突都需要进行扩容c;如果这次插入的并没有发生Hash冲突的话c;那么就会造成一次无效扩容c;但是在1.8的时候是先插入再扩容的(可能是到了红黑树上)c;优点其实是因为为了减少这一次无效的扩容c;原因就是如果这次插入没有发生Hash冲突的话c;那么其实就不会造成扩容c;但是在1.7的时候就会急造成扩容;
(5)扩容策略:1.7中是只要不小于阈值就直接扩容2倍;而1.8的扩容策略会更优化c;当数组容量未达到64时c;以2倍进行扩容c;超过64之后若桶中元素个数不小于7就将链表转换为红黑树c;但如果红黑树中的元素个数小于6就会还原为链表c;当红黑树中元素不小于32的时候才会再次扩容。
concurrentHashMap的1.7和1.8的区别?1.7为什么要优化到1.8?是否会发生全表锁(size计算) 🌟🌟🌟🌟🌟
(1)JDK1.7:ReentrantLock+Segment+HashEntryc;
JDK1.8:取消segments字段c;直接采用transient volatile HashEntry<K,V>[] table保存数据c;采用table数组元素作为锁c;从而实现了对每一行数据进行加锁c;进一步减少并发冲突的概率。将原先table数组+单向链表的数据结构c;变更为table数组+单向链表+红黑树的结构。那么查询某个节点的时间复杂度从O(n)降低到O(logN)c;可以改进性能。(为什么要用红黑树的原因)
(2)JDK1.8的实现降低锁的粒度c;JDK1.7版本锁的粒度是基于Segment的c;包含多个HashEntryc;而JDK1.8锁的粒度就是HashEntry(首节点) (3)JDK1.8版本的数据结构变得更加简单c;使得操作也更加清晰流畅c;因为已经使用synchronized来进行同步c;所以不需要分段锁的概念c;也就不需要Segment这种数据结构了c;由于粒度的降低c;实现的复杂度也增加了 (4)JDK1.8使用红黑树来优化链表c;基于长度很长的链表的遍历是一个很漫长的过程c;而红黑树的遍历效率是很快的c;代替一定阈值的链表c;这样形成一个最佳拍档 (5)JDK1.8为什么使用内置锁synchronized来代替重入锁ReentrantLockc;我觉得有以下几点 因为粒度降低了c;在相对而言的低粒度加锁方式c;synchronized并不比ReentrantLock差c;在粗粒度加锁中ReentrantLock可能通过Condition来控制各个低粒度的边界c;更加的灵活c;而在低粒度中c;Condition的优势就没有了。
1.7的size的ConcurrentHashMap的做法是先尝试2次通过不锁住Segment的方式来统计各个Segment大小c;如果统计的过程中c;容器的count发生了变化c;则再采用加锁的方式来统计所有Segment的大小。
在JDK1.8版本中c;对于size的计算c;在扩容和addCount()方法就已经有处理了c;可以注意一下Put函数c;里面就有addCount()函数c;早就计算好的c;然后你size的时候直接给你。
有时间可以去着重熟悉下put/get/扩容机制的详细过程并加以理解。
可借鉴 https://zhuanlan.zhihu.com/p/133923068
hashMap如何快速提取数据? 🌟🌟
HashEntry的方式一次性取到key和值。
为什么扩容是2倍? 🌟🌟🌟🌟
向集合中添加元素时c;会使用(n - 1) & hash的计算方法来得出该元素在集合中的位置;
而HashMap扩容时调用resize()方法中会新建一个tabc;然后遍历旧的tabc;将旧的元素经过e.hash & (newCap - 1)的计算添加进新的tab中c;还是用(n - 1) & hash的计算方法c;其中n是集合的容量c;hash是添加的元素经过hash函数计算出来的hash值。
可见这个(n - 1) & hash的计算方法有着千丝万缕的关系c;符号&是按位与的计算c;这是位运算c;特别高效c;按位与&的计算方法是c;只有当对应位置的数据都为1时c;运算结果也为1c;当HashMap的容量是2的n次幂时c;(n-1)的2进制也就是1111111***111这样形式的c;这样与添加元素的hash值进行位运算时c;能够充分的散列c;使得添加的元素均匀分布在HashMap的每个位置上c;减少hash碰撞。
并发场景下是否会出现数据的丢失? 🌟🌟🌟🌟🌟
HashMap在并发场景下可能存在问题:数据丢失;数据重复;死循环。
在Java8之前的版本中之所以出现死循环是因为在resize的过程中对链表进行了头插法处理;在Java8中是尾插法c;自然也不会出现死循环。
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
如果两条线程同时执行到语句 table[i]=null时两个线程都会区创建Entry,这样存入会出现数据丢失。
如果有两个线程同时发现自己都key不存在c;而这两个线程的key实际是相同的c;在向链表中写入的时候第一线程将e设置为了自己的Entry,而第二个线程执行到了e.nextc;此时拿到的是最后一个节点c;依然会将自己持有是数据插入到链表中c;这样就出现了数据重复。
两个有序集合的合并(leetcode上是有序数组c;从后往前处理的c;我采用的是从前往后c;以两个变量分别记录下标c;找好边界)
链表反转。
删除链表倒数第N个节点。
熟悉的排序算法c;简要介绍一些。(快排/冒泡/归并等c;需要知道怎么排c;以及时间空间复杂度)
海量大文件如何排序?(分割排序+topN+多路归并)
二叉树层序遍历(借助队列)/深度遍历/前中后序遍历;
升序链表的合并。(递归判断)
如何判断两个单链表是否有交点(尾节点相等)判断是否有环(快慢指针)
系统和下游系统对接时c;rpc接口应该如何处理?超时了怎么处理?重试怎么处理?数据不一致怎么处理?
接口对接应该注意几点:1、出入参;2、返回结果中的异常处理;3、超时时间的设置与重试设置;4、异常报警机制;5、一次传输的数据量级的卡控;
超时的话c;一是超时时间的调整;一是看数据量级的调整;
重试的话需要根据业务场景来制定c;有些场景不允许自动重拾c;需要手动下发;有些是允许重拾的c;主要是分辨重试的数据是否会对下游的数据造成影响而导致不一致的情况发生;
数据不一致时c;可以考虑将下游数据删除c;上游重新下发一次。
项目的时间把控
先根据项目的需求的优先级及难易程度进行排期c;如果有风险:
一是协调研发和测试时间。研发资源与测试资源是否可以增加。
以上是大佬教程为你收集整理的Java后端社招面试个人总结全部内容,希望文章能够帮你解决Java后端社招面试个人总结所遇到的程序开发问题。
如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。