当前位置:必发365电子游戏 > 编程 > 并支持自定义计算Component组件,首次执行它时从数据库获取的所有数据会被存储在一段高速缓存中
并支持自定义计算Component组件,首次执行它时从数据库获取的所有数据会被存储在一段高速缓存中
2019-12-19

MySpace作为.NET结构在网络平台最为成功的案例之生龙活虎,当中特别首要的系统datarelay布满式数据缓存也开源了,DataRelay提供了高品质的缓存系统和消息管理体制,并接济自定义计算Component组件,支持Cluster,有整机的Replication和负载均衡机制,组件都是以windows服务的样式,能够特别灵活的进展布局,客商端与服务端使用Socket实行通讯通讯,别的还足以异常低价的扩展各类自定义组件,举例缓存部分可以动用Memcached,还大概有近期比较流行Redis。

1 缓存介绍#

MyBatis支持证明式数据缓存(declarative data caching)。当一条SQL语句被标识为“可缓存”后,第一回奉行它时从数据库获取的享有数据会被储存在黄金年代段高速缓存中,以往举行那条语句时就能够从高速缓存中读取结果,并非重新命中数据库。MyBatis提供了私下认可下基于Java HashMap的缓存完结,以至用于与OSCache、Ehcache、Hazelcast和Memcached连接的暗许连接器。MyBatis还提供API供其余缓存完结应用。

根本的那句话就是:MyBatis施行SQL语句之后,那条语句就是被缓存,以往再执行那条语句的时候,会直接从缓存中拿结果,实际不是再度实践SQL。

这也等于富贵人家常说的MyBatis拔尖缓存,超级缓存的效用域scope是SqlSession。MyBatis同期还提供了意气风发种全局意义域global scope的缓存,那也叫做二级缓存,也称作全局缓存。

MyBatis将数据缓存设计成两级组织,分为顶尖缓存、二级缓存:

一流缓存是Session会话级其他缓存,坐落于表示叁次数据库会话的SqlSession对象之中,又被称呼本地缓存。一流缓存是MyBatis内部贯彻的一个本性,客户无法配置,暗中同意意况下自行帮助的缓存,客户并未定制它的任务(不过那亦不是绝对的,可以经过支付插件对它进行纠正);

二级缓存是Application应用等第的缓存,它的是生命周期十分长,跟Application的证明周期同样,也正是说它的效果与利益范围是整整Application应用

MyBatis中拔尖缓存和二级缓存的集体如下图所示:

图片 1

MyBatis缓存机制暗暗表示图

MySpace即便开源出来datarelay,然而未有很好的文书档案帮忙大家学习,下边将对全部代码分析,让我们从大局认知DataRelay这套在.net平台上少见的精品。

2 超级缓存#

拔尖缓存的干活机制:

一级缓存是Session会话等第的,日常来说,三个SqlSession对象会使用叁个Executor对象来完毕会话操作,Executor对象会维护三个Cache缓存,以巩固查询质量。关于超级缓存的详尽达成,可参见MyBatis一级缓存完结。

CodePlex代码下载地址:http://datarelay.codeplex.com

2.1 缓存测量检验##

  1. 同个session举行五遍相近查询:
@Test
public void test() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try {
        User user = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 1);
        log.debug(user);
        User user2 = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 1);
        log.debug(user2);
    } finally {
        sqlSession.close();
    }
}

MyBatis只实行1次数据库查询:

==> Preparing: select * from USERS WHERE ID = ?
==> Parameters: 1(Integer)
<== Total: 1
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}
  1. 同个session实行若干次差异的查询:
@Test
public void test() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try {
        User user = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 1);
        log.debug(user);
        User user2 = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 2);
        log.debug(user2);
    } finally {
        sqlSession.close();
    }
}

MyBatis举办三回数据库查询:

==> Preparing: select * from USERS WHERE ID = ?
==> Parameters: 1(Integer)
<== Total: 1
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}
==> Preparing: select * from USERS WHERE ID = ?
==> Parameters: 2(Integer)
<== Total: 1
User{id=2, name='FFF', age=50, birthday=Sat Dec 06 17:12:01 CST 2014}
  1. 今是昨非session,实行相通查询:
@Test
public void test() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    try {
        User user = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 1);
        log.debug(user);
        User user2 = (User)sqlSession2.selectOne("org.format.mybatis.cache.UserMapper.getById", 1);
        log.debug(user2);
    } finally {
        sqlSession.close();
        sqlSession2.close();
    }
}

MyBatis进行了一次数据库查询:

==> Preparing: select * from USERS WHERE ID = ?
==> Parameters: 1(Integer)
<== Total: 1
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}
==> Preparing: select * from USERS WHERE ID = ?
==> Parameters: 1(Integer)
<== Total: 1
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}
  1. 同个session,查询之后更新数据,再度查询同生机勃勃的讲话:
@Test
public void test() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try {
        User user = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 1);
        log.debug(user);
        user.setAge(100);
        sqlSession.update("org.format.mybatis.cache.UserMapper.update", user);
        User user2 = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 1);
        log.debug(user2);
        sqlSession.commit();
    } finally {
        sqlSession.close();
    }
}

修改操作之后缓存会被拔除:

==> Preparing: select * from USERS WHERE ID = ?
==> Parameters: 1(Integer)
<== Total: 1
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}
==> Preparing: update USERS SET NAME = ? , AGE = ? , BIRTHDAY = ? where ID = ?
==> Parameters: format(String), 23(Integer), 2014-10-12 23:20:13.0(Timestamp), 1(Integer)
<== Updates: 1
==> Preparing: select * from USERS WHERE ID = ?
==> Parameters: 1(Integer)
<== Total: 1
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}

