I need to figure out if the following scenario is possible in Spring.
If we have different services / databases per region, can Spring facilitate directing calls to those services / databases per request from a single deployment? To give an example, all requests from user X will be directed to services / databases in the EAST region while all requests from user Y will be directed to services / databases in the WEST region.
Obviously connections to each database will use connection pooling, so the configuration will need to differ, not just properties. When other services are initialized, there is authentication done, so it's not just about databases connections.
This being Spring, I'd like to avoid having to pass implementations around. Can I direct Spring to use a specific configuration per request? Is there a better way to accomplish this?
-- Edit --
Technically it can be done like this, though this isn't exactly easily maintainable.
#Configuration
#PropertySource("classpath:region1.properties")
public class TestIndependentConfigurationRegion1Configuration {
#Bean
public String sampleServiceUrl(#Value("${sample.service.url}") String value) {
return value;
}
#Bean
public TestIndependentConfigurationSampleService testSampleService() {
return new TestIndependentConfigurationSampleService();
}
}
#Configuration
#PropertySource("classpath:region2.properties")
public class TestIndependentConfigurationRegion2Configuration {
#Bean
public String sampleServiceUrl(#Value("${sample.service.url}") String value) {
return value;
}
#Bean
public TestIndependentConfigurationSampleService testSampleService() {
return new TestIndependentConfigurationSampleService();
}
}
#Controller
public class TestIndependentConfigurationController {
protected ApplicationContext testRegion1ApplicationContext = new AnnotationConfigApplicationContext(TestIndependentConfigurationRegion1Configuration.class);
protected ApplicationContext testRegion2ApplicationContext = new AnnotationConfigApplicationContext(TestIndependentConfigurationRegion2Configuration.class);
#RequestMapping("/sample/service")
#ResponseBody
public String testSampleService() {
TestIndependentConfigurationSampleService testSampleService = null;
if(/* region 1 */) {
testSampleService = (TestIndependentConfigurationSampleService) testRegion1ApplicationContext.getBean("testSampleService");
}
if(/* region 2 */) {
testSampleService = (TestIndependentConfigurationSampleService) testRegion2ApplicationContext.getBean("testSampleService");
}
testSampleService.executeSampleService();
return "SUCCESS";
}
}
I don't think you can do that with properties. BUT, you should look at (netflix) ribbon client that is integrated with spring. Some of the ribbon's features allow you to load balance request's between regions. You could customize the ribbon client to do what you want.
Some readings here :
https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-ribbon.html
Related
I use Spring Boot 2.1.2.RELEASE, and I try to use Micrometer with CompositeMeterRegistry. My goal is to publish some selected meters to ElasticSearch. The code below shows my sample config. The problem is, that the filter is completely ignored (so all metrics are sent to ElasticSearch), although I can see in the logs that it was processed ("filter reply of meter ..." lines).
Strangely, if I define the MeterFilter as a Spring bean, then it's applied to ALL registries (however, I want it to be applied only on "elasticMeterRegistry").
Here is a sample configuration class:
#Configuration
public class AppConfiguration {
#Bean
public ElasticConfig elasticConfig() {
return new ElasticConfig() {
#Override
#Nullable
public String get(final String k) {
return null;
}
};
}
#Bean
public MeterRegistry meterRegistry(final ElasticConfig elasticConfig) {
final CompositeMeterRegistry registry = new CompositeMeterRegistry();
registry.add(new SimpleMeterRegistry());
registry.add(new JmxMeterRegistry(new JmxConfig() {
#Override
public Duration step() {
return Duration.ofSeconds(10);
}
#Override
#Nullable
public String get(String k) {
return null;
}
}, Clock.SYSTEM));
final ElasticMeterRegistry elasticMeterRegistry = new ElasticMeterRegistry(elasticConfig, Clock.SYSTEM);
elasticMeterRegistry.config().meterFilter(new MeterFilter() {
#Override
public MeterFilterReply accept(Meter.Id id) {
final MeterFilterReply reply =
id.getName().startsWith("logback")
? MeterFilterReply.NEUTRAL
: MeterFilterReply.DENY;
log.info("filter reply of meter {}: {}", id.getName(), reply);
return reply;
}
});
registry.add(elasticMeterRegistry);
return registry;
}
}
So, I expect ElasticSearch to receive only "logback" metrics, and JMX to receive all metrics.
UPDATE:
I have played with filters and found a "solution", but I don't really understand why the code above doesn't work.
This works:
elasticMeterRegistry.config().meterFilter(new MeterFilter() {
#Override
public MeterFilterReply accept(Meter.Id id) {
final MeterFilterReply reply =
id.getName().startsWith("logback")
? MeterFilterReply.ACCEPT
: MeterFilterReply.DENY;
log.info("filter reply of meter {}: {}", id.getName(), reply);
return reply;
}
});
The difference is: I return ACCEPT instead of NEUTRAL.
Strangely, the following code does not work (ES gets all metrics):
elasticMeterRegistry.config().meterFilter(
MeterFilter.accept(id -> id.getName().startsWith("logback")));
But this works:
elasticMeterRegistry.config().meterFilter(
MeterFilter.accept(id -> id.getName().startsWith("logback")));
elasticMeterRegistry.config().meterFilter(
MeterFilter.deny());
CONCLUSION:
So, it seems that instead of NEUTRAL, the filter should return ACCEPT. But for meters not starting with "logback", my original filter (with NEUTRAL) returns DENY. Then why are those metrics published to ElasticSearch registry?
Can someone explain this?
This is really a composite of questions. I'll just point out a few points.
For the MeterRegistry bean you defined, Spring Boot will auto-configure an ElasticMeterRegistry bean as there's no ElasticMeterRegistry bean. Instead of creating a CompositeMeterRegistry bean on your own, just define a custom ElasticMeterRegistry bean which is applied the MeterFilter you want and let Spring Boot create one (CompositeMeterRegistry bean) for you.
For MeterFilterReply, ACCEPT will accept the meter immediately, DENY will deny the meter immediately, and NEUTRAL will postpone the decision to next filter(s). Basically meters will be accepted unless there's any DENY.
I'm trying to figure out how to set common tags for specific metrics. NOTE: I'm using the Cloudwatch monitoring system. Here is what I have:
#Bean
public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
return new MeterRegistryCustomizer<MeterRegistry>() {
#Override
public void customize(MeterRegistry registry) {
registry.config()
.meterFilter(MeterFilter.denyNameStartsWith("jvm.gc.pause"))
.meterFilter(MeterFilter.denyNameStartsWith("logback"))
.meterFilter(MeterFilter.denyNameStartsWith("process"))
.meterFilter(MeterFilter.denyNameStartsWith("system.cpu"))
.meterFilter(MeterFilter.denyNameStartsWith("jvm.buffer"))
.meterFilter(MeterFilter.denyNameStartsWith("jvm.classes")
.commonTags(Arrays.asList(Tag.of("instanceId", instanceId)));
}
};
}
I'm thinking of a MeterFilter method similar to MeterFilter.allow("metric.name").tags("tag1","tag2")
Micrometer does allow me to set the tags at the creation of the Meter, however that does not help me with the Spring enabled Meters.
It appears the only way to do that is by creating two MeterRegistryCustomizer objects, one for the Spring metrics and any custom metrics I create that DO need the common tags and the other for those that don't.
Is there a way to accomplish this that I'm missing?
For posterity sake, here's my solution with code. The chosen answer suggested an #Autowired MeterFilter bean, but that wasn't necessary for my specific use-case.
In order to differentiate between meters that I do and do not want to have the instanceId tag, I set an "AGG" tag-key on those that I don't want to have the instanceId tag (i.e. they are metrics that will be aggregated from all instances) and then remove it.
#Bean
public MeterRegistryCustomizer<MeterRegistry> buildMeterRegistry() {
return new MeterRegistryCustomizer<MeterRegistry>() {
#Override
public void customize(MeterRegistry registry) {
registry.config()
.meterFilter(new MeterFilter() {
#Override
public Meter.Id map(Meter.Id id) {
// Check for the "AGG" tag
if (id.getTag("AGG") != null) {
log.debug("Setting an aggregate meter: {} :: {}", id.getName(), id.getTags());
// Remove the "AGG" tag
List<Tag> tags = id.getTags().stream()
.filter(tag -> !StringUtils.equalsIgnoreCase(tag.getKey(), "AGG"))
.collect(Collectors.toList());
// Create a new Meter.Id
return new Meter.Id(id.getName(), tags, id.getBaseUnit(), id.getDescription(), id.getType());
}
// Create a new Meter.Id with the instanceId tag
return new Meter.Id(id.getName(), Arrays.asList(Tag.of("instanceId", instanceId)), id.getBaseUnit(), id.getDescription(), id.getType());
}
})
.meterFilter(MeterFilter.denyNameStartsWith("jvm.gc.pause"))
.meterFilter(MeterFilter.denyNameStartsWith("logback"))
.meterFilter(MeterFilter.denyNameStartsWith("process"))
.meterFilter(MeterFilter.denyNameStartsWith("system.cpu"))
.meterFilter(MeterFilter.denyNameStartsWith("jvm.buffer"))
.meterFilter(MeterFilter.denyNameStartsWith("jvm.classes"));
}
};
}
If you want to add tags to specific meters, register MeterFilter as a bean. For an example, see the following code: https://github.com/izeye/sample-micrometer-spring-boot/blob/so-53925641/src/main/java/com/izeye/sample/config/MetricsConfig.java#L40-L52
I have a CXF client configured in my Spring Boot app like so:
#Bean
public ConsumerSupportService consumerSupportService() {
JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean();
jaxWsProxyFactoryBean.setServiceClass(ConsumerSupportService.class);
jaxWsProxyFactoryBean.setAddress("https://www.someservice.com/service?wsdl");
jaxWsProxyFactoryBean.setBindingId(SOAPBinding.SOAP12HTTP_BINDING);
WSAddressingFeature wsAddressingFeature = new WSAddressingFeature();
wsAddressingFeature.setAddressingRequired(true);
jaxWsProxyFactoryBean.getFeatures().add(wsAddressingFeature);
ConsumerSupportService service = (ConsumerSupportService) jaxWsProxyFactoryBean.create();
Client client = ClientProxy.getClient(service);
AddressingProperties addressingProperties = new AddressingProperties();
AttributedURIType to = new AttributedURIType();
to.setValue(applicationProperties.getWex().getServices().getConsumersupport().getTo());
addressingProperties.setTo(to);
AttributedURIType action = new AttributedURIType();
action.setValue("http://serviceaction/SearchConsumer");
addressingProperties.setAction(action);
client.getRequestContext().put("javax.xml.ws.addressing.context", addressingProperties);
setClientTimeout(client);
return service;
}
private void setClientTimeout(Client client) {
HTTPConduit conduit = (HTTPConduit) client.getConduit();
HTTPClientPolicy policy = new HTTPClientPolicy();
policy.setConnectionTimeout(applicationProperties.getWex().getServices().getClient().getConnectionTimeout());
policy.setReceiveTimeout(applicationProperties.getWex().getServices().getClient().getReceiveTimeout());
conduit.setClient(policy);
}
This same service bean is accessed by two different threads in the same application sequence. If I execute this particular sequence 10 times in a row, I will get a connection timeout from the service call at least 3 times. What I'm seeing is:
Caused by: java.io.IOException: Timed out waiting for response to operation {http://theservice.com}SearchConsumer.
at org.apache.cxf.endpoint.ClientImpl.waitResponse(ClientImpl.java:685) ~[cxf-core-3.2.0.jar:3.2.0]
at org.apache.cxf.endpoint.ClientImpl.processResult(ClientImpl.java:608) ~[cxf-core-3.2.0.jar:3.2.0]
If I change the sequence such that one of the threads does not call this service, then the error goes away. So, it seems like there's some sort of a race condition happening here. If I look at the logs in our proxy manager for this service, I can see that both of the service calls do return a response very quickly, but the second service call seems to get stuck somewhere in the code and never actually lets go of the connection until the timeout value is reached. I've been trying to track down the cause of this for quite a while, but have been unsuccessful.
I've read some mixed opinions as to whether or not CXF client proxies are thread-safe, but I was under the impression that they were. If this actually not the case, and I should be creating a new client proxy for each invocation, or use a pool of proxies?
Turns out that it is an issue with the proxy not being thread-safe. What I wound up doing was leveraging a solution kind of like one posted at the bottom of this post: Is this JAX-WS client call thread safe? - I created a pool for the proxies and I use that to access proxies from multiple threads in a thread-safe manner. This seems to work out pretty well.
public class JaxWSServiceProxyPool<T> extends GenericObjectPool<T> {
JaxWSServiceProxyPool(Supplier<T> factory, GenericObjectPoolConfig poolConfig) {
super(new BasePooledObjectFactory<T>() {
#Override
public T create() throws Exception {
return factory.get();
}
#Override
public PooledObject<T> wrap(T t) {
return new DefaultPooledObject<>(t);
}
}, poolConfig != null ? poolConfig : new GenericObjectPoolConfig());
}
}
I then created a simple "registry" class to keep references to various pools.
#Component
public class JaxWSServiceProxyPoolRegistry {
private static final Map<Class, JaxWSServiceProxyPool> registry = new HashMap<>();
public synchronized <T> void register(Class<T> serviceTypeClass, Supplier<T> factory, GenericObjectPoolConfig poolConfig) {
Assert.notNull(serviceTypeClass);
Assert.notNull(factory);
if (!registry.containsKey(serviceTypeClass)) {
registry.put(serviceTypeClass, new JaxWSServiceProxyPool<>(factory, poolConfig));
}
}
public <T> void register(Class<T> serviceTypeClass, Supplier<T> factory) {
register(serviceTypeClass, factory, null);
}
#SuppressWarnings("unchecked")
public <T> JaxWSServiceProxyPool<T> getServiceProxyPool(Class<T> serviceTypeClass) {
Assert.notNull(serviceTypeClass);
return registry.get(serviceTypeClass);
}
}
To use it, I did:
JaxWSServiceProxyPoolRegistry jaxWSServiceProxyPoolRegistry = new JaxWSServiceProxyPoolRegistry();
jaxWSServiceProxyPoolRegistry.register(ConsumerSupportService.class,
this::buildConsumerSupportServiceClient,
getConsumerSupportServicePoolConfig());
Where buildConsumerSupportServiceClient uses a JaxWsProxyFactoryBean to build up the client.
To retrieve an instance from the pool I inject my registry class and then do:
JaxWSServiceProxyPool<ConsumerSupportService> consumerSupportServiceJaxWSServiceProxyPool = jaxWSServiceProxyPoolRegistry.getServiceProxyPool(ConsumerSupportService.class);
And then borrow/return the object from/to the pool as necessary.
This seems to work well so far. I've executed some fairly heavy load tests against it and it's held up.
Using Springboot 1.5.x, Spring Cloud, and JAX-RS:
I could use a second pair of eyes since it is not clear to me whether the Spring configured, Javanica HystrixCommand works for all use cases or whether I may have an error in my code. Below is an approximation of what I'm doing, the code below will not actually compile.
From below WebService lives in a library with separate package path to the main application(s). Meanwhile MyWebService lives in the application that is in the same context path as the Springboot application. Also MyWebService is functional, no issues there. This just has to do with the visibility of HystrixCommand annotation in regards to Springboot based configuration.
At runtime, what I notice is that when a code like the one below runs, I do see "commandKey=A" in my response. This one I did not quite expect since it's still running while the data is obtained. And since we log the HystrixRequestLog, I also see this command key in my logs.
But all the other Command keys are not visible at all, regardless of where I place them in the file. If I remove CommandKey-A then no commands are visible whatsoever.
Thoughts?
// Example WebService that we use as a shared component for performing a backend call that is the same across different resources
#RequiredArgsConstructor
#Accessors(fluent = true)
#Setter
public abstract class WebService {
private final #Nonnull Supplier<X> backendFactory;
#Setter(AccessLevel.PACKAGE)
private #Nonnull Supplier<BackendComponent> backendComponentSupplier = () -> new BackendComponent();
#GET
#Produces("application/json")
#HystrixCommand(commandKey="A")
public Response mainCall() {
Object obj = new Object();
try {
otherCommandMethod();
} catch (Exception commandException) {
// do nothing (for this example)
}
// get the hystrix request information so that we can determine what was executed
Optional<Collection<HystrixInvokableInfo<?>>> executedCommands = hystrixExecutedCommands();
// set the hystrix data, viewable in the response
obj.setData("hystrix", executedCommands.orElse(Collections.emptyList()));
if(hasError(obj)) {
return Response.serverError()
.entity(obj)
.build();
}
return Response.ok()
.entity(healthObject)
.build();
}
#HystrixCommand(commandKey="B")
private void otherCommandMethod() {
backendComponentSupplier
.get()
.observe()
.toBlocking()
.subscribe();
}
Optional<Collection<HystrixInvokableInfo<?>>> hystrixExecutedCommands() {
Optional<HystrixRequestLog> hystrixRequest = Optional
.ofNullable(HystrixRequestLog.getCurrentRequest());
// get the hystrix executed commands
Optional<Collection<HystrixInvokableInfo<?>>> executedCommands = Optional.empty();
if (hystrixRequest.isPresent()) {
executedCommands = Optional.of(hystrixRequest.get()
.getAllExecutedCommands());
}
return executedCommands;
}
#Setter
#RequiredArgsConstructor
public class BackendComponent implements ObservableCommand<Void> {
#Override
#HystrixCommand(commandKey="Y")
public Observable<Void> observe() {
// make some backend call
return backendFactory.get()
.observe();
}
}
}
// then later this component gets configured in the specific applications with sample configuraiton that looks like this:
#SuppressWarnings({ "unchecked", "rawtypes" })
#Path("resource/somepath")
#Component
public class MyWebService extends WebService {
#Inject
public MyWebService(Supplier<X> backendSupplier) {
super((Supplier)backendSupplier);
}
}
There is an issue with mainCall() calling otherCommandMethod(). Methods with #HystrixCommand can not be called from within the same class.
As discussed in the answers to this question this is a limitation of Spring's AOP.
We are building an API for our service and we would like to leverage Spring Data Rest as much as possible.
This API and the new model underneath will substitute a legacy API (and it's old model) that we still need to support.
Our idea is to build an "adapter" web app that replicates the structure of the old api and serve the old model using some internal transformations.
Also the old api is using Spring Data Rest, so here the idea:
build a repository implementation that instead of querying a database will query our brand new API, retrieve the new model, apply some transformations, and return the old model.
Unfortunately, even if I'm annotating the repository implementation with the #Repository annotation, Spring is not exposing the repository in the API.
I'm not sure if this is actually something possible to do or is just a matter of me not implementing some core functionalities.
What I would like to avoid is reimplement all spring data rest methods manually in a controller.
Here my Repository class
// Method are not implemented, this is just the backbone
#Repository
public class SampleRespositoryImpl implements ReadOnlyRepository<OldSample, String> {
NewApiClient client;
public SampleRespositoryImpl(NewApiClient client) {
this.client = client;
}
#Override
public OldSample findOne(String accession) {
NewSample newSample = client.fetch(accession)
OldSample oldSample = //apply transformation to newSample
return oldSample;
}
#Override
public boolean exists(String accession) {
return client.fetch(accession) != null;
}
#Override
public Iterable<OldSample> findAll() {
return new ArrayList<>();
}
#Override
public Iterable<OldSample> findAll(Iterable<String> var1) {
return new ArrayList<>();
}
#Override
public long count() {
return 0;
}
#Override
public Iterable<OldSample> findAll(Sort var1) {
return new ArrayList<>();
}
#Override
public Page<OldSample> findAll(Pageable var1) {
List<OldSample> OldSampleList = new ArrayList<>();
Page<OldSample> page = new PageImpl<>(OldSampleList);
return page;
}
}
Here what I would like to get back when I hit the api root (http://localhost:8080/)
{
"_links": {
"samples": {
"href": "http://localhost:8080/samples{?page,size,sort}
}
}
}
Someone else linked me to another answer in StackOverflow available here as possible duplication.
Reading through that answer, I decided that is too much effort to follow this path for our needs, so I'm more oriented to create a custom controller to expose necessary methods.
This solution was reported by Kevin as answer to Implementing methods of Spring Data repository and exposing them through REST