Developer Summit 2020 Data Tutarlılığını Microservisler ile Sağlama
Selamlar,
21 Kasım 2020 Cumartesi günü online “DEVELOPER SUMMIT” etkinliğini gerçekleştirdik. Amaç, farklı lokasyonlarda bulunan dağıtık cacheler’de, data tutarlılığını sağlamakdı. Kısaca 2 farklı şehirde, birbirinden bağımsız Redis cachelerin tek bir DB üzerinde çalışması durumunda, birinin güncellemesi anında diğerinin buna nasıl eşitleneceğini hep beraber tartıştık.
Öncelikle NodeJS ile bir haber portalının CRUD servislerini yazdık. Projede kullanılan teknolojiler DB için mongoDB, Backend için NodeJS ve Express kütüphaneleri, Front-End için Angular Framework, Data paketlerinin işlenmesi için sıraya alındığı queue RabbitMQ, Real-Time socket için SocketIO ve son olarak performansı arttırmak için Distributed Cache Redis kullanılmıştır.
Angular UI’da güncellenen bir haberi, Backend NodeJS tarafında mongoDB’de güncelledik. Daha sonra Redis cachelerin güncelleme işini, asenkron olarak var olan sistemden bağımsız ilerletebilmek adına RabbitMQ’nun “newsChannel”‘ına pushladık. Bir başka NodeJS ile yazdığımız MicroService consumer, ilgili “newsChannel”‘ı dinleyip kendisine gelen News datasını, tanımlanan Redis sonuçlarında tek tek güncelledi ve aynı zamanda SocketIO ile kendisine bağlı clientların “updatedNews” function’ını tetikleyerek, client browserlarının yenilenmeden güncel haber bilgisinin gösterilmesi sağladı.
NodeJS
Get/ID: Aşağıda örnek amaçlı seçilen bir haberin detayını gösteren servis tanımlanmıştır. En başta ilgili haber Redis’de var mı diye bakıldı, yok ise mongoDB’den çekilip Redis’e 5 dakkalık expire süresi ile atıldı. Eğer Redis’e hiç erişilemiyor ise, doğrudan mongoDB’den kayıt çekildi.
Önemli Konu: Redis sistemden çıkarılır ise de, projenin kesintiye uğramadan çalışması gerekmektedir.
service.js/app.get(“/news/:newsId”)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
app.get("/news/:newsId", function (req, res) { //NewsID alınır. var newsId = req.params.newsId; //Redis'e bağlanılmış mı? if (redisClient.connected) { var newsKey = 'news:' + newsId; //Redisden Haber Kaydı Çekilir. redisClient.get(newsKey, function (err, news) { //Redisde Haber Kaydı yok ise. if (news == null) { var query = { "NewsId": newsId }; //MongoDB'den Haber Çekilir. //mongoClient.find(query, function (err, doc) { mongoClient.findOne(query, function (err, doc) { var data = JSON.stringify(doc); //Redis'e kayıt konur. redisClient.set(newsKey, data, function (err, res) { }); redisClient.expire(newsKey, 300); //Expire Süresi 5 dakika res.send(doc); }) } //Redisde Haber Kaydı var ise. else { var doc = JSON.parse(news) res.send(doc); } }); } else { var query = { "NewsId": req.params.newsId }; mongoClient.findOne(query, function (err, doc) { res.send(doc); }) } }) |
Post/updateNews: Güncellenecek haber, aşağıda tanımlı NodeJS servise gelir. İlgili data, mongoDB güncellendikten sonra rabbitMQ’da “newsChannel“‘ına atılır. Dikkat edilir ise, bu aşamada Redis’de güncelleme işlemi yapılmamaktadır. Güncellenen data RabbitMQ’ya atılarak, Microservis tarafından clientların browser’ının, real-time olarak güncel haber datası ile yenilenmesi ve tanımlı tüm Redis sunucularına haberin son halinin atılması sağlanmaktadır.
service.js/app.post(“/updateNews”)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
app.post('/updateNews', async (req, res) => { //console.log("data:" + stringify(req.body)); try { var News = mongoose.model('News', newsSchema, 'news'); var updateNews = new News(req.body); const news = await News.findOne({ NewsId: updateNews.NewsId }); await news.updateOne(updateNews); //Send RabbitMQ sendRabbitMQ("newsChannel", JSON.stringify(updateNews)); return res.status(200).json({ status: "succesfully update" }); } catch (error) { res.status(500).send(error); } }) |
AngularUI
app.component.html: Ekrana basılan, yukarıda görülen güncel haber detay sayfasıdır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<head> <title>{{dataNews.Title}}</title> </head> <div class="container"> <div class="jumbotron"> <h2>{{dataNews.Title}}</h2> </div> <div align="right"><b>{{dataNews.CreatedDate}}</b></div> <div> <div> <h3>{{dataNews.Detail}}</h3> </div> <table class="table"> <tr> <td> <div> <img src="../assets/images/{{dataNews.Image}}.jpg" width="800px"> </div> </td> </tr> </table> </div> </div> |
newService.ts: UI tarafında, ilgili haber detaya gidildiğinde “getNewsById()” ile ilgili haber detayı, servisden çekilir. Ayrıca “updatedNews” SocketIO tarafından tetiklenerek, güncellenen haber datası Real Time olarak clientların Browserın’da güncellenir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
export class NewsService { constructor(private httpClient: HttpClient, private socket: Socket) { } //SocketIO updatedNews = this.socket.fromEvent<News>('updatedNews'); baseUrl: string = "http://localhost:1923/news/"; public getNewsById(newId: number): Observable<News> { return this.httpClient.get<News>(this.baseUrl + newId) .pipe( retry(1), catchError(this.errorHandel) ) } updateUrl: string = "http://localhost:1923/updateNews"; public updateNews(data: News): Observable<any> { let httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json', }), observe: 'response' as 'body', } return this.httpClient.post<any>(this.updateUrl, JSON.stringify(data), httpOptions) .pipe( map(response => { return response.body; }), retry(1), catchError(this.errorHandel) ) } } |
SocketIO:
Yukarıdaki resimde görüldüğü gibi, Microservice’de Single Responsible prensibini bozmuştur. Hem clientların browser’ını SocketIO ile Real-Time yenilemekte, hem de kendisine tanımlı tüm Redis sunucularında, alınan son haber datasını güncellemektedir. Ama maalesef bazen gerçek hayatta, benzer işleri yapan yapıların, yönetim, test ve deployment işlerini kolaylaştırmak adına aynı yerde çalıştırılması gerekebilmektedir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
amqp.connect(rabbitUrl, opt, function (error0, connection) { if (error0) { throw error0; } connection.createChannel(function (error1, channel) { if (error1) { throw error1; } var queue = 'newsChannel'; channel.assertQueue(queue, { durable: false }); console.log(" [*] Waiting for messages in %s. To exit press CTRL+C", queue); channel.consume(queue, function (data) { updateNews = JSON.parse(data.content.toString()) console.log(" [x] Received News:", updateNews.Title + " - " + updateNews.CreatedDate); //socket.emit("updatedNews", updateNews); io.emit("updatedNews", updateNews); if (redisClient.clientACompany.connected) { var newsKey = 'news:' + updateNews.NewsId; //Redisde Haber Varsa Güncellenir..Değilse Kaydedilir. redisClient.clientACompany.set(newsKey, JSON.stringify(updateNews), function (err, res) { }); } if (redisClient.clientBCompany.connected) { var newsKey = 'news:' + updateNews.NewsId; //Redisde Haber Varsa Güncellenir..Değilse Kaydedilir. redisClient.clientBCompany.set(newsKey, JSON.stringify(updateNews), function (err, res) { }); } }, { noAck: true }); }); }); |
Covid19 nedeni ile online yapılan bu seminerde, performans için üretilen bir çözümün, müşteri bazlı özel bir istek ile nasıl yetersiz kaldığını ve çok daha büyük sorunlara yol açtığını gördük. Daha sonra oluşturduğumuz yeni bir çözüm ile mutlu sona ulaştık. “Ekstrem sorunlar, ekstrem çözümler gerektirir”. Normalde ilgili Redis Cache, ortak bir sunucudan kullanılabilse idi, o zaman bu kadar complex yapılara hiç ihtiyaç kalınılmıyacaktı. Kısacası gerçek hayatta esas zorluk, bazen insanların aldıkları kararlardan dolayı karşımıza çıkabilmektedir. Hastalık nedeni ile zorunlu yapılan bu online etkinliğin tek faydası, yer kısıtlaması olmadan, uzak lokasyonlardan 1500 kişinin aynı anda bir sunumu katılabilmesiydi. Bu güzel etkinliğin geçekleşmesini sağlayan tüm Devnot Ekibine, Kommunity’ye, Uğur Umutluoğlu’na ve Emir Karşıyakalı’ya teşekkürü bir borç bilirim.
Yeni bir seminerde görüşmek üzere hepinize hoşçakalın.
Seminerin Sunumu: Data Consistency in Microservices Architecture-Devnot 2020
Kaynak Kodlar: https://github.com/borakasmer/DevNot2020
Çok başarılı olmuş.
Teşekkürler..
Çok keyifli bir dev not sunumuydu.
Teşekkürler.
Ben teşekkür ederim..