2023年3月

文章首发于公众号:BiggerBoy。欢迎关注。

往期文章推荐

大坑!隐式转换导致索引失效...


高性能分布式限流:Redis+Lua真香!


MySQL索引知识点&常见问题汇总
联合索引在B+树上的存储结构及数据查找方式


Redis分布式锁实战
Mybatis第三方PageHelper插件分页原理
MySQL索引底层原理

一、问题交代

当我们使用dubbo作为服务间通信的组件时,在后期的系统维护中可能会因为业务需要,服务提供者某些接口需要升级,对应的服务消费者配合作相应的修改,测试通过后一起发版上线即可。但是在这个过程中,有很多需要注意的点,不妨来梳理一下以作记录,希望对此不清楚的开发者有所帮助。
再次申明一下我们的需求吧。假设服务A提供了一个服务IHelloService,其中有一个方法sayHello,服务B、C、D...都对此有依赖,假设服务A提供的该接口已上线且被服务B、C、D正常消费,线上运行稳定。

先将服务提供者和服务消费者分别启动,此时消费方正常消费服务。

api:

public interfaceIHelloService {
Result sayHello(ModelParent param);
}

服务提供者:

@Componentpublic class HelloServiceImpl implementsIHelloService {
@Override
publicResult sayHello(ModelParent param) {
MyDto dto
= newMyDto();
dto.setMsg(
"hello," +param.getName());returnResult.success(dto);
}
}

服务消费者:

@Servicepublic classMyService {
@DubboReference(group
= "test",version = "1.0",registry = "registry2")privateIHelloService helloService;publicResult test(ModelParent param) {returnhelloService.sayHello(param);
}
}

二、接口迭代升级方案

过了几个月,服务B接到产品需求,为了满足需求,对服务A的IHelloService.sayHello提出新的要求,因为本次需求不涉及服务C、D的改动,所以服务A对该服务还需要满足服务C、D调用时保持老逻辑不变,因此服务A需要格外注意服务的兼容性。

注意:这里说的服务A、B、C...是一个个应用或系统。应用或系统对外提供多种服务,例如用户服务对外提供用户信息查询服务、用户注册服务等。

假设服务A和服务B的开发人员确认完需求后,服务A的开发人员想到的接口升级方案如下:

  1. 服务A新提供一个sayHelloV2方法或新服务IHelloServiceV2,提供新的业务逻辑,服务B调这个新方法或新服务,原来的sayHello保留,服务C、D继续调用且本次不用修改上线。
  2. 服务A修改IHelloService.sayHello,在原来的入参中新增字段,用于区分走新逻辑还是老逻辑。在这种方式下就需要特别注意了,因为入参增加了参数,要考虑是否会影响本次需求不涉及的服务C和服务D。

2.1 在原入参对象中新增字段。服务C、D对于新增的字段肯定传的是null,所以可以用null标识走老逻辑,用其他值比如1标识走新逻辑。

2.2 创建新的入参对象 ModelChildren 增加新字段,且该入参对象继承原入参对象。此时又分不同的情况。

a.接口方法入参对象保持原入参对象不变,即,方法签名不变,仍然是Result sayHello(ModelParent param)。仅需改动有新需求的服务B的代码,将入参对象改为新入参对象,由于该对象继承了原入参对象,所以不会有问题,但服务A需要判断传的是父类 ModelParent 还是子类 ModelChildren,并从子类 ModelChildren 中获取新字段。

b.接口方法入参对象改为新入参对象,即,方法签名修改,入参改为新对象Result sayHello(ModelChildren param)。此时,所有消费该服务的消费方均需要修改,如果消费方不改,消费方调用该方法时会抛异常
org.apache.dubbo.remoting.RemotingException: Fail to decode request due to:java.lang.IllegalArgumentException:Service not found:com.biggerboy.api.IHelloService, sayHello

三、 总结

