源码分析: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
2SELECT 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
2SELECT 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
2SELECT 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
2SELECT 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
2SELECT 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
2select 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
2select 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
8spring:
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
22import 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
25import 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
19import 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
28import 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
10Receiver 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
2Sender 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配置),
#如果依然不行,返回失败信息。