[Spring cloud 一步步实现广告系统] 14. 全量索引代码实现
上一节我们实现了索引基本操作的类以及索引缓存工具类,本小节我们开始实现加载全量索引数据,在加载全量索引数据之前,我们需要先将数据库中的表数据导出到一份文件中。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
在实现初始化加载全量索引的过程中,一定要保证数据加载的顺序问题,因为不同的数据有可能存在着相互依赖的关联关系,一旦顺序写错,会造成程序报错问题。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
一道78%的Java程序员搞不清的Spring bean面试题
熟悉Spring开发的朋友都知道Spring提供了5种scope分别是singleton、prototype、request、session、global session。如下图是官方文档上的截图,感兴趣的朋友可以进去看看这五种分别有什么不同。今天要介绍的是这五种中的前两种,也是Spring最初提供的bean scope singleton 和 prototype。Spring官方文档介绍如下图: 单例bean与原型bean的区别 如果一个bean被声明为单例的时候,在处理多次请求的时候在Spring容器里只实例化出一个bean,后续的请求都公用这个对象,这个对象会保存在一个map里面。当有请求来的时候会先从缓存(map)里查看有没有,有的话直接使用这个对象,没有的话才实例化一个新的对象,所以这是个单例的。但是对于原型(prototype)bean来说当每次请求来的时候直接实例化新的bean,没有缓存以及从缓存查的过程。1.画图分析 2.源码分析生成bean时先判断单例的还是原型的 如果是单例的则先尝试从缓存里获取,没有在新创建 结论:单例的bean只有第一次创建新的bean 后面都会...
- 下一篇
一文带你了解JavaScript函数式编程
摘要: 函数式编程入门。 作者:浪里行舟 Fundebug经授权转载,版权归原作者所有。 前言 函数式编程在前端已经成为了一个非常热门的话题。在最近几年里,我们看到非常多的应用程序代码库里大量使用着函数式编程思想。 本文将略去那些晦涩难懂的概念介绍,重点展示在 JavaScript 中到底什么是函数式的代码、声明式与命令式代码的区别、以及常见的函数式模型都有哪些?想阅读更多优质文章请猛戳GitHub博客。 一、什么是函数式编程 函数式编程是一种编程范式,主要是利用函数把运算过程封装起来,通过组合各种函数来计算结果。函数式编程意味着你可以在更短的时间内编写具有更少错误的代码。举个简单的例子,假设我们要把字符串 functional programming is great 变成每个单词首字母大写,我们可以这样实现: var string = 'functional programming is great'; var result = string .split(' ') .map(v => v.slice(0, 1).toUpperCase() + v.slice(1)) .jo...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- CentOS7,8上快速安装Gitea,搭建Git服务器
- CentOS关闭SELinux安全模块
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- CentOS8安装Docker,最新的服务器搭配容器使用