canal怎样监听多个数据库

前言

  1. 前一些天已经发了一个版本,但只监听一个业务系统的数据库。今天领导找我谈,说能不能多监听几个数据库,对接更多的业务系统。下面我们就围绕这个主题进行学习展开
  2. 稍微跟了一下源码,最终发现在canal.destinations配置中配置多个实例(用逗号分隔),然后在对应配置名目录下增加对应Instance.properties
    canal怎样监听多个数据库_第1张图片

canal整体架构分析

canal怎样监听多个数据库_第2张图片

  1. 看上面的架构图,感觉每监听一个数据库就对应一个Instance

  2. 通过分析下方的生成Instance过程分析总结:
    2.1 根据canal.properties初始化instance全局配置
    (1)关于实例全局配置项:canal.instance.global.mode、canal.instance.global.spring.xml
    2.2 读取canal.destinations配置,如果有多个实例用逗号分隔,遍历初始化instance配置
    (1)如果想覆盖全局配置,则配置指定实例的配置:canal.instance.<实例名>.mode、canal.instance.<实例名>.spring.xml
    2.3 通过SpringCanalInstanceGenerator#generate生成实例

  3. 下方Instance启动分析总结:
    3.1 一个instance对应一个spring容器,spring配置文件为:spring/file-instance.xml
    3.2 instance启动入口为CanalInstanceWithSpring,启动实例的各个组件:metaManager、alarmHandler、eventStore、eventSink、eventParser(各个组件运行分析待分析)

生成Instance过程分析

  1. 初始化全局instance配置(入口:CanalController#initGlobalConfig)

    private InstanceConfig initGlobalConfig(Properties properties) {
    	InstanceConfig globalConfig = new InstanceConfig();
    	// 读取canal.instance.global.mode配置 -> spring
    	String modeStr = getProperty(properties, CanalConstants.getInstanceModeKey(CanalConstants.GLOBAL_NAME));
    	if (StringUtils.isNotEmpty(modeStr)) {
            globalConfig.setMode(InstanceMode.valueOf(StringUtils.upperCase(modeStr)));
        }
        // 读取canal.instance.global.spring.xml配置 -> classpath:spring/file-instance.xml
        String springXml = getProperty(properties, CanalConstants.getInstancSpringXmlKey(CanalConstants.GLOBAL_NAME));
        if (StringUtils.isNotEmpty(springXml)) {
            globalConfig.setSpringXml(springXml);
        }
    }
    
  2. 在启动server时,会初始化instance配置(入口:CanalController#initInstanceConfig)

    private void initInstanceConfig(Properties properties) {
    	// 从canal.properties中读取canal.destinations配置
    	String destinationStr = getProperty(properties, CanalConstants.CANAL_DESTINATIONS);
    	// 多个实例instance用逗号分隔
    	String[] destinations = StringUtils.split(destinationStr, CanalConstants.CANAL_DESTINATION_SPLIT);
    	for (String destination : destinations) {
              InstanceConfig config = parseInstanceConfig(properties, destination);
              InstanceConfig oldConfig = instanceConfigs.put(destination, config);
              if (oldConfig != null) {
                  logger.warn("destination:{} old config:{} has replace by new config:{}", destination, oldConfig, config);
              }
         }
    }
    

    1.1 解析实例配置:CanalController#parseInstanceConfig

    private InstanceConfig parseInstanceConfig(Properties properties, String destination) {
    	InstanceConfig config = new InstanceConfig(globalInstanceConfig);
    	// 实例配置mode的key:canal.instance.express(destination名).mode
    	String modeStr = getProperty(properties, CanalConstants.getInstanceModeKey(destination));
    	if (StringUtils.isNotEmpty(modeStr)) { // 如果存在,则覆盖全局实例配置
    		config.setMode(InstanceMode.valueOf(StringUtils.upperCase(modeStr)));
    	}
    	// 读取实例的xml文件:canal.instance.express.spring.xml
    	String springXml = getProperty(properties, CanalConstants.getInstancSpringXmlKey(destination));
    	if (StringUtils.isNotEmpty(springXml)) {
            config.setSpringXml(springXml);
        }
        return config;
    }
    
  3. 根据配置生成instance入口:CanalController#initGlobalConfig

    private InstanceConfig initGlobalConfig(Properties properties) {
    	instanceGenerator = destination -> {
    		InstanceConfig config = instanceConfigs.get(destination);
    		if (config.getMode().isSpring()) {
                SpringCanalInstanceGenerator instanceGenerator = new SpringCanalInstanceGenerator();
                instanceGenerator.setSpringXml(config.getSpringXml());
                return instanceGenerator.generate(destination);
            } 
    	}
    }
    

Instance启动分析

  1. 生成Instance入口:SpringCanalInstanceGenerator#generate
    1.1 观察下面代码发现,一个instance对应一个spring容器
    1.2 instance对应的实现类为CanalInstanceWithSpring
    public CanalInstance generate(String destination) {
    	synchronized (CanalEventParser.class) {
    		// 加载classpath:spring/file-instance.xml的spring容器
    		this.beanFactory = getBeanFactory(springXml);
    		 String beanName = destination;
    		 // 在file-instance.xml没有对应实例名的bean,只有默认的instance的beanName
    		 if (!beanFactory.containsBean(beanName)) {
                    beanName = defaultName;
              }
              return (CanalInstance) beanFactory.getBean(beanName);
    	}
    }
    
  2. 启动instance入口:CanalInstanceWithSpring#start
    2.1 启动各个组件
    public void start() {
        super.start();
        if (!metaManager.isStart()) {
            metaManager.start();
        }
    
        if (!alarmHandler.isStart()) {
            alarmHandler.start();
        }
    
        if (!eventStore.isStart()) {
            eventStore.start();
        }
    
        if (!eventSink.isStart()) {
            eventSink.start();
        }
    
        if (!eventParser.isStart()) {
            beforeStartEventParser(eventParser);
            eventParser.start();
            afterStartEventParser(eventParser);
        }
        logger.info("start successful....");
    }
    

你可能感兴趣的:(canal,java)