2024年10月

创建一个.net 8的webapi项目备用
0
编辑一个实体类,该实体类用于存储获取ip地址的经纬度数据使用
0
继续编辑三个类,用来存储对应经纬度的具体天气数据包:
0
改造默认的天气控制器,里面写成我们自己的。例如先写个获取IP的经纬度坐标的请求
0
运行一下,看下现在的效果,可以看到获取到ip的具体信息,包括运营商、经纬度坐标等。
0
继续拓展内容,根据经纬度获取天气数据
0
运行,可以看到一些天气信息
0
以下是返回的具体json报文,供参考:
{"latitude": 22.5,"longitude": 114,"generationtime_ms": 0.05698204040527344,"utc_offset_seconds": 0,"timezone": "GMT","timezone_abbreviation": "GMT","elevation": 37,"current_weather_units": {"time": "iso8601","interval": "seconds","temperature": "°C","windspeed": "km/h","winddirection": "°","is_day": "","weathercode": "wmo code"},"current_weather": {"time": "2024-10-11T09:45","interval": 900,"temperature": 26.1,"windspeed": 6.6,"winddirection": 131,"is_day": 1,"weathercode": 2}
}

数据字段解析
基本信息
  • latitude: 22.5 - 纬度。表示该地点的纬度位置。
  • longitude: 114 - 经度。表示该地点的经度位置。
  • generationtime_ms: 0.05698204040527344 - 数据生成的时间,单位是毫秒。表示从数据请求到数据生成所用的时间。
  • utc_offset_seconds: 0 - 与 UTC 时间的偏移,单位为秒。这里为 0 表示该数据是在 GMT 时区下计算的。
  • timezone: "GMT" - 时区,表示为 GMT。
  • timezone_abbreviation: "GMT" - 时区缩写,这里也是 GMT。
  • elevation: 37 - 表示该地点的海拔高度为 37 米。
当前天气单位
current_weather_units 部分用于解释当前天气数据中每个字段的单位。
  • time: "iso8601" - 时间格式遵循 ISO 8601 标准。
  • interval: "seconds" - 时间间隔单位为秒。
  • temperature: "°C" - 温度单位为摄氏度。
  • windspeed: "km/h" - 风速单位为每小时公里数。
  • winddirection: "°" - 风向单位是角度,以度数表示。
  • is_day: "" - 此字段没有单位,仅用来指示是否是白天(通常为 0 或 1)。
  • weathercode: "wmo code" - 表示天气情况的代码,使用 WMO(世界气象组织)标准代码。
当前天气情况
current_weather 部分提供了实际的天气数据。
  • time: "2024-10-11T09:45" - 表示观测的具体时间点,格式为 ISO 8601,即 2024 年 10 月 11 日 09:45。此处我们要换算成自己的东八区时间,要增加8小时,则是下午17点45分。
  • interval: 900 - 表示观测间隔为 900 秒,即 15 分钟。
  • temperature: 26.1 - 当前温度为 26.1°C。
  • windspeed: 6.6 - 当前风速为 6.6 km/h。
  • winddirection: 131 - 当前风向为 131 度。风向用度数表示,0 度表示正北,90 度表示正东,131 度则大致为东南方向。
  • is_day: 1 - 表示当前观测是在白天,1 代表白天,0 通常代表夜晚。
  • weathercode: 2 - 当前的天气代码为 2,按照 WMO 的天气代码标准,2 通常表示“部分多云”。
WMO 天气代码详解
根据 WMO(世界气象组织)的标准,weathercode 字段提供了天气状况的具体信息。以下是一些常见的 WMO 天气代码:
  • 0: 晴朗
  • 1: 主要晴朗
  • 2: 部分多云
  • 3: 多云
  • 4: 阴天
  • 45: 有雾
  • 48: 有浓雾
  • 51: 轻微毛毛雨
  • 53: 中等毛毛雨
  • 55: 强毛毛雨
  • 61: 轻微阵雨
  • 63: 中等阵雨
  • 65: 强阵雨
  • 71: 轻雪
  • 73: 中雪
  • 75: 强雪
  • 95: 雷阵雨,可能有轻微的降水
  • 99: 雷阵雨,伴有强降水
