MyCat源码分析系列之——BufferPool与缓存机制
更多MyCat源码分析,请戳MyCat源码分析系列
BufferPool
MyCat的缓冲区采用的是java.nio.ByteBuffer,由BufferPool类统一管理,相关的设置在SystemConfig中。先明确一下相关概念和配置:
- 每个Buffer单元称之为一个chunk,默认chunk的大小(DEFAULT_BUFFER_CHUNK_SIZE)为4096字节
- BufferPool的总大小为DEFAULT_BUFFER_CHUNK_SIZE * processors * 1000,其中processors为处理器数量(Runtime.getRuntime().availableProcessors())
- 缓冲区有两种类型:本地缓存线程缓冲区和其他缓冲区,其中本地缓存线程指的是线程名以"$_"开头的线程(坦白说,我并不清楚这类线程是如何产生的,求各位指导)
BufferPool中核心的变量如下:
private final ThreadLocalBufferPool localBufferPool;
private final int chunkSize;
private final ConcurrentLinkedQueue<ByteBuffer> items = new ConcurrentLinkedQueue<ByteBuffer>();
private final long threadLocalCount;
private final long capactiy;
这些变量代表的含义分别如下:
- chunkSize:每个chunk的大小
- capacity:chunk的个数,计算方式为BufferPool的总大小/chunkSize
- items:ByteBuffer队列,初始大小为capacity,其中每个ByteBuffer由ByteBuffer.allocateDirect(chunkSize)创建
- threadLocalCount:本地线程数量,由capacity的某个比例计算得出,看起来相当于每个处理器分到的chunk个数
- localBufferPool:本地线程缓冲区,类型为继承了ThreadLocal<BufferQueue>的ThreadLocalBufferPool,BufferQueue中包含了类似的ByteBuffer链表items,其容量固定为threadLocalCount
接下来重点介绍分配Buffer和回收Buffer的过程。
1. 分配Buffer
分配Buffer时可以指定Buffer的大小,也可缺省该值,分别对应两个方法public ByteBuffer allocate(int size)和public ByteBuffer allocate(),实现如下:
public ByteBuffer allocate(int size) {
if (size <= this.chunkSize) {
return allocate();
} else {
LOGGER.warn("allocate buffer size large than default chunksize:"
+ this.chunkSize + " he want " + size);
return createTempBuffer(size);
}
} public ByteBuffer allocate() {
ByteBuffer node = null;
if (isLocalCacheThread()) {
// allocate from threadlocal
node = localBufferPool.get().poll();
if (node != null) {
return node;
}
}
node = items.poll();
if (node == null) {
//newCreated++;
newCreated.incrementAndGet();
node = this.createDirectBuffer(chunkSize);
}
return node;
}
- allocate():当执行线程为本地缓存线程时(isLocalCacheThread()返回true),先尝试从localBufferPool中获取一个可用的ByteBuffer;反之,从items中获取一个可用的ByteBuffer,若还是失败,则调用createDirectBuffer(size)创建新的ByteBuffer
private ByteBuffer createDirectBuffer(int size) {
// for performance
return ByteBuffer.allocateDirect(size);
}
- allocate(size):如果用户指定的size不大于chunkSize,则调用allocate()进行分配;反之则调用createTempBuffer(size)创建临时缓冲区,代码如下:
private ByteBuffer createTempBuffer(int size) {
return ByteBuffer.allocate(size);
}
2. 回收Buffer
回收Buffer时调用方法recycle(),相关代码如下:
public void recycle(ByteBuffer buffer) {
if (!checkValidBuffer(buffer)) {
return;
}
if (isLocalCacheThread()) {
BufferQueue localQueue = localBufferPool.get();
if (localQueue.snapshotSize() < threadLocalCount) {
localQueue.put(buffer);
} else {
// recyle 3/4 thread local buffer
items.addAll(localQueue.removeItems(threadLocalCount * 3 / 4));
items.offer(buffer);
sharedOptsCount++;
}
} else {
sharedOptsCount++;
items.offer(buffer);
}
} private boolean checkValidBuffer(ByteBuffer buffer) {
// 拒绝回收null和容量大于chunkSize的缓存
if (buffer == null || !buffer.isDirect()) {
return false;
} else if (buffer.capacity() > chunkSize) {
LOGGER.warn("cant' recycle a buffer large than my pool chunksize "
+ buffer.capacity());
return false;
}
totalCounts++;
totalBytes += buffer.limit();
buffer.clear();
return true;
}
首先调用checkValidBuffer()进行Buffer的有效性检测,该检测的目的是判断Buffer是否满足被回收(后续重用)的条件,以下3种情况不符合:
- Buffer为null
- Buffer不是Direct Buffer,即在分配时是通过createTempBuffer()创建出来的,而不是createDirectBuffer()
- Buffer的容量大于chunkSize
满足回收条件后,判断执行线程如果是本地缓存线程(isLocalCacheThread()返回true),若localBufferPool还有空余容量则将其放入,反之将localBufferPool中3/4的Buffer转移到items中并放入该Buffer;如果不是本地缓存线程直接放入items中
缓冲区的分配与回收机制如上所述,但单独设置所谓的本地缓存线程缓冲区的意义以及回收时出现的3/4转移的设置本人暂不清楚。
缓存机制
MyCat的缓存机制用于路由信息计算时为某些特定场景节省二次计算的开销,直接从相应的缓存中获取结果。
配置文件为cacheservice.properties,里面可以配置各类缓存统一的类型、大小、过期时间等,也可为每张表独立设置参数,其中提供3类缓存类型:ehcache、leveldb和mapdb。
缓存池为CachePool,它是一个接口,具体每个CachePool实现类由对应CachePoolFactory创建:
public interface CachePool { public void putIfAbsent(Object key, Object value); public Object get(Object key); public void clearCache(); public CacheStatic getCacheStatic(); public long getMaxSize();
}
CacheService作为缓存服务类存在,其init()方法负责读取缓存配置文件并创建相应的CachePoolFactory和CachePool:
private void init() throws Exception {
Properties props = new Properties();
props.load(CacheService.class
.getResourceAsStream("/cacheservice.properties"));
final String poolFactoryPref = "factory.";
final String poolKeyPref = "pool.";
final String layedPoolKeyPref = "layedpool.";
String[] keys = props.keySet().toArray(new String[0]);
Arrays.sort(keys);
for (String key : keys) { if (key.startsWith(poolFactoryPref)) {
createPoolFactory(key.substring(poolFactoryPref.length()),
(String) props.get(key));
} else if (key.startsWith(poolKeyPref)) {
String cacheName = key.substring(poolKeyPref.length());
String value = (String) props.get(key);
String[] valueItems = value.split(",");
if (valueItems.length < 3) {
throw new java.lang.IllegalArgumentException(
"invalid cache config ,key:" + key + " value:"
+ value);
}
String type = valueItems[0];
int size = Integer.valueOf(valueItems[1]);
int timeOut = Integer.valueOf(valueItems[2]);
createPool(cacheName, type, size, timeOut);
} else if (key.startsWith(layedPoolKeyPref)) {
String cacheName = key.substring(layedPoolKeyPref.length());
String value = (String) props.get(key);
String[] valueItems = value.split(",");
int index = cacheName.indexOf(".");
if (index < 0) {// root layer
String type = valueItems[0];
int size = Integer.valueOf(valueItems[1]);
int timeOut = Integer.valueOf(valueItems[2]);
createLayeredPool(cacheName, type, size, timeOut);
} else {
// root layers' children
String parent = cacheName.substring(0, index);
String child = cacheName.substring(index + 1);
CachePool pool = this.allPools.get(parent);
if (pool == null || !(pool instanceof LayerCachePool)) {
throw new java.lang.IllegalArgumentException(
"parent pool not exists or not layered cache pool:"
+ parent + " the child cache is:"
+ child);
} int size = Integer.valueOf(valueItems[0]);
int timeOut = Integer.valueOf(valueItems[1]);
((DefaultLayedCachePool) pool).createChildCache(child,
size, timeOut);
}
}
}
}
MyCat设置了3种缓存,分别是SQLRouteCache、TableId2DataNodeCache和ER_SQL2PARENTID:
- SQLRouteCache:
- 根据SQL语句查找路由信息的缓存,CachePool类型,key为虚拟库名+SQL语句,value为路由信息RouteResultSet
- 该缓存只针对select语句,如果执行了之前已经执行过的某个SQL语句(缓存命中),那路由信息就不需要重复计算,直接从缓存中获取,RouteService的route()方法中有关于此缓存的相关代码片段:
/**
* SELECT 类型的SQL, 检测
*/
if (sqlType == ServerParse.SELECT) {
cacheKey = schema.getName() + stmt;
rrs = (RouteResultset) sqlRouteCache.get(cacheKey);
if (rrs != null) {
return rrs;
}
} if (rrs!=null && sqlType == ServerParse.SELECT && rrs.isCacheAble()) {
sqlRouteCache.putIfAbsent(cacheKey, rrs);
}
- TableId2DataNodeCache:
- 表主键到datanode的缓存,LayerCachePool类型,为双层CachePool,第一层:key为虚拟库名+表名,value为CachePool;第二层:key为主键值,value为datanode名
- 设置该缓存的目的在于当分片字段与主键字段不同时,直接通过主键值查询是无法定位具体分片的(只能全分片下发),所以设置之后就可以利用主键值查找到分片名
- 该缓存的放入过程在MultiNodeQueryHandler的rowResponse()中,代码片段如下:
// cache primaryKey-> dataNode
if (primaryKeyIndex != -1) {
RowDataPacket rowDataPkg = new RowDataPacket(fieldCount);
rowDataPkg.read(row);
String primaryKey = new String(rowDataPkg.fieldValues.get(primaryKeyIndex));
LayerCachePool pool = MycatServer.getInstance()
.getRouterservice().getTableId2DataNodeCache();
pool.putIfAbsent(priamaryKeyTable, primaryKey, dataNode);
}- ER_SQL2PARENTID:ER关系专用,子表插入数据时根据父子关联字段确定子表分片,下次可以直接从缓存中获取所在分片,key为虚拟库名+SQL语句,value是datanode名
缓存查看:通过9066管理端口连接MyCat,执行命令mysql> show @@cache;可以观察目前系统中设置的各类缓存,以及数量、访问次数和命中情况等
为尊重原创成果,如需转载烦请注明本文出处:
http://www.cnblogs.com/fernandolee24/p/5198192.html,特此感谢
MyCat源码分析系列之——BufferPool与缓存机制的更多相关文章
- 开源分布式数据库中间件MyCat源码分析系列
MyCat是当下很火的开源分布式数据库中间件,特意花费了一些精力研究其实现方式与内部机制,在此针对某些较为重要的源码进行粗浅的分析,希望与感兴趣的朋友交流探讨. 本源码分析系列主要针对代码实现,配置. ...
- MyCat源码分析系列之——配置信息和启动流程
更多MyCat源码分析,请戳MyCat源码分析系列 MyCat配置信息 除了一些默认的配置参数,大多数的MyCat配置信息是通过读取若干.xml/.properties文件获取的,主要包括: 1)se ...
- MyCat源码分析系列之——结果合并
更多MyCat源码分析,请戳MyCat源码分析系列 结果合并 在SQL下发流程和前后端验证流程中介绍过,通过用户验证的后端连接绑定的NIOHandler是MySQLConnectionHandler实 ...
- MyCat源码分析系列之——SQL下发
更多MyCat源码分析,请戳MyCat源码分析系列 SQL下发 SQL下发指的是MyCat将解析并改造完成的SQL语句依次发送至相应的MySQL节点(datanode)的过程,该执行过程由NonBlo ...
- MyCat源码分析系列之——前后端验证
更多MyCat源码分析,请戳MyCat源码分析系列 MyCat前端验证 MyCat的前端验证指的是应用连接MyCat时进行的用户验证过程,如使用MySQL客户端时,$ mysql -uroot -pr ...
- spring源码分析系列 (8) FactoryBean工厂类机制
更多文章点击--spring源码分析系列 1.FactoryBean设计目的以及使用 2.FactoryBean工厂类机制运行机制分析 1.FactoryBean设计目的以及使用 FactoryBea ...
- jQuery-1.9.1源码分析系列(四) 缓存系统
先前在分析Sizzle的时候分析到Sizzle有自己的缓存机制,点击这里查看.不过Sizzle的缓存只是对内使用的(内部自己存,自己取).接下来分析jQuery可以对外使用的缓存(可存可取). 首先需 ...
- Thinkphp源码分析系列(六)–路由机制
在ThinkPHP框架中,是支持URL路由功能,要启用路由功能,需要设置ROUTER_ON 参数为true. 开启路由功能后,系统会自动进行路由检测,如果在路由定义里面找到和当前URL匹配的路由名称, ...
- jQuery-1.9.1源码分析系列完毕目录整理
jQuery 1.9.1源码分析已经完毕.目录如下 jQuery-1.9.1源码分析系列(一)整体架构 jQuery-1.9.1源码分析系列(一)整体架构续 jQuery-1.9.1源码分析系列(二) ...
随机推荐
- Log4net - 项目使用的一个简单Demo
参考页面: http://www.yuanjiaocheng.net/entity/entitytypes.html http://www.yuanjiaocheng.net/entity/entit ...
- linux服务器开发一 基础
注:本文仅限交流使用,请务用于商业用途,否则后果自负! Linux 1.Linux介绍 Linux是类Unix计算机操作系统的统称. Linux操作系统的内核的名字也是“Linux”. Linux这个 ...
- atitit.细节决定成败的适合情形与缺点
atitit.细节决定成败的适合情形与缺点 1. 在理论界有两种观点:一种是"细节决定成败",另一种是"战略决定成败".1 1.1. 格局决定成败,方向决定成败 ...
- python性能检测工具整理
python 运行后出现core dump产生core.**文件,可通过gdb来调试 Using GDB with a core dump having found build/python/core ...
- JavaScript多线程之HTML5 Web Worker
在博主的前些文章Promise的前世今生和妙用技巧和JavaScript单线程和浏览器事件循环简述中都曾提到了HTML5 Web Worker这一个概念.在JavaScript单线程和浏览器事件循环简 ...
- [转载]大型网站应用中 MySQL 的架构演变史
没有什么东西是一成不变的,包含我们的理想和生活!MySQL作为一个免费的开源的关系型数据库,深受大家喜爱,从最初的无人问津到当下的去IOE,都体现出了MySQL举足轻重的作用.今天我们就从淘宝的发展来 ...
- useful Ansible commands
This article includes some useful Ansible commands. I will try to write blogs by English. You may wa ...
- Linux下UPnP sample分析
一.UPnP简介 UPnP(Universal Plug and Play)技术是一种屏蔽各种数字设备的硬件和操作系统的通信协议.它是一种数字网络中间件技术,建立在TCP/IP.HTTP协 ...
- 自定义ActionBar标题与菜单中的文字样式
自定义标题文字样式 标题样式是ActionBar样式的一部分,所以要先定义ActionBar的样式 <style name="AppTheme" parent="A ...
- Rxjava Subjects
上次提到调用observable的publish和connect方法后可以将一个Observable发出的对象实时传递到订阅在上的subscriber. 这个和Rxjava中Subject的概念十分相 ...