题目源自Java团长公众号,内容个人整理,来源于各大博客,未经允许,不准摘抄,仅供分享,不做商业使用。

 本分享多数为浅层知识体系,更为底层的还请自行多写写代码,若有不对之处,望广大的人才指点,不喜勿喷,文明交流。

 

一、hashcode相等两个类一定相等吗?equals呢?相反呢?
不一定相等(不一定相当)。因为在散列表中存在不相等的对象的键值对的hash值相同。
equals() 的作用是用来判断两个对象是否相等。 equals()定义在JDK的Object.java中。通过判断两个对象的地址是否相等(即,是否是同一个对象)来区分它们是否相等。
两个对象相等,则hashcode相等;hashcode相等,对象不一定相等(两个对象的hashcode计算出来的散列键值对的hash值;两个不同的键值对,哈希值相等也就是hash冲突)。
补充:1、不创建类对应的散列表:只是单纯的用equals比较两个对象是否相等。2、会创建类对应的散列表:⑴若两个对象相等,则hashcode值相等.⑵若两个对象的hashcode相等,但这两个对象不一定相等.在散列表中存在不相等的对象的键值对的hash值相同。

二、介绍一下集合框架?
宗介:一个用来代表和操纵集合的统一架构。包含以下:
1、接口:代表集合的抽象数据类型。允许集合独立操纵其所代表的细节,面向对象中接口形成一层(简单理解成service)。
2、实现(类):接口的具体实现。本质是可重复使用的数据结构(简单理解成impl)。
3、算法:接口的对象里的方法所执行的一些计算,类似排序等算法。(也称为多态,相同的算法在类似的接口上可以实现不同的功能)

三、hashmap hastable 底层实现什么区别?hashtable和concurrenthashmap呢?
1、hashmap是异步、速度快、保存null,允许key值为null、线程不安全;hashtable是同步、速度慢、不允许key值为null、线程安全(同步线程、悲观锁)。
2、hashmap是异步、速度快、保存null,允许key值为null、线程不安全(并发情况下可能产生环状链表);concurrenthashmap是异步、速度快、不允许key值为null、线程安全(采用分段锁,主干是segment,继承了ReentrantLock,是一种可重入锁。JDK1.8引入红黑树概念,即查找时间复杂度为 O(logn),链表长度大于8时转换为红黑树)。
补充:concurrentHashmap在8前后的变化,segment从桶结构的线性结构变为基于synchronized的node数组,提高的性能点是从线性到二分。摒弃segment的原因:1、锁的粒度(下面补充);2、segment+hashentry的二次hash来实现的快速查找,8之后引入红黑树已经满足这方面;3、通过检测红黑树来确定是否要扩容,减小扩容粒度,提升效率。
锁粒度:8之后摒弃分段锁,采用unsafe类的cas自旋赋值+synchronize+lockSupport阻塞来实现高并发(可读性差),锁粒度更细,影响更小,并发的效果更好,但是对于动态扩容较差(引入forwardingnode类,并发扩容时其他线程不会等待,也会参与扩容任务)。
hashmap不安全:hash冲突的时候,会在同一个数组的位置建立链表来解决,对链表而言,新的节点会添加到头结点,但是这个实现不是同步的(多线程下多个线程同时访问一个哈希映射,至少一个线程从结构上修改则必须保证同步)。1、多个线程同时put时,后来的线程会把前面的线程的值给替换掉(A、B同时put,A先B后,这个位置上只会存B的值);2、扩容时,与put机制类似,只有最后一个线程生成的新数据被赋给table变量,其他线程都会丢失。

四、hashmap和treemap什么区别?底层数据结构是什么?
hashmap是基于hash表的map接口实现的,提供了所有可选的映射操作,键、值都允许可为null,但不保证顺序;treemap是基于红黑树实现的,输出的结果都是排好序的(因为红黑树永远都出于平衡状态)。(最根本的区别)
简单点说,区别就是hashmap是无序的,是基于hash表的;treemap是有序的,基于红黑树的。
结构:
hashmap:底层是一个数组,数组中每一个元素是一个链表。
treemap:底层是一个数组,数组中每一个元素是一个红黑树。

五、线程池用过吗?都有什么参数?底层如何实现的?
线程池的参数:corePoolSize(核心线程数)、queueCapacity(任务队列容量)、maxPoolSize(最大线程数)、keepAliveTime(线程空闲时间)、allowCoreThreadTimeout(允许核心线程超时)、rejectedExcutionHandler任务拒绝处理器。
默认值:
* corePoolSize=1
* queueCapacity=Integer.MAX_VALUE
* maxPoolSize=Integer.MAX_VALUE
* keepAliveTime=60s
* allowCoreThreadTimeout=false
* rejectedExecutionHandler=AbortPolicy()
底层实现:一个线程集合workerSet和一个阻塞队列workQueue。
1、线程池的状态;2、执行过程;3、任务拒绝策略;4、执行过程中涉及到的方法。
1、running 111,-1<<count_bits;shutdown 000,0<<count_bits;stop 011,1<<count_bits; tidying 010,2<<count_bits; terminated 100, 3<<count_bits(只写了高3位,低29位均为0).大小关系为1<2<3<4<5,基本遵循线程池从运行到终止。
2、当前线程数 < corePoolSize,来一个任务创建一个线程;corePoolSize <= 当前线程数 < maxPoolSize && queueCapacity没有满,每来一个任务,将其添加到缓存队列中,等待有空闲线程时去执行;corePoolSize <= 当前线程数 < maxPoolSize && queueCapacity已满,每来一个任务,创建新的线程来处理被添加的任务;当前线程数 > maxPoolSize,再来新的任务时,采取任务拒绝策略进行处理。
3、①AbortPolicy:丢弃任务并抛出异常;②DiscardPolicy:丢弃任务(不抛出异常);③DiscardOldesPolicy:丢弃最前面的任务,重新尝试执行新进来的任务(并重复操作直到新进来的任务进入线程池);④CallerRunPolicy:由调用线程处理该任务(直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务)。
4、(1)execute(Runnable command)(提交任务);(2)addwork(Runnable firstTask,boolean core)(添加任务);(3)内部类worker;(4)runwork(Worker w)(执行任务);(5)getTask()(获取任务);(6)processWorkerExit(Worker w,boolean completedAbruptly)(worker线程退出)。
(参照博客:http://www.cnblogs.com/sxkgeek/p/9343519.html#_label2)

六、synchronized和Lock什么区别?synchronized什么情况是对象锁? 什么时候是全局锁?为什么?
区别:
1、Lock是可中断锁,Synchronized是不可中断锁(在等待获取锁过程中可以中断)
2、Lock是可公平可非公平锁,Synchronized是公平锁(按等待获取锁的线程的等待时间进行获取,等待时间长的具有优先获取锁权利——先到先得即为公平锁)
3、Synchronized是jvm层,是java的一个关键字;Lock是一个类
4、Synchronized:获取锁的线程结束时同步释放所,线程异常是jvm会让线程释放锁;Lock必须由代码释放锁,不然会造成线程死锁。
5、Synchronized是悲观锁,前一个线程获取锁后,之后的线程都必须等待;Lock则分情况而定,包含了读写锁、公平锁等(读锁为乐观锁,写锁为悲观锁)。
使用Synchronized关键字处理同步问题有两种模式,一个是同步代码块,一个是同步方法。同步代码块必须设置一个需要锁定的对象,Synchronized锁定的代码块同一时间只允许一个线程进入,这种方式是在方法中拦截,进入到方法中的线程依然可以是多个,但是只有一个线程进入被锁的代码块。同步方法就是只允许一个线程进入到被锁定的方法中,即为全局锁。需要锁住的是整个代码段,锁住多个对象的同一方法,被锁住的是class类,而不是this。

七、ThreadLocal 是什么?底层如何实现?写一个例子呗?
ThreadLocall的实例代表了一个线程局部的变量,每条线程都只能看到自己的值,并不会意识到其它的线程中也存在该变量。
原理:针对于每一个线程创建一份新的对象设置在其中。
底层实现:封装了ThreadLocalMap集合类来绑定当前线程和变量副本的关系,各个线程独立并且访问安全!
设计思想:(1) ThreadLocal仅仅是个变量访问的入口;(2) 每一个Thread对象都有一个ThreadLocalMap对象,这个ThreadLocalMap持有对象的引用;(3) ThreadLocalMap以当前的threadLocal对象为key,以真正的存储对象为value。get()方法时通过threadLocal实例就可以找到绑定在当前线程上的副本对象。
demo:
private static final ThreadLocal threadSession = new ThreadLocal();
public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}

