面向读者,对运筹和OptaPlanner都不了解;
以一个NP-Hard的例子引出运筹优化的概念;
继续讲解决这类NP-Hard的常用思路便是启发式算法;
从而引出开源求解器OptaPlanner;
解决了什么问题;
不能解决什么问题;
以护士排班为例贯穿整体框架源码分析;
再以路径规划为例展示框架的应用
总结+后续会有一系列更细节的介绍及实践案例;
组内要求每人都要进行分享,故准备一个组内的小分享,因为实际工作中使用过Tomcat和Jetty作为Servlet容器,所以先来熟悉下Tomcat,当然现在使用Jetty更多;
通过了解一次HTTP请求在Tomcat中的流转来认识Tomcat内的重要组件及设计;
假设时间回到20年前,你是Sun Microsystems的一名开发人员,产品经理James Duncan Davidson给你提了一个需求:
为了推广Sun的Servlet规范,让广大程序员喜欢用Servlet开发应用、降低程序员的开发成本,需要提供一个开源的Servlet容器;
先写一个Demo,看实现一个WebServer+Servlet容器要做哪些最基本的事情,如下图:
简化下Demo版的逻辑:
在Tomcat中也存在这5步,只不过作为一个可广泛应用的Server,Tomcat考虑的逻辑更多更周全。
先把变和不变的逻辑拆分开来,哪些是易变的逻辑、哪些是不易变的逻辑?
变:第[1、2、3、5]步骤是不易变的,基本上每一个服务器都要做的事情;
不变:第[4]步是每个业务都不一样的,如下图:
先把第[4]步放一放、因为这是应用层的事情,对[1、2、3、5]步骤再进行拆分:
从线程的角度来看,目前只有一个应用启动线程(main线程)在处理监听端口-解析http请求的事情,这样当请求多的话、main线程的处理效率成为瓶颈,所以我们将监听端口和请求处理分开,如下图:
一共三类线程,main线程、监听线程、工作线程池,处理模型是这样的:
上图的Controller其实就对应Servlet,如下图:
以HTTP请求流转的视角将上图简化一下,如下图:
Tomcat对于Servlet支持层级的抽象、分为了4层:
将上图换一种方式描述,如下:
当Engine从上游收到请求,Engine先判断要交给哪个Host—>Host再判断要交给哪个Context—>Context再判断要交给哪个Servlet处理;
打个比方,XX组织要求公司的支持IPV6标准,找到公司CEO:
既然每一层(Engine–>Host–>Context–>ServletWrapper)将请求往下分派时都会对请求做一部分加工处理,那么Tomcat是如何处理这种场景呢?没错,就是责任链模式,如图:
Pipeline是责任链、责任链中可以配置多个过滤器-Vavle,不过Tomcat的责任链做了一点小改造,每个责任链中至少要配置一个默认的过滤器,这个过滤器承担的职责是将请求传递到下一层级的Pipeline中,请求流是这样流转的:
这4层每一层都有一个责任链,然后4个责任链串到一起来将请求传递到Servlet,如图:
当最后的WrapperVavle拿到请求后,要把请求传递给Servlet,但是我们都知道,Servlet规范是支持在请求到达Servlet之前也配置过滤器(Filter)的,其实是就Sun在制定Servlet规范时,在应用层给了广大程序员提供可扩展手段,要求Servlet容器再实现一个应用层的责任链(过滤器链),公司内部的一些SSO、权限校验等都会用到Filter实现,如下图:
我们将前边提到的这些概念整合一下、如图:
上图基本描述了整个请求从进入Tomcat到返回结果流经的路线;
但是这样还不够,我们需要将上图这些组件进行组装、抽像,将左侧方框中Acceptor、Queue、ThreadPool、Handler这些组装起来,叫做Connector(连接器:连接浏览器来的请求);
然后右侧方框中这些抽像起来叫做Container(容器),Wrapper是小容器、Context是中容器、Host大容器、Engine是特大容器,如图:
如果我们再抽像一层,将Connetor和Container组装到一起呢,是不是就可以组成一个服务(Service),如图:
如果多个Service组合起来再抽像一层,可以称为一个服务器(Server),如图:
把上图和Tomcat的配置文件放一起对比下,上边提到过的概念基本上在配置文件中都有对应的标签:
Server
--Service
--Connector
--Engine
--Host
--Context
--Context
--Host
--Service
下边我们通过Debug一个HTTP请求,看下请求过程中的组件(上边提到的Sever、Service、Connector、Container等)及调用堆栈,
先来看下Server,如图:
上图显示:Server对象中可以有多个Service、Service对象中可以有多个Connector+一个Engine;
再看Engine,如图:
上图显示:Engine对象中可以有多个Host,Engine用一个Map来存储多个Host,图中示例仅一个名叫localhost的Host;
再看Host,如图:
上图显示:Host对象中可以有多个Context(应用),Host也用一个Map来存储多个Context,图中示例有5个Context,就是Tomcat本身自带几个应用;
再看Context,如图:
上图显示:Context对象中可以有多个Wrapper(Servlet),Context也用一个Map来存储多个Wrapper,图中示例有19个Wrapper,就是Tomcat自带example应用下的demo,Map的key就是web.xml中配置的Servlet名称,value是具体Tomcat生成的Wrapper实例;
再来看下请求的调用堆栈,如图:
在堆栈中我们见到一个叫ErrorReportVavle的过滤器,是Tomcat在Host这一层中内置的,它的作用是干啥呢?还记得Tomcat著名的404错误页面么?没错:
这个就是当请求的url不存在时,tomcat返回的默认404界面,来看下它内部的主要实现:
其实就是用代码拼出一个404 Not Found的HTML界面,当然我们可以自己去实现一个替换掉默认的。
让我们再来回顾一遍整个Tomcat处理HTTP请求的过程:
回过头来想一想,为何Tomcat把自己内部搞的这么复杂,是为了达到什么目标?像Jetty那样简洁一些不好么?
作为一个带有Sun官方基因的开源容器,面对的使用场景是各种各样的,不同的场景需要有不同的诉求,所以说开源项目要考虑的一个重要因素便是扩展可替换,大到扩展核心组件、小到扩展某一个功能点如日志打印,所以进行这么多抽像封装、设计,比如我们可以切换不同的IO实现、可以替换整个Connector;(仅个人观点)
后续大家有啥想了解的,我们可以进一步探讨,如:
还记得文章开头给大家提需求的产品经理么,在Sun Microsystems任职了4年(1997-2001)的James Duncan Davidson,没错,他当年是Sun的一个工程师(一年后title由技术专员晋升为资深工程师),开发了Tomcat、后来Sun将Tomcat贡献给Apache。
在今年(2021-02-28)James Duncan Davidson从Microsoft离职去创业了,去Microsoft之前James Duncan Davidson在奇妙清单工作,2015年6月奇妙清单被Microsoft收购,这之后James Duncan Davidson在Microsoft工作了五年半。
James Duncan Davidson从2005年开始喜欢上摄影,摄影已经成为他的业余兴趣爱好,此处摘一张照片:
近段时间做了些招聘面试工作,作下思考及总结;
招聘难,这是第一感觉,看下最近的招聘数据:筛选了上千份简历,约面的有三十多人,面试通过的2人;与招人难映衬相的是:目前市场上又很多人在投简历找工作,如何进行招聘、提升招聘效率、提升招聘效果,这是用人部门不得不思考的事情;
先看下招聘的过程:
有什么事、需要什么样的人;
首先,对团队资源进行盘点:现在有什么事、有什么人,这些人是否满足现在及后续规划的事情,现有的人当中哪些存在风险、哪些可以培养;
其次,想清楚为什么要招人,什么原因招人,比如业务要做大、原有人员离职、保持团队适当换血增加活力等等;
再次,想清楚需要什么样的人,不一定招最优秀的、找相对合适的;
明确了为什么要招人,就可以写Job了,描述清楚:
可以用SWOT法分析下自己这个岗位:
Job写的好不好取决于上一阶段是否思考清楚招人原因、招人后要做什么事;
明确了有什么事、要招什么样的人,筛选简历其实就是个体力活,比如有互联网工作经验、所用到的技术栈是否匹配、所做的项目经历是否匹配;
简历渠道:公司内部的简历库、外部招聘网站、同事推荐、个人博客、github等各种网站;
优秀的人很少投简历,工作会自然找过来,比如通过猎头、朋友推荐等等,如果你要找很优秀的人,那么在公共的招聘网站可能很难找到合适人选;
面试的过程,是要让面试官尽可能全面的了解候选人真实水平,要看到候选人的优势和不足,然后做权衡;
挖掘候选人的能力图谱,比如专业技能、项目经历、沟通协作、管理能力、主动性、潜力、学习能力等等,看候选人是否与我们岗位要求是否匹配;
STAR行为面试法是一个不错的技巧,”过去的行为是未来行为的良好预测指标”,不同的行为特征能够将绩效优异者和绩效平平者区分开来,经常做的行为很容易回忆起来,如果一个人具备某项能力,那么他会比较容易回忆出和此能力相关的过往事例,所以在面试过程中,需要对候选人过去的行为进行深入了解,从而预测候选人能否适合新的工作岗位;
通过STAR行为面试法不断的追问,更全面的了解候选人,比如
实践–>反思–>总结–>再实践
当面试一段时间后,发现效果不好,需要进行反思总结,做出改变,比如面试通过率过低,需要思考是自己筛选简历过于轻率还是自己要求过高?
玩王者荣耀的时候,怎么样玩胜率才能更大?
其实当开局英雄匹配完,大概率本局胜负已分,只是每个人心里还不知道结果,操作、配合、意识这些都是长期练出来的,双方谁犯错误少、谁能不断扩大优势,谁就能赢;
知已知彼;为什么走上路、为什么要先手、为什么要后手,打得过就打、打不过就跑;自己放了一技能之后怎么办?对方有什么技能?
兵无常势、水无常形,套路都是有一定背景的,所有的策略都是依据不断发生的情况而定的;
守正出奇,打团是站位很重要,有正有奇;
意识;另外两路没人,那么就猥琐点,别出塔;
定位:要给自己一个初步的定位,是打野、发育、切后排等;依据双方所选英雄;
开局五分钟,就要知道这局该怎么打,自己团队水平如何、对方团队水平如何;
团战如何打,先手还是后手;
装备如何出,攻击型、防御型;
英雄的优势、与哪种装备配合更好,对方的优势、对方的不足;
团结:已方再菜,也要团结一切可团结的力量,逆风局时尤其重要,不要嘲讽开骂队友,多鼓励;
如何胜利,干掉对方的大本营;
怎么干掉对方的大本营,拿人头、推塔;
如何拿人头、推塔,打钱、发育、买装备;
如何扩大优势干掉对方?
随着时间不断的扩展已方优势,越来越稳;
每分钟大家打钱机会是一样的(兵线、野怪、人头),所以时刻记得打钱,怎么单位时间内打钱最多就怎么打;
自己打完的局要复盘下,为何失败、为何成功;自己失误的点在哪里、自己成功的点在哪里?搞清楚为什么输为什么赢.
学习其他人是怎么玩的,如何出装备、如何出招,学习别人经验;
知行合一最难,往往在实践过程中随意发挥,忘记了原则;
前段时间买了一辆小牛电动车,看着店老板安装电动车配件忙碌的样子,想到自己工作时忙碌的样子也差不多,每个社会角色在不同时刻都有自己的所谓的“正事”要做。
学生时代,正事便是努力学习、形成自己的学习方式;
步入社会,工作赚钱,形成自己的处事方式;
大家都上一个小学,为什么长大之后各自的人生路径不同;每个时间段做合适的事;社会的运行规则是什么样的?是什么导致了每人的路径不同?什么导致了社会上大家的分工各不相同?
有人玩、有人学,中考刷掉个别人;无家底无背景的去打工;
有人玩、有人学,高考刷掉一批人;无家底无背景的去打工;
有考的好、考的差,有趋势专业有夕阳专业,毕业后人生轨迹各异;
先玩和后玩的区别,有人周末在逛商场、有人周末在商场卖衣服、卖餐饮;
有人毕业便履历光鲜、名校名企创业一路直上,有人最底层摸爬滚打多年劳累无果;在人生的几个关键节点走对了,便走上了正常的轨道,即使是轨道上跑的最差的,也比下一层轨道要快;
有哪些关键节点,高考、择业;
世界的运行趋势、国家的运行趋势、一个行业的运行趋势、一个公司的运行趋势、一个部门的运行趋势、个人的选择和努力;
三年一小段、五年一大段;每段有每段的事、有每段的责任;
前三年做的事决定了后三年的走向;
教育阶段、工作阶段;社会是终身的课堂,行动、复盘、反思、进步;
多数人都是需要约束的,上学时靠学校去约束、毕业后靠工作、生活去约束,如若无忧无虑都会好吃懒做;
终身学习,求知好奇;
提高自身视野、竞争力、知识;
总结下作为Java后端开发人员需要掌握的技术栈;
对于一项技术要知道:是什么、解决了什么、怎么实现;
基本语法
面向对象
Java线程:Thread、线程池、ForkJoin、AQS、ThreadLocal、线程安全、共享与可见性、原子操作、同步与异步、锁
Java集合:Set、List、Queue、Map
JavaIO:BIO、NIO、AIO
Java反射
JavaWeb:Servlet、HttpClient
JDBC:用JDBC可以写出读写数据的代码、事务、回滚、Statement、PreparedStatement
JVM:类加载机制、内存模型、垃圾收集算法、垃圾回收器、jmap、jstack、jstat
JavaAgent:agent机制、instrument机制
Java字节码
线性结构
树形结构
排序算法
搜索算法
SpringMVC
Spring
SpringBoot
SpringCloud
Mybatis
工具包:Netty、Log4J、Junit
MySQL:SQL优化、B+树、分库分表、索引设计
Hadoop:Hdfs、Yarn、HBase、MapReduce
ES
常用Linux命令:cat grep awk sed top netstat tail
Shell
Python
监控报警
面向对象设计基本原则
设计模式
领域驱动
SOA
微服务
常见的架构设计:主从架构
TCP/IP
Reactor
Proactor
HTTP:http、https协议
CAP
常见的设计取舍
RPC框架
MQ
缓存
数据库
ZK
IDEA
Maven
Git
HTML
JS
CSS
https://zhuanlan.zhihu.com/p/57141372
https://www.processon.com/diagrams
业务系统缺少压测
两个字:预防
一句话:预防胜于治疗
为应对流量高峰,通过压测提前发现系统短板,提前预防故障;
其实这是个资源成本问题;做个假设:如果预防要花费10w、而出了故障也就损失1w,那么没必要做预防、没必要压测;
只能能解决部分问题,常见的比如:机器资源不足提前扩容、线程池不足提前扩容等;扩容类的问题;
减少故障而不能避免故障;
不能解决所有问题,常见的比如:外部依赖挂掉该出故障还是要出故障;
其实这么拆分是以组织结构为维度来说的,一般以一个组织结构/小组为单位,比如促销业务模块会拆分成多个小应用,如红包应用、返减应用等,而多个业务模块串起来就是整个链路,比如api模块、产品模块、商家模块、订单模块、促销模块、支付模块等串起来实现一个完整的业务链路;
此处的单应用指一个独立部署运行的服务,比如订单查询服务;
对单应用的压测,通常针对某些接口进行,可将外部依赖mock掉,只压系统内部的问题;
此处多应用指多个单应用之间有调用关系,通常一个小组内部负责的服务,比如订单模块(假如服务是这么拆分的,这个跟组织结构和拆分粒度有关):
整个链路,比如Api—>下单—>支付—>退款整个业务链路,通常需要多个团队一起配置;
一般读压测相对简单一些,准备好数据、将需要的依赖mock掉就可以开始,对于写压测会麻烦些,主要是会造成写压测数据、压测后还要清理压测数据,对于一些外部依赖还要做写mock,如支付接口、回调接口;说下我在做主流程写压测过程中遇到的一些问题及解决方法;
压测流程一般为:
业务场景大概如下:上游RPC推数据到服务A—>服务A发MQ—>服务B监听MQ进行处理(内部有N多外部依赖及写数据操作)—>服务B再调用服务C外部请求(C内部也有外部依赖及写数据操作),如下图:
做链路压测的一个核心关键点是在整个链路中要标识出压测流量,这样我们才能针对压测数据做特殊处理,如何标识出压测流量,需要我们对压测数据做标识,原理类似分布式追踪;这需要公司中间件的支持,比如MQ、Cache、数据库中间件、压测平台、RPC组件等等;
创建影子表,当压测流量来的时候数据全部写入影子表,将压测数据和真实数据分离;
创建影子Cache,当压测流量来的时候数据全部写入影子Cache;
MQ支持开关配置,开关控制是否处理MQ,如果处理则按正常MQ转发,如不处理则忽略掉,开关支持Topic和消费组粒度;
现在微服务横行,一个应用没几个外部依赖都不好意思出门跟人家打招呼,对于外部依赖,如有需要则要Mock掉,比如我们录制了线上流量开始压测,但是压测的时候我们要查询订单状态(外部依赖),此时订单状态已经变更,而我们业务逻辑需要满足一定状态的订单才处理,为了让流程正常走下去,我们需要将订单状态Mock掉,此时有多种Mock方式,Mock外部依赖或本地方法皆可;
压测整个链路的关键在压测标识,所以压测标识不能丢,整个链路上除了一些中间件要支持压测标识透传,我们应用中也不能丢,特别是对于应用内部有异步线程处理的时候,父线程需要将压测标识传递下去;
记录平时工作中的一些VIM用法;
在进行写压测的时候,需要生成影子表;将建表语句导出一份后、将表名批量生成影子表,如原表名叫my_table,对应的影子表叫_shadow_my_table_,如下:
%s/CREATE TABLE
\(.*\)
/CREATE TABLE_shadow_\1_
/g
:g/pattern/d
好习惯终生受用。
要不断学习、进步;
学习效果=时间X方法
做有创造力的事情、创造力越大越好;越机械的工作越容易被取代;
做完一件事、每日、每周、每月、每季、每年、三年、五年、十年做总结,可以不止一次总结;
勤于思考;多问为什么;
只想不练假把式,快速动手、验证想法;