实现HTTP的请求同步

HTTP 的请求同步的需求来自于我们的游戏接入到第三平台上的支付,包含了兑入兑出. 这个时候会遇到一个问题就是, 假如前一次兑出还没有完成而后一次兑出又马上开始, 那么就会导致用户的游戏币为负数, 而平台币却翻倍了. 之前解决这个问题的方法是做成 TCP 的, 用户发来的所有消息都被存放在一个消息队列中, 一次仅有一条线程去处理.

为了实现这个需求, 我立马想到了用注解的方式对每一个需要顺序处理的方法进行注解, 并且向注解传递一个 value, 作为同步的键, 如果 Action 中存在对应 value 的 get 方法, 那么我们就用获取后的值作为同步键. 比如 @RequestSynchronized(“userId”) 那么每次调用前就会获取 Action 对象的 userId 属性, 并进行同步. 这样同一个用户的所有操作都是 synchronized 的. 而不同用户之间是互相不影响的. 那如果是注解中 value 元素没有对应的 get 方法, 那么就以 value 元素值作为键进行加锁. 如 @RequestSynchronized(“serialMethod”) 那么所有带有这个的方法同一 Action 的方法的调用都是同步的. 我这里所说的范围仅限于同一个 Action 内的. 对于不同的 Action , 它们锁处理的业务基本不相关, 那么我认为没有必要同步. 而且这样实现起来也简单.

为了实现上面的方法, 我想到了用 Spring 的 AOP 方式以及 struts2 自身的拦截器. 最终选择了拦截器的方式, 一来是它本身就体现了这个功能的目的, 对于 HTTP 请求的同步. 而来配置起来也是更加容易看懂, Spring AOP 的那个表达式还是不够直观, 而且扩展性不强. 用 struts2, 只需要那些有用到的 package 去继承我们这个拦截栈就够了. 像这个拦截器如果需要对所有请求的用户都分配一把锁, 那么锁会越来越多, 最后我选择了 WeakHash 来实现, 并以 actionCalssName.methodName.annotationValue.userId 作为弱键, 这样随着时间的流逝, 这些键自然就会消失. 而以值作为锁, 最初我想选用 Objct 作为锁. 随后我便发现了一个难题, 就是加入弱键已经被移除, 而锁仍然处于加锁状态, 下一个请求就会生成另外一把锁, 那就失去原来的意义了. 最后我将值定为 WeakReference, 来包含弱键. 这样我便可以获取这个弱键来作为锁, 而任何时候只要弱键被其它强引用的, 它便不会消失. 这样便保证了同一类型的方法始终只持有一把锁. 最后我为了不必每次调用方法是都去查看方法的注解和它的弱键等等信息, 我在 Action 第一次被调用时就讲这些信息缓存起来. 最终得了一个 100 行不到的方案. 代码这这里:串行化拦截器

写代码还是晚上的时候在一个比较轻松安静的环境下会比较好, 效率比较高. 要知道在工作时, 整天 RTX/QQ 响个不停, 而且一直有同事问你问题, 催你 bug 或者进度. 基本想要构思一些有点深度的东西都不行. 很高兴我在晚上构思了 map.sh 以及现在这个方案. 知识就像工具一样, 懂到越多, 构思起方案来就越顺手. Struts2 的拦截器我本身是只有一点点印象的, 那么昨晚花了一点时间了解了一下发现还能这样用. 也算是蛮欣慰的.

ps: 最近迷上了 keep 软件中的锻炼, 每天早上起来坚持锻炼七分钟, 已经瘦了六斤了. 运动和编程都是人生乐事.