项目使用aop实现的接口访问控制,整合了redis,登录的时候查询当前用户的权限保存到redis中,以后每次从redis中获取,避免频繁访问数据库。
今天偶然发现了一个非常影响性能的问题,居然是因为使用redis保存权限列表的代码导致的,因为运行正常,以前没有关心,而且有时候很快,有时候很慢。
package cn.edu.sgu.www.mhxysy.redis;
import cn.edu.sgu.www.mhxysy.service.system.PermissionService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class RedisRepository {
private static final String SUFFIX = "ROLE_PERMISSIONS::";
@Autowired
private PermissionService permissionService;
@Autowired
RedisTemplate<String, List<String>> redisTemplate;
/**
* 查询用户的权限信息,并保存到redis
* @param username 用户名
*/
public void save(String username) {
String key = SUFFIX + username;
// 查询用户的权限
List<String> permissions = permissionService.selectPermissionsByUsername(username);
redisTemplate.boundListOps(key).leftPush(permissions);
redisTemplate.boundListOps(key).expire(60, TimeUnit.MINUTES);
log.debug("用户{}的权限保存到了redis中", username);
}
/**
* 通过用户名获取用户的权限信息
* @param username 用户名
*/
public List<String> get(String username) {
String key = SUFFIX + username;
Boolean hasKey = redisTemplate.hasKey(key);
if (Boolean.TRUE.equals(hasKey)) {
List<String> list = redisTemplate.boundListOps(key).rightPop();
return list;
} else {
save(username);
return get(username);
}
}
/**
* 通过用户名删除用户的权限信息
* @param username 用户名
*/
public void remove(String username) {
String key = SUFFIX + username;
redisTemplate.delete(key);
log.debug("从redis中删除用户{}的权限...", username);
}
/**
* 删除全部用户的权限信息
*/
public void removeAll() {
Set<String> keys = redisTemplate.keys(SUFFIX + "*");
log.debug("删除全部用户的权限...");
assert keys != null;
redisTemplate.delete(keys);
}
}
问题发生的原因是:rightPop()方法是根据key获取value(同时还会删除这个key),所以每次获取权限的时候,对应的key一直不存在,导致了每次通过get(String key)方法获取权限时都是走的else分支,执行save()方法去查数据库,redis根本没有起到作用。
解决方案:
方案一:既然rightPop()方法的作用是删除并返回key,那么执行完rightPop()之后再保存一次就行了,也不需要对原有代码做太大的改进。
package cn.edu.sgu.www.mhxysy.redis;
import cn.edu.sgu.www.mhxysy.service.system.PermissionService;
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class RedisRepository {
private static final String SUFFIX = "ROLE_PERMISSIONS::";
@Autowired
private PermissionService permissionService;
@Autowired
RedisTemplate redisTemplate;
/**
* 查询用户的权限信息,并保存到redis
* @param username 用户名
*/
public void save(String username) {
String key = SUFFIX + username;
// 查询用户的权限
List<String> permissions = permissionService.selectPermissionsByUsername(username);
redisTemplate.boundListOps(key).leftPush(permissions);
redisTemplate.boundListOps(key).expire(30, TimeUnit.MINUTES);
log.debug("用户{}的权限保存到了redis中", username);
}
/**
* 通过用户名获取用户的权限信息
* @param username 用户名
*/
public List<String> get(String username) {
String key = SUFFIX + username;
if (Boolean.TRUE.equals(redisTemplate.hasKey(key))) {
List<String> permissions = redisTemplate.opsForList().rightPop(key);
if (permissions != null && !permissions.isEmpty()) {
redisTemplate.boundListOps(key).leftPush(permissions);
redisTemplate.boundListOps(key).expire(30, TimeUnit.MINUTES);
}
return permissions;
} else {
save(username);
return get(username);
}
}
}
方案二:把List<String>转成json字符串存到redis中,获取的时候再将json格式的字符串反序列化成对象。
package cn.edu.sgu.www.mhxysy.redis;
import cn.edu.sgu.www.mhxysy.service.system.PermissionService;
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class RedisRepository {
private static final String SUFFIX = "ROLE_PERMISSIONS::";
@Autowired
private PermissionService permissionService;
@Autowired
RedisTemplate redisTemplate;
/**
* 查询用户的权限信息,并保存到redis
* @param username 用户名
*/
public void save(String username) {
String key = SUFFIX + username;
String jsonString = JSON.toJSONString(permissions);
redisTemplate.opsForValue().set(key, jsonString);
redisTemplate.expire(key, 30, TimeUnit.MINUTES);
log.debug("用户{}的权限保存到了redis中", username);
}
/**
* 通过用户名获取用户的权限信息
* @param username 用户名
*/
public List<String> get(String username) {
String key = SUFFIX + username;
if (Boolean.TRUE.equals(redisTemplate.hasKey(key))) {
String permissions = (String) redisTemplate.opsForValue().get(key);
return (List<String>) JSON.parse(permissions);
} else {
save(username);
return get(username);
}
}
}
最后经过简单地测试性能之后,发现方案二的速度要比方案一快了1倍,所以最终选择了方案二。
好了,文章就分享到这里了,看完如果对你有帮助,不要忘了点赞、收藏哦~