Repository navigation
Expand file tree
/
Copy pathExpServiceMesh.java
More file actions
254 lines (200 loc) · 11.8 KB
/
Copy pathExpServiceMesh.java
File metadata and controls
254 lines (200 loc) · 11.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
import java.util.*;
import java.util.function.*;
/**
* Simulación del patrón Service Mesh con sidecar proxy.
*
* Componentes:
* - Sidecar: proxy que intercepta TODAS las llamadas de un servicio
* - ControlPlane: distribuye reglas de routing a todos los sidecars
* - Capacidades del sidecar: retries, timeouts, circuit breaker, mTLS simulado
*
* Por qué sidecar: la lógica de resiliencia vive en el proxy, no en el código
* de la app. La app no sabe que hay reintentos, mTLS ni circuit breakers.
*/
public class ExpServiceMesh {
// ─────────────────────────────────────────────
// ROUTING RULE (distribuida por el Control Plane)
// ─────────────────────────────────────────────
record RoutingRule(String destination, int maxRetries, long timeoutMs, int cbFailureThreshold) {}
// ─────────────────────────────────────────────
// CONTROL PLANE: distribuye configuración a sidecars
// ─────────────────────────────────────────────
static class ControlPlane {
private final Map<String, RoutingRule> rules = new LinkedHashMap<>();
private final List<Sidecar> sidecars = new ArrayList<>();
void addRule(String destination, RoutingRule rule) {
rules.put(destination, rule);
System.out.printf("[ControlPlane] Regla añadida: %s → retries=%d timeout=%dms cb=%d%n",
destination, rule.maxRetries(), rule.timeoutMs(), rule.cbFailureThreshold());
}
void registerSidecar(Sidecar sidecar) {
sidecars.add(sidecar);
}
// Push de configuración a todos los sidecars registrados (xDS protocol simulado)
void pushConfig() {
System.out.printf("%n[ControlPlane] Push de configuración a %d sidecars%n", sidecars.size());
sidecars.forEach(s -> {
rules.forEach(s::applyRule);
System.out.printf(" → Sidecar '%s' actualizado%n", s.serviceName());
});
}
RoutingRule getRule(String destination) {
return rules.get(destination);
}
}
// ─────────────────────────────────────────────
// BACKEND SERVICE SIMULADO
// ─────────────────────────────────────────────
static class Service {
private final String name;
private int callCount = 0;
private int failUntilCall = 0; // falla las primeras N llamadas
Service(String name) { this.name = name; }
void failFirstNCalls(int n) { failUntilCall = n; }
String call(String endpoint) {
callCount++;
if (callCount <= failUntilCall) {
throw new RuntimeException("Transient error (call #" + callCount + ")");
}
return String.format("{\"service\":\"%s\",\"endpoint\":\"%s\",\"call\":%d}",
name, endpoint, callCount);
}
String name() { return name; }
}
// ─────────────────────────────────────────────
// SIDECAR PROXY
// ─────────────────────────────────────────────
static class Sidecar {
private final String serviceName;
private final Map<String, RoutingRule> rules = new LinkedHashMap<>();
// Circuit breaker state por destino
private final Map<String, Integer> cbFailures = new HashMap<>();
private final Set<String> openCircuits = new HashSet<>();
Sidecar(String serviceName) {
this.serviceName = serviceName;
}
String serviceName() { return serviceName; }
void applyRule(String destination, RoutingRule rule) {
rules.put(destination, rule);
}
// Intercepta la llamada saliente, aplica retry + timeout + CB + mTLS
String outbound(String destination, String endpoint, Supplier<String> call) {
System.out.printf("%n [Sidecar '%s'] → outbound a '%s'%s%n",
serviceName, destination, endpoint);
// mTLS: simular verificación de certificado mutuo
System.out.printf(" [Sidecar] mTLS: verificando certificado de '%s'... OK%n", destination);
// Circuit breaker: rechazar si el circuito está abierto
if (openCircuits.contains(destination)) {
System.out.printf(" [Sidecar] Circuit OPEN para '%s' → rechazando%n", destination);
return "{\"error\":\"circuit open — destination unavailable\"}";
}
RoutingRule rule = rules.getOrDefault(destination,
new RoutingRule(destination, 1, 1000, 3));
int maxRetries = rule.maxRetries();
long timeoutMs = rule.timeoutMs();
for (int attempt = 1; attempt <= maxRetries; attempt++) {
System.out.printf(" [Sidecar] Intento %d/%d (timeout=%dms)%n",
attempt, maxRetries, timeoutMs);
try {
// Simular timeout: en producción sería un Future con get(timeout)
long start = System.currentTimeMillis();
String result = call.get();
long elapsed = System.currentTimeMillis() - start;
if (elapsed > timeoutMs) {
throw new RuntimeException("Timeout: " + elapsed + "ms > " + timeoutMs + "ms");
}
// Éxito: resetear contador de fallos del circuit breaker
cbFailures.remove(destination);
System.out.printf(" [Sidecar] Éxito en intento %d%n", attempt);
return result;
} catch (Exception e) {
System.out.printf(" [Sidecar] Fallo en intento %d: %s%n", attempt, e.getMessage());
// Actualizar circuit breaker
int failures = cbFailures.merge(destination, 1, Integer::sum);
System.out.printf(" [Sidecar] CB fallos acumulados para '%s': %d/%d%n",
destination, failures, rule.cbFailureThreshold());
if (failures >= rule.cbFailureThreshold()) {
openCircuits.add(destination);
System.out.printf(" [Sidecar] *** CIRCUITO ABIERTO para '%s' ***%n", destination);
return "{\"error\":\"circuit opened after " + failures + " failures\"}";
}
if (attempt == maxRetries) {
return "{\"error\":\"all retries exhausted: " + e.getMessage() + "\"}";
}
// Pequeño backoff entre reintentos (simulado sin Thread.sleep real)
System.printf(" [Sidecar] Backoff antes del siguiente intento...%n");
}
}
return "{\"error\":\"unexpected\"}";
}
}
// ─────────────────────────────────────────────
// APLICACIÓN: usa el sidecar de forma transparente
// ─────────────────────────────────────────────
static class AppServiceA {
private final Sidecar sidecar;
private final Service serviceB;
AppServiceA(Sidecar sidecar, Service serviceB) {
this.sidecar = sidecar;
this.serviceB = serviceB;
}
// La app llama al sidecar como si fuera localhost — no sabe de retries ni CB
String callServiceB(String endpoint) {
System.out.printf("%n[AppServiceA] Llamando a service-b%s (via sidecar)%n", endpoint);
return sidecar.outbound("service-b", endpoint, () -> serviceB.call(endpoint));
}
}
// ─────────────────────────────────────────────
// MAIN
// ─────────────────────────────────────────────
public static void main(String[] args) {
System.out.println("═".repeat(65));
System.out.println(" SERVICE MESH — Sidecar Pattern");
System.out.println(" Java puro, sin frameworks");
System.out.println("═".repeat(65));
ControlPlane cp = new ControlPlane();
cp.addRule("service-b", new RoutingRule("service-b", 3, 500, 3));
Sidecar sidecarA = new Sidecar("service-a");
cp.registerSidecar(sidecarA);
cp.pushConfig();
Service serviceB = new Service("service-b");
AppServiceA appA = new AppServiceA(sidecarA, serviceB);
// ── Demo 1: Retry en fallo transitorio ────────────────────────
System.out.println("\n══ DEMO 1: Retry en fallo transitorio ══");
System.out.println(" service-b falla las primeras 2 llamadas (fallo transitorio)");
serviceB.failFirstNCalls(2);
String result = appA.callServiceB("/orders/1");
System.out.printf(" Resultado: %s%n", result);
// ── Demo 2: Circuit breaker — 3 fallos consecutivos ───────────
System.out.println("\n══ DEMO 2: Circuit breaker se abre tras 3 fallos ══");
Service badService = new Service("service-b-bad");
badService.failFirstNCalls(100); // siempre falla
Sidecar sidecarB = new Sidecar("service-a-2");
cp.registerSidecar(sidecarB);
cp.pushConfig();
AppServiceA appA2 = new AppServiceA(sidecarB, badService);
// Primera llamada: 3 reintentos → todos fallan → circuito abre
System.out.println(" Primera llamada (fallos hasta abrir circuito):");
String r1 = appA2.callServiceB("/orders/2");
System.out.printf(" Resultado: %s%n", r1);
// Segunda llamada: circuito abierto → rechazada sin intentar el backend
System.out.println("\n Segunda llamada (circuito ya abierto):");
String r2 = appA2.callServiceB("/orders/3");
System.out.printf(" Resultado: %s%n", r2);
// ── Demo 3: Control Plane push de nueva configuración ─────────
System.out.println("\n══ DEMO 3: Control Plane actualiza configuración en caliente ══");
cp.addRule("service-b", new RoutingRule("service-b", 5, 2000, 10));
cp.pushConfig();
System.out.println(" → Sidecars actualizados con nuevas reglas: retries=5 timeout=2000ms cb=10");
System.out.println(" → La app NO sabe que la configuración cambió");
System.out.println("\n" + "═".repeat(65));
System.out.println(" RESUMEN SERVICE MESH");
System.out.println("═".repeat(65));
System.out.println(" Sidecar: proxy inyectado junto a cada instancia de servicio");
System.out.println(" Intercepta: TODO el tráfico entrante y saliente");
System.out.println(" Capacidades: retries, timeouts, CB, mTLS, tracing, rate limit");
System.out.println(" Control Plane: Istio/Linkerd pushea config a todos los sidecar");
System.out.println(" Beneficio: la app no implementa lógica de resiliencia — zero code");
System.out.println("═".repeat(65));
}
}