这里给出的令牌桶是以redis单节点或者集群为中间件. 不过, 这里的实现比较简单, 主要提供两个函数, 一个用于消费令牌, 一个用于添加令牌. 这里, 消费令牌和添加令牌都是通过lua来保证原子性.

消费令牌的代码如下 :

// FetchToken 用来获取某个key的一个令牌
func (acc *Accessor) FetchToken(key string) (bool, error) {
/*
* KEYS[1] 表示特定的key, 这个key是当前的令牌数
*/
keyFetchScript :=
`--[[测试显示, 通过call, 可以将error返回给客户端, 即使没有使用return]]--
local curNum = redis.call("DECR", KEYS[1])
if (curNum >= 0)
then
return true
end
redis.call("INCR", KEYS[1])
return false
` keyFetchCmd := redis.NewScript(keyFetchScript)
res, err := keyFetchCmd.Run(acc.client, []string{key}).Result()
if err != nil && err != redis.Nil {
return false, err
} if res == redis.Nil {
return false, nil
} if val, ok := res.(int64); ok {
return (val == 1), nil
} return false, errors.New("res should be bool")
}

这里每一个key都有一个辅助的key_idx, 每次增加key的令牌数, 都会使key_idx的值加1, 同时这个函数调用会返回对应的key_idx的值. 如果传入的idx的值与key_idx值不相同, 则不会执行增加令牌数的操作. 这样设计的目的是, 如果你在不同机器中启动多个增加令牌数的程序, 而且这些程序启动时间不同, 那么其中一个程序将会起到增加令牌数的效果, 而另外的程序不会新增令牌数. 当增加令牌数的这个程序意外关闭, 将会有新的增加令牌的程序起作用. 这个实现的思想类似于乐观锁.具体代码如下:

// AddToken 用来添加某个key的令牌
func (acc *Accessor) AddToken(key string, keyIdx int, keyAdd int, keyLimit int) (int, error) {
/* KEYS[1] 表示特定key,这个key是当前的令牌
* KEYS[2] 表示特定key的idx
* ARGV[1] 表示修改的key的增加的值
* ARGV[2] 表示修改的key的最大值
* ARGV[3] 表示修改的key的idx的序号
*/
// 实现思路, 先判断这个key当前的序号与修改调用的序号是否一致,如果一致, 则进行修改,否则返回当前的序号
keyAddScript :=
`--[[测试显示, 通过call, 可以将error返回给客户端, 即使没有使用return]]--
local curIdx = redis.call("INCR", KEYS[2])
if (curIdx ~= (ARGV[3]+1))
then
curIdx = redis.call("DECR", KEYS[2])
return curIdx
end
local curNum = redis.call("INCRBY", KEYS[1], ARGV[1])
local maxNum = tonumber(ARGV[2])
if (curNum > maxNum)
then
redis.call("SET", KEYS[1], ARGV[2])
end
return curIdx
`
keyAddCmd := redis.NewScript(keyAddScript)
res, err := keyAddCmd.Run(acc.client, []string{key, getKeyIdx(key)},
keyAdd, keyLimit, keyIdx).Result()
if err != nil && err != redis.Nil {
return 0, err
} if idx, ok := res.(int64); ok {
return int(idx), nil
} return 0, errors.New("res should be integer")
}
 

假设现在有多个节点,例如有20个节点,我希望每次都有3个节点作为添加令牌桶的节点,那么这个怎么实现呢?

