Apache Marmotta 介绍与核心概念

介绍 Apache Marmotta 3.4.0 的项目定位、发展历程、KiWi 存储架构与 RDF 三元组、SPARQL、LDP 等核心概念,并结合 Ubuntu 22.04 实测行为说明各组件的真实接口表现。

Apache MarmottaMarmottaApache知识图谱2026/10/15

Apache Marmotta 介绍与核心概念

一、项目概览与定位

Apache Marmotta 官方对其的一句话定位是"An Open Platform for Linked Data",即一个开放的关联数据平台。它将三个能力打包在同一个 Java Web 应用中:其一是一个原生的 RDF 三元组存储引擎 KiWi,负责持久化并检索以主谓宾三元组形式表达的事实;其二是一个符合 SPARQL 1.1 规范的查询与更新端点,外界可以通过标准 HTTP 协议对其中的知识进行读写;其三是一个符合 W3C Linked Data Platform(LDP)规范的资源服务,允许以 REST 风格创建、读取、修改和删除关联数据资源。在 Ubuntu 22.04 环境实测中,将 3.4.0 版本的 war 包部署进 Tomcat 9 后,响应头中会明确返回 Server: Apache Marmotta/3.4.0 (build 0),这三个能力分别对应 /marmotta/sparql 与 /marmotta/ldp 两组入口,无需任何额外安装动作即可使用。

对熟悉知识图谱领域的读者而言,可以把 Marmotta 理解为一个"中间规模"的选择:它不像商业图数据库那样提供分布式集群与百亿级边遍历能力,也不像 Jena Fuseki 那样只专注于存储与查询本身,而是在存储之上叠加了 LDPath 查询语言、自定义 RDF 导入、用户与权限管理、资源版本化等平台级功能,目标是让机构能够在自有服务器上搭建一套可控的关联数据发布与集成环境。这种定位也解释了它后来的命运——当社区维护动力减弱、同类组件各自成熟之后,项目于 2020 年 11 月宣布退役,2021 年 4 月正式进入 Apache Attic,终版为 3.4.0。因此本文所介绍的一切特性都应放在"适合学习与遗产系统维护,不建议新项目采用"这一前提下阅读。

二、发展历程与退役现状

Marmotta 的前身是萨尔茨堡大学与相关机构联合开发的 KiWi 项目,2012 年 4 月以"Marmotta"之名进入 Apache 孵化器,2013 年 11 月毕业成为 Apache 顶级项目。项目在 2014 年至 2015 年间最为活跃,先后发布了 3.0 到 3.3 系列版本,LDP 实现在那个阶段通过了 W3C 的 LDP 实现报告验证——这一点在实测中仍有痕迹:向 /marmotta/ldp 发起 POST 创建资源时,响应头的 Link 字段会返回 <http://wiki.apache.org/marmotta/LDPImplementationReport/2014-09-16>; rel="http://www.w3.org/ns/ldp#constrainedBy",指向的就是 2014 年 9 月的实现报告页面。2018 年发布的 3.4.0 是迄今最后一个版本,此后社区提交逐渐停滞,2020 年 11 月 Apache 董事会通过退役决议,2021 年 4 月迁移至 Attic 完成。

退役后的现状对使用者有三个直接影响。第一,发行包不再出现在 Apache 主分发目录的最新推荐列表中,但 dlcdn 与 archive.apache.org 仍保留着 3.4.0 的完整安装包与校验文件,实测下载的 apache-marmotta-3.4.0-installer.tar.gz 为 47,775,588 字节,SHA1 摘要 228e323c44f333db8be9d399e10558bc2117141d 与官方 .sha1 文件完全一致,包的完整性不受退役影响。第二,官网 marmotta.apache.org 仍在线,但内容停留在退役公告快照,文档中的下载链接多数需要手工改到 dlcdn 路径。第三,源码仓库 gitbox.apache.org/repos/asf#marmotta 转为只读,不会再有任何安全补丁,这是"不建议新项目采用"的根本原因:一旦部署环境中出现针对其依赖链(旧版 Sesame、旧版 H2 等)的新漏洞,官方不会再发布修复。对存量系统而言,合理策略是锁定 3.4.0 版本、收窄网络暴露面,并把迁移评估(例如迁往 Jena Fuseki 或 GraphDB)提上日程。

三、总体架构

