Skip to content

Memcached - 递增/递减

Memcached 提供了 incr 和 decr 命令,用于原子地修改以字符串形式存储的数值。‘原子’ 意味着该操作作为一个单一、不可分割的步骤执行,无需更复杂的 ‘检查并设置’ (CAS) 逻辑即可防止竞态条件。这使得它们非常适合实现高性能计数器、速率限制器或简单的排行榜。

incr 命令将现有键的数值增加指定量。存储在 Memcached 中的值必须是无符号 64 位整数的字符串表示。

incr key increment_value

在此示例中,我们首先将键 visitors 设置为一个数值。然后使用 incr 命令增加它。

# 首先,设置一个初始数值。值必须单独占一行。
set visitors 0 900 2
10
STORED
# 获取当前值
get visitors
VALUE visitors 0 2
10
END
# 将值增加 5
incr visitors 5
15
# 验证新值
get visitors
VALUE visitors 0 2
15
END

decr 命令减少数值。一个重要的规则是,decr 永远不会将值减小到 0 以下。

decr key decrement_value
# 假设 'visitors' 键的值为前一个示例中的 15
decr visitors 5
10
get visitors
VALUE visitors 0 2
10
END
# 尝试将值减小到零以下时,会停止在 0
decr visitors 20
0
get visitors
VALUE visitors 0 1
0
END

使用现代 Java 客户端实现原子计数器

Section titled “使用现代 Java 客户端实现原子计数器”

在应用程序代码中,初始化计数器是一个常见挑战。如果多个客户端同时尝试设置初始值,可能会产生竞态条件。现代 Memcached 客户端通过提供一个 incr 版本来解决这个问题,该版本能在计数器不存在时原子地初始化它。这是推荐的最佳实践。

确保您的 pom.xml 中包含 spymemcached 客户端依赖:

<dependency>
<groupId>net.spy</groupId>
<artifactId>spymemcached</artifactId>
<version>2.12.3</version>
</dependency>
import net.spy.memcached.MemcachedClient;
import java.net.InetSocketAddress;
public class IncrDecrExample {
public static void main(String[] args) {
MemcachedClient mcc = null;
try {
mcc = new MemcachedClient(new InetSocketAddress("127.0.0.1", 11211));
System.out.println("Connection to server successful.");
String counterKey = "page_visits";
// 为了进行干净的测试,如果键存在则删除它
mcc.delete(counterKey);
// 1. 原子地初始化并增加计数器。
// 方法签名是 incr(key, by, defaultValue, expiration)。
// 如果 'page_visits' 不存在,它将以 defaultValue (1) 创建,然后由 'by' (1) 递增,最终结果为 1。
// 注意:某些客户端可能直接设置默认值。Spymemcached 会将其设置为 defaultValue 然后执行操作。
// 对于 spymemcached,更好的初始调用是 incr(key, 1, 1),它在键不存在时将其设置为 1。
long firstVisit = mcc.incr(counterKey, 1, 1, 3600); // 初始化为 1,1 小时后过期
System.out.println("After first increment (and initialization): " + firstVisit);
// 2. 增加现有计数器
long secondVisit = mcc.incr(counterKey, 1, 1, 3600);
System.out.println("After second increment: " + secondVisit);
// 3. 增加一个更大的值
long multiVisit = mcc.incr(counterKey, 5, 1, 3600);
System.out.println("After incrementing by 5: " + multiVisit);
// 4. 减少计数器
long decrementedValue = mcc.decr(counterKey, 2, 0, 3600);
System.out.println("After decrementing by 2: " + decrementedValue);
} catch (Exception e) {
System.err.println("An error occurred: " + e.getMessage());
} finally {
if (mcc != null) {
mcc.shutdown();
}
}
}
}
Connection to server successful.
After first increment (and initialization): 1
After second increment: 2
After incrementing by 5: 7
After decrementing by 2: 5

**非数值型值:**尝试对持有非数值型字符串(例如 “hello”)的键执行 incr 或 decr 操作,将导致服务器返回错误。客户端库通常会抛出异常。

**不存在的键(无默认值):**如果您对不存在的键使用简单的 incr(key, by) 操作,该操作将失败(在 spymemcached 中返回 -1)。这就是为什么推荐使用带有默认值的版本,以避免初始化时的竞态条件。

incr/decr 性能高,常用于:

  • **API 速率限制:**跟踪用户在特定时间窗口内发出的 API 调用次数。
  • **浏览/下载计数器:**统计文章被阅读或文件被下载的次数。
  • **实时统计:**聚合简单的、非关键的统计数据,而无需在每个事件发生时都访问数据库。