我的开发日记(十五)

时间:2022-07-22
本文章向大家介绍我的开发日记(十五),主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

❝报告一个好消息:测试至今,依然没有BUG。❞

这两天又遇到了两个坑:一个「host」缓存,一个多节点用户锁,解决方案比较现成,但是实施起来还是费了大劲。

host缓存

运行用例获取「host」,之前的方案是程勋启动把所有的「host」全部放在JVM里面,这样直接从一个map中获取「host」信息,会非常方便。后来在调试过程中发现,这样有一个弊端:无法感知到数据库数据的变化,本来想着可以做一个订阅或者通知功能,由于实施方案比较麻烦,改成了全部查库,每次查询的耗时在「20-50」ms,因为多线程运行,总体耗时增加是可以接受的。不过还是觉得「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,) > OkayConstant.HOST_TIMEOUT) || !hosts.containsKey(id)) null
        else hosts.get(id)
    }

    static String getHost(int envId, int serviceId) {
        getHost(serviceId *  + envId)
    }

    static void putHost(int envId, int serviceId, String host) {
        int key = serviceId *  + 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 == ) {
                int i = ;
                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 ;
                    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+原创文章,欢迎关注、交流,禁止第三方擅自转载。