上一節咱們實現了索引基本操做的類以及索引緩存工具類,本小節咱們開始實現加載全量索引數據,在加載全量索引數據以前,咱們須要先將數據庫中的表數據導出到一份文件中。Let's code.java
1.首先定義一個常量類,用來存儲導出文件存儲的目錄和文件名稱mysql
由於咱們導出的文件須要在搜索服務中使用到,所以,咱們將文件名 & 目錄以及導出對象的信息編寫在
mscx-ad-commom
項目中。sql
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
項目中bash
/** * IExportDataService for 導出數據庫廣告索引初始化數據 * * @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a> */ public interface IExportDataService { }
@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!"); } } }
@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監聽的操做類型)app
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.讀取文件實現全量索引加載。this
由於咱們文件加載以前須要依賴另外一個組件,也就是咱們的索引工具類,須要添加上
@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
在實現初始化加載全量索引的過程當中,必定要保證數據加載的順序問題,由於不一樣的數據有可能存在着相互依賴的關聯關係,一旦順序寫錯,會形成程序報錯問題。