天气描述
在当前的数据中,weathercode 为 2,这表示天气状况为“部分多云”。
获取经纬度和天气的在线请求代码:
LocationInfo locationInfo = null;using (var httpClient =_httpClientFactory.CreateClient())
{
httpClient.Timeout
= TimeSpan.FromSeconds(100);var res = httpClient.GetAsync("http://ip-api.com/json/").GetAwaiter().GetResult();
res.EnsureSuccessStatusCode();
var location =res.Content.ReadAsStringAsync().GetAwaiter().GetResult();if (!string.IsNullOrEmpty(location))
{
locationInfo
= JsonConvert.DeserializeObject<LocationInfo>(location);
}
}
if (locationInfo != null)
{
using (var httpClient =_httpClientFactory.CreateClient())
{
httpClient.Timeout
= TimeSpan.FromSeconds(100);var res = httpClient.GetAsync($"https://api.open-meteo.com/v1/forecast?latitude={locationInfo.lat}&longitude={locationInfo.lon}&current_weather=true").GetAwaiter().GetResult();
res.EnsureSuccessStatusCode();
var weather =res.Content.ReadAsStringAsync().GetAwaiter().GetResult();if (!string.IsNullOrEmpty(weather))
{
WeatherResponse weatherInfo
= JsonConvert.DeserializeObject<WeatherResponse>(weather);returnOk(weatherInfo);
}
}
}

如果需要完整源码,可以在公众号【Dotnet Dancer】内回复“天气查询”即可获取源码地址。

以上就是本文章全部内容,如果有帮助,欢迎点赞、在看、转发分享或评论,谢谢大佬们捧场~

参考文献

问题产生的原因

后端有个文件上传服务,前端可以直接像文件上传到服务器,但这个上传服务除了有form-data文件流之外,还需要有其它key/value的表单参数,这些参数是固定的,或者有一定的规则,这时我们通过apisix代理一下,就显得更加灵活和理了。

http中的multipart/form-data消息体如下

修改后的请求,是一个标准的http请求,你通过postman的codesnippet视图也可以看到,代码如下

POST /mobile-server/manager/6.0.0.0.0/cdnManage/customUpload HTTP/1.1
Host: api-gw-test.pkulaw.com
Cookie: CookieId=b97385476b3c721c81a9163f1c8a85dd; SUB=347c9e9e-076c-45e3-be74-c482fffcc6e5; preferred_username=test; session_state=458053bd-5970-4200-9b6f-cf538ec9808b
Content-Length: 508
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="folder"

app/icon
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="domain"

https://static.pkulaw.com
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="fileName"

xzcf.png
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="multipartFile"; filename="/C:/Users/User/Pictures/21111.png"
Content-Type: image/png

(data)
----WebKitFormBoundary7MA4YWxkTrZu0gW--

开发过程中的一些坑

  1. 参数拼接错误,form-data的文件流应该是第一个参数
服务端收到的请求体和参数为空
  1. 后端服务直接报错,原因有以下几个
  • 有空的boundary,
  • boundary与字段之间没有\r\n换行
  • 将所有\n替换为\r\n,可能会解决上传文件和参数在接收端为空的问题
  • http请求头中的boundary是没有开头的两个减号的,这块非常容易出错,例如
    ngx.req.set_header("Content-Type", "multipart/form-data; boundary=" .. boundary)
  • boundary在各字段之前并不相同,需要着重看一下,一般是------开头,看看是否-的数量不同,可能接收端会有下面的错误,表示请求体拼接不正确
Failed to parse multipart servlet request; nested exception is java.io.IOException: org.apache.tomcat.util.http.fileupload.FileUploadException: Stream ended unexpectedly

file-upload-proxy文件上传转发插件源码

-- author: zhangzhanling
-- 文件上传服务代理
-- 代理前端,与文件上传服务进行通讯
-- 在请求体中,添加统一的参数
local core = require("apisix.core")
local uuid = require("resty.jit-uuid")
local ngx = require("ngx")
-- 定义原数据格式
local schema = {
    type = "object",
    properties = {
        folder = {
            type = "string",
            description = "相对目录"
        },
        domain = {
            type = "string",
            description = "图片服务的域名"
        }
    }
}

local _M = {
    version = 0.1,
    priority = 1009, --数值超大,优先级越高,因为authz-keycloak是2000,它需要在authz-keycloak之后执行,所以把它定为1000,因为咱们也依赖proxy_rewrite插件
    name = "file-upload-proxy",
    schema = schema
}

local function get_specific_header(ctx, header_name)
    local headers = core.request.headers(ctx)
    local value = headers[header_name]
    if type(value) == "table" then
        return table.concat(value, ", ")
    else
        return value
    end

end
-- 辅助函数:查找边界字符串
local function find_boundary(content_type)
    return content_type:match("boundary=([^;]+)")
end