八、volatile的工作原理?
核心思想:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。
volatile可以保证线程可见性且提供了一定的有序性,但是无法保证原子性。在JVM底层volatile是采用“内存屏障”来实现的。即1.保证可见性、不保证原子性,2.禁止指令重排序。
相对于Synchronized的轻量级,在某些场合可以代替synchronized,但不能完全代替(需要满足两个条件:1、对变量的写操作不依赖当前值,2、该变量没有包含在具有其他变量的不变式中。)
唯一需要用到volatile的场合是为了避免活性失败,但是又不想用锁和同步类。

九、cas知道吗?如何实现的?
compare and swap:用于实现多线程同步的原子指令(cas的底层是java -> C++ -> 汇编)。
逻辑执行流程:
1、obj是AtomicInteger对象,通过 JNIHandles::resolve() 获取obj在内存中OOP实例p
2、根据成员变量value反射后计算出的内存偏移值offset去内存中取指针addr
3、获得更新值x、指针addr、期待值e三个参数后,调用Atomic::cmpxchg(x, addr, e)
4、通过Atomic::cmpxchg(x, addr, e)实现CAS
实现:以cas伪代码来解释,比较内存位置p(指针*p)的内容和已知值oldval(当前*p所指向的值),当他们相同时,将newval写入*p中,若其他线程在此前已经对*p做了操作则返回false。
将内存块和给定值比较,相同时将该内存位置的内容修改为新的给定值(保证新值基于最新信息计算),如果改值在同一时间被另一个线程更新,则写入失败。
cas算法的特性:1、由若干条指令组成,用于完成一定功能的过程;2、连续,执行不允许被中断。(CPU提供了指令执行期间对总线加锁,加锁方式为拉低电位)。不管使用什么锁都会有一定的代价,加锁、释放锁、等待、挂起等都会造成性能的损耗。可能会造成ABA问题。

