optimización de la funcionalidad de gestión de privilegios uso Redis + AOP que esta ola de frío operaciones ladrón!

SpringBoot centro proveedor real de electricidad elemento (30k + estrella) Dirección: github.com/macrozheng / ...

resumen

Hay un montón de amigos mencionados antes, capacidades de gestión de los derechos del proyecto centro comercial tienen problemas de rendimiento, ya que cada vez que acceda a la interfaz para consultar la autoridad verificará la información de usuario de la base de datos. Recientemente, esta cuestión ha sido optimizado para resolver el problema Redis + AOP, los siguientes términos en mis ideas de optimización.

Pre-conocimiento

Este artículo tiene que aprender un poco de conocimiento de la primavera de datos Redis, y los amigos no entienden pueden buscar "mejores prácticas de primavera de datos Redis! " . También es necesario un cierto conocimiento de la primavera AOP, amigos no entienden pueden mirar "aplicaciones SpringBoot utilizando la interfaz de AOP a los registros de registro de acceso" .

Para reproducir el problema

En mall-securityun módulo de filtro, cuando un inicio de sesión de usuario, petición de señal se llevará a través del filtro. Este filtro se realiza mediante la operación de usuario similar elemento secreto de registro gratuito, etapa que se consulta la información de inicio de sesión de usuario de la base de datos, el siguiente es el código de la clase de filtro.

/**
 * JWT登录授权过滤器
 * Created by macro on 2018/4/26.
 */
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class);
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Value("${jwt.tokenHeader}")
    private String tokenHeader;
    @Value("${jwt.tokenHead}")
    private String tokenHead;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws ServletException, IOException {
        String authHeader = request.getHeader(this.tokenHeader);
        if (authHeader != null && authHeader.startsWith(this.tokenHead)) {
            String authToken = authHeader.substring(this.tokenHead.length());// The part after "Bearer "
            String username = jwtTokenUtil.getUserNameFromToken(authToken);
            LOGGER.info("checking username:{}", username);
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                //此处会从数据库中获取登录用户信息
                UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
                if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    LOGGER.info("authenticated user:{}", username);
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        }
        chain.doFilter(request, response);
    }
}
复制代码

Cuando nos registramos en el acceso a cualquier interfaz, la consola imprimir los siguientes registros, que iba a consultar la información de usuarios y recursos de información de propiedad del usuario de la base de datos, cada uno de interfaces de acceso han desencadenado esta operación, algún tiempo traerá un poco problemas de rendimiento.

2020-03-17 16:13:02.623 DEBUG 4544 --- [nio-8081-exec-2] c.m.m.m.UmsAdminMapper.selectByExample   : ==>  Preparing: select id, username, password, icon, email, nick_name, note, create_time, login_time, status from ums_admin WHERE ( username = ? ) 
2020-03-17 16:13:02.624 DEBUG 4544 --- [nio-8081-exec-2] c.m.m.m.UmsAdminMapper.selectByExample   : ==> Parameters: admin(String)
2020-03-17 16:13:02.625 DEBUG 4544 --- [nio-8081-exec-2] c.m.m.m.UmsAdminMapper.selectByExample   : <==      Total: 1
2020-03-17 16:13:02.628 DEBUG 4544 --- [nio-8081-exec-2] c.macro.mall.dao.UmsRoleDao.getMenuList  : ==>  Preparing: SELECT m.id id, m.parent_id parentId, m.create_time createTime, m.title title, m.level level, m.sort sort, m.name name, m.icon icon, m.hidden hidden FROM ums_admin_role_relation arr LEFT JOIN ums_role r ON arr.role_id = r.id LEFT JOIN ums_role_menu_relation rmr ON r.id = rmr.role_id LEFT JOIN ums_menu m ON rmr.menu_id = m.id WHERE arr.admin_id = ? AND m.id IS NOT NULL GROUP BY m.id 
2020-03-17 16:13:02.628 DEBUG 4544 --- [nio-8081-exec-2] c.macro.mall.dao.UmsRoleDao.getMenuList  : ==> Parameters: 3(Long)
2020-03-17 16:13:02.632 DEBUG 4544 --- [nio-8081-exec-2] c.macro.mall.dao.UmsRoleDao.getMenuList  : <==      Total: 24
复制代码

Use Redis como caché

Para el problema anterior, los más propensos a pensar en la información del usuario y la información del usuario en el Redis recursos para ir, para evitar la consulta frecuente de la base de datos, la optimización de la idea general de que este es el caso.

En primer lugar tenemos que conseguir información de usuario para la primavera de Seguridad para añadir método de almacenamiento en caché, primero vistazo a lo que este método para realizar operaciones de consulta de base de datos.

