上一节我们实现了索引基本操作的类以及索引缓存工具类,本小节我们开始实现加载全量索引数据,在加载全量索引数据之前,我们需要先将数据库中的表数据导出到一份文件中。Let's code.

1.首先定义一个常量类,用来存储导出文件存储的目录和文件名称

因为我们导出的文件需要在搜索服务中使用到,因此,我们将文件名 & 目录以及导出对象的信息编写在mscx-ad-commom项目中。

public class FileConstant {
public static final String DATA_ROOT_DIR = "/Users/xxx/Documents/promotion/data/mysql/"; //各个表数据的存储文件名
public static final String AD_PLAN = "ad_plan.data";
public static final String AD_UNIT = "ad_unit.data";
public static final String AD_CREATIVE = "ad_creative.data";
public static final String AD_CREATIVE_RELARION_UNIT = "ad_creative_relation_unit.data";
public static final String AD_UNIT_HOBBY = "ad_unit_hobby.data";
public static final String AD_UNIT_DISTRICT = "ad_unit_district.data";
public static final String AD_UNIT_KEYWORD = "ad_unit_keyword.data";
}

2.定义索引对象导出的字段信息,依然用Ad_Plan为例。

/**
* AdPlanTable for 需要导出的表字段信息 => 是搜索索引字段一一对应
*
* @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a>
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AdPlanTable {
private Long planId;
private Long userId;
private Integer planStatus;
private Date startDate;
private Date endDate;
}

3.导出文件服务实现

同样,最好的实现方式就是将导出服务作为一个子工程来独立运行,我这里直接实现在了mscx-ad-db项目中

  • 定义一个空接口,为了符合我们的编码规范
/**
* IExportDataService for 导出数据库广告索引初始化数据
*
* @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a>
*/
public interface IExportDataService {
}
  • 实现service
@Slf4j
@Service
public class ExportDataServiceImpl implements IExportDataService { @Autowired
private AdPlanRepository planRepository; /**
* 导出 {@code AdPlan} from DB to File
*
* @param fileName 文件名称
*/
public void exportAdPlanTable(String fileName) {
List<AdPlan> planList = planRepository.findAllByPlanStatus(CommonStatus.VALID.getStatus());
if (CollectionUtils.isEmpty(planList)) {
return;
} List<AdPlanTable> planTables = new ArrayList<>();
planList.forEach(item -> planTables.add(
new AdPlanTable(
item.getPlanId(),
item.getUserId(),
item.getPlanStatus(),
item.getStartDate(),
item.getEndDate()
)
)); //将数据写入文件
Path path = Paths.get(fileName);
try (BufferedWriter writer = Files.newBufferedWriter(path)) {
for (AdPlanTable adPlanTable : planTables) {
writer.write(JSON.toJSONString(adPlanTable));
writer.newLine();
}
writer.close();
} catch (IOException e) {
e.printStackTrace();
log.error("export AdPlanTable Exception!");
}
}
}
  • 实现Controller,提供操作入口
@Slf4j
@Controller
@RequestMapping("/export")
public class ExportDataController {
private final ExportDataServiceImpl exportDataService; @Autowired
public ExportDataController(ExportDataServiceImpl exportDataService) {
this.exportDataService = exportDataService;
} @GetMapping("/export-plan")
public CommonResponse exportAdPlans() { exportDataService.exportAdPlanTable(String.format("%s%s", FileConstant.DATA_ROOT_DIR, FileConstant.AD_PLAN));
return new CommonResponse();
}
}
  • 结果文件内容如下,每一行都代表了一个推广计划
{"endDate":1561438800000,"planId":10,"planStatus":1,"startDate":1561438800000,"userId":10}
{"endDate":1561438800000,"planId":11,"planStatus":1,"startDate":1561438800000,"userId":10}
根据文件内容构建索引

我们在之前编写索引服务的时候,创建了一些索引需要使用的实体对象类,比如构建推广计划索引的时候,需要使用到的实体对象com.sxzhongf.ad.index.adplan.AdPlanIndexObject,可是呢,我们在上一节实现索引导出的时候,实体对象又是common 包中的com.sxzhongf.ad.common.export.table.AdPlanTable,读取出来文件中的数据只能反序列化为JSON.parseObject(p, AdPlanTable.class),我们需要将2个对象做相互映射才能创建索引信息。

