分类 DB 下的文章

本文采用知识共享 署名-相同方式共享 4.0 国际 许可协议进行许可。
访问 https://creativecommons.org/licenses/by-sa/4.0/ 查看该许可协议。

DB 调优

本文基于 MySQL 编写,兼容版本 5.7+

1) 调优数据库纬度

从上往下成本依次递减, 从上往下效果依次递增, 尽量从下往上优化, 提高投入产出比

  • 硬件和系统调优
    • 硬件
      • 硬件配置
    • OS 配置
      • 内核参数如 swappiness 等
  • MySQL 自身调优
    • 数据库参数配置
      • 性能参数如 buffer 等
    • 表结构
      • 良好的表结构
    • SQL 以及索引
      • 良好 SQL
      • 高效索引
  • 架构调优
    • 系统架构
      • 读写分离
      • 高可用
      • 实例个数
      • 分库分表
      • 数据库选择
    • 业务需求
      • 拒绝不合理的需求, 提出优化方案

2) 性能分析

2.0) 数据准备

基于 MySQL 官方测试数据库, 可按照 README 安装:
https://github.com/datacharmer/test_db

2.1) 慢查询分析

2.1.1) MySQL 慢查询日志配置

加入 my.cnf 中 mysqld 重启; 或在 clinet 中 set global 临时生效.

  • slow_query_log: 开启日志输出
  • slow_query_log_file: 默认如 /var/log/mysql/xxxx.slow_log, 慢日志存放路径
  • log_output(FILE, TABLE): 默认 FILE, 输出至 mysql.slow_log, 可以组合使用如: FILE,TABLE
  • long_query_time: 默认 10, 即执行时间超过 10 秒记录为慢查询
  • long_queries_not_using_indexes(OFF, ON): 默认 OFF, 是否将未使用索引 SQL 同时记录
  • long_throttle_queries_not_using_indexes: 默认 0, 与 long_queries_not_using_indexes 搭配使用, 限制每分钟写入未使用索引 SQL 数量
  • min_examined_row_limit: 默认 0, 慢查询 SQL 行数超过此阈值才记录
  • log_slow_admin_statements(OFF, ON): 默认 OFF, 是否记录管理语句(ALTER TABLE, ANALYZE TABLE, CHECK TABLE, CREATE INDEX, DROP INDEX, OPTIMIZE TABLE, REPAIR TABLE)
  • log_slow_slave_statements(OFF, ON): 默认 OFF, 是否记录 Slave 节点做主从复制时, 超过 long_query_time 时间的复制查询
  • log_slow_extra(OFF, ON): 默认 OFF, 仅当日志输出为文件时有效, 额外输出一些额外 log

2.1.2) MySQL 慢查询日志分析

2.1.3) TABLE 类型日志分析
SELECT * FROM `mysql`.slow_log;

终端查询 SQL 会显示如下, 可以使用其他工具如 DataGrip 查看具体 SQL

+----------------------------+---------------------------+-----------------+-----------------+-----------+---------------+-----------+----------------+-----------+-----------+------------------------------------------------------------+-----------+
| start_time                 | user_host                 | query_time      | lock_time       | rows_sent | rows_examined | db        | last_insert_id | insert_id | server_id | sql_text                                                   | thread_id |
+----------------------------+---------------------------+-----------------+-----------------+-----------+---------------+-----------+----------------+-----------+-----------+------------------------------------------------------------+-----------+
| 2020-10-17 22:56:21.693516 | root[root] @ localhost [] | 00:00:00.004481 | 00:00:00.000000 |         0 |             0 | employees |              0 |         0 |         1 | 0x73657420676C6F62616C20736C6F775F71756572795F6C6F673D4F4E |        22 |
| 2020-10-17 22:56:28.742718 | root[root] @ localhost [] | 00:00:00.165174 | 00:00:00.000109 |    300024 |        300024 | employees |              0 |         0 |         1 | 0x73656C656374202A2066726F6D20656D706C6F79656573           |        22 |
+----------------------------+---------------------------+-----------------+-----------------+-----------+---------------+-----------+----------------+-----------+-----------+------------------------------------------------------------+-----------+
2.1.4) FILE 类型日志分析

日志文件存放路径可以使用如下 SQL 查询:

show variables LIKE '%slow_query_log%';

日志文件不好直接浏览, 可以使用 MySQL 自带工具 mysqldumpslow 分析, 使用方法可以参考 mysqldumpslow --help:

mysqldumpslow -s t -t 10 -g "ORDER BY" /usr/local/var/mysql/warsdeiMac-slow.log

2.1.3) EXPLAIN 分析 SQL 执行计划

使用 EXPLAIN 加上 SQL 即可分析 SQL 的执行计划如: explain SELECT * FROM employees;
输出格式一共有三种, 可以在 EXPLAIN 后拼上 FORMAT=(TREE,JSON) 使用其他两种.

默认输出的信息列含义解释如下:

还可以在 SQL 结尾使用 SHOW WARNING, 查看扩展信息, 这里不深究.

2.1.4) SHOW PROFILE 分析各阶段开销

SHOW PROFILE 已经被废弃, 但是 PERFORMANCE_SCHEMA 使用过于繁琐, 依然建议使用 SHOW PROFILE.

  • SELECT @@have_profiling;
    是否支持 SHOW PROFILE
  • SELECT @@PROFILING;
    是否已启用
  • SET profiling=1/0;
    开启或关闭, 分析完成请关闭此功能降低性能损耗
  • SHOW PROFILES;
    查看最近执行 15 条 SQL 耗时, 可通过 SET profiling_history_size=x 调整数量
  • SHOW PROFILE [type, ...] FOR QUERY {Query_ID}
    • Query_ID 通过 SHOW PROFILES 获得
    • type:
      • ALL
      • BLOCK IO
      • CONTEXT SWITCHES
      • CPU
      • IPC
      • MEMORY
      • PAGE FAULTS
      • SOURCE
      • SWAPS

2.1.5) OPTIMIZER_TRACE

待完善

3) 索引

3.1) 常见 Tree 数据结构

3.1.1) 二叉树

左边叶子节点始终比右边节点小, 因为无法保证左右平衡, 所以上界 O(n)

3.1.2) 平衡二叉树

左边叶子节点始终比右边节点小, 加入平衡算法改变树结构保证平衡, 所以上界 O(logn)

3.1.3) B-tree

算法 平均 最差
空间 O(n) O(n)
搜索 O(log n) O(log n)
插入 O(log n) O(log n)
删除 O(log n) O(log n)

图有点难画, 转自维基百科:

  • m 为树的层数
  • 根节点的子节点个数为 2 <= x <= m
  • 中间节点的子节点个数为 m/2 <= y <= m
  • 有 k 个子节点的非叶子节点有 k - 1 个键

3.1.4) B+tree

相对于 B-tree, 适合范围查找

图有点难画, 转自维基百科:

  • 与 B-tree 最大的区别是各节点中包含了所有父节点的关键字, 有序链表存储
  • 所有叶子节点中间有指针相连

3.2) MySQL 索引类型

3.2.1) InnoDB vs MyISAM

InnoDB 和 MyISAM 数据结构都默认使用 B+tree 实现

  • InnoDB: 聚簇索引
    • 叶子节点索引和数据存储在一起
  • MyISAM: 非聚簇索引
    • 的叶子节点只存储数据指针

3.2.2) Hash

上界 O(1)

转自慕课网架构师直通车:

3.2.2.1) MySQL Hash 索引

默认只支持 Memory 引擎, InnoDB 可以通过 innodb-adaptive_hash_index 参数开启 ‘自适应 Hash 索引’, 默认打开

CREATE TABLE hash_test(
  name varchar(55) not null,
  age tinyint(4) not null,
  key using hash(name)
)engine = memory;

3.2.3) 空间索引

MySQL 5.7 后支持 InnoDB, 之前只支持 MyISAM, 建议使用 PostgreSQL 玩空间索引

3.2.4) 全文索引

MySQl 5.7 后支持中文,之前通常搭配搜索引擎使用, 建议使用搜索引擎

3.3) 索引限制

3.3.1) 匹配规则支持

  • 完全匹配: WHERE name = 'wars'
  • 范围匹配: WHERE age > 18
  • 前缀匹配: WHERE name LIKE 'w%'

3.3.1) B-tree / B+tree 组合索引限制

index(name, age, sex)

  • 组合索引查询条件不包括最左列(name),则无法使用索引
  • 组合索引若不连续使用(WHERE name='a' AND sex=1),只能使用到 name 索引
  • 组合索引查询中如有列范围(模糊)查询(WHERE age>1 AND sex=1), 右边列都玩法使用索引(sex)

3.3.2) Hash 索引限制

  • 无法使用排序
  • 不支持范围/模糊查询
  • 不支持部分索引列匹配查找

3.4) 创建索引原则

建议创建场景:

  • SELECT 中频繁 WHERE 字段
  • UPDATE/DELETE 中非主键 WHERE 条件
  • ORDER BY/GROUP BY 字段
  • DISTINCT 字段
  • 唯一约束字段
  • 多表连接字段,务必类型一致(避免隐式转换)
    不建议创建场景:
  • WHERE 中用不到的字段
  • 表记录过少
  • 表中大量重复数据
  • 频繁更新的字段,会产生索引维护开销

3.5) 索引失效

  • WHERE 中对索引列使用了表达式或函数
  • 尽量避免使用左模糊, 可考虑转搜索引擎
  • OR 条件左右侧有无索引字段,引起全表扫描
  • WHERE 条件和索引列类型不一致
  • WHERE 条件字段含有 NULL 值, 无法索引, 建议将表字段都定义成 NOT NULL

3.6) 索引调优

3.6.1) 长字段索引调优

对于长字段列, 可以新建一列 Hash 列作为索引, 在插入时, 可以计算该字段值的 Hash, 然后与该字段一同插入表中.
查询时直接计算 Hash 值直接查 Hash 列即可.

3.6.1.1) 无法模糊查询问题

但是这种 Hash 索引无法模糊查询, 所以可以引进前缀索引:

-- 5 代表使用该列的前几个字符进行索引
ALTER TABLE employees ADD KEY(first_name(5));

那么前缀多少比较好呢, 可以使用一个完整列选择性公式计算:

-- 计算此列的最大选择性积分
SELECT COUNT(DISTINCT first_name) / COUNT(*) FROM employees;
-- 计算当前缀索引长度为 x 时, 选择性积分, 可以依次递增计算次列合适的长度
SELECT COUNT(DISTINCT LEFT(first_name, x)) / COUNT(*) FROM employees;

还可以新增一个字段反转列, 建立前缀索引, 即可实现后缀索引

本文采用知识共享 署名-相同方式共享 4.0 国际 许可协议进行许可。
访问 https://creativecommons.org/licenses/by-sa/4.0/ 查看该许可协议。

Redis 优化方案

1) 缓存穿透

一般发生在系统被 CC 攻击时, 反复请求一条 DB 中没有的数据, 代码无法将此数据缓存, 导致大并发的请求直接打在 DB 上, 引起 DB 宕机, 即缓存穿透.

1.1) Redis 填充空数据(推荐)

原理是在 Redis 中插入一条空值数据, 将数据库压力转移到 Redis 上, 直接上伪代码:

int requestId = -1;

String cacheValue = redis.get(requestId);
if(null == cacheValue) { // 只作 null 判断
    if (DB 有数据) {
        redis.put(requestId, DB 数据);
        return DB 数据;
    } else {
        redis.put(requestId, ""); // Put 一个空数据
        return "";
    }
}

1.2) 布隆过滤器

百度 Google 吧这边不献丑了

2) 缓存雪崩

Redis 瞬时大面积 Key 过期, 同时客户端产生大量并发, 会导致并发直接打在 DB 上引起宕机, 即缓存雪崩.
方案:

  • Key 永不过期
  • 多层缓存: 如 Redis + Memcached, 获取数据顺序: Redis -> Memcached -> DB
  • 分散过期时间
  • 第三方 Redis: 如阿里云 Redis, 也算是一种永不过期方案

3) 批量查询 multi 和 pipeline

multi 和 pipeline 的区别在于 multi 会将操作都即刻的发送至 redis 服务端 queued 起来,每条指令 queued 的操作都有一次通信开销,执行 exec 时 redis 服务端再一口气执行 queued 队列里的指令,pipeline 则是在客户端本地 queued 起来,执行 exec 时一次性的发送给 redis 服务端,这样只有一次通信开销。比如我有 5 个 incr 操作,multi 的话这 5 个 incr 会有 5 次通信开销,但 pipeline 只有一次。

本文采用知识共享 署名-相同方式共享 4.0 国际 许可协议进行许可。
访问 https://creativecommons.org/licenses/by-sa/4.0/ 查看该许可协议。

Redis

1) 常用 NoSQL 应用特点

1.1) Ehcache

  • 优点
    • 基于 java
    • 基于 JVM 缓存
    • 简单, 轻巧, 方便
  • 缺点:
    • 官方不支集群分布式

1.2) Memcache

  • 优点
    • 简单 Key - Value 存储
    • 内存使用率高
    • 多线程, CPU 利用效率高
  • 缺点
    • 不支持持久化

1.3) Redis

  • 优点
    • 数据结构丰富
    • 持久化
  • 缺点
    • 单线程

2) Redis conf 常用配置

  • port
  • bind 0.0.0.0
  • daemonize no/yes : 后台启动, Systemd 不需要
  • requirepass password
  • dir : Redis 工作(数据)目录
  • pidfile
  • logfile
  • databases 16

3) Redis 基本数据类型

更全的 Command 参考官方文档: https://redis.io/commands

展开查看 * common * SELECT index : 切换数据库, config 默认 16 个 * FLUSHDB * FLUSHALL * key * KEYS *xoxo* * TYPE key * string * SET key value [EX second] * SETNX * GET key * DEL key * TTL key * EXPIRE key second * APPEND key value * STRLEN key * INCR key * DECR key * INCRBY key value * DECRBY key value * GETRANGE key start end * SETRANGE key index value * MSET k1 v1 k2 v2 * MSETNX * MGET k1 k2 * hash * HSET name key value * HGET name key * HMSET name k1 v1 k2 v2 * HMGET name k1 k2 * HGETALL name * HKEYS name * HVALS name * HLEN name * HINCRBY name key value * HINCRBYFLOAT name key value * HDECRBY name key value * HDECRBYFLOAT name key value * HEXISTS name key * HDEL name key * list * LPUSH/RPUSH name v1 v2... * LRANGE name start end * LPOP/RPOP name * LINDEX name index * LLEN name * LSET name index value * LGET name index * LINSERT name BEFORE/AFTER value target_value * LREM name count value * LTRIM name start end * DEL name * set * SADD name v1 v2... * SMEMBERS name * SCARD name * SISMEMBER name value * SREM name value * SPOP name [num] * SRANDMEMBERS name [num] * SMOVE name name2 value * SDIFF name name2 : 差集(name - name2) * SINTER name name2 : 交集 * SUNION name name2 : 并集 * zset(score/sorted set) * ZADD name score v1 score2 v2... * ZRANGE name start end [withscores] * ZRANK name value : 排名(start of 0) * ZCARD name

4) RDB VS AOF

  • RDB
    1. 快照式存储
    2. 每隔一个特定时间备份
    3. 效率高
    4. 因为非同步备份, 容易在宕机时丢数据
  • AOF
    1. 单文件存储
    2. 有每秒备份/同步备份两种模式
    3. 每隔一段时间会整理碎片以及压缩
    4. 效率相比 RDB 较低
    5. 数据丢失率更低

在 Redis 老版本中 Bug 会引起 AOF 备份内容和实际内容有出入问题;
可根据需求将 RDB 和 AOF 结合使用解决持久化风险问题.

5) Redis 主从/读写分离

Redis 的主从复制即读写分离, 分为 Master(写) 和 Slave(读) 两种角色, 原理图大致如下:

  • Master 在启动时将 RDB 持久化文件同步到 Slave 上然后做批量恢复节约资源
  • 启动后 Master 上产生的写入将实时同步给 Slave

请开启 Master 的持久化! 否则当 Master 宕机重启时, 因无持久化文件会同步将 Slave 上数据清空!

5.1) Redis 主从复制扩展方案

一般一个 Master 只用两个 Slave 节点, Slave 节点过多会导致主节点产生网络 IO 过大.

5.2) Redis 无磁盘复制方案

在主从复制时, 如果 Redis 实例所在机器磁盘 IO 吞吐量较低时, 可以采用此方案, 将磁盘 IO 压力转移至 RAM.

#repl-diskless-sync no # 开启
repl-diskless-sync yes

#repl-diskless-sync-delay 5 # 同步前等待时间(S), 等待客户端连接
repl-diskless-sync-delay 5

6) Redis 缓存过期机制

  • 主动删除: 根据配置中的 hz 参数(1S 扫描次数), 扫描 Redis 中的过期 Key, 主动删除
    • 相对于占用 CPU 资源
  • 惰性删除: 当客户端访问到过期 Key 时才删除
    • 相对于占用内存资源
  • 内存淘汰: 根据配置中设置的内存淘汰机制删除 Key

7) Redis 哨兵

Redis 的主从复制时, 为了保证高可用, 引入了 redis-sentinel 哨兵进程; 使可以在 Master 节点宕机时, 将 Slave 升级为 Master.
注意事项:

  • 若 Slave 升级为新 Master, 原 Master 即使上线也会变成 Slave 只提供读取操作.
  • 哨兵一般集群部署至少 3 个节点

7.1) SpringBoot 整合哨兵模式

spring:
  redis:
    database: 0
    password: passwd
    sentinel:
      master: master
      nodes: 10.0.96.200:26379,10.0.96.201:26379,10.0.96.202:26379
      #password:

8) Redis Cluster 集群

Redis 集群至少要有 3 个 Master 节点, 常用架构如下:

config 基本配置如下:

# Auth password
requirepass passwd
# Cluster
cluster-enabled yes
cluster-config-file nodes-6379.conf
cluster-node-timeout 5000
# AOF
appendonly yes

启动各个实例的 Redis-Server 后使用 redis-cli 创建集群

# redis-cli -a 密码 --cluster create 主节点 1 主节点 2... 从节点 1 从节点 2... --cluster-replicas 每个节点中从节点数量
redis-cli -a passwd --cluster create 10.0.96.150:6379 10.0.96.151:6379 10.0.96.154:6379 10.0.96.152:6379 10.0.96.153:6379 10.0.96.155:6379 --cluster-replicas 1

运行后得到如下确认提示

>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 10.0.96.152:6379 to 10.0.96.150:6379
Adding replica 10.0.96.153:6379 to 10.0.96.151:6379
Adding replica 10.0.96.155:6379 to 10.0.96.154:6379
M: cf946565d99c6a9231ab94e771d4970088853f58 10.0.96.150:6379
   slots:[0-5460] (5461 slots) master
M: 6ad9624f0901ddc91546420844557633c3ba2d77 10.0.96.151:6379
   slots:[5461-10922] (5462 slots) master
M: 457564f62eff029fc38ccbac72fafdce817de100 10.0.96.154:6379
   slots:[10923-16383] (5461 slots) master
S: c3cc750a9706850ed14fab9fcca7ba3b3de98ed6 10.0.96.152:6379
   replicates cf946565d99c6a9231ab94e771d4970088853f58
S: aad4d73437964fab0c9a950c48fff133cb2b37b1 10.0.96.153:6379
   replicates 6ad9624f0901ddc91546420844557633c3ba2d77
S: 9d6f3ec40c372f86a8f13e7f4e65effa2bc19def 10.0.96.155:6379
   replicates 457564f62eff029fc38ccbac72fafdce817de100
Can I set the above configuration? (type 'yes' to accept): 

yes 即可开始创建, 接着我们随便 check 一个节点是否成功:

redis-cli -a passwd --cluster check 10.0.96.150:6379
>>> Performing Cluster Check (using node 10.0.96.150:6379)
M: cf946565d99c6a9231ab94e771d4970088853f58 10.0.96.150:6379
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
S: 9d6f3ec40c372f86a8f13e7f4e65effa2bc19def 10.0.96.155:6379
   slots: (0 slots) slave
   replicates 457564f62eff029fc38ccbac72fafdce817de100
M: 457564f62eff029fc38ccbac72fafdce817de100 10.0.96.154:6379
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
S: aad4d73437964fab0c9a950c48fff133cb2b37b1 10.0.96.153:6379
   slots: (0 slots) slave
   replicates 6ad9624f0901ddc91546420844557633c3ba2d77
M: 6ad9624f0901ddc91546420844557633c3ba2d77 10.0.96.151:6379
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: c3cc750a9706850ed14fab9fcca7ba3b3de98ed6 10.0.96.152:6379
   slots: (0 slots) slave
   replicates cf946565d99c6a9231ab94e771d4970088853f58
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

完成

8.1) Redis Cluster Slots 槽

在创建集群的时候会有一些 Slots 相关的输出, 如:

Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383

Redis 默认会有 16384 个槽, 均匀的分配给集群中的每个主节点.
当往集群中插入 Key 时, 会拿 Key 做 Hash, 接着做取模 16384 的操作, 取模后会得到 Slot 值, 然后插入该条数据至拥有此 Slot 的节点上.

9) RedisTemplate 序列化问题

存入 Redis 中的数据含有多余字符问题解决方案:

9.1) 修改 RedisTemplate 默认序列化器

  @Bean
  public RedisTemplate redisTemplate(RedisTemplate redisTemplate) {
    RedisSerializer<String> stringSerializer = new StringRedisSerializer();
    redisTemplate.setKeySerializer(stringSerializer);
    redisTemplate.setValueSerializer(stringSerializer);
    redisTemplate.setHashKeySerializer(stringSerializer);
    redisTemplate.setHashValueSerializer(stringSerializer);
    return redisTemplate;
  }

9.2) 改用 StringRedisTemplate

可以将 RedisTemplate 改用继承了它的 StringRedisTemplate, 具体可以康康源码哈和上一种解决方案差差不多.

本文采用知识共享 署名-相同方式共享 4.0 国际 许可协议进行许可。
访问 https://creativecommons.org/licenses/by-sa/4.0/ 查看该许可协议。

1) 环境

  • Centos 7.2 +

2) 环境要求

  • CPU 要求
    • 小型集群: 2 - 4 Core
    • 大型集群: 8 - 16 Core
  • 内存
    • 普通并发:8G
    • 百万千万并发:16G - 64G
  • 磁盘: IOPS

3) 步骤

本文采用 1主 2从 架构搭建

  1. 安装 etcd

    yum install etcd -y
  2. 配置

    # 备份配置文件
    cp /etc/etcd/etcd.conf /etc/etcd/etcd.conf.bak
    # 防火墙配置
    firewall-cmd --add-port=2379/tcp --permanent
    firewall-cmd --add-port=2380/tcp --permanent
    firewall-cmd --reload
    # 编辑配置文件
    vim /etc/etcd/etcd.conf
    1. Master01
      localhost 全局替换为本机 IP
      ETCD_LISTEN_PEER_URLS="http://Master01的IP:2380"
      ETCD_LISTEN_CLIENT_URLS="http://Master01的IP:2379,http://127.0.0.1:2379"
      ETCD_NAME="Master01" 
      ETCD_INITIAL_ADVERTISE_PEER_URLS="http://Master01的IP:2380"
      ETCD_INITIAL_CLUSTER="Master01=http://Master01的IP:2380,Slave01=http://Slave01的IP:2380,Slave02=http://Slave02的IP:2380"
      ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
      ETCD_INITIAL_CLUSTER_STATE="new"
    2. Slave01
      localhost 全局替换为本机 IP
      ETCD_LISTEN_PEER_URLS="http://Slave01的IP:2380"
      ETCD_LISTEN_CLIENT_URLS="http://Slave01的IP:2379,http://127.0.0.1:2379"
      ETCD_NAME="Slave01" 
      ETCD_INITIAL_ADVERTISE_PEER_URLS="http://Slave01的IP:2380"
      ETCD_INITIAL_CLUSTER="Master01=http://Master01的IP:2380,Slave01=http://Slave01的IP:2380,Slave02=http://Slave02的IP:2380"
      ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
      ETCD_INITIAL_CLUSTER_STATE="new"
    3. Slave02
      localhost 全局替换为本机 IP
      ETCD_LISTEN_PEER_URLS="http://Slave02的IP:2380"
      ETCD_LISTEN_CLIENT_URLS="http://Slave02的IP:2379,http://127.0.0.1:2379"
      ETCD_NAME="Slave02" 
      ETCD_INITIAL_ADVERTISE_PEER_URLS="http://Slave02的IP:2380"
      ETCD_INITIAL_CLUSTER="Master01=http://Master01的IP:2380,Slave01=http://Slave01的IP:2380,Slave02=http://Slave02的IP:2380"
      ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
      ETCD_INITIAL_CLUSTER_STATE="new"
  3. 启动

    # 重启 etcd 并配置开机自启
    systemctl restart etcd
    systemctl enable etcd
    # 检查 member 状态
    etcdctl member list
    # 在 Master10 上 set 测试集群工作是否正常
    etcdctl set wars cat
    # 在 Slave 上 get 测试
    etcdctl get wars

本文采用知识共享 署名-相同方式共享 4.0 国际 许可协议进行许可。
访问 https://creativecommons.org/licenses/by-sa/4.0/ 查看该许可协议。

MongoDB

MongoDB体系结构

  • 传统SQL数据库:实例(N):数据库(1),这种情况在Oracle中称为RAC
  • MongoDB:实例(1):数据库(N)

逻辑存储结构

  • database
  • collection
  • document

    物理存储结构

  • 存储位置:默认存储在/data/db文件夹中
  • 存储文件(3.2版本后默认存储引擎改为wiredTiger,不再采用mmapv1(4.2版本被移除)内存映射,所以看不到命名空间和数据文件了)
  • 命名空间文件:后缀为.ns,默认大小16M
  • 数据文件:后缀为0,1,2,3,4..., 默认大小为0-64M, 1-128M, 2-256M...7-2048M, 8-2048M, 9-2048M
  • 开始文件Size:早期mmapv1为16M, 目前wiredTiger为64M
  • 最大文件Size:默认为2G
  • 日志文件
  • 直接存储在操作系统
  • 系统(进程)日志:默认存储在/var/log/mongodb
  • journal日志:redo重做日志, 用于恢复
  • 存储在Collection中
  • oplog:复制操作日志(主从复制log)
  • 慢查询(需要单独配置):一般用在生产系统中,查询大于200ms的log

Mongo shell

启动: mongo/mongo --username/-u --password/-p --host/-h --port/-p
指定编辑器: export EDITOR=vim

启动配置文件

// 优化显示
host=db.serverStatus().host;
cmdCount=1;
prompt=function() {
        return db + "@" + host + " " + (cmdCount++) + ">"
}

基本操作

  • show dbs - 查看所有数据库
  • use dbname - 选定一个数据库,如数据库不存在,当插入数据时自动创建
  • db - 当前Database
  • db.con1.insertOne({name: "wars"}) - 插入一条数据,如Collection不存在自动创建
  • show collections - 查看当前Database所有Collection

    数据类型

    https://docs.mongodb.com/manual/reference/bson-types/

  • Double(数字默认类型), 当查询某个数字字段时间, 筛选{"val": 9.9},只会查询Double类型的数据;筛选{"val": 10}, 会查询所有数字类型的数据
  • String
  • Object
  • Array
  • Binary data
  • Undefined
  • ObjectId(12byte type: BSON)
  • 当插入数据时,自动生成一个_id字段(主键)
  • Boolean
  • Date
  • Date() 表示当前时间,返回一个字符串
  • new Date()/ISODate() 返回一个ISODate类型, 格林威治标准时间
  • Null
  • Regular Expression
  • DBPointer
  • JavaScript
  • Symbol
  • JavaScript (with scope)
  • 32-bit integer
  • Timestamp
  • 64-bit integer
  • Decimal128
  • Min key
  • Max key

CRUD

插入

Insert数据可以嵌套
insertOne - 插入一条数据
insertMany - 接收数组,插入多条Document
insert - 单条多条皆可插入

查询

find() - 查询所有
find({"docname": "value"}) - 条件查询
find({$or: [{"deptno": 10], {"deptno": 20}})
find({"deptno": {$in: [10, 20]}}) - or或in查询10号或20号部门的员工
find({"sal": {$gt: 2000}, "deptno": 20}) - gt查询大于2000工资的20号部门员工
find({"a.b": {$lt: 10}}) - 查询a中b小于10的数据
find({"a": {"b": {$lt: 10}}}) - 匹配a中,只包含b且b小于10的数据
find({a: {$all: [10,20]}}) - 查询数组嵌套,使用$all
find({a: [10, 20, 30]}) - 匹配数组嵌套
find({a: {$elemMatch: {"b":"", "c": ""}}}) - 单字段,多条件匹配
find({a: {$type:10}}) - $type:10表示空,查询ae为null的数据
find(a: {$exits: true/false}) - 查询字段是否缺失

游标

定义
var cursor = db.emp.find()
循环访问Document
while(cursor.hasNext()) {
printjson(cursor.next())
}

游标转换Array
var arr = cursor.toArray()
arr[0]

分页查询
db.emp.find().limit(5)
db.emp.find().limit(5).skip(5)
db.emp.find().limit(5).skip(10)

更新

updateOne({"_id": 1}, {$set: {sal: 8000}})
updateMany({deptno: 10}, {$inc: {sal: 100}}) - 10号部门张100元
replaceOne()

删除

deleteOne({_id: 1})
deleteMany({deptno: 10})

批处理

支持insert update remove insertMany
bulkWrite([
{insertOne: {"document": {"_id": 001, "name": "wars", "age": 19}}},
{insertOne: {"document": {"_id": 002, "name": "cat", "age": 1}}},
{updateOne: {"filter": {"_id": 001}, "update": {"age": 20}}}
])

索引工作原理

当给emp表的depno字段建立一个索引

CREATE INDEX index1 ON EMP(deptno);

oracle会自动新建一张索引表,索引表

Title - Artist
0:00