1. Ehcache簡介
EhCache 是一個純Java的進程內緩衝架構,具有快速、精乾等特點,是Hibernate中預設CacheProvider。Ehcache是一種廣泛使用的開源Java分布式緩衝。主要面向通用緩衝,Java EE和輕量級容器。它具有記憶體和磁碟儲存,緩衝載入器,緩衝擴充,緩衝例外處理常式,一個gzip緩衝servlet過濾器,支援REST和SOAP api等特點。
Spring 提供了對緩衝功能的抽象:即允許綁定不同的緩衝解決方案(如Ehcache),但本身不直接提供緩衝功能的實現。它支援註解方式使用緩衝,非常方便。
Ehcache的特點: 快速 簡單 多種緩衝策略 快取資料有兩級:記憶體和磁碟,因此無需擔心容量問題 快取資料會在虛擬機器重啟的過程中寫入磁碟 可以通過RMI、可插入API等方式進行分布式緩衝 具有緩衝和緩衝管理器的偵聽介面 支援多緩衝管理器執行個體,以及一個執行個體的多個快取區域 提供Hibernate的緩衝實現 2.Redis簡介
Redis是一個開源的使用ANSI C語言編寫、支援網路、可基於記憶體亦可持久化的日誌型、Key-Value資料庫,並提供多種語言的API。從2010年3月15日起,Redis的開發工作由VMware主持。從2013年5月開始,Redis的開發由Pivotal贊助。
Redis是一個key-value儲存系統。它支援儲存的value類型很多,包括string(字串)、list(鏈表)、set(集合)、zset(sorted set --有序集合)和hash(雜湊類型)。這些資料類型都支援push/pop、add/remove及取交集並集和差集及更豐富的操作,而且這些操作都是原子性的。在此基礎上,redis支援各種不同方式的排序。Redis將資料都是緩衝在記憶體中。Redis會周期性的把更新的資料寫入磁碟或者把修改操作寫入追加的記錄檔案,並且在此基礎上實現了master-slave(主從)同步。
Redis的特點:
1、速度快
Redis是用C語言實現的; Redis的所有資料存放區在記憶體中。 2、持久化
Redis的所有資料存放區在記憶體中,對資料的更新將非同步地儲存到磁碟上。
3、支援多種資料結構
Redis支援五種資料結構:String、List、Set、Hash、Zset 4、支援多種程式設計語言
Java、php、Python、Ruby、Lua、Node.js 5、功能豐富
除了支援五種資料結構之外,還支援事務、流水線、發布/訂閱、訊息佇列等功能。 6、源碼簡單
約23000行C語言原始碼。 7、主從複製
主伺服器(master)執行添加、修改、刪除,從伺服器執行查詢。 8、高可用及分布式
Redis-Sentinel(v2.8)支援高可用 。Redis-Cluster(v3.0)支援分布式
3. Ehcache 和 Redis 比較
|
Ehcache |
Redis |
存取速度 |
Ehcache直接在jvm虛擬機器中緩衝,速度快,效率高 |
Redis是通過socket訪問到快取服務,效率比ecache低 |
叢集和分布式 |
Ehcache有緩衝共用方案,不過是通過RMI或者Jgroup多播方式進行廣播快取通知更新,緩衝共用複雜,維護不方便;簡單的共用可以,但是涉及到緩衝恢複,大資料緩衝,則不合適。 |
Redis有成熟的分布式解決方案。適合大規模分布式叢集部署。 |
操作複雜度 |
Ehcache提供的介面非常簡單明了,從Ehcache的搭建到運用運行僅僅需要的是你寶貴的幾分鐘。其實很多開發人員都不知道自己用在用Ehcache,Ehcache被廣泛的運用於其他的開源項目。比如:Hibernate |
至少需要安裝服務端和用戶端才能使用。操作略比Ehcache複雜一些。 |
|
|
|
4.SpringBoot與Ehcache整合方案
配置Ehcache
@Configuration@EnableCachingpublic class EhCacheConfiguration implements CachingConfigurer { @Bean(destroyMethod="shutdown") public net.sf.ehcache.CacheManager ehCacheManager() { CacheConfiguration cacheConfiguration = new CacheConfiguration(); cacheConfiguration.setName("HelloWorldCache"); cacheConfiguration.setMemoryStoreEvictionPolicy("LRU"); cacheConfiguration.setMaxEntriesLocalHeap(1000); net.sf.ehcache.config.Configuration config = new net.sf.ehcache.config.Configuration(); //可以建立多個cacheConfiguration,都添加到Config中 config.addCache(cacheConfiguration); return net.sf.ehcache.CacheManager.newInstance(config); } @Bean @Override public CacheManager cacheManager() { return new EhCacheCacheManager(ehCacheManager()); } @Bean @Override public KeyGenerator keyGenerator() { return new SimpleKeyGenerator(); } @Override public CacheResolver cacheResolver() { return null; } @Override public CacheErrorHandler errorHandler() { return null; }}
寫一個測試Service和其實作類別
public interface EhcacheService { // 測試失效情況,有效期間為5秒 public String getTimestamp(String param);}
@Servicepublic class EhcacheServiceImpl implements EhcacheService { /** * @Cacheable 表明所修飾的方法是可以緩衝的:當第一次調用這個方法時,它的結果會被緩衝下來,在緩衝的有效時間內, * 以後訪問這個方法都直接返回緩衝結果,不再執行方法中的程式碼片段。 * 這個註解可以用condition屬性來設定條件,如果不滿足條件,就不使用緩衝能力,直接執行方法。 * 可以使用key屬性來指定key的建置規則。 * * value:緩衝位置名稱,不可為空,如果使用EHCache,就是ehcache.xml中聲明的cache的name, 指明將值緩衝到哪個Cache中 * * key:緩衝的key,預設為空白,既表示使用方法的參數類型及參數值作為key,支援SpEL,如果要引用參數值使用井號加參數名,如:#userId, * 一般來說,我們的更新操作只需要重新整理緩衝中某一個值,所以定義緩衝的key值的方式就很重要,最好是能夠唯一,因為這樣可以準確的清除掉特定的緩衝,而不會影響到其它緩衝值 , * 本例子中使用實體加冒號再加ID組合成鍵的名稱,如”user:1”、”order:223123”等 * * condition:觸發條件,只有滿足條件的情況才會加入緩衝,預設為空白,既表示全部都加入緩衝,支援SpEL * @param param * @return */ @Cacheable(value="HelloWorldCache", key="#param") @Override public String getTimestamp(String param) { Long timestamp = System.currentTimeMillis(); return timestamp.toString(); }}
寫個單元測試:
@RunWith(SpringRunner.class)@SpringBootTestpublic class EhcacheServiceTest { @Autowired EhcacheService ehcacheService; /** * 返回第一次調用getTimestamp方法的時間 * @throws InterruptedException */ @Test public void testGetTimestamp() throws InterruptedException { System.out.println("第一次調用:" + ehcacheService.getTimestamp("param")); Thread.sleep(2000); System.out.println("2秒之後調用:" + ehcacheService.getTimestamp("param")); Thread.sleep(4000); System.out.println("再過4秒之後調用:" + ehcacheService.getTimestamp("param")); }}
可以發現後兩次調用返回的都是第一次方法調用的時間。
5. SpringBoot整合 Redis 方案
配置Redis
@Configurationpublic class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){ RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String,Object>(); redisTemplate.setConnectionFactory(factory); // key序列化方式;(不然會出現亂碼;),但是如果方法上有Long等非String類型的話,會報類型轉換錯誤; // 所以在沒有自己定義key建置原則的時候,以下這個代碼建議不要這麼寫,可以不配置或者自己實現ObjectRedisSerializer // 或者JdkSerializationRedisSerializer序列化方式; RedisSerializer<String> keyRedisSerializer = new StringRedisSerializer(); RedisSerializer<Object> valueRedisSerializer = new GenericJackson2JsonRedisSerializer(); //設定序列化Key的執行個體化對象 redisTemplate.setKeySerializer(keyRedisSerializer); //設定序列化Value的執行個體化對象 redisTemplate.setValueSerializer(valueRedisSerializer); redisTemplate.setHashKeySerializer(keyRedisSerializer); redisTemplate.setHashValueSerializer(valueRedisSerializer); return redisTemplate; }}
寫個測試Service和其實作類別。redis中沒有資料的是會通過dao從資料庫撈取資料然後存入redis
public interface CityService { /** * 根據城市 ID,查詢城市資訊 * * @param id * @return */ City findCityById(Long id); /** * 新增城市資訊 * * @param city * @return */ Long saveCity(City city); /** * 更新城市資訊 * * @param city * @return */ Long updateCity(City city); /** * 根據城市 ID,刪除城市資訊 * * @param id * @return */ Long deleteCity(Long id);}
@Servicepublic class CityServiceImpl implements CityService { private static final Logger LOGGER = LoggerFactory.getLogger(CityServiceImpl.class); private static final String CITY_REDIS_KEY_PREFIX = "city_"; @Autowired private CityDao cityDao; @Autowired private RedisTemplate redisTemplate; /** * 擷取城市邏輯: * 如果緩衝存在,從緩衝中擷取城市資訊 * 如果緩衝不存在,從 DB 中擷取城市資訊,然後插入緩衝 */ @Override public City findCityById(Long id) { // 從緩衝中擷取城市資訊 String key = CITY_REDIS_KEY_PREFIX + id; ValueOperations<String, City> operations = redisTemplate.opsForValue(); // 緩衝存在 boolean hasKey = redisTemplate.hasKey(key); if (hasKey) { City city = operations.get(key); LOGGER.info("CityServiceImpl.findCityById() : 從緩衝中擷取了城市 >> " + city.toString()); return city; } // 從 DB 中擷取城市資訊 City city = cityDao.findById(id); // 插入緩衝 operations.set(key, city, 10, TimeUnit.MINUTES); LOGGER.info("CityServiceImpl.findCityById() : 城市插入緩衝 >> " + city.toString()); return city; } /** * 儲存城市資訊 * @param city * @return */ @Override public Long saveCity(City city) { // 從 DB 中擷取城市資訊,如果已經存在就不在儲存 City cityInDB = cityDao.findById(city.getId()); if (cityInDB != null) { return null; } return cityDao.saveCity(city); } /** * 更新城市邏輯: * 如果緩衝存在,刪除 * 如果緩衝不存在,不操作 */ @Override public Long updateCity(City city) { Long ret = cityDao.updateCity(city); // 緩衝存在,刪除緩衝 String key = CITY_REDIS_KEY_PREFIX + city.getId(); boolean hasKey = redisTemplate.hasKey(key); if (hasKey) { redisTemplate.delete(key); LOGGER.info("CityServiceImpl.updateCity() : 從緩衝中刪除城市 >> " + city.toString()); } return ret; } /** * 刪除城市資訊 * @param id * @return */ @Override public Long deleteCity(Long id) { Long ret = cityDao.deleteCity(id); // 緩衝存在,刪除緩衝 String key = CITY_REDIS_KEY_PREFIX + id; boolean hasKey = redisTemplate.hasKey(key); if (hasKey) { redisTemplate.delete(key); LOGGER.info("CityServiceImpl.deleteCity() : 從緩衝中刪除城市 ID >> " + id); } return ret; }}
dao類
epublic interface CityDao { /** * 擷取城市資訊列表 * * @return */ List<City> findAllCity(); /** * 根據城市 ID,擷取城市資訊 * * @param id * @return */ City findById(@Param("id") Long id); /** * 儲存城市資訊 * @param city * @return */ Long saveCity(City city); /** * 修改城市資訊 * @param city * @return */ Long updateCity(City city); /** * 刪除城市資訊 * @param id * @return */ Long deleteCity(Long id);}
寫個測試Controller
@RestControllerpublic class CityRestController { @Autowired private CityService cityService; @RequestMapping(value = "/api/city/{id}", method = RequestMethod.GET) public City findOneCity(@PathVariable("id") Long id) { return cityService.findCityById(id); } @RequestMapping(value = "/api/city/create", method = RequestMethod.POST) public void createCity(@RequestBody City city) { cityService.saveCity(city); } @RequestMapping(value = "/api/city", method = RequestMethod.PUT) public void modifyCity(@RequestBody City city) { cityService.updateCity(city); } @RequestMapping(value = "/api/city/{id}", method = RequestMethod.DELETE) public void modifyCity(@PathVariable("id") Long id) { cityService.deleteCity(id); }}