一路繁花似锦绣前程
失败的越多,成功才越有价值

导航

 

二十四、自研统计服务,使作品能实现分渠道统计

1、自研统计服务EAS
* 引言
    - 自定义事件统计,是一个线上产品必备的统计功能。目前市面上没有合适的第三方服务,
      干脆我们就自研一个。包括收集日志、分析日志、Open API功能。
* 标题
    - Event Analytics Server事件统计服务--为了实现分渠道统计
* 将收获什么
    - nginx收集统计日志
    - 定时分析日志结果,定时清理日志文件
    - 提供OpenAPI
* 主要内容
    - 技术方案设计
    - 日志收集、拆分和定期清理
    - 分析日志,得到统计结果
    - 提供Open API服务
* 关键词
    - 统计服务
    - nginx
    - 定时任务
* 学习方法
    - 要先熟悉业务需求,否则可能不知道这一周在干嘛
    - 如果此前不了解nginx,不要陷入nginx的细节学习
* 注意事项
    - 像这种比较专业的服务,尽量使用靠谱的第三方(哪怕花点钱),自研是下策
    - 如果某一块感觉看不懂,记录下,然后继续往下学习。等学完本章,再统一复习--特别
      是没有接触过统计服务的同学
    - 我们要做的是一个能满足大众需求的自定义事件统计服务,而不仅仅是为分渠道统计服务的--具备
      通用性和扩展性
2、技术方案设计
* 引言
    - 需求指导设计,设计指导开发
* 主要产出
    - 《技术方案设计》文档
* 主要内容
    - 回顾:需求,各个项目之间的关系,url设计
    - 技术方案设计,写出文档
* 注意事项
    - 温故知新,不要拒绝回顾前面的内容
3、回顾
* 需求:分渠道统计的重要性
* 多个项目之间的关系
* url设计:channel参数
4、技术方案设计
* 需求
    - 分渠道统计
* 第三方服务
    - 没有可靠的第三方
        ~ 百度统计,免费的不可用,需要开通一个付费服务,每年3w元左右,价格太贵。
        ~ 友盟,web统计不支持自定义事件和Open API。
        ~ 腾讯移动分析,支持自定义事件上报,但Open API里没有提到自定义事件。
        ~ 阿里arms,不支持自定义事件的Open API获取。
* 主要流程
* 如何实现
    - nginx收集日志
        ~ nginx提供一个web server,返回一个小图片,如http://localhost:8083/event.png
            简单,无论是server还是h5
            没有跨域
            小图片,小流量
        ~ 访问请求,nginx会自动记录access_log
    - 定时任务
        ~ 用于
            定制拆分access_log,视流量大小,可以按天、小时、分钟来拆分
            定时分析计算日志结果,例如每天凌晨4:00,分析昨天的日志
            定时清理历史日志文件,例如
        ~ 技术
            linux自带的crontab定时任务
            npm插件node-cron或node-schedule
    - Open API
        ~ OpenAPI用nodejs服务即可
* 安全和运维
    - 日志服务对硬盘要求高
        ~ 购买服务器时,多考虑硬盘空间
        ~ 运维监控时,考虑硬盘监控(如达到80%报警)--ali-node有这个功能,后面会讲
        ~ 及时清理历史日志
    - 安全
        ~ OpenAPI设置CORS,只允许特定的host访问。
        ~ 日志收集,可能会被恶意灌入数据,这些靠阿里云防火墙。
* 小结
    - 需求背景&没有靠谱的第三方服务
    - 主要流程
    - 如何实现
    - 安全和运维
    - 最后,写文档《自研统计服务-技术方案设计》
5、nginx收集日志
* 引言
    - nginx已发布很多年,目前依然是必备工具,因其高效稳定。
    - 提供静态服务(如CDN,统计打点服务),nginx是无二的选择。
* 主要产出
    - nginx服务:收集日志
    - 定时任务:拆分日志和清理历史日志
* 主要内容
    - nginx安装和配置
    - access_log日志拆分
    - 定时清理历史日志文件
* 注意事项
    - 不要深入nginx的细节,能满足本章学习即可
    - nginx配置代码,也要记录到git仓库中
6、nginx安装和配置
* 引言
    - nginx的核心价值
        ~ 高性能的静态服务--本章用到
        ~ 反向代理--后面再讲
        ~ 负载均衡--暂时用不到
* 安装和使用
    - 安装
        ~ 自行安装,安装完运行nginx -v可以看到当前版本。
        ~ 【注意】测试机centOS系统,使用root权限(或su sudo)来安装。
    - 使用
        ~ 常用命令
            nginx -t测试配置文件语法(查看配置文件位置)
            nginx启动
            nginx -s reload重启
            nginx -s stop停止
            nginx -c xxx.conf修改配置文件位置
            【注意】测试机centOS系统,需使用root权限(或su sudo)执行nginx命令。
* 基本配置
########
http {
    include         mime.types;
    default_type    application/octet-stream;
    
    log_format  main    '$remote_addr - $remote_user [$time_local] "$request" '
                        '$status $body_bytes_sent "$http_referer" '
                        '"$http_user_agent" "$http_x_forwarded_for"';
                        
    access_log  logs/access.log main;
    error_log   logs/error.log;
    
    # 一个服务
    server {
        listen      8083;
        server_name event_analytics;
        access_log  /Users/wfp/nginx_logs/event_analytics/access.log    main;
        error_log   /Users/wfp/nginx_logs/event_analytics/error.log;
        root        /Users/wfp/xxx/code/event-analytics-server/nginx_files; # 该目录有静态文件
    }
    
    ## 另一个服务
    server {
        listen      8088;
        server_name another_server;
        root        /xxx/xxx; # 其他的静态文件
        # 没单独定义access_log和error_log,就会默认使用上面的配置,存放再logs/目录下
    }
    
    ## 再扩展其他服务
}
########
* include
    - nginx配置文件只有一个,而且目录一般是固定的(虽然可以nginx -c xxx.conf配置,但也不会频繁修改)。
      当服务过多时,讲配置拆分开,通过include正在一起,这样更加符合设计模式。
