Fix cache by future (#652)

* 利用future回调避免并发调用时有多个线程同时去数据库查询,导致消耗额外资源。经过测试在8个线程并发查询时性能提升

* 修复缓存测试时懒加载报错问题

* 调整格式
pull/702/head
dante 2021-11-23 15:33:59 +08:00 committed by GitHub
parent 7b91c4405c
commit 931ecb3ba7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 100 additions and 18 deletions

View File

@ -30,7 +30,8 @@ import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author Zheng Jie
@ -53,21 +54,15 @@ public class UserDetailsServiceImpl implements UserDetailsService {
*
* @see {@link UserCacheClean}
*/
static Map<String, JwtUserDto> userDtoCache = new ConcurrentHashMap<>();
final static Map<String, Future<JwtUserDto>> userDtoCache = new ConcurrentHashMap<>();
public static ExecutorService executor = newThreadPool();
@Override
public JwtUserDto loadUserByUsername(String username) {
boolean searchDb = true;
JwtUserDto jwtUserDto = null;
if (loginProperties.isCacheEnable() && userDtoCache.containsKey(username)) {
jwtUserDto = userDtoCache.get(username);
// 检查dataScope是否修改
List<Long> dataScopes = jwtUserDto.getDataScopes();
dataScopes.clear();
dataScopes.addAll(dataService.getDeptIds(jwtUserDto.getUser()));
searchDb = false;
}
if (searchDb) {
Future<JwtUserDto> future = userDtoCache.get(username);
if (!loginProperties.isCacheEnable()) {
UserDto user;
try {
user = userService.findByName(username);
@ -86,9 +81,86 @@ public class UserDetailsServiceImpl implements UserDetailsService {
dataService.getDeptIds(user),
roleService.mapToGrantedAuthorities(user)
);
userDtoCache.put(username, jwtUserDto);
}
}
return jwtUserDto;
}
if (future==null) {
Callable<JwtUserDto> call=()->getJwtBySearchDB(username);
FutureTask<JwtUserDto> ft=new FutureTask<>(call);
future=userDtoCache.putIfAbsent(username,ft);
if(future==null){
future=ft;
executor.submit(ft);
}
try{
return future.get();
}catch(CancellationException e){
userDtoCache.remove(username);
}catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e.getMessage());
}
}else{
try {
jwtUserDto=future.get();
}catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e.getMessage());
}
// 检查dataScope是否修改
List<Long> dataScopes = jwtUserDto.getDataScopes();
dataScopes.clear();
dataScopes.addAll(dataService.getDeptIds(jwtUserDto.getUser()));
}
return jwtUserDto;
}
private JwtUserDto getJwtBySearchDB(String username) {
UserDto user;
try {
user = userService.findByName(username);
} catch (EntityNotFoundException e) {
// SpringSecurity会自动转换UsernameNotFoundException为BadCredentialsException
throw new UsernameNotFoundException("", e);
}
if (user == null) {
throw new UsernameNotFoundException("");
} else {
if (!user.getEnabled()) {
throw new BadRequestException("账号未激活!");
}
JwtUserDto jwtUserDto = new JwtUserDto(
user,
dataService.getDeptIds(user),
roleService.mapToGrantedAuthorities(user)
);
return jwtUserDto;
}
}
public static ExecutorService newThreadPool() {
ThreadFactory namedThreadFactory = new ThreadFactory() {
final AtomicInteger sequence = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
int seq = this.sequence.getAndIncrement();
thread.setName("future-task-thread" + (seq > 1 ? "-" + seq : ""));
if (!thread.isDaemon()) {
thread.setDaemon(true);
}
return thread;
}
};
return new ThreadPoolExecutor(10, 200,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(1024),
namedThreadFactory,
new ThreadPoolExecutor.AbortPolicy());
}
}

View File

@ -45,14 +45,14 @@ public class User extends BaseEntity implements Serializable {
@ApiModelProperty(value = "ID", hidden = true)
private Long id;
@ManyToMany
@ManyToMany(fetch = FetchType.EAGER)
@ApiModelProperty(value = "用户角色")
@JoinTable(name = "sys_users_roles",
joinColumns = {@JoinColumn(name = "user_id",referencedColumnName = "user_id")},
inverseJoinColumns = {@JoinColumn(name = "role_id",referencedColumnName = "role_id")})
private Set<Role> roles;
@ManyToMany
@ManyToMany(fetch = FetchType.EAGER)
@ApiModelProperty(value = "用户岗位")
@JoinTable(name = "sys_users_jobs",
joinColumns = {@JoinColumn(name = "user_id",referencedColumnName = "user_id")},

View File

@ -5,7 +5,11 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ -13,14 +17,19 @@ public class LoginCacheTest {
@Resource(name = "userDetailsService")
private UserDetailsServiceImpl userDetailsService;
ExecutorService executor = Executors.newCachedThreadPool();
@Test
public void testCache() {
public void testCache() throws InterruptedException {
long start1 = System.currentTimeMillis();
int size = 10000;
int size = 1000;
CountDownLatch latch = new CountDownLatch(size);
for (int i = 0; i < size; i++) {
userDetailsService.loadUserByUsername("admin");
executor.submit(() -> userDetailsService.loadUserByUsername("admin"));
latch.countDown();
}
latch.await();
long end1 = System.currentTimeMillis();
//关闭缓存
userDetailsService.setEnableCache(false);
@ -31,4 +40,5 @@ public class LoginCacheTest {
long end2 = System.currentTimeMillis();
System.out.print("使用缓存:" + (end1 - start1) + "毫秒\n 不使用缓存:" + (end2 - start2) + "毫秒");
}
}