İnşaat Yürütme: Laravel Reverb, WebSocket yayını, Redis pub/sub ve gerçek yük altında dayanıklılığını sağlamaya yönelik teknikler.
Problem
Problem
Canlı değerli maden ticaret platformu, altın ve gümüş fiyat güncellemelerini her bağlı kullanıcıya anlık olarak iletmek zorundadır. Yani her 5 saniyede bir kontrol değil, gerçek zamanlı ve alt saniye gecikme gerekmektedir. Ayrıca 30,000 eşzamanlı bağlantıyı sürdürebilmelidir.
Bu yazı, bunu Laravel 11, Laravel Reverb, Redis ve bazı mimari kararlarla nasıl inşa ettiğimi detaylandırıyor. Bu kararlar, bir demo ile üretime hazır bir sistem arasındaki farkı yarattı.
Teknoloji Yığını
Teknoloji Yığını
- Laravel 11 + PHP 8.2
- Laravel Reverb — kendinden barındırılan WebSocket sunucusu (Pusher’ın yerini alır)
- Laravel Echo — frontend WebSocket istemcisi
- Redis — pub/sub omurgası + önbellek
- Laravel Horizon — kuyruğu izleme
- MySQL 8 — OHLCV mum verisi depolama (kapsayıcı indekslerle)
Tam kaynak: github.com/Hafiz-M-Subhan/laravel-precious-metals-platform
Mimari Genel Bakış
Mimari Genel Bakış
Price Feed (external API)
↓
PriceService::ingestTick()
↓
DB update + Redis cache (5s TTL)
↓
broadcast(new PriceUpdated($asset))
↓
Laravel Reverb (WebSocket server)
↓
prices.XAU channel → 30k subscribers
Ana içgörü: veritabanı, aboneler için sıcak yol değildir. Fiyat verisi Redis üzerinden akar ve doğrudan WebSocket ile iletilir. Veritabanına yalnızca her tikte yazılır, 30,000 kez okunmaz.
Adım 1 — Olay
Adım 1 — Olay
PriceUpdated olayı yayılması gereken olaydır. En kritik kararlar şunlardır:
Hangi kanal? Herkes tarafından erişilebilen, böylece kimliği doğrulanmamış ziyaretçiler de canlı sayfada tikleri alabilir.
Ne tür bir yük içeriği? Mümkün olduğunca küçük. Her ek bayt, 30,000 ile çarpılır.
class PriceUpdated implements ShouldBroadcast
{
public function broadcastOn(): array
{
return [
new Channel("prices.{$this->asset->symbol}"),
new PresenceChannel('live-event'), // izleyici sayısını taşır
];
}
public function broadcastWith(): array
{
// 7 alan — kasıtlı olarak minimal
return [
'symbol' => $this->asset->symbol,
'spot' => (float) $this->asset->spot_price,
'bid' => (float) $this->asset->bid_price,
'ask' => (float) $this->asset->ask_price,
'change_pct' => (float) $this->asset->daily_change_pct,
'direction' => $this->asset->spot_price > $this->previousPrice ? 'up' 'down',
'ts' => now()->toIso8601String(),
];
}
// Fiyat %0.001'den az hareket ederse yayını atla — gürültüyü %80 oranında azaltır
public function broadcastWhen(): bool
{
if ($this->previousPrice == 0) return true;
$change = abs(($this->asset->spot_price - $this->previousPrice) / $this->previousPrice);
return $change >= 0.00001;
}
}
broadcastWhen() kapısı çoğu Laravel projesinde yeterince kullanılmaz. Metal akışında, fiyatlar bazen aynı değeri tekrar tekrar güncelleyebilir. Filtre olmadan, 30,000 müşteriye on binlerce no-op mesajı yayınlayacaksınız. Bununla, yaklaşık %80 oranında kuyruk mesajlarını kesmiş olursunuz.
Adım 2 — WebSocket Sunucusu (Laravel Reverb)
Adım 2 — WebSocket Sunucusu (Laravel Reverb)
Laravel Reverb, Laravel’in resmi kendinden barındırılan WebSocket sunucusudur ve 2024’te yayınlanmıştır. Reverb’den önce ya Pusher için ödeme yapmanız ya da ayrı bir Node.js sunucusu (Soketi vb.) çalıştırmanız gerekiyordu. Reverb, yerel bir PHP süreci olarak çalışır:
php artisan reverb:start --host=0.0.0.0 --port=8080
config/broadcasting.php dosyasında:
'reverb' => [
'driver' => 'reverb',
'app_id' => env('REVERB_APP_ID'),
'app_key' => env('REVERB_APP_KEY'),
'app_secret' => env('REVERB_APP_SECRET'),
'options' => [
'host' => env('REVERB_HOST', '0.0.0.0'),
'port' => env('REVERB_PORT', 8080),
'scheme' => env('REVERB_SCHEME', 'http'),
],
],
Reverb, altında bir engelleme yapmayan olay döngüsü (ReactPHP) kullanır. Tek bir süreçte binlerce eşzamanlı bağlantıyı yönetir; geleneksel PHP gibi bağlantı başına bir iplik modeli yoktur.
Adım 3 — Redis’in Temel Görevi
Adım 3 — Redis’in Temel Görevi
Redis burada iki görevi üstlenmektedir:
1. Fiyat önbelleği — her tik, 5 saniyelik bir TTL ile Redis’e yazılır. API yanıtları, MySQL yerine Redis’ten okunur. Talep patlaması sırasında (30 bin izleyicinin /api/v1/assets‘ye odaklandığı canlı bir etkinlikte), veritabanı kesinlikle 0 ek okuma almaz.
// PriceService::ingestTick()
Cache::put("asset:price:{$symbol}", [
'spot' => $spot,
'bid' => $bid,
'ask' => $ask,
'ts' => now()->toIso8601String(),
], 5); // 5 saniye
2. Kuyruk arka ucu — yayın görevi Redis kuyrukları üzerinden geçer, veritabanı üzerinden değil. Bu kritik bir nokta. QUEUE_CONNECTION=redis değişkenini .env dosyasında ayarlayın. Veritabanı kuyrukları seri olduğundan, 2 saniyede bir ateş eden bir fiyat akışındaki yük altında zorlanırlar.
Horizon tüm bunları gerçek zamanlı bir kontrol paneli ile izler, /horizon adresinde görebilirsiniz. Kuyruk akışını, başarısız işleri ve işçi yükünü gerçek zamanlı olarak görebilirsiniz.
Adım 4 — OHLCV Mum Veri Depolama
Adım 4 — OHLCV Mum Veri Depolama
Her tik, mevcut 1 dakikalık mumu (açılış, en yüksek, en düşük, kapanış) güncellemelidir. Naif yaklaşım: SELECT + UPDATE. 30 tik/dakika ve 4 metal ile bu, dakikada 120 gidiş-dönüş ve kilitleme sorunları yaratır.
Daha iyi yaklaşım: upsert() — tek sorgu, atomik, SELECT gerektirmeden:
PriceHistory::upsert(
[[
'asset_id' => $assetId,
'resolution' => '1m',
'open' => $price, // yalnızca INSERT sırasında ayarlanır
'high' => $price,
'low' => $price,
'close' => $price,
'volume' => 0,
'recorded_at' => now()->startOfMinute(),
]],
uniqueBy: ['asset_id','resolution','recorded_at'],
update: [
'high' => DB::raw("GREATEST(high, {$price})"),
'low' => DB::raw("LEAST(low, {$price})"),
'close' => $price,
],
);
Uniq indeks (asset_id, resolution, recorded_at) ile bu hem hızlı hem de idempotent hale gelir; eğer aynı tik bir şekilde iki kez gelirse, hiçbir şey bozulmaz.
Adım 5 — Frontend (Laravel Echo)
Adım 5 — Frontend (Laravel Echo)
import Echo from 'laravel-echo';
import Pusher from 'pusher-js'; // Echo, Reverb ile bile Pusher protokolünü kullanır
window.Pusher = Pusher;
const echo= new Echo({
broadcaster: 'reverb',
key: import.meta.env.VITE_REVERB_APP_KEY,
wsHost: import.meta.env.VITE_REVERB_HOST,
wsPort: import.meta.env.VITE_REVERB_PORT,
forceTLS: false,
enabledTransports: ['ws', 'wss'],
});
// Altın fiyat güncellemelerine abone ol
echo.channel('prices.XAU')
.listen('.price.updated', (data) => {
updatePriceTicker(data.symbol, data.spot, data.direction);
});
// Presence kanalı — canlı etkinlik sayfası için izleyici sayısını al
echo.join('live-event')
.here((users) => setViewerCount(users.length))
.joining(() => setViewerCount(prev => prev + 1))
.leaving(() => setViewerCount(prev => prev - 1));
Presence kanalı, canlı izleyici sayısını ücretsiz olarak almanızı sağlar — bu mekanizma, Kettner’in canlı etkinlik sayfasında, 30,000 eşzamanlı izleyiciye ulaştığında kullanılır.
Performans Rakamları
Performans Rakamları
| Senaryo | Optimize Edilmemiş | Optimize Edilmiş |
|---|---|---|
| Patlamada API (1k req/s) | MySQL: 1000 reads/s | MySQL: ~0 reads (Redis cache) |
| Kuyruk mesajları/dakika | ~120 raw ticks | ~25 (broadcastWhen filtreleri) |
| Mum upsert’leri | 2 sorgu (SELECT+UPDATE) | 1 sorgu (upsert) |
| Bağlantı yükü | 30k × anket aralığı | 1 kalıcı bağlantı/kullanıcı |
Yerel Olarak Çalıştırmak
Yerel Olarak Çalıştırmak
git clone https://github.com/Hafiz-M-Subhan/laravel-precious-metals-platform.git
cd laravel-precious-metals-platform
docker compose up -d # MySQL + Redis + Elasticsearch + Reverb + Horizon
php artisan migrate --seed
php artisan reverb:start
php artisan horizon
php artisan prices:simulate --interval=2 # canlı fiyat akışını simüle eder
Simülatör, gerçekçi fiyat hareketlerini oluşturmak için Geometrik Brownian Hareketi kullanır. Bu model, Black-Scholes opsiyon fiyatlandırmasında kullanılan model ile aynıdır.
Gerçek Ölçekle Farklı Ne Yapardım
Gerçek Ölçekle Farklı Ne Yapardım
- Yatay Reverb ölçekleme — yük dengeleyici arkasında birden fazla Reverb düğümü, arasındaki yayınların senkronizasyonu için Redis pub/sub kullanarak
- Ayrı okuma kopyaları — mum grafik sorguları için OHLCV geçmişi, birincil veritabanına ihtiyaç duymaz.
- Mesaj sıkıştırılması — 30 bin abone ile WebSocket çerçevelerinde gzip kullanmak önemli ölçüde bant genişliği tasarrufu sağlar.
- Geri basınç yönetimi — yavaş tüketicilerin algılanması ve bellek basıncı oluşmadan önce bağlantılarının kesilmesi.
Kaynak Kodu
Kaynak Kodu
Tam proje GitHub’da: github.com/Hafiz-M-Subhan/laravel-precious-metals-platform
Modelleri, olayları, işleri, hizmetleri, Filament 3 admin panelini, Docker Compose’u, migrasyonu ve bir fiyat simülatörünü içerir.
Etiketler: #laravel #php #websocket #redis #mimari
Kaynak: Orijinal Makale


