联系方式
Java服务器开发群:66728073
游戏开发者高级群:398808948
Unity3d游戏开发:286114103
龙井,铁观音,茶叶
游戏服务器之用户注册
2018-06-03 19:57浏览数:246 

注册服务相信大家都很熟悉,基本上不管开发什么项目,只要有用户,都需要注册和登陆。也是我们做的最多的一个服务。它的功能也可能也不会太多,无非就是注册和登陆,以及用户信息设置。但是在注册的时候,产品总是有一个要求,一个用户名不能注册多次。可能有的人觉得好实现,也许你在项目中已经实现了。

本人一开始做这个功能的时候是这样做的,当服务器收到注册请求之后,先从数据库查询此用户名是否存在,如果存在就返回给客户端错误,并提示用户用户名已存在。如果不存在,则插入数据库。后来为了提升性能,在服务器启动的时候,会把所有的用户名初始化redis中或内存中,这样检测用户的时候就直接访问内存或redis,而不用直接访问mysql。

直觉感觉是有问题的,越想越不对劲。假如我们同时收到同一个用户名同时注册时会不会出问题?能不能防止只有一个注册成功?我们知道,在web中,处理web请求的是一个线程池,也就是说多个请求可能是在不同的线程中执行。那么这里就会出现一种情况,两个请求同时查询数据库或缓存中是否存在已注册的用户名时,就有可能都得到不存在的结果,然后同时写入数据库。当然,如果你设置用户名为主键,就会有一个报错。但是刚才说了,如果想提升性能我们可能使用了缓存或redis,而同步到数据库可能是异步的。如果是这样,就会给用户返回注册成功,而在同步数据到数据库时发生错误,导致数据的不一致性。

一种解决办法是,把相同的用户名注册请求放到同一个线程中去执行。这样就算同时收到相同的用户名多次请求,也只会在同一个线程中执行,不会出现并发的现象。比如,在服务启动时,我们手动创建n个单线程线程池,即每个线程池只有一个线程,然后把用户名取hashCode% n,0取第一个线程池,1取第二个线程池。。。这样就可以把收到的注册请求包装成一个Runnable任务,然后丢到相应的线程池中去执行。这个时候,如果我们的web服务是平常同步的操作,就会直接给客户端返回了,因为在Controller的方法中,我们把请求放到别的线程去执行了,Controller的方法就执行结果了。要解决这个问题,只能使用异步的controller,这个springmvc也为我们准备好了:

@RequestMapping(value = "/deferred", method = RequestMethod.POSt)  

@ResponseBody

public DeferredResult executeSlowTask() {  

logger.info("Request received");  

DeferredResult deferredResult =new DeferredResult<>();

////这里执行封装任务,处理逻辑,在任务执行完成之后,调用deferredResult.setResult即可。

return deferredResult;  

  }

具体的实现以后再说!

另外,为了提升性能,最好把用户注册成功的数据放在内存中,用户登陆的时候可以直接验证,不用再查询数据库了。但是当请求量非常在,要实现动态扩展或nginx负载的时候,一个ngix对应多台注册服务的时候,就会出现用户缓存不一致的问题,相同的用户名,同时请求,有可能会落到不同的注册服务上面。那这个该怎么解决呢?还好,nginx提供了hash路由功能,和上面注册的时候一样,把同一个用户名的注册都hash到同一台注册服务器上面就可以了。当然也可以自己开发一个负载管理器,管理所有的注册服务节点,收到注册请求后,进行一次hash计算,把用户名相同的请求重定向或转发到同一台注册服务器,这个可以使用zuu的webgate和eureka,即spring cloud实现。