3、浅谈Apache Atlas-HiveHook源码分析

392 阅读6分钟
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 后缀的主要是起到适配。提供一个统一的接口,用于兼容不同版本的插件。

04-Atlas-源码.png

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 的处理流程
  1. 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);
        }
    }
}
  1. 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);
		}
    }
}
  1. 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;
    }
}
  1. 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;
}
  1. 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));
        }

    }
}

05-HiveHook流程图.png

​ 一句话总结下 HiveHook 处理流程:HiveHook 通过实现 Hive 开放的 ExecuteWithHookContext 重写 run() 方法,通过 HookContext 将相应的 HiveOperation 转变为对应的事件,并调用父类 Atlas 的 notifyEntities() 方法将事件消息发送至 Kafka,Topic 为 ATLAS_HOOK。

​ 下面再补充下 HiveHook 以及其他各类的关系图

06-HiveHook类图.png