第十四章 广告检索系统——Binlog 增量数据的投递

此博客用于个人学习,来源于网上,对知识点进行一个整理。

1. 增量数据投递前的准备工作:

MySQL 增量数据的投递核心目的是构建检索服务中的增量索引,也就是说 IncrementListener 中使用到的 Isender 的方法去投递增量数据。首先我们观察到 AdPlanTable 中有两个特殊的索引——startDate 和 endDate。Long 和 Integer 这种类型的数据对于 MySQL 的表达不会有问题。但对于 Date 这种类型,Binlog 序列化成字符串,所以说我们需要去搞清楚 Date 这种数据类型的表达是什么样的。同样的,我们最好的方式就是看一下 Binlog 怎么样以字符串的形式表达日期。

1.1 设计转化格式:

启动 Binlog 的监听开关,然后去数据库中写入一些内容,比如插入一条数据,然后查看控制台上的打印信息,特别观察其中的日期信息。

Tue Jan 01 08:00:00 CST 2019

根据上面的打印信息设置解析方式。

1.2 实现转化:

在 CommonUtils 类中定义一个方法,实现对数据的解析,将其解析为一个 Date 类型。

@Slf4j
public class CommonUtils {

    /**
     * 传进来一个 map,如果这个 map 的 key 不存在,返回一个新的 value 对象
     * @param key
     * @param map
     * @param factory
     * @param <K>
     * @param <V>
     * @return
     */
    public static <K,V> V getOrCreate(K key, Map<K,V> map, Supplier<V> factory){
        return map.computeIfAbsent(key,k -> factory.get());
    }

    /**
     * 拼接字符串
     * @param args
     * @return
     */
    public static String stringConcat(String... args){
        StringBuffer result = new StringBuffer();
        for (String arg : args) {
            result.append(arg);
            result.append("-");
        }
        result.deleteCharAt(result.length() - 1);
        return result.toString();
    }

    /**
     * 将 String 类型转化为 Date 类型
     * @param dateString
     * @return
     */
    public static Date parseStringDate(String dateString){
        try{
            DateFormat dateFormat = new SimpleDateFormat(
                    "EEE MMM dd HH:mm:ss zzz yyyy",
                    Locale.US
            );
            return DateUtils.addHours(
                    dateFormat.parse(dateString),
                    -8
            );
        }catch (ParseException ex){
            log.error("parseStringDate error:{}",dateString);
            return null;
        }
    }
}

1.3 定义数据层级:

因为我们构建索引的时候是根据表之间的层级关系去构建的,但是目前我们还没有一个常量定义去定义这样的一个常量关系。因为我们去定义索引的时候,可能要去处理 level2,level3,level4,在代码中写上234也不太好维护,所以我们去定义一个 DataLevel 的一个数据层级,因为数据层级是索引内部的含义,于是我们将其定义在 index 包下。定义一个枚举类 DateLevel。

@Getter
public enum DateLevel {

    LEVEL2("2","level 2"),
    LEVEL3("3","level 3"),
    LEVEL4("4","level 4");

    private String level;
    private String desc;

    DateLevel(String level,String desc){
        this.level = level;
        this.desc = desc;
    }
}

2. 增量数据的投递:

增量数据的投递其实就是去实现 ISender 这个接口,ISender 接口去接收一个参数,是 MySqlRowData 的类型。构建索引的过程是使用我们之前完成的 AdLevelDataHandler,里面分为好几个层级,对于每一个层级都是将表转化为 table 对象,最终我们在增量数据投递的过程中,去实现增量索引的加载。

2.1 第二层级增量数据的投递:

@Slf4j
@Component("indexSender")
public class IndexSender implements ISender {

    @Override
    public void sender(MySqlRowData rowData) {
        String level = rowData.getLevel();

        if (DateLevel.LEVEL2.getLevel().equals(level)){
            //第二层级增量数据的投递
            Level2RowData(rowData);
        }else if (DateLevel.LEVEL3.getLevel().equals(level)){
            //第三层级增量数据的投递
        }else if (DateLevel.LEVEL4.getLevel().equals(level)){
            //第四层级增量数据的投递
        }else {
            log.error("MysqlRowData ERROR:{}", JSON.toJSONString(rowData));
        }
    }

