La lliçó anterior va tancar amb dos límits que cap de les dues senyals anteriors no pot superar. El primer: el rastreId del FiltreRastreig de 03-06 existeix només dins de CicloUrbana; quan la petició surt cap a la passarel·la de pagaments de 07-06, el proveïdor no el coneix, i si demà el monòlit es divideix (07-05), cada servei generarà el seu i la correlació es trencarà justament a la frontera on més falta fa.
El segon és més profund i no depèn de dividir res. Una petició POST /api/v1/lloguers triga 2,1 segons. Les mètriques de 09-04 ho diuen: el p99 ha pujat. Els logs de 09-05 ho confirmen: hi ha una línia en entrar i una altra en sortir, amb 2,1 segons entre totes dues. I aquí s'acaba la informació. On van anar aquests dos segons? A la validació, a les tres consultes, a la memòria cau, al cobrament, a l'enviament del correu? Ni un agregat numèric ni una successió de línies amb marques de temps ho responen amb precisió, perquè cap de les dues no coneix l'estructura de l'operació.
La traçabilitat distribuïda sí. Descompon cada petició en un arbre d'operacions cronometrades i imbricades, i permet mirar una petició concreta i assenyalar amb el dit el tros que es va menjar el 80 % del temps. Aquesta lliçó la munta sencera a CicloUrbana: els conceptes, la propagació del context entre processos, Micrometer Tracing —el substitut del descontinuat Spring Cloud Sleuth—, spans propis amb l'Observation API que ja coneixes de 09-03, la correlació de les tres senyals, un backend on llegir la cascada, i el mostreig, que és la decisió que fa tot això viable econòmicament.
Contingut
- El problema: on se'n va anar el temps
- Conceptes: traça, span i context
- Una traça de lloguer, descomposta
- Propagació del context entre processos
- Sleuth ha mort: Micrometer Tracing
- Instrumentar CicloUrbana
- Què s'instrumenta sense escriure codi
- Spans propis amb l'Observation API
- Correlacionar les tres senyals
- Exemplars: del gràfic a la traça
- El backend: Grafana Tempo
- Llegir una cascada de spans
- Mostreig: al cap, a la cua i errors
- L'OpenTelemetry Collector
- Traçabilitat i microserveis
- L'agent d'OpenTelemetry davant de Micrometer
- Cost, sobrecàrrega i què no traçar
- Cas pràctic: el p99 dels lloguers
- Errors Comuns i Consells
- Exercicis
- El problema: on se'n va anar el temps
El flux real d'un lloguer a Ribalta travessa cinc components:
sequenceDiagram
participant M as App mobil
participant A as CicloUrbana API
participant C as Cache · 09-02
participant B as PostgreSQL
participant P as Passarela de pagaments · 07-06
M->>A: POST /api/v1/lloguers
A->>C: cercar tarifa
C-->>A: fallada de cache
A->>B: select tarifa
A->>B: select usuari, bicicleta, estacio
A->>B: insert lloguer
A->>P: autoritzar cobrament
P-->>A: autoritzat (1.740 ms)
A->>B: update lloguer
A-->>M: 201 Created (2.100 ms)
Amb mètriques saps que l'operació sencera va trigar 2,1 s. Amb logs saps que va començar i va acabar. Cap de les dues no et diu que 1.740 d'aquells 2.100 mil·lisegons se'ls va endur la passarel·la, ni que hi va haver una fallada de memòria cau que va afegir una consulta, ni quantes consultes hi va haver en realitat.
Podries instrumentar cada pas amb un Timer de 09-03, i molts equips ho fan. El resultat són desenes de mètriques soltes de les quals es perd la relació: no se sap quina invocació de la passarel·la pertany a quina petició, ni si les tres consultes van ser seqüencials o paral·leles, ni per què aquesta petició concreta —la del ciutadà que ha trucat a l'ajuntament— va trigar el que va trigar. La traça conserva aquesta relació: és l'estructura d'una operació, cronometrada.
- Conceptes: traça, span i context
| Concepte | Què és | A CicloUrbana |
|---|---|---|
| Traça (trace) | L'arbre complet d'una operació de principi a fi | Un POST /api/v1/lloguers sencer |
traceId |
Identificador únic de la traça, 128 bits | Compartit per tots els seus spans i per tots els serveis |
| Span | Una unitat de treball amb inici, fi i nom | «insert lloguer», «autoritzar cobrament» |
spanId |
Identificador del span, 64 bits | Únic dins de la traça |
| Span pare | El span que va originar aquest | El span HTTP és pare del span de la consulta |
| Context de traça | traceId + spanId + banderes, que viatgen |
El que es propaga a les capçaleres |
| Atributs | Parells clau-valor del span | estacio=2, tarifa=ESTUDIANT |
| Esdeveniments | Marques puntuals dins d'un span | «tallacircuits obert» |
| Estat | Si el span va acabar bé o amb error | L'excepció registrada |
| Mostreig | Decidir quines traces es desen | 10 % en producció |
Dues idees que aclareixen el model. Un span és un Timer amb genealogia: mesura el mateix, però a més sap qui el va cridar i a qui va cridar, i aquesta relació és tota la diferència. I el traceId és el mateix al llarg de tota l'operació, fins i tot creuant processos: és el que permet veure en una sola pantalla la feina feta per CicloUrbana i la feta per un altre servei.
- Una traça de lloguer, descomposta
gantt
title Traca de POST /api/v1/lloguers (traceId a3f19c2e...) - 2.100 ms
dateFormat X
axisFormat %L
section HTTP
POST /api/v1/lloguers :0, 2100
section Servei
LloguerService.iniciar :12, 2080
section Cache
cache estacions (fallada) :20, 22
section Base de dades
select tarifa :24, 31
select usuari i bicicleta :33, 48
insert lloguer :50, 62
update lloguer :1880, 1894
section Extern
POST passarela /autoritzacions :140, 1880
Llegida de dalt a baix, la traça explica la història completa: el span arrel dura 2.100 ms; a dins, el servei dura 2.080; dins seu hi ha una fallada de memòria cau, tres consultes ràpides que sumen 45 ms i un span de 1.740 ms cap a la passarel·la. El diagnòstic és immediat i no requereix interpretació: el 83 % del temps és en un sistema que no controlem.
Compara-ho amb el que teníem: una mètrica que deia «2,1 s» i dues línies de log. La diferència no és de quantitat d'informació, és de forma: la traça té estructura, i l'estructura és el que permet atribuir el temps.
- Propagació del context entre processos
Perquè el span creat per la passarel·la pertanyi a la mateixa traça que el nostre, el context ha de viatjar a la petició HTTP. Hi ha dos formats i un ha guanyat:
W3C Trace Context (traceparent) |
B3 (Zipkin) | |
|---|---|---|
| Estàndard | Recomanació del W3C | De facto, de Zipkin |
| Capçaleres | traceparent, tracestate |
X-B3-TraceId, X-B3-SpanId, X-B3-Sampled, X-B3-ParentSpanId |
| Format | Una capçalera compacta | Diverses capçaleres, o b3 condensada |
| Interoperabilitat | Universal: OTel, proveïdors comercials, malles de serveis | Bona a l'ecosistema Zipkin/Brave |
| Estat | El que cal fer servir avui | Compatibilitat amb sistemes existents |
Una capçalera traceparent real:
traceparent: 00-a3f19c2e8b7d4f6a9c1e2d3b4a5f6e7d-b7d4f6a9c1e2d3b4-01
│ │ │ │
│ │ │ └─ banderes: 01 = mostrejada
│ │ └─ span pare (16 hex)
│ └─ traceId (32 hex)
└─ versioEls quatre camps separats per guions són tot el mecanisme. La bandera final és més important del que sembla: transporta la decisió de mostreig, de manera que si nosaltres decidim desar aquesta traça, el servei següent desa la seva part també; sense aquest acord, les traces sortirien incompletes. tracestate és la capçalera complementària on cada proveïdor afegeix informació pròpia sense trencar l'estàndard.
La regla pràctica: fes servir W3C per defecte, i activa a més B3 només si has de parlar amb un sistema antic que només entén Zipkin. Micrometer permet emetre i acceptar tots dos alhora.
- Sleuth ha mort: Micrometer Tracing
Qui hagi vist projectes Spring Boot 2 coneixerà Spring Cloud Sleuth, que feia exactament això. Convé dir-ho sense embuts: Sleuth està descontinuat i no suporta Spring Boot 3. La seva funcionalitat es va traslladar al projecte Micrometer, com a part del model d'observabilitat unificat que ja fem servir a 09-03.
| Opció | Què és | Estat | Quan triar-la |
|---|---|---|---|
| Spring Cloud Sleuth | La solució de Boot 2 | Descontinuat | Mai en un projecte nou |
| Micrometer Tracing + pont OTel | Façana de Micrometer sobre OpenTelemetry | Recomanat | CicloUrbana: l'estàndard cap al qual convergeix tot |
| Micrometer Tracing + pont Brave | Façana sobre Brave (Zipkin) | Suportat | Ja existeix un Zipkin a l'organització |
Agent d'OpenTelemetry (-javaagent) |
Instrumentació sense tocar el codi | Vàlid | Aplicacions que no es poden modificar (apartat 16) |
L'arquitectura reprodueix la idea d'SLF4J i de Micrometer Metrics: Micrometer Tracing és la façana, i al darrere hi va un pont cap a una implementació real. El teu codi parla amb Observation i Tracer; el pont decideix si això acaba a OpenTelemetry o a Brave. Canviar d'un a l'altre és canviar dues dependències.
L'elecció de CicloUrbana és el pont a OpenTelemetry, per la raó que vam anticipar al final de 09-04: OTLP és el protocol que accepten Tempo, Jaeger, Grafana Cloud, Datadog, New Relic i Elastic APM, així que la instrumentació no queda lligada a cap destinació.
- Instrumentar CicloUrbana
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>La primera aporta la façana i el pont; la segona, l'exportador que envia els spans per OTLP. Les versions les governa l'spring-boot-starter-parent de 01-04, així que no es declaren.
spring:
application.name: ciclourbana # es converteix en service.name a les traces
management:
tracing:
enabled: true
sampling.probability: 0.1 # 10 % en produccio (apartat 13)
propagation:
type: w3c # w3c, b3 o totes dues: [w3c, b3]
otlp.tracing:
endpoint: ${OTLP_ENDPOINT:http://otel-collector:4318/v1/traces}
timeout: 10s
logging.pattern.level: "%5p [${spring.application.name},%X{traceId:-},%X{spanId:-}]"Quatre decisions que convé entendre. spring.application.name és obligatori de facto: sense ell, totes les traces apareixen sota un servei anomenat unknown_service i no es poden separar. sampling.probability: 0.1 desa una traça de cada deu, i l'apartat 13 explica per què 1.0 no val en producció. L'endpoint apunta al Collector de l'apartat 14 i no directament al backend, expressament. I el patró de log és la peça que uneix aquesta lliçó amb l'anterior: traceId i spanId apareixen al MDC automàticament i s'escriuen a cada línia.
A dev, un perfil amb sampling.probability: 1.0 i l'endpoint apuntant a un Tempo local permet veure totes les traces mentre es desenvolupa, que és quan més ensenyen.
- Què s'instrumenta sense escriure codi
Aquesta és la part que sorprèn: amb les dues dependències i la configuració anterior, la major part de la feina ja està feta. Micrometer Tracing es recolza en les observacions que Spring Boot produeix de sèrie:
| Component | Span que genera | Atributs automàtics |
|---|---|---|
| Peticions HTTP entrants | Span arrel per petició | http.method, http.route (plantilla), http.status_code |
RestClient / WebClient sortints |
Span fill per crida, amb propagació de capçaleres | URL, mètode, estat |
@Scheduled (07-03) |
Span arrel per execució | Nom de la tasca |
@Async i ThreadPoolTaskExecutor |
Continuació de la traça a l'altre fil | — |
| Spring Data / JDBC | Span per consulta, amb l'agent o datasource-micrometer |
Sentència SQL (sense paràmetres) |
| Spring Security | Span de la cadena de filtres | — |
| Missatgeria (Kafka, RabbitMQ) (07-05) | Span de producció i de consum, amb el context a les capçaleres del missatge | Tema, partició |
| Resilience4j (07-06) | Esdeveniments al span de la crida | Estat del tallacircuits |
Dos matisos honestos. Les consultes SQL no s'instrumenten soles amb la configuració mínima: calen datasource-micrometer-spring-boot o l'agent d'OTel de l'apartat 16, i val la pena perquè, com vam veure a 09-01, la base de dades és on sol ser el temps. I la propagació a RestClient funciona sola només si el client es construeix des del RestClient.Builder que injecta Spring —el bean de l'apartat 2 de 07-06 ho fa—; un RestClient creat amb RestClient.create() a mà no porta instrumentació i trenca la traça a la frontera exterior, que és justament on més fa mal.
- Spans propis amb l'Observation API
L'automàtic cobreix la infraestructura. Els spans de negoci —«càlcul de tarifa», «validació de disponibilitat»— cal declarar-los, i aquí es cobra la inversió de 09-03: el mateix codi que ja produïa mètriques comença a produir spans, sense canviar una línia.
La versió declarativa, sobre el càlcul de tarifa:
@Observed(name = "ciclourbana.tarifa.calcul", contextualName = "calcul-tarifa")
public BigDecimal calcular(TipusTarifa tipus, Duration durada) { ... }I la programàtica, per instrumentar un bloc concret de LloguerService amb atributs de negoci:
@Service
public class LloguerService {
private final ObservationRegistry observacions;
@Transactional
public LloguerResponse iniciar(IniciarLloguerRequest peticio) {
return Observation.createNotStarted("ciclourbana.lloguer.iniciar", observacions)
.contextualName("iniciar-lloguer")
.lowCardinalityKeyValue("tarifa", peticio.tipusTarifa().name())
.lowCardinalityKeyValue("estacio", String.valueOf(peticio.estacioOrigenId()))
.highCardinalityKeyValue("idUsuari", String.valueOf(usuariActual()))
.observe(() -> {
Bicicleta bicicleta = seleccionarDisponible(peticio.estacioOrigenId());
Lloguer lloguer = registrar(bicicleta, peticio);
return mapejador.aResposta(lloguer);
});
}
}Què produeix això exactament: un Timer anomenat ciclourbana.lloguer.iniciar amb etiquetes tarifa i estacio —les de baixa cardinalitat, les mateixes regles de 09-03— i un span anomenat iniciar-lloguer amb aquests atributs més idUsuari, que va només al span. Una instrumentació, dues senyals, i la regla de cardinalitat convertida en API.
L'advertiment important, que enllaça amb 09-05: els atributs d'un span s'emmagatzemen i es consulten igual que un log, així que la política de l'apartat 10 de la lliçó anterior s'aplica íntegra. idUsuari com a identificador intern és acceptable; el correu, el telèfon, el DNI o les coordenades GPS del ciutadà no ho són. Un span no és un lloc privat: viatja a un backend, de vegades d'un tercer, i el consulta qui hi tingui accés.
Quan cal control fi —afegir un esdeveniment a mitges, marcar el span com a fallit sense llançar excepció— es fa servir el Tracer directament:
Span span = tracer.currentSpan();
if (span != null) {
span.event("tallacircuits-obert");
span.tag("respatller", "cobrament-diferit");
}
- Correlacionar les tres senyals
Ara hi ha dos identificadors en joc: el rastreId que el FiltreRastreig de 03-06 posa al MDC i el traceId que Micrometer Tracing també posa al MDC. Tenir-ne dos és pitjor que tenir-ne un, així que cal unificar-los, i la resposta correcta és quedar-se amb l'estàndard.
El pla d'unificació, en tres passos:
- Canviar el
FiltreRastreigperquè no generi res pel seu compte. Micrometer ja col·locatraceIdispanIdal MDC de cada petició, i a més —a diferència del nostre— respecta eltraceparententrant: si l'app mòbil o un proxy ja va començar una traça, es continua en lloc d'inventar-ne una altra. - Mantenir la responsabilitat que sí que era seva: retornar l'identificador al client. El filtre passa a llegir
tracer.currentSpan().context().traceId()i a escriure'l a la capçalera de resposta i alProblemDetaildelGestorGlobalExcepcions(03-06), perquè el ciutadà que truca a l'ajuntament continuï podent llegir-lo a la seva pantalla. - Actualitzar el patró de log i les consultes de Loki, substituint
%X{rastreId}per%X{traceId}i| json | rastreId = "..."per| json | traceId = "...".
@Component
public class FiltreRastreig extends OncePerRequestFilter {
private final Tracer tracer;
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res,
FilterChain cadena) throws ServletException, IOException {
Span span = tracer.currentSpan(); // ja creat per la instrumentacio HTTP
if (span != null) {
res.setHeader("X-Trace-Id", span.context().traceId());
}
cadena.doFilter(req, res); // sense MDC.put ni MDC.remove: els posa Micrometer
}
}El resultat és la correlació completa de les tres senyals, que és l'objectiu de tot el mòdul:
| Des de | Cap a | Com |
|---|---|---|
| Alerta (09-04) | Quadre de comandament | El panell de la mètrica que va disparar |
| Mètrica | Traça concreta | Exemplars (apartat 10) |
| Traça | Logs d'aquella petició | {app="ciclourbana"} | json | traceId = "..." |
| Log | Traça | El traceId de la línia, a Tempo |
| Ciutadà que truca | Tot l'anterior | La capçalera X-Trace-Id de la seva resposta |
- Exemplars: del gràfic a la traça
Un exemplar és un enllaç que Prometheus desa al costat d'una mostra d'histograma: «aquesta observació de 2,1 segons pertany a la traça a3f19c2e...». Converteix un punt d'un gràfic en un cas concret que es pot obrir.
Al format de /actuator/prometheus apareixen després d'un coixinet:
http_server_requests_seconds_bucket{uri="/api/v1/lloguers",le="2.5"} 12801 # {trace_id="a3f19c2e8b7d4f6a9c1e2d3b4a5f6e7d"} 2.1 1756628062.481Requisits, que són tres i convé tenir-los junts: histogrames activats en aquesta mètrica (percentiles-histogram, 09-03), traçabilitat activa perquè existeixi un traceId, i Prometheus arrencat amb --enable-feature=exemplar-storage, més exemplarTraceIdDestination: trace_id a l'origen de dades de Grafana.
El resultat a la pràctica: al panell de latència p99 de 09-04 apareixen punts sobre la corba; en prémer-ne un, Grafana obre la traça exacta d'una petició que va trigar això. És el salt que faltava entre «el p99 ha pujat» i «mira aquesta petició». I té una virtut que compensa el mostreig: els exemplars tendeixen a apuntar a les observacions de les cubetes altes, és a dir, a les peticions lentes, que són justament les que interessen.
- El backend: Grafana Tempo
Els spans cal enviar-los a algun lloc que els desi i els sàpiga mostrar:
| Backend | Model | Fort en | Quan |
|---|---|---|---|
| Grafana Tempo | Emmagatzematge d'objectes, indexa només el traceId |
Molt barat; integrat amb Grafana, Loki i Prometheus | CicloUrbana |
| Jaeger | Cassandra, Elasticsearch o memòria | Interfície d'anàlisi molt bona, comparació de traces | Sense Grafana |
| Zipkin | Senzill, veterà | Lleuger i fàcil d'arrencar | Sistemes existents amb B3 |
| Comercials | Gestionat | Correlació automàtica i detecció d'anomalies | Pressupost disponible |
S'afegeix a la pila d'observabilitat de 09-04:
# docker-compose.observabilitat.yml
tempo:
image: grafana/tempo:2.5.0
command: ["-config.file=/etc/tempo/tempo.yml"]
volumes:
- ./observabilitat/tempo.yml:/etc/tempo/tempo.yml:ro
- tempo-dades:/var/tempo
ports: ["3200:3200"] # consultes
networks: [xarxa-ciclourbana]
otel-collector:
image: otel/opentelemetry-collector-contrib:0.104.0
command: ["--config=/etc/otel/config.yml"]
volumes: ["./observabilitat/otel-collector.yml:/etc/otel/config.yml:ro"]
ports: ["4317:4317", "4318:4318"] # OTLP gRPC i HTTP
depends_on: [tempo]
networks: [xarxa-ciclourbana]I l'origen de dades de Grafana, amb els enllaços que fan possible saltar entre senyals:
- name: Tempo
type: tempo
url: http://tempo:3200
jsonData:
tracesToLogsV2:
datasourceUid: loki
filterByTraceID: true # d'un span a les seves linies de log
lokiSearch:
datasourceUid: loki
tracesToMetrics:
datasourceUid: prometheus
- Llegir una cascada de spans
A la interfície de Grafana, una traça es mostra com una cascada: cada span una barra horitzontal, la longitud és la seva durada i el sagnat, la seva profunditat a l'arbre.
POST /api/v1/lloguers ───────────────────────────────────────────── 2.100 ms
└─ LloguerService.iniciar ─────────────────────────────────────── 2.080 ms
├─ cache estacions (miss) ─ 2 ms
├─ select tarifa ── 7 ms
├─ select usuari, bicicleta ──── 15 ms
├─ insert lloguer ─── 12 ms
├─ POST passarela /autoritzacions ─────────────────────────── 1.740 ms ← 83 %
└─ update lloguer ─── 14 msCom es llegeix, en l'ordre que funciona:
- La barra més llarga que no sigui el pare. És el sospitós immediat: aquí, la passarel·la amb el 83 % del temps.
- Els buits. Si la suma dels fills és molt menor que el pare, hi ha temps no instrumentat: esperant un fil, un bloqueig, una pausa de GC o simplement codi sense span. Un buit gran és tan informatiu com una barra llarga.
- La repetició. Cinquanta spans
selectidèntics i consecutius són un N+1 de 09-01 vist amb els ulls, i és la manera més ràpida que existeix de detectar-lo. - El paral·lelisme. Barres que se solapen indiquen feina concurrent; barres estrictament seqüencials que podrien solapar-se són una oportunitat.
- Els spans en error, marcats en vermell, amb la seva excepció com a atribut.
- Mostreig: al cap, a la cua i errors
Desar totes les traces és inviable: cadascuna són desenes de spans d'uns centenars de bytes, i amb 100 peticions per segon surten milions de spans al dia. El mostreig decideix quines es desen.
| Estratègia | Quan decideix | Avantatge | Inconvenient |
|---|---|---|---|
| Al cap, probabilística | En començar la traça | Simple, barata, coherent entre serveis | Es perden traces interessants per atzar |
| Al cap, per taxa | En començar, amb un límit per segon | Cost acotat i previsible | Igual de cega davant del contingut |
| A la cua | En acabar, veient la traça completa | Desa tots els errors i les lentes | Requereix Collector amb memòria i latència |
| Sempre | — | Tot disponible | Cost prohibitiu llevat de dev |
Per què probability: 1.0 no val en producció. Tres raons acumulatives: el cost d'emmagatzematge creix linealment amb el trànsit; la sobrecàrrega a l'aplicació —crear spans, serialitzar-los, enviar-los— deixa de ser menyspreable; i la xarxa cap al backend transporta un cabal constant. Amb un 10 % es conserva la capacitat de diagnòstic —els problemes sistemàtics apareixen igual en una mostra— a la desena part del cost.
Però el 10 % cec té un defecte greu: la petició que va fallar i per la qual truca el ciutadà té un 90 % de probabilitats de no haver-se desat. La solució és el mostreig de cua al Collector: es retenen els spans en memòria uns segons i, quan la traça acaba, es decideix amb la traça sencera a la vista.
# fragment d'otel-collector.yml
processors:
tail_sampling:
decision_wait: 10s
policies:
- name: tots-els-errors
type: status_code
status_code: { status_codes: [ERROR] } # 100 % dels errors
- name: les-lentes
type: latency
latency: { threshold_ms: 1000 } # 100 % de les que passen d'1 s
- name: mostra-de-la-resta
type: probabilistic
probabilistic: { sampling_percentage: 5 } # 5 % del que es normalAquesta política és la que CicloUrbana fa servir en producció, i respon exactament al que es necessita: tots els errors, totes les lentes i una mostra del que és normal per tenir referència. Requereix que l'aplicació enviï el 100 % al Collector (sampling.probability: 1.0 a l'aplicació i el filtratge al Collector), cosa que trasllada el cost de la decisió al lloc on es pot prendre bé.
- L'OpenTelemetry Collector
Enviar els spans directament de l'aplicació al backend funciona i és el que fa la majoria en començar. Posar un Collector al mig aporta cinc coses que s'agraeixen aviat:
- Desacoblament: canviar de Tempo a Jaeger o a un servei comercial es fa al Collector, sense desplegar l'aplicació.
- Mostreig de cua, que només es pot fer on convergeix la traça completa (apartat 13).
- Transformació: eliminar atributs sensibles abans que surtin —una segona xarxa de seguretat per a la política de 09-05—, reanomenar, afegir metadades de l'entorn.
- Amortiment: si el backend cau o s'alenteix, el Collector encua i reintenta en lloc que ho pateixi l'aplicació.
- Una sola sortida: mètriques, logs i traces pel mateix canal OTLP.
# otel-collector.yml
receivers:
otlp:
protocols: { grpc: { endpoint: 0.0.0.0:4317 }, http: { endpoint: 0.0.0.0:4318 } }
processors:
batch: { timeout: 5s, send_batch_size: 512 }
memory_limiter: { check_interval: 1s, limit_mib: 512 }
attributes:
actions:
- key: usuari.correu # xarxa de seguretat: mai hauria d'arribar aqui
action: delete
- key: entorn
value: prod
action: upsert
exporters:
otlp/tempo:
endpoint: tempo:4317
tls: { insecure: true }
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, tail_sampling, attributes, batch]
exporters: [otlp/tempo]L'ordre dels processadors importa: memory_limiter primer per protegir-se, el mostreig abans que la transformació —per no gastar CPU transformant el que es descartarà— i batch al final, sempre, perquè agrupar abans d'exportar redueix molt el nombre de connexions.
- Traçabilitat i microserveis
A 07-05 vam deixar un advertiment sobre dividir el monòlit, i aquesta lliçó aporta l'argument que faltava: la traçabilitat distribuïda és requisit previ, no una millora posterior.
El motiu és que dividir un sistema destrueix la capacitat de diagnòstic que es tenia. En un monòlit, una traça de pila recorre tota l'operació i un depurador la segueix sencera. Tan bon punt hi ha tres serveis, aquella petició es converteix en tres processos, tres logs, tres desplegaments i cap visió conjunta: quan el ciutadà informa que alguna cosa va lenta, ningú no sap quin dels tres és el lent, i comença el joc d'acusacions creuades entre equips. La traçabilitat retorna la visió única, i per això l'ordre correcte és instrumentar primer i dividir després.
El que cal garantir a la frontera és simplement que el context viatgi. Amb RestClient construït des del Builder de Spring, passa sol: la capçalera traceparent surt a cada petició i el servei receptor —si està instrumentat— continua la traça en lloc de començar-ne una altra. Amb missatgeria (07-05), el context viatja a les capçaleres del missatge, i hi ha un matís conceptual: com que el consumidor processa després, el seu span no és exactament un fill síncron sinó un span enllaçat, i a la interfície apareix separat en el temps però unit pel traceId. Amb la passarel·la de pagaments de 07-06, que és un tercer, la nostra traça acaba al span de la crida sortint: no en veiem l'interior, però sabem exactament quant va trigar, que és justament el que necessitàvem a l'apartat 3.
- L'agent d'OpenTelemetry davant de Micrometer
Hi ha una alternativa que no toca el codi: adjuntar l'agent d'OpenTelemetry a la JVM.
java -javaagent:/opt/opentelemetry-javaagent.jar \
-Dotel.service.name=ciclourbana \
-Dotel.traces.exporter=otlp \
-Dotel.exporter.otlp.endpoint=http://otel-collector:4318 \
-Dotel.traces.sampler=parentbased_traceidratio \
-Dotel.traces.sampler.arg=0.1 \
-jar app.jarAgent -javaagent |
Micrometer Tracing | |
|---|---|---|
| Canvis al codi | Cap | Dues dependències i configuració |
| Cobertura automàtica | Molt àmplia: JDBC, JMS, Redis, més de 100 biblioteques | La que Spring instrumenta |
| Spans de negoci | Requereix anotacions d'OTel | @Observed i l'Observation API |
| Mètriques i traces unificades | Separades | Una instrumentació, dues senyals |
| Arrencada | Una mica més lenta (reescriu bytecode) | Sense impacte |
| Depuració | Més opac | Explícit al codi |
| Quan | Aplicacions heretades o que no es poden tocar | Projecte Spring Boot 3 propi |
L'elecció de CicloUrbana és Micrometer Tracing, per coherència: ja fem servir l'Observation API per a les mètriques de 09-03, i aquesta mateixa instrumentació produeix els spans. Dit això, hi ha una combinació molt pràctica: agent per a la cobertura automàtica de baix nivell —sobretot JDBC, que és on és el temps— i Micrometer per als spans de negoci. Tots dos escriuen al mateix context d'OpenTelemetry i les traces surten unificades.
- Cost, sobrecàrrega i què no traçar
La instrumentació no és gratis, encara que sigui barata: crear un span, mantenir el context en un ThreadLocal, serialitzar-lo i enviar-lo té un cost de l'ordre de microsegons per span. Amb mostreig raonable, la sobrecàrrega en CPU se situa habitualment entre l'1 % i el 3 %, més el trànsit de xarxa cap al Collector. És assumible; el que no ho és són els excessos.
Què no traçar. Mètodes trivials: un span per cada getter multiplica el volum per cent i no aporta res; la regla útil és un span per operació amb significat o amb espera d'E/S. Els endpoints d'Actuator: /actuator/** recollit cada 15 segons genera un flux constant de traces inútils, i s'exclou amb un ObservationPredicate. Les sondes de salut de Kubernetes, pel mateix. I els bucles molt calents: instrumentar dins d'un bucle de mil iteracions produeix mil spans en una traça que després ningú no pot llegir.
@Bean
ObservationPredicate ignorarActuator() {
return (nom, context) -> !(context instanceof ServerRequestObservationContext ctx)
|| !ctx.getCarrier().getRequestURI().startsWith("/actuator");
}I què no posar en un span: exactament el mateix que no es posa en un log (09-05). Els atributs d'un span s'emmagatzemen, s'indexen i es consulten; sovint en un servei de tercers. Contrasenyes, tokens, targetes, correus, telèfons, DNI i coordenades GPS dels ciutadans de Ribalta queden fora, i el processador attributes del Collector actua com a última xarxa.
- Cas pràctic: el p99 dels lloguers
08:14. Alertmanager avisa: LatenciaP99Degradada, p99 de POST /api/v1/lloguers per sobre d'1 s durant 10 minuts.
Pas 1 — el quadre de comandament (09-04). La fila RED confirma la pujada: p99 a 2,2 s, taxa d'error normal, trànsit normal. La fila de recursos està neta: pool sense pending, GC sense pauses llargues, CPU al 30 %. Conclusió provisional: no som nosaltres els que estem lents, estem esperant algú.
Pas 2 — de la mètrica a la traça. Al panell de p99 hi ha exemplars. Es prem un dels punts alts i Grafana obre la traça a Tempo. Aquest salt —que era impossible fa tres lliçons— costa un clic.
Pas 3 — la cascada. La traça és exactament la de l'apartat 12: 2.100 ms totals, dels quals 1.740 ms al span POST passarela /autoritzacions. Tota la resta està en desenes de mil·lisegons. El diagnòstic ja està fet, i ha portat dos minuts.
Pas 4 — confirmar que és general. Una traça podria ser un cas aïllat, així que es torna a Prometheus:
histogram_quantile(0.95, sum by (le) (rate(http_client_requests_seconds_bucket{client_name="passarela"}[5m])))
rate(resilience4j_retry_calls_total{kind="successful_with_retry"}[5m])El p95 del client cap a la passarel·la ha passat de 180 ms a 1,8 s, i els reintents s'han multiplicat. El tallacircuits de 07-06 encara no està obert, perquè les crides no fallen: són lentes. Aquest matís és important, i és el motiu que a 07-06 es recomanés alertar també sobre la taxa de reintents.
Pas 5 — els logs (09-05). Amb el traceId de la traça:
Retorna la història completa d'aquella petició, inclosos dos WARN de reintent amb el missatge de la passarel·la: 504 Gateway Timeout upstream. El diagnòstic queda tancat: la passarel·la està degradada, no caiguda.
Pas 6 — decidir. El servei funciona, més lent. Les opcions: baixar el temps d'espera del RestClient per fallar abans i activar el respatller de cobrament diferit; obrir el tallacircuits manualment; o esperar i avisar el proveïdor. La decisió —de negoci— és baixar l'espera a 800 ms, amb la qual cosa la majoria dels cobraments passen a diferir-se i el p99 torna a 300 ms mentre el proveïdor ho resol.
El que fa possible aquest recorregut són les tres senyals connectades: la mètrica va detectar, la traça va localitzar, el log va explicar. Cadascuna va fer el que sap fer, i el traceId les va unir. Sense traces, el pas 3 hauria estat una investigació d'hores comparant marques de temps entre logs de tres instàncies.
Errors Comuns i Consells
Intentar fer servir Spring Cloud Sleuth a Spring Boot 3. Està descontinuat i no funciona. El seu substitut és Micrometer Tracing amb un pont.
Deixar sampling.probability: 1.0 en producció. Cost d'emmagatzematge, sobrecàrrega i xarxa, per desar milions de traces que ningú no mirarà. Mostreig de cua al Collector, o un 10 % al cap.
Mostrejar al 10 % a l'aplicació i pretendre desar tots els errors. Són incompatibles: la decisió al cap es pren abans de saber si hi haurà error. Per a això cal enviar-ho tot al Collector i filtrar-hi.
Oblidar spring.application.name. Totes les traces apareixen com a unknown_service i no es poden separar per servei.
Crear el RestClient a mà amb RestClient.create(). No porta instrumentació: no propaga traceparent i no genera span. La traça es talla justament a la frontera exterior. Sempre des del RestClient.Builder injectat.
Posar dades personals als atributs d'un span. Els spans s'emmagatzemen, s'indexen i sovint surten a un tercer. S'aplica íntegra la política de 09-05.
Mantenir dos identificadors de traça. El rastreId propi de 03-06 i el traceId estàndard convivint dupliquen la feina i confonen. Unifica en l'estàndard i deixa al filtre només la responsabilitat de retornar-lo al client.
Traçar /actuator/** i les sondes. Generen un flux constant de traces inútils i embruten les estadístiques. S'exclouen amb un ObservationPredicate.
Consell: instrumenta amb l'Observation API i no amb Timer a mà. Costa el mateix i produeix les dues senyals.
Consell: activa els exemplars. El salt d'un punt del gràfic a la traça concreta és la millor relació entre esforç i benefici de tot el mòdul.
Consell: posa el Collector des del principi. Canviar de backend, mostrejar per cua o netejar atributs deixa de requerir un desplegament de l'aplicació.
Exercicis
Exercici 1: diagnosticar des de la cascada
Aquesta traça correspon a GET /api/v1/estacions en producció. Digues què està malament, quants problemes diferents hi veus, com ho arreglaries i què esperaries que passés amb la durada total després de cada correcció.
GET /api/v1/estacions ────────────────────────────────────────── 1.850 ms
└─ EstacioService.llistar ─────────────────────────────────── 1.840 ms
├─ select estacions ── 8 ms
├─ select bicicletes where estacio_id = ? ─ 6 ms
├─ select bicicletes where estacio_id = ? ─ 6 ms
├─ ... (18 spans identics mes) ... 108 ms
├─ (buit sense spans) 900 ms
└─ GET servei-mapes /geocodificar ────────────── 700 msExercici 2: dissenyar l'estratègia de mostreig
CicloUrbana atén 120 peticions per segon en hora punta i unes 15 fora d'ella. Cada traça té una mitjana de 12 spans de 400 bytes. L'ajuntament fixa tres requisits: poder investigar qualsevol petició fallida dels últims set dies, poder investigar qualsevol petició que hagi trigat més d'un segon, i no gastar més de 50 GB d'emmagatzematge. Calcula el volum sense mostreig, dissenya l'estratègia que compleix els tres requisits i escriu la configuració de l'aplicació i del Collector.
Exercici 3: unificar el rastreId
CicloUrbana fa des de 03-06 amb el seu FiltreRastreig propi, i ara Micrometer Tracing hi afegeix traceId i spanId. Hi ha logs històrics amb rastreId a Loki, quadres de comandament que filtren per aquest camp, la capçalera X-Rastre-Id documentada a OpenAPI (03-07) i el ProblemDetail amb la propietat rastre. Dissenya la migració completa —codi, configuració, consultes, documentació i compatibilitat— indicant l'ordre dels passos i què faries amb el que és antic.
Solucions
Solució 1.
Es veuen tres problemes diferents.
Problema 1 — un N+1 de manual (04-04, 09-01). Vint spans select bicicletes where estacio_id = ? idèntics i consecutius: una consulta per estació. És l'N+1 vist, sense necessitat de comptar sentències al log. Correcció: @EntityGraph/JOIN FETCH, o millor la projecció amb subconsulta de 09-01, que deixa una sola consulta. Efecte esperat: els 120 ms de consultes baixen a uns 10.
Problema 2 — un buit de 900 ms sense spans. És la troballa més interessant, perquè el temps no instrumentat també és informació. Gairebé la meitat de la petició no és en cap span fill, així que se'n va anar en alguna cosa que no està instrumentada: codi Java pesat —una ordenació o un mapatge sobre milers d'objectes—, espera d'una connexió del pool (hikaricp.connections.pending de 09-03 ho confirmaria), un bloqueig, o una pausa de GC (jvm.gc.pause). Com s'investiga: s'afegeix un @Observed als mètodes candidats per partir el buit, es comproven les mètriques de pool i GC en aquell instant, i si res no ho explica, es perfila amb async-profiler (09-01). És un cas on la traça no dóna la resposta però acota la pregunta a un tram concret.
Problema 3 — una crida remota síncrona de 700 ms en un endpoint de llistat. El servei de mapes s'invoca per geocodificar dins d'una petició de lectura molt usada. Correccions possibles, en ordre de preferència: posar a la memòria cau el resultat (09-02), ja que les coordenades d'una estació no canvien; precalcular-lo i desar-lo a la taula, que encara és millor perquè són dades estables; o, si de debò ha de ser en línia, treure'l del camí síncron. A més, aquesta crida ha de tenir temps d'espera i tallacircuits (07-06), i probablement no els té.
Efecte acumulat esperat: eliminant l'N+1 (−110 ms), resolent el buit (−900 ms) i posant a la memòria cau la geocodificació (−700 ms), la petició passa de 1.850 ms a uns 130 ms. L'ordre de treball el marca 09-01: primer el que més temps consumeix, i aquí això significa començar pel buit i la crida remota, no per l'N+1, encara que l'N+1 sigui el més cridaner.
Solució 2.
Volum sense mostreig. En hora punta, 120 pet/s × 12 spans × 400 B = 576 KB/s. Suposant 4 hores punta i 20 hores de 15 pet/s: 576 KB/s × 14.400 s ≈ 8,3 GB més 72 KB/s × 72.000 s ≈ 5,2 GB, és a dir, uns 13,5 GB al dia i 94 GB en set dies. Gairebé el doble del pressupost, i això sense compressió.
Estratègia: mostreig de cua. És l'única que compleix els dos primers requisits, perquè «totes les fallides» i «totes les lentes» són decisions que només es poden prendre quan la traça ha acabat. Un mostreig al cap del 10 % perdria el 90 % dels errors.
Configuració de l'aplicació —envia el 100 % al Collector, que és qui decideix—:
management:
tracing.sampling.probability: 1.0 # la decisio es pren al Collector
otlp.tracing.endpoint: http://otel-collector:4318/v1/tracesConfiguració del Collector:
processors:
tail_sampling:
decision_wait: 15s # major que la peticio mes lenta esperable
num_traces: 100000
policies:
- name: errors
type: status_code
status_code: { status_codes: [ERROR] }
- name: lentes
type: latency
latency: { threshold_ms: 1000 }
- name: mostra-normal
type: probabilistic
probabilistic: { sampling_percentage: 3 }Volum resultant. Suposant un 0,5 % d'errors i un 1 % de peticions per sobre d'1 s, es desa 0,5 % + 1 % + 3 % ≈ 4,5 % del total: uns 0,6 GB al dia, 4,3 GB en set dies. Molt per sota del límit, amb marge per pujar el 3 % al 10 % si es vol més referència. Retenció a Tempo: --storage.trace.retention=168h.
Contrapartides que cal declarar. El Collector necessita memòria per retenir les traces incompletes durant decision_wait —d'aquí memory_limiter i num_traces—; les traces triguen 15 segons extra a aparèixer a Tempo; i si el Collector cau, es perd tot, així que convé desplegar-lo amb més d'una rèplica i, en aquest cas, garantir que tots els spans d'una traça arriben al mateix Collector (un balanceig per traceId), o el mostreig de cua veurà traces partides.
Solució 3.
Principi: migrar a l'estàndard i mantenir compatibilitat temporal, sense big bang. Sis passos en aquest ordre:
1. Emetre tots dos, sense trencar res. S'afegeixen les dependències i la configuració de tracing. Micrometer posa traceId i spanId al MDC; el FiltreRastreig continua posant el seu rastreId. S'actualitza el patró de log perquè surtin els tres. Durant aquesta fase, tots els logs nous són consultables pels dos camps i res no es trenca.
2. Alinear els valors. Perquè tots dos identificadors coincideixin durant la transició, el FiltreRastreig deixa de generar un UUID i copia el traceId del span actual a la clau rastreId. Amb això, els dos camps tenen el mateix valor i les consultes antigues continuen funcionant sobre dades noves.
3. Migrar consultes i quadres de comandament. S'actualitzen els panells de Loki i les consultes desades per fer servir traceId. Com que al pas 2 tots dos coincideixen, es pot fer sense pressa i comprovant panell a panell.
4. Compatibilitat de l'API pública. La capçalera X-Rastre-Id està documentada a OpenAPI i pot haver-hi clients que la llegeixin. S'emeten les dues capçaleres amb el mateix valor, es marca X-Rastre-Id com a obsoleta a la documentació (03-07) amb una data de retirada, i s'anuncia als consumidors. El ProblemDetail fa el mateix: s'hi afegeix la propietat traceId i es manté rastre durant el període de gràcia. Una API pública no es canvia de cop, encara que el canvi sembli cosmètic.
5. Retirar el que és antic. Passat el període —el que dicti la retenció de Loki, 30 dies, més el marge acordat amb els clients—, el FiltreRastreig deixa d'escriure rastreId al MDC i d'emetre la capçalera antiga, i queda reduït a llegir el traceId del Tracer i retornar-lo a X-Trace-Id. Els logs històrics amb rastreId continuen sent llegibles fins que caduquen sols: no cal migrar dades.
6. Aprofitar el que es guanya. Acabada la migració, hi ha dues capacitats noves que abans no existien: les peticions que arriben amb un traceparent de l'app mòbil o d'un proxy continuen la traça en lloc de començar-ne una altra, i l'identificador dels logs és el mateix que el de les traces i el dels exemplars, de manera que les tres senyals queden unides per un únic valor.
Què es fa amb el que és antic: res d'especial. No es reescriuen els logs històrics —caduquen en 30 dies— ni es migren dades. L'únic element que exigeix cura i calendari és l'API pública, perquè és l'única part amb consumidors externs que no controlem.
Conclusió
Aquí es tanca el mòdul, i convé mirar el trajecte complet. Vam començar amb una aplicació en producció que es desplegava sola i de la qual ningú no sabia res: ni la seva latència real, ni on se n'anava el seu temps, ni què passava dins de la JVM. Acabem amb CicloUrbana instrumentada d'extrem a extrem.
D'aquesta lliçó t'endús el següent. El problema que ni les mètriques ni els logs resolen —atribuir el temps dins d'una operació— i la seva solució: descompondre cada petició en un arbre de spans cronometrats. Els conceptes de traça, span, traceId, context, atributs i mostreig, amb la idea que els resumeix: un span és un Timer amb genealogia. La propagació entre processos amb traceparent de W3C —inclosa la bandera de mostreig que manté les traces completes— davant de l'antic B3. L'estat real de l'ecosistema: Sleuth està descontinuat i el seu lloc l'ocupa Micrometer Tracing amb pont a OpenTelemetry, que és l'opció que no lliga a cap backend.
Has instrumentat la xarxa de Ribalta amb dues dependències i cinc propietats, sabent què s'instrumenta sol —HTTP entrant i sortint, @Scheduled, @Async, missatgeria, JDBC amb el complement adequat— i què cal declarar. I els spans propis els escrius amb l'Observation API de 09-03, de manera que el mateix codi produeix mètrica i traça, amb lowCardinalityKeyValue i highCardinalityKeyValue decidint què va a cada senyal. Saps unificar el rastreId propi de 03-06 amb el traceId estàndard i deixar al filtre només la responsabilitat de retornar-lo al ciutadà; saps activar exemplars per saltar d'un punt d'un gràfic a la traça concreta; i saps llegir una cascada de spans buscant la barra llarga, els buits sense instrumentar, la repetició que delata un N+1 i els spans en vermell. Tens Tempo i el Collector al docker-compose d'observabilitat, l'estratègia de mostreig que desa tots els errors i totes les lentes amb un 3 % de la resta, i les raons per posar un intermediari entre l'aplicació i el backend. I tens l'argument que faltava a 07-05: la traçabilitat és requisit previ a dividir un monòlit, no una millora posterior.
El balanç del mòdul sencer és aquest. 09-01 va ensenyar el mètode —mesurar abans de tocar, p95 i p99 en lloc de la mitjana, la línia base amb k6, i el recorregut prioritzat que comença sempre per la base de dades— i ho va demostrar portant un endpoint de 2,4 s a 91 ms sense afegir ni una sola màquina. 09-02 hi va afegir la memòria cau amb la seva regla d'or —primer arregla la consulta— i amb la seva part difícil, invalidar a AFTER_COMMIT. 09-03 va convertir l'Actuator de 07-01 en instrumentació real amb Micrometer, la regla de la cardinalitat i les mètriques de negoci de Ribalta. 09-04 les va treure del procés cap a Prometheus i Grafana, amb PromQL, quadres de comandament i alertes que avisen de símptomes i no de causes. 09-05 va convertir el log en una dada consultable, estructurada, correlacionada i neta de dades personals. I 09-06 ha tancat el cercle unint les tres senyals: la mètrica detecta, la traça localitza, el log explica, i el traceId les uneix. El cas pràctic de l'apartat 18 —d'una alerta a les 08:14 a una decisió de negoci en menys de deu minuts— és la prova que el conjunt funciona.
CicloUrbana està construïda, provada, desplegada i observada. Sabem fer-la, lliurar-la i veure-la funcionar. Queda una última pregunta, i és d'una altra naturalesa: està ben feta? Al llarg de nou mòduls hem pres desenes de decisions —on posar la lògica, com anomenar les coses, quan fer servir una anotació i quan no, què exposar i què amagar— i han aparegut patrons que es repeteixen i paranys que hem trepitjat diverses vegades: el del proxy, tres vegades; el de la cardinalitat, dues; el de no mesurar abans d'optimitzar, a totes. El mòdul 10 recull tot això: les millors pràctiques que han anat emergint, els errors comuns i com evitar-los abans de cometre'ls, els principis de codi net aplicats a un projecte Spring Boot real, un recorregut final per CicloUrbana sencera que uneix les peces dels deu mòduls en una sola visió, i els recursos per continuar aprenent quan aquest curs s'acabi. De construir la xarxa de Ribalta passem a entendre per què l'hem construïda així.
Curs de Spring Boot
Mòdul 1: Introducció a Spring Boot
- Què és Spring Boot?
- Configuració del teu entorn de desenvolupament
- Creant la teva primera aplicació Spring Boot
- Entenent l'estructura del projecte
- L'arrencada i el cicle de vida de l'aplicació
Mòdul 2: Conceptes bàsics de Spring Boot
- Anotacions de Spring Boot
- Injecció de dependències a Spring Boot
- Àmbit i cicle de vida dels beans
- Configuració de Spring Boot
- Propietats de Spring Boot
- Autoconfiguració i starters per dins
Mòdul 3: Construint serveis web RESTful
- Introducció als serveis web RESTful
- Creant controladors REST
- Gestió dels mètodes HTTP
- Validació de dades d'entrada
- DTOs i mapatge entre capes
- Gestió d'excepcions a REST
- Documentar l'API amb OpenAPI
Mòdul 4: Accés a dades amb Spring Boot
- Introducció a Spring Data JPA
- Configuració de fonts de dades
- Creació d'entitats JPA
- Relacions entre entitats
- Ús de repositoris de Spring Data
- Mètodes de consulta a Spring Data JPA
- Transaccions i gestió de la persistència
- Migracions d'esquema amb Flyway
Mòdul 5: Seguretat a Spring Boot
- Introducció a Spring Security
- Configuració de Spring Security
- Autenticació i autorització d'usuaris
- Implementació d'autenticació JWT
- Seguretat a nivell de mètode i enduriment de l'API
Mòdul 6: Proves a Spring Boot
- Introducció a les proves
- Proves unitàries amb JUnit
- Simulació amb Mockito
- Proves d'integració
- Proves amb Testcontainers
Mòdul 7: Funcions avançades de Spring Boot
- Spring Boot Actuator
- Perfils de Spring Boot
- Tasques programades i execució asíncrona
- Spring Boot amb Docker
- Spring Boot i microserveis
- Comunicació entre serveis i tolerància a fallades
Mòdul 8: Desplegament d'aplicacions Spring Boot
- Introducció al desplegament
- Desplegant a Heroku
- Desplegant a AWS
- Desplegant a Kubernetes
- Integració i lliurament continus
Mòdul 9: Rendiment i monitoratge
- Ajust de rendiment
- Memòria cau amb Spring Cache
- Monitoratge amb Spring Boot Actuator
- Ús de Prometheus i Grafana
- Gestió de registres i logs
- Traçabilitat distribuïda
