2024年10月

记录一下最近sv.db的完善

1. 让查询可以使用 json path

有时候我们会存储 json 到 db,也有时会只取json部分数据,或者通过json部分数据进行过滤

所以sv.db 也支持这些场景,(目前只有 db 实现,json的操作都是依靠db json 函数)

举例:
数据

a.ExecuteNonQuery("""
    INSERT INTO Weather
    (name, value)
    VALUES ('Hello', '{"a":2}'),('A', '{"a":3,"c":[4,5,{"f":7}]}')
    """);

然后配置字段允许json

 [Db(StaticInfo.Demo)]
 [Table(nameof(Weather))]
 public class Weather
 {
     [Select, Where, OrderBy]
     public string Name { get; set; }

     [Select(Field = "Value"), Where, OrderBy, Column(IsJson = true)]
     public string V { get; set; }
 }

api 方法不用做额外的实现

[HttpGet]
public async Task<object> Selects()
{
    return await this.QueryByParamsAsync<Weather>();
}

用户查询api 时就可以对json字段进行任意操作,比如

curl --location 'http://localhost:5259/weather?Fields=v,json(v,'$.a',vvva)&OrderBy=json(v,'$.a') asc&Where=json(v,'$.a') != 1'

结果

{
    "totalCount": null,
    "rows": [
        {
            "vvva": 2,
            "v": "{\"a\":2}"
        },
        {
            "vvva": 3,
            "v": "{\"a\":3,\"c\":[4,5,{\"f\":7}]}"
        }
    ]
}

ps:json 实现对应 db json 函数

db json 函数
SQLite json_extract
PostgreSQL jsonb_path_query_first
MySql json_unquote(json_extract())
sql server JSON_QUERY

2. 字段白名单验证

默认会对解析的statement结果进行字段验证,不通过的会返回 400

验证:

  • 不在字段配置的白名单范围
  • 不允许类似 1 = 1, 只能 field = 1
  • 未配置 json 字段不允许使用 json 函数

如需改变 验证逻辑或自行验证,可以通过
SelectStatementOptions
自行处理

public record class SelectStatementOptions
{
    public bool AllowNotFoundFields { get; init; } = false;
    public bool AllowNonStrictCondition { get; init; } = false;
    public Action<Statement> Visiter { get; init; } = null;
}

3. swagger 生成

安装 swagger

<PackageReference Include="SV.Db.Sloth.Swagger" Version="0.0.2.3" />

swagger gen 配置 sv.db 方法

builder.Services.AddSwaggerGen(c =>
{
    c.AddDbSwagger();
}); 

api 方法配置 swagger

[DbSwaggerByType(typeof(Weather))]
[HttpGet]
public async Task<object> Selects()
{
    return await this.QueryByParamsAsync<Weather>();
}

只需配置这些,swagger 将为大家自动生成字段描述

4. 主要功能已完善,已发布 nuget

如想尝试,只需安装所需 package

<PackageReference Include="SV.Db.Sloth.Swagger" Version="0.0.2.3" />
<PackageReference Include="SV.Db.Sloth.WebApi" Version="0.0.2.3" />
<PackageReference Include="SV.Db.Analyzers" Version="0.0.2.3">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="SV.Db.Sloth.MSSql" Version="0.0.2.3" />
<PackageReference Include="SV.Db.Sloth.MySql" Version="0.0.2.3" />
<PackageReference Include="SV.Db.Sloth.PostgreSQL" Version="0.0.2.3" />
<PackageReference Include="SV.Db.Sloth.Sqlite" Version="0.0.2.3" />

在现今国产化浪潮的驱动下,跨平台或者缩小范围说基于国产化Linux或者基于国产鸿蒙系统的开发才是未来的趋势了,风口浪尖上,我们开发人员也只能顺势而为,本篇随笔介绍在Python开发中,使用使用Python+nicegui实现系统布局界面的开发。

1、Nicegui的介绍和应用需求

我们先来介绍一个比较新兴的一个界面组件 nicegui 的资源:

nicegui的官网:
https://nicegui.io/documentation

Github地址:
https://github.com/zauberzeug/nicegui

