Что такое сервисный шлюз
В предыдущей статье мы изучили основы Springboot для создания микросервисов, а также можем использовать Springboot для создания сервисов. Далее поговорим о springcloud на основе springboot. Этот springcloud не является конкретной технологией, он относится к экосистеме микросервисов. Например, в него входят шлюз, центр регистрации, центр конфигурации и т. д. Сегодня давайте взглянем на шлюз микросервисов.Существует много видов шлюзов микросервисов.На этот раз для объяснения мы используем текущий массовый облачный шлюз Spring. В микросервисной системе каждый сервис представляет собой независимый модуль и независимый компонент.Полная микросервисная система состоит из нескольких независимых сервисов, каждый из которых выполняет свою функцию бизнес-модуля. Например, пользовательский сервис предоставляет услуги и функции, связанные с пользовательской информацией, а платежный модуль предоставляет функции, связанные с платежами. Каждая служба взаимодействует через REST API или RPC (будет обсуждаться позже), и, как правило, наши микрослужбы должны обеспечивать связь без сохранения состояния. После того, как мы внедрим микросервисы, это также принесет неудобства в некоторых аспектах.Если веб-странице или приложению необходимо запросить изменение адреса доставки и оплатить после покупки, в этом сценарии:Будут некоторые проблемы, как показано выше:
- Клиенту необходимо инициировать несколько запросов на получение услуг, соответствующих разным доменным именам, что увеличивает стоимость связи и усложняет обслуживание клиентского кода.
- Проверка сервиса будет производиться отдельно в каждом сервисе, если логика аутентификации у каждого сервиса разная, то клиент будет верифицироваться повторно.
- Кроме того, если протоколы, используемые каждым сервисом, будут разными, это будет катастрофой для клиента.
Исходя из вышеизложенного, нам нужен средний уровень, чтобы клиент мог запросить средний уровень.Что касается службы, которую необходимо запросить, промежуточное программное обеспечение запросит ее и, наконец, вернет результаты клиенту.Этот средний уровень является шлюзом. .
Зачем использовать шлюз
Использование шлюза имеет несколько эффектов:
Единая аутентификация
Как правило, на шлюзе существует два типа аутентификации: 1. Аутентификация личности запрошенного клиента. 2. Контроль доступа заключается в определении наличия доступа к ресурсу после подтверждения личности пользователя. В нашем монолитном приложении клиентские запросы на проверку личности и ограничения на разрешения ресурсов относительно просты, и соответствующие данные о пользователе и разрешениях могут быть получены через запрошенный сеанс, но в микросервисной архитектуре все службы разделены на одиночный микросервис и развертывание кластера усложнятся, потому что, если сессия все еще используется, каждый запрос может не обязательно падать на одну и ту же машину в распределенной ситуации, что приведет к тому, что сессия будет недействительной. Нам нужно проделать дополнительную работу, чтобы гарантировать согласованность сеансов в кластере. Итак, мы выполняем унифицированную обработку и аутентификацию на уровне шлюза:
протоколирование
Когда приходит клиентский запрос, нам необходимо записать зависимый от времени адрес источника, ip и другую информацию текущего запроса, чтобы мы могли унифицированным образом перехватить и получить его на уровне шлюза, а затем вывести на сервер. log и выводить его через компонент ELK.Записываемый контент может быть многомерным и многоинформационным унифицированным записям не нужно записывать отдельно для каждого конкретного сервиса.
Распространение и фильтрация запросов
Для шлюза это сопоставление и распределение запросов является наиболее важной функцией.На самом деле наш общий компонент nginx имеет функцию переадресации и фильтрации запросов.Для шлюза запрос может быть предварительно отфильтрован и постфильтрован.
- Распределение запросов: получите запрос клиента, сопоставьте запрос с каждой последующей микрослужбой и запросите микрослужбу.Поскольку гранулярность микрослужбы относительно высока, шлюз может функционально интегрировать каждую микрослужбу и, наконец, вернуть ее клиенту.
- Фильтрация: шлюз будет перехватывать все запросы, что эквивалентно горизонтальному аспекту AOP в spring, и в этом аспекте выполняются такие операции, как аутентификация, ограничение тока и аутентификация.
выпуск оттенков серого
В целом интернет-продукты компании итеративно очень быстры и работают небольшими шагами. По сути, итерация версии выпускается каждую неделю. В этом случае будут риски, такие как совместимость, функциональная целостность, а баги в конечном итоге приведут к авариям за относительно короткое время. Таким образом, когда мы выпустим, мы выпустим новую функцию на назначенную машину и разделим небольшую часть трафика для наблюдения за конкретной ситуацией. Следовательно, шлюз как точка входа запроса может просто выполнять эту функцию.
Общие шлюзовые решения
Как правило, мы обычно используем несколько шлюзов, например: OpenResty, Zuul, Gateway, Kong, Tyk и т. д. В основном мы используем фреймворк системы Spring, поэтому в этой статье мы объясним Gateway, а другие реализации шлюза не будем освещать.OpenResty — это веб-сервер, интегрированный с nginx+lua, в который интегрировано множество сторонних библиотек и модулей. На самом деле Zuul использовал springcloud на ранней стадии интеграции, но в то время из-за проблем с производительностью, которые могут быть вызваны его стратегией поточной модели, Spring наконец выбрал Spring Cloud Gateway, разработанный самостоятельно.
Подготовка окружающей среды
В этой статье мы используем простой случай, чтобы продемонстрировать использование Spring Cloud Gateway. Во-первых, нам нужно жить в более чем двух приложениях Spring Boot. Конкретный метод создания см. В нашей второй статье в этой теме.
- spring-cloud-gateway-service1 Это микросервис
- Spring-cloud-gateway-wangguan Gateway Microservice
Создали 2 сервиса по мотивам предыдущей темы.В первый сервис добавляем контроллер
@RequestMapping(value = {"/getUser"}, method = RequestMethod.GET)
public String getUser() {
Map<String ,String> user = new HashMap<>();
user.put("name", "张三");
user.put("age", "45");
String s = JSONObject.toJSONString(user);
return s;
}
Второй сервис шлюза мы добавляем зависимость pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>2.0.4.RELEASE</version>
</dependency>
Добавить маршрут шлюза в application.yml
spring:
cloud:
gateway:
routes:
- predicates:
- Path =/gateway/** #匹配规则
uri: http://localhost:8099/getUser #服务1的访问地址
filters:
- StripRrefix: 1 #去掉前缀
server:
port: 8077
Объясните смысл приведенной выше конфигурации:
- uri: целевой адрес службы, настраиваемый uri и lb://имя службы приложения.
- предикаты: условия сопоставления, запрашивать ли маршрут в соответствии с правилом сопоставления
- фильтры: правила фильтрации, этот фильтр включает предварительную фильтрацию и постфильтрацию,
- StripPrefix=1, что означает удаление префикса, то есть удаление «шлюза» при переадресации целевого URL.
На этот раз мы нашли журнал запуска службы после запуска службы: Netty запущена на порту (ах): 8077. Это означает, что наша служба прошла успешно, и шлюз полагается на nettyserver для запуска мониторинга нескольких служб. Мы посетили:
Результат, возвращаемый службой, будет возвращен, если конфигурация верна.
принцип весеннего облачного шлюза
Вышеприведенный рисунок представляет собой схематическую диаграмму, официально предоставленную шлюзом, которую может быть нелегко понять.Давайте нарисуем картинку, чтобы помочь понять ее:Как показано на рисунке выше, сначала нужно объяснить несколько понятий:
- Маршрут: это один из компонентов шлюза, состоящий из идентификатора, uri, предиката и фильтра.
- Утверждение (предикат): соответствует содержимому http-запроса. Если возвращаемый результат верен, он будет перенаправлен в соответствии с текущим маршрутизатором.
- Фильтр: обеспечивает предварительную и постфильтрацию запросов.
Когда клиент отправляет запрос шлюзу, шлюз решает, к какому маршруту получить доступ, основываясь на результатах сопоставления ряда предикатов, а затем обрабатывает запрос в соответствии с фильтром.Фильтр может выполняться до и после отправки запроса. к серверной службе.
правила маршрутизации
Облачный шлюз Spring предоставляет механизм сопоставления маршрутов, например Path=/gateway/**, который мы настроили ранее, что означает сопоставление запросов с префиксом URL-адреса /gateway/ через атрибут Path. Фактически, Spring Cloud Gateway предоставляет нам множество правил, которые мы можем использовать. Вы можете понимать использование каждого предиката следующим образом: он будет перенаправлен только при выполнении этого условия Если их больше одного, он будет перенаправлен при выполнении всех. Исходный код этих Predict находится в пакете org.springframework.cloud.gateway.handler.predicate Давайте кратко рассмотрим:
динамическая маршрутизация
Есть два основных способа настроить маршрутизацию по шлюзу: 1. Использовать конфигурационный файл yml, 2. Прописать его в коде. Будь то конфигурация yml или кода, конфигурация маршрутизации не может быть изменена после запуска шлюза.Если должна быть запущена новая служба, шлюз должен быть отключен в первую очередь, а конфигурация yml изменена до перезапуска шлюза. Таким образом, если на шлюзе не будет корректного завершения работы, будут перебои в обслуживании, что, несомненно, недопустимо. При запуске шлюза информация о маршрутизации загружается в память по умолчанию, а информация о маршрутизации инкапсулируется в объект RouteDefinition. Несколько RouteDefinitions настроены для формирования системы маршрутизации шлюза. Атрибуты в RouteDefinition соответствуют атрибуты, настроенные в приведенном выше коде:Тогда нам нужна наша динамическая маршрутизация, чтобы решить эту проблему. Spring Cloud Gateway предоставляет конечные точки конечных точек, которые предоставляют информацию о маршрутизации. Существуют такие методы, как получение всех маршрутов, обновление маршрутов, просмотр одного маршрута и удаление маршрутов. Конкретный класс реализации org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint хочет получить доступ к методам в конечной точке. Вам нужно добавить аннотацию spring-boot-starter-actuator и указать все конечные точки в файле конфигурации. Чтобы написать класс реализации динамической маршрутизации, необходимо реализовать интерфейс ApplicationEventPublisherAware.
/**
* 动态路由服务
*/
@Service
public class GoRouteServiceImpl implements ApplicationEventPublisherAware {
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
//增加路由
public String add(RouteDefinition definition) {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "success";
}
//更新路由
public String update(RouteDefinition definition) {
try {
delete(definition.getId());
} catch (Exception e) {
return "update fail,not find route routeId: "+definition.getId();
}
try {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "success";
} catch (Exception e) {
return "update route fail";
}
}
//删除路由
public Mono<ResponseEntity<Object>> delete(String id) {
return this.routeDefinitionWriter.delete(Mono.just(id)).then(Mono.defer(() -> {
return Mono.just(ResponseEntity.ok().build());
})).onErrorResume((t) -> {
return t instanceof NotFoundException;
}, (t) -> {
return Mono.just(ResponseEntity.notFound().build());
});
}
}
Напишите интерфейсы Rest и реализуйте функции динамической маршрутизации через эти интерфейсы.
@RestController
@RequestMapping("/changeRoute")
public class ChangeRouteController {
@Autowired
private GoRouteServiceImpl goRouteServiceImpl;
//增加路由
@PostMapping("/add")
public String add(@RequestBody GatewayRouteDefinition gwdefinition) {
String flag = "fail";
try {
RouteDefinition definition = assembleRouteDefinition(gwdefinition);
flag = this.goRouteService.add(definition);
} catch (Exception e) {
e.printStackTrace();
}
return flag;
}
//删除路由
@DeleteMapping("/routes/{id}")
public Mono<ResponseEntity<Object>> delete(@PathVariable String id) {
try {
return this.goRouteService.delete(id);
}catch (Exception e){
e.printStackTrace();
}
return null;
}
//更新路由
@PostMapping("/update")
public String update(@RequestBody GatewayRouteDefinition gwdefinition) {
RouteDefinition definition = assembleRouteDefinition(gwdefinition);
return this.goRouteService.update(definition);
}
//把传递进来的参数转换成路由对象
private RouteDefinition assembleRouteDefinition(GatewayRouteDefinition gwdefinition) {
RouteDefinition definition = new RouteDefinition();
definition.setId(gwdefinition.getId());
definition.setOrder(gwdefinition.getOrder());
//设置断言
List<PredicateDefinition> pdList=new ArrayList<>();
List<GatewayPredicateDefinition> gatewayPredicateDefinitionList=gwdefinition.getPredicates();
for (GatewayPredicateDefinition gpDefinition: gatewayPredicateDefinitionList) {
PredicateDefinition predicate = new PredicateDefinition();
predicate.setArgs(gpDefinition.getArgs());
predicate.setName(gpDefinition.getName());
pdList.add(predicate);
}
definition.setPredicates(pdList);
//设置过滤器
List<FilterDefinition> filters = new ArrayList();
List<GatewayFilterDefinition> gatewayFilters = gwdefinition.getFilters();
for(GatewayFilterDefinition filterDefinition : gatewayFilters){
FilterDefinition filter = new FilterDefinition();
filter.setName(filterDefinition.getName());
filter.setArgs(filterDefinition.getArgs());
filters.add(filter);
}
definition.setFilters(filters);
URI uri = null;
if(gwdefinition.getUri().startsWith("http")){
uri = UriComponentsBuilder.fromHttpUrl(gwdefinition.getUri()).build().toUri();
}else{
// uri为 lb://consumer-service 时使用下面的方法
uri = URI.create(gwdefinition.getUri());
}
definition.setUri(uri);
return definition;
}
}
На самом деле, мы редко вызываем остальные службы для добавления или удаления информации о маршрутизации через API.Как правило, наша основная задача заключается в динамическом добавлении маршрутов путем интеграции функции конфигурации nacos. Об интеграции с nacos мы поговорим позже.
фильтр
Фильтр шлюза Filter делится на Pre и Post, то есть предварительную фильтрацию и постфильтрацию. Они выполняются до того, как конкретный запрос будет перенаправлен внутренней микрослужбе, и выполняются до того, как результат будет возвращен клиенту. Существует около 19 встроенных фильтров GatewayFilters, таких как:
- AddRequestHeader GatewayFilter Factory,
- AddRequestParameter GatewayFilter Factory ,
- AddResponseHeader GatewayFilter Factory
Примеров не так уж много, и пользоваться им относительно просто. Давайте сосредоточимся на том, как настроить фильтр:
- Глобальный фильтр: глобальный фильтр действует для всех маршрутов, и все не нужно настраивать в файле конфигурации.В основном он реализует интерфейсы GlobalFilter и Ordered и регистрирует фильтр в контейнере Spring.
@Service
@Slf4j
public class AllDefineFilter implements GlobalFilter,Ordered{
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("[pre]-Enter AllDefineFilter");
return chain.filter(exchange).then(Mono.fromRunnable(()->{
log.info("[post]-Return Result");
}));
}
@Override
public int getOrder() {
return 0;
}
}
- Локальный фильтр: необходимо настроить в конфигурационном файле, если он настроен, фильтр вступит в силу. Он в основном реализует интерфейсы GatewayFilter и Ordered, и регистрирует его в контейнере spring через подкласс AbstractGatewayFilterFactory.Конечно, он также может напрямую наследовать AbstractGatewayFilterFactory, писать в него логику фильтра и читать внешние данные из конфигурационного файла.
@Component
@Slf4j
public class UserDefineGatewayFilter extends AbstractGatewayFilterFactory<UserDefineGatewayFilter.GpConfig>{
public UserDefineGatewayFilter(){
super(GpConfig.class);
}
@Override
public GatewayFilter apply(GpConfig config) {
return ((exchange, chain) -> {
log.info("[Pre] Filter Request,name:"+config.getName());
return chain.filter(exchange).then(Mono.fromRunnable(()->{
log.info("[Post] Response Filter");
}));
});
}
public static class UserConfig{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
На что здесь обратить внимание:
- Имя класса должно заканчиваться на GatewayFiterFactory, поскольку по умолчанию имя фильтра будет использовать префикс пользовательского класса. Здесь name=UserDefine, которое является значением имени в фильтрах в yml.
- В методе применения включены фильтры Pre и Post. В методе then выполняется постобработка после завершения выполнения запроса.
- UserConfig — это класс конфигурации только с одним именем свойства в этом классе. Это свойство можно использовать в файлах ym.
- Этот класс необходимо загрузить в контейнер Spring IoC, который реализован с помощью аннотации @Component.
На самом деле, весь Spring Cloud Gateway хорошо интегрирован с Spring Cloud Alibaba. Он может быть интегрирован с nacos и может быть интегрирован с Sentinel для ограничения тока. Мы объясним это отдельно на этом более позднем этапе.