很鲜明,结果证实了一流缓存的定义,在同个SqlSession中,查询语句雷同的sql会被缓存,但是只要实行新扩张或更新或删除操作,缓存就能够被肃清

MIX 10上的阐述:罗布ots at MySpace: Massive Scaling a .NET Website with the Microsoft 罗布otic Studio

2.2 源码解析##

在言之有序MyBatis的一级缓存早先,大家先轻松看下MyBatis中多少个根本的类和接口:

org.apache.ibatis.session.Configuration类:MyBatis全局配置新闻类

org.apache.ibatis.session.SqlSessionFactory接口:操作SqlSession的工厂接口,具体的完毕类是DefaultSqlSessionFactory

org.apache.ibatis.session.SqlSession接口:推行sql,处总管务的接口,具体的兑现类是DefaultSqlSession

org.apache.ibatis.executor.Executor接口:sql执行器,SqlSession实践sql最后是经过该接口完毕的,常用的兑现类有SimpleExecutor和CachingExecutor,那几个完结类都施用了装饰者设计形式

顶级缓存的作用域是SqlSession,那么大家就先看一下SqlSession的select进程:

  1. 那是DefaultSqlSession(SqlSession接口完成类,MyBatis默许使用那一个类)的selectList源码(大家例子上使用的是selectOne方法,调用selectOne方法最终会进行selectList方法):
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
     try {
         MappedStatement ms = configuration.getMappedStatement(statement);
         List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
         return result;
     } catch (Exception e) {
         throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
     } finally {
         ErrorContext.instance().reset();
     }
}
  1. 咱俩看见SqlSession最后会调用Executor接口的方式。接下来我们看下DefaultSqlSession中的executor接口属性具体是哪位达成类。DefaultSqlSession的布局进度(DefaultSqlSessionFactory内部):
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
      Transaction tx = null;
      try {
          final Environment environment = configuration.getEnvironment();
          final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
          tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
          final Executor executor = configuration.newExecutor(tx, execType, autoCommit);
          return new DefaultSqlSession(configuration, executor);
      } catch (Exception e) {
          closeTransaction(tx); // may have fetched a connection so lets call close()
          throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
      } finally {
          ErrorContext.instance().reset();
      }
}
  1. 小编们看来DefaultSqlSessionFactory布局DefaultSqlSession的时候,Executor接口的兑现类是由Configuration布局的:
public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) {
      executorType = executorType == null ? defaultExecutorType : executorType;
      executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
      Executor executor;
      if (ExecutorType.BATCH == executorType) {
          executor = new BatchExecutor(this, transaction);
      } else if (ExecutorType.REUSE == executorType) {
          executor = new ReuseExecutor(this, transaction);
      } else {
          executor = new SimpleExecutor(this, transaction);
      }
      if (cacheEnabled) {
          executor = new CachingExecutor(executor, autoCommit);
      }
      executor = (Executor) interceptorChain.pluginAll(executor);
      return executor;
}

Executor依据ExecutorType的不如而创办,最常用的是SimpleExecutor,本文的例子也是创建这些实现类。 最后大家发掘只要cacheEnabled这么些性格为true的话,那么executor会被包黄金时代层装饰器,这么些装饰器是 CachingExecutor。在那之中cacheEnabled这几个个性是mybatis总构造文件中settings节点中cacheEnabled子节点的值,私下认可正是true,也正是说大家在mybatis总计构文件中不配cacheEnabled的话,它也是默感觉展开的。

  1. 这段日子,难点就剩下三个了,CachingExecutor实施sql的时候到底做了如何?带着这一个难点,我们继续走下去(CachingExecutor的query方法):
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
      Cache cache = ms.getCache();
      if (cache != null) {
          flushCacheIfRequired(ms);
          if (ms.isUseCache() && resultHandler == null) {
              ensureNoOutParams(ms, parameterObject, boundSql);
              if (!dirty) {
                  cache.getReadWriteLock().readLock().lock();
                  try {
                      @SuppressWarnings("unchecked")
                      List<E> cachedList = (List<E>) cache.getObject(key);
                      if (cachedList != null) return cachedList;
                  } finally {
                      cache.getReadWriteLock().readLock().unlock();
                  }
              }
              List<E> list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
              tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks
              return list;
          }
      }
      return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

