EhCache — Spring Boot

Furkan Şahin Kulaksız
9 min readDec 2, 2023

--

GÖRSELDEKİ QR KODU KULLANARAK İLETİŞİM KANALLARIMA ULASABILIRSINIZ

Öncelikle bu yazıya başlamadan önce çok güzel bir mülakat sorusunu cevaplamak istiyorum.

Cache (Önbellek )vs Buffer

Cache ve Buffer, verileri geçici olarak depolamak için kullanılan veri depolama katmanlarıdır. Buffer, giriş ve çıkış veri aktarımları sırasında verileri depolayan ana bellek alanıdır. Verileri bir bilgisayardaki işlemler arasında taşırken arabellek kullanışlıdır. Önbellek, belleğe erişim süresini azaltmak ve bilgisayar hızını artırmak için kullanılır.

Buffer :

  • Amaç: Bir buffer, genellikle veri transferi veya işleme hızlarını eşitlemek için kullanılır. Ana veri kaynağı ile hedefi arasındaki hız farklarını dengelemek için kullanılır.
  • Çalışma Prensibi: Bufferlar, verilerin geçici olarak saklandığı arabellek alanlarıdır. Veri akışı veya iletimi sırasında veriler buffer’a alınır ve ardından hedefe aktarılır veya işlenir. Bu, kaynak ve hedef arasındaki veri akışının düzenli olmasını sağlar ve veri kaybını önler.
  • Kullanım Alanları: İnternet tarayıcılarında video akışı sırasında buffer kullanılır. Ayrıca, yazıcılar, depolama cihazları ve ağ ekipmanları gibi bir çok cihazda tamponlar kullanılır.

Cache:

  • Amaç: Bir cache, sık kullanılan verilerin daha hızlı erişilebilir olmasını sağlamak için kullanılır. Bu, verilere daha hızlı erişim sağlayarak genel sistem performansını artırır.
  • Çalışma Prensibi: Cache, bilgisayarın hafızasında bulunan özel bir alan veya veri deposudur. Sık kullanılan veriler burada saklanır. Bir uygulama veya işletim sistemi, bir veriye ihtiyaç duyduğunda önce cachr’i kontrol eder. Eğer veri önbellekte bulunuyorsa, bu veri daha hızlı erişilir. Eğer veri önbellekte yoksa, ana kaynaktan alınır ve önbelleğe kaydedilir.
  • Kullanım Alanları: İnternet tarayıcılarında web sayfaları, resimler ve tarayıcı tarafından sıkça ziyaret edilen veriler cache’e alınır. İşletim sistemleri, uygulama cache’i kullanarak programların daha hızlı başlatılmasını sağlar.

Caching, genellikle çok hızlı bellekte verilerin ara depolanmasını içeren bir teknik olan, bu nedenle bu veriler, genellikle daha yavaş olan birincil bellekten ilk olarak alınması veya yeniden hesaplanması gerekmediği için daha hızlı bir şekilde sonraki istekler için kullanılabilir hale getirilebilir.

Caching, aşağıdaki senaryolar için özellikle faydalıdır:

  1. Aynı veri tekrar tekrar isteniyor (sık kullanılan bölgeler denilen), bu verilerin her istekte veritabanından yeniden yüklenmesi gerekiyor. Bu veriler, sunucu uygulamasının ana belleğinde (RAM) veya istemci tarafında (tarayıcı önbelleği) önbelleğe alınabilir. Bu, erişim sürelerini ve veri transferlerinin sayısını azaltır, çünkü sunucunun veriyi sürekli olarak veritabanından istemesi ve istemciye göndermesi gerekmez.
  2. Uzun vadeli veya kaynak yoğun işlemler sık sık belirli parametrelerle gerçekleştirilir. Parametrelere bağlı olarak işlemin sonucu geçici olarak saklanabilir, böylece sunucu işlemi gerçekleştirmeden sonucu istemciye gönderebilir.

Redis ya da EhCache fark etmez, bir cache sistemi için spring boot’ta çok güzel bir standart vardır. Cache varsa @EnableCaching ve @Cachable annotatonlarını kullanırız.

EhCache

EhCache hibernate’in second level caching sistemlerinden bir tanesidir. Verileri ön belleğe alır ve bizim verdiğimiz konfigürasyonlara göre çalışır.