方案 参数列表 优点 缺点
新增方法sayHelloV2 - 1.新老逻辑分开,兼容老版本2.对本次不涉及的服务消费者友好,因为可以不用改动代码继续使用老方法 1.额外创建新的服务方法,对内存消耗略微增加(可忽略)
新增服务IHelloServiceV2 - 1.新老逻辑分开,兼容老版本2.对本次不涉及的服务消费者友好,因为可以不用改动代码继续使用老服务 1.额外创建新的服务,对内存消耗略微增加(可忽略)
修改入参ModelParent增加字段 不变 1.兼容老版本,对不涉及的服务无影响2.不用新增服务,不会额外消费内存 1.程序中需使用新增的字段区分新老逻辑
新增入参子类ModelChildren增加字段并继承原入参ModelParent 不变 1.兼容老版本,对不涉及的服务无影响2.不用新增服务,不会额外消费内存3.新增加了入参不会影响其他同样使用ModelParent作为参数的服务 1.程序中需使用新增的字段区分新老逻辑2.额外创建新的类,对内存消耗略微增加(可忽略)
新增入参子类ModelChildren增加字段并继承原入参ModelParent 改为子类 1.不用新增服务,不会额外消费内存2.新增加了入参不会影响其他同样使用ModelParent作为参数的服务 1.程序中需使用新增的字段区分新老逻辑2.额外创建新的类,对内存消耗略微增加(可忽略)3.需修改参数列表,当方法调用链较长且使用入参对象作为参数列表时,需修改的方法较多4.不兼容老版本,本次不涉及的服务消费者也需修改,负责报错

你们公司是如何做的呢?欢迎评论区一起讨论~

End

基于Go/Grpc/kubernetes/Istio开发微服务的最佳实践尝试 - 1/3

基于Go/Grpc/kubernetes/Istio开发微服务的最佳实践尝试 - 2/3

基于Go/Grpc/kubernetes/Istio开发微服务的最佳实践尝试 - 3/3

项目地址:
https://github.com/janrs-io/Jgrpc


转载请注明来源:
https://janrs.com/br6f


Jgrpc

本项目为基于
Go/Grpc/kubernetes/Istio
开发微服务的最佳实践提供参考。

并基于
Jenkins/Gitlab/Harbor
实现了
CICD

并使用
grpc-gateway
作为网关代理。

本最佳实践分为三个部分:

  1. 创建一个
    pingservice
    的微服务
  2. 创建一个
    pongservice
    的微服务
  3. 基于
    Jenkins/Gitlab/Harbor
    创建
    CICD
    部署流程并部署到
    k8s/istio

在这一部分中,我们将创建
pongservice
微服务。


前提

假设已经安装了以下工具:

  • protoc-gen-grpc-gateway
  • protoc-gen-openapiv2
  • protoc-gen-go
  • protoc-gen-go-grpc
  • buf
  • wire

下面是安装这些工具的教程地址:

protoc-gen-grpc-gateway
&
protoc-gen-openapiv2
&
protoc-gen-go
&
protoc-gen-go-grpc
这四个工具的安装教程请查看:
install protoc-gen* tools

wire
wire
工具的安装教程请查看:
install wire tool

buf
buf
工具的安装教程请查看:
install buf tool

项目结构

这部分最终的目录结构如下:

Jgrpc
├── devops
├── istio-manifests
├── kubernetes-manifests
└── src
    └── pongservice
        ├── buf.gen.yaml
        ├── cmd
        │   ├── main.go
        │   └── server
        │       ├── grpc.go
        │       ├── http.go
        │       ├── run.go
        │       ├── wire.go
        │       └── wire_gen.go
        ├── config
        │   ├── config.go
        │   └── config.yaml
        ├── genproto
        │   └── v1
        │       ├── gw
        │       │   └── pongservice.pb.gw.go
        │       ├── pongservice.pb.go
        │       └── pongservice_grpc.pb.go
        ├── go.mod
        ├── go.sum
        ├── proto
        │   ├── buf.lock
        │   ├── buf.yaml
        │   └── v1
        │       ├── pongservice.proto
        │       └── pongservice.yaml
        └── service
            ├── client.go
            └── server.go

