跳转至

devops

消息实时推送方案

需求:客户端及时收到服务端的消息通知

  • 对于 Gitee 等网站,其消息都是界面刷新时才通知,注意实时的性价比

短轮询

短轮询:指定的时间间隔,由浏览器向服务器发出HTTP请求,服务器实时返回未读消息数据给客户端,浏览器再做渲染显示。

  • 实现:JS定时器;
  • 缺点:推送数据并不会频繁变更,无论后端此时是否有新的消息产生,客户端都会进行请求,势必会对服务端造成很大压力,浪费带宽和服务器资源。

长轮询(Comet)

示例代码见:LongPullingCode

客户端主动拉pull模型,应用长轮询(Long Polling

  • 由服务端控制响应客户端请求的返回时间,来减少客户端无效请求的一种优化手段;

  • 客户端发起请求后,服务端不会立即返回请求结果,而是将请求挂起等待一段时间,如果此段时间内服务端数据变更,立即响应客户端请求,若是一直无变化则等到指定的超时时间后响应请求,客户端重新发起长链接

长轮询

实现

  • DeferredResultservelet3.0后经过Spring封装提供的一种异步请求机制

  • 允许容器线程快速释放占用的资源,不阻塞请求线程,以此接受更多的请求提升系统的吞吐量。

  • 启动异步工作线程处理真正的业务逻辑,处理完成调用DeferredResult.setResult(200)提交响应结果。

  • 一个ID可能会被多个长轮询请求监听,使用guava包的Multimap结构,一个key对应多个value。一旦监听到key变化,对应的所有长轮询都会响应;

  • 请求超过设置的超时时间,会抛出AsyncRequestTimeoutException异常,全局捕获统一返回,前端获取约定好的状态码后再次发起长轮询请求。

  • 前端得到非请求超时的状态码,知晓数据变更,主动查询未读消息数接口,更新页面数据;

注意

  • 竞态分析:如果在前端重新建立连接时,后端接收到新消息,此时前端还没有建立连接,该消息会丢失,需要根据业务设计相应方案;

缺点

长轮询相比于短轮询在性能上提升了很多,但依然会产生较多的请求,在响应之后,会引起请求突然激增。

SSE

不应该设置 WriteTimeOut,示例代码见:SSE 代码

服务器发送事件(Server-sent events),简称SSE

SSE在服务器和客户端之间打开一个单向通道,服务端响应的不再是一次性的数据包而text/event-stream类型的数据流信息,在有数据变更时从服务器流式传输到客户端。

注:SSE不支持IE浏览器,对其他主流浏览器兼容性做的还不错。

SSE与Websocket的区别

WebSocket SSE
全双工,可以同时发送和接收消息 单工,只能服务端单向发送消息
独立的协议 基于HTTP协议
需要代理服务器单独支持 代理服务器直接支持
协议相对复杂 协议相对简单,易于理解和使用
默认不支持断线重连 默认支持断线重连
默认支持传送二进制数据 一般只用来传送文本,二进制数据需要编码后传送
不支持自定义发送的数据类型 支持自定义发送的数据类型
支持CORS 不支持CORS,协议和端口都必须相同
  • 以1次/秒或者更快的频率向服务端传输数据,那应该用WebSocket;
  • 客户端和服务端脚本之间具有网络服务器情况时,一个SSE连接不仅使用一个套接字,还会占用一个Apache线程或进程;

实现

客户端:通过 EventSource建立连接;

  • 会自动断线重连(如超时重连),并且会发送Last-Event-ID header

服务端:Springboot 通过 SseEmitter实现;

  • SSE消息的格式为field:value\nfield:value\n\n, field 支持如下
: 即以:开头表示注释可以理解为服务端向客户端发送的心跳确保连接不中断
data数据
event: 事件默认值为 message如果是其它事件前端可以通过 addEventListener 监听不同的事件
id: 数据标识符用 id 字段表示相当于每一条数据的编号
retry: 重连时间

Nginx 转发 SSE

  • nginx upstream 超时连接关闭(upstream timeout)时,前端会自动进行重连;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;

# Nginx 默认 OFF
proxy_cache off;

# 应该由 server 端发送 X-Accel-Buffering: no; 
# 针对 proxy_pass 的后端配置,好像不需要配置,即使开启前端也会实时响应
# proxy_buffering off;

# 该配置对于SSE不需要
# chunked_transfer_encoding off;

MQTT

MQTT 全称(Message Queue Telemetry Transport):一种基于发布/订阅(publish/subscribe)模式的轻量级通讯协议,通过订阅相应的主题来获取消息,是物联网Internet of Thing)中的一个标准传输协议。

将消息的发布者(publisher)与订阅者(subscriber)进行分离,因此可以在不可靠的网络环境中,为远程连接的设备提供可靠的消息服务,使用方式与传统的MQ有点类似。

实现

https://mp.weixin.qq.com/s/U-fUGr9i1MVa4PoVyiDFCg

WebSocket

TCP连接上进行全双工通信的协议

SOCKJS

当今主流浏览器都支持 Websocket,因此 SOCKJS 不再需要

  • 除非反向代理(如Nginx)的配置不支持WebSocket.

使用 SOCKJS (模拟 Websocket 协议)

  • sockjs-client 提供浏览器兼容性,优先使用原生的WebSocket,如果某个浏览器不支持WebSocket,SockJS会自动降级为SSE 或者 长轮询;

心跳

  • SockJS 协议要求 servers 发送心跳消息,排除代理导致的连接挂起。(Spring SockJS 默认 heartbeatTime 为 25s);

  • When using STOMP over WebSocket and SockJS, if the STOMP client and server negotiate heartbeats to be exchanged, the SockJS heartbeats are disabled.

STOMP

使用 STOMP(定义Websocket的数据格式),消息的订阅和发送。

  • STOMP即Simple Text Orientated Messaging Protocol,简单文本定向消息协议;
  • stompjs 客户端,1.1 版本提供心跳机制(默认10s,但会跟 broker 连接时协调出最终的in/out心跳);
  • 连接稳定性,支持自动重连;

事务支持

// start the transaction
const tx = client.begin();
// send the message in a transaction
client.publish({
  destination: '/queue/test',
  headers: { transaction: tx.id },
  body: 'message in a transaction',
});
// commit the transaction to effectively send the message
tx.commit();

Spring

https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#websocket-server

On the Servlet stack, the Spring Framework provides both server (and also client) support for the SockJS protocol;

  • 跨域问题,the default behavior for WebSocket and SockJS is to accept only same-origin requests. ;
  • nginx 配置 proxy_set_header Host $host;
  • 或者 spring 配置 setAllowedOriginPatterns("*")

  • 事件问题:

  • SessionDisconnectEvent需要幂等处理,一个WS连接(Session)可能会被发送多次;
Simple Broker

The built-in simple message broker handles subscription requests from clients, stores them in memory, and broadcasts messages to connected clients that have matching destinations.

纯 WebSocket 的心跳问题(不使用 SockJS):

外部 Broker

支持 RabbitMQ, ActiveMQ 等

认证

通过configureClientInboundChannel()注册消息处理,进行验证。

nginx 转发 Websocket

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;

proxied server 端需要定时发送 PING 消息保证 nginx 不会关闭连接。

站内信设计

实现一个站内信web消息推送的功能

img

消息推送(push) 通常是指网站的运营工作等人员,通过某种工具对用户当前网页或移动设备APP进行的主动消息推送。

消息推送一般又分为web端消息推送移动端消息推送

web端消息推送常见的诸如站内信、未读邮件数量、监控报警数量等,应用的也非常广泛。

需求:站内信

  • 管理员可以发布多种不同的消息,用户在界面可以及时收到通知;

  • 每个 client,需要有自己的唯一标识,防止用户对一个界面同时多开,此时每个界面都会有一个独立的连接;

  • 即仅用用户ID作为 ClientId 不行,因为 Server 端对同一个ID,只会保留一个连接,但是对应多个前端页面连接,会出错;

非实时方案

类似 Gitee,其站内信是非实时的,需要界面刷新才可以获取。

  • unread接口,获取未读消息;

实时方案

触发某个事件(主动分享了资源或者后台主动推送消息),web页面的通知小红点就会实时的+1

在服务端会有若干张消息推送表,用来记录用户触发不同事件所推送不同类型的消息,前端主动查询(拉)或者被动接收(推)用户所有未读的消息数

基于Tag自动发布Github Release

在 Github 上发布 Release 时,默认只会发布源码的zip包,但是有时候会想要将一些编译出来的文件也放在 Release 中。虽然可以在本地编译,然后通过界面手动上传。但是可以基于 Github Action 实现自动发布

版本自动发布

规范化 git commit 信息

参考:规范化git commit信息

用于识别 Feat, Fix, Test 等特性。

1. commit基本要求

Git-Commit-Best-Practices这个项目总结了一个最基本的 git commit 实践:

  • Commit Related Changes( 提交相关的改变)
  • Commit Often (经常提交)
  • Don’t Commit Half-Done Work (只提交完成的工作)
  • Test Your Code Before You Commit (提交前需要测试代码)
  • Write Good Commit Messages(写良好的提交记录)
  • Use Branches (使用分支)
  • Agree on A Workflow (认同工作流)

2. 开源项目的Commit示例

Angular项目,可以很方便的生成Release Notes

img

3. Commit 规范

commit 基本格式如下:

<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>

type用于说明 commit 的类别,只允许使用下面 7 个标识:

  • feat:新功能(feature)
  • fix:修补 bug
  • docs:文档(documentation)
  • style: 格式(不影响代码运行的变动)
  • refactor:重构(即不是新增功能,也不是修改 bug 的代码变动)
  • test:增加测试
  • chore:构建过程或辅助工具的变动
  • ci :CI 相关的改动
  • perf :性能提升的代码改动(不新增功能)

通常featfix会被放入 changelog 中,其他(docschorestylerefactortest)通常不会放入 changelog 中。

scope用于说明 commit 影响的范围,可选值。通常是文件、路径、功能等。

subject是 commit 目的的简短描述,不超过 50 个字符。

Body部分是对本次 commit 的详细描述,可以分成多行。

Footer 部分只用于两种情况:

  • Break Changes:不兼容变动
  • Closes:关闭Issue

示例:

feat(python): add greedy_snake.py

Closes #73

4. 本地配置git commit规范检查

4.1 commit之后通过命令进行检查

在git commit的hook中加入commitlint检测,不符合 commit 规范的提交在本地就无法提交进去。

# 1. 安装commitlint命令行和验证使用的规则config-conventional
npm install -g @commitlint/config-conventional @commitlint/cli

# linux shell 或者 windows git-bash环境执行echo命令
# 2.1 单个项目的配置文件,每个项目可以配置不同的commit lint规范
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js

# 2.1 全局commitlint.config.js配置windows下暂时不知如何配置

# 3. 验证最新一条提交记录(必须添加上述配置,否则需要加上 -x "@commitlint/config-conventional")
commitlint -e

# 3.2 检查信息是否符合配置(linux shell 或者 windows git-bash)
echo "your commit message" | commitlint
4.2 集成git命令在commit时检查

git-cz 是一个简化版的commitizen+cz-conventional-changelog组合,提供了开箱即用的功能,默认使用Angular规范,默认模板不填写scope部分内容。

# 安装git-cz包
npm install -g git-cz

# 以后所有使用git commit的地方都用git-cz或git cz命令提交代码
# 交互式使用,兼容git commit 的参数,比如-a, --amend
git cz 
4.3 添加git hook在commit时检查(推荐)
NodeJS项目

NodeJS 项目直接使用 husky:

npm install -D husky

安装@commitlint/cli@commitlint/config-conventional这两个包(建议安装到全局,这样所有项目都可以用):

npm install -g @commitlint/cli @commitlint/config-conventional

然后在 package.json 添加 husky 配置:

{
  "husky": {
    "hooks": {
      "commit-msg": "commitlint -x @commitlint/config-conventional -E HUSKY_GIT_PARAMS"
    }
  }
}

然后使用git commit会触发husky的hook,检测commit记录是否符合规范。

其他类型项目

其它项目,手动添加 git hook,仍然使用husky

# 全局安装husky
npm install -g husky

# 1. 安装commitlint命令行和验证使用的规则config-conventional
npm install -g @commitlint/config-conventional @commitlint/cli

项目中初始化husky配置

# husky 对项目进行初始化,创建目录.husky目录和脚本husky.sh
husky install

# 添加commit-msg hook,执行`npx commitlint --edit $1` 命令,对commit message进行检验
husky add .husky/commit-msg "commitlint -x @commitlint/config-conventional --edit $1"

# 可选,如果不用-x @commitlint/config-conventional,则需要项目中配置commitlint.config.js文件
# echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js文件

执行git commit时(注意空格),会进行命令输出

img

img

删除 husky 和 git hook

npm uninstall husky && git config --unset core.hooksPath

5. gitlab CI 配置 git commit 规范检查

第4部分内容是在开发本地做的,因此需要禁止开发人员删除hook

在 gitlab ci 中运行以下命令检测当前提交是否符合 conventional-changelog 规范:

image: node:latest

stages:
  - test

compile_job:
  stage: test
  script:
    - npm install "@commitlint/cli" "@commitlint/config-conventional" "commitlint-format-junit" 
    - npx commitlint -x @commitlint/config-conventional -o commitlint-format-junit -f ${CI_COMMIT_BEFORE_SHA} > commitlint_result.xml
  artifacts:
    name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME"
    reports:
      junit: commitlint_result.xml
  • $CI_COMMIT_BEFORE_SHA 是 gitlab ci 的内置变量;

将 lint result 输出为 Junit 格式,方便 Gitlab 在 merge request 的时候展示 lint 失败的结果,如下图所示。img

semantic-release 自动发布

更适合在 CI 环境中运行,自带支持各种 git server 的认证支持,如 Github,Gitlab,Bitbucket 等等,此外,还支持插件,以便完成其他后续的流程步骤,比如自动生成 git tag 和 release note 之后再 push 回中央仓库,自动发布 npm 包等等。

大致的工作流如下:

  • 提交到特定的分支触发 release 流程
  • 验证 commit 信息,生成 release note,打 git tag
  • 其他后续流程,如生成CHANGELOG.mdnpm publish等等(通过插件完成)

npm install -g @semantic-release

  • 默认安装 "@semantic-release/commit-analyzer","@semantic-release/github","@semantic-release/npm", "@semantic-release/release-notes-generator"

版本号更新逻辑

版本号更新的逻辑:只有 featfix 提交才会触发版本升级

  • 如果包含 feat 记录,版本由1.0.0升级到了1.1.0
  • 只有 fix 记录,版本由1.1.0升级到了1.1.1
  • feat 且 commit footer内有BREAKING CHANGE: 提交将会升级主版本号,版本由1.2.0升级到了2.0.0

Git 仓库认证

https://github.com/semantic-release/semantic-release/blob/master/docs/usage/ci-configuration.md#authentication

Gitlab 仓库需要配置 GL_TOKEN or GITLAB_TOKEN

生命周期

Step Description
Verify Conditions Verify all the conditions to proceed with the release.
Get last release Obtain the commit corresponding to the last release by analyzing Git tags.
Analyze commits Determine the type of release based on the commits added since the last release.
Verify release Verify the release conformity.
Generate notes Generate release notes for the commits added since the last release.
Create Git tag Create a Git tag corresponding to the new release version.
Prepare Prepare the release.
Publish Publish the release.
Notify Notify of new releases or errors.

插件

@semantic-release/commit-analyzer(自带)

analyze commits with conventional-changelog

  • 默认preset使用 angular 形式的commit规范;
@sematic-release/release-notes-generator(自带)

generate changelog content with conventional-changelog

通过conventional-changelog插件,生成从上个release到现在的变更信息。

  • 默认preset使用 angular 形式的commit规范;
@semantic-release/changelog

Create or update a changelog file in the local project directory with the changelog content created in the generate notes step.

创建或更新changelog文件(默认路径为 CHANGELOG.md

$ npm install @semantic-release/changelog -D
  • 如果和@semantic-release/git@semantic-release/npm共用,则其位置必须在最前面。
@semantic-release/git
  • 支持将某些文件反向 push 回中央仓库(并添加skip ci commit 信息跳过 CI )
  • 默认的文件为['CHANGELOG.md', 'package.json', 'package-lock.json', 'npm-shrinkwrap.json']

配置,.releaserc

{
  "plugins": [
    ["@semantic-release/git", {
          // 配置哪些文件会被 add 推送回仓库
          "assets": ["Dockerfile", "./build/userservice.yaml","./build/version.md", "CHANGELOG.md"],
          // 自定义 commit 信息的格式
          "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
     }
    ],
  ]
}
@semantic-release/gitlab

publish a GitLab release

默认会通过 CI_API_V4_URL内置的环境变量识别 Gitlab 的地址

  • 如果 Gitlab SSL 没配置好,可能会出现 404 的问题,此时通过CI/CD重新定义该变量,解决问题。
{
    "plugins": [
        "@semantic-release/commit-analyzer",
        "@semantic-release/release-notes-generator",
        "@semantic-release/changelog",
        "@semantic-release/git",
        ["@semantic-release/gitlab", {
            "assets": [
                {"path": "README.md", "label": "CSS distribution"}
            ]
        }]
    ]
}

assets 字段

Property Description Default
path Required, unless url is set. A glob to identify the files to upload. -
url Alternative to setting path this provides the ability to add links to releases, e.g. URLs to container images. Supports Lodash templating. -
label Short description of the file displayed on the GitLab release. Ignored if path matches more than one file. Supports Lodash templating. File name extracted from the path.
type Asset type displayed on the GitLab release. Can be runbook, package, image and other (see official documents on release assets). Supports Lodash templating. other
filepath A filepath for creating a permalink pointing to the asset (requires GitLab 12.9+, see official documents on permanent links). Ignored if path matches more than one file. Supports Lodash templating. -
@semantic-release/exec

execute custom shell commands.

$ npm install @semantic-release/exec -g

配置(.releaserc

{
  "plugins": [
    "@semantic-release/commit-analyzer",
    "@semantic-release/release-notes-generator",
    ["@semantic-release/exec", {
      "verifyConditionsCmd": "./verify.sh",
      "publishCmd": "./publish.sh ${nextRelease.version} ${branch.name} ${commits.length} ${Date.now()}"
    }],
  ]
}

生命周期

Step Description
verifyConditions Execute a shell command to verify if the release should happen.
analyzeCommits Execute a shell command to determine the type of release.
verifyRelease Execute a shell command to verifying a release that was determined before and is about to be published.
generateNotes Execute a shell command to generate the release note.
prepare Execute a shell command to prepare the release.
publish Execute a shell command to publish the release.
success Execute a shell command to notify of a new release.
fail Execute a shell command to notify of a failed release.
google/semantic-release-replace-plugin

update version strings throughout a project.

修改特定文件中的版本号信息。

$ npm install @google/semantic-release-replace-plugin -D

配置

{
  "plugins": [
    "@semantic-release/commit-analyzer",
    [
      "@google/semantic-release-replace-plugin",
      {
        "replacements": [
          {
            "files": ["foo/__init__.py"],
            "from": "__VERSION__ = \".*\"",
            "to": "__VERSION__ = \"${nextRelease.version}\"",
            "results": [
              {
                "file": "foo/__init__.py",
                "hasChanged": true,
                "numMatches": 1,
                "numReplacements": 1
              }
            ],
            "countMatches": true
          }
        ]
      }
    ],
    [
      "@semantic-release/git",
      {
        "assets": ["foo/*.py"]
      }
    ]
  ]
}

版本号需求的不同阶段

说明:

  • semantic-release 是最后进行执行,因为会需要将CHANGELOG等变更文件推回 git 仓库
  • 项目构建时,需要根据版本号出制品(如zip包,docker image tag等);
  • semantic-relase 执行镜像跟项目的构建镜像不会是一个镜像
  • 版本制品会区分快照(SNAPSHOT)和发布(RELEASE);

因此:

  1. 通过semantic-releasedry模式,区分release/snapshot,先生成版本号;
  2. 根据版本号进行项目构建,出相应版本的制品;
  3. semantic-release 变更文件推回git仓库,并创建 Git Tag;

Gitlab CI 使用

  • 项目工程中添加.releaserc配置
  • 默认的插件顺序是commit-analyzer, release-notes-generator, npm, github,
{
  "plugins": [
    "@semantic-release/commit-analyzer",
    "@semantic-release/release-notes-generator",
    "@semantic-release/changelog",
    "@semantic-release/git"
  ]
}
  • .gitlab-ci.yml 配置(gitlab-runner运行环境为powershell)
# lint 过程用于检测 commitlint 结果
# release 过程用于自动化产生 git tag 和 CHANGELOG.md


stages:
  - lint
  - build
  - deploy
  - release

commitlint:
  stage: lint
  # node:lts 镜像并包含 npm install -g @commitlint/cli @commitlint/config-conventional commitlint-format-junit
  # @semantic-release @semantic-release/gitlab @semantic-release/git @semantic-release/changelog @semantic-release/exec
  image: ${GIT_NODE_IMAGE}
  script: |
    if [ "${CI_COMMIT_BEFORE_SHA}" = "0000000000000000000000000000000000000000" ]; then
      npx commitlint -x @commitlint/config-conventional -f HEAD^
    else
      npx commitlint -x @commitlint/config-conventional -f "${CI_COMMIT_BEFORE_SHA}"
    fi

    echo "===${CI_COMMIT_REF_NAME}===${CI_COMMIT_BRANCH}"

    # --dry-run 模式,预先生成版本号,区分 release / snapshot
    if [ "${CI_COMMIT_REF_NAME}" == "master" ]; then
      npx semantic-release --dry-run --no-ci
      echo "VERSION_VAR=`cat VERSION`" > build.env
      cat build.env
    else
      echo "VERSION_VAR=SNAPSHOT-`cat VERSION`-`date "+%Y%m%d-%H%M%S"`" > build.env
      cat build.env
    fi
  # 通过环境变量传递版本号
  artifacts:
    reports:
      dotenv: build.env

build:
  stage: build
  image: ${MKDOCS_IMAGE}
  # 构建版本制品,并上传制品库
  script:
    - mkdocs build
    - ls -all ./
    - tar -zcvf helpdoc-${VERSION_VAR}.tar.gz site 
    - curl -v --user 'admin:admin123' --upload-file helpdoc-${VERSION_VAR}.tar.gz http://172.16.1.217:8081/repository/static/helpdoc/helpdoc-${VERSION_VAR}.tar.gz
  artifacts:
    paths:
      - helpdoc-*.tar.gz
    expire_in: 1 hour

deploy:
  stage: deploy
  # 通过 SSH 部署到机器
  image: ${SSHPASS_IMAGE}
  script:
    - sshpass -p ${NODE_125_PASSWD} scp -o StrictHostKeyChecking=no helpdoc-${VERSION_VAR}.tar.gz root@${NODE_125_IP}:/tmp
    - sshpass -p ${NODE_125_PASSWD} ssh -o StrictHostKeyChecking=no root@${NODE_125_IP} "cd /tmp && rm -rf site && tar -zxvf helpdoc-${VERSION_VAR}.tar.gz && rm -rf /home/experiment/web_ai_education/nginx/html/help/ && mv site /home/experiment/web_ai_education/nginx/html/help/ && cd /home/experiment/web_ai_education/nginx/ && ./sbin/nginx -s reload"


release:
  stage: release
  image: ${GIT_NODE_IMAGE}
  script:
    # 生成版本号,更新CHANGELOG,并推回仓库
    - npx semantic-release
  only:
    - master
  dependencies: []
  • gitlab项目环境变量配置(GITLAB_TOKEN或者GL_TOKEN)

效果图示例(master分支构建流水线):

CHANGELOG出现在中央仓库

直播的弹幕设计

如何设计一个 70w 在线人数的弹幕系统 ?

需求分析

70w 在线人数的弹幕系统

带宽压力

假如说每3秒促达用户一次,那么每次内容至少需要有15条才能做到视觉无卡顿。15条弹幕+http包头的大小将超过3k,那么每秒的数据大小约为8Gbps

带宽优化

  • Http 压缩(小数据的 Http 压缩的性价比?)

通过查阅资料,http gzip压缩比率可以达到40%以上(gzip比deflate要高出4%~5%)

  • 弹幕的 Response 结构简化,降低传输字节数

  • 内容排列顺序优化,将字符串和数字内容放在一起摆放,增加压缩比;

  • 频率控制

  • 带款控制:通过添加请求间隔参数(下次请求时间),保证客户端的请求频率服务端可控

  • 通过添加请求间隔参数(下次请求时间),保证客户端的请求频率服务端可控

弹幕卡顿、丢失分析

根据了解腾讯云的弹幕系统,在300人以下使用的是推送模式,300人以上则是采用的轮训模式。

促达机制,推送 vs 拉取

  • Long Pulling

  • 减少轮询次数,低延迟,浏览器兼容性较好

  • 服务器需要保持大量连接

  • WebSocket:

  • 较少的控制开销(相对于 HTTP 请求每次都要携带完整的头部),更强的实时性;

  • 每个客户端使用一个持久化的连接

Long Polling 能发现连接异常的最短间隔为:\(min(keepalive\_intvl, polling\_interval)\)

Websockets能发现连接异常的最短间隔为:\(min(keepalive\_intvl, client\_sending\_interval)\)

弱网情况下 Websockets 其实已经不能作为一个候选项

  • 即使 Websockets 服务端已经发现连接断开,仍然没有办法推送数据,只能被动等待客户端重新建立好连接才能推送,在此之前数据将可能会被采取丢弃的措施处理掉;(没有缓存/入库?)

  • 在每次断开后均需要再次发送应用层的协议进行连接建立。

可靠与性能

逻辑较为复杂、调用较少的发送弹幕业务与逻辑简单、调用量高的弹幕拉取服务拆分开来。

  • 不同服务的QPS往往是不对等的,例如像拉取弹幕的服务的请求频率和负载通常会比发送弹幕服务高1到2个数量级

拉取弹幕

  • 数据更新的策略是服务会定期发起RPC调⽤从弹幕服务拉取数据,拉取到的弹幕缓存到内存中

  • 缓存:按照时间进行分片(采用 RingBuffer),最多保留60秒的数据,只保留了尾指针,它随着时间向前移动,每⼀秒向前移动一格

  • 读请求:缓冲环会根据客户端传入的时间戳计算出指针的索引位置,并从尾指针的副本区域往回遍历直至跟索引重叠,收集到一定数量的弹幕列表返回

  • 写操作是单线程,读和写是相反的方向,⽽决定读和写的位置是否出现重叠取决于index的位置,

  • 保证读操作最多只能读到30秒内的数据,因此缓冲环完全可以做到无锁读写

    发送弹幕

  • 用户一定时间能看得过来弹幕总量是有限

  • 对弹幕进行限流,有选择的丢弃多余的弹幕

Gitlab CI/CD

Gitlab 安装

官方docker 安装

将 gitlab, nginx, postgres, redis 都运行在一个镜像中

  • 拉取gitlab-ce镜像
docker pull gitlab/gitlab-ce
  • 将 GitLab 的配置 (etc) 、 日志 (log) 、数据 (data) 放到容器之外, 便于日后升级, 因此请先准备这三个目录。
mkdir -p /mnt/gitlab/etc
mkdir -p /mnt/gitlab/log
mkdir -p /mnt/gitlab/data
  • 启动镜像,需要建立端口映射,8090和22是容器内gitlab的http和ssh的端口
docker run \
    --detach \
    --publish 8090:8090 \
    --publish 222:22 \
    --name gitlab \
    --restart unless-stopped \
    -v /mnt/gitlab/etc:/etc/gitlab \
    -v /mnt/gitlab/log:/var/log/gitlab \
    -v /mnt/gitlab/data:/var/opt/gitlab \
    gitlab/gitlab-ce
  • 配置 gitlab gitlab上创建项目的时候,生成项目的URL访问地址是按容器的 hostname 来生成的,也就是容器的id。作为gitlab 服务器,我们需要一个固定的 UR L访问地址,于是需要配置 gitlab.rb(宿主机路径:/mnt/gitlab/etc/gitlab.rb
# gitlab.rb文件内容默认全是注释
vim /mnt/gitlab/etc/gitlab.rb
# 配置http协议所使用的访问地址,不加端口号默认为80
external_url 'https://192.168.199.231:8090'
# 配置ssh协议所使用的访问地址和端口
gitlab_rails['gitlab_ssh_host'] = '192.168.199.231'
gitlab_rails['gitlab_shell_ssh_port'] = 222 # 此端口是run时22端口映射的222端口

#保存配置文件并退出
:wq
  • 重启gitlab容器

docker restart gitlab

FAQ

GitLab 访问返回 502

gitlab-ctl status 查看对应的服务,是否有不停重启的服务,进而查看服务日志。

unicorn一直重试,

  • 端口问题,改unicorn端口,再对gitlab重启;
  • 没有明显原因,则排查资源问题(CPU和内存);
  • 尝试 docker exec -it gitlab rm /opt/gitlab/var/unicorn/unicorn.pid && docker restart gitlab

https://forum.gitlab.com/t/error-502-failed-to-start-a-new-unicorn-master/29790

三方docker安装

https://github.com/sameersbn/docker-gitlab

拆分为 gitlab, postgres, redis 三个镜像,通过 docker compose 启动;

操作命令

docker容器安装gitlab时,需要先进入到容器中

docker exec -ti gitlab /bin/bash

  • 重新应用gitlab的配置

gitlab-ctl reconfigure

  • 重启gitlab服务

gitlab-ctl restart

  • 查看gitlab运行状态

gitlab-ctl status

  • 停止gitlab服务

gitlab-ctl stop

  • 查看gitlab运行日志

gitlab-ctl tail

  • 停止相关数据连接服务

gitlab-ctl stop unicorn

gitlab-ctl stop sideki

Gitlab CI

Runner

Docker-Runner

Gitlab-runner 安装:更好的管理方式是k8s

  • 拉取镜像
docker pull gitlab/gitlab-runner
  • 启动gitlab-runner
docker run -d --name gitlab-runner --restart always \
    -v /mnt/gitlab-runner/config:/etc/gitlab-runner \
    -v /var/run/docker.sock:/var/run/docker.sock \
    gitlab/gitlab-runner
  • 注册到gitlab,根据gitlab admin中的runner的信息,填写以下信息
docker run --rm -t -i -v /mnt/gitlab-runner/config:/etc/gitlab-runner gitlab/gitlab-runner register \
  --non-interactive \
  --url "http://172.16.1.181:8090/" \
  --registration-token "DA1wNdAchnBFH_frXa9N" \
  --executor "docker" \
  --docker-image alpine:latest \
  --description "docker-runner" \
  --tag-list "docker,test"

如果出现了no route to host异常,需要在宿主机上添加端口防火墙(原因见docker章节No Route to Host 问题)

firewall-cmd --zone=public --add-port=8090/tcp --permanent
firewall-cmd --reload
K8s-Runner

Helm 安装:GitLab Runner Helm chart | GitLab Docs

.gitlab-ci.yml 配置

需要在项目中创建 .gitlab-ci.yml 文件,下面是个示例,其中tags是创建gitlab-runner时指定的tags,匹配上才会有runner执行CI:

image: maven:latest
stages:
  - build
  - test
  - run
variables:
  MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
cache:
  paths:
    - .m2/repository/
    - target/
build:
  stage: build
  script:
    - mvn $MAVEN_CLI_OPTS compile
  only:
    - master
  tags:
    - test
test:
  stage: test
  script:
    - mvn $MAVEN_CLI_OPTS test
  only:
    - master
  tags:
    - test
deploy:
  stage: deploy
  script:
    - echo "deploy over..."
  only:
    - master
  tags:
    - test
  • Pipeline:相当于一次构建任务,里面可以包含多个流程,如安装依赖、运行测试、编译、部署测试服务器、部署生产服务器等。
  • 任何提交或者 Merge Request 的合并都可以触发 Pipeline 构建;
  • Stages:表示一个构建阶段。一次 Pipeline 中可定义多个 Stages
  • 所有 Stages 会顺序运行,即当一个 Stage 完成后,下一个 Stage 才会开始
  • 只有当所有 Stages 完成后,该构建任务才会成功
  • 如果任何一个 Stage 失败,那么后面的 Stages 不会执行,该构建任务失败
Pipeline

Branch pipelines that run for Git push events to a branch, like new commits or tags.

Tag pipelines that run only when a new Git tag is pushed to a branch.

Merge request pipelines that run for changes to a merge request, like new commits or selecting the Run pipeline button in a merge request’s pipelines tab.

Scheduled pipelines.

Variables Branch Tag Merge request Scheduled
CI_COMMIT_BRANCH Yes Yes
CI_COMMIT_TAG Yes Yes, if the scheduled pipeline is configured to run on a tag.
CI_PIPELINE_SOURCE = push Yes Yes
CI_PIPELINE_SOURCE = scheduled Yes
CI_PIPELINE_SOURCE = merge_request_event Yes
CI_MERGE_REQUEST_IID Yes
Jobs

表示构建工作,即某个 Stage 里面执行的工作。一个 Stage 中可定义多个 Jobs

  • 默认,相同 Stage 中的 Jobs 会并行执行

  • 相同 Stage 中的 Jobs 都执行成功时,该 Stage 才会成功

  • 如果任何一个 Job 失败,那么该 Stage 失败,即该构建任务失败

可以通过needs字段改变执行顺序。

  • 同一个stage的:job1 和 job2 是可以并行的。
  • job1之后将会启动 job3 (立即执行, 不会等待job2完成作业)
  • job2之后将会启动 job4 (立即执行, 不会等待job1完成作业)
stages:
    - stage-1
    - stage-2

job-1:
    stage: stage-1
    needs: []
    script: 
      - echo "job-1 started"
      - sleep 5
      - echo "job-1 done"

job-2:
    stage: stage-1
    needs: []
    script: 
      - echo "job-2 started"
      - sleep 60
      - echo "job-2 done"

job-3:
    stage: stage-2
    needs: [job-1]
    script: 
      - echo "job-3 started"
      - sleep 5
      - echo "job-3 done"

job-4:
    stage: stage-2
    needs: [job-2]
    script: 
      - echo "job-4 started"
      - sleep 5
      - echo "job-4 done"
variables

GitLab CI/CD 预先定义的变量:https://docs.gitlab.com/ee/ci/variables/predefined_variables.html

.gitlab-ci.yaml中定义变量:

  • jobs 中定义 variables{}表明不需要全局变量;
variables:
  GLOBAL_VAR: "A global variable"

job1:
  variables:
    JOB_VAR: "A job variable"
  script:
    - echo "Variables are '$GLOBAL_VAR' and '$JOB_VAR'"

job1:
  variables: {}
  script:
    - echo This job does not need any variables
将变量传递到其它job

create a new environment variables in a job, and pass it to another job in a later stage.

build-job:
  stage: build
  script:
    - echo "BUILD_VARIABLE=value_from_build_job" >> build.env
  artifacts:
    reports:
      dotenv: build.env

test-job:
  stage: test
  script:
    - echo "$BUILD_VARIABLE"  # Output is: 'value_from_build_job'
cache

https://docs.gitlab.com/ee/ci/caching/

cache是用来指定 jobs 之间可以缓存的文件和目录

  • Locally defined cache overrides globally defined options;

  • 不同的 key 下的缓存也不会相互影响;

  • cache 在同一个项目的不同的 pipeline 之间也实现共享;

  • 不同的项目不能共享 cache;

  • 如果整个 pipeline 配置全局的 cache,意味着每个 job 在没有特殊配置的情况下会使用全局的配置

  • 对整个 job 的 cache 禁用

    job:
      cache: {}
    

默认的配置是 cache:policy 中的 pull-push 策略:

  • pull:每个 job 会在开始执行前将对应路径的文件下载下来;
  • push:任务结束前重新上传,不管文件是否有变化;
  • 可以单独指定 pull 或者 push;
rspec:
  stage: test
  cache:
    paths:
      - vendor/bundle
    policy: pull
  script:
    - bundle exec rspec ...

示例:maven项目配置缓存

image: nnntln/3.6.1-jdk-8:latest

variables:
   MAVEN_OPTS: -Dmaven.repo.local=/cache/maven.repository
cache:
   key: PortalReportBackend
   paths:
     - /root/.m2/repository

stages:
  - build
  - execute

build:
  stage: build
  script: /usr/lib/jvm/java-8-openjdk-amd64/bin/javac Hello.java
  artifacts:
    paths:
     - Hello.*

execute:
  stage: execute
  script: /usr/lib/jvm/java-8-openjdk-amd64/bin/java Hello
artifacts

Use artifacts to pass intermediate build results between stages.

  • Subsequent jobs in later stages of the same pipeline can use artifacts.
  • Different projects cannot share artifacts.
  • Artifacts expire after 30 days by default. You can define a custom expiration time.
  • The latest artifacts do not expire if keep latest artifacts is enabled.
  • Use dependencies to control which jobs fetch the artifacts

artifacts is used to specify a list of files and directories which should be attached to the job when it succeeds, fails, or always.

The artifacts will be sent to GitLab after the job finishes and will be available for download in the GitLab UI.

job artifacts

https://docs.gitlab.com/ee/ci/pipelines/job_artifacts.html

job 的制品,可以在 Pipeline界面进行下载

pdf:
  script: xelatex mycv.tex
  artifacts:
    paths:
      - mycv.pdf
    expire_in: 1 week

Keep artifacts from most recent successful jobs

By default artifacts are always kept for successful pipelines for the most recent commit on each ref.

  • 最新的artifacts 不会受expire_in字段影响;

Keep the latest artifacts for all jobs in the latest successful pipelines

By default the artifacts of the most recent pipeline for each Git ref are locked against deletion and kept regardless of the expiry time.

  • 默认流水线的 artifacts 不受过期时间影响;
  • 此设置优先于项目级别设置(Keep artifacts from most recent successful jobs)

Pipeline artifacts

Pipeline artifacts are different to job artifacts because they are not explicitly managed by .gitlab-ci.yml definitions.

Pipeline artifacts are used by the test coverage visualization feature to collect coverage information.

Gitlab Webhook

默认情况下

  • 新建分支,会触发 pipeline(需要分析是否符合预期)
  • push 时 total_commits_count 为0时,表示新建分支会触发 pipeline hook
  • 1次 push webhook;
  • 3次 pipeline webhook(pending -> running -> succeed)

  • open a MergeRequest 触发一次 webhook,action 为 opened, "merge_status": "preparing",

  • 没有 pipeline id,此时只能生成链接,点击查看

  • approve a MergeRequest 触发一次 webhook, action 为 approved

  • merge Request 的时候,state 变成 merged ,action 为 merge

  • close merge request 时,state 变成 closed ,action 为 close

Gitlab CD

https://docs.gitlab.com/ee/topics/release_your_application.html

K8s Cluster

Gitops(Pull-Based)

https://docs.gitlab.com/ee/user/clusters/agent/gitops.html

  • Moved from GitLab Premium to GitLab Free in 15.3

通过在 K8s 集群部署 Agent,监听

gitlab_cd_k8s_pull

CI Push-Based

https://docs.gitlab.com/ee/user/clusters/agent/ci_cd_workflow.html

  • Moved to GitLab Free in 14.5.

直接在.gitlab-ci.yml中选择Agent的K8s context 并运行 kubectl命令,部署到 K8s 环境

  • 多环境支持不好;

Gitlab 集成 Jira

想要解决的问题:

  • 代码推送到 gitlab 时,自动在 jira 上添加评论信息;
  • 代码合并时,自动将 Jira 的 issue 关闭;