Viper  Viper是一个强大的配置管理库,主要用于管理Go应用的配置信息。它可以处理多种配置来源,包括配置文件、环境变量、命令行参数等

1.读取配置文件 假设有一个config.yaml配置文件,使用Viper读取配置文件的代码如下:

package main import (

"fmt"
"log"
"github.com/spf13/viper"

) func main() {

// 设置配置文件路径和名称
viper.SetConfigName("config") // 配置文件名(不带扩展名)
viper.SetConfigType("yaml")   // 配置文件类型
viper.AddConfigPath(".")      // 配置文件所在路径

// 读取配置文件

if err := viper.ReadInConfig(); err != nil {
    log.Fatalf("Error reading config file, %s", err)
}

// 获取配置值

appName := viper.GetString("app_name")
debug := viper.GetBool("debug")
dbHost := viper.GetString("database.host")
dbPort := viper.GetInt("database.port")
dbUser := viper.GetString("database.username")
dbPass := viper.GetString("database.password")

}

2.设置默认值 Viper支持为配置项设置默认值,这在配置项未在配置文件中定义,但我又需要一个默认值的情况下非常有用 :

viper.SetDefault(“database.port”, 3306)

3.绑定环境变量 Viper可以自动从环境变量中读取配置 :

viper.AutomaticEnv() viper.SetEnvPrefix(“myapp”)

4.动态监听配置变化 Viper可以监听配置文件的变化,并在文件更新时自动重新加载配置:

viper.WatchConfig() viper.OnConfigChange(func(e fsnotify.Event) {

fmt.Println("Config file changed:", e.Name)

})

5.绑定到结构体 Viper可以将配置解析到结构体中:

type AppConfig struct {

AppName  string `mapstructure:"app_name"`
Debug    bool   `mapstructure:"debug"`
Database struct {
    Host     string `mapstructure:"host"`
    Port     int    `mapstructure:"port"`
    Username string `mapstructure:"username"`
    Password string `mapstructure:"password"`
} `mapstructure:"database"`

} var config AppConfig if err := viper.Unmarshal(&config); err != nil {

panic(err)

}

6.读取多个配置文件 Viper可以通过多次调用viper.ReadInConfig()来逐个读取多个配置文件。Viper会按照读取的顺序覆盖配置项,后面的配置文件中的相同键会覆盖前面的配置文件中的值。

Viper可以调用viper.MergeConfig方法可以将多个配置文件合并到一起。

package main import (

"fmt"
"log"
"github.com/spf13/viper"

) func main() {

// 读取第一个配置文件
viper.SetConfigName("config1") // 配置文件名(不带扩展名)
viper.SetConfigType("yaml")   // 配置文件类型
viper.AddConfigPath(".")      // 配置文件所在路径
if err := viper.ReadInConfig(); err != nil {
    log.Fatalf("Error reading config file, %s", err)
}

// 读取第二个配置文件并合并

viper.SetConfigName("config2") // 配置文件名(不带扩展名)
if err := viper.MergeInConfig(); err != nil {
    log.Fatalf("Error reading config file, %s", err)
}

}

也可以省略多次调用viper.SetConfigName,直接合并指定路径的配置文件:

// 读取第二个配置文件并合并 if err := viper.MergeConfigFile(“./config2.yaml”); err != nil {

log.Fatalf("Error reading config file, %s", err)

}

Go模块  模块(Module)是一个相对独立的代码集合,它有自己独立的依赖关系。

1.概念 Go模块是以go.mod文件为标志的代码集合。一个模块可以包含多个包(Package),而包是Go语言中组织代码的基本单元。当

在一个目录下运行go mod init 命令时,就会在该目录下创建一个go.mod文件,从而将该目录及其子目录下的代码定义为一个模块。通常是模块的导入路径,一般是一个以域名开头的字符串,用来唯一标识这个模块。

每个模块都有自己的go.mod和go.sum文件,用于记录依赖关系和依赖的版本信息。 (类似于JS的package.json)

