源码分析:SpringBoot启动流程分析原理
我们都知道SpringBoot自问世以来,一直有一个响亮的口号”约定优于配置”,其实一种按约定编程的软件设计范式,目的在于减少软件开发人员在工作中的各种繁琐的配置,我们都知道传统的SSM框架的组合,会伴随着大量的繁琐的配置;稍有不慎,就可能各种bug,被人发现还以为我们技术很菜。而SpringBoot的出现不仅大大提高的开发人员的效率,还能避免由于”手抖”带来的配置错误。
文件上传与魔数
Elasticsearch 安全功能入门
1. 前言
**从 Elastic Stack 6.8 和 7.1 开始,Elasticsearch在默认分发包中免费提供多项安全功能,例如 TLS 加密通信、基于角色的访问控制 (RBAC)**,等等。在本文中,我将会演示如何启用这些功能来确保您的 Elasticsearch 集群的安全。
实际演示中,我将会在两台centos7上各自创建一个一节点 Elasticsearch 集群并进行安全设置。要实现这一点,我们首先需要在两个节点之间配置 TLS 通信。然后,我会为 Kibana 实例启用安全功能。再然后,我会在 Kibana 中配置基于角色的访问控制,从而确保用户只能看到他们获授权能够看到的内容。
尽管关于安全功能的运行过程还有很多内容,但现在我们仅会介绍入门所需知识。
2. 安装 Elasticsearch 和 Kibana
略
3. 传输层配置 TLS 和身份验证
3.1. 在 Elasticsearch 主节点上配置 TLS
我要做的第一件事是生成证书,通过这些证书便能允许节点安全地通信。您可以使用企业 CA 来完成这一步骤,但是在此演示中,我将会使用一个名为 elasticsearch-certutil 的命令,通过这一命令,就无需担心证书通常带来的任何困扰,便能完成这一步。
bin/elasticsearch-certutil cert -out config/elastic-certificates.p12 -pass ""
如果您使用密码保护了节点证书的安全,请将密码添加到您的Elasticsearch密钥库中:
bin/elasticsearch-certutil cert -out config/elastic-certificates.p12 -pass "testpassword"
bin/elasticsearch-keystore add xpack.security.transport.ssl.keystore.secure_password
bin/elasticsearch-keystore add xpack.security.transport.ssl.truststore.secure_password
接下来,使用您最常用的文本编辑器打开文件 config/elasticsearch.yaml。将下列代码行粘贴到文件末尾。
xpack.security.enabled: true
xpack.security.transport.ssl.enabled: true
xpack.security.transport.ssl.verification_mode: certificate
xpack.security.transport.ssl.keystore.path: elastic-certificates.p12
xpack.security.transport.ssl.truststore.path: elastic-certificates.p12
保存文件,现在我们便可以启动主节点了。运行命令 bin/elasticsearch。这一可执行文件必须保持运行,现在可以将此终端放在一边。
3.2. Elasticsearch 集群密码
elasticsearch-setup-passwords 官方文档
注意:elasticsearch-setup-passwords 这个命令只能使用一次。
# 生成随机密码
bin/elasticsearch-setup-passwords auto
# 手动定义密码(建议使用)
bin/elasticsearch-setup-passwords interactive
但是如果完全忘记了 Elasticsearch 的超级用户的密码,请看
3.3. 在从节点上配置 TLS
复制证书文件,然后将 xpack.security.* 键设置为与主节点一模一样。然后通过运行 bin/elasticsearch 来启动节点。我们将看到其加入集群。而且,如果看一下主节点的终端窗口,我们会看到有一条消息显示已有一个节点加入集群。现在,我们的两节点集群便开始运行了。
3.4. 在 Kibana 中实现安全性
在 kibana 安装目录中编辑 config/kibana.yml到类似下面的代码行
#elasticsearch.username: "kibana"
#elasticsearch.password: "testpassword"
对 username 和 password 字段取消注释,方法是删除代码行起始部分的 # 符号。将 “user” 更改为 “kibana”,然后将 “pass” 更改为 setup-passwords 命令告诉我们的任何 Kibana 密码。保存文件,然后我们便可通过运行 bin/kibana 启动 Kibana 了。
ElasticSearch 安装中文分词插件
1. 安装 Elasticsearch
略
2. 安装IK分词器插件
进入 Elasticsearch 安装目录
使用 elasticsearch-plugin 安装插件
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.1.1/elasticsearch-analysis-ik-7.1.1.zip
安装步骤截图如下

