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
写代码还是晚上的时候在一个比较轻松安静的环境下会比较好, 效率比较高. 要知道在工作时, 整天 RTX/QQ 响个不停, 而且一直有同事问你问题, 催你 bug 或者进度. 基本想要构思一些有点深度的东西都不行. 很高兴我在晚上构思了 map.sh 以及现在这个方案. 知识就像工具一样, 懂到越多, 构思起方案来就越顺手. Struts2 的拦截器我本身是只有一点点印象的, 那么昨晚花了一点时间了解了一下发现还能这样用. 也算是蛮欣慰的.
ps: 最近迷上了 keep 软件中的锻炼, 每天早上起来坚持锻炼七分钟, 已经瘦了六斤了. 运动和编程都是人生乐事.