wars 发布的文章

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

JVM 笔记

本文都基于 Hotspot 编写

1) JVM 内存

1.1) JVM 内存结构

  • 线程共享
    • 方法区
  • 线程隔离
    • 虚拟机栈
    • 本地方法栈
    • 程序计数器

1.2) 堆

1.2.1) JDK 1.8 以前
  • 新生代
    • Eden
    • Survivor 1
    • Survivor 2
  • 老年代
    • Tenured
  • 持久代
    • Perm Gen
1.2.1) JDK 1.8 后

1.8 后将 Perm Gen 持久代替换成了 Metaspace 元空间:
从使用堆内存 升级为 直接使用 OS 内存资源

  • 新生代
    • Eden
    • Survivor 1
    • Survivor 2
  • 老年代
    • Tenured
  • 元空间
    • Metaspace

1.3) 方法区

有两个陌生的常量池: Class 常量池主要存放类加载到内存之后的常量等, 然后将这些常量继续加载至运行时常量池

1.4) 虚拟机栈

当创建一个线程时, 会建立对应此线程的虚拟机栈, 管理本地方法, 一图流:

1.5) 本地方法栈

与虚拟机栈类似, 但只管理 Native 方法, 这里不细讲了.

1.6) 程序计数器

与前两者一致, 都是线程级的, 主要记录当前线程的执行到了哪个位置, 供于多线程切换后运行状态的恢复.

2) 类加载

Hotspot 1.8 类加载过程如下图:

如果确认项目能正常加载, 可以用 -Xverify:none 关闭链接阶段验证步骤提高启动速度, 可应用在 IDEA 之类的 Java IDE 上.

3) 编译器优化

3.1) 运行模式优化

JVM 一共有三种运行模式, 默认 Mixed 混合模式:

  1. -Xint: 解释模式
  2. -Xcomp: 编译模式, 优先以编译模式运行
  3. -mixed: 混合模式, 用 JIT(即时编译器) 将热点代码编译提高启动速度
  • 分层编译优化

3.2) JIT 优化

JIT(Just-In-Time Compiler) 即时编译器, 由 JIT 来即时决定调用代码是否需要编译, Hotspot 中提供两个 JIT, C1 和 C2

3.2.1) JIT_C1(Client Compiler)
  • 只局部优化, 简单快速
  • 启动速度高, 适合做 GUI 等 Client 应用
3.2.2) JIT_C2(Server Compiler)
  • 相比 C1 优化更全面
  • 启动速度慢, 适合做执行时间较长或追求性能的程序

3.3) 热点代码优化

被判断为热点代码则会被 JIT 编译成字节码执行, 反之则解释执行.

以下的代码为热点代码:

  • 重复出现在栈顶的方法
  • 计数器探测
    • 方法调用计数器(Invocation Counter): 可通过 -XX:CompileThreshold=x 设置阈值
      1. 统计方法被调用次数, 不开启分层编译时, C1 阈值 1500, C2 阈值 10000 次
      2. 每过一段时间会在 GC 时, 顺便发生热度衰减导致阈值减半
        • -XX:-UseCounterDecay 关闭热度衰减
        • -XX:COunterHalfLifeTime=x 设置热度衰减周期
    • 回边计数器(Back Edge Counter): 可通过 -XX:OnStackReplacePercentage=X 设置阈值
      1. 统计循环代码执行次数, 不开启分层编译时, C1 阈值 13995, C2 阈值 10700 次

当开启分层编译时, JVM 根据当前待编译方法数, 编译线程数动态调整阈值, 上述两个 JVM 参数会失效

3.4) 分层编译优化

分层编译一共有 5 种级别, 根据代码的热点程度使用相应级别优化代码:
0. 解释执行

  1. 简单 C1 编译, 不开启 Profiling JVM 性能监控
  2. 受限的 C1 编译, Profiling 只监控方法调用次数, 循环回边执行次数
  3. 完全 C1 编译, 会使用 C1 的所有 Profiling 监控
  4. C2 编译, 某些情况会根据性能监控信息进行一些非常激进的优化