此中Cache cache = ms.getCache(卡塔尔(قطر‎;那句代码中,这些cache实际上便是个二级缓存,由于大家尚无拉开二级缓存(二级缓存的原委上面会深入分析卡塔尔,因而这里进行了最后一句话。此间的delegate也即是SimpleExecutor,SimpleExecutor未有Override父类的query方法,由此最后施行了SimpleExecutor的父类BaseExecutor的query方法

  1. 所以一级缓存最要害的代码正是BaseExecutor的query方法!
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
      ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
      if (closed) throw new ExecutorException("Executor was closed.");
      if (queryStack == 0 && ms.isFlushCacheRequired()) {
         clearLocalCache();
      }
      List<E> list;
      try {
         queryStack++;
         list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
         if (list != null) {
             handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
         } else {
             list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
         }
      } finally {
         queryStack--;
      }
      if (queryStack == 0) {
         for (DeferredLoad deferredLoad : deferredLoads) {
             deferredLoad.load();
         }
         deferredLoads.clear(); // issue #601
         if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
             clearLocalCache(); // issue #482
         }
      }
      return list;
}

BaseExecutor的习性localCache是个PerpetualCache类型的实例,PerpetualCache类是促成了MyBatis的Cache缓存接口的兑现类之风姿罗曼蒂克,其间有个Map类型的属性用来储存缓存数据。那些localCache的品种在BaseExecutor内部是写死的。这几个localCache正是顶级缓存!

  1. 接下去大家看下何以执行新扩张或更新或删除操作,一流缓存就能够被消逝那么些难点。首先MyBatis管理新扩充或删除的时候,说起底都是调用update方法,也正是说新扩展或许去除操作在MyBatis眼里都以一个修改操作。大家看下DefaultSqlSession的update方法:
public int update(String statement, Object parameter) {
     try {
         dirty = true;
         MappedStatement ms = configuration.getMappedStatement(statement);
         return executor.update(ms, wrapCollection(parameter));
     } catch (Exception e) {
         throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
     } finally {
         ErrorContext.instance().reset();
     }
}

很明显,这里调用了CachingExecutor的update方法:

public int update(MappedStatement ms, Object parameterObject) throws SQLException {
     flushCacheIfRequired(ms);
      return delegate.update(ms, parameterObject);
}

此处的flushCacheIfRequired方法消逝的是二级缓存,我们现在会深入分析。 CachingExecutor委托给了(在此以前曾经解析过卡塔尔SimpleExecutor的update方法,SimpleExecutor没有Override父类BaseExecutor的update方法,由此大家看BaseExecutor的update方法:

public int update(MappedStatement ms, Object parameter) throws SQLException {
      ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
      if (closed) throw new ExecutorException("Executor was closed.");
      clearLocalCache();
      return doUpdate(ms, parameter);
}
  1. 咱俩看见了着重的一句代码: clearLocalCache(卡塔尔国; 进去看看:
public void clearLocalCache() {
     if (!closed) {
         localCache.clear();
         localOutputParameterCache.clear();
     }
}

没有错,正是那条,sqlsession未有关闭的话,实行增加生产数量、删除、改正操作的话正是祛除一级缓存,也正是SqlSession的缓存

DataRelay布局深入分析

重点解析DataRelay的布局,分别从DataRelay的特征,系统的概况安插结构,以至系统的内部结商谈遵照组件标准接口拆解分析,详细地介绍了DataRelay系统的构架思想以致落到实处方案。

3 二级缓存#

二级缓存的作用域是大局,换句话说,二级缓存已经淡出SqlSession的支配了。二级缓存的功效域是大局的,二级缓存在SqlSession关闭或提交现在才会生效。

在分条析理MyBatis的二级缓存以前,我们先轻便看下MyBatis中多少个有关二级缓存的类(其余连锁的类和接口早前已经剖判过卡塔尔,org.apache.ibatis.mapping.MappedStatement:

MappedStatement类在Mybatis框架中用来表示XML文件中二个sql语句节点,即八个<select />、<update />或然<insert />标签。Mybatis框架在最早化阶段会对XML配置文件进行读取,将个中的sql语句节点指标形成二个个MappedStatement对象。

二级缓存的办事机制:

一个SqlSession对象会动用一个Executor对象来完毕会话操作,MyBatis的二级缓存机制的主要便是对这些Executor对象做小说。如若客商配置了"cacheEnabled=true",那么MyBatis在为SqlSession对象创立Executor对象时,会对Executor对象加上三个装饰者:CachingExecutor,这个时候SqlSession使用CachingExecutor对象来完毕操作央浼。CachingExecutor对于查询央求,会先剖断该查询乞求在Application品级的二级缓存中是否有缓存结果,假设有询问结果,则直接重返缓存结果;如若缓存中从不,再付诸真正的Executor对象来实现查询操作,之后CachingExecutor会将真正Executor重临的查询结果放置到缓存中,然后在回来给顾客。

MyBatis的二级缓存设计得相比灵敏,您能够运用MyBatis本身定义的二级缓存完毕;你也足以通过兑现org.apache.ibatis.cache.Cache接口自定义缓存;也可以利用第三方内部存款和储蓄器缓存库,如Memcached等。

图片 2

Paste_Image.png

图片 3

Paste_Image.png

1.DataRelay的基本特点

DataRelay在参照他事他说加以考察各样数码缓存功效和准备思想功底上,在.NET平台系统下统筹并贯彻的意气风发套布满式缓存体系,具备以下特点:

<!--[if !supportLists]-->1) <!--[endif]-->利用现存的Cache建设方案来形开支地Cache功用。现成的Beck雷DB、Memcached、 当地Cache模块都得以作为插件,接入该系统中,作为地点Cache机制。

<!--[if !supportLists]-->2) <!--[endif]-->自定义连串化和反连串接口,缩小存款和储蓄空间,提供互连网传输功用。

<!--[if !supportLists]-->3) <!--[endif]-->服务配置简单,对服务节点帮助热插拔。

<!--[if !supportLists]-->4) <!--[endif]-->对符合DataRelay组件接口定义的模块,通过联合的零器件接口管理模块,对服务端组件扶持动态更新。

<!--[if !supportLists]-->5) <!--[endif]-->标准的零部件开采接口,大大简化了组件开垦,提升增添性。

<!--[if !supportLists]-->6) <!--[endif]-->网络音讯分发与协同结合了Replicated Cache和Distributed Cache情势,保障了系统的可信行。

<!--[if !supportLists]-->7) <!--[endif]-->利用微软CCPRADO组件(并发与协和运营时(Concurrency and Coordination Runtime卡塔尔)很好的治本音讯的异步、并发、协和养曲折管理,保险了系统的长足,稳固。

3.1 缓存配置##

二级缓存跟一级缓存分化,一级缓存不需要配置任何东西,且默认打开。 二级缓存就要求配备部分东西。本文就说下最简便易行的安顿,在mapper文件上增添这句配置就能够。其实二级缓存跟3个布局有关:

  1. mybatis全局配置文件中的setting中的cacheEnabled需求为true(默感觉true,不安装也行卡塔尔
  2. mapper配置文件中须要投入<cache>节点
  3. mapper配置文件中的select节点供给丰硕属性useCache须求为true(默许为true,不安装也行卡塔尔国

2 DataRelay的情理构造

DataRelay的物理结构图如图1所示,评释了DataRelay在整个网址系统中所处的地位。DataRelay处于整个网址类别中的中间层,差别于日常中间层设计,Web服务器即三番五次数据库服务器,也还要连接中间层,那样设计可避防卫单点,尽管Web服务器只连接中间层,风姿洒脱旦中间层服务器荡机,整个网址将不能够做事,而选拔图中解决方案,生龙活虎旦中间层服务器当机,Web服务器相近能够一向访谈数据库服务器,不至于不干活。当Web服务器央求被Cache的业务对象时,首先央浼DataRelay系统,如若该数额在DataRelay系统中设有,将直接再次回到给Web服务器,当不设有DataRelay系统中,系统将倡议转向数据库诉求,诉求到数码首先将数据保存到DataRelay系统中,过后再次来到给Web服务器。

 图片 4
<!--[endif]-->

图1 网址物理布局图

任何DataRelay集群陈设如图2所示,对于DataRelay的服务器的团伙构造,首要有以下三点定义:

<!--[if !supportLists]-->1) <!--[endif]-->Groups

<!--[if !supportLists]-->l <!--[endif]-->差异的组存款和储蓄不一样的数额,在DataRelay系统中,能够定义三个组,能够针对组进行拜望形式设置。

<!--[if !supportLists]-->2) <!--[endif]-->Clusters

<!--[if !supportLists]-->l <!--[endif]-->三个集群存在一个组中,缓存业务的数码对象根据Distributed Cache情势分配选用必要保留的集群地址。

<!--[if !supportLists]-->3) <!--[endif]-->Servers

<!--[if !supportLists]-->l <!--[endif]-->DataRelay集群中的服务器,各样集群中服务器之直接纳Replicated Cache方式同步保存数据。

图片 5

图2 DataRelay集群铺排图

在这里构造中,每黄金时代组Cluster中的服务器之间会联手数据,保存相似的数据备份,当Web服务器央浼数据,获取数据服务器节点的算法:

Cluster Index = ObjectID %(# Cluster)

Server Node = Random (Cluster Index)

注:ObjectID 代表存储数据的类型ID,# Cluster 代表二个Group中有稍许个Cluster

当明显了Cluster Index过后,就率性从该Cluster中收取一台可用的节点服务器管理数据诉求。

3.2 缓存测量试验##

  1. 不等SqlSession,查询同一语句,第4回询问之后commit SqlSession:
@Test
public void testCache2() {
      SqlSession sqlSession = sqlSessionFactory.openSession();
      SqlSession sqlSession2 = sqlSessionFactory.openSession();
      try {
          String sql = "org.format.mybatis.cache.UserMapper.getById";
          User user = (User)sqlSession.selectOne(sql, 1);
          log.debug(user);
          // 注意,这里一定要提交。 不提交还是会查询两次数据库
          sqlSession.commit();
          User user2 = (User)sqlSession2.selectOne(sql, 1);
          log.debug(user2);
      } finally {
          sqlSession.close();
          sqlSession2.close();
      }
}

MyBatis仅举行了二回数据库查询:

==> Preparing: select * from USERS WHERE ID = ?
==> Parameters: 1(Integer)
<== Total: 1
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}
  1. 今是昨非SqlSession,查询同一语句,第二回询问现在close SqlSession:
@Test
public void testCache2() {
      SqlSession sqlSession = sqlSessionFactory.openSession();
      SqlSession sqlSession2 = sqlSessionFactory.openSession();
      try {
          String sql = "org.format.mybatis.cache.UserMapper.getById";
          User user = (User)sqlSession.selectOne(sql, 1);
          log.debug(user);
          sqlSession.close();
          User user2 = (User)sqlSession2.selectOne(sql, 1);
          log.debug(user2);
      } finally {
          sqlSession2.close();
      }
}

MyBatis仅举行了叁次数据库查询:

==> Preparing: select * from USERS WHERE ID = ?
==> Parameters: 1(Integer)
<== Total: 1
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}
  1. 现在和过去很区别SqlSesson,查询同一语句。 第一遍查询现在SqlSession不交付:
@Test
public void testCache2() {
      SqlSession sqlSession = sqlSessionFactory.openSession();
      SqlSession sqlSession2 = sqlSessionFactory.openSession();
      try {
          String sql = "org.format.mybatis.cache.UserMapper.getById";
          User user = (User)sqlSession.selectOne(sql, 1);
          log.debug(user);
          User user2 = (User)sqlSession2.selectOne(sql, 1);
          log.debug(user2);
      } finally {
          sqlSession.close();
          sqlSession2.close();
      }
}

MyBatis试行了三回数据库查询:

==> Preparing: select * from USERS WHERE ID = ?
==> Parameters: 1(Integer)
<== Total: 1
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}
==> Preparing: select * from USERS WHERE ID = ?
==> Parameters: 1(Integer)
<== Total: 1
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}

3 DataRelay内部模块组合

DataRelay各样模块之间协和工作,保险了系统的例行运作,各类模块的准备有分别的义务,DataRelay的此中模块组合如图3所示,首要职务如下:

<!--[if !supportLists]-->1) <!--[endif]-->DataRelay.Client:是整套体系提要求客户端应用的接口,客商端通过该接口完结数据操作。

<!--[if !supportLists]-->2) <!--[endif]-->DataRelay.Server:服务端的治本组件,控制伏务的生命周期,甚至扩大组件的热插拔。

图片 6

图3 DataRelay的里边模块布局图

<!--[if !supportLists]-->3) <!--[endif]-->DataRelay.Transports.Socket:管理顾客端和服务端的TCP连接池管理。

<!--[if !supportLists]-->4) <!--[endif]-->DataRelay.Common:首要封装了DataRelay体系中通用操作以至接口定义,首要回顾:

<!--[if !supportLists]-->a) <!--[endif]-->RelayComponent.Interface 定义DataRelay的构件接口标准,扩充组件必须落实该接口。

<!--[if !supportLists]-->b) <!--[endif]-->RelayMessage 定义了服务端和顾客端交互作用的音信类型,是整整体系通讯的功底。

<!--[if !supportLists]-->c) <!--[endif]-->RelayConfiguration Schemas 对系统中的配置文件举行格式验证,保险配置的正确性。

<!--[if !supportLists]-->5) <!--[endif]-->DataRelay.Components:组件模块,富含了基本模块,以致扩张模块

<!--[if !supportLists]-->a) <!--[endif]-->Storage是当真存放Cache的地点,对于贮存的媒介物有多样,选择伯克利DB用来悠久化存款和储蓄数据,也足以为了高品质,接收内部存款和储蓄器保存Cache。这部分使用DataRelay组件设计标准,能够依赖缓存的数据类型以致数据操作方式,扩张合适的囤积组件模块。

<!--[if !supportLists]-->b) <!--[endif]-->Forwarding :网络音信分发组件,该器件模块是全体DataRelay的中央器件,它担任RelayMessage的传递,以致音信的拍卖,它的构成满含以下多少个为主模块:

<!--[if !supportLists]-->l <!--[endif]-->CC奇骏是微软提供的异步编制程序组件,在Forwarding中它担负管理音讯的异步、并发、和睦护医治挫败处理。

<!--[if !supportLists]-->l <!--[endif]-->NodeManager 对DataRelay服务器节点的拘留,Forwarding通过它很好的对节点开展分配以致调用,实现互连网音讯的散发与协同。

<!--[if !supportLists]-->l <!--[endif]-->PerfCounter 质量计数器[10]重要职务是监督服务器各类节点的劳动情形。

<!--[if !supportLists]-->6) <!--[endif]-->DataRelay.Logging:肩负记录DataRelay的日记。

3.3 源码解析##

  1. XMLMappedBuilder(深入分析种种mapper配置文件的深入剖判类,每三个mapper配置都会实例化二个XMLMapperBuilder类)的深入分析方法:
private void configurationElement(XNode context) {
     try {
         String namespace = context.getStringAttribute("namespace");
         if (namespace.equals("")) {
             throw new BuilderException("Mapper's namespace cannot be empty");
         }
         builderAssistant.setCurrentNamespace(namespace);
         cacheRefElement(context.evalNode("cache-ref"));
         cacheElement(context.evalNode("cache"));
         parameterMapElement(context.evalNodes("/mapper/parameterMap"));
         resultMapElements(context.evalNodes("/mapper/resultMap"));
         sqlElement(context.evalNodes("/mapper/sql"));
         buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
     } catch (Exception e) {
         throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
     }
}
  1. 大家见到了分析cache的这段代码:
private void cacheElement(XNode context) throws Exception {
     if (context != null) {
         String type = context.getStringAttribute("type", "PERPETUAL");
         Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
         String eviction = context.getStringAttribute("eviction", "LRU");
         Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
         Long flushInterval = context.getLongAttribute("flushInterval");
         Integer size = context.getIntAttribute("size");
         boolean readWrite = !context.getBooleanAttribute("readOnly", false);
         Properties props = context.getChildrenAsProperties();
         builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, props);
     }
}
  1. 浅析完cache标签之后会使用builderAssistant的userNewCache方法,这里的builderAssistant是一个MapperBuilderAssistant类型的支持类,各类XMLMappedBuilder布局的时候都会实例化那性子情,MapperBuilderAssistant类内部有个Cache类型的currentCache属性,那本性情也等于mapper配置文件中 cache节点所表示的值:
public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      Properties props) {
          typeClass = valueOrDefault(typeClass, PerpetualCache.class);
          evictionClass = valueOrDefault(evictionClass, LruCache.class);
          Cache cache = new CacheBuilder(currentNamespace)
              .implementation(typeClass)
              .addDecorator(evictionClass)
              .clearInterval(flushInterval)
              .size(size)
              .readWrite(readWrite)
              .properties(props)
              .build();
         configuration.addCache(cache);
         currentCache = cache;
         return cache;
}

OK,今后mapper配置文件中的cache节点被拆解剖判到了XMLMapperBuilder实例中的builderAssistant属性中的currentCache值里

  1. 接下去XMLMapperBuilder会解析select节点,剖析select节点的时候利用XMLStatementBuilder举办深入分析(也包罗其余insert,update,delete节点卡塔尔(英语:State of Qatar):
public void parseStatementNode() {
     String id = context.getStringAttribute("id");
     String databaseId = context.getStringAttribute("databaseId");

     if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) return;

     Integer fetchSize = context.getIntAttribute("fetchSize");
     Integer timeout = context.getIntAttribute("timeout");
     String parameterMap = context.getStringAttribute("parameterMap");
     String parameterType = context.getStringAttribute("parameterType");
     Class<?> parameterTypeClass = resolveClass(parameterType);
     String resultMap = context.getStringAttribute("resultMap");
     String resultType = context.getStringAttribute("resultType");
     String lang = context.getStringAttribute("lang");
     LanguageDriver langDriver = getLanguageDriver(lang);

     Class<?> resultTypeClass = resolveClass(resultType);
     String resultSetType = context.getStringAttribute("resultSetType");
     StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
     ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

     String nodeName = context.getNode().getNodeName();
     SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
     boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
     boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
     boolean useCache = context.getBooleanAttribute("useCache", isSelect);
     boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

     // Include Fragments before parsing
     XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
     includeParser.applyIncludes(context.getNode());

     // Parse selectKey after includes and remove them.
     processSelectKeyNodes(id, parameterTypeClass, langDriver);

     // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
     SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
     String resultSets = context.getStringAttribute("resultSets");
     String keyProperty = context.getStringAttribute("keyProperty");
     String keyColumn = context.getStringAttribute("keyColumn");
     KeyGenerator keyGenerator;
     String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
     keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
     if (configuration.hasKeyGenerator(keyStatementId)) {
         keyGenerator = configuration.getKeyGenerator(keyStatementId);
     } else {
         keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
         configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
         ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
     }

     builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
       fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
       resultSetTypeEnum, flushCache, useCache, resultOrdered,
       keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

这段代码前边都以分析一些标签的性情,大家看看了终极风流倜傥行选择builderAssistant增加MappedStatement,其中builderAssistant属性是布局XMLStatementBuilder的时候经过XMLMappedBuilder传入的,大家世襲看builderAssistant的addMappedStatement方法:

图片 7

builderAssistant的addMappedStatement方法

  1. 进入setStatementCache:
private void setStatementCache(
      boolean isSelect,
      boolean flushCache,
      boolean useCache,
      Cache cache,
      MappedStatement.Builder statementBuilder) {
          flushCache = valueOrDefault(flushCache, !isSelect);
          useCache = valueOrDefault(useCache, isSelect);
          statementBuilder.flushCacheRequired(flushCache);
          statementBuilder.useCache(useCache);
          statementBuilder.cache(cache);
}

终极mapper配置文件中的<cache/>棉被服装置到了XMLMapperBuilder的builderAssistant属性中,XMLMapperBuilder中使用XMLStatementBuilder遍历CRUD节点,遍历CRUD节点的时候将以此cache节点设置到那么些CRUD节点中,其生龙活虎cache就是所谓的二级缓存!

  1. 接下去大家回过头来看查询的源码,CachingExecutor的query方法:
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
     throws SQLException {
     Cache cache = ms.getCache();
     if (cache != null) {
         flushCacheIfRequired(ms);
         if (ms.isUseCache() && resultHandler == null) {
             ensureNoOutParams(ms, parameterObject, boundSql);
             @SuppressWarnings("unchecked")
             List<E> list = (List<E>) tcm.getObject(cache, key);
             if (list == null) {
                 list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                 tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks
             }
             return list;
         }
     }
     return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
  1. 进入TransactionalCacheManager的putObject方法:
public void putObject(Cache cache, CacheKey key, Object value) {
     getTransactionalCache(cache).putObject(key, value);
}

private TransactionalCache getTransactionalCache(Cache cache) {
     TransactionalCache txCache = transactionalCaches.get(cache);
     if (txCache == null) {
         txCache = new TransactionalCache(cache);
         transactionalCaches.put(cache, txCache);
     }
     return txCache;
}
  1. TransactionalCache的putObject方法:
public void putObject(Object key, Object object) {
     entriesToRemoveOnCommit.remove(key);
     entriesToAddOnCommit.put(key, new AddEntry(delegate, key, object));
}

作者们看见,数码被投入到了entriesToAddOnCommit中,这么些entriesToAddOnCommit是何等事物吧,它是TransactionalCache的一个Map属性:

private Map<Object, AddEntry> entriesToAddOnCommit;

AddEntry是TransactionalCache内部的二个类:

private static class AddEntry {
     private Cache cache;
     private Object key;
     private Object value;

     public AddEntry(Cache cache, Object key, Object value) {
         this.cache = cache;
         this.key = key;
         this.value = value;
     }

     public void commit() {
         cache.putObject(key, value);
     }
}

好了,今后我们开掘使用二级缓存之后:询问数据的话,先从二级缓存中拿多少,若无的话,去拔尖缓存中拿,一流缓存也从未的话再查询数据库。有了数额今后在丢到TransactionalCache那些指标的entriesToAddOnCommit属性中

接下去我们来验证为啥SqlSession commit或close之后,二级缓存才会生效那几个主题材料。

  1. DefaultSqlSession的commit方法:
public void commit(boolean force) {
     try {
         executor.commit(isCommitOrRollbackRequired(force));
         dirty = false;
     } catch (Exception e) {
         throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + e, e);
     } finally {
         ErrorContext.instance().reset();
     }
}
  1. CachingExecutor的commit方法:
public void commit(boolean required) throws SQLException {
     delegate.commit(required);
     tcm.commit();
     dirty = false;
}
  1. tcm.commit即 TransactionalCacheManager的commit方法:
public void commit() {
     for (TransactionalCache txCache : transactionalCaches.values()) {
         txCache.commit();
     }
}
  1. TransactionalCache的commit方法:
public void commit() {
     delegate.getReadWriteLock().writeLock().lock();
     try {
         if (clearOnCommit) {
             delegate.clear();
         } else {
            for (RemoveEntry entry : entriesToRemoveOnCommit.values()) {
                entry.commit();
            }
         }
         for (AddEntry entry : entriesToAddOnCommit.values()) {
             entry.commit();
         }
         reset();
     } finally {
         delegate.getReadWriteLock().writeLock().unlock();
     }
}
  1. 发觉调用了AddEntry的commit方法:
public void commit() {
      cache.putObject(key, value);
}

发现了! AddEntry的commit方法会把数量丢到cache中,也正是丢到二级缓存中

关于为啥调用close方法后,二级缓存才会收效,因为close方法内部会调用commit方法。本文就不现实说了。 读者有趣味的话看豆蔻梢头看源码就知道怎么了。

4类别化和反系列化

DataRelay为了升高连串化功用,对事情缓存对象举办了自定义连串化和反类别化的完成,自定义的系列化数据布局特别紧密,如图4自定义系列化数据布局图所示,三12位整型(int32)只占用4个字节,布尔型(bool)占用1个字节,三个长短为2的十几个人的整型数组(int16[2])占用总共8个字节,数老板度占4个字节,每位拾伍个人数占用2个字节。能够见见,DataRelay自行编码的体系化数据布局极度的后生可畏体。

图片 8

图4 自定义类别化数据构造

通过对系列化和反连串化的兑现,做了对待测验,在包含大器晚成多级System.Int32品类的数额对象中,使用.NET种类化体系,做连串化生成的字节流是190 KB,若是运用自定义系列化达成,将扭转仅仅14 KB,字节流减弱超越85%,而且系列化的流年减弱14.4s,缩小了字节流就收缩了网络的传输量以至类别化时间减弱,互联网传输品质分明加强。

4 Cache接口#

org.apache.ibatis.cache.Cache是MyBatis的缓存接口,想要实现自定义的缓存须要完成这么些接口。MyBatis中关于Cache接口的落到实处类也应用了装饰者设计方式。我们看下它的风流浪漫对落到实处类:

图片 9

Cache接口辅车相依类图

轻易表明:

LRU – 这几天起码使用的:移除最长日子不被应用的指标。

FIFO – 先进先出:按目的踏向缓存的相继来移除它们。

SOFT – 软引用:移除基于垃圾回笼器状态和软援引准则的靶子。

WEAK – 弱引用:更主动地移除基于垃圾搜集器状态和弱援引法则的对象。

<cache
  eviction="FIFO" <!-- 可以通过cache节点的eviction属性设置,也可以设置其他的属性。-->
  flushInterval="60000"
  size="512"
  readOnly="true"/>

cache-ref节点:mapper配置文件中还足以参预cache-ref节点,它有个属性namespace。假使各类mapper文件都是用cache-ref,且namespace都平等,那么就象征着真正含义上的全局缓存。倘诺只用了cache节点,那仅表示这几个这么些mapper内部的询问被缓存了,其余mapper文件的不起成效,那并不是所谓的全局缓存。

5 DataRelay消息(RelayMessage)

RelayMessage是DataRelay框架的通讯数据基本功,它肩负世襲要求缓存的数量在服务端和顾客端之间互相。RelayMessage设计具备以下特点:

<!--[if !supportLists]-->1) <!--[endif]-->规范音信的类型定义,包含get,update,save,delete 等等,随着框架的扩展,增多扩张的类型。

<!--[if !supportLists]-->2) <!--[endif]-->为了提供传输质量,收缩互连网传输量,音信被系列化成Byte数组寄存到服务端,客商端获取到数量之后要求反种类。

<!--[if !supportLists]-->3) <!--[endif]-->每一个音讯具备唯生机勃勃的ID,假若ID不能够分明唯大器晚成性,还应该有ExtendedID 组合使用。

<!--[if !supportLists]-->4) <!--[endif]-->信息TypeID, 针对每种类型的讯息都将分配三个TypeID,用来牢固缓存的数码地点。

6 DataRelay组件

DataRelay是风流罗曼蒂克套基于组件的系统结构,网络音讯分发是个零件,长久化存款和储蓄是个零部件,内部存款和储蓄器存款和储蓄是个构件,在DataRelay中任何作用的开采都以三个构件,那样能很好的提供了系统的扩张性。

当然对与安排组件本人,还具有很强的自己作主性,各个组件能够定义本人的布置文件,在布置文件中经过反射生成管理作者配备音讯的实例,如DataRelay设计的伯克利DB存款和储蓄组件,由于BerkeleyDB自个儿配置就一定复杂,所以DataRelay在安插该器件时,单独对伯克利DB配置进行田间管理,

在DataRelay组件接口定义中,首倘诺概念了组件要拍卖新闻的接口以致组件本身运维时的音信。特点如下:

<!--[if !supportLists]-->1) <!--[endif]-->服务框架正视新组合件的接口操作RelayMessage。

<!--[if !supportLists]-->2) <!--[endif]-->组件可以自定义的安排文件,服务框架通过反射,获取组件配置音讯。

<!--[if !supportLists]-->3) <!--[endif]-->当组件配置文件变动,服务框架会自行读取重新读取配置信息。

7 DataRelay 组件容器

DataRelay系列是依据组件模块的,对于组件的运维需求一个条件,DataRelay提供组件容器,组件容器的首要职分就是保险组件的生命周期,以致调治音信在组件中传输。该类完成多个接口,分别是IRelayNode和IDataHandler。

<!--[if !supportLists]-->1) <!--[endif]-->IRelayNode:该接口定义容器中组件节点的生命周期,甚至配备音讯,通过该接口,大家得以拿走到容器中相继零器件当前的运营意况,以至相关陈设新闻。

<!--[if !supportLists]-->2) <!--[endif]-->IDataHandler:该接口是传输音讯的接口定义,同样在组件接口定义中也须要集成该接口,该接口定义在任何连串中国国投息的传导。

服务端音信将选用到音讯全体传输到零件容器中,有组件容器进行音讯分发,所以在RelayNode类的安插性上,对大气高并发的新闻,也运用CCEvoque组件管理。

8 DataRelay 网络新闻分发机制

网络新闻分发在DataRelay中是由Forwarding组件模块产生的,Forwarding是DataRelay的叁个基本模块,在服务端和客商端都要动用。它落成DataRelay布满式缓存系统的网络新闻分发与联合。它对音讯分发与一块机制分成二种艺术,生龙活虎种是要实时操作消息,后生可畏种是异步操作音信。对于获得Cache数据,供给实时操作,对于立异、保存、删除Cache数据足以依附作业场景选用异步操作。

DataRelay在系统中得以完结的布满式Cache是Replicated Cache和Distributed Cache相结合的做法:

<!--[if !supportLists]-->1) <!--[endif]-->在对于同贰个组下缓存对象的选项放在有些集群中蕴藏是采取Distributed Cache形式,依照Mod运算定位寄放的集群地点。

<!--[if !supportLists]-->2) <!--[endif]-->对于在同一个组下同八个集群中节点机器上的Cache数据布满采取的是Replicated Cache,就是指在同叁个组中的同一个集群下的每个节点所蕴涵的Cache数据是一模二样的。

Forwarding组件模块管理Cache数据重要分为2个方面,一方面是获取数据,另一个下边是创新数据。如图10表示了在保留和收获的逻辑进度。即使当前DataRelay系统4台服务器节点,分成2个集群,在同风度翩翩App组中,须求管理的缓存业务对象数占领2个,2个数据对象的ID分别是120和121。下边分别证实数据在获取和封存的逻辑进度。

<!--[if !supportLists]-->1) <!--[endif]-->获取对象Id为121的缓存数据:

<!--[if !supportLists]-->a) <!--[endif]-->获取对象Id为121构造描述中装置的组名称:App。

<!--[if !supportLists]-->b) <!--[endif]-->选用Cluster Index:首先根据Cluster Index 算法,总结得出 Cluster Index = 1 ( 121 % 2 = 1 卡塔尔国 mod ( 业务对象ID,集群数量 卡塔尔(英语:State of Qatar)

<!--[if !supportLists]-->c) <!--[endif]-->从该Cluster中随机选取朝气蓬勃台服务节点,获取数据。

<!--[if !supportLists]-->2) <!--[endif]-->保存对象Id为120的缓存数据:

<!--[if !supportLists]-->a) <!--[endif]-->获取对象Id为120构造描述中装置的组名称:App。

<!--[if !supportLists]-->b) <!--[endif]-->选拔Cluster Index: 首先依据Cluster Index算法,计算得出 Cluster Index = 0 ( 120 % 2 卡塔尔(英语:State of Qatar)。

<!--[if !supportLists]-->c) <!--[endif]-->从该Cluster中随机接收风流倜傥台服务器节点,保存数据

<!--[if !supportLists]-->d) <!--[endif]-->该服务器节点会异步发送网络消息,同步该缓存数据到该Cluster的别的节点中。

图片 10

图10 互联网音信分发模型

DataRelay在Forwarding的希图上独具以下几性景况:

<!--[if !supportLists]-->1) <!--[endif]-->结合了Replicated Cache 和Distributed Cache各自特点,很好的拍卖了Cache数据在集群上的数据分布与协同。

<!--[if !supportLists]-->2) <!--[endif]-->CCXC60组件模块的购并,使得网络音讯处理具备高速行,可靠性。

<!--[if !supportLists]-->3) <!--[endif]-->通过安顿能够对新闻批量打包,叁回性交给,减弱互联网通讯。

并支持自定义计算Component组件,首次执行它时从数据库获取的所有数据会被存储在一段高速缓存中。9 DataRelay 服务配置

在劳动配置那块,datarelay也可能有特有之处,利用了.net appdomain那本本性,做到可热插拔,在统筹那部分功效时,接收DataRelay系统框架和构件模块使用不一样的AppDomain来加载,将要一个单独的 AppDomain将具备组件模块程序集加载到零器件容器中,那样当增加恐怕更新组件dll和布局文件时,DataRelay将得以动态卸载 AppDomain,过后在从新创设新的 AppDomain,然后将眼下组件加载到里面。

如此那般就足以毫不从新开发银行DataRelay服务处理组件更新,在线上运转还是不行有协助的,杜撰一下譬喻有几十台Relay服务器须求更新组件,那样安插很有益很迅猛。

《程序员2012.11期》 作者:张庆化

原文:http://www.tita.com/blog/tech/myspace-datarelay-分布式数据缓存源码解析