專案背景
做的是一個醫療資料分析專案,在此之前專案其實已經開發完成並且執行一段時間了,但是因為專案的體驗極差以及對開發人員很不友好,與領導商議對專案進行重構
專案邏輯
這個專案完全是一個分析性專案,大致的邏輯就是從病歷庫中計算資料到當前專案庫中,然後從當前庫查詢展示到前臺,主要涉及到資料排名、查詢。
資料量:百萬級
技術選型
框架:本來打算用的springboot進行重構,因為公司整體的專案都在向java語言轉移。但是因為當時業務部門對時間卡的很緊,考慮到這個專案跟語言的關係也不是很大,決定使用hyperf進行快速開發。
(因為以前的版本是用的laravel框架,可以很效率的轉移到hyperf)
資料庫:之前用的是mysql,但是計算和查詢的速度都是不盡人意。因為不是直接的業務資料庫,於是決定選用OLAP型資料庫搭建數倉。最後在clickhouse和Dorisdb中選擇了Dorisdb
數倉搭建
當前專案的計算資料全部基於病歷資料庫,所以我們要對病歷資料庫建一個數倉,然後在這個基礎上新建幾張計算結果的表就可以了,我們平臺業務的要求是月更一次,所以對資料實時性的要求很低,但是後面上線之後我們改成了T+1更新模式。
資料同步
因為對實時性沒要求,所以採用的是kettle定時指令碼的模式,這裡不對kettle做過多介紹,其他類似產品還有datax、canal
數倉使用
數倉的具體使用每個產品也都有自己的特點,不做過多闡述。這裡具體說下Dorisdb,他本身的語法和mysql極度相似,所以好多sql都是通用的,像laravel、hyperf這些框架的model也是直接支援使用的。這也是當時選用這個資料庫的原因之一。其他單獨特性自行去官網瞭解
計算資料
到這一步才算把swoole的優勢發揮出來,dorisdb本身也比mysql快,但是框架也起到了很大的作用
直接貼結果,前後計算時間對比:
60w病歷資料(彙總欄位500個左右) + 100w+隨訪資料時間參考:
老版(laravel + mysql) 50小時左右
新版(hyperf + dorisdb) 開啟多協程(50個) 40分鐘左右
新版(hyperf + dorisdb) 一個協程 5個小時
訪問速度
在老版中最慢的一個介面需要2min,在更新後介面都能達到秒級
ab測試結果因為老專案停運了當時結果也沒儲存,所以就不貼出來了
訪問速度整體看來,我認為作用是數倉 > 框架處理的,因為之前介面慢的主要原因其實就是在於資料庫層面
注意事項
hyperf框架日常使用和laravel基本沒有太大區別,只是在執行的時候是基於一個程式的。所以直接改動程式碼可能不會像fpm框架那樣直接看到效果,推薦使用官方推薦的熱部署
輪子方面確實與laravel還有很大差距,目前基於hyperf的擴充還是很少的,但是有很多擴充在兩種框架都是通用的
中同一個客戶端的請求都是單例的,所以你不能直接更改request資料,這個問題我當時鬱悶了好久
如果有個跑資料指令碼,想啟用多個協程,一定要控制協程的數量,比如我跑資料開啟的是50個協程,那麼一旦數量達到50個後,就要等全部協程工作完成後再開啟下一輪的50個協程。這個具體的協程數量可以根據資料庫配置和伺服器配置自己定義
協程管理可以用WaitGroup來控制,用法也很簡單,swoole官方文件和hyperf文件上都有介紹
貼一段跑資料的demo
$wg = new WaitGroup();
RedisUtil::set($this->redisKey, 0);
foreach ($provinces as $province) {
for ($year = 2017; $year <= date('Y'); $year++) {
$cities = $provincesData[$province];
foreach ($cities as $city) {
// 如果協程數量達到50 掛起等待執行完成後開啟新協程
if ($wg->count() >= 50) {
$wg->wait();
}
$wg->add();
// 具體業務程式碼
RedisUtil::incrBy($this->redisKey, count($all));
SyncLog::updateLog($log_id, RedisUtil::get($this->redisKey), 0);
$this->output->writeln("同步數量:".RedisUtil::get($this->redisKey));
$wg->done();
}
}
}
部署
傳統的php專案配合nginx轉發一下目錄可能就ok了,但是swoole的http server是基於一個程式的,所以可以用nginx做一個反向代理。
因為有程式的原因,還是使用docker部署,不然還要守護這個程式,而且官方的dockerfile也為我們準備了最合適的php版本,不用額外去裝一些擴充。
關於專案重啟期間訪問失敗的問題,如果一定要專案不能停止,可以將專案分兩個埠部署兩次用nginx做負載均衡即可
這裡貼出dockerfile和部署指令碼
# Default Dockerfile
#
# @link https://www.hyperf.io
# @document https://hyperf.wiki
# @contact group@hyperf.io
# @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
FROM hyperf/hyperf:7.4-alpine-v3.11-swoole
LABEL maintainer="Hyperf Developers <group@hyperf.io>" version="1.0" license="MIT" app.name="Hyperf"
##
# ---------- env settings ----------
##
# --build-arg timezone=Asia/Shanghai
ARG timezone
ENV TIMEZONE=${timezone:-"Asia/Shanghai"} \
#APP_ENV=prod \
SCAN_CACHEABLE=(true)
# update
RUN set -ex \
# show php version and extensions
&& php -v \
&& php -m \
&& php --ri swoole \
# ---------- some config ----------
&& cd /etc/php7 \
# - config PHP
&& { \
echo "upload_max_filesize=128M"; \
echo "post_max_size=128M"; \
echo "memory_limit=-1"; \
echo "date.timezone=${TIMEZONE}"; \
} | tee conf.d/99_overrides.ini \
# - config timezone
&& ln -sf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime \
&& echo "${TIMEZONE}" > /etc/timezone \
# ---------- clear works ----------
&& rm -rf /var/cache/apk/* /tmp/* /usr/share/man \
&& echo -e "\033[42;37m Build Completed :).\033[0m\n"
WORKDIR /opt/www
# Composer Cache
# COPY ./composer.* /opt/www/
# RUN composer install --no-dev --no-scripts
COPY . /opt/www
RUN composer install --no-dev -o && php bin/hyperf.php
EXPOSE 9501
ENTRYPOINT ["php", "/opt/www/bin/hyperf.php", "start"]
# 容器名
containerName='你的專案名'
cid=$(docker ps | grep $containerName | awk '{print $1}')
if [ -n "$cid" ]
then
echo "prepare to stop $cid"
docker stop $cid
echo "stop $cid success"
echo "prepare to remove $cid"
docker rm $cid
echo "stop $cid success"
else
echo "no such container"
fi
# 映象名
oldBuild="qc_doris"
oldImageID=$(docker images | grep $oldBuild | awk '{print $3}')
if [ -n "$oldImageID" ]
then
echo "prepare to remove old image"
docker rmi $oldImageID
echo "remove $oldImageID success"
else
echo "no such image"
fi
docker build -t qc_doris:0.1 .
echo "build success"
#run
docker run -d -v $(pwd)/runtime/logs:/opt/www/runtime/logs \
-v $(pwd)/.env:/opt/www/.env \
-v $(pwd)/static:/opt/www/static \
-p 9501:9501 --privileged=true --name xxx xxx:0.1
echo "running success"
# -d:後臺執行; -p:將容器暴露埠對映到伺服器埠 imageName
總結
如果從laravel過渡到hyperf整體還是十分絲滑的,甚至你不怎麼了解swoole也可以直接使用,但是還是推薦大家把swoole官方的文件啃一遍,有助於理解框架的一些設計。使用體驗目前還不錯
專案整體重構時間加上數倉建設其實只用了一個月,去年年底上線到現在已經穩定執行了有一段時間。這次重構讓業務部門很滿意,也讓領導很滿意。這裡推薦大家去擁抱新的技術但是同時也不要為了技術而技術。一個專案開發不僅僅是IT部門的工作還要與業務有各種磨合,如果有些吃力不討好的技術非要上我覺得是完全沒有必要的。
公司因為整體專案都在用java重構,下次有時間我會講講如何將php專案用java進行重構以及遇到的各種問題和解決辦法。
本作品採用《CC 協議》,轉載必須註明作者和本文連結