news 2026/9/21 17:04:00

Ceph 分布式追踪实战:基于 LTTng 的事件追踪与 Blkin/Zipkin 端到端请求链路分析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Ceph 分布式追踪实战:基于 LTTng 的事件追踪与 Blkin/Zipkin 端到端请求链路分析
  • 存储
  • 分布式文件系统
  • 对象存储
  • 后端
  • 高可用

【免费下载链接】ceph

Ceph is a distributed object, block, and file storage platform

项目地址:https://gitcode.com/gh_mirrors/ce/ceph
点击查看免费下载

Ceph 作为统一的分布式对象、块与文件存储平台,其 I/O 请求会跨越客户端(librados/librbd)、消息层(Messenger)、Objecter、OSD 主处理线程直至底层对象存储等多个阶段。本指南围绕 doc/dev/blkin.rst 展开,系统讲解如何用 LTTng 用户态追踪(Userspace Tracing)为 Ceph 埋点、如何借助 Blkin 实现基于 Dapper 语义的跨阶段请求链路追踪,以及最终如何用 Zipkin 将追踪结果可视化。读完本文,你将掌握从编译开关、ceph.conf配置、vstart.sh本地集群验证到 Zipkin 展示的完整操作链路,并理解其底层 tracepoint 与 span 传播机制。

一、Ceph 追踪体系总览:tracepoint 是骨架,Blkin 是链路

Ceph 的追踪能力建立在两个层次上:

  1. LTTng 事件追踪(第一层):Ceph 在关键代码路径中插入 LTTng-UST tracepoint(用户态静态探针),用于观察某一阶段"发生了什么"(如 OSD 处理一个 op 的进入/退出)。所有 tracepoint 定义文件(.tp)与对应的 provider 共享库源码集中在 src/tracing 目录,包括bluestore.tpobjectstore.tposd.tpoprequest.tppg.tplibrados.tplibrbd.tprgw_op.tprgw_rados.tpmgroprequest.tpeventtrace.tp等。

  2. Blkin 分布式追踪(第二层):Blkin 是 Marios Kogias 等人创建的库,它实现 Dapper 追踪语义(trace_id/span_id/parent_span_id),用于把"一个请求从高层进入系统、到最终由 RADOS 服务完成"的因果链路串联起来,配合每个阶段的时延信息,实现端到端的请求路径可视化。由于底层仍由 LTTng 承载,开销很低且可实时采集,采集到的数据最终可交给 Twitter 的 Zipkin 展示。

需要特别留意的是:Blkin 功能在 Squid 版本中已被标记为弃用(deprecated),并将在后续版本中移除(见 doc/dev/blkin.rst 中的.. deprecated::声明)。而 LTTng 事件追踪仍是 Ceph 长期维护的能力,因此本文先完整展开 LTTng 部分,再讲解 Blkin/Zipkin 链路。

二、使用 LTTng 追踪 Ceph

2.1 编译:通过-DWITH_LTTNG=ON启用(默认开启)

LTTng 支持是 Ceph 默认编译选项(default: ON)。从源码编译时显式指定:

./do_cmake -DWITH_LTTNG=ON

在构建系统中,WITH_LTTNG会驱动 src/tracing/CMakeLists.txt 生成各 provider 共享库。该文件对每个.tp文件调用lttng-gen-tp生成头文件,并通过add_tracing_library打包出多个*.so

  • osd_tp(由oprequest.tposd.tppg.tp组成)
  • rados_tplibrados.tp
  • os_tpobjectstore.tp
  • bluestore_tpbluestore.tp
  • rgw_op_tprgw_rados_tp
  • mgr_op_tpmgroprequest.tp
  • rbd_tp(仅当WITH_RBD时构建)
  • cyg_profile_tp(仅当WITH_OSD_INSTRUMENT_FUNCTIONS时构建,见 2.5)
  • eventtrace_tp(仅当WITH_EVENTTRACE时构建)

这些共享库最终安装到${CMAKE_INSTALL_LIBDIR},运行时会由目标进程通过动态加载方式绑定到对应 tracepoint。

2.2 包安装:为什么缺少 tracepoint.so会导致 coredump

