شركات التوصيل والخدمات اللوجستية في لبنان والمنطقة تواجه تحدياً تشغيلياً واضحاً: سائقون في الميدان، عملاء ينتظرون، ومدراء يحتاجون رؤية فورية. Go وWebSockets وPostgreSQL يُقدمون أساساً تقنياً ممتازاً لنظام تتبع مخصص.
لماذا يحتاج التتبع الفوري نظاماً خاصاً
شركات التوصيل والخدمات اللوجستية في لبنان والمنطقة تواجه تحدياً تشغيلياً واضحاً: سائقون في الميدان، عملاء ينتظرون التحديثات، ومدراء عمليات يحتاجون صورة فورية دقيقة. المنصات الجاهزة كـ Google Fleet Management تحل المشكلة للشركات الكبيرة بأسعار مرتفعة وقيود على التخصيص.
بناء نظام تتبع خاص بـ Go وWebSockets يمنحك تحكماً كاملاً في التكلفة، الخصوصية، والمنطق التشغيلي الخاص بعملياتك.
البنية الأساسية لنظام التتبع الفوري
نظام التتبع الفوري يتكون من ثلاثة طبقات متمايزة:
طبقة الاستقبال: تقبل تحديثات الموقع من تطبيق الهاتف عبر WebSocket أو HTTP POST. تتحقق من الهوية وتوجه للـ hub.
Hub التوزيع: يحتفظ بسجل السائقين النشطين والمشتركين في لوحة التحكم، يوجه التحديثات للمستأجرين الصحيحين، ويخزن آخر موقع معروف في Redis.
طبقة الحفظ: تكتب سجل الحركة في PostgreSQL باستخدام إدراج مجمّع مؤجل يخفض تكلفة الكتابة 80-90% مقارنة بالإدراج الفردي.
type TrackingHub struct {
mu sync.RWMutex
subscribers map[string][]chan LocationUpdate
lastKnown map[string]LocationUpdate
}
func (h *TrackingHub) Broadcast(update LocationUpdate) {
h.mu.Lock()
h.lastKnown[update.DriverID] = update
h.mu.Unlock()
h.mu.RLock()
subs := h.subscribers[update.TenantID]
h.mu.RUnlock()
for _, ch := range subs {
select {
case ch <- update:
default:
// المشترك بطيء، تخطَّ التحديث بدلاً من الحجب
}
}
}
الإرسال غير المحجوب (select default) ضروري. عميل لوحة تحكم بطيء لا يجب أن يحجب التحديثات عن باقي المشتركين.
مخطط PostgreSQL لسجل الحركة
تخزين كل تحديث موقع في جدول واحد يُشكّل مشكلة أداء وصيانة مع النمو. الجدول المقسّم زمنياً هو النهج الآمن للإنتاج:
CREATE TABLE driver_locations (
id BIGSERIAL,
driver_id UUID NOT NULL,
tenant_id UUID NOT NULL,
lat DOUBLE PRECISION NOT NULL,
lng DOUBLE PRECISION NOT NULL,
speed REAL,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT now()
) PARTITION BY RANGE (recorded_at);
CREATE INDEX ON driver_locations (driver_id, recorded_at DESC);
CREATE INDEX ON driver_locations (tenant_id, recorded_at DESC);
الأقسام الشهرية تعني أن حذف البيانات القديمة هو DROP TABLE على قسم واحد، لا DELETE كبير يُقفل الجدول.
الكتابة المجمّعة المؤجلة
مع 200 سائق نشط يرسل كل منهم تحديثاً كل 8 ثوانٍ، لديك 25 كتابة في الثانية. هذا قابل للإدارة لكنه ينمو بسرعة. الكتابة المجمّعة تخفض هذا بشكل كبير:
type LocationBuffer struct {
mu sync.Mutex
pending []LocationUpdate
}
func (b *LocationBuffer) Flush(ctx context.Context, db *pgxpool.Pool) error {
b.mu.Lock()
batch := b.pending
b.pending = b.pending[:0]
b.mu.Unlock()
if len(batch) == 0 {
return nil
}
return bulkInsertLocations(ctx, db, batch)
}
مؤقت زمني يُنفّذ Flush كل 5 ثوانٍ. 200 سائق يولّدون 1600 تحديث خلال 5 ثوانٍ يُصبحون إدراجاً مجمّعاً واحداً.
Redis للموقع الأخير المعروف
مستخدمو لوحة التحكم يحتاجون الموقع الحالي لكل سائق نشط لحظة فتح الواجهة:
func (s *TrackingService) GetDriverLocation(ctx context.Context, driverID string) (*LocationUpdate, error) {
key := fmt.Sprintf("driver:loc:%s", driverID)
data, err := s.redis.Get(ctx, key).Bytes()
if err == redis.Nil {
return s.db.GetLastDriverLocation(ctx, driverID)
}
var loc LocationUpdate
return &loc, json.Unmarshal(data, &loc)
}
Redis يتعامل مع القراءات الحساسة للتأخير. PostgreSQL يتعامل مع المتانة وسجل الحركة القابل للاستعلام.
حساب وقت الوصول المتوقع بدون تكاليف API خارجية
لحساب ETA تقديري بدون استدعاء Google Maps في كل طلب:
func haversineKm(lat1, lng1, lat2, lng2 float64) float64 {
const R = 6371.0
dLat := (lat2 - lat1) * math.Pi / 180
dLng := (lng2 - lng1) * math.Pi / 180
a := math.Sin(dLat/2)*math.Sin(dLat/2) +
math.Cos(lat1*math.Pi/180)*math.Cos(lat2*math.Pi/180)*
math.Sin(dLng/2)*math.Sin(dLng/2)
return R * 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
}
للتطبيقات التي تحتاج دقة أعلى في أحوال سير المدن كبيروت والرياض، HERE Maps وTomTom يوفران بيانات حركة المرور الإقليمية بأسعار مناسبة.
الدروس الأساسية من الإنتاج
نمط الإرسال غير المحجوب ضروري. مشترك بطيء واحد لا يجب أن يُسبب تأخراً لجميع مستخدمي لوحة التحكم في نفس الـ tenant.
Redis للموقع الأخير المعروف وPostgreSQL للسجل هو التقسيم المعماري الصحيح. لا تحاول خدمة طلبات لوحة التحكم الفورية من PostgreSQL مباشرة.
صمّم الكتابة المجمّعة المؤجلة قبل النشر. إضافتها بعد أول اختبار حمل مع حجم أسطول حقيقي أكثر إزعاجاً بكثير من بنائها من البداية.
هل تريد بناء نظام تتبع أو تشغيل لوجستي مخصص لعملياتك؟ تواصل مع فريق Voxire على https://voxire.com/get-a-quote/


