序)沉迷游戏,只因不安于现状却又没法改变已逝的过去sql
昨天下午开始作一些细化的东西,其中就包括了用户注册的时候,系统随机分配一个昵称,这样的状况几乎在全部游戏中均可见到,不算什么稀奇的东西,策划要求的是只有汉字,必须看着像名字,不能重复,这样我最开始想的直接随机产生x个汉字的想法看来就太简单了,而且不少游戏的随机昵称看着还像模像样,确实是一个名字。数据库
另外在这里还有个细节问题,当用户打开注册界面选择角色的时候从服务器得到了惟一性的角色名后,该角色名则不能再分配给其余人,当用户注册后则与之绑定,若是用户选择刷新从新获取角色名,则以前角色名如何处置?固然有两种方案:服务器
1)直接丢掉,只要分配了即便用户没有绑定该角色名也废弃 并发
2)丢回池里,可再分配给其余人app
第一种状况使用率过低,当太多用户不停刷的时候,使用率更低了,因而我以为第二种比较好,鉴于名字要像模像样这样的状况看来必须走词库进行组合了,固然百家姓要拉下来,因而我本身动手整理了以下三个文件:dom
依次为:姓氏 ----> 女性角色名 ----> 男性角色名ide
之因此要把男女分开,那是由于男性总是分配到柔柔,瑶瑶,这样的昵称,估计瞬间就会开口大骂,为了我少挨点骂,因而把他们分开了,另外因为词库比较大,组合后的数据更加大,这样的分开也可以提升速度,在刚刚开始的时候我犯了一个很大的错误,那就是把问题想简单了,我最开始的想法是:测试
1)根据性别从woman.txt或者man.txt里随机选择一个名字,再随机在姓氏文件中选取一个姓氏,组合成名字this
2)判断该角色名是否已经被注册,若是已经被注册,跳转到1spa
3)判断该角色是否存在于Set集合(个Set集合用来保存当前已经分配出去的,可是用户还未绑定的角色名)
4)丢入Set集合,发给客户端
5)用户刷新角色则将该角色从Set移除,以便复用
作完后客户端测试了从功能上讲没啥问题,可是作完后立刻本身就发现这里有很大的问题,首先走了词库,有IO问题,这里的词库男女不一样的角色名分别各有100万左右,其次当用户数量太大的时候,剩下的未使用的角色名被抽中的几率会变得很小很小,越靠后分配时间越久,特别是词库快要耗尽的时候,剩下未分配的名称几乎不会再被分配到,甚至会死在这里,想到这里瞬间精神了,粗心害死人啊。
因而打算重写这一块,首先不能使用随机数,随机就会出问题,另外由于词库的大小,正式上线的时候根据词库组合出来的用户名可能会有上千万,所以这样的数据确定不能直接丢内存,因而个人大概处理方式以下:
1)分别建立男性角色名表与女性角色名表,将全部昵称初始化到数据库,做为一个池。
2)分别建立男性角色名视图与女性角色名视图
3)服务器启动后初始化角色名表的数据,保证里面都是没有被分配的数据
4)初始化男性与女性昵称访问游标,游标与数据库里的行index对应
5)每次用户请求随机昵称的时候将游标的位置下移一位,取出里面的值发给客户端
6)即便用户不使用该名字,要求刷新名字,游标依旧保持下移
这样游标始终取一下个值,则确定不会重复,省掉了取出了还要和数据库比对的状况,而且这里的游标必定不能后退,不然就会出现一个用户刷新昵称不停在两个昵称中切换的状况,这样估计就要骂娘了,这样作了后还有几个问题就是如何保证使用率,就是游标移过了,可是该昵称没有被使用的数据,可以再次被分配到,因而个人想法是刷新数据表,重置游标位置。天天或者每周刷新一次表,重置游标位置为1,这样则可合理使用里面的数据。
这里还要处理一些其余并发问题,好比游标移动要保证惟一,另外刷新后台数据表的时候不能让游标移动,不然会出现脏数据。
实现取随机昵称的代码:
private volatile int currentMaleRoleNameCursorIndex = 1; private volatile int currentFemaleRoleNameCursorIndex = 1; private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); private boolean isRefurbish = false; public static final int DEFAULT_INDEX_VALUE = 1; private interface SexType { int WOMAN = 0; int MAN = 1; } public String getRandomRoleName(int sex) { try { lock.lock(); while (isRefurbish) { condition.await(); } String name = null; switch (sex) { case SexType.MAN: name = this.jdbcAccess.findString("select * from v_male_role_name_table where index = ?", currentMaleRoleNameCursorIndex, sex); currentMaleRoleNameCursorIndex += 1; break; case SexType.WOMAN: name = this.jdbcAccess.findString("select * from v_female_role_name_table where index = ?", currentFemaleRoleNameCursorIndex, sex); currentFemaleRoleNameCursorIndex += 1; break; default: break; } return StringUtils.hasText(name) ? name : "notFound"; } catch (Exception e) { e.printStackTrace(); return "error"; } finally { lock.unlock(); } }
Quartz中的任务天天刷新池里的数据:
@Inject private RandomHan randomHan; private static class RoleItems { private int id; private String name; public static RoleItems setValues(ResultSet resultSet) throws SQLException { RoleItems item = new RoleItems(); item.id = resultSet.getInt("id"); item.name = resultSet.getString("name"); return item; } } public void refurbishRandomNameTable() { randomHan.setRefurbish(true); List<RoleItems> cacheMaleNameItems = this.jdbcAccess.find("select t.id, t.name from v_male_role_name_table where 1 = 1 and index <= ?", new RowMapper<RoleItems>() { @Override public RoleItems mapRow(ResultSet resultSet, int rowNum) throws SQLException { return RoleItems.setValues(resultSet); } }, randomHan.getCurrentMaleRoleNameCursorIndex()); for (RoleItems item : cacheMaleNameItems) { if (isExistRoleName(item.name)) { this.jdbcAccess.execute("delete from male_role_name_table where id = ?", item.id); } } randomHan.setCurrentMaleRoleNameCursorIndex(RandomHan.DEFAULT_INDEX_VALUE); List<RoleItems> cacheFemaleNameItems = this.jdbcAccess.find("select t.id, t.name from v_female_role_name_table where 1 = 1 and index <= ?", new RowMapper<RoleItems>() { @Override public RoleItems mapRow(ResultSet resultSet, int rowNum) throws SQLException { return RoleItems.setValues(resultSet); } }, randomHan.getCurrentMaleRoleNameCursorIndex()); for (RoleItems item : cacheFemaleNameItems) { if (isExistRoleName(item.name)) { this.jdbcAccess.execute("delete from female_role_name_table where id = ?", item.id); } } randomHan.setCurrentFemaleRoleNameCursorIndex(RandomHan.DEFAULT_INDEX_VALUE); randomHan.setRefurbish(false); } private boolean isExistRoleName(String nickName) { return this.jdbcAccess.findInteger("select count(*) from player_role where nick_name = ?", nickName) > 0; }
数据库基本操做本身从新封装了下,以下:
public class JDBCAccess { private final Logger logger = LoggerFactory.getLogger(JDBCAccess.class); private JdbcTemplate jdbcTemplate; public <T> List<T> find(String sql, RowMapper<T> rowMapper, Object... params) { StopWatch watch = new StopWatch(); try { return jdbcTemplate.query(sql, params, rowMapper); } finally { logger.debug("find, sql={}, params={}, elapsedTime={}", new Object[]{sql, params, watch.elapsedTime()}); } } public <T> T findUniqueResult(String sql, RowMapper<T> rowMapper, Object... params) { StopWatch watch = new StopWatch(); try { return jdbcTemplate.queryForObject(sql, params, rowMapper); } finally { logger.debug("findUniqueResult, sql={}, params={}, elapsedTime={}", new Object[]{sql, params, watch.elapsedTime()}); } } public int findInteger(String sql, Object... params) { StopWatch watch = new StopWatch(); try { return jdbcTemplate.queryForInt(sql, params); } finally { logger.debug("findInteger, sql={}, params={}, elapsedTime={}", new Object[]{sql, params, watch.elapsedTime()}); } } public boolean findBooleanFiled(String sql, Object... params) { StopWatch watch = new StopWatch(); try { return jdbcTemplate.queryForObject(sql, params, new RowMapper<Boolean>() { @Override public Boolean mapRow(ResultSet resultSet, int rowNum) throws SQLException { return resultSet.getBoolean(1); } }); } catch(Exception e) { return false; } finally { logger.debug("findString, sql={}, params={}, elapsedTime={}", new Object[]{sql, params, watch.elapsedTime()}); } } public Long findLongFiled(String sql, Object... params) { StopWatch watch = new StopWatch(); try { return jdbcTemplate.queryForObject(sql, params, new RowMapper<Long>() { @Override public Long mapRow(ResultSet resultSet, int rowNum) throws SQLException { return resultSet.getLong(1); } }); } finally { logger.debug("findString, sql={}, params={}, elapsedTime={}", new Object[]{sql, params, watch.elapsedTime()}); } } public int findIntegerFiled(String sql, Object... params) { StopWatch watch = new StopWatch(); try { return jdbcTemplate.queryForObject(sql, params, new RowMapper<Integer>() { @Override public Integer mapRow(ResultSet resultSet, int rowNum) throws SQLException { return resultSet.getInt(1); } }); } catch (Exception e) { return -1; } finally { logger.debug("findString, sql={}, params={}, elapsedTime={}", new Object[]{sql, params, watch.elapsedTime()}); } } public String findString(String sql, Object... params) { StopWatch watch = new StopWatch(); try { return jdbcTemplate.queryForObject(sql, params, new RowMapper<String>() { @Override public String mapRow(ResultSet resultSet, int rowNum) throws SQLException { return resultSet.getString(1); } }); } finally { logger.debug("findString, sql={}, params={}, elapsedTime={}", new Object[]{sql, params, watch.elapsedTime()}); } } public int execute(String sql, Object... params) { StopWatch watch = new StopWatch(); try { return jdbcTemplate.update(sql, params); } finally { logger.debug("execute, sql={}, params={}, elapsedTime={}", new Object[]{sql, params, watch.elapsedTime()}); } } public int[] batchExecute(String sql, List<Object[]> params) { StopWatch watch = new StopWatch(); try { return jdbcTemplate.batchUpdate(sql, params); } finally { logger.debug("batchExecute, sql={}, params={}, elapsedTime={}", new Object[]{sql, params, watch.elapsedTime()}); } } public void setDataSource(DataSource dataSource) { jdbcTemplate = new JdbcTemplate(dataSource); } public JdbcTemplate getJdbcTemplate() { return jdbcTemplate; } }
这里还有一个问题,那就是用户刷新昵称后应该是不同的姓氏的昵称,这个可使用游标解决,也但是在初始化数据里作手脚,不过再说吧,总以为这个东西本身也搞的很挫了,先在这里记录下吧。。。