引言
学习的思路是从单体项目(BBS)到微服务分布式项目(shortlink)到自制底层框架项目(RPC),选取的都是 Github 开源项目,有很完善的代码和文档可供学习。未来可能会参与一下知名的开源项目,我想这是如果暂时没有机会进入大厂工作很好的学习方式,当然我觉得选取学习的开源项目的原则是文档比较全面,难度适中符合自己目前的水平慢慢的再增加难度。自己也曾经去过企业实习实践过一段时间,不同的企业的开发方式确实不是很一样,大厂的开发流程和培养方案还是更加的完善科学一些,去不规范的公司其实反而会学到一堆坏毛病。而且最好还是去核心的岗位,能积累下的东西也更多,天花板更高一些。
这些练习项目其实最大的问题是没有那么代码没有那么规范,远远达不到工业级的标准,很多的技术工具使用的也莫名其妙,什么时候该使用消息队列中间件,什么时候该使用搜索引擎中间件说的很少,大多在讲 how,很少说 why,而没有理解的东西其实很难接受的,我想这大概是应试教育下的一种风气,计算机科学基础变成应试题,项目变成操作手册,如果没有很好的耐心的话真的很难坚持下去。
另外我觉得复盘输出是一个很好的习惯,可以很好的整合自己在之前遇到的问题和积累的知识,最后的体会会更加的可感具体而深刻。而且一定是要自己加工过的有思考的东西,经过逻辑处理之后很多 why 的东西也就能够明白了。平时要养成写文档,写博客的习惯。
做项目其实在训练自己的干活的能力,雇主雇佣了你的技能付你工资你替他解决问题,技能越是在市场上稀缺在企业中的杠杆越大相应的薪酬也会更高,这也就是为什么大厂的 P5 可以动辄几十万年薪,P7 以上甚至百万年薪的原因。至于为什么国内的会出现 35 岁危机这个现象,我在上大学的时候听到这个传闻还是感觉不可思议的,因为我觉得三十出头正是青年的时候,有精力有经验,看过一些行业内牛人的分析经历了一些事情之后大概有一些理解了,工作十年其实一直是在重复工作一年的内容, IT 软件行业的技术工具的半衰期大概是五年,十年过后如果没有长进的话,所拥有的技术其实已经贬值了许多,平时如果不注意锻炼身体素质可能也会下降许多,加上家庭的元素,对于只追逐短期价值的企业来说性价比已经不高了,工资要求高的多,工作能力也不一定更高。中国人的文化中还是以年长者为尊,管理者不太希望手下的人比自己大很多可能不服管?当然这只是自己的一个猜测,对于大多数企业来说要么需要这个年龄的专家,要么就招一些性价比更高的年轻人。在这样的一个大环境下个人又该如何面对呢?对于软件工程师来说,最核心的技能又到底是什么,是技术工具的源码吗?是高可用高并发的技术方案吗?吴军博士(Google 高级研究员,腾讯副总裁,投资人)在《计算之魂》这本书中提到对于工程师级别的分类:
第五级:能独立解决问题,完成工程工作;
第四级:能指导和带领其他人,一同完成更有影响力的工作;
第三级:能独立设计和实现产品,并在市场上获得成功。
我只列出三级是因为我觉得这是大多数人可能能够达到的。对于软件工程师来说,软件构建能力肯定是最基本的,无论采用什么语言,能不能很好的在不同的场景下应用最佳实践的技术方案去解决问题,使用不同的数据结构与算法在海量数据场景下的复杂度是天差地别,对计算机很好的理解能够知道计算的瓶颈在哪里,科学的软件工程能够很好的保证代码质量,项目进度。最后甚至需要懂管理,市场,产品,运营,销售的知识才能独立设计和带领团队实现产品,在市场上获得成功。而计算机与程序员的角色在其中,我始终觉得只是一个工具而已,不要自己只是成为一个工具,而是要有主人翁的态度。
BBS
很好的练习了一遍后端业务开发的流程和 Spring 生态下对 Spring Boot,Spring MVC,Spring Security,Spring Actuator,Thymeleaf,MyBatis,MySQL,Redis,Kafka,Elasticsearch等框架和中间件的整合使用,调用第三方接口缓存用户头像图片,前端也可以熟悉 H5,CSS,JS 的使用,涉及到 Ajax 异步请求调用,前端框架的使用。
-
项目介绍:项目基于牛客网,使用Spring Boot、Mybatis、MySQL、Redis、Kafka等技术。实现了用户注册、登录、发帖、点赞、系统通知、帖子热度排序、搜索等功能。引入Redis提升性能,Kafka用于系统通知,定时任务计算帖子分数。
-
注册:注册流程包括数据验证、账号邮箱重复性检查、密码加盐MD5加密。
-
登录状态管理:登录状态保存在用户凭证表,使用ticket和cookie管理登录状态。
-
拦截器和线程安全:拦截器用于控制未登录用户访问特定页面。ThreadLocal用于线程安全,保证线程内资源共享。
-
AOP(面向切面编程):用于统一处理日志,减少代码重复。
-
Redis Key设计:使用工具类生成Redis Key,采用冒号分隔单词的方式。
-
Redis基础使用:缓存点赞和关注数据,优化登录流程,存储验证码和登录凭证。
-
点赞数缓存实现:使用Redis的set类型存储点赞数据,事务操作确保数据一致性。
-
网站UA和DAU统计:使用Redis的HyperLogLog和Bitmap数据结构进行统计。
-
缓存与数据库一致性:通过先删数据库再删缓存策略解决数据不一致问题。
-
Kafka消息模型:Kafka作为分布式流平台,具有高吞吐量、持久化、可靠性和拓展性。项目中用于处理点赞、评论、关注的通知。
-
ElasticSearch:用于帖子搜索,提供全文检索和数据分析功能。
-
Trie Tree前缀树:用于敏感词过滤,提高内容合法性和健康性。
shortlink
很好的一个前后端分离的微服务练习项目,由阿里巴巴一位 P7 工程师开源的项目,项目的结构比较规范,练习了一遍后端代码 API 接口的设计,对 Spring Cloud Alibaba 技术栈的使用,相比于 BBS 项目,又增加练习了在海量数据场景下,进行数据库的分库分表,和恰到好处的使用数据结构(布隆过滤器),搭配缓存可以有效的减少数据库的压力,提高用户的访问速度。
RPC
RPC 框架的目的是为了让客户端调用远程的服务想调本地服务一样简单,它隐藏了底层的网络通信和数据序列化等细节,使开发人员能够专注于业务逻辑的实现。框架确实要比业务项目的代码难度要高得多,涉及到很多的设计模式(单例,工厂,代理),动态代理,反射,SPI 机制等高级的内容,对于网络部分使用 Netty 实现高性能的网络传输,Zookeeper 实现注册中心,一致性哈希算法实现负载均衡。这些东西之前看介绍的时候云里雾里,真正使用代码跑一下实现功能之后就知道到底是怎么一回事。我会详细介绍一下代码的实现逻辑,业务逻辑其实就是上面的图面。
服务端
首先服务端需要使用 Zookeeper 客户端工具 Curator 注册一些服务供远程调用,使用一致性哈希算法注册服务可以让在改变服务节点时对原来服务的影响达到最小。使用 Netty 实现网络传输。这里自定义了编解码的数据包结构,并在 Netty 管道中添加自定义的编码器和解码器,以保证数据按照定义的格式进行编解码。
数据包的详细结构:
- 魔数 : 通常是 4 个字节。这个魔数主要是为了筛选来到服务端的数据包,有了这个魔数之后,服务端首先取出前面四个字节进行比对,能够在第一时间识别出这个数据包并非是遵循自定义协议的,也就是无效数据包,为了安全考虑可以直接关闭连接以节省资源。
- 序列化器编号 :标识序列化的方式,比如是使用 Java 自带的序列化,还是 JSON,Kyro 等序列化方式。
- 消息体长度 : 运行时计算出来。
- 压缩类型。
- .....
客户端
用户通过动态代理模式屏蔽掉连接 Zookeeper 获得服务端地址和 Netty 网络传输的细节。
设计模式的使用
单例和工厂模式
对于一些需要保证全局唯一性的对象,可以通过单例工厂模式来管理。例如:
- SingletonFactory:使用单例工厂模式管理单例对象,确保全局只有一个实例,防止重复创建。
this.unprocessedRequests = SingletonFactory.getInstance(UnprocessedRequests.class);
this.channelProvider = SingletonFactory.getInstance(ChannelProvider.class);
使用 SingletonFactory
来管理 UnprocessedRequests
和 ChannelProvider
的实例,确保在整个应用程序中,这些类只有一个实例。这不仅可以减少内存消耗,还可以避免多实例带来的资源竞争问题。
通过使用工厂模式,可以实现更好的可扩展性和灵活性。例如:
- SPI(Service Provider Interface)机制:通过
ExtensionLoader
实现 SPI 机制,可以根据配置动态加载不同的实现类,而不需要修改代码。这种方式特别适合需要支持多种实现的情况。
this.serviceDiscovery = ExtensionLoader.getExtensionLoader(ServiceDiscovery.class).getExtension(ServiceDiscoveryEnum.ZK.getName());
-
通过
ExtensionLoader
,可以在运行时动态选择和加载ServiceDiscovery
的具体实现,例如基于 Zookeeper 的实现。这使得代码具有很好的灵活性,可以根据需要扩展新的实现,而不需要修改现有代码。
代理模式
RpcClientProxy
是一个动态代理类,用于在客户端调用远程方法时,屏蔽中间的网络通信过程,使得远程方法调用看起来像是本地方法调用。通过动态代理,当代理对象调用方法时,实际会调用 RpcClientProxy
的 invoke
方法,完成远程方法调用的整个流程。
总结
珍爱生命,多学习一些优质的工业级开源项目~