Hive Metastore动态切换存储引擎方案探索

Posted 咬定青松

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Hive Metastore动态切换存储引擎方案探索相关的知识,希望对你有一定的参考价值。

Hadoop统一封装了对底层不同存储引擎的支持,且通过开放一致的API接口,便于调用方切换不同的存储访问,但是在Hadoop之上的Hive因为通过静态的配置方式来访问存储引擎,且对外不暴露接口,调用方切换引擎十分不变,本文来探讨下动态切换存储的可行性和方案。

Hadoop如何动态切换存储引擎

在Hadoop 的文件系统中能够根据路径和配置信息生成FileSystem,接口定义是:

public static FileSystem get(URI uri, Configuration conf);

其中FileSystem代表了通用抽象的文件系统基类,其实现可以是本地磁盘文件系统,也可以是分布式文件系统以及第三方对象存储系统。本地文件系统的实现类是LocalFileSystem,分布式文件系统的实现类是DistributedFileSystem、对象存储系统实现类比如S3AFileSystem、OBSFileSystem等。通过FileSystem,可以实现对底层存储引擎的读写操作。拿MinIO(一种兼容S3协议的对象存储系统)为例,创建其FileSystem:

Configuration conf = new Configuration();
conf.set("fs.s3a.impl", "org.apache.hadoop.fs.s3a.S3AFileSystem");
conf.set("fs.s3a.access.key", "admin123");
conf.set("fs.s3a.secret.key", "admin123");
conf.set("fs.s3a.endpoint", "http://ip-address-for-minio:9000");
String warehouse = "s3a://tmp/iceberg/warehouse";
FileSystem fileSystem=FileSystem.get(new URI(warehouse), conf);
S3AFileSystem s3AFileSystem= (S3AFileSystem) S3AFileSystem.get(new URI(warehouse), conf);
s3AFileSystem.delete(new Path(warehouse),true);

以上配置是创建S3AFileSystem文件系统,必需的几个配置属性。具体来讲,生成S3AFileSystem,有两个主要步骤:S3AFileSystem对象创建和客户端初始化。

  • S3AFileSystem对象创建

在FileSystem抽象类中根据具体文件系统Schema(hdfs、s3a、obs等)的实现类,通过反射自动生成FileSystem实例:

Class<? extends FileSystem> clazz =
    getFileSystemClass(uri.getScheme(), conf);
Constructor<T> meth = clazz.getDeclaredConstructor(new Class[]);
 meth.setAccessible(true);
result = meth.newInstance();

这里用到的是s3a 属性fs.s3a.impl指定的实现类:org.apache.hadoop.fs.s3a.S3AFileSystem。

  • 客户端初始化

S3ClientFactory.S3ClientCreationParameters parameters = null;
parameters = new S3ClientFactory.S3ClientCreationParameters()
    .withCredentialSet(credentials)
    .withEndpoint(endpoint)
    .withRequestHandlers(auditManager.createRequestHandlers());
//AmazonS3 s3
s3 = ReflectionUtils.newInstance(s3ClientFactoryClass, conf)
    .createS3Client(getUri(),
        parameters);

这里通过S3ClientFactory创建访问FileSystem的客户端,用到的主要属性是s3a 配置的endpoint和秘钥对。

从上述过程来看,动态切换不同的存储引擎,相当容易,只需要更改上述配置即可,比如切换到华为OBS,只需要配置形如下面的属性:

fs.obs.impl:org.apache.hadoop.fs.obs.OBSFileSystem
fs.AbstractFileSystem.obs.impl:org.apache.hadoop.fs.obs.OBS
fs.obs.access.key:xxx
fs.obs.secret.key:xxx
fs.obs.endpoint:obs.cn-southwest-2.myhuaweicloud.com

对于秘钥对的配置方式,AWS提供了很多种AWSCredentialsProvider接口的实现类,而且允许实现自定义获取方式:

public interface AWSCredentialsProvider 
    /**
     * Returns AWSCredentials which the caller can use to authorize an AWS request.
     * Each implementation of AWSCredentialsProvider can chose its own strategy for
     * loading credentials.  For example, an implementation might load credentials
     * from an existing key management system, or load new credentials when
     * credentials are rotated.
     *
     * @return AWSCredentials which the caller can use to authorize an AWS request.
     */
    public AWSCredentials getCredentials();


    /**
     * Forces this credentials provider to refresh its credentials. For many
     * implementations of credentials provider, this method may simply be a
     * no-op, such as any credentials provider implementation that vends
     * static/non-changing credentials. For other implementations that vend
     * different credentials through out their lifetime, this method should
     * force the credentials provider to refresh its credentials.
     */
    public void refresh();

但是该接口只能动态获取秘钥对,而且秘钥对本身跟endpoint是无法关联的,使用AWSCredentialsProvider实现类的方式适合在endpoint确定的前提下使用。

Hive Metastore如何实现对存储引擎的访问

当FileSystem跟Hive Metastore结合时,相比Hadoop直接使用FileSystem,问题要复杂得多,具体表现为:

1. Hive Metastore是有关元数据操作的,也会涉及到FileSystem操作

Metastore作为rpc实现对外服务,在内部除了对元数据操作外,也会涉及到对底层存储数据的操作,这会调用FileSystem相关接口,比如:

  • 当在HMS中创建Database时,会根据Database中指定的路径创建目录;

  • 当在HMS中创建表时,会根据Table中指定的路径创建数据目录;

  • 在drop database时,可能会删除Table的数据目录和文件;

  • 在drop  table时,可能会删除Table的数据目录和文件;

  • 在truncate表时,会删除表数据文件;

  • 在添加、删除分区时,会创建、删除数据分区目录;

  • 在表重命名时,可能涉及到数据存储目录的变更;

2.  创建FileSystem所需要的属性无法通过rpc接口传递

以创建表和删除表的接口实现为例:

public class HiveMetaStore extends ThriftHiveMetastore 
    @Override
    public void create_table_with_environment_context(final Table tbl)
            create_table_core(getMS(), tbl, null);    
        
    
    @Override
    public void drop_table(final String dbname, final String name, final boolean deleteData)
    throws NoSuchObjectException, MetaException 
          drop_table_with_environment_context(dbname, name, deleteData, null);

 

其中Table各属性为:

private String tableName; // required
private String dbName; // required
private String owner; // required
private int createTime; // required
private int lastAccessTime; // required
private int retention; // required
private StorageDescriptor sd; // required
private List<FieldSchema> partitionKeys; // required
private Map<String,String> parameters; // required
private String viewOriginalText; // required
private String viewExpandedText; // required
private String tableType; // required
private PrincipalPrivilegeSet privileges; // optional
private boolean temporary; // optional
private boolean rewriteEnabled; // optional
private CreationMetadata creationMetadata; // optional
private String catName; // optional
private PrincipalType ownerType; // optional

可见,虽然创建表和删除表都需要访问FileSystem,但是我们却无法从该方法中传递其需要的属性。唯一有可能传递参数的数据结构是:

private Map<String,String> parameters;

但是FileSystem并不会从这里获取,FileSystem所需要的属性从哪里来呢?

3. 创建FileSystem所需要的属性只能静态加载

HMSHandler承接了所有rpc接口的功能实现,其所需要的外部配置属性都来源于Configuration,而Configuration则在HiveMetaStore启动时候从环境变量中加载。

public class HiveMetaStore extends ThriftHiveMetastore 
    ...
    public static void main(String[] args) throws Throwable 
        final Configuration conf = MetastoreConf.newMetastoreConf();
        HiveMetastoreCli cli = new HiveMetastoreCli(conf);
        cli.parse(args);
        Properties hiveconf = cli.addHiveconfToSystemProperties();
        ...
        startMetaStore(cli.getPort(), HadoopThriftAuthBridge.getBridge(), conf, startLock,
            startCondition, startedServing)
        ...
     
     
   public static void startMetaStore(int port, HadoopThriftAuthBridge bridge,
        Configuration conf, Lock startLock, Condition startCondition,
        AtomicBoolean startedServing) throws Throwable 
        ...
        HMSHandler baseHandler = new HiveMetaStore.HMSHandler("new db based metaserver", conf,
            false);
        IHMSHandler handler = newRetryingHMSHandler(baseHandler, conf);
        ...    
    
 