十、请用至少四种写法写一个单例模式?
https://www.cnblogs.com/zhaoyan001/p/6365064.html
1、饿汉式(静态常量)[可用]
public class Singleton {
private final static Singleton INSTANCE = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return INSTANCE;
}
}
2、饿汉式(静态代码块)[可用]
public class Singleton {
private static Singleton instance;
static {
instance = new Singleton();
}
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
3、懒汉式(线程不安全)[不可用]
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
4、懒汉式(线程安全,同步方法)[不推荐用]
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
5、懒汉式(线程安全,同步代码块)[不可用]
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
singleton = new Singleton();
}
}
return singleton;
}
}
6、双重检查[推荐用]
public class Singleton {
private static volatile Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
7、静态内部类[推荐用]
public class Singleton {
private Singleton() {}
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
8、枚举[推荐用]
public enum Singleton {
INSTANCE;
public void whateverMethod() {}
}

十一、请介绍一下JVM内存模型?用过什么垃圾回收器?都说说呗
JVM内存模型分为哪些区:程序计数器、虚拟机栈、本地方法栈、方法区、堆。
虚拟机的堆作为主要的垃圾回收场所,分为年轻代、年老代、永久代,不同区域的对象根据不同的算法进行垃圾回收,触发minor gc和full gc来实现垃圾回收(参照阿里五面:JVM自动内存管理,Minor GC与Full GC的触发机制)。
哪些垃圾回收器:清除(死亡的对象清理)、压缩(把存活的聚集,放到内存的起始位置,整理内存碎片)、复制(年轻代的两个survior区的转移过程)。

十二、线上发送频繁full gc如何处理? CPU 使用率过高怎么办?
1、频繁full gc:首先分析问题:①机器负载是否过高②机器内存的使用情况是否不平稳③jvm的内存(各区的使用情况,年轻代、年老代区域的使用)8之后永久代被Meetadata元数据区替代。
①调整机器负载。
②垃圾回收算法会将eden区的对象往survior区转移,如果这两个区(使用情况不超过70%即为正常)太小的话会经常发生minor gc,导致对象不断的往年老代写入,不断触发fullgc;如果是old区的对象不再被回收可能是内存泄漏的原因(1秒1次的gc基本就是内存泄漏导致的了)。
③补充:full gc前后java堆大小的变化,是否会是xms的值的原因(默认值为物理内存的1/64),每次gc,jvm会根据各种条件自动调节,java堆的取值范围是(Xms,Xmx),将Xms修改为Xmx就不会因为所使用的java堆不够用而进行调节。另外,也有可能是程序中不断调用system.gc导致的。
2、cpu使用率过高:us用户进程过高(死循环通过阻塞队列解决——使用阻塞队列非阻塞方法也可能出现cpu过高;大计算放在后台或者使用分布式实现,避免影响用户)、sy系统进程过高(减少线程数,当线程数与CPU核心数相同时效率最高-不会造成线程切换也不会浪费cpu资源)。

十三、如何定位问题?如何解决说一下解决思路和处理方法
emmmm。。。。。。(虽然很熟悉这一块,也专门查问题做了近一年,但好像还是不怎么会描述呀)

十四、知道字节码吗?字节码都有哪些?Integer x =5,int y =5,比较x =y 都经过哪些步骤?
1、字节码:java程序通过编译器编译成字节码文件,也就是计算机可以识别的二进制
2、byte (8位), short (16位), int (32位), long (64-bit位), char(16位无符号Unicode), float(32-bit IEEE 754 单精度浮点型), double (64-bit IEEE 754 双精度浮点型)
3、①给x、y赋值②取值y③将y的值赋值给x
补充:Integer是int的包装类,可以直接比较,java会自动拆包装,x==y为true。

十五、讲讲类加载机制呗,都有哪些类加载器,这些类加载器都加载哪些文件?
jvm的类加载机制有3种:
全盘负责。当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入。
父类委托。先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。
缓存机制。缓存机制保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换为Class对象,存入缓存区中。
有哪些及对应加载的对象类文件:类加载器有4种分别是bootstrapClassLoader (主要加载java核心api) , ExtClassLoaders是扩展类的类加载器,AppClassLoader 程序类加载器,还有一个是用户继承ClassLoader重写的类加载器。

十六、手写一下类加载Demo
//ExtClassLoader类中获取路径的代码
private static File[] getExtDirs() {
//加载<JAVA_HOME>/lib/ext目录中的类库
String s = System.getProperty("java.ext.dirs");
File[] dirs;
if (s != null) {
StringTokenizer st =
new StringTokenizer(s, File.pathSeparator);
int count = st.countTokens();
dirs = new File[count];
for (int i = 0; i < count; i++) {
dirs[i] = new File(st.nextToken());
}
} else {
dirs = new File[0];
}
return dirs;
}

十七、知道osgi吗? 他是如何实现的?
osgi:open service gateway initiative开放服务网关,以java为技术平台的动态模块化规范。
从概念上可以分为三层:模块层、生命周期层和服务层。
Module Layer:模块层主要涉及包及共享的代码;
Lifecycle Layer:生命周期层主要涉及Bundle的运行时生命周期管理;
Service Layer:服务层主要涉及模块之间的交互和通信。

模块化是通过为Jar包添加metadata 来定义哪些类该暴露,哪些类该隐藏,其控制单元叫做 Bundle(jar 包)。
在OSGi框架中属于模块层上面的一层,它的运作是建立在模块层的功能之上的。主要功能是控制动态安装、开启、关闭、更新和卸载的bundles。
一个OSGi 服务就是注册到 OSGi 框架中的一个 Java 对象。注册的时候可以设置这个 Service 的属性。而在获取 Service的时候可以根据属性进行过滤。


十八、请问你做过哪些JVM优化?使用什么方法达到什么效果?
根据不同的情况进行不同的调优操作。
1、CPU过高:us用户进程过高(死循环通过阻塞队列解决——使用阻塞队列非阻塞方法也可能出现cpu过高;大计算放在后台或者使用分布式实现,避免影响用户)、sy系统进程过高(减少线程数,当线程数与CPU核心数相同时效率最高-不会造成线程切换也不会浪费cpu资源)。
2、内存消耗过高:使用对象缓存池作为缓冲、及时释放不必要的对象(另,NIO操作的是物理内存,分配过小的话也会出现内存溢出的情况且监控堆、栈的内存消耗不大)。
3、磁盘IO过高:增加缓存、采用异步读写、采取文件批量读写处理(大量线程读写同一个文件容易造成磁盘IO飙升)。
4、网络消耗过大:增加带宽(java程序一般不会出现网络I/O的问题)。
5、资源消耗不多但程序运行缓慢:增加分布式处理,增加并发包减少锁的竞争;对于必须单线程的使用队列。(a、锁竞争激烈;b、单线程过多导致硬件未充分利用;c、大量的分布式处理,cpu负载低,sql的压力过大)。
6、未充分利用硬件资源:优化代码、业务拆分。
另外,对于代码调优(已达到节约资源的目的):
1、使用局部变量。
2、单线程使用hashmap和ArrayList(避免使用hashtable,降低性能)。
3、在finally块中释放资源。
4、常用的数据放缓存。
5、内存分配细化。

十九、classforName("java.lang.String")和String classgetClassLoader() LoadClass("java.lang.String") 什么区别啊?
类装载过程:加载 => 链接(校验、准备、解析) => 初始化
classforName("java.lang.String"):返回的是一个类,作用是要求jvm查找并加载指定的类(jvm会执行该类的静态代码段)。
String classgetClassLoader() LoadClass("java.lang.String"):
相同:都能在运行时对任意一个类,知道该类的所有属性和方法;对于任意一个对象,调用他的任意方法和属性。
区别:Class.forName得到的class是已经初始化完成的,一旦初始化,就会触发目标对象的 static块代码执行,static参数也也会被再次初始化;而Classloder.loaderClass得到的class是还没有链接的,不进行链接意味着不进行包括初始化等一些列步骤,那么静态块和静态对象就不会得到执行。
补充——数据库链接使用class.forName(className):使用这个才能在反射回去类的时候执行static块(附:JDBC源码)。
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}

二十、探查Tomcat的运行机制及框架?
运行机制:Tomcat其实就是一个servlet的容器:
1:实现Servlet api规范。这是最基础的一个实现,servlet api大部分都是接口规范。如request、response、session、cookie。为了我们应用端能正常使用,容器必须有一套完整实现。
2:启动Socket监听端口,等待http请求。
3:获取http请求,分发请求给不同的协议处理器,如http和https在处理上是不一样的。
4:封装请求,构造HttpServletRequest。把socket获取的用户请求字节流转换成java对象httprequest。构造httpResponse。
5:调用(若未创建,则先加载)servlet,调用init初始化,执行servlet.service()方法。
6:为httpResponse添加header等头部信息。
7:socket回写流,返回满足http协议格式的数据给浏览器。
8:实现JSP语法分析器,JSP标记解释器。JSPservlet实现和渲染引擎。
9:JNDI、JMX等服务实现。容器一般额外提供命名空间服务管理。
10:线程池管理,创建线程池,并为每个请求分配线程。

tomcat框架:
一、tomcat组成:server、service、connector、protocol、engine、host、context、wrapper组件。参照:https://www.jianshu.com/p/4866852b3462
二、处理过程:
1:Tomcat启动后,Connector组件的接收器(Acceptor)将会监听是否有客户端套接字连接并接收Socket。
2:监听到客户端连接后将连接交由线程池Executor处理,开始执行请求响应任务。
3:Http11Processor组件负责从客户端连接中读取信息报文,解析HTTP的请求行、请求头部、请求体;将解析后的报文封装成Request对象,方便后面处理时通过Request对象获取HTTP协议的相关值。
4:Mapper组件根据HTTP协议请求行的URL属性值和请求头部的Host属性值匹配对应的Servlet;将路由的结果封装到Request对象中。
5:CoyoteAdaptor组件负责将Connector组件和Engine容器连接起来,将Request和Response传递到Engine容器,调用它的管道。
6:Engine容器的管道(Pipeline)开始处理请求,管道里包含若干阀门(Valve),每个阀门负责某些处理逻辑;可以根据自己的需要往管道中添加自定义的阀门;最后执行基础阀门EngineValve,负责调用Host容器的管道。
7:Host容器的管道开始处理请求;执行完若干阀门后执行基础阀门HostValve,调用Context容器的管道。
8:Context容器的管道开始处理请求;执行完若干阀门后执行基础阀门ContextValve,调用Wrapper容器的管道。
9:Wrapper容器的管道开始处理请求;执行若干阀门后执行基础阀门WrapperValve,执行该Wrapper容器对应的Servlet对象的处理方法,对请求进行逻辑处理并将结果输出到客户端。

二十一、分析Tomcat线程模型?
tomcat作为最常用的服务器之一,应用范围非常广。支持4种线程模型:BIO、NIO、APR、AIO(tomcat8之后支持)。
1、BIO:阻塞I/O,使用的是传统的java I/O操作,tomcat7以下版本默认使用BIO运行,每个请求都要创建一个线程处理,导致线程开销大,不能高并发,性能最低。
2、NIO:java SE1.4及以后提供的一种新I/O操作,一个机遇缓冲区并能提供非阻塞I/O操作的java api,拥有比BIO更好的并发性能。(tomcat8前在安装目录/conf/server.xml修改protocol,从"HTTP/1.1"修改成"org.apache.coyote.http11.Http11NioProtocol"即可,tomcat8后的默认使用NIO)
3、APR:从操作系统级别解决异步IO问题,大幅度提高服务器的处理和响应性能(tomcat运行高并发的首选)。
4、AIO:Linux异步I/O,基本思想是允许进程发起很多I/O操作,而不用阻塞或等待任何操作完成。(java里使用与jdk7以后的版本,网上之前的案例不太好查)

tomcat的NIO模型实现方式就是有专门的线程负责IO事件监听,这些线程有自己的Selector,一旦监听到有IO读写事件,并不是像第一种实现方式那样(自己去执行IO操作),而是将IO操作封装成一个Runnable交给Worker线程池来执行,这种情况每个连接可能会被多个线程同时操作,相比第一种并发性提高了,但是也可能引来多线程问题,在处理上要更加谨慎些。一般参数就是Acceptor线程个数,Worker线程个数。(NIO源码分析:https://yq.aliyun.com/articles/39093)
tomcat的NioEndpoint(下图:tomcat高并发场景的bug排查图之一):


二十二、Tomcat系统参数认识和调优?
className 官方文档上说了This MUST be set to org.apache.catalina.valves.AccessLogValve to use the default access log valve小于60; 想访问日志这就必须得写成这样。
directory 这个东西是日志文件放置的目录,在tomcat下面有个logs文件夹,那里面是专门放置日志文件的,当然你也可以修改,我就给改成了D:
prefix 这个是日志文件的名称前缀,我的日志名称为localhost_access_log.2007-09-22.txt,前面的前缀就是这个localhost_access_log
suffix 这就是后缀名啦,可以改成别的
pattern 这个是最主要的参数了,具体的咱们下面讲,这个参数的内容比较丰富。
resolveHosts 如果这个值是true的话,tomcat会将这个服务器IP地址通过DNS转换为主机名,如果是false,就直接写服务器IP地址啦
rotatable 默认为true,默认的设置使得你的tomcat生成的文件命为prefix(前缀)+.+时间(一般是按天算)+.+suffix(后缀),参照我的日志名就知道了:localhost_access_log.2007-09-22.txt。使用这个需要谨慎,因为你将其设置为false的话,tomcat会忽略时间,不会新生成文件,最后导致你的文件超级大,这样生成的文件名就是:localhost_access_log.txt
condition 这个参数不太实用,可以设置任何值,比如咱们设置成condition=”tkq”,那么只有当ServletRequest.getAttribute(“tkq”)为空的时候,才会被记录下来
fileDateFormat 最后的一个参数,很明白,这就是时间格式嘛,但是这个时间格式是针对日志文件起作用的,还记得咱们生成的日志文件全名么:localhost_access_log.2007-09-22.txt,这里面的2007-09-22就是这么来的,如果你想让tomcat每小时生成一个日志文件,也很简单,将这个值设置为:fileDateFormat=”yyyy-MM-dd.HH”,当然也可以按分钟生成什么的,自己改改吧

 

 


二十三、MySQL底层B+Tree机制?
由二叉查找树演变而来,与跳表结构相似。通过存储在磁盘的多叉树结构做到时间、空间的平衡。B+树的叶子节点使用单链表串联。
B+树特点:
1、每个节点中子节点的个数不超过m,不小于m/2(m取决于磁盘数据块的大小,innoDB的m大小大约为1200)
2、根节点的子节点个数不超过m/2;
3、m叉树是存储索引,不真正存储数据,类似跳表;
4、通过链表将叶子节点串联在一起,这一页可以方便按区间查找;
5、一般情况,根节点会被存储在内存中,其他节点存储在磁盘中。
B+树与B树的区别:
1、B+树中的节点不存储数据,只是索引,而B树种的节点存储数据;
2、B树种的椰子节点并不需要链表来串联。
B+tree的查询效率比es低:es是基于lucence的倒排索引技术,实现比B+tree更快的过滤数据。


二十五、索引优化详解?
最左前缀匹配原则,上面讲到了
主键外键一定要建索引
对 where,on,group by,order by 中出现的列使用索引
尽量选择区分度高的列作为索引,区分度的公式是count(distinct col)/count(*),表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就是0
对较小的数据列使用索引,这样会使索引文件更小,同时内存中也可以装载更多的索引键
索引列不能参与计算,保持列“干净”,比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因很简单,b+树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都应用函数才能比较,显然成本太大。所以语句应该写成create_time = unix_timestamp(’2014-05-29’);
为较长的字符串使用前缀索引
尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可
不要过多创建索引, 权衡索引个数与DML之间关系,DML也就是插入、删除数据操作。这里需要权衡一个问题,建立索引的目的是为了提高查询效率的,但建立的索引过多,会影响插入、删除数据的速度,因为我们修改的表数据,索引也需要进行调整重建
对于like查询,”%”不要放在前面。
SELECT * FROMhoudunwangWHEREunameLIKE'后盾%' -- 走索引
SELECT * FROMhoudunwangWHEREunameLIKE "%后盾%" -- 不走索引
查询where条件数据类型不匹配也无法使用索引
字符串与数字比较不使用索引;
CREATE TABLEa(achar(10));
EXPLAIN SELECT * FROM a WHERE a="1" – 走索引
EXPLAIN SELECT * FROM a WHERE a=1 – 不走索引
正则表达式不使用索引,这应该很好理解,所以为什么在SQL中很难看到regexp关键字的原因


二十七、spring都有哪些机制啊?AOP底层如何实现的?IOC呢?
aop、ioc、jdbc、web、webmvc、core、tx、context、beans、orm。
aop底层实现:基于JDK动态代理或者cglib字节码操纵等技术,运行时动态生成被调用类型的子类等,并实例化代理对象,实际的方法调用会被代理给相应的代理对象。
ioc:Inversion of Control(控制反转,一种设计思想),ioc将设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。1、ioc容器控制对象,控制了外部资源获取(谁控制谁,控制了什么)。2、由容器帮我们查找及注入依赖对象,对象只是被动地接受依赖对象,所以是反转(为什么反转);依赖对象的获取被反转(哪个方面反转了)。由ioc容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。

二十八、cgLib知道吗?他和jdk动态代理什么区别?手写一个jdk动态代理呗?
jdk动态代理是以接口为中心,相当于添加了一种被调用者没有太大意义的限制,实例化的是proxy对象,而不是真正的被调用类型,有可能带来不便和能力退化。
cglib采取的是创建目标类的子类的方式,由于子类化所以可达到近似使用被调用者本身的效果。
相比于cglib,jdk动态代理1、最小化了依赖关系(意味着简化开发和维护),jdk本身的支持比cglib更可靠;2、平滑的进行jdk版本升级,而字节码类库一般需要进行更新以保证在新版java上能使用;3、代码实现简单。
而cglib的优势在于1、不用实现额外接口(对于某些调用目标不变实现额外接口的比较友好,不会有侵入性的实践);2、只操作我们关心的类;3、高性能。
public class MyDynamicProxy {
public static void main(String[] args) {
HelloImpl hello = new HelloImpl();
MyInvocationHandler handler = new MyInvocationHandler(hello);
// 构造实例
Hello proxyHello = (Hello) Proxy.newProxyInstance(HelloImpl.class.getClassLoader(),
HelloImpl.class.getInterfaces(), handler);
// 代理方法
proxyHello.sayHello();
}
}
interface Hello {
void sayHello();
}
class HelloImpl implements Hello {
@Override
public void sayHello() {
System.out.println("Hello");
}
}
class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Invoking sayHello");
Object result = method.invoke(target, args);
return result;
}
}