Ehcache, önbellek katmanının birden fazla bellek alanından oluşabileceği şekilde yapılandırılabilir. Birden fazla bellek alanı kullanırken, alanlar hiyerarşik katmanlar olarak düzenlenir. En düşük katman, Authority Tier olarak adlandırılır ve diğer katmanlar Near Cache larak adlandırılır.

En sık kullanılan veriler en hızlı önbellek katmanında (Top Layer) saklanır. Authority Tier temel olarak tüm önbellek girişlerini içerir.

Ehcache tarafından desteklenen bellek alanları şunları içerir:

  1. On-Heap Store: Önbellek girişlerini saklamak için Java heap belleğini kullanır ve bu belleği uygulama ile paylaşır. Bu bellek çok hızlıdır, ancak çok sınırlıdır. Önbellek aynı zamanda Garbage Collector tarafından taranır.
  2. Off-Heap Store: Önbellek girişlerini saklamak için RAM’i kullanır. Bu bellek çöp toplamasına tabi değildir. Hala oldukça hızlı bir bellektir, ancak önbellek girişleri kullanılmadan önce on-heap belleğe taşınması gerektiği için on-heap belleğe göre daha yavaştır.
  3. Disk Store: Önbellek girişlerini saklamak için sabit diski kullanır. RAM’den çok daha yavaştır. Sadece önbellek için kullanılan özel bir SSD kullanılması önerilir.

EhCache ile alakalı araştırma yaptığınızda genelde XML şeklinde konfigürasyonlar yapıldığını görmeniz kuvvetle muhtemel olsa da aslında kodun içerisinde de kodlarla da EhCache konfigürasyonlarını sağlayabilirsiniz.

XML konfigürasyonları yapıldıktan sonra kullanılmaya kolay bir şekilde başlanabilir. Adımları çok kolaydır,

  • EhCache için maven ya da gradle bağımlılığını projeye eklemek gerekir.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.10.6</version>
<classifier>jakarta</classifier>
</dependency>
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
<version>1.1.1</version>
</dependency>

Burada önemli bir nokta var. <classifier> property’si önemli çünkü Spring Boot artık modellerini jakarta ile yönetiyor.

XML konfigürasyonları ile ehcahce’i konfigüre etmek istersek; spring boot projemizde src -> main -> resources klasörü altına ehcache.xml adında bir dosya açıp içine aşağıdaki xml’i ekleyelim.

<config
xmlns='http://www.ehcache.org/v3'
xmlns:jsr107='http://www.ehcache.org/v3/jsr107'>
<!-- Persistent cache directory -->
<persistence directory="spring-ehcache/ehcache" />
<!-- Default cache template -->
<cache-template name="default">
<expiry>
<ttl unit="seconds">20</ttl>
</expiry>
<listeners>
<listener>
<class>com.fsk.ehcache.configuration.CacheLogger</class>
<event-firing-mode>ASYNCHRONOUS</event-firing-mode>
<event-ordering-mode>UNORDERED</event-ordering-mode>
<events-to-fire-on>CREATED</events-to-fire-on>
<events-to-fire-on>EXPIRED</events-to-fire-on>
<events-to-fire-on>EVICTED</events-to-fire-on>
</listener>
</listeners>
<resources>
<heap>1000</heap>
<offheap unit="MB">10</offheap>
<disk persistent="true" unit="MB">20</disk>
</resources>
</cache-template>

<persistence> tagı

XML dosyasındaki <persistence> tagı, önbellek verilerinin kalıcılığını (persistence) yapılandırmak için kullanılır. Bu etiket, önbellekten verilerin nasıl saklanacağını ve geri alınacağını belirlemenize olanak tanır.

<persistance> tagı içinde belirleyebileceğiniz bazı önemli özellikler şunlar olabilir:

strategy: Önbellek verilerinin nasıl kalıcı hale getirileceğini belirleyen bir stratejiyi tanımlar. Örneğin, verilerin diskte saklanması veya başka bir veritabanına taşınması gibi farklı stratejiler kullanulabilir.

synchronousWrites: Bu özellik, önbellek verilerinin eşzamanlı olarak yazılıp yazılmayacağını belirler. Eğer true olarak ayarlanırsa, önbellek verileri eşzamanlı olarak yazılır, bu da işlemi yavaşlatabilir. false olarak ayarlanırsa, veriler daha hızlı yazılabilir, ancak bazı veri kaybı riski olabilir.

persistenceDirectory: Kalıcı verilerin depolanacağı dizini belirtir. Bu, önbellekten alınan verilerin fiziksel olarak nerede saklanacağını gösterir.

persistence tagı, önbellek verilerinin kalıcı hale getirilmesi gereken durumlar için kullanılır ve özellikle veri kaybını önlemek veya uygulama yeniden başladığında önbellek verilerini geri yüklemek gibi senaryolarda önemlidir. Bu nedenle, önbelleğin nasıl kalıcı hale getirileceğini yapılandırmak için kullanışlıdır.

<expiry> tagı

XML dosyasındaki <expiry> tagı, önbellek girişlerinin ne kadar süreyle önbellekte kalacağını belirlemek için kullanılır. Bu etiket, önbellekten girişlerin otomatik olarak sona ermesini ve önbellekten kaldırılmasını sağlar.

Bu tag içinde belirleyebileceğimiz bazı önemli özellikler şunlar olabilir:

value: Önbellekteki girişlerin ne kadar süreyle geçerli olacağını belirten bir zaman aralığıdır. Genellikle bu değer milisaniye cinsinden belirtilir. Örneğin, bir girişin 1 saat boyunca geçerli olmasını istiyorsanız, 3600000 (1 saat milisaniye olarak) gibi bir değer kullanabilirsiniz.

unit: Zaman aralığının birimi olan bir özelliktir. Bu, value özelliğiyle birlikte kullanılır ve önbellek girişlerinin geçerlilik süresini anlamada yardımcı olur. Örnek birimler arasında "milliseconds" (milisaniyeler), "seconds" (saniyeler), "minutes" (dakikalar), "hours" (saatler) vb. bulunabilir.

Örneğin, aşağıdaki gibi bir <expiry> tagı kullanarak, önbellek girişlerinin 5 dakika boyunca geçerli olacağını belirtebiliriz.

<listeners> tagı

XML dosyasındaki <listeners> tagı önbellek olaylarını dinlemek ve özel event listenerslar tanımlamak için kullanılır. Bu tag sayesinde önbellek işlemleri sırasında meydana gelen eventler yakalanabilir ve bu eventlere özel işlemler eklenebilir.

Bir <listeners> tagı birden fazla <listener> tagını içerebilir.

Bizim XML dosyamızda,

<class>: Bu tag, özel olay dinleyici sınıfının tam adını içerir. Bu durumda, fsk.ehcache.config.CacheLogger adlı bir event listener sınıfı kullanılmıştır. Bu sınıfın, önbellek olaylarını dinlemek ve işlemek için kullanılması beklenir.

<event-firing-mode>: Bu tag, olayların nasıl tetikleneceğini belirtir. ASYNCHRONOUS olarak ayarlandığında, olaylar asenkron bir şekilde işlenir. Bu, olayların anında işlenmesi yerine daha sonra işlenmesini sağlar. Bu, performansı artırabilir, çünkü olayların işlenmesi ana iş akışınızı engellemez.

<event-ordering-mode>: Bu tag, olayların sıralama modunu belirtir. UNORDERED olarak ayarlandığında, olaylar sırasız olarak işlenir, yani olayların işlenme sırası garanti edilmez. Bu, olayların işlenme sırasının önemli olmadığı durumlarda tercih edilir.

<events-to-fire-on>: Bu etiket, hangi olayların event listener tarafından yakalanacağını belirtir. Bu durumda, CREATED, EXPIRED ve EVICTED olaylarına tepki verilecektir. Yani önbelleğe yeni bir öğe eklenirse (CREATED), bir öğenin süresi dolduğunda (EXPIRED) veya bir öğe önbellekten kaldırıldığında (EVICTED), bu olayları dinleyici tarafından işlenmesi beklenir.

<resources> tagı

<resources> tagı, önbellek için kullanılacak bellek ve disk kaynaklarını yapılandırmak için kullanılır. Bu tag, önbellek için ayrılacak bellek ve disk alanlarını belirtmenizi sağlar.

<heap>: Bu tag, önbellek için ayrılacak Java heap belleğinin boyutunu belirtir. 1000 gibi bir sayı kullanarak bellek boyutunu megabayt cinsinden belirtebilirsiniz. Java heap belleği, önbellekteki verilerin hızlı erişim için saklandığı bellek türüdür.

<offheap>: Bu tag, önbelleğin kullanabileceği off-heap bellek boyutunu belirtir. unit özelliği ile birlikte megabayt veya gigabayt cinsinden belirtilebilir. Off-heap bellek, Java heap belleği dışında, ancak yine de hızlı bir şekilde erişilebilen bir bellek türüdür.

<disk>: Bu tag, önbelleğin verileri diske yazmak ve kalıcı hale getirmek için kullanabileceği disk belleği boyutunu belirtir. unit özelliği ile birlikte megabayt veya gigabayt cinsinden belirtilebilir. persistent özelliği true olarak ayarlandığında, önbellek verileri disk üzerinde kalıcı hale getirilir ve uygulama yeniden başlatıldığında bile veriler korunur.

Bu yapılandırma, önbellek için ayrılacak bellek ve disk alanlarını tanımlamak için kullanılır ve önbellek performansını ve dayanıklılığını yönetmenize olanak tanır. Bellek ve disk alanlarını doğru bir şekilde yapılandırmak, uygulamanızın önbellek performansını optimize etmenize yardımcı olur.

Burada artık cache’imizi yazmaya başlayabiliriz.

Yine aynı XML dosyasına

<cache alias="CachePost" uses-template="default">
<key-type>java.lang.String</key-type>
<value-type>com.fsk.ehcache.model.Post</value-type>
</cache>

<cache alias="CacheWriter" uses-template="default">
<key-type>java.lang.String</key-type>
<value-type>com.fsk.ehcache.model.Writer</value-type>
</cache>

Yukarıdaki konfigürasyonları eklememiz lazım.

Burada kullanacağımız cache konfiglerini belirliyoruz. alias ile konfigüre ettiğimiz Cache’lere bir isim veriyoruz. uses-template ile tanımladığımız default cache değerini veriyoruz. <key-type> etiketi kullanacağımız domanin class’larımızın id’sinin tipini veriyoruz. <value-type> tagı ile de domain class’ımızın kendisini veriyoruz.

Yani aslında XML konfigürasyonumuzun son hali aşağıdaki gibi oldu.

<config
xmlns='http://www.ehcache.org/v3'
xmlns:jsr107='http://www.ehcache.org/v3/jsr107'>
<!-- Persistent cache directory -->
<persistence directory="spring-ehcache/ehcache" />
<!-- Default cache template -->
<cache-template name="default">
<expiry>
<ttl unit="seconds">20</ttl>
</expiry>
<listeners>
<listener>
<class>com.fsk.ehcache.configuration.CacheLogger</class>
<event-firing-mode>ASYNCHRONOUS</event-firing-mode>
<event-ordering-mode>UNORDERED</event-ordering-mode>
<events-to-fire-on>CREATED</events-to-fire-on>
<events-to-fire-on>EXPIRED</events-to-fire-on>
<events-to-fire-on>EVICTED</events-to-fire-on>
</listener>
</listeners>
<resources>
<heap>1000</heap>
<offheap unit="MB">10</offheap>
<disk persistent="true" unit="MB">20</disk>
</resources>
</cache-template>

<cache alias="CachePost" uses-template="default">
<key-type>java.lang.String</key-type>
<value-type>com.fsk.ehcache.model.Post</value-type>
</cache>

<cache alias="CacheWriter" uses-template="default">
<key-type>java.lang.String</key-type>
<value-type>com.fsk.ehcache.model.Writer</value-type>
</cache>

</config>

Şimdi sırada application.yml dosyası var.

#SERVER-CFG
server:
port: 1905

#MONGO-CFG
spring:
data:
mongodb:
port: 27017
uri: mongodb://localhost:27017/blogpost
database: blogpost

#EHCACHE-CFG
cache:
type: jcache
jcache:
config: classpath:ehcache.xml
provider: org.ehcache.jsr107.EhcacheCachingProvider

#LOGGING-CFG
logging:
level:
org:
springframework:
data:
mongodb:
core:
MongoTemplate: DEBUG

Burada bahsetmek isteyeceğim tek konfigürasyon EhCache ile olan kısımlar.
cache.type: jcache: Bu konfig, uygulamamızın önbellek türünün JCache (JSR-107) olduğunu belirtir. JSR-107, Java özel olarak önbellekleme için bir API ve spesifikasyon setidir. JCache, önbellekleme işlemleri için ortak bir arabirim sunar.

jcache.config: classpath:ehcache.xml: Bu yapılandırma, Ehcache'in önbellek konfigürasyonunu içeren bir XML dosyasının uygulamamızın classpathinde ehcache.xml olarak bulunacağını belirtir. Bu XML dosyasını resources altına koyduğumuz için spring boot bu dosyayı direkt okudu.

jcache.provider: org.ehcache.jsr107.EhcacheCachingProvider: Bu konfig, JCache (JSR-107) önbellek sağlayıcısının hangi sınıfı kullanacağını belirtir. Bu durumda, EhcacheCachingProvider, Ehcache tarafından sağlanan JSR-107 önbellek sağlayıcısını temsil eder. Bu sağlayıcı, JCache API'sini kullanarak Ehcache'i entegre etmemizi sağlar.

Şimdi spring boot uygulaması aslında çok kolay.
Java 17 — Spring Boot 3.2.0 — EhCache 3.10.6 — MongoDB tech stackinde çok basit kodlamalar olacak.

@Document
@Data
public class Post implements Serializable {

@Id
private String id;
private String title;
private String content;
@DBRef
private Writer writer;
}
@Document
@Data
public class Writer implements Serializable {

@Id
private String id;

@Indexed(unique = true)
private String userName;
private String name;
private String surname;

}

Aslında repository, service ve controller katmanları bildiğimiz katmanlar.

Bu katmanların kodlarını GitHub reposundan alabilirsiniz.

Paket yapısında configüration diye bir paket açtım

Bu paketteki kodları direkt aşağıya alıyorum.

import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableCaching
public class EhcacheCfg {

}
import org.ehcache.event.CacheEvent;
import org.ehcache.event.CacheEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CacheLogger implements CacheEventListener<Object, Object> {

private final Logger LOG = LoggerFactory.getLogger(CacheLogger.class);

@Override
public void onEvent(CacheEvent<?, ?> cacheEvent) {
LOG.info("Key: {} | EventType: {} | Old value: {} | New value: {}",
cacheEvent.getKey(), cacheEvent.getType(), cacheEvent.getOldValue(), cacheEvent.getNewValue());
}

}

Cache ile işlem yapıyorsak muhakkak @EnableCache annotationunu eklememiz lazım.

CacheLogger class’ı ise Cache’deki eventleri dinleyecek ve buna göre bize log verecek. Hangi eventleri dinleyeceğini ise ehcache.xml dosyasında belirttik.

Şimdi bir tane get requesti atıp olanlara bakalım.

localhost:1905/writer/get-writer-by-id/64581a8d0edcc049c96c7fea

Aslında eski value’yi null olarak gördük ve yeni value’yi bizim için cache’ledi.

XML dosyamızda cache süresini 20 sn vermiştik. Aynı isteği 20 sn sonra gönderince aldığımız log aşağıdaki gibi.

Önceki value’yi sildi. EXPIRE etti. sonrasında yeniden CREATED event type’ı ile cache’in içerisine aldı.

@Cacheable(key = "#writerId", condition = "#result != null")
public List<Post> getPostsByWriterId(String writerId) {
return postRepository.findByWriterId(writerId);
}

Aynı zamanda yukarıdaki gibi de Cache işlemlerimiz için Condition verebiliyoruz.

EhCahe ile alakalı daha detaylı bilgiye buradan erişebilirsiniz.

Projenin kodlarına yine buradan erişebilirsiniz.

İletişim, İşbirlikleri ve Teklifler için,

github: https://github.com/fsk

bitbucket: https://bitbucket.org/furkandev

twitter: https://twitter.com/0xfsk

mail: furkansahinkulaksiz@gmail.com

linkedIn: https://www.linkedin.com/in/frknshnklksz/

superpeer: https://superpeer.com/fsk

--

--