1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/megaease-easegress

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
developer-guide.md 16 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
gitlife-traslator Отправлено 29.11.2024 00:57 ea14e4f

Руководство разработчика

Архитектура

Как показано на диаграмме, кластер выполняет синхронизацию данных всех узлов, а супервизор управляет жизненным циклом всех видов объектов:

  1. Системный контроллер: каждый экземпляр EG имеет один и только один его вид.
  2. Бизнес-контроллер: компонент выполняет свою задачу, которая не связана напрямую с обработкой трафика.
  3. Трафик-гейт: он принимает трафик различных протоколов и направляет его в конвейеры.
  4. Конвейер: это цепочка фильтров, которая обрабатывает трафик от трафик-гейта.

Структура

Мы стараемся следовать стандарту макета проекта Go (https://github.com/golang-standards/project-layout), и важные каталоги описаны ниже:

.
├── bin                 // исполняемый двоичный файл
├── cmd                 // исходный код команды
├── doc                 // документы
├── pkg                 // импортируемые пакеты Go
│   ├── api             // слой restful API
│   ├── cluster             // компонент кластера
│   ├── common              // некоторые общие утилиты
│   ├── context             // контекст для трафик-гейта и конвейера
│   ├── env             // подготовка к рабочей среде
│   ├── filter              // корзина фильтров
│   ├── graceupdate         // плавное обновление
│   ├── logger              // утилиты логгера
│   ├── object              // корзина контроллеров
│   ├── option              // аргументы запуска утилиты
│   ├── pidfile             // файл для записи pid
│   ├── profile             // выделенный pprof
│   ├── protocol            // развязка для протокола
│   ├── registry            // реестр для всех динамически регистрируемых компонентов
│   ├── storage             // распределённая оболочка хранилища
│   ├── supervisor          // супервизор для управления контроллерами
│   ├── tracing             // распределённое отслеживание
│   ├── util                // все виды утилит
│   ├── v               // инструмент проверки
│   └── version             // версия выпуска
├── test                // сценарии интеграционного тестирования

Разработка объекта

Первая задача — выбрать категорию объекта. Часть архитектуры достаточно хорошо описывает их. В большинстве случаев создание нового бизнес-контроллера является лучшим выбором для расширения возможностей EG на уровне объекта. Поэтому мы разработаем лёгкий бизнес-контроллер, чтобы показать детали. Например, мы хотим разработать контроллер для дампа состояния всех объектов в локальный файл. Назовём вид контроллера StatusInLocalController, поэтому конфигурация контроллера:

kind: StatusInLocalController
name: statusInLocal
path: ./running_status.yaml

Основная бизнес-логика

Мы помещаем пакет контроллера в pkg/object/statusinlocalcontroller. Мы могли бы реализовать основную логику в pkg/object/statusinlocalcontroller/statusinlocalcontroller.go:

Супервизор имеет все ссылки на запущенные объекты, поэтому нам нужно вызвать супервизор, чтобы получить статус запущенных объектов, и основная бизнес-логика будет такой:

type (
    // StatusInLocalController публикует статус всех объектов в локальном файле.
    StatusInLocalController struct {
        super     *supervisor.Supervisor
        superSpec *supervisor.Spec
        spec      *Spec

        done chan struct{}
    }

    // Spec описывает StatusInLocalController.
    Spec struct {
        Path string `yaml:"path" jsonschema:"required"`
    }

    // Entry — структура файла статуса.
    Entry struct {
        Statuses     map[string]interface{}
        UnixTimestamp int64
    }
)

func (c
``` ### Регистрация в Supervisor

Все объекты должны удовлетворять интерфейсу `Object` в [`pkg/object/supervisor/registry.go`](https://github.com/megaease/easegress/blob/master/pkg/supervisor/registry.go).

```go
package statusinlocalcontroller

import (
    "io/ioutil"
    "runtime/debug"
    "time"

    "github.com/megaease/easegress/pkg/logger"
    "github.com/megaease/easegress/pkg/supervisor"

    "gopkg.in/yaml.v2"
)

const (
    // Kind — это тип StatusInLocalController.
    Kind = "StatusInLocalController"
)

type (
    // StatusInLocalController публикует статус всех объектов в локальном файле.
    StatusInLocalController struct {
        super     *supervisor.Supervisor
        superSpec *supervisor.Spec
        spec      *Spec

        done chan struct{}
    }

    // Spec описывает StatusInLocalController.
    Spec struct {
        Path string `yaml:"path" jsonschema:"required"`
    }

    // Entry — это структура файла статуса.
    Entry struct {
        Statuses     map[string]interface{}
        UnixTimestamp int64
    }
)

// init регистрирует себя в реестре супервизора.
func init() { supervisor.Register(&StatusInLocalController{}) }

// Category возвращает категорию StatusInLocalController.
func (c *StatusInLocalController) Category() supervisor.ObjectCategory {
    return supervisor.CategoryBusinessController
}

// Kind возвращает вид StatusInLocalController.
func (c *StatusInLocalController) Kind() string { return Kind }

// DefaultSpec возвращает спецификацию по умолчанию для StatusInLocalController.
func (c *StatusInLocalController) DefaultSpec() interface{} { return &Spec{} }

// Init инициализирует StatusInLocalController.
func (c *StatusInLocalController) Init(superSpec *supervisor.Spec, super *supervisor.Supervisor) {
    c.superSpec, c.spec, c.super = superSpec, superSpec.ObjectSpec().(*Spec), super
    c.reload()
}

// Inherit наследует предыдущее поколение StatusInLocalController.
func (c *StatusInLocalController) Inherit(spec *supervisor.Spec,
    previousGeneration supervisor.Object, super *supervisor.Supervisor) {

    previousGeneration.Close()
    c.Init(spec, super)
}

func (c *StatusInLocalController) reload() {
    c.done = make(chan struct{})

    go c.run()
}

func (c *StatusInLocalController) run() {
    for {
        select {
        case <-time.After(5 * time.Second):
            c.syncStatus()
        case <-c.done:
            return
        }
    }
}

// Status возвращает статус StatusInLocalController.
func (c *StatusInLocalController) Status() *supervisor.Status {
    return &supervisor.Status{
        ObjectStatus: struct{}{},
    }
}

// Close закрывает StatusInLocalController.
func (c *StatusInLocalController) Close() {
    close(c.done)
}

Разработка фильтра

В большинстве сценариев обработки трафика правильным выбором является вторая разработка фильтров, поскольку их планирование покрывается гибким конвейером. Фильтр занимается только своим делом, например, мы хотим разработать фильтр для подсчёта количества запросов с указанным заголовком. Давайте назовём этот вид фильтра... headerCounter, поэтому конфигурация фильтра в спецификации конвейера будет следующей:

filters:
- kind: HeaderCounter
  name: headerCounter
  headers: ['Cookie', 'Authorization']

Основная бизнес-логика

Мы помещаем пакет фильтров в pkg/filter/headercounter. Мы могли бы реализовать основную логику подсчёта заголовков в pkg/filter/headercounter/headercounter.go:

type (
    HeaderCounter struct {
        super    *supervisor.Supervisor     // The supervisor runtime.
        pipeSpec *httppipeline.FilterSpec   // The filter spec in pipeline level, which has two more fiels: kind and name.
        spec     *Spec                      // The filter spec in its own level.

        // The read and write for count must be locked, because the Handle is called concurrently.
        countMutex sync.Mutex
        count      map[string]int64
    }

    Spec struct {
        Headers []string `yaml:"headers"`
    }
)

func (m *HeaderCounter) Handle(ctx context.HTTPContext) (result string) {
    for _, key := range m.spec.Headers {
        value := ctx.Request().Header().Get(key)
        if value != "" {
            m.countMutex.Lock()
            m.count[key]++
            m.countMutex.Unlock()
        }
    }

    // NOTE: The filter must call the next handler to satisfy the Chain of Responsibility Pattern.
    return ctx.CallNextHandler("")
}

Регистрация в Pipeline

Наша основная логика очень проста, теперь давайте добавим некоторый код, не связанный с бизнесом, чтобы наш новый фильтр соответствовал требованиям фреймворка Pipeline. Все фильтры должны удовлетворять интерфейсу Filter в pkg/object/httppipeline/registry.go.

Все методы с их именами и комментариями понятны, единственный, который нам нужно подчеркнуть, это Inherit, он будет вызываться при обновлении конвейера, но фильтр с тем же именем и типом всё ещё существует. Это собственная ответственность фильтра — выполнять горячее обновление в Inherit, например, передавать значимые последовательные данные.

// init регистрирует себя в реестре конвейеров.
func init() { httppipeline.Register(&HeaderCounter{}) }

// Kind возвращает тип HeaderCounter.
func (hc *HeaderCounter) Kind() string { return "HeaderCounter" }

// DefaultSpec возвращает спецификацию по умолчанию для HeaderCounter.
func (hc *HeaderCounter) DefaultSpec() interface{} { return &Spec{} }

// Description возвращает описание HeaderCounter.
func (hc *HeaderCounter) Description() string {
    return "HeaderCounter подсчитывает количество запросов, содержащих указанный заголовок."
}

// Results возвращает результаты HeaderCounter.
func (hc *HeaderCounter) Results() []string { return nil }

// Init инициализирует HeaderCounter.
func (hc *HeaderCounter) Init(pipeSpec *httppipeline.FilterSpec, super *supervisor.Supervisor) {
    hc.pipeSpec, hc.spec, hc.super = pipeSpec, pipeSpec.FilterSpec().(*Spec), super
    hc.reload()
}

// Inherit наследует предыдущее поколение HeaderCounter.
func (hc *HeaderCounter) Inherit(pipeSpec *httppipeline.FilterSpec,
    previousGeneration httppipeline.Filter, super *supervisor.Supervisor) {

    previousGeneration.Close()
    hc.Init(pipeSpec, super)
}

func (m *HeaderCounter) reload() {
    m.count = make(map[string]int64)
}

// Status возвращает статус.
func (m *HeaderCounter) Status() interface{} {
    m.countMutex.Lock()
    defer m.countMutex.Unlock()

    return m.count
}


// Close закрывает HeaderCounter.
func (m *HeaderCounter) Close() {}

Затем нам нужно добавить строку импорта в pkg/registry/registry.go:

import (
    _ "github.com/megaease/easegress/pkg/filter/headercounter
)

Механизм JumpIf в Pipeline

Как мы описали в разделе Начало работы, конвейер ниже использует результат validator:

name: pipeline-demo
kind: HTTPPipeline
flow:
- filter: validator
  jumpIf: { invalid: END }
- filter: requestAdaptor
- filter: proxy 

Этот jumpIf означает, что запрос перейдёт в конец без прохождения через requestAdaptor и proxy, если validator возвращает результат invalid. Поэтому метод Results должен регистрировать все возможные результаты фильтра. В примере с HeaderCounter пустые результаты означают, что Handle возвращает только пустой результат. Поэтому, если мы хотим предотвратить передачу запросов без заголовков подсчёта следующим фильтрам, мы можем изменить его на:

const resultInvalidHeader = "invalidHeader"

// Results возвращает результаты HeaderCounter.
func (hc *HeaderCounter) Results() []string {
    return []string{resultInvalidHeader}            // Новый код
}

// Handle подсчитывает заголовок запросов.
func (m *HeaderCounter) Handle(ctx context.HTTPContext) (result string) {
    counted := false                                // Новый код
    for _, key := range m.spec.Headers {
        value := ctx.Request().Header().Get(key)
        if value != "" {
            m.countMutex.Lock()
            counted = true                          // Новый код
            m.count[key]++
            m.countMutex.Unlock()
        }
    }

    if !counted {                                   // Новый код
        return resultInvalidHeader                  // Новый код

    }                                               // Новый код

    return ""
}

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/megaease-easegress.git
git@api.gitlife.ru:oschina-mirror/megaease-easegress.git
oschina-mirror
megaease-easegress
megaease-easegress
main