Files
..
2026-07-21 17:31:31 +03:00
2026-07-21 17:09:08 +03:00
2026-08-02 17:52:28 +03:00
2026-08-02 17:52:28 +03:00
2026-08-02 17:52:28 +03:00
2026-08-02 17:52:28 +03:00
2026-08-02 17:52:28 +03:00

سيرو في حاويات — دليل التشغيل والفحص

الهدف: نسخة سيرو كاملة على سيرفر واحد داخل 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)

قواعد قراءة النتيجة بصدق

  1. سيناريو الاختبار أخف من الرحلة الحقيقية بـ 3-5× (4 نداءات API + 6 نبضات موقع؛ الحقيقية فيها تسعير وبحث وقرب ودردشة). لتبنّي رقم "10k رحلة حقيقية بالذروة" يجب أن يعبر الاختبار 30k بنجاح >99% و p95<500ms.
  2. الصندوق مشغول ~40% بغيرنا — افحص خارج الذروة وبجولات قصيرة (5-10 د)، وأعد الجولة مرتين للتأكد من ثبات الرقم.
  3. مولّد الحمل على نفس الصندوق يسرق ~5% CPU عند هذه المعدلات — مقبول؛ الأدق تشغيله من جهاز آخر نحو http://IP:8080.
  4. المُلتزم به للعملاء = الرقم الذي عبر الاختبار، لا أكثر.

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.php S2S، admin/payouts.php لتوليد تقرير تسوية المطاعم.

    ⚠️ ثلاث نقاط تحتاج تأكيداً حقيقياً قبل أي تشغيل بمال فعلي — موثّقة كتعليقات صريحة في الكود نفسه (food/functions.php)، وليست تفاصيل تنفيذ ثانوية:

    1. لا يوجد حجز حقيقي (hold/capture): سيرفر المحفظة الخارجي لا يعرض API حجز مسبق — التنفيذ الحالي هو خصم فوري عند إنشاء الطلب + استرجاع كامل عند الرفض/الإلغاء. إن أُضيف hold حقيقي لاحقاً، foodWalletMove() هو أول مكان يُعدَّل.
    2. عامل تحويل العملة غير مؤكَّد: FOOD_CURRENCY_DIVISOR (افتراضي 1000، أي fils) يحوّل "أصغر وحدة نقدية" في siro_food إلى المبلغ العشري الذي يتوقعه سيرفر المحفظة — يجب تأكيده مع فريق المحفظة.
    3. لا تحويل آلي لأرباح السائقين: العقد S2S المؤكد فقط لتحويلات سائق↔سائق (driverWallet/transfer.php)، لا لإيداع أرباح من المنصة مباشرة إلى محفظة سائق. أرباح التوصيل (delivery_fee) وديون التحصيل النقدي تُسجَّل محاسبياً فقط في food_order_payments (نوع courier_payout / cash_settlement) بحالة pending — لا صرف فعلي بعد.
    4. أسطول التوصيل معزول عمداً عن loction_server: لا تعديل على driver_socket.php الحي. السائق يُفعّل "وضع التوصيل" فيُضاف إلى SET مستقلة food:couriers:opted_in نتقاطع معها مع geo:drivers:available (قراءة فقط).

    لم يُبنَ بعد: المرحلة الخامسة (التقسية والإطلاق — مراجعة أمنية مخصصة، اختبار ضغط، إطلاق تدريجي)، وربط الصرف الفعلي بمجرد تأكيد النقاط الثلاث أعلاه.