使用hyperf结合DorisDB数仓的项目实战体验

项目背景

做的是一个医疗数据分析项目,在此之前项目其实已经开发完成并且运行一段时间了,但是因为项目的体验极差以及对开发人员很不友好,与领导商议对项目进行重构

项目逻辑

这个项目完全是一个分析性项目,大致的逻辑就是从病历库中计算数据到当前项目库中,然后从当前库查询展示到前台,主要涉及到数据排名、查询。
数据量:百万级

技术选型

框架:本来打算用的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 协议》,转载必须注明作者和本文链接
本帖由系统于 1年前 自动加精
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 14
laravel_peng

file

摘自你的文章:访问速度整体看来,我认为作用是数仓 > 框架处理的,因为之前接口慢的主要原因其实就是在于数据库层面

看样子在处理大量数据汇总方面,hyperf + dorisdb 确实快很多。其实我有些疑问?

  1. 如果使用 hyperf 框架,但协程只设置为一个,和正常使用 Laravel 框架执行效果会不会一样?
  2. 如果上面的猜想能够成立的话,是否可以理解是 dorisdb 数据库让你在处理大量数据汇总上速度提升了 10 倍。
  3. 还有一点就是跑脚本 50 个小时这个,你们真的试过了么(感觉这个耗时好长啊)?还是说是预测的时间。
1年前 评论
卖蛋饼等你下课 (楼主) 1年前
卖蛋饼等你下课 (楼主) 1年前
李铭昕

厉害👍🏻

1年前 评论

为什么社区不添加一个hyperf 版块

1年前 评论
黑将军 1年前

Doris和MySQL的表结构是怎么同步的呢?手动创建吗

1年前 评论
卖蛋饼等你下课 (楼主) 1年前

hyperf 怎么配置Dorisdb的,还有模型那边是怎么对数据做查询等等的呀,楼主没分享下

1年前 评论
卖蛋饼等你下课 (楼主) 1年前

楼主大大你好,我这边用model直接写查询语句,总是提示不支持命令。即使是在selelect里面加上了查询的字段,没用用*也会这样。想问一下这个地方有没有什么特殊的配置需要变更或者新增

1年前 评论
卖蛋饼等你下课 (楼主) 1年前
卖蛋饼等你下课 (楼主) 1年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!