如果我敢买几百万辆量产的飞行汽车,你敢让我飞吗?
06-21
背景 在最近的一个项目中,需要限制一个用户在两个小时内只能访问3次,所以我简单地使用了redis来实现一个限流方案。二。
设计与实现 1.方法定义代码语言:txt复制 //RateLimiter redis实现限流器 //userId限流用户id //funcName需要是限流方法 //窗口周期,单位秒 //限制窗口Size func RateLimiter(redisClient *redis.Client, userId string, funcName string, period int64,limit int64) (ok bool) 二、实现方案 1、SortedSet+pipeline 利用redis的SortedSet数据结构实现。实现逻辑如下: 代码语言:txt copy func RateLimiter(redisClient *redis.Client, userId string, funcName string, period int64, limit int64) bool {pipeline := redisClient.Pipeline()defer pipeline.Close()key := "rate_limit:" + userId + ":" + funcName // 步骤1.获取当前微秒数 currMs := time.Now().UnixNano() / 10e3// step2.将微妙数字放入redis pipeline.ZAdd(key, redis.Z{Score: float64(currMs), Member: currMs})// step3.删除窗口外的memberpipeline.ZRemRangeByScore(key, "0", strconv.FormatInt(currMs-10e6*period, 10))//step4。
设置窗口过期时间 pipeline.Expire(key, time.Duration(period)*time.Second)// step5.计算窗口pipeline中的成员数量。ZCard(key)//提交命令cmder, err := pipeline.Exec()if err != nil {fmt.Errorf(err.Error())return false}//获取当前窗口成员个数 currentCount, err := cmder[3].(*redis.IntCmd).Result()if err != nil {fmt.Errorf(err.Error())return false}return currentCount <= limit}这种方法有一个问题,就是无论用户请求数是否达到限制,都会执行插入操作。
极端场景会导致redis内存过大。主要是因为管道不能依赖前面的命令来返回结果。
所以我决定用lua来做一轮改造。2.使用SortedSet+lua脚本完善实现逻辑,与之前基本相同。
它只是在插入之前判断窗口是否已满。如果满了,直接返回false。
实现逻辑如下: 代码语言:txt copy // 生成lua脚本 func createScript() *redis.Script {script := redis.NewScript(`local key = KEYS[1]local cur_ms = ARGV[1]local period = tonumber(ARGV[2]) 或 0local limit = tonumber(ARGV[ 3]) 或 0local t1 = redis.call('ZREMRANGEBYSCORE', key, 0, cur_ms-(10e6*period))local t2 = redis.call( 'EXPIRE',key, period)local cur_count = redis.call('ZCARD',key)if tonumber(cur_count) < limit thenlocal t3 = redis.call('ZADD',key,cur_ms,cur_ms)return trueendreturn false`) return script}// RateLimiter redis 实现限流器 // userId 限制 Streaming 用户 id // funcName 限流方法 // 窗口周期,单位为秒 // 限制窗口大小 func RateLimiter2(client *redis.Client, userId string , funcName string, period int64, limit int64) bool {script: = createScript()sha, err := script.Load(client).Result()if err != nil {fmt.Print(err.Error())} key := "rate_limit:" + userId + ":" + funcName// step1 获取当前时刻的微妙数字 currMs := time.Now().UnixNano() / 10e3ret := client.EvalSha(sha, []string{key,}, currMs, period, limit)//步骤2。获取执行结果后的结果, err := ret.Result()if err != nil {fmt.Printf("执行Redis失败: %v", err.Error())return false} else {fmt.Printf(" userid: %s, funcName:%s, result: %d", userId, funcName, result)}if result == int64(1) {return true}return false}三.测试使用goconvey对已经编写好的模块进行单元测试,测试组件功能是否正常。
代码语言:txt复制包examplesimport("github.com/go-redis/redis""time"//引入go sell库。
版权声明:本文内容由互联网用户自发贡献,本站不拥有所有权,不承担相关法律责任。如果发现本站有涉嫌抄袭的内容,欢迎发送邮件 举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。
标签:
相关文章
06-21
06-06
06-18
06-06
06-18
最新文章
【玩转GPU】ControlNet初学者生存指南
【实战】获取小程序中用户的城市信息(附源码)
包雪雪简单介绍Vue.js:开学
Go进阶:使用Gin框架简单实现服务端渲染
线程池介绍及实际案例分享
JMeter 注释 18 - JMeter 常用配置组件介绍
基于Sentry的大数据权限解决方案
【云+社区年度征文集】GPE监控介绍及使用