微服务架构Micro的基本内容和简单使用

2,226 阅读15分钟

1.什么是Micro?

Micro是云原生开发框架, 解决了构建云原生服务的主要需求点。利用microservices架构模式Micro解决了分布式系统的复杂性,并提供了更简单的可编程抽象作为基础。并提供了一组服务,作为平台的构建模块。

技术是不断发展的,基础设施栈也在一直变化。Micro是一个可插拔的基础和可在其上构建的严格定义的api来解决这些问题的平台。可在任何堆栈或云环境下插拔使用。

2. 主要组成

Micro的关键组件如下:

  • Server:分布式系统runtime由构建模块服务组成,这些服务对基础结构进行抽象并提供可编程的抽象层。
  • Clients:多个入口点,通过这些入口点可以访问您的服务。编写服务一次,并通过您已经知道的所有方法对其进行访问。HTTP api,gRPC代理或者命令行界面
  • Library:Go库使编写服务变得简单,而不必拼凑一行行代码。默认情况下自动配置和初始化,只需导入即可快速上手。
  • Plugins:Micro是与运行时和基础架构无关的。每个基础构建块服务都使用Go Micro标准库来提供可插入的基础。我们通过预先初始化本地使用和云计算来简化使用。
  • Service:
    • auth:身份验证和授权是任何生产就绪平台的核心需求。Micro内置了一个身份验证服务,用于管理服务到服务和用户到服务的身份验证。
    • broker:允许异步消息传递的消息代理。微服务是事件驱动的体系结构,应该作为第一类公民提供消息传递。将事件通知其他服务,而无需担心响应。
    • config: 在一个集中的位置管理动态配置,以便您的服务访问。能够从多个源加载配置,使您能够更新配置而无需重新启动服务。
    • debug:内置用于调试的统计信息、日志和跟踪信息的聚合。调试服务提取所有服务的信息,以帮助从一个位置了解系统的总体范围。
    • network: 服务到服务联网解决方案的下降。将服务发现、负载平衡和容错的负载分配给网络。微网络根据本地注册表动态地构建一个基于延迟的路由表。它包括对多云网络的支持。
    • registry: 注册中心提供服务发现以定位其他服务,存储功能丰富的元数据和端点信息。它是一个服务浏览器,可以让你在运行时集中和动态地存储这些信息。
    • runtime: 服务运行时,它管理服务的生命周期,从服务开始运行。运行时服务可以本地运行,也可以在kubernetes上运行,提供了跨两者的无缝抽象。
    • store: 状态是任何系统的基本要求。我们提供了键值存储来提供状态的简单存储,这些状态可以在服务之间共享,或者长期卸载以保持微服务的无状态和水平可伸缩。

要编写运行在Micro上的应用程序,你可以使用Go Micro框架。

  • go-micro: 利用强大的Go微框架轻松快速地开发微服务。Go Micro消除了分布式系统的复杂性,并提供了更简单的抽象来构建高度可扩展的微服务。

3.快速开始

安装

使用go:

go install github.com/micro/micro/v3
或者下载二进制文件

# MacOS
curl -fsSL https://raw.githubusercontent.com/micro/micro/master/scripts/install.sh | /bin/bash

# Linux
wget -q  https://raw.githubusercontent.com/micro/micro/master/scripts/install.sh -O - | /bin/bash

# Windows
powershell -Command "iwr -useb https://raw.githubusercontent.com/micro/micro/master/scripts/install.ps1 | iex"

当然了,想在docker中使用micro也是可以的

docker pull micro/micro

会使用最新版本的micro

运行一个服务

# 1.第一步我们需要启动我们的server
micro server
# 1.在docker环境下我们可以运行下面的命令
docker run micro/micro server
# 此时可以看到运行该命令输出的日志

