Redis 从入门到精通之高并发场景中的秒杀理论基础
1.什么是高并发
高并发场景是指在短时间内有大量用户同时访问同一资源,例如网站、应用程序、数据库、服务器等。在高并发场景下,系统需要处理大量的请求,并且需要保证请求的响应时间和系统的稳定性。
高并发场景的特点包括:
大量的请求:在高并发场景中,有大量的请求同时访问同一资源,例如网站、应用程序、数据库、服务器等。
短时间内处理:在高并发场景中,需要在短时间内处理大量的请求,要求系统处理速度和响应时间非常快。
资源的竞争:在高并发场景中,由于资源有限,大量的请求会竞争同一资源,例如数据库连接、内存、带宽等,可能会导致资源的瓶颈和耗尽。
需要保证稳定性:在高并发场景中,需要保证系统的稳定性和可靠性,避免系统崩溃或出现故障。
在高并发场景中,为了提高系统的并发能力和稳定性,需要使用一些特殊的技术和方案,例如负载均衡、缓存、限流、队列、分布式、事务等。同时,需要进行充分的测试和评估,并根据实际情况进行调整和优化,以确保系统的性能和可靠性。
针对上述问题典型的解决方案
下面举例说明每种技术在 Java 中的实现:
负载均衡
Java 中常用的负载均衡框架有 Apache 的 mod_jk 和 mod_proxy_balancer,以及 Nginx 和 HAProxy 等。其中,Nginx 是一种高性能的 Web 服务器,可以作为反向代理服务器和负载均衡器使用。HAProxy 是一种高性能的负载均衡器,支持多种负载均衡算法,并具有丰富的配置选项和监控功能。
缓存
Java 中常用的缓存框架有 Ehcache、Redis 和 Memcached 等。其中,Ehcache 是一种基于内存的缓存框架,可以提高数据访问的性能和响应速度。Redis 是一种高性能的键值存储系统,可以支持多种数据结构和持久化方式。Memcached 是一种分布式内存对象缓存系统,可以减少对数据库等后端资源的访问,提高系统的响应速度和并发能力。
限流
Java 中常用的限流框架有 Guava RateLimiter、Spring Cloud Gateway 和 Sentinel 等。其中,Guava RateLimiter 是一种基于令牌桶算法的限流器,可以控制单位时间内的请求速率。Spring Cloud Gateway 是一种基于 Spring Spring Cloud 的 API 网关,可以提供限流、熔断、负载均衡等功能。Sentinel 是一种分布式的流量控制框架,可以提供实时监控和流量控制等功能。
队列
Java 中常用的队列框架有 ActiveMQ、RabbitMQ 和 Kafka 等。其中,ActiveMQ 和 RabbitMQ 是两种常用的消息中间件,可以提供可靠的消息传递和异步处理功能。Kafka 是一种分布式的消息队列系统,可以支持高吞吐量和低延迟的消息传递。
分布式
Java 中常用的分布式框架有 Dubbo、Spring Cloud 和 Hadoop 等。其中,Dubbo 是一种高性能的分布式服务框架,可以提供服务注册、发现、负载均衡等功能。Spring Cloud 是一种基于 Spring Boot 的微服务框架,可以提供服务注册、发现、配置中心、负载均衡等功能。Hadoop 是一种分布式计算框架,可以提供分布式存储和计算等功能。
事务
事务框架有 Spring Transaction 和 JTA 等。其中,Spring Transaction 是一种基于 Spring 的事务管理框架,可以提供声明式事务管理和编程式事务管理等功能。JTA 是一种 Java 事务 API,可以提供分布式事务管理和多资源事务管理等功能。
预热
预热技术有缓存预热和连接池预热等。其中,缓存预热可以在系统启动时将热点数据加载到缓存中,提高系统的响应速度和并发能力。连接池预热可以在系统启动时创建连接池,并提前创建一定数量的连接,减少连接的初始化时间和等待时间,提高系统的并发能力和响应速度。
熔断
熔断框架有 Hystrix 和 Sentinel 等。其中,Hystrix 是一种基于 Netflix 的熔断框架,可以提供熔断、降级、限流等功能。Sentinel 是一种分布式的流量控制框架,可以提供实时监控和流量控制等功能。
异步步技术有 CompletableFuture、RxJava 和 Netty 等。其中,CompletableFuture 是 Java 8 引入的异步编程框架,可以实现异步执行和组合多个异步任务。RxJava 是一种基于 ReactiveX 的异步编程框架,可以实现响应式编程和异步流处理。Netty 是一种高性能的网络编程框架,可以实现非阻塞 I/O 和异步事件驱动的网络通信。这些框架可以提高系统的并发能力和响应速度,并且可以提供更好的用户体验和系统性能。
Redis 在高并发场景处于什么地位
通过以上的概念和基本的理论,我们能够看到在高并发场景中redis处于什么地位
分布式缓存分布式锁,其实也是利用分布式缓存和redis单线程和高性能基础上
本文只讨论了基本概念,下文我们通过一个简单示例,演示一下在高并发场景 redis 是如何使用的。
源码解析Spring Boot 的启动流程
作者简介:大家好,我是冰点,从业11年,目前在物流独角兽企业从事技术方面工作,
博主正在努力完成2023计划中:以梦为马,扬帆起航,2023追梦人
联系方式:iceicepip,加我进群,大家一起学习,一起进步
@[TOC]
0.前言
背景:最近有位开发同学说面试被问到Spring Boot 的启动流程,以及被问到Spring Boot 的嵌入式Web容器是什么时候加载的。如何加载的。是怎么无缝切换的。这些问题,其实回答起来也是比较复杂的。我们今天就从 SpringApplication.run(Application.class, args);入口,逐渐向下看下执行流程。来试着回答一下前面这两个问题。后面关于SpringBoot 的web容器可以无缝随意切换为jetty,undertow..这个问题的回答涉及到Spring Boot是如何设计WebServer的。我们后续专门讲解一下。
1. 执行逻辑梳理
一般我们SpringBoot 应用的启动入口都是如下这种固定的写法,也可以是这样
public static void main(String[] args) {
SpringApplication application = new SpringApplication(MyApplication.class);
// ... customize application settings here
application.run(args)
}
但总之,都是使用SpringApplication 调用静态方法此方法的注释Static helper that can be used to run a SpringApplication from the specified source using default settings.
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] {
primarySource }, args);
}
跟过来就到这,可以看到注释运行Spring应用程序,创建并刷新一个新的ApplicationContext。
跟代码到这儿其实我们对于SpringBoot 的基本启动流程已经知道了。但是要解答什么时候启动的Tomcat 还需要继续分析。到这儿我们就可以继续下去,发现Spring Boot 启动WebServer。此处的WebServer我就不展开了,可以点击去就三个方法start ,stop,getPort。可以看出来Spring 在设计接口的时候还是很严谨和精简。我们的核心脉络是梳理SpringBoot 启动过程,并且回答Tomcat 是如何被启动的。我们可以看到WebServer 的实现目前内置的有5种。其实Spring Boot 还有一个特性叫做 自动装配。这就是为什么5个实现,我们最后启动的是Tomcat。此处也不做展开。后面我专门搞一个解析SpringBoot 自动装配的文章。我们看一下内部start 的TomcatWebServer的内部实现。了解过Tomcat 源码的同学看到这儿就基本明白了。\==好源码跟进过程我们到此结束,我们整理和总结一下。==通过扫一遍源码我们大概可以总结出来如下三个阶段准备阶段、应用上下文创建阶段、刷新上下文阶段。
1. 准备阶段: Spring Boot 会加载应用程序的初始设置,并创建 Spring Boot 上下文。这个阶段的核心源码是 SpringApplication 类的 run() 方法,它会调用 Spring Boot 的各个初始化器进行初始化和准备工作。
2. 应用上下文创建阶段 : Spring Boot 会创建应用程序的上下文,包括各种配置信息、Bean 的加载和初始化等。这个阶段的核心源码是 Spring Boot 自动配置机制,通过扫描 classpath 中的配置文件,自动加载和配置各种组件和 Bean。
3. 刷新上下文阶段: Spring Boot 会执行各种启动任务,包括创建 Web 服务器、加载应用程序的配置、初始化各种组件等。这个阶段的核心源码是 Spring Boot 的刷新机制,它会调用各种初始化器和监听器,执行各种启动任务。其中启动Tomcat 就是在这个环节进行。
2. 核心源码解析
既然上面我们已经基本上总结除了,Spring Boot的启动脉络。也梳理出了一些核心源码。那么我们对启动过程的核心源码解析一下。
2.1. 准备阶段
在准备阶段中,Spring Boot 会加载应用程序的初始设置,并创建 Spring Boot 上下文。这个阶段的核心源码是 SpringApplication 类的 run() 方法,它会调用 Spring Boot 的各个初始化器进行初始化和准备工作。
public ConfigurableApplicationContext run(String... args) {
// 启动计时器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 定义应用程序上下文和异常报告器列表
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 配置 Headless 属性
configureHeadlessProperty();
// 获取 Spring Boot 启动监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
// 执行启动监听器的 starting 方法
listeners.starting();
try {
// 解析命令行参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 准备应用程序环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 配置忽略 BeanInfo
configureIgnoreBeanInfo(environment);
// 打印 Banner
Banner printedBanner = printBanner(environment);
// 创建应用程序上下文
context = createApplicationContext();
// 获取异常报告器,关于异常报告,我下次专门讲一下SpringBoot 的异常收集器。
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{
ConfigurableApplicationContext.class}, context);
// 准备应用程序上下文
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 刷新应用程序上下文
refreshContext(context);
// 刷新后操作
afterRefresh(context, applicationArguments);
// 停止计时器
stopWatch.stop();
// 记录启动日志
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 执行启动监听器的 started 方法
listeners.started(context);
// 执行 Runner
callRunners(context, applicationArguments);
} catch (Throwable ex) {
// 处理启动失败
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 执行启动监听器的 running 方法
listeners.running(context);
} catch (Throwable ex) {
// 处理启动失败
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
// 返回应用程序上下文
return context;
}
在 run() 方法中,Spring Boot 首先会创建一个 StopWatch 对象,用于记录整个启动过程的耗时。然后,Spring Boot 会调用 getRunListeners(args) 方法获取 Spring Boot 的各个启动监听器,并调用starting() 方法通知这些监听器启动过程已经开始。 接着调用 prepareEnvironment(listeners, applicationArguments) 方法创建应用程序的环境变量。这个方法会根据用户的配置和默认设置创建一个 ConfigurableEnvironment对象,并将其传给后面的 createApplicationContext() 方法。printBanner(environment) 方法打印启动界面的 Banner,调用 refreshContext(context)方法刷新上下文。这个方法会启动上下文,执行各种启动任务,包括创建 Web 服务器、加载应用程序的配置、初始化各种组件等。具体的启动任务会在刷新上下文阶段中进行。
2.2. 应用上下文创建阶段
在应用上下文创建阶段中,Spring Boot 会创建应用程序的上下文,包括各种配置信息、Bean 的加载和初始化等。这个阶段的核心源码是 Spring Boot 自动配置机制,通过扫描 classpath 中的配置文件,自动加载和配置各种组件和 Bean。
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable to create a default ApplicationContext, " +
"please specify an ApplicationContextClass", ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
在 createApplicationContext() 方法中,Spring Boot 首先会判断应用程序的类型,如果是 Web 应用程序,则会创建一个 WebApplicationContext;否则,会创建一个普通的 ApplicationContext。调用 BeanUtils.instantiateClass(contextClass) 方法创建应用程序的上下文。这个方法会根据上面的逻辑创建一个相应的 ApplicationContext。调用 load() 方法加载应用程序的配置。关于加载应用配置,可以参阅我之前写一篇文章《三分钟了解SpringBoot配置优先级底层源码解析》。这个方法会扫描 classpath 中的各种配置文件,例如 application.properties、application.yml、META-INF/spring.factories 等,自动配置各种组件和 Bean。调用 postProcessApplicationContext() 方法对应用程序的上下文进行后处理。这个方法会调用各种初始化器和监听器,执行各种初始化任务。
2.3. 刷新上下文阶段
在刷新上下文阶段中,Spring Boot 会执行各种启动任务,包括创建 Web 服务器(刚才我们跟源码的时候也看到了,如上我的截图)、加载应用程序的配置、初始化各种组件等。这个阶段的核心源码是 Spring Boot 的刷新机制,它会调用各种初始化器和监听器,执行各种启动任务。
protected void refreshContext(ConfigurableApplicationContext applicationContext) {
refresh(applicationContext);
if (this.registerShutdownHook) {
try {
applicationContext.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
在 refreshContext() 方法中调用 refresh(applicationContext) 方法刷新上下文。这个方法是 ApplicationContext 接口的核心方法,会启动上下文,执行各种启动任务。调用 registerShutdownHook() 方法注册应用程序的关闭钩子。这个方法会在应用程序关闭时自动执行,清理资源、关闭线程等,所以我们利用此特性在服务关闭的时候清理一些资源。并向外部发送告警通知。在 refresh(applicationContext) 方法中,Spring Boot 会执行上下文的各种启动任务,包括创建 Web 服务器、加载应用程序的配置、初始化各种组件等。具体的启动任务会调用各种初始化器和监听器,例如:
for (ApplicationContextInitializer<?> initializer : getInitializers()) {
initializer.initialize(applicationContext);
}
另外,Spring Boot 还会调用各种监听器,我们不做赘述,例如:
for (ApplicationListener<?> listener : getApplicationListeners()) {
if (listener instanceof SmartApplicationListener) {
SmartApplicationListener smartListener = (SmartApplicationListener) listener;
if (smartListener.supportsEventType(eventType)
&& smartListener.supportsSourceType(sourceType)) {
invokeListener(smartListener, event);
}
}
else if (supportsEvent(listener, eventType)) {
invokeListener(listener, event);
}
}
基本上就是这些了。关于SpringApplication的官方文档讲的比较简单,大家可供参考。地址如下:https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.spring-application
好了,本次的分享就到这儿,我是冰点,下次见。如果我的文章对你有所收获,可以给个赞。如果有疑问可以在评论区留言。
带你读《Elastic Stack 实战手册》之41:——3.4.3.Kibana基础应用(10)
《Elastic Stack 实战手册》——三、产品能力——3.4.入门篇——3.4.3.Kibana基础应用(9) /article/1228976根据 Nginx 日志作图?状态码?导航 -> Kibana -> Visualize -> Create visualization(创建可视化) Create visualization(创建可视化)Select a visualization type(选择可视化类型)-> Pie(饼图)Choose a source(选择数据源)-> nginx-access-logsData(数据)-> Metrics(指标)-> Slice size(切片大小)-> Aggregation(聚合)->Count(计数)-> Custom label(定指标签)-> 状态码 Buckets(存储桶)-> Add(添加)Split slices(拆分切片)Aggregation(聚合)-> Terms(词)-> Field(字段)-> http_code -> Update(更新)Save(保存)状态码 -> 保存流量?导航 -> Kibana -> Visualize -> Create visualization(创建可视化)-> Select a visualization type(选择可视化类型)-> Area(面积图)-> Choose a source(选择数据源)-> nginx-access-logs -> Data(数据)-> Y-axis(Y轴)-> Aggregation(聚合)-> Count(计数)-> Custom label(定指标签)-> 流量 -> Buckets(存储桶)-> Add(添加)-> X-axis(X轴)-> Aggregation(聚合)-> Date Histogram-> Field(字段)-> log_time -> Minimum interval(最小时间间隔)-> Second(秒)-> Custom label(每秒流量)-> Update(更新)-> Save(保存)-> 网络流量《Elastic Stack 实战手册》——三、产品能力——3.4.入门篇——3.4.3.Kibana基础应用(11) /article/1228974
带你读《Elastic Stack 实战手册》之41:——3.4.3.Kibana基础应用(8)
《Elastic Stack 实战手册》——三、产品能力——3.4.入门篇——3.4.3.Kibana基础应用(7) /article/1228978作图?以 Nginx 日志为例,插入数据,生产环境中可以通过 Beats 收集到 Elasticsearch 再作图。?插入 Nginx 日志测试数据?在 Kibana 的开发工具中执行:POST nginx-access-logs/_bulk
{"index":{"_id":"1"}}
{"log_time":"2020-06-30T18:05:03+08:00","client_ip":"115.159.116.79","method":"POST","http_code":"200","size":"66","usersip":"119.85.16.64, 115.159.116.79","request_uri":"http://qdweb.zksf.com/xfjr-zfb/PhoneQry.do","req_time":"0.016","user_ua":"Mozilla/5.0 (iPhone; CPU iPhone OS 12_3_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/7.0.4(0x17000428) NetType/4G Language/zh_CN"}
{"index":{"_id":"2"}}
{"log_time":"2020-06-30T18:05:04+08:00","client_ip":"123.206.205.161","method":"GET","http_code":"200","size":"11133","usersip":"117.136.84.181, 123.206.205.161","request_uri":"http://qdweb.zksf.com/static/wx/dist/htmls/applyCardMoneySuc/mod.js","req_time":"0.000","user_ua":"Mozilla/5.0 (Linux; Android 8.0.0; SM-G9550 Build/R16NW; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/6.2 TBS/044704 Mobile Safari/537.36 MMWEBID/1866 MicroMessenger/7.0.4.1420(0x2700043C) Process/tools NetType/4G Language/zh_CN"}
{"index":{"_id":"3"}}
{"log_time":"2020-06-30T18:05:06+08:00","client_ip":"123.206.107.139","method":"POST","http_code":"200","size":"3887","usersip":"117.136.44.137, 123.206.107.139","request_uri":"http://qdweb.zksf.com/xfjr-zfb/custLoanInfoQry.do","req_time":"0.028","user_ua":"Mozilla/5.0 (Linux; Android 8.1.0; PACM00 Build/O11019; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/6.2 TBS/044705 Mobile Safari/537.36 MMWEBID/908 MicroMessenger/7.0.4.1420(0x2700043C) Process/tools NetType/4G Language/zh_CN"}
{"index":{"_id":"4"}}
{"log_time":"2020-06-30T18:05:06+08:00","client_ip":"115.159.93.78","method":"POST","http_code":"200","size":"86","usersip":"218.26.54.246, 115.159.93.78","request_uri":"http://qdweb.zksf.com/xfjr-zfb/LoanAntiFraudQry.do","req_time":"0.022","user_ua":"Mozilla/5.0 (Linux; Android 8.1.0; vivo X21A Build/OPM1.171019.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/6.2 TBS/044705 Mobile Safari/537.36 MicroMessenger/6.7.2.1340(0x260702C5) NetType/4G Language/zh_CN"}
{"index":{"_id":"5"}}
{"log_time":"2020-06-30T18:05:31+08:00","client_ip":"123.206.205.161","method":"POST","http_code":"200","size":"110","usersip":"117.84.191.27, 123.206.205.161","request_uri":"http://qdweb.zksf.com/xfjr-zfb/WeixinForOpenId.do","req_time":"0.154","user_ua":"Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/7.0.4(0x17000428) NetType/WIFI Language/zh_CN"}
{"index":{"_id":"6"}}
{"log_time":"2020-06-30T18:05:32+08:00","client_ip":"123.206.205.161","method":"GET","http_code":"400","size":"2119","usersip":"117.84.191.27, 123.206.205.161","request_uri":"http://qdweb.zksf.com/static/wx/dist/htmls/applyCardMoney/applyCardMoney.html","req_time":"0.000","user_ua":"Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/7.0.4(0x17000428) NetType/WIFI Language/zh_CN"}
{"index":{"_id":"7"}}
{"log_time":"2020-06-30T18:05:32+08:00","client_ip":"123.206.205.161","method":"POST","http_code":"302","size":"150","usersip":"117.84.191.27, 123.206.205.161","request_uri":"http://qdweb.zksf.com/xfjr-zfb/LoginStatusQry.do","req_time":"0.014","user_ua":"Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/7.0.4(0x17000428) NetType/WIFI Language/zh_CN"}
{"index":{"_id":"8"}}
{"log_time":"2020-06-30T18:05:32+08:00","client_ip":"111.231.53.89","method":"POST","http_code":"200","size":"174","usersip":"117.136.67.251, 111.231.53.89","request_uri":"http://qdweb.zksf.com/xfjr-zfb/AntiFraudResultQry.do","req_time":"0.027","user_ua":"Mozilla/5.0 (Linux; Android 8.1.0; vivo Y83A Build/O11019; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/6.2 TBS/044705 Mobile Safari/537.36 MMWEBID/2371 MicroMessenger/7.0.4.1420(0x2700043C) Process/tools NetType/4G Language/zh_CN"}
{"index":{"_id":"9"}}
{"log_time":"2020-06-30T18:05:32+08:00","client_ip":"123.206.205.161","method":"GET","http_code":"200","size":"1306","usersip":"117.84.191.27, 123.206.205.161","request_uri":"http://qdweb.zksf.com/static/wx/dist/images/emApprove.png","req_time":"0.000","user_ua":"Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/7.0.4(0x17000428) NetType/WIFI Language/zh_CN"}
{"index":{"_id":"10"}}
{"log_time":"2020-06-30T18:05:32+08:00","client_ip":"122.152.197.50","method":"POST","http_code":"200","size":"110","usersip":"60.119.37.213, 122.152.197.50","request_uri":"http://qdweb.zksf.com/xfjr-zfb/CheckNotice.do","req_time":"0.015","user_ua":"Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/7.0.4(0x17000428) NetType/WIFI Language/zh_CN"}返回{
"took" : 612,
"errors" : false,
"items" : [
{
"index" : {
"_index" : "nginx-access-logs",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 2,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1,
"status" : 201
}
},
{
"index" : {
"_index" : "nginx-access-logs",
"_type" : "_doc",
"_id" : "6",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 2,
"failed" : 0
},
"_seq_no" : 5,
"_primary_term" : 1,
"status" : 201
}
},
{
"index" : {
"_index" : "nginx-access-logs",
"_type" : "_doc",
"_id" : "7",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 2,
"failed" : 0
},
"_seq_no" : 6,
"_primary_term" : 1,
"status" : 201
}
},
{
"index" : {
"_index" : "nginx-access-logs",
"_type" : "_doc",
"_id" : "8",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 2,
"failed" : 0
},
"_seq_no" : 7,
"_primary_term" : 1,
"status" : 201
}
},
......
]
}《Elastic Stack 实战手册》——三、产品能力——3.4.入门篇——3.4.3.Kibana基础应用(9) /article/1228976
带你读《Elastic Stack 实战手册》之12:——3.4.1.5.配置集群安全访问(1)
3.4.1.5.配置集群安全访问创作人:刘晓国?Elastic Stack 的组件是不安全的,因为它没有内置的固有安全性。 这意味着任何人都可以访问它。 在生产环境中运行 Elastic Stack 时,这会带来安全风险。 为了防止生产中未经授权的访问,采用了不同的机制来施加安全性,例如在防火墙后运行Elastic Stack并通过反向代理(例如 nginx,HAProxy 等)进行保护。 Elastic提供商业产品来保护 Elastic Stack。 此产品是 X-Pack 的一部分,模块称为安全性。?本文中将介绍如何为我们的 Elasticsearch 索引设置字段级的安全。这样有的字段对有些用户是可见的,而对另外一些用户是不可见的。我们也可以通过对用户安全的设置,使得不同的用户有不同的权限。?User authentication?在 X-Pack 安全性中,安全资源是基于用户的安全性的基础。 安全资源是需要访问以执行Elasticsearch 集群操作的资源,例如索引,文档或字段。 X-Pack 安全性通过分配给用户的角色的权限来实现。 权限是针对受保护资源的一项或多项特权。 特权是一个命名的组,代表用户可以针对安全资源执行的一个或多个操作。 用户可以具有一个或多个角色,并且用户拥有的总权限集定义为其所有角色的权限的并集,如下图所示: 从上面的图上可以看出来:一个用户可以用多个 role,而每个 role 可以对应多个 permission (权限)。在接下来的练习中,我们来展示如何创建用户,role(角色)以及把permission 分配到每个 role。通过这样的组合,我们可以实现对字段级的安全控制。?《Elastic Stack 实战手册》——三、产品能力——3.4.入门篇——3.4.1.Elastic Stack 安装部署——3.4.1.5.配置集群安全访问(2) /article/1231353
带你读《Elastic Stack 实战手册》之8:—— 3.4.1.1.安装Elasticsearch(本地及docker)(8)
《Elastic Stack 实战手册》——三、产品能力——3.4.入门篇——3.4.1.Elastic Stack 安装部署—— 3.4.1.1.安装Elasticsearch(本地及docker)(7) /article/1231506tar 和 rpm 包安装方式?登陆每个 ES 节点,并修改配置文件并和其他节点组成集群。?l?tar 包安装的配置文件 vi /usr/local/elasticsearch/config/elasticsearch.ymll?rpm 包安装的配置文件 vi /etc/elasticsearch/elasticsearch.yml?这里的 network.host 也可以配置为 _site_方便在节点批量初始化时进行配置。?# IP: 192.168.10.221
cluster.name: es-cluster
node.name: node-221
network.host: 192.168.10.221
http.port: 9200
discovery.seed_hosts: ["192.168.10.221", "192.168.10.222"]
cluster.initial_master_nodes: ["192.168.10.221"]
# IP: 192.168.10.221
cluster.name: es-cluster
node.name: node-221
network.host: 192.168.10.221
http.port: 9200
discovery.seed_hosts: ["192.168.10.221", "192.168.10.222"]
cluster.initial_master_nodes: ["192.168.10.221"]?节点启动?systemctl start elasticsearch?Docker 启动的配置方式?纯靠 docker run 命令方式启动 ES 集群会比较麻烦,建议通过 docker-compose 方式启动。示例 docker-compose.yml 文件。?version: "2.2"
networks:
bigdata:
driver: bridge
volumes:
es-data-01:
driver: local
es-data-02:
driver: local
services:
es-node-01:
image: elasticsearch:7.10.1
restart: always
container_name: es-node-01
environment:
- node.name=es-node-01
- cluster.name=docker-cluster
- cluster.initial_master_nodes=es-node-01
- discovery.seed_hosts=es-node-01,es-node-02
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- es-data-01:/usr/share/elasticsearch/data
ports:
- 9200:9200
- 9300:9300
networks:
- bigdata
es-node-02:
image: elasticsearch:7.10.1
restart: always
container_name: es-node-02
environment:
- node.name=es-node-02
- cluster.name=docker-cluster
- cluster.initial_master_nodes=es-node-01
- discovery.seed_hosts=es-node-01,es-node-02
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- es-data-02:/usr/share/elasticsearch/data
networks:
- bigdata?或者也可以在宿主机维护每个节点自己的 elasticsearch.yml 文件,并通过 -v $PATH/to/elasticsearch.yml:/usr/local/elasticsearch/config/elasticsearch.yml 的方式把这些配置文件映射到 docker container 里面进行使用。?这里需要注意在一个宿主机上,可以开放监听同一个端口的 container 只能有一个,所以如果需要整个集群里所有的节点都能监听/支持访问的话,需要把他们的 9200/9300 端口映射成宿主机里不同的端口,或者在 docker 环境中启动一个类似 NGINX 的网关来代理所有的节点。?小结?本节对 ES 集群组建流程进行了简要描述,并列举了通过多种方式对 ES 进行集群化配置。?《Elastic Stack 实战手册》——三、产品能力——3.4.入门篇——3.4.1.Elastic Stack 安装部署—— 3.4.1.1.安装Elasticsearch(本地及docker)(9) /article/1231504
带你读《Elastic Stack 实战手册》之7:——3.3.2.专有名词解释(1)
3.3.2.专有名词解释创作人:刘晓国?当我们开始使用 Elasticsearch 时,我们必须理解其中的一些重要的概念。这些概念的理解对于以后我们使用 Elastic Stack 是非常重要的。在今天的这篇文章里,我们先来介绍一下在Elastic Stack 中最重要的一些概念。?首先,我们来看下一下如下的这个图:?Cluster?Cluster 也就是集群的意思。?Elasticsearch 集群由一个或多个节点组成,可通过其集群名称进行标识。通常这个 Cluster 的名字是可以在 Elasticsearch 里的配置文件中设置的。在默认的情况下,如我们的 Elasticsearch 已经开始运行,那么它会自动生成一个叫做 “Elasticsearch” 的集群。我们可以在config/elasticsearch.yml 里定制我们的集群的名字: ?一个 Elasticsearch 的集群就像是下面的一个布局:带有 NginX 代理及 Balancer 的架构图是这样的:我们可以通过:?GET _cluster/state来获取整个 cluster 的状态。这个状态只能被 master node 所改变。上面的接口返回的结果是:{
"cluster_name": "elasticsearch",
"compressed_size_in_bytes": 1920,
"version": 10,
"state_uuid": "rPiUZXbURICvkPl8GxQXUA",
"master_node": "O4cNlHDuTyWdDhq7vhJE7g",
"blocks": {},
"nodes": {...},
"metadata": {...},
"routing_table": {...},
"routing_nodes": {...},
"snapshots": {...},
"restore": {...},
"snapshot_deletions": {...}
}?《Elastic Stack 实战手册》——三、产品能力——3.3.基础篇——3.3.2.专有名词解释(2) /article/1231573
带你读《Elastic Stack 实战手册》之3:——3.1.1.从 Elasticsearch 到 Elastic Stack(中)
《Elastic Stack 实战手册》——三、产品能力——3.1.理解 Elastic Stack——3.1.1.从 Elasticsearch 到 Elastic Stack(上) /article/1231738最初发展的问题早期,Elastic 开发和发布软件时采用的是工程师各自为战的方法:工程师可以在任何时候推出任何喜欢的版本,唯一的要求就是产品要好。Kibana 有公测版,Logstash 采用里程碑,Elasticsearch 则采用数字编号。如果工程师高兴,还可以推出插件。尽管十分混乱,但是一切还算行得通,直到最后无法使用。随着用户通过产品来完成越来越多的任务,Elastic 需要开发更好的产品来为用户提供更多帮助,所以添加了更多功能,开发了新插件和扩展。产品的确变得越来越好了,然而也越来越复杂,技术栈变得越来越混乱。例如,如果运行的 Elasticsearch 是 1.7 版本,而运行的其他插件是 2.3 版本,则软件不能自动检测二者是否兼容,也无法验证插件是否在无预警的情况下已不能正常使用。在 Elastic,也开始听到内部员工说:“如果想使用 Shield,需要使用 Elasticsearch 1.4.2,但前提是不能使用 Watcher。如果使用 Watcher 的话,则需要使用 Elasticsearch 1.5.2。而如果使用 Elasticsearch 1.5.2 的话,其仅能与 Kibana 4.0.x、Logstash1.4.x、Shield 1.2.x和 Watcher 1.0.x 兼容。”Elastic 的版本控制做得一团糟,必须得研究对策;同时,支持矩阵也表现欠佳。 调整业务步伐,推出 Beats就在产品团队为版本编号忙得团团转的时候,另外一个产品故事正在拉开序幕。Elastic 在 2015 年迎来了 Packetbeat,这是一家夫妻档公司,致力于开发一种轻量化方式来将网络数据发送至 Elasticsearch。这启发了当时的 Elastic:如果开发一系列单一用途的轻量化数据传送工具,将网络数据、日志、指标、审计数据等从边缘机器传输到 Logstash 和 Elasticsearch,结果会怎样?就这样,Beats 应运而生了。 同步推出新产品版本2015 年 10 月对 Elastic 来说是一个重大转折点,因为解决了产品版本编号问题,同时也降低了兼容性的复杂程度。这一发布版本又称为“Bonanza 同步版”,是 Elastic 第一次在同一天面向公众发布全部产品:Elasticsearch2.0、Logstash 2.0、Watcher2.0、Shield 2.0 和 Kibana 4.2。通过这次调整,用户得以更轻松地启用产品,同时也提高了产品的可靠性,帮助用户出色地完成任务。 一键部署,Elastic Cloud 隆重推出几个月后,“Bonanza 同步版”不再仅仅局限于供人们下载的产品,通过 Elastic Cloud(即之前的 Found),在 AWS 上推出了 Elasticsearch 和 Kibana 服务。2016 年Elastic Stack 5.0Elastic 致力于推出更为成熟的产品系列,通过发布 Elasticsearch 2.0 来统一发布步调就是第一步,5.0 的发布则是第二步。与之前的所有版本相比,用户通过这一版本可以体验集成性能更强,经过更严格测试且更加易于入门的产品。5.0 发行版本的同时还将所有商用插件(当时被称为 Shield、Marvel 和 Watcher)整合为单一扩展,即 X-Pack,其包含了核心产品,诸如 security、monitoring 和 alerting 等的功能,并且随着 Prelert 公司也加入 Elastic,machine learning 也开始纳入其中。 模块应运而生在 5.3 版本中,Filebeat 正式引入了“模块”的概念,可以将模块理解为用于在 ElasticStack 中传输、解析、存储、分析常用日志格式(例如 Apache、Nginx 和 MySQL 等),并实现可视化的一组安全配置,它简化了用户从数据集至仪表板的入门体验。Metricbeat 和 Packetbeat 的模块都各具特色,几个月后,Logstash 也将针对 ArcSight 和NetFlow 数据引入自身模块。《Elastic Stack 实战手册》——三、产品能力——3.1.理解 Elastic Stack——3.1.1.从 Elasticsearch 到 Elastic Stack(下) /article/1231736
Java 容器详解:使用与案例
Java容器是一套工具,用于存储数据和对象。可以与C++的STL类比。Java容器也称为Java Collection Framework (JCF)。除了存储对象的容器之外,还提供了一套工具类,用于处理和操作容器中的对象。总体来说,这是一个框架,它包含了Java对象容器和工具类。一、概览容器主要包括 Collection 和 Map 两种,Collection 存储着对象的集合,而 Map 存储着键值对(两个对象)的映射表。Collection1. SetTreeSet:基于红黑树实现,支持有序性操作,例如根据一个范围查找元素的操作。但是查找效率不如 HashSet,HashSet 查找的时间复杂度为 O(1),TreeSet 则为 O(logN)。HashSet:基于哈希表实现,支持快速查找,但不支持有序性操作。并且失去了元素的插入顺序信息,也就是说使用 Iterator 遍历 HashSet 得到的结果是不确定的。LinkedHashSet:具有 HashSet 的查找效率,并且内部使用双向链表维护元素的插入顺序。2. ListArrayList:基于动态数组实现,支持随机访问。Vector:和 ArrayList 类似,但它是线程安全的。LinkedList:基于双向链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素。不仅如此,LinkedList 还可以用作栈、队列和双向队列。3. QueueLinkedList:可以用它来实现双向队列。PriorityQueue:基于堆结构实现,可以用它来实现优先队列。MapTreeMap:基于红黑树实现。HashMap:基于哈希表实现。HashTable:和 HashMap 类似,但它是线程安全的,这意味着同一时刻多个线程同时写入 HashTable 不会导致数据不一致。它是遗留类,不应该去使用它,而是使用 ConcurrentHashMap 来支持线程安全,ConcurrentHashMap 的效率会更高,因为 ConcurrentHashMap 引入了分段锁。LinkedHashMap:使用双向链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序。二、容器中的设计模式迭代器模式Collection 继承了 Iterable 接口,其中的 iterator() 方法能够产生一个 Iterator 对象,通过这个对象就可以迭代遍历 Collection 中的元素。从 JDK 1.5 之后可以使用 foreach 方法来遍历实现了 Iterable 接口的聚合对象。List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
for (String item : list) {
System.out.println(item);
}适配器模式java.util.Arrays#asList() 可以把数组类型转换为 List 类型。@SafeVarargs
public static <T> List<T> asList(T... a)应该注意的是 asList() 的参数为泛型的变长参数,不能使用基本类型数组作为参数,只能使用相应的包装类型数组。Integer[] arr = {1, 2, 3};
List list = Arrays.asList(arr);也可以使用以下方式调用 asList():List list = Arrays.asList(1, 2, 3);三、源码分析如果没有特别说明,以下源码分析基于 JDK 1.8。在 IDEA 中 double shift 调出 Search EveryWhere,查找源码文件,找到之后就可以阅读源码。ArrayList1. 概览因为 ArrayList 是基于数组实现的,所以支持快速随机访问。RandomAccess 接口标识着该类支持快速随机访问。public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable数组的默认大小为 10。private static final int DEFAULT_CAPACITY = 10;2. 扩容添加元素时使用 ensureCapacityInternal() 方法来保证容量足够,如果不够时,需要使用 grow() 方法进行扩容,新容量的大小为 oldCapacity + (oldCapacity >> 1),即 oldCapacity+oldCapacity/2。其中 oldCapacity >> 1 需要取整,所以新容量大约是旧容量的 1.5 倍左右。(oldCapacity 为偶数就是 1.5 倍,为奇数就是 1.5 倍-0.5)扩容操作需要调用 Arrays.copyOf() 把原数组整个复制到新数组中,这个操作代价很高,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数。public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}3. 删除元素需要调用 System.arraycopy() 将 index+1 后面的元素都复制到 index 位置上,该操作的时间复杂度为 O(N),可以看到 ArrayList 删除元素的代价是非常高的。public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null;
return oldValue;
}4. 序列化ArrayList 基于数组实现,并且具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。保存元素的数组 elementData 使用 transient 修饰,该关键字声明数组默认不会被序列化。transient Object[] elementData;ArrayList 实现了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那部分内容。private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
s.defaultReadObject();
s.readInt();
if (size > 0) {
ensureCapacityInternal(size);
Object[] a = elementData;
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
int expectedModCount = modCount;
s.defaultWriteObject();
s.writeInt(size);
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}序列化时需要使用 ObjectOutputStream 的 writeObject() 将对象转换为字节流并输出。而 writeObject() 方法在传入的对象存在 writeObject() 的时候会去反射调用该对象的 writeObject() 来实现序列化。反序列化使用的是 ObjectInputStream 的 readObject() 方法,原理类似。ArrayList list = new ArrayList();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(list);5. Fail-FastmodCount 用来记录 ArrayList 结构发生变化的次数。结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化。在进行序列化或者迭代等操作时,需要比较操作前后 modCount 是否改变,如果改变了需要抛出 ConcurrentModificationException。代码参考上节序列化中的 writeObject() 方法。Vector1. 同步它的实现与 ArrayList 类似,但是使用了 synchronized 进行同步。public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
}2. 扩容Vector 的构造函数可以传入 capacityIncrement 参数,它的作用是在扩容时使容量 capacity 增长 capacityIncrement。如果这个参数的值小于等于 0,扩容时每次都令 capacity 为原来的两倍。public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}调用没有 capacityIncrement 的构造函数时,capacityIncrement 值被设置为 0,也就是说默认情况下 Vector 每次扩容时容量都会翻倍。public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
public Vector() {
this(10);
}3. 与 ArrayList 的比较Vector 是同步的,因此开销就比 ArrayList 要大,访问速度更慢。最好使用 ArrayList 而不是 Vector,因为同步操作完全可以由程序员自己来控制;Vector 每次扩容请求其大小的 2 倍(也可以通过构造函数设置增长的容量),而 ArrayList 是 1.5 倍。4. 替代方案可以使用 Collections.synchronizedList(); 得到一个线程安全的 ArrayList。List<String> list = new ArrayList<>();
List<String> synList = Collections.synchronizedList(list);也可以使用 concurrent 并发包下的 CopyOnWriteArrayList 类。List<String> list = new CopyOnWriteArrayList<>();CopyOnWriteArrayList1. 读写分离写操作在一个复制的数组上进行,读操作还是在原始数组中进行,读写分离,互不影响。写操作需要加锁,防止并发写入时导致写入数据丢失。写操作结束之后需要把原始数组指向新的复制数组。public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
final void setArray(Object[] a) {
array = a;
}@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
return (E) a[index];
}2. 适用场景CopyOnWriteArrayList 在写操作的同时允许读操作,大大提高了读操作的性能,因此很适合读多写少的应用场景。但是 CopyOnWriteArrayList 有其缺陷:内存占用:在写操作时需要复制一个新的数组,使得内存占用为原来的两倍左右;数据不一致:读操作不能读取实时性的数据,因为部分写操作的数据还未同步到读数组中。所以 CopyOnWriteArrayList 不适合内存敏感以及对实时性要求很高的场景。LinkedList1. 概览基于双向链表实现,使用 Node 存储链表节点信息。private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
}每个链表存储了 first 和 last 指针:transient Node<E> first;
transient Node<E> last;2. 与 ArrayList 的比较ArrayList 基于动态数组实现,LinkedList 基于双向链表实现。ArrayList 和 LinkedList 的区别可以归结为数组和链表的区别:数组支持随机访问,但插入删除的代价很高,需要移动大量元素;链表不支持随机访问,但插入删除只需要改变指针。HashMap为了便于理解,以下源码分析以 JDK 1.7 为主。1. 存储结构内部包含了一个 Entry 类型的数组 table。Entry 存储着键值对。它包含了四个字段,从 next 字段我们可以看出 Entry 是一个链表。即数组中的每个位置被当成一个桶,一个桶存放一个链表。HashMap 使用拉链法来解决冲突,同一个链表中存放哈希值和散列桶取模运算结果相同的 Entry。transient Entry[] table;static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
public final K getKey() {
return key;
}
public final V getValue() {
return value;
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
Object k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}
public final int hashCode() {
return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
}
public final String toString() {
return getKey() + "=" + getValue();
}
}2. 拉链法的工作原理HashMap<String, String> map = new HashMap<>();
map.put("K1", "V1");
map.put("K2", "V2");
map.put("K3", "V3");新建一个 HashMap,默认大小为 16;插入 键值对,先计算 K1 的 hashCode 为 115,使用除留余数法得到所在的桶下标 115%16=3。插入 键值对,先计算 K2 的 hashCode 为 118,使用除留余数法得到所在的桶下标 118%16=6。插入 键值对,先计算 K3 的 hashCode 为 118,使用除留余数法得到所在的桶下标 118%16=6,插在 前面。应该注意到链表的插入是以头插法方式进行的,例如上面的 不是插在 后面,而是插入在链表头部。查找需要分成两步进行:计算键值对所在的桶;在链表上顺序查找,时间复杂度显然和链表的长度成正比。3. put 操作public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
// 键为 null 单独处理
if (key == null)
return putForNullKey(value);
int hash = hash(key);
// 确定桶下标
int i = indexFor(hash, table.length);
// 先找出是否已经存在键为 key 的键值对,如果存在的话就更新这个键值对的值为 value
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
// 插入新键值对
addEntry(hash, key, value, i);
return null;
}HashMap 允许插入键为 null 的键值对。但是因为无法调用 null 的 hashCode() 方法,也就无法确定该键值对的桶下标,只能通过强制指定一个桶下标来存放。HashMap 使用第 0 个桶存放键为 null 的键值对。private V putForNullKey(V value) {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}使用链表的头插法,也就是新的键值对插在链表的头部,而不是链表的尾部。void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
// 头插法,链表头部指向新的键值对
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}4. 确定桶下标很多操作都需要先确定一个键值对所在的桶下标。int hash = hash(key);
int i = indexFor(hash, table.length);4.1 计算 hash 值final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}4.2 取模令 x = 1<<4,即 x 为 2 的 4 次方,它具有以下性质:x : 00010000
x-1 : 00001111令一个数 y 与 x-1 做与运算,可以去除 y 位级表示的第 4 位以上数:y : 10110010
x-1 : 00001111
y&(x-1) : 00000010这个性质和 y 对 x 取模效果是一样的:y : 10110010
x : 00010000
y%x : 00000010我们知道,位运算的代价比求模运算小的多,因此在进行这种计算时用位运算的话能带来更高的性能。确定桶下标的最后一步是将 key 的 hash 值对桶个数取模:hash%capacity,如果能保证 capacity 为 2 的 n 次方,那么就可以将这个操作转换为位运算。static int indexFor(int h, int length) {
return h & (length-1);
}5. 扩容-基本原理设 HashMap 的 table 长度为 M,需要存储的键值对数量为 N,如果哈希函数满足均匀性的要求,那么每条链表的长度大约为 N/M,因此查找的复杂度为 O(N/M)。为了让查找的成本降低,应该使 N/M 尽可能小,因此需要保证 M 尽可能大,也就是说 table 要尽可能大。HashMap 采用动态扩容来根据当前的 N 值来调整 M 值,使得空间效率和时间效率都能得到保证。和扩容相关的参数主要有:capacity、size、threshold 和 load_factor。参数含义capacitytable 的容量大小,默认为 16。需要注意的是 capacity 必须保证为 2 的 n 次方。size键值对数量。thresholdsize 的临界值,当 size 大于等于 threshold 就必须进行扩容操作。loadFactor装载因子,table 能够使用的比例,threshold = (int)(capacity* loadFactor)。static final int DEFAULT_INITIAL_CAPACITY = 16;
static final int MAXIMUM_CAPACITY = 1 << 30;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
transient Entry[] table;
transient int size;
int threshold;
final float loadFactor;
transient int modCount;从下面的添加元素代码中可以看出,当需要扩容时,令 capacity 为原来的两倍。void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
if (size++ >= threshold)
resize(2 * table.length);
}扩容使用 resize() 实现,需要注意的是,扩容操作同样需要把 oldTable 的所有键值对重新插入 newTable 中,因此这一步是很费时的。void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable);
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry<K,V> e = src[j];
if (e != null) {
src[j] = null;
do {
Entry<K,V> next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
}
}
}6. 扩容-重新计算桶下标在进行扩容时,需要把键值对重新计算桶下标,从而放到对应的桶上。在前面提到,HashMap 使用 hash%capacity 来确定桶下标。HashMap capacity 为 2 的 n 次方这一特点能够极大降低重新计算桶下标操作的复杂度。假设原数组长度 capacity 为 16,扩容之后 new capacity 为 32:capacity : 00010000
new capacity : 00100000对于一个 Key,它的哈希值 hash 在第 5 位:为 0,那么 hash%00010000 = hash%00100000,桶位置和原来一致;为 1,hash%00010000 = hash%00100000 + 16,桶位置是原位置 + 16。7. 计算数组容量HashMap 构造函数允许用户传入的容量不是 2 的 n 次方,因为它可以自动地将传入的容量转换为 2 的 n 次方。先考虑如何求一个数的掩码,对于 10010000,它的掩码为 11111111,可以使用以下方法得到:mask |= mask >> 1 11011000
mask |= mask >> 2 11111110
mask |= mask >> 4 11111111mask+1 是大于原始数字的最小的 2 的 n 次方。num 10010000
mask+1 100000000以下是 HashMap 中计算数组容量的代码:static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}8. 链表转红黑树从 JDK 1.8 开始,一个桶存储的链表长度大于等于 8 时会将链表转换为红黑树。9. 与 Hashtable 的比较Hashtable 使用 synchronized 来进行同步。HashMap 可以插入键为 null 的 Entry。HashMap 的迭代器是 fail-fast 迭代器。HashMap 不能保证随着时间的推移 Map 中的元素次序是不变的。ConcurrentHashMap1. 存储结构static final class HashEntry<K,V> {
final int hash;
final K key;
volatile V value;
volatile HashEntry<K,V> next;
}ConcurrentHashMap 和 HashMap 实现上类似,最主要的差别是 ConcurrentHashMap 采用了分段锁(Segment),每个分段锁维护着几个桶(HashEntry),多个线程可以同时访问不同分段锁上的桶,从而使其并发度更高(并发度就是 Segment 的个数)。Segment 继承自 ReentrantLock。static final class Segment<K,V> extends ReentrantLock implements Serializable {
private static final long serialVersionUID = 2249069246763182397L;
static final int MAX_SCAN_RETRIES =
Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;
transient volatile HashEntry<K,V>[] table;
transient int count;
transient int modCount;
transient int threshold;
final float loadFactor;
}final Segment<K,V>[] segments;默认的并发级别为 16,也就是说默认创建 16 个 Segment。static final int DEFAULT_CONCURRENCY_LEVEL = 16;2. size 操作每个 Segment 维护了一个 count 变量来统计该 Segment 中的键值对个数。transient int count;在执行 size 操作时,需要遍历所有 Segment 然后把 count 累计起来。ConcurrentHashMap 在执行 size 操作时先尝试不加锁,如果连续两次不加锁操作得到的结果一致,那么可以认为这个结果是正确的。尝试次数使用 RETRIES_BEFORE_LOCK 定义,该值为 2,retries 初始值为 -1,因此尝试次数为 3。如果尝试的次数超过 3 次,就需要对每个 Segment 加锁。static final int RETRIES_BEFORE_LOCK = 2;
public int size() {
final Segment<K,V>[] segments = this.segments;
int size;
boolean overflow;
long sum;
long last = 0L;
int retries = -1;
try {
for (;;) {
// 超过尝试次数,则对每个 Segment 加锁
if (retries++ == RETRIES_BEFORE_LOCK) {
for (int j = 0; j < segments.length; ++j)
ensureSegment(j).lock();
}
sum = 0L;
size = 0;
overflow = false;
for (int j = 0; j < segments.length; ++j) {
Segment<K,V> seg = segmentAt(segments, j);
if (seg != null) {
sum += seg.modCount;
int c = seg.count;
if (c < 0 || (size += c) < 0)
overflow = true;
}
}
// 连续两次得到的结果一致,则认为这个结果是正确的
if (sum == last)
break;
last = sum;
}
} finally {
if (retries > RETRIES_BEFORE_LOCK) {
for (int j = 0; j < segments.length; ++j)
segmentAt(segments, j).unlock();
}
}
return overflow ? Integer.MAX_VALUE : size;
}3. JDK 1.8 的改动JDK 1.7 使用分段锁机制来实现并发更新操作,核心类为 Segment,它继承自重入锁 ReentrantLock,并发度与 Segment 数量相等。JDK 1.8 使用了 CAS 操作来支持更高的并发度,在 CAS 操作失败时使用内置锁 synchronized。并且 JDK 1.8 的实现也在链表过长时会转换为红黑树。LinkedHashMap存储结构继承自 HashMap,因此具有和 HashMap 一样的快速查找特性。public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>内部维护了一个双向链表,用来维护插入顺序或者 LRU 顺序。transient LinkedHashMap.Entry<K,V> head;
transient LinkedHashMap.Entry<K,V> tail;accessOrder 决定了顺序,默认为 false,此时维护的是插入顺序。final boolean accessOrder;LinkedHashMap 最重要的是以下用于维护顺序的函数,它们会在 put、get 等方法中调用。void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }afterNodeAccess()当一个节点被访问时,如果 accessOrder 为 true,则会将该节点移到链表尾部。也就是说指定为 LRU 顺序之后,在每次访问一个节点时,会将这个节点移到链表尾部,保证链表尾部是最近访问的节点,那么链表首部就是最近最久未使用的节点。void afterNodeAccess(Node<K,V> e) {
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}afterNodeInsertion()在 put 等操作之后执行,当 removeEldestEntry() 方法返回 true 时会移除最晚的节点,也就是链表首部节点 first。evict 只有在构建 Map 的时候才为 false,在这里为 true。void afterNodeInsertion(boolean evict) {
LinkedHashMap.Entry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}removeEldestEntry() 默认为 false,如果需要让它为 true,需要继承 LinkedHashMap 并且覆盖这个方法的实现,这在实现 LRU 的缓存中特别有用,通过移除最近最久未使用的节点,从而保证缓存空间足够,并且缓存的数据都是热点数据。protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}LRU 缓存以下是使用 LinkedHashMap 实现的一个 LRU 缓存:设定最大缓存空间 MAX_ENTRIES 为 3;使用 LinkedHashMap 的构造函数将 accessOrder 设置为 true,开启 LRU 顺序;覆盖 removeEldestEntry() 方法实现,在节点多于 MAX_ENTRIES 就会将最近最久未使用的数据移除。class LRUCache<K, V> extends LinkedHashMap<K, V> {
private static final int MAX_ENTRIES = 3;
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX_ENTRIES;
}
LRUCache() {
super(MAX_ENTRIES, 0.75f, true);
}
}public static void main(String[] args) {
LRUCache<Integer, String> cache = new LRUCache<>();
cache.put(1, "a");
cache.put(2, "b");
cache.put(3, "c");
cache.get(1);
cache.put(4, "d");
System.out.println(cache.keySet());
}[3, 1, 4]WeakHashMap存储结构WeakHashMap 的 Entry 继承自 WeakReference,被 WeakReference 关联的对象在下一次垃圾回收时会被回收。WeakHashMap 主要用来实现缓存,通过使用 WeakHashMap 来引用缓存对象,由 JVM 对这部分缓存进行回收。private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V>ConcurrentCacheTomcat 中的 ConcurrentCache 使用了 WeakHashMap 来实现缓存功能。ConcurrentCache 采取的是分代缓存:经常使用的对象放入 eden 中,eden 使用 ConcurrentHashMap 实现,不用担心会被回收(伊甸园);不常用的对象放入 longterm,longterm 使用 WeakHashMap 实现,这些老对象会被垃圾收集器回收。当调用 get() 方法时,会先从 eden 区获取,如果没有找到的话再到 longterm 获取,当从 longterm 获取到就把对象放入 eden 中,从而保证经常被访问的节点不容易被回收。当调用 put() 方法时,如果 eden 的大小超过了 size,那么就将 eden 中的所有对象都放入 longterm 中,利用虚拟机回收掉一部分不经常使用的对象。public final class ConcurrentCache<K, V> {
private final int size;
private final Map<K, V> eden;
private final Map<K, V> longterm;
public ConcurrentCache(int size) {
this.size = size;
this.eden = new ConcurrentHashMap<>(size);
this.longterm = new WeakHashMap<>(size);
}
public V get(K k) {
V v = this.eden.get(k);
if (v == null) {
v = this.longterm.get(k);
if (v != null)
this.eden.put(k, v);
}
return v;
}
public void put(K k, V v) {
if (this.eden.size() >= size) {
this.longterm.putAll(this.eden);
this.eden.clear();
}
this.eden.put(k, v);
}
}总结本文介绍了Java容器的基本知识,其中包括容器的使用方法和注意事项。虽然这些知识已经足够入门,但要真正掌握Java容器,建议深入了解容器的内部实现方式。建议多查阅Java容器的API和源码,学习容器的算法和数据结构。当你能够自己动手实现这些容器时,才能真正掌握Java容器,并在使用Java容器时更加得心应手。在学习Java容器时,需要注意的是,不同的容器适用于不同的场景,我们需要根据自己的实际需求选择合适的容器。总之,学习Java容器是Java开发者必备的技能之一,只有掌握了Java容器的使用和实现方式,才能在开发中更加得心应手,提高开发效率和代码质量。最后为了方便其他设备和平台的小伙伴观看往期文章,链接奉上:牛客,知乎,开源中国,CSDN,思否,掘金,InfoQ,简书,博客园,慕课,51CTO,helloworld,腾讯开发者社区,阿里开发者社区看完如果觉得有帮助,帮忙点个赞👍
wordpress无法安装插件——安装失败:无法复制文件
我之前提出过相似的问题,但是回答中并没能解决。我按照回答中的方法去检查了,现在有补充信息,但是我没法修改当时的问题,所以我来重新提交一个新的问题。
#操作环境 nginx version: nginx/1.16.1
mysql Ver 8.0.26 for Linux on x86_64 (Source distribution)
PHP 7.3.20 (cli) (built: Jul 7 2020 07:53:49) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.20, Copyright (c) 1998-2018 Zend Technologies
#当前问题
当前wordpress下面的目录权限都被我修改成了777 但是wordpress目录权限还是755 wordpress站点内显示可写 安装显示无法复制文件 plugins文件夹中并没有这个插件的目录
当我调整wordpress及下面的目录全部改为755权限时
问题依然是无法复制文件。
PHP最长的响应时间设置为30s,内存限制为512mb
文件权限限制为744 #我希望达成的效果
能够通过站内正常的安装插件目录权限不设置为777
#最起码想要了解到的东西可能导致这种情况的原因完善的排查方向