@yexiaoqi
2022-04-12T17:50:29.000000Z
字数 9319
阅读 1013
java面试总结
秒杀在同一时间段会有很大的并发,对web服务器,数据库,业务代码都有要求。
web服务器:大集群(tomcat),页面静态化(nginx)
数据库:数据库分片(集群),减少对数据库的访问(缓存)
业务代码:被请求方法中的线程安全问题,可能造成库存超卖。主要有几种解决方案,比如悲观锁,分布式锁,乐观锁,队列串行化,异步队列分散,Redis原子操作等。一般情况下不会加锁,加锁会有性能问题。
尽量不去操作共享变量
代码尽量单一
//提交订单
public void submitOrder(Long seckillId, String userId){
//用redis中的List作为队列,秒杀结束或者指定的时间到后,从队列中取出前1000个(此商品的库存量)作为秒杀成功的订单,同步数据到数据库
redisTemplate.boundListOps("seckillId").rightPush(userId);
}
秒杀开始,用户点击秒杀,进入秒杀详情页,点击“立即购买”,页面显示“正在为您抢购,请稍后......”,页面隔10s去查一次,如果秒杀成功,跳转到支付页面;秒杀失败,提示该商品已售罄,秒杀结束。
商品上下架后,发消息(商品的sku_id)给ActiveMQ,页面生成服务拿到id,查询数据库生成对应的静态页面,解决高并发场景中对web服务器的压力,同时用户的体验更好。
用户输入关键字,用solr完成搜索,返回给前端页面展示。
传递消息的方式:
主要有三点:解耦、异步、削峰
消费者收到消息了,但是处理过程出bug
final Session session = connection.createSession(false,Session.CLIENT_ACKNOWLEDGE);
try{
//1.收消息
//2.处理消息
//3.手动签收 message.acknowledge();
}catch(){
//4.选择性重发
session.recover();
}
ActiveMQ:最流行,但可能丢消息
RabbitMQ:基于erlang语言开发,高可用高并发,适合集群服务器。跨平台,支持多种语言,有消息确认机制和持久化机制,可靠性高。
ZeroMQ:延迟低,快
Kafka:高吞吐(10W/s),完全的分布式系统,适合处理海量数据
field
)、复制域(copyField
)、动态域(dynamicField
)三种。<em style='coclr:red'>
</em>
可以设置文档中域的boost值,boost值越高计算出来的相关度得分就越高,排名就越靠前。用来把热点商品或者推广商品的排名提高。
指标 | 适用于 | 文件分布 | 系统性能 | 复杂性 | FUSE | POSIX | 备份机制 | 通讯协议接口 | 社区支持 | 开发语言 |
---|---|---|---|---|---|---|---|---|---|---|
FastDFS | 4KB~500MB | 小文件合并存储,不分片 | 很高 | 简单 | 不支持 | 不支持 | 组内冗余备份 | API HTTP | 国内用户群 | C |
HDFS | 大文件 | 大文件分片分块存储 | 简单 | 支持 | 支持 | 多副本 | 原生api | 较多 | java |
四大特征:MVC模式、双向绑定、依赖注入、模块化设计
核心过滤器:springSecurityFilterChain
类:org.springframework.web.filter.DelegatingFilterProxy
认证管理器里配放行的用户与角色,或者指定认证类(自定义认证类时,要实现UserDetailsService
接口,重写loadUserByUsername
方法)
问题:在处理订单时要用到定时任务,当时采用的是Spring Task来完成,由于一个订单服务会部署多个,多个订单服务同时去处理任务会造成任务被重复处理的情况,如何解决任务的重复处理。
解决:采用乐观锁解决,在任务表中设置一个version字段记录版本号,取出任务记录同时拿到任务的版本号,执行前对任务进行锁定,具体的做法是执行update根据当前版本号将版本号加1,update成功表示锁定任务成功,即可开始执行任务。
采用Spring security + Oauth2 完成用户认证及用户授权,认证授权流程如下:
1. 用户请求身份认证服务完成身份认证
2. 认证服务下发用户身份令牌和jwt令牌,拥有身份令牌表示身份合法,jwt令牌用于完成授权
3. 用户携带jwt令牌请求资源服务
4. 网关校验用户身份令牌的合法性,不合法表示用户没有登陆,如果合法则放行继续访问
5. 资源服务获取jwt令牌,根据jwt令牌完成授权
ElasticSearch | index | Type(准备废弃) | Document | Field | Mapping |
---|---|---|---|---|---|
MySQL | Database | Table | Row | Column | Schema |
CREATE TABLE `course_pub` (
`id` varchar(32) NOT NULL COMMENT '主键',
`name` varchar(32) NOT NULL COMMENT '课程名称',
`users` varchar(500) NOT NULL COMMENT '适用人群',
`mt` varchar(32) NOT NULL COMMENT '大分类',
`st` varchar(32) NOT NULL COMMENT '小分类',
`grade` varchar(32) NOT NULL COMMENT '课程等级',
`studymodel` varchar(32) NOT NULL COMMENT '学习模式',
`teachmode` varchar(32) DEFAULT NULL COMMENT '教育模式',
`description` text NOT NULL COMMENT '课程介绍',
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '时间戳logstash使用',
`charge` varchar(32) NOT NULL COMMENT '收费规则,对应数据字典',
`valid` varchar(32) NOT NULL COMMENT '有效性,对应数据字典',
`qq` varchar(32) DEFAULT NULL COMMENT '咨询qq',
`price` float(10,2) DEFAULT NULL COMMENT '价格',
`price_old` float(10,2) DEFAULT NULL COMMENT '原价格',
`expires` varchar(32) DEFAULT NULL COMMENT '过期时间',
`start_time` varchar(32) DEFAULT NULL COMMENT '课程有效期-开始时间',
`end_time` varchar(32) DEFAULT NULL COMMENT '课程有效期-结束时间',
`pic` varchar(500) DEFAULT NULL COMMENT '课程图片',
`teachplan` text NOT NULL COMMENT '课程计划',
`pub_time` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '发布时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
方案一:使用官方的热更新方案,加载外部词典
<!--用户可以在这里配置远程扩展字典 -->
<entry key="remote_ext_dict">http://xxx.com/hot_ext.dic</entry>
<!--用户可以在这里配置远程扩展停止词字典-->
<entry key="remote_ext_stopwords">http://xxx.com/stopwords.dic</entry>
http 请求需要返回两个头部(header),一个是 Last-Modified,一个是 ETag,这两者都是字符串类型,只要有一个发生变化,该插件就会去抓取新的分词进而更新词库。
该 http 请求返回的内容格式是一行一个分词,换行符用 \n 即可
可以将需自动更新的热词放在一个 UTF-8 编码的文件里,放在 nginx 或其他简易 http server 下,当文件修改时 http server 会在客户端请求该文件时自动返回相应的 Last-Modified 和 ETag。另外做一个工具来并更新这个文件。
缺点:据反馈可以用但不太稳定
方案二:修改 ik 分词器的源码,从mysql中加载
推荐第二种方案,不过两种方案更新词典后需要根据业务场景,看是否需要重做索引
RabbitMQ基于主从,使用镜像集群模式,每个节点都有这个队列的完整数据,无法水平扩展,网络带宽压力比较大
这个一般在业务代码中保证,可以基于数据库唯一键来约束,或者消费之前先查一下,已经处理就跳过
confirm
机制,写入消息后回传ACK。RabbitMQ没能处理会回调nack
接口告诉生产者消息接收失败,可以重试。拆分成多个队列,每个队列一个消费者。
或者就一个队列对应一个消费者,在消费者内部用内存队列排队,然后分发给底层不同的worker来处理
由于CPU和IO速度的差异问题,产生了DMA技术,通过DMA搬运来减少CPU的等待时间。
传统的IOread+write
方式会产生2次DMA拷贝+2次CPU拷贝,同时有4次上下文切换。
而通过mmap+write
方式则产生2次DMA拷贝+1次CPU拷贝,4次上下文切换,通过内存映射减少了一次CPU拷贝,可以减少内存使用,适合大文件的传输。
sendfile
方式是新增的一个系统调用函数,产生2次DMA拷贝+1次CPU拷贝,但是只有2次上下文切换。因为只有一次调用,减少了上下文的切换,但是用户空间对IO数据不可见,适用于静态文件服务器。
sendfile+DMA gather
方式产生2次DMA拷贝,没有CPU拷贝,而且也只有2次上下文切换。虽然极大地提升了性能,但是需要依赖新的硬件设备支持。
扫码登录当中涉及到的三种角色:PC端、手机端、服务端。涉及到2个问题:
具体流程如下:
- 访问PC端二维码生成页面,PC端请求服务端获取二维码ID
- 服务端生成相应的二维码ID,设置二维码的过期时间,状态等
- PC获取二维码ID,生成相应的二维码并展示
- 手机端扫描二维码,获取二维码ID
- 手机端将手机端token和二维码ID发送给服务端,确认登陆
- 服务端校验手机端token,根据手机端token和二维码ID生成PC端token
- PC端通过轮询方式请求服务端,通过二维码ID获取二维码状态,如果已成功,返回PC端token,登陆成功
就是一次调度任务只有一个机器执行,不会因为是分布式部署而出现多台机器同时执行某个job。思路是使用分布式锁
Quartz:
acquireTriggersWithinLock=true
获取 trigger 的时候上锁(抢占式获取数据库锁并由抢占成功的节点负责运行),默认是 false,使用乐观锁,但可能出现 ABA 导致重复调度XXL-JOB:使用数据库悲观锁
setAutoCommit(false)
关闭隐式自动提交select lock for update
显示排他锁,其他事务无法进入&无法实现for update
- 读数据库任务信息 -> 拉任务到内存时间轮 -> 更新数据库任务信息
commit
提交事务,同时释放for update
排他锁(悲观锁)
分析 GC 日志及 dump 文件,判断是否需要优化,确定瓶颈问题点;
确定 JVM 调优量化目标;
确定 JVM 调优参数(根据历史 JVM 参数来调整);
依次调优内存、延迟、吞吐量等指标;
对比观察调优前后的差异;
不断分析和调整,直到找到合适的 JVM 参数配置;
找到最合适的参数,将这些参数应用到所有服务器,并进行后续跟踪。
堆栈配置相关
垃圾收集器相关
辅助信息
集中解决方案:
- 2PC、3PC
- TCC(Try、Confirm、Cancel)
- 本地消息表
- 最大努力通知
- Seata 事务
TCC 的优点时可以自动逸数据库操作的粒度,降低了所冲突,可以提升性能;
TCC 的缺点时应用侵入性强,需要根据网络、系统故障等不同失败原因实现不同的回滚策略,实现难度大,一般借助 TCC 开源框架,例如 ByteTCC、TCC-transaction、Himly。
Spring 事务:再对应service层方法上使用@Transcational
,分布式的话需使用分布式事务
访问权限不是 public
方法用 final 修饰/是static
spring 事务底层通过 jdk 动态代理/cglib,帮我们生成代理类,在代理类中实现事务功能;如果某个方法用 final 修饰/是static,在它的代理类中就无法重写该方法
同一个类中的方法内部调用
未被 Spring 管理
多线程调用(不同的线程拿到的数据库连接不同,事务必然失效)
表不支持事务(比如使用 MyISAM)
未开启事务
错误的传播特性
默认的传播特性REQUIRED
:如果当前上下文中存在事务,则加入该事务,如果不存在,则创建一个事务。只有这三种传播特性才会创建新事务:REQUIRED
,REQUIRES_NEW
,NESTED
自己吞异常
手动抛了别的异常
spring 事务默认情况下只会滚RuntimeException
(运行时异常)和Error
(错误),对于普通的 Exception(非运行时异常)不会回滚
自定义了回滚异常
抛出的异常不属于 rollbackFor 设置的异常,则事务不会回滚,一般建议设置为 Exception
嵌套事务多回滚了
即:只想回滚嵌套事务(propagation=Propagation.NESTED
),但将整个事务都回滚了,如何处理?使用 try……catch 包住嵌套事务内的异常不往外抛
其他
大事务问题:事务执行耗时比较长,会导致死锁、锁等待、回滚时间长、接口超时、并发情况下数据库连接被占满、数据库主从延迟等问题
编程式事务,基于TransactionTemplate
的编程式事务优点:避免由于 Spring AOP 导致事务失效的问题、能更小粒度的控制事务的范围,更直观
@Autowired
private TransactionTemplate transactionTemplate;
public void save(final User user) {
queryData1();
queryData2();
transactionTemplate.execute((status) => {
addData1();
updateData2();
return Boolean.TRUE;
})
}