Hive 中 Hook 的分类
Hook 是一种事件处理机制,通过 Hook 我们可以获取或者拦截程序运行过程中某些信息以便进行拓展使用,Hive Hook 可以将事件绑定在内部 Hive 的执行流程中,而无需重新编译 Hive。Hook 提供了扩展和集成外部组件的方式。根据不同的 Hook 类型,可以在不同的阶段运行。在 Hive 中提供了如下几类 Hook:
- Pre-driver-run hook:在 Driver 执行查询之前,用于在查询执行前进行初始化操作,例如设置环境变量、记录查询开始时间等。
- Post-driver-run hook:在 Driver 执行查询后,用于在查询执行后进行一些清理操作,例如记录查询结束时间、释放资源等。
- Pre-semantic-analyzer hook:在进行语义分析器之前,用于在查询语法分析前进行一些预处理操作,例如验证查询的合法性、记录查询信息等。
- Post-semantic-analyzer hook:在进行语义分析器之后,用于在查询语法分析后进行一些后处理操作,例如优化查询计划、记录分析结果等。
- Pre-execution hook:在执行引擎执行查询任务之前,用于在查询执行前进行一些预处理操作,例如验证权限、记录日志等。
- Post-execution hook:在执行引擎执行查询任务之后,用于在查询执行后进行一些后处理操作,例如记录查询结果、更新元数据等。其中 Atlas 提供的 HiveHook 属于这一类型。
- Failure-execution hook:在查询执行失败时,用于在查询执行失败时进行一些处理操作,例如记录错误日志、发送告警通知等。
Atlas-HiveHook 的实现
从 Atlas 集成 Hive Hook 的配置中得知两个信息
- hive.exec.post.hooks:表示 Atlas 实现的 Hook 类型为 Post-execution hook。
- org.apache.atlas.hive.hook.HiveHook:是 Atlas 实现 Post-execution hook 的入口。
<property>
<name>hive.exec.post.hooks</name>
<value>org.apache.atlas.hive.hook.HiveHook</value>
</property>
一句话总结下上述配置的作用,Atlas 的HiveHook 实现了 Hive 提供的 Post-execution hook 接口, HiveHook 在 HQL 执行查询任务后获取到事件并进行后续更新元数据操作。
Atlas-Hook 源码的前置知识
本文演示的源码为 Atlas 2.3.0 版本。Atlas 源码中与 Hook 相关的代码主要在 atlas/addons 目录下。
- bridge 后缀的主要是 Atlas 提供的一个插件,处理逻辑的代码。
- bridge-shim 后缀的主要是起到适配。提供一个统一的接口,用于兼容不同版本的插件。
HiveHook 的入口
- Atlas 源码路径:atlas/addons/hive-bridge-shim
- 类全名:org.apache.atlas.hive.hook.HiveHook
- 解释:下述源码笔者删减了非主流程的部分源码仅留下核心的代码。从源码可得知该类仅为用于 Atlas 实体注册的 Hive 钩子,而并非实现处理逻辑的代码,具体处理逻辑为 atlas/addons/hive-bridge 路径下的 HiveHook。
/**
* Hive hook used for atlas entity registration.
*/
public class HiveHook implements ExecuteWithHookContext {
private static final String ATLAS_HIVE_HOOK_IMPL_CLASSNAME = "org.apache.atlas.hive.hook.HiveHook"; // 实现类
private ExecuteWithHookContext hiveHookImpl = null;// 实际进行逻辑处理的对象
public HiveHook() {
this.initialize();
}
@Override
public void run(final HookContext hookContext) throws Exception {
try {
activatePluginClassLoader();
hiveHookImpl.run(hookContext); // HiveHook 入口
} finally {
deactivatePluginClassLoader();
}
}
private void initialize() {
// ...
Class<ExecuteWithHookContext> cls = (Class<ExecuteWithHookContext>) Class
.forName(ATLAS_HIVE_HOOK_IMPL_CLASSNAME, true, atlasPluginClassLoader);
hiveHookImpl = cls.newInstance();// 初始化 org.apache.atlas.hive.hook.HiveHook
// ...
}
}
HiveHook 的处理流程
- HiveHook:通过 HookContext 获取 HiveOperation,并根据 oper 创建对应的事件,交由父类 (AtlasHook) notifyEntities 处理事件。
public class HiveHook extends AtlasHook implements ExecuteWithHookContext {
// HiveHook 源码188行
public void run(HookContext hookContext) throws Exception {
try {
// 1、通过 hookContext 获取到 HiveOperation
HiveOperation oper = OPERATION_MAP.get(hookContext.getOperationName());
// 2、将 hookContext 包装为 AtlasHiveHookContext
AtlasHiveHookContext context = new AtlasHiveHookContext(this, oper, hookContext, getKnownObjects(), isSkipTempTables());
BaseHiveEvent event = null;
// 3、基于 oper 创建相应的 event(org.apache.atlas.hive.hook.events.BaseHiveEvent 的实现类)
switch (oper) {
case CREATEDATABASE:
event = new CreateDatabase(context);
break;
// .....
}
if (event != null) {
final UserGroupInformation ugi = hookContext.getUgi() == null ? Utils.getUGI() : hookContext.getUgi();
// 4 、调用父类(AtlasHook) notifyEntities 处理 event
super.notifyEntities(ActiveEntityFilter.apply(event.getNotificationMessages()), ugi);
}
} catch (Throwable t) {
LOG.error("HiveHook.run(): failed to process operation {}", hookContext.getOperationName(), t);
}
}
}
- AtlasHook:主要是做事件的预处理(是否拓展其他额外信息),同时就是创建并通过同步/异步的方式调用生产者客户端发送消息。
public abstract class AtlasHook {
protected static NotificationInterface notificationInterface; // 生产者客户端
static {
notificationInterface = NotificationProvider.get(); // 生产者客户端初始化
}
// 1、AtlasHook 源码339行:调用内容静态方法
protected void notifyEntities(List<HookNotification> messages, UserGroupInformation ugi) {
notifyEntities(messages, ugi, notificationMaxRetries, source);
}
// 2、AtlasHook 源码256行:同步或者异步执行,默认都是走异步
public static void notifyEntities(List<HookNotification> messages, UserGroupInformation ugi, int maxRetries, MessageSource source) {
if (executor == null) {
notifyEntitiesPostPreprocess(messages, ugi, maxRetries, source);
} else {
executor.submit(new Runnable() {
@Override
public void run() {
notifyEntitiesPostPreprocess(messages, ugi, maxRetries, source);
}
});
}
}
// 3、AtlasHook 源码238行:前置消息处理,默认为 False
private static void notifyEntitiesPostPreprocess(List<HookNotification> messages, UserGroupInformation ugi, int maxRetries, MessageSource source) {
if (shouldPreprocess) {
preprocessEntities(messages);
}
if (CollectionUtils.isNotEmpty(messages)) {
notifyEntitiesInternal(messages, maxRetries, ugi, notificationInterface, logFailedMessages, failedMessagesLogger, source);
}
}
// 4、AtlasHook 源码270行:通过生产者客户端发送消息
static void notifyEntitiesInternal(List<HookNotification> messages, int maxRetries, UserGroupInformation ugi,
NotificationInterface notificationInterface,
boolean shouldLogFailedMessages, FailedMessagesLogger logger, MessageSource source) {
if (ugi == null) {
notificationInterface.send(NotificationInterface.NotificationType.HOOK, messages, source); // type:NotificationType.HOOK
} else {
PrivilegedExceptionAction<Object> privilegedNotify = new PrivilegedExceptionAction<Object>() {
@Override
public Object run() throws Exception {
notificationInterface.send(NotificationInterface.NotificationType.HOOK, messages, source); // type:NotificationType.HOOK
return messages;
}
};
ugi.doAs(privilegedNotify);
}
}
}
- NotificationProvider:主要创建负责生产者客户端
public class NotificationProvider {
private static NotificationInterface notificationProvider;
// NotificationProvider 源码47行
public static NotificationInterface get() {
if (notificationProvider == null) {
try {
Configuration conf = ApplicationProperties.get();
String spoolDir = getSpoolDir(conf);
AbstractNotification absNotifier = null;
if (AtlasHook.isRESTNotificationEnabled) { // 默认为 False
absNotifier = new RestNotification(conf);
} else {
absNotifier = new KafkaNotification(conf); // 默认使用 KafkaNotification
}
if (isSpoolingEnabled(conf) && StringUtils.isNotEmpty(spoolDir)) {
conf.setProperty(CONF_ATLAS_HOOK_SPOOL_DIR, spoolDir);
notificationProvider = new AtlasFileSpool(conf, absNotifier);
} else {
notificationProvider = absNotifier;
}
} catch (AtlasException e) {
throw new RuntimeException("Error while initializing Notification interface", e);
}
}
return notificationProvider;
}
}
- AbstractNotification:负责将消息转变为 Json 字符串,并定义了抽象方法让子类自己实现发送消息的方式。
public abstract class AbstractNotification implements NotificationInterface {
// 1、AbstractNotification 源码92行
public <T> void send(NotificationType type, List<T> messages, MessageSource source) throws NotificationException {
List<String> strMessages = new ArrayList<>(messages.size());
for (int index = 0; index < messages.size(); index++) {
createNotificationMessages(messages.get(index), strMessages, source); // 将 messages 转化为 JSON 字符串
}
sendInternal(type, strMessages); // 具体调用子类的 sendInternal 方法
}
// 2、AbstractNotification 源码121行
public abstract void sendInternal(NotificationType type, List<String> messages) throws NotificationException;
}
- KafkaNotification:主要定了与 Kafka 通信的框架,同时实现了 AbstractNotification 的抽象方法,将消息发送至 Kafka。
public class KafkaNotification extends AbstractNotification implements Service {
// KafkaNotification 源码62行
public static final String ATLAS_HOOK_TOPIC = AtlasConfiguration.NOTIFICATION_HOOK_TOPIC_NAME.getString(); // 默认为 ATLAS_HOOK
// KafkaNotification 源码88行
private static final Map<NotificationType, String> PRODUCER_TOPIC_MAP = new HashMap<NotificationType, String>() {
{
put(NotificationType.HOOK, ATLAS_HOOK_TOPIC);
put(NotificationType.HOOK_UNSORTED, ATLAS_HOOK_TOPIC_UNSORTED);
put(NotificationType.ENTITIES, ATLAS_ENTITIES_TOPIC);
}
};
// 1、KafkaNotification 源码297行:获取生产者
public void sendInternal(NotificationType notificationType, List<String> messages) throws NotificationException {
KafkaProducer producer = getOrCreateProducer(notificationType);
sendInternalToProducer(producer, notificationType, messages);
}
// 2、KafkaNotification 源码304行:获取 Topic
void sendInternalToProducer(Producer p, NotificationType notificationType, List<String> messages) throws NotificationException {
String topic = PRODUCER_TOPIC_MAP.get(notificationType); // 根据 NotificationType.HOOK 获取 Topic 的内容为:ATLAS_HOOK
sendInternalToProducer(p, topic, messages);
}
// 3、KafkaNotification 源码309行:利用生产者发送消息
void sendInternalToProducer(Producer p, String topic , List<String> messages) throws NotificationException {
List<MessageContext> messageContexts = new ArrayList<>();
for (String message : messages) {
ProducerRecord record = new ProducerRecord(topic, message);
Future future = p.send(record); // 发送消息
messageContexts.add(new MessageContext(future, message));
}
}
}
一句话总结下 HiveHook 处理流程:HiveHook 通过实现 Hive 开放的 ExecuteWithHookContext 重写 run() 方法,通过 HookContext 将相应的 HiveOperation 转变为对应的事件,并调用父类 Atlas 的 notifyEntities() 方法将事件消息发送至 Kafka,Topic 为 ATLAS_HOOK。
下面再补充下 HiveHook 以及其他各类的关系图