برنامه نویسی

مدار شکن 🎛️ الگوی با GO

Summarize this content to 400 words in Persian Lang

مشکل

همانطور که انیشتین گفت: ماشین های حرکت دائمی غیرممکن هستند.

سرور ⚙️ می تواند

برو پایین یعنی برو آفلاین
مدام بده 500 یعنی DBهایی که می زنند می توانند پایین بیایند
N تعداد دلایل طبیعی که می تواند سرور راه دور را بی فایده کند.

از آنجایی که کلاینت هیچ اطلاعاتی از وضعیت سرور نخواهد داشت، به تلاش خود ادامه می دهد و همه درخواست ها با شکست مواجه می شوند.

راه حل

پروکسی Circuit Breaker برای مشتری

راه حل پیشنهادی مایکروسافت یک ماشین حالت ساده است که می تواند در یکی از سه حالت وجود داشته باشد

تعطیل 🢔

— هیچ مشکلی وجود ندارد، عملیات می تواند همانطور که هست پیش برود.

باز 🔴

— ماشین (یعنی سرور) در وضعیت خوبی نیست، همه چیز را مسدود کنید.

نیمه باز

– آزمایش آتش، اجازه دهید یک یا چند درخواست برای بررسی وضعیت ماشین انجام شود. — در صورت موفقیت، حالت دستگاه را به حالت “بسته” برگردانید — در غیر این صورت آن را به “open 🔴” منتقل کنید

پیاده سازی

ما می توانیم از طریق یک پیاده سازی خوب فکر شده برای این کار در سونی مدار شکن برویم
در زیر مثالی برای آن آورده شده است.

سرور

// server.go
package main

import (
“log”
“net/http”
“os”
)

// ExampleServer is a test server to check the “CircuitBreaker” pattern
type ExampleServer struct {
addr string
logger *log.Logger
isEnabled bool
}

// NewExampleServer creates the instance of our server
func NewExampleServer(addr string) *ExampleServer {
return &ExampleServer{
addr: addr,
logger: log.New(os.Stdout, “Server\t”, log.LstdFlags),
isEnabled: true,
}
}

// ListenAndServe starts listening on the address provided
// on creating the instance.
func (s *ExampleServer) ListenAndServe() error {
// The main endpoint we will request to
http.HandleFunc(“https://dev.to/”, func(w http.ResponseWriter, r *http.Request) {
if s.isEnabled {
s.logger.Println(“responded with OK”)
w.WriteHeader(http.StatusOK)
} else {
s.logger.Println(“responded with Error”)
w.WriteHeader(http.StatusInternalServerError)
}
})

// Toggle endpoint to switch on and off responses from the main one
http.HandleFunc(“/toggle”, func(w http.ResponseWriter, r *http.Request) {
s.isEnabled = !s.isEnabled
s.logger.Println(“toggled. Is enabled:”, s.isEnabled)
w.WriteHeader(http.StatusOK)
})

return http.ListenAndServe(s.addr, nil)
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

Server.go

type ExampleServer struct {
addr string
logger *log.Logger
isEnabled bool
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در اینجا “isEnabled” روش سرور برای گفتن من آنلاین / آفلاین است.
چه زمانی false اصلا برای من چیزی نفرستید، چون بدون توجه به آنها شکست خواهم خورد.

ExampleServer سرور دو مسیر

/

— با 200 پاسخ می دهد

/toggle

— با 200 پاسخ می دهد — وضعیت ساختار “isEnabled” را تغییر می دهد.

client.go

// client.go
package main

import (
“errors”
“net/http”
)

type NotificationClient interface {
Send() error // We ignore all the arguments to simplify the demo
}

type SmsClient struct {
baseUrl string
}

func NewSmsClient(baseUrl string) *SmsClient {
return &SmsClient{
baseUrl: baseUrl,
}
}

func (s *SmsClient) Send() error {
url := s.baseUrl + “https://dev.to/”
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return errors.New(“bad response”)
}

return nil
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

SmsClient struct یک روش دارد Send فقط یک Api به سرور می‌زند، برگردانید error هنگامی که ضربه Api با موفقیت انجام نمی شود.

func (s *SmsClient) Send() error {
url := s.baseUrl + “https://dev.to/”
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return errors.New(“bad response”)
}

return nil
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

circuit_breaker.go

// circuit_breaker.go
package main

import (
“log”
“os”
“time”

“github.com/sony/gobreaker/v2”
)

type ClientCircuitBreakerProxy struct {
client NotificationClient
logger *log.Logger
gb *gobreaker.CircuitBreaker[any] // downloaded lib structure
}

// shouldBeSwitchedToOpen checks if the circuit breaker should
// switch to the Open state
func shouldBeSwitchedToOpen(counts gobreaker.Counts) bool {
failureRatio := float64(counts.TotalFailures) / float64(counts.Requests)
return counts.Requests >= 3 && failureRatio >= 0.6
}

func NewClientCircuitBreakerProxy(client NotificationClient) *ClientCircuitBreakerProxy {
logger := log.New(os.Stdout, “CB\t”, log.LstdFlags)

// We need circuit breaker configuration
cfg := gobreaker.Settings{
// When to flush counters int the Closed state
Interval: 5 * time.Second,
// Time to switch from Open to Half-open
Timeout: 7 * time.Second,
// Function with check when to switch from Closed to Open
ReadyToTrip: shouldBeSwitchedToOpen,
// set Max Request in Half Open state to 5
MaxRequests: 5,
// On State change Handler Fn
OnStateChange: func(_ string, from gobreaker.State, to gobreaker.State) {
// Handler for every state change. We’ll use for debugging purpose
logger.Println(“state changed from”, from.String(), “to”, to.String())
},
}

return &ClientCircuitBreakerProxy{
client: client,
logger: logger,
gb: gobreaker.NewCircuitBreaker[any](cfg),
}
}

func (c *ClientCircuitBreakerProxy) Send() error {
// We call the Execute method and wrap our client’s call
_, err := c.gb.Execute(func() (any, error) {
err := c.client.Send()
return nil, err
})
return err
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ساخت ClientCircuitBreakerProxy

نگه می دارد client که می تواند خانه کند SmsClient که در بالا اعلام کردیم.
یک نمونه برای CircuitBreaker ماشین حالتی که اجرای الگوی “Circuit Breaker” را نگه می دارد.

type ClientCircuitBreakerProxy struct {
client NotificationClient
logger *log.Logger
gb *gobreaker.CircuitBreaker[any] // downloaded lib structure
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در اجرا NewClientCircuitBreakerProxy fn a را می گیرد SmsClient به عنوان پارامتر و مقادیر اولیه را برای CircuitBreaker

// We need circuit breaker configuration
cfg := gobreaker.Settings{
// When to flush counters int the Closed state
Interval: 5 * time.Second,
// Time to switch from Open to Half-open
Timeout: 7 * time.Second,
// Function with check when to switch from Closed to Open
ReadyToTrip: shouldBeSwitchedToOpen,
// set Max Request in Half Open state to 5
MaxRequests: 5,
// On State change Handler Fn
OnStateChange: func(_ string, from gobreaker.State, to gobreaker.State) {
// Handler for every state change. We’ll use for debugging purpose
logger.Println(“state changed from”, from.String(), “to”, to.String())
},
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

Send روش در ساختار ClientCircuitBreakerProxy یک لفاف است به client's Send روش

main.go

// main.go
package main

import (
“log”
“os”
“time”
)

func main() {
logger := log.New(os.Stdout, “Main\t”, log.LstdFlags)
server := NewExampleServer(“:8080”)

go func() {
_ = server.ListenAndServe()
}()

var client NotificationClient

client = NewSmsClient(“http://127.0.0.1:8080”)

client = NewClientCircuitBreakerProxy(client)

for {
err := client.Send()
time.Sleep(1 * time.Second)
if err != nil {
logger.Println(“caught an error”, err)
}
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

logger := log.New(os.Stdout, “Main\t”, log.LstdFlags)

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

سرور ساده go را با اجرای همزمان آن شروع کنید go routine

server := NewExampleServer(“:8080”)

go func() {
_ = server.ListenAndServe()
}()

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

var client NotificationClient

client = NewSmsClient(“http://127.0.0.1:8080”)

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

آن را با پراکسی Circuit Breaker بپیچید

client = NewClientCircuitBreakerProxy(client)

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

یک حلقه بی نهایت اجرا کنید که باعث می شود API به سرور ساده دسترسی پیدا کند

for {
err := client.Send()
time.Sleep(1 * time.Second)
if err != nil {
logger.Println(“caught an error”, err)
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نتیجه گیری

همانطور که Client باعث می شود API در فواصل منظم به Server
تا زمانی که یک API مورد استفاده قرار گیرد /toggle مدار باعث می شود که مدار شکن وارد شود open 🢔

مرجع

الگوی قطع کننده مدار در GO

کد

اجرای ساده الگوی مدار شکن.

اجرای ساده الگوی مدار شکن.

اعدام

go run .

خروجی

مشکل

همانطور که انیشتین گفت: ماشین های حرکت دائمی غیرممکن هستند.

سرور ⚙️ می تواند

  • برو پایین یعنی برو آفلاین
  • مدام بده 500 یعنی DBهایی که می زنند می توانند پایین بیایند
  • N تعداد دلایل طبیعی که می تواند سرور راه دور را بی فایده کند.

از آنجایی که کلاینت هیچ اطلاعاتی از وضعیت سرور نخواهد داشت، به تلاش خود ادامه می دهد و همه درخواست ها با شکست مواجه می شوند.

راه حل

پروکسی Circuit Breaker برای مشتری

توضیحات تصویر

راه حل پیشنهادی مایکروسافت یک ماشین حالت ساده است که می تواند در یکی از سه حالت وجود داشته باشد

تعطیل 🢔

— هیچ مشکلی وجود ندارد، عملیات می تواند همانطور که هست پیش برود.

باز 🔴

— ماشین (یعنی سرور) در وضعیت خوبی نیست، همه چیز را مسدود کنید.

نیمه باز

– آزمایش آتش، اجازه دهید یک یا چند درخواست برای بررسی وضعیت ماشین انجام شود.
— در صورت موفقیت، حالت دستگاه را به حالت “بسته” برگردانید
— در غیر این صورت آن را به “open 🔴” منتقل کنید

پیاده سازی

  • ما می توانیم از طریق یک پیاده سازی خوب فکر شده برای این کار در سونی مدار شکن برویم
  • در زیر مثالی برای آن آورده شده است.

سرور

// server.go
package main

import (
    "log"
    "net/http"
    "os"
)

// ExampleServer is a test server to check the "CircuitBreaker" pattern
type ExampleServer struct {
    addr      string
    logger    *log.Logger
    isEnabled bool
}

// NewExampleServer creates the instance of our server
func NewExampleServer(addr string) *ExampleServer {
    return &ExampleServer{
        addr:      addr,
        logger:    log.New(os.Stdout, "Server\t", log.LstdFlags),
        isEnabled: true,
    }
}

// ListenAndServe starts listening on the address provided
// on creating the instance.
func (s *ExampleServer) ListenAndServe() error {
    // The main endpoint we will request to
    http.HandleFunc("https://dev.to/", func(w http.ResponseWriter, r *http.Request) {
        if s.isEnabled {
            s.logger.Println("responded with OK")
            w.WriteHeader(http.StatusOK)
        } else {
            s.logger.Println("responded with Error")
            w.WriteHeader(http.StatusInternalServerError)
        }
    })

    // Toggle endpoint to switch on and off responses from the main one
    http.HandleFunc("/toggle", func(w http.ResponseWriter, r *http.Request) {
        s.isEnabled = !s.isEnabled
        s.logger.Println("toggled. Is enabled:", s.isEnabled)
        w.WriteHeader(http.StatusOK)
    })

    return http.ListenAndServe(s.addr, nil)
}


وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

Server.go

type ExampleServer struct {
    addr      string
    logger    *log.Logger
    isEnabled bool
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

  • در اینجا “isEnabled” روش سرور برای گفتن من آنلاین / آفلاین است.
  • چه زمانی false اصلا برای من چیزی نفرستید، چون بدون توجه به آنها شکست خواهم خورد.

ExampleServer سرور دو مسیر

  • /

    — با 200 پاسخ می دهد

  • /toggle

    — با 200 پاسخ می دهد — وضعیت ساختار “isEnabled” را تغییر می دهد.

client.go

// client.go
package main

import (
    "errors"
    "net/http"
)

type NotificationClient interface {
    Send() error // We ignore all the arguments to simplify the demo
}

type SmsClient struct {
    baseUrl string
}

func NewSmsClient(baseUrl string) *SmsClient {
    return &SmsClient{
        baseUrl: baseUrl,
    }
}

func (s *SmsClient) Send() error {
    url := s.baseUrl + "https://dev.to/"
    resp, err := http.Get(url)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    if resp.StatusCode < 200 || resp.StatusCode >= 300 {
        return errors.New("bad response")
    }

    return nil
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

SmsClient struct یک روش دارد Send فقط یک Api به سرور می‌زند، برگردانید error هنگامی که ضربه Api با موفقیت انجام نمی شود.

func (s *SmsClient) Send() error {
    url := s.baseUrl + "https://dev.to/"
    resp, err := http.Get(url)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    if resp.StatusCode < 200 || resp.StatusCode >= 300 {
        return errors.New("bad response")
    }

    return nil
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

circuit_breaker.go

// circuit_breaker.go
package main

import (
    "log"
    "os"
    "time"

    "github.com/sony/gobreaker/v2"
)

type ClientCircuitBreakerProxy struct {
    client NotificationClient
    logger *log.Logger
    gb     *gobreaker.CircuitBreaker[any] // downloaded lib structure
}

// shouldBeSwitchedToOpen checks if the circuit breaker should
// switch to the Open state
func shouldBeSwitchedToOpen(counts gobreaker.Counts) bool {
    failureRatio := float64(counts.TotalFailures) / float64(counts.Requests)
    return counts.Requests >= 3 && failureRatio >= 0.6
}

func NewClientCircuitBreakerProxy(client NotificationClient) *ClientCircuitBreakerProxy {
    logger := log.New(os.Stdout, "CB\t", log.LstdFlags)

    // We need circuit breaker configuration
    cfg := gobreaker.Settings{
        // When to flush counters int the Closed state
        Interval: 5 * time.Second,
        // Time to switch from Open to Half-open
        Timeout: 7 * time.Second,
        // Function with check when to switch from Closed to Open
        ReadyToTrip: shouldBeSwitchedToOpen,
        // set Max Request in Half Open state to 5
        MaxRequests: 5,
        // On State change Handler Fn
        OnStateChange: func(_ string, from gobreaker.State, to gobreaker.State) {
            // Handler for every state change. We'll use for debugging purpose
            logger.Println("state changed from", from.String(), "to", to.String())
        },
    }

    return &ClientCircuitBreakerProxy{
        client: client,
        logger: logger,
        gb:     gobreaker.NewCircuitBreaker[any](cfg),
    }
}

func (c *ClientCircuitBreakerProxy) Send() error {
    // We call the Execute method and wrap our client's call
    _, err := c.gb.Execute(func() (any, error) {
        err := c.client.Send()
        return nil, err
    })
    return err
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ساخت ClientCircuitBreakerProxy

  • نگه می دارد client که می تواند خانه کند SmsClient که در بالا اعلام کردیم.
  • یک نمونه برای CircuitBreaker ماشین حالتی که اجرای الگوی “Circuit Breaker” را نگه می دارد.
type ClientCircuitBreakerProxy struct {
    client NotificationClient
    logger *log.Logger
    gb     *gobreaker.CircuitBreaker[any] // downloaded lib structure
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در اجرا NewClientCircuitBreakerProxy fn a را می گیرد SmsClient به عنوان پارامتر و مقادیر اولیه را برای CircuitBreaker

    // We need circuit breaker configuration
    cfg := gobreaker.Settings{
        // When to flush counters int the Closed state
        Interval: 5 * time.Second,
        // Time to switch from Open to Half-open
        Timeout: 7 * time.Second,
        // Function with check when to switch from Closed to Open
        ReadyToTrip: shouldBeSwitchedToOpen,
        // set Max Request in Half Open state to 5
        MaxRequests: 5,
        // On State change Handler Fn
        OnStateChange: func(_ string, from gobreaker.State, to gobreaker.State) {
            // Handler for every state change. We'll use for debugging purpose
            logger.Println("state changed from", from.String(), "to", to.String())
        },
    }
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

Send روش در ساختار ClientCircuitBreakerProxy یک لفاف است به client's Send روش

main.go

// main.go
package main

import (
    "log"
    "os"
    "time"
)

func main() {
    logger := log.New(os.Stdout, "Main\t", log.LstdFlags)
    server := NewExampleServer(":8080")

    go func() {
        _ = server.ListenAndServe()
    }()

    var client NotificationClient

    client = NewSmsClient("http://127.0.0.1:8080")

    client = NewClientCircuitBreakerProxy(client)

    for {
        err := client.Send()
        time.Sleep(1 * time.Second)
        if err != nil {
            logger.Println("caught an error", err)
        }
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

logger := log.New(os.Stdout, "Main\t", log.LstdFlags)
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

  • سرور ساده go را با اجرای همزمان آن شروع کنید go routine
server := NewExampleServer(":8080")

    go func() {
        _ = server.ListenAndServe()
    }()
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید


    var client NotificationClient

    client = NewSmsClient("http://127.0.0.1:8080")
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

  • آن را با پراکسی Circuit Breaker بپیچید
    client = NewClientCircuitBreakerProxy(client)

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

  • یک حلقه بی نهایت اجرا کنید که باعث می شود API به سرور ساده دسترسی پیدا کند
    for {
        err := client.Send()
        time.Sleep(1 * time.Second)
        if err != nil {
            logger.Println("caught an error", err)
        }
    }
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نتیجه گیری

  • همانطور که Client باعث می شود API در فواصل منظم به Server
  • تا زمانی که یک API مورد استفاده قرار گیرد /toggle مدار باعث می شود که مدار شکن وارد شود open 🢔

مرجع

الگوی قطع کننده مدار در GO

کد

اجرای ساده الگوی مدار شکن.

اجرای ساده الگوی مدار شکن.

اعدام

go run .

خروجی

توضیحات تصویر

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا