淺談軟件工程中的Shim

什麼是Shim

Shim一詞的原本含義是“墊片”或者“楔子”,而首先將這個詞應用到軟件工程領域的似乎是微軟。根據Wikipedia的總結:

A shim is a library that transparently intercepts API calls and changes the arguments passed, handles the operation itself or redirects the operation elsewhere. Shims can be used to support an old API in a newer environment, or a new API in an older environment. Shims can also be used for running programs on different software platforms than they were developed for.

按照這個釋義,Shim是一種(小型的)庫,負責透明地攔截API調用,並更改其參數,或將其轉發至其他組件,或者自行處理。它用於在新環境中支持舊API(或反過來),以及使程序能夠在非特定支持的平臺上運行。

熟悉Docker的看官可能會立即想起該體系中的containerd-shim組件,它作爲containerd與容器運行時交互的中間層發揮作用,並且符合上文的釋義。

具體到不同API版本這一方面的話,我們可以參考一下Flink(當然其他很多開源組件也同理)的設計思路。

Flink Hive Catalog中的Shims

我們知道,Flink 1.14支持從1.0.0~3.1.2各版本的Hive,而橫跨這麼多版本的Hive API底層邏輯勢必不會完全一致,如果將全部版本的Hive依賴都引入進來,也一定會造成衝突,這裏就需要Shims發揮作用。在代碼中,HiveShim是一個接口,定義了所有需要做兼容性支持的方法,如下圖所示。

該接口的實現類就從HiveShimV100一直命名至HiveShimV312,每個類都會override對應版本出現變更的方法。例如,alterPartition()方法對應1.0.0版本的實現是:

    @Override
    public void alterPartition(
            IMetaStoreClient client, String databaseName, String tableName, Partition partition)
            throws InvalidOperationException, MetaException, TException {
        String errorMsg = "Failed to alter partition for table %s in database %s";
        try {
            Method method =
                    client.getClass()
                            .getMethod(
                                    "alter_partition", String.class, String.class, Partition.class);
            method.invoke(client, databaseName, tableName, partition);
        } catch (InvocationTargetException ite) {
            // ...
        } catch (NoSuchMethodException | IllegalAccessException e) {
            // ...
        }
    }

而由於Hive Metastore提供的API alter_partition() 方法的簽名發生了變化,對應2.1.0版本的實現是:

    @Override
    public void alterPartition(
            IMetaStoreClient client, String databaseName, String tableName, Partition partition)
            throws InvalidOperationException, MetaException, TException {
        String errorMsg = "Failed to alter partition for table %s in database %s";
        try {
            Method method =
                    client.getClass()
                            .getMethod(
                                    "alter_partition",
                                    String.class,
                                    String.class,
                                    Partition.class,
                                    EnvironmentContext.class);
            method.invoke(client, databaseName, tableName, partition, null);
        } catch (InvocationTargetException ite) {
            // ...
        } catch (NoSuchMethodException | IllegalAccessException e) {
            // ...
        }
    }

顯然,由於Flink環境並不能事先確定外部Hive的版本,所以全部的Shim方法都需要依賴反射調用。另外,爲了保持向後兼容性,Shim實現類從低版本到高版本會自然形成鏈式繼承關係,如下圖所示。

在Flink App啓動時,HiveShimLoader組件會根據Catalog定義時傳入的hive-version參數或者自動探測到的Hive版本加載特定的HiveShim,不再贅述。

不用Shim的場景?

在Flink Connector體系內也能找到這類場景,如ES Connector就使用了3個不同的module來實現:flink-connector-elasticsearch5flink-connector-elasticsearch6flink-connector-elasticsearch7

造成這種不同選擇的原因有二:一是ES版本並不像Hive版本那麼細分,即使6.x和7.x之間有大量的重複代碼也在可接受的範圍內;二是5.x採用Transport Client進行通信,而6.x和7.x採用REST High Level Client進行通信,相當於失去了統一的接口規約(如Hive的IMetaStoreClient那樣),再使用反射調用會更加複雜,得不償失。

Iceberg基於同樣的考慮,在0.13版本之後也對Spark和Flink做了非Shim化的支持,看官可以去GitHub看看。

vs 適配器模式?

在筆者看來,Shim是適配器模式的一種(另闢蹊徑的)實現方式。參考GoF對適配器模式的解說:

The adapter design pattern solves problems like:

  • How can a class be reused that does not have an interface that a client requires?
  • How can classes that have incompatible interfaces work together?
  • How can an alternative interface be provided for a class?

The adapter design pattern describes how to solve such problems:

  • Define a separate adapter class that converts the (incompatible) interface of a class (adaptee) into another interface (target) clients require.
  • Work through an adapter to work with (reuse) classes that do not have the required interface.

用上文的例子套用這個定義,HiveShim就是適配器本體,而Hive的原生API就是被適配者(adaptee),各個不同版本的HiveShim實現的方法就是目標接口(target)。一家之言,僅供參考。

The End

晚安晚安。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章