可以通过以下 JVM 参数限制级别:

  • 仅使用 C1: -XX:+TieredCompilation -XX:TieredStopAtLevel=1
    • TieredStopAtLevel 即分层停止级别, 若设置为 3 则只使用 0,1,2,3 级编译
  • 仅使用 C2: -XX:-TieredCompilation , 关闭分层编译, 仅使用 0,4 级别优化

3.5) 方法内联优化

JVM 会使用内联优化, 将满足条件的目标代码, 尝试内联(复制)至调用处, 减少入栈出栈开销:

  1. 方法体足够小
    • 热点方法体阈值 325 字节, 可用 -XX:FreqInlineSize=x 修改
    • 非热点方法体阈值 35 字节, 可用 -XX:MaxInlineSize=x 修改
  2. 目标方法运行时的实现可以被唯一确定

方法内联带来的问题: 方法内联实际上是空间换时间, 如果内联过多可能导致 CodeCache 溢出, 使得 JVM 降级解释模式运行

以下还有一些其他的方法内联参数:
| 参数名 | 默认 | 说明 |
| --- | --- | --- |
| -XX:+Printlnlining | - | 打印内联详情, 该参数需和 -XX:+UnlockDiagnosticVMOptions 配合使用 |
| -XX:+UnlockDiagnosticVMOptions | - | 打印 JVM 诊断相关的信息 |
| -XX:MaxInlineSize=n | 35 | 如果非热点方法的字节码超过该值, 则无法内联, 单位字节 |
| -XX:FreqInlineSize=n | 325 | 如果热点方法的字节码超过该值, 则无法内联, 单位字节 |
| -XX:InlineSmallCode=n | 1000 | 目标编译后生成的机器码代销大于该值则无法内联, 单位字节 |
| -XX:MaxInlineLevel=n | 9 | 内联方法的最大调用帧数(嵌套调用的最大内联深度)|
| -XX:MaxTrivialSize=n | 6 | 如果方法的字节码少于该值,则直接内联,单位字节 |
| -XX:MinInliningThreshold=n | 250 | 如果目标方法的调用次数低于该值,则不去内联 |
| -XX:LiveNodeCountlnliningCutoff=n | 40000 | 编译过程中最大活动节点数(IR节点)的上限,仅对C2编译器有效 |
| -XX:InlineFrequencyCount=n | 100 | 如果方法的调用点(call site)的执行次数超过该值,则触发内联 |
| -XX:MaxRecursiveInlineLevel=n | 1 | 递归调用大于这么多次就不内联 |
| -XX:+InlineSynchronizedMethods | 开启 | 是否允许内联同步方法 |

3.5) 标量替换 / 栈上分配

| -XX:+DoEscapeAnalysis | 开启 | 是否开启逃逸分析 |
| -XX:+EliminateAllocations | 开启 | 是否开启标量替换 |
| -XX:+EliminateLocks | 开启 | 是否开启锁消除 |

4) GC 优化

我们可以根据各种场景下的需求, 来选择垃圾回收策略, 如:

  • 内存不足场景: 提高对象的回收效率, 腾出更多内存
  • CPU 资源不足: 降低高并发时垃圾回收频率, 充分利用 CPU 资源提高并发量

4.1) GC 在哪回收

上文提到 JVM 的内存结构可以得知, (虚拟机栈,本地方法栈,程序计数器) 都是线程隔离的, 对象会随着栈入栈出自动销毁, 所以它们不需要考虑线程隔离.
而线程共享的(堆,方法区), 则是会发生 GC 的部分:

  • : 回收对象
  • 方法区: 回收常量和未被使用的类

4.2) 垃圾计算算法

Java 默认使用的垃圾计算算法是可达性分析算法, 我们还可以了解以下引用计数法:

4.2.1) 引用计数法

记录对象的被引用的次数, 当计数器归 0, 即回收, 无法解决循环引用问题

4.2.1) 可达性分析

可达性分析即: 被 GCRoots 直接或间接引用则可达, 反之不可达, 可以回收.

GCRoots 有以下几类, 可达性可以理解为堆外指向堆内的引用:

  • 虚拟机栈中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中 JNI 引用的对象

引用又有以下几种:

  • 强引用
  • 软引用
  • 弱引用
  • 虚引用

