FunTester 我的开发日记 (十五)

FunTester · July 16, 2020 · 551 hits

报告一个好消息:测试至今,依然没有 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+ 原创文章,欢迎关注、交流,禁止第三方擅自转载。

热文精选

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
No Reply at the moment.
需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up