@@ -680,6 +680,141 @@ func main() {
680680m = 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