小白君的博客

凡事必先骑上虎背


  • 首页

  • 关于

  • 标签

  • 归档

Mybatis缓存

发表于 2019-08-20

一级缓存

在应用运行过程中,我们有可能在一次数据库会话中,执行多次查询条件完全相同的SQL,MyBatis提供了一级缓存的方案优化这部分场景,如果是相同的SQL语句,会优先命中一级缓存,避免直接对数据库进行查询,提高性能。具体执行过程如下图所示。

每个SqlSession中持有了Executor,每个Executor中有一个LocalCache。当用户发起查询时,MyBatis根据当前执行的语句生成MappedStatement,在Local Cache进行查询,如果缓存命中的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入Local Cache,最后返回结果给用户。具体实现类的类关系图如下图所示。

image-20190820170058613

一级缓存配置

我们来看看如何使用MyBatis一级缓存。开发者只需在MyBatis的配置文件中,添加如下语句,就可以使用一级缓存。共有两个选项,SESSION或者STATEMENT,默认是SESSION级别,即在一个MyBatis会话中执行的所有语句,都会共享这一个缓存。一种是STATEMENT级别,可以理解为缓存只对当前执行的这一个Statement有效。

1
<setting name="localCacheScope" value="SESSION"/>

总结

  1. MyBatis一级缓存的生命周期和SqlSession一致。
  2. MyBatis一级缓存内部设计简单,只是一个没有容量限定的HashMap,在缓存的功能性上有所欠缺。
  3. MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement。

二级缓存

在上文中提到的一级缓存中,其最大的共享范围就是一个SqlSession内部,如果多个SqlSession之间需要共享缓存,则需要使用到二级缓存。开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询,具体的工作流程如下所示。

image-20190820171955509

二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。

当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。

二级缓存配置

要正确的使用二级缓存,需完成如下配置的。

  1. 在MyBatis的配置文件中开启二级缓存。
1
<setting name="cacheEnabled" value="true"/>
  1. 在MyBatis的映射XML中配置cache或者 cache-ref 。

cache标签用于声明这个namespace使用二级缓存,并且可以自定义配置。

1
<cache/>
  • type:cache使用的类型,默认是PerpetualCache,这在一级缓存中提到过。
  • eviction: 定义回收的策略,常见的有FIFO,LRU。
  • flushInterval: 配置一定时间自动刷新缓存,单位是毫秒。
  • size: 最多缓存对象的个数。
  • readOnly: 是否只读,若配置可读写,则需要对应的实体类能够序列化。
  • blocking: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。

cache-ref代表引用别的命名空间的Cache配置,两个命名空间的操作使用的是同一个Cache。

1
<cache-ref namespace="mapper.StudentMapper"/>

总结

  1. MyBatis的二级缓存相对于一级缓存来说,实现了SqlSession之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,通过Cache接口实现类不同的组合,对Cache的可控性也更强。
  2. MyBatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
  3. 在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis、Memcached等分布式缓存可能成本更低,安全性也更高。

SpringAOP

发表于 2019-08-19

AOP概述

AOP称为面向切面编程,那我们怎么理解面向切面编程?

我们学Java面向对象的时候,如果代码重复了怎么办啊?可以分成下面几个步骤:

  • 抽取成方法
  • 抽取类

抽取成类的方式我们称之为:纵向抽取

  • 通过继承的方式实现纵向抽取

但是,我们现在的办法不行:即使抽取成类还是会出现重复的代码,因为这些逻辑(开始、结束、提交事务)依附在我们业务类的方法逻辑中!

阅读全文 »

SpringIOC

发表于 2019-08-18

IoC(Inversion of Control)控制反转,包含了两个方面:一、控制。二、反转

我们可以简单认为:

  • 控制指的是:当前对象对内部成员的控制权。
  • 反转指的是:这种控制权不由当前对象管理了,由其他(类,第三方容器)来管理。

使用IOC的好处:

  1. 不用自己组装,拿来就用。
  2. 享受单例的好处,效率高,不浪费空间。
  3. 便于单元测试,方便切换mock组件。
  4. 便于进行AOP操作,对于使用者是透明的。
  5. 统一配置,便于修改
阅读全文 »

LinkedListMap介绍

发表于 2019-08-16

前言

LinkedHashMap继承于HashMap

应用场景

HashMap是无序的,当我们希望有顺序地去存储key-value时,就需要使用LinkedHashMap了。

