Managing Connections using the Java SDK with Couchbase Server
This section describes how to connect the Java SDK to a Couchbase cluster and bucket. It contains best practices as well as information on SSL and other advanced connection options.
Connecting to a bucket is a two-step process: first, a
CouchbaseCluster object needs to be initialized, followed by one or more calls to openBucket():
Cluster cluster = CouchbaseCluster.create(); Bucket bucket = cluster.openBucket();
If no further arguments are applied, this code connects to
localhost and opens the default bucket.
While this is suitable for development, you most probably want to connect to a remote cluster and also a different bucket (with a password):
Cluster cluster = CouchbaseCluster.create("192.168.56.101", "192.168.56.102"); Bucket bucket = cluster.openBucket("myapp", "password");
Alternatively, a list of nodes can be passed in:
List<String> nodes = Arrays.asList("192.168.56.101", "192.168.56.102"); Cluster cluster = CouchbaseCluster.create(nodes);
The Java SDK also offers limited support for creating a cluster from a Couchbase connection string:
String connectionString = "couchbase://192.168.56.101,192.168.56.102"; CouchbaseCluster cluster = CouchbaseCluster.fromConnectionString(connectionString);
|When creating a cluster from a connection string, the Java SDK ignores the scheme component; if a secure connection is desired, the Couchbase environment must be configured as described in Connecting with SSL. The Java SDK also ignores any options in the connection string, and will not work with a connection string that specifies a bucket name.|
It is very important in a production setup to pass in at least two or three nodes of the cluster because if the first one in the list fails the other ones can be tried. After initial contact is made, the bootstrap list provided by the user is thrown away and replaced with a list provided by the server (which contains all nodes of the cluster).
More buckets can be open at the same time if needed:
Bucket bucket1 = cluster.openBucket("bucket1", "password"); Bucket bucket2 = cluster.openBucket("bucket2", "password");
If more than one bucket is open at a time, the underlying
core-io library makes sure to utilize the resources as best as possible, that is sharing sockets, thread pools and so on.
Here are some very important things to keep in mind:
Always create only one instance of a
CouchbaseClusterand share it across threads (same with buckets).
The SDK is thread-safe, so no additional synchronization is needed when interacting with the SDK.
If different clusters need to be accessed, reuse the
CouchbaseEnvironment(See Scalability and Concurrency).
If the underlying environment is not shared the application will continue to work, but a warning is raised in the logs:
WARNING: More than 1 Couchbase Environments found (N), this can have severe impact on performance and stability. Reuse environments!
In general, this is an indication of misconfiguration, the only exception being unit and integration tests where multiple paths are executed at once and cannot share the
ClusterEnvironment for whatever reason.
You are strongly recommended against using Round Robin DNS, which can return a different address at each connection, as Java uses
The most common case is to disconnect the whole
CouchbaseCluster from the server, which has the same effect as closing all buckets manually and in addition close all underlying resources like thread pools.
This also means that once disconnect has happened, you can’t reopen buckets from the same
Boolean disconnected = cluster.disconnect();
ClusterEnvironment is shared it needs to be closed manually, but if not (which is the regular case) it gets also shut down.
After a disconnect, it is not possible to open buckets again, so only use it when you are sure that you do not need access to a
If you only want to close a bucket, you can do that without shutting down everything:
Boolean closed = bucket.close();
This will release only the resources allocated for this bucket and it is possible to reopen it at a later point.
All threads used by the SDK are daemon threads, so even if you do not
disconnect() manually, your JVM will exit.
It is very important that you properly shut down the SDK so that remaining operations are finished and nothing is left behind.
If no timeout is used for the synchronous
disconnect() method, the environment
disconnect timeout is used, which defaults to 25 seconds.
This holds also true if you call
close() on a bucket.
Every synchronous API is just a wrapper around an asynchronous one. To get to it, you can use one of these methods:
async()on the interface to access its
CouchbaseAsyncClusterin the first place.
So if you are connecting to the bucket synchronously but then want to switch over to asynchronous data operations, you can do it like this:
Cluster cluster = CouchbaseCluster.create(); Bucket bucket = cluster.openBucket(); // Same API as Bucket, but completely async with Observables AsyncBucket asyncBucket = bucket.async();
On the other hand, you can use the Async API right from the beginning:
AsyncCluster cluster = CouchbaseAsyncCluster.create(); Observable<AsyncBucket> bucketObservable = cluster.openBucket();
As mentioned previously, you should create only one instance of a CouchbaseCluster and open buckets from there.
If you need to connect to multiple clusters though, this is not going to work.
To keep things efficient, you should share the
CouchbaseEnvironment object between those instances:
CouchbaseEnvironment env = DefaultCouchbaseEnvironment.create(); CouchbaseCluster cluster1 = CouchbaseCluster.create(env, "192.168.56.1"); CouchbaseCluster cluster2 = CouchbaseCluster.create(env, "192.168.57.10");
This ensures that the resources in the environment (thread pools for I/O and computation) are reused. Note that if you pass in your custom environment, it needs to be shut down manually because it’s not under the complete control of any cluster. The recommended approach is to first shut down all clusters and after this is done shut down the environment (here using asynchronous code, but you can also loop synchronously):
Observable .just(cluster1, cluster2) .flatMap(CouchbaseCluster::disconnect) .last() .flatMap(aBoolean -> env.shutdown()) .toBlocking() .single();
Couchbase Server EE 3.0 and later supports full encryption of client-side traffic. That includes key-value type operations, queries, and configuration communication. Make sure to have a proper Couchbase Server version installed before proceeding with configuring encryption on the client side.
To configure encryption for the Java SDK:
Load and import the certificate from the cluster into your JVM keystore
Enable encryption on the client side and point it to the keystore
The JVM keystore is independent of the Java SDK, so your own setup might look different. It is important to make sure you are transferring the certificate in an encrypted manner from the server to the client side, so either copy it through SSH or through a similar secure mechanism.
If you are running on
localhost and just want to enable it for a development machine, just copying and pasting it suffices.
Navigate in the admin UI to and copy the input box of the SSL certificate into a file on your machine (here named cluster.cert).
It looks similar to this:
-----BEGIN CERTIFICATE----- MIICmDCCAYKgAwIBAgIIE4FSjsc3nyIwCwYJKoZIhvcNAQEFMAwxCjAIBgNVBAMT ASowHhcNMTMwMTAxMDAwMDAwWhcNNDkxMjMxMjM1OTU5WjAMMQowCAYDVQQDEwEq MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzz2I3Gi1XcOCNRVYwY5R ................................................................ mgDnQI8nw2arBRoseLpF6WNw22CawxHVOlMceQaGOW9gqKNBN948EvJJ55Dhl7qG BQp8sR0J6BsSc86jItQtK9eQWRg62+/XsgVCmDjrB5owHPz+vZPYhsMWixVhLjPJ mkzeUUj/kschgQ0BWT+N+pyKAFFafjwFYtD0e5NwFUUBfsOyQtYV9xu3fw+T2N8S itfGtmmlEfaplVGzGPaG0Eyr53g5g2BgQbi5l5Tt2awqhd22WOVbCalABd9t2IoI F4+FjEqAEIr1mQepDaNM0gEfVcgd2SzGhC3yhYFBAH//8W4DUot5ciEhoBs= -----END CERTIFICATE-----
Now, use the
keytool command to import it into your JVM keystore.
$ keytool -importcert -file cluster.cert Enter keystore password: Owner: CN=* Issuer: CN=* Serial number: 1381528ec7379f22 Valid from: Tue Jan 01 01:00:00 CET 2013 until: Sat Jan 01 00:59:59 CET 2050 Certificate fingerprints: MD5: 4A:5E:DB:4F:F6:7E:FD:C3:0E:0C:56:C4:05:34:C1:4A SHA1: 3A:BC:48:3C:0F:36:99:EB:35:76:7C:E5:14:DE:89:DE:AE:79:9B:ED SHA256: 24:46:59:55:F2:65:23:85:E2:80:9F:CC:D1:EF:41:E9:4E:D8:ED:11:C8:CF:60:C7:C5:AD:63:56:D0:E6:7F:4D Signature algorithm name: SHA1withRSA Version: 3 Trust this certificate? [no]: yes Certificate was added to keystore
You can verify with
$ keytool -list Enter keystore password: Keystore type: JKS Keystore provider: SUN Your keystore contains 1 entry mykey, Aug 18, 2014, trustedCertEntry, Certificate fingerprint (SHA1): 3A:BC:48:3C:0F:36:99:EB:35:76:7C:E5:14:DE:89:DE:AE:79:9B:ED
The next step is to enable encryption and pass it the path and password of the file.
CouchbaseEnvironment env = DefaultCouchbaseEnvironment .builder() .sslEnabled(true) .sslKeystoreFile("/path/tokeystore") .sslKeystorePassword("password") .build();
Alternatively, since 2.3.0 it is possible to point it directly to a keystore that has been previously initialized so it can be created from different sources:
KeyStore myKeystore = loadKeystore(); CouchbaseEnvironment env = DefaultCouchbaseEnvironment.builder() .sslEnabled(true) .sslKeystore(myKeystore) .build();
There are no other application changes needed.
If you want to verify it’s actually working ,you can use a tool like
For example, an unencrypted upsert request looks like this (using
sudo tcpdump -i lo0 -A -s 0 port 11210):
After enabling encryption, you cannot inspect the traffic in cleartext (same upsert request, but watched on port 11207 which is the default encrypted port):
E.....@.@.............+....Z.'yZ..#........ ..... ...xuG.O=.#.........?.Q)8..D...S.W.4.-#....@7...^.Gk.4.t..C+......6..)}......N..m..o.3...d.,. ...W.....U.. .%v.....4....m*...A.2I.1.&.*,6+..#..#.5
As an alternative to specifying multiple hosts in your program, you can get the actual bootstrap node list from a DNS SRV record. The following steps are necessary to make it work:
Set up your DNS server to respond properly from a DNS SRV request.
Enable it on the SDK and point it towards the DNS SRV entry.
Your DNS server should be set up like this (one row for each bootstrap node):
_couchbase._tcp.example.com. 3600 IN SRV 0 0 0 node1.example.com. _couchbase._tcp.example.com. 3600 IN SRV 0 0 0 node2.example.com. _couchbase._tcp.example.com. 3600 IN SRV 0 0 0 node3.example.com.
|The ordering, priorities, ports and weighting are completely ignored and should not be set on the records to avoid ambiguities.|
If you plan to use secure connections, you use
_couchbases._tcp.example.com. 3600 IN SRV 0 0 0 node1.example.com. _couchbases._tcp.example.com. 3600 IN SRV 0 0 0 node2.example.com. _couchbases._tcp.example.com. 3600 IN SRV 0 0 0 node3.example.com.
DNS SRV bootstrapping is available in the Java SDK from version 2.1.0.
In order to make the SDK actually use the SRV records, you need to enable DNS SRV on the environment and pass in the host name from your records (here
CouchbaseEnvironment env = DefaultCouchbaseEnvironment .builder() .dnsSrvEnabled(true) .build(); Cluster cluster = CouchbaseCluster.create(env, "example.com");
If the DNS SRV records could not be loaded properly you’ll get the exception logged and the given host name will be used as a A record lookup.
WARNING: DNS SRV lookup failed, proceeding with normal bootstrap. javax.naming.NameNotFoundException: DNS name not found [response code 3]; remaining name '_couchbase._tcp.example.com' at com.sun.jndi.dns.DnsClient.checkResponseCode(DnsClient.java:651) at com.sun.jndi.dns.DnsClient.isMatchResponse(DnsClient.java:569)
Also, if you pass in more than one node, DNS SRV bootstrap will not be initiated:
INFO: DNS SRV enabled, but less or more than one seed node given. Proceeding with normal bootstrap.