সমস্যার পটভূমি:
9 মাসের 1 তারিখ 17:12 নাগাদ বাস্তবায়নকর্মীদের কাছ থেকে অভিযোগ আসে যে কিছু ডিভাইস স্বাভাবিকভাবে আপগ্রেড হচ্ছে না、কনসোল থেকে পাঠানো নির্দেশ পাচ্ছে না ইত্যাদি। সহকর্মীরা control প্রকল্পের(এরপর সংক্ষেপে control)লগ দেখে পান যে control ডিভাইসের পাঠানো শ্যাডো তথ্য পায়নি, তাই নির্দেশ পাঠায়নি। control প্রকল্প সরাসরি ডিভাইসের সঙ্গে সংযোগ রাখে এবং ডিভাইসের পাঠানো তথ্য অনুযায়ী আপগ্রেড、লগ পাঠানো ইত্যাদির নির্দেশ ও কনফিগারেশন তথ্য ডিভাইসে পাঠায়। IoT চালু হওয়ার আগে control ডিভাইসের বর্তমান তথ্য পেতে হার্টবিট রিপোর্টের ওপর নির্ভর করত;IoT চালু হওয়ার পর ডিভাইস শ্যাডো তথ্যের ওপর নির্ভর করে
দিয়ে ডিভাইসের বর্তমান তথ্য সংগ্রহ করা হয়, control ডিভাইসের শ্যাডো তথ্য সাবস্ক্রাইব করে, কিন্তু শ্যাডো তথ্য শ্যাডো পরিষেবা(সংক্ষেপে IoT)থেকে ফরওয়ার্ড করা হয়, এটি সরাসরি ডিভাইসের শ্যাডো রিপোর্টের সঙ্গে যুক্ত নয়, প্রবাহের বিস্তারিত জানতে নিচের দুটি ছবি দেখুন:
অ্যাপ্লিকেশন(control)যন্ত্রের অবস্থা সংগ্রহ করে

অ্যাপ্লিকেশন (control) থেকে ডিভাইসে নির্দেশ পাঠানো

control শ্যাডো বার্তা পাচ্ছে না জানার পর, আমি সঙ্গে সঙ্গে rabbitmq কনসোলে গিয়ে বার্তা আছে কি না দেখি, দুটি বিষয় নিশ্চিত করতে: 1.ডিভাইস বার্তা রিপোর্ট করেছে কি না 2.rabbitmq স্বাভাবিক আছে কি না, নিচের চিত্র 1、চিত্র 2 তখন তোলা rabbitmq কনসোলের দুটি ছবি,চিত্র 1 এটি স্পষ্টভাবে নিশ্চিত করা যায় যে ডিভাইস বার্তা পাঠাচ্ছে, কিন্তু অনেক বার্তা unacked(প্রাপ্তিস্বীকৃতি দেওয়া হয়নি,এতে বোঝা যায় বার্তা ইতিমধ্যে কনজিউমারের কাছে পৌঁছেছে, শুধু কনজিউমার ack দেয়নি; তাত্ত্বিকভাবে কিছুক্ষণ অপেক্ষা করলে স্বাভাবিক হতে পারবে)এর, ঠিক কোন কিউতে unacked বার্তা জমেছে তা দেখতে দেখুন চিত্র2,“spacebridgeiot-shadow” আমরা ডিভাইসের পাঠানো শ্যাডো তথ্য গ্রহণ করতে এটিই ব্যবহার করি। বার্তাগুলো কিউতে জমে আছে, তাই control-এ না পাঠানোও যুক্তিসংগত। কিছুক্ষণ পর্যবেক্ষণ করে দেখা গেল unacked-এর সংখ্যা 0 হয়ে গেছে, কিন্তু total-এর মোট সংখ্যায় তেমন পরিবর্তন হয়নি। দেখে মনে হলো unacked বার্তাগুলো আবার কিউতে ফিরে গিয়ে সরবরাহের অপেক্ষায় আছে। সত্যিই কয়েক মিনিট পর আবার প্রচুর unacked বার্তা দেখা গেল, আরও কয়েক মিনিট পর সেগুলো আবার কিউতে ফিরে গেল। control তখনও কোনো বার্তা পায়নি। এ সময় IoT-এর লগ দেখে দেখা গেল, আশ্চর্যজনকভাবে কোনো শ্যাডো বার্তাই আসেনি। rabbitmq-এর কনসোলে দেখলাম “spacebridgeiot-shadow” দেখা গেল এই কিউতে আর কোনো কনজিউমারই নেই, যেমন চিত্র3 যেমন দেখানো হয়েছে।এই সময় rabbitmq-এর লগ পরীক্ষা করলে সত্যিই ত্রুটির বার্তা পাওয়া যায়,যেমন চিত্র 4 এ দেখানো হয়েছে, rabbitmq নিজে থেকে সংযোগ বন্ধ করেছে।
চিত্র1:rabbitmq-এর সংক্ষিপ্ত বিবরণের চিত্র