function _M.rewrite(conf, ctx)
    ngx.req.read_body()
    local body_data = ngx.req.get_body_data()

    if not body_data then
        core.log.warn("Failed to read request body.")
        return 400
    end

    local content_type = ngx.req.get_headers()["content-type"]
    local boundary = find_boundary(content_type)

    if not boundary then
        core.log.warn("No boundary found in content type.")
        return 400
    end

    local startBoundary = "--" .. boundary

    local sub_value = get_specific_header(ctx, "sub")
    local folder = conf.folder
    if sub_value then
        folder = folder .. "/" .. sub_value
    end

    ---- 构建新的请求体
    local new_body = ""

    local fileExt = ".jpg"
    local filename = string.match(body_data, 'filename="([^"]+)"')

    if filename then
        -- 从filename中提取扩展名
        local _, _, ext = string.find(filename, "%.([^.]+)$")
        if ext then
            core.log.info("文件扩展名为: " .. ext)
            fileExt = "." .. ext;
        end
    end

    -- 添加新字段
    local new_fields = {
        { name = "domain", value = conf.domain },
        { name = "fileName", value = uuid() .. fileExt },
        { name = "folder", value = folder }
    }
    ---- 添加新字段
    for _, field in ipairs(new_fields) do
        new_body = new_body .. string.format("\r\n%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s", startBoundary, field.name, field.value)
    end

    new_body = new_body .. "\r\n" .. body_data

    -- 设置新的请求体
    ngx.req.set_body_data(new_body)

    -- 更新 Content-Type 头
    ngx.req.set_header("Content-Type", "multipart/form-data; boundary=" .. boundary)

    -- 计算并设置 Content-Length
    local content_length = string.len(new_body)
    ngx.req.set_header("Content-Length", content_length)

    -- 日志输出新请求体和内容长度
    core.log.warn("boundary:", boundary)
    core.log.warn("New request body: ", new_body)
    core.log.warn("Content-Length: ", content_length)
end

-- 注册插件
return _M

前言

昨天写了一篇介绍 Blazor Hybrid 技术的文章
,但限于篇幅,一些问题未能深入探讨。今天,我想继续记录使用 Blazor Hybrid 过程中遇到的几个问题,以及这个技术目前的一些局限性。

文件拖放事件的局限

Blazor Hybrid 的运行环境是 WebView,这导致了在处理文件拖放时出现了一些限制。在传统桌面应用中(如 WinForms 或 WPF),开发者可以直接捕获拖放事件,并获得文件的完整路径。但在 Blazor 中,拖放事件只能像浏览器中一样处理,意味着我们只能获得上传文件的流,而无法获取文件的实际路径。

这对于那些需要直接访问文件路径的功能(如Clipify中把视频拖进去处理)带来了很大的不便。

冗余代码
(不是)

看了项目代码的同学可能会发现,FormMain.cs里还有处理拖放事件的代码,不过实际上并没有生效。

// 处理拖动进入事件,检测是否为文件
private void blazorWebView1_DragEnter(object sender, DragEventArgs e) {
  Console.WriteLine("drag enter");
  if (e.Data.GetDataPresent(DataFormats.FileDrop)) {
    // 改变鼠标图标,表示可以拖放
    e.Effect = DragDropEffects.Copy;
  }
  else {
    e.Effect = DragDropEffects.None;
  }
}

// 处理拖放事件,获取文件路径
private void blazorWebView1_DragDrop(object sender, DragEventArgs e) {
  Console.WriteLine("drag drop");
  if (e.Data.GetDataPresent(DataFormats.FileDrop)) {
    var files = (string[]?)e.Data.GetData(DataFormats.FileDrop);

    // 这里只处理单个文件,当然你也可以处理多个文件
    if (files?.Length > 0) {
      var filePath = files[0]; // 获取拖放的文件路径
      MessageBox.Show($"文件路径: {filePath}");

      // 在这里你可以将文件路径传递给 Blazor 或其他处理逻辑
    }
  }
}

// 处理 DragOver 事件,防止系统默认行为
private void blazorWebView1_DragOver(object sender, DragEventArgs e) {
  if (e.Data.GetDataPresent(DataFormats.FileDrop)) {
    e.Effect = DragDropEffects.Copy; // 明确允许拖放文件
  }
  else {
    e.Effect = DragDropEffects.None;
  }
}

解决方案

目前的解决办法有限,根据查找到的资料和我自己的探索,有以下几种:

  1. 在需要拖放的时候,使用一个WinForms原生控件覆盖webview
  2. 使用hook技术,拦截webview的拖放事件
  3. 重写微软提个的这个 Blazor Webview 控件,自己实现
    WndProc
    方法