Tips: 如果是集群,则每个 es 节点都要安装该插件
3. 确认安装插件成功
./bin/elasticsearch-plugin list

4. 移除插件
./bin/elasticsearch-plugin remove analysis-ik
5. 自定义中文词库
5.1. 在{es}/plugins/ik/config下,创建:
| 1 | vim custom.dic | 
5.2. 并且添加内容:
| 1 | 雨女无瓜 | 
5.3.配置自定义扩展词典
| 1 | <entry key="ext_dict">custom.dic</entry> | 
5.4. 重启
Spring boot 中 @Scheduled 不起作用的一个解决办法
Spring boot 中 @Scheduled 不起作用的一个解决办法
在 spring boot 应用中添加定时任务,按照网上的资料却怎么都不能启动,都说是缺少了 @EnableScheduling,我在加上了后却任然启动不了。
最后是这样解决的:主要是新增一个 org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler  的 bean
| 1 | @Slf4j | 
cron表达式常用例子
Mysql中日期比较大小的方法
假如有个表commodity有个字段add_time,它的数据类型为datetime,有人可能会这样写sql:
| 1 | select * from product where add_time = '2013-01-12'; | 
这种语句,如果你存储的格式是YY-mm-dd这样,那么OK,
如果你存储的格式是:2018-01-12 23:23:56 这种就悲剧了,此时你可以用 DATE() 函数用来返回日期的部分;sql如下处理:
| 1 | select * from product where Date(add_time) = '2018-01-12' | 
- 如果你要查询2017年1月份加入的产品呢?
| 1 | select * from product where date(add_time) between '2013-01-01' and '2013-01-31' | 
或者: 还可以这样写:
| 1 | select * from product where Year(add_time) = 2013 and Month(add_time) = 1 | 
- 其date_col的值是在最后30天以内:
| 1 | SELECT * FROM commodity WHERE TO_DAYS( NOW() ) - TO_DAYS( date_col ) <= 30; | 
- DAYOFWEEK(date) : 返回日期date的星期索引(1=星期天, 7=星期六) 这些索引值对应于ODBC标准。
| 1 | select DAYOFWEEK('1998-02-03'); -> 3 | 
- WEEKDAY(date) : 返回date的星期索引 (0=星期一, 6= 星期天)
| 1 | select WEEKDAY('1997-10-04 22:23:00'); -> 5 | 
- DAYOFMONTH(date) : 返回date的月份中日期,在1到31范围内。
| 1 | select DAYOFMONTH('1998-02-03'); -> 3 | 
- DAYOFYEAR(date) : 返回date在一年中的日数, 在1到366范围内。
| 1 | select DAYOFYEAR('1998-02-03'); -> 34 | 
- MONTH(date) : 返回date的月份,范围1到12。
| 1 | select MONTH('1998-02-03'); -> 2 | 
- DAYNAME(date) : 返回date的星期名字。
| 1 | select DAYNAME("1998-02-05"); -> 'Thursday' | 
- MONTHNAME(date) : 返回date的月份名字。
| 1 | select MONTHNAME("1998-02-05"); -> 'February' | 
- QUARTER(date) : 返回date一年中的季度,范围1到4。
| 1 | select QUARTER('98-04-01'); -> 2 | 
这是做统计数据时候用了的sql.
查询今天、昨天、一周内、8周、12周等数据 直接在sql写时间查询。 避免时区转化。数据库我们使用的UTC时间。
- Today - 1 
 2- SELECT str_to_date(curdate(),'%Y-%m-%d %H:%i:%s') as todayBegin; 
 SELECT date_add(date_add(str_to_date(curdate(),'%Y-%m-%d %H:%i:%s'),interval 1 DAY),INTERVAL -1 SECOND) as todayEnd;
- 昨天 - ``sql 
 SELECT str_to_date(date_sub(curdate(), interval 1 day),’%Y-%m-%d %H:%i:%s’) as yestodayBegin;
 SELECT date_add(date_add(str_to_date(date_sub(curdate(), interval 1 day),’%Y-%m-%d %H:%i:%s’),interval 1 DAY),INTERVAL -1 SECOND) as yestodayEnd;- 1 
 2
 3
 4
 5
 6
 - 最近7天 Last Seven days
 ```sql
 SELECT str_to_date(date_sub(curdate(), interval 6 day),'%Y-%m-%d %H:%i:%s') as lastSevenDaysBegin;
 SELECT date_add(date_add(str_to_date(curdate(),'%Y-%m-%d %H:%i:%s'),interval 1 DAY),INTERVAL -1 SECOND) as todayEnd;
- 最近14天 Last Fourteen days - 1 
 2- SELECT str_to_date(date_sub(curdate(), interval 13 day),'%Y-%m-%d %H:%i:%s') as lastFourteenDaysBegin; 
 SELECT date_add(date_add(str_to_date(curdate(),'%Y-%m-%d %H:%i:%s'),interval 1 DAY),INTERVAL -1 SECOND) as todayEnd;
- 最近30天 Last Thirty days - 1 
 2- SELECT str_to_date(date_sub(curdate(), interval 29 day),'%Y-%m-%d %H:%i:%s') as lastThirtyDaysBegin; 
 SELECT date_add(date_add(str_to_date(curdate(),'%Y-%m-%d %H:%i:%s'),interval 1 DAY),INTERVAL -1 SECOND) as todayEnd;
- 最近8周 Last eight weeks - 1 
 2- SELECT str_to_date(date_sub(curdate(), interval 8 week),'%Y-%m-%d %H:%i:%s') as lastEightWeeksBegin; 
 SELECT date_add(date_add(str_to_date(curdate(),'%Y-%m-%d %H:%i:%s'),interval 1 DAY),INTERVAL -1 SECOND) as todayEnd;
- 12 最近12周 Last twelve weeks - 1 
 2- SELECT str_to_date(date_sub(curdate(), interval 12 week),'%Y-%m-%d %H:%i:%s') as lastTwelveWeeksBegin; 
 SELECT date_add(date_add(str_to_date(curdate(),'%Y-%m-%d %H:%i:%s'),interval 1 DAY),INTERVAL -1 SECOND) as todayEnd;
- 最近3月 Last three month - ``sql 
 SELECT str_to_date(date_sub(curdate(), interval 3 month),’%Y-%m-%d %H:%i:%s’) as lastThreeMonthBegin;- 1 
 2
 3
 4
 5
 - 未来3天 Three day later
 ```sql
 SELECT str_to_date(date_sub(curdate(), interval -3 day),'%Y-%m-%d %H:%i:%s') as threeDaysLaterEnd;
- 未来3月 Three months later - 1 - SELECT str_to_date(date_sub(curdate(), interval -3 month),'%Y-%m-%d %H:%i:%s') as threeMonthsLaterEnd; 
- 取一天的开始时间 - 1 - SELECT str_to_date(DATE_FORMAT('2018-03-03','%Y-%m-%d'),'%Y-%m-%d %H:%i:%s'); 
- 取第二天的开始时间 - 1 
 2- select DATE_ADD(str_to_date(DATE_FORMAT(NOW(),'%Y-%m-%d'),'%Y-%m-%d %H:%i:%s'),INTERVAL 1 DAY); 
 select DATE_ADD(str_to_date(DATE_FORMAT('2018-03-03','%Y-%m-%d'),'%Y-%m-%d %H:%i:%s'),INTERVAL 1 DAY);
- 取一天的结束时间 - 1 
 2- select DATE_ADD(DATE_ADD(str_to_date(DATE_FORMAT(NOW(),'%Y-%m-%d'),'%Y-%m-%d %H:%i:%s'),INTERVAL 1 DAY),INTERVAL -1 SECOND); 
 select DATE_ADD(DATE_ADD(str_to_date(DATE_FORMAT('2018-03-03','%Y-%m-%d'),'%Y-%m-%d %H:%i:%s'),INTERVAL 1 DAY),INTERVAL -1 SECOND);