চিত্র2:rabbitmq কিউয়ের পরিসংখ্যানচিত্র

চিত্র 3:spacebridgeiot-shadow সংক্ষিপ্ত পরিচিতি

চিত্র 4:rabbitmq-এর ত্রুটিবার্তা

অস্থায়ী সমাধান:
ততক্ষণে অনেক অভিযোগ এসে গিয়েছিল,তাই বেশ কঠোর একটি সমাধান নেওয়া হয়েছিল “জমে থাকা বার্তাগুলো মুছে ফেলা”,মুছে দেওয়ার পর সত্যিই সব স্বাভাবিক হয়ে গেল(মন্তব্য:প্রোডাকশনের সমস্যা যত দ্রুত সম্ভব সমাধান করতে হবে, লগ বিশ্লেষণ করে তারপর ধীরেসুস্থে সুশৃঙ্খলভাবে সমাধানের সময় নেই, দ্রুত কাজ করতেই হবে)。

স্থানীয় পরিবেশে সমস্যাটি পুনরুৎপাদন করে:
1.10.200.41.166 পরিবেশের rabbitmq-এর “mirrorTestQueue” কিউতে প্রচুর বার্তা জমা করুন(অন্তত দশ হাজারের পর্যায়ে)
2.mirrorTestQueue-এর কনজিউমার বন্ধ করুন,বার্তা জমা শেষ হলে আবার চালু করুন
3.বার্তা জমা সম্পূর্ণ হলে কনজিউমার আবার চালু করুন
আমাদের ধারণামতোই কয়েক সেকেন্ডের মধ্যে কয়েক হাজার বার্তা কনজিউমারের কাছে পাঠানো হয়। কয়েক মিনিট চলার পর rabbitmq নিজেই কনজিউমারের সঙ্গে সংযোগ বন্ধ করে দেয়; তখন কনসোলে কিউয়ের কনজিউমার আর দেখা যায় না। আমাদের কনজিউমারে স্বয়ংক্রিয় পুনরুদ্ধার চালু থাকায় কিছুক্ষণ পর আবার নিজে থেকেই সংযোগ হয়,
কিন্তু খুব তাড়াতাড়ি আবার সংযোগ বিচ্ছিন্ন হয়ে যায়, আমাদের প্রোডাকশনে দেখা সমস্যার সঙ্গে প্রায় একই, ঠিক কী কারণে এই সমস্যা হচ্ছিল? সত্যি বলতে, তখন কোনো দিশা পাচ্ছিলাম না, অনলাইনে অনেক খুঁজেও তেমন সন্তোষজনক উত্তর পেলাম না(তখন সমস্যার মূলে পৌঁছাতে পারিনি, খোঁজার জন্য ব্যবহৃত শব্দগুলো খুবই ব্যাপক ছিল),পরে
আমাদের ধারণা হয়েছিল TCP স্তরে হয়তো কোনো সমস্যা হয়েছে, তাই প্যাকেট ধরে কোনো সূত্র পাওয়া যায় কি না দেখার সিদ্ধান্ত নিলাম। সত্যিই ভাগ্য সহায় হলো, আর কথা না বাড়িয়ে সরাসরি ছবি দেখাই।


13:06:25.643428-এর আগ পর্যন্ত rabbitmq কনজিউমারকে ক্রমাগত বার্তা পাঠাচ্ছিল,13:06:25.643428 সময়ে এসে কনজিউমারের tcp উইন্ডো পুরো ভরে যেতে শুরু করে,এ অবস্থা প্রায় 30 সেকেন্ড চলার পর,rabbitmq নিজে থেকে সংযোগ বিচ্ছিন্ন করে(একটি rst প্যাকেট পাঠিয়ে),এরপর কনজিউমার আবার সংযুক্ত হয়,তারপর উইন্ডো আবার পুরো ভরে যায়,আরও প্রায় 30 সেকেন্ড চলার পর আবার সংযোগ বিচ্ছিন্ন হয়।