1.首先我们定义一个操作类型枚举,代表我们每一次的操作类型(也需要对应到后期binlog监听的操作类型

public enum OperationTypeEnum {
ADD,
UPDATE,
DELETE,
OTHER; public static OperationTypeEnum convert(EventType type) {
switch (type) {
case EXT_WRITE_ROWS:
return ADD;
case EXT_UPDATE_ROWS:
return UPDATE;
case EXT_DELETE_ROWS:
return DELETE;
default:
return OTHER;
}
}
}

2.因为全量索引的加载和增量索引加载的本质是一样的,全量索引其实就是一种特殊的增量索引,为了代码的可复用,我们创建统一的类来操作索引。

/**
* AdLevelDataHandler for 通用处理索引类
* 1. 索引之间存在层级划分,也就是相互之间拥有依赖关系的划分
* 2. 加载全量索引其实是增量索引 "添加"的一种特殊实现
*
* @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a>
*/
@Slf4j
public class AdLevelDataHandler { /**
* 实现广告推广计划的第二层级索引实现。
* (第一级为用户层级,但是用户层级不参与索引,所以从level 2开始)
* 第二层级的索引是表示 不依赖于其他索引,但是可被其他索引所依赖
*/
public static void handleLevel2Index(AdPlanTable adPlanTable, OperationTypeEnum type) {
// 对象转换
AdPlanIndexObject planIndexObject = new AdPlanIndexObject(
adPlanTable.getPlanId(),
adPlanTable.getUserId(),
adPlanTable.getPlanStatus(),
adPlanTable.getStartDate(),
adPlanTable.getEndDate()
); //调用通用方法处理,使用IndexDataTableUtils#of来获取索引的实现类bean
handleBinlogEvent(
// 在前一节我们实现了一个索引工具类,来获取注入的bean对象
IndexDataTableUtils.of(AdPlanIndexAwareImpl.class),
planIndexObject.getPlanId(),
planIndexObject,
type
);
} /**
* 处理全量索引和增量索引的通用处理方式
* K,V代表索引的键和值
*
* @param index 索引实现代理类父级
* @param key 键
* @param value 值
* @param type 操作类型
*/
private static <K, V> void handleBinlogEvent(IIndexAware<K, V> index, K key, V value, OperationTypeEnum type) {
switch (type) {
case ADD:
index.add(key, value);
break;
case UPDATE:
index.update(key, value);
break;
case DELETE:
index.delete(key, value);
break;
default:
break;
}
}
}

3.读取文件实现全量索引加载。

因为我们文件加载之前需要依赖另一个组件,也就是我们的索引工具类,需要添加上@DependsOn("indexDataTableUtils"),全量索引在系统启动的时候就需要加载,我们需要添加@PostConstruct来实现初始化加载,被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器调用一次。

@Component
@DependsOn("indexDataTableUtils")
public class IndexFileLoader { /**
* 服务启动时,执行全量索引加载
*/
@PostConstruct
public void init() {
//加载 推广计划
List<String> adPlanStrings = loadExportedData(String.format("%s%s",
FileConstant.DATA_ROOT_DIR, FileConstant.AD_PLAN
));
adPlanStrings.forEach(p -> AdLevelDataHandler.handleLevel2Index(
JSON.parseObject(p, AdPlanTable.class), OperationTypeEnum.ADD
));
} /**
* <h3>读取全量索引加载需要的文件</h3>
*
* @param fileName 文件名称
* @return 文件行数据
*/
private List<String> loadExportedData(String fileName) {
try (BufferedReader reader = Files.newBufferedReader(Paths.get(fileName))) {
return reader.lines().collect(Collectors.toList());
} catch (IOException e) {
throw new RuntimeException(e.getMessage());
}
}
}

Tips

在实现初始化加载全量索引的过程中,一定要保证数据加载的顺序问题,因为不同的数据有可能存在着相互依赖的关联关系,一旦顺序写错,会造成程序报错问题。

[Spring cloud 一步步实现广告系统] 14. 全量索引代码实现的更多相关文章

  1. [Spring cloud 一步步实现广告系统] 22. 广告系统回顾总结

    到目前为止,我们整个初级广告检索系统就初步开发完成了,我们来整体回顾一下我们的广告系统. 整个广告系统编码结构如下: mscx-ad 父模块 主要是为了方便我们项目的统一管理 mscx-ad-db 这 ...

  2. [Spring cloud 一步步实现广告系统] 20. 系统运行测试

    系统运行 经过长时间的编码实现,我们的主体模块已经大致完成,因为之前我们都是零散的对各个微服务自行测试,接下来,我们需要将所有的服务模块进行联调测试,Let's do it. 清除测试数据&测 ...

  3. [Spring cloud 一步步实现广告系统] 13. 索引服务编码实现

    上一节我们分析了广告索引的维护有2种,全量索引加载和增量索引维护.因为广告检索是广告系统中最为重要的环节,大家一定要认真理解我们索引设计的思路,接下来我们来编码实现索引维护功能. 我们来定义一个接口, ...

  4. [Spring cloud 一步步实现广告系统] 12. 广告索引介绍

    索引设计介绍 在我们广告系统中,为了我们能更快的拿到我们想要的广告数据,我们需要对广告数据添加类似于数据库index一样的索引结构,分两大类:正向索引和倒排索引. 正向索引 通过唯一键/主键生成与对象 ...

  5. [Spring cloud 一步步实现广告系统] 19. 监控Hystrix Dashboard

    在之前的18次文章中,我们实现了广告系统的广告投放,广告检索业务功能,中间使用到了 服务发现Eureka,服务调用Feign,网关路由Zuul以及错误熔断Hystrix等Spring Cloud组件. ...

  6. [Spring cloud 一步步实现广告系统] 21. 系统错误汇总

    广告系统学习过程中问题答疑 博客园 Eureka集群启动报错 Answer 因为Eureka在集群启动过程中,会连接集群中其他的机器进行数据同步,在这个过程中,如果别的服务还没有启动完成,就会出现Co ...

  7. [Spring cloud 一步步实现广告系统] 2. 配置&Eureka服务

    父项目管理 首先,我们在创建投放系统之前,先看一下我们的工程结构: mscx-ad-sponsor就是我们的广告投放系统.如上结构,我们需要首先创建一个Parent Project mscx-ad 来 ...

  8. [Spring cloud 一步步实现广告系统] 7. 中期总结回顾

    在前面的过程中,我们创建了4个project: 服务发现 我们使用Eureka 作为服务发现组件,学习了Eureka Server,Eureka Client的使用. Eureka Server 加依 ...

  9. [Spring cloud 一步步实现广告系统] 1. 业务架构分析

    什么是广告系统? 主要包含: 广告主投放广告的<广告投放系统> 媒体方(广告展示媒介-)检索广告用的<广告检索系统> 广告计费系统(按次,曝光量等等) 报表系统 Etc. 使用 ...

随机推荐

  1. python的数据类型之字符串(一)

    字符串(str) 双引号或者单引号中的数据,就是字符串. 注意事项 1.反斜杠可以用来转义,使用r可以让反斜杠不发生转义. 2.字符串可以用+运算符连接在一起,用*运算符重复. 3.Python中的字 ...

  2. 数据结构与算法---树结构(Tree structure)

    为什么需要树这种数据结构 数组存储方式的分析 优点:通过下标方式访问元素,速度快.对于有序数组,还可使用二分查找提高检索速度. 缺点:如果要检索具体某个值,或者插入值(按一定顺序)会整体移动,效率较低 ...

  3. Codeforces 781A:Andryusha and Colored Balloons(DFS染色)

    http://codeforces.com/contest/782/problem/C 题意:给一棵树染最少的颜色,使得相邻距离为2的点都是不同的颜色,问最少是多少种颜色并输出每个点的颜色. 思路:比 ...

  4. Python 爬虫从入门到进阶之路(十八)

    在之前的文章我们通过 scrapy 框架 及 scrapy.Spider 类做了一个<糗事百科>的糗百爬虫,本章我们再来看一下相较于 scrapy.Spider 类更为强大的 CrawlS ...

  5. c++学习书籍推荐《Exceptional C++ Style》下载

    百度云及其他网盘下载地址:点我 编辑推荐 软件“风格”所要讨论的主题是如何在开销与功能之间.优雅与可维护性之间.灵活.性与过分灵活之间寻找完美的平街点.在本书中,著名的C++大师Herb Sutter ...

  6. 使用Gitlab-CI 实现NetCore项目Docker化并部署到阿里云K8S

    使用Gitlab-CI 实现NetCore项目Docker化并部署到阿里云K8S 先行条件: 1.了解NetCore项目基础命令,如dotnet publish   等几个常用命令. 2.了解Dock ...

  7. R018---RPA是什么东东?

    1.缘起 这个问题,很多文章回答过,一直想站在客户角度写个答案,今天正好. 2.RPA的名字  RPA是英文Robotic Process Automation的缩写,中文爱翻译为“流程自动化机器人” ...

  8. Error:(949) Multiple substitutions specified in non-positional format; Android格式化string.xml

    string.xml问题代码 <string name="msg">书名:%s\n价格:%d</string> 异常信息 Error:(949) Multi ...

  9. vijos p1449 字符串还原

    学习<algorithm>下的reverse函数 #include<iostream> #include<string> #include<algorithm ...

  10. 如何优雅关闭 Spring Boot 应用

    ## 前言 随着线上应用逐步采用 SpringBoot 构建,SpringBoot应用实例越来多,当线上某个应用需要升级部署时,常常简单粗暴地使用 kill 命令,这种停止应用的方式会让应用将所有处理 ...