这里给出的令牌桶是以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作为中间的协调中间件的实现可能更加简单。

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

java redis令牌桶_redis实现的简单令牌桶相关推荐

  1. 认证令牌_Java应用程序的简单令牌认证

    认证令牌 "我喜欢编写身份验证和授权代码." 〜从来没有Web开发人员. 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验证. ...

  2. java redis 限流_Redis——限流算法之滑动窗口、漏斗限流的原理及java实现

    限流的意义 限流一般是指在一个时间窗口内对某些操作请求的数量进行限制,比如一个论坛限制用户每秒钟只能发一个帖子,每秒钟只能回复5个帖子.限流可以保证系统的稳定,限制恶意请求,防止因为流量暴增导致系统瘫 ...

  3. java redis 网络断开_Redis长时间连接后自动断开

    从日志看2小时 [DEBUG] 22:02:48.206 org.nutz.ioc.impl.NutIoc.get(NutIoc.java:151) - Get 'emailAlertService' ...

  4. java redis 主从配置_Redis实现主从复制(MasterSlave)

    Redis实现主从复制(Master&Slave) Redis主从复制 1.是什么 1.单机有什么问题: 单机故障 容量瓶颈 qps瓶颈 主机数据更新后根据配置和策略,自动同步到备机的mast ...

  5. java redis释放连接_redis在应用中使用连接不释放问题解决

    今天测试,发现redis使用的时候,调用的链接一直不释放.后查阅蛮多资料,才发现一个配置导致的.并不是他们说的服务没有启动导致的. 1)配置文件 #redis连接配置================= ...

  6. java redis工具类_redis Java工具类详解

    redis 工具 [Java]代码 private static ShardedJedisPool sharedJedisPool; public synchronized static void i ...

  7. java redis缓存使用_redis缓存在项目中的使用

    关于redis为什么能作为缓存这个问题我们就不说了,直接来说一下redis缓存到底如何在项目中使用吧: 1.redis缓存如何在项目中配置? 1.1redis缓存单机版和集群版配置?(redis的客户 ...

  8. java redis事务机制_Redis 事务机制

    Redis 事务:可以一次执行多个命令,本质是一组命令的集合.一个事务中的所有命令都会序列化,按顺序串行化执行而不会被其它命令插入,一次性.顺序性.排他性的执行一系列命令. 一.常用命令 [1] :开 ...

  9. java redis 主从 哨兵_Redis主从复制与哨兵机制

    Redis主从复制 1.redis的复制功能是支持多个数据库之间的数据同步.一类是主数据库(master)一类是从数据库(slave),主数据库可以进行读写操作,当发生写操作的时候自动将数据同步到从数 ...

  10. java redis 商品秒杀_redis编写lua脚本实现商品秒杀

    编写lua脚本 --是否秒杀 local hasBuy = redis.call('sismember',KEYS[1],ARGV[1]) if hasBuy~=0 then return 0; en ...

最新文章

  1. connot not ensure the target project location exist and is accessible
  2. 关于CSS 3 及浏览器兼容性问题
  3. YouCompleteMe unavailable: requires Vim compiled with Python 2.x support
  4. HttpClientFactory日志不好用,自己扩展一个?
  5. Android Glide图片加载框架(二)源码解析之load()
  6. appium怎么操作物理返回键_这些Appium常用元素定位技巧,你掌握了几种?
  7. PHP数组合并+与array_merge的区别分析 对多个数组合并去重技巧
  8. 用vs2011 编译 orchard 源代码
  9. 关于 CSS will-change 属性你需要知道的事
  10. Eclipse SVN插件版本
  11. 微信测试拉黑的软件,如何检测微信里有没有人把你拉黑?教你一招!
  12. plt文件怎么转化为txt文件
  13. 软件运维监控有哪些?
  14. 国开大学计算机实操,国开大学计算机实操答案一.doc
  15. 网络编程---I/O多路转接之select
  16. 使用阿里邮箱发送邮件,邮件被反垃圾系统认定为垃圾邮件,导致邮件被系统退回。
  17. Ubuntu 完全卸载软件常用命令
  18. 阔别母校35年!院士,履新北大!
  19. document.referrer和history.go(-1)退回上一页区别
  20. 本章设计了三种不同的神经网络,神经网络简答题

热门文章

  1. 3dmax 计算机中丢失,3dmax材质丢失怎么快速找回-解决3dmax材质不见了的方法 - 河东软件园...
  2. librdkafka 封装的C++类
  3. 解决wps在windows上弹窗等的流氓行为
  4. 2022天府杯国际赛数学建模题目和思路
  5. vue中引用BScroll监听上拉加载报错
  6. c# 调用浏览器打开页面
  7. DroidCam连接教程+资源
  8. RS485接口保护电路
  9. 非线性可视化(2)非线性相图
  10. 计算机专业学微机原理与接口技术,信息技术学院计算机科学与技术专业《微机原理与接口技术.doc...