404 Not Found

404 Not Found


nginx

تحسين الأداء

تحسين الأداء هو عمل هندسي منظومي—يتضمن ثلاثة أبعاد: JVM وقاعدة البيانات وتجمع الاتصالات. فقط من خلال تحديد الاختناقات بدقة يمكننا معالجة الأسباب الجذرية.

1. ما ستتعلمه


2. قصة حقيقية لمهندس أداء

(1) نقطة الألم: واجهة برمجة التطبيقات بطيئة كالحلزون

أبلغ Charlie أن زمن استجابة P99 لواجهة قائمة طلبات OrderFlow كان 500 مللي ثانية، مما أدى إلى تجربة مستخدم سيئة للغاية. كشف تحليل Alice ما يلي: 1) توقفات Full GC متكررة في JVM تستمر 200 مللي ثانية؛ 2) مشكلة N+1 تحدث عندما يستعلم JPA عن قائمة الطلبات (100 طلب = 101 استعلام SQL)؛ 3) حجم تجمع اتصالات HikariCP غير كافٍ، مما يتسبب في انتهاء مهلة الطلبات أثناء انتظار الاتصال.

(2) حلول تحسين المنظومة

تحسين طبقة تلو الأخرى لمعالجة الاختناقات بدقة:

المستوى الاختناق حل التحسين
JVM توقف Full GC G1GC + ضبط حجم الكومة
تجمع الاتصالات اتصالات غير كافية تحسين معاملات HikariCP
ORM استعلامات N+1 JOIN FETCH / EntityGraph
قاعدة البيانات مسح كامل للجدول إضافة فهرس

(3) الإيرادات

بعد أن قامت Alice بتحسين كل مكون على حدة، انخفض زمن استجابة P99 من 500 مللي ثانية إلى 50 مللي ثانية، وزادت الإنتاجية عشرة أضعاف.


3. ضبط معاملات JVM

(1) مقارنة خوارزميات GC

خوارزمية GC أقصى وقت توقف السيناريوهات المناسبة معاملات JVM
G1GC 10-200 مللي ثانية عام (افتراضي JDK 17) -XX:+UseG1GC
ZGC < 1 مللي ثانية زمن استجابة منخفض (JDK 17+) -XX:+UseZGC
SerialGC طويل كومة صغيرة (< 200MB) -XX:+UseSerialGC
ParallelGC متوسط الأولوية للإنتاجية -XX:+UseParallelGC
100%
graph LR
    A["ضبط JVM"] --> B["حجم الكومة<br/>-Xms = -Xmx"]
    A --> C["خوارزمية GC<br/>G1 / ZGC"]
    A --> D["سجل GC<br/>-Xlog:gc*"]
    B --> B1["حاوية: MaxRAMPercentage"]
    C --> C1["الافتراضي: G1GC"]
    C --> C2["زمن استجابة منخفض: ZGC"]

(1) ▶ مثال: تهيئة JVM في بيئة Docker

DOCKERFILE
ENTRYPOINT ["java", \
  "-XX:+UseG1GC", \
  "-XX:MaxRAMPercentage=75.0", \
  "-XX:InitialRAMPercentage=50.0", \
  "-XX:+UseStringDeduplication", \
  "-Xlog:gc*:file=/app/logs/gc.log:time,uptime,level,tags", \
  "-jar", "app.jar"]

الناتج:

TEXT
// التنفيذ ناجح
المعامل المعنى القيمة الموصى بها
MaxRAMPercentage الحد الأقصى للكومة كنسبة مئوية من ذاكرة الحاوية 75.0
InitialRAMPercentage النسبة المئوية من ذاكرة الحاوية التي تشغلها الكومة الأولية 50.0
+UseG1GC استخدام جامع القمامة G1 ممكّن افتراضيًا
+UseStringDeduplication إزالة التكرار من السلاسل لتوفير الذاكرة موصى به
MaxGCPauseMillis وقت توقف GC المستهدف 200 (G1) / لا يوجد (ZGC)

(2) ▶ مثال: تهيئة ZGC منخفضة زمن الاستجابة

