首页
烟花
归档
随笔
聚合
部落格精华
部落格资讯
CSS教程
CSS3教程
HTML教程
HTML5教程
建站经验
网站优化
资讯
今日早报
资讯日历
科技新闻
今天
罗盘
网盘
友链
留言
1
「今日早报」 2026年6月7日, 农历四月廿二, 星期日
2
「今日早报」 2026年6月6日, 农历四月廿一, 星期六
3
「今日早报」 2026年5月16日, 农历三月卅十, 星期六
4
「今日早报」 2026年5月15日, 农历三月廿九, 星期五
5
「今日早报」 2026年5月14日, 农历三月廿八, 星期四
沙漠渔
把過去的累積,善用到當下
累计撰写
2,789
篇文章
累计创建
385
个标签
累计收到
997
条评论
栏目
首页
烟花
归档
随笔
聚合
部落格精华
部落格资讯
CSS教程
CSS3教程
HTML教程
HTML5教程
建站经验
网站优化
资讯
今日早报
资讯日历
科技新闻
今天
罗盘
网盘
友链
留言
搜索
标签搜索
代理服务
winsw
override
VMware
api
popai
拉取镜像
人工智能
copilot
chatgpt
openai
coze
objdump
ldd
日志
版本
latest
批处理
bat
节能模式
iwconfig
排序
du
设计模式
hostname
面板
cockpit
版本不兼容
npm
统计
烟花
新春
leveldb
Java heap space
堆内存
harbor
utf8mb4
网络聚合
IPV6
nmtui
测速
带宽
千兆
路由器
nmcli
nmlci
orangepi
motd
中文乱码
webdav
香橙派
代码折叠
享元模式
单例模式
解锁
锁定
无法安装
Xshell
并发编程
ScheduledThreadPoolExecutor
ThreadPoolExecutor
线程池
Fock-Join
并发
ExecutorService
nextcloud
alist
panic
hung task
时间戳
ping
tail
dd
嵌入式
点灯
mount
共享
NFS
curl
全屏
ChannelOption
C++
comparator
桥头堡
开源
varchar
char
StringBuilder
StringBuffer
String
命令行工具
网络配置
netsh
建议
专家
离谱
2022
设备管理器
虚拟网卡
环回适配器
安全
攻击
CC
DDOS
跳槽
过年
年假
春节
2023
优站计划
鼻塞
咳嗽
大号流感
阳
新冠病毒
循环冗余校验
CRC-16/XMODEM
crc校验
stream
DMZ主机
域名解析
CDN
七牛云
DDNS
类加载器
双亲委派
加载机制
删除
搜索
变化时间
修改时间
访问时间
响应异常
超时
jsoup
用法详解
压缩命令
打包压缩
zip
优缺点
打包
数据压缩
密码
sudoers
索引量
权重
瞬间
魔鬼洞
沙漠鱼
沙漠渔
面试
引用传递
值传递
即时编译器
机器码
JIT
旗舰版
ISO
原版镜像
win7
参数
配置文件
8小时
时间错误
报告
coverity
jetbrains
idea
谷歌翻译
rest
403
ERR_UNKNOWN_URL_SCHEME
DevTools
优先级
location
FTP
挂载
curlftpfs
提示
自定义参数
springboot
配置
死锁
超级密码
桥接
联通光猫
Dockerfile
构建
命令行
eclipse
光猫
路由模式
桥接模式
bash-4.2
sudo
oracle帐号
JDK下载
百度收录
定时发布
哨兵模式
内存管理
JVM
登录
免密
不一致
服务器时间
BIO,NIO,AIO
ByteBuffer
FileWriter
BufferedOutputStream
流水线
pipeline
环境变量
重启
任务消失
jenkins
卸载
snap
20.04
Ubuntu
容器
大数据传输
InputStream
学习笔记
CSS
最佳实践
DOM操作
原型链
原型
No such file or Directory
SSH配置
SSH免密
tar
命令
网站优化
取消快照
百度快照
全局配置
修改密码
控制台
gitlab
解码器
Netty
文件传输
sz
rz
Jar
打铁花
羊毛沟
容器日志
docker
规范
博客
rejected
Gerrit
wireshark
代理
Go
http-server
nodejs
package-lock.json
996加班
删除标签
钟薛高
60s新闻接口
每日新闻接口
百度福利
自动回复
localstorage
QQ机器人
redis
近乎
客户端
JuiceSSH
乔恩
加菲猫
新浪邮箱
joe
公告
父亲节
图标
每日新闻
51la
服务器
腾讯云
插件
罗永浩
编码
avatar
小虎墩大英雄
端午劫
端午节
中石化
中国石化
儿童节
win10
激活
shell
金门大桥
旧金山
产品经理
免疫力
熬夜
云旅游
云游
招聘
海尔
halo
校友会
创新
哈工大
鸿蒙
王成录
标题
主题
北京
疫情
域名
沙漠渔溏
方法声明
jpa
表情包
emoji
JavaAgent
姓名测试
姓名打分
起名
百度
收录
SEO
group by
distinct
去重
JDBC
validationQuery
兄弟元素
点击事件
$event
9600
传输延时
波特率
串口
站点统计
站长工具
cnzz
生命周期
refs
vue
replaceAll
replace
JavaScript
软件
FRP
内网外入
内网穿透
学习
解题
leetcode
云计算
中国医药
神思电子
股票
Java
报错
数据块
关键词
风筝
清明节
注解
spring
clang-format
格式化
反向代理
nginx
索引
linux
vim
数据库
仓库管理
git
压缩
winrar
mysql
测试
markdown
目 录
CONTENT
以下是
网络聚合
相关的文章
2024-01-19
手把手教你玩转 Excel 数据透视表
1. 什么是数据透视表 数据透视表是一种可以快速汇总、分析大量数据表格的交互式分析工具。使用数据透视表可以按照数据表格的不同字段从多个角度进行透视,并建立交叉表格,用以查看数据表格不同层面的汇总信息、分析结果以及摘要数据。 使用数据透视表可以深入分析数值数据,以帮助用户发现关键数据,并做出有关企业中关键数据决策。 2. 为什么使用透视表 简单、高效、灵活、出错率低。 3. 什么时候用数据透视表 找出同类数据在不同时期的某种特定关系 以简洁友好的方式,查看大量的表格数据 对数值数据快速分类汇总,按分类和子类查看数据信息 建立交叉表格,将行移动到列或将列移动到行,以查看数据源的不同汇总 快速的计算数值数据的汇总信息、差异、个体占总体的百分比信息等 数据源经常变化 4. 数据透视表的使用方式 4-1 数据源 用于生成透视表的原始数据成为数据源。 数据源需要们组一定的规则才能成为一个合法的数据源,详细规则如下: 每列数据的第一行是该列的标题 数据源不能包含空行和空列 数据源不能包含空单元格 数据源中不能包含合并单元格 数据源中不能包含同类字段 上图中,方框圈出的区域都属于不合法的数据区域。其中,绿色区域为合并单元格,黄色为空白单元格,蓝色为同类字段,均不符合对标准数据源的要求。 4-2 透视表刷新 透视表刷新粉两种方式,手动刷新和自动刷新。 手动刷新下,也分为两种情况,分别是数据源区域未改变及数据源区域发生改变。如果只是单元格数值发生变化,可以选中透视表区域右键刷新或者在透视表分析面板中点击刷新。而如果对数据源进行了删除或新增,则需要手动的去更改数据源。 自动刷新可以在数据透视表选项面板中选择打开文件时刷新数据或使用VBA自动刷新数据表。 5. 切片器 透视表默认提供了筛选、排序等功能,但在需要多维度筛选分析数据或者多个透视表之间共享筛选条件时,默认的筛选按钮操作起来十分繁琐,并且不够直观,在这种情况下,可以使用切片器来达到数据筛选及共享条件效果。 切片器是Office 2013以上版本才有的功能,主要作用就是简化数据筛选,可应用在超级表或者透视表上。给文件中普通的区域套用表格样式之后,该区域会变成超级表,在此选择该区域,就可以插入切片器了,详细操作如下: 切片器同样也可以应用于透视表,在一个Excel文件中,基于相同的数据源,可以生成多个透视表,这些透视表之间会共享数据透视缓存。此时基于某个透视表创建的切片器,选择报表连接,即可与其它透视表共用同一个切片器,实现筛选条件的同步,详细操作如下: 6 透视表的应用场景 6-1 教学管理系统 生成课表是教学管理系统中的一个高频需求点,使用透视表可以十分快捷地生成每个班级的课程表,具体操作如下: 除了制作每个班级的课程表之外,简单的改变透视维度,又可以生成每位老师的课程表: 上边生成的透视表可以让老师们直观的看到自己每天的课程数量,也方便管理者更加直观地了解员工的工作量。 6-2 人事管理系统 人事管理系统中,高频的需求点就是对人员的分类汇总。例如,我们需要对公司所有员工按照性别进行分类,就可以基于人员信息生成透视表,具体操作如下: 该透视表,行维度为部门信息,列维度为性别,最终统计字段为员工姓名。只需简单几步,即可直观的看到人员性别的统计数据,再也不需要我们去做筛选后再统计数据了。初次之外,透视表可以应对复杂多变的统计条件,某一天,你的领导突然想知道公司人员的学历占比,此时你只需要 轻轻调节维度信息,就可以快速交工,又可以快乐的摸鱼了。基于透视表,可以生成更加直观炫酷的透视表,是时候在领导面前秀一波了。 除此之外,透视表也可用于区间数据汇总分析,例如,我们可以分年龄段统计人数。并且统计结果黏贴为普通区域,基于该区域生成一张有对比效果的图表,一起来看看吧~ 6-3 在销售中的应用 在销售管理系统中,一个高频的需求点就是根据销售订单,快速生成月报、季度报告、年报等等。使用透视表,只需要简单几步,即可完成报告的生成,再也不需要苦哈哈的手动去统计数据,之后再制作报表了。首先,我们基于销售历史数据生成一张透视表,并按照销售日期等维度制作一张基础透视表。 接下来,我们对日期创建组,实例中以月维单位,实际项目中,可以根据实际需求,按照季度等其它单位创建组。 透视表在实际业务中应用广泛,也可应用于报表的合并分析及拆分。 拓展阅读 React + Springboot + Quartz,从0实现Excel报表自动化 电子表格也能做购物车?简单三步就能实现 使用纯前端类Excel表格控件SpreadJS构建企业现金流量表
2024-01-19
255
0
0
网络聚合
2024-01-19
服务端技术方案模板参考
如何写好一个技术方案对于开发来说非常关键,这是一个工程类技术方案模板,基于我之前的一些积累,适合相对独立的业务需求承接。 实际开发中可以作为一个模板来套用,在这个基础上进行增加或者删减。 一、概要 很多时候,大家做项目的时候容易陷入为了上线而上线,尤其技术出身的同学,接到一个项目后,会陷入快速了解需求、做方案的圈子里。 通过概要分析,先了解下你要做的项目、在整个业务发展过程中所处的位置及价值,可以简单问几个问题,相信会有所帮助: 1、项目是解决哪个业务的需求?处于什么发展阶段? 2、当前业务有哪些问题/优化空间/增长点,通过这个项目能否解决? 3、参与业务上下游有哪些角色? 4、项目的成本和收益如何评价,有没有可衡量的指标体系? 5、后期业务发展的趋势如何?未来可能的增长规模多大? 基于以上的问题,为后续落地的方案可扩展性及未来业务发展做预判。 1.1 术语 统一术语的目的是让沟通在一个频道上,避免出现鸡同鸭讲,在项目后期出现返工等现象。 把术语放在最开始,以确保本文档的阅读者能够在一篇文档中就可以了解所有的关键术语。 术语将贯穿需求、分析、设计、开发、测试、维护阶段,在整个项目的生命周期中,参与者都将以统一的术语交流。 术语定义要求精确,严格,且勿模糊,含蓄; 寻找术语的方法:从需求文档中寻找那些多次出现的名词,业务名词,系统分析时引入的抽象概念。 术语范围:产品名称相关的名词,业务相关的名词,参与者相关的角色名词,边界、额度、时间相关的名词,系统抽象的名词。 如果在PRD文档中已经定义了某一个术语,建议在此处引用定义。 示例: 优惠券:商品促销手段,通过发放或者领取两种渠道,让用户在下单时抵扣一定的金额。 1.2 背景 背景是STAR法则中情境(situation)的体现,一般来自需求提出者,产研等部门。 背景通常包括项目内外部、团队内外部、组织内外部、公司内外部等。 综合不同维度上的背景,可以综合反映项目当前的价值,帮助下一步统一项目目标。 1.3 目标 基于背景分析,明确项目的目标,项目开发的意义,挖掘用户真实的可评测目标。 1.3.1 商业目标 商业目标一般会在PRD文档中明确,列出商业目标的目的是为了让我们的系统分析不要偏离业务主线。 1.3.2 系统目标 系统目标是为了支持商业目标,是商业目标在系统层面的具体化。 系统目标只是达到商业目标的充分条件,当我们的系统最终达到这几个方面,也意味着系统完全能够支持商业目标,但商业目标能否实现,与系统目标没有必然关系。 系统目标应该是具体的,可以实现的,可以达到的,相比商业目标是可落地的。 对于技术型比较强的系统,一些技术上要达到的指标也是重要的系统目标。比如一些中间件产品的技术指标,RT,SLA等。 系统目标要从当前需求出发,不局限于当前需求。 系统分析不是简单的对PRD当前需求的满足,而是站在整体产品的角度来看本次需求处于产品的那个位置,该位置在产品中的目标就是一个具体的系统目标。 二、总体设计 2.1 业务主流程 “如果你不能用一个过程来描述你正在做的事情,你就不可能真正明白你在做什么。” —— By W.爱德华兹.戴明 (PDCA循环的推广者) 系统的业务流程与具体的一个用例的流程有哪些差异? 业务流程从系统全局来看,用例关注的是自己内部的流程。 系统的业务流程是用户要完成他的业务目的,在系统层面发生着什么样的流程、事件来支撑用户的业务。 此处的流程节点同时是粗粒度的,它关注数据流、事件流在系统里的流转。 对于一些内部系统,如CRM、OA等,系统业务流程可能会成为设计阶段的BPM流程的主要来源。 对于一些小的项目,可能不存在复杂的流程,它们只是简单的请求-处理模式,此时可以不去关注系统业务流程。 对于那些改造型的项目,如果没有流程的变化,可以沿用已有的用例。 如果发生了变化,要把2个版本都画出来,且对差异之处进行重点说明。同时差异之处也是我们系统分析的重点。 2.2 系统整体架构 根据从相似系统或相似问题领域中获取的经验,定义备选架构和模型。 可以结合一些成熟的外部实例,比如电商可以参考淘宝、京东类似业务的设计。 通常需要产品团队和技术同学一起迭代来确定架构和模型,可以分为业务和技术架构。 2.3 业务系统边界 通过梳理业务系统边界,明确系统的上下游关系。 示例: 2.4 关键技术及第三方框架依赖 本系统采用了哪些关键技术,如业务算法、分布式session、分布式事务、分布式cache、分库分表等。 本系统引入了哪些外部框架、jar包。 三、模块实现 3.1 整体模块设计 模块的划分,相关描述 3.2 模块间交互接口 对接口的输入,输出,主要作用,使用场景做描述,输入输出中传递的值对象,也要做描述。不需要描述接口的实现细节。 3.3 状态机设计 对于一些复杂状态的流转,建议通过状态机的方式来描述。 3.4 XX模块功能实现 3.4.1 模块描述 输入输出,功能描述,UC图等 示例,秒杀模块设计: 3.4.2 业务流程 几个图可根据情况选择,但至少要有一个。 例如:状态图,流程度,顺序图 3.4.3 改造点(可选) 如果是改造型功能,列出改造 3.4.4 算法设计(可选) 系统内用到的算法要有技术公式和计算步骤,参数管理方案等。算法要充分考虑性能因素,大批量操作的部分要首先保证性能。需要加密的数据要定义加解密方式。 3.4.5 核心业务逻辑类图(可选) 核心业务逻辑的实现方法可事先定义好,把类图画出来 3.4.6 接口设计详细说明 比如RPC接口,HTTP接口,可以通过单独的文档描述 3.5 YY模块实现设计 同 XX模块设计 3.6 基础模块设计 可以从以下几个角度进行分析: 项目中那些公共组件是很多业务通用的? 改造了那些公共组件是替他业务也在使用的,对其他业务有什么影响? 那些基础设施在本项目中设计,但是以后也打算给其它业务使用? 给其它业务使用过程中是否需要扩展,修改? 本项目中是否已经做了本项目用不到的超前设计? 超前设计是否需要评估和测试? 3.8 关联系统改造 描述关联上下游系统需要的改造。 四、数据存储设计 描述数据库的设计,包括新的设计和原来设计的修改。 4.1 领域模型设计 关注模型,设计ER图等描述数据库设计 示例,权限模型的ER图: 4.2 数据库设计 4.2.1 表结构设计 表中字段长度要留有余量,每个字段都要有详细说明 如果记录可能删除,建议用DELETED表示是否删除(0正常,-1删除)] 预估变化比较多的表,可以增加extend活着feature字段,用于将来可能增加的字段,以键值对形式追加 4.2.2 分库分表设计 影响分表选择的因素是表的业务是否支持水平拆分、表的大小、表的访问量(QPS+TPS)。 拆分的前提是表的业务有一个好的水平拆分维度。如买家id、商家id、订单id,都可以作为拆分的维度。、业务SQL,必须避免跨分表。 表大小上一定规模后,无论是根据pk查询还是根据二级索引查询,sql的rt都会随着表的大小增长而增长,最终超出应用对数据库响应时间的需求。所以,表大了要分表。 表的访问量上一定规模后,对于根据二级索引查询,sql的rt会随着page读写冲突概率的增加而增长,最终超出应用对数据库响应时间的需求。所以,表的访问量高了也要分表。 4.2.3 数据字典设计 数据字典中要定义状态值描述 4.2.4 索引设计 列出使用的主键索引,唯一索引,普通索引。 常用的组合查询条件等 4.2.5 优化设计 注:数据冗余,优化查询方案等 表之间的关系要简单,涉及到多表的复杂查询和跨数据库的操作要合理使用冗余字段。 数据删除一般要采用逻辑删除,逻辑删除的数据要有清理机制。 合理利用事务,误操作后数据如何恢复。 根据数据流量,执行频率,制定分页方案。 要考虑数据库的设计方案对存储成本有多少影响。 4.3 缓存方案设计 缓存选型,本地缓存设计,分布式缓存设计 进一步需要考虑缓存失效等常见问题 4.4 ES索引设计 列出可能需要的索引层设计。 五、数据安全和风控 5.1 业务和数据安全 业务安全可以从如下几个方面考虑: 外部系统提交的数据是完整的,能够满足我方进行数据核对的需求 我方返回的数据是完整的,能够满足外部系统进行数据核对的需求 业务数据的机密性,关键业务数据、敏感业务数据 传输过程中涉及到隐私和机密性,需要找出来,采取对应的措施予以解决 系统支持那些类型的外部访问者,对访问者进行标示,访问者标示和业务数据的关系限制 并发情况下,数据的安全性,主要体现在脏数据,错误数据方面 当存在与外部数据关联的情况下,如果关联失败后,数据不完整的处理方案 5.2 资损风险分析 只要涉及资金(如广告消耗)和电商履约(如商品出库)的项目,都存在资损风险。 5.2.1 风险列表描述 添加资损评估checklist内容。 本项目所有的资损风险点,在此处都要有分析描述。 5.2.2 资损防控方案分析 针对某些资损风险点,需要通过一个完整的方案才能从根源上解决资损风险,具体的方案分析在这里展开。 比如,在做电商退货退款的业务设计,我们要从几个角度进行防资损: 保证退款业务操作的唯一性,针对所有请求都遵循幂等性原则; 保证幂等检查的正确性,如果通过数据库进行幂等控制,要考虑业务重试的风险; 保证退款金额的合理性,退款金额不超出原有的支付金额,在系统模型的设计上要有所体现; 保证并发情况下,不会出现超额退款; 六、性能和可测性 6.1 容量规划 从系统目标和业务的角度,进行合理的容量规划。 6.2 性能分析 从业务的角度分析对性能的需求: • 对事务的响应时间(平均、最长) • 吞吐量(例如每秒处理的事务数) • 容量(例如业务所能容纳的客户或事务数) 在性能分析的时候,通常关注业务链路的起点、连接点、受限制的资源点。 6.2.1 并发访问控制 至少可以从以下方面分析: 有没有资源需要并发访问控制? 有没有资源在并发控制出错的情况下会造成损失? 并发访问控制会不会造成性能瓶颈? 6.2.2 接口性能延时要求 有没有具体的指标或要求? 有没有可能影响现有系统? 避免使用统一的指标要求所有业务,可能各个系统use case,或各个业务有不同的性能要求,应该要分别描述。 6.3 可测性分析 可以从以下方面分析: 系统依赖的外部环境是否可以测试,是否需要开发专门的mock系统 系统依赖的时间因素是否可以测试,是否需要开发专门的配合工具进行测试 系统依赖的时间,环境,权限等因素是否可以预发布确认,是否需要特别的准备才可以预发布确认。 系统依赖的特殊因素如果不能很好的测试或预发布确认,是否需要试运行策略? 七、稳定性设计 7.1 系统降级方案 考虑极端场景下,系统的降级方案。 7.2 系统容错分析 系统的容错能力可以从以下方面进行分析: 一、系统所依赖的外部系统发生一些错误的时候,系统有没有相应的容错机制 二、系统所依赖的硬件网络等基础设施发生故障,系统有没有相应的容错机制 由于所选择的通信,存储等方面的不可确定的因素,以及在某一个临界点处导致的质变,需要我们在此处指出可能存在的风险以及解决方案。 通信层面常出故障:例如带宽太小 文件系统:文件系统损坏、文件系统容量、文件系统每个目录所能容纳的目录、文件数量的限制 文件系统同时打开的句柄数的限制,间接影响并发行、文件自身的损坏 7.3 系统和业务监控 可以从以下方面分析: 系统发布和运行期间是否需要系统日志做监控 系统发布和运行期间是否要对原有业务做监控,以便知道影响 有没有制定新的一些重要的监控指标 系统是否在特定情况下需要一些报警 系统有没有考虑以后方便的扩充监控 监控发现严重问题是否有方法来得及挽回损失 7.4 系统日志 可以从以下方面分析: 日志的写入量有没有明显变化,异步或者同步,会不会影响业务 是否有新的需要长期归档的日志,请遵守命名规范 那些业务需要从日志中进行分析,以方便进行监控或排错 作为外部交互凭据的日志是否有归档,并和其它日志分开 八、数据影响分析 8.1 对已有业务数据分析的影响 可以从以下方面分析: 本项目所作的改造有没有影响现有的数据分析进行 本项目所作的改造有没有影响现有的业务数据分析结果 如果不明确影响,应该主动找数据分析团队沟通 8.2 发布期间影响分析 可以从以下方面分析: 是否有兼容方案,可以做到业务无感知 系统发布期间服务是否正常可用 系统发布期间对用户有没有影响 系统发布期间新老系统共存产生的数据有没有影响 各个系统发布顺序有没有要求 系统发布期间的兼容性是否建议专门测试 8.3 数据归档方案 可以从以下方面分析: 新增的表是否需要回流到HIVE表进行存储 如果表的数量持续增长,是否需要进行数据归档,对历时数据进行定期清理 九、工作量评估 9.1 工作量评估 拆解到人天,可以划分具体里程碑。 十、发布计划 10.1 上线计划 根据具体的业务情况,选择是否需要灰度上线、AB方案等。
2024-01-19
248
0
0
网络聚合
2024-01-19
靶机练习 - 温故知新 - Toppo(sudo 提权)
重新做了一下以前做过的第一个靶机(https://www.cnblogs.com/sallyzhang/p/12792042.html),这个靶机主要是练习sudo提权,当时不会也没理解。 开启靶机,直接告诉了IP地址: 端口扫描: 目录扫描: 一个一个访问,发现密码: 尝试用账户root,admin,ted登录,发现ted登录成功,但不是root权限: 上传扫描文件进行信息搜集,从搜集的信息发现可进行sudo提权: 网上搜一下awk命令咋执行的。。。 查看shadow文件: 执行/bin/bash,不是root。。。 回去翻一下搜集到的信息: 再次尝试,成功: 完成: 又试了下其他几个: 有的是ted权限,有的是root,所以前期的信息搜集还是很有用的,如果只是尝试/bin/bash没有提权到root可能就放弃了。 本文仅用于技术学习和交流,严禁用于非法用途,否则产生的一切后果自行承担。 如需转载,请注明出处,这是对他人劳动成果的尊重。
2024-01-19
184
0
0
网络聚合
2024-01-19
《痞子衡嵌入式半月刊》 第 72 期
痞子衡嵌入式半月刊: 第 72 期 这里分享嵌入式领域有用有趣的项目/工具以及一些热点新闻,农历年分二十四节气,希望在每个交节之日准时发布一期。 本期刊是开源项目(GitHub: JayHeng/pzh-mcu-bi-weekly),欢迎提交 issue,投稿或推荐你知道的嵌入式那些事儿。 上期回顾 :《痞子衡嵌入式半月刊: 第 71 期》 唠两句 历史上的今天:1992年2月25日,中国已勘明稀土资源工业储量占世界的80%。 本期共收录 5 个项目、1 个工具,希望对你有帮助! 项目类 1、Newlib - 面向嵌入式系统的开源C运行库 Newlib 是一个面向嵌入式系统的 C 运行库。最初是由 Cygnus Solutions 收集组装的一个源代码集合,取名为 newlib,现在由 Red Hat 维护。 对于与 GNU 兼容的嵌入式C运行库,Newlib 并不是唯一的选择,但是从成熟度来讲,Newlib 是最优秀的。Newlib 具有独特的体系结构,使得它能够非常好地满足深度嵌入式系统的要求。Newlib 可移植性强,具有可重入特性、功能完备等特点,已广泛应用于各种嵌入式系统中。 项目主页:https://sourceware.org/newlib/ 2、AVR LibC - AVR芯片及AVR-GCC工具链下标准库 AVR LibC 库包含了 ISO C 标准所要求的大部分功能,只有 wchar_t 支持是个明显的例外。它还包含许多针对 AVR 控制器家族的辅助功能。此外,它还包括可定制的启动代码,可以与 GNU binutils 提供的链接器脚本一起工作,因此对于大多数 AVR 应用程序来说,通常不需要为此目的提供特定于项目的文件。 项目主页:https://github.com/avrdudes/avr-libc 3、Picolib - 基于newlib和AVR Libc的轻量型C库 Picolibc 是一个提供标准 C 库 api 的库,针对内存有限的小型嵌入式 32bit/64bit 系统。Picolibc 是通过混合 Newlib 和 AVR Libc 的代码而形成的。 项目主页:https://github.com/picolibc/picolibc Picolib 支持的架构如下: 4、Libwebsockets - 轻量级用来开发服务器和客户端的C库 Libwebsockets (LWS)是一个灵活的、轻量级的纯C库,用于使用非阻塞事件循环轻松实现现代网络协议,占用空间很小。自2010年以来,它一直在不断开发,并被世界各地成千上万的开发人员用于数千万台设备中。 项目主页:https://libwebsockets.org/ 5、Flipper Zero - 面向极客的开源多功能工具 Flipper Zero 是一款便携式多功能工具,适合在玩具一样的身体里测试者和极客。它喜欢入侵数字设备,比如无线电协议、访问控制系统、硬件等等。它是完全开源和可定制的,所以你可以以任何你喜欢的方式扩展它。Flipper Zero 主控是 STM32WB55RG。 项目主页:https://github.com/flipperdevices 工具类 1、Kitspace - 入门级PCB项目分享网站 Kitspace 是一个分享现成电子产品设计(PCB)的地方,可以作为 PCB 入门设计参考,通过在线小工具可以看各种参数。 项目主页:https://kitspace.org/ 欢迎订阅 文章会同时发布到我的 博客园主页、CSDN主页、知乎主页、微信公众号 平台上。 微信搜索"痞子衡嵌入式"或者扫描下面二维码,就可以在手机上第一时间看了哦。
2024-01-19
224
0
0
网络聚合
2024-01-19
重学c#系列——linq(2) [二十八]
前言 前文提及到了一些基础的linq的基础,那么这一节是一些补充。 正文 关于一个orderby的问题。 比如我们输入两个order by。 这里告诉我们多个order by是没有意义的,如果多个那么就是最后一个是有意义的。 这里要使用,不要去记最后一个是有意义的,这会让你的思维混乱,记得用一个orderby就好。用完order by之后就可以用then by了。 这里提及一下原理,前面提及到包装器,为什么生效的是最后一个呢。 通过包装成一个OrderedEnumerable: 然后foreach 前文说过其实是调用GetEnumerator: 那么只要最后一个包装才生效。 这里其实是做了优化的,为什么多个order by 只有一个生效,这是符合现实的。 比如一个是前面男生后面女生 ,前面女生后面男生,那么排序只有一个生效,前面的排序都是无效的。 order by之后,要使用then by,这个then by 就是order by排完序之后的排序。 比如order by前面男生后面女生,那么then by 是按照身高进行排序。 那么就是男生从第低到高,女生从低搞到,这样子的排序。 then by 其实也是OrderedEnumerable。 CreateOrderedEnumerable 值得看一下: 看到这个parent,那么应该想到链式结构。 就是通过这种链式结构,完成了这个order by 和 then by。有兴趣可以看一下。数据结构这不就用上了。 有兴趣可以看下。 原理就是上一个排序的时候,如果相等,那么就交给下一个排序进行比较。 张三 26 180 李四 25 180 王哥 27 179 张三 17 175 张三 26 175 orderby(name).thenby(age).thenby(high) 比如就是按照name和age 还有high。 那么首先会得到3个数组: 张三 李四 王哥 张三 张三 26,25,27,17,26 180,180,179,175,175 那么第一个数组会进行快速排序,当张三 0号 张三 3号相等的时候。 那么就会差第二个数组的0号和3号比较,因为26大17,那么17就排在前面。(order 从小到大) 当张三0号和张三4号相等,然后第二个数组的0号和4号也相等,那么就看第三个数组的0号和4号了。 大致就是这个理论了,其实只是做了一次排序。 结 下一节为groupjoin和selectmany还有匿名匿名类型linq,下下节为linq表达式。 该系列剩余40余篇,c# IL 阅读在汇编系列之后,为重学c#系列的外篇。
2024-01-19
205
0
0
网络聚合
2024-01-19
Blazor组件自做十二 : Blazor Pdf Reader PDF阅读器 组件 (新版 7.1 移除pdfobject)
Blazor Pdf Reader PDF阅读器 组件 示例: https://www.blazor.zone/PdfReaders https://blazor.app1.es/pdfReaders 使用方法: 1.nuget包 BootstrapBlazor.PdfReader 2._Imports.razor 文件 或者页面添加 添加组件库引用 @using BootstrapBlazor.Components 3.razor页面 <PdfReader Filename="https://densen.es/test/webdev/pdf/sample.pdf" /> <PdfReader UrlBase="https://blazor.app1.es/" Filename="_content/DemoShared/sample.pdf" /> <pre>流化方式,可跨域</pre> <PdfReader UrlBase="https://blazor.app1.es/" Filename="_content/DemoShared/sample.pdf" StreamMode="true"/> 4.参数说明 ** 7.1 移除pdfobject, 一些参数也被移除,请注意更改 ** 参数 说明 默认值 旧版名称 Stream 用于渲染的文件流,为空则用URL参数读取文件 PdfStream Filename PDF文件URL null StreamMode 使用流化模式,可跨域读取文件 false EnableStreamingMode UrlBase PDF文件基础路径, (使用流化模式才需要设置), https://xx.com Width 宽 单位(px/%) 100% Height 高 单位(px/%) 500px Page 页码 1 Navpanes 显示导航窗格 1 Toolbar 显示工具栏 1 Statusbar 显示状态栏 1 View *视图模式 FitV Pagemode *页面模式 thumbs Search *查询字符串 ViewerBase 浏览器页面路径 内置 PDFJS_URL 移除参数 Func<string, Task>? OnInfo Func<string, Task>? OnError ForceIframe ForcePDFJS 开源地址 https://github.com/densen2014/BootstrapBlazor.PdfReader Blazor 组件 条码扫描 ZXingBlazor 图片浏览器 Viewer 条码扫描 BarcodeScanner 手写签名 Handwritten 手写签名 SignaturePad 定位/持续定位 Geolocation 屏幕键盘 OnScreenKeyboard 百度地图 BaiduMap 谷歌地图 GoogleMap 蓝牙和打印 Bluetooth PDF阅读器 PdfReader 文件系统访问 FileSystem 光学字符识别 OCR 电池信息/网络信息 WebAPI 视频播放器 VideoPlayer AlexChow 今日头条 | 博客园 | 知乎 | Gitee | GitHub
2024-01-19
326
0
0
网络聚合
2024-01-19
Less常用功能使用
Less 是一门 CSS 预处理语言,它扩充了 CSS 语言,增加了诸如变量、混合(mixin)、函数等功能,让 CSS 更易维护、方便制作主题、扩充。Less 可以运行在 Node 或浏览器端。 Less常用的重要功能有: 1.变量 2.cala计算 3.html样的选择器嵌套 4. &父选择器本身或父选择器伪类 1.变量 包括变量的定义,变量的引用,变量的计算 值变量 @width:5000px; @height:300px; @font_size:12px; .textarea { width:@width; height:@height; font-size:@font_size; } 名称变量(选择器或属性名) @width:5000px; @height:300px; @font_size:12px; @name:.text; @b_c:border-color; .textarea { width:@width; height:@height; font-size:@font_size; } @{name} { @{b_c}: aqua; } 2.calc计算 div{ @val: 0.20rem; width: calc(~”100% - @{val}”); } 编译成: div { width: calc(100% - 0.2rem); } 3.选择器嵌套,引用 less的使用像html一样使用的嵌套结构,并且可以引用 .bordered { border-top: dotted 1px black; border-bottom: solid 2px black; } //引用使用 #menu a .post a { color: red; .bordered(); } 4. &父选择器引用 在使用嵌套规则时,需要注意 & 符号。 当内层选择器前面没有& 符号时,它表示的是父选择器后面的后代选择器。 如果有& 时,表示的是父元素自身或父元素的伪类 .van-cell { background-color: #007bff; color: white; &::after { display: none; } .avatar { width: 60px; height: 60px; background-color: #fff; border-radius: 50%; margin-right: 10px; } .username { font-size: 14px; font-weight: bold; } } 更多使用方式从官网查看:https://less.bootcss.com/#概览 伪类与伪元素介绍 伪元素: 使用::before ::after修饰,创建的一个有内容的虚拟容器。这个元素虽然逻辑上存在,但并不存在于实际的DOM树中,它是新创建的元素,这个新创建的元素叫“伪元素”。 伪类(xxx的伪类,如a:link): 使用:link :hover修饰,它存在于DOM树中,逻辑上存在,但在DOM树上无须标识的“幽灵”标签。 :before/:after是Css2的写法, ::before/::after是Css3的写法 1.伪元素要配合content属性一起使用 2.伪元素不会出现在DOM中,所以不能通过js来操作,仅仅是在 CSS 渲染层加入 3.伪元素的特效通常要使用:hover伪类样式来激活 4.eg:当鼠标移在span上时,span前插入”duang” <style> span{ background: yellow; } span:hover::before{ content:"duang"; } </style> <span>222</span> 参考文章:https://blog.csdn.net/weixin_49115895/article/details/108683791
2024-01-19
192
0
0
网络聚合
2024-01-19
室内单目深度估计-3
注: 研究方向为depth estimation,欢迎同一个方向的加入QQ群(602708168)交流。 1. 论文简介 论文题目:MonoIndoor: Towards Good Practice of Self-Supervised Monocular Depth Estimation for Indoor Environments Paper地址:https://openaccess.thecvf.com/content/ICCV2021/papers/Ji_MonoIndoor_Towards_Good_Practice_of_Self-Supervised_Monocular_Depth_Estimation_for_ICCV_2021_paper.pdf Paper类型:深度学习(自监督) 发表刊物:ICCV 发表时间:2019 2. Abstract 室内单目深度估计比室外更具挑战: 室内的深度范围在不同的帧之间变化很大,使得深度网络很难诱导出一致的深度线索,而室外的的最大距离基本上保持不变,因为最远处一般是天空,无论相机怎么移动,天空始终无穷远。(这里需要注意的是:室外深度估计一般会将深度约束在一定范围,比如[0.1, 100],所以无论怎么变化,无穷远处影响不大); 室内的图像序列之间包含更多的旋转运动,而室外主要是平移移动,比如说KITTI数据集。注意:旋转运动对于姿态网络是一种困难。 本文主要提出的方法,一篇半监督单目室内场景深度估计: 深度分解模块:将depth map分解成一个全局深度尺度因子和一个相对深度,这里比较重要的是深度尺度因子,用于自适应的适应训练过程中深度尺度的变化。 残差姿态估计模块:核心就是一次性完成source view到target view的工作,换成了几次迭代来完成,缓解旋转预测不准确的问题。 3. Abstract 深度估计的背景介绍,价值和意义。 现有半监督方法的简单介绍,主要说明存在的问题: 性能上比不过监督方法; 室外比室内更好做半监督; 室内深度估计对比室外深度估计存在的两个关键挑战: 场景内的深度变化大,让深度网络在跨图像保持深度一致上存在困难;其实可以理解为,深度网络学习固定深度尺度比较好学习,当深度尺度各种变化时,它就很难拟合了。 针对姿态估计网络的,在半监督方法中,通常采用由源视角的图像投影(合成)到目标视角的图像,或者反过来。一般的做法是一次性根据相机内参,深度图,姿态网络估计的相对位姿来投影得到图像做loss,本文采用残差的方式,多阶段生成相对相机位姿阶段性合成图像,最终把所有合成图像加起来的到最终图像。类似一种软过度。 室内深度估计困难的根本原因是:人工手持相机捕获图像,或者MAVs方式,就会导致旋转。; 本文的框架以细节,前面已经有很详细的描述了:深度分解模块和残差姿态估计模块: 在深度分解模块中,我们将深度图分解为全局深度比例尺(用于当前图像)和相对深度图。深度尺度因子由深度网络中的一个额外分支单独预测。这样,深度网络具有更强的模型可塑性,能够适应训练过程中深度尺度的变化。 在残差位姿估计模块中,我们通过执行残差位姿估计以及初始大位姿预测来缓解旋转预测不准确的问题。这种残差方法可以更准确地计算光度损失,这反过来又可以更好地训练深度网络的模型。 3. Related Work 在本节中,回顾了单眼深度估计的监督和自监督方法。 3.1 Supervised Monocular Depth Estimation 早期的深度估计方法大多是有监督的。 Saxena等[30]用超像素特征和马尔可夫随机场(MRF)回归单幅图像的深度。 Eigen等[6]提出了第一个基于深度学习的单目深度估计方法,使用多尺度卷积神经网络(CNN)。后来的方法通过更好的网络架构[19]或通过更复杂的训练损失来提高深度预测的性能[21,8,41]。一些方法[363,34]依赖于两个网络,一个用于深度预测,另一个用于运动,在监督框架中模拟几何运动结构(SfM)或同步定位和映射(SLAM)。训练这些方法需要地面真相深度数据,而获取这些数据通常成本很高。还有一些方法采用传统的三维重建方法生成伪地真深度标签[23,22],如SfM[31]和SLAM[26],或3D电影[28]。这种方法具有更好的跨不同数据集的泛化能力,但不一定能达到手头数据集的最佳性能。 3.2 Self-Supervised Monocular Depth Estimation 由于自监督深度估计不需要使用地面事实进行训练,因此近年来受到了广泛的关注。沿着这条线,Garg等[9]提出了第一个自监督方法,使用立体图像之间的颜色一致性损失来训练单目深度模型。 Zhou等人[46]使用两个网络(即一个深度网络和一个姿态网络)来构建跨时间框架的光度损失。许多后续方法试图通过新的损失条款来改善自我监督。戈达尔等[11]在立体训练中引入了左右深度一致性损失。Bian等人[1]提出了时间深度一致性损失,以鼓励相邻帧具有一致的深度预测。Wang等[37]在训练过程中观察到了深度模型的递减问题,并提出了一种简单的归一化方法来对抗这种影响。Yin et al[42]和Zou et al[48]使用三个网络(即一个深度网络,一个姿态网络和一个额外流网络)来强制光流和密集深度之间的跨任务一致性。Wang等人[39]和Zou等人[47]利用循环神经网络(如LSTMs)来建模姿态网络和/或深度网络中的长期依赖关系。Tiwari等人[35]利用单目SLAM和自监督深度模型[12]组成了一个自改善环,以提高各自的性能。值得注意的是,Monodepth2[12]通过一组技术显著提高了以前的方法的性能:每像素最小光度损失来处理遮挡,自动掩蔽方法来掩盖静态像素,以及多尺度深度估计策略来缓解深度纹理复制问题。由于Monodepth2的良好性能,我们实现了基于Monodepth2的自监督深度估计框架,但对深度和位姿网络都做了重要的改变。 上述大多数方法仅在KITTI等室外数据集上进行评估。 一些最近的方法[45,44,2]集中在室内自监督深度估计。Zhou等人[45]提出了一种基于光流的训练范式,并通过预处理步骤去除所有具有“纯旋转”的图像对来处理较大的旋转运动。Zhao等[44]采用几何增广策略,通过两视图三角剖分求解深度,然后使用三角剖分深度作为监督。Bian等[2]认为“训练时旋转表现为噪声”,并提出了一个整流步骤来去除连续帧之间的旋转。我们有一个类似[45]和[2]的观察,大的旋转会给网络带来困难。然而,我们采取了不同的策略。我们没有从训练数据中去除旋转,而是通过一种新的残差姿态模块逐步估计它们。这反过来又提高了深度预测。 4. Method 在本节中,我们将详细描述如何使用MonoIndoor执行自监督深度估计。 具体来说,我们首先介绍了自监督深度估计的背景。然后,我们描述了使用MonoIndoor预测深度的良好实践。 4.1 Self-Supervised Depth Estimation 半监督的核心思想(基于投影策略): 将自监督深度估计作为一种新的视图合成问题,通过训练一个模型来从源图像的不同视点预测目标图像。 利用深度图作为桥接变量对图像合成过程进行训练和约束。该系统既需要目标图像的预测深度图,也需要一对目标图像和源图像之间的估计相对位姿。 具体来说,给定目标图像It和另一个视图下的源图像It0,联合训练系统预测目标图像的密集深度图Dt和从目标到源的相对相机姿态Tt→t0。 公式(1)(2)就是投影中的两个核心公式,公式2将源图像投影到目标图像,公式(1)计算目标图像与投影图像直接的loss。 公式(3)是对公式(1)的具体展开,公式(4)是公式(2)的细节; 公式(5)是一个常用的损失函数。 深度一致损失,公式(7)。 现有的自监督深度估计方法在室外很好,室内不好。 4.2 Depth Factorization 这一段我觉得是特别重要,因为说明了室内场景与室外场景本质的不同。 本文使用Monodepth2作为backbone。 注意,最终的深度预测不是直接来自卷积层,而是经过一个sigmoid激活函数和一个线性缩放函数,如公式(8)所示。 在公式(8)中,a和b将深度图D限制在一定范围内,a表示最小深度值,b表示最大深度值。比如KITTI数据集,a被设置为0.1,b被设置为100。主要是因为室外场景相机最远点在天空,永远无限远,所以统一被设置为100。 但是,公式(8)不太适用于室内场景。因为随着场景的变化,深度变化时不一样的。比如说,浴室大概是[0.1, 3],而大厅一般为[0.1, 10]。 预设深度范围将作为一个不准确的引导,不利于模型捕获准确的深度尺度。 在室内场景中常见的快速尺度变化尤其如此。为了克服这个问题,我们提出了深度分解模块(参见图1),以相对深度图和全局比例因子的形式学习解纠缠表示。 我们使用Monodepth2[12]的深度网络来预测相对深度,并提出了一种自注意引导的尺度回归网络来预测当前视图的全局尺度因子(这一步应该是自适应策略)。 4.2.1 Scale Network 尺度网络本身是一个self-attention block,输入为encoder得到的特征,首先得到查询,键和值,接着查询和键结合后经过一个softmax,再和值结合,最后与输入的特征结合,经过1×1卷积输出一个全局深度尺度因子。 4.2.2 Probabilistic Scale Regression Head 为了预测全局尺度,高维特征图必须映射为单个正数。一种直接的方法是让网络直接回归得到尺度因子。然而,我们观察到使用这种方法训练是不稳定的。为了缓解这个问题,受[4]的启发,我们建议使用概率尺度回归头来估计这个连续值。给定一个全局尺度因子所处的最大边界,通过softmax(·)运算,从尺度网络eS的输出中计算出每个尺度s的概率。预测的全局尺度S计算为公式(11)。 通过这样做,回归问题可以通过基于概率分类的策略顺利解决(更多消融结果见第4.1.1节)。 4.2.3 Residual Pose Estimation 如3.1节所述,自监督深度估计建立在新的视图合成之上,这需要精确的深度图和相机姿态。估计准确的相对位姿是光度重投影损失的关键,因为不准确的位姿可能导致目标和源像素之间的错误对应,从而导致预测深度的问题。现有的方法大多使用一个独立的PoseNet来估计两幅图像之间的6自由度(DoF)姿态。在户外环境中(例如,像KITTI这样的驾驶场景),相对的相机姿势是相当简单的,因为汽车大部分是向前移动的,平移很大,但旋转很小。这意味着姿态估计通常不那么具有挑战性。相比之下,在室内环境中,序列通常是用手持设备(如Kinect)记录的,因此涉及到更复杂的自我运动以及更大的旋转运动。因此,姿态网络学习精确的摄像机姿态更加困难。 与现有方法[45,2]在数据预处理过程中专注于“去除”或“减少”旋转分量不同,我们提出了残差姿态估计模块,迭代学习目标与源图像之间的相对相机姿态(如图2所示)。 在第一阶段,姿态网络以目标图像It和源图像It0为输入,预测初始相机姿态Tt0→t,其中t0中的下标0表示还没有应用转换。然后根据式(2)对源图像进行双线性采样,重建虚拟视图It0→t,如果对应关系匹配准确,则期望它与目标图像相同。然而,由于不准确的姿态预测,情况不会如此。注意这里的转换定义为公式(12)。 第二阶段,我们利用残差姿态网络(见图1中的ResidualPoseNet),将目标图像和合成视图It0→t作为输入,输出残差相机姿态t res(t0→t)→t,表示合成图像It0→t相对于目标图像的相机姿态。现在,我们对合成图像使用公式(13)进行双线性采样得到新的合成视角。 后面可以继续迭代第二阶段。 最终的公式可以表达为公式(14),在获得多个残差姿态自后,总的相机姿态可以表达为所有残差姿态的求和,如公式(15)。 我猜想,公式(14)替代了公式(2)。 5. Experiments 数据集:EuRocMAV,NYUv2,RGB-D 7-Scenes。 验证指标:常规的几个。 实现细节:Implementation Details 我们使用PyTorch[27]来实现我们的模型。在深度分解模块中,我们使用与[12]中相同的深度网络;对于规模网络,我们使用两个基本剩余块,然后是三个完全连接的层,中间是一个辍学层。退出率设置为0.5。在残差位姿模块中,我们让残差位姿网络使用一种通用的结构[12],由共享的姿态编码器和独立的姿态回归器组成。每个实验使用Adam[17]优化器训练40个epoch,前20个epoch的学习率设置为10−4,其余epoch的学习率降至10−5。平滑项τ和一致性项γ分别设为0.001和0.05。 6.1 EuRoC MA V Dataset 6. Conclusion 亮点: 本文对室内场景对比室外场景在做深度估计任务上存在的难点做了很好的分析,个人觉得很适合想做室内深度估计的同学阅读; 本文其实是针对无监督的两个核心:深度估计网络,姿态估计网络;深度估计网络对于深度尺度变化大时难以拟合,而姿态估计对于旋转问题很难拟合。因此提出了分别的针对方法。针对深度估计网络,进行因式分解,分别学习全局深度尺度和相对深度图;针对姿态估计网络,设计多阶段的残差学习软过度合成图像过程,替代一次性合成。 不足: 总体框架还是很不错,可惜的是自己的设计相对较少,backbone是monodepth2,尺度因子估计用的是self-attention module,大多是用的现有方法。 代码未公布,对于一些消融细节比较难确定是否有效。 多阶段残差学习是否真的有效,我不太确定。 7. 结语 努力去爱周围的每一个人,付出,不一定有收获,但是不付出就一定没有收获! 给街头卖艺的人零钱,不和深夜还在摆摊的小贩讨价还价。愿我的博客对你有所帮助(*^▽^*)(*^▽^*)! 如果客官喜欢小生的园子,记得关注小生哟,小生会持续更新(#^.^#)(#^.^#)。
2024-01-19
227
0
0
网络聚合
2024-01-19
基于AbstractProcessor扩展MapStruct自动生成实体映射工具类
作者:京东物流 王北永 姚再毅 1 背景 日常开发过程中,尤其在 DDD 过程中,经常遇到 VO/MODEL/PO 等领域模型的相互转换。此时我们会一个字段一个字段进行 set|get 设置。要么使用工具类进行暴力的属性拷贝,在这个暴力属性拷贝过程中好的工具更能提高程序的运行效率,反之引起性能低下、隐藏细节设置 OOM 等极端情况出现。 2 现有技术 直接 set|get 方法:字段少时还好,当字段非常大时工作量巨大,重复操作,费时费力。 通过反射 + 内省的方式实现值映射实现:比如许多开源的 apache-common、spring、hutool 工具类都提供了此种实现工具。这种方法的缺点就是性能低、黑盒属性拷贝。不同工具类的处理又有区别:spring 的属性拷贝会忽略类型转换但不报错、hutool 会自动进行类型转、有些工具设置抛出异常等等。出现生产问题,定位比较困难。 mapstruct:使用前需要手动定义转换器接口,根据接口类注解和方法注解自动生成实现类,属性转换逻辑清晰,但是不同的领域对象转换还需要单独写一层转换接口或者添加一个转换方法。 3 扩展设计 3.1 mapstruct 介绍 本扩展组件基于 mapstruct 进行扩展,简单介绍 mapstruct 实现原理。 mapstruct 是基于 JSR 269 实现的,JSR 269 是 JDK 引进的一种规范。有了它,能够实现在编译期处理注解,并且读取、修改和添加抽象语法树中的内容。JSR 269 使用 Annotation Processor 在编译期间处理注解,Annotation Processor 相当于编译器的一种插件,因此又称为插入式注解处理。 我们知道,java 的类加载机制是需要通过编译期运行期。如下图所示 mapstruct 正是在上面的编译期编译源码的过程中,通过修改语法树二次生成字节码,如下图所示 以上大概可以概括如下几个步骤: 1、生成抽象语法树。Java 编译器对 Java 源码进行编译,生成抽象语法树(Abstract Syntax Tree,AST)。 2、调用实现了 JSR 269 API 的程序。只要程序实现了 JSR 269 API,就会在编译期间调用实现的注解处理器。 3、修改抽象语法树。在实现 JSR 269 API 的程序中,可以修改抽象语法树,插入自己的实现逻辑。 4、生成字节码。修改完抽象语法树后,Java 编译器会生成修改后的抽象语法树对应的字节码文件件。 从 mapstruct 实现原理来看,我们发现 mapstruct 属性转换逻辑清晰,具备良好的扩展性,问题是需要单独写一层转换接口或者添加一个转换方法。能否将转换接口或者方法做到自动扩展呢? 3.2 改进方案 上面所说 mapstruct 方案,有个弊端。就是如果有新的领域模型转换,我们不得不手动写一层转换接口,如果出现 A/B 两个模型互转,一般需定义四个方法: 鉴于此,本方案通过将原 mapstruct 定义在转换接口类注解和转换方法的注解,通过映射,形成新包装注解。将此注解直接定义在模型的类或者字段上,然后根据模型上的自定义注解直接编译期生成转换接口,然后 mapstruct 根据自动生成的接口再次生成具体的转换实现类。 注意:自动生成的接口中类和方法的注解为原 mapstruct 的注解,所以 mapstruct 原有功能上没有丢失。详细调整如下图: 4 实现 4.1 技术依赖 编译期注解处理器 AbstractProcessor:Annotation Processor 相当于编译器的一种插件,因此又称为插入式注解处理。想要实现 JSR 269,主要有以下几个步骤。 1)继承 AbstractProcessor 类,并且重写 process 方法,在 process 方法中实现自己的注解处理逻辑。 2)在 META-INF/services 目录下创建 javax.annotation.processing.Processor 文件注册自己实现的 谷歌 AutoService:AutoService 是 Google 开源的用来方便生成符合 ServiceLoader 规范的开源库,使用非常的简单。只需要增加注解,便可自动生成规范约束文件。 知识点: 使用 AutoService 的好处是帮助我们不需要手动维护 Annotation Processor 所需要的 META-INF 文件目录和文件内容。它会自动帮我们生产,使用方法也很简单,只需要在自定义的 Annotation Processor 类上加上以下注解即可 @AutoService (Processor.class) mapstruct:帮助实现自定义插件自动生成的转换接口,并注入到 spring 容器中 (现有方案中已做说明)。 javapoet:JavaPoet 是一个动态生成代码的开源库。帮助我们简单快速的生成 java 类文件,期主要特点如下: JavaPoet 是一款可以自动生成 Java 文件的第三方依赖。 简洁易懂的 API,上手快。 让繁杂、重复的 Java 文件,自动化生成,提高工作效率,简化流程。 4.2 实现步骤 第一步:自动生成转换接口类所需的枚举,分别为类注解 AlpacaMap 和字段注解 AlpacaMapField。 1) AlpacaMap:定义在类上,属性 target 指定所转换目标模型;属性 uses 指定雷专转换过程中所依赖的外部对象。 2)AlpacaMapField:原始 mapstruct 所支持的所有注解做一次别名包装,使用 spring 提供的 AliasFor 注解。 知识点: @AliasFor 是 Spring 框架的一个注解,用于声明注解属性的别名。它有两种不同的应用场景: 注解内的别名 元数据的别名 两者主要的区别在于是否在同一个注解内。 第二步:AlpacaMapMapperDescriptor 实现。此类主要功能是加载使用第一步定义枚举的所有模型类,然后将类的信息和类 Field 信息保存起来方便后面直接使用,片段逻辑如下: AutoMapFieldDescriptor descriptor = new AutoMapFieldDescriptor(); descriptor.target = fillString(alpacaMapField.target()); descriptor.dateFormat = fillString(alpacaMapField.dateFormat()); descriptor.numberFormat = fillString(alpacaMapField.numberFormat()); descriptor.constant = fillString(alpacaMapField.constant()); descriptor.expression = fillString(alpacaMapField.expression()); descriptor.defaultExpression = fillString(alpacaMapField.defaultExpression()); descriptor.ignore = alpacaMapField.ignore(); .......... 第三步:AlpacaMapMapperGenerator 类主要是通过 JavaPoet 生成对应的类信息、类注解、类方法以及方法上的注解信息 生成类信息:TypeSpec createTypeSpec(AlpacaMapMapperDescriptor descriptor) 生成类注解信息 AnnotationSpec buildGeneratedMapperConfigAnnotationSpec(AlpacaMapMapperDescriptor descriptor) { 生成类方法信息: MethodSpec buildMappingMethods(AlpacaMapMapperDescriptor descriptor) 生成方法注解信息:List<AnnotationSpec> buildMethodMappingAnnotations(AlpacaMapMapperDescriptor descriptor){ 在实现生成类信息过程中,需要指定生成类的接口类 AlpacaBaseAutoAssembler,此类主要定义四个方法如下: public interface AlpacaBaseAutoAssembler<S,T>{ T copy(S source); default List<T> copyL(List<S> sources){ return sources.stream().map(c->copy(c)).collect(Collectors.toList()); } @InheritInverseConfiguration(name = "copy") S reverseCopy(T source); default List<S> reverseCopyL(List<T> sources){ return sources.stream().map(c->reverseCopy(c)).collect(Collectors.toList()); } } 第四步:因为生成的类转换器是注入 spring 容器的。所以需要顶一个专门生成 mapstruct 注入 spring 容器的注解,此注解通过类 AlpacaMapSpringConfigGenerator 自动生成,核心代码如下 private AnnotationSpec buildGeneratedMapperConfigAnnotationSpec() { return AnnotationSpec.builder(ClassName.get("org.mapstruct", "MapperConfig")) .addMember("componentModel", "$S", "spring") .build(); } 第五步:通过以上步骤,我们定义好了相关类、相关类的方法、相关类的注解、相关类方法的注解。此时将他们串起来通过 Annotation Processor 生成类文件输出,核心方法如下 private void writeAutoMapperClassFile(AlpacaMapMapperDescriptor descriptor){ System.out.println("开始生成接口:"+descriptor.sourcePackageName() + "."+ descriptor.mapperName()); try (final Writer outputWriter = processingEnv .getFiler() .createSourceFile( descriptor.sourcePackageName() + "."+ descriptor.mapperName()) .openWriter()) { alpacaMapMapperGenerator.write(descriptor, outputWriter); } catch (IOException e) { processingEnv .getMessager() .printMessage( ERROR, "Error while opening "+ descriptor.mapperName() + " output file: " + e.getMessage()); } } 知识点: 在 javapoet 中核心类第一大概有一下几个类,可参考如下: JavaFile 用于构造输出包含一个顶级类的 Java 文件,是对.java 文件的抽象定义 TypeSpec TypeSpec 是类 / 接口 / 枚举的抽象类型 MethodSpec MethodSpec 是方法 / 构造函数的抽象定义 FieldSpec FieldSpec 是成员变量 / 字段的抽象定义 ParameterSpec ParameterSpec 用于创建方法参数 AnnotationSpec AnnotationSpec 用于创建标记注解 5 实践 下面举例说明如何使用,在这里我们定义一个模型 Person 和模型 Student,其中涉及字段转换的普通字符串、枚举、时间格式化和复杂的类型换砖,具体运用如下步骤。 5.1 引入依赖 代码已上传代码库,如需特定需求可重新拉去分支打包使用 <dependency> <groupId>com.jdl</groupId> <artifactId>alpaca-mapstruct-processor</artifactId> <version>1.1-SNAPSHOT</version> </dependency> 5.2 对象定义 uses 方法必须为正常的 spring 容器中的 bean,此 bean 提供 @Named 注解的方法可供类字段注解 AlpacaMapField 中的 qualifiedByName 属性以字符串的方式指定,如下图所示 @Data @AlpacaMap(targetType = Student.class,uses = {Person.class}) @Service public class Person { private String make; private SexType type; @AlpacaMapField(target = "age") private Integer sax; @AlpacaMapField(target="dateStr" ,dateFormat = "yyyy-MM-dd") private Date date; @AlpacaMapField(target = "brandTypeName",qualifiedByName ="convertBrandTypeName") private Integer brandType; @Named("convertBrandTypeName") public String convertBrandTypeName(Integer brandType){ return BrandTypeEnum.getDescByValue(brandType); } @Named("convertBrandTypeName") public Integer convertBrandType(String brandTypeName){ return BrandTypeEnum.getValueByDesc(brandTypeName); } } 5.3 生成结果 使用 maven 打包或者编译后观察,此时在 target/generated-source/annotatins 目录中生成两个文件 PersonToStudentAssembler 和 PersonToStudentAssemblerImpl 类文件 PersonToStudentAssembler 是由自定义注解器自动生成,内容如下 @Mapper( config = AutoMapSpringConfig.class, uses = {Person.class} ) public interface PersonToStudentAssembler extends AlpacaBaseAutoAssembler<Person, Student> { @Override @Mapping( target = "age", source = "sax", ignore = false ) @Mapping( target = "dateStr", dateFormat = "yyyy-MM-dd", source = "date", ignore = false ) @Mapping( target = "brandTypeName", source = "brandType", ignore = false, qualifiedByName = "convertBrandTypeName" ) Student copy(final Person source); } PersonToStudentAssemblerImpl 是 mapstruct 根据 PersonToStudentAssembler 接口注解器自动生成,内容如下 @Component public class PersonToStudentAssemblerImpl implements PersonToStudentAssembler { @Autowired private Person person; @Override public Person reverseCopy(Student arg0) { if ( arg0 == null ) { return null; } Person person = new Person(); person.setSax( arg0.getAge() ); try { if ( arg0.getDateStr() != null ) { person.setDate( new SimpleDateFormat( "yyyy-MM-dd" ).parse( arg0.getDateStr() ) ); } } catch ( ParseException e ) { throw new RuntimeException( e ); } person.setBrandType( person.convertBrandType( arg0.getBrandTypeName() ) ); person.setMake( arg0.getMake() ); person.setType( arg0.getType() ); return person; } @Override public Student copy(Person source) { if ( source == null ) { return null; } Student student = new Student(); student.setAge( source.getSax() ); if ( source.getDate() != null ) { student.setDateStr( new SimpleDateFormat( "yyyy-MM-dd" ).format( source.getDate() ) ); } student.setBrandTypeName( person.convertBrandTypeName( source.getBrandType() ) ); student.setMake( source.getMake() ); student.setType( source.getType() ); return student; } } 5.4 Spring 容器引用 此时在我们的 spring 容器中可直接 @Autowired 引入接口 PersonToStudentAssembler 实例进行四种维护数据相互转换 AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); applicationContext.scan("com.jdl.alpaca.mapstruct"); applicationContext.refresh(); PersonToStudentAssembler personToStudentAssembler = applicationContext.getBean(PersonToStudentAssembler.class); Person person = new Person(); person.setMake("make"); person.setType(SexType.BOY); person.setSax(100); person.setDate(new Date()); person.setBrandType(1); Student student = personToStudentAssembler.copy(person); System.out.println(student); System.out.println(personToStudentAssembler.reverseCopy(student)); List<Person> personList = Lists.newArrayList(); personList.add(person); System.out.println(personToStudentAssembler.copyL(personList)); System.out.println(personToStudentAssembler.reverseCopyL(personToStudentAssembler.copyL(personList))); 控制台打印: personToStudentStudent(make=make, type=BOY, age=100, dateStr=2022-11-09, brandTypeName=集团KA) studentToPersonPerson(make=make, type=BOY, sax=100, date=Wed Nov 09 00:00:00 CST 2022, brandType=1) personListToStudentList[Student(make=make, type=BOY, age=100, dateStr=2022-11-09, brandTypeName=集团KA)] studentListToPersonList[Person(make=make, type=BOY, sax=100, date=Wed Nov 09 00:00:00 CST 2022, brandType=1)] 注意: qualifiedByName 注解属性使用不太友好,如果使用到此属性时,需要定义反转类型转换函数。因为在前面我们定义的抽象接口 AlpacaBaseAutoAssembler 有如下图一个注解,从目的对象到源对象的反转映射,因为 java 的重载性,同名不同参非同一个方法,所以在 S 转 T 的时候回找不到此方法。故需要自行定义好转换函数 @InheritInverseConfiguration(name = "copy") 比如从 S 转换 T 会使用第一个方法,从 T 转 S 的时候必须定义一个同名 Named 注解的方法,方法参数和前面方法是入参变出参、出参变入参。 @Named("convertBrandTypeName") public String convertBrandTypeName(Integer brandType){ return BrandTypeEnum.getDescByValue(brandType); } @Named("convertBrandTypeName") public Integer convertBrandType(String brandTypeName){ return BrandTypeEnum.getValueByDesc(brandTypeName); } 在使用 qualifiedByName 注解时,指定的 Named 注解方法必须定义为 spring 容器可管理的对象,并需要通过模型类注解属性 used 引入此对象 Class 知识点: InheritInverseConfiguration 功能很强大,可以逆向映射,从上面 PersonToStudentAssemblerImpl 看到上面属性 sax 可以正映射到 sex,逆映射可自动从 sex 映射到 sax。但是正映射的 @Mapping#expression、#defaultExpression、#defaultValue 和 #constant 会被逆映射忽略。此外某个字段的逆映射可以被 ignore,expression 或 constant 覆盖 6 结束语 参考文档: https://github.com/google/auto/tree/master/service https://mapstruct.org/ https://github.com/square/javapoet
2024-01-19
219
0
0
网络聚合
2024-01-19
UTF-8编码
介绍 UTF-8 编码 UTF-8 是一种针对 Unicode 的可变长度字符编码。 针对 Unicode:UTF-8 是 Unicode 的实现方式之一。相当于 Unicode 规定了字符对应的代码值,这个代码值需要转换为字节序列的形式,用于数据存储、传输。代码值到字节序列的转换工作由 UTF-8 来完成。 可变长度字符编码:UTF-8 使用一至四个字节对 Unicode 字符集中的所有有效代码点进行编码。 UTF-8 使用 1 个字节表示 ASCII 字符; UTF-8 使用 2 个字节表示带有附加符号的拉丁文、希腊文等; UTF-8 使用 3 个字节表示其他基本多文种平面(BMP)中的字符(包含了大部分常用字,如大部分的汉字); UTF-8 使用 4 个字节表示 Unicode 辅助平面的字符。 技术是为了解决问题而生的,UTF-8 编码是为了解决什么问题而设计的呢?UTF-8 是为了兼容 ASCII 编码而设计的。 ASCII 编码使用 1 个字节表示 ASCII 字符,而 Unicode 最初规定使用 2 个字节来表示所有的 Unicode 字符。如果使用 2 个字节来表示 ASCII 字符的话,那么含有大量 ASCII 字符的文本将浪费大量的存储空间。 UTF-8 编码使用 1 个字节来表示 ASCII 字符,而且字面与 ASCII 码的字面一一对应,这使得原来处理 ASCII 字符的软件无须或只须做少部分修改,即可继续使用。 UTF-8 编码的规则 Unicode 和 UTF-8 之间的转换关系表(x 字符表示码点占据的位) 码点的位数 码点起值 码点终值 Byte 1 Byte 2 Byte 3 Byte 4 Byte 5 Byte 6 7 U+0000 U+007F 1 0xxxxxxx 11 U+0080 U+07FF 2 110xxxxx 10xxxxxx 16 U+0800 U+FFFF 3 1110xxxx 10xxxxxx 10xxxxxx 21 U+10000 U+1FFFFF 4 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 26 U+200000 U+3FFFFFF 5 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 31 U+4000000 U+7FFFFFFF 6 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx UTF-8 编码的规则: 在 ASCII 码范围内的代码点,UTF-8 使用 1 个字节表示。 大于 ASCII 码范围的代码点,UTF-8 使用多个字节表示。UTF-8 使用第一个字节的前几位表示该 Unicode 字符的字节长度(第一个字节的开头 1 的数目就是该 Unicode 字符的字节长度),其余字节的前两位固定为 10,作为标记 如果第一个字节的前两位为 1,第三位为 0(110xxxxx),则表示 UTF-8 使用 2 个字节表示该 Unicode 字符; 如果第一个字节的前三位为 1,第四位为 0(1110xxxx),则表示 UTF-8 使用 3 个字节表示该 Unicode 字符; 依此类推; 如果第一个字节的前六位为 1,第七位为 0(1111110x),则表示 UTF-8 使用 6 个字节表示该 Unicode 字符; UTF-8 编码的字节含义:对于 UTF-8 编码中的任意字节 B: 如果 B 的第一位为 0(0xxxxxxx),则 B 独立的表示一个 ASCII 字符; 如果 B 的第一位为 1,第二位为 0(10xxxxxx),则 B 为一个多字节表示的字符中的一个字节; 如果 B 的前二 / 三 / 四 / 五 / 六位为 1,其余位为 0,则 B 为二 / 三 / 四 / 五 / 六个字节表示的字符中的第一个字节。 UTF-8 编码示例 Unicode/UTF-8-character table (utf8-chartable.de) 通过 UTF-8 编码表,我们可以看到中文字符 “一” 的 Unicode 代码点为 "U+4E00",UTF-8 编码结果为 "e4 b8 80", 对中文字符 “一” 进行 UTF-8 编码,是如何得到 "e4 b8 80" 的呢?我们下面来看。 "4E00" 的二进制表示为 "0100 1110 0000 0000"。 UTF-8 使用 3 个字节表示常用的汉字,因此中文字符对应的字节序列格式为:"1110xxxx 10xxxxxx 10xxxxxx" 于是中文字符 “一” 的 UTF-8 编码结果为 "11100100 10111000 10000000",它的十六进制表示为 "e4 b8 80" public static void main(String[] args) throws UnsupportedEncodingException { byte[] bytes = "一".getBytes("UTF-8"); // [-28, -72, -128] System.out.println(Arrays.toString(bytes)); } UTF-8 编码的优劣局限 UTF-8 编码的优点 UTF-8 和 ASCII 兼容:ASCII 是 UTF-8 的一个子集。因为一个纯 ASCII 字符串也是一个合法的 UTF-8 字符串,所以现存的 ASCII 文本不需要转换。为传统的扩展 ASCII 字符集设计的软件通常可以不经修改或很少修改就能与 UTF-8 一起使用。 任何面向字节的字符串搜索算法都可以用于 UTF-8 的数据(只要输入仅由完整的 UTF-8 字符组成)。UTF-8 可以保证一个字符的字节序列不会包含在另一个字符的字节序列中。而有些比较旧的可变长度字符编码(如Shift JIS)没有这个特质,故它们的字符串搜索算法变得相当复杂。 UTF-8 字符串可以由一个简单的算法可靠地识别出来。由于 UTF-8 字节序列的设计,如果一个疑似为字符串的序列被验证为 UTF-8 编码,那么我们可以有把握地说它是 UTF-8 字符串。一个字符串在任何其它编码中表现为合法的 UTF-8 的可能性很低,可能性随着字符串长度的增长而减小。 举例说明,字符值 C0、C1、F5 至 FF 从来没有出现。为了更好的可靠性,可以使用正则表达式来统计非法过长和替代值(可以查看W3 FAQ: Multilingual Forms上的验证 UTF-8 字符串的正则表达式)。 UTF-8 编码可以通过屏蔽位 和 移位操作快速读写:屏蔽位是指将字节的高位置零,以便获取低位的值;移位操作是指将字节的低位移动到高位,以便获取高位的值。这样,可以快速读取和写入 UTF-8 编码的字符。 UTF-8 编码的缺点 UTF-8 编码不利于使用正则表达式进行读音检索 正则表达式可以进行很多高级的英文模糊检索。比如,[a-h] 表示 a 到 h 间的所有字母。 同样 GBK 编码的中文也可以这样利用正则表达式,比如在只知道一个字的读音而不知道怎么写的情况下,也可用正则表达式检索,因为 GBK 编码是按读音排序的。虽然正则表达式检索并未考虑中文的多音字,但是由于中文的多音字数量不多,不少多音字还是同音不同调类型的多音字,所以大多数情况下正则表达式检索是还可以接受的。 但是 Unicode 汉字不是按读音排序的,它是按部首排序,所以不利于用正则表达式进行读音检索。在只知道一个字的部首而不知道如何发音的情况下,UTF-8 可用正则表达式检索而 GBK 不行。 UTF-8 的 ASCII 字符只占用一个字节,比较节省空间,但是更多字符的 UTF-8 编码占用的空间就要多出1/2,特别是中文、日文和韩文(CJK)这样的方块文字,它们大多需要三个字节。 无法根据 Unicode 字符数判断出 UTF-8 文本占用的字节数。因为 UTF-8 是一种可变长度字符编码。 参考资料 UTF-8 - 维基百科,自由的百科全书 (wikipedia.org) Unicode/UTF-8-character table (utf8-chartable.de)
2024-01-19
283
0
0
网络聚合
2024-01-19
Fabric2.x中Raft共识算法核心数据结构
一、共识算法可插拔的代码体现Chain接口 Hyperledger Fabric的共识算法是可插拔的,在代码上体现为Chain接口,所有不同的共识算法均可根据Chain接口进行具体实现,目前fabric支持solo、kafka、raft、sbft等共识算法。Chain接口的代码在fabric/orderer/consensus/consensus.go中 // Chain定义了为ordering注入messages的方式,这种设计允许如下两种流程: // 1.消息被排序为流,流被切割成块,块被提交(solo,kafka) // 2.消息被切成块,块被排序,然后块被提交(sbft) type Chain interface { // 排序函数,env是交易信息,configSeq是自增序号,每次有配置更新时都会+1,以确保交易是在最新的config下进行 Order(env *cb.Envelope, configSeq uint64) error // 处理配置交易,提交后更新configSeq Configure(config *cb.Envelope, configSeq uint64) error // 超过max in-flight限制的消息会被阻塞,等待in-flight先执行完 WaitReady() error // 当发生error时,会返回一个空channel Errored() <-chan struct{} // 分配与Chain保持最新数据的资源 Start() // 刷新分配给Chain的资源 Halt() } 二、Fabric的raft共识算法Chain结构体 Hyperledger Fabric对Raft算法的核心实现代码都是放在fabric/orderer/consensus/etcdraft包下的,这里主要包含几个核心的数据结构,即Chain结构体和node结构体。 Chain结构体实现了Chain接口,代码位于orderer/consensus/etcdraft/chain.go,它里面主要定义了一些通道(channel)用于节点间的通信,以便根据通信消息做相应的操作。 // Chain implements consensus.Chain interface. type Chain struct { configurator Configurator rpc RPC // 节点与外部节点进行通信的对象,RPC 是一个接口,包含两个方法SendConsensus 和 SendSubmit。前面这种用于节点间 raft 信息的通讯,后者用于转发交易请求给 leader 节点。 raftID uint64 channelID string lastKnownLeader uint64 ActiveNodes atomic.Value submitC chan *submit // 接收 Orderer 客户端提交的共识请求消息的通道 applyC chan apply // 接收 raft 节点间应用消息的通道 observeC chan<- raft.SoftState haltC chan struct{} doneC chan struct{} startC chan struct{} snapC chan *raftpb.Snapshot //接收 raft 节点快照数据的通道 gcC chan *gc … Node *node // fabric封装了底层 etcdraft 库的节点实例 … } 三、Fabric的raft共识算法node结构体 node结构体封装了底层ercdraft库的节点实例,代码位于orderer/consensus/etcdraft/node.go,主要用于将Fabric自己实现的Raft上层应用和etcd的底层Raft实现连接起来,可以说node结构体是它们之间通信的桥梁,正是它的存在屏蔽了Raft实现的细节。 type node struct { chainID string logger *flogging.FabricLogger metrics *Metrics unreachableLock sync.RWMutex unreachable map[uint64]struct{} tracker *Tracker storage *RaftStorage config *raft.Config rpc RPC chain *Chain // fabric自己定义的Chain结构体,具体在orderer/consensus/etcdraft/chain.go中 tickInterval time.Duration clock clock.Clock metadata *etcdraft.BlockMetadata subscriberC chan chan uint64 raft.Node // raft底层的Node接口 } 四、小结 Fabric的raft共识算法通过Chain结构体实现可插拔的共识算法Chain接口;又通过node结构体,实现对etcdraft底层算法实现细节的封装。Chain结构体和node结构体是承上启下的一层,fabirc的raft共识算法启动、交易处理流程均围绕上述两个结构体进行。下一篇博客将围绕fabric的raft共识算法启动流程进行分析。
2024-01-19
198
0
0
网络聚合
2024-01-19
我来泼盆冷水:正面迎击AI的时代千万别被ChatGPT割了韭菜
前言 ChatGPT从出来的时候我就一直密切关注,为此还加了不少群,用了不少套壳的程序,公司还开了专门的培训会,技术团队还为此搭建了接入ChatGPT的服务,帮助全公司的产品、商务、测试、运维、研发一起对ChatGPT做直观了解以及认知。 时隔数月,我才又重新写一篇文章,借此表达对ChatGPT的看法,帮助大家减少一些浮躁的心情。 因为之前我有写过一篇关于副业防骗的文章:https://www.cnblogs.com/fulongyuanjushi/p/16843513.html 为此我还受到不少攻击,如果是经常关注我的人应该知道,我不是经常发文的,这个通过我发文的时间就可以看出来,主要还是以技术为主,这次蹭热点发文,主要是发现加了我好友的不少同行因为ChatGPT影响了学习的态度,所以我专门从另一个角度来写关于ChatGPT文章,希望给大家一些我觉得正确的认识。 AI诈骗 为什么一上来就说这个呢,因为我明显发现骗子们开始按捺不住了,混在人群中放冷箭,但大家依然徜徉在ChatGPT的海洋中不可自拔,殊不知黑手已经在身边。 因为今年这个东西才真的火起来,所以骗子们一开始也没有研究出太多套路,我借用河南省反诈骗中心揭露的骗局和危害给大家一些清醒的认识。 1)、骗取使用费 国内涌现一批名字中包含"ChatGPT"的微信公众号、小程序产品,这些产品使用ChatGPT的官方图标当头像,通过免费试用吸引用户,一旦免费次数用尽就开始收取费用; 河南省反诈骗中心温馨提醒:ChatGPT目前还没有国内版本,那些所谓可以代问、代注册的软件或者小程序,顶着ChatGPT官方图标当头像,打着先免费试用的幌子,他们有的是收取高额费用给你牵线搭桥的违法中介;有的甚至是在你充值或者购买会员后就会突然跑路的骗子,只不过是蹭着ChatGPT的热点,在盯着你的钱袋子。 2)、钓鱼邮件 不法分子在境外会使用ChatGPT生成针对特定人或者组织的“鱼叉式”网络钓鱼邮件,致使邮件接收方更容易上当受骗,接收者一旦点击该邮件,系统就会被恶意代码感染中毒。 不法分子锁定目标人群或者组织趁虚而入,生成鱼叉式网络钓鱼邮件,一旦点开这些邮件,系统就会被恶意代码感染中毒,甚至掉入电信网络诈骗的陷阱。 3)、泄露个人信息 不法分子利用用户注册使用仿冒的ChatGPT,从而获取用户姓名、电话、银行卡号、身份证号码等个人信息,为实施电信网络诈骗设好圈套。 目前,ChatGPT的开发者、人工智能研究机构OpenAl并没有在中国大陆提供有关ChatGPT的服务,也没有相应的公司运营。请务必提高警惕,分辨网上信息真伪,切勿轻易泄露个人信息。 这一点希望大家注意,这是一种电信+AI联合诈骗的手段,未来诈骗集团可能会强强联合,信息安全将成为一个沉重的话题。 警方提醒 同样的,还存在一些有组织的团伙进行诈骗,手法老套但却利用了从众的心理。这里借用南宁市公安局福建园派出所社区民警周鼎晨揭露的骗局给大家了解下: 1)、有一些山寨的小程序或者是APP,然后就是把这个头像换成是ChatGPT一样的,但是经过我们的认证,就是跟真正的这个ChatGPT, 回答的答案是完全不一样的,另一方面的话,它可能是首次使用免费,后面也会继续收取高额的费用。 2)、有不法分子他会利用这个ChatGPT来生成与提问者相关的一些链接,然后当你点进去的时候, 链接它就会进行一个比较特定的攻击,会恶意获取你电脑里面的内容。 3)、不法分子还可能会利用智能聊天软件拟人的聊天对话能力,冒充真实的人或者组织骗取他人信息等。 我认为第3点会是未来主要的诈骗手段,同时会衍生出各种花样和套路。 如果ChatGPT是李逵,那么国内有不少是李鬼,注意别被割了韭菜而不自知。 一些打着套壳程序的产品很可能是披着羊皮的狼,目前ChatGPT刚火起来,你们需要一定的时间来认知和吸收,对于骗子也是如此,他们目前也在研究骗法,如何发展出新时代的套路是他们当前研究的重要课题。 我大胆预测未来两年,会出现接近于ChatGPT的各种仿冒伪劣产品,内核其实是有组织、有资源、有规模的AFI,也就是人工诈骗智能,其中最突出的肯定是FraudGPT。 法律红线 接下来,我想对很多正在依靠ChatGPT谋利的同行们做一个提醒,那就是千万不要踩法律红线,否则得不偿失。 有别于一些诈骗团伙,一部分人其实还是借着真东西来谋利的,这部分人的做法我认为初衷是好的,就是简单的交易变现,但要特别注意遵守国内的法律,尤其是互联网安全相关的法律,如果违反的话其实判的还是挺重的,你们可以去了解下。 我这里主要提醒两点: 1)、商标这块,一定注意不要违反商标法,这块很多人是没当回事的,比如ChatGPT的商标,它即使没有注册本身也受法律保护,因为它已经驰名了,你们可以去查找商标法的资料; 2)、转接服务,这块就是当下最多人干的事情,转接国外的API接口从注册到落地阶段都有可能是违法的,最严重的就是很可能触及国内的网络安全和数据安全管理法,下面我摘录的一段法治网的文章中标注的内容: 转接服务者首先绕开了国家公用电信网提供的国际出入口信道,为用户提供接入ChatGPT的服务,提供能使他人访问国内IP不能访问的境外网站的“转接”服务,该行为不仅违反了电信条例、互联网信息服务管理办法,其行为极有可能触犯刑法第285条规定的提供侵入、非法控制计算机信息系统程序、工具罪,处最高七年有期徒刑并处罚金。 3)、代注册和卖账号,其实做这个买卖的人本身就知道是违法的,纯粹就是铤而走险。下面是我摘录的另一段法治网的文章中标注的内容: 中国大陆地区是没有办法注册chatgpt账号的,只能破碎虚空进行注册,这涉嫌违反了计算机信息网络国际联网管理暂行规定。因此,这类代注册行为涉嫌非法经营,情节严重的可能会构成犯罪。而且ChatGPT无法使用中国手机号注册,消费者不能用自己现有的手机号码,那么代注册行为可能涉及到一些能够批量制造、联网、验证手机号码的“技术手段”,涉嫌违反《中华人民共和国反电信网络诈骗法》。 所以,希望同行们引以为鉴,不要给自己的职业生涯留下隐患,你目前没有事,可能只是因为没有人举报你,一旦惹上麻烦就是一辈子的事。 我的观点 ChatGPT这么火,未来的路在哪里?我不是专家,只能站在一个工作多年但其实挺普通的软件工程师的角度来看待这个问题。 1)、加强对国内ChatGPT相关热点的关注 我这里稍微说下自己浅显的看法,目前ChatGPT很火,但国内和国外分别有不同的态势。 国外是爆炸式冲击,甚至给某些国家带来了一些恐慌,意大利封禁就是一个开端,究其原因还是为了本国利益着想,但可以侧面说明国外在自然语言处理方面的研究确实走在了前面,至少已经拿出了可以让普通人也感受到变革的产品,这给我们敲响了警钟。 而国内对于ChatGPT的态度显而易见,是肯定会扶持本地厂商的,如果明白了这一点,我建议大家要开始多关注百度、阿里、腾讯等等这些大厂在ChatGPT这方面的动态,早点参与内测及研究他们的产品。 这就类似于很多年前你用Google搜索,眨眼几年之后你就发现大部分人依赖百度了,然后就是微信、支付宝,以及之后的美团、饿了么、滴滴等等如雨后春笋一般出现,哪怕是比较近的抖音、小红书、视频号这些也是谁先下场谁先获利。 你现在早点开始接触国内厂商的AI产品,其实就好比早些年你提前比别人会使用微信支付宝一样的情境,走在前面你才能早点发现机会。 2)、短期内对编程领域的冲击不大 一个原因是上一点讲过的国情原因上了一把锁,第二个原因是国内厂商在这块的发展方向着重于非编程领域,主要还是提供知识库、强化内容检索、AI作画、文学创作、智能媒体助手等等。 这些方向广泛冲击的还是一些具备重复性工作的领域,另外我有使用过一下百度的AI产品,比如AI作画,其实没有想象中的厉害,至少我觉得远不如原创画师的作品,因为完全没有灵魂。 另外就是像法律、文学、医疗等行业,可能会带来变革,但现阶段程度较小,因为这些领域不仅仅是流程化,还存在变数,需要大量人工干预; 对于编程领域而言,我暂时没看到AI从项目产品设计到编码到生产落地的完整流程化的影子,只能作为知识库和检索工具辅助研发人员的工作,这本身反而是一件好事,如何利用它让自己变得更强大才是接下来你应该关心的课题。 3)、大部分人尚不关心 普通老百姓对于我们热烈讨论的这些事情的认知其实还是一片空白,说简单点就是大部分人其实是不知道的,只有特定的圈子比如互联网行业的人了解的更多一点,其次就是网络诈骗集团了。 当这些AI产品真正开始影响到方方面面生活的时候,我敢肯定会有不少人出来反对和提出异议的,但这不会阻碍发展,此乃大势所趋。 可是会给国内厂商带来思考,就是如何降低这些社会影响,比如百度现在就开始调整思路了,他所做的AI作画即使还没有达到预期水准就已经被艺术家们联合抵制上了热搜,这就是苗头,所以接下来他们的产品都尽量打上了为民服务的标签,以辅助和提高各行业效率为主,绝不越俎代庖。 这就是害怕未来真正智能化变革给他造成反噬,最终淹没在广大人民群众的海洋里,你人工智能发展再好没用啊,你害我没饭吃,我只能起来反抗啊(笑)。 所以说,我认为在国内,人工智能的发展一定是润物细无声的,这也是国情。 4)、真正的变革需要一个契机 我认为真正的变革还没开始,因为缺少一个契机,ChatGPT虽然大火,但存在一个问题,就是大家都觉得他会改变整个生态和世界,可是他怎么改变,通过什么手段改变,还没有一个具象化的显现。 我举个例子,很多人说ChatGPT可以取代程序员了,我觉得如果排除各种可能存在的干扰,这真的是一种可以预见的现实,不仅仅存在于理论,而是他确实可以取代程序员。 但问题就在这里,目前还没有一个可行性的方案来呈现ChatGPT帮助企业完整的将一个项目从产品设计、编码、测试、上线发布、最终落地,通通都实现,最终形成流水线,我通常认为这样才是真正取代了程序员,否则依然是懂行的程序员的辅助工具,我以为你很强,原来你只是我打败敌人的黄金圣衣。 那到底ChatGPT能不能做到呢?我认为未来一定是可以的,甚至我觉得现在可能已经可以了,只是还没有一个人真的成为ChatGPT工程师,让ChatGPT将这个完整流程具象化。 当真有这么一个人或某个产品问世的时候,我觉得这个契机就来了,变革也真正开始了。 说的更通俗一点,单纯就以ChatGPT而言,如果有词圣问世了,那么真正的变革就来了,所以我们要关注这个契机的出现。 总结 以上是我当下的考量,我们面临变革的时候不要恐慌,也别急着到处去迎合ChatGPT,到处去找账号,甚至到处去花钱,一旦用不了这东西我就感觉天要塌了之类的。 还是要静极思动,先冷静的了解他,尝试使用他,多看看关于他的新闻,关注国内相关的政策,这些才是最终影响你生活的,毕竟你不是老外,如果你确定自己不会移民的话,那你还是把关注点多放在国内的产品上吧。 ChatGPT未来在国内的发展形势我大体认为不会影响编程技术人员,除非你自己放弃接触和了解,我想这样的人几乎没有吧,因为卷。 希望我这篇从另一个角度帮你认知ChatGPT的文章可以带给你一些思考,让你沉心静气,重新有条不紊地开始自己的工作学习。 敲键盘不易,转载请注明来源及作者,谢谢。
2024-01-19
165
0
0
网络聚合
2024-01-19
我做的百度飞桨PaddleOCR .NET调用库
我做的百度飞桨PaddleOCR .NET调用库 .NET Conf 2021中国我做了一次《.NET玩转计算机视觉OpenCV》的分享,其中提到了一个效果特别好的OCR识别引擎——百度飞桨PaddleOCR,可离线部署,后来我逐步把它封装了一下,代码全部开源(可点击查看原文跳转到Github):https://github.com/sdcb/paddlesharp,可以直接安装NuGet包使用,支持.NET Framework/.NET Core、支持Linux、支持GPU调用,支持14种语言模型的自动下载: 这里有使用方法和示例代码: 运行效果: 促使我给PaddleOCR做.NET封装的原因,是PaddleOCR令人惊讶的识别精度。我之前用过TesseractOCR,看到有人说是“世界上唯一”免费且好用的OCR引擎,但我发现它不好用,它的精度一直介于“可用”与“不可用”之间,处于勉强可用的状态——即使是我使用了Best的TesseractOCR模型也是如此(而且性能也不快)。 比如你看这个例子,用TesseractOCR跑的,耗时48秒,英语和数字识别还是可圈可点的,但中文……里面有空格不说,而且大量识别错误,非常不通顺,诠释了什么叫“介于可用与不可用之间”。 但PaddleOCR不同,去看看官网示例,全部都是效果爆炸的感觉,最令人我惊讶的是它的精度,尤其是文字在旋转的状态下的精度: 上文中同样的例子,在PaddleOCR中的执行结果: 可见精度好得多,耗时也只要9秒。 我是怎么封装的? 我发现市场上有人封装过,但他们都是基于C++ API,然后自己写了一层C++,然后包装成C API进行封装。这样的好处是暴露出来的C API比较简单,调用起来很方便,但缺点是不方便扩展,使用起来笨重得多,跨平台也很难。 基于C API使用起来不方便,但上层不是有咱们.NET/C#嘛,我相信再不方便的API,只要用上了C#/.NET去封装它,都能做得很方便地去调用,于是我做出了这样的一个架构(这个架构本质是模仿了OpenCvSharp4) 最底层是C API的NuGet封装包,这个用PInvoke来封装C API,它的NuGet包名字是:Sdcb.PaddleInference 与底层配套的包叫native binding包,我提供了两个,一个是基于CPU的Sdcb.PaddleInference.runtime.win64.mkl,一个是基于GPU的Sdcb.PaddleInference.runtime.win64.cuda11_cudnn8_tr7。 值得注意的是,native binding包与低层包没有任何依赖关系。 再往上层是应用包,应用包依赖于低层的推理库包Sdcb.PaddleInference,文字识别OCR就是Paddle推理库Inference的一个应用,因此提供了一个Sdcb.PaddleOCR,封装了PaddleDetector、PaddleClassificator、PaddleRecognizor以及PaddleOcrAll用来做串联 最往上层走就是扩展包,我提供了一个用于帮助用户自动下载OCR模型的Sdcb.PaddleOCR.KnownModels,注意这个扩展包与上述包没有任何引用关系。 有了这些包,我做出来的这个封装就比其它封装更有竞争力,比如能支持GPU或者不支持GPU,比如支持Linux平台,比如更换不同的模型,比如支持设置不同的参数——用户甚至可以不基于我提供的应用包,自己去使用自己的逻辑封装PaddleOCR或者其它应用。 这几天我参与了百度飞桨的一个车牌号识别的3天训练营,我发现可以从百度的BML平台下载模型之后,只需简短的改动就能将我的PaddleSharp改成支持车牌号识别: 我发现通过这些绵薄之力,能为.NET社区带来一些方便。比如有客户已经用上我的包,做了一个Word插件,是付费产品,效果很不错: 这些内容都是开源的:https://github.com/sdcb/paddlesharp,喜欢的朋友请给我一个star哦。 另外我还创建了一个QQ群,C#/.NET计算机视觉技术交流,里面也包括有关这个PaddleSharp的使用、部署答疑和技术讨论,欢迎有兴趣的同行一起参与!
2024-01-19
197
0
0
网络聚合
2024-01-19
__int128:懒人的福音
前言 这是给c++党的一点福利吧!(python根本不用写高精度) 对于一个懒懒的,不想写高精的人(就是我),每次都会遭遇到答案爆$long$ $long$的危险 比如说这道题: 题目传送门 最后的$23-25$的两个点,$long$ $long$甚至$unsigned$ $long$ $long$都无法满足,难道真的要手打高精度了吗? 不,我们有$\_$$\_$$int$$128$! 那么这到底是什么 可以吃吗 ? 关于$\_$$\_$$int$$128$ 先来看看一些常见的整数变量能存的范围与占用的字节: 类型名称 占用字节 存储范围 $int$ $4$ $-2^{31}$ ~ $2^{31}-1$ $long$ $long$ $8$ $-2^{63}$ ~ $2^{63}-1$ $unsigned$ $long$ $long$ $8$ $0$ ~ $2^{64}-1$ 再来看看$\_$$\_$$int$$128$ 存储范围为$-2^{127}$ ~ $2^{127}-1$,但是占用了$128$字节 虽然内存占的多,但存储范围依然多 那么如何使用? 输入 其实就是把快读输入改了一改 __int128 read() { __int128 x=0; int f=1; char ch=getchar(); while(ch<'0' || ch>'9') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0' && ch<='9') x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); return x*f; } __int128 n=read(); 输出 其实也是把快输改了一改 void write(__int128 x) { if(x<0) x=-x,putchar('-'); if(x>9) write(x/10); putchar(x%10+'0'); } __int128 n; n=... write(n); 写在最后 $\_$$\_$$int$$128$是好,但是用的时候一定一定要注意空间限制! 当然,$\_$$\_$$int$$128$并不能完全取代高精,所以如果$\_$$\_$$int$$128$过不掉时,还是老老实实打高精吧!
2024-01-19
302
0
0
网络聚合
2024-01-19
为K8S集群准备Ceph存储
随着K8S存储接口逐渐成熟并顺势推出CSI接口规范后,原来“in-tree”(树内)模式的很多存储插件也逐步迁移到了“out-of-tree”(树外)模式的CSI插件上,甚至有些原来支持的存储卷类型都被直接移除了(例如在K8S v1.26上直接移除了 glusterfs 卷类型),查阅了一下K8S官方最新版本的存储相关(Storage/Volumes)的说明,综合最新存储支持情况,我们选择Ceph作为K8S集群的存储提供者。 首先,进入Ceph官网文档查看其安装部署方法,主要看下是否有基于K8S的安装方法,最后在官网“Installing ceph>Recommended methods”(推荐的Ceph安装方法)果然发现了基于K8S的安装方法: Ceph官方推荐在K8S集群上使用Rook来部署和管理Ceph集群! 我们进入Rook官网看看,从官网可以看出Rook是为K8S量身定制的,那就它了: Ceph是一个在大规模生产集群中提供文件、块和对象存储的分布式存储系统,而Rook是一个专门支持Ceph与云原生环境集成的开源云原生存储协调器。Rook利用K8S的Operator机制推出了自己的Rook operator,实现自动化的Ceph部署和管理。Rook作为云原生存储平台已经从CNCF顺利毕业! 以上是对Rook简要说明,接下来借助Rook在K8S集群上部署和管理Ceph。 Rook支持K8S v1.19+的版本,CPU架构为amd64、x86_64或arm64均可,除此之外部署Ceph存储集群还必须至少满足以下先决条件之一: 每个节点至少有一块裸设备(Raw devices,未分区未进行文件系统格式化) 裸分区(Raw partitions,未进行文件系统格式化) LVM逻辑卷(LVM Logical Volumes,未进行文件系统格式化) block模式下存储类(storage class)中可用的持久卷(PV) 这里我们选择为K8S集群的每个工作节点添加一块额外的未格式化磁盘(裸设备),步骤见以下截图: 将新增的磁盘设置成独立模式(模拟公有云厂商提供的独立磁盘),然后启动K8S集群虚拟机,在工作节点上使用以下命令检查一下磁盘条件是否符合Ceph部署要求: [root@node1 ~]# lsblk -f NAME FSTYPE LABEL UUID MOUNTPOINT sdb sr0 iso9660 CentOS 7 x86_64 2020-11-04-11-36-43-00 sda ├─sda2 LVM2_member 45inUD-qJ4O-Fq9E-L6KD-8eJV-mofD-BuJDq6 │ └─centos_node1-root xfs 704f37f0-ae59-4995-80ec-58cba66e023b / └─sda1 xfs 67243cc8-c3fb-490f-b0da-cc439371d5e1 /boot 上述命令输出中 sdb 磁盘就是我们为工作节点新添加的裸设备(它的FSTYPE为空),我们可以把它分配给Ceph使用。 需要在K8S集群中启用Rook准入控制器,用于验证使用自定义资源(CR)正确地配置了Rook。该准入控制器在身份认证和授权之后并在持久化对象之前,拦截发往K8S API Server的请求以进行验证。我们在安装Rook之前,使用以下命令在K8S集群中安装Rook准备入控制器: #在master1节点直接应用在线yaml文件 kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.7.1/cert-manager.yaml #在master1将cert-manager.yaml下载到本地的方式(推荐) kubectl apply -f /etc/kubernetes/rook/cert-manager.yaml ...... service/cert-manager created service/cert-manager-webhook created deployment.apps/cert-manager-cainjector created deployment.apps/cert-manager created deployment.apps/cert-manager-webhook created mutatingwebhookconfiguration.admissionregistration.k8s.io/cert-manager-webhook created validatingwebhookconfiguration.admissionregistration.k8s.io/cert-manager-webhook created 另外,Ceph OSD在以下情况下需要依赖于LVM(逻辑卷,OSD是Ceph用于在各个存储节点实现存储功能的组件): 在裸设备或裸分区上创建OSD 如果启用了加密(在集群CR中设置了encryptedDevice: "true") 指定了元数据设备(metadata device) 在以下情况下OSD不需要LVM 在使用 storageClassDeviceSets 的PVC上创建OSD 目前大多数Linux发生版的LVM由lvm2包提供,在K8S集群中运行Ceph OSD的所有存储节点上都需要有这个包。虽然没有这个包Rook也能够成功创建Ceph OSD,但是当相应的节点(node)重启之后,其上运行的OSD pod将会启动失败。所以需要确保作为存储节点的操作系统上安装了LVM(从上面磁盘条件查验的结果中看到我们是有LVM卷的),CentOS可以使用以下命令安装LVM: sudo yum install -y lvm2 Ceph需要一个带有RBD模块的Linux内核。大多数Linux发行版都有这个模块,但不是所有,你可以在K8S集群的存储节点上运行 lsmod|grep rbd 命令检测一下,如果该命令返回空,那说明当前系统内核没有加载RBD模块,可以使用以下命令尝试加载RBD模块: #将RBD模块加载命令放入开机加载项里 cat > /etc/sysconfig/modules/rbd.modules << EOF #!/bin/bash modprobe rbd EOF #为上述为脚本添加执行权限 chmod +x /etc/sysconfig/modules/rbd.modules #执行上述脚本(如果返回'not found',你可能需要安装一个新的内核、或重新编译一个带有RBD模块的内核、或换一个带有RBD的Linux 发行版) /bin/bash /etc/sysconfig/modules/rbd.modules #查看RBD模块是否加载成功 lsmod|grep rbd Rook默认的RBD配置只指定了分层特性,以便与较旧的内核广泛兼容。如果K8S节点运行在5.4+的系统内核上,则可以启用其他功能特性。例如特别有用的 fast-diff 和 object-map 特性,主要的功能特性如下(在进行块存储的StorageClass定义时指定): imageFeatures: layering,fast-diff,object-map,deep-flatten,exclusive-lock 如果你将来会从Ceph共享文件系统(CephFS)创建卷(volume),那么需要使用4.17+的系统内核,PVC请求的存储配额只在高于该版本的内核上生效。 ------------------------------- 以上为使用Rook在K8S集群部署Ceph存储的前提条件 ------------------------------- 接下来正式使用Rook在K8S集群部署Ceph存储集群! 首先在K8S所有集群节点上安装Git客户端(用于拉取Rook部署组件清单): #安装Git yum install -y git #查看Git版本 git --version git version 1.8.3.1 使用Rook官方提供的示例部署组件清单(manifests)部署一个简单的Ceph集群(测试环境够用了): #使用git将部署组件清单示例下载到本地(慢或无法接通的话自己想法办FQ) git clone --single-branch --branch v1.10.11 https://github.com/rook/rook.git #进入到本地部署组件清单示例目录 cd rook/deploy/examples #执行以下命令将Rook和Ceph相关CRD资源和通用资源创建到K8S集群(其中psp.yaml是K8S集群受Pod安全策略保护的情况下的可选资原文件) kubectl create -f crds.yaml -f common.yaml -f psp.yaml 接下来部署Rook Operator组件,该组件为Rook与Kubernetes交互的组件,整个集群只需要一个副本,特别注意 Rook Operator 的配置在Ceph集群安装后不能修改,否则Rook会删除Ceph集群并重建,所以部署之前一定要做好规划,修改好operator.yaml的相关配置: 修改 rook/deploy/examples/operator.yaml文件中的以下内容: #修改镜像地址为华中科技大学和阿里云的(可以使用docker pull <url>验证一下,原来的地址很难下载) ROOK_CSI_CEPH_IMAGE: "quay.mirrors.ustc.edu.cn/cephcsi/cephcsi:v3.7.2" ROOK_CSI_REGISTRAR_IMAGE: "registry.aliyuncs.com/google_containers/csi-node-driver-registrar:v2.7.0" ROOK_CSI_RESIZER_IMAGE: "registry.aliyuncs.com/google_containers/csi-resizer:v1.7.0" ROOK_CSI_PROVISIONER_IMAGE: "registry.aliyuncs.com/google_containers/csi-provisioner:v3.4.0" ROOK_CSI_SNAPSHOTTER_IMAGE: "registry.aliyuncs.com/google_containers/csi-snapshotter:v6.2.1" ROOK_CSI_ATTACHER_IMAGE: "registry.aliyuncs.com/google_containers/csi-attacher:v4.1.0" #生产环境一般都会将裸设备自动发现开关设为true(方便后面追加设备) ROOK_ENABLE_DISCOVERY_DAEMON: "true" #打开CephCSI 提供者的节点(node)亲和性(去掉前面的注释即可,会同时作用于CephFS和RBD提供者,如果要分开这两者的调度,可以继续打开后面专用的节点亲和性) CSI_PROVISIONER_NODE_AFFINITY: "role=storage-node; storage=rook-ceph" #如果CephFS和RBD提供者的调度亲各性要分开,则在上面的基础上继打开它们专用的开关(去除下面两行前端的#即可) CSI_RBD_PROVISIONER_NODE_AFFINITY: "role=rbd-node" CSI_CEPHFS_PROVISIONER_NODE_AFFINITY: "role=cephfs-node" #打开CephCSI 插件的节点(node)亲和性(去掉前面的注释即可,会同时作用于CephFS和RBD插件,如果要分开这两者的调度,可以继续打开后面专用的节点亲和性) CSI_PLUGIN_NODE_AFFINITY: "role=storage-node; storage=rook-ceph" #如果CephFS和RBD提供者的调度亲各性要分开,则在上面的基础上继打开它们专用的开关(去除下面两行前端的#即可) CSI_RBD_PLUGIN_NODE_AFFINITY: "role=rbd-node" CSI_CEPHFS_PLUGIN_NODE_AFFINITY: "role=cephfs-node" #rook-ceph-operator的Deployment中的容器镜像地址rook/ceph:v1.10.11 可以不用换,下载还是很快的! #生产环境一般还会打开裸设备自动发现守护进程(方便后期增加设备) ROOK_ENABLE_DISCOVERY_DAEMON: "true" #同时开打发现代理的节点亲和性环境变量 - name: DISCOVER_AGENT_NODE_AFFINITY value: "role=storage-node; storage=rook-ceph" 确认修改完成后,在master1节点上执行以下命令进行Rook Ceph Operator的部署: #执行以下命令在K8S集群中部署Rook Ceph Operator(镜像拉取可能需要一定时间,耐心等待,可用后一条命令监控相关Pod部署情况) kubectl create -f operator.yaml #使用以下命令监控Rook Ceph Operator相关Pod的部署情况(rook-ceph为默认Rook Ceph Operator部署命名空间) watch kubectl get pods -n rook-ceph 确保rook-ceph-operator相关Pod都运行正常的情况下,修改 rook/deploy/examples/cluster.yaml文件中的以下内容: # enable prometheus alerting for cluster(为集群打开prometheus告警) monitoring: # requires Prometheus to be pre-installed enabled: true #打开节点亲和性调度和污点容忍 To control where various services will be scheduled by kubernetes, use the placement configuration sections below. The example under 'all' would have all services scheduled on kubernetes nodes labeled with 'role=storage-node' and tolerate taints with a key of 'storage-node'. placement: all: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: role operator: In values: - storage-node # podAffinity: # podAntiAffinity: # topologySpreadConstraints: # tolerations: # - key: storage-node # operator: Exists #将存储设置为我们三个工作节点新加的sdb裸盘 storage: # cluster level storage configuration and selection useAllNodes: false useAllDevices: false #deviceFilter: config: # crushRoot: "custom-root" # specify a non-default root label for the CRUSH map # metadataDevice: "md0" # specify a non-rotational storage so ceph-volume will use it as block db device of bluestore. # databaseSizeMB: "1024" # uncomment if the disks are smaller than 100 GB # journalSizeMB: "1024" # uncomment if the disks are 20 GB or smaller # osdsPerDevice: "1" # this value can be overridden at the node or device level # encryptedDevice: "true" # the default value for this option is "false" Individual nodes and their config can be specified as well, but 'useAllNodes' above must be set to false. Then, only the named nodes below will be used as storage resources. Each node's 'name' field should match their 'kubernetes.io/hostname' label. nodes: - name: "node1" devices: # specific devices to use for storage can be specified for each node - name: "sdb" - name: "node2" devices: # specific devices to use for storage can be specified for each node - name: "sdb" - name: "node3" devices: # specific devices to use for storage can be specified for each node - name: "sdb" # - name: "nvme01" # multiple osds can be created on high performance devices # config: # osdsPerDevice: "5" # - name: "/dev/disk/by-id/ata-ST4000DM004-XXXX" # devices can be specified using full udev paths # config: # configuration can be specified at the node level which overrides the cluster level config # - name: "172.17.4.301" # deviceFilter: "^sd." # when onlyApplyOSDPlacement is false, will merge both placement.All() and placement.osd onlyApplyOSDPlacement: false 修改完后,根据我们在operator.yaml和cluster.yaml上的节点标签亲和性设置,为三个工作节点打上对应的标签: kubectl label nodes node1 node2 node3 role=storage-node kubectl label nodes node1 node2 node3 storage=rook-ceph #确保工作节点打上对应标签,并且cluster文件修改好后,就可以使用cluster.yaml部署Ceph存储集群了(部署需要一定的时间,可用后一条命令监控) kubectl create -f cluster.yaml #使用以下命令监控Ceph Cluster相关Pod的部署情况(rook-ceph为默认部署命名空间) watch kubectl get pods -n rook-ceph Rook Ceph集群搭建好并成功运行后,我们就可以开始基于Rook Ceph集群创建可供K8S内的应用消费的块存储(Block Devices)、共享文件系统(Shared Filesystem)和对象存储(Object Storage)了。这些类型的存储资源在Rook Ceph中分别被定义为CephBlockPool、CephFilesystem 和 CephObjectStore。 首先,来创建Ceph块存储(Block Devices):Ceph能为Pod提供裸块设备存储卷(volumn)。只需要将Rook官方提供的示例部署组件清单项目中 /rook/deploy/examples/csi/rbd 目录下的storageclass.yaml文件或storageclass-ec.yaml文件应用到K8S里即可(这两个文件的主要区别是保障数据高可用的方式不一样,前者是通过多副本的方式,后者是通过擦除算法的方式保障节点故障时数据不丢失,后者不需要三倍的存储空间,但需要更多的CPU资源进行计算),它们都需要三个节点进行部署。我们这里选用storageclass.yaml文件,因为我们的内核版本已升级至5.4+,所以可以开启前面说的特别有用的 fast-diff 和 object-map 特性,打开storageclass.yaml文件修改以下内容(红色为新开启的功能特性): --- apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: rook-ceph-block # 下面注释将该存储类型设置为默认存储类型 annotations: storageclass.beta.kubernetes.io/is-default-class: "true" ...省略... # RBD image features # Available for imageFormat: "2". Older releases of CSI RBD # support only the `layering` feature. The Linux kernel (KRBD) supports the # full complement of features as of 5.4 # `layering` alone corresponds to Ceph's bitfield value of "2" ; # `layering` + `fast-diff` + `object-map` + `deep-flatten` + `exclusive-lock` together # correspond to Ceph's OR'd bitfield value of "63". Here we use # a symbolic, comma-separated format: # For 5.4 or later kernels: #imageFeatures: layering,fast-diff,object-map,deep-flatten,exclusive-lock # For 5.3 or earlier kernels: imageFeatures: layering,fast-diff,object-map,deep-flatten,exclusive-lock ...省略... 修改好后执行以下命令应用一下即可: kubectl create -f rook/deploy/examples/csi/rbd/storageclass.yaml 可以查看一下当前集群中的存储资源: kubectl get sc NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE rook-ceph-block (default) rook-ceph.rbd.csi.ceph.com Delete Immediate true 5m10s 然后,来创建Ceph共享文件系统(Shared Filesystem):Ceph文件系统(CephFS)允许用户将符合posix的共享文件夹“mount”到一个或多个主机中((对于K8S来说就是pod),在K8S中创建CephFS只需要将Rook官方提供的示例部署组件清单项目中 rook/deploy/examples/csi/cephfs 目录下的storageclass.yaml文件或storageclass-ec.yaml文件应用一下即可: kubectl create -f /root/rook/deploy/examples/csi/cephfs/storageclass.yaml 可以查看一下当前集群中的存储资源: kubectl get sc NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE rook-ceph-block (default) rook-ceph.rbd.csi.ceph.com Delete Immediate true 6m15s rook-cephfs rook-ceph.cephfs.csi.ceph.com Delete Immediate true 22s 最后,来创建Ceph对象存储(Object Storage):Ceph对象存储支持使用HTTP的get、put、post以及delete语义操作的称为对象数据块的存储。在K8S中创建Ceph对象存储只需要将Rook官方提供的示例部署组件清单项目中 rook/deploy/examples 目录下的object.yaml文件或object-ec.yaml文件应用一下即可(如果文件内默认的gateway.port:80端口已经被占用的话,需要更换一个未占用的端口,比如我这边80端口已经分配置Ingress Nginx Controller了,将Ceph对象存储API服务的网关端口换成8088): # The gateway service configuration gateway: # A reference to the secret in the rook namespace where the ssl certificate is stored # sslCertificateRef: # A reference to the secret in the rook namespace where the ca bundle is stored # caBundleRef: # The port that RGW pods will listen on (http) port: 8088 # The port that RGW pods will listen on (https). An ssl certificate is required. # securePort: 443 # The number of pods in the rgw deployment instances: 1 修改好后执行以下命令应用一下即可: kubectl create -f rook/deploy/examples/object.yaml 可以查看一下rook-ceph命名空间中的对象存储服务运行状态: kubectl get CephObjectStore -n rook-ceph NAME PHASE my-store Progressing 最后对于K8S v1.20+后的版本提个醒:官方在 K8S 1.20 中基于对性能和统一API Server调用方式的初衷,移除了对 SelfLink 的支持,而 大多数 provisioner 需要 SelfLink 该项功能,否则会出现PVC申请PV时一直处于 pending 状态,所以我们需要修改 API Server 的配置文件,重新启用 SelfLink 功能: # vi /etc/kubernetes/manifests/kube-apiserver.yaml ...省略... spec: containers: - command: - kube-apiserver ...省略... - --feature-gates=RemoveSelfLink=false # 重新加回的参数 ...省略... 然后重新应用一下 kube-apiserver.yaml : kubectl apply -f /etc/kubernetes/manifests/kube-apiserver.yaml 上述问题也可以使用新的不基于 SelfLink 功能的 provisioner 镜像,重新创建 provisioner 容器进行解决,例如下面这个境像: registry.cn-beijing.aliyuncs.com/pylixm/nfs-subdir-external-provisioner:v4.0.0 如果你试了上面的解决法法都不行,这时你要认真检查一下你创建StorageClass资源对象时用的yaml配置文件里的 provisioner 的值是否与创建存储提供者Pod时用的Deployment资源对象文件里env节的 PROVISIONER_NAME 的值相同(不同的话,使用相应StorageClass的PVC也会报相同的错误): apiVersion: apps/v1 kind: Deployment metadata: ...省略... containers: ...省略... env: - name: PROVISIONER_NAME value: local/nfs ...省略... apiVersion: storage.k8s.io/v1 kind: StorageClass ...省略... provisioner: local/nfs #要与Deployment里env里的PROVISIONER_NAME的值对应 allowVolumeExpansion: true 关于 Rook Ceph就介绍到这里,更多相关知识请到Rook官网查看了。
2024-01-19
241
0
0
网络聚合
67
68
69
70
71