web-dev-qa-db-fra.com

Spring webflux et lecture de la base de données

Spring 5 introduit le style de programmation réactif pour les API de repos avec webflux . Je suis moi-même assez nouveau dans ce domaine et je me demandais si le fait d'appuyer des appels synchrones vers une base de données dans Flux ou Mono est logique au niveau de la préforme? Si oui, est-ce la façon de procéder:

@RestController
public class HomeController {

    private MeasurementRepository repository;

    public HomeController(MeasurementRepository repository){
        this.repository = repository;
    }

    @GetMapping(value = "/v1/measurements")
    public Flux<Measurement> getMeasurements() {
        return Flux.fromIterable(repository.findByFromDateGreaterThanEqual(new Date(1486980000L)));
    }

}

Existe-t-il quelque chose comme un CrudRepository asynchrone? Je ne l'ai pas trouvé.

23
Lukasz

Une option consisterait à utiliser des clients SQL alternatifs totalement non bloquants. Quelques exemples: https://github.com/mauricio/postgresql-async ou https://github.com/finagle/roc . Bien sûr, aucun de ces pilotes n'est encore officiellement pris en charge par les fournisseurs de bases de données. De plus, la fonctionnalité est beaucoup moins attrayante que les abstractions basées sur JDBC matures telles que Hibernate ou jOOQ.

L'idée alternative m'est venue du monde Scala. L'idée est de répartir les appels bloquants dans ThreadPool isolé pour ne pas mélanger les appels bloquants et non bloquants ensemble. Cela nous permettra de contrôler le nombre total de threads et permettra au CPU d'effectuer des tâches non bloquantes dans le contexte d'exécution principal avec quelques optimisations potentielles. En supposant que nous avons une implémentation basée sur JDBC telle que Spring Data JPA qui est en effet bloquante, nous pouvons rendre son exécution asynchrone et la distribuer sur le thread dédié bassin.

@RestController
public class HomeController {

    private final MeasurementRepository repository;
    private final Scheduler scheduler;

    public HomeController(MeasurementRepository repository, @Qualifier("jdbcScheduler") Scheduler scheduler) {
        this.repository = repository;
        this.scheduler = scheduler;
    }

    @GetMapping(value = "/v1/measurements")
    public Flux<Measurement> getMeasurements() {
        return Mono.fromCallable(() -> repository.findByFromDateGreaterThanEqual(new Date(1486980000L))).publishOn(scheduler);
    }

}

Notre planificateur pour JDBC doit être configuré en utilisant un pool de threads dédié avec un nombre de tailles égal au nombre de connexions.

@Configuration
public class SchedulerConfiguration {
    private final Integer connectionPoolSize;

    public SchedulerConfiguration(@Value("${spring.datasource.maximum-pool-size}") Integer connectionPoolSize) {
        this.connectionPoolSize = connectionPoolSize;
    }

    @Bean
    public Scheduler jdbcScheduler() {
        return Schedulers.fromExecutor(Executors.newFixedThreadPool(connectionPoolSize));
    }

}

Cependant, cette approche présente des difficultés. Le principal est la gestion des transactions. Dans JDBC, les transactions ne sont possibles que dans une seule connexion Java.sql.Connection. Pour effectuer plusieurs opérations en une seule transaction, ils doivent partager une connexion. Si nous voulons faire des calculs entre eux, nous devons garder la connexion. Ce n'est pas très efficace, car nous gardons un nombre limité de connexions inactives tout en effectuant des calculs entre les deux.

Cette idée d'un wrapper JDBC asynchrone n'est pas nouvelle et est déjà implémentée dans Scala bibliothèque Slick 3. Enfin, JDBC non bloquant peut apparaître sur le Java Comme cela a été annoncé lors de JavaOne en septembre 2016, et il est possible que nous le voyions dans Java 10.

27
Grygoriy Gonchar

Sur cette base blog vous devez réécrire votre extrait de la manière suivante

@GetMapping(value = "/v1/measurements")
public Flux<Measurement> getMeasurements() {
    return Flux.defer(() -> Flux.fromIterable(repository.findByFromDateGreaterThanEqual(new Date(1486980000L))))
           .subscribeOn(Schedulers.elastic());
}
7
Dmytro Boichenko

Les données de Spring prennent en charge l'interface de référentiel réactif pour Mongo et Cassandra.

Interface réactive de données Spring MongoDb

Spring Data MongoDB fournit un support de référentiel réactif avec les types réactifs Project Reactor et RxJava 1. L'API réactive prend en charge la conversion de type réactif entre les types réactifs.

public interface ReactivePersonRepository extends ReactiveCrudRepository<Person, String> {

    Flux<Person> findByLastname(String lastname);

    @Query("{ 'firstname': ?0, 'lastname': ?1}")
    Mono<Person> findByFirstnameAndLastname(String firstname, String lastname);

    // Accept parameter inside a reactive type for deferred execution
    Flux<Person> findByLastname(Mono<String> lastname);

    Mono<Person> findByFirstnameAndLastname(Mono<String> firstname, String lastname);

    @InfiniteStream // Use a tailable cursor
    Flux<Person> findWithTailableCursorBy();

}

public interface RxJava1PersonRepository extends RxJava1CrudRepository<Person, String> {

    Observable<Person> findByLastname(String lastname);

    @Query("{ 'firstname': ?0, 'lastname': ?1}")
    Single<Person> findByFirstnameAndLastname(String firstname, String lastname);

    // Accept parameter inside a reactive type for deferred execution
    Observable<Person> findByLastname(Single<String> lastname);

    Single<Person> findByFirstnameAndLastname(Single<String> firstname, String lastname);

    @InfiniteStream // Use a tailable cursor
    Observable<Person> findWithTailableCursorBy();
}
5
yousafsajjad

L'obtention d'un flux ou d'un mono ne signifie pas nécessairement qu'il s'exécutera dans un thread dédié. Au lieu de cela, la plupart des opérateurs continuent de travailler dans le thread sur lequel l'opérateur précédent s'est exécuté. Sauf indication contraire, l'opérateur le plus haut (la source) s'exécute lui-même sur le thread dans lequel l'appel subscribe () a été effectué.

Si vous avez des API de persistance de blocage (JPA, JDBC) ou des API de réseau à utiliser, Spring MVC est au moins le meilleur choix pour les architectures courantes. Il est techniquement possible avec Reactor et RxJava d'effectuer des appels de blocage sur un thread séparé, mais vous ne tireriez pas le meilleur parti d'une pile Web non bloquante.

Alors ... Comment encapsuler un appel synchrone et bloquant?

Utilisez Callable pour différer l'exécution. Et vous devez utiliser Schedulers.elastic car il crée un thread dédié pour attendre la ressource bloquante sans bloquer une autre ressource.

  • Schedulers.immediate (): thread actuel.
  • Schedulers.single (): un seul thread réutilisable.
  • Schedulers.newSingle (): Un thread dédié par appel.
  • Schedulers.elastic (): un pool de threads élastiques. Il crée de nouveaux pools de travail selon les besoins et réutilise ceux qui sont inactifs. C'est un bon choix pour le travail de blocage d'E/S par exemple.
  • Schedulers.parallel (): un pool fixe de travailleurs qui est réglé pour un travail parallèle.

exemple:

Mono.fromCallable(() -> blockingRepository.save())
        .subscribeOn(Schedulers.elastic());
3
kkd927