something just like this


解决问题:
1、正则表达式,导致cpu飙升,拖慢了其他业务响应。
    top -H -p [PID]  --》 最耗cpu的线程--》转16进制--》去jstack中找
    死锁,Deadlock!!  资源争抢互斥
    阻塞,Blocked !!等待资源超时
    等待资源,Waiting on condition!!等待资源(网络IO、分布式锁等)
    等待获取监视器,Waiting on monitor entry!!等待资源(Class的锁)    

2、redis aof,导致redis连接超时,进程假死,jetty连接数爆了。集群模式下aof多节点同步,阻塞主线程: 
    a、fork子进程(写日志)阻塞:10G物理内存,需要fork20M的内存页表,正常情况fork10G,200ms。
    b、aof刷盘阻塞:AOF fsync,大于2秒,阻塞主线程,直到上次刷盘完成,才开始本次刷盘。
    c、HugePage写操作阻塞:开启Transparent HugePages的操作系统,每次写命令引起的复制内存页由4K变为2MB,拖慢整体。

3、mysql批量插入优化:
a)    目标库建表时先不加索引,导入完成后添加索引。
b)    尝试改造程序逻辑,保留多线程从uds获取,减少insert线程数,增加mysql顺序写磁盘,记录性能表现。
c)    减少通信次数,增加单次写入量,insert单次包大小保持在32M左右。
d)    Mysql配置max_allowed_packet默认1M,改为64M,innodb_log_buffer_size 默认8M改为64M,innodb_buffer_pool_size默认128M不变。
e)    观察JVM情况,调整XmX  Xms  newRation等值,记录GC表现。
f)    代码优化,字符拼接、大对象的使用等优化。

4、mysql深度翻页问题,内存读缓冲区被污染,虽然有升级版的LRU淘汰机制。可走缓存,或拷贝另一个库。

==================================================
jvm同时拥有:JIT(即时编译器)和解释器

解释器的执行,抽象的看是这样的:
输入的代码 -> [ 解释器 解释执行 ] -> 执行结果
而要JIT编译然后再执行的话,抽象的看则是:
输入的代码 -> [ 编译器 编译 ] -> 编译后的代码 -> [ 执行 ] -> 执行结果
说JIT比解释快,其实说的是“执行编译后的代码”比“解释器解释执行”要快,并不是说“编译”这个动作比“解释”这个动作快。
JIT编译再怎么快,至少也比解释执行一次略慢一些,而要得到最后的执行结果还得再经过一个“执行编译后的代码”的过程。
所以,对“只执行一次”的代码而言,解释执行其实总是比JIT编译执行要快。
怎么算是“只执行一次的代码”呢?粗略说,下面两个条件同时满足时就是严格的“只执行一次”
1、只被调用一次,例如类的构造器(class initializer,<clinit>())
2、没有循环
对只执行一次的代码做JIT编译再执行,可以说是得不偿失。
对只执行少量次数的代码,JIT编译带来的执行速度的提升也未必能抵消掉最初编译带来的开销。
只有对频繁执行的代码,JIT编译才能保证有正面的收益。

在HotSpot虚拟机中使用的是第二种——基于计数器的热点探测方法,因此它为每个方法准备了两个计数器:
方法调用计数器和回边计数器。在确定虚拟机运行参数的前提下,这两个计数器都有一个确定的阈值,当计数器超过阈值溢出了,就会触发JIT编译。

==================================================


1.如何理解逃逸分析?
所谓的逃逸分析是指方法创建对象之后,除了在方法体内被引用到之外,还在别处也被引用到了。由于GC进行对象回收的时候需要判断该对象是否有被引用,因此当相应方法执行完毕后,由于方法类对象还被外部程序引用,就会导致相应对象无法被GC回收处理,也就造成了内存逃逸现象。

2.什么是栈上分配?
在以往的java程序运行时候,对象的内存空间都是通过堆来进行分配的。方法体内的局部变量都是通过栈来进行内存空间分配的,因此如果我们能够控制一个对象的活动范围只在一个局部方法里面的话,并且控制该对象的空间分配就存在于栈里面。这样随着栈的出栈操作,对象就会被销毁,减少了对于GC的效率影响。

3.同步消除是什么?
当通过逃逸分析之后,发下某个对象在没有被外部线程所引用的时候,他的读写竞争也就不存在了,这个时候就可以消除他的同步操作。

4.标量替换
标量是指java虚拟机里面的原始数据(那些不可再去细化分隔的基本类型,例如int,float这些)。当某个数据可以被继续进行细化拆分的时候,我们称之为聚合量(例如我们常说的对象)。如果jvm的逃逸分析分析出来了某个对象不会被外部所引用。那么当jvm执行相应函数的时候也不会直接创建相应的对象,而是通过将对象里面的各个属性拆开单独创建,单独维护。这样做的好处在于可以将原本需要连续空间存储的对象给拆分开来,减少连续内存空间的占用。

由于java的对象内存分配大多数情况下都是通过堆来进行分配,因此它的垃圾回收效率成为了java性能较慢的一个因素,这也是java语言被人吐槽的一个缺点。

5.逃逸分析总结
逃逸分析的原理理解起来很简单,但JVM在应用过程中,还是有诸多考虑。
比如,逃逸分析不能在静态编译时进行,必须在JIT里完成。原因是,与java的动态性有冲突。因为你可以在运行时,通过动态代理改变一个类的行为,此时,逃逸分析是无法得知类已经变化了。逃逸分析另一个重要的优化 - 同步消除。如果你定义的类的方法上有同步锁,但在运行时,却只有一个线程在访问,此时逃逸分析后的机器码,会去掉同步锁运行。

==================================================
类的加载过程
Java 类的加载过程主要分为五步:加载、验证、准备、解析、初始化。其中验证、准备、解析可以合称为连接。此外,这五步的顺序并不是完全固定的,比如为了支持动态绑定,解析的过程可以放在初始化之后。

加载过程主要做三件事情:
1.根据全类名获取 *.class 文件的路径,通过二进制流读入 JVM 的方法区;
2.在方法区中将该字节流转为方法区的运行时数据结构;
3.在堆中生成代表该类的 java.lang.Class 对象,Class 对象的实例作为访问方法区中运行时数据结构的访问入口;

校验
校验阶段主要确保 Class 文件字节流中的内容不会违反当前 JVM 的规范,不会危害到 JVM 运行时的安全。主要验证的有文件格式、元数据、字节码、符号引用。

准备
准备阶段主要是将为类变量分配内存,并初始化为默认值。以下面的片段为例:
在准备阶段,jvm只会为类变量分配内存,而不会为类成员变量分配内存,到初始化阶段才会为类成员变量分配内存
//在准备阶段,jvm会为其赋值为0,而不是1,初始化阶段才赋值为1
public static int test = 1;
//在准备阶段,jvm会为其赋值1
public static final int test2 = 1;


解析
解析是将符号引用转换为直接引用的过程。
符号引用:一组用于标识类型的符号,符合 Java 虚拟机规范的常量表,例如其中一项常量池项目类型如下图所示;
直接引用:在内存中能够唯一标识对象的引用。可以是内存指针、偏移量、或者是能间接定位到目标的句柄等。

初始化
执行类构建方法 clinit 的过程。clinit 方法由所有类变量的赋值动作和静态语句块 static{} 合并而来,这其中也包含了父类的 clinit 方法(类变量赋值动作与父类的静态语句块),同时在执行一个类的 clinit 方法时,也会通过递归方式保证其父类的 clinit 方法先被调用。
此外对于初始化阶段,只有几种情况才会要求类立刻执行 clinit 方法:

new:new 关键字某个未被初始化的类;
父类:初始化某子类时,父类未被初始化,则先初始化父类;
反射:通过反射调用某个未初始化的类;
main 方法所在类;
有的时候父类加载器也需要加载子类加载器的 Class,这时候就需要打破双亲委派机制,主要方式是使用 Thread 类里的线程上下文类加载器的方法 setContextClassLoader。

==========================================
Class的装载分了三个阶段,loading,linking和initializing
Class.forName(className) 实际上是调用Class.forName(className, true, this.getClass().getClassLoader())。
ClassLoader.loadClass(className)实际上调用的是ClassLoader.loadClass(name, false),第二个参数指出Class是否被link。第二个参数为true,代表Class.forName装载的class已经被初始化,而ClassLoader.loadClass(className)装载的class还没有被link。
forName支持数组类型,loadClass不支持数组
例如,在JDBC编程中,常看到这样的用法,Class.forName("com.mysql.jdbc.Driver"),如果换成了 getClass().getClassLoader().loadClass("com.mysql.jdbc.Driver"),就不行。
static {
    try {
        java.sql.DriverManager.registerDriver(new Driver());
    } catch (SQLException E) {}
}
原来,Driver在static块中会注册自己到java.sql.DriverManager。而static块就是在Class的初始化中被执行。

==========================================
启动类加载器(Bootstrap ClassLoader):加载JAVA_HOME/lib 目录
扩展类加载器(Extension ClassLoader):加载JAVA_HOME/lib/ext 目录
应用程序类加载器(Application ClassLoader)

tomcat 为了实现隔离性,没有遵守双亲委派的约定,每个webappClassLoader加载自己的目录下的class文件,不会传递给父类加载器。
==================================================
强引用:类似于 new Object(),无论如何都不会被回收,即使 OOM 异常;
软引用SoftReference:JVM 内存不够的时候就会回收;
弱引用WeakReference:只要 GC 就会回收 (WeakHashMap 与其有关)
虚引用PhantomReference:在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收的活动

================================================
浅拷贝:仅仅克隆基本类型变量,而不克隆引用类型的变量
深克隆:既克隆基本类型变量,也克隆引用类型变量

a.重写clone方法,clone目标对象中的引用对象
@Override
public Object clone() throws CloneNotSupportedException {
    //注意以下代码
    Teacher teacher = (Teacher)super.clone();
    teacher.setStudent((Student)teacher.getStudent().clone());
    return teacher;
}

b.使用序列化也能完成深复制:对象序列化后写入流中,再从流中读取,生成新的对象。非侵入的,不需要修改目标代码就可以实现
//将对象写到流里
ByteArrayOutputStream byteOut=new ByteArrayOutputStream();
ObjectOutputStream objOut=new ObjectOutputStream(byteOut);
objOut.writeObject(father);
//从流里读出来
ByteArrayInputStream byteIn=new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream objInput=new ObjectInputStream(byteIn);
fatherCopy = (Son) objInput.readObject()

==================================================
伪共享:
1、CPU在工作时,将对象从主存一级一级读至寄存器(l3、l2、l1、寄存器)
2、Core1改了a对象中一个x变量,需要将Core2缓存中a对象置无效,下次Core2再操作a.y变量时,还需要重新加载缓存行
3、多核CPU交替修改同一个对象不同变量,就会不停的装载缓存,即伪共享

应对:
缓存填充,一个对象填满64字节(缓存行大小),如long p1,p2,p3,p4,p5,p6,p7。jdk8字段注解:@Contended

==================================================
synchronized(非公平、可重入),jdk1.6优化:无锁-》偏向锁-》轻量级自旋锁-》重量级锁

java对象:对象头(Header)、实例数据(Instance Data)、对齐填充(Padding)
java对象头:MarkWord(对象的HashCode,分代年龄、3个级别的锁标志位信息)+klassPointer(指向类元数据的指针)

偏向锁:上个线程优先获得,仅判断是否为上个线程threadId。线程不挂起。
轻量级锁:偏向锁未获得,当前线程不挂起,开始自旋,cas判断是否获得锁。缺点:自旋cas消耗cpu。
重量级锁:自旋未获得锁,升级成为重量级锁(操作系统的mutex lock,线程挂起:内核态->用户态)。对象的monitor指针,指向新的线程。


synchronized与Lock区别:
synchronized是关键字,是JVM层面的底层啥都帮我们做了,而Lock是一个接口,是JDK层面的有丰富的API。
synchronized会自动释放锁,而Lock必须手动释放锁。
synchronized是不可中断的,Lock可以中断也可以不中断。
通过Lock可以知道线程有没有拿到锁,而synchronized不能。 
synchronized能锁住方法和代码块,而Lock只能锁住代码块。 
Lock可以使用读锁提高多线程读效率。 
synchronized是非公平锁,ReentrantLock可以控制是否是公平锁。

读写锁ReentrantReadWriteLock
在读多写少的场景下,该锁就非常适用。该类维护了一个读锁ReadLock和一个写锁WriteLock。

关于写锁:
1.写锁是一个支持可重入的排它锁。如果当前线程已经获取了写锁,则增加写状态。如果当前线程在获取写锁时,读锁已经被获取(读状态不为 0)或者该线程不是已经获取写锁的线程,则当前线程进入等待状态。
2.如果读锁被获取,则写锁不能被获取。
因为读写锁要保证写锁的操作对读锁可见,如果读锁已被获取,又对写锁获取,那么其他读线程就无法感知到当前写线程的操作。因此,写锁被当前线程获取的前提是其他读线程全部释放读锁,
3.写锁一旦被获取,则其他读写线程的访问均被阻塞。
关于读锁:
1.读锁是一个支持重进入的共享锁,它能够被多个线程同时获取。
2.在写锁没有被获取时,读锁可以被多次获取。获取成功则安全地增加读状态(该状态是所有线程获取读锁的次数和,但每个线程获取读锁的次数保存在ThreadLocal中)
3.如果在获取读锁时,写锁被其他线程获取,则当前线程阻塞等待

关于锁的升降级:
1.锁降级指的是写锁降级为读锁:当前持有写锁,同时再去获取读锁,最后再释放写锁
2.锁升级指的是读锁升级为写锁:当前持有读锁,获取写锁,最后释放读锁,但RentrantReadWriteLock不支持锁的升级,因为如果多个线程持有读锁,任意一个线程升级到了写锁,这样就不能保证写线程的更新对其他读线程的可见性。

==================================================
对象占用的内存大小=(对象头 + 实例数据 + padding) ,且% 8等于0
对象指针压缩:JVM参数UseCompressedOops,压缩大概为原2/3
 
64位机器上:
对象头:16bytes(字节)
reference类型:8bytes
数组对象的对象头:24bytes
boolean、byte:1bytes
short、char:2bytes
int、float:4bytes
long、double:8bytes

==================================================
concurrentHashmap1.8,put:
根据 key 计算出 hashcode 。
判断是否需要进行初始化。
f 即为当前 key 定位出的 Node,如果为空,利用 CAS 尝试写入,失败则自旋保证成功。
如果当前位置的 hashcode == -1,则需要进行扩容。数组扩容因子0.75,每次扩容为原来2倍:保证2的幂次方(查找快:与操作,散列均衡)
如果node有值,则利用 synchronized 锁写入数据。
如果链表数量大于 TREEIFY_THRESHOLD(默认为8) 则要转换为红黑树。

hashmap1.8 做了什么优化?
数组+链表 -》 数组+链表+红黑树,链表头插法改为尾插法,链表长度>8转为红黑树,< 6 变回链表(泊松分布算出是6)

ConcurrentHashMap 是如何实现的? 1.7、1.8 实现有何不同?为什么这么做?
entry改为node,segment+reentrantlock  改为 node + CAS(当前node为空,cas写入,失败则自旋) + synchronized(当前node有值,替换,加锁) 来保证并发安全性

==========================================
Disruptor相对于传统方式的优点:
1、ringbuffer(内存读取,缓存行预加载) +  两个volatile(内存屏障)读指针、写指针,
2、没有锁
3、所有访问者都记录自己的读写指针,共享数据结构。
4、cache line padding,没有伪共享

==========================================
堆 -Xmx -Xms 
线程共享,新生代(eden 满了youngGC通常是俩s区的10倍大、survivor0 、survivor1)、老生代 满了fullGC

栈  -Xss
线程独有,局部变量表、操作栈、动态连接、方法返回地址

程序计数器
线程独有,字节码行号指示器,唯一不会OOM的区域

本地方法栈
线程独有,大量本地方法出现时,势必会削弱JVM对系统的控制力  如 System.currentTimeMillis()

方法区/永久代(JDK1.8:元数据区)
元空间在本地内存中分配,只要本地内存足够,它不会出现类似永久代的java.lang.OutOfMemoryError: PermGen space
线程共享,类信息,常量,静态变量,即时编译器(JIT)编译后的代码等数据
区满时也会引发Full GC,会导致Class、Method元信息的卸载

==========================================
关键字final的好处小结
final关键字提高了性能。JVM和Java应用都会缓存final变量。
final变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销。
使用final关键字,JVM会对方法、变量及类进行优化。
对于不可变类,它的对象是只读的,可以在多线程环境下安全的共享,不用额外的同步开销。

==========================================

在Spring中循环依赖处理分为3种情况

1. 构造器循环依赖(无法解决)
因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决
2. setter循环依赖(可以解决)
解决方式:Spring容器提前暴露刚完    成构造器注入,但未完成属性注入(setter方法)的bean来完成的。
3. prototype范围的依赖处理(无法解决)
因为spring容器不进行缓存prototype作用域的bean

spring对象三级缓存:
三级:singletonFactories : 单例对象工厂的cache 
二级: earlySingletonObjects :提前暴光的单例对象的Cache 。【用于检测循环引用,与singletonFactories互斥】
一级: singletonObjects:单例对象的cache

==========================================

fail-fast与fail-safe有什么区别?
    Java.util包中的所有集合类都被设计为fail-fast的,而java.util.concurrent中的集合类都为fail-safe的。
    Fail-fast迭代器抛出ConcurrentModificationException,不克隆对象;而fail-safe迭代器从不抛出ConcurrentModificationException,会克隆对象。
==========================================
标准GC情况:
Minor GC执行时间不到50ms;
Minor GC执行不频繁,约10秒一次;
Full GC执行时间不到1s;
Full GC执行频率不算频繁,不低于10分钟1次;
eden:survivor = 10:1,因为99%的eden区对象,都会在第一次创建后销毁,留下1%在MinorGC时进入survivor区,所以大约10次MinorGC,survivor区才满。
吞吐量优先:Parallel Scavenge收集器(复制算法),-XX:+UseAdaptiveSizePolicy ,新生代大小、eden/survivor比例,交给jvm完成。新生代尽量大(NewRatio=1) ,大量短期对象在新生代中回收。
相应时间优先:CMS收集器(标记清除算法:单线程初次标记、多线程并发标记(与业务线程并行)、重新标记浮动垃圾、并发清理。并发清理fail的话,后备serialGC启动,压缩整理,阻塞)
G1(标记整理算法):将new、old都划分区域,每次回收垃圾最多的区域,兼顾了响应时间、吞吐量

==========================================
jstack:
死锁,Deadlock(重点关注)!!  资源争抢互斥
阻塞,Blocked(重点关注)    !!等待资源超时
等待资源,Waiting on condition(重点关注)!!等待资源(网络IO、分布式锁等)
等待获取监视器,Waiting on monitor entry(重点关注)!!等待资源(Class的锁)    
暂停,Suspended
对象等待中,Object.wait() 或 TIMED_WAITING
停止,Parked
执行中,Runnable

jmap
-heap 打印heap的概要信息,GC使用的算法,heap的配置及wise heap的使用情况. 
-histo[:live] 打印每个class的实例数目,内存占用,类全名信息.
-dump:生成java堆转储快照

jstat
-class:统计class loader行为信息 
-compile:统计编译行为信息 
-gc:统计jdk gc时heap信息 
-gccapacity:统计不同的generations(不知道怎么翻译好,包括新生区,老年区,permanent区)相应的heap容量情况 
-gccause:统计gc的情况,(同-gcutil)和引起gc的事件 
-gcnew:统计gc时,新生代的情况 
-gcnewcapacity:统计gc时,新生代heap容量 
-gcold:统计gc时,老年区的情况 
-gcoldcapacity:统计gc时,老年区heap容量 
-gcpermcapacity:统计gc时,permanent区heap容量 
-gcutil:统计gc时,heap情况 ,输出参数内容 
    S0  — Heap上的 Survivor space 0 区已使用空间的百分比 
    S1  — Heap上的 Survivor space 1 区已使用空间的百分比 
    E   — Heap上的 Eden space 区已使用空间的百分比 
    O   — Heap上的 Old space 区已使用空间的百分比 
    P   — Perm space 区已使用空间的百分比 
    YGC — 从应用程序启动到采样时发生 Young GC 的次数 
    YGCT– 从应用程序启动到采样时 Young GC 所用的时间(单位秒) 
    FGC — 从应用程序启动到采样时发生 Full GC 的次数 
    FGCT– 从应用程序启动到采样时 Full GC 所用的时间(单位秒) 

top -H -p [PID] 
最耗cpu的线程--》转16进制--》去jstack中找

==========================================
jdk动态代理、cglib动态代理
一、原理区别:
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。    
而cglib动态代理是利用asm开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP ,也可以强制使用CGLIB
2、如果目标对象没有实现接口,必须采用CGLIB实现
==========================================
(1)如果o1.equals(o2),那么o1.hashCode() == o2.hashCode()总是为true的。
(2)如果o1.hashCode() == o2.hashCode(),并不意味着o1.equals(o2)会为true。

==========================================
 mmap,在Unix/Linux系统下读写文件,一般有两种方式。

第一种:open一个文件,这里就涉及到了数据的两次拷贝:磁盘->内核,内核->用户态。
第二种:open一个文件,然后调用mmap系统调用,将文件的内容的全部或一部分直接mmap映射到进程的地址空间。mmap并不分配物理地址空间,它只是占有进程的虚拟地址空间。

==========================================
AQS    
AQS就是基于CLH队列,用volatile修饰共享变量state,线程通过CAS去改变状态符,成功则获取锁成功,失败则进入等待队列,等待被唤醒。AQS 定义了两种资源共享方式:
1.Exclusive:独占,只有一个线程能执行,如ReentrantLock
2.Share:共享,多个线程可以同时执行,如Semaphore、CountDownLatch

AQS使用一个整数state以表示状态,并通过getState、setState及compareAndSetState等protected类型方法进行状态转换。巧妙的使用state,可以表示任何状态,如:
ReentrantLock用state表示所有者线程已经重复获取该锁的次数。
Semaphore用state表示剩余的许可数量。
CountDownLatch用state代表CountDownLatch所剩余的计数次数。
FutureTask用state表示任务的状态,如尚未开始、正在运行、已完成、已取消。

CountDownLatch:
计数器,某个线程等待N个线程执行完毕后再继续执行,N就是对应的计数,每个执行完毕就减一,直到所有完成。主线程await,等待 N个工作线程逐个减一。
CyclicBarrier:
循环计数器,一般是N个线程相互执行等待,所有执行完毕再继续下一步动作。可以看作是n个互相等待。n个工作线程 await,互相等待全部完成。
Semaphore:
信号量,一般是获取到信号量的执行某个动作,完毕后释放,其他继续获取。类似锁。它的作用是限制某段代码块的并发数

==========================================

ThreadLocal 原理:
每个Thread类中有两个变量threadLocals和inheritableThreadLocals,二者都是ThreadLocal.ThreadLocalMap类型的变量,key 是ThreadLocal 对象本身,弱引用,而 value 存的是线程变量的值。


1.ThreadLocal 的内存泄露是怎么回事?
ThreadLocal 在 ThreadLocalMap 中是以一个弱引用身份被 Entry 中的 Key 引用的,因此如果 ThreadLocal 没有外部强引用来引用它,那么 ThreadLocal 会在下次 JVM 垃圾收集时被回收。这个时候 Entry 中的 key 已经被回收,但是 value 又是强引用,不会被垃圾收集器回收,又没有Threadlocal.ThreadlocalMap也没有提供public方法来访问value,这样就会发生内存泄露。所以,每次用完需要在finally中执行remove(),来回收内存。

2.为什么 ThreadLocalMap 的 key 是弱引用?
key 使用强引用:这样会导致一个问题,引用 ThreadLocal 的Thread对象被回收了,但是 ThreadLocalMap 还持有 ThreadLocal 的强引用,如果没有手动删除,ThreadLocal 不会被回收,则会导致内存泄漏。
key 使用弱引用:引用ThreadLocal 的Thread对象被回收了,由于ThreadLocalMap 持有 ThreadLocal 的弱引用,下一次GC时也会被回收。但value没有,需要在finally中remove()。

3.为什么value不是弱引用
不设置为弱引用,是因为不清楚这个Value除了map的引用还是否还存在其他引用,如果不存在其他引用,当GC的时候就会直接将这个Value干掉了,而此时我们的ThreadLocal还处于使用期间,就会造成Value为null的错误,所以将其设置为强引用。

==========================================
线程池 ThreadPoolExecutor
corePoolSize:核心线程数(最新线程数),超过了进队列queue
maximumPoolSize:最大线程数,超过这个数量的任务会被拒绝,用户可以通过RejectedExecutionHandler接口自定义处理方式
keepAliveTime:线程保持活动的时间
workQueue:工作队列,存放执行的任务
    SynchronousQueue: 一个无容量的等待队列,一个线程的insert操作必须等待另一线程的remove操作,采用这个Queue线程池将会为每个任务分配一个新线程,内部无AQS,直接CAS操作,吞吐量大:
    LinkedBlockingQueue : 无界队列,线程池将忽略 maximumPoolSize参数,仅用corePoolSize的线程处理所有的任务,未处理的任务便在LinkedBlockingQueue中排队
    ArrayBlockingQueue: 有界队列

    大吞吐量实践:
        以SynchronousQueue作为参数,使maximumPoolSize发挥作用,以防止线程被无限制的分配,同时可以通过提高maximumPoolSize来提高系统吞吐量
        自定义一个RejectedExecutionHandler,当线程数超过maximumPoolSize时进行处理,处理方式为隔一段时间检查线程池是否空闲,如果有空闲则把拒绝的Task重新放入到线程池,检查的时间小于keepAliveTime。
RejectedExecutionHandler
    ThreadPoolExecutor.AbortPolicy():     抛出java.util.concurrent.RejectedExecutionException异常
       ThreadPoolExecutor.CallerRunsPolicy():     重试添加当前的任务,他会自动重复调用execute()方法
    ThreadPoolExecutor.DiscardOldestPolicy():     抛弃旧的任务
    ThreadPoolExecutor.DiscardPolicy():     抛弃当前的任务

==========================================
LinkedBlockingQueue与ArrayBlockingQueue 区别
1.队列大小有所不同,ArrayBlockingQueue必须指定大小,而LinkedBlockingQueue可以是有界、也可以无界的(Integer.MAX_VALUE),对于后者而言,当添加速度大于移除速度时,在无界的情况下,可能会造成内存溢出等问题。
2.由于ArrayBlockingQueue采用的是数组的存储容器,因此在插入或删除元素时不会产生或销毁任何额外的对象实例,而LinkedBlockingQueue则会生成一个额外的Node对象。这可能在长时间内需要高效并发地处理大批量数据的时,对于GC可能存在较大影响。
3.两者的实现队列添加或移除的锁不一样,ArrayBlockingQueue实现的队列中的锁是没有分离的,即添加操作和移除操作采用的同一个ReenterLock锁,而LinkedBlockingQueue实现的队列中的锁是分离的,其添加采用的是putLock,移除采用的则是takeLock,这样能大大提高队列的吞吐量,也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。

CopyOnWriteArrayList 合适读多写少的场景,写操作时,内存拷贝,消耗内存,可能造成频繁的fullGC

==========================================
sun.misc.Unsafe
1、内存操作:堆外内存的分配、拷贝、释放、给定地址值。如:DirectByteBuffer是Java用于实现堆外内存的一个重要类,通常用在通信过程中做缓冲池,如在Netty、MINA等NIO框架中应用广泛。
    使用对外内存好处:
    a、对垃圾回收停顿的改善。由于堆外内存是直接受操作系统管理而不是JVM,所以当我们使用堆外内存时,即可保持较小的堆内内存规模。从而在GC时减少回收停顿对于应用的影响。
    b、提升程序I/O操作的性能。通常在I/O通信过程中,会存在堆内内存到堆外内存的数据拷贝操作,对于需要频繁进行内存间数据拷贝且生命周期较短的暂存数据,都建议存储到堆外内存,用mmap内存映射。
2、CAS相关:CAS在java.util.concurrent.atomic相关类、Java AQS、CurrentHashMap等,如:compareAndSwapInt()
3、线程调度:Java锁和同步器框架的核心类AbstractQueuedSynchronizer,就是通过调用LockSupport.park()和LockSupport.unpark()

==========================================

Netty的接收和发送ByteBuffer采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,然后才写入Socket中。

Netty工作工程:
Server 端包含 1 个 Boss NioEventLoopGroup 和 1 个 Worker NioEventLoopGroup。
NioEventLoopGroup 相当于 1 个事件循环组,这个组里包含多个事件循环 NioEventLoop,每个 NioEventLoop 包含 1 个 Selector 和 1 个事件循环线程TaskQueue。

每个 Boss NioEventLoop 循环执行的任务包含 3 步:
轮询 Accept 事件;boss
处理 Accept I/O 事件,与 Client 建立连接,生成 NioSocketChannel,并将 NioSocketChannel 注册到某个 Worker NioEventLoop 的 Selector 上;
处理任务队列中的任务,runAllTasks。任务队列中的任务包括用户调用 eventloop.execute 或 schedule 执行的任务,或者其他线程提交到该 eventloop 的任务。

每个 Worker NioEventLoop 循环执行的任务包含 3 步:
轮询 Read、Write 事件;
处理 I/O 事件,即 Read、Write 事件,在 NioSocketChannel 可读、可写事件发生时进行处理;
处理任务队列中的任务,runAllTasks。

==========================================

1、Netty 简介
是一个基于 NIO 的、异步的、事件驱动的网络通信框架。
简化了 TCP、UDP 等网络编程。
支持多种协议,如 FTP、SMTP、HTTP 等。
2、Netty 特点
高并发:基于 NIO,相比 BIO,并发性得到了很大的提高。
传输快:传输依赖于零拷贝。
封装好:封装了 NIO 操作的很多细节,提供易于使用的 API。
3、Netty 应用场景
实现特定协议的服务器,比如 HTTP 服务器。
作为 RPC 框架的网络通讯工具,比如 Dubbo。
实现即时通讯系统。
实现消息推送系统。
4、Netty 高性能表现在哪些方面
异步非阻塞通信:基于 NIO,支持阻塞和非阻塞两种模式。
Reactor 线程模型:基于事件驱动、多路 IO 复用。
串行化处理读写:避免使用锁带来的性能开销。可同时启动多个串行化的线程并行运行。
高效的并发编程:对 volatile、CAS、原子类、线程安全容器、读写锁等的合理使用。
高性能序列化框架:支持 Google 的 Protobuf 等。
零拷贝:减少不必要的内存拷贝。
内存池:对申请的内存块进行划分,然后按需分配,并且可以重复使用。
灵活的 TCP 参数配置能力。
5、Netty 核心组件
Bootstrap/ServerBootstrap:客户端/服务端启动引导类,用于串联各个组件。
EventLoop:事件循环,用于处理连接过程中所发生的事件。主要负责监听网络事件,并调用事件处理器处理 Channel 上发生的网络 IO 事件。
EventLoopGroup:一组 EventLoop 的抽象。每个 EventLoop 内部包含一个线程。为了更好的利用 CPU 资源,一般会有多个 EventLoop 同时工作。
Channel:通道,网络 IO 操作的抽象类。用于执行网络 IO 操作,比如 bind()、connect()、read()、write() 等。
ChannelFuture:用于保存 Channel 异步操作的结果。可通过 ChannelFuture 的 addListener() 注册一个监听器,来监听操作结果。
ChannelHandler:事件处理器,用于处理 Channel 上发生的事件。
ChannelPipeline:将多个 ChannelHandler 组合在一起,形成一个链条。该链条会拦截并处理 Channel 上的事件。
6、Netty 服务端、客户端实现
服务端:

// bossGroup负责客户端连接
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
// workerGroup负责读写操作
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
    // 创建服务端启动引导类
    ServerBootstrap bootstrap = new ServerBootstrap();
    // 设置事件循环组
    bootstrap.group(bossGroup, workerGroup)
            // 设置服务端通道(Channel),指定IO模型
            .channel(NioServerSocketChannel.class)
            // 初始化服务器连接队列大小,服务端处理客户端连接是顺序处理的,同一时间只能处理一个,来不及处理的将会放在队列中等待处理
            .option(ChannelOption.SO_BACKLOG, 1024)
            // 设置通道初始化器,用于初始化通道(Channel)
            .childHandler(new ChannelInitializer<SocketChannel>() {

                @Override
                protected void initChannel(SocketChannel sc) throws Exception {
                    ChannelPipeline p = sc.pipeline();
                    // 设置ChannelHandler处理器链
                    p.addLast(new NettyServerHandler());
                }
            });
    // 启动服务端(并绑定端口),bind()是异步操作,sync()同步等待bind()执行完成
    ChannelFuture future = bootstrap.bind(9000).sync();
    // 监听通道关闭,closeFuture()是异步操作,sync()同步等待closeFuture()执行完成
    future.channel().closeFuture().sync();
} finally {
    // 优雅关闭事件循环组资源
    bossGroup.shutdownGracefully();
    workerGroup.shutdownGracefully();
}

客户端:

// 客户端事件循环组
EventLoopGroup eventGroup = new NioEventLoopGroup();
try {
    // 创建客户端启动引导类
    Bootstrap bootstrap = new Bootstrap();
    // 设置事件循环组
    bootstrap.group(eventGroup)
            // 设置客户端端通道(Channel),指定IO模型
            .channel(NioSocketChannel.class)
            // 设置通道初始化器,用于初始化通道(Channel)
            .handler(new ChannelInitializer<SocketChannel>() {

                @Override
                protected void initChannel(SocketChannel sc) throws Exception {
                    ChannelPipeline p = sc.pipeline();
                    // 设置ChannelHandler处理器链
                    p.addLast(new NettyClientHandler());
                }
            });
    // 连接服务端,connect()是异步操作,sync()是等待connect()执行完成
    ChannelFuture future = bootstrap.connect("127.0.0.1", 9000).sync();
    // 监听通道关闭,closeFuture()是异步操作,sync()同步等待closeFuture()执行完成
    future.channel().closeFuture().sync();
} finally {
    // 优雅关闭事件循环组资源
    eventGroup.shutdownGracefully();
}

7、Reactor 模式
Reactor 模式是基于事件驱动的,它会监听事件的发生,当监听到事件发生后,根据多路复用策略,将事件分发给相应的处理器处理。

核心组件:

Handle(Event):用于表示事件。
Event Demultiplexer:事件分离器,用于同步等待事件的发生。
Reactor:反应器,用于监听和分发事件。内部会调用 Event Demultiplexer 来同步等待事件的发生,然后将事件交由 Event Handler 处理。
Event Handler:事件处理器,用于处理事件。
8、网络编程中的 Reactor 模式
网络编程中,Reactor 模式的核心组成包括 Reactor 和处理资源池(进程池或线程池)。其中 Reactor 负责监听和分发事件,处理资源池负责处理事件。

主要包含三种角色:

Reactor:负责监听事件,并将事件分发给绑定了该事件的 Handler。
Handler:绑定了某类事件,负责处理事件。
Acceptor:Handler 的一种,负责处理连接事件。
根据 Reactor 的数量和处理资源池(以线程池为例)的数量,可分为三种:

单 Reactor 单线程:Reactor 负责监听和分发事件,如果是连接事件,则由 Acceptor 处理,如果是读写事件,则由 Handler 处理(同时进行业务处理)。实现简单,但是无法做到高性能,只适用于业务处理非常快的场景。


单 Reactor 多线程:相对于单 Reactor 单线程来说,将 Handler 的执行放入线程池中(一般只将业务处理放入线程池中)。不适用于客户端并发连接量大的场景。


多 Reactor 多线程(主从 Reactor 多线程):将 Reactor 分成两部分:mainReactor、subReactor。主线程的 mainReactor 负责监听连接事件,并交由 Acceptor 处理,Acceptor 将建立的连接注册到子线程的 subReactor。子线程的 subReactor 负责监听读写事件,并交由 Handler 处理(同时进行业务处理)。主流模型,性能最高。


9、Netty 线程模型
Netty 线程模型就是 Reactor 模式的一个实现。主要靠 NioEventLoopGroup 线程池来实现具体的线程模型。NioEventLoopGroup 默认线程数为 CPU 核心数 * 2。

Netty 实现服务端时,一般会创建两个线程组:bossGroup、workerGroup。其中 bossGroup 负责客户端连接,workerGroup 负责读写操作以及业务处理。

单 Reactor 单线程模型:由一个线程同时负责客户端连接、读写操作以及业务处理。
代码实现:

// eventGroup(线程数为1)同时负责客户端连接,读写操作,业务处理
EventLoopGroup eventGroup = new NioEventLoopGroup(1);
// 创建服务端启动引导类
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(eventGroup, eventGroup)

单 Reactor 多线程模型:一个 Acceptor 线程负责客户端连接,一个 NIO 线程池负责读写操作、业务处理。
代码实现:

// bossGroup(线程数为1,对应Acceptor线程)负责客户端连接
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
// workerGroup(对应NIO线程池)负责读写操作,业务处理
EventLoopGroup workerGroup = new NioEventLoopGroup();
// 创建服务端启动引导类
ServerBootstrap bootstrap = new ServerBootstrap();
// 设置线程组
bootstrap.group(bossGroup, workerGroup)

主从 Reactor 多线程模型:从 Acceptor 线程池中随机选择一个线程负责客户端连接,一个 NIO 线程池负责读写操作、业务处理。
代码实现:

// bossGroup(对应Acceptor线程池)负责客户端连接
EventLoopGroup bossGroup = new NioEventLoopGroup();
// workerGroup(对应NIO线程池)负责读写操作
EventLoopGroup workerGroup = new NioEventLoopGroup();
// 创建服务端启动引导类
ServerBootstrap bootstrap = new ServerBootstrap();
// 设置线程组
bootstrap.group(bossGroup, workerGroup)

PS:实际上,Netty 中不存在主从 Reactor 多线程模型。因为服务端的 ServerSocketChannel 只会绑定到 Acceptor 线程池中的一个线程上,因此,在调用 Java NIO 的 Selector.select() 处理客户端连接时,实际上是在一个线程中。

10、TCP 粘包、拆包
粘包:基于 TCP 发送数据时,出现多次发送的数据“粘”在一起的情况。即接收端一次读取时,读取到了发送端多次发送的数据。
拆包:基于 TCP 发送数据时,出现某次发送的数据被“拆”开的情况。即接收端一次读取时,只读取到了发送端发送数据的一部分。
Netty 如何解决粘包、拆包
消息定长:每个数据包都固定长度,不足则以空格填充。Netty 提供 FixedLengthFrameDecoder 来实现。
使用分隔符:在每个数据包的末尾加上特定的分隔符。Netty 提供 DelimiterBasedFrameDecoder 来实现。
将消息分为消息头和消息体,消息头保存消息的总长度。
自定义协议进行粘包、拆包处理。
12、TCP 短连接、长连接
短连接:TCP 客户端和服务端建立连接后,一旦该次读写完成就关闭连接。如果后续有新的读写,则需要重新连接。短连接管理、实现简单,但是频繁连接会消耗网络资源、也耗费时间。
长连接:TCP 客户端和服务端建立连接后,即使该次读写完成也不会关闭连接。如果后续有新的读写,则可继续使用该连接。长连接网络资源消耗低、节约时间,适合读写频繁的场景。
13、Netty 心跳机制
心跳机制原理:在 TCP 长连接过程中,客户端和服务端之间定期发送一种特殊的数据包,通知对方自己还在线,以确保连接的有效性。
Netty 实现心跳机制的核心类是 IdleStateHandler,它可以指定读超时、写超时、读/写超时时间。一旦出现超时,则会触发 IdleStateEvent 事件。
14、Netty 零拷贝
零拷贝通常是指避免在操作系统的用户空间缓冲区(对应 JVM 的堆内存)与内核空间缓冲区(对应堆外直接内存)之间来回拷贝数据。

Socket 读写零拷贝:ByteBuf 底层用于接收、发送数据的 ByteBuffer,使用堆外直接内存进行 Socket 读写,避免了堆内存和直接内存之间的拷贝。(使用堆内存进行 Socket 读写时,会先从 Socket 读取数据到直接内存,再拷贝到堆内存缓冲区;或者先将堆内存缓冲区的数据拷贝到直接内存,再写入 Socket。)
File 文件读写零拷贝:使用 FileRegion 的 transferTo 方法,直接把文件缓冲区的数据发送到目标 Channel。
ByteBuf 合并零拷贝:使用 CompositeByteBuf 类,将多个 ByteBuf 进行逻辑上的合并,避免多个 ByteBuf 之间的拷贝。
ByteBuf 拆分零拷贝:使用 ByteBuf 的 slice 方法,将 ByteBuf 进行逻辑上的拆分,避免内存的拷贝。
————————————————
版权声明:本文为CSDN博主「惊却一目」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_36357530/article/details/117232178
==========================================
协程的好处:
跨平台
跨体系架构
无需线程上下文切换的开销
无需原子操作锁定及同步的开销
方便切换控制流,简化编程模型
高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。

缺点:
无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序:这一点和事件驱动一样,可以使用异步IO操作来解决
==========================================

Enumeration的速度是Iterator的两倍,也使用更少的内存。
Iterator更加安全,因为当一个集合正在被遍历的时候,它会阻止其它线程去修改集合
==========================================
spring生命周期,springcontext生命周期
根据图示的生命周期,具体过程如下:

1、实例化
InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation()
Instant
InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation()
2、属性赋值
InstantiationAwareBeanPostProcessor.postProcessPropertyValues()
Bean.setXxx()
BeanNameAware.setBeanName()
BeanFactoryAware.setBeanFactory()
3、初始化
BeanPostProcessor.postProcessBeforeInitialization()
InitializingBean.afterPropertiesSet()
Bean.init-method()
BeanPostProcessor.postProcessAfterInitialization()
4、销毁
在配置文件中指定了bean的scope=”prototype”为多例,新实例化一个bean,返回给用户,不再spring容器里管理。
对于scope=”singleton”的Bean(默认情况),把这个Bean缓存到在Spring IOC容器中
容器关闭时,DisposableBean.destroy()


==========================================
BeanFactory和ApplicationContext的区别

可以看到,ApplicationContext继承了BeanFactory,BeanFactory是Spring中比较原始的Factory,它不支持AOP、Web等Spring插件,而ApplicationContext不仅包含了BeanFactory的所有功能,还支持Spring的各种插件,还以一种面向框架的方式工作以及对上下文进行分层和实现继承。

BeanFactory是Spring框架的基础设施,面向Spring本身;而ApplicationContext面向使用Spring的开发者,相比BeanFactory提供了更多面向实际应用的功能,几乎所有场合都可以直接使用ApplicationContext而不是底层的BeanFactory。

==========================================

springAOP的实现原理
1. @EnableAspectJAutoProxy开启注解功能,并在容器中注册一个组件——AnnotationAwareAspectJProxyCreator,这个组件实现了SmartInstantiationAwareBeanPostProcessor,是一个后置处理器;
2.  在创建springIOC容器时,有一个步骤是refresh()即刷新容器,方法中有一步是registerBeanPostProcessors,这一步会初始化所有的后置处理器,就是在这时生成了AnnotationAwareAspectJProxyCreator组件;
3. refresh后面还有一步,finishBeanFactoryInitilization,即初始化剩下的单实例bean,实例化目标类组件和切面类组件;
4. AnnotationAwareAspectJProxyCreator会对目标类组件和切面类组件进行拦截,即在这些组件创建完成并初始化之后,调用postProcessAfterInitialization方法,判断目标类组件是否需要增强,如果需要,会将切面类的通知方法包装成增强器(Advisor),然后用cglib动态代理(如果目标类实现了接口,也可以使用jdk动态代理)给目标类对象创建一个代理对象,这个代理对象中就有上述增强器;
5. 经过2-4步,容器就创建完毕,接下来代理对象执行目标方法,首先获取目标方法的拦截器链(即MethodInterceptor,由增强器包装而来),利用拦截器的链式机制依次进入每一个拦截器进行增强;
6. 拦截器链的执行效果
目标方法成功:前置通知 → 目标方法 → 后置通知 → 返回通知;
目标方法抛出异常:前置通知 → 目标方法 → 后置通知 → 异常通知。

 
基于注解的声明式事务的原理
1. 在容器配置类上使用@EnableTransactionManagement注解,该注解在容器中注册了两大组件——AutoProxyRegistrar、ProxyTransactionManagementConfiguration;
2. AutoProxyRegistrar通过导入方式在容器中注册了InfrastructureAdvisorAutoProxyCreator,后置处理器;
3. ProxyTransactionManagementConfiguration本身就是一个容器配置类,它注册了transactionAdvisor(事务增强器),然后又在这个事务增强器中注入了两个属性transactionAttributeSource、transactionInterceptor:
    a. transactionAttributeSource用于解析@Transactional注解的各种属性;
    b. transactionInterceptor实现了MethodInterceptor,是一个拦截器链,这个拦截器链会从容器中获取事务管理器,利用事务管理器,在目标方法发生异常时执行回滚,在目标发生正常完成后提交事务;
4. 第2步的InfrastructureAdvisorAutoProxyCreator后置处理器,会在目标对象创建完成之后调用postProcessAfterInitialization方法,对需要增强的代理对象用cglib或jdk动态代理,将其包装为代理对象;
5、代理对象在执行目标方法时,会首先获取拦截器链,这个拦截器链就是第3.b步的transactionInterceptor。

@Transaction不生效:
1、只对方法名为pubic的才生效,其他事物不会生效。
2、只有来自外部的方法调用才会被AOP代理捕获,也就是,类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用@Transactional注解进行修饰。
3、trycache住,不生效。抛出RuntimeExecption,或指定了待捕获异常的

==========================================
算法时间复杂度
排序方法        时间(平均)    时间(最坏)    时间(最好)    空间复杂度    稳定性    复杂性
直接插入排序    O(n2)        O(n2)        O(n)            O(1)            稳定    简单
希尔排序        O(nlog2n)    O(n2)        O(n)            O(1)            不稳定    
直接选择排序    O(n2)        O(n2)        O(n2)        O(1)            不稳定    
堆排序        O(nlog2n)    O(nlog2n)    O(nlog2n)    O(1)            不稳定    
冒泡排序        O(n2)        O(n2)        O(n)            O(1)            稳定    简单
快速排序        O(nlog2n)    O(n2)        O(nlog2n)    O(nlog2n)    不稳定    
归并排序        O(nlog2n)    O(nlog2n)    O(nlog2n)    O(n)            稳定    较复杂
基数排序        O(d(n+r))    O(d(n+r))    O(d(n+r))    O(n+r)O(n+r) 稳定    


==========================================
B+树优点:
1.由于B+树在内部节点上不包含数据信息,因此在内存页中能够存放更多的key。 
  数据存放的更加紧密,具有更好的空间局部性。因此访问叶子节点上关联的数据也具有更好的缓存命中率。
2.B+树的叶子结点都是相链的,因此对整棵树的便利只需要一次线性遍历叶子结点即可。
  而且由于数据顺序排列并且相连,所以便于区间查找和搜索。而B树则需要进行每一层的递归遍历,相邻的元素可能在内存中不相邻,所以缓存命中性没有B+树好。

3.B+树的查询效率更加稳定 
由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

B树优点:
  由于B树的每一个节点都包含key和value,因此经常访问的元素可能离根节点更近,因此访问也更迅速。

红黑树和平衡二叉树区别如下: 
1、红黑树放弃了追求完全平衡,追求大致平衡,在与平衡二叉树的时间复杂度相差不大的情况下,保证每次插入最多只需要三次旋转就能达到平衡,实现起来也更为简单。 
2、平衡二叉树追求绝对平衡,条件比较苛刻,实现起来比较麻烦,每次插入新节点之后需要旋转的次数不能预知。

==========================================
设计模式
https://my.oschina.net/u/1431757?tab=newest&catalogId=3640158

单例模式,jdk中的单例模式: runtime类,恶汉模式
工厂模式
装饰模式:允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
模板方法模式:一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。
观察者模式
策略模式:我们出门的时候会选择不同的出行方式,比如骑自行车、坐公交、坐火车、坐飞机、坐火箭等等,这些出行方式,每一种都是一个策略。一个类的行为或其算法可以在运行时更改
职责链模式:避免将一个请求的发送者与接受者耦合在一起,让多个对象都有机会处理请求。将接受请求的对象接成一条链,并且沿着这条链传递请求,直到有一个对象能够处理它为止。

==========================================

SPI

面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。 java spi就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,java spi的具体约定为:当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。 基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader


==========================================
Redis,集群,哨兵,一致性哈希,单线程问题,Aof效率,分布式锁

Sorted Set(有序集合 ZSet)
内部实现:跳跃表(SkipList<obj>) + HashMap<obj, score> 


缓存穿透:key没命中,打到db
    1、空值也存redis,过期时间短
    2、布隆过滤器,快速判断key存在
缓存击穿:单个key过期,大量并发到db
    1、不设过期
    2、取db加锁、set值 + 自旋判断value是否有值
缓存雪崩:多个key同时过期
    1、热点数据不过期
    2、过期时间加随机值,定期更新

sentinel:
优点:高可用、自动故障迁移,raft算法,节点间心跳连接,投票选举master
缺点:切换需要时间,丢数据,没有解决 master 写的压力

cluster(3.0开始):
优点:
1、无中心,数据按照key进行crc16算法哈希, 分布在16383个slot,多个备份。可横向扩展,节点动态添加。
2、实现故障自动 failover,raft算法,节点之间通过 gossip 协议交换状态信息,投票选举master。
缺点:
1、资源隔离性较差,容易出现相互影响的情况。
2、数据通过异步复制,不保证数据的强一致性

