后端擂台赛 - 2024年第一季度
结果在此
直播录像在此
后端擂台赛是一项以挑战形式分享知识为主要目标的比赛!这是第二届。提交作品的截止日期是2024年3月10日23:59:59,结果将于2024年3月14日19:00在YouTube上通过直播公布。
本届擂台赛的主要主题是关于贷记和借记(信贷)的并发控制,灵感来自于同行@lucascs和@kmyokoyama在这条和这条评论中对这条推文的回应。
如果你想更深入地了解擂台赛的精神,请查看第一届的仓库。
需要做什么?
要参与,您需要开发一个具有以下端点的HTTP API:
交易
请求
POST /clientes/[id]/transacoes
{
"valor": 1000,
"tipo" : "c",
"descricao" : "descricao"
}
其中
[id]
(在URL中)必须是一个整数,表示客户的标识。valor
必须是一个正整数,表示分(我们不处理分的分数)。例如,10元相当于1000分。tipo
只能是c
表示信用或d
表示借记。descricao
必须是1到10个字符的字符串。 所有字段都是必填的。
响应
HTTP 200 OK
{
"limite" : 100000,
"saldo" : -9098
}
其中
limite
必须是客户的注册限额。saldo
必须是交易完成后的新余额。 成功的交易请求的HTTP状态码必须是200!
规则 借记交易绝不能使客户的余额低于其可用限额。例如,限额为1000(10元)的客户余额绝不应低于-1000(-10元)。在这种情况下,-1001或更低的余额意味着后端竞赛中的不一致!
如果借记请求会导致余额不一致,API应返回HTTP状态码422,并且不完成交易!这种情况下的响应体不会被测试,您可以选择如何表示它。如果有效载荷字段不符合规范,例如descricao
字段超过10个字符或tipo
字段不是c
或d
,也应返回HTTP 422。如果valor
字段指定了非整数,您可以返回HTTP 422或400。
如果URL中的[id]
属性是不存在的客户标识,API应返回HTTP状态码404。这种情况下的响应体不会被测试,您可以选择如何表示它。如果API返回类似HTTP 200并在响应体中表示未找到客户,或者返回无响应体的HTTP 204,我将极度沮丧,后端竞赛将永远取消。
对账单
请求
GET /clientes/[id]/extrato
其中
[id]
(在URL中)必须是一个代表客户标识的整数。
响应
HTTP 200 OK
{
"saldo": {
"total": -9098,
"data_extrato": "2024-01-17T02:34:41.217753Z",
"limite": 100000
},
"ultimas_transacoes": [
{
"valor": 10,
"tipo": "c",
"descricao": "descricao",
"realizada_em": "2024-01-17T02:34:38.543030Z"
},
{
"valor": 90000,
"tipo": "d",
"descricao": "descricao",
"realizada_em": "2024-01-17T02:34:38.543030Z"
}
]
}
其中
saldo
total
必须是客户当前的总余额(不仅仅是以下显示的最近交易)。data_extrato
必须是查询对账单的日期/时间。limite
必须是客户的注册限额。
ultimas_transacoes
是按日期/时间降序排列的交易列表,包含最多10笔最近交易,具有以下内容:valor
必须是交易金额。tipo
必须是c
表示贷记,d
表示借记。descricao
必须是交易期间提供的描述。realizada_em
必须是交易的执行日期/时间。
规则
如果URL中的 [id]
属性是不存在的客户标识,API必须返回HTTP状态代码404。在这种情况下,响应体不会被测试,你可以选择如何表示它。你已经知道如果你的API返回2XX范围内的东西会发生什么,对吧?谢谢。
客户初始登记
为了在测试中强调并发性,应该只登记和测试少数客户。因此,测试前必须预先登记以下五位客户,包括他们的ID、限额和初始余额 - 这一点至关重要!
ID | 限额 | 初始余额 |
---|---|---|
1 | 100000 | 0 |
2 | 80000 | 0 |
3 | 1000000 | 0 |
4 | 10000000 | 0 |
5 | 500000 | 0 |
注意:请勿特意登记ID为6的客户,因为测试的一部分是验证ID为6的客户确实不存在,且API会返回HTTP 404错误!
如何制作和提交?
与之前的后端竞赛一样,你需要将你的API和其他使用的组件容器化为docker-compose格式,遵守CPU和内存资源限制,最低架构配置,以及制品结构和提交流程(你的内容需要提交什么、在哪里提交、何时提交)。
你可以个人提交,也可以2人组队、3人组队,甚至50人组队。没有人数限制。你和/或你的团队可以提交多个作品,只要API不同即可。
工件、流程和截止日期
要参与,只需在本存储库中创建一个拉取请求,在participantes中包含一个子目录,其中包含以下文件:
docker-compose.yml
- 可由docker-compose
解释的文件,其中包含组成您的API的服务声明,遵守CPU/内存限制和最小架构。README.md
- 至少包括您的姓名、使用的技术、API源代码存储库的链接,以及如果获胜时的联系方式。您可以自由添加其他信息,如网站链接等。- 在此处也包括任何其他必要的目录/文件,以确保您的容器正确启动,例如
nginx.conf
、banco.sql
等。 这里有一个提交示例可以帮助您。 重要! 所有在docker-compose.yml
中声明的服务必须公开可用!否则,将无法执行测试。为此,您可以在hub.docker.com上创建一个账户来提供您的镜像。这些镜像通常格式为<user>/<image>:<tag> - 例如,zanfranceschi/rinha-api:latest
。 上一届比赛中的一个常见错误是将镜像声明为本地存在。这对于构建镜像的人可能是正确的(在本地进行了构建),但对于执行测试的服务器来说并非如此! 重要! 必须将包含API源代码的存储库设为公开访问,并在提交的README.md
文件中提供信息。毕竟,后端擂台赛的主要目标是分享知识! Ana的提交/拉取请求示例可能包含以下文件:
├─ participantes/
| ├─ ana-01/
| | ├─ docker-compose.yml
| | ├─ nginx.config
| | ├─ sql/
| | | ├─ ddl.sql
| | | ├─ dml.sql
| | ├─ README.md
提交拉取请求的截止日期/时间是2024-03-10T23:59:59-03:00
。在此日期/时间之后,任何拉取请求都将被自动拒绝。
请注意,在截止日期/时间之前,您可以提交任意数量的拉取请求!
API的最小架构
这里所说的"API"指的是为了使处理HTTP请求的服务能够运行而涉及的所有服务,如负载均衡器、数据库和HTTP服务器。
你的API至少需要包含以下服务:
- 一个使用轮询算法进行流量分配的负载均衡器。与之前的版本不同,你不必使用Nginx - 可以选择(甚至自己开发)任何一种,比如HAProxy。负载均衡器将是接收测试请求的服务,它需要在9999端口接受请求!
- 2个Web服务器实例用于处理HTTP请求(由负载均衡器分发)。
- 一个关系型或非关系型数据库(不包括以内存存储为主要特征的数据库,如Redis等)。
flowchart TD
G(压力测试 - Gatling) -.-> LB(负载均衡器 / 端口9999)
subgraph 你的应用
LB -.-> API1(API - 实例01)
LB -.-> API2(API - 实例02)
API1 -.-> Db[(数据库)]
API2 -.-> Db[(数据库)]
end
注意:你可以使用额外的组件,如果你想的话。但请记住,CPU和内存的限制必须遵守以下规则:所有服务声明的限制总和不得超过1.5个CPU单位和550MB内存!请使用常识和诚信,不要添加一个关系型数据库和一个Redis,然后只使用Redis作为存储 - 毕竟,Rinha只是一个促进学习而非不公平竞争的小游戏。
CPU/内存限制
在您的docker-compose.yml文件中,您应该限制所有服务,使它们的总和不超过以下限制:
deploy.resources.limits.cpu
1.5 – 所有服务之间分配的一个半CPU单位deploy.resources.limits.memory
550MB – 所有服务之间分配的550兆字节内存 注:请使用MB
作为内存计量单位;这样可以更容易验证限制。
# docker-compose.yml文件中某个服务配置的部分示例
...
nginx:
image: nginx:latest
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- api01
- api02
ports:
- "9999:9999"
deploy:
resources:
limits:
cpus: "0.17"
memory: "10MB"
...
帮助你的示例文件
以下仅为示例文件,以便你不必从零开始,如果遇到任何困难或只是想加快 API 的构建过程。显然,你可以根据自己的需要进行修改,但要遵守之前解释的所有限制。再次强调,你不一定要使用关系型数据库 - 以下示例仅作说明用途。
docker-compose.yml
version: "3.5"
services:
api01: &api
# 请记住,你的 HTTP 服务必须托管在公开可访问的仓库中!
# 例如:hub.docker.com
image: ana/minha-api-matadora:latest
hostname: api01
environment:
- DB_HOSTNAME=db
# 除了负载均衡器的端口外,不需要暴露任何其他端口,
# 但人们通常会这样做以便在开发阶段测试他们的 API 并连接到数据库。
ports:
- "8081:8080"
depends_on:
- db
deploy:
resources:
limits:
cpus: "0.6"
memory: "200MB"
api02:
# 这个语法重用了 'api01' 中声明的内容。
<<: *api
hostname: api02
environment:
- DB_HOSTNAME=db
ports:
- "8082:8080"
nginx:
image: nginx:latest
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- api01
- api02
ports:
# 负载均衡器必须暴露/使用 9999 端口!
- "9999:9999"
deploy:
resources:
limits:
cpus: "0.17"
memory: "10MB"
db:
image: postgres:latest
hostname: db
environment:
- POSTGRES_PASSWORD=123
- POSTGRES_USER=admin
- POSTGRES_DB=rinha
ports:
- "5432:5432"
volumes:
- ./script.sql:/docker-entrypoint-initdb.d/script.sql
deploy:
resources:
limits:
# 请注意,这里声明的所有服务限制总和
# 是 1.5 个 CPU 单位和 550MB 内存。
# 这里的分配只是一个例子 - 你可以根据需要进行分配。
cpus: "0.13"
memory: "140MB"
# 使用 `bridge` 模式应该适合测试中使用的负载。
# 上一版本受益于 host 模式,因为请求量相对较高,网络虚拟化成为瓶颈,
# 但这种模式配置更复杂。你可以自由选择想要的模式,
# 只要不与操作系统中常用的端口冲突即可。
networks:
default:
driver: bridge
name: rinha-nginx-2024q1
script.sql
-- 在这里放置初始脚本
CREATE TABLE...
DO $$
BEGIN
INSERT INTO clientes (nome, limite)
VALUES
('o barato sai caro', 1000 * 100),
('zan corp ltda', 800 * 100),
('les cruders', 10000 * 100),
('padaria joia de cocaia', 100000 * 100),
('kid mais', 5000 * 100);
END; $$
nginx.conf
events {
worker_connections 1000;
}
http {
access_log off;
sendfile on;
upstream api {
server api01:8080;
server api02:8080;
}
server {
listen 9999; # 还记得必须使用 9999 端口吗?
location / {
proxy_pass http://api;
}
}
}
测试工具
与上一版本一样,本次仍将使用Gatling工具进行性能测试。在开发阶段执行测试可以帮助发现潜在的问题和瓶颈,这一点非常重要。测试脚本可在本仓库的load-test目录中找到。
测试环境
要了解环境详情(操作系统和软件版本),请访问测试环境规格。
请注意,测试将在Linux x64环境中执行。因此,如果您的开发环境使用不同的架构,您需要按以下方式构建Docker镜像:
$ docker buildx build --platform linux/amd64
例如:
$ docker buildx build --platform linux/amd64 -t ana/minha-api-matadora:latest .
执行测试
以下是快速执行测试的说明:
- 从 https://gatling.io/open-source/ 下载Gatling
- 确保已安装JDK (64位OpenJDK LTS(长期支持)版本:11、17和21) https://gatling.io/docs/gatling/tutorials/installation/
- 确保将GATLING_HOME环境变量设置为Gatling安装目录。
要确认变量设置正确,以下路径应该有效:
Linux上的
$GATLING_HOME/bin/gatling.sh
和Windows上的%GATLING_HOME%\bin\gatling.bat
。 - 配置
./executar-teste-local.sh
脚本(如果在Windows上,则使用./executar-teste-local.ps1
) - 在9999端口启动您的API(或负载均衡器)
- 运行
./executar-teste-local.sh
(如果在Windows上,则运行./executar-teste-local.ps1
) - 等待测试完成并打开报告
模拟结束时会显示报告路径。
结果/报告保存在
./load-test/user-files/results
中。
请随意修改模拟以测试不同方面和场景。请不要在提交的pull request中包含这些更改!
不客气 :)
预测试
在上一届Rinha中,测试在容器启动后几秒钟就开始了,由于CPU和内存限制,并非所有服务都能在如此短的时间内准备好接收请求。在本届中,测试开始前,一个脚本将在最多40秒内每2秒检查一次API是否正确响应(通过GET /clientes/1/extrato
)。因此,请确保所有服务在40秒内能够接收请求!
关于编写测试的重要说明!
模拟中包含了一个超出性能测试常规范围的余额/限额逻辑测试。我这样写只是因为后端Rinha的特殊性。通常情况下,请避免在性能测试中这样做,因为这不是推荐的做法。逻辑测试应该与源代码一起以单元测试或集成测试的形式进行!
后端大战获胜标准
惊喜!:)