zoukankan      html  css  js  c++  java
  • 论看源码的重要性,写给自己程序员的第二年

    记:笔者于2018年6月毕业至今,即将进入程序员的转折点,在第二年到第三年。

    之前写代码总是自己闷头造车,crud写的越来越多,但是真正的技术上的进步却很少看到,现在已经进入到半吊子时期,基本的业务需求都能写下来,但是真正遇到一些稀奇古怪的问题就感觉到半吊子深深的无力感。

    出现的场景如下:

    1、公司方面业务需要,整合用户,想实现单点登录,登出的方式,选用cas框架。

    2、奇怪的bug ,原项目整合cas客户端后偶尔出现异常不被全局异常拦截,直接将堆栈信息打印在页面上的问题。

    针对于场景一:

      公司原有的老项目接入cas,在项目中整合进cas客户端,却频繁出现无法单点登出的情况。采用的方式是重复去复现bug的场景,定位看是否有规律产生,就像我们知道的那样,机器是不会骗人的,经过多次的重复测试复现之后,将问题稳定复现出来。如下:系统A 和系统B  如果用户在系统A调用登出接口,则系统B必定登出,如果用户在系统B调用单点登出接口,则系统A有一定的概率下会登出失败。

      之前cas项目是公司的一个同事写的,现在该同事已经离职,新接手项目,只能闷头去看了。鉴于复现的场景的问题,询问了部署的场景,猜测是因为集群的问题导致的。项目A是采用集群的方式部署的,java的有两台机器,项目B是单机。初步猜测原因可能是在项目A登出的时候通知项目B,单机必然通知到。项目B通知项目A的时候偶发通知不到的情况。(为了能简单理解如此描述,实际的通知过程是:项目A -> cas ->项目B  或者 项目B -> cas ->项目A)。

      在网上搜寻解决方案的时候看到三种解决方案:(1)将集群项目A的配置改成ip hash,实际现在线上的方式是ip轮询的方式  (2)通知的时候采用ip广播的形式,将机器部署的ip广播出去,如果本台机器没有执行成功就转发给下一台机器。(3)重写session存储。

      以上是解决方案,但是关于为什么cas会出现这个问题,网上并没有很详细的解释。个人猜测是因为用来作为登录的标识是存放在内存中,并不是redis中,但是在接手项目时,同事明确告知标识是存储在redis中的,到redis库中看的情况确实是存储了,而且失效的时候,redis中这个key也没有值,但是在登出失败的情况下,redis中的这个值还是存在。在网上看博客的解决方案是,看到有一篇说到-cas本身就是不支持集群的,突然恍然大悟,去翻看源码中存储session标识的代码,如下:

    public final class HashMapBackedSessionMappingStorage implements SessionMappingStorage {
        private final Map<String, HttpSession> MANAGED_SESSIONS = new HashMap();
        private final Map<String, String> ID_TO_SESSION_KEY_MAPPING = new HashMap();
        private final Logger logger = LoggerFactory.getLogger(this.getClass());
    
        public HashMapBackedSessionMappingStorage() {
        }
    
        public synchronized void addSessionById(String mappingId, HttpSession session) {
            this.ID_TO_SESSION_KEY_MAPPING.put(session.getId(), mappingId);
            this.MANAGED_SESSIONS.put(mappingId, session);
        }
    }
    

      可以看到 SessionMappingStorage 的实现类中 实际上存储session 和session 和mapping关系映射的内容是存储在map当中的,也就是写在内存当中。那么解决这个问题就不难了,重写这个方法的实现并添加到filter中,这两个关系都采用redis来存储。完结,撒花。网上也有人采用广播的形式的方法,也是很好的,但是对比来说实现成本较大,而且也不是从根本上来解决问题。对于采用ip hash的方式在测试服务器上搭建集群来看也是可行的,但是领导不允许线上采用ip hash   enenen   所以此路不通。

      总结看来源码也没那么可怕,找到思路和重点关注的问题点去看问题,解决方案就出来了。

    针对于场景二:

      在写java项目的时候,我们通常都会在项目创建之初写一个全局异常处理类,将系统中由于代码错误或者其他问题产生的异常情况封装成code message的固定形式返回给前台页面。之前项目一直平稳的运行着,在引入cas客户端后,解决单点登出问题的时候出现情况:接口访问500,堆栈信息直接打印在前台。

      还是先将问题定位,之前怀疑是与异常类型有关,在登录接口是 ,票据验证不通过,登录失败的情况下打印出来是 TicketValidationException  还出现过 由于redis服务器挂掉出现: RedisConnectionFailureException。在本地模拟

    @GetMapping("/test")
        public void test() throws Exception {
            throw new TicketValidationException("票据验证异常");
            // throw new RedisConnectionFailureException("redis连接异常");
        }
    

      两种异常均能被拦截,正常返回。至此,此路不通,跟异常类型一点原因都没有,去查找异常发生的位置,发现了如下代码:

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            HttpServletRequest request = (HttpServletRequest)servletRequest;
            HttpServletResponse response = (HttpServletResponse)servletResponse;
            if(!this.handlerInitialized.getAndSet(true)) {
                HANDLER.init();
            }
    
            if(HANDLER.process(request, response)) {
                filterChain.doFilter(servletRequest, servletResponse);// 发生异常的代码行
            }
    
        }
    

      好嘛,这还不简单,我来try catch你,  先catch下TicketValidationException  模拟线上发生的票据不通过的情况,然后一脸震惊,居然还是把堆栈信息打印在前台页面上,,,接着周五快下班了,大家都懂的,这个问题就被搁置了。回家后还是在想这个问题,打开电脑试了下  RedisConnectionFailureException  可以被捕获到,但是页面返回还是不正确的。在这里重写response后正常返回。重写response的方法如下:

    public static void outResponse(HttpServletResponse response, Object object) {
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            PrintWriter out = null;
            try {
                out = response.getWriter();
                out.append(JSONObject.toJSONString(object));
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (out != null) {
                    out.close();
                }
            }
        }
    

      redis连接异常解决了,那么票据验证失败异常呢?在catch了redis连接异常之后catch到 Exception大异常类,输出response 发现能够可以被拦截到。那为什么票据验证失败这个异常拦截不到呢,去看源码里的实现:

    catch (TicketValidationException var8) {
        this.logger.debug(var8.getMessage(), var8);
        this.onFailedValidation(request, response);
        if (this.exceptionOnValidationFailure) {
          throw new ServletException(var8);
        }
        response.sendError(403, var8.getMessage());
        return;
    

      好家伙,他在这里抛出的居然是  ServletException 修改代码,发生票据异常的时候捕获的内容应该捕获  ServletException 至此完结。

      听到源码的时候就感觉它像个大老虎站在那里,但是认真的去研究他的时候,真的像个小猫咪一样温柔可亲。不排除确实有大佬写的代码晦涩难懂,但是毕竟都是人写出来的~,只要肯用心去读,肯定会有所收获。希望疫情赶快过去,2020大家都能被善待。

  • 相关阅读:
    leetcode 309. Best Time to Buy and Sell Stock with Cooldown
    leetcode 714. Best Time to Buy and Sell Stock with Transaction Fee
    leetcode 32. Longest Valid Parentheses
    leetcode 224. Basic Calculator
    leetcode 540. Single Element in a Sorted Array
    leetcode 109. Convert Sorted List to Binary Search Tree
    leetcode 3. Longest Substring Without Repeating Characters
    leetcode 84. Largest Rectangle in Histogram
    leetcode 338. Counting Bits
    git教程之回到过去,版本对比
  • 原文地址:https://www.cnblogs.com/cswxl/p/12691370.html
Copyright © 2011-2022 走看看