Статьи

Микросервисы с пружиной

Первоначально написано Полом Чепменом для весеннего блога 

Микросервисы позволяют создавать большие системы из нескольких взаимодействующих компонентов. Это делает на уровне процессов то, что Spring всегда делал на уровне компонентов: слабосвязанные процессы вместо слабосвязанных компонентов.

Приложение для покупок

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

Неизбежно существует ряд движущихся частей, которые необходимо настроить и настроить для построения такой системы. Как заставить их работать вместе, не очевидно — вам нужно хорошо ознакомиться с Spring Boot, поскольку Spring Cloud активно его использует, требуется несколько проектов Netflix или других OSS и, конечно, есть некоторая «магия» конфигурации Spring!

Демо-приложение

В этой статье я попытаюсь прояснить, как все работает, создавая простейшую возможную систему шаг за шагом. Поэтому я буду реализовывать только небольшую часть большой системы — службу учетных записей пользователей.

Web-приложения будут делать запросы на Счет-Сервис microservice с использованием RESTful API. Нам также необходимо добавить службу обнаружения — чтобы другие процессы могли находить друг друга.

Код для этого приложения находится здесь: https://github.com/paulc4/microservices-demo .

Продолжение 1: Другие ресурсы

В этой статье обсуждается только минимальная система. Для получения дополнительной информации вы можете прочитать статью в блоге Джоша Лонга « Регистрация и обнаружение микросервисов с помощью Spring Cloud» и «Eureka» от Netflix, в которой рассказывается о работе полной системы микросервисов в Cloud Foundry.

Проекты Spring Cloud находятся здесь .

Follow Up 2: SpringOne 2GX 2015

Забронируйте свое место в SpringOne2GX в Вашингтоне, округ Колумбия, в ближайшее время — просто лучшая возможность узнать из первых рук обо всем, что происходит, и предоставить прямой отзыв. В приложениях Spring Cloud и Cloud Native будет целый трек.

 

ОК, начнем …

Служба регистрации

Когда у вас есть несколько процессов, работающих вместе, они должны найти друг друга. Если вы когда-либо использовали механизм RMI в Java, вы можете вспомнить, что он опирался на центральный реестр, чтобы процессы RMI могли находить друг друга. Микросервисы имеют такое же требование.

Разработчики в Netflix столкнулись с этой проблемой при создании своих систем и создали сервер регистрации под названием Eureka («Я нашел это» на греческом языке). К счастью для нас, они сделали свой сервер обнаружения открытым исходным кодом, и Spring включил его в Spring Cloud, что еще больше упростило запуск сервера Eureka. Вот полное приложение:

@SpringBootApplication
@EnableEurekaServer
public class ServiceRegistrationServer {

  public static void main(String[] args) {
    // Tell Boot to look for registration-server.yml
    System.setProperty("spring.config.name", "registration-server");
    SpringApplication.run(ServiceRegistrationServer.class, args);
  }
}

Это действительно так просто!

Spring Cloud построен на основе Spring Boot и использует POM для родителей и начинающих. Важными частями POM являются:

    <parent>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-parent</artifactId>
        <version>Angel.SR3</version>  <!-- Name of release train -->
    </parent>
    <dependencies>
        <dependency>
            <!-- Setup Spring Boot -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <!-- Setup Spring MVC & REST, use Embedded Tomcat -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <!-- Spring Cloud starter -->
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter</artifactId>
        </dependency>

        <dependency>
            <!-- Eureka for service registration -->
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
        </dependency>
    </dependencies>

Примечание: Angel.SR3 — это текущий «выпуск релизов» — набор скоординированных релизов — см. Примечание на домашней странице Spring Cloud (прокрутите вниз до второго раздела).

По умолчанию приложения Spring Boot ищут файл application.propertiesили application.ymlдля конфигурации. Установив spring.config.nameсвойство, мы можем указать Spring Boot искать другой файл — это полезно, если у вас есть несколько приложений Spring Boot в одном проекте — как я сделаю в ближайшее время.