Marmotta 的架构可以自底向上分为四层。最底层是存储抽象层,默认实现是内嵌的 H2 数据库,实测部署后数据文件落盘为 /opt/marmotta/home/db/marmotta.mv.db,系统生成的 system-config.properties 中记录了完整的连接串 database.h2.url = jdbc:h2:/opt/marmotta/home/db/marmotta;MVCC=true;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=10,也可切换为 MySQL 或 PostgreSQL 等外部关系库。其上是 KiWi 三元组引擎层,它把 RDF 三元组按节点表与三元组表的关系模式存进上述关系库,并对 SPARQL 查询做基于上下文的索引优化,kiwi.version = 3.4.0 就记录在同一配置文件里。再往上是服务层,以模块化 Web 服务的形式提供 SPARQL 端点、LDP 资源服务、LDPath 查询、导入导出、用户与权限等能力,每个模块都是 war 内的一个 REST 子应用,按 /marmotta/<模块名> 前缀挂载。最外层是标准的 Java Web 运行环境,3.4.0 支持部署进 Tomcat 等符合 Servlet 规范的运行环境,实测使用 Ubuntu 22.04 软件源中的 Tomcat 9.0.58 即可正常运行。

这种分层的直接好处是替换成本低:存储层通过配置文件切换后端而不必改动服务层;服务层各模块可以按需启停,例如不需要版本管理时可以在管理界面关闭相应模块减少资源占用。对部署者来说,真正需要关心的只有两个外部接口——数据库连接参数和 web 应用的家目录 marmotta.home,后者决定配置、数据、日志、导入文件等所有运行产物的落盘位置,实测通过 Tomcat 的 Context 描述文件以 <Parameter name="marmotta.home" value="/opt/marmotta/home" override="false"/> 方式指定,应用首次启动时会自动在该目录下创建 config、db、import、log、resources、templates 等子目录,这一行为本身就是验证部署是否成功的第一手信号。

四、核心概念:RDF 与三元组

RDF(Resource Description Framework)是 Marmotta 存储的基本数据模型,一切知识都被拆解为"主语-谓语-宾语"三元组。主语和谓语必须是 URI,宾语可以是 URI 也可以是带数据类型的字面量。实测中向 SPARQL 更新端点提交 PREFIX ex: <http://example.org/> INSERT DATA { ex:quzuo ex:name "Apache Marmotta Test" . ex:quzuo ex:year 2026 . } 之后,查询返回的结果清晰地展示了这两种宾语的差异:ex:name 对应的宾语是普通字面量 "Apache Marmotta Test",类型为空;而 ex:year 对应的 2026 被引擎自动补上了 http://www.w3.org/2001/XMLSchema#integer 数据类型标记,说明 KiWi 在写入时会做字面量类型推断与规范化。理解这一点对后续数据清洗很重要——同一数值以带类型整数和不带类型字符串两种形式写入,会被当成两个不同的宾语节点。

命名空间与前缀是 RDF 的第二个核心概念。三元组中的每个 URI 都可能很长,SPARQL 允许用 PREFIX ex: <http://example.org/> 声明前缀后以 ex:quzuo 的紧凑形式书写。Marmotta 对此没有任何私有扩展,完全遵循 W3C 语法,因此任何标准 SPARQL 教程的知识都可以直接套用。第三个值得强调的概念是"命名图"(Named Graph):KiWi 支持把一组三元组划入不同的上下文(context)中管理,LDP 子系统内部就利用了这一机制来隔离每个资源页面的元数据与正文。默认情况下不指定图名时写入的是默认图,实测用 SELECT ?s ?p ?o WHERE { ?s ?p ?o } LIMIT 5 查询时,除了刚插入的业务数据外还能看到系统自动生成的图数据,例如 <http://localhost:8080/marmotta/ldp> 这个根资源节点带有 rdfs:label "Marmotta's LDP Root Container" 以及 rdf:type 指向 ldp:Resource、ldp:RDFSource、ldp:Container、ldp:BasicContainer 的多条类型声明——这些都是引擎初始化时写入的系统级三元组,初学者查询时看到它们不必惊讶。

五、核心概念:SPARQL 端点

Marmotta 的 SPARQL 端点提供 SPARQL 1.1 全套查询形态的入口,实测 SELECT、ASK、CONSTRUCT、DESCRIBE 均可用——例如 ASK 查询返回 {"head":{},"boolean":true} 的布尔结果,CONSTRUCT 查询按请求的 Accept 头返回 Turtle 等图格式,这为"先探测再取数"的客户端模式提供了标准支持。