一致性哈希:
key对2的32次方取模,为防止key分布不均,可添加虚拟节点

为什么快:
1、内存操作,查询效率o1
2、单线程,减少线程切换消耗。
3、利用epoll多路复用,nio管理多个连接,不在io上浪费时间。

aof:
重建慢,但一致性强。集群模式下aof多节点同步,阻塞主线程:
1、fork子进程(写日志)阻塞:10G物理内存,需要fork20M的内存页表,正常情况fork1G,20ms。
2、aof刷盘阻塞:AOF fsync,大于2秒,阻塞主线程,直到上次刷盘完成,才开始本次刷盘。
3、HugePage写操作阻塞:开启Transparent HugePages的操作系统,每次写命令引起的复制内存页由4K变为2MB,拖慢整体。

分布式锁:
释放锁时,需要用lua脚本,保证原子性
redlock:
过半数节点完成,才算完成。牺牲效率。

redis failover 选主、切换:

默认情况下,所有的读写命令只能发送到 Master。如果需要使用 Slave 处理读请求,需要先在客户端执行 readonly 命令。
主从自动切换机制:当一个 Master 发生故障,如果有 Slave,则会切换为 Master。如何判断 Master 发生故障了呢?
Redis 集群配置中有一个配置,cluster-node-timeout集群心跳超时时间。当集群内节点建立连接后,定时任务 clusterCron 函数。会每隔一秒随机选择一个节点发送心跳。如果在超时时间(cluster-node-timeout)的时间内未收到心跳响应,则将这个节点标记为 pfail。
如果集群中有一半以上的 Master 标记一个节点的状态是 pfail,那么这个节点的状态就会变成 fail。
当节点变成 fail 就会触发自动主从切换:对应的 Slave 节点执行定时任务 clusterCron 函数时,选取复制偏移量,也就是主从同步进度最大、数据最新的 Slave 尝试变为主。这个 Slave 设置自己的 currentEpoch += 1(正常情况下集群中所有的 currentEpoch 相同,每次选举都会加 1,并且每个 currentEpoch 只能投一次,防止多个 Slave 同时发起选举后难以获取大多数票)
之后向所有的 Master 发送 failover 请求,如果得到大多数 Master 的同意则开始执行主从切换。

集群不可用情况根据上面的描述,我们可以总结出如下不可用的情况当访问一个 Master 和 Slave 节点都挂了的槽的时候,会报槽无法获取。
当集群 Master 节点个数小于 3 个的时候,或者集群可用节点个数为偶数的时候,基于 fail 的这种选举机制的自动主从切换过程可能会不能正常工作,一个是标记 fail 的过程,一个是选举新的 master 的过程,都有可能异常。

Redis 的过期策略是如何实现的?

保存过期时间
redisDb 结构的 expire 字典(过期字典)保存了所有键的过期时间\

过期键的删除策略:惰性删除
就是在执行 Redis 的读写命令前都会调用 expireIfNeeded 方法对键做过期检查:

如果键已经过期,expireIfNeeded 方法将其删除,如果键未过期,expireIfNeeded 方法不做处理

补充
我们通常说 Redis 是单线程的,其实 Redis 把处理网络收发和执行命令的操作都放到了主线程,但 Redis 还有其他后台线程在工作,这些后台线程一般从事 IO 较重的工作,比如刷盘等操作。


定期删除
定期策略是每隔一段时间执行一次删除过期键的操作,并通过限制删除操作执行的时长和频率来减少删除操作对CPU 时间的影响,同时也减少了内存浪费

Redis 默认会每秒进行 10 次(redis.conf 中通过 hz 配置)过期扫描,扫描并不是遍历过期字典中的所有键,而是采用了如下方法

从过期字典中随机取出 20 个键,删除这 20 个键中过期的键,如果过期键的比例超过 25% ,重复这个步骤

为了保证扫描不会出现循环过度,导致线程卡死现象,还增加了扫描时间的上限,默认是 25 毫秒(即默认在慢模式下,如果是快模式,扫描上限是 1 毫秒)


补充
因为 Redis 在扫描过期键时,一般会循环扫描多次,如果请求进来,且正好服务器正在进行过期键扫描,那么需要等待 25 毫秒,如果客户端设置的超时时间小于 25 毫秒,那就会导致链接因为超时而关闭,就会造成异常,这些现象还不能从慢查询日志中查询到,因为慢查询只记录逻辑处理过程,不包括等待时间。

所以我们在设置过期时间时,一定要避免同时大批量键过期的现象,所以如果有这种情况,最好给过期时间加个随机范围,缓解大量键同时过期,造成客户端等待超时的现象


Redis 过期键删除策略
Redis 服务器采用惰性删除和定期删除这两种策略配合来实现,这样可以平衡使用 CPU 时间和避免内存浪费


AOF、RDB 和复制功能对过期键的处理
RDB文件
生成 RDB 文件

在执行 save 命令或 bgsave 命令创建一个新的 RDB文件时,程序会对数据库中的键进行检查,已过期的键就不会被保存到新创建的 RDB文件中

载入 RDB 文件

主服务器:载入 RDB 文件时,会对键进行检查,过期的键会被忽略

从服务器:载入 RDB文件时,所有键都会载入。但是会在主从同步的时候,清空从服务器的数据库,所以过期的键载入也不会造成啥影响


AOF文件
AOF 文件写入

当过期键被惰性删除或定期删除后,程序会向 AOF 文件追加一条 del 命令,来显示的记录该键已经被删除

AOF 重写

重启过程会对键进行检查,如果过期就不会被保存到重写后的 AOF 文件中


复制
从服务器的过期键删除动作由主服务器控制

主服务器在删除一个过期键后,会显示地向所有从服务器发送一个 del 命令,告知从服务器删除这个过期键

从服务器收到在执行客户端发送的读命令时,即使碰到过期键也不会将其删除,只有在收到主服务器的 del 命令后,才会删除,这样就能保证主从服务器的数据一致性


疑问点1-如果主从服务器链接断开怎么办?

Redis采用 PSYNC 命令来执行复制时的同步操作(从向主发送带最近偏移量的PSYNC命令,主从最近偏移量开始推数据给从),当从服务器在断开后重新连接主服务器时,主服务器会把从服务器断线期间执行的写命令发送给从服务器,然后从服务器接收并执行这些写命令,这样主从服务器就会达到一致性,那主服务器如何判断从服务器断开链接的过程需要哪些命令?主服务器会维护一个固定长度的先进先出的队列,即复制积压缓冲区,缓冲区中保存着主服务器的写命令和命令对应的偏移量,在主服务器给从服务器传播命令时,同时也会往复制积压缓冲区中写命令。从服务器在向主服务器发送 PSYNC 命令时,同时会带上它的最新写命令的偏移量,这样主服务器通过对比偏移量,就可以知道从服务器从哪里断开的了

疑问点2-如果发生网络抖动,主服务器发送的 del 命令没有传递到从服务器怎么办?

其实主从服务器之间会有心跳检测机制,主从服务器通过发送和接收 REPLCONF ACK 命令来检查两者之间的网络连接是否正常。当从服务器向主服务器发送 REPLCONF ACK 命令时,主服务器会对比自己的偏移量和从服务器发过来的偏移量,如果从服务器的偏移量小于自己的偏移量,主服务器会从复制积压缓冲区中找到从服务器缺少的数据,并将数据发送给从服务器,这样就达到了数据一致性

========================================
缓存策略:
cache aside(适用:读多写少)
缓存不保证一致性,旁路策略,缓存设置过期
读:cache、db
写:db、删cache(写cache)

Read/Write through(适用:读多写少)
以缓存为操作为主, 数据存先存在于缓存, 缓存不过期
读:cache、db
写:cache、同步db

Write Back(behind)(适用:读少写多)
更高的写入性能,Linux系统的页缓存和MySQL InnoDB 引擎的Cache Pool其实就是使用的WriteBack策略
读:cache、db
写:cache、异步db

MYSQL==========================================
MyISAM和InnoDB都使用B+树来实现索引:
MyISAM的主键索引和普通索引,都是非聚集索引:索引与数据分开存储
MyISAM的索引叶子存储指针。
InnoDB的聚集索引存储数据行本身,普通索引存储主键
InnoDB一定有且只有一个聚集索引
InnoDB建议使用趋势递增整数作为PK,而不宜使用较长的列作为PK(普通索引存储量大)

主从复制:
1、slave开启两个线程:IO线程(同步binlog,写入relaylog),sql线程(从relaylog读sql,插入)
2、master开启一个线程:binlog dump
3、全同步复制(全部从节点ack即完成)、异步复制(只通知dump线程)、半同步复制(其中一个从节点ack即完成)

InnoDB整体分为三层: 
(1)内存结构(In-Memory Structure),这一层在MySQL服务进程内;
    (1)缓冲池(Buffer Pool);加速读请求,避免每次数据访问都进行磁盘IO。技术点包括:预读,局部性原理,LRU,预读失败+缓冲池污染(大量结果返回,替换了原有用缓存)
    (2)写缓冲(Change Buffer);加速写请求,避免每次写入都进行磁盘IO。(innodb_change_buffer_max_size,默认值是25%,读多写少的业务:5-10%)
    (3)自适应哈希索引(Adaptive Hash Index);mysql自己维护,为了加速读请求。当业务有大量like或者join,AHI会成为负担,可手动关闭。
    (4)日志缓冲(Log Buffer);极大优化redo日志性能,并提供了高并发与强一致性的折衷方案。技术点包括:redo log作用,流程,三层架构,随机写优化为顺序写,次次写优化为批量写
(2)OS Cache,这一层属于内核态内存;
(3)磁盘,这一层在文件系统上,主要包括日志与表空间;

redo log,保证已提交事务的ACID特性。
(1)事务提交的时候,会写入内存Log Buffer
(2)MySQL发起系统调用写文件write(优化为:顺序写),Log Buffer里的数据,才会写到OS cache。此时认为文件已经写完。什么时候fsync到磁盘,是操作系统决定的(当然,MySQL也可以主动flush)
(3)由操作系统将OS cache里的数据,最终fsync到磁盘上(批量写)(但唯一索引每次都会fsync,保证唯一性检查)
MySQL事务提交时刷redo log有三种策略(参数):
(1)innodb_flush_log_at_trx_commit=0:每秒write一次OS cache,同时fsync刷磁盘,性能好;
(2)innodb_flush_log_at_trx_commit=1:每次都write入OS cache,同时fsync刷磁盘,一致性好;
(3)innodb_flush_log_at_trx_commit=2:每次都write入OS cache,每秒fsync刷磁盘,折衷;业界最佳实践 。
undo log有两个作用:
(1)回滚:undo log和redo log记录物理日志不一样,它是逻辑日志,记录delete、insert、update的反向操作,用来rollback。
(2)MVCC:当读取的某一行被其他事务锁定时,它可以从undo log中分析出该行记录以前的数据是什么。(即可重复读的解决方式)
(3)undo log也会产生redo log,因为undo log也要实现持久性保护
(4)mysql支持 128*1024=10w个undo操作

当前读:
通过加record lock(记录锁)和gap lock(间隙锁)来实现的
快照读:
在RR级别下,快照读是通过MVVC(多版本控制)和undo log来实现的。
mysql每次对记录进行改动,都会记录一条undo日志,每条 undo日志都有一个trx_id(产生当前undo log的事务id),一个 roll_pointer(指向上一个版本undo日志,从而形成链)。

快照读需要判断哪个版本是当前事务可见的。 为此,提出了一个 ReadView 的概念,每个事务开启一个ReadView(select、insert、update、delete都会产生),中主要包含4个比较重要的内容:
 creator_trx_id:生成该 ReadView 的事务的事务ID。
 m_ids:             在生成 ReadView 时当前系统中活跃的 读写事务的 事务ID 列表。
 min_trx_id:     在生成 ReadView 时当前系统中活跃的读写事务中 最小的事务ID,也就是m_ids中的最小值。
 max_trx_id:    生成 ReadView 时系统中应该分配给下一个事务的 ID 值。
判断规则:
1、如果被访问版本的trx_id,与readview中的creator_trx_id值相同,表明当前事务在访问自己修改过的记录,该版本可以被当前事务访问;
2、如果被访问版本的trx_id,小于readview中的min_trx_id值,表明生成该版本的事务在当前事务生成readview前已经提交,该版本可以被当前事务访问;
3、如果被访问版本的trx_id,大于或等于readview中的max_trx_id值,表明生成该版本的事务在当前事务生成readview后才开启,该版本不可以被当前事务访问;
4、如果被访问版本的trx_id,值在readview的min_trx_id和max_trx_id之间,就需要判断trx_id属性值是不是在m_ids列表中?
如果在:说明创建readview时生成该版本的事务还是活跃的,该版本不可以被访问
如果不在:说明创建readview时生成该版本的事务已经被提交,该版本可以被访问;