1
2
3
4
5
6
7
8
9
10
11
12
Map<String, String> hashMap = new HashMap<String, String>();
hashMap.put("name5", "value5");
hashMap.put("name6", "value6");
hashMap.put("name7", "value7");
Set<Map.Entry<String, String>> set = hashMap.entrySet();
Iterator<Map.Entry<String, String>> iterator = set.iterator();
while(iterator.hasNext()) {
Map.Entry entry = iterator.next();
String key = (String) entry.getKey();
String value = (String) entry.getValue();
System.out.println("key:" + key + ",value:" + value);
}

输出结果

1
2
3
key:name6,value:value6
key:name5,value:value5
key:name7,value:value7

同样的数据,我们再试试LinkedHashMap

1
2
3
4
5
6
7
8
9
10
11
12
Map<String, String> hashMap = new LinkedHashMap<>();
hashMap.put("name5", "value5");
hashMap.put("name6", "value6");
hashMap.put("name7", "value7");
Set<Map.Entry<String, String>> set = hashMap.entrySet();
Iterator<Map.Entry<String, String>> iterator = set.iterator();
while(iterator.hasNext()) {
Map.Entry entry = iterator.next();
String key = (String) entry.getKey();
String value = (String) entry.getValue();
System.out.println("key:" + key + ",value:" + value);
}

输出结果

1
2
3
key:name5,value:value5
key:name6,value:value6
key:name7,value:value7

LinkedHashMap是有序的,且默认为插入顺序。

LinkedHashMap空参的构造方法。

1
2
3
4
5
6
public LinkedHashMap() {
// 调用HashMap的构造方法
super();
// 这里是指是否基于访问排序,默认为false
accessOrder = false;
}

这里accessOrder设置为false,表示不是访问顺序而是插入顺序存储的,这也是默认值,表示LinkedHashMap中存储的顺序是按照调用put方法插入的顺序进行排序的。LinkedHashMap也提供了可以设置accessOrder的构造方法。

在HashMap的构造函数中,调用了init方法,而在HashMap中init方法是空实现,但LinkedHashMap重写了该方法,所以在LinkedHashMap的构造方法里,调用了自身的init方法,init的重写实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Called by superclass constructors and pseudoconstructors (clone,
* readObject) before any entries are inserted into the map. Initializes
* the chain.
*/
@Override
void init() {
// 创建了一个hash=-1,key、value、next都为null的Entry
header = new Entry<>(-1, null, null, null);
// 让创建的Entry的before和after都指向自身,注意after不是之前提到的next
// 其实就是创建了一个只有头部节点的双向链表
header.before = header.after = header;
}

LinkedHashMap有自己的静态内部类Entry,它继承了HashMap.Node,定义如下:

1
2
3
4
5
6
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}

所以LinkedHashMap构造函数,主要就是调用HashMap构造函数初始化了一个Entry[] table,然后调用自身的init初始化了一个只有头结点的双向链表。

总结

LinkedHashMap 是继承于HashMap,是基于HashMap和双向链表来实现的。

HashMap无序;LinkedHashMap有序,可分为插入顺序和访问顺序两种。如果是访问顺序,那put和get操作已存在的Entry时,都会把Entry移动到双向链表的表尾(其实是先删除再插入)。

LinkedHashMap存取数据,还是跟HashMap一样使用的Entry[]的方式,双向链表只是为了保证顺序。

LinkedHashMap是线程不安全的。

并发下是先操作数据库还是先操作缓存

发表于 2019-08-15

分布式锁

发表于 2019-08-13

分布式锁

在单机场景下,可以使用语言的内置锁来实现进程同步。但是在分布式场景下,需要同步的进程可能位于不同的节点上,那么就需要使用分布式锁。

阻塞锁通常使用互斥量来实现:

  • 互斥量为 0 表示有其它进程在使用锁,此时处于锁定状态;
  • 互斥量为 1 表示未锁定状态。

1 和 0 可以用一个整型值表示,也可以用某个数据是否存在表示。

阅读全文 »

再探索引-索引优化

发表于 2019-08-13

1. 独立的列

在进行查询时,索引列不能是表达式的一部分,也不能是函数的参数,否则无法使用索引。

例如下面的查询不能使用 actor_id 列的索引:

1
SELECT actor_id FROM sakila.actor WHERE actor_id + 1 = 5;

