This article will share a highly available pooled Thrift Client and its source code implementation, welcome to read the source (Github) and use, while welcome to provide valuable comments and suggestions, I will continue to improve.
The main target of this article is to Thrift have a certain understanding and use of children's shoes, such as the basic knowledge of Thrift not much or want to revisit the basic knowledge, recommended first read this site article "and Thrift a beautiful encounter."
Let's get down to the chase.
Why do we need such a component?
We know that Thrift is an RPC framework system that makes it very easy to develop and invoke cross-language RPC services. However, it does not provide Smart Client "1" for multiple servers. For example, you have a service that is deployed on 116.31.1.1 and 116.31.1.2 two servers, and when you need to invoke a remote method of the service from the client side, you can only explicitly specify using 116.31.1.1 or 11 in your code. 6.31.1.2 one of them. In this case, you will not be able to predict when the service of the specified IP is available, and when the service is unavailable, you cannot automatically switch to a service that calls another IP. In other words, the state of the service is not transparent to you and cannot be load balanced and highly available for the service.
In addition, when you call the remote method, each time you have to create a new connection, when the volume of the request is very large, the constant creation, deletion of the service resources consumed by the connection is huge.
Therefore, we need such a component to make the service state transparent and the underlying load balanced and highly available, so that you can focus on the implementation of business logic, improve productivity and quality of service. The following is a detailed anatomy of the component (THRIFJ).
What can it do?
features
- Chain call API, simple and intuitive
- Complete default configuration without worrying about incorrect configuration during invocation
- Pooling connection objects to efficiently manage the life cycle of connections
- Automatic isolation and recovery of abnormal services
- A variety of configurable load balancing policies that support random, polling, weighting, and hashing
- A variety of configurable service levels and automatic service demotion based on service levels
How do I use it?
Currently the latest version is 1.0.1 (click here for the latest version of the update), first introduce Thriftj-1.0.1.jar in the project, or include in Maven dependencies:
<dependency> <groupId>com.github.cyfonly</groupId> <artifactid>thriftj</ Artifactid> <version>1.0.1</version></dependency>
It is important to note that THRIFTJ is built on slf4j, so you need to add dependencies to the project for specific log implementations, such as log4j or Logback.
Then, in the project, call it in the following code:
Thrift Server List private static final String servers = "127.0.0.1:10001,127.0.0.1:10002";//ttransport Validator Connectionvalidator validator = new Connectionvalidator () {@Override public boolean isValid (Ttransport object) { return Object.isopen (); }};//Connection Object Pool configuration generickeyedobjectpoolconfig poolconfig = new Generickeyedobjectpoolconfig ();//failover Policy Failoverstrategy failoverstrategy = new Failoverstrategy ();//Construct Thriftclient object and configure final thriftclient thriftclient = New Thriftclient (); thriftclient.servers (servers). LoadBalance (Constant.LoadBalance.RANDOM). Connecti Onvalidator (Validator). Poolconfig (Poolconfig). Failoverstrategy (failoverstrategy). connt Imeout (5). Backupservers (""). Servicelevel (Constant.ServiceLevel.NOT_EMPTY). Start ();//Print List of available services obtained from thriftclient list<thriftserver> servers = thriftclient.getavailableservers (); for (thriftserver Server:servers) {System.out.println (SerVer.gethost () + ":" + server.getport ());} Service Invocation if (Servers.size () >0) {try{testthriftj.client Client = Thriftclient.iface (TestThriftJ.Client.class) ; Qryresult result = client.qrytest (1); System.out.println ("result[code=" + Result.code + "msg=" + result.msg + "]"); }catch (Throwable t) {logger.error ("-------------exception happen", T); }}
Friendly tip: Except servers must be configured, other configurations are optional (use default configuration)
How is it designed and implemented?
Overall Design
Management of connection pool object factories and connection objects
Based on the keyedpooledobjectfactory in Commons-pool2, the Thriftserver is implemented as a value of key,ttransport. The key code is as follows:
@Overridepublic pooledobject<ttransport> makeobject (Thriftserver thriftserver) throws Exception {Tsocket Tsocke t = new Tsocket (Thriftserver.gethost (), Thriftserver.getport ()); Tsocket.settimeout (timeout); Tframedtransport transport = new Tframedtransport (tsocket); Transport.open (); defaultpooledobject<ttransport> result = new defaultpooledobject<ttransport> (transport); Logger.trace ("Make new Thrift connection: {}:{}", Thriftserver.gethost (), Thriftserver.getport ()); return result;} @Overridepublic boolean validateobject (Thriftserver thriftserver, pooledobject<ttransport> pooledobject) { Boolean isvalidate; try {if (Failoverchecker = = null) {isvalidate = Pooledobject.getobject (). IsOpen (); } else {Connectionvalidator validator = Failoverchecker.getconnectionvalidator (); Isvalidate = Pooledobject.getobject (). IsOpen () && (validator = = NULL | | validator.isvalid (POOLEDOBJECT.GEtobject ())); }} catch (Throwable e) {Logger.warn ("Fail to validate Tsocket: {}:{}", New Object[]{thriftserver.gethost (), th Riftserver.getport (), e}); Isvalidate = false; } if (Failoverchecker! = null &&!isvalidate) {failoverchecker.getfailoverstrategy (). Fail (thriftserver) ; } logger.info ("Validateobject isvalidate:{}", isvalidate); return isvalidate;} @Overridepublic void Destroyobject (Thriftserver thriftserver, pooledobject<ttransport> pooledobject) throws Exception {Ttransport transport = pooledobject.getobject (); if (transport! = null) {transport.close (); Logger.trace ("Close Thrift Connection: {}:{}", Thriftserver.gethost (), Thriftserver.getport ()); }}
When using a Connection object, create a connection pool based on the user's custom connection pool configuration and implement the Get, pool, purge, and close operations of connection objects. The key code is as follows:
Public Defaultthriftconnectionpool (Keyedpooledobjectfactory<thriftserver, Ttransport> factory, Generickeyedobjectpoolconfig config) {connections = new generickeyedobjectpool<> (factory, config);} @Overridepublic ttransport getconnection (thriftserver thriftserver) {try {return Connections.borrowobject ( Thriftserver);} catch (Exception e) {Logger.warn ("Fail to get connection for {}:{}", New Object[]{thriftserver.gethost (), thriftserver.ge Tport (), e}); throw new RuntimeException (e);}} @Overridepublic void ReturnConnection (Thriftserver thriftserver, Ttransport Transport) {Connections.returnobject ( Thriftserver, transport);} @Overridepublic void Returnbrokenconnection (Thriftserver thriftserver, Ttransport Transport) {try { Connections.invalidateobject (thriftserver, transport);} catch (Exception e) {Logger.warn ("Fail to Invalid object:{},{}", new object[] {thriftserver, transport, E});}} @Overridepublic void Close () {connections.close ();} @Overridepublic void Clear (Thriftserver thriftserver) {connections.clear (thriftserver);}
automatic isolation and recovery of abnormal services
To realize the transparency of service state, it is necessary to implement service monitoring, isolation and recovery at the bottom. In THRIFTJ, invoking Thriftclient initiates a thread that asynchronously monitors the service, and the user can specify an inspection rule (corresponding to connectionvalidator) and a Failover policy (corresponding to the configured Failoverstrategy, you can specify the number of failures, the duration of the failure, and the duration of the recovery. By default, the service validation rule determines whether the ttransport is turned on, that is:
if (This.validator = = null) {This.validator = new Connectionvalidator () {@Override public boolean isValid (TTRANSP Ort object) {return Object.isopen (); } };}
The default failover policy is
- Number of failures: 10 (times), indicating that the service is not invalidated after failing 10 times through the Connectionvalidator test and that it needs to be used together with the expiration duration
- Aging Duration: 1 (minutes), indicating that the first test failure lasts for a period of time until the value is reached before considering invalidating the service and using it together with the number of failures
- Recovery duration: 1 (minutes), indicating that after a service has been invalidated and quarantined, the service is restored after that value has been determined
The above features are based on the guava cache implementation, the key code is as follows:
/** * Use the default failover policy */public failoverstrategy () {This (Default_fail_count, default_fail_duration, Default_recover_ DURATION);} /** * Custom Failover policy * @param failcount failure times * @param failduration expiration duration * @param recoverduration recovery duration */public Failov Erstrategy (final int failcount, long failduration, long recoverduration) {this.failduration = failduration; This.failedlist = Cachebuilder.newbuilder (). Weakkeys (). Expireafterwrite (Recoverduration, TimeUnit.MILLISECONDS). Build (); This.failcountmap = Cachebuilder.newbuilder (). Weakkeys (). Build (new cacheloader<t, Evictingqueue<long >> () {@Overridepublic evictingqueue<long> load (T key) throws Exception {return evictingqueue.create ( Failcount);}});} public void Fail (T object) {logger.info ("Server {}:{} failed.", ((Thriftserver) object). GetHost (), ((Thriftserver) Object). Getport ()); Boolean addtofail = false;try {evictingqueue<long> evictingqueue = Failcountmap.get (object); Synchronized (evictingqueue) {Evictingqueue.add (System.currenTtimemillis ()); if (evictingqueue.remainingcapacity () = = 0 && evictingqueue.element () >= ( System.currenttimemillis ()-failduration)) {Addtofail = true;}}} catch (Executionexception e) {logger.error ("Ops.", e);} if (addtofail) {Failedlist.put (object, boolean.true); Logger.info ("Server {}:{} failed. Add to Fail list., ((Thriftserver) object). GetHost (), ((Thriftserver) object). Getport ());}} Public set<t> getfailed () {return Failedlist.asmap (). KeySet ();
Load Balancing
The THRIFTJ provides four optional load balancing strategies:
- Random
- Polling
- Weight
- Hash
A random algorithm is used by default when the user does not explicitly specify it. The implementation of the specific algorithm is no longer described too much here.
It is important to note that THRIFTJ strictly regulates the semantics of calls, such as the need to specify a hash key when using a hashing strategy, and when using other non-hash policies, you must not specify key, avoiding the two semantics of understanding.
Service level and service downgrade
THRIFTJ provides a variety of configurable service levels, and service downgrade processing based on service levels, with the following corresponding relationships:
- Servers_only: The highest level, only the services available in the configured SERVERS list are returned
- All_servers: Medium level, returns the available services in the Backupservers list when all services in the SERVERS list are not available
- Not_empty: Lowest level, returns all services in the Servers list when all services in the servers and Backupservers lists are not available
Where THRIFTJ is used by default, the service level is not_empty. The key code for service demotion processing is as follows:
Private List<thriftserver> Getavailableservers (Boolean all) {list<thriftserver> returnlist = new ArrayList <> (); set<thriftserver> failedservers = failoverstrategy.getfailed (); for (Thriftserver thriftserver:serverlist) {if ( !failedservers.contains (Thriftserver)) Returnlist.add (thriftserver);} if (This.servicelevel = = Constant.ServiceLevel.SERVERS_ONLY) {return returnlist;} if (all | | returnlist.isempty ()) &&!backupserverlist.isempty ()) {for (Thriftserver thriftserver: Backupserverlist) {if (!failedservers.contains (thriftserver)) Returnlist.add (Thriftserver);}} if (This.servicelevel = = Constant.ServiceLevel.ALL_SERVERS) {return returnlist;} if (Returnlist.isempty ()) {Returnlist.addall (serverlist);} return returnlist;}
I have something else to say.
Technology promotion from selfless sharing, good technology or tools to share, and will not let themselves lose anything, but can be in common research and communication to make it better. Do not worry about the tools you write good enough, do not be afraid of their own technology is not enough, who can step on the sky?
Please release your love!
"1"Smart Client: such as Mongoclient, can automatically discover Cluster service nodes, automatic failover and load balancing.
Highly available pooled Thrift Client implementations (source sharing)