/**
 * UmsAdminService实现类
 * Created by macro on 2018/4/26.
 */
@Service
public class UmsAdminServiceImpl implements UmsAdminService {
    @Override
    public UserDetails loadUserByUsername(String username){
        //获取用户信息
        UmsAdmin admin = getAdminByUsername(username);
        if (admin != null) {
            //获取用户的资源信息
            List<UmsResource> resourceList = getResourceList(admin.getId());
            return new AdminUserDetails(admin,resourceList);
        }
        throw new UsernameNotFoundException("用户名或密码错误");
    }
}
复制代码

La principal es la obtención de información y la información de usuario del acceso de los usuarios a los recursos de estas dos operaciones, entonces tenemos que añadir estas dos operaciones caché de operaciones, tal como se utiliza en este documento, es el modo de operación RedisTemple. Cuando los datos de la consulta, van consultas de caché Redis, si no hay Redis, y de una consulta de base de datos, la consulta a los datos almacenados en el futuro para ir Redis.

/**
 * UmsAdminService实现类
 * Created by macro on 2018/4/26.
 */
@Service
public class UmsAdminServiceImpl implements UmsAdminService {
    //专门用来操作Redis缓存的业务类
    @Autowired
    private UmsAdminCacheService adminCacheService;
    @Override
    public UmsAdmin getAdminByUsername(String username) {
        //先从缓存中获取数据
        UmsAdmin admin = adminCacheService.getAdmin(username);
        if(admin!=null) return  admin;
        //缓存中没有从数据库中获取
        UmsAdminExample example = new UmsAdminExample();
        example.createCriteria().andUsernameEqualTo(username);
        List<UmsAdmin> adminList = adminMapper.selectByExample(example);
        if (adminList != null && adminList.size() > 0) {
            admin = adminList.get(0);
            //将数据库中的数据存入缓存中
            adminCacheService.setAdmin(admin);
            return admin;
        }
        return null;
    }
    @Override
    public List<UmsResource> getResourceList(Long adminId) {
        //先从缓存中获取数据
        List<UmsResource> resourceList = adminCacheService.getResourceList(adminId);
        if(CollUtil.isNotEmpty(resourceList)){
            return  resourceList;
        }
        //缓存中没有从数据库中获取
        resourceList = adminRoleRelationDao.getResourceList(adminId);
        if(CollUtil.isNotEmpty(resourceList)){
            //将数据库中的数据存入缓存中
            adminCacheService.setResourceList(adminId,resourceList);
        }
        return resourceList;
    }
}
复制代码

Esta consulta es en realidad por encima con la primavera caché funcione de manera más sencilla, el uso directo @Cacheable para lograr, por qué el uso RedisTemplate manipular directamente? Porque como caché, esperamos que si Redis es hacia abajo, no se verá afectada nuestra lógica de negocio, mientras que el uso de la primavera caché hecho realidad, cuando después de Redis es hacia abajo, de inicio de sesión del usuario y varias otras operaciones se pueden no estar a.

Como nos ponemos la información de usuario y la información de recursos de usuario se almacenan en caché en el Redis, por lo que cuando modificamos la información del usuario y la información de recursos se requiere que los datos de borrado en la memoria caché, exactamente cuando se suprime, puede ver la clase de negocios comentario caché.

/**
 * 后台用户缓存操作类
 * Created by macro on 2020/3/13.
 */
public interface UmsAdminCacheService {
    /**
     * 删除后台用户缓存
     */
    void delAdmin(Long adminId);

    /**
     * 删除后台用户资源列表缓存
     */
    void delResourceList(Long adminId);

    /**
     * 当角色相关资源信息改变时删除相关后台用户缓存
     */
    void delResourceListByRole(Long roleId);

    /**
     * 当角色相关资源信息改变时删除相关后台用户缓存
     */
    void delResourceListByRoleIds(List<Long> roleIds);

    /**
     * 当资源信息改变时,删除资源项目后台用户缓存
     */
    void delResourceListByResource(Long resourceId);
}
复制代码

Después de una serie de optimizado anteriormente, el problema de rendimiento se resuelve. Pero después de la introducción de nuevas tecnologías, nuevos problemas también se producen, por ejemplo, cuando después de Redis abajo, no vamos a ser capaces de conectarse directamente, aquí usamos AOP para resolver este problema.

Cache excepción de operación de procesamiento usando AOP

¿Por qué utilizar AOP para resolver este problema? Debido a que nuestra memoria caché de clase de negocios UmsAdminCacheServicese ha escrito, para asegurar que la clase empresarial cachés método no afectan a la lógica de negocio normal, es necesario agregar en todos los métodos de try catchla lógica. El uso de AOP, podemos escribir en un local de try catchla lógica, a continuación, se aplica a todos los métodos arriba. Imagínese, si tenemos unos cuantos caché de clase de negocios, siempre y cuando la siguiente sección para configurar, operar y más conveniente esta ola!

En primer lugar, vamos a definir un corte en la clase de negocios por encima de la aplicación de caché correspondiente, en torno a su manejo consejos directamente de una excepción, aseguran operaciones de seguimiento para ser ejecutados.

/**
 * Redis缓存切面,防止Redis宕机影响正常业务逻辑
 * Created by macro on 2020/3/17.
 */
@Aspect
@Component
@Order(2)
public class RedisCacheAspect {
    private static Logger LOGGER = LoggerFactory.getLogger(RedisCacheAspect.class);

    @Pointcut("execution(public * com.macro.mall.portal.service.*CacheService.*(..)) || execution(public * com.macro.mall.service.*CacheService.*(..))")
    public void cacheAspect() {
    }

    @Around("cacheAspect()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result = null;
        try {
            result = joinPoint.proceed();
        } catch (Throwable throwable) {
            LOGGER.error(throwable.getMessage());
        }
        return result;
    }

}
复制代码

Después de este tratamiento, incluso si nuestra Redis se ha reducido, nuestra lógica de negocio se puede ejecutar correctamente.

Sin embargo, se necesitan no todos los métodos para manejar excepciones, como nuestro almacenamiento de código, si nuestra Redis es abajo, es necesaria nuestra interfaz de almacenamiento de código es un error, en lugar de devolver ejecutado con éxito.

Por lo anterior esta demanda Podemos aduana anotación se realiza, en primer lugar nos personalizar un CacheExceptioncomentario, si usted tiene este método de anotación anterior, la excepción se produce lanzado directamente.

/**
 * 自定义注解,有该注解的缓存方法会抛出异常
 */
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheException {
}
复制代码

Después de la necesidad de transformar nuestras clases de aspecto, por un @CacheExceptionmétodo anotado, si se produce una excepción lanzada directamente.

/**
 * Redis缓存切面,防止Redis宕机影响正常业务逻辑
 * Created by macro on 2020/3/17.
 */
@Aspect
@Component
@Order(2)
public class RedisCacheAspect {
    private static Logger LOGGER = LoggerFactory.getLogger(RedisCacheAspect.class);

    @Pointcut("execution(public * com.macro.mall.portal.service.*CacheService.*(..)) || execution(public * com.macro.mall.service.*CacheService.*(..))")
    public void cacheAspect() {
    }

    @Around("cacheAspect()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        Object result = null;
        try {
            result = joinPoint.proceed();
        } catch (Throwable throwable) {
            //有CacheException注解的方法需要抛出异常
            if (method.isAnnotationPresent(CacheException.class)) {
                throw throwable;
            } else {
                LOGGER.error(throwable.getMessage());
            }
        }
        return result;
    }

}
复制代码

A continuación tenemos que @CacheExceptionlas aplicaciones y los métodos Anotar para almacenar hasta obtener un código de señalar aquí es que desea aplicar en lugar de en la interfaz de la clase de implementación porque isAnnotationPresentel método sólo se puede llegar a hacer comentarios sobre el método actual, pero no se puede llegar a ella Notas de implementación de los métodos de interfaz.

/**
 * UmsMemberCacheService实现类
 * Created by macro on 2020/3/14.
 */
@Service
public class UmsMemberCacheServiceImpl implements UmsMemberCacheService {
    @Autowired
    private RedisService redisService;
    
    @CacheException
    @Override
    public void setAuthCode(String telephone, String authCode) {
        String key = REDIS_DATABASE + ":" + REDIS_KEY_AUTH_CODE + ":" + telephone;
        redisService.set(key,authCode,REDIS_EXPIRE_AUTH_CODE);
    }

    @CacheException
    @Override
    public String getAuthCode(String telephone) {
        String key = REDIS_DATABASE + ":" + REDIS_KEY_AUTH_CODE + ":" + telephone;
        return (String) redisService.get(key);
    }
}
复制代码

resumen

Para el impacto en el rendimiento de consulta frecuente la base de datos, que puede optimizarse Redis como una memoria caché. operación de caché no debe afectar a la lógica de negocio normal, podemos utilizar AOP para manejar excepciones unificadas operación de caché.

Proyecto Dirección de origen

github.com/macrozheng/...

sin pública

centro proyectar un tutorial completo serializado en número de la preocupación del público por primera vez de obtener.

No hay imagen pública

Supongo que te gusta

Origin juejin.im/post/5e78b96b6fb9a07cb83e4a10
Recomendado
Clasificación