springboot使用rabbitmq
Message Broker与AMQP简介
Message Broker是一种消息验证、传输、路由的架构模式,其设计目标主要应用于下面这些场景:
- 消息路由到一个或多个目的地
- 消息转化为其他的表现方式
- 执行消息的聚集、消息的分解,并将结果发送到他们的目的地,然后重新组合相应返回给消息用户
- 调用Web服务来检索数据
- 响应事件或错误
- 使用发布-订阅模式来提供内容或基于主题的消息路由
AMQP是Advanced Message Queuing Protocol的简称,它是一个面向消息中间件的开放式标准应用层协议。AMQP定义了这些特性:
- 消息方向
- 消息队列
- 消息路由(包括:点到点和发布-订阅模式)
- 可靠性
- 安全性
RabbitMQ
RabbitMQ就是以AMQP协议实现的一种中间件产品,它可以支持多种操作系统,多种编程语言,几乎可以覆盖所有主流的企业级技术平台。
ubuntu 安装
在Ubuntu中,我们可以使用APT仓库来进行安装
- 安装Erlang,执行: - apt-get install erlang
- 执行下面的命令,新增APT仓库到 - /etc/apt/sources.list.- 1 - echo 'deb http://www.rabbitmq.com/debian/ testing main' | sudo tee /etc/apt/sources.list.d/rabbitmq.list 
- 更新APT仓库的package list,执行 - sudo apt-get update命令
- 安装Rabbit Server,执行 - sudo apt-get install rabbitmq-server命令
- 开启Web管理插件 - 1 - rabbitmq-plugins enable rabbitmq_management 
docker 安装
- 查找镜像 - 1 - docker search rabbitmq 
- 拉取镜像 - 1 - docker pull rabbitmq 
- 启动镜像 - 1 - docker run -d -p 15672:15672 -p 5672:5672 -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin --name rabbitmq --hostname=my_rabbitmq rabbitmq:latest - 参数解释: - –hostname:指定容器主机名称
- –name:指定容器名称
- -p:将mq端口号映射到本地
- 15672 :表示 RabbitMQ 控制台端口号,可以在浏览器中通过控制台来执行 RabbitMQ 的相关操作。
- 5672 : 表示 RabbitMQ 所监听的 TCP 端口号,应用程序可通过该端口与 RabbitMQ 建立 TCP 连接,完成后续的异步消息通信
- RABBITMQ_DEFAULT_USER:用于设置登陆控制台的用户名,设置为 admin, 默认是guest
- RABBITMQ_DEFAULT_PASS:用于设置登陆控制台的密码,设置为 admin, 默认是guest
 - 容器启动成功后,可以在浏览器输入地址:http://localhost:15672/ - ps:RabbitMQ出于安全的考虑,默认是只能访问localhost:15762访问的,如果想用其他ip,是需要自己配置的。 
WEB 界面

rabbitmq 介绍
spring boot 集成 RabbitMQ
- maven 依赖 - 1 
 2
 3
 4- <dependency> 
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-amqp</artifactId>
 </dependency>
- 配置文件 - 1 
 2
 3
 4
 5
 6
 7
 8- spring: 
 application:
 name: spring-boot-rabbitmq
 rabbitmq:
 host: localhost
 port: 5672
 username: guest
 password: guest
- 简单发送字符串 - 定义队列
 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22- import org.springframework.amqp.core.Queue; 
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 /**
 * @author fengxuechao
 * @version 0.1
 * @date 2019/9/12
 */
 @Configuration
 public class RabbitConfig {
 @Bean
 public Queue queueHello() {
 return new Queue("hello");
 }
 @Bean
 public Queue queueObject() {
 return new Queue("object");
 }
 }- 定义发送者
 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25- import org.springframework.amqp.core.AmqpTemplate; 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 /**
 * @author fengxuechao
 * @version 0.1
 * @date 2019/9/12
 */
 @Component
 public class HelloSender {
 @Autowired
 private AmqpTemplate rabbitTemplate;
 public void send(int i) {
 String context = "hello " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")) + ":" + i;
 System.out.println("Sender : " + context);
 this.rabbitTemplate.convertAndSend("hello", context);
 }
 }- 定义接收者
 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19- import org.springframework.amqp.rabbit.annotation.RabbitHandler; 
 import org.springframework.amqp.rabbit.annotation.RabbitListener;
 import org.springframework.stereotype.Component;
 /**
 * @author fengxuechao
 * @version 0.1
 * @date 2019/9/12
 */
 @Component
 @RabbitListener(queues = "hello")
 public class HelloReceiver2 {
 @RabbitHandler
 public void process(String hello) {
 System.out.println("Receiver 2 : " + hello);
 }
 }- 测试
 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28- import org.junit.Test; 
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.test.annotation.Repeat;
 import org.springframework.test.context.junit4.SpringRunner;
 import static org.junit.Assert.*;
 /**
 * @author fengxuechao
 * @version 0.1
 * @date 2019/9/12
 */
 @RunWith(SpringRunner.class)
 @SpringBootTest
 public class HelloSenderTest {
 @Autowired
 private HelloSender sender;
 @Test
 public void send() {
 for (int i=0;i<10;i++){
 sender.send(i);
 }
 }
 }- 控制台打印结果 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10- Receiver 2 : hello 2019-09-12 17:27:42.744:0 
 Receiver 2 : hello 2019-09-12 17:27:42.744:1
 Receiver 2 : hello 2019-09-12 17:27:42.744:2
 Receiver 2 : hello 2019-09-12 17:27:42.744:3
 Receiver 2 : hello 2019-09-12 17:27:42.744:4
 Receiver 2 : hello 2019-09-12 17:27:42.744:5
 Receiver 2 : hello 2019-09-12 17:27:42.744:6
 Receiver 2 : hello 2019-09-12 17:27:42.744:7
 Receiver 2 : hello 2019-09-12 17:27:42.744:8
 Receiver 2 : hello 2019-09-12 17:27:42.744:9
- 发送对象 - Spring Boot 完美的支持对象的发送和接收,不需要额外的配置。 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28- @Bean 
 public Queue queueObject() {
 return new Queue("object");
 }
 @Component
 @RabbitListener(queues = "object")
 public class ObjectReceiver {
 @RabbitHandler
 public void process(User user) {
 System.out.println("Receiver object : " + user);
 }
 }
 @Component
 public class ObjectSender {
 @Autowired
 private AmqpTemplate rabbitTemplate;
 public void send(User user) {
 System.out.println("Sender object: " + user.toString());
 this.rabbitTemplate.convertAndSend("object", user);
 }
 }- 结果如下: - 1 
 2- Sender object: User{name='neo', pass='123456'} 
 Receiver object : User{name='neo', pass='123456'}
zuul超时时间和重试设置
Zuul 网关的超时设置
Zuul(网关)的超时时间需要设置zuul、hystrix、ribbon等三部分:
zuul超时设置
#zuul超时设置
#默认1000
zuul.host.socket-timeout-millis=2000
#默认2000
zuul.host.connect-timeout-millis=4000
hystrix超时设置
#熔断器启用
feign.hystrix.enabled=true
hystrix.command.default.execution.timeout.enabled=true
#断路器的超时时间,下级服务返回超出熔断器时间,即便成功,消费端消息也是TIMEOUT,所以一般断路器的超时时间需要大于ribbon的超时时间,ribbon是真正去调用下级服务
#当服务的返回时间大于ribbon的超时时间,会触发重试
#断路器的超时时间默认为1000ms,太小了
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=60000
# 为某个特定的服务配熔断时间
hystrix.command.service-a.execution.isolation.thread.timeoutInMilliseconds=60000
#断路器详细设置
#当在配置时间窗口内达到此数量的失败后,进行短路。默认20个)
#hystrix.command.default.circuitBreaker.requestVolumeThreshold=20
#短路多久以后开始尝试是否恢复,默认5s)
#hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds=5
#出错百分比阈值,当达到此阈值后,开始短路。默认50%)
#hystrix.command.default.circuitBreaker.errorThresholdPercentage=50%
ribbon超时设置
#ribbon请求连接的超时时间,限制3秒内必须请求到服务,并不限制服务处理的返回时间
ribbon.ConnectTimeout=3000
ribbon.SocketTimeout=5000
#请求处理的超时时间 下级服务响应最大时间,超出时间消费方(路由也是消费方)返回timeout
ribbon.ReadTimeout=5000
# 单独设置某个服务的超时时间,会覆盖其他的超时时间限制,服务的名称以注册中心页面显示的名称为准,超时时间不可大于断路器的超时时间
service-a.ribbon.ReadTimeout=50000
service-a.ribbon.ConnectTimeout=50000
重试机制
#重试机制
#该参数用来开启重试机制,默认是关闭
spring.cloud.loadbalancer.retry.enabled=true
#对所有操作请求都进行重试
ribbon.OkToRetryOnAllOperations=true
#对当前实例的重试次数
ribbon.MaxAutoRetries=1
#切换实例的重试次数
ribbon.MaxAutoRetriesNextServer=1
#根据如上配置,当访问到故障请求的时候,它会再尝试访问一次当前实例(次数由MaxAutoRetries配置),
#如果不行,就换一个实例进行访问,如果还是不行,再换一次实例访问(更换次数由MaxAutoRetriesNextServer配置),
#如果依然不行,返回失败信息。