########
http {
    # 和上述配置一样,不在赘述
    
    ## 一个服务
    server {
        listen      8088;
        server_name another_server;
        root        /xxx/xxx; # 其他的静态文件
    }
    
    ## 引入其他配置,即该目录下的所有配置文件
    include /Users/wfp/xxx/code/event-analytics-server/nginx_conf/dev/*;
    include /xxx/xxx/*;
}
########
    - /Users/wfp/xxx/code/event-analytics-server/nginx_conf/dev/event.conf文件的内容
########
server {
    listen      8083;
    server_name event_analytics;
    access_log  /Users/wfp/nginx_logs/event_analytics/access.log    main;
    error_log   /Users/wfp/nginx_logs/event_analytics/error.log;
    root        /Users/wfp/xxx/code/event-analytics-server/nginx_files;
}
########
* 实现演练
    - 新建nginx_files和nginx_conf目录
    - 新建access_log和error_log目录(access_log非常重要,要单独存放)
    - 配置nginx,include nginx_conf文件,并重启
    - 访问http://localhost:8083/event.png,并查看access_log
* 小结
    - nginx安装和使用
    - nginx配置和include
    - 实战演练
    - 【思考】使用include分离nginx配置,利于将nginx配置存储到git仓库,不易丢失,还方便CI/CD。
7、access_log日志拆分
* 为何要拆分日志
    - access.log日志默认不会拆分,会越积累越多,大文件不易操作。
    - 离线分析日志、计算统计结果,一般是计算昨天的。
    - 试想这种场景下,从access.log一个大文件读取方便?还是拆分的零散的文件方便?
* 如何拆分日志
    - 视流量情况(流量越大日志文件积累的越快),按天、小时、分钟来拆分。
    - 例如,将access.log按天拆分到某个文件夹中。
########
logs_by_day/access.2021-03-09.log
logs_by_day/access.2021-03-10.log
logs_by_day/access.2021-03-11.log
########
    - 拆分日志的技术实现
        ~ nginx依然是持续的写入access.log
        ~ 启动定时任务,到凌晨00:00,即将当前的access.log复制一份,
          并命名为access.2021-03-11.log(昨天的日期)
        ~ 将当前的access.log内容清空。这样nginx持续写入access.log,即新一天的日志
* 定时任务
########
/**
    通用的定时表达式规则:
    *   *   *   *   *   *
    |   |   |   |   |   |
    |   |   |   |   |   ----day of week(0 - 7)(0 or 7 is Sun)
    |   |   |   |   --------month(1 - 12)
    |   |   |   ------------day of month(1 - 31)
    |   |   ----------------hour(0 - 23)
    |   --------------------minute(0 - 59)
    ------------------------second(0 - 59,OPTIONAL) **【注意】linux crontab不支持秒**
 */
########
* 了解linux crontab
    - 登录测试机,在~目录实验。
        ~ 创建一个空文件a.txt
        ~ 执行crontab -e编辑
        ~ 加入一行* * * * * echo $(date) >> /Users/wfp/a.txt,每分钟执行一次
          (注意文件目录,不一定和我的一样)
        ~ 保存并退出
        ~ 观察文件
    - 测试完成,记得删除crontab配置和a.txt文件。
* nodejs工具
    - npm插件node-cron或node-schedule,选择前者。
        ~ 安装cron和fs-extra
        ~ 新建demo/cron-demo.js文件
        ~ 运行该文件
* 代码演示
    - analysis/index.js定义函数出口
    - analysis/split-log-file/index.js实现拆分功能,还有其需要的config和util
    - app.js作为启动运行
    - package.json中定义scripts:npm run dev
* 总结
    - 为何要拆分日志文件
    - 如何拆分日志文件
    - 定时任务
        ~ linux crontab
        ~ node-cron
    - 代码演示
* 注意
    - 到最后,这就是一个koa2项目环境,目前还不需要koa2的服务。
        ~ app.js会变成koa2的样子
        ~ scripts.dev也会使用bin/www来启动
8、分析日志
* 引言
    - 分析日志,得到统计结果,并写入数据库
* 主要产出
    - 技术方案设计文档
    - 定时任务:日志分析,计算统计结果,写入数据库
* 主要内容
    - 技术方案设计
    - 定时分析日志,结果写入数据库
    - 单元测试
* 注意事项
    - 注意url参数设计,和存储数据结构的设计
    - 一定要使用readline(即stream),日志文件可能很大,一次性打开可能占据很大内存
    - 考虑通用性和扩展性
9、技术方案设计
* 要做一个通用的自定义事件统计服务,而不仅仅是为了作品分渠道统计
    - 需求是什么
    - 需要收集哪些信息
    - 需要输出什么信息
    - 如何做到
* 需求是什么
    - 分渠道统计。例如,一个h5页,一段时间之内
        ~ 总 pv 100
        ~ 渠道 A pv 30
        ~ 渠道 B pv 20
        ~ 渠道 C pv 50
* 收集统计数据
    - 收集哪些数据?
        ~ 以一个作品http://182.92.168.192:8082/p/85-8d14?channel=41为例子,需要收集
            作品id85
            作品渠道id41
        ~ 根据nginx收集日志的服务,通过http://localhost:8083/event.png?xxxx可以上报统计数据
          (目前服务在本地,以后会部署到测试机)那么上报数据应该是
          http://localhost:8083/event.png?workId=85&channelId=41--但不能这样做!!!
    - 如何收集
        ~ 要做一个通用的自定义事件统计服务,而不仅仅是为了作品分渠道统计。所以,不能按照分渠道
          统计的需求来设计参数。按照通用的参数设计,可以定义四个参数(很多企业内部的事件统计服务,
          都是这么定义的)
            category
            action
            label
            value
        ~ 分渠道统计http://localhost:8083/event.png?category=h5&action=pv&label=85&value=41
        ~ 如果再有新需求,有两个按钮“参与”和“不参与”,想统计一下用户点击了哪个按钮
            点击“参与”则发送?category=h5&action=someButton&label=85&value=1
            点击“不参与”则发送?category=h5&action=someButton&label=85&value=0
* 计算结果
    - 按天计算。
    - 如何计算
        ~ 离线计算,每天凌晨定时分析昨天的日志,计算出昨天的统计结果。
        ~ 离线计算,要晚于拆分日志(凌晨0:00),拆分完再计算。
            根据日志文件名,得到昨天的日志(一个文件,或者多个文件)
            逐行读取日志文件,累加统计结果
            读取完毕,输出统计结果
    - 数据结构
########
{
    eventDate: '2021-03-21', // 统计结果的日期
    eventKey: 'h5',
    eventData: { pv: 10000 } // category=h5的数据汇总
}
{
    eventDate: '2021-03-21',
    eventKey: 'h5.pv',
    eventData: { pv: 8000 } // category=h5&action=pv的数据汇总
}
{
    eventDate: '2021-03-21',
    eventKey: 'h5.pv.85',
    eventData: { pv: 100 } // category=h5&action=pv&label=85的数据汇总
}
{
    eventDate: '2021-03-21',
    eventKey: 'h5.pv.85.41',
    eventData: { pv: 30 } // category=h5&action=pv&label=85&value=41的数据汇总
}
{
    eventDate: '2021-03-21',
    eventKey: 'h5.pv.85.42',
    eventData: { pv: 20 } // category=h5&action=pv&label=85&value=42的数据汇总
}
{
    eventDate: '2021-03-21',
    eventKey: 'h5.pv.85.43',
    eventData: { pv: 50 } // category=h5&action=pv&label=85&value=43的数据汇总
}
########
    - 获取结果
        ~ 输入
            开始日期
            结束日期
            各个参数category action label value等
        ~ 输出
            日期范围之内的,所有统计数据
* 存入数据库
    - 选择mongodb数据库,按照数据结构设计schema
########
mongoose.Schema(
    {
        eventKey: String,
        eventData: {
            pv: Number,
            // uv: Number, // 后续可扩展uv
        },
        eventDate: Date,
    },
    { timestamps: true }
)
########
* 总结
    - 需求是什么
    - 如何收集数据(做一个通用平台,不仅仅为了分渠道统计)
    - 如何分析日志
    - 如何存储数据,数据结构
    - 最后,写技术方案设计文档
10、分析日志结果写入数据库
* readline
    - 操作大文件,一定要用stream,否则可能导致内存爆满。readline是stream的一个具体场景,即逐行读取。
* 分析日志计算结果
    - analysis-logs/EventData.js定义数据模型EventData
    - analysis-logs/index.js逐行读取日志,生成数据模型
    - 模拟测试
* 写入数据库
    - 连接数据库
        ~ 安装mongoose
        ~ db/mongoose.js
    - 数据模型和操作
        ~ model/EventModel.js
        ~ service/event-data.js
    - 写入数据库
        ~ analysis-logs/writeDB.js
        ~ 修改analysis-logs/index.js,写入数据库
* 定时任务
    - 修改analysis/index.js,引入分析日志方法
    - 修改app.js,引入并执行
* 总结
    - readline和stream
    - 分析日志,计算结果的过程
    - 写入数据库
    - 定时任务
11、单元测试
* 整体思路
    - 模拟日志目录、日志文件
    - 执行日志操作(拆分、删除、分析)
    - 检查操作结果,是否符合预期
    - 【注意】只关心日志操作,其他的不要管,如写入数据库。
* 代码调整
    - 为了完成单元测试,需要对代码做一些调整
        ~ 各个日志操作函数,需要传入accessLogPath参数
        ~ 分析日志并入库,拆分为:分析日志得到结果+入库
* 代码修改
    - 安装jest
    - __test__/analysis-log/logs.test.js
    - beforeEach为每个test准备日志目录和文件
    - 执行命令npx jest __test__/analysis-log/logs.test.js
12、Open API
* 主要产出
    - 完成Open API
    - 发布到测试机
* 主要内容
    - 搭建koa2环境
    - 开发接口&接口测试
    - 发布到测试机
* 注意事项
    - OpenAPI设计CORS,只允许特定的host访问
    - 注意docker容器如何访问nginx日志文件
13、搭建koa2环境
* 安装需要的npm插件
* 完善src/app.js,CORS中间件,定义/api/db-check路由
* 增加bin/www文件
* eslint相关配置
* 修改package.json中的scripts和pre-commit
14、开发接口&接口测试
* 接口开发
    - 路由route/event.js并注册到app.js
    - controller/event-data.js
    - 【回顾】Date和时区
* 接口测试
    - __test__/apis/目录
    - package.json的scripts,将test拆分为test:local和test:remote(remote需要部署测试机)
    - 修改app.js-isTest不执行日志定时任务
15、发布到测试机
* 配置docker
    - 代码改动
        ~ Dockerfile和.dockerignore
        ~ 配置docker-compose
    - 特别注意
        ~ docker-host连接mongodb数据库
        ~ volumes配置nginx日志目录
* 启动服务
    - package.json scripts prd-dev
    - bin/目录下pm2配置
* github actions
    - .github/workflows/deploy-dev.yml
* nginx服务
    - 使用su权限操作nginx。
    - nginx配置文件,include代码中的配置,然后重启nginx -s reload,并测试服务。
########
include /home/work/imooc-lego/event-analytics-server/nginx_conf/prd-dev/*;
########
    - 再次回顾一下docker-compose.yml中volumes,将日志目录对应到docker容器。
* 总结
    - docker和docker-compose
    - 启动服务
    - github actions
    - 测试机nginx服务配置
    - 部署完之后,运行npm run test:remote测试。

二十五、后台管理,让所有数据都在我们的掌握之中

1、后台管理系统
* 引言
    - 让所有的数据和内容都在我们的掌握之中。
    - 查看网站数据和内容,管理用户、作品和模板,推荐优质内容,屏蔽垃圾数据。
    - 后台管理系统,是业务闭环的重要拼图。
* 将收获什么
    - 管理后台的需求分析和技术方案设计
    - 用脚手架umijs搭建React管理系统
* 主要内容
    - 需求分析和技术方案设计
    - 前端开发(使用React Hooks)
    - 服务端开发
* 关键词
    - 后台管理
    - umijs
    - React Hooks
* 学习方法
    - 把umijs的文档浏览一遍,了解一下有哪些内容
    - 提前学习React Hooks的基本使用
* 注意事项
    - 不会从0开始讲解umijs和React Hooks
    - 也可以选择Vue,这里使用React是为了丰富课程技术栈,供大家全面学习
2、需求分析&技术方案设计
* 引言
    - 需求指导设计,设计指导开发。
* 主要产出
    - 《技术方案设计》文档
* 主要内容
    - 需求分析,看看需要管理哪些内容和数据
    - 技术方案设计,看看如何实现
* 注意事项
    - 一边分析需求,一边考虑“业务闭环”,从此记住这种思维方式
    - 一切关于业务增长的特性,都需要被管控,不可野蛮生长
3、需求分析
* 需求文档
    - https://www.yuque.com/docs/share/2567a15a-2092-485f-b96d-5f0e84bf705b
* 其他
    - B端统计数据,这里拿不到,使用了阿里ARMS(第三方服务)。
* 总结
    - 思考:业务闭环
4、技术方案设计
* 回顾
    - B端、H5、管理后台,共用一个数据源--这是一切设计方案的基础
    - 各个项目的关系
* 首页dashboard
    - 功能
        ~ 作品总数/分月总数:查询数据库做统计,用到的sql语句如select count(xxx) ... group by xxx
        ~ 发布作品的pv:通过event-analysis-server的OpenAPI获取
        ~ 模板总数/使用次数:查询数据库做统计
        ~ 用户总数/活跃数量:查询数据库做统计
    - 实时统计vs离线计算
        ~ 脱离业务的架构就是耍流氓(老话重提~)
        ~ 首先,从需求角度来讲,首先dashboard不需要非常精确的实时统计数据(这不是股票信息),
          每天更新一次即可。所以,实时统计和离线计算,都能满足需求。
        ~ 接下来就从技术实现来看,哪个简单就用哪个--显然,实时统计更加简单,选择它。
        ~ 但是,实时统计相比离线计算,会有更多的查询请求,增加数据库的负担。
          而我们只有几个管理员,每天登录次数不多。所以这点请求,根本造不成数据库的任何负担。
* 用户管理
    - 显示列表、搜索:从数据库读取
    - 冻结/解除冻结:修改数据状态,再登录时做判断。可回顾B端:
        ~ UserModel
        ~ 登录判断是否冻结
* 作品管理
    - 显示列表、搜索:从数据库读取
    - 强制下线/恢复:修改数据状态,h5-server渲染h5页时做状态判断。可回顾:
        ~ B端的WorkModel
        ~ 发布功能设计,为何不把html直接发布到CDN?
        ~ h5-server渲染h5页时判断status
* 模板管理
    - 显示列表、搜索:从数据库读取
    - 其他:修改数据状态,较简单
* 总结
    - 回顾
    - 首页dashboard
        ~ 实时统计vs离线计算
    - 用户管理
    - 作品管理
    - 模板管理
5、前端开发
* 引言
    - 使用umijs和React Hooks。
* 主要产出
    - 管理后台的前端代码(后台数据先用mock)
* 主要内容
    - 用umijs初始化项目环境
    - 开发功能
    - 发布到测试机
* 注意事项
    - React是高级前端工程师的必备技能,必须要刻意练习(不能只用Vue)
    - React不会从0基础开始讲解
    - umijs可能会更新,要学会看文档,以你现在的文档为主
6、创建项目
* 引言
    - 文档参考参考umijs快速上手
        ~ 初始化项目yarn create @umijs/umi-app
        ~ 安装依赖yarn install
        ~ 启动yarn start浏览器打开http://localhost:8000/
    - 查看配置文件.umirc.ts。
* 配置
    - 刚创建项目时,有配置文件.umirc.ts。根据文档,配置可以放在config/config.ts,两者二选一。
      【注意】如果要用config/config.ts,一定要把.umirc.ts删除,否则运行报错。
        ~ 删除.umirc.ts
        ~ 新建config/config.ts(还可以根据环境变量,扩展其他配置文件,参考文档)
        ~ 将路由拆分到config/route.ts
* 解决warning
    - yarn start可能会出现以下warning
########
warning in ./src/.umi/plugin-layout/layout/layout/renderRightContent.tsx
"export 'SelectLang' was not found in 'umi'
warning in ./src/.umi/plugin-layout/layout/layout/renderRightContent.tsx
"export 'SelectLang' was not found in 'umi'
########
    - 解决方案
        ~ config/config.ts中增加locale: {}
        ~ 新建文件src/locales/zh-CN.ts,文件中写export default {}
        ~ 重启服务
* mock
    - mock接口
        ~ 新增mock/demo.ts mock一个接口
        ~ 在src/pages/index.ts中获取mock接口数据
    - 关闭mock
        ~ package.json script增加"dev-no-mock": "cross-env UMI_ENV=dev-no-mock umi dev",
        ~ 增加配置文件config/config.dev-no-mock.ts
* 总结
    - 初始化
    - 配置
    - mock
7、开发功能
* 配置路由
    - 修改路由config/route.ts
    - 新增页面src/pages/
* 登录验证
    - 获取用户信息
        ~ axios封装src/utils/ajax.ts
        ~ host配置src/config/host.ts
        ~ 获取数据src/service/admin.ts
    - 校验并显示用户信息
        ~ 校验用户src/app.tsx
        ~ 显示用户信息src/components/userInfo.tsx
    - 登录页的css样式,要覆盖住左侧的菜单栏。
    - 【注意】框架会一致更新迭代,项目开发时使用z-index: 10;正常,备课时就得改为z-index: 100;了。
        ~ 拥抱变化
* 各个功能
    - 登录
        ~ mock接口mock/admin.ts
        ~ 页面src/pages/login/index.tsx
    - 用户管理
        ~ mock接口mock/users.ts
        ~ 获取数据src/service/users.ts
        ~ 页面src/pages/users/index.tsx(及其附加的组件)
    - 作品管理
        ~ mock接口mock/works.ts
        ~ 获取数据src/service/works.ts
        ~ 页面src/pages/works/index.tsx(及其附加的组件)
    - 模板管理
        ~ mock接口mock/template.ts
        ~ 获取数据src/service/template.ts
        ~ 页面src/pages/templates/index.tsx(及其附加的组件)
    - 首页dashboard
        ~ 获取统计数据src/service/stat.ts
        ~ 页面src/pages/dashboard/index.ts(及其附加的组件)
        ~ 页面src/pages/dashboard/sub-pages/(及其附加的组件)
    - 404
        ~ 页面src/pages/404/index.tsx
* 总结
    - 配置路由
    - 登录校验
    - 各个功能开发
        ~ 登录
        ~ 用户管理
        ~ 作品管理
        ~ 模板管理
        ~ 首页fashboard
        ~ 404
* 注意
    - echarts v4.9和v5.0使用不一样
        ~ v4.9引用时import echarts from 'echarts'
        ~ v5.0引用时import * as echarts from 'echarts'
    - 拥抱变化,遇到问题主动去搜索、查阅文档。
8、发布到测试机
* 引言
    - 前端代码发布到测试,和server端不一样,注意区分。
* 打包
    - 增加package.json scripts "build-dev": "cross-env UMI_ENV=prd-dev umi build"
    - 增加配置文件config/config.prd-dev.ts,取消mock,设置环境变量
    - 查看host配置src/config/host.ts
    - 运行yarn run build-dev,打包到dist/目录。
* Docker容器
    - 前端代码,打包出来的都是静态文件,可用nginx做服务。思路是:
        ~ 构建一个Docker容器,有nginx
        ~ 将dist/目录拷贝到Docker容器中
        ~ 启动nginx服务
        ~ 宿主机端口,对应到Docker容器端口,即可访问
    - 代码改动
        ~ 创建nginx.conf,这是给Docker容器的nginx用的
        ~ 创建Dockerfile
        ~ 创建docker-compose.yml
* github actions
    - 创建github actions配置文件deploy-dev.yml,提交dev分支,即可部署。
* 总结
    - 打包
    - Docker容器
    - github actions
9、服务端开发
* 主要产出
    - 管理后台服务端代码和接口
* 主要内容
    - 浏览代码结构
    - 主要功能的实现
* 注意事项
    - 代码、技术方案,和之前的那些服务端基本一致,不会再分阶段详细讲解了
10、浏览代码结构
* koa2项目环境
    - src/目录
    - bin/目录和package.json script
* 发布到测试机
    - Dockerfile
    - docker-compose.yml
    - github actions
11、主要功能的实现
* 主要功能
    - 按月统计sql语句
        ~ 统计每月注册用户数量
        ~ 统计每月作品的创建、发布数量
        ~ 统计每月模板的发布、使用数量
* 本地联调
    - 启动server
    - 启动前端,取消mock yarn run dev-no-mock

二十六、发布到阿里云服务器,支持快速回滚

1、发布上线
* 标题
    - 服务端发布上线-发布到阿里云服务器,支持快速回滚
* 将收获什么
    - 基于git tag和github actions实现发布上线和回滚
    - 域名CNAME转发和nginx反向代理
* 主要内容
    - 购买和配置云服务器、数据库
    - 上线和回滚的流程
    - 域名转发和nginx配置
* 关键词
    - 云服务器、云数据库
    - 上线、回滚
    - 域名、https
    - 反向代理
* 学习方法
    - 一定要动手操作起来,并且记录学习笔记
* 注意事项
    - 如果要自己实际操作,你不用购买这么多云服务器(很贵),你可以只用你当前的服务器:
        ~ 域名可以购买一个(比较便宜)或者去申请免费的域名,先不用买https证书
        ~ 域名指向你的机器
        ~ 启动不同的服务,通过nginx做反向代理
        ~ 数据库可以在自己机器上安装,或者用Docker
    - 按上述操作,不用再多花钱,但是:
        ~ 代码需要做相应的修改,如nginx配置、数据库连接配置等
        ~ 都在一个机器,会比较慢
        ~ 免费域名不知是否高效、稳定
2、购买和配置云服务器、云数据库
* 引言
    - 测试环境下要求配置灵活、集成性高、一键部署,数据安全性要求不高,适合用Docker。
    - 线上环境要求稳定、高效,数据安全性要求高,适合用独立的数据库服务。
* 主要产出
    - 配置所有的线上环境
* 主要内容
    - 购买服务
    - 配置服务
* 注意事项
    - 要参考当前系统的情况购买云服务器,前期可以少投入,或者按量付费
    - 要学会查阅阿里云(或其他云平台)的文档,他们都有详细的文档供使用者参考,专人维护的。
      一定要利用起来。
3、需要购买哪些服务
* 引言
    - 前提:测试环境和线上环境要分开。
* 需要购买的服务
    - 域名imooc-lego.com
        ~ 备案
        ~ https证书(之前想用免费的,但3个月更新一次,索性花钱买了一个)
    - 两台服务器-B端和C端
    - 数据库
        ~ mysql
        ~ mongodb
        ~ redis
    - OSS和CDN
* 购买服务
    - 可以选择其他云平台(阿里云可能较贵)
    - 尽量按量付费,流量小了便宜
    - 前期可以买配置低一点的服务,降低成本
* 总结
    - 梳理了需要购买的服务
    - 线上环境B端和C端要分开
4、配置服务器
* 参考测试机的配置
    - 参考第15周的配置测试机。
        ~ 创建work账号
        ~ 增加登录信任,免密登录
        ~ 安装必备软件
            git
            docker
        ~ 开放防火墙端口
* 安装nodejs
    - 测试机中,所有的运行环境都在docker中,
    - 切换到su权限,执行命令:
########
curl -sl https://rpm.nodesource.com/setup_12.x | bash -
yum install -y nodejs
node -v # v12.x
########
    - 是否要全局安装pm2 npm i pm2 -g,安装日志管理插件pm2 install pm2-logrotate,
      需要su权限
* 开放端口不一样!
    - 测试机可以为每个服务都单独开放一个端口,这样比较方便。
      每个端口都对应这一个服务,不需要nginx反向代理。少了nginx这一层,安装、配置、调试都简单。
    - 但是,线上环境不一样。开放的端口多,安全隐患就大。另外,线上环境必须要nginx来统一配置流量,
      记录日志。所以,线上环境只开放80和443端口即可,其他的都通过nginx反向代理。
    - 配置方式:
        ~ 进入控制台,进入ECS实例管理页面。
        ~ 左侧导航“本实例安全组”,“配置规则”,“手动添加”端口。
* 配置数据库
    - 以mysql为例子
        ~ 进入控制台,进入数据库实例的界面。先创建数据库
        ~ 再创建账号,记住密码
        ~ 可以得到数据库的连接信息,有内网的和外网的。
        ~ 需要哪个服务器访问,就添加它的IP到白名单。这里注意:
            本地访问时,需要添加本地IP到白名单,但是本地电脑IP经常变,每次访问前都要添加一次
            如果是阿里云的服务器,可以选择添加“内网IP”,通过“内网IP”访问数据库,这样更快更安全
        ~ 最后,添加上本地的IP到白名单,就可以打开本地的workbench来连接线上mysql了。
    - mongodb和redis
        ~ mongodb和redis也是类似的流程。
        ~ 大家一定要利用好云平台的文档,每个云平台都有详细的文档供使用者参考。
        ~ 配置完,要用本地客户端连接测试一下。
* 总结
    - 服务器配置
    - 数据库配置
5、上线和回滚的流程
* 引言
    - 测试环境下,我们通过push dev分支来触发github actions,自动发布到测试机。
    - 如果要上线,该什么时机触发呢?push master分支的时候吗?--思考一下
* 主要产出
    - 实现各个项目的上线和回滚
* 主要内容
    - git tag+github actions实现上线和回滚
    - 使用release-it进行改造,增加changeLog
    - 将各个系统发布上线
* 注意事项
    - 有上线,必须有版本、有回滚,否则无法闭环
    - 上线是一件非常严肃的事情,从流程上就要严肃慎重对待
6、上线和回滚
* 部署线上服务
    - 参考bin/deploy.sh
* 基于push tag触发
    - 测试环境下,基于push dev分支触发部署,这样简单方便,测试环境也不需要回滚。
    - 线上环境下,使用tag而不是master分支:
        ~ master分支可能会被频繁合并,但不能频繁上线,push tag可以作为上线正式的“仪式”
        ~ 回滚时,使用tag更加方便(否则,就得在master分支去revert commit记录)
    - 参考github actions的deploy-prd.yml
* 一键生成tag
    - 手动操作,总有失误的时候。所以,要整合为一个命令,一键执行。
    - 参考bin/create-version-tag.sh
* 回滚
    - 重新执行github actions任务。
* 总结
    - 部署线上服务 
    - 基于push tag触发
    - 一键生成tag
    - 一键回滚
7、用release-it
* release-it可以自动生成版本号,生成并push tag,生成change log。
* 有这么方便的工具,要利用起来,去替换我们自己的shell脚本。
    - 安装必要的npm插件
    - 增加配置文件.release-it.js
    - package.json增加scripts "release": "release-it",
    - 运行npm run release
8、将各个系统发布上线
* 我们购买了两台服务器,一台用于B端,一台用于C端
    - B端xx.xx.xx.165
        ~ biz-editor-server
        ~ admin-server
        ~ admin-fe
    - C端xx.xx.xx.18
        ~ h5-server
        ~ 统计服务-nginx服务
        ~ 统计服务-Open API
* 以biz-editor-server为例
    - 增加配置文件src/config/envs/prd.js
    - 执行发布上线,确保github actions执行成功
    - 登录服务器
        ~ 看代码文件和git tag
        ~ 检查服务是否启动curl http://127.0.0.1:3000/api/db-check和pm2 list
* 服务列表
    - 将所有讲过的服务,都发布上线,按照如下要求。
    - 针对每个服务,都可以用curl来测试。
项目 服务器IP 代码文件位置 端口 服务软件
biz-editor-server B端服务器xx.xx.xx.165 /home/work/lego-team/biz-editor-server 3000 nodejs pm2
admin-server B端服务器xx.xx.xx.165 /home/work/lego-team/admin-server 3003 nodejs pm2
admin-fe B端服务器xx.xx.xx.165 /home/work/lego-team/admin-fe 8000 nginx
h5-server C端服务器xx.xx.xx.18 /home/work/imooc-lego/h5-server 3001 nodejs pm2
统计服务 收集日志 C端服务器xx.xx.xx.18 /home/work/imooc-lego/event-analytics-server 8083 nginx
统计服务 Open API C端服务器xx.xx.xx.18 /home/work/imooc-lego/event-analytics-server 3002 nodejs pm2
9、域名转发和nginx配置
* 本章产出
    - 通过域名可以访问各个服务,能线上运行
* 主要内容
    - 域名配置
    - nginx配置
    - 线上环境的CORS限制
    - 线上日志拆分
* 注意事项
    - 购买域名之后需要备案,备案需要时间(有些服务商,不备案可以使用非80 443端口)
    - 自己练习时,要关注完整的流程,不要过分纠结一些细节,如https证书
10、域名配置
* 域名购买
    - 购买imooc-lego.com域名,域名价格不贵(比QQ会员便宜多了~)
    - 域名备案,按照服务商的指导来操作即可,傻瓜式的操作。域名备案,就类似于买车挂牌,
      给你的域名、网站备注一个负责人。所以,很多事情需要域名备案之后才能正常继续,
      例如域名解析到服务器,再例如之前的短信服务。
    - 正式的线上项目,为了安全性,必须使用https协议。所以,购买https证书,并配置到域名上。
    - 【注意】你如果自己做练习,不建议买https证书。第一,价格较高;第二,工作中不常用。
* 域名设计
项目 服务器 域名
biz-editor-server B端服务器xx.xx.xx.165 api.imooc-lego.com
admin-server B端服务器xx.xx.xx.165 admin.imooc-lego.com
admin-fe B端服务器xx.xx.xx.165 admin.imooc-lego.com
h5-server C端服务器xx.xx.xx.18 h5.imooc-lego.com
统计服务 收集日志 C端服务器xx.xx.xx.18 statistic.imooc-lego.com
统计服务 Open API C端服务器xx.xx.xx.18 statistic-res.imooc-lego.com
* 域名解析
    - 虽然项目分布到两个不同的服务器,但是域名应该统一用nginx转发,即反向代理。
      所以,域名统一解析到xx.xx.xx.18服务器,然后由nginx转发到各个服务。
      如果后面系统业务增长,需要增加服务器,那就会单独由一台服务器做nginx转发和负载均衡。
    - nginx如何转发,下一节再说。
    - 二级域名的解析可以使用CNAME记录。
      域名备案成功之后,登录控制台,域名管理,找到域名解析的界面,添加CNAME记录即可。
      以我的域名wangeditor.com为例
    - 【注意】修改解析有生效时间,不会立马生效。
* 总结
    - 域名购买
    - 域名设计
    - 域名转发
11、nginx转发
* 引言
    - 保证此时域名备案已经成功了。
    - 再次回顾一下两台服务器都各自在运行着哪些服务?包括nginx和nodejs的。
* 开始
    - 域名CNAME统一解析到xx.xx.xx.18服务器
    - 再由xx.xx.xx.18服务器的nginx反向代理到各个项目
    - 各个项目分布在xx.xx.xx.18服务器和xx.xx.xx.165服务器
* 服务器xx.xx.xx.18的nginx配置
    - http永久重定向到https
    - https需要证书文件
    - 转发到其他服务器,使用“内网IP”(再服务器的控制台,可以找到),因为都是阿里云服务器
########
server {
    listen 80;
    listen [::]:80;
    server_name imooc-lego.com www.imooc-lego.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name imooc-lego.com www.imooc-lego.com;
    ssl_certificate /etc/nginx/certs/4701008_imooc-lego.com.pem;
    ssl_certificate_key /etc/nginx/certs/4701008_imooc-lego.com.key;
    
    if ($http_host ~* "^(.*?)\.imooc-lego\.com$") {
        set $domain $1;
    }
    
    location / {
        if ($domain ~* "h5") {
            proxy_pass http://127.0.0.1:3001;
        }
        if ($domain ~* "statistic") {
            proxy_pass http://127.0.0.1:8083;
        }
        if ($domain ~* "statistic-res") {
            proxy_pass http://127.0.0.1:3002;
        }
        if ($domain ~* "admin") {
            proxy_pass http://127.0.0.1:8000;
        }
        if ($domain ~* "api") {
            proxy_pass http://127.0.0.1:3000;
        }
        
        # default
        proxy_pass http://172.31.249.251:80;
        
        # proxy_set_header Host $host:$server_port;
        proxy_set_header    Host $host;
        proxy_set_header    X-Real-IP $remote_addr;
        proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

# imooc-lego/event-analytics-server
include /home/work/imooc-lego/event-analytics-server/nginx_conf/prd/*;
########
* 服务器xx.xx.xx.165的nginx配置
########
## B端前端
server {
    listen  80;
    server_name imooc-lego-editor;
    charset utf-8;
    
    index   index.html;
    root    /home/work/lego-team/editor;
    
    location / {
        ## 为了前端使用h5 history路由模式
        try_files $uri $uri/ /index.html;
    }
}

## 管理后台前端和后端
server {
    listen  8000;
    server_name imooc-lego-admin;
    charset utf-8;
    
    location / {
        index   index.html;
        root    /home/work/lego-team/admin-fe/dist;
    }
    
    location /api/ {
        proxy_pass http://127.0.0.1:3003;
        proxy_set_header    Host $host:$server_port;
        proxy_set_header    X-Real-IP $remote_addr;
        proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}
########
* 总结
    - 服务器xx.xx.xx.18的nginx配置
    - 服务器xx.xx.xx.165的nginx配置
12、线上环境的CORS限制
* 引言
    - dev和测试环境,都可以完全开放跨域限制,随便访问。
    - 但线上环境,必须严格控制跨域限制,否则这是很大的安全隐患。
* 代码实现
    - 回顾src/middlewares/cors.js中间件代码。
* 各个项目的CORS配置
项目 CORS配置
biz-editor-server 'https://www.imooc-lego.com,https://imooc-lego.com'
h5-server
admin-server 'https://admin.imooc-lego.com'
admin-fe
统计服务 Open API 'https://imooc-lego.com,https://www.imooc-lego.com,https://admin.imooc-leqo.com'
统计服务 日志收集
13、线上日志拆分
* 引言
    - 之前在讲服务端技术选型时讲过日志拆分的技术选型,现在继续应用起来。
* 范围
    - nginx日志
    - pm2日志
* 必要性
    - 之前在讲统计服务时重点、详细的讲过这个问题。所有的日志必须要拆分,不能任由积累。
        ~ 大文件不好管理,小文件方便管理
        ~ 按日期或时间拆分日志文件,方便统计和分析
        ~ 按日志和时间拆分日志文件,方便清理历史日志
* nginx日志拆分
    - 下面的方案二选一,无特殊需求就推荐第一种:使用系统的logrotate
* 使用系统的logrotate
    - CentOS系统进入/etc/logrotate.d/目录,看是否有nginx文件。有则可编辑,无则新建。
########
/var/log/nginx/*.log {
    daily
    missingok
    rotate 52
    compress
    delaycompress
    notifempty
    create 640 nginx adm
    sharedscripts
    postrotate
        if [ -f /var/run/nginx.pid ]; then
            kill -USR1 `cat /var/run/nginx.pid`
        fi
    endscript
}
########
    - 文件的配置可参考https://ethendev.github.io/2019/01/10/roate-nginx-log/
    - 当前服务器的运行效果如下:
* 使用crontab
    - 如果用crontab,就把/etc/logrotate.d/nginx删掉。二选一,不要重复。
    - 写一个shell脚本,代码如下,用于拷贝日志。
########
#!/bin/bash
base_path='/var/log/nginx'
log_path=$(date -d yesterday +"%Y%m")
day=$(date -d yesterday +"%Y%m%d")
mkdir -p $base_path/$log_path
cp $base_path/access.log $base_path/$log_path/access_$day.log
echo "" > $base_path/access.log
cp $base_path/error.log $base_path/$log_path/error_$day.log
echo "" > $base_path/error.log
########
    - 然后编辑crontab -e,增加0 0 0 * * /xx/xx/xxx.shell每天凌晨0:00执行。
* pm2日志拆分
    - 常见的工具是pm2-logrotate。从名字看来,是模仿的linux logrotate。
      pm2-logrotate可以自动识别pm2配置文件指定的日志目录,不用每个项目单独配置。
    - 安装pm2 install pm2-logrotate,运行pm2 list即可看到pm2-logrotate的进程。
    - 通过pm2 set pm2-logrotate:<key> <value>配置,它的默认配置是:
########
# 默认配置
$ pm2 set pm2-logrotate:max_size 10M # 日志文件最大10M
$ pm2 set pm2-logrotate:retain 30 # 保留30个文件,多了就自动删掉
$ pm2 set pm2-logrotate:compress false # gzip压缩文件
$ pm2 set pm2-logrotate:dateFormat YYYY-MM-DD_HH-mm-ss
$ pm2 set pm2-logrotate:workerInterval 30 # 单位s,日志检查的时间间隔
$ pm2 set pm2-logrotate:rotateInterval 0 0 * * * # 定时规则
$ pm2 set pm2-logrotate:rotateModule true # 分割pm2模块的日志
########
    - 【注意】PM2配置时,需要combine_logs: true合并日志文件,这样也方便管理日志。
    - 线上服务器的结果如下图:
* 总结
    - 日志拆分的必要性
    - nginx日志拆分
        ~ 使用logrotate
        ~ 使用crontab
    - pm2日志拆分
        - pm2-logrotate
posted on 2023-11-13 13:43  一路繁花似锦绣前程  阅读(17)  评论(0编辑  收藏  举报