ID | IEP-46 |
Author | |
Sponsor | |
Created |
|
Status | COMPLETED |
Thin clients should be able to invoke Ignite Services.
This IEP is about existing services (deployed to the cluster). Deployment is out of scope.
Service invocation is a two-step process:
Existing PlatformServices.java is a good start for the server-side logic, see OP_SERVICE_PROXY and OP_INVOKE.
But for thin clients, if we create a proxy by a separate operation we should store this resource somewhere on the server-side and we should also have an ability to close this resource when it's not needed anymore. So there is an additional operation on client-side needed to close the proxy explicitly. This is more complex from users point of view than the current approach for java thick client, so perhaps for thin client it's better to do both steps in one operation and create a service proxy on each method invocation.
Benchmarking is needed to choose between these approaches.
Two new client operations are required:
Name | Code |
---|---|
OP_SERVICE_GET_PROXY | 7000 |
OP_SERVICE_INVOKE | 7001 |
Request | |
---|---|
String | Service name |
byte | Flags: 0x01 Sticky flag 0x02 Keep binary flag |
long | Timeout |
int | Count of nodes selected to invoke service. If this value is 0, no server nodes should be explicitly listed in the message, but all server cluster nodes should be selected. |
UUID * count | Node IDs |
Response | |
---|---|
long | resourceId |
Resulting resourceId should be used to call the service with OP_SERVICE_INVOKE and to release the proxy with OP_RESOURCE_CLOSE.
Request | |
---|---|
long | Resource ID |
String | Method name |
int[] | Argument type IDs to resolve method (optional, can be NULL, see "service name resolve logic") |
int | Argument count |
Object * count | Arguments |
Response | |
---|---|
Object | Result |
In this case, sticky flag is useless since we always will create a new proxy for each request.
New client operation is required:
Name | Code |
---|---|
OP_SERVICE_INVOKE | 7000 |
Request | |
---|---|
String | Service name |
byte | Flags: 0x01 Keep binary flag 0x02 "Has parameter types" flag (see "service name resolve logic") |
long | Timeout |
int | Count of nodes selected to invoke service. If this value is 0, no server nodes should be explicitly listed in the message, but all server cluster nodes should be selected. |
UUID * count | Node IDs |
String | Method name |
int | Argument count |
(int? + Object) * count | Argument type IDs to resolve method (if "has parameter types" flag is set) + Arguments |
Response | |
---|---|
Object | Result |
Benchmarks were made by Yardstick framework with 4 servers and 4 drivers (each server and driver on own hardware host). Each driver has 128 threads to invoke cluster-singleton service. Each thread has its own client. Each benchmark was run for 3 minutes with 1 minute warmup. Service proxies were created at warmup, benchmark threads just invoke method from already created proxies.
There 20 benchmarks totally were made (10 with the one-operation approach and 10 with the two-operations approach).
Results:
tps 1, op/sec, AVG | latency 1, nsec, AVG | tps 2, op/sec, AVG | latency 2, nsec, AVG | delta tps | delta latency |
---|---|---|---|---|---|
390113 | 2625103 | 393930 | 2600288 | 0,98% | -0,95% |
383307 | 2671531 | 370831 | 2762661 | -3,25% | 3,41% |
378431 | 2706381 | 382540 | 2678767 | 1,09% | -1,02% |
372316 | 2750314 | 377884 | 2711388 | 1,50% | -1,42% |
383258 | 2672952 | 380790 | 2689943 | -0,64% | 0,64% |
381941 | 2681639 | 398006 | 2573471 | 4,21% | -4,03% |
378778 | 2703999 | 379971 | 2695359 | 0,31% | -0,32% |
374484 | 2735758 | 380260 | 2694853 | 1,54% | -1,50% |
379535 | 2698315 | 375098 | 2731863 | -1,17% | 1,24% |
374196 | 2737032 | 373018 | 2746522 | -0,31% | 0,35% |
Conclusion: there is almost no difference between the one-operation approach and the two-operations approach (about 1% on average), so there is no reason to implement two-operations approach. Single-operation approach should be implemented as more convenient from the user's point of view.
In .Net thick client implementation there is a method used to find an appropriate service method to invoke by method name and args values: PlatformServices.ServiceProxyHolder#getMethod. But methods of some interface can be overloaded and using only args values we don't have full information about args types, so sometimes we can't find method only by method name and args values for overloaded methods.
For example, if you have an interface like:
public interface TestServiceInterface { public String testMethod(String val); public String testMethod(Object val); }
And invoke service like:
Object arg = null; svc.testMethod(arg);
Java will resolve the correct method to call on client-side, but using only arg value (null) it's impossible to get exactly one method on the server-side (PlatformServices.ServiceProxyHolder#getMethod will throw an error in this case: Ambiguous proxy method 'testMethod')
To solve this problem we can pass additional information (parameter types) optionally instead of just method name. If parameter types are passed - method should be resolved using this information, if parameter types are not passed method should be resolved using argument values (the same way as PlatformServices.ServiceProxyHolder#getMethod).
To pass parameter types "has parameter types" flag of the request should be set. In this case, each argument is prefixed by int value - argument's binary type ID.
The new interface should be added for thin-client services to get service proxies:
public interface ClientServices { public ClientClusterGroup clusterGroup(); public <T> T serviceProxy(String name, Class<? super T> svcItf); public <T> T serviceProxy(String name, Class<? super T> svcItf, long timeout); }
Objects of this interface can be obtained using methods from IgniteClient:
public ClientServices services(); public ClientServices services(ClientClusterGroup grp);
// Describe project risks, such as API or binary compatibility issues, major protocol changes, etc.
// Links to various reference documents, if applicable.