一、問題場景
之前做過一個子產品的功能,但是實作功能之後存在性能問題(4萬條資料100個并發的時候會很卡,頁面響應會很慢),經過一系列的排查,最後終于解決了這個問題,現在想起來還是記錄一下解決過程,避免下次踩坑,也給碰到類似問題的朋友提供一個思路(前三步是記錄自己解決問題的過程,隻想了解springboot+shiro+ehcache使用的朋友可直接到第四步)。
二、功能描述及表結構
任務清單的功能主要是一個樹形的展示,點選父級要展開子集。而且還有一個需求(就是這個很坑的需求導緻的頭疼)就是模糊查詢(還有很多篩選條件)的時候查出符合條件的父級和子級,如果子級滿足條件,則找出它的父級在頁面顯示,子級要展開。
三、爬坑之路
坑一、
最開始的時候采用的方法是先找出符合條件的父級id集合,然後再去資料庫中查詢符合條件的父級,帶上分頁條件,最後再循環查出各個父級的子級一起拼裝後傳回給前端。這樣雖然也實作了功能,但是有一個問題就是在查詢符合條件的父級id的時候,不可避免的出現全表掃描的情況,導緻查出的資料量特别大,從資料庫中查出來再放到集合中也是需要時間的,是以影響性能。還有就是查詢出符合條件的父級之後,需要循環父級集合去查詢子級,頻繁連接配接資料庫也會很慢。
坑二、
後面的想法是能不能通過隻連接配接一次資料庫将所有符合條件的資料都查出來,父任務和子任務的資料存在一張表裡,用super_id關聯,經過sql的書寫發現是可以實作的,思路就是表的left join連接配接。但是由于表中存在處理人id和建立人id,要關聯使用者表去查詢人名,是以在多并發的時候依然會影響性能,資料庫連接配接會爆滿。除此之外,也并不能滿足關于篩選條件查詢子級并顯示父級的需求,是以,這種方案也不行。
解決辦法:其實有了前兩步的爬坑,對解決性能問題也有了一定的思路。一是盡量減少與資料庫之間的互動,二是盡量避免全表掃描,盡量不要一次查詢大量資料,三是避免sql語句中出現大量的連接配接查詢。基于這幾種思路,關于使用者的資料每天隻更新一次,是以可以将它放在緩存中,不用每次去查詢使用者資料。而對于需求中條件篩選出父級和子級也可以在擷取父級之後擷取父級下所有的子級(資料不會太多),然後再根據父級id去比對(不必循環去查,多次連接配接資料庫)。
四、springboot+shiro中使用ehcach緩存
4.1 引入依賴
<!-- ehcache 緩存 -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
</dependency>
4.2 二、引入ehcache配置檔案
在項目resource下面增加ehcache的配置檔案
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<defaultCache
eternal="false"
maxElementsInMemory="1000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="0"
timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LRU" />
<cache
name="users"
eternal="false"
maxElementsInMemory="1000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="0"
timeToLiveSeconds="300"
memoryStoreEvictionPolicy="LRU" />
</ehcache>
4.3 內建shiro并使用ehcache緩存
配置緩存管理器
@Bean
public EhCacheManager getEhCacheManager() {
EhCacheManager ehcacheManager = new EhCacheManager();
//ehcacheManager.setCacheManagerConfigFile("classpath:config/ehcache-shiro.xml");
ehcacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
return ehcacheManager;
}
将自己驗證方式加入容器
@Bean(name = "myShiroRealm")
public MyShiroRealm myShiroRealm(){
MyShiroRealm realm = new MyShiroRealm();
realm.setCacheManager(getEhCacheManager());
return realm;
}
配置安全管理器
@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
securityManager.setCacheManager(getEhCacheManager());
// 自定義session管理 使用redis
//securityManager.setSessionManager(sessionManager());
return securityManager;
}
ehcache緩存配置類(設定為共享模式)
@Configuration
public class EhcacheConfig {
/**
* 設定為共享模式
* @return
*/
@Bean
public EhCacheManagerFactoryBean ehCacheManagerFactoryBean() {
EhCacheManagerFactoryBean cacheManagerFactoryBean = new EhCacheManagerFactoryBean();
cacheManagerFactoryBean.setShared(true);
return cacheManagerFactoryBean;
}
}
五、使用ehcache緩存
啟動類需開啟支援緩存的注解 @EnableCaching
在使用者的service層實作類添加如下方法
@Cacheable 加入緩存
@Override
@Cacheable(value="users")
public List<User> selectAllUsers() {
return userMapper.selectAllUsers();
}
@CacheEvict 清除緩存
@Override
@CacheEvict(value="users")
public void clearCache() {
System.out.println("使用者緩存資料已清除");
value的值與ehcache.xml的配置要一緻
以後再有相關人員查詢的時候可以直接調用該方法,将從緩存中取資料(第一次從資料庫查),不會再去資料庫中查