นโยบายการลองใหม่ในการพัฒนาโมบายล์ — แก่นแท้ กลยุทธ์ และหลักการ

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-03-11 เวลาอ่าน: 10 นาที

นโยบายการลองใหม่ — คือชุดของกฎที่กำหนดว่าแอปพลิเคชันมือถือจะลองเรียกเครือข่ายที่ล้มเหลวอีกครั้งโดยอัตโนมัติเมื่อใดและอย่างไร เมื่อการเชื่อมต่อไม่เสถียรหรือเกิดข้อผิดพลาดชั่วคราวของเซิร์ฟเวอร์ นโยบายการลองใหม่ที่ออกแบบมาอย่างดีจะช่วยเพิ่มความน่าเชื่อถือของแอปพลิเคชันโดยไม่ต้องให้ผู้ใช้ดำเนินการ ตามการวิจัยของ Google Developer Relations (2025) การใช้นโยบายการลองใหม่อย่างถูกต้องจะลดเปอร์เซ็นต์ของคำขอที่สูญหายลง 40–60% ในแอปพลิเคชันมือถือที่มีการดำเนินการเครือข่ายบ่อยครั้ง

ประเด็นสำคัญ

  • นโยบายการลองใหม่ — กลยุทธ์สำหรับลองคำขอใหม่โดยอัตโนมัติเมื่อเครือข่ายล้มเหลวหรือเกิดข้อผิดพลาดชั่วคราวของเซิร์ฟเวอร์
  • Exponential backoff — วิธีการเพิ่มความล่าช้าระหว่างการลองใหม่เพื่อลดภาระบนเซิร์ฟเวอร์
  • Jitter — การเบี่ยงเบนความล่าช้าแบบสุ่มที่ป้องกันผลกระทบฝูงชน (thundering herd)
  • Idempotency — ข้อกำหนดสำคัญสำหรับการลองใหม่อย่างปลอดภัย: คำขอที่ทำซ้ำต้องไม่ก่อให้เกิดผลข้างเคียง
  • Circuit Breaker — กลไกสำหรับหยุดการลองใหม่เมื่อบริการไม่พร้อมใช้งานเป็นเวลานานเพื่อรักษาทรัพยากร

นโยบายการลองใหม่คืออะไร?

นโยบายการลองใหม่ — คือกลยุทธ์ซอฟต์แวร์ที่กำหนดพฤติกรรมของไคลเอนต์เมื่อคำขอเครือข่ายล้มเหลว: ข้อผิดพลาดใดที่ควรลองใหม่ กี่ครั้ง ด้วยความล่าช้าเท่าใด และเมื่อใดควรหยุดลอง ในแอปพลิเคชันมือถือ นโยบายการลองใหม่มีความสำคัญอย่างยิ่งเนื่องจากความไม่เสถียรของเครือข่ายมือถือและความเป็นไปได้ของความล้มเหลวชั่วคราวฝั่งเซิร์ฟเวอร์

นโยบายการลองใหม่พื้นฐานประกอบด้วยสามพารามิเตอร์: จำนวนการลองใหม่สูงสุด (maxRetries), ความล่าช้าเริ่มต้น (baseDelay) และกลยุทธ์ backoff นอกจากนี้ สามารถระบุรายการรหัสสถานะ HTTP ที่ควรกระตุ้นให้ลองใหม่ และหมดเวลาสำหรับยกเลิกการลองทั้งหมด

ตามหนังสือ “Designing Data-Intensive Applications” โดย Martin Kleppmann 50% ของความล้มเหลวในระบบกระจายเป็นแบบชั่วคราวและแก้ไขได้ด้วยการลองใหม่ สิ่งนี้ทำให้นโยบายการลองใหม่เป็นหนึ่งในวิธีที่มีประสิทธิภาพและถูกที่สุดในการปรับปรุงความทนทานต่อข้อผิดพลาดของแอปพลิเคชันมือถือโดยไม่ต้องเปลี่ยนแปลงสถาปัตยกรรมเซิร์ฟเวอร์

ข้อผิดพลาดใดที่ควรลองใหม่

ข้อผิดพลาดชั่วคราว (Retriable) — เป็นความล้มเหลวประเภทเดียวที่นโยบายการลองใหม่ควรตอบสนอง ซึ่งรวมถึงการหมดเวลาการเชื่อมต่อ (SocketTimeoutException), การไม่พร้อมใช้งานชั่วคราวของเซิร์ฟเวอร์ (HTTP 503, 502) และข้อผิดพลาด DNS ข้อผิดพลาดถาวร — HTTP 400, 401, 403, 404 — ไม่ควรลองใหม่เพราะบ่งบอกถึงปัญหาในคำขอ ไม่ใช่เครือข่ายหรือเซิร์ฟเวอร์

ตาม AWS Architecture Blog การจำแนกข้อผิดพลาดอย่างถูกต้องเป็น Retriable และ Non-retriable เป็นการตัดสินใจที่สำคัญที่สุดในการออกแบบนโยบายการลองใหม่ การลองคำขอที่ไม่ใช่ idempotent ซ้ำด้วย HTTP 401 อาจทำให้บัญชีถูกล็อค และการลอง HTTP 400 ซ้ำอาจสร้างข้อมูลซ้ำซ้อน กำหนดค่ารายการรหัสสำหรับการลองใหม่อย่างชัดเจนเสมอ

กลยุทธ์การลองใหม่พื้นฐาน

ช่วงเวลาคงที่ — กลยุทธ์ที่ง่ายที่สุด: การลองใหม่แต่ละครั้งเกิดขึ้นหลังจากช่วงเวลาเดียวกัน ตัวอย่างเช่น ด้วยความล่าช้า 2 วินาที แอปพลิเคชันจะลองคำขอใหม่หลังจาก 2, 2, 2 วินาที ช่วงเวลาคงที่ง่ายต่อการนำไปใช้และคาดเดาได้ แต่สร้างภาระที่สม่ำเสมอบนเซิร์ฟเวอร์เมื่อเกิดความล้มเหลวจำนวนมาก

ช่วงเวลาเพิ่มขึ้น — ความล่าช้าเพิ่มขึ้นเป็นเส้นตรงในการลองใหม่แต่ละครั้ง: การลองใหม่ครั้งแรกหลังจาก 1 วินาที ครั้งที่สองหลังจาก 2 ครั้งที่สามหลังจาก 3 เป็นต้น กลยุทธ์นี้ให้เวลาเซิร์ฟเวอร์มากขึ้นในการกู้คืนเมื่อเกิดความล้มเหลวซ้ำ ๆ แต่ยังคงคาดเดาได้สำหรับไคลเอนต์จำนวนมากที่ล้มเหลวพร้อมกัน

กลยุทธ์สูตรความล่าช้าเวลาสะสม (3 ครั้ง)การประยุกต์ใช้
คงที่delay = D3 × Dสถานการณ์ง่าย, การหมดเวลาเฉพาะที่
เพิ่มขึ้นdelay = N × D6 × Dการลดภาระแบบค่อยเป็นค่อยไป
Exponentialdelay = D × 2^N7 × Dความล้มเหลวจำนวนมาก, บริการคลาวด์
Exponential + Jitterdelay = random(0, D × 2^N)แปรผันภาระสูง, ไมโครเซอร์วิส

การเลือกกลยุทธ์ขึ้นอยู่กับลักษณะของแอปพลิเคชัน สำหรับงานเบื้องหลังของการซิงโครไนซ์ข้อมูลบนอุปกรณ์มือถือ กลยุทธ์ exponential กับ jitter เหมาะสมที่สุด — ให้ความน่าจะเป็นของความสำเร็จสูงสุดโดยมีภาระน้อยที่สุดบนเซิร์ฟเวอร์และอุปกรณ์ของผู้ใช้

Exponential Backoff และ Jitter