二十九、使用mysql索引都有哪些原则? 索引是什么数据结构? B+tree 和B tree 什么区别?
原则:1、索引列不能参与计算,保持列“干净”;2、选择唯一性索引;3、为经常需要排序、分组和联合操作的字段建立索引;4、为常作为查询条件的字段建立索引;5、限制索引的数目;6、尽量使用数据量少的索引;7、尽量使用前缀来索引;8、删除不再使用或者很少使用的索引;9、最左前缀匹配原则(重要);10、=和in可以乱序;11、尽量选择区分度高的列作为索引;12、尽量的扩展索引,不要新建索引。
数据结构:1、散列表;2、有序数组(应用于不再改变的历史数据);3、B+树。
区别:
1、B+树中的节点不存储数据,只是索引,而B树中的节点存储数据;
2、B树中的叶子节点并不需要链表来串联。

三十、MySQL有哪些存储引擎啊?都有啥区别? 要详细!
1、MyISAM:分为静态(适用于数据表的各数据列长度都预先固定)、动态(相比静态,存储空间较小,但每条数据的长度不一定。应为数据存储的碎片化导致的,需要经常使用命令和工具对内存碎片进行整理)和压缩(动静态的都可以进行压缩,压缩后所需空间减小,但是压缩后数据不能变化,每次读取需先解压)。
2、MyISAM Merge引擎:MyISAM的变种,将几个相同的MyISAM表合并为一个虚表(应用于日志和数仓)。
3、InnoDB存储引擎:对MyISAM的进阶版,提供了事物、行级锁机制和外键约束
4、memory(heap):只存在于内存中,使用散列索引,存取很快(应用于临时表)。对于按区间查找效率较低,时间复杂度为O(n)。
5、archive:只支持select和insert,不支持索引(常应用于日志记录和聚合分析)。