第3种方法的代码大概是这样(未验证)

public class CustomBlazorWebView : BlazorWebView {
  protected override void WndProc(ref Message m) {
    const int WM_DROPFILES = 0x233; // 拖放文件消息

    if (m.Msg == WM_DROPFILES) {
      // 处理文件拖放逻辑
      // 你可以在这里调用你的拖放事件处理逻辑

      // 阻止消息传递,避免系统默认处理文件
      return;
    }

    base.WndProc(ref m);
  }
}

PS:我嫌麻烦就还没去折腾实现这个拖放功能,目前只做了打开对话框选择文件。

社区反馈

同样的问题我在 Github issues 和 Stack Overflow 之类的平台也有看到很多人提出,不过看起来微软并不想解决这些问题。

相关链接:

桌面应用体验差异

Blazor Hybrid 尽管以桌面应用的形式运行,但表现更接近于网页应用。

浏览器的快捷键

一个明显的例子是,在 WebView 中按下 F5 键时,页面会像浏览器一样刷新,这种行为显然不符合传统桌面软件的用户体验。

在类似的技术中,如 Electron,也存在类似的局限。但不同的是,Electron 提供了更多对浏览器行为的控制手段,可以阻止或重定义这些行为,而 Blazor Hybrid 目前则没有这些更细粒度的控制能力。

从桌面应用的角度来看,用户希望获得一致且原生的操作体验,因此这些细微的差异可能会影响开发者对 Blazor Hybrid 应用的期望。

窗口大小调整的表现

在使用 Blazor Hybrid 时,我还注意到窗口大小调整的流畅度问题。相比起原生的桌面应用,Blazor Hybrid 的表现不尽如人意。当用户调整窗口大小时,界面偶尔会出现黑边或画面撕裂的现象。

这种问题不仅在 Blazor Hybrid 中出现,实际上,在浏览器(chrome)和 Electron 应用(QQ)中,我也观察到类似的问题。

为了更深入地理解这个现象,我还测试了 C++ 原生应用,结果发现原生应用在调整窗口大小时相对来说更流畅,没有出现黑边或撕裂的问题。

我猜测造成这种差异的原因可能在于,Blazor Hybrid 和 Electron 依赖 WebView 作为渲染引擎,而 WebView 的渲染机制在处理窗口大小调整时不如原生 UI 渲染引擎高效。

小结

Blazor Hybrid 是一个非常有潜力的技术,它让 C# 开发者能够轻松地构建跨平台桌面应用。

然而,在使用过程中,我发现了一些需要关注的问题,尤其是在拖放事件、桌面应用行为一致性和窗口大小调整表现上。

这些问题目前可能对开发者造成一定的困扰,也影响了用户体验的流畅性。

接下来我会找时间试一下 Electron 和 wails 的开发体验,进一步探索 Blazor Hybrid 在桌面软件开发中的优势。

一、nodejs 和 npm 的版本是有适配的

首先看下官网列明的大概匹配关系:

官网链接地址:
https://nodejs.org/zh-cn/about/previous-releases

可以查看下本地的版本,根据上表中的对应关系,看是否差别太大。如果差别太大需要更新到对应版本,不然会出现各种报错问题,导致 npm 命令执行失败。

博主当前版本示例:

>npm -v
6.14.18
>node -v
v14.18.0
    
// "node-sass": "^4.14.1"
// "sass-loader": "^7.3.0"

二、版本更新

2.1 推荐使用 nvm 工具,直接通过命令行安转和切换各个版本

无论是 Windows 平台,还是 Linux 平台,都推荐使用 nvm 工具。

nvm(Node.js Version Management)
,是一个 nodejs 的版本管理工具。它是专门为解决 node.js 各种版本存在不兼容的问题。
可以通过它安装和切换不同版本的 node.js,还可同时在一个环境中安装多个 node.js 版本(和配套的 npm)
,这样在日常开发中就方便多了。

但需要注意的是,在安装 nvm 前,需要将原先已安装的 nodejs 全部删除干净。

详情可参考: https://blog.csdn.net/goods_yao/article/details/137854626

2.2 手动更新

当然,除了通过工具来更新,
也可以手动安装
,下面推荐个下载 nodejs 的阿里云地址,其中包含历史的各个版本。

安装包下载地址: https://mirrors.aliyun.com/nodejs-release/

单击对应的版本,进入详情,会有各个平台的安装文件。

  • Windows 平台

可以直接下载系统对应的 .msi 后缀的安装程序。现今 32 位的系统已经不多了,一般可以下载 x64 版本。

