更多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:
  1. 根据SQL语句查找路由信息的缓存,CachePool类型,key为虚拟库名+SQL语句,value为路由信息RouteResultSet
  2. 该缓存只针对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:
  1. 表主键到datanode的缓存,LayerCachePool类型,为双层CachePool,第一层:key为虚拟库名+表名,value为CachePool;第二层:key为主键值,value为datanode名
  2. 设置该缓存的目的在于当分片字段与主键字段不同时,直接通过主键值查询是无法定位具体分片的(只能全分片下发),所以设置之后就可以利用主键值查找到分片名
  3. 该缓存的放入过程在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与缓存机制的更多相关文章

  1. 开源分布式数据库中间件MyCat源码分析系列

    MyCat是当下很火的开源分布式数据库中间件,特意花费了一些精力研究其实现方式与内部机制,在此针对某些较为重要的源码进行粗浅的分析,希望与感兴趣的朋友交流探讨. 本源码分析系列主要针对代码实现,配置. ...

  2. MyCat源码分析系列之——配置信息和启动流程

    更多MyCat源码分析,请戳MyCat源码分析系列 MyCat配置信息 除了一些默认的配置参数,大多数的MyCat配置信息是通过读取若干.xml/.properties文件获取的,主要包括: 1)se ...

  3. MyCat源码分析系列之——结果合并

    更多MyCat源码分析,请戳MyCat源码分析系列 结果合并 在SQL下发流程和前后端验证流程中介绍过,通过用户验证的后端连接绑定的NIOHandler是MySQLConnectionHandler实 ...

  4. MyCat源码分析系列之——SQL下发

    更多MyCat源码分析,请戳MyCat源码分析系列 SQL下发 SQL下发指的是MyCat将解析并改造完成的SQL语句依次发送至相应的MySQL节点(datanode)的过程,该执行过程由NonBlo ...

  5. MyCat源码分析系列之——前后端验证

    更多MyCat源码分析,请戳MyCat源码分析系列 MyCat前端验证 MyCat的前端验证指的是应用连接MyCat时进行的用户验证过程,如使用MySQL客户端时,$ mysql -uroot -pr ...

  6. spring源码分析系列 (8) FactoryBean工厂类机制

    更多文章点击--spring源码分析系列 1.FactoryBean设计目的以及使用 2.FactoryBean工厂类机制运行机制分析 1.FactoryBean设计目的以及使用 FactoryBea ...

  7. jQuery-1.9.1源码分析系列(四) 缓存系统

    先前在分析Sizzle的时候分析到Sizzle有自己的缓存机制,点击这里查看.不过Sizzle的缓存只是对内使用的(内部自己存,自己取).接下来分析jQuery可以对外使用的缓存(可存可取). 首先需 ...

  8. Thinkphp源码分析系列(六)–路由机制

    在ThinkPHP框架中,是支持URL路由功能,要启用路由功能,需要设置ROUTER_ON 参数为true. 开启路由功能后,系统会自动进行路由检测,如果在路由定义里面找到和当前URL匹配的路由名称, ...

  9. jQuery-1.9.1源码分析系列完毕目录整理

    jQuery 1.9.1源码分析已经完毕.目录如下 jQuery-1.9.1源码分析系列(一)整体架构 jQuery-1.9.1源码分析系列(一)整体架构续 jQuery-1.9.1源码分析系列(二) ...

随机推荐

  1. $.type 怎么精确判断对象类型的 --(源码学习2)

    目标:  var a = [1,2,3];     console.log(typeof a); //->object     console.log($.type(a)); //->ar ...

  2. Hive on Spark安装配置详解(都是坑啊)

    个人主页:http://www.linbingdong.com 简书地址:http://www.jianshu.com/p/a7f75b868568 简介 本文主要记录如何安装配置Hive on Sp ...

  3. ASP.NET MVC with Entity Framework and CSS一书翻译系列文章之目录导航

    ASP.NET MVC with Entity Framework and CSS是2016年出版的一本比较新的.关于ASP.NET MVC.EF以及CSS技术的图书,我将尝试着翻译本书以供日后查阅. ...

  4. 卸载oracle之后,如何清除注册表

    之前卸载了oracle,今天偶然间发现,在服务和应用程序里面,还残存着之前的oracle服务.原来,还需要去清理下注册表. 在开始菜单的这个框里面 输入regedit,进入注册表.找到这个目录 HKE ...

  5. 那些年【深入.NET平台和C#编程】

    一.深入.NET框架 1..NET框架具有两个组件:CLR(公共语言运行时)和FCL(框架类库),CLR是.NET框架的基础 2.框架核心类库: System.Collections.Generic: ...

  6. Android中的LinearLayout布局

    LinearLayout : 线性布局 在一般情况下,当有很多控件需要在一个界面列出来时,我们就可以使用线性布局(LinearLayout)了,  线性布局是按照垂直方向(vertical)或水平方向 ...

  7. MongoDB学习笔记四—增删改文档下

    $slice 如果希望数组的最大长度是固定的,那么可以将 $slice 和 $push 组合在一起使用,就可以保证数组不会超出设定好的最大长度.$slice 的值必须是负整数. 假设$slice的值为 ...

  8. 最近在玩linux时 yum 遇到了问题

    主要是软件源出现了问题 我做的方式可能比较粗暴 ls -l /etc/yum.repos.d/       /*查看软件源*/ rm -rf /etc/yum.repos.d/   /*全删了*/ m ...

  9. 【微信SEO】公众号也能做排名?

    [写于2016年8月] 最近,微信团队发出一则公告,开放公众号运营者一年内更改公众号名一次,这对不少名字起的奇葩名字(包括dkplus)的公众号来说是一件好事. 为什么说是好事呢?公众号名字直接关联到 ...

  10. VS2012+EF6+Mysql配置心路历程

    为了学习ORM,选择了EntityFramework,经历了三天两夜的煎熬,N多次错误,在群里高手的帮助下,终于成功,现在将我的心路历程记录下来,一是让自己有个记录,另外就是让其它人少走些弯路. 我的 ...