2. 多列索引

在需要使用多个列作为条件进行查询时,使用多列索引比使用多个单列索引性能更好。例如下面的语句中,最好把 actor_id 和 film_id 设置为多列索引。

1
SELECT film_id, actor_ id FROM sakila.film_actor WHERE actor_id = 1 AND film_id = 1;

3. 索引列的顺序

让选择性最强的索引列放在前面。

索引的选择性是指:不重复的索引值和记录总数的比值。最大值为 1,此时每个记录都有唯一的索引与其对应。选择性越高,每个记录的区分度越高,查询效率也越高。

例如下面显示的结果中 customer_id 的选择性比 staff_id 更高,因此最好把 customer_id 列放在多列索引的前面。

1
2
3
4
SELECT COUNT(DISTINCT staff_id)/COUNT(*) AS staff_id_selectivity,
COUNT(DISTINCT customer_id)/COUNT(*) AS customer_id_selectivity,
COUNT(*)
FROM payment;

4. 前缀索引

对于 BLOB、TEXT 和 VARCHAR 类型的列,必须使用前缀索引,只索引开始的部分字符。

前缀长度的选取需要根据索引选择性来确定。

5. 覆盖索引

索引包含所有需要查询的字段的值。

具有以下优点:

  • 索引通常远小于数据行的大小,只读取索引能大大减少数据访问量。
  • 一些存储引擎(例如 MyISAM)在内存中只缓存索引,而数据依赖于操作系统来缓存。因此,只访问索引可以不使用系统调用(通常比较费时)。
  • 对于 InnoDB 引擎,若辅助索引能够覆盖查询,则无需访问主索引。

索引的优点

  • 大大减少了服务器需要扫描的数据行数。
  • 帮助服务器避免进行排序和分组,以及避免创建临时表(B+Tree 索引是有序的,可以用于 ORDER BY 和 GROUP BY 操作。临时表主要是在排序和分组过程中创建,不需要排序和分组,也就不需要创建临时表)。
  • 将随机 I/O 变为顺序 I/O(B+Tree 索引是有序的,会将相邻的数据都存储在一起)。

索引的使用条件

  • 对于非常小的表、大部分情况下简单的全表扫描比建立索引更高效;
  • 对于中到大型的表,索引就非常有效;
  • 但是对于特大型的表,建立和维护索引的代价将会随之增长。这种情况下,需要用到一种技术可以直接区分出需要查询的一组数据,而不是一条记录一条记录地匹配,例如可以使用分区技术。

关于CMS的Young GC

发表于 2019-08-12

CMS 收集器

​ CMS 收集器是一种最短回收时间为目标的收集器。在重视服务的相应速度,希望系统停顿时间最短,以给用户最好的体验

​ CMS 收集器,实现的算法是标记-清除算法,整个过程分为:

  • 初始标记
  • 并发标记
  • 重新标记
  • 并发清除

其中初始标记和重新标记需要 STW

阅读全文 »

分布式事务

发表于 2019-08-11

分布式事务

指事务的操作位于不同的节点上,需要保证事务的 ACID 特性。

例如在下单场景下,库存和订单如果不在同一个节点上,就涉及分布式事务。

分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。

阅读全文 »

CPU缓存一致性协议MESI

发表于 2019-08-10

CPU高速缓存(Cache Memory)

CPU为何要有高速缓存

CPU在摩尔定律的指导下以每18个月翻一番的速度在发展,然而内存和硬盘的发展速度远远不及CPU。这就造成了高性能能的内存和硬盘价格及其昂贵。然而CPU的高度运算需要高速的数据。为了解决这个问题,CPU厂商在CPU中内置了少量的高速缓存以解决I\O速度和CPU运算速度之间的不匹配问题。

在CPU访问存储设备时,无论是存取数据抑或存取指令,都趋于聚集在一片连续的区域中,这就被称为局部性原理。

时间局部性(Temporal Locality):如果一个信息项正在被访问,那么在近期它很可能还会被再次访问。

比如循环、递归、方法的反复调用等。

空间局部性(Spatial Locality):如果一个存储器的位置被引用,那么将来他附近的位置也会被引用。

比如顺序执行的代码、连续创建的两个对象、数组等。

阅读全文 »
1234…8

Ma Xu

72 日志
23 标签
RSS
本站总访问量次
© 2020 Ma Xu