导图社区 JAVA企业级开发
当今大型软件系统的开发多采用企业级的开发模式,而Java语言也是目前较为流行的企业级开发语言之一。针对Java企业级开发,涉及的知识点和技术栈较为丰富,包括但不限于Java EE、Spring框架、Hibernate框架、Maven、Git、Jenkins等等。这份思维导图以Java企业级开发为主题,通过图解的形式将涉及的知识点进行了梳理和整理,从Java EE体系结构、Servlet、JSP、Spring框架、Hibernate框架、Maven等基础知识开始讲解,逐步深入到SpringMVC、
编辑于2023-02-16 19:19:34 四川省这份思维导图主要按照《python从入门到实践》的大纲来做出来的,并在相关内容的解释处加入了相关代码,欢迎大家一起学习!
职能地图-Java,干货分享~Java语言技术,java技术扩展,数据结构,维优,个人职能,技术面试知识点总结。
当今大型软件系统的开发多采用企业级的开发模式,而Java语言也是目前较为流行的企业级开发语言之一。针对Java企业级开发,涉及的知识点和技术栈较为丰富,包括但不限于Java EE、Spring框架、Hibernate框架、Maven、Git、Jenkins等等。这份思维导图以Java企业级开发为主题,通过图解的形式将涉及的知识点进行了梳理和整理,从Java EE体系结构、Servlet、JSP、Spring框架、Hibernate框架、Maven等基础知识开始讲解,逐步深入到SpringMVC、
社区模板帮助中心,点此进入>>
这份思维导图主要按照《python从入门到实践》的大纲来做出来的,并在相关内容的解释处加入了相关代码,欢迎大家一起学习!
职能地图-Java,干货分享~Java语言技术,java技术扩展,数据结构,维优,个人职能,技术面试知识点总结。
当今大型软件系统的开发多采用企业级的开发模式,而Java语言也是目前较为流行的企业级开发语言之一。针对Java企业级开发,涉及的知识点和技术栈较为丰富,包括但不限于Java EE、Spring框架、Hibernate框架、Maven、Git、Jenkins等等。这份思维导图以Java企业级开发为主题,通过图解的形式将涉及的知识点进行了梳理和整理,从Java EE体系结构、Servlet、JSP、Spring框架、Hibernate框架、Maven等基础知识开始讲解,逐步深入到SpringMVC、
JAVA企业级开发
web
html
css
javascript
vue
el表达式
div id="app"> p>{{ message }}p> div>
NPM方法
npm -v
cnpm install -npm -g
npm install cnpm -g
升级或安装cnpm
cnpm install vue
最稳定版本
命令行工具
全局安装vue-cli
cnpm install --global vue-cli
创建模板的新项目
vue init webpack my-project
进入项目安装并运行
$ cd my-project $ cnpm install $ cnpm run dev DONE Compiled successfully in 4388ms > Listening at http://localhost:8080
目录结构
目录解析
build
config
node_modules
起步
每一个vue应用都需要通过实例化Vue来实现
var rm = new Vuew({})
data
用于定义属性,实例中有三个属性
site
url
alexa
method
用于定义函数,可以通过return 返回值
{{ }}
用于输出对象属性和函数返回值
都有前缀$
模板语法
html
v-html
将html代码显示
v-model
如果为true使用class1样式否则不适用该类
v-bind
使用use值,如果为true使用class1类的样式,否则不使用该类
v-if
v-if 指令将根据表达式 seen 的值(true 或 false )来决定是否显示元素
v-on
javascript表达式
过滤器
{{ messge | capitalize}}
可以串联
{{ message filterA filterB }}
可以是javascript函数
{{ message filterA('arg1', arg2) }}
条件语句
v-if
v-else
v-else-if
v-show
循环语句
v-for
可以通过一个对象的属性来迭代数据:
key value
key value index
循环整数
计算属性
computed
computed: { // 计算属性的 getter reversedMessage: function () { // `this` 指向 vm 实例 return this.message.split('').reverse().join('') } }
methods
methods: { reversedMessage2: function () { return this.message.split('').reverse().join('') } }
监听属性
watch
angular
react
JavaSE
JVM
JVM运行机制
java源文件->.Class文件->机器码
类加载器子系统
运行时数据区
方法区
运行时常量池
本地方法区
程序计数器
虚拟机栈
栈针
虚拟机堆
新生代
老年代
永久代
执行引擎
即使编译器
垃圾回收机制
本地接口库
本地方法库
多线程
jvm允许一个进程同事并发执行多个线程,在jvm线程本地存储,缓冲区分配,同步对象,栈,程序计数器等准备工作都完成,jvm会调用操作系统的接口创建一个与之对应的原生线程
虚拟机线程
虚拟线程在jvm到达安全点时出现
周期性任务线程
通过定时器调度线程来实现周期性操作的执行
GC线程
支持JVM中不同的垃圾回收机制
编译器线程
编译器线程在运行时将字节码动态编译成本地平台机器码
信号分发线程
接收发送到jvm的信号并调用jvm方法
JVM的内存区域
线程私有区域
程序计数器
程序技术器是一块很小的内存空间,用于存储当前运行的线程所执行的字节码的行号指示器
虚拟机栈
栈针
局部变量
操作数栈
动态链接
方法出口
本地方法区
线程共享区域
方法区
本地方法区和虚拟机栈的作用类似,区别是虚拟机栈为执行java方法服务,本地方法栈为Native方法服务
存储常量
静态变量
类信息
即使编译后代码
运行时常量
堆
在jvm运行过程中创建的对象和产生的数据都被存储在堆中,堆是被线程共享的内存区域,也是垃圾收集器进行垃圾回收的主要的内存区域
直接内存
jvm的运行时内存
新生代
jvm新创建的对象(除了大对象)会被存放在新生代,默认占1/3堆内存空间,由于jvm会频繁创建对象,所以新生代会频繁的处罚MinorGC进行垃圾回收。
Eden区
ServiorTo区
保留上一次MinorGC时的幸存者
ServiorFrom区
将上次MinorGC时的幸存者作为这一次MInorGC的被扫描着
老年代
老念叨主要存放有长生命周期的对象和大对象
老年代的GC过程叫做MajorGC
在进行MajorGC前,JVM会进行一次MinorGC,在MinorGC过后仍然出现老年代空间不足或无法找到足够大的连续空间分配给创建的大对象时,会处罚MajorGC进行垃圾回收
永久代
指内存的永久保存区域,主要存放Class和Meta(元数据)
GC不会在程序的运行期间对永久代的内存进行清理,这也导致了永久代的内存会随着加载的Class文件的增加而增加
MinorGC
新生代GC的过程,采用复制算法
1.把Eden区和ServivorFrom区中存活的对象复制到ServivorTo区
清空Eden区和ServivorFrom区中的对象
将ServivorTo区和ServivorFrom区互换
垃圾回收与算法
如何确定垃圾
引用计数法
循环引用的问题
为对象添加一个引用时计数加一
为对象减少一个引用时,计数减一
如果一个对象的引用计数为0,则表示刺此刻该对象没有被引用
如果两个对象相互引用,导致他们的引用一直存在
可达性分析
以一系列GCRoots的点作为起点向下搜索,当一个对象到任何GC Roots都没有引用链接相连时,说明其已经死亡
根搜索算法
栈中的引用
方法区中的静态引用
JNI中的引用
java中常用的垃圾回收算法
标记清楚算法
标记和清除,标记清除算法在清理对象所占用的内存空间后并没有重新整理可用的内存空间,因此如果内存中被回收的小对象居多,则会引起内存碎片化的问题,引发大对象无法获得连续可用空间的问题
效率低
内存碎片多
复制算法
将内存分为两块带下相等的内存区域,将区域1的对象标记移到区域二
同一时刻,只有一个内存可以用内存空间会被压缩一半
标记整理算法
整合了标记清楚算法和复制算法的优点
分代收集算法
针对不同对象类型,jvm采用了不同的垃圾回收算法
新生代采用复制算法
老年代存放大对象和生命周期长的对象可回收的对象相对较少
java四中引用类型
强引用
把一个对象赋值给一个引用变量时,这个引用变量就是一个强引用
软引用
如果一个对象只有软引用,怎在系统内存空间不足时改对象被回收
弱引用
如果一个对象只有弱引用,则在垃圾回收过程中一定会被回收
虚引用
通过PhantomReference实现,虚引用和引用队列联合使用,主要用于跟踪对象的垃圾回收状态
分代收集算法
新生代与复制算法
老年代与标记整理算法
分区收集算法
垃圾收集器
针对新生代
Serial
单线程,复制算法,他正在进行垃圾收集时,必须暂停掐所有工程线程,直到垃圾收集结束
ParNew
多线程复制算法 在垃圾收集中会暂停所有其他工作线程 是java虚拟机运行在Server模式下的新生代的默认垃圾收集器 ParNew垃圾收集器默认开启与cpu同等数量的线程进行垃圾回收,在java应用启动时可以通过-XX:ParallelGCThreads参数调节ParNew垃圾收集器的工作线程数
Parallel Scavenage
是为了提高新生代垃圾收集效率而设计的垃圾收集器,基于多线程复制算法实现,在系统的吞吐量上有很大的优化,可以更高的利用CPU尽快的完成垃圾回收任务
-XX:MaxGCPauseMillis
控制最大的垃圾收集停顿时间
-XX:GCTimeRatio
控制吞吐量大小
UseAdaptiveSizePolicy
控制自适应调节策略开启与否
老年代
CMS
是为老年代设计的垃圾收集器,其主要目的是达到短时间的垃圾回收停顿时间
初始标记
只标记和GC Roots直接相关联的对象,速度快,需要暂停所有工作线程
并发标记
和用户线程一起工作,执行GC Roots跟踪标记过程,不需要暂停工作线程
重新标记
在并发标记过程中用户线程继续运行,导致在垃圾回收过程中部分对象的状态发生变化,为了确保对象的状态正确性,需要对其重新标记并暂停工作线程
并发清楚
和用户线程一起工作,执行清楚GC Roots不可表达对象的任务,不需要暂停工作线程
Serial Old
是Serial垃圾收集器的老年代实现,同Serial一样采用单线程执行,基于标记整理算法实现
Parallel Old
采用多线程并发进行垃圾回收,在设计上优先考虑系统吞吐量,其次考虑停顿时间等因素
GI
java网络编程模型
阻塞io模型
一直等待内存数据就绪
非阻塞io模型
用户线程不断询问内核数据是否就绪,如果没有就绪,用户线程可以执行其他任务
多路复用io模型
被称为selecttor的线程不断的轮询多个 socket状态
信号驱动io模型
用户线程发起一个I/O请求操作时,系统会为该请求对应的Socket注册一个信号函数,然后用户线程可以继续执行其他业务逻辑
异步IO模型
java I/O
java NIO
Selecoter
Channel
FIleChannel
DatagramChannel
SocketChannel
ServerSocketChannel
Buffer
ByteBuffer
IntBuffer
CharBuffer
LongBuffer
DoubleBuffer
jvm类加载机制
类加载阶段
氛围五个阶段,在类初始化完成后就可以使用该类信息,一个类不再被需要时可以从jvm卸载
1.加载
jvm读取class文件,并且根据class文件描述创建java.lang.class对象的过程 包含将class文件读取到运行时区域的方法区内,在队中创建java.lang.class对象。并封装类在方法区的数据结构的过程。在读取class文件时既可以通过文件的形式读取,也可以通过jar包、war包读取,还可以通过代理自动生成class或其他方式读取
2.验证
用于保证class文件符合当前虚拟机的要求
3.准备
在方法区中为类分配内存空间,并设置类中的变量初始值。初始值指不同数据类型的默认值
4.解析
jvm将常量池中的符号引用替换为直接引用
5.初始化
类加载器
启动类加载器
负责加载java_home/lib目录中的类库。或通过-Xbootclasspath参数指定路径中被虚拟机认可的类库
扩展类加载器
负责加载java_home/lib/ext目录中的类库,或者工通过java.ext.dirs系统变量加载指定路径中的类库
应用程序类加载器
负责加载用户路径
双亲委派机制
一个类在收到类在家请求后不会常会自己加载这个类,而是把该类加载请求向上委派给其父类去完成,其父类在接收到该类加载请求后又会将其委派给自己的父类,若父类加载器在收到类加载请求后发现自己也无法加载该类(通常原因是该类的Class文件在父类的类加载路径中不存在),则父类会将该信息反馈给子类,并向下委派子类加载器加载改类。直到该类被加载成功
1.将自定义加载器挂载到应用程序类加载器
2.应用程序类加载器将类加载请求委托给扩展类加载器
3.扩展类加载器将类加载请求委托给启动类加载器
4.启动类加载器在加载路径下查找并加载class文件,如果没有找到目标class文件,则交由扩展类加载器加载
5.扩展类加载器在加载路径下查找并加载Class文件,如果没有找到目标Class文件,则交由引用程序类加载器加载
6.应用程序类加载器在加载路径下查找并加载Class文件,如果没有找到目标Class文件,则交由自定义加载器加载
7.自定义加载器下查找并加载用户指定目标
OSGI
jvm实际操作
如何使用jmap
使用dump内存信息到heap.bin文件 jmap -dump:live,format=b,file=heap.bin 1963
查看该进程下堆内存的使用情况 jmap -heap 1963(进程号)
快速定位内存泄露的方法: jmap -histo:live 1963
jps
查询系统中HotSpot虚拟机进程
-l:输出主类全名或jar路径
-q:抑制类名的输出,JAR文件名和传递给main方法的参数,仅生成本地JVM标识符列表
-m:输出JVM启动时传递给main()的参数
-v:输出JVM启动时显示指定的JVM参数
jstack
使用jstack线程号查询线程状态
线程状态
初始状态
New,线程对象创建出来后,没有调用start方法,线程处于初始状态
运行状态
1.就绪状态:Ready,调用了Start方法,等待CPU分配资源 2.运行状态:RUNNING,CPU分配资源给该线程,该线程处于运行状态
阻塞状态 BLOCKED
线程获取资源,如果资源获取成功则正常运行,如果资源获取失败,就处于阻塞状态,等待什么时候获取到资源再变为运行状态
等待状态 WAITING
线程手动调用了wait()方法,或者join()方法,这些方法都是主动进入等待状态,等待状态会将CPU资源让渡 需要其他线程手动唤醒,notify(),notifyAll()唤起所有的等待线程
超时等待状态 TIMED_WAITING
线程手动调用了wait()方法,或者join()方法,这些方法都是主动进入等待状态,等待状态会将CPU资源让渡 需要其他线程手动唤醒,notify(),notifyAll()唤起所有的等待线程
终止状态 DIED
线程结束之后的状态
查看是否BLOCKED导致线程死锁
jhat
jhat -J-mx512m heap.bin
jstat
监视垃圾回收(GC)时间,次数
jstat -compiler 线程号
输出JIT编译过的方法数量耗时等
Compiled:编译数量 Failed:编译失败数量 Invalid:无效数量 Time:编译耗时 FailedType:失败类型 FailedMethod:失败方法的全限定名
jstat -gc 线程号
垃圾回收堆的行为统计,
YGC : 年轻代YGC的次数 YGCT :年轻代YGC所消耗的时间 FGC : 年老代full GC的次数 FGCT :年老代full GC所消耗的时间 GCT : 用于GC所消耗的总时间
S0: 幸存区0 S1: 幸存区1 E:年轻代 O:年老代 M:持久代
jconsole
可以查看本地的进程,也可以查看远程主机上的进程
connection连接
pid
host
port
-j
用法
-interval
将更新间设置为n秒(默认值为4s)
-notile
初始不平铺窗口
-pluginpath
指定jconsole用于查找插件的路径
-version
输出程序版本
jvm分析dump工具
mat
visualVm
load dump文件后
右键点击Heap Dump查看存储
右键Thread Dump 查看线程状态(等于jstack)
ApplicationSnapshot 查看jvm配置参数
jvm配置参数
1.常用跟踪监控参数:
①打印gc简要信息
-XX:+PrintGC
-verbose:gc
②打印gc详细信息及堆使用详细信息:-XX:+PrintGCDetails
eden(伊甸区)
from
to
新生代
老年代
元空间
③将gc日志记录到外部文件中去:-Xloggc:log/gc.log(参数中gc.log就是外部文件的名称)
④监控类的加载情况:-XX:+TraceClassLoading
2.常用堆分配参数
①最大堆:-Xmx
java程序最大能使用多少内存大小,如果超过这个大小,那么java程序会报:out of memory(OOM错误)
②最小堆:-Xms
③指定新生代的内存:-Xmn
④新生代(eden+from+to)和老年代(不包含永久区)的比值:-XX:NewRatio
-Xns5m先分配5m的空间 -XX:NewRatio=4,则表示新生代:老年代=1:4,那么新生代(eden+from+to)=3072+512+512=4096k,老年代=16384k,新生代:老年代=4096k:16384k=1:4。
⑤Survivor区与Eden区的比值:-XX:SurvivorRatio
若-XX:SurvivorRatio=6,则表示Survivor区:Eden区=2:6,那么Survivor区(from+to)=1024+1024=2048,Eden区=6144,Survivor区:Eden区=2048:6144=2:6,这样的话,一个幸存区占整个新生代区的1:(2+6)=1/8 ———————————————— 版权声明:本文为CSDN博主「zj_daydayup」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/u012556994/article/details/81264962
发生OOM异常时把堆栈信息打印到外部文件
-XX:+HeapDumpOnOutOfMemoryError
-XX:+HeapDumpPath
将堆的最小值-Xms 参数与最大值-Xmx 参数设置为一样即可避免堆自动扩展
因为JVM初始分配的内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的内存由-Xmx指 定,默认是物理内存的1/4。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制。因此服务器一般设置-Xms、-Xmx相等以避免在每次GC 后调整堆的大小。对象的堆内存由称为垃圾回收器的自动内存管理系统回收。 ———————————————— 版权声明:本文为CSDN博主「zj_daydayup」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/u012556994/article/details/81264962
3、常用栈分配参数
①栈大小参数为-Xss
栈大小通常只有几百k,决定于函数调用的深度。每个线程都有自己独立的栈空间(私有栈空间,不共享)。如果函数调用太深,超过了栈的大小,则会抛出java.lang.StackOverflowError。遇到这种错误,通常情况下,不是去调整-Xss参数,而是应该去检查是否是函数调用太深导致的,是否使用了递归,能不能保证递归出口等。 ———————————————— 版权声明:本文为CSDN博主「zj_daydayup」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/u012556994/article/details/81264962
java算法
二分查找
实现原理
查找的必须是有序集合
二分查找法的实现
public static int binarySearch(int [] array,int a){ int low = 0; int high =array.length - 1; int mid ; while(low mid = (low + high)/2;//中间位置 if(array[mid] == a){ return mid; }else if(a > array[mid]){//向右查找 low = mid + 1; }else{//向左查找 high = mid -1; } } return -1; }
冒泡排序
冒泡排序重复访问要排序的元素,一次比较相邻的两个元素,如果左边的元素大于右边的元素,就将二者姜欢,如此重复,直到没有相邻的元素需要交换位置,这个时候列表的元素排序完成
冒泡排序算法的原理
代码实现
public static int[] bubbleSort(int [] arr){ //外层循环控制排序躺数 for(int i=0;i //内层循环控制每一层趟排序多少次 for(int j =0;j if(arr[j] > arr[j+1]){ int temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } } } return arr; }
插入排序算法
将一个数据插入已经排好的序列中,从而得到一个新的有序数据,所以用于少量的数据的排序
插入排序算法原理
在排序时将该数组分为两个子集。一个是有序的L子集,另一个是无序的R子集。每次都从R中拿出一个元素插入L中从右至左比自己打的元素后面,然后将L中比自己打的所有元素整体往后移。重复上面的操作直到R子集的数据为空
代码实现
public static int[] insertSort(int arr[]){ for(int i=1;i //插入数据 int insertVal = arr[i]; //被插入的位置(准备和前一个数进行比较) int index = i-1; //如果插入的数比被插入的数小 while(index>0 && insertVal //则将arr[index]向以后移动 arr[index+1] = arr[index]; index --; } arr[index+1] = insertVal; } return arr; }
快速排序算法
是对冒泡排序的一种改进,通过一趟排序将要排序的数据序列分成独立的两部分,其中一一部分的所有数据比另一部分的所有数据都要笑,然后按此方法对两部分数据分别进行快速排序
java数据结构
栈
顶部元素插入和取出快
定义数据结构
数据入栈push
数据出栈pop
数据查询peek
队列
顶部元素插入和尾部元素取出快
add()
peek()
poll()
链表
插入和删除快,查找慢
属性
存储数据
存储下一个节点的指针域
特点
插入块
查询慢
单向链表
特点
链表方向是单项的
第一部分数据区用来保存数据
第二部分为指针区,用于存储下一个节点地址
单向链表的操作
查找
单向链表只可以向一个方向遍历,一般查找一个节点时需要从单向链表的第一个节点开始依次访问下一个节点
删除
插入
将插入的节点设置为头节点
单向链表的实现
定义单向链表的数据结构
public class SingleLinkedList { private int length; private Node head; public SingleLinkedList(){ length = 0; head = null; } private class Node{ private Object data; private Node next; public Node(Object data){ this.data = data; } } }
插入单向链表数据
public Object addHead(Object obj){ Node newHead = new Node(obj); if(length ==0){//如果链表为空,则将该节点设置头部节点 head = newHead; }else{//设置当前节点为头部节点,并将当前的下一个节点指向原来的头部节点 head = newHead; newHead.next = head; } length ++; return obj; }
删除单向链表节点
public boolean delete(Object value){ if(length == 0){ return false; } Node current = head; Node previous = head; while(current.data != value ){ if(current.next == null){ return false; }else{ previous = current; current = current.next; } } if(current == head){ head = current.next; }else{ previous.next = current.next; } length--; return true; }
单向链表数据查询
public Node find(Object obj){ Node current = head; int tempSize = length; while(tempSize > 0 ){ if(obj.equals(current.data)){ return current; }else{ current = current.next; } tempSize --; } return null; }
双向链表
每个数据节点中都有两个指针,分别指向其直接节后继和直接前驱节点,任意一个节点都可以直接访问它的前驱节点和后记节点
定义数据结构
public class TwoWayLinkedList { private Node head; private Node tail; private int length; private class Node{ private Object data; private Node next; private Node prev; public Node(Object data){ this.data = data; } } public TwoWayLinkedList(){ length = 0; head =null; tail =null; } }
在链表头部增加节点
/** * 链表的头部增加节点 * @param value */ public void addHead(Object value){ Node newNode = new Node(value); if(length ==0){ head = newNode; tail = newNode; length ++; }else{ head.prev = newNode; newNode.next = head; head = newNode; length ++; } }
在链表尾部增加节点
public void addTail(Object value){ Node newNode = new Node(value); if(length == 0){ head = newNode; tail = newNode; length ++; }else{ newNode.prev = tail; tail.next = newNode; tail = newNode; length++; } }
删除链表的头部节点
public Node deleteHead(){ Node temp = head; if(length !=0){ head = head.next; head.prev = null; length --; return temp; } return null; }
循环链表
单向循环链表
数据结构
private Node head; private int size = 0; public class Node{ T data; Node next; public Node(T data){ this.data = data; next = null; } }
尾部添加元素
public boolean add(T data){ boolean isSuccessful = false; //处理空表 if(head ==null){ Node node =new Node(data); head = node; node.next = head; isSuccessful = true; }else{ Node node = head; while(true){ if(node.next == head){ break; } node = node.next; } Node newNode = new Node(data); node.next = newNode; newNode.next = head; isSuccessful=true; } return isSuccessful; }
尾部删除元素
public Node delete(int index){ Node outputNode = null; Node node = head;//指向第一个节点 if(index==0){//查到该节点的前一个节点 outputNode = node; head = null; head = node.next;//删除语句 return outputNode; } int location = 0; while(true){ if(isEmpty()){ //若链表为空则中断 break; }else if(location == index - 1){ //查询该节点 outputNode = node.next; node.next = node.next.next; break; } if(node.next == null){ break; } location ++ ;//计数器 node = node.next;//节点后移 } return outputNode; }
遍历元素
public void display(){ //遍历整个链表 Node node = head; while(node !=null){ System.out.println(node.data.toString()); if(node.next == head){ break; } node = node.next; } }
双向循环链表
数据结构
private Node first; private Node last; class Node{ T data; Node next; Node pre; public Node(T data){ this.data = data; } public void displayCurrentNode(){ System.out.println(data+""); } }
构造函数
public DoublyLinkedList(){ first = null; last =null; }
添加头节点
public void addFirst(T dataue){ Node newNode = new Node(dataue); if(isEmpty()){ last = newNode;//last -> newNode }else{ first.pre = newNode;// } newNode.next = first;//newNode ->first first = newNode;//first->newNode }
添加尾节点
public void addLast(T dataue){ Node newNode = new Node(dataue); if(isEmpty()){ first = newNode;// 表头指针直接指向新节点 }else{ last.next = newNode;//last指向的节点指向 newNode.pre = last; } last = newNode; }
在该节点之前添加节点
public boolean addBefore(T key,T dataue){ Node cur = first; if(first.next.data == key){ addFirst(dataue); return true; }else{ while(cur.next.data != key){ cur = cur.next; if(cur == null){ return false; } } Node newNode = new Node(dataue); newNode.next = cur.next; cur.next.pre = newNode; newNode.pre = cur; cur.next = newNode; return true; } }
在该节点之后添加节点
public void addAfter(T key,T dataue) throws RuntimeException{ Node cur = first; while(cur.data != key){ cur = cur.next; if(cur == null){ throw new RuntimeException("Node is not exists"); } } Node newNode = new Node(dataue); if(cur == last){ //如果当前节点是尾节点 newNode.next = null;//新节点指向null last = newNode;//last指针指向新节点 }else{ newNode.next = cur.next; cur.next.pre = newNode; } newNode.pre = cur;//当前结点的前驱指向当前节点 cur.next = newNode;//当前结点的后继指向新节点 }
删除第一个节点
public void deleteFirst(){ if(first.next == null){ last =null; }else{ first.next.pre = null; } first = first.next; }
删除最后一个节点
public void deleteLast(T key){ if(first.next == null){ first = null; }else{ last.pre.next = null; } last = last.pre; }
删除对应的节点
public void deleteKey(T key) throws RuntimeException{ Node cur = first; while(cur.data != key){ cur = cur.next; if(cur == null){ throw new RuntimeException("Node is not exists"); } } if(cur == first){//如果first指向的节点 first = cur.next;//first指针后移 }else{ cur.pre.next = cur.next; } if(cur == last){//如果当前节点是尾节点 last = cur.pre;//尾节点的前驱前移 }else{ cur.next.pre = cur.pre; } }
散列表
散列表通过映射函数把关键码值映射到表中的一个位置来加快查找
散列函数
给定表M,存在函数f(key),对任意给定的关键字key,代入函数后若能得到包含该关键字的记录在表中的地址,则称M为散列表,称函数f(key)为散列函数
直接地址法
取关键字或关键字的某个线性函数值为散列地址,即h(key) = key或h(key) = axkey+b
平方取值法
取关键字平方后的中间几位为散列地址
折叠法
将关键字分割成位数相同的几部分,然后取这个几部的叠加和作为三列地址
保留余数法
取关键字被某个不大于散列表长度m的数p除后所得的余数为三列地址h(key) = key/p(p
随机数法
选择一个随机函数,取关键字的随机函数值作为其散列地址 h(key)=random(key)
java HashCode实现
java HashCode公式为f(key)=s[0]x 31 n-1次方+s[1]x31 n-2次方 + ...+s[n-1] public static int hashCode(byte[] value) { int h = 0; for (byte v : value) { h = 31 * h + (v & 0xff); } return h; }
二叉树
插入删除查找都快,删除算法复杂
特点
左子树不空,则左子树上所有节点的值均小于它根节点的值
若右子树不为空,则右子树上所有节点的值均大于或等于它的跟节点的值
左右子树也分别为二叉树
基本操作
插入操作
将待插入的新节点与当前节点进行比较,如果两个节点值相同,则表示新节点已经存在于二叉排序树中
将带插入的新节点与当前节点进行比较,如果待插入的新节点的值小于当前节点的值,则在当前节点的左子树中寻找,直到左子树为空
将待插入的新节点与当前节点进行比较,如果待插入的新节点的值大于当前节点的值,则在当前节点的右子树中寻找,直到右子树为空
删除操作
在待删除的节点没有子节点,直接删除该节点
待删除的节点只有一个子节点时,使用子节点替换当前节点然后删除
在待删除的节点有两个子节点时,首先查找该节点的替换节点(替换节点为左子树的最大节点或者右子树中的最小节点),然后替换待删除的节点为替换节点
用java实现
定义数据结构
class Node{ private int value; private Node left; private Node right; Node(int value){ this.value = value; } public Node(Node left,Node right,int value){ this.left =left; this.right = right; this.value = value; } public int getValue() { return value; } public void setValue(int value) { this.value = value; } public Node getLeft() { return left; } public void setLeft(Node left) { this.left = left; } public Node getRight() { return right; } public void setRight(Node right) { this.right = right; } }
插入节点
public void insertBST(int key){ Node p = root; //记录前一个节点 Node prev = null; //一直查找下去 while(p!=null){ prev = p; if(key p = p.getLeft(); }else if(key > p.getValue()){ p = p.getRight(); }else{ return; } if(root ==null){ root = new Node(key); }else if(key prev.setLeft(new Node(key)); }else{ prev.setRight(new Node(key)); } } }
删除节点
public void deleteBST(int key){ deleteBST(root,key); } private boolean deleteBST(Node node,int key){ if(node == null) { return false; } else{ if(key == node.getValue()){ return delete(node); }else if(key return deleteBST(node.getLeft(),key); }else{ return deleteBST(node.getRight(),key); } } } private boolean delete(Node node){ Node temp = null; if(node.getRight() == null){ temp = node; node = node.getLeft(); } else if(node.getLeft() == null){ temp = node; node = node.getRight(); }else{ temp = node; Node s = node; s = s.getLeft(); while(s.getRight() != null){ temp = s; s = s.getRight(); } node.setValue(s.getValue()); if(temp != node){ temp.setRight(s.getLeft()); }else{ temp.setLeft(s.getLeft()); } } return false; }
查找节点
public boolean searchBST(int key){ Node current = root; while(current != null){ if(key == current.getValue()){ return true; }else if(key current = current.getLeft(); }else{ current = current.getRight(); } } return false; }
红黑树
同上
特点
每个节点是黑色的或者是红色的
根节点是黑色的
每个叶子节点(NIL)都是黑色的
如果一个节点时红色的,则它的子节点必须是黑色的
从一个节点到该节点的孙子节点的所有路径上都包含相同数量的黑色节点
左旋
对a节点进行左旋,指将a节点的右子节点设为a节点的父节点
右旋
对b节点进行右旋,指将b节点的左子节点设为b节点的父节点
红黑树的添加
红黑树的添加分为三补,1、将红黑树看作一个二叉查找树,并以二叉树的插入规则插入新节点,2、将插入的节点涂为红色或者黑色,3、通过左旋,右旋或者着色操作,使之重新成为一颗红黑树
1、如果被插入的节点是根节点,则直接把此节点涂为黑色
如果被插入的节点的父节点是黑色的,则什么也不需要做,在节点插入后,仍然是红黑树
如果被插入的节点的父节点是红色的,则在被插入节点的父节点是红色时,被插入节点一定存在非空父节点
红黑树的删除
红黑树的删除妃位两步,1、将红黑树看做一颗二叉查找树,根据二茬查找树的删除规则删除节点,2、通过左旋、旋转、重新着色操作进行树修正,使之重新成为一棵红黑树
将红黑树看做一颗二叉树查找,将节点删除
如果被删除的节点没有子节点,那么直接将该节点删除
如果被删除的节点只有一个子节点,那么直接删除该节点,并用该节点的唯一子节点替换该节点
如果被删除的节点有两个子节点,那么先找出该节点的替换节点,然后把替换节点的数据复制给该节点的数据,之后删除替换的节点
通过左旋,旋转,重新着色操作进行树修正
B+Tree
特点
叶子节点连起来
双向链表
非叶子节点不存数据
数据和节点一样多
阶层是根据磁盘页数的大小计算出来
根节点要么是空,要么是独根,否则至少有两个子节点
有k个子节点的节点必有k个关键码
复合索引
先查单个索引,再根据单个索引的值查找第二个字段的索引
B-Tree
B树N叉的排序树
重要特点
节点最多含有m颗子树(指针),m-1个关键字(存的数据,空间)(m>=2)
除根几诶安和叶子节点外,其他每个节点至少有cell(m/2)个子节点(子树),cell为上取整,5/2=2.5 取整,分裂 cell取上整数 2.5>=3 分裂时候从中间冯凯,分成两个子树
非根节点有两个子节点
B*Tree
特点
B+树节点满时就会分裂,而B*树节点满时会检查兄弟节点是否满(因为每个节点都有指向兄弟的指针),如果兄弟节点未满则向兄弟节点转移关键字,如果兄弟节点已满,则从当前节点和兄弟节点各拿出1/3的数据创建一个新的节点出来;
B*树定义了非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为2/3(代替B+树的1/2);
在B+树的基础上因其初始化的容量变大,使得节点空间使用率更高,而又存有兄弟节点的指针,可以向兄弟节点转移关键字的特性使得B*树额分解次数变得更少;
散列表
插入删除查找都快,数据散列,对存储空间有浪费
图
图是由有穷非空集合的定点和定点之间的边组合的集合,通常表示为G(V,E),其中G表示一个图,V是图G中定点的集合,E是图G中边的集合
无向图
有向图
图的存储结构:领接矩阵
无向图的领接矩阵
有向图的领接矩阵
带权重的领接矩阵
图的存储结构:邻接表
数组与链表相结合的存储方法叫做邻接表。邻接表是图的一种链式存储结构,主要用于解决领接矩阵中顶点多边少时空间浪费的问题
将图中的定点信息存储在一个一维数组中,同时在定点信息中存储用于指向第一个领接定点的指正
图中的每个顶点Vi的所有领接点构成一个线性表,由于领接点的个数不定,所以用单向链表存储,如果是无向图,则称链表为顶点Vi的边表,如果是有向图,则称链表为以顶点vi为弧尾的出边表
图的遍历
广度有限遍历
深度优先遍历
位图
数组与链表相结合的存储方法叫做邻接表。邻接表是图的一种链式存储结构,主要用于解决领接矩阵中顶点多边少时空间浪费的问题
位图的数据结构
位图在内部维护了一个MxN维数组char[M][N],在这个数组里面每个字节占8位,因此可以存储MxNx8个数据,在要存储数据时候,只需要将有数据的位设置为1,表示该位存在数据,将其他位置设置为0
位图的实现
使用byte[]字节来存储bit 1byte = 8bit 对于bit中的第i位,该bit为1则表示true数据存在;为0则表示false数据不存在。
数据结构
class Bitmap{ private byte[] bytes; private int length; public Bitmap(int length){ this.length = length; bytes = new byte[length % 8 == 0 ? length/8 : length/8+1]; } }
查询方法的实现
位图的查询操作在为拿到目标bit所在的Byte后,将其向右位移(并将高位置0),使目标bit在第一位,这样结果值就是目标bit值
通过byte[index>>3](等价与byte【index/8】)取到目标bit所在的Byte
令i = index&7(等价于index%8),得到目标bit在该Byte中的位置
为了将目标bit前面的最高位置0(这样位移后的值才等于目标bit本身)
JAVA基础
集合
List
ArrayList
概述
ArrayList是可以动态增长和缩减的索引序列,它是基于数组实现的List类。
该类封装了一个动态再分配的Object[]数组,每一个类对象都有一个capacity属性,表示它们所封装的Object[]数组的长度,当向ArrayList中添加元素时,该属性值会自动增加。
ArrayList的用法和Vector向类似,但是Vector是一个较老的集合,具有很多缺点,不建议使用。另外,ArrayList和Vector的区别是:ArrayList是线程不安全的,当多条线程访问同一个ArrayList集合时,程序需要手动保证该集合的同步性,而Vector则是线程安全的。
arrayList和Collection的关系
arrayList的数据结构
底层的数据结构就是数组,数组元素类型为Object类型,即可以存放所有类型数据
arrayList源码分析
继承结构和层次关系
ArrayList extends AbstractList AbstractList extends AbstractCollection
为什么要先继承AbstractList,而让AbstractList先实现List<E>?而不是让ArrayList直接实现List<E>?
这里是有一个思想,接口中全都是抽象的方法,而抽象类中可以有抽象方法,还可以有具体的实现方法,正是利用了这一点,让AbstractList是实现接口中一些通用的方法,而具体的类, 如ArrayList就继承这个AbstractList类,拿到一些通用的方法,然后自己在实现一些自己特有的方法,这样一来,让代码更简洁,就继承结构最底层的类中通用的方法都抽取出来, 先一起实现了,减少重复代码。所以一般看到一个类上面还有一个抽象类,应该就是这个作用
ArrayList实现了哪些接口
RandomAccess接口
这个是一个标记性接口,通过查看api文档,它的作用就是用来快速随机存取,有关效率的问题,在实现了该接口的话,那么使用普通的for循环来遍历,性能更高,例如arrayList。 而没有实现该接口的话,使用Iterator来迭代,这样性能更高,例如linkedList。所以这个标记性只是为了让我们知道我们用什么样的方式去获取数据性能更好。
List<E>接口
Serializable接口
实现该序列化接口,表明该类可以被序列化,什么是序列化?简单的说,就是能够从类变成字节流传输,然后还能从字节流变成原来的类。
Cloneable接口
实现了该接口,就可以使用Object.Clone()方法了。
类中的属性
版本号
private static final long serialVersionUID = 8683452581122892189L;
缺省容量
private static final int DEFAULT_CAPACITY = 10;
空数组对象
private static final Object[] EMPTY_ELEMENTDATA = {};
缺省空数组对象
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
元数组
transient Object[] elementData;
实际元素大小,默认为0
private int size;
最大容量数组
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
构建方法
ArrayList()
/** * Constructs an empty list with an initial capacity of ten. 这里就说明了默认会给10的大小,所以说一开始arrayList的容量是10. */ //ArrayList中储存数据的其实就是一个数组,这个数组就是elementData,在123行定义的 private transient Object[] elementData; public ArrayList() { super(); //调用父类中的无参构造方法,父类中的是个空的构造方法 this.elementData = EMPTY_ELEMENTDATA;//EMPTY_ELEMENTDATA:是个空的Object[], 将elementData初始化,elementData也是个Object[]类型。空的Object[]会给默认大小10,等会会解释什么时候赋值的。 }
ArrayList(int initialCapacity)
/** * Constructs an empty list with the specified initial capacity. * * @param initialCapacity the initial capacity of the list * @throws IllegalArgumentException if the specified initial capacity * is negative */ public ArrayList(int initialCapacity) { super(); //父类中空的构造方法 if (initialCapacity throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; //将自定义的容量大小当成初始化elementData的大小 }
ArrayList(Collection<? extends E> c)
//这个构造方法不常用,举个例子就能明白什么意思 /* Strudent exends Person ArrayList、 Person这里就是泛型 我还有一个Collection、由于这个Student继承了Person,那么根据这个构造方法,我就可以把这个Collection转换为ArrayList这就是这个构造方法的作用 */ public ArrayList(Collection c) { elementData = c.toArray(); //转换为数组 size = elementData.length; //数组中的数据个数 // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) //每个集合的toarray()的实现方法不一样,所以需要判断一下,如果不是Object[].class类型,那么久需要使用ArrayList中的方法去改造一下。 elementData = Arrays.copyOf(elementData, size, Object[].class); }
核心方法
add(E)
默认直接在末尾添加元素
ensureCapacityInternal(xxx);
确定内部容量的方法
ensureExplicitCapacity(xxx)
grow(xxx);
arrayList核心的方法,能扩展数组大小的真正秘密
hugeCapacity();
void add(int,E);
在特定位置添加元素,也就是插入元素
rangeCheckForAdd(index)
System.arraycopy(...)
就是将elementData在插入位置后的所有元素往后面移一位。查看api文档
remove(int)
通过删除指定位置上的元素
remove(Object)
这个方法可以看出来,arrayList是可以存放null值得。
clear()
将elementData中每个元素都赋值为null,等待垃圾回收将这个给回收掉,所以叫clear
removeAll(collection c)
batchRemove(xx,xx)
用于两个方法,一个removeAll():它只清楚指定集合中的元素,retainAll()用来测试两个集合是否有交集。
set()方法
LinkedLIst
概念
基本存储单元
// 一个私有的内部类 private static class Node { E item; // 真正存储的数据 Node next; // 前一个节点引用地址 Node prev; // 后一个节点引用地址 Node(Node prev, E element, Node next) { this.item = element; this.next = next; this.prev = prev; } }
LinkedList是一个双向链表
在存储自身值之外,还 额外存储了其前一个和后一个元素的地址
链表的尾部元素的后一个节点是链表的头节点;而链表的头结点前一个节点则是则是链表的尾节点
特点
可以为空
有序
允许重复
线程 不安全
类结构集成图
属性
// LinkedList节点个数 transient int size = 0;
** * Pointer to first node. 指向头结点 * Invariant: (first == null && last == null) || * (first.prev == null && first.item != null) */ transient Node<E> first
/** * Pointer to last node. 指向尾节点 * Invariant: (first == null && last == null) || * (last.next == null && last.item != null) */ transient Node<E> last;
构造函数
空参构造函数LinkedList()
collection参数构造函数
addAll(Collection<? extends E> c)
addAll(int index, Collection<? extends E> c)
Vector
属性
capacityIncrement
自动扩容的大小,即当数组满了之后,就添加capacityIncrement个空间装载元素,如果capacityIncrement
elementCount
记录数组中数据的个数。
elementData
构造方法
.Vector()
public Vector() { this(10);//这里的this调用的是Vector(int initialCapacity) 方法 }
Vector(Collection <\?extends E> c)
public Vector(Collection c) { elementData = c.toArray(); elementCount = elementData.length; // c.toArray might (incorrectly) not return Object[] (see 6260652) //c.toArray可能(不正确)不返回Object [](参见6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, elementCount, Object[].class); //用Arrays.copyOf()方法转换类型。 }
Vector(int initialCapacity)
public Vector(int initialCapacity) { this(initialCapacity, 0);//这里的this其实调用的是Vector(int initialCapacity, int capacityIncrement)方法 }
Vector(int initialCapacity, int capacityIncrement)
//initialCapacity指的是初始容量。 //capacityIncrement指的是扩容容量。 public Vector(int initialCapacity, int capacityIncrement) { super(); if (initialCapacity throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; //从这里可以看出Vector底层也是数组实现的。 this.capacityIncrement = capacityIncrement; }
常用方法
.addElement(E obj)
public synchronized void addElement(E obj) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = obj; }
ensureCapacityHelper(int minCapacity)
private void ensureCapacityHelper(int minCapacity) { // overflow-conscious code检测是否大于数组长度 if (minCapacity - elementData.length > 0) grow(minCapacity); }
grow(int minCapacity)
private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity); //注意:这是和ArrayList不同的地方,这个的自动扩容是直接增加一个oldCapacity,也就是扩大了一倍。 /*第一个判断是怕扩容的数组长度还是太小,就用minCapacity 来进行对数组的扩张。 第二个判断是如果扩张1倍太大或者是我们所需的空间大小minCapacity太大,则进行Integer.MAX_VALUE来进行扩张。 */ if (newCapacity - minCapacity newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); }
elementAt(int index)
public synchronized E elementAt(int index) { if (index >= elementCount) { throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount); } return elementData(index); } //--------------------------------------------- E elementData(int index) { return (E) elementData[index]; }
elements()
hasMoreElements()
nextElement()
add
get
set
remove
Queue
ArrayBlockingQueue
基于数组实现的有界阻塞队列
属性
//取出元素的位置 int takeIndex;
//添加元素的位置 int putIndex;
//队列中元素的数量 int count;
//锁对象 final ReentrantLock lock;
//不空的信号量 private final Condition notEmpty;
//不满的信号量 private final Condition notFull;
transient Itrs itrs = null;
构造方法
ArrayBlockingQueue(int)
public ArrayBlockingQueue(int capacity) { this(capacity, false); }
ArrayBlockingQueue(int,boolean)
public ArrayBlockingQueue(int capacity, boolean fair) { if (capacity throw new IllegalArgumentException(); this.items = new Object[capacity]; lock = new ReentrantLock(fair); notEmpty = lock.newCondition(); notFull = lock.newCondition(); }
ArrayBlockingQueue(int capacity, boolean fair,Collection<? extends E> c)
public ArrayBlockingQueue(int capacity, boolean fair, Collection c) { this(capacity, fair); final ReentrantLock lock = this.lock; lock.lock(); // Lock only for visibility, not mutual exclusion try { int i = 0; try { for (E e : c) { checkNotNull(e); items[i++] = e; } } catch (ArrayIndexOutOfBoundsException ex) { throw new IllegalArgumentException(); } count = i; putIndex = (i == capacity) ? 0 : i; } finally { lock.unlock(); } }
队列操作
add
public boolean add(E e) { return super.add(e); }
add(E)
public boolean add(E e) { if (offer(e)) return true; else throw new IllegalStateException("Queue full"); }
peek()
public E peek() { final ReentrantLock lock = this.lock; lock.lock(); try { return itemAt(takeIndex); // null when queue is empty } finally { lock.unlock(); } }
itemA()
@SuppressWarnings("unchecked") final E itemAt(int i) { return (E) items[i]; }
put()
public void put(E e) throws InterruptedException { checkNotNull(e); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == items.length) notFull.await(); enqueue(e); } finally { lock.unlock(); } }
take
public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == 0) notEmpty.await(); return dequeue(); } finally { lock.unlock(); } }
dequeue()
private E dequeue() { // assert lock.getHoldCount() == 1; // assert items[takeIndex] != null; final Object[] items = this.items; @SuppressWarnings("unchecked") E x = (E) items[takeIndex]; items[takeIndex] = null; if (++takeIndex == items.length) takeIndex = 0; count--; if (itrs != null) itrs.elementDequeued(); notFull.signal(); return x; }
offer
public boolean offer(E e) { checkNotNull(e); final ReentrantLock lock = this.lock; lock.lock(); try { if (count == items.length) return false; else { enqueue(e); return true; } } finally { lock.unlock(); } }
poll
public E poll() { final ReentrantLock lock = this.lock; lock.lock(); try { return (count == 0) ? null : dequeue(); } finally { lock.unlock(); } }
LinkedBlockingQueue
继承关系
源码
Node
/** * 节点类,用于存储数据 */ static class NodeE> { E item; NodeE> next; Node(E x) { item = x; } }
/** 阻塞队列的大小,默认为Integer.MAX_VALUE */ private final int capacity;
/** 当前阻塞队列中的元素个数 */ private final AtomicInteger count = new AtomicInteger();
/** * 阻塞队列的头结点 */ transient Node<E> head;
/** * 阻塞队列的尾节点 */ private transient Node<E> last;
/** 获取并移除元素时使用的锁,如take, poll, etc */ private final ReentrantLock takeLock = new ReentrantLock();
/** notEmpty条件对象,当队列没有数据时用于挂起执行删除的线程 */ private final Condition notEmpty = takeLock.newCondition();
/** notFull条件对象,当队列数据已满时用于挂起执行添加的线程 */ private final Condition notFull = putLock.newCondition();
构造函数
LinkedBlockingQueue()
public LinkedBlockingQueue(Collection c) { this(Integer.MAX_VALUE); final ReentrantLock putLock = this.putLock; putLock.lock(); try { int n = 0; for (E e : c) { if (e == null) throw new NullPointerException(); if (n == capacity) throw new IllegalStateException("Queue full"); enqueue(new Node(e)); ++n; } count.set(n); } finally { putLock.unlock(); } }
LinkedBlockingQueue
publicLinkedBlockingQueue(int capacity){ if(capacity this.capacity = capacity; last = head =newNode(null); }
LinkedBlockingQueue(Collection<? extends E> c)
public LinkedBlockingQueue(Collection c) { this(Integer.MAX_VALUE); final ReentrantLock putLock = this.putLock; putLock.lock(); try { int n = 0; for (E e : c) { if (e == null) throw new NullPointerException(); if (n == capacity) throw new IllegalStateException("Queue full"); enqueue(new Node(e)); ++n; } count.set(n); } finally { putLock.unlock(); } }
方法
入队方法
void put(E e);
public void put(E e) throws InterruptedException { if (e == null) throw new NullPointerException(); int c = -1; Node node = new Node(e); final ReentrantLock putLock = this.putLock; final AtomicInteger count = this.count; // 获取锁中断 putLock.lockInterruptibly(); try { //判断队列是否已满,如果已满阻塞等待 while (count.get() == capacity) { notFull.await(); } // 把node放入队列中 enqueue(node); c = count.getAndIncrement(); // 再次判断队列是否有可用空间,如果有唤醒下一个线程进行添加操作 if (c + 1 notFull.signal(); } finally { putLock.unlock(); } // 如果队列中有一条数据,唤醒消费线程进行消费 if (c == 0) signalNotEmpty(); }
enqueue
private void enqueue(Node node) { last = last.next = node; }
signalNotEmpty
private void signalNotEmpty() { final ReentrantLock takeLock = this.takeLock; takeLock.lock(); try { notEmpty.signal(); } finally { takeLock.unlock(); } }
signalNotFull
private void signalNotFull() { final ReentrantLock putLock = this.putLock; putLock.lock(); try { notFull.signal(); } finally { putLock.unlock(); } }
boolean offer(E e);
public boolean offer(E e) { if (e == null) throw new NullPointerException(); final AtomicInteger count = this.count; if (count.get() == capacity) return false; int c = -1; Node node = new Node(e); final ReentrantLock putLock = this.putLock; putLock.lock(); try { // 队列有可用空间,放入node节点,判断放入元素后是否还有可用空间, // 如果有,唤醒下一个添加线程进行添加操作。 if (count.get() enqueue(node); c = count.getAndIncrement(); if (c + 1 notFull.signal(); } } finally { putLock.unlock(); } if (c == 0) signalNotEmpty(); return c >= 0; }
boolean offer(E e, long timeout, TimeUnit unit)。
public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException { if (e == null) throw new NullPointerException(); long nanos = unit.toNanos(timeout); int c = -1; final ReentrantLock putLock = this.putLock; final AtomicInteger count = this.count; putLock.lockInterruptibly(); try { // 等待超时时间nanos,超时时间到了返回false while (count.get() == capacity) { if (nanos return false; nanos = notFull.awaitNanos(nanos); } enqueue(new Node(e)); c = count.getAndIncrement(); if (c + 1 notFull.signal(); } finally { putLock.unlock(); } if (c == 0) signalNotEmpty(); return true; }
出对方法
E take();
public E take() throws InterruptedException { E x; int c = -1; final AtomicInteger count = this.count; final ReentrantLock takeLock = this.takeLock; takeLock.lockInterruptibly(); try { while (count.get() == 0) { notEmpty.await(); } x = dequeue(); c = count.getAndDecrement(); if (c > 1) notEmpty.signal(); } finally { takeLock.unlock(); } if (c == capacity) signalNotFull(); return x; }
E poll();
public E poll() { final AtomicInteger count = this.count; if (count.get() == 0) return null; E x = null; int c = -1; final ReentrantLock takeLock = this.takeLock; takeLock.lock(); try { if (count.get() > 0) { x = dequeue(); c = count.getAndDecrement(); if (c > 1) notEmpty.signal(); } } finally { takeLock.unlock(); } if (c == capacity) signalNotFull(); return x; }
E poll(long timeout, TimeUnit unit);
public E poll(long timeout, TimeUnit unit) throws InterruptedException { E x = null; int c = -1; long nanos = unit.toNanos(timeout); final AtomicInteger count = this.count; final ReentrantLock takeLock = this.takeLock; takeLock.lockInterruptibly(); try { while (count.get() == 0) { if (nanos return null; nanos = notEmpty.awaitNanos(nanos); } x = dequeue(); c = count.getAndDecrement(); if (c > 1) notEmpty.signal(); } finally { takeLock.unlock(); } if (c == capacity) signalNotFull(); return x; }
E dequeue()
private E dequeue() { // 获取到head节点 Node h = head; // 获取到head节点指向的下一个节点 Node first = h.next; // head节点原来指向的节点的next指向自己,等待下次gc回收 h.next = h; // help GC // head节点指向新的节点 head = first; // 获取到新的head节点的item值 E x = first.item; // 新head节点的item值设置为null first.item = null; return x; }
获取元素的方法
peek()
public boolean remove(Object o) { if (o == null) return false; // 两个lock全部上锁 fullyLock(); try { // 从head开始遍历元素,直到最后一个元素 for (Node trail = head, p = trail.next; p != null; trail = p, p = p.next) { // 如果找到相等的元素,调用unlink方法删除元素 if (o.equals(p.item)) { unlink(p, trail); return true; } } return false; } finally { // 两个lock全部解锁 fullyUnlock(); } } void fullyLock() { putLock.lock(); takeLock.lock(); } void fullyUnlock() { takeLock.unlock(); putLock.unlock(); }
删除元素的方法
remove()
public boolean remove(Object o) { if (o == null) return false; // 两个lock全部上锁 fullyLock(); try { // 从head开始遍历元素,直到最后一个元素 for (Node trail = head, p = trail.next; p != null; trail = p, p = p.next) { // 如果找到相等的元素,调用unlink方法删除元素 if (o.equals(p.item)) { unlink(p, trail); return true; } } return false; } finally { // 两个lock全部解锁 fullyUnlock(); } } void fullyLock() { putLock.lock(); takeLock.lock(); } void fullyUnlock() { takeLock.unlock(); putLock.unlock(); }
PriorityBlockingQueue
无界队列,它没有限制,在内存允许的情况下可以无限添加元素;它又是具有优先级的队列,是通过构造函数传入的对象来判断,传入的对象必须实现comparable接口。
构造函数
PriorityBlockingQueue()
public PriorityBlockingQueue() { this(DEFAULT_INITIAL_CAPACITY, null); }
PriorityBlockingQueue(int initialCapacity)
public PriorityBlockingQueue(int initialCapacity) { this(initialCapacity, null); }
PriorityBlockingQueue(int initialCapacity, Comparator<? super E> comparator)
public PriorityBlockingQueue(int initialCapacity, Comparator comparator) { if (initialCapacity throw new IllegalArgumentException(); this.lock = new ReentrantLock(); this.notEmpty = lock.newCondition(); this.comparator = comparator; this.queue = new Object[initialCapacity]; }
属性
DEFAULT_INITIAL_CAPACITY
默认容量11
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
// 二叉堆数组 private transient Object[] queue;
// 队列元素的个数 private transient int size;
// 比较器,如果为空,则为自然顺序 private transient Comparator<? super E> comparator;
// 内部锁 private final ReentrantLock lock;
// 优先队列:主要用于序列化,这是为了兼容之前的版本。只有在序列化和反序列化才非空 private PriorityQueue<E> q;
入队
put()
public void put(E e) { offer(e); // never need to block }
add()
offer()
private static void siftUpUsingComparator(int k, T x, Object[] array, Comparator cmp) { while (k > 0) { int parent = (k - 1) >>> 1; Object e = array[parent]; if (cmp.compare(x, (T) e) >= 0) break; array[k] = e; k = parent; } array[k] = x; }
tryGrow
private void tryGrow(Object[] array, int oldCap) { lock.unlock(); // 扩容操作使用自旋,不需要锁主锁,释放 Object[] newArray = null; // CAS 占用 if (allocationSpinLock == 0 && UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset, 0, 1)) { try { // 新容量 最小翻倍 int newCap = oldCap + ((oldCap > 1)); // 超过 if (newCap - MAX_ARRAY_SIZE > 0) { // possible overflow int minCap = oldCap + 1; if (minCap MAX_ARRAY_SIZE) throw new OutOfMemoryError(); newCap = MAX_ARRAY_SIZE; // 最大容量 } if (newCap > oldCap && queue == array) newArray = new Object[newCap]; } finally { allocationSpinLock = 0; // 扩容后allocationSpinLock = 0 代表释放了自旋锁 } } // 到这里如果是本线程扩容newArray肯定是不为null,为null就是其他线程在处理扩容,那就让给别的线程处理 if (newArray == null) Thread.yield(); // 主锁获取锁 lock.lock(); // 数组复制 if (newArray != null && queue == array) { queue = newArray; System.arraycopy(array, 0, newArray, 0, oldCap); } }
siftUpUsingComparator
private static void siftUpUsingComparator(int k, T x, Object[] array, Comparator cmp) { while (k > 0) { int parent = (k - 1) >>> 1; Object e = array[parent]; if (cmp.compare(x, (T) e) >= 0) break; array[k] = e; k = parent; } array[k] = x; }
siftUpUsingComparator
private static void siftUpUsingComparator(int k, T x, Object[] array, Comparator cmp) { while (k > 0) { int parent = (k - 1) >>> 1; Object e = array[parent]; if (cmp.compare(x, (T) e) >= 0) break; array[k] = e; k = parent; } array[k] = x; }
出队
poll
public E poll() { final ReentrantLock lock = this.lock; lock.lock(); try { return dequeue(); } finally { lock.unlock(); } }
dequeue()
private E dequeue() { // 没有元素 返回null int n = size - 1; if (n return null; else { Object[] array = queue; // 出对元素 E result = (E) array[0]; // 最后一个元素(也就是插入到空穴中的元素) E x = (E) array[n]; array[n] = null; // 根据比较器释放为null,来执行不同的处理 Comparator cmp = comparator; if (cmp == null) siftDownComparable(0, x, array, n); else siftDownUsingComparator(0, x, array, n, cmp); size = n; return result; } }
siftDownComparable
private static void siftDownComparable(int k, T x, Object[] array, int n) { if (n > 0) { Comparable key = (Comparable)x; // 最后一个叶子节点的父节点位置 int half = n >>> 1; while (k int child = (k Object c = array[child]; //左节点 int right = child + 1; //右节点 //左右节点比较,取较小的 if (right ((Comparable) c).compareTo((T) array[right]) > 0) c = array[child = right]; //如果待调整key最小,那就退出,直接赋值 if (key.compareTo((T) c) break; //如果key不是最小,那就取左右节点小的那个放到调整位置,然后小的那个节点位置开始再继续调整 array[k] = c; k = child; } array[k] = key; } }
siftDownUsingComparator
private static void siftDownUsingComparator(int k, T x, Object[] array, int n, Comparator cmp) { if (n > 0) { int half = n >>> 1; while (k int child = (k Object c = array[child]; int right = child + 1; if (right 0) c = array[child = right]; if (cmp.compare(x, (T) c) break; array[k] = c; k = child; } array[k] = x; } }
二叉堆
二叉堆是一种特殊的堆,就结构性而言就是完全二叉树或者是近似完全二叉树,满足树结构性和堆序性。
最大堆
父节点的键值总是大于或等于任何一个子节点的键值(下右图)
最小堆
父节点的键值总是小于或等于任何一个子节点的键值(下走图)
DelayQueue
DelayQueue是一个无界的BlockingQueue,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即队头对象的延迟到期时间最长。注意:不能将null元素放置到这种队列中。
成员变量
//可重入锁 private final transient ReentrantLock lock = new ReentrantLock();
//存储元素的优先级队列 private final PriorityQueue<E> q = new PriorityQueue<E>();
//获取数据 等待线程标识 private Thread leader = null;
//条件控制,表示是否可以从队列中取数据 private final Condition available = lock.newCondition();
构造方法
public DelayQueue() {}
public DelayQueue(Collection<? extends E> c) { this.addAll(c); }
入队
add(E)
public boolean add(E e) { return offer(e); }
offer(E e)
public boolean offer(E e) { final ReentrantLock lock = this.lock; //获取锁 lock.lock(); try { //通过PriorityQueue 来将元素入队 q.offer(e); //peek 是获取的队头元素,唤醒阻塞在available 条件上的一个线程,表示可以从队列中取数据了 if (q.peek() == e) { leader = null; available.signal(); } return true; } finally { lock.unlock(); } }
offer(E e, long timeout, TimeUnit unit)
/** * Inserts the specified element into this delay queue. As the queue is * unbounded this method will never block. * * @param e the element to add * @param timeout This parameter is ignored as the method never blocks * @param unit This parameter is ignored as the method never blocks * @return {@code true} * @throws NullPointerException {@inheritDoc} */ public boolean offer(E e, long timeout, TimeUnit unit) { //调用offer 方法 return offer(e); }
put(E e)
public void put(E e) { offer(e); }
出队
poll
/** * Retrieves and removes the head of this queue, or returns {@code null} * if this queue has no elements with an expired delay. * * @return the head of this queue, or {@code null} if this * queue has no elements with an expired delay */ public E poll() { final ReentrantLock lock = this.lock; //获取同步锁 lock.lock(); try { //获取队头 E first = q.peek(); //如果队头为null 或者 延时还没有到,则返回null if (first == null || first.getDelay(NANOSECONDS) > 0) return null; else return q.poll(); //元素出队 } finally { lock.unlock(); } }
SysnchronousQueue
SynchronousQueue作为阻塞队列的时候,对于每一个take的线程会阻塞直到有一个put的线程放入元素为止,反之亦然。在SynchronousQueue内部没有任何存放元素的能力。所以类似peek操作或者迭代器操作也是无效的,元素只能通过put类操作或者take类操作才有效。通常队列的第一个元素是当前第一个等待的线程。如果没有线程阻塞在该队列则poll会返回null。从Collection的视角来看SynchronousQueue表现为一个空的集合。 SynchronousQueue相似于使用CSP和Ada算法(不知道怎么具体指什么算法),他非常适合做交换的工作,生产者的线程必须与消费者的线程同步以传递某些信息、事件或任务 SynchronousQueue支持支持生产者和消费者等待的公平性策略。默认情况下,不能保证生产消费的顺序。如果是公平锁的话可以保证当前第一个队首的线程是等待时间最长的线程,这时可以视SynchronousQueue为一个FIFO队列。
数据结构
队列(实现公平策略)
公平模式下,底层实现使用的是TransferQueue这个内部队列,它有一个head和tail指针,用于指向当前正在等待匹配的线程节点。
栈(实现非公平策略)
属性
NCPUS
在阻塞等待时间之前旋转的次数。 *该值是根据经验得出的-在各种处理器和OS上都可以很好地工作。根据经验,最佳值*不会随CPU数量的变化而变化(超过2),因此*只是一个常数。
static final int maxTimedSpins = (NCPUS < 2) ? 0 : 32;
在阻塞等待时间之前旋转的次数。 *该值是根据经验得出的-在各种处理器和OS上都可以很好地工作。根据经验,最佳值*不会随CPU数量的变化而变化(超过2),因此*只是一个常数。
static final int maxUntimedSpins = maxTimedSpins * 16;
在阻塞未定时的等待之前旋转的次数。 这比定时值大,因为未定时等待旋转更快,因为他们不需要每次旋转都检查时间。
static final long spinForTimeoutThreshold = 1000L;
旋转比使用定时停驻更快的纳秒数。粗略的估计就足够了。
abstract static class Transferer<E>
双重堆栈和队列的共享内部API。
abstract E transfer(E e, boolean timed, long nanos);
执行一个put或take操作
static final class TransferStack<E> extends Transferer<E>
双栈
// 下一个结点 volatile SNode next;
// 相匹配的结点 volatile SNode match;
// 等待的线程 volatile Thread waiter;
// 元素项 Object item;
// 模式 int mode;
// item域和mode域不需要使用volatile修饰,因为它们在volatile/atomic操作之前写,之后读 SNode(Object item) { this.item = item; }
boolean casNext(SNode cmp, SNode val) { return cmp == next && UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val); }
LinkedTransferQueue
采用一种预占模式。意思就是消费者线程取元素时,如果队列不为空,则直接取走数据,若队列为空,那就生成一个节点(节点元素为null)入队,然后消费者线程被等待在这个节点上,后面生产者线程入队时发现有一个元素为null的节点,生产者线程就不入队了,直接就将元素填充到该节点,并唤醒该节点等待的线程,被唤醒的消费者线程取走元素,从调用的方法返回。我们称这种节点操作为“匹配”方式。
构造函数
public LinkedTransferQueue() { }
public LinkedTransferQueue(Collection<? extends E> c) { this(); addAll(c); }
继承类
TransferQueue
// 如果存在一个消费者已经等待接收它,则立即传送指定的元素,否则返回false,并且不进入队列。 boolean tryTransfer(E e);
// 如果存在一个消费者已经等待接收它,则立即传送指定的元素,否则等待直到元素被消费者接收。 void transfer(E e) throws InterruptedException;
// 在上述方法的基础上设置超时时间 boolean tryTransfer(E e, long timeout, TimeUnit unit) throws InterruptedException;
// 如果至少有一位消费者在等待,则返回true boolean hasWaitingConsumer(); 4
// 获取所有等待获取元素的消费线程数量 int getWaitingConsumerCount();
内部类
Node
// 如果是消费者请求的节点,则isData为false,否则该节点为生产(数据)节点为true final boolean isData; // false if this is a request node
// 数据节点的值,若是消费者节点,则item为null volatile Object item;
// 指向下一个节点 volatile Node next;
// 等待线程 volatile Thread waiter; // null until waiting
// CAS设置next final boolean casNext(Node cmp, Node val) { return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val); }
// CAS设置item final boolean casItem(Object cmp, Object val) { // assert cmp == null || cmp.getClass() != Node.class; return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val); }
// 构造方法 Node(Object item, boolean isData) { UNSAFE.putObject(this, itemOffset, item); // relaxed write this.isData = isData; }
// 将next指向自己 final void forgetNext() { UNSAFE.putObject(this, nextOffset, this); }
// 匹配或者节点被取消的时候会调用,设置item自连接,waiter为null final void forgetContents() { UNSAFE.putObject(this, itemOffset, this); UNSAFE.putObject(this, waiterOffset, null); }
// 节点是否被匹配过了 final boolean isMatched() { Object x = item; return (x == this) || ((x == null) == isData); }
// 是否是一个未匹配的请求节点 // 如果是的话,则isData为false,且item为null,因为如果被匹配过了,item就不再为null,而是指向自己 final boolean isUnmatchedRequest() { return !isData && item == null; }
// 如果给定节点不能连接在当前节点后则返回true final boolean cannotPrecede(boolean haveData) { boolean d = isData; Object x; return d != haveData && (x = item) != this && (x != null) == d; }
成员变量
// 是否为多核 private static final boolean MP = Runtime.getRuntime().availableProcessors() > 1;
// 作为第一个等待节点在阻塞之前的自旋次数 private static final int FRONT_SPINS = 1 << 7;
// 前驱节点正在处理,当前节点在阻塞之前的自旋次数 private static final int CHAINED_SPINS = FRONT_SPINS >>> 1;
// sweepVotes的阈值 static final int SWEEP_THRESHOLD = 32;
// 队列首节点 transient volatile Node head;
// 队列尾节点 private transient volatile Node tail;
// 断开被删除节点失败的次数 private transient volatile int sweepVotes;
/ xfer方法的how参数的可能取值 // 用于无等待的poll、tryTransfer private static final int NOW = 0; // for untimed poll, tryTransfer
// 用于offer、put、add private static final int ASYNC = 1; // for offer, put, add
// 用于无超时的阻塞transfer、take private static final int SYNC = 2; // for transfer, take
// 用于超时等待的poll、tryTransfer private static final int TIMED = 3; // for timed poll, tryTransfer
方法
put
// 入队方法 public void put(E e) { xfer(e, true, ASYNC, 0); }
offer
public boolean offer(E e, long timeout, TimeUnit unit) { xfer(e, true, ASYNC, 0); return true; }
public boolean offer(E e) { xfer(e, true, ASYNC, 0); return true; }
add
public boolean add(E e) { xfer(e, true, ASYNC, 0); return true; }
take
// 出队方法 public E take() throws InterruptedException { E e = xfer(null, false, SYNC, 0); if (e != null) return e; Thread.interrupted(); throw new InterruptedException(); }
poll
public E poll() { return xfer(null, false, NOW, 0); }
public E poll(long timeout, TimeUnit unit) throws InterruptedException { E e = xfer(null, false, TIMED, unit.toNanos(timeout)); if (e != null || !Thread.interrupted()) return e; throw new InterruptedException(); }
tryTransfer
public boolean tryTransfer(E e) { return xfer(e, true, NOW, 0) == null; }
public boolean tryTransfer(E e, long timeout, TimeUnit unit) throws InterruptedException { if (xfer(e, true, TIMED, unit.toNanos(timeout)) == null) return true; if (!Thread.interrupted()) return false; throw new InterruptedException(); }
transfer
public void transfer(E e) throws InterruptedException { if (xfer(e, true, SYNC, 0) != null) { Thread.interrupted(); // failure possible only due to interrupt throw new InterruptedException(); } }
xfer
LinkedBlockingDeque
构造方法
LinkedBlocLinkedBlockingDeque()
public LinkedBlockingDeque(int capacity)
public LinkedBlockingDeque(Collection<? extends E> c)
成员变量
// 队列双向链表首节点 transient Node<E> first;
// 队列双向链表尾节点 transient Node<E> last
// 双向链表元素个数 private transient int count;
// 双向链表最大容量 private final int capacity;
// 全局独占锁 final ReentrantLock lock = new ReentrantLock();
// 非空Condition对象 private final Condition notEmpty = lock.newCondition();
// 非满Condition对象 private final Condition notFull = lock.newCondition();
成员方法
入队
putFirst(E e)
public void putFirst(E e) throws InterruptedException { // 若插入元素为null,则直接抛出NullPointerException异常 if (e == null) throw new NullPointerException(); // 将插入节点包装为Node节点 Node node = new Node(e); // 获取全局独占锁 final ReentrantLock lock = this.lock; lock.lock(); try { while (!linkFirst(node)) notFull.await(); } finally { // 释放全局独占锁 lock.unlock(); } }
linkFirst(E e)
private boolean linkFirst(Node node) { // assert lock.isHeldByCurrentThread(); // 元素个数超出容量。直接返回false if (count >= capacity) return false; // 获取双向链表的首节点 Node f = first; // 将node设置为首节点 node.next = f; first = node; // 若last为null,设置尾节点为node节点 if (last == null) last = node; else // 更新原首节点的前驱节点 f.prev = node; ++count; // 唤醒阻塞在notEmpty上的线程 notEmpty.signal(); return true; }
putLast(E e)
public void putLast(E e) throws InterruptedException { // 若插入元素为null,则直接抛出NullPointerException异常 if (e == null) throw new NullPointerException(); // 将插入节点包装为Node节点 Node node = new Node(e); // 获取全局独占锁 final ReentrantLock lock = this.lock; lock.lock(); try { while (!linkLast(node)) notFull.await(); } finally { // 释放全局独占锁 lock.unlock(); } }
出队
pollFirst()
public E pollFirst() { // 获取全局独占锁 final ReentrantLock lock = this.lock; lock.lock(); try { return unlinkFirst(); } finally { // 释放全局独占锁 lock.unlock(); } }
unlinkFirst()
private E unlinkLast() { // assert lock.isHeldByCurrentThread(); // 获取尾节点 Node l = last; // 尾节点为null,则返回null if (l == null) return null; // 获取尾节点的前驱节点 Node p = l.prev; // 移除尾节点,将尾节点更新为p E item = l.item; l.item = null; l.prev = l; // help GC last = p; // 移除尾节点后,为空队列 if (p == null) first = null; else // 将新的尾节点的后继节点设置为null p.next = null; --count; // 唤醒阻塞在notFull上的线程 notFull.signal(); return item; }
Map
HashTable
原理
哈希表
哈希函数
主要应用在以下这几个方面:文件校验、数字签名、鉴权协议
MD5
MD5即Message-Digest Algorithm 5(信息-摘要算法5),用于确保信息传输完整一致。MD5是输入不定长度信息,输出固定长度128bits的算法。
SHA-1
常用于HTTPS传输和软件签名
SHA-3
之前名为Keccak算法,是一个加密杂凑算法
如何避免哈希冲突
拉链法
讲一个数除以三列列表的总数,添加到后面
线性探索法
哈希碰撞攻击
源码
成员变量
private transient Entry<?,?>[] table;
hash数据表
private transient int count;
hash表中条目的总数
private int threshold;
当表的大小超过此阈值时将对表进行重新hash处理
private transient int modCount = 0;
已对该哈希表进行结构修改的次数结构修改是更改哈希表中条目数或以其他方式修改其内部结构(例如,重新哈希)的修改
private float loadFactor;
hash表加载因子
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
要分配的最大数组大小。 一些虚拟机在数组中保留一些头字。 尝试分配更大的阵列可能会导致OutOfMemoryError:请求的阵列大小超出VM限制
成员函数
public Hashtable(int initialCapacity, float loadFactor)
/** * Constructs a new, empty hashtable with the specified initial * capacity and the specified load factor. * * @param initialCapacity the initial capacity of the hashtable. * @param loadFactor the load factor of the hashtable. * @exception IllegalArgumentException if the initial capacity is less * than zero, or if the load factor is nonpositive. */ public Hashtable(int initialCapacity, float loadFactor) { if (initialCapacity throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); if (loadFactor throw new IllegalArgumentException("Illegal Load: "+loadFactor); if (initialCapacity==0) initialCapacity = 1; this.loadFactor = loadFactor; table = new Entry[initialCapacity]; threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1); }
public Hashtable(int initialCapacity)
** * Constructs a new, empty hashtable with the specified initial capacity * and default load factor (0.75). * * @param initialCapacity the initial capacity of the hashtable. * @exception IllegalArgumentException if the initial capacity is less * than zero. */ public Hashtable(int initialCapacity) { this(initialCapacity, 0.75f); }
public Hashtable()
this(11, 0.75f);
public Hashtable(Map<? extends K, ? extends V> t)
/** * Constructs a new hashtable with the same mappings as the given * Map. The hashtable is created with an initial capacity sufficient to * hold the mappings in the given Map and a default load factor (0.75). * * @param t the map whose mappings are to be placed in this map. * @throws NullPointerException if the specified map is null. * @since 1.2 */ public Hashtable(Map t) { this(Math.max(2*t.size(), 11), 0.75f); putAll(t); }
public synchronized int size()
/** * Returns the number of keys in this hashtable. * * @return the number of keys in this hashtable. */ public synchronized int size() { return count; }
public synchronized boolean isEmpty()
public synchronized Enumeration<K> keys()
public synchronized Enumeration<V> elements()
/** * Returns an enumeration of the values in this hashtable. * Use the Enumeration methods on the returned object to fetch the elements * sequentially. * * @return an enumeration of the values in this hashtable. * @see java.util.Enumeration * @see #keys() * @see #values() * @see Map */ public synchronized Enumeration elements() { return this.getEnumeration(VALUES); }
public synchronized boolean contains(Object value)
public synchronized boolean contains(Object value) { if (value == null) { throw new NullPointerException(); } Entry tab[] = table; for (int i = tab.length ; i-- > 0 ;) { for (Entry e = tab[i] ; e != null ; e = e.next) { if (e.value.equals(value)) { return true; } } } return false; }
public synchronized boolean containsKey(Object key)
public synchronized boolean containsKey(Object key) { Entry tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { return true; } } return false; }
public synchronized V get(Object key)
@SuppressWarnings("unchecked") public synchronized V get(Object key) { Entry tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { return (V)e.value; } } return null; }
protected void rehash()
@SuppressWarnings("unchecked") protected void rehash() { int oldCapacity = table.length; Entry[] oldMap = table; // overflow-conscious code int newCapacity = (oldCapacity if (newCapacity - MAX_ARRAY_SIZE > 0) { if (oldCapacity == MAX_ARRAY_SIZE) // Keep running with MAX_ARRAY_SIZE buckets return; newCapacity = MAX_ARRAY_SIZE; } Entry[] newMap = new Entry[newCapacity]; modCount++; threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1); table = newMap; for (int i = oldCapacity ; i-- > 0 ;) { for (Entry old = (Entry)oldMap[i] ; old != null ; ) { Entry e = old; old = old.next; int index = (e.hash & 0x7FFFFFFF) % newCapacity; e.next = (Entry)newMap[index]; newMap[index] = e; } } }
private void addEntry(int hash, K key, V value, int index)
private void addEntry(int hash, K key, V value, int index) { modCount++; Entry tab[] = table; if (count >= threshold) { // Rehash the table if the threshold is exceeded rehash(); tab = table; hash = key.hashCode(); index = (hash & 0x7FFFFFFF) % tab.length; } // Creates the new entry. @SuppressWarnings("unchecked") Entry e = (Entry) tab[index]; tab[index] = new Entry(hash, key, value, e); count++; }
public synchronized V put(K key, V value)
public synchronized V put(K key, V value) { // Make sure the value is not null if (value == null) { throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Entry tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry entry = (Entry)tab[index]; for(; entry != null ; entry = entry.next) { if ((entry.hash == hash) && entry.key.equals(key)) { V old = entry.value; entry.value = value; return old; } } addEntry(hash, key, value, index); return null; }
public synchronized V remove(Object key)
public synchronized V remove(Object key) { Entry tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry e = (Entry)tab[index]; for(Entry prev = null ; e != null ; prev = e, e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { modCount++; if (prev != null) { prev.next = e.next; } else { tab[index] = e.next; } count--; V oldValue = e.value; e.value = null; return oldValue; } } return null; }
public synchronized void putAll(Map<? extends K, ? extends V> t)
public synchronized void putAll(Map t) { for (Map.Entry e : t.entrySet()) put(e.getKey(), e.getValue()); }
public synchronized void clear()
public synchronized Object clone()
public synchronized String toString()
HashMap
源码
成员变量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
初始容量16
static final int MAXIMUM_CAPACITY = 1 << 30;
最大容量,如果两个构造函数都使用参数隐式指定了更高的值,则使用该容量。 必须是两个
static final float DEFAULT_LOAD_FACTOR = 0.75f;
在构造函数中未指定时使用的负载系数
static final int TREEIFY_THRESHOLD = 8;
使用树而不是列表列出容器的容器计数阈值。 将元素添加到至少具有这么多节点的bin中时,bin会转换为树。 该值必须大于2,并且至少应为8才能与树删除中的假设(即收缩时转换回原始垃圾箱)相啮合。
static final int UNTREEIFY_THRESHOLD = 6;
static final int MIN_TREEIFY_CAPACITY = 64;
可将其分类为树木的最小桌子容量。 (否则,如果bin中的节点过多,则将调整表的大小。)应至少为4 * TREEIFY_THRESHOLD,以避免调整大小和树化阈值之间的冲突。
transient Node<K,V>[] table;
该表在首次使用时初始化,并根据需要调整大小。 分配时,长度始终是2的幂。 (在某些操作中,我们还允许长度为零,以允许使用当前不需要的引导机制。)
transient Set<Map.Entry<K,V>> entrySet;
保存缓存的entrySet()。 注意,AbstractMap字段用于keySet()和values()。
transient int size;
此映射中包含的键-值映射数。
transient int modCount;
已对HashMap进行结构修改的次数结构修改是指更改HashMap中的映射数或以其他方式修改其内部结构(例如,重新哈希)的修改。 此字段用于使HashMap的Collection-view上的迭代器快速失败
int threshold;
要调整大小的下一个大小值(容量*负载系数)
final float loadFactor;
哈希表的负载因子。
static class Node<K,V> implements Map.Entry<K,V>
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next)
Node(int hash, K key, V value, Node next) { this.hash = hash; this.key = key; this.value = value; this.next = next; }
getKey()
getValue()
toString()
int hashCode()
public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); }
setValue(V newValue)
public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; }
boolean equals(Object o)
public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { Map.Entry e = (Map.Entry)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; }
成员函数
static final int hash(Object key)
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
static Class<?> comparableClassFor(Object x)
static Class comparableClassFor(Object x) { if (x instanceof Comparable) { Class c; Type[] ts, as; Type t; ParameterizedType p; if ((c = x.getClass()) == String.class) // bypass checks return c; if ((ts = c.getGenericInterfaces()) != null) { for (int i = 0; i if (((t = ts[i]) instanceof ParameterizedType) && ((p = (ParameterizedType)t).getRawType() == Comparable.class) && (as = p.getActualTypeArguments()) != null && as.length == 1 && as[0] == c) // type arg is c return c; } } } return null; }
static int compareComparables(Class<?> kc, Object k, Object x)
static int compareComparables(Class kc, Object k, Object x) { return (x == null || x.getClass() != kc ? 0 : ((Comparable)k).compareTo(x)); }
static int compareComparables(Class<?> kc, Object k, Object x)
@SuppressWarnings({"rawtypes","unchecked"}) // for cast to Comparable static int compareComparables(Class kc, Object k, Object x) { return (x == null || x.getClass() != kc ? 0 : ((Comparable)k).compareTo(x)); }
//对于给定的目标容量,返回两倍大小的幂。 static final int tableSizeFor(int cap)
/** * Returns a power of two size for the given target capacity. */ static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n = MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; }
HashMap(int initialCapacity)
public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); }
public HashMap()
public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted }
public HashMap(Map<? extends K, ? extends V> m)
public HashMap(Map m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false); }
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict)
//实现Map.putAll和Map构造函数 final void putMapEntries(Map m, boolean evict) { int s = m.size(); if (s > 0) { if (table == null) { // pre-size float ft = ((float)s / loadFactor) + 1.0F; int t = ((ft (int)ft : MAXIMUM_CAPACITY); if (t > threshold) threshold = tableSizeFor(t); } else if (s > threshold) resize(); for (Map.Entry e : m.entrySet()) { K key = e.getKey(); V value = e.getValue(); putVal(hash(key), key, value, false, evict); } } }
public int size()
boolean isEmpty()
public V get(Object key)
public V get(Object key) { Node e; return (e = getNode(hash(key), key)) == null ? null : e.value; }
final Node<K,V> getNode(int hash, Object key)
final Node getNode(int hash, Object key) { Node[] tab; Node first, e; int n; K k; if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) return first; if ((e = first.next) != null) { if (first instanceof TreeNode) return ((TreeNode)first).getTreeNode(hash, key); do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } } return null; }
public boolean containsKey(Object key)
如果此映射包含指定键的映射,则返回true 。
public V put(K key, V value)
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)
https://blog.csdn.net/ljf199701/article/details/106309417
final Node<K,V>[] resize()
final Node[] resize() { Node[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr } else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) Node[] newTab = (Node[])new Node[newCap]; table = newTab; if (oldTab != null) { for (int j = 0; j Node e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode)e).split(this, newTab, j, oldCap); else { // preserve order Node loHead = null, loTail = null; Node hiHead = null, hiTail = null; Node next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
final void treeifyBin(Node<K,V>[] tab, int hash)
final void treeifyBin(Node[] tab, int hash) { //定义几个变量,n是数组长度,index是索引 int n, index; Node e; //这里的tab指的是本HashMap中的数组,n为数字长度,如果数组为null或者数组长度小于64 if (tab == null || (n = tab.length) //则调用resize()方法直接扩容,不转红黑树 resize(); //否则说明满足转红黑树条件,通过按位与运算取得索引index,并将该索引对应的node节点赋值给e,e不为null时 else if ((e = tab[index = (n - 1) & hash]) != null) { //定义几个变量,hd代表头节点,tl代表尾节点 TreeNode hd = null, tl = null; do { //先把e节点转成TreeNode类型,并赋值给p TreeNode p = replacementTreeNode(e, null); //如果尾节点tl为空,则说明还没有根节点,试想下,这时元素数目都超过8个了,还能没有尾节点么,所以没有尾节点只能说明还没设置根节点 if (tl == null) //设置根节点,把p赋值给根节点hd hd = p; else { //把tl设置为p的前驱节点 p.prev = tl; //把p设置为tl的后继节点,这两步其实就是我指向你,你指向我的关系,为了形成双向链表 tl.next = p; } //把首节点设置成p后,把p赋值给尾节点tl,然后会再取链表的下一个节点,转成TreeNode类型后再赋值给p,如此循环 tl = p; //取下一个节点,直到下一个节点为空,也就代表这链表遍历好了 } while ((e = e.next) != null); //用新生成的双向链表替代旧的单向链表,其实就是把这个数组对应的位置重新赋值成新双向链表的首节点 if ((tab[index] = hd) != null) //这个方法里就开始做各种比较,左旋右旋,然后把双向链表搞成一个红黑树 hd.treeify(tab); } }
public V remove(Object key)
public V remove(Object key) { Node e; return (e = removeNode(hash(key), key, null, false, true)) == null ? null : e.value; }
final Node<K,V> removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable)
final Node removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable) { Node[] tab; Node p; int n, index; //当table不为空,并且hash对应的桶不为空时 if ((tab = table) != null && (n = tab.length) > 0 && (p = tab[index = (n - 1) & hash]) != null) { Node node = null, e; K k; V v; //桶中的头节点就是我们要删除的节点 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) //用node记录要删除的头节点 node = p; //头节点不是要删除的节点,并且头节点之后还有节点 else if ((e = p.next) != null) { //头节点为树节点,则进入树查找要删除的节点 if (p instanceof TreeNode) node = ((TreeNode)p).getTreeNode(hash, key); //头节点为链表节点 else { //遍历链表 do { //hash值相等,并且key地址相等或者equals if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { //node记录要删除的节点 node = e; break; } //p保存当前遍历到的节点 p = e; } while ((e = e.next) != null); } } //我们要找的节点不为空 if (node != null && (!matchValue || (v = node.value) == value || (value != null && value.equals(v)))) { if (node instanceof TreeNode) //在树中删除节点 ((TreeNode)node).removeTreeNode(this, tab, movable); //我们要删除的是头节点 else if (node == p) tab[index] = node.next; //不是头节点,将当前节点指向删除节点的下一个节点 else p.next = node.next; ++modCount; --size; afterNodeRemoval(node); return node; } } return null; }
public void clear()
public void clear() { Node[] tab; modCount++; if ((tab = table) != null && size > 0) { size = 0; for (int i = 0; i tab[i] = null; } }
public Set<K> keySet()
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)
computeIfAbsent的方法有两个参数 第一个是所选map的key,第二个是需要做的操作。这个方法当key值不存在时才起作用。 当key存在返回当前value值,不存在执行函数并保存到map中 public V computeIfAbsent(K key, Function mappingFunction) { if (mappingFunction == null) throw new NullPointerException(); int hash = hash(key); Node[] tab; Node first; int n, i; int binCount = 0; TreeNode t = null; Node old = null; if (size > threshold || (tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((first = tab[i = (n - 1) & hash]) != null) { if (first instanceof TreeNode) old = (t = (TreeNode)first).getTreeNode(hash, key); else { Node e = first; K k; do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { old = e; break; } ++binCount; } while ((e = e.next) != null); } V oldValue; if (old != null && (oldValue = old.value) != null) { afterNodeAccess(old); return oldValue; } } V v = mappingFunction.apply(key); if (v == null) { return null; } else if (old != null) { old.value = v; afterNodeAccess(old); return v; } else if (t != null) t.putTreeVal(this, tab, hash, key, v); else { tab[i] = newNode(hash, key, v, first); if (binCount >= TREEIFY_THRESHOLD - 1) treeifyBin(tab, hash); } ++modCount; ++size; afterNodeInsertion(true); return v; }
public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
public V compute(K key, BiFunction remappingFunction) { if (remappingFunction == null) throw new NullPointerException(); int hash = hash(key); Node[] tab; Node first; int n, i; int binCount = 0; TreeNode t = null; Node old = null; if (size > threshold || (tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((first = tab[i = (n - 1) & hash]) != null) { if (first instanceof TreeNode) old = (t = (TreeNode)first).getTreeNode(hash, key); else { Node e = first; K k; do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { old = e; break; } ++binCount; } while ((e = e.next) != null); } } V oldValue = (old == null) ? null : old.value; V v = remappingFunction.apply(key, oldValue); if (old != null) { if (v != null) { old.value = v; afterNodeAccess(old); } else removeNode(hash, key, null, false, true); } else if (v != null) { if (t != null) t.putTreeVal(this, tab, hash, key, v); else { tab[i] = newNode(hash, key, v, first); if (binCount >= TREEIFY_THRESHOLD - 1) treeifyBin(tab, hash); } ++modCount; ++size; afterNodeInsertion(true); } return v; }
public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction)
public V merge(K key, V value, BiFunction remappingFunction) { if (value == null) throw new NullPointerException(); if (remappingFunction == null) throw new NullPointerException(); int hash = hash(key); Node[] tab; Node first; int n, i; int binCount = 0; TreeNode t = null; Node old = null; if (size > threshold || (tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((first = tab[i = (n - 1) & hash]) != null) { if (first instanceof TreeNode) old = (t = (TreeNode)first).getTreeNode(hash, key); else { Node e = first; K k; do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { old = e; break; } ++binCount; } while ((e = e.next) != null); } } if (old != null) { V v; if (old.value != null) v = remappingFunction.apply(old.value, value); else v = value; if (v != null) { old.value = v; afterNodeAccess(old); } else removeNode(hash, key, null, false, true); return v; } if (value != null) { if (t != null) t.putTreeVal(this, tab, hash, key, value); else { tab[i] = newNode(hash, key, value, first); if (binCount >= TREEIFY_THRESHOLD - 1) treeifyBin(tab, hash); } ++modCount; ++size; afterNodeInsertion(true); } return value; }
红黑树
TreeMap
TreeMap的实现是红黑树算法的实现
特点
TreeMap 继承于AbstractMap,所以它是一个Map,即一个key-value集合。
TreeMap 是一个 有序的key-value集合,它是通过 红黑树 实现的。
TreeMap 实现了java.io.Serializable接口,意味着 它支持序列化。
TreeMap 实现了Cloneable接口,意味着 它能被克隆。
TreeMap 实现了NavigableMap接口,意味着它 **支持一系列的导航方法。**比如返回有序的key集合。
TreeMap基于红黑树 实现。该映射根据 其键的自然顺序进行排序,或者根据 创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。
成员变量的介绍
//key的比较器 private final Comparator<? super K> comparator;
private transient TreeMapEntry<K,V> root;//数的根节点
private transient int size = 0;//treemap节点的数量
private transient EntrySet entrySet;//键值对集合
private transient KeySet<K> navigableKeySet;//键集合
private transient NavigableMap<K,V> descendingMap;//降序的NavigableMap
构造函数
public TreeMap() { comparator = null; }
public TreeMap(Comparator<? super K> comparator) { this.comparator = comparator; }
public TreeMap(Map<? extends K, ? extends V> m) { comparator = null; putAll(m); }
public TreeMap(SortedMap<K, ? extends V> m) { comparator = m.comparator(); try { buildFromSorted(m.size(), m.entrySet().iterator(), null, null); } catch (java.io.IOException cannotHappen) { } catch (ClassNotFoundException cannotHappen) { } }
成员函数
get(Object key) and getEntry(Object key)
public V get(Object key) { TreeMapEntry p = getEntry(key); return (p==null ? null : p.value); } final TreeMapEntry getEntry(Object key) { // Offload comparator-based version for sake of performance if (comparator != null) return getEntryUsingComparator(key); if (key == null) throw new NullPointerException(); @SuppressWarnings("unchecked") Comparable k = (Comparable) key; TreeMapEntry p = root; while (p != null) { int cmp = k.compareTo(p.key); if (cmp p = p.left; else if (cmp > 0) p = p.right; else return p; } return null; } final TreeMapEntry getEntryUsingComparator(Object key) { @SuppressWarnings("unchecked") K k = (K) key; Comparator cpr = comparator; if (cpr != null) { TreeMapEntry p = root; while (p != null) { int cmp = cpr.compare(k, p.key); if (cmp p = p.left; else if (cmp > 0) p = p.right; else return p; } } return null; }
ceilingEntry(K key)
获取 大于等于 key的最小节点。当 key比树中最大的key大时返回null。 public Map.Entry ceilingEntry(K key) { return exportEntry(getCeilingEntry(key)); } final TreeMapEntry getCeilingEntry(K key) { TreeMapEntry p = root; while (p != null) { int cmp = compare(key, p.key); if (cmp if (p.left != null) p = p.left; else return p; } else if (cmp > 0) { if (p.right != null) { p = p.right; } else { TreeMapEntry parent = p.parent; TreeMapEntry ch = p; while (parent != null && ch == parent.right) { ch = parent; parent = parent.parent; } return parent; } } else return p; } return null; }
higherEntry(K key)
public K floorKey(K key) { return keyOrNull(getFloorEntry(key)); } final TreeMapEntry getFloorEntry(K key) { TreeMapEntry p = root; while (p != null) { int cmp = compare(key, p.key); if (cmp > 0) { if (p.right != null) p = p.right; else return p; } else if (cmp if (p.left != null) { p = p.left; } else { TreeMapEntry parent = p.parent; TreeMapEntry ch = p; while (parent != null && ch == parent.left) { ch = parent; parent = parent.parent; } return parent; } } else return p; } return null; }
lowerEntry(K key)
public K lowerKey(K key) { return keyOrNull(getLowerEntry(key)); } final TreeMapEntry getLowerEntry(K key) { TreeMapEntry p = root; while (p != null) { int cmp = compare(key, p.key); if (cmp > 0) { if (p.right != null) p = p.right; else return p; } else { if (p.left != null) { p = p.left; } else { TreeMapEntry parent = p.parent; TreeMapEntry ch = p; while (parent != null && ch == parent.left) { ch = parent; parent = parent.parent; } return parent; } } } return null; }
put
public V put(K key, V value) { TreeMapEntry t = root; if (t == null) { compare(key, key); // type (and possibly null) check root = new TreeMapEntry(key, value, null); size = 1; modCount++; return null; } int cmp; TreeMapEntry parent; // split comparator and comparable paths Comparator cpr = comparator; if (cpr != null) { do { parent = t; cmp = cpr.compare(key, t.key); if (cmp t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } else { if (key == null) throw new NullPointerException(); @SuppressWarnings("unchecked") Comparable k = (Comparable) key; do { parent = t; cmp = k.compareTo(t.key); if (cmp t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } TreeMapEntry e = new TreeMapEntry(key, value, parent); if (cmp parent.left = e; else parent.right = e; fixAfterInsertion(e); size++; modCount++; return null; }
remove(Object key)
public V remove(Object key) { TreeMapEntry p = getEntry(key); if (p == null) return null; V oldValue = p.value; deleteEntry(p); return oldValue; } private void deleteEntry(TreeMapEntry p) { modCount++; size--; if (p.left != null && p.right != null) {// p has 2 children TreeMapEntry s = successor(p); p.key = s.key; p.value = s.value; p = s; } TreeMapEntry replacement = (p.left != null ? p.left : p.right); if (replacement != null) { // Link replacement to parent replacement.parent = p.parent; if (p.parent == null) root = replacement; else if (p == p.parent.left) p.parent.left = replacement; else p.parent.right = replacement; p.left = p.right = p.parent = null; if (p.color == BLACK) fixAfterDeletion(replacement); } else if (p.parent == null) { // return if we are the only node. root = null; } else { // No children. Use self as phantom replacement and unlink. if (p.color == BLACK) fixAfterDeletion(p); if (p.parent != null) { if (p == p.parent.left) p.parent.left = null; else if (p == p.parent.right) p.parent.right = null; p.parent = null; } } }
Set
HashSet
特点
(1)HashSet内部使用HashMap的key存储元素,以此来保证元素不重复
(2)HashSet是无序的,因为HashMap的key是无序的;
(3)HashSet中允许有一个null元素,因为HashMap允许key为null;
(4)HashSet是非线程安全的;
(5)HashSet是没有get()方法的;
属性
// 内部使用HashMap private transient HashMap<E,Object> map;
// 虚拟对象,用来作为value放到map中 private static final Object PRESENT = new Object();
构造方法
public HashSet() { map = new HashMap<>(); }
public HashSet(Collection<? extends E> c) { map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16)); addAll(c); }
public HashSet(int initialCapacity, float loadFactor) { map = new HashMap<>(initialCapacity, loadFactor); }
public HashSet(int initialCapacity) { map = new HashMap<>(initialCapacity); }
// 非public,主要是给LinkedHashSet使用的 HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<>(initialCapacity, loadFactor); }
成员函数
add
public boolean add(E e) { return map.put(e, PRESENT)==null; }
remove
public boolean remove(Object o) { return map.remove(o)==PRESENT; }
contains
public boolean contains(Object o) { return map.containsKey(o); }
clone
// 克隆方法 @SuppressWarnings("unchecked") public Object clone() { try { HashSet newSet = (HashSet) super.clone(); newSet.map = (HashMap) map.clone(); return newSet; } catch (CloneNotSupportedException e) { throw new InternalError(e); } }
writeObject
// 序列化写出方法 private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { // 写出非static非transient属性 s.defaultWriteObject(); // 写出map的容量和装载因子 s.writeInt(map.capacity()); s.writeFloat(map.loadFactor()); // 写出元素个数 s.writeInt(map.size()); // 遍历写出所有元素 for (E e : map.keySet()) s.writeObject(e); }
readObject
// 序列化读入方法 private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { // 读入非static非transient属性 s.defaultReadObject(); // 读入容量, 并检查不能小于0 int capacity = s.readInt(); if (capacity throw new InvalidObjectException("Illegal capacity: " + capacity); } // 读入装载因子, 并检查不能小于等于0或者是NaN(Not a Number) // java.lang.Float.NaN = 0.0f / 0.0f; float loadFactor = s.readFloat(); if (loadFactor throw new InvalidObjectException("Illegal load factor: " + loadFactor); } // 读入元素个数并检查不能小于0 int size = s.readInt(); if (size throw new InvalidObjectException("Illegal size: " + size); } // 根据元素个数重新设置容量 // 这是为了保证map有足够的容量容纳所有元素, 防止无意义的扩容 capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f), HashMap.MAXIMUM_CAPACITY); // 再次检查某些东西, 不重要的代码忽视掉 SharedSecrets.getJavaOISAccess() .checkArray(s, Map.Entry[].class, HashMap.tableSizeFor(capacity)); // 创建map, 检查是不是LinkedHashSet类型 map = (((HashSet)this) instanceof LinkedHashSet ? new LinkedHashMap(capacity, loadFactor) : new HashMap(capacity, loadFactor)); // 读入所有元素, 并放入map中 for (int i=0; i @SuppressWarnings("unchecked") E e = (E) s.readObject(); map.put(e, PRESENT); } }
LinkHashSet
TreeSet
底层是基于TreeMap来实现的,所以底层结构也是红黑树,因为他和HashSet不同的是不需要重写hashCode()和equals()方法,因为它去重是依靠比较器来去重,因为结构是红黑树,所以每次插入都会遍历比较来寻找节点插入位置,如果发现某个节点的值是一样的那就会直接覆盖。
异常的分类和处理
反射机制
Class类
代表类的实体,在运行的Java应用程序中表示类和接口
获取类中的方法
asSubclass(Class<U> clazz)
把传递的类的对象转换成代表其子类的对象
Cast
把对象转换成代表类或是接口的对象
getClassLoader()
获得类的加载器
getClasses()
返回一个数组,数组中包含该类中所有公共类和接口类的对象
getDeclaredClasses()
返回一个数组,数组中包含该类中所有类和接口类的对象
forName(String className)
根据类名返回类的对象
getName()
获得类的完整路径名字
newInstance()
创建类的实例
getPackage()
获得类的包
getInterfaces()
获得当前类实现的类或是接口
getSuperclass()
获得当前类继承的父类的名字
getSimpleName()
获得类的名字
获得类中属性相关的方法
getField(String name)
获得某个公有的属性对象
getFields()
获得所有公有的属性对象
getDeclaredField(String name)
获得某个属性对象
getDeclaredFields()
获得所有属性对象
获取类中注解相关的方法
getAnnotation(Class<A> annotationClass)
返回该类中与参数类型匹配的公有注解对象
getAnnotations()
返回该类所有的公有注解对象
getDeclaredAnnotation(Class<A> annotationClass)
返回该类中与参数类型匹配的所有注解对象
getDeclaredAnnotations()
返回该类所有的注解对象
获得类中构造器相关的方法
getConstructor(Class...<?> parameterTypes)
获得该类中与参数类型匹配的公有构造方法
getConstructors()
获得该类的所有公有构造方法
getDeclaredConstructor(Class...<?> parameterTypes)
获得该类中与参数类型匹配的构造方法
getDeclaredConstructors()
获得该类所有构造方法
获得类中方法相关的方法
getMethod(String name, Class...<?> parameterTypes)
获得该类某个公有的方法
getMethods()
获得该类所有公有的方法
getDeclaredMethod(String name, Class...<?> parameterTypes)
获得该类某个方法
getDeclaredMethods()
获得该类所有方法
类中其他重要的方法
isAnnotation()
如果是注解类型则返回true
isAnnotationPresent(Class<? extends Annotation> annotationClass)
如果是指定类型注解类型则返回true
isAnonymousClass()
如果是匿名类则返回true
isArray()
isEnum()
isInstance(Object obj)
isInterface()
isLocalClass()
isMemberClass()
Field类
代表类的成员变量(成员变量也成为类的属性)
equals(Object obj)
属性与obj相等则返回true
get(Object obj)
获得obj中对应的属性值
set(Object obj, Object value)
设置obj中对应属性值
Method
代表类的方法
invoke(Object obj, Object... args)
传递object对象及参数调用该对象对应的方法
Constructor类
代表类的构造方法
newInstance(Object... initargs)
根据传递的参数创建类的对象
注解
内部类
泛型
序列化
多线程编程
java 多线程创建的方式
继承Thread类
实现runable接口
通过executorService和Callable<Class>实现有返回值的线程
基于线程池
线程池的工作原理
线程复用
线程池的核心逐渐和核心类
线程池管理器
工作线程
任务接口
任务队列
线程池的工作流程
1 当一个任务通过submit或者execute方法提交到线程池的时候,如果当前池中线程数(包括闲置线程)小于coolPoolSize,则创建一个线程执行该任务。
2、如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务
3、如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列
4、如果这时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务
5、如果对队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行.
6、当一个线程完成任务时,它会从队列中取下一个任务来执行.
7、如果当前运行的线程数大于corePoolSize,那么这个线程就会被停掉
线程池的拒绝策略
1、AbortPolicy
直接抛出异常
2、CallerRunsPolicy
如果被丢弃的线程任务未关闭,则执行改线程任务
3、DiscardOldestPolicy
移出线程队列中最早的一个线程任务,并尝试提交当前任务
4、DiscardPolicy
丢弃当前的线程任务而不做任何处理
5、自定义拒绝策略
五中常用的线程池
newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor
创建一个定长线程池,支持定时及周期性任务执行。
newWorkStealingPool
线程的生命周期
新建状态:new
就绪状态:Runnable
运行状态:Running
阻塞状态:Blocked
等待阻塞
同步阻塞
线程死亡:Dead
线程正常结束
run方法或者call方法执行完成
线程异常退出
手动结束线程
stop方法
线程的基本方法
线程等待:wait方法
线程睡眠:sleep方法
线程让步:yield方法
线程中断:interrupt方法
线程加入:join方法
线程唤醒:notify方法
后台守护线程:setDaemon方法
sleep方法与wait方法的区别
sleep方法属于Thread类,wait方法属于Object类 sleep方法暂停执行的时间,让出cpu给其他线程,其他
终止线程的四种方式
正常运行结束
使用退出标志退出线程
使用interrupt方法终止县城
1、线程处于阻塞状态
2、线程处于未阻塞状态
使用stop方法终止线程:不安全
java中的锁
乐观锁
每次读取数据时认为别人不会修改,在写时先去出版本号然后枷锁
悲观锁
每次读数据时都认为别人会修改数据,所以每次在读写数据时都会上锁
自旋锁
自旋锁的优点
减少cpu的上下文切换,对于占用的时间非常短,或者锁竞争不激烈的代码块来说性能大幅度提升,因为自旋锁的cpu耗时时间明显少于线程的阻塞挂起,在唤醒时两次cpu上下文切换所用的时间
自旋锁的缺点
在持有锁的线程占用锁时间过长或锁的竞争过于激烈时,线程在自旋过程中会长时间获取不到资源
自旋锁的时间阈值
synchronized
作用范围
作用于成员变量和非静态方法时,锁住的是对象的实例。
作用于静态方法时,锁住的是class实例
作用于一个代码块时,锁住的是所有代码块黄总配置的对象
死锁问题
synchronized的实现原理
ContentionList
锁竞争,所有的请求锁的线程都被放在竞争队列中
EntryList
有资格成为候选则从ContentionList移到这里
WaitSet
等待集合,调用wait方法后被阻塞的线程将被放在竞争队列中
OnDeck
竞争队列候选者
Owner
竞争到锁资源的线程
!Owner
ReentrantLock
ReentrantLocK的用法
ReentranLock如何避免死锁:响应中断,可轮询锁,定时锁
响应中断
lockInterrruptibly
可轮询锁 tryLock()
定时琐 bealean tyrLock(long time,TimeUnit unit)
Lock接口的主要方法
void lock()
加锁
tryLock()
tryLock(long timeout TimeUnit unit)
unlock
newCondition()
getHoldCount()
查询当前线程保持此锁次数
getQueueLength()
查询当前线程保持此锁的次数
getWaitQueuLength()
返回等待获取此锁的线程估计数
hasWaiters(Condition condtion)
查询是否有县城正在等待与给定条件有关的锁
hasQueueTheard(Thread thread)
查询当前线程是否等待获取改锁
hasQueuedThreads()
查询有线程等待该锁
atomic
AtomicInteger
AtomicBoolean
AtomicLong
AtomicReference
可重入锁
非公平锁
synchronized
ReentrantLock
公平锁
读写锁
ReentrantReadWritLock
readLock
writeLock
独占锁
共享锁
重量锁
Mutex Lock,需要在用户太和核心太之间做转换
synchronized
轻量级锁
适用于线程交替执行同步代码块的情况,如果同一时刻有多个线程访问同一个锁,则将会导致轻量级锁膨胀为重量级锁
偏向锁
在某个线程获取某个锁之后,消除这个线程锁重入的开销
分段锁
如何进行锁优化
减少锁的持有时间
只有在线程安全要求的程序上加锁尽量来减少同步代码块对锁的持有时间
减小锁粒度
将单个耗时较多的锁操作拆分为多个耗时较少的锁操作来增加锁的并行度,减少同一个锁的竞争。
锁分离
根据不同的应用场景将锁的功能进行分离,以应对不同的变化
锁粗化
锁粗化为了保障性能,会要求尽可能将锁的操作细化以减少线程持有锁的时间
锁消除
线程上下文切换
进程
上下文
寄存器
程序计数器
切换过程
挂起一个进程,将这个进程在cpu中的状态(上下文信息)存储于内存的PCB
在PCB中检索下一个进程的上下文将其在cpu的寄存器中恢复
跳转到程序计数器所指向的位置
引起上下文切换的原因
当前正在执行的任务完成,系统cpu正常调度下一个任务
当前正在执行的任务遇到I/o阻塞操作,调度挂起此任务
java并发关键字
countDownLatch
CountDown
减少一个信号量
await()
CyclicBarrier
await
await(long timeout,TimeUnit unit)
Semaphore
acquire()
以阻塞的方式获取一个许可,在有可用许可时返回该许可,在没有可用许可时阻塞等待
acquire(int permits)
同时获取permits个许可
release()
释放某个许可
release(int permits)
释放permits个许可
tryAcquire()
以非阻塞的方式获取一个许可,在有可用许可时获取该许可并返回一个true
tryAcquire(long timeout,TimeUnit unit)
在指定的时间内成功获取permits个许可,则返回true,否则返回false
avaliablePermits()
查询可用的许可数量
volaile关键字
禁止指令重排
让cpu从内存中读取而不是从catch中读取
并发环境的线程安全
原子性操作
多线程如何共享数据
JavaEE
UML图
类图
泛化
1.继承
用三角形加实线吧表示
2.实现
用实线三角形加虚线表示
关联
对于两个相对独立的对象,当一个对象的实例与另一个对象的一些特定实例存在固定的对应关系时,这两个对象之间为关联关系。 表示方法: 关联关系用实线箭头表示。 示例: 企鹅需要‘知道’气候的变化,需要‘了解’气候规律。当一个类‘知道’另一个类时,可以用关联。 作者:最后的轻语_dd43 链接:https://www.jianshu.com/p/57620b762160 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
用实线加实线箭头表示
聚合
表示一种弱的‘拥有’关系,即has-a的关系,体现的是A对象可以包含B对象,但B对象不是A对象的一部分。 两个对象具有各自的生命周期。
用菱形加实线表示
组合
组合是一种强的‘拥有’关系,是一种contains-a的关系,体现了严格的部分和整体关系,部分和整体的生命周期一样。 表示方法: 组合关系用实心的菱形+实线箭头表示,还可以使用连线两端的数字表示某一端有几个实例。 作者:最后的轻语_dd43 链接:https://www.jianshu.com/p/57620b762160 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
实心的菱形+实线箭头
依赖
对于两个相对独立的对象,当一个对象负责构造另一个对象的实例,或者依赖另一个对象的服务时,这两个对象之间主要体现为依赖关系。 表示方法: 依赖关系用虚线箭头表示。
用虚线箭头加虚线表示
设计模式
原则
单一职责原则
定义是:应该有且仅有一个原因引起类的变更
开闭原则
里式替换原则
优点
代码共享
定义
1.如果对每一个类型为S的对象o1,都有类型为T的对 象o2,使得以T定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变 化,那么类型S是类型T的子类型
2.所有引用基类的地方必须能透明地使用其子类的 对象
依赖倒转原则
接口隔离原则
合成、聚合复用原则
迪米特法则
分类
创建型模式
提供了多重优雅创建对象的方法
工厂模式
根据类型判断生成的Bean对象,
抽象工厂模式
单例模式
懒汉式
在调用函数的时候才加载对象并返回
恶汉式
静态内部类
双重校验锁
元组
建造者模式
Builder
创建一个复杂的产品对象抽象接口
ConcreteBuilder
Builder接口的实现类,用于定义复杂产品各个部件的装配流程
Driector
构造一个使用Builder接口的对象
Product
表示被构造的复杂对象。
原型模式
浅复制
对象的基本数据类型的变量会被复制和创建,而引用数据类型仍指向原对象的引用
深复制
结构型模式
通过类和借口之间的继承和引用实现创建负载结构对象的功能
适配模式
角色
Source
Targetable
Adapter
类适配器模式
在需要不改变(或者由于项目原因无法改变)原有接口或类结构的情况下扩展类的功能以适配不同的接口
对象适配器模式
修改了adapter类
接口适配器模式
桥接模式
通过将抽象及其实现解偶,使二者可以根据需求独立变化,主要用于解决在需求多变的情况下使用继承造成类爆炸的问题
组合模式
主要用于实现部分和整体操作的一致性,组合模式常根据树形机构来表示部分及整体之间的关系,组合模式通过特定的数据结构简化了部分和整体之间的关系,使得客户端可以先给处理单个元素一样来处理整体的数据集,而无须关心单个元素和整体数据集之间的内部复杂结构
装饰者模式
在无须改变原有类及类的继承关系的情况下,动态扩展一个类的功能。它通过装饰着来包裹真实的对象,并动态的向对象添加或撤销功能
source
被装饰者
Decorator
装饰者
外观模式
通过一个门面向客户端提供一个访问系统的统一接口,
子系统角色
门面系统角色
客户角色
享元模式
主要通过对象的复用来减少对象创建的次数和数量,享元模式属于结构型模式,在系统需要一个对象时享元模式首先在系统中查找并尝试重用现有的对象,如果未找到匹配的对象,则创建新对象并将其缓存在系统中以便下次使用
代理模式
为对象提供一种通过代理的方式来访问并控制该对象行为的方法。
行为模式
通过类之间的不同的通信方式实现不同的行为方式
策略模式
为同一个行为定义不同的策略,并为没中策略都实现了不同的方法。在用户使用的时候,系统根据不同的策略自动切换为不同的方法来实现策略的改变。同一个策略下的不同方法是对同一个功能的不同实现,因此在使用时可以互相替换而不影响用户的使用
模板方法模式
模板方法定义了一个算法框架,并通过继承的方式将算法的实现延迟到子类中,使得子类可以在不改变算法框架及其流程的前提下重新定义该算法在某些特定环节的实现,是一种类行为模式
抽象类
定义了算法的框架,由基本方法和模板方法组成,基本法定义了算法有哪些环节,模板方法定义了算法各个环节执行的流程
具体子类
在对抽象类中定义的算法根据需求进行不同的实现
观察者模式
是指在被观察的状态发生变化时,系统基于事件驱动理论将其状态通知到订阅其状态额观察者对象中,以完成状态的修改和事件传播
抽象主题
持有订阅了该主题的观察者对象的集合,同时提供了增加删除观察者对象的方法和主题状态发生变化后的通知方法
具体主题
实现了抽象主题的通知方法,在主题的内部状态发生变化时,调用该方法通知订阅了主题状态的观察者对象
抽象观察者
具体观察者
迭代器模式
体统了顺序访问集合对象中的各个元素,而不暴露内部结构的方法
责任链模式
用于避免请求发送者与过多个请求处理者耦合在一起,让所有请求的处理者持有下一个对象的引用,从而将请求串联成一条链,在请求有发生时,可将请求沿着这条链路传递,直到遇到该对象的处理器
handler接口
用于规定在责任链上具体要执行的方法
AbstractHandler
抽象类,持有Handler实例并通过sethandler()和gethandler将各个具体的业务Handler串成一个责任链,客户端上的请求在责任链上执行
业务Handler
根据用户具体的业务徐需求实现的业务逻辑
命令模式
抽象命令模式
执行命令的接口,定义执行命令的抽象方法execute()
具体命令类(Concrete Command)
抽象命令类的实现类,持有接受者对象,并在接受到命令后调用命令执行者的方法action()实现命令的调和和执行
命令执行者(Receiver)
命令的具体执行者,定义了命令的具体方法action()
命令调用者(invoker)
接受客户端的命令异步执行
备忘录模式
将当亲对象的内部状态芭欧刺挠备忘录中,以便在需要时能将该对象的状态恢复到原先保存状态
发起人
记录当前时刻对象的内部状态,定义创建备忘录和恢复备忘数据的方法
备忘录
负责存储对象的内部状态
状态管理者
对备忘录的历史状态进行存储,定义了保存和获取备忘录状态的功能。
状态模式
状态模式指给对象定义不同的状态,并为不同的状态定义不同的行为,在对象的状态发生变化时自动切换状态行为
环境
上下文,用于维护对象当前的状态,并在对象状态发生变化时触发对象行为的变化
抽象状态
定义了一个借口,用于定义对象中不同状态所对应的行为
具体状态
实现抽象状态所定义的行为
访问模式
将数据结构和对数据的操作分离开来,在不改变数据结构的前提下动态添加作用于这些元素上的操作
抽象访问者
定义了一个访问元素的接口,为每类元素都定义了一个访问具操作visit(),该操作中,的参数类型对应被访问元素的数据类型
具体访问者
抽象访问者的实现类,实现了不同访问者访问到元素后具体的操作行为
抽象元素
元素的抽象表示,定义定义了访问该元素的入口的accept()方法
具体元素
实现抽象元素定义的accept()操作,并根据访问者的不同类型实现不同的业务逻辑
中介者模式
抽象中介者
中介接口,定义课注册同时对象方法和转发同时对象信息的方法
具体中介者
中介者接口的实现类,定义了一个list来保存同事对象,协调各个同时角色之间的交互关系
抽象同事类
定义同事类的借口,持有中介者对象,并定义同事对象交互的抽象方法,同时现实同事类的公共方法和功能
具体同时类
抽象同事类的实现者,在需要与其他同事对象交互时,通过中介者对象来完成
解释器模式
给定一种语言,并定义该语言的语法表示,然后设计一个解析器来解释语言中的语法
抽象表达
定义解器的接口
终结符表达式
非终结符表达式
环境
nginx网关
正向代理
正向代理就是一个位于客户端A和原始服务器B 之间的服务器(代理服务Z),为了从原始服务器内取得内容,用户A向代理服务器Z发送一个请求并指定目标(服务器B),然后代理服务器z向服务器B转交请求内容并且返回给客户端
配置说明
resolver 8.8.8.8
配置DNS解析ip地址,级超时时间
子主题 2
反向代理
客户端(用户A)向反向代理的命名空间(name-space)中的内容发送普通请求,接着反向代理将判断向何处(原始服务)转交请求,并将获得内容的内容返回给客户端。而客户端始终认为他访问的是原始服务器b
负载均衡
spring
测试模块Test
原理
解析Bean的定义信息
XML
properties
yaml
BeanDefinItionReader
定义规范,方便扩展,解析bean定义
beanDefinition
bean定义信息
BeanFatoryPostProcessor
BeanFactory
Bean工厂,整个容器的根接口,也是容器的入口,Construcotr ctor = class.getConstructor(); objet obj = ctor.newInstance();
实例化
填充属性
poulate
设置Aware属性
ApplicationContextAware
设置EnviormentAwares
创建类时继承Aware重写set方法进行对象的注入,当spring容器创建的bean对象在具体操作的时候,如果需要容器的其他对象,此时可以将对象实现Aware接口,来满足当前的需要
BeanPostProcessor.before
执行init-method方法
完整对象
context.getBean
Aop
ioc
Inversion of COntrol 控制翻转
应用场景
Setter方法注入
接口注入
构造方法注入
Beans模块
BeanFactory
接口定义了基本的IOC容器的规范..这个接口定义中,包括了getBean()这样的IOC容器的基本房
应用场景
可以使用转移符&来得到FactoryBean本身
用来区分通过容器获取FactoryBean产生的对象和获取FactorBean本身
方法
getBean()
获取需要的Bean的prototype类型
containsBean
isSingleton
isPrototype
isTypeMacth
getType
getAliases
子类
ListableBeanFactory
把里面的对象像枚举一样一一列出来
DefaultListableBeanFactory
这个基本Ioc容器的实现就是实现了ConfigurableBeanFactor从而成为一个简单Ioc容器的实现
getBean
HierarchicalBeanFactory
继承BeanFactory的基本接口之后,增加了getparentBeanFactory()接口功能,是BeanFactory具备了双亲Ioc容器的管理功能
XmlBeanFactory
FileSystemXmlApplicationContext创建
可以从文件系统载入Resource
ClassPathXmlApplicationContext
可以从ClassPath 载入Resource
XmlWebApplicationContext
可以在Web容器中载入Resource
ConfigurableBeanFatory
实现对应的接口来设置配置
setParentBeanFactory()设置双亲容器
addBeanPostProcessor()配置Bean后值处理器
AbstractBeanFactory
方法
doGetBean()
返回指定bean的实例,该实例可以是共享的,也可以是独立的。
transformedBeanName(name)
获取bean名称值
getSingleton(beanName)
提前检查单例中的缓存是否有手动注册的丹丽对象,跟着循环依赖有关系
isPrototypeCurrentlyInCreation(beanName)
当对象是单例的时候回尝试解决循环依赖问题,但是原型模式下如果存在循环依赖的情况,那么直接抛出异常
markBeanAsCreated(beanName)
做标致位
getSingletone(beanName,()->{return createBean(beanName,mbd,ars)});
beforeSingletonCreation(beanName)
记录当前象的加在状态
singletonFactory.getObject()
开始进行bean对象的创建,调用完后会调用createBean()
afterSingletonCreation(beanName)
移除缓存中对该bean的正在加在状态的记录
addSingleton(beanName,singletonObject)
加入到缓存中
getObjectForBeanInstance(sharedInstance,name,beanName,mbd)
获取FactoryBean
getObjectFromFactoryBean(factory,beanName,!synthetic)
doGetObjectFromFactoryBean
factory.getObject()
子类
AbstractAutowireCapableBeanFactory
方法
doGreateBean
createBeanInstance(String beanName, RootBeanDefinition mbd,@Nullable Object[] args)
创建bean的实例
// Candidate constructors for autowiring? Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
确定要为给定bean使用的候选构造函数,检查所有已注册的构造函数
autowireConstructor(beanName, mbd, ctors, args);
使用构造函数进行实例化操作
instantiateBean(beanName,mbd)
如果都没有的话就使用默认的构造器进行操作
getInstantiationStrategy()
获取实例化略
instantiate(RootBeanDefinition bd,@Nullable String beanName,BeanFactory owner)
BeanUtils.instantiateClass(constructorToUse);
开始对象的实例化
addSIngletonFactory(beanName,()->getEarlyBeanReference(beanName,mbd,bean));
解决循环赖
getEarlyBeanReference(String beanName,RootBeanDefinition mbd,Object bean)
获取对指定bean的早期访问的引用,*通常用于解析循环引用。
populateBean(beanName,mbd,instanceWrapper)
填充属性
initializeBean(beanName,exposedObject,mbd)
实例化bean
invokeAwareMehods(beanName,bean)
实例化bean
applyBeanPostProcessorsBeforeInitialization(wrapedBean,beanName);
执行beanPostProcessor
invokeInitMethos(beanName,wrappedBean,mbd)
初始化方法
registerDispsableBeanIfNecessary(beanName,bean,mbd)
销毁bean的方法
DefaultSingleBeanRegistry
singletonObjects
一级缓存
earlySingletonObjects
二级缓存
singletonFactories
三级缓存
ApplicationContext上下文new ClassPathXmlApplicationContext(String configLoation)
应用场景
支持不同的信息源
扩展MessageSource接口
访问资源
可以从不同的地方得到Bean定义资源,这种抽象使用户程序可以灵活地定义Bean定义信息,尤其是从不同的I/O途径的到Bean定义信息。
支持应用事件
集成了接口ApplicatinEventPublisher,从而在上下文中引入事件机制。这些事件和Bean的生命周期的结合为Bean的管理提供了便利
在ApplicationContext中提供的附加服务
设计原理
FileSystemXmlApplicationContext设计
子类
FileSystemXmlApplicaitonContext
从文件系统载入Resource
AbstractXmlApplicationContext
getEnvironment()
获取环境量
protected ConfigurableEnviroment createEnviroment(){ return new StrandardEnviroment(); }
创建环境量
AbstractRefreshableConfigApplicationContext
setConfigLocations(@Nullable String .. locations)
设置配置资源路径 public void setConfigLocations(@Nullable String... locations){ if(locations !=null){ this.configLocations = new String[locations.lentgh]; for(int i=0;i //解析给定的路径 this.configLocatins[i] = resolvePath.locations[i].trim(); } } }
protected String resovlePath(String path){ retrun getEnviroment().resolveRequiredPlaceholders(path); }
解析给定的路径
ClassPathXmlApplicationContext
可以从ClassPath在入Resouce
方法
super(parent)
调用父类的构造方法
setConfigLocation(configLocation)
设置配置资源路径 public void setConfigLocations(@Nullable String... locations){ if(locations !=null){ this.configLocations = new String[locations.lentgh]; for(int i=0;i //解析给定的路径 this.configLocatins[i] = resolvePath.locations[i].trim(); } } }
refresh()
XmlWebApplicationContext
可以再Web器中载入Resource
configurableApplicationContext
子类
方法
AbstractApplicationContext
属性
ObjectUtils.identityToString(this);
创建上下文的唯一标识
List<BeanFactoryPostProcessor>beanFactoryPostPorcessors = new ArrayLIst<>();
增强或者修改bean信息的定义的集合
startupShutdownMonitor
锁
方法
AbstractApplicationContext(){this.resourcePatternResolever = getResourcePatternResolver()}
创建资源式处理器
protected ResourcePatternResolver getResourcePatterResolver(){ return new PathMatchingResourcePatternResolver(this); }
创建一个资源解析器(其实就是用来解析xml配置的)
public AbstractApplicationContext(@Nullable ApplicationContext parent){ this(); setParent(parent); }
public void setParent(@Nullable ApplicationContext parent){ this.parent = parent; if(parent !=null){ Environment parentEnvironment = parent.getEnviroment(); if()...... } }
如果父容器位部位空则获取父容器对象,如果父容器的环境对象为可配置对象,合并对象
refresh()
prepareRefresh()
做容器新前的准备工作 1.设置容器的启动时间 2.设置活跃状态为true 3.设置关闭状态为false 4.获取Envionment对象,并加载当前系统的属性值到Environment对象中 5.准备监听器和事件的集合对象,默认为空的集合
initPropertySource
初始化属性资源
getEnvironment().validateRequiredProperties()
获取环境配置验证环境配置
createEnviroment()
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory()
创建容器对象,DefaultListableBeanFactory,加在xml配置文件的属性值到当前工厂中,最重要的就是BeanDefinition
refreshBeanFactory()
刷新beanFactory
hasBeanFactory
判断是否有BeanFactory
destroyBeans()
如果有BeanFactory则销毁bean
closeBeanFactory
关闭bean工厂
DefaultListableBeanFactory beanFactory = createBeanFactory()
创建Bean工厂
new DefaultListableBeanFactory(getInternalParentBeanFactory())
customizeBeanFactory
自定义Bean工厂 beanFactory.setAllowBeanDefinitionOverrding(this.allowBeanDefinitionOverriding);//设置Bean定义是否覆盖 beanFactory.setAllowCircularReferences(this.allowCircularReferences);//设置循环依赖
loadBeanDefintions(beanFactory)
加载bean属性值,解析bean xml
prepareBeanFactory(beanFactory)
给当前的bean工厂设置某些属性值,完成bean工厂的初始化操作
beanFactory.setBeanClassLoader(getClassLoader());
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
设置beanPostProcessor
beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
beanFactory.registerResolvableDependency(BeanFactory.class,beanFactory);
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));
postProcessBeanFactory(beanFactory)
invokeBeanFactoryPostProcessors(beanFactory)
实例化并且行所有已经注册了的BeanFactoryPostProcessor beans,必须在单例实例化之前调用
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory,getBeanFactoryPostProcessors);
registerBeanPostProcessors(beanFactory)
实现和注册beanPostProcessor
initMessageSource()
初始化消息资源
initApplicationEventMulitcaster()
初始化应用程序多播器
onRefresh()
registerListeners()
注册监听器
finishBeanFactoryInitialization(beanFactory)
实例化所有懒加载的对象
beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
处理当前的环境变量,加载属性值
String[] weaverAwareNames = beanFactory .getBeanNamesForType(LoadTimeWeaverAware.class,false,false);
设置织入
beanFactory.freezeConfiguration
beanFactory.preInstantiateSingletons()
实例化所有的非懒加载单例对象
getMergedLocalBeanDefinition
把父类和子类bean的描述信息都做一个整合
getBean()
finishRefresh()
子类
AbstractXmlApplicationContext
方法
loadBeanDefintions(beanFactory)
Resource[] configResources = getConfigResource(); if(confiResource !=null){ reader.loadBeanDefinitions(configResources); } String[] configLocations = getConfigLocations(); if(configLocations != null){ reader.loadBeanDefinitions(configLocations); }
成员变量
private boolean validating = true
设置xml文件的1验证标志,默认为true
设计原理
XmlBeanFactory
XmlBeanFactory初始化了一个XmlBeanDefinitionReader对象,有了这个Reader对象,那些以xml方式定义的BeanDefinition就有了处理的地方。我们可以看到,对这些Xml形式处理是由这个XmlBeanDefinitionReader
DefaultListableBeanFactory
ResourceLoader
方法
子类
ResourcePatternResolver
方法
子类
PathMathcingResourcePatternResolver
成员变量
方法
private PatchMatcher patchMatcher = new AntPathMatcher()
ant风格路径的路径匹配器
InstantiationStrategy
子类
SimpleInstantiationStrategy
方法
instantiate(RootBeanDefinition bd,@Nullable String beanName,BeanFactory owner)
BeanDefinitionReader
这个载入过程是把用户定义好的bean表示成IOC容器内部的数据结构,而这个容器内部的数据结构就是BeanDefinition.
AbstractBeanDefinitionReader
方法
loadBeanDefinitions
Bean的配置
id唯一标识符
name:为bean制定多个名称
class:制定了bena的具体实现类
scope:设置bean实例的作用域
singleton:单例
prototype:原型
constructor-args:传入参构造参数进行实例初始化
index:制定构造参数的序列号
type属性制定构造参数类型
ref或者value制定参数值
property
制定bean中的属性
ref或者value指定值
list
set
map
entry:<map>元素的资子元素
Bean的实例化
构造器实例化
通过xml文件<bean>注解配置对象使用getBean方法获取
静态工厂实例化
使用bean中的class方法指定静态工厂类
实例工厂方法实例化
factory-bean属性指向配置的实例工厂
factory-method属性确定使用工厂中的哪个方法
Bean的作用域
作用域种类
singletons单例模式
prototype原型
每次通过spring容器获取prototype定义的bean时都会获取一个新的bean
request
不同的http请求会返回一个不同的bean
session
一次session中产生一个实例
globalSession
application
websocket
Bean的生命周期
1.实例化
createBeanInstance
2.属性赋值
populateBean
3.初始化
initializeBean
4.销毁
扩展点
BeanPostProcessor
InstantiantionAwareBeanPostProcessor
Ioc容器的初始化过程
Ioc容器的初始化是由前面介绍的refresh()方法启动,这个启动包括BeanDefinition的Resource定位,载入和注册
过程
Resource定位过程
指的是BeanDefinition的资源定位,它是由ResourceLaoder通过同一的Resrouce接口完成
BeanDefinition载入
这个载入过程是吧用户定义好的Bean
BeanDefinition的Resource定位
向Ioc容器中注册这些BeanDefinition
这个过程是通过调用BeanDefinitionRegistry接口的实现来完成的。这个注册过程把载入过程中解析得到的BeanDefinition向Ioc容器进行注册
例子
ResourceLoader
BeanDefinitionReader
这个载入过程是把用户定义好的bean表示成IOC容器内部的数据结构,而这个容器内部的数据结构就是BeanDefinition.
AbstractBeanDefinitionReader
方法
loadBeanDefinitions
DI
Dependency Inversion Principle 依赖倒置原理
Aspect
核心容器
Bean模块
core核心模块
context上下文
context-support ,提供对第三方库的嵌入
SpEL
数据访问/集成
jdbc
orm模块
JPA
JDO
Hibernate
OXM模块
JMS
Transactionss
Web应用层
web
提供了面向Web应用的基本功能,还提供了HTTP超文本传输协议客户端及Spring远程调用中与Web相关的部分。
Web-MVC
web-MVC提供了模型识图控制(model-view-controller ,MVC)和Rest Api服务的实现。spring的mvc框架使数据模型和视图分离,数据模型负责数据的业务逻辑,视图负责数据的展示。同时,Web-MVC可与Spring框架的其他模块方便集成
Web-socket
Web-Socket模块提供了对WebSocket-Base的支持,用于实现在web应用程序中服务端和客户端时双向通信,尤其在实时消息推广中应用广泛
web-porlet
提供了基于Porlet环境的MVC实现,并提供了与Spring Web-MVC模块相关的功能
spring注解
spring注解的使用
1.导入命名空间及规范
2.配置扫描包
3.使用注解
spring的常用注解
bean的声明
@Componet
@Service
@Repository
@Controller
bean注入
@Autowired
@Resource
配置类注解
@Configuration
声明该类为配置类,其中@Value属性可以直接和配置文件属性映射
@Bean
注解在方法上,声明该方法的返回值为一个bean的实例
@ComponentScan
用于对Component进行扫描配置
AOP注解
@EnableAspectJAutoProxy
开启Spring对AspectJ代理的支持
@Aspect
声明一个切面,使用@After,@Before,@Around定义同志(Advice),可直接将拦截规则(切点)作为参数
@Before
在方法执行以前执行
@Around
在方法执行之前和之后都执行
@PointCut
声明一个切点
@Bean属性支持注解
@Scope
设置spring容器bean实例的生命周期,取值有singleton,prototype,request,session和global session
@PostConstruct
声明方法在构造函数知心完之后开始执行
@PreDestroy
声明方法在Bean销毁之前执行
@Value
为属性注入值
@PropertySource
声明和加载配置文件
异步操作注解
@EnableAsync
声明在泪伤,开启对异步任务的支持
@Async
声明方法是一个异步任务,Spring后台基于县城池异步执行该方法
定时任务相关
@EnableScheduling
声明在类上,开启对异步任务的支持
@Scheduled
声明一个定时任务,包括cron、fixDelay、fixRate等参数
开启功能支持
@EnableAspectJAutoProxy
开启对AspectJ自动代理
@EnableWebMvc
Hibernate
Mybatis
mybatis工作原理
1.读取Mybatis配置文件mybatis-config.xml
2.加载映射文件Mapper.xml
3.构造会话工厂
4.构造会话对象
5.Executor执行器
6.MapperdStatement对象
7.输入映射
8.输出映射
myBatis核心对象
SqlSessionFactory
是单个数据库映射关系进过编译后的内存镜像,通过SqlSeesionFactoryBuilder对象来构建
是线程安全的,一旦被创建,真个执行期间都会存在
SqlSession
单线程对象,用于执行持久化操作
包含了所有sql操作的方法,底层封装了jdbc
selectOne(String statement)
selectOne(String statement,Object parameter)
selectList(String statement)
selectList(String statement,Object parameter
selectList(String statement,Object parameter,RowBounds rowBounds)
void select(String statement,Object parameter,ResultHandler handler)
int insert(String statement)
int insert(String statement,Object parameter)
int update(String statement)
int delete()
void commit()
void rollback()
void close()
getMapper(Class<T> type)
mybatis配置文件
将mybatis内部配置外在化
cacheEabled
配置所有映射器中缓存的全局开关
lazyLoadingEnabled
为true时所有关联对象都会延迟加载
aggressiveLazyLoading
关联对象属性的延迟加载开关
mulitpleResulSetsEnabled
是否允许单一语句返回多个结果集
useColumnLabel
使用列标签代替列名
useGeneratedKeys
允许jdbc自动生成主键
autoMappingBehavior
NONE表示取消自动映射,PARTAL只会自动映射没有定义嵌套结果集映射的结果集,FULL会自动映射任意复杂的结果集
defaultExecutorType
配置默认的执行器,SIMPLE是普通执行器,REUSE执行器会重用预处理语句BATCH执行器讲重用语句并执行批量更新
defaultStatementTimeout
设置超时时间,当没有设置的时候,它取得就是驱动的默认时间
mapUnderscoreToCamelCase
设置是否开启驼峰命名
jdbcTypeForNull
当没有参数提供特定的JDBC类型时,为空值指定JDBC类型.(NULL,VARCHAR,OTHER)
设置别名 1.使用配置文件定义别名 2.使用注解定义别名 @Alias(value="user")
将预处理语句中传入的参数从javaType(java类型)转换为jdbcType(jdbc类型)或者反过来
由DefaultObjectFactory来提供服务 1.自定义工厂 2.在配置文件中使用
属性type
JDBC
MANAGED
从来不提交或者回滚一个连接,而是让容器来管理事物的整个生命周期
属性type
UNPOOLED
diver
url
数据库的映射地址
username
password
defaultTransactionlsolationLevel
POOLED
poolMaximunActiveConnections
正在使用的连接数量,默认值是10
poolMaximumldleConnections
任意时间可能存在的空闲连接数
poolMaximumCheckoutTime
在被轻质返回之前,池中连接被检测时间
poolTimeToWait
如果获取连接花费的时间比较长,它会给连接池打印状态日志并重新尝试获取连接
poolPingQury
发送到数据库的侦查寻,用于检测连接是否处于正常工作秩序中
poolPingEnable
是否开启帧查询
poolPingConnectionsNotUsedFor
配置poolPingQuery的使用频度,可以被设置成匹配具体的数据库超时时间.
JNDI
initial_context
此属性主要用于在InitialContext中寻找上下问
date_source
引用数据源实例位置的上下文的路径
default属性
1.使用类路劲引入
2.使用本地文件路径引入
3.使用接口类引入
4.使用包名引入
mybatis映射文件
id
命名空间的唯一标识
parameterType
传入SQL语句的参数类的全限定名称或者别名.mybatis可以通过TypeHandler推断出具体传入语句的参数.默认值为unset.
resultType
从SQL语句中返回的类型的类的全限定名或者别名
flushCache
表示在调用SQL语句后,是否需要MyBatis清空之前查询的本地缓存和二级缓存
userCache
用来控制二级缓存的开启和关闭
timeout
fetchSize
用于获取记录的总条数设定
statementType
用于设置Mybatis使用哪个JDBC的statemnt工作
resultSetType
表示结果集的类型
keyProperty
讲插入或者更新操作时的返回值复制给PO类的某个属性通常设置主键为对应的属性.如果设置联合主键,就用逗号隔开
keyColumn
用于设置第几列是主键,当主键列不第一列时需要设置
useGeneratedKeys
仅对insert和update有用,此属性会使Mybati使用JDBC的getGeneratedKeys()方法获取由数据库内部产生的主键
keyProperty
resultType
order
BEFORE
如果设置为before会首先执行元素中的配置来设置主键,如果设置after会先执行插入语句
AFTER
statementType
定义一部分sql,然后可以被其投资语句引用
给定义命名空的缓存配置
其他命名空间的缓存配置引用
type
id
ID参数,标记结果作为ID
注入到构造方法的一个普通结果
主键
注入到字段或javabean属性的普通结果
用于一对一关联
用于一对多关联
基于某些值的结果映射
动态sql
判断语句用于单条分支的判断
<choose>(<when>,<otherwise>)
where 1= 1 and
prefix
去除语句的前缀
prefixOverrides
去除那些多余的字符
item
配置的是循环中当前的元素
index
配置的是当前元素在集合的位置下标
collection
open,close
配置是以什么符号将这些集合元素包装起来的
separator
配置是各个元素的间隔符号
bind
mybatis关联关系映射
property
映射到的实体类对象属性
column
表中对应的字段
javaType
指定映射的实体对象属性的类型
select
指定引入嵌套查询的子sql语句,该属性用于关联映射中的嵌套查询
fechType
在关联查询时是否启用加载延迟
oftype
对应javaType属性
Mybatis自动生成工具
JHipster
是一个应用代码生成器,能够创建SpringBoot + AngularJs应用
设置环境
安装方法
使用NPM
npm install -g generator -jhipster
npm install -g yo
使用YARN
yarn global add generator -jhipster
使用DOCKER
docker image pull jhipster/jhipster:master
拉取开发版本的镜像
运行镜像
mkdir ~/jhipster
docker container run --name jhipster -v ~/jhipster:/home/jhipster/app -v ~/.m2:/home/jhipster/.m2 -p 8080:8080 -p 9000:9000 -p 3001:3001 -d -t jhipster/jhipster
将容器中的”/home/jhipster/app”文件夹路径共享到宿主机本地的”〜/jhipster”路径 转发Docker公开的所有端口(Java应用程序为8080,BrowserSync为9000,BrowserSync UI为3001)
docker container ps
检查您的容器是否正在运行
docker container stop jhipster
停止容器
docker container start jhipster
并再次启动
进入容器
docker container exec -it jhipster bash
如果在容器中复制粘贴了以上命令运行,必须将容器名称指定为jhipster
docker container exec -it --user root jhipster bash
如果您想以”root”身份登录,但因为sudo命令在Ubuntu Xenial中是不可用,则需要运行:
cd /home/jhipster/app
jhipster
./mvnw
核心工作
创建应用
创建一个生产应用程序的空目录:
mkdir myapplication
转到该目录:
cd myapplication/
要生成您的应用程序
jhipster
创建实体类
需要项目
数据库表
Liquibase变更集
JPA实体
Spring Data JPA Repository
Spring MVC REST 控制器,具有基本的CRUD操作
Angular路由器,组件和服务
HTML视图
集成测试
性能测试
命令行
--table-name <table_name>
默认情况下,JHipster将根据您的实体名称生成一个表名,如果您希望使用其他表名,则可以通过传递此选项来实现。
--angular-suffix <suffix>
- 如果您希望所有Angular路由都具有自定义后缀,则可以使用此选项传递该后缀。
--client-root-folder <folder-name>
指定前端侧实体使用的根文件夹名称。对于富应用和微服务中的网关,默认情况下为空。
--angular-suffix <suffix>
如果您希望所有Angular路由都具有自定义后缀,则可以使用此选项传递该后缀
--client-root-folder <folder-name>
指定前端侧实体使用的根文件夹名称。对于富应用和微服务中的网关,默认情况下为空。
--regenerate
不询问任何问题重新生成现有实体
--skip-server
- -这将跳过服务器端代码,仅生成前端代码。
--skip-client
这将跳过前端代码,仅生成服务器端代码。
--skip-db-changelog
这将跳过数据库更改日志的生成(对于SQL数据库使用Liquibase)。
--db
-跳过服务器端生成时,指定的数据库,其他时候无效。
创建Controller
jhipster spring-controller Foo
Foo spring mvc rest控制器
创建Service
jhipster spring-service Bar
创建DTO
配置实体类关系
JHipsterUML和JDL Studio
双向一对多关系
Owner (1) (*) Car
jhipster entity Owner
jhipster entity Car
entity Owner entity Car relationship OneToMany { Owner{car} to Car{owner} }
双向多对一关系
entity Owner entity Car relationship ManyToOne { Car{owner} to Owner{car} }
单向多对一关系
Owner (1) <----- (*) Car
微服务
架构图
Gataway ------------------------------- Angular Application Zuul proxy Security Rate limiting
Microservice1 --------------------- Spring boot
Microservice2 --------------------- Spring boot
JHipster Registry ------------------------- Eureka Spring Cloud Config Monitoring dashboards
JHipster Console --------------------------- Elasticsearch /Logstash/Kibana
API网关
HTTP请求使用网关进行路由
启动网关和微服务后,它们将在registry中注册自己(使用src/main/resources/config/application.yml文件中的eureka.client.serviceUrl.defaultZone项)。
JWT
JHipster使用Okta提供的JJWT library来实现JWT。 令牌由网关生成,并发送到底层微服务:由于它们共享一个公共密钥,因此微服务能够验证令牌并使用该令牌对用户进行身份验证。
为了确保安全,必须在所有应用程序之间共享JWT秘密令牌。
对于每个应用程序,默认令牌是唯一的,由JHipster生成。它存储在.yo-rc.json文件中。
使用src/main/resources/config/application.yml文件中的jhipster.security.authentication.jwt.secret密钥配置令牌。
要在所有应用程序之间共享此密钥,请将密钥从网关复制到所有微服务,或使用JHipster Registry的Spring Config Server或JHipster的Consul K / V存储的特定配置进行共享。这是人们使用这些中心配置服务器的主要原因之一
自动文档
网关暴露了它所代理服务的Swagger API,许多工具依赖此特性,例如Swagger UI和swagger-codegen。 网关的”admin > API”菜单具有特定的下拉列表,其中显示了网关的API以及已注册的微服务中的所有暴露API。
限速
网关提供速率限制功能,因此可以限制REST请求的数量: 通过IP地址(对于匿名用户) 通过用户登录(对于已登录的用户) 要启用速率限制,请打开application-dev.yml或application-prod.yml文件,并将enabled设置为true: jhipster: gateway: rate-limiting: enabled: true
访问控制策略
JHipster注册中心
springMvc
工作流程
DispatcherServlet
dispatcher org.springframework.web.servlet.DispatcherServlet
param-value
1表示立即加载 1
HandlerAdapter
Handler
处理器
ViewResolver
视图解析器
view
注解
@Controller
不需要实现Controller接口 1、在配置文件的申明中引入spring-context 2、使用元素指定需要扫描的类包
@RequestMapping
value
指定请求的实际地址,指定的地址可以是URI Template模式
method
指定请求的method类型,GET、POST、PUT、DELETE
consumes
制定处理请求的提交内容类型Content-Type),例如application/json, text/html; @RequestMapping(value = "/pets", method = RequestMethod.POST, consumes="application/json")
produces
指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回; @RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, produces="application/json")
params
指定request中必须包含某些参数值是,才让该方法处理。 @RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, params="myParam=myValue")
headers
指定request中必须包含某些指定的header值,才能让该方法处理请求。 @RequestMapping(value = "/pets", method = RequestMethod.GET, headers="Referer=http://www.ifeng.com/")
组合注解
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
重定向
redirect:queryUser
forward请求转发
forward:editUser
ViewResolver(视图解析器)
数据绑定
简单数据绑定
绑定默认数据类型
Model/ModelMap
HttpSession
HttpServletResponse
HttpServletRequest
绑定简单数据类型
@RequestParam
value
name属性的别名,请求参数的名字
name
请求投绑定的名称
required
用于制定参数是否必须
defaultValue
绑定POJO类型
传入实体类
绑定包装POJO类型
在一个类中包装另一个类
自定义数据绑定
Converter
用于讲一种类型的对象转换为另一种类型的对象,自定义Converter类需要实现org.springframework.core.convert.converter.Converter借口 1、首先添加3个mvcde1schema信息, 2、然后定义了组件扫描器和视图解析器 3、显示装配了自定义的类型转换器, 4、最后编写了自定义类型转换器的配置
显示装配自定义类型转换器
Formatter
print()
返回目标对象的字符串
parse()
方法会利用制定的Locale将一个String解析程目标类型
复杂数据的绑定
绑定数组
List集合
绑定集合
集合中是对象
JSON数据交互
HttpMessageConverter<T>
MappingJacksonH2HttpMessageConverter
是springMvc默认处理Json格式请求响应的实现类
@RequestBody
@ResponseBody
配置josn转换器
使用<bean>标签方式的JSON转换器配置
配置静态资源的访问方式
location
定位需要访问的本地静态资源路径
mapping
匹配静态资源路全路径
使用<mvc:default-servlet-handler>
会在springmvc上下文中定义一个org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler
激活Tomcat默认的Servlet来处理静态文件访问
default *.js
RESTFUL支持
@PathVariable
拦截器
HandlerInterceptor
preHandle()
在控制器方法前执行,返回值表示是否中断,
postHandle()
在控制的方法调用后,解析视图之前
afterCompletion()
整个请求完成之后
WebRequestInterceptor
拦截器的配置
基子元素中定义的是全局拦截器,它会拦截所有的请求
请求路径中包含不需要拦截的内容
单个拦截器的执行流程
多个拦截器的执行流程
子主题 1
文件上传和下载
权限
shiro
https://blog.csdn.net/kity9420/article/details/88909426
简介
Shiro是Apache 旗下的一个简单易用的权限框架,可以轻松的完成 认证、授权、加密、会话管理、与 Web 集成、缓存等
Subject
主体,代表了当前 “用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager
SecurityManager
安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;它是 Shiro 的核心,它负责与他组件进行交互,可以把它看成 DispatcherServlet 前端控制器
Realm
域,Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源 原文链接:https://blog.csdn.net/kity9420/article/details/88909426
spring整合shiro
1.在pom.xml文件中引入对Shiro的依赖
shiro-core
shiro-web
shiro-ehcache
shiro-quartz
shiro-spring
2.在config目录下创建Shiro配置文件spring-shiro-web.xml
自定义凭证(密码)匹配器
自定义登录验证过滤器
Shiro的web过滤器
3.修改web.xml文件将shiro集成到工程中
4.定义realm
1.Shiro进行身份验证时,会调用到doGetAuthenticationInfo方法,在方法内部,我们通过UsernamePasswordToken 获得用户传过来的用户名,再通过userService.selectUserByUserName方法从数据库中查询用户信息,如果用户为空,说账号不存在,否则将查询出来的用户名及密码,封装到SimpleAuthenticationInfo 对象中,并返回,用于接下来的密码验证
2.Shiro角色权限验证,会调用doGetAuthorizationInfo方法,通过SimpleAuthorizationInfo.setRoles()方法设置用户角色,通过SimpleAuthorizationInfo.setStringPermissions()设置用户权限,这里暂时给个空集合,在项目中,用户的角色权限需要从数据库中查询
5.自定义凭证(密码)匹配
6.自定义登录验证过滤器
1.shiro内置Filter
在没有自定义的情况下,默认如 anon表示使用org.apache.shiro.web.filter.authc.AnonymousFilter处理 authc表示使用org.apache.shiro.web.filter.authc.FormAuthenticationFilter处理 ———————————————— 版权声明:本文为CSDN博主「longwentao1999」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/kity9420/article/details/89067794
anon
authc
authcBasic
logout
noSessionCreation
perms
port
rest
roles
ssl
user
2.自定义filter
ShiroFilterFactoryBean
filterChainDefinitions
定义了url的访问规则,从源码中可以看到,这些规则最后存储在shiroFilterFactorybean的
filterChainDefinitionMap
key值是url规则,value值是filte的引用,表示使用那些filter处理URL
filter
属性filters是一个map集合,key值是filter name,value值是具体的filter类
注解权限验证
在配置文件汇总增加配置、
验证
创建权限表
角色表
角色权限表
用户角色权限关系表
自定义UserShiroRealm
自定义UserShiroRealm,继承AuthorizingRealm,重写doGetAuthorizationInfo方法,在方法中查询用户的所有权限,并交给Shiro进行权限验证
编写服务
服务名称:增加用户
权限要求:普通用户角色的新增权限 或 区域管理员的新增权限才可访问
logical=Logical.OR 表示或,满足其中一个权限即可访问 logical=Logical.AND 表示与,满足所有权限才可访问
注解
@RequiresAuthentication
验证用户是否登录,等同于方法subject.isAuthenticated() 结果为true时。
@RequiresUser
一种是成功登录的(subject.isAuthenticated() 结果为true); 另外一种是被记忆的(subject.isRemembered()结果为true)。
@RequiresGuest
@RequiresRoles
例如:@RequiresRoles(“aRoleName”); void someMethod(); 如果subject中有aRoleName角色才可以访问方法someMethod。如果没有这个权限则会抛出异常AuthorizationException。
@RequiresPermissions
例如: @RequiresPermissions({“file:read”, “write:aFile.txt”} ) void someMethod(); 要求subject中必须同时含有file:read和write:aFile.txt的权限才能执行方法someMethod()。否则抛出异常AuthorizationException。 ———————————————— 版权声明:本文为CSDN博主「longwentao1999」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/kity9420/article/details/89198613
注解验证异常处理
登录认证异常
UnauthenticatedException
AuthenticationException
权限认证异常
UnauthorizedException
AuthorizationException
SpringSecurity
权限认证,过滤器和拦截器
简介
WebSecurityConfigurerAdapter
自定义的Security策略
AuthenticationManagerBuilder
自定义认证授权策略
@EnableWebSecurity
开启WebSecurity模式
configure
/** * 通过 {@link #authenticationManager()} 方法的默认实现尝试获取一个 {@link AuthenticationManager}. * 如果被复写, 应该使用{@link AuthenticationManagerBuilder} 来指定 {@link AuthenticationManager}. * * 例如, 可以使用以下配置在内存中进行注册公开内存的身份验证{@link UserDetailsService}: * * // 在内存中添加 user 和 admin 用户 * @Override * protected void configure(AuthenticationManagerBuilder auth) { * auth * .inMemoryAuthentication().withUser("user").password("password").roles("USER").and() * .withUser("admin").password("password").roles("USER", "ADMIN"); * } * * // 将 UserDetailsService 显示为 Bean * @Bean * @Override * public UserDetailsService userDetailsServiceBean() throws Exception { * return super.userDetailsServiceBean(); * } * */ protected void configure(AuthenticationManagerBuilder auth) throws Exception { this.disableLocalConfigureAuthenticationBldr = true; } /** * 复写这个方法来配置 {@link HttpSecurity}. * 通常,子类不能通过调用 super 来调用此方法,因为它可能会覆盖其配置。 默认配置为: * * http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic(); * */ protected void configure(HttpSecurity http) throws Exception { logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity)."); http .authorizeRequests() .anyRequest().authenticated() .and() .formLogin().and() .httpBasic(); }
HttpSecurity
openidLogin()
用于基础于OpenId的验证
headers()
将安全标头添加到响应
cors()
配置跨域资源共享(CORS)
sessionManagement()
允许配置会话管理
portMapper()
允许配置一个PortMapper(HttpSecurity#(getSharedObject(class))),其他提供SecurityConfigurer的对象使用 PortMapper 从 HTTP 重定向到 HTTPS 或者从 HTTPS 重定向到 HTTP。默认情况下,Spring Security使用一个PortMapperImpl映射 HTTP 端口8080到 HTTPS 端口8443,HTTP 端口80到 HTTPS 端口443 ———————————————— 版权声明:本文为CSDN博主「盡盡」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_22172133/article/details/86503223
jee()
配置基于容器的预认证。 在这种情况下,认证由Servlet容器管理
x509()
配置基于x509的认证
rememberMe()
允许配置“记住我”的验证
authorizeRequests()
允许基于使用HttpServletRequest限制访问
requestCache()
允许配置请求缓存
exceptionHandling()
允许配置错误处理
securityContext()
在HttpServletRequests之间的SecurityContextHolder上设置SecurityContext的管理。 当使用WebSecurityConfigurerAdapter时,这将自动应用
servletApi()
将HttpServletRequest方法与在其上找到的值集成到SecurityContext中。 当使用WebSecurityConfigurerAdapter时,这将自动应用
csrf()
添加 CSRF 支持,使用WebSecurityConfigurerAdapter时,默认启用
logout()
添加退出登录支持。当使用WebSecurityConfigurerAdapter时,这将自动应用。默认情况是,访问URL”/ logout”,使HTTP Session无效来清除用户,清除已配置的任何#rememberMe()身份验证,清除SecurityContextHolder,然后重定向到”/login?success” ———————————————— 版权声明:本文为CSDN博主「盡盡」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_22172133/article/details/86503223
anonymous()
允许配置匿名用户的表示方法。 当与WebSecurityConfigurerAdapter结合使用时,这将自动应用。 默认情况下,匿名用户将使用org.springframework.security.authentication.AnonymousAuthenticationToken表示,并包含角色 “ROLE_ANONYMOUS”
formLogin()
指定支持基于表单的身份验证。如果未指定FormLoginConfigurer#loginPage(String),则将生成默认登录页面
oauth2Login()
根据外部OAuth 2.0或OpenID Connect 1.0提供程序配置身份验证
requiresChannel()
配置通道安全。为了使该配置有用,必须提供至少一个到所需信道的映射
httpBasic()
配置 Http Basic 验证
addFilterAt()
在指定的Filter类的位置添加过滤器
特点
功能权限
访问权限
菜单权限
拦截器,过滤器
springboot
创建第一个工程
工程目录
- src -main -java -package -SpringbootApplication -resouces - statics - templates - application.yml -test - pom
启动springboot方式
mvn clean mvn package 编译项目的jar
RestTemplate
GET
POST
postForEntity
该方法同GET请求中的getforEntity类似,会在调用后返回ResponseEntity对象,其中T为请求响应的body类型,
postForEntity(String url,Object request,Class responseType,Object .... uriVariables)
postForEntity(String url,Object request,Class responseType,Map uriVariables)
postForEntity(URI url,Object request,Class responseType)
postForObject
简化了postForEntity的后续处理,通过直接请求响应的body内容包装成对象返回来使用
postForObject(String url,Object request,Class responseType,Object.... uriVariables)
postForObject(String url,Object request,Class responseType,Map uriVariables)
postForObject(URI url, Object request, Class responseType)
postForLocation
方法实现了以POST请求提交资源,并返回新的资源的URI
postForLocation(String url, Object request, Object... uriVariables)
postForLocation(String url, Object request, Map uriVariables)
postForLocation(URI url, Object request)
springboot整合rabbitMq
springboot整合rocketMq
jackson日期格式话问题
异常原因
Springboot默认使用的json解析框架是jackson框架
jackson解析框架在解析实体类里面是Date数据类型的数据时的默认格式是:UTC类型,即yyyy-MM-dd’T’HH:mm:ss.SSS 并且默认为+8时区,即时间基础上加8小时
解决方案
将spring的jackson日期格式写在配置文件中即可
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 serialization: write-dates-as-timestamps: false
在实体Date类型的字段上使用@JsonFormat注解格式化日期
/** * 创建时间 */ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date createTime;
通过代码的方式取消timestampes形式
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
springData
Spring Data的使命是为数据访问提供熟悉且一致的基于Spring的编程模型,同时仍保留底层数据存储的特殊特性。 它使数据访问技术,关系数据库和非关系数据库,map-reduce框架和基于云的数据服务变得简单易用。这是一个伞形项目,其中包含许多特定于给定数据库的子项目。这些项目是通过与这些激动人心的技术背后的许多公司和开发人员合作开发的。 ———————————————— 版权声明:本文为CSDN博主「白衬衫猿」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_32328959/article/details/88293914
特征
强大的存储库和自定义对象映射抽象
从存储库方法名称派生动态查询
实现域基类提供基本属性
支持透明审核(创建,最后更改)
可以集成自定义存储库代码
通过JavaConfig和自定义XML命名空间轻松实现Spring集成
与Spring MVC控制器的高级集成
跨存储持久性的实验支持
主要模块
Spring Data common
支持每个Spring Data模块的Core Spring概念
独立使用Spring Data
RepositoryFactorySupport factory = … // Instantiate factory here UserRepository repository = factory.getRepository(UserRepository.class);
Repository -dao -存储仓库
Repository
CurdRepository
JpaRepository
指定spring data 模块
存储库
实体类
定义查询方法的配置方法
从方法命中可以指定特定用于存储的查询和更新
查询方法的拆分
findBy
deleteBy
And
Or
Between
LessThan
LessThanEqual
GreaterThan
After
Before
IsNull
IsNotNull,NotNull
Like
NotLike
StartingWith
EndingWith
Containing
OrderBy
Not
In
NotIn
使用@Query手动自定义查询接口
自定义查询
Repository fragments
1.做出一个自己的接口
interface CustomizedUserRepository { void someCustomMethod(User user); }
2.实现接口,就是很普通的接口实现
class CustomizedUserRepositoryImpl implements CustomizedUserRepository { public void someCustomMethod(User user) { // Your custom implementation } }
使用命名空间来配置定义存储片段Bean
在XML和Java Config中的配置base-package,Spring Data的基础架构会在启动的时候到配置的路径下去扫描对应的存储库包,找到实现并配置为Bean。
@EnableJpaRepositories(basePackages = "cn.marer", repositoryImplementationPostfix = "DAO") public class ....
自定义BaseRepository
class MyRepositoryImpl extends SimpleJpaRepository { private final EntityManager entityManager; MyRepositoryImpl(JpaEntityInformation entityInformation, EntityManager entityManager) { super(entityInformation, entityManager); // Keep the EntityManager around to used from the newly introduced methods. this.entityManager = entityManager; } @Transactional public S save(S entity) { // implementation goes here } }
继承接口
JpaRepository
PagingAndSortingRepository
CrudRepository
JpaSpecificationExectuor
List<T> findAll(Specification<T> spec)
Specification<T>
toPredicate(Root<T> root),CriteriaQuery query,CriteriaBuilder cb)
QueryByExampleExecutor
方法查询策略设置
通过@EnableJpaRepositories(queryLookupStrategy.Key.CREATE_IF_NOT_FOUND)可以配置方法的查询策略,其中QueryLookupStrategy.Key一共有三个
QueryLookupStrategy.Key
CREATE
根据方法进行创建。规则是根据方法名称的构造进行尝试,一般的方法是从方法名中删除给定的的一组通知前缀,并解析该方法的其余部分
USE_DECLARED_QUERY
CREATE_IF_NOT_FOUND
分页查询
查询参数
Pageable
Pageable是Spring Data提供出来进行分页查询参数输入的接口,里面主要定义多个与页面设定的方法:
PageRequest
PageRequest废弃了原本的new方式来构建分页参数,建议使用类中提供的of(...)静态方法来创建相关的分页参数实体。 of(...)分页查询中允许带上排序字段以及排序方向两个参数,在源码中,这两个共同构建成了Sort实体。这是很多业务中需要使用上的,首先需要讲内容进行排序,然后再分页列出。 分页查询中,next()、previous()返回的分别是下一页、上一页的分页参数实体, 作者:老猫烧须 链接:https://www.jianshu.com/p/cb5a3ab2727e 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
返回结果
Page<T>
Slice<T>
List<T>
Set<T>
limit查询
在Mysql中,我们需要获得前N个结果,使用limit关键字。
top
first
User findFirstByUsername(String username) List findFirst10Bytitle(String title
排序查询
排序方向:ASC(升序)、DESC(降序)
Sort sort = new Sort(Sort.Direction.ASC,"id");
排序字段
example查询
创建exampleMatcher对象,最后使用Example的of方法构造相应的example对象并传递给相关查询方法。 Persion person = new Persion(); person.setId("id") ExampleMatcher matcher = ExampleMatcher.mactching() .withMacther("name",GemerocPropertyMatcher.startsWith()) .withIgnorePath("int"); Exmaple exmaple = Example.of(person,matcher);
.withMatcher
跟什么配
.matching()
.withIncludeNullValues()
.withIgonrePaths()
QueryDSL复杂查询
单表分页查询
QueryDslPredicateExecturtor
QueryDslPredicateExecutor
Predicate
可以创建预览,该Predicate为querydsl下的类,支持嵌套组装复杂查询条件
JpaRepository tiCityRepository.findAll()
多表动态查询
JPAQueryFactory
JPAQuery<Tuple>
JPAQuery .from(QTCity.tCity) .leftJoin(QTHotel.tHotel) .on(QTHotel.tHotel.city.longValue().eq(QTCity.tCity.id)); jpqQuery.where(predicate); jpaQuery.fecth();
流是结果查询
Spring Data支持使用Stram流式API使用。
try-with-resources
try (Stream stream = repository.findAllByCustomQueryAndStream()) { stream.forEach(…); }
发布事件
@DomainEvents
使用的方法@DomainEvents可以返回单个事件实例或事件集合。它
@AfterDomainEventPublication
它可用于潜在地清除要发布的事件列表(以及其他用途)
SpringData 对SpringMvc支持
xml
java config
@EnableSpringDataWebSupport piublic class WebConfiguration { ... }
Spring Data JDBC
对JDBC的Spring Data存储库支持
配置
添加依赖
plugins { id 'org.springframework.boot' version '2.1.6.RELEASE' id 'java' } apply plugin: 'io.spring.dependency-management' // ... ... dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jdbc' implementation 'mysql:mysql-connector-java:8.0.17' // ... ... }
配置类
@Configuration @EnableJdbcRepositories("com.example.springdatajdbcdemo") //这里的扫描目录要写好了 public class JdbcConfig { }
数据库配置参数
spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password=123456
创建实体
spring data jdbc读取记录后,会先创建对象,然后给对象赋值
@Data
用lombok来消除模板代码
@Id
标记属性为主键
@PersistenceConstructor
//1.如果有无参数的构造函数, spring data jdbc会使用无参数的构造函数来创建对象 //2.如果只有一个构造函数, spring data jdbc会使用它 //3.如果有多个构造函数, spring data jdbc会使用有@PersistenceConstructor标记的那个 https://blog.csdn.net/crazyman2010/article/details/98207539
创建操作接口Repository
//继承CrudRepository, 第一个模板参数是实体类, 第二个参数是主键对应的数据类型
读写数据
可以直接使用CrudRepository中的方法
save*(
findById()
自己写查询语句
public interface AccountRepository extends CrudRepository { @Query("select * from account where login_name=:loginName") Optional getByLoginName(@Param("loginName") String loginName); }
Spring Data JPA
对JPA的Spring Data存储库支持
常用注解
@EableJpaRepositories
用于spring的jpa代码配置
@EntityScan
用来扫描和发现指定报及其自爆中entity定义 @EntitySan(basePackage = {"com.departmenet.entities"})
@Entity
表名是一个实体类
@Id
主键
@GeneratedValue
主键生产策略
@Column
映射表对应的字段名
@Basic
表示该属性是字段的映射。如果实体的字段上没有任何注解默认就是@Basic
@Transient
表示该属性不是字段的映射
@Lob
将属性映射成支持的大对象类型,Clob、Blob
@IdClass
联合主键
@Temporal
用来指定java.util.Date或java.util.Calender属性与数据库类型date,time或timestamp中的哪一种类型
@Enumerated
用于标注枚举字段,对应mysql的enum
@Embedded
尤其是一个实体类要在多个不同的实体类中进行使用,而本身又不需要独立生成一个数据库表
@Transient
(pojo)属性上使用、表示数据库表中没有这个字段就忽略
@Query
注解使用于查询的数据无法通过关键字查询得到结果的查询。这种查询可以巴托像关键字查询那样的约束,将查询直接在相应的接口方法中声明,结构更为清晰
索引参数与命名参数
索引参数使用?x数字的形式,需要对应入参的顺序
命名参数 使用 :lastName 和@Param("lastName")的形式不需要管顺序
含有LIKE关键字的查询,在命名参数加“%”
使用原生SQL查询 nativeQuery = true
@Modifying
在@Query中编写JPQ实现DELETE和UPDATE操作时必须加上@Modifying注解
Update或者DELETE操作需要使用事务,此时需要定义Service层,在Service层的方法添加事务操作
不支持INSERT操作
Spring Data mongo
对MongoDB的基于Spring对象文档的存储库支持。
常用注解
@Id
@Document
用于标记实体类时mongodb集合映射类,可以使用collection参数指定集合名称。特别需要注意的是如果实体类没有为任何字段创建不会自动创建集合
@Indexed
用于标记某一个字段创建索引。direction参数可以指定排序方向,升序或降序
@CompoundIndex
用于创建符合索引。def参数可以定义符合索引的字段及排序方向
@Transient
被该注解标注的,将不会录入到数据库中
@PersistenceConstructor
用于声明构造函数,作用是把从数据库取出来的数据实例化为对象
@Field
用于指定木一个字段映射到数据库中的名称
@DBRef
用于指定其他集合的级联关系,但是需要注意的是并不会自动创建爱你级联集合
Spring Data Redis
从Spring应用程序轻松配置和访问Redis
RedisRepositories
@EnableRedisRepositories
CRUD操作将会操作redis中的数据
@RedisHash("user")
redisHash非常重要,user表示在redis中新建user集合,之后所有的userEntity的保存操作都会保存在user这个集合,保存key的格式为--user:id
Spring Data JDBC Ext
支持标准JDBC的数据库特定扩展,包括对Oracle RAC快速连接故障转移的支持,AQ JMS支持以及对使用高级数据类型的支持。
Spring Data KeyValue
Map基于库和SPI轻松建立键值存储一个Spring数据模块
Spring Data LDAP
对Spring LDAP的 Spring Data存储库支持
Spring Data REST
将Spring Data存储库导出为超媒体驱动的RESTful资源
Spring Data for Pivotal GemFire
轻松配置和访问Pivotal GemFire,实现高度一致,低延迟/高吞吐量,面向数据的Spring应用程序。
Spirng Data for Apache Cassandra
轻松配置和访问Apache Cassandra或大规模,高可用性,面向数据的Spring应用程序。
Spring Data for Apace Geode
轻松配置和访问Apache Geode,实现高度一致,低延迟,面向数据的Spring应用程序。
springcloud
服务的注册与发现(Eureka)
创建服务注册中心
创建maven主工程
首先创建一个主Maven工程,在其pom文件引入依赖,spring Boot版本为2.0.3.RELEASE,Spring Cloud版本为Finchley.RELEASE。这个pom文件作为父pom文件,起到依赖版本控制的作用,其他module工程继承该pom。这一系列文章全部采用这种模式,其他文章的pom跟这个pom一样。再次说明一下,以后不再重复引入。代码如下 ———————————————— 版权声明:本文为CSDN博主「方志朋」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/forezp/article/details/81040925
创建Eureka Server
创建完后的工程,其pom.xml继承了父pom文件,并引入spring-cloud-starter-netflix-eureka-server的依赖,代码如下:
@EnableEurekaServer
启动一个服务注册中心
创建配置文件
通过eureka.client.registerWithEureka:false和fetchRegistry:false来表明自己是一个eureka server. eureka是一个高可用的组件,它没有后端缓存,每一个实例注册之后需要向注册中心发送心跳(因此可以在内存中完成),在默认情况下erureka server也是一个eureka client ,必须要指定一个 server。eureka server的配置文件appication.yml ———————————————— 版权声明:本文为CSDN博主「方志朋」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/forezp/article/details/81040925
创建Eureka Client
当client向server注册时,它会提供一些元数据,例如主机和端口,URL,主页等。Eureka server 从每个client实例接收心跳消息。 如果心跳超时,则通常将该实例从注册server中删除。
@EnableEurekaClient
表明自己是一个client
服务消费者(rest+ribbon)
ribbon简介
负载均衡客户端
ribbon是一个负载均衡客户端,可以很好的控制htt和tcp的一些行为。Feign默认集成了ribbon。
默认实现了bean
IClientConfig ribbonClientConfig: DefaultClientConfigImpl
com.netflix.loadbalancer.DiscoveryEnabledNIWSServerList
使用注册心
IRule ribbonRule: ZoneAvoidanceRule
IPing ribbonPing: NoOpPing
ServerList ribbonServerList: ConfigurationBasedServerList
使用配置件
ServerListFilter ribbonServerListFilter: ZonePreferenceServerListFilter
ILoadBalancer ribbonLoadBalancer: ZoneAwareLoadBalancer
服务消费者
service-ribbon
pom.xml
spring-cloud-starter-eureka
spring-cloud-starter-ribbon
spring-boot-starter-web
@EnableDiscoveryClient
向服务中心注册,
@LoadBalanced
这个restRemplate开启负载均衡的功能。 使用 ribbon向其他接口发送请求
负载均衡算法及配置
1、负载均衡算法
RoundRobinRule
轮询选择,轮询下表,选择下表对应位置的Server
Random
随机选择erver
RetryRule
对选定的负载均衡策略机上重试机制,在一个配置时间端内选择的Server不成功时,则一直尝试使用subRule的方式选择一个可用的Server
AvailabilityFilteringRule
过滤掉一直连接失败的被标记为 circuit tripped (断路器状态)的后端Server,并过滤掉那些高并发的后端Server或者使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就就是检查status里记录的各个Server的运行状态。对剩下的使用轮询策略去访问。
BestAvailableRule
先过滤掉由于多次访问故障而处于断路器跳闸状态(tripped)的Server,然后选择一个并发量最小的Server
WeightedResponseTimeRule
根据响应时间加权,响应时间越长,权重越小,被选中的可能性越低;根据平均响应时间计算所有服务的权重,响应时间越快的服务权重越大被选中的概率越大。刚启动时如果统计信息不足,则使用RoundRobinRule(轮询)策略,等统计信息足够,会切换到WeightedResponseTimeRule。
ZoneAvoidanceRule
复合判断Server所在Zone的性能和Server的可用性选择Server,在没有Zone的情况下类是轮询。
2.负载均衡算法的选择
注解方式
配置文件的方式
{server-name}: ribbon: NFLoadBalancerRuleClassName: 负载均衡算法对应的全类名 示例: product-center: ribbon: NFLoadBalancerRuleClassName:com.netflix.loadbalancer.RandomRule
3.Ribbon的颗粒度配置
注解方式
ProductRibbonCofig
@Configuration public class ProductRibbonConfig { @Bean public IRule randomRule() { return new RandomRule(); } }
PayRibbonConfig
@Configuration public class PayRibbonConfig { @Bean public IRule roundRobinRule() { return new RoundRobinRule(); } }
示例
@Configuration @RibbonClients(value = { @RibbonClient(name = "product‐center", configuration = ProductRibbonConfig.class), @RibbonClient(name = "pay‐center", configuration = PayRibbonConfig.class) }) public class CustomRibbonConfig { }
配置文件方式
# 全局的负载均衡算法使用ZoneAvoidanceRule ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.ZoneAvoidanceRule # 调用product-center服务使用RandomRule product-center: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 调用pay-center服务使用RoundRobinRule pay-center: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
4.解决Ribbon第一次调用耗时高的问题
开启饥饿加载
ribbon: eager-load: clients: product-center,pay-center enabled: true
Ribbon的常用参数
# 每一台服务器重试的次数,不包含首次调用的那一次 ribbon.MaxAutoRetries=1 # 重试的服务器的个数,不包含首次调用的那一台实例 ribbon.MaxAutoRetriesNextServer=2 # 是否对所以的操作进行重试(True 的话 会对post put操作进行重试,存在服务幂等问题) ribbon.OkToRetryOnAllOperations=false # 建立连接超时 ribbon.ConnectTimeout=3000 # 读取数据超时 ribbon.ReadTimeout=3000
架构预览
一个服务中心,eureka server端口8761
service-hi工程跑了两个实例,端口分别为8762,8763,分别向服务注册中心注册
sercvice-ribbon端口为8764,向服务注册中心注册
当sercvice-ribbon通过restTemplate调用service-hi的hi接口时,因为用ribbon进行了负载均衡,会轮流的调用service-hi:8762和8763 两个端口的hi接口;
服务消费者(Feign)
feign简介
Feign是一个声明式的伪Http客户端,它使得写Http客户端变得更简单。使用Feign,只需要创建一个接口并注解。它具有可插拔的注解特性,可使用Feign 注解和JAX-RS注解。Feign支持可插拔的编码器和解码器。Feign默认集成了Ribbon,并和Eureka结合,默认实现了负载均衡的效果 ———————————————— 版权声明:本文为CSDN博主「方志朋」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/forezp/article/details/81040965
Feign 采用的是基于接口的注解
Feign 整合了ribbon,具有负载均衡的能力
整合了Hystrix,具有熔断的能力
创建feign的服务
pom.xml
spring-cloud-starter-feign
spring-cloud-starter-netflix-eureka-client
spring-boot-starter-web
@EnableFeignClients
开启Feign的功能
@FeignClient("服务名")
来指定调用那个服务
断路器(Hystrix)
断路器简介
Netflix开源了Hystrix组件,实现了断路器模式,SpringCloud对这一组件进行了整合。
一个请求调用多个服务
较底层的服务如果出现故障,会导致连锁故障
当对特定的服务的调用的不可用达到一个阀值(Hystric 是5秒20次) 断路器将会被打开。
fallback方法可以直接返回一个固定值。
断路打开后,可用避免连锁故障,
在ribbon使用断路器
1.在pom文件中加入spring-cloud-starter-netflix-hystrix的起步依赖
在程序的启动类ServiceRibbonApplication 加@EnableHystrix注解开启Hystrix:
改造HelloService类,在hiService方法上加上@HystrixCommand注解。
该注解对该方法创建了熔断器的功能,并指定了fallbackMethod熔断方法,熔断方法直接返回了一个字符串,字符串为"hi,"+name+",sorry,error!",代码如下:
Feign中使用断路器
1.feign.hystrix.enabled=true
打开断路器
2.在FeignClient的SchedualServiceHi接口的注解中加上fallback的指定类
3.SchedualServiceHiHystric需要实现SchedualServiceHi 接口,并注入到Ioc容器中
路由网关(zuul)
Spring Cloud微服务系统中,一种常见的负载均衡方式是,客户端的请求首先经过负载均衡(zuul、Ngnix),再到达服务网关(zuul集群),然后再到具体的服。,服务统一注册到高可用的服务注册中心集群,服务的所有的配置文件由配置服务管理(下一篇文章讲述),配置服务的配置文件放在git仓库,方便开发人员随时改配置。 ———————————————— 版权声明:本文为CSDN博主「方志朋」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/forezp/article/details/81041012
Zuul简介
Zuul的主要功能是路由转发和过滤器。路由功能是微服务的一部分,比如/api/user转发到到user服务,/api/shop转发到到shop服务。zuul默认和Ribbon结合实现了负载均衡的功能。
Authentication
Insights
Stress Testing
Canary Testing
Dynamic Routing
Service Migration
Load Shedding
Security
Static Response handling
Active/Active traffic management
创建service-zuuL工程
pom.xml
spring-cloud-starter-netflix-eureka-client
spring-cloud-starter-netflix-zuul
在application类上加上注解@EnableZuulProxy
开启zuul功能
application.yml加上配置
eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ server: port: 8769 spring: application: name: service-zuul zuul: routes: api-a: path: /api-a/** serviceId: service-ribbon api-b: path: /api-b/** serviceId: service-feign
服务端口8769
注册中心的地址为http://localhost:8761/eureka/
服务名称service-zuul
以/api-a/ 开头的请求都转发给service-ribbon服务
以/api-b/开头的请求都转发给service-feign服务
ZuulFilter过滤器
过滤器
filterType
返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下
pre
路由之前
routing
路由之时
post
路由之后
error
发送错误调用
filterOrder
过滤的顺序
shouldFilter
这里可以写逻辑判断,是否要过滤,本文true,永远过滤。
run
过滤器的具体逻辑。可用很复杂,包括查sql,nosql去判断该请求到底有没有权限访问。
token校验/安全认证
@Component public class AccessFilter extends ZuulFilter { private static Logger logger = LoggerFactory.getLogger(AccessFilter.class); @Override public String filterType() { return FilterConstants.PRE_TYPE; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); Object token = request.getParameter("token"); //校验token if (token == null) { logger.info("token为空,禁止访问!"); ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(401); return null; } else { //TODO 根据token获取相应的登录信息,进行校验(略) } //添加Basic Auth认证信息 ctx.addZuulRequestHeader("Authorization", "Basic " + getBase64Credentials("app01", "*****")); return null; } private String getBase64Credentials(String username, String password) { String plainCreds = username + ":" + password; byte[] plainCredsBytes = plainCreds.getBytes(); byte[] base64CredsBytes = Base64.encodeBase64(plainCredsBytes); return new String(base64CredsBytes); } }
动态修改请求参数
zuulFilter可以拦截所有请求参数,并对其进行修改
灰度发布(Gated Launch/Gray Release)
熔断处理
如果网管后服务挂了,zuul还允许定义个fallback,用于熔断处理,
implements ZuulFallbackProvider
分布式配中心(SpringCloud Config)
简介
conifg server
config client
构建Config Server
创建父pom文件
创建config-server pom.xml
在程序的入口Application类加上@EnableConfigServer注解开启皮遏制服务器的功能
需要在程序的配置文件application.properties文件配置
spring.application.name=config-server server.port=8888 spring.cloud.config.server.git.uri=https://github.com/forezp/SpringcloudConfig/ spring.cloud.config.server.git.searchPaths=respo spring.cloud.config.label=master spring.cloud.config.server.git.username= spring.cloud.config.server.git.password= ———————————————— 版权声明:本文为CSDN博主「方志朋」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/forezp/article/details/81041028
spring.cloud.config.server.git.uri:配置git仓库地址
spring.cloud.config.server.git.searchPaths:配置仓库路径
spring.cloud.config.label:配置仓库的分支
spring.cloud.config.server.git.username:访问git仓库的用户名
spring.cloud.config.server.git.password:访问git仓库的用户密码
spring.cloud.config.label
获取配置件的分支,默认是master.如果是本地获取的话,则无用
spring.cloud.config.discovery.enabled
开启配置息发现
spring.cloud.config.discovery.serviceId
指定配置中心的service-id,便于扩展为高可用配置集群
eureka.client.serviceUrl.defaultZone
这个是设置与Eureka Server交互的地址,客户端的查询服务和注册服务都需要依赖这个地址。
http请求地址和支援文件映射
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
构建一个config client
构建pom文件
spring-cloud-starter-config
配置文件boostrap.properties
spring.application.name=config-client spring.cloud.config.label=master spring.cloud.config.profile=dev spring.cloud.config.uri= http://localhost:8888/ server.port=8881
spring.cloud.config.label
指明远程仓库的分支
spring.cloud.config.profile
dev开发环境配置文件
test测试环境
pro正式环境
spring.cloud.config.uri= http://localhost:8888/ 指明配置服务中心的网址。
高可用化
,当服务实例很多时,都从配置中心读取文件,这时可以考虑将配置中心做成一个微服务,将其集群化,从而达到高可用,架构图如下:
创建一个eureka-server
pom.xml
spring-cloud-starter-netflix-eureka-client
spring-boot-starter-web
spring-cloud-config-server
application.xml
入口类加上@EnableEurekaServer
改造config-server
pom.xml
spring-cloud-starter-netflix-eureka-client
application.yml添加
http://localhost:8889/eureka/
启动类Application加上@EnableEureka
改造config-client
pom.xml
spring-cloud-starter-netflix-eureka-client
bootstrap.properties
加上服务注册地址为http://localhost:8889/eureka/
springcloud stream
spring message
比如消息 Messaging 对应的模型就包括一个消息体 Payload 和消息头 Header:
package org.springframework.messaging; public interface Message { T getPayload(); MessageHeaders getHeaders(); }
消息通道 MessageChannel 用于接收消息,调用 send 方法可以将消息发送至该消息通道中 :
@FunctionalInterface public interface MessageChannel { long INDEFINITE_TIMEOUT = -1; default boolean send(Message message) { return send(message, INDEFINITE_TIMEOUT); } boolean send(Message message, long timeout); }
由消息通道的子接口可订阅的消息通道 SubscribableChannel 实现,被 MessageHandler 消息处理器所订阅:
public interface SubscribableChannel extends MessageChannel { boolean subscribe(MessageHandler handler); boolean unsubscribe(MessageHandler handler); }
由MessageHandler 真正地消费/处理消息:
@FunctionalInterface public interface MessageHandler { void handleMessage(Message message) throws MessagingException; }
Spring Integration
提供了 Spring 编程模型的扩展用来支持企业集成模式(Enterprise Integration Patterns),是对 Spring Messaging 的扩展。
消息的分割:
消息的聚合
消息的过滤
消息的分发:
消息总线(SpringCloudBus)
Spring Cloud Bus 将分布式的节点用轻量的消息代理连接起来。它可以用于广播配置文件的更改或者服务之间的通讯,也可以用于监控。本文要讲述的是用Spring Cloud Bus实现通知微服务架构的配置文件的更改
改造config-client
spring-cloud-starter-bus-amqp
spring-cloud-starter-config
spring-cloud-starter-netflix-eureka-client
spring-boot-starter-actuator
application.properties
spring.rabbitmq.host=localhost spring.rabbitmq.port=5672 spring.rabbitmq.username=guest spring.rabbitmq.password=guest spring.cloud.bus.enabled=true spring.cloud.bus.trace.enabled=true management.endpoints.web.exposure.include=bus-refresh
rabbitmq地址
rabbitmq端口号
用户名
密码
spring.cloud.bus.enabled=true
spring.cloud.bus.trace.enabled=true
management.endpoints.web.exposure.include=bus-refresh
ConfigClientApplication启动类代码
@RefreshScope
配置文件自动刷新
发送post请求:http://localhost:8881/actuator/bus-refresh
config-client会重新读取配置文件
服务链路追踪(Spring Cloud Sleuth)
服务追踪分期
服务架构上通过业务来划分服务的,通过REST调用,对外暴露的一个接口,可能需要很多个服务协同才能完成这个接口功能,如果链路上任何一个服务出现问题或者网络超时,都会形成导致接口调用失败。随着业务的不断扩张,服务之间互相调用会越来越复杂。
Span
span在不断的启动和停止,同时记录了时间信息,当你创建了一个span,你必须在未来的某个时刻停止它。
64位ID唯一表示
span通过一个64位ID唯一标识,trace以另一个64位ID表示
摘要
时间戳事件
关键值注释(tags)
进度Id(通常是IP地址)
Trace
一系列spans组成的一个树状结构,例如,如果你正在跑一个分布式大数据工程,你可能需要创建一个trace。
Annotation
用来及时记录一个事件的存在,一些核心annotations用来定义一个请求的开始和结束
cs-Client Sent
客户端发起一个请求,这个annotion描述了这个span的开始
sr-Server Received
-服务端获得请求并准备开始处理它,如果将其sr减去cs时间戳便可得到网络延迟
ss—Server Sent
-注解表明请求处理的完成(当请求返回客户端),如果ss减去sr时间戳便可得到服务端需要的处理请求时间
cr-Client Received
-表明span的结束,客户端成功接收到服务端的回复,如果cr减去cs时间戳便可得到客户端从服务端获取回复的所有所需时间
构建工程
构建server-zipkin
下载链接 https://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server/
构建service-hi
pom.xml
spring-cloud-starter-zipkin
spring-boot-starter-web
application.yml
server.port=8988 spring.zipkin.base-url=http://localhost:9411 spring.application.name=service-hi
子主题 1
构建service-miya
引入相同的依赖,配置下spring.zipkin.base-url。
启动工程
打开浏览器访问:http://localhost:9411/,
http://localhost:8989/miya,浏览器出现
http://localhost:9411/的界面,点击Dependencies,可以发现服务的依赖关系:
高可用服务注册中心
这篇文章主要介绍怎么将Eureka Server集群化。
pom.xml
spring-boot-starter-web
spring-cloud-starter-netflix-eureka-server
spring-boot-starter-test
application-peer1.yml
server: port: 8761 spring: profiles: peer1 application: name: eureka-server eureka: instance: hostname: peer1 client: register-with-eureka: false fetch-registry: false service-url: defaultZone: http://peer2:8769/eureka/
application-peer2.yml
server: port: 8769 spring: profiles: peer2 application: name: eureka-server eureka: instance: hostname: peer2 client: register-with-eureka: false fetch-registry: false service-url: defaultZone: http://peer1:8761/eureka/
NewEurekaServerApplication
application.xml
spring: profiles: active: peer1
断路器
断路器监控(Hystrix Dashboard)
简介
在微服务架构中为例保证程序的可用性,防止程序出错导致网络阻塞,出现了断路器模型。断路器的状况反应了一个程序的可用性和健壮性,它是一个重要指标。Hystrix Dashboard是作为断路器状态的一个组件,提供了数据监控和友好的图形化界面。
改造service-hi
pom.xml
spring-cloud-starter-netflix-eureka-client
spring-boot-starter-web
spring-boot-starter-actuator
监控
spring-cloud-starter-netflix-hystrix
hystrix 依赖
spring-cloud-starter-netflix-hystrix-dashboard
hystrix web 视图查看
ServiceHiApplication
@EnableHystrix
启动熔断降级服务
@HystrixCommand(fallbackMethod="hiError")
使用注解的方式进行熔断降级fallbackMenthod 属性表示使用熔断降级的方法,针对具体的某个方法,该注解使用在server方法上
@EnableHystrixDashboard
HystrixDashboard
断路器聚合监控(Hystrix Turbine)
简介
Hystrix Turbine将每个服务Hystrix Dashboard数据进行了整合。Hystrix Turbine的使用非常简单,只需要引入相应的依赖和加上注解和配置就可以了。
创建service-turbine
Hystrix Metrics 收集
收集流程
1.使用HystrixComandMetrics记录metrics
// 构造器利用HystrixCommandMetrics获取命令key对应的对象 HystrixCommandMetrics.getInstance(commandKey, groupKey, threadPoolKey, properties); // HystrixCommandMetrics 中存储HystrixCommandMetrics的数据结构 private static final ConcurrentHashMap metrics;
@FeignClient(name = "service-B")
2.Per-Thread 事件处理者
3. 合并命令metrics
Resilience4j
1.CircuitBreaker(熔断器)
CircuitBreaker主要是实现针对接口异常的断路统计以及断路处理。 @Test public void testCircuitBreaker(){ // Create a CircuitBreaker (use default configuration) CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig .custom() .enableAutomaticTransitionFromOpenToHalfOpen() .build(); CircuitBreaker circuitBreaker = CircuitBreaker .of("backendName",circuitBreakerConfig); String result = circuitBreaker.executeSupplier(() -> backendService.doSomethingWithArgs("world")); System.out.println(result); }
有限状态机
CLOSED
OPEN
HALF_OPEN
DISABLED
FORCED_OPEN
滑动窗口
基于计数的滑动窗口
基于计数的滑动窗口是由包含N个测量单元的循环数组实现的。若计数窗口的大小为10,循环数组总会有10个测量单元。
基于时间的滑动窗口
基于时间的滑动窗口是由N个部分统计单元(桶)的循环数组实现的。
失败率和慢调用率阈值
当失败率等于或大于阈值时,CircuitBreaker的状态由CLOSED转为OPEN。比如,当大于50%被记录的调用失败了。 当慢调用的比例等于或大于阈值时,CircuitBreaker的状态由CLOSED转为OPEN。比如,当大于50%被记录的调用花的时间大于5秒。这将有助于在外部系统变得无响应阄,降低它的负载。
创建CircuitBreakerRegistry
Resilience4j会在内存中带一个CircuitBreakerRegistry,这个CircuitBreakerRegistry基于ConcurrentHashMap,提供了线程安全性和原子性保证。你能使用CircuitBreakerRegistry管理(创建和获取)CircuitBreaker实例。可以使用一个全局默认CircuitBreakerConfig为所有CircuitBreaker实例创建一个CircuitBreakerRegistry。
创建和配置CircuitBreaker
failureRateThreshold
以百分率形式配置失败率阈值。失败率大于等于阈值时,CircuitBreaker转变为打开状态,并使调用短路。
slowCallRateThreshold
以百分率形式配置慢调用率阈值。当调用执行的时长大于slowCallDurationThreshold时,CircuitBreaker会认为调用为慢调用。当慢调用占比大于等于此阈值时,CircuitBreaker转变为打开状态,并使调用短路。
slowCallDurationThreshold
配置调用执行的时长阈值。当超过这个阈值时,调用会被认为是慢调用,并增加慢调用率。
permittedNumberOfCallsInHalfOpenState
当CircuitBreaker是半开状态时,配置被允许的调用次数。
slidingWindowType
配置滑动窗口类型。当CircuitBreaker关闭时,这种类型的滑动窗口会记录调用结果。滑动窗口要么是基于计数的,要么是基于时间的。若滑动窗口为COUNT_BASED,则最近slidingWindowSize次的调用会被记录和统计。若滑动窗口为TIME_BASED,则最近slidingWindowSize秒中的调用会被记录和统计。
slidingWindowSize
配置滑动窗口的大小。当CircuitBreaker关闭后用于记录调用结果。
minimumNumberOfCalls
配置最小调用次数。在CircuitBreaker计算错误率前,要求(在每滑动窗口周期)用到这个值。例如,若minimumNumberOfCalls是10,为计算失败率,则最小要记录10个调用。若只记录了9个调用,即使9个都失败,CircuitBreaker也不会打开。
waitDurationInOpenState
CircuitBreaker状态从打开转化为半开时,需要等待的时长。
automaticTransitionFromOpenToHalfOpenEnabled
如果为true,则CircuitBreaker会自动从打开状态转化为半开状态。不需要另外的调用来触发这种转换。
2. Timelimiter 实现超时控制
@Test public void testTimelimiter(){ ExecutorService executorService = Executors.newSingleThreadExecutor(); TimeLimiterConfig config = TimeLimiterConfig.custom() .timeoutDuration(Duration.ofMillis(600)) .cancelRunningFuture(true) .build(); TimeLimiter timeLimiter = TimeLimiter.of(config); Supplier> futureSupplier = () -> { return executorService.submit(backendService::doSomethingThrowException); }; Callable restrictedCall = TimeLimiter.decorateFutureSupplier(timeLimiter,futureSupplier); Try.of(restrictedCall::call) .onFailure(throwable -> System.out.println("We might have timed out or the circuit breaker has opened.")); }
3. Bulkhead 用来控制并行(parallel)调用的次数。
/** * A Bulkhead can be used to limit the amount of parallel executions */ @Test public void testBulkhead(){ Bulkhead bulkhead = Bulkhead.of("test", BulkheadConfig.custom() .maxConcurrentCalls(1) .build()); Supplier decoratedSupplier = Bulkhead.decorateSupplier(bulkhead, backendService::doSomethingSlowly); IntStream.rangeClosed(1,2) .parallel() .forEach(i -> { String result = Try.ofSupplier(decoratedSupplier) .recover(throwable -> "Hello from Recovery").get(); System.out.println(result); }); }
4.RateLimiter 用来做流控
5.Fallback
6.Retry
SpringCloudGateway
Finchley.SR1
创建sc-f-gateway-first-sight
pom.xml
org.springframework.boot spring-boot-starter-parent 2.0.5.RELEASE org.springframework.cloud spring-cloud-dependencies Finchley.SR1 pom import org.springframework.cloud spring-cloud-starter-gateway
创键一个简单的路由
@SpringBootApplication @RestController public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Bean public RouteLocator myRoutes(RouteLocatorBuilder builder) { return builder.routes() .route(p -> p .path("/get") .filters(f -> f.addRequestHeader("Hello", "World")) .uri("http://httpbin.org:80")) .build(); } }
在上面的myRoutes方法中,使用了一个RouteLocatorBuilder的bean去创建路由,
分布式事务
两阶段提交(2PC)
两阶段提交(Two-phase Commit,2PC),通过引入协调者(Coordinator)来协调参与者的行为,并最终决定这些参与者是否要真正执行事务。
运行过程
准备阶段
协调者询问参与者事务是否执行成功,参与者发回事务执行结果。
提交阶段
如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。 需要注意的是,在准备阶段,参与者执行了事务,但是还未提交。只有在提交阶段接收到协调者发来的通知后,才进行提交或者回滚。
存在问题
1、同步阻塞
所有事务参与者在等待其它参与者响应的时候都处于同步阻塞状态,无法进行其它操作
2、单点问题
协调者在 2PC 中起到非常大的作用,发生故障将会造成很大影响。特别是在阶段二发生故障,所有参与者会一直等待状态,无法完成其它操作。
3、数不一致
4、太过保守
补偿事务(TCC)
针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作
Try阶段
主要是对业务系统做检测及资源预留
Confirm阶段
主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。
Cancel 阶段
要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。
本地消息表(异步确保)
本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在对这两个表的操作满足事务特性,并且使用了消息队列来保证最终一致性。
在分布式事务操作的一方完成写业务数据的操作之后向本地消息表发送一个消息,本地事务能保证这个消息一定会被写入本地消息表中
之后将本地消息表中的消息转发到 Kafka 等消息队列中,如果转发成功则将消息从本地消息表中删除,否则继续重新转发。
在分布式事务操作的另一方从消息队列中读取一个消息,并执行消息中的操作
MQ事务消息
第一阶段Prepared消息
会拿到消息的地址
第二阶段执行本地事务
第三阶段通过第一阶段拿到的地址去访问消息,并修改状态
最大努力通知型事务
Hazelcast
在微服务多个实例间广播、多播的机制,
特点
Hazelcast提供弹性可扩展的分布式内存计算,
azelcast通过提供对开发者友好的Map、Queue、ExecutorService、Lock和JCache接口使分布式计算变得更加简单
提供了一组方便的api来访问集群中的cpu,以获得最大的处理速度
Hazelcast以Jar包的方式发布,因此除Java语言外Hazelcast没有任何依赖
Hazelcast 具有高可扩展性和高可用性(100%可用,从不失败)
分布式应用程序可以使用Hazelcast进行分布式缓存、同步、集群、处理、发布/订阅消息等。Hazelcast基于Java实现,并提供C/C++,.NET,REST,Python、Go和Node.js客户端。Hazelcast遵守内存缓存协议,可以内嵌到Hibernate框架,并且可以和任何现有的数据库系统一起使用。 作者:大哥你先走 链接:https://www.jianshu.com/p/99d98acb3195 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
节点对称
和大多数NoSQL解决方案不同,Hazelcast集群中的节点是对等的,集群中没有主备角色之分,因此Hazelcast无单点故障问题。集群内所有节点存储和计算同量数据。可以把Hazelcast内嵌到已有的应用程序中或使用客户端服务器模式(应用程序作为Hazelcast集群中一个节点的客户端)。
可扩展
Hazelcast被设计为可以扩展到成百上千个节点,简单的增加节点,新加入的节点可以自动发现集群,集群的内存存储能力和计算能力可以维持线性增加。集群内每两个节点之间都有一条TCP连接,所有的交互都通过该TCP连接。
Hazelcast的拓扑结构
内嵌模式
如果您有一个应用程序,其主要关注点是异步或高性能计算和执行大量任务,在这种应用场景使用内嵌部署模式比较合适,在内嵌部署模式下,Hazelcast集群中的一个节点包括:应用程序,Hazelcast分区数据,Hazelcast服务三部分。内嵌部署模式的优势是读取数据延迟低。
客户端/服务器部署模式
Hazelcast数据和服务集中在一个或多个节点上,应用通过客户端读写数据。可以部署一个提供服务的独立Hazelcast集群,服务集群可以独立创建,独立扩展。客户端通过和集群中的节点交互来获取Hazelcast数据和服务。Hazelcast提供Java,.NET、C++、Memcache和REST客户端。客户端/服务器部署模式如下图所示:
Hazelcast的一致性解决方案
围绕分布式思想设计的Hazelcast提供一种全新访问处理数据的方法。为了提高灵活性和性能,Hazelcast在集群周围共享数据。Hazelcast基于内存的数据网格为分布式数据提供集群和高可扩展性。 集群无主节点是Hazelcast的一个主要特性,从功能上来讲,集群内每个节点都被配置为对等。第一个加入集群的节点负责管理集群内其他所有节点,例如数据自动平衡、分区表更新广播。如果第一个节点下线,第二个加入集群的节点负责管理集群其他节点。第一个节点故障切换方 数据完全基于内存存储、访问速度快是Hazelcast的另外一个特点。由于Hazelcast将数据的副本分布到集群中的其他节点上,所以在故障条件下(节点宕机)也不会有数据丢失。Hazelcast提供很多分布式数据结构和分布式计算工具,增强分布式集群内存的访问并通过CPU最大化分布式计算的速度。 作者:大哥你先走 链接:https://www.jianshu.com/p/99d98acb3195 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者:大哥你先走 链接:https://www.jianshu.com/p/99d98acb3195 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
数据分区
Hazelcast中的分片也叫做分区,分区是一个内存段,分区中存储数百或数千数据实体,分区的存储容量取决于节点自身的存储能力。
Vertx.x
Netty
Reactor线程模型
一种并发处理客户端请求响应的额时间驱动模型。服务端在接收到客户端请求后采用多路复用策略,统统一个非阻塞线程来异步接收所有的客户端请求。并将这些请求转发到相关的工作线程组上
1.Reator单线程模型
1.client
NIO客户端,向服务端发起TCP连接,并发送数据
2.Acceptor
NIO服务端,通过Acceptor接收客户端的TCP连接
3.Dispather
接收客户端的数据并将数据以ByteBuffer的形式发送到对应的编解码器
4.DecoderHandler
解码器,读取客户端的数据并进行数据解码,数据处理和消息应答
5.EncoderHandler
编码器,将客户端发动的数据(消息请求或消息应答)进行统一的编码处理,并写入通道
2.Reactor多线程模型
Reactor多线程模型与单线程模型最大的区别就是由一组线程(Thread Poll)处理客户端的I/O请求。Reactor多线程模型将Acceptor的操作封装在一组线程池中,
3.Reactor主从多线程模型
javaNIO
selector选择器
用于检测多个注册的channel上是否有I/O事件发生,并对检查到的I/O时间进行响应的响应和处理。因此,通过一个Selectot线程可以是现实多个channel
Channel通道
channel 与I/O中的Stream(流)类似,只不过Stream是单向的(列如InputStream,OutputStream)
FileChannel
DatagramChannel
SocketChannel
ServerSocketChannel
Buffer缓冲区
实际上是一个容器,其内部通过一个连续的字节数组存储I/O上的数据,NIO中,Channel在文件,网络上对数据的读取或写入都必须经过buffer。 客户端在向服务端发送数据时,必须先将数据写入buffer,然后将buffer中的数据写入微服务对应的channel
ByteBuffer
IntBuffer
ChartBuffer
LongBuffer
Netty的架构
1.Transport Services
Transport Services 指传输服务层,主要定义了数据的传输和通信方式,包括
Socket And Datagram(Socket 协议和数据包)
HTTP Tunnel(HTTP隧道)
In-VM Pip(本地传输管道)
2.Protocol Support
指传输协议层,主要定义数据传输过程中的服务类型、数据安全、数据压缩等。具体包括HTTP And WebSocket(HTTP和WebSocket服务)等
HTTP和WebSocket
服务定义了客服端和服务端的数据通信方式。HTTP服务基于HTTP实现,主要用于客户端主动向服务端发起数据包请求。WebSocket主要用于服务端基于消息推送的机制实时将数据包催送到客户端
SSL
StartTLS
是一种文明通信协议的扩展,能够让文明的通信连线直接成为加密连线
zlib/gzip Compression
定义了消息传递过程中数据的压缩和解压缩算法,主要用于提高批量数据传输过程中网络的吞吐量。
Large File Transfer
Google ProtoBuf
RTS(Real Time Streaming Protocal)
为实时输流洗衣,是一种网络应用协议,专为娱乐和通信使用,以控制流媒体服务。
3.Core
Netty的核心组件
1.Bootstrap/ServerBootstap
用于客户端服务的启动引导,ServerBootstrap用于服务端服务的启动引导
2.NioEventLoop
基于线程队列的方式执行时间操作,
3.NioEventLoopGroup
NioEventLoop生命
4.Future/ChannelFuture
Future和channelFuture用于异步通信实现
5.Channel
是netty中得网络通信组件。用于执行具体得I/O操作,Netty中所有的数据通信都基于Channel读取或者江数据写入对应的Channel。Channel的主要功能包括网络连接的建立、连接状态的管理、网络连接参数的配、基于异步NIO的网络数据操作
6.Selector
Selector用于多路复用中Channel管理。在Netty中,一个Selector可以管理多个Channel,在channel连接建立后江连接注册到Selector。Selector在内部监听每个Channel上I/O事件的变化
7.ChannelHandlerContext
8.ChannelHandler
基于拦截器设计模式实现的事件拦截处理和转发
9.ChannelPipeline
基于拦截器设计模式实现的事件拦截处理和转发。Netty中的每个Channel都对应一个ChannelPipelineq1
Netty的原理
Netty的运行核心包含两个NioEventLoopGroup工作组,一个是BossGroup,用于接收客户端连接,接收客户端数据和进行数据转发;另一个是WorkerGroup,用于具体I/O事件的触发和数据处理
1.NettyServer 的初始化步骤
初始化BossGroup和WorkerGroup
基于ServerBootstrap配置EventLoopGroup,包括连接参数设置
绑定端口和服务启动
2.BossGroup的职责
BossGroup 为一个事件循环组
1.轮询监听Accept事件
2.接收和处理accept事件
包括和客户端建立连接并生成NioSocketChannel,将NioSocketChannel注册到某个Worker NioEventLoop的selector上
3.处理runAllTask的任务
3.WorkerGroup的职责
WorkerGroup为一个事假循环组,其中包括多个事件循环(NioEventLoop),每个Worker NioEventLoop循环都执行一下三个步骤
1.轮询监听NioSocketChannel上的I/O事假
2.NioSocketChannel有I/O事件触发时执行具体的I/O操作
3.处理任务队列中的任务
Netty特性
1.I/O多路复用模型
2.数据零拷贝
Netty的数据接收和发送均采用堆外直接内存进行socket读写
3.内存重用机制
4.无锁化设计
Netty内部采用串行无锁话设计的思想对I/O进行操作,避免多线程竞争CPU和资源锁定导致的性能下降。
5.高新能的序列化框架
Netty默认基于Google ProtoBuf实现数据化的序列化。通过扩展Netty的编码接口
1.SO_RCVBUF和SO_SNDBUF的设置
SO_RCVBUF为接收数据的Buffer 大小,SO_SNDBUF为发送数据的Buffer大小,通常建议值为128KB或者256KB
2.SO_TCPNODELAY设置
3.软中断
Netty会根据数据包的源地址,源端口,目的地址,目的端口计算一个Hash值,根据该hash值选择对应的CPU执行软件中断
redis
redis数据结构
SDS动态字符串
定义
struct sdshr{ int len;//记录buf数组中已使用字节的数量 int free;//记录buf数组中未使用字节的数量 char buf [];//用于保存字符串的字节数组 }
SDS与C字符串的区别
C语言适应长度为N+1的字符数组来表示长度为N的字符串,并且字符数组的最后以恶元素总是空字符串‘\0’. c 语言使用的这种简单的字符串表示方式,并不能满足redis对字符串在安全性、效率及功能方面的要求
常数福再度获取字符串长度
C字符串的复杂度为O(N),而SDS的时间复杂度为O(1)
杜绝缓冲区溢出
当SDS API需要对SDS进行修改时,API会先检查SDS的空间是否满足修改所需的要求,如果不满足的话,API会自动将SDS的空间扩展至执行修改所需要的大小,然后才执行实际的修改操作
减少修改字符串时带来的内存重分配次数
1。空间预分配
空间预分配用于优化SDS的字符串增张操作
2.惰性空间释放
3.二进制安全
4.兼容部分c字符串函数
链表
链表的实现
listNode
typedef struct listNode{ //前置节点 struct listNode * prev; //后值节点 struct listNode * next; //节点的值 void *value }
list
typedef struct list{ //表头节点 listNode *head; //表尾节点 listNode * tail; //链表包含的节点数量 unsigned long len; //节点复制函数 void *(*dup)(void *ptr); //节点值释放函数 void (*free)(void *ptr); //节点值对比函数 int (*match)(void * ptr,void * key); } list;
dup 函数用于复制链表节点所保存的值
free函数用于释放链表节点所保存的值
match函数则用于对比链表节点所保存的值和另一个输入职是否相等
总结
双端
无环
表头节点的prev指针和表为节点的next指针都指向NULL,对链表的访问以NULL为终点
带表头指针和表尾指针
通过lis结构的head指针和tail指针,获取链表的表头节点和表尾节点的复杂度为o(1)
带链表长度计数器
程序使用ist结构的len属性来对list持有的链表节点进行计数,程序获取链表中节点数量的复杂度为o(1)
多态
链表节点使用void*指针来保存节点值,并且可以通过list结构的dup、free、match三个属性为节点设置类型特定函数,所以链表可以用于保存各种不容类型的值
字典
HGETALL
获取所有的hash 键和值
字典的实现
哈希表
typedef struct dictht{ //哈希表数组 dictEntry ** table; //哈希表大小 unsigned long size; //哈希表大小掩码,用于计算索引hi //总是等于size-1 unsigned long sizemask; //该哈希表已有节点的数量 unsigned long used; } dicht;
哈希表节点
typedef struct dictEntry{ //键 void * key; //值 union{ void * val; uint64_tu64; int64_ts64; } v; struc dictEntry *next; } dictEntry; key属性保存着键值对中的键,
key属性保存着键值对中的键
v保存着键值对中的值,其中键值对的值可以是一个指针,或者是一个uint64_t整肃,又或者是一个int64_t整数
next属性是指向另一个哈希表节点的指针,这个指针可以将多个哈希值相同的键值对连接在一次,以此来解决键冲突的问题
ht属性是一个包含两个项目的数组,数组中的每一个项都是一个dictht哈希表,一般情况下,字典使用ht[0]哈希表,ht[1]哈希表只会对ht[0]哈希表进行rehash时使用
字典
typedef struct dict{ //类型特定参数 dictType *type; //私有数据 void *privadata; //哈希表 dictht ht[2]; //rehash索引 //当rehash不在进行时值为-1 in trehashidex; } dict;
type
type属性是一个指向dictType结构的指针,每个dictType结构保存了一簇用于操作特定类型键值对的函数,
privdata
保存了需要传那些类型特定函数的可选参数
dictType
typedef struct dicTYpe{ //计算哈希值得函数 unsigned int (* hashFunction) (const void *key); //复制键的函数 void * (*keyDup) (void * privadata , const void * key); //对比键的函数 int (*keyCompare)(void *privdata,const void *key1,const void *key2); //销毁键的函数 void (*keyDestructor) (void *privdata, void *key); //销毁值得函数 void (*valDestructor)(void *privdata, void *obj); }dictType
哈希算法
将一个新的键值对添加到字典里面时,程序需要根据键值对的键计算出哈希值和索引值,然后再根据索引值,将包含新键值对的哈希表节点放到哈希表数组中的指定索引上面
hash = dict->type->hashFunction(key);
使用字典设置的哈希函数,计算键key的哈希值
index= hash&dict->ht[x].sizemask;
使用哈希表的sizemask属性和哈希值,计算出索引值,根据情况不同,ht[x]可以使ht[0]或者ht[1]
hash = dict->type->hashFunction(k0);
index = hash&dict->ht[0].sizemask=8&3 = 0
解决键冲突
当有两个或者以上数量的键被分配到了哈希表数组的同一个索引上面时,我们称这些键发生了冲突
rehash
1,为字典的ht[1]哈希表分配空间
如果执行的是扩展操作,那么ht[1]的大小为第一个大于等于ht[0].used*2的2的n次方幂
如果执行的是收缩操作,那么ht[1]的大小为第一个大于等于ht[0].used的2的n次
2.将保存在ht[0]中的所有键值对rehash到ht[1]上面
rehash指的重新计算键的哈希值和索引值,然后将键值对放置到ht[1]哈希表的指定位置上
3.当ht[0]包含的所有键值对都迁移到ht[1]之后(ht[0]变为空表),释放ht[0],将ht[1]设置为ht[0],并在ht[1]新创建一个空白哈希表,为下一次rehash准备
哈希表的扩展和收缩
1.服务器目前没有在执行BGSAVE命令或者BGREWRITEAOF命令,并且哈希表的负载因子大于等于1
2.服务器目前正在执行BGSAVE命令或者BGREWRITEAOF命令,并且哈希表的负载因子大于等于5
渐进式rehash
分多次,渐进的将ht[0]里面的键值对慢慢地rehash到ht[1]
2.在字典中维持一个索引计数器rehashidex,并将他的值设置为0,表示rehash工作正式开始。
3.在rehash进行区间,每次对字典执行添加、删除、查找或者更新操作时,程序除了执行指定的操作以外,还会顺带将ht[0]的哈希表在rehashidx索引上的所有键值对rehash到ht[1]上
4.随着字典操作的不断执行
程序先在ht[0]里面进行查找,如果没有找到的话,就会就绪到ht[1]里面进行查找
跳跃表
是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的
时间复杂度O(logN)
应用场景
实现有序集合键
集群节点中作内部数据结构
跳跃表的实现
redis.h/zskiplistNode
用于保存跳跃表节点 typedef struct zskiplistNode{ //层 struct zskiplistLevel{ //前进指针 struct zskiplistNode * forward; //跨度 unsigned int span; } level[]; //后退指针 struct zskipklistNode * backward; //分值 double score; //成员对象 robj * obj; }zskiplistNode;
层
跳跃表点的level数组可以包含多个元素,每一个元素都包含一个指向其他节点的指针,程序可以通过这些层来加快访问其他节点的速度, 每次创建爱你一个新跳跃表节点的时候,程序都根据幂次定律(power law,越大的数出现的概率越小)随机生成一个介于1和32之间的值作为level数组的大小
前进指针
每个层都有一个指向表尾方向的前进指针(level[i].forward属性),用于从表头向表尾方向访问节点。
跨度
层的跨度用于记录两个节点之间的距离
两个节点之间的跨度越大,它们相距的就越远
指向NULL的所有前进指正的跨度都为0,因为他们没有连向任何节点
后退指针
节点的后退指针用于从表尾向表头反向访问节点:跟可以一次跳过多个节点的前进指针不同,
分值和成员
redis.h/zskiplist
用于保存跃表节点的相关信息
header
指向跳跃表的表头结点
tail
指向跳跃表的表尾节点
level
记录目前跳跃表内,层数最大的那个节点的层数(表头节点的层数不计算在内)
length
记录跳跃表的长度,
层(level)
节点中用L1,L2,L3等字样标记节点的各个层,L1代表第一层,L2代表第二层,
后退
节点中用BW字样标记节点的后退指针,指向位于当前节点的前一个节点
分值(score)
各个节点中的1.0,2.0和3.0是节点所保存的分值。在跳跃表中,节点按各自所保存的分值从小到大排列
成员对象(obj)
各个节点的o1,o2和o3是节点所保存的成员对象
整数集合
整数集合的实现
redis用于保存整数值得集合抽象数据结构,他可以保存类型为int16_t,int32_t或者int64_t的整数值,并且保证集合中不会出现重复元素 typedef struct intset{ //编码方式 uint32_t encoding; //集合包含的元素数量 //保存元素的数组 int8_t contents[]; }
contents数组是整数集合的底层实现
整数集合的每个元素都是contents数组的一个数组项(item),各个项在数组中按值得大小从小到大有序的排列,并且数组中不包含任何重复项
length属性记录了整数集合包含的元素数量
升级
当我们要讲一个新元素添加到整数集合里面,并且新元素的类型比整数集合现已有所有元素的类型都要长是,整数集合需要进行升级。然后才能将新元素添加到整数集合里面
根据新元素类型,扩展整数集合底层数组的空间大小,并为新元素分配空间
将底层数组现有的所有元素都转换成与新元素相同的类型,并将类型和转换后的元素放置到正确的位上,
将新元素添加到底层数组里面
升级的好处
一个是提升整数集合的灵活性,另一个是尽可能地节约内存
提升灵活性
我们可以随意的将int16_5,int32_t或者int64_t类型额整数添加到集合中,而不担心出现类型错误。
节约内存
降级
整数集合支持降级,一旦堆数组进行了升级,编码就会一直保持升级后的状态
压缩列表
是列表键和哈希键的底层实现之一。当一个列表键只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么redis就会使用压缩列表来做列表键的底层实现
压缩列表的构成
zlbytes
记录整个压缩列表占用的内存字节数,在对压缩列表进行内存分配,或者计算zlend的位置使用
zltail
记录压缩列表节点距离压缩列表的其实地址有多少字节,通过这个盘一辆,程序无需便利整个压缩列表就可以确定表尾节点的地址
zllen
记录了压缩列表包含的节点数量
压缩列表节点的构成
每一个压缩表节点可以保存一个字节数组或者一个整数值,其中,字节数组可以是一下三种长度的其中一种
previous_entry_length长度打下等于63字节的字节数组
节点previous_entry_length属性以字节为单位,记录了压缩列表中前一个节点的长度。
如果前一个节点长度小于254字节,那么previous_entry_length属性的长度为1字节:前一个节点长度就保存在这个字节里面
如果前一个节点的长度大于等于254字节,那么previous_entry_length属性的长度为5字节:其中属性的第一个字节会被设置为0xFE(十进制254),而之后的四个字节则用于保存前一个节点的长度
长度大小等于16383字节的字节数组
长度大小等于4294967295字节的字节数组
redis常用配置
daemonize
是否是守护进程(no yes)
port
logfile
redis系统日志
dir
redis工作目录
redis默认端口
6379
redis配置查询
config get *
redis常见的数据类型
String Key-Value
常见操作命令
set
set key value 设置一个key 值为 value
get
get key 获得key值得value
decr
将redis对应的键的值减一
incr
将redis 对用键的值加一
Hash:key -filed-value
相当于map
hset key file value
hget key filed 取值
List
有序可以重复
lpush list 1 2 3 4
rpush list 1 2 3 4
lrange list 0 -1 (查寻所有元素)
lpop list
rpop list
Set
无序不能重复
sadd set1 a b c d d
向set1 集合当中添加元素,元素不会重复
smemebers set1
查询元素
srem set1 a
删除元素
SortedSet(zset)
有序,不能复
zadd
添加元素 zadd zset1 9 a 8 c 10 d 1 e (添加元素 zadd key score member )
zrange
查询元素 zrange zset1 0 -1 (从小到大)
zrevrange
zrevrange zset 0 -1(从大到小)
zincrby zset1 1 a
增加元素的大小
HyperLogLog
PFADD
添加指定元素在HyerLogLog中
PFCOUNT
返回给定HyperLogLog的基数估算值
PFMERGE
将多个HyperLogLog合并为一个HyperLogLog
Bitmap
setbit
设置BitMap值
getbit
获取BitMap值
destkey
对Bitmap进行操作,可以是and(交集)、or(并集)、not(非集)或xor(异或)
redis分布式锁
redis实现分布式锁 setnx命令为中心实现,setnx是Redis的写入命令,在写入的时候能够保持数据的原子性,在key不存在时setnx返回1,在key存在时setnx返回0
获取锁
在获取锁时调用setnx,如果返回0,则该锁正在被别人使用;如果返回1,则成功获取锁
释放锁
在释放锁时,判断锁是否存在,如果存在,则执行redis的delete操作
jedis
加锁
@Service public class RedisLock { Logger logger = LoggerFactory.getLogger(this.getClass()); private String lock_key = "redis_lock"; //锁键 protected long internalLockLeaseTime = 30000;//锁过期时间 private long timeout = 999999; //获取锁的超时时间 //SET命令的参数 SetParams params = SetParams.setParams().nx().px(internalLockLeaseTime); @Autowired JedisPool jedisPool; /** * 加锁 * @param id * @return */ public boolean lock(String id){ Jedis jedis = jedisPool.getResource(); Long start = System.currentTimeMillis(); try{ for(;;){ //SET命令返回OK ,则证明获取锁成功 String lock = jedis.set(lock_key, id, params); if("OK".equals(lock)){ return true; } //否则循环等待,在timeout时间内仍未获取到锁,则获取失败 long l = System.currentTimeMillis() - start; if (l>=timeout) { return false; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }finally { jedis.close(); } } }
解锁
/** * 解锁 * @param id * @return */ public boolean unlock(String id){ Jedis jedis = jedisPool.getResource(); String script = "if redis.call('get',KEYS[1]) == ARGV[1] then" + " return redis.call('del',KEYS[1]) " + "else" + " return 0 " + "end"; try { Object result = jedis.eval(script, Collections.singletonList(lock_key), Collections.singletonList(id)); if("1".equals(result.toString())){ return true; } return false; }finally { jedis.close(); } }
key命令
expire key second
设置key的过期时间
ttl key
查看剩余时间 (-2表示不存在,-1表示持久化,整数表示剩余时间)
persist key
清除过期的时间,也就是持久化 持久化成功显示1 不成功 0
del key
select 0
选择0号数据库
Redis管道
Redis是基于请求响应的tcp服务。在客户端向服务器发送一个查询请求后,需要监听Socket的返回,客户端可以继续向服务端发送请求,并最终一次性读取所有服务端的影响
redis管道基于springboot
redisTemplate.executePipelined()
新建RedisCallback
重写doinRedis()
connection.openPipeline()
发布和订阅
发布订阅模式
subscribe
publish
优势支持多订阅,简单,性能高
BLPOP+LPUSH争抢
DEL
LPUSH
blpop
是列表的阻塞式
exits job
BLPOP+LPUSH非争抢模式
事务
支持分布式环境下的事物操作,事务可以一次执行多个命令,事务中的所有命令都会序列化的顺序执行。事务在执行的过程中,不会北汽其他客户端发送来的命令请求打断。在执行玩事务中的所有命令,才会继续处理其他客户端的其他命令
概念
Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
事务没有隔离级别的概念
操作流程
事物开启
客户端执行Multt命令开启事务
提交请求
客户端提交命令到事务
任务入队列
redis将客户端请求放入事物队列中等待执行
入队状态反馈
服务器返回QURUD,表示命令已被放入事务队列
执行命令
客户端通过Exec执行事物
事务执行错误
在redis事物中如果某条命令执行错误,则其他命令会继续执行,不会回滚
相关命令
Multi
标记一个事务块的开始
Exec
执行所有事务块内的命令
Discard
取消事务,放弃执行事务内的所有命令
Watch
监视一个(或者多个)key,如果在事务执行之前这个(或这些)key被其他命令改动,那么事务将被打断
Unwatch
取消Watch命令对所有的key监视
缓存穿透
缓存雪崩
集群
主从模式
1、 从Redis宕机 这个相对而言比较简单,在Redis中从库重新启动后会自动加入到主从架构中,自动完成同步数据; 2、 主Redis宕机 a) 这个相对而言就会复杂一些,需要以下2步才能完成 i. 第一步,在从数据库中执行SLAVEOF NO ONE命令,断开主从关系并且提升为主库继续服务; ii. 第二步,将主库重新启动后,执行SLAVEOF命令,将其设置为其他库的从库,这时数据就能更新回来; b) 这个手动完成恢复(人工介入)的过程其实是比较麻烦的并且容易出错,有没有好办法解决呢?当前有的,Redis提供的哨兵(sentinel)的功能。
主数据库(master)
一个主数据库可以拥有多个从数据库,而一个从数据库只能有一个主数据库
从数据库(slave)
从数据一般只读,并且接收主数据库同步过来的数据
哨兵模式
监控(Monitoring)
Redis Sentinel实时监控主服务器和从服务器运行状态,并且实现自动切换
提醒(Notificaiton)
:当被监控的某个 Redis 服务器出现问题时, Redis Sentinel 可以向系统管理员发送通知, 也可以通过 API 向其他程序发送通知
自动故障转移(Automatic failover)
当一个主服务器不能正常工作时,Redis Sentinel 可以将一个从服务器升级为主服务器, 并对其他从服务器进行配置,让它们使用新的主服务器。当应用程序连接Redis 服务器时, Redis Sentinel会告之新的主服务器地址和端口。
Sentinel
Sentinel作用
Master状态检测
集群模式
所有redis节点都彼此通过PING-PONG机制互联,内部使用二进制协议优化传输速度和宽带
在集群中超过半数节点检测到某个节点Fail后将该节点设置为Fail状态
客户端链接Redis节点直连,连接集群中任何一个可用节点即可对集群进行操作
Redis-Cluster把所有的物理节点都映射到0-16383的slot(槽)上
集群复制原理
redis提供了复制功能,可以实现在主数据库(Master)中的数据更新之后,自动将跟新的数据同步到从数据库(Slave)。一个主数据库可以拥有多个从数据库,而一个从数据库能有一个主数据库
从数据库启动后,会向主数据库发送SYNC命令
主数据库在接受到SYNC命令后会开始在后台保存快照,并将保存快照期间收到的命令缓存起来
在从数据库快照执行完成后,redis会将快照文件和所有缓存的命令以.rdb快照文件形式发送给从数据库
从库将.rdb快照载入本地
从库将.rdb文件载入内存中
在复制初始化后,主数据库在每次收到写命令时都会将命令同步给从数据库,从而保证主数据库的一致性
Ehcache
基于java实现的一套简单,搞笑,线程安全的缓存管理类库,提供了内存,磁盘文件件及分布式存储范式等多种灵活的cache管理方案,特点是快速,轻量,支持持久化
原理
内部采用多线程实现,采用linkedHashMap元素,同时支持将数据持久化到物理磁盘上
特点
快速
采用多闲程机制的特点,数据存取新能高
可伸缩
缓存和磁盘的存储可以伸缩到数十几GB
轻量
安装包只有1.6MB
灵活操作
提供了丰富的接口
支持持久化
支持多重淘汰算法
存储方式
堆存储
将缓存存储在java堆内存中,特点是存取速度快,但容量有限
堆外存储
基于NIO的DirectByteBuffers实现,将缓存数据存储在堆外内存上。
磁盘存储
Ehcache的扩展模块
ehcache-core
ehcache
ehcahe-monitor
ehcahe-web
ehcahe-jcahe
ehcahe-jgroupsreplication
ehcahe-openjpa
ehcahe-server
ehcahe-unlockedreadsview
ehcahe-debugger
应用
引入jar包
org.ehcache ehcache 3.8.1
设置ehcache.xml
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="false"> eternal="false" maxElementsInMemory="10000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="1800" timeToLiveSeconds="259200" memoryStoreEvictionPolicy="LRU"/> name="cloud_user" eternal="false" maxElementsInMemory="5000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="1800" timeToLiveSeconds="1800" memoryStoreEvictionPolicy="LRU"/>
使用Ehcahe缓存
@Cacheable(value = "user",key = "#user.id")
分布式缓存设计
缓存预热
指在用于请求数据前先将数据加载都缓存系统中,有启动加载,和定时加载
定时加载
启动加载
缓存更新
定时更新
过期更新
写请求更新
读请求更新
缓存淘汰策略
FIFo(First in Firstsout)
LRU(Least Recently Used) 最近最少使用
LFU(Least Frequently Used) 最不经常使用
缓存雪崩
在同一时刻由于大量缓存失效,导致大量原本应该访问缓存的请求查询数据库
请求加锁
对于并发量不是很多的应用,使用请求加锁排队的方案防止过多的请求数据库
失效更新
为每一个缓存数据都增加过期标记来记录缓存数据是否失效
设置不同的失效时间
缓存穿透
缓存穿透指由于缓存系统故障或者用户频繁查询系统(缓存和数据库)中不存在的数据
布隆过滤器
指将所有可能存在的数据都映射到一个足够大的Bitmap中,在用户发起请求时首先经过布隆过滤器的拦截,一个一定不存在的数据会被这个布隆过滤器拦截,从而避免对底层存储系统带来查询上的压力
cache null
指如果一个查询返回的结果为null(可能是数据不存在,也可能是系统故障),我们仍然缓存这个null结果,但他的过期时间会很短,通常不超过五分钟
缓存降级
指由于访问量剧增导致服务出现问题(如响应时间慢或不响应),优先保障核心业务的运行,减少或关闭非核心业务对资源的使用。
写降级
在谢请求增大时,可以只进行Cache更新,然后将数据异步保存到数据库中,
读降级
在数据库服务负载过高或数据库系统故障时,可以只对Cache进行读取并将结果返回给用户,在数据库服务正常后再去查询数据库
rabbitMq
简介
消息(Message)是指在应用间传送的数据。消息可以非常简单,比如只包含文本字符串,也可以更复杂,可能包含嵌入对象。
作用
从上面的描述中可以看出消息队列是一种应用间的异步协作机制,那什么时候需要使用 MQ 呢? 以常见的订单系统为例,用户点击【下单】按钮之后的业务逻辑可能包括:扣减库存、生成相应单据、发红包、发短信通知。在业务发展初期这些逻辑可能放在一起同步执行,随着业务的发展订单量增长,需要提升系统服务的性能,这时可以将一些不需要立即生效的操作拆分出来异步执行,比如发放红包、发短信通知等。这种场景下就可以用 MQ ,在下单的主流程(比如扣减库存、生成相应单据)完成之后发送一条消息到 MQ 让主流程快速完结,而由另外的单独线程拉取MQ的消息(或者由 MQ 推送消息),当发现 MQ 中有发红包或发短信之类的消息时,执行相应的业务逻辑。 以上是用于业务解耦的情况,其它常见场景包括最终一致性、广播、错峰流控等等。 作者:预流 链接:https://www.jianshu.com/p/79ca08116d57 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
特点
1.可靠性(Reliability)
RabbitMQ 使用一些机制来保证可靠性,如持久化、传输确认、发布确认
2.灵活的路由(Flexible Routing)
在消息进入队列之前,通过 Exchange 来路由消息的。对于典型的路由功能,RabbitMQ 已经提供了一些内置的 Exchange 来实现。针对更复杂的路由功能,可以将多个 Exchange 绑定在一起,也通过插件机制实现自己的 Exchange 。
3.消息集群(Clustering)
多个 RabbitMQ 服务器可以组成一个集群,形成一个逻辑 Broker 。
4.高可用(Highly Available Queues)
队列可以在集群中的机器上进行镜像,使得在部分节点出问题的情况下队列仍然可用。
5.多种协议(Multi-protocol)
RabbitMQ 支持多种消息队列协议,比如 STOMP、MQTT 等等。
6.管理界面(Management UI)
7.跟踪机制(Tracing)
如果消息异常,RabbitMQ 提供了消息跟踪机制,使用者可以找出发生了什么。
8.插件机制(Plugin System)
RabbitMQ 提供了许多插件,来从多方面进行扩展,也可以编写自己的插件。
rabbitmq架构
一个流行的开源消息队列系统,是AMQP(高级消息队列协议)标准的实现,由以高性能、健壮、可伸缩性出名的Erlang语言开发,并继承了这些优点。rabbitmq简单架构如下:
tcp_acceptor
进程接收客户端连接,创建rabbit_reader、rabbit_writer、rabbit_channel进程。
rabbit_reader
接收客户端接,解析AMQP帧;rabbit_writer向客户端返回数据;
rabbit_channel
解析AMQP方法,对消息进行路由,然后发给相应队列进程。
rabbit_amqqueue_process
是队列进程,在RabbitMQ启动(恢复durable类型队列)或创建队列时创建。
消息组成
Broker
简单来说就是消息队列服务器实体
Exchange
消息交换,它指定消息按什么规则,路由到哪个队列
Queue
消息队列载体,每个消息都会被投入到一个或多个队列,队列类型又分为临时队列,持久化队列,排他队列
Binding
绑定,它的作用就是把exchange和queue按照路由规则绑定起来
Routing Key
路由关键字,exchange根据这个关键字进行消息投递
vhost
虚拟主机,一个broker里可以开设多个vhost,用作不同用户的权限分离
producer
消息生产者,就是投递消息的程序
consumer
消息消费者,就是接受消息的程序
channel
消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务
消息整体发送流程
集群及高可用方案
rabbitmq实现高可用集群部署需要以一台服务器的.erlang.cookie文件为标准,复制到其他服务器上,因为erlang语言必须有一个相同的cookie文件才能进行分布式的通信。
节点间消息同步
集群内节点之间的消息是以同步元数据的方式来同步的。每个节点不会同步其他节点的消息内容,但是会知道哪条消息的元数据在哪个节点上。如node1内存储着消息A,这时有一个请求访问node2,需要消费消息A,那么这时node2会从node1上把A拿出来给这个请求。
数据同步(镜像队列模式)
由于集群内的单点容易出现故障。如果将数据存放在单节点内,其他节点只是同步元数据的话,一旦单点出现故障,则会导致数据丢失。那么解决这种问题就需要数据同步。rabbitmq提供镜像队列模式用于queue和exchange的数据同步
virtual host=选择虚拟主机
name=镜像队列名字
pattern=模式使用正则表达式(^)
apply to=应用到哪里
priority=优先级(数值越大优先级越高)
definition=目标(什么样的内容需要同步)在下图中选择
角色分类
none
不能访问management plugin
manegement
用户可以通过AMQP做的任何事外加: 列出自己可以通过AMQP登入的vitual hosts 查看自己的virtual hosts中的queues、exchanges和bindings 查看和关闭自己的channels和connections 查看有关自己的virtual hosts的“全局”的统计信息,包括其他用户在这些virtual hosts中的活动
policymaker
management可以做的任何事外加: 查看、创建、删除自己的virtual hosts所属的policies和parameters
monitoring
management可以做的任何事外加: 列出所有virtual hosts,包括他们不能登录的virtual hosts 查看其他用户的connection和channels 查看节点级别的数据如clustering和memory使用情况 查看真正的关于所有virtual hosts的全局的统计信息 ———————————————— 版权声明:本文为CSDN博主「不想当裁缝的厨子不是好程序员」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/cdefggg/article/details/88375249
administrator
policymaker和monitoring可以做的任何事外加: 创建和删除virtual hosts 查看、创建、删除users 查看、创建、删除permissions 关闭其他用户的connections
核心概念
producer在发送消息前建立链接connection ,在connection 里可以建立多个channel。producer发送的消息有一个路由键(routerKey),将这个消息先发送到交换机exchange,然后从exchange再发送到queue,而exchange与对应的queue是通过bind绑定在一起的。
AMQP协议
1.AMQP帧组件
帧类型
协议头帧、方法帧、内容帧、消息体帧及心跳帧。每种帧类型都有明确的目的,有些帧的使用频率比其他的高很多:
信道编号
一字节为单位的帧大小
帧有效载荷payload
结束字节标致(ASCII值206)
2.帧类型
协议头帧,方法帧,内容帧,消息帧
3.将消息编组成帧
我们使用方法帧、内容头帧和消息体帧组成一个完整的rabbitmq消息。方法头帧携带命令和执行它所需要的参数(如交换器和路由键)、内容帧包含消息的基本属性以及消息的大小,消息体帧也就是携带我们真正需要发送的消息内容。
4.方法帧接口
5.内容头帧结构
content-type:消息体的报文编码,如application/json
expiration:消息过期时间
reply-to:响应消息的队列名
content-encoding:报文压缩的编码,如gzip
message-id:消息的编号
correlation-id:链路id
deliver-mode:告诉rabbitmq将消息写入磁盘还是内存
user-id:投递消息的用户(发送消息时不要设置该值)
timestamp:投递消息的时间
headers:定义一些属性,可用于实现rabbitmq路由(比如exchange类型是headers的时候用到)
AMQP中的消息路由
Exchange类型
Exchange分发消息时根据类型的不同分发策略有区别,目前共四种类型:direct、fanout、topic、headers 。headers 匹配 AMQP 消息的 header 而不是路由键,此外 headers 交换器和 direct 交换器完全一致,但性能差很多,目前几乎用不到了,所以直接看另外三种类型:
1.direct
消息中的路由键(routing key)如果和 Binding 中的 binding key 一致, 交换器就将消息发到对应的队列中。路由键与队列名完全匹配,如果一个队列绑定到交换机要求路由键为“dog”,则只转发 routing key 标记为“dog”的消息,不会转发“dog.puppy”,也不会转发“dog.guard”等等。它是完全匹配、单播的模式。
2.fanout
交换机会把接收到的消息发送到所有queue
3.topic
topic 交换器通过模式匹配分配消息的路由键属性,将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上。它将路由键和绑定键的字符串切分成单词,这些单词之间用点隔开。它同样也会识别两个通配符:符号“#”和符号“”。#匹配0个或多个单词,匹配不多不少一个单词。
4.header
根据消息的内容来匹配,如消息内有x=1,header交换机绑定queue时也用的x=1来作为规则,那么这条消息会被header发送到指定queue里
5.默认交换机
在producer没有指定交换机的情况下,是会把消息发送到默认的交换机上,默认交换机会根据消息内queue的名字来分发到指定的queue上
集群
普通模式
队列元数据
交换机
vhost元数据
多机多借点部署
绑定关系元数据
federation插件
镜像队列模式
federation插件
设计目标是使rabbitmq在不同的broker借点之间进行消息传递而无须建立集群,该功能在一下场景非常有用: 各个借点运行在不同版本的erlang和rabbitmq上 网络环境不稳定,比如广域网中
localqueue
这个插件的同步原理是在本地创建一个localqueue,broker需要同步queue信息时先把数据同步到本地创建的这个localqueue上,这样就保证数据不会丢失,等到网络稳定时会自动同步到其他的broker插件上。
shovel插件
优势
与上一个插件相比有一点小小差别,shovel能够可靠且持续的从一个broker中的队列(作为源source)拉取数据并转发至另一个broker中的交换机
松耦合
可以移动位于不同管理域中的broker或者集群上的消息,这些broker或者集群可以包含不同的用户和vhost,也可以使用不同的rabbitmq和erlang版本
支持广域网
同样基于AMQP协议在broker之间进行通信,呗设计可以容忍时断时续的连通情形
高度定制
持久化机制
持久化分为队列持久化、消息持久化、交换器持久化不管是持久化的消息是非持久化的消息都可以被写入到磁盘。
队列持久化
队列持久化是在定义队列时的durable参数来实现的,durable为true时,队列才会持久化
消息持久化
消息持久化通过消息的属性deliveryMode来设置是否持久化,在发送消息时通过basicPublish的参数传入。通过传入MessageProperties.PERSISTENT_TEXT_PLAIN实现持久化
交换器持久化
与队列一样,交换器也需要在定义时设置持久化标志。 durable=true则开启持久化 exchangeDeclare(String exchange,String type,boolean durable)
rocketMq
特点
是一个队列模型的消息中间件,具有高性能、高可靠、高实时、分布式等特点
Producer、Consumer、队列都可以分布式
Producer 向一些队列轮流发送消息,队列集合称为 Topic,Consumer 如果做广播消费,则一个 Consumer 实例消费这个 Topic 对应的所有队列,如果做集群消费,则多个 Consumer 实例平均消费这个 Topic 对应的队列集合
能够保证严格的消息顺序
支持拉(pull)和推(push)两种消息模式
高效的订阅者水平扩展能力
实时的消息订阅机制
亿级消息堆积能力
支持多种消息协议,如 JMS、OpenMessaging 等
较少的依赖
核心概念
RocketMQ主要由 Producer、Broker、Consumer 三部分组成,其中Producer 负责生产消息,Consumer 负责消费消息,Broker 负责存储消息。Broker 在实际部署过程中对应一台服务器,每个 Broker 可以存储多个Topic的消息,每个Topic的消息也可以分片存储于不同的 Broker。Message Queue 用于存储消息的物理地址,每个Topic中的消息地址存储于多个 Message Queue 中。ConsumerGroup 由多个Consumer 实例构成。
NameServer
接收broker的请求,注册broker的路由信息 接收client(producer/consumer)的请求,根据某个topic获取其到broker的路由信息 NameServer没有状态,可以横向扩展。每个broker在启动的时候会到NameServer注册;Producer在发送消息前会根据topic到NameServer获取路由(到broker)信息;Consumer也会定时获取topic路由信息。
Broker
消息中转角色,负责存储消息,转发消息。可以理解为消息队列服务器,提供了消息的接收、存储、拉取和转发服务。broker是RocketMQ的核心,它不不能挂的,所以需要保证broker的高可用。
MasterBoker
可以对应多个SlaveBroker
SlaveBroker
生产者
与NameServer集群中的所有节点建立长连接,定期从NameServer读取Topic路由信息,并向提供Topic服务的MasterBroker建立长连接,且定时向MasterBroker发送心跳
消费者
与 Name Server 集群中的其中一个节点(随机)建立长连接,定期从 Name Server 拉取 Topic 路由信息,并向提供 Topic 服务的 Master Broker、Slave Broker 建立长连接,且定时向 Master Broker、Slave Broker 发送心跳。Consumer 既可以从 Master Broker 订阅消息,也可以从 Slave Broker 订阅消息,订阅规则由 Broker 配置决定。
Topic
表示消息的第一级类型,比如一个电商系统的消息可以分为:交易消息、物流消息...... 一条消息必须有一个Topic。
Queue
主题被划分为一个或多个子主题,称为“message queues”。一个topic下,我们可以设置多个queue(消息队列)。当我们发送消息时,需要要指定该消息的topic。RocketMQ会轮询该topic下的所有队列,将消息发送出去。
tags
是topic下的次级消息类型/二级消息类型,可以在同一个Topic下基于Tags进行消息过滤。Tags的过滤需要经过两次对比,
Producer
表示消息列的生产者,消息队列的本质就是实现了publish-subscribe模式,生产者生产消息,消费者消费消息。所以这里的Producer就是用来生产和发送消息的,一般指业务系统。RocketMq提供了发送:普通股消息(同步、异步和单向(one-way)消息)
Producer Group
是一类Producer的集合名称,这类Producer通常发送一个类消息,并且发送逻辑一致
consumer
消息消费者,一般由业务后台系统异步的消费消息
Push Consumer
Consumer 的一种,应用通常向Consumer对象注册一个Listener接口,一旦收到消息,Consumer对象立刻回调Listener接口方法
Pull Consumer
Consumer的一种,应用通常主动调用Consumer的拉消息方法从Broker拉消息,主动权由应用控制
Consumer Group
Consumer Group 是一类Consumer的集合名称,这类Consumer通常消费一类消息,且消费逻辑一致(使用)
mongodb
database
数据库
show collection
查看数据库中的表
show dbs
查看服务骑上所有的数据库
show user
查询
se admin
切换数据库
db.help()
db.test.insert({"_id":"520","name","xiaoming"}) #创建表+数据db.dropDatabase() #删除数据库
collection
数据库表/集合
db.createCollection("ccname")#ccname同dbname一样为自己剪的集合
db.createCollection("ccname"){capped:true,autoIndexID:true,size:10000,max:10000})
capped
autoindexid
size
max
db.ccname.drop
document
数据库记录/文档
文档add
db.zaraList.insert({})
文档delete
db.zaraList.remove({"_id":ObjectId("5bf8b297ac045dc7d0a20b2c")})
db.collection.remove()
db.collection.deleteOne()
db.collection.deleteMany()
文档update
db.collection.update( , , { upsert: , multi: , writeConcern: } )
query
update的查询条件,类似sql update查询内where后面的。
update
update的对象和一些更新的操作符(如$,$inc...)等,也可以理解为sql update查询内set后面的
upsert
可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入
multi
可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。
writeConcern
field
数据字段/域
Oplog
是用于存储 MongoDB 数据库所有数据的操作记录的(实际只记录增删改和一些系统命令操作,查是不会记录的),有点类似于 mysql 的 binlog 日志。
默认存储大小
如何避免数据丢失
设置更大的Oplog
第一次穿件Oplog更改--oplogsize 选项不会影响Oplog大小
使用replSetResizeOplog动态设置大小
集群
Replica Set副本
硬件发生故障事,可以使用副本进行恢复,能够进行故障转移,可以读写分离,减轻主(Primary)的读压力
主节点Primary
收所有的写请求,然后把修改同步到所有Secondary。一个Replica Set只能有一个Primary节点,当Primar挂掉后,其他Secondary或者Arbiter节点会重新选举出来一个主节点。默认读请求也是发到Primary节点处理的,需要转发到Secondary需要客户端修改一下连接配置。 作者:davidpp 链接:https://www.jianshu.com/p/2825a66d6aed 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
副本节点(Seconday)
与主节点保持同样的数据集。当主节点挂掉的时候,参与选主。
仲裁者(Arbiter)
不保有数据,不参与选主,只进行选主投票。使用Arbiter可以减轻数据存储的硬件需求,Arbiter跑起来几乎没什么大的硬件资源需求,但重要的一点是,在生产环境下它和其他数据节点不要部署在同一台机器上。 作者:davidpp 链接:https://www.jianshu.com/p/2825a66d6aed 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
搭建Replica Set流程
启动数据节点
Sharding分片
主从架构
不支链式结构
主(master)
可读可写,有数据修改时,会将oplog同步到所有的slave上
启动master
mongod --port 2000 --master --dbpath masterdb/
启动slave
mongod --port 2001 --slave --source 127.0.0.1:2000 --dbpath slavedb/
从
只可以读
HA
主从方式(非对称方式)
双机双工作方式(互备互援)
集群的工作方式(多服务器互备方式)
docker
优势
更高的利用系统资源
更快速的启动时间
可以做到秒级、甚至毫秒级的启动时间。大大的节约了开发、测试、部署的时间。
一致的运行环境
Docker 的镜像提供了除内核外完整的运行时环境,确保了应用运行环境一致性,从而不会再出现“这段代码在我机器上没问题啊”这类问题。
持续交付和部署
使用Docker可以通过定制应用镜像来实现持续集成、持续交付、部署。
更轻松的迁移
以很轻易的将在一个平台上运行的应用,迁移到另一个平台上,而不用担心运行环境的变化导致应用无法正常运行的情况。
基本概念
镜像(Image)
相当于一个模板,从本质上来说,镜像相当于一个文件系统。Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。
docker image
列出本机所有image文件
REPOSITORY
表示镜像仓库源
Tag
镜像标签
Image Id
镜像ID
Created
镜像创建时间
size
docker image rmi
删除image文件
docker pull [imageName]
拉取镜像
docker run -t -i ubuntu:15.10 /bin/bash
-t
终端
-i
交互式
docker search httpd
查找镜像
docker commit -m="has update" -a="runoob" e218edb10161 runoob/ubuntu:v2
创建镜像
-m
提交的描述信息
-a
制定镜像的作者
runoob/ubuntu:v2
镜像名称
Dockerfile
cat Dockerfile
查看镜像
FROM
指定使用哪个镜像源
RUN
镜像内执行的命令
docker build -t nginx:test .
通过Dockerfile构建一个nginx:test镜像 最后的 .代表本次执行的上下文路径
NAINTAINER
指明进项维护着及其联系方式
COPY
拷贝文件或目录到镜像中,用法同ADD,只是不支持自动下载和解压
ADD
拷贝文件或目录到镜像中
CMD
容器执行时的shell命令
EXPOSE
申明容器运行的服务端口
ENV
设置环境内环境变量
ENTRYPOINT
启动容器时执行Shell命令,同CMD类似,只是由ENTRYPOINT启动的程序不会被docker run命令执行执行的参数覆盖,而且,这些命令参数会被当作参数传递给ENTRYPOINT指定的程序
VOLUME
指定容器挂载的目录
USER
为RUN、CMD和ENTRYPOINT执行Shell命令指定运行用户
HEALTHCHECK
告诉Dodkcer如何测试容器以检查它是否仍然在工作
ARG
构建镜像时指定一些参数
容器(Container)
就是依据进项这个模板创建出来的实体,容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于自己的独立命名空间,因此容器可以拥有自己的root文件系统、自己的网络配置,自己的进程空间,甚至自己的用户ID空间,image 文件生成的容器实例,本身也是一个文件,称为容器文件。也就是说,一旦容器生成,就会同时存在两个文件: image 文件和容器文件。而且关闭容器并不会删除容器文件,只是容器停止运行而已。
启动容器
docker container run -it ubuntu bash
docker run -i -t ubuntu /bin/bash
-t定义一个伪终端 -i 允许你对容器的标准输入进行交互
docker ps
container ID
容器id
IMAGE
使用的镜像
COMMAND
启动容器时运行的命令
CREATED
STATUS
状态
created
restarting
running
removing
paused
exited
dead
PORTS
容器的端口类型和使用连接信息
NAMES
docker logs 进程名/进程id
查看输出
容器的使用
docker pull ubuntu
获取镜像
docker run -it ubuntu /bin/bash
启动容器
docker run -itd ubuntu-test ubuntu /bin/bash
后台运行
docker stop id
docker restart id
docker attach 容器id
从后台进入容器,退出时会导致容器的停止
docker exec 容器id
从后台进入容器,退出时不会导致容器的停止
docker export 容器id > ubuntu.tar
导出容器的快照到本地文件
docker rm -f 容器id
docker run -d -p 5000:5000 training/webapp python app.py
指定端口启动容器
docker port 容器id
查询容器端口
docker top 容器id
查看容器进程
docker inspect 容器id
查询容器底层信息
docker top 容器id
docker start 容器id
docker ps -l
查询最后一次创建的容器
docker rm 容器id
删除容器
docker run [containID]
docker kill [containID]
对于不会自动终止的容器,必须使用docker container kill
容器连接
docker run -d -P training/webapp python app.py
-P :是容器内部端口随机映射到主机的高端口。
docker run -d -p 5000:5000 training/webapp python app.py
docker port adoring_stonebraker 5000
查看端口绑定情况
容器互联
-name
容器的命名
docker network create -d bridge test-net
新建一个网络
docker run -itd --name test1 --network test-net ubuntu /bin/bash
运行一个容器并连接到新建的 test-net 网络:
Docker Compose
轻松搞笑的管理容器
1.安装compose
2.准备测试目录
3.创建Dockerfile文件
4.创键docker-compose.yml
web
该 web 服务使用从 Dockerfile 当前目录中构建的镜像。然后,它将容器和主机绑定到暴露的端口 5000。此示例服务使用 Flask Web 服务器的默认端口 5000
redis
该 redis 服务使用 Docker Hub 的公共 Redis 映像。
5.使用Compose命令构建和运行
docker-compose up
docker-compose up -d
后台执行
yml配置指令参数
version
compose版本
build
指定为构建镜像上下文路径
context
上下文路径
args
添加构建参数,这是只能在构建过程中访问的环境变量。
labels
设置构建的镜像标签
target
多层构建,可以指定构建那一层
cap_add
添加宿主机的内核工能
cap_drop
删除宿主机的内核工能
cgroup_parent
为容器指定父 cgroup 组,意味着将继承该组的资源限制。
command
command: ["bundle", "exec", "thin", "-p", "3000"]
container_name
指定自定义容器名称,而不是生成的默认名称。 container_name: my-web-container
endpoint_mode
访问集群方服务的方式 endpoint_mode: vip # Docker 集群服务一个对外的虚拟 ip。所有的请求都会通过这个虚拟 ip 到达集群服务内部的机器。 endpoint_mode: dnsrr # DNS 轮询(DNSRR)。所有的请求会自动轮询获取到集群 ip 列表中的一个 ip 地址。
labels
在服务上设置标签。可以用容器上的 labels(跟 deploy 同级的配置) 覆盖 deploy 下的 labels。
mode
指定服务提供的模式。
replicated
复制服务,复制指定服务到集群的机器上。
global
全局服务,服务将部署至集群的每个节点
Docker Matchine
管理的虚拟主机可以是机上的,也可以是云供应商,使用 docker-machine 命令,您可以启动,检查,停止和重新启动托管主机,也可以升级 Docker 客户端和守护程序
docker-machine ls
docker-machine create --driver virtualbox test
创建一台名为test的机器 --driver指定用来创建机器的驱动类型
docker-machine ip test
查看机器的ip test为机器名称
docker-machine stop test
停止机器
docker-machine ssh test
仓库(Repository)
软件包上传下载站,有各种软件的不同版本被上传供用户下载。
public(公有仓库)
private(私有仓库)
Docker Hub
docker login
docker logout
关键动作
Build(构建镜像)
Ship(运输镜像)
Run(运行镜像)
应用场景
快速部署
尤其是集群服务,把各个服务及依赖的环境都打包成一个个镜像,上传镜像到私有仓库中,集群各个节点从私有仓库下拉镜像然后运行即可,不需要再给每个服务器安装 JDK、Nginx 等环境。
同步开发环环境和生产环境
大家都经常遇到过:程序在自己电脑运行正常,放到服务器上就报错了,这就是运行环境不同造成的,而通过 Docker在本机配置好 Dockerfile 文件后创建镜像,上传镜像库,再从镜像库下载到生产服务器,直接运行,这样就保证了开发和生产环境的一致。
分层存储
镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。比如,删除前一层文件的操作,实际不是真的删除前一层的文件,而是仅在当前层标记为该文件已删除。在最终容器运行的时候,虽然不会看到这个文件,但是实际上该文件会一直跟随镜像。因此,在构建镜像的时候,需要额外小心,每一层尽量只包含该层需要添加的东西,任何额外的东西应该在该层构建结束前清理掉。
swarm
swarm 集群由管理节点(manager)和工作节点(work node)构成。
swarm manager
负责整个集群的管理工作包括集群配置、服务管理等所有跟集群有关的工作
maven
项目的目录结构
pom文件定于一个maven项目的maven配置,一般pom文件的放在项目或者模块的根目录下
${basedir}
存放pom.xml和所有的子目录
${basedir}/src/main/java
项目的java源代码
${basedir}/src/main/resources
项目的资源,比如说property文件,springmvc.xml
${basedir}/src/main/webapp/WEB-INF
web应用文件目录,web项目的信息,比如存放web.xml、本地图片、jsp视图页面
${basedir}/target
打包输出目录
${basedir}/target/classes
编译输出目录
${basedir}/target/site
生成文档的目录,可以通过index.html查看项目的文档
${basedir}/target/test-classes
测试编译输出目录
根元素和必要配置
<modelVersion>4.0.0</modelVersion>
版本模型
<groupId>com.companyname.project-group</groupId>
<artifactId>project</artifactId>
项目的唯一ID,一个groupId下面可能多个项目,就是靠artifactId来区分的
<version>1.0</version>
项目当前版本,格式为:主版本.次版本.增量版本-限定版本号
<packaging>jar</packaging>
项目产生的构件类型,包括jar、war、ear、pom等
父项目和parent元素
parent
父项目的坐标,坐标包括group ID,artifact ID和version 如果项目中没有规定某个元素的值,那么父项目中的对应值即为项目的默认值
<artifactId>com.companyname.project-group</artifactId>
被继承的父项目的构件标识符
<relativePath>../pom.xml</relativePath>
父项目的pom.xml文件的相对路径,默认值是../pom.xml
项目构建需要的信息
构建项目要的信息
项目源码目录,当构建项目的时候,构建系统会编译目录里的源码。该路径是相对于pom.xml的相对路径。
被编译过的应用程序class文件存放的目录
-该元素设置了项目单元测试使用的源码目录。该路径是相对于pom.xml的相对路径
被编译过的测试class文件存放的目录。
资源管理<resources></resources>
这个元素描述了项目相关或测试相关的所有资源路径
描述了资源的目标输出路径。该路径是相对于target/classes的路径,如果是想要把资源直接放在target/classes下,不需要配置该元素
是否使用参数值代替参数名。参数值取自文件里配置的属性,文件在filters元素里列出
-包含的模式列表,例如**/*.xml,只有符合条件的资源文件才会在打包的时候被放入到输出路径中
排除的模式列表,例如**/*.xml,符合的资源文件不会在打包的时候会被过滤掉
这个元素描述了单元测试相关的所有资源路径,例如和单元测试相关的属性文件
插件管理
子项目可以引用的默认插件信息。pluginManagement中的插件直到被引用时才会被解析或绑定到生命周期 这里只是做了声明,并没有真正的引入。给定插件的任何本地配置都会覆盖这里的配置
可使用的插件列表
plugin元素包含描述插件所需要的信息
插件定位坐标三元素:groupId + artifactId + version
- 是否使用这个插件的Maven扩展(extensions),默认为false 由于性能原因,只有在真需要下载时,该元素才被设置成enabled
在构建生命周期中执行一组目标的配置。每个目标可能有不同的配置。
execution元素包含了插件执行需要的信息
执行目标的标识符,用于标识构建过程中的目标,或者匹配继承过程中需要合并的执行目标
绑定了目标的构建生命周期阶段,如果省略,目标会被绑定到源数据里配置的默认阶段
配置的执行目标
配置是否被传播到子POM
作为DOM对象的配置
项目引入插件所需要的额外依赖,参见dependencies元素
任何配置是否被传播到子项目
作为DOM对象的配置
项目依赖相关信息
pom文件中通过dependencyManagement来声明依赖,通过dependencies元素来管理依赖。dependencyManagement下的子元素只有一个直接的子元素dependencice,其配置和dependencies子元素是完全一致的;而dependencies下只有一类直接的子元素:dependency。 一个dependency子元素表示一个依赖项目。
<groupId>org.apache.maven</groupId>
<artifactId>maven-artifact</artifactId>
<version>3.8.1</version>
<type>jar</type>
———————————————— 版权声明:本文为CSDN博主「哎呀呀别老偷看我」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_40419080/article/details/116464380
依赖的分类器。分类器可以区分属于同一个POM,但不同构建方式的构件。分类器名被附加到文件名的版本号后面 如果想将项目构建成两个单独的JAR,分别使用Java 4和6编译器,就可以使用分类器来生成两个单独的JAR构 件
依赖排除,即告诉maven只依赖指定的项目,不依赖该项目的这些依赖。此元素主要用于解决版本冲突问题
<optional>true</optional>
可选依赖,用于阻断依赖的传递性。如果在项目B中把C依赖声明为可选,那么依赖B的项目中无法使用C依赖
<scope>test</scope>
依赖范围。在项目发布过程中,帮助决定哪些构件被包括进来 - compile:默认范围,用于编译; - provided:类似于编译,但支持jdk或者容器提供,类似于classpath - runtime: 在执行时需要使用; - systemPath: 仅用于范围为system。提供相应的路径 - test: 用于test任务时使用; - system: 需要外在提供相应的元素。通过systemPath来取得 - optional: 当项目自身被依赖时,标注依赖是否传递。用于连续依赖时使用
MavenWrapper
安装
1.安装方式
mvn -N io.takar:maven:0.7.6:wrapper
2.从别的项目将mvnw和mvnw.conf文件以及.mvn目录copy过来
maven环境都会下载到${home}/.m2./dist目录
修改maven版本只需要修改项目下的.mvn/wrapper/maven-warper.properties
mvnw会在每次执行命令时检测${home}/.m2/wrapper.dists目录下是否有maven-wrapper.properties中指定Maven版本,如果没有就自动下载
使用
.\mvnw或.\mvnw.cmd
gradle
数据库
sql 优化
主从配置
分库分表
水平切分
垂直切分
数据库存储引擎
MySql
MyIASM
特点
不支持数据库事物
不支持行级锁
不支持外键
支持表锁
直接查找offset定位比InnoDb要快(Innodb寻址要先映射到块,再映射到行)
InnoDB
特点
提供了事物支持,回滚,崩溃修复能力
多版本控制(MVCC)
事物安全
InnoDB对应的大小是固定的一半设为16KB(磁盘一页的页数)
支持外检约束
支持自动增加列属性auto_increment
灾难回复(binlog)
数据结构
B+
TokuDb
数据结构
Factal Tree
除了每一个指针(key),都需要指向一个child(孩子)节点,child节点带一个Message buffer
messageBuffer
是一个先进先出队列,用来缓存更新操作
Memory
存放在内存中,通常使用Hash索引来实现数据索引,一旦服务器关闭,表中的数据就会丢失
临时表
CREATE TEMPORARY TABLE IF NOT EXISTS temp_user ( id INT NOT NULL DEFAULT 0, name VARCHAR(10) NOT NULL ); CREATE TEMPORARY TABLE temp_user AS SELECT * FROM tb_user;
创建
CREATE TEMPORARY TABLE IF NOT EXISTS temp_user ( id INT NOT NULL DEFAULT 0, name VARCHAR(10) NOT NULL ); CREATE TEMPORARY TABLE temp_user AS SELECT * FROM tb_user;
数据处理
联机事务处理OLTP
统强调数据库内存效率,强调内存各种指标的命令率,强调绑定变量,强调并发操作;
联机分析处理OLAP
系统则强调数据分析,强调SQL执行市场,强调磁盘I/O,强调分区等。
创建索引规则
选择唯一索引
唯一索引一般基于Hash算法实现,可以快速,唯一的定位某条数据
经常作为查询条件的字段建立索引
限制索引的数量
索引数量越多,数据更新表约满,因为在数据更新时会不断计算和添加索引
尽量使用数据量少的索引
如果索引的值很长,则占用的磁盘变大,查询数据会受到影响
尽量使用前缀索引
删除不使用或使用很少的索引
尽量选择区分度高的列作为索引
区分度表示字段值不重复的比例
索引列不能参与计算
尽量扩展现有的索引:联合索引的查询比多个独立索引查询高很多
数据库第三范式
第一范式
每一列都是不能再分的最小数据单位(也叫最小原子单位)
第二范式
在第一范式的基础上,规定表中的非主列不存在对主键的部分依赖
第三范式
满足第一范式和第二范式,并且表中的列不存在对非主键列的传递依赖
数据库事物
原子性
事物是一个完整的操作,参与事物的业务逻辑单元
一致性
在事物执行完成时(无论是正常执行完毕还是异常退出)数据都必须处于一致状态
隔离性
对数据进行修改的所有并发事务都是彼此隔离的,他不应该以任何方式影响或者依赖其他事务
永久性
在事物操作完成后,对数据的修改将被持久化到永久性存储中
存储过程
尽量利用一些sql语句代替一些小循环,列如聚合函数,求平均函数
中间结果被存在临时表中,并加索引
少使用有标
sql是中集合语句,对于集合运算有较高的性能,而游标是过程运算。
事务越短越好
s
使用try-catch
经量不要将查询语句放在循环中,防止过度消耗资源
触发器
数据库锁
行锁
表锁
页锁
日志
PropertyPlaceholderHelper
replacePlaceholder
return parseStringValue(value,placeholderResolver,null);
parseStringValue()
findPlaceholderEndIndex(result,startIndex)
parseStringValue(placeholder,placeholderResolver,visitedPlaceholders)
ProperyResolver
ConfigurablePropertyResolver
AbstractPropertyResolver
resolveRequiredPlacehodler(String text)
替换占位符
createPlaceholderHelper(false)
创建helper pirvate PropertPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders){ return new PropertyPlaceholderHelper(this.placeholderPrefx,this.placeholderSuffix,this.valueSeparator,ignoreUnresolveablePlaceholders); }
doResolvePlaceholders(text,this.stricHelper)
进行替换 private String doResolvePlaceholders(String text,PropertyPlaceholderHelper helper){ return helper.replacePlaceholders(text,this::getPropertyAsRaswString) }
Environment
子类
StandardEnviroment
成员函数
System.getEnv()
获取系统的属性值
System.getProperties()
获取配置信息
customizePropertySources
成员变量
“systemProperties”
系统属性
“systemEnironment”
系统环境
private final MutablePropertySources proertySources = new MutablePropertySources()
用来加属性资源
子类
StandardServletEnvironment
ConfigurableEnvironment
子类
AbstractEnviroment
构造方法
public AbstractEnvironment(){customizePropertySources(this.propertySources)}
public String resolveRequiredPlaceholders(String text)
return this.propertyResolver.resolveRequiredPlaceHolders(text)
成员方法
getSystemProperties()
获取配置值
getSystemEnviroment()
获取系统境变量
成员变量
"spring.getenv.ignore"
设置忽略的环境变量的配置
"spring.profile.active"
设置哪属性是激活的
"default"
默认使用的当前的文件
SpringBean
普通对象
我们自定义需要的对象
容器对象
内置对象Spring需要的对象
FactorBean
beanFactory和factoryBean的区别
共同:都是用来创建对象的 不同:当使用BeanFactory的时候都必要遵循完整的创建过程,这个过程是由spring来管理控制, 而使用FactoryBean只需要调用getObject就可以返回具体的对象,整个对象的创建过程是由用户自己来控制额,更加灵活
getObject
isSingleton
getObjectType
创建对象
实例化
在堆中开辟一块空间
对象的属性都是默认值
初始化
给属性设置值
填充属性
执行初始化方法
init-method
PostProcessor
后置处理其增强器
BeanFatoryPostProcessor
增强beanDefiniton信息
BeanPostProcessor
增强bean信息
JAVA大数据
hadoop
HDFS
hadoop distributed file system
主从结构
主节点 nameNode
存储文件的元数据:如文件名,文件目录结构,文件属性(创建时间、副本数、文件权限),以及每个文件的块列表和块所在的dataNode等
fsimage(元数据镜像文件)
存储某一时段NameNode内存元数据信息
edits
操作日志文件
fstime
保存最近一次checkpoint时间点
从节点 dataNode
保存具体的block数据 负责数据的读写操作和复制操作 DataNode启动时会向NameNode报告当前存储的数据块信息,后续也会定时报告修改信息 DataNode之间会进行通信,复制数据块,保证数据的冗余性
block
最基本的存储单位,一个文件长度大小是size,那么从文件的0偏移开始,按照固定的大小,顺序对文件进行划分并编号,划分好的灭一个块称一个block.HDFS默认block大小是128MB,以一个256MB文件
dfs.block,size
如果一个文件小于一个数据块的大小,并不占用整个数据块存储空间;
replication
多副本,默认三
Secondary NameNode
合并fsimage和edits文件来更新NameNode的metedata HA的一个解决方案。但不支持热备。配置即可。 执行过程:从NameNode上下载元数据信息(fsimage,edits),然后把二者合并,生成新的fsimage,在本地保存,并将其推送到NameNode,替换旧的fsimage.
工作流程
secondary通知namenode切换edits文件
secondary从namenode获得fsimage和edits(通过http)
secondary将fsimage载入内存,然后开始合并edits
secondary将新的fsimage发回给namenode
namenode用新的fsimage替换旧的fsimage
checkPoint
fs.checkPoint.period
指定两次checkPoint的最大时间间隔,默认为3600秒
fs.checkpoint.size
规定edits文件的最大值,一旦超过这个值则强制checkpoint,不管是否到达最大时间间隔。默认大小是64M。
client
切分文件,访问HDFS时,首先与NameNode交互,获取目标文件的位置信息,然后与DataNode交互,读写数据
读过程
1.初始化FileSystem,然后客户端(client)用FileSystem的open()函数打开文件
2.FileSystem用RPC调用元数据节点,得到文件的数据块信息,对于每一个数据块,元数据节点返回保存数据块的数据节点的地址
3.FIleSystem返回FSDataInputStream给客户端,用来读取数据,客户端调用stream的read()函数开始读取数据
4.DSFInputStream连接保存此文件第一个数据块最近的数据节点,data从数据节点到客户端(client)
5.当客户端读取完毕时,DFSInputStream关闭和此数据节点的连接,然后连接此文件下一个数据块的最近的数据节点
6.此数据块读取完毕数据时候,调用FSDataInputStream的close函数
7.在读取数据的过程中,如果客户端在数据节点通信出现错误,则尝试连接包含此数据块的下一个数据节点
8.失败的数据节点将被几率,此后不再连接
写过程
1.初始化FileSystem,客户端调用create()来创建文件
2.FileSystem用RPC调用元数据节点,在文件系统的命名空间中创建一个新的文件,元数据节点受限确定文件原来不存在,并且客户端有创建文件的权限,然后创建新文件
3.FileSystem返回DFSOutStream,客户端用于写数据,客户端开始写入数据
4.DFSOutputStream将数据块分成块,写入data queue。data queue由Data Streamer将数据块写入pipeline中的第一个数据节点。第一个数据节点将数据块发送给第二个数据节点。第二个数据节点将数据发送给第三个数据节点
5.DFSOutputStream为发出去的数据块保存了ack queue,等待pipeline中的数据节点告知数据已经写入成功
6.当客户端结束写入数据,则调用stream的close函数。此操作将所有的数据块写入pipeline中的数据节点,并等待ack queue返回成功。最后通知元数据节点写入完毕。
7.如果数据节点在写入的过程中失败,关闭pipeline,将ack queue中的数据块放入data queue的开始,当前的数据块在已经写入的数据节点中被元数据节点赋予新的标示,则错误节点重启后能够察觉其数据块是过时的,会被删除。失败的数据节点从pipeline中移除,另外的数据块则写入pipeline中的另外两个数据节点。元数据节点则被通知此数据块是复制块数不足,将来会再创建第三份备份。
容错机制
故障类型(三类故障)
1.节点失效
DN节点失效
2.网络故障
无法发送和接收数据
3.数据损坏(脏数据)
故障检测机制
1.节点失败检测
如果是NameNode故障,整个集群都会挂掉,每个DN以固定周期(3秒)向NN发送心跳信号,通过这种方式告诉NN是正常工作
2.通信故障检测机制
只要发送了数据,接收方就会返回确认码
3.数据错误检测机制
在传输数据的时候,同时会发送总和校验码,所有的DN都会定期向NN发送数据块的存储状况
回复:心跳信息和数据块报告
读写容错
写容错
以数据包(通常为64kb)写入数据,同时每个DN只要收到数据包就会返回缺人应答,确保所有数据都已经存储,所以,如果没有收到确认码,client就知道DN挂掉了,会调整路线跳过DN,这样就会导致数据块的备份数量少于指定的份数,但是NN就会很快注意到
读容错
数据节点(DN)失效
有两张表数据块列表和DN列表,NN发现某个DN上的数据块存在错误,会跟新第一张表(从数据块里列表中删除此数据块)如果发现DN失效,就会同事更新这两张表
HDFS备份规则
1.机架与数据节点
集群被分配给不同的机架,每一个机架都拥有多个DN节点
2.副本放置策略
第一个副本存放策略很简单,若写入方式集群中的一个节点,那么就把副本1存储在本机架节点上,否则随机选择DN
YARN
yet another resource negotiator 资源管理调度系统
NodeManger
常驻进程
1.管理单个节点的资源
2.处理来自ResourceManager的命令
3.处理来自ApplicationMaster的命令
ApplicationMaster
是ResourceManager临时启用的一个节点,不是常驻进程
1.负责数据的切分。任务的容错和监控
2.为应用程序申请资源分配给内部任务
ResouceManager
1.处理客户端请求 2.监控nodeManager 3.启动或者监控applicationMaster
Client Service
应用的提交,终止,输出信息(应用,队列,集群等的状态信息)
Adaminstration Service
队列,节点,Client权限管理
ApplicationMasterService
注册、总之ApplicationMaster,获取ApplicationMaster的资源申请或取消的请求,并将其异步地传给Scheduler, 单线程处理
Scheduler
负责为集群中运行的各个application分配所需要的资源。Scheduler只负责资源的调度,它不做任何对application监控或跟踪的工作,此外,在任务由于各种原因执行失败时,它也不负责对任务进行重启。
FIFO Scheduler
这就是先进先出(first in, first out)调度策略
Capacity Scheduler
队列 A 和 B 享有独立的资源,但是 A 所占的资源比重更多。如果任务在被执行的时候,集群恰好有空闲资源,比如队列 B 为空,那么调度器就可能分配更多的资源给队列 A,以更好地利用空闲资源。这种处理方式被叫做「queue elasticity」(弹性队列)。 但是弹性队列也有一些副作用,如果此时队列 B 有了新任务,之前被队列 A 占用的资源并不会立即释放,只能等到队列 A 的任务执行完。为了防止某个队列过多占用集群资源,YARN 提供了一个设置可以控制某个队列能够占用的最大资源。但这其实又是跟弹性队列冲突的,因此这里有一个权衡的问题,这个最大值设为多少需要不断试验和尝试。
root
prod
dev
eng
science
Fair Scheduler
试图为每个任务均匀分配资源,比如当前只有任务 1 在执行,那么它拥有整个集群资源,此时任务 2 被提交,那任务 1 和任务 2 将平分集群资源,以此类推。
ApplicationManager
Container
非常驻进程,他是yarn中的资源抽象,封装了某个节点上的多维度资源,入内存,cpu,磁盘网络,
yarn执行流程
Client向YARN提交一个job,首先向ResourceManager中的ApplicationManager申请资源,用于运行本作业的ApplicationMaster。 ApplicationManager给集群中的一个NodeManager发命令,通知其创建一个Container并运行作业的ApplicationMaster。 NodeManager创建一个Container并启动作业的ApplicationMaster。 ApplicationMaster将自己注册到ApplicationManager,使得ApplicationManager可以监控到Job的执行状态,Client也可以通过ApplicationManager对作业进行控制。 Scheduler将资源分配信息发给ApplicationMaster。 ApplicationMaster将获取到的资源分配信息发送给各个NodeManager。 各个NodeManager接收到资源分配命令,创建Container并启动对应的task。 各个task直接与ApplicationMaster进行通信,汇报心跳和任务执行进度。 所有的Task都执行完毕,将讲过反馈给ApplicationMaster。ApplicationMaster再将任务执行的结果反馈ApplicationManager。
MapReduce
分布式运算架构
mapReduce编程模型
map阶段
map 函数:(k1:v1)-->[(k2;v2)] 并行处理计算数据 对集合里的每个目标应用同一个操作。即,如果你想把表单里每个单元格乘以二,那么把这个函数单独地应用在每个单元格上的操作就属于mapping。
输入
键值对(k1:v1)表示数据
处理
文档数据记录(如文本文件中的一行)以键值对的形式传入map函数,处理完后以另一种键值对的形式输出中间处理结果[k2:v2]
输出
键值对[k2;v2]表示的一组中间数据
reduce阶段
(k2:[v2])-->[(k3:v3)] 对map结果进行汇总 遍历集合汇总的元素来返回一个综合的结果,即输出表单里一列数字和这个任务属于reducing.你向mapReduce框架提交一个计算作业时,它会首先把计算作业拆分成若干个Map任务,然后分配到不同的节点上去执行,每一个map任务处理输入数据的一部分,当map任务完成后,它会生成一些中间文件,这些中间文件将会作为Reduce任务的输入数据Reduce任务的主要目标就是把前面若干个Map的输出汇总到一起并输出
输入
map输出的一组键值对[(k2:v2)]将被进行合并处理将同样主键下的不同数值合并到一个列表[v2]中,故reduce的输入为(k2:[v2])
处理
对传入的中间结果列表数据进行某种调整或者进一步的处理,并输出最终的某种键值对形式的结果[(k3:v3)]
输出
键值对[k3:v3]表示的最终数据
整体处理流程
1.各个map函数对所划分的数据并行处理,从不同的输入数据产生不同的中间结果输出
2.各个reduce也各自子并行计算,各自负责处理不同的中间结果数据集合
3.进行reduce处理之前,必须等到所有的map函数做完,因此,在进入reduce前需要要有一个同步保障(barrier);
4.所有reduce的输出结果即可获得最终结果
编写MapReduce程序
原理分析
基本过程
1.把userprogram的输入文件划分为M份(M为用户定义)如图左方所示分成了split0~4;然后使用fork将用户进程拷贝到集群内其它机器上。
2.user program 的副本中有一个称为master,其余的worker,master是负责调度的,为空闲的worker分配作业(Map作业或者Reduce作业),worker数量也是可以由用户指定
3.被分配了Map作业的worker,开始读取对应分片的输入数据,Map作业数量是由M决定的,和split一一对应;Map作业从输入数据中抽取键值1对,每一个键值对都作为参数传递给map函数,map函数产生的中间键值对被缓存在内存中。
4.缓存的中间键值对会被定期写入本地磁盘,而且被分为R个区,R的大小是由用户定义的,将来每个区会对应一个Reduce作业;这些中间键值对的位置会被通报给master.master负责将信息转发给Reduce worker
5.master通知分配了Reduce作业的worker它负责的分区在什么位置(肯定不止一个地方,每个Map作业产生的中间键值对都可能映射到所有R个不同的分区),当Reduce worker把所有它负责的中间键值对都读过来后,先对它们进行排序,使得相同键的键值对聚集在一起。因为不同的键可能会映射到同一个分区也就是同一个Reduce作业(谁让分区少呢),所以排序是必须的。
6.reduce worker遍历排序后的中间键值对,对于每个唯一的键,都将键与关联的值传递给reduce函数,reduce函数产生的输出会添加到这个分区的输出文件中
7.当所有的Map和Reduce作业都完成了,master唤醒正版的user program,MapReduce函数调用返回user program的代码。
8)所有执行完毕后,MapReduce输出放在了R个分区的输出文件中(分别对应一个Reduce作业)。用户通常并不需要合并这R个文件,而是将其作为输入交给另一个MapReduce程序处理。整个过程中,输入数据是来自底层分布式文件系统(GFS)的,中间数据是放在本地文件系统的,最终输出数据是写入底层分布式文件系统(GFFS)的。而且我们要注意Map/Reduce作业和map/reduce函数的区别:Map作业处理一个输入数据的分片,可能需要调用多次map函数来处理每个输入键值对;Reduce作业处理一个分区的中间键值对,期间要对每个不同的键调用一次reduce函数,Reduce作业最终也对应一个输出文件。
MapReduce输入和输出问题
序列化操作
key类必须实现WriteableComparable接口
Map/Reduce作业的输入和输出类型
(input) ->map -> -> combine -> -> reduce -> (output)
MapReduce实际处理流程
分而治之,将输入进行分片,然后交给不同的task进行处理,然后合并成最终的解。 mapreduce实际的处理过程Input->Map->Sort->Combine->Partition->Reduce->Output。
1.Input阶段
数据以一定的格式传递给Mapper,有TextInputFormat,DBInputFormat,SequenceFileFormat等可以使用,在Job.setInputFormat可以设置,也可以自定义分片函数。
2.map阶段
对输入的(key,value)进行处理,即map(k1,v1)->list(k2,v2),使用Job.setMapperClass进行设置。
3.Sort阶段
对于Mapper的输出进行排序,使用Job.setOutputKeyComparatorClass进行设置,然后定义排序规则。
4.Combine阶段
这个阶段对于Sort之后又相同key的结果进行合并,使用Job.setCombinerClass进行设置,也可以自定义CombineClass类
5.Partion阶段
将Mapper的中间结果按照key的范围划分为R份(reduce作业的个数),默认使用HashPartioner(key.hashCode()&Integer.MAX_VALUE%numPartitions),也可以自定义划分的函数,使用Job.setPartitionClass设置
6.Reduce阶段
对于Mapper阶段的结果进行进一步的处理,Job.setReducerClass进行设置自定义的Reduce类
7.Output阶段
Reducer输出数据的格式
一个Job的运行流程
作业提交
使用runjob方法创建一个JobClient实例,然后调用submitJob()方法进行作业的提交
1.通过调用JobTracker对象的getNewJobId()方法从JobTracker处获得一个作业Id
2.检查作业的相关路径。如果输出路径存在,作业将不会被提交(保护上一个作业运行结果)
3.计算作业的输入分片,如果无法计算,例如输入路径不存在,作业将不被提交,错误返回给mapreduce程序
4.将运行作业所需资源(作业jar文件,配置文件和计算得到的分片)
5.告知JobTracker作业准备执行(使用jobTracker)对象的submitJob()方法来真正提交作业
作业初始化
当JobTracker收到Job提交的请求后,将Job保存在一个内部队列,并让Job Scheduler(作业调度器)处理并初始化。初始化涉及到创建一个封装了其tasks的job对象, 并保持对task的状态和进度的跟踪(step 5)。当创建要运行的一系列task对象后,Job Scheduler首先开始从文件系统中获取由JobClient计算的input splits(step 6),然后 再为每个split创建map tas
任务分配
TaskTracker和JobTracker之间的通信和任务分配是通过心跳机制完成的。TaskTracker作为一个单独的JVM,它执行一个简单的循环,主要实现每隔一段时间向JobTracker 发送心跳,告诉JobTracker此TaskTracker是否存活,是否准备执行新的任务。如果有待分配的任务,它就会为TaskTracker分配一个任务。
任务执行
TaskTracker申请到新的任务之后,就要在本地运行,首先,是将任务本地化(包括运行任务所需的数据、配置信息、代码等),即从HDFS复制到本地。调用localizeJob()完成的。 对于使用Streaming和Pipes创建Map或者Reduce程序的任务,Java会把key/value传递给外部进程,然后通过用户自定义的Map或者Reduce进行处理,然后把key/value传回到java中。 其中就好像是TaskTracker的子进程在处理Map和Reduce代码一样。
更新任务执行进度和状态
进度和状态是通过heartbeat(心跳机制)来更新和维护的。对于Map Task,进度就是已处理数据和所有输入数据的比例。对于Reduce Task,情况就邮电复杂,包括3部分, 拷贝中间结果文件、排序、reduce调用,每部分占1/3。
作业完成
当Job完成后,JobTracker会收一个Job Complete的通知,并将当前的Job状态更新为successful,同时JobClient也会轮循获知提交的Job已经完成,将信息显示给用户。 最后,JobTracker会清理和回收该Job的相关资源,并通知TaskTracker进行相同的操作(比如删除中间结果文件)
子主题 11
客户端
client,编写mapreduce程序,配置作业,提交作业
jobTracker
协调这个作业的运行,分配作业,初始化作业,与TaskTracker进行通信
TaskTracker
负责运行作业,保持与JobTracker进行通信
HDFS
分布式文件系统,保持作业的数据和结果
MapReduce框架结构及核心运行机制
结构
MRAppMaster
负责整个程序的过程调度及状态协调
mapTask
负责map阶段的整个数据处理流程
ReduceTask
负责reduce阶段的整个数据处理流程
MapReduce运行流程解析
1.启动MRAppMaster
一个mr程序启动的时候,最先启动的是MRAppMaster,MRAppMaster启动后根据本次job的描述信息,计算出需要的maptask实例数量,然后向集群申请机器启动 相应数量的maptask进程。
2.启动后根据给定的数据切片范围进行数据处理,
利用客户指定的inputformat来获取RecordReader读取数据,形成输入KV对 将输入KV对传递给客户定义的map()方法,做逻辑运算,并将map()方法输出的KV对收集到缓存 将缓存中的KV对按照K分区排序后不断溢写到磁盘文件
3.启动相应数量的reducetask进程,并告知reducetask进程要处理的数据范围
MRAppMaster监控到所有maptask进程任务完成之后,会根据客户指定的参数启动相应数量的reducetask进程,并告知reducetask进程要处理的数据范围(数据分区)
4.Reducetask进程启动之后,根据MRAppMaster告知的待处理数据所在位置,从若干maptask运行所在机器上获取到若干个maptask输出结果文件,并在本地进行重新归并排序
MapTask并行度决定机制
maptask的并行度决定map阶段的任务处理并发度,进而影响到整个job的处理速度
mapTask并行度的决定机制
一个job的map阶段并行度由客户端在提交job时决定而客户端对map阶段并行度的规划的基本逻辑为: 将待处理数据执行逻辑切片(即按照一个特定切片 大小,将待处理数据划分成逻辑上的多个split), 然后每一个split分配一个mapTask并行实例处理
FileInputFormat切片机制
FIleInputFormat切片机切片定义在InputFormat类中的getSplit()方法
FIleInputFormat中默认的切片机制
简单地按照文件的内容长度进行切片 切片大小,默认等于block大小 切片时不考虑数据集整体,而是逐个针对每一个文件单独切片 比如待处理数据有两个文件: file1.txt 320M file2.txt 10M 经过FileInputFormat的切片机制运算后,形成的切片信息如下: file1.txt.split1– 0~128 file1.txt.split2– 128~256 file1.txt.split3– 256~320 file2.txt.split1– 0~10M
FileInputFormat中切片大小的参数配置
通过分析源码,在FileInputFormat中,计算切片大小的逻辑,Math.max(minSize,Math.min(maxSize,blockSize));切片主要又这几个值运算来决定 minsize:默认值:1 配置参数:mapReduce.input。fileinputformat.split.minsize maxsize:默认值:Long.MAXValue
ReduceTask并行度的决定
reducetask的并行度同样影响整个job的执行并发度和执行效率,但与maptask的并发数由切片数决定不同,Reducetask数量的决定是可以直接手动设置: //默认值是1,手动设置为4 job.setNumReduceTasks(4); 如果数据分布不均匀,就有可能在reduce阶段产生数据倾斜 注意: reducetask数量并不是任意设置,还要考虑业务逻辑需求,有些情况下,需要计算全局汇总结果,就只能有1个reducetask 尽量不要运行太多的reduce task。对大多数job来说,最好rduce的个数最多和集群中的reduce持平,或者比集群的 reduce slots小。这个对于小集群而言,尤其重要。
MapReduce的shuffle机制
特点
扩容能力
能可靠的存储和处理千兆字节(PB)数据
成本低
可以通过普通机器组成的服务集群来分发以及处理数据
效率高
通过分发数据,hadoop可以在数据所在的节点并行的处理他们,
可考性
hadoop能自动的维护数据的多份副本,并且在任务失败后自动的重新部署计算任务
1.x和2.x的区别
1.x
HDFS的变化
增加了NameNode的水平扩展及可用性
NameSpace
包含目录,文件以及块的信息
支持NameSpace相关文件系统的操作
如增加,删除,修改及文件和目录的展示
Block Storage Service
块管理(在NameNode中实现)
提供数据节点集群成员的登记,并定期通过心跳进行检查
提供块报告以及块的存储位置的维护
提供对块的操作
对块进行增删改的操作及获取块的存储地址
对块的副本的复制以及存储位置的管理
存储
提供DataNode进行数据的本地存储,并提供读写操作
2.x
存储块池(Block Pool)
存储块
属于一个单独的NameSpace(Namenode),集群中所有存储块池的存储块都是存放在Datanodes中
集群id
用于确认集群中所有的节点,也可以在格式化其他NameNode时指定集群Id,并使其加入到某个集群中
MapReduce
资源管理
任务生命周期管理
ResourceManager
applicationMaster
shell命令
bin/hadoop dfs/fs -ls 文件(夹)路径
若是非空文件夹,则为目录:查看结果显示目录下的文件和文件夹。 若是空文件夹:查看结果为空。 若是文件:查看结果为该文件本身信息。 对于查看结果显示的信息说明: 三种文件权限:r 可读,w 可写,x 可执行。如果没有哪种权限,用- 代替。 三个类别的权限:文件所属主,文件所属组,其他用户。 如果文件权限前面第一个字符是"d ”,则该文件为目录文件;如果文件权限前面第一个字符是"- ",则该文件为普通文件
bin/hadoop dfs/fs -mkdir 文件路径
bin/hadoop dfs/fs -rm 文件路径
// 只能删除普通文件,不能删除文件夹(目录文件)
bin/hadoop dfs/fs -put [src] [dest]
上传一个文件或者文件夹
bin/hadoop dfs/fs -get [src] [dest]
得到HDFS中的一个文件(夹)
bin/hadoop dfs/fs -text/cat/tail 文件目录
hadoop fs - moveFromLocal /home/hadoop/a.txt /aaa/bbb/cc/dd
从本地剪切粘贴到hdfs
hadoop fs - moveToLocal /aaa/bbb/cc/dd /home/hadoop/a.txt
从hdfs剪切粘贴到本地
hadoop fs -appendToFile ./hello.txt hdfs://hadoop-server01:9000/hello.txt
追加一个文件到已经存在的文件末尾
hadoop fs -cat /hello.txt
显示文件内容
hadoop fs -tail /weblog/access_log.1
显示一个文件的末尾
hadoop fs -text /weblog/access_log.1
以字符形式打印一个文件的内容
hadoop fs -chmod 666 /hello.txt
linux文件系统中的用法一样,对文件所属权限
hadoop fs -copyFromLocal ./jdk.tar.gz /aaa/
:从本地文件系统中拷贝文件到hdfs路径去
hadoop fs -setrep 3 /aaa/jdk.tar.gz
设置hdfs中文件的副本数量
zookeeper
概述
分布式应用
可以在给定时间(同时)在网络中的铎哥系统上运行,通过协调他们以快速有效的方式完成特定任务
Server(服务器)
Client(客户端)
分布式应用的优点
可靠性
单个或几个系统的故障不会使整个系统出现故障
可扩展性
可以在需要时增加性能,通过添加更多机器,在应用程序配置中进行微小的更改,而不会有停机时间。
分布式应用的挑战
竞争条件
两个或多个机器尝试执行特定任务,实际上只需在任意给定时间由单个机器完成。例如,共享资源只能在任意给定时间由单个机器修改。
死锁
两个或多个操作等待彼此无限期完成。
不一致
数据的部分失败
分布式提供的服务
命名服务
按名称标识集群中的节点。它类似于DNS,但仅对于节点
配置管理
加入节点的最近的和最新的系统配置信息。
集群管理
实时地在集群和节点状态中加入/离开节点。
选举算法
选举一个节点作为协调目的的leader。
锁定和同步服务
在修改数据的同时锁定数据。此机制可帮助你在连接其他分布式应用程序(如Apache HBase)时进行自动故障恢复。
高度可靠的数据注册表
好处
简单的分布式协调过程
同步
有序的消息
序列化
可靠性
原子性
基础
1.Architecture(架构)
Zookeeper角色包括Leader、Follower、Observer。Leader是集群主节点,主要负责管理集群状态和接收用户的写请求;Follwer是从节点,主要负责集群选投票和接收用户的读请求;Obsever的功能与Follower类似,只是没有投票权,主要用于分担Follower的读请求
Client(客户端)
客户端,我们的分布式应用集群中的一个节点,从服务器访问信息。对于特定的时间间隔,每个客户端向服务器发送消息以使服务器知道客户端是活跃的。 类似地,当客户端连接时,服务器发送确认码。如果连接的服务器没有响应,客户端会自动将消息重定向到另一个服务器。
Server(服务器)
服务器,我们的ZooKeeper总体中的一个节点,为客户端提供所有的服务。向客户端发送确认码以告知服务器是活跃的。
Ensemble
Leader
一个运行的ZooKeeper集群只有一个Leader服务,Leader服务主要有两个职责:一个是负责集群数据写操作;二是发起并维护各个Follower及Observer之间的心跳以及监控集群的运行状态。在Zookeeper集群中,所有的写操作必须经过leader,只有leader写操作完成后,才将写操作广播到其他Follower.只有超过半数的节点(不包括Observer节点),写入成功时,该写请求才算写成功
Follower
一个zookeeper集群可以有多个Follower,Follower通过心跳和leader保持连接。Follower服务主要有两个职责:一个是负责集群数据的读操作,二是参与集群的Leader选举
Observer
不参与投票,只用来接收客户端的连接并响应客户端读请求,将写请求转发给leader节点
ZAB协议
ZAB(Zookeeper atomic Broadcast)既Zookeeper原子消息广播协议。改协议主要通过唯一的事务编号Zxid(Zookeeper Transaction id )保障集群状态的唯一性
Epoch:
Epoch指当前集群的周期号(年代号),集群的每次leader变更都会在产生一个新额周期号,周期号的生产规则是在上一个周期号的基础上加1。leader崩溃后发现自己的周期号比当前的周期号小说明此时集群已经产生了新的leader,旧的leader会再次以Follower的角色加入集群
Zxid
指ZAB协议的事务编号。他是一个64位数字,其中底32位存储的是一个简单的单调递增的计数器,针对客户端的每一个事务请求,计数器都加一。高32位存储的是leader的周期号Epoch每次选举产生一个新的leader时,该Leader都会从当前服务器的日志中取出最大事务的Zxid,获取其中最高32位的Epoch值并加一,以此作为新的Epoch,并降低32位从0开始重新技术
1.恢复模式
当集群启动、集群重启或者Leader奔溃后,集群将开始选主,改过程为恢复模式
2.广播模式
当leader被选举出来后,leader将
ZAB协议的四个阶段
1.选举阶段
在集群选举开始时,在所有节点都处于选举阶段。当某一个节点的票数超过半数节点后,该节点将被推选为准leader。选举阶段的目的就是产生一个准leader。只有到达广播节点后,准leader才会成为真正的Leader
2.发现阶段
发现阶段,各个Follower开始和准Leader进行通信,同步Follower最近接收的事务提议。这时准leader会产生一个新的Epoch,并尝试让其他的Follower接收该Epoch后再更新到本地。
3.同步阶段
同步阶段主要是将Leader在前一段获得的最新提议信息同步到集群中的所有的副本,只有当半数以上的节点都同步完成时,准Leader才会成为真正的Leader。Follower只会接收Zxid比自己大的lastZxid大的提议。同步阶段后,集群选主的操作才完成。新的leader将产生
4.广播阶段
在广播段,Zookeeper集群开始正式对外提供事务服务
2.Hierarchical nameSpace(层次命名空间)
znode
/master
存储了当前主节点的信息
/config
config命名空间用于集中式配置管理,在config命名空间下,每个znode最多可以存储1MB的数据。这与UNIX文件系统类似,除了父znode也可以存储数据
/workers
下面的每个子znode代表一个从节点,子znode上存储的数据,如“foo.com:2181”,代表从节点的信息。
/task
下面每个子znode代表一个任务,子znode上存储的信息如“runcmd",代表该内务内容
/assign
下面每个子znode代表一个从节点的任务集合。
stat结构
ZooKeeper数据模型中的每个znode都维护着一个 stat 结构。一个stat仅提供一个znode的元数据。它由版本号,操作控制列表(ACL),时间戳和数据长度组成
版本号
每个znode都有版本号,这意味着每当与znode相关联的数据发生变化时,其对应的版本号也会增加。当多个zookeeper客户端尝试在同一znode上执行操作时,版本号的使用就很重要。
操作控制列表(ACL)
ACL基本上是访问znode的认证机制。它管理所有znode读取和写入操作
时间戳
时间戳表示创建和修改znode所经过的时间。它通常以毫秒为单位。ZooKeeper从“事务ID"(zxid)标识znode的每个更改。Zxid 是唯一的,并且为每个事务保留时间,以便你可以轻松地确定从一个请求到另一个请求所经过的时间。
数据长度
时间戳表示创建和修改znode所经过的时间。它通常以毫秒为单位。ZooKeeper从“事务ID"(zxid)标识znode的每个更改。Zxid 是唯一的,并且为每个事务保留时间,以便你可以轻松地确定从一个请求到另一个请求所经过的时间。
znode类型
持久节点
即使在创建该特定znode的客户端断开连接后,持久节点仍然存在。默认情况下,除非另有说明,否则所有znode都是持久的。
顺序节点
临时节点
znode支持的操作及暴露的API
create /path data
创建一个名为/path的znode,数据为data
delete /path
删除名为/path的znode
exists /path
检查是否存在名为/path的znode
setData /path data
设置名为/path的znode的数据为data
getData /path
getChildren /path
3.Session(会话)
回话对于Zookeeper的操作非常重要,回话中的请求按FIFO顺序执行。一旦客户端连接到服务器,将建立回话并向客户端分配会话
4.Watches(监视)
监视是一种简单的机制,使客户端收到关于ZooKeeper集合中的更改的通知。客户端可以在读取特定znode时设置Watches。Watches会向注册的客户端发送任何znode(客户端注册表)更改的通知。 Znode更改是与znode相关的数据的修改或znode的子项中的更改。只触发一次watches。如果客户端想要再次通知,则必须通过另一个读取操作来完成。当连接会话过期时,客户端将与服务器断开连接,相关的watches也将被删除。
安装和使用
创建配置文件
解压后的路径下找到conf文件夹,进入conf文件夹复制zoo_sample.cfg,命名为zoo.cfg
单机启动Zookeeper
bin/zkServer.sh start
通过客户端连接ZooKeeper
➜ zookeeper-3.4.12 bin/zkCli.sh
通过客户端执行基本命令
创建znode.
[zk: localhost:2181(CONNECTED) 0] create /my_test testData
查看znode信息
[zk: localhost:2181(CONNECTED) 2] get /my_test
集群的配置和启动
修改zoo.cfg文件
tickTime=2000 dataDir=/var/lib/zookeeper clientPort=2181 initLimit=5 syncLimit=2 server.1=ip1:2888:3888 server.2=ip2:2888:3888 server.3=ip3:2888:3888
initLimit
zookeeper用来限制zookeeper服务器连接到leader的时长
syncLimit
一个服务器多久在leader那里过期
tickTime
initLimit和syncLimit的时间单位
server.A=B:C:D
列出了所有的zookeeper服务。集群启动它通过查看data下面的myid来知道自己是哪台服务器。 第二个需要新增的是 server.A=B:C:D 配置,其中 A 对应下面我们即将介绍的myid 文件。B是集群的各个IP地址,C:D 是端口配置。
A
其中 A 是一个数字,表示这个是服务器的编号;
B
是这个服务器的 ip 地址;
C
Leader选举的端口;
D
Zookeeper服务器之间的通信端口。
创建myid文件
如果配置文件上面的配置dataDir=/usr/local/software/zookeeper-3.3.6/data那么就必须在 192.168.146.200 机器的的 /usr/local/software/zookeeper-3.3.6/data 目录下创建 myid 文件,然后在该文件中写上 0 即可。
环境变量的配置
export ZK_HOME=/usr/local/software/zookeeper-3.3.6 export PATH=$PATH:$ZK_HOME/bin
启动zookeeper命令
启动
zkServer.sh start
停止
zkServer.sh stop
重启
zkServer.sh restart
查询集群节点的状态
zkServer.sh status
搭建问题
查看防火墙状态
dataDir配置的目录有没有创建
Zookeeper分布式锁
1.0版本
1.用zookeeper中一个临时节点代表锁,比如在/exlusive_lock下创建临时子节点/exlusive_lock/lock
2.所有客户端争相创建此节点,但是只有一个客户端创建成功
3.创建成功代表获取锁陈宫。此客户端执行业务逻辑
4.未创建成功的客户端,监听/exlusive_lock变更
5.获取锁的客户端执行完成后,删除/exlusive_lock/lock,表示锁被释放
6.锁被释放后,其他监听/exlusive_lock变更的客户端得到通知,再次争相创建临时子节点/exlusive_lock/lock
2.0版本
让每个客户端在/exlusive_lock下创建的临时节点为有序节点,这样每个客户端都在/exlusive_lock下有自己对应的锁节点,而序号排在最前面的节点,代表对应的客户端获取锁成功。排在后面的客户端监听自己前面一个节点,那么在他前序客户端执行完成后,他将得到通知,获得锁成功。逻辑修改如下: ———————————————— 版权声明:本文为CSDN博主「稀有气体」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/liyiming2017/article/details/83786331
1.每个客户端往/exlusive_lock下创建有序临时节点/exlusive_lock/lock_。创建成功后/exlusive_lock下面会有每个客户端对应的节点,如/exlusive_lock/lock_000000001
2.客户端取得/exlusive_lock下子节点,并进行排序,判断排在最前面的是否为自己。
3. 如果自己的锁节点在第一位,代表获取锁成功,此客户端执行业务逻辑
4.如果自己的所不在第一位置,则监听自己前位的锁节点。例如,自己锁节lock_000000002,那么则监听lock_000000001.
5.当前一位锁节点(lock_000000001)对应的客户端执行完成,释放了锁,将会触发监听客户端(lock_000000002)的逻辑
6.监听客户端重新执行第2步逻辑,判断自己是否获得了锁。
LockSample类
获取锁
释放锁
kafka
kafka简介
流平台
发布订阅记录流,和消息队列或者企业新消息系统类似
可以容错,持久的方式保存记录流
当记录流产生时就进行处理
两种广播类型
在系统和应用间建立实时的数据管道,能够可信赖的获取数据
建立实时的流应用,可以处理或者响应数据流
特性
1.消息持久化
2.高吞吐量
3.可扩展性
应用场景
1.消息系统
2.日志系统
3.流处理
安装使用
单机环境
jdk1.8
下载zookeeper解压
创建zookeeper配置文件
在zookeeper解压后的目录下找到conf文件夹,进入后,复制文件zoo_sample.cfg,并命名为zoo.cfg zoo.cfg中一共四个配置项,可以使用默认配置。
启动zookeeper
bin/zkServer.sh start
下载kafka解压
修改kafka的配置文件
进入kafka根目录下的config文件夹下,打开server.properties,修改如下配置项 zookeeper.connect=localhost:2181 broker.id=0 log.dirs=/tmp/kafka-logs zookeeper.connect是zookeeper的链接信息,broker.id是当前kafka实例的id,log.dirs是kafka存储消息内容的路径。 ———————————————— 版权声明:本文为CSDN博主「稀有气体」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/liyiming2017/article/details/82790574
启动kafka
bin/kafka-server-start.sh config/server.properties
启动zookeeper的client
bin/zkCli.sh -server 127.0.0.1:2181
输入命令 ls /brokers
输入命令 ls/brokers/ids
创建topic
bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic study
启动消费者
bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic study --from-beginning
开启生产者
bin/kafka-console-producer.sh --broker-list localhost:9092 --topic study
kafka核心概念
集群环境
1.每个实例设置不同的broker.id
2.如果多个实例部署在一台服务器上,还要注意修改log.dirs为不同目录,确保消息存储时不会有冲突
集群结构
producer
consumer
broker
broker就是kafka集群中的一个实例,或者说是一个服务单元,连接到同一个zookeeper的多个broker实例组成kafka的集群。在若干个broker中会有一个broker是leader,其余的broker为follower。leader在集群启动时候选举出来,负责和外部的通讯。当leader死掉的时候,follower们会再次通过选举,选择出新的leader,确保集群的正常工作。 ———————————————— 版权声明:本文为CSDN博主「稀有气体」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/liyiming2017/article/details/82805479
Consumer Group
同一个group的consumer可以并行消费同一个topic的消息,但是同group的consumer,不会重复消费。这就好比多个consumer组成了一个团队,一起干活,当然干活的速度就上来了。group中的consumer是如何配合协调的,其实和topic的分区相关联,后面我们会详细论述。 ———————————————— 版权声明:本文为CSDN博主「稀有气体」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/liyiming2017/article/details/82805479
Topic
kafka中消息订阅和发送都是基于某个topic。比如有个topic叫做NBA赛事信息,那么producer会把NBA赛事信息的消息发送到此topic下面。所有订阅此topic的consumer将会拉取到此topic下的消息。Topic就像一个特定主题的收件箱,producer往里丢,consumer取走。 ———————————————— 版权声明:本文为CSDN博主「稀有气体」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/liyiming2017/article/details/82805479
partition
分片
一个partition只能被同组的一个consumer消费(图中只会有一个箭头指向一个partition)
同一个组里面的consumer可以消费铎哥partition
消费效率最高的情况是partition和consumer的数量相同
consumer数量不能大于partition数量。由于第一点的限制,当consumer多于partition时,就会有consumer闲置。
consumer group可以认为是一个订阅者的集群,其中的每个consumer负责自己所消费的分区
读写
Replica
副本
leader
follower
Replica均匀分配在broker上,同一个partittion的replica不会再同一个broker上
同一个partition的replica数量不能多于broker数量。多个replica为了数据安全,一台server存多个replica没有意义。
分区的leader replica均衡分布在broker上。此时集群的负载是均衡的。这就叫做分区平衡 分区平衡是个很重要的概念,接下来我们就来讲解分区平衡
分区平衡
AR
assigned replicas,已分配的副本。每个partition都有自己的AR列表,里面存储着这个partition最初分配的所有replica。注意AR列表不会变化,除非增加分区。
PR
AR列表中的第一个replica就是优先replica,而且永远是优先replica。最初,优先replica和leader replica是同一个replica。
ISR
每个partition都有自己的ISR列表。ISR是会根据同步情况动态变化的。 最初ISR列表和AR列表是一致的,但由于某个节点死掉,或者某个节点的follower replica落后leader replica太多,那么该节点就会被从ISR列表中移除。此时,ISR和AR就不再一致 ———————————————— 版权声明:本文为CSDN博主「稀有气体」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/liyiming2017/article/details/82805479
核心组件和流程
控制器
控制器选举
kafka每个broker启动的时候,都会实例化一个KafkaController,并将broker的id注册到zookeeper,这在第二章中已经通过例子做过讲解。集群在启动过程中,通过选举机制选举出其中一个broker作为leader,也就是前面所说的控制器。
3个broker从zookeeper获取/controller临时节点信息。/controller存储的是选举出来的leader信息。此举是为了确认是否已经存在leader。
如果还没有选举出leader,那么此节点是不存在的,返回-1。如果返回的不是-1,而是leader的json数据,那么说明已经有leader存在,选举结束。
三个broker发现返回-1,了解到目前没有leader,于是均会触发向临时节点/controller写入自己的信息。最先写入的就会成为leader。
假设broker 0的速度最快,他先写入了/controller节点,那么他就成为了leader。而broker1、broker2很不幸,因为晚了一步,他们在写/controller的过程中会抛出ZkNodeExistsException,也就是zk告诉他们,此节点已经存在了。
控制器初始化
初始化控制器所用到的组件及监听器,准备元数据
KafkaController
每个代理启动时,会实例开启kafkaController·
ControllerContext
缓冲控制器处理所需数据
controller_epoch
控制器选举次数,每个向控制器发起的请求都会带此字段,作比较
zkVersion
broker列表
topic列表
AR列表
PartitionStateMachine
分区状态机
TopicChangeListener
变化实时更新 ControllerContext中主题列表,对应分区AR
DeleteTopicListener
删除主题时,往该节点写入删除节点,由于变化,触发监听,把该几点从/brokers/topics下删除
ZooKeeperLeaderElector
用于将当前代理选举为控制器
BrokerChangeListener
节点变化时做相应处理
TopicDeletionManager
管理主题删除
PartitionLeaderSelector
分区leader选举器,分区状态变化时选出分区leader副本
ControllerBrokerRequestBatch
缓存状态机处理结果通过他的sendRequestToBrokers()批量发出
PartitionReassignedListener
监听分区重新分配
PreferredReplicaElectionListener
监听分区状态变化,触发选举优先副本为leader
lsrChangeNotificationListener
isr变化时,通知zookeeper更新,向所有broker发送元数据修改请求
故障转移
1.注册区分管理的玄关监听器·
PartitionsReassignedListener
/admin/reassign_partitions 节点变化将会引发分区重新分配
IsChangeNotificationListener
/isr_change_notification 处理分区的ISR发生变化引发的操作
PreferredReplicaElectionListener
/admin/preferred_replica_election 将优先副本选举为leader副本
2.注册主题管理的相关监听
TopicChangeListener
/brokers/topics 监听主题发生变化时进行相应操作
DeleteTopicsListener
/admin/delete_topics 完成服务器端删除主题的相应操作。否则客户端删除主题仅仅是表示删除
3、注册代理变化监听器
BrokerChangeListener
/brokers/ids 代理发生增减的时候进行相应的处理
4.重新初始化ControllerContext
5.启动控制器和其他代理之间通信的ControllerChannelManager
6.创建用于删除主题的TopicDeletionManager对象,并启动
7.启动分区状态机和副本状态机
8.轮询每个主题,添加监听分区变化的PartitionModifcationsListener
9.如果设置了分区分恒
代理上下线
代理上线
1.新代理启动时向/brokers/ids写数据
2.BrokerChangeListener监听到变化,对新上线节点调用controllerChannelManager.addBroker(),完成新上线代理网络层初始化
3.调用KafkaController.onBrokerStartup()处理
1.通过向所有代理发送UpdateMetadataRequest,告诉所有代理有新代理加入
2.根据分配给新上线节点的副本集合,对副本状态做变迁。对分区也进行处理
3.出发一次leader选举,缺人新加入的是否为分区leader
4.轮询分配给新broker的副本,调用KafkaController.onPartitionReassignment(),执行分区副本分配
5.恢复因新代理上线暂停的删除主题操作线程
代理下线
1.查找下线节点集合
2.轮询下线节点,调用controllerChannelManager.removeBroker(),关闭每个下线节点网络连接。清空下线节点消息队列,关闭下线节点request请求
3.轮询下线节点,调用KafkaController.onBrokerFailure处理
1.处理leader副本在下线节点上的分区,重新选出leader副本,发送updateMetadataRequest请求
2.处理下线节点的副本集合,做下线处理,从ISR集合中删除,不在同步,发送updateMetadataRequest请求
4.向集群全部存活代理发送updateMetadataRequest请求
主题管理
1.创建主题
/brokers/topic下创建主题对应子节点
TopicChangeListener监听此节点
变化时获取重入锁ReentrantLock,调用handleChildChange方法进行处理。
变化时获取重入锁ReentrantLock,调用handleChildChange方法进行处理
通过对比zookeeper中/brokers/topics存储的主题集合及控制器的ControllerContext中缓存的主题集合的差集,得到新增的主题。反过来求差集,得到删除的主题。
2.删除主题
/admin/delete_topics创建删除主题的子节点
DeleteTopicsListener监听此节点,
ElasticSearch
node
单个节点
Cluster
集群
分片(Shard)
ES的“分片(shard)”机制可将一个索引内部的数据分布地存储于多个节点,它通过将一个索引切分为多个底层物理的Lucene索引完成索引数据的分割存储功能,这每一个物理的Lucene索引称为一个分片(shard)。 这样的好处是可以把一个大的索引拆分成多个,分布到不同的节点上。降低单服务器的压力,构成分布式搜索,提高整体检索的效率(分片数的最优值与硬件参数和数据量大小有关)。分片的数量只能在索引创建前指定,并且索引创建后不能更改。
副本(Replica)
副本是一个分片的精确复制,每个分片可以有零个或多个副本。副本的作用一是提高系统的容错性,当某个节点某个分片损坏或丢失时可以从副本中恢复。二是提高es的查询效率,es会自动对搜索请求进行负载均衡。
index
Elastic 会索引所有字段,经过处理后写入一个反向索引(Inverted Index)。查找数据的时候,直接查找该索引。 所以,Elastic 数据管理的顶层单位就叫做 Index(索引)。它是单个数据库的同义词。每个 Index (即数据库)的名字必须是小写。
查询所有的index
下面的命令可以查看当前节点的所有 Index。 $ curl -X GET 'http://localhost:9200/_cat/indices?v'
新建index
$ curl -X PUT 'localhost:9200/weather' 服务器返回一个 JSON 对象,里面的acknowledged字段表示操作成功。 { "acknowledged":true, "shards_acknowledged":true }
删除index
$ curl -X DELETE 'localhost:9200/weather'
Document
Index 里面单条的记录称为 Document(文档)。许多条 Document 构成了一个 Index。 Document 使用 JSON 格式表示,下面是一个例子。 { "user": "张三", "title": "工程师", "desc": "数据库管理" } 同一个 Index 里面的 Document,不要求有相同的结构(scheme),但是最好保持相同,这样有利于提高搜索效率。
Type
Document 可以分组,比如weather这个 Index 里面,可以按城市分组(北京和上海),也可以按气候分组(晴天和雨天)。这种分组就叫做 Type,它是虚拟的逻辑分组,用来过滤 Document。 不同的 Type 应该有相似的结构(schema),举例来说,id字段不能在这个组是字符串,在另一个组是数值。这是与关系型数据库的表的一个区别。性质完全不同的数据(比如products和logs)应该存成两个 Index,而不是一个 Index 里面的两个 Type(虽然可以做到)。 下面的命令可以列出每个 Index 所包含的 Type。 $ curl 'localhost:9200/_mapping?pretty=true'
分词
插件
ik
smartcn
hbase
hive
sprak
flume
flink
ETL
数据的抽取(Extract)
对于与存放DW的数据库系统相同的数据源处理方法
对于与DW数据库系统不同的数据源的处理方法
对于文件类型数据源(.txt,.xls)
增量更新的问题
数据的清洗转换(Cleaning、Transform)
一般情况下,数据仓库分为ODS、DW两部分。通常的做法是从业务系统到ODS做清洗,将脏数据和不完整数据过滤掉,在从ODS到DW的过程中转换,进行一些业务规则的计算和聚合。
1、 数据清洗
不完整的数据
错误的数据
重复的数据
2、数据转换
不一致数据转换
这个过程是一个整合的过程,将不同业务系统的相同类型的数据统一,比如同一个供应商在结算系统的编码是XX0001,而在CRM中编码是YY0001,这样在抽取过来之后统一转换成一个编码
数据粒度的转换
业务系统一般存储非常明细的数据,而数据仓库中数据是用来分析的,不需要非常明细的数据。一般情况下,会将业务系统数据按照数据仓库粒度进行聚合。
商务规则的计算
不同的企业有不同的业务规则、不同的数据指标,这些指标有的时候不是简单的加加减减就能完成,这个时候需要在ETL中将这些数据指标计算好了之后存储在数据仓库中,以供分析使用。
工具
Apache Camel
Camel概念
CamelContext
Camel的容器,通过CamelContext可以访问内部服务:Components,Endpoints,Endpoints,Registry等等
Routes
通过路由可以实现:客户端与服务端,生产者与消费者的解耦
endpoint URI
对于消费者(from方法)来说,表示消息从哪里来
对于生产者(to方法)来说,表示消息到哪里去
Scheme:指明使用的是FtpComponent
Context path: ftp服务和端口号,以及文件路径
Options:一些操作配置,每个组件都不同
Exchange
Message的容器,其的内部属性,如下图所示
message
消息数据的基本实体
MEP
Exchange支持多种消息交换模式 (MEPs),通过其内部持有的pattern属性进行区分
inOnly
单向消息模式(也称为事件消息),简言之:不需要等待对方的响应
InOut
请求响应模式,例如:基于http的传输,通常是此模式,客户端请求web页面,等待服务端的回应
exception
如果路由期间发生错误,此属性将被赋值
properties
Exchange的消息头,Camel本身和开发者可以设置或读取属性值
Endpoints
Endpoints是模拟通道末端的camel抽象,充当一个工厂,用于创建消息的producer和consumer
Component
内部组件介绍
Direct Component
基于内存的同步消息组件 使用Direct组件,生产者直接调用消费者。因此使用Direct组件的唯一开销是方法调用。
Direct的线程模型
由于生产者直接调用消费者 因此:调用者与camel的消费者共用一个线程
SED Component
基于内存的异步消息组件:生产者和消费者通过BlockingQueue交换消息,生产者与消费者是不同的线程 如果VM在消息尚未处理时终止,则seda不会实现消息的持久化或恢复,因此有丢失消息的风险
消费者视角
Consumer thread pool
SedaConsumer内部持有一个线程池,默认是1个线程,可以通过concurrentConsumers指定线程数
Threads thread pool
Consumer thread pool中的每个线程,还可以开启新的线程池,代码如下所示 from("seda:start?concurrentConsumers=2") .to("log:A") // create a thread pool with a pool size of 5 and a maxi- mum size of 10. .threads(5, 10) .to("log:B");
生产者视角
异步发送消息
生产者发完消息,立刻返回,不需要等待消息消费成功
同步发送消息
//InOut消息模式 producerTemplate.requestBody("seda:start", body);
Camel使用
Camel可以使用ProducerTemplate将消息发送到endpoint,或从endpoint请求数据
发送消息
Camel可以使用ProducerTemplate将消息发送到endpoint,或从endpoint请求数据 我们可以使用@Produce创建ProducerTemplate,代码如下
方法调用
自定义Processor
异常处理
Apatar
Heka
Logstash
Scriptella
Talend
Kettle
数据仓库
db
ods
dw
dwd
数据明细层该层一般保持和ODS层一样的数据粒度,并且提供一定的数据质量保证。同时,为了提高数据明细层的易用性,该层会采用一些维度退化手法,将维度退化至事实表中,减少事实表和维表的关联。 另外,在该层也会做一部分的数据聚合,将相同主题的数据汇集到一张表中,提高数据的可用性,后文会举例说明
dwm
数据中间层该层会在DWD层的数据基础上,对数据做轻度的聚合操作,生成一系列的中间表,提升公共指标的复用性,减少重复加工。 直观来讲,就是对通用的核心维度进行聚合操作,算出相应的统计指标。
dws
数据服务层又称数据集市或宽表。按照业务划分,如流量、订单、用户等,生成字段比较多的宽表,用于提供后续的业务查询,OLAP分析,数据分发等。 一般来讲,该层的数据表会相对比较少,一张表会涵盖比较多的业务内容,由于其字段较多,因此一般也会称该层的表为宽表。 在实际计算中,如果直接从DWD或者ODS计算出宽表的统计指标,会存在计算量太大并且维度太少的问题,因此一般的做法是,在DWM层先计算出多个小的中间表,然后再拼接成一张DWS的宽表。由于宽和窄的界限不易界定,也可以去掉DWM这一层,只留DWS层,将所有的数据在放在DWS亦可。
DM
APP
数据建模
1、关系建模(3NF)
1、范式介绍
1NF:属性原子不可分
2NF:满足 1NF,且表中的每一个非主属性,必须完全依赖于本表的主键
3NF:确保每列都和主键列直接相关,而不是间接相关
2、维度建模
星型模型
星型模是一种多维的数据关系,它由一个事实表和一组维表组成。每个维表都有一个维作为主键,所有这些维的主键组合成事实表的主键。强调的是对维度进行预处理,将多个维度集合到一个事实表,形成一个宽表。这也是我们在使用 hive 时,经常会看到一些大宽表的原因,大宽表一般都是事实表,包含了维度关联的主键和一些度量信息,而维度表则是事实表里面维度的具体信息,使用时候一般通过 join 来组合数据,相对来说对OLAP 的分析比较方便
雪花模型
当有一个或多个维表没有直接连接到事实表上,而是通过其他维表连接到事实表上时,其图解就像多个雪花连接在一起,故称雪花模型。雪花模型是对星型模型的扩展。它对星型模型的维表进一步层次化,原有的各维表可能被扩展为小的事实表,形成一些局部的 "层次 " 区域,这些被分解的表都连接到主维度表而不是事实表。雪花模型更加符合数据库范式,减少数据冗余,但是在分析数据的时候,操作比较复杂,需要 join 的表比较多所以其性能并不一定比星型模型高。