技术面:
面试官您好,我叫XXX,来自东北大学,专业是软件工程,目前是一名研二的学生。在大学期间学习了Java以及Spring、MyBatis等框架。在近期参与了两个项目的开发,第一个项目是一个在线教育平台系统,该系统由实验室三个人共同开发,我主要参与后端开发任务,使用SpringCloud、MyBatis、Redis等相关技术,主要负责课程内容模块和订单模块的设计与开发。第二个项目是一个基于Netty的轻量级RPC框架,自定义通信协议,利用Zookeeper作为服务发现中心,实现多种动态代理、负载均衡和序列化算法,并集成了SpringBoot框架。学习之外也会通过跑步或打羽毛球来放松自己。我的自我介绍完毕,谢谢面试官。
面试官您好,我叫XXX,来自东北大学,专业是软件工程,在校期间主要学习了Java以及Spring、MyBatis等框架,参与了两个项目的开发,第一个项目是一个在线教育平台的系统,该系统由我和实验室其他两名组员共同完成,我主要负责后端代码的编写,使用SpringCloud、MyBatis和Redis等技术,负责课程内容管理模块和订单服务管理模块的开发和设计;第二个项目是基于Netty的RPC框架,使用zk作为服务的注册中心,实现了两种动态代理方式,五种序列化算法以及三种负载均衡算法,并继承了SpringBoot框架,实现了基于注解提供服务的注册消费,同时实现了自动配置功能。
跨区调用实现过程:
1、在配置中心leo中配置需要跨区掉用的部署区,例如:cn, us, eu
2、定义一个 @DubboRefrenceGroup 注解,这个注解配置项主要有 name-beanName 和 groups-部署区组,然后定义一个 RefrenceHolder 泛型类,其中 T 为 dubbo 的服务类,然后自定义一个实现 BeanPostProcessor 的增强配置类,其中在 after 方法中的内容为: 扫面 @DubboRefrenceGroup 注解,然后创建一个 beanName 为 name 的 Holder 类,然后再遍历 groups 数组,以 group 为 key,创建一个 T 类型的远程过程调用的代理对象,这里将对应参数都设置为默认属性,如果没有配置的话,然后通过内部的 rpc 框架开发的 remoteCall 接口,更改调用服务所在的部署区,再将对应的调用参数传入,返回值为 Object 类型的结果,这样就得到了一个 Map<group, T> 的对象,从而达到根据不同的 dr 调用不同 dr 区的服务了。
优化点:可以把这个功能写入到我们组的一个通用服务类里面,然后实现自动配置功能,后续直接使用对应的注解加载便可以实现开箱即用的功能。
1、首先,目前的应用大部分都是分布式/微服务架构,通常各个模块之间都是通过RPC来进行调用的,所以我认为自己写一个RPC项目可以有更加深入的理解RPC的一个原理;
2、其次的话,在写RPC项目的过程中,学会了对Zookeeper、Netty的使用,包括对动态代理、负载均衡、序列化算法的实现,以及对SpringBoot框架的扩展和自动配置的理解。
在线教育平台系统主要采用的是B2B2C的业务模式,主要包括认证授权、内容管理、媒体资源管理、课程搜索、订单支付管理、选课管理等六大模块,然后我主要负责的是课程内容管理模块和订单支付模块的设计与实现。
RPC框架中的几种容灾措施,包括超时设置、重试、Client端屏蔽策略、Server端反馈机制、限流与服务降级,最后谈到分布式服务的雪崩。雪崩的处理涉及柔性,对用户体验有损,需要人为决策参与实施。
client ----> server
client:失败重试 + 服务降级/mock(默认值 或 MockService类-在其他服务) + 服务熔断(统计失败次数,达到阈值后启动熔断,恢复时使用定时任务监控,先放行几个请求,当检测到恢复后则取消熔断)
服务降级:通常情况下可以手动配置mock属性,主动针对某些非核心的服务进行服务降级策略,减轻服务端压力,从而去响应核心服务,或者自动降级,为每个服务新增一个实例状态,根据失败阈值统计触发服务降级。
服务熔断:通常是自动触发的,当出现一个服务调用他所依赖的下游服务,依赖服务出现故障从而导致该服务响应异常导致整个系统发生雪崩效应,这个时候应该自动触发熔断机制,并且可以对熔断的服务进行降级,返回fallback默认值。熔断的三个状态:open、close、half open。
rpc框架可以集成服务熔断插件,例如 Hystrix,在客户端处引入,监控服务调用状态,从而去实现自动熔断恢复功能。
异步调用:
- 直接返回 Future 对象,由用户自身调用;
- 为每个 channel future 注册一个回调机制,将回调事件通过线程池去异步处理,根据全局的消息id去获取到请求参数来执行后续逻辑。
通信协议版本协商:
- 客户端服务端先进行通信协议版本协商,双方一致后再使用协商的版本进行通信。
当RPC框架出现服务雪崩时,可以采取以下措施来进行处理:
服务降级:在服务雪崩期间,可以暂时关闭一些非核心的服务或功能,以减轻系统负载。例如,可以降低某些服务的并发连接数、限制资源使用或关闭一些不太重要的功能。
限流措施:通过设置请求的速率限制或并发连接数限制,可以有效控制系统的负载并防止雪崩效应的扩大。可以使用令牌桶算法或漏桶算法等来实现请求的限流。
熔断机制:熔断是一种快速失败和快速恢复的机制,当依赖的服务出现异常或响应时间过长时,可以立即熔断该服务的调用,并返回一个预设的错误响应。通过熔断机制可以避免请求堆积和传递故障,保护整个系统免受雪崩效应的影响。
超时设置:合理设置每个请求的超时时间,避免因为单个请求的响应时间过长而拖慢整个系统的响应速度。可以根据服务的特性和性能指标,设置合理的超时时间,并在超时后及时取消请求。
异步处理:对于一些非必要的同步操作,可以考虑使用异步方式进行处理。将一些计算密集型或耗时较长的任务放入消息队列或线程池中异步执行,减少请求的等待时间,提高系统的吞吐量和响应速度。
优化系统架构:对于系统中的瓶颈或性能瓶颈进行分析和优化。可能需要对数据库、缓存、负载均衡等组件进行优化,以提高系统的整体性能和稳定性。
监控与告警:建立完善的监控系统,及时捕获和发现服务的异常情况,并及时发送告警通知。通过监控系统可以实时监测系统的各项指标,预防服务雪崩问题的发生。
以上是一些常见的处理服务雪崩问题的方法,根据实际情况,可以综合采取多种措施来保障系统的可用性和稳定性。
MinIO 一个轻量级的分布式文件系统,由多个 MinIO 节点连接组成,可根据文件规模进行扩展,适用于海量文件的存储与访问。
MinIO 有什么优势?
1)MinIO 开源,使用简单,功能强大。
2)MinIO 集群采用去中心化共享架构,每个结点是对等关系,提高高可用,通过 Nginx 可对 MinIO 进行负载均衡访问。
3))MinIO 使用纠删码算法,只要不超过一半的节点坏掉整个文件系统就可以使用。
4)如果将坏的节点重新启动,自动恢复没有上传成功的文件。
什么是页面静态化?
页面静态化是指使用模板引擎技术将一个动态网页生成 html 静态页面。
满足下边的条件可以考虑使用静态化:
1、该页面被访问频率高,比如:商品信息展示、专家介绍页面等。
2、页面上的数据变化频率低,比如:商品发布后对商品信息的修改频率低,专家介绍信息修改频率低
静态化的技术很多,Freemarker 是一个成熟的开源的模板引擎工具,简单易用,功能强大。
本项目使用 Freemarker 将课程信息静态化:
1)使用 Freemarker 的标签编写课程信息的模板
2)调用接口获取模板上需要的模型数据
3)调用 Freemarker 的 API 生成静态页面。
4)生成的静态页面最终会上传到文件系统方便访问。
什么是分布式事务?
由多个服务通过网络完成一个事务叫分布式事务。
比如:课程发布操作不仅要在本地数据库插入课程信息,而且还要请求索引服务将课程信息添加到索引库,还要请求 MinIO 将课程静态化并上传静态页面,这里就存在分布式事务。
分布式事务控制的方案有哪些?
首先根据 CAP 原理决定我们的需求,是要实现 CP、还是要实现 AP。
实现 CP 就是要实现强一致性,可以使用 Seata 框架基于 AT、TCC 模式去实现。
我们项目中大部分实现的是 AP,使用本地消息表加任务调度完成分布式事务最终数据一致性。
如何使用本地消息表加任务调度完成分布式事务控制?
以发布课程为例进行说明,发布课程需要在内容管理数据库中写课程发布表记录,同时将课程信息同步到 redis、ES、MinIO,这里存在分布式事务。
1)点击发布课程使用本地事务向发布表写一个课程信息,同时向消息表写一个消息记录(标记了发布了哪门课程);
2)xxl-job 的调度中心使用分片广播模式向执行器下发任务,开始扫描消息表,查询到了待处理的消息;
3)根据消息的内容将课程信息同步到 redis、ES、MinIO;
4)任务完成删除消息表记录。整个分布式事务完成,最终保证了一致性。
如何解决分布式事务回滚的?
当在执行过程中某个环节失败后,会先进行两次重试机制,尽最大努力成功,当重试之后还是失败,则会将对应的消息表的失败记录存储在数据库中的事务失败表当中,然后启动定时任务去进行相应事务的失败处理操作,同时更新对应消息表的一个状态字段,以便后续的分布式任务调度能够继续执行消息表进行分布式操作。或者是人工处理。
本项目使用 Elasticsearch 开发搜索服务,步骤如下:
1)首先创建索引(相当于 mysql 的表),将课程信息添加到索引库,对课程信息进行分词,存储到索引库。
2)开发一个搜索服务,编写课程搜索接口,调用 Elasticsearch 的 rest 接口根据关键字、课程分类等信息进行搜索。
如何保证索引同步?
我们项目是使用本地任务表加 xxl-job 任务调度进行索引同步,具体的作法如下:
1)添加或修改或删除课程的同时向任务表插入一条记录,这条记录就记录了是添加还是修改还是删除了哪个课程。
2)任务调度定时扫描任务表,根据任务表的内容对课程信息进行同步,如果添加了课程将课程添加到索引库,如果修改了课程就修改索引库的课程,如果是删除了课程将课程信息从索引库删除。
关于索引同步的技术还可以使用 Logstash 和 Canal 去实现。
我们是基于分块上传的模式实现断点续传的需求,当文件上传一部分断网后前边已经上传
过的不再上传。
1)前端对文件分块。
2)前端使用多线程一块一块上传,上传前给服务端发一个消息校验该分块是否上传,如果已上传则不再上传。
3)等所有分块上传完毕,服务端合并所有分块,校验文件的完整性。
因为分块全部上传到了服务器,服务器将所有分块按顺序进行合并,就是写每个分块文件内容按顺序依次写入一个文件中。使用字节流去读写文件。
4)前端给服务传了一个 md5 值,服务端合并文件后计算合并后文件的 md5 是否和前端传的一样,如果一样则说文件完整,如果不一样说明可能由于网络丢包导致文件不完整,这时上传失败需要重新上传。
上传一个文件进行分块上传,上传一半不传了,之前上传到 minio 的分块文件要清理吗?
怎么做的?
1)在数据库中有一张文件表记录 minio 中存储的文件信息。
2)文件开始上传时会写入文件表,状态为上传中,上传完成会更新状态为上传完成
3)当一个文件传了一半不再上传了说明该文件没有上传完成,会有定时任务去查询文件表中的记录,如果文件未上传完成则删除 minio 中没有上传成功的文件目录。
XXL-JOB 分布式任务调度服务由调用中心和执行器组成,调用中心负责按任务调度策略向执行器下发任务,执行器负责接收任务执行任务。
1)首先部署并启动 xxl-job 调度中心。(一个 java 工程)
2)首先在微服务添加 xxl-job 依赖,在微服务中配置执行器
3)启动微服务,执行器向调度中心上报自己。
4)在微服务中写一个任务方法并用 xxl-job 的注解去标记执行任务的方法名称。
5)在调度中心配置任务调度策略,调度策略就是每隔多长时间执行还是在每天或每月的固定时间去执行,比如每天 0 点执行,或每隔 1 小时执行一次等。
6)在调度中心启动任务。
7)调度中心根据任务调度策略,到达时间就开始下发任务给执行器。
8)执行器收到任务就开始执行任务。
1)调度中心按分片广播的方式去下发任务
2)执行器收到作业分片广播的参数:分片总数和分片序号,计算 任务 id 除以 分片总数得到一个余数,如果余数等于分片序号这时就去执行这个任务,这里保证了不同的执行器执行不同的任务。
3)配置调度过期策略为“忽略”,避免同一个执行器多次重复执行同一个任务
4)配置任务阻塞处理策略为“丢弃后续调度”,注意:丢弃也没事下一次调度就又可以执行了
5)另外还要保证任务处理的幂等性,执行过的任务可以打一个状态标记已完成,下次再调度执行该任务判断该任务已完成就不再执行。
它描述了一次和多次请求某一个资源对于资源本身应该具有同样的结果。
幂等性是为了解决重复提交问题,比如:恶意刷单,重复支付等。
解决幂等性常用的方案:
1)数据库约束,比如:唯一索引,主键。同一个主键不可能两次都插入成功。
2)乐观锁,常用于数据库,更新数据时根据乐观锁状态去更新。
3)唯一序列号(Token),请求前生成唯一的序列号,携带序列号去请求,执行时在 redis 记录该序列号表示以该序列号的请求执行过了,如果相同的序列号再次来执行说明是重复执行。
4)悲观锁,悲观锁使用时一般伴随事务一起使用,数据锁定时间可能会很长,根据实际情况选用(另外还要考虑id是否为主键,如果id不是主键或者不是 InnoDB 存储引擎,那么就会出现锁全表)。
这里我们在数据库视频处理表中添加处理状态字段,视频处理完成更新状态为完成,执行视频处理前判断状态是否完成,如果完成则不再处理。
基于 RabbitMQ 延迟队列处理未支付订单
-
创建订单的同时向 MQ 发送一条消息给一个队列并设置消息的 TTL (过期时间),比如: 设置 30 分钟,由于该队列设置了死信交换机,30 分钟后消息将投递到死信交换机。
-
当消息过期,消息发到了死信交换机,同时发给了死信队列 (死信队列绑定了死信交换机)
-
程序监听了死信队列,收到了过期的订单。
-
程序收到消息判断如果订单未支付则取消订单(同时删除对应的支付记录以及选课记录),如果说已支付不用处理。(如果是电商项目则存在库存问题,同时也需要更改库存,涉及到分布式事务)
1、设置消息持久化
首先设置交换机支持持久化(定义交换机时设置持久化为 true)、其次设置队列支持持久化(定义队列时设置持久化为 true)、发送消息时设置消息要持久化;
2、消费者收到消息处理完成要确认
设置消费者确认模式为自动确认 acknowledge-mode=auto,当程序处理正常没有异常会发送 ack,抛出异常则发关 nack 也可以设置为手动确认,在程序处理完成的代码处手动发送 ack。
3、消费失败重试
消费失败后在消费者本地进行重试,达到最大重试次数会将失败消息投递到指定交换机,交换机绑定一个异常消息队列,程序监听这个队列收到异常消息后放在数据库中单独处理,或由人工处理。
重复投递的原因:等待超时后需要重试;
避免重复投递::消息生产时,生产者发送的消息携带一个 Message ID(全局唯一 ID)作为去重和幂等的依据,避免重复的消息进入队列;
重复消费的原因:消费者接收消息后,在确认之前断开了连接或者取消订阅消息会被重新分发给下一个订阅的消费者;
避免重复消费:消息消费时,要求消息体中必须要有一个全局唯一 ID,作为去重和幂等的依据,避免同一条消息被重复消费。
redis 缓存的是白名单接口(无需认证即可访问)所需要的数据,缓存了普通用户所要查询的数据(我的订单、我的选课),缓存热点数据(最新发布的课程信息、推荐课程信息等)。
每类信息有不同的缓存过期时间,为了避免缓存雪崩缓存时间加了随机数。
| 内容 | 时间 | 类型 |
|---|---|---|
| 验证码 | 30秒 | 字符串 |
| 课程发布信息 | 7天 | hash |
| 课程视频信息 | 7天 | hash |
| 我的课程 | 3分钟 | hash |
| 我的订单 | 3分钟 | hash |
缓存一致性是数据库和缓存保持一致,当修改了数据库的信息缓存的数据也要同时更新和数据库保持一致。
去查询数据时先查询缓存,如果缓存有就返回,如果没有就查询数据库,如果查不到则缓存一个 null 字符串(过期时间设置的小一些),如果查询到了,缓存到 redis 具体的信息。(解决缓存穿透)
项目中使用:先更新数据库,再删除缓存。
首先使用 redis 集群。
其次使用分布式锁进行控制避免缓存击穿,通过分布式锁控制只有一个线程去查询数据库,查完数据库后存入缓存。
我们使用 redisson 实现分布锁。redisson 实现 Lock 接口,基于此接口使用,具体方法是获取锁调用 lock()方法,用完释放锁调用 unlock()。
当线程还没有执行完时会有看门狗对锁进行续期,保证线程在执行过程中不会让锁过期。
缓存雪崩问题。
缓存穿透问题。
缓存一致性问题。
订单服务作为通用服务在订单支付成功后需要将支付结果通知给与订单服务对接的其它微服务。
为了保证通知过程的简便还要保证消息全部到达消费服务,采用发布订阅的方式通知支付结果。
此处也是分布式事务:使用 本地消息表+MQ 的方式实现。
学习中心服务:对收费课程选课需要支付,与订单服务对接完成支付。
学习资源服务:对收费的学习资料需要购买后下载,与订单服务对接完成支付。
订单服务完成支付后将支付结果发给每一个与订单服务对接的微服务,订单服务将消息发给交换机,由交换机广播消息,每个订阅消息的微服务都可以接收到支付结果,根据支付结果的内容去更新自己的业务数据。
学习中心等微服务收到消息并处理完成通过消息队列回复订单服务。
分布式事务问题
订单服务收到第三方支付系统的通知更新支付结果,订单服务将支付结果通知给其它微服务,订单服务需要保证更新支付结果成功并且向其它微服务通知支付结果也成功,两件事跨多个微服务并且需要保证一致性,存在分布式事务控制的需求。
针对该业务场景如何控制分布式事务?
根据需求可知,订单服务先将支付结果更新成功后再将支付结果通知给其它微服务,只要保证最终将支付结果通知到微服务保证最终一致性即可,可以采用课程发布模块的技术方案,先通过本地事务更新支付结果的同时添加一条消息表记录,再由任务调度去定时调度将支付结果通知给其它微服务。
首先是进行 缓存预热,将代金券信息(开始时间、结束时间、机构id,金额、库存等)存入Redis中,预减库存(基于乐观锁防止超卖-当库存大于0就允许购买,同时判断是否存在该用户订单信息防止重复下单);
然后基于 Redis + Lua 脚本进行资格判断和秒杀业务,当条件成立,更新缓存(在Redis层扣减库存并添加用户订单到集合set中),返回true的状态,流量削峰(然后后端生成对应的订单信息发送到MQ当中,订单id使用Redis生成唯一id,同时创建一条消息放入MQ,设置TTL,并且绑定死信交换机和死信队列,当超时会被发送到死信队列中进行处理,读取对应订单的支付记录,若支付成功则添加或更新对应订单支付状态,否则删除对应订单并且回滚库存);
使用线程池从MQ当中取出订单信息进行添加,并且使用MQ的可靠传输模式,当订单添加失败重试后还是失败的时候会放入到一个特殊的队列当中去单独处理。
在添加订单时,基于Redisson分布式锁,防止重复下单问题,插入数据需要获取锁才能进行操作,并且同时更新数据库中库存信息时使用乐观锁防止超卖和判断是否存在订单信息防止重复下单;
当支付成功时,生成支付记录,更新订单的支付状态。
为了防止对秒杀接口进行恶意刷单,使用 Redis + Lua 脚本对同一 IP 和 用户进行 防刷限流,自定义注解 @Prevent ,然后基于 AspectJ,定义防刷切面类以及定义切入点,切入点就是在被注解@Prevent修饰的方法执行前去执行防刷判断逻辑,即先通过 ip 获取 Redisson分布式锁,然后通过 user 获取用户锁(直接执行 lua 脚本即可),获取成功之后基于 lua 脚本去判断对应的用户和ip访问的次数,大于指定5次就决绝往下执行,否则次数加一并更新过期时间,然后释放获取到的锁即可。只需要在执行的接口方法上加上@Prevent注解即可生效。
- 高并发:在秒杀活动中,可能需要处理数十万到数百万的请求,而且这些请求通常都是集中在短时间内到达的。通过Jmeter压力测试,系统的QPS从150/s(SSM)提升到2000/s(SpringBoot)。
- 测试:【关闭支付功能,默认支付成功条件下,在10000个线程并发调用1000个用户,每个用户发起10次访问,保证了超卖以及重复下单问题,并且系统吞吐量从原先的 150/s 上下到现在保持在 2200/s 上下】
参考优化策略:秒杀令牌(token)加 秒杀大闸 限制入口流量。线程池技术限制瞬时并发数。验证码做防刷功能。
使用 Redis 判断库存并放行请求确实可以达到限流的作用,因为只有库存充足的请求才能够进入后台处理,从而避免了后台处理过多请求的情况。但是,这种方式并不能完全解决限流问题,因为如果短时间内有大量请求同时到达,即使库存充足,也可能会导致后台处理过多请求,从而影响系统的稳定性和性能。
因此,为了更好地限流,可以使用秒杀大闸来进行秒杀令牌的获取。秒杀大闸可以控制请求的流量,确保只有一定数量的请求能够进入后台处理,从而避免了后台处理过多请求的情况。因此,使用秒杀大闸可以更好地保证系统的稳定性和性能,而不是多此一举。
在三台服务器上分别部署了一台Redis节点和一台Sentinel节点,然后通过 slaveof 命令配置好三台Redis节点的主从关系,这里是一主二从模式,启动三台Redis,然后配置三台Sentinel中master节点的地址信息,在项目服务端中配置Sentinel的配置信息,然后创建读写策略的Bean并配置读写策略为 REPLICA _PREFERRED:优先从slave(replica)节点读取,所有的slave都不可用才读取master,完成哨兵模式的集群搭建。
提高系统的高可用,高性能以及高并发能力。
技术面、小Leader:
1、贵部门/公司的主要业务是什么?
2、贵部门/公司的主要技术栈用的是什么?
3、我觉得我今天表现得不是很好,您对我有什么建议呢?
4、大概什么时候能有答复呢?(下一面大概多久安排呢?)
5、后续还有多少论面试呢?
6、胜任岗位需要学习的技能以及如何去学习和提升自己的专业技能?
主管、大Leader:
1、贵公司对新入职得员工/实习生的培养机制是什么?
2、新入职得员工/实习生主要做的工作内容可以简单介绍一下吗?
3、老员工在公司的(拼多多)的工作体验?
1、哪个部门以及哪个小组?
2、部门规模和小组人数有多少人?
3、假如有幸来到贵公司,贵公司的试用期多少时间呢?
4、团队的职责,在公司的定位和重要性?团队未来的发展空间以及团队的范围和文化等
5、个人岗位的考核、评价标准以及成长路径?
6、团队招人比较看重候选人哪些方面的能力?
1、getFields():获取所有的 public 的元素,包含父类;getDeclaredFields():获取当前类型所有的元素,不区分访问修饰符。但是不能访问到父类的元素。可以通过class.getSuperclass来获取父类的元素。
2、field.getClass()得到的是 java.lang.reflect.Field,field.getType()得到的才是域所对应的类型。
3、JDK动态代理问题:
Class<?> clazz; // 要代理的接口类型
Proxy.newInstance(
clazz.getClassLoader(), // 用于定义代理类的类加载器
new Class[]{clazz}, // 实现代理类的接口列表
new InvocationHandelr(); // 将方法调用分派到调用处理程序
)1、序列化错误:
@RpcService(interfaceClass = UserService.class)
public class UserServiceImpl implements UserService {
@Override
public List<User> getAllUsers() {
// 注意:直接使用 Arrays.ArrayList 会导致序列化异常,与 List 类型不匹配
return new ArrayList<>(Arrays.asList(new User("xm", "123456", 23),
new User("hwd", "123456", 23),
new User("hwd", "123456", 24)));
}
}1、基于Netty
{
"耗时": 30, // 最低 24 - protostuff
"调用次数": 100
}
{
"耗时": 25164, // 最低 25164
"调用次数": 100000
}2、测试 Dubbo
{
"耗时": 25, // 最低 19
"调用次数": 100
}
{
"耗时": 18600, // 最低 17816
"调用次数": 100000
}教学机构人员在课程审核通过后即可发布课程,课程发布后会公开展示在网站上供学生查看、选课和学习。
在网站上展示课程信息需要解决课程信息显示的性能问题,如果速度慢(排除网速)会影响用户的体验性。
如何去快速搜索课程?
打开课程详情页面仍然去查询数据库可行吗?
为了提高网站的速度需要将课程信息进行缓存,并且要将课程信息加入索引库方便搜索,下图显示了课程发布后课程信息的流转情况
1、向内容管理数据库的课程发布表存储课程发布信息。
2、向 Redis 存储课程缓存信息。
3、向 Elasticsearch 存储课程索引信息。
4、请求分布文件系统存储课程静态化页面(即 html 页面),实现快速浏览课程详情页面。
redis 中的课程缓存信息是将课程发布表中的数据转为 json 进行存储。
elasticsearch 中的课程索引信息是根据搜索需要将课程名称、课程介绍等信息进行索引存储。
MinIO 中存储了课程的静态化页面文件(html 网页),查看课程详情是通过文件系统去浏览课程详情页面。
涉及分布式事务,课程内容模块、媒体资源模块和课程搜索模块,基于【本地消息表 + 任务调度 + OpenFeign】实现,在课程内容模块中进行远程过程调用其他微服务实现分布式事务控制。(主要原因是 Redis 并不属于一个单独的微服务,所以 Redis 在课程内容模块进行缓存添加,同时使用 OpenFeign 调用其他微服务进行相应操作来简化编码操作,统一在一个模块,方便管理。其次就是其他微服务没有涉及到DB的操作)
具体流程图如下:
1、执行发布操作,内容管理服务存储课程发布表的同时向消息表添加一条“课程发布任务”。这里使用本地事务保证课程发布信息保存成功,同时消息表也保存成功。
2、任务调度服务定时调度内容管理服务扫描消息表,由于课程发布操作后向消息表插入一条课程发布任务,此时扫描到一条任务。
3、拿到任务开始执行任务,分别向 redis、elasticsearch 及文件系统存储数据。
4、任务完成后删除消息表记录。
1、前端上传文件前请求媒资接口层检查文件是否存在,如果已经存在则不再上传。
2、如果文件在系统不存在则前端开始上传,首先对视频文件进行分块
3、前端分块进行上传,上传前首先检查分块是否上传,如已上传则不再上传,如果未上传则开始上传分块。
4、前端请求媒资管理接口层请求上传分块。
5、接口层请求服务层上传分块。
6、服务端将分块信息上传到 MinIO。
7、前端将分块上传完毕请求接口层合并分块。
8、接口层请求服务层合并分块。
9、服务层根据文件信息找到 MinIO 中的分块文件,下载到本地临时目录,将所有分块下载完毕后开始合并 。
10、合并完成将合并后的文件上传到 MinIO。
1、请求学习中心服务创建选课记录
2、请求订单服务创建商品订单、生成支付二维码。
3、用户扫码请求订单支付服务,订单支付服务请求第三方支付平台生成支付订单。
4、前端唤起支付客户端,用户输入密码完成支付(支付二维码只能使用一次)。
5、第三方支付平台支付完成发起支付通知。
6、订单支付服务接收支付通知结果。
7、用户在前端查询支付结果,请求订单支付服务查询支付结果,如果订单支付服务还没有收到支付结果则请求学习中心查询支付结果。
8、订单支付服务向学习中心服务通知支付结果。
9、学习中心服务收到支付结果,如果支付成功则更新选课记录,并添加到我的课程表。
涉及分布式事务,订单服务模块 和 学习中心模块,基于【本地消息表 + MQ】的方式实现。
选课状态:
[{"code":"701001","desc":"选课成功"},{"code":"701002","desc":"待支付"}]订单交易类型状态:
[
{"code":"600001","desc":"未支付"},
{"code":"600002","desc":"已支付"},
{"code":"600003","desc":"已关闭"},
{"code":"600004","desc":"已退款"},
{"code":"600005","desc":"已完成"}
]支付记录交易状态:
[
{"code":"601001","desc":"未支付"},
{"code":"601002","desc":"已支付"},
{"code":"601003","desc":"已退款"}
]选课学习资格:
[
{"code":"702001","desc":"正常学习"},
{"code":"702002","desc":"没有选课或选课后没有支付"},
{"code":"702003","desc":"已过期需要申请续期或重新支付"}
]HR面:
面试官您好,我叫XXX,来自东北大学,专业是软件工程,目前是一名研三的学生。我本科就读于南昌航空大学,连续四年获得一等奖学金和国家励志奖学金,年级排名前2%,保研到东北大学,硕士期间连续两年获得一等奖学金,年级排名前8%,在校期间参与多项与本专业相关的比赛,获得了国奖。项目经历的话,主要参与了两个项目的开发和设计,一个是基于Netty的RPC框架,还有一个是在线教育平台系统,使用到的技术栈有SpringCloud、SpringBoot、Zookeeper、Mybatis、Redis、Elasticsearch、RabbitMQ等,和贵公司的Java后端开发工程师岗位的职责比较贴切,贵公司作为互联网领域的领先企业,我非常想加入阿里体验先进的技术平台,与众多优秀的同事共事学习、共同进步,提升自己。我的自我介绍完毕,谢谢面试官。
腾讯:互联网领域
美团:生活服务领域
携程:旅游领域
拼多多:电商领域
快手:短视频领域
优点:1、乐观 2、积极 3、善学 4、抗压能力强 5、和善 6、认真负责、严谨、有耐心
缺点:1、犹豫 2、感性 3、不会拒绝 4、强迫症?
感性:比较感性意思是指对别人看待或处理问题多从感情角度出发的印象性评价。
优点:
- 细心、有耐心,注重细节和质量。
- 积极进取、乐观向上,充满热情和动力。
- 沟通能力强,善于与人合作。
- 学习能力强,擅长掌握新知识和技能。
- 适应性强,能够适应不同的工作环境和任务要求。
缺点:
- 容易过于追求完美,导致时间管理不够好。
- 在压力较大的情况下,有时会显得有些焦虑。
- 对某些工作领域不够了解,需要不断学习和提高。
- 有时候会被自己的思维方式限制,需要多听取别人的意见和建议。
- 有时候会过于关注细节,忽略了整体目标和战略。
优势:
1、首先是和大部分同学相比的话,我参与过很多实验室的项目开发,因此有项目开发经验,并且的话有两次担任项目负责人的经历,所以能更快的上手公司的项目开发当中;然后的话,参与过比较多和专业相关的竞赛,并且也取得了一些奖项。
2、学习能力较强,并且的话比较能静下心来去学习和专研一门技术,比如我的一个RPC框架的项目,在开始编写这个项目前,我提前花了几周的时间去参考了目前比较优秀的RPC框架的源码设计和思想,例如Dubbo,然后去仔细研究相关的设计理念和代码实现,对整个框架有了一定了解后,然后花了一个月左右的时间写了一个RPC项目。
3、喜欢思考和复盘的人,过往的每次经历,我都会在其中提炼出自己的好的和不好的地方,并在以后的工作中进行提升和改进。
劣势:
1、犹豫;2、不会拒绝。
3、比较执着,比如在技术方面比较爱钻研,有的时候会为一个技术问题加班到深夜。还有就是,工作比较按部就班,总是按照主管的要求完成任务。另外的缺点是,总在自己的工作范围内有创新意识,并没有扩展给其他同事
对于技术方面的难题,就是去年我们实验室在做一个项目的时候,这个时候我们几个组员都在家里面,有辽宁、江西、山东的,然后这个时候我们需要涉及到各种前后端接口对接测试问题,实验室服务器又在这个时间段维修然后无法使用,同时我们这个项目涉及深度学习,因此需要服务器带有较高配置的GPU才能运行起来,然后现有的带有GPU的云服务器的价格非常高,一两个月将近六七千,然后这个时候我为了能使得小组项目能正常调试开发,去上网查询如何让两台电脑互相进行接口访问,这个时候找到了一种方式,也就是内网穿透,之前我是没有听过这个词的,大概的解决思路就是只要有一个在公网的服务器,然后通过这个公网服务器来让两台电脑去进行数据交互,这个时候我用自己买来学习用的服务器,没有GPU配置一个月大概就几十块钱,然后学着在我的这个服务器上搭建内网穿透,通过几天的学习之后,最终搭建好了内网穿透的服务,配置好两台电脑的信息之后,就能让我们直接的电脑通过公网进行数据访问(PS:我们自己的电脑是带有GPU的,因此可以运行项目)。
对于生活方面的就是去年11月底的时候,这个时候我们的项目还有一周左右就要验收了,此时我们的项目还没有经过系统测试,并且的话相关的提交文档等资料还没有写完全,然后一周之后我们的开题答辩也要开始,由于我们前段时间几乎都在忙着做项目,开题答辩的具体内容也还没有一个完整的方案,这个时候两件事都集中在一块过来了,然后我们这段时间几乎每天都工作到一两点钟,白天看论文,然后和我们小导师时不时开会交流我们的开题内容,晚上的话就是抓紧时间负责进行系统的测试,然后针对系统测试内容编写项目的交付文档,最终在验收答辩会之前的话,将所有的文档都交付了,然后在系统验收会上成功通过了系统测试环节,随后几天就是针对之前看的论文还有小导师提供的思路,确定好自己的毕设题目以及技术路线,然后撰写开题报告和答辩PPT,在开题答辩前夕完成了开题的任务,最终的话,我在开题答辩上获得了全组第三的成绩。这段经历我觉得属于近期遇到比较有挑战性的经历,也让自己收获了很多,能够抗住压力,并且在压力下可以协调自己的时间,从而去克服这些困难。
坚持写日记,从高一开始坚持每天写日记,现在还在保持着,即使很忙,也会写几句话总结一下今天做了什么,以及心情和感受。
包括在大四毕业到现在,我每天都坚持做LeetCode上的每日一题的算法题。
我觉得我最后悔得一件事情应该自己高中有一段时间,因为自己得一些原因,几乎没有怎么认真学习过,然后从班级十几名到后面的三四十名,最差是倒数十来名。
我认为最自豪的一件事,近期印象比较深刻的就是去年5月份开始做的一个项目,之前看合同看到这个项目是个120w的项目,这个项目的话基本上是我一个人完成了近70%,总共是花了6个多月的时间。整个项目的阶段有算法研究、后端开发、前端开发和系统测试、系统部署、项目交付文档撰写六个环节,其中算法研发、后端开发和系统部署全是我一个人独立完成的,然后前端开发也是另一名同学接着我之前写好的代码框架和交互往下进行的,系统测试和项目文档撰写环节也有参与。经过六个月的开发,期间大部分时间整天在研究算法和编码跑实验,在系统验收成功之后,就觉得自己也能在短短六个月的时间去完成一个百万级别的项目,让我觉得特别自豪。
我目前刚进入贵公司的话职位是初级工程师,然后我个人是比较倾向于去做技术方向,所以我的一个规划是,能在2-3年之内晋升为中级甚至高级工程师,3-5年内期望成为技术专家,再往后就是希望能达到高级技术专家甚至技术总监职位。
其次的话,因为之前面试过程中我了解到贵公司也会有自己自研的中间件,在我写RPC项目之后,我对中间件或者框架的开发也有比较浓厚的兴趣,因此如果有机会的话,我会想参与贵公司某些自研框架的设计和开发当中,去提升自己的技术能力。
携程集团(Trip.comGroup)是中国一家大型旅游网站,1999年创办,总部设立在上海,公司业务范围涵盖酒店预订、机票预订、度假预订等领域。
携程平台非常的高,非常大,不管是在技术或业务在互联网+旅游领域都处于龙头地位,并且在各大平台携程的风评也是非常的好,我就想加入携程体验先进开放的平台,与众多优秀的同事共事学习、共同进步,提升自己。
拼多多,是以电商为主要业务的一家互联网大厂。拼多多平台非常大,在国内电商领域也是排在TOP级别,并且的话拼多多据我所了解技术氛围以及业务前景都非常好,因此我非常想加入拼多多体验先进的平台,提升自己。
能直接表达出太过消极或者负面的态度。以下是一个可能的回答:
在工作中我非常注重细节和质量,所以如果碰到一些缺乏规范或者不够严谨的工作流程或者代码,会让我感到有些烦躁。但是我相信这些问题都可以通过与同事沟通协调、提出改进建议来得到解决,这样不仅能提高我们团队的工作效率和品质,也能促进个人的成长和发展。
- 自我学习:我经常利用业余时间阅读相关书籍、观看教育视频、参加在线课程、听取专家讲座,以提升自己的知识和技能水平。
- 社交学习:我会积极参加各种行业和领域的活动和聚会,在与同行和专家的交流中获取新的观点和见解,并且建立有价值的人脉关系。
- 实践学习:我注重实践,通过参与项目、做练习、搭建实验环境等方式,把理论知识转化为实际应用能力,并不断优化和改进自己的方法和思路。
- 反思学习:每次完成一个任务或项目后,我都会反思过去的经验和教训,分析成功因素和失败原因,并在未来的学习和实践中做出相应的调整和改进。
1、技术栈是否匹配
2、base地
3、互联网大厂
4、业务是否感兴趣或者核心
5、薪资
大概多久之后可以出结果呀?
薪资?
前两轮的面评?
主要提升了自己在真实的生成环境中开发的能力,开发过程中提高了自己阅读前人项目代码的水平,理解并熟悉业务,在接到一个需求时,要先去制定一个开发方案,然后再对开发方案作评估,评估通过后再去进行开发,并且开发过程中需要知道自己改动的某个地方会影响到其他的模块,考虑的点更多了,对一个系统的业务熟悉和梳理能力有很大的提升。
技术上也得到了一定的提升,比如通过多次设计开发方案,提升了自己对一个需求或者系统模块模块的理解,并且学习使用到了新的工具,规范了自己的编码。
做需求时,不要仅限于自己需求所涉及的那一部分内容,要善于对整体系统的了解和把控,主要是对整体系统的架构理解,模块划分,各模块所提供的服务和如何协调工作,技术选型,当前系统面向的用户群体,以及数据规模,系统的迭代开发过程以及需求变更。