14 directories, 20 files


开始

创建项目的整体目录结构如下:

Jgrpc
├── devops
├── istio-manifests
├── kubernetes-manifests
├── src

创建 pongservice 微服务

创建基本目录


src
目录下创建名为
pongservice
的微服务,目录结构如下:

pongservice
├── cmd
│   └── server
├── config
├── proto
│   └── v1
└── service

6 directories, 0 files

生成代码和文件

生成 buf.yaml

在 pongservice/proto 目录下执行以下命令:

buf mod init

此命令创建一个名为
buf.yaml
的文件,位于
ponservice/proto
目录中。
buf.yaml
的代码如下:

version: v1
breaking:
  use:
    - FILE
lint:
  use:
    - DEFAULT


version

breaking
之间添加如下依赖代码:

deps:
  - buf.build/googleapis/googleapis

请查看添加此依赖代码的原因:
https://github.com/grpc-ecosystem/grpc-gateway#3-external-configuration

添加依赖代码后完整的
buf.yaml
文件如下:

version: v1
deps:
  - buf.build/googleapis/googleapis
breaking:
  use:
    - FILE
lint:
  use:
    - DEFAULT

然后在
pongservice/proto
目录下执行如下命令:

buf mod update

执行命令后会生成一个
buf.lock
文件,代码如下:

# Generated by buf. DO NOT EDIT.
version: v1
deps:
  - remote: buf.build
    owner: googleapis
    repository: googleapis
    commit: 463926e7ee924d46ad0a726e1cf4eacd

生成 pongservice.proto


pongservice/proto/v1
目录中使用以下代码创建一个名为
pongservice.proto
的原型文件:

syntax = "proto3";

package proto.v1;

option go_package = "github.com/janrs-io/Jgrpc/src/pongservice/genproto/v1";

service PongService {
  rpc Pong(PongRequest) returns(PongResponse){}
}

message PongRequest {
  string msg = 1 ;
}

message PongResponse {
  string msg = 1;
}

生成 pongservice.yaml


pongservice/proto/v1
目录中使用以下代码创建一个名为
pongservice.yaml
的原型文件:

type: google.api.Service
config_version: 3

http:
  rules:
    - selector: proto.v1.PongService.Pong
      get: /pong.v1.pong

生成 buf.gen.yaml


pongservice
目录中使用以下代码创建一个名为
buf.gen.yaml
的 yaml 文件:

version: v1
plugins:
  - plugin: go
    out: genproto/v1
    opt:
      - paths=source_relative
  - plugin: go-grpc
    out: genproto/v1
    opt:
      - paths=source_relative
  - plugin: grpc-gateway
    out: genproto/v1/gw
    opt:
      - paths=source_relative
      - grpc_api_configuration=proto/v1/pongservice.yaml
      - standalone=true


pongservice
目录中,执行以下命令:

buf generate proto/v1

执行命令后,会在
pongservice
目录下自动创建一个genproto目录,该目录下有以下文件:

genproto
└── v1
    ├── gw
    │   └── ponservice.pb.gw.go
    ├── ponservice.pb.go
    └── ponservice_grpc.pb.go

2 directories, 3 files

生成 go.mod


pongservice
目录中创建
go.mod

go mod init github.com/janrs-io/Jgrpc/src/pongservice && go mod tidy

生成 config.yaml


pongservice/config
目录下,创建
config.yaml
文件并添加以下代码:

# grpc config
grpc:
  host: ""
  port: ":50051"
  name: "pong-grpc"

# http config
http:
  host: ""
  port: ":9001"
  name: "pong-http"

生成 config.go


pongservice/config
目录下,创建
config.go
文件并添加以下代码:

package config

import (
	"net/http"

	"github.com/spf13/viper"
	"google.golang.org/grpc"
)

// Config Service config
type Config struct {
	Grpc Grpc `json:"grpc" yaml:"grpc"`
	Http Http `json:"http" yaml:"http"`
}

// NewConfig Initial service's config
func NewConfig(cfg string) *Config {

	if cfg == "" {
		panic("load config file failed.config file can not be empty.")
	}

	viper.SetConfigFile(cfg)

	// Read config file
	if err := viper.ReadInConfig(); err != nil {
		panic("read config failed.[ERROR]=>" + err.Error())
	}
	conf := &Config{}
	// Assign the overloaded configuration to the global
	if err := viper.Unmarshal(conf); err != nil {
		panic("assign config failed.[ERROR]=>" + err.Error())
	}

	return conf

}

// Grpc Grpc server config
type Grpc struct {
	Host   string `json:"host" yaml:"host"`
	Port   string `json:"port" yaml:"port"`
	Name   string `json:"name" yaml:"name"`
	Server *grpc.Server
}

// Http Http server config
type Http struct {
	Host   string `json:"host" yaml:"host"`
	Port   string `json:"port" yaml:"port"`
	Name   string `json:"name" yaml:"name"`
	Server *http.Server
}

然后在
pongservice
目录中再次运行
go mod tidy

生成 client.go


pongservice/service
目录下,创建
client.go
文件并添加以下代码:

package service

import (
	"context"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"

	"github.com/janrs-io/Jgrpc/src/pongservice/config"
	v1 "github.com/janrs-io/Jgrpc/src/pongservice/genproto/v1"
)

// NewClient New service's client
func NewClient(conf *config.Config) (v1.PongServiceClient, error) {

	serverAddress := conf.Grpc.Host + conf.Grpc.Port
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	conn, err := grpc.DialContext(ctx, serverAddress, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		return nil, err
	}
	client := v1.NewPongServiceClient(conn)
	return client, nil

}


pongservice/service
目录下,创建
server.go
文件并添加以下代码:

package service

import (
	"context"

	"github.com/janrs-io/Jgrpc/src/pongservice/config"
	v1 "github.com/janrs-io/Jgrpc/src/pongservice/genproto/v1"
)

// Server Server struct
type Server struct {
	v1.UnimplementedPongServiceServer
	pongClient v1.PongServiceClient
	conf       *config.Config
}

// NewServer New service grpc server
func NewServer(conf *config.Config, pongClient v1.PongServiceClient) v1.PongServiceServer {
	return &Server{
		pongClient: pongClient,
		conf:       conf,
	}
}

func (s *Server) Pong(ctx context.Context, req *v1.PongRequest) (*v1.PongResponse, error) {
	return &v1.PongResponse{Msg: "response pong msg:" + req.Msg}, nil
}

生成 run server 文件


pongservice/cmd/server
目录下,创建以下四个文件:

  • grpc.go
  • http.go
  • run.go
  • wire.go

将以下代码添加到
grpc.go
文件中:

package server

import (
	"fmt"
	"log"
	"net"

	"google.golang.org/grpc"

	"github.com/janrs-io/Jgrpc/src/pongservice/config"
	v1 "github.com/janrs-io/Jgrpc/src/pongservice/genproto/v1"
)

// RunGrpcServer Run grpc server
func RunGrpcServer(server v1.PongServiceServer, conf *config.Config) {

	grpcServer := grpc.NewServer()
	v1.RegisterPongServiceServer(grpcServer, server)

	fmt.Println("Listening grpc server on port" + conf.Grpc.Port)
	listen, err := net.Listen("tcp", conf.Grpc.Port)
	if err != nil {
		panic("listen grpc tcp failed.[ERROR]=>" + err.Error())
	}

	go func() {
		if err = grpcServer.Serve(listen); err != nil {
			log.Fatal("grpc serve failed", err)
		}
	}()

	conf.Grpc.Server = grpcServer

}

