Java技术升华分布式&高可用大数据

利用Binlog和Kafka实时同步mysql数据到Elasti

2019-03-20  本文已影响35人  Liuzz25

目录

1、利用Binlog和Kafka实时同步mysql数据到Elasticsearch(一) - 开启Binlog日志
2、利用Binlog和Kafka实时同步mysql数据到Elasticsearch(二) - 安装并运行Kafka
3、利用Binlog和Kafka实时同步mysql数据到Elasticsearch(三) - Binlog日志生产消息到Kafka
4、利用Binlog和Kafka实时同步mysql数据到Elasticsearch(四) - 消费Kafka消息同步数据到ES


前言

- 项目模块

BinlogMiddleware

1、binlog中间件,负责解析binlog,把变动的数据以json形式发送到kafka队列。

KafkaMiddleware

2、kafka中间件,负责消费kafka队列中的Message,把数据写入Elasticsearch中。

- 基础服务

(1)Mysql
(2)Kafka(用于存放mysql变动消息,存放于Kafka队列)
(3)Elasticsearch

- 项目源码

码云:https://gitee.com/OrgXxxx/SyncMysqlToElasticsearch

简介:

KafkaMiddleware服务主要负责消费Kafka队列消息,并将其同步到Elastcsearch(及Kafka消费者)。

{"event":"teemoliu.user.update","value":[1,"TeemoLiu"]}
{"event":"teemoliu.role.insert","value":[1,"管理员"]}

1、导入maven引用

        <!--kafka-->
        <dependency>
            <groupId>org.springframework.kafka</groupId>
            <artifactId>spring-kafka</artifactId>
            <version>2.2.4.RELEASE</version>
        </dependency>
        <!-- elasticsearch http api client -->
        <dependency>
            <groupId>io.searchbox</groupId>
            <artifactId>jest</artifactId>
            <version>5.3.3</version>
        </dependency>
        <!--fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.49</version>
        </dependency>

2、配置文件如下:

#停用服务端口
spring.main.web-environment=false
#============== kafka ===================
# 指定kafka 代理地址,可以多个
spring.kafka.bootstrap-servers=localhost:9092
#=============== consumer  =======================
# 指定默认消费者group id
spring.kafka.consumer.group-id=consumer1
spring.kafka.consumer.auto-offset-reset=latest
#spring.kafka.consumer.auto-offset-reset=earliest
spring.kafka.consumer.enable-auto-commit=true
#spring.kafka.consumer.auto-commit-interval=100
# 指定消息key和消息体的编解码方式
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer
# 消息JSON格式化模板
es.data.format.user={"id":"0","name":"1"}
es.data.format.role={"id":"0","name":"1"}

3、初始化Jest客户端

public class EsJestClient {

    private static JestClient client;

    /**
     * 获取客户端
     *
     * @return jestclient
     */
    public static synchronized JestClient getClient() {
        if (client == null) {
            build();
        }
        return client;
    }

    /**
     * 关闭客户端
     */
    public static void close(JestClient client) {
        if (!Objects.isNull(client)) {
            try {
                client.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 建立连接
     */
    private static void build() {
        JestClientFactory factory = new JestClientFactory();
        factory.setHttpClientConfig(
                new HttpClientConfig
                        .Builder(Config.ES_HOST)
                        .multiThreaded(true)
                        //一个route 默认不超过2个连接  路由是指连接到某个远程注解的个数。总连接数=route个数 * defaultMaxTotalConnectionPerRoute
                        .defaultMaxTotalConnectionPerRoute(2)
                        //所有route连接总数
                        .maxTotalConnection(2)
                        .connTimeout(10000)
                        .readTimeout(10000)
                        .gson(new GsonBuilder()
                                .setDateFormat("yyyy-MM-dd HH:mm:ss")
                                .create())
                        .build()
        );
        client = factory.getObject();
    }

}

4、实现Kafka批量消费

@Configuration
@EnableKafka
public class KafkaConsumerConfig {

    @Value("${spring.kafka.consumer.group-id}")
    String groupId;
    @Value("${spring.kafka.bootstrap-servers}")
    String bootstrapServers;
    @Value("${spring.kafka.consumer.auto-offset-reset}")
    String autoOffsetReset;
    
    @Bean
    KafkaListenerContainerFactory<?> batchFactory() {
        ConcurrentKafkaListenerContainerFactory<String, String> factory = new
                ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(new DefaultKafkaConsumerFactory<>(consumerConfigs()));
        factory.setBatchListener(true);
        return factory;
    }

    @Bean
    public Map<String, Object> consumerConfigs() {
        Map<String, Object> props = new HashMap<>();
        props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
        props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, autoOffsetReset);
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
        props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 100);//每一批数量
        props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "100");
        props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, 120000);
        props.put(ConsumerConfig.REQUEST_TIMEOUT_MS_CONFIG, 180000);
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        return props;
    }

}