2020-09-23 08:49:21  file=user/user.go:54 level=info Setting up config key to /root/.micro/config_secret_key
2020-09-23 08:49:21  file=user/user.go:45 level=info Loading config key from /root/.micro/config_secret_key
2020-09-23 08:49:21  file=user/user.go:80 level=info Loading keys /root/.micro/id_rsa and /root/.micro/id_rsa.pub
2020-09-23 08:49:21  file=user/user.go:99 level=info Setting up keys for JWT at /root/.micro/id_rsa and /root/.micro/id_rsa.pub
2020-09-23 08:49:24  file=server/server.go:110 level=info Starting server
2020-09-23 08:49:24  file=server/server.go:121 level=info Registering network
2020-09-23 08:49:24  file=server/server.go:121 level=info Registering runtime
2020-09-23 08:49:24  file=server/server.go:121 level=info Registering registry
2020-09-23 08:49:24  file=server/server.go:121 level=info Registering config
2020-09-23 08:49:24  file=server/server.go:121 level=info Registering store
2020-09-23 08:49:24  file=server/server.go:121 level=info Registering broker
2020-09-23 08:49:24  file=server/server.go:121 level=info Registering events
2020-09-23 08:49:24  file=server/server.go:121 level=info Registering auth
2020-09-23 08:49:24  file=server/server.go:121 level=info Registering proxy
2020-09-23 08:49:24  file=server/server.go:121 level=info Registering api
2020-09-23 08:49:24  file=server/server.go:186 level=info Starting server runtime
2020-09-23 08:49:25  file=service/service.go:195 level=info Starting [service] server
2020-09-23 08:49:25  file=grpc/grpc.go:902 level=info Server [grpc] Listening on [::]:10001
2020-09-23 08:49:25  file=grpc/grpc.go:732 level=info Registry [mdns] Registering node: server-b4a865a4-cab0-4d1d-93f5-83ea83ea10dc

在另外的命令行终端下

# 
# 使用默认的账号登录micro
# 用户名: admin 密码: micro
micro login
# 2.在docker环境下我们可以运行下面的命令
docker exec -it 7184a69ac6cc(此值为容器id) /micro login

# 现在就可以跑我们的hellworld服务了
micro run github.com/micro/services/helloworld

# 3.在docker环境下我们可以运行下面的命令
docker exec -it 7184a69ac6cc /micro run github.com/micro/services/helloworld

我们可以通过如下命令来查看服务运行的输出日志