三十一、设计高并发系统数据库层面该怎么设计?数据库锁有哪些类型?如何实现呀?
如何设计:高并发环境下,1、要支持10万次/秒的更新,首先要对数据库做分库分表(单库表是不可能实现的),以支持数据的存取;2、其次高并发下数据增长快,每次信泽鞥需要获取一个全局id,自增不太合适,可以使用squeeze或者snowflake来实现全局唯一id的获取,在全局id头部增加其分库分表信息(即维一键,并以维一键作为索引,方便查询;如果是写动作频繁的适合使用唯一索引)。如果当前表的数据类似订单数据,需要同步到商务表的:1、商务表对实时性要求不高,使用消息队列来实现,同事引入实时监控,以保证数据的一致性(使用强一致性的分布式事务效率太低);2、商务标实时要求高的(及在订单表写入后会有对商务标进行实时查询到的,可以使用redis缓存,只存订单表-商务表的主键索引id)。
类型&实现:
共享锁:在查询语句后面增加LOCK IN SHARE MODE,在查询是添加一个共享锁表示A在执行查询,其他共享锁可以继续获取,但是排它锁需要等待。
更新锁:在语句后面增加(updlock)。
排它锁:在查询语句后面增加FOR UPDATE。
意向锁:不是锁,是一种意向,也可以认为是 行锁+表锁,当A对某行数据加了共享锁后,事务B来获取该表的排它锁时发现有共享锁(意向共享锁),排它锁获取失败,等待。
计划锁:。
Bulk Update Locks:主要在批量导数据时用(比如用类似于oracle中的imp/exp的bcp命令)。

三十二、数据库事务有哪些(特点)?
关于事务,ACID(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔离性、持久性)
当数据库上有多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题,为了解决这些问题,就有了“隔离级别”的概念。
事务隔离的级别包含:读未提交、读提交、可重复读、串行化。


三十四、用过哪些分库分表中间件,有啥优点和缺点?讲一下你了解的分库分表中间件的底层实现原理?
主流:
cobar:分布式服务的中间件,它可以让传统的数据库得到良好的线性扩展,并看上去还是一个数据库,对应用保持透明。
Mycat:在cobar基础上进行二次开发,解决了cobar当时存在的一些问题,不仅仅可以用作读写分离,以及分表分库、容灾管理,而且可以用于多租户应用开发、云平台基础设施,让架构具备很强的适应性和灵活性。
tddl:基于集中式配置的jdbc datasourcce实现,具有主备,读写分离,动态数据库配置等功能,主要解决了分库分表对应用的透明化以及异构数据库之间的数据复制。复杂度相对较高,还需要依赖diamond。
DRDS:脱胎于开源的Cobar分布式数据库引擎,吸收了Cobar核心的Cobar-Proxy源码,对应用程序屏蔽各种复杂的底层DB拓扑结构,借鉴TDDL实现了对分布式Join支持。
Atlas:实现了MySQL的客户端和服务端协议,作为服务端与应用程序通讯,同时作为客户端与MySQL通讯。不能实现分布式分表,没有自动建表的功能。
DBProxy:美团点评内部基于Atlas改进的,高可靠、高可用企业级数据库中间件,项目的Github地址是https://github.com/Meituan-Dianping/DBProxy。
sharding-JDBC:从关系型数据库模块dd-rdb中分离出来的数据库水平分片框架,实现透明化数据库分库分表访问,理论上可支持任意实现JDBC规范的数据库,轻量Java框架,使用客户端直连数据库,以jar包形式提供服务,无proxy代理层,无需额外部署,无其他依赖,DBA也无需改变原有的运维方式。
市场上用的相对较少的:
Heisenberg(baidu):优点:分库分表与应用脱离,分库表如同使用单库表一样,减少db连接数压力,热重启配置,可水平扩容,遵守MySQL原生协议,读写分离,无语言限制,可以调整采用velocity的分库分表脚本进行自定义分库表,相当的灵活。
CDS:一款基于客户端开发的分库分表中间件产品,实现了JDBC标准API,支持分库分表,读写分离和数据运维等诸多共,提供高性能,高并发和高可靠的海量数据路由存取服务。
oneProxy:基于mysql官方的proxy思想利用c进行开发的,OneProxy是一款商业收费的中间件, 舍去了一些功能点,专注在性能和稳定性上,高并发下很稳定。
Oceanus(58同城):易上手,分库分表逻辑不再与业务紧密耦合,扩容有标准模式,减少意外错误的发生。
Vitess(Youtube):使用Vitess应用改动比较大要 使用他提供语言的API接口。架构复杂。
原理(以cobar为例):Cobar的前、后端模块都实现了MySQL协议;当接受到SQL请求时,会依次进行解释(SQL Parser)和路由(SQL Router)工作,然后使用SQL Executor去后端模块获取数据集(后端模块还负责心跳检测功能);如果数据集来自多个数据源,Cobar则需要把数据集进行组合(Result Merge),最后返回响应。(主要提供水平切分,其原理是根据字段值的一致性Hash分布进行多维拆分;数据查询方式,根据where中的拆分字段分发,SQL语句其他元素的处理,将Cobar收到的SQL语句做变换 分发到各个分库执行,对执行结果合并、处理 保证返回前端的内容满足语义。)
补充:Cobar采用了主流的Reactor设计模式来处理请求,并使用NIO进行底层的数据交换,这大大提升系统的负载能力。其中,NIOAcceptor用于处理前端请求,NIOConnector则用于管理后端的连接,NIOProcessor用于管理多线程事件处理,NIOReactor则用于完成底层的事件驱动机制,就是看起来和Mina和Netty的网络模型比较相似。