将以下代码添加到
http.go
文件中:

package server

import (
	"context"
	"fmt"
	"net/http"

	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"

	"github.com/janrs-io/Jgrpc/src/pongservice/config"
	v1 "github.com/janrs-io/Jgrpc/src/pongservice/genproto/v1/gw"
)

// RunHttpServer Run http server
func RunHttpServer(conf *config.Config) {

	mux := runtime.NewServeMux()
	opts := []grpc.DialOption{
		grpc.WithTransportCredentials(insecure.NewCredentials()),
	}

	if err := v1.RegisterPongServiceHandlerFromEndpoint(
		context.Background(),
		mux,
		conf.Grpc.Port,
		opts,
	); err != nil {
		panic("register service handler failed.[ERROR]=>" + err.Error())
	}

	httpServer := &http.Server{
		Addr:    conf.Http.Port,
		Handler: mux,
	}
	fmt.Println("Listening http server on port" + conf.Http.Port)

	go func() {
		if err := httpServer.ListenAndServe(); err != nil {
			fmt.Println("listen http server failed.[ERROR]=>" + err.Error())
		}
	}()

	conf.Http.Server = httpServer

}

将以下代码添加到
run.go
文件中:

package server

import (
	"context"
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/janrs-io/Jgrpc/src/pongservice/config"
	v1 "github.com/janrs-io/Jgrpc/src/pongservice/genproto/v1"
)

// Run Run service server
func Run(cfg string) {

	conf := config.NewConfig(cfg)
	// run grpc server
	RunGrpcServer(initServer(conf), conf)
	// run http server
	RunHttpServer(conf)
	// listen exit server event
	HandleExitServer(conf)

}

// SetServer Wire inject service's component
func initServer(conf *config.Config) v1.PongServiceServer {
	server, err := InitServer(conf)
	if err != nil {
		panic("run server failed.[ERROR]=>" + err.Error())
	}
	return server
}

// HandleExitServer Handle service exit event
func HandleExitServer(conf *config.Config) {

	ch := make(chan os.Signal, 1)
	signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
	<-ch
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	conf.Grpc.Server.GracefulStop()
	if err := conf.Http.Server.Shutdown(ctx); err != nil {
		panic("shutdown service failed.[ERROR]=>" + err.Error())
	}
	<-ctx.Done()
	close(ch)
	fmt.Println("Graceful shutdown http & grpc server.")

}

将以下代码添加到
wire.go
文件中:

//go:build wireinject
// +build wireinject

package server

import (
	"github.com/google/wire"

	"github.com/janrs-io/Jgrpc/src/pongservice/config"
	v1 "github.com/janrs-io/Jgrpc/src/pongservice/genproto/v1"
	"github.com/janrs-io/Jgrpc/src/pongservice/service"
)

// InitServer Inject service's component
func InitServer(conf *config.Config) (v1.PongServiceServer, error) {

	wire.Build(
		service.NewClient,
		service.NewServer,
	)

	return &service.Server{}, nil

}


pongservice
目录中再次运行
go mod tidy

go mod tidy

然后在
pongservice
目录中执行以下 wire 命令:

wire ./...

执行
wire
命令后,将在
pongsevice/cmd/server
目录中自动创建
wire_gen.go
文件。

生成 main.go

最后一步,在
pongservice/cmd
目录下创建
main.go
文件.

package main

import (
	"flag"

	"github.com/janrs-io/Jgrpc/src/pongservice/cmd/server"
)

var cfg = flag.String("config", "config/config.yaml", "config file location")

// main main
func main() {
	server.Run(*cfg)
}

启动 service


pongservice
目录下执行以下命令启动微服务:

注意
pongservice 目录而不是 pongservice/cmd 目录中执行命令

go run cmd/main.go

启动服务后,会显示如下信息:

Listening grpc server on port:50051
Listening http server on port:9001

在浏览器中输入以下地址即可访问该服务:

127.0.01:9001/pong.v1.pong?msg=best practice

如果成功,将返回以下数据:

{
    "msg": "response pong msg:best practice"
}

总结

现在,我们已经了解了如何创建可以开发基本功能微服务的项目结构。

在接下来的部分中,我们继续创建一个名为
pingservice
的微服务,并访问我们在这部分中创建的
pongservice


转载请注明来源:
https://janrs.com/br6f

2023 年 2 月 6 日,土耳其东南部发生 7.7 级和 7.6 级地震,影响 10 个城市,截至 2 月 21 日已造成 42,000 多人死亡和 120,000 多人受伤。

地震发生几个小时后,一群程序员启动了一个 Discord 服务,推出了一个名为
afetharita
的应用程序,字面意思是
灾难地图
。该应用程序将为搜救队和志愿者提供服务,以寻找幸存者并为他们提供帮助。当幸存者在社交媒体上发布带有他们的地址和他们需要的东西 (包括救援) 的文本截图时,就需要这样一个应用程序。一些幸存者还在发布了他们需要的东西,这样他们的亲属就知道他们还活着并且需要救援。需要从这些推文中提取信息,我们开发了各种应用程序将它们转化为结构化数据,并争分夺秒的开发和部署这些应用程序。

当我被邀请到 Discord 服务时,关于我们 (志愿者) 将如何运作以及我们将做什么的问题弄得非常混乱。我们决定协作训练模型,并且我们需要一个模型和数据集注册表。我们开设了一个 Hugging Face 组织帐户,并通过拉取请求进行协作,以构建基于 ML 的应用程序来接收和处理信息。

organization

其他团队的志愿者告诉我们,需要一个应用程序来发布屏幕截图,从屏幕截图中提取信息,对其进行结构化并将结构化信息写入数据库。我们开始开发一个应用程序,该应用程序将拍摄给定图像,首先提取文本,然后从文本中提取姓名、电话号码和地址,并将这些信息写入将交给当局的数据库。在尝试了各种开源 OCR 工具之后,我们开始使用
easyocrOCR
部分和
Gradio
为此应用程序构建界面。我们还被要求为 OCR 构建一个独立的应用程序,因此我们从界面打开了接口。使用基于 transformer 的微调 NER 模型解析 OCR 的文本输出。

OCR

为了协作和改进该应用程序,我们将其托管在 Hugging Face Spaces 上,并且我们获得了 GPU 资助以保持该应用程序的正常运行。Hugging Face Hub 团队为我们设置了一个 CI 机器人,让我们拥有一个短暂的环境,这样我们就可以看到拉取请求如何影响 Space,并且帮助我们在拉取请求期间审查。

后来,我们从各种渠道 (例如 Twitter、Discord) 获得了带有标签的内容,其中包括幸存者求救电话的原始推文,以及从中提取的地址和个人信息。我们开始尝试使用闭源模型的少量提示和微调来自 transformers 的我们自己的 token 分类模型。我们使用
bert-base-turkish-cased
作为 token 分类的基础模型,并提出了第一个地址提取模型。

NER

该模型后来用于 afetharita 提取地址。解析后的地址将被发送到地理编码 API 以获取经度和纬度,然后地理定位将显示在前端地图上。对于推理,我们使用了 Inference API,这是一个托管模型进行推理的 API,当模型被推送到 Hugging Face Hub 时会自动启用。使用 Inference API 进行服务使我们免于拉取模型、编写应用程序、构建 Docker 镜像、设置 CI/CD 以及将模型部署到云实例,这些对于 DevOps 和云团队来说都是额外的开销工作以及。Hugging Face 团队为我们提供了更多的副本,这样就不会出现停机时间,并且应用程序可以在大量流量下保持健壮。

backend_pipeline