如果 Ceph 是通过 YUM / DNF / APT 等包管理器安装的(而非容器化部署),必须根据你要追踪的模块安装对应的开发包。文档明确列出:

librbd-devel librgw-devel librados-devel

原因在于:当进程(如ceph-osdradosgw)在运行时遇到缺失的 LTTng tracepoint 共享库(*.so)文件时,会因无法解析符号而直接 coredump。因此"编译时开了追踪、运行环境却没装齐 provider 库"是一个典型的崩溃场景,务必按需把三个-devel包装齐。

2.3 配置选项:在ceph.conf中开启对应追踪开关

编译启用只是第一步,追踪行为默认是关闭的。必须在ceph.conf中把目标开关设为true。下表汇总了当前可用的选项,并对照仓库中的配置定义(src/common/options/global.yaml.in、src/common/options/rbd.yaml.in、src/common/options/rgw.yaml.in)给出默认值(所有选项均为bool类型、advanced级别,默认false):

配置项默认值含义仓库配置位置
bluestore_tracingfalse启用 BlueStore 事件追踪global.yaml.in
event_tracingfalse通用事件追踪(需-DWITH_EVENTTRACE编译)global.yaml.in
osd_function_tracingfalseOSD 函数级插桩追踪(需-DWITH_OSD_INSTRUMENT_FUNCTIONSglobal.yaml.in
osd_objectstore_tracingfalse对象存储层追踪(实际即 filestore 追踪)global.yaml.in
rbd_tracingfalseRBD(librbd)LTTng-UST tracepointrbd.yaml.in
osd_tracingfalseOSD LTTng-UST tracepointglobal.yaml.in
rados_tracingfalselibrados LTTng-UST tracepointglobal.yaml.in
rgw_op_tracingfalseRGW 操作级追踪rgw.yaml.in
rgw_rados_tracingfalseRGW 调用 RADOS 层追踪rgw.yaml.in

从配置定义看,两个选项还可细读:

  • bluestore_tracing有配套的bluestore_throttle_trace_rate(float,默认 0,单位:每秒采样的 BlueStore 事务数),可用于控制追踪采样率、降低开销(见 global.yaml.in)。
  • 文档中括号标注了部分选项与编译开关的对应关系:event_tracing需要-DWITH_EVENTTRACEosd_function_tracing需要-DWITH_OSD_INSTRUMENT_FUNCTIONS

2.4 测试 Trace:从启动 LTTng 会话到查看结果

完整的测试流程如下(命令均来自 doc/dev/blkin.rst)。

第一步:启动 LTTng 会话守护进程

lttng-sessiond --daemonize

第二步:用 vstart 启动本地集群并注入追踪配置

../src/vstart.sh -d -n -l -e -o "osd_tracing = true"

其中-o是核心机制:它会把后面的键值对作为额外配置注入到生成的ceph.conf(当前 src/vstart.sh 中-o将参数追加进extra_conf)。其他常用开关如-n(新建集群)、-e(启用纠删码池,创建名为ec的池)在 src/vstart.sh 中有对应实现;-d(debug 模式)与-l等参数的具体含义以你所检出版本的./vstart.sh --help为准。如果你用的版本不支持某个短选项,直接通过-o注入osd_tracing = true也能达到同样的追踪开启效果。

第三步:列出用户态可用 tracepoint

lttng list --userspace

可以看到类似如下的输出(每个运行中的进程列出其绑定的 UST 事件):

UST events: ------------- PID: 100859 - Name: /path/to/ceph-osd pg:queue_op (loglevel: TRACE_DEBUG_LINE (13)) (type: tracepoint) osd:do_osd_op_post (loglevel: TRACE_DEBUG_LINE (13)) (type: tracepoint) osd:do_osd_op_pre_unknown (loglevel: TRACE_DEBUG_LINE (13)) (type: tracepoint) osd:do_osd_op_pre_copy_from (loglevel: TRACE_DEBUG_LINE (13)) (type: tracepoint) osd:do_osd_op_pre_copy_get (loglevel: TRACE_DEBUG_LINE (13)) (type: tracepoint) ...

第四步:创建会话、启用事件并开始追踪

lttng create trace-test lttng enable-event --userspace osd:* lttng start

osd:*会一次性启用osdprovider 下的全部事件;你也可以按需只启用某个事件,例如lttng enable-event --userspace osd:do_osd_op_pre_write

第五步:制造负载

rados bench -p ec 5 write

ec池执行 5 秒的写入基准测试,产生真实的 OSD 操作流。

第六步:停止追踪并查看结果

lttng stop lttng view

第七步:销毁会话

lttng destroy

2.5 源码视角:tracepoint 长什么样

以 src/tracing/osd.tp 为例,osdprovider 定义了非常细粒度的探针。每个TRACEPOINT_EVENT包含参数(TP_ARGS)与字段(TP_FIELDS)两部分,例如:

  • osd:prepare_tx_enter/osd:prepare_tx_exit:一次 OSD 事务准备阶段的进入/退出,字段为osd_reqid_t的四个分量(typenumtidinc);
  • osd:ms_fast_dispatchosd:opwq_process_start/osd:opwq_process_finish:消息快速分发与 op 工作队列处理的起止;
  • osd:do_osd_op_pre_*系列:按操作类型细分的"op 执行前"探针,覆盖readwritewritefullwritesamezerotruncatedeletecreateappendsetxattromap*copy_fromcallwatchchecksum等,字段通常包含对象名(oid)、快照(snap)、偏移(offset)、长度(length)、操作码(op)与操作名(opname);
  • osd:do_osd_op_post:op 执行完成后的探针,除上述字段外还携带执行结果(result)。

也就是说,osd:*这一组事件足以还原"一个对象操作在 OSD 内被解析、排队、执行、返回"的全过程。如果你需要做函数级的火焰图分析,可参考 src/tracing/README.md:Ceph 支持 GCC 的-finstrument-functions插桩(-DWITH_OSD_INSTRUMENT_FUNCTIONS=ON,目前仅 GCC 生效),进入/退出探针为lttng_ust_cyg_profile:func_enterlttng_ust_cyg_profile:func_exit,payload 中的addrcall_site可通过nm解析为函数名,后续可用 TraceCompass 生成火焰图,或用 libbabeltrace 编写自定义分析。

三、Blkin:端到端的请求链路追踪

3.1 Blkin 是什么

Blkin 的作用是追踪"一个请求从进入系统的高层(客户端)开始,直到被 RADOS 最终服务"的完整路径。其核心思想是实现 Dapper 的追踪语义:通过trace_id(一次请求的唯一标识)、span_id(每个处理阶段的标识)与parent_span_id(父阶段标识)表达各处理阶段之间的因果关系,目标是端到端可视化请求在系统中的路由,并附带每个阶段的时延信息。借助 LTTng,这一切可以以极小开销、实时地完成;LTTng 采集的 trace 再由 Twitter 的 Zipkin 做可视化。

再次强调:该功能在 Squid 版本中被标记为弃用,后续版本将移除(见 doc/dev/blkin.rst 的 deprecation 声明),本文按文档内容完整呈现其用法,供仍在旧版本上的维护者参考。

3.2 编译与配置

编译WITH_BLKIN依赖WITH_LTTNG,二者需同时开启):

./do_cmake -DWITH_LTTNG=ON -DWITH_BLKIN=ON

从构建系统看,WITH_BLKIN=ON时会执行add_subdirectory(blkin/blkin-lib)(见 src/CMakeLists.txt),把 Blkin 的 ztracer 库编入构建。相应地,src/common/zipkin_trace.h 在WITH_BLKIN宏下会#include <ztracer.hpp>引入真实实现;未开启时则退化为全空的 stub(TraceEndpoint所有方法均为 no-op,valid()恒为false),从而在关闭 Blkin 的构建中做到零开销。

配置:在ceph.conf中把以下选项设为true(均为booladvanced、默认false):

配置项默认值含义仓库配置位置
rbd_blkin_trace_allfalse为所有 RBD 请求创建 blkin tracerbd.yaml.in
osd_blkin_trace_allfalse为所有 OSD 请求创建 blkin traceglobal.yaml.in
osdc_blkin_trace_allfalse为所有 Objecter(客户端侧请求转发层)请求创建 blkin traceglobal.yaml.in

3.3 源码结构:zipkin_trace.h与 span 传播

src/common/zipkin_trace.h 定义了追踪的核心数据结构与接口:

  • struct blkin_trace_info:三个int64_t字段trace_idspan_idparent_span_id,并配套encode/decode,使其可以随 Ceph 的消息(buffer::list)跨进程序列化传递——这正是链路跨节点传播的基础;
  • ZTracer::Trace:提供init()(初始化 span)、keyval()(记录整数/字符串键值对,对应zipkin:keyval_integer/zipkin:keyval_string探针)、event()(记录事件,对应zipkin:timestamp探针)等方法;
  • ZTracer::Endpoint:表示服务端点(名称、IP、端口)。