下载好后直接双击安装即可。

nodejs 详细的安装步骤可以参考: https://blog.csdn.net/Nicolecocol/article/details/136788200

npm 是 Node.js 的包管理器,用于发布和管理 Node.js 应用程序的依赖关系。

通常情况下,在安装 Node.js 时,npm 会自动包含在内,无需单独下载和配置。

若要
更新指定版本的 npm
,可以参考以下命令:

npm install -g npm
npm install -g npm@<指定版本号>
npm install -g npm@6.14.18
  • Linux 平台

安装步骤可以参考:
https://blog.csdn.net/qq_40743057/article/details/139139574

要注意的是,CentOS 7 最多支持到 Node.js-16.x 版本。在 CentOS 7 上安装更高版本的 Node.js,如 Node.js-18.x,可能会导致系统出现兼容性问题和报错。这是因为随着 Node.js 版本的更新,其依赖的库和模块可能也会发生变化,这可能与 CentOS 7 上的旧版软件包产生冲突。为了确保 Node.js 能够正常运行并避免潜在的错误,建议在 CentOS 7 上安装官方推荐的 Node.js 16.x 版本。这一版本已经经过了充分的测试和验证,与 CentOS 7 的兼容性较好,能够提供稳定的运行环境。

三、遇到的几个错误提示

3.1 Node Sass could not find a binding for your current environment...

报错内容:

// 执行命令 npm run dev 时报错:
Node Sass could not find a binding for your current environment: Windows 64-bit with Node.js 14.x
// 执行 npm 命令报错:
Failed to resolve loader: sass-loader You may need to install it. Error from chokidar (E:\): Error: EBUSY: resource busy or locked

报错原因是,项目中可能是区分测试和正式服务配置的。

可以尝试通过如下命令修复:

// 默认是在正式服中操作
// 先卸载,再安装
npm uninstall sass-loader node-sass
npm install sass-loader node-sass
// 若需要再测试环境更新,则需要加上一个配置:--save-dev
npm uninstall sass-loader node-sass --save-dev
npm install sass-loader node-sass --save-dev
// 安装指定版本
npm uninstall node-sass
npm install node-sass@4.14.1
npm install node-sass@latest

3.2 errno: -4071,  code: 'EINVAL',  syscall: 'spawnSync npm.cmd',  path: 'npm.cmd' ...

报错详情:

【初始化报错】
errno: -4071,  code: 'EINVAL',  syscall: 'spawnSync npm.cmd',  path: 'npm.cmd',
arco design 初始化项目时报错:× 模板内容拷贝失败 Error: spawnSync npm.cmd EINVAL

原因是 child_process.spawn 有漏洞 (CVE-2024-27980)-(HIGH),调用要加{ shell: true }。2024.4.10 node 修复了这个漏洞,代码执行就报错了。cli 下载 arco-design-pro-vue 执行到 .arco-cli\init.js 就出现 spawn 报错。

解决办法:使用 18.20.2、20.12.2、21.17.3 之前的版本就可以了。我用 18.20.0 版本就可以了。

解决方案参考: https://github.com/arco-design/arco-cli/issues/92

3.3 淘宝镜像地址证书过期

报错详情:

request to https://registry.npm.taobao.org/axios failed, reason: certificate has expired

解决方案:

先备份 npm 配置,可以防止在更换过程中出现意外导致配置丢失
npm config get > npm_config.txt
npm config set registry https://registry.npmmirror.com
npm install --save-dev @arco-design/web-vue
npm i -g arco-cli

3.4 Error: Command 「yarn 」 executed failed

报错详情:

× 项目依赖安失败,你可以稍后尝试手动安装项目依赖
Error: Command 「yarn 」 executed failed:
warning ..\package.json: No license field
warning gifsicle > bin-build > tempfile > uuid@3.4.0: Please upgrade  to version 7 or higher.
。。。

解决方案:

打开命令行,切换到项目目录,例如:cd your_project_directory
运行以下命令来清除之前的依赖缓存:yarn cache clean
更新项目的依赖包:yarn upgrade
重新安装项目依赖:yarn install

3.5 npm 命令执行时报错:Error: Cannot find module 'node:path'

可能得原因是,nodejs 和 npm 版本不匹配,按照本文第一章节中的版本对应关系,升级 nodejs 即可。