Exponential backoff — กลยุทธ์ที่ความล่าช้าระหว่างการลองใหม่เพิ่มเป็นสองเท่าในการลองแต่ละครั้ง หากความล่าช้าเริ่มต้นคือ 1 วินาที ลำดับความล่าช้าจะเป็น 1, 2, 4, 8, 16 วินาที ซึ่งให้เวลาเซิร์ฟเวอร์ในการกู้คืนเพิ่มขึ้นแบบทวีคูณ

Jitter — การเบี่ยงเบนความล่าช้าแบบสุ่มที่ป้องกันคำขอลองใหม่พร้อมกันจากไคลเอนต์หลายตัว (ปัญหา thundering herd) หากไม่มี jitter ไคลเอนต์พันรายการที่มีนโยบายการลองใหม่เดียวกันจะลองคำขอพร้อมกัน สร้างภาระสูงสุดบนเซิร์ฟเวอร์ Jitter กระจายการลองใหม่ตามเวลา

การนำไปใช้ใน Kotlin กับ Coroutines

Coroutines ของ Kotlin ช่วยให้นำ exponential backoff กับ jitter ไปใช้ได้โดยไม่บล็อกเธรดหลัก ฟังก์ชัน retry จาก kotlinx-coroutines รับเงื่อนไขการลองใหม่และบล็อกที่มีเนื้อหาคำขอ จัดการความล่าช้าและจำนวนครั้งโดยอัตโนมัติ

kotlin
suspend fun RetryPolicy.executeWithRetry(
    block: suspend () -> Result<T>
): Result<T> {
    var lastError: Throwable? = null
    repeat(maxRetries + 1) { attempt ->
        try {
            return block()
        } catch (e: Exception) {
            if (!isRetriable(e) || attempt == maxRetries) {
                return Result.failure(e)
            }
            val delay = (baseDelayMs * (1 shl attempt))
                .toLong()
            val jitteredDelay = (delay * (0.5 + Random.nextDouble())).toLong()
            delay(jitteredDelay)
            lastError = e
        }
    }
    return Result.failure(lastError!!)
}

ฟังก์ชัน executeWithRetry รับ lambda ที่มีการเรียกเครือข่ายและดำเนินการด้วย exponential backoff และ jitter หากข้อผิดพลาดไม่สามารถลองใหม่ได้หรือเกินจำนวนครั้งสูงสุด ฟังก์ชันจะส่งคืนข้อผิดพลาด ความล่าช้าจะถูกคูณด้วยปัจจัยสุ่มตั้งแต่ 0.5 ถึง 1.5 เพื่อการกระจายการลองใหม่อย่างสม่ำเสมอ

Circuit Breaker และการหยุดลองใหม่

Circuit Breaker — รูปแบบการออกแบบที่ป้องกันคำขอลองใหม่ไม่รู้จบเมื่อบริการไม่พร้อมใช้งานเป็นเวลานาน เมื่อจำนวนข้อผิดพลาดเกินเกณฑ์ Circuit Breaker จะเปลี่ยนเป็นสถานะ OPEN และส่งคืนข้อผิดพลาดทันทีโดยไม่ดำเนินการตามคำขอ ให้เวลาเซิร์ฟเวอร์ในการกู้คืน

ในแอปพลิเคชันมือถือ Circuit Breaker มีประโยชน์อย่างยิ่งเมื่อ API ไม่พร้อมใช้งานเนื่องจากการบำรุงรักษาตามแผนหรือความล้มเหลวของเครือข่ายผู้ให้บริการ หากไม่มี Circuit Breaker แอปพลิเคชันจะสิ้นเปลืองแบตเตอรี่และการรับส่งข้อมูลในการลองใหม่ไม่รู้จบ ทำให้ประสบการณ์ผู้ใช้แย่ลงและลดอายุการใช้งานแบตเตอรี่ของอุปกรณ์

แผนภาพสถานะ Circuit Breaker

