Java ve Redis
Redis (REmote DIctionary Service); database, cache manager, message broker, stream engine, pub/sub mekanizması olarak kullanılabilen bir in-memory data structure store’dur.
String, hash, list, set, sorted set, stream, geospital index gibi verileri tutabilir.
En yüksek performanslara erişebilmek için in-memory-dataset’ler ile çalışır.
NOT: Redis’teki veriler direkt olarak diske persist edilebilir ama bu durum opsiyoneldir.
Redis’i bilgisayarda hem docker ile çalıştırıp docker’da ayağa kaldırdığınız redis instance’ına bağlanabilirsiniz. Hem de redis’i buradaki linkten indirip kurduktan sonra redis’e client olabilirsiniz.
Docker ile Redis’i çalıştırmak
Redis’i docker ile çalıştırmak için öncelik olarak
docker run — name my-medium-redis -d -redis
yukarıdaki komutu çalıştırmamız lazım.
Sonrasındaki adım ise aslında Redis CLI’ya bağlanmak. Bunun için ise yapmamız gereken,
docker exec -it my-medium-redis redis-cli
Bu komutu da çalıştırdıktan sonra artık Redis CLI içerisindeyiz ve Redis ile script çalıştırmaya başlayabiliriz.
Redis’de key-value çiftleri çok önemlidir ve bunun için başlayacağımız ilk komut KEYS komutu.
-> KEYS komutu redisde saklanan tüm KEY değerlerini getirir.
Herhangi bir key set edilmediği için empty array döndü.
-> SET ile bir kaç değer ekleyip KEYS komutunu çalıştırdığımız zaman ise,
Artık bizim redis’imizde KEY değerleri ve onlara ait value değerleri mevcut.
GET anahtar kelimesi ile bu değerleri görebiliriz.
-> Eğer herhangi bir key değerinin varlığını kontrol etmek istersek de EXISTS anahtar kelimesini kullanabiliriz. Geriye integer olarak 0 ya da 1 olacak şekilde bize sonuç döner. Eğer değer varsa integer 1, eğer değer yoksa integer 0 olarak sonucu görebiliriz.
-> Eğer bir key değerini silmek istersek de DEL anahtar kelimesini kullanabiliriz. DEL anahtar kelimesini, eğer silmek istediğimiz key değeri mevcut ise parametre olarak verdiğimiz key’i siler integer 1 döndürür. Ama eğer parametre olarak verdiğimiz key değeri yoksa integer 0 döndürür.
APPEND anahtar kelimesi, eğer parametre olarak verdiğimiz key değeri yoksa ilgili key değerini oluşturur ve değeri döndürür, eğer key değeri varsa değeri, var olan değerin sonuna ekler. Geriye ise integer olarak eklenen value değerinin lengthini döndürür.
Buraya kadar redis’i biraz tanıdık ve redis komutları ile işlemler yaptık. CLI ekranından çıkmak için ise EXIT komutunu yazıp enter’a basmanız yeterlidir.
Java Redis Connection
Java ile redis connectionunu yaparken redis server’ın ayakta olması lazım. Bu sefer de redis’i bilgisayar’a yükleyip bağlanalım.
ÇOK ÖNEMLİ NOT: Redis’i docker’dan ayağa kaldırıp, java ile bağlanabilirsiniz ama burada dockerda ayağa kaldırdığınız redis instance’ı ile sizin ayağa kaldırdığınız java projesinin aynı network’de olması gerekir ve bakılması gereken bazı parametreler olabilir. Bu yüzden direkt olarak docker üzerinde çalışan redis’e core java ile direkt olarak bağlanamayabilirsiniz.
Redis’i bilgisayara download ettikten sonra redis’in bulunduğu klasöre gidip terminal ekranında “make” komutunu çalıştırdığınız zaman redis localinize kurulmuş olur. İşlemler bittikten sonra “make test” komutu ile test de edebilirsiniz.
Terminali de kapattıktan sonra redis-cli komutu ile redis-cli’a bağlanabilirsiniz.
Her şey tamam olduktan sonra
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.1</version>
</dependency>
Yukarıdaki bağımlılığı pom.xml’e eklemeniz gerekmektedir. Gradle bağımlılığı için ya da daha güncel version için buraya bakabilirsiniz.
Jedis, redis için performans ve kullanım kolaylığı sağlaması için tasarlanan bir java client’idir.
public static void main(String[] args) {
JedisPool pool = new JedisPool("localhost", 6379);
}
Yukarıdaki kod ile çok basit bir şekilde bağlantı işlemini sağlayabilirsiniz.
* Redis’e Key-Value Değeri Set Etmek
public static void main(String[] args) {
JedisPool pool = new JedisPool("localhost", 6379);
try(Jedis jedis = pool.getResource()) {
jedis.set("name", "Furkan Sahin Kulaksiz");
System.out.println(jedis.get("name"));
}
}
Yukarıdaki kod ile de redis’e bir key-value değeri set edebilirsiniz.
try-with-serource’u gönül rahatlığı ile kullanabilirsiniz. JedisPool clas’ı Pool<T> class’ını extend etmiştir. Pool<T> class’ı GenericObjectPool<T> class’ını implemente etmiştir. GenericObjectPool<T> class’ının implemente ettiği interface’lerden birisi ObjectPool<T> interface’idir ve bu interface de Closable interface’ini implemente etmiştir.
* Redis’e Map değeri Set Etmek
Yukarıdaki mantıkla aynı şekilde Redis’e HashMap ekleyebiliriz.
public static void main(String[] args) {
JedisPool pool = new JedisPool("localhost", 6379);
try(Jedis jedis = pool.getResource()) {
Map<String, String> hashMap = new HashMap<>();
hashMap.put("name", "Furkan");
hashMap.put("surname", "Kulaksiz");
hashMap.put("company", "BeeMe");
jedis.hset("user-session:1234", hashMap);
System.out.println(jedis.hgetAll("user-session:1234"));
}
}
jedis instance’ı üzerinden HashMap’imizi user-session:123 key’i ile Redis’e ekledik.
HGETALL ile user-session:1234 içerisindeki tüm değerleri getirdik.
HGET ile ‘ de map içerisindeki istediğimiz değeri alabiliriz.
* Redis’e List Set Etmek
Buraya geçmeden önce, JedisPool ile her seferinde connection için try-with-resources kullanmak zahmetli olabileceğinden JedisPooled class’ı kullanılabilir.
public static void main(String[] args) {
JedisPooled jedis = new JedisPooled("localhost", 6379);
jedis.lpush("queue#tasks", "firstTask");
jedis.lpush("queue#tasks", "secondTask");
jedis.lrange("queue#tasks", 0, -1).forEach(System.out::println);
}
LPUSH methodu ile bir list belirleyip bu list içerisine değerlerimizi atabiliriz.
Yukarıdaki kodu çalıştırdığımız zaman hem IDE’nin console ekranında, hem de Redis-CLI’da firstTask ve secondTask değerlerini görebiliriz.
(LPUSH komutu list adını ve içerisine eklenecek value değerini alır.)
(LRANGE komutu list tasklarını göstermek için kullanılır ve parametre olarak list adını, start değerini, stop değerini alır.)
Ve bu şekilde de list değeri ekleyebiliriz.
* JSON Veri Eklemek
public static void main(String[] args) {
JedisPooled jedisPooled = new JedisPooled("localhost", 6379);
String jsonDocument = "{ \"name\": \"John\", \"age\": 30 }";
jedisPooled.jsonSetWithEscape("user:123", jsonDocument);
Object o = jedisPooled.jsonGet("user:123");
System.out.println(o);
}
String olarak yine json verimizi koddaki gibi jsonWithEscape methodu ile ekleyebiliriz ve bu JSON’u CLI’da görebiliriz.
jedisPooled üzerinden bir de jsonSet methoduna erişebilirsiniz. Bu da aslında ama aynı amaca hizmet eder fakat tek fark kaçış karakterlerinde ortaya çıkar.
Buraya kadar olan kısımları data-structure store başlığı altında toplayabiliriz.
REDIS İLE DOCUMENT DATABASE İŞLEMLERİ
Buradaki işlemler için RedisStack ‘ in bilgisayarınızda kurulu olması gerekir. Tabiiki docker ile de RedisStack’i çalıştırabilirsiniz ama şu an için ben bu kodları yazarken bilgisayara kurup çalıştırdım.
RedisStack’i indirmek için buraya tıklayabilirsiniz.
RedisStack’i kurup çalıştırdıktan sonra aşağıdaki gibi bir kod yazalım.
public record Bicycle(String brand, String model, BigDecimal price,
String description, String condition) {
}
Yukarıdaki Record bizim kullanacağımız yapı olacak.
public static void main(String[] args) {
JedisPooled jedisPooled = new JedisPooled("localhost", 6379);
SchemaField[] schemaField = {
TextField.of("$.brand").as("brand"),
TextField.of("$.model").as("model"),
TextField.of("$.description").as("description"),
NumericField.of("$.price").as("price"),
TagField.of("$.condition").as("condition")
};
jedisPooled.ftCreate("idx:bicycle",
FTCreateParams.createParams()
.on(IndexDataType.JSON).addPrefix("bicycle:"), schemaField);
Bicycle[] bicycles = {
new Bicycle(
"Velorim",
"Jigger",
new BigDecimal(270),
"new",
"""
Small and powerful, the Jigger is the best ride
for the smallest of tikes! This is the tiniest
kids’ pedal bike on the market available without
a coaster brake, the Jigger is the vehicle of
choice for the rare tenacious little rider
raring to go.
"""
),
new Bicycle(
"Bicyk",
"Hillcraft",
new BigDecimal(1200),
"used",
"""
Kids want to ride with as little weight as possible.
Especially on an incline! They may be at the age
when a 27.5 inch wheel bike is just too clumsy coming
off a 24 inch bike. The Hillcraft 26 is just the solution
they need!
"""
),
new Bicycle(
"Nord",
"Chook air 5",
new BigDecimal(815),
"used",
"""
The Chook Air 5 gives kids aged six years and older
a durable and uberlight mountain bike for their first
experience on tracks and easy cruising through forests
and fields. The lower top tube makes it easy to mount
and dismount in any situation, giving your kids greater
safety on the trails.
"""
),
new Bicycle(
"Eva",
"Eva 291",
new BigDecimal(3400),
"used",
"""
The sister company to Nord, Eva launched in 2005 as the
first and only women-dedicated bicycle brand. Designed
by women for women, allEva bikes are optimized for the
feminine physique using analytics from a body metrics
database. If you like 29ers, try the Eva 291. It's a
brand new bike for 2022.. This full-suspension,
cross-country ride has been designed for velocity. The
291 has 100mm of front and rear travel, a superlight
aluminum frame and fast-rolling 29-inch wheels. Yippee!
"""
),
new Bicycle(
"Noka Bikes",
"Kahuna",
new BigDecimal(3200),
"used",
"""
Whether you want to try your hand at XC racing or are
looking for a lively trail bike that's just as inspiring
on the climbs as it is over rougher ground, the Wilder
is one heck of a bike built specifically for short women.
Both the frames and components have been tweaked to
include a women’s saddle, different bars and unique
colourway.
"""
),
new Bicycle(
"Breakout",
"XBN 2.1 Alloy",
new BigDecimal(810),
"new",
"""
The XBN 2.1 Alloy is our entry-level road bike – but that’s
not to say that it’s a basic machine. With an internal
weld aluminium frame, a full carbon fork, and the slick-shifting
Claris gears from Shimano’s, this is a bike which doesn’t
break the bank and delivers craved performance
"""
),
new Bicycle(
"ScramBikes",
"WattBike",
new BigDecimal(2300),
"new",
"""
The WattBike is the best e-bike for people who still feel young
at heart. It has a Bafang 1000W mid-drive system and a 48V
17.5AH Samsung Lithium-Ion battery, allowing you to ride for
more than 60 miles on one charge. It’s great for tackling hilly
terrain or if you just fancy a more leisurely ride. With three
working modes, you can choose between E-bike, assisted bicycle,
and normal bike modes.
"""
),
new Bicycle(
"Peaknetic",
"Secto",
new BigDecimal(430),
"new",
"""
If you struggle with stiff fingers or a kinked neck or back after
a few minutes on the road, this lightweight, aluminum bike
alleviates those issues and allows you to enjoy the ride. From
the ergonomic grips to the lumbar-supporting seat position, the
Roll Low-Entry offers incredible comfort. The rear-inclined seat
tube facilitates stability by allowing you to put a foot on the
ground to balance at a stop, and the low step-over frame makes it
accessible for all ability and mobility levels. The saddle is
very soft, with a wide back to support your hip joints and a
cutout in the center to redistribute that pressure. Rim brakes
deliver satisfactory braking control, and the wide tires provide
a smooth, stable ride on paved roads and gravel. Rack and fender
mounts facilitate setting up the Roll Low-Entry as your preferred
commuter, and the BMX-like handlebar offers space for mounting a
flashlight, bell, or phone holder.
"""
),
new Bicycle(
"nHill",
"Summit",
new BigDecimal(1200),
"new",
"""
This budget mountain bike from nHill performs well both on bike
paths and on the trail. The fork with 100mm of travel absorbs
rough terrain. Fat Kenda Booster tires give you grip in corners
and on wet trails. The Shimano Tourney drivetrain offered enough
gears for finding a comfortable pace to ride uphill, and the
Tektro hydraulic disc brakes break smoothly. Whether you want an
affordable bike that you can take to work, but also take trail in
mountains on the weekends or you’re just after a stable,
comfortable ride for the bike path, the Summit gives a good value
for money.
"""
),
new Bicycle(
"ThrillCycle",
"BikeShind",
new BigDecimal(815),
"refurbished",
"""
An artsy, retro-inspired bicycle that’s as functional as it is
pretty: The ThrillCycle steel frame offers a smooth ride. A
9-speed drivetrain has enough gears for coasting in the city, but
we wouldn’t suggest taking it to the mountains. Fenders protect
you from mud, and a rear basket lets you transport groceries,
flowers and books. The ThrillCycle comes with a limited lifetime
warranty, so this little guy will last you long past graduation.
"""
),
};
for (int i = 0; i < bicycles.length; i++) {
jedisPooled.jsonSetWithEscape(String.format("bicycle:%d", i), bicycles[i]);
}
}
Yukarıdaki main methodunu detaylı bir şekilde inceleyecek olursak,
SchemaField[] schemaField = {...};
RedisJSON indeksi oluşturmak için kullanılacak JSON belgesinin şema alanlarını tanımlayan bir SchemaField
array oluşturuluyor. Bu şema alanları, JSON belgesinin içindeki belirli alanları ve bu alanların türlerini temsil eder. Örneğin, "brand"
alanı bir metin (TextField
) olarak belirtiliyor, "price"
alanı bir sayı (NumericField
) olarak belirtiliyor.
of
ve as
ifadeleri RedisJSON indekslemesi için kullanılan şema alanlarınnı tanımlar ve bu alanlara bir alias vermek için kullanılır.
TextField ya da NumericField gibi kullanabileceğiniz başka türler de vardır. JsonField, AutoField, GeoField bunlardan bazılarıdır.
jedisPooled.ftCreate("idx:bicycle", FTCreateParams.createParams()...)
: Bu satırda RedisJSON indeksi oluşturuluyor. idx:bicycle
indeks adı olarak belirtilir. jedisPooled
nesnesi, Redis'e bağlanmak için kullanılır.
FTCreateParams.createParams()
: Bu bölüm, indeksi oluşturmak için gerekli parametreleri ayarlamak için kullanılır. Bu parametreler, indeksin türünü, öneklerini ve şema alanlarını belirtir.
.on(IndexDataType.JSON)
: Bu bölüm, indeksin JSON veri türüne uygulanacağını belirtir. Yani indeks, JSON belgelerini indekslemek için kullanılacaktır.
.addPrefix("bicycle:")
: Bu bölüm, indeks anahtarlarına önek eklemek için kullanılır. Örneğin, indekslenen JSON belgesinin anahtarlarına "bicycle:"
öneki eklenir. Bu, indekslenen belgeleri kolayca tanımlamanıza yardımcı olur.
schemaField
: Bu bölüm, JSON belgesinin şema alanlarını belirtir. Daha önce açıklanan gibi, her şema alanı belirli bir JSON alanını ve bu alanın türünü temsil eder. Örneğin, "brand"
alanı bir metin (string) türündeyse, TextField.of("$.brand").as("brand")
ile belirtilir.
Sonrasında ise tanımladığımız Record tipinde bir array oluşturup içeriğini doldurduk. Ve bu değerleri redis’e kaydettik.
Artık eklediğimiz dokümanlar üzerinden sorgulama işlemleri yapabiliriz.
public static void main(String[] args) {
JedisPooled jedisPooled = new JedisPooled("localhost", 6379);
Query query1 = new Query("*");
List<Document> result1 = jedisPooled
.ftSearch("idx:bicycle", query1).getDocuments();
System.out.println("Documents found:" + result1.size());
}
Query query1 = new Query("*");
: Bu satırda, Query
sınıfından bir sorgu (query1
) oluşturulur. Bu sorgu, "*"
ifadesini kullanarak indeksteki tüm belgeleri getirmek için kullanılır. Yani, bu sorgu tüm dokümanları seçer.
jedisPooled.ftSearch("idx:bicycle", query1)
: Bu satırda, jedisPooled
nesnesi üzerinden RedisJSON indeksini (idx:bicycle
) ve oluşturulan sorguyu (query1
) kullanarak sorgulama yapılır. ftSearch
yöntemi, verilen indeks üzerinde belge sorgulaması yapar.
.getDocuments()
: Bu bölüm, sorgu sonucunda dönen belgelerin listesini alır. Sorgu sonucunda dönen belgeler, List<Document>
türünde bir liste olarak elde edilir.
Query query2 = new Query("@model:Jigger");
List<Document> result2 = jedisPooled.ftSearch("idx:bicycle", query2)
.getDocuments();
System.out.println(result2);
Query query2 = new Query("@model:Jigger");
: Bu satırda, Query
sınıfından bir sorgu (query2
) oluşturulur. Bu sorgu, @model:Jigger
ifadesini kullanarak "model"
alanının değeri "Jigger"
olan belgeleri filtrelemek için kullanılır. Yani, sorgu belirli bir kriteri karşılayan belgeleri seçer.
jedisPooled.ftSearch("idx:bicycle", query2)
: Bu satırda, jedisPooled
nesnesi üzerinden RedisJSON indeksini (idx:bicycle
) ve oluşturulan sorguyu (query2
) kullanarak sorgulama yapılır. ftSearch
yöntemi, verilen indeks üzerinde belge sorgulaması yapar.
Kodun sonucu ise aşağıdaki gibidir.
Query price = query2.returnFields("price");
//ya da açık açık yazmak gerekirse,
Query price = new Query("model: Jigger").returnFields("price");
List<Document> documents = jedisPooled.ftSearch("idx:bicycle", price)
.getDocuments();
System.out.println(documents);
Yukarıdaki kodda ise model’i Jigger olan bcycle’ların price değerlerini ekrana yazdırdık. Yukarıdaki kodun sonucu ise aşağıdaki gibidir.
Query query4 = new Query("@price:[500 1000]");
List<Document> result4 = jedisPooled.ftSearch("idx:bicycle", query4)
.getDocuments();
System.out.println(result4);
Query içerisinde aratmak istediğimiz bir alanı bu şekilde de dilediğimiz gibi filtreleyebiliriz.
AggregationBuilder ab = new AggregationBuilder("*").groupBy("@condition",
Reducers.count().as("count"));
AggregationResult ar = jedisPooled.ftAggregate("idx:bicycle", ab);
for (int i = 0; i < ar.getTotalResults(); i++) {
System.out.println(ar.getRow(i).getString("condition") + " - "
+ ar.getRow(i).getString("count"));
}
Bu kod, RedisJSON indeksi üzerinde dokümanları belirli bir kritere göre gruplamak ve bu grupların sayısını hesaplamak için kullanılır. İşte bu kodun ayrıntılı açıklaması:
AggregationBuilder ab = new AggregationBuilder("*").groupBy("@condition", Reducers.count().as("count"));
: Bu satırda, bir AggregationBuilder
nesnesi oluşturulur. Bu nesne, belgelerin gruplandırılması ve toplu işlemler yapılması için kullanılır. groupBy
yöntemi, belgeleri "@condition"
alanına göre gruplar ve bu grupların sayısını "count"
adıyla hesaplar. Yani, belgeleri "@condition"
alanına göre gruplayacak ve her grup için bir "count"
değeri oluşturacak.
AggregationResult ar = jedis.ftAggregate("idx:bicycle", ab);
: Bu satırda, jedis
nesnesi üzerinden RedisJSON indeksini (idx:bicycle
) ve oluşturulan toplu işlemi (ab
) kullanarak gruplandırma ve sayma işlemi yapılır. ftAggregate
yöntemi, belirli bir indeks üzerinde toplu işlemleri gerçekleştirir.
for (int i = 0; i < ar.getTotalResults(); i++) { ... }
: Bu bölüm, toplu işlemin sonuçlarını döngü içinde işlemek için kullanılır. getTotalResults
methodu, toplu işlemin sonucunda kaç tane sonuç bulunduğunu döndürür.
System.out.println(ar.getRow(i).getString("condition") + " - " + ar.getRow(i).getString("count"));
: Bu satırda, her grup için "condition"
alanının değeri ile hesaplanan "count"
değeri ekrana yazdırılır. Bu, gruplanmış belgelerin her birinin "condition"
değerini ve bu grupların sayısını görselleştirmek için kullanılır.
Kodlara erişmek için buraya tıklayabilirsiniz.
İletişim, İşbirlikleri ve Teklifler için,
superpeer: https://superpeer.com/fsk
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/
linktree: https://linktr.ee/furkandev