开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

视频通话实时换脸:支持训练面部模型 开源日报 No23

发表于 2024-04-27

picture

iperov/DeepFaceLive

Stars: 19.7k License: GPL-3.0

picture

DeepFaceLive 是一个用于 PC 实时流媒体或视频通话的人脸换装工具。

  • 可以使用训练好的人脸模型从网络摄像头或视频中交换面部。
  • 提供多个公共面部模型,包括 Keanu Reeves、Mr. Bean 等。
  • 支持自己训练面部模型以获取更高质量和更好匹配度。
  • 包含 Face Animator 模块,可控制静态面部图片使用视频或摄像头实现动画效果。

seaweedfs/seaweedfs

Stars: 20.0k License: Apache-2.0

picture

SeaweedFS 是一个快速的分布式存储系统,适用于大量文件的 blobs、objects、files 和 data lake。其主要功能和核心优势包括:

  • Blob 存储具有 O(1) 磁盘查找和云层级。
  • Filer 支持 Cloud Drive、跨数据中心主动-主动复制、Kubernetes、POSIX FUSE 挂载点等多种功能。
  • 提供 S3 API 和 S3 Gateway,支持 Hadoop 和 WebDAV。
  • 支持加密和 Erasure Coding 技术。

opencontainers/runc

Stars: 11.1k License: Apache-2.0

runc 是一个根据 OCI 规范在 Linux 上生成和运行容器的 CLI 工具。

  • 根据 OCI 规范生成和运行容器
  • 支持安全审计
  • 仅支持 Linux 平台
  • 使用 Go Modules 进行依赖管理
  • 提供可选的构建标签,用于编译各种功能支持

stefanprodan/podinfo

Stars: 4.8k License: Apache-2.0

picture

podinfo 是一个用 Go 编写的微服务模板,展示了在 Kubernetes 中运行微服务的最佳实践。

  • 健康检查(可读性和活性)
  • 收到中断信号时优雅关闭
    • 用于监视秘密和配置映射的文件观察者
  • 使用 Prometheus 和 Open Telemetry 进行仪表化
  • 使用 zap 进行结构化日志记录
  • 遵循 12 要素应用程序设计原则,使用 viper 库
  • 故障注入(随机错误和延迟)
  • Swagger 文档支持

layerdiffusion/sd-forge-layerdiffuse

Stars: 2.2k License: Apache-2.0

picture

sd-forge-layerdiffuse 是一个通过 Forge 实现的 WebUI 的图层扩散项目。

  • 生成透明图像和图层
  • 支持基本的图像生成和图层功能
  • 提供多个模型用于转换 SDXL 为透明图片生成器
  • 高度动态且可能在未来一个月内发生大量变化

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

2024-4-26 群讨论:PostgreSQL & MyS

发表于 2024-04-27

以下来自本人拉的一个关于 Java 技术的讨论群。关注公众号:hashcon,私信进群拉你

PostgresSQL 和 MySQL 各自适用的场景(仅考虑 OLTP)

假设都是默认的事务引擎,默认的编码压缩方式:

MySQL 与 PG 在 OLTP 的场景下,主要区别在于:两点:

  1. 对于二级索引处理的差异:
* MySQL 二级索引叶子节点是保存的主键的指针,PG 的二级索引叶子节点与主键索引一样直接是记录位置,行记录发生更新的时候,由于 MVCC 与可变长度字段与 null 字段,很可能导致行位置变化,对于 PG 需要更新所有二级索引,但是 MySQL 不需要
    + PG 如果是非索引字段更新,缓冲池够的情况下 Heap-Only Tuples (HOT) 生效,不一定用更新索引
    + 但是 Heap-Only Tuples (HOT) 其实在实际 OLTP 场景中,命中率不是特别理想。
* 所以 **MySQL 对于有二级索引的表高并发更新,以及涉及数据位置改变的更新**(比如更新 varchar 字段为更长的),**以及插入,会比 PG 表现好**。
* 但是,**这种设计下,MySQL 的二级索引读取性能肯定也不如 PG**。因此,需要好好考虑场景。
  1. 对于 MVCC 处理的差异:
* PostgreSQL 的 MVCC 基于 xmin, xmax 机制实现:当一行数据需要被更新或删除时,PostgreSQL 并不是直接更改原有的行记录。相反,它会:
    + 更新:插入一个新的行版本,其中 xmin 设置为当前事务的 ID,同时将旧版本行的 xmax 设置为当前事务的 ID。
    + 删除:简单地将行的 xmax 设置为当前事务的 ID。
