DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Java: How Object Reuse Can Reduce Latency and Improve Performance
  • How the Go Runtime Preempts Goroutines for Efficient Concurrency
  • Real-World Garbage Collection Scenarios and Solutions
  • Using Heap Dumps to Find Memory Leaks

Trending

  • Top Book Picks for Site Reliability Engineers
  • Event-Driven Architectures: Designing Scalable and Resilient Cloud Solutions
  • Unlocking AI Coding Assistants Part 4: Generate Spring Boot Application
  • Unlocking the Benefits of a Private API in AWS API Gateway
  1. DZone
  2. Software Design and Architecture
  3. Integration
  4. Locking for Multiple Nodes the Easy Way: GCS

Locking for Multiple Nodes the Easy Way: GCS

It happens to all of us. We develop stateless applications that can scale horizontally without any effort. Find out more!

By 
Emmanouil Gkatziouras user avatar
Emmanouil Gkatziouras
DZone Core CORE ·
Oct. 22, 20 · Tutorial
Likes (3)
Comment
Save
Tweet
Share
3.8K Views

Join the DZone community and get the full member experience.

Join For Free

It happens to all of us. We develop stateless applications that can scale horizontally without any effort.

However, sometimes cases arise where you need to achieve some type of coordination.

You can go really advanced on this one. For example, you can use a framework like Akka and it’s cluster capabilities. Or you can go really simple like rolling a mechanism on your own as long as it gives you the results needed. On another note, you can just have different node groups based on the work you need them to do. The options and the solutions can change based on the problem.

If your problem can go with a simple option, one way to do so, provided you use Google Cloud Storage, is to use its lock capabilities.

Imagine for example a scenario of 4 nodes, they do scale dynamically but each time a new node registers you want to change its actions by acquiring a unique configuration that does not collide with a configuration another node might have received.

The strategy can be to use a file on Google Cloud Storage for locking and a file that acts as a centralized configuration registry.

The lock file is nothing more than a file on cloud storage which shall be created and deleted. What will give us lock abilities is the option on GCS to create a file only if it not exists.

Thus a process from one node will try to create the `lock` file, this action would be equivalent to obtaining the lock.

Once the process is done will delete the file, this action would be equivalent to releasing the lock.

Other processes in the meantime will try to create the file (acquire the lock) and fail (file already exists) because other processes have created the file.

Meanwhile, the process that has successfully created the file (acquired the lock) will change the centralized configuration registry and once done will delete the file (release the lock).

So let’s start with the lock object.

Java
 




x
43


 
1
package com.gkatzioura.gcs.lock;
2
 
3
import java.util.Optional;
4
 
5
import com.google.cloud.storage.Blob;
6
import com.google.cloud.storage.BlobInfo;
7
import com.google.cloud.storage.Storage;
8
import com.google.cloud.storage.StorageException;
9
 
10
public class GCSLock {
11
 
12
    public static final String LOCK_STRING = "_lock";
13
    private final Storage storage;
14
    private final String bucket;
15
    private final String keyName;
16
 
17
    private Optional<Blob> acquired = Optional.empty();
18
 
19
    GCSLock(Storage storage, String bucket, String keyName) {
20
        this.storage = storage;
21
        this.bucket = bucket;
22
        this.keyName = keyName;
23
    }
24
 
25
    public boolean acquire() {
26
        try {
27
            var blobInfo = BlobInfo.newBuilder(bucket, keyName).build();
28
            var blob = storage.create(blobInfo, LOCK_STRING.getBytes(), Storage.BlobTargetOption.doesNotExist());
29
            acquired = Optional.of(blob);
30
            return true;
31
        } catch (StorageException storageException) {
32
            return false;
33
        }
34
    }
35
 
36
    public void release() {
37
        if(!acquired.isPresent()) {
38
            throw new IllegalStateException("Lock was never acquired");
39
        }
40
        storage.delete(acquired.get().getBlobId());
41
    }
42
 
43
}



As you can see the write specifies to write an object only if it does not exist. This operation behind the scenes is using the x-goog-if-generation-match header which is used for concurrency.

Thus one node will be able to acquire the lock and change the configuration files.
Afterward, it can delete the lock. If an exception is raised probably the operation fails and the lock is already acquired.

To make the example more complete let’s make the configuration file. The configuration file would be a simple JSON file for keymap actions.

Java
 




xxxxxxxxxx
1
57


 
1
 
2
import java.util.HashMap;
3
import java.util.Map;
4
 
5
import com.google.cloud.storage.BlobId;
6
import com.google.cloud.storage.BlobInfo;
7
import com.google.cloud.storage.Storage;
8
import org.json.JSONObject;
9
 
10
public class GCSConfiguration {
11
 
12
    private final Storage storage;
13
    private final String bucket;
14
    private final String keyName;
15
 
16
    GCSConfiguration(Storage storage, String bucket, String keyName) {
17
        this.storage = storage;
18
        this.bucket = bucket;
19
        this.keyName = keyName;
20
    }
21
 
22
    public void addProperty(String key, String value) {
23
        var blobId = BlobId.of(bucket, keyName);
24
        var blob = storage.get(blobId);
25
 
26
        final JSONObject configJson;
27
 
28
        if(blob==null) {
29
            configJson = new JSONObject();
30
        } else {
31
            configJson = new JSONObject(new String(blob.getContent()));
32
        }
33
 
34
        configJson.put(key, value);
35
 
36
        var blobInfo = BlobInfo.newBuilder(blobId).build();
37
        storage.create(blobInfo, configJson.toString().getBytes());
38
    }
39
 
40
    public Map<String,String> properties() {
41
 
42
        var blobId = BlobId.of(bucket, keyName);
43
        var blob = storage.get(blobId);
44
 
45
        var map = new HashMap<String,String>();
46
 
47
        if(blob!=null) {
48
            var jsonObject = new JSONObject(new String(blob.getContent()));
49
            for(var key: jsonObject.keySet()) {
50
                map.put(key, jsonObject.getString(key));
51
            }
52
        }
53
 
54
        return map;
55
    }
56
 
57
}



It is simple config util backed by GCS. Eventually, it can be changed and put the lock operating inside the addProperty operation, it’s up to the user and the code. For this blog, we shall just acquire the lock change the configuration, and release the lock.
Our main class will look like this.

Java
 




xxxxxxxxxx
1
31


 
1
package com.gkatzioura.gcs.lock;
2
 
3
import com.google.cloud.storage.StorageOptions;
4
 
5
public class Application {
6
 
7
    public static void main(String[] args) {
8
        var storage = StorageOptions.getDefaultInstance().getService();
9
 
10
        final String bucketName = "bucketName";
11
        final String lockFileName = "lockFileName";
12
        final String configFileName = "configFileName";
13
 
14
        var lock = new GCSLock(storage, bucketName, lockFileName);
15
        var gcsConfig = new GCSConfiguration(storage, bucketName, configFileName);
16
 
17
        var lockAcquired = lock.acquire();
18
        if(lockAcquired) {
19
            gcsConfig.addProperty("testProperty", "testValue");
20
            lock.release();
21
        }
22
 
23
        var config = gcsConfig.properties();
24
 
25
        for(var key: config.keySet()) {
26
            System.out.println("Key "+key+" value "+config.get(key));
27
        }
28
 
29
    }
30
 
31
}



Now let’s go for some multithreading. Ten threads will try to put values, it is expected that they have some failure.

Java
 




xxxxxxxxxx
1
52


 
1
package com.gkatzioura.gcs.lock;
2
 
3
import java.util.ArrayList;
4
import java.util.concurrent.ExecutionException;
5
import java.util.concurrent.Executors;
6
import java.util.concurrent.Future;
7
 
8
import com.google.cloud.storage.Storage;
9
import com.google.cloud.storage.StorageOptions;
10
 
11
public class ApplicationConcurrent {
12
 
13
    private static final String bucketName = "bucketName";
14
    private static final String lockFileName = "lockFileName";
15
    private static final String configFileName = "configFileName";
16
 
17
    public static void main(String[] args) throws ExecutionException, InterruptedException {
18
        var storage = StorageOptions.getDefaultInstance().getService();
19
 
20
        final int threads = 10;
21
        var service = Executors.newFixedThreadPool(threads);
22
        var futures = new ArrayList<Future>(threads);
23
 
24
        for (var i = 0; i < threads; i++) {
25
            futures.add(service.submit(update(storage, "property-"+i, "value-"+i)));
26
        }
27
 
28
        for (var f : futures) {
29
            f.get();
30
        }
31
 
32
        service.shutdown();
33
 
34
        var gcsConfig = new GCSConfiguration(storage, bucketName, configFileName);
35
        var properties = gcsConfig.properties();
36
 
37
        for(var i=0; i < threads; i++) { System.out.println(properties.get("property-"+i)); } } private static Runnable update(final Storage storage, String property, String value) { return () -> {
38
            var lock = new GCSLock(storage, bucketName, lockFileName);
39
            var gcsConfig = new GCSConfiguration(storage, bucketName, configFileName);
40
 
41
            boolean lockAcquired = false;
42
 
43
            while (!lockAcquired) {
44
                lockAcquired = lock.acquire();
45
                System.out.println("Could not acquire lock");
46
            }
47
 
48
            gcsConfig.addProperty(property, value);
49
            lock.release();
50
        };
51
    }
52
}



Obviously, 10 threads are ok to display the capabilities. During the thread initialization and execution, some threads will try to acquire the lock simultaneously and one will fails, while other threads will be late and will fail and wait until the lock is available.

In the end, what is expected is for all of them to have their values added to the configuration.

That’s it. If your problems have a simple nature this approach might do the trick. Obviously, you can use the HTTP API instead of the SDK. You can find the code on Github.


Threading garbage collection

Published at DZone with permission of Emmanouil Gkatziouras, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Java: How Object Reuse Can Reduce Latency and Improve Performance
  • How the Go Runtime Preempts Goroutines for Efficient Concurrency
  • Real-World Garbage Collection Scenarios and Solutions
  • Using Heap Dumps to Find Memory Leaks

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!