Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a72061c

Browse files
committed
docs(casbin): adapter, watcher, enforcer usage
1 parent 09632fe commit a72061c

File tree

5 files changed

+267
-4
lines changed

5 files changed

+267
-4
lines changed

content/docs/basic/01_basic_type.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,23 @@ func (b *Builder) String() string {
127127

128128
### 类型转换
129129

130-
在日常开发中,`string``[]byte` 之间的转换是很常见的,不管是 `string``[]byte` 还是 `[]byte``string` 都需要拷贝数据,而内存拷贝带来的性能损耗会随着字符串和 `[]byte` 长度的增长而增长。
130+
在日常开发中,`string``[]byte` 之间的转换是很常见的,不管是 `string``[]byte` 还是 `[]byte``string` 都需要拷贝数据,而内存拷贝带来的性能损耗会随着字符串和 `[]byte` 长度的增长而增长。
131+
132+
## interface{} 和 any
133+
134+
`interface{}``any` 都是 Go 语言中表示**任意类型**的类型,但是它们的含义和使用方式有一些不同的背景和语境。
135+
136+
- `interface{}` 是 Go 语言的一个**空接口类型,表示没有方法集合的接口**。任何类型都实现了空接口,因为空接口没有要求具体实现任何方法。换句话说,**`interface{}` 可以持有任何类型的值**
137+
- `any` 是 Go 1.18 引入的一个新的别名,它是 `interface{}` 的类型**别名**。从语义上讲,`any``interface{}` 是等价的,但 `any` 是为了增强代码的可读性和清晰度。在新的 Go 代码中,使用 `any` 可以更明确地表达类型含义,避免误解。
138+
139+
```go
140+
var x interface{}
141+
x = 42 // 可以存储 int 类型
142+
x = "hello" // 也可以存储 string 类型
143+
x = true // 甚至可以存储 bool 类型
144+
145+
var x2 any
146+
x2 = 42 // 可以存储 int 类型
147+
x2 = "hello" // 也可以存储 string 类型
148+
x2 = true // 甚至可以存储 bool 类型
149+
```

content/docs/basic/03_slice.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ func main() {
8989
![slice-cut-append](https://gitee.com/shipengqi/illustrations/raw/main/go/slice-cut-append.png)
9090

9191
再次追加一个元素 `s2 = append(s2, 200)``s2` 的容量不够了,需要扩容,于是 `s2` 申请一块新的连续内存,并将数据拷贝过去,扩容后的容量是原来的 2 倍。
92-
这时候 `s2``Data` 指向了新的底层数组,已经和 `s1` `slice` 没有关系了, `s2` 的修改不会再影响 `s1` `slice`
92+
这时候 `s2``Data` 指向了新的底层数组,已经和 `s1` 这个 `slice` 没有关系了,所以对 `s2` 的修改不会再影响 `s1`
9393

9494
![slice-cut-append2](https://gitee.com/shipengqi/illustrations/raw/main/go/slice-cut-append2.png)
9595

@@ -114,7 +114,7 @@ Go 1.18 后切片的扩容策略:
114114

115115
Go 是值传递。那么传入一个切片,切片会不会被函数中的操作改变?
116116

117-
**不管传入的是切片还是切片指针,如果改变了底层数组,原切片的底层数组也会被改变**
117+
**不管传入的是切片还是切片指针,如果改变了底层数组,那么外部切片的底层数组也会被改变**
118118

119119
示例:
120120

content/docs/basic/04_map.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,3 +398,22 @@ map 在扩容后,`key/value` 会进行迁移,在同一个桶中的 key,有
398398

399399
Go 底层的实现简单粗暴,直接生成一个随机数,这个随机数决定从哪里开始遍历,因此**每次 `for range map` 的结果都是不一样的。那是因为它的起始位置根本就不固定**
400400

401+
## map 传入函数
402+
403+
```go
404+
package main
405+
406+
import "fmt"
407+
408+
func main() {
409+
var ts map[string]string
410+
ts2 := make(map[string]string)
411+
fmt.Println(ts, len(ts))
412+
fmt.Println(ts2, len(ts2))
413+
ts2["test1"] = "test1"
414+
fmt.Println(ts2, len(ts2))
415+
// ts["test1"] = "test1" // nil map 不能进行写操作
416+
v := ts["test1"] // nil map 可以进行读操作
417+
fmt.Println("-------", v)
418+
}
419+
```

content/docs/project/14_error.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func WithStack(err error) error
6565
func Cause(err error) error
6666
```
6767

68-
![error-handle](https://github.com/shipengqi/illustrations/blob/63b742cee77624dfc1f8de5946f051e3c8f395be/go/error-handle.png?raw=true)
68+
![error-handle](https://gitee.com/shipengqi/illustrations/raw/main/go/error-handle.png)
6969

7070
{{< callout type="info" >}}
7171
调用第三方库或者标准库也考虑使用 `errors.Wrap` 保存堆栈信息。

content/docs/project/16_casbin.md

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,141 @@ func main() {
680680
m = r.sub == p.sub && ReverseMatch(r.obj, p.obj) && r.act == p.act
681681
```
682682

683+
684+
## Enforcer
685+
686+
Casbin [Enforcer(https://casbin.org/zh/docs/enforcers) 执行器往内存中缓存策略数据时并**不是并发安全**的,
687+
所以 Casbin 还实现了多种 `Enforcer`,可以分为并发安全的 `SyncedEnforcer`,带缓存的 `CachedEnforcer` 等等。
688+
689+
- `Enforcer`:基础的执行器,内存中的操作不是并发安全的。
690+
- `CachedEnforcer`:基于 `Enforcer` 实现,使用内存中的映射来缓存请求的评估结果,这样可以提高访问控制决策的性能。
691+
- `SyncedEnforcer`:基于 `Enforcer` 实现的并发安全的执行器。
692+
- `SyncedCachedEnforcer`:是 `SyncedEnforcer``CachedEnforcer` 的结合体,支持缓存请求的评估结果,同时是并发安全的。
693+
- `DistributedEnforcer`:基于 `SyncedEnforcer` 实现,支持分布式集群中的多个实例。
694+
695+
## Adapter
696+
697+
[Adapter](https://casbin.org/zh/docs/adapters) 是用来持久化策略数据的(支持文件,数据库等各种持久化方式)。用户可以使用 `LoadPolicy()` 从持久化存储中加载策略规则,
698+
使用 `SavePolicy()` 将策略规则保存到持久化存储中。
699+
700+
1.`Adapter` A 加载策略规则到内存中:
701+
702+
```go
703+
e, _ := NewEnforcer(m, A)
704+
e.LoadPolicy()
705+
```
706+
707+
2. 将适配器从 A 转换到 B
708+
```go
709+
e.SetAdapter(B)
710+
```
711+
712+
3. 将策略从内存保存到持久化存储中:
713+
714+
```go
715+
e.SavePolicy()
716+
```
717+
718+
Casbin 初始化后是可以重新加载模型,重新加载策略或保存策略的:
719+
720+
```go
721+
// Reload the model from the model CONF file.
722+
e.LoadModel()
723+
724+
// Reload the policy from file/database.
725+
e.LoadPolicy()
726+
727+
// Save the current policy (usually after changed with Casbin API) back to file/database.
728+
e.SavePolicy()
729+
```
730+
731+
### AutoSave
732+
733+
由于 Casbin 为了提高性能,在 `Enforcer` 中内置了一个**内存缓存**,所有的操作都是在操作内存中的数据,直到调用 `SavePolicy()`
734+
AutoSave 功能,是将策略的操作自动保存到存储中,这意味着单个策略规则添加,删除作者更新,都会自动保存,而不需要再调用 `SavePolicy()`
735+
736+
{{< callout type="info" >}}
737+
AutoSave 功能需要适配器的支持,如果适配器支持,可以使用 `EnableAutoSave()` 开启或者禁用。**默认启用**
738+
{{< /callout >}}
739+
740+
```go
741+
742+
// By default, the AutoSave option is enabled for an enforcer.
743+
a := xormadapter.NewAdapter("mysql", "mysql_username:mysql_password@tcp(127.0.0.1:3306)/")
744+
e := casbin.NewEnforcer("examples/basic_model.conf", a)
745+
746+
// Disable the AutoSave option.
747+
e.EnableAutoSave(false)
748+
749+
// Because AutoSave is disabled, the policy change only affects the policy in Casbin enforcer,
750+
// it doesn't affect the policy in the storage.
751+
e.AddPolicy(...)
752+
e.RemovePolicy(...)
753+
754+
// Enable the AutoSave option.
755+
e.EnableAutoSave(true)
756+
757+
// Because AutoSave is enabled, the policy change not only affects the policy in Casbin enforcer,
758+
// but also affects the policy in the storage.
759+
e.AddPolicy(...)
760+
e.RemovePolicy(...)
761+
```
762+
763+
{{< callout type="warning" >}}
764+
`SavePolicy()` 会删除持久化存储中的所有策略规则,然后将 Enforcer 内存中的策略规则保存到持久化存储中。因此,当策略规则的数量较大时,可能会有性能问题。
765+
{{< /callout >}}
766+
767+
## Watcher
768+
769+
在使用 Casbin 作为权限管理时,一般会增加多个 Casbin 实例来保证请求高并发和稳定性,但是与此同时会出现多个实例之间数据不一致的问题。
770+
771+
这是因为在 Casbin 内置的**内存缓存**,每当执行读数据的动作时,会先获取内存缓存中的数据,而不是数据库中的数据。
772+
773+
因此在多实例场景下,当一个实例的 Casbin 数据发生变更时,其它实例的 Casbin 内存缓存并没有及时同步刷新,这就导致了当请求被分发到对应的实例上时,获取到的依然还是旧的数据。
774+
775+
对此 Casbin 官方提供了一种解决方案 **[Watcher 机制](https://casbin.org/zh/docs/watchers)**,来维持多个 Casbin 实例之间数据的一致性。
776+
777+
## Dispatcher
778+
779+
Casbin 多节点之间的策略数据同步,可以通过 Watcher 机制来实现,也可通过 Dispatcher 调度器来实现。
780+
781+
官方实现的 [hraft-dispatcher](https://github.com/casbin/hraft-dispatcher) 的架构:
782+
783+
![dispatcher-architecture](https://gitee.com/shipengqi/illustrations/raw/main/go/dispatcher-architecture.svg)
784+
785+
在 Dispatcher 中能使用适配器,因为 Dispatcher 自带一个适配器。所有策略都由 Dispatcher 维护。不能调用 `LoadPolicy``SavePolicy` 方法,
786+
因为这会影响数据的一致性。可以理解为 `Dispatcher = Adapter + Watcher`
787+
788+
使用示例:
789+
790+
```go
791+
m, err := model.NewModelFromString(modelText)
792+
if err != nil {
793+
log.Fatal(err)
794+
}
795+
796+
// Adapter is not required here.
797+
// Dispatcher = Adapter + Watcher
798+
e, err := casbin.NewDistributedEnforcer(m)
799+
if err != nil {
800+
log.Fatal(err)
801+
}
802+
803+
// New a Dispatcher
804+
dispatcher, err := hraftdispatcher.NewHRaftDispatcher(&hraftdispatcher.Config{
805+
Enforcer: e,
806+
JoinAddress: config.JoinAddress,
807+
ListenAddress: config.ListenAddress,
808+
TLSConfig: tlsConfig,
809+
DataDir: config.DataDir,
810+
})
811+
if err != nil {
812+
log.Fatal(err)
813+
}
814+
815+
e.SetDispatcher(dispatcher)
816+
```
817+
683818
## 性能优化
684819

685820
### 优化匹配器
@@ -714,6 +849,96 @@ m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
714849

715850
角色继承关系会影响匹配器的效率。深度过大的继承链会增加计算成本。
716851

852+
### CachedEnforcer
853+
854+
`CachedEnforcer` 基于 `Enforcer`,但是增加了缓存机制。
855+
856+
1. **缓存评估结果**`CachedEnforcer` 使用内存中的映射来缓存请求的评估结果,这样可以提高访问控制决策的性能,减少对策略存储的频繁访问。
857+
2. **指定过期时间清除缓存**:它允许在指定的过期时间内清除缓存,这有助于确保策略的变更能够及时生效 。
858+
3. **通过读写锁保证缓存的并发安全**
859+
4. **启用缓存**:可以使用 `EnableCache` 方法来启用评估结果的缓存,**默认是启用的**
860+
5. **API 一致性**`CachedEnforcer 的其他 API 方法与 `Enforcer` 相同。
861+
862+
`CachedEnforcer` 的部分源码:
863+
864+
```go
865+
// Enforce decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act).
866+
// if rvals is not string , ignore the cache.
867+
func (e *CachedEnforcer) Enforce(rvals ...interface{}) (bool, error) {
868+
// 没有开启缓存
869+
if atomic.LoadInt32(&e.enableCache) == 0 {
870+
return e.Enforcer.Enforce(rvals...)
871+
}
872+
873+
// 查询缓存 key 是否存在
874+
key, ok := e.getKey(rvals...)
875+
if !ok {
876+
return e.Enforcer.Enforce(rvals...)
877+
}
878+
879+
// 查询缓存的评估结果
880+
if res, err := e.getCachedResult(key); err == nil {
881+
return res, nil
882+
} else if err != cache.ErrNoSuchKey {
883+
return res, err
884+
}
885+
886+
// 没有找到缓存的评估结果
887+
res, err := e.Enforcer.Enforce(rvals...)
888+
if err != nil {
889+
return false, err
890+
}
891+
892+
// 缓存评估结果
893+
err = e.setCachedResult(key, res, e.expireTime)
894+
return res, err
895+
}
896+
897+
// LoadPolicy,策略重新加载,会清除缓存的所有评估结果
898+
func (e *CachedEnforcer) LoadPolicy() error {
899+
if atomic.LoadInt32(&e.enableCache) != 0 {
900+
if err := e.cache.Clear(); err != nil {
901+
return err
902+
}
903+
}
904+
return e.Enforcer.LoadPolicy()
905+
}
906+
907+
// RemovePolicy 策略删除会清除响应的 key 的评估结果
908+
func (e *CachedEnforcer) RemovePolicy(params ...interface{}) (bool, error) {
909+
if atomic.LoadInt32(&e.enableCache) != 0 {
910+
key, ok := e.getKey(params...)
911+
if ok {
912+
if err := e.cache.Delete(key); err != nil && err != cache.ErrNoSuchKey {
913+
return false, err
914+
}
915+
}
916+
}
917+
return e.Enforcer.RemovePolicy(params...)
918+
}
919+
920+
// getCachedResult setCachedResult 对缓存的操作是并发安全的
921+
func (e *CachedEnforcer) getCachedResult(key string) (res bool, err error) {
922+
e.locker.Lock()
923+
defer e.locker.Unlock()
924+
return e.cache.Get(key)
925+
}
926+
927+
func (e *CachedEnforcer) setCachedResult(key string, res bool, extra ...interface{}) error {
928+
e.locker.Lock()
929+
defer e.locker.Unlock()
930+
return e.cache.Set(key, res, extra...)
931+
}
932+
```
933+
934+
{{< callout type="warning" >}}
935+
`CachedEnforcer` 并没有针对 `UpdatePolicy` 做特殊处理,如果使用 `UpdatePolicy` 修改策略,那么缓存的评估结果不会被清理。
936+
所以尽量使用 `AddPolicy``RemovePolicy` 来修改策略。
937+
{{< /callout >}}
938+
939+
717940
### 分片
718941

719942
进行分片,让 Casbin 执行器只加载一小部分策略规则。例如,`执行器_0` 可以服务于 `租户_0``租户_99`,而 `执行器_1` 可以服务于 `租户_100``租户_199`
943+
944+
可以利用 [LoadFilteredPolicy ](https://casbin.org/zh/docs/policy-subset-loading) 来实现。

0 commit comments

Comments
 (0)