要对Go语言中的字符串切片进行排序,可以使用sort包中的Strings函数。
以下是对一个字符串切片进行升序排序的示例代码:
package main
import (
"fmt"
"sort"
)
func main() {
fruits := []string{"banana", "apple", "orange", "pear"}
sort.Strings(fruits)
fmt.Println(fruits) // 输出 ["apple", "banana", "orange", "pear"]
}在上面的代码中,Strings函数对fruits字符串切片进行升序排序,输出结果为["apple", "banana", "orange", "pear"]。
如果要按照降序排列,可以使用sort.Reverse函数和sort.StringSlice类型进行排序:
package main
import (
"fmt"
"sort"
)
func main() {
fruits := []string{"banana", "apple", "orange", "pear"}
sort.Sort(sort.Reverse(sort.StringSlice(fruits)))
fmt.Println(fruits) // 输出 ["pear", "orange", "banana", "apple"]
}在上面的代码中,sort.StringSlice将fruits字符串切片转换为sort.Interface类型,然后使用sort.Reverse将其反转,最后使用sort.Sort方法进行排序,输出结果为["pear", "orange", "banana", "apple"]。
如果要按照自定义的排序规则进行排序,可以使用sort.Slice函数和比较函数作为参数。例如,可以按照字符串长度对字符串切片进行排序
package main
import (
"fmt"
"sort"
)
func main() {
fruits := []string{"banana", "apple", "orange", "pear"}
sort.Slice(fruits, func(i, j int) bool {
return len(fruits[i]) < len(fruits[j])
})
fmt.Println(fruits) // 输出 ["pear", "apple", "banana", "orange"]
}在上面的代码中,sort.Slice函数接受一个比较函数作为参数,该函数比较字符串的长度。按照字符串长度升序排序后,输出结果为["pear", "apple", "banana", "orange"]。
要在 Gin 中获取所有的 POST 数据,可以使用 Context 对象的 PostForm 属性数据。
以下是一个简单的示例代码,演示如何获取所有的POST数据:
data := make([]string, 0)
form := ctx.Request.PostForm
for key, value := range form {
if key != "Sign" {
data = append(data, fmt.Sprintf("%v", value[0]))
}
}
fmt.Printf("data: %v\n", data)在上面的代码中,通过 PostForm 获取的POST数据的。
在实际开发中,可以根据POST数据的键值对进行逐个获取,也可以使用PostForm方法获取单个POST数据。例如:
r.POST("/test", func(c *gin.Context) {
name := c.PostForm("name")
age := c.PostForm("age")
fmt.Println(name, age)
c.JSON(200, gin.H{
"message": "success",
})
})在上面的代码中,我们使用PostForm方法逐个获取POST数据。如果POST数据中包含name和age这两个键值对,则将其打印到控制台。
在Go语言中,变量类型转换可以通过在变量名前面放置需要转换的类型的名称,使用圆括号将其括起来实现。
Go语言不存在隐式类型转换,因此所有的类型转换都必须显式的声明:
valueOfTypeB = typeB(valueOfTypeA)
类型 B 的值 = 类型 B(类型 A 的值)例如,假设有一个int类型的变量x,我们想将其转换为float64类型,可以使用以下语法:
var x int = 10
var y float64 = float64(x)类型转换只能在定义正确的情况下转换成功,例如从一个取值范围较小的类型转换到一个取值范围较大的类型(将 int16 转换为 int32)。当从一个取值范围较大的类型转换到取值范围较小的类型时(将 int32 转换为 int16 或将 float32 转换为 int),会发生精度丢失(截断)的情况。
将一个字符串转换成另一个类型,可以使用以下语法:
var str string = "10"
var num int
num, _ = strconv.Atoi(str)以上代码将字符串变量 str 转换为整型变量 num。
注意,strconv.Atoi 函数返回两个值,第一个是转换后的整型值,第二个是可能发生的错误,我们可以使用空白标识符 _ 来忽略这个错误
。以下实例将字符串转换为整数 :
func main() {
str := "123"
num, err := strconv.Atoi(str)
if err != nil {
fmt.Println("转换错误:", err)
} else {
fmt.Printf("字符串 '%s' 转换为整数为:%d\n", str, num)
}
}以下实例将整数转换为字符串:
num := 123
str := strconv.Itoa(num)
fmt.Printf("整数 %d 转换为字符串为:'%s'\n", num, str)以下实例将字符串转换为浮点数:
str := "3.14"
num, err := strconv.ParseFloat(str, 64)
if err != nil {
fmt.Println("转换错误:", err)
} else {
fmt.Printf("字符串 '%s' 转为浮点型为:%f\n", str, num)
}在Go语言中,还可以使用fmt包中的Sprintf函数将变量转换为字符串类型,例如:
import "fmt"
func main() {
var num int = 42
str := fmt.Sprintf("%d", num)
fmt.Printf("The number is %s\n", str)
}在上述代码中,我们将一个整数变量num转换为字符串类型,并将其赋值给str变量。在这里,我们使用fmt.Sprintf函数来将整数格式化为字符串类型。Sprintf函数的作用是将格式化的字符串写入指定的变量中。
需要注意的是,对于复杂的数据类型,如结构体、切片等,需要自定义实现String()函数来将其转换为字符串。这个函数是由fmt包在打印时自动调用的,例如:
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("%s is %d years old", p.Name, p.Age)
}
func main() {
p := Person{Name: "Alice", Age: 25}
fmt.Println(p) // Output: Alice is 25 years old
}在上述代码中,我们定义了一个Person结构体,并为其定义了String()函数来实现转换为字符串。当我们打印Person类型的变量时,fmt包会自动调用String()函数来将其转换为字符串。
类型断言(Type Assertion)是一个使用在接口值上的操作,用于检查接口类型变量所持有的值是否实现了期望的接口或者具体的类型。
在Go语言中类型断言的语法格式如下:
value, ok := x.(T)其中,x 表示一个接口的类型,T 表示一个具体的类型(也可为接口类型)。
该断言表达式会返回 x 的值(也就是 value)和一个布尔值(也就是 ok),可根据该布尔值判断 x 是否为 T 类型:
如果 T 是具体某个类型,类型断言会检查 x 的动态类型是否等于具体类型 T。如果检查成功,类型断言返回的结果是 x 的动态值,其类型是 T。
如果 T 是接口类型,类型断言会检查 x 的动态类型是否满足 T。如果检查成功,x 的动态值不会被提取,返回值是一个类型为 T 的接口值。
无论 T 是什么类型,如果 x 是 nil 接口值,类型断言都会失败。
示例:
package main
import (
"fmt"
)
func main() {
var x interface{}
x = 1
value, ok := x.(int)
fmt.Print(value, ",", ok)
}
// 运行结果 1, true需要注意
如果不接收第二个参数也就是上面代码中的 ok,断言失败时会直接造成一个 panic。如果 x 为 nil 同样也会 panic。
示例:
package main
import (
"fmt"
)
func main() {
var x interface{}
x = "Hello"
value := x.(int)
fmt.Println(value)
}运行结果如下:panic: interface conversion: interface {} is string, not int
类型断言还可以配合 switch 使用,示例代码如下:package main
import (
"fmt"
)
func main() {
var a int
a = 10
getType(a)
}
func getType(a interface{}) {
switch a.(type) {
case int:
fmt.Println("the type of a is int")
case string:
fmt.Println("the type of a is string")
case float64:
fmt.Println("the type of a is float")
default:
fmt.Println("unknown type")
}
}
// 运行结果 : the type of a is intfunc SafePOST(ctx *gin.Context, newString string, needReplaceString ...string) {
if ctx.Request.Method == "POST" {
// 读取请求体中的POST数据
bodyBytes, err := io.ReadAll(ctx.Request.Body)
if err != nil {
ctx.Abort()
return
}
bodyString, err := url.QueryUnescape(string(bodyBytes))
if err != nil {
ctx.Abort()
return
}
for _, v := range needReplaceString {
bodyString = strings.ReplaceAll(bodyString, v, newString)
bodyString = strings.ReplaceAll(bodyString, v, newString)
}
ctx.Request.Body = io.NopCloser(bytes.NewReader([]byte(bodyString)))
}
}在路由中间件内或者路由函数内执行 SafePOST 函数即可。
func SafeQuery(ctx *gin.Context, newString string, needReplaceString ...string) {
if ctx.Request.URL.RawQuery == "" {
return
}
rawQuery, err := url.QueryUnescape(string(ctx.Request.URL.RawQuery))
if err != nil {
ctx.Abort()
return
}
for _, v := range needReplaceString {
rawQuery = strings.ReplaceAll(rawQuery, v, newString)
}
ctx.Request.URL.RawQuery = rawQuery
}在路由中间件内或者路由函数内执行 SafeQuery 函数即可。
在JWT中设置一个永不过期的令牌(token)是可行的,你可以使用 jwt.StandardClaims 结构体的 ExpiresAt 字段来设置过期时间。将过期时间设置为一个未来的极大值或者将其留空,就可以创建一个永不过期的JWT。
以下是一个示例,演示如何在Go语言中创建一个永不过期的JWT:
package main
import (
"fmt"
"time"
"github.com/dgrijalva/jwt-go"
)
func main() {
// 创建一个JWT
token := jwt.New(jwt.SigningMethodHS256)
claims := token.Claims.(jwt.MapClaims)
claims["username"] = "johndoe"
claims["exp"] = time.Now().AddDate(100, 0, 0).Unix() // 过期时间设置为100年后
tokenString, err := token.SignedString([]byte("secret"))
if err != nil {
fmt.Println("Error creating JWT:", err)
return
}
fmt.Println("JWT:", tokenString)
}在这个示例中,我们将JWT的过期时间设置为100年后,这几乎等同于不设置过期时间。如果你想要一个真正的永不过期的JWT,可以将过期时间设置为0或者不设置过期时间,这样JWT就不会自动过期。
需要注意的是
虽然设置永不过期的JWT可以方便地进行身份验证和授权,但也增加了安全风险。如果你需要在JWT中存储敏感信息,请考虑将其加密,以确保数据的安全性。
GORM是一个非常流行的Go语言ORM框架,可以帮助开发者快速地搭建和管理数据库连接。在使用 GORM 时,需要进行一些配置,以适配应用程序和数据库的需求。下面是 GORM 的几个常用配置参数:
Dialect
指定数据库类型。GORM 支持多种数据库类型,例如MySQL、PostgreSQL、SQLite等等,可以通过Dialect参数进行配置。
DNS
指定数据库连接信息。DNS 参数用于配置数据库的连接信息,包括主机名、端口号、数据库名称、用户名、密码等。
MaxIdleConns
指定最大空闲连接数。MaxIdleConns 参数用于指定数据库连接池中最大的空闲连接数,即连接池中最多可以保留多少个空闲连接。
MaxOpenConns
指定最大打开连接数。MaxOpenConns 参数用于指定连接池中最大的连接数量,即连接池中最多可以打开多少个连接。
在设置 MaxOpenConns 参数时,需要考虑到应用程序的实际并发情况和数据库的处理能力。通常情况下,建议将 MaxOpenConns 设置为应用程序的最大并发数加上一定的缓冲值,以保证数据库连接池中有足够的连接资源。
具体来说,如果应用程序的最大并发数为N,可以将MaxOpenConns设置为N+10~50左右的值,这取决于应用程序的实际情况和数据库的性能。在实际使用中,可以通过压力测试和性能监控等手段,来确定合适的MaxOpenConns值。
需要注意的是,如果MaxOpenConns设置得太小,可能会导致应用程序在高并发的情况下无法获得足够的连接资源,从而影响应用程序的性能。而如果 MaxOpenConns 设置得太大,则可能会导致数据库连接数过多,从而消耗过多的系统资源,甚至会导致数据库崩溃。因此,在设置MaxOpenConns时需要慎重考虑,并结合实际情况进行合理的配置。
ConnMaxLifetime
指定连接最大存活时间。ConnMaxLifetime参数用于指定连接在连接池中最长的存活时间,即连接在连接池中的最长存活时间,超过这个时间连接将会被关闭并重新创建。
Logger
指定日志记录器。Logger参数用于指定GORM的日志记录器,可以选择不同级别的日志记录方式,例如打印SQL语句、打印错误信息等等。
NamingStrategy
指定命名策略。NamingStrategy 参数用于指定GORM的命名策略,例如表名、列名的命名方式等等。
在 GO 语言中,当我们当获取一个不存在的 map 元素时会返回什么呢?我们通过下面的例子进行总结 :
例1
map1 := map[string]string{
"name": "lesscode",
"class": "code001",
}
res := map1["test"]
fmt.Printf("res: %v--%T\n", res, res)
// 输出 空字符串:
// res: --string例2
map1 := map[string]int{
"key1": 1,
"key2": 2,
}
res := map1["name"]
fmt.Printf("res: %v %T\n", res, res)
// 输出 int 类型的 0
// res: 0 int例3
type Person struct {
Name string
}
func main() {
map1 := map[string]Person{
"key1": {Name: "test"},
}
res := map1["name"]
fmt.Printf("res: %v %T\n", res, res)
}
通过上面的例子我们可以总结 :
当我们当获取一个不存在的 map 元素时会返回对应 map 类型的”空元素”。
正则表达式是一种进行模式匹配和文本操纵的复杂而又强大的工具。
Go 通过 regexp 包为正则表达式提供了官方支持,其采用 RE2 语法,除了\c、\C外,Go语言和 Perl、Python 等语言的正则基本一致。
正则表达式是由普通字符(例如字符 a 到 z)以及特殊字符(称为"元字符")构成的文字序列,可以是单个的字符、字符集合、字符范围、字符间的选择或者所有这些组件的任意组合。
下面的表格中列举了构成正则表达式的一些语法规则及其含义。
一般字符 匹配自身 abc abc
. 匹配任意除换行符"\n"外的字符, 在 DOTALL 模式中也能匹配换行符 a.c abc ;
\ 转义字符,使后一个字符改变原来的意思;
[...] 字符集(字符类),对应的位置可以是字符集中任意字符。
字符集中的字符可以逐个列出,也可以给出范围,如 [abc] 或 [a-c],
第一个字符如果是 ^ 则表示取反,如 [^abc] 表示除了abc之外的其他字符。 a[bcd]e abe 或 ace 或 ade
\d 数字:[0-9] a\dc a1c
\D 非数字:[^\d] a\Dc abc
\s 空白字符:[<空格>\t\r\n\f\v] a\sc a c
\S 非空白字符:[^\s] a\Sc abc
\w 单词字符:[A-Za-z0-9] a\wc abc
\W 非单词字符:[^\w] a\Wc a c * 匹配前一个字符 0 或无限次 abc* ab 或 abccc
+ 匹配前一个字符 1 次或无限次 abc+ abc 或 abccc
? 匹配前一个字符 0 次或 1 次 abc? ab 或 abc
{m} 匹配前一个字符 m 次 ab{2}c abbc
{m,n} 匹配前一个字符 m 至 n 次,m 和 n 可以省略,若省略 m,则匹配 0 至 n 次;
若省略 n,则匹配 m 至无限次 ab{1,2}c abc 或 abbc ^ 匹配字符串开头,在多行模式中匹配每一行的开头 ^abc abc
$ 匹配字符串末尾,在多行模式中匹配每一行的末尾 abc$ abc
\A 仅匹配字符串开头 \Aabc abc
\Z 仅匹配字符串末尾 abc\Z abc
\b 匹配 \w 和 \W 之间 a\b!bc a!bc
\B [^\b] a\Bbc abc | 代表左右表达式任意匹配一个,优先匹配左边的表达式 abc|def abc 或 def
(...) 括起来的表达式将作为分组,分组将作为一个整体,可以后接数量词 (abc){2} abcabc
(?P<name>...) 分组,功能与 (...) 相同,但会指定一个额外的别名 (?P<id>abc){2} abcabc
\<number> 引用编号为 <number> 的分组匹配到的字符串 (\d)abc\1 1abe1 或 5abc5
(?P=name) 引用别名为 <name> 的分组匹配到的字符串 (?P<id>\d)abc(?P=id) 1abe1 或 5abc5 (?:...) (…) 的不分组版本,用于使用 "|" 或后接数量词 (?:abc){2} abcabc
(?iLmsux) iLmsux 中的每个字符代表一种匹配模式,只能用在正则表达式的开头,可选多个 (?i)abc AbC
(?#...) # 后的内容将作为注释被忽略。 abc(?#comment)123 abc123
(?=...) 之后的字符串内容需要匹配表达式才能成功匹配 a(?=\d) 后面是数字的 a
(?!...) 之后的字符串内容需要不匹配表达式才能成功匹配 a(?!\d) 后面不是数字的 a
(?<=...) 之前的字符串内容需要匹配表达式才能成功匹配 (?<=\d)a 前面是数字的a
(?<!...) 之前的字符串内容需要不匹配表达式才能成功匹配 (?<!\d)a 前面不是数字的a 下面通过几个示例来演示一下 regexp 包的使用。
package main
import (
"fmt"
"regexp"
)
func main() {
str := "abcdefg13456abcdefg13456"
reg := regexp.MustCompile("a.*3")
// FindString
res := reg.FindString(str)
fmt.Printf("res: %v\n", res)
// FindAllString
resArray := reg.FindAllString(str, -1)
fmt.Printf("res: %v\n", resArray)
// 禁止贪婪匹配
reg2 := regexp.MustCompile("(?U)a.*3")
resArray = reg2.FindAllString(str, -1)
fmt.Printf("res: %v\n", resArray)
// 替换示例
str = reg2.ReplaceAllString(str, "--")
fmt.Printf("str: %v\n", str)
}切片反序是指将一个切片中的元素按照相反的顺序重新排列,即将原来的最后一个元素放置在第一个位置,原来的第一个元素放置在最后一个位置,以此类推。
sort.Slice() 函数是 Go 语言中一个非常有用的排序函数。该函数可以对切片进行排序,同时也可以自定义排序规则。
sort.Slice() 函数的运行原理如下:
1 接收一个待排序的切片和一个 less 函数作为参数。less 函数接收两个参数,用于比较两个元素的大小,并返回 bool 类型的结果。 2 sort.Slice() 函数会使用快速排序算法对切片进行排序。 3 在排序的过程中,sort.Slice() 函数会调用 less 函数来比较切片中的元素大小。 4 排序完成后,sort.Slice() 函数返回排好序的切片。
示例
package main
import (
"fmt"
"sort"
)
func main() {
// 定义一个切片
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// 将切片反序
sort.Slice(nums, func(i, j int) bool { return i > j })
// 打印反序后的切片
fmt.Println(nums)
}
// 输出
// [10 9 8 7 6 5 4 3 2 1]可以通过 net.InterfaceAddrs() 来获取内网相关数据,从而获得内网 IP :
package main
import (
"fmt"
"net"
"os"
)
// 获取内网 IP
func GetIntranetIp() string {
addrs, err := net.InterfaceAddrs()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
IntranetIp := ""
for _, address := range addrs {
// 检查ip地址判断是否回环地址
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
IntranetIp = ipnet.IP.String()
break
}
}
}
return IntranetIp
}
func main() {
ip := GetIntranetIp()
fmt.Printf("ip: %v\n", ip)
}
回调函数是一种通过将函数作为参数传递给其他函数,并在需要时由其他函数调用的技术。
要使用回调函数,需要先定义一个函数类型,然后将该类型的函数作为参数传递给其他函数。
以下是一个简单的回调函数的示例:
package main
import (
"fmt"
)
// 定义一个函数类型
type Callback func(int) int
// 执行回调函数的函数
func process(data int, callback Callback) int {
// 调用回调函数
result := callback(data)
return result
}
func main() {
// 通过匿名函数实现回调函数
result := process(5, func(i int) int {
return i + 2
})
fmt.Println(result) // 输出 7
}
package main
import (
"fmt"
)
// 定义一个函数类型
type Callback func(...any) int
// 执行回调函数的函数
func process(data int, callback Callback) int {
// 调用回调函数
result := callback(data)
return result
}
func main() {
// 通过匿名函数实现回调函数
result := process(5, func(args ...any) int {
num, ok := args[0].(int)
if ok {
return num + 3
} else {
return 0
}
})
fmt.Println(result) // 输出 8
}
在Go语言中,可以使用os和io/ioutil包来追加内容到文件。示例代码:
package main
import (
"fmt"
"os"
)
func main() {
// 打开文件,如果文件不存在则创建
file, err := os.OpenFile("demo.txt", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0777)
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
// 要追加的内容
content := "新的内容\n"
// 追加内容到文件
_, err = file.WriteString(content)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("执行成功")
}
在上面的演示示例中 :
我们使用 os.OpenFile 函数打开一个名为 demo.txt 的文件。
通过指定 os.O_APPEND 标志,声明在文件末尾追加内容而不是覆盖原有内容。
另外,我们还指定了os.O_WRONLY标志以只写方式打开文件,以及os.O_CREATE标志以创建文件(如果文件不存在)。
文件权限设置为 0777。
最后,我们定义了要追加的内容,并使用 file.WriteString 方法将其写入文件。
在 Gin 框架中,SetTrustedProxies 是一个设置函数,用于指定哪些代理可以信任并发送请求头 X-Forwarded-For 和 X-Real-IP。
当使用 Gin 构建 API 服务器时,通常部署在反向代理(例如 Nginx)后面。客户端的请求通过反向代理转发到 Gin 服务器,Gin 服务器通过 X-Forwarded-For 和 X-Real-IP 获取客户端的真实 IP 地址。
默认情况下,Gin 会信任所有的代理服务器,这意味着所有的代理服务器都会被 Gin 认为是可信任的,并将它们的 IP 地址作为 X-Forwarded-For 和 X-Real-IP 的值。这可能会导致安全问题,因为攻击者可以通过伪造代理服务器的 IP 地址来攻击你的服务器。
为了解决这个问题,Gin 提供了 SetTrustedProxies 方法,可以指定哪些代理可以信任。这个方法接收一个字符串切片参数,参数中的每个字符串都代表一个可以信任的代理服务器 IP 地址。例如:
func main() {
router := gin.Default()
// 设置 Gin 只信任本机的代理服务器
router.SetTrustedProxies([]string{"127.0.0.1"})
// ...
}在上面的例子中,Gin 只信任本机的代理服务器,即只有来自 127.0.0.1 的请求会被 Gin 认为是可信任的,并将它们的 IP 地址作为 X-Forwarded-For 和 X-Real-IP 的值。其他代理服务器的 IP 地址会被 Gin 忽略。这样可以有效地防止攻击者通过伪造代理服务器的 IP 地址来攻击你的服务器。
pprof是Go的性能分析工具,在程序运行过程中,可以记录程序的运行信息,可以是CPU使用情况、内存使用情况、goroutine运行情况等,当需要性能调优或者定位Bug时候,这些记录的信息是相当重要。 go 对于 profiling 支持的比较好,标准库就提供了profile 库 runtime/pprof 和 net/http/pprof,而且也提供了很多好用的可视化工具来辅助开发者做 profiling。 对于在线服务,对于一个 HTTP Server,访问 pprof 提供的 HTTP 接口,获得性能数据。
pprof 的应用场景主要分为两种:
1. 服务型应用,例如 web 服务器等各种服务类型端的性能分析; 2. 工具型应用,例如一些命令行工具,执行完毕后直接退出的应用;
工具型应用性能分析基于 "runtime/pprof" 包,将性能数据存放在文件中 :
package main
import (
"fmt"
"os"
"runtime/pprof"
)
func main() {
// 创建 cup 数据记录文件
cpuProfile, err := os.Create("./cpu_profile")
if err != nil {
fmt.Printf("创建文件失败:%s", err.Error())
return
}
defer cpuProfile.Close()
// 创建内存数据记录文件
memProfile, err := os.Create("./mem_profile")
if err != nil {
fmt.Printf("创建文件失败:%s", err.Error())
return
}
defer memProfile.Close()
//采集CPU信息
pprof.StartCPUProfile(cpuProfile)
defer pprof.StopCPUProfile()
//采集内存信息
pprof.WriteHeapProfile(memProfile)
// 执行一个逻辑
for i := 0; i < 9999; i++ {
fmt.Println("pprof 工具型测试")
}
println("-- mian done --")
}
执行上面的代码后,会产生2个性能数据文件 : cpu_profile 和 mem_profile,可以使用 go tool pprof 命令查看数据 :
# 01. 执行命令
# cup 使用数据
go tool pprof .\cpu_profile
# 内存使用数据
# go tool pprof .\mem_profile
# 02. 回显 :
PS ...\goDemo> go tool pprof .\cpu_profile
File: main.exe
Build ID: ...\main.exe2023-10-11 13:54:05.4308623 +0800 CST
Type: cpu
Time: Oct 11, 2023 at 1:54pm (CST)
Duration: 979.59ms, Total samples = 510ms (52.06%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)
# 03. 输入命令继续
top5
# 数据内容 :
flat flat% sum% cum cum%
510ms 100% 100% 510ms 100% runtime.cgocall
0 0% 100% 510ms 100% fmt.Fprintln
0 0% 100% 510ms 100% fmt.Println
0 0% 100% 510ms 100% internal/poll.(*FD).Write
0 0% 100% 510ms 100% internal/poll.(*FD).writeConsole列信息字段作用 :
flat:函数在 CPU 上运行的时间
flat%:函数在CPU上运行时间的百分比
sum%:是从第一行到当前行所有函数累加使用 CPU 的比例,如第二行sum=53.85=30.77+23.08
cum:这个函数以及子函数运行所占用的时间,应该大于等于flat
cum%:这个函数以及子函数运行所占用的比例,应该大于等于flat%
最后一列:函数的名字您还可以开启一个 web 服务,以 web 形式查看性能数据 :
go tool pprof -http=:8808 .\mem_profile注意 : 需要安装 graphviz
Graphviz 是一款由 AT&T Research 和 Lucent Bell 实验室开源的可视化图形工具,可以很方便的用来绘制结构化的图形网络,支持多种格式输出。Graphviz 输入是一个用 dot 语言编写的绘图脚本,通过对输入脚本的解析,分析出其中的点、边及子图,然后根据属性进行绘制。Graphviz layout 以简单的文本语言描述图形,并以实用的格式制作图表,如用于网页的 images 和 SVG ;用于放入在其它文件中或显示在交互式图形浏览器中的 PDF 和 Postscript 。
官网 : https://www.graphviz.org/
下载及安装 graphviz : https://www.graphviz.org/download/
go tool pprof -http=:8808 .\mem_profile会打开一个浏览器窗口,以图形界面形式展示性能数据 :

