昨晚双12通宵值班,无聊的时候玩了几局五子棋,然后对这个匹配的功能产生了兴趣,想了想我们平时玩的LOL 5V5对战的匹配,想用自己的想法简单实现一下这个对战匹配功能。
MatchService接口
public interface MatchService {
/**
* 加入到对应的池中
*
* @param person
*/
void join(UserMatch person);
/**
* 批量加入到对应的池中(拉好友一起队列)
*
* @param persons
*/
void batchJoin(List<UserMatch> persons);
/**
* 匹配出段位相同的几个人,放到本地缓存里
*
* @param peopleNumber
*/
void match(UserMatch person, int peopleNumber);
/**
* 查询本地缓存里的数据,返回给前端
*
* @param userId
* @return
*/
List<UserMatch> findMatch(Long userId, int peopleNumber);
/**
* 用户取消队列,要把这个用户从池里删除,其他已经被锁的用户继续匹配
*
* @param userId
*/
void cancel(Long userId);
}
枚举类
/**
* 段位枚举
*/
public enum LevelEnum {
BRONZE("bronze", "青铜"),
SILVER("silver", "白银"),
GOLD("gold", "黄金"),
PLATINUM("platinum", "白金"),
DIAMONDS("diamonds", "钻石"),
KING("king", "王者"),
;
private String code;
private String value;
public static LevelEnum getByCode(String code) {
if (StringUtils.isBlank(code)) {
return null;
}
for (LevelEnum level : values()) {
if (org.apache.commons.lang3.StringUtils.equals(code, level.getCode())) {
return level;
}
}
return null;
}
LevelEnum(String code, String value) {
this.code = code;
this.value = value;
}
public String getCode() {
return code;
}
public String getValue() {
return value;
}
}
UserMatch实体类
@Data
public class UserMatch {
/**
* 用户ID
*/
private Long userId;
/**
* 段位
*/
private LevelEnum level;
/**
* 是否锁住,当被用户匹配到之后则锁住,不让其他线程再来找该用户
*/
private volatile boolean lock = false;
}
用CommonMap当做正在队列中的池
public class CommonMap {
public static Map<LevelEnum, List<UserMatch>> map = new ConcurrentHashMap<>();
}
匹配成功之后把数据存起来返回给前端,利用本地缓存LoadingCache,这里是配置LoadingCacheConfiguration
@Configuration
public class LoadingCacheConfiguration {
private static final int EXPIRE_SECONDS = 60;
@Bean
public LoadingCache<Long, List<UserMatch>> myCacheStorage() {
return CacheBuilder.newBuilder().concurrencyLevel(10).maximumSize(3000).expireAfterWrite(EXPIRE_SECONDS, TimeUnit.SECONDS)
.build(new CacheLoader<Long, List<UserMatch>>() {
@Override
public List<UserMatch> load(Long name) throws Exception {
//在这里可以初始化加载数据的缓存信息,读取数据库中信息或者是加载文件中的某些数据信息
return null;
}
});
}
}
实现类:MatchServiceImpl
@Service
public class MatchServiceImpl implements MatchService {
@Resource
private LoadingCache<Long, List<UserMatch>> cache;
@Override
public void join(UserMatch person) {
if (CommonMap.map.containsKey(person.getLevel())) {
CommonMap.map.get(person.getLevel()).add(person);
} else {
List<UserMatch> list = new ArrayList<>();
list.add(person);
CommonMap.map.put(person.getLevel(), list);
}
}
@Override
public void batchJoin(List<UserMatch> persons) {
persons.forEach(person -> {
join(person);
});
}
/**
* 1、多个用户可以同时去调接口匹配,所以用线程池去处理每一个请求
* 2、当一个用户被一个线程拿到,就要在池里删除掉(用个锁标识),保证别人不会再拿到这个用户
* 3、当池里只有一个用户,但是我需要匹配4个用户,那我就要先把符合的用户拿到,再去监听池里满足情况的用户,新加入满足的再拿过来
* 4、当调取消接口的话,要把拿到的用户全部放在池里去,取消的用户要从池里删除
*
* @param person
* @param peopleNumber
* @return
*/
@Override
public void match(UserMatch person, int peopleNumber) {
ExecutorUtils.execute(() -> {
List<UserMatch> newUserList = new ArrayList<>();
while (newUserList.size() < peopleNumber + 1) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//该用户已经被其他线程匹配到
if (person.isLock()) {
return;
}
//所有这个段位的用户
List<UserMatch> matchList = CommonMap.map.get(person.getLevel());
//先过滤掉目前已经锁上的用户
matchList = matchList.stream().filter(userMatch -> !userMatch.isLock()).collect(Collectors.toList());
if (matchList.size() == 0 || matchList == null) {
continue;
}
Random random = new Random();
//选出peopleNumber个用户出来
for (int i = 0; i < peopleNumber; i++) {
if (matchList.size() == 0 || matchList == null) {
continue;
}
int index = random.nextInt(matchList.size());
UserMatch userMatch = matchList.get(index);
if (userMatch.isLock()) {
continue;
}
if (newUserList.contains(userMatch)) {
userMatch.setLock(true);
matchList.remove(index);
continue;
}
userMatch.setLock(true);
newUserList.add(userMatch);
matchList.remove(index);
if (newUserList.size() == peopleNumber + 1) {
break;
}
}
}
newUserList.forEach(newUser -> {
cache.put(newUser.getUserId(), newUserList);
});
});
}
@Override
public List<UserMatch> findMatch(Long userId, int peopleNumber) {
try {
List<UserMatch> userMatches = cache.get(userId);
if (userMatches != null && userMatches.size() == peopleNumber + 1) {
return userMatches;
}
return null;
} catch (Exception e) {
return new ArrayList<>(1);
}
}
/**
* 木有写,你们可以自己思考下
* @param person
*/
@Override
public void cancel(UserMatch person) {
}
}
测试controller
@RestController
public class TestMatchController {
@Resource
private MatchService matchService;
@RequestMapping("/testMatch")
public void testMatch() {
List<UserMatch> persons = new ArrayList<>();
UserMatch person = new UserMatch();
person.setUserId(123L);
person.setLevel(LevelEnum.getByCode("bronze"));
persons.add(person);
UserMatch person1 = new UserMatch();
person1.setUserId(234L);
person1.setLevel(LevelEnum.getByCode("bronze"));
persons.add(person1);
UserMatch person2 = new UserMatch();
person2.setUserId(345L);
person2.setLevel(LevelEnum.getByCode("bronze"));
persons.add(person2);
UserMatch person3 = new UserMatch();
person3.setUserId(000L);
person3.setLevel(LevelEnum.getByCode("gold"));
persons.add(person3);
UserMatch person4 = new UserMatch();
person4.setUserId(111L);
person4.setLevel(LevelEnum.getByCode("gold"));
persons.add(person4);
UserMatch person5 = new UserMatch();
person5.setUserId(222L);
person5.setLevel(LevelEnum.getByCode("gold"));
persons.add(person5);
matchService.batchJoin(persons);
}
@RequestMapping("/testMatch1")
public void testMatch1() {
List<UserMatch> persons = new ArrayList<>();
UserMatch person = new UserMatch();
person.setUserId(1231L);
person.setLevel(LevelEnum.getByCode("bronze"));
persons.add(person);
UserMatch person1 = new UserMatch();
person1.setUserId(2341L);
person1.setLevel(LevelEnum.getByCode("bronze"));
persons.add(person1);
UserMatch person2 = new UserMatch();
person2.setUserId(3451L);
person2.setLevel(LevelEnum.getByCode("bronze"));
persons.add(person2);
UserMatch person3 = new UserMatch();
person3.setUserId(0001L);
person3.setLevel(LevelEnum.getByCode("gold"));
persons.add(person3);
UserMatch person4 = new UserMatch();
person4.setUserId(1111L);
person4.setLevel(LevelEnum.getByCode("gold"));
persons.add(person4);
UserMatch person5 = new UserMatch();
person5.setUserId(2221L);
person5.setLevel(LevelEnum.getByCode("gold"));
persons.add(person5);
matchService.batchJoin(persons);
}
@RequestMapping("/match")
public void match(Long userId, String code) {
UserMatch person = new UserMatch();
person.setUserId(userId);
person.setLevel(LevelEnum.getByCode(code));
matchService.match(person, 4);
}
@RequestMapping("/find")
public List<UserMatch> find(Long userId, int peopleNumber) {
return matchService.findMatch(userId, peopleNumber);
}
}
1、启动项目
2、http://localhost:8080/testMatch 先初始化几条数据进行,代码里是三个黄铜、三个黄金的用户
3、http://localhost:8080/match?userId=123&code=bronze 当用户点击 LOL里开始队列时,调join接口(这里我们用第一步的初始化数据代替了)和match接口 (你可以debug)
4、第三步的时候可以发现,我要匹配四个黄铜队友,但是第二步里初始化的池中只有3个用户,所以代码里会继续对池里的数据进行实时查询(你依然可以debug,这样观察的更明显);
5、http://localhost:8080/testMatch1 执行这个接口,再往池中新增几个刚刚点击开始队列的用户,断点f9,然后这些新用户会被上面第四步的线程读取到,再把符合条件的两个黄铜用户拉过来,凑成了五个人,放进本地缓存里。
6、http://localhost:8080/find?userId=123&peopleNumber=4 前端点击队列之后,就立即轮训该接口获取匹配到的用户。
我的测试结果为:
这个是我即兴的一次撸码,代码写的简单或不完善还请多担待,只是为了增加对事情的思考。谢谢~