怎么使用zookeeper限制同一个请求

本文介绍如何利用Redis实现API请求的频率限制,并通过Zookeeper解决同一请求多次提交的问题,确保服务稳定性和数据一致性。

目录

生产中,我们经常会把接口api 提供给第三方的使用者,但是 如果当调用者出现故障疯狂的调用我们的接口,或者 同一个请求发了多次,第一种 情况 会造成 服务器大量的链接被占用,造成服务挂掉,正常的服务无法提供给其他人, 第二种情况会造成 业务上很大的困扰,比如 减库存或者转账多加多减得情况,

解决调用频率太高的问题

我使用的是 redis 作为解决调用频率的问题
首先 我们将 需要 进行 访问频率的url列出来,给他们限制住 访问的间隔时间,然后在 拦截器中 使用redis 去判断每一次进来的请求 是否 符合 间隔时间 的要求,

 Integer milliSecond = Integer.parseInt(repeatMap.get(currUrl));
                logger.info("当前url={}进行防重校验,{}毫秒禁止重复请求!", currUrl, milliSecond);
                //用户ID
                String tUserId = userInfo.getUserId();
                Map<String, String> currThreadMap = new HashMap<String, String>();
                // 系统当前毫秒数
                Long currTime = System.currentTimeMillis();
                Long expireTime = currTime + new Long(milliSecond);
                Long count = jedisTemplate.STRINGS.setnx(Constants.INSTALLMENT_KEY + Constants.REPEAT_KEY + tUserId, expireTime.toString());
                if (count == 1L) {
                    logger.info("防重校验通过,不是重复提交!");
                    currThreadMap.put(LOCK, tUserId);
                } else {
                    logger.info("防重校验失败,是重复提交,需校验超时时间!");
                    String cacheExpTime = jedisTemplate.STRINGS.get(Constants.INSTALLMENT_KEY + Constants.REPEAT_KEY + tUserId);
                    if (StringUtils.isNotEmpty(cacheExpTime)) {
                        if (currTime > Long.valueOf(cacheExpTime)) {
                            logger.info("redis锁已超时,当前请求可以重新获取锁,currTime={}, cacheExpTime={}", currTime, cacheExpTime);
                            Long newExpTime = System.currentTimeMillis() + new Long(milliSecond);
                            String oldExpTime = jedisTemplate.STRINGS.getSet(Constants.INSTALLMENT_KEY + Constants.REPEAT_KEY + tUserId,
                                    newExpTime.toString());
                            if (!cacheExpTime.equals(oldExpTime)) {
                                logger.info("锁已经被其他线程获取,当前线程拒绝执行请求");
                                setResponse(response, ResCode.REQUEST_PROCESS);
                                return false;
                            }
                            logger.info("当前线程已经获得锁,可以执行请求");
                            currThreadMap.put(LOCK, tUserId);
                        } else {
                            logger.info("redis锁未超时,当前请求拒绝执行,currTime={}, cacheExpTime={}", currTime, cacheExpTime);
                            setResponse(response, ResCode.REQUEST_PROCESS);
                            return false;
                        }
                    } else {
                        logger.info("redis缓存时间为空,锁已解除,重新获取锁");
                        expireTime = currTime + new Long(milliSecond);
                        count = jedisTemplate.STRINGS.setnx(Constants.INSTALLMENT_KEY + Constants.REPEAT_KEY + tUserId, expireTime.toString());
                        if (count == 0L) {
                            logger.info("锁已经被其他线程获取,当前线程拒绝执行请求");
                            setResponse(response, ResCode.REQUEST_PROCESS);
                            return false;
                        }
                        logger.info("当前线程已经获得锁,可以执行请求");
                        currThreadMap.put(LOCK, tUserId);
                    }
                }
                threadMap.set(currThreadMap);

threadMap 是 一个 ThreadLocal,用于最后将 失效的 redis key清除

解决同一个请求的问题

我们使用是 zookpeer 的临时节点

   String path = publicKey[1] + "_" + params.get("platform") + "_" + params.get("outUserId");
            boolean lock = zkClient.getEphemeralLock(path);
            if (!lock) {
                logger.info("当前存在并发问题,获取锁失败,请求打回,path={}", publicKey[1]);
                throw new RequestException(OutApiResCode.REQUEST_CONCURRENT_WAINING.getCode(), "请求提交频率过高!");
            }

这一步可以在 验证参数的时候 使用,如果是 同一个 outUserId 那么就会有拦截的功能

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值