Implementing Host Cache and MySQL‑Based Distributed Lock in Java Backend Services
This article explains how to implement a host cache within the JVM and a MySQL‑based distributed lock in a Java backend service, including detailed code examples for the service layer, a static cache class, lock acquisition, and release logic.
Good news: after extensive testing, no bugs have been found yet. Recently two challenges were encountered: a host cache and a multi‑node user lock. The following sections describe practical solutions for both.
Host Cache
The initial approach stored all host information in a JVM‑wide Map, which made retrieval convenient but could not detect changes in the underlying database. To avoid stale data, the solution was changed to query the database each time, incurring a 20‑50 ms latency that is acceptable under multithreaded execution. The final design caches verified host entries in the JVM with an expiration time, eliminating the need for explicit thread locks because re‑caching the same host does not cause bugs.
Service layer implementation:
/**
* Get host, with cache
*
* @param envId
* @param service_id
* @return
*/
@Override
public String getHost(int envId, int service_id) {
String host = ServerHost.getHost(envId, service_id);
if (StringUtils.isBlank(host)) {
host = commonMapper.getHost(envId, service_id);
if (StringUtils.isBlank(host) || !host.startsWith("http"))
CommonException.fail("服务ID:{},环境ID:{}域名配置错误");
ServerHost.putHost(envId, service_id, host);
}
return host;
}Static cache class:
package com.okay.family.common.basedata
import com.okay.family.fun.frame.SourceCode
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.util.concurrent.ConcurrentHashMap
class ServerHost extends SourceCode {
private static Logger logger = LoggerFactory.getLogger(ServerHost.class)
static Map<Integer, String> hosts = new ConcurrentHashMap<>()
static Map<Integer, Integer> timeout = new ConcurrentHashMap<>()
public static String getHost(int id) {
if ((getMark() - timeout.getOrDefault(id, 0) > OkayConstant.HOST_TIMEOUT) || !hosts.containsKey(id))
return null
else
return hosts.get(id)
}
static String getHost(int envId, int serviceId) {
getHost(serviceId * 10 + envId)
}
static void putHost(int envId, int serviceId, String host) {
int key = serviceId * 10 + envId
timeout.put(key, getMark())
hosts.put(key, host)
}
}Distributed Lock
A MySQL table is used as the lock store. The primary‑key ID of the table serves as the lock key. Inserting a row with id = key indicates successful lock acquisition; deleting the row releases the lock. The approach is straightforward and the code below demonstrates the implementation.
Service implementation for obtaining a user certificate (without cache):
@Override
@Transactional(isolation = Isolation.REPEATABLE_READ, propagation = Propagation.REQUIRES_NEW)
public TestUserCheckBean getCertificate(int id) {
Object o = UserLock.get(id);
synchronized (o) {
TestUserCheckBean user = testUserMapper.findUser(id);
if (user == null) UserStatusException.fail("用户不存在,ID:" + id);
String create_time = user.getCreate_time();
long create = Time.getTimestamp(create_time);
long now = Time.getTimeStamp();
if (now - create < OkayConstant.CERTIFICATE_TIMEOUT && user.getStatus() == UserState.OK.getCode())
return user;
boolean b = UserUtil.checkUserLoginStatus(user);
if (!b) {
updateUserStatus(user);
} else {
testUserMapper.updateUserStatus(user);
}
return user;
}
}Service implementation for updating user status with lock handling:
@Override
@Transactional(isolation = Isolation.REPEATABLE_READ, propagation = Propagation.REQUIRES_NEW)
public int updateUserStatus(TestUserCheckBean bean) {
Object o = UserLock.get(bean.getId());
int userLock = NodeLock.getUserLock(bean.getId());
synchronized (o) {
int lock = commonService.lock(userLock);
if (lock == 0) {
int i = 0;
while (true) {
SourceCode.sleep(OkayConstant.WAIT_INTERVAL);
TestUserCheckBean user = testUserMapper.findUser(bean.getId());
String create_time = user.getCreate_time();
long create = Time.getTimestamp(create_time);
long now = Time.getTimeStamp();
if (now - create < OkayConstant.CERTIFICATE_TIMEOUT && user.getStatus() == UserState.OK.getCode())
return 1;
i++;
if (i > OkayConstant.WAIT_MAX_TIME) {
UserStatusException.fail("获取分布式锁超时,导致无法更新用户凭据:id:" + bean.getId());
}
}
} else {
try {
UserUtil.updateUserStatus(bean);
int i = testUserMapper.updateUserStatus(bean);
return i;
} finally {
commonService.unlock(userLock);
}
}
}
}SQL definition of the lock table:
CREATE TABLE `qa_lock` (
`id` bigint(20) unsigned NOT NULL COMMENT '锁key',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分布式锁表-QA-FunTester-20200715';The article was originally published on the "FunTester" public account, which hosts many original technical posts.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