micro logs helloworld
# 在docker环境下我们可以运行下面的命令
docker exec -it 7184a69ac6cc /micro logs helloworld
.0-20200526211855-cb27e3aa2013
go: finding github.com/micro/micro/v3 v3.0.0-beta.4.0.20200921154750-68282c70c194
go: finding github.com/micro/go-micro/v3 v3.0.0-beta.2.0.20200921154545-9dbd75f2cc13
go: finding github.com/google/uuid v1.1.2
go: finding google.golang.org/protobuf v1.25.0
go: finding github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c
go: finding github.com/miekg/dns v1.1.27
go: finding golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899
go: finding golang.org/x/net v0.0.0-20200707034311-ab3426394381
go: finding golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae
go: finding golang.org/x/text v0.3.3
go: finding github.com/stretchr/testify v1.6.1
go: finding github.com/davecgh/go-spew v1.1.1
go: finding github.com/pmezard/go-difflib v1.0.0
go: finding gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
go: finding github.com/blang/semver v3.5.1+incompatible
go: finding github.com/hashicorp/go-version v1.2.1
go: finding github.com/bitly/go-simplejson v0.5.0
go: finding github.com/juju/fslock v0.0.0-20160525022230-4d5c94c67b4b
go: finding github.com/urfave/cli/v2 v2.2.0
go: finding github.com/cpuguy83/go-md2man/v2 v2.0.0
go: finding github.com/russross/blackfriday/v2 v2.0.1
go: finding github.com/shurcooL/sanitized_anchor_name v1.0.0
go: finding github.com/dgrijalva/jwt-go v3.2.0+incompatible
go: finding google.golang.org/grpc v1.26.0
go: finding google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013
go: finding github.com/patrickmn/go-cache v2.1.0+incompatible
go: finding github.com/pkg/errors v0.9.1
go: finding github.com/hpcloud/tail v1.0.0
go: finding gopkg.in/fsnotify.v1 v1.4.7
go: finding gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7
go: finding github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf
go: finding github.com/xanzy/go-gitlab v0.35.1
go: finding github.com/google/go-querystring v1.0.0
go: finding github.com/hashicorp/go-cleanhttp v0.5.1
go: finding github.com/hashicorp/go-retryablehttp v0.6.4
go: finding golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
go: finding golang.org/x/time v0.0.0-20191024005414-555d28b269f0
go: finding go.etcd.io/bbolt v1.3.5
go: finding github.com/micro/go-micro v1.18.0
go: finding github.com/rhysd/go-github-selfupdate v1.2.2
go: finding github.com/google/go-github/v30 v30.1.0
go: finding github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf
go: finding github.com/tcnksm/go-gitconfig v0.1.2
go: finding github.com/ulikunitz/xz v0.5.5
2020-09-23 10:12:53  file=service/service.go:195 level=info Starting [service] helloworld
2020-09-23 10:12:53  file=grpc/grpc.go:902 level=info Server [grpc] Listening on [::]:37227
2020-09-23 10:12:53  file=grpc/grpc.go:732 level=info Registry [service] Registering node: helloworld-e7fc841c-53fe-41e5-a7e8-10efe56bc935
2020-09-23 10:13:17  file=handler/helloworld.go:14 level=info Received Helloworld.Call request
2020-09-23 10:13:25  file=handler/helloworld.go:14 level=info Received Helloworld.Call request

通过如下命令,可以查看服务的状态以及元数据

micro status
# 在docker环境下我们可以运行下面的命令
docker exec -it 7184a69ac6cc /micro status
NAME		VERSION	SOURCE					STATUS	BUILD	UPDATED		METADATA
helloworld	latest	github.com/micro/services/helloworld	running	n/a	21m48s ago	owner=admin, group=micro

通过如下命令,可以调用相关的服务

micro call helloworld Helloworld.Call '{"name":"Jane"}'
# 在docker环境下我们可以运行下面的命令
docker exec -it 7184a69ac6cc /micro call helloworld Helloworld.Call '{"name":"Jane"}'
{
	"msg": "Hello Jane"
}

通过如下命令,可以查询服务的列表

micro services
# 在docker环境下我们可以运行下面的命令
docker exec -it 7184a69ac6cc /micro services
api
auth
broker
config
events
helloworld
network
proxy
registry
runtime
server
store

通过如下命令,可以新实现一个服务

micro new foobar
Creating service go.micro.service.foobar in foobar

.
├── main.go
├── generate.go
├── plugin.go
├── handler
│   └── foobar.go
├── subscriber
│   └── foobar.go
├── proto/foobar
│   └── foobar.proto
├── Dockerfile
├── Makefile
├── README.md
├── .gitignore
└── go.mod


download protobuf for micro:

brew install protobuf
go get -u github.com/golang/protobuf/proto
go get -u github.com/golang/protobuf/protoc-gen-go
go get github.com/micro/micro/v2/cmd/protoc-gen-micro@master

compile the proto file foobar.proto:

cd foobar
protoc --proto_path=.:$GOPATH/src --go_out=. --micro_out=. proto/foobar/foobar.proto

正如你看到的一样,在构建第一个服务之前,一下工具必须要安装

  • protoc
  • protobuf/proto
  • protoc-gen-micro

proto文件转化为Go代码,都需要他们。proto文件的存在是为了提供一种语言无关的方式来描述服务端点、它们的输入和输出类型,并提供一种有效的序列化格式。

因此,一旦安装了所有的工具,在服务根目录内,我们可以发出以下命令来从原文件生成Go代码

