先讲项目背景,再讲技术栈模块划分,
讲业务的时候可以先讲一般再特殊
为什么用这个,好处是什么,应用场景
Debug发现问题/日志发现问题.
QPS
TPS
项目单元测试,代码的变更覆盖率达到80%,项目的复用性高。
测试用例,考虑清楚,自动测试框架
Qps\TpS,压测(修复前后对比,提升了多少倍)
项目难点:
FGC(内存泄漏),定位bug
CPU飙升
并发问题(死锁(代码),并发集合原理(源码))
制造问题-发现问题-解决问题-看了源码
实习内容:
http框架使用nestjs,数据库使用mongodb3.x-4.x,redis,消息队列使用kafka,nosql,服务承载使用k8s容器化架构,日志使用的kibana
nestjs的依赖循环,REQUEST注解,链路追踪id(cls-rtracer)等造成的内存泄露和性能问题
在做了优化之后,就开始使用jmeter进行测试,使用两种测试方案
提供最简单的http服务和一个数据库查询,最高qps达到1000+
提供普通业务的http服务,内部有简单的数据库增删改查或请求其他服务,qps普遍在200-300
技术方向
灰度发布目的:一个是降低线上系统问题影响的客户群体,一个是降低线上系统问题的故障时
0. Spring Boot/DDD + JDK21 + MyBatis + Redis + Elasticsearch 8 + LogStash + Kibana + K8S + Docker + Dubbo + Eureka 、Nacos
-
熟悉微服务架构技术,掌握开源MQ、RPC框架、注册中心、配置中心、分布式跟踪、微服务网关和分布式事务(操作一致性,要么全部成功、失败)、网关(高并发、监控与限流、路由转发)gateway等中间件技术
-
服务熔断,熔断器(Sentinel) A – > B ,其中B不可调用,A为保证自身不受影响,从而不调用B,减轻A和B的压力,直至服务B恢复。
-
服务降级(减轻系统压力)A (50) —> B (30) —> C (20) ,50条数据A扛住,B扛不住,解决服务雪崩
-
服务限流(高并发)滑动窗口计数器、令牌桶
-
分布式 , 缓存 , 消息队列
-
集合、I/O、多线程/线程池的使用
-
后端Velocity、XML、JSON、ZooKeeper(watch机制和临时节点特性)、Tomcat
-
前端JQuery/Bootstrap、Vue3
-
XML、HTML、JSP、javascript、JDBC、Servlet
-
Oracle、mysql、NoSQL、MongoDB等数据库
-
Kafka、ZooKeeper
-
Istio + K8S
-
Spring Security / Shiro
-
具备自动化测试脚本编写经验,熟悉业界常用自动化测试工具,如Jenkins、selenium、SoapUI、jMeter、cucumber等;
-
JVM工作原理
-
Minio图床,将文件上传到图床,数据库保存url地址为了方便之后,下载。
-
es底层原理、redis应用场景和缓存管理:热点数据维护、缓存雪崩、缓存穿透
-
文件下载提高效率 - FastDFS
-
链路分析 Sleuth 、Zipkin
-
集群管理:Spring cloud cluster
-
事件驱动:Spring cloud stream ,方便通过发送消息(数据)来进行通信 || RocketMQ || KAFka;
-
消息推送实时性,建立长连接-vertx框架(基于netty的异步网络轻量级框架)、websocket
-
用户越来越多,一台服务器不够用,需要用到多台服务器 - 负载均衡 Nginx 、集群TPS、QPS
-
多台服务器下他们会有通信问题 - RPC远程调用
-
支付、认证会产生对方接口调用过慢,网络等影响 - 异步
-
同时使用的人数过多,避免服务器爆炸 - 消息队列 MQ/KafKa 消息处理
-
数据库的数据量过大 - 建立索引,分库分表
-
常用信息访问过多占用资源 - Nosql 缓存
-
重复提交【接口幂等性】,分布式事务,分布式锁。
-
资源争抢问题和秒杀场景如何设计
-
Spring源码以及相关的场景
-
RPC
-
AOP(动态代理)自定义注解-前置通知(登录前校验)、环绕通知(操作日志)、参数、执行结果,生命周期声明的原理
IOC 、DI – > 生命周期 – > 循环依赖
AOP --> 动态代理 --> 声明式事务
Spring
1. Autowired 和 Resource区别
来源不同:@Autowired 来自 Spring 框架,而 @Resource 来自于Java;
依赖查找的顺序不同:@Autowired 先根据类型再根据名称查询,而 @Resource 先根据名称再根据类型查询;
支持的参数不同:@Autowired 只支持设置 1 个参数,而 @Resource 支持设置 7 个参数;
依赖注入的用法支持不同:@Autowired 既支持构造方法注入,又支持属性注入和 Setter 注入,而 @Resource 只支持属性注入和 Setter 注入
2. IOC
控制反转,创建和获取对象的技术思想,依赖注入(DI)是实现这种技术的一种方式,分为两个注解:Bean初始化注解和依赖注入注解@Resource / @Autowired 和 @Service/@Configuration/ @Component/@Bean
省去了new 创建对象,这个过程由IOC容器接管
通过封装对象的创建和生命周期的管理,使用依赖注入,解耦对象之间的依赖关系,利用反射和配置元数据,来动态创建和管理对象,同时提供作用域管理的功能,这些机制共同构建了Spring IOC的原理。
3. AOP
AOP一种面向切面编程的思想,主要就是在不修改现有业务逻辑代码的情况下,动态的添加或修改程序的执行逻辑。什么是切面,简单来说切面:编写公共逻辑代码的那个类,通过@Aspect注解的类实现
应用:1. 权限校验拦截器(token校验):首先把所有Controller标记为一个切入点,在切入点之前创建一个切面,切面中实现token校验逻辑。
- 动态切换数据源:可以将@DS注解表示的方法定义为“切入点”,另外再定义一个切面类,定义一个切面类, 定一个切换数据源的方changDateSource,拿到注解里面那个制定的数据源ID,如果我们的数据库不支持制定的数据源,就使用默认数据源,否则,如何支持就设置为手动设置的那个数据源,定义一个切点,使用@Pointcut注解指明对添加了所有@DS注解的接口,在这里拓展,我们切换数据源的功能。(@Before \ @After)
AOP原理:
相对原有的代码进行添加或者修改,最直接的办法就是直接在原有代码的基础上进行添加或者修改,但是如果在面对很多方法都要新增逻辑,这样就需要同样的方法复制到多个方法里面,造成程序的冗余,aspectJ是一个独立的AOP框架,同时Spring提供了对AspectJ注解的驱动支持,通常@EnableAspectJAutoProxy注解启用对ASpectJ框架 的支持,使Spring能够识别织入里@Aspect声明的切面。
AOP主要通过动态代理实现,在运行的时候创建代理对象,这些代理对象可以拦截目标对象的方法调用,并在方法执行前后执行切面中定义的逻辑,主要分为两种:JDK动态代理和CGLIB代理。
- JDK动态代理:Spring使用Proxy类和InvocationHandler接口在运行时动态创建代理类,由代理类实现目标对象的所有接口,并重写接口中方法,然后将切面逻辑执行到方法的执行中。
举个例子:银行存钱取钱,首先有一个Account账户接口,存钱的话,账户余额就增加,取钱的话就减少,首先使用JDK自带的Proxy类来实例化一个Proxy对象,然后拿到代理对象,重写InnovocationHandler里面的invoke方法。在被代理对象方法的前面后者后面执行我们需要拓展的业务逻辑即可。
- CGLIB代理是一个强大的高性能代码生成库,它在运行时动态生成目标类的子类来创建代理对象,并重写方法,实现前面逻辑的织入,它是一种基于类的代理方式,不要求目标对象实现接口,操作:实例化一个代理工厂ProxyFactory(),设置代理对象,重写MethodInterceotor 的invoke方法,然后在拓展原有的业务逻辑之前或者之后,进行拓展。
总的来说呢,Springboot主要解决两点:“业务功能拓展” 和 ”性能消耗管理“,AOP负责业务功能的拓展,他保证在拓展业务功能时,不用修改原有的业务逻辑,动态添加或修改程序的执行逻辑,IOC负责统一管理对象创建和依赖关系,解决了内存消耗问题。
-
bean的生命周期
-
BeanFactory 和 FactoryBean 的区别
微服务
微服务就是一种软件架构风格,专注于单一职责的小型项目为基础,组合成复杂的大型项目。
分布式架构 - 解耦合,降低服务集群影响范围
- Eureka - 实现服务治理(服务注册与发现),可以对所有的微服务进行集中管理,包括他们的运行状态、信息等。
- Ribbon - 提供客户端的软件负载均衡算法(现在被SpringCloudLoadBalancer取代)。
Hystrix - 断路器,保护系统,控制故障范围。 - Zuul - 具有api网关,路由,负载均衡等多种作用。
- Config - 配置管理,可以实现配置文件集中管理。
分布式架构要考虑的问题:
- 服务拆分粒度如何?
- 服务集群地址如何维护?
- 服务之间如何远程调用?
- 服务健康状态如何感知?
RPC
- (Remote Procedure Call 远程过程调用,IPC单机中运行的进程之间的相互通信,应用场景:A服务器想调用B服务器的接口)
我们怎么告诉远程服务器,我们调用的是方法1,而不是方法2:Call ID进行函数映射
在本地调用中,函数题是直接通过函数指针来确定的,我们通过调用函数
1,编译器就会自动帮我们调用它对应的函数指针,但是在远程调用中,调用函数指针的方法是行不通的,因为两个机器的两个进程的空间地址是完全不一样的,
所以,在RPC调用中,所有的函数必须有一个自己的ID,这个ID在所有进程中是唯一确定的,客户端在做远程过程调用时,必须附上这个ID,
同时我们还需要在客户端和服务分别维护一个“函数与Call ID的对应表”,客户端和服务端上的对应表不一定需要完全相同,到哪相同的函数对应的Call ID必须相同,
当客户端进行远程调用的时候,就查一下这个表,找出想要调用函数对应的Call ID,然后把 Call ID传给服务端,服务端也通过查表,来确定客户端调用的函数,然后执行对应的函数代码。
客户端怎么把参数传给远程的函数:序列化和反序列化(Serizlize)
在本地调用中,我们只需要把参数压到栈里,然后让函数自己去栈里读就行。
但是在远程过程调用时,客户端跟服务端是不同的(机器的)进程,不能通过内存来传递参数,
甚至有时候客户端和服务端使用的都不是同一种语言(比如服务端用C++,客户端用Java或者Python),
这时候就需要客户端把参数先转成一个字节流,传给服务端后,服务端再把这个字节流转成自己能读取的格式,
这个过程叫序列化和反序列化。同理,从服务端返回的值也需要序列化反序列化的过程。
网络传输问题(将Call ID和序列化后的参数字节流传送给服务端,在把序列话后的函数调用结果返回给客户端)
远程过程调用往往用在网络上,客户端和服务端是通过网络连接的。所有的数据都需要通过网络传输,因此就需要有一个网络传输层。网络传输层需要把“Call ID”和“序列化后的参数字节流”传给服务端,然后再把“序列化后的调用结果”传回客户端,只要能完成这两个功能的,都可以作为传输层使用。因此,RPC所使用的协议其实是不限的,只要能完成传输就行。尽管大部分RPC框架都使用TCP协议,但其实UDP也可以,而gRPC干脆就用了HTTP2
综上所述,要实现一个RPC框架,其实只需要把以上三点实现了就基本完成了。Call ID映射可以直接使用函数字符串,也可以使用整数ID,映射表一般就是一个哈希表;序列化反序列化可以自己写,也可以使用Protobuf或者FlatBuffers之类的;网络传输库可以自己写Socket,或者用Asio,ZeroMQ,Netty之类。
RPC调用基本流程:建立通信连接 -> IP+端口+方法名称 -> Client进行参数序列化 ->反序列化 -> 调用函数返回序列化函数结果 -> 反序列化
1. 要解决通信的问题。主要是通过在客户端和服务器之间建立连接(如TCP连接),远程过程调用的所有数据都在这个连接中传输。此连接可以是按需连接,调用结束后就断掉,即短连接;也可以是长连接,多个远程过程共享同一个连接。
2. 要解决寻址问题。也就是说,服务器上的应用怎么告诉底层的RPC框架,如何连接到B服务器(如主机或IP地址)以及特定的端口、方法名称(IP+端口+方法名),这样才能完成调用。
3. client上的应用发起远程过程调用时,方法的参数需要通过底层的网络协议(如TCP)传递到server上。由于网络协议是基于二进制的,内存中的参数也要序列化成二进制的形式,这就是序列化过程(Serizlize),通过连接寻址,将序列化的二进制发送给server。
4. server收到请求后,需要对参数进行反序列化,恢复为内存中的表达形式,然后找到对应的方法(根据CALL ID与函数的对应表),进行本地调用,然后得到函数的返回值。
5. server需要将函数返回值发送给client上的应用。该返回值也需要经过序列化后发送,client接到server发送的消息后,再对该消息进行反序列化,恢复为内存中的表达形式,交给client上的应用。
Dubbo:RPC框架
服务消费者在调用服务提供者的时候发生了阻塞、等待的情形,这个时候,服务消费者会一直等待下去。
在某个峰值时刻,大量的请求都在同时请求服务消费者,会造成线程的大量堆积,势必会造成雪崩。
解决办法:
(1)设置超时和重试机制
dubbo 利用超时机制来解决这个问题,设置一个超时时间,在这个时间段内,无法完成服务访问,则自动断开连接。
使用timeout属性配置超时时间,默认值1000,单位毫秒。
设置了超时时间,在这个时间段内,无法完成服务访问,则自动断开连接。
如果出现网络抖动,则这一次请求就会失败。
Dubbo 提供重试机制来避免类似问题的发生。
通过 retries 属性来设置重试次数。默认为 2 次。
(2)超时和重试代码
其实就是在service上加配置属性,
@Service(timeout=3000) @Reference(timeout=3000)
@Service(retries=2) @Reference(retries=2)
负载均衡策略(4种):
Random :按权重随机,默认值。按权重设置随机概率。
RoundRobin :按权重轮询。
LeastActive:最少活跃调用数,相同活跃数的随机。
ConsistentHash:一致性 Hash,相同参数的请求总是发到同一提供者。
// 代码:
@Service(loadbalance="random")
@Reference(loadbalance="random")
集群容错模式:
Failover Cluster:失败重试。默认值。当出现失败,重试其它服务器 ,默认重试2次,使用 retries 配置。一般用于读操作
Failfast Cluster :快速失败,只发起一次调用,失败立即报错。通常用于写操作。
Failsafe Cluster :失败安全,出现异常时,直接忽略。返回一个空结果。
Failback Cluster :失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
Forking Cluster :并行调用多个服务器,只要一个成功即返回。
Broadcast Cluster :广播调用所有提供者,逐个调用,任意一台报错则报错。
// 代码
@Service(cluster="failfast")
@Reference(cluster="failfast")
ELK
IK分词器源码
热启动
ELK连接Mysql(热点词)
Elasticsearch + Logstash + Kibana + Filebeat
Elasticsearch + Logstash + Kafka + Kibana
收集磁盘中的日志文件,然后导入到Elasticsearch。
ELK中Logstash底层原理
解决问题:
- 日志记录的格式复杂,正则表达式非常磨人。
- 服务日志有多种格式,如何匹配。
- 错误日志打印了堆栈信息,包含很多行,如何合并。
- 日志记录行数过多(100 多行),被拆分到了其他的日志记录中。
- 输出到 ES 的日志包含很多无意义字段。
- 输出到 ES 的日志时间和本来的日志时间相差 8 小时。
- 如何优化 Logstash 的性能
- Logstash 单点故障如何处理。
Logstash:(收集、解析、转换日志)解决生产环境出现问题都需要远程到服务器查看日志文件,定义统一的日志搜索入口,从几十万条日志中搜索关键信息
正则解析
分片 sharding(分布式存储)和副本replicas(备份,注意:不可与分片内容放在同一台服务器防止内容丢失)
ES不可root用户启动,必须普通用户启动
ES.port : 9200
kabana.port : 5601
post /es_db/_doc/_search
{
"query" : {
// term 整体匹配,match 拆分匹配
"term / match " : {
"name" : "admin"
}
}
}
- ES工作原理 与Mysql 模糊查询的区别
ES是是一种分布式数据引擎,处理大规模数据,并采用倒排索引存储数据,预处理的分词机制全文扫描
MySql更适合事务性擦欧总和关系数据的查询,全表扫描 (数据越大,效率越低)
关系型 database table row column
ES Index type document field
type : keyword 不会拆开
: text 分词/拆开
NoSQL:
MongoDB
try {
db.student.insertMany([
{"_id":"1", "articleid":"100001", "content":"哈哈哈", "createTime":new Date("2024-01-11")}
])
}catch (e) {
print(e);
}
// 修改语句格式
db.collection.update(query, update, options)
// 默认修改一条数据
db.studnet.update({_id:"1001",{$set{name:"123"}})
// 批量修改db.student.uptate({_id:"1221",{$set{name:"123"}}, {multi:true})
// 对列值增长的修改
// 对其数值,每次递增1
db.collection.update({_id:"2"},{$inc:NumberInt(1)}})
// 查找
db.collection.find()
// 删除
db.collection.remove(“条件”)
// 统计查询,限制查询条数,默认是20,skip是跳过多少条数据
db.collection.find().limit(NUMBER).skip(NUMBER)
// 排序查询
db.collection.find().sort({userId:-1,likemnum:1}) // -1降序,1升序
db.collection.find ({"field":{$gt value}}) # 大于 fielde > value
# $lt 小于 $gte 大于等于 $lte小于等于 $ne 不等于
# 包含
db.collection.find({userId{$in:["1101","1104"]}})
db.collection.find({userId{$nin:["1101","1104"]}}) # 不包含
# 条件连接查询
# $and:[{},{}]
# $or:[{},{}]
db.collection.find($and:[{num:{$gte:NumberInt(700)}}, {num:{$lt:NumberInt(800)}])
# 索引
db.student.createIndex({userId:1})
# 执行计划:查看查询性能
db.student.find({userId:"1121"}).explain
MongnDB使用B树,Mysql使用B+树
应用场景:
- 对数据库高并发读写的需求
- 对海量数据的高效存储和访问的需求
- 对数据库的高可拓展性和高可用性等
- 应用不需要事物以及复杂join支持
- 读写QPS在2000以上
- 应用需要TB/PB级别的数据存储
- 应用需要大量的地理位置查询、文本查询
缺点:数据量大、读写很频繁、数据价值较低,对事务性要求不高
具体应用场景:
- 视频直播,存储用户信息
- 物联网场景,使用MongoDB存储订单信息,订单状态在运送过程中会不断更行,以内嵌数组的形式来存储,一次查询就能将订单错有的变更读取出来。
- 用户发表朋友圈信息
数据插入
Redis:
推荐极客时间的《redis核心技术与实战》
问题
使用redis实现分布式锁,如果持有锁的主线程挂了,那么其他锁一直等待(锁未释放,如何解决)
设置锁的过期时间
利用消费订阅机制 与 数据超时特性
缓存(关键数据的数据副本):
适用数据:1.实时性、数据一致性要求不高、2.访问量大且更新频率不高的数据(读多 && 写少)
本地缓存 && 分布式缓存
缓存中存入的数据是JSON字符串
本地缓存缺点:单体架构影响小、分布式存在数据不一致问题
将数据存入缓存 —> 序列化 && 反序列化
1. 从缓存中查询数据 .get()
2. if(StringUtils.isEmpty(Obj)) 1. 从数据库中查询 2. 将查到的data save --> redis 将对象转化为JSON格式
3. return JSON字符串转化为Obj (JOSN.parseObject())
- 在使用缓存之前,使用布隆过滤器,检查一个元素是否在一个集合,如果布隆过滤器不存在,那么就直接返回,不在查询数据库,解决缓存穿透问题(查询一个数据库中不存在的数据,由于缓存不命中(因为数据根本就不存在),请求便会穿过缓存,直接请求数据库。如果有大量此类请求,数据库压力会突然增大,严重时可能会拖垮数据库)。
2.加锁或队列:当热点key过期时,不是所有请求都去数据库查询,而是让某一个请求去数据库查询并更新缓存,其他请求等待缓存更新后再访问缓存,解决缓存击穿(一个热点key在缓存中有效期过期的瞬间,大量请求同时涌入数据库去查询这个数据,因为缓存过期这些请求不能被缓存拦截,直接请求到数据库,导致数据库瞬间压力过大)- 使用高可用的缓存架构,即使缓存服务出现问题,也能通过备份机制快速恢复,解决缓存雪崩(在某一个时间段内,大量的缓存键集中过期失效,导致所有的请求都落到数据库上,造成数据库瞬间压力过大可能到达崩溃的状态)
计算机网络:
计算机操作系统、计算机网络:
推荐小林coding:https://xiaolincoding.com/
Cookie、Session(会话)、Token
Cookie把Session放到Cookie中保存到浏览器
Session 诞生并保存在服务器
Token诞生在服务器保存在浏览器,持有令牌可以访问服务器数据
Mysql
Neo4j
-
如何选择合适的分布式主键方案
数据库自增长序列或字段。
UUID。
Redis 生成 ID
雪花算法,或者雪花加盐算法
利用 zookeeper 生成唯一 ID
MongoDB 的 ObjectId
-
解决在Mysql表很大,进行分页查询加载很慢,limit加载很慢的问题
好的,面试官,这个问题在生产环境也是比较常见的。
limit m,n ;其实去扫描m+n条数据,然后过滤掉前面的m条数据,当m越大,那么需要扫描的数据也就越多,性能也会越来越慢。
针对这种情况,有以下几种方案可以进行一定的优化。
1.如果id是趋势递增的,那么每次查询都可以返回这次查询最大的ID,然后下次查询,加上大于上次最大id的条件,这样会通过主键索引去扫描,并且扫描数量会少很多很多。因为只需要扫描where
条件的数据
2.先limit出来主键ID,然后用主表跟查询出来的ID进行inner join 内连接,这样,也能一定上提速,因为减少了回表,查询ID只需要走聚集索引就行。
3.当然,如果mysql级别优化不了了。我们也可以对分页数据进行缓存,比如Redis缓存,数据进行变动的时候,做好缓存依赖即可。
4.在业务允许的条件下限制页数,因为越往后,一般用户行为触及不到,比如你去看淘宝,不会去翻后面几百页的数据,所以,业务
层面也可以做一些让步,比如不做后面几百页的数据。
Mysql双主 + keepalived
两个数据库分别部署在两台服务器上,相互同步数据,但是只有一个提供给外部访问,当一个宕机后,另外一个可以继续提供服务,在没有 keepalived 的帮助下,只能手动切换。
- 检测和重启:两台服务器上都部署 keepalived 软件,定时检测 MySQL 服务是否正常,如果一个数据库服务崩了,keepalived 会用脚本尝试重启 mysql 服务。
- 备份:两个 keepalived 服务都提供了虚拟 IP 供客户端使用,但是流量只会转到一台 MySQL 服务上。
- 虚拟 IP:keepalived 配置好了后,会有一个 虚拟 IP,对于客户端来说,不关心连接的是哪台 MySQL,访问虚拟 IP 就可以了。
- 流量切换:如果客户端正在访问的 MySQL 服务崩了后,keepalived 会用我们写的脚本自动重启 MySQL,如果重启失败,脚本主动停掉 keepalived,客户端的流量就不会访问到这台服务器上的 MySQL 服务,后续访问的流量都会切到另外一台 MySQL 服务。
主主集群、主从集群
熟悉mysql事务的ACID特性和事务隔离级别
- MySQL*:
推荐尚硅谷视频MySQL数据库高级,mysql优化,数据库优化
推荐极客时间的《MySQL实战45讲》
优化SQL:
Mysql 是一个IO访问量非常频繁的关系型数据库
1.搭建Mysql主从集群:单个Mysql服务容易当个节点故障,一旦服务器宕机,将会导致数据应用无响应,主从或主主集群可提高服务的可用性。
2.读写分离设计:在读多写少的场景中,通过读写分离的方案,避免读写冲突导致的性能影响。
3.引入分库分表机制:分库可以降低单个服务器节点的IO压力,分表可以降低单表的数据量,提高SQL查询效率。
4.针对热点数据,引入更高效的分布式数据库:利用Redis、MongoDB。
SQL优化三部曲:
-
慢SQL的定位和排查:
通过慢查询日志和慢查询日志分析工具得到有问题的SQL列表
-
执行计划分析
针对慢SQL使用关键字explain来查看当前SQL的执行计划,重点关注type key rows filterd等字段,定位SQL慢的根本原因,再有放矢地进行优化。
-
使用 show profile工具
此工具分析当前会话中SQL资源消耗情况的工具,用于SQL调优的测量。分析IO开销、CPU开销、内存开销等
-- 注意:
-- SQL查询一定要通过索引来进行数据扫描
-- 避免索引列上使用函数或者运算,这样回导致索引失效
-- where字句中like % 尽量防止在右边
-- 查询中少用 *
分库和分表设计:
按照业务归属拆分为不同的库中
按照字段数据的活跃性将字段拆分为主表和副表
按照字段拆分到多个库或多个表
分库分表的中间件;sharding-jdbc(当当)、Mycat、TDDL(淘宝)、Oceanus (58同城)、Atlas(360)
-- 注意:
-- 事务问题:需要用分布式事务
-- 跨节点 Join问题:解决这个问题可以分两次查询实现
-- 夸节点的count、order、group by 以及聚合函数问题:分别在各个节点上得到结果后在应用程序端进行合并。
-- 数据迁移、容量规划、扩容等问题
-- ID问题:无法依赖数据库自身饿的主键生成机制,解决:用UUID
-- 跨分片的排序分页问题(后台加大Pagesize处理)
应用场景:
分库不分表:数据库的读写访问量过高,数据库连接不够用
分表不分库:单表数据量非常大,数据写入和查询的性能出现瓶颈,并发量不高,数据库连接够用
分裤分表:数据库连接不够用、单表数据量大
Docker + K8S
docker -help 查看所有帮助文档
docker run -help 查看帮助文档
docker run --name 容器名称 -p 80:80 -d naginx
docker load -i mysql.tar 加载一个tar文件,导入为镜像
容器运行命令
左侧是宿主端口 : 右侧是容器端口
-d 后台运行
docker start 容器名称
docker stop 容器名称
docker rm -f 容器名称
强制删除容器
docker ps -a
查看所有 容器
docker exec -it mn bash
进入容器执行命令
docker exec - 进入容器内部,执行一个命令
-it - 给当前进入的容器创建一个标准的输入、输出终端
mn -进入容器的名称
bash -进入容器后执行的命令
docker run --name mr -p 6379:6379 -d redis redis-server --appendonly yes
# 运行redis
数据卷(volume) 虚拟目录
docker volume -help
docker volume [COMMAND]
create -创建一个volume
inspect -显示一个或者多个volume的信息
ls -列出所有的volume
prune -删除未使用的volume
rm -删除一个或多个指定的volume
数据挂载
docker run \ 创建并运行容器
--name mn \ 给容器起个名字
--v html:/root/html\ 把html数据卷挂载到容器内的/root/html目录下
-p 8080:80 把宿主机的8080端口映射到容器内的80端口
nginx \ 镜像名称
docker run --name mn -p 80:80 -v html:/usr/share/nginx/html -d nginx
docker inspect html 查看数据卷挂载位置信息
cd /var/lib/docker/volumes/html/_data
自定义镜像结构
# 指定基础镜像
FROM java:8-alpine
COPY ./docker-demo.jar /tmp/app.jar
# 暴露接口
EXPOSE 8090
# 入口,java项目启动命令
ENTRYPOINT java -jar /tmp/app.jar
docker build -t javaweb:2.0 空格.
dockerCompose
基于Compose文件,快速部署分布式应用,无需一个个微服务去构建镜像和部署
touch docker -compose.yml
# 创建一个文件
私有镜像仓库
-
重新tag本地镜像,名称前缀为私有仓库的地址:192.168.150.101:8080/
docker tag nginx:lastest 192.168.150.101:8080/nginx:1.0
-
推送镜像
docker push 192.168.150.101:8080/nginx:1.0
-
拉取镜像
docker pull 192.168.150.101:8080/nginx:1.0
Docker:将环境和程序一起打包
Docker.file:操作系统到运行程序的一个完成的命令清单文件(要做的哪些事情)
镜像仓库
Docker容器 和 虚拟机的区别
传统虚拟机自带一个完整的操作系统,Docker容器自身不带完整的操作系统,容器内部只包含操作系统的核心依赖库、配置文件等,利用NameSpace让容器看起来像一个独立操作系统一样,再利用Cgroup限制容器使用的计算资源,总的来说就是容器本质上是一个自带独立运行环境的特殊进程,底层用的还是宿主机的操作系统内核。
docker compose
应用场景:顺序化部署。数据库 --> 身份验证 --> WEB服务
yaml文件写清楚部署顺序,以及容器占用CPU和内存等信息,通过docker-compose up 解析yaml文件,进行整套程序的顺序化部署。
docker swarm
解决问题:多台服务器集群部署问题(服务器A停运),应用迁移,应用扩缩容。
K8S(Kubernetes)
以API变成的方式管理安排各个容器的引擎。
解决问题:简化服务的部署运维流程。
将服务器内部分为:控制平面 + Node(运行各个应用服务),控制平面控制各个Node
将程序和系统环打包为Container Image
程序 + 日志收集器、监控采集器 Container,共同组成一个Pod,Pod运行在Node上
k8s可以将pod从某个Node调度到另一个Node上,还能进行Pod的重启和动态扩缩容
外部用户通过Ingress控制器(入口)访问Prod cluster 、 Test cluster 集群
部署流程:
1. 编写yaml文件,写清楚部署顺序,以及容器占用CPU和内存等信息
2. Kubectl 执行 kubetcl apply -f xx.ymal
3. Kubectl将API发送给控制平面
4. Schedule(调度器),查看服务资源,比如CPU和内存是否充,部署应用
5. Controller Mgr 控制Node -> kubelet接受命令,Container runtime对镜拉取,完成pod创建
持续集成CI :代码合并、构建、部署、测试(频繁的将代码上传到主干)
持续交付CD:部署到测试环境、预生产环境
持续部署CD:部署到生产环境
(私有仓库)Nexus3 + Maven
部署流程:
- 代码push到gitlab库
- Jenkins构建,git pull 使用maven打包
- 打包成声的代码,生成一个新版本镜像,push到本地docker仓库harbor
- 发布,测试机器pull新版本镜像,并删除原来的容器,重新运行新版本镜像。(执行docker.file文件)
消息队列
SpringAMQP
spring:
rabbitmq:
host: # 主机名
virtual-host: # 虚拟主机名
username: # 用户名
password: # 密码
listener:
simple:
prefetch: 1 # 每次只能获取一条消息,处理完才能获取下一个消息
交换机作用:
- 接收publisher发送的消息
- 将消息按照规则路由到与之绑定的队列
- 不能缓存消息,路由失败,消息丢失
- FanoutExchange会将消息路由到每一个绑定的队列(群发)
声明队列、交换机、绑定关系Bean
- Queue、FanoutExchange、Binding
Direct交换机和Fanout交换机的差异(发给特定对象)
- Fanout交换机将消息路由给每一个与之绑定的队列
- Direct交换机根据RoutingKey判断路由给哪个队列
- 如果多个队列具有相同的RoutingKey,则与Fanout功能相似
发布订阅 - TopicExchange
@RabitListener(bindings = @QueueBinding (
value = @Queue(name = "topic.queue2"),
exchange = @Exchange(name = "itcast.topic", type = ExchangeTypes.TOPIC),
key = "#.news"
key = "china.#"
))
Direct 交换机 和 Topic 交换机的差异
Topic交换机支持通配符,routingKey多个单词以.分割,#代表0个或者多个,* 代表恰好一个
消息转换器
SpringbootAMQP中消息的序列化和反序列化是怎么实现的?
利用MessageConverter实现的,默认是JDK的序列化(缺点:内容较长,存储不好)
改进:采用JSON形式,采用自定义方法覆盖JDK的方法,同时接收方和发送方使用相同MessageConverter
@Bean
public MessageConverter messageConverter( ) {
return new Jackson2JsonMessageConverter();
}
RabbitMQ | RocketMQ | Kafka |
---|---|---|
数据量万级 | 10w | 10w |
基于erlang语言开发,并发能力强,延迟低(微秒) | 分布式架构,基于Java开发 | 基于大数据开发,结合Spark + flink + hadoop |
消息基本不丢失 | 经过参数优化配置,可以做到0丢失 | 经过参数优化配置,可以做到0丢失 |
应用场景:即时通讯系统 | 实时数据分析、电商秒杀、分布式事务消息 | 实时计算、日志收集 |
三者topic | mmap零拷贝技术 | sendFile零拷贝技术(比RocketMQ优秀) |
性能提升
TPS:系统每秒处理交易数
QPS:系统每秒处理查询次数
指标:CPU占用率、内存占用率
JUC推荐寒食君up主评论有总结的PDF、有时间可以读读《Java并发编程之美》
jemter发送请求测试接口,在usr/local
visualvm对Java进程监视
开源网站、性能压力测试
内存泄漏fgc
并发问题
没问题,虚拟制造问题
源码
同步和异步
全局锁(分布式锁)、互斥使用共享资源
问题:
- OOM
基础配置参数,从XXX 调到 XXX ,为什么这么调
- 如何进行JVM参数调优
1. 图形界面(压力测试服务器)-> 集群化环境,对某一台机器进行熔断(大家的环境都一样) -> 定位分析
2. arthas工具 查看业务线程(mian)死循环&& 系统线程(system)垃圾回收机制是否忙不过来,heapdump && jvisualvm -> 分析文件 -> 定位对象,不可在生产环境导出文件
jinfo pid
jstack
jstack
jmap
并发编程
程序出现死锁,是因为在多线程环境里面两个或两个以上的线程同时满足
互斥条件、请求和保持条件、不可抢占条件、循环等待条件。
(
互斥条件:共享资源x和y只能被一个线程占用
请求和保持条件,:线程t1已经获取共享资源x,在等待共享资源y的时候,不释放共享资源x
不可抢占条件:其他线程不能强行抢占线程t1占有的资源
循环等待条件,线程t1等到线程t2占有的资源,线程t2等待线程t1占有的资源,形成循环等待
)
在这四个条件里面,互斥条件是锁本身的特性,无法被破坏,其他三个条件都可以被破坏。
对于请求和保持条件,我们可以在第一次执行的时候一次性申请所有的共享资源
对于不可抢占条件,占用部分资源的线程在进一步申请其他资源的时候,如果申请不到,就主动释放
它占有的资源。
对于循环等待条件,可以按照顺序来申请资源,相当于给资源编号,按照编号顺序申请就可以避免循
环等待
出现死锁以后,可以通过jstack命令去导出线程的dump日志,
然后从dump日志里面定位到具体死锁的程序代码。
通过修改程序代码去破坏这四个条件里面的任意一个,就可以解决死锁问题。
*当然,因为互斥条件因为是锁本身的特性,所以不能被破坏。
游戏开发:
unity使用左手坐标系
ECS架构(Entity-Component-System)实体-组件-系统
entity - id(多个组件组成一个entity,组件可选择性使用)
- compont1(HP)血量
- compont2(vel)可移动
- compont3
组件只存状态,而不具有行为,实体中挂载组件(无method只有成员变量),System遍历所有(无data,只有method),完成data的逻辑处理
核心:逻辑与数据分离
规则:组件没有函数,系统没有数据
继承:为对象强行建立父子关系耦合高。(解决问题)
人工智能:
数据分析,数据可视化基础,mayavi-三唯数据可视化,nltk自然语言处理库,jieba,Python-docx基于python写一word,scikit-learn-机器学习方法工具集,tensorflow-机器学习框架,pytorch工业机器学习框架,pytorch-高校、研究机构的机器学习框架
scrapy,pyspider,web网页爬取系统,djangoweb框架,pyramid-web中型框架,flask-小型框架,aip-百度ai开放数据接口,myqr-二维码功能,pygame-游戏开发,panda3d-游戏渲染
计算机视觉、R语言、scala(spark框架),linux,scala然后spark
TensorFlow工业届机器学习,pytouch学术界机器学习
支持向量机就是把维度升高,用低维度看高维度的东西,例如:区分红绿豆
面试:
1.你做的项目中有哪些难点?以及你是如何克服的。
京东一面:
1. 自我介绍
2. java集合有哪些实现,list/map/set区别,ArrayList和LinkedList区别,扩容方式,查找速度。
ArrayList自动扩容机制
ArrayList底层实现是数组,在无参构造条件下,数组默认长度为0,add添加一个元素之后,长度为10,添加内容超过数组长度之后,就会发生数组自动扩容,每次扩容的大小为原来数组大小的1.5倍,创建新数组,然后把旧数组的元素copeOf到新数组之中,然后把新添加的元素添加到新数组中的位置,旧数组不引用,就被垃圾回收机制回收
3. 静态方法和实例方法的区别。
4. public private protect的区别,private可修饰静态方法吗?
5. springMVC知道吗?MVC的原理,分别指什么?
6. spring的装配原理(没讲出来)
7. Mybatis使用方式
8. mybatis返回一个list如何封装成一个object对象(没讲出来)
9. java反射机制(讲得不好)
10. Mysql的B+树优势
11. MySQL如何优化,explain
12. 外键,为什么不用外键。如何保证数据库表之间的关联关系。
13. 数据库的三范式
14. 分库分表
15. 查询数据量大如何导出
16. redis的LRU和LFU,持久化AOF和RDB
17. 分布式锁
18. 穿透,击穿,雪崩以及解决方法
19. 对象存储,大对象存储怎么优化
20. 权限系统,那五个表,新增接口改哪些表?
21. 限流令牌桶,水滴桶,了解其他限流框架吗?
22. 排序方法,对象对某个属性进行排序。
第二个人
23. 项目介绍,任务分配,项目流程,时间...
24. 消息队列用过吗?
25. python会用吗?
26. 对大模型方面了解吗?
27. 从哪些指标评价一个平台的稳定性
28. 时序数据库了解吗?
29. 未来规划
30. 上班城市有偏向吗?
31.反问。
代码:
用二叉树排序方法对一个数组进行排序。(卧槽,当时没想出来,就没做。想叉了,其实就是递归构建树)
腾讯金融科技
1.自我介绍
2.coding
\- 最大和连续子数组
\- 一个文件里一行有一个8位的电话号,很多文件,数据大到内存无法容纳,如何统计不同电话号出现的次数?
先说思路,文件分块读,单纯统计哪些出现→用set,因为电话号范围是00000000-99999999共10^9个数,int型可以容纳,根据os不同int所占大小不同,这里取32位,即4B,一共可能有10^8大小,因此set最大4*10^8B≈400MB。如果考虑统计次数,用map,为了简化我们的统计,key是号码仍然用int,多的就是val的大小,事先如果已知出现次数的范围,用int或者long计数,int则在set基础上多一倍,800MB,lomg这里假设是int两倍,则1.2GB。
面试官提到像一些单片机和一些简单的设备,内存还是太多了,用一些基础的结构?想到了bitmap,号码是多少就把第几位变成1。大小就只需要10^8bit≈125*10^5B≈12.5MB。那如果考虑需要统计多少次呢?那就用连续的几位来进行二进制加法计数,比如次数不超过255就连续8位表示次数,空间需要12.5*8=100MB,以此类推。
那就实现一下最初说的Map方法吧,进行了coding
3.刚刚的hashmap如果在累加过程中,val的Integer溢出,map会有什么处理吗(我记得没有,会抛出异常,所以我catch了)
4.java注解的原理和用途
5.假设我是一个不懂java的人,怎么给我介绍java aop
6.反射了解吗,是java的特性的话,c++有反射吗,反射和黑客所说的hook,钩子有关系吗
7.redis为什么快(传统八股)
8.redis怎么保证数据安全的,会发生丢失吗
9.如果数据过大,redis性能会不会受限,接近mysql
10.关系型数据库和非关系型数据库的区别联系
11.mysql你是怎么学的
12.mysql主从复制分类和原理
13.mysql除了binlog,还有其他的吗,什么作用
14.执行sql语句过程是什么,binlog,redolog这些什么时候写
15.binlog具体作用是什么
16.如果现在有一台主一台备,采用半同步复制,必须要binlog吗,如果没有备呢
17.秒杀系统,redis作用,如果并发几十万,库存10,如何设计,怎么扛得住并发,而且不超卖(这里确实有点问题,回答偏了,答到先放部分库存容错,后续再开一次秒杀了)
18.本科学过密码学吗,有哪些加解密算法,RSA的用途是什么,为什么要存密码用AES,可以看看目前业界的方式,主要是不存储密码了。对称加密算法,ARS算法,用于大量数据的加密
19.这边是做财付通的,有问题没,base地,以及是倾向于AI还是纯后台开发,都可以选
20.实习内容,目前手里的offer,实习转正吗
整体感觉非常舒服,很尊重人,一直在给予反馈和引导,也没pua,大概是一个40左右的哥,体验非常好,整体100分钟,很累,下午看已经通过,等约面
函数式编程
Lambda
(参数列表) -> {代码}
new Thread( () -> {
System.out.println("语法糖");
}
).start();
public static void main(String[] args){
for (int val) {
sout(val);
}
}
参数类型省略
方法体只有一句代码时大括号和唯一一句代码的分号可以省略
Stream流
List<Author> authors = getAuthors();
authors
.stream()
.distinct()
.filter(author -> author.getAge() < 19)
.forEach(author -> System.out.println(author.getName()));
// 单列集合
List<Author> authors = gatAuthors();
Stream<Author> stream = authors.stream();
// 双列集合
Map<String, Integer> map = new HashMap<>();
map.put("XXX", 11);
Stream<Map.Entry<String, Integer>> stream = map.entrySet().stream();
中间操作
filter(过滤)
map(对流中的元素进行计算和转换Integer,存在装箱操作,浪费时间) 优化方式 - mapToInt
authors.stream()
.map(author -> author.getName())
.map(s -> s + " 1")
.forEach(s -> sout(s));
distinct(去重) 在实体类中添加@EqualsAndHashCode (相当于equals)
sort(排序)
.sort((o1, o2)) -> o1.getAge() - o2.getAge())
// 空参条件下需要在实体类中实现Comparable<实体类>接口
limit(限制条数)
skip(跳过流中的前n个元素,返回剩下的元素)
flatMap
说明:map只能把一个对象转换成一个对象来作为流中的元素,而flatMap可以把一个对象转换成多个对象作为流中的元素
// author实体类中有ArrayList<Book>
List<Author> authors = getAuthors();
authors.stream()
.flatMap(author -> author.getBooks().stream())
.distinct()
.forEach(book -> sout(book,getName()));
// 根据种类分开打印
flatMap(book -> Arrays.stream(book.getCategory().split(",")))
终结操作
forEach
count
min & max
collection将当前元素转换成一个集合
.collection(Collectors.toList()); // 将元素转化为集合
anyMatch boolean类型,判断是否有匹配类型
allMatch 判断所有
noneMatch 都不符合
findAny 获取流中的任意一个元素,该方法没有办法保证获取的一定是流中的第一个元素
findFirst 获取流中的第一个元素
reduce归并对流中的数据按照制定的计算方式计算出一个结果(缩减操作)
.reduce(0,(result, element) -> result + element); // 累加
// identit:0 相当于result的初始值
Optional
避免空指针异常
// 无论传入的参数是否为null都不会出现问题
Author authors = getAuthors();
Optional<Author> authorOptional = Optional.ofNullable(author);
// ifPresent 安全消费值
// 会判断其中封装的数据是否为空,不为空才会执行具体的消费代码
authorOptional.filter(author -> author.age() > 19)ifPresent(author -> sout(author.getName()));
Author authors = getAuthors();
Optional<Author> authorOptional = Optional.ofNullable(author);
// 获取数据,如果数据不为空则获取该数据,如果为空则抛出异常
try {
Author author = authorOptional.orElseThrow((Supplier<Throwable>) () -> new RuntimeException("author为空") );
sout(author.name);
}catch{
res = 0;
}
map 数据转换
函数式接口
只有一个抽象方法的接口
@FunctionalInterface
方法引用
类名或对象名 ::方法名
// 引用类的静态方法,把重写的抽象方法中的所有参数都按照顺序传入了这个静态方法中
List<Author> authors = getAuthors();
Stream<Authors> authorStream = authors.stream();
authors.parallelStream() // 并行流
stream.parallel()
.filter(num -> num + 5)
authorStream.map(author -> author.getAge())
.map(String::valueOf);
// 引用对象的实例方法
StringBuilder sb = new StringBuilder();
authorStream.map(author -> author.getName())
// .forEach(name -> sb.append(name))
.forEach(sb::append);
// 构造器引用
// forEach(str -> sout(str))
.forEach(System.out::println)
// .map(name -> new StringBuilder(name))
.map(StringBuilder::new)
代码规范
判断所有集合内部的元素是否为空,使用isEmpty()方法,而不是size() == 0
项目总结:
Controller 类 0. Try catch 1.参数校验(不用每个参数都做校验、判断对象不为空即可)2.iml实现,3.result的code返回。
catch中不能有return,通过修改res的数值,来进行return。
前端先写死,然后和用户沟通,确保样式后修改
空仓库中的.git文件拖动到要提交的文件中,然后commit提交文件
数据库:字符集:utf8mb4;排序规则:utf8mb4_general_ci;数据库索引;状态/性别tintint 1;主键 varchar (20/ 50)/ Long --> id;日期:datatime
多线程考虑加锁的同步机制、原子性(事物要么全部提交成功,要么全部失败)
方法.var 自动补充对象
名称
boolean valid 有效的
常量实体类
枚举类
// 非空判断
String.trim().length == 0 || Object.isEmpty();
// 方法参数注解
@NotEmpt // 非空
@Email // 邮箱
easy-captcha // 验证码
读代码:ctrl + shift + - 收缩代码,只看每个方法的作用,了解代码结构
@Target(TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_CARIABLE)
// 用于 类 字段 方法 参数 构造函数 局部变量
@Retention(RetentionPolicy.SOURCE)
// 只在源代码中存在,编译后被丢弃
通用常量类(可结合枚举类使用)通常是一个类(或接口),里面定义一组 public static final
变量。常量类不允许实例化,通常会将构造函数设为私有。
使用场景:适用于定义不需要关联行为的常量,常常用于配置项、系统常量等。
public class Constants {
public static final String TASK_CLASS_NAME ="TASK_CLASS_NAME";
public static final int SUCCESS = 200;
public static final String[] COLUMNTYPE_TIME = { "datetime", "time", "date", "timestamp" };
public static final String UTF8 = "UTF-8";
public static final String GBK = "GBK";
public static final String WWW = "www.";
public static final String HTTPS = "https://";
}
枚举类,自定义参数
使用场景:适用于需要固定一组常量,并且这些常量可能需要关联一些行为或属性的情况,例如状态、类型、分类等。
public enum Status {
// 正常
NORMAL("0");
// 暂停
PAUSE("1");
private String value;
private Status(String value) {this.value = value;}
public String getValue() {return value;}
}
// 业务操作类型
public enum BusinessType
{
OTHER, // 其他
INSERT, // 添加
UPDATE, // 修改
DELETE, // 删除
GRANT, // 授权
EXPORT, // 导出
IMPORT, // 导入
FORCE, // 强退
GENCODE,// 生成代码
CLEAN, // 清空数据
}
public enum DataSourceType {
MASTER, // 主库
SLAVE // 从库
}
// 请求方式
public enum HttpMethod
{
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
private static final Map<String, HttpMethod> mappings = new HashMap<>(16);
static
{
for (HttpMethod httpMethod : values())
{
mappings.put(httpMethod.name(), httpMethod);
}
}
@Nullable
public static HttpMethod resolve(@Nullable String method)
{
return (method != null ? mappings.get(method) : null);
}
public boolean matches(String method)
{
return (this == resolve(method));
}
}
测试:
压测内容 | 压测线程数 | 吞吐量 / s | 90%响应时间 | 99%响应时间
写excel表格(测试用例、测试数据、预期结果、有效/无效)
性能测试JMH
<!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core -->
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.23</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-generator-annprocess -->
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.23</version>
<scope>provided</scope>
</dependency>
测试代码
@BenchmarkMode(Mode.AverageTime) // 测试完成时间
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) // 预热 2 轮,每次 1s
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 1s
@Fork(1) // fork 1 个线程
@State(Scope.Thread) // 每个测试线程一个实例
public class HashMapCycleTest {
static Map<Integer, String> map = new HashMap() {{
// 添加数据
for (int i = 0; i < 100; i++) {
put(i, "val:" + i);
}
}};
public static void main(String[] args) throws RunnerException {
// 启动基准测试
Options opt = new OptionsBuilder()
.include(HashMapCycle.class.getSimpleName()) // 要导入的测试类
.output("/Users/admin/Desktop/jmh-map.log") // 输出测试结果的文件
.build();
new Runner(opt).run(); // 执行测试
}
@Benchmark
public void entrySet() {
// 遍历
Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Integer, String> entry = iterator.next();
Integer k = entry.getKey();
String v = entry.getValue();
}
}
@Benchmark
public void forEachEntrySet() {
// 遍历
for (Map.Entry<Integer, String> entry : map.entrySet()) {
Integer k = entry.getKey();
String v = entry.getValue();
}
}
@Benchmark
public void keySet() {
// 遍历
Iterator<Integer> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
Integer k = iterator.next();
String v = map.get(k);
}
}
@Benchmark
public void forEachKeySet() {
// 遍历
for (Integer key : map.keySet()) {
Integer k = key;
String v = map.get(k);
}
}
@Benchmark
public void lambda() {
// 遍历
map.forEach((key, value) -> {
Integer k = key;
String v = value;
});
}
@Benchmark
public void streamApi() {
// 单线程遍历
map.entrySet().stream().forEach((entry) -> {
Integer k = entry.getKey();
String v = entry.getValue();
});
}
// 此方法为多线程,一定是最好,就不参与测试
public void parallelStreamApi() {
// 多线程遍历
map.entrySet().parallelStream().forEach((entry) -> {
Integer k = entry.getKey();
String v = entry.getValue();
});
}
}