操作泥棒のこの波は冷やすことを利用Redisの+ AOP最適化の権限管理機能!

SpringBoot実際の電力供給のアイテムモール(30K +スター)住所:github.com/macrozheng / ...

概要

前に述べた多くの友人がありますたびにデータベースからユーザー情報をチェックする権限を照会するインターフェイスにアクセスするので、モール事業権管理機能は、パフォーマンス上の問題を抱えています。最近、この問題はRedisの+ AOP、私の最適化のアイデアの下に、以下の用語によって問題を解決するために最適化されています。

事前知識

この記事では、SpringのデータのRedisのいくつかの知識を習得する必要がある、と友人は見ることができます理解していない「春データRedisのベストプラクティスを!"また、春AOPのいくつかの知識を必要とし、理解していない友人が見ることができる「アクセスログレコードにAOPのインターフェースを使用してSpringBootアプリケーションを」

問題を再現するために、

ではmall-securityフィルタモジュール、ときに、ユーザのログイン、トークン要求は、フィルタを介してかかります。このフィルタは、次のフィルタクラスのコードで、ステップは、データベースからユーザーのログイン情報を照会するユーザーの操作と同様のトークンシークレットフリーログ、によって運ばれます。

/**
 * 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);
    }
}
复制代码

我々は任意のインターフェイスにアクセスするためにログインすると、コンソールは、彼が、データベースからユーザーが所有しているユーザー情報とリソース情報を照会することを、次のログを出力します、各アクセスインタフェースは、この操作をトリガしている、いくつかの時間は、いくつかをもたらしますパフォーマンスの問題。

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
复制代码

キャッシュとしてRedisの利用

上記の問題については、最も可能性が高い頻度の高いクエリにデータベースを避けるために、行くために、リソースのRedisにユーザー情報とユーザ情報を考えると、これの一般的な考え方を最適化することがケースです。

春のセキュリティは、このメソッドは、データベースクエリ操作を実行するためにどのような時にキャッシュ方法、私たち最初の外観を追加するためにまず、ユーザー情報を取得する必要があります。

/**
 * 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("用户名或密码错误");
    }
}
复制代码

メインは、これら2つの操作のリソースへのユーザーアクセスの情報とユーザ情報を取得することで、我々は本明細書中で使用されるように、これらの2つの操作は、操作をキャッシュに追加する必要があり、操作のRedisTempleモードです。クエリデータは、Redisのキャッシュクエリを行くと、無Redisの場合は、データベースのクエリから、将来的に格納されたデータへの問合せは、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;
    }
}
复制代码

このクエリは、使用RedisTemplateはそれを直接操作するために、なぜ、もっとシンプルな、達成するために直接使用@Cacheableを動作させるために春キャッシュして上に実際にありますか?キャッシュとして、我々はRedisのがダウンしている場合は、私たちのビジネス・ロジックに影響がないことを願っていますのでRedisのがダウンした後に、ユーザのログインおよび様々な他の操作をすることができないだろうと、使用中に春のキャッシュは、叶いますA。

我々はRedisのにキャッシュされたユーザ情報とユーザのリソース情報、削除されたときに、我々は変更する場合、ユーザー情報やリソース情報を正確に、キャッシュ内のデータを削除する必要がありますを置くので、あなたはコメントキャッシュビジネスクラスを表示することができます。

/**
 * 后台用户缓存操作类
 * 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);
}
复制代码

上記の最適化された一連の後、パフォーマンスの問題が解決されます。しかし、新しい技術を導入した後に、新たな問題もダウンRedisの後、我々はここで、我々はこの問題を解決するためにAOPを使用し、直接ログインすることができないとき、例えば、生成されます。

AOPを使用してキャッシュの動作例外処理

なぜ、このような問題を解決するためにAOPを使うのか?私たちのビジネスクラスのキャッシュがあるのでUmsAdminCacheService書かれている、方法キャッシュのビジネスクラスは、通常のビジネス・ロジックに影響を与えないことを保証するために、あなたは内のすべてのメソッドで追加する必要がtry catchロジック。使用AOPは、私たちは地元に書くことができtry catchロジック、その後、すべてのメソッドまでに適用されます。我々はいくつかのビジネスクラスのキャッシュを得た場合、想像して、時間を設定次のセクションなどとして、動作し、より便利に、この波!

まずは、直接例外のうちのアドバイスを中心にその取り扱いには、関連するキャッシュアプ​​リケーション上のビジネスクラスでのカットを定義でき、確実にフォローアップの操作が実行されます。

/**
 * 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;
    }

}
复制代码

この処理の後、私たちのRedisがダウンした場合でも、当社のビジネス・ロジックを適切に実行することができます。

ただし、いないすべてのメソッドは、このような我々のコードのストレージとして例外を処理するために必要とされ、私たちのRedisがダウンした場合、我々のコード・ストレージ・インターフェースが必要とされているではなく、正常に実行返すので、エラーです。

私たちは、カスタムアノテーションが行われていることができます。この需要以上の場合は、最初にすべての私たちは、カスタマイズCacheException、あなたが上記のこの注釈の方法を持っている場合は、例外がスローされた直接発生し、コメントを。

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

以下のために、私たちのアスペクトクラスを変換する必要が後@CacheExceptionに例外を直接スロー発生した場合は、注釈付きの方法。

/**
 * 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;
    }

}
复制代码

我々が必要とする次の@CacheExceptionここで注意すべきコードを取得するまで保存するために注釈アプリケーションおよび方法はので、あなたが実装クラスのインターフェイスに適用するのではなくしたいということですisAnnotationPresent方法は、現在の方法にコメントを得ることができますが、それを得ることができませんインターフェイスメソッドの実装の注意事項。

/**
 * 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);
    }
}
复制代码

概要

頻繁に、クエリのパフォーマンスへの影響データベースのために、我々はキャッシュとしてのRedisことによって最適化することができます。キャッシュの操作は、我々はキャッシュ操作統一例外を処理するためにAOPを使用することができ、通常のビジネス・ロジックに影響を与えるべきではありません。

プロジェクトのソースアドレス

github.com/macrozheng/...

いいえ公共ありません

モールは、プロジェクトで連載フルチュートリアル国民の関心番号取得する最初の時間を。

いいえ公共絵ません

おすすめ

転載: juejin.im/post/5e78b96b6fb9a07cb83e4a10