protoc --proto_path=.:$GOPATH/src --go_out=. --micro_out=. proto/foobar/foobar.proto

生成的代码必须提交给源代码控制,以使其他服务在进行服务调用时能够导入原型

至此,我们知道了如何编写服务,运行该服务以及如何调用其他服务。我们的一切触手可及,但是仍然缺少一些编写应用程序的片段。存储接口就是其中之一,即使没有数据库也可以帮助持久存储数据。

除其他许多有用的内置服务外,Micro还包括用于存储数据的持久性存储服务。

为了保持一个值,我们可以使用如下命令

micro store write key1 value1

然后使用如下命令来读取该值

micro store read key

现在,由于示例服务正在运行,现在我们不能使用micro run,但是可以使用micro update来重新部署它。

我们可以简单地发出update命令(记住首先要切换回示例服务的根目录)

micro update .


docker exec -it 7184a69ac6cc /micro update helloworld

此时,可以通过micro status可以查看该服务已经更新了

如果事情因为某些原因失控了,我们可以尝试久经考验的“关闭然后再打开”的解决方案:

micro kill .
micro run .

4.FAQ

micro 和go-kit有什么区别?

Go-kit将自己描述为microservices的标准库。与Go一样,go-kit为您提供了可用于构建应用程序的单独软件包。Go-kit非常适合希望完全控制定义服务方式的地方。

Go Micro也是microservices的标准库,因此如果您想为工作选择最好的抽象,请查看它。另外,Micro是microservices的框架,封装了后端和API开发的所有要求。把它想象成一个平台。

性能怎么样?

Micro利用了gRPC,所以它的性能也差不多。Micro还可以在多个服务实例之间实现负载平衡,并可以在需要扩展时将负载分配给分布式系统基础设施。

什么是local和platform?

Micro支持将环境作为一个概念。env是在本地或其他地方托管的微型服务器。它定义为映射到指向微型代理(gRPC代理)的host:port的名称。我们引入了两种环境,即“local”和“platform”。

docker exec -it 7184a69ac6cc /micro env
* local      127.0.0.1:8081
  platform   proxy.m3o.com

Local是由micro server启动的本地服务器,代理位于端口:8081上。platform是我们作为付费产品托管在云中的环境,理想情况下,如果您需要托管,则可以在其中运行代码。

5.附录1.命令行工具库cli

micro代码中,比较多的使用到了命令行工具库,这块内容可以说在理解micro的道路上还是比较重要的,下面一起来看一下这个工具的详情;

5.1.cli

cli是一个简易,快速,有趣的软件包用于构建命令行程序。目标是让开发人员以富有表现力的方式编写快速且可分发的命令行应用程序。

5.1.1 安装

$ GO111MODULE=on go get github.com/urfave/cli/v2

5.1.2 简单使用

cli背后的哲学之一是API应该是有趣的并且充满发现。因此,cli应用程序中的代码可以少到一行

package main

import (
  "os"

  "github.com/urfave/cli/v2"
)

func main() {
  (&cli.App{}).Run(os.Args)
}

该应用程序将运行并显示帮助文本,但不是很有用。

NAME:
   main - A new cli application

USAGE:
   main [global options] command [command options] [arguments...]

COMMANDS:
   help, h  Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --help, -h  show help (default: false)

让我们执行一个动作并提供一些帮助文档:

package main

import (
  "fmt"
  "log"
  "os"

  "github.com/urfave/cli/v2"
)

func main() {
  app := &cli.App{
    Name: "boom",
    Usage: "make an explosive entrance",
    Action: func(c *cli.Context) error {
      fmt.Println("boom! I say!")
      return nil
    },
  }

  err := app.Run(os.Args)
  if err != nil {
    log.Fatal(err)
  }
}

在运行程序时,app.Run会做出Action指定的动作。

boom! I say!

您可以通过调用cli.Context上的Args函数来查找参数,例如:

package main

import (
  "fmt"
  "log"
  "os"

  "github.com/urfave/cli/v2"
)

