[JAVA] Implementierung der asynchronen Verarbeitung für einen einzelnen Mandanten in Tomcat

Implementierung der asynchronen Verarbeitung für einen einzelnen Mandanten in Tomcat

Dieser Artikel ist Teil von "Implementieren der asynchronen Verarbeitung in Tomcat".

Einführung

Hier implementieren wir eine Tomcat-Anwendung, die den Fluss asynchroner Aufgaben mithilfe eines Thread-Pools in einem einzelnen Mandanten begrenzt.

Software zur Funktionsprüfung

Wir haben den Betrieb in den folgenden Software- und Bibliotheksumgebungen bestätigt. Weitere abhängige Bibliotheken finden Sie in der später beschriebenen pom.xml.

Pom.xml erstellen

Erstellen Sie ein Maven-Projekt mit Eclipse oder Maven und fügen Sie den unten gezeigten Inhalt zu pom.xml hinzu. Der Spring-Boot-Starter-Aktuator für Abhängigkeiten ist eine Bibliothek für die Tomcat-Überwachung, stellt jedoch ein Sicherheitsrisiko in der Produktionsumgebung dar. Entfernen Sie ihn daher.

<project xmlns=...>
    :
  <build>
      :
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <version>2.1.4.RELEASE</version>
      </plugin>
    </plugins>
      :
  </build>

  <dependencies>
      :
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <version>1.5.22.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
      <version>1.5.22.RELEASE</version>
    </dependency>
      :
  </dependencies>
</project>

** Abbildung 3.2 Ergänzungen zu pom.xml **

Konfigurationsdatei erstellen

Erstellen Sie "application.properties", eine Einstellungsdatei von Spring Boot.

#Tomcat-Überwachung
#Einstellungen für die Betriebsüberwachung, z. B. Überwachen und Herunterfahren des Tomcat-Containers
#Entfernen Sie es, da es ein Sicherheitsrisiko in der Produktionsumgebung darstellt
endpoints.shutdown.enabled=true
management.security.enabled=false
management.endpoints.web.exposure.include=**

#Thread-Pool
#Geben Sie die Anzahl der akzeptierten asynchronen Aufgaben und die Anzahl der gleichzeitigen Ausführungen an
#Fragt so viele Aufgaben wie queueCapacity ab und wird im Hintergrund so viele wie maxPoolSize ausgeführt
#Maximum "maxPoolSize+Akzeptiert so viele asynchrone Aufgaben wie "queueCapacity" und lehnt ab, wenn diese Anzahl überschritten wird(Nicht akzeptiert)
#  corePoolSize  =Anfängliche Thread-Nummer
#  queueCapacity =Nummer in der Warteschlange, wenn corePoolSize voll ist
#  maxPoolSize   =Maximale Anzahl von Threads, wenn queueCapacity überschritten wird
threadPool.corePoolSize=1
threadPool.queueCapacity=2
threadPool.maxPoolSize=2
threadPool.keepAliveSeconds=1

#Geben Sie die Ausgabeebene des Protokollierungsprotokolls an
#Geben Sie den Ausgabepegel der Protokollierung an
# https://docs.oracle.com/javase/jp/8/docs/api/java/util/logging/Level.html
logging.level.root=INFO
logging.level.org.springframework.web=INFO
logging.level.web01=INFO

** Abbildung 3.3 Inhalt von application.properties **

--Verweise

Erstellen einer POJO-Klasse für Thread-Pool-Einstellungen

Erstellen Sie eine POJO-Klasse, die die Thread-Pool-Einstellungen enthält. Das Spring Framework fügt dieser Klasse automatisch Einstellungen ab "threadPool" in application.properties hinzu.

@Component
@ConfigurationProperties(prefix = "threadPool")
public class ThreadPoolSettings {
	private String corePoolSize;
	private String queueCapacity;
	private String maxPoolSize;
	private String keepAliveSeconds;

	public void setCorePoolSize(String corePoolSize)         {this.corePoolSize = corePoolSize;}
	public void setQueueCapacity(String queueCapacity)       {this.queueCapacity = queueCapacity;}
	public void setMaxPoolSize(String maxPoolSize)           {this.maxPoolSize = maxPoolSize;}
	public void setKeepAliveSeconds(String keepAliveSeconds) {this.keepAliveSeconds = keepAliveSeconds;}

	public int getCorePoolSize()     {return Integer.valueOf(corePoolSize);}
	public int getQueueCapacity()    {return Integer.valueOf(queueCapacity);}
	public int getMaxPoolSize()      {return Integer.valueOf(maxPoolSize);}
	public int getKeepAliveSeconds() {return Integer.valueOf(keepAliveSeconds);}
}

** Abbildung 3.4 Inhalt der ThreadPoolSettings-Klasse **

Erstellen einer Tomcat-Startklasse

Erstellen Sie eine Anwendungsklasse, die den integrierten Tomcat-Container startet.

@SpringBootApplication wird automatisch zum Spring Framework hinzugefügt @Configuration und [@Component](https://docs.spring.io/ Sie werden angewiesen, Klassen wie spring-framework / docs / current / javadoc-api / org / springframework / stereotype / Component.html zu scannen. @Repository, [@Service](https: //docs.spring .io / spring-framework / docs / current / javadoc-api / org / springframework / stereotype / Service.html), @Controller -api / org / springframework / stereotype / Controller.html) ist auch @Component ), Also werden diese auch erkannt. Beachten Sie jedoch, dass das Scan-Ziel standardmäßig auf dasselbe Paket oder dieselben Pakete darunter beschränkt ist.

@SpringBootApplication
public class Application {
    public static void main(final String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

** Abbildung 3.5 Inhalt der Anwendungsklasse **

--Verweise

@Configuration Erstellen einer Klasse

Erstellen Sie eine SampleConfig-Klasse, die die Anwendung beim Start von Tomcat initialisiert.

Deklarieren Sie diese Klasse als Standardklasse @Configuration. /Configuration.html) und @EnableAsync, um asynchrone Aufrufe zu aktivieren Es gibt EnableAsync.html). @Bean in taskExecutor1 () ist ein Bean in Spring Framework. Ist zu registrieren. Standardmäßig entspricht der Bean-Name dem Methodennamen, wobei "taskExecutor" der Bean-Name ist.

taskExecutor1 () erstellt einen ThreadPoolTaskExecutor, einen Thread-Pool mit Warteschlangen. Durch Ändern des oben genannten Einstellungswerts von [application.properties](#Erstellung der Einstellungsdatei) entsprechend der Anzahl der Kerne und der Speicherkapazität der Produktionsumgebung kann die Kapazität der Warteschlange und des Thread-Pools angepasst werden.

Außerdem sollten die vom Thread-Pool gehaltenen Threads "** Nicht-Dämonen **" sein, damit laufende Threads nicht beendet werden, wenn Tomcat beendet wird. ThreadPoolTaskExecutor ist standardmäßig nicht damon, wird hier jedoch explizit angegeben. Durch das Herunterfahren des Thread-Pools werden jedoch alle Aufgaben in der Warteschlange zerstört. Wenn Sie möchten, dass die Aufgaben, die in der Warteschlange stecken geblieben sind, beim Neustart der Tomcat-Anwendung ausgeführt werden, ist eine zusätzliche Implementierung erforderlich, die ich hier jedoch weglassen werde.

@Configuration
@EnableAsync
public class SampleConfig {
	//application.von Eigenschaften"threadPool"Variable, die den Einstellwert beginnend mit enthält
	@Autowired
	ThreadPoolSettings threadPoolSettings;

	//@Registrieren Sie den Thread-Pool für Async als Spring Bean
	//Der Bean-Name lautet "taskExecutor1" und entspricht dem Methodennamen.
	@Bean
	public ThreadPoolTaskExecutor taskExecutor1() {
		final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
		executor.setCorePoolSize(threadPoolSettings.getCorePoolSize());
		executor.setQueueCapacity(threadPoolSettings.getQueueCapacity());
		executor.setMaxPoolSize(threadPoolSettings.getMaxPoolSize());
		executor.setKeepAliveSeconds(threadPoolSettings.getKeepAliveSeconds());
		executor.setThreadNamePrefix("Executor1-");
		executor.setDaemon(false);
		executor.initialize();
		return executor;
	}
}

** Abbildung 3.6 Inhalt der SampleConfig-Klasse **

@Service Erstellen einer Klasse

Erstellen Sie die unten gezeigte SampleService-Klasse.

@Service dieser Klasse ist DDD (Domain-Driven Design). Es ist eine der Komponenten und trägt die Schuld, die Geschäftslogik zu verarbeiten.

cmd () startet den durch das Argument angegebenen Befehl und gibt ein Future-Objekt zurück, das den Ausführungsstatus des Befehls überprüfen kann, ohne ihn zu blockieren. @Async, um zu erklären, dass es das Ziel eines asynchronen Aufrufs ist ) Wird hinzugefügt und der Parameter "taskExcecutor1" wird hinzugefügt, um eine Verknüpfung zu dem in der SampleConfig-Klasse definierten Thread-Pool herzustellen.

@Service
public class SampleService {
	@Async("taskExecutor1")
	public CompletableFuture<String> cmd(final String[] command) {
		final int exitValue = run(command);
		return CompletableFuture.completedFuture("exitValue="+exitValue);
	}

	//Führen Sie einen externen Befehl aus
	public static int run(final String[] command) {
		try{
			final Process p = new ProcessBuilder(command).redirectErrorStream(true).start();
			try(final BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()))){
				String str;
				while((str = r.readLine()) != null) {
					System.out.println("Command output: "+str);
				}
			}
			return p.waitFor();
		}catch(IOException | InterruptedException e) {
			e.printStackTrace();
			return -1;
		}
	}
}