不可达对象回收流程:

  1. 不可达的对象首先会被标记死缓
    1. 判断该对象有无必要执行(重写) finalize(), 如不需要则回收
  2. 若有必要执行 finalize(), JVM 会创建一个低优先级线程执行其
    1. finalize() 中的代码重新使该对象建立引用, 则放弃回收

4.3) 垃圾回收算法

三种常用基础算法:

  1. 标记清除算法: 会产生碎片, 放不下大对象导致溢出
    1. 标记需要被回收的对象
    2. 清理需要被回收的对象
  2. 标记整理/压缩算法: 相比标记清除避免内存碎片
    1. 标记需要被回收的对象
    2. 把存活对象移动到一起
    3. 剩余区域回收
  3. 复制算法:
    1. 需要两块一样大小的内存区域 AB, 只使用其中一块
    2. 将 A 存活对象复制到 B, 切换使用空间至 B
    3. 清空 A
    4. 将 B 存活对象复制到 A, 切换使用空间至 A
    5. 清空 B
    6. ....

三种常用基础算法对比:

算法 优点 缺点
标记清除 实现简单 存在内存碎片, 分配内存开销
标记整理 无碎片 整理开销
复制 性能好, 无碎片 内存利用率低

两种综合算法:

  1. Java 分代收集算法:
  2. 增量算法:

4.4) 垃圾收集器

新生代收集器(复制算法):

  1. Serial (Client 模式默认收集器):
    • 单线程
    • 简单, 高效
    • 收集全程 Stop The World
  2. ParNew (Serial 多线程版)
    • 多线程
    • 线程数: -XX:ParallelGCThreads=x
    • 主要和 CMS 配合使用
  3. Parallel Scavenge (吞吐量优先收集器)
    • 多线程
    • 可控制回收吞吐量
      • 回收最大停顿时间(尽量保证): -XX:MaxGCPauseMilis
      • 吞吐量大小, 设置回收时间不超过运行时间的 1/(1+n): -XX:GCTimeRatio
      • 自适应 GC: -XX:+UseAdptiveSizePolicy
        • 开启后无需手动设置新生代大小(-Xmn), Eden/Survivor 区比例(-XX:SurvivorRatio) 等参数

老年代收集器(标记清除算法):

  1. Seria Old
    • CMS 收集器的后备收集器
  2. Parallel Old
  3. CMS(Concurrent Mark Sweep)
    • 并发收集器
    • 老年代占比触发阈值: -XX:CMSInitiatingOccupancyFraction=-1

CMS 执行流程:

  1. 初始标记
    • 标记 GC Roots 直接关联对象
    • Stop The World
  2. 并发标记
    • 标记 GC Roots 关联的所有对象
    • 并发执行, 无 Stop The World
  3. 并发预清理
    • 重新标记上阶段中, 引用被更新的对象
    • 并发执行
    • 关闭此阶段: -XX:-CMSPrecleaningEnable=true
  4. 并发可终止预清理
    • 与上阶段一致
    • 当 Eden 的使用量大于阈值: -XX:CMSScheduleRemarkEdenSizeThreshold=2M才执行
    • 控制预清理阶段结束时机
      • 扫描时间阈值(s): -XX:CMSMaxAbortablePrecleanTime=5
      • Eden 占比阈值: -XX:CmsScheduleRemarkEdenPenetration=50
  5. 重新标记
    • 修正并发标记期间, 标记发生变动的对象标记
    • Stop The World
  6. 并发清理
    • 直接清除被标记对象
    • 并发执行
  7. 并发重置
    • 清理本次 CMS GC 的长下文信息, 为下一次 GC 做准备

G1 收集器

  • Region 式内存区块分布
  • CMS 替代品
  • Java9 后删除了 CMS
  • 复制算法, 没有碎片

本文采用知识共享 署名-相同方式共享 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/ 查看该许可协议。

ElasticSearch(ES)

1) ES 基本概念名称

1.1) 基本术语

抽象成数据库

  • ES -> 数据库
  • Index -> 索引表
  • Type -> 表类型(新版 ES 弃用)
  • Document -> 行
  • Field -> 列

1.2) 集群术语

  • Shard: Primary Shard 主分片
  • Replica: Replica Shard 备份节点

2) ES 安装

2.1) ES 本体安装

展开查看
  1. 下载二进制包并解压, 当前最新版本为 7.9.2:
    https://www.elastic.co/cn/downloads/elasticsearch

     wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.9.2-linux-x86_64.tar.gz
     tar -zxvpf elasticsearch-7.9.2-linux-x86_64.tar.gz
     cd elasticsearch-7.9.2
     # 创建 Data 和 Log 目录
     mkdir data
     mkdir logs
  2. 修改主配置文件 elasticsearch.yml

    vim config/elasticsearch.yml
    cluster.name: es-cluster # 集群 name, 单机也可以设置一个替换默认名称
    node.name: node-1 # 节点名称
    path.data: /usr/local/es/elasticsearch-7.9.2/data # 数据目录
    path.logs: /wars/es/elasticsearch-7.9.2/logs # 日志目录
    network.host: 0.0.0.0 # 绑定 IP
    #http.port: 9200 # 端口
    cluster.initial_master_nodes: ["node-1"] # Cluster 中主节点
  3. 适当调整 JVM 参数

    vim config/jvm.options
    -Xms128m
    -Xmx128m
  4. 配置 Systemd 脚本, 修改自官方 Deb 二进制包, 适当修改一下脚本的一些 PATH:

    vim /etc/systemd/system/elasticsearch.service
    [Unit]
    Description=Elasticsearch
    Documentation=https://www.elastic.co
    Wants=network-online.target
    After=network-online.target
    
    [Service]
    RuntimeDirectory=elasticsearch
    PrivateTmp=true
    WorkingDirectory=/wars/es/elasticsearch-7.9.2
    
    User=elasticsearch
    Group=elasticsearch
    
    ExecStart=/wars/es/elasticsearch-7.9.2/bin/elasticsearch -p /wars/es/elasticsearch-7.9.2/elasticsearch.pid
    
    # StandardOutput is configured to redirect to journalctl since
    # some error messages may be logged in standard output before
    # elasticsearch logging system is initialized. Elasticsearch
    # stores its logs in /var/log/elasticsearch and does not use
    # journalctl by default. If you also want to enable journalctl
    # logging, you can simply remove the "quiet" option from ExecStart.
    StandardOutput=journal
    StandardError=inherit
    
    # Specifies the maximum file descriptor number that can be opened by this process
    LimitNOFILE=65535
    
    # Specifies the maximum number of processes
    LimitNPROC=4096
    
    # Specifies the maximum size of virtual memory
    LimitAS=infinity
    
    # Specifies the maximum file size
    LimitFSIZE=infinity
    
    # Disable timeout logic and wait until process is stopped
    TimeoutStopSec=0
    
    # SIGTERM signal is used to stop the Java process
    KillSignal=SIGTERM
    
    # Send the signal only to the JVM rather than its control group
    KillMode=process
    
    # Java process is never killed
    SendSIGKILL=no
    
    # When a JVM receives a SIGTERM signal it exits with code 143
    SuccessExitStatus=143
    
    [Install]
    WantedBy=multi-user.target
    
    # Built for packages-7.9.2 (packages)
  5. 创建 ES 用户并设置 ES 工作目录所有者供 Systemd 使用

    useradd elasticsearch
    chown -R elasticsearch:elasticsearch /wars/es/elasticsearch-7.9.2
  6. 配置 Kernel 参数否则启动报错

    vim /etc/sysctl.conf
    vm.max_map_count=262144
    sysctl -p
  7. 配置 ES 开机启动并启动 ES

    systemctl enable elasticsearch
    systemctl start elasticsearch

2.2) ES Header Module 安装

官方描述: 一个 ES 集群的 WEB 前端, Github 项目链接:
https://github.com/mobz/elasticsearch-head
推荐安装方式是 Chrome 插件安装, 新 EDGE 也可以使用, 插件链接:
https://chrome.google.com/webstore/detail/elasticsearch-head/ffmkiejjmecolpfloofpjologoblkegm/

3) ES 基本操作

安装完 ES 后, ES 所在机器 IP + 配置中配置的端口(默认 9200)即 ES 的接口 Base Path, 如:
10.0.96.156:9200
可以直接访问它查看 ES 的一些 About

3.1) Cluster Health

观察集群情况
GET /_cluster/health

3.2) 索引

Index 就像一张数据库表, 用于存放我们需要搜索的数据

3.2.1) 创建索引

PUT /{IndexName}

3.2.2) 查询索引

GET /_cat/indices?v

3.2.3) 删除索引

DELETE /{IndexName}

3.3) 索引映射

Index Mappings 即索引表结构, 规定 Field 的类型, 名称等

3.3.0) Mappings 常用数据类型

https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html

  • text, keyword, string
  • byte, short, integer, long
  • float, double
  • boolean
  • date
  • object

3.3.1) 同时创建索引和 Mappings

PUT /{indexName}?include_type_name=true

{
  "mappings": {
    "properties": {
      "{fieldName}": {
        "type": "{type}",
        "index": {是否被索引}
      },
      "{fieldName2}": {
        "type": "{type}",
        "index": false
      }
    }
  }
}

3.3.2) 分词测试

GET /{indexName}/_analyze

{
  "field": "{fieldName}",
  "text": "I am from china"
}

3.3.3) 修改(创建) Mappings

已经创建的 Mappings Field 是不能修改的, 一般只新增 Field
POST /{indexName}/_mapping?include_type_name=true

{
  "properties": {
    "{fieldName}": {
      "type": "{type}",
      "index": {是否被索引}
    },
    "{fieldName2}": {
      "type": "{type}",
      "index": false
    }
  }
}

3.4) 文档

Document 即索引表中的每一行, 我们在搜索时, 依赖的真正数据源

3.4.1) 创建文档

POST /{indexName}/_doc/[id]
如 URL 不指定 ID 则自动生成, 此 ID 为 ES 的 ID, 我们另外还能在 Json 里指定业务 ID.
可以根据 Mappings 创建文档, 若文档中的 Field 在 Mappings 中没有, 会自动生成 Mapping

{
  "id": 1,
  "name": "wars",
  "age": 16,
  "about": "I am from China, I am from Beijing, Beijing is capital of China",
  "create_date": "2020-01-01"
}

3.4.2) 删除文档

DELETE /{indexName}/_doc/{id}

3.4.3) 编辑文档

局部替换
POST /{indexName}/_doc/{id}

{
  "doc": {
    "name": "wars"
  }
}

全文替换
PUT /{indexName}/_doc/{id}

{
  "name": "wars",
  "age": 16
}

使用 ES 提供的乐观锁编辑文档, 请求后携带两个 VERSION 参数, 查询文档时可以获取到当前的 VERSION 参数:
?if_seq_no={seq_no}&if_primary_term={primary_term}

3.4.4) 查询文档

GET /{indexName}/_doc/_search[?source=id,name]
GET /{indexName}/_doc/{id}[?source=id,name]

3.4.5) 文档是否存在

HEAD /{indexName}/_doc/{id}

3.5) 分词

`POST /_analyze'

{
  "analyzer": "standard",
  "text": "分词文字"
}

`POST /{indexName}/_analyze'

{
  "analyzer": "standard",
  "field": "",
  "text": "分词文字"
}

analyzer 即使用的分词器, ES 内置分词器如下:
https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-analyzers.html

  • standard:默认分词,单词会被拆分,大小会转换为小写。
  • simple:按照非字母分词。大写转为小写。
  • whitespace:按照空格分词。忽略大小写。
  • stop:去除无意义单词,比如 the / a / an / is …
  • keyword:不做分词。把整个文本作为一个单独的关键词。

4) ES 整合 IK 分词器

项目地址:
https://github.com/medcl/elasticsearch-analysis-ik

安装完成后即可获得两个分词器, ik_max_word 和 ik_smart.

4.1) 安装

Optional 1

download pre-build package from here: https://github.com/medcl/elasticsearch-analysis-ik/releases
create plugin folder cd your-es-root/plugins/ && mkdir ik
unzip plugin to folder your-es-root/plugins/ik

Optional 2

use elasticsearch-plugin to install ( supported from version v5.5.1 ):
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.3.0/elasticsearch-analysis-ik-6.3.0.zip

4.2) ik_max_word 和 ik_smart 什么区别?

ik_max_word: 会将文本做最细粒度的拆分,比如会将 “中华人民共和国国歌” 拆分为 “中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌”,会穷尽各种可能的组合,适合 Term Query;
ik_smart: 会做最粗粒度的拆分,比如会将 “中华人民共和国国歌” 拆分为 “中华人民共和国,国歌”,适合 Phrase 查询。

4.3) IK 自定义字典

https://github.com/medcl/elasticsearch-analysis-ik#dictionary-configuration

5) ES 搜索

5.1) QueryString 搜索

可针对于一些简单的搜索
GET /{indexName}/_search?q={field}:{搜索关键字}&q={field2}:{搜索关键字2}...

5.2) DSL(Domain Specific Language)

对于一些复杂查询, 我们根据使用 DSL, 基于 JSON 格式来构建请求

5.2.1) 查询

Query, 对 ES 中的数据做一个检索, 一些范围区间查询可以使用过滤器实现, 并不属于检索这一部分

5.2.1.1) 查询所有

POST /{indexName}/_search

{
  "query": {
    "match_all": {}
  }
}
5.2.1.2) 分词查询

POST /{indexName}/_search

{
  "query": {
    "match": {
      "{fieldName}": "{搜索关键字}"
    }
  }
}

其中 match 关键字有其他两种常用替换品:

  • match: 分词查询
  • term: 单词输入, 不分词查询
  • terms: 多词输入, 不分词查询
  • match_phrase: 匹配名词(短语), 先对传入的多个名词分词, 且按照分词后的前后顺序, 去搜索顺序匹配的 Document
    • slop: 匹配名词默认要求: 必须匹配顺序, 名词在文档里词间隔需为 0 , 可以通过 slop 参数指定词间隔, 如 slop 是 2, 代表名词之间可以最多有两个词存在.
      {
      "query": {
      "term": {
        "{fieldName}": "{搜索关键字}"
      }
      }
      }
      {
      "query": {
      "terms": {
        "{fieldName}": ["{搜索关键字1}", "{搜索关键字2}..."]
      }
      }
      }
      {
      "query": {
      "match_phrase": {
        "{fieldName}": {
          "query": "China Sichuan Chengdu",
          "slop": 2
        }
      }
      }
      }
5.2.1.3) 过滤响应 Field

通过 _source 指定需要的 Field

{
  "query": {
    "match": {
      "{fieldName}": "{搜索关键字}"
    }
  },
  "_source": [ "name", "age" ]
}
5.2.1.4) 分页查询

通过 from 和 size 指定起始和单页数据量

{
  "query": {
    "match": {
      "{fieldName}": "{搜索关键字}"
    }
  },
  "_source": [ "name", "age" ],
  "from": 0,
  "size": 10
}
5.2.1.5) Match 扩展

使用 Match 扩展, 需要将将 Query 简写打开, 如:

{
  "query": {
    "match": {
      "{fieldName}": {
        "query": "{搜索关键字}"
      }
    }
  }
}

operator: 有两个选项 or(默认) 和 and

  • or: 分词后只需匹配其中一个词
  • and: 分词后必须匹配所有词
    {
    "query": {
      "match": {
        "{fieldName}": {
          "query": "{搜索关键字}",
          "operator": "and"
        }
      }
    }
    }

minimum_should_match: 上面的 and 相当于 100% 匹配, minimum_should_match 可以指定匹配的百分比, 文档需满足其百分比

{
  "query": {
    "match": {
      "{fieldName}": {
        "query": "{搜索关键字}",
        "minimum_should_match": "50%"
      }
    }
  }
}
5.2.1.6) IDS 查询

可通过单个或多个 ID 查询文档:

GET /{indexName}/_search

{
  "query": {
    "ids": {
      "type": "_doc",
      "values": [ "1", "2", "3" ]
    }
  }
}
5.2.1.7) 多 Field 匹配

multi_match: 将输入分词, 匹配多个字段

GET /{indexName}/_search

{
  "query": {
    "multi_match": {
      "query": "{搜索关键字}",
      "fields": [ "name", "about" ]
    }
  }
}

还可以为 Field 指定对应的权重, 提高搜索体验:
在 Field 后加上 ^{提升权重级别}, 非 multi_match 查询的时候想提高权重, 可以在 Field 标签中添加 boost 字段指定权重放大倍数

{
  "query": {
    "multi_match": {
      "query": "{搜索关键字}",
      "fields": [ "name^10", "about^20" ]
    }
  }
}
5.2.1.8) 多条件查询

ES 中的条件查询叫 bool 查询, bool 查询有三种类型, 几种类型是可以组合使用的:

  • must: 查询条件必须全部匹配
  • must_not: 查询条件必须全部不匹配
  • should: 查询条件匹配一个即可
  • should(must_not): 任意一个不匹配即可(待验证)
    {
    "query": {
      "bool": {
        "must": [
        {
          "term": {
            "name": "${搜索关键字}"
          }
        },
        {
          "match": {
            "about": "${搜索关键字}"
          }
        }
        ],
        "must_not": [],
        "should": []
      }
    }
    }

5.2.2) 是否存在

POST /{indexName}/_search

{
  "query": {
    "exists": {
      "{fieldName}": "{搜索关键字}"
    }
  }
}

5.2.3) 过滤器

Query 负责分词, 检索, 检索后的数据可以使用过滤器进行一次过滤, 通常用于实现区间查询等操作, 过滤器即 post_filter:
range 操作包含常见的 gte, lte, gt, lt, 此外过滤还可以使用 match 等操作

{
  "query": {
    "match": {
      "name": "wars"
    }
  },
  "post_filter": {
    "range": {
      "age": {
        "gte": 16,
        "lte": 24
      }
    }
  }
}

5.2.4) 排序

在过滤之后, 可以 sort 对过滤后的数据进行排序

5.2.4.1) Field 排序

Field 排序不能对 text 类型的 Field 排序, 如果想对 text 排序, 可以使用复合 mapping 为 Field 分配多个 type, 增加一个 keyword 类型

{
  "query": {
    "match": {
      "name": "wars"
    }
  },
  "sort": [
    {
      "age": "desc"
    },
    {
      "_id": "desc"
    }
  ]
}

5.2.5) 高亮

针对最后返回的结果, 可以进行一个类似与淘宝商品搜索的高亮显示

{
  "query": {
    "match": {
      "about": "Beijing"
    }
  },
  "highlight": {
    "pre_tags": ["<H>"],
    "post_tags": ["</H>"],
    "fields": {
      "desc": {}
    }
  }
}

6) ES 集群搭建

只需要两台机器就可以实现 ES 的集群了, 且能同时实现高可用和分片负载

修改主配置文件 elasticsearch.yml

vim config/elasticsearch.yml
cluster.name: es-cluster # 集群 name, 各节点须相同
node.name: node-1 # 节点名称, 各节点须不同
node.master: true # 是否主节点, 集群中只需要设置一个
node.data: true # 是否数据节点, 对外提供服务
discovery.seed_hosts: ["192.168.1.156", "192.168.1.157"] # 节点列表
cluster.initial_master_nodes: ["node-1"] # Cluster 中主节点

path.data: /usr/local/es/elasticsearch-7.9.2/data # 数据目录
path.logs: /wars/es/elasticsearch-7.9.2/logs # 日志目录
network.host: 0.0.0.0 # 绑定 IP
#http.port: 9200 # 端口

7) SpringBoot 整合 ES

7.1) 依赖

引入 SpringBoot 官方提供的依赖, 注意一下依赖最好和 ES 版本对应:
spring-boot-starter-data-elasticsearch
然后, SpringBoot 万物皆 Template, ElasticsearchRestTemplate 搞定

7.2) Config

application.yml

spring:
  data:
    elasticsearch:
      cluster-name: es-cluster
      cluster-nodes: 192.168.1.156:9200

本文采用知识共享 署名-相同方式共享 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, 具体可以康康源码哈和上一种解决方案差差不多.

Title - Artist
0:00