/*
* 作用: 判断这个节点是否用于新增键令牌(以下称为: 加令牌节点),
* 从而实现每个redis(或者redis集群)总是有N个节点(例如2个或者3个)用于添加令牌操作
* 我们可能采用多种方式获取所有的key, 然后向对应的key增加令牌, 例如
* (1) 通过数据库获取所有键值
* (2) 通过遍历redis获取所有键值
* (3) 直接读取配置文件
* (4) 通过远程调用设置
* 判断方式通过如下实现:
* (1) 实现这个判断需要在redis中使用一个键值存储信息, 这里使用"{}"的键值, 这个键存储所有的加令牌节点
* 1) 这个键使用类型为list
* 2) 这个list中存储的值为是否可用标示+":"+节点标识符(可用时, 为"1:节点标识符")
* (2) 程序启动时, 监听这个键的修改操作
* 1) "加令牌节点"监听对这个键的包括LREM,LSET和PUSH类型的消息
* 2) 非"加令牌节点"监听对这个键的包括LREM类型的消息
* (3) 然后, 读取redis中键名为"{}"的键的值, 然后判断当前"加令牌节点"的个数, 如果这个个数小于配置值,
* 则修改redis中这个键的值,将自己设为"加令牌节点", 否则, 不做处理
* (4) 对于每个设置为"加令牌节点"的应用, 会在这个list中排在后面的M个节点(例如2个或者3个)建立tcp连接,
* 然后通过ping和pong消息, 来判断这个节点是否可以连接.
* (5) 如果A节点发现B节点不可连接(假设每秒发送一条消息,经过20次没有发送成功), 向redis发送一条修改请求,
* 请求中先查看B节点是否可用,如果B节点当前显示可用, 那么修改"{}"的值,设置1+":"+节点标识符为
* 0+":"+节点标识符(B节点), 如果显示B节点已经不可用, 则继续进行tcp通信, (如果B节点已经不存在,
* 则断开与这个节点的tcp连接,按照当前逻辑,这个应该不会出现)
* (6) 节点继续等待若干时间(例如15s), 在期间查看这个节点是否已经被重置正常,如果恢复正常,则继续进行tcp通信,
* 查看是否存在问题,如果没有恢复正常,从这个list中清除这个节点
* (7) 那些非"加令牌节点"接收到清楚的消息之后, 会申请自己成为"加令牌节点",会检测当前"加令牌节点"的个数,
* 如果条件满足, 则将这个节点信息插入, 让这个节点成为"加令牌节点", 然后, 每个当前"加令牌节点"
* 读取所有的"加令牌节点", 重新更新与哪些节点建立tcp连接.
* (8) 当前标识符为Ip:port
*/

完整的代码请参考如下地址:

https://github.com/ss-torres/ratelimiter.git

在这个git地址中, 如果想调用db_test.go中的测试, 可以参考如下命令:

go test ratelimiter/db -args "localhost:6379" "" "hello"

当前这个实现,没有充分利用redis的提供的功能,按照我的观点来看,使用redis的subscribe和publish的实现可能更加简单,之后可能会提供使用subscribe和publish的实现,然后再进行必要的详细测试和benchmark。另外,使用zookeeper作为中间的协调中间件的实现可能更加简单。

如果有什么好的建议, 或者有什么问题, 欢迎提出。

redis实现的简单令牌桶的更多相关文章

  1. Redis令牌桶限流

    一 .场景描述 在开发接口服务器的过程中,为了防止客户端对于接口的滥用,保护服务器的资源, 通常来说我们会对于服务器上的各种接口进行调用次数的限制.比如对于某个 用户,他在一个时间段(interval ...

  2. 令牌桶限流思路分享(PHP+Redis实现机制)

    一 .场景描述 在开发接口服务器的过程中,为了防止客户端对于接口的滥用,保护服务器的资源, 通常来说我们会对于服务器上的各种接口进行调用次数的限制.比如对于某个 用户,他在一个时间段(interval ...

  3. 使用Redis实现令牌桶算法

    在限流算法中有一种令牌桶算法,该算法可以应对短暂的突发流量,这对于现实环境中流量不怎么均匀的情况特别有用,不会频繁的触发限流,对调用方比较友好. 例如,当前限制10qps,大多数情况下不会超过此数量, ...

  4. php 基于redis使用令牌桶算法 计数器 漏桶算法 实现流量控制

    通常在高并发和大流量的情况下,一般限流是必须的.为了保证服务器正常的压力.那我们就聊一下几种限流的算法. 计数器计数器是一种最常用的一种方法,在一段时间间隔内,处理请求的数量固定的,超的就不做处理. ...

  5. 基于令牌桶算法实现的SpringBoot分布式无锁限流插件

    本文档不会是最新的,最新的请看Github! 1.简介 基于令牌桶算法和漏桶算法实现的纳秒级分布式无锁限流插件,完美嵌入SpringBoot.SpringCloud应用,支持接口限流.方法限流.系统限 ...

  6. 限流10万QPS、跨域、过滤器、令牌桶算法-网关Gateway内容都在这儿

    一.微服务网关Spring Cloud Gateway 1.1 导引 文中内容包含:微服务网关限流10万QPS.跨域.过滤器.令牌桶算法. 在构建微服务系统中,必不可少的技术就是网关了,从早期的Zuu ...

  7. ASP.NET Core中使用令牌桶限流

    在限流时一般会限制每秒或每分钟的请求数,简单点一般会采用计数器算法,这种算法实现相对简单,也很高效,但是无法应对瞬时的突发流量. 比如限流每秒100次请求,绝大多数的时间里都不会超过这个数,但是偶尔某 ...

  8. QoS令牌桶工作原理

    QoS的一个重要作用就是对port流量进行监管,也就是限制port流量.但QoS是怎样做到这点的呢?那就是QoS的令牌桶机制了.以下是在笔者刚刚出版的<Cisco/H3C交换机高级配置与管理技术 ...

  9. CIR,CBS,EBS,PIR,PBS傻傻分不清楚?看这里!—-揭秘令牌桶

    概述 春暖花开的时候,大家都开着汽车外出旅游欣赏美丽的风景,却被堵在高速公路上,你是否为此感到痛苦?但如果有一种机制可以评估高速公路上的车流量.控制车流情况,确保进入高速公路的汽车都能在路上安全畅行, ...

随机推荐

  1. python2.7报错Non-ASCII character '\xe5' in file的解决方法

    在文件首行加#coding=utf-8,一定要在最顶行添加

  2. Mac下 python2和python3共存

    一般是python2默认安装了,python3没有安装,这时候一般使用命令:brew install python3 进行安装 不同方法安装python的路径是不一样的,如下所示: 接下来就要看具体步 ...

  3. osg指定向量旋转指定角度

    向量AB,沿着n旋转10度 osg::Vec3 left = AB*osg::Matrix::rotate(osg::inDegrees(10), n); osg::Vec3 right = AB*o ...

  4. docker安装并运行ngnix

    拉取nginx最新版本的镜像: [mall@VM_0_7_centos ~]$ sudo docker pull nginx:latest [sudo] password for mall: late ...

  5. Coder 健康 知识

  6. [LeetCode] 3.Longest Substring Without Repeating Characters 最长无重复子串

    Given a string, find the length of the longest substring without repeating characters. Example 1: In ...

  7. [LeetCode] 265. Paint House II 粉刷房子

    There are a row of n houses, each house can be painted with one of the k colors. The cost of paintin ...

  8. [LeetCode] 680. Valid Palindrome II 验证回文字符串 II

    Given a non-empty string s, you may delete at most one character. Judge whether you can make it a pa ...

  9. MySQL之SQL语句的使用

    SQL使用 mysql中的基本逻辑对象 mysql有这么几种对象 mysqld--->库---->表---->记录(由行和列组成)一条记录中的一列叫做字段 什么是关系型数据库 表与表 ...

  10. hive学习(1)

    什么是Hive Hive是基于Hadoop的一个数据仓库工具(E抽取T转换L加载),可以将结构化的数据文件映射为一张表,并提供类SQL查询功能. 本质是:将HQL转化成MapReduce程序 Hive ...