Весной ходит интересная линия. Он обеспечивает большую ценность независимо от того, где вы его запускаете, и — поскольку он построен на уровне внедрения зависимостей — он предлагает естественную часть косвенности между нижележащим слоем и приложениями, которые на нем работают. Эта косвенность способствует переносимости кода посредством разделения: код вашего приложения не знает, гдеjavax.sql.DataSource
(или любой другой) дескриптор, который он использует, исходит от поиска JNDI, переменных среды или простого нового bean-компонента, предоставляемого Spring. Эта развязка и богатый набор инструментов над Spring, поддерживающий всевозможные варианты использования — пакетная обработка, интеграция, потоковая обработка, веб-сервисы, микросервисы, операции, веб-приложения, безопасность и т. Д. — сделали Spring логичным выбором для разработчиков. развертывание в (иногда встраиваемых) веб-контейнерах, таких как Apache Tomcat или Eclipse Jetty, на серверах приложений, таких как WebSphere и WildFly, и в облачные среды выполнения, такие как Google App Engine, Heroku, OpenShift и (мой любимый в наши дни) Cloud Foundry. Эта переносимость также что позволяет легко переносить большинство (разумно написанных!) приложений с серверов приложений в более легкие веб-контейнеры и, в конечном итоге, в облако.
(Stateful) ложка дегтя
Так в чем проблема? Зачем вообще писать этот блог?
Однако не все идеально с приложениями, использующими сеанс HTTP. Масштабирование HTTP сессии , где вещи становятся, — помилование в HTTP терминологии сессии каламбур — липкий . Вы видите, что вашему приложению необходимо масштабировать две вещи для масштабирования сеанса HTTP: соответствие сеанса и репликация сеанса. Сходство с сеансами (или сессионные сеансы ) означает, что запросы к кластерному веб-приложению будут перенаправляться на узел, который первоначально отправил файл cookie сеанса HTTP. Если этот экземпляр приложения следует перевести в автономный режим, то репликация сеанса гарантирует, что соответствующее состояние доступно на другом узле. Клиент может маршрутизироваться там без проблем, сохраняя все представления о состоянии разговора. Это не тосложно настроить репликацию HTTP-сессии в популярных контейнерах. Вот страница о том, как настроить его с помощью Apache Tomcat, и страница о том, как настроить его с помощью Jetty . Типичные стратегии репликации сеансов включают использование многоадресной сети для уведомления других узлов в кластере об изменениях состояния. Сходство с сеансами и репликация сеансов хорошо работают в небольших средах, где у вас всего несколько узлов. Если вы не используете встроенный веб-контейнер, настройка репликации сеанса HTTP — это еще одна вещь, которую необходимо настроить в контейнере, вне контроля приложения.
Все в порядке, облако исправит это, верно?
Можно подумать, что — если ничего другого — такая конфигурация станет проще и более предсказуемой, когда вы перенесете свое приложение в облако, но на самом деле это может быть более болезненным! В большинстве облачных сред, в том числе в веб-сервисах Amazon, нет многоадресных сетей. Даже в высокоуровневых, более ориентированных на приложения средах Platform-as-a-Service, таких как Heroku или Cloud Foundry, репликация сеансов не была супер легкой. Heroku, например, не предлагает сродства сессий и репликации сессий, Это ограничение понятно: помимо ограничений в многоадресной сети, приложения должны — насколько это возможно — минимизировать состояние на стороне сервера. Помните, что Heroku ограничивает ОЗУ вашего приложения 512 МБ! Этого более чем достаточно, если вы не пытаетесь рассматривать запасную оперативную память как базу данных или уровень постоянства! Cloud Foundry, со своей стороны, обслуживает большие сообщества разработчиков и работает локально в различных дата-центрах, поэтому он должен быть немного более практичным. Например, Pivotal Web Services (в которой работает Cloud Foundry ) предлагает 1 ГБ ОЗУ для приложений и обеспечивает сессионную привязку в течение нескольких лет. Он не предлагал репликацию сеансов до конца прошлого года, когда изменение в buildpack позволяет репликацию сеансов для любых .war
веб-приложений на основеразвернут на стандартной конфигурации веб-сервера Apache Tomcat . Однако эта поддержка не использует многоадресную сеть. Вместо этого он использует соглашение о настройке любой связанной службы поддержки Redis для использования со стратегией репликации сеансов контейнера Tomcat. Использование вспомогательного сервиса, такого как Redis, или, возможно, некоторой общей файловой системы, действительно является единственным разумным подходом к репликации сеансов в облаке.
Все эти подходы имеют разные компромиссы:
- некоторые из них относятся к конкретным контейнерам, и это означает, что они не могут легко перемещаться из одной среды в другую.
- они могут вносить дополнительную сложность для операций (если у вас еще есть эта команда!), и это вносит гораздо большее трение между приложением и производством
- они могут использовать многоадресную сеть, которая плохо работает в облачной среде
- они могут полагаться на магию, подобную Java-пакету Cloud Foundry, который знает только о тех, которые
.war
развернуты на автономном Apache Tomcat, а не о встроенных.jar
или других веб-контейнерах, таких как Jetty. - Подразумеваемое ограничение для всех этих других моментов состоит в том, что стратегия постоянства не является подключаемой. Многоадресная передача не для вас? Отлично, используйте Redis. Redis не для вас и хотите использовать Memcache или что-то еще, что не поддерживается? Ой…
Войдите в весеннюю сессию
Spring Session предоставляет очень хорошее решение для всех этих проблем. Это обертка вокруг стандартной абстракции Servlet HTTP Session. Его легко подключить к любому приложению, будь то на основе Spring или нет. Он действует как своего рода прокси перед сеансом HTTP, который перенаправляет запросы в реализацию стратегии. Из коробки есть реализация, которая поддерживает работу с java.util.Map<K,V>
s, а другая — напрямую с Redis. Реализация, в которой сначала используется java.util.Map<K,V>
не очень интересная реализация, но помните, что все ваши любимые распределенные сетки данных (Pivotal GemFire, Hazelcast, Oracle Coherence и т. Д.) Могут дать вам ссылку на Map
реализацию, поддерживаемую сеткой данных. Память.
Реализация, специфичная для Redis, использует некоторые преимущества Redis, если она доступна. Давайте рассмотрим настройку простого приложения Spring Session с использованием Redis. Почему Редис? Потому что это по праву «веб-масштаб» — ознакомьтесь с этим постом о том, как Twitter использует его для масштабирования до 105 ТБ ОЗУ, 39 ММ QPS и более 10 000 экземпляров в блоге о высокой масштабируемости !
Чтобы этот пример работал, я добавил следующие зависимости Maven в простой проект Spring Boot .
org.springframework.boot
:spring-boot-starter-redis
:1.2.0.RELEASE
org.springframework.boot
:spring-boot-starter-web
:1.2.0.RELEASE
org.springframework.session
:spring-session-data-redis
:1.0.0.RELEASE
Вот простой пример приложения:
package demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
import java.util.UUID;
@EnableRedisHttpSession
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@RestController
class HelloRestController {
@RequestMapping("/")
String uid(HttpSession session) {
UUID uid = (UUID) session.getAttribute("uid");
if (uid == null) {
uid = UUID.randomUUID();
}
session.setAttribute("uid", uid);
return uid.toString();
}
}
Прежде чем запускать его, убедитесь, что вы посвятили этому приложению чистую базу данных Redis. Вы можете, например, сбросить текущую базу данных с помощью FLUSHDB
. Spring Boot Redis Starter автоматически подключается к работающей базе данных Redis localhost
. Если вы хотите указать это где-то конкретное, используйте различные spring.redis.*
свойства .
Пример настолько минимален, насколько это возможно: он просто подтверждает, что данные записываются в хранилище данных Redis. Откройте вашу redis-cli
утилиту после взаимодействия с веб-приложением localhost:8080/
в вашем браузере. Первый запрос вызовет уникальный сеанс, который будет использоваться для кэширования uid
значения. Последующие запросы к тому же сеансу браузера будут видеть то же значение. Войдите keys *
в, redis-cli
чтобы увидеть, что было сохранено.
Развертывание в CloudFoundry
Переход к этому облаку может быть немного сложнее. Если вы развертываете это в Cloud Foundry, пакет сборки Cloud Foundry автоматически заменит автоматически настроенный Spring Boot RedisConnectionFactory
на RedisConnectionFactory
тот, который указывает на экземпляр Redis, связанный с приложением. Это работает, если вы работаете в Cloud Foundry, используете правильный buildpack-пакет и RedisConnectionFactory
в вашем приложении их нет.
Я буду использовать Cloud Foundry, manifest.yml
чтобы описать, как это приложение должно выглядеть при развертывании в Cloud Foundry. В этом случае потребуется как минимум служба redis-session
поддержки с именем, которая поддерживает базу данных Redis. Я поместил этот файл в корень моего проекта, рядом с моим Maven pom.xml
. Обратите внимание, что это manifest.yml
вносит переменную окружения SPRING_PROFILES_ACTIVE
, которая активирует cloud
профиль Spring. Мы будем использовать это позже.
---
applications:
- name: connectors
memory: 512M
instances: 1
host: connectors-${random-word}
domain: cfapps.io
path: target/connectors.jar
services:
- redis-session
env:
SPRING_PROFILES_ACTIVE: cloud
DEBUG: "true"
debug: "true"
Вам нужно создать экземпляр Redis перед отправкой приложения. Я использовал следующее заклинание для создания простого экземпляра Redis (называемого redis-session
, мы ссылаемся на него manifest.yml
) в Pivotal Web Services, а затем протолкнул приложение.
cf create-service rediscloud 25mb redis-session
cf push
Я могу развернуть приложение как есть, и в этом примере все должно работать просто с одной связанной службой поддержки и одним компонентом известного типа.
Вы можете использовать коннекторы Spring Cloud PaaS, чтобы быстро настроить и использовать облачную службу поддержки Redis. В этом новом соглашении мы будем использовать профили Spring для явной настройки конфигурации для работы в Cloud Foundry. Добавьте коннекторы Spring Cloud PaaS следующим образом:
org.springframework.cloud
: spring-boot-starter-cloud-connectors
:1.2.0.RELEASE
Это сделает так, что Spring Boot будет автоматически связывать экземпляры каждого связанного типа службы поддержки, о котором он знает. Если есть база данных Redis с идентификатором службы, redis-session
ее можно добавить с помощью обычных квалификаторов Spring, например:
// ..
@Autowired
@Qualifier("redis-session")
RedisConnectionFactory rcf;
Этот подход является самым простым, и это то, что вы получите, если просто добавите начальную зависимость Spring Cloud. Если вы хотите явно настроить службы, отключите стартер Spring Boot, добавив следующее свойство в свой Spring Boot src/main/resources/application.properties
:
spring.cloud.enabled=false
Затем явно используйте PaaS-соединители Spring Cloud. Определение компонента будет активным только тогда, когда активен cloud
профиль Spring. В противном случае включится автоконфигурация Spring Boot (это то, что вы хотите при локальном запуске).
package demo;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.Cloud;
import org.springframework.cloud.CloudFactory;
import org.springframework.cloud.service.common.RedisServiceInfo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Profile;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Field;
import java.util.UUID;
@EnableRedisHttpSession
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Bean
@Profile("cloud")
RedisConnectionFactory redisConnectionFactory() {
CloudFactory cloudFactory = new CloudFactory();
Cloud cloud = cloudFactory.getCloud();
RedisServiceInfo redisServiceInfo = (RedisServiceInfo) cloud.getServiceInfo("redis-session");
return cloud.getServiceConnector(redisServiceInfo.getId(),
RedisConnectionFactory.class, null);
}
}
@RestController
class HelloRestController {
@RequestMapping("/")
String hello(HttpSession session) {
UUID uid = (UUID) session.getAttribute("uid");
if (uid == null) {
uid = UUID.randomUUID();
}
session.setAttribute("uid", uid);
return uid.toString();
}
}
Но подождите, есть больше ..
Цель этого поста состояла в том, чтобы посмотреть, как легко вы можете получить масштабируемые сеансы HTTP для ваших приложений Spring в локальной среде или в облаке. Я не рекомендую вам снова начинать сессию HTTP с графиками страниц JSF! Если вам нужно устаревшее, масштабируемое, эфемерное хранилище для легких бизнес-состояний — таких как токены безопасности — тогда Spring Session может помочь. Поскольку Spring Session находится между вашим приложением и HTTP-сеансом, он может предоставить несколько других полезных абстракций, помимо сервлета HttpSession
. Роб Винч, руководитель Spring Security и Spring Session , проделал потрясающую работу, рассказав о некоторых других случаях использования в документации и других постах в блоге, поэтому я просто расскажу здесь:
- Вы можете легко реализовать функцию «Переключение пользователей» (вроде аккаунтов Google). Проверьте удивительную рецензию в документах для большего .
- Spring Session знает о вашем трафике веб-сокета Spring и будет правильно поддерживать сеанс HTTP. Это позволяет избежать проблем со стандартом веб-сокетов, где нет практического способа увековечить сеанс HTTP от трафика веб-сокетов. Проверьте удивительную рецензию в документах для большего .
- Вы можете тянуть ручки и рычаги для всего, включая механизм, используемый для сопоставления клиентских запросов в состоянии сеанса на стороне сервера: заголовки HTTP ? Печенье? Что-то другое? Подход с заголовками чертовски крут и может дать вам своего рода токен доступа OAuth для бедного человека, если он используется вместе со Spring Security для защиты служб REST. Проверьте удивительную рецензию в документах для большего .
Мне посчастливилось провести вебинар на весенней сессии на прошлой неделе. Роб дал мне 411 на некоторые вещи, которые могут быть в будущих выпусках:
- контроль параллельного сеанса («вывести меня из других учетных записей»)
- Поддержка проверки заявок Spring Batch и Spring Integration
- поддержка управления учетными записями — оптимизированное постоянство (помимо сериализации Java),
- более умные, инъецируемые bean-компоненты (в отличие от bean-компонентов, которые выставляются как общеизвестные атрибуты запроса, но в противном случае могут быть доступны как аргументы Spring MVC или тому подобное)
Спасибо, Роб, за всю замечательную информацию.