Статьи

Портативная облачная HTTP-сессия

Весной ходит интересная линия. Он обеспечивает большую ценность независимо от того, где вы его запускаете, и — поскольку он построен на уровне внедрения зависимостей — он предлагает естественную часть косвенности между нижележащим слоем и приложениями, которые на нем работают. Эта косвенность способствует переносимости кода посредством разделения: код вашего приложения не знает, где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 , проделал потрясающую работу, рассказав о некоторых других случаях использования в документации и других постах в блоге, поэтому я просто расскажу здесь:

    Мне посчастливилось провести вебинар на весенней сессии на прошлой неделе. Роб дал мне 411 на некоторые вещи, которые могут быть в будущих выпусках:

    • контроль параллельного сеанса («вывести меня из других учетных записей»)
    • Поддержка проверки заявок Spring Batch и Spring Integration
    • поддержка управления учетными записями — оптимизированное постоянство (помимо сериализации Java),
    • более умные, инъецируемые bean-компоненты (в отличие от bean-компонентов, которые выставляются как общеизвестные атрибуты запроса, но в противном случае могут быть доступны как аргументы Spring MVC или тому подобное)

    Спасибо, Роб, за всю замечательную информацию.