上一节我们分析了广告索引的维护有2种,全量索引加载和增量索引维护。因为广告检索是广告系统中最为重要的环节,大家一定要认真理解我们索引设计的思路,接下来我们来编码实现索引维护功能。
我们来定义一个接口,来接收所有index的增删改查操作,接口定义一个范型,来接收2个参数,K代表我们索引的健值,V代表返回值。
/** * IIndexAware for 实现广告索引的增删改查 * * @author <a href="mailto:magicianisaac@gmail.com">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表我们在数据检索的时候其实是不需要的,当然也就没必要创建索引,并且,也不是表中的所有字段都需要索引,这个也是根据具体的业务来确定字段信息,比如我们接下来要编写的推广计划索引中,推广计划名称就可以不需要。下面,我们来实现我们的第一个正向索引。
- 首先创建操作推广计划的实体对象
/** * AdPlanIndex for 推广计划索引对象 * 这个索引对象我们没有添加 推广计划名称 * @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a> */@Data@NoArgsConstructor@AllArgsConstructorpublic class AdPlanIndex { private Long planId; private Long userId; private Integer planStatus; private Date startDate; private Date endDate; /** * 根据实际字段来更新索引 */ public void update(AdPlanIndex new ) { if (null != new .getPlanId()) { this.planId = new .getPlanId(); } if (null != new .getUserId()) { this.userId = new .getUserId(); } if (null != new .getPlanStatus()) { this.planStatus = new .getPlanStatus(); } if (null != new .getStartDate()) { this.startDate = new .getStartDate(); } if (null != new .getEndDate()) { this.endDate = new .getEndDate(); } }}- 然后创建推广计划索引实现类,并实现
IIndexAware接口。
/** * AdPlanIndexAwareImpl for 推广计划索引实现类 * * @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a> */@Slf4j@Componentpublic class AdPlanIndexAwareImpl implements IIndexAware<Long, AdPlanIndex > { private static Map<Long, AdPlanIndex > planIndex Map; /** * 因为操作索引的过程中有可能对索引进行更新,为了防止多线程造成的线程不安全问题,我们不能使用hashmap,需要实现ConcurrentHashMap */ static { planIndex Map = new ConcurrentHashMap<>(); } @Override public AdPlanIndex get(Long key) { return planIndex Map.get(key); } @Override public void add(Long key, AdPlanIndex value) { log.info("AdPlanIndexAwareImpl before add::{}", planIndex Map); planIndex Map.put(key, value); log.info("AdPlanIndexAwareImpl after add::{}", planIndex Map); } @Override public void update(Long key, AdPlanIndex value) { log.info("AdPlanIndexAwareImpl before update::{}", planIndex Map); //查询当前的索引信息,如果不存在,直接新增索引信息 AdPlanIndex oldObj = planIndex Map.get(key); if (null == oldObj) { planIndex Map.put(key, value); } else { oldObj.update(value); } log.info("AdPlanIndexAwareImpl after update::{}", planIndex Map); } @Override public void delete(Long key, AdPlanIndex value) { log.info("AdPlanIndexAwareImpl before delete::{}", planIndex Map); planIndex Map.remove(key); log.info("AdPlanIndexAwareImpl after delete::{}", planIndex Map); }}至此,我们已经完成了推广计划的索引对象和索引操作的代码编写,大家可以参考上面的示例,依次完成推广单元、推广创意、地域、兴趣、关键词以及推广创意和推广单元的关联索引,或者可直接从 Github传送门 / Gitee传送门 下载源码。
按照上述代码展示,我们已经实现了所有的索引操作的定义,但是实际情况中,我们需要使用这些服务的时候,需要在每一个Service中
@Autowired注入,我们那么多的索引操作类,还不包含后续还有可能需要新增的索引维度,工作量实在是太大,而且不方便维护,作为一个合格的程序员来说,这是非常不友好的,也许会让后续的开发人员骂娘。
为了防止后续被骂,我们来编写一个索引缓存工具类com.sxzhongf.ad.index.IndexDataTableUtils,通过这个索引缓存工具类来实现一次注入,解决后顾之忧。要实现这个工具类,我们需要实现2个接口:org.spring work.context.ApplicationContextAware和org.spring work.core.PriorityOrdered
org.spring work.context.ApplicationContextAware, 统一通过实现该接口的类,来操作Spring容器以及其中的Bean实例。
在Spring中,以Aware为后缀结束的类,大家可以简单的理解为应用程序想要XXX,比如ApplicationContextAware代表应用程序想要ApplicationContext,BeanFactoryAware表示应用程序想要BeanFactory...等等org.spring work.core.PriorityOrdered组件加载顺序,也可以使用org.spring work.core.Ordered
以下代码为我们的工具类:
package com.sxzhongf.ad.index;import org.spring work.beans.BeansException;import org.spring work.context.ApplicationContext;import org.spring work.context.ApplicationContextAware;import org.spring work.core.PriorityOrdered;import org.spring work.stereotype.Component;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;/** * IndexDataTableUtils for 所有索引服务需要缓存的Java Bean * * 使用方式: * 获取{@ com.sxzhongf.ad.index.creative.CreativeIndexAwareImpl}索引服务类 * 如下: * {@code * IndexDataTableUtils.of(CreativeIndexAwareImpl.class) * } * @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a> */@Componentpublic class IndexDataTableUtils implements ApplicationContextAware, PriorityOrdered { //注入ApplicationContext private static ApplicationContext applicationContext; /** * 定义用于保存所有Index的Map * Class标示我们的索引类 */ private static final Map<Class, > 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; }} 继续阅读与本文标签相同的文章
搞技术十年,还是这套架构体系靠谱!
Istio,灰度发布从未如此轻松!!!
-
内存KV缓存/数据库,可以选择它? | 1分钟系列
2026-05-21栏目: 教程
-
20道BAT面试官最喜欢问的JVM+MySQL面试题(含答案解析)
2026-05-21栏目: 教程
-
全网云主机爆款特惠,新用户限时享低至2折优惠
2026-05-21栏目: 教程
-
MessagePack Java 0.6.X 动态类型
2026-05-21栏目: 教程
-
几个线上问题追查的常用命令 | 1分钟系列
2026-05-21栏目: 教程