它是一个可以创建基于服务器端运行的BS前端,也可以是一个独立运行的程序,类似Electron(https://www.electronjs.org/) 的独立运行程序。根据编译的方式不同,生成不同的文件。

我在随笔《
基于Python后端构建多种不同的系统终端界面研究
》中大致介绍了一下该组件的使用效果,其实主要优势就是能够利用Python跨平台的开发和部署运行能力,并结合nicegui能够编译独立App或者桌面程序,或者基于服务端的BS管理系统,皆可以一气呵成,一套代码按需发布不同的UI即可。

另外朋友有需要,要我为其AI模块的中控系统做一套管理各个终端设备的终端,要求使用这个nicegui来实现。一个小盒子Orange Pi 跑ubuntu的设备,还很顺滑。

2、系统界面和布局和模块化页面处理

基于这样的需求,我们可以先做一套管理面板来实现一些功能入口,首先有一个登录的界面,然后一个布局界面进行处理即可.

接着就是设计一个主要框架的布局页面,如下所示。

如果主体框架是模块的页面管理,那么剩下的就是我们根据不同的需求设计不同的页面,放置在目录下即可,根据需要添加所需的菜单。

例如,我们在main.py入口页面上,可以添加模块的路由处理,如下所示。

#首页显示
@ui.page("/")def index_page() ->None:
with theme.frame(
"Homepage"):
home.content()

login.create_page()
#登录页面 #使用APIRouter路由处理,每个模块独立一个路由#参考文档: https://nicegui.io/documentation/page#modularize_with_apirouter app.include_router(example.router)
app.include_router(customer.router)

这样我们把Home页面、Login页面路由、其他页面路由都一并处理好,我们还可以优化一下,把路由放在一个独立的文件如router.api中实现统一管理页面的路由处理。

#router.py

from nicegui importapp, uiimportpages.example as exampleimportpages.home as homeimportpages.customer as customerimportpages.login as login#使用APIRouter路由处理,每个模块独立一个路由#参考文档: https://nicegui.io/documentation/page#modularize_with_apirouter
definit_routes():"""初始化系统的路由"""app.include_router(home.router)#首页显示
    app.include_router(login.router)  #登录页面
    app.include_router(example.router)  #示例页面
    app.include_router(customer.router)  #客户页面

    #............其他页面

统一处理路由信息后,那么main.py的代码就可以优化如下所示。

from nicegui importapp, ui, languageimportrouter as routerfrom auth_middleware importAuthMiddleware

router.init_routes()
#初始化路由 app.add_middleware(AuthMiddleware) #自定义中间件,处理登录验证 app.add_static_files("/public", "public") #添加静态文件目录 #启动运行,并设置全局语言配置为中文 ui.run(
title
="企业信息化平台-广州爱奇迪软件科技有限公司",
language
="zh-CN",
storage_secret
="THIS_NEEDS_TO_BE_CHANGED",
)

通过直接调用 init_routes 来处理路由即可。

测试一个简单的表格查询页面处理,如下所示。

可以打开或者折叠行的定义信息。

主要通过ui.table和slot来实现多种表格的处理效果。

        #表格
        table =ui.table(
columns
=columns,
rows
=rows,
title
="客户列表",
pagination
=10,
row_key
="name",
selection
="single",
on_pagination_change
=lambdae: ui.notify(e.value),
)

折叠信息我们通过下面的Slot处理展示。

table.add_slot("body",
r
"""<q-tr :props="props">
<q-td auto-width>
<q-btn size="sm" color="accent" round dense
@click="props.expand = !props.expand"
:icon="props.expand ? 'remove' : 'add'" />
</q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ col.value }}
</q-td>
</q-tr>
<q-tr v-show="props.expand" :props="props">
<q-td colspan="100%">
<div class="text-left" >
<div class="text-primary line-clamp-1 text-base tracking-wide" v-for="col in props.cols" :key="col.name">{{col.label}}: {{col.value}}</div>
</div>
</q-td>
</q-tr>
""",
)

我们也可以采用 nicegui_tabulator 第三方组件来丰富表格的处理效果。

Githhub地址:
https://github.com/CrystalWindSnake/nicegui-tabulator

它是使用niceui来改造过著名表格组件:
https://github.com/olifolkerd/tabulator
,相关使用参数等也可以参考下官网文档:
http://tabulator.info

案例代码:

from nicegui_tabulator importtabulator, use_themefrom nicegui importui#use the theme for all clients#use_theme("bootstrap4")
tabledata=[
{
"id": 1, "name": "Oli Bob", "age": "12", "col": "red", "dob": ""},
{
"id": 2, "name": "Mary May", "age": "1", "col": "blue", "dob": "14/05/1982"}
]
table_config
={"height": 205,"layout": "fitDataFill","pagination": "local","paginationSize": 10,"movableColumns": True,"resizableRows": True,"data": tabledata,"columns": [
{
"title": "Name", "field": "name", "width": 150, "headerFilter": "input"},
{
"title": "Age", "field": "age", "hozAlign": "left", "formatter": "progress"},
{
"title": "Favourite Color", "field": "col"},
{
"title": "Date Of Birth","field": "dob","sorter": "date","hozAlign": "center",
},
],
}

table
= tabulator(table_config).on_event("rowClick", lambda e: ui.notify(e))

界面效果如下:

汉化按钮后,界面效果如下所示。

根据需要我们可以整合更多的相关界面下效果,这样可以跨平台的运行在各个应用上,非常方便。

痞子衡嵌入式半月刊: 第 109 期

这里分享嵌入式领域有用有趣的项目/工具以及一些热点新闻,农历年分二十四节气,希望在每个交节之日准时发布一期。

本期刊是开源项目(GitHub:
JayHeng/pzh-mcu-bi-weekly
),欢迎提交 issue,投稿或推荐你知道的嵌入式那些事儿。

上期回顾

《痞子衡嵌入式半月刊: 第 108 期》

唠两句

历史上的今天:1999年10月14日,中国发射第一颗传输型对地遥感资源卫星 - “资源一号”。

本期共 4 个项目,希望对你有帮助!

项目类

1、RAMTEX - 适用于RGB/多级灰度/黑白屏的GUI

RAMTEX 是一家软件公司,专注于黑白/RGB/多级灰度屏的图形用户界面(GUI)开发,他们的 GUI 框架为市面上大部分的 LCD 驱动器都做了适配。此外他们还做了一系列测试工具(PC 上模拟器,字库编辑软件,还有一套能在 PC 上快速调试各种 LCD 屏的软硬件)。

其中 IOTester 将“嵌入式”I/O硬件连接到 PC,并使硬件可以直接从用 C 或 c++ 编写的普通 PC 程序应用程序访问和控制。

2、canopen - ECSS组织创建的适用于航天器的CANopen