Circuit Breaker มีสามสถานะ: CLOSED (การทำงานปกติ, คำขอถูกดำเนินการ), OPEN (ความล้มเหลว, คำขอถูกบล็อก) และ HALF_OPEN (คำขอทดสอบเพื่อตรวจสอบการกู้คืน) หลังจากหมดเวลาที่กำหนดในสถานะ OPEN เบรกเกอร์จะเปลี่ยนเป็น HALF_OPEN และดำเนินการหนึ่งคำขอ — หากสำเร็จจะกลับไปที่ CLOSED หากล้มเหลวจะกลับไปที่ OPEN

kotlin
class CircuitBreaker(
    private val failureThreshold: Int = 3,
    private val timeoutMs: Long = 30000
) {
    private var state = State.CLOSED
    private var failureCount = 0
    private var lastFailureTime: Long = 0

    suspend fun T.protect(block: suspend () -> T): T {
        checkState()
        return try {
            val result = block()
            onSuccess()
            result
        } catch (e: Exception) {
            onFailure()
            throw e
        }
    }
}

การนำ Circuit Breaker ไปใช้ใน Kotlin ประกอบด้วยตัวนับข้อผิดพลาดและตัวจับเวลาการกู้คืน เมธอด protect ตรวจสอบสถานะปัจจุบันก่อนดำเนินการตามคำขอที่ถูกห่อ และอัปเดตตัวนับข้อผิดพลาดเมื่อเกิดความล้มเหลว หลังจากถึง failureThreshold คำขอทั้งหมดจะถูกปฏิเสธทันทีจนกว่า timeoutMs จะหมดอายุ

นโยบายการลองใหม่ในแอปพลิเคชันมือถือ

เครือข่ายมือถือ มีลักษณะที่ทำให้นโยบายการลองใหม่สำคัญเป็นพิเศษ การสลับระหว่าง Wi-Fi และข้อมูลมือถือ การสูญเสียสัญญาณในรถไฟใต้ดินและอุโมงค์ การบล็อกชั่วคราวในระดับผู้ให้บริการ — สถานการณ์ทั้งหมดนี้นำไปสู่ความล้มเหลวของคำขอที่สามารถจัดการได้สำเร็จด้วยการลองใหม่

บน Android ไลบรารี Retrofit และ OkHttp มีกลไกการลองใหม่ในตัวผ่าน Interceptor บน iOS งานจะแก้ไขผ่าน URLSessionConfiguration และการมอบหมายแบบกำหนดเอง สำหรับการพัฒนาข้ามแพลตฟอร์ม Ktor (KMP) รวมถึงการรองรับการลองใหม่ในตัวพร้อมกลยุทธ์ที่กำหนดค่าได้

การนำไปใช้บน iOS กับ Combine

Combine — เฟรมเวิร์กของ Apple สำหรับการเขียนโปรแกรมเชิงรับ ตัวดำเนินการ retry ใน Combine จะทำซ้ำ publisher ตามจำนวนครั้งที่ระบุเมื่อเกิดข้อผิดพลาด แต่ไม่อนุญาตให้กำหนดค่าความล่าช้าระหว่างการลองใหม่ สำหรับนโยบายการลองใหม่ที่สมบูรณ์ จะใช้การรวมแบบกำหนดเองของ catch และ flatMap กับความล่าช้า

swift
extension Publisher {
    func retryWithBackoff(
        retries: Int = 3,
        baseDelay: TimeInterval = 1.0
    ) -> AnyPublisher<Output, Failure> {
        return self.catch { error -> AnyPublisher in
            guard retries > 0 else {
                return Fail(error).eraseToAnyPublisher()
            }
            return Just(())
                .delay(for: .seconds(baseDelay), scheduler: DispatchQueue.main)
                .flatMap { self.retryWithBackoff(
                    retries: retries - 1,
                    baseDelay: baseDelay * 2
                ) }
                .eraseToAnyPublisher()
        }
        .eraseToAnyPublisher()
    }
}

