-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathMicroservice.java
More file actions
288 lines (249 loc) · 11.3 KB
/
Microservice.java
File metadata and controls
288 lines (249 loc) · 11.3 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
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
package cambio.simulator.entities.microservice;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import cambio.simulator.entities.NamedEntity;
import cambio.simulator.entities.networking.InternalRequest;
import cambio.simulator.entities.patterns.*;
import cambio.simulator.export.AccumulativeDataPointReporter;
import cambio.simulator.export.MultiDataPointReporter;
import cambio.simulator.models.MiSimModel;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import desmoj.core.dist.NumericalDist;
import desmoj.core.simulator.Event;
/**
* A Microservice is one of the core Entities of the simulation. It represents the meta layer of a microservice that is
* usually present in its managing platform, e.g. CloudFoundry.
*
* <p>
* Specifically, it can take care of starting, killing and shutting down {@link MicroserviceInstance}s (in the following
* just called instances) and provides metadata to each instance. For example, a {@link Microservice} object knows which
* resilience patterns should be implemented by each instance and how many resources each instances is assigned.
* Naturally it also knows the status of all existing (including killed ones) instances of this service.
*
* <p>
* Further it has the ability to apply resilience patterns such as autoscaling and different types of load balancing to
* itself.
*
* <p>
* The interface of a {@link Microservice} is defined via its operations.
*
* @author Lion Wagner
* @see MicroserviceInstance
* @see ILoadBalancingStrategy
* @see ServiceOwnedPattern
* @see InstanceOwnedPattern
*/
public class Microservice extends NamedEntity {
private final transient Set<MicroserviceInstance> instancesSet =
new TreeSet<>(Comparator.comparingInt(MicroserviceInstance::getInstanceID));
private final transient MultiDataPointReporter reporter;
private final transient AccumulativeDataPointReporter accReporter;
@Expose
@SerializedName(value = "loadbalancer_strategy", alternate = {"load_balancer", "loadbalancer"})
private final LoadBalancer loadBalancer;
private transient boolean started = false;
private transient int instanceSpawnCounter = 0; // running counter to create instance ID's
@Expose
@SerializedName(value = "name")
private String plainName = "";
@Expose
private int capacity = 1;
@Expose
@SerializedName(value = "instances", alternate = {"starting_instance_count", "starting_instances"})
private int startingInstanceCount = 1;
@Expose
private Operation[] operations = new Operation[0];
@Expose
@SerializedName(value = "i_patterns",
alternate = {"instance_patterns", "patterns", "i_pattern", "instance_pattern"})
private InstanceOwnedPatternConfiguration[] instanceOwnedPatternConfigurations =
new InstanceOwnedPatternConfiguration[0];
@Expose
@SerializedName(value = "s_patterns", alternate = {"service_patterns", "s_pattern", "service_pattern"})
private ServiceOwnedPattern[] serviceOwnedPatterns = new ServiceOwnedPattern[0];
/**
* Creates a new instance of a {@link Microservice}.
*/
public Microservice(MiSimModel model, String name, boolean showInTrace) {
super(model, name, showInTrace);
//default load balancer
loadBalancer = new LoadBalancer(model, "Loadbalancer", traceIsOn(), null);
reporter = new MultiDataPointReporter(String.format("S[%s]_", name));
accReporter = new AccumulativeDataPointReporter(String.format("S[%s]_", name));
}
/**
* Starts this {@link Microservice}. This procedure includes the starting of the defined amount of instances and the
* initiation of the {@link ServiceOwnedPattern}s.
*/
public synchronized void start() {
started = true;
scaleToInstancesCount(startingInstanceCount);
}
@Override
public String toString() {
return this.getName();
}
@Override
public String getQuotedName() {
return "'" + this.getName() + "'";
}
public int getCapacity() {
return capacity;
}
public void setCapacity(int capacity) {
this.capacity = capacity;
}
public int getInstancesCount() {
return instancesSet.size();
}
/**
* Similar to {@link Microservice#scaleToInstancesCount(int)} but also overwrites the general target instance count
* of this service.
*
* @param numberOfInstances amount of instance that this service should target.
*/
public synchronized void setInstancesCount(final int numberOfInstances) {
startingInstanceCount = numberOfInstances;
if (started) {
scaleToInstancesCount(numberOfInstances);
}
}
/**
* Schedules the immeidate start or shutdown of {@link MicroserviceInstance}s until the amount of active instances
* reaches the target instance count.
*
* <p>
* TODO: restart instances that were shut down.
*
* @param targetInstanceCount amount of instance to which this service should scale.
*/
public synchronized void scaleToInstancesCount(final int targetInstanceCount) {
if (!started) {
throw new IllegalStateException("Microservice was not started. Use start() first or setInstanceCount()");
}
while (getInstancesCount() != targetInstanceCount) {
Event<MicroserviceInstance> changeEvent;
MicroserviceInstance changedInstance;
if (getInstancesCount() < targetInstanceCount) {
//TODO: restart shutdown instances instead of creating new ones
changedInstance =
new MicroserviceInstance(getModel(), String.format("%s_I%d", getName(), instanceSpawnCounter),
this.traceIsOn(), this, instanceSpawnCounter);
changedInstance.activatePatterns(instanceOwnedPatternConfigurations);
instanceSpawnCounter++;
changeEvent =
new InstanceStartupEvent(getModel(), "Instance Startup of " + changedInstance.getQuotedName(),
traceIsOn());
instancesSet.add(changedInstance);
} else {
//tires to find the least used instance to shut it down
changedInstance =
instancesSet.stream().min(Comparator.comparingDouble(MicroserviceInstance::getUsage)).get();
changeEvent = new InstanceShutdownStartEvent(getModel(),
String.format("Instance %s Shutdown Start", changedInstance.getQuotedName()), traceIsOn());
instancesSet.remove(changedInstance);
}
changeEvent.schedule(changedInstance, presentTime());
}
reporter.addDatapoint("InstanceCount", presentTime(), instancesSet.size());
}
/**
* Kills the given number of services many random instances. Accept numbers larger than the current amount of
* instances.
*
* @param numberOfInstances number of instances that should be instantly killed
*/
public synchronized void killInstances(final int numberOfInstances) {
final int maxKills = Math.max(0, Math.min(numberOfInstances, this.instancesSet.size()));
for (int i = 0; i < maxKills; i++) {
killInstance();
}
}
/**
* Kills a random instance. Can be called on a service that has 0 running instances.
*/
public synchronized void killInstance() {
//TODO: use UniformDistribution form desmoj
MicroserviceInstance instanceToKill =
instancesSet.stream().findFirst().orElse(null); //selects an element of the stream, not
if (instanceToKill == null) {
return;
}
instanceToKill.die();
instancesSet.remove(instanceToKill);
reporter.addDatapoint("InstanceCount", presentTime(), instancesSet.size());
}
public Operation[] getOperations() {
return operations;
}
public void setOperations(Operation[] operations) {
this.operations = operations;
}
/**
* Searches an {@code Operation} that has the name that is given as an argument. The real name of the operation may
* differ. It may starts with the name of this mircoservice instance or ands with a '#' and a number.
*
* @param name name of the operation that should be found
* @return an operation that has exactly that name, {@code null} if not found
*/
public Operation getOperationByName(String name) {
//format of the name: (this.getName()_)name(#[0-9]+), (..) being 'optional' and [..] 'pick one from'
Pattern searchPattern =
Pattern.compile(String.format("^(\\Q%s\\E_)?\\(?\\Q%s\\E\\)?(#[0-9]+)?$", this.getName(), name));
return Arrays.stream(operations)
.filter(operation -> searchPattern.matcher(operation.getName()).matches())
.findFirst()
.orElse(null);
}
/**
* Uses the loadbalancer of this microservice to find the next suitable target instance.
*
* @return a {@code MicroserviceInstance} that should receive the next request
* @throws NoInstanceAvailableException if no instance is available
*/
public MicroserviceInstance getNextAvailableInstance() throws NoInstanceAvailableException {
MicroserviceInstance nextInstance = loadBalancer.getNextInstance(instancesSet);
List<String> data = new ArrayList<>();
data.add(nextInstance.getPlainName());
accReporter.addDatapoint("Load_Distribution", presentTime(), data);
return nextInstance;
}
/**
* Applies the given delay distribution to the given operations.
*
* @param dist {@link NumericalDist} of the delay.
* @param operationSrc {@link Operation} of this {@link Microservice} that should be affected, can be set to {@code
* null} to affect all {@link Operation}s
* @param operationTrg target {@link Operation} of the operationSrc that should be affected, can be set to {@code
* null} to affect all outgoing {@link InternalRequest}s
*/
public void applyDelay(NumericalDist<Double> dist, Operation operationSrc, Operation operationTrg) {
if (operationTrg == null) {
if (operationSrc == null) {
//delay all operations
for (Operation operation : operations) {
operation.applyExtraDelay(dist);
}
return;
}
}
operationSrc.applyExtraDelay(dist, operationTrg);
}
public void finalizeStatistics() {
reporter.addDatapoint("InstanceCount", presentTime(), instancesSet.size());
}
public List<Double> getRelativeUtilizationOfInstances() {
return instancesSet.stream().map(MicroserviceInstance::getRelativeWorkDemand).collect(Collectors.toList());
}
public double getAverageRelativeUtilization() {
return instancesSet.stream().mapToDouble(MicroserviceInstance::getRelativeWorkDemand).average().orElse(0);
}
public List<Double> getUtilizationOfInstances() {
return instancesSet.stream().map(MicroserviceInstance::getUsage).collect(Collectors.toList());
}
public double getAverageUtilization() {
return getUtilizationOfInstances().stream().mapToDouble(value -> value).average().orElse(0);
}
}