数据资产管理是一项系统而复杂的工程,涉及到元数据、数据血缘、数据质量、数据服务、数据监控、数据安全、数据权限等众多方面,为了更高效的管理好数据资产,因此在很多大型的企业或者组织中,通常会构建一个数据资产管理平台来管理这些各种各样的数据资产,数据资产管理平台通常会包含如下功能: 关注
清哥聊技术
公众号,获取更多权威技术文章。

  • 元数据:主要负责元数据的维护和查看,让元数据成为数据资产的一个“电子目录”,方便外部用户查看和检索其需要的数据是存储在哪个数据库以及哪个表的哪个字段中,也方便外部用户知道数据资产中每个数据库、表、字段的具体含义。
  • 数据血缘:主要负责数据与数据之间的血缘关系跟踪,以方便用户在使用数据时,能快速知道数据的处理过程以及来龙去脉。
  • 数据质量:主要负责数据质量的监控与告警,当数据质量出现问题时,能够快速让相关的人员知道,数据质量的监控是持续提高数据质量的关键所在,也是数据资产持续优化、改进、提高质量的关键。
  • 数据服务:主要负责数据服务的管理,包括服务的创建、开发、发布上线以及被业务请求调用。数据服务是数据对外使用和产生价值的最常见方式之一,所以数据服务的管理与维护至关重要。
  • 数据监控:主要负责数据链路、数据任务、数据处理资源、数据处理结果等的监控与告警,当数据出现问题时,能够通过监控与告警,让数据问题得到及时快速的解决。
  • 数据安全:主要负责数据安全的管理,数据安全是数据资产管理中最重要的环节,也是数据资产管理的基础。通过评估数据的安全风险、制定数据安全管理的规章制度、对数据进行安全级别的分类,完善数据安全管理相关规范文档,保证数据是被合法合规、安全地获取、处理、存储以及使用
  • 数据权限:主要负责数据权限的分配与管理,通过数据权限的控制,能够更好的去保护数据资产中的隐私信息和敏感信息。

数据资产管理架构在设计时,通常需要考虑和解决如下问题:

  • 数据冗余:一般指的是由于数据没有进行统一的管理,导致很多不同的平台或者系统都存储了相同的数据,特别是对于一些很多业务或者系统都需要共用的数据。
  • 数据孤岛和数据分散:由于数据没有进行统一的集中式管理,所以数据很容易分散在不同的系统中并且容易产生数据孤岛。
  • 数据口径无法统一:每个业务系统都有自己的数据管理和分析,导致数据计算的口径会存在不一致,这样的话,就导致在做数据决策时,不知道到底以那一份数据口径为准。

1、数据资产的架构设计

数据资产架构是指为了让数据资产管理更加信息化、高效化、平台化而构建的一套系统架构。通常来说数据资产架构会包含如下的一些方面:

1.1、数据获取层

数据获取层通常又叫数据采集层,主要负责从各种不同的数据源中去获取数据,如下图

数据获取层在获取数据时会存在多种不同类型的数据源,从每一种类型的数据源中获取数据的方式是不一样的,所以在数据获取层的架构设计中,需要考虑兼容多种不同数据源,并且在出现新的类型的数据源时,需要能够支持花最小的代码改造代价去做扩展。所以通常建议数据资产架构设计中数据获取层的架构可以设计成即插即用的插件类型,如下图所示,这种设计方式可以很好的解决数据源的可扩展性的问题。

从图中可以看到

  • 设计了一个抽象类型的插件,这个插件中包含了从数据源中获取数据时需要的三个基本的步骤,也是需要实现的三个通用的底层方法。
  • 数据资产管理平台的数据获取层在加载完实现好的插件后,便可以去按照步骤顺序去调用已经实现好的三个通用的方法来获取数据。

1.2、数据处理层

数据处理层主要负责将从不同数据源中获取到的数据做处理,是整个数据资产架构的核心部分,数据处理的方式通常包含实时和离线两种方式,通常情况下数据处理层需要完成的主要功能如下图所示。

  • 数据脱敏:对原始数据中的敏感信息进行脱敏操作,防止隐私数据被泄露。
  • 数据清洗:去除原始数据中的无效数据、重复数据等,以提供数据处理的质量。
  • 数据集成:将同时来自于多个不同的数据源的数据进行整合形成统一的数据集。
  • 数据转换:对原始数据进行转换(比如进行统一的格式转换、类型转换等),以满足数据仓库或者数据湖的存储设计。
  • 数据加密:对一些隐私数据进行加密,方便数据存储后确保数据的安全性,对隐私数据进行保护。
  • 数据压缩:为了节约存储成本,在数据存储前,对数据进行压缩处理,在不丢失数据的前提下,减小数据的存储大小。

在大数据处理中,最常用的架构方式就是Lambda架构和Kappa架构,如下所示

  • Lambda架构:是一套强调将离线和实时任务分开处理的大数据处理架构,如下图所示

从图中可以看到Lambda架构是将离线处理和实时处理分开进行维护的,这就意味着需要开发和维护两套不同的数据处理代码,系统的复杂度很高,管理和维护的成本也很高。

  • Kappa架构:是一套将离线和实时数据处理整合在一起的大数据处理架构,如下图所示

Kappa架构其实可以看成是Lambda架构的优化和改进,在Kappa架构中实时任务需要承担全部数据的处理,会让实时任务处理的压力较大,但是Kappa架构将实时代码和离线代码进行了统一,方便了代码进行管理和维护也让数据的口径保持了统一,同时也降低了维护两套代码的工作量。

相比于Lambda架构,Kappa架构最大的问题在于一旦需要对历史数据进行重新处理,那么Kappa架构将难以实现,因为Kappa架构通常所使用的都是实时流处理的技术组件,比如像Flink等,但是如果做历史数据处理时,可能像Flink这样的技术组件就难以胜任,而擅长做离线数据处理的类似Spark这样的技术组件会更加适合,但是Flink的代码和Spark的代码通常是无法做共用的。

从对Lambda架构和Kappa架构的对比分析来看,两者各有优点,也有缺点,在实际应用当中,可能还需要同时结合这两种架构的优缺点来设计最符合自身业务和需求的数据处理架构。通常建议如下:

  • Lambda架构和Kappa架构 可以同时存在,对于经常有需要做历史数据处理的数据类型,建议保留为Lambda架构。
  • 对于几乎不需要做历史数据处理的数据类型,建议尽可能走Kappa架构来实现。

1.3、数据存储层

数据存储层主要负责各种类型的数据的存储,在架构设计时,还需要综合考虑如下问题来制定数据存储的架构和策略。

  • 数据查询的性能:比如查询的响应时间、数据访问的吞吐量、查询的TPS等。
  • 数据的冷热程度:根据数据的冷热程度,对数据进行划分,对于冷热程度不一样的数据,可以分开存储,通常对于冷数据,可以采用一些成本更低的存储介质来进行存储,方便节省数据存储的成本。

数据存储的技术方案可以有很多选型,如下所示,通常需要根据实际的业务需要来进行综合的选择。

  • 传统数据仓库存储:传统的数据仓库的代表就是Hive,可以负责海量数据的存储和基于Hive做数据分析和挖掘,但是Hive 数仓存在以下不足
  • 通常只能存储结构化的数据或者经过处理后生成的结构化数据。
  • Hive数仓中更新数据的能力较弱,一般只能做数据的批量插入。
    • 数据湖存储:数据湖是在传统数仓上发展而来的,也是可以完成海量数据的存储,相比于数据仓库,数据湖具有如下优势:
    • 数据湖可以存储结构化数据,也可以存储半结构化数据和非结构化数据。
    • 数据湖中可以直接存储没有经过任何处理的原始数据,也可以支持直接对原始数据做分析。
    • 数据湖中支持数据快速做更新、删除等操作。
    • 更适合做机器学习、探索性分析、数据价值挖掘等。