ส่วนขยาย retryWithBackoff สำหรับ Publisher ใน Combine นำ exponential backoff ไปใช้ผ่านการเรียกซ้ำโดยลดตัวนับและเพิ่มความล่าช้าเป็นสองเท่า ตัวดำเนินการ delay สร้างการหยุดชั่วคราวระหว่างการลองใหม่ ในขณะที่ catch ดักจับข้อผิดพลาดและตัดสินใจว่าจะลองใหม่หรือส่งคืนความล้มเหลว

ข้อผิดพลาดทั่วไปในนโยบายการลองใหม่

ข้อผิดพลาดแรก — การลองคำขอซ้ำโดยไม่ตรวจสอบ idempotency หากเซิร์ฟเวอร์สร้างทรัพยากรแล้วแต่ไม่ส่งคืนการยืนยันเนื่องจากเครือข่ายล้มเหลว การลองใหม่จะสร้างข้อมูลซ้ำซ้อน สำหรับคำขอ POST ให้ใช้คีย์ idempotency (Idempotency-Key) ในส่วนหัวเสมอ หรือจำกัดการลองใหม่เฉพาะ GET, PUT และ DELETE

ข้อผิดพลาดที่สอง — การลองใหม่ไม่รู้จบ กำหนดจำนวนครั้งสูงสุดเสมอ (3–5 สำหรับแอปพลิเคชันมือถือ) และหมดเวลารวมสำหรับการลองทั้งหมด การลองใหม่ไม่รู้จบทำให้แบตเตอรี่หมดและสร้างภาระปรสิตบนเซิร์ฟเวอร์ โดยเฉพาะอย่างยิ่งระหว่างการย้ายฐานข้อมูลหรือการเปลี่ยนแปลง API

ข้อผิดพลาดที่สาม — การละเลยบริบทของแอปพลิเคชัน หากผู้ใช้ปิดแอปพลิเคชันหรือเปลี่ยนไปทำงานเบื้องหลัง นโยบายการลองใหม่ที่ทำงานอยู่ควรถูกยกเลิกอย่างถูกต้อง ใช้ coroutines กับ SupervisorScope หรือ Combine กับวงจรชีวิต UI เพื่อยกเลิกการลองใหม่โดยอัตโนมัติเมื่อปิดหน้าจอ

ข้อผิดพลาดที่สี่ — การไม่บันทึกการลองใหม่ หากไม่มีการบันทึก คุณจะไม่รู้ว่ามีคำขอจำนวนเท่าใดที่ถูกลองใหม่ ข้อผิดพลาดใดที่เกิดขึ้น และนโยบายการลองใหม่ของคุณมีประสิทธิภาพเพียงใด เพิ่มเมตริก: จำนวนการลองใหม่ ความสำเร็จหลังการลองใหม่ การกระจายความล่าช้า ข้อมูลนี้จะช่วยปรับพารามิเตอร์กลยุทธ์ที่เหมาะสมสำหรับแอปพลิเคชันเฉพาะของคุณ

คำถามที่พบบ่อย

ควรลองคำขอใหม่กี่ครั้งในแอปพลิเคชันมือถือ?

จำนวนการลองใหม่ที่เหมาะสมคือ 3–5 ครั้งสำหรับสถานการณ์ส่วนใหญ่ สำหรับการซิงโครไนซ์เบื้องหลัง 5–7 ครั้งก็ยอมรับได้ สำหรับคำขอเชิงโต้ตอบ (เช่น การส่งแบบฟอร์ม) ไม่เกิน 3 ครั้ง การลองใหม่มากกว่าไม่ได้เพิ่มโอกาสสำเร็จแต่สิ้นเปลืองแบตเตอรี่และการรับส่งข้อมูลของผู้ใช้

Exponential backoff คืออะไรในคำง่าย ๆ?

Exponential backoff — คือการเพิ่มความล่าช้าระหว่างการลองใหม่เป็นสองเท่า: 1 วินาที, 2, 4, 8, 16 ไปเรื่อย ๆ หากเซิร์ฟเวอร์โอเวอร์โหลด การหยุดสั้น ๆ ระหว่างการลองครั้งแรกช่วยให้ตอบสนองได้เร็ว ในขณะที่การหยุดที่เพิ่มขึ้นในการลองครั้งต่อ ๆ ไปให้เวลาเซิร์ฟเวอร์มากขึ้นในการกู้คืน

ควรรหัสสถานะ HTTP ใดที่ควรลองใหม่?

ลองใหม่เฉพาะข้อผิดพลาดชั่วคราว: 408 (Request Timeout), 429 (Too Many Requests), 502 (Bad Gateway), 503 (Service Unavailable), 504 (Gateway Timeout) ข้อผิดพลาด 4xx (ยกเว้น 408 และ 429) บ่งบอกถึงปัญหาไคลเอนต์ — การลองใหม่นั้นไม่มีประโยชน์และอาจเป็นอันตรายต่อข้อมูลผู้ใช้

นโยบายการลองใหม่ต่างจาก Circuit Breaker อย่างไร?

นโยบายการลองใหม่ จัดการการลองใหม่ของคำขอเดียวเมื่อเกิดความล้มเหลว Circuit Breaker จัดการสถานะการเชื่อมต่อกับบริการ: เมื่อข้อผิดพลาดสะสม จะเปิดวงจร (OPEN) และบล็อกคำขอใหม่ การลองใหม่ทำงานในระดับการเรียกแต่ละครั้ง Circuit Breaker ทำงานในระดับการรวมบริการ

วิธีทดสอบนโยบายการลองใหม่บนอุปกรณ์มือถือ?

สำหรับการทดสอบนโยบายการลองใหม่ ให้ใช้ NetworkInterceptor (OkHttp) บน Android และ URLProtocol (URLSession) บน iOS เพื่อจำลองความล้มเหลวของเครือข่าย กำหนดพารามิเตอร์: ความถี่ข้อผิดพลาด ระยะเวลาไม่พร้อมใช้งาน และรหัสตอบสนอง การทดสอบหน่วยกับ MockWebServer (OkHttp) หรือ OHHTTPStubs (iOS) ตรวจสอบตรรกะการลองใหม่โดยไม่มีเครือข่ายจริง

สรุป

  • นโยบายการลองใหม่ — กลยุทธ์สำหรับลองคำขอเครือข่ายใหม่โดยอัตโนมัติเมื่อเกิดความล้มเหลวชั่วคราวด้วยพารามิเตอร์ที่กำหนดค่าได้ของความล่าช้าและจำนวนครั้ง
  • Exponential backoff กับ jitter — กลยุทธ์พื้นฐานสำหรับแอปพลิเคชันมือถือ ลดภาระเซิร์ฟเวอร์ระหว่างความล้มเหลวจำนวนมากและป้องกันผลกระทบ thundering herd
  • Idempotency — เงื่อนไขบังคับสำหรับการลองคำขอที่ไม่ใช่ GET อย่างปลอดภัย: หากไม่มี การลองใหม่จะสร้างข้อมูลซ้ำซ้อนหรือผลข้างเคียงที่ไม่พึงประสงค์
  • Circuit Breaker เสริมนโยบายการลองใหม่โดยป้องกันการลองใหม่ไม่รู้จบเมื่อบริการไม่พร้อมใช้งานเป็นเวลานานและประหยัดทรัพยากรอุปกรณ์
  • การจำแนกข้อผิดพลาด เป็น Retriable (503, 502, timeout) และ Non-retriable (400, 401, 403) มีความสำคัญอย่างยิ่งต่อการทำงานที่ถูกต้องของนโยบายการลองใหม่
  • สูงสุด 3–5 ครั้งในสถานการณ์โต้ตอบและสูงสุด 7 ครั้งสำหรับการซิงโครไนซ์เบื้องหลังเป็นค่าที่เหมาะสมสำหรับแอปพลิเคชันมือถือตาม Google Developer Relations
  • คำแนะนำ — ใช้นโยบายการลองใหม่กับ exponential backoff, Circuit Breaker และการบันทึกสำหรับคำขอเครือข่ายทั้งหมดในแอปพลิเคชันมือถือ

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม