Managing connections

This section covers the different approaches to connecting and disconnecting, as well as how to configure encryption.

Important: A newer version of this software with updated documentation is available. Visit the Couchbase Developer Portal for more information.

Connecting

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);

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 module 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 CouchbaseCluster and 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 ClusterEnvironment.

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.

Connecting Asynchronously

Every synchronous API is just a wrapper around an asynchronous one. To get to it, you can use one of these methods:

  • Use async() on the interface to access its Async* counterpart.
  • Instantiate a CouchbaseAsyncCluster in 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();

Disconnecting

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 Cluster instance.

Boolean disconnected = cluster.disconnect();

If the 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 CouchbaseCluster again. 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.

Especially with disconnect, make sure you wait until it is finished (so everything is shut down cleanly), either by using the toBlocking methods or by using other mechanisms in the subscriber (like a CountDownLatch to orchestrate behavior).

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.

Sharing resources

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();

Encryption

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:

  1. Load and import the certificate from the cluster into your JVM keystore
  2. 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 Settings > Cluster 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.

Note: Use the -keystore option to specify where to create the keystore. If you don't use this option, the default location is the .keystore file in user's home directory.
$ 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:

$ 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();

Depending on the OS used there are different default passwords and paths, so consult the JDK manual if you need further information on keytool (Java 7 for Windows and Unix) and the JVM keystore.

There are no other application changes needed. If you want to verify it's actually working ,you can use a tool like tcpdump. For example, an unencrypted upsert request looks like this (using sudo tcpdump -i lo0 -A -s 0 port 11210):

E..e..@.@.............+......q{...#..Y.....
.E...Ey........9........................id{"key":"value"}

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

Bootstrapping through DNS SRV (experimental)

Beginning with Java SDK version 2.1.0 you can get the actual bootstrap node list from a DNS SRV record. The following steps are necessary to make it work:

  1. Set up your DNS server to respond properly from a DNS SRV request.
  2. 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.

Note that ordering and priorities are completely ignored and should not be set on the records to avoid ambiguities. If you plan to use secure connections, you use _couchbases instead:

_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.

Now you need to enable DNS SRV on the environment and pass in the host name from your records (here example.com):

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.