"上一節" 我們分析了廣告索引的維護有2種, 和`增量索引維護`。因為廣告檢索是廣告系統中最為重要的環節,大家一定要認真理解我們索引設計的思路,接下來我們來編碼實現索引維護功能。 我們來定義一個介面,來接收所有index的增刪改查操作,介面定義一個範型,來接收2個參數, 代表我們索引的健值, 代表返 ...
上一節我們分析了廣告索引的維護有2種,全量索引載入
和增量索引維護
。因為廣告檢索是廣告系統中最為重要的環節,大家一定要認真理解我們索引設計的思路,接下來我們來編碼實現索引維護功能。
我們來定義一個介面,來接收所有index的增刪改查操作,介面定義一個範型,來接收2個參數,K
代表我們索引的健值,V
代表返回值。
/**
* IIndexAware for 實現廣告索引的增刪改查
*
* @author <a href="mailto:[email protected]">Isaac.Zhang | 若初</a>
*/
public interface IIndexAware<K, V> {
/**
* 通過key 獲取索引
*/
V get(K key);
/**
* 添加索引
* @param key
* @param value
*/
void add(K key, V value);
/**
* 更新索引
*/
void update(K key, V value);
/**
* 刪除索引
*/
void delete(K key, V value);
}
我們一定要知道,並不是所有的資料庫表都需要創建索引,比如User
表我們在數據檢索的時候其實是不需要的,當然也就沒必要創建索引,並且,也不是表中的所有欄位都需要索引,這個也是根據具體的業務來確定欄位信息,比如我們接下來要編寫的推廣計劃
索引中,推廣計劃名稱就可以不需要。下麵,我們來實現我們的第一個正向索引
。
- 首先創建操作推廣計劃的實體對象
/**
* AdPlanIndexObject for 推廣計劃索引對象
* 這個索引對象我們沒有添加 推廣計劃名稱
* @author <a href="mailto:[email protected]">Isaac.Zhang | 若初</a>
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AdPlanIndexObject {
private Long planId;
private Long userId;
private Integer planStatus;
private Date startDate;
private Date endDate;
/**
* 根據實際欄位來更新索引
*/
public void update(AdPlanIndexObject newObject) {
if (null != newObject.getPlanId()) {
this.planId = newObject.getPlanId();
}
if (null != newObject.getUserId()) {
this.userId = newObject.getUserId();
}
if (null != newObject.getPlanStatus()) {
this.planStatus = newObject.getPlanStatus();
}
if (null != newObject.getStartDate()) {
this.startDate = newObject.getStartDate();
}
if (null != newObject.getEndDate()) {
this.endDate = newObject.getEndDate();
}
}
}
- 然後創建推廣計劃索引實現類,並實現
IIndexAware
介面。
/**
* AdPlanIndexAwareImpl for 推廣計劃索引實現類
*
* @author <a href="mailto:[email protected]">Isaac.Zhang | 若初</a>
*/
@Slf4j
@Component
public class AdPlanIndexAwareImpl implements IIndexAware<Long, AdPlanIndexObject> {
private static Map<Long, AdPlanIndexObject> planIndexObjectMap;
/**
* 因為操作索引的過程中有可能對索引進行更新,為了防止多線程造成的線程不安全問題,我們不能使用hashmap,需要實現ConcurrentHashMap
*/
static {
planIndexObjectMap = new ConcurrentHashMap<>();
}
@Override
public AdPlanIndexObject get(Long key) {
return planIndexObjectMap.get(key);
}
@Override
public void add(Long key, AdPlanIndexObject value) {
log.info("AdPlanIndexAwareImpl before add::{}", planIndexObjectMap);
planIndexObjectMap.put(key, value);
log.info("AdPlanIndexAwareImpl after add::{}", planIndexObjectMap);
}
@Override
public void update(Long key, AdPlanIndexObject value) {
log.info("AdPlanIndexAwareImpl before update::{}", planIndexObjectMap);
//查詢當前的索引信息,如果不存在,直接新增索引信息
AdPlanIndexObject oldObj = planIndexObjectMap.get(key);
if (null == oldObj) {
planIndexObjectMap.put(key, value);
} else {
oldObj.update(value);
}
log.info("AdPlanIndexAwareImpl after update::{}", planIndexObjectMap);
}
@Override
public void delete(Long key, AdPlanIndexObject value) {
log.info("AdPlanIndexAwareImpl before delete::{}", planIndexObjectMap);
planIndexObjectMap.remove(key);
log.info("AdPlanIndexAwareImpl after delete::{}", planIndexObjectMap);
}
}
至此,我們已經完成了推廣計劃的索引對象和索引操作的代碼編寫,大家可以參考上面的示例,依次完成推廣單元
、推廣創意
、地域
、興趣
、關鍵詞
以及推廣創意和推廣單元的關聯索引
,或者可直接從 Github傳送門 / Gitee傳送門 下載源碼。
按照上述代碼展示,我們已經實現了所有的索引操作的定義,但是實際情況中,我們需要使用這些服務的時候,需要在每一個Service中
@Autowired
註入,我們那麼多的索引操作類,還不包含後續還有可能需要新增的索引維度,工作量實在是太大,而且不方便維護,作為一個合格的程式員來說,這是非常不友好的,也許會讓後續的開發人員罵娘。
為了防止後續被罵,我們來編寫一個索引緩存工具類com.sxzhongf.ad.index.IndexDataTableUtils
,通過這個索引緩存工具類來實現一次註入,解決後顧之憂。要實現這個工具類,我們需要實現2個介面:org.springframework.context.ApplicationContextAware
和org.springframework.core.PriorityOrdered
org.springframework.context.ApplicationContextAware
, 統一通過實現該介面的類,來操作Spring容器以及其中的Bean實例。
在Spring中,以Aware
為尾碼結束的類,大家可以簡單的理解為應用程式想要XXX,比如ApplicationContextAware
代表應用程式想要ApplicationContext
,BeanFactoryAware
表示應用程式想要BeanFactory
...等等org.springframework.core.PriorityOrdered
組件載入順序,也可以使用org.springframework.core.Ordered
以下代碼為我們的工具類:
package com.sxzhongf.ad.index;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.PriorityOrdered;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* IndexDataTableUtils for 所有索引服務需要緩存的Java Bean
*
* 使用方式:
* 獲取{@link com.sxzhongf.ad.index.creative.CreativeIndexAwareImpl}索引服務類
* 如下:
* {@code
* IndexDataTableUtils.of(CreativeIndexAwareImpl.class)
* }
* @author <a href="mailto:[email protected]">Isaac.Zhang | 若初</a>
*/
@Component
public class IndexDataTableUtils implements ApplicationContextAware, PriorityOrdered {
//註入ApplicationContext
private static ApplicationContext applicationContext;
/**
* 定義用於保存所有Index的Map
* Class標示我們的索引類
*/
private static final Map<Class, Object> dataTableMap = new ConcurrentHashMap<>();
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
IndexDataTableUtils.applicationContext = applicationContext;
}
/**
* 獲取索引服務緩存
*/
public static <T> T of(Class<T> klass) {
T instance = (T) dataTableMap.get(klass);
//如果獲取到索引bean,直接返回當前bean
if (null != instance) {
return instance;
}
//首次獲取索引bean為空,寫入Map
dataTableMap.put(klass, bean(klass));
return (T) dataTableMap.get(klass);
}
/**
* 獲取Spring 容器中的Bean對象
*/
private static <T> T bean(String beanName) {
return (T) applicationContext.getBean(beanName);
}
/**
* 獲取Spring 容器中的Bean對象
*/
private static <T> T bean(Class klass) {
return (T) applicationContext.getBean(klass);
}
@Override
public int getOrder() {
return PriorityOrdered.HIGHEST_PRECEDENCE;
}
}