SPARQL 之于 RDF,就如同 SQL 之于关系表。Marmotta 在 /marmotta/sparql 前缀下提供了一组标准端点,实测行为如下。查询端点 /marmotta/sparql/select 接受 query 参数,不带任何参数直接访问会返回 HTTP 400,提示缺少查询语句;带查询访问时返回体默认是 SPARQL Results XML 格式,实测一次未指定格式的查询返回了以 <?xml version='1.0' encoding='UTF-8'?> 开头、以 xmlns='http://www.w3.org/2005/sparql-results#' 为结果命名空间的 XML 文档;若客户端在请求中携带 Accept: application/sparql-results+json 头,服务端会切换为 JSON 输出,实测返回的结构是标准的 {"head":{"vars":[...]},"results":{"bindings":[...]}} 形式,每个绑定值都带有 type(uri 或 literal)与 value 字段,带类型的字面量还额外携带 datatype 字段。这套内容协商行为完全遵循 SPARQL 1.1 Protocol,意味着任何支持该协议的客户端库(如 Apache Jena 的 RDFConnection、Python 的 rdflib)都能直接对接。

更新端点 /marmotta/sparql/update 接受表单方式提交的 update 参数,实测用 curl -X POST --data-urlencode "update=INSERT DATA {...}" 提交一段 INSERT DATA 语句,服务端返回 HTTP 200、响应体为空,随后用查询端点即可检索到刚写入的两条三元组,整条链路无需重启服务。需要注意的边界行为有两条:一是 /marmotta/sparql 这个不带子路径的入口会返回 303 重定向到内置的查询工作台页面,浏览器用户从这里可以手写 SPARQL 并即时看到表格化结果,适合调试;二是 /marmotta/meta 端点返回 405,说明它只接受特定方法而非 GET。对自动化脚本而言,推荐始终走 select/update 两个纯协议端点并显式声明 Accept 头,避免解析重定向与页面外壳。

六、核心概念:Linked Data Platform 与 LDP 资源

LDP 是 W3C 定义的一组基于 HTTP 的规范,把"关联数据资源"的增删改查标准化为普通的 POST/GET/PUT/DELETE 语义。Marmotta 的 /marmotta/ldp 是一个 LDP 根资源(Root Resource,类型为 BasicContainer),实测交互全流程如下。向根资源 POST 一段 Turtle 文档并携带 Slug: demo-note 头与 Content-Type: text/turtle 头,服务端返回 HTTP 201,响应头中 Location: http://localhost:8080/marmotta/ldp/demo-note 给出新资源的地址,多个 Link 头依次声明该资源属于 ldp:Resource、ldp:RDFSource、ldp:Container、ldp:BasicContainer 类型并给出约束说明链接。随后 GET 该地址并携带 Accept: text/turtle,返回的 Turtle 文档里既有 POST 时提交的业务三元组(<http://example.org/title> "Hello Marmotta"、<http://example.org/body> "Created via LDP POST on Ubuntu 22.04"),也有服务端自动维护的元数据——dcterms:created 与 dcterms:modified 精确到秒的创建修改时间,以及 ldp:interactionModel ldp:Container 等交互模型声明。这意味着使用方完全不必手写元数据,平台已按 Dublin Core 词表代为记录。

删除行为同样符合规范:对资源地址发起 DELETE,服务端返回 204;紧接着再 GET 同一地址,返回的不是常见的 404 而是 HTTP 410 Gone,明确表达"该资源曾经存在且已被删除",这一语义可以避免下游同步程序把删除误判为"尚未创建"。LDP 的价值在于它让知识图谱的维护摆脱了对专用导入工具的依赖——任何能发 HTTP 请求的程序(脚本、编辑器插件、其他系统的事件回调)都能以统一的REST方式向库中增删资源,这正是 Marmotta 作为"发布平台"区别于纯存储引擎的关键设计。实测中,一次 POST 加两次 GET 加一次 DELETE 的完整往返,从发起到确认删除总耗时在秒级以内完成,资源创建即上线,无需任何显式的"提交"或"发布"动作。

七、存储引擎与持久化

Marmotta 默认使用 H2 内嵌数据库承载 KiWi 三元组引擎,这一设计让首次部署零依赖——不需要预装任何数据库服务,解压 war、启动 Tomcat 后数据即可落盘。实测中 H2 数据文件为 /opt/marmotta/home/db/marmotta.mv.db(282,624 字节),同目录还会出现记录异常信息的 marmotta.trace.db。连接参数由应用首次启动时自动生成到 /opt/marmotta/home/system-config.properties,其中 database.user 与 database.password 默认均为 sa,DB_CLOSE_DELAY=10 保证短暂的请求间隙内连接不会被立即关闭。备份策略因此非常直接:停止 Tomcat 后拷贝整个 db 目录即可获得一致性快照,恢复时反向覆盖。需要注意 H2 文件不能在服务运行时直接拷贝,否则可能得到写入一半的页。

当数据规模增长到千万级三元组或需要多应用共享数据库时,可以把 system-config.properties 中的 database.type 切换为 mysql 或 postgres 并填写对应连接参数,KiWi 会自动建表并迁移元数据结构。这一设计在今天看来仍有参考价值:三元组按节点值字典表加三元组映射表的方式存储,字符串节点去重后以整数 ID 参与三元组索引,是典型的"字典编码 + 多索引"关系化 RDF 存储方案。对学习者而言,Marmotta 的 KiWi 源码至今仍是理解这类存储引擎实现的良好教材;对遗产系统维护者而言,理解这一层则意味着可以直接在数据库层面做核查与修复,而不必完全依赖上层接口。

八、模块组成与扩展点

3.4.0 的发行包按功能划分为多个模块,部署后以 REST 子路径与服务类的形式共存于同一个 war 中。marmotta-core 提供配置管理、模块生命周期与公共工具;kiwi 模块组(kiwi-core、kiwi-loader、kiwi-reasoner、kiwi-sparql、kiwi-versioning)承担存储与推理,其中 kiwi-loader 支持批量导入 N-Triples 等格式的数据文件,kiwi-reasoner 可配置 OWL-RL 风格的自定义推理规则,kiwi-versioning 提供资源级版本追踪;ldpath 模块实现了 LDPath 查询语言,一种面向关联数据图遍历的类 XPath 语法,适合从复杂图中抽取扁平视图;sparql 模块即第五节所述的端点集合;ldp 模块实现 LDP 规范;此外还有 importer(从 URL 或文件批量抓取 RDF)、user(用户账户)、security(权限策略)、geo(地理空间扩展)等。模块化带来的运维优势是可观测性好:出问题时可以从日志中按模块名快速定位子系统,配置层面也可以按模块独立开关。

模块化的印证可以从发行物本身找到。实测 webapp 包解压后的 WEB-INF 目录包含 classes、jetty-web.xml、lib 与 web.xml 四项,其中 lib 目录装载 169 个 jar——数量远少于全模块安装的形态,说明二进制 webapp 包在打包时就按核心集裁剪过。web.xml 之外还有一份 jetty-web.xml,这是 Tomcat 专用的嵌入式 Jetty 配置描述,解释了一个部署现象:把 war 直接放进 Tomcat 的 webapps 时,无需额外安装 Jetty 运行时,war 自带的 jetty-web.xml 会在启动时向 Servlet 运行环境声明所需的连接器参数。对维护者而言,判断一个 Marmotta 实例启用了哪些功能模块,最可靠的办法不是翻文档而是直接探测 URL 路径——实测在 webapp 包部署的实例上,/ldp 与 /sparql 正常服务,而 /ldpath、/webadmin、/importer 均返回 404,说明这些模块未包含在当前打包集内;模块是否存在随发行物形态而异,这是排障前必须先核对的第一层事实。

扩展点上,除配置化选项外,Marmotta 支持以自定义模块(custom module)方式把开发者自己的 Web 应用嵌入主 war 一起构建,这需要从源码发行包重新打包,适合需要在平台内部署业务页面的场景。对今天的使用者来说,更现实的做法是把 Marmotta 当作一个独立的关联数据服务,通过其标准 HTTP 接口与外部系统集成,而不是深度定制内部代码——毕竟项目已停止维护,定制的部分将永远由使用方自行承担维护责任。

九、适用场景与同类对比

综合实测表现与项目现状,Marmotta 适合的场景有三类。第一是教学与源码研究:它的代码规模适中、分层清晰,覆盖了从存储引擎到 LDP、SPARQL 的完整链路,是学习关联数据平台内部实现的优质样本。第二是存量系统维护:如果某个机构的知识库基于 Marmotta 搭建且数据仍在稳定产出,继续以锁定版本的方式运行它,并配合收窄访问范围、定期备份 H2 数据文件的运维策略,成本远低于仓促重写。第三是小型静态知识库的临时发布:在完全离线、数据量在百万三元组以内的场景下,它的开箱即用特性依然有优势。

与同类组件对比:Apache Jena Fuseki 同样提供 SPARQL 端点与 REST 接口,且处于活跃维护状态,存储引擎 TDB2 在查询性能与稳定性上均优于 KiWi,是新项目的首选;Virtuoso 面向大规模数据但部署较重;GraphDB 增强了推理与连接器生态但为商业许可。Marmotta 的相对优势集中在 LDP 实现的完整性与 LDPath 等平台级附加能力上,而这些能力如今在 Fuseki 生态中也有替代路径。因此在做技术选型时,除非有明确的遗产兼容需求,否则应优先考虑仍在维护的组件;选择 Marmotta 的理由应当被明确表述为"学习目的"或"延续既有系统"。

