Redis 存储整数在它们的整数表示里, 所以对实际存储整数的字符串值，并没有负担来存储字符串表示整数。
整数应答Integer reply: key存储的value自增之后值
redis> INCR mykey
redis> GET mykey
To do so the web application may simply increment a key every time the user performs a page view, creating the key name concatenating the User ID and a string representing the current date.
This simple pattern can be extended in many ways:
- It is possible to use INCR and EXPIRE together at every page view to have a counter counting only the latest N page views separated by less than the specified amount of seconds.
- A client may use GETSET in order to atomically get the current counter value and reset it to zero.
- Using other atomic increment/decrement commands like DECR or INCRBY it is possible to handle values that may get bigger or smaller depending on the operations performed by the user. Imagine for instance the score of different users in an online game.
*Pattern: 频率限制器Rate limiter
The rate limiter pattern is a special counter that is used to limit the rate at which an operation can be performed. The classical materialization of this pattern involves limiting the number of requests that can be performed against a public API.
We provide two implementations of this pattern using INCR, where we assume that the problem to solve is limiting the number of API calls to a maximum of ten requests per second per IP address.
*Pattern: 频率限制器 1
The more simple and direct implementation of this pattern is the following:
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" ELSE MULTI INCR(keyname,1) EXPIRE(keyname,10) EXEC PERFORM_API_CALL() END
Basically we have a counter for every IP, for every different second. But this counters are always incremented setting an expire of 10 seconds so that they’ll be removed by Redis automatically when the current second is a different one.
*Pattern: 频率限制器 2
An alternative implementation uses a single counter, but is a bit more complex to get it right without race conditions. We’ll examine different variants.
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(value,1) END PERFORM_API_CALL() END
The counter is created in a way that it only will survive one second, starting from the first request performed in the current second. If there are more than 10 requests in the same second the counter will reach a value greater than 10, otherwise it will expire and start again from 0.
local current current = redis.call("incr",KEYS) if tonumber(current) == 1 then redis.call("expire",KEYS,1) end
There is a different way to fix this issue without using scripting, but using Redis lists instead of counters. The implementation is more complex and uses more advanced features but has the advantage of remembering the IP addresses of the clients currently performing an API call, that may be useful or not depending on the application.
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
The RPUSHX command only pushes the element if the key already exists.
Note that we have a race here, but it is not a problem: EXISTS may return false but the key may be created by another client before we create it inside the MULTI / EXEC block. However this race will just miss an API call under rare conditions, so the rate limiting will still work correctly.