{"id":56804,"date":"2023-02-22T13:48:24","date_gmt":"2023-02-22T08:18:24","guid":{"rendered":"https:\/\/www.tothenew.com\/blog\/?p=56804"},"modified":"2023-02-23T13:59:23","modified_gmt":"2023-02-23T08:29:23","slug":"distributed-locks-in-spring-boot-microservice-environment","status":"publish","type":"post","link":"https:\/\/www.tothenew.com\/blog\/distributed-locks-in-spring-boot-microservice-environment\/","title":{"rendered":"Distributed Locks in Spring-boot Microservice Environment"},"content":{"rendered":"<p>Suppose we are in an environment where only one instance is running in production. We want to do an account update and synchronise the transaction where we are doing the update. This can easily be achieved with the help of Reentrant Locks api\u2019s of java, as shown below.<\/p>\n<pre><span class=\"hljs-meta\">@Service<\/span>\r\n<span class=\"hljs-meta\">@RequiredArgsConstructor<\/span>\r\n<span class=\"hljs-meta\">@Slf4j<\/span>\r\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title.class\">AccountService<\/span> {\r\n<span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> AccountRepository accountRepository;\r\n<span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-type\">ReentrantLock<\/span> <span class=\"hljs-variable\">lock<\/span> <span class=\"hljs-operator\">=<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-title.class\">ReentrantLock<\/span>();\r\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title.function\">updateAccount<\/span><span class=\"hljs-params\">(Long id)<\/span> <span class=\"hljs-keyword\">throws<\/span> InterruptedException {\r\n<span class=\"hljs-type\">boolean<\/span> <span class=\"hljs-variable\">lockAquired<\/span> <span class=\"hljs-operator\">=<\/span> lock.tryLock();\r\n<span class=\"hljs-keyword\">if<\/span>(lockAquired){\r\n<span class=\"hljs-keyword\">try<\/span>{\r\nlog.info(<span class=\"hljs-string\">\"lock taken\"<\/span>);\r\n<span class=\"hljs-type\">Account<\/span> <span class=\"hljs-variable\">account<\/span> <span class=\"hljs-operator\">=<\/span> accountRepository.findById(id).get();\r\naccount.setBalance(account.getBalance() + <span class=\"hljs-number\">100L<\/span>);\r\nThread.sleep(<span class=\"hljs-number\">20_000<\/span>);\r\naccountRepository.save(account);\r\n}\r\n<span class=\"hljs-keyword\">finally<\/span> {\r\nlock.unlock();\r\n}\r\n}\r\n}\r\n}<\/pre>\n<p id=\"312b\" class=\"pw-post-body-paragraph rd re rf iy b rg rh fr ri rj rk fv rl rm rn ro rp rq rr rs rt ru rv rw rx ry hw bi\" data-selectable-paragraph=\"\">Now consider the scenario where multiple instances of this service are running in production, which is quite common in today\u2019s microservice environment. The above method fails as lock is applicable for each instance and we want some kind of mechanism to take a lock on something common across all instances of this microservice. What could be better than a table in our database which is common across all instances. By taking this into cue, Spring has provided distributed locks. Let see how we can configure our distributed locks in Spring Boot.<\/p>\n<p data-selectable-paragraph=\"\">We would require the below dependencies in our pom.xml.<\/p>\n<pre><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\r\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>org.springframework.boot<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>\r\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>spring-boot-starter-integration<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>\r\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\r\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\r\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>org.springframework.integration<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>\r\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>spring-integration-jdbc<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>\r\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\r\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\r\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>org.springframework.boot<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>\r\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>spring-boot-starter-jdbc<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>\r\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dependency<\/span>&gt;<\/span><\/pre>\n<p id=\"0df2\" class=\"pw-post-body-paragraph rd re rf iy b rg rh fr ri rj rk fv rl rm rn ro rp rq rr rs rt ru rv rw rx ry hw bi\" data-selectable-paragraph=\"\">In our configuration file, we need following beans to be setup<\/p>\n<pre class=\"sa sb sc sd py sk sl sm bn sn so bi\"><span class=\"hljs-meta\">@Bean<\/span>\r\n<span class=\"hljs-keyword\">public<\/span> DefaultLockRepository <span class=\"hljs-title.function\">DefaultLockRepository<\/span><span class=\"hljs-params\">(DataSource dataSource)<\/span>{\r\n<span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-title.class\">DefaultLockRepository<\/span>(dataSource);\r\n}\r\n\r\n<span class=\"hljs-meta\">@Bean<\/span>\r\n<span class=\"hljs-keyword\">public<\/span> JdbcLockRegistry <span class=\"hljs-title.function\">jdbcLockRegistry<\/span><span class=\"hljs-params\">(LockRepository lockRepository)<\/span>{\r\n<span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-title.class\">JdbcLockRegistry<\/span>(lockRepository);\r\n}\r\n\r\nNow, coming back to our service, we need to inject first LockRegistry\r\n\r\n<span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> LockRegistry lockRegistry; <span class=\"hljs-comment\">\/\/ this is injected automatically if we use @RequiredArgsConstructor of lombok<\/span>\r\n\r\n<span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> JdbcTemplate jdbcTemplate;\r\n\r\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title.function\">updateAccountViaDistributedLocks<\/span><span class=\"hljs-params\">(Long id)<\/span> <span class=\"hljs-keyword\">throws<\/span> InterruptedException {\r\n<span class=\"hljs-type\">var<\/span> <span class=\"hljs-variable\">lock<\/span> <span class=\"hljs-operator\">=<\/span> lockRegistry.obtain(String.valueOf(id));\r\n<span class=\"hljs-type\">boolean<\/span> <span class=\"hljs-variable\">lockAquired<\/span> <span class=\"hljs-operator\">=<\/span> lock.tryLock();\r\n<span class=\"hljs-keyword\">if<\/span>(lockAquired){\r\n<span class=\"hljs-keyword\">try<\/span>{\r\nlog.info(<span class=\"hljs-string\">\"lock taken\"<\/span>);\r\n<span class=\"hljs-type\">Account<\/span> <span class=\"hljs-variable\">account<\/span> <span class=\"hljs-operator\">=<\/span> accountRepository.findById(id).get();\r\naccount.setBalance(account.getBalance() + <span class=\"hljs-number\">100L<\/span>);\r\nThread.sleep(<span class=\"hljs-number\">20_000<\/span>);\r\naccountRepository.save(account);\r\n}\r\n<span class=\"hljs-keyword\">finally<\/span> {\r\nlock.unlock();\r\n}\r\n}\r\n}\r\n\r\n<\/pre>\n<p id=\"93a4\" class=\"pw-post-body-paragraph rd re rf iy b rg rh fr ri rj rk fv rl rm rn ro rp rq rr rs rt ru rv rw rx ry hw bi\" data-selectable-paragraph=\"\">We have first got the lock on id, and this is a distributed lock which is being taken on INT_LOCK table.<\/p>\n<p id=\"bc91\" class=\"pw-post-body-paragraph rd re rf iy b rg rh fr ri rj rk fv rl rm rn ro rp rq rr rs rt ru rv rw rx ry hw bi\" data-selectable-paragraph=\"\">We need to create a table in sql database with the following script.<\/p>\n<pre><span class=\"hljs-keyword\">CREATE<\/span> <span class=\"hljs-keyword\">TABLE<\/span> `INT_LOCK` (\r\n`LOCK_KEY` <span class=\"hljs-type\">char<\/span>(<span class=\"hljs-number\">36<\/span>) <span class=\"hljs-keyword\">NOT<\/span> <span class=\"hljs-keyword\">NULL<\/span>,\r\n`REGION` <span class=\"hljs-type\">varchar<\/span>(<span class=\"hljs-number\">100<\/span>) <span class=\"hljs-keyword\">NOT<\/span> <span class=\"hljs-keyword\">NULL<\/span>,\r\n`CLIENT_ID` <span class=\"hljs-type\">char<\/span>(<span class=\"hljs-number\">36<\/span>) <span class=\"hljs-keyword\">DEFAULT<\/span> <span class=\"hljs-keyword\">NULL<\/span>,\r\n`CREATED_DATE` <span class=\"hljs-type\">timestamp<\/span> <span class=\"hljs-keyword\">NOT<\/span> <span class=\"hljs-keyword\">NULL<\/span>,\r\n<span class=\"hljs-keyword\">PRIMARY<\/span> KEY (`LOCK_KEY`,`REGION`)\r\n) ENGINE<span class=\"hljs-operator\">=<\/span>InnoDB <span class=\"hljs-keyword\">DEFAULT<\/span> CHARSET<span class=\"hljs-operator\">=<\/span>utf8mb4 <span class=\"hljs-keyword\">COLLATE<\/span><span class=\"hljs-operator\">=<\/span>utf8mb4_0900_ai_ci;<\/pre>\n<p data-selectable-paragraph=\"\">Now let\u2019s create the apis for demo purposes. We have created two apis one for update account via simple locks and other with distributed locks<\/p>\n<pre><span class=\"hljs-variable\">@PostMapping<\/span>(<span class=\"hljs-string\">\"\/updateAccount\/{id}\"<\/span>)\r\npublic void <span class=\"hljs-built_in\">updateAccount<\/span>(<span class=\"hljs-variable\">@PathVariable<\/span>(<span class=\"hljs-string\">\"id\"<\/span>) Long id) throws InterruptedException {\r\n<span class=\"hljs-selector-tag\">accountService<\/span><span class=\"hljs-selector-class\">.updateAccount<\/span>(id);\r\n}\r\n\r\n@<span class=\"hljs-selector-tag\">PostMapping<\/span>(<span class=\"hljs-string\">\"\/updateAccountViaDistributedLocks\/{id}\"<\/span>)\r\n<span class=\"hljs-selector-tag\">public<\/span> <span class=\"hljs-selector-tag\">void<\/span> <span class=\"hljs-selector-tag\">updateAccountViaDistributedLocks<\/span>(<span class=\"hljs-variable\">@PathVariable<\/span>(<span class=\"hljs-string\">\"id\"<\/span>) Long id) <span class=\"hljs-selector-tag\">throws<\/span> <span class=\"hljs-selector-tag\">InterruptedException<\/span> {\r\n<span class=\"hljs-selector-tag\">accountService<\/span><span class=\"hljs-selector-class\">.updateAccountViaDistributedLocks<\/span>(id);\r\n}<\/pre>\n<p data-selectable-paragraph=\"\">Let\u2019s see our application.yaml<\/p>\n<pre><span class=\"hljs-attr\">spring:<\/span>\r\n<span class=\"hljs-attr\">datasource:<\/span>\r\n<span class=\"hljs-attr\">driver-class-name:<\/span> <span class=\"hljs-string\">com.mysql.cj.jdbc.Driver<\/span>\r\n<span class=\"hljs-attr\">maxIdle:<\/span> <span class=\"hljs-number\">1<\/span>\r\n<span class=\"hljs-attr\">url:<\/span> <span class=\"hljs-string\">jdbc:mysql:\/\/localhost:3306\/distributed_locks<\/span>\r\n<span class=\"hljs-attr\">testWhileIdle:<\/span> <span class=\"hljs-literal\">true<\/span>\r\n<span class=\"hljs-attr\">username:<\/span> <span class=\"hljs-string\">root<\/span>\r\n<span class=\"hljs-attr\">password:<\/span> <span class=\"hljs-string\">root1234<\/span>\r\n<span class=\"hljs-attr\">jpa:<\/span>\r\n<span class=\"hljs-attr\">database:<\/span> <span class=\"hljs-string\">mysql<\/span>\r\n<span class=\"hljs-attr\">generate-ddl:<\/span> <span class=\"hljs-literal\">true<\/span>\r\n<span class=\"hljs-attr\">properties:<\/span>\r\n<span class=\"hljs-attr\">hibernate:<\/span>\r\n<span class=\"hljs-attr\">dialect:<\/span> <span class=\"hljs-string\">org.hibernate.dialect.MySQL55Dialect<\/span>\r\n<span class=\"hljs-attr\">jdbc:<\/span>\r\n<span class=\"hljs-attr\">time_zone:<\/span> <span class=\"hljs-string\">UTC<\/span>\r\n<span class=\"hljs-attr\">hibernate:<\/span>\r\n<span class=\"hljs-attr\">ddl-auto:<\/span> <span class=\"hljs-string\">update<\/span>\r\n<span class=\"hljs-attr\">show-sql:<\/span> <span class=\"hljs-literal\">true<\/span>\r\n<span class=\"hljs-attr\">server:<\/span>\r\n<span class=\"hljs-attr\">port:<\/span> <span class=\"hljs-number\">0<\/span><\/pre>\n<p id=\"2088\" class=\"pw-post-body-paragraph rd re rf iy b rg rh fr ri rj rk fv rl rm rn ro rp rq rr rs rt ru rv rw rx ry hw bi\" data-selectable-paragraph=\"\">We have server.port as 0 so that we can have multiple instances of our service running on random ports.<\/p>\n<p id=\"09fa\" class=\"pw-post-body-paragraph rd re rf iy b rg rh fr ri rj rk fv rl rm rn ro rp rq rr rs rt ru rv rw rx ry hw bi\" data-selectable-paragraph=\"\"><strong class=\"iy ic\">Testing<\/strong><\/p>\n<p id=\"7cfa\" class=\"pw-post-body-paragraph rd re rf iy b rg rh fr ri rj rk fv rl rm rn ro rp rq rr rs rt ru rv rw rx ry hw bi\" data-selectable-paragraph=\"\">Run the below curls which is using simple locks.<\/p>\n<pre>curl --location --request POST <span class=\"hljs-string\">'localhost:57841\/monolith\/updateAccount\/1'<\/span>\r\ncurl --location --request POST <span class=\"hljs-string\">'localhost:57896\/monolith\/updateAccount\/1'<\/span><\/pre>\n<p id=\"49ac\" class=\"pw-post-body-paragraph rd re rf iy b rg rh fr ri rj rk fv rl rm rn ro rp rq rr rs rt ru rv rw rx ry hw bi\" data-selectable-paragraph=\"\">If we check the data in our Account table we would see that our data will be updated once only even though we have hit the apis twice. Our database is in a state we do not know(inconsistent state).<\/p>\n<p id=\"5d09\" class=\"pw-post-body-paragraph rd re rf iy b rg rh fr ri rj rk fv rl rm rn ro rp rq rr rs rt ru rv rw rx ry hw bi\" data-selectable-paragraph=\"\">Now, hit the below two curls for which are using distributed locks<\/p>\n<pre>curl --location --request POST <span class=\"hljs-string\">'localhost:57841\/monolith\/updateAccountViaDistributedLocks\/1'<\/span>\r\ncurl --location --request POST <span class=\"hljs-string\">'localhost:57896\/monolith\/updateAccountViaDistributedLocks\/1'<\/span><\/pre>\n<p id=\"bc8b\" class=\"pw-post-body-paragraph rd re rf iy b rg rh fr ri rj rk fv rl rm rn ro rp rq rr rs rt ru rv rw rx ry hw bi\" data-selectable-paragraph=\"\">We would see that our account has been updated twice properly. The database will be in a consistent state.<\/p>\n<p id=\"6dd8\" class=\"pw-post-body-paragraph rd re rf iy b rg rh fr ri rj rk fv rl rm rn ro rp rq rr rs rt ru rv rw rx ry hw bi\" data-selectable-paragraph=\"\">That\u2019s all from my side. Please find the the link of this project on Github:<\/p>\n<p data-selectable-paragraph=\"\">https:\/\/github.com\/anilgola90\/distributedlock<\/p>\n<p data-selectable-paragraph=\"\">\n<p data-selectable-paragraph=\"\">This blog is originally published on <a href=\"https:\/\/medium.com\/@anil.java.story\/distributed-locks-in-spring-boot-microservice-environment-f11dad3cc378\">Medium<\/a>.<\/p>\n<div class=\"ap-custom-wrapper\"><\/div><!--ap-custom-wrapper-->","protected":false},"excerpt":{"rendered":"<p>Suppose we are in an environment where only one instance is running in production. We want to do an account update and synchronise the transaction where we are doing the update. This can easily be achieved with the help of Reentrant Locks api\u2019s of java, as shown below. @Service @RequiredArgsConstructor @Slf4j public class AccountService { [&hellip;]<\/p>\n","protected":false},"author":1520,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":75},"categories":[446],"tags":[5124,1202],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/56804"}],"collection":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/users\/1520"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/comments?post=56804"}],"version-history":[{"count":2,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/56804\/revisions"}],"predecessor-version":[{"id":56809,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/56804\/revisions\/56809"}],"wp:attachment":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/media?parent=56804"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/categories?post=56804"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/tags?post=56804"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}