3.4 调用链示例:Objecter 侧如何打 span

以客户端 Objecter 为例,src/osdc/Objecter.cc 在组装 MOSDOp 消息前有如下逻辑:

if (!op->trace.valid() && cct->_conf->osdc_blkin_trace_all) { op->trace.init("op", &trace_endpoint); }

即:当osdc_blkin_trace_all = true且该 op 尚未携带 trace 时,以服务名"op"和本地 endpoint 初始化一个新的 span。随后在发送消息时:

if (op->trace.valid()) { m->trace.init("op msg", nullptr, &op->trace); }

把父 span 挂到消息上随请求发出(src/osdc/Objecter.cc),从而让 OSD 端能延续同一trace_id生成子 span。期间还会记录"op submit""post op complete""osd op reply"等关键事件(src/osdc/Objecter.cc、src/osdc/Objecter.cc、src/osdc/Objecter.cc)。可见osdc_blkin_trace_all打开的是一条"客户端发起 → 消息携带 → OSD 处理 → 回复"的完整链路。

3.5 测试 Blkin:完整操作流程

假设 Ceph 尚未运行、且你已编译出带 Blkin 支持但未安装的版本,按文档在src目录用vstart.sh启动(OSD=3 MON=3 RGW=1表示 3 个 OSD、3 个 MON、1 个 RGW):

OSD=3 MON=3 RGW=1 ../src/vstart.sh -n -o "rbd_blkin_trace_all" lttng list --userspace

此时lttng list --userspace会看到每个进程都注册了 Blkin 的 zipkin 探针,例如:

UST events: ------------- PID: 8987 - Name: ./ceph-osd zipkin:timestamp (loglevel: TRACE_WARNING (4)) (type: tracepoint) zipkin:keyval_integer (loglevel: TRACE_WARNING (4)) (type: tracepoint) zipkin:keyval_string (loglevel: TRACE_WARNING (4)) (type: tracepoint) lttng_ust_tracelog:TRACE_DEBUG (loglevel: TRACE_DEBUG (14)) (type: tracepoint) PID: 8407 - Name: ./ceph-mon zipkin:timestamp (loglevel: TRACE_WARNING (4)) (type: tracepoint) zipkin:keyval_integer (loglevel: TRACE_WARNING (4)) (type: tracepoint) zipkin:keyval_string (loglevel: TRACE_WARNING (4)) (type: tracepoint) lttng_ust_tracelog:TRACE_DEBUG (loglevel: TRACE_DEBUG (14)) (type: tracepoint) ...

下一步是先停掉 Ceph,以便 tracepoint 能被 LTTng 会话启用(避免进程运行期间动态库绑定影响事件启用):

../src/stop.sh

创建 LTTng 会话并启用 zipkin 相关事件:

lttng create blkin-test lttng enable-event --userspace zipkin:timestamp lttng enable-event --userspace zipkin:keyval_integer lttng enable-event --userspace zipkin:keyval_string lttng start

重新启动 Ceph:

OSD=3 MON=3 RGW=1 ../src/vstart.sh -n -o "rbd_blkin_trace_all"

确认集群状态正常:

ceph status

然后通过rados做一轮"写入 → 列出 → 定位 → 读回 → 校验 → 删除"的完整对象操作:

ceph osd pool create test-blkin rados put test-object-1 ../src/vstart.sh --pool=test-blkin rados -p test-blkin ls ceph osd map test-blkin test-object-1 rados get test-object-1 ./vstart-copy.sh --pool=test-blkin md5sum vstart* rados rm test-object-1 --pool=test-blkin

也可以直接使用 examples/librados 中的示例程序,或用rados bench制造持续负载。

