如何在Vert.x中处理变量的并发更新问题?
嘿,这个问题我刚上手Vert.x的时候也踩过同款坑!其实核心原因是如果你的REST Verticle部署了多个实例,或者用到了Worker Verticle,不同的事件线程会同时操作计数器,导致竞态条件,自然就会计数不准。下面给你几个符合Vert.x风格的解决方案,按需选就行:
Vert.x自带的SharedData组件里的Counter就是专门解决这种跨线程、跨实例甚至跨集群的计数场景的,天生线程安全,完全适配Vert.x的异步模型。
示例代码:
@Override public void start() { SharedData sharedData = vertx.sharedData(); // 获取或创建名为request-counter的计数器 sharedData.getCounter("request-counter", res -> { if (res.succeeded()) { Counter counter = res.result(); Router router = Router.router(vertx); router.get("/api/hello").handler(ctx -> { // 异步递增计数器并获取最新值 counter.incrementAndGet(asyncRes -> { if (asyncRes.succeeded()) { Long total = asyncRes.result(); System.out.println("累计请求数:" + total); // 处理你的业务逻辑 ctx.response().end("Hello, Vert.x! 当前累计请求:" + total); } else { ctx.response().setStatusCode(500).end("计数失败"); } }); }); vertx.createHttpServer().requestHandler(router).listen(8080, listenRes -> { if (listenRes.succeeded()) { System.out.println("服务启动在8080端口"); } }); } else { System.err.println("获取计数器失败:" + res.cause()); } }); }
不管你部署多少个Verticle实例,甚至是集群环境,这个计数器都能准确统计,完全不用自己操心线程安全问题。
如果你的服务是单JVM部署,不需要集群支持,用Java的AtomicLong或者AtomicInteger就足够了,它们本身就是线程安全的原子操作类,简单直接。
示例代码:
private final AtomicLong requestCount = new AtomicLong(0); @Override public void start() { Router router = Router.router(vertx); router.get("/api/hello").handler(ctx -> { // 原子递增,直接拿到最新计数 long total = requestCount.incrementAndGet(); System.out.println("累计请求数:" + total); ctx.response().end("Hello, Vert.x! 当前累计请求:" + total); }); vertx.createHttpServer().requestHandler(router).listen(8080); }
这种方式代码最简洁,但只局限于单JVM环境,适合小型服务或者测试场景。
要是你想彻底贴合Vert.x的消息驱动理念,可以把计数逻辑抽成一个单独的Verticle,所有REST请求都通过事件总线发消息给它来计数。因为单个Verticle是单线程执行的,完全不会有并发冲突,还能实现业务逻辑和计数逻辑的解耦。
首先写计数专用Verticle:
public class CounterVerticle extends AbstractVerticle { private long totalRequests = 0; @Override public void start() { // 监听事件总线的count-request地址 vertx.eventBus().consumer("count-request", message -> { totalRequests++; System.out.println("累计请求数:" + totalRequests); // 把最新计数返回给调用方 message.reply(totalRequests); }); } }
然后在REST Verticle里部署它并发送计数消息:
@Override public void start() { // 先部署计数Verticle vertx.deployVerticle(new CounterVerticle(), deployRes -> { if (deployRes.succeeded()) { Router router = Router.router(vertx); router.get("/api/hello").handler(ctx -> { // 发送消息到事件总线触发计数 vertx.eventBus().request("count-request", "", replyRes -> { if (replyRes.succeeded()) { Long total = (Long) replyRes.result().body(); ctx.response().end("Hello, Vert.x! 当前累计请求:" + total); } else { ctx.response().setStatusCode(500).end("计数失败"); } }); }); vertx.createHttpServer().requestHandler(router).listen(8080); } else { System.err.println("部署计数Verticle失败:" + deployRes.cause()); } }); }
这种方式扩展性很强,后续如果要加计数的其他逻辑(比如定时上报、重置计数),直接改计数Verticle就行,完全不影响REST服务的代码。
最后补个小提醒:如果你的Verticle是单实例部署的默认类型(非Worker),其实用普通long变量递增也是安全的——因为所有请求都在同一个事件线程里串行处理,压根不会有并发问题。但只要涉及多实例或者Worker Verticle,就必须用上面的线程安全方案哦!
内容的提问来源于stack exchange,提问作者KRR16

