Jeśli Twoja aplikacja robi te same zapytania do bazy danych setki razy na sekundę, marnujesz czas i zasoby. Redis rozwiązuje ten problem – to błyskawicznie szybka baza danych w pamięci, której głównym zadaniem jest cache’owanie często używanych danych.
Dlaczego Redis jest ważny
W 2016 roku Redis zyskuje coraz większą popularność jako rozwiązanie cache’ujące i message broker. Używa go Twitter, GitHub, Instagram, Stack Overflow i tysięce innych firm. Redis jest open source, stabilny i ma aktywną społeczność deweloperską.
Co się nauczysz:
- Czym jest cache i dlaczego aplikacje go potrzebują
- Jak zainstalować i skonfigurować Redis
- Podstawowe operacje: SET, GET, DELETE
- Rodzaje danych w Redis: strings, lists, sets, hashes
- Jak używać Redis w aplikacji Java
Wymagania wstępne:
- Podstawowa znajomość programowania
- Rozumienie pojęć: baza danych, aplikacja web
- Znajomość podstaw SQL i baz relacyjnych
- Doświadczenie z Javą będzie pomocne w przykładach
Problem wydajności bez cache’a
Zobaczmy typowy problem wydajnościowy:
// Bez cache - każde wywołanie idzie do bazy danych @GetMapping("/users/{id}") public User getUser(@PathVariable Long id) { // Ta operacja może trwać 50-200ms return userRepository.findById(id); } // Problem: jeśli ten endpoint jest wywoływany 1000 razy/sekundę // dla tego samego użytkownika, wykonujemy 1000 identycznych zapytań!
Czym jest Redis
Redis (Remote Dictionary Server) to:
- In-memory database – wszystkie dane w pamięci RAM
- Key-value store – przechowuje pary klucz-wartość
- NoSQL – nie używa SQL, ma własne komendy
- Persistent – może zapisywać dane na dysk
- Distributed – wspiera clustering
Dlaczego Redis jest szybki?
Aspekt | Redis (RAM) | Tradycyjna baza (Dysk) |
---|---|---|
Czas dostępu | ~0.1ms | ~10-50ms |
Operacje/sek | 100,000+ | 1,000-10,000 |
Typ dostępu | Random access | Sequential (optymalne) |
Latencja | Mikrosecundy | Milisekundy |
Instalacja Redis
Ubuntu/Debian:
# Instalacja Redis sudo apt-get update sudo apt-get install redis-server # Uruchomienie Redis sudo systemctl start redis-server # Sprawdzenie czy działa redis-cli ping # Odpowiedź: PONG
macOS (Homebrew):
# Instalacja brew install redis # Uruchomienie redis-server # Test połączenia redis-cli ping
Windows:
Redis oficjalnie nie wspiera Windows, ale możesz użyć:
- Windows Subsystem for Linux (WSL)
- Docker Desktop z obrazem Redis
- Microsoft fork Redis dla Windows
Podstawowe operacje Redis
Strings – podstawowy typ danych
# Podstawowe operacje key-value redis-cli # Zapisanie wartości SET user:123:name "Jan Kowalski" SET user:123:email "jan@example.com" SET user:123:age 25 # Odczytanie wartości GET user:123:name # Zwraca: "Jan Kowalski" # Sprawdzenie czy klucz istnieje EXISTS user:123:name # Zwraca: 1 (tak) lub 0 (nie) # Usunięcie klucza DEL user:123:age # Ustawienie z czasem wygaśnięcia (TTL) SETEX session:abc123 3600 "user_data" # Klucz wygaśnie po 3600 sekundach (1 godzina)
Lists – listy wartości
# Dodawanie do listy (od lewej) LPUSH notifications:user123 "Nowa wiadomość" LPUSH notifications:user123 "Komentarz do posta" # Dodawanie do listy (od prawej) RPUSH recent_searches "redis tutorial" RPUSH recent_searches "java spring boot" # Pobranie elementów z listy LRANGE notifications:user123 0 -1 # Zwraca wszystkie elementy # Pobranie i usunięcie pierwszego elementu LPOP notifications:user123
Hashes – obiekty
# Hash idealny dla obiektów HSET user:123 name "Jan Kowalski" HSET user:123 email "jan@example.com" HSET user:123 age 25 # Lub wszystko na raz HMSET user:124 name "Anna Nowak" email "anna@example.com" age 30 # Pobranie pojedynczego pola HGET user:123 name # Zwraca: "Jan Kowalski" # Pobranie wszystkich pól HGETALL user:123 # Zwraca: name "Jan Kowalski" email "jan@example.com" age "25" # Sprawdzenie czy pole istnieje HEXISTS user:123 phone # Zwraca: 0 (nie istnieje)
Sets – zbiory unikalnych wartości
# Dodawanie do zbioru SADD user:123:skills "Java" SADD user:123:skills "Spring" SADD user:123:skills "SQL" SADD user:123:skills "Java" # Duplikat - nie zostanie dodany # Sprawdzenie członkostwa SISMEMBER user:123:skills "Java" # Zwraca: 1 (tak) # Wszystkie elementy zbioru SMEMBERS user:123:skills # Zwraca: "Java" "Spring" "SQL" # Różnica między zbiorami SADD user:124:skills "Python" "SQL" "Docker" SDIFF user:123:skills user:124:skills # Zwraca umiejętności które ma user:123 ale nie ma user:124
Redis w aplikacji Java
Konfiguracja Spring Boot + Redis
<!-- pom.xml --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency>
# application.properties spring.redis.host=localhost spring.redis.port=6379 spring.redis.timeout=2000ms spring.redis.jedis.pool.max-active=8 spring.redis.jedis.pool.max-idle=8
Serwis z cache’owaniem
@Service public class UserService { @Autowired private UserRepository userRepository; @Autowired private RedisTemplate<String, Object> redisTemplate; public User findById(Long id) { String cacheKey = "user:" + id; // Sprawdź cache User cachedUser = (User) redisTemplate.opsForValue().get(cacheKey); if (cachedUser != null) { System.out.println("Cache HIT dla user:" + id); return cachedUser; } // Cache MISS - pobierz z bazy danych System.out.println("Cache MISS dla user:" + id); User user = userRepository.findById(id).orElse(null); if (user != null) { // Zapisz w cache na 1 godzinę redisTemplate.opsForValue().set(cacheKey, user, 3600, TimeUnit.SECONDS); } return user; } public void updateUser(User user) { userRepository.save(user); // Usuń z cache żeby następne wywołanie pobrało świeże dane String cacheKey = "user:" + user.getId(); redisTemplate.delete(cacheKey); } }
Annotations-based caching
@EnableCaching @Configuration public class CacheConfig { @Bean public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofHours(1)) .serializeKeysWith(RedisSerializationContext.SerializationPair .fromSerializer(new StringRedisSerializer())) .serializeValuesWith(RedisSerializationContext.SerializationPair .fromSerializer(new GenericJackson2JsonRedisSerializer())); return RedisCacheManager.builder(redisConnectionFactory) .cacheDefaults(config) .build(); } } @Service public class ProductService { @Cacheable(value = "products", key = "#id") public Product findById(Long id) { // Ta metoda zostanie wywołana tylko przy cache MISS return productRepository.findById(id).orElse(null); } @CacheEvict(value = "products", key = "#product.id") public void updateProduct(Product product) { productRepository.save(product); // Cache zostanie automatycznie wyczyszczony } @Cacheable(value = "products", key = "#category + '_' + #page") public List<Product> findByCategory(String category, int page) { return productRepository.findByCategory(category, PageRequest.of(page, 20)); } }
Strategie cache’owania
Cache-Aside (Lazy Loading)
// Sprawdź cache -> jeśli nie ma, pobierz z DB -> zapisz w cache public User getUser(Long id) { User user = cache.get("user:" + id); if (user == null) { user = database.findById(id); cache.set("user:" + id, user, TTL); } return user; }
Write-Through
// Zapisz jednocześnie do DB i cache public void saveUser(User user) { database.save(user); cache.set("user:" + user.getId(), user, TTL); }
Write-Behind (Write-Back)
// Zapisz do cache, do DB asynchronicznie później public void saveUser(User user) { cache.set("user:" + user.getId(), user, TTL); // Async task zapisze do DB później asyncWriter.schedule(() -> database.save(user)); }
Monitoring i debugging Redis
# Statystyki Redis INFO memory INFO stats # Monitorowanie komend w czasie rzeczywistym MONITOR # Lista wszystkich kluczy (UWAGA: wolne na dużych bazach!) KEYS * # Lepiej: skanowanie z pattern SCAN 0 MATCH user:* COUNT 100 # Sprawdzenie TTL klucza TTL user:123 # Zwraca: pozostały czas w sekundach lub -1 (brak TTL) # Ile pamięci zajmuje klucz MEMORY USAGE user:123
Best practices
✅ Dobre praktyki:
- Używaj TTL – zawsze ustawiaj czas wygaśnięcia dla kluczy cache
- Konwencja nazw – używaj strukturalnych nazw: „user:123:profile”
- Monitoruj memory usage – Redis trzyma wszystko w RAM
- Ustaw maxmemory – zapobiega zjedzeniu całej pamięci serwera
- Cache tylko często używane dane – nie wszystko musi być w cache
Redis vs inne rozwiązania cache
Rozwiązanie | Zalety | Wady |
---|---|---|
Redis | Szybki, różne typy danych, persistence | Single-threaded, wymaga dodatkowego serwera |
Memcached | Bardzo prosty, multi-threaded | Tylko key-value, brak persistence |
In-memory cache (aplikacja) | Najszybszy, brak network overhead | Nie dzielony między instancjami |
Database query cache | Automatyczny, transparentny | Ograniczona kontrola, może być nieefektywny |
Redis może być primary database dla prostych aplikacji, ale zwykle używa się go jako cache lub session store. Brak złożonych zapytań i joins ogranicza zastosowania.
Domyślnie Redis zapisuje dane na dysk (RDB snapshots + AOF log). Po restarcie dane zostają przywrócone, ale może być krótka przerwa w dostępności.
Praktycznie tyle ile masz RAM. Redis może mieć do 2^32 kluczy (4 miliardy), każda wartość string do 512MB. Całkowity limit to dostępna pamięć serwera.
Redis domyślnie nie ma autoryzacji i szyfrowania. W produkcji skonfiguruj hasło, SSL/TLS i firewall. Redis 6.0 wprowadza ACL (Access Control Lists).
Redis jest single-threaded ale bardzo wydajny. Dla większej skali użyj Redis Cluster (sharding) lub Redis Sentinel (high availability).
Gdy dane zmieniają się bardzo często, są unnikalne dla każdego requestu, albo gdy overhead zarządzania cache jest większy niż korzyści.
Przydatne zasoby:
🚀 Zadanie dla Ciebie
Zaimplementuj system cache’owania dla bloga:
- Cache artykułów (tytuł, treść, autor, data)
- Cache listy najpopularniejszych artykułów
- Cache komentarzy dla każdego artykułu
- Ustaw TTL: artykuły 1h, popularne 15min, komentarze 5min
Przetestuj:
- Czy cache działa (sprawdź logi cache HIT/MISS)
- Czy dane się odświeżają po TTL
- Czy cache jest czyszczony po aktualizacji artykułu
- Jaka jest różnica w czasie odpowiedzi z cache i bez
Masz pytania o Redis? Podziel się swoimi doświadczeniami w komentarzach – cache’owanie potrafi dramatycznie przyspieszyć aplikację, ale wymaga przemyślanej strategii!