سوكيت الراكب (سبب عدم ظهور معلومات السائق عند القبول): - تطبيق الراكب كان يرسل id فقط بلا jwt، و passenger_socket.php يرفض أي اتصال بلا jwt ⇒ الراكب لا ينضم لغرفته أبداً ولا يستلم ride_status_change ولا driver_location_update. تظهر حالة القبول عبر الـ polling فقط بينما driver_info يصل بالسوكيت وحده. (سوكيت السائق يعتبر الـ jwt اختيارياً، ومن هنا جاء التباين بين التطبيقين.) - cancelled_by_driver كان يسقط من switch حالات الراكب فيبقى معلّقاً بعد إلغاء السائق. - حماية socket (late) من القراءة قبل التهيئة عند الانسحاب بلا jwt. الإشعارات والرسائل (سبب "مرات توصل ومرات لا"): - جدول tokens يخزّن توكن الراكب مشفّراً، و getRideWaiting.php كان يرجعه بلا فك تشفير ⇒ من يقبل من قائمة السوق يحمل blob مشفّراً يستخدمه كـ FCM target فيرفضه FCM بـ 400: لا إشعار قبول ولا رسائل. ومن يقبل من الـ dispatch/FCM يحمل نصاً صريحاً فتعمل. الفرق كان في طريقة القبول. - acceptRide.php يحلّ التوكن من القاعدة دائماً ولا يثق بالعميل (أصحّ أمنياً). - market_new_ride كان لا يحمل passengerId ولا الإحداثيات فتصل "null"؛ أُضيفت بلا أي PII لأن الحمولة تُبَثّ لكل سائق قريب لا للفائز فقط. - send_fcm.php: مهلة على OAuth (كان يعلّق حتى مهلة PHP فتُسقط الرسالة بصمت)، توحيد ding→default لأندرويد، وحقن title/body/tone في data مطابقةً لـ FcmService. - تطبيق السائق يقرأ title/body من data أولاً مثل الراكب، ولا يعرض فقاعة فارغة للرسائل الصامتة. السوكيت والإعدادات: - forwardLocationToPassengerSocket كان يقرأ lat/lng والحمولة فيها latitude/longitude ⇒ المسافة تخرج ضخمة والـ throttle معطّل تماماً فيُعاد التوجيه مع كل نبضة GPS. - notifyPassengerOnRideServer كان يرجع null بصمت مطلق عند حجب العنوان. - العنوان الافتراضي لسيرفر الموقع كان nginx/loction_server/driver_socket.php وهو ديمون Workerman لا يُخدَم عبر nginx ⇒ صار socket_driver:2021. (LOCATION_API_URL بقي على nginx لأن api_get_nearby.php سكربت عادي.) - ride_server/passenger_socket.php (النسخة التي يشغّلها Docker) كانت ناقصة كل كود مواصلاتي الموجود في passenger_server/ ⇒ نُقل مع REDIS_HOST. - .env.example: ALLOWED_SOCKET_URLS يغطّي أسماء حاويات Docker، وإضافة PASSENGER_SOCKET_INTERNAL_URL. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
سيرو في حاويات — دليل التشغيل والفحص
الهدف: نسخة سيرو كاملة على سيرفر واحد داخل compose واحدة، والتطبيق لا يتغير فيه شيء سوى الـ base URLs (تُبنى مرة واحدة بـ
.envالجديد — envied وقت الترجمة). القرارات والأسباب فيTripz/docs/30-siro-port-plan.md§5.
0) قبل أي شيء — على السيرفر
df -h / # ⚠️ الصندوق المشترك مزمنياً ~90% ممتلئ — الصور والقواعد تحتاج ~4-6GB
docker image prune -f # نظّف أولاً، وإن لم تكفِ المساحة فالفحص على صندوق آخر
1) الإعداد (مرة واحدة)
cd /path/to/Siro/docker
cp .env.example .env && nano .env # املأ الأسرار من قيم اللايف — خاصة JWT_SECRET وكلمات MySQL
# ⚠️ طابق أسماء القواعد في mysql/init/00-databases.sql مع ما يتوقعه الكود
docker compose build
# تبعيات composer للخدمات التي لا تحمل vendor/ داخلها:
docker compose run --rm socket_driver composer install
docker compose run --rm socket_passenger composer install
docker compose run --rm --workdir /var/www/backend php composer install
2) التشغيل والاستيراد
docker compose up -d
# استيراد السكيمات (أول مرة فقط):
docker compose exec -T mysql mysql -uroot -p"$MYSQL_ROOT_PASSWORD" siro_primary < ../backend/schema_primary.sql
docker compose exec -T mysql mysql -uroot -p"$MYSQL_ROOT_PASSWORD" siro_ride < ../backend/schema_ride.sql
docker compose exec -T mysql mysql -uroot -p"$MYSQL_ROOT_PASSWORD" siro_tracking < ../backend/schema_tracking.sql
docker compose exec -T mysql mysql -uroot -p"$MYSQL_ROOT_PASSWORD" siro_wallet < ../payment_server/WalletDB.sql
docker compose exec -T mysql mysql -uroot -p"$MYSQL_ROOT_PASSWORD" siro_location < ../loction_server/locationDB.sql
3) التحقق
docker compose ps # الست خدمات up
curl -s http://localhost:8080/backend/index.html # nginx→ملفات
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8080/backend/connect.php # يرد (401/400 طبيعي بلا JWT)
curl -s http://localhost:8080/fpm-status # حالة حوض fpm
docker compose logs socket_driver | tail # Workerman أقلع على 2020/2021
اتصال التطبيق للتجربة: http://IP:8080/backend + سوكيت http://IP:2020 — فقط بدّل
الـ base URLs في .env تبع التطبيق وأعد البناء. APP_DOMAIN فارغ = حارس
REGION_MISMATCH متعطل أثناء التجربة.
4) اختبار الضغط (الهدف: 10-20 ألف رحلة/ساعة)
cd ../stress_test && npm install
# دخان أولاً (الأداة القديمة، دفعة صغيرة):
node load_test.js --trips=10 -b http://localhost:8080/backend -s http://localhost:2020
# ثم المعدل المستمر:
node rate_test.js --rate 10000 --duration 10 -b http://localhost:8080/backend -s http://localhost:2020
node rate_test.js --rate 20000 --duration 10 -b http://localhost:8080/backend -s http://localhost:2020
node rate_test.js --rate 30000 --duration 5 ... # حد الانهيار — أين تبدأ p95 بالقفز؟
أثناء التشغيل راقب من طرفية ثانية:
docker stats # cpu/ذاكرة كل حاوية
docker compose exec mysql sh -c 'tail -f /var/lib/mysql/slow.log' # استعلامات >200ms
watch -n2 'curl -s http://localhost:8080/fpm-status' # طابور fpm (listen queue)
قواعد قراءة النتيجة بصدق
- سيناريو الاختبار أخف من الرحلة الحقيقية بـ 3-5× (4 نداءات API + 6 نبضات موقع؛ الحقيقية فيها تسعير وبحث وقرب ودردشة). لتبنّي رقم "10k رحلة حقيقية بالذروة" يجب أن يعبر الاختبار 30k بنجاح >99% و p95<500ms.
- الصندوق مشغول ~40% بغيرنا — افحص خارج الذروة وبجولات قصيرة (5-10 د)، وأعد الجولة مرتين للتأكد من ثبات الرقم.
- مولّد الحمل على نفس الصندوق يسرق ~5% CPU عند هذه المعدلات — مقبول؛ الأدق
تشغيله من جهاز آخر نحو
http://IP:8080. - المُلتزم به للعملاء = الرقم الذي عبر الاختبار، لا أكثر.
5) ملاحظات أداء محسومة (لماذا الحاويات لا تبطئنا)
- الحاوية على لينكس = عمليات عادية بنفس النواة (namespaces/cgroups) — لا محاكاة ولا آلة افتراضية. حمل CPU/ذاكرة ≈ صفر.
- المكان الوحيد الذي يُخسر فيه أداء فعلاً: كتابة قاعدة البيانات فوق overlayfs — محلول بـ volume مسمّى (
mysql-data). - أداء PHP الحقيقي يصنعه opcache + مقاس حوض fpm + فهارس MySQL — كلها مضبوطة في
php/وليست متعلقة بالدوكر أصلاً. - التقسيم إلى 6 حاويات لا يكلف أداءً (نفس العمليات) ويعطي: إعادة تشغيل خدمة وحدها، حدود ذاكرة لكل خدمة، لوغات منفصلة، واستنساخ عميل =
clone + .env + up.