一文搞懂Go語(yǔ)言堆內(nèi)存原理小結(jié)
一、基本概念理解
1.1 什么是堆內(nèi)存?
堆內(nèi)存是程序內(nèi)存中用于動(dòng)態(tài)內(nèi)存分配的部分。堆內(nèi)存不是在編譯過(guò)程中預(yù)先確定的,而是在程序運(yùn)行過(guò)程中動(dòng)態(tài)管理的。程序在執(zhí)行過(guò)程中可以根據(jù)需要從堆中申請(qǐng)、釋放內(nèi)存。
在繼續(xù)介紹之前,試著了解一下進(jìn)程的內(nèi)存布局,如下圖所示,可以簡(jiǎn)單了解大致的內(nèi)存布局。
+ - - - - - - - - - - - - - - - +
| Stack | ←- 棧,靜態(tài)分配
| - - - - - - - - - - - - - - - |
| Heap | ←- 堆,動(dòng)態(tài)分配
| - - - - - - - - - - - - - - - |
| Uninitialized Data | ←- 未初始化數(shù)據(jù)
| - - - - - - - - - - - - - - - |
| Initialized Data | ←- 初始化數(shù)據(jù)
| - - - - - - - - - - - - - - - |
| Code | ←- 代碼(文本段)
+ - - - - - - - - - - - - - - - +
進(jìn)程內(nèi)存布局
我們來(lái)分解一下進(jìn)程的內(nèi)存布局,看看它們是如何協(xié)同工作的:
- 棧(Stack):這部分內(nèi)存用于靜態(tài)內(nèi)存分配,是存儲(chǔ)局部變量和函數(shù)調(diào)用信息的地方,會(huì)隨著函數(shù)的調(diào)用和返回而自動(dòng)增大和縮小。
- 堆(Heap):這是動(dòng)態(tài)內(nèi)存分配區(qū)域。當(dāng)程序需要申請(qǐng)未預(yù)先定義的內(nèi)存時(shí),就會(huì)向堆申請(qǐng)空間。這里的內(nèi)存可以在運(yùn)行時(shí)分配和釋放,為程序提供了處理數(shù)組、鏈表等動(dòng)態(tài)數(shù)據(jù)結(jié)構(gòu)所需的靈活性。
- 未初始化數(shù)據(jù)(BSS 段):該段存放開(kāi)發(fā)者已聲明但并未初始化的全局變量和靜態(tài)變量。程序啟動(dòng)時(shí),操作系統(tǒng)會(huì)將這些變量初始化為零。
- 初始化數(shù)據(jù):該區(qū)域包含開(kāi)發(fā)者已初始化的全局變量和靜態(tài)變量。程序一開(kāi)始運(yùn)行,這些變量就可以立即使用。
- 代碼(文本段):該段存儲(chǔ)程序的可執(zhí)行指令。通常這部分內(nèi)存是只讀的,以防止意外修改程序指令。
1.2 堆內(nèi)存的特點(diǎn)
動(dòng)態(tài)分配:內(nèi)存在運(yùn)行時(shí)申請(qǐng)、釋放。
可變大?。悍峙涞膬?nèi)存大小可以變化。
基于指針的管理:使用指針訪問(wèn)和控制內(nèi)存。
+ - - - - - - - - - - -+
| Heap Memory. | ←- 堆內(nèi)存
| - - - - - - - - - - -|
| Free Block | ←- 空閑塊
| - - - - - - - - - - -|
| Allocated Block 1 | ←- 已分配塊1
| [Pointer -> Data] |
| - - - - - - - - - - -|
| Free Block | ←- 空閑塊
| - - - - - - - - - - -|
| Allocated Block 2 | ←- 已分配塊2
| [Pointer -> Data] |
| - - - - - - - - - - -|
| Free Block. | ←- 空閑塊
+ - - - - - - - - - - -+
動(dòng)態(tài)分配
- 空閑塊(Free Blocks):這些是當(dāng)前未分配的內(nèi)存塊,可供將來(lái)使用。當(dāng)程序請(qǐng)求內(nèi)存時(shí),可以從這些空閑塊中獲取。
- 已分配塊(Allocated Blocks):這些部分已分配給程序并儲(chǔ)存了數(shù)據(jù)。每個(gè)已分配塊通常都包含一個(gè)指向其所含數(shù)據(jù)的指針。
多個(gè)空閑塊和已分配塊的存在表明,內(nèi)存的分配和釋放在程序運(yùn)行過(guò)程中不斷發(fā)生。由于內(nèi)存分配和釋放的時(shí)間不同,導(dǎo)致空閑內(nèi)存段和已用內(nèi)存段交替出現(xiàn),堆就會(huì)出現(xiàn)這種碎片化現(xiàn)象。
1.3 前置知識(shí):棧與堆的根本區(qū)別
要理解堆,必先理解棧。在 Go 程序中,每個(gè) Goroutine 都有一個(gè)獨(dú)立的棧,而所有 Goroutine 共享一個(gè)堆。
| 特性 | 棧 | 堆 |
|---|---|---|
| 所有權(quán) | Goroutine 獨(dú)有 | 進(jìn)程內(nèi)所有 Goroutine 共享 |
| 分配與釋放 | 編譯器/運(yùn)行時(shí)自動(dòng)管理,函數(shù)入棧時(shí)分配,出棧時(shí)釋放,速度極快 | 垃圾回收器管理,分配相對(duì)較慢,釋放時(shí)機(jī)不確定(GC時(shí)) |
| 大小 | 小而固定(初始幾KB,可動(dòng)態(tài)增長(zhǎng),但有上限) | 非常大(可達(dá) TB 級(jí)別,受限于系統(tǒng)內(nèi)存) |
| 存儲(chǔ)數(shù)據(jù) | 函數(shù)參數(shù)、局部變量、返回地址等生命周期明確的數(shù)據(jù) | 生命周期不確定的數(shù)據(jù),比如函數(shù)返回后仍需被訪問(wèn)的對(duì)象 |
| 訪問(wèn)速度 | 快(連續(xù)內(nèi)存,CPU緩存友好) | 慢(內(nèi)存不連續(xù),可能涉及系統(tǒng)調(diào)用) |
1.4 堆內(nèi)存如何工作?
堆內(nèi)存由操作系統(tǒng)管理。當(dāng)程序請(qǐng)求內(nèi)存時(shí),操作系統(tǒng)會(huì)從進(jìn)程的堆內(nèi)存段中分配內(nèi)存。這一過(guò)程涉及多個(gè)關(guān)鍵組件和功能:
主要組成部分:
- 堆內(nèi)存段:進(jìn)程內(nèi)存中保留用于動(dòng)態(tài)分配的部分
- mmap:調(diào)整數(shù)據(jù)段末尾以增加或減少堆大小的系統(tǒng)調(diào)用
- malloc 和 free:C 庫(kù)提供的函數(shù),用于分配和釋放堆上的內(nèi)存
- 內(nèi)存管理器:C 庫(kù)的一個(gè)組件,用于管理堆,跟蹤已分配和已釋放的內(nèi)存塊。
1.5 核心原理:Go 對(duì)象如何分配到堆上?
當(dāng)一個(gè) Goroutine 需要在堆上分配內(nèi)存時(shí),流程如下:
- 獲取 P:該 Goroutine 綁定到某個(gè) P(邏輯處理器)上。
- 查找 mcache:從 P 中獲取其專屬的內(nèi)存緩存
mcache。 - 大小分級(jí):根據(jù)要分配的對(duì)象大小,選擇合適的規(guī)格:
- 微小對(duì)象 (< 16 bytes):直接在
mcache中用一個(gè)專門(mén)的tiny對(duì)象來(lái)處理,避免浪費(fèi)。 - 小對(duì)象 (< 32KB):在
mcache中尋找對(duì)應(yīng)大小規(guī)格的mspan(內(nèi)存跨度)來(lái)分配。 - 大對(duì)象 (>= 32KB):直接跳過(guò)
mcache和mcentral,向全局的mheap申請(qǐng)內(nèi)存。
關(guān)鍵點(diǎn):絕大多數(shù)對(duì)象都是小對(duì)象,它們的分配都可以在mcache中無(wú)鎖完成,速度極快。
- 微小對(duì)象 (< 16 bytes):直接在
核心問(wèn)題:編譯器如何決定一個(gè)對(duì)象放棧上還是堆上?
答案是:逃逸分析。
Go 編譯器會(huì)分析代碼,如果一個(gè)局部變量的作用域超出了它所在的函數(shù)(即“逃逸”了),那么它就必須被分配在堆上。如:
// 情況一:不逃逸,分配在棧上
func stackExample() int {
x := 10 // x 的生命周期只在 stackExample 函數(shù)內(nèi)
return x
}
// 情況二:逃逸,分配在堆上
func heapExample() *int {
x := 10 // x 的指針被返回,作用域超出了函數(shù),x 逃逸到堆上
return &x
}
可以使用 go build -gcflags="-m" 命令來(lái)查看逃逸分析的結(jié)果:
$ go build -gcflags="-m" main.go # command-line-arguments ./main.go:10:6: can inline heapExample ./main.go:11:2: leaking param: x ./main.go:11:2: moved to heap: x
moved to heap: x 明確告訴我們變量 x 被分配到了堆上。
1.6 Go 內(nèi)存分配器:TCMalloc 的繼承與演進(jìn)
Go 的內(nèi)存分配器高度借鑒了 Google 的 TCMalloc。其核心思想是:避免多線程競(jìng)爭(zhēng),將內(nèi)存管理工作分?jǐn)偟矫總€(gè)處理器(P)上。
這帶來(lái)了兩個(gè)核心設(shè)計(jì):
- 無(wú)鎖化:每個(gè) P 都有自己的本地內(nèi)存緩存,大部分分配操作都在 P 內(nèi)部完成,無(wú)需加鎖。
- 分級(jí)管理:將內(nèi)存按大小分為不同級(jí)別,用不同策略管理,提高分配效率。
二、Go 如何管理堆內(nèi)存
Go 為堆內(nèi)存管理提供了內(nèi)置函數(shù)和數(shù)據(jù)結(jié)構(gòu),如 new、make、slices、maps 和 channels。這些函數(shù)和數(shù)據(jù)結(jié)構(gòu)抽象掉了底層細(xì)節(jié),在內(nèi)部與操作系統(tǒng)的內(nèi)存管理機(jī)制進(jìn)行了交互。
2.1 案例
我們通過(guò)一個(gè)簡(jiǎn)單的 Go 程序來(lái)理解,該程序?yàn)檎麛?shù)片段分配內(nèi)存、初始化數(shù)值并打印。(main.go)
package main
import (
"fmt"
"runtime"
)
func main() {
// 為包含10個(gè)整數(shù)的切片分配內(nèi)存(動(dòng)態(tài)數(shù)組)
memorySize := 10
slice := make([]int, memorySize)
// 初始化并使用分配的內(nèi)存
for i := 0; i < len(slice); i++ {
slice[i] = 5 // 為每個(gè)元素賦值
}
// 打印值
for i := 0; i < len(slice); i++ {
fmt.Printf("%d ", slice[i])
}
fmt.Println()
// 通過(guò)強(qiáng)制垃圾收集演示內(nèi)存釋放
runtime.GC()
}
為了了解 Go 如何與 Linux 內(nèi)存管理庫(kù)交互,可以使用 strace(centos系統(tǒng)可通過(guò):yum install strace安裝)來(lái)跟蹤 Go 程序進(jìn)行的系統(tǒng)調(diào)用。
2.2 內(nèi)存分配中的系統(tǒng)調(diào)用
$ go build -o memory_allocation main.go $ strace -f -e trace=mmap,munmap ./memory_allocation
執(zhí)行結(jié)果如下:
mmap(NULL, 262144, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f50caa6b000 mmap(NULL, 131072, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f50caa4b000 mmap(NULL, 1048576, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f50ca94b000 mmap(NULL, 8388608, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f50ca14b000 mmap(NULL, 67108864, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f50c614b000 mmap(NULL, 536870912, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f50a614b000 mmap(NULL, 536870912, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f508614b000 mmap(0xc000000000, 67108864, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xc000000000 mmap(NULL, 33554432, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f508414b000 mmap(NULL, 69648, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5084139000 mmap(0xc000000000, 4194304, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xc000000000 mmap(0x7f50caa4b000, 131072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f50caa4b000 mmap(0x7f50ca9cb000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f50ca9cb000 mmap(0x7f50ca551000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f50ca551000 mmap(0x7f50c817b000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f50c817b000 mmap(0x7f50b62cb000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f50b62cb000 mmap(0x7f50962cb000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f50962cb000 mmap(NULL, 1048576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5084039000 mmap(NULL, 65536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5084029000 mmap(NULL, 65536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5084019000 mmap(NULL, 1439992, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5083eb9000 strace: Process 1425438 attached [pid 1425437] mmap(NULL, 262144, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5083e79000 strace: Process 1425439 attached strace: Process 1425440 attached [pid 1425437] mmap(NULL, 262144, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5083e39000 strace: Process 1425441 attached 5 5 5 5 5 5 5 5 5 5 [pid 1425437] mmap(NULL, 65536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5083e29000 [pid 1425440] +++ exited with 0 +++ [pid 1425439] +++ exited with 0 +++ [pid 1425438] +++ exited with 0 +++ [pid 1425441] +++ exited with 0 +++ +++ exited with 0 +++
+ - - - - - - - - - - -+
| Go Program | ←- Go 程序
| - - - - - - - - - - -|
| Calls Go Runtime | ←- 調(diào)用 Go 運(yùn)行時(shí)
| - - - - - - - - - - -|
| Uses syscalls: | ←- 系統(tǒng)調(diào)用:mmap,munmap
| mmap, munmap |
| - - - - - - - - - - -|
| Interacts with OS | ←- 與操作系統(tǒng)內(nèi)存管理器交互
| Memory Manager |
+ - - - - - - - - - - -+
系統(tǒng)調(diào)用的簡(jiǎn)化示例
strace 輸出解釋
- mmap 調(diào)用:mmap 系統(tǒng)調(diào)用用于分配內(nèi)存頁(yè)。輸出中的每個(gè) mmap 調(diào)用都是請(qǐng)求操作系統(tǒng)分配特定數(shù)量(用 size 參數(shù)指定,例如 262144、131072 字節(jié))的內(nèi)存,。
- 內(nèi)存保護(hù)(Memory Protections):參數(shù) PROT_READ|PROT_WRITE 表示分配的內(nèi)存應(yīng)是可讀和可寫(xiě)的。
- 匿名映射(Anonymous Mapping):MAP_PRIVATE|MAP_ANONYMOUS 標(biāo)記表示內(nèi)存沒(méi)有任何文件支持,所做更改對(duì)進(jìn)程來(lái)說(shuō)是私有的。
- 固定地址映射(Fixed Address Mapping):有些 mmap 調(diào)用使用 MAP_FIXED 標(biāo)記,指定內(nèi)存應(yīng)映射到特定地址,通常用于直接管理特定內(nèi)存區(qū)域。
2.3 內(nèi)存分配過(guò)程的各個(gè)階段:
+ - - - - - - - - - - -+ | Initialize Slice | ←- 初始化切片 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] | | - - - - - - - - - - -| | Set Values | ←- 設(shè)置值 | [5, 5, 5, 5, 5, 5, 5, 5, 5, 5] | | - - - - - - - - - - -| | Print Values | ←- 打印值 | 5 5 5 5 5 5 5 5 5 5 | | - - - - - - - - - - -| | Force GC | ←- 強(qiáng)制垃圾回收 | - - - - - - - - - - -|
上圖說(shuō)明了 Go 動(dòng)態(tài)內(nèi)存分配和管理的逐步過(guò)程。
- 1、初始化切片:
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],切片(動(dòng)態(tài)數(shù)組)的初始狀態(tài)為 10 個(gè)元素,全部設(shè)置為 0。這一步展示了 Go 如何為切片分配內(nèi)存。 - 2、設(shè)置值:
[5, 5, 5, 5, 5, 5, 5, 5, 5, 5],然后,在切片的每個(gè)元素中填入值 5。這一步演示了如何初始化和使用分配的內(nèi)存。 - 3、打印值:
5 5 5 5 5 5 5 5 5 5,打印切片的值,確認(rèn)內(nèi)存分配和初始化成功。這一步驗(yàn)證程序是否正確訪問(wèn)和使用了分配的內(nèi)存。 - 4、強(qiáng)制 GC(垃圾回收):手動(dòng)觸發(fā)垃圾回收器,釋放不再使用的內(nèi)存。這一步強(qiáng)調(diào) Go 的自動(dòng)內(nèi)存管理和清理過(guò)程,確保了資源的有效利用。
三、 堆內(nèi)存管理:三級(jí)結(jié)構(gòu)
Go 的堆內(nèi)存管理是一個(gè)精密的三級(jí)(或四級(jí))結(jié)構(gòu),理解它就理解了 Go 內(nèi)存管理的核心。
3.1 第一級(jí):mcache (Per-P Cache)
- 是什么:每個(gè) P 都有一個(gè)獨(dú)立的
mcache。 - 作用:存儲(chǔ)各種大小規(guī)格的
mspan的空閑列表。 - 特點(diǎn):無(wú)鎖分配。因?yàn)?P 同一時(shí)間只能被一個(gè) Goroutine 占用,所以從
mcache分配內(nèi)存是線程安全的,無(wú)需加鎖。這是 Go 高并發(fā)內(nèi)存分配性能高的關(guān)鍵。
3.2 第二級(jí):mcentral (Central Cache)
- 是什么:全局的內(nèi)存中心,所有 P 共享。它按
spanclass(大小規(guī)格)分為多個(gè)mcentral。 - 作用:當(dāng)
mcache中的mspan不夠用時(shí),P 會(huì)向?qū)?yīng)的mcentral申請(qǐng)新的mspan。 - 特點(diǎn):需要加鎖。因?yàn)槎鄠€(gè) P 可能同時(shí)向同一個(gè)
mcentral申請(qǐng)內(nèi)存,所以需要加鎖保證線程安全。
3.3 第三級(jí):mheap (Heap Manager)
- 是什么:全局唯一的堆內(nèi)存管理器,掌管著所有從操作系統(tǒng)申請(qǐng)來(lái)的大塊內(nèi)存。
- 作用:
- 管理
mcentral,當(dāng)mcentral的mspan不足時(shí),向mheap申請(qǐng)。 - 直接處理大對(duì)象(>= 32KB)的分配請(qǐng)求。
- 當(dāng)內(nèi)存不足時(shí),向操作系統(tǒng)申請(qǐng)更多內(nèi)存(調(diào)用
mmap)。
- 管理
3.4 基礎(chǔ)單元:mspan (Memory Span)
- 是什么:
mcache、mcentral、mheap之間流轉(zhuǎn)的基本單位。它是一段連續(xù)的內(nèi)存地址,由多個(gè)頁(yè)組成。 - 作用:
mspan會(huì)被劃分為特定大小的塊,用于存儲(chǔ)同一種規(guī)格的對(duì)象。例如,一個(gè)mspan可能專門(mén)用來(lái)存放所有 16 字節(jié)大小的對(duì)象。
流程串聯(lián): Goroutine -> mcache (無(wú)鎖) -> mcentral (加鎖) -> mheap (全局鎖) -> OS
到此這篇關(guān)于一文搞懂Go語(yǔ)言堆內(nèi)存原理小結(jié)的文章就介紹到這了,更多相關(guān)Go語(yǔ)言 堆內(nèi)存內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
通過(guò)案例詳細(xì)聊聊Go語(yǔ)言的變量與常量
在任何一門(mén)現(xiàn)代的高級(jí)語(yǔ)言中,變量和常量都是它非常基礎(chǔ)的程序結(jié)構(gòu)的組成部分,下面這篇文章主要給大家介紹了關(guān)于如何通過(guò)案例詳細(xì)聊聊Go語(yǔ)言的變量與常量的相關(guān)資料,需要的朋友可以參考下2023-03-03
linux下通過(guò)go語(yǔ)言獲得系統(tǒng)進(jìn)程cpu使用情況的方法
這篇文章主要介紹了linux下通過(guò)go語(yǔ)言獲得系統(tǒng)進(jìn)程cpu使用情況的方法,實(shí)例分析了Go語(yǔ)言使用linux的系統(tǒng)命令ps來(lái)分析cpu使用情況的技巧,需要的朋友可以參考下2015-03-03
Go語(yǔ)言實(shí)現(xiàn)常見(jiàn)限流算法的示例代碼
計(jì)數(shù)器、滑動(dòng)窗口、漏斗算法、令牌桶算法是我們常見(jiàn)的幾個(gè)限流算法,本文將依次用Go語(yǔ)言實(shí)現(xiàn)這幾個(gè)限流算法,感興趣的可以了解一下2023-05-05
golang環(huán)形隊(duì)列實(shí)現(xiàn)代碼示例
這篇文章主要介紹了golang環(huán)形隊(duì)列實(shí)現(xiàn)代碼示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11
Go語(yǔ)言定時(shí)任務(wù)cron的設(shè)計(jì)與使用
這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言中定時(shí)任務(wù)cron的設(shè)計(jì)與使用,文中的示例代碼講解詳細(xì),對(duì)我們深入掌握Go語(yǔ)言有一定的幫助,需要的可以參考下2023-11-11
Golang基于JWT與Casbin身份驗(yàn)證授權(quán)實(shí)例詳解
這篇文章主要為大家介紹了Golang基于JWT與Casbin實(shí)現(xiàn)身份驗(yàn)證授權(quán)實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08