2.go.mod 在 Go 语言中,一个项目目录下的子目录并不一定都是单独的包。是否构成一个包,取决于目录中是否包含 Go 源代码文件(.go 文件)以及这些文件的包声明,包声明的相关命令:

module:声明模块的名称。

go:声明模块兼容的最低 Go 版本。

toolchain:指定构建模块时使用的 Go 工具链版本。

require:声明模块的依赖关系。

知识点 struct 是一种数据类型,用于将多个不同类型的数据组合成一个逻辑单元。interface 是一种类型,用于定义一组方法的集合,它只包含方法签名,不包含数据字段。

1.Struct struct 是一种用户自定义的数据类型,用于将多个不同类型的数据组合成一个逻辑单元。它类似于其他语言中的类,但没有方法,主要用途:

用于表示复杂的数据结构。 可以包含字段(成员变量)。 可以通过方法(绑定到结构体的函数)来操作数据。

2.Interface interface 是一种类型,它定义了一组方法的集合。一个类型只要实现了接口中定义的所有方法,就自动实现了该接口。主要还是用于方便各种方法进行调用

定义行为规范(必须传递包含哪些行为方法的数据、变量可以被赋值任意实现行为的数据) 实现多态,方法被调用时可以传递不同类型变量,只要它实现了interface的方法 解耦合,interface可以解耦具体实现和使用代码,使得代码更加模块化和可维护。

new和make new 和 make 是两个用于分配内存的内置函数,但它们的用途和行为有很大不同。

new:分配内存,但不初始化。它会分配零值。 make:分配内存并初始化。它只能用于切片、map 和 channel。

new(T) 会分配一个类型为 T 的零值,并返回指向该值的指针。当需要一个指向某个类型的零值的指针时,使用 new。:

package main import “fmt” func main() {

// 使用 new 分配一个 int 类型的零值
p := new(int)
fmt.Println(*p) // 输出 0,因为 int 的零值是 0
*p = 42
fmt.Println(*p) // 输出 42

}

make(T, size) 会分配一个类型为 T 的内存,并初始化,返回该内存的引用。T 只能是切片、map 或 channel。当需要初始化一个切片、map 或 channel 时,使用 make:

package main import “fmt” func main() {

// 使用 make 初始化一个切片
s := make([]int, 5) // 创建一个长度为 5 的切片
fmt.Println(s)      // 输出 [0 0 0 0 0],因为 int 的零值是 0

// 使用 make 初始化一个 map

m := make(map[string]int)
m["key"] = 42
fmt.Println(m) // 输出 map[key:42]

// 使用 make 初始化一个 channel

c := make(chan int)
go func() {
    c <- 42
}()
fmt.Println(<-c) // 输出 42

} context模块 context模块可以传递一个可取消的信号,让各个协程(goroutine)能够感知到这个取消信号,从而优雅地停止正在执行的任务。

1.方法 ctx := context.Background() ctx, cancel := context.WithCancel(context.Background()) defer cancel() // 在合适的时候调用cancel函数来取消ctxctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() deadline := time.Now().Add(5 * time.Second) ctx, cancel := context.WithDeadline(context.Background(), deadline) defer cancel() ctx := context.WithValue(context.Background(), “user_id”, 12345)

2.使用 context模块在Go语言的并发编程和分布式系统中起着非常重要的作用,它可以更好地控制协程的执行,传递请求范围的值,以及实现超时和取消机制  :

网络请求:在处理HTTP请求时,context可以用来传递请求的超时时间、取消信号以及请求范围的值(如用户身份信息等)。例如,当一个HTTP请求被取消时,与这个请求相关的所有操作(如数据库查询、调用其他服务等)都可以通过context感知到取消信号,从而停止执行。 分布式系统:在分布式系统中,context可以用来传递追踪ID等信息,方便进行日志记录和问题追踪。同时,也可以用来控制分布式任务的超时和取消。 并发任务:在并发编程中,context可以用来控制多个协程的执行。例如,当一个任务被取消时,与这个任务相关的所有协程都可以通过context感知到取消信号,从而停止执行。