上述代码表明了HiveMetaStore启动时从环境变量中查找所需的配置文件,然后启动HMSHandler rpc服务,HiveMetaStore查找的配置文件依次来源于:

hive-default.xml hive-site.xml hivemetastore-site.xml metastore-site.xml

除了通过配置文件,还可以通过conf 环境变量传给HiveMetaStore,但是一旦HiveMetaStore启动起来,这些属性也就确定了,意味着无法动态修改FileSystem所需要的配置信息。

通过配置文件配置参数的方式也是有局限性的,比如:

<property>
<name>fs.obs.impl</name>
<value>org.apache.hadoop.fs.obs.OBSFileSystem</value>
</property>
<property>
<name>fs.AbstractFileSystem.obs.impl</name>
<value>org.apache.hadoop.fs.obs.OBS</value>
</property>
<property>
<name>fs.obs.access.key</name>
<value>xxx</value>
<description>huaweicloud access key</description>
</property>
<property>
<name>fs.obs.secret.key</name>
<value>xxx</value>
<description>huaweicloud secret key</description>
</property>
<property>
<name>fs.obs.endpoint</name>
<value>xxx</value>
<description>huaweicloud endpoint</description>
</property>

因为属性名称是唯一的,同一种存储引擎,只能有一个实例,比如不能配置多个s3a存储引擎。

Hive Metastore实现动态切换存储引擎的方案

通过静态配置的方式肯定不行,那只能从接口传递参数入手,且只能从Map类型的参数入手最简单,比如下面示例中,给定Map属性和Table,将其设置到Table的表参数:

//Table tbl
Map<String, String> parameters = Optional.ofNullable(tbl.getParameters())
    .orElseGet(HashMap::new);
//Map<String, String> tableProps
tableProps.forEach((key, value) -> 
  parameters.put(key, value);
);
tbl.setParameters(parameters);
//HiveMetaStoreClient metaClients
metaClients.createTable(tbl);

这里是从API层面来看的,下面从SQL层,比如Flink SQL,如果能够将秘钥对以及endpoint属性通过with语句来创建Catalog、database以及Table,那么事情就完成了一半:

CREATE CATALOG hive_catalog WITH (
    'fs.obs.access.key' = 'xxx',
    'fs.obs.secret.key' = 'xxx',
    'fs.obs.endpoint' = 'huaweicloud endpoint'
     'uri'='thrift://localhost:9083',
     'warehouse'='s3a://tmp/warehouse/path'
);


CREATE DATABASE [IF NOT EXISTS] [catalog_name.]db_name
  [COMMENT database_comment]
  WITH (key1=val1, key2=val2, ...)


CREATE TABLE Orders (
    user BIGINT,
    product STRING,
    order_time TIMESTAMP(3)
) WITH ( 
    'fs.obs.access.key' = 'xxx',
    'fs.obs.secret.key' = 'xxx',
    'fs.obs.endpoint' = 'huaweicloud endpoint'
);

事情的另一半是在hms 接口实现端取出上述参数,传递到Configuration,然后再动态构建FileSystem及其 client。

小结

Hadoop在FileSystem API层面统一了底层存储引擎,用户可以轻易切换存储,而Hive Metastore 在Hadoop 之上,对外屏蔽了存储访问接口,通过配置文件的方式暴露交互接口,但是配置文件的方式是静态的,且对同一类型的文件系统,只能配置一个,在大数据场景下,该交互方式很受限制,本文探讨了HMS多存储引擎访问的实现方式,然后基于HMS本身如何解决动态切换存储的方案。

以上是关于Hive Metastore动态切换存储引擎方案探索的主要内容,如果未能解决你的问题,请参考以下文章

Hive 元数据服务 MetaStore

Hive 元数据服务 MetaStore

万能的Hive Metastore能存哪些类型的表?

万能的Hive Metastore能存哪些类型的表?

Hive MetaStore 在快手遇到的挑战与优化

如何使 Hive 查询利用存储在 Metastore 中的统计信息