- 性能测试
- 测试
- 后端
- 开发者工具
【免费下载链接】FrameworkBenchmarks
Source for the TechEmpower Framework Benchmarks project
本篇技术指南以 frameworks/Haskell/ihp/README.md 为纲,结合仓库内 IHP 应用的源码、配置与构建脚本,系统讲解 TechEmpower FrameworkBenchmarks 中 IHP 基准测试的实现方式:六个测试端点的路由、控制器逻辑、数据库模型、视图渲染与性能调优参数,以及本地验证这些端点的方法。读完本文,你将能独立定位 IHP 基准实现的每一处代码,理解queries参数边界规则与数据库连接池等关键配置的底层作用。
一、IHP 基准测试概览
IHP(Integrated Haskell Platform)是一个面向生产环境的 Haskell 全栈 Web 框架。在 FrameworkBenchmarks 仓库中,它的基准实现位于 frameworks/Haskell/ihp,其定位由 config.toml 与 benchmark_config.json 共同声明:
- Approach:
Realistic(贴近真实生产场景) - Classification:
Fullstack(全栈框架) - Database:
postgres - ORM:
Full(使用完整的 ORM 能力) - Platform / Webserver:
Wai(基于 WAI 接口与 Warp 服务器) - Language:
Haskell
config.toml中同时登记了六个测试端点的 URL 映射,这是基准测试工具定位路由的依据:
[main] urls.plaintext = "/plaintext" urls.json = "/json" urls.db = "/db" urls.query = "/query?queries=" urls.update = "/update?queries=" urls.fortune = "/fortunes"二、六项测试的实现源码位置
根据 frameworks/Haskell/ihp/README.md 的指引,所有测试逻辑集中在一个控制器文件 src/Web/Controller/FrameworkBenchmarks.hs 中,各测试类型的实现位置如下:
| 测试类型 | 源码位置(行号) | 对应 Action |
|---|---|---|
| JSON | FrameworkBenchmarks.hs#L15 | JsonAction |
| DB | FrameworkBenchmarks.hs#L18 | DbAction |
| QUERY | FrameworkBenchmarks.hs#L23 | QueryAction |
| FORTUNES | FrameworkBenchmarks.hs#L32 | FortuneAction |
| UPDATE | FrameworkBenchmarks.hs#L39 | UpdatesAction |
| PLAINTEXT | FrameworkBenchmarks.hs#L52 | PlaintextAction |
控制器数据类型FrameworkBenchmarksController定义在 src/Web/Types.hs,六个构造子与上表一一对应:
data FrameworkBenchmarksController = JsonAction | PlaintextAction | DbAction | QueryAction | FortuneAction | UpdatesAction deriving (Eq, Show, Data)三、测试 URL 与路由映射
README 列出了六项测试的完整访问地址(基准容器监听 8080 端口):
| 测试 | URL |
|---|---|
| JSON | http://localhost:8080/json |
| PLAINTEXT | http://localhost:8080/plaintext |
| DB | http://localhost:8080/db |
| QUERY | http://localhost:8080/query?queries= |
| UPDATE | http://localhost:8080/update?queries= |
| FORTUNES | http://localhost:8080/fortunes |
这些路径的路由规则定义在 src/Web/Routes.hs,通过 IHP 的parseRoute'组合子完成字符串匹配与 action 的绑定:
instance CanRoute FrameworkBenchmarksController where parseRoute' = (string "/json" <* endOfInput >> pure JsonAction) <|> (string "/plaintext" <* endOfInput >> pure PlaintextAction) <|> (string "/db" <* endOfInput >> pure DbAction) <|> (string "/fortunes" <* endOfInput >> pure FortuneAction) <|> (string "/query" <* endOfInput >> pure QueryAction) <|> (string "/update" <* endOfInput >> pure UpdatesAction)注意endOfInput的存在意味着路径必须精确匹配,/json/这类带尾斜杠的请求不会被匹配。路由解析后由 src/Web/FrontController.hs 挂载到应用前端控制器,最终由 src/Main.hs 中的main = IHP.Server.run config启动服务。
四、数据库模型:World 与 Fortune
所有涉及数据库的测试(DB、QUERY、UPDATE、FORTUNES)都基于两张表,其定义见 src/Application/Schema.sql:
CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE TABLE World ( id SERIAL PRIMARY KEY NOT NULL, randomnumber INT DEFAULT 0 NOT NULL ); CREATE TABLE Fortune ( id SERIAL PRIMARY KEY NOT NULL, message TEXT NOT NULL );World表对应id与randomnumber两个字段,用于 DB / QUERY / UPDATE 测试;Fortune表对应id与message,用于 FORTUNES 测试。
在控制器中,这两个模型直接由 IHP 的 ORM 生成类型Generated.Types提供,并通过自定义ToJSON实例控制 JSON 输出的字段名。注意randomnumber字段在 JSON 中被映射为驼峰式randomNumber(见 FrameworkBenchmarks.hs#L8-L9):
instance ToJSON World where toJSON world = object [("id" .= get #id world), ("randomNumber" .= get #randomnumber world)]五、六个测试端点的实现详解
5.1 JSON:返回固定 JSON 消息
实现于 FrameworkBenchmarks.hs#L15-L16。这是最轻量的测试,不访问数据库,直接通过renderJson输出:
action JsonAction = do renderJson (toJSON (object ["message" .= ("Hello, World!" :: Text)]))5.2 PLAINTEXT:返回纯文本
实现于 FrameworkBenchmarks.hs#L58-L59,同样不访问数据库:
action PlaintextAction = do renderPlain "Hello, World!"5.3 DB:单条世界记录查询
实现于 FrameworkBenchmarks.hs#L18-L21。逻辑为:生成1..10000范围内的随机 id,再按主键查询World记录并返回 JSON:
action DbAction = do randomNumber :: Int <- randomRIO (1, 10000) randomWorld <- query @World |> findBy #id (Id randomNumber) renderJson (toJSON randomWorld)这里使用了 IHP 的query @World |> findBy #id查询链,即按id字段精确查找的 ORM 操作。
5.4 QUERY:多次随机查询(支持 queries 参数)
实现于 FrameworkBenchmarks.hs#L23-L33。核心逻辑:
- 读取
queries查询参数,通过defaultParam容错(参数缺失或非法时回退为1); - 用
toBoundaries将数量钳制到[1, 500]区间; - 使用
Async.mapConcurrently并发执行 N 次随机记录查询,最后一次性renderJson返回结果数组。
action QueryAction = do let queries = defaultParam (paramOrError @Int "queries") |> toBoundaries let fetchRandomWorld i = do randomWorldId :: Id World <- Id <$> randomRIO (1, 10000) fetch randomWorldId [1..queries] |> Async.mapConcurrently fetchRandomWorld >>= renderJson where defaultParam (Right a) = a defaultParam _ = 1边界钳制函数toBoundaries定义在文件末尾(L61-L64),这是基准测试对queries参数的强制约束:
toBoundaries n | n < 1 = 1 | n > 500 = 500 | otherwise = n5.5 UPDATE:读取并更新随机记录
实现于 FrameworkBenchmarks.hs#L42-L56。与 QUERY 结构对称:同样解析并钳制queries参数、同样用Async.mapConcurrently并发执行;不同之处在于每次操作包含「取随机 id → 取记录 → 生成新的随机数 → 更新randomnumber字段 → 写回数据库」五步:
action UpdatesAction = do let queries :: Int = (defaultParam $ paramOrError @Int "queries" ) |> toBoundaries let updateRandomWorld i = do randomWorldId :: Id World <- Id <$> randomRIO (1, 10000) newRandom :: Int <- randomRIO (1, 10000) world <- fetch randomWorldId world |> set #randomnumber newRandom |> updateRecord [1..queries] |> Async.mapConcurrently updateRandomWorld >>= renderJson where defaultParam (Right a) = a defaultParam _ = 1其中set #randomnumber newRandom |> updateRecord是 IHP 的 ORM 更新链式写法:先修改字段,再调用updateRecord持久化。
5.6 FORTUNES:数据排序与 HSX 模板渲染
实现于 FrameworkBenchmarks.hs#L35-L40,是唯一需要完整页面渲染的测试:
- 取出全部
Fortune记录; - 新建一条消息为
"Additional fortune added at request time."的记录; - 按
message字段对合并后的列表排序; - 通过
renderHtml交给 HSX 视图渲染并响应。
action FortuneAction = do allFortunes :: [Fortune] <- query @Fortune |> fetch let newFortune :: Fortune = newRecord @Fortune |> set #message "Additional fortune added at request time." let compareFortunes :: Fortune -> Fortune -> Ordering = \f1 f2 -> compare (get #message f1) (get #message f2) let fortunes :: [Fortune] = sortBy compareFortunes (newFortune:allFortunes) renderHtml FortuneView { .. } >>= respondHtmlHTML 模板定义在 src/Web/View/FrameworkBenchmarks/Fortune.hs,使用 IHP 的 HSX 内嵌 HTML 语法生成符合规范的表格页面,并通过preEscapedToHtml声明<!DOCTYPE html>:
instance View FortuneView where beforeRender _ = setLayout \v -> v html FortuneView { .. } = preEscapedToHtml ("<!DOCTYPE html>" :: Text) <> [hsx| <html> <head><title>Fortunes</title></head> <body> <table> <tr> <th>id</th> <th>message</th> </tr> {forEach fortunes renderFortunes} </table> </body> </html> |]注意beforeRender中使用了setLayout \v -> v(恒等布局),即绕过 src/Web/View/Layout.hs 中默认的defaultLayout页面壳,直接输出最小化的测试页面,避免额外 HTML 开销。
六、运行配置与性能调优
6.1 应用级配置
src/Config/Config.hs 定义了应用启动参数,其中有两项直接影响基准性能:
config :: ConfigBuilder config = do option Production option (AppHostname "localhost") option (RequestLoggerMiddleware $ \application -> application) option (DBPoolIdleTime 10) option (DBPoolMaxConnections 512)Production:强制生产模式(与开发模式相比会加载/prod.css、/prod.js等精简资源,见 Layout.hs);DBPoolMaxConnections 512:将数据库连接池上限抬升到 512,支撑 QUERY / UPDATE 测试中的高并发查询(并发由Async.mapConcurrently发起);DBPoolIdleTime 10:空闲连接回收时间(秒)。
6.2 Docker 构建与启动参数
ihp.dockerfile 采用 Nix 方式构建(基于nixos/nix镜像、使用cachix use digitallyinduced获取 IHP 缓存),并通过 flake 构建optimized-prod-server产物。关键运行时配置:
ENV DATABASE_URL=postgres://benchmarkdbuser:benchmarkdbpass@tfb-database:5432/hello_world ENV PORT=8080 EXPOSE 8080 CMD ./result/bin/RunProdServer +RTS -A32m -N$(nproc) -qn2 -M2G -RTSDATABASE_URL:连接基准环境的 PostgreSQL(库名hello_world,账号benchmarkdbuser);PORT=8080:服务监听端口,与 README 中的测试 URL 一致;- GHC RTS 参数:
-A32m(扩大分配区减少 GC 次数)、-N$(nproc)(启用多核并行)、-qn2(限制并行 GC 线程)、-M2G(堆上限 2GB)。
6.3 Nix flake 依赖锁定
flake.nix 将 IHP 版本锁定为v1.3.1,并显式声明wai、text、cabal-install等构建依赖。nixpkgs-config.nix则通过make-nixpkgs-from-options.nix集成额外的 Haskell 包来源,保证构建可复现。
七、本地验证测试端点
按 README 提供的 URL,可以在构建并启动基准容器后逐一验证各测试端点:
# JSON 测试 curl http://localhost:8080/json # 期望输出: {"message":"Hello, World!"} # PLAINTEXT 测试 curl http://localhost:8080/plaintext # 期望输出: Hello, World! # DB 测试 curl http://localhost:8080/db # 期望输出: {"id":<随机id>,"randomNumber":<随机数>} # QUERY 测试(queries 指定查询条数) curl "http://localhost:8080/query?queries=10" # 期望输出: 10 条 World 记录的 JSON 数组 # UPDATE 测试(queries 指定更新条数) curl "http://localhost:8080/update?queries=10" # 期望输出: 10 条更新后 World 记录的 JSON 数组 # FORTUNES 测试 curl http://localhost:8080/fortunes # 期望输出: 按 message 排序的完整 HTML 表格(含追加的 fortune)验证时可结合queries参数的边界规则:传queries=0或负数会被钳制为 1,传超过 500 的值会被钳制为 500(对应 FrameworkBenchmarks.hs#L61-L64 的toBoundaries),缺省时回退为 1。
八、实现要点总结
综合 README 与源码,可以归纳 IHP 基准实现的关键设计:
- 单一控制器承载全部测试:六个 action 集中在 FrameworkBenchmarks.hs,便于基准工具按行号定位实现;
queries参数三段式处理:defaultParam容错回退 →paramOrError @Int解析 →toBoundaries钳制[1, 500],QUERY 与 UPDATE 共用这套规则;- 并发查询依赖库级能力:借助
Control.Concurrent.Async的mapConcurrently实现并行数据库访问,配合 512 连接上限的连接池(Config.hs)支撑高并发负载; - FORTUNES 走完整渲染链路:查询 → 追加记录 →
sortBy排序 → HSX 模板输出,且以恒等布局剥离多余页面壳,保持输出与基准规范一致; - 构建与运行链路清晰:flake 锁定 IHP v1.3.1,Dockerfile 以 Nix 产物
RunProdServer启动,数据库连接与端口通过环境变量注入。
以上内容全部可从 frameworks/Haskell/ihp 目录下的 README、源码与配置文件中直接验证,读者可按需深入各文件继续研究。
- 性能测试
- 测试
- 后端
- 开发者工具
【免费下载链接】FrameworkBenchmarks
Source for the TechEmpower Framework Benchmarks project
相关推荐
FrameworkBenchmarks 中的 Goji(Go)基准测试:基于极简 Web 框架的六项 TFB 测试实现解析
FrameworkBenchmarks 中的 Goji(Go)基准测试:基于极简 Web 框架的六项 TFB 测试实现解析 导读 本文以 FrameworkBe
性能测试测试后端开发者工具FrameworkBenchmarks 中的 Hertz 基准测试:Go 微服务框架的 TechEmpower 全栈实现解析
FrameworkBenchmarks 中的 Hertz 基准测试:Go 微服务框架的 TechEmpower 全栈实现解析 本文围绕 FrameworkBen
性能测试测试后端开发者工具Luminus 框架在 TechEmpower FrameworkBenchmarks 中的基准测试实现全解析
Luminus 框架在 TechEmpower FrameworkBenchmarks 中的基准测试实现全解析 Luminus 是 Clojure 社区主流的全
性能测试测试后端开发者工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考