十、常见问题(FAQ)

1. Marmotta 项目现在还能下载吗?官方状态是什么? 能。项目 2020 年 11 月退役、2021 年 4 月进入 Apache Attic,但 dlcdn 分发目录仍保留 3.4.0 终版的安装包,实测 apache-marmotta-3.4.0-installer.tar.gz(47,775,588 字节)的 SHA1 与官方校验文件一致。官方明确不再提供任何更新与安全补丁,定位是学习与遗产维护,不建议新项目采用。

2. 它支持哪些 SPARQL 版本? 支持 SPARQL 1.1 查询与更新。实测 SELECT、INSERT DATA、COUNT 聚合均可通过 /marmotta/sparql/select 与 /marmotta/sparql/update 端点正常工作,结果格式遵循 SPARQL 1.1 Query Results JSON/XML 规范,通过 Accept 头即可切换。

3. 查询端点返回的总是 XML 吗?怎么拿到 JSON? 不带 Accept 头时默认返回 SPARQL Results XML;请求时携带 Accept: application/sparql-results+json 即返回 JSON。实测 JSON 结果的顶层结构为 head/results,每个绑定带 type 与 value 字段。

4. LDP 删除资源后为什么返回 410 而不是 404? 这是 LDP 规范要求的语义:410 Gone 表示资源曾存在且已被显式删除。实测 DELETE 返回 204 后,对同一地址 GET 确实返回 410,客户端可据此区分"从未存在"与"已被删除"。

5. 数据默认存在哪里?如何备份? 默认在应用家目录的 db 子目录,实测为 /opt/marmotta/home/db/marmotta.mv.db(H2 格式)。备份方法是停止 Tomcat 后整目录拷贝;运行中直接拷贝可能得到不一致快照。也可改配置切换到 MySQL/PostgreSQL 后按数据库自身方式备份。

6. marmotta.home 目录是必须配置的吗?不配会怎样? 不显式配置时应用会使用默认位置,但生产部署强烈建议显式指定。实测通过 Tomcat Context 文件的 <Parameter name="marmotta.home" ... override="false"/> 指定到 /opt/marmotta/home 后,应用首启自动创建 config/db/import/log/resources/templates 子目录,数据与配置集中、便于备份与权限管控。若运行环境对文件系统有只读保护,还需为该目录显式授权,否则启动时报 "could not create directory"。

7. 为什么插入的整数会被加上 XMLSchema#integer 类型? KiWi 写入时对字面量做类型推断与规范化,纯数字串会被识别为 xsd:integer。查询结果中该宾语的 datatype 字段即为证据。若需要原样字符串,写入时应加引号并显式声明为 xsd:string 或使用语言标签。

8. 能否把已有 RDF 文件批量导入? 可以。一类途径是通过 importer 模块从 URL 或上传文件导入 N-Triples/RDF-XML/Turtle 等格式;另一类是通过 SPARQL update 的 LOAD 语句从可达地址拉取;大批量场景推荐 kiwi-loader 命令行工具直接写入存储层。

9. 部署需要什么版本的 Java 与运行环境? 3.4.0 按 Java 8 时代构建,实测在 OpenJDK 8 上运行正常;更高的 Java 版本会遇到旧依赖的字节码或序列化兼容问题。Web 运行环境使用 Tomcat 9 实测可用,响应头返回 Apache Marmotta/3.4.0 (build 0)。

10. 新项目应该选 Marmotta 吗? 不应。项目已停止维护,依赖链中的旧组件不会再有安全更新。新项目建议评估 Apache Jena Fuseki 等活跃组件;仅在维护既有系统或做源码学习时选择 Marmotta,并锁定 3.4.0 版本运行。

11. 安全模块默认启用了哪些约束? 发行配置自带一组基于来源与路径的写操作约束,实测安全日志中记录了这些规则的装载过程,其中包括对 /config/、/transaction/ 等管理路径的写限制,以及通过 system-config.properties 中 security.restriction 前缀配置项声明的主机条件。这些规则按来源地址匹配,默认配置对本机来源放行管理操作;遗产系统接手时应先通读安全日志与配置文件确认实际生效的规则集,再决定是否收紧。

12. webapp 包和 installer 包的功能范围有差别吗? 有。实测 webapp 包部署的实例上 /ldpath、/webadmin、/importer 等路径返回 404,说明该打包集以核心模块为主;installer 包安装时会给出模块勾选界面,可按需勾入更多模块。选型前先探测目标实例的可用路径,是判断功能边界最直接的方法。

参考链接