kylin与superset集成实现数据可视化
原文地址:http://minirick.duapp.com/kylinyu-supersetji-cheng-shi-xian-shu-ju-ke-shi-hua/?utm_source=tuicool&utm_medium=referral
apache kylin是一個(gè)開(kāi)源分布式引擎,提供Hadoop之上的SQL查詢(xún)接口及多維分析(OLAP)能力以支持超大規(guī)模數(shù)據(jù)。而superset是airbnb開(kāi)源的一款數(shù)據(jù)可視化工具。
kylin在超大數(shù)據(jù)規(guī)模下仍然可以提供秒級(jí)甚至毫秒級(jí)sql響應(yīng)的OLAP多維分析查詢(xún)服務(wù)。而且對(duì)服務(wù)器內(nèi)存的要求也不像spark sql那么高,經(jīng)過(guò)多方面的優(yōu)化,數(shù)據(jù)膨脹率甚至可以控制在100%以?xún)?nèi)。它利用hive做預(yù)計(jì)算,然后建立多維的數(shù)據(jù)立方體,并存在hbase中,從而提供了實(shí)時(shí)查詢(xún)的能力。
superset也就是早先的caravel,提供了豐富的圖表供用戶(hù)配置。只要連上數(shù)據(jù)源,勾幾個(gè)簡(jiǎn)單的配置,或者寫(xiě)點(diǎn)sql。用戶(hù)就可以輕易的構(gòu)建基于d3、nvd3、mapbox-gl等的炫酷圖表。
superset
我廠(chǎng)也是選擇了kylin和superset,遺憾的是superset支持多種數(shù)據(jù)源,包括druid、hive、impala、sparkSQL、presto以及多種主流關(guān)系型數(shù)據(jù)庫(kù),但是并不支持kylin。于是我們對(duì)其進(jìn)行了改進(jìn)。
首先觀(guān)察superset的源碼,它后臺(tái)使用Flask App Builder搭建的,數(shù)據(jù)訪(fǎng)問(wèn)層用sqlalchemy實(shí)現(xiàn)。也就是說(shuō),它本質(zhì)上可以支持所有數(shù)據(jù)源,只要實(shí)現(xiàn)一套kylin的dialect即可。而同時(shí)github上有一個(gè)pykylin項(xiàng)目,就是實(shí)現(xiàn)的這個(gè)dialect。這極大增強(qiáng)了我解決這個(gè)問(wèn)題的信心。
正好前幾周,superset出了一個(gè)新的prod版本airbnb_prod.0.15.5.0。裝好它和pykylin之后,導(dǎo)入kylin數(shù)據(jù)源,成功! 導(dǎo)入kylin數(shù)據(jù)源 但是點(diǎn)開(kāi)sqllab想敲點(diǎn)sql驗(yàn)證一下時(shí),卻出了異常。 異常 Debug了pykylin代碼,發(fā)現(xiàn)get_table_names方法的入?yún)onnection實(shí)際已經(jīng)是sqlalchemy的Engine對(duì)象了,這可能是最新sqlalchemy的版本升級(jí)造成的。總之,將原來(lái)的代碼:
def get_table_names(self, connection, schema=None, **kw):
return connection.connection.list_tables()
改成:
def get_table_names(self, engine, schema=None, **kw):
connection = engine.contextual_connect()
return connection.connection.list_tables()
即可。
順便我們看到這里它擴(kuò)展了sqlalchemy的list_tables方法,sqllab左上方的table選擇區(qū)還有列出所有schema的下拉框,于是我們順帶把list_schama方法也實(shí)現(xiàn)。connection.py添加:
def list_schemas(self):
route = ‘tables_and_columns’
params = {‘project’: self.project}
tables = self.proxy.get(route, params=params)
return [t[‘table_SCHEM’] for t in tables]
dialect.py添加:
def get_schema_names(self, engine, schema=None, **kw):
connection = engine.contextual_connect()
return connection.connection.list_schemas()
之后執(zhí)行sql還是有錯(cuò): 異常 pykylin在每次調(diào)用kylin的api時(shí)會(huì)首先登錄,以獲得JSESSIONID,并存入cookie中,這里是登錄失敗,檢查代碼,發(fā)現(xiàn)這里問(wèn)題還挺多的,首先proxy.py中的login方法作者寫(xiě)的是self.password = user應(yīng)改成password。dialect.py中create_connect_args方法改為:
def create_connect_args(self, url):
opts = url.translate_connect_args()
api_prefix = ‘kylin/api/’
args = {
‘username’: opts[‘username’],
‘password’: opts[‘password’],
‘endpoint’: ‘http://%s:%s/%s’ % (opts[‘host’], opts[‘port’], api_prefix)
}
args.update(url.query)
return [], args
這樣大部分sql查詢(xún)沒(méi)有問(wèn)題,但是有的查詢(xún)結(jié)果有部分值是null,這樣也會(huì)出錯(cuò)。修改cursor.py的_type_mapped方法:
def _type_mapped(self, result):
meta = self.description
size = len(meta)
for i in range(0, size):
column = meta[i]
tpe = column[1]
val = result[i]
if val is None:
pass
elif tpe == ‘DATE’:
val = parser.parse(val)
elif tpe == ‘BIGINT’ or tpe == ‘INT’ or tpe == ‘TINYINT’:
val = int(val)
elif tpe == ‘DOUBLE’ or tpe == ‘FLOAT’:
val = float(val)
elif tpe == ‘BOOLEAN’:
val = (val == ‘true’)
result[i] = val
return result
這樣在sqllab中執(zhí)行sql基本沒(méi)問(wèn)題了。 sqllab
下一步開(kāi)始自定義slice,定制自己的可視化dashboard。
在這里再次遇到問(wèn)題,superset會(huì)自動(dòng)把count函數(shù)計(jì)算的列設(shè)置別名叫count,而count是kylin的關(guān)鍵字,因此導(dǎo)致查找失敗。修改superset的models.py的sqla_col方法:
@property def sqla_col(self):name = self.metric_nameif name == 'count':name = 'total_count'return literal_column(self.expression).label(name)另外在slice中還經(jīng)常會(huì)遇到pandas拋出的KeyError異常。這是因?yàn)樵趕uperset里面所有的關(guān)鍵字都是小寫(xiě),然而kylin返回的所有的數(shù)據(jù)metadata全是大寫(xiě),導(dǎo)致superset在kylin的返回結(jié)果中查詢(xún)關(guān)鍵字的時(shí)候出現(xiàn)找不到關(guān)鍵字的錯(cuò)誤。
修改pykylin的cursor.py的execute方法。
def execute(self, operation, parameters={}, acceptPartial=True, limit=None, offset=0):
sql = operation % parameters
data = {
‘sql’: sql,
‘offset’: offset,
‘limit’: limit or self.connection.limit,
‘a(chǎn)cceptPartial’: acceptPartial,
‘project’: self.connection.project
}
logger.debug(“QUERY KYLIN: %s” % sql)
resp = self.connection.proxy.post(‘query’, json=data)
最后,我發(fā)現(xiàn)在查找的字段包含kylin中的date類(lèi)型時(shí)也會(huì)出錯(cuò)。點(diǎn)擊slice頁(yè)面右上角的query按鈕,可以查看superset最終發(fā)送的sql。 kylin不支持的長(zhǎng)日期格式 將它直接拷貝到kylin的insight頁(yè)面去執(zhí)行,發(fā)現(xiàn)報(bào)錯(cuò)。原來(lái)kylin的date類(lèi)型只支持年月日,而superset在添加日期搜索條件時(shí)為了實(shí)現(xiàn)定時(shí)刷新圖表而在sql的日期條件中都精確到了時(shí)分秒。關(guān)于這個(gè)我原先是在superset中做了修改。
在superset的models.py的get_query_str方法中:
time_filter = dttm_col.get_time_filter(from_dttm, to_dttm)
改為
if engine.name == ‘kylin’:
time_filter = dttm_col.get_date_filter(from_dttm, to_dttm)
else:
time_filter = dttm_col.get_time_filter(from_dttm, to_dttm)
添加get_date_filter,dt_sql_literal函數(shù):
def get_date_filter(self, start_dttm, end_dttm):
col = self.sqla_col.label(‘__time’)
return and_(
col >= text(self.dt_sql_literal(start_dttm)),
col <= text(self.dt_sql_literal(end_dttm)),
)
def dt_sql_literal(self, dttm):
return “’{}’”.format(dttm.strftime(‘%Y-%m-%d’))
這樣對(duì)于所有kylin數(shù)據(jù)源的查找時(shí)間范圍條件都將轉(zhuǎn)為年月日的格式。
不過(guò)我一直感覺(jué)這個(gè)改動(dòng)不是很完美,是一種典型的打補(bǔ)丁的做法。后來(lái)我發(fā)現(xiàn)superset支持在列的設(shè)置頁(yè)面為一個(gè)日期列添加自定義的格式轉(zhuǎn)換函數(shù) superset日期轉(zhuǎn)換函數(shù)設(shè)置 于是我在這里設(shè)置日期列格式
TO_DATE(‘{}’, ‘yyyy-MM-dd’)
然后可以看到slice這里sql中的該列都變成了to_date函數(shù)形式 to_date sql 最后的工作就是修改kylin源碼,添加對(duì)日期函數(shù)的支持。hive sql是支持to_date等日期格式轉(zhuǎn)換函數(shù)的,kylin憑什么不支持?
大致debug了一下kylin的源碼,kylin處理sql的入口在server-base模塊下的QueryController.java的query方法中。我發(fā)現(xiàn)在最終調(diào)用jdbc驅(qū)動(dòng)執(zhí)行sql之前,kylin會(huì)調(diào)QueryUtil類(lèi)的massageSql方法來(lái)優(yōu)化sql。主要是加上limit和offset參數(shù)。最后調(diào)內(nèi)部類(lèi)DefaultQueryTransformer的transform方法改掉sql中的一些通病,比如SUM(1)改成count(1)等。日期轉(zhuǎn)換函數(shù)的處理放在這后面我覺(jué)得是最合適的。
添加正則表達(dá)式,以匹配日期函數(shù):
private static final String TO_DATE = “(to_date|TO_DATE)\(‘|\”[‘|\”],\s?‘|\”[‘|\”]\)”;
private static final Pattern FN_TO_DATE = Pattern.compile(TO_DATE);
添加日期轉(zhuǎn)行函數(shù)解析:
private String executeFN(String sql) {
Matcher m;
while (true) {
m = FN_TO_DATE.matcher(sql);
if (!m.find())
break;
String dateTime = m.group(2);
String format = m.group(3);
SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);
Date dt = null;
try {
dt = sdf.parse(dateTime);
} catch (ParseException e) {
logger.error(“Parse date error”, e);
}
sdf = new SimpleDateFormat(format);
String date = sdf.format(dt);
String begin = sql.substring(0, m.start());
String end = sql.substring(m.end(), sql.length());
sql = begin + “’” + date + “’” + end;
}
return sql;
}
然后kylin就可以支持上面sql的執(zhí)行了 kylin支持to_date 最后,讓我們多嘗試一些可視化圖表吧,把它們做成dashboard superset dashboard
結(jié)論:kylin很好地支持了我廠(chǎng)每天上百GB數(shù)據(jù)的立方體建模和實(shí)時(shí)查找,結(jié)合superset的方案,作為我廠(chǎng)內(nèi)部的可視化工具,收到了很好地效果。
總結(jié)
以上是生活随笔為你收集整理的kylin与superset集成实现数据可视化的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: android layout(l, t,
- 下一篇: java中Decimaformat_Ja