বেশ নিয়মমাফিকই মনে হচ্ছে, প্রতিবার 30 সেকেন্ড স্থায়ী হয়, মনে হচ্ছে এটি কনফিগার করা যায় এমন কোনো প্যারামিটার, মোটামুটি বলতে গেলে “tcp full window-এর কারণে সার্ভার সক্রিয়ভাবে সংযোগে rst পাঠিয়ে বিচ্ছিন্ন করছে, আর সেটাও নিয়মমাফিক”
এবার অনুসন্ধানের মূলশব্দ বদলে খুঁজে পেলাম উত্তর,rabbitmq-তে একটি প্যারামিটার আছে যার নাম tcp_listen_options.send_timeout লেখার সময়সীমা নিয়ন্ত্রণের একটি প্যারামিটার, লেখার সময়সীমা পেরিয়ে গেলে tcp-এর RST( সক্রিয় হবেhttps://github.com/rabbitmq/rabbitmq-java-client/issues/341),একটু বদলে দেখে নেওয়া যাক ফল কেমন হয়:
1. রাইট টাইমআউট 10s করুন
tcp_listen_options.send_timeout = 10000
2.প্যাকেট ধরে দেখুন এটি কাজ করছে কি না


উইন্ডো পূর্ণ হওয়া থেকে সংযোগ বন্ধ হওয়া পর্যন্ত প্রায় 10 সেকেন্ড লাগে।
ঘটনার পর্যালোচনা:
rabbitmq-এর কনজিউমারে prefetch সেট করা না থাকায় rabbitmq একসঙ্গে অতিরিক্ত বার্তা কনজিউমারের কাছে পাঠিয়েছিল,ফলে কনজিউমারের tcp উইন্ডো ভরে যায়,এরপর rabbitmq-এর tcp_listen_options.send_timeout সক্রিয় হয়,এই লেখার সময়সীমা অতিক্রম নির্দিষ্ট সীমায় পৌঁছালে rabbitmq কনজিউমারের tcp সংযোগ বিচ্ছিন্ন করে。
চূড়ান্ত সমাধান:
আগে বার্তা মুছে ফেলা ছিল বাধ্য হয়ে নেওয়া ব্যবস্থা,সমস্যার সমাধান হলেও তা ছিল অতিরিক্ত কঠোর,এটি সামলানোর জন্য আমাদের একটি মার্জিত সমাধান খুঁজতে হবে,যেহেতু গ্রাহকের কাছে অতিরিক্ত বার্তা পাঠানোর কারণেই tcp উইন্ডো ভরে যাচ্ছে,তাই আমাদের গ্রহণের হার নিয়ে কাজ করা উচিত,rabbitmq-এর সঙ্গে সংযোগ করার সময় তাকে বলে দিলেই হয় যেন আমাকে অতিরিক্ত বার্তা না পাঠায়。(পরের অংশের কথাগুলি নেওয়া হয়েছেhttps://blog.csdn.net/james_searcher/article/details/70308565)rabbitmq-তে prefetch নামে একটি বৈশিষ্ট্য আছে, যা একজন ভোক্তা সর্বাধিক কতটি unacked messages গ্রহণ করতে পারবে তা বোঝায়। বিষয়টি কীভাবে বুঝবেন?mq প্রতিটি consumer-এর জন্য একটি বাফার নির্ধারণ করে, যার আকার হলো prefetch। প্রতিবার একটি বার্তা এলে, MQ বার্তাটি বাফারে পাঠায়, তারপর ক্লায়েন্টের কাছে পাঠায়। একটি ack বার্তা পেলে(consumer baseack নির্দেশ পাঠায়), mq বাফারে একটি জায়গা খালি করে এবং নতুন বার্তা যোগ করে। তবে তখন বাফার পূর্ণ থাকলে, MQ অবরুদ্ধ অবস্থায় প্রবেশ করে। আরও নির্দিষ্ট করে বললে, ধরা যাক prefetch-এর মান 10 এবং মোট দুটি consumer আছে। অর্থাৎ প্রতিটি consumer প্রতিবার queue থেকে আগাম 10টি বার্তা নিয়ে স্থানীয় ক্যাশে প্রক্রিয়াকরণের অপেক্ষায় রাখবে। একই সঙ্গে ওই channel-এর unacked সংখ্যা 20 হয়ে যাবে। Rabbit-এর সরবরাহের ক্রম হলো, প্রথমে consumer1-কে পুরো 10টি message দেওয়া, তারপর consumer2-কে 10টি message দেওয়া। এ সময় নতুন message সরবরাহ করতে হলে, আগে দেখা হবে channel-এর unacked সংখ্যা 20-এর সমান কি না; সমান হলে বার্তাটি consumer-এর কাছে পাঠানো হবে না, message queue-তেই থাকবে। পরে কোনো consumer একটি বার্তার ack দিলে, unacked তখন 19 হয়; Rabbit তখন দেখে কোন consumer-এর unacked সংখ্যা 10-এর কম, এবং সেই consumer-কে বার্তা দেয়।
কোডে নির্দিষ্টভাবে বললে তা হলো

এই মান কীভাবে নির্ধারণ করবেন, rabbitmq-এর আনুষ্ঠানিক একটি নিবন্ধে তা খুব ভালোভাবে বলা হয়েছে, তাই আর বিস্তারিত বলছি না, আমাদের সিস্টেমে বর্তমানে এটি 20 নির্ধারণ করা আছে।
https://www.rabbitmq.com/blog/2012/05/11/some-queuing-theory-throughput-latency-and-bandwidth/
সমাপনী কথা:
rabbitmq ব্যবহারে আমরা এখনও শিক্ষানবিশ পর্যায়ে আছি, তাই ব্যবহারের আগে অবশ্যই rabbitmq-এর API ভালো করে পড়তে হবে, প্রচলিত API-গুলির ব্যবহার জানতে হবে এবং স্থানীয় পরীক্ষামূলক পরিবেশে বেশি করে পরীক্ষা করতে হবে।