停止追踪并查看采集结果:

lttng stop lttng view

输出形如(每条记录都带有trace_idspan_idparent_span_id三件套,这正是还原链路的依据):

[15:33:08.884275486] (+0.000225472) ubuntu zipkin:timestamp: { cpu_id = 53 }, { trace_name = "op", service_name = "Objecter", port_no = 0, ip = "0.0.0.0", trace_id = 5485970765435202833, span_id = 5485970765435202833, parent_span_id = 0, event = "osd op reply" } [15:33:08.884614135] (+0.000002839) ubuntu zipkin:keyval_integer: { cpu_id = 10 }, { trace_name = "", service_name = "Messenger", port_no = 6805, ip = "0.0.0.0", trace_id = 7381732770245808782, span_id = 7387710183742669839, parent_span_id = 1205040135881905799, key = "tid", val = 2 } [15:33:08.884616431] (+0.000002296) ubuntu zipkin:keyval_string: { cpu_id = 10 }, { trace_name = "", service_name = "Messenger", port_no = 6805, ip = "0.0.0.0", trace_id = 7381732770245808782, span_id = 7387710183742669839, parent_span_id = 1205040135881905799, key = "entity type", val = "client" }

解读要点:

  • zipkin:timestamp记录的是"某个事件发生"的时间点与 span 归属(如 Objecter 收到osd op reply);
  • zipkin:keyval_integer/zipkin:keyval_string记录附加键值(如tidentity type = client),丰富 span 上下文;
  • 时间戳前的(+0.000xxx)是相对上一条记录的时间增量,可用于估算阶段时延。

四、用 Zipkin 可视化 Blkin 追踪结果

4.1 安装 Zipkin

Blkin 的价值在于可视化。Zipkin 同时作为 tracepoint 收集器与 Web 服务运行:可执行 jar 在9410端口启动 collector,在9411端口提供 Web 界面。

方式一:下载并运行 jar

git clone https://github.com/openzipkin/zipkin && cd zipkin wget -O zipkin.jar 'https://search.maven.org/remote_content?g=io.zipkin.java&a=zipkin-server&v=LATEST&c=exec' java -jar zipkin.jar

方式二:Docker 镜像

docker run -d -p 9411:9411 openzipkin/Zipkin

4.2 使用 babeltrace-zipkin 把 LTTng 数据送入 Zipkin

babeltrace-zipkin 项目负责读取 Blkin 产生的 LTTng trace,并通过 scribe 协议发送给 Zipkin collector:

git clone https://github.com/vears91/babeltrace-zipkin cd babeltrace-zipkin

发送命令的格式为:

python3 babeltrace_zipkin.py ${lttng-traces-dir}/${blkin-test}/ust/uid/0/64-bit/ -p ${zipkin-collector-port(9410 by default)} -s ${zipkin-collector-ip}

其中-p指定 Zipkin collector 端口(默认 9410),-s指定 collector 的 IP。实际示例:

python3 babeltrace_zipkin.py ~/lttng-traces-dir/blkin-test-20150225-160222/ust/uid/0/64-bit/ -p 9410 -s 127.0.0.1

注意~/lttng-traces-dir/...中的目录名blkin-test-<时间戳>lttng create blkin-test自动生成,ust/uid/0/64-bit/是 UST trace 在 trace 目录下的标准存放路径,请以实际目录为准。

4.3 在 Zipkin Web 上查看链路

浏览器访问 Zipkin Web 界面:

http://${zipkin-collector-ip}:9411

点击"Find traces",即可看到按trace_id聚合的请求链路。Zipkin 会以时间轴形式展示trace_name(如"op")、service_name(如ObjecterMessenger、OSD 等)以及各 span 的父子关系与耗时,从而直观地定位请求在哪个阶段出现瓶颈。