BASH
java -XX:+UseZGC \
     -XX:MaxRAMPercentage=75.0 \
     -XX:+ZGenerational \
     -Xlog:gc*:file=gc.log \
     -jar app.jar

الناتج:

TEXT
# تم تنفيذ الأمر بنجاح
📌 نقطة رئيسية: ZGC هي ميزة تجريبية في JDK 17 (متاحة رسميًا في JDK 21). بوقت توقف أقل من 1 مللي ثانية، فهي مناسبة للسيناريوهات شديدة الحساسية لزمن الاستجابة.


4. تحسين تجمع اتصالات HikariCP

(1) معاملات تجمع الاتصالات

المعامل القيمة الافتراضية الوصف القيمة الموصى بها
maximumPoolSize 10 الحد الأقصى لعدد الاتصالات عدد أنوية المعالج x 2 + عدد الأقراص
minimumIdle = maxPool الحد الأدنى للاتصالات الخاملة = maxPoolSize
connectionTimeout 30,000 مللي ثانية مهلة الاتصال 3,000 مللي ثانية
idleTimeout 600,000 مللي ثانية مهلة الاتصال الخامل 600,000 مللي ثانية
maxLifetime 1,800,000 مللي ثانية الحد الأقصى لعمر الاتصال 1,800,000 مللي ثانية
leakDetectionThreshold 0 (معطل) اكتشاف تسرب الاتصالات 60,000 مللي ثانية

(1) ▶ مثال: تهيئة HikariCP للإنتاج

YAML
spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 20
      connection-timeout: 3000
      idle-timeout: 600000
      max-lifetime: 1800000
      leak-detection-threshold: 60000
      pool-name: OrderFlowHikariCP

الناتج:

TEXT
أصبحت التهيئة سارية المفعول.

(2) ▶ مثال: اكتشاف تسرب الاتصالات

YAML
# تفعيل اكتشاف التسرب (تحذير في السجل إذا احتُجز الاتصال لأكثر من 60 ثانية)
spring:
  datasource:
    hikari:
      leak-detection-threshold: 60000
💻 الناتج (عند وجود تسرب):

TEXT
WARN  - Connection leak detection triggered for {conn-12345}
  Stack trace of the call site that acquired the connection:
    at com.orderflow.service.OrderService.getOrder(OrderService.java:45)
    ...

5. تحسين استعلامات JPA

(1) مشكلة N+1

(1) ▶ مثال: عرض مشكلة N+1

JAVA
// سيئ: مشكلة N+1
// 1 استعلام SQL لجلب 100 طلب + 100 استعلام SQL لجلب عناصر كل طلب = 101 استعلام SQL!
List<Order> orders = orderRepository.findAll();
for (Order order : orders) {
    order.getItems().size();  // يحفز التحميل الكسول لكل طلب
}

الناتج:

TEXT
// التنفيذ ناجح

(2) ▶ مثال: استخدام JOIN FETCH لحل مشكلة N+1

JAVA
// جيد: استعلام واحد باستخدام JOIN FETCH
@Query("SELECT DISTINCT o FROM Order o JOIN FETCH o.items i JOIN FETCH i.product WHERE o.status = :status")
List<Order> findByStatusWithItems(@Param("status") String status);

الناتج:

TEXT
// التنفيذ ناجح

(3) ▶ مثال: التحميل التعريفي باستخدام @EntityGraph

JAVA
@Entity
@NamedEntityGraph(
    name = "Order.withItemsAndProduct",
    attributeNodes = {
        @NamedAttributeNode("items"),
        @NamedAttributeNode(value = "items", subgraph = "item-product")
    },
    subgraphs = {
        @NamedSubgraph(name = "item-product", attributeNodes = @NamedAttributeNode("product"))
    }
)
public class Order { /* ... */ }

// الاستخدام في المستودع
@EntityGraph(value = "Order.withItemsAndProduct", type = EntityGraphType.LOAD)
List<Order> findByStatus(String status);

الناتج:

TEXT
// التنفيذ ناجح
الحل عدد عبارات SQL السيناريوهات المطبقة قابلية الصيانة
التحميل الكسول الافتراضي N+1 استعلام كائن واحد عالية
JOIN FETCH 1 استعلام محدد متوسطة
@EntityGraph 1 خطة تحميل قابلة لإعادة الاستخدام عالية
@BatchSize N/batchSize تحميل دفعي عالية

6. تحسين فهرسة قاعدة البيانات

(1) استراتيجية الفهرسة

(1) ▶ مثال: إضافة فهرس قاعدة بيانات

SQL
-- فهرس لاستعلامات حالة الطلب
CREATE INDEX idx_order_status ON orders(status);

-- فهرس مركب لنمط الاستعلام الشائع
CREATE INDEX idx_order_status_created ON orders(status, created_at DESC);

-- فهرس للبحث عن المنتجات
CREATE INDEX idx_product_name ON products(name);

-- فهرس للبحث عن منتج عناصر الطلب
CREATE INDEX idx_order_item_product ON order_items(product_id);

الناتج:

TEXT
CREATE TABLE
نوع الفهرس حالة الاستخدام مثال
فهرس عمود واحد استعلام بشرط واحد WHERE status = 'PENDING'
فهرس مركب استعلام متعدد الشروط WHERE status = ? AND created_at > ?
فهرس فريد قيد فريد UNIQUE(sku)
فهرس تغطي الفهرس يحتوي على جميع أعمدة الاستعلام SELECT status FROM orders WHERE id = ?

(2) ▶ مثال: تحليل الاستعلامات البطيئة

SQL
-- تهيئة سجل الاستعلامات البطيئة في MySQL
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;  -- تسجيل الاستعلامات التي تتجاوز ثانية واحدة
SET GLOBAL log_queries_not_using_indexes = ON;

-- تحليل خطة تنفيذ الاستعلام
EXPLAIN SELECT o.*, i.*
FROM orders o
JOIN order_items i ON o.id = i.order_id
WHERE o.status = 'PENDING'
  AND o.created_at > '2024-01-01'
ORDER BY o.created_at DESC
LIMIT 20;

الناتج:

TEXT
CREATE TABLE
حقل EXPLAIN المعنى النقاط الرئيسية
type نوع الوصول ALL (مسح كامل للجدول) يحتاج إلى تحسين
key الفهرس المستخدم NULL يشير إلى عدم استخدام فهرس
rows عدد الصفوف الممسوحة كلما قل كان أفضل
Extra معلومات إضافية Using filesort يحتاج إلى تحسين

7. مثال شامل: انخفاض P99 لـ OrderFlow من 500 مللي ثانية إلى 50 مللي ثانية

YAML
# application-prod.yml (محسّن)
spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 20
      connection-timeout: 3000
      leak-detection-threshold: 60000
  jpa:
    hibernate:
      ddl-auto: validate
    show-sql: false
    properties:
      hibernate:
        default_batch_fetch_size: 100
        jdbc:
          batch_size: 50
        order_inserts: true
        order_updates: true
BASH
# معاملات JVM (Docker ENTRYPOINT)
java -XX:+UseG1GC \
     -XX:MaxRAMPercentage=75.0 \
     -XX:InitialRAMPercentage=50.0 \
     -XX:+UseStringDeduplication \
     -XX:MaxGCPauseMillis=100 \
     -Xlog:gc*:file=/app/logs/gc.log:time,uptime,level,tags \
     -jar app.jar
JAVA
// OrderRepository محسّن مع EntityGraph والتحميل الدفعي
public interface OrderRepository extends JpaRepository<Order, Long> {

    @EntityGraph(value = "Order.withItemsAndProduct", type = EntityGraphType.LOAD)
    @Query("SELECT o FROM Order o WHERE o.status = :status ORDER BY o.createdAt DESC")
    Page<Order> findByStatusPaged(@Param("status") String status, Pageable pageable);

    @EntityGraph(value = "Order.withItemsAndProduct", type = EntityGraphType.LOAD)
    Optional<Order> findById(Long id);
}

// تهيئة حجم الدفعة في Hibernate تزيل مشكلة N+1 للمجموعات
// application.yml: hibernate.default_batch_fetch_size=100
قبل التحسين بعد التحسين عامل التحسين
P99: 500 مللي ثانية P99: 50 مللي ثانية 10x
SQL/طلب: 101 SQL/طلب: 1 100x
توقف GC: 200 مللي ثانية توقف GC: 50 مللي ثانية 4x
انتظار الاتصال: 100 مللي ثانية انتظار الاتصال: 2 مللي ثانية 50x

❓ أسئلة شائعة

س أيهما أختار، G1GC أم ZGC؟
ج G1GC هو الافتراضي في JDK 17 ومناسب للغالبية العظمى من السيناريوهات، بزمن توقف 10-200 مللي ثانية. ZGC بزمن توقف أقل من 1 مللي ثانية ومناسب لسيناريوهات زمن الاستجابة المنخفض مثل المعاملات المالية، لكن إنتاجيته أقل قليلاً. ابدأ بـ G1GC، وانتقل إلى ZGC فقط إذا واجهت مشاكل في زمن الاستجابة.
س هل التجمع الأكبر للاتصالات أفضل دائمًا؟
ج لا. التجمع الأكبر جدًا يؤدي إلى: 1) زيادة حمل قاعدة البيانات؛ 2) عبء تبديل السياق؛ 3) زيادة استخدام الذاكرة. الصيغة الموصى بها: اتصالات = (أنوية المعالج x 2) + عدد الأقراص الفعّالة.
س ما الفرق بين @EntityGraph وJOIN FETCH؟
ج @EntityGraph تعريفي ويمكن تعريفه على Entity أو Repository لإعادة الاستخدام؛ JOIN FETCH إلزامي ويُكتب داخل كل @Query. EntityGraph أسهل في الصيانة في السيناريوهات المعقدة.
س ماذا يفعل batch_size في Hibernate؟
ج عند تعيين hibernate.jdbc.batch_size=50، يتم تنفيذ عبارات INSERT/UPDATE على دفعات (50 سجل لكل دفعة)، مما يقلل عدد الرحلات إلى قاعدة البيانات. يعمل بشكل أفضل عند استخدامه مع order_inserts=true.
س كيف أحدد اختناقات الأداء؟
ج 1) استخدم Actuator /metrics للتحقق من مدة طلبات HTTP؛ 2) حلل سجلات GC لتحديد توقفات GC؛ 3) مقاييس HikariCP للتحقق من حالة تجمع الاتصالات؛ 4) سجل الاستعلامات البطيئة في MySQL لتحديد الاستعلامات البطيئة؛ 5) أدوات APM (SkyWalking/Zipkin) للتتبع الشامل.
س كيف تجري اختبارات الأداء؟
ج استخدم JMeter أو Gatling أو k6. زِد الحمل تدريجيًا (ramp-up) وراقب نقاط الانعطاف في زمن الاستجابة ومعدل الخطأ. يجب أن تغطي سيناريوهات الاختبار: نقاط نهاية واجهة برمجة تطبيقات فردية، وسيناريوهات مختلطة، وسيناريوهات الذروة.

📖 ملخص


📝 تمارين

  1. تمرين أساسي (الصعوبة: ⭐): هيئ معاملات JVM G1GC ومعاملات تجمع اتصالات HikariCP لـ OrderFlow، وفعّل تسجيل GC واكتشاف تسرب الاتصالات.

  2. مسألة متقدمة (الصعوبة ⭐⭐): استخدم @EntityGraph وJOIN FETCH لحل مشكلة N+1 في استعلام قائمة الطلبات، وقارن عدد عبارات SQL وزمن الاستجابة قبل وبعد التحسين.

  3. تحدٍ (الصعوبة: ⭐⭐⭐): استخدم JMeter لإجراء اختبار حمل على واجهة قائمة طلبات OrderFlow، وأنشئ خطًا أساسيًا للأداء، وحسّنه تدريجيًا (JVM + تجمع الاتصالات + N+1 + فهارس). سجّل التغيرات في P50 وP99 لكل خطوة تحسين، وفي النهاية حسّن P99 إلى أقل من 50 مللي ثانية.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%