func main() {
  app := &cli.App{
    Action: func(c *cli.Context) error {
      fmt.Printf("Hello %q", c.Args().Get(0))
      return nil
    },
  }

  err := app.Run(os.Args)
  if err != nil {
    log.Fatal(err)
  }
}
go run ~/workspace/src/just.for.test/clilearn/main.go friends
Hello "friends"%

设置和查询标志很简单。

package main

import (
  "fmt"
  "log"
  "os"

  "github.com/urfave/cli/v2"
)

func main() {
  app := &cli.App{
    Flags: []cli.Flag {
      &cli.StringFlag{
        Name: "lang",
        Value: "english",
        Usage: "language for the greeting",
      },
    },
    Action: func(c *cli.Context) error {
      name := "Nefertiti"
      if c.NArg() > 0 {
        name = c.Args().Get(0)
      }
      if c.String("lang") == "spanish" {
        fmt.Println("Hola", name)
      } else {
        fmt.Println("Hello", name)
      }
      return nil
    },
  }

  err := app.Run(os.Args)
  if err != nil {
    log.Fatal(err)
  }
}
go run ~/workspace/src/just.for.test/clilearn/main.go -lang spanish
Hola someone

有时在使用方法字符串中指定flag的值是有用的。这种占位符用反引号表示。

package main

import (
  "log"
  "os"

  "github.com/urfave/cli/v2"
)

func main() {
  app := &cli.App{
    Flags: []cli.Flag{
      &cli.StringFlag{
        Name:    "config",
        Aliases: []string{"c"},
        Usage:   "Load configuration from `FILE`",
      },
    },
  }

  err := app.Run(os.Args)
  if err != nil {
    log.Fatal(err)
  }
}
NAME:
   main - A new cli application

USAGE:
   main [global options] command [command options] [arguments...]

COMMANDS:
   help, h  Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --config FILE, -c FILE  Load configuration from FILE
   --help, -h              show help (default: false)

请注意,仅使用第一个占位符。随后的反引号将保留原样。

您可以通过提供名称的逗号分隔列表来设置flag的备用(或简称)名称

package main

import (
  "log"
  "os"

  "github.com/urfave/cli/v2"
)

func main() {
  app := &cli.App{
    Flags: []cli.Flag {
      &cli.StringFlag{
        Name:    "lang",
        Aliases: []string{"l"},
        Value:   "english",
        Usage:   "language for the greeting",
      },
    },
  }

  err := app.Run(os.Args)
  if err != nil {
    log.Fatal(err)
  }
}

这样,flag就可以设置为--lang spanish 或者 -l spanish.注意,在同一个命令调用中给出相同标志的两种不同形式是错误的。

应用程序和命令的标志按定义顺序显示。但是,可以从这个库之外对它们进行排序通过使用FlagsByName或者CommandByName结合sort

package main

import (
  "log"
  "os"
  "sort"

  "github.com/urfave/cli/v2"
)

func main() {
  app := &cli.App{
    Flags: []cli.Flag{
      &cli.StringFlag{
        Name:  "lang, l",
        Value: "english",
        Usage: "Language for the greeting",
      },
      &cli.StringFlag{
        Name:  "config, c",
        Usage: "Load configuration from `FILE`",
      },
    },
    Commands: []*cli.Command{
      {
        Name:    "complete",
        Aliases: []string{"c"},
        Usage:   "complete a task on the list",
        Action:  func(c *cli.Context) error {
          return nil
        },
      },
      {
        Name:    "add",
        Aliases: []string{"a"},
        Usage:   "add a task to the list",
        Action:  func(c *cli.Context) error {
          return nil
        },
      },
    },
  }

  sort.Sort(cli.FlagsByName(app.Flags))
  sort.Sort(cli.CommandsByName(app.Commands))

  err := app.Run(os.Args)
  if err != nil {
    log.Fatal(err)
  }
}

输出顺序就会按照排序的顺序输出

--config FILE, -c FILE  Load configuration from FILE
--lang value, -l value  Language for the greeting (default: "english")

通过将required字段设置为true,可以创建required标志。如果用户没有提供必需的标志,则会显示一条错误消息。

package main

import (
  "log"
  "os"
  "github.com/urfave/cli/v2"
)

func main() {
  app := cli.NewApp()

  app.Flags = []cli.Flag {
    &cli.StringFlag{
      Name: "lang",
      Value: "english",
      Usage: "language for the greeting",
      Required: true,
    },
  }

  app.Action = func(c *cli.Context) error {
    var output string
    if c.String("lang") == "spanish" {
      output = "Hola"
    } else {
      output = "Hello"
    }
    fmt.Println(output)
    return nil
  }

  err := app.Run(os.Args)
  if err != nil {
    log.Fatal(err)
  }
}

有时在标志声明中指定flag的默认值很有用。如果标记的默认值是计算值,则这很有用。可以通过DefaultText结构字段设置默认值。

package main

import (
  "log"
  "os"

  "github.com/urfave/cli/v2"
)

func main() {
  app := &cli.App{
    Flags: []cli.Flag{
      &cli.IntFlag{
        Name:    "port",
        Usage:   "Use a randomized port",
        Value: 0,
        DefaultText: "random",
      },
    },
  }

  err := app.Run(os.Args)
  if err != nil {
    log.Fatal(err)
  }
}

输出文本中将包含,默认值信息

--port value  Use a randomized port (default: random)

标志值不同源的优先级如下(从高到低):

  • 用户指定的命令行flag值
  • 环境变量(如果指定的话)
  • 配置文件(如果指定的话)
  • 定义的默认值

子命令:可以为更类似git的命令行应用程序定义子命令。

package main

import (
  "fmt"
  "log"
  "os"

  "github.com/urfave/cli/v2"
)

func main() {
  app := &cli.App{
    Commands: []*cli.Command{
      {
        Name:    "add",
        Aliases: []string{"a"},
        Usage:   "add a task to the list",
        Action:  func(c *cli.Context) error {
          fmt.Println("added task: ", c.Args().First())
          return nil
        },
      },
      {
        Name:    "complete",
        Aliases: []string{"c"},
        Usage:   "complete a task on the list",
        Action:  func(c *cli.Context) error {
          fmt.Println("completed task: ", c.Args().First())
          return nil
        },
      },
      {
        Name:        "template",
        Aliases:     []string{"t"},
        Usage:       "options for task templates",
        Subcommands: []*cli.Command{
          {
            Name:  "add",
            Usage: "add a new template",
            Action: func(c *cli.Context) error {
              fmt.Println("new task template: ", c.Args().First())
              return nil
            },
          },
          {
            Name:  "remove",
            Usage: "remove an existing template",
            Action: func(c *cli.Context) error {
              fmt.Println("removed task template: ", c.Args().First())
              return nil
            },
          },
        },
      },
    },
  }

  err := app.Run(os.Args)
  if err != nil {
    log.Fatal(err)
  }
}
go run main.go template add job
new task template:  job

对于有许多子命令的应用程序中的其他组织,您可以为每个命令关联一个类别,以便在帮助输出中将它们分组在一起

package main

import (
  "log"
  "os"

  "github.com/urfave/cli/v2"
)

func main() {
  app := &cli.App{
    Commands: []*cli.Command{
      {
        Name: "noop",
      },
      {
        Name:     "add",
        Category: "template",
      },
      {
        Name:     "remove",
        Category: "template",
      },
    },
  }

  err := app.Run(os.Args)
  if err != nil {
    log.Fatal(err)
  }
}
go run  main.go
NAME:
   main - A new cli application

USAGE:
   main [global options] command [command options] [arguments...]

COMMANDS:
   noop
   help, h  Shows a list of commands or help for one command
   template:
     add
     remove