五、注意事项与后续方向

  1. 版本与弃用状态:Blkin 在 Squid 版本中被标记为弃用并将被移除,新项目请评估该约束;LTTng 事件追踪(第二节内容)不受影响。当前仓库配置中还保留了jaeger_tracing_enable等新一代追踪选项(见 global.yaml.in),可作为迁移/后续方向的参考。
  2. 编译与运行环境必须一致:编译时开启WITH_LTTNG/WITH_BLKIN后,运行环境必须安装对应的 tracepoint 共享库(librbd-devellibrgw-devellibrados-devel),否则进程可能因缺少*.so而 coredump。
  3. 配置开关默认关闭:所有追踪相关配置项默认均为false,无论编译是否开启,都需在ceph.conf(或 vstart 的-o注入)中显式开启,且建议只在排障窗口期开启,避免常驻性能开销。
  4. 调试闭环lttng list --userspace是确认探针是否注册的第一手段;lttng view输出的三件套(trace_id/span_id/parent_span_id)是人工校验链路是否完整的最快途径。若需程序化分析,可参照 src/tracing/README.md 使用 libbabeltrace 编写分析工具,或用 TraceCompass 生成火焰图。

参考路径速查

  • 本文档源:doc/dev/blkin.rst
  • tracepoint 定义与构建:src/tracing、src/tracing/CMakeLists.txt、src/tracing/osd.tp
  • LTTng/函数插桩说明:src/tracing/README.md
  • Blkin 追踪头文件:src/common/zipkin_trace.h
  • Blkin 编译开关:src/CMakeLists.txt
  • 配置项定义:src/common/options/global.yaml.in、src/common/options/rbd.yaml.in、src/common/options/rgw.yaml.in
  • Objecter 端 span 传播:src/osdc/Objecter.cc
  • vstart 本地集群脚本:src/vstart.sh
  • 存储
  • 分布式文件系统
  • 对象存储
  • 后端
  • 高可用

【免费下载链接】ceph

Ceph is a distributed object, block, and file storage platform

项目地址:https://gitcode.com/gh_mirrors/ce/ceph
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/21 16:59:41

DLSS Swapper 教程:免费切换 DLSS/FSR/XeSS 版本,不用等游戏更新

DLSS Swapper 教程&#xff1a;免费切换 DLSS/FSR/XeSS 版本&#xff0c;不用等游戏更新 【免费下载链接】dlss-swapper 项目地址: https://gitcode.com/GitHub_Trending/dl/dlss-swapper DLSS Swapper 是一款免费的 DLSS 版本切换工具&#xff1a;它让你直接下载、管理…

作者头像 李华
网站建设 2026/9/21 16:45:47

可再生能源与电动汽车协同调度的两阶段随机规划实现

1. 项目背景与研究价值在能源结构转型的大背景下&#xff0c;这个硕士论文复现项目聚焦于解决两个关键问题&#xff1a;如何高效消纳波动性强的可再生能源发电&#xff0c;以及如何利用规模化电动汽车&#xff08;EV&#xff09;的灵活性实现电网协同优化。我之所以选择复现这个…

作者头像 李华
网站建设 2026/9/21 16:43:58

PyCharm安装pyserial全攻略:从pip报错到串口联调实战

前阵子做一个小工具&#xff0c;要用HC05蓝牙模块和电脑串口通信&#xff0c;结果在PyCharm里第一步就栽在了装pyserial上。这个库看着简单&#xff0c;网上一搜教程满天飞&#xff0c;可真照着操作下来&#xff0c;各种报错照样能把人绕晕&#xff1a;“pip不是内部或外部命令…

作者头像 李华
网站建设 2026/9/21 16:41:59

TiXL InvertFloat 操作符详解:浮点数值取反的节点实现与实战用法

TiXL InvertFloat 操作符详解&#xff1a;浮点数值取反的节点实现与实战用法 【免费下载链接】t3 TiXL is an open source software to create realtime motion graphics. 项目地址: https://gitcode.com/GitHub_Trending/t3/t3 InvertFloat 是 TiXL 操作符库中一个轻量…

作者头像 李华
网站建设 2026/9/21 16:40:08

VisionMaster图像自动上传FTP服务器配置详解与故障排查指南

1. 整体思路&#xff1a;为什么要用FTP自动上传图像做视觉项目最容易被忽略、却又最绕不开的需求&#xff0c;就是把检测图像自动留档。尤其是现场部署了VisionMaster的产线&#xff0c;客户大概率会提一句“图片能不能自动存到服务器上&#xff0c;后面好追溯”。人工拷贝不现…

作者头像 李华