package main import (

"context"
"fmt"
"time"

) func main() {

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

// 启动多个子协程来处理任务

for i := 0; i < 3; i++ {
    go func(id int) {
        select {
        case <-ctx.Done(): // 监听上下文的结束
            fmt.Printf("Goroutine %d cancelledn", id)
            return
        case <-time.After(20 * time.Second):
            fmt.Printf("Goroutine %d finished after 20 secondsn", id)
        }
    }(i)
}

// 模拟任务的执行

time.Sleep(10 * time.Second)
fmt.Println("Cancelling all goroutines")
cancel()

}

sync模块 sync 包是 Go 标准库中的一个非常重要的包,它提供了多种同步原语,用于在并发编程中协调多个协程(goroutine)的行为。这些同步原语可以帮助解决并发编程中的竞态条件、死锁等问题。

1.sync.Mutex sync.Mutex 是一个互斥锁,用于保护共享资源,确保同一时间只有一个协程可以访问该资源。

var mu sync.Mutex mu.Lock() // 加锁 defer mu.Unlock() // 解锁

2.sync.RWMutex sync.RWMutex 是一个读写互斥锁,允许多个协程同时读取共享资源,但写入时需要独占访问。

var rwMu sync.RWMutex rwMu.RLock() // 加读锁 defer rwMu.RUnlock() // 解读锁 rwMu.Lock() // 加写锁 defer rwMu.Unlock() // 解写锁

3.sync.WaitGroup sync.WaitGroup 用于等待一组协程完成。它通过 Add、Done 和 Wait 方法来协调协程的完成。

var wg sync.WaitGroup wg.Add(1) // 增加计数 defer wg.Done() // 减少计数 wg.Wait() // 等待所有协程完成

4.sync.Once sync.Once 用于确保某个操作只执行一次。它通过 Do 方法来保证操作的唯一性。

var once sync.Once once.Do(func() {

// 只执行一次的操作

})

os/signal模块 os/signal 包用于处理操作系统发出的信号。

1.基础用法 创建一个 os.Signal 类型的通道,用于接收信号:

sigChan := make(chan os.Signal, 1)

使用 signal.Notify 函数将信号通道注册到一个或多个信号上:

signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

通过监听信号通道来处理接收到的信号:

go func() {

for sig := range sigChan {
    fmt.Printf("Received signal: %vn", sig)
    // 根据信号类型执行相应的处理逻辑
}

}()

信号忽略:signal.Ignore(syscall.SIGPIPE), 函数忽略某些信号 恢复默认:signal.Reset(syscall.SIGINT),恢复默认的信号处理

2.高级技巧 可以创建多个信号通道来分组处理不同类型的信号:

package main import (

"fmt"
"os"
"os/signal"
"syscall"

) func main() {

sigChan1 := make(chan os.Signal, 1)
sigChan2 := make(chan os.Signal, 1)
signal.Notify(sigChan1, syscall.SIGINT)
signal.Notify(sigChan2, syscall.SIGTERM, syscall.SIGHUP)

go func() {

    for {
        select {
        case sig := <-sigChan1:
            fmt.Printf("Caught SIGINT: %vn", sig)
        case sig := <-sigChan2:
            fmt.Printf("Caught SIGTERM or SIGHUP: %vn", sig)
        }
    }
}()

fmt.Println(“Program is running… Press Ctrl+C to exit”)

select {}

}

阅读剩余 0%
本站所有文章资讯、展示的图片素材等内容均为注册用户上传(部分报媒/平媒内容转载自网络合作媒体),仅供学习参考。 用户通过本站上传、发布的任何内容的知识产权归属用户或原始著作权人所有。如有侵犯您的版权,请联系我们反馈本站将在三个工作日内改正。