12 KiB
سيرو في حاويات — دليل التشغيل والفحص
الهدف: نسخة سيرو كاملة على سيرفر واحد داخل 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.
6) Mawasalati (transit) — حاوية php-fpm معزولة
- الخدمة
php_transitمنفصلة تماماً عنphpالرئيسي: صورة مختلفة (php/Dockerfile.fpm.transit، بدون composer/vendor)، حوض fpm أصغر (php/www-pool.transit.conf، 16 عامل)، وحدّ ذاكرة 512m خاص بها. - الملفات المرَكَّبة داخلها فقط:
backend/core(read-only — JWT/Database/RateLimiter المشتركة) وbackend/functions.php(read-only) وbackend/transit(rw). لا صلة بـbackendالرئيسي كاملاً ولاpayment_server/v2ولاloction_server. - التوجيه في nginx: أي طلب يطابق
^/backend/transit/.*\.php$يذهب إلىphp_transit:9000بدلphp:9000— هذا location يجب أن يبقى قبل الـlocation ~ \.php$العام فيnginx/default.conf. - المشترك الوحيد فعلياً مع النظام الأساسي: مصادقة JWT للسائق/الراكب (
backend/core)، ونفسmysql/redis(قاعدةtransitمنفصلة أصلاً عبرDatabase::get('transit')). - انهيار أو ازدحام
phpالرئيسي لا يوقف Mawasalati، والعكس صحيح. - cron jobs (
cron_sync_members.php,cron_cleanup.php,cron_approaching_alerts.php): تبقى تُستدعى من crontab المضيف كما هي حالياً (لا تغيير) — الملفات ما تزال متاحة من حاويةphpالرئيسية أيضاً لأن../backendكاملة ما تزال مركّبة فيها.
7) طلبات الطعام (food) — نفس نمط العزل
-
الخدمتان
php_foodوsocket_foodمنفصلتان تماماً عنphpالرئيسي وعنphp_transit، بنفس منطق §6: صورةphp/Dockerfile.fpm.transit(fpm خفيفة بلا composer، مُعاد استخدامها لأن الطعام أيضاً لا يحتاج vendor)، حوضphp/food-pool.conf(24 عاملاً)، حد ذاكرة 768m لـphp_foodو512m لـsocket_food. -
الملفات المرَكَّبة داخل
php_foodفقط:backend/core(ro)،backend/functions.php(ro)،backend/food(rw). لا صلة بـbackendالرئيسي ولاpayment_server/v2ولاloction_server. -
التوجيه في nginx:
^/backend/food/.*\.php$يذهب إلىphp_food:9000، ويجب أن يبقى قبل الـ location العام. -
socket_food— WS بورت 4040 (يتطلب TLS من nginx المضيف مثل بقية السوكيتات، انظر §… أعلاه) + HTTP داخلي 4041 يستقبل نداءات منphp_foodبمفتاحX-Internal-Key(نفسINTERNAL_SOCKET_KEY) لدفع تحديثات حالة الطلب لحظياً — نفس نمطbroadcast_bus_locationفي مواصلاتي، وليس Redis pub/sub. -
قاعدة
siro_foodمنفصلة تماماً (Database::get('food')، ممنوعDatabase::get('main')داخلbackend/food/)، والمحفظة/الدفع/JWT/FCM مشتركة مع النظام الرئيسي — نفس مبدأ transit تماماً، موثّق بالتفصيل في docs/10_food_orders/FOOD_ORDERS_PLAN_AR.md. -
FOOD_ENABLED=falseفي.envيُرجع 503 من كل بواباتbackend/food/*فوراً بلا نشر جديد — مفتاح التراجع الأول. -
الحالة الحالية: المراحل صفر–الرابعة مبنية على فرع
feature/food-delivery-module(غير مُلتزم بها على main، وغير مُختبرة على بيئة حقيقية):- صفر — التأسيس: حاويتان، nginx،
Database::get('food')،schema_food.sql، بوابات الزبون/المطعم/السائق/الإدارة. - الأولى — الكتالوج: تصفح/بحث/تفاصيل مطعم، اعتماد إداري للمطاعم، دخول لوحة المطعم، تفعيل/إيقاف صنف.
- الثانية — الطلب:
cart/quote.php(تسعير من الخادم فقط، موقّع بـ HMAC صالح 10 دقائق)،order/create.php(idempotent عبرclient_order_uuid، آلة الحالةfood_transition_status())،order/status.php|cancel.php|rate.php|history.php،merchant_ops/accept.php|reject.php|preparing.php|ready.php. - الثالثة — التوصيل:
courier/toggle_availability.php|offer_respond.php|picked_up.php|delivered.php|active.php، قفل ذرّيSET NX EX 20يمنع سباق القبول،cron_order_timeouts.phpلإعادة العرض بعد المهلة وإلغاء الطلبات المعلّقة. - الرابعة — المال: خصم/استرجاع فوري من المحفظة عبر نفس عقد
initiate_prime.phpS2S،admin/payouts.phpلتوليد تقرير تسوية المطاعم.
⚠️ ثلاث نقاط تحتاج تأكيداً حقيقياً قبل أي تشغيل بمال فعلي — موثّقة كتعليقات صريحة في الكود نفسه (
food/functions.php)، وليست تفاصيل تنفيذ ثانوية:- لا يوجد حجز حقيقي (hold/capture): سيرفر المحفظة الخارجي لا يعرض API حجز مسبق — التنفيذ الحالي هو خصم فوري عند إنشاء الطلب + استرجاع كامل عند الرفض/الإلغاء. إن أُضيف hold حقيقي لاحقاً،
foodWalletMove()هو أول مكان يُعدَّل. - عامل تحويل العملة غير مؤكَّد:
FOOD_CURRENCY_DIVISOR(افتراضي 1000، أي fils) يحوّل "أصغر وحدة نقدية" فيsiro_foodإلى المبلغ العشري الذي يتوقعه سيرفر المحفظة — يجب تأكيده مع فريق المحفظة. - لا تحويل آلي لأرباح السائقين: العقد S2S المؤكد فقط لتحويلات سائق↔سائق (
driverWallet/transfer.php)، لا لإيداع أرباح من المنصة مباشرة إلى محفظة سائق. أرباح التوصيل (delivery_fee) وديون التحصيل النقدي تُسجَّل محاسبياً فقط فيfood_order_payments(نوعcourier_payout/cash_settlement) بحالةpending— لا صرف فعلي بعد. - أسطول التوصيل معزول عمداً عن
loction_server: لا تعديل علىdriver_socket.phpالحي. السائق يُفعّل "وضع التوصيل" فيُضاف إلى SET مستقلةfood:couriers:opted_inنتقاطع معها معgeo:drivers:available(قراءة فقط).
لم يُبنَ بعد: المرحلة الخامسة (التقسية والإطلاق — مراجعة أمنية مخصصة، اختبار ضغط، إطلاق تدريجي)، وربط الصرف الفعلي بمجرد تأكيد النقاط الثلاث أعلاه.
- صفر — التأسيس: حاويتان، nginx،