三十六、分布式事务知道吗? 你们怎么解决的?
cap定理:一致性、可用性、分区容错性。
1、两阶段提交(2PC),使用是XA协议的原理,牺牲一部分可用性来换取一致性。优点:尽量保证一致性,适合数据强一致性高的领域(非100%);缺点:实现复杂,牺牲了可用性,性能影响大。
2、TCC(补偿事务try-confirm-cancel):t成功,confirm一定成功,cancel主要是在业务执行错误,需要回滚的状态下执行取消,预留资源释放。实现和流程相对2PC简单,但是后两步可能失败,属于补偿方式。
3、本地消息表(异步确保):生产者建个表,生产者和消费者定是扫描消息表,没处理的重试。(经典场景)最终实现一致性;但消息表耦合到系统中,可能会有很多杂货需要处理。
4、MQ事务:rocketMq。
5、sagas:。

三十七、为什么要分库分表啊?
高并发环境下,如果只单纯支持读操作的话,要支持10万次/秒以上的系统并不复杂,通过一致性哈希扩展缓存节点,或者水平扩展web服务器等,但是要支持更新操作的话,单库表是不可能实现的,独立数据库无法支持大数据下的高并发insert和update。

三十八、RPC通信原理,分布式通信原理
RPC:分为用户层、服务层和stub层,客户端发起一个远程调用时,通过本地 调用 本地调用方的stub,通过本地的RPCRuntime将网络包发送给服务端,服务端的RPCRuntime收到请求后交给提供方stub解码,然后调用服务端方法,服务端执行方法后返回结果,提供方stub把结果编码后发送给客户端,客户端RPCRuntime接收到结果发给调用方stub解码得到结果返回客户端。对于RPCRuntime,主要处理高性能的传输,以及网络的错误和异常。
分布式通信的基本原理:主要是使用客户端上的Stub(存根)和远程对象上的Skeleton(骨架)作为中介,来实现分布式通信的,在客户端会有一个叫做Stub(存根)的东西,其实现采用的是非常典型的代理模式,是远程对象在客户端的代理。Stub会封装所交互的数据的访问细节(如何压缩、压包、编码等),然后通过相应的协议与Skeleton(骨架)交换数据,对于Java领域的分布式通信技术,较常见的有EJB技术、CORBA技术、WebService技术等等。如果是EJB技术,那么Stub就会采用RMI-IIOP协议来传送数据给Skeleton;如果是CORBA技术,那么Stub就会采用IIOP协议来传送数据给Skeleton;如果是WebServices技术,那么Stub就会通过SOAP协议来传送数据给Skeleton。也就是说Stub会按照特定协议将信息传送给Skeletion,而Skeleton会将Stub传送过来的数据解析成特定的语言对象并发送给远程对象,即服务端。比如说服务端是采用Java开发的,那么Skeleton就会将接收到的数据解析成Java对象,再传送给服务端。

三十九、分布式寻址方式都有哪些算法?知道一致性hash吗?手写一下java实现代码?你若userId取摸分片,那我要查一段连续时间里的数据怎么办?
分布式寻址方式:hash算法(大量缓存重建)、一致性hash(自动缓存迁移) + 虚拟节点(自动负载均衡)、redis cluster的hash slot算法。
一致性hash:相同参数的请求总是发到同一服务节点。当某一个服务节点出现故障时,原本发往该节点的请求,基于虚拟节点机制,平摊到其他节点上,不会引起剧烈变动。


四十、如何解决分库分表主键问题?有什么实现方案?
并发量不大时使用数据库自增来解决是一个方案(只能在非高并发环境下),高并发环境使用squeeze或者snowflake(生成的ID整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞,秒级26万个ID)来实现全局唯一id的获取。

四十一、redis和memcached什么区别?为什么单线程的redis比多线程的memcached效率要高啊?
1、除了数据外,memcache还能缓存图片、视频等。
2、redis除了键值对外,还支持list、set、hash等数据结构存储(支持的数据类型多)。
3、当物理内存用完时redis可以将很久没使用的value交换到磁盘。
4、redis的过期策略支持通过expire来设定。
5、redis存储数据更安全,支持数据持久化到磁盘。
6、redis数据丢失后可以通过aof找回。
7、redis支持数据备份(master-slave模式数据备份)
8、redis只能在linux下运行。
9、底层模型不同,redis自己构建VM。
为什么redis更高:
1、纯内存操作。
2、异步非阻塞IO。
3、单线程操作不存在切换CPU导致的性能消耗。
4、没有锁机制造成的性能消耗问题。

redis使用单线程的原因:使用多线程是因为CPU不够,而对于redis,值简单的存储key-value,hash查找可以轻松达到秒级数百万的量级,所以redis的瓶颈主要来源于网络IO,当然内存也有可能,但是一般情况下内存都是够用的,所以单线程就足够了(redis的单线程是指一个线程处理所有网络请求,其他模块还是有用了多线程的)。

四十二、redis有什么数据类型?都在哪些场景下使用啊?
1、String:缓存、计数器、共享session、限速。
2、哈希:哈希结构更加直观,操作更加敏捷,常用于用户信息等的管理。
3、列表:消息队列。
4、集合:标签(tag),例如一个用户对某一方面(娱乐、体育)比较感兴趣另一个对新闻感兴趣。
5、有序集合:排行榜。

四十三、reids的主从复制是怎么实现的?redis的集群模式是如何实现的呢?redis的key是如何寻址的啊?
主从复制:用的是RDB快照方式实现的,与持久化的实现方式之一类似,将Redis主机中的数据,完整的生成一个快照,以二进制格式文件(后缀RDB)保存在从机当中。
集群:Redis Cluster是一个实现了分布式且允许单点故障的Redis高级版本,它没有中心节点,各个节点地位一致,具有线性可伸缩的功能。如图给出Redis Cluster的分布式存储架构,其中节点与节点之间通过二进制协议进行通信,节点与客户端之间通过ascii协议进行通信。在数据的放置策略上,Redis Cluster将整个 key的数值域分成16384个哈希槽,每个节点上可以存储一个或多个哈希槽,也就是说当前Redis Cluster支持的最大节点数就是16384。

 

寻址:KEYS命令(速度快,但在大数据库中可能造成性能问题,在数据集中查找特定的KEYS可用集合结构SETS代替)和SCAN命令(每次返回少量元素,适用生产环境,不会出现类似KEYS和SMEMBERS带来可能阻塞服务器的问题,是基于游标的迭代器。)。