后来,我们被问及是否可以从给定的推文中提取地震幸存者的需求。在给定的推文中,我们获得了带有多个标签的数据,用于满足多种需求,这些需求可能是住所、食物或物流,因为那里很冷。我们首先开始在 Hugging Face Hub 上使用开源 NLI 模型进行零样本实验,并使用闭源生成模型接口进行少量样本实验。我们已经尝试过
xlm-roberta-large-xnli

convbert-base-turkish-mc4-cased-allnli_tr
. NLI 模型非常有用,因为我们可以直接推断出候选标签,并在数据漂移时更改标签,而生成模型可能会编造标签,在向后端提供响应时导致不匹配。最初,我们没有标记的数据,因此任何东西都可以使用。

最后,我们决定微调我们自己的模型,在单个 GPU 上微调 BERT 的文本分类头大约需要三分钟。我们进行了标记工作来开发数据集去训练该模型。我们在模型卡的元数据中记录了我们的实验,这样我们以后就可以出一个 leaderboard 来跟踪应该将哪个模型部署到生产环境中。对于基本模型,我们尝试了
bert-base-turkish-uncased

bert-base-turkish-128k-cased
并发现它们的性能优于
bert-base-turkish-cased
。你可以在
下面的链接
找到我们的 leaderboard。

intent_model

考虑到手头的任务和我们数据类别的不平衡,我们专注于消除假阴性并创建了一个 Space 来对所有模型的召回率和 F1 分数进行基准测试。为此,我们将元数据标签添加
deprem-clf-v1
到所有相关模型存储库中,并使用此标签自动检索记录的 F1 和召回分数以及模型排名。我们有一个单独的基准测试集,以避免泄漏到训练集,并始终如一地对我们的模型进行基准测试。我们还对每个模型进行了基准测试,以确定每个标签的最佳部署阈值。

我们希望对我们的命名实体识别模型进行评估,并发起了众包努力,因为数据标记者正在努力为我们提供更好和更新的意图数据集。为了评估 NER 模型,我们使用
Argilla

Gradio
搭建了一个标注界面,人们可以输入一条推文,并将输出标记为正确/不正确/模糊。

active_learning

后来,数据集被去重并用于基准测试我们的进一步实验。

机器学习团队中的另一个小组使用生成模型 (通过门控 API) 来获取特定需求 (因为标签过于宽泛) 的自由文本,并将文本作为每个帖子的附加上下文传递。为此,他们进行了提示工程,并将 API 接口包装为单独的 API ,并将它们部署在云端。我们发现,使用 LLM 的少样本提示有助于在快速发展的数据漂移存在的情况下适应细粒度的需求,因为我们需要调整的唯一的东西是提示,而且我们不需要任何标记的数据。

这些模型目前正在生产中使用,以创建下面热图中的点,以便志愿者和搜救团队可以将需求带给幸存者。

afetharita

我们已经意识到,如果没有 Hugging Face Hub 和其生态系统,我们将无法如此快速地协作、制作原型和部署。下面是我们用于地址识别和意图分类模型的 MLOps 流水线。

mlops

这个应用程序及其各个组件背后有数十名志愿者,他们不眠不休地工作以在如此短的时间内就完成了。

遥感应用

其他团队致力于遥感应用,以评估建筑物和基础设施的损坏情况,以指导搜索和救援行动。地震发生后的最初 48 小时内,电力和移动网络都没有稳定,再加上道路倒塌,这使得评估损坏程度和需要帮助的地方变得极其困难。由于通讯和运输困难,搜救行动也因建筑物倒塌和损坏的虚假报告而受到严重影响。

为了解决这些问题并创建可在未来利用的开源工具,我们首先从 Planet Labs、Maxar 和 Copernicus Open Access Hub 收集受影响区域的震前和震后卫星图像。

input_satellite

我们最初的方法是快速标记卫星图像以进行目标检测和实体分割,并使用“建筑物”的单一类别。目的是通过比较从同一地区收集的震前和震后图像中幸存建筑物的数量来评估损坏程度。为了更容易训练模型,我们首先将 1080x1080 的卫星图像裁剪成更小的 640x640 块。接下来,我们微调了用于建筑物检测的
YOLOv5
、YOLOv8 和 EfficientNet 模型以及用于建筑物语义分割的
SegFormer
模型,并将这些应用部署到 Hugging Face Spaces。

app

同样,数十名志愿者致力于标记、准备数据和训练模型。除了个人志愿者外,像
Co-One
这样的公司也自愿为卫星数据进行更详细的建筑和基础设施注释标记,包括
无损伤

被摧毁

受损

受损设施

未受损设施
标签。我们当前的目标是发布一个广泛的开源数据集,以便在未来加快全球的搜救行动。

output_satellite

总结

对于这种极端应用案例,我们必须迅速行动,并优化分类指标,即使 1% 的改进也很重要。在此过程中有许多伦理讨论,因为甚至选择要优化的指标也是一个伦理问题。我们已经看到了开源机器学习和民主化是如何使个人能够构建挽救生命的应用程序。

我们感谢 Hugging Face 社区发布这些模型和数据集,以及 Hugging Face 团队提供的基础架构和 MLOps 支持。

原文:
https://hf.co/blog/using-ml-for-disasters

作者: Merve Noyan、Alara Dirik

译者: innovation64(李洋)

审校: zhongdongy (阿东)

  1. 首先,从 Docker Hub 上拉取 Zabbix 镜像。可以使用以下命令:

    docker pull zabbix/zabbix-server-mysql:latest
    

    这会下载最新版本的 Zabbix Server 镜像和 MySQL 镜像。

  2. 然后,创建一个 Docker 网络以便容器可以相互通信:

    docker network create zabbix_network
    
  3. 接下来,启动 MySQL 容器并将其连接到上面创建的 Docker 网络:

    docker run --name zabbix-mysql-server -t \
      -e MYSQL_DATABASE="zabbix" \
      -e MYSQL_USER="zabbix" \
      -e MYSQL_PASSWORD="zabbix_pwd" \
      -e MYSQL_ROOT_PASSWORD="mysql_root_pwd" \
      --network zabbix_network \
      -d mysql:latest \
      --character-set-server=utf8 --collation-server=utf8_bin \
      --default-authentication-plugin=mysql_native_password
    

    这将创建一个名为
    zabbix-mysql-server
    的容器,使用
    mysql
    镜像运行一个 MySQL 服务器,并将其连接到
    zabbix_network
    网络。请确保使用强密码。

  4. 接下来,启动 Zabbix Server 容器并将其连接到 Docker 网络和 MySQL 容器:

    docker run --name zabbix-server -t \
      -e DB_SERVER_HOST="zabbix-mysql-server" \
      -e MYSQL_USER="zabbix" \
      -e MYSQL_PASSWORD="zabbix_pwd" \
      -e MYSQL_DATABASE="zabbix" \
      --network zabbix_network \
      -p 10051:10051 \
      -d zabbix/zabbix-server-mysql:latest
    

    这将创建一个名为
    zabbix-server
    的容器,并使用 Zabbix Server 镜像运行 Zabbix Server。请注意,我们设置了
    DB_SERVER_HOST
    以指向先前创建的 MySQL 容器。

  5. 最后,我们可以启动一个 Zabbix Agent 容器,以便监视主机。这可以使用以下命令完成:

    docker run --name zabbix-agent -t \
      --network zabbix_network \
      -d zabbix/zabbix-agent:latest
    

    这将在 Docker 网络上创建一个名为
    zabbix-agent
    的容器,并使用 Zabbix Agent 镜像启动 Zabbix Agent。

现在,您应该已经成功在 Docker 容器上部署了 Zabbix。您可以使用 Web 界面登录 Zabbix Server 并开始监视主机。