    private void Level2RowData(MySqlRowData rowData){
        if (rowData.getTableName().equals(Constant.AD_PLAN_TABLE_INFO.TABLE_NAME)){
            List<AdPlanTable> planTables = new ArrayList<>();
            for (Map<String,String> fieldValueMap : rowData.getFieldValueMap()){
                AdPlanTable planTable = new AdPlanTable();
                fieldValueMap.forEach((k,v) -> {
                    switch (k){
                        case Constant.AD_PLAN_TABLE_INFO.COLUMN_ID:
                            planTable.setPlanId(Long.valueOf(v));
                            break;
                        case Constant.AD_PLAN_TABLE_INFO.COLUMN_USER_ID:
                            planTable.setUserId(Long.valueOf(v));
                            break;
                        case Constant.AD_PLAN_TABLE_INFO.COLUMN_PLAN_STATUS:
                            planTable.setPlanStatus(Integer.valueOf(v));
                            break;
                        case Constant.AD_PLAN_TABLE_INFO.COLUMN_START_DATE:
                            planTable.setStartDate(CommonUtils.parseStringDate(v));
                            break;
                        case Constant.AD_PLAN_TABLE_INFO.COLUMN_END_DATE:
                            planTable.setEndDate(CommonUtils.parseStringDate(v));
                            break;
                    }
                });
                planTables.add(planTable);
            }
            planTables.forEach(p -> AdLevelDataHandler.handleLevel2(p,rowData.getOpType()));
        }else if (rowData.getTableName().equals(Constant.AD_CREATIVE_TABLE_INFO.TABLE_NAME)){
            List<CreativeTable> creativeTables = new ArrayList<>();
            for (Map<String,String> filedValueMap : rowData.getFieldValueMap()){
                CreativeTable creativeTable = new CreativeTable();
                filedValueMap.forEach((k,v) -> {
                    switch (k){
                        case Constant.AD_CREATIVE_TABLE_INFO.COLUMN_ID:
                            creativeTable.setAdId(Long.valueOf(v));
                            break;
                        case Constant.AD_CREATIVE_TABLE_INFO.COLUMN_TYPE:
                            creativeTable.setType(Integer.valueOf(v));
                            break;
                        case Constant.AD_CREATIVE_TABLE_INFO.COLUMN_MATERIAL_TYPE:
                            creativeTable.setMaterialType(Integer.valueOf(v));
                            break;
                        case Constant.AD_CREATIVE_TABLE_INFO.COLUMN_HEIGHT:
                            creativeTable.setHeight(Integer.valueOf(v));
                            break;
                        case Constant.AD_CREATIVE_TABLE_INFO.COLUMN_WIDTH:
                            creativeTable.setWidth(Integer.valueOf(v));
                            break;
                        case Constant.AD_CREATIVE_TABLE_INFO.COLUMN_AUDIT_STATUS:
                            creativeTable.setAuditStatus(Integer.valueOf(v));
                            break;
                        case Constant.AD_CREATIVE_TABLE_INFO.COLUMN_URL:
                            creativeTable.setAdUrl(v);
                            break;
                    }
                });
                creativeTables.add(creativeTable);
            }
            creativeTables.forEach(c -> AdLevelDataHandler.handleLevel2(c,rowData.getOpType()));
        }
    }
}

2.2 第三层级增量数据的投递:

第三层级与第二层级有依赖关系,需要增加外键的维护。

private void Level3RowData(MySqlRowData rowData){
    if (rowData.getTableName().equals(Constant.AD_UNIT_TABLE_INFO.TABLE_NAME)) {
        List<AdUnitTable> unitTables = new ArrayList<>();
        for (Map<String, String> fieldValueMap : rowData.getFieldValueMap()) {
            AdUnitTable unitTable = new AdUnitTable();
            fieldValueMap.forEach((k, v) -> {
                switch (k) {
                    case Constant.AD_UNIT_TABLE_INFO.COLUMN_ID:
                        unitTable.setUnitId(Long.valueOf(v));
                        break;
                    case Constant.AD_UNIT_TABLE_INFO.COLUMN_UNIT_STATUS:
                        unitTable.setUnitStatus(Integer.valueOf(v));
                        break;
                    case Constant.AD_UNIT_TABLE_INFO.COLUMN_POSITION_TYPE:
                        unitTable.setPositionType(Integer.valueOf(v));
                        break;
                    case Constant.AD_UNIT_TABLE_INFO.COLUMN_PLAN_ID:
                        unitTable.setPlanId(Long.valueOf(v));
                        break;
                }
            });
            unitTables.add(unitTable);
        }
        unitTables.forEach(u -> AdLevelDataHandler.handleLevel3(u, rowData.getOpType()));
    } else if (rowData.getTableName().equals(Constant.AD_CREATIVE_UNIT_TABLE_INFO.TABLE_NAME)) {
        List<CreativeUnitTable> creativeUnitTables = new ArrayList<>();
        for (Map<String, String> fieldValueMap : rowData.getFieldValueMap()) {
            CreativeUnitTable creativeUnitTable = new CreativeUnitTable();
            fieldValueMap.forEach((k, v) -> {
                switch (k) {
                    case Constant.AD_CREATIVE_UNIT_TABLE_INFO.COLUMN_CREATIVE_ID:
                        creativeUnitTable.setAdId(Long.valueOf(v));
                        break;
                    case Constant.AD_CREATIVE_UNIT_TABLE_INFO.COLUMN_UNIT_ID:
                        creativeUnitTable.setUnitId(Long.valueOf(v));
                        break;
                }
            });
            creativeUnitTables.add(creativeUnitTable);
        }
        creativeUnitTables.forEach(u -> AdLevelDataHandler.handleLevel3(u, rowData.getOpType()));
    }
}