* MySQL 的 MVCC 是基于行锁和 undo log实现的。每行记录都有两个隐藏的列,分别记录事务ID(`trx_id`)和回滚指针(`roll_pointer`)。读取数据时,InnoDB 会根据事务 ID 和回滚指针找到行数据的可见版本
* PG 的优势体现在读取,老版本也可以直接读取,同时读取这行不阻塞这行的更新。但是劣势也很明显,频繁更新,表膨胀过快,vacuum 有时候完全跟不上高速写操作,另外 vacuum 本身也有很多问题,autovacuum 本身在部分场景下会导致 dead tuple 不断积攒以至于一段时间后查询的 IO 开销将极为高昂,必须要 DBA 手动的进行释放(此处感谢:B站 滑稽\_1 [space.bilibili.com/38107834](https://space.bilibili.com/38107834) )。插入性能也会受这个多版本影响。
* MySQL 优势在写入,只为当前读写的行加锁,其他写入不受影响,并发写入更高。
* PostgreSQL 和 MySQL 在大表更新频率很高达到一定阈值的时候,不是那种订单表,交易表,而是类似于用户余额表那种,带来的查询与插入的性能严重下降。在这种场景下,PostgreSQL 本身由于 xmin 与 xmax 的回滚 MVCC 设计导致表膨胀过快,与 MySQL 类似 Oracle 的 Redolog 设计上,MySQL 需要分库分表的阈值相对于 PostgreSQL 高一些。PostgreSQL 之前推出过 zheap 想改用 Redolog,但是后来在 20 年之后就没有下文了,不知道为啥。参考:[wiki.postgresql.org/wiki/Zheap](https://wiki.postgresql.org/wiki/Zheap)

综合来看,其实 MySQL 更适合 OLTP 的场景。现在云服务商提供的数据库基本都实现了主从延迟很低,读取性能可以加从库解决。例如 Aurora,一个写入实例最多可以加 12 个读取实例,延迟在业务最高峰,也只有 300 ms,平常在 10 ms 左右。

PostgreSQL 目前的生态更丰富,并且 OLAP 的很多数据库,其实在协议层用的是 PostgreSQL。PostgreSQL 目前的发展方向,也主要在 OLAP 的生态场景不断完善。

另外,Uber 在 2015 年的时候,从分库分表的 PostgreSQL,转移到了分库分表的 MySQL 以应对他们的 OLTP 场景,原文:www.uber.com/en-HK/blog/…

个人简介:个人业余研究了 AI LLM 微调与 RAG,目前成果是微调了三个模型:

  1. 一个模型是基于 whisper 模型的微调,使用我原来做的精翻的视频按照语句段落切分的片段,并尝试按照方言类别,以及技术类别分别尝试微调的成果。用于视频字幕识别。
  2. 一个模型是基于 Mistral Large 的模型的微调,识别提取视频课件的片段,辅以实际的课件文字进行识别微调。用于识别课件的片段。
  3. 最后一个模型是基于 Claude 3 的模型微调,使用我之前制作的翻译字幕,与 AWS、Go 社区、CNCF 生态里面的官方英文文档以及中文文档作为语料,按照内容段交叉拆分,进行微调,用于字幕翻译。

目前,准确率已经非常高了。大家如果有想要我制作的视频,欢迎关注留言。

本人也是开源代码爱好者,贡献过很多项目的源码(Mycat 和 Java JFRUnit 的核心贡献者,贡献过 OpenJDK,Spring,Spring Cloud,Apache Bookkeeper,Apache RocketMQ,Ribbon,Lettuce、 SocketIO、Langchain4j 等项目 ),同时也是深度技术迷,编写过很多硬核的原理分析系列(JVM)。本人也有一个 Java 技术交流群,感兴趣的欢迎关注。

另外,一如即往的是,全网的所有收益,都会捐赠给希望工程,坚持靠爱与兴趣发电。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Stencil 搭建 Web Component 组件库项目

发表于 2024-04-26

前言

在之前介绍了 Web Component 组件库有什么优势,这篇讲一下如何使用 Stencil 搭建 web component 组件库

Stencil 是一个开源的编译器,用于构建可复用、可扩展的 Web Component 组件库,是由 Ionic 团队开源的,方面我们以一种简单高效的方式来创建和管理 Web Component 组件库。

为什么选择Stencil

选择 Stencil 的原因在于它的跨框架兼容性、性能优化以及对标准 Web Components 的支持

Stencil的优势和特点

  • 性能优化:自动懒加载、异步渲染等特性使得组件加载更快。
  • 跨框架:生成的组件可以在多个框架中使用,如Vue、Angular、React等。
  • JSX 支持:可以使用类似于 React 的方式来编写组件。
  • TypeScript持:使用强类型语言来编写组件,提高了代码的可读性和可维护性
  • 测试工具:提供了一套完整的测试工具,可以方便地进行单元测试和端到端测试。
  • 社区支持:由Ionic团队维护,社区活跃,资源丰富。

Stencil 搭建开发环境

使用 Stencil 提供的脚手架安装项目,进入 packages 目录,执行创建命令

1
2
3
4
5
shell复制代码# npm
npm init stencil

# pnpm
pnpm create stencil

执行完后出现交互性的界面,有三种模式,构建组件库选择第一个 component

  • component:是构建一个可以用到任何地方的 Web Components 组件库
  • app:是用来构建一个静态网站或者一个应用,支持路由
  • ionic-pwa:构建一个 pwa 应用

选择“component”选项将提示您输入项目的名称。

1
2
css复制代码✔ Pick a starter › component
? Project name › components

成功创建我们的项目之后,CLI将向控制台输出如下内容

安装依赖,执行 npm start 启动项目

目录结构

初始化目录结构,最外层的目录结构

  • dist、loader:默认的输出目录,编译后的文件
  • src:源文件目录,包含了组件的代码、样式和测试
  • tsconfig.json 是用来配置 ts 的一些 config 配置。
  • www :启动本地开发环境后,经过 stencil 编译后 src 的 js 文件和 index.html 会被执行复制过移动到 www 文件夹中去,而启动的热服务器(live server)访问就会被映射到此文件夹,从而进行渲染和调试。
1
2
3
4
5
6
7
8
lua复制代码├── dist
├── loader
├── src
├── package.json
├── readme.md
├── stencil.config.ts
├── tsconfig.json
└── www

组件和配置目录结构

创建组件

在新建项目后,package.json 默认添加了如下脚本命令,其中 stencil generate 是创建组件的命令

1
2
3
4
5
6
7
8
9
10
11
12
13
json复制代码{
"scripts": {
# 构建组件
"build": "stencil build",
# 启动开发
"start": "stencil build --dev --watch --serve",
# 端对端测试
"test": "stencil test --spec --e2e",
"test.watch": "stencil test --spec --e2e --watchAll",
# 创建组件
"generate": "stencil generate"
}
}

运行 pnpm generate,提示创建的组件是否要添加 样式、单元测试、端对端测试文件

输入组件名称 my-button,直接回车在 components 目录下创建一个 my-button 组件目录

Stencil 使用 tsx 进行开发,类似 react ,提供完整的声明周期函数和装饰器等功能,查看 my-component.tsx 文件

  • @Component() 声明一个新的 Web 组件
  • @Prop() 声明一个公开的属性/属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
jsx复制代码import { Component, Prop, h } from '@stencil/core';
import { format } from '../../utils/utils';

@Component({
tag: 'my-component',
styleUrl: 'my-component.css',
shadow: true,
})
export class MyComponent {
// 声明一个公开的属性/属性
@Prop() first: string;

/**
* The middle name
*/
@Prop() middle: string;

/**
* The last name
*/
@Prop() last: string;

private getText(): string {
return format(this.first, this.middle, this.last);
}

render() {
return <div>Hello, World! I'm {this.getText()}</div>;
}
}

使用组件像普通 html 标签一样使用,传入 first、last 会被 @Prop 声明的属性接收,修改它们的值,组件内部会响应式的更新渲染视图

1
jsx复制代码<my-component first="Stencil" last="'Don't call me a framework' JS"></my-component>

stencil 工作原理

入口文件

src/index.html 是用来启动本地 dev 环境时候的一个入口文件,在初始化过程中,它会默认添加需要的 js 产物的路径,如下:

1
2
3
4
5
6
7
html复制代码  <head>
<script type="module" src="/build/editor.esm.js"></script>
<script nomodule src="/build/editor.js"></script>
</head>
<body>
<my-component first="Stencil" last="'Don't call me a framework' JS"></my-component>
</body>

在启动后,根据 stencil.config.ts 命名空间字段设置如 namespace: 'editor',会将编译 js 文件生成在 dist/editor 目录下,同时会拷贝一份到 www/build 文件夹提供给服务器访问,src/index.html 也会一并被复制过去

可以看到在启动后服务器访问的 js 目录 www/build 和 dist/editor 文件是一样的

www 本地调式目录

在启动本地命令 stencil build --dev --watch --serve 后,改动下组件的内容后会发现,www 文件夹内的资源文件也随之更新,浏览器相应界面的也发生变化,执行逻辑

所以 www 文件夹存放的是开发调试所包含资源文件,build 文件夹是编译的产物;host.config.json 会存放一些本地热更新服务器的配置,比如缓存的配置等;而 index.html 则是经过压缩的 src/index.html 文件。

stencil.config.ts 配置

stencil.config.ts 文件是 stencil 工具的配置文件,可以在此个性化的配置打包功能,比如:

  • globalScript:全局脚本入口文件,可以用来实现在初始化组件,进行挂载的时候进行一些逻辑的处理
  • globalStyle: 全局的样式路径,如设置 var 函数的主题变量
  • plugins 插件: 配置一些编译过程中的额外功能,比如 sass 插件可以用来适配 scss 文件的编译,postcss 可以用来压缩、增加一些兼容性的代码来适配各种浏览器等等,官方还提供了以下插件:@stencil/less、@stencil/postcss、@stencil/sass、@stencil/stylus
  • outputTargets:根据配置 target 进行打包

dist / loader 打包产物

执行 pnpm run build 命令进行打包,最终生成编译产物如下,包括 dist 和 loader 两个目录

通过观察 stencil 默认生成的 package.json 文件中 files 自动的配置也能知道,最终输出的文件资源 dist 和 loader 发布到 npm 上

1
2
3
4
json复制代码"files": [
"dist/",
"loader/"
],

那么这两个文件夹有什么关系呢,我们先看下 loader 文件夹 package.json 的内容和其他文件的内容

loader 目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
json复制代码{
"name": "editor-loader",
"private": true,
"typings": "./index.d.ts",
"module": "./index.js",
"main": "./index.cjs.js",
"jsnext:main": "./index.es2017.js",
"es2015": "./index.es2017.js",
"es2017": "./index.es2017.js",
"unpkg": "./cdn.js"
}

// index.js

export * from '../dist/esm/polyfills/index.js';
export * from '../dist/esm/loader.js';

// index.es2017.js

export * from '../dist/esm/polyfills/index.js';
export * from '../dist/esm/loader.js';


//index.cjs.js

module.exports = require('../dist/cjs/loader.cjs.js');
module.exports.applyPolyfills = function() { return Promise.resolve() };

可以看出,loader 文件夹是兼容 ESModule 和 Common.js 模块规范,根据当前的使用环境,分别引入 dist 的不同的文件夹产物内,比如 cjs 的引入类型会加载 ../dist/cjs/loader.cjs.js ;es6+ 的引入会加载 polyfills 文件和 esm 模式的资源文件 ../dist/esm/loader.js 。

dist 目录

在默认的 stencil.config.ts 配置下,打包结果为

1
2
3
4
5
6
7
8
复制代码├── cjs
├── collection
├── components
├── esm
├── editor
├── index.cjs.js
├── index.js
└── types

其中 cjs 和 esm 的逻辑几乎相同,esm 只是多了一些兼容性的代码,它们都是为了适配不同的环境所构造的不同产物

stencil 初始化配置里面默认包含了 dist-custom-elements,意思就是可以单独打包出符合 web components 规范的组件 class

打包的组件单独引入会进行自动注册

1
2
3
js复制代码import { HelloWorld } from 'my-library/dist/components/hello-world';

customElements.define('hello-world', HelloWorld);

打包的文件夹的内容与 www/build 文件内容相同,因为 stencil 也默认开启了 www 编译的 outTarget,所以也会编译出一份可以直接用于本地环境的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
json复制代码outputTargets: [
{
type: 'dist',
esmLoaderPath: '../loader',
},
{
type: 'dist-custom-elements',
customElementsExportBehavior: 'auto-define-custom-elements',
externalRuntime: false,
},
{
type: 'docs-readme',
},
{
type: 'www',
serviceWorker: null, // disable service workers
},
],

如果能对新技术开发组件库感兴趣,也欢迎加入stencil-component-ui,给个 star 鼓励一下 👏👏

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

作用域你真的了解吗?

发表于 2024-04-26

作用域

作用域是指在程序中变量、函数等可被访问的范围。JavaScript中有三种,全局作用域,函数作用域,块级作用域。

1、全局作用域

全局作用域是指变量或函数在整个脚本或程序中都可以被访问和使用的范围。在 JavaScript 中,全局作用域可以分为以下两种情况:

a. 直接编写在<script>标签中的 JavaScript 代码,或在一个单独的 JavaScript 文件中,都属于全局作用域。

1
2
3
scss复制代码var a = 1;
function foo(){}
if(){}

其中变量a,函数foo,if语句都在全局作用域下。

b. 在全局作用域中,有一个全局对象window,可以直接使用。全局作用域在页面打开时创建,页面关闭时销毁。

c. 变量如果声明在全局作用域下,在任意位置都可以使用。

1
2
3
4
5
6
7
8
scss复制代码var a = 1;
console.log(a);//1 在全局域中可以使用
function foo(){
console.log(a);//1 在函数内部可以使用
}
if(a){} //在语句中可以使用
for(var i = 1;i<10;i++){} //在语句中声明的变量也属于全局变量
console.log(i)//10

d. 如果变量没有使用关键字,则为全局变量

1
2
3
4
5
scss复制代码function foo() {
x= 1;
}
foo();
console.log(x);//1

2、函数作用域

函数作用域是指在函数内部定义的变量和声明的作用范围。在函数作用域中,变量仅在函数内部可访问,在函数外部无法直接访问这些变量。当函数执行完毕后,函数内部的变量会被销毁。

1
2
3
4
javascript复制代码function foo(){
var a = 1;
}
console.log(a)//报错 a is not defined

3、块级作用域

块级作用域是指在JavaScript 中,用花括号 {} 括起来的一段代码所形成的作用域。在块级作用域中,变量的定义和使用只在该块内有效,超出该块范围后,这些变量将无法访问。

在 ES6 之前的 JavaScript 中,只有全局作用域和函数作用域,没有块级作用域的概念。这导致了一些问题,例如内层变量可能会覆盖外层变量,以及用来计数的循环变量可能会泄露为全局变量。

就像这样

1
2
3
4
5
6
7
8
ini复制代码function foo() {
var x = 2;
function bar() {
var x = 3;
}
bar();
console.log(x); // 3
}

ES6 中新增了块级作用域,使得 JavaScript 语言更加严谨和安全。块级作用域可以用于解决上述问题,并且提供了更好的代码组织和封装能力,就像这样。

1
2
3
4
5
6
7
8
9
10
ini复制代码function foo() {
var x = 2;
{
function bar() {
var x = 3;
}
}
bar();
console.log(x); // 2
}

当我们将bar声明成块级作用域时,x就不会发生泄露。既然聊到了作用域,就不得不聊一下var,let,和const了。

var let const

前置小知识

1、属性声明提升

1
2
ini复制代码console.log(a); // undefined
var a = 2;

v8引擎将变量声明提升到代码的开头,使得变量可以在整个代码中使用。变成这样:

1
2
3
ini复制代码var a;
console.log(a); // undefined
a = 2;

2、函数声明提升

1
2
3
4
scss复制代码foo(); //输出 "foo"
function foo() {
console.log("foo");
}

var的短处:

当我们在使用var时,有时会因为声明提升导致出现一些难以预料的结果。var允许重复声明同一个变量,这样会导致变量被覆盖和产生混乱,当全局声明时,还可能造成全局污染。所以ES6专门更新了两种新的声明变量的关键字,let和const。

let的作用和var基本差不多,但是let不存在声明提升问题,例如:

1
2
ini复制代码console.log(a); // 报错
let a = 2;

并且不能重复声明同一个变量例如:

1
2
ini复制代码let a = 1;
let a = 2;//报错

let会和{}形成块级作用域,只在其内部访问才有效,例如:

1
2
3
4
scss复制代码for(let i =0;i<10;i++){
console.log(i);//可以
}
console.log(i);//报错

const用于声明一个常量,也会和{}形成块级作用域,常量的意思是不能被修改的量。

1
2
ini复制代码const a = 1;
a = 2;//错误

总结

var let const相同点:

var let他们都是用来声明一个变量 ,const用来声明一个常量,在其作用域下三者都能被访问的到。

不同点:

var可以重复声明同一个变量,并且可以随意修改他的值,而且他存在声明提升。

let会和他所在{}之间组成块级作用域并只在其内部有效,不存在声明提升,并且不能重复声明同一个变量。

const也是只在其块级作用域下有效,并且声明时必须初始化,后续不能修改其值。

欺骗词法作用域

聊完三大作用域的之后,我们聊聊一些奇奇怪怪的函数,他可以欺骗词法作用域规则。

eval():在 JavaScript 中,eval() 函数用于执行一段字符串表示的 JavaScript 代码。它可以将字符串转换为相应的对象,并返回表达式的结果。eval() 将原本不属于当前作用域的变量声明提升到当前作用域,所以根据这个特性,我们可以写出这样的代码。

1
2
3
4
5
6
scss复制代码function foo(str,a) {
eval(str);//返回var b = 3; 语句
console.log(a,b);// 1 2 ?错误
}
var b = 2;
foo("var b = 3",1);// 1 3

with(obj){}:在 with() 语句中,obj 是要访问的对象。在 {} 内部,可以直接使用 obj 的属性和方法,而无需使用点号操作符。with() 用于修改一个对象的属性值,但如果属性不存在,则会创建新的属性,泄露到全局作用域。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
scss复制代码var  obj = {
a: 3
}
with(obj){//正常使用,obj里面有a属性
a: 4
}
function foo(obj){
with (obj) {//不正常使用,obj里面没有b属性
b = 2;
}
}
foo(obj);
console.log(obj); // {a: 3, b: 2}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Rust入门掌握这50个写法就够了

发表于 2024-04-26

Rust,被誉为系统编程语言的新星,以其内存安全和高效的并发控制吸引了大量开发者的关注。对于初学者来说,要想快速上手并灵活运用Rust,掌握一些基本的写法和技巧至关重要。以下是Rust入门的50个关键写法的前十个,帮助你轻松迈出Rust编程的第一步。

各位美女帅哥只要老实把下面的代码都敲一遍,以后就不要再说没听过 Rust 了吧~

image.png

1. Hello, World!

这是学习任何新编程语言的起点。在Rust中,你可以这样写:

1
2
3
rust复制代码fn main() {
println!("Hello, World!");
}

2. 变量声明

在Rust中,你可以使用let关键字来声明变量。默认情况下,这些变量是不可变的。

1
rust复制代码let x = 5; // 不可变变量

3. 可变变量

如果你需要一个可以修改的变量,可以在let之后添加mut关键字。

1
2
rust复制代码let mut y = 10; // 可变变量
y = 15; // 修改变量值

4. 数据类型

Rust有多种内置的数据类型,如整数、浮点数、布尔值等。

1
2
3
rust复制代码let i32_num: i32 = -12345; // 32位有符号整数
let f64_num: f64 = 3.14; // 64位浮点数
let is_true: bool = true; // 布尔值

5. 元组

元组是一种可以包含多种不同类型元素的复合数据类型。

1
rust复制代码let tup = (500, "hello", 4.5); // 元组包含整数、字符串和浮点数

6. 数组

数组是相同类型元素的连续集合。

1
rust复制代码let arr = [1, 2, 3, 4, 5]; // 包含5个整数的数组

7. 切片

切片是对数组的一部分的引用。

1
rust复制代码let slice = &arr[1..3]; // 引用数组的第2和第3个元素(索引从0开始)

8. 控制流 - 条件语句

使用if、else if和else来控制程序的流程。

1
2
3
4
5
6
7
8
rust复制代码let num = 3;
if num > 5 {
println!("大于5");
} else if num == 5 {
println!("等于5");
} else {
println!("小于或等于5");
}

9. 控制流 - 循环

使用for循环来遍历集合中的元素。

1
2
3
rust复制代码for i in 0..5 { // 遍历0到4的整数序列
println!("{}", i);
}

10. 函数定义

在Rust中,你可以定义自己的函数来执行特定的任务。

1
2
3
4
rust复制代码fn greet(name: &str) {
println!("Hello, {}!", name); // 打印问候语
}
greet("Rustacean"); // 调用函数并传递参数

11. 函数返回值

在Rust中,你可以定义有返回值的函数。

1
2
3
4
rust复制代码fn add(a: i32, b: i32) -> i32 {
a + b // 返回两个整数的和
}
let sum = add(5, 7); // 调用函数并将结果存储在变量sum中

12. 引用和借用

Rust通过引用(&)来借用变量的值,而不直接获取其所有权。

1
2
3
4
5
rust复制代码fn print_value(value: &i32) {
println!("Value: {}", value);
}
let x = 10;
print_value(&x); // 通过引用传递变量x

13. 可变引用

可变引用允许你修改引用的值。

1
2
3
4
5
rust复制代码fn modify_value(value: &mut i32) {
*value += 1; // 修改引用的值
}
let mut y = 20;
modify_value(&mut y); // 通过可变引用传递变量y

14. 结构体(Structs)

结构体允许你定义一个复合数据类型,可以包含多个字段。

1
2
3
4
5
rust复制代码struct Person {
name: String,
age: u8,
}
let person = Person { name: "Alice".to_string(), age: 30 };

15. 枚举(Enums)

枚举类型用于定义一组命名的值。

1
2
3
4
5
6
rust复制代码enum Color {
Red,
Green,
Blue,
}
let my_color = Color::Red; // 使用枚举的一个变体

16. 模式匹配(Match)

match表达式允许你根据枚举或其他类型的值进行分支处理。

1
2
3
4
5
rust复制代码match my_color {
Color::Red => println!("It's red!"),
Color::Green => println!("It's green!"),
Color::Blue => println!("It's blue!"),
}

17. Option枚举

Option是一个表示可能有值或没有值的枚举。

1
2
3
4
5
rust复制代码enum Option<T> {
Some(T),
None,
}
let optional_value: Option<i32> = Some(42); // 或者None表示没有值

18. Result类型

Result类型用于处理可能出错的操作,它包含Ok或Err两个变体。

1
2
3
4
5
6
7
8
9
10
11
rust复制代码enum Result<T, E> {
Ok(T),
Err(E),
}
fn divide(a: i32, b: i32) -> Result<i32, &'static str> {
if b == 0 {
Err("Division by zero!")
} else {
Ok(a / b)
}
}

19. 泛型

泛型允许你编写适用于多种类型的函数或结构体。

1
2
3
4
rust复制代码fn largest<T: Ord>(a: T, b: T) -> T {
if a > b { a } else { b }
}
let largest_i32 = largest(5i32, 10i32); // 泛型函数用于i32类型

20. 闭包(Closures)和迭代器(Iterators)

闭包是可以捕获环境的匿名函数,而迭代器提供了一种遍历集合元素的方法。

1
2
rust复制代码let nums = vec![1, 2, 3, 4, 5];
let squares = nums.iter().map(|x| x * x).collect::<Vec<_>>(); // 使用闭包和迭代器计算平方并收集结果

21. 闭包作为函数参数

在Rust中,你可以将闭包作为函数的参数传递。

1
2
3
4
rust复制代码fn apply<F>(f: F, x: i32) -> i32 where F: Fn(i32) -> i32 {
f(x)
}
let result = apply(|x| x * 2, 5); // 传递闭包,将数字乘以2

22. 使用标准库中的集合

Rust的标准库提供了多种集合类型,如Vec<T>、HashMap<K, V>等。

1
2
3
rust复制代码let mut vec = Vec::new();
vec.push(1);
vec.push(2);

23. 字符串操作

在Rust中,字符串是不可变的,但你可以通过String类型来创建和修改字符串。

1
2
rust复制代码let mut s = String::from("Hello");
s.push_str(", World!"); // 修改字符串

24. 使用match进行错误处理

match可以用于处理Result类型的错误。

1
2
3
4
5
rust复制代码let result: Result<i32, &str> = Ok(42); // 或者Err("Error message")
match result {
Ok(value) => println!("Value: {}", value),
Err(e) => println!("Error: {}", e),
}

25. 生命周期

Rust中的生命周期参数用于指定引用的有效范围,确保数据竞争和悬垂引用的安全。

1
2
3
4
5
6
7
rust复制代码fn print_longest<'a>(x: &'a str, y: &'a str) {
if x.len() > y.len() {
println!("{} is longer", x);
} else {
println!("{} is longer", y);
}
}

26. 使用use语句导入模块

你可以使用use语句来导入模块,以便在代码中使用它们。

1
2
3
rust复制代码use std::collections::HashMap;
let mut map = HashMap::new();
map.insert("key", "value");

27. 实现特征(Traits)

在Rust中,你可以通过实现特征来为类型添加行为。

1
2
3
4
5
6
7
8
9
10
11
rust复制代码trait Greeting {
fn greet(&self);
}
struct Person;
impl Greeting for Person {
fn greet(&self) {
println!("Hello!");
}
}
let p = Person;
p.greet(); // 调用实现的特征方法

28. 宏(Macros)

Rust支持宏,它们允许你在编译时生成代码。

1
2
3
4
5
6
rust复制代码macro_rules! print_hello {
() => {
println!("Hello from macro!");
};
}
print_hello!(); // 调用宏

29. 线程安全

Rust提供了多种同步原语,如Mutex和Arc,以确保线程安全。

1
2
3
rust复制代码use std::sync::{Arc, Mutex};
let counter = Arc::new(Mutex::new(0));
// 在多线程环境中安全地修改counter的值...

30. 测试

Rust内置了强大的测试框架,你可以使用#[test]属性来编写测试函数。

1
2
3
4
rust复制代码#[test]
fn test_addition() {
assert_eq!(1 + 1, 2); // 断言1+1等于2,用于测试
}

31. 条件编译

Rust支持条件编译,允许你根据某些条件来编译特定的代码块。

1
2
3
4
5
6
7
rust复制代码#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}

32. 文档注释

Rust使用特定的注释语法来生成文档。

1
2
3
rust复制代码/// 这是一个文档注释,用于描述下面的函数。
/// # 示例
///

/// println!(“Hello, world!”);
///

1
2
3
pub fn hello_world() {
println!("Hello, world!");
}

33. 外部依赖管理

使用Cargo.toml文件来管理项目的外部依赖。

1
2
toml复制代码[dependencies]
rand = "0.8"

34. 模块系统

Rust有一个强大的模块系统,允许你将代码组织成多个文件和模块。

1
2
3
4
5
6
7
rust复制代码// lib.rs
pub mod my_module;

// my_module.rs
pub fn my_function() {
println!("This is my function in my_module");
}

35. 范围

Rust支持使用范围来表示一系列的数值。

1
2
3
rust复制代码for i in 1..=5 {
println!("{}", i); // 打印从1到5的数字
}

36. 所有权和借用

理解Rust的所有权系统和借用检查器是编写安全Rust代码的关键。

1
2
3
4
5
6
7
rust复制代码fn take_ownership(v: Vec<i32>) {
// ...
}

fn borrow_vector(v: &Vec<i32>) {
// ...
}

37. 默认参数和可变参数

Rust函数支持默认参数和可变参数列表。

1
2
3
4
5
6
7
rust复制代码fn function_with_default(param: i32 = 10) {
// ...
}

fn function_with_variadic_params(params: &[&str]) {
// ...
}

38. 静态生命周期

在Rust中,你可以使用静态生命周期来指定某个引用的生命周期与整个程序的执行时间一样长。

1
rust复制代码static MY_CONST: &'static str = "Hello, world!";

39. 内部可变性和单元化

Rust提供了Cell和RefCell等类型,允许你在不违反Rust所有权规则的情况下实现内部可变性。

1
2
3
rust复制代码use std::cell::Cell;
let x = Cell::new(1);
x.set(x.get() + 1); // 内部可变性,不需要mut关键字

40. 不安全代码

在某些情况下,你可能需要编写不安全代码来执行底层操作。Rust提供了unsafe关键字来处理这些情况。

1
2
3
4
5
6
7
rust复制代码unsafe fn dangerous_function() {
// 执行不安全操作...
}

unsafe {
dangerous_function(); // 必须在unsafe块中调用不安全函数
}

41. 泛型

Rust支持泛型编程,允许你编写灵活且可重用的代码。

1
2
3
4
5
6
7
8
9
rust复制代码fn largest<T: Ord>(list: &[T]) -> T {
let mut largest = &list[0];
for item in list {
if item > *largest {
largest = item;
}
}
*largest
}

42. 特性(Trait)对象

特性对象允许你使用动态分发来处理具有相同特性的多种类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
rust复制代码trait Animal {
fn make_sound(&self);
}

struct Dog;
impl Animal for Dog {
fn make_sound(&self) {
println!("Woof!");
}
}

fn animal_sound(animal: &dyn Animal) {
animal.make_sound();
}

43. 关联函数和关联类型

在Rust中,你可以在特性中定义关联函数和关联类型。

1
2
3
4
rust复制代码trait Graph {
type Node;
fn add_node(&mut self, node: Self::Node);
}

44. 从错误中恢复

使用?操作符可以方便地处理可能返回Err的结果。

1
2
3
4
rust复制代码fn read_file(path: &str) -> Result<String, std::io::Error> {
let content = std::fs::read_to_string(path)?; // 错误处理被简化
Ok(content)
}

45. 自定义错误类型

你可以定义自己的错误类型来实现更复杂的错误处理逻辑。

1
2
3
4
5
6
7
8
9
10
11
rust复制代码enum MyError {
Io(std::io::Error),
Parse(std::num::ParseIntError),
}

impl From<std::io::Error> for MyError {
fn from(err: std::io::Error) -> MyError {
MyError::Io(err)
}
}
// 类似地实现其他错误类型的转换...

46. 使用Option和Result进行错误处理

Rust使用Option<T>和Result<T, E>来表示可能失败的操作。

1
2
3
4
5
6
7
rust复制代码fn divide(a: f64, b: f64) -> Result<f64, &'static str> {
if b == 0.0 {
Err("Cannot divide by zero")
} else {
Ok(a / b)
}
}

47. 迭代器

Rust的迭代器提供了一种处理集合中元素的高效方式。

1
2
3
4
rust复制代码let vec = vec![1, 2, 3, 4, 5];
for i in vec.iter() {
println!("{}", i);
}

48. 并发与并行

Rust提供了强大的并发和并行支持,如线程、消息传递和异步I/O。

1
2
3
4
5
rust复制代码use std::thread;
let handle = thread::spawn(|| {
println!("Running in a new thread!");
});
handle.join().unwrap();

49. 结构体更新语法

Rust支持结构体更新语法,可以方便地创建结构体的新实例,同时保留部分字段的值。

1
2
3
rust复制代码struct Point { x: i32, y: i32 }
let point = Point { x: 1, y: 2 };
let new_point = Point { x: 5, ..point }; // y字段保持不变,x字段更新为5

50. 宏的高级用法

Rust的宏系统非常强大,支持声明性宏和过程性宏,可以实现复杂的代码生成和抽象。

1
2
3
4
5
6
rust复制代码macro_rules! print_vec {
($($x:expr),*) => {
println!("Vector: [{:?}]", vec![$($x),*]);
}
}
print_vec!(1, 2, 3); // 输出:Vector: [1, 2, 3]

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

git revert后再次merge到master会丢失部分

发表于 2024-04-26

提前声明,这个问题你在公司可能一辈子碰不上,只有很多野路子协同开发可能会遇到这个问题。不过,这个对于理解revert的本质确实很有帮助。

一、实验复现问题

在feature分支上开发了一个新功能,其中有部分带点bug

image.png
恰巧,代码评审时没有发现这个问题,将这个feature开发的新功能合并到了master上

image.png
自然而然的,master在线上运行的过程中出现了问题,这个时候我们选择了通过revert回滚

image.png
按照正常流程,有bug就要修复,于是在feature上修复了这个bug

image.png
经过更加严格的代码评审,这个代码没问题,于是又将修复后的feature合并到了master

image.png

这个时候问题出现了,master上feture1和feature2的文件没有了,仅留下feature3

二、分析下问题如何造成的

在我们将feature的修复代码合并到master之前,master和feature的分支如下:

initfeaturerevert feature
initfeaturefix
此时按照常理来说,合并时会进行差异检查。两个分支会因为revert feature和fix两个commit出现了冲突需要进行合并。但是,事实上并没有。

非常反直觉的是,在操作合并时revert feature这个记录像失效了一样,fix直接合并了上来。

此时fix仅变更了含有bug的feature3而revert feature中没有任何fearue的部分。

三、出现问题的原因

对此,Linus早就给出了解释:

Reverting a regular commit just effectively undoes what that commit did, and is fairly straightforward. But reverting a merge commit also undoes the data that the commit changed, but it does absolutely nothing to the effects on history that the merge had.

So the merge will still exist, and it will still be seen as joining the two branches together, and future merges will see that merge as the last shared state - and the revert that reverted the merge brought in will not affect that at all.

So a “revert” undoes the data changes, but it’s very much not an “undo” in the sense that it doesn’t undo the effects of a commit on the repository history.

So if you think of “revert” as “undo”, then you’re going to always miss this part of reverts. Yes, it undoes the data, but no, it doesn’t undo history.

简单的梳理下:

  1. 我们在feature分支上revert一个常规commit,就仅仅是撤销这个commit做的改动
  2. 但是如果我们在master分支上revert一个merged commit(合并上来的commit),也仅仅撤销这个commit的改动,而不会对master分支产生任何对这个merge状态的影响
  3. 这就意味着意味着在master分支上merge feature的状态仍然存在
  4. 我们在feature修复后再发起merge,检查时会看到master分支上存在merged状态
  5. 因此只会带上merge状态之后(也就是feature之后)的改动,即fix,之前被revert的改动(即feature)不会再被merge到master分支上

最后总结下核心原因:revert撤回了代码的变更,但是没有撤回合并的记录,在master分支上慎用此方法。

四、如何解决这个问题

  1. 在master上复原feature的改动,也就是revert revert feature, 让master和feature分支上的代码一致,然后再进行合并。

initfeaturerevert featurerevert revert feature
initfeaturefix
2. 在feature上同步master,然后在进行修复,最后合并也能解决这个问题。

这就是为什么这个问题很难在公司遇到的原因,正经人谁不先看一下master直接提merge啊。但是这样也会把feature分支上有用的代码全部弄没。

initfeaturerevert feature
initfeaturerevert featurefix
3. 虽然,revert没有撤回合并的状态,但是那个合并的状态绑定的是原有的commit ID,我们可以从feature将原commit的内容带到master,但是需要产生和原merged commit不同的状态(不同的commit SHA ID)

在feature分支reset + commit,产生新的commits:feature-copy 修复后再merge到master

initfeaturerevert feature
initfeature-copyfix
参考文章


Git revert 某次merge后再重新 merge代码被丢失(第一次想merge的代码再也merge不上了)_git revert 后 再merge-CSDN博客

git 分回滚后无法合并代码问题 - 赵坤坤 - 博客园 (cnblogs.com)

如何再次合并已经被revert的合并? | furthergo

git撤销merge,彻底学会git revert的用法 - 我的诗和远方 - 博客园 (cnblogs.com)

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

以太网口硬件知识分享 一、了解网口通信基本原理 二、了解MD

发表于 2024-04-26

一、了解网口通信基本原理

实现网络通信实质上是PHY与MAC及RJ45接口实现信号传输。MAC 就是以太网控制器,MAC属于数据链路层,主要负责把数据封装成帧,对帧进行界定实现帧同步。对MAC地址和源MAC地址及逆行相应的处理并对错误帧进行处理。PHY属于物理层,在以太网控制器中负责物理层功能的芯片叫PHY芯片,因为网线上传输的是模拟信号而MAC发出或接收的信号为数字信号所以PHY主要负责对网络数据的编解码处理以及一些网络状态的控制。RJ45就是我们常用的网口座子。

在嵌入式领域通常MAC是被集成在CPU里面的,这种方案是目前的主流方案,PHY层(物理层)一般是使用专用的PHY芯片。如果要PHY芯片在一个特定的模式下工作就需要对PHY芯片进行控制,在这里用到的是MDIO总线。通过MDIO总线对PHY芯片的寄存器进行相应配置实现一些模式以及功能的控制。而数据信息,是由另外的网络协议接口进行传输,例如ELF 1上的PHY和MAC之间是用的RMII接口实现的数据传输,整体架构如图2.4所示:

二、了解MDIO总线

MDC是开漏(OD)输出,只能输出低电平,因此需要上拉处理,为MDIO提供时钟信号;根据KSZ8081RNB芯片手册(数据手册中查找),MDIO管理接口数据传输格式及含义如下表所示:

需要注意的是,TA在读操作和写操作两种状态下数据位不同,TA是介于寄存器地址和寄存器数据之间的2个bit位,用来转换数据传输方向。读操作时,地址传输和数据传输控制方不同,设置2bit TA的目的就是为了防止MDIO总线上产生竞争。TA的第1位z,PHY和MAC均释放总线控制输出高阻,且后面MAC一直保持高阻态状态,第2位0由PHY提供。第2位相当于一个应答信号,如果第2位为高电平,PHY无应答。除此之外,Idle为空闲状态,此时MDIO无源驱动,处高阻状态,但一般用上拉电阻使其处在高电平。

三、关于网络信号模式

常见网络信号模式包含:MII、RMII、GMII、RGMII、SGMII。每种模式包含的信号线数量和控制线数量各不相同,通讯速度也不相同:MII、RMII为百兆网络,GMII、RGMII、SGMII为千兆网络。区别和关系可以参考表2.2和图2.5。各模式工作原理可根据我们的视频进行详细了解。

常见网络模式区别

网络变压器

ELF 1使用的RJ45内部集成了网络变压器,其4、5脚位中心抽头引脚,该引脚有两种接法:电流型,中心抽头直接接电源;电压型,中心抽头通过100nF电容接地(ELF 1的接法,引脚4、5接100nF电容C87和C88)。

实际使用中需要如何处理中心抽头需要根据PHY芯片的驱动类型确定。

四、PHY芯片电路原理说明

ELF 1开发板最多支持两路百兆网口,底板(图2.5)和扩展板(图2.6)上各一路。ELF 1使用两个型号为KSZ8081RNB的PHY芯片实现网络通信,通过对前几段内容的了解可以分析出,KSZ8081RNB是通过MDIO总线挂载到ELF 1上的,MDIO总线对应芯片的11、12引脚主要负责通过配置PHY芯片KSZ8081RNB的寄存器控制PHY芯片的网口速率、网口双工模式、自协商使能等功能。KSZ8081RNB的数据接口是通过RMII和ELF 1连接,并传输网络数据的。

ENET_PHYAD0、ENET_PHYAD1和ENET_RXD1在芯片上电复位时会锁存一个电平状态作为芯片的PHY地址,在芯片上电后,其数据被锁存到相应寄存器中,而后恢复芯片的默认功能,不会影响通信。由于该款芯片有三条地址线,因此理论上同一块板子最多可以挂载7颗(001—111。000作为广播地址,是不可以被使用的);

ENET_CRS_DV、ENET_CFG1、ENET_CFG0是芯片接口模式选择, ELF 1设置为100;

底板网口原理图

扩展板网口原理图

网口电路设计指南

(1)ELF 1可支持两路百兆网。

(2)设计网口电路时8081的10号引脚必须连接6.49K_%1精度的电阻,否则可能会影响网口正常工作出现芯片无法挂载、网口不Link等问题。

(3)MDIO以及RMII接口的引脚要注意电平匹配,核心板为3.3V电平。

(4)MDIO总线上需要加上拉电阻阻值可以根据实际情况调整。

网口PCB设计指南

(1)MDIO总线上挂载多个PHY芯片时,使用串联方式,不要分叉布线

(2)RGMII接口分为发送信号,接收信号和控制信号,各组阻抗控制在50Ω±10%

(3)发送信号和接收信号,布线长度不超过100mm,组内信号长度误差不超过2.54mm

(4)时钟预留对地电容,方便后期调试

(5)MDI接口采用差分布线,阻抗100Ω±10%

(6)MDI组内差分误差不超过0.12mm

(7)芯片内部DCDC连接的功率电感要靠近芯片保证回路最短,并且保证地回路的完整;

(8)数据线上预留的串联电阻需要靠近源端放置;

(9)保护器件建议放置在变压器内侧,在变压器和PHY之间,靠近变压器;

(10)供电部分要考虑电流的大小,线宽尽量宽一点。要有足够的载流能力,滤波电容的位置尽量靠近芯片。

(11)网口 RJ45 在布线时要注意进行隔离地。

网口问题排查思路

在遇到网口问题时排查网口问题首先要明确问题点,网口不通的情况下首先要看 PHY 有没有成功挂载上,可通过是否可以启动网卡来判断,如果根本看不到设备节点或者在输入启动网卡的命令后报错,找不到 PHY 芯片说明 PHY 芯片没有成功挂载。如果可以正常启动网卡说明 PHY 可以成功挂载。

如果 PHY 没挂载上排查思路如下:

(1)首先看是否有缺件、少件,原件焊接错误的情况,PHY 芯片焊接是否过关等。

(2)检查各路供电是否正常,电源是否稳定,是否有明显压降、跌坑现象。滤波电容容量是否达标。

(3)检查复位电路是否有复位动作,复位后是否可以正常抬起复位信号,复位信号低电平的维持时间是否满足 PHY 的要求。

(4) 检查芯片的时钟频率、幅值、以及信号质量是否满足手册要求。不同类型的时钟输入方式原理是否正确。

(5)检查 PHY 芯片的地址、模式、电平配置等参数设置的上下拉电阻是否正常。

(6)检查 MDIO 总线的波形幅值、斜率、是否正常,振铃是否严重,是否有下降沿压降不到 0 的现象,在 MDC 的上升沿来临时 MDIO 的电平维持时间是否满足手册要求等问题。

先将芯片成功挂载后再看网口通不通。如果经过以上检测排查成功将 PHY 芯片挂载但是网口还是不通,那就需要继续检查数据接口部分。根据不同接口的规定来对数据接口的总线进行检查。

(1)按照具体的接口形式检查数据线连接是否正确,尤其是在使用MII,RMII,GMII,RGMII时的线序。检查参考时钟波形是否正常。

(2)检查网络变压器的中心抽头的接法是否与PHY 规定的网络变压器的驱动类型相对应。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

又重新搭了个个人博客

发表于 2024-04-26

哈喽大家好,我是咸鱼。

前段时间看到一个学弟写了篇用 Hexo 搭建博客的教程,心中沉寂已久的激情重新被点燃起来。(以前搞过一个个人网站,但是因为种种原因最后不了了之)

于是花了一天时间参考教程搭了个博客网站,我的博客网址是:xxxsalted.github.io/

下面是相关教程。

环境准备

俗话说:”工欲善其事必先利其器”,在搭建个人博客之前我们先要把环境准备好。

  • Github 准备

作为全球最大的程序员同性交友网站(不是),我们首先创建一个 Github 账号,官网地址:github.com

然后我们下载并安装 Git,官网地址: git-scm.com/downloads

毕竟是国外地址,我们访问起来可能比较慢,这时候我们可以访问下面的地址来下载

1
bash复制代码https://registry.npmmirror.com/binary.html?path=git-for-windows/v2.42.0.windows.2/

  • Node.js 准备

Node.js 简单来讲是一个 JavaScript 的运行环境,让我们的 JavaScript 代码不需要在浏览器上也能运行。

下载安装 Nodejs:nodejs.cn/download/

  • 环境配置

下载安装好 Git 和 Node.js 之后,我们在键盘上按下 win+R 键,输入 cmd。

然后在弹出的 cmd 窗口中输入下面的命令,来检验是否安装成功

1
2
3
bash复制代码git --version

npm version
  • 下载 cnpm

npm 是 Node.js 的安装包管理器,但是由于限制我们有时候通过 npm 下载东西会特别的慢,这时候我们就需要换一个国内的下载源并且使用 cnpm 来管理 Node.js。

1
bash复制代码npm install -g cnpm --registry=https://registry.npmmirror.com

初始化博客

  • 安装 Hexo

接下来我们要安装 Hexo ,还是在刚刚弹出的 cmd 窗口中输入:

1
bash复制代码cnpm install -g hexo
  • 创建新文件夹

我们在电脑任意位置创建一个新文件夹(最好别放在 C 盘下且文件名是英文),这个文件夹将用于储存你网站的静态文件。

比如说我的新建文件夹路径如下:

1
bash复制代码E:\myblog

然后右键鼠标,打开Git Bash Here,并依次输入以下命令:

1
2
3
4
5
6
7
8
9
csharp复制代码
# 初始化 hexo
hexo init

# 生成网页文件
hexo generate

# 部署到本地上
hexo server

在执行命令的时候出现权限相关问题(npm ERR! { Error: EPERM: operation not permitted, mkdir 'C:\Program Files\nodejs\node_cache\_locks'),可以参考下面这篇文章来解决:

www.cnblogs.com/yinxiangzhe…

然后你会发现你的目录下面多了很多文件:

接着打开下面网址验证一下:

1
bash复制代码http://localhost:4000/

如果你想停止运行,可以在上面的 bash 命令窗口中执行 Ctrl+C 命令。

到现在我们的个人博客就完成了一半了!

托管至 GitHub

  • 仓库创建

我们登录自己的 Github ,然后在 Github 中新建一个仓库。

注意: Repository name一定要按照图片上的格式填写,其他的默认即可。

  • 设置免密登录

为了方便我们本地与 Github 互相通信,我们需要设置 SSH 免密登录。

首先找到你的 Github 邮箱和用户名,然后在你个人博客的文件夹下右键鼠标,打开Git Bash Here,并依次输入以下命令:

1
2
3
bash复制代码git config --global user.name "github 用户名"

git config --global user.email "github 邮箱"

然后我们检验一下:

1
2
bash复制代码git config user.name
git config user.email

接着我们在键盘上按下 win+R 键,输入 cmd,在 cmd 窗口里面输入下面的命令:

1
2
bash复制代码# 检查本机是否已经存在 SSH 密钥
cd ~/.ssh

如果提示:No such file or directory 则输入如下代码,生成 SSH 文件:

1
bash复制代码ssh-keygen -t rsa -C "github 邮箱地址"

然后连续敲 3 次回车,最终会生成一个文件在“用户”目录下,打开 “用户” 目录(C:\用户\用户名.ssh)并找到id_rsa.p ub文件。

用“记事本”打开并复制里面的内容,在 Github 主页的右上角进入 Setting -> 左侧菜单栏 “SSH and GPG keys” -> New SSH Key -> 填写你获得的 SSH Key。

部署博客

到了这一步,我们就可以正式地把博客部署到 Git 上了,进入我们前面创建的博客文件夹,然后找到配置文件 _config.yml。

下面是我的配置文件的部分内容,大家可以参考一下

部署路径部分:

1
2
3
4
bash复制代码deploy:
type: git
repo: 你的 git 仓库地址
branch: master

网站相关部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
bash复制代码# Site
title: Amoon's blog
subtitle: ''
description: ''
keywords:
author: Ammon
language: zh-CN
timezone: 'Asia/shanghai'


## Set your site url here. For example, if you use GitHub Page, set url as 'https://username.github.io/project'
url:https://xxxsalted.github.io
root: /
permalink: :year/:month/:day/:title/
permalink_defaults:
pretty_urls:
trailing_index: true # Set to false to remove trailing 'index.html' from permalinks
trailing_html: true # Set to false to remove trailing '.html' from permalinks

我们还需要安装一个插件,打开 Git bash 窗口

1
2
bash复制代码# 安装自动部署工具
cnpm install hexo-deployer-git --save

然后依次执行下面的命令

1
2
3
4
5
6
7
8
bash复制代码# 清除 public 缓存文件
hexo clean

# 生成新的网站静态文件到默认设置的 public 文件夹,也可以用 hexo g 命令
hexo generate

# 部署到 github,也可以用 hexo d 命令
hexo deploy

当然,在生成静态文件之后你可以使用 hexo s 命令生成一个本地的 hexo 网站来做相关检验和测试,如果没什么问题就使用 hexo d 命令将其部署到 git 上

最后我们的博客就搭建成功啦!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

热度排名系统背后的技术:如何用Redis实现

发表于 2024-04-26

热度排名系统背后的技术:如何用Redis实现

在当今的互联网应用中,热度排名系统是一种非常常见的功能,它可以帮助用户发现热门内容,同时也为内容的推广和管理提供了一种有效的方式。在本篇技术博客中,我们将通过一个简单的案例,介绍如何使用Redis实现一个热度排名系统。

一、Redis简介

Redis是一个开源的内存数据结构服务器,它可以通过键值对的方式来处理数据。由于其出色的性能和丰富的数据类型,Redis被广泛应用于缓存、消息队列、排行榜等场景。

二、案例需求

假设我们需要为一个字符串集合中的每个字符实现一个热度排名,用户可以查看哪些字符的热度最高。

三、环境准备

首先,确保你的环境中安装了Node.js和Redis。接下来,安装ioredis模块,这是一个Node.js的Redis客户端库,它提供了一个简单易用的API来操作Redis。

1
复制代码npm install ioredis

四、实现热度排名

我们将通过以下步骤来实现热度排名:

  1. 创建Redis客户端实例:使用ioredis模块连接到Redis服务器。
  2. 生成随机数和随机字符:用于模拟字符的初始热度值和随机选择字符。
  3. 异步处理函数:定义一个异步函数jihe来处理热度值的更新和查询。

五、代码实现

以下是使用ioredis模块实现热度排名的完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
javascript复制代码const Redis = require('ioredis');
const redis = new Redis();

let num = Math.floor(Math.random() * 30) + 1; // 生成随机数1到30
let str = 'abcdefghkjlwer'; // 定义一个字符串
let strtap = Math.floor(Math.random() * 11); // 生成一个0到10的随机整数作为字符串的索引

async function jihe() {
 try {
   let data = await redis.zscore('hots', str[strtap]); // 查询有序集中特定成员的分数

   if (data !== null) {
     await redis.zincrby('hots', 1, str[strtap]); // 成员存在,分数增加1
     console.log(`${str[strtap]} 分数增加1`);
  } else {
     await redis.zadd('hots', num, str[strtap]); // 成员不存在,添加成员及其分数
     console.log(`写入新成员 ${str[strtap]},初始分数为 ${num}`);
  }

   let paixu = await redis.zrevrange('hots', 0, -1, 'WITHSCORES'); // 获取所有成员按分数从高到低排序的列表

   let obj = paixu.reduce((acc, val, index) => {
     if (index % 2 === 0) {
       acc[val] = parseFloat(paixu[index + 1]);
    }
     return acc;
  }, {});

   console.log(obj); // 打印转换后的对象
} catch (error) {
   console.error('An error occurred:', error);
}
}

jihe(); // 调用异步函数jihe

六、运行结果

运行上述代码后,控制台将输出每个字符的热度排名。初始时,会随机选择一个字符并为其分配一个初始热度值。随后,该字符的热度值每次增加1,模拟热度上升。

七、总结

通过本案例,我们学习了如何使用Redis的有序集合(sorted set)来实现一个简单的热度排名系统。这种系统可以轻松扩展,用于更复杂的应用场景,如社交媒体热门帖子、商品销量排行等。

Redis的高性能和低延迟特性使其成为实现这类系统的理想选择。随着对Redis更深入的探索,你将发现更多令人兴奋的可能性。


希望这篇技术博客能够帮助你理解如何使用Redis来实现热度排名系统。如果你有任何问题或想要了解更多关于Redis的内容,请随时留言讨论。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

释放Stable Diffusion 无限可能

发表于 2024-04-26

最近在整理大语言模型的系列内容,Stable Diffusion 是我下一篇博客的主题。关注 Stable Diffusion,是因为它是目前最受欢迎和影响力最大的多模态生成模型之一。Stable Diffusion 于 2022 年 8 月发布,主要用于根据文本的描述产生详细图像,当然它也可以应用于其他任务,比如内补绘制、外补绘制,以及在提示词指导下,对现有图形进行风格化或转变。Stable Diffusion 模型版本正在快速迭代,开源生态也在逐步扩展,对行业生产力带来了巨大的变革。如今出现了很多的开源软件,通过调用 Stable Diffusion 来支持各种功能,并提供简洁的用户界面以方便设计师和爱好者使用。然而 Stable Diffusion 的大规模部署不是一件简单的事情,需要考虑多种因素:

亚马逊云科技开发者社区为开发者们提供全球的开发技术资源。这里有技术文档、开发案例、技术专栏、培训视频、活动与竞赛等。帮助中国开发者对接世界最前沿技术,观点,和项目,并将中国优秀开发者或技术推荐给全球云社区。如果你还没有关注/收藏,看到这里请一定不要匆匆划过,点这里让它成为你的技术宝库!

  • 个性化和可扩展性:Stable Diffusion 生态广泛,仅广泛使用的基础模型就有 1.5 ,XL 和 2 三个版本。同时大量的插件和附加模型(如 LoRA ,ControlNet 等)可被附加到基础模型上。还可针对特定工作场景(如人像生成)进行精细化调优。这些模型和插件也都在不断迭代。在大规模部署时,针对不同的工作场景可以使用不同的模型进行模型推理,这对整个系统可扩展性要求很高。
  • 推理速度:Stable Diffusion 的基础模型是在亚马逊云计算服务上使用 256 个 NVIDIA A100 GPU 训练,共花费 15 万个 GPU 小时,成本为 60 万美元。而这只是基础模型,对于调用基础模型并加载个性化数据进行推理的应用场景来说,需要使用加速计算芯片(如 NVIDIA GPU 或亚马逊云科技的 Inferentia 芯片)来提升单任务推理速度,降低用户等待时间,提升用户体验。
  • 弹性伸缩:在多种业务场景中,使用者的请求有较大的不确定性。从平衡成本的角度出发,需要考虑在请求较多时快速增加推理实例数量以应对请求,而在请求较少时降低实例数量以降低成本。
    1.png

上图是一个常见的大语言模型在容器集群上的部署方式,这种部署方式存在以下问题:

  1. 所有请求都是同步的。由于模型推理相对比较耗时,每个请求耗时可达几十秒,甚至几分钟。这不仅要求客户与后端之间的网络绝对稳定,在流量突增且没有限流手段时,甚至会导致系统雪崩。
  2. 常见的自动扩容策略是基于 CPU 或 GPU 利用率的指标跟踪,无法直观反应系统负载,且触发时间长,无法应对突增请求。但如果为了避免冷启动而保留大量的空闲容量,则资源在低谷期大量闲置,空置成本高昂。
  3. 在弹性伸缩拉起新实例后,还需要加载 Stable Diffusion 运行时和模型才能对外提供服务。Stable Diffusion 运行时的容器镜像普遍在 10 GB 以上,新实例下载镜像和解压耗费的时间过长,导致冷启动时间过长,大大影响使用者的体验。
  4. Stable Diffusion 模型通常使用存放在块存储或文件存储中,每次加载模型时候拉取性能受限,成本也较高。
解决方案

为了解决传统部署方式带来的业务痛点,亚马逊云科技中国区的技术团队基于使用者的需求和对多种业务场景的深刻理解提出了一套解决方案。该方案充分利用云上资源的弹性,无服务器产品的简便,和 Kubernetes 生态强大的任务调度能力,展示了如何构建端到端低成本、快速扩展的异步图像生成架构。

  • 拥抱开源生态:支持 Stable Diffusion Web UI 和 ComfyUI 两种开源的 Stable Diffusion 运行时,和基于这两种运行时的插件生态。
  • 更快速的扩容:基于 KEDA、Karpenter、Bottlerocket,提供可自定义的,任务粒度的自动扩容能力。最快可以达到 1 分钟内业务就绪,适合高并发,且对响应时间有要求的情况。
  • 大幅降低成本:通过使用 Amazon EC2 Spot 实例和 Mountpoint for Amazon S3 CSI Driver,使得计算成本相较于 EC2 标准价格可节省最高约 70%,多节点场景下的存储成本相较于其他存储方案更可节省大半。
  • 扩展非常方便:该方案是云原生架构,基于 Amazon EKS、无服务器(Serverless)和事件驱动(Event-Driven)架构,大部分组件无需自行管理,且便利扩展。
  • 可视化监控:提供调用流程详细的可视化,历史统计功能,提供完善的可观测性组件,有效缩短 MTTI(Mean Time to Identify)。
  • 自动化部署:方案使用 Amazon CDK,借助编程语言的强大表达能力和高效执行效率,仅需 30-40 分钟就可以自动完成构建。
  • Stable DiffusionWeb UI

github.com/AUTOMATIC11…

  • ComfyUI

github.com/comfyanonym…

  • KEDA

keda.sh/?trk=cndc-d…

  • Karpenter

karpenter.sh/?trk=cndc-d…

  • Bottlerocket

aws.amazon.com/cn/bottlero…

  • Amazon EC2 Spot 实例

aws.amazon.com/cn/ec2/spot…

  • Mountpoint for Amazon S3 CSI Driver

github.com/awslabs/mou…

  • Amazon EKS

aws.amazon.com/cn/eks/?trk…

  • Amazon CDK

aws.amazon.com/cn/cdk/?trk…

方案的具体工作流程如下图所示:

2.png

  1. 使用者将请求(模型,Prompt 等)发送至业务应用,业务应用将请求发送至 Amazon API Gateway 提供的 API 端点。
  2. 请求通过 Amazon Lambda 进行校验,并投送至 Amazon SNS 主题。
  3. Amazon SNS 根据请求中的运行时名称,基于请求过滤机制,将请求投送至对应运行时的 SQS 队列。
  4. 在 EKS 集群中,KEDA 会根据队列内消息数量扩充运行时的副本数。
  5. Stable Diffusion 运行时启动时会通过 Mountpoint for Amazon S3 CSI Driver,直接从 S3 存储桶中加载模型。
  6. Queue Agent 会从 Amazon SQS 队列里接收任务,并发送给 Stable Diffusion 运行时生成图像。
  7. 生成的图片由 Queue Agent 存储至 Amazon S3 存储桶中,并将完成通知投送至 Amazon SNS 主题,SNS 可将响应投送至 SQS 或其他目标中。
  8. 该解决方案提供完整的可观测性和管理组件,包含基于 CloudWatch 和 ADOT 的数值监控和日志,以及基于 Amazon X-Ray 的全链路跟踪。
  9. 该解决方案通过基于 Amazon CDK 的基础设施即代码部署方式进行部署和配置,通过 IAM 和 API Key 提供安全和访问控制。
方案的生产实践
API 接入和事件分发

该解决方案使用基于 Amazon API Gateway 的 API 端点对外提供服务,并基于 Amazon SNS,Amazon SQS 进行任务分发。用户将请求(模型,Prompt 等)发送至 Amazon API Gateway 提供的 API 端点,请求通过 Amazon Lambda 进行校验,并投送至 Amazon SNS 主题。Amazon SNS 根据请求中的运行时名称,将请求投送至对应运行时的 SQS 队列。

3.png

使用 Queue Agent 组件进行异步推理

部署时,每个运行时有独立的 Amazon SQS 队列以接收请求。而考虑到不同的的 Stable Diffusion 运行时的 API 接口和行为不同,对异步处理和队列的支持也不同,故我们构建了名为 Queue Agent 的组件。Queue Agent 会从 Amazon SQS 队列里接收任务,并转换成对应运行时的 API 请求格式,发送给 Stable Diffusion 运行时生成图像。Queue Agent 还兼顾了从 Internet 或 Amazon S3 上获取图像,和任务跟踪,错误处理的功能,将更多任务从 Stable Diffusion 运行时解耦,从而支持更多的 Stable Diffusion 运行时。

4.png

利用 KEDA 自动扩缩容器副本

当 Amazon SQS 队列中积压过多消息时,需要增加容器副本数以快速处理这些积压任务。我们使用开源的 Kubernetes Event-Driven Autoscaling(简称 KEDA)进行弹性伸缩。KEDA 会轮询 SQS 队列的队列内消息数量,并根据消息数量和预设的阈值来决定运行时的副本数。由于 KEDA 持续监控队列,在没有负载的时候,可以不启动任何运行时,而在有任务时再启动运行时实例,有效节省成本。除了 SQS 队列长度外,KEDA 还支持根据许多其他指标(被称为 Scaler)进行扩缩容。例如部分业务场景有明确的业务高峰期,则可以使用 Cron 在预估的业务高峰期提前进行扩容,而在业务低谷期不预置资源。通过多个 Scaler 的组合,可以支持更多的工作场景。

  • Kubernetes Event-Driven Autoscaling

keda.sh/?trk=cndc-d…

5.png

利用 Karpenter 自动扩缩实例

在运行时副本数扩展后,需要节点弹性伸缩组件创建实例承载新的运行时副本。传统的节点弹性伸缩组件是 Kubernetes 社区的 Cluster Autoscaler。而亚马逊云科技开源的弹性伸缩组件 Karpenter 可以智能选择实例类型和购买方式。根据同时启动的 Pod 数量,选择最合适的实例类型。例如在同时启动的 Pod 数量很多时,可以选择包含 2 张或 4 张 GPU 的实例,减少实例启动时间对冷启动时间的影响。Karpenter 使用 EC2 Fleet,实例启动速度快于基于 Autoscaling Group 的节点组。在推理任务的成本划分中,GPU 实例的成本占了总成本的 95% 以上。Karpenter 支持创建 Spot 实例,且在创建时会智能选择回收几率最低的实例类型,降低 Spot 实例回收对任务造成的影响。

  • Cluster Autoscaler

github.com/kubernetes/…

6.png

基于 Bottlerocket 实现容器镜像缓存

在存储方面,方案针对 Stable Diffusion 运行时和模型使用了不同的存储。新创建的实例采用了 Bottlerocket 操作系统。Bottlerocket 是一款基于 Linux 的开源操作系统,由亚马逊云科技专门构建用于运行容器。Bottlerocket 仅包括运行容器所需的基本软件,启动速度极快。而在 Stable Diffusion 运行时镜像方面,由于 Docker 拉取镜像是单线程,且拉取完成后还需要解压缩,导致镜像拉取耗时较长。我们将 Stable Difussion 运行时镜像预加载到磁盘,并抓取 EBS 快照。在每次创建新实例时,EBS 快照会恢复到新实例的数据盘,新的实例在加入 EKS 集群后可以立即启动容器,节省拉取镜像和解压的时间。

7.png

利用对象存储支撑大量模型动态读取

常见的模型存储会使用块存储或者文件存储,但对于模型读取这种连续读的工作负载,挂载对象存储存在优势。Mountpoint for Amazon S3 可以将 S3 存储桶挂载到文件系统。在读取模型时支持多线程读取,充分利用网卡资源。在 Amazon S3 上的性能测试显示,使用 Mountpoint for Amazon S3 加载 Stable Diffusion 1.5 的基础模型仅需 14.5 秒,进一步缩减冷启动耗时。

8.png

推送处理结果

生成的图片由 Queue Agent 存储至 Amazon S3 存储桶中,并将完成通知投送至 Amazon SNS 主题。不论任务成功或失败,运行时的返回也会存储到 S3 存储桶,方便进行调试。而只有任务真正处理完成后,Queue Agent 才会从队列中删除对应的任务。当出现意外状况(如 SD 运行时崩溃)时,任务会在设定的一段时间(被称为 Visibility Timeout)后,重新出现在队列中,其他运行时可以重试这个任务。Amazon SQS 这个“两阶段确认”的特性有效的提升了整体方案的可靠性。

9.png

X-Ray 端到端全链路追踪

而在可观测性方面,除了传统的基于 CloudWatch 数值监控和日志之外,通过分布式跟踪,我们能了解每一步的耗时和性能瓶颈,方便运维人员快速排查问题。

10.png

方案的性能与成本

这套方案已经在多个 Amazon Web Services 使用者的生产环境上部署。而我们在 Amazon Web Services 美国西部(俄勒冈)区域的内部实测,端到端的扩容速度(即请求发送到处理完成)最低可达 60 秒,完全可以有效应对需要快速扩容的场景。推理成本由于使用了 Spot 实例最高可节省 70%,有效降低了方案的整体成本,适应大规模部署。

开始使用

目前这套方案已经开源,支持部署到亚马逊云科技海外和中国区域。您可以从 github.com/aws-samples… 获取源代码并开始在您的环境中部署。

资源与参考文献:

  • aws.amazon.com/cn/solution…
  • aws-samples.github.io/stable-diff…

文章来源:dev.amazoncloud.cn/column/arti…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…8910…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%