四十四、使用redis如何设计分布式锁?使用zk可以吗?如何实现啊?这两种哪个效率更高啊?
分布式锁:主要用于在分布式环境中保护跨进程、跨主机、跨网络的共享资源实现互斥访问,以达到保证数据的一致性;基本原理是用用一个状态值表示锁,对锁的占用和释放通过状态值来标识。
分布式锁实现方式:1.使用redis的setnx()和expire();2.使用redis的getset();3.使用zookeeper的创建节点node;4.使用zookeeper的创建临时序列节点。
redis:采用队列模式将并发访问变成串行访问,且多客户端对redis的链接并不存在竞争关系。redis的SETNX命令可以方便的实现分布式锁。(setnx(key,value) 如果key不存在,设置为当前key的值为value;如果key存在,直接返回;expire()来设置超时时间)
zk:使用zookeeper创建节点node,如果创建节点成功,表示获取了此分布式锁;如果创建节点失败,表示此分布式锁已经被其他程序占用(多个程序同时创建一个节点node,只有一个能够创建成功)。
区别:Redis分布式锁,必须使用者自己间隔时间轮询去尝试加锁,当锁被释放后,存在多线程去争抢锁,并且可能每次间隔时间去尝试锁的时候,都不成功,对性能浪费很大。Zookeeper分布锁,首先创建加锁标志文件,如果需要等待其他锁,则添加监听后等待通知或者超时,当有锁释放,无须争抢,按照节点顺序,依次通知使用者。两者都是企业级分布式锁,效率方面可能差距不大,但是redis更消耗性能。

四十五、知道redis的持久化吗?都有什么缺点优点啊? 具体底层实现呢?
两种方式:1、使用RDB快照的方式,将内存中的数据不断写入磁盘;2、使用类似MySQL的AOF日志方式,记录每次更新的日志。前者性能较高,但是可能会引起一定程度的数据丢失;后者相反。 Redis支持将数据同步到多台从数据库上,这种特性对提高读取性能非常有益。
1、快照:将Redis内存中的数据,完整的生成一个快照,以二进制格式文件(后缀RDB)保存在硬盘当中。当需要进行恢复时,再从硬盘加载到内存中。redis主从复制用的也是RDB,做一个复制文件的传输。
2、写日志方式:每次执行Redis写命令,让命令同时记录日志(以AOF日志格式)。Redis宕机时,只要进行日志回放就可以恢复数据。

四十六、redis过期策略都有哪些LRU?写一下java版本的代码吧?
LRU策略:1、主动淘汰(通过定时任务serverCron定期清理过期的key);2、被动淘汰(①每次写入key时若发现内存不够则调用activeExpireCycle释放一部分内存;②每次访问相关的key,若发现key过期直接释放掉该key相关的内存)。
主动淘汰伪代码:
/* Expire keys by random sampling. Not required for slaves
* as master will synthesize DELs for us. */
if (server.active_expire_enabled && server.masterhost == NULL)
activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);

四十七、说一下dubbo的实现过程注册中心挂了可以继续通信吗?
可以。启动dubbo时消费者会从注册中心拉取server注册在注册中心的server地址、接口邓数据缓存在本地。
注册中心主要是1、定期发送心跳包来监听server提供的服务是否存活;2、server节点变更时,registry会同步变更并让client感知刷新本地缓存的节点列表。

四十八、dubbo支持哪些序列化协议?hessian,说一下hessian的数据结构。PB知道吗?为啥PB效率是最高的啊?
序列化协议:hessian、二进制序列化、json、SOAP文本序列化(基于xml)。(通信协议包括:rmi、hessian、http、webservice)
数据结构:不知道!!!hession将远程调用序列化为二进制进行传输,并且可以进行一定的压缩。传输机制与SOAP类似,但是相比于SOAP,hessian传输数据量比SOAP小了一个数量级,更适用于分布式应用的大数据量的数据交换。
PB一般用作大型数据库开发。代码量少,访问MYsql的效率仅次于VB,但是VB的开发效率低。

四十九、知道netty吗?netty可以干嘛呀?NIO,BIO,AIO 都是什么啊?有什么区别啊?
netty是基于事件的NIO(非阻塞)网络传输框架,在服务端启动时会监听一个端口并注册事件(注册的事件包括:连接时间、可写事件、可读事件、发生异常)。典型的reactor模型结构,可以通过ChannelHandler链来控制执行流向。
NIO(同步非阻塞方式):client每次发送请求,server不是每次都生成一个新线程来处理,通过I/O多路复用处理,将多个I/O的阻塞服务用到同一个select的阻塞上,使得单线程下可以同时处理多个客户端请求(开销小)。适用于连接数多且请求消耗轻的场景。NIO通过selector来实现一个线程监听多个channel的IO事件。
BIO(同步阻塞方式):client每次发送请求,server就生成一个线程处理,当client发送请求达到瓶颈时,新来的请求将无法被处理。适用于连接数小的场景。
AIO(异步非阻塞方式):client发送一个I/O操作立即返回,等I/O操作完成后client会得到I/O操作完成的通知,client只需对数据处理即可,真正的I/O操作在内核中完成(不存在阻塞等待)。适用于连接数多且请求消耗重的场景。

五十、dubbo负载均衡策略和高可用策略都有哪些啊?动态代理策略呢?
负载均衡(loadbalance):random(默认使用,对provider不同势力设置不同权限,按权重负载均衡)、roundrobin(均匀分布流量,但是如果机器性能不一,可能造成某些机器负载过高)、leastactive(自动感知,性能越差的机器活跃度越低,给这些机器的请求变少)、consistanthash(一致性hash)。
高可用(容错策略cluster):failover(默认使用,失败自动切换,自动重试其他机器)、failfast(一次调用失败则立即失败,常用于写操作)、failsafe(出现异常时忽略,用于记录日志等不重要的接口调用)、failbackc(后台自动记录失败请求,定时重发,适用于消息队列)、forking(并行调用多个provider,一个成功立即返回)、broadcacst(诸葛调用provider)
动态代理:默认使用javassist动态字节码生成,创建代理类;通过spi扩展机制配置动态代理策略。

五十二、为什么使用消息队列啊?消息队列有什么优点和缺点啊?
使用:1、数据需要交互或同步;2、瞬间流量峰值过高,使用消息队列来缓冲(当峰值超过存储空间的上限消息队列也可能被压垮);
优点:解耦、异步、削峰。
缺点:性能降低、复杂性提高、数据一致性

五十三、如何保证消息队列的高可用?如何保证消息不被重复消费啊
高可用:failbackc cluster策略,后台自动记录失败的请求,定时重发,以此来保证高可用。
重复消费问题:幂等,将消息的唯一标识保存到外部介中,每次消费处理时判断是否处理过。

五十四、kafka ,activemq,rabbitmq,rocketmq都有什么优点,缺点啊?
kafka:单机十万级吞吐量,ms级,topic从几十升到几百时吞吐量会大幅下降(大规模topic需要增加机器),基于分布式的高可用,一个数据多个副本,少数机器宕机不会丢失数据和不可用,消息可做到0丢失,应用于大数据领域的实时计算和日志采集中,唯一的劣势是存在消息被重复消费的可能(几率不大,而且这点可以用幂等来解决,重点是kafka是业内标准)。
activemq:单机万级,ms,基于主从的高可用,较低概率丢失数据,主要基于解耦和异步,适用较少的大规模吞吐场景。
rabbitmq:单机万级,μs,基于主从的高可用,基于erlong开发,并发性能好、延时低,动态扩容麻烦,可读性差。
rocketmq:单机十万级,ms,可支持大量topic(可达几百到几千,吞吐量会有小规模下降),基于分布式的高可用性(比active、rabbit好),参数优化配置可达数据0丢失,扩展性好;没有按照JMS标准,可能造成部分系统迁移修改量大。