2.3 第四层级增量数据的投递:

第四层级有三张表,可以采用 switch 的形式。

private void Level4RowData(MySqlRowData rowData) {
    switch (rowData.getTableName()) {
        case Constant.AD_UNIT_DISTRICT_TABLE_INFO.TABLE_NAME:
            List<UnitDistrictTable> districtTables = new ArrayList<>();
            for (Map<String, String> fieldValueMap : rowData.getFieldValueMap()) {
                UnitDistrictTable districtTable = new UnitDistrictTable();
                fieldValueMap.forEach((k, v) -> {
                    switch (k) {
                        case Constant.AD_UNIT_DISTRICT_TABLE_INFO.COLUMN_UNIT_ID:
                            districtTable.setUnitId(Long.valueOf(v));
                            break;
                        case Constant.AD_UNIT_DISTRICT_TABLE_INFO.COLUMN_PROVINCE:
                            districtTable.setProvince(v);
                            break;
                        case Constant.AD_UNIT_DISTRICT_TABLE_INFO.COLUMN_CITY:
                            districtTable.setCity(v);
                            break;
                    }
                });
                districtTables.add(districtTable);
            }
            districtTables.forEach(d -> AdLevelDataHandler.handleLevel4(d, rowData.getOpType()));
            break;
        case Constant.AD_UNIT_IT_TABLE_INFO.TABLE_NAME:
            List<UnitItTable> itTables = new ArrayList<>();
            for (Map<String, String> fieldValueMap : rowData.getFieldValueMap()) {
                UnitItTable itTable = new UnitItTable();
                fieldValueMap.forEach((k, v) -> {
                    switch (k) {
                        case Constant.AD_UNIT_IT_TABLE_INFO.COLUMN_UNIT_ID:
                            itTable.setUnitId(Long.valueOf(v));
                            break;
                        case Constant.AD_UNIT_IT_TABLE_INFO.COLUMN_IT_TAG:
                            itTable.setItTag(v);
                            break;
                    }
                });
                itTables.add(itTable);
            }
            itTables.forEach(i -> AdLevelDataHandler.handleLevel4(i, rowData.getOpType()));
            break;
        case Constant.AD_UNIT_KEYWORD_TABLE_INFO.TABLE_NAME:
            List<UnitKeywordTable> keywordTables = new ArrayList<>();
            for (Map<String, String> fieldValueMap : rowData.getFieldValueMap()) {
                UnitKeywordTable keywordTable = new UnitKeywordTable();
                fieldValueMap.forEach((k, v) -> {
                    switch (k) {
                        case Constant.AD_UNIT_KEYWORD_TABLE_INFO.COLUMN_UNIT_ID:
                            keywordTable.setUnitId(Long.valueOf(v));
                            break;
                        case Constant.AD_UNIT_KEYWORD_TABLE_INFO.COLUMN_KEYWORD:
                            keywordTable.setKeyword(v);
                            break;
                    }
                });
                keywordTables.add(keywordTable);
            }
            keywordTables.forEach(k -> AdLevelDataHandler.handleLevel4(k, rowData.getOpType()));
            break;
    }
}

3. 将增量数据投递到 Kafka:

对于 MySQL Binlog 可以有多种方式实现分发,对于我们的检索系统,更重要的是构建增量索引,比如说给我们的广告分析服务。接下来去实现一种更加常用的方式,将增量数据投递到 Kafka 里面,可以去监听 Kafka 的一个 topic,然后去获取对应的增量数据。其他服务可以去监听这个类,去获取到相应的服务。

@Component("kafkaSender")
public class KafkaSender implements ISender {

    @Value("${adconf.kafka.topic}")
    private String topic;

    private final KafkaTemplate<String,String> kafkaTemplate;

    public KafkaSender(KafkaTemplate<String, String> kafkaTemplate) {
        this.kafkaTemplate = kafkaTemplate;
    }

    @Override
    public void sender(MySqlRowData rowData) {
        kafkaTemplate.send(topic, JSON.toJSONString(rowData));
    }

    @KafkaListener(topics = {"ad-search-mysql-data"}, groupId = "ad-search")
    public void processMysqlRowData(ConsumerRecord<?,?> record){
        Optional<?> kafkaMessage = Optional.ofNullable(record.value());
        if (kafkaMessage.isPresent()) {
            Object message = kafkaMessage.get();
            MySqlRowData rowData = JSON.parseObject(
                    message.toString(),
                    MySqlRowData.class
            );
            System.out.println("kafka processMysqlRowData: " + JSON.toJSONString(rowData));
        }
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值