二十四、自研统计服务,使作品能实现分渠道统计
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配置
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