对于服务类型的应用,主要在服务内部匿名引入 net/http/pprof 包,然后通过 HTTP 访问 pprof 页面。
package main
import (
"fmt"
"net/http"
_ "net/http/pprof"
)
func main() {
http.HandleFunc("/", Test)
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println("ListenAndServe Err:", err.Error())
return
}
}
func Test(resp http.ResponseWriter, req *http.Request) {
fmt.Fprintln(resp, "")
}上面的代码开启了一个 HTTP 服务,端口为 8080,可以通过浏览器访问 :
http://localhost:8080/ 进行测试。
查看服务性能数据 :
执行http://localhost:8080/debug/pprof/可以看到画像信息:

项目功能描述
allocs:过去所有内存分配的采样
block:导致同步基元阻塞的堆栈跟踪
cmdline:当前程序的命令行调用
goroutine:堆栈所有当前goroutine的跟踪。使用debug=2作为查询参数,以与未恢复的死机相同的格式导出。
heap:活动对象的内存分配的采样。您可以指定gc GET参数以在获取堆样本之前运行gc。
mutex:争用互斥体持有者的堆栈跟踪
profile:CPU配置文件。您可以在seconds GET参数中指定持续时间。获取配置文件后,使用go tool pprof命令来调查该配置文件。
threadcreate:导致创建新操作系统线程的堆栈跟踪
trace:当前程序执行的跟踪。您可以在seconds GET参数中指定持续时间。获取跟踪文件后,使用go tool trace命令来调查跟踪。记录指定时间段内的性能数据 :
浏览器访问 : http://localhost:8080/debug/pprof/profile?seconds=10
10 秒( 可以通过参数设置监控时长 )后 ( 期间可以访问网站首页或者使用 ab 进行压测 ) 会自动下载得到一个性能文件 : profile。
在 profile 同目录下执行 :
go tool pprof .\profile
# 回显
Type: cpu
...
# 继续输入
top
# 显示
Showing top 10 nodes out of 146
flat flat% sum% cum cum%
1700ms 28.05% 28.05% 1710ms 28.22% runtime.cgocall
560ms 9.24% 37.29% 560ms 9.24% runtime.stdcall1
450ms 7.43% 44.72% 460ms 7.59% runtime.stdcall6
350ms 5.78% 50.50% 1910ms 31.52% github.com/cnlesscode/graceMessageQueue/kernel.SaveMsgToDisk
310ms 5.12% 55.61% 320ms 5.28% runtime.mapaccess1_faststr
120ms 1.98% 57.59% 150ms 2.48% runtime.chanrecv
110ms 1.82% 59.41% 570ms 9.41% runtime.netpoll
80ms 1.32% 60.73% 80ms 1.32% runtime.siftupTimer
70ms 1.16% 61.88% 70ms 1.16% runtime.casgstatus
70ms 1.16% 63.04% 80ms 1.32% runtime.lock2
# 使用 list 命令查看耗时函数
list github.com/cnlesscode/graceMessageQueue/kernel.SaveMsgToDisk以 web 形式查看数据
go tool pprof -http=:9090 .\profile