** Abbildung 3.7 Inhalt der SampleService-Klasse **

@Controller Erstellen einer Klasse

Erstellen Sie die unten gezeigte SampleController-Klasse.

@Controller entspricht dem Controller des MVC-Modells und stammt vom Client. Nehmen Sie die Schulden auf, um die Nachfrage zu befriedigen.

Ein Feld mit @Autowired wurde dieser Klasse hinzugefügt Es gibt @Component und [@Bean](https: / Weist Spring Framework an, Teile, die in /docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Bean.html definiert sind, automatisch einzufügen. Ding.

cmd () ruft cmd () von SampleService als Antwort auf "GET http: //.../ cmd" auf. Es sieht aus wie ein synchroner Anruf, aber der Angerufene ist @Async. Da HTML) hinzugefügt wird, ändert das Spring Framework es automatisch in einen asynchronen Aufruf.

Wenn die Thread-Pool-Warteschlange oder die Threads voll sind und keine asynchrone Verarbeitung akzeptieren können, wird eine RejectedExecutionException ausgelöst. Dies ermöglicht es, die Flussrate der asynchronen Verarbeitung zu steuern. Hier wird der Einfachheit halber die Zeichenfolge "Abgelehnt" an den Client zurückgegeben, ursprünglich jedoch das HTTP "429 Too Many Requests". Es wäre eine gute Idee, einen Statuscode wie "zurückzugeben.

Als Antwort auf "GET http: //.../ status" durchsucht status () das von cmd () von SampleService zurückgegebene Future-Objekt und gibt den Ausführungsstatus des asynchronen Aufrufs als Zeichenfolge zurück.

@Controller
public class SampleController {
	private static final String[] COMMAND = new String[] {"cmd", "/c", "ping", "-n", "10", "localhost", ">", "nul"};
	private AtomicInteger counter = new AtomicInteger(1);
	private HashMap<String, Future<String>> futures = new HashMap<>();

	@Autowired
	private SampleService sampleService;

	@RequestMapping(value="/cmd", method=RequestMethod.GET)
	@ResponseBody
	public String cmd() {
		final String id = String.format("<noTenantId>#%03d", counter.getAndIncrement());
		try{
			final CompletableFuture<String> future = sampleService.cmd(COMMAND)
				.exceptionally(ex -> id+": Exception: "+ex.getMessage()+"\n");
			synchronized(this.futures) {
				this.futures.put(id, future);
			}
			return "Accepted: id="+id+"\n";
		}catch(RejectedExecutionException e) {
			final CompletableFuture<String> future = new CompletableFuture<>();
			future.complete("Rejected");
			synchronized(this.futures) {
				this.futures.put(id, future);
			}
			return "Rejected: id="+id+"\n";
		}
	}

	@RequestMapping(value="/status", method=RequestMethod.GET)
	@ResponseBody
	public String status() {
		final Map<String, Future<String>> map;
		synchronized(this.futures) {
			map = (Map<String, Future<String>>)this.futures.clone();
		}
		return map.entrySet().stream()
			.map(entry->SampleController.futureGet(entry.getKey(), entry.getValue()))
			.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
			.toString();
	}

	//Konvertieren Sie den Status von asynchronen Aufgaben in Zeichenfolgen
	private static String futureGet(String tenantId, Future<String> future) {
		if(future.isDone()==false && future.isCancelled()==false) {
			return tenantId+": Running\n";
		}else if(future.isCancelled()) {
			return tenantId+": Canceled\n";
		}
		try {
			return tenantId+": "+future.get()+"\n";
		} catch (InterruptedException | ExecutionException | CancellationException e) {
			return tenantId+": Exception\n";
		}
	}
}

** Abbildung 3.8 Inhalt der SampleController-Klasse **

Funktionsprüfung

Sie können den Tomcat-Container lokal starten und den Befehl wie folgt ausführen, um den Vorgang zu überprüfen.

$ curl -X GET http://localhost:8080/cmd
Accepted: id=<noTenantId>#001

#Nach etwas mehr als 10 Sekunden warten/Status bekommen
$ curl -X GET http://localhost:8080/status
<noTenantId>#001: exitValue=0

#Normale Beendigung des Tomcat-Containers
$ curl -X POST http://localhost:8080/shutdown

** Abbildung 3.9 Bestätigung des asynchronen Taskbetriebs **

Fahren Sie fort mit "Implementierung der asynchronen Verarbeitung mit mehreren Mandanten in Tomcat" ...

Recommended Posts

Implementierung der asynchronen Verarbeitung für einen einzelnen Mandanten in Tomcat
Implementierung der asynchronen Verarbeitung in Tomcat
Implementierung einer mandantenfähigen kompatiblen asynchronen Verarbeitung in Tomcat
Java-Implementierung von Tri-Tree
Implementierung von HashMap mit Kotlin
[Rails] Asynchrone Implementierung der Like-Funktion
Reihenfolge der Verarbeitung im Programm
Implementierung einer ähnlichen Funktion in Java
Implementierung der Klonmethode für Java Record
Implementierung von DBlayer in Java (RDB, MySQL)
[Für mich] Übertragung der Servlet-Klassenverarbeitung
Rails [Für Anfänger] Implementierung der Kommentarfunktion
Android Asynchrone UI-Thread-Verarbeitung
So implementieren Sie die asynchrone Verarbeitung in Outsystems
[Swift, ein Muss für Jungvögel! ] Lassen Sie uns die Implementierung der Kommunikationsverarbeitung der Web-API verstehen
Implementierung der Zifferngruppierung in der Furima App
Implementierungsnotiz für SKStoreReviewController in der Swift-Benutzeroberfläche von iOS14
[Rails] Implementierung von "Benachrichtigung auf irgendeine Weise benachrichtigen"
Verarbeitung zum Lesen / Aktualisieren / Aktualisieren von Daten in TableView