GLOBAL OPTIONS:
   --help, -h  show help (default: false)
go run  main.go noop
NAME:
   main noop -

USAGE:
   main noop [command options] [arguments...]

OPTIONS:
   --help, -h  show help (default: false)
go run main.go noop add
NAME:
   main add -

USAGE:
   main add [arguments...]

CATEGORY:
   template

调用App.Run不会自动调用os.Exit,这也就意味着默认情况下,退出代码将“掉线”为0。可以通过返回满足cli.ExitCoder的非nil错误来设置显式退出代码,或者cli.MultiError包含满足cli.ExitCoder的错误。

package main

import (
  "log"
  "os"

  "github.com/urfave/cli/v2"
)

func main() {
  app := &cli.App{
    Flags: []cli.Flag{
      &cli.BoolFlag{
        Name:  "ginger-crouton",
        Usage: "is it in the soup?",
      },
    },
    Action: func(ctx *cli.Context) error {
      if !ctx.Bool("ginger-crouton") {
        return cli.Exit("Ginger croutons are not in the soup", 86)
      }
      return nil
    },
  }

  err := app.Run(os.Args)
  if err != nil {
    log.Fatal(err)
  }
}
Ginger croutons are not in the soup
exit status 86

结合短选项:使用选项的短名称的传统用法如下所示:

cmd -s -o -m "Some message"

假设您希望用户能够将选项与其短名称结合在一起。可以在应用程序配置中使用UseShortOptionHandling bool来完成此操作,或将单个命令附加到命令配置中。

package main

import (
  "fmt"
  "log"
  "os"

  "github.com/urfave/cli/v2"
)

func main() {
  app := &cli.App{}
  app.UseShortOptionHandling = true
  app.Commands = []*cli.Command{
    {
      Name:  "short",
      Usage: "complete a task on the list",
      Flags: []cli.Flag{
        &cli.BoolFlag{Name: "serve", Aliases: []string{"s"}},
        &cli.BoolFlag{Name: "option", Aliases: []string{"o"}},
        &cli.StringFlag{Name: "message", Aliases: []string{"m"}},
      },
      Action: func(c *cli.Context) error {
        fmt.Println("serve:", c.Bool("serve"))
        fmt.Println("option:", c.Bool("option"))
        fmt.Println("message:", c.String("message"))
        return nil
      },
    },
  }

  err := app.Run(os.Args)
  if err != nil {
    log.Fatal(err)
  }
}
go run main.go  short -s -o -m "s"
serve: true
option: true
message: s
go run main.go  short -som "s"
serve: true
option: true
message: s

您可以启用命令完成通过设置EnableBashCompletion标志

package main
import (
	"fmt"
	"log"
	"os"
	"github.com/urfave/cli/v2"
)
func main() {
	app := cli.NewApp()
	app.EnableBashCompletion = true
	app.Commands = []*cli.Command{
		{
			Name:    "add",
			Aliases: []string{"a"},
			Usage:   "add a task to the list",
			Action: func(c *cli.Context) error {
				fmt.Println("added task: ", c.Args().First())
				return nil
			},
		},
		{
			Name:    "complete",
			Aliases: []string{"c"},
			Usage:   "complete a task on the list",
			Action: func(c *cli.Context) error {
				fmt.Println("completed task: ", c.Args().First())
				return nil
			},
		},
		{
			Name:    "template",
			Aliases: []string{"t"},
			Usage:   "options for task templates",
			Subcommands: []*cli.Command{
				{
					Name:  "add",
					Usage: "add a new template",
					Action: func(c *cli.Context) error {
						fmt.Println("new task template: ", c.Args().First())
						return nil
					},
				},
				{
					Name:  "remove",
					Usage: "remove an existing template",
					Action: func(c *cli.Context) error {
						fmt.Println("removed task template: ", c.Args().First())
						return nil
					},
				},
			},
		},
	}
	err := app.Run(os.Args)
	if err != nil {
		log.Fatal(err)
	}
}