Это приложение ищет registration-server.propertiesили registration-server.yml. Вот соответствующая конфигурация от registration-server.yml:

# Configure this Discovery Server
eureka:
  instance:
    hostname: localhost
  client:  # Not a client, don't register with yourself
    registerWithEureka: false
    fetchRegistry: false

server:
  port: 1111   # HTTP (Tomcat) port

По умолчанию Eureka работает на порту 8761, но здесь мы будем использовать порт 1111. Также, включив регистрационный код в мой процесс, я могу быть сервером или клиентом. Конфигурация указывает, что я не являюсь клиентом, и останавливает процесс сервера, пытающийся зарегистрироваться сам.

Используя Консул

Spring Cloud также поддерживает Consul в качестве альтернативы Eureka. Вы запускаете Консул-агент (его сервер регистрации) с помощью скрипта, а затем клиенты используют его для поиска своих микросервисов. Подробности смотрите в этой статье блога или домашней странице проекта .

Попробуйте запустить сервер регистрации (см. Справку по запуску приложения). Вы можете открыть панель инструментов Eureka здесь: http: // localhost: 1111, и раздел, показывающий Приложения, будет пустым.

С этого момента мы будем ссылаться на сервер обнаружения, поскольку это может быть Eureka или Consul (см. Боковую панель).

Создание микросервиса: Account-Service

Микросервис — это самостоятельный процесс, который обрабатывает четко определенные требования.

Бобы против процессов

При настройке приложений с помощью Spring мы подчеркиваем «Свободное соединение» и «Tight Cohesion». Это не новые концепции (Ларри Константину приписывают их первое определение в конце 1960-х — ссылка ), но теперь мы применяем их, а не к взаимодействующим компонентам (Spring Beans), но для взаимодействующих процессов.

В этом примере у меня есть простой микросервис управления учетными записями, который использует Spring Data для реализации JPA AccountRepositoryи Spring REST для предоставления RESTful-интерфейса к информации об учетной записи. В большинстве случаев это простое приложение Spring Boot.

Что делает его особенным, так это то, что он регистрируется на сервере обнаружения при запуске. Вот класс запуска Spring Boot:

@EnableAutoConfiguration
@EnableDiscoveryClient
@Import(AccountsWebApplication.class)
public class AccountsServer {

    @Autowired
    AccountRepository accountRepository;

    public static void main(String[] args) {
        // Will configure using accounts-server.yml
        System.setProperty("spring.config.name", "accounts-server");

        SpringApplication.run(AccountsServer.class, args);
    }
}

Аннотации делают работу:

  1. @EnableAutoConfiguration — определяет это как приложение Spring Boot.
  2. @EnableDiscoveryClient— это позволяет регистрировать и обнаруживать услуги. В этом случае этот процесс регистрируется в службе discovery-server с использованием имени приложения (см. Ниже).
  3. @Import(AccountsWebApplication.class) — этот класс конфигурации Java устанавливает все остальное (см. более подробно).

То, что делает это микросервисом, — это регистрация через discovery-сервер через, @EnableDiscoveryClientи его конфигурация YML завершает настройку:

# Spring properties
spring:
  application:
     name: accounts-service

# Discovery Server Access
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:1111/eureka/

# HTTP Server
server:
  port: 2222   # HTTP (Tomcat) port

Обратите внимание, что этот файл

  1. Устанавливает имя приложения как accounts-service. Эта служба регистрируется под этим именем и также может быть доступна по этому имени — см. Ниже.
  2. Указывает настраиваемый порт для прослушивания (2222). Все мои процессы используют Tomcat, они не могут все слушать порт 8080.
  3. URL-адрес процесса обслуживания Eureka — из предыдущего раздела.

Eureka Dashboard

Запустите приложение AccountsService сейчас и дайте ему завершить инициализацию. Обновите панель мониторинга http: // localhost: 1111, и вы должны увидеть СЧЕТ-СЕРВИС, перечисленный в разделе Приложения. Иногда регистрация может занять 10-20 секунд, поэтому наберитесь терпения — проверьте вывод журнала в RegistrationService

Предупреждение. Не пытайтесь отображать вывод XML с помощью внутреннего средства просмотра веб-страниц Eclipse / STS, потому что это невозможно. Вместо этого используйте ваш любимый веб-браузер.

Для более подробной информации, перейдите сюда: http: // localhost: 1111 / eureka / apps /, и вы должны увидеть что-то вроде этого:

<applications>
    <versions__delta>1</versions__delta>
    <apps__hashcode>UP_1_</apps__hashcode>
    <application>
        <name>ACCOUNTS-SERVICE</name>
        <instance>
            <hostName>autgchapmp1m1.corp.emc.com</hostName>
            <app>ACCOUNTS-SERVICE</app>
            <ipAddr>172.16.84.1</ipAddr><status>UP</status>
            <overriddenstatus>UNKNOWN</overriddenstatus>
            <port enabled="true">3344</port>
            <securePort enabled="false">443</securePort>
            ...
        </instance>
    </application>
</applications>

В качестве альтернативы перейдите на http: // localhost: 1111 / eureka / apps / ACCOUNTS-SERVICE и посмотрите только детали для AccountsService — если он не зарегистрирован, вы получите 404.

Доступ к микросервису: веб-сервис

Spring использует RestTemplateкласс RESTful . Это позволяет отправлять HTTP-запросы на сервер RESTful и получать данные в различных форматах, таких как JSON и XML.

Примечание. Микросервис Accounts предоставляет интерфейс RESTful через HTTP, но можно использовать любой подходящий протокол. Обмен сообщениями с использованием AMQP или JMS является очевидной альтернативой.

Какие форматы могут быть использованы, зависит от наличия классов маршалинга на пути к классам — например, JAXB всегда определяется, поскольку это стандартная часть Java. JSON поддерживается, если в пути к классам присутствуют фляги Джексона.

Микросервисный (обнаружительный) клиент может использовать a, RestTemplateи Spring автоматически настроит его для поддержки микросервиса (подробнее об этом через минуту).

Инкапсуляция микросервисного доступа

Вот часть WebAccountServiceмоего клиентского приложения:

@Service
public class WebAccountsService {

    @Autowired        // Created automatically by Spring Cloud
    @LoadBalanced
    protected RestTemplate restTemplate; 

    protected String serviceUrl;

    public WebAccountsService(String serviceUrl) {
        this.serviceUrl = serviceUrl.startsWith("http") ?
               serviceUrl : "http://" + serviceUrl;
    }

    public Account getByNumber(String accountNumber) {
        Account account = restTemplate.getForObject(serviceUrl
                + "/acounts/{number}", Account.class, accountNumber);

        if (account == null)
            throw new AccountNotFoundException(accountNumber);
        else
            return account;
    }
    ...
}

Обратите внимание, что my WebAccountService— это просто оболочка для RestTemplate, извлекающая данные из микросервиса. Интересные части serviceUrlи RestTemplate.

Доступ к микросервису

serviceUrlОбеспечивается основной программе на WebAccountControllerкоторый , в свою очередь , передает ее к WebAccountService(как показано выше):

@SpringBootApplication
@EnableDiscoveryClient
@ComponentScan(useDefaultFilters=false)  // Disable component scanner
public class WebServer {

    public static void main(String[] args) {
        // Will configure using web-server.yml
        System.setProperty("spring.config.name", "web-server");
        SpringApplication.run(WebServer.class, args);
    }

    @Bean
    public WebAccountsController accountsController() {
         // 1. Value should not be hard-coded, just to keep things simple
         //        in this example.
         // 2. Case insensitive: could also use: http://accounts-service
         return new WebAccountsController
                       ("http://ACCOUNTS-SERVICE");  // serviceUrl
    }
}

Несколько замечаний:

  1. WebControllerТипичный Spring MVC вид на основе контроллера возвращения HTML. Приложение использует Thymeleaf в качестве технологии просмотра (для генерации динамического HTML)
  2. WebServerтакже является, @EnableDiscoveryClientно в этом случае, а также регистрируя себя на сервере обнаружения (который не является необходимым, поскольку он не предлагает своих собственных услуг), он использует Eureka для поиска службы учетной записи.
  3. Установка компонента-сканера по умолчанию, унаследованная от Spring Boot, ищет @Componentклассы, и в этом случае находит my WebAccountControllerи пытается его создать. Однако я хочу создать его сам, поэтому отключаю сканер следующим образом @ComponentScan(useDefaultFilters=false).
  4. Сервис URL я передаю в WebAccountControllerэтом имени служба используется для регистрации себя с открытием-сервером по умолчанию — это то же самое , как spring.application.nameдля процесса , который account-service— см account-service.ymlвыше. Использование верхнего регистра не обязательно, но это помогает подчеркнуть, что ACCOUNTS-SERVICE является логическим хостом (который будет получен с помощью обнаружения), а не фактическим хостом.

Сбалансированный RestTemplate

Spring Cloud автоматически настроил RestTemplate для использования пользовательского интерфейса, HttpRequestClientкоторый использует ленту Netflix для поиска микро-службы. Лента также является балансировщиком нагрузки, поэтому, если у вас есть несколько доступных экземпляров службы, она выбирает один для вас. (Ни Eureka, ни Consul самостоятельно не выполняют балансировку нагрузки, поэтому вместо этого мы используем Ribbon).

Если вы посмотрите в RibbonClientHttpRequestFactory, вы увидите этот код:

    String serviceId = originalUri.getHost();
    ServiceInstance instance =
             loadBalancer.choose(serviceId);  // loadBalancer uses Ribbon
    ... if instance non-null (service exists) ...
    URI uri = loadBalancer.reconstructURI(instance, originalUri);

Он loadBalancerберет логическое имя службы (зарегистрированное на сервере обнаружения ) и преобразует его в фактическое имя хоста выбранного микросервиса.

RestTemplateЭкземпляр потокобезопасен и может быть использован для доступа к любому числу услуг в различных частях приложения (например, я мог бы иметь CustomerServiceоберточный тот же RestTemplateэкземпляр с доступом к microservice данных клиентов).

конфигурация

Ниже соответствующая конфигурация от web-server.yml. Он используется для:

  1. Установите имя приложения
  2. Определите URL для доступа к серверу обнаружения
  3. Установите порт Tomcat на 3333
# Spring Properties
spring:
  application:
     name: web-service

# Discovery Server Access
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:1111/eureka/

# HTTP Server
server:
  port: 3333   # HTTP (Tomcat) port

Как запустить демо

Небольшая демонстрация этой системы находится по адресу http://github.com/paulc4/microservices-demo . Клонируйте его и загрузите в свою любимую IDE или используйте maven напрямую. Предложения по запуску демо-версии включены в README на домашней странице проекта.

Дополнительные заметки

Некоторые заметки об использовании Spring Boot этими приложениями. Если вы не знакомы с Spring Boot, это объясняет некоторую «магию»!

Просмотр шаблонизаторов

Панель инструментов Eureka (внутри RegistrationServer) реализована с использованием шаблонов FreeMarker, но два других приложения используют Thymeleaf. Чтобы убедиться, что каждый из них использует правильный механизм просмотра, в каждом файле YML есть дополнительная конфигурация.

Это в конце, registration-server.ymlчтобы отключить Thymeleaf.

...
# Discovery Server Dashboard uses FreeMarker.  Don't want Thymeleaf templates
spring:
  thymeleaf:
    enabled: false     # Disable Thymeleaf spring:

Так как оба AccountServiceи WebServiceиспользуют тимелеф, нам также нужно указать каждому на свои шаблоны. Вот часть account-server.yml:

# Spring properties
spring:
  application:
     name: accounts-service  # Service registers under this name
  freemarker:
    enabled: false      # Ignore Eureka dashboard FreeMarker templates
  thymeleaf:
    cache: false        # Allow Thymeleaf templates to be reloaded at runtime
    prefix: classpath:/accounts-server/templates/
                        # Template location for this application only
...

web-server.yml похож, но его шаблоны определяются

   prefix: classpath:/web-server/templates/

Обратите внимание на / в конце каждого spring.thymeleaf.prefixпути к классам — это очень важно .

Выполнение командной строки

Jar компилируется для автоматического запуска io.pivotal.microservices.services.Mainпри вызове из командной строки — см. Main.java .

Параметр Spring Boot для установки start-classможно увидеть в POM :

    <properties>
        <!-- Stand-alone RESTFul application for testing only -->
        <start-class>io.pivotal.microservices.services.Main</start-class>
    </properties>

Настройка AccountsWebApplication

@SpringBootApplication
@EntityScan("io.pivotal.microservices.accounts")
@EnableJpaRepositories("io.pivotal.microservices.accounts")
@PropertySource("classpath:db-config.properties")
public class AccountsWebApplication {
...
}

Это основной класс конфигурации для AccountService и является классическим приложением Spring Boot, использующим Spring Data. Аннотации выполняют большую часть работы:

  1. @SpringBootApplication— определяет это как приложение Spring Boot. Эта удобная аннотация объединяет @EnableAutoConfiguration, @Configurationи @ComponentScan(что, по умолчанию, заставляет Spring искать пакет, содержащий этот класс, и его подпакеты, для компонентов — потенциальных Spring Beans: AccountControllerи AccountRepository).
  2. @EntityScan("io.pivotal.microservices.accounts")— потому что я использую JPA, мне нужно указать, где @Entityнаходятся классы. Обычно это опция, которую вы указываете в JPA persistence.xmlили при создании LocalContainerEntityManagerFactoryBean. Spring Boot создаст этот фабричный компонент для меня, потому что spring-boot-starter-data-jpaзависимость находится на пути к классам. Таким образом, альтернативный способ указать, где найти @Entityклассы — использовать @EntityScan. Это найдет Account.
  3. @EnableJpaRepositories("io.pivotal.microservices.accounts")— искать классы, расширяющие Repositoryинтерфейс маркеров Spring Data, и автоматически реализовывать их с помощью JPA — см. Spring Data JPA .
  4. @PropertySource("classpath:db-config.properties")— свойства, чтобы настроить мой DataSource— см. db-config.properties .

Обратите внимание, что AccountsWebApplication может быть запущен как отдельное приложение само по себе, что я нашел полезным для тестирования. Он слушает порт Tomcat по умолчанию: 8080, поэтому домашней страницей является http: // localhost: 8080 .

Настройка свойств

Как уже упоминалось выше, приложения Spring Boot ищут application.propertiesили application.ymlнастраивают сами. Поскольку все три сервера, используемые в этом приложении, находятся в одном проекте, они будут автоматически использовать одну и ту же конфигурацию.

Чтобы избежать этого, каждый указывает альтернативный файл, устанавливая spring.config.nameсвойство.

Например, вот часть WebServer.java.

public static void main(String[] args) {
  // Tell server to look for web-server.properties or web-server.yml
  System.setProperty("spring.config.name", "web-server");
  SpringApplication.run(WebServer.class, args);
}

Во время выполнения приложение найдет и использует web-server.ymlв src/main/resources.

логирование

Spring Boot по умолчанию настраивает ведение журнала уровня INFO для Spring. Так как нам нужно проверять журналы на наличие работоспособности наших микросервисов, я поднял уровень до WARN, чтобы уменьшить количество журналирования.

Для этого необходимо указать уровень ведения журнала в каждом из xxxx-server.ymlфайлов конфигурации. Обычно это лучшее место для их определения, поскольку в файлах свойств нельзя указывать свойства ведения журнала (ведение журнала уже было инициализировано до обработки директив @PropertySource). Об этом есть примечание в руководстве по Spring Boot, но его легко пропустить.

Вместо того, чтобы дублировать конфигурацию ведения журнала в каждом файле YAML, я вместо этого решил поместить ее в файл конфигурации возврата, так как Spring Boot использует logback — см. Src / main / resources / logback.xml . Все три сервиса будут делиться одинаково logback.xml.