5、实现ES 通用业务逻辑层

@Service
public class ESService{

    private JestClient client;

    public ESService(JestClient client) {
        this.client = client;
    }

    public boolean update(String id, String esType, Object object) {
        Index index = new Index.Builder(object).index(Config.ES_INDICES).type(esType).id(id).refresh(true).build();
        try {
            JestResult result = client.execute(index);
            return result != null && result.isSucceeded();
        } catch (Exception ignore) {
        }
        return false;
    }

    public Index getUpdateIndex(String id, String esType, Object object) {
        return new Index.Builder(object).index(Config.ES_INDICES).type(esType).id(id).refresh(true).build();
    }

    public Delete getDeleteIndex(String id, String esType) {
        return new Delete.Builder(id).index(Config.ES_INDICES).type(esType).build();
    }

    public boolean executeESClientRequest(List indexList, String esType) {
        Bulk bulk = new Bulk.Builder()
                .defaultIndex(Config.ES_INDICES)
                .defaultType(esType)
                .addAction(indexList)
                .build();
        indexList.clear();
        try {
            JestResult result = client.execute(bulk);
            return result != null && result.isSucceeded();
        } catch (Exception ignore) {
        }
        return false;
    }

    public boolean delete(String id, String esType) {
        try {
            DocumentResult result = client.execute(new Delete.Builder(id)
                    .index(Config.ES_INDICES)
                    .type(esType)
                    .build());
            return result.isSucceeded();
        } catch (Exception e) {
            throw new RuntimeException("delete exception", e);
        }
    }
}

6、实现消费逻辑

@Component
public class JsonConsumer {

    @Value("${es.data.format.user}")
    String userFormat;
    @Value("${es.data.format.role}")
    String roleFormat;

    JestClient client = EsJestClient.getClient();
    ESService documentDao = new ESService(client);

    @KafkaListener(topics = Config.KAFKA_JSON_TOPICS, id = Config.KAFKA_JSON_ID, containerFactory = "batchFactory")
    public void listen(List<ConsumerRecord<?, ?>> list) {
        List<String> messages = new ArrayList<>();
        for (ConsumerRecord<?, ?> record : list) {
            Optional<?> kafkaMessage = Optional.ofNullable(record.value());
            // 获取消息
            kafkaMessage.ifPresent(o -> messages.add(o.toString()));
        }
        if (messages.size() > 0) {
            // 更新索引
            updateES(messages);
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取ES的TYPE
     *
     * @param tableName
     * @return
     */
    private String getESType(String tableName) {
        String esType = "";
        switch (tableName) {
            case "role": {
                esType = Config.ES_ROLE_TYPE;
                break;
            }
            case "user": {
                esType = Config.ES_USER_TYPE;
                break;
            }
        }
        return esType;
    }

    /**
     * 获取消息JSON解析格式
     *
     * @param tableName
     * @return
     */
    private String getJsonFormat(String tableName) {
        String format = "";
        switch (tableName) {
            case "role": {
                format = roleFormat;
                break;
            }
            case "user": {
                format = userFormat;
                break;
            }
        }
        return format;
    }


    /**
     * 获取解析后的ES对象
     *
     * @param message
     * @param tableName
     * @return
     */
    private JSONObject getESObject(JSONArray message, String tableName) {
        JSONObject resultObject = new JSONObject();
        String format = getJsonFormat(tableName);
        if (!format.isEmpty()) {
            JSONObject jsonFormatObject = JSON.parseObject(format);
            for (String key : jsonFormatObject.keySet()) {
                String[] formatValues = jsonFormatObject.getString(key).split(",");
                if (formatValues.length < 2) {
                    resultObject.put(key, message.get(jsonFormatObject.getInteger(key)));
                } else {
                    Object object = message.get(Integer.parseInt(formatValues[0]));
                    if (object == null) {
                        String[] array = {};
                        resultObject.put(key, array);
                    } else {
                        String objectStr = message.get(Integer.parseInt(formatValues[0])).toString();
                        String[] result = objectStr.split(formatValues[1]);
                        resultObject.put(key, result);
                    }
                }
            }
        }
        return resultObject;
    }


    /**
     * 更新ES索引
     *
     * @param messages
     */
    private void updateES(List<String> messages) {
        List<Index> updateUserList = new ArrayList<>();
        List<Index> updateRoleList = new ArrayList<>();
        List<Delete> deleteUserList = new ArrayList<>();
        List<Delete> deleteRoleList = new ArrayList<>();
        for (String message : messages) {
            JSONObject result = null;
            try {
                result = JSON.parseObject(message);
            } catch (Exception e) {
                continue;
            }
            // 获取事件类型 event:"wtv3.videos.insert"
            String event = (String) result.get("event");
            String[] eventArray = event.split("\\.");
            String tableName = eventArray[1];
            String eventType = eventArray[2];
            // 获取具体数据
            JSONArray valueStr = (JSONArray) result.get("value");
            // 转化为对应格式的json字符串
            JSONObject object = getESObject(valueStr, tableName);
            // 获取ES的type
            String esType = getESType(tableName);
            switch (eventType) {
                case "insert": {
                    appendUpdateList(updateUserList, updateRoleList, object, esType);
                    break;
                }
                case "update": {
                    // 更新videos
                    appendUpdateList(updateUserList, updateRoleList, object, esType);
                    break;
                }
                case "delete": {
                    // 删除videos
                    appendDeleteList(deleteUserList, deleteRoleList, object, esType);
                    break;
                }
            }
        }
        if (updateUserList.size() > 0) {
            documentDao.executeESClientRequest(updateUserList, Config.ES_USER_TYPE);
        }
        if (updateRoleList.size() > 0) {
            documentDao.executeESClientRequest(updateRoleList, Config.ES_ROLE_TYPE);
        }
        if (deleteUserList.size() > 0) {
            documentDao.executeESClientRequest(deleteUserList, Config.ES_USER_TYPE);
        }
        if (deleteRoleList.size() > 0) {
            documentDao.executeESClientRequest(deleteRoleList, Config.ES_ROLE_TYPE);
        }
    }

    private void appendDeleteList(List<Delete> userList, List<Delete> roleList, JSONObject object, String esType) {
        switch (esType) {
            case Config.ES_USER_TYPE: {
                userList.add(documentDao.getDeleteIndex(object.get("id").toString(), esType));
                break;
            }
            case Config.ES_ROLE_TYPE: {
                roleList.add(documentDao.getDeleteIndex(object.get("id").toString(), esType));
                break;
            }
        }
    }

    private void appendUpdateList(List<Index> userList, List<Index> roleList, JSONObject object, String esType) {
        switch (esType) {
            case Config.ES_USER_TYPE: {
                userList.add(documentDao.getUpdateIndex(object.get("id").toString(), esType, object));
                break;
            }
            case Config.ES_ROLE_TYPE: {
                roleList.add(documentDao.getUpdateIndex(object.get("id").toString(), esType, object));
                break;
            }
        }
    }

}

7、运行结果(源码在前言)

Mysql-role表数据 Mysql-user表数据 Elasticsearch同步数据
上一篇下一篇

猜你喜欢

热点阅读