常见需求解决方案与开发总结

175 阅读16分钟

需求解决方案

如何查询千万数据量:

  • 查询大数据量用途: ①迁移数据 ②导出数据 ③批量处理数据
  • 查询大数据量方式: ①常规查询,一次性读取 500w 数据到 JVM 内存中,或者分页读取 ②流式查询,建立长连接,利用服务端游标,每次读取一条加载到 JVM 内存 ③游标查询,和流式一样,通过 fetchSize 参数,控制一次读取多少条数据
  • 常规查询: ①假设单表 500w 数据量,没有人会一次性加载到内存中,一般会采用分页的方式。因为将全部数据一次性查询到内存中的话数据量太大会爆掉内存。 ②上述方式比较简单,但是在不考虑 LIMIT 深分页优化情况下,线上数据库服务器就凉了,亦或者你能等个几天时间检索数据。
@SneakyThrows
@Override
public void pageQuery() {
    @Cleanup Connection conn = dataSource.getConnection();
    @Cleanup Statement stmt = conn.createStatement();
    long start = System.currentTimeMillis();
    long offset = 0;
    int size = 100;
    while (true) {
        String sql = String.format("SELECT COLUMN_A, COLUMN_B, COLUMN_C FROM YOU_TABLE LIMIT %s, %s", offset, size);
        @Cleanup ResultSet rs = stmt.executeQuery(sql);
        long count = loopResultSet(rs);
        if (count == 0) break;
        offset += size;
    }

    log.info("      分页查询耗时 :: {} ", System.currentTimeMillis() - start);
}

自定义SpringMVC的数据校验注解以及处理过程:

  • 自定义注解
  • 创建一个类实现ConstraintValidator接口,实现接口方法 ①initialize方法:会在校验实例化后被调用,一般用于做些初始化工作。 ②isValid方法:实际执行验证的方法,第一个参数获取的是字段或对象实际对应的值,取决于类的枚举限定类型。第二个参数是ConstraintValidatorContext,上下文可以做些默认的设置。
  • 最后再自定义注解的@Constraint注解中加上创建的类名。
@Constraint(validatedBy = XXX.class)

自定参数解析器:

  • 自定义解析器需要实现HandlerMethodArgumentResolver接口
  • 接口说明: ①supportsParameter:用于判定是否需要处理该参数分解,返回true为需要,并会去调用下面的方法resolveArgument。 ②resolveArgument:真正用于处理参数分解的方法,返回的Object就是controller方法上的形参对象。
/**
 - 有@LoginUser注解的方法参数,注入当前登录用户
 */
@Component
public class LoginUserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
    @Autowired
    private UserService userService;

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.getParameterType().isAssignableFrom(UserEntity.class) && parameter.hasParameterAnnotation(LoginUser.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer container,
                                  NativeWebRequest request, WebDataBinderFactory factory) throws Exception {
        //获取用户ID
        Object object = request.getAttribute(AuthorizationInterceptor.USER_KEY, RequestAttributes.SCOPE_REQUEST);
        if(object == null){
            return null;
        }
        //获取用户信息
        UserEntity user = userService.selectById((Long)object);
        return user;
    }
}

如何保证缓存和数据库一致性:

  • 链接:如何保持mysql和redis中数据的一致性?
  • 想要提高应用的性能,可以引入「缓存」来解决
  • 引入缓存后,需要考虑缓存和数据库一致性问题,可选的方案有:「更新数据库 + 更新缓存」、「更新数据库 + 删除缓存」
  • 更新数据库 + 更新缓存方案,在「并发」场景下无法保证缓存和数据一致性,解决方案是加「分布锁」,但这种方案存在「缓存资源浪费」和「机器性能浪费」的情况
  • 采用「先删除缓存,再更新数据库」方案,在「并发」场景下依旧有不一致问题,解决方案是「延迟双删」,但这个延迟时间很难评估
  • 采用「先更新数据库,再删除缓存」方案,为了保证两步都成功执行,需配合「消息队列」或「订阅变更日志」的方案来做,本质是通过「重试」的方式保证数据最终一致
  • 采用「先更新数据库,再删除缓存」方案,「读写分离 + 主从库延迟」也会导致缓存和数据库不一致,缓解此问题的方案是「延迟双删」,凭借经验发送「延迟消息」到队列中,延迟删除缓存,同时也要控制主从库延迟,尽可能降低不一致发生的概率
  • 总结一下:没有完美的强一致性解决方案。除非你在更新缓存成功之前,停止所有的读缓存操作,但是这也就失去了缓存的意义。我们所能做的就是在缓存的不一致程度与缓存有效期之间做一个权衡,尽可能的减少不一致的情况发生

