上个月扫尾了2016欠下的知识债,2017要有个新的计划,毕竟暂时没了别人的带领,工作也不是很忙,不能停止学习呀,有什么总结感慨还是留到年前的总结去写吧。
之前在做MySQL Cluster读写分离的时候接触到了Mycat这个强大的数据库中间件,我记得当时还是用的1.4的稳定版,1.5还没有发布release版本,现在看Github上面正在开发2.0版本,并且已经成为Apache中的一份子,感觉mycat的前景还是很强的。
Mycat主要解决的问题是数据库的拆分,不管是在集群层次的读写分离,还是在数据库层面的表分片,Mycat将本来需要我们在程序中手写代码实现的功能单独抽取了出来,这样我们在需要快速开发框架的阶段或者在数据量突然膨胀的情况下有个稳定、易上手的解决方案。
Mycat的简单使用我在之前的博客中已经写过一次了,不过分片这方面还没有写过,后面会慢慢补完。当然有多慢就没准了~
Mycat在使用中充当的是一个数据库和应用程序中间层的角色,我的理解就是数据库中间件。Mycat在启动之后对程序端来说模拟了一个MySQL5.6的逻辑数据库,应用程序使用Mycat配置的用户名密码控制对逻辑数据库的访问。Mycat在接收到应用程序发来的SQL请求之后会进行拦截,并且解析SQL语句。之后会按照配置的分片规则进行对真正的数据库的访问,数据库将数据返回之后,Mycat会对返回的结果进行拼接,最后返回给应用程序。
大概就是这个样子了,Mycat在接收应用程序请求的时候,默认采用了NIO的方式,或者也可以配置成AIO,然而这一块并不是我的强项,就不胡说八道了。
可以在下面的启动代码中看到。代码本身带了一些英文注释,为了方便阅读和自己的理解,我就写了一些注释,当然有可能理解的不到位注释的会有问题。
public final class MycatStartup {
private static final String dateFormat = "yyyy-MM-dd HH:mm:ss";
private static final Logger LOGGER = LoggerFactory.getLogger(MycatStartup.class);
public static void main(String[] args) {
//初始化zk
ZkConfig.getInstance().initZk();
try {
String home = SystemConfig.getHomePath();
if (home == null) {
System.out.println(SystemConfig.SYS_HOME + " is not set.");
System.exit(-1);
}
// 实例化化MycatServer
MycatServer server = MycatServer.getInstance();
// 启动前的工作
server.beforeStart();
// 启动mycat服务
server.startup();
System.out.println("MyCAT Server startup successfully. see logs in logs/mycat.log");
} catch (Exception e) {
SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
LOGGER.error(sdf.format(new Date()) + " startup error", e);
System.exit(-1);
}
}
}
这里可以看到,Mycat截止到1.6版本已经结合zookeeper解决了本身的单点问题,预计未来2.0的版本大更新将会和zookeeper结合的更加紧密。
首先开始读取系统的配置了,这一步主要也是读取的MyCat所在路径,毕竟之后这个路径会频繁的用到。
获取路径 public static String getHomePath() {
String home = System.getProperty(SystemConfig.SYS_HOME);
if (home != null
&& home.endsWith(File.pathSeparator)) {
home = home.substring(0, home.length() - 1);
System.setProperty(SystemConfig.SYS_HOME, home);
}
// MYCAT_HOME为空,默认尝试设置为当前目录或上级目录。BEN
if (home == null) {
try {
String path = new File("..").getCanonicalPath().replaceAll("\\\\", "/");
File conf = new File(path + "/conf");
if (conf.exists() && conf.isDirectory()) {
home = path;
} else {
path = new File(".").getCanonicalPath().replaceAll("\\\\", "/");
conf = new File(path + "/conf");
if (conf.exists() && conf.isDirectory()) {
home = path;
}
}
if (home != null) {
System.setProperty(SystemConfig.SYS_HOME, home);
}
} catch (IOException e) {
// 如出错,则忽略。
}
}
return home;
}
在读取mycat目录之后,会执行一个启动前的方法,这个方法我看到的是读取zk的配置,连接zk等操作,目前在github上的master分支上已经把这部分代码提前了,大概写到了,上面启动代码里面,就没有这个beforeStart方法了。
在MycatServer类的构造方法中会读取用户的配置文件,Schema.xml rule.xml和user.xml,之后初始化两个线程池,初始化了路由解析器,SQL记录器,SQL解析器等等,这里的注释挺全的,看看就能看懂。
private MycatServer() {
//读取文件配置
this.config = new MycatConfig();
//定时线程池,单线程线程池
scheduler = Executors.newSingleThreadScheduledExecutor();
//心跳调度独立出来,避免被其他任务影响
heartbeatScheduler = Executors.newSingleThreadScheduledExecutor();
//SQL记录器
this.sqlRecorder = new SQLRecorder(config.getSystem().getSqlRecordCount());
/**
* 是否在线,MyCat manager中有命令控制
* | offline | Change MyCat status to OFF |
* | online | Change MyCat status to ON |
*/
this.isOnline = new AtomicBoolean(true);
//缓存服务初始化
cacheService = new CacheService();
//路由计算初始化
routerService = new RouteService(cacheService);
// load datanode active index from properties
dnIndexProperties = loadDnIndexProps();
try {
//SQL解析器
sqlInterceptor = (SQLInterceptor) Class.forName(
config.getSystem().getSqlInterceptor()).newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
//catlet加载器
catletClassLoader = new DynaClassLoader(SystemConfig.getHomePath()
+ File.separator + "catlet", config.getSystem().getCatletClassCheckSeconds());
//记录启动时间
this.startupTime = TimeUtil.currentTimeMillis();
if(isUseZkSwitch()) {
String path= ZKUtils.getZKBasePath()+"lock/dnindex.lock";
dnindexLock = new InterProcessMutex(ZKUtils.getConnection(), path);
}
}
start方法比较复杂,主要流程也是启动Mycat的连接工厂和管理工厂,并且可以处理NIO连接。设定定时对物理节点的心跳检测,初始化XA若事务日志,初始化路由规则,初始化分片规则。
public void startup() throws IOException {
SystemConfig system = config.getSystem();
int processorCount = system.getProcessors();
// 写启动日志,主要是一些配置信息
LOGGER.info(NAME + " is ready to startup ...");
String inf = "Startup processors ...,total processors:"
+ system.getProcessors() + ",aio thread pool size:"
+ system.getProcessorExecutor()
+ " \r\n each process allocated socket buffer pool "
+ " bytes ,a page size:"
+ system.getBufferPoolPageSize()
+ " a page's chunk number(PageSize/ChunkSize) is:"
+ (system.getBufferPoolPageSize()
/system.getBufferPoolChunkSize())
+ " buffer page's number is:"
+ system.getBufferPoolPageNumber();
LOGGER.info(inf);
LOGGER.info("sysconfig params:" + system.toString());
// 启动连接工厂
ManagerConnectionFactory mf = new ManagerConnectionFactory();
ServerConnectionFactory sf = new ServerConnectionFactory();
SocketAcceptor manager = null;
SocketAcceptor server = null;
aio = (system.getUsingAIO() == 1);
// 启动处理连接类
int threadPoolSize = system.getProcessorExecutor();
processors = new NIOProcessor[processorCount];
// 缓冲区pagesize
int bufferPoolPageSize = system.getBufferPoolPageSize();
// 缓冲区page数量
short bufferPoolPageNumber = system.getBufferPoolPageNumber();
// 最小分配单元
short bufferPoolChunkSize = system.getBufferPoolChunkSize();
// 本地缓冲池百分比
int socketBufferLocalPercent = system.getProcessorBufferLocalPercent();
// 缓冲池类型
int bufferPoolType = system.getProcessorBufferPoolType();
//根据不同的类型初始化不同的缓冲区
switch (bufferPoolType){
case 0:
bufferPool = new DirectByteBufferPool(bufferPoolPageSize,bufferPoolChunkSize,
bufferPoolPageNumber,system.getFrontSocketSoRcvbuf());
totalNetWorkBufferSize = bufferPoolPageSize*bufferPoolPageNumber;
break;
case 1:
/**
* todo 对应权威指南修改:
*
* bytebufferarena由6个bytebufferlist组成,这六个list有减少内存碎片的机制
* 每个bytebufferlist由多个bytebufferchunk组成,每个list也有减少内存碎片的机制
* 每个bytebufferchunk由多个page组成,平衡二叉树管理内存使用状态,计算灵活
* 设置的pagesize对应bytebufferarena里面的每个bytebufferlist的每个bytebufferchunk的buffer长度
* bufferPoolChunkSize对应每个bytebufferchunk的每个page的长度
* bufferPoolPageNumber对应每个bytebufferlist有多少个bytebufferchunk
*/
totalNetWorkBufferSize = 6*bufferPoolPageSize * bufferPoolPageNumber;
break;
default:
bufferPool = new DirectByteBufferPool(bufferPoolPageSize,bufferPoolChunkSize,
bufferPoolPageNumber,system.getFrontSocketSoRcvbuf());;
totalNetWorkBufferSize = bufferPoolPageSize*bufferPoolPageNumber;
}
/**
* Off Heap For Merge/Order/Group/Limit 初始化
*/
if(system.getUseOffHeapForMerge() == 1){
try {
myCatMemory = new MyCatMemory(system,totalNetWorkBufferSize);
} catch (NoSuchFieldException e) {
LOGGER .error("NoSuchFieldException",e);
} catch (IllegalAccessException e) {
LOGGER.error("Error",e);
}
}
// 创建线程池
businessExecutor = ExecutorUtil.create("BusinessExecutor",
threadPoolSize);
timerExecutor = ExecutorUtil.create("Timer", system.getTimerExecutor());
listeningExecutorService = MoreExecutors.listeningDecorator(businessExecutor);
// 给每个NIOprocessor分配线程池
for (int i = 0; i < processors.length; i++) {
processors[i] = new NIOProcessor("Processor" + i, bufferPool,
businessExecutor);
}
// 如果使用AIO方式,初始化AIO连接类,这个可以在配置文件中配置
if (aio) {
LOGGER.info("using aio network handler ");
asyncChannelGroups = new AsynchronousChannelGroup[processorCount];
// startup connector
connector = new AIOConnector();
for (int i = 0; i < processors.length; i++) {
asyncChannelGroups[i] = AsynchronousChannelGroup.withFixedThreadPool(processorCount,
new ThreadFactory() {
private int inx = 1;
@Override
public Thread newThread(Runnable r) {
Thread th = new Thread(r);
//TODO
th.setName(DirectByteBufferPool.LOCAL_BUF_THREAD_PREX + "AIO" + (inx++));
LOGGER.info("created new AIO thread "+ th.getName());
return th;
}
}
);
}
manager = new AIOAcceptor(NAME + "Manager", system.getBindIp(),
system.getManagerPort(), mf, this.asyncChannelGroups[0]);
// startup server
server = new AIOAcceptor(NAME + "Server", system.getBindIp(),
system.getServerPort(), sf, this.asyncChannelGroups[0]);
} else {
// 默认使用NIO连接
LOGGER.info("using nio network handler ");
NIOReactorPool reactorPool = new NIOReactorPool(
DirectByteBufferPool.LOCAL_BUF_THREAD_PREX + "NIOREACTOR",
processors.length);
// 初始化NIO连接
connector = new NIOConnector(DirectByteBufferPool.LOCAL_BUF_THREAD_PREX + "NIOConnector", reactorPool);
((NIOConnector) connector).start();
// NIO请求接收器
manager = new NIOAcceptor(DirectByteBufferPool.LOCAL_BUF_THREAD_PREX + NAME
+ "Manager", system.getBindIp(), system.getManagerPort(), mf, reactorPool);
// 另一个NIO请求接收,一个负责接收前端程序发过来的请求,一个负责向后端数据库发送请求
server = new NIOAcceptor(DirectByteBufferPool.LOCAL_BUF_THREAD_PREX + NAME
+ "Server", system.getBindIp(), system.getServerPort(), sf, reactorPool);
}
// 启动NIOAcceptor
manager.start();
LOGGER.info(manager.getName() + " is started and listening on " + manager.getPort());
server.start();
LOGGER.info(server.getName() + " is started and listening on " + server.getPort());
LOGGER.info("===============================================");
// 初始化datahost,datahost在schema.xml中配置
Map<String, PhysicalDBPool> dataHosts = config.getDataHosts();
LOGGER.info("Initialize dataHost ...");
// 对读取的物理节点开始心跳检测
for (PhysicalDBPool node : dataHosts.values()) {
String index = dnIndexProperties.getProperty(node.getHostName(),"0");
if (!"0".equals(index)) {
LOGGER.info("init datahost: " + node.getHostName() + " to use datasource index:" + index);
}
node.init(Integer.parseInt(index));
node.startHeartbeat();
}
long dataNodeIldeCheckPeriod = system.getDataNodeIdleCheckPeriod();
// 开始心跳检测定时任务
heartbeatScheduler.scheduleAtFixedRate(updateTime(), 0L, TIME_UPDATE_PERIOD,TimeUnit.MILLISECONDS);
heartbeatScheduler.scheduleAtFixedRate(processorCheck(), 0L, system.getProcessorCheckPeriod(),TimeUnit.MILLISECONDS);
heartbeatScheduler.scheduleAtFixedRate(dataNodeConHeartBeatCheck(dataNodeIldeCheckPeriod), 0L, dataNodeIldeCheckPeriod,TimeUnit.MILLISECONDS);
heartbeatScheduler.scheduleAtFixedRate(dataNodeHeartbeat(), 0L, system.getDataNodeHeartbeatPeriod(),TimeUnit.MILLISECONDS);
heartbeatScheduler.scheduleAtFixedRate(dataSourceOldConsClear(), 0L, DEFAULT_OLD_CONNECTION_CLEAR_PERIOD, TimeUnit.MILLISECONDS);
scheduler.schedule(catletClassClear(), 30000,TimeUnit.MILLISECONDS);
if(system.getCheckTableConsistency()==1) {
scheduler.scheduleAtFixedRate(tableStructureCheck(), 0L, system.getCheckTableConsistencyPeriod(), TimeUnit.MILLISECONDS);
}
if(system.getUseSqlStat()==1) {
scheduler.scheduleAtFixedRate(recycleSqlStat(), 0L, DEFAULT_SQL_STAT_RECYCLE_PERIOD, TimeUnit.MILLISECONDS);
}
if(system.getUseGlobleTableCheck() == 1){ // 全局表一致性检测是否开启
scheduler.scheduleAtFixedRate(glableTableConsistencyCheck(), 0L, system.getGlableTableCheckPeriod(), TimeUnit.MILLISECONDS);
}
//定期清理结果集排行榜,控制拒绝策略
scheduler.scheduleAtFixedRate(resultSetMapClear(),0L, system.getClearBigSqLResultSetMapMs(),TimeUnit.MILLISECONDS);
// 初始化路由工厂
RouteStrategyFactory.init();
// new Thread(tableStructureCheck()).start();
//XA Init recovery Log
LOGGER.info("===============================================");
LOGGER.info("Perform XA recovery log ...");
// 开启XA日志
performXARecoveryLog();
if(isUseZkSwitch()) {
//首次启动如果发现zk上dnindex为空,则将本地初始化上zk
initZkDnindex();
}
// 初始化分片规则
initRuleData();
startup.set(true);
}
到这里就是启动完成了,基本上没有什么复杂的方法,后面初始化这几个方法下片再写吧,因为我也是一边看一边总结的,并没有什么进度可言,希望能坚持到,分片,解析,路由,处理连接这种核心代码吧,毕竟做sharding这些都是需要用到的。
(比如写完核心的博客买个GTX1080 TI怎么样~)