该项目是 ECSS((European Cooperation for Space Standardization,欧洲空间标准化合作组织)创建的一个适用于航天器的 CANopen 版本,提供完整的 CANopen 软件栈实现,符合 CiA 301, CiA 306 和 ECSS-E-ST-50-15C 标准,针对关键嵌入式系统的高度可定制和可配置的开源库。

3、ecat_servo - 基于STM32的EtherCAT CiA402伺服驱动

EtherCAT 是用于自动化的实时以太网协议,目前最流行的 EtherCAT 伺服驱动应用协议是 CANopen(基于EtherCAT) CiA402。该项目是 CoE CiA402 标准的开源实现,用于开放式电机控制器,如 ODrive, STMBL 等。该项目展示了如何使用免费和开源工具进行 EtherCAT 设备开发。

4、programmable_precision_resistor - 一个开源可编程精密电阻仪

该项目是一个纯开源的可编程电阻仪,其电阻值可以通过数字信号来调节,可调范围 1Ω - 999.999KΩ,支持 SCPI 可编程仪器标准命令。

欢迎订阅

文章会同时发布到我的
博客园主页

CSDN主页

知乎主页

微信公众号
平台上。

微信搜索"
痞子衡嵌入式
"或者扫描下面二维码,就可以在手机上第一时间看了哦。

vscode 插件 esbuild类型提示报错

最近在上手开发vscode插件,demo阶段就遇到了一个小问题。 搜索引擎没有特别好的回答, 记录一下,以供查漏补缺。

vscode插件开发

做为一统前端的开发插件,vscode+其丰富的插件能力,共同构建了欣欣向荣的vscode 插件。在团队效率方面,也是不可或缺的利器。

开发文档就不说了,直接看官网很详细:
https://code.visualstudio.com/api
。 跟着跑了一遍,明白下大概得理念就可以跟着开发自己功能。

问题出现: problemmatcher 引用无效: $esbuild-watch

本来很愉快的基于
第一个扩展
进行了项目初始化和安装。

因为对于esbuild的偏爱,所以yo初始化是选择了 esbuild做为打包工具。

结果问题出现了,在进行

// 执行调试
Debug: Start Debugging

vscode 开始报错:

ps: 其实vscode 晚点也会给出解决建议:

但是,手快的下意识行为已经打开了,具体信息

Activating task providers npm
错误: problemMatcher 引用无效: $esbuild-watch

问题解决

面对不熟悉的领域,问题出现直接搜索引擎就行了,但是这次直接给我看懵逼了。
直接搜索上面报错,找到了微软的官方文章。
https://marketplace.visualstudio.com/items?itemName=connor4312.esbuild-problem-matchers

其实没注意这里就是对应插件了, 看了看 是介绍如何使用这个插件的。
下意识行为 不应该额外手动安装插件。 就忽略了。

gpt提示:版本匹配问题。

目标不明确直接问了gpt,得出了一下提示。

检查 VSCode 版本是否较新,更新到最新版本。

重启 VSCode,有时候这可以解决临时性问题。

手动添加 $esbuild-watch 到 problemMatcher:
"problemMatcher": [
"$esbuild-watch"
]

尝试使用不同的匹配模式,比如:
"problemMatcher": [
{
"base": "$esbuild-watch",
"background": {
"activeOnStart": true,
"beginsPattern": "^\\swatching\\s",
"endsPattern": ""
}
}
]

下面就是各种检查了,问题都是对得上,问题陷入纠结中。

问题解决

毕竟是task.json 的报错,其实大部分情况下直接查看官方文档是最有效的。我就找到了这篇:
https://code.visualstudio.com/Docs/editor/tasks

作用就是 在task 输出 log 中查找对应标识,以确定哪个任务除了异常。

既然如此 那就找

esbuild.js --watch problemMatcher

找到了这篇文章
https://github.com/connor4312/esbuild-problem-matchers
定睛一眼,这不刚才那个插件吗。

果然安装了对应插件之后就可正确运行了。

结束语

vscode 不得不说插件生态确实厉害,不过文档确实有点老了, 应该提示下安装对应插件的。

大家好,我是 V 哥。在实际的业务场景中,
Spark任务出现OOM(Out of Memory)
问题通常是由于任务处理的数据量过大、资源分配不合理或者代码存在性能瓶颈等原因造成的。针对不同的业务场景和原因,可以从以下几个方面进行优化和解决。

一、业务场景及可能的OOM原因分析

  1. 数据量过大


    • 业务场景:处理海量数据集(例如,数亿行日志数据或数十TB的数据集),任务执行过程中需要对数据进行大规模的聚合、排序、连接等操作。
    • OOM 原因:数据无法完全放入内存,导致溢出,尤其是在
      shuffle

      join
      操作时,数据量暴增。
  2. 数据倾斜


    • 业务场景:处理的数据分布不均匀(如某个用户或产品的数据量过多),导致部分节点上出现计算或内存瓶颈。
    • OOM 原因:由于部分节点需要处理大量的数据,某些节点的任务会使用超出可用内存的资源,而其他节点的负载较轻。
  3. 不合理的资源分配


    • 业务场景:资源分配过低,导致单个任务分配到的内存、CPU等资源不足。
    • OOM 原因:Executor的内存设置太小,或者数据过度缓存,导致内存不足。
  4. 代码中存在缓存过多或内存使用不合理


    • 业务场景:频繁使用
      cache()

      persist()
      ,或对数据结构进行不必要的操作,导致内存过度消耗。
    • OOM 原因:数据缓存没有及时释放,导致内存占用过多。

二、针对OOM问题的解决方案

1. 调整Executor的内存和CPU资源

通过合理的资源分配,确保每个
Executor
有足够的内存处理数据。

  1. 增加Executor的内存

    Spark 中的
    Executor
    负责在集群节点上执行任务,默认每个
    Executor
    的内存可能不足以处理大数据集。可以增加
    Executor
    的内存以缓解OOM问题。
   --executor-memory 8G

可以通过
--executor-memory
选项来设置每个
Executor
的内存。例如,将内存设置为8GB。如果数据量很大,可以根据情况设置更大的内存。

  1. 调整堆外内存

    Spark还使用了一部分堆外内存(off-heap memory)。如果涉及大量的堆外内存操作,可以通过以下配置增加堆外内存:
   --conf spark.memory.offHeap.enabled=true
   --conf spark.memory.offHeap.size=4G
  1. 调整Executor的CPU核心数

    为每个
    Executor
    分配更多的CPU核心,以加快任务的处理速度,防止长时间占用内存。
   --executor-cores 4

通过
--executor-cores
设置每个
Executor
使用的核心数。例如,可以将核心数设置为4,以提升并发计算能力。

2. 调整内存管理策略

Spark的内存管理策略主要涉及以下几个关键参数,它们的优化配置可以帮助减少OOM问题。

  1. 调整内存管理比例

    Spark 2.x 及以上版本采用统一的内存管理模型,可以通过调节以下参数优化内存使用:
   --conf spark.memory.fraction=0.8
   --conf spark.memory.storageFraction=0.5
  • spark.memory.fraction
    :该参数控制了存储与执行内存的总占比,默认是0.6,可以适当调高。
  • spark.memory.storageFraction
    :该参数决定了在
    memory.fraction
    的基础上,存储内存的占比。如果需要更多执行内存,可以适当减小该值。
  1. 减少缓存数据的存储占用

    • 及时清理缓存
      :对于不再需要的数据,及时调用
      unpersist()
      来清理缓存,释放内存。
   rdd.unpersist()
  • 调整缓存级别
    :在缓存时,使用
    StorageLevel.DISK_ONLY

    StorageLevel.MEMORY_AND_DISK
    ,以减少内存占用。
   rdd.persist(StorageLevel.MEMORY_AND_DISK)

3. 数据切分与优化操作

Spark任务中的
shuffle

join

groupBy
等操作通常会引起大量内存消耗,以下优化可以减轻这些操作带来的OOM风险。

  1. 调整分区数

    • 对于大规模数据操作如
      join

      shuffle
      等,分区数的设置至关重要。如果分区数过少,可能会导致某些分区数据量过大,进而导致内存溢出。
   rdd.repartition(200)

或者在执行某些操作时,显式指定分区数:

   rdd.reduceByKey(_ + _, numPartitions = 200)
  • 通常的经验是将分区数量设置为比Executor数量高出数倍(例如,每个核心处理2-4个分区)。
  1. 避免过多的宽依赖

    宽依赖(如
    groupByKey
    )会在shuffle时造成内存的压力,特别是数据量较大时,应该尽量避免。可以通过替换为
    reduceByKey
    等具有预聚合功能的操作来减少内存消耗:
   rdd.reduceByKey(_ + _)
  1. 避免数据倾斜

    如果存在数据倾斜,部分节点处理大量数据,容易导致OOM。以下是常见的解决方法:


    • 随机键拆分
      :可以为数据加上随机前缀,以打散数据,避免部分节点数据量过大。
   rdd.map(x => ((x._1 + new Random().nextInt(10)), x._2))
  • 广播小表
    :在
    join
    操作中,如果一张表很小,可以使用广播变量,将小表广播到每个节点,减少数据传输和内存占用:
   val broadcastVar = sc.broadcast(smallTable)
   largeTable.mapPartitions { partition =>
     val small = broadcastVar.value
     partition.map(largeRow => ...)
   }

4. 调整Spark的并行度和Shuffle机制

Spark的shuffle操作(如
groupByKey

join
)会导致大量数据需要在不同的节点之间传输。如果并行度设置过低,容易导致某个节点处理的数据量过大,从而引发OOM。

  1. 增加并行度
   --conf spark.sql.shuffle.partitions=200

或者在代码中显式设置:

   spark.conf.set("spark.sql.shuffle.partitions", "200")
  • 默认情况下,
    spark.sql.shuffle.partitions
    的值可能偏小(例如200),根据数据规模适当调整该值可以减轻单个节点的负载。
  1. 调整Shuffle合并机制

    Spark 3.0引入了
    Adaptive Query Execution (AQE)
    ,可以在执行时动态调整shuffle的分区数,避免某些分区数据量过大:
   --conf spark.sql.adaptive.enabled=true
   --conf spark.sql.adaptive.shuffle.targetPostShuffleInputSize=64M

AQE 可以根据任务的执行情况自动调整shuffle的分区数,从而避免OOM。

五、小结一下

Spark任务中的OOM问题常常由于数据量过大、数据倾斜、资源分配不合理等问题引起,针对不同的业务场景,可以采取以下措施进行优化:

  1. 合理分配内存和CPU
    :增加Executor的内存和CPU核心数,合理配置内存管理参数。
  2. 调整分区数和优化操作
    :通过调整分区数、减少宽依赖等方式减少内存占用。
  3. 处理数据倾斜
    :通过随机键拆分、广播小表等方法避免数据倾斜。
  4. 使用缓存优化内存
    :减少不必要的
    cache()

    persist()
    操作,并及时释放缓存数据。

好了,今天的内容就写到这里,这些优化方法结合使用,可以有效解决Spark任务中的OOM问题。关注威哥爱编程,码码通畅不掉发。