SNS Webgame 社区类页面网游开发, 自己所用的架构,以及遇到的一些问题和困惑

大家好,我第一次做服务器的程序,之前我一直做手机客户端,什么WEB框架啥的都没用过 ,遇到很多困难跟疑惑,请大家多多指教。
目前在做的一个社区页面网游,HTML页面,非FLASH的,所有的交互都是基于HTTP的。

这个游戏的逻辑比较常见,类似于SLG,玩家消耗时间采集资源(比如点一个按钮,等5分钟就可以采集了10个粮食),随后制造了5个士兵,接着玩家之间可以进行PK,双方损失N个士兵等等.
同时具有社区的常见功能,玩家之间也可以互相加好友,留言等等。 但是除了管理员,普通用户没有广播的功能。也没有专门的聊天频道。
这个系统的特点是:
1. 用户数可能会很多, 可能会有上百万在线(至少所用的架构要能扩展到支持那么多),并且不分区,大家都在同一个区。
也就是, 所有的玩家,都可以通过用户名查询到另外一个玩家的信息,然后可以加好友或者PK。
2. PV量很大,并且数据库操作频繁,玩家的每个点击,都可能生成新的对象.比如生成几个士兵,跟别人进行一场PK,会产生一条PK记录。

我现在的设计是

浏览器 ----> LOAD BALANCE -----> 动态页面服务器 TOMCAT集群 -----> Logic server -----> DB server

前端, 静态的图片,CSS什么的,都可以交给 NGIX
动态页面服务器,用JSP,可以用 N台 TOMCAT 集群

后端, 逻辑服务器 Logic server
数据库 DB server , 一台MYSQL


困惑之一: 页面服务器与逻辑服务器的通信问题
我现在的做法是,N台 TOMCAT 通过SOCKET 连接到 逻辑服务器, 然后发送自定义的消息格式;逻辑服务器用一个线程维护消息队列,然后逻辑服务器开启一个线程,不停地处理消息,并把结果返回对应的页面服务器。
然后页面服务器把逻辑服务器返回的结果,翻译成HTML返回给玩家
页面服务器
//
static Integer msgId = 0;
List<MyMsg> msgQueue = Collections.synchronizedList(new LinkedList());
HashMap<Integer, MyMsg> resultMap = new HashMap<Integer, MyMsg>();

public String requestHandler(HttpServletRequest hsr) {
// 一些检查
// 根据 request 生成 msg
MyMsg msg = new MyMsg();
synchronized (msgId) {
msg.setId(msgId++);
}
msg.startTime = System.currentTimeMillis();
msgQueue.add(msg);

while (resultMap.get(msg.getMsgId()) == null) {
Thread.yield();
if (System.currentTimeMillis() > msg.startTime + 10000) {
break; // 超过 10 秒没响应, 算超时
}
}

MyMsg result = resultMap.get(msg.getMsgId());
resultMap.remove(result);
if (result != null) {
return "翻译result";
} else {
return "返回超时错误";
}
}

// 接收消息 , 逻辑服务器收到 MyMsg, 处理后, 会返回一个同样 msgId 的MyMsg 给页面服务器, 页面服务器直接保存
public void onReceiveMsg(MyMsg msg){
if (System.currentTimeMillis() > msg.startTime + 10000) {
return; // 超过 10 秒才回来, 直接丢弃
}
resultMap.put(msg.getMsgId(), msg);
}

请问这样的设计,有没有问题? 很多线程在等待自己的结果的时候,会不会造成服务器出问题;
自己写这些,总感觉有些不可靠, 有没有现成的 服务器间通信框架,以及 消息队列什么的可以直接用的?

逻辑服务器中:
把所有收到的消息都加入到队列里面,然后进行 串行处理。 这样就省得同步什么的了。
但这种做法,会不会造成效率损失?
一般大家做 逻辑服务器与页面服务器的通信是怎么做的?


困惑之二
逻辑服务器上的数据库缓存问题, 因为数据库操作非常频繁,所以肯定需要缓存。
我现在的做法是
逻辑服务器起三个线程, 一个负责逻辑处理以及数据管理, 一个负责数据库读取, 一个负责数据库更新。 我没有用 hibernate , 直接用的JDBC。

比如玩家登陆
页面服务器创建玩家登陆消息 -> 逻辑服务器 -> DB读取线程读取数据 -> 读取到的数据,放入 数据管理进程 -> 返回玩家的信息给 页面服务器
然后数据管理线程,定时遍历 所有的账户数据(一个列表),判断该账户是不是有更新,如果有更新, 则放到DB更新线程去更新数据库。
我用的判断数据是不是有更新的方法是,
在该 object 上次被更新到数据库以后,就生成一个 saved = object.clone(), 把所有的成员对象都 clone一遍,
然后比较 当前object 与 saved 的所有成员, 看是否完全一致, 不一致,则说明有更新, 需要放到 数据库更新线程去更新。


现在这种做法,
1.我感觉很麻烦, 所有的entity,都要写 clone 方法 以及 saved与当前对象判断更新的方法,
2.非常担心的就是一旦服务器突然出故障退出, 则可能会有很多账户的数据是过期的。只能靠日志慢慢恢复。
3.所有的数据以及逻辑都放在一台电脑上, 如果用户数达到几十上百万以后, 会不会性能上有问题。


我后来看了一些数据库缓存的资料,比如memcache, redis ,都是类似于 hashMap 的功能, 好像是为了处理分布式系统,我的逻辑这块,完全就在一台机器上,所以我也不太懂怎么把这些缓存的工具应用到现在的系统中。
请大家多多指教。

>请问这样的设计,有没有问题? 很多线程在等待自己的结果的时候,会不会造成服务器出问题;自己写这些,总感觉有些不可靠, 有没有现成的 服务器间通信框架,以及 消息队列什么的可以直接用的?

Socket通讯一般采取NIO,队列可以替代锁机制,也就是说你有读写操作,队列可以避免锁,而且队列目前只能使用Disruptor这种无锁队列,JDK的并发包的队列几乎不能用,因为最后这些锁是瓶颈,会发生CPU利用率很低,但是吞吐量上不去的情况。

既然是社区游戏,对数据一致性就没有什么要求,数据库就只是一种存储作用,所以,逻辑和数据库存储之间一定是松耦合,比如通过事件驱动数据库存储,只有对象自己知道是否更新了,所有这些对象更新是可以放到一个队列去,守护线程读取队列,更新数据库。

逻辑如果想扩展到其他电脑上,软件本身要注重Scalable扩展性,你在设计时,要考虑到逻辑前端如果有一个负载平衡器情况下怎么办。

关于你的三个担心,其实可以引入NoSQL,如Redis等帮助你,这些NoSQL有自动存储机制,帮助你处理状态的可伸缩性;而你只要注重计算能力比如吞吐量与延迟的矛盾解决即可,比如采取并发并行或事件驱动即可。

谢谢板桥老师的回复,学到很多东西, 谢谢!

我觉得社区游戏,如果一致性要求不高的话
可以用到mq之类的集群,去做插入,更新,删除等操作,用memcache之类的集群做缓存,如果用户发生对象更新,可以先去更新memcache,然后插入mq队列,等待插入就可以。
感觉数百万的级别,tomcat集群压力比较大,做好balance很重要吧,数据库,如果想稳定点,成本低点,最好用mysql毕竟DBA成本小,主要的经历花在了拆表上,如果用nosql,研发成本会很高,毕竟很多的监控,日志分析之类的工具都需要自己开发,毕竟还不如mysql健全,但是像BANQ老师说的一样,可以尝试,毕竟scalebility是很强的
主要是逻辑如果拆分,模块之间解耦合就好,这样就可以做到服务的相对分布式,缓存的集群还是很重要的,还有就是雪崩问题的解决
我感觉是这样的,希望老师再提出点意见