gtid:
从MySQL 5.6.5 开始新增了一种基于 GTID 的复制方式。通过 GTID 保证了每个在主库上提交的事务在集群中有一个唯一的ID。这种方式强化了数据库的主备一致性,故障恢复以及容错能力。

读缓冲池:InnoDB对普通LRU进行了优化:
    将缓冲池分为老生代和新生代,入缓冲池的页,优先进入老生代,页被访问,才进入新生代,以解决预读失效的问题(提前把页放入了缓冲池,但最终MySQL并没有从页中读取数据,称为预读失效。)
    页被访问,且在老生代停留时间超过配置阈值的,才进入新生代,以解决缓冲池污染的问题(批量数据访问,大量热数据淘汰)
参数:
innodb_buffer_pool_size  配置缓冲池的大小,默认是128M,越大越好,80G
innodb_old_blocks_pct    老生代占整个LRU链长度的比例,默认是37
innodb_old_blocks_time  老生代停留时间窗口,默认是1000毫秒


distinct:hash结构,key为列的值,内存占用大。时间复杂为n,空间复杂度为n。
group by:将col排序、sort。时间复杂为nlogn,空间复杂度为1。

索引失效:
1.前导模糊查询不能利用索引(like '%XX'或者like '%XX%')
2.如果是组合索引的话,如果不按照索引的顺序进行查找,则会进行全表查询
3.条件中有or,可以改用union all查询:select id from t where num=10 union all select id from t where num=20
4.索引无法存储null值,is null/is not null 会执行全表扫描。可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:select id from t where num=0
5.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
6.in 和 not in 也要慎用,对于连续的数值,能用 between 就不要用 in 了:
7. 应尽量避免在where子句中对字段进行函数操作,表达式操作,会进行全表扫描。可以把表达式放在值上,而不是字段上。


分库分表
sharding-jdbc 这种 client 层方案的优点在于不用部署,运维成本低,不需要代理层的二次转发请求,性能很高,但是如果遇到升级啥的需要各个系统都重新升级版本再发布,各个系统都需要耦合 sharding-jdbc 的依赖;
mycat 这种 proxy 层方案的缺点在于需要部署,建议中小型公司选用 sharding-jdbc,client 层方案轻便,而且维护成本低,但是中大型公司最好还是选用 mycat 这类 proxy 层方案

mysql配置优化:
1、 max_connections:MySQL的最大连接数
show  variables like ‘max_connections’最大连接数
show  status      like ‘max_used_connections’使用的连接数
max_used_connections / max_connections * 100% (理想值≈ 85%) 

2、back_log:MySQL能暂存的连接数量。连接数据达到max_connections时,新来的请求将会被存在堆栈中,可暂存的数量即back_log,如果超过back_log,将不被授予连接资源。
mysql> show full processlist,发现大量Connect 的待连接进程时,就要加大back_log 的值了。
默认数值是50,可调优为128,对于Linux系统设置范围为小于512的整数

https://www.cnblogs.com/grimm/p/5649138.html

zk==========================================
zk提供了:1、文件系统  2、通知机制

zk每个znode节点存放数据上限为1M

Zookeeper的ZAB协议包括两种基本的模式:崩溃恢复(启动/异常时)、消息广播(正常时)。

分布式协调zookeeper是如何进行领导选举的?
ZAB算法:
1)发起选主投票:每个Server启动以后都询问其它的Server它要投票给谁。
2)一次上报已有/自己:对于其他server的询问,server每次根据自己的状态都回复自己推荐的leader的id和上一次处理事务的zxid(系统启动时每个server都会推荐自己)
3)接上报,计算zxid最大:收到所有Server回复以后,就计算出zxid最大的哪个Server,并将这个Server相关信息设置成下一次要投票的Server。
4)二次上报,过半数当选:这过程中获得票数最多的的sever为获胜者,如果获胜者的票数超过半数,则该server被选为leader。否则,继续这个过程,直到leader被选举出来。

一致性算法paxos、multi-paxos、raft、ZAB相同点,两阶段提交:
1、第一次提议(提交自己,或原leader)
2、根据询问的结果(判断id最大) ,提出二次决议 
3、接收决议、判断id最大、半数通过,决议产生。

不同点:
1、raft协议日志连续性,日志从leader向follower同步
2、multi-paxos、raft、ZAB都有Leader,降低多个proposer冲突

Zookeeper集群管理
1、判断节点退出/加入:所有节点在父目录下创建临时目录节点,然后watch父目录节点的子节点变化消息。一旦有机器与 zookeeper的连接断开,其所创建的临时目录节点被删除,所有其他机器都收到通知。
2、选举master:所有机器创建临时顺序编号目录节点,每次选取编号最小的机器作为master就好(master先注册)。

zookeeper锁服务分为两类,独占锁,控制时序锁。
独占锁:所有客户端都去创建 /distribute_lock 节点,成功创建的拥有锁。
控制时序锁, /distribute_lock 已经预先存在,所有客户端在它下面创建临时顺序编号目录节点,和选master一样,编号最小的获得锁。

==========================================
MongoDB,源码语言是C++,索引也是B+tree
应用不需要事务、复杂 join 支持
应用需要大量的地理位置查询、文本查询

es==========================================
ES,索引原理,调优,index量,用于场景
DB ⇒ 数据库         ⇒ 表               ⇒ 行                         ⇒ 列(Columns)
ES  ⇒ 索引(Index) ⇒ 类型(type) ⇒ 文档(Docments) ⇒ 字段(Fields)

索引:
1、有序的倒排索引(posting List,采用Roaring bitmaps结构压缩), 形成索引term index
2.索引term为b-tree结构,且经过FST(Finite State Transducers)的压缩后,放入内存。
    a、Roaring bitmaps结构压缩:有序、存差值(大数变小数,节省空间)、转bitmap存储
    b、FST(Finite State Transducers)的压缩:将字符串转为了字母+数字:重复字符减少存储,通过数字标识字符联系,形成字符串。

优化:
不需要索引的字段,一定要明确定义出来,因为默认是自动建索引的
对于String类型的字段,不需要analysis的也需要明确定义出来,因为默认也是会analysis的
选择有规律的ID很重要,随机性太大的ID(比如java的UUID)不利于查询,压缩比也低,且ES是分Segment存储的,根据ID这个大范围的Term定位到Segment的效率很重要。


1、ulimit -a 命令,查看文件句柄(open files)的个数为1024,改为:ulimit -n 655360
2、jvm老年代和新生代的比例为2:1是比较合适的
3、内存分配,官方给出了解决方案,把一半的内存分配给Luence,另外的内存分配给ElasticSearch。内存消耗大户 :Lucene, 非堆内存 (off-heap)。
4、JVM不要 超过 32 GB,会让GC 应对大内存。且,Java 使用内存指针压缩(compressed oops)技术,指针表示 偏移量(内存占用小),一旦越过 ~32 GB,指针就会切回普通对象的指针
5、关掉swap:swapoff -a,如果内存交换到磁盘上,一个 100 微秒的操作可能变成 10 毫秒。
6、Elasticsearch 默认的线程设置已经很合理了,搜索线程池可以设置的大一点,配置为 int(( 核心数 * 3 )/ 2 )+ 1 
7、GC,Elasticsearch 默认的垃圾回收器( GC )是 CMS,低延迟需求,官方建议使用。
8、最小主节点数:minimum_master_nodes 设置及其重要,为了防止集群脑裂,这个参数应该设置为法定个数就是 ( master 候选节点个数 / 2) + 1
9、集群分片数:一个分片实际上对应一个lucene 索引,而lucene索引的读写会占用很多的系统资源,因此,分片数不能设置过大。控制每个分片占用的硬盘容量不超过ES的最大JVM的堆空间设置(一般设置不超过32G)。
如果分片数过多,大大超过了节点数,很可能会导致一个节点上存在多个分片,一旦该节点故障,有可能会导致数据丢失。所以, 一般都设置分片数不超过节点数的3倍。
10、索引优化
a.修改index_buffer_size 的设置,可以设置成百分数,也可设置成具体的大小,大小可根据集群的规模做不同的设置测试。
indices.memory.index_buffer_size:10%(默认)
indices.memory.min_index_buffer_size:48mb(默认)
indices.memory.max_index_buffer_size: 32GB
b. _id字段的使用,应尽可能避免自定义_id, 以避免针对ID的版本管理;建议使用ES的默认ID生成策略或使用数字类型ID做为主键。
c. _all字段及_source字段的使用,应该注意场景和需要,_all字段包含了所有的索引字段,方便做全文检索,如果无此需求,可以禁用;
   _source存储了原始的document内容,如果没有获取原始文档数据的需求,可通过设置includes、excludes 属性来定义放入_source的字段。、
d. 合理的配置使用index属性,analyzed 和not_analyzed(分词),根据业务需求来控制字段是否分词或不分词。只有 groupby需求的字段,配置时就设置成not_analyzed, 以提高查询或聚类的效率。
11、查询优化
a. 调整filter过滤顺序,把过滤效果明显的条件提前,按照过滤效果把过滤条件排序
b. 索引时间精度优化,时间粒度越大,搜索时间越短,所以时间精度越低越好;或者,增加冗余的时间字段,精确到天,带有时间范围的查询使用该字段进行查询。
c. 查询Fetch Source优化,不取非必须的字段。举例:只需要从es中查询id这一个字段,却把所有字段查询了出来

==========================================
kafka,不同partition之间不能保证顺序。同一个partition可以保证顺序。
一个topic 可以配置几个partition,produce发送的消息分发到不同的partition中,增加producer吞吐量。
consumer接受数据的时候是按照group来接受,kafka确保每个partition只能同一个group中的同一个consumer消费。如果想要重复消费,那么需要其他的组来消费。
Zookeerper中保存这每个topic下的每个partition在每个group中消费的offset .

Kafka如何保证高吞吐量
使用顺序写方式实现数据存储,Kafka是采用不断的将数据追加到文件中,该特性利用了磁盘的顺序读写性能比传统的磁盘读写可以减少寻地址浪费的时间;
能够支持生产者与消费者(批量发送和批量消费) 减少ioc操作
可以将消息投递到缓存区中,在以定时或者/缓存大小方式将数据写入到MQ服务器中,
这样可以减少IO的网络操作,但是这种方式也存在很大缺陷数据可能会丢失。
数据零拷贝
实现数据的分区:根据Partition实现对我们的数据的分区

kafka如何处理 消息丢失和消息重复
消费端:
重复消费:建立去重表,幂等判断
消费端丢失数据:关闭自动提交offset,处理完后再手动提交移位
消费者会自动每隔一段时间将offset保存到zookeeper上,此时如果刚好将偏移量提交到zookeeper上后,但这条数据还没消费完,机器发生宕机,此时数据就丢失了。
解决方法:关闭自动提交,改成手动提交,每次数据处理完后,再提交。auto.commit.enable=true

生产端重复发送:这个没关系,消费端去重处理就行
生产端丢失消息:
1.异步方式缓冲区满了,就阻塞到那,等到缓冲区可用,不能情况缓存区
2.发送消息后回调函数,发送成功一条就发下一条,发送失败就记录到日志,等着定时脚本来扫描。

保证信息有序:
1.同步发送模式:发出消息后,必须阻塞等待收到通知后,才发送下一条消息
2.异步发送模式:一直往缓冲区写,然后一把写到队列中去

ack:
ack确认机制设置为0,表示不等待响应,不等待borker的确认信息,最小延迟,producer无法知道消息是否发生成功,消息可能丢失,但具有最大吞吐量。
ack确认机制设置为-1,也就是让消息写入leader和所有的副本,ISR列表中的所有replica都返回确认消息。
ack确认机制设置为1,leader已经接收了数据的确认信息,replica异步拉取消息,比较折中。
ack确认机制设置为2,表示producer写partition leader和其他一个follower成功的时候,broker就返回成功,无论其他的partition follower是否写成功。
ack确认机制设置为 "all" 即所有副本都同步到数据时send方法才返回, 以此来完全判断数据是否发送成功, 理论上来讲数据不会丢失。

8.
kafka 为什么那么快
Cache Filesystem Cache PageCache缓存
顺序写 由于现代的操作系统提供了预读和写技术,磁盘的顺序写大多数情况下比随机写内存还要快。
Zero-copy 零拷技术减少拷贝次数
Batching of Messages 批量量处理。合并小的请求,然后以流的方式进行交互,直顶网络上限。
Pull 拉模式 使用拉模式进行消息的获取消费,与消费端处理能力相符。

9.
kafka producer如何优化打入速度
增加线程
提高 batch.size
增加更多 producer 实例
增加 partition 数
设置 acks=-1 时,如果延迟增大:可以增大 num.replica.fetchers(follower 同步数据的线程数)来调解;
跨数据中心的传输:增加 socket 缓冲区设置以及 OS的tcp 缓冲区设置。

10
.kafka producer 打数据,ack  为 0, 1, -1 的时候代表啥, 设置 -1 的时候,什么情况下,leader 会认为一条消息 commit了
1(默认)  数据发送到Kafka后,经过leader成功接收消息的的确认,就算是发送成功了。在这种情况下,如果leader宕机了,则会丢失数据。
0 生产者将数据发送出去就不管了,不去等待任何返回。这种情况下数据传输效率最高,但是数据可靠性确是最低的。
-1 producer需要等待ISR中的所有follower都确认接收到数据后才算一次发送完成,可靠性最高。当ISR中所有Replica都向Leader发送ACK时,leader才commit,这时候producer才能认为一个请求中的消息都commit了。

11
AR(Assigned Replicas 所有副本) = ISR(In-Sync Replicas 副本同步队列) + OSR(Outof-Sync Replicas 副本同步队列)
ISR是由leader维护,follower从leader同步数据有一些延迟(包括延迟时间replica.lag.time.max.ms和延迟条数replica.lag.max.messages两个维度),任意一个超过阈值都会把follower剔除出ISR, 存入OSR列表,新加入的follower也会先存放在OSR中。OSR中同步速度追上来,会再次放入ISR(每个Partition都会有一个ISR)。

消息会先发送到leader副本,然后follower副本才能从leader中拉取消息进行同步。
当leader副本发生故障时,只有在 ISR 集合中的follower副本才有资格被选举为新的leader,而在 OSR 集合中的副本则没有任何机会(不过这个可以通过配置来改变)

unclean.leader.election.enable 为true的话,意味着OSR集合的broker 也可以参与选举,这样有可能就会丢数据,可用性提高
而如果unclean.leader.election.enable参数设置为false,只有ISR的broker可参与选举,一致性可保证。可用性降低。

12
要确定Kafka的消息是否丢失或重复,从两个方面分析入手:消息发送和消息消费。
1)、消息发送
Kafka消息发送有两种方式:同步(sync)和异步(async),默认是同步方式,可通过producer.type属性进行配置。Kafka通过配置request.required.acks属性来确认消息的生产:
0---表示不进行消息接收是否成功的确认;
1---表示当Leader接收成功时确认;
-1---表示Leader和Follower都接收成功时确认;
综上所述,有6种消息生产的情况,下面分情况来分析消息丢失的场景:
(1)acks=0,不和Kafka集群进行消息接收确认,则当网络异常、缓冲区满了等情况时,消息可能丢失;
(2)acks=1、同步模式下,只有Leader确认接收成功后但挂掉了,副本没有同步,数据可能丢失;
 
2)、消息消费
Kafka消息消费有两 个consumer接口,Low-level API和High-level API:
Low-level API:消费者自己维护offset等值,可以实现对Kafka的完全控制;
High-level API:封装了对parition和offset的管理,使用简单;
如果使用高级接口High-level API,可能存在一个问题就是当消息消费者从集群中把消息取出来、并提交了新的消息offset值后,还没来得及消费就挂掉了,那么下次再消费时之前没消费成功的消息就“诡异”的消失了;

解决办法:
针对消息丢失:同步模式下,确认机制设置为-1,即让消息写入Leader和Follower之后再确认消息发送成功;异步模式下,为防止缓冲区满,可以在配置文件设置不限制阻塞超时时间,当缓冲区满时让生产者一直处于阻塞状态;
针对消息重复:将消息的唯一标识保存到外部介质中,每次消费时判断是否处理过即可。

13
为什么Kafka不支持读写分离:
主从同步的延时问题带来的数据不一致。

==========================================
关于限流熔断降级,目前开源框架有hystrix和sentinel、Guava RateLimiter。比如Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统自适应保护等多个维度来帮助您保障微服务的稳定性,限流:令牌桶、漏斗、冷启动限流、 滑动窗口统计机制

==========================================
springboot、springcloud

服务调用的原理:
服务首先注册到注册中心eureka中(注册一个名字通过名字调用)
配置详解:
1>、eureka.client.register-with-eureka:是否向注册中心注册自己,注册为true反之为false
2>、eureka.client.fetch-registry: 是否需要去检索服务,检索为true反之为false
3>、eureka.client.serviceUrl.defaultZone : 指定服务注册中心的地址

Eureka:
1>、eureka可分为三个角色:服务发现者、服务注册者、注册发现中心,但是这三个角色并不和实际部署的模型是一对一的关系
2>、所有的网络通信都是基于http(s)协议的
高可用配置:
Eureka server 的高可用实际上就是将自己作为服务向其他服务注册中心注册自己,这样就可以形成一组互相注册的服务注册中心,以实现服务清单的互相同步,达到高可用效果。


3、服务划分:将一个完整的系统拆分成很多个服务,是一件非常困难的事,因为这涉及了具体的业务场景
4、服务部署:最佳部署容器Docker

服务发布时指定对应的服务名(IP地址和端口号),将服务注册到注册中心(eureka和zookeeper),但是这一切是Springcloud自动实现的,只需要在SpringBoot的启动类上加上@EnableDisscoveryClient注解,同一服务修改端口就可以启动多个实例调用方法:传递服务名称通过注册中心获取所有的可用实例,通过负载均衡策略(Ribbon和Feign)调用对应的服务
Ribbon和Feign的区别:
Ribbon添加的maven依赖是spring-starter-ribbon,使用@RibbonClient(value=“服务名称”)使用RestTemplate调用远程服务对应的方法,
Feign添加的maven依赖是spring-starter-feign,服务提供方提供对外接口,调用方使用,在接口上使用FeignClient(“指定服务名”),
具体区别:
1、启动类使用的注解不同,Ribbon使用的是@RibbonClient,Feign使用的是@EnableFeignClients
2、服务的指定位置不同,Ribbon是在@RibbonClient注解上声明,Feign则是在定义抽象方法的接口中使用@FeignClient声明
3、调用方式不同,Ribbon需要自己构建http请求,模拟http请求然后使用RestTemplate发送给其他服务,步骤比较繁琐。Feign则是在Ribbon的基础上进行了一次改进,采用接口调用的方式,将需要调用的其他服务的方法定义成抽象方法即可,不需要自己构建http请求,不过要注意的是抽象方法的注解、方法签名要和提供方的完全一致。

 
熔断机制:
1、当一个服务出现故障时,请求失败次数超过设定的阀值(默认50)之后,该服务就会开启熔断器,之后该服务就不进行任何业务逻辑操作,执行快速失败,直接返回请求失败的信息。其他依赖于该服务的服务就不会因为得不到响应而造成线程阻塞,这是除了该服务和依赖于该服务的部分功能不可用外,其他功能正常。
2、熔断器还有一个自我修复机制,当一个服务熔断后,经过一段时间(5s)半打开熔断器。半打开的熔断器会检查一部分请求(只能有一个请求)是否正常,其他请求执行快速失败,检查的请求如果响应成功,则可判断该服务正常了,就可关闭该服务的熔断器,反之则继续打开熔断器。这种自我熔断机制和自我修复机制可以使程序更加健壮、也可以为开发和运维减少很多不必要的工作。
3、熔断组件往往会提供一系列的监控,如:服务可用与否、熔断器是否被打开、目前的吞吐量、网络延迟状态的监控等,从而可以让开发人员和运维人员的了解服务的状况。

Eureka基础架构:
1>、服务注册中心:Eureka提供的服务端,提供服务注册与发现的功能
    a、失效剔除:对于那些非正常下线的服务实例(内存溢出、网络故障导致的),服务注册中心不能收到“服务下线”的请求,为了将这些无法提供服务的实例从服务列表中剔除,Eureka Server在启动的时候会创建一个定时任务,默认每隔一段时间(默认60s)将当前清单中超时(默认90s)没有续约的服务剔除出去。
   b、自我保护:Eureka Server 在运行期间,会统计心跳失败的比例在15分钟之内是否低于85%,如果出现低于的情况(生产环境由于网络不稳定会导致),Eureka Server会降当前的实例注册信息保护起来,让这些实例不过期,尽可能保护这些注册信息,但是在这保护期间内实例出现问题,那么客户端就很容易拿到实际上已经不存在的服务实例,会出现调用失败的情况,所以客户端必须有容错机制,比如可以使用请求重试、断路器等机制。
在本地进行开发时可以使用 eureka.server.enable-self-preseervation=false参数来关闭保护机制,以确保注册中心可以将不可用的实例剔除。
2>、服务提供者:提供服务的应用,可以是SpringBoot应用也可以是其他的技术平台且遵循Eureka通信机制的应用。他将自己提供的服务注册到Eureka,以供其他应用发现,(如:service层)
    a、服务注册:服务提供者在启动的时候会通过发送Rest请求的方式将自己注册到Eureka Server(服务注册中心)中,同时带上自身服务的一些元数据,Eureka Server 接收到这个Rest请求后,将元数据存储在一个双层结构Map中,第一层的key是服务名,第二层key是具体服务的实例名
    b、服务同步:若有两个或两个以上的Eureka Server(服务注册中心)时,他们之间是互相注册的,当服务提供者发送注册请求到一个服务注册中心时,它会将该请求转发到集群中相连的其他注册中心,从而实现注册中心间的服务同步,这样服务提供者的服务信息可以通过任意一台服务中心获取到
    c、服务续约:在注册完服务之后,服务提供者会维护一个心跳来持续告诉Eureka Server:“我还活着”,以防止Eureka Server的“剔除任务”将该服务实例从服务列表中排除出去。配置:eureka.instance.lease-renewal-in-seconds=30(续约任务的调用间隔时间,默认30秒,也就是每隔30秒向服务端发送一次心跳,证明自己依然存活),eureka.instance.lease-expiration-duration-in-seconds=90(服务失效时间,默认90秒,也就是告诉服务端,如果90秒之内没有给你发送心跳就证明我“死”了,将我剔除)   
3>、服务消费者:消费者应用从服务注册中心获取服务列表,从而使消费者可以知道去何处调用其所需要的服务,如:Ribbon实现消费方式、Feign实现消费方式
    a、获取服务:当启动服务消费者的时候,它会发送一个Rest请求给注册中心,获取上面注册的服务清单,Eureka Server会维护一份只读的服务清单来返回给客户端,并且每三十秒更新一次
    b、服务调用:在服务消费者获取到服务清单后,通过服务名可以获得具体提供服务的实例名和该实例的元信息,采用Ribbon实现负载均衡
   c、服务下线:当服务实例进行正常的关闭操作时,它会触发一个服务下线的Rest请求给Eureka Server,告诉服务注册中心“我要下线了”。服务端接收到请求之后,将该服务状态设置为下线,并把下线时间传播出去。

Eureka和zookeeper都可以提供服务注册与发现的功能,两者的区别:
Zookeeper保证了CP(C:一致性,P:分区容错性),Eureka保证了AP(A:高可用,P:分区容错)
1、Zookeeper-----当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的信息,但不能容忍直接down掉不可用的。也就是说服务注册功能对高可用性要求比较高,但是zk会出现这样的一种情况,当master节点因为网络故障与其他节点失去联系时,剩余的节点会重新选leader。问题在于,选取leader的时间过长(30~120s),且选取期间zk集群都不可用,这样就会导致选取期间注册服务瘫痪。在云部署的环境下,因网络问题使得zk集群失去master节点是较大概率会发生的事,虽然服务最终恢复,但是漫长的选择时间导致的注册长期不可用是不能容忍的
2、Eureka则看明白这一点,因此再设计的优先保证了高可用性。Eureka各个节点都是平等的,几个节点挂掉不会影响到正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端再向某个Eureka注册时如果发现连接失败,则会自动切换至其他节点,只要有一台Eureka还在,就能保证注册服务的可用(保证可用性),只不过查到的信息可能不是最新的(不保证一致性)。除此之外Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时就会出现以下几种情况:
1>、Eureka不再从注册列表移除因为长时间没收到心跳而应该过期的服务
2>、Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(保证当前节点可用)
3>、当网络稳定时,当前实例新的注册信息会被同步到其它节点中


CAP理论:
1、Consistency:指数据的强一致性。如果写入某个数据成功,之后读取,读到的都是新写入的数据;如果写入失败,读到的都不是写入失败的数据。
2、Availability:指服务的可用性
3、Partition-tolerance:指分区容错

Ribbon和Nginx的区别:
Nginx性能好,但Ribbon可以剔除不健康节点,Nginx剔除比较麻烦,Ribbon是客户端负载均衡,Nginx是服务端负载均衡

==========================================
DDD 领域驱动开发

失⾎模型
Domain Object 只有属性的 getter/setter ⽅法的纯数据类,所有的业务逻辑完全由 busi‐ness object 来完成。

贫⾎模型
简单来说,就是 Domain Object 包含了不依赖于持久化的领域逻辑,⽽那些依赖持久化的领域逻辑被分离到 Service 层。
优点:各层单向依赖,结构清晰。
缺点:Domain Object 的部分⽐较紧密依赖的持久化 Domain Logic 被分离到 Service 层,显得不够 OO,Service 层过于厚重


充⾎模型
和第⼆种模型差不多,区别在于业务逻辑划分,将绝⼤多数业务逻辑放到 Domain中
优点:
更加符合 OO 的原则;Service 层很薄,只充当 Facade(门面) 的⻆⾊,不和 DAO 打交道。
缺点:
DAO 和 Domain Object 形成了双向依赖,复杂的双向依赖会导致很多潜在的问题。
如何划分 Service 层逻辑和 Domain 层逻辑是⾮常含混的,在实际项⽬中,由于设计
和开发⼈员的⽔平差异,可能 导致整个结构的混乱⽆序。

胀⾎模型
基于充⾎模型的第三个缺点,有同学提出,⼲脆取消 Service 层,只剩下 Domain Object和 DAO 两层,在 Domain Object 的 Domain Logic 上⾯封装事务。
优点:简化了分层,也算符合 OO
该模型缺点:很多不是 Domain Logic 的 Service 逻辑也被强⾏放⼊ Domain Object ,引起了Domain Object 模型的不稳定;Domain Object 暴露给 Web 层过多的信息,可能引起意想不到的副作⽤。


防腐层
亦称适配层。在⼀个上下⽂中,有时需要对外部上下⽂进⾏访问,通常会引⼊防腐层的概念来对外部上下⽂的访问进⾏⼀次转义。
有以下⼏种情况会考虑引⼊防腐层:
1、需要将外部上下⽂中的模型翻译成本上下⽂理解的模型。
2、不同上下⽂之间的团队协作关系,如果是供奉者关系,建议引⼊防腐层,避免外部上下⽂变化对本上下⽂的侵蚀。
3、该访问本上下⽂使⽤⼴泛,为了避免改动影响范围过⼤。

菱形架构:
http://it.hzqiuxm.com/%E9%A2%86%E5%9F%9F%E9%A9%B1%E5%8A%A8%E6%9E%B6%E6%9E%84%E7%AF%87/

==========================================
分布式事务,seata
1、AT 模式,两阶段:预留+执行/回滚
好处:业务开发无侵入,框架完成sql拦截、生成数据的before image、after image(回滚时验证脏写)、行锁、回滚删除img。
缺点:全局行锁,效率低

2、TCC模式,两阶段:预留+执行/回滚
好处:无行锁,性能高
缺点:业务需拆分为两阶段业务,侵入性高
三种异常:
Try未执行,Cancel执行了,解决方案:cancel空回滚
多次Confirm,解决方案:业务幂等
Cancel接口 比 Try接口先执行,解决方案:cancel悬挂

3、Saga: 是长事务解决方案,事务驱动
好处:参与者包含其他公司或遗留系统服务,无法提供 TCC 模式要求的三个接口,典型金融机构业务
一阶段提交本地数据库事务,无锁,高薪能
补偿服务即正向服务的 “反向”,高吞吐
参与者可异步执行,高吞吐

4、XA无侵入,将快照数据和行锁等通过 XA 指令委托给了数据库来完成。
分布式强一致性的解决方案,但性能低而使用较少。

==========================================
大数据,hadoop spark flink jstorm hbase akka比较
==========================================
列式存储,clickhouse
ClickHouse从OLAP场景需求出发,定制开发了一套全新的高效列式存储引擎,并且实现了数据有序存储、主键索引、稀疏索引、数据Sharding(自定义)、数据Partitioning(自定义)、TTL、主备复制等丰富功能

适用于:
1、读多于写。
2、大宽表,读大量行但是少量列。
3、数据批量写入。
4、无需事务,数据一致性要求低。
5、灵活多变,不适合预先建模。

列式存储好处:
1、宽表少量列读出时,降低磁盘io。
2、高压缩比 --> 高缓存率。

特性:
1、支持在建表时,指定将数据按照某些列进行sort by,等值、范围查询时,降低IO,也能充分利用操作系统page cache。
2、主键索引,按照index granularity(默认8192行)进行划分,主键索引存储每个index granularity的第一行的pk值,不排重。
3、稀疏索引,minmax、set(max_rows)、ngrambf_v1、tokenbf_v1、bloom_filter等
4、数据Sharding,解决分片间数据倾斜问题:random随机分片、constant固定分片、column value分片、自定义表达式分片。
5、数据Partitioning,在建表时可以指定合法表达式做数据分区,toYYYYMM()按月分区、toMonday()按照周几分区、Enum类型每值一个分区。
6、数据TTL,保留热数据:列级别、行级别、分区级别。
7、高吞吐写入:LSM Tree(log-struct-merge)的结构(磁盘批量的顺序写,log-struct),定期在后台merge(树状结构,从上层向下层合并)。50MB-200MB/s。(适用于写多读少,读的时候,先读上层,没有再读下层,所以读较慢)
8、多核并行、分布式计算、主备同步。

==========================================
脑裂,从与主失去心跳,变成主。 
1,从延迟选主动作
2,增加心跳
3,主增加分布式锁,锁资源,只有一个节点能编辑资源
4,从增加自我ping判断,是否自己断网

==========================================
linux系统单机支持的tcp连接数主要受三个方面的限制:

1、文件描述符的限制:
每个tcp连接都对应一个socket对象,而每个socket对象本身就占用一个文件描述符

2、tcp本身的限制
系统内存限制


Linux下TCP最大连接数限制修改:
1、文件 /etc/sysctl.conf,增加以下设置,运行 sysctl -p即可生效:

该参数设置系统的TIME_WAIT的数量,如果超过默认值则会被立即清除
net.ipv4.tcp_max_tw_buckets = 20000

定义了系统中每一个端口最大的监听队列的长度,这是个全局的参数
net.core.somaxconn = 65535

对于还未获得对方确认的连接请求,可保存在队列中的最大数目
net.ipv4.tcp_max_syn_backlog = 262144

在每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数目
net.core.netdev_max_backlog = 30000

能够更快地回收TIME-WAIT套接字。此选项会导致处于NAT网络的客户端超时,建议为0
net.ipv4.tcp_tw_recycle = 0

系统所有进程一共可以打开的文件数量
fs.file-max = 6815744

防火墙跟踪表的大小。注意:如果防火墙没开则会提示error: "net.netfilter.nf_conntrack_max" is an unknown key,忽略即可
net.netfilter.nf_conntrack_max = 2621440

2、修改打开文件限制
(1)ulimit -HSn 102400 这只是在当前终端有效,退出之后,open files 又变为默认值。
(2)将ulimit -HSn 102400写到/etc/profile中,这样每次登录终端时,都会自动执行/etc/profile。
(3)令修改open files的数值永久生效,则必须修改配置文件:/etc/security/limits.conf. 在这个文件后加上:
soft nofile 1024000
hard nofile 1024000
root soft nofile 1024000
root hard nofile 10240

==========================================
设计模式五大原则:SOLID原则
开闭原则:当需求改变时,在不修改源代码的前提下,可以扩展模块的功能,满足新的需求。例如:Windows 的桌面主题设计
里氏替换原则:所有父类出现的地方,子类可替换父类。子类可以扩展父类的功能,但不能改变父类原有的功能。例如:企鹅属于鸟类;但它不飞,所以不能定义成“鸟”的子类。
依赖倒置原则:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象;
单一职责原则:的核心就是控制类的粒度大小、将对象解耦、提高其内聚性。一个类只负责一项职责,提高类的可读性,提高系统的可维护性。例如:学校系统中,老师、学生的角色。
接口隔离原则:将臃肿庞大的接口拆分成更小的和更具体的接口,让接口中只包含客户感兴趣的方法。例如:学生成绩管理程序,将诸多功能分类放在输入、统计、打印 3 个接口中。

==========================================

在RPC框架中主要有以下4个角色:

注册中心:RpcAutoConfiguration实现
1 ServiceDiscovery:provider初始化、注册、响应远程调用。consumer订阅注册中心、监听服务提供者的变化。
2 monitor:统计服务的消费和调用情况。(可扩展限流、熔断等,如:hystrix)

服务提供者,ProviderAutoConfiguration完成:
1 扫描服务提供者中所有提供服务的类(被@RpcService修饰的类),并将其交由BeanFactory管理。
2 启动Netty服务端RpcServer,用来收到消费者的调用消息,并且返回调用结果。
3 向注册中心注册。

消费者,ConsumerAutoConfiguration完成:
1 为所有被@RpcConsumer的类,设置上动态代理
2 启动动态代理:根据provider名称获取一个服务提供者的地址,构建请求,并调用network请求及响应。

网络及代理:netWorker + RpcProxy
1 RpcProxy:(消费者)动态代理的实现。
2 netWorker:用Netty实现的RpcServer、RpcClient

全链路===============================================

zipkin和cat对代码有一定的侵入性。而pinpoint和skywalking都是基于字节码注入技术,可以做到完全的代码无侵入。
pinpoint:韩国人开发。只支持java、php。存储只支持hbase。
skywalking:中国人开发。社区活跃。多语言支持。支持es、mysql、tiDB、sharding-sphere等。JVM监控:Heap, Non-Heap, GC(YGC和FGC)


===============================================
/**
 * 快速排序
 * @param array
 */
public static void quickSort(int[] array) {
    int len;
    if(array == null
            || (len = array.length) == 0
            || len == 1) {
        return ;
    }
    sort(array, 0, len - 1);
}
/**
 * 快排核心算法,递归实现
 * @param array
 * @param left
 * @param right
 */
public static void sort(int[] array, int left, int right) {
    if(left > right) {
        return;
    }
    // base中存放基准数
    int base = array[left];
    int i = left, j = right;
    while(i != j) {
        // 顺序很重要,先从右边开始往左找,直到找到比base值小的数
        while(array[j] >= base && i < j) {
            j--;
        }

        // 再从左往右边找,直到找到比base值大的数
        while(array[i] <= base && i < j) {
            i++;
        }

        // 上面的循环结束表示找到了位置或者(i>=j)了,交换两个数在数组中的位置
        if(i < j) {
            int tmp = array[i];
            array[i] = array[j];
            array[j] = tmp;
        }
    }

    // 将基准数放到中间的位置(基准数归位)
    array[left] = array[i];
    array[i] = base;

    // 递归,继续向基准的左右两边执行和上面同样的操作
    // i的索引处为上面已确定好的基准值的位置,无需再处理
    sort(array, left, i - 1);
    sort(array, i + 1, right);
}

public class Singleton        
{        
    private static Singleton instance = new Singleton();        
    private Singleton(){        
        …        
    }        
    public static Singleton getInstance(){        
             return instance;         
    }        
}   

/**
     * 快速排序方法
     * @param array
     * @param start
     * @param end
     * @return
     */
    public static int[] QuickSort(int[] array, int start, int end) {
        if (array.length < 1 || start < 0 || end >= array.length || start > end) return null;
        int smallIndex = partition(array, start, end);
        if (smallIndex > start)
            QuickSort(array, start, smallIndex - 1);
        if (smallIndex < end)
            QuickSort(array, smallIndex + 1, end);
        return array;
    }
    /**
     * 快速排序算法——partition
     * @param array
     * @param start
     * @param end
     * @return
     */
    public static int partition(int[] array, int start, int end) {
        int pivot = (int) (start + Math.random() * (end - start + 1));
        int smallIndex = start - 1;
        swap(array, pivot, end);
        for (int i = start; i <= end; i++)
            if (array[i] <= array[end]) {
                smallIndex++;
                if (i > smallIndex)
                    swap(array, i, smallIndex);
            }
        return smallIndex;
    }

    /**
     * 交换数组内两个元素
     * @param array
     * @param i
     * @param j
     */
    public static void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值