Huawei Cloud Yaoyun Server L 인스턴스 평가 │ 운하 이미지 구성 관련 매개 변수 가져 오기 및 생성 및 MySQL 데이터베이스에 대한 운하 연결 구축 및 스프링 프로젝트 응용 프로그램 운하 예비

여기에 이미지 설명을 삽입하세요.

머리말

최근 Huawei Cloud Yaoyun Server L 인스턴스가 출시되었는데, 저도 하나 만들어 봤습니다. 이번 블로그에서는 Huawei Cloud에 canal의 도커 이미지를 배포하는 방법과 봄 프로젝트에서의 예비 적용 방법을 소개합니다.

기타 관련 Huawei Cloud Yaoyun Server L 인스턴스 평가 기사 목록은 다음과 같습니다.

여기에 이미지 설명을 삽입하세요.

밖으로 이끌어내다


1.Canal: MySQL 데이터베이스 증분 로그 분석을 기반으로 증분 데이터 구독 및 소비 제공
2.Canal 사용, MySQL 구성, docker Canal 설치
3. 클라우드 서버 오픈 포트, 서버 포트가 열려 있는지 테스트하는 방법
4 .봄철 운하의 예비 적용;

1. 운하 파이프라인 이해

1.운하란 무엇인가?

https://github.com/alibaba/canal

https://github.com/alibaba/canal/wiki/ClientExample

Canal은 Mysql binlog를 기반으로 하는 Alibaba 오픈 소스 증분 구독 및 소비 구성 요소입니다. 이를 통해 데이터베이스의 binlog 로그를 구독한 후 데이터 미러링, 데이터 이질성, 데이터 인덱싱, 캐시 업데이트 등 일부 데이터 소비를 수행할 수 있습니다. 등. 메시지 대기열과 비교하여 이 메커니즘은 데이터 순서와 일관성을 달성할 수 있습니다.

여기에 이미지 설명을 삽입하세요.

canal [kə'næl] , 수로/파이프라인/도랑으로 번역되며, 주요 목적은 MySQL 데이터베이스 증분 로그 분석을 기반으로 증분 데이터 구독 및 소비를 제공하는 것입니다.

알리바바 초기에는 항저우와 미국에 이중 전산실을 배치함으로써 전산실 간의 동기화에 대한 비즈니스 필요성이 있었고 구현 방법은 주로 비즈니스 트리거를 기반으로 점진적인 변경 사항을 얻는 것이 었습니다. 2010년부터 기업에서는 동기화를 위한 증분 변경 사항을 얻기 위해 점차적으로 데이터베이스 로그를 구문 분석하려고 시도했으며, 이로 인해 많은 수의 데이터베이스 증분 구독 및 소비 기업이 탄생했습니다.

로그 증분 구독 및 소비를 기반으로 하는 비즈니스에는 다음이 포함됩니다.

  • 데이터베이스 미러링
  • 데이터베이스 실시간 백업
  • 지수 구성 및 실시간 유지관리(분할 이종지수, 역지수 등)
  • 비즈니스 캐시 새로 고침
  • 비즈니스 로직을 사용한 증분 데이터 처리

현재 운하에서는 5.1.x, 5.5.x, 5.6.x, 5.7.x, 8.0.x를 포함한 소스 MySQL 버전을 지원합니다.

2.운하의 예비 원리

여기에 이미지 설명을 삽입하세요.
MySQL 마스터-슬레이브의 원리

  • MySQL 마스터는 데이터 변경 사항을 바이너리 로그(바이너리 로그, 여기서 레코드는 바이너리 로그 이벤트라고 하며 show binlog 이벤트를 통해 볼 수 있음)
  • MySQL 슬레이브는 마스터의 바이너리 로그 이벤트를 릴레이 로그(릴레이 로그)에 복사합니다.
  • MySQL 슬레이브는 릴레이 로그의 이벤트를 재생하고 데이터 변경 사항을 자체 데이터에 반영합니다.

운하의 원리---> 노예로 변장

  • canal은 MySQL 슬레이브의 상호 작용 프로토콜을 시뮬레이션하고, 자신을 MySQL 슬레이브로 위장하고, 덤프 프로토콜을 MySQL 마스터로 보냅니다.
  • MySQL 마스터는 덤프 요청을 수신하고 바이너리 로그를 슬레이브(예: 운하)로 푸시하기 시작합니다.
  • canal은 바이너리 로그 객체(원래는 바이트 스트림)를 구문 분석합니다.

운하의 적용 전망

  • canal은 MySQL에서 데이터를 가져온 다음 이를 redis와 동기화합니다. MySQL과 Redis 간의 데이터 일관성을 보장하고 지연된 이중 삭제로 인해 발생하는 다른 문제를 방지하기 위해 지연된 이중 삭제를 수행할 필요가 없습니다.

2. MySQL 컨테이너가 운하 사용자를 생성합니다.

docker exec -it mysql_3306 bash
mysql -uroot -p    
show VARIABLES like 'log_%';

여기에 이미지 설명을 삽입하세요.

create user 'canal'@'%' IDENTIFIED with mysql_native_password by 'canal';
grant SELECT, REPLICATION SLAVE, REPLICATION CLIENT on *.* to 'canal'@'%';
FLUSH PRIVILEGES;

여기에 이미지 설명을 삽입하세요.

3. 근관 이미지를 당겨서 컨테이너 생성

1. 근관 이미지를 쿼리하고 가져옵니다.

docker pull canal/canal-server

여기에 이미지 설명을 삽입하세요.

root@hcss-ecs-52b8:~# docker pull canal/canal-server
Using default tag: latest
latest: Pulling from canal/canal-server
1c8f9aa56c90: Pull complete 
c5e21c824d1c: Pull complete 
4ba7edb60123: Pull complete 
80d8e8fac1be: Pull complete 
705a43657e98: Pull complete 
28e38bfb6fe7: Pull complete 
7d51a00deff6: Pull complete 
4f4fb700ef54: Pull complete 
Digest: sha256:0d1018759efd92ad331c7cc379afa766c8d943ef48ef8d208ade646f54bf1565
Status: Downloaded newer image for canal/canal-server:latest
docker.io/canal/canal-server:latest

2. 운하 컨테이너를 실행하고 구성 파일을 얻습니다.

후속 장착 및 시작 준비

docker run --name canal -itd canal/canal-server

도커 실행

  • -i: 컨테이너를 대화형 모드로 실행합니다.
  • -t: 의사 입력 터미널을 컨테이너에 다시 할당
  • –name: 컨테이너 이름
  • –privileged: 컨테이너 공개 권한 설정(기본값은 true)
  • -p: 매핑된 포트 linux 포트: 컨테이너 내장 포트(mysql 기본 포트는 3306)
  • -v: 컨테이너 내 Linux 마운트 폴더/파일 및 경로 매핑
  • -e: 컨테이너 환경 변수(mysql 기본 사용자 이름 및 비밀번호 설정)
  • -d: 백그라운드에서 컨테이너를 실행하고 컨테이너 ID를 반환합니다.

여기에 이미지 설명을 삽입하세요.

docker exec -it canal bash

여기에 이미지 설명을 삽입하세요.

/home/admin/canal-server/conf/canal.properties
/home/admin/canal-server/conf/example/instance.properties
docker cp canal:/home/admin/canal-server/conf/canal.properties ./
docker cp canal:/home/admin/canal-server/conf/example/instance.properties ./

운하의 도커 컨테이너에서 구성 파일을 복사합니다.

여기에 이미지 설명을 삽입하세요.

구성 파일 복사 결과

여기에 이미지 설명을 삽입하세요.

3. 운하 구성 파일 편집

vim instance.properties 

구성 파일 편집에 필요한 매개변수, mysql의 내부 IP 주소, binlog 파일 이름 및 로그 위치

docker inspect mysql_3306 | grep IPA
show master status;

여기에 이미지 설명을 삽입하세요.

포트 번호를 추가하는 것을 잊지 않도록 주의하세요

여기에 이미지 설명을 삽입하세요.

MySQL 데이터베이스에 연결하기 위한 사용자 이름과 비밀번호를 수정합니다.

여기에 이미지 설명을 삽입하세요.

4. 이전 근관 컨테이너를 삭제하고 장착 시작 컨테이너를 만듭니다.

docker stop canal 
docker rm canal

여기에 이미지 설명을 삽입하세요.

docker run -itd  --name canal \
-p 11111:11111 --privileged=true \
-v /usr/local/software/canal/conf/instance.properties:/home/admin/canal-server/conf/example/instance.properties \
-v /usr/local/software/canal/conf/canal.properties:/home/admin/canal-server/conf/example/canal.properties \
canal/canal-server

여기에 이미지 설명을 삽입하세요.

5. 로그 보기

docker logs canal

로그를 확인하면 작업이 성공했습니다.

여기에 이미지 설명을 삽입하세요.

6. 운하 포트를 엽니다

firewall-cmd --zone=public --add-port=11111/tcp --permanent
firewall-cmd --reload
firewall-cmd --zone=public --list-ports

여기에 이미지 설명을 삽입하세요.

ps: Huawei Cloud 백엔드에서 포트를 열어야 합니다.

여기에 이미지 설명을 삽입하세요.

Tips: 서비스 포트 개발 여부를 모니터링하는 방법

nc는 넷캣입니다. netcat은 TCP 또는 UDP 프로토콜을 사용하여 네트워크 연결 간에 데이터를 읽고 쓰는 간단한 Unix 도구입니다.

직접 사용하거나 다른 프로그램이나 스크립트에서 간단히 호출할 수 있는 안정적인 백엔드 도구로 설계되었습니다.

동시에 필요한 거의 모든 유형의 연결을 생성할 수 있고 여러 가지 흥미로운 기능이 내장되어 있기 때문에 기능이 풍부한 네트워크 디버깅 및 탐색 도구이기도 합니다.

Netcat에는 연결 모드, 청취 모드 및 터널 모드의 세 가지 기능 모드가 있습니다.

nc(netcat) 명령의 일반 구문:

$ nc [-options] [HostName or IP] [PortNumber]

명령 세부정보:

  • nc: 즉, 실행된 명령의 주체입니다.
  • z:제로 I/O 모드(스캔에 사용됨);
  • v: 명시적으로 출력합니다.
  • w3: 시간 초과를 3초로 설정합니다.
  • 192.168.1.8:대상 시스템의 IP 주소입니다.
  • 22: 확인이 필요한 포트입니다.

사용 사례

[root@localhost conf]# nc -zvw3 124.80.139.65 3927
Ncat: Version 7.50 ( https://nmap.org/ncat )
Ncat: Connection timed out.
[root@localhost conf]# nc -zvw3 124.80.139.65 3927
Ncat: Version 7.50 ( https://nmap.org/ncat )
Ncat: Connected to 124.80.139.65:3927.
Ncat: 0 bytes sent, 0 bytes received in 0.02 seconds.

4. springboot는 운하를 통합합니다

1. 의존성 소개

        <!--        canal管道-->
        <dependency>
            <groupId>com.alibaba.otter</groupId>
            <artifactId>canal.client</artifactId>
            <version>1.1.0</version>
        </dependency>

2. 공식 홈페이지 케이스 코드 변경

package com.woniu.fresh.config.redis;


import java.net.InetSocketAddress;
import java.util.List;


import com.alibaba.otter.canal.client.CanalConnectors;
import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.common.utils.AddressUtils;
import com.alibaba.otter.canal.protocol.Message;
import com.alibaba.otter.canal.protocol.CanalEntry.Column;
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
import com.alibaba.otter.canal.protocol.CanalEntry.EntryType;
import com.alibaba.otter.canal.protocol.CanalEntry.EventType;
import com.alibaba.otter.canal.protocol.CanalEntry.RowChange;
import com.alibaba.otter.canal.protocol.CanalEntry.RowData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

/**
 * 用canal管道监听MySQL数据变化,自动更新redis缓存
 */
@Slf4j
@Component
public class AutoUpdateRedis {
    
    

    @Value("${canal.host}")
    private String host;

    @Value("${canal.port}")
    private Integer port;

    public void run() {
    
    

        // 创建链接
        final InetSocketAddress HOST = new InetSocketAddress(host,port);
//        final InetSocketAddress HOST = new InetSocketAddress("192.168.111.130",11111);
        CanalConnector connector = CanalConnectors.newSingleConnector(HOST, "example", "", "");
        int batchSize = 1000;
        int emptyCount = 0;
        try {
    
    
            connector.connect();
            connector.subscribe(".*\\..*");
            connector.rollback();
            int totalEmptyCount = 120;
            while (emptyCount < totalEmptyCount) {
    
    
                Message message = connector.getWithoutAck(batchSize); // 获取指定数量的数据
                long batchId = message.getId();
                int size = message.getEntries().size();
                if (batchId == -1 || size == 0) {
    
    
                    emptyCount++;
                    System.out.println("empty count : " + emptyCount);
                    try {
    
    
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
    
    
                    }
                } else {
    
    
                    emptyCount = 0;
                    // System.out.printf("message[batchId=%s,size=%s] \n", batchId, size);
                    printEntry(message.getEntries());
                }

                connector.ack(batchId); // 提交确认
                // connector.rollback(batchId); // 处理失败, 回滚数据
            }

            System.out.println("empty too many times, exit");
        } finally {
    
    
            connector.disconnect();
        }
    }

    private void printEntry(List<Entry> entrys) {
    
    
        for (Entry entry : entrys) {
    
    
            if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {
    
    
                continue;
            }

            RowChange rowChage = null;
            try {
    
    
                rowChage = RowChange.parseFrom(entry.getStoreValue());
            } catch (Exception e) {
    
    
                throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(),
                        e);
            }

            EventType eventType = rowChage.getEventType();
            System.out.println(String.format("================&gt; binlog[%s:%s] , name[%s,%s] , eventType : %s",
                    entry.getHeader().getLogfileName(), entry.getHeader().getLogfileOffset(),
                    entry.getHeader().getSchemaName(), entry.getHeader().getTableName(),
                    eventType));

            for (RowData rowData : rowChage.getRowDatasList()) {
    
    
                if (eventType == EventType.DELETE) {
    
    
                    printColumn(rowData.getBeforeColumnsList()); // 删除
                } else if (eventType == EventType.INSERT) {
    
    
                    printColumn(rowData.getAfterColumnsList());  // 添加
                } else {
    
    
                    // 修改
                    log.debug("-------修改之前before");
                    updateBefore(rowData.getBeforeColumnsList());
                    log.debug("-------修改之后after");
                    updateAfter(rowData.getAfterColumnsList());
                }
            }
        }
    }

    private static void printColumn(List<Column> columns) {
    
    
        for (Column column : columns) {
    
    
            System.out.println(column.getName() + " : " + column.getValue() + "    update=" + column.getUpdated());
        }
    }

    /**
     * 数据库更新之前
     * @param columns
     */
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    private  void updateBefore(List<Column> columns) {
    
    
        for (Column column : columns) {
    
    
            System.out.println(column.getName() + " : " + column.getValue() + "    update=" + column.getUpdated());
//            // 如果数据更新,就更新缓存的数据
//            if ("username".equals(column.getName())){
    
    
//                // 把更新之前的数据删除
//                stringRedisTemplate.opsForSet().remove("usernames", column.getValue());
//                break;
//            }
        }
    }
    private  void updateAfter(List<Column> columns) {
    
    
        for (Column column : columns) {
    
    
            System.out.println(column.getName() + " : " + column.getValue() + "    update=" + column.getUpdated());
//            // 如果数据更新,就更新缓存的数据
//            if ("username".equals(column.getName()) && column.getUpdated()){
    
    
//                // 把更新后的数据放入缓存
//                stringRedisTemplate.opsForSet().add("usernames", column.getValue());
//                break;
//            }
        }
    }
}


3. 메인 스타트업 클래스가 운하를 시작합니다.

package com.woniu.fresh;

import com.woniu.fresh.config.redis.AutoUpdateRedis;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.CommandLineRunner;

@SpringBootApplication
@Slf4j
public class FreshApp implements CommandLineRunner {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(FreshApp.class);
    }

    @Autowired
    private AutoUpdateRedis autoUpdateRedis;

    @Override
    public void run(String... args) throws Exception {
    
    
        log.debug(">>>>>启动缓存自动更新");
        autoUpdateRedis.run();
    }
}

4. 운하 시작 및 데이터베이스 수정

여기에 이미지 설명을 삽입하세요.

5. 백그라운드에서 변경 사항 모니터링

여기에 이미지 설명을 삽입하세요.


요약하다

1.Canal: MySQL 데이터베이스 증분 로그 분석을 기반으로 증분 데이터 구독 및 소비 제공
2.Canal 사용, MySQL 구성, docker Canal 설치
3. 클라우드 서버 오픈 포트, 서버 포트가 열려 있는지 테스트하는 방법
4 .봄철 운하의 예비 적용;

추천

출처blog.csdn.net/Pireley/article/details/132900906