前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Curator学习笔记(二)- 防重复提交

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

作者头像
写一点笔记
发布2022-08-11 15:43:05
4020
发布2022-08-11 15:43:05
举报
文章被收录于专栏:程序员备忘录

上一篇文章中我们大概了解了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和拦截器进行处理。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-02-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 写点笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档