Spring初始化操作:

  • Spring中的InitializingBean接口的使用: ①InitializingBean接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是继承该接口的类,在初始化bean的时候都会执行该方法。 ②在配置bean的时候使用init-method配置也可以为bean配置初始化方法,在Spring初始化bean的时候,如果该bean实现了InitializingBean接口,并且同时在配置文件中指定了init-method,系统则是先调用afterPropertieSet()方法,然后再调用init-method中指定的方法。 ③链接:Spring中的InitializingBean接口的使用
  • Java 中的 static 使用之静态初始化块: ①在类的声明中,可以包含多个初始化块,当创建类的实例时,就会依次执行这些代码块。如果使用 static 修饰初始化块,就称为静态初始化块。 ②需要特别注意:静态初始化块只在类加载时执行,且只会执行一次,同时静态初始化块只能给静态变量赋值,不能初始化普通的成员变量。

开发总结

每日开发总结:

  • 对于每天开发时都需要更新代码(git pull),而且每次更新完都需要刷新一下maven。
  • 对于每个新项目时都需要确保IDEA是否配置好,例如:maven路径,文件编码等等。
  • 对于每个需求理解到位后,写好解决方案(包括数据库设计,sql编写,接口api以及以及影响的地方的改造)。
  • 对于每个缺陷理解到位后,在前端复现一遍然后由接口一步一步往里分析。
  • 处理数据时要思考清楚前后的处理维度,处理业务时要思考清楚业务的特性。
  • 对于每个任务(需求开发,缺陷修复,文档撰写,基础服务配置)
  • 入手项目时对于配置文件: ①spring和springMVC和jpa和mybatis等配置文件放在Resource或classes中 ②对于扫描都是规定好XXX/XXX/*这种,只要把对应的文件放在对应的组织.公司.模块名中即可
  • 人力成本,优化,代码维护性,安全,开发时四者取其二或其三即可。

编程数理逻辑:

  • 编程需求的逻辑要求: ①把各种情况、会出现的问题都想全了 ②要你能抽丝剥茧,然后做得井井有条
  • 集关系: 在这里插入图片描述

计算机Java学习路线:

  • 学计算机应该自顶向下:知道GitHub->了解云服务->知道VSCode->知道科学上网->知道Google->知道Stackoverflow
  • 两不要: ①学个啥都要买本教材,试图线性地,从头读到尾(真实情况:经典教材都能下载到免费pdf,语言,框架,文档往往已经写的很好,而且最新,github上有无数优质开源学习资源) ②不读优质代码,不参考最佳实践,啥都要自己从头开始搞
  • 三个要: ①要读文档,英文文档 ②做知识输出,用文字总结自己的学习内容 ③多用命令行
  • 两个原则: ①有问题,先文档,再stackoverflow,再技术文章 ②要用实例驱动学习,不要说你会什么,要说你做了什么

学习新技术流程:

  • 学习官方文档: ①深刻理解里面的概念(是什么)以及设计(为什么,怎么做) ②并不需要一下子读完,期间可以穿插一些教程或宣传视频学习,显得不那么枯燥 ③并跟着官方文档的Hello World教程学习 ④在做②③的同时,还要回到官方文档了解 ⑤直到读完大部分官方文档(可根据个人需求跳过少许章节) ⑥做简单笔记(不要机械地抄下来或翻译,而是写一些看到的关键字,反复出现的术语,阅读中产生的困惑等等)
  • 实践: ①官方文档是学习的基础和核心,实践是验收学习到的书面知识的唯一办法 ②如何实践: <1>除了Hello World教程的简单代码,可以进一步魔改它,让它变出花样 <2>在个人项目里尝试根据自己的需求使用这门技术 <3>研究公司的项目代码是如何使用这门技术的 ③这时你一定会踩很多坑: <1为什么这里代码没有像预期那么跑? <2>为什么多种写法却得到同样效果? <3>如果我想实现这种特别的效果怎么做? <4>为什么公司项目代码里不是按照官方示例代码那么写? ④我们就要去填坑,去debug,去查搜索引擎(首推Google,Stackoverflow),去问同事。过程中我们会切切实实开始理解这门技术,并逐渐灵活使用它。
  • 进阶: ①官方文档更倾向于介绍技术的核心设计和基础概念,部分官方文档也有进阶教程 ②如果想进阶,经典书籍和大神博客是两个很好的途径 ③如果还想进一步,那就考虑读源码了
  • 其他: ①Hello World实践的小技巧:学习新技术时可能需要折腾计算机环境,例如增加了文件结构,改变了环境变量,注册了开机启动等等。这时我们可以优先使用Docker

入手项目流程:

  • 首先对于个人发展来说技术最为重要,但对于一个项目或产品来说技术服务于业务(此外要想有所提升还要去关注需求背后的业务问题,而不是产品经理的需求)
  • 因此先弄清楚业务的用户,提供的功能,系统上下游的位置
  • 再去了解项目或产品使用的技术,部署环境,发布流程
  • 了解完业务与技术,再从一个个小问题去入手,逐渐了解全部,因为系统较为复杂时,实现细节太多
  • 以上三步即可基本了解完整个项目,若自己个人想进一步提升,需要做到开发过程中: ①整理项目的需求和设计思路,并思考项目的设计思路是否可以优化 ②照着需求和设计思路自己独立开发 ③对比自己与原先项目的差距,逐步完善并测试通过
  • 倘若不想如此重新编写项目,可以尝试修改核心部分代码,重新设计,并跑起来
  • 吃透一个项目的标准:下次遇到此类项目可以快速使用写过项目的解决方案完成它

解决一个Bug的流程:

  • 先google,stackoverflow 搜索类似问题,寻找跟问题有关联的答案,提出问题等,寻求解决办法或者启发。
  • 第一步无果后,问题向项目组抛出,寻求组织的力量和援助,让pm知道你碰到了技术难题,以此pm可能需要协调资源进来,并重新把握进度,对风险有一个心理预估。
    ①对于一个技术型公司而言,总有有几个大牛,善于解决各种疑难杂症。
    ②寻求他们的帮助,注意是请求,而不是命令,更不是通过组织级行政的压力施加,迫使他们就范来帮你解决问题,那样完全不可能。
  • 前两步不一定能解决,因为毕竟他对环境的熟悉程度不如你,这个时候还得靠你自己。
    ①对于开源技术,找到你所使用版本的源码,翻源码,看他们的单元测试是如何模拟你需要解决的异常的。通常而言,成熟的开源技术,文档和源码组织的非常规范,你很容易找到你发生问题的那段代码。
    ②如果你自己的写的框架或者其他的代码。出现了bug,相信你自己能搞定,搞不定的,说明对此技术或者方案的了解程度还不够,要解决,同第二步。
  • 前三步均失败,放大招儿,请联系相关开发者,无论是商业级产品,还是开源的,想办法联系上他们,通常电邮是最靠谱的,前提是你得学会如何给他们写一封得体的咨询或者请求提供援助的邮件。
    ①以我的经历来看,我没有碰到过一次被拒绝的,都有反馈,那些优秀的开源软件的开发方,或者说其中的开发者,非常礼貌,并且也非常乐意帮助我解决问题,因为他们认为,我反馈的问题也是他们进行改进的难得机会。
    ②我通过此种办法解决过一些异常棘手的问题。
  • 如果前4步均失败,相比你的pm这个时候早已经介入进来了,想必技术部的各路大牛或在自愿的情况下,或在组织协调的情况下,已经共同介入到你碰到的问题上来。这个时候,问题你已经不用担心了,因为这已经不是你的问题了,你要做的是尽可能的描述清楚你的问题,不要误导,不要妄言,也不要猜测。让公司的力量去解决吧,没你事儿了。
    ①如果是个人项目而且是你自己的写的框架或者其他的代码,给自己时间先忘记bug是必须的。睡觉前,上厕所,喝咖啡的时候,再偶尔想一想,说不定就有解决方案了。实在不行,就放弃吧。把这个bug加入自己的待解决问题列表,等以后强大了,再回来fix他吧
  • 通常第五步失败,技术方案会被重新评估,有可能会换方案等。到这一步,与你无关了。

解决完bug或开发完需求后笔记整理流程:

  • 不知道你有过这样的经历没有:
    ①感觉知识学了忘。
    ②感觉之前看到一篇解决这个问题的文章,但是想不起来了,百度谷歌也找不到了,很烦。
    ③之前明明遇到过这个问题,解决了,但是忘记了,又要从头开始研究。
    ④天天看到好多博客,大佬好厉害,但是吸收不进去啊。
  • 整理笔记分类:
    ①共享笔记:需要和团队或者用户共享的文档或者信息,以及开发进度等等。
    ②资料库:和开发有关的文件,比如图片图标等。
    ③手册式学习文档或代码片段:比如Linux的一些操作,不用都存在脑子里,需要做什么的时候,找找笔记,复制粘贴就解决了。不用每次都去Google,而且还能记下你自己之前使用的感想和经验,帮助你消化和解决问题。常用的方法函数都放进来。记得多配一些关键字来帮助将来的搜索查找。
    开发学习笔记:学习了某个技术或者某个项目后整理的技术文档或项目解决方案文档,学习完后才去编写整理。技术文档在初次开发时非常有用,解决方案文档在二次开发时非常有用。
    Bug解决流程文档:解决完Bug后整理解决Bug的所有流程,重点记录解决思路。
  • 开发学习笔记和Bug解决流程文档注意事项:
    ①这两类文档不一定要在解决过程中写文档,遇到问题解决了或想到一些Idea可以先找个地方记录起来,等到有空去深究的时候再整合理解归纳,整理成一篇有质量的技术文章,然后再发布到博客里。拒绝做流水账笔记/文章
    ②因为只有当一个东西结合编程实际,理解了就记牢了。
  • 关于开发学习笔记和Bug解决流程文档:
    ①好的文章剪辑下来,添加标注和重点,不要删内容。
    ②听课笔记保留,不要觉得会了就删掉了。
    ③觉得学会了,就写个浓缩笔记,记录关键点,想不起来再去看好文章或者听课笔记。
    ④bug和代码,都保留最完整的现场、分析、解决方案、查询的文章。
    ⑤标签不要打太杂,标记种类,标题可以详细点。
  • 总结:解决问题还是要根治,不能囫囵吞枣,否则相同的问题可能会以不同的形式出现,每次都得花时间去搞。刨根问底,一步到位,再遇到类似问题就可以分分钟解决了。因此平时遇到问题,如果项目不是很赶的话,可以留意多想想,多追究一下原因,说不定你会有很多意外的收获

读懂源码思路技巧:

  • 找到地图:拿到项目代码后,提前梳理整个项目结构,知晓整个项目的模块划分,以及对应的代码文件。
  • 前置知识准备:提前掌握项目中用到的前置知识,比如数据结构、操作系统原理、网络协议、网络 IO 模型、编程语言语法等等。
  • 从基础模块开始读:从最底层的基础模块开始入手,先掌握了这些模块,之后基于它们构建的模块读起来会更加高效。
  • 找到核心主线:找到整个项目中最核心的主线逻辑,以此为目标,了解各模块为了完成这个功能,是如何协作和组织的。
  • 先整体后细节:对于复杂函数,不要上来就陷入细节,前期阅读只需了解这个函数大致做了什么事情,建立框架,等搭建起框架之后,再去填充细节。
  • 先主线后支线:整个主线逻辑清晰之后,再去延伸阅读支线逻辑,因为支线逻辑肯定是服务主线逻辑的,读完主线后再去读这些支线,也会变得更简单。
  • 查漏补缺:在工作中遇到具体问题,带着这些实际的问题出发再次去读源码,进行查漏补缺,填补之前读源码时没有注意到的地方。
  • 链接:读懂Redis源码,我总结了这7大心得

构建Java知识体系思路: