什么是Shim
Shim一詞的原本含義是“墊片”或者“楔子”艇抠,而首先將這個(gè)詞應(yīng)用到軟件工程領(lǐng)域的似乎是微軟漆魔。根據(jù)Wikipedia的總結(jié):
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.
按照這個(gè)釋義互躬,Shim是一種(小型的)庫(kù)耍共,負(fù)責(zé)透明地?cái)r截API調(diào)用苗傅,并更改其參數(shù)滑潘,或?qū)⑵滢D(zhuǎn)發(fā)至其他組件,或者自行處理查近。它用于在新環(huán)境中支持舊API(或反過(guò)來(lái))眉踱,以及使程序能夠在非特定支持的平臺(tái)上運(yùn)行。下面舉兩個(gè)栗子簡(jiǎn)單說(shuō)明下Shim在不同場(chǎng)景下的實(shí)現(xiàn)霜威。
Kubernetes中的Shim
熟悉K8s的看官可能會(huì)立即想起該體系中的containerd-shim組件谈喳,它作為containerd與OCI運(yùn)行時(shí)交互的中間層發(fā)揮作用。
展開來(lái)講戈泼,當(dāng)Kubelet請(qǐng)求containerd來(lái)創(chuàng)建容器時(shí)婿禽,并不會(huì)由containerd直接來(lái)操作,而是創(chuàng)建一個(gè)叫做containerd-shim的進(jìn)程大猛,且containerd-shim會(huì)進(jìn)一步啟動(dòng)containerd-shim-runc-v2進(jìn)程并立即退出扭倾。這樣,containerd-shim-runc-v2就和containerd脫離了關(guān)系(因?yàn)閏ontainerd-shim-runc-v2的父進(jìn)程此時(shí)為systemd)挽绩,而通過(guò)runc啟動(dòng)的容器進(jìn)程父進(jìn)程為containerd-shim-runc-v2膛壹,負(fù)責(zé)后續(xù)接管容器內(nèi)虛擬OS的管理和狀態(tài)上報(bào)。
也就是說(shuō)唉堪,即使containerd守護(hù)進(jìn)程異常退出模聋,容器也不會(huì)受影響,將兩者解耦開來(lái)就是containerd-shim最重要的作用巨坊。
Flink Hive Catalog中的Shim
Shim支持不同API版本這一方面的話撬槽,我們可以參考一下Flink(當(dāng)然其他很多開源組件也同理)的設(shè)計(jì)思路。
我們知道趾撵,F(xiàn)link 1.14支持從1.0.0~3.1.2各版本的Hive侄柔,而橫跨這么多版本的Hive API底層邏輯勢(shì)必不會(huì)完全一致共啃,如果將全部版本的Hive依賴都引入進(jìn)來(lái),也一定會(huì)造成沖突暂题,這里就需要Shims發(fā)揮作用移剪。在代碼中,HiveShim
是一個(gè)接口薪者,定義了所有需要做兼容性支持的方法纵苛,如下圖所示。
該接口的實(shí)現(xiàn)類就從HiveShimV100
一直命名至HiveShimV312
言津,每個(gè)類都會(huì)override對(duì)應(yīng)版本出現(xiàn)變更的方法攻人。例如,alterPartition()
方法對(duì)應(yīng)1.0.0版本的實(shí)現(xiàn)是:
@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()
方法的簽名發(fā)生了變化悬槽,對(duì)應(yīng)2.1.0版本的實(shí)現(xiàn)是:
@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環(huán)境并不能事先確定外部Hive的版本,所以全部的Shim方法都需要依賴反射調(diào)用初婆。另外蓬坡,為了保持向后兼容性,Shim實(shí)現(xiàn)類從低版本到高版本會(huì)自然形成鏈?zhǔn)嚼^承關(guān)系磅叛,如下圖所示屑咳。
在Flink App啟動(dòng)時(shí),HiveShimLoader
組件會(huì)根據(jù)Catalog定義時(shí)傳入的hive-version
參數(shù)或者自動(dòng)探測(cè)到的Hive版本加載特定的HiveShim
弊琴,不再贅述兆龙。
不用Shim的場(chǎng)景?
在Flink Connector體系內(nèi)也能找到這類場(chǎng)景访雪,如ES Connector就使用了3個(gè)不同的module來(lái)實(shí)現(xiàn):flink-connector-elasticsearch5
详瑞、flink-connector-elasticsearch6
和flink-connector-elasticsearch7
:
造成這種不同選擇的原因有二:一是ES版本并不像Hive版本那么細(xì)分,即使6.x和7.x之間有大量的重復(fù)代碼也在可接受的范圍內(nèi)臣缀;二是5.x采用Transport Client進(jìn)行通信,而6.x和7.x采用REST High Level Client進(jìn)行通信泻帮,相當(dāng)于破壞了統(tǒng)一的接口規(guī)約(統(tǒng)一接口的示例如上文中Hive的IMetaStoreClient
)精置,在這基礎(chǔ)上再使用反射調(diào)用會(huì)更加復(fù)雜,得不償失锣杂。
Iceberg基于同樣的考慮脂倦,在0.13版本之后也對(duì)Spark和Flink做了非Shim化的支持,看官可以去GitHub看看元莫。
vs 適配器模式赖阻?
在筆者看來(lái),Shim是適配器模式的一種另辟蹊徑的實(shí)現(xiàn)方式踱蠢。參考GoF對(duì)適配器模式的解說(shuō):
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.
用上文HiveCatalog
的例子套用這個(gè)定義火欧,HiveShim
就是適配器本體棋电,而Hive的原生API就是被適配者(adaptee),各個(gè)不同版本的HiveShim
實(shí)現(xiàn)的方法就是目標(biāo)接口(target)苇侵。一家之言赶盔,僅供參考。
The End
晚安晚安榆浓。