在开源社区中,常见的数据湖有Hudi、Delta Lake、Iceberg等。

  • 分布式数据库存储:分布式数据库存储一般用于存储对于实时查询要求较高或者要求实时做OLAP数据分析的数据,在开源社区中,常见的分布式数据库有Apache Doris(可以通过官网
    https://doris.apache.org/
    了解更多关于Apache Doris的介绍)、Apache Druid(可以通过官网https://druid.apache.org/了解更多关于Apache Druid的介绍)等。

通过以上分析,数据存储层的架构设计通常建议设计成当前最为流行的湖仓一体的架构,并且针对特殊的业务场景,可以引入一些分布式数据库或者关系型数据库进行辅助,如下图所示。

数据存储层在存储数据时,通常还会对数据进行分层存储,数据分层的架构实现方案通常如下图所示,数据分层主要是为了

  • 对数据进行模块化设计来达到数据之间解耦的目的,数据通过分层可以将一些非常复杂的数据解耦为很多个独立的数据块,每一层完成特定的数据处理,便于开发、维护以及让数据可以被更好的复用。
  • 让数据的可扩展性更强,当数据业务发生需求变化时,只需要调整响应数据层的数据处理逻辑,避免了整个数据都需要从原始数据(也就是图中的ODS层的数据)来重新计算,节省了开发和数据计算的资源成本。
  • 让数据的查询性能更快,在大数据中,由于存在海量的数据,如果全部从原始数据(也就是图中的ODS层的数据)中来查询业务需要的数据结果,需要扫描的数据量会非常大,将数据分层后,可以优化数据的查询路径,减少数据扫描的时间以达到提高数据查询性能的目的。

1.4、数据管理层

数据管理层主要负责对数据进行分类、标识以及管理,主要会包含元数据管理、数据血缘跟踪管理、数据质量管理、数据权限和安全管理、数据监控和告警管理等,其总体的实现架构图如下图所示。

数据管理层的技术核心就是元数据、血缘数据、质量数据、监控数据等采集获取,我们在 清华大学出版社出版的
《数据资产管理核心技术与应用》
一书的前面的章节中已经有过很具体的描述,在拿到这些数据后,数据管理层主要要实现的功能就是把这些数据做集成并且展示到数据资产管理平台中,数据管理层是数据资产管理的核心。

1.5、数据分析层

在数据分析层的架构设计中,主要包含如下两个部分:

  • 数据分析工具的选择:随着大数据分析技术的发展,诞生了很多和数据分析相关的BI工具,常见的BI分析工具的相关介绍如下表所示:

BI 工具名称

描述

适用的场景

Power BI

是由微软推出的一款BI数据分析工具

成本较高,通常适合于微软云相关的服务中使用

Pentaho

开源的BI分析工具,具有数据整合、报表生成和数据可视化等功能

开源产品。适合于自己有部署和运维能力的团队进行使用

Quick BI

是阿里云推出的一款BI数据分析工具

由于是阿里云推出,所以通常只适合于阿里云中使用。

FineBI

是由帆软推出的一款BI数据分析工具

商业软件,一般需要购买,通常适用于政府或者企事业单位使。

在选择BI的数据分析工具时,一般建议结合自身的业务需求、使用成本、管理维护成本等多个方面来综合考虑,然后再选择最合适的BI工具。

  • 数据的加工与处理:这里的数据加工与处理主要是指数据分析需要做的数据预加工与处理,以便数据分析工具能快速得到自己想要的数据。在大数据中,由于是海量的数据,所以在数据分析时,BI分析工具通常不会直接去从海量的原始数据中直接做分析。

通过如上两点的分析,数据分析层的整体架构设计通常如下图所示。

  • 数据分析时,对于实时性要求较高的数据,通常会存储在分布式数据库中,不做太多的预处理,让BI工具直接去查询和访问,这样可以保证整个数据分析链路的实时性。
  • 对于实时性要求不高的数据,可以每天通过离线的方式进行处理,通常会从数据仓库或者数据湖中,每天离线对数据做预处理,处理的结果数据可以根据数据量的大小选择放入普通关系型数据还是放入数据仓库或者数据湖的ADS应用层来供BI工具做分析使用,甚至数据湖或者数据仓库的DWD数据明细层或者DWS数据轻度汇总层也可以开放给BI数据分析工具直接做分析。

1.6、数据服务层

数据服务层通常是让数据对外提供服务,让数据可以服务于业务,并且负责对数据服务进行管理,数据服务层通常的架构实现如下图所示,数据服务的具体技术实现细节可以参考清华大学出版社出版的
《数据资产管理核心技术与应用》
一书的第六章。

数据服务层在设计时,通常需要包括服务创建、服务发布,服务接入、服务降级、服务熔断、服务监控以及权限管理等模块,对于服务访问的权限管理通常建议也可以采用基于角色的访问控制 (RBAC)来实现,如下图所示。

  • 一个角色可以拥有一个或者多个不同服务,也可以拥有一个或者多个不同的菜单。
  • 角色可以赋予给用户,也可以赋予给调用服务的业务需求的上游。

通过对每一层做架构分析与设计后,得到最终如下图所示的数据资产架构图,这是大数据处理中最常见的架构设计方案,解决了数据的可扩展性以及对于不管什么类型或者什么什么格式的数据,都可以做数据处理、存储以及分析。

《数据资产管理核心技术与应用》是清华大学出版社出版的一本图书,全书共分10章,第1章主要让读者认识数据资产,了解数据资产相关的基础概念,以及数据资产的发展情况。第2~8章主要介绍大数据时代数据资产管理所涉及的核心技术,内容包括元数据的采集与存储、数据血缘、数据质量、数据监控与告警、数据服务、数据权限与安全、数据资产管理架构等。第9~10章主要从实战的角度介绍数据资产管理技术的应用实践,包括如何对元数据进行管理以发挥出数据资产的更大潜力,以及如何对数据进行建模以挖掘出数据中更大的价值。