使用redis保存List导致的接口鉴权慢的问题

项目使用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倍,所以最终选择了方案二。

好了,文章就分享到这里了,看完如果对你有帮助,不要忘了点赞、收藏哦~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值