forked from huangzworks/redis
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathincr.html
245 lines (219 loc) · 13 KB
/
incr.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>INCR — Redis 命令参考</title>
<link rel="stylesheet" href="../_static/pyramid.css" type="text/css" />
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT: '../',
VERSION: '2.8',
COLLAPSE_INDEX: false,
FILE_SUFFIX: '.html',
HAS_SOURCE: true
};
</script>
<script type="text/javascript" src="../_static/jquery.js"></script>
<script type="text/javascript" src="../_static/underscore.js"></script>
<script type="text/javascript" src="../_static/doctools.js"></script>
<link rel="top" title="Redis 命令参考" href="../index.html" />
<link rel="up" title="String(字符串)" href="index.html" />
<link rel="next" title="INCRBY" href="incrby.html" />
<link rel="prev" title="GETSET" href="getset.html" />
<!--[if lte IE 6]>
<link rel="stylesheet" href="../_static/ie6.css" type="text/css" media="screen" charset="utf-8" />
<![endif]-->
</head>
<body>
<div class="related">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="../genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="incrby.html" title="INCRBY"
accesskey="N">next</a> |</li>
<li class="right" >
<a href="getset.html" title="GETSET"
accesskey="P">previous</a> |</li>
<li><a href="../index.html">Redis 命令参考</a> »</li>
<li><a href="index.html" accesskey="U">String(字符串)</a> »</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body">
<div class="section" id="incr">
<span id="id1"></span><h1>INCR<a class="headerlink" href="#incr" title="Permalink to this headline">¶</a></h1>
<p><strong>INCR key</strong></p>
<p>将 <code class="docutils literal"><span class="pre">key</span></code> 中储存的数字值增一。</p>
<p>如果 <code class="docutils literal"><span class="pre">key</span></code> 不存在,那么 <code class="docutils literal"><span class="pre">key</span></code> 的值会先被初始化为 <code class="docutils literal"><span class="pre">0</span></code> ,然后再执行 <a class="reference internal" href="#incr">INCR</a> 操作。</p>
<p>如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。</p>
<p>本操作的值限制在 64 位(bit)有符号数字表示之内。</p>
<div class="admonition note">
<p class="first admonition-title">Note</p>
<p class="last">这是一个针对字符串的操作,因为 Redis 没有专用的整数类型,所以 key 内储存的字符串被解释为十进制 64 位有符号整数来执行 INCR 操作。</p>
</div>
<dl class="docutils">
<dt><strong>可用版本:</strong></dt>
<dd>>= 1.0.0</dd>
<dt><strong>时间复杂度:</strong></dt>
<dd>O(1)</dd>
<dt><strong>返回值:</strong></dt>
<dd>执行 <a class="reference internal" href="#incr">INCR</a> 命令之后 <code class="docutils literal"><span class="pre">key</span></code> 的值。</dd>
</dl>
<div class="highlight-python"><div class="highlight"><pre>redis> SET page_view 20
OK
redis> INCR page_view
(integer) 21
redis> GET page_view # 数字值在 Redis 中以字符串的形式保存
"21"
</pre></div>
</div>
<div class="section" id="id2">
<h2>模式:计数器<a class="headerlink" href="#id2" title="Permalink to this headline">¶</a></h2>
<p>计数器是 Redis 的原子性自增操作可实现的最直观的模式了,它的想法相当简单:每当某个操作发生时,向 Redis 发送一个 <a class="reference internal" href="#incr">INCR</a> 命令。</p>
<p>比如在一个 web 应用程序中,如果想知道用户在一年中每天的点击量,那么只要将用户 ID 以及相关的日期信息作为键,并在每次用户点击页面时,执行一次自增操作即可。</p>
<p>比如用户名是 <code class="docutils literal"><span class="pre">peter</span></code> ,点击时间是 2012 年 3 月 22 日,那么执行命令 <code class="docutils literal"><span class="pre">INCR</span> <span class="pre">peter::2012.3.22</span></code> 。</p>
<p>可以用以下几种方式扩展这个简单的模式:</p>
<ul class="simple">
<li>可以通过组合使用 <a class="reference internal" href="#incr">INCR</a> 和 <a class="reference internal" href="../key/expire.html#expire"><span>EXPIRE</span></a> ,来达到只在规定的生存时间内进行计数(counting)的目的。</li>
<li>客户端可以通过使用 <a class="reference internal" href="getset.html#getset"><span>GETSET</span></a> 命令原子性地获取计数器的当前值并将计数器清零,更多信息请参考 <a class="reference internal" href="getset.html#getset"><span>GETSET</span></a> 命令。</li>
<li>使用其他自增/自减操作,比如 <a class="reference internal" href="decr.html#decr"><span>DECR</span></a> 和 <a class="reference internal" href="incrby.html#incrby"><span>INCRBY</span></a> ,用户可以通过执行不同的操作增加或减少计数器的值,比如在游戏中的记分器就可能用到这些命令。</li>
</ul>
</div>
<div class="section" id="id3">
<h2>模式:限速器<a class="headerlink" href="#id3" title="Permalink to this headline">¶</a></h2>
<p>限速器是特殊化的计算器,它用于限制一个操作可以被执行的速率(rate)。</p>
<p>限速器的典型用法是限制公开 API 的请求次数,以下是一个限速器实现示例,它将 API 的最大请求数限制在每个 IP 地址每秒钟十个之内:</p>
<div class="highlight-python"><div class="highlight"><pre>FUNCTION LIMIT_API_CALL(ip)
ts = CURRENT_UNIX_TIME()
keyname = ip+":"+ts
current = GET(keyname)
IF current != NULL AND current > 10 THEN
ERROR "too many requests per second"
END
IF current == NULL THEN
MULTI
INCR(keyname, 1)
EXPIRE(keyname, 1)
EXEC
ELSE
INCR(keyname, 1)
END
PERFORM_API_CALL()
</pre></div>
</div>
<p>这个实现每秒钟为每个 IP 地址使用一个不同的计数器,并用 <a class="reference internal" href="../key/expire.html#expire"><span>EXPIRE</span></a> 命令设置生存时间(这样 Redis 就会负责自动删除过期的计数器)。</p>
<p>注意,我们使用事务打包执行 <a class="reference internal" href="#incr"><span>INCR</span></a> 命令和 <a class="reference internal" href="../key/expire.html#expire"><span>EXPIRE</span></a> 命令,避免引入竞争条件,保证每次调用 API 时都可以正确地对计数器进行自增操作并设置生存时间。</p>
<p>以下是另一个限速器实现:</p>
<div class="highlight-python"><div class="highlight"><pre>FUNCTION LIMIT_API_CALL(ip):
current = GET(ip)
IF current != NULL AND current > 10 THEN
ERROR "too many requests per second"
ELSE
value = INCR(ip)
IF value == 1 THEN
EXPIRE(ip,1)
END
PERFORM_API_CALL()
END
</pre></div>
</div>
<p>这个限速器只使用单个计数器,它的生存时间为一秒钟,如果在一秒钟内,这个计数器的值大于 <code class="docutils literal"><span class="pre">10</span></code> 的话,那么访问就会被禁止。</p>
<p>这个新的限速器在思路方面是没有问题的,但它在实现方面不够严谨,如果我们仔细观察一下的话,就会发现在 <a class="reference internal" href="#incr"><span>INCR</span></a> 和 <a class="reference internal" href="../key/expire.html#expire"><span>EXPIRE</span></a> 之间存在着一个竞争条件,假如客户端在执行 <a class="reference internal" href="#incr"><span>INCR</span></a> 之后,因为某些原因(比如客户端失败)而忘记设置 <a class="reference internal" href="../key/expire.html#expire"><span>EXPIRE</span></a> 的话,那么这个计数器就会一直存在下去,造成每个用户只能访问 <code class="docutils literal"><span class="pre">10</span></code> 次,噢,这简直是个灾难!</p>
<p>要消灭这个实现中的竞争条件,我们可以将它转化为一个 Lua 脚本,并放到 Redis 中运行(这个方法仅限于 Redis 2.6 及以上的版本):</p>
<div class="highlight-python"><div class="highlight"><pre>local current
current = redis.call("incr",KEYS[1])
if tonumber(current) == 1 then
redis.call("expire",KEYS[1],1)
end
</pre></div>
</div>
<p>通过将计数器作为脚本放到 Redis 上运行,我们保证了 <a class="reference internal" href="#incr"><span>INCR</span></a> 和 <a class="reference internal" href="../key/expire.html#expire"><span>EXPIRE</span></a> 两个操作的原子性,现在这个脚本实现不会引入竞争条件,它可以运作的很好。</p>
<p>关于在 Redis 中运行 Lua 脚本的更多信息,请参考 <a class="reference internal" href="../script/eval.html#eval"><span>EVAL</span></a> 命令。</p>
<p>还有另一种消灭竞争条件的方法,就是使用 Redis 的列表结构来代替 <a class="reference internal" href="#incr"><span>INCR</span></a> 命令,这个方法无须脚本支持,因此它在 Redis 2.6 以下的版本也可以运行得很好:</p>
<div class="highlight-python"><div class="highlight"><pre>FUNCTION LIMIT_API_CALL(ip)
current = LLEN(ip)
IF current > 10 THEN
ERROR "too many requests per second"
ELSE
IF EXISTS(ip) == FALSE
MULTI
RPUSH(ip,ip)
EXPIRE(ip,1)
EXEC
ELSE
RPUSHX(ip,ip)
END
PERFORM_API_CALL()
END
</pre></div>
</div>
<p>新的限速器使用了列表结构作为容器, <a class="reference internal" href="../list/llen.html#llen"><span>LLEN</span></a> 用于对访问次数进行检查,一个事务包裹着 <a class="reference internal" href="../list/rpush.html#rpush"><span>RPUSH</span></a> 和 <a class="reference internal" href="../key/expire.html#expire"><span>EXPIRE</span></a> 两个命令,用于在第一次执行计数时创建列表,并正确设置地设置过期时间,最后, <a class="reference internal" href="../list/rpushx.html#rpushx"><span>RPUSHX</span></a> 在后续的计数操作中进行增加操作。</p>
</div>
</div>
<div class="section" id="discuss">
<h2>
讨论
<a class="headerlink" href="#discuss" title="永久链接至标题">¶</a>
</h2>
<div id="disqus_thread"></div>
<script type="text/javascript">
/* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
var disqus_shortname = 'redis-command-cn'; // required: replace example with your forum shortname
/* * * DON'T EDIT BELOW THIS LINE * * */
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
<a href="http://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>
</div>
<div id="sponser">
<h2>赞助商</h2>
<a href="http://yunba.io/"><img src="../_static/yunba-logo.jpg"/></a>
<p><a href="http://yunba.io/">云巴(yunba.io)</a>,专业的App.Web.物联网实时通信后端云服务提供商。</p>
</div>
</div>
</div>
</div>
<div class="clearer"></div>
</div>
<div class="related">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="../genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="incrby.html" title="INCRBY"
>next</a> |</li>
<li class="right" >
<a href="getset.html" title="GETSET"
>previous</a> |</li>
<li><a href="../index.html">Redis 命令参考</a> »</li>
<li><a href="index.html" >String(字符串)</a> »</li>
</ul>
</div>
<div class="footer">
© Copyright 2015, Redis.
Last updated on Dec 02, 2015.
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.3.1.
</div>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-53959484-7', 'auto');
ga('send', 'pageview');
</script>
</body>
</html>