Memcached - 递增/递减
Memcached - 原子递增与递减
Section titled “Memcached - 原子递增与递减”Memcached 提供了 incr 和 decr 命令,用于原子地修改以字符串形式存储的数值。‘原子’ 意味着该操作作为一个单一、不可分割的步骤执行,无需更复杂的 ‘检查并设置’ (CAS) 逻辑即可防止竞态条件。这使得它们非常适合实现高性能计数器、速率限制器或简单的排行榜。
语法 - incr
Section titled “语法 - incr”incr 命令将现有键的数值增加指定量。存储在 Memcached 中的值必须是无符号 64 位整数的字符串表示。
incr key increment_value在此示例中,我们首先将键 visitors 设置为一个数值。然后使用 incr 命令增加它。
# 首先,设置一个初始数值。值必须单独占一行。set visitors 0 900 210STORED
# 获取当前值get visitorsVALUE visitors 0 210END
# 将值增加 5incr visitors 515
# 验证新值get visitorsVALUE visitors 0 215END语法 - decr
Section titled “语法 - decr”decr 命令减少数值。一个重要的规则是,decr 永远不会将值减小到 0 以下。
decr key decrement_value# 假设 'visitors' 键的值为前一个示例中的 15decr visitors 510
get visitorsVALUE visitors 0 210END
# 尝试将值减小到零以下时,会停止在 0decr visitors 200
get visitorsVALUE visitors 0 10END使用现代 Java 客户端实现原子计数器
Section titled “使用现代 Java 客户端实现原子计数器”在应用程序代码中,初始化计数器是一个常见挑战。如果多个客户端同时尝试设置初始值,可能会产生竞态条件。现代 Memcached 客户端通过提供一个 incr 版本来解决这个问题,该版本能在计数器不存在时原子地初始化它。这是推荐的最佳实践。
前置条件:Maven 依赖
Section titled “前置条件:Maven 依赖”确保您的 pom.xml 中包含 spymemcached 客户端依赖:
<dependency> <groupId>net.spy</groupId> <artifactId>spymemcached</artifactId> <version>2.12.3</version></dependency>Java 示例
Section titled “Java 示例”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): 1After second increment: 2After incrementing by 5: 7After decrementing by 2: 5常见错误与最佳实践
Section titled “常见错误与最佳实践”**非数值型值:**尝试对持有非数值型字符串(例如 “hello”)的键执行 incr 或 decr 操作,将导致服务器返回错误。客户端库通常会抛出异常。
**不存在的键(无默认值):**如果您对不存在的键使用简单的 incr(key, by) 操作,该操作将失败(在 spymemcached 中返回 -1)。这就是为什么推荐使用带有默认值的版本,以避免初始化时的竞态条件。
incr/decr 性能高,常用于:
- **API 速率限制:**跟踪用户在特定时间窗口内发出的 API 调用次数。
- **浏览/下载计数器:**统计文章被阅读或文件被下载的次数。
- **实时统计:**聚合简单的、非关键的统计数据,而无需在每个事件发生时都访问数据库。