右键—》Run As—》Run Configurations—》在Arguments的tab里面设置VM参数如下
Springboot汇总
数据库连接池
B+树,B树,红黑树对比
B+树,B树
区别有以下两点:
- B+树中只有叶子节点会带有指向记录的指针,而B树则所有节点都带有,在内部节点出现的索引项不会再出现在叶子节点中。
- B+树中所有叶子节点都是通过指针连接在一起,而B树不会。
B+树的优点:
- 非叶子节点不会带上指向记录的指针,这样,一个块中可以容纳更多的索引项,一是可以降低树的高度。二是一个内部节点可以定位更多的叶子节点。
- 叶子节点之间通过指针来连接,范围扫描将十分简单,而对于B树来说,则需要在叶子节点和内部节点不停的往返移动。具体的来讲,如何想扫描一次所有数据,对于b+树来说,可以从因为他们的叶子结点是连在一起的,所以可以横向的遍历过去。而对于b-树来说,就这能中序遍历了。
B树的优点:
对于在内部节点的数据,可直接得到,不必根据叶子节点来定位。
B树长什么样子?
红黑树和B树应用场景有何不同?
为什么要设计红黑树?
先说一下红黑树,红黑树有一个比较复杂的规则,平衡树和红黑树的区别是什么?为什么有了平衡树还要设计出来红黑树?
红黑树的规则:
- 1)每个结点要么是红的,要么是黑的。
- 2)根结点是黑的。
- 3)每个叶结点(叶结点即指树尾端NIL指针或NULL结点)是黑的。
- 4)如果一个结点是红的,那么它的俩个儿子都是黑的。
- 5)对于任一结点而言,其到叶结点树尾端NIL指针的每一条路径都包含相同数目的黑结点。
现在想想,我的理解是平衡树(AVL)更平衡,结构上更加直观,时间效能针对读取而言更高,但是维护起来比较麻烦!!!(插入和删除之后,都需要rebalance)。但是,红黑树通过它规则的设定,确保了插入和删除的最坏的时间复杂度是O(log N) 。
设计红黑树的目的,就是解决平衡树的维护起来比较麻烦的问题,红黑树,读取略逊于AVL,维护强于AVL,每次插入和删除的平均旋转次数应该是远小于平衡树。
小结一下:
能用平衡树的地方,就可以用红黑树。用红黑树之后,读取略逊于AVL,维护强于AVL。
红黑树 和 b+树的用途有什么区别?
红黑树多用在内部排序,即全放在内存中的,STL的map和set的内部实现就是红黑树。
B+树多用于外存上时,B+也被成为一个磁盘友好的数据结构。
为什么b+磁盘友好?
磁盘读写代价更低
树的非叶子结点里面没有数据,这样索引比较小,可以放在一个blcok(或者尽可能少的blcok)里面。避免了树形结构不断的向下查找,然后磁盘不停的寻道,读数据。这样的设计,可以降低io的次数。查询效率更加稳定
非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。遍历所有的数据更方便
B+树只要遍历叶子节点就可以实现整棵树的遍历,而其他的树形结构 要中序遍历才可以访问所有的数据。
为什么mysql索引使用B+树而不使用红黑树?
B+树就是为文件存储而生的。如果数据库文件存储在主存中我认为两种结构的查询速度差距不是很大,因为主存的查找速度非常快。
而数据库文件实际存储在磁盘中,定位一行信息需要查找该行文件所在柱面号,磁盘号,扇区号,页号这个阶段是很耗费时间的。
每一次的定位请求意味着要做一次IO操作,也意味着成倍的时间消耗。因此减少IO查询的次数是提高查询性能的关键。而IO的查询次数就是索引树的高度,高度越低查询的次数越少。
同样的结点次数红黑树的高度最多为2log(n+1),而B+树的高度最多为(logt (n+1)/2)+1,随着t增大高度会更小,IO次数也会减少。
Java 并发包ConcurrentHashMap图解下
现在我们来对每一步的细节进行源码分析,在第一步中,符合条件会进行初始化操作,我们来看看initTable()方法
1 | /** |
在第二步中没有hash冲突就直接调用Unsafe的方法CAS插入该元素,进入第三步如果容器正在扩容,则会调用helpTransfer()方法帮助扩容,现在我们跟进helpTransfer()方法看看
1 | /** |
其实helpTransfer()方法的目的就是调用多个工作线程一起帮助进行扩容,这样的效率就会更高,而不是只有检查到要扩容的那个线程进行扩容操作,其他线程就要等待扩容操作完成才能工作。
既然这里涉及到扩容的操作,我们也一起来看看扩容方法transfer()
1 | private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) { |
扩容过程有点复杂,这里主要涉及到多线程并发扩容,ForwardingNode的作用就是支持扩容操作,将已处理的节点和空节点置为ForwardingNode,并发处理时多个线程经过ForwardingNode就表示已经遍历了,就往后遍历,下图是多线程合作扩容的过程:
介绍完扩容过程,我们再次回到put流程,在第四步中是向链表或者红黑树里加节点,到第五步,会调用treeifyBin()方法进行链表转红黑树的过程。
1 | private final void treeifyBin(Node<K,V>[] tab, int index) { |
到第六步表示已经数据加入成功了,现在调用addCount()方法计算ConcurrentHashMap的size,在原来的基础上加一,现在来看看addCount()方法。
1 | private final void addCount(long x, int check) { |
put的流程现在已经分析完了,你可以从中发现,他在并发处理中使用的是乐观锁,当有冲突的时候才进行并发处理,而且流程步骤很清晰,但是细节设计的很复杂,毕竟多线程的场景也复杂。
get操作
我们现在要回到开始的例子中,我们对个人信息进行了新增之后,我们要获取所新增的信息,使用String name = map.get(“name”)获取新增的name信息,现在我们依旧用debug的方式来分析下ConcurrentHashMap的获取方法get()
1 | public V get(Object key) { |
ConcurrentHashMap的get操作的流程很简单,也很清晰,可以分为三个步骤来描述:
- 计算hash值,定位到该table索引位置,如果是首节点符合就返回1. 如果遇到扩容的时候,会调用标志正在扩容节点ForwardingNode的find方法,查找该节点,匹配就返回1. 以上都不符合的话,就往下遍历节点,匹配就返回,否则最后就返回null
如果遇到扩容的时候,会调用标志正在扩容节点ForwardingNode的find方法,查找该节点,匹配就返回
size操作
最后我们来看下例子中最后获取size的方式int size = map.size();,现在让我们看下size()方法:
1 | public int size() { |
在JDK1.8版本中,对于size的计算,在扩容和addCount()方法就已经有处理了,JDK1.7是在调用size()方法才去计算,其实在并发集合中去计算size是没有多大的意义的,因为size是实时在变的,只能计算某一刻的大小,但是某一刻太快了,人的感知是一个时间段,所以并不是很精确。
总结与思考
其实可以看出JDK1.8版本的ConcurrentHashMap的数据结构已经接近HashMap,相对而言,ConcurrentHashMap只是增加了同步的操作来控制并发,从JDK1.7版本的ReentrantLock+Segment+HashEntry,到JDK1.8版本中synchronized+CAS+HashEntry+红黑树,相对而言,总结如下思考:
JDK1.8的实现降低锁的粒度,JDK1.7版本锁的粒度是基于Segment的,包含多个HashEntry,而JDK1.8锁的粒度就是HashEntry(首节点)
JDK1.8版本的数据结构变得更加简单,使得操作也更加清晰流畅,因为已经使用synchronized来进行同步,所以不需要分段锁的概念,也就不需要Segment这种数据结构了,由于粒度的降低,实现的复杂度也增加了
JDK1.8使用红黑树来优化链表,基于长度很长的链表的遍历是一个很漫长的过程,而红黑树的遍历效率是很快的,代替一定阈值的链表,这样形成一个最佳拍档
JDK1.8为什么使用内置锁synchronized来代替重入锁ReentrantLock,我觉得有以下几点:
因为粒度降低了,在相对而言的低粒度加锁方式,synchronized并不比ReentrantLock差,在粗粒度加锁中ReentrantLock可能通过Condition来控制各个低粒度的边界,更加的灵活,而在低粒度中,Condition的优势就没有了
JVM的开发团队从来都没有放弃synchronized,而且基于JVM的synchronized优化空间更大,使用内嵌的关键字比使用API更加自然
在大量的数据操作下,对于JVM的内存压力,基于API的ReentrantLock会开销更多的内存,虽然不是瓶颈,但是也是一个选择依据
参考
https://bentang.me/tech/2016/12/01/jdk8-concurrenthashmap-1/
Groovy
幂等性设计
隔离设计
常见方式
1、服务的种类来做分离
2、用户维度做分离
服务种类分离
以微服务形式,将业务拆分为用户中心,商品中心,评论中心,各自采用独立的服务器集群部署,独立的数据库。达到物理层面的隔离。
用户维度分离
将用户分成不同的组,并把后端的同一个服务根据这些不同的组分成不同的实例。同一个服务对于不同的用户进行冗余和隔离。这样一个服务实例挂掉,只会影响一部分用户。
“多租户”模式。针对一些大的客户,专用集群。
- 特点:
增加设计的复杂度,资源上存在浪费。
- 多租户模式分为三种:
1、完全独立。每个租户有自己完全独立的服务和数据。
2、独立的数据分区,服务共享。(较推荐方式)
3、共享服务,共享数据分区。
设计要点
定义好隔离业务的大小和粒度。过大、过小都不好。需要结合具体业务来看。
考虑系统的复杂度、成本、性能、资源使用的问题。找到合适的均衡方案。
配置一些高可用、重试、异步、消息中间件、流控、熔断等框架
自动化运维工具,象容器或虚拟化技术帮助我们更方便的管理
非常完善的监控系统
jsoup
- 依赖jar包
基于数据库实现分布式锁
要实现分布式锁,最简单的方式可能就是直接创建一张锁表,然后通过操作该表中的数据来实现了。