Curator学习笔记(二)- 防重复提交

2022-08-11 15:43:05 浏览数 (1)

上一篇文章中我们大概了解了Curator做读写锁的原理和过程。根据了解,我们可以使用curator的读写锁来做一个分布式防重复提交的策略。为什么采用curator来做这个事情的原因是curator提供的读写锁能够跨线程和jvm进行加锁。如果不加锁,那么因为网络抖动或者线程切换,谁都不知道防重复提交的token标志是否被其他请求修改。因此这块必然要采用加锁的方式。通过锁的创建和删除来保持多个重复请求的有序性,在保证有序性之后,我们就可以按照逻辑对token进行修改,这样其他线程就能够判断自身是否为重复请求。除此之外,在加锁的时候我们采用临时znode,在会话结束之后就可以自动销毁。因此可以避免zk服务端被累计打满的情况。当然这块的会话时间是可以根据业务需求设置的。对于放重复提交的一般规则来说,我无非就是将session提取出来,而session则是和用户绑定的,因此这块我们将userId作为放重复提交的判断标志,将token表示该用户下次提交的表单的有效token,因此同一时刻,只允许同一用户提交一个表单,否则就会因为抢占token,而导致后一表单提交被认定为重复的提交(这块需要优化,下一个版本再优化!)。

以下为作者编写的相关代码。

代码语言:javascript复制
@Configuration
public class TestCurd {

    @Bean
    public CuratorFramework main() {
               //        每3秒重连一次,重连3次
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        //创建连接对象
        CuratorFramework client = CuratorFrameworkFactory.builder()
                //IP地址端口号
                .connectString("127.0.0.1:2181")
                //客户端与服务器之间的会话超时时间
                .sessionTimeoutMs(10000)
                //当客户端与服务器之间会话超时3s后,进行一次重连
                .retryPolicy(retryPolicy)
                //命名空间,当我们创建节点的时候,以/create为父节点
                .namespace("create")
                //构建连接对象
                .build();
        //打开连接
        client.start();
        //是否成功建立连接,true :建立, false:没有建立
        System.out.println(client.isStarted());
        return client;
    }
}

主要逻辑

代码语言:javascript复制
@Component
public class ReSubmitLock {


    /**
     * 进行一些操作
     */
    @Autowired
    private CuratorFramework client;

    /**
     * 防重复判断
     * @param userId
     * @param token
     * @return
     * @throws Exception
     */
    public boolean check(String userId, String token) throws Exception {
        boolean status = false;
        String mainKey="/" userId;
        InterProcessReadWriteLock interProcessReadWriteLock = new InterProcessReadWriteLock(client, mainKey);
        InterProcessLock interProcessLock = interProcessReadWriteLock.writeLock();
        // 获取锁
        try {
            interProcessLock.acquire();
            // 读取数据时读取节点的属性
            Stat stat = new Stat();
            byte[] zkToken = client.getData()
                    .storingStatIn(stat)
                    .forPath(mainKey);
            String oldToken = new String(zkToken);
            System.out.println("允许的Token:"   oldToken);
            if (token.equals(oldToken)) {
                System.out.println("校验成功!");
                generateToken(userId);
                status = true;
            }
        } catch (Exception e){
            e.printStackTrace();
            System.out.println("节点加锁产生错误");
        }finally {
            // 释放锁
            interProcessLock.release();
            System.out.println("释放锁!");
        }
        if (!status) {
            System.out.println("不能重复提交表单");
        }
        return status;
    }

    /**
     * 创建token
     */
    public String generateToken(String userId) throws Exception {
        String mainKey="/" userId;
        String newToken = UUID.randomUUID().toString();
        System.out.println("设置的新token为:"   newToken);
        client.setData()
                .forPath(mainKey, newToken.getBytes());
        return newToken;
    }
}

测试代码

代码语言:javascript复制

@RestController
@RequestMapping(value = "/zklock")
public class TestLock {

    /**
     * 防重复提交
     */
    @Autowired
    private ReSubmitLock reSubmitLock;
    /**
     * 加锁
     * @param userId
     * @param token
     * @return
     * @throws Exception
     */
    @GetMapping(value = "/ReSubmitLock/{userId}/{token}")
    public ResponseResult test1(@PathVariable String userId,@PathVariable String token) throws Exception {
        boolean f=reSubmitLock.check(userId,token);
        if (f){
            System.out.println("提交成功!");
            return ResponseResult.success("ok");
        }else{
            System.out.println("重复的表单");
            String newToken=reSubmitLock.generateToken(userId);
            return ResponseResult.error(newToken);
        }
    }

}

在浏览器中发送请求:

一些反思:通过逻辑分析,这里做的防重复提交的工具适合做单个有序接口,对一批不同接口却使用相同的token进行提交的表单就会失效。所以这块的解决办法要不就是对我们的userId作为key进行改造,使之与接口向关联,进行横向扩展。要么就对value进行改造,但是对value的改造势必很难兼容我们的读写锁。除此之外如果我们要做一个公用的放重复提交的starter,那么比较理想的还是做成注解的方式,采用aop进行代理。除此之外我们在向前端传递token的时候要对业务侵入最少,因此我们可以将token设置在response的header中,这块采用threadLocal和拦截器进行处理。

0 人点赞