考点1:注册功能要求
功能需求:使用手机号注册,且已经注册的手机号不能重复注册,密码不能使用简单的md5加密;用户上传头像需要用到文件存储。
安全需求:高并发下账号的唯一性注册邮箱或手机号验证码不能被恶意调用。(验证码+唯一索引);头像文件存储访问方便扩容和管理。(阿里云OSS);高并发处理:异步+池化思想。
考点2:压测工具对比
LoadRunner : 性能稳定,压测结果及细粒度大,可以自定义脚本进行压测,但是太过于重大,功能比较繁多 Apache AB (单接口压测最方便): 模拟多线程并发请求。ab 命令对发出负载的计算机要求很低,既不会占用很多 CPU,也不会占用太多的内存,但却会给目标服务器造成巨大的负载,简单 DDOS 攻击等 Webbench: webbench 首先 fork 出多个子进程,每个子进程都循环做 web 访问测试。子进程把访问的结果通过 pipe 告诉父进程,父进程做最终的统计结果。 Jmeter **:**开源免费,功能强大,在互联网公司普遍使用
Jmeter的优点:
-
功能测试
-
压力测试
-
分布式压力测试
-
纯 java 开发
-
上手容易,高性能
-
提供测试数据分析
-
各种报表数据图形展示
考点3:@Async注解
什么是异步任务:异步调用时相对于同步调用而言的,同步调用时指程序按预定顺序一步步执行,每一步必须等到上一步执行完成后才能执行,异步调用则无需等待上一步程序执行完成即可执行。
异步任务适用在什么场景上:处理log,发送邮件,短信等,以及各种各样处理网络IO调用的操作上面。
@Async失效情况:
-
方法不是public
-
放回值只能为void或者Future
-
注解方法使用static修饰
-
没加注解@Async或者@EnableAsync注解
-
调用方与被调用方法不能在同一个类
考点4:为什么调用方法和被调用方法不能在同一个类?
-
Spring在扫描bean的时候会扫描方法上是否包含@Async注解,动态地生产一个子类(即proxy代理类),当这个有注解的方法被调用时,实际上是由代理类来调用的,代理类在调用时增加异步作用。
-
如果这个有注解的方法是被同一个类中的其他方法调用的,那么该方法的调用并没有通过代理类,而是直接通过原来的那个bean,使用就失败了。
-
所以调用方和被调用方不能在用一个类,主要是使用了动态代理,同一个类的时候直接调用,不是通过生成的动态代理类调用。
-
一般将要异步执行的方法单独抽取成一个类。
-
类中需要使用@Autowired和@Resource等注解自动注入,不能自己动手new对象。
-
在Async方法上标注@Transactional是没有用的,但在Async方法上调用的方法上标注@Transactional是有效的。
考点5:自定义线程池
为什么要自定义 线程池 ?
当我们使用@Async注解没有指定线程池的话,即未设置TaskExecutor时,默认使用Spring创建ThreadPoolTaskExecutor,默认线程数为8,最大线程数为:Integer。MAX_VALUE(21亿多),队列使用LinkedBlockingQueue,容量是Inter.MAX_VALUE,空闲线程保留时间是60s。线程池拒绝策略为:AbortPolicy。
线程池内部的阻塞队列里面。但是这样有一些弊端,极其容易出现OOM,或者消息丢失。
ThreadPoolTaskExecutor和 ThreadPoolExecutor:
-
ThreadPoolExecutor,这个是JDK里面的线程池类,继承自Executor,里面有一个execute()方法,用来执行线程,线程池主要提供了一个线程队列,队列中保存着所有等待状态的线程,避免了创建与销毁的额外开销
-
ThreadPoolTaskExecutor,是spring包下的,是spring为我们提供的线程池类,其异步线程池的接口类是TaskExecutor,本质还是java.util.concurrent.Executor。
ThreadPoolTaskExecutor线程池,有哪几个重要参数,什么时候会创建线程池?
-
查看核心线程池是否满,不满就创建一个线程池。满的话执行下一步。
-
查看阻塞队列是否满,不满创建阻塞队列。满的话执行下一步。
-
查看线程池是否满,即是否达到最大线程池数,不满创建一个线程执行任务,满则按照策略处理无法执行的任务。
高并发下核心线程怎么设置?
-
首先我们需要知道是IO密集还是CPU密集?CPU密集设置跟核心数一样大小。IO密集型设置为2倍的CPU核心数。
-
但是这个是非固定的,我们还是要根据实际压测结果调整。
考点6:自定义线程池出现的问题
-
线程池参数往小了配置(此方法qps小,等待队列小)
-
线程池参数往大了配置(此方法qps等待队列大,瞬间很高,但是容易发送OOM)
通过这两个方法,我们会发现一些问题。
-
当我们采用异步发送用户体验是变好了,但是数据存在丢失的可能性,阻塞队列存储内存中,如果队列长度过多则重启容易出现丢失数据情况。
-
当我们采用异步发送+阻塞队列缓冲,刚开始瞬间QPS高,但是后续还是会降低很多。
考点7:RestTemplate里面存在的问题
服务端向前端socket连接管道写返回数据时 链接(pipe)却断开了。从应用角度分析,这是因为客户端等待返回超时了,主动断开了与服务端连接。连接数设置太小,并发量增加后,造成大量请求排队等待。网络延迟,是否丢包。内存是否足够多支持对应的并发量。
重新认识RestTemplate:
-
RestTemplate是spring提供的用于访问Rest服务的客户端
-
底层通过使用java.net包下创建HTTP请求
-
通过使用ClientHttpRequestFactory指定不同的HTTP请求方式,主要提供了两种实现方法。
-
SimpleClientHttpRequestFactory(默认)底层使用j2se提供的方式,即java.net包提供的方式,创建底层Http请求连接,主要createRequest方法(端点调试),每次都会创建连接会造成极大的资源浪费,而且若连接不能及时释放,会因为无法建立新的连接导致后面的请求阻塞。
-
HttpComponentsClientHttpRequestFactory底层使用HttpClient访问远程的Http服务。
-
问题解决:
-
客户端每次请求都要和服务端建立新的连接,即三次握手将会非常耗时。
-
通过http连接池可以减少连接建立与释放的时间,提升http请求的性能
-
Spring的restTemplate是对httpclient进行了封装,而httpclient是支持池化机制
-
对httpclient进⾏封装的有:Apache的Fluent、es的 restHighLevelClient、spring的restTemplate等
@Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate(ClientHttpRequestFactory requestFactory){ return new RestTemplate(requestFactory); } @Bean public ClientHttpRequestFactory httpRequestFactory(){ return new HttpComponentsClientHttpRequestFactory(httpClient()); } @Bean public HttpClient httpClient(){ Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create() .register("http", PlainConnectionSocketFactory.getSocketFactory()) .register("https", SSLConnectionSocketFactory.getSocketFactory()) .build(); PoolingHttpClientConnectionManager connectionManager=new PoolingHttpClientConnectionManager(registry); //设置连接池最大是500个连接 connectionManager.setMaxTotal(500); //MaxPerRout是对maxTotal的细分,每个主机的并发最大是300,route指域名 connectionManager.setDefaultMaxPerRoute(300); /** * 只请求 该网站 , 最大并发量300 */ RequestConfig requestConfig=RequestConfig.custom() //返回数据的超时时间 .setSocketTimeout(20000) //连接上服务器的超时时间 .setConnectTimeout(10000) //从连接池中获取连接的超时时间 .setConnectionRequestTimeout(1000) .build(); CloseableHttpClient closeableHttpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig) .setConnectionManager(connectionManager) .build(); return closeableHttpClient; } }
