hello云胜

技术与生活

0%

架构师不仅需要关注当下流行什么,要选择什么方案、用什么开源框架。一个以架构师为 职业的人,更需要有战略意图和思考力,比如:

  • 在一个架构活动中到底应该关注什么?干预什么?
  • 如何通过架构方案为团队或企业创造价值?
  • 如何在各种资源条件的制约下,去实现架构目标?
  • 如何通过价值创造让自己变得不可或缺

我有一个坚信的理念:要想在架构师这个职业上超越别人,你必须要尽早建立好你的架构 师成长战略。否则你靠运气赚来的架构机 会,也必然会因为你的实力不济而败干净。

借用企管学者哈莫与帕哈拉德(Hamel & Prahalad)在Strategic Intent 这篇文章里提到的一个概念:“过去 20 年中达到世界顶尖地位的公司,每一家都有战略意 图(Strategic Intent)”。

所谓战略意图,就是拥有与其资源和能力极不相称的雄心壮志。你把公司换成马斯克、乔 布斯和蔡志忠等人,或者你身上,这句话同样适用。每个想达到顶峰的人,都应该有自己 的战略意图。

靠记忆和技能学习,是成不了一个好架构师的。真正的架构师 成长,主要靠思考力的提升。

影响架构活动成败的因素,主要有六个,分别是:目标、输入、输出、商业和技术环境、 文化环境以及架构活动本身。

TiDB是什么

TiDB 是一个分布式 NewSQL 数据库。它支持水平弹性扩展、ACID 事务、标准 SQL、MySQL 语法和 MySQL 协议,具有数据强一致的高可用特性,是一个不仅适合 OLTP 场景还适OLAP 场景的混合数据库。

TiDB怎么来的?

开源分布式缓存服务 Codis 的作者,PingCAP 联合创始人& CTO ,资深 infrastructure 工程师的黄东旭,擅长分布式存储系统的设计与实现,开源狂热分子的技术大神级别人物。即使在互联网如此繁荣的今天,在数据库这片边界模糊且不确定地带,他还在努力寻找确定性的实践方向。

2012 年底,他看到 Google 发布的两篇论文,得到了很大的触动,这两篇论文描述了 Google 内部使用的一个海量关系型数据库 F1/Spanner ,解决了关系型数据库、弹性扩展以及全球分布的问题,并在生产中大规模使用。“如果这个能实现,对数据存储领域来说将是颠覆性的”,黄东旭为完美方案的出现而兴奋, PingCAP 的 TiDB 在此基础上诞生了。

TiDB架构

TiDB在整体架构基本是参考 Google Spanner 和 F1 的设计,上分两层为 TiDBTiKV 。 TiDB 对应的是 Google F1, 是一层无状态的 SQL Layer ,兼容绝大多数 MySQL 语法,对外暴露 MySQL 网络协议,负责解析用户的 SQL 语句,生成分布式的 Query Plan,翻译成底层 Key Value 操作发送给 TiKV , TiKV 是真正的存储数据的地方,对应的是 Google Spanner ,是一个分布式 Key Value 数据库,支持弹性水平扩展,自动的灾难恢复和故障转移(高可用),以及 ACID 跨行事务。值得一提的是 TiKV 并不像 HBase 或者 BigTable 那样依赖底层的分布式文件系统,在性能和灵活性上能更好,这个对于在线业务来说是非常重要。

img

所以一套集群是又这样的3类角色共同组建而成。每个部分的解释如下:

TiDB Server

TiDB Server 负责接收 SQL 请求,处理 SQL 相关的逻辑,并通过 PD 找到存储计算所需数据的 TiKV 地址,与 TiKV 交互获取数据,最终返回结果。 TiDB Server 是无状态的,其本身并不存储数据,只负责计算,可以无限水平扩展,可以通过负载均衡组件(如LVS、HAProxy 或 F5)对外提供统一的接入地址。

PD Server

Placement Driver (简称 PD) 是整个集群的管理模块,其主要工作有三个: 一是存储集群的元信息(某个 Key 存储在哪个 TiKV 节点);二是对 TiKV 集群进行调度和负载均衡(如数据的迁移、Raft group leader 的迁移等);三是分配全局唯一且递增的事务 ID。 PD 是一个集群,需要部署奇数个节点,一般线上推荐至少部署 3 个节点。

TiKV Server

TiKV Server 负责存储数据,从外部看 TiKV 是一个分布式的提供事务的 Key-Value 存储引擎。存储数据的基本单位是 Region,每个 Region 负责存储一个 Key Range (从 StartKey 到 EndKey 的左闭右开区间)的数据,每个 TiKV 节点会负责多个 Region 。TiKV 使用 Raft 协议做复制,保持数据的一致性和容灾。副本以 Region 为单位进行管理,不同节点上的多个 Region 构成一个 Raft Group,互为副本。数据在多个 TiKV 之间的负载均衡由 PD 调度,这里也是以 Region 为单位进行调度

TiDB开发语言

在 TiDB 研发语言的选择过程中,放弃了 Java 而采用 Go 。TiDB整个项目分为两层,TiDB 作为 SQL 层,采用 Go 语言开发, TiKV 作为下边的分布式存储引擎,采用 Rust 语言开发。在架构上确实类似 FoundationDB,也是基于两层的结构。 FoundationDB 的 SQL Layer 采用 Java ,底层是 C++ ,不过在去年,被 Apple 收购了。 在选择编程语言并没有融入太多的个人喜好偏向, SQL 层选择 Go 相对 Java 来说:

第一是 他们团队的背景使用 Go 的开发效率更高,而且性能尚可,尤其对于高并发程序而言,可以使用 goroutine / channel 等工具用更少的代码写出正确的程序;

第二是 在标准库中很多包对网络程序开发非常友好,这个对于一个分布式系统来说非常重要;

第三是 在存储引擎底层对于性能要求很高,Go 毕竟是一个带有 GC 和 Runtime 的语言,在 TiKV 层可以选择的方案并不多,过去基本只有 C 或 C++,不过近两年随着 Rust 语言的成熟,又在经过长时间的思考和大量实验,最终他们团队选择了 Rust( Rust是Mozilla开发的注重安全、性能和并发性的编程语言。“Rust”,由web语言的领军人物Brendan Eich(js之父),Dave Herman以及Mozilla公司的Graydon Hoare 合力开发。)。

与 MySQL 兼容性对比

TiDB 支持包括跨行事务,JOIN 及子查询在内的绝大多数 MySQL 的语法,用户可以直接使用现有的 MySQL 客户端连接。如果现有的业务已经基于 MySQL 开发,大多数情况不需要修改代码即可直接替换单机的 MySQL。

包括现有的大多数 MySQL 运维工具(如 PHPMyAdmin, Navicat, MySQL Workbench 等),以及备份恢复工具(如 mysqldump, mydumper/myloader)等都可以直接使用。

不过一些特性由于在分布式环境下没法很好的实现,目前暂时不支持或者是表现与 MySQL 有差异。

一些 MySQL 语法在 TiDB 中可以解析通过,但是不会做任何后续的处理,例如 Create Table 语句中 Engine 以及 Partition 选项,都是解析并忽略。更多兼容性差异请参考具体的文档。

不支持的特性

存储过程

视图

触发器

自定义函数

外键约束

全文索引

空间索引

非 UTF8 字符集

TiDB 基本操作

下面具体介绍 TiDB 中基本的增删改查操作。

创建、查看和删除数据库

1
2
3
4
5
6
7
8
使用 CREATE DATABASE 语句创建数据库。语法如下:
CREATE DATABASE db_name [options];
例如,要创建一个名为 samp_db 的数据库,可使用以下语句:
CREATE DATABASE IF NOT EXISTS samp_db;
使用 SHOW DATABASES 语句查看数据库:
SHOW DATABASES;
使用 DROP DATABASE 语句删除数据库,例如:
DROP DATABASE samp_db;

创建、查看和删除表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
使用 CREATE TABLE 语句创建表。语法如下:
CREATE TABLE table_name column_name data_type constraint;
例如:
CREATE TABLE person (
number INT(11),
name VARCHAR(255),
birthday DATE
);
如果表已存在,添加 IF NOT EXISTS 可防止发生错误:
CREATE TABLE IF NOT EXISTS person (
number INT(11),
name VARCHAR(255),
birthday DATE
);
使用 SHOW CREATE 语句查看建表语句。例如:
SHOW CREATE table person;
使用 SHOW FULL COLUMNS 语句查看表的列。 例如:
SHOW FULL COLUMNS FROM person;
使用 DROP TABLE 语句删除表。例如:
DROP TABLE person;
或者
DROP TABLE IF EXISTS person;
使用 SHOW TABLES 语句查看数据库中的所有表。例如:
SHOW TABLES FROM samp_db;

创建、查看和删除索引

1
2
3
4
5
6
7
8
9
10
11
12
13
对于值不唯一的列,可使用 CREATE INDEX 或 ALTER TABLE 语句。例如:
CREATE INDEX person_num ON person (number);
或者
ALTER TABLE person ADD INDEX person_num (number);
对于值唯一的列,可以创建唯一索引。例如:
CREATE UNIQUE INDEX person_num ON person (number);
或者
ALTER TABLE person ADD UNIQUE person_num on (number);
使用 SHOW INDEX 语句查看表内所有索引:
SHOW INDEX from person;
使用 ALTER TABLE 或 DROP INDEX 语句来删除索引。与 CREATE INDEX 语句类似,DROP INDEX 也可以嵌入 ALTER TABLE 语句。例如:
DROP INDEX person_num ON person;
ALTER TABLE person DROP INDEX person_num;

增删改查数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
使用 INSERT 语句向表内插入数据。例如:
INSERT INTO person VALUES("1","tom","20170912");
使用 SELECT 语句检索表内数据。例如:
SELECT * FROM person;
+--------+------+------------+
| number | name | birthday |+--------+------+------------+
| 1 | tom | 2017-09-12 |+--------+------+------------+
使用 UPDATE 语句修改表内数据。例如:
UPDATE person SET birthday='20171010' WHERE name='tom';
SELECT * FROM person;
+--------+------+------------+
| number | name | birthday |+--------+------+------------+
| 1 | tom | 2017-10-10 |+--------+------+------------+
使用 DELETE 语句删除表内数据:
DELETE FROM person WHERE number=1;
SELECT * FROM person;
Empty set (0.00 sec)

创建、授权和删除用户

1
2
3
4
5
6
7
8
使用 CREATE USER 语句创建一个用户 tiuser,密码为 123456:
CREATE USER 'tiuser'@'localhost' IDENTIFIED BY '123456';
授权用户 tiuser 可检索数据库 samp_db 内的表:
GRANT SELECT ON samp_db.* TO 'tiuser'@'localhost';
查询用户 tiuser 的权限:
SHOW GRANTS for tiuser@localhost;
删除用户 tiuser:
DROP USER 'tiuser'@'localhost';

TiDB资料

TiDB中文简介(墙裂推荐)

https://pingcap.com/docs-cn

TiDB最佳实践等PPT

https://eyun.baidu.com/s/3huniXE0#sharelink/path=%2F

开源项目地址

https://github.com/pingcap/tidb

TiDB 部署指导

https://github.com/pingcap/docs-cn/blob/master/op-guide/binary-deployment.md#%E5%8D%95%E8%8A%82%E7%82%B9%E6%96%B9%E5%BC%8F%E5%BF%AB%E9%80%9F%E9%83%A8%E7%BD%B2

TiDB整体架构

https://github.com/pingcap/docs-cn/blob/master/overview.md#tidb-%E6%95%B4%E4%BD%93%E6%9E%B6%E6%9E%84

TiDB:支持 MySQL 协议的分布式数据库解决方案

http://www.sohu.com/a/55958574_255

Skywalking配置告警

在skywalking目录的config目录下的alarm-setings.yml文件中进行配置。

我前面使用docker部署的skywalking

docker下修改

docker ps 查看下skywalking-oap容器的id

1
docker exec -it 容器id bash

进入oap容器

1
2
3
4
5
6
7
8
bash-5.0# ls
LICENSE README.txt cli docker-entrypoint.sh ext-libs oap-libs
NOTICE bin config ext-config licenses tools
bash-5.0# cd config/
bash-5.0# ls
alarm-settings-sample.yml application.yml endpoint-name-grouping.yml gateways.yml meter-analyzer-config otel-oc-rules ui-initialized-templates
alarm-settings.yml component-libraries.yml fetcher-prom-rules log4j2.xml oal service-apdex-threshold.yml

修改alarm-settings.yml文件

重启容器

配置规则

触发条件:在一定的时间段内满足告警条件,通过webhook接口对外发送告警信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
rules:
# Rule unique name, must be ended with `_rule`.
endpoint_percent_rule:
# Metrics value need to be long, double or int
metrics-name: endpoint_percent
threshold: 75
op: <
# The length of time to evaluate the metrics
period: 10
# How many times after the metrics match the condition, will trigger alarm
count: 3
# How many times of checks, the alarm keeps silence after alarm triggered, default as same as period.
silence-period: 10
message: Successful rate of endpoint {name} is lower than 75%
  • 规则名:必须唯一,必须以_rule结尾。message中的{name}会替换成该名字
  • metrics-name:指标名。oal脚本中的指标名。
  • threshold:阈值。单个指标的很好理解。对于多个值指标,例如百分比percentile,阈值是一个数组。像value1 value2 value3 value4 value5这样描述. 每个值可以作为度量中每个值的阈值。如果不想通过此值或某些值触发警报,则将值设置为 -.
    例如在百分比中,value1是P50的阈值,且 -,-,value3, value4, value5的意思是,没有阈值的P50和P75百分位告警规则
  • op:操作符,目前支持>>=<<==
  • period:周期窗口。
  • count:计数次数或时间。在周期窗口内,如果超过阈值的次数达到设定的这个count,触发告警
  • silence-period:静默时间。触发告警后,多少时间保持静默,不要告警。
  • message:告警信息

默认规则

alarm-setings.yml文件中已经配置了7个默认规则

1.最近 3 分钟内服务平均响应时间超过 1 秒。

2.服务成功率在最近 2 分钟内低于80%。

3.服务响应时间在最近 3 分钟内低于 1000 毫秒.

4.服务实例在最近 2 分钟内的平均响应时间超过 1 秒。

5.端点平均响应时间在最近 2 分钟内超过1秒。

6.数据库访问平均响应时间在过去 2 分钟内超过 1 秒。

7.端点之间平均响应时间在最近 2 分钟内超过 1 秒。

webhooks

1
2
webhooks:
- http://127.0.0.1/notify/

webhooks里配置接收告警的服务。这个就需要我们自己开发了。

告警的消息会通过 HTTP 请求进行发送, 请求方法为 POST, Content-Typeapplication/json, JSON 格式基于 List<org.apache.skywalking.oap.server.core.alarm.AlarmMessage, 包含以下信息.

  • scopeId. 所有可用的 Scope 请查阅 org.apache.skywalking.oap.server.core.source.DefaultScopeDefine.
  • name. 目标 Scope 的实体名称.
  • id0. Scope 实体的 ID.
  • id1. 未使用.
  • ruleName. 您在 alarm-settings.yml 中配置的规则名.
  • alarmMessage. 报警消息内容.
  • startTime. 告警时间, 位于当前时间与 UTC 1970/1/1 之间.

源码中的类

1
2
3
4
5
6
7
8
9
10
11
12
13
@Setter
@Getter
public class AlarmMessage {
private int scopeId;
private String scope;
private String name;
private String id0;
private String id1;
private String ruleName;
private String alarmMessage;
private long startTime;
private transient boolean onlyAsCondition;
}

动态配置

在config/application.yml文件中,有以下配置

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
configuration:
selector: ${SW_CONFIGURATION:none}
none:
grpc:
host: ${SW_DCS_SERVER_HOST:""}
port: ${SW_DCS_SERVER_PORT:80}
clusterName: ${SW_DCS_CLUSTER_NAME:SkyWalking}
period: ${SW_DCS_PERIOD:20}
apollo:
apolloMeta: ${SW_CONFIG_APOLLO:http://localhost:8080}
apolloCluster: ${SW_CONFIG_APOLLO_CLUSTER:default}
apolloEnv: ${SW_CONFIG_APOLLO_ENV:""}
appId: ${SW_CONFIG_APOLLO_APP_ID:skywalking}
period: ${SW_CONFIG_APOLLO_PERIOD:5}
zookeeper:
period: ${SW_CONFIG_ZK_PERIOD:60} # Unit seconds, sync period. Default fetch every 60 seconds.
nameSpace: ${SW_CONFIG_ZK_NAMESPACE:/default}
hostPort: ${SW_CONFIG_ZK_HOST_PORT:localhost:2181}
# Retry Policy
baseSleepTimeMs: ${SW_CONFIG_ZK_BASE_SLEEP_TIME_MS:1000} # initial amount of time to wait between retries
maxRetries: ${SW_CONFIG_ZK_MAX_RETRIES:3} # max number of times to retry
etcd:
period: ${SW_CONFIG_ETCD_PERIOD:60} # Unit seconds, sync period. Default fetch every 60 seconds.
group: ${SW_CONFIG_ETCD_GROUP:skywalking}
serverAddr: ${SW_CONFIG_ETCD_SERVER_ADDR:localhost:2379}
clusterName: ${SW_CONFIG_ETCD_CLUSTER_NAME:default}
consul:
# Consul host and ports, separated by comma, e.g. 1.2.3.4:8500,2.3.4.5:8500
hostAndPorts: ${SW_CONFIG_CONSUL_HOST_AND_PORTS:1.2.3.4:8500}
# Sync period in seconds. Defaults to 60 seconds.
period: ${SW_CONFIG_CONSUL_PERIOD:60}
# Consul aclToken
aclToken: ${SW_CONFIG_CONSUL_ACL_TOKEN:""}
k8s-configmap:
period: ${SW_CONFIG_CONFIGMAP_PERIOD:60}
namespace: ${SW_CLUSTER_K8S_NAMESPACE:default}
labelSelector: ${SW_CLUSTER_K8S_LABEL:app=collector,release=skywalking}
nacos:
# Nacos Server Host
serverAddr: ${SW_CONFIG_NACOS_SERVER_ADDR:127.0.0.1}
# Nacos Server Port
port: ${SW_CONFIG_NACOS_SERVER_PORT:8848}
# Nacos Configuration Group
group: ${SW_CONFIG_NACOS_SERVER_GROUP:skywalking}
# Nacos Configuration namespace
namespace: ${SW_CONFIG_NACOS_SERVER_NAMESPACE:}
# Unit seconds, sync period. Default fetch every 60 seconds.
period: ${SW_CONFIG_NACOS_PERIOD:60}
# Nacos auth username
username: ${SW_CONFIG_NACOS_USERNAME:""}
password: ${SW_CONFIG_NACOS_PASSWORD:""}
# Nacos auth accessKey
accessKey: ${SW_CONFIG_NACOS_ACCESSKEY:""}
secretKey: ${SW_CONFIG_NACOS_SECRETKEY:""}

可以看到,skywalking支持非常多的外部配置中心。默认是不开启的,设置为none。

要使用哪个,就将selector配置为哪个。

配置的key为alarm.default.alarm-settings

配置的值格式必须和alarm-settings.yml 文件一致

日期时间截取 date_format

1
select date_format(expenses_date,'%H:%i:%S') from t_expenses_records

和case when配合,判断时间

1
2
3
4
5
6
7
8
9
select d.records_id, e.res_name, e.service_name,
case when date_format(expenses_date,'%H:%i:%S') > '03:00:01' and date_format(expenses_date,'%H:%i:%S') < '10:00:59' then '早餐'
when date_format(expenses_date,'%H:%i:%S') > '10:01:00' and date_format(expenses_date,'%H:%i:%S') < '16:00:59' then '午餐'
when date_format(expenses_date,'%H:%i:%S') > '16:01:00' and date_format(expenses_date,'%H:%i:%S') < '21:00:59' then '晚餐'
else '夜餐'
end '餐类',
d.food_type_name, d.food_name, d.subsidy_rate,d.expenses_number, d.food_amount, r.expenses_date from t_expenses_detailed d, temp_expense_record e, t_expenses_records r
where d.records_id = e.record_id
and e.record_id = r.id

字符串替换 replace

将字段中的/ 替换成 -

1
update t_clean_expense_food_day set expense_day = replace(expense_day,'/','-');

每个月定时生成一张新表

需求:

​ 现在有一张日志表log。需要每个月的25号,将日志转到log_当前月份去。同时生成一张新的log表。

新建一张测试表 test_table_rename

1,解决动态生成表名的问题

我们可以用set @var=…设置变量,然后用prepare stml from @var设置动态sql语句,最后用EXECUTE stml;执行语句。

1
2
3
SET @sqlstr = CONCAT('rename table test_table_rename to test_table_rename_',DATE_FORMAT(CURDATE(),'%Y_%m'));
PREPARE stmt1 FROM @sqlstr ;
EXECUTE stmt1 ;

2,开启定时任务

写一个一次性的测试任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CREATE EVENT e_test_rename
ON SCHEDULE AT CURRENT_TIMESTAMP+INTERVAL 50 SECOND
DO
BEGIN
SET @sqlstr = CONCAT('rename table test_table_rename to test_table_rename_',DATE_FORMAT(CURDATE(),'%Y_%m'));
PREPARE stmt1 FROM @sqlstr;
EXECUTE stmt1;
create table test_table_rename
(
id int auto_increment primary key,
create_time datetime default CURRENT_TIMESTAMP null
)
comment '用来测试重命名的表';
END;

image-20200528150316402

3,最后的定时任务

1
2
3
4
5
6
7
8
9
CREATE EVENT e_test_rename
ON SCHEDULE EVERY 1 MONTH
STARTS DATE_ADD(DATE_ADD('2020-05-25', INTERVAL 1 MONTH), INTERVAL 30 MINUTE)
DO
BEGIN
SET @sqlstr = CONCAT('rename table test_table_rename to test_table_rename_',DATE_FORMAT(CURDATE(),'%Y_%m'));
PREPARE stmt1 FROM @sqlstr;
EXECUTE stmt1;
END;

每个月的25号的零点三十分执行一次

SpringBoot轻量级分布式任务调度实现

Springboot自带的@EnableScheduling,让我们创建任务调度已经非常容易。但是这个任务调度只适合单机环境,如果是分布式分布式部署多个服务,需要自己实现任务调度。否则会有任务执行多次的问题。

分布式任务调度可以选择的方案挺多,比如Quartz,LTS,XXL-JOB,elastic-job等。

但是这些框架相对较重,需要额外的部署和运维。或者建大量的表。

所以,想自己实现一个简单的,轻量级的任务调度。

方案就是,实现一个分布式锁获取机制,获取到锁的执行任务,没获得锁的放弃。

所以,核心问题是要实现一个分布式锁。

实现分布式锁的方案,

  1. redis的setNx命令
  2. 数据库锁机制
  3. zookeep的临时顺序节点

基于mysql的实现

我的方案如下:

  1. 创建一张task_lock表。每一行对应一个分布式任务

  2. 乐观锁策略。每个任务有一个status状态,标记任务执行中,还是空闲。

    记录中有一个version字段。每次任务执行时,各个节点来取任务。

    然后,对version+1,再放回去。放的时候判断version要判断是否匹配。

    能成功更新的为获取锁成功,更新状态为执行中

  3. 获取锁的任务再执行完后,要将status改为空闲状态

1, 建表

1
2
3
4
5
6
7
8
9
10
11
12
13
create table task_lock
(
id int auto_increment
primary key,
status int default '0' null comment '任务状态 0:空闲 1:执行中',
version int default '0' null,
create_time datetime default CURRENT_TIMESTAMP null,
update_time datetime null,
name varchar(10) null comment '任务名',
description varchar(100) null comment '任务描述'
)
comment '任务锁'
;

2, 代码

基础代码使用mybatis generator生成,不放了。

再service层创建一对方法

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 获取锁
* @param taskId
* @return
*/
boolean getLock(int taskId);

/**
* 释放锁
* @param taskId
* @return
*/
boolean releaseLock(int taskId);

实现

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
29
30
31
32
33
34
35
36
@Override
public boolean getLock(int id) {
TaskLockExample example = new TaskLockExample();
example.createCriteria()
.andIdEqualTo(id)
.andStatusEqualTo(0);
List<TaskLock> taskLocks = taskLockMapper.selectByExample(example);
if (CollUtil.isEmpty(taskLocks)) {
return false;
}
TaskLock taskLock = taskLocks.get(0);
Integer oldVersion = taskLock.getVersion();

TaskLockExample updateExample = new TaskLockExample();
updateExample.createCriteria().andIdEqualTo(id)
.andVersionEqualTo(oldVersion)
.andStatusEqualTo(0);

TaskLock newLock = new TaskLock();
newLock.setStatus(1);
newLock.setVersion(oldVersion + 1);
newLock.setUpdateTime(LocalDateTime.now());
// 通过更新的行数,得到是否获得锁
int i = taskLockMapper.updateByExampleSelective(newLock, updateExample);
return i == 1;
}

@Override
public boolean releaseLock(int id) {
TaskLock newLock = new TaskLock();
newLock.setId(id);
newLock.setStatus(0);
newLock.setUpdateTime(LocalDateTime.now());
int i = taskLockMapper.updateByPrimaryKeySelective(newLock);
return i == 1;
}

注意事项:更新时主键id作为第一个查询条件,这样保证更新时使用行锁而不是表锁。因为MySQL的innoDB引擎是支持行锁的,但是行锁建立在索引之上。

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 每天2点执行一次
*/
@Scheduled(cron = "0 0 2 * * ?")
public void configureTasks() {
boolean lock = taskLockService.getLock(2);
if (lock) {
try {
int i = cleanService.doCompanyMonthClean();
log.info(">>>>>>>>>>>>>>>>>>>>>>CompanyMonthCleanSummaryTask Start {}<<<<<<<<<<<<<<<<<<", LocalDateTime.now());
log.info("更细记录:" + i);
log.info(">>>>>>>>>>>>>>>>>>>>>>CompanyMonthCleanSummaryTask end {} <<<<<<<<<<<<<<<<<<", LocalDateTime.now());
} finally {
boolean b = taskLockService.releaseLock(2);
if (!b) {
log.error("释放锁失败:" + 2);
}
}
}
}

注意:一定要在finally中释放锁

局限

1,各个执行节点的时钟要同步。要同时过来取锁。

2,缺失执行记录等

但是对我目前的业务场景够用了。

Mysql索引原理推演

我们在进行sql优化时,很常用的手段是进行索引的优化。那mysql索引的原理是什么?或者mysql是如何实现索引的?

我们可以类比图书馆的书目索引。索引,我们会很自然的想到使用树结构。使用二叉树。

https://www.cnblogs.com/sujing/p/11110292.html

二叉查找树(Binary Search Trees)

  二叉树是每个结点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。二叉树常被用于实现二叉查找树和二叉堆。二叉树有如下特性:

1、每个结点都包含一个元素以及n个子树,这里0≤n≤2。
2、左子树和右子树是有顺序的,次序不能任意颠倒。左子树的值要小于父结点,右子树的值要大于父结点。

  光看概念有点枯燥,假设我们现在有这样一组数[35 27 48 12 29 38 55],顺序的插入到一个数的结构中,步骤如下
![img](Mysql索引原理推演.assets/WeChat Screenshot_20190616134805.png)
![img](Mysql索引原理推演.assets/WeChat Screenshot_20190616134818.png)
![img](Mysql索引原理推演.assets/WeChat Screenshot_20190616134835.png)
![img](Mysql索引原理推演.assets/WeChat Screenshot_20190616134852.png)
![img](Mysql索引原理推演.assets/WeChat Screenshot_20190616134900.png)
![img](Mysql索引原理推演.assets/WeChat Screenshot_20190616134916.png)
![img](Mysql索引原理推演.assets/WeChat Screenshot_20190616134934.png)

  好了,这就是一棵二叉树啦!我们能看到,经通过一系列的插入操作之后,原本无序的一组数已经变成一个有序的结构了,并且这个树满足了上面提到的两个二叉树的特性!

  但是如果同样是上面那一组数,我们自己升序排列后再插入,也就是说按照[12 27 29 35 38 48 55]的顺序插入,会怎么样呢?

![img](Mysql索引原理推演.assets/WeChat Screenshot_20190616144826.png)

  由于是升序插入,新插入的数据总是比已存在的结点数据都要大,所以每次都会往结点的右边插入,最终导致这棵树严重偏科!!!上图就是最坏的情况,也就是一棵树退化为一个线性链表了,这样查找效率自然就低了,完全没有发挥树的优势了呢!
为了较大发挥二叉树的查找效率,让二叉树不再偏科,保持各科平衡,所以有了平衡二叉树!

平衡二叉树 (AVL Trees)

  平衡二叉树是一种特殊的二叉树,所以他也满足前面说到的二叉树的两个特性,同时还有一个特性:

它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

  大家也看到了前面[35 27 48 12 29 38 55]插入完成后的图,其实就已经是一颗平衡二叉树啦。

  那如果按照[12 27 29 35 38 48 55]的顺序插入一颗平衡二叉树,会怎么样呢?我们看看插入以及平衡的过程:

![img](Mysql索引原理推演.assets/WeChat Screenshot_20190616165744.png)
![img](Mysql索引原理推演.assets/WeChat Screenshot_20190616165806.png)
![img](Mysql索引原理推演.assets/WeChat Screenshot_20190616165835.png)
![img](Mysql索引原理推演.assets/WeChat Screenshot_20190616165909.png)
![img](Mysql索引原理推演.assets/WeChat Screenshot_20190616165924.png)
![img](Mysql索引原理推演.assets/WeChat Screenshot_20190616165936.png)
![img](Mysql索引原理推演.assets/WeChat Screenshot_20190616165954.png)

  这棵树始终满足平衡二叉树的几个特性而保持平衡!这样我们的树也不会退化为线性链表了!我们需要查找一个数的时候就能沿着树根一直往下找,这样的查找效率和二分法查找是一样的呢!

  一颗平衡二叉树能容纳多少的结点呢?这跟树的高度是有关系的,假设树的高度为h,那每一层最多容纳的结点数量为2(n-1),整棵树最多容纳节点数为20+21+22+…+2^(h-1)。这样计算,100w数据树的高度大概在20左右,那也就是说从有着100w条数据的平衡二叉树中找一个数据,最坏的情况下需要20次查找。如果是内存操作,效率也是很高的!但是我们数据库中的数据基本都是放在磁盘中的,每读取一个二叉树的结点就是一次磁盘IO,这样我们找一条数据如果要经过20次磁盘的IO?那性能就成了一个很大的问题了!那我们是不是可以把这棵树压缩一下,让每一层能够容纳更多的节点呢?虽然我矮,但是我胖啊…

B-Tree

  这颗矮胖的树就是B-Tree,注意中间是杠精的杠而不是减,所以也不要读成B减Tree了~

  那B-Tree有哪些特性呢?一棵m阶的B-Tree有如下特性:

1、每个结点最多m个子结点。
2、除了根结点和叶子结点外,每个结点最少有m/2(向上取整)个子结点。
3、如果根结点不是叶子结点,那根结点至少包含两个子结点。
4、所有的叶子结点都位于同一层。
5、每个结点都包含k个元素(关键字),这里m/2≤k<m,这里m/2向下取整。
6、每个节点中的元素(关键字)从小到大排列。
7、每个元素(关键字)字左结点的值,都小于或等于该元素(关键字)。右结点的值都大于或等于该元素(关键字)。

  是不是感觉跟丈母娘张口问你要彩礼一样,列一堆的条件,而且每一条都让你很懵逼!下面我们以一个[0,1,2,3,4,5,6,7]的数组插入一颗3阶的B-Tree为例,将所有的条件都串起来,你就明白了!

![img](Mysql索引原理推演.assets/WeChat Screenshot_20190619204220.png)
![img](Mysql索引原理推演.assets/WeChat Screenshot_20190619204227.png)
![img](Mysql索引原理推演.assets/WeChat Screenshot_20190619204243.png)
![img](Mysql索引原理推演.assets/WeChat Screenshot_20190619204302.png)
![img](Mysql索引原理推演.assets/WeChat Screenshot_20190619204311.png)
![img](Mysql索引原理推演.assets/WeChat Screenshot_20190619204327.png)
![img](Mysql索引原理推演.assets/WeChat Screenshot_20190619204336.png)

  那么,你是否对B-Tree的几点特性都清晰了呢?在二叉树中,每个结点只有一个元素。但是在B-Tree中,每个结点都可能包含多个元素,并且非叶子结点在元素的左右都有指向子结点的指针。

  如果需要查找一个元素,那流程是怎么样的呢?我们看下图,如果我们要在下面的B-Tree中找到关键字24,那流程如下
![img](Mysql索引原理推演.assets/WeChat Screenshot_20190619210818.png)
![img](Mysql索引原理推演.assets/WeChat Screenshot_20190619210824.png)
![img](Mysql索引原理推演.assets/WeChat Screenshot_20190619210831.png)
![img](Mysql索引原理推演.assets/WeChat Screenshot_20190619210838.png)

  从这个流程我们能看出,B-Tree的查询效率好像也并不比平衡二叉树高。但是查询所经过的结点数量要少很多,也就意味着要少很多次的磁盘IO,这对
性能的提升是很大的。

  前面对B-Tree操作的图我们能看出来,元素就是类似1、2、3这样的数值,但是数据库的数据都是一条条的数据,如果某个数据库以B-Tree的数据结构存储数据,那数据怎么存放的呢?我们看下一张图

![img](Mysql索引原理推演.assets/WeChat Screenshot_20190620221029.png)

  普通的B-Tree的结点中,元素就是一个个的数字。但是上图中,我们把元素部分拆分成了key-data的形式,key就是数据的主键,data就是具体的数据。这样我们在找一条数的时候,就沿着根结点往下找就ok了,效率是比较高的。

B+Tree

  B+Tree是在B-Tree基础上的一种优化,使其更适合实现外存储索引结构。B+Tree与B-Tree的结构很像,但是也有几个自己的特性:

1、所有的非叶子节点只存储关键字信息。
2、所有的具体数据都存在叶子结点中。
3、所有的叶子结点中包含了全部元素的信息。
4、所有叶子节点之间都有一个链指针。

  如果上面B-Tree的图变成B+Tree,那应该如下:
![img](Mysql索引原理推演.assets/WeChat Screenshot_20190621220003.png)

  大家仔细对比于B-Tree的图能发现什么不同?
  1、非叶子结点上已经只有key信息了,满足上面第1点特性!
  2、所有叶子结点下面都有一个data区域,满足上面第2点特性!
  3、非叶子结点的数据在叶子结点上都能找到,如根结点的元素4、8在最底层的叶子结点上也能找到,满足上面第3点特性!
  4、注意图中叶子结点之间的箭头,满足满足上面第4点特性!

B-Tree or B+Tree?

  在讲这两种数据结构在数据库中的选择之前,我们还需要了解的一个知识点是操作系统从磁盘读取数据到内存是以磁盘块(block)为基本单位的,位于同一个磁盘块中的数据会被一次性读取出来,而不是需要什么取什么。即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存。这样做的理论依据是计算机科学中著名的局部性原理: 当一个数据被用到时,其附近的数据也通常会马上被使用。
  预读的长度一般为页(page)的整倍数。页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在许多操作系统中,页得大小通常为4k)。

  B-Tree和B+Tree该如何选择呢?都有哪些优劣呢?
  1、B-Tree因为非叶子结点也保存具体数据,所以在查找某个关键字的时候找到即可返回。而B+Tree所有的数据都在叶子结点,每次查找都得到叶子结点。所以在同样高度的B-Tree和B+Tree中,B-Tree查找某个关键字的效率更高。
  2、由于B+Tree所有的数据都在叶子结点,并且结点之间有指针连接,在找大于某个关键字或者小于某个关键字的数据的时候,B+Tree只需要找到该关键字然后沿着链表遍历就可以了,而B-Tree还需要遍历该关键字结点的根结点去搜索。
  3、由于B-Tree的每个结点(这里的结点可以理解为一个数据页)都存储主键+实际数据,而B+Tree非叶子结点只存储关键字信息,而每个页的大小有限是有限的,所以同一页能存储的B-Tree的数据会比B+Tree存储的更少。这样同样总量的数据,B-Tree的深度会更大,增大查询时的磁盘I/O次数,进而影响查询效率。
  鉴于以上的比较,所以在常用的关系型数据库中,都是选择B+Tree的数据结构来存储数据!下面我们以mysql的innodb存储引擎为例讲解,其他类似sqlserver、oracle的原理类似!

innodb引擎数据存储

  在InnoDB存储引擎中,也有页的概念,默认每个页的大小为16K,也就是每次读取数据时都是读取4*4k的大小!假设我们现在有一个用户表,我们往里面写数据

![img](Mysql索引原理推演.assets/WeChat Screenshot_20190623130137.png)

  这里需要注意的一点是,在某个页内插入新行时,为了不减少数据的移动,通常是插入到当前行的后面或者是已删除行留下来的空间,所以在某一个页内的数据并不是完全有序的(后面页结构部分有细讲),但是为了数据访问顺序性,在每个记录中都有一个指向下一条记录的指针,以此构成了一条单向有序链表,不过在这里为了方便演示我是按顺序排列的!

  由于数据还比较少,一个页就能容下,所以只有一个根结点,主键和数据也都是保存在根结点(左边的数字代表主键,右边名字、性别代表具体的数据)。假设我们写入10条数据之后,Page1满了,再写入新的数据会怎么存放呢?我们继续看下图

![img](Mysql索引原理推演.assets/WeChat Screenshot_20190623130144.png)

  有个叫“秦寿生”的朋友来了,但是Page1已经放不下数据了,这时候就需要进行页分裂,产生一个新的Page。在innodb中的流程是怎么样的呢?

1、产生新的Page2,然后将Page1的内容复制到Page2。
2、产生新的Page3,“秦寿生”的数据放入Page3。
3、原来的Page1依然作为根结点,但是变成了一个不存放数据只存放索引的页,并且有两个子结点Page2、Page3。

  这里有两个问题需要注意的是
  1、为什么要复制Page1为Page2而不是创建一个新的页作为根结点,这样就少了一步复制的开销了?
  如果是重新创建根结点,那根结点存储的物理地址可能经常会变,不利于查找。并且在innodb中根结点是会预读到内存中的,所以结点的物理地址固定会比较好!

  2、原来Page1有10条数据,在插入第11条数据的时候进行裂变,根据前面对B-Tree、B+Tree特性的了解,那这至少是一颗11阶的树,裂变之后每个结点的元素至少为11/2=5个,那是不是应该页裂变之后主键1-5的数据还是在原来的页,主键6-11的数据会放到新的页,根结点存放主键6?
  如果是这样的话新的页空间利用率只有50%,并且会导致更为频繁的页分裂。所以innodb对这一点做了优化,新的数据放入新创建的页,不移动原有页面的任何记录。

  随着数据的不断写入,这棵树也逐渐枝繁叶茂,如下图
![img](Mysql索引原理推演.assets/WeChat Screenshot_20190623131149.png)

  每次新增数据,都是将一个页写满,然后新创建一个页继续写,这里其实是有个隐含条件的,那就是主键自增!主键自增写入时新插入的数据不会影响到原有页,插入效率高!且页的利用率高!但是如果主键是无序的或者随机的,那每次的插入可能会导致原有页频繁的分裂,影响插入效率!降低页的利用率!这也是为什么在innodb中建议设置主键自增的原因!

  这棵树的非叶子结点上存的都是主键,那如果一个表没有主键会怎么样?在innodb中,如果一个表没有主键,那默认会找建了唯一索引的列,如果也没有,则会生成一个隐形的字段作为主键!

  有数据插入那就有删除,如果这个用户表频繁的插入和删除,那会导致数据页产生碎片,页的空间利用率低,还会导致树变的“虚高”,降低查询效率!这可以通过索引重建来消除碎片提高查询效率!

innodb引擎数据查找

  数据插入了怎么查找呢?

1、找到数据所在的页。这个查找过程就跟前面说到的B+Tree的搜索过程是一样的,从根结点开始查找一直到叶子结点。
2、在页内找具体的数据。读取第1步找到的叶子结点数据到内存中,然后通过分块查找的方法找到具体的数据。

  这跟我们在新华字典中找某个汉字是一样的,先通过字典的索引定位到该汉字拼音所在的页,然后到指定的页找到具体的汉字。innodb中定位到页后用了哪种策略快速查找某个主键呢?这我们就需要从页结构开始了解。

![img](Mysql索引原理推演.assets/WeChat Screenshot_20190623225950.png)

  左边蓝色区域称为Page Directory,这块区域由多个slot组成,是一个稀疏索引结构,即一个槽中可能属于多个记录,最少属于4条记录,最多属于8条记录。槽内的数据是有序存放的,所以当我们寻找一条数据的时候可以先在槽中通过二分法查找到一个大致的位置。

  右边区域为数据区域,每一个数据页中都包含多条行数据。注意看图中最上面和最下面的两条特殊的行记录Infimum和Supremum,这是两个虚拟的行记录。在没有其他用户数据的时候Infimum的下一条记录的指针指向Supremum,当有用户数据的时候,Infimum的下一条记录的指针指向当前页中最小的用户记录,当前页中最大的用户记录的下一条记录的指针指向Supremum,至此整个页内的所有行记录形成一个单向链表。

  行记录被Page Directory逻辑的分成了多个块,块与块之间是有序的,也就是说“4”这个槽指向的数据块内最大的行记录的主键都要比“8”这个槽指向的数据块内最小的行记录的主键要小。但是块内部的行记录不一定有序。

  每个行记录的都有一个n_owned的区域(图中粉红色区域),n_owned标识这个这个块有多少条数据,伪记录Infimum的n_owned值总是1,记录Supremum的n_owned的取值范围为[1,8],其他用户记录n_owned的取值范围[4,8],并且只有每个块中最大的那条记录的n_owned才会有值,其他的用户记录的n_owned为0。

  所以当我们要找主键为6的记录时,先通过二分法稀疏索引中找到对应的槽,也就是Page Directory中“8”这个槽,“8”这个槽指向的是该数据块中最大的记录,而数据是单向链表结构所以无法逆向查找,所以需要找到上一个槽即“4”这个槽,然后通过“4”这个槽中最大的用户记录的指针沿着链表顺序查找到目标记录。

聚集索引&非聚集索引

  前面关于数据存储的都是演示的聚集索引的实现,如果上面的用户表需要以“用户名字”建立一个非聚集索引,是怎么实现的呢?我们看下图:

![img](Mysql索引原理推演.assets/WeChat Screenshot_20190623152229.png)

  非聚集索引的存储结构与前面是一样的,不同的是在叶子结点的数据部分存的不再是具体的数据,而数据的聚集索引的key。所以通过非聚集索引查找的过程是先找到该索引key对应的聚集索引的key,然后再拿聚集索引的key到主键索引树上查找对应的数据,这个过程称为回表

  图中的这些名字均来源于网络,希望没有误伤正在看这篇文章的你~_

innodb与MyISAM两种存储引擎对比

  上面包括存储和搜索都是拿的innodb引擎为例,那MyISAM与innodb在存储上有啥不同呢?憋缩话,看图:

![img](Mysql索引原理推演.assets/WeChat Screenshot_20190623161414.png)

  上图为MyISAM主键索引的存储结构,我们能看到的不同是

1、主键索引树的叶子结点的数据区域没有存放实际的数据,存放的是数据记录的地址。
2、数据的存储不是按主键顺序存放的,按写入的顺序存放。

  也就是说innodb引擎数据在物理上是按主键顺序存放,而MyISAM引擎数据在物理上按插入的顺序存放。并且MyISAM的叶子结点不存放数据,所以非聚集索引的存储结构与聚集索引类似,在使用非聚集索引查找数据的时候通过非聚集索引树就能直接找到数据的地址了,不需要回表,这比innodb的搜索效率会更高呢!

Mysql定时事件操作

1.什么是事件

一组SQL集,用来执行定时任务,跟触发器很像,都是被动执行的,事件是因为时间到了触发执行,而触发器是因为某件事件(增删改)触发执行;

mqsql的事件类似于linux的定时任务,不过是完全在mqsql内部实现的。

事件是在一个独立的事件调度线程中被初始化,这个事件调度线程和处理链接的线程没有任何关系。并且该线程在事件执行结束后,会自动销毁。

2.开启事件

查看是否开启:

show variables like ‘event_scheduler’;

image-20200523115532708

如果显示OFF,则输入以下语句开启:

set global event_scheduler = on;

查看事件:

通过show events;可以查看创建的事件

3.创建自定义事件

首先创建一张测试表。

1
2
3
4
5
6
7
create table test_table
(
id int auto_increment primary key,
create_time datetime default CURRENT_TIMESTAMP null
)
comment '用来测试的表'
;

3.1 语法

1
2
3
4
5
6
CREATE EVENT [IFNOT EXISTS] event_name
   ONSCHEDULE schedule
   [ONCOMPLETION [NOT] PRESERVE]
   [ENABLE | DISABLE]
   [COMMENT ``'comment'``]
   DO sql_statement;
解释:

event_name: 自己取的事件名

schedule:触发的时间周期。写法为:

1
2
AT TIMESTAMP [+ INTERVAL INTERVAL]
或 EVERY INTERVAL [STARTS TIMESTAMP] [ENDS TIMESTAMP]

AT + 时间戳,用来完成单次的计划任务

EVERY 时间(单位)的数量实践单位[STARTS 时间戳] [ENDS时间戳],用来完成重复的计划任务。

其中 INTERVAL 的取值又有每年/每月等等:

1
2
3
{YEAR | QUARTER | MONTH | DAY | HOUR | MINUTE |
   WEEK | SECOND | YEAR_MONTH | DAY_HOUR | DAY_MINUTE |
   DAY_SECOND | HOUR_MINUTE | HOUR_SECOND | MINUTE_SECOND}

很多,但是常用的YEAR,MONTH,DAY,HOUR,MINUTE 或者SECOND。剩下的不标准的不建议使用。

ON COMPLETION参数

表示”当这个事件不会再发生的时候”,即当单次计划任务执行完毕后或当重复性的计划任务执行到了ENDS阶段。而PRESERVE的作用是使事件在执行完毕后阻止该事件被Drop掉。所以,对于特定时间执行的事件,如果保持默认,执行完毕后,事件将被删除,不想删除的话要设置成ON COMPLETION PRESERVE。对于重复性时间,如果设置了ON COMPLETION NOT PRESERVE,那么也是只会在执行一次后停止并删除事件。

[ENABLE | DISABLE] 可以设置该事件创建后状态是否开启或关闭,默认为ENABLE。

[COMMENT ‘comment’]可以给该事件加上注释。

3.2 例子

3.2.1 每隔10秒插入一条记录
1
2
3
CREATE EVENT e_test_insert
ON SCHEDULE EVERY 10 SECOND
DO insert INTO test_table (create_time) VALUES (current_timestamp)

可以通过show events;查看刚创建的事件

image-20200528104548658

查看test_table

image-20200528104639044

3.2.2 在特定时间执行一次
1
2
3
CREATE EVENT e_test_insert_at
ON SCHEDULE AT TIMESTAMP '2020-05-28 11:00:00'
DO insert INTO test_table (create_time) VALUES (current_timestamp)

image-20200528110013626

到时间后,查看test_table

image-20200528110052418

确实执行了。再查看一下事件

image-20200528110137898

被删除了。验证了前面的ON COMPLETION参数

3.2.3 在指定延时后执行一次
1
2
3
4
CREATE EVENT e_test_insert_at
ON SCHEDULE AT CURRENT_TIMESTAMP+INTERVAL 50 SECOND
ONCOMPLETION PRESERVE
DO insert INTO test_table (create_time) VALUES (current_timestamp)

50秒后执行一次,并且不要完成后不要删除任务。

image-20200528111323280

并且执行完成后,没有被删除

3.2.4 5分钟后开始,每10秒插入一次
1
2
3
4
CREATE EVENT e_test_insert
ON SCHEDULE EVERY 10 SECOND
STARTS CURRENT_TIMESTAMP+INTERVAL 5 MINUTE
DO insert INTO test_table (create_time) VALUES (current_timestamp)

Starts关键字,可以指定在什么时候开始

image-20200528113235628

3.2.5 指定时间后结束
1
2
3
4
5
CREATE EVENT e_test_insert
ON SCHEDULE EVERY 10 SECOND
STARTS CURRENT_TIMESTAMP+INTERVAL 1 MINUTE
ENDS CURRENT_TIMESTAMP+INTERVAL 2 MINUTE
DO insert INTO test_table (create_time) VALUES (current_timestamp);

在当前1分钟后开始,在当前2分钟后结束。所以,只会执行1分钟。

image-20200528113957801

3.2.6 每天的定时执行
1
2
3
4
CREATE EVENT e_test_insert_day
ON SCHEDULE EVERY 1 DAY
STARTS DATE_ADD(DATE_ADD(CURDATE(), INTERVAL 1 DAY), INTERVAL 1 HOUR)
DO insert INTO test_table (create_time) VALUES (current_timestamp);

每天1点执行一次。DATE_ADD是mysql得内置函数,对某个时间加上指定时间。

3.2.7 每个月的29号0点30分执行一次
1
2
3
4
CREATE EVENT e_test_insert_month
ON SCHEDULE EVERY 1 MONTH
STARTS DATE_ADD(DATE_ADD('2020-04-29', INTERVAL 1 MONTH), INTERVAL 30 MINUTE)
DO insert INTO test_table (create_time) VALUES (current_timestamp);
3.2.8 每小时的30分时执行一次
1
2
3
4
CREATE EVENT e_test_insert_hour
ON SCHEDULE EVERY 1 HOUR
STARTS DATE_ADD('2020-05-28 13:30:00', INTERVAL 1 HOUR)
DO insert INTO test_table (create_time) VALUES (current_timestamp);

4,删除事件

语法:

1
DROP EVENT [IF EXISTS] event_name

如果事件不存在,会报错。所以执行时,最好加上IF EXISTS

5, 修改事件

语法:

1
2
3
4
5
6
7
ALTER EVENT event_name 
[ON SCHEDULE schedule]
[RENAME TO new_event_name]
[ON COMPLETION [NOT] PRESERVE]
[COMMENT ``'comment'``]
[ENABLE | DISABLE]
[DO sql_statement]
5.1 临时关闭事件
1
ALTER EVENT e_test DISABLE;
5.2 开启事件
1
ALTER EVENT e_test ENABLE;
5.3 将每天清空test表改为5天清空一次:
1
ALTER EVENT e_test ON SCHEDULE EVERY 5 DAY;

mongo异常退出问题排查

mongo跑了一段时间后莫名其妙的异常退出,在mongo自己的日志里并没有留下任何遗言。

这种情况下,凭借个人的经验,猜测又是系统内存不足,触发OOM-killer,杀死了mongod。

查看 /var/log/messages文件

image-20220520103414628

果然又是OOM的问题

(93条消息) Mongo崩溃crash, 报out of memory的问题分析与解决方案_飞出四季做的茧的博客-CSDN博客_mongodb 崩溃

默认的是,系统内存的一半再减1,我现在的机器是16G的,也就是最高会申请到7G。

太多了,触发oom

设置为2G,观察

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
May 19 18:22:00 pfsmongo1 kernel: NetworkManager invoked oom-killer: gfp_mask=0x201da, order=0, oom_score_adj=0
May 19 18:22:00 pfsmongo1 kernel: NetworkManager cpuset=/ mems_allowed=0
May 19 18:22:00 pfsmongo1 kernel: CPU: 4 PID: 671 Comm: NetworkManager Kdump: loaded Tainted: G OE ------------ 3.10.0-1160.36.2.el7.x86_64 #1
May 19 18:22:00 pfsmongo1 kernel: Hardware name: OpenStack Foundation OpenStack Nova, BIOS rel-1.10.2-0-g5f4c7b1-20181220_000000-szxrtosci10000 04/01/2014
May 19 18:22:00 pfsmongo1 kernel: Call Trace:
May 19 18:22:00 pfsmongo1 kernel: [<ffffffff94783559>] dump_stack+0x19/0x1b
May 19 18:22:00 pfsmongo1 kernel: [<ffffffff9477e5f8>] dump_header+0x90/0x229
May 19 18:22:00 pfsmongo1 kernel: [<ffffffff94106972>] ? ktime_get_ts64+0x52/0xf0
May 19 18:22:00 pfsmongo1 kernel: [<ffffffff9415dfff>] ? delayacct_end+0x8f/0xb0
May 19 18:22:00 pfsmongo1 kernel: [<ffffffff941c252d>] oom_kill_process+0x2cd/0x490
May 19 18:22:00 pfsmongo1 kernel: [<ffffffff941c1f1d>] ? oom_unkillable_task+0xcd/0x120
May 19 18:22:00 pfsmongo1 kernel: [<ffffffff941c2c1a>] out_of_memory+0x31a/0x500
May 19 18:22:00 pfsmongo1 kernel: [<ffffffff941c9834>] __alloc_pages_nodemask+0xad4/0xbe0
May 19 18:22:00 pfsmongo1 kernel: [<ffffffff94219388>] alloc_pages_current+0x98/0x110
May 19 18:22:00 pfsmongo1 kernel: [<ffffffff941bdfe7>] __page_cache_alloc+0x97/0xb0
May 19 18:22:00 pfsmongo1 kernel: [<ffffffff941c0f80>] filemap_fault+0x270/0x420
May 19 18:22:00 pfsmongo1 kernel: [<ffffffffc03f1756>] ext4_filemap_fault+0x36/0x50 [ext4]
May 19 18:22:00 pfsmongo1 kernel: [<ffffffff941ee78a>] __do_fault.isra.61+0x8a/0x100
May 19 18:22:00 pfsmongo1 kernel: [<ffffffff941eed3c>] do_read_fault.isra.63+0x4c/0x1b0
May 19 18:22:00 pfsmongo1 kernel: [<ffffffff941f6580>] handle_mm_fault+0xa20/0xfb0
May 19 18:22:00 pfsmongo1 kernel: [<ffffffff94790653>] __do_page_fault+0x213/0x500
May 19 18:22:00 pfsmongo1 kernel: [<ffffffff94790a26>] trace_do_page_fault+0x56/0x150
May 19 18:22:00 pfsmongo1 kernel: [<ffffffff9478ffa2>] do_async_page_fault+0x22/0xf0
May 19 18:22:00 pfsmongo1 kernel: [<ffffffff9478c7a8>] async_page_fault+0x28/0x30
May 19 18:22:00 pfsmongo1 kernel: Mem-Info:

NetworkManager invoked oom-killer

华为云、阿里云上都有k8s、中间件服务,但那些都只属于paas层的调度编排层和工具集合

  1. 服务化是 PaaS 的本质。软件模块重用,服务治理,对外提供能力是 PaaS 的本质。

  2. 分布式是 PaaS 的根本特性。多租户隔离、高可用、服务编排是 PaaS 的基本特性。

  3. 自动化是 PaaS 的灵魂。 自动化部署安装运维,自动化伸缩调度是 PaaS 的关键。

img

img