shiro緩存管理


一. 概述

Shiro作為一個開源的權限框架,其組件化的設計思想使得開發者可以根據具體業務場景靈活地實現權限管理方案,權限粒度的控制非常方便。
首先,我們來看看Shiro框架的架構圖:

從上圖我們可以很清晰地看到,CacheManager也是Shiro架構中的主要組件之一,Shiro正是通過CacheManager組件實現權限數據緩存。
當權限信息存放在數據庫中時,對於每次前端的訪問請求都需要進行一次數據庫查詢。特別是在大量使用shiro的jsp標簽的場景下,對應前端的一個頁面訪問請求會同時出現很多的權限查詢操作,這對於權限信息變化不是很頻繁的場景,每次前端頁面訪問都進行大量的權限數據庫查詢是非常不經濟的。因此,非常有必要對權限數據使用緩存方案。

關於shiro權限數據的緩存方式,可以分為2類:其一,將權限數據緩存到集中式存儲中間件中,比如redis或者memcached;其二,將權限數據緩存到本地。使用集中式緩存方案,頁面的每次訪問都會向緩存發起一次網絡請求,如果大量使用了shiro的jsp標簽,那么對應一個頁面訪問將會出現N個到集中緩存的網絡請求,會給集中緩存組件帶來一定的瞬時請求壓力。另外,每個標簽都需要經過一個網絡查詢,其實效率並不高。而采用本地緩存方式均不存在這些問題。所以,針對shiro的緩存方案,需要根據實際的使用場景進行權衡。如果在項目中並未使用shiro的jsp標簽庫,那么使用集中式的緩存方案也未嘗不妥;但是,如果大量使用shiro的jsp標簽庫,那么采用本地緩存才是最佳選擇。

二. 如何在shiro中使用緩存

根據Shiro官方的說法,雖然緩存在權限框架中非常重要,但是如果實現一套完整的緩存機制會使得shiro偏離了核心的功能(認證和授權)。因此,Shiro只提供了一個可以支持具體緩存實現(如:Hazelcast, Ehcache, OSCache, Terracotta, Coherence, GigaSpaces, JBossCache等)的抽象API接口,這樣就允許Shiro用戶根據自己的需求靈活地選擇具體的CacheManager。當然,其實Shiro也自帶了一個本地內存CacheManager:org.apache.shiro.cache.MemoryConstrainedCacheManager。

其實,從Shiro緩存組件類圖可以看到,Shiro提供的緩存抽象API接口正是:org.apache.shiro.cache.CacheManager。
那么,我們應該如何配置和使用CacheManager呢?如下我們以使用Shiro提供的MemoryConstrainedCacheManager組件為例進行說明。
我們知道,SecurityManager是Shiro的核心控制器,我們來看一下其類圖:

org.apache.shiro.mgt.CachingSecurityManager是Shiro中SecurityManager接口的基礎抽象類,我們來看一下其源碼結構:

從圖中我們看到,在CachingSecurityManager中存在一個CacheManager類型的成員變量。
另外,接口org.apache.shiro.realm.Realm定義了權限數據的存儲方式,我們看一下其類圖:

顯然,org.apache.shiro.realm.CachingRealm是Shiro中Realm接口的基礎實現類,我們同樣來看一下其源碼結構:

同樣,在CachingRealm也存在一個CacheManager類型的成員變量。
從以上分析我們知道:Shiro支持在2個地方定義緩存管理器,既可以在SecurityManager中定義,也可以在Realm中定義,任選其一即可。
通常我們都會自定義Realm實現,例如將權限數據存放在數據庫中,那么在Realm實現中定義緩存管理器再合適不過了。
舉個例子,我們擴展了org.apache.shiro.realm.jdbc.JdbcRealm,在其中定義一個緩存組件。

<!-- Define the Shiro Realm implementation you want to use to connect to your back-end -->
<!-- security datasource: -->
<bean id="myRealm" class="org.chench.test.shiro.spring.dao.ShiroCacheJdbcRealm">
    <property name="dataSource" ref="dataSource"/>
    <property name="permissionsLookupEnabled" value="true"/>
    <property name="cacheManager" ref="cacheManager" />
</bean>

<bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" />

當然,同樣可以在SecurityManager中定義緩存組件:

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <!-- Single realm app.  If you have multiple realms, use the 'realms' property instead. -->
    <property name="realm" ref="myRealm" />
    <property name="cacheManager" ref="cacheManager" />
</bean>
<bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" />

那么,我們不禁要問了:
第一:為什么Shiro要設計成既可以在Realm,也可以在SecurityManager中設置緩存管理器呢?
第二:分別在Realm和SecurityManager定義的緩存管理器,他們有什么區別或聯系嗎?
下面,我們追蹤一下org.apache.shiro.mgt.RealmSecurityManage的源碼實現:

protected void applyCacheManagerToRealms() {
    CacheManager cacheManager = getCacheManager();
    Collection<Realm> realms = getRealms();
    if (cacheManager != null && realms != null && !realms.isEmpty()) {
        for (Realm realm : realms) {
            if (realm instanceof CacheManagerAware) {
                ((CacheManagerAware) realm).setCacheManager(cacheManager);
            }
        }
    }
}

這下終於真相大白了吧!其實在SecurityManager中設置的CacheManager組中都會給Realm使用,即:真正使用CacheManager的組件是Realm。

三. 緩存方案

1. 集中式緩存

我們在前面分析了,使用集中式緩存方案只適用於那些沒有使用shiro的jsp標簽的場景,比如:前后端完全分離的項目。目前比較流行的集中式緩存組件有:Redis,Memcache等,我們可以借助於這樣的集中式緩存實現shiro的緩存方案。
雖然使用了集中式緩存組件,但是不必要直接把權限數據本身存放到集中式緩存中,而是通過在集中式緩存中存放緩存標志即可。這樣可以避免直接從集中式緩存中取權限數據,當權限數據比較大時,大量權限數據查詢所占用的帶寬也是比較可觀的。

基於Redis的集中式緩存方案:https://github.com/alexxiyang/shiro-redis

基於Memcached的集中式緩存方案:https://github.com/mythfish/shiro-memcached

基於Ehcache集群模式的存放方案:http://www.ehcache.org/

2. 本地緩存

本地緩存的實現有幾種方式:(1)直接存放到JVM堆內存(2)使用NIO存放在堆外內存,自定義實現或者借助於第三方緩存組件。
不論是采用集中式緩存還是使用本地緩存,shiro的權限數據本身都是直接存放在本地的,不同的是緩存標志的存放位置。采用本地緩存方案是,我們將緩存標志也存放在本地,這樣就避免了查詢緩存標志的網絡請求,能更進一步提升緩存效率。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM