Как показано на диаграмме, кластер выполняет синхронизацию данных всех узлов, а супервизор управляет жизненным циклом всех видов объектов:
Мы стараемся следовать стандарту макета проекта 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. Все фильтры должны удовлетворять интерфейсу 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
)
Как мы описали в разделе Начало работы, конвейер ниже использует результат 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 )