k8s集群下canal-adapter连接canal-server实践

k8s集群下adapter连接server域名问题改造

  • 前言
  • 问题
  • 解析
  • 解决方案

前言

成也容器重启,败也容器重启,说好的重启治百病,在容器这里,是重启出百病啊!

之前说过,我们使用statefuset类型使canal-server域名固定之后又挂载pv使server即使重启也不会丢失数据,本以为万事大吉,没想到在用adapter同步数据时发现还是出了bug…

问题

使用域名注册server之后,马上启了个adapter去检测一下域名方式是否可用,将canalServerHost的值设置成域名之后启动,数据成功同步,内心狂喜,感觉自己已经彻底成功了,但还是本着严谨的态度,手动delete掉canal-server的pod打算做一下测试,sever停止,adapter马上抛出异常,然后尝试重连,嗯,是我预想的现象,但是…五分钟过去了,server的pod早就重启了,为什么adapter还在尝试重连?

哪里的问题呢?第一次使用域名连接同步正常,表示域名方式的连接是没有问题的,但是重启之后就连接不上了,心里觉得和adapter与server的连接机制有关系,没办法,去读代码吧。

解析

注:本人在使用canal-adapter-v1.1.4版本的时候遇到过数据丢失的情况,没有查出原因,最终选择了1.1.5-alpha-1版本

  • com.alibaba.otter.canal.adapter.launcher.loader.CanalAdapterLoader.java
    /**
     * 初始化canal-client
     */
    public void init() {
        loader = ExtensionLoader.getExtensionLoader(OuterAdapter.class);

        String canalServerHost = this.canalClientConfig.getCanalServerHost();
        SocketAddress sa = null;
        if (canalServerHost != null) {
            String[] ipPort = canalServerHost.split(":");
            sa = new InetSocketAddress(ipPort[0], Integer.parseInt(ipPort[1]));
        }
        String zkHosts = this.canalClientConfig.getZookeeperHosts();

        if ("tcp".equalsIgnoreCase(canalClientConfig.getMode())) {
            // 初始化canal-client的适配器
            for (CanalClientConfig.CanalAdapter canalAdapter : canalClientConfig.getCanalAdapters()) {
                List> canalOuterAdapterGroups = new CopyOnWriteArrayList<>();

                for (CanalClientConfig.Group connectorGroup : canalAdapter.getGroups()) {
                    List canalOutConnectors = new CopyOnWriteArrayList<>();
                    for (OuterAdapterConfig c : connectorGroup.getOuterAdapters()) {
                        loadAdapter(c, canalOutConnectors);
                    }
                    canalOuterAdapterGroups.add(canalOutConnectors);
                }
                CanalAdapterWorker worker;
                if (sa != null) {
                    worker = new CanalAdapterWorker(canalClientConfig,
                        canalAdapter.getInstance(),
                        sa,
                        canalOuterAdapterGroups);
                } else if (zkHosts != null) {
                    worker = new CanalAdapterWorker(canalClientConfig,
                        canalAdapter.getInstance(),
                        zkHosts,
                        canalOuterAdapterGroups);
                } else {
                    throw new RuntimeException("No canal server connector found");
                }
                canalWorkers.put(canalAdapter.getInstance(), worker);
                worker.start();
                logger.info("Start adapter for canal instance: {} succeed", canalAdapter.getInstance());
            }
        }

从client的初始化过程中,我们发现,CanalAdapterWorker对象传入的是SocketAddress对象,这个对象就是构建的我们要连接的canal-server地址

 public InetSocketAddress(String hostname, int port) {
        checkHost(hostname);
        InetAddress addr = null;
        String host = null;
        try {
            addr = InetAddress.getByName(hostname);
        } catch(UnknownHostException e) {
            host = hostname;
        }
        holder = new InetSocketAddressHolder(host, addr, checkPort(port));
    }
    
    
    public static InetAddress getByName(String host)
        throws UnknownHostException {
        return InetAddress.getAllByName(host)[0];
    }

而从InetSocketAddress的构建过程来看,getByName方法获取的是我们域名后面对应的ip地址,看到这里,终于明白为什么在容器重启之后,重连一直失败了,传入CanalAdapterWorker对象的sa对象绑定的一直是初次建立连接时server的ip地址,容器重启之后,ip地址变化,canaladapterWorker还是在重连之前已经错误的ip,这当然有问题了!

解决方案

知道了问题所在,一切就好解决了,只需要将sa对象构建地址的位置放在连接时就可以了,所以我们直接给CanalAdapterWorker创建一个新的构造方法,传入携带sa对象连接信息的canalServerHost参数即可。

  • com.alibaba.otter.canal.adapter.launcher.loader.CanalAdapterWorker.java,增加新的构造方法
    /**
     * worker针对域名类server重连机制修改构造方法
     * @param canalClientConfig
     * @param canalDestination
     * @param address
     * @param hoststr ip:port
     * @param canalOuterAdapters
     */
    public CanalAdapterWorker(CanalClientConfig canalClientConfig, String canalDestination, SocketAddress address,String hoststr,
            List> canalOuterAdapters){
		super(canalOuterAdapters);
		this.canalClientConfig = canalClientConfig;
		this.canalDestination = canalDestination;
		connector = CanalConnectors.newSingleConnector(address,hoststr, canalDestination, "", "");
    }

其他需要添加相应的方法的地方不再赘述,编辑器会告诉你的,哪里报错加哪里!

  • com.alibaba.otter.canal.client.impl.SimpleCanalConnector.java
private InetSocketAddress doConnect() throws CanalClientException {
        try {
            channel = SocketChannel.open();
            channel.socket().setSoTimeout(soTimeout);
            SocketAddress address = getAddress();
            if (address == null) {
                address = getNextAddress();
            }
            channel.connect(address);
            readableChannel = Channels.newChannel(channel.socket().getInputStream());
            writableChannel = Channels.newChannel(channel.socket().getOutputStream());

通过源码,我们发现真正连接server是在SimpleCanalConnector的doConnect方法内,将其改造成如下

private InetSocketAddress doConnect() throws CanalClientException {
        try {
            channel = SocketChannel.open();
            channel.socket().setSoTimeout(soTimeout);
            
            if (null != hostPort && !"".equals(hostPort)) {
            	String[] ipPort = hostPort.split(":");
                address = new InetSocketAddress(ipPort[0], Integer.parseInt(ipPort[1]));
                channel.connect(address);
			}else {
				SocketAddress address = getAddress();
	            if (address == null) {
	                address = getNextAddress();
	            }
	            channel.connect(address);
			}
            
            readableChannel = Channels.newChannel(channel.socket().getInputStream());
            writableChannel = Channels.newChannel(channel.socket().getOutputStream());
  • com.alibaba.otter.canal.adapter.launcher.loader.CanalAdapterLoader.java
if (sa != null) {
                    worker = new CanalAdapterWorker(canalClientConfig,
                        canalAdapter.getInstance(),
                        sa,
                        canalServerHost,
                        canalOuterAdapterGroups);
                }

最后别忘了将init方法内的CanalAdapterWorker替换成我们新增的构造方法

到这里,canal-adapter在k8s环境下使用域名方式连接canal-server的问题就顺利解决了,只需要将工程重新打包,然后按照之前构建canal-adapter镜像的方式重新构建一下镜像就大功告成了!

欢迎关注我的个人微信公众号,一个菜鸟程序猿的技术分享和奔溃日常

一个菜鸟程序猿的技术技术分享和奔溃日常

你可能感兴趣的:(k8s,数据同步,linux,docker,kubernetes,数据库,java)