五十五、如果让你写一个消息队列,该如何进行架构设计啊?说一下你的思路
1、分布式,支持快速扩容(给topic增加partition,做数据迁移,增加机器)
2、数据落地磁盘,数据持久化,不至于因宕机或断电导致数据丢失。
3、可用性,类似kafka给数据做多副本。
4、生产者发消息时增加确认反馈机制来确保消息正常能被收到;消费者通过offset commit每次记录消费的offset值来确保消息不丢失。

五十六、说一下TCP/IP四层?
(自上而下)
应用层:在用户空间实现,处理业务逻辑(包含DNS、telne、OSPF、http、ftp等协议)。
传输层:向用户提供可靠的端到端的差错和流量控制,保证报文的正确传输(监控服务质量)。
网络层:通过路由选择算法为报文或分组通过子网选择最适当的路径,主要解决不同子网间的通信。
数据链路:第二层,负责建立和管理节点间的链路,提供可靠的通过物理介质传输数据的方法。
物理层:最底层,实现相邻计算机节点间比特流的透明传输,尽可能屏蔽掉传输介质和物理设备的差异。(数据链路层与物理层同属物理层)

另外2层
表示层:处理用户信息的表示问题,类似编码、数据格式转换、加解密等。
会话层:向两个实体的表示层提供建立和使用连接的方法。

五十七、http的工作流程?http1.0 http1.1http2.0 具体哪些区别啊?
http基于tcp协议,面向连接的方式发送请求,通过stream二进制流的方式传给对方。
流程:地址解析 => 封装HTTP请求 => 封装TCP包 => 客户端发送请求 => 服务器端响应 => 关闭连接
1.0:早期的应用层网络协议,只使用在一些简单的网页和网络请求上。
1.1:相比于1.0,1、缓存处理(1.0使用header里的呃呃if-modified-since,expires来作为缓存判断标准,1.1引入了Entitytag、If-Match等新的缓存策略);2、1.0中存在浪费带宽的现象,1.1引入range头域;3、错误通知管理的区别,新增了24个错误状态响应码;4、增加Host头(1.0默认每台服务器绑定唯一ip,随着后续一台物理服务器可以有多个虚拟主机,1.1中没有添加Host头的会报错);5、支持长连接和请求的流水线处理。
2.0:先比与1.*,新增了一些特性,1、新的二进制格式(1.*的解析基于文本);2、多路复用(连接共享,一个连接上可以有多个request,接收方根据request的id将其归属到不同的额服务端请求);3、服务端推送,server push功能;4、header压缩(使用encoder减少header的大小,通讯双方各cachee一份header fields表,避免重传,减小大小)。
补充:1.1的长连接复用是一次请求-响应建立一个连接,用完关闭;2.0的多路复用是多个请求可以同时在一个连接上并行执行。

五十八、TCP三次握手,四层分手的工作流程画一下流程图。为什么不是四次五次或者二次啊?
第一次握手,A的消息有去;第二次握手,A有回,B有去;第三次,B有回。第三次握手完成猜能达到客户端有去有回,服务端的消息也有去有回的要求,次数在增加的结果与3此一致。
3次握手保证了服务端与客户端的链接已经建立(至此双方可以开始通信),但是不论是3次还是次数再增加上去都不能保证消息一定是可靠的。

A发送报文给B(你好,我是A),B反馈给A(你好,我是B),A返回消息给B确认(你好)。

A告诉B,不玩了,B回复好的;B发送不玩了给A,A回复好的。(如果B回复了好的之后“直接跑路,B结束了,但是A的状态会一直停留在当前状态。”)A最后的回复需要等待时间,等待时间的间隔为B发送的包全部都时间到期(即报文的时间超过期限时间后若B没有拿到A回复的消息会再度发送“不玩了”的消息给A)。

五十九、画一下https的工作流程?具体如何实现啊?如何防止被抓包啊?
防抓包:1、流量加密,抓到的内容乱码;2、https采用的公钥私钥的非对称加密来传输对称加密的密钥,通信采用获取到的对称加密的私钥进行加密,无法获取到密钥抓到的包不能解密。


六十一、系统架构如何选择合适日志技术(log4j、log4j2、slf4j、jcl…….)
slf4j是一个简单Facade,允许最终用户在部署时使用其所希望的日志系统,对于类库和嵌入式开发时使用比较合适。
log4j是Apache的开源项目,可以控制每一条日志输出和级别。
log4j2基于log4j基础上改良的,各方面与logback类似,优势在于1、改为插件式结构,根据需要随时扩展;2、配置文件优化,增加json格式的支持,重新配置后不会丢失原文件。
logback:可靠、通用、快速灵活的java日志框架,消耗低速度快(log4j的10倍速度),实现了slf4j已有的接口,可配置化(xml配置文件,配置文件更新会自动检测),可主动设置日志上限(自动删除旧日志),日志压缩

六十二、springAOP的原理,springAOP和Aspectj的关系,springAOP的源码问题
AOP底层:基于JDK动态代理或者cglib字节码操纵等技术,运行时动态生成被调用类型的子类等,并实例化代理对象,实际的方法调用会被代理给相应的代理对象。
AOP底层基于动态代理,在不修改原有类的基础上对其进行增强,拓展原有类的功能。
关系:AspectJ就是AOP(面向方面编程)中的方面,定义了AOP的语法。spring对于aop的支持(总共4种)中包含了@AspectJ注解驱动切面和注入式AspectJ切面(这个使用AspectJ框架实现aop编程)。
实际联系在于AspectJ提供了注解驱动的AOP,本质spring基于代理的AOP,但编程模型与AspectJ一致,不需要使用XML。

六十三、dubbo框架的底层通信原理
底层通信采用的是netty来实现的。netty是一个非阻塞的基于事件的网络传输框架,工作在socket层上,采用的是IO多路复用的方式。

六十八、mybaits的底层实现原理,如何从源码来分析mybaits。
实现方式:
传统:传递Statement Id 和查询参数给 SqlSession 对象,使用 SqlSession对象完成和数据库的交互;MyBatis 提供了非常方便和简单的API,供用户实现对数据库的增删改查数据操作,以及对数据库连接信息和MyBatis自身配置信息的维护操作。
改进:为适应面向接口的编程,增加了第二种使用MyBatis 支持接口(Interface)调用方式,将配置文件中的每一个<mapper> 节点抽象为一个 Mapper 接口,而这个接口中声明的方法和跟<mapper> 节点中的<select|update|delete|insert> 节点项对应,以此来实现。


六十九、mysql的索引原理,索引是怎么实现的
底层使用的是B+树。

七十、索引的底层算法、如何正确使用、优化索引
索引底层算法:散列表、二叉查找树、B+树、跳表、红黑树。

posted on 2019-03-19 10:43  penrose  阅读(1176)  评论(0编辑  收藏  举报