「原创声明:保留所有权利,禁止转载」
报告一个好消息:测试至今,依然没有 BUG。
这两天又遇到了两个坑:一个host缓存,一个多节点用户锁,解决方案比较现成,但是实施起来还是费了大劲。
host 缓存
运行用例获取host,之前的方案是程勋启动把所有的host全部放在JVM
里面,这样直接从一个map
中获取host信息,会非常方便。后来在调试过程中发现,这样有一个弊端:无法感知到数据库数据的变化,本来想着可以做一个订阅或者通知功能,由于实施方案比较麻烦,改成了全部查库,每次查询的耗时在20-50ms,因为多线程运行,总体耗时增加是可以接受的。不过还是觉得host信息更改的机会太少了,缓存下来比较方便。现在的方案就是把查询过的,通过验证的host缓存在JVM
里面,设置一个有效期。这里没有用到线程锁,因为重复设置host不会导致程序出BUG
。
代码如下:
- service 层实现
/**
* 获取host,缓存
*
* @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;
}
- 静态类
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)) null
else 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)
}
}
分布式锁
经过同事建议,决定采用MySQL
数据库实现,新建一张表,用表的主键 ID 作为锁的key
,根据插入id=key
这条数据的存在与否作为获取锁的成功状态,删除表示释放该锁。基本方案比较现成,代码如下:
service 实现
下面是获取用户凭据的方法(非缓存)
@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;
}
}
- 下面是通过登录获取用户凭据的方法
@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);
}
}
}
}
- 下面是数据库
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';
- 公众号FunTester首发,更多原创文章:FunTester420+ 原创文章,欢迎关注、交流,禁止第三方擅自转载。
热文精选
TesterHome 为用户提供「保留所有权利,禁止转载」的选项。
除非获得原作者的单独授权,任何第三方不得转载标注了「原创声明:保留所有权利,禁止转载」的内容,否则均视为侵权。
具体请参见TesterHome 知识产权保护协议。
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
暂无回复。