diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000..55754d07 --- /dev/null +++ b/.babelrc @@ -0,0 +1,3 @@ +{ + "compact": false +} diff --git a/.editorconfig b/.editorconfig index ee762040..d72a75ea 100644 --- a/.editorconfig +++ b/.editorconfig @@ -19,7 +19,7 @@ insert_final_newline = true [*.{bat, cmd}] end_of_line = crlf -[*.{java, gradle, groovy, kt, sh, xml}] +[*.{java, gradle, groovy, kt, sh}] indent_size = 4 [*.md] diff --git a/.gitattributes b/.gitattributes index 07962a1f..eaae227f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -22,6 +22,7 @@ *.less text *.sql text *.properties text +*.md text # unix style *.sh text eol=lf diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..04010943 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,36 @@ +name: CI + +# 在master分支发生push事件时触发。 +on: + push: + branches: + - master + +env: # 设置环境变量 + TZ: Asia/Shanghai # 时区(设置时区可使页面中的`最近更新时间`使用时区时间) + +jobs: + build: # 自定义名称 + runs-on: ubuntu-latest # 运行在虚拟机环境ubuntu-latest + + strategy: + matrix: + node-version: [16.x] + + steps: + # 使用的动作。格式:userName/repoName。作用:检出仓库,获取源码。 官方actions库:https://github.com/actions + - name: Checkout + uses: actions/checkout@master + + # 指定 nodejs 版本 + - name: Use Nodejs ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + + # 部署 + - name: Deploy + env: # 设置环境变量 + GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} + GITEE_TOKEN: ${{ secrets.GITEE_TOKEN }} + run: npm install && npm run deploy diff --git a/.gitignore b/.gitignore index 83948575..7d98dac9 100644 --- a/.gitignore +++ b/.gitignore @@ -29,7 +29,6 @@ hs_err_pid* # maven plugin temp files .flattened-pom.xml -package-lock.json # ------------------------------- javascript ------------------------------- @@ -37,10 +36,12 @@ package-lock.json node_modules # temp folders -.temp +build dist _book _jsdoc +.temp +.deploy*/ # temp files *.log @@ -48,7 +49,11 @@ npm-debug.log* yarn-debug.log* yarn-error.log* bundle*.js +.DS_Store +Thumbs.db +db.json book.pdf +package-lock.json # ------------------------------- intellij ------------------------------- diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 47463f91..00000000 --- a/.travis.yml +++ /dev/null @@ -1,24 +0,0 @@ -# 持续集成 CI -# @see https://docs.travis-ci.com/user/tutorial/ - -language: node_js - -sudo: required - -node_js: '16.13.0' - -branches: - only: - - master - -before_install: - - export TZ=Asia/Shanghai - -script: bash ./scripts/deploy.sh - -notifications: - email: - recipients: - - forbreak@163.com - on_success: change - on_failure: always diff --git a/README.md b/README.md index 6f25e0be..5cc46533 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,27 @@

- logo + logo

- license - build + + + star + + + + fork + + + + build + + + + code style + +

DB-TUTORIAL

@@ -16,87 +31,81 @@ > - 🔁 项目同步维护:[Github](https://github.com/dunwu/db-tutorial/) | [Gitee](https://gitee.com/turnon/db-tutorial/) > - 📖 电子书阅读:[Github Pages](https://dunwu.github.io/db-tutorial/) | [Gitee Pages](https://turnon.gitee.io/db-tutorial/) -## 📖 内容 +## 数据库综合 -### 关系型数据库 +### 分布式存储原理 -> [关系型数据库](docs/sql) 整理主流关系型数据库知识点。 +#### 分布式理论 -#### [共性知识](docs/sql/common) +- [分布式一致性](https://dunwu.github.io/blog/pages/dac0e2/) +- [深入剖析共识性算法 Paxos](https://dunwu.github.io/blog/pages/874539/) +- [深入剖析共识性算法 Raft](https://dunwu.github.io/blog/pages/e40812/) +- [分布式算法 Gossip](https://dunwu.github.io/blog/pages/d15993/) -- [关系型数据库面试总结](docs/sql/common/sql-interview.md) 💯 -- [SQL Cheat Sheet](docs/sql/common/sql-cheat-sheet.md) 是一个 SQL 入门教程。 -- [分布式存储基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/theory/distributed-storage.md) -- [分布式事务基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/theory/distributed-transaction.md) +#### 分布式关键技术 -#### [Mysql](docs/sql/mysql) 📚 +##### 流量调度 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716103611.png) +- [流量控制](https://dunwu.github.io/blog/pages/282676/) +- [负载均衡](https://dunwu.github.io/blog/pages/98a1c1/) +- [服务路由](https://dunwu.github.io/blog/pages/d04ece/) +- [分布式会话基本原理](https://dunwu.github.io/blog/pages/3e66c2/) -- [Mysql 应用指南](docs/sql/mysql/mysql-quickstart.md) ⚡ -- [Mysql 工作流](docs/sql/mysql/mysql-workflow.md) - 关键词:`连接`、`缓存`、`语法分析`、`优化`、`执行引擎`、`redo log`、`bin log`、`两阶段提交` -- [Mysql 索引](docs/sql/mysql/mysql-index.md) - 关键词:`Hash`、`B 树`、`聚簇索引`、`回表` -- [Mysql 锁](docs/sql/mysql/mysql-lock.md) - 关键词:`乐观锁`、`表级锁`、`行级锁`、`意向锁`、`MVCC`、`Next-key 锁` -- [Mysql 事务](docs/sql/mysql/mysql-transaction.md) - 关键词:`ACID`、`AUTOCOMMIT`、`事务隔离级别`、`死锁`、`分布式事务` -- [Mysql 性能优化](docs/sql/mysql/mysql-optimization.md) -- [Mysql 运维](docs/sql/mysql/mysql-ops.md) 🔨 -- [Mysql 配置](docs/sql/mysql/mysql-config.md) -- [Mysql 问题](docs/sql/mysql/mysql-faq.md) +##### 数据调度 -#### 其他关系型数据库 +- [缓存基本原理](https://dunwu.github.io/blog/pages/471208/) +- [读写分离基本原理](https://dunwu.github.io/blog/pages/7da6ca/) +- [分库分表基本原理](https://dunwu.github.io/blog/pages/103382/) +- [分布式 ID 基本原理](https://dunwu.github.io/blog/pages/0b2e59/) +- [分布式事务基本原理](https://dunwu.github.io/blog/pages/910bad/) +- [分布式锁基本原理](https://dunwu.github.io/blog/pages/69360c/) -- [H2 应用指南](docs/sql/h2.md) -- [SqLite 应用指南](docs/sql/sqlite.md) -- [PostgreSQL 应用指南](docs/sql/postgresql.md) +### 其他 -### Nosql 数据库 +- [Nosql 技术选型](docs/12.数据库/01.数据库综合/01.Nosql技术选型.md) +- [数据结构与数据库索引](docs/12.数据库/01.数据库综合/02.数据结构与数据库索引.md) -> [Nosql 数据库](docs/nosql) 整理主流 Nosql 数据库知识点。 +## 数据库中间件 -- [Nosql 技术选型](docs/nosql/nosql-selection.md) +- [ShardingSphere 简介](docs/12.数据库/02.数据库中间件/01.Shardingsphere/01.ShardingSphere简介.md) +- [ShardingSphere Jdbc](docs/12.数据库/02.数据库中间件/01.Shardingsphere/02.ShardingSphereJdbc.md) +- [版本管理中间件 Flyway](docs/12.数据库/02.数据库中间件/02.Flyway.md) -#### [Redis](docs/nosql/redis) 📚 +## 关系型数据库 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200713105627.png) +> [关系型数据库](docs/12.数据库/03.关系型数据库) 整理主流关系型数据库知识点。 -- [Redis 面试总结](docs/nosql/redis/redis-interview.md) 💯 -- [Redis 应用指南](docs/nosql/redis/redis-quickstart.md) ⚡ - 关键词:`内存淘汰`、`事件`、`事务`、`管道`、`发布与订阅` -- [Redis 数据类型和应用](docs/nosql/redis/redis-datatype.md) - 关键词:`STRING`、`HASH`、`LIST`、`SET`、`ZSET`、`BitMap`、`HyperLogLog`、`Geo` -- [Redis 持久化](docs/nosql/redis/redis-persistence.md) - 关键词:`RDB`、`AOF`、`SAVE`、`BGSAVE`、`appendfsync` -- [Redis 复制](docs/nosql/redis/redis-replication.md) - 关键词:`SLAVEOF`、`SYNC`、`PSYNC`、`REPLCONF ACK` -- [Redis 哨兵](docs/nosql/redis/redis-sentinel.md) - 关键词:`Sentinel`、`PING`、`INFO`、`Raft` -- [Redis 集群](docs/nosql/redis/redis-cluster.md) - 关键词:`CLUSTER MEET`、`Hash slot`、`MOVED`、`ASK`、`SLAVEOF no one`、`redis-trib` -- [Redis 实战](docs/nosql/redis/redis-action.md) - 关键词:`缓存`、`分布式锁`、`布隆过滤器` -- [Redis 运维](docs/nosql/redis/redis-ops.md) 🔨 - 关键词:`安装`、`命令`、`集群`、`客户端` +### 关系型数据库综合 -#### [Elasticsearch](docs/nosql/elasticsearch) 📚 +- [关系型数据库面试总结](docs/12.数据库/03.关系型数据库/01.综合/01.关系型数据库面试.md) 💯 +- [SQL 语法基础特性](docs/12.数据库/03.关系型数据库/01.综合/02.SQL语法基础特性.md) +- [SQL 语法高级特性](docs/12.数据库/03.关系型数据库/01.综合/03.SQL语法高级特性.md) +- [扩展 SQL](docs/12.数据库/03.关系型数据库/01.综合/03.扩展SQL.md) +- [SQL Cheat Sheet](docs/12.数据库/03.关系型数据库/01.综合/99.SqlCheatSheet.md) -> Elasticsearch 是一个基于 Lucene 的搜索和数据分析工具,它提供了一个分布式服务。Elasticsearch 是遵从 Apache 开源条款的一款开源产品,是当前主流的企业级搜索引擎。 +### Mysql -- [Elasticsearch 面试总结](docs/nosql/elasticsearch/elasticsearch-interview.md) 💯 -- [Elasticsearch 快速入门](docs/nosql/elasticsearch/Elasticsearch快速入门.md) -- [Elasticsearch 简介](docs/nosql/elasticsearch/Elasticsearch简介.md) -- [Elasticsearch Rest API](docs/nosql/elasticsearch/ElasticsearchRestApi.md) -- [ElasticSearch Java API 之 High Level REST Client](docs/nosql/elasticsearch/ElasticsearchHighLevelRestJavaApi.md) -- [Elasticsearch 索引管理](docs/nosql/elasticsearch/Elasticsearch索引管理.md) -- [Elasticsearch 查询](docs/nosql/elasticsearch/Elasticsearch查询.md) -- [Elasticsearch 高亮](docs/nosql/elasticsearch/Elasticsearch高亮.md) -- [Elasticsearch 排序](docs/nosql/elasticsearch/Elasticsearch排序.md) -- [Elasticsearch 聚合](docs/nosql/elasticsearch/Elasticsearch聚合.md) -- [Elasticsearch 分析器](docs/nosql/elasticsearch/Elasticsearch分析器.md) -- [Elasticsearch 运维](docs/nosql/elasticsearch/Elasticsearch运维.md) -- [Elasticsearch 性能优化](docs/nosql/elasticsearch/Elasticsearch性能优化.md) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716103611.png) -#### HBase +- [Mysql 应用指南](docs/12.数据库/03.关系型数据库/02.Mysql/01.Mysql应用指南.md) ⚡ +- [Mysql 工作流](docs/12.数据库/03.关系型数据库/02.Mysql/02.MySQL工作流.md) - 关键词:`连接`、`缓存`、`语法分析`、`优化`、`执行引擎`、`redo log`、`bin log`、`两阶段提交` +- [Mysql 事务](docs/12.数据库/03.关系型数据库/02.Mysql/03.Mysql事务.md) - 关键词:`ACID`、`AUTOCOMMIT`、`事务隔离级别`、`死锁`、`分布式事务` +- [Mysql 锁](docs/12.数据库/03.关系型数据库/02.Mysql/04.Mysql锁.md) - 关键词:`乐观锁`、`表级锁`、`行级锁`、`意向锁`、`MVCC`、`Next-key 锁` +- [Mysql 索引](docs/12.数据库/03.关系型数据库/02.Mysql/05.Mysql索引.md) - 关键词:`Hash`、`B 树`、`聚簇索引`、`回表` +- [Mysql 性能优化](docs/12.数据库/03.关系型数据库/02.Mysql/06.Mysql性能优化.md) +- [Mysql 运维](docs/12.数据库/03.关系型数据库/02.Mysql/20.Mysql运维.md) 🔨 +- [Mysql 配置](docs/12.数据库/03.关系型数据库/02.Mysql/21.Mysql配置.md) 🔨 +- [Mysql 问题](docs/12.数据库/03.关系型数据库/02.Mysql/99.Mysql常见问题.md) -> [HBase](https://dunwu.github.io/bigdata-tutorial/hbase) 📚 因为常用于大数据项目,所以将其文档和源码整理在 [bigdata-tutorial](https://dunwu.github.io/bigdata-tutorial/) 项目中。 +### 其他 -- [HBase 原理](https://github.com/dunwu/bigdata-tutorial/blob/master/docs/hbase/HBase原理.md) ⚡ -- [HBase 命令](https://github.com/dunwu/bigdata-tutorial/blob/master/docs/hbase/HBase命令.md) -- [HBase 应用](https://github.com/dunwu/bigdata-tutorial/blob/master/docs/hbase/HBase应用.md) -- [HBase 运维](https://github.com/dunwu/bigdata-tutorial/blob/master/docs/hbase/HBase运维.md) +- [PostgreSQL 应用指南](docs/12.数据库/03.关系型数据库/99.其他/01.PostgreSQL.md) +- [H2 应用指南](docs/12.数据库/03.关系型数据库/99.其他/02.H2.md) +- [SqLite 应用指南](docs/12.数据库/03.关系型数据库/99.其他/03.Sqlite.md) -#### [MongoDB](docs/nosql/mongodb) 📚 +## 文档数据库 + +### MongoDB > MongoDB 是一个基于文档的分布式数据库,由 C++ 语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。 > @@ -104,38 +113,208 @@ > > MongoDB 最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。 -- [MongoDB 应用指南](docs/nosql/mongodb/mongodb-quickstart.md) -- [MongoDB 聚合操作](docs/nosql/mongodb/mongodb-aggregation.md) -- [MongoDB 建模](docs/nosql/mongodb/mongodb-model.md) -- [MongoDB 建模示例](docs/nosql/mongodb/mongodb-model-example.md) -- [MongoDB 索引](docs/nosql/mongodb/mongodb-index.md) -- [MongoDB 复制](docs/nosql/mongodb/mongodb-replication.md) -- [MongoDB 分片](docs/nosql/mongodb/mongodb-sharding.md) -- [MongoDB 运维](docs/nosql/mongodb/mongodb-ops.md) +- [MongoDB 应用指南](docs/12.数据库/04.文档数据库/01.MongoDB/01.MongoDB应用指南.md) +- [MongoDB 的 CRUD 操作](docs/12.数据库/04.文档数据库/01.MongoDB/02.MongoDB的CRUD操作.md) +- [MongoDB 聚合操作](docs/12.数据库/04.文档数据库/01.MongoDB/03.MongoDB的聚合操作.md) +- [MongoDB 事务](docs/12.数据库/04.文档数据库/01.MongoDB/04.MongoDB事务.md) +- [MongoDB 建模](docs/12.数据库/04.文档数据库/01.MongoDB/05.MongoDB建模.md) +- [MongoDB 建模示例](docs/12.数据库/04.文档数据库/01.MongoDB/06.MongoDB建模示例.md) +- [MongoDB 索引](docs/12.数据库/04.文档数据库/01.MongoDB/07.MongoDB索引.md) +- [MongoDB 复制](docs/12.数据库/04.文档数据库/01.MongoDB/08.MongoDB复制.md) +- [MongoDB 分片](docs/12.数据库/04.文档数据库/01.MongoDB/09.MongoDB分片.md) +- [MongoDB 运维](docs/12.数据库/04.文档数据库/01.MongoDB/20.MongoDB运维.md) + +## KV 数据库 + +### Redis + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200713105627.png) + +- [Redis 面试总结](docs/12.数据库/05.KV数据库/01.Redis/01.Redis面试总结.md) 💯 +- [Redis 应用指南](docs/12.数据库/05.KV数据库/01.Redis/02.Redis应用指南.md) ⚡ - 关键词:`内存淘汰`、`事件`、`事务`、`管道`、`发布与订阅` +- [Redis 数据类型和应用](docs/12.数据库/05.KV数据库/01.Redis/03.Redis数据类型和应用.md) - 关键词:`STRING`、`HASH`、`LIST`、`SET`、`ZSET`、`BitMap`、`HyperLogLog`、`Geo` +- [Redis 持久化](docs/12.数据库/05.KV数据库/01.Redis/04.Redis持久化.md) - 关键词:`RDB`、`AOF`、`SAVE`、`BGSAVE`、`appendfsync` +- [Redis 复制](docs/12.数据库/05.KV数据库/01.Redis/05.Redis复制.md) - 关键词:`SLAVEOF`、`SYNC`、`PSYNC`、`REPLCONF ACK` +- [Redis 哨兵](docs/12.数据库/05.KV数据库/01.Redis/06.Redis哨兵.md) - 关键词:`Sentinel`、`PING`、`INFO`、`Raft` +- [Redis 集群](docs/12.数据库/05.KV数据库/01.Redis/07.Redis集群.md) - 关键词:`CLUSTER MEET`、`Hash slot`、`MOVED`、`ASK`、`SLAVEOF no one`、`redis-trib` +- [Redis 实战](docs/12.数据库/05.KV数据库/01.Redis/08.Redis实战.md) - 关键词:`缓存`、`分布式锁`、`布隆过滤器` +- [Redis 运维](docs/12.数据库/05.KV数据库/01.Redis/20.Redis运维.md) 🔨 - 关键词:`安装`、`命令`、`集群`、`客户端` -### 中间件 +## 列式数据库 -- [版本管理中间件 flyway](docs/middleware/flyway.md) -- [分库分表中间件 ShardingSphere](docs/middleware/shardingsphere.md) +### HBase -## 📚 资料 +- [HBase 快速入门](docs/12.数据库/06.列式数据库/01.HBase/01.HBase快速入门.md) +- [HBase 数据模型](docs/12.数据库/06.列式数据库/01.HBase/02.HBase数据模型.md) +- [HBase Schema 设计](docs/12.数据库/06.列式数据库/01.HBase/03.HBaseSchema设计.md) +- [HBase 架构](docs/12.数据库/06.列式数据库/01.HBase/04.HBase架构.md) +- [HBase Java API 基础特性](docs/12.数据库/06.列式数据库/01.HBase/10.HBaseJavaApi基础特性.md) +- [HBase Java API 高级特性之过滤器](docs/12.数据库/06.列式数据库/01.HBase/11.HBaseJavaApi高级特性之过滤器.md) +- [HBase Java API 高级特性之协处理器](docs/12.数据库/06.列式数据库/01.HBase/12.HBaseJavaApi高级特性之协处理器.md) +- [HBase Java API 其他高级特性](docs/12.数据库/06.列式数据库/01.HBase/13.HBaseJavaApi其他高级特性.md) +- [HBase 运维](docs/12.数据库/06.列式数据库/01.HBase/21.HBase运维.md) +- [HBase 命令](docs/12.数据库/06.列式数据库/01.HBase/22.HBase命令.md) -### Mysql 资料 +## 搜索引擎数据库 + +### Elasticsearch + +> Elasticsearch 是一个基于 Lucene 的搜索和数据分析工具,它提供了一个分布式服务。Elasticsearch 是遵从 Apache 开源条款的一款开源产品,是当前主流的企业级搜索引擎。 + +- [Elasticsearch 面试总结](docs/12.数据库/07.搜索引擎数据库/01.Elasticsearch/01.Elasticsearch面试总结.md) 💯 +- [Elasticsearch 快速入门](docs/12.数据库/07.搜索引擎数据库/01.Elasticsearch/02.Elasticsearch快速入门.md) +- [Elasticsearch 简介](docs/12.数据库/07.搜索引擎数据库/01.Elasticsearch/03.Elasticsearch简介.md) +- [Elasticsearch 索引](docs/12.数据库/07.搜索引擎数据库/01.Elasticsearch/04.Elasticsearch索引.md) +- [Elasticsearch 查询](docs/12.数据库/07.搜索引擎数据库/01.Elasticsearch/05.Elasticsearch查询.md) +- [Elasticsearch 高亮](docs/12.数据库/07.搜索引擎数据库/01.Elasticsearch/06.Elasticsearch高亮.md) +- [Elasticsearch 排序](docs/12.数据库/07.搜索引擎数据库/01.Elasticsearch/07.Elasticsearch排序.md) +- [Elasticsearch 聚合](docs/12.数据库/07.搜索引擎数据库/01.Elasticsearch/08.Elasticsearch聚合.md) +- [Elasticsearch 分析器](docs/12.数据库/07.搜索引擎数据库/01.Elasticsearch/09.Elasticsearch分析器.md) +- [Elasticsearch 性能优化](docs/12.数据库/07.搜索引擎数据库/01.Elasticsearch/10.Elasticsearch性能优化.md) +- [Elasticsearch Rest API](docs/12.数据库/07.搜索引擎数据库/01.Elasticsearch/11.ElasticsearchRestApi.md) +- [ElasticSearch Java API 之 High Level REST Client](docs/12.数据库/07.搜索引擎数据库/01.Elasticsearch/12.ElasticsearchHighLevelRestJavaApi.md) +- [Elasticsearch 集群和分片](docs/12.数据库/07.搜索引擎数据库/01.Elasticsearch/13.Elasticsearch集群和分片.md) +- [Elasticsearch 运维](docs/12.数据库/07.搜索引擎数据库/01.Elasticsearch/20.Elasticsearch运维.md) + +### Elastic + +- [Elastic 快速入门](docs/12.数据库/07.搜索引擎数据库/02.Elastic/01.Elastic快速入门.md) +- [Elastic 技术栈之 Filebeat](docs/12.数据库/07.搜索引擎数据库/02.Elastic/02.Elastic技术栈之Filebeat.md) +- [Filebeat 运维](docs/12.数据库/07.搜索引擎数据库/02.Elastic/03.Filebeat运维.md) +- [Elastic 技术栈之 Kibana](docs/12.数据库/07.搜索引擎数据库/02.Elastic/04.Elastic技术栈之Kibana.md) +- [Kibana 运维](docs/12.数据库/07.搜索引擎数据库/02.Elastic/05.Kibana运维.md) +- [Elastic 技术栈之 Logstash](docs/12.数据库/07.搜索引擎数据库/02.Elastic/06.Elastic技术栈之Logstash.md) +- [Logstash 运维](docs/12.数据库/07.搜索引擎数据库/02.Elastic/07.Logstash运维.md) + +## 资料 📚 + +### 数据库综合资料 + +- [DB-Engines](https://db-engines.com/en/ranking) - 数据库流行度排名 +- **书籍** + - [《数据密集型应用系统设计》](https://book.douban.com/subject/30329536/) - 这可能是目前最好的分布式存储书籍,强力推荐【进阶】 +- **教程** + - [CMU 15445 数据库基础课程](https://15445.courses.cs.cmu.edu/fall2019/schedule.html) + - [CMU 15721 数据库高级课程](https://15721.courses.cs.cmu.edu/spring2020/schedule.html) + - [检索技术核心 20 讲](https://time.geekbang.org/column/intro/100048401) - 极客教程【进阶】 + - [后端存储实战课](https://time.geekbang.org/column/intro/100046801) - 极客教程【入门】:讲解存储在电商领域的种种应用和一些基本特性 +- **论文** + - [Efficiency in the Columbia Database Query Optimizer](https://15721.courses.cs.cmu.edu/spring2018/papers/15-optimizer1/xu-columbia-thesis1998.pdf) + - [How Good Are Query Optimizers, Really?](http://www.vldb.org/pvldb/vol9/p204-leis.pdf) + - [Architecture of a Database System](https://dsf.berkeley.edu/papers/fntdb07-architecture.pdf) + - [Data Structures for Databases](https://www.cise.ufl.edu/~mschneid/Research/papers/HS05BoCh.pdf) +- **文章** + - [Data Structures and Algorithms for Big Databases](https://people.csail.mit.edu/bradley/BenderKuszmaul-tutorial-xldb12.pdf) + +### 关系型数据库资料 + +- **综合资料** + - [《数据库的索引设计与优化》](https://book.douban.com/subject/26419771/) + - [《SQL 必知必会》](https://book.douban.com/subject/35167240/) - SQL 的基本概念和语法【入门】 +- **Oracle 资料** + - [《Oracle Database 9i/10g/11g 编程艺术》](https://book.douban.com/subject/5402711/) + +#### Mysql 资料 - **官方** - [Mysql 官网](https://www.mysql.com/) - - [Mysql 官方文档](https://dev.mysql.com/doc/refman/8.0/en/) - - [Mysql 官方文档之命令行客户端](https://dev.mysql.com/doc/refman/8.0/en/mysql.html) + - [Mysql 官方文档](https://dev.mysql.com/doc/) + - **官方 PPT** + - [How to Analyze and Tune MySQL Queries for Better Performance](https://www.mysql.com/cn/why-mysql/presentations/tune-mysql-queries-performance/) + - [MySQL Performance Tuning 101](https://www.mysql.com/cn/why-mysql/presentations/mysql-performance-tuning101/) + - [MySQL Performance Schema & Sys Schema](https://www.mysql.com/cn/why-mysql/presentations/mysql-performance-sys-schema/) + - [MySQL Performance: Demystified Tuning & Best Practices](https://www.mysql.com/cn/why-mysql/presentations/mysql-performance-tuning-best-practices/) + - [MySQL Security Best Practices](https://www.mysql.com/cn/why-mysql/presentations/mysql-security-best-practices/) + - [MySQL Cluster Deployment Best Practices](https://www.mysql.com/cn/why-mysql/presentations/mysql-cluster-deployment-best-practices/) + - [MySQL High Availability with InnoDB Cluster](https://www.mysql.com/cn/why-mysql/presentations/mysql-high-availability-innodb-cluster/) - **书籍** - - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) - 经典,适合 DBA 或作为开发者的参考手册 - - [《MySQL 必知必会》](https://book.douban.com/subject/3354490/) - 适合入门者 + - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) - 经典,适合 DBA 或作为开发者的参考手册【进阶】 + - [《MySQL 技术内幕:InnoDB 存储引擎》](https://book.douban.com/subject/24708143/) + - [《MySQL 必知必会》](https://book.douban.com/subject/3354490/) - Mysql 的基本概念和语法【入门】 - **教程** - [runoob.com MySQL 教程](http://www.runoob.com/mysql/mysql-tutorial.html) - 入门级 SQL 教程 - [mysql-tutorial](https://github.com/jaywcjlove/mysql-tutorial) +- **文章** + - [MySQL 索引背后的数据结构及算法原理](http://blog.codinglabs.org/articles/theory-of-mysql-index.html) + - [Some study on database storage internals](https://medium.com/@kousiknath/data-structures-database-storage-internals-1f5ed3619d43) + - [Sharding Pinterest: How we scaled our MySQL fleet](https://medium.com/@Pinterest_Engineering/sharding-pinterest-how-we-scaled-our-mysql-fleet-3f341e96ca6f) + - [Guide to MySQL High Availability](https://www.mysql.com/cn/why-mysql/white-papers/mysql-guide-to-high-availability-solutions/) + - [Choosing MySQL High Availability Solutions](https://dzone.com/articles/choosing-mysql-high-availability-solutions) + - [High availability with MariaDB TX: The definitive guide](https://mariadb.com/sites/default/files/content/Whitepaper_High_availability_with_MariaDB-TX.pdf) + - Mysql 相关经验 + - [Booking.com: Evolution of MySQL System Design](https://www.percona.com/live/mysql-conference-2015/sessions/bookingcom-evolution-mysql-system-design) ,Booking.com 的 MySQL 数据库使用的演化,其中有很多不错的经验分享,我相信也是很多公司会遇到的的问题。 + - [Tracking the Money - Scaling Financial Reporting at Airbnb](https://medium.com/airbnb-engineering/tracking-the-money-scaling-financial-reporting-at-airbnb-6d742b80f040) ,Airbnb 的数据库扩展的经验分享。 + - [Why Uber Engineering Switched from Postgres to MySQL](https://eng.uber.com/mysql-migration/) ,无意比较两个数据库谁好谁不好,推荐这篇 Uber 的长文,主要是想让你从中学习到一些经验和技术细节,这是一篇很不错的文章。 + - Mysql 集群复制 + - [Monitoring Delayed Replication, With A Focus On MySQL](https://engineering.imvu.com/2013/01/09/monitoring-delayed-replication-with-a-focus-on-mysql/) + - [Mitigating replication lag and reducing read load with freno](https://githubengineering.com/mitigating-replication-lag-and-reducing-read-load-with-freno/) + - [Better Parallel Replication for MySQL](https://medium.com/booking-com-infrastructure/better-parallel-replication-for-mysql-14e2d7857813) + - [Evaluating MySQL Parallel Replication Part 2: Slave Group Commit](https://medium.com/booking-com-infrastructure/evaluating-mysql-parallel-replication-part-2-slave-group-commit-459026a141d2) + - [Evaluating MySQL Parallel Replication Part 3: Benchmarks in Production](https://medium.com/booking-com-infrastructure/evaluating-mysql-parallel-replication-part-3-benchmarks-in-production-db5811058d74) + - [Evaluating MySQL Parallel Replication Part 4: More Benchmarks in Production](https://medium.com/booking-com-infrastructure/evaluating-mysql-parallel-replication-part-4-more-benchmarks-in-production-49ee255043ab) + - [Evaluating MySQL Parallel Replication Part 4, Annex: Under the Hood](https://medium.com/booking-com-infrastructure/evaluating-mysql-parallel-replication-part-4-annex-under-the-hood-eb456cf8b2fb) + - Mysql 数据分区 + - [StackOverflow: MySQL sharding approaches?](https://stackoverflow.com/questions/5541421/mysql-sharding-approaches) + - [Why you don’t want to shard](https://www.percona.com/blog/2009/08/06/why-you-dont-want-to-shard/) + - [How to Scale Big Data Applications](https://www.percona.com/sites/default/files/presentations/How to Scale Big Data Applications.pdf) + - [MySQL Sharding with ProxySQL](https://www.percona.com/blog/2016/08/30/mysql-sharding-with-proxysql/) + - 各公司的 Mysql 数据分区经验分享 + - [MailChimp: Using Shards to Accommodate Millions of Users](https://devs.mailchimp.com/blog/using-shards-to-accommodate-millions-of-users/) + - [Uber: Code Migration in Production: Rewriting the Sharding Layer of Uber’s Schemaless Datastore](https://eng.uber.com/schemaless-rewrite/) + - [Sharding & IDs at Instagram](https://instagram-engineering.com/sharding-ids-at-instagram-1cf5a71e5a5c) + - [Airbnb: How We Partitioned Airbnb’s Main Database in Two Weeks](https://medium.com/airbnb-engineering/how-we-partitioned-airbnb-s-main-database-in-two-weeks-55f7e006ff21) - **更多资源** - - [awesome-mysql](https://github.com/jobbole/awesome-mysql-cn) + - [awesome-mysql](https://github.com/jobbole/awesome-mysql-cn) - MySQL 的资源列表 -### Redis 资料 +### Nosql 数据库综合 + +- Martin Fowler 在 YouTube 上分享的 NoSQL 介绍 [Introduction To NoSQL](https://youtu.be/qI_g07C_Q5I), 以及他参与编写的 [NoSQL Distilled - NoSQL 精粹](https://book.douban.com/subject/25662138/),这本书才 100 多页,是本难得的关于 NoSQL 的书,很不错,非常易读。 +- [NoSQL Databases: a Survey and Decision Guidance](https://medium.com/baqend-blog/nosql-databases-a-survey-and-decision-guidance-ea7823a822d#.nhzop4d23),这篇文章可以带你自上而下地从 CAP 原理到开始了解 NoSQL 的种种技术,是一篇非常不错的文章。 +- [Distribution, Data, Deployment: Software Architecture Convergence in Big Data Systems](https://resources.sei.cmu.edu/asset_files/WhitePaper/2014_019_001_90915.pdf),这是卡内基·梅隆大学的一篇讲分布式大数据系统的论文。其中主要讨论了在大数据时代下的软件工程中的一些关键点,也说到了 NoSQL 数据库。 +- [No Relation: The Mixed Blessings of Non-Relational Databases](http://ianvarley.com/UT/MR/Varley_MastersReport_Full_2009-08-07.pdf),这篇论文虽然有点年代久远。但这篇论文是 HBase 的基础,你花上一点时间来读读,就可以了解到,对各种非关系型数据存储优缺点的一个很好的比较。 +- [NoSQL Data Modeling Techniques](https://highlyscalable.wordpress.com/2012/03/01/nosql-data-modeling-techniques/) ,NoSQL 建模技术。这篇文章我曾经翻译在了 CoolShell 上,标题为 [NoSQL 数据建模技术](https://coolshell.cn/articles/7270.htm),供你参考。 + - [MongoDB - Data Modeling Introduction](https://docs.mongodb.com/manual/core/data-modeling-introduction/) ,虽然这是 MongoDB 的数据建模介绍,但是其很多观点可以用于其它的 NoSQL 数据库。 + - [Firebase - Structure Your Database](https://firebase.google.com/docs/database/android/structure-data) ,Google 的 Firebase 数据库使用 JSON 建模的一些最佳实践。 +- 因为 CAP 原理,所以当你需要选择一个 NoSQL 数据库的时候,你应该看看这篇文档 [Visual Guide to NoSQL Systems](http://blog.nahurst.com/visual-guide-to-nosql-systems)。 + +选 SQL 还是 NoSQL,这里有两篇文章,值得你看看。 + +- [SQL vs. NoSQL Databases: What’s the Difference?](https://www.upwork.com/hiring/data/sql-vs-nosql-databases-whats-the-difference/) +- [Salesforce: SQL or NoSQL](https://engineering.salesforce.com/sql-or-nosql-9eaf1d92545b) + +### 列式数据库资料 + +#### Cassandra 资料 + +- 沃尔玛实验室有两篇文章值得一读。 + - [Avoid Pitfalls in Scaling Cassandra Cluster at Walmart](https://medium.com/walmartlabs/avoid-pitfalls-in-scaling-your-cassandra-cluster-lessons-and-remedies-a71ca01f8c04) + - [Storing Images in Cassandra at Walmart](https://medium.com/walmartlabs/building-object-store-storing-images-in-cassandra-walmart-scale-a6b9c02af593) +- [Yelp: How We Scaled Our Ad Analytics with Apache Cassandra](https://engineeringblog.yelp.com/2016/08/how-we-scaled-our-ad-analytics-with-cassandra.html) ,Yelp 的这篇博客也有一些相关的经验和教训。 +- [Discord: How Discord Stores Billions of Messages](https://blog.discordapp.com/how-discord-stores-billions-of-messages-7fa6ec7ee4c7) ,Discord 公司分享的一个如何存储十亿级消息的技术文章。 +- [Cassandra at Instagram](https://www.slideshare.net/DataStax/cassandra-at-instagram-2016) ,Instagram 的一个 PPT,其中介绍了 Instagram 中是怎么使用 Cassandra 的。 +- [Netflix: Benchmarking Cassandra Scalability on AWS - Over a million writes per second](https://medium.com/netflix-techblog/benchmarking-cassandra-scalability-on-aws-over-a-million-writes-per-second-39f45f066c9e) ,Netflix 公司在 AWS 上给 Cassandra 做的一个 Benchmark。 + +#### HBase 资料 + +- [Imgur Notification: From MySQL to HBASE](https://medium.com/imgur-engineering/imgur-notifications-from-mysql-to-hbase-9dba6fc44183) +- [Pinterest: Improving HBase Backup Efficiency](https://medium.com/@Pinterest_Engineering/improving-hbase-backup-efficiency-at-pinterest-86159da4b954) +- [IBM : Tuning HBase performance](https://www.ibm.com/support/knowledgecenter/en/SSPT3X_2.1.2/com.ibm.swg.im.infosphere.biginsights.analyze.doc/doc/bigsql_TuneHbase.html) +- [HBase File Locality in HDFS](http://www.larsgeorge.com/2010/05/hbase-file-locality-in-hdfs.html) +- [Apache Hadoop Goes Realtime at Facebook](http://borthakur.com/ftp/RealtimeHadoopSigmod2011.pdf) +- [Storage Infrastructure Behind Facebook Messages: Using HBase at Scale](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.294.8459&rep=rep1&type=pdf) +- [GitHub: Awesome HBase](https://github.com/rayokota/awesome-hbase) + +针对于 HBase 有两本书你可以考虑一下。 + +- 首先,先推荐两本书,一本是偏实践的《[HBase 实战](https://book.douban.com/subject/25706541/)》,另一本是偏大而全的手册型的《[HBase 权威指南](https://book.douban.com/subject/10748460/)》。 +- 当然,你也可以看看官方的 [The Apache HBase™ Reference Guide](http://hbase.apache.org/0.94/book/book.html) +- 另外两个列数据库: + - [ClickHouse - Open Source Distributed Column Database at Yandex](https://clickhouse.yandex/) + - [Scaling Redshift without Scaling Costs at GIPHY](https://engineering.giphy.com/scaling-redshift-without-scaling-costs/) + +### KV 数据库资料 + +#### Redis 资料 - **官网** - [Redis 官网](https://redis.io/) @@ -155,8 +334,24 @@ - [redisson 官方文档(英文)](https://github.com/redisson/redisson/wiki/Table-of-Content) - [CRUG | Redisson PRO vs. Jedis: Which Is Faster? 翻译](https://www.jianshu.com/p/82f0d5abb002) - [redis 分布锁 Redisson 性能测试](https://blog.csdn.net/everlasting_188/article/details/51073505) +- **文章** + - [Learn Redis the hard way (in production) at Trivago](http://tech.trivago.com/2017/01/25/learn-redis-the-hard-way-in-production/) + - [Twitter: How Twitter Uses Redis To Scale - 105TB RAM, 39MM QPS, 10,000+ Instances](http://highscalability.com/blog/2014/9/8/how-twitter-uses-redis-to-scale-105tb-ram-39mm-qps-10000-ins.html) + - [Slack: Scaling Slack’s Job Queue - Robustly Handling Billions of Tasks in Milliseconds Using Kafka and Redis](https://slack.engineering/scaling-slacks-job-queue-687222e9d100) + - [GitHub: Moving persistent data out of Redis at GitHub](https://githubengineering.com/moving-persistent-data-out-of-redis/) + - [Instagram: Storing Hundreds of Millions of Simple Key-Value Pairs in Redis](https://engineering.instagram.com/storing-hundreds-of-millions-of-simple-key-value-pairs-in-redis-1091ae80f74c) + - [Redis in Chat Architecture of Twitch (from 27:22)](https://www.infoq.com/presentations/twitch-pokemon) + - [Deliveroo: Optimizing Session Key Storage in Redis](https://deliveroo.engineering/2016/10/07/optimising-session-key-storage.html) + - [Deliveroo: Optimizing Redis Storage](https://deliveroo.engineering/2017/01/19/optimising-membership-queries.html) + - [GitHub: Awesome Redis](https://github.com/JamzyWang/awesome-redis) + +### 文档数据库资料 -### MongoDB 资料 +- [Couchbase Ecosystem at LinkedIn](https://engineering.linkedin.com/blog/2017/12/couchbase-ecosystem-at-linkedin) +- [SimpleDB at Zendesk](https://medium.com/zendesk-engineering/resurrecting-amazon-simpledb-9404034ec506) +- [Data Points - What the Heck Are Document Databases?](https://msdn.microsoft.com/en-us/magazine/hh547103.aspx) + +#### MongoDB 资料 - **官方** - [MongoDB 官网](https://www.mongodb.com/) @@ -169,7 +364,61 @@ - [mongodb-json-files](https://github.com/ozlerhakan/mongodb-json-files) - **文章** - [Introduction to MongoDB](https://www.slideshare.net/mdirolf/introduction-to-mongodb) + - [eBay: Building Mission-Critical Multi-Data Center Applications with MongoDB](https://www.mongodb.com/blog/post/ebay-building-mission-critical-multi-data-center-applications-with-mongodb) + - [The AWS and MongoDB Infrastructure of Parse: Lessons Learned](https://medium.baqend.com/parse-is-gone-a-few-secrets-about-their-infrastructure-91b3ab2fcf71) + - [Migrating Mountains of Mongo Data](https://medium.com/build-addepar/migrating-mountains-of-mongo-data-63e530539952) +- **更多资源** + - [Github: Awesome MongoDB](https://github.com/ramnes/awesome-mongodb) + +### 搜索引擎数据库资料 -## 🚪 传送 +#### ElasticSearch -◾ 🏠 [DB-TUTORIAL 首页](https://github.com/dunwu/db-tutorial) ◾ 🎯 [我的博客](https://github.com/dunwu/blog) ◾ +- **官方** + - [Elasticsearch 官网](https://www.elastic.co/cn/products/elasticsearch) + - [Elasticsearch Github](https://github.com/elastic/elasticsearch) + - [Elasticsearch 官方文档](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) + - [Elasticsearch: The Definitive Guide](https://www.elastic.co/guide/en/elasticsearch/guide/master/index.html) - ElasticSearch 官方学习资料 +- **书籍** + - [《Elasticsearch 实战》](https://book.douban.com/subject/30380439/) +- **教程** + - [ELK Stack 权威指南](https://github.com/chenryn/logstash-best-practice-cn) + - [Elasticsearch 教程](https://www.knowledgedict.com/tutorial/elasticsearch-intro.html) +- **文章** + - [Elasticsearch+Logstash+Kibana 教程](https://www.cnblogs.com/xing901022/p/4704319.html) + - [ELK(Elasticsearch、Logstash、Kibana)安装和配置](https://github.com/judasn/Linux-Tutorial/blob/master/ELK-Install-And-Settings.md) + - **性能调优相关**的工程实践 + - [Elasticsearch Performance Tuning Practice at eBay](https://www.ebayinc.com/stories/blogs/tech/elasticsearch-performance-tuning-practice-at-ebay/) + - [Elasticsearch at Kickstarter](https://kickstarter.engineering/elasticsearch-at-kickstarter-db3c487887fc) + - [9 tips on ElasticSearch configuration for high performance](https://www.loggly.com/blog/nine-tips-configuring-elasticsearch-for-high-performance/) + - [Elasticsearch In Production - Deployment Best Practices](https://medium.com/@abhidrona/elasticsearch-deployment-best-practices-d6c1323b25d7) +- **更多资源** + - [GitHub: Awesome ElasticSearch](https://github.com/dzharii/awesome-elasticsearch) + +### 图数据库 + +- 首先是 IBM Devloperworks 上的两个简介性的 PPT。 + - [Intro to graph databases, Part 1, Graph databases and the CRUD operations](https://www.ibm.com/developerworks/library/cl-graph-database-1/cl-graph-database-1-pdf.pdf) + - [Intro to graph databases, Part 2, Building a recommendation engine with a graph database](https://www.ibm.com/developerworks/library/cl-graph-database-2/cl-graph-database-2-pdf.pdf) +- 然后是一本免费的电子书《[Graph Database](http://graphdatabases.com)》。 +- 接下来是一些图数据库的介绍文章。 + - [Handling Billions of Edges in a Graph Database](https://www.infoq.com/presentations/graph-database-scalability) + - [Neo4j case studies with Walmart, eBay, AirBnB, NASA, etc](https://neo4j.com/customers/) + - [FlockDB: Distributed Graph Database for Storing Adjacency Lists at Twitter](https://blog.twitter.com/engineering/en_us/a/2010/introducing-flockdb.html) + - [JanusGraph: Scalable Graph Database backed by Google, IBM and Hortonworks](https://architecht.io/google-ibm-back-new-open-source-graph-database-project-janusgraph-1d74fb78db6b) + - [Amazon Neptune](https://aws.amazon.com/neptune/) + +### 时序数据库 + +- [What is Time-Series Data & Why We Need a Time-Series Database](https://blog.timescale.com/what-the-heck-is-time-series-data-and-why-do-i-need-a-time-series-database-dcf3b1b18563) +- [Time Series Data: Why and How to Use a Relational Database instead of NoSQL](https://blog.timescale.com/time-series-data-why-and-how-to-use-a-relational-database-instead-of-nosql-d0cd6975e87c) +- [Beringei: High-performance Time Series Storage Engine @Facebook](https://code.facebook.com/posts/952820474848503/beringei-a-high-performance-time-series-storage-engine/) +- [Introducing Atlas: Netflix’s Primary Telemetry Platform @Netflix](https://medium.com/netflix-techblog/introducing-atlas-netflixs-primary-telemetry-platform-bd31f4d8ed9a) +- [Building a Scalable Time Series Database on PostgreSQL](https://blog.timescale.com/when-boring-is-awesome-building-a-scalable-time-series-database-on-postgresql-2900ea453ee2) +- [Scaling Time Series Data Storage - Part I @Netflix](https://medium.com/netflix-techblog/scaling-time-series-data-storage-part-i-ec2b6d44ba39) +- [Design of a Cost Efficient Time Series Store for Big Data](https://medium.com/@leventov/design-of-a-cost-efficient-time-series-store-for-big-data-88c5dc41af8e) +- [GitHub: Awesome Time-Series Database](https://github.com/xephonhq/awesome-time-series-database) + +## 传送 🚪 + +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ diff --git "a/assets/eddx\346\250\241\346\235\277.eddx" "b/assets/eddx\346\250\241\346\235\277.eddx" deleted file mode 100644 index f56e27cd..00000000 Binary files "a/assets/eddx\346\250\241\346\235\277.eddx" and /dev/null differ diff --git a/assets/elasticsearch/Elasticsearch.eddx b/assets/elasticsearch/Elasticsearch.eddx deleted file mode 100644 index a9969e7b..00000000 Binary files a/assets/elasticsearch/Elasticsearch.eddx and /dev/null differ diff --git a/assets/elasticsearch/Elasticsearch.xmind b/assets/elasticsearch/Elasticsearch.xmind deleted file mode 100644 index 3896fa3c..00000000 Binary files a/assets/elasticsearch/Elasticsearch.xmind and /dev/null differ diff --git a/assets/mongodb.xmind b/assets/mongodb.xmind deleted file mode 100644 index 42041666..00000000 Binary files a/assets/mongodb.xmind and /dev/null differ diff --git a/assets/mysql/Mysql.xmind b/assets/mysql/Mysql.xmind deleted file mode 100644 index 751d872e..00000000 Binary files a/assets/mysql/Mysql.xmind and /dev/null differ diff --git "a/assets/mysql/Mysql\344\274\230\345\214\226.xmind" "b/assets/mysql/Mysql\344\274\230\345\214\226.xmind" deleted file mode 100644 index 79ca3505..00000000 Binary files "a/assets/mysql/Mysql\344\274\230\345\214\226.xmind" and /dev/null differ diff --git a/assets/redis/Redis.xmind b/assets/redis/Redis.xmind deleted file mode 100644 index cd8a6823..00000000 Binary files a/assets/redis/Redis.xmind and /dev/null differ diff --git "a/assets/redis/redis\345\223\250\345\205\265.eddx" "b/assets/redis/redis\345\223\250\345\205\265.eddx" deleted file mode 100644 index 03c61373..00000000 Binary files "a/assets/redis/redis\345\223\250\345\205\265.eddx" and /dev/null differ diff --git "a/assets/redis/redis\345\244\215\345\210\266.eddx" "b/assets/redis/redis\345\244\215\345\210\266.eddx" deleted file mode 100644 index f524c294..00000000 Binary files "a/assets/redis/redis\345\244\215\345\210\266.eddx" and /dev/null differ diff --git "a/assets/redis/redis\346\214\201\344\271\205\345\214\226.eddx" "b/assets/redis/redis\346\214\201\344\271\205\345\214\226.eddx" deleted file mode 100644 index 3ef62b7c..00000000 Binary files "a/assets/redis/redis\346\214\201\344\271\205\345\214\226.eddx" and /dev/null differ diff --git "a/assets/redis/redis\351\233\206\347\276\244.eddx" "b/assets/redis/redis\351\233\206\347\276\244.eddx" deleted file mode 100644 index 9dd88dc6..00000000 Binary files "a/assets/redis/redis\351\233\206\347\276\244.eddx" and /dev/null differ diff --git a/assets/sql.xmind b/assets/sql.xmind deleted file mode 100644 index 641ab6b6..00000000 Binary files a/assets/sql.xmind and /dev/null differ diff --git "a/assets/\346\225\260\346\215\256\345\272\223\346\212\200\346\234\257\351\270\237\347\236\260.xmind" "b/assets/\346\225\260\346\215\256\345\272\223\346\212\200\346\234\257\351\270\237\347\236\260.xmind" deleted file mode 100644 index a0230584..00000000 Binary files "a/assets/\346\225\260\346\215\256\345\272\223\346\212\200\346\234\257\351\270\237\347\236\260.xmind" and /dev/null differ diff --git a/codes/javadb/elasticsearch/elasticsearch6/pom.xml b/codes/javadb/elasticsearch/elasticsearch6/pom.xml new file mode 100644 index 00000000..72683b1d --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/pom.xml @@ -0,0 +1,69 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.7.7 + + + io.github.dunwu + javadb-elasticsearch6 + 1.0.0 + jar + + + 1.8 + ${java.version} + ${java.version} + UTF-8 + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-aop + + + org.springframework.boot + spring-boot-starter-web + + + org.elasticsearch.client + elasticsearch-rest-high-level-client + + + org.projectlombok + lombok + + + cn.hutool + hutool-all + 5.8.25 + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.elasticsearch.client + elasticsearch-rest-high-level-client + 6.4.3 + + + org.elasticsearch + elasticsearch + 6.4.3 + + + + + diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/ElasticsearchFactory.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/ElasticsearchFactory.java new file mode 100644 index 00000000..33109cb0 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/ElasticsearchFactory.java @@ -0,0 +1,173 @@ +package io.github.dunwu.javadb.elasticsearch; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.HttpHost; +import org.elasticsearch.client.RestClient; +import org.elasticsearch.client.RestClientBuilder; +import org.elasticsearch.client.RestHighLevelClient; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Elasticsearch 客户端实例工厂 + * + * @author Zhang Peng + * @date 2024-02-07 + */ +@Slf4j +public class ElasticsearchFactory { + + public static int CONNECT_TIMEOUT_MILLIS = 1000; + + public static int SOCKET_TIMEOUT_MILLIS = 30000; + + public static int CONNECTION_REQUEST_TIMEOUT_MILLIS = 500; + + public static int MAX_CONN_TOTAL = 30; + + public static int MAX_CONN_PER_ROUTE = 10; + + public static RestClient newRestClient() { + // 从配置中心读取环境变量 + String env = "test"; + return newRestClient(env); + } + + public static RestClient newRestClient(String env) { + String hostsConfig = getDefaultEsAddress(env); + List hosts = StrUtil.split(hostsConfig, ","); + return newRestClient(hosts); + } + + public static RestClient newRestClient(Collection hosts) { + HttpHost[] httpHosts = toHttpHostList(hosts); + RestClientBuilder builder = getRestClientBuilder(httpHosts); + if (builder == null) { + return null; + } + try { + return builder.build(); + } catch (Exception e) { + log.error("【ES】connect failed.", e); + return null; + } + } + + public static RestHighLevelClient newRestHighLevelClient() { + // 从配置中心读取环境变量 + String env = "test"; + return newRestHighLevelClient(env); + } + + public static RestHighLevelClient newRestHighLevelClient(String env) { + String hostsConfig = getDefaultEsAddress(env); + List hosts = StrUtil.split(hostsConfig, ","); + return newRestHighLevelClient(hosts); + } + + public static RestHighLevelClient newRestHighLevelClient(Collection hosts) { + HttpHost[] httpHosts = toHttpHostList(hosts); + RestClientBuilder builder = getRestClientBuilder(httpHosts); + if (builder == null) { + return null; + } + try { + return new RestHighLevelClient(builder); + } catch (Exception e) { + log.error("【ES】connect failed.", e); + return null; + } + } + + public static ElasticsearchTemplate newElasticsearchTemplate() { + // 从配置中心读取环境变量 + String env = "test"; + return newElasticsearchTemplate(env); + } + + public static ElasticsearchTemplate newElasticsearchTemplate(String env) { + String hostsConfig = getDefaultEsAddress(env); + List hosts = StrUtil.split(hostsConfig, ","); + return newElasticsearchTemplate(hosts); + } + + public static ElasticsearchTemplate newElasticsearchTemplate(Collection hosts) { + RestHighLevelClient client = newRestHighLevelClient(hosts); + if (client == null) { + return null; + } + return new ElasticsearchTemplate(client); + } + + public static ElasticsearchTemplate newElasticsearchTemplate(RestHighLevelClient client) { + if (client == null) { + return null; + } + return new ElasticsearchTemplate(client); + } + + public static RestClientBuilder getRestClientBuilder(HttpHost[] httpHosts) { + if (ArrayUtil.isEmpty(httpHosts)) { + log.error("【ES】connect failed. hosts are empty."); + return null; + } + RestClientBuilder restClientBuilder = RestClient.builder(httpHosts); + restClientBuilder.setRequestConfigCallback(builder -> { + builder.setConnectTimeout(CONNECT_TIMEOUT_MILLIS); + builder.setSocketTimeout(SOCKET_TIMEOUT_MILLIS); + builder.setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT_MILLIS); + return builder; + }); + restClientBuilder.setHttpClientConfigCallback(builder -> { + builder.setMaxConnTotal(MAX_CONN_TOTAL); + builder.setMaxConnPerRoute(MAX_CONN_PER_ROUTE); + return builder; + }); + return restClientBuilder; + } + + private static HttpHost[] toHttpHostList(Collection hosts) { + if (CollectionUtil.isEmpty(hosts)) { + return new HttpHost[0]; + } + List list = hosts.stream().map(ElasticsearchFactory::toHttpHost).collect(Collectors.toList()); + if (CollectionUtil.isEmpty(list)) { + return new HttpHost[0]; + } + return list.toArray(new HttpHost[0]); + } + + public static HttpHost toHttpHost(String host) { + List params = StrUtil.split(host, ":"); + return new HttpHost(params.get(0), Integer.parseInt(params.get(1)), "http"); + } + + public static String getDefaultEsAddress() { + // 从配置中心读取环境变量 + String env = "test"; + return getDefaultEsAddress(env); + } + + private static String getDefaultEsAddress(String env) { + String defaultAddress; + switch (env) { + case "prd": + defaultAddress = "127.0.0.1:9200,127.0.0.2:9200,127.0.0.3:9200"; + break; + case "pre": + defaultAddress = "127.0.0.1:9200"; + break; + case "test": + default: + defaultAddress = "127.0.0.1:9200"; + break; + } + return defaultAddress; + } + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/ElasticsearchTemplate.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/ElasticsearchTemplate.java new file mode 100644 index 00000000..5e627cbe --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/ElasticsearchTemplate.java @@ -0,0 +1,701 @@ +package io.github.dunwu.javadb.elasticsearch; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; +import io.github.dunwu.javadb.elasticsearch.entity.BaseEsEntity; +import io.github.dunwu.javadb.elasticsearch.entity.common.PageData; +import io.github.dunwu.javadb.elasticsearch.entity.common.ScrollData; +import io.github.dunwu.javadb.elasticsearch.util.JsonUtil; +import lombok.extern.slf4j.Slf4j; +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.DocWriteResponse; +import org.elasticsearch.action.admin.indices.alias.Alias; +import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; +import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest; +import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; +import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; +import org.elasticsearch.action.admin.indices.get.GetIndexRequest; +import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; +import org.elasticsearch.action.bulk.BackoffPolicy; +import org.elasticsearch.action.bulk.BulkProcessor; +import org.elasticsearch.action.bulk.BulkRequest; +import org.elasticsearch.action.bulk.BulkResponse; +import org.elasticsearch.action.delete.DeleteRequest; +import org.elasticsearch.action.get.GetRequest; +import org.elasticsearch.action.get.GetResponse; +import org.elasticsearch.action.get.MultiGetItemResponse; +import org.elasticsearch.action.get.MultiGetRequest; +import org.elasticsearch.action.get.MultiGetResponse; +import org.elasticsearch.action.index.IndexRequest; +import org.elasticsearch.action.index.IndexResponse; +import org.elasticsearch.action.search.ClearScrollRequest; +import org.elasticsearch.action.search.ClearScrollResponse; +import org.elasticsearch.action.search.SearchRequest; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.action.search.SearchScrollRequest; +import org.elasticsearch.action.support.WriteRequest; +import org.elasticsearch.action.support.master.AcknowledgedResponse; +import org.elasticsearch.action.update.UpdateRequest; +import org.elasticsearch.action.update.UpdateResponse; +import org.elasticsearch.client.GetAliasesResponse; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.cluster.metadata.AliasMetaData; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.unit.ByteSizeUnit; +import org.elasticsearch.common.unit.ByteSizeValue; +import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.index.query.BoolQueryBuilder; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.search.Scroll; +import org.elasticsearch.search.SearchHits; +import org.elasticsearch.search.builder.SearchSourceBuilder; +import org.elasticsearch.search.sort.SortOrder; + +import java.io.Closeable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * ES 工具类 + * + * @author Zhang Peng + * @date 2023-06-27 + */ +@Slf4j +public class ElasticsearchTemplate implements Closeable { + + private final RestHighLevelClient client; + + public ElasticsearchTemplate(RestHighLevelClient client) { + this.client = client; + } + + public RestHighLevelClient getClient() { + return client; + } + + public BulkProcessor newAsyncBulkProcessor() { + BulkProcessor.Listener listener = new BulkProcessor.Listener() { + @Override + public void beforeBulk(long executionId, BulkRequest request) { + } + + @Override + public void afterBulk(long executionId, BulkRequest request, BulkResponse response) { + if (response.hasFailures()) { + log.error("【ES】Bulk [{}] executed with failures,response = {}", executionId, + response.buildFailureMessage()); + } + } + + @Override + public void afterBulk(long executionId, BulkRequest request, Throwable failure) { + } + }; + + int bulkTimeout = 30; + int bulkActions = 1000; + int bulkSize = 5; + int concurrentRequests = 2; + int flushInterval = 1000; + int retryInterval = 100; + int retryLimit = 3; + BiConsumer> bulkConsumer = + (request, bulkListener) -> client.bulkAsync(request, RequestOptions.DEFAULT, bulkListener); + BackoffPolicy backoffPolicy = + BackoffPolicy.constantBackoff(TimeValue.timeValueMillis(retryInterval), retryLimit); + BulkProcessor bulkProcessor = BulkProcessor.builder(bulkConsumer, listener) + // 1000条数据请求执行一次bulk + .setBulkActions(bulkActions) + // 5mb的数据刷新一次bulk + .setBulkSize(new ByteSizeValue(bulkSize, ByteSizeUnit.MB)) + // 并发请求数量, 0不并发, 1并发允许执行 + .setConcurrentRequests(concurrentRequests) + // 刷新间隔时间 + .setFlushInterval(TimeValue.timeValueMillis(flushInterval)) + // 重试次数、间隔时间 + .setBackoffPolicy(backoffPolicy).build(); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + try { + bulkProcessor.flush(); + bulkProcessor.awaitClose(bulkTimeout, TimeUnit.SECONDS); + } catch (Exception e) { + log.error("【ES】Failed to close bulkProcessor", e); + } + log.info("【ES】bulkProcessor closed!"); + })); + return bulkProcessor; + } + + // ==================================================================== + // 索引管理操作 + // ==================================================================== + + public void createIndex(String index, String type, String alias, int shard, int replica) throws IOException { + + if (StrUtil.isBlank(index) || StrUtil.isBlank(type)) { + throw new ElasticsearchException("【ES】index、type 不能为空!"); + } + + CreateIndexRequest request = new CreateIndexRequest(index); + if (StrUtil.isNotBlank(alias)) { + request.alias(new Alias(alias)); + } + + Settings.Builder settings = + Settings.builder().put("index.number_of_shards", shard).put("index.number_of_replicas", replica); + request.settings(settings); + AcknowledgedResponse response = client.indices().create(request, RequestOptions.DEFAULT); + if (!response.isAcknowledged()) { + String msg = StrUtil.format("【ES】创建索引失败!index: {}, type: {}", index, type); + throw new ElasticsearchException(msg); + } + } + + public void deleteIndex(String index) throws IOException { + DeleteIndexRequest request = new DeleteIndexRequest(index); + AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT); + if (!response.isAcknowledged()) { + String msg = StrUtil.format("【ES】删除索引失败!index: {}", index); + throw new ElasticsearchException(msg); + } + } + + public void updateAlias(String index, String alias) throws IOException { + IndicesAliasesRequest request = new IndicesAliasesRequest(); + IndicesAliasesRequest.AliasActions aliasAction = + new IndicesAliasesRequest.AliasActions(IndicesAliasesRequest.AliasActions.Type.ADD).index(index) + .alias(alias); + request.addAliasAction(aliasAction); + AcknowledgedResponse response = client.indices().updateAliases(request, RequestOptions.DEFAULT); + if (!response.isAcknowledged()) { + String msg = StrUtil.format("【ES】更新索引别名失败!index: {}, alias: {}", index, alias); + throw new ElasticsearchException(msg); + } + } + + public boolean isIndexExists(String index) throws IOException { + GetIndexRequest request = new GetIndexRequest(); + return client.indices().exists(request.indices(index), RequestOptions.DEFAULT); + } + + public Set getIndexSet(String alias) throws IOException { + GetAliasesRequest request = new GetAliasesRequest(alias); + GetAliasesResponse response = client.indices().getAlias(request, RequestOptions.DEFAULT); + if (StrUtil.isNotBlank(response.getError())) { + String msg = StrUtil.format("【ES】获取索引失败!alias: {}, error: {}", alias, response.getError()); + throw new ElasticsearchException(msg); + } + if (response.getException() != null) { + throw response.getException(); + } + Map> aliasMap = response.getAliases(); + return aliasMap.keySet(); + } + + public void setMapping(String index, String type, Map propertiesMap) throws IOException { + + if (MapUtil.isEmpty(propertiesMap)) { + throw new ElasticsearchException("【ES】设置 mapping 的 properties 不能为空!"); + } + + PutMappingRequest request = new PutMappingRequest(index).type(type); + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + builder.startObject(type); + builder.startObject("properties"); + + for (Map.Entry entry : propertiesMap.entrySet()) { + + String field = entry.getKey(); + String fieldType = entry.getValue(); + if (StrUtil.isBlank(field) || StrUtil.isBlank(fieldType)) { + continue; + } + + builder.startObject(field); + { + builder.field("type", fieldType); + } + builder.endObject(); + } + + builder.endObject(); + builder.endObject(); + builder.endObject(); + request.source(builder); + AcknowledgedResponse response = client.indices().putMapping(request, RequestOptions.DEFAULT); + if (!response.isAcknowledged()) { + throw new ElasticsearchException("【ES】设置 mapping 失败!"); + } + } + + // ==================================================================== + // CRUD 操作 + // ==================================================================== + + public T save(String index, String type, T entity) throws IOException { + + if (entity == null) { + log.warn("【ES】save 实体为空!"); + return null; + } + + Map map = toMap(entity); + if (MapUtil.isEmpty(map)) { + log.warn("【ES】save 实体数据为空!"); + return null; + } + + IndexRequest request = new IndexRequest(index, type).source(map); + if (entity.getDocId() != null) { + request.id(entity.getDocId()); + } + IndexResponse response = client.index(request, RequestOptions.DEFAULT); + if (response == null) { + log.warn("【ES】save 响应结果为空!"); + return null; + } + + if (response.getResult() == DocWriteResponse.Result.CREATED + || response.getResult() == DocWriteResponse.Result.UPDATED) { + return entity; + } else { + log.warn("【ES】save 失败,result: {}!", response.getResult()); + return null; + } + } + + public boolean saveBatch(String index, String type, Collection list) + throws IOException { + + if (CollectionUtil.isEmpty(list)) { + return true; + } + + BulkRequest bulkRequest = toBulkIndexRequest(index, type, list); + BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT); + if (response == null) { + log.warn("【ES】saveBatch 失败,result 为空!list: {}", JsonUtil.toString(list)); + return false; + } + if (response.hasFailures()) { + log.warn("【ES】saveBatch 失败,result: {}!", response.buildFailureMessage()); + return false; + } + return true; + } + + public void asyncSaveBatch(String index, String type, Collection list, + ActionListener listener) { + if (CollectionUtil.isEmpty(list)) { + return; + } + BulkRequest bulkRequest = toBulkIndexRequest(index, type, list); + client.bulkAsync(bulkRequest, RequestOptions.DEFAULT, listener); + } + + public T updateById(String index, String type, T entity) throws IOException { + + if (entity == null) { + log.warn("【ES】updateById 实体为空!"); + return null; + } + + if (entity.getDocId() == null) { + log.warn("【ES】updateById docId 为空!"); + return null; + } + + Map map = toMap(entity); + if (MapUtil.isEmpty(map)) { + log.warn("【ES】updateById 实体数据为空!"); + return null; + } + + UpdateRequest request = new UpdateRequest(index, type, entity.getDocId()).doc(map); + UpdateResponse response = client.update(request, RequestOptions.DEFAULT); + if (response == null) { + log.warn("【ES】updateById 响应结果为空!"); + return null; + } + + if (response.getResult() == DocWriteResponse.Result.UPDATED) { + return entity; + } else { + log.warn("【ES】updateById 响应结果无效,result: {}!", response.getResult()); + return null; + } + } + + public boolean updateBatchIds(String index, String type, Collection list) + throws IOException { + + if (CollectionUtil.isEmpty(list)) { + return true; + } + + BulkRequest bulkRequest = toBulkUpdateRequest(index, type, list); + BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT); + if (response == null) { + log.warn("【ES】updateBatchIds 失败,result 为空!list: {}", JsonUtil.toString(list)); + return false; + } + if (response.hasFailures()) { + log.warn("【ES】updateBatchIds 失败,result: {}!", response.buildFailureMessage()); + return false; + } + return true; + } + + public void asyncUpdateBatchIds(String index, String type, Collection list, + ActionListener listener) { + if (CollectionUtil.isEmpty(list)) { + return; + } + BulkRequest bulkRequest = toBulkUpdateRequest(index, type, list); + client.bulkAsync(bulkRequest, RequestOptions.DEFAULT, listener); + } + + private BulkRequest toBulkIndexRequest(String index, String type, Collection list) { + BulkRequest bulkRequest = new BulkRequest(); + bulkRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); + for (T entity : list) { + if (entity == null) { + continue; + } + Map map = toMap(entity); + if (MapUtil.isEmpty(map)) { + continue; + } + IndexRequest request = new IndexRequest(index, type).source(map); + if (entity.getDocId() != null) { + request.id(entity.getDocId()); + } + bulkRequest.add(request); + } + return bulkRequest; + } + + private BulkRequest toBulkUpdateRequest(String index, String type, Collection list) { + BulkRequest bulkRequest = new BulkRequest(); + bulkRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); + for (T entity : list) { + if (entity == null || entity.getDocId() == null) { + continue; + } + Map map = toMap(entity); + if (MapUtil.isEmpty(map)) { + continue; + } + UpdateRequest request = new UpdateRequest(index, type, entity.getDocId()).doc(map); + bulkRequest.add(request); + } + return bulkRequest; + } + + public boolean deleteById(String index, String type, String id) throws IOException { + return deleteBatchIds(index, type, Collections.singleton(id)); + } + + public boolean deleteBatchIds(String index, String type, Collection ids) throws IOException { + + if (CollectionUtil.isEmpty(ids)) { + return true; + } + + BulkRequest bulkRequest = new BulkRequest(); + bulkRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); + ids.stream().filter(Objects::nonNull).forEach(id -> { + DeleteRequest deleteRequest = new DeleteRequest(index, type, id); + bulkRequest.add(deleteRequest); + }); + + BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT); + if (response == null) { + log.warn("【ES】deleteBatchIds 失败,result 为空!ids: {}", JsonUtil.toString(ids)); + return false; + } + if (response.hasFailures()) { + log.warn("【ES】deleteBatchIds 失败,result: {}!", response.buildFailureMessage()); + return false; + } + return true; + } + + public void asyncDeleteBatchIds(String index, String type, Collection ids, + ActionListener listener) { + + if (CollectionUtil.isEmpty(ids)) { + return; + } + + BulkRequest bulkRequest = new BulkRequest(); + ids.forEach(id -> { + DeleteRequest deleteRequest = new DeleteRequest(index, type, id); + bulkRequest.add(deleteRequest); + }); + + client.bulkAsync(bulkRequest, RequestOptions.DEFAULT, listener); + } + + public GetResponse getById(String index, String type, String id) throws IOException { + return getById(index, type, id, null); + } + + public GetResponse getById(String index, String type, String id, Long version) throws IOException { + GetRequest getRequest = new GetRequest(index, type, id); + if (version != null) { + getRequest.version(version); + } + return client.get(getRequest, RequestOptions.DEFAULT); + } + + public T pojoById(String index, String type, String id, Class clazz) throws IOException { + return pojoById(index, type, id, null, clazz); + } + + public T pojoById(String index, String type, String id, Long version, Class clazz) throws IOException { + GetResponse response = getById(index, type, id, version); + if (response == null) { + return null; + } + return toPojo(response, clazz); + } + + public List pojoListByIds(String index, String type, Collection ids, Class clazz) + throws IOException { + + if (CollectionUtil.isEmpty(ids)) { + return new ArrayList<>(0); + } + + MultiGetRequest request = new MultiGetRequest(); + for (String id : ids) { + request.add(new MultiGetRequest.Item(index, type, id)); + } + + MultiGetResponse multiGetResponse = client.mget(request, RequestOptions.DEFAULT); + if (null == multiGetResponse + || multiGetResponse.getResponses() == null + || multiGetResponse.getResponses().length <= 0) { + return new ArrayList<>(0); + } + + List list = new ArrayList<>(); + for (MultiGetItemResponse itemResponse : multiGetResponse.getResponses()) { + if (itemResponse.isFailed()) { + log.error("通过id获取文档失败", itemResponse.getFailure().getFailure()); + } else { + T entity = toPojo(itemResponse.getResponse(), clazz); + if (entity != null) { + list.add(entity); + } + } + } + return list; + } + + public long count(String index, String type, SearchSourceBuilder builder) throws IOException { + SearchResponse response = query(index, type, builder); + if (response == null || response.status() != RestStatus.OK) { + return 0L; + } + SearchHits searchHits = response.getHits(); + return searchHits.getTotalHits(); + } + + public long count(String index, String type, QueryBuilder queryBuilder) throws IOException { + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(queryBuilder); + return count(index, type, searchSourceBuilder); + } + + public SearchResponse query(String index, String type, SearchSourceBuilder builder) throws IOException { + SearchRequest request = new SearchRequest(index).types(type); + request.source(builder); + return client.search(request, RequestOptions.DEFAULT); + } + + public SearchResponse query(SearchRequest request) throws IOException { + return client.search(request, RequestOptions.DEFAULT); + } + + /** + * from+size 分页 + *

+ * 注:在深分页的场景下,效率很低(一般超过 1万条数据就不适用了) + */ + public PageData pojoPage(String index, String type, SearchSourceBuilder builder, Class clazz) + throws IOException { + SearchResponse response = query(index, type, builder); + if (response == null || response.status() != RestStatus.OK) { + return null; + } + + List content = toPojoList(response, clazz); + SearchHits searchHits = response.getHits(); + int from = builder.from(); + int size = builder.size(); + int page = from / size + (from % size == 0 ? 0 : 1) + 1; + return new PageData<>(page, size, searchHits.getTotalHits(), content); + } + + /** + * from+size 分页 + *

+ * 注:在深分页的场景下,效率很低(一般超过 1万条数据就不适用了) + */ + public PageData pojoPage(String index, String type, int from, int size, QueryBuilder queryBuilder, + Class clazz) throws IOException { + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(queryBuilder); + searchSourceBuilder.from(from); + searchSourceBuilder.size(size); + return pojoPage(index, type, searchSourceBuilder, clazz); + } + + /** + * search after 分页 + */ + public ScrollData pojoPageByScrollId(String index, String type, String scrollId, + int size, + QueryBuilder queryBuilder, Class clazz) throws IOException { + + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.size(size); + searchSourceBuilder.sort(BaseEsEntity.DOC_ID, SortOrder.ASC); + if (StrUtil.isNotBlank(scrollId)) { + BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); + boolQueryBuilder.must(queryBuilder).must(QueryBuilders.rangeQuery(BaseEsEntity.DOC_ID).gt(scrollId)); + searchSourceBuilder.query(boolQueryBuilder); + } else { + searchSourceBuilder.query(queryBuilder); + } + + SearchResponse response = query(index, type, searchSourceBuilder); + if (response == null || response.status() != RestStatus.OK) { + return null; + } + List content = toPojoList(response, clazz); + ScrollData scrollData = new ScrollData<>(); + scrollData.setSize(size); + scrollData.setTotal(response.getHits().getTotalHits()); + scrollData.setContent(content); + if (CollectionUtil.isNotEmpty(content)) { + T lastEntity = content.get(content.size() - 1); + scrollData.setScrollId(lastEntity.getDocId()); + } + return scrollData; + } + + /** + * 首次滚动查询批量查询,但是不适用与搜索,仅用于批查询 + **/ + public ScrollData pojoScrollBegin(String index, String type, SearchSourceBuilder searchBuilder, + Class clazz) throws IOException { + + int scrollTime = 10; + final Scroll scroll = new Scroll(TimeValue.timeValueSeconds(scrollTime)); + SearchRequest request = new SearchRequest(index); + request.types(type); + request.source(searchBuilder); + request.scroll(scroll); + SearchResponse response = client.search(request, RequestOptions.DEFAULT); + if (response == null || response.status() != RestStatus.OK) { + return null; + } + List content = toPojoList(response, clazz); + ScrollData scrollData = new ScrollData<>(); + scrollData.setSize(searchBuilder.size()); + scrollData.setTotal(response.getHits().getTotalHits()); + scrollData.setScrollId(response.getScrollId()); + scrollData.setContent(content); + return scrollData; + } + + /** + * 知道ScrollId之后,后续根据scrollId批量查询 + **/ + public ScrollData pojoScroll(String scrollId, SearchSourceBuilder searchBuilder, Class clazz) + throws IOException { + + int scrollTime = 10; + final Scroll scroll = new Scroll(TimeValue.timeValueSeconds(scrollTime)); + SearchScrollRequest request = new SearchScrollRequest(scrollId); + request.scroll(scroll); + SearchResponse response = client.scroll(request, RequestOptions.DEFAULT); + if (response == null || response.status() != RestStatus.OK) { + return null; + } + List content = toPojoList(response, clazz); + ScrollData scrollData = new ScrollData<>(); + scrollData.setSize(searchBuilder.size()); + scrollData.setTotal(response.getHits().getTotalHits()); + scrollData.setScrollId(response.getScrollId()); + scrollData.setContent(content); + return scrollData; + } + + public boolean pojoScrollEnd(String scrollId) throws IOException { + ClearScrollRequest request = new ClearScrollRequest(); + request.addScrollId(scrollId); + ClearScrollResponse response = client.clearScroll(request, RequestOptions.DEFAULT); + if (response != null) { + return response.isSucceeded(); + } + return false; + } + + public T toPojo(GetResponse response, Class clazz) { + if (null == response || StrUtil.isBlank(response.getSourceAsString())) { + return null; + } else { + return JsonUtil.toBean(response.getSourceAsString(), clazz); + } + } + + public List toPojoList(SearchResponse response, Class clazz) { + if (response == null || response.status() != RestStatus.OK) { + return new ArrayList<>(0); + } + if (ArrayUtil.isEmpty(response.getHits().getHits())) { + return new ArrayList<>(0); + } + return Stream.of(response.getHits().getHits()) + .map(hit -> JsonUtil.toBean(hit.getSourceAsString(), clazz)) + .collect(Collectors.toList()); + } + + public Map toMap(T entity) { + return JsonUtil.toMap(JsonUtil.toString(entity)); + } + + @Override + public synchronized void close() { + if (null == client) { + return; + } + IoUtil.close(client); + } + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/config/ElasticsearchConfig.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/config/ElasticsearchConfig.java new file mode 100644 index 00000000..791bbf1c --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/config/ElasticsearchConfig.java @@ -0,0 +1,44 @@ +package io.github.dunwu.javadb.elasticsearch.config; + +import cn.hutool.core.util.StrUtil; +import io.github.dunwu.javadb.elasticsearch.ElasticsearchFactory; +import io.github.dunwu.javadb.elasticsearch.ElasticsearchTemplate; +import org.elasticsearch.client.RestHighLevelClient; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +/** + * ES 配置 + * + * @author Zhang Peng + * @date 2024-02-07 + */ +@Configuration +@ComponentScan(value = "io.github.dunwu.javadb.elasticsearch.mapper") +public class ElasticsearchConfig { + + @Value("${es.hosts:#{null}}") + private String hostsConfig; + + @Bean("restHighLevelClient") + @ConditionalOnMissingBean + public RestHighLevelClient restHighLevelClient() { + if (hostsConfig == null) { + return ElasticsearchFactory.newRestHighLevelClient(); + } else { + List hosts = StrUtil.split(hostsConfig, ","); + return ElasticsearchFactory.newRestHighLevelClient(hosts); + } + } + + @Bean("elasticsearchTemplate") + public ElasticsearchTemplate elasticsearchTemplate(RestHighLevelClient restHighLevelClient) { + return ElasticsearchFactory.newElasticsearchTemplate(restHighLevelClient); + } + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/config/EnableElasticsearch.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/config/EnableElasticsearch.java new file mode 100644 index 00000000..c2c24479 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/config/EnableElasticsearch.java @@ -0,0 +1,26 @@ +package io.github.dunwu.javadb.elasticsearch.config; + +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 启动 Elasticsearch 配置注解 + * + * @author Zhang Peng + * @date 2023-06-30 + */ +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@EnableAspectJAutoProxy( + proxyTargetClass = false +) +@Import({ ElasticsearchConfig.class }) +@Documented +public @interface EnableElasticsearch { +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/constant/CodeMsg.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/constant/CodeMsg.java new file mode 100644 index 00000000..96e46f6c --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/constant/CodeMsg.java @@ -0,0 +1,15 @@ +package io.github.dunwu.javadb.elasticsearch.constant; + +/** + * 请求 / 应答状态接口 + * + * @author Zhang Peng + * @since 2019-06-06 + */ +public interface CodeMsg { + + int getCode(); + + String getMsg(); + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/constant/ResultCode.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/constant/ResultCode.java new file mode 100644 index 00000000..d4822fb8 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/constant/ResultCode.java @@ -0,0 +1,97 @@ +package io.github.dunwu.javadb.elasticsearch.constant; + +import cn.hutool.core.util.StrUtil; + +import java.util.stream.Stream; + +/** + * 系统级错误码 + * + * @author Zhang Peng + * @see HTTP 状态码 + * @see 腾讯开放平台错误码 + * @see 新浪开放平台错误码 + * @see 支付宝开放平台API + * @see 微信开放平台错误码 + * @since 2019-04-11 + */ +public enum ResultCode implements CodeMsg { + + OK(0, "成功"), + + PART_OK(1, "部分成功"), + + FAIL(-1, "失败"), + + // ----------------------------------------------------- + // 系统级错误码 + // ----------------------------------------------------- + + ERROR(1000, "服务器错误"), + + PARAM_ERROR(1001, "参数错误"), + + TASK_ERROR(1001, "调度任务错误"), + + CONFIG_ERROR(1003, "配置错误"), + + REQUEST_ERROR(1004, "请求错误"), + + IO_ERROR(1005, "IO 错误"), + + // ----------------------------------------------------- + // 2000 ~ 2999 数据库错误 + // ----------------------------------------------------- + + DATA_ERROR(2000, "数据库错误"), + + // ----------------------------------------------------- + // 3000 ~ 3999 三方错误 + // ----------------------------------------------------- + + THIRD_PART_ERROR(3000, "三方错误"), + + // ----------------------------------------------------- + // 3000 ~ 3999 认证错误 + // ----------------------------------------------------- + + AUTH_ERROR(4000, "认证错误"); + + private final int code; + + private final String msg; + + ResultCode(int code, String msg) { + this.code = code; + this.msg = msg; + } + + @Override + public int getCode() { + return code; + } + + @Override + public String getMsg() { + return msg; + } + + public static String getNameByCode(int code) { + return Stream.of(ResultCode.values()).filter(item -> item.getCode() == code).findFirst() + .map(ResultCode::getMsg).orElse(null); + } + + public static ResultCode getEnumByCode(int code) { + return Stream.of(ResultCode.values()).filter(item -> item.getCode() == code).findFirst().orElse(null); + } + + public static String getTypeInfo() { + StringBuilder sb = new StringBuilder(); + ResultCode[] types = ResultCode.values(); + for (ResultCode type : types) { + sb.append(StrUtil.format("{}:{}, ", type.getCode(), type.getMsg())); + } + return sb.toString(); + } +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/entity/BaseEsEntity.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/entity/BaseEsEntity.java new file mode 100644 index 00000000..32206066 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/entity/BaseEsEntity.java @@ -0,0 +1,37 @@ +package io.github.dunwu.javadb.elasticsearch.entity; + +import lombok.Data; +import lombok.ToString; + +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * ES 实体接口 + * + * @author Zhang Peng + * @since 2023-06-28 + */ +@Data +@ToString +public abstract class BaseEsEntity implements Serializable { + + public static final String DOC_ID = "docId"; + + /** + * 获取版本 + */ + protected Long version; + + protected Float hitScore; + + public abstract String getDocId(); + + public static Map getPropertiesMap() { + Map map = new LinkedHashMap<>(1); + map.put(BaseEsEntity.DOC_ID, "keyword"); + return map; + } + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/entity/User.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/entity/User.java new file mode 100644 index 00000000..b21b229c --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/entity/User.java @@ -0,0 +1,44 @@ +package io.github.dunwu.javadb.elasticsearch.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * 短剧、长视频消费数据 ES 实体 + * + * @author Zhang Peng + * @date 2024-04-02 + */ +@Builder +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class User extends BaseEsEntity implements Serializable { + + private String id; + private String name; + private Integer age; + + @Override + public String getDocId() { + return id; + } + + public static Map getPropertiesMap() { + Map map = new LinkedHashMap<>(); + map.put(BaseEsEntity.DOC_ID, "keyword"); + map.put("id", "long"); + map.put("name", "keyword"); + map.put("age", "integer"); + return map; + } + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/entity/common/PageData.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/entity/common/PageData.java new file mode 100644 index 00000000..e436f8b6 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/entity/common/PageData.java @@ -0,0 +1,37 @@ +package io.github.dunwu.javadb.elasticsearch.entity.common; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 分页实体 + * + * @author Zhang Peng + * @date 2023-06-28 + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class PageData implements Serializable { + + private int page; + private int size; + private long total; + private List content = new ArrayList<>(); + + public PageData(int page, int size, long total) { + this.total = total; + this.page = page; + this.size = size; + } + + private static final long serialVersionUID = 1L; + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/entity/common/ScrollData.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/entity/common/ScrollData.java new file mode 100644 index 00000000..4f90cb85 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/entity/common/ScrollData.java @@ -0,0 +1,30 @@ +package io.github.dunwu.javadb.elasticsearch.entity.common; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.io.Serializable; +import java.util.Collection; + +/** + * Hbase 滚动数据实体 + * + * @author Zhang Peng + * @date 2023-11-16 + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class ScrollData implements Serializable { + + private String scrollId; + private int size = 10; + private long total = 0L; + private Collection content; + + private static final long serialVersionUID = 1L; + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/exception/CodeMsgException.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/exception/CodeMsgException.java new file mode 100644 index 00000000..98ab1995 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/exception/CodeMsgException.java @@ -0,0 +1,128 @@ +package io.github.dunwu.javadb.elasticsearch.exception; + +import cn.hutool.core.util.StrUtil; +import io.github.dunwu.javadb.elasticsearch.constant.CodeMsg; +import io.github.dunwu.javadb.elasticsearch.constant.ResultCode; + +/** + * 基础异常 + * + * @author Zhang Peng + * @since 2021-09-25 + */ +public class CodeMsgException extends RuntimeException implements CodeMsg { + + private static final long serialVersionUID = 6146660782281445735L; + + /** + * 状态码 + */ + protected int code; + + /** + * 响应信息 + */ + protected String msg; + + /** + * 提示信息 + */ + protected String toast; + + public CodeMsgException() { + this(ResultCode.FAIL); + } + + public CodeMsgException(CodeMsg codeMsg) { + this(codeMsg.getCode(), codeMsg.getMsg()); + } + + public CodeMsgException(CodeMsg codeMsg, String msg) { + this(codeMsg.getCode(), msg, null); + } + + public CodeMsgException(CodeMsg codeMsg, String msg, String toast) { + this(codeMsg.getCode(), msg, toast); + } + + public CodeMsgException(String msg) { + this(ResultCode.FAIL, msg); + } + + public CodeMsgException(int code, String msg) { + this(code, msg, msg); + } + + public CodeMsgException(int code, String msg, String toast) { + super(msg); + setCode(code); + setMsg(msg); + setToast(toast); + } + + public CodeMsgException(Throwable cause) { + this(cause, ResultCode.FAIL); + } + + public CodeMsgException(Throwable cause, String msg) { + this(cause, ResultCode.FAIL, msg); + } + + public CodeMsgException(Throwable cause, CodeMsg codeMsg) { + this(cause, codeMsg.getCode(), codeMsg.getMsg()); + } + + public CodeMsgException(Throwable cause, CodeMsg codeMsg, String msg) { + this(cause, codeMsg.getCode(), msg, null); + } + + public CodeMsgException(Throwable cause, CodeMsg codeMsg, String msg, String toast) { + this(cause, codeMsg.getCode(), msg, toast); + } + + public CodeMsgException(Throwable cause, int code, String msg) { + this(cause, code, msg, null); + } + + public CodeMsgException(Throwable cause, int code, String msg, String toast) { + super(msg, cause); + setCode(code); + setMsg(msg); + setToast(toast); + } + + @Override + public String getMessage() { + if (StrUtil.isNotBlank(msg)) { + return StrUtil.format("[{}]{}", code, msg); + } + return super.getMessage(); + } + + @Override + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + @Override + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public String getToast() { + return toast; + } + + public void setToast(String toast) { + this.toast = toast; + } + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/exception/DefaultException.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/exception/DefaultException.java new file mode 100644 index 00000000..14908e39 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/exception/DefaultException.java @@ -0,0 +1,72 @@ +package io.github.dunwu.javadb.elasticsearch.exception; + +import io.github.dunwu.javadb.elasticsearch.constant.CodeMsg; +import io.github.dunwu.javadb.elasticsearch.constant.ResultCode; + +/** + * 默认异常 + * + * @author Zhang Peng + * @since 2021-12-30 + */ +public class DefaultException extends CodeMsgException { + + private static final long serialVersionUID = -7027578114976830416L; + + public DefaultException() { + this(ResultCode.FAIL); + } + + public DefaultException(CodeMsg codeMsg) { + this(codeMsg.getCode(), codeMsg.getMsg()); + } + + public DefaultException(CodeMsg codeMsg, String msg) { + this(codeMsg.getCode(), msg, null); + } + + public DefaultException(CodeMsg codeMsg, String msg, String toast) { + this(codeMsg.getCode(), msg, toast); + } + + public DefaultException(String msg) { + this(ResultCode.FAIL, msg); + } + + public DefaultException(int code, String msg) { + this(code, msg, msg); + } + + public DefaultException(int code, String msg, String toast) { + super(code, msg, toast); + } + + public DefaultException(Throwable cause) { + this(cause, ResultCode.FAIL); + } + + public DefaultException(Throwable cause, String msg) { + this(cause, ResultCode.FAIL, msg); + } + + public DefaultException(Throwable cause, CodeMsg codeMsg) { + this(cause, codeMsg.getCode(), codeMsg.getMsg()); + } + + public DefaultException(Throwable cause, CodeMsg codeMsg, String msg) { + this(cause, codeMsg.getCode(), msg, null); + } + + public DefaultException(Throwable cause, CodeMsg codeMsg, String msg, String toast) { + this(cause, codeMsg.getCode(), msg, toast); + } + + public DefaultException(Throwable cause, int code, String msg) { + this(cause, code, msg, null); + } + + public DefaultException(Throwable cause, int code, String msg, String toast) { + super(cause, code, msg, toast); + } + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/mapper/BaseDynamicEsMapper.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/mapper/BaseDynamicEsMapper.java new file mode 100644 index 00000000..c75c6cc6 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/mapper/BaseDynamicEsMapper.java @@ -0,0 +1,331 @@ +package io.github.dunwu.javadb.elasticsearch.mapper; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import io.github.dunwu.javadb.elasticsearch.ElasticsearchTemplate; +import io.github.dunwu.javadb.elasticsearch.constant.ResultCode; +import io.github.dunwu.javadb.elasticsearch.entity.BaseEsEntity; +import io.github.dunwu.javadb.elasticsearch.entity.common.PageData; +import io.github.dunwu.javadb.elasticsearch.entity.common.ScrollData; +import io.github.dunwu.javadb.elasticsearch.exception.DefaultException; +import lombok.extern.slf4j.Slf4j; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.bulk.BulkResponse; +import org.elasticsearch.action.get.GetResponse; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.search.builder.SearchSourceBuilder; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * 动态 ES Mapper 基础类(以时间为维度动态创建、删除 index),用于数据量特别大,需要按照日期分片的索引。 + *

+ * 注:使用此 Mapper 的索引、别名必须遵循命名格式:索引名 = 别名_yyyyMMdd + * + * @author Zhang Peng + * @date 2024-04-07 + */ +@Slf4j +public abstract class BaseDynamicEsMapper extends BaseEsMapper { + + public BaseDynamicEsMapper(ElasticsearchTemplate elasticsearchTemplate) { + super(elasticsearchTemplate); + } + + @Override + public boolean enableAutoCreateIndex() { + return true; + } + + // ==================================================================== + // 索引管理操作 + // ==================================================================== + + public String getIndex(String day) { + + String alias = getAlias(); + if (StrUtil.isBlank(day)) { + String msg = StrUtil.format("【ES】获取 {} 索引失败!day 不能为空!", alias); + throw new DefaultException(ResultCode.PARAM_ERROR, msg); + } + + DateTime date; + try { + date = DateUtil.parse(day, DatePattern.NORM_DATE_PATTERN); + } catch (Exception e) { + String msg = StrUtil.format("【ES】获取 {} 索引失败!day: {} 不符合日期格式 {}!", + alias, day, DatePattern.NORM_DATE_PATTERN); + throw new DefaultException(e, ResultCode.PARAM_ERROR, msg); + } + + String formatDate = DateUtil.format(date, DatePattern.PURE_DATE_FORMAT); + return alias + "_" + formatDate; + } + + public boolean isIndexExistsInDay(String day) { + if (StrUtil.isBlank(day)) { + return false; + } + String index = getIndex(day); + try { + return elasticsearchTemplate.isIndexExists(getIndex(day)); + } catch (Exception e) { + log.error("【ES】判断索引是否存在异常!index: {}", index, e); + return false; + } + } + + public String createIndexIfNotExistsInDay(String day) { + String index = getIndex(day); + String type = getType(); + String alias = getAlias(); + int shard = getShard(); + int replica = getReplica(); + return createIndex(index, type, alias, shard, replica); + } + + public void deleteIndexInDay(String day) { + String index = getIndex(day); + try { + log.info("【ES】删除索引成功!index: {}", index); + elasticsearchTemplate.deleteIndex(index); + } catch (Exception e) { + log.error("【ES】删除索引异常!index: {}", index, e); + } + } + + public void updateAliasInDay(String day) { + String index = getIndex(day); + String alias = getAlias(); + try { + log.info("【ES】更新别名成功!alias: {} -> index: {}", alias, index); + elasticsearchTemplate.updateAlias(index, alias); + } catch (IOException e) { + log.error("【ES】更新别名异常!alias: {} -> index: {}", alias, index, e); + } + } + + // ==================================================================== + // CRUD 操作 + // ==================================================================== + + public GetResponse getByIdInDay(String day, String id) { + String index = getIndex(day); + String type = getType(); + try { + return elasticsearchTemplate.getById(index, type, id, null); + } catch (IOException e) { + log.error("【ES】根据ID查询异常!index: {}, type: {}, id: {}", index, type, id, e); + return null; + } + } + + public T pojoByIdInDay(String day, String id) { + String index = getIndex(day); + String type = getType(); + try { + return elasticsearchTemplate.pojoById(index, type, id, null, getEntityClass()); + } catch (IOException e) { + log.error("【ES】根据ID查询POJO异常!index: {}, type: {}, id: {}", index, type, id, e); + return null; + } + } + + public List pojoListByIdsInDay(String day, Collection ids) { + String index = getIndex(day); + String type = getType(); + try { + return elasticsearchTemplate.pojoListByIds(index, type, ids, getEntityClass()); + } catch (IOException e) { + log.error("【ES】根据ID查询POJO列表异常!index: {}, type: {}, ids: {}", index, type, ids, e); + return new ArrayList<>(0); + } + } + + public long countInDay(String day, SearchSourceBuilder builder) { + String index = getIndex(day); + String type = getType(); + try { + return elasticsearchTemplate.count(index, type, builder); + } catch (IOException e) { + log.error("【ES】获取匹配记录数异常!index: {}, type: {}", index, type, e); + return 0L; + } + } + + public SearchResponse queryInDay(String day, SearchSourceBuilder builder) { + String index = getIndex(day); + String type = getType(); + try { + return elasticsearchTemplate.query(index, type, builder); + } catch (IOException e) { + log.error("【ES】条件查询异常!index: {}, type: {}", index, type, e); + return null; + } + } + + public PageData pojoPageInDay(String day, SearchSourceBuilder builder) { + String index = getIndex(day); + String type = getType(); + try { + return elasticsearchTemplate.pojoPage(index, type, builder, getEntityClass()); + } catch (IOException e) { + log.error("【ES】from + size 分页条件查询异常!index: {}, type: {}", index, type, e); + return null; + } + } + + public ScrollData pojoPageByLastIdInDay(String day, String scrollId, int size, QueryBuilder queryBuilder) { + String index = getIndex(day); + String type = getType(); + try { + return elasticsearchTemplate.pojoPageByScrollId(index, type, scrollId, size, queryBuilder, getEntityClass()); + } catch (IOException e) { + log.error("【ES】search after 分页条件查询异常!index: {}, type: {}", index, type, e); + return null; + } + } + + public ScrollData pojoScrollBeginInDay(String day, SearchSourceBuilder builder) { + String index = getIndex(day); + String type = getType(); + try { + return elasticsearchTemplate.pojoScrollBegin(index, type, builder, getEntityClass()); + } catch (IOException e) { + log.error("【ES】开启滚动分页条件查询异常!index: {}, type: {}", index, type, e); + return null; + } + } + + /** + * 根据日期动态选择索引并更新 + * + * @param day 日期,格式为:yyyy-MM-dd + * @param entity 待更新的数据 + * @return / + */ + public T saveInDay(String day, T entity) { + if (StrUtil.isBlank(day) || entity == null) { + return null; + } + String index = getIndex(day); + String type = getType(); + try { + checkIndex(day); + checkData(entity); + return elasticsearchTemplate.save(index, getType(), entity); + } catch (IOException e) { + log.error("【ES】添加数据异常!index: {}, type: {}, entity: {}", index, type, JSONUtil.toJsonStr(entity), e); + return null; + } + } + + /** + * 根据日期动态选择索引并批量更新 + * + * @param day 日期,格式为:yyyy-MM-dd + * @param list 待更新的数据 + * @return / + */ + public boolean saveBatchInDay(String day, Collection list) { + if (StrUtil.isBlank(day) || CollectionUtil.isEmpty(list)) { + return false; + } + String index = getIndex(day); + String type = getType(); + try { + checkIndex(day); + checkData(list); + return elasticsearchTemplate.saveBatch(index, type, list); + } catch (IOException e) { + log.error("【ES】批量添加数据异常!index: {}, type: {}, size: {}", index, type, list.size(), e); + return false; + } + } + + public void asyncSaveBatchInDay(String day, Collection list) { + asyncSaveBatchInDay(day, list, DEFAULT_BULK_LISTENER); + } + + public void asyncSaveBatchInDay(String day, Collection list, ActionListener listener) { + if (StrUtil.isBlank(day) || CollectionUtil.isEmpty(list)) { + return; + } + String index = getIndex(day); + String type = getType(); + try { + checkIndex(day); + checkData(list); + elasticsearchTemplate.asyncSaveBatch(index, type, list, listener); + } catch (Exception e) { + log.error("【ES】异步批量添加数据异常!index: {}, type: {}, size: {}", index, type, list.size(), e); + } + } + + public void asyncUpdateBatchIdsInDay(String day, Collection list) { + asyncUpdateBatchIdsInDay(day, list, DEFAULT_BULK_LISTENER); + } + + public void asyncUpdateBatchIdsInDay(String day, Collection list, ActionListener listener) { + if (StrUtil.isBlank(day) || CollectionUtil.isEmpty(list)) { + return; + } + String index = getIndex(day); + String type = getType(); + try { + checkData(list); + elasticsearchTemplate.asyncUpdateBatchIds(index, type, list, listener); + } catch (Exception e) { + log.error("【ES】异步批量更新数据异常!index: {}, type: {}, size: {}", index, type, list.size(), e); + } + } + + public boolean deleteByIdInDay(String day, String id) { + if (StrUtil.isBlank(day) || StrUtil.isBlank(id)) { + return false; + } + String index = getIndex(day); + String type = getType(); + try { + return elasticsearchTemplate.deleteById(index, type, id); + } catch (IOException e) { + log.error("【ES】根据ID删除数据异常!index: {}, type: {}, id: {}", index, type, id, e); + return false; + } + } + + public boolean deleteBatchIdsInDay(String day, Collection ids) { + if (StrUtil.isBlank(day) || CollectionUtil.isEmpty(ids)) { + return false; + } + String index = getIndex(day); + String type = getType(); + try { + return elasticsearchTemplate.deleteBatchIds(index, type, ids); + } catch (IOException e) { + log.error("【ES】根据ID批量删除数据异常!index: {}, type: {}, ids: {}", index, type, ids, e); + return false; + } + } + + protected String checkIndex(String day) { + if (!enableAutoCreateIndex()) { + return getIndex(day); + } + String index = createIndexIfNotExistsInDay(day); + if (StrUtil.isBlank(index)) { + String msg = StrUtil.format("【ES】索引 {}_{} 找不到且创建失败!", getAlias(), day); + throw new DefaultException(ResultCode.ERROR, msg); + } + return index; + } + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/mapper/BaseEsMapper.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/mapper/BaseEsMapper.java new file mode 100644 index 00000000..b125bea9 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/mapper/BaseEsMapper.java @@ -0,0 +1,502 @@ +package io.github.dunwu.javadb.elasticsearch.mapper; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import io.github.dunwu.javadb.elasticsearch.ElasticsearchTemplate; +import io.github.dunwu.javadb.elasticsearch.constant.ResultCode; +import io.github.dunwu.javadb.elasticsearch.entity.BaseEsEntity; +import io.github.dunwu.javadb.elasticsearch.entity.common.PageData; +import io.github.dunwu.javadb.elasticsearch.entity.common.ScrollData; +import io.github.dunwu.javadb.elasticsearch.exception.DefaultException; +import lombok.extern.slf4j.Slf4j; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.bulk.BulkProcessor; +import org.elasticsearch.action.bulk.BulkResponse; +import org.elasticsearch.action.get.GetResponse; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.search.builder.SearchSourceBuilder; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * ES Mapper 基础类 + * + * @author Zhang Peng + * @date 2023-06-27 + */ +@Slf4j +public abstract class BaseEsMapper implements EsMapper { + + protected BulkProcessor bulkProcessor; + + protected final ElasticsearchTemplate elasticsearchTemplate; + + public BaseEsMapper(ElasticsearchTemplate elasticsearchTemplate) { + this.elasticsearchTemplate = elasticsearchTemplate; + } + + public int getShard() { + return 5; + } + + public int getReplica() { + return 1; + } + + @Override + public RestHighLevelClient getClient() { + if (elasticsearchTemplate == null) { + return null; + } + return elasticsearchTemplate.getClient(); + } + + @Override + public synchronized BulkProcessor getBulkProcessor() { + if (bulkProcessor == null) { + bulkProcessor = elasticsearchTemplate.newAsyncBulkProcessor(); + } + return bulkProcessor; + } + + @SuppressWarnings("unchecked") + public Map getPropertiesMap() { + Class clazz = getEntityClass(); + Method method; + try { + method = clazz.getMethod("getPropertiesMap"); + } catch (NoSuchMethodException e) { + log.error("【ES】{} 中不存在 getPropertiesMap 方法!", clazz.getCanonicalName()); + return new HashMap<>(0); + } + + Object result = ReflectUtil.invokeStatic(method); + if (result == null) { + return new HashMap<>(0); + } + return (Map) result; + } + + // ==================================================================== + // 索引管理操作 + // ==================================================================== + + @Override + public boolean isIndexExists() { + String index = getIndex(); + try { + return elasticsearchTemplate.isIndexExists(index); + } catch (Exception e) { + log.error("【ES】判断索引是否存在异常!index: {}", index, e); + return false; + } + } + + @Override + public String createIndexIfNotExists() { + String index = getIndex(); + String type = getType(); + String alias = getAlias(); + int shard = getShard(); + int replica = getReplica(); + return createIndex(index, type, alias, shard, replica); + } + + protected String createIndex(String index, String type, String alias, int shard, int replica) { + try { + if (elasticsearchTemplate.isIndexExists(index)) { + return index; + } + elasticsearchTemplate.createIndex(index, type, alias, shard, replica); + log.info("【ES】创建索引成功!index: {}, type: {}, alias: {}, shard: {}, replica: {}", + index, type, alias, shard, replica); + Map propertiesMap = getPropertiesMap(); + if (MapUtil.isNotEmpty(propertiesMap)) { + elasticsearchTemplate.setMapping(index, type, propertiesMap); + log.error("【ES】设置索引 mapping 成功!index: {}, type: {}, propertiesMap: {}", + index, type, JSONUtil.toJsonStr(propertiesMap)); + } + return index; + } catch (Exception e) { + log.error("【ES】创建索引异常!index: {}, type: {}, alias: {}, shard: {}, replica: {}", + index, type, alias, shard, replica, e); + return null; + } + } + + @Override + public void deleteIndex() { + String index = getIndex(); + try { + log.info("【ES】删除索引成功!index: {}", index); + elasticsearchTemplate.deleteIndex(index); + } catch (Exception e) { + log.error("【ES】删除索引异常!index: {}", index, e); + } + } + + @Override + public void updateAlias() { + String index = getIndex(); + String alias = getAlias(); + try { + log.info("【ES】更新别名成功!alias: {} -> index: {}", alias, index); + elasticsearchTemplate.updateAlias(index, alias); + } catch (Exception e) { + log.error("【ES】更新别名异常!alias: {} -> index: {}", alias, index, e); + } + } + + @Override + public Set getIndexSet() { + String alias = getAlias(); + try { + return elasticsearchTemplate.getIndexSet(alias); + } catch (Exception e) { + log.error("【ES】获取别名的所有索引异常!alias: {}", alias, e); + return new HashSet<>(0); + } + } + + // ==================================================================== + // CRUD 操作 + // ==================================================================== + + @Override + public GetResponse getById(String id) { + return getById(id, null); + } + + @Override + public GetResponse getById(String id, Long version) { + String index = getIndex(); + String type = getType(); + try { + return elasticsearchTemplate.getById(index, type, id, version); + } catch (Exception e) { + log.error("【ES】根据ID查询异常!index: {}, type: {}, id: {}, version: {}", index, type, id, version, e); + return null; + } + } + + @Override + public T pojoById(String id) { + return pojoById(id, null); + } + + @Override + public T pojoById(String id, Long version) { + String index = getIndex(); + String type = getType(); + try { + return elasticsearchTemplate.pojoById(index, type, id, version, getEntityClass()); + } catch (Exception e) { + log.error("【ES】根据ID查询POJO异常!index: {}, type: {}, id: {}, version: {}", index, type, id, version, e); + return null; + } + } + + @Override + public List pojoListByIds(Collection ids) { + String index = getIndex(); + String type = getType(); + try { + return elasticsearchTemplate.pojoListByIds(index, type, ids, getEntityClass()); + } catch (Exception e) { + log.error("【ES】根据ID查询POJO列表异常!index: {}, type: {}, ids: {}", index, type, ids, e); + return new ArrayList<>(0); + } + } + + @Override + public long count(SearchSourceBuilder builder) { + String index = getIndex(); + String type = getType(); + try { + return elasticsearchTemplate.count(index, type, builder); + } catch (Exception e) { + log.error("【ES】获取匹配记录数异常!index: {}, type: {}", index, type, e); + return 0L; + } + } + + @Override + public SearchResponse query(SearchSourceBuilder builder) { + String index = getIndex(); + String type = getType(); + try { + return elasticsearchTemplate.query(index, type, builder); + } catch (Exception e) { + log.error("【ES】条件查询异常!index: {}, type: {}", index, type, e); + return null; + } + } + + @Override + public PageData pojoPage(SearchSourceBuilder builder) { + String index = getIndex(); + String type = getType(); + try { + return elasticsearchTemplate.pojoPage(index, type, builder, getEntityClass()); + } catch (Exception e) { + log.error("【ES】from + size 分页条件查询异常!index: {}, type: {}", index, type, e); + return null; + } + } + + @Override + public ScrollData pojoPageByLastId(String scrollId, int size, QueryBuilder queryBuilder) { + String index = getIndex(); + String type = getType(); + try { + return elasticsearchTemplate.pojoPageByScrollId(index, type, scrollId, size, queryBuilder, + getEntityClass()); + } catch (Exception e) { + log.error("【ES】search after 分页条件查询异常!index: {}, type: {}", index, type, e); + return null; + } + } + + @Override + public ScrollData pojoScrollBegin(SearchSourceBuilder builder) { + String index = getIndex(); + String type = getType(); + try { + return elasticsearchTemplate.pojoScrollBegin(index, type, builder, getEntityClass()); + } catch (Exception e) { + log.error("【ES】开启滚动分页条件查询异常!index: {}, type: {}", index, type, e); + return null; + } + } + + @Override + public ScrollData pojoScroll(String scrollId, SearchSourceBuilder builder) { + try { + return elasticsearchTemplate.pojoScroll(scrollId, builder, getEntityClass()); + } catch (Exception e) { + log.error("【ES】滚动分页条件查询异常!scrollId: {}", scrollId, e); + return null; + } + } + + @Override + public boolean pojoScrollEnd(String scrollId) { + try { + return elasticsearchTemplate.pojoScrollEnd(scrollId); + } catch (Exception e) { + log.error("【ES】关闭滚动分页条件查询异常!scrollId: {}", scrollId, e); + return false; + } + } + + @Override + public T save(T entity) { + if (entity == null) { + return null; + } + String index = getIndex(); + String type = getType(); + try { + checkIndex(); + checkData(entity); + return elasticsearchTemplate.save(index, type, entity); + } catch (Exception e) { + log.error("【ES】添加数据异常!index: {}, type: {}, entity: {}", index, type, JSONUtil.toJsonStr(entity), e); + return null; + } + } + + @Override + public boolean saveBatch(Collection list) { + if (CollectionUtil.isEmpty(list)) { + return false; + } + String index = getIndex(); + String type = getType(); + try { + checkIndex(); + checkData(list); + return elasticsearchTemplate.saveBatch(index, type, list); + } catch (Exception e) { + log.error("【ES】批量添加数据异常!index: {}, type: {}, size: {}", index, type, list.size(), e); + return false; + } + } + + @Override + public void asyncSaveBatch(Collection list) { + asyncSaveBatch(list, DEFAULT_BULK_LISTENER); + } + + @Override + public void asyncSaveBatch(Collection list, ActionListener listener) { + if (CollectionUtil.isEmpty(list)) { + return; + } + String index = getIndex(); + String type = getType(); + try { + checkIndex(); + checkData(list); + elasticsearchTemplate.asyncSaveBatch(index, getType(), list, listener); + } catch (Exception e) { + log.error("【ES】异步批量添加数据异常!index: {}, type: {}, size: {}", index, type, list.size(), e); + } + } + + @Override + public T updateById(T entity) { + if (entity == null) { + return null; + } + String index = getIndex(); + String type = getType(); + try { + checkData(entity); + return elasticsearchTemplate.updateById(index, type, entity); + } catch (Exception e) { + log.error("【ES】更新数据异常!index: {}, type: {}", index, type, e); + return null; + } + } + + @Override + public boolean updateBatchIds(Collection list) { + if (CollectionUtil.isEmpty(list)) { + return false; + } + String index = getIndex(); + String type = getType(); + try { + checkData(list); + return elasticsearchTemplate.updateBatchIds(index, type, list); + } catch (Exception e) { + log.error("【ES】批量更新数据异常!index: {}, type: {}, size: {}", index, type, list.size(), e); + return false; + } + } + + @Override + public void asyncUpdateBatchIds(Collection list) { + asyncUpdateBatchIds(list, DEFAULT_BULK_LISTENER); + } + + @Override + public void asyncUpdateBatchIds(Collection list, ActionListener listener) { + if (CollectionUtil.isEmpty(list)) { + return; + } + String index = getIndex(); + String type = getType(); + try { + checkData(list); + elasticsearchTemplate.asyncUpdateBatchIds(index, type, list, listener); + } catch (Exception e) { + log.error("【ES】异步批量更新数据异常!index: {}, type: {}, size: {}", index, type, list.size(), e); + } + } + + @Override + public boolean deleteById(String id) { + if (StrUtil.isBlank(id)) { + return false; + } + String index = getIndex(); + String type = getType(); + try { + return elasticsearchTemplate.deleteById(index, type, id); + } catch (Exception e) { + log.error("【ES】根据ID删除数据异常!index: {}, type: {}, id: {}", index, type, id, e); + return false; + } + } + + @Override + public boolean deleteBatchIds(Collection ids) { + if (CollectionUtil.isEmpty(ids)) { + return false; + } + String index = getIndex(); + String type = getType(); + try { + return elasticsearchTemplate.deleteBatchIds(index, type, ids); + } catch (Exception e) { + log.error("【ES】根据ID批量删除数据异常!index: {}, type: {}, ids: {}", index, type, ids, e); + return false; + } + } + + @Override + public void asyncDeleteBatchIds(Collection ids) { + asyncDeleteBatchIds(ids, DEFAULT_BULK_LISTENER); + } + + @Override + public void asyncDeleteBatchIds(Collection ids, ActionListener listener) { + if (CollectionUtil.isEmpty(ids)) { + return; + } + String index = getIndex(); + String type = getType(); + try { + elasticsearchTemplate.asyncDeleteBatchIds(index, type, ids, listener); + } catch (Exception e) { + log.error("【ES】异步根据ID批量删除数据异常!index: {}, type: {}, ids: {}", index, type, ids, e); + } + } + + protected String checkIndex() { + if (!enableAutoCreateIndex()) { + return getIndex(); + } + String index = createIndexIfNotExists(); + if (StrUtil.isBlank(index)) { + String msg = StrUtil.format("【ES】索引 {} 找不到且创建失败!", index); + throw new DefaultException(ResultCode.ERROR, msg); + } + return index; + } + + protected void checkData(Collection list) { + if (CollectionUtil.isEmpty(list)) { + String msg = StrUtil.format("【ES】写入 {} 失败!list 不能为空!", getIndex()); + throw new DefaultException(ResultCode.PARAM_ERROR, msg); + } + } + + protected void checkData(T entity) { + if (entity == null) { + String msg = StrUtil.format("【ES】写入 {} 失败!entity 不能为空!", getIndex()); + throw new DefaultException(ResultCode.PARAM_ERROR, msg); + } + } + + protected final ActionListener DEFAULT_BULK_LISTENER = new ActionListener() { + @Override + public void onResponse(BulkResponse response) { + if (response != null && !response.hasFailures()) { + log.info("【ES】异步批量写数据成功!index: {}, type: {}", getIndex(), getType()); + } else { + log.warn("【ES】异步批量写数据失败!index: {}, type: {}", getIndex(), getType()); + } + } + + @Override + public void onFailure(Exception e) { + log.error("【ES】异步批量写数据异常!index: {}, type: {}", getIndex(), getType()); + } + }; + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/mapper/EsMapper.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/mapper/EsMapper.java new file mode 100644 index 00000000..5630d664 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/mapper/EsMapper.java @@ -0,0 +1,142 @@ +package io.github.dunwu.javadb.elasticsearch.mapper; + +import cn.hutool.core.collection.CollectionUtil; +import io.github.dunwu.javadb.elasticsearch.entity.BaseEsEntity; +import io.github.dunwu.javadb.elasticsearch.entity.common.PageData; +import io.github.dunwu.javadb.elasticsearch.entity.common.ScrollData; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.bulk.BulkProcessor; +import org.elasticsearch.action.bulk.BulkResponse; +import org.elasticsearch.action.get.GetResponse; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.search.builder.SearchSourceBuilder; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * ES Mapper + * + * @author Zhang Peng + * @date 2023-06-27 + */ +public interface EsMapper { + + /** + * 获取别名 + */ + String getAlias(); + + /** + * 获取索引名 + */ + String getIndex(); + + /** + * 获取索引类型 + */ + String getType(); + + /** + * 获取分片数 + */ + int getShard(); + + /** + * 获取副本数 + */ + int getReplica(); + + /** + * 获取实体类型 + */ + Class getEntityClass(); + + /** + * 如果开启,添加 ES 数据时,如果索引不存在,会自动创建索引 + */ + default boolean enableAutoCreateIndex() { + return false; + } + + RestHighLevelClient getClient(); + + BulkProcessor getBulkProcessor(); + + boolean isIndexExists(); + + String createIndexIfNotExists(); + + void deleteIndex(); + + void updateAlias(); + + Set getIndexSet(); + + GetResponse getById(String id); + + GetResponse getById(String id, Long version); + + T pojoById(String id); + + T pojoById(String id, Long version); + + List pojoListByIds(Collection ids); + + default Map pojoMapByIds(Collection ids) { + List list = pojoListByIds(ids); + if (CollectionUtil.isEmpty(list)) { + return new HashMap<>(0); + } + + Map map = new HashMap<>(list.size()); + for (T entity : list) { + map.put(entity.getDocId(), entity); + } + return map; + } + + long count(SearchSourceBuilder builder); + + SearchResponse query(SearchSourceBuilder builder); + + PageData pojoPage(SearchSourceBuilder builder); + + ScrollData pojoPageByLastId(String scrollId, int size, QueryBuilder queryBuilder); + + ScrollData pojoScrollBegin(SearchSourceBuilder builder); + + ScrollData pojoScroll(String scrollId, SearchSourceBuilder builder); + + boolean pojoScrollEnd(String scrollId); + + T save(T entity); + + boolean saveBatch(Collection list); + + void asyncSaveBatch(Collection list); + + void asyncSaveBatch(Collection list, ActionListener listener); + + T updateById(T entity); + + boolean updateBatchIds(Collection list); + + void asyncUpdateBatchIds(Collection list); + + void asyncUpdateBatchIds(Collection list, ActionListener listener); + + boolean deleteById(String id); + + boolean deleteBatchIds(Collection ids); + + void asyncDeleteBatchIds(Collection ids); + + void asyncDeleteBatchIds(Collection ids, ActionListener listener); + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/mapper/UserEsMapper.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/mapper/UserEsMapper.java new file mode 100644 index 00000000..de6d1b7c --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/mapper/UserEsMapper.java @@ -0,0 +1,47 @@ +package io.github.dunwu.javadb.elasticsearch.mapper; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import io.github.dunwu.javadb.elasticsearch.ElasticsearchTemplate; +import io.github.dunwu.javadb.elasticsearch.entity.User; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Date; + +/** + * open_applet_consume_yyyyMMdd ES Mapper + * + * @author Zhang Peng + * @date 2023-06-27 + */ +@Slf4j +@Component +public class UserEsMapper extends BaseDynamicEsMapper { + + public UserEsMapper(ElasticsearchTemplate elasticsearchTemplate) { + super(elasticsearchTemplate); + } + + @Override + public String getAlias() { + return "user"; + } + + @Override + public String getIndex() { + String date = DateUtil.format(new Date(), DatePattern.PURE_DATE_FORMAT); + return getAlias() + "_" + date; + } + + @Override + public String getType() { + return "_doc"; + } + + @Override + public Class getEntityClass() { + return User.class; + } + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/util/JsonUtil.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/util/JsonUtil.java new file mode 100644 index 00000000..dabe0df5 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/util/JsonUtil.java @@ -0,0 +1,99 @@ +package io.github.dunwu.javadb.elasticsearch.util; + +import cn.hutool.core.util.StrUtil; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import lombok.extern.slf4j.Slf4j; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * JSON 工具类 + * + * @author Zhang Peng + * @date 2023-06-29 + */ +@Slf4j +public class JsonUtil { + + private static final ObjectMapper MAPPER = + JsonMapper.builder() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true) + .configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true) + .serializationInclusion(JsonInclude.Include.ALWAYS) + .build(); + + public static List toList(String json, Class clazz) { + if (StrUtil.isBlank(json)) { + return null; + } + JavaType javaType = MAPPER.getTypeFactory().constructParametricType(List.class, clazz); + try { + return MAPPER.readValue(json, javaType); + } catch (Exception e) { + log.error("反序列化失败!json: {}, msg: {}", json, e.getMessage()); + } + return null; + } + + public static Map toMap(String json) { + if (StrUtil.isBlank(json)) { + return new HashMap<>(0); + } + try { + return MAPPER.readValue(json, new TypeReference>() { }); + } catch (Exception e) { + log.error("反序列化失败!json: {}, msg: {}", json, e.getMessage()); + } + return Collections.emptyMap(); + } + + public static T toBean(String json, Class clazz) { + if (StrUtil.isBlank(json)) { + return null; + } + try { + return MAPPER.readValue(json, clazz); + } catch (Exception e) { + log.error("反序列化失败!json: {}, msg: {}", json, e.getMessage()); + } + return null; + } + + public static T toBean(String json, TypeReference typeReference) { + if (StrUtil.isBlank(json)) { + return null; + } + try { + return (T) MAPPER.readValue(json, typeReference); + } catch (Exception e) { + log.error("反序列化失败!json: {}, msg: {}", json, e.getMessage()); + } + return null; + } + + public static String toString(T obj) { + if (obj == null) { + return null; + } + if (obj instanceof String) { + return (String) obj; + } + try { + return MAPPER.writeValueAsString(obj); + } catch (Exception e) { + log.error("序列化失败!obj: {}, msg: {}", obj, e.getMessage()); + } + return null; + } + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/test/java/io/github/dunwu/javadb/elasticsearch/BaseApplicationTests.java b/codes/javadb/elasticsearch/elasticsearch6/src/test/java/io/github/dunwu/javadb/elasticsearch/BaseApplicationTests.java new file mode 100644 index 00000000..1fadeff5 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/test/java/io/github/dunwu/javadb/elasticsearch/BaseApplicationTests.java @@ -0,0 +1,33 @@ +package io.github.dunwu.javadb.elasticsearch; + +import io.github.dunwu.javadb.elasticsearch.config.EnableElasticsearch; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@EnableElasticsearch +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public abstract class BaseApplicationTests { + + protected MockMvc mockMvc; + + @Autowired + private WebApplicationContext context; + + @BeforeEach + public void setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); + } + + @BeforeAll + public static void setEnvironmentInDev() { + } + +} \ No newline at end of file diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/test/java/io/github/dunwu/javadb/elasticsearch/BaseElasticsearchTemplateTest.java b/codes/javadb/elasticsearch/elasticsearch6/src/test/java/io/github/dunwu/javadb/elasticsearch/BaseElasticsearchTemplateTest.java new file mode 100644 index 00000000..4ec530a1 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/test/java/io/github/dunwu/javadb/elasticsearch/BaseElasticsearchTemplateTest.java @@ -0,0 +1,295 @@ +package io.github.dunwu.javadb.elasticsearch; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import io.github.dunwu.javadb.elasticsearch.entity.BaseEsEntity; +import io.github.dunwu.javadb.elasticsearch.entity.common.PageData; +import io.github.dunwu.javadb.elasticsearch.entity.common.ScrollData; +import io.github.dunwu.javadb.elasticsearch.util.JsonUtil; +import lombok.extern.slf4j.Slf4j; +import org.assertj.core.api.Assertions; +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.bulk.BulkResponse; +import org.elasticsearch.action.get.GetResponse; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.index.query.BoolQueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.search.SearchHit; +import org.elasticsearch.search.builder.SearchSourceBuilder; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** + * ElasticsearchTemplate 测试 + * + * @author Zhang Peng + * @date 2023-11-13 + */ +@Slf4j +public abstract class BaseElasticsearchTemplateTest { + + static final int FROM = 0; + static final int SIZE = 10; + static final String TEST_ID_01 = "1"; + static final String TEST_ID_02 = "2"; + + protected ElasticsearchTemplate TEMPLATE = ElasticsearchFactory.newElasticsearchTemplate(); + + protected abstract String getAlias(); + + protected abstract String getIndex(); + + protected abstract String getType(); + + protected abstract int getShard(); + + protected abstract int getReplica(); + + protected abstract Class getEntityClass(); + + protected abstract Map getPropertiesMap(); + + protected abstract T getOneMockData(String id); + + protected abstract List getMockList(int num); + + protected void deleteIndex() throws IOException { + try { + Set set = TEMPLATE.getIndexSet(getAlias()); + if (CollectionUtil.isNotEmpty(set)) { + for (String index : set) { + log.info("删除 alias: {}, index: {}", getAlias(), index); + TEMPLATE.deleteIndex(index); + } + } + } catch (IOException | ElasticsearchException e) { + log.error("删除索引失败!", e); + } + boolean exists = TEMPLATE.isIndexExists(getIndex()); + Assertions.assertThat(exists).isFalse(); + } + + protected void createIndex() throws IOException { + boolean exists = TEMPLATE.isIndexExists(getIndex()); + if (exists) { + return; + } + TEMPLATE.createIndex(getIndex(), getType(), getAlias(), getShard(), getReplica()); + TEMPLATE.setMapping(getIndex(), getType(), getPropertiesMap()); + exists = TEMPLATE.isIndexExists(getIndex()); + Assertions.assertThat(exists).isTrue(); + } + + public void getIndexList() throws IOException { + Set set = TEMPLATE.getIndexSet(getAlias()); + log.info("alias: {}, indexList: {}", getAlias(), set); + Assertions.assertThat(set).isNotEmpty(); + } + + protected void save() throws IOException { + String id = "1"; + T oldEntity = getOneMockData(id); + TEMPLATE.save(getIndex(), getType(), oldEntity); + T newEntity = TEMPLATE.pojoById(getIndex(), getType(), id, getEntityClass()); + log.info("记录:{}", JsonUtil.toString(newEntity)); + Assertions.assertThat(newEntity).isNotNull(); + } + + protected void saveBatch() throws IOException { + int total = 5000; + List> listGroup = CollectionUtil.split(getMockList(total), 1000); + for (List list : listGroup) { + Assertions.assertThat(TEMPLATE.saveBatch(getIndex(), getType(), list)).isTrue(); + } + long count = TEMPLATE.count(getIndex(), getType(), new SearchSourceBuilder()); + log.info("批量更新记录数: {}", count); + Assertions.assertThat(count).isEqualTo(total); + } + + protected void asyncSave() throws IOException { + String id = "10000"; + T entity = getOneMockData(id); + TEMPLATE.save(getIndex(), getType(), entity); + T newEntity = TEMPLATE.pojoById(getIndex(), getType(), id, getEntityClass()); + log.info("记录:{}", JsonUtil.toString(newEntity)); + Assertions.assertThat(newEntity).isNotNull(); + } + + protected void asyncSaveBatch() throws IOException, InterruptedException { + int total = 10000; + List> listGroup = CollectionUtil.split(getMockList(total), 1000); + for (List list : listGroup) { + TEMPLATE.asyncSaveBatch(getIndex(), getType(), list, DEFAULT_BULK_LISTENER); + } + TimeUnit.SECONDS.sleep(20); + long count = TEMPLATE.count(getIndex(), getType(), new SearchSourceBuilder()); + log.info("批量更新记录数: {}", count); + Assertions.assertThat(count).isEqualTo(total); + } + + protected void getById() throws IOException { + GetResponse response = TEMPLATE.getById(getIndex(), getType(), TEST_ID_01); + Assertions.assertThat(response).isNotNull(); + log.info("记录:{}", JsonUtil.toString(response.getSourceAsMap())); + } + + protected void pojoById() throws IOException { + T entity = TEMPLATE.pojoById(getIndex(), getType(), TEST_ID_01, getEntityClass()); + Assertions.assertThat(entity).isNotNull(); + log.info("记录:{}", JsonUtil.toString(entity)); + } + + protected void pojoListByIds() throws IOException { + List ids = Arrays.asList(TEST_ID_01, TEST_ID_02); + List list = TEMPLATE.pojoListByIds(getIndex(), getType(), ids, getEntityClass()); + Assertions.assertThat(list).isNotEmpty(); + Assertions.assertThat(list.size()).isEqualTo(2); + for (T entity : list) { + log.info("记录:{}", JsonUtil.toString(entity)); + } + } + + protected void count() throws IOException { + BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); + queryBuilder.must(QueryBuilders.rangeQuery("docId").lt("100")); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(queryBuilder); + long total = TEMPLATE.count(getIndex(), getType(), searchSourceBuilder); + Assertions.assertThat(total).isNotZero(); + log.info("符合条件的记录数:{}", total); + } + + protected void query() throws IOException { + BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); + queryBuilder.must(QueryBuilders.rangeQuery("docId").lt("100")); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(queryBuilder); + searchSourceBuilder.from(FROM); + searchSourceBuilder.size(SIZE); + SearchResponse response = TEMPLATE.query(getIndex(), getType(), searchSourceBuilder); + Assertions.assertThat(response).isNotNull(); + Assertions.assertThat(response.getHits()).isNotNull(); + for (SearchHit hit : response.getHits().getHits()) { + log.info("记录:{}", hit.getSourceAsString()); + Map map = hit.getSourceAsMap(); + Assertions.assertThat(map).isNotNull(); + Assertions.assertThat(Integer.valueOf((String) map.get("docId"))).isLessThan(100); + } + } + + protected void pojoPage() throws IOException { + BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); + queryBuilder.must(QueryBuilders.rangeQuery("docId").lt("100")); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(queryBuilder); + searchSourceBuilder.from(FROM); + searchSourceBuilder.size(SIZE); + PageData page = TEMPLATE.pojoPage(getIndex(), getType(), searchSourceBuilder, getEntityClass()); + Assertions.assertThat(page).isNotNull(); + Assertions.assertThat(page.getContent()).isNotEmpty(); + for (T entity : page.getContent()) { + log.info("记录:{}", JsonUtil.toString(entity)); + } + } + + protected void pojoPageByLastId() throws IOException { + + BoolQueryBuilder queryBuilder = new BoolQueryBuilder(); + queryBuilder.must(QueryBuilders.rangeQuery("docId").lt("100")); + + long total = TEMPLATE.count(getIndex(), getType(), queryBuilder); + ScrollData scrollData = + TEMPLATE.pojoPageByScrollId(getIndex(), getType(), null, SIZE, queryBuilder, getEntityClass()); + if (scrollData == null || scrollData.getScrollId() == null) { + return; + } + Assertions.assertThat(scrollData.getTotal()).isEqualTo(total); + + long count = 0L; + scrollData.getContent().forEach(data -> { + log.info("docId: {}", data.getDocId()); + }); + count += scrollData.getContent().size(); + + String scrollId = scrollData.getScrollId(); + while (CollectionUtil.isNotEmpty(scrollData.getContent())) { + scrollData = TEMPLATE.pojoPageByScrollId(getIndex(), getType(), scrollId, SIZE, + queryBuilder, getEntityClass()); + if (scrollData == null || CollectionUtil.isEmpty(scrollData.getContent())) { + break; + } + if (StrUtil.isNotBlank(scrollData.getScrollId())) { + scrollId = scrollData.getScrollId(); + } + scrollData.getContent().forEach(data -> { + log.info("docId: {}", data.getDocId()); + }); + count += scrollData.getContent().size(); + } + log.info("total: {}", total); + Assertions.assertThat(count).isEqualTo(total); + } + + protected void pojoScroll() throws IOException { + + BoolQueryBuilder queryBuilder = new BoolQueryBuilder(); + queryBuilder.must(QueryBuilders.rangeQuery("docId").lt("100")); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.size(SIZE).query(queryBuilder).trackScores(false); + + long total = TEMPLATE.count(getIndex(), getType(), queryBuilder); + ScrollData scrollData = + TEMPLATE.pojoScrollBegin(getIndex(), getType(), searchSourceBuilder, getEntityClass()); + if (scrollData == null || scrollData.getScrollId() == null) { + return; + } + Assertions.assertThat(scrollData.getTotal()).isEqualTo(total); + + long count = 0L; + scrollData.getContent().forEach(data -> { + log.info("docId: {}", data.getDocId()); + }); + count += scrollData.getContent().size(); + + String scrollId = scrollData.getScrollId(); + while (CollectionUtil.isNotEmpty(scrollData.getContent())) { + scrollData = TEMPLATE.pojoScroll(scrollId, searchSourceBuilder, getEntityClass()); + if (scrollData == null || CollectionUtil.isEmpty(scrollData.getContent())) { + break; + } + if (StrUtil.isNotBlank(scrollData.getScrollId())) { + scrollId = scrollData.getScrollId(); + } + scrollData.getContent().forEach(data -> { + log.info("docId: {}", data.getDocId()); + }); + count += scrollData.getContent().size(); + } + TEMPLATE.pojoScrollEnd(scrollId); + log.info("total: {}", total); + Assertions.assertThat(count).isEqualTo(total); + } + + final ActionListener DEFAULT_BULK_LISTENER = new ActionListener() { + @Override + public void onResponse(BulkResponse response) { + if (response != null && !response.hasFailures()) { + log.info("【ES】异步批量写数据成功!index: {}, type: {}", getIndex(), getType()); + } else { + log.warn("【ES】异步批量写数据失败!index: {}, type: {}", getIndex(), getType()); + } + } + + @Override + public void onFailure(Exception e) { + log.error("【ES】异步批量写数据异常!index: {}, type: {}", getIndex(), getType()); + } + }; + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/test/java/io/github/dunwu/javadb/elasticsearch/TestApplication.java b/codes/javadb/elasticsearch/elasticsearch6/src/test/java/io/github/dunwu/javadb/elasticsearch/TestApplication.java new file mode 100644 index 00000000..e9ae33cd --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/test/java/io/github/dunwu/javadb/elasticsearch/TestApplication.java @@ -0,0 +1,20 @@ +package io.github.dunwu.javadb.elasticsearch; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +@SpringBootApplication +public class TestApplication extends SpringBootServletInitializer { + + public static void main(String[] args) { + SpringApplication.run(TestApplication.class, args); + } + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { + return builder.sources(TestApplication.class); + } + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/test/java/io/github/dunwu/javadb/elasticsearch/UserElasticsearchTemplateTest.java b/codes/javadb/elasticsearch/elasticsearch6/src/test/java/io/github/dunwu/javadb/elasticsearch/UserElasticsearchTemplateTest.java new file mode 100644 index 00000000..aa8aab5c --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/test/java/io/github/dunwu/javadb/elasticsearch/UserElasticsearchTemplateTest.java @@ -0,0 +1,116 @@ +package io.github.dunwu.javadb.elasticsearch; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.RandomUtil; +import io.github.dunwu.javadb.elasticsearch.entity.User; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * 使用 ElasticsearchTemplate 对 user 索引进行测试 + * + * @author Zhang Peng + * @date 2024-04-09 + */ +@Slf4j +public class UserElasticsearchTemplateTest extends BaseElasticsearchTemplateTest { + + @Override + protected String getAlias() { + return "user"; + } + + @Override + protected String getIndex() { + String date = DateUtil.format(new Date(), DatePattern.PURE_DATE_FORMAT); + return getAlias() + "_" + date; + } + + @Override + protected String getType() { + return "_doc"; + } + + @Override + protected int getShard() { + return 5; + } + + @Override + protected int getReplica() { + return 1; + } + + @Override + protected Class getEntityClass() { + return User.class; + } + + @Override + protected Map getPropertiesMap() { + return User.getPropertiesMap(); + } + + @Override + protected User getOneMockData(String id) { + return User.builder() + .id(id) + .name("测试数据" + id) + .age(RandomUtil.randomInt(1, 100)) + .build(); + } + + @Override + protected List getMockList(int num) { + List list = new LinkedList<>(); + for (int i = 1; i <= num; i++) { + User entity = getOneMockData(String.valueOf(i)); + list.add(entity); + } + return list; + } + + @Test + @DisplayName("索引管理测试") + public void indexTest() throws IOException { + super.deleteIndex(); + super.createIndex(); + super.getIndexList(); + } + + @Test + @DisplayName("写数据测试") + protected void writeTest() throws IOException { + super.save(); + super.saveBatch(); + } + + @Test + @DisplayName("异步写数据测试") + public void asyncWriteTest() throws IOException, InterruptedException { + super.asyncSave(); + super.asyncSaveBatch(); + } + + @Test + @DisplayName("读数据测试") + public void readTest() throws IOException { + super.getById(); + super.pojoById(); + super.pojoListByIds(); + super.count(); + super.query(); + super.pojoPage(); + super.pojoPageByLastId(); + super.pojoScroll(); + } + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/test/java/io/github/dunwu/javadb/elasticsearch/mapper/UserEsMapperTest.java b/codes/javadb/elasticsearch/elasticsearch6/src/test/java/io/github/dunwu/javadb/elasticsearch/mapper/UserEsMapperTest.java new file mode 100644 index 00000000..a287be67 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/test/java/io/github/dunwu/javadb/elasticsearch/mapper/UserEsMapperTest.java @@ -0,0 +1,472 @@ +package io.github.dunwu.javadb.elasticsearch.mapper; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import io.github.dunwu.javadb.elasticsearch.BaseApplicationTests; +import io.github.dunwu.javadb.elasticsearch.entity.User; +import io.github.dunwu.javadb.elasticsearch.entity.common.PageData; +import io.github.dunwu.javadb.elasticsearch.entity.common.ScrollData; +import io.github.dunwu.javadb.elasticsearch.util.JsonUtil; +import lombok.extern.slf4j.Slf4j; +import org.assertj.core.api.Assertions; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.index.query.BoolQueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.search.SearchHit; +import org.elasticsearch.search.builder.SearchSourceBuilder; +import org.elasticsearch.search.sort.SortOrder; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * ElasticsearchTemplate 测试 + * + * @author Zhang Peng + * @date 2023-11-13 + */ +@Slf4j +public class UserEsMapperTest extends BaseApplicationTests { + + static final int FROM = 0; + static final int SIZE = 10; + private static final String day = "2024-04-07"; + + @Autowired + private UserEsMapper mapper; + + @Nested + @DisplayName("删除索引测试") + class DeleteIndexTest { + + @Test + @DisplayName("删除当天索引") + public void deleteIndex() { + String index = mapper.getIndex(); + boolean indexExists = mapper.isIndexExists(); + if (!indexExists) { + log.info("【ES】{} 不存在!", index); + return; + } + mapper.deleteIndex(); + indexExists = mapper.isIndexExists(); + Assertions.assertThat(indexExists).isFalse(); + } + + @Test + @DisplayName("根据日期删除索引") + public void deleteIndexInDay() { + String index = mapper.getIndex(day); + boolean indexExists = mapper.isIndexExistsInDay(day); + if (!indexExists) { + log.info("【ES】{} 不存在!", index); + return; + } + mapper.deleteIndexInDay(day); + indexExists = mapper.isIndexExistsInDay(day); + Assertions.assertThat(indexExists).isFalse(); + } + + } + + @Nested + @DisplayName("创建索引测试") + class CreateIndexTest { + + @Test + @DisplayName("创建当天索引") + public void createIndex() { + + String index = mapper.getIndex(); + boolean indexExists = mapper.isIndexExists(); + if (indexExists) { + log.info("【ES】{} 已存在!", index); + return; + } + + mapper.createIndexIfNotExists(); + indexExists = mapper.isIndexExists(); + Assertions.assertThat(indexExists).isTrue(); + } + + @Test + @DisplayName("根据日期创建索引") + public void createIndexInDay() { + + String index = mapper.getIndex(day); + boolean indexExists = mapper.isIndexExistsInDay(day); + if (indexExists) { + log.info("【ES】{} 已存在!", index); + return; + } + + mapper.createIndexIfNotExistsInDay(day); + indexExists = mapper.isIndexExistsInDay(day); + Assertions.assertThat(indexExists).isTrue(); + } + + } + + @Nested + @DisplayName("写操作测试") + class WriteTest { + + @Test + @DisplayName("保存当天数据") + public void save() { + String id = "1"; + User entity = getOneMockData(id); + mapper.save(entity); + User newEntity = mapper.pojoById(id); + log.info("entity: {}", JsonUtil.toString(newEntity)); + Assertions.assertThat(newEntity).isNotNull(); + } + + @Test + @DisplayName("保存指定日期数据") + public void saveInDay() { + String id = "1"; + User entity = getOneMockData(id); + mapper.saveInDay(day, entity); + User newEntity = mapper.pojoByIdInDay(day, id); + log.info("entity: {}", JsonUtil.toString(newEntity)); + Assertions.assertThat(newEntity).isNotNull(); + } + + @Test + @DisplayName("批量保存当天数据") + public void batchSave() throws InterruptedException { + int total = 10000; + List> listGroup = CollectionUtil.split(getMockList(total), 1000); + for (List list : listGroup) { + mapper.asyncSaveBatch(list); + } + TimeUnit.SECONDS.sleep(20); + long count = mapper.count(new SearchSourceBuilder()); + log.info("count: {}", count); + Assertions.assertThat(count).isEqualTo(10 * 1000); + } + + @Test + @DisplayName("批量保存指定日期数据") + public void batchSaveInDay() throws InterruptedException { + int total = 10000; + List> listGroup = CollectionUtil.split(getMockList(total), 1000); + for (List list : listGroup) { + mapper.asyncSaveBatchInDay(day, list); + } + TimeUnit.SECONDS.sleep(20); + long count = mapper.countInDay(day, new SearchSourceBuilder()); + log.info("count: {}", count); + Assertions.assertThat(count).isEqualTo(10 * 1000); + } + + } + + @Nested + @DisplayName("读操作测试") + class ReadTest { + + @Test + @DisplayName("根据ID查找当日数据") + public void pojoById() { + String id = "1"; + User newEntity = mapper.pojoById(id); + log.info("entity: {}", JsonUtil.toString(newEntity)); + Assertions.assertThat(newEntity).isNotNull(); + } + + @Test + @DisplayName("根据ID查找指定日期数据") + public void pojoByIdInDay() { + String id = "1"; + User newEntity = mapper.pojoByIdInDay(day, id); + log.info("entity: {}", JsonUtil.toString(newEntity)); + Assertions.assertThat(newEntity).isNotNull(); + } + + @Test + @DisplayName("获取匹配条件的记录数") + public void count() { + BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); + queryBuilder.must(QueryBuilders.rangeQuery("docId").lt("100")); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(queryBuilder); + long total = mapper.count(searchSourceBuilder); + Assertions.assertThat(total).isNotZero(); + log.info("符合条件的记录数:{}", total); + } + + @Test + @DisplayName("获取匹配条件的指定日期记录数") + public void countInDay() { + BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); + queryBuilder.must(QueryBuilders.rangeQuery("docId").lt("100")); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(queryBuilder); + long total = mapper.countInDay(day, searchSourceBuilder); + Assertions.assertThat(total).isNotZero(); + log.info("符合条件的记录数:{}", total); + } + + @Test + @DisplayName("获取匹配条件的记录") + public void query() { + BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); + queryBuilder.must(QueryBuilders.rangeQuery("docId").lt("100")); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(queryBuilder); + searchSourceBuilder.from(FROM); + searchSourceBuilder.size(SIZE); + SearchResponse response = mapper.query(searchSourceBuilder); + Assertions.assertThat(response).isNotNull(); + Assertions.assertThat(response.getHits()).isNotNull(); + for (SearchHit hit : response.getHits().getHits()) { + log.info("记录:{}", hit.getSourceAsString()); + Map map = hit.getSourceAsMap(); + Assertions.assertThat(map).isNotNull(); + Assertions.assertThat(Integer.valueOf((String) map.get("docId"))).isLessThan(100); + } + } + + @Test + @DisplayName("获取匹配条件的指定日期记录") + public void queryInDay() { + BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); + queryBuilder.must(QueryBuilders.rangeQuery("docId").lt("100")); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(queryBuilder); + searchSourceBuilder.from(FROM); + searchSourceBuilder.size(SIZE); + SearchResponse response = mapper.queryInDay(day, searchSourceBuilder); + Assertions.assertThat(response).isNotNull(); + Assertions.assertThat(response.getHits()).isNotNull(); + for (SearchHit hit : response.getHits().getHits()) { + log.info("记录:{}", hit.getSourceAsString()); + Map map = hit.getSourceAsMap(); + Assertions.assertThat(map).isNotNull(); + Assertions.assertThat(Integer.valueOf((String) map.get("docId"))).isLessThan(100); + } + } + + @Test + @DisplayName("from + size 分页查询当日数据") + public void pojoPage() { + BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); + queryBuilder.must(QueryBuilders.rangeQuery("docId").lt("100")); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(queryBuilder); + searchSourceBuilder.from(FROM); + searchSourceBuilder.size(SIZE); + PageData page = mapper.pojoPage(searchSourceBuilder); + Assertions.assertThat(page).isNotNull(); + Assertions.assertThat(page.getContent()).isNotEmpty(); + for (User entity : page.getContent()) { + log.info("记录:{}", JsonUtil.toString(entity)); + } + } + + @Test + @DisplayName("from + size 分页查询指定日期数据") + public void pojoPageInDay() { + BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); + queryBuilder.must(QueryBuilders.rangeQuery("docId").lt("100")); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(queryBuilder); + searchSourceBuilder.from(FROM); + searchSourceBuilder.size(SIZE); + PageData page = mapper.pojoPageInDay(day, searchSourceBuilder); + Assertions.assertThat(page).isNotNull(); + Assertions.assertThat(page.getContent()).isNotEmpty(); + for (User entity : page.getContent()) { + log.info("记录:{}", JsonUtil.toString(entity)); + } + } + + @Test + @DisplayName("search after 分页查询当日数据") + protected void pojoPageByLastId() { + + BoolQueryBuilder queryBuilder = new BoolQueryBuilder(); + queryBuilder.must(QueryBuilders.rangeQuery("docId").lt("100")); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(queryBuilder); + long total = mapper.count(searchSourceBuilder); + ScrollData scrollData = mapper.pojoPageByLastId(null, SIZE, queryBuilder); + if (scrollData == null || scrollData.getScrollId() == null) { + return; + } + Assertions.assertThat(scrollData.getTotal()).isEqualTo(total); + + long count = 0L; + scrollData.getContent().forEach(data -> { + log.info("docId: {}", data.getDocId()); + }); + count += scrollData.getContent().size(); + + String scrollId = scrollData.getScrollId(); + while (CollectionUtil.isNotEmpty(scrollData.getContent())) { + scrollData = mapper.pojoPageByLastId(scrollId, SIZE, queryBuilder); + if (scrollData == null || CollectionUtil.isEmpty(scrollData.getContent())) { + break; + } + if (StrUtil.isNotBlank(scrollData.getScrollId())) { + scrollId = scrollData.getScrollId(); + } + scrollData.getContent().forEach(data -> { + log.info("docId: {}", data.getDocId()); + }); + count += scrollData.getContent().size(); + } + log.info("total: {}", total); + Assertions.assertThat(count).isEqualTo(total); + } + + @Test + @DisplayName("search after 分页查询指定日期数据") + protected void pojoPageByLastIdInDay() { + + BoolQueryBuilder queryBuilder = new BoolQueryBuilder(); + queryBuilder.must(QueryBuilders.rangeQuery("docId").lt("100")); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(queryBuilder); + long total = mapper.count(searchSourceBuilder); + ScrollData scrollData = mapper.pojoPageByLastIdInDay(day, null, SIZE, queryBuilder); + if (scrollData == null || scrollData.getScrollId() == null) { + return; + } + Assertions.assertThat(scrollData.getTotal()).isEqualTo(total); + + long count = 0L; + scrollData.getContent().forEach(data -> { + log.info("docId: {}", data.getDocId()); + }); + count += scrollData.getContent().size(); + + String scrollId = scrollData.getScrollId(); + while (CollectionUtil.isNotEmpty(scrollData.getContent())) { + scrollData = mapper.pojoPageByLastIdInDay(day, scrollId, SIZE, queryBuilder); + if (scrollData == null || CollectionUtil.isEmpty(scrollData.getContent())) { + break; + } + if (StrUtil.isNotBlank(scrollData.getScrollId())) { + scrollId = scrollData.getScrollId(); + } + scrollData.getContent().forEach(data -> { + log.info("docId: {}", data.getDocId()); + }); + count += scrollData.getContent().size(); + } + log.info("total: {}", total); + Assertions.assertThat(count).isEqualTo(total); + } + + @Test + @DisplayName("滚动翻页当日数据") + public void pojoScroll() { + + final int size = 100; + + BoolQueryBuilder queryBuilder = new BoolQueryBuilder(); + queryBuilder.must(QueryBuilders.rangeQuery("docId").lt("100")); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.size(size).sort("docId", SortOrder.ASC).query(queryBuilder).trackScores(false); + + long total = mapper.count(searchSourceBuilder); + log.info("total: {}", total); + + ScrollData scrollData = mapper.pojoScrollBegin(searchSourceBuilder); + if (scrollData == null || scrollData.getScrollId() == null) { + return; + } + + long count = 0L; + scrollData.getContent().forEach(data -> { + log.info("docId: {}", data.getDocId()); + }); + Assertions.assertThat(scrollData.getTotal()).isEqualTo(total); + count += scrollData.getContent().size(); + + String scrollId = scrollData.getScrollId(); + while (CollectionUtil.isNotEmpty(scrollData.getContent())) { + scrollData = mapper.pojoScroll(scrollId, searchSourceBuilder); + if (scrollData != null && StrUtil.isNotBlank(scrollData.getScrollId())) { + scrollId = scrollData.getScrollId(); + } + scrollData.getContent().forEach(data -> { + log.info("docId: {}", data.getDocId()); + }); + count += scrollData.getContent().size(); + } + mapper.pojoScrollEnd(scrollId); + Assertions.assertThat(count).isEqualTo(total); + } + + @Test + @DisplayName("滚动翻页指定日期数据") + public void pojoScrollInDay() { + + final int size = 100; + + BoolQueryBuilder queryBuilder = new BoolQueryBuilder(); + queryBuilder.must(QueryBuilders.rangeQuery("docId").lt("100")); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.size(size).sort("docId", SortOrder.ASC).query(queryBuilder).trackScores(false); + + long total = mapper.countInDay(day, searchSourceBuilder); + log.info("total: {}", total); + + ScrollData scrollData = mapper.pojoScrollBeginInDay(day, searchSourceBuilder); + if (scrollData == null || scrollData.getScrollId() == null) { + return; + } + + long count = 0L; + scrollData.getContent().forEach(data -> { + log.info("docId: {}", data.getDocId()); + }); + Assertions.assertThat(scrollData.getTotal()).isEqualTo(total); + count += scrollData.getContent().size(); + + String scrollId = scrollData.getScrollId(); + while (CollectionUtil.isNotEmpty(scrollData.getContent())) { + scrollData = mapper.pojoScroll(scrollId, searchSourceBuilder); + if (scrollData != null && StrUtil.isNotBlank(scrollData.getScrollId())) { + scrollId = scrollData.getScrollId(); + } + scrollData.getContent().forEach(data -> { + log.info("docId: {}", data.getDocId()); + }); + count += scrollData.getContent().size(); + } + mapper.pojoScrollEnd(scrollId); + Assertions.assertThat(count).isEqualTo(total); + } + + } + + public User getOneMockData(String id) { + return User.builder() + .id(id) + .name("测试数据" + id) + .age(RandomUtil.randomInt(1, 100)) + .build(); + } + + public List getMockList(int num) { + List list = new LinkedList<>(); + for (int i = 1; i <= num; i++) { + User entity = getOneMockData(String.valueOf(i)); + list.add(entity); + } + return list; + } + +} diff --git a/codes/javadb/elasticsearch/elasticsearch7/pom.xml b/codes/javadb/elasticsearch/elasticsearch7/pom.xml new file mode 100644 index 00000000..bd4e66b1 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch7/pom.xml @@ -0,0 +1,87 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.6.3 + + + io.github.dunwu + javadb-elasticsearch7 + 1.0.0 + jar + + + 7.16.3 + + + + + org.springframework.boot + spring-boot-starter-data-elasticsearch + + + org.springframework.boot + spring-boot-starter-json + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + + + cn.hutool + hutool-all + 5.7.20 + + + + co.elastic.clients + elasticsearch-java + 7.16.3 + + + + org.elasticsearch.client + elasticsearch-rest-client + ${elasticsearch.version} + + + org.elasticsearch.client + elasticsearch-rest-high-level-client + ${elasticsearch.version} + + + + + + + com.fasterxml.jackson.core + jackson-databind + 2.12.3 + + + com.fasterxml.jackson.core + jackson-core + 2.12.3 + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/SpringBootDataElasticsearchApplication.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/SpringBootDataElasticsearchApplication.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/SpringBootDataElasticsearchApplication.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/SpringBootDataElasticsearchApplication.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/NamingStrategy.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/NamingStrategy.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/NamingStrategy.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/NamingStrategy.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/OrderType.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/OrderType.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/OrderType.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/OrderType.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/QueryJudgeType.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/QueryJudgeType.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/QueryJudgeType.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/QueryJudgeType.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/QueryLogicType.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/QueryLogicType.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/QueryLogicType.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/QueryLogicType.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/elasticsearch/ElasticSearchUtil.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/elasticsearch/ElasticSearchUtil.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/elasticsearch/ElasticSearchUtil.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/elasticsearch/ElasticSearchUtil.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/elasticsearch/QueryDocument.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/elasticsearch/QueryDocument.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/elasticsearch/QueryDocument.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/elasticsearch/QueryDocument.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/elasticsearch/QueryField.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/elasticsearch/QueryField.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/elasticsearch/QueryField.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/elasticsearch/QueryField.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Article.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Article.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Article.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Article.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/ArticleBuilder.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/ArticleBuilder.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/ArticleBuilder.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/ArticleBuilder.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Author.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Author.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Author.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Author.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Book.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Book.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Book.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Book.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Car.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Car.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Car.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Car.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/GirlFriend.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/GirlFriend.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/GirlFriend.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/GirlFriend.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Operation.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Operation.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Operation.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Operation.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Person.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Person.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Person.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Person.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/PersonMultipleLevelNested.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/PersonMultipleLevelNested.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/PersonMultipleLevelNested.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/PersonMultipleLevelNested.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Product.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Product.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Product.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Product.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Sector.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Sector.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Sector.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Sector.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/User.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/User.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/User.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/User.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/UserQuery.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/UserQuery.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/UserQuery.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/UserQuery.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/ArticleRepository.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/ArticleRepository.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/ArticleRepository.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/ArticleRepository.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/BookRepository.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/BookRepository.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/BookRepository.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/BookRepository.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/OperationRepository.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/OperationRepository.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/OperationRepository.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/OperationRepository.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/ProductRepository.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/ProductRepository.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/ProductRepository.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/ProductRepository.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/UserRepository.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/UserRepository.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/UserRepository.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/UserRepository.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/resources/application.properties b/codes/javadb/elasticsearch/elasticsearch7/src/main/resources/application.properties similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/resources/application.properties rename to codes/javadb/elasticsearch/elasticsearch7/src/main/resources/application.properties diff --git a/codes/javadb/javadb-elasticsearch/src/main/resources/banner.txt b/codes/javadb/elasticsearch/elasticsearch7/src/main/resources/banner.txt similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/resources/banner.txt rename to codes/javadb/elasticsearch/elasticsearch7/src/main/resources/banner.txt diff --git a/codes/javadb/javadb-elasticsearch/src/main/resources/logback.xml b/codes/javadb/elasticsearch/elasticsearch7/src/main/resources/logback.xml similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/resources/logback.xml rename to codes/javadb/elasticsearch/elasticsearch7/src/main/resources/logback.xml diff --git a/codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestHighLevelClientDocumentApiTest.java b/codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestHighLevelClientDocumentApiTest.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestHighLevelClientDocumentApiTest.java rename to codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestHighLevelClientDocumentApiTest.java diff --git a/codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestHighLevelClientDocumentSearchApiTest.java b/codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestHighLevelClientDocumentSearchApiTest.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestHighLevelClientDocumentSearchApiTest.java rename to codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestHighLevelClientDocumentSearchApiTest.java diff --git a/codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestHighLevelClientIndexApiTest.java b/codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestHighLevelClientIndexApiTest.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestHighLevelClientIndexApiTest.java rename to codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestHighLevelClientIndexApiTest.java diff --git a/codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestLowLevelClientTest.java b/codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestLowLevelClientTest.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestLowLevelClientTest.java rename to codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestLowLevelClientTest.java diff --git a/codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/Geoip.java b/codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/Geoip.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/Geoip.java rename to codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/Geoip.java diff --git a/codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/KibanaSampleDataEcommerceBean.java b/codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/KibanaSampleDataEcommerceBean.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/KibanaSampleDataEcommerceBean.java rename to codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/KibanaSampleDataEcommerceBean.java diff --git a/codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/Location.java b/codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/Location.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/Location.java rename to codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/Location.java diff --git a/codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/ProductsItem.java b/codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/ProductsItem.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/ProductsItem.java rename to codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/ProductsItem.java diff --git a/codes/javadb/elasticsearch/pom.xml b/codes/javadb/elasticsearch/pom.xml new file mode 100644 index 00000000..f7079658 --- /dev/null +++ b/codes/javadb/elasticsearch/pom.xml @@ -0,0 +1,15 @@ + + + 4.0.0 + + io.github.dunwu + javadb-elasticsearch + 1.0.0 + pom + + + elasticsearch6 + elasticsearch7 + + diff --git a/codes/javadb/h2/pom.xml b/codes/javadb/h2/pom.xml new file mode 100644 index 00000000..b7d09205 --- /dev/null +++ b/codes/javadb/h2/pom.xml @@ -0,0 +1,53 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.6.3 + + + io.github.dunwu + javadb-h2 + 1.0.0 + jar + + + + org.springframework.boot + spring-boot-starter-data-rest + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-test + test + + + org.projectlombok + lombok + + + + + com.h2database + h2 + 2.1.210 + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/codes/javadb/javadb-h2/src/main/java/io/github/dunwu/javadb/h2/springboot/SpringBootDataJpaApplication.java b/codes/javadb/h2/src/main/java/io/github/dunwu/javadb/h2/springboot/SpringBootDataJpaApplication.java similarity index 100% rename from codes/javadb/javadb-h2/src/main/java/io/github/dunwu/javadb/h2/springboot/SpringBootDataJpaApplication.java rename to codes/javadb/h2/src/main/java/io/github/dunwu/javadb/h2/springboot/SpringBootDataJpaApplication.java diff --git a/codes/javadb/javadb-h2/src/main/java/io/github/dunwu/javadb/h2/springboot/User.java b/codes/javadb/h2/src/main/java/io/github/dunwu/javadb/h2/springboot/User.java similarity index 100% rename from codes/javadb/javadb-h2/src/main/java/io/github/dunwu/javadb/h2/springboot/User.java rename to codes/javadb/h2/src/main/java/io/github/dunwu/javadb/h2/springboot/User.java diff --git a/codes/javadb/javadb-h2/src/main/java/io/github/dunwu/javadb/h2/springboot/UserRepository.java b/codes/javadb/h2/src/main/java/io/github/dunwu/javadb/h2/springboot/UserRepository.java similarity index 100% rename from codes/javadb/javadb-h2/src/main/java/io/github/dunwu/javadb/h2/springboot/UserRepository.java rename to codes/javadb/h2/src/main/java/io/github/dunwu/javadb/h2/springboot/UserRepository.java diff --git a/codes/javadb/javadb-h2/src/main/resources/application.properties b/codes/javadb/h2/src/main/resources/application.properties similarity index 100% rename from codes/javadb/javadb-h2/src/main/resources/application.properties rename to codes/javadb/h2/src/main/resources/application.properties diff --git a/codes/javadb/javadb-h2/src/main/resources/banner.txt b/codes/javadb/h2/src/main/resources/banner.txt similarity index 100% rename from codes/javadb/javadb-h2/src/main/resources/banner.txt rename to codes/javadb/h2/src/main/resources/banner.txt diff --git a/codes/javadb/javadb-h2/src/main/resources/logback.xml b/codes/javadb/h2/src/main/resources/logback.xml similarity index 100% rename from codes/javadb/javadb-h2/src/main/resources/logback.xml rename to codes/javadb/h2/src/main/resources/logback.xml diff --git a/codes/javadb/javadb-h2/src/main/resources/sql/data-h2.sql b/codes/javadb/h2/src/main/resources/sql/data-h2.sql similarity index 100% rename from codes/javadb/javadb-h2/src/main/resources/sql/data-h2.sql rename to codes/javadb/h2/src/main/resources/sql/data-h2.sql diff --git a/codes/javadb/javadb-h2/src/main/resources/sql/schema-h2.sql b/codes/javadb/h2/src/main/resources/sql/schema-h2.sql similarity index 100% rename from codes/javadb/javadb-h2/src/main/resources/sql/schema-h2.sql rename to codes/javadb/h2/src/main/resources/sql/schema-h2.sql diff --git a/codes/javadb/javadb-h2/src/test/java/io/github/dunwu/javadb/h2/H2JdbcTest.java b/codes/javadb/h2/src/test/java/io/github/dunwu/javadb/h2/H2JdbcTest.java similarity index 100% rename from codes/javadb/javadb-h2/src/test/java/io/github/dunwu/javadb/h2/H2JdbcTest.java rename to codes/javadb/h2/src/test/java/io/github/dunwu/javadb/h2/H2JdbcTest.java diff --git a/codes/javadb/javadb-h2/src/test/java/io/github/dunwu/javadb/h2/springboot/SpringBootJpaRestTest.java b/codes/javadb/h2/src/test/java/io/github/dunwu/javadb/h2/springboot/SpringBootJpaRestTest.java similarity index 100% rename from codes/javadb/javadb-h2/src/test/java/io/github/dunwu/javadb/h2/springboot/SpringBootJpaRestTest.java rename to codes/javadb/h2/src/test/java/io/github/dunwu/javadb/h2/springboot/SpringBootJpaRestTest.java diff --git a/codes/javadb/javadb-h2/src/test/java/io/github/dunwu/javadb/h2/springboot/SpringBootJpaTest.java b/codes/javadb/h2/src/test/java/io/github/dunwu/javadb/h2/springboot/SpringBootJpaTest.java similarity index 100% rename from codes/javadb/javadb-h2/src/test/java/io/github/dunwu/javadb/h2/springboot/SpringBootJpaTest.java rename to codes/javadb/h2/src/test/java/io/github/dunwu/javadb/h2/springboot/SpringBootJpaTest.java diff --git a/codes/javadb/hbase/pom.xml b/codes/javadb/hbase/pom.xml new file mode 100644 index 00000000..1c0e69f8 --- /dev/null +++ b/codes/javadb/hbase/pom.xml @@ -0,0 +1,60 @@ + + + 4.0.0 + + + io.github.dunwu + javadb + 1.0.0 + ../pom.xml + + + javadb-hbase + jar + + + 1.8 + ${java.version} + ${java.version} + UTF-8 + UTF-8 + + + + + org.apache.hbase + hbase-client + + + org.apache.hadoop + hadoop-auth + + + cn.hutool + hutool-all + + + com.fasterxml.jackson.core + jackson-databind + + + com.alibaba + fastjson + + + org.projectlombok + lombok + + + org.springframework + spring-context-support + + + org.springframework.boot + spring-boot-starter-test + test + + + diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseAdmin.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseAdmin.java new file mode 100644 index 00000000..fd9b07a8 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseAdmin.java @@ -0,0 +1,294 @@ +package io.github.dunwu.javadb.hbase; + +import cn.hutool.core.io.IoUtil; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.NamespaceDescriptor; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.client.Admin; +import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; +import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; +import org.apache.hadoop.hbase.client.Connection; +import org.apache.hadoop.hbase.client.ConnectionFactory; +import org.apache.hadoop.hbase.client.Table; +import org.apache.hadoop.hbase.client.TableDescriptor; +import org.apache.hadoop.hbase.client.TableDescriptorBuilder; +import org.apache.hadoop.hbase.security.User; +import org.apache.hadoop.security.UserGroupInformation; + +import java.io.Closeable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * HBase 管理工具类 + * + * @author Zhang Peng + * @date 2023-03-27 + */ +public class HbaseAdmin implements Closeable { + + private final Connection connection; + private final Configuration configuration; + + protected HbaseAdmin(Configuration configuration) throws IOException { + this.configuration = configuration; + // 无需鉴权连接 + // this.connection = ConnectionFactory.createConnection(configuration); + // 鉴权连接 + this.connection = ConnectionFactory.createConnection(configuration, null, + new User.SecureHadoopUser(UserGroupInformation.createRemoteUser("test"))); + } + + protected HbaseAdmin(Connection connection) { + this.configuration = connection.getConfiguration(); + this.connection = connection; + } + + public synchronized static HbaseAdmin newInstance(Configuration configuration) throws IOException { + if (configuration == null) { + throw new IllegalArgumentException("configuration can not be null!"); + } + return new HbaseAdmin(configuration); + } + + public synchronized static HbaseAdmin newInstance(Connection connection) throws IOException { + if (connection == null) { + throw new IllegalArgumentException("connection can not be null!"); + } + return new HbaseAdmin(connection); + } + + /** + * 关闭内部持有的 HBase Connection 实例 + */ + @Override + public synchronized void close() { + if (null == connection || connection.isClosed()) { + return; + } + IoUtil.close(connection); + } + + /** + * 获取 HBase 连接实例 + * + * @return / + */ + public Connection getConnection() { + if (null == connection) { + throw new RuntimeException("HBase connection init failed..."); + } + return connection; + } + + /** + * 获取 HBase 配置 + * + * @return / + */ + public Configuration getConfiguration() { + return configuration; + } + + /** + * 创建命名空间 + * + * @param namespace 命名空间 + */ + public void createNamespace(String namespace) throws IOException { + Admin admin = null; + try { + admin = getAdmin(); + NamespaceDescriptor nd = NamespaceDescriptor.create(namespace).build(); + admin.createNamespace(nd); + } finally { + recycle(admin); + } + } + + /** + * 删除命名空间 + * + * @param namespace 命名空间 + */ + public void dropNamespace(String namespace) throws IOException { + dropNamespace(namespace, false); + } + + /** + * 删除命名空间 + * + * @param namespace 命名空间 + * @param force 是否强制删除 + */ + public void dropNamespace(String namespace, boolean force) throws IOException { + Admin admin = null; + try { + admin = getAdmin(); + if (force) { + TableName[] tableNames = admin.listTableNamesByNamespace(namespace); + for (TableName name : tableNames) { + admin.disableTable(name); + admin.deleteTable(name); + } + } + admin.deleteNamespace(namespace); + } finally { + recycle(admin); + } + } + + /** + * 获取所有命名空间 + */ + public String[] listNamespaces() throws IOException { + Admin admin = null; + try { + admin = getAdmin(); + return admin.listNamespaces(); + } finally { + recycle(admin); + } + } + + /** + * 指定表是否存在 + * + * @param tableName 表名 + */ + public boolean existsTable(TableName tableName) throws IOException { + Admin admin = getAdmin(); + boolean result = admin.tableExists(tableName); + admin.close(); + return result; + } + + /** + * 创建表 + * + * @param tableName 表名 + * @param families 列族 + */ + public void createTable(TableName tableName, String... families) throws IOException { + createTable(tableName, null, families); + } + + /** + * 创建表 + * + * @param tableName 表名 + * @param splitKeys 表初始区域的拆分关键字 + * @param families 列族 + */ + public void createTable(TableName tableName, byte[][] splitKeys, String... families) throws IOException { + + List columnFamilyDescriptorList = new ArrayList<>(); + TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(tableName); + for (String cf : families) { + ColumnFamilyDescriptor columnFamilyDescriptor = ColumnFamilyDescriptorBuilder.of(cf); + columnFamilyDescriptorList.add(columnFamilyDescriptor); + } + builder.setColumnFamilies(columnFamilyDescriptorList); + + TableDescriptor td = builder.build(); + Admin admin = getAdmin(); + if (splitKeys != null) { + admin.createTable(td, splitKeys); + } else { + admin.createTable(td); + } + admin.close(); + } + + /** + * 删除表 + * + * @param tableName 表名 + */ + public void dropTable(TableName tableName) throws IOException { + if (existsTable(tableName)) { + Admin admin = getAdmin(); + if (admin.isTableEnabled(tableName)) { + disableTable(tableName); + } + admin.deleteTable(tableName); + admin.close(); + } + } + + /** + * 禁用表 + * + * @param tableName 表名 + */ + public void disableTable(TableName tableName) throws IOException { + Admin admin = getAdmin(); + admin.disableTable(tableName); + admin.close(); + } + + /** + * 启用表 + * + * @param tableName 表名 + */ + public void enableTable(TableName tableName) throws IOException { + Admin admin = getAdmin(); + admin.enableTable(tableName); + admin.close(); + } + + /** + * 获取所有表 + */ + public TableName[] listTableNames() throws IOException { + Admin admin = null; + try { + admin = getAdmin(); + return admin.listTableNames(); + } finally { + recycle(admin); + } + } + + /** + * 获取指定命名空间下的所有表 + */ + public TableName[] listTableNamesByNamespace(String namespace) throws IOException { + Admin admin = null; + try { + admin = getAdmin(); + return admin.listTableNamesByNamespace(namespace); + } finally { + recycle(admin); + } + } + + /** + * 获取 {@link org.apache.hadoop.hbase.client.Table} 实例 + * + * @param tableName 表名 + * @return / + */ + public Table getTable(TableName tableName) throws IOException { + return getConnection().getTable(tableName); + } + + /** + * 获取 {@link org.apache.hadoop.hbase.client.Admin} 实例 + * + * @return / + */ + public Admin getAdmin() throws IOException { + return getConnection().getAdmin(); + } + + private void recycle(Admin admin) { + if (null == admin) { + return; + } + IoUtil.close(admin); + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseFactory.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseFactory.java new file mode 100644 index 00000000..c4a66c53 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseFactory.java @@ -0,0 +1,35 @@ +package io.github.dunwu.javadb.hbase; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseConfiguration; + +import java.io.IOException; + +/** + * HBase 工具实例化工厂 + * + * @author Zhang Peng + * @date 2023-07-05 + */ +public class HbaseFactory { + + public static HbaseTemplate newHbaseTemplate() throws IOException { + return HbaseTemplate.newInstance(newHbaseConfiguration()); + } + + public static HbaseAdmin newHbaseAdmin() throws IOException { + return HbaseAdmin.newInstance(newHbaseConfiguration()); + } + + public static Configuration newHbaseConfiguration() { + Configuration configuration = HBaseConfiguration.create(); + configuration.set("hbase.zookeeper.quorum", "127.0.0.1"); + configuration.set("hbase.zookeeper.property.clientPort", "2181"); + configuration.set("hbase.rootdir", "/hbase"); + configuration.set("hbase.meta.replicas.use", "true"); + configuration.set("hbase.client.retries.number", "5"); + configuration.set("hbase.rpc.timeout", "600000"); + return configuration; + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseTemplate.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseTemplate.java new file mode 100644 index 00000000..018bbddd --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseTemplate.java @@ -0,0 +1,1011 @@ +package io.github.dunwu.javadb.hbase; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; +import io.github.dunwu.javadb.hbase.entity.BaseHbaseEntity; +import io.github.dunwu.javadb.hbase.entity.common.ColumnDo; +import io.github.dunwu.javadb.hbase.entity.common.FamilyDo; +import io.github.dunwu.javadb.hbase.entity.common.PageData; +import io.github.dunwu.javadb.hbase.entity.common.RowDo; +import io.github.dunwu.javadb.hbase.entity.common.ScrollData; +import io.github.dunwu.javadb.hbase.entity.scan.MultiFamilyScan; +import io.github.dunwu.javadb.hbase.entity.scan.SingleFamilyScan; +import io.github.dunwu.javadb.hbase.util.JsonUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.Cell; +import org.apache.hadoop.hbase.CellUtil; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.client.Connection; +import org.apache.hadoop.hbase.client.ConnectionFactory; +import org.apache.hadoop.hbase.client.Delete; +import org.apache.hadoop.hbase.client.Durability; +import org.apache.hadoop.hbase.client.Get; +import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.client.ResultScanner; +import org.apache.hadoop.hbase.client.Row; +import org.apache.hadoop.hbase.client.Scan; +import org.apache.hadoop.hbase.client.Table; +import org.apache.hadoop.hbase.security.User; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.security.UserGroupInformation; + +import java.io.Closeable; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * HBase 客户端封装工具类 + * + * @author Zhang Peng + * @date 2023-03-27 + */ +@Slf4j +public class HbaseTemplate implements Closeable { + + private final Connection connection; + + private final Configuration configuration; + + protected HbaseTemplate(Configuration configuration) throws IOException { + this.configuration = configuration; + // 无需鉴权连接 + // this.connection = ConnectionFactory.createConnection(configuration); + // 鉴权连接 + this.connection = ConnectionFactory.createConnection(configuration, null, + new User.SecureHadoopUser(UserGroupInformation.createRemoteUser("test"))); + } + + protected HbaseTemplate(Connection connection) { + this.configuration = connection.getConfiguration(); + this.connection = connection; + } + + public static synchronized HbaseTemplate newInstance(Configuration configuration) throws IOException { + if (configuration == null) { + throw new IllegalArgumentException("configuration can not be null!"); + } + return new HbaseTemplate(configuration); + } + + public synchronized static HbaseTemplate newInstance(Connection connection) { + if (connection == null) { + throw new IllegalArgumentException("connection can not be null!"); + } + return new HbaseTemplate(connection); + } + + /** + * 关闭内部持有的 HBase Connection 实例 + */ + @Override + public synchronized void close() { + if (null == connection || connection.isClosed()) { + return; + } + IoUtil.close(connection); + } + + /** + * 获取 HBase 连接实例 + * + * @return / + */ + public Connection getConnection() { + if (null == connection) { + throw new RuntimeException("HBase connection init failed..."); + } + return connection; + } + + /** + * 获取 HBase 配置 + * + * @return / + */ + public Configuration getConfiguration() { + return configuration; + } + + /** + * 获取 {@link org.apache.hadoop.hbase.client.Table} 实例 + * + * @param tableName 表名 + * @return / + */ + public Table getTable(String tableName) throws IOException { + return getTable(TableName.valueOf(tableName)); + } + + /** + * 获取 {@link org.apache.hadoop.hbase.client.Table} 实例 + * + * @param tableName 表名 + * @return / + */ + + public synchronized Table getTable(TableName tableName) throws IOException { + return connection.getTable(tableName); + } + + // ===================================================================================== + // put 操作封装 + // ===================================================================================== + + public void put(String tableName, Put put) throws IOException { + if (StrUtil.isBlank(tableName) || put == null) { + return; + } + Table table = getTable(tableName); + try { + table.put(put); + } finally { + recycle(table); + } + } + + public void put(String tableName, String row, String family, String column, String value) + throws IOException { + Put put = newPut(row, null, family, column, value); + put(tableName, put); + } + + public void put(String tableName, String row, Long timestamp, String family, String column, String value) + throws IOException { + Put put = newPut(row, timestamp, family, column, value); + put(tableName, put); + } + + public void put(String tableName, String row, String family, Object obj) throws IOException { + put(tableName, row, null, family, obj); + } + + public void put(String tableName, String row, Long timestamp, String family, Object obj) throws IOException { + Put put = newPut(row, timestamp, family, obj); + put(tableName, put); + } + + public void put(String tableName, String row, String family, Map columnMap) + throws IOException { + Put put = newPut(row, null, family, columnMap); + put(tableName, put); + } + + public void put(String tableName, String row, Long timestamp, String family, Map columnMap) + throws IOException { + Put put = newPut(row, timestamp, family, columnMap); + put(tableName, put); + } + + public void put(String tableName, String row, Long timestamp, Map> familyMap) + throws IOException { + Put put = newPut(row, timestamp, familyMap); + put(tableName, put); + } + + public void put(String tableName, String family, T entity) throws IOException { + put(tableName, entity.getRowKey(), family, entity); + } + + public void batchPut(String tableName, Collection list) throws IOException, InterruptedException { + batch(tableName, list); + } + + public void batchPut(String tableName, String family, Collection list) + throws IOException, InterruptedException { + if (StrUtil.isBlank(tableName) || StrUtil.isBlank(family) || CollectionUtil.isEmpty(list)) { + return; + } + List puts = newPutList(family, list); + batchPut(tableName, puts); + } + + public static Put newPut(String row, Long timestamp, String family, String column, String value) { + if (StrUtil.isBlank(row) || StrUtil.isBlank(family) || StrUtil.isBlank(column) || StrUtil.isBlank(value)) { + return null; + } + Map columnMap = new LinkedHashMap<>(1); + columnMap.put(column, value); + return newPut(row, timestamp, family, columnMap); + } + + public static Put newPut(String row, Long timestamp, String family, Map columnMap) { + if (StrUtil.isBlank(row) || StrUtil.isBlank(family) || MapUtil.isEmpty(columnMap)) { + return null; + } + Map> familyMap = new LinkedHashMap<>(1); + familyMap.put(family, columnMap); + return newPut(row, timestamp, familyMap); + } + + public static Put newPut(String row, Long timestamp, String family, Object obj) { + if (obj == null) { + return null; + } + Map columnMap = JsonUtil.toMap(obj); + return newPut(row, timestamp, family, columnMap); + } + + public static Put newPut(String row, Long timestamp, Map> familyMap) { + + if (StrUtil.isBlank(row) || MapUtil.isEmpty(familyMap)) { + return null; + } + + if (timestamp == null) { + timestamp = System.currentTimeMillis(); + } + + Put put = new Put(Bytes.toBytes(row)); + for (Map.Entry> e : familyMap.entrySet()) { + String family = e.getKey(); + Map columnMap = e.getValue(); + if (MapUtil.isNotEmpty(columnMap)) { + for (Map.Entry entry : columnMap.entrySet()) { + String column = entry.getKey(); + Object value = entry.getValue(); + if (ObjectUtil.isEmpty(value)) { + continue; + } + if (value instanceof String) { + put.addColumn(Bytes.toBytes(family), Bytes.toBytes(column), timestamp, + Bytes.toBytes(value.toString())); + } else if (value instanceof Date) { + put.addColumn(Bytes.toBytes(family), Bytes.toBytes(column), timestamp, + Bytes.toBytes(DateUtil.format((Date) value, DatePattern.NORM_DATETIME_PATTERN))); + } else { + put.addColumn(Bytes.toBytes(family), Bytes.toBytes(column), + timestamp, Bytes.toBytes(JsonUtil.toString(value))); + } + } + } + } + return put; + } + + private static List newPutList(String family, Collection list) { + long timestamp = System.currentTimeMillis(); + List puts = new ArrayList<>(); + for (T entity : list) { + Put put = newPut(entity.getRowKey(), timestamp, family, entity); + puts.add(put); + } + return puts; + } + + // ===================================================================================== + // delete 操作封装 + // ===================================================================================== + + public void delete(String tableName, Delete delete) throws IOException { + if (StrUtil.isBlank(tableName) || delete == null) { + return; + } + Table table = getTable(tableName); + try { + table.delete(delete); + } finally { + recycle(table); + } + } + + public void delete(String tableName, String row) throws IOException { + Delete delete = new Delete(Bytes.toBytes(row)); + delete(tableName, delete); + } + + public void batchDelete(String tableName, String... rows) throws IOException, InterruptedException { + if (ArrayUtil.isEmpty(rows)) { + return; + } + List deletes = Stream.of(rows) + .map(row -> new Delete(Bytes.toBytes(row))) + .distinct().collect(Collectors.toList()); + batchDelete(tableName, deletes); + } + + public void batchDelete(String tableName, List deletes) throws IOException, InterruptedException { + batch(tableName, deletes); + } + + // ===================================================================================== + // get 操作封装 + // ===================================================================================== + + public Result get(String tableName, String row) throws IOException { + if (StrUtil.isBlank(tableName) || StrUtil.isBlank(row)) { + return null; + } + Get get = newGet(row); + return get(tableName, get); + } + + public Result get(String tableName, Get get) throws IOException { + if (StrUtil.isBlank(tableName) || get == null) { + return null; + } + Table table = getTable(tableName); + try { + return table.get(get); + } finally { + recycle(table); + } + } + + public Result[] batchGet(String tableName, String[] rows) throws IOException { + if (StrUtil.isBlank(tableName) || ArrayUtil.isEmpty(rows)) { + return null; + } + List gets = newGetList(rows); + return batchGet(tableName, gets); + } + + public Result[] batchGet(String tableName, List gets) throws IOException { + if (StrUtil.isBlank(tableName) || CollectionUtil.isEmpty(gets)) { + return null; + } + Table table = getTable(tableName); + try { + return table.get(gets); + } finally { + recycle(table); + } + } + + /** + * 指定行、列族,以实体 {@link T} 形式返回数据 + * + * @param tableName 表名 + * @param row 指定行 + * @param family 列族 + * @param clazz 返回实体类型 + * @param 实体类型 + * @return / + */ + public T getEntity(String tableName, String row, String family, Class clazz) throws IOException { + + if (StrUtil.isBlank(tableName) || StrUtil.isBlank(row) || StrUtil.isBlank(family) || clazz == null) { + return null; + } + Map fieldMap = ReflectUtil.getFieldMap(clazz); + String[] columns = fieldMap.keySet().toArray(new String[0]); + Map columnMap = getColumnMap(tableName, row, family, columns); + if (MapUtil.isEmpty(columnMap)) { + return null; + } + return toEntity(ColumnDo.toKvMap(columnMap), clazz); + } + + /** + * 指定多行、列族,以实体 {@link T} 列表形式返回数据 + * + * @param tableName 表名 + * @param rows 指定多行 + * @param family 列族 + * @param clazz 返回实体类型 + * @param 实体类型 + * @return / + */ + public List getEntityList(String tableName, String[] rows, String family, Class clazz) + throws IOException { + Map map = getEntityMap(tableName, rows, family, clazz); + if (MapUtil.isEmpty(map)) { + return new ArrayList<>(0); + } + return new ArrayList<>(map.values()); + } + + /** + * 指定多行、列族,以实体 {@link T} 列表形式返回数据 + * + * @param tableName 表名 + * @param rows 指定多行 + * @param family 列族 + * @param clazz 返回实体类型 + * @param 实体类型 + * @return / + */ + public List getEntityList(String tableName, Collection rows, String family, Class clazz) + throws IOException { + if (CollectionUtil.isEmpty(rows)) { + return new ArrayList<>(0); + } + return getEntityList(tableName, rows.toArray(new String[0]), family, clazz); + } + + public Map getEntityMap(String tableName, String[] rows, String family, Class clazz) + throws IOException { + + if (StrUtil.isBlank(tableName) || ArrayUtil.isEmpty(rows) || StrUtil.isBlank(family) || clazz == null) { + return null; + } + + Map fieldMap = ReflectUtil.getFieldMap(clazz); + String[] columns = fieldMap.keySet().toArray(new String[0]); + List gets = newGetList(rows, family, columns); + + Result[] results = batchGet(tableName, gets); + if (ArrayUtil.isEmpty(results)) { + return new LinkedHashMap<>(0); + } + + Map map = new LinkedHashMap<>(results.length); + for (Result result : results) { + Map columnMap = + getColumnsFromResult(result, tableName, family, CollectionUtil.newArrayList(columns)); + if (MapUtil.isNotEmpty(columnMap)) { + T entity = toEntity(ColumnDo.toKvMap(columnMap), clazz); + map.put(Bytes.toString(result.getRow()), entity); + } + } + return map; + } + + public Map getEntityMap(String tableName, Collection rows, String family, Class clazz) + throws IOException { + if (CollectionUtil.isEmpty(rows)) { + return new LinkedHashMap<>(0); + } + return getEntityMap(tableName, rows.toArray(new String[0]), family, clazz); + } + + /** + * 查询列信息 + * + * @param tableName 表名 + * @param row 指定行 + * @param family 列族 + * @param column 列 + * @return / + */ + public ColumnDo getColumn(String tableName, String row, String family, String column) throws IOException { + + if (StrUtil.isBlank(tableName) || StrUtil.isBlank(row) || StrUtil.isBlank(family) || StrUtil.isBlank(column)) { + return null; + } + + Result result = get(tableName, row); + if (result == null) { + return null; + } + + return getColumnFromResult(result, tableName, family, column); + } + + /** + * 查询多列信息 + * + * @param tableName 表名 + * @param row 指定行 + * @param family 列族 + * @param columns 指定列 + * @return / + */ + public Map getColumnMap(String tableName, String row, String family, String... columns) + throws IOException { + + if (StrUtil.isBlank(tableName) || StrUtil.isBlank(row) || StrUtil.isBlank(family)) { + return null; + } + + Get get = newGet(row, family, columns); + Result result = get(tableName, get); + if (result == null) { + return null; + } + return getColumnsFromResult(result, tableName, family, Arrays.asList(columns)); + } + + /** + * 查询列族信息 + * + * @param tableName 表名 + * @param row 指定行 + * @param family 指定列族 + * @return / + */ + public FamilyDo getFamily(String tableName, String row, String family) throws IOException { + Map columnMap = getColumnMap(tableName, row, family); + if (MapUtil.isEmpty(columnMap)) { + return null; + } + return new FamilyDo(tableName, row, family, columnMap); + } + + /** + * 查询多列族信息 + * + * @param tableName 表名 + * @param row 指定行 + * @param familyColumnMap <列族, 要查询的列> + * @return / + */ + public Map getFamilyMap(String tableName, String row, + Map> familyColumnMap) throws IOException { + + if (StrUtil.isBlank(tableName) || StrUtil.isBlank(row)) { + return new LinkedHashMap<>(0); + } + + if (MapUtil.isEmpty(familyColumnMap)) { + RowDo rowDo = getRow(tableName, row); + if (rowDo == null) { + return new LinkedHashMap<>(0); + } + return rowDo.getFamilyMap(); + } + + Get get = newGet(row); + for (Map.Entry> entry : familyColumnMap.entrySet()) { + String family = entry.getKey(); + Collection columns = entry.getValue(); + if (CollectionUtil.isNotEmpty(columns)) { + for (String column : columns) { + get.addColumn(Bytes.toBytes(family), Bytes.toBytes(column)); + } + } + } + Result result = get(tableName, get); + if (result == null) { + return null; + } + + return getFamiliesFromResult(result, tableName, familyColumnMap); + } + + /** + * 查询行信息 + * + * @param tableName 表名 + * @param row 指定行 + * @return / + */ + public RowDo getRow(String tableName, String row) throws IOException { + if (StrUtil.isBlank(tableName) || StrUtil.isBlank(row)) { + return null; + } + Result result = get(tableName, row); + if (result == null) { + return null; + } + return getRowFromResult(result, tableName); + } + + /** + * 查询多行信息 + * + * @param tableName 表名 + * @param rows 指定多行 + * @return / + */ + public Map getRowMap(String tableName, String... rows) throws IOException { + if (StrUtil.isBlank(tableName) || ArrayUtil.isEmpty(rows)) { + return null; + } + Result[] results = batchGet(tableName, rows); + if (ArrayUtil.isEmpty(results)) { + return new LinkedHashMap<>(0); + } + Map map = new LinkedHashMap<>(results.length); + for (Result result : results) { + String row = Bytes.toString(result.getRow()); + RowDo rowDo = getRowFromResult(result, tableName); + map.put(row, rowDo); + } + return map; + } + + private static Get newGet(String row) { + return new Get(Bytes.toBytes(row)); + } + + private static Get newGet(String row, String family, String... columns) { + Get get = newGet(row); + get.addFamily(Bytes.toBytes(family)); + if (ArrayUtil.isNotEmpty(columns)) { + for (String column : columns) { + get.addColumn(Bytes.toBytes(family), Bytes.toBytes(column)); + } + } + return get; + } + + private static List newGetList(String[] rows) { + if (ArrayUtil.isEmpty(rows)) { + return new ArrayList<>(); + } + return Stream.of(rows).map(HbaseTemplate::newGet).collect(Collectors.toList()); + } + + private static List newGetList(String[] rows, String family, String[] columns) { + if (ArrayUtil.isEmpty(rows)) { + return new ArrayList<>(); + } + return Stream.of(rows).map(row -> newGet(row, family, columns)).collect(Collectors.toList()); + } + + // ===================================================================================== + // scan 操作封装 + // ===================================================================================== + + /** + * 返回匹配 {@link org.apache.hadoop.hbase.client.Scan} 的所有列族的数据 + * + * @param tableName 表名 + * @param scan {@link org.apache.hadoop.hbase.client.Scan} 实体 + * @return / + */ + public Result[] scan(String tableName, Scan scan) throws IOException { + Table table = getTable(tableName); + ResultScanner scanner = null; + try { + scanner = table.getScanner(scan); + return ArrayUtil.toArray(scanner, Result.class); + } finally { + IoUtil.close(scanner); + recycle(table); + } + } + + public PageData page(SingleFamilyScan scan) throws IOException { + if (scan == null) { + return null; + } + return getPageData(scan.getTableName(), scan.getPage(), scan.getSize(), scan.toScan(), + scan.getFamilyColumnMap()); + } + + public PageData page(MultiFamilyScan scan) throws IOException { + if (scan == null) { + return null; + } + return getPageData(scan.getTableName(), scan.getPage(), scan.getSize(), scan.toScan(), + scan.getFamilyColumnMap()); + } + + public ScrollData scroll(SingleFamilyScan scan) throws IOException { + if (scan == null) { + return null; + } + return getScrollData(scan.getTableName(), scan.getSize(), scan.toScan(), scan.getFamilyColumnMap()); + } + + public ScrollData scroll(MultiFamilyScan scan) throws IOException { + if (scan == null) { + return null; + } + return getScrollData(scan.getTableName(), scan.getSize(), scan.toScan(), scan.getFamilyColumnMap()); + } + + public PageData getEntityPage(SingleFamilyScan scan, Class clazz) throws IOException { + + Map fieldMap = ReflectUtil.getFieldMap(clazz); + Set columns = fieldMap.keySet(); + scan.setColumns(columns); + + PageData data = page(scan); + if (data == null || CollectionUtil.isEmpty(data.getContent())) { + return new PageData<>(scan.getPage(), scan.getSize(), 0L, new ArrayList<>()); + } + + List list = data.getContent().stream().map(rowDo -> { + Map> familyKvMap = rowDo.getFamilyKvMap(); + Map columnKvMap = familyKvMap.get(scan.getFamily()); + return toEntity(columnKvMap, clazz); + }).collect(Collectors.toList()); + return new PageData<>(scan.getPage(), scan.getSize(), data.getTotal(), list); + } + + public ScrollData getEntityScroll(SingleFamilyScan scan, Class clazz) throws IOException { + + Map fieldMap = ReflectUtil.getFieldMap(clazz); + Set columns = fieldMap.keySet(); + scan.setColumns(columns); + + ScrollData data = scroll(scan); + if (data == null || CollectionUtil.isEmpty(data.getContent())) { + return new ScrollData<>(scan.getStartRow(), scan.getStopRow(), null, 0, new ArrayList<>()); + } + + List list = data.getContent().stream().map(rowDo -> { + Map> familyKvMap = rowDo.getFamilyKvMap(); + Map columnKvMap = familyKvMap.get(scan.getFamily()); + return toEntity(columnKvMap, clazz); + }).collect(Collectors.toList()); + return new ScrollData<>(data.getStartRow(), data.getStopRow(), data.getScrollRow(), 0, list); + } + + public ScrollData getEntityScroll(String tableName, String family, String scrollRow, int size, + Class clazz) throws IOException { + SingleFamilyScan scan = new SingleFamilyScan(); + scan.setFamily(family) + .setScrollRow(scrollRow) + .setTableName(tableName) + .setSize(size) + .setReversed(false); + return getEntityScroll(scan, clazz); + } + + private PageData getPageData(String tableName, Integer page, Integer size, Scan scan, + Map> familyColumnMap) throws IOException { + Table table = getTable(tableName); + Map rowMap = new LinkedHashMap<>(size); + try { + int pageIndex = 1; + byte[] lastRow = null; + long total = 0L; + while (true) { + if (lastRow != null) { + scan.withStartRow(lastRow, false); + } + ResultScanner rs = table.getScanner(scan); + Iterator it = rs.iterator(); + int count = 0; + while (it.hasNext()) { + Result result = it.next(); + if (page == pageIndex) { + RowDo rowDo = getRowFromResult(result, tableName, familyColumnMap); + if (rowDo != null) { + rowMap.put(rowDo.getRow(), rowDo); + } + } + lastRow = result.getRow(); + count++; + } + + pageIndex++; + rs.close(); + total += count; + if (count == 0) { + break; + } + } + return new PageData<>(page, size, total, rowMap.values()); + } finally { + recycle(table); + } + } + + private ScrollData getScrollData(String tableName, int size, Scan scan, + Map> familyColumnMap) throws IOException { + Table table = getTable(tableName); + ResultScanner scanner = null; + Map rowMap = new LinkedHashMap<>(size); + try { + scanner = table.getScanner(scan); + for (Result result : scanner) { + RowDo rowDo = getRowFromResult(result, tableName, familyColumnMap); + if (rowDo != null) { + rowMap.put(rowDo.getRow(), rowDo); + } + } + + String scrollRow = null; + if (MapUtil.isNotEmpty(rowMap)) { + List rows = rowMap.values().stream() + .map(RowDo::getRow) + .collect(Collectors.toList()); + if (scan.isReversed()) { + scrollRow = CollectionUtil.min(rows); + } else { + scrollRow = CollectionUtil.max(rows); + } + } + return new ScrollData<>(Bytes.toString(scan.getStartRow()), Bytes.toString(scan.getStopRow()), + scrollRow, size, rowMap.values()); + } finally { + IoUtil.close(scanner); + recycle(table); + } + } + + // ===================================================================================== + // 其他操作封装 + // ===================================================================================== + + public long incrementColumnValue(String tableName, String row, String family, String column, long amount) + throws IOException { + return incrementColumnValue(tableName, row, family, column, amount, Durability.SYNC_WAL); + } + + public long incrementColumnValue(String tableName, String row, String family, String column, long amount, + Durability durability) throws IOException { + if (StrUtil.isBlank(tableName) || StrUtil.isBlank(row) || StrUtil.isBlank(family) || StrUtil.isBlank(column)) { + return -1L; + } + Table table = getTable(tableName); + try { + return table.incrementColumnValue(Bytes.toBytes(row), Bytes.toBytes(family), Bytes.toBytes(column), amount, + durability); + } finally { + recycle(table); + } + } + + private void batch(String tableName, Collection list) + throws IOException, InterruptedException { + if (StrUtil.isBlank(tableName) || CollectionUtil.isEmpty(list)) { + return; + } + Object[] results = new Object[list.size()]; + Table table = getTable(tableName); + try { + table.batch(new ArrayList<>(list), results); + } finally { + recycle(table); + } + } + + private void recycle(Table table) { + if (null == table) { + return; + } + IoUtil.close(table); + } + + private static RowDo getRowFromResult(Result result, String tableName) { + + if (result == null || result.isEmpty()) { + return null; + } + + String row = Bytes.toString(result.getRow()); + Map> familyColumnMap = new LinkedHashMap<>(result.size()); + for (Cell cell : result.listCells()) { + String family = Bytes.toString(CellUtil.cloneFamily(cell)); + if (!familyColumnMap.containsKey(family)) { + familyColumnMap.put(family, new LinkedHashMap<>(0)); + } + String column = Bytes.toString(CellUtil.cloneQualifier(cell)); + ColumnDo columnDo = getColumnFromResult(result, tableName, family, column); + familyColumnMap.get(family).put(column, columnDo); + } + + Map familyMap = new LinkedHashMap<>(familyColumnMap.size()); + familyColumnMap.forEach((family, columnMap) -> { + FamilyDo familyDo = new FamilyDo(tableName, row, family, columnMap); + familyMap.put(family, familyDo); + }); + if (MapUtil.isEmpty(familyMap)) { + return null; + } + return new RowDo(tableName, row, familyMap); + } + + private static RowDo getRowFromResult(Result result, String tableName, + Map> familyColumnMap) { + if (MapUtil.isEmpty(familyColumnMap)) { + return getRowFromResult(result, tableName); + } + String row = Bytes.toString(result.getRow()); + Map familyMap = getFamiliesFromResult(result, tableName, familyColumnMap); + if (MapUtil.isEmpty(familyMap)) { + return null; + } + return new RowDo(tableName, row, familyMap); + } + + private static FamilyDo getFamilyFromResult(Result result, String tableName, String family) { + + if (result == null || result.isEmpty()) { + return null; + } + + RowDo rowDo = getRowFromResult(result, tableName); + if (rowDo == null || MapUtil.isEmpty(rowDo.getFamilyMap())) { + return null; + } + return rowDo.getFamilyMap().get(family); + } + + private static Map getFamiliesFromResult(Result result, String tableName, + Map> familyColumnMap) { + + if (result == null || StrUtil.isBlank(tableName) || MapUtil.isEmpty(familyColumnMap)) { + return new LinkedHashMap<>(0); + } + + String row = Bytes.toString(result.getRow()); + Map familyMap = new LinkedHashMap<>(familyColumnMap.size()); + familyColumnMap.forEach((family, columns) -> { + FamilyDo familyDo; + if (CollectionUtil.isNotEmpty(columns)) { + Map columnMap = new LinkedHashMap<>(columns.size()); + for (String column : columns) { + ColumnDo columnDo = getColumnFromResult(result, tableName, family, column); + columnMap.put(column, columnDo); + } + familyDo = new FamilyDo(tableName, row, family, columnMap); + } else { + familyDo = getFamilyFromResult(result, tableName, family); + } + familyMap.put(family, familyDo); + }); + return familyMap; + } + + private static ColumnDo getColumnFromResult(Result result, String tableName, String family, String column) { + + if (result == null || StrUtil.isBlank(tableName) || StrUtil.isBlank(family) || StrUtil.isBlank(column)) { + return null; + } + + Cell cell = result.getColumnLatestCell(Bytes.toBytes(family), Bytes.toBytes(column)); + if (cell == null) { + return null; + } + String row = Bytes.toString(result.getRow()); + String value = Bytes.toString(CellUtil.cloneValue(cell)); + long timestamp = cell.getTimestamp(); + return new ColumnDo(tableName, row, family, timestamp, column, value); + } + + private static Map getColumnsFromResult(Result result, String tableName, String family, + Collection columns) { + if (CollectionUtil.isEmpty(columns)) { + RowDo rowDo = getRowFromResult(result, tableName); + if (rowDo == null) { + return new LinkedHashMap<>(0); + } + return rowDo.getFamilyMap().get(family).getColumnMap(); + } + Map columnMap = new LinkedHashMap<>(columns.size()); + for (String column : columns) { + ColumnDo columnDo = getColumnFromResult(result, tableName, family, column); + if (columnDo != null) { + columnMap.put(column, columnDo); + } + } + return columnMap; + } + + private static T toEntity(Map kvMap, Class clazz) { + + if (MapUtil.isEmpty(kvMap)) { + return null; + } + + MapUtil.removeNullValue(kvMap); + T obj; + try { + Map> typeMap = new LinkedHashMap<>(); + Field[] fields = ReflectUtil.getFields(clazz); + for (Field f : fields) { + typeMap.put(f.getName(), f.getType()); + } + obj = clazz.newInstance(); + for (Map.Entry entry : kvMap.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + Class filedType = typeMap.get(key); + if (filedType != null) { + Object fieldObj = JsonUtil.toBean(value, filedType); + ReflectUtil.setFieldValue(obj, key, fieldObj); + } + } + return obj; + } catch (InstantiationException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/annotation/RowKeyRule.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/annotation/RowKeyRule.java new file mode 100644 index 00000000..d19e3dc2 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/annotation/RowKeyRule.java @@ -0,0 +1,42 @@ +package io.github.dunwu.javadb.hbase.annotation; + +import io.github.dunwu.javadb.hbase.constant.RowType; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 表主键标识 + * + * @author Zhang Peng + * @date 2023-11-17 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE }) +public @interface RowKeyRule { + + /** + * 唯一索引的 get 方法 + */ + String uk(); + + /** + * 主键类型 {@link RowType} + */ + RowType type() default RowType.ORIGIN_ID; + + /** + * 原 ID 长度,type 为 {@link RowType#ORIGIN_ID} 或 {@link RowType#BUCKET} 时必填 + */ + int length() default 0; + + /** + * 分桶数,type 为 {@link RowType#BUCKET} 时,才需要且必须指定 + */ + int bucket() default 0; + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/annotation/RowKeyUtil.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/annotation/RowKeyUtil.java new file mode 100644 index 00000000..712249c6 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/annotation/RowKeyUtil.java @@ -0,0 +1,142 @@ +package io.github.dunwu.javadb.hbase.annotation; + +import cn.hutool.core.util.HashUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; +import io.github.dunwu.javadb.hbase.constant.RowType; +import io.github.dunwu.javadb.hbase.entity.BaseHbaseEntity; + +import java.lang.reflect.Method; + +/** + * {@link RowKeyRule} 解析器 + * + * @author Zhang Peng + * @date 2023-11-20 + */ +public class RowKeyUtil { + + /** + * 获取主键 + */ + public static String getRowKey(T entity) throws IllegalArgumentException { + + String row = null; + Class clazz = entity.getClass(); + RowKeyRule rule = getRowKeyRule(entity.getClass()); + Method method = ReflectUtil.getMethodByName(clazz, rule.uk()); + if (method == null) { + String msg = StrUtil.format("{} 实体类定义错误!@RowKeyRule 指定的 uk:{} 方法未找到!", + clazz.getCanonicalName(), rule.uk()); + throw new IllegalArgumentException(msg); + } + switch (rule.type()) { + case ORIGIN_ID: + row = getRowKeyForOriginId(entity, method, rule.length()); + break; + case TIMESTAMP: + row = getRowKeyForTimestamp(); + break; + case UUID: + row = IdUtil.fastSimpleUUID(); + break; + case BUCKET: + row = getRowKeyForBucket(entity, method, rule.length(), rule.bucket()); + default: + break; + } + + if (StrUtil.isBlank(row)) { + throw new IllegalArgumentException(StrUtil.format("实体定义错误!未定义 @RowKeyRule", entity.getClass(), + BaseHbaseEntity.class.getCanonicalName())); + } + return row; + } + + public static RowKeyRule getRowKeyRule(Class clazz) { + + RowKeyRule rule = clazz.getAnnotation(RowKeyRule.class); + + if (rule == null) { + String msg = StrUtil.format("{} 实体类定义错误!未定义 @RowKeyRule", clazz.getCanonicalName()); + throw new IllegalArgumentException(msg); + } + + if (rule.type() == RowType.ORIGIN_ID && rule.length() <= 0) { + String msg = StrUtil.format("{} 实体类定义错误!@RowKeyRule type 为 ORIGIN_ID 时,length 必须大于 0!", + clazz.getCanonicalName()); + throw new IllegalArgumentException(msg); + } + + if (rule.type() == RowType.BUCKET && (rule.length() <= 0 || rule.bucket() <= 0)) { + String msg = StrUtil.format("{} 实体类定义错误!@RowKeyRule type 为 BUCKET 时,length 和 bucket 必须大于 0!", + clazz.getCanonicalName()); + throw new IllegalArgumentException(msg); + } + return rule; + } + + public static String getRowKeyForOriginId(T entity, Method method, int length) + throws IllegalArgumentException { + String originId; + Object value = ReflectUtil.invoke(entity, method); + if (value instanceof String) { + originId = (String) value; + } else { + originId = String.valueOf(value); + } + if (length == 0) { + throw new IllegalArgumentException("length 不能为 0"); + } + return getRowKeyForOriginId(originId, length); + } + + public static String getRowKeyForOriginId(String bizId, int length) { + return StrUtil.padPre(bizId, length, "0"); + } + + public static String getRowKeyForTimestamp() { + String timestamp = String.valueOf(System.currentTimeMillis() / 1000); + return StrUtil.padPre(timestamp, 10, "0"); + } + + public static String getRowKeyForBucket(T entity, Method method, int length, int bucket) + throws IllegalArgumentException { + if (bucket == 0) { + throw new IllegalArgumentException("bucket 不能为 0"); + } + + String originId = getRowKeyForOriginId(entity, method, length); + int bucketLength = getBucketIdLength(bucket); + String bucketId = String.valueOf(HashUtil.fnvHash(originId) % bucket); + return StrUtil.padPre(bucketId, bucketLength, "0") + originId; + } + + public static String getRowKeyForBucket(String contentId, Class clazz) { + RowKeyRule rule = RowKeyUtil.getRowKeyRule(clazz); + return RowKeyUtil.getRowKeyForBucket(contentId, rule.length(), rule.bucket()); + } + + public static String getRowKeyForBucket(String bizId, int length, int bucket) throws IllegalArgumentException { + String originId = getRowKeyForOriginId(bizId, length); + int bucketLength = getBucketIdLength(bucket); + String bucketId = String.valueOf(HashUtil.fnvHash(originId) % bucket); + return StrUtil.padPre(bucketId, bucketLength, "0") + originId; + } + + private static int getBucketIdLength(int bucket) { + bucket = bucket - 1; + if (bucket <= 0) { + return 1; + } + + int length = 0; + while (bucket > 0) { + length++; + bucket = bucket / 10; + } + return length; + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/config/EnableHbase.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/config/EnableHbase.java new file mode 100644 index 00000000..466cc9c1 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/config/EnableHbase.java @@ -0,0 +1,26 @@ +package io.github.dunwu.javadb.hbase.config; + +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 启动 HBase 配置注解 + * + * @author Zhang Peng + * @date 2023-06-30 + */ +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@EnableAspectJAutoProxy( + proxyTargetClass = false +) +@Import({ HbaseConfiguration.class }) +@Documented +public @interface EnableHbase { +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/config/HbaseConfiguration.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/config/HbaseConfiguration.java new file mode 100644 index 00000000..1b5cb0d2 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/config/HbaseConfiguration.java @@ -0,0 +1,29 @@ +package io.github.dunwu.javadb.hbase.config; + +import io.github.dunwu.javadb.hbase.HbaseAdmin; +import io.github.dunwu.javadb.hbase.HbaseFactory; +import io.github.dunwu.javadb.hbase.HbaseTemplate; +import org.springframework.context.annotation.Bean; + +import java.io.IOException; + +/** + * HBase 启动配置 + * + * @author Zhang Peng + * @date 2023-07-04 + */ +@org.springframework.context.annotation.Configuration +public class HbaseConfiguration { + + @Bean("hbaseTemplate") + public HbaseTemplate hbaseTemplate() throws IOException { + return HbaseFactory.newHbaseTemplate(); + } + + @Bean("hbaseAdmin") + public HbaseAdmin hbaseAdmin() throws IOException { + return HbaseFactory.newHbaseAdmin(); + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/constant/RowType.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/constant/RowType.java new file mode 100644 index 00000000..d5a49ab0 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/constant/RowType.java @@ -0,0 +1,43 @@ +package io.github.dunwu.javadb.hbase.constant; + +import lombok.Getter; + +/** + * 生成ID类型枚举类 + * + * @author Zhang Peng + * @date 2023-11-17 + */ +@Getter +public enum RowType { + + /** + * 原 ID + */ + ORIGIN_ID(1), + + /** + * 以 10 位的时间戳(秒级)作为 ID + *

+ * 特点:数据存储保证单调递增,适用于 scan 为主,且数据量不大(100w以内),读频率不高的业务场景。 + */ + TIMESTAMP(2), + + /** + * UUID作为主键,适合数据量较大,且以 get 为主的场景(尽量保证数据存储离散) + */ + UUID(3), + + /** + * ID = bucket(2/3) + timestamp(10) + bizId,适合数据量较大,且需要大量 scan 的场景 + *

+ * 注:如果选择此 ID 类型,必须在 @TableId 中指定分桶数 + */ + BUCKET(4); + + private final int key; + + RowType(int key) { + this.key = key; + } +} \ No newline at end of file diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/BaseHbaseContentEntity.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/BaseHbaseContentEntity.java new file mode 100644 index 00000000..4b32fa86 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/BaseHbaseContentEntity.java @@ -0,0 +1,12 @@ +package io.github.dunwu.javadb.hbase.entity; + +import io.github.dunwu.javadb.hbase.mapper.UkGetter; + +/** + * HBase 基础 Content 实体 + * + * @author Zhang Peng + * @date 2023-11-24 + */ +public interface BaseHbaseContentEntity extends UkGetter, BaseHbaseEntity { +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/BaseHbaseEntity.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/BaseHbaseEntity.java new file mode 100644 index 00000000..5a9cbf86 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/BaseHbaseEntity.java @@ -0,0 +1,22 @@ +package io.github.dunwu.javadb.hbase.entity; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.github.dunwu.javadb.hbase.annotation.RowKeyUtil; + +/** + * HBase 基础实体 + * + * @author Zhang Peng + * @date 2023-11-15 + */ +public interface BaseHbaseEntity { + + /** + * 获取主键 + */ + @JsonIgnore + default String getRowKey() { + return RowKeyUtil.getRowKey(this); + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/common/ColumnDo.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/common/ColumnDo.java new file mode 100644 index 00000000..95167743 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/common/ColumnDo.java @@ -0,0 +1,82 @@ +package io.github.dunwu.javadb.hbase.entity.common; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * HBase 列实体 + * + * @author Zhang Peng + * @date 2023-05-19 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ColumnDo { + + /** 表名 */ + private String tableName; + /** 行 */ + private String row; + /** 列族 */ + private String family; + /** 时间戳 */ + private Long timestamp; + /** 列 */ + private String column; + /** 列值 */ + private String value; + + public boolean check() { + return check(this); + } + + public static boolean check(ColumnDo columnDo) { + return columnDo != null + && StrUtil.isNotBlank(columnDo.getTableName()) + && StrUtil.isNotBlank(columnDo.getRow()) + && StrUtil.isNotBlank(columnDo.getFamily()) + && StrUtil.isNotEmpty(columnDo.getColumn()); + } + + public static Map toColumnMap(String tableName, String row, String family, + Map columnValueMap) { + if (MapUtil.isEmpty(columnValueMap)) { + return new HashMap<>(0); + } + Map map = new HashMap<>(columnValueMap.size()); + columnValueMap.forEach((column, value) -> { + ColumnDo columnDo = new ColumnDo(tableName, row, family, null, column, value); + if (columnDo.check()) { + map.put(column, columnDo); + } + }); + return map; + } + + public static Map toKvMap(Map columnMap) { + if (MapUtil.isEmpty(columnMap)) { + return new HashMap<>(0); + } + Collection columns = columnMap.values().stream() + .filter(Objects::nonNull) + .collect(Collectors.toList()); + Map map = new HashMap<>(columns.size()); + for (ColumnDo columnDo : columns) { + if (columnDo.check()) { + map.put(columnDo.getColumn(), columnDo.getValue()); + } + } + return map; + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/common/FamilyDo.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/common/FamilyDo.java new file mode 100644 index 00000000..8d59bd8e --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/common/FamilyDo.java @@ -0,0 +1,73 @@ +package io.github.dunwu.javadb.hbase.entity.common; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.HashMap; +import java.util.Map; + +/** + * HBase 列族实体 + * + * @author Zhang Peng + * @date 2023-05-19 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class FamilyDo { + + /** 表名 */ + private String tableName; + /** 行 */ + private String row; + /** 列族 */ + private String family; + /** 列 Map(key 为 column;value 为列详细信息) */ + private Map columnMap; + + public boolean check() { + return check(this); + } + + public Map getColumnKvMap() { + return FamilyDo.getColumnKvMap(this); + } + + public static Map getColumnKvMap(FamilyDo familyDo) { + if (familyDo == null || MapUtil.isEmpty(familyDo.getColumnMap())) { + return new HashMap<>(0); + } + return ColumnDo.toKvMap(familyDo.getColumnMap()); + } + + public static boolean check(FamilyDo familyDo) { + return familyDo != null + && StrUtil.isNotBlank(familyDo.getTableName()) + && StrUtil.isNotBlank(familyDo.getRow()) + && StrUtil.isNotBlank(familyDo.getFamily()) + && MapUtil.isNotEmpty(familyDo.getColumnMap()); + } + + public static Map toFamilyMap(String tableName, String row, + Map> familyColumnValueMap) { + if (MapUtil.isEmpty(familyColumnValueMap)) { + return new HashMap<>(0); + } + + Map familyMap = new HashMap<>(familyColumnValueMap.size()); + familyColumnValueMap.forEach((family, columnMap) -> { + familyMap.put(family, toFamily(tableName, row, family, columnMap)); + }); + return familyMap; + } + + public static FamilyDo toFamily(String tableName, String row, String family, Map columnValueMap) { + Map columnMap = ColumnDo.toColumnMap(tableName, row, family, columnValueMap); + return new FamilyDo(tableName, row, family, columnMap); + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/common/PageData.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/common/PageData.java new file mode 100644 index 00000000..f044aab5 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/common/PageData.java @@ -0,0 +1,35 @@ +package io.github.dunwu.javadb.hbase.entity.common; + +import lombok.Data; + +import java.util.Collection; + +/** + * HBase 分页数据实体 + * + * @author Zhang Peng + * @date 2023-05-19 + */ +@Data +public class PageData { + + private Integer page; + private Integer size; + private Long total; + private Integer totalPages; + private Collection content; + + public PageData() { } + + public PageData(Integer page, Integer size, Long total, Collection content) { + this.page = page; + this.size = size; + this.total = total; + this.content = content; + } + + public int getTotalPages() { + return this.getSize() == 0 ? 0 : (int) Math.ceil((double) this.total / (double) this.getSize()); + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/common/RowDo.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/common/RowDo.java new file mode 100644 index 00000000..f9625d3f --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/common/RowDo.java @@ -0,0 +1,87 @@ +package io.github.dunwu.javadb.hbase.entity.common; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * HBase 行实体 + * + * @author Zhang Peng + * @date 2023-05-19 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class RowDo { + + /** 表名 */ + private String tableName; + /** 行 */ + private String row; + /** 列族 Map(key 为 family;value 为列族详细信息) */ + private Map familyMap; + + public boolean check() { + return check(this); + } + + public Map> getFamilyKvMap() { + return RowDo.getFamilyKvMap(this); + } + + public static boolean check(RowDo rowDo) { + return rowDo != null + && StrUtil.isNotBlank(rowDo.getTableName()) + && StrUtil.isNotBlank(rowDo.getRow()) + && MapUtil.isNotEmpty(rowDo.getFamilyMap()); + } + + public static Map> getFamilyKvMap(RowDo rowDo) { + if (rowDo == null || MapUtil.isEmpty(rowDo.getFamilyMap())) { + return new HashMap<>(0); + } + Map> kvMap = new HashMap<>(rowDo.getFamilyMap().size()); + rowDo.getFamilyMap().forEach((family, familyDo) -> { + kvMap.put(family, familyDo.getColumnKvMap()); + }); + return kvMap; + } + + public static Map toRowMap(String tableName, Map>> map) { + if (MapUtil.isEmpty(map)) { + return new HashMap<>(0); + } + + Map rowMap = new HashMap<>(map.size()); + map.forEach((row, familyMap) -> { + RowDo rowDo = new RowDo(tableName, row, FamilyDo.toFamilyMap(tableName, row, familyMap)); + rowMap.put(row, rowDo); + }); + return rowMap; + } + + public static List toRowList(String tableName, Map>> map) { + Map rowMap = toRowMap(tableName, map); + if (MapUtil.isEmpty(rowMap)) { + return new ArrayList<>(); + } + return new ArrayList<>(rowMap.values()); + } + + public static RowDo toRow(String tableName, String row, Map> familyColumnMap) { + if (MapUtil.isEmpty(familyColumnMap)) { + return null; + } + Map familyMap = FamilyDo.toFamilyMap(tableName, row, familyColumnMap); + return new RowDo(tableName, row, familyMap); + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/common/ScrollData.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/common/ScrollData.java new file mode 100644 index 00000000..96cd4412 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/common/ScrollData.java @@ -0,0 +1,26 @@ +package io.github.dunwu.javadb.hbase.entity.common; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Collection; + +/** + * Hbase 滚动数据实体 + * + * @author Zhang Peng + * @date 2023-11-16 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ScrollData { + + private String startRow; + private String stopRow; + private String scrollRow; + private Integer size; + private Collection content; + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/scan/BaseScan.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/scan/BaseScan.java new file mode 100644 index 00000000..667b99ce --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/scan/BaseScan.java @@ -0,0 +1,85 @@ +package io.github.dunwu.javadb.hbase.entity.scan; + +import cn.hutool.core.util.StrUtil; +import lombok.Data; +import lombok.experimental.Accessors; +import org.apache.hadoop.hbase.client.Scan; +import org.apache.hadoop.hbase.filter.Filter; +import org.apache.hadoop.hbase.filter.FilterList; +import org.apache.hadoop.hbase.filter.PageFilter; +import org.apache.hadoop.hbase.util.Bytes; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * HBase 基本 scan 封装请求参数 + * + * @author Zhang Peng + * @date 2023-05-19 + */ +@Data +@Accessors(chain = true) +public class BaseScan { + + /** 表名 */ + protected String tableName; + /** 起始 row */ + protected String startRow; + /** 结束 row */ + protected String stopRow; + /** 起始时间 */ + protected Long minTimeStamp; + /** 结束时间 */ + protected Long maxTimeStamp; + /** 是否降序(true: 降序;false:正序) */ + protected boolean reversed = false; + /** 页号 */ + protected Integer page; + /** 每页记录数大小 */ + protected Integer size = 100; + /** 过滤器列表 */ + protected List filters = new ArrayList<>(); + + public void addFilter(Filter filter) { + this.filters.add(filter); + } + + public Scan toScan() throws IOException { + Scan scan = new Scan(); + + // 缓存1000条数据 + scan.setCaching(1000); + scan.setCacheBlocks(false); + scan.setReversed(reversed); + if (StrUtil.isNotBlank(startRow)) { + if (reversed) { + scan.withStopRow(Bytes.toBytes(startRow), true); + } else { + scan.withStartRow(Bytes.toBytes(startRow), true); + } + } + if (StrUtil.isNotBlank(stopRow)) { + if (reversed) { + scan.withStartRow(Bytes.toBytes(stopRow), true); + } else { + scan.withStopRow(Bytes.toBytes(stopRow), true); + } + } + if (minTimeStamp != null && maxTimeStamp != null) { + scan.setTimeRange(minTimeStamp, maxTimeStamp); + } + if (size != null) { + PageFilter pageFilter = new PageFilter(size); + filters.add(pageFilter); + } + FilterList filterList = new FilterList(); + for (Filter filter : filters) { + filterList.addFilter(filter); + } + scan.setFilter(filterList); + return scan; + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/scan/MultiFamilyScan.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/scan/MultiFamilyScan.java new file mode 100644 index 00000000..69c58990 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/scan/MultiFamilyScan.java @@ -0,0 +1,67 @@ +package io.github.dunwu.javadb.hbase.entity.scan; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import org.apache.hadoop.hbase.client.Scan; +import org.apache.hadoop.hbase.util.Bytes; + +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * HBase 多列族 scan 封装请求参数 + * + * @author Zhang Peng + * @date 2023-05-19 + */ +public class MultiFamilyScan extends BaseScan { + + /** + * 列族, 列族所包含的列(不可为空) + */ + private Map> familyColumnMap = new HashMap<>(); + private String scrollRow; + + public Map> getFamilyColumnMap() { + return familyColumnMap; + } + + public MultiFamilyScan setFamilyColumnMap( + Map> familyColumnMap) { + this.familyColumnMap = familyColumnMap; + return this; + } + + public String getScrollRow() { + return scrollRow; + } + + public MultiFamilyScan setScrollRow(String scrollRow) { + this.scrollRow = scrollRow; + return this; + } + + @Override + public Scan toScan() throws IOException { + Scan scan = super.toScan(); + if (StrUtil.isNotBlank(scrollRow)) { + scan.withStartRow(Bytes.toBytes(scrollRow), false); + } + if (MapUtil.isNotEmpty(familyColumnMap)) { + for (Map.Entry> entry : familyColumnMap.entrySet()) { + String family = entry.getKey(); + Collection columns = entry.getValue(); + if (CollectionUtil.isNotEmpty(columns)) { + for (String column : columns) { + scan.addColumn(Bytes.toBytes(family), Bytes.toBytes(column)); + } + } + } + } + return scan; + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/scan/SingleFamilyScan.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/scan/SingleFamilyScan.java new file mode 100644 index 00000000..614b689a --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/scan/SingleFamilyScan.java @@ -0,0 +1,59 @@ +package io.github.dunwu.javadb.hbase.entity.scan; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import lombok.Data; +import lombok.experimental.Accessors; +import org.apache.hadoop.hbase.client.Scan; +import org.apache.hadoop.hbase.util.Bytes; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * HBase 单列族 scan 封装请求参数 + * + * @author Zhang Peng + * @date 2023-05-19 + */ +@Data +@Accessors(chain = true) +public class SingleFamilyScan extends BaseScan { + + private String family; + private Collection columns = new ArrayList<>(); + private String scrollRow; + + @Override + public Scan toScan() throws IOException { + Scan scan = super.toScan(); + if (StrUtil.isNotBlank(scrollRow)) { + scan.withStartRow(Bytes.toBytes(scrollRow), false); + } + if (CollectionUtil.isNotEmpty(this.getColumns())) { + for (String column : columns) { + scan.addColumn(Bytes.toBytes(family), Bytes.toBytes(column)); + } + } + return scan; + } + + public Map> getFamilyColumnMap() { + + if (StrUtil.isBlank(family)) { + return new HashMap<>(0); + } + + Map> familyColumnMap = new HashMap<>(1); + if (CollectionUtil.isNotEmpty(columns)) { + familyColumnMap.put(family, columns); + } else { + familyColumnMap.put(family, new ArrayList<>()); + } + return familyColumnMap; + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/BaseHbaseMapper.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/BaseHbaseMapper.java new file mode 100644 index 00000000..8f5bbbcc --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/BaseHbaseMapper.java @@ -0,0 +1,194 @@ +package io.github.dunwu.javadb.hbase.mapper; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import io.github.dunwu.javadb.hbase.HbaseTemplate; +import io.github.dunwu.javadb.hbase.entity.BaseHbaseEntity; +import io.github.dunwu.javadb.hbase.entity.common.ScrollData; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.hadoop.hbase.client.Connection; + +import java.io.IOException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * HBase Mapper 基础类 + * + * @author Zhang Peng + * @date 2023-11-15 + */ +@Slf4j +@RequiredArgsConstructor +public abstract class BaseHbaseMapper implements HbaseMapper { + + protected final HbaseTemplate hbaseTemplate; + + @Override + public Connection getClient() { + return hbaseTemplate.getConnection(); + } + + @Override + public String getNamespace() { + return "default"; + } + + @Override + public String getFamily() { + return "f"; + } + + @Override + public int deleteById(Serializable id) { + + String rowKey = getIdStr(id); + if (StrUtil.isBlank(rowKey)) { + return 0; + } + + try { + hbaseTemplate.delete(getFullTableName(), rowKey); + return 1; + } catch (IOException e) { + log.error("【Hbase】deleteById 异常", e); + return 0; + } + } + + @Override + public int deleteById(T entity) { + if (entity == null) { + return 0; + } + return deleteById(entity.getRowKey()); + } + + @Override + public int deleteBatchById(Collection ids) { + + if (CollectionUtil.isEmpty(ids)) { + return 0; + } + + List rowKeys = getIdStrList(ids); + try { + hbaseTemplate.batchDelete(getFullTableName(), rowKeys.toArray(new String[0])); + return rowKeys.size(); + } catch (IOException | InterruptedException e) { + log.error("【Hbase】deleteBatchIds 异常", e); + return 0; + } + } + + @Override + public int save(T entity) { + try { + String rowKey = entity.getRowKey(); + hbaseTemplate.put(getFullTableName(), rowKey, getFamily(), entity); + return 1; + } catch (IOException e) { + log.error("【Hbase】updateById 异常", e); + return 0; + } + } + + @Override + public int batchSave(Collection list) { + + if (CollectionUtil.isEmpty(list)) { + return 0; + } + + try { + hbaseTemplate.batchPut(getFullTableName(), getFamily(), list); + return list.size(); + } catch (IOException | InterruptedException e) { + log.error("【Hbase】batchSave 异常", e); + return 0; + } + } + + @Override + public T getOneById(Serializable id) { + + String rowKey = getIdStr(id); + if (StrUtil.isBlank(rowKey)) { + return null; + } + + try { + return hbaseTemplate.getEntity(getFullTableName(), rowKey, getFamily(), getEntityClass()); + } catch (IOException e) { + log.error("【Hbase】getOneById 异常", e); + return null; + } + } + + @Override + public Map getMapByIds(Collection ids) { + + if (CollectionUtil.isEmpty(ids)) { + return new LinkedHashMap<>(0); + } + + List rowKeys = getIdStrList(ids); + try { + return hbaseTemplate.getEntityMap(getFullTableName(), rowKeys.toArray(new String[0]), getFamily(), + getEntityClass()); + } catch (IOException e) { + log.error("【Hbase】getMapByIds 异常", e); + return new LinkedHashMap<>(0); + } + } + + @Override + public List scroll(Serializable scrollId, int size) { + String scrollRowKey = getIdStr(scrollId); + try { + ScrollData scrollData = + hbaseTemplate.getEntityScroll(getFullTableName(), getFamily(), scrollRowKey, size, getEntityClass()); + if (scrollData == null || CollectionUtil.isEmpty(scrollData.getContent())) { + return new ArrayList<>(); + } + return new ArrayList<>(scrollData.getContent()); + } catch (IOException e) { + log.error("【Hbase】getEntityScroll 异常", e); + return new ArrayList<>(); + } + } + + protected String getFullTableName() { + return StrUtil.format("{}:{}", getNamespace(), getTableName()); + } + + protected String getIdStr(Serializable id) { + + if (id == null) { + return null; + } + + String rowKey; + if (id instanceof String) { + rowKey = (String) id; + } else { + rowKey = String.valueOf(id); + } + return rowKey; + } + + protected List getIdStrList(Collection ids) { + if (CollectionUtil.isEmpty(ids)) { + return new ArrayList<>(0); + } + return ids.stream().map(this::getIdStr).filter(Objects::nonNull).collect(Collectors.toList()); + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/BaseHbaseUkMapper.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/BaseHbaseUkMapper.java new file mode 100644 index 00000000..bc9dd800 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/BaseHbaseUkMapper.java @@ -0,0 +1,21 @@ +package io.github.dunwu.javadb.hbase.mapper; + +import io.github.dunwu.javadb.hbase.HbaseTemplate; +import io.github.dunwu.javadb.hbase.entity.BaseHbaseContentEntity; +import lombok.extern.slf4j.Slf4j; + +/** + * HBase Mapper 基础类 + * + * @author Zhang Peng + * @date 2023-11-15 + */ +@Slf4j +public abstract class BaseHbaseUkMapper extends BaseHbaseMapper + implements HbaseUkMapper { + + public BaseHbaseUkMapper(HbaseTemplate hbaseTemplate) { + super(hbaseTemplate); + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/CommonMapper.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/CommonMapper.java new file mode 100644 index 00000000..cc31c2dd --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/CommonMapper.java @@ -0,0 +1,93 @@ +package io.github.dunwu.javadb.hbase.mapper; + +import cn.hutool.core.collection.CollectionUtil; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * 通用 Mapper + * + * @author Zhang Peng + * @date 2023-11-22 + */ +public interface CommonMapper { + + /** + * 插入一条记录 + * + * @param entity 实体对象 + */ + default int insert(T entity) { + return insertBatch(Collections.singleton(entity)); + } + + /** + * 批量插入记录 + * + * @param list 实体对象列表 + */ + int insertBatch(Collection list); + + /** + * 根据 ID 删除 + * + * @param id 主键ID + */ + default int deleteById(Serializable id) { + return deleteBatchById(Collections.singleton(id)); + } + + /** + * 根据实体(ID)删除 + * + * @param entity 实体对象 + */ + int deleteById(T entity); + + /** + * 删除(根据ID或实体 批量删除) + * + * @param idList 主键ID列表或实体列表(不能为 null 以及 empty) + */ + int deleteBatchById(Collection idList); + + /** + * 根据 ID 更新 + * + * @param entity 实体对象 + */ + default int updateById(T entity) { + return updateBatchById(Collections.singleton(entity)); + } + + /** + * 批量更新记录 + * + * @param list 实体对象列表 + */ + int updateBatchById(Collection list); + + /** + * 根据 ID 查询 + * + * @param id 主键ID + */ + default T getOneById(Serializable id) { + List list = getListByIds(Collections.singleton(id)); + if (CollectionUtil.isEmpty(list)) { + return null; + } + return list.get(0); + } + + /** + * 查询(根据ID 批量查询) + * + * @param idList 主键ID列表(不能为 null 以及 empty) + */ + List getListByIds(Collection idList); + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/CommonUkMapper.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/CommonUkMapper.java new file mode 100644 index 00000000..a185e122 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/CommonUkMapper.java @@ -0,0 +1,77 @@ +package io.github.dunwu.javadb.hbase.mapper; + +import cn.hutool.core.collection.CollectionUtil; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * 基于唯一索引的通用 Mapper + * + * @author Zhang Peng + * @date 2023-11-23 + */ +public interface CommonUkMapper extends CommonMapper { + + /** + * 根据唯一索引删除 + * + * @param uk 唯一索引 + */ + default int deleteByUk(Serializable uk) { + return deleteBatchByUk(Collections.singleton(uk)); + } + + /** + * 根据唯一索引删除 + * + * @param entity 实体对象 + */ + int deleteByUk(T entity); + + /** + * 根据唯一索引批量删除 + * + * @param ukList 唯一索引列表 + */ + int deleteBatchByUk(Collection ukList); + + /** + * 根据唯一索引更新 + * + * @param entity 实体对象 + */ + default int updateByUk(T entity) { + return updateBatchByUk(Collections.singleton(entity)); + } + + /** + * 根据唯一索引批量更新 + * + * @param list 实体对象 + */ + int updateBatchByUk(Collection list); + + /** + * 根据唯一索引查询 + * + * @param uk 唯一索引 + */ + default T getOneByUk(Serializable uk) { + List list = getListByUk(Collections.singleton(uk)); + if (CollectionUtil.isEmpty(list)) { + return null; + } + return list.get(0); + } + + /** + * 根据唯一索引批量查询 + * + * @param ukList 唯一索引列表 + */ + List getListByUk(Collection ukList); + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/HbaseMapper.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/HbaseMapper.java new file mode 100644 index 00000000..c16cc1f1 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/HbaseMapper.java @@ -0,0 +1,106 @@ +package io.github.dunwu.javadb.hbase.mapper; + +import cn.hutool.core.map.MapUtil; +import io.github.dunwu.javadb.hbase.entity.BaseHbaseEntity; +import org.apache.hadoop.hbase.client.Connection; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * Hbase Mapper + * + * @author Zhang Peng + * @date 2023-11-15 + */ +public interface HbaseMapper extends CommonMapper { + + /** + * 获取 Hbase 官方客户端实体 + */ + Connection getClient(); + + /** + * 获取命名空间 + */ + String getNamespace(); + + /** + * 获取表名 + */ + String getTableName(); + + /** + * 获取列族 + */ + String getFamily(); + + /** + * 获取实体类型 + */ + Class getEntityClass(); + + @Override + default int insert(T entity) { + return save(entity); + } + + @Override + default int updateById(T entity) { + return save(entity); + } + + @Override + default int insertBatch(Collection list) { + return batchSave(list); + } + + @Override + default int updateBatchById(Collection list) { + return batchSave(list); + } + + /** + * 保存一条记录 + * + * @param entity 实体对象 + */ + int save(T entity); + + /** + * 批量保存记录 + * + * @param list 实体对象列表 + */ + int batchSave(Collection list); + + @Override + default List getListByIds(Collection ids) { + Map map = getMapByIds(ids); + if (MapUtil.isEmpty(map)) { + return new ArrayList<>(); + } + return new ArrayList<>(map.values()); + } + + /** + * 根据 ID 列表批量查数据,以 Map 形式返回 + * + * @param ids 即 Hbase rowkey + * @return / + */ + Map getMapByIds(Collection ids); + + /** + * 根据 ID 滚动分页查询 + * + * @param scrollId 为空值时,默认查第一页 + * @param size 每页记录数 + * @return / + */ + List scroll(Serializable scrollId, int size); + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/HbaseUkMapper.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/HbaseUkMapper.java new file mode 100644 index 00000000..7b4f8cae --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/HbaseUkMapper.java @@ -0,0 +1,74 @@ +package io.github.dunwu.javadb.hbase.mapper; + +import cn.hutool.core.collection.CollectionUtil; +import io.github.dunwu.javadb.hbase.annotation.RowKeyUtil; +import io.github.dunwu.javadb.hbase.entity.BaseHbaseContentEntity; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Hbase Content Mapper + * + * @author Zhang Peng + * @date 2023-11-15 + */ +public interface HbaseUkMapper extends HbaseMapper, CommonUkMapper { + + @Override + default int deleteByUk(Serializable uk) { + String rowKey = RowKeyUtil.getRowKeyForBucket((String) uk, getEntityClass()); + return deleteById(rowKey); + } + + @Override + default int deleteByUk(T entity) { + String rowKey = RowKeyUtil.getRowKeyForBucket(entity.getUk(), getEntityClass()); + return deleteById(rowKey); + } + + @Override + default int deleteBatchByUk(Collection ukList) { + if (CollectionUtil.isEmpty(ukList)) { + return 0; + } + List rowKeys = ukList.stream() + .map(contentId -> RowKeyUtil.getRowKeyForBucket((String) contentId, + getEntityClass())) + .collect(Collectors.toList()); + return deleteBatchById(rowKeys); + } + + @Override + default int updateByUk(T entity) { + return save(entity); + } + + @Override + default int updateBatchByUk(Collection list) { + return batchSave(list); + } + + @Override + default T getOneByUk(Serializable uk) { + String rowKey = RowKeyUtil.getRowKeyForBucket((String) uk, getEntityClass()); + return getOneById(rowKey); + } + + @Override + default List getListByUk(Collection ukList) { + if (CollectionUtil.isEmpty(ukList)) { + return new ArrayList<>(); + } + + List rowKeys = ukList.stream() + .map(contentId -> RowKeyUtil.getRowKeyForBucket((String) contentId, + getEntityClass())) + .collect(Collectors.toList()); + return getListByIds(rowKeys); + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/UkGetter.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/UkGetter.java new file mode 100644 index 00000000..2e58c863 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/UkGetter.java @@ -0,0 +1,16 @@ +package io.github.dunwu.javadb.hbase.mapper; + +/** + * 获取唯一索引 Get 方法接口 + * + * @author Zhang Peng + * @date 2023-11-24 + */ +public interface UkGetter { + + /** + * 获取唯一索引 + */ + String getUk(); + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/util/JsonUtil.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/util/JsonUtil.java new file mode 100644 index 00000000..17d57d03 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/util/JsonUtil.java @@ -0,0 +1,144 @@ +package io.github.dunwu.javadb.hbase.util; + +import cn.hutool.core.util.StrUtil; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.type.TypeFactory; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Slf4j +public class JsonUtil { + + private JsonUtil() { } + + private static final ObjectMapper OBJECT_MAPPER; + private static final TypeFactory TYPE_FACTORY; + + static { + OBJECT_MAPPER = new ObjectMapper(); + OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); + OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + TYPE_FACTORY = OBJECT_MAPPER.getTypeFactory(); + } + + public static ObjectMapper getInstance() { + return OBJECT_MAPPER; + } + + /** + * 简单对象转换 + */ + @SuppressWarnings("unchecked") + public static T toBean(String json, Class clazz) { + if (StrUtil.isBlank(json)) { + return null; + } + if (clazz == String.class) { + return (T) json; + } + try { + return OBJECT_MAPPER.readValue(json, clazz); + } catch (IOException e) { + log.error("反序列化失败!json: {}, msg: {}", json, e.getMessage()); + } + return null; + } + + /** + * 复杂对象转换 + */ + public static T toBean(String json, TypeReference typeReference) { + if (StrUtil.isBlank(json)) { + return null; + } + try { + return (T) OBJECT_MAPPER.readValue(json, typeReference); + } catch (Exception e) { + log.error("反序列化失败!json: {}, msg: {}", json, e.getMessage()); + } + return null; + } + + public static T toBean(Map map, Class clazz) { + return OBJECT_MAPPER.convertValue(toString(map), clazz); + } + + public static String toString(Object obj) { + if (obj == null) { + return null; + } + if (obj instanceof String) { + return (String) obj; + } + try { + return OBJECT_MAPPER.writeValueAsString(obj); + } catch (JsonProcessingException e) { + log.error("序列化失败!obj: {}, msg: {}", obj, e.getMessage()); + } + return null; + } + + public static Map toMap(String json) { + if (StrUtil.isBlank(json)) { + return new HashMap<>(0); + } + try { + return OBJECT_MAPPER.readValue(json, new TypeReference>() { }); + } catch (Exception e) { + log.error("反序列化失败!json: {}, msg: {}", json, e.getMessage()); + } + return Collections.emptyMap(); + } + + public static Map toMap(Object obj) { + + if (obj == null) { + return null; + } + + try { + return OBJECT_MAPPER.readValue(toString(obj), new TypeReference>() { }); + } catch (IOException e) { + log.error("反序列化失败!json: {}, msg: {}", toString(obj), e.getMessage()); + } + return null; + } + + public static List toList(String json, Class clazz) { + if (StrUtil.isBlank(json)) { + return null; + } + JavaType javaType = TYPE_FACTORY.constructParametricType(List.class, clazz); + try { + return OBJECT_MAPPER.readValue(json, javaType); + } catch (IOException e) { + log.error("反序列化失败!json: {}, msg: {}", json, e.getMessage()); + } + return null; + } + + public static List toList(String json, TypeReference typeReference) { + if (StrUtil.isBlank(json)) { + return null; + } + JavaType elementType = TYPE_FACTORY.constructType(typeReference); + JavaType javaType = TYPE_FACTORY.constructParametricType(List.class, elementType); + try { + return OBJECT_MAPPER.readValue(json, javaType); + } catch (IOException e) { + log.error("反序列化失败!json: {}, msg: {}", json, e.getMessage()); + } + return null; + } + +} \ No newline at end of file diff --git a/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/HbaseMapperTest.java b/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/HbaseMapperTest.java new file mode 100644 index 00000000..edf8d9bc --- /dev/null +++ b/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/HbaseMapperTest.java @@ -0,0 +1,76 @@ +package io.github.dunwu.javadb.hbase; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.json.JSONUtil; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.Collections; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Zhang Peng + * @date 2023-11-15 + */ +public class HbaseMapperTest { + + private static final OrderMapper mapper; + + static { + HbaseTemplate hbaseTemplate = null; + try { + hbaseTemplate = HbaseFactory.newHbaseTemplate(); + mapper = new OrderMapper(hbaseTemplate); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Test + @DisplayName("批量保存、查询、删除 BaseHbaseEntity 实体") + public void batchSave() { + + Date now = new Date(); + Product product1 = new Product("1", "product1", new BigDecimal(4000.0)); + Product product2 = new Product("2", "product2", new BigDecimal(5000.0)); + List products = CollectionUtil.newArrayList(product1, product2); + User user1 = new User(1, "user1"); + Map tags = new LinkedHashMap<>(); + tags.put("type", "tool"); + tags.put("color", "red"); + + Order originOrder = Order.builder() + .id("1") + .user(user1) + .products(products) + .desc("测试订单") + .date(now) + .tags(tags) + .build(); + mapper.batchSave(Collections.singleton(originOrder)); + + List list = mapper.getListByIds(Collections.singleton(originOrder.getRowKey())); + Assertions.assertThat(list).isNotEmpty(); + Order order = list.get(0); + Assertions.assertThat(order).isNotNull(); + Assertions.assertThat(order.getDate()).isNotNull().isEqualTo(now); + Assertions.assertThat(order.getTags()).isNotNull().isEqualTo(tags); + Assertions.assertThat(order.getUser()).isNotNull().isEqualTo(user1); + Assertions.assertThat(order.getProducts()).isNotEmpty(); + Assertions.assertThat(list).isNotEmpty(); + Assertions.assertThat(list.size()).isEqualTo(1); + System.out.println(JSONUtil.toJsonStr(list)); + + mapper.deleteBatchById(Collections.singletonList(originOrder.getRowKey())); + + List list2 = mapper.getListByIds(Collections.singletonList(originOrder.getRowKey())); + Assertions.assertThat(list2).isEmpty(); + } + +} diff --git a/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/HbaseTemplateGetTest.java b/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/HbaseTemplateGetTest.java new file mode 100644 index 00000000..a0e953ce --- /dev/null +++ b/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/HbaseTemplateGetTest.java @@ -0,0 +1,308 @@ +package io.github.dunwu.javadb.hbase; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import io.github.dunwu.javadb.hbase.entity.BaseHbaseEntity; +import io.github.dunwu.javadb.hbase.entity.common.ColumnDo; +import io.github.dunwu.javadb.hbase.entity.common.FamilyDo; +import io.github.dunwu.javadb.hbase.entity.common.RowDo; +import io.github.dunwu.javadb.hbase.util.JsonUtil; +import org.apache.hadoop.hbase.client.Put; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Hbase Get 测试 + * + * @author Zhang Peng + * @date 2023-11-13 + */ +public class HbaseTemplateGetTest { + + public static final String TABLE_NAME = "test:test"; + + private static final HbaseTemplate HBASE_TEMPLATE; + + static { + try { + HBASE_TEMPLATE = HbaseFactory.newHbaseTemplate(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Test + @DisplayName("put、get 单列数据") + public void test00() throws IOException { + long timestamp = System.currentTimeMillis(); + HBASE_TEMPLATE.put(TABLE_NAME, "test-key-0", "f1", "name", "user0"); + ColumnDo columnDo = HBASE_TEMPLATE.getColumn(TABLE_NAME, "test-key-0", "f1", "name"); + Assertions.assertThat(columnDo).isNotNull(); + Assertions.assertThat(columnDo.getColumn()).isEqualTo("name"); + Assertions.assertThat(columnDo.getValue()).isEqualTo("user0"); + + HBASE_TEMPLATE.put(TABLE_NAME, "test-key-0", timestamp, "f2", "姓名", "张三"); + ColumnDo columnDo2 = HBASE_TEMPLATE.getColumn(TABLE_NAME, "test-key-0", "f2", "姓名"); + Assertions.assertThat(columnDo2).isNotNull(); + Assertions.assertThat(columnDo2.getColumn()).isEqualTo("姓名"); + Assertions.assertThat(columnDo2.getValue()).isEqualTo("张三"); + Assertions.assertThat(columnDo2.getTimestamp()).isEqualTo(timestamp); + + HBASE_TEMPLATE.delete(TABLE_NAME, "test-key-0"); + columnDo = HBASE_TEMPLATE.getColumn(TABLE_NAME, "test-key-0", "f1", "name"); + Assertions.assertThat(columnDo).isNull(); + columnDo2 = HBASE_TEMPLATE.getColumn(TABLE_NAME, "test-key-0", "f2", "姓名"); + Assertions.assertThat(columnDo2).isNull(); + } + + @Test + @DisplayName("put、get 多列数据") + public void test01() throws IOException { + + String row = "test-key-1"; + long timestamp = System.currentTimeMillis(); + Map map1 = new HashMap<>(2); + map1.put("id", 1); + map1.put("name", "zhangsan"); + Map map2 = new HashMap<>(2); + map2.put("编号", 1); + map2.put("姓名", "张三"); + + HBASE_TEMPLATE.put(TABLE_NAME, row, timestamp, "f1", map1); + HBASE_TEMPLATE.put(TABLE_NAME, row, timestamp, "f2", map2); + + Map f1ColumnMap = HBASE_TEMPLATE.getColumnMap(TABLE_NAME, row, "f1", "id", "name"); + Assertions.assertThat(f1ColumnMap).isNotEmpty(); + Assertions.assertThat(f1ColumnMap.get("id")).isNotNull(); + Assertions.assertThat(f1ColumnMap.get("id").getValue()).isEqualTo(String.valueOf(1)); + Assertions.assertThat(f1ColumnMap.get("name")).isNotNull(); + Assertions.assertThat(f1ColumnMap.get("name").getValue()).isEqualTo("zhangsan"); + + Map f2ColumnMap = HBASE_TEMPLATE.getColumnMap(TABLE_NAME, row, "f2", "编号", "姓名"); + Assertions.assertThat(f2ColumnMap).isNotEmpty(); + Assertions.assertThat(f2ColumnMap.get("编号")).isNotNull(); + Assertions.assertThat(f2ColumnMap.get("编号").getValue()).isEqualTo(String.valueOf(1)); + Assertions.assertThat(f2ColumnMap.get("姓名")).isNotNull(); + Assertions.assertThat(f2ColumnMap.get("姓名").getValue()).isEqualTo("张三"); + + HBASE_TEMPLATE.delete(TABLE_NAME, row); + f1ColumnMap = HBASE_TEMPLATE.getColumnMap(TABLE_NAME, row, "f1", "id", "name"); + Assertions.assertThat(f1ColumnMap).isEmpty(); + f2ColumnMap = HBASE_TEMPLATE.getColumnMap(TABLE_NAME, row, "f2", "编号", "姓名"); + Assertions.assertThat(f2ColumnMap).isEmpty(); + } + + @Test + @DisplayName("put、get 列族数据") + public void test02() throws IOException { + + String row = "test-key-2"; + long timestamp = System.currentTimeMillis(); + Map map1 = new HashMap<>(2); + map1.put("id", 1); + map1.put("name", "zhangsan"); + Map map2 = new HashMap<>(2); + map2.put("编号", 1); + map2.put("姓名", "张三"); + + HBASE_TEMPLATE.put(TABLE_NAME, row, timestamp, "f1", map1); + HBASE_TEMPLATE.put(TABLE_NAME, row, timestamp, "f2", map2); + + FamilyDo f1 = HBASE_TEMPLATE.getFamily(TABLE_NAME, row, "f1"); + Assertions.assertThat(f1).isNotNull(); + Assertions.assertThat(f1.getColumnMap().get("id")).isNotNull(); + Assertions.assertThat(f1.getColumnMap().get("id").getValue()).isEqualTo(String.valueOf(1)); + Assertions.assertThat(f1.getColumnMap().get("name")).isNotNull(); + Assertions.assertThat(f1.getColumnMap().get("name").getValue()).isEqualTo("zhangsan"); + + FamilyDo f2 = HBASE_TEMPLATE.getFamily(TABLE_NAME, row, "f2"); + Assertions.assertThat(f2).isNotNull(); + Assertions.assertThat(f2.getColumnMap().get("编号")).isNotNull(); + Assertions.assertThat(f2.getColumnMap().get("编号").getValue()).isEqualTo(String.valueOf(1)); + Assertions.assertThat(f2.getColumnMap().get("姓名")).isNotNull(); + Assertions.assertThat(f2.getColumnMap().get("姓名").getValue()).isEqualTo("张三"); + + HBASE_TEMPLATE.delete(TABLE_NAME, row); + f1 = HBASE_TEMPLATE.getFamily(TABLE_NAME, row, "f1"); + Assertions.assertThat(f1).isNull(); + f2 = HBASE_TEMPLATE.getFamily(TABLE_NAME, row, "f2"); + Assertions.assertThat(f2).isNull(); + } + + @Test + @DisplayName("put、get 单行数据") + public void test03() throws IOException { + + String row = "test-key-3"; + long timestamp = System.currentTimeMillis(); + Map map1 = new HashMap<>(2); + map1.put("id", 1); + map1.put("name", "zhangsan"); + Map map2 = new HashMap<>(2); + map2.put("编号", 1); + map2.put("姓名", "张三"); + Map> familyMap = new HashMap<>(2); + familyMap.put("f1", map1); + familyMap.put("f2", map2); + + HBASE_TEMPLATE.put(TABLE_NAME, row, timestamp, familyMap); + + RowDo rowDo = HBASE_TEMPLATE.getRow(TABLE_NAME, row); + Assertions.assertThat(rowDo).isNotNull(); + + FamilyDo f1 = rowDo.getFamilyMap().get("f1"); + Assertions.assertThat(f1).isNotNull(); + Assertions.assertThat(f1.getColumnMap()).isNotEmpty(); + Assertions.assertThat(f1.getColumnMap().get("id")).isNotNull(); + Assertions.assertThat(f1.getColumnMap().get("id").getValue()).isEqualTo(String.valueOf(1)); + Assertions.assertThat(f1.getColumnMap().get("name")).isNotNull(); + Assertions.assertThat(f1.getColumnMap().get("name").getValue()).isEqualTo("zhangsan"); + + FamilyDo f2 = rowDo.getFamilyMap().get("f2"); + Assertions.assertThat(f2).isNotNull(); + Assertions.assertThat(f2.getColumnMap()).isNotEmpty(); + Assertions.assertThat(f2.getColumnMap().get("编号")).isNotNull(); + Assertions.assertThat(f2.getColumnMap().get("编号").getValue()).isEqualTo(String.valueOf(1)); + Assertions.assertThat(f2.getColumnMap().get("姓名")).isNotNull(); + Assertions.assertThat(f2.getColumnMap().get("姓名").getValue()).isEqualTo("张三"); + + HBASE_TEMPLATE.delete(TABLE_NAME, row); + rowDo = HBASE_TEMPLATE.getRow(TABLE_NAME, row); + Assertions.assertThat(rowDo).isNull(); + } + + @Test + @DisplayName("put get 多行数据") + public void test04() throws IOException, InterruptedException { + + long timestamp = System.currentTimeMillis(); + + Map columnMap1 = new HashMap<>(2); + columnMap1.put("id", 1); + columnMap1.put("name", "zhangsan"); + Put put = HbaseTemplate.newPut("test-key-1", timestamp, "f1", columnMap1); + + Map columnMap2 = new HashMap<>(2); + columnMap2.put("id", 2); + columnMap2.put("name", "lisi"); + Put put2 = HbaseTemplate.newPut("test-key-2", timestamp, "f1", columnMap2); + + List puts = CollectionUtil.newArrayList(put, put2); + + HBASE_TEMPLATE.batchPut(TABLE_NAME, puts); + + Map rowMap = HBASE_TEMPLATE.getRowMap(TABLE_NAME, "test-key-1", "test-key-2"); + + RowDo rowDo1 = rowMap.get("test-key-1"); + Assertions.assertThat(rowDo1).isNotNull(); + FamilyDo f1 = rowDo1.getFamilyMap().get("f1"); + Assertions.assertThat(f1).isNotNull(); + Assertions.assertThat(f1.getColumnMap()).isNotEmpty(); + Assertions.assertThat(f1.getColumnMap().get("id")).isNotNull(); + Assertions.assertThat(f1.getColumnMap().get("id").getValue()).isEqualTo(String.valueOf(1)); + Assertions.assertThat(f1.getColumnMap().get("name")).isNotNull(); + Assertions.assertThat(f1.getColumnMap().get("name").getValue()).isEqualTo("zhangsan"); + + RowDo rowDo2 = rowMap.get("test-key-2"); + FamilyDo f2 = rowDo2.getFamilyMap().get("f1"); + Assertions.assertThat(f2).isNotNull(); + Assertions.assertThat(f2.getColumnMap()).isNotEmpty(); + Assertions.assertThat(f2.getColumnMap().get("id")).isNotNull(); + Assertions.assertThat(f2.getColumnMap().get("id").getValue()).isEqualTo(String.valueOf(2)); + Assertions.assertThat(f2.getColumnMap().get("name")).isNotNull(); + Assertions.assertThat(f2.getColumnMap().get("name").getValue()).isEqualTo("lisi"); + + HBASE_TEMPLATE.batchDelete(TABLE_NAME, "test-key-1", "test-key-2"); + rowDo1 = HBASE_TEMPLATE.getRow(TABLE_NAME, "test-key-1"); + Assertions.assertThat(rowDo1).isNull(); + rowDo2 = HBASE_TEMPLATE.getRow(TABLE_NAME, "test-key-2"); + Assertions.assertThat(rowDo2).isNull(); + } + + @Test + @DisplayName("put get 简单 Java 实体数据") + public void test05() throws IOException, InterruptedException { + + User originUser1 = new User(1, "user1"); + HBASE_TEMPLATE.put(TABLE_NAME, "test-key-1", "f1", originUser1); + User user1 = HBASE_TEMPLATE.getEntity(TABLE_NAME, "test-key-1", "f1", User.class); + Assertions.assertThat(user1).isNotNull(); + Assertions.assertThat(ObjectUtil.equals(originUser1, user1)).isTrue(); + + HBASE_TEMPLATE.batchDelete(TABLE_NAME, "test-key-1", "test-key-2"); + user1 = HBASE_TEMPLATE.getEntity(TABLE_NAME, "test-key-1", "f1", User.class); + Assertions.assertThat(user1).isNull(); + } + + @Test + @DisplayName("put get 实现 BaseHbaseEntity 的简单 Java 实体数据") + public void test06() throws IOException, InterruptedException { + + Product product1 = new Product("1", "product1", new BigDecimal(4000.0)); + Product product2 = new Product("2", "product2", new BigDecimal(5000.0)); + List products = CollectionUtil.newArrayList(product1, product2); + HBASE_TEMPLATE.batchPut(TABLE_NAME, "f1", products); + + List rows = products.stream().map(BaseHbaseEntity::getRowKey).collect(Collectors.toList()); + List list = HBASE_TEMPLATE.getEntityList(TABLE_NAME, rows, "f1", Product.class); + Assertions.assertThat(list).isNotEmpty(); + Assertions.assertThat(list.size()).isEqualTo(rows.size()); + + HBASE_TEMPLATE.batchDelete(TABLE_NAME, rows.toArray(new String[0])); + product1 = HBASE_TEMPLATE.getEntity(TABLE_NAME, "test-key-1", "f1", Product.class); + Assertions.assertThat(product1).isNull(); + product2 = HBASE_TEMPLATE.getEntity(TABLE_NAME, "test-key-2", "f1", Product.class); + Assertions.assertThat(product2).isNull(); + list = HBASE_TEMPLATE.getEntityList(TABLE_NAME, rows, "f1", Product.class); + Assertions.assertThat(list).isEmpty(); + } + + @Test + @DisplayName("put get 实现 BaseHbaseEntity 的复杂 Java 实体数据") + public void test07() throws IOException { + + Date now = new Date(); + Product product1 = new Product("1", "product1", new BigDecimal(4000.0)); + Product product2 = new Product("2", "product2", new BigDecimal(5000.0)); + List products = CollectionUtil.newArrayList(product1, product2); + User user1 = new User(1, "user1"); + Map tags = new LinkedHashMap<>(); + tags.put("type", "tool"); + tags.put("color", "red"); + + Order originOrder = Order.builder() + .id("1") + .user(user1) + .products(products) + .desc("测试订单") + .date(now) + .tags(tags) + .build(); + + HBASE_TEMPLATE.put(TABLE_NAME, "f1", originOrder); + + Order order = HBASE_TEMPLATE.getEntity(TABLE_NAME, originOrder.getRowKey(), "f1", Order.class); + Assertions.assertThat(order).isNotNull(); + Assertions.assertThat(order.getDate()).isNotNull().isEqualTo(now); + Assertions.assertThat(order.getTags()).isNotNull().isEqualTo(tags); + Assertions.assertThat(order.getUser()).isNotNull().isEqualTo(user1); + Assertions.assertThat(order.getProducts()).isNotEmpty(); + + System.out.println("order: " + JsonUtil.toString(order)); + + HBASE_TEMPLATE.delete(TABLE_NAME, originOrder.getRowKey()); + order = HBASE_TEMPLATE.getEntity(TABLE_NAME, order.getRowKey(), "f1", Order.class); + Assertions.assertThat(order).isNull(); + } + +} diff --git a/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/HbaseTemplateScanTest.java b/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/HbaseTemplateScanTest.java new file mode 100644 index 00000000..4496fc08 --- /dev/null +++ b/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/HbaseTemplateScanTest.java @@ -0,0 +1,242 @@ +package io.github.dunwu.javadb.hbase; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import io.github.dunwu.javadb.hbase.entity.common.PageData; +import io.github.dunwu.javadb.hbase.entity.common.RowDo; +import io.github.dunwu.javadb.hbase.entity.common.ScrollData; +import io.github.dunwu.javadb.hbase.entity.scan.MultiFamilyScan; +import io.github.dunwu.javadb.hbase.entity.scan.SingleFamilyScan; +import org.apache.hadoop.hbase.client.Put; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Get 测试集 + *

+ * 测试前,先完整执行 {@link HbaseTemplateGetTest} + * + * @author Zhang Peng + * @date 2023-11-13 + */ +public class HbaseTemplateScanTest { + + public static final String TABLE_NAME = "test:test"; + + private static final HbaseTemplate HBASE_TEMPLATE; + + static { + try { + HBASE_TEMPLATE = HbaseFactory.newHbaseTemplate(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Test + @DisplayName("批量初始化") + public void init() throws IOException, InterruptedException { + List products = new ArrayList<>(); + List userPuts = new ArrayList<>(); + for (int i = 1; i <= 100; i++) { + Product product = new Product(String.valueOf(i), "product" + i, + new BigDecimal(RandomUtil.randomDouble(9999.0))); + products.add(product); + + User user = new User(i, "user" + i); + Put put = HbaseTemplate.newPut(product.getRowKey(), null, "f2", user); + userPuts.add(put); + } + HBASE_TEMPLATE.batchPut(TABLE_NAME, "f1", products); + HBASE_TEMPLATE.batchPut(TABLE_NAME, userPuts); + } + + @Test + @DisplayName("单列族分页查询") + public void test01() throws IOException { + SingleFamilyScan scan = new SingleFamilyScan(); + scan.setFamily("f1") + .setTableName(TABLE_NAME) + .setPage(1) + .setSize(10) + .setReversed(true); + PageData firstPage = HBASE_TEMPLATE.page(scan); + System.out.println(StrUtil.format("第 {} 页数据: {}", 1, JSONUtil.toJsonStr(firstPage))); + + int totalPages = firstPage.getTotalPages(); + for (int page = 2; page <= totalPages; page++) { + scan.setPage(page); + PageData nextPage = HBASE_TEMPLATE.page(scan); + System.out.println(StrUtil.format("第 {} 页数据: {}", page, JSONUtil.toJsonStr(nextPage))); + Assertions.assertThat(nextPage).isNotNull(); + } + } + + @Test + @DisplayName("多列族分页查询") + public void test02() throws IOException { + Map> familyColumnMap = new HashMap<>(); + familyColumnMap.put("f1", CollectionUtil.newArrayList("id", "name", "price")); + familyColumnMap.put("f2", CollectionUtil.newArrayList("id", "name")); + + MultiFamilyScan scan = new MultiFamilyScan(); + scan.setFamilyColumnMap(familyColumnMap) + .setTableName(TABLE_NAME) + .setPage(1) + .setSize(10) + .setReversed(true); + PageData firstPage = HBASE_TEMPLATE.page(scan); + System.out.println(StrUtil.format("第 {} 页数据: {}", 1, JSONUtil.toJsonStr(firstPage))); + + int totalPages = firstPage.getTotalPages(); + for (int page = 1; page <= totalPages; page++) { + scan.setPage(page); + PageData nextPage = HBASE_TEMPLATE.page(scan); + System.out.println(StrUtil.format("查询实体: {}", JSONUtil.toJsonStr(nextPage))); + Assertions.assertThat(nextPage).isNotNull(); + } + } + + @Test + @DisplayName("实体分页查询") + public void test03() throws IOException { + + SingleFamilyScan scan = new SingleFamilyScan(); + scan.setFamily("f2") + .setTableName(TABLE_NAME) + .setPage(1) + .setSize(10) + .setReversed(true); + PageData firstPage = HBASE_TEMPLATE.getEntityPage(scan, User.class); + System.out.println(StrUtil.format("第 {} 页数据: {}", 1, JSONUtil.toJsonStr(firstPage))); + + int totalPages = firstPage.getTotalPages(); + for (int page = 2; page <= totalPages; page++) { + scan.setPage(page); + PageData nextPage = HBASE_TEMPLATE.getEntityPage(scan, User.class); + System.out.println(StrUtil.format("第 {} 页数据: {}", page, JSONUtil.toJsonStr(nextPage))); + Assertions.assertThat(nextPage).isNotNull(); + } + } + + @Test + @DisplayName("单列族滚动查询") + public void test04() throws IOException { + + SingleFamilyScan scan = new SingleFamilyScan(); + scan.setFamily("f1") + .setTableName(TABLE_NAME) + .setSize(10) + .setReversed(false); + + int page = 1; + ScrollData first = HBASE_TEMPLATE.scroll(scan); + System.out.println(StrUtil.format("第 {} 页数据: {}", page, JSONUtil.toJsonPrettyStr(first))); + Assertions.assertThat(first).isNotNull(); + scan.setScrollRow(first.getScrollRow()); + + while (true) { + page++; + ScrollData next = HBASE_TEMPLATE.scroll(scan); + if (next == null || CollectionUtil.isEmpty(next.getContent())) { + break; + } + System.out.println(StrUtil.format("第 {} 页数据: {}", page, JSONUtil.toJsonPrettyStr(first))); + scan.setScrollRow(next.getScrollRow()); + } + } + + @Test + @DisplayName("多列族滚动查询") + public void test05() throws IOException { + Map> familyColumnMap = new HashMap<>(); + familyColumnMap.put("f1", CollectionUtil.newArrayList("id", "name", "price")); + familyColumnMap.put("f2", CollectionUtil.newArrayList("id", "name")); + + MultiFamilyScan scan = new MultiFamilyScan(); + scan.setFamilyColumnMap(familyColumnMap) + .setTableName(TABLE_NAME) + .setSize(10) + .setReversed(true); + + ScrollData first = HBASE_TEMPLATE.scroll(scan); + System.out.println(StrUtil.format("查询实体: {}", JSONUtil.toJsonPrettyStr(first))); + Assertions.assertThat(first).isNotNull(); + scan.setScrollRow(first.getScrollRow()); + + while (true) { + ScrollData next = HBASE_TEMPLATE.scroll(scan); + if (next == null || CollectionUtil.isEmpty(next.getContent())) { + break; + } + System.out.println(StrUtil.format("查询实体: {}", JSONUtil.toJsonPrettyStr(next))); + scan.setScrollRow(next.getScrollRow()); + } + } + + @Test + @DisplayName("滚动查询实体") + public void test06() throws IOException { + + SingleFamilyScan scan = new SingleFamilyScan(); + scan.setFamily("f1") + .setTableName(TABLE_NAME) + .setSize(10) + .setReversed(false); + + ScrollData first = HBASE_TEMPLATE.getEntityScroll(scan, Product.class); + System.out.println(StrUtil.format("查询实体: {}", JSONUtil.toJsonPrettyStr(first))); + Assertions.assertThat(first).isNotNull(); + scan.setScrollRow(first.getScrollRow()); + + while (true) { + ScrollData next = HBASE_TEMPLATE.getEntityScroll(scan, Product.class); + if (next == null || CollectionUtil.isEmpty(next.getContent())) { + break; + } + System.out.println(StrUtil.format("查询实体: {}", JSONUtil.toJsonPrettyStr(next))); + scan.setScrollRow(next.getScrollRow()); + } + } + + @Test + @DisplayName("滚动删除全部记录") + public void clear() throws IOException, InterruptedException { + + SingleFamilyScan scan = new SingleFamilyScan(); + scan.setFamily("f1") + .setTableName(TABLE_NAME) + .setSize(100) + .setReversed(false); + + ScrollData first = HBASE_TEMPLATE.scroll(scan); + System.out.println(StrUtil.format("查询实体: {}", JSONUtil.toJsonPrettyStr(first))); + Assertions.assertThat(first).isNotNull(); + scan.setScrollRow(first.getScrollRow()); + HBASE_TEMPLATE.batchDelete(TABLE_NAME, + first.getContent().stream().map(RowDo::getRow).distinct().toArray(String[]::new)); + + while (true) { + ScrollData next = HBASE_TEMPLATE.scroll(scan); + if (next == null || CollectionUtil.isEmpty(next.getContent())) { + break; + } + System.out.println(StrUtil.format("查询实体: {}", JSONUtil.toJsonPrettyStr(next))); + scan.setScrollRow(next.getScrollRow()); + HBASE_TEMPLATE.batchDelete(TABLE_NAME, + next.getContent().stream().map(RowDo::getRow).distinct().toArray(String[]::new)); + } + } + +} diff --git a/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/Order.java b/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/Order.java new file mode 100644 index 00000000..acef0afd --- /dev/null +++ b/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/Order.java @@ -0,0 +1,34 @@ +package io.github.dunwu.javadb.hbase; + +import io.github.dunwu.javadb.hbase.annotation.RowKeyRule; +import io.github.dunwu.javadb.hbase.entity.BaseHbaseEntity; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * 较为复杂的 Java 实体 + * + * @author Zhang Peng + * @date 2023-11-20 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@RowKeyRule(uk = "getId", length = 20) +public class Order implements BaseHbaseEntity { + + private String id; + private User user; + private List products; + private String desc; + private Date date; + private Map tags; + +} diff --git a/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/OrderMapper.java b/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/OrderMapper.java new file mode 100644 index 00000000..f6639380 --- /dev/null +++ b/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/OrderMapper.java @@ -0,0 +1,30 @@ +package io.github.dunwu.javadb.hbase; + +import io.github.dunwu.javadb.hbase.mapper.BaseHbaseMapper; + +/** + * @author Zhang Peng + * @date 2023-11-15 + */ +public class OrderMapper extends BaseHbaseMapper { + + public OrderMapper(HbaseTemplate hbaseTemplate) { + super(hbaseTemplate); + } + + @Override + public String getTableName() { + return "test"; + } + + @Override + public String getFamily() { + return "f1"; + } + + @Override + public Class getEntityClass() { + return Order.class; + } + +} diff --git a/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/Product.java b/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/Product.java new file mode 100644 index 00000000..1a486365 --- /dev/null +++ b/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/Product.java @@ -0,0 +1,29 @@ +package io.github.dunwu.javadb.hbase; + +import io.github.dunwu.javadb.hbase.annotation.RowKeyRule; +import io.github.dunwu.javadb.hbase.entity.BaseHbaseEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; + +/** + * 产品实体 + * + * @author Zhang Peng + * @date 2023-11-15 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@RowKeyRule(uk = "getId", length = 10) +public class Product implements BaseHbaseEntity { + + private String id; + private String name; + private BigDecimal price; + + private static final long serialVersionUID = -2596114168690429555L; + +} diff --git a/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/User.java b/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/User.java new file mode 100644 index 00000000..63cc86c8 --- /dev/null +++ b/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/User.java @@ -0,0 +1,16 @@ +package io.github.dunwu.javadb.hbase; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class User { + + private int id; + + private String name; + +} \ No newline at end of file diff --git a/codes/javadb/javadb-elasticsearch/pom.xml b/codes/javadb/javadb-elasticsearch/pom.xml deleted file mode 100644 index ab483b6d..00000000 --- a/codes/javadb/javadb-elasticsearch/pom.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - 4.0.0 - - - org.springframework.boot - spring-boot-starter-parent - 2.6.3 - - - io.github.dunwu - javadb-elasticsearch - 1.0.0 - jar - - - 7.16.3 - - - - - org.springframework.boot - spring-boot-starter-data-elasticsearch - - - org.springframework.boot - spring-boot-starter-json - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - - - cn.hutool - hutool-all - 5.7.20 - - - - co.elastic.clients - elasticsearch-java - 7.16.3 - - - - org.elasticsearch.client - elasticsearch-rest-client - ${elasticsearch.version} - - - org.elasticsearch.client - elasticsearch-rest-high-level-client - ${elasticsearch.version} - - - - - - - com.fasterxml.jackson.core - jackson-databind - 2.12.3 - - - com.fasterxml.jackson.core - jackson-core - 2.12.3 - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - diff --git a/codes/javadb/javadb-h2/pom.xml b/codes/javadb/javadb-h2/pom.xml deleted file mode 100644 index 5660fec6..00000000 --- a/codes/javadb/javadb-h2/pom.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - 4.0.0 - - - org.springframework.boot - spring-boot-starter-parent - 2.6.3 - - - io.github.dunwu - javadb-h2 - 1.0.0 - jar - - - - org.springframework.boot - spring-boot-starter-data-rest - - - org.springframework.boot - spring-boot-starter-data-jpa - - - org.springframework.boot - spring-boot-starter-test - test - - - org.projectlombok - lombok - - - - - com.h2database - h2 - 2.1.210 - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - diff --git a/codes/javadb/javadb-hbase/pom.xml b/codes/javadb/javadb-hbase/pom.xml deleted file mode 100644 index 76d12d07..00000000 --- a/codes/javadb/javadb-hbase/pom.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - 4.0.0 - io.github.dunwu - javadb-hbase - 1.0.0 - jar - - - UTF-8 - 1.8 - ${java.version} - ${java.version} - - 1.3.1 - 4.13.1 - 0.5.7 - - - - - org.apache.hbase - hbase-client - - - io.github.dunwu - dunwu-tool-core - - - - - junit - junit - - - - - - - - org.apache.hbase - hbase-client - ${hbase.version} - - - io.github.dunwu - dunwu-tool-core - ${dunwu.version} - - - - - junit - junit - ${junit.version} - test - - - - - diff --git a/codes/javadb/javadb-hbase/src/main/java/io/github/dunwu/javadb/hbase/HBaseConstant.java b/codes/javadb/javadb-hbase/src/main/java/io/github/dunwu/javadb/hbase/HBaseConstant.java deleted file mode 100644 index 63ca6e9d..00000000 --- a/codes/javadb/javadb-hbase/src/main/java/io/github/dunwu/javadb/hbase/HBaseConstant.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.dunwu.javadb.hbase; - -public enum HBaseConstant { - - HBASE_ZOOKEEPER_QUORUM("hbase.zookeeper.quorum"), - HBASE_ENABLE("hbase.enable"), - HBASE_MASTER("hbase.master"), - HBASE_ZOOKEEPER_PROPERTY_CLIENTPORT("hbase.zookeeper.property.clientPort"), - HBASE_HCONNECTION_THREADS_MAX("hbase.hconnection.threads.max"), - HBASE_HCONNECTION_THREADS_CORE("hbase.hconnection.threads.core"), - ZOOKEEPER_ZNODE_PARENT("zookeeper.znode.parent"), - HBASE_COLUMN_FAMILY("hbase.column.family"), - HBASE_EXECUTOR_NUM("hbase.executor.num"), - HBASE_IPC_POOL_SIZE("hbase.client.ipc.pool.size"); - - private String key; - - HBaseConstant(String key) { - this.key = key; - } - - public String key() { - return key; - } -} diff --git a/codes/javadb/javadb-hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseCellEntity.java b/codes/javadb/javadb-hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseCellEntity.java deleted file mode 100644 index 5b228f41..00000000 --- a/codes/javadb/javadb-hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseCellEntity.java +++ /dev/null @@ -1,84 +0,0 @@ -package io.github.dunwu.javadb.hbase; - -/** - * HBase Cell 实体 - * @author Zhang Peng - * @since 2019-03-04 - */ -public class HbaseCellEntity { - - private String table; - - private String row; - - private String colFamily; - - private String col; - - private String val; - - public HbaseCellEntity() { - } - - public HbaseCellEntity(String row, String colFamily, String col, String val) { - this.row = row; - this.colFamily = colFamily; - this.col = col; - this.val = val; - } - - public HbaseCellEntity(String table, String row, String colFamily, String col, String val) { - this.table = table; - this.row = row; - this.colFamily = colFamily; - this.col = col; - this.val = val; - } - - public String getTable() { - return table; - } - - public void setTable(String table) { - this.table = table; - } - - public String getRow() { - return row; - } - - public void setRow(String row) { - this.row = row; - } - - public String getColFamily() { - return colFamily; - } - - public void setColFamily(String colFamily) { - this.colFamily = colFamily; - } - - public String getCol() { - return col; - } - - public void setCol(String col) { - this.col = col; - } - - public String getVal() { - return val; - } - - public void setVal(String val) { - this.val = val; - } - - @Override - public String toString() { - return "HbaseCellEntity{" + "table='" + table + '\'' + ", row='" + row + '\'' + ", colFamily='" + colFamily - + '\'' + ", col='" + col + '\'' + ", val='" + val + '\'' + '}'; - } - -} diff --git a/codes/javadb/javadb-hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseHelper.java b/codes/javadb/javadb-hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseHelper.java deleted file mode 100644 index c4f86684..00000000 --- a/codes/javadb/javadb-hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseHelper.java +++ /dev/null @@ -1,369 +0,0 @@ -package io.github.dunwu.javadb.hbase; - -import io.github.dunwu.tool.util.PropertiesUtil; -import org.apache.commons.lang.StringUtils; -import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.hbase.*; -import org.apache.hadoop.hbase.client.*; -import org.apache.hadoop.hbase.util.Bytes; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; - -/** - * HBase 服务实现类 - * @author Zhang Peng - * @since 2019-03-01 - */ -public class HbaseHelper { - - private static final String FIRST_CONFIG = "classpath://config//hbase.properties"; - - private static final String SECOND_CONFIG = "classpath://application.properties"; - - private HbaseProperties hbaseProperties; - - private Connection connection; - - public HbaseHelper() throws Exception { - // 初始化参数 - Properties properties = loadConfigFile(); - if (properties == null) { - throw new Exception("读取 Hbase 配置失败,无法建立连接"); - } - Boolean enable = PropertiesUtil.getBoolean(properties, HBaseConstant.HBASE_ENABLE.key(), true); - if (!enable) { - return; - } - String quorum = PropertiesUtil.getString(properties, HBaseConstant.HBASE_ZOOKEEPER_QUORUM.key(), ""); - String hbaseMaster = PropertiesUtil.getString(properties, HBaseConstant.HBASE_MASTER.key(), ""); - String clientPort = - PropertiesUtil.getString(properties, HBaseConstant.HBASE_ZOOKEEPER_PROPERTY_CLIENTPORT.key(), ""); - String znodeParent = PropertiesUtil.getString(properties, HBaseConstant.ZOOKEEPER_ZNODE_PARENT.key(), ""); - String maxThreads = PropertiesUtil.getString(properties, HBaseConstant.HBASE_HCONNECTION_THREADS_MAX.key(), ""); - String coreThreads = - PropertiesUtil.getString(properties, HBaseConstant.HBASE_HCONNECTION_THREADS_CORE.key(), ""); - String columnFamily = PropertiesUtil.getString(properties, HBaseConstant.HBASE_COLUMN_FAMILY.key(), ""); - String hbaseExecutorsNum = PropertiesUtil.getString(properties, HBaseConstant.HBASE_EXECUTOR_NUM.key(), "10"); - String ipcPoolSize = PropertiesUtil.getString(properties, HBaseConstant.HBASE_IPC_POOL_SIZE.key(), "1"); - - hbaseProperties = - new HbaseProperties(hbaseMaster, quorum, clientPort, znodeParent, maxThreads, coreThreads, columnFamily, - hbaseExecutorsNum, ipcPoolSize); - init(hbaseProperties); - } - - private Properties loadConfigFile() { - Properties properties = null; - try { - properties = PropertiesUtil.loadFromFile(FIRST_CONFIG); - } catch (Exception e) { - e.printStackTrace(); - } - - if (properties == null) { - try { - properties = PropertiesUtil.loadFromFile(SECOND_CONFIG); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - return properties; - } - - private void init(HbaseProperties hbaseProperties) throws Exception { - try { - // @formatter:off - Configuration configuration = HBaseConfiguration.create(); - configuration.set(HBaseConstant.HBASE_ZOOKEEPER_QUORUM.key(), hbaseProperties.getQuorum()); - configuration.set(HBaseConstant.HBASE_MASTER.key(), hbaseProperties.getHbaseMaster()); - configuration.set(HBaseConstant.HBASE_ZOOKEEPER_PROPERTY_CLIENTPORT.key(), - hbaseProperties.getClientPort()); - configuration.set(HBaseConstant.HBASE_HCONNECTION_THREADS_MAX.key(), - hbaseProperties.getMaxThreads()); - configuration.set(HBaseConstant.HBASE_HCONNECTION_THREADS_CORE.key(), - hbaseProperties.getCoreThreads()); - configuration.set(HBaseConstant.ZOOKEEPER_ZNODE_PARENT.key(), hbaseProperties.getZnodeParent()); - configuration.set(HBaseConstant.HBASE_COLUMN_FAMILY.key(), hbaseProperties.getColumnFamily()); - configuration.set(HBaseConstant.HBASE_IPC_POOL_SIZE.key(), hbaseProperties.getIpcPoolSize()); - // @formatter:on - connection = ConnectionFactory.createConnection(configuration); - } catch (Exception e) { - throw new Exception("hbase链接未创建", e); - } - } - - public HbaseHelper(HbaseProperties hbaseProperties) throws Exception { - this.hbaseProperties = hbaseProperties; - init(hbaseProperties); - } - - public void destory() { - if (connection != null) { - try { - connection.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - public HTableDescriptor[] listTables() throws Exception { - return listTables(null); - } - - public HTableDescriptor[] listTables(String tableName) throws Exception { - if (connection == null) { - throw new Exception("hbase链接未创建"); - } - - HTableDescriptor[] hTableDescriptors = new HTableDescriptor[0]; - try { - if (StringUtils.isEmpty(tableName)) { - hTableDescriptors = connection.getAdmin().listTables(); - } else { - hTableDescriptors = connection.getAdmin().listTables(tableName); - } - } catch (IOException e) { - throw new Exception("执行失败", e); - } - return hTableDescriptors; - } - - /** - * 创建表 - *

- * 等价于: - *

    - *
  • create 'tablename','family1','family2','family3'...
  • - *
- */ - public void createTable(String tableName) throws Exception { - createTable(tableName, new String[] {hbaseProperties.getColumnFamily()}); - } - - /** - * 创建表 - *

- * 等价于: - *

    - *
  • create 'tablename','family1','family2','family3'...
  • - *
- */ - public void createTable(String tableName, String[] colFamilies) throws Exception { - if (connection == null) { - throw new Exception("hbase链接未创建"); - } - - try { - TableName tablename = TableName.valueOf(tableName); - // 如果表存在,先删除 - if (connection.getAdmin().isTableAvailable(tablename)) { - dropTable(tableName); - } - HTableDescriptor tableDescriptor = new HTableDescriptor(tablename); - for (String famliy : colFamilies) { - tableDescriptor.addFamily(new HColumnDescriptor(famliy)); - } - - connection.getAdmin().createTable(tableDescriptor); - } catch (IOException e) { - e.printStackTrace(); - } - } - - /** - * 删除表 - *

- * 等价于: - *

    - *
  • disable 'tablename'
  • - *
  • drop 't1'
  • - *
- * @param name - */ - public void dropTable(String name) throws Exception { - if (connection == null) { - throw new Exception("hbase链接未创建"); - } - - Admin admin = null; - try { - admin = connection.getAdmin(); - TableName tableName = TableName.valueOf(name); - // 如果表存在,先删除 - if (admin.isTableAvailable(tableName)) { - admin.disableTable(tableName); - admin.deleteTable(tableName); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - - private Put toPut(HbaseCellEntity hBaseTableDTO) throws Exception { - if (connection == null) { - throw new Exception("hbase链接未创建"); - } - - Put put = new Put(Bytes.toBytes(hBaseTableDTO.getRow())); - put.addColumn(Bytes.toBytes(hBaseTableDTO.getColFamily()), Bytes.toBytes(hBaseTableDTO.getCol()), - Bytes.toBytes(hBaseTableDTO.getVal())); - return put; - } - - public void delete(String tableName, String rowKey) throws Exception { - if (connection == null) { - throw new Exception("hbase链接未创建"); - } - - Table table = null; - try { - table = connection.getTable(TableName.valueOf(tableName)); - Delete delete = new Delete(Bytes.toBytes(rowKey)); - table.delete(delete); - } catch (IOException e) { - e.printStackTrace(); - throw new Exception("delete失败"); - } - } - - public String resultToString(Result result) { - if (result == null) { - return null; - } - Cell[] cells = result.rawCells(); - StringBuilder sb = new StringBuilder(); - for (Cell cell : cells) { - sb.append("{ "); - sb.append("RowName -> ").append(new String(CellUtil.cloneRow(cell))); - sb.append(", Timetamp -> ").append(cell.getTimestamp()); - sb.append(", Column Family -> ").append(new String(CellUtil.cloneFamily(cell))); - sb.append(", Row Name -> ").append(new String(CellUtil.cloneQualifier(cell))); - sb.append(", value -> ").append(new String(CellUtil.cloneValue(cell))); - sb.append(" }\n"); - } - return sb.toString(); - } - - public Result get(String tableName, String rowKey) throws Exception { - return get(tableName, rowKey, null, null); - } - - public Result get(String tableName, String rowKey, String colFamily, String qualifier) throws Exception { - if (connection == null) { - throw new Exception("hbase链接未创建"); - } - - if (connection.isClosed()) { - throw new Exception("hbase 连接已关闭"); - } - - if (StringUtils.isEmpty(tableName) || StringUtils.isEmpty(rowKey)) { - return null; - } - - Result result = null; - try { - Table table = connection.getTable(TableName.valueOf(tableName)); - Get get = new Get(Bytes.toBytes(rowKey)); - if (StringUtils.isNotEmpty(colFamily)) { - if (StringUtils.isNotEmpty(qualifier)) { - get.addColumn(Bytes.toBytes(colFamily), Bytes.toBytes(qualifier)); - } else { - get.addFamily(Bytes.toBytes(colFamily)); - } - } - result = table.get(get); - } catch (IOException e) { - throw new Exception("查询时发生异常"); - } - return result; - } - - public Result get(String tableName, String rowKey, String colFamily) throws Exception { - return get(tableName, rowKey, colFamily, null); - } - - public Result[] scan(String tableName) throws Exception { - return scan(tableName, null, null, null, null); - } - - public Result[] scan(String tableName, String colFamily, String qualifier, String startRow, String stopRow) - throws Exception { - if (connection == null) { - throw new Exception("hbase链接未创建"); - } - - if (StringUtils.isEmpty(tableName)) { - return null; - } - - ResultScanner resultScanner = null; - List list = new ArrayList<>(); - try { - Table table = connection.getTable(TableName.valueOf(tableName)); - Scan scan = new Scan(); - if (StringUtils.isNotEmpty(colFamily)) { - if (StringUtils.isNotEmpty(qualifier)) { - scan.addColumn(Bytes.toBytes(colFamily), Bytes.toBytes(qualifier)); - } - scan.addFamily(Bytes.toBytes(colFamily)); - } - if (StringUtils.isNotEmpty(startRow)) { - scan.setStartRow(Bytes.toBytes(startRow)); - } - if (StringUtils.isNotEmpty(stopRow)) { - scan.setStopRow(Bytes.toBytes(stopRow)); - } - resultScanner = table.getScanner(scan); - Result result = resultScanner.next(); - while (result != null) { - list.add(result); - result = resultScanner.next(); - } - } catch (IOException e) { - e.printStackTrace(); - } finally { - if (resultScanner != null) { - resultScanner.close(); - } - } - return list.toArray(new Result[0]); - } - - public Result[] scan(String tableName, String colFamily) throws Exception { - return scan(tableName, colFamily, null, null, null); - } - - public Result[] scan(String tableName, String colFamily, String qualifier) throws Exception { - return scan(tableName, colFamily, qualifier, null, null); - } - - private List resultScannerToResults(ResultScanner resultScanner) { - if (resultScanner == null) { - return null; - } - - List list = new ArrayList<>(); - Result result = null; - try { - result = resultScanner.next(); - while (result != null) { - list.add(result); - result = resultScanner.next(); - } - } catch (IOException e) { - e.printStackTrace(); - } - return list; - } - - public HbaseProperties getHbaseProperties() { - return hbaseProperties; - } - -} diff --git a/codes/javadb/javadb-hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseProperties.java b/codes/javadb/javadb-hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseProperties.java deleted file mode 100644 index f52b69a3..00000000 --- a/codes/javadb/javadb-hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseProperties.java +++ /dev/null @@ -1,127 +0,0 @@ -package io.github.dunwu.javadb.hbase; - -import java.io.Serializable; - -/** - * Hbase 配置参数管理对象 - * @author Zhang Peng - */ -public class HbaseProperties implements Serializable { - - private static final long serialVersionUID = 2930639554689310736L; - - private String hbaseMaster; - - private String quorum; - - private String clientPort; - - private String znodeParent; - - private String maxThreads; - - private String coreThreads; - - private String columnFamily; - - private String hbaseExecutorsNum = "10"; - - private String ipcPoolSize; - - public HbaseProperties() { - } - - public HbaseProperties(String hbaseMaster, String quorum, String clientPort, String znodeParent, String maxThreads, - String coreThreads, String columnFamily, String hbaseExecutorsNum, String ipcPoolSize) { - this.hbaseMaster = hbaseMaster; - this.quorum = quorum; - this.clientPort = clientPort; - this.znodeParent = znodeParent; - this.maxThreads = maxThreads; - this.coreThreads = coreThreads; - this.columnFamily = columnFamily; - this.hbaseExecutorsNum = hbaseExecutorsNum; - this.ipcPoolSize = ipcPoolSize; - } - - public String getHbaseMaster() { - return hbaseMaster; - } - - public void setHbaseMaster(String hbaseMaster) { - this.hbaseMaster = hbaseMaster; - } - - public String getQuorum() { - return quorum; - } - - public void setQuorum(String quorum) { - this.quorum = quorum; - } - - public String getClientPort() { - return clientPort; - } - - public void setClientPort(String clientPort) { - this.clientPort = clientPort; - } - - public String getZnodeParent() { - return znodeParent; - } - - public void setZnodeParent(String znodeParent) { - this.znodeParent = znodeParent; - } - - public String getMaxThreads() { - return maxThreads; - } - - public void setMaxThreads(String maxThreads) { - this.maxThreads = maxThreads; - } - - public String getCoreThreads() { - return coreThreads; - } - - public void setCoreThreads(String coreThreads) { - this.coreThreads = coreThreads; - } - - public String getColumnFamily() { - return columnFamily; - } - - public void setColumnFamily(String columnFamily) { - this.columnFamily = columnFamily; - } - - public String getHbaseExecutorsNum() { - return hbaseExecutorsNum; - } - - public void setHbaseExecutorsNum(String hbaseExecutorsNum) { - this.hbaseExecutorsNum = hbaseExecutorsNum; - } - - public String getIpcPoolSize() { - return ipcPoolSize; - } - - public void setIpcPoolSize(String ipcPoolSize) { - this.ipcPoolSize = ipcPoolSize; - } - - @Override - public String toString() { - return "HbaseProperties{" + "quorum='" + quorum + '\'' + ", clientPort='" + clientPort + '\'' - + ", znodeParent='" + znodeParent + '\'' + ", maxThreads='" + maxThreads + '\'' + ", coreThreads='" - + coreThreads + '\'' + ", columnFamily='" + columnFamily + '\'' + ", hbaseExecutorsNum='" - + hbaseExecutorsNum + '\'' + '}'; - } - -} diff --git a/codes/javadb/javadb-hbase/src/test/java/io/github/dunwu/javadb/hbase/HbaseHelperTest.java b/codes/javadb/javadb-hbase/src/test/java/io/github/dunwu/javadb/hbase/HbaseHelperTest.java deleted file mode 100644 index c79aaa54..00000000 --- a/codes/javadb/javadb-hbase/src/test/java/io/github/dunwu/javadb/hbase/HbaseHelperTest.java +++ /dev/null @@ -1,110 +0,0 @@ -package io.github.dunwu.javadb.hbase; - -import io.github.dunwu.javadb.hbase.HbaseHelper; -import org.apache.hadoop.hbase.HTableDescriptor; -import org.apache.hadoop.hbase.client.Result; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; - -/** - * @author Zhang Peng - * @since 2019-03-29 - */ -public class HbaseHelperTest { - - private static HbaseHelper hbaseHelper; - - @BeforeClass - public static void BeforeClass() { - try { - hbaseHelper = new HbaseHelper(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - @Test - public void listTable() throws Exception { - HTableDescriptor[] hTableDescriptors = hbaseHelper.listTables(); - if (hTableDescriptors == null || hTableDescriptors.length <= 0) { - Assert.fail(); - } - - System.out.println("Tables:"); - for (HTableDescriptor item : hTableDescriptors) { - System.out.println(item.getTableName()); - } - } - - @Test - public void createTable() throws Exception { - hbaseHelper.createTable("table1", new String[] {"columnFamliy1", "columnFamliy2"}); - HTableDescriptor[] table1s = hbaseHelper.listTables("table1"); - if (table1s == null || table1s.length <= 0) { - Assert.fail(); - } - - hbaseHelper.createTable("table2", new String[] {"columnFamliy1", "columnFamliy2"}); - table1s = hbaseHelper.listTables("table2"); - if (table1s == null || table1s.length <= 0) { - Assert.fail(); - } - } - - @Test - public void dropTable() throws Exception { - hbaseHelper.dropTable("table1"); - HTableDescriptor[] table1s = hbaseHelper.listTables("table1"); - if (table1s != null && table1s.length > 0) { - Assert.fail(); - } - } - - @Test - public void get() throws Exception { - Result result = hbaseHelper.get("table1", "row1"); - System.out.println(hbaseHelper.resultToString(result)); - - result = hbaseHelper.get("table1", "row2", "columnFamliy1"); - System.out.println(hbaseHelper.resultToString(result)); - } - - @Test - public void scan() throws Exception { - Result[] results = hbaseHelper.scan("table1"); - System.out.println("HbaseUtil.scan(\"table1\") result: "); - if (results.length > 0) { - for (Result r : results) { - System.out.println(hbaseHelper.resultToString(r)); - } - } - - results = hbaseHelper.scan("table1", "columnFamliy1"); - System.out.println("HbaseUtil.scan(\"table1\", \"columnFamliy1\" result: "); - if (results.length > 0) { - for (Result r : results) { - System.out.println(hbaseHelper.resultToString(r)); - } - } - - results = hbaseHelper.scan("table1", "columnFamliy1", "a"); - System.out.println("HbaseUtil.scan(\"table1\", \"columnFamliy1\", \"a\") result: "); - if (results.length > 0) { - for (Result r : results) { - System.out.println(hbaseHelper.resultToString(r)); - } - } - } - - @Test - public void delete() throws Exception { - Result result = hbaseHelper.get("table1", "row1"); - System.out.println(result.toString()); - - hbaseHelper.delete("table1", "row1"); - result = hbaseHelper.get("table1", "row1"); - System.out.println(result.toString()); - } - -} diff --git a/codes/javadb/javadb-hbase/src/test/resources/config/hbase.properties b/codes/javadb/javadb-hbase/src/test/resources/config/hbase.properties deleted file mode 100644 index 4ba3ffd3..00000000 --- a/codes/javadb/javadb-hbase/src/test/resources/config/hbase.properties +++ /dev/null @@ -1,7 +0,0 @@ -hbase.enable = true -hbase.zookeeper.quorum = localhost,xxxx,xxxx -hbase.zookeeper.property.clientPort = 2181 -zookeeper.znode.parent = /hbase -hbase.hconnection.threads.max = 256 -hbase.hconnection.threads.core = 32 -hbase.column.family = F diff --git a/codes/javadb/javadb-mongodb/pom.xml b/codes/javadb/javadb-mongodb/pom.xml deleted file mode 100644 index 864c6dcf..00000000 --- a/codes/javadb/javadb-mongodb/pom.xml +++ /dev/null @@ -1,73 +0,0 @@ - - - 4.0.0 - - - org.springframework.boot - spring-boot-starter-parent - 2.6.3 - - - io.github.dunwu - javadb-mongodb - 1.0.0 - jar - - - - org.springframework.boot - spring-boot-starter-data-mongodb - - - org.springframework.boot - spring-boot-starter-json - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - - - cn.hutool - hutool-all - 5.7.20 - - - - com.querydsl - querydsl-mongodb - - - org.mongodb - mongo-java-drver - - - - - io.projectreactor - reactor-core - 3.4.14 - - - - junit - junit - test - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - diff --git a/codes/javadb/javadb-mysql/pom.xml b/codes/javadb/javadb-mysql/pom.xml deleted file mode 100644 index 808030e6..00000000 --- a/codes/javadb/javadb-mysql/pom.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - 4.0.0 - - - org.springframework.boot - spring-boot-starter-parent - 2.6.3 - - - io.github.dunwu - javadb-mysql - 1.0.0 - jar - - - - org.springframework.boot - spring-boot-starter-jdbc - - - org.springframework.boot - spring-boot-starter-test - test - - - mysql - mysql-connector-java - - - org.projectlombok - lombok - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - diff --git a/codes/javadb/javadb-redis/pom.xml b/codes/javadb/javadb-redis/pom.xml deleted file mode 100644 index 3b251add..00000000 --- a/codes/javadb/javadb-redis/pom.xml +++ /dev/null @@ -1,78 +0,0 @@ - - - 4.0.0 - - - org.springframework.boot - spring-boot-starter-parent - 2.6.3 - - - io.github.dunwu - javadb-redis - 1.0.0 - jar - - - 3.7.2 - - - - - org.springframework.boot - spring-boot-starter-data-redis - - - org.springframework.boot - spring-boot-starter-json - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - - - - - redis.clients - jedis - - - org.redisson - redisson - 3.16.8 - - - - - junit - junit - test - - - - - - - org.redisson - redisson - ${redisson.version} - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - diff --git a/codes/javadb/javadb-redis/src/test/java/io/github/dunwu/javadb/RedissonStandaloneTest.java b/codes/javadb/javadb-redis/src/test/java/io/github/dunwu/javadb/RedissonStandaloneTest.java deleted file mode 100644 index 6863682b..00000000 --- a/codes/javadb/javadb-redis/src/test/java/io/github/dunwu/javadb/RedissonStandaloneTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.github.dunwu.javadb; - -import org.junit.jupiter.api.Test; -import org.redisson.api.RBucket; -import org.redisson.api.RedissonClient; -import org.springframework.context.ApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; - -/** - * @author Zhang Peng - * @since 2018/6/19 - */ -public class RedissonStandaloneTest { - - @Test - public void testRedissonConnect() { - ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:redisson-standalone.xml"); - RedissonClient redisson = (RedissonClient) applicationContext.getBean("standalone"); - // 首先获取redis中的key-value对象,key不存在没关系 - RBucket keyObject = redisson.getBucket("key"); - // 如果key存在,就设置key的值为新值value - // 如果key不存在,就设置key的值为value - keyObject.set("value"); - } - -} diff --git a/codes/javadb/javadb-sqlite/pom.xml b/codes/javadb/javadb-sqlite/pom.xml deleted file mode 100644 index c335c5dc..00000000 --- a/codes/javadb/javadb-sqlite/pom.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - 4.0.0 - - - org.springframework.boot - spring-boot-starter-parent - 2.6.3 - - - io.github.dunwu - javadb-sqlite - 1.0.0 - jar - - - - org.springframework.boot - spring-boot-starter-jdbc - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - - - - - org.xerial - sqlite-jdbc - 3.36.0.2 - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - diff --git a/codes/javadb/mongodb/pom.xml b/codes/javadb/mongodb/pom.xml new file mode 100644 index 00000000..1c6c2c27 --- /dev/null +++ b/codes/javadb/mongodb/pom.xml @@ -0,0 +1,73 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.6.3 + + + io.github.dunwu + javadb-mongodb + 1.0.0 + jar + + + + org.springframework.boot + spring-boot-starter-data-mongodb + + + org.springframework.boot + spring-boot-starter-json + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + + + cn.hutool + hutool-all + 5.7.20 + + + + com.querydsl + querydsl-mongodb + + + org.mongodb + mongo-java-drver + + + + + io.projectreactor + reactor-core + 3.4.14 + + + + junit + junit + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/SpringBootDataMongodbApplication.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/SpringBootDataMongodbApplication.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/SpringBootDataMongodbApplication.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/SpringBootDataMongodbApplication.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/advanced/AdvancedRepository.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/advanced/AdvancedRepository.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/advanced/AdvancedRepository.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/advanced/AdvancedRepository.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/advanced/ApplicationConfiguration.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/advanced/ApplicationConfiguration.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/advanced/ApplicationConfiguration.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/advanced/ApplicationConfiguration.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/ApplicationConfiguration.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/ApplicationConfiguration.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/ApplicationConfiguration.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/ApplicationConfiguration.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/Invoice.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/Invoice.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/Invoice.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/Invoice.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/LineItem.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/LineItem.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/LineItem.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/LineItem.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/Order.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/Order.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/Order.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/Order.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepository.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepository.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepository.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepository.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepositoryCustom.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepositoryCustom.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepositoryCustom.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepositoryCustom.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepositoryImpl.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepositoryImpl.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepositoryImpl.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepositoryImpl.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrdersPerCustomer.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrdersPerCustomer.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrdersPerCustomer.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrdersPerCustomer.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/Address.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/Address.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/Address.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/Address.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/ApplicationConfiguration.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/ApplicationConfiguration.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/ApplicationConfiguration.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/ApplicationConfiguration.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/Customer.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/Customer.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/Customer.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/Customer.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/CustomerRepository.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/CustomerRepository.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/CustomerRepository.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/CustomerRepository.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/immutable/ApplicationConfiguration.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/immutable/ApplicationConfiguration.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/immutable/ApplicationConfiguration.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/immutable/ApplicationConfiguration.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/immutable/ImmutablePerson.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/immutable/ImmutablePerson.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/immutable/ImmutablePerson.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/immutable/ImmutablePerson.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/Customer.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/Customer.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/Customer.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/Customer.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerDto.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerDto.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerDto.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerDto.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerProjection.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerProjection.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerProjection.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerProjection.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerRepository.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerRepository.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerRepository.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerRepository.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerSummary.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerSummary.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerSummary.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerSummary.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/ApplicationConfiguration.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/ApplicationConfiguration.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/ApplicationConfiguration.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/ApplicationConfiguration.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/Contact.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/Contact.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/Contact.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/Contact.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/ContactRepository.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/ContactRepository.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/ContactRepository.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/ContactRepository.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/Person.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/Person.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/Person.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/Person.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/Relative.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/Relative.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/Relative.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/Relative.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/RelativeRepository.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/RelativeRepository.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/RelativeRepository.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/RelativeRepository.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/UserRepository.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/UserRepository.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/UserRepository.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/UserRepository.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/package-info.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/package-info.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/package-info.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/package-info.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/BlogPost.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/BlogPost.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/BlogPost.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/BlogPost.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/BlogPostRepository.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/BlogPostRepository.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/BlogPostRepository.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/BlogPostRepository.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/MongoTestConfiguration.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/MongoTestConfiguration.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/MongoTestConfiguration.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/MongoTestConfiguration.java diff --git a/codes/javadb/javadb-mongodb/src/main/resources/application.properties b/codes/javadb/mongodb/src/main/resources/application.properties similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/resources/application.properties rename to codes/javadb/mongodb/src/main/resources/application.properties diff --git a/codes/javadb/javadb-mongodb/src/main/resources/banner.txt b/codes/javadb/mongodb/src/main/resources/banner.txt similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/resources/banner.txt rename to codes/javadb/mongodb/src/main/resources/banner.txt diff --git a/codes/javadb/javadb-mongodb/src/main/resources/db/books.json b/codes/javadb/mongodb/src/main/resources/db/books.json similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/resources/db/books.json rename to codes/javadb/mongodb/src/main/resources/db/books.json diff --git a/codes/javadb/javadb-mongodb/src/main/resources/db/products.json b/codes/javadb/mongodb/src/main/resources/db/products.json similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/resources/db/products.json rename to codes/javadb/mongodb/src/main/resources/db/products.json diff --git a/codes/javadb/javadb-mongodb/src/main/resources/db/students.json b/codes/javadb/mongodb/src/main/resources/db/students.json similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/resources/db/students.json rename to codes/javadb/mongodb/src/main/resources/db/students.json diff --git a/codes/javadb/javadb-mongodb/src/main/resources/logback.xml b/codes/javadb/mongodb/src/main/resources/logback.xml similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/resources/logback.xml rename to codes/javadb/mongodb/src/main/resources/logback.xml diff --git a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/advanced/AdvancedIntegrationTests.java b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/advanced/AdvancedIntegrationTests.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/advanced/AdvancedIntegrationTests.java rename to codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/advanced/AdvancedIntegrationTests.java diff --git a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/advanced/package-info.java b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/advanced/package-info.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/advanced/package-info.java rename to codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/advanced/package-info.java diff --git a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepositoryIntegrationTests.java b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepositoryIntegrationTests.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepositoryIntegrationTests.java rename to codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepositoryIntegrationTests.java diff --git a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/SpringBooksIntegrationTests.java b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/SpringBooksIntegrationTests.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/SpringBooksIntegrationTests.java rename to codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/SpringBooksIntegrationTests.java diff --git a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/customer/CustomerRepositoryIntegrationTest.java b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/customer/CustomerRepositoryIntegrationTest.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/customer/CustomerRepositoryIntegrationTest.java rename to codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/customer/CustomerRepositoryIntegrationTest.java diff --git a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/customer/package-info.java b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/customer/package-info.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/customer/package-info.java rename to codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/customer/package-info.java diff --git a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/immutable/ImmutableEntityIntegrationTest.java b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/immutable/ImmutableEntityIntegrationTest.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/immutable/ImmutableEntityIntegrationTest.java rename to codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/immutable/ImmutableEntityIntegrationTest.java diff --git a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerRepositoryIntegrationTest.java b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerRepositoryIntegrationTest.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerRepositoryIntegrationTest.java rename to codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerRepositoryIntegrationTest.java diff --git a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/ContactRepositoryIntegrationTests.java b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/ContactRepositoryIntegrationTests.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/ContactRepositoryIntegrationTests.java rename to codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/ContactRepositoryIntegrationTests.java diff --git a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/MongoOperationsIntegrationTests.java b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/MongoOperationsIntegrationTests.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/MongoOperationsIntegrationTests.java rename to codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/MongoOperationsIntegrationTests.java diff --git a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/UserRepositoryIntegrationTests.java b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/UserRepositoryIntegrationTests.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/UserRepositoryIntegrationTests.java rename to codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/UserRepositoryIntegrationTests.java diff --git a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/TextSearchRepositoryTests.java b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/TextSearchRepositoryTests.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/TextSearchRepositoryTests.java rename to codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/TextSearchRepositoryTests.java diff --git a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/TextSearchTemplateTests.java b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/TextSearchTemplateTests.java similarity index 96% rename from codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/TextSearchTemplateTests.java rename to codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/TextSearchTemplateTests.java index 6f50b0e0..5b6cc98b 100644 --- a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/TextSearchTemplateTests.java +++ b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/TextSearchTemplateTests.java @@ -15,7 +15,6 @@ */ package io.github.dunwu.javadb.mongodb.springboot.textsearch; -import io.github.dunwu.javadb.mongodb.springboot.SpringBootDataMongodbApplication; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; diff --git a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/util/BlogPostInitializer.java b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/util/BlogPostInitializer.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/util/BlogPostInitializer.java rename to codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/util/BlogPostInitializer.java diff --git a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/util/ConsoleResultPrinter.java b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/util/ConsoleResultPrinter.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/util/ConsoleResultPrinter.java rename to codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/util/ConsoleResultPrinter.java diff --git a/codes/javadb/javadb-mongodb/src/test/resources/spring-blog.atom.json b/codes/javadb/mongodb/src/test/resources/spring-blog.atom.json similarity index 100% rename from codes/javadb/javadb-mongodb/src/test/resources/spring-blog.atom.json rename to codes/javadb/mongodb/src/test/resources/spring-blog.atom.json diff --git a/codes/javadb/mysql/pom.xml b/codes/javadb/mysql/pom.xml new file mode 100644 index 00000000..c314b060 --- /dev/null +++ b/codes/javadb/mysql/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.6.3 + + + io.github.dunwu + javadb-mysql + 1.0.0 + jar + + + + org.springframework.boot + spring-boot-starter-jdbc + + + org.springframework.boot + spring-boot-starter-test + test + + + mysql + mysql-connector-java + + + org.projectlombok + lombok + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/codes/javadb/javadb-mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/SpringBootDataJdbcApplication.java b/codes/javadb/mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/SpringBootDataJdbcApplication.java similarity index 100% rename from codes/javadb/javadb-mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/SpringBootDataJdbcApplication.java rename to codes/javadb/mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/SpringBootDataJdbcApplication.java diff --git a/codes/javadb/javadb-mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/User.java b/codes/javadb/mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/User.java similarity index 100% rename from codes/javadb/javadb-mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/User.java rename to codes/javadb/mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/User.java diff --git a/codes/javadb/javadb-mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/UserDao.java b/codes/javadb/mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/UserDao.java similarity index 100% rename from codes/javadb/javadb-mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/UserDao.java rename to codes/javadb/mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/UserDao.java diff --git a/codes/javadb/javadb-mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/UserDaoExecutor.java b/codes/javadb/mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/UserDaoExecutor.java similarity index 100% rename from codes/javadb/javadb-mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/UserDaoExecutor.java rename to codes/javadb/mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/UserDaoExecutor.java diff --git a/codes/javadb/javadb-mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/UserDaoImpl.java b/codes/javadb/mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/UserDaoImpl.java similarity index 100% rename from codes/javadb/javadb-mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/UserDaoImpl.java rename to codes/javadb/mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/UserDaoImpl.java diff --git a/codes/javadb/javadb-mysql/src/main/resources/application.properties b/codes/javadb/mysql/src/main/resources/application.properties similarity index 100% rename from codes/javadb/javadb-mysql/src/main/resources/application.properties rename to codes/javadb/mysql/src/main/resources/application.properties diff --git a/codes/javadb/javadb-mysql/src/main/resources/banner.txt b/codes/javadb/mysql/src/main/resources/banner.txt similarity index 100% rename from codes/javadb/javadb-mysql/src/main/resources/banner.txt rename to codes/javadb/mysql/src/main/resources/banner.txt diff --git a/codes/javadb/javadb-mysql/src/main/resources/logback.xml b/codes/javadb/mysql/src/main/resources/logback.xml similarity index 100% rename from codes/javadb/javadb-mysql/src/main/resources/logback.xml rename to codes/javadb/mysql/src/main/resources/logback.xml diff --git a/codes/javadb/javadb-mysql/src/main/resources/sql/data.sql b/codes/javadb/mysql/src/main/resources/sql/data.sql similarity index 100% rename from codes/javadb/javadb-mysql/src/main/resources/sql/data.sql rename to codes/javadb/mysql/src/main/resources/sql/data.sql diff --git a/codes/javadb/javadb-mysql/src/main/resources/sql/schema.sql b/codes/javadb/mysql/src/main/resources/sql/schema.sql similarity index 100% rename from codes/javadb/javadb-mysql/src/main/resources/sql/schema.sql rename to codes/javadb/mysql/src/main/resources/sql/schema.sql diff --git a/codes/javadb/javadb-mysql/src/test/java/io/github/dunwu/javadb/mysql/springboot/MysqlDemoTest.java b/codes/javadb/mysql/src/test/java/io/github/dunwu/javadb/mysql/springboot/MysqlDemoTest.java similarity index 91% rename from codes/javadb/javadb-mysql/src/test/java/io/github/dunwu/javadb/mysql/springboot/MysqlDemoTest.java rename to codes/javadb/mysql/src/test/java/io/github/dunwu/javadb/mysql/springboot/MysqlDemoTest.java index c8a2236d..6b0f2dbf 100644 --- a/codes/javadb/javadb-mysql/src/test/java/io/github/dunwu/javadb/mysql/springboot/MysqlDemoTest.java +++ b/codes/javadb/mysql/src/test/java/io/github/dunwu/javadb/mysql/springboot/MysqlDemoTest.java @@ -6,11 +6,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.sql.*; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; /** * Mysql 测试例 - * @author Zhang Peng + * @author Zhang Peng * @see https://dev.mysql.com/doc/connector-j/5.1/en/ */ public class MysqlDemoTest { diff --git a/codes/javadb/javadb-mysql/src/test/java/io/github/dunwu/javadb/mysql/springboot/SpringBootDataJdbcTest.java b/codes/javadb/mysql/src/test/java/io/github/dunwu/javadb/mysql/springboot/SpringBootDataJdbcTest.java similarity index 100% rename from codes/javadb/javadb-mysql/src/test/java/io/github/dunwu/javadb/mysql/springboot/SpringBootDataJdbcTest.java rename to codes/javadb/mysql/src/test/java/io/github/dunwu/javadb/mysql/springboot/SpringBootDataJdbcTest.java diff --git a/codes/javadb/pom.xml b/codes/javadb/pom.xml index f5b53999..7e45ab8c 100644 --- a/codes/javadb/pom.xml +++ b/codes/javadb/pom.xml @@ -1,6 +1,6 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 io.github.dunwu @@ -8,13 +8,718 @@ 1.0.0 pom + + 1.8 + ${java.version} + ${java.version} + UTF-8 + UTF-8 + 3.5.4 + + + 2.2.2.RELEASE + 3.3.6 + + + 1.6.2 + 2.3.3 + 1.2 + + + 4.2.0 + 1.1.24 + 1.26.6 + 3.5.6 + 1.3.5 + 3.4.2 + 2.0.2 + 4.0.4 + 5.1.8 + 6.2.5.Final + 3.8.7 + 2.4.15 + + + 6.4.1 + 1.2.70 + 2.56 + 1.18.16 + 3.16.1 + 0.10.2 + + + 1.12 + 4.3 + 1.20 + 2.7 + 1.6 + 1.4 + 2.7 + 3.9 + 3.6.1 + 2.8.0 + 1.10.0 + 31.1-jre + 5.8.9 + + + 4.1.2 + 3.4.2 + 7.11.0.Final + 0.11.1 + 1.14.2 + 5.6.0 + 2.4.7.Final + 1.7.1 + 2.9.2 + 1.5.22 + 1.9.0.RELEASE + 0.4.19 + 3.4.0 + 3.1.1 + 3.24.0 + + + + 3.0.0 + 3.2.0 + 3.1.1 + 3.1.0 + 3.8.0 + 3.1.2 + 2.8.2 + 3.0.0-M2 + 2.22.2 + 3.2.0 + 2.5.2 + 3.2.0 + 3.2.0 + 3.2.0 + 3.12.0 + 3.1.0 + 3.2.3 + 3.9.0 + 3.2.1 + 2.22.2 + 3.2.3 + + + 3.0.0 + 1.6.0 + 1.2.2 + 2.7 + 1.0.2 + + + 1.6 + 1.6.8 + + + 1.4.0 + + - javadb-h2 - javadb-hbase - javadb-mysql - javadb-redis - javadb-sqlite - javadb-mongodb - javadb-elasticsearch + h2 + hbase + mysql + redis + sqlite + mongodb + elasticsearch + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + + javax.servlet.jsp + javax.servlet.jsp-api + ${javax.servlet.jsp-api.version} + + + javax.servlet.jsp.jstl + jstl + ${javax.servlet.jsp.jstl.version} + + + com.sun.mail + javax.mail + ${javax.mail.version} + + + + + + org.apache.curator + curator-framework + ${curator.version} + + + org.apache.curator + curator-test + ${curator.version} + + + com.alibaba + druid-spring-boot-starter + ${druid.version} + + + com.alibaba + druid + ${druid.version} + + + com.github.tobato + fastdfs-client + ${fastdfs.version} + + + org.mybatis + mybatis + ${mybatis.version} + + + org.mybatis.generator + mybatis-generator-core + ${mybatis-generator.version} + + + org.mybatis + mybatis-spring + ${mybatis-spring.version} + + + com.baomidou + mybatis-plus + ${mybatis-plus.version} + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis-plus.version} + + + com.baomidou + mybatis-plus-core + ${mybatis-plus.version} + + + com.baomidou + mybatis-plus-extension + ${mybatis-plus.version} + + + com.baomidou + mybatis-plus-generator + ${mybatis-plus.version} + + + tk.mybatis + mapper + ${mybatis.mapper.version} + + + com.github.pagehelper + pagehelper + ${mybatis.pagehelper.version} + + + p6spy + p6spy + ${p6spy.version} + + + org.hibernate.validator + hibernate-validator + ${hibernate-validator.version} + + + org.apache.hbase + hbase-client + ${hbase.version} + + + + + + com.github.dozermapper + dozer-core + ${dozer.version} + + + com.github.dozermapper + dozer-spring4 + ${dozer.version} + + + com.github.dozermapper + dozer-spring-boot-starter + ${dozer.version} + + + + org.reflections + reflections + ${reflections.version} + + + com.alibaba + fastjson + ${fastjson.version} + + + org.projectlombok + lombok + ${lombok.version} + + + com.google.protobuf + protobuf-java + ${protobuf-java.version} + + + de.ruedigermoeller + fst + ${fst.version} + + + + + commons-codec + commons-codec + ${commons-codec.version} + + + org.apache.commons + commons-configuration2 + ${commons-configuration2.version} + + + org.apache.commons + commons-collections4 + ${commons-collections4.version} + + + org.apache.commons + commons-compress + ${commons-compress.version} + + + org.apache.commons + commons-csv + ${commons-csv.version} + + + commons-fileupload + commons-fileupload + ${commons-fileupload.version} + + + commons-io + commons-io + ${commons-io.version} + + + org.apache.commons + commons-lang3 + ${commons-lang3.version} + + + org.apache.commons + commons-math3 + ${commons-math3.version} + + + org.apache.commons + commons-pool2 + ${commons-pool2.version} + + + org.apache.commons + commons-text + ${commons-text.version} + + + com.google.guava + guava + ${guava.version} + + + cn.hutool + hutool-all + ${hutool.version} + + + + + org.apache.poi + poi + ${poi.version} + + + org.apache.poi + poi-ooxml + ${poi.version} + + + xml-apis + xml-apis + 1.4.01 + + + org.apache.xmlbeans + xmlbeans + 3.1.0 + + + com.lmax + disruptor + ${disruptor.version} + + + org.drools + drools-core + ${drools.version} + + + org.drools + drools-compiler + ${drools.version} + + + org.drools + drools-decisiontables + ${drools.version} + + + org.drools + drools-templates + ${drools.version} + + + org.kie + kie-api + ${drools.version} + + + io.jsonwebtoken + jjwt-api + ${jjwt.version} + + + io.jsonwebtoken + jjwt-impl + ${jjwt.version} + + + io.jsonwebtoken + jjwt-jackson + ${jjwt.version} + + + org.jsoup + jsoup + ${jsoup.version} + + + org.mvel + mvel2 + ${mvel.version} + + + org.apache.shiro + shiro-spring + ${shiro.version} + + + io.springfox + springfox-swagger2 + ${swagger.ui} + + + io.springfox + springfox-swagger-ui + ${swagger.ui} + + + io.swagger + swagger-annotations + ${swagger-annotations.version} + + + com.spring4all + swagger-spring-boot-starter + ${swagger-spring-boot-starter.version} + + + net.coobird + thumbnailator + ${thumbnailator.version} + + + com.google.zxing + core + ${zxing.version} + + + com.alibaba + easyexcel + ${easyexcel.version} + + + + com.github.javaparser + javaparser-symbol-solver-core + ${javaparser.version} + + + + org.junit.jupiter + junit-jupiter + ${junit-jupiter.version} + + + + + org.apache.hadoop + hadoop-hdfs + ${hadoop.version} + + + org.apache.hadoop + hadoop-auth + ${hadoop.version} + + + org.apache.hadoop + hadoop-common + ${hadoop.version} + + + org.apache.hadoop + hadoop-client + ${hadoop.version} + + + commons-logging + commons-logging + + + jsr305 + com.google.code.findbugs + + + nimbus-jose-jwt + com.nimbusds + + + curator-client + org.apache.curator + + + + + + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + ${maven-antrun-plugin.version} + + + org.apache.maven.plugins + maven-assembly-plugin + ${maven-assembly-plugin.version} + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${maven-checkstyle-plugin.version} + + + org.apache.maven.plugins + maven-clean-plugin + ${maven-clean-plugin.version} + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + + org.apache.maven.plugins + maven-dependency-plugin + ${maven-dependency-plugin.version} + + + org.apache.maven.plugins + maven-deploy-plugin + ${maven-deploy-plugin.version} + + + org.apache.maven.plugins + maven-enforcer-plugin + ${maven-enforcer-plugin.version} + + + org.apache.maven.plugins + maven-failsafe-plugin + ${maven-failsafe-plugin.version} + + + org.apache.maven.plugins + maven-gpg-plugin + ${maven-gpg-plugin.version} + + + org.apache.maven.plugins + maven-help-plugin + ${maven-help-plugin.version} + + + org.apache.maven.plugins + maven-install-plugin + ${maven-install-plugin.version} + + + org.apache.maven.plugins + maven-invoker-plugin + ${maven-invoker-plugin.version} + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + + org.apache.maven.plugins + maven-pmd-plugin + ${maven-pmd-plugin.version} + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + + org.apache.maven.plugins + maven-shade-plugin + ${maven-shade-plugin.version} + + + org.apache.maven.plugins + maven-site-plugin + ${maven-site-plugin.version} + + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + org.apache.maven.plugins + maven-war-plugin + ${maven-war-plugin.version} + + + + + org.codehaus.mojo + build-helper-maven-plugin + ${build-helper-maven-plugin.version} + + + org.codehaus.mojo + exec-maven-plugin + ${exec-maven-plugin.version} + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + + org.codehaus.mojo + versions-maven-plugin + ${versions-maven-plugin.version} + + + org.codehaus.mojo + xml-maven-plugin + ${xml-maven-plugin.version} + + + + + org.basepom.maven + duplicate-finder-maven-plugin + ${duplicate-finder-maven-plugin.version} + + + + + diff --git a/codes/javadb/redis/pom.xml b/codes/javadb/redis/pom.xml new file mode 100644 index 00000000..3150ffba --- /dev/null +++ b/codes/javadb/redis/pom.xml @@ -0,0 +1,67 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.7.18 + + + io.github.dunwu + javadb-redis + 1.0.0 + jar + + + 3.7.2 + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.springframework.boot + spring-boot-starter-json + + + org.springframework.boot + spring-boot-starter-test + test + + + + cn.hutool + hutool-all + 5.8.27 + + + org.projectlombok + lombok + + + + + redis.clients + jedis + + + org.redisson + redisson + 3.29.0 + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/codes/javadb/javadb-redis/src/main/java/io/github/dunwu/javadb/redis/springboot/RedisAutoConfiguration.java b/codes/javadb/redis/src/main/java/io/github/dunwu/javadb/redis/springboot/RedisAutoConfiguration.java similarity index 77% rename from codes/javadb/javadb-redis/src/main/java/io/github/dunwu/javadb/redis/springboot/RedisAutoConfiguration.java rename to codes/javadb/redis/src/main/java/io/github/dunwu/javadb/redis/springboot/RedisAutoConfiguration.java index 6ea387bf..eb7c40e1 100644 --- a/codes/javadb/javadb-redis/src/main/java/io/github/dunwu/javadb/redis/springboot/RedisAutoConfiguration.java +++ b/codes/javadb/redis/src/main/java/io/github/dunwu/javadb/redis/springboot/RedisAutoConfiguration.java @@ -1,14 +1,24 @@ package io.github.dunwu.javadb.redis.springboot; +import cn.hutool.core.util.StrUtil; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.*; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.ListOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.SetOperations; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.data.redis.core.ZSetOperations; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @@ -22,6 +32,19 @@ public class RedisAutoConfiguration { @Autowired private ObjectMapper objectMapper; + @Value("${spring.redis.host:localhost}") + private String host; + + @Value("${spring.redis.port:6379}") + private String port; + + @Bean + public RedissonClient redissonClient() { + Config config = new Config(); + config.useSingleServer().setAddress(StrUtil.format("redis://{}:{}", host, port)); + return Redisson.create(config); + } + @Bean public HashOperations hashOperations(RedisTemplate redisTemplate) { return redisTemplate.opsForHash(); @@ -44,7 +67,6 @@ public RedisTemplate redisTemplate(RedisConnectionFactory factor // 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式) Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class); serializer.setObjectMapper(objectMapper); - RedisTemplate template = new RedisTemplate<>(); // 配置连接工厂 template.setConnectionFactory(factory); diff --git a/codes/javadb/javadb-redis/src/main/java/io/github/dunwu/javadb/redis/springboot/SpringBootDataRedisApplication.java b/codes/javadb/redis/src/main/java/io/github/dunwu/javadb/redis/springboot/SpringBootDataRedisApplication.java similarity index 100% rename from codes/javadb/javadb-redis/src/main/java/io/github/dunwu/javadb/redis/springboot/SpringBootDataRedisApplication.java rename to codes/javadb/redis/src/main/java/io/github/dunwu/javadb/redis/springboot/SpringBootDataRedisApplication.java diff --git a/codes/javadb/javadb-redis/src/main/java/io/github/dunwu/javadb/redis/springboot/data/User.java b/codes/javadb/redis/src/main/java/io/github/dunwu/javadb/redis/springboot/data/User.java similarity index 100% rename from codes/javadb/javadb-redis/src/main/java/io/github/dunwu/javadb/redis/springboot/data/User.java rename to codes/javadb/redis/src/main/java/io/github/dunwu/javadb/redis/springboot/data/User.java diff --git a/codes/javadb/javadb-redis/src/main/java/io/github/dunwu/javadb/redis/springboot/data/UserService.java b/codes/javadb/redis/src/main/java/io/github/dunwu/javadb/redis/springboot/data/UserService.java similarity index 100% rename from codes/javadb/javadb-redis/src/main/java/io/github/dunwu/javadb/redis/springboot/data/UserService.java rename to codes/javadb/redis/src/main/java/io/github/dunwu/javadb/redis/springboot/data/UserService.java diff --git a/codes/javadb/javadb-redis/src/main/java/io/github/dunwu/javadb/redis/springboot/data/UserServiceImpl.java b/codes/javadb/redis/src/main/java/io/github/dunwu/javadb/redis/springboot/data/UserServiceImpl.java similarity index 100% rename from codes/javadb/javadb-redis/src/main/java/io/github/dunwu/javadb/redis/springboot/data/UserServiceImpl.java rename to codes/javadb/redis/src/main/java/io/github/dunwu/javadb/redis/springboot/data/UserServiceImpl.java diff --git a/codes/javadb/javadb-redis/src/main/resources/application.properties b/codes/javadb/redis/src/main/resources/application.properties similarity index 100% rename from codes/javadb/javadb-redis/src/main/resources/application.properties rename to codes/javadb/redis/src/main/resources/application.properties diff --git a/codes/javadb/javadb-redis/src/main/resources/banner.txt b/codes/javadb/redis/src/main/resources/banner.txt similarity index 100% rename from codes/javadb/javadb-redis/src/main/resources/banner.txt rename to codes/javadb/redis/src/main/resources/banner.txt diff --git a/codes/javadb/javadb-redis/src/main/resources/logback.xml b/codes/javadb/redis/src/main/resources/logback.xml similarity index 100% rename from codes/javadb/javadb-redis/src/main/resources/logback.xml rename to codes/javadb/redis/src/main/resources/logback.xml diff --git a/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/RedissonStandaloneTest.java b/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/RedissonStandaloneTest.java new file mode 100644 index 00000000..ed3c13a3 --- /dev/null +++ b/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/RedissonStandaloneTest.java @@ -0,0 +1,104 @@ +package io.github.dunwu.javadb.redis; + +import cn.hutool.core.thread.ThreadUtil; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.redisson.api.RBucket; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * @author Zhang Peng + * @since 2018/6/19 + */ +@Slf4j +public class RedissonStandaloneTest { + + private static RedissonClient redissonClient; + + static { + ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:redisson-standalone.xml"); + redissonClient = (RedissonClient) applicationContext.getBean("standalone"); + } + + @Test + @DisplayName("测试连接") + public void testRedissonConnect() { + // 首先获取redis中的key-value对象,key不存在没关系 + RBucket keyObject = redissonClient.getBucket("key"); + // 如果key存在,就设置key的值为新值value + // 如果key不存在,就设置key的值为value + keyObject.set("value"); + String value = keyObject.get(); + System.out.println("value=" + value); + } + + @Test + @DisplayName("分布式锁测试") + public void testLock() { + // 两个线程任务都是不断再尝试获取或,直到成功获取锁后才推出任务 + // 第一个线程获取到锁后,第二个线程需要等待 5 秒超时后才能获取到锁 + CountDownLatch latch = new CountDownLatch(2); + ExecutorService executorService = ThreadUtil.newFixedExecutor(2, "获取锁", true); + executorService.submit(new Task(latch)); + executorService.submit(new Task(latch)); + + try { + latch.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + // 输出: + // 17:59:25.896 [获取锁1] [INFO ] i.g.d.j.redis.RedissonStandaloneTest.run - + // 获取分布式锁成功 + // 17:59:26.888 [获取锁0] [WARN ] i.g.d.j.redis.RedissonStandaloneTest.run - + // 获取分布式锁失败 + // 17:59:27.889 [获取锁0] [WARN ] i.g.d.j.redis.RedissonStandaloneTest.run - + // 获取分布式锁失败 + // 17:59:28.891 [获取锁0] [WARN ] i.g.d.j.redis.RedissonStandaloneTest.run - + // 获取分布式锁失败 + // 17:59:29.892 [获取锁0] [WARN ] i.g.d.j.redis.RedissonStandaloneTest.run - + // 获取分布式锁失败 + // 17:59:30.895 [获取锁0] [WARN ] i.g.d.j.redis.RedissonStandaloneTest.run - + // 获取分布式锁失败 + // 17:59:30.896 [获取锁0] [INFO ] i.g.d.j.redis.RedissonStandaloneTest.run - + // 获取分布式锁成功 + + static class Task implements Runnable { + + private CountDownLatch latch; + + public Task(CountDownLatch latch) { + this.latch = latch; + } + + @Override + public void run() { + while (true) { + RLock lock = redissonClient.getLock("test_lock"); + try { + boolean isLock = lock.tryLock(1, 5, TimeUnit.SECONDS); + if (isLock) { + log.info("获取分布式锁成功"); + break; + } else { + log.warn("获取分布式锁失败"); + } + } catch (Exception e) { + log.error("获取分布式锁异常", e); + } + } + latch.countDown(); + } + + } + +} diff --git a/codes/javadb/javadb-redis/src/test/java/io/github/dunwu/javadb/JedisDemoTest.java b/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/JedisDemoTest.java similarity index 93% rename from codes/javadb/javadb-redis/src/test/java/io/github/dunwu/javadb/JedisDemoTest.java rename to codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/JedisDemoTest.java index 7d3a0cd4..a48d1ea2 100644 --- a/codes/javadb/javadb-redis/src/test/java/io/github/dunwu/javadb/JedisDemoTest.java +++ b/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/JedisDemoTest.java @@ -1,11 +1,10 @@ -package io.github.dunwu.javadb; +package io.github.dunwu.javadb.redis.jedis; +import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import redis.clients.jedis.Jedis; import redis.clients.jedis.exceptions.JedisConnectionException; @@ -15,9 +14,11 @@ /** * Jedis 测试例 - * @author Zhang Peng + * + * @author Zhang Peng * @see https://github.com/xetorthio/jedis */ +@Slf4j public class JedisDemoTest { private static final String REDIS_HOST = "localhost"; @@ -26,8 +27,6 @@ public class JedisDemoTest { private static Jedis jedis = null; - private static Logger logger = LoggerFactory.getLogger(JedisDemoTest.class); - @BeforeAll public static void beforeClass() { // Jedis 有多种构造方法,这里选用最简单的一种情况 @@ -36,7 +35,7 @@ public static void beforeClass() { // 触发 ping 命令 try { jedis.ping(); - logger.debug("jedis 连接成功。"); + log.debug("jedis 连接成功。"); } catch (JedisConnectionException e) { e.printStackTrace(); } @@ -46,7 +45,7 @@ public static void beforeClass() { public static void afterClass() { if (null != jedis) { jedis.close(); - logger.debug("jedis 关闭连接。"); + log.debug("jedis 关闭连接。"); } } diff --git a/codes/javadb/javadb-redis/src/test/java/io/github/dunwu/javadb/JedisPoolDemoTest.java b/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/JedisPoolDemoTest.java similarity index 88% rename from codes/javadb/javadb-redis/src/test/java/io/github/dunwu/javadb/JedisPoolDemoTest.java rename to codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/JedisPoolDemoTest.java index 193edfcd..e52d65ba 100644 --- a/codes/javadb/javadb-redis/src/test/java/io/github/dunwu/javadb/JedisPoolDemoTest.java +++ b/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/JedisPoolDemoTest.java @@ -1,13 +1,11 @@ -package io.github.dunwu.javadb; +package io.github.dunwu.javadb.redis.jedis; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; @@ -16,9 +14,8 @@ import java.util.Set; /** - * @author Zhang Peng + * @author Zhang Peng */ -@RunWith(SpringJUnit4ClassRunner.class) @ActiveProfiles("dev") @ContextConfiguration(locations = {"classpath:/applicationContext.xml"}) public class JedisPoolDemoTest { diff --git a/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankDemo.java b/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankDemo.java new file mode 100644 index 00000000..f1cebb31 --- /dev/null +++ b/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankDemo.java @@ -0,0 +1,667 @@ +package io.github.dunwu.javadb.redis.jedis.rank; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollectionUtil; +import lombok.extern.slf4j.Slf4j; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.Pipeline; +import redis.clients.jedis.Response; +import redis.clients.jedis.Tuple; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 利用 sorted set 实现排行榜示例 + * + * @author Zhang Peng + * @date 2022-05-26 + */ +@Slf4j +public class RankDemo { + + public static final boolean isRegionRankEnabled = true; + private final Jedis jedis; + + public RankDemo(Jedis jedis) { + this.jedis = jedis; + } + + // ================================================================================ + // 排行榜公共常量、方法 + // ================================================================================ + + /** + * 第一名 + */ + static final int FIRST = 0; + /** + * 头部排行榜长度 + */ + static final int HEAD_RANK_LENGTH = 200; + /** + * 总排行榜长度 + */ + static final long TOTAL_RANK_LENGTH = 1000; + /** + * 排行榜第一个分区长度 + */ + static final int FIRST_REGION_LEN = 1; + /** + * 普通分区长度 + */ + static final int COMMON_REGION_LEN = 50; + /** + * 排行榜最后一名位置 + */ + static final long RANK_END_OFFSET = -TOTAL_RANK_LENGTH - 1; + + /** + * 根据 member,查询成员在排行榜中的排名,从 0 开始计数 + *

+ * 如果成员不在排行榜,则统一返回 {@link #TOTAL_RANK_LENGTH} + * + * @param member zset 成员 + * @return / + */ + public RankElement getRankByMember(String member) { + if (isRegionRankEnabled) { + RankRegionElement element = getRankByMemberWithRegions(member); + if (element == null) { + return null; + } + return BeanUtil.toBean(element, RankElement.class); + } else { + // 排行榜采用不分区方案 + return getRankByMemberWithNoRegions(member); + } + } + + /** + * 根据从总排名的范围获取元素列表 + * + * @param begin 总排名中的起始位置 + * @param end 总排名中的结束位置 + * @param isAsc true:从低到高 / false:从高到低 + * @return / + */ + public List getRankElementList(long begin, long end, boolean isAsc) { + + if (begin < 0 || end >= TOTAL_RANK_LENGTH) { + log.error("【排行榜】请求范围 begin = {}, end = {} 超出排行榜实际范围", begin, end); + return null; + } + + if (isRegionRankEnabled) { + // 排行榜采用分区方案 + List elementList = getRankElementListWithRegions(begin, end, isAsc); + if (CollectionUtil.isEmpty(elementList)) { + return null; + } + return elementList.stream().map(i -> BeanUtil.toBean(i, RankElement.class)).collect(Collectors.toList()); + } else { + // 排行榜采用不分区方案 + return getRankElementListWithNoRegions(begin, end, isAsc); + } + } + + /** + * 更新排行榜 + * + * @param member 榜单成员 + * @param score 榜单成员分值 + */ + public void saveRank(String member, double score) { + if (isRegionRankEnabled) { + // 排行榜采用分区方案 + saveRankWithRegions(member, score); + } else { + // 排行榜采用不分区方案 + saveRankWithNoRegions(member, score); + } + } + + + // ================================================================================ + // 排行榜【不分区】方案 + // ================================================================================ + + /** + * 排行榜缓存前缀 + */ + static final String RANK = "rank"; + + /** + * 根据 member,查询成员在排行榜中的排名,从 0 开始计数 + *

+ * 如果成员不在排行榜,则统一返回 {@link #TOTAL_RANK_LENGTH} + * + * @param member zset 成员 + * @return / + */ + public RankElement getRankByMemberWithNoRegions(String member) { + Pipeline pipeline = jedis.pipelined(); + Response rankResponse = pipeline.zrevrank(RANK, member); + Response scoreResponse = pipeline.zscore(RANK, member); + pipeline.syncAndReturnAll(); + + if (rankResponse == null || scoreResponse == null) { + return null; + } + + Long rank = rankResponse.get(); + Double score = scoreResponse.get(); + if (rank == null || score == null) { + return null; + } + return new RankElement(member, score, rank); + } + + /** + * 根据从总排名的范围获取元素列表 + * + * @param begin 总排名中的起始位置 + * @param end 总排名中的结束位置 + * @param isAsc true:从低到高 / false:从高到低 + * @return / + */ + private List getRankElementListWithNoRegions(long begin, long end, boolean isAsc) { + Set tuples; + if (isAsc) { + tuples = jedis.zrangeWithScores(RANK, begin, end); + } else { + tuples = jedis.zrevrangeWithScores(RANK, begin, end); + } + + if (CollectionUtil.isEmpty(tuples)) { + return null; + } + + long rank = 0; + List list = new ArrayList<>(); + for (Tuple tuple : tuples) { + RankElement elementVo = new RankElement(tuple.getElement(), tuple.getScore(), rank++); + list.add(elementVo); + } + return list; + } + + /** + * 更新【不分区】排行榜 + * + * @param member 榜单成员 + * @param score 榜单成员分值 + */ + private void saveRankWithNoRegions(final String member, final double score) { + Pipeline pipeline = jedis.pipelined(); + pipeline.zadd(RANK, score, member); + pipeline.zremrangeByRank(RANK, 0, RANK_END_OFFSET); + pipeline.sync(); + } + + + // ================================================================================ + // 排行榜【分区】方案 + // ================================================================================ + + /** + * 排行榜缓存前缀 + */ + static final String RANK_PREFIX = "rank:"; + /** + * 排行榜所有分区的分区号(分区号实际上就是该分区排名第一元素的实际排名) + */ + static final List REGIONS = getAllRankRegions(); + + /** + * 根据 member,查询成员在排行榜中的排名,从 0 开始计数 + *

+ * 如果成员不在排行榜,则统一返回 {@link #TOTAL_RANK_LENGTH} + * + * @param member zset 成员 + * @return / + */ + public RankRegionElement getRankByMemberWithRegions(String member) { + + // pipeline 合并查询 + List>> responseList = new LinkedList<>(); + Pipeline pipeline = jedis.pipelined(); + for (RankRegion region : REGIONS) { + Map> map = new HashMap<>(2); + map.put("rank", pipeline.zrevrank(region.getRegionKey(), member)); + map.put("score", pipeline.zscore(region.getRegionKey(), member)); + responseList.add(map); + } + pipeline.syncAndReturnAll(); + + if (CollectionUtil.isEmpty(responseList)) { + log.error("【排行榜】getRankByMemberWithRegions pipeline 结果为空!"); + return null; + } + + // 处理 pipeline 查询结果 + for (int i = 0; i < responseList.size(); i++) { + Map> map = responseList.get(i); + Response rankResponse = map.get("rank"); + Response scoreResponse = map.get("score"); + if (rankResponse == null && scoreResponse == null) { + continue; + } + + Long rank = (Long) rankResponse.get(); + Double score = (Double) scoreResponse.get(); + if (rank == null || score == null) { + continue; + } + + RankRegion region = REGIONS.get(i); + long totalRank = getTotalRank(region.getRegionNo(), rank); + return new RankRegionElement(region.getRegionNo(), region.getRegionKey(), member, score, rank, totalRank); + } + + return null; + } + + /** + * 根据从总排名的范围获取元素列表 + * + * @param begin 总排名中的起始位置 + * @param end 总排名中的结束位置 + * @param isAsc true:从低到高 / false:从高到低 + * @return / + */ + public List getRankElementListWithRegions(long begin, long end, boolean isAsc) { + if (begin < 0 || end >= TOTAL_RANK_LENGTH) { + log.error("【排行榜】请求范围 begin = {}, end = {} 超出排行榜实际范围", begin, end); + return null; + } + + List>> responseList = new LinkedList<>(); + Pipeline pipeline = jedis.pipelined(); + for (RankRegion region : REGIONS) { + + // 计算当前分区的起始、结束位置 + long regionBegin = region.getRegionNo(); + long regionEnd = region.getRegionNo() + region.getMaxSize() - 1; + + if (regionBegin > end) { + break; + } + + if (regionEnd < begin) { + continue; + } + + // 计算查询区间 + RankRegionElement firstElement = getRegionRank(Math.max(regionBegin, begin)); + RankRegionElement lastElement = getRegionRank(Math.min(regionEnd, end)); + if (firstElement == null || lastElement == null) { + log.error("【排行榜】查询区间错误!"); + break; + } + long first = firstElement.getRank(); + long last = lastElement.getRank(); + + if (isAsc) { + // 从低到高排名 + responseList.add(pipeline.zrangeWithScores(region.getRegionKey(), first, last)); + } else { + // 从高到低排名 + responseList.add(pipeline.zrevrangeWithScores(region.getRegionKey(), first, last)); + } + } + pipeline.syncAndReturnAll(); + + return parseZsetTuples(responseList); + } + + /** + * 解析 pipeline 返回的 zset 响应结果,转化为 List + */ + private List parseZsetTuples(List>> responseList) { + + List finalList = new LinkedList<>(); + if (CollectionUtil.isEmpty(responseList)) { + return finalList; + } + + for (int i = 0; i < responseList.size(); i++) { + + Response> response = responseList.get(i); + if (response == null || response.get() == null) { + continue; + } + + Set tuples = response.get(); + if (CollectionUtil.isEmpty(tuples)) { + continue; + } + + long regionRank = 0; + RankRegion region = REGIONS.get(i); + List list = new ArrayList<>(); + for (Tuple tuple : tuples) { + long totalRank = getTotalRank(region.getRegionNo(), regionRank); + RankRegionElement rankElementVo = new RankRegionElement(region.getRegionNo(), region.getRegionKey(), + tuple.getElement(), tuple.getScore(), + regionRank, totalRank); + list.add(rankElementVo); + regionRank++; + } + if (CollectionUtil.isNotEmpty(list)) { + finalList.addAll(list); + } + } + return finalList; + } + + /** + * 获取指定分区中指定排名的信息 + * + * @param region 指定榜单分区 + * @param rank 分区中的排名 + * @param isAsc true:从低到高 / false:从高到低 + * @return 匹配排名的信息 + */ + private RankRegionElement getRankElementInRegion(RankRegion region, long rank, boolean isAsc) { + Set tuples; + if (isAsc) { + // 从低到高排名 + tuples = jedis.zrangeWithScores(region.getRegionKey(), rank, rank); + } else { + // 从高到低排名 + tuples = jedis.zrevrangeWithScores(region.getRegionKey(), rank, rank); + } + + if (CollectionUtil.isEmpty(tuples)) { + return null; + } + + Tuple tuple = tuples.iterator().next(); + if (tuple == null) { + return null; + } + + long regionRank = rank; + if (isAsc) { + regionRank = region.getMaxSize() - 1; + } + + long totalRank = getTotalRank(region.getRegionNo(), rank); + return new RankRegionElement(region.getRegionNo(), region.getRegionKey(), tuple.getElement(), tuple.getScore(), + regionRank, totalRank); + } + + /** + * 获取最后一名 + */ + private RankRegionElement getMinRankElementInRegion(RankRegion region) { + return getRankElementInRegion(region, FIRST, true); + } + + /** + * 获取第一名 + */ + private RankRegionElement getMaxRankElementInRegion(RankRegion region) { + return getRankElementInRegion(region, FIRST, false); + } + + /** + * 更新【分区】排行榜 + * + * @param member 榜单成员 + * @param score 榜单成员分值 + */ + public void saveRankWithRegions(final String member, final double score) { + + List regions = new LinkedList<>(REGIONS); + + // member 的原始排名 + RankRegionElement oldRank = null; + for (RankRegion region : regions) { + + region.setSize(jedis.zcard(region.getRegionKey())); + region.setMin(getMinRankElementInRegion(region)); + region.setMax(getMaxRankElementInRegion(region)); + + // 查找 member 是否已经在榜单中 + Long rank = jedis.zrevrank(region.getRegionKey(), member); + if (rank != null) { + jedis.zrevrangeWithScores(region.getRegionKey(), rank, rank); + oldRank = getRankElementInRegion(region, rank, false); + } + } + + Pipeline pipeline = jedis.pipelined(); + // 如果成员已入榜,并且无任何变化,无需任何修改 + if (oldRank != null) { + if (oldRank.getMember().equals(member) && oldRank.getScore() == score) { + log.info("【排行榜】member = {}, score = {} 值没有变化,无需任何修改", member, score); + return; + } + + // 成员已经在 10W 排行榜中,先将旧记录自适应删除 + if (oldRank.getTotalRank() < TOTAL_RANK_LENGTH) { + log.info("【排行榜】member = {} 已入 TOP {},rank = {}", member, TOTAL_RANK_LENGTH, oldRank); + // 先将原始排名记录删除,并动态调整所有分区 + deleteWithAutoAdjust(oldRank, regions, pipeline); + } + } + + // 将成员的记录插入到合适的分区中,并自适应调整各分区 + addWithAutoAdjust(member, score, regions, pipeline); + pipeline.syncAndReturnAll(); + + long newRank = TOTAL_RANK_LENGTH; + for (RankRegion region : regions) { + Long rank = jedis.zrevrank(region.getRegionKey(), member); + if (rank != null) { + newRank = getTotalRank(region.getRegionNo(), rank); + break; + } + } + log.info("【排行榜】member = {}, score = {}, 排名:{}", member, score, newRank); + + if (oldRank != null && oldRank.getTotalRank() < HEAD_RANK_LENGTH && newRank >= HEAD_RANK_LENGTH) { + log.info("【排行榜】member = {} 跌出 TOP {},oldRank = {}, newRank = {}", member, HEAD_RANK_LENGTH, oldRank, + newRank); + } + } + + /** + * 根据 member,score 将成员的记录插入到合适的分区中,如果没有合适的分区,说明在 10W 名以外,则不插入 + *

+ * 如果成员在 {@link #TOTAL_RANK_LENGTH} 以内排行榜,则返回真实排名;否则,则统一返回 {@link #TOTAL_RANK_LENGTH} + * + * @param member zset 成员 + * @param score 成员分值 + */ + private void addWithAutoAdjust(String member, double score, List regions, Pipeline pipeline) { + + String insertedMember = member; + double insertedScore = score; + + for (RankRegion region : regions) { + + // 判断分区长度 + if (region.getSize() < region.getMaxSize()) { + // 如果分区中实际数据量小于分区最大长度,则直接将成员插入排行榜即可: + // 由于排行榜是按照分值从高到低排序,各分区也是有序排列。 + // 分区没有满的情况下,不会创建新的分区,所以,此时必然是最后一个分区。 + pipeline.zadd(region.getRegionKey(), insertedScore, insertedMember); + region.setSize(region.getSize() + 1); + break; + } + + // 当前分区不为空,取最后一名 + if (region.getMin() == null) { + log.error("【排行榜】【删除老记录】key = {} 未找到最后一名数据!", region.getRegionKey()); + break; + } + + // 待插入分值比分区最小值还小 + if (region.getMin().getScore() >= insertedScore) { + continue; + } + + // 待插入分值大于当前分区的最小值,当前分区即为合适插入的分区 + // 将待插入成员、分值写入 + pipeline.zadd(region.getRegionKey(), insertedScore, insertedMember); + + // 从本分区中移出最后一名 + pipeline.zrem(region.getRegionKey(), region.getMin().getMember()); + + // 移入下一个分区 + insertedMember = region.getMin().getMember(); + insertedScore = region.getMin().getScore(); + } + } + + /** + * 先将原始排名记录从所属分区中删除,并动态调整之后的分区 + */ + private void deleteWithAutoAdjust(RankRegionElement oldRank, List regions, Pipeline pipeline) { + + // 计算排行榜分区的 Redis Key + pipeline.zrem(oldRank.getRegionKey(), oldRank.getMember()); + log.info("【排行榜】【删除老记录】删除原始记录:key = {}, member = {}", oldRank.getRegionKey(), oldRank.getMember()); + + int prevRegionNo = oldRank.getRegionNo(); + RankRegion prevRegion = null; + for (RankRegion region : regions) { + + // prevRegion 及之前的分区无需处理 + if (Objects.equals(region.getRegionNo(), prevRegionNo)) { + prevRegion = region; + continue; + } + if (region.getRegionNo() < oldRank.getRegionNo()) { continue; } + + // 当前分区如果为空,则无需调整,结束 + if (region.getSize() == null || region.getSize() == 0L) { + log.info("【排行榜】【删除老记录】key = {} 数据为空,无需处理", region.getRegionKey()); + break; + } + + // 当前分区不为空,取第一名 + if (region.getMax() == null) { + log.error("【排行榜】【删除老记录】key = {} 未找到第一名数据!", region.getRegionKey()); + break; + } + + if (prevRegion == null) { + break; + } + + // 从本分区中移出第一名 + pipeline.zrem(region.getRegionKey(), region.getMax().getMember()); + region.setSize(region.getSize() - 1); + // 移入上一个分区 + pipeline.zadd(prevRegion.getRegionKey(), region.getMax().getScore(), region.getMax().getMember()); + prevRegion.setSize(prevRegion.getSize() + 1); + // 替换上一分区 key + prevRegion = region; + } + } + + /** + * 获取排行榜所有分区 + *

+ * 排行榜存储 10W 条数据,分区规则为: + * 第一个分区,以 0 开始,存储 100 条数据(因为 TOP 100 查询频率高,所以分区大小设小一点,提高查询速度) + * 最后一个分区,以 95100 开始,存储 4900 条数据; + * 其他分区,都存储 5000 条数据 + */ + private static List getAllRankRegions() { + List regions = new ArrayList<>(); + RankRegion firstRegion = new RankRegion(FIRST, getRankRedisKey(FIRST), null, getRegionLength(FIRST)); + regions.add(firstRegion); + for (int index = FIRST_REGION_LEN; index < TOTAL_RANK_LENGTH; index = index + COMMON_REGION_LEN) { + RankRegion region = new RankRegion(index, getRankRedisKey(index), null, getRegionLength(index)); + regions.add(region); + } + return regions; + } + + /** + * 根据排行榜每个分区的第一个索引数字,获取该分区的长度 + *

+ * 分区大小的规则: + * 第一个分区,以 0 开始,存储 100 条数据; + * 最后一个分区,以 95100 开始,存储 4900 条数据; + * 其他分区,都存储 5000 条数据 + * + * @param region 分区第一条数据的索引 + * @return 分区的长度 + */ + private static long getRegionLength(int region) { + final int LAST = (int) ((TOTAL_RANK_LENGTH - 1) / COMMON_REGION_LEN * COMMON_REGION_LEN + FIRST_REGION_LEN); + switch (region) { + case FIRST: + return FIRST_REGION_LEN; + case LAST: + return COMMON_REGION_LEN - FIRST_REGION_LEN; + default: + return COMMON_REGION_LEN; + } + } + + /** + * 根据分区和分区中的排名,返回总排名 + */ + private static long getTotalRank(long regionNo, long rank) { + for (RankRegion region : REGIONS) { + if (region.getRegionNo().longValue() == regionNo) { + return regionNo + rank; + } + } + // 如果分区不存在,则统一返回 TOTAL_RANK_LENGTH + return TOTAL_RANK_LENGTH; + } + + /** + * 根据总排名,返回该排名应该所属的分区及分区中的排名信息 + */ + private static RankRegionElement getRegionRank(long totalRank) { + + if (totalRank < 0 || totalRank >= TOTAL_RANK_LENGTH) { return null; } + + long length = totalRank; + for (RankRegion region : REGIONS) { + if (region.getMaxSize() > length) { + return new RankRegionElement(region.getRegionNo(), region.getRegionKey(), null, null, length, + totalRank); + } else { + length -= region.getMaxSize(); + } + } + return null; + } + + /** + * 根据总排名,计算得出排名所属分区 + */ + private static int getRegionByTotalRank(long totalRank) { + if (totalRank < FIRST_REGION_LEN) { + return 0; + } + return (int) (totalRank / COMMON_REGION_LEN * COMMON_REGION_LEN + FIRST_REGION_LEN); + } + + /** + * 获取最后一个分区 + */ + private static int getLastRegionNo() { + return (int) ((TOTAL_RANK_LENGTH / COMMON_REGION_LEN - 1) * COMMON_REGION_LEN + FIRST_REGION_LEN); + } + + /** + * 排行榜缓存 Key + * + * @param regionNo 该分区第一个元素的排名 + */ + private static String getRankRedisKey(long regionNo) { + return RANK_PREFIX + regionNo; + } + +} diff --git a/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankDemoTests.java b/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankDemoTests.java new file mode 100644 index 00000000..e3a9d80a --- /dev/null +++ b/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankDemoTests.java @@ -0,0 +1,152 @@ +package io.github.dunwu.javadb.redis.jedis.rank; + +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.*; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.Tuple; +import redis.clients.jedis.exceptions.JedisConnectionException; + +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +/** + * 测试 {@link RankDemo} + * + * @author Zhang Peng + * @date 2022-05-24 + */ +@Slf4j +@DisplayName("使用 zset 维护分区的排行榜缓存") +public class RankDemoTests { + + private static final String REDIS_HOST = "localhost"; + private static final int REDIS_PORT = 6379; + private static Jedis jedis = null; + private RankDemo rank; + + @BeforeAll + public static void beforeClass() { + // Jedis 有多种构造方法,这里选用最简单的一种情况 + jedis = new Jedis(REDIS_HOST, REDIS_PORT); + + // 触发 ping 命令 + try { + jedis.ping(); + jedis.select(0); + log.debug("jedis 连接成功。"); + } catch (JedisConnectionException e) { + e.printStackTrace(); + } + } + + @AfterAll + public static void afterClass() { + if (null != jedis) { + jedis.close(); + log.debug("jedis 关闭连接。"); + } + } + + @BeforeEach + public void beforeEach() { + rank = new RankDemo(jedis); + } + + @Test + @DisplayName("刷新 MOCK 数据") + public void refreshMockData() { + log.info("刷新 MOCK 数据"); + + // 清理所有排行榜分区 + for (RankRegion region : RankDemo.REGIONS) { + jedis.del(region.getRegionKey()); + } + jedis.del(RankDemo.RANK); + + for (int i = 0; i < RankDemo.TOTAL_RANK_LENGTH; i++) { + double score = RandomUtil.randomDouble(100.0, 10000.0); + String member = StrUtil.format("id-{}", i); + rank.saveRank(member, score); + } + } + + @Test + @DisplayName("测试各分区最大值、最小值") + public void getRankElementList() { + List list = rank.getRankElementList(0, 99, false); + System.out.println(JSONUtil.toJsonStr(list)); + Assertions.assertEquals(100, list.size()); + } + + @Test + @DisplayName("添加新纪录") + public void testAdd() { + + String member1 = StrUtil.format("id-{}", RankDemo.TOTAL_RANK_LENGTH + 1); + rank.saveRank(member1, 20000.0); + + String member2 = StrUtil.format("id-{}", RankDemo.TOTAL_RANK_LENGTH + 2); + rank.saveRank(member2, 1.0); + + RankElement rank1 = rank.getRankByMember(member1); + RankElement rank2 = rank.getRankByMember(member2); + Assertions.assertEquals(RankDemo.FIRST, rank1.getTotalRank()); + Assertions.assertNull(rank2); + } + + + @Nested + @DisplayName("分区方案特殊测试") + public class RegionTest { + + @Test + @DisplayName("测试各分区长度") + public void testRegionLength() { + for (RankRegion region : RankDemo.REGIONS) { + Long size = jedis.zcard(region.getRegionKey()); + log.info("【排行榜】redisKey = {}, count = {}", region.getRegionKey(), size); + Assertions.assertEquals(region.getMaxSize(), size); + } + } + + @Test + @DisplayName("测试各分区最大值、最小值") + public void testRegionSort() { + // 按序获取每个分区的最大值、最小值 + List maxScores = new LinkedList<>(); + List minScores = new LinkedList<>(); + for (RankRegion region : RankDemo.REGIONS) { + Set minSet = jedis.zrangeWithScores(region.getRegionKey(), 0, 0); + Tuple min = minSet.iterator().next(); + minScores.add(min.getScore()); + + Set maxSet = jedis.zrevrangeWithScores(region.getRegionKey(), 0, 0); + Tuple max = maxSet.iterator().next(); + maxScores.add(max.getScore()); + } + System.out.println(maxScores); + System.out.println(minScores); + + // 最大值、最小值数量必然相同 + Assertions.assertEquals(maxScores.size(), minScores.size()); + + for (int i = 0; i < minScores.size(); i++) { + compareMinScore(maxScores, i, minScores.get(i)); + } + } + + public void compareMinScore(List maxScores, int region, double score) { + for (int i = region + 1; i < maxScores.size(); i++) { + Assertions.assertFalse(score <= maxScores.get(i), + StrUtil.format("region = {}, score = {} 的最小值小于后续分区中的数值(region = {}, score = {})", + region, score, i, maxScores.get(i))); + } + } + + } + +} diff --git a/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankElement.java b/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankElement.java new file mode 100644 index 00000000..ec03bfaf --- /dev/null +++ b/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankElement.java @@ -0,0 +1,25 @@ +package io.github.dunwu.javadb.redis.jedis.rank; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 排行榜元素信息 + * + * @author Zhang Peng + * @date 2022-05-26 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class RankElement { + + /** zset member */ + private String member; + /** zset score */ + private Double score; + /** 总排名 */ + private Long totalRank; + +} diff --git a/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankRegion.java b/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankRegion.java new file mode 100644 index 00000000..e865caf4 --- /dev/null +++ b/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankRegion.java @@ -0,0 +1,38 @@ +package io.github.dunwu.javadb.redis.jedis.rank; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 排行榜分区信息实体 + * + * @author Zhang Peng + * @date 2022-05-26 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class RankRegion { + + /** 排行榜分区号 */ + private Integer regionNo; + /** 排行榜分区 Redis Key */ + private String regionKey; + /** 分区实际大小 */ + private Long size; + /** 分区最大大小 */ + private Long maxSize; + /** 分区中的最小值 */ + private RankRegionElement min; + /** 分区中的最大值 */ + private RankRegionElement max; + + public RankRegion(Integer regionNo, String regionKey, Long size, Long maxSize) { + this.regionNo = regionNo; + this.regionKey = regionKey; + this.size = size; + this.maxSize = maxSize; + } + +} diff --git a/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankRegionElement.java b/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankRegionElement.java new file mode 100644 index 00000000..1a52e182 --- /dev/null +++ b/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankRegionElement.java @@ -0,0 +1,31 @@ +package io.github.dunwu.javadb.redis.jedis.rank; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 排行榜(分区)元素信息 + * + * @author Zhang Peng + * @date 2022-05-25 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class RankRegionElement { + + /** 排行榜分区号 */ + private Integer regionNo; + /** 排行榜分区 Redis Key */ + private String regionKey; + /** zset member */ + private String member; + /** zset score */ + private Double score; + /** 当前分区的排名 */ + private Long rank; + /** 总排名 */ + private Long totalRank; + +} diff --git a/codes/javadb/javadb-redis/src/test/resources/applicationContext.xml b/codes/javadb/redis/src/test/resources/applicationContext.xml similarity index 100% rename from codes/javadb/javadb-redis/src/test/resources/applicationContext.xml rename to codes/javadb/redis/src/test/resources/applicationContext.xml diff --git a/codes/javadb/javadb-redis/src/test/resources/config.xml b/codes/javadb/redis/src/test/resources/config.xml similarity index 100% rename from codes/javadb/javadb-redis/src/test/resources/config.xml rename to codes/javadb/redis/src/test/resources/config.xml diff --git a/codes/javadb/javadb-redis/src/test/resources/properties/application-dev.properties b/codes/javadb/redis/src/test/resources/properties/application-dev.properties similarity index 100% rename from codes/javadb/javadb-redis/src/test/resources/properties/application-dev.properties rename to codes/javadb/redis/src/test/resources/properties/application-dev.properties diff --git a/codes/javadb/javadb-redis/src/test/resources/properties/application-test.properties b/codes/javadb/redis/src/test/resources/properties/application-test.properties similarity index 100% rename from codes/javadb/javadb-redis/src/test/resources/properties/application-test.properties rename to codes/javadb/redis/src/test/resources/properties/application-test.properties diff --git a/codes/javadb/javadb-redis/src/test/resources/properties/application.properties b/codes/javadb/redis/src/test/resources/properties/application.properties similarity index 100% rename from codes/javadb/javadb-redis/src/test/resources/properties/application.properties rename to codes/javadb/redis/src/test/resources/properties/application.properties diff --git a/codes/javadb/javadb-redis/src/test/resources/redis.xml b/codes/javadb/redis/src/test/resources/redis.xml similarity index 100% rename from codes/javadb/javadb-redis/src/test/resources/redis.xml rename to codes/javadb/redis/src/test/resources/redis.xml diff --git a/codes/javadb/javadb-redis/src/test/resources/redisson-standalone.xml b/codes/javadb/redis/src/test/resources/redisson-standalone.xml similarity index 89% rename from codes/javadb/javadb-redis/src/test/resources/redisson-standalone.xml rename to codes/javadb/redis/src/test/resources/redisson-standalone.xml index 462abd6c..47607374 100644 --- a/codes/javadb/javadb-redis/src/test/resources/redisson-standalone.xml +++ b/codes/javadb/redis/src/test/resources/redisson-standalone.xml @@ -14,8 +14,6 @@ idle-connection-timeout="10000" connect-timeout="10000" timeout="3000" - ping-timeout="30000" - reconnection-timeout="30000" database="0"/> diff --git a/codes/javadb/sqlite/pom.xml b/codes/javadb/sqlite/pom.xml new file mode 100644 index 00000000..0fe9bdc2 --- /dev/null +++ b/codes/javadb/sqlite/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.6.3 + + + io.github.dunwu + javadb-sqlite + 1.0.0 + jar + + + + org.springframework.boot + spring-boot-starter-jdbc + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + + + + + org.xerial + sqlite-jdbc + 3.36.0.2 + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/codes/javadb/javadb-sqlite/src/main/java/io/github/dunwu/javadb/sqlite/springboot/SpringBootDataSqliteApplication.java b/codes/javadb/sqlite/src/main/java/io/github/dunwu/javadb/sqlite/springboot/SpringBootDataSqliteApplication.java similarity index 91% rename from codes/javadb/javadb-sqlite/src/main/java/io/github/dunwu/javadb/sqlite/springboot/SpringBootDataSqliteApplication.java rename to codes/javadb/sqlite/src/main/java/io/github/dunwu/javadb/sqlite/springboot/SpringBootDataSqliteApplication.java index e7fad56e..3870e9b9 100644 --- a/codes/javadb/javadb-sqlite/src/main/java/io/github/dunwu/javadb/sqlite/springboot/SpringBootDataSqliteApplication.java +++ b/codes/javadb/sqlite/src/main/java/io/github/dunwu/javadb/sqlite/springboot/SpringBootDataSqliteApplication.java @@ -6,7 +6,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; /** - * @author Zhang Peng + * @author Zhang Peng * @since 2019-03-05 */ @Slf4j diff --git a/codes/javadb/javadb-sqlite/src/main/java/io/github/dunwu/javadb/sqlite/springboot/SqliteDemo.java b/codes/javadb/sqlite/src/main/java/io/github/dunwu/javadb/sqlite/springboot/SqliteDemo.java similarity index 99% rename from codes/javadb/javadb-sqlite/src/main/java/io/github/dunwu/javadb/sqlite/springboot/SqliteDemo.java rename to codes/javadb/sqlite/src/main/java/io/github/dunwu/javadb/sqlite/springboot/SqliteDemo.java index b2ac44f7..fac342d3 100644 --- a/codes/javadb/javadb-sqlite/src/main/java/io/github/dunwu/javadb/sqlite/springboot/SqliteDemo.java +++ b/codes/javadb/sqlite/src/main/java/io/github/dunwu/javadb/sqlite/springboot/SqliteDemo.java @@ -6,7 +6,7 @@ import java.sql.Statement; /** - * @author Zhang Peng + * @author Zhang Peng * @since 2019-03-05 */ public class SqliteDemo { diff --git a/codes/javadb/javadb-sqlite/src/main/resources/application.properties b/codes/javadb/sqlite/src/main/resources/application.properties similarity index 100% rename from codes/javadb/javadb-sqlite/src/main/resources/application.properties rename to codes/javadb/sqlite/src/main/resources/application.properties diff --git a/codes/javadb/javadb-sqlite/src/main/resources/banner.txt b/codes/javadb/sqlite/src/main/resources/banner.txt similarity index 100% rename from codes/javadb/javadb-sqlite/src/main/resources/banner.txt rename to codes/javadb/sqlite/src/main/resources/banner.txt diff --git a/codes/javadb/javadb-sqlite/src/main/resources/logback.xml b/codes/javadb/sqlite/src/main/resources/logback.xml similarity index 100% rename from codes/javadb/javadb-sqlite/src/main/resources/logback.xml rename to codes/javadb/sqlite/src/main/resources/logback.xml diff --git "a/codes/mysql/Leetcode\344\271\213SQL\351\242\230/easy/\346\237\245\346\211\276\351\207\215\345\244\215\347\232\204\347\224\265\345\255\220\351\202\256\347\256\261.sql" "b/codes/mysql/Leetcode\344\271\213SQL\351\242\230/easy/\346\237\245\346\211\276\351\207\215\345\244\215\347\232\204\347\224\265\345\255\220\351\202\256\347\256\261.sql" index 0567c469..6b59150a 100644 --- "a/codes/mysql/Leetcode\344\271\213SQL\351\242\230/easy/\346\237\245\346\211\276\351\207\215\345\244\215\347\232\204\347\224\265\345\255\220\351\202\256\347\256\261.sql" +++ "b/codes/mysql/Leetcode\344\271\213SQL\351\242\230/easy/\346\237\245\346\211\276\351\207\215\345\244\215\347\232\204\347\224\265\345\255\220\351\202\256\347\256\261.sql" @@ -1,7 +1,7 @@ -- -------------------------------------------------------------------------------------- -- 查找重复的电子邮箱 -- @link https://leetcode-cn.com/problems/duplicate-emails/ --- @author Zhang Peng +-- @author Zhang Peng -- @date 2020/02/29 -- ---------------------------------------------------------------------------------------- diff --git "a/codes/mysql/SQL\345\277\205\347\237\245\345\277\205\344\274\232\347\244\272\344\276\213/README.md" "b/codes/mysql/SQL\345\277\205\347\237\245\345\277\205\344\274\232\347\244\272\344\276\213/README.md" index 50ade328..4ec36034 100644 --- "a/codes/mysql/SQL\345\277\205\347\237\245\345\277\205\344\274\232\347\244\272\344\276\213/README.md" +++ "b/codes/mysql/SQL\345\277\205\347\237\245\345\277\205\344\274\232\347\244\272\344\276\213/README.md" @@ -1,3 +1,3 @@ # 源码说明 -> 本目录代码为 [《SQL 必知必会》](https://item.jd.com/11232698.html) 部分示例源码 +> 本目录代码为 [《SQL 必知必会》](https://book.douban.com/subject/35167240/) 部分示例源码 diff --git "a/codes/mysql/SQL\345\277\205\347\237\245\345\277\205\344\274\232\347\244\272\344\276\213/create.sql" "b/codes/mysql/SQL\345\277\205\347\237\245\345\277\205\344\274\232\347\244\272\344\276\213/create.sql" index b3928a23..b15e1c3f 100644 --- "a/codes/mysql/SQL\345\277\205\347\237\245\345\277\205\344\274\232\347\244\272\344\276\213/create.sql" +++ "b/codes/mysql/SQL\345\277\205\347\237\245\345\277\205\344\274\232\347\244\272\344\276\213/create.sql" @@ -72,25 +72,25 @@ CREATE TABLE vendors ( -- Define primary keys -- ------------------- ALTER TABLE customers - ADD PRIMARY KEY (cust_id); +ADD PRIMARY KEY (cust_id); ALTER TABLE orderitems - ADD PRIMARY KEY (order_num, order_item); +ADD PRIMARY KEY (order_num, order_item); ALTER TABLE orders - ADD PRIMARY KEY (order_num); +ADD PRIMARY KEY (order_num); ALTER TABLE products - ADD PRIMARY KEY (prod_id); +ADD PRIMARY KEY (prod_id); ALTER TABLE vendors - ADD PRIMARY KEY (vend_id); +ADD PRIMARY KEY (vend_id); -- ------------------- -- Define foreign keys -- ------------------- ALTER TABLE orderitems - ADD CONSTRAINT fk_orderitems_orders FOREIGN KEY (order_num) REFERENCES orders(order_num); +ADD CONSTRAINT fk_orderitems_orders FOREIGN KEY (order_num) REFERENCES orders(order_num); ALTER TABLE orderitems - ADD CONSTRAINT fk_orderitems_products FOREIGN KEY (prod_id) REFERENCES products(prod_id); +ADD CONSTRAINT fk_orderitems_products FOREIGN KEY (prod_id) REFERENCES products(prod_id); ALTER TABLE orders - ADD CONSTRAINT fk_orders_customers FOREIGN KEY (cust_id) REFERENCES customers(cust_id); +ADD CONSTRAINT fk_orders_customers FOREIGN KEY (cust_id) REFERENCES customers(cust_id); ALTER TABLE products - ADD CONSTRAINT fk_products_vendors FOREIGN KEY (vend_id) REFERENCES vendors(vend_id); +ADD CONSTRAINT fk_products_vendors FOREIGN KEY (vend_id) REFERENCES vendors(vend_id); diff --git "a/codes/mysql/SQL\345\277\205\347\237\245\345\277\205\344\274\232\347\244\272\344\276\213/select.sql" "b/codes/mysql/SQL\345\277\205\347\237\245\345\277\205\344\274\232\347\244\272\344\276\213/select.sql" index 79dd3d46..6ec69d84 100644 --- "a/codes/mysql/SQL\345\277\205\347\237\245\345\277\205\344\274\232\347\244\272\344\276\213/select.sql" +++ "b/codes/mysql/SQL\345\277\205\347\237\245\345\277\205\344\274\232\347\244\272\344\276\213/select.sql" @@ -1,8 +1,8 @@ -/** - * Mysql 查询示例 - * @author Zhang Peng - * @date 2018/5/5 - */ +-- -------------------------------------------------------------------------------------- +-- Mysql 查询示例 +-- @author Zhang Peng +-- @date 2018/5/5 +-- ---------------------------------------------------------------------------------------- -- ------------------------------------------- -- 查询数据 @@ -24,7 +24,7 @@ FROM products; SELECT DISTINCT vend_id FROM products; --- 限制结果 +-- 限制查询数量 -- 返回前 5 行(1) SELECT * FROM products diff --git "a/codes/mysql/SQL\346\200\247\350\203\275\344\274\230\345\214\226/SQL\345\207\275\346\225\260\345\275\261\345\223\215\347\264\242\345\274\225.sql" "b/codes/mysql/SQL\346\200\247\350\203\275\344\274\230\345\214\226/SQL\345\207\275\346\225\260\345\275\261\345\223\215\347\264\242\345\274\225.sql" new file mode 100644 index 00000000..577ec893 --- /dev/null +++ "b/codes/mysql/SQL\346\200\247\350\203\275\344\274\230\345\214\226/SQL\345\207\275\346\225\260\345\275\261\345\223\215\347\264\242\345\274\225.sql" @@ -0,0 +1,119 @@ +-- -------------------------------------------------------------------------------------- +-- 函数操作影响索引效率示例 +-- @author Zhang Peng +-- ---------------------------------------------------------------------------------------- + +-- 步骤 1、建表 +CREATE TABLE tradelog ( + id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'Id', + tradeid VARCHAR(32) DEFAULT NULL, + operator INT(11) DEFAULT NULL, + t_modified DATETIME DEFAULT NULL, + PRIMARY KEY (id), + KEY tradeid(tradeid), + KEY t_modified(t_modified) +) + ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4; + +CREATE TABLE trade_detail ( + id INT(11) NOT NULL, + tradeid VARCHAR(32) DEFAULT NULL, + trade_step INT(11) DEFAULT NULL, /* 操作步骤 */ + step_info VARCHAR(32) DEFAULT NULL, /* 步骤信息 */ + PRIMARY KEY (id), + KEY tradeid(tradeid) +) + ENGINE = InnoDB + DEFAULT CHARSET = utf8; + +-- 步骤 2、存储过程初始化数据 + +INSERT INTO trade_detail +VALUES (1, 'aaaaaaaa', 1, 'add'); +INSERT INTO trade_detail +VALUES (2, 'aaaaaaaa', 2, 'update'); +INSERT INTO trade_detail +VALUES (3, 'aaaaaaaa', 3, 'commit'); +INSERT INTO trade_detail +VALUES (4, 'aaaaaaab', 1, 'add'); +INSERT INTO trade_detail +VALUES (5, 'aaaaaaab', 2, 'update'); +INSERT INTO trade_detail +VALUES (6, 'aaaaaaab', 3, 'update again'); +INSERT INTO trade_detail +VALUES (7, 'aaaaaaab', 4, 'commit'); +INSERT INTO trade_detail +VALUES (8, 'aaaaaaac', 1, 'add'); +INSERT INTO trade_detail +VALUES (9, 'aaaaaaac', 2, 'update'); +INSERT INTO trade_detail +VALUES (10, 'aaaaaaac', 3, 'update again'); +INSERT INTO trade_detail +VALUES (11, 'aaaaaaac', 4, 'commit'); + +INSERT INTO tradelog +VALUES (1, 'aaaaaaaa', 1000, now()); +INSERT INTO tradelog +VALUES (2, 'aaaaaaab', 1000, now()); +INSERT INTO tradelog +VALUES (3, 'aaaaaaac', 1000, now()); + +DELIMITER ;; +DROP PROCEDURE IF EXISTS init; +CREATE PROCEDURE init() +BEGIN + DECLARE i INT; + SET i = 3; + WHILE i < 10000 + DO + INSERT INTO tradelog(tradeid, operator, t_modified) + VALUES (concat(char(97 + (i DIV 1000)), char(97 + (i % 1000 DIV 100)), char(97 + (i % 100 DIV 10)), + char(97 + (i % 10))), i, now()); + SET i = i + 1; + END WHILE; +END;; +DELIMITER ; +CALL init(); + +-- 步骤 3、执行计划查看SQL效率 +-- 3.1.1 此 SQL 对索引字段做函数操作,优化器会放弃走树搜索功能,改为全表扫描 +EXPLAIN +SELECT count(*) +FROM tradelog +WHERE month(t_modified) = 7; + +-- 3.1.2 SQL 优化 +EXPLAIN +SELECT count(*) +FROM tradelog +WHERE (t_modified >= '2016-7-1' AND t_modified < '2016-8-1') OR + (t_modified >= '2017-7-1' AND t_modified < '2017-8-1') OR + (t_modified >= '2018-7-1' AND t_modified < '2018-8-1'); + +-- 3.2.1 此 SQL 对索引字段隐式的使用了转换函数操作,优化器会放弃走树搜索功能,改为全表扫描 +-- 相当于 select * from tradelog where CAST(tradid AS signed int) = 110717; +EXPLAIN +SELECT * +FROM tradelog +WHERE tradeid = 110717; + +-- 3.3.1 下面两条 SQL 的扫描行数不同 +-- 原因是:字符集 utf8mb4 是 utf8 的超集,所以当这两个类型的字符串在做比较的时候, +-- MySQL 内部的操作是,先把 utf8 字符串转成 utf8mb4 字符集,再做比较。 +# 需要做字符编码转换 +EXPLAIN +SELECT d.* +FROM tradelog l, trade_detail d +WHERE d.tradeid = l.tradeid AND l.id = 2; + +# 上面的 SQL 等价于这条注掉的 SQL +# SELECT * +# FROM trade_detail +# WHERE CONVERT(traideid USING utf8mb4) = $l2.tradeid.value; + +# 不需要做字符编码转换 +EXPLAIN +SELECT l.operator +FROM tradelog l, trade_detail d +WHERE d.tradeid = l.tradeid AND d.id = 2; diff --git "a/codes/mysql/SQL\346\200\247\350\203\275\344\274\230\345\214\226/\347\255\211MDL\351\224\201.sql" "b/codes/mysql/SQL\346\200\247\350\203\275\344\274\230\345\214\226/\347\255\211MDL\351\224\201.sql" new file mode 100644 index 00000000..67acf42c --- /dev/null +++ "b/codes/mysql/SQL\346\200\247\350\203\275\344\274\230\345\214\226/\347\255\211MDL\351\224\201.sql" @@ -0,0 +1,27 @@ +-- -------------------------------------------------------------------------------------- +-- 函数操作影响索引效率示例 +-- @author Zhang Peng +-- ---------------------------------------------------------------------------------------- + +CREATE TABLE t ( + id INT(11) NOT NULL AUTO_INCREMENT COMMENT 'Id', + c INT(11) DEFAULT NULL, + PRIMARY KEY (id) +) + ENGINE = InnoDB; + +DELIMITER ;; +DROP PROCEDURE IF EXISTS init; +CREATE PROCEDURE init() +BEGIN + DECLARE i INT; + SET i = 1; + WHILE(i <= 100000) + DO + INSERT INTO t VALUES (i, i); + SET i = i + 1; + END WHILE; +END;; +DELIMITER ; + +CALL init(); diff --git "a/codes/mysql/\344\272\213\345\212\241\347\244\272\344\276\213.sql" "b/codes/mysql/\344\272\213\345\212\241/\344\272\213\345\212\241\347\244\272\344\276\213.sql" similarity index 95% rename from "codes/mysql/\344\272\213\345\212\241\347\244\272\344\276\213.sql" rename to "codes/mysql/\344\272\213\345\212\241/\344\272\213\345\212\241\347\244\272\344\276\213.sql" index 10f3fdac..5dfde103 100644 --- "a/codes/mysql/\344\272\213\345\212\241\347\244\272\344\276\213.sql" +++ "b/codes/mysql/\344\272\213\345\212\241/\344\272\213\345\212\241\347\244\272\344\276\213.sql" @@ -1,6 +1,6 @@ -- -------------------------------------------------------------------------------------- -- Mysql 事务示例 --- @author Zhang Peng +-- @author Zhang Peng -- @date 2020/02/29 -- ---------------------------------------------------------------------------------------- diff --git "a/codes/mysql/\344\272\213\345\212\241/\345\271\273\350\257\273\347\244\272\344\276\2131.sql" "b/codes/mysql/\344\272\213\345\212\241/\345\271\273\350\257\273\347\244\272\344\276\2131.sql" new file mode 100644 index 00000000..55467a1e --- /dev/null +++ "b/codes/mysql/\344\272\213\345\212\241/\345\271\273\350\257\273\347\244\272\344\276\2131.sql" @@ -0,0 +1,55 @@ +-- -------------------------------------------------------------------------------------- +-- 幻读示例 +-- 实验说明:以下 SQL 脚本必须严格按照顺序执行,并且事务 A 和事务 B 必须在不同的 Client 中执行。 +-- @author Zhang Peng +-- @date 2023/10/25 +-- ---------------------------------------------------------------------------------------- + +-- --------------------------------------------------------------------- (1)数据初始化 + +-- 创建表 test +CREATE TABLE `test` ( + `id` INT(10) UNSIGNED PRIMARY KEY AUTO_INCREMENT, + `value` INT(10) NOT NULL +); + +-- 数据初始化 +INSERT INTO `test` (`id`, `value`) VALUES (1, 1); +INSERT INTO `test` (`id`, `value`) VALUES (2, 2); +INSERT INTO `test` (`id`, `value`) VALUES (3, 3); + +-- --------------------------------------------------------------------- (2)事务 A + +BEGIN; + +-- 查询 id = 4 的记录 +SELECT * FROM `test` WHERE `id` = 4; +-- 结果为空 + +-- --------------------------------------------------------------------- (3)事务 B + +BEGIN; + +INSERT INTO `test` (`id`, `value`) VALUES (4, 4); + +COMMIT; + +-- --------------------------------------------------------------------- (4)事务 A + +-- 查询 id = 4 的记录 +SELECT * FROM `test` WHERE `id` = 4; +-- 结果依然为空 + +-- 成功更新本应看不到的记录 id = 4 +UPDATE `test` SET `value` = 0 WHERE `id` = 4; + +-- 再一次查询 id = 4 的记录 +SELECT * FROM `test` WHERE `id` = 4; +-- 结果为: +-- +----+-------+ +-- | id | value | +-- +----+-------+ +-- | 4 | 0 | +-- +----+-------+ + +COMMIT; \ No newline at end of file diff --git "a/codes/mysql/\344\272\213\345\212\241/\345\271\273\350\257\273\347\244\272\344\276\2132.sql" "b/codes/mysql/\344\272\213\345\212\241/\345\271\273\350\257\273\347\244\272\344\276\2132.sql" new file mode 100644 index 00000000..2bb125d7 --- /dev/null +++ "b/codes/mysql/\344\272\213\345\212\241/\345\271\273\350\257\273\347\244\272\344\276\2132.sql" @@ -0,0 +1,53 @@ +-- -------------------------------------------------------------------------------------- +-- 幻读示例 +-- 实验说明:以下 SQL 脚本必须严格按照顺序执行,并且事务 A 和事务 B 必须在不同的 Client 中执行。 +-- @author Zhang Peng +-- @date 2023/10/25 +-- ---------------------------------------------------------------------------------------- + +-- --------------------------------------------------------------------- (1)数据初始化 + +-- 创建表 test +CREATE TABLE `test` ( + `id` INT(10) UNSIGNED PRIMARY KEY AUTO_INCREMENT, + `value` INT(10) NOT NULL +); + +-- 数据初始化 +INSERT INTO `test` (`id`, `value`) VALUES (1, 1); +INSERT INTO `test` (`id`, `value`) VALUES (2, 2); +INSERT INTO `test` (`id`, `value`) VALUES (3, 3); + +-- --------------------------------------------------------------------- (2)事务 A + +BEGIN; + +-- 查询 id > 2 的记录数 +SELECT COUNT(*) FROM `test` WHERE `id` > 2; +-- 结果为: +-- +----------+ +-- | count(*) | +-- +----------+ +-- | 1 | +-- +----------+ + +-- --------------------------------------------------------------------- (3)事务 B + +BEGIN; + +INSERT INTO `test` (`id`, `value`) VALUES (4, 4); + +COMMIT; + +-- --------------------------------------------------------------------- (4)事务 A + +-- 查询 id = 4 的记录 +SELECT COUNT(*) FROM `test` WHERE `id` > 2 FOR UPDATE; +-- 结果为: +-- +----------+ +-- | count(*) | +-- +----------+ +-- | 2 | +-- +----------+ + +COMMIT; \ No newline at end of file diff --git "a/codes/mysql/\345\237\272\346\234\254DDL\347\244\272\344\276\213.sql" "b/codes/mysql/\345\237\272\346\234\254DDL\347\244\272\344\276\213.sql" index 87e937d2..608c9605 100644 --- "a/codes/mysql/\345\237\272\346\234\254DDL\347\244\272\344\276\213.sql" +++ "b/codes/mysql/\345\237\272\346\234\254DDL\347\244\272\344\276\213.sql" @@ -1,13 +1,13 @@ -- -------------------------------------------------------------------------------------- -- Mysql 基本 DDL 语句示例 --- @author Zhang Peng +-- @author Zhang Peng -- @date 2018/4/28 -- ---------------------------------------------------------------------------------------- -- --------------------------------------------------------------------- 数据库定义 --- 撤销数据库 test +-- 删除数据库 db_tutorial DROP DATABASE IF EXISTS db_tutorial; -- 创建数据库 db_tutorial @@ -18,11 +18,11 @@ USE db_tutorial; -- --------------------------------------------------------------------- 数据表定义 --- 撤销表 user +-- 删除数据表 user DROP TABLE IF EXISTS user; DROP TABLE IF EXISTS vip_user; --- 创建表 user +-- 创建数据表 user CREATE TABLE user ( id INT(10) UNSIGNED NOT NULL COMMENT 'Id', username VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '用户名', @@ -37,31 +37,32 @@ FROM user; -- 添加列 age ALTER TABLE user - ADD age INT(3); +ADD age INT(3); -- 修改列 age 的类型为 tinyint ALTER TABLE user - MODIFY COLUMN age TINYINT; +MODIFY COLUMN age TINYINT; --- 撤销列 age +-- 删除列 age ALTER TABLE user - DROP COLUMN age; +DROP COLUMN age; -- --------------------------------------------------------------------- 索引定义 --- 创建表 user 的索引 user_index -CREATE INDEX user_index - ON user(id); +-- 创建表的索引 +CREATE INDEX idx_email + ON user(email); --- 创建表 user 的唯一索引 user_index2 -CREATE UNIQUE INDEX user_index2 - ON user(id); +-- 创建表的唯一索引 +CREATE UNIQUE INDEX uniq_username + ON user(username); --- 撤销表 user 的索引 +-- 删除表 user 的索引 ALTER TABLE user - DROP INDEX user_index; +DROP INDEX idx_email; + ALTER TABLE user - DROP INDEX user_index2; +DROP INDEX uniq_username; -- --------------------------------------------------------------------- 视图定义 @@ -71,5 +72,5 @@ SELECT id, username FROM user WHERE id < 10; --- 撤销表 user 的视图 top_10_user_view +-- 删除表 user 的视图 top_10_user_view DROP VIEW top_10_user_view; diff --git "a/codes/mysql/\345\237\272\346\234\254DML\347\244\272\344\276\213.sql" "b/codes/mysql/\345\237\272\346\234\254DML\347\244\272\344\276\213.sql" index b0db5628..11c3bd2e 100644 --- "a/codes/mysql/\345\237\272\346\234\254DML\347\244\272\344\276\213.sql" +++ "b/codes/mysql/\345\237\272\346\234\254DML\347\244\272\344\276\213.sql" @@ -1,6 +1,6 @@ -- -------------------------------------------------------------------------------------- -- Mysql 基本 DML 语句示例 --- @author Zhang Peng +-- @author Zhang Peng -- @date 2018/4/28 -- ---------------------------------------------------------------------------------------- diff --git "a/codes/mysql/\345\237\272\346\234\254TCL\347\244\272\344\276\213.sql" "b/codes/mysql/\345\237\272\346\234\254TCL\347\244\272\344\276\213.sql" index 28c55dd5..013a5188 100644 --- "a/codes/mysql/\345\237\272\346\234\254TCL\347\244\272\344\276\213.sql" +++ "b/codes/mysql/\345\237\272\346\234\254TCL\347\244\272\344\276\213.sql" @@ -1,6 +1,6 @@ -- -------------------------------------------------------------------------------------- -- Mysql 基本 TCL 语句示例 --- @author Zhang Peng +-- @author Zhang Peng -- @date 2018/4/28 -- ---------------------------------------------------------------------------------------- diff --git "a/codes/mysql/\345\270\270\350\247\201\346\237\245\350\257\242\347\244\272\344\276\213.sql" "b/codes/mysql/\345\270\270\350\247\201\346\237\245\350\257\242\347\244\272\344\276\213.sql" index f6a843c1..a079f543 100644 --- "a/codes/mysql/\345\270\270\350\247\201\346\237\245\350\257\242\347\244\272\344\276\213.sql" +++ "b/codes/mysql/\345\270\270\350\247\201\346\237\245\350\257\242\347\244\272\344\276\213.sql" @@ -1,6 +1,6 @@ -- -------------------------------------------------------------------------------------- -- Mysql 常见查询示例 --- @author Zhang Peng +-- @author Zhang Peng -- @date 2018/5/4 -- ---------------------------------------------------------------------------------------- diff --git "a/codes/mysql/\350\247\246\345\217\221\345\231\250\347\244\272\344\276\213.sql" "b/codes/mysql/\350\247\246\345\217\221\345\231\250\347\244\272\344\276\213.sql" index f9337eb9..d36d9183 100644 --- "a/codes/mysql/\350\247\246\345\217\221\345\231\250\347\244\272\344\276\213.sql" +++ "b/codes/mysql/\350\247\246\345\217\221\345\231\250\347\244\272\344\276\213.sql" @@ -1,11 +1,11 @@ /** * Mysql 触发器(TRIGGER)创建、使用示例 - * @author Zhang Peng + * @author Zhang Peng * @date 2018/5/2 */ -- -------------------------------------------------------------------------------------- -- Mysql 基本 DDL 语句示例 --- @author Zhang Peng +-- @author Zhang Peng -- @date 2018/4/28 -- ---------------------------------------------------------------------------------------- diff --git a/codes/redis/redis-in-action/pom.xml b/codes/redis/redis-in-action/pom.xml index 3c2c2edf..5699fbd1 100644 --- a/codes/redis/redis-in-action/pom.xml +++ b/codes/redis/redis-in-action/pom.xml @@ -62,5 +62,11 @@ javatuples 1.1 + + + cn.hutool + hutool-all + 5.5.9 + diff --git a/codes/redis/redis-in-action/src/main/java/io/github/dunwu/db/redis/Chapter02.java b/codes/redis/redis-in-action/src/main/java/io/github/dunwu/db/redis/Chapter02.java index 936153ee..b8c86522 100644 --- a/codes/redis/redis-in-action/src/main/java/io/github/dunwu/db/redis/Chapter02.java +++ b/codes/redis/redis-in-action/src/main/java/io/github/dunwu/db/redis/Chapter02.java @@ -451,6 +451,7 @@ public interface Callback { } + public static class Inventory { private String id; @@ -469,6 +470,33 @@ public static Inventory get(String id) { return new Inventory(id); } + public String getId() { + return id; + } + + public Inventory setId(String id) { + this.id = id; + return this; + } + + public String getData() { + return data; + } + + public Inventory setData(String data) { + this.data = data; + return this; + } + + public long getTime() { + return time; + } + + public Inventory setTime(long time) { + this.time = time; + return this; + } + } } diff --git a/codes/redis/redis-in-action/src/main/java/io/github/dunwu/db/redis/SortedSetDemo.java b/codes/redis/redis-in-action/src/main/java/io/github/dunwu/db/redis/SortedSetDemo.java new file mode 100644 index 00000000..f8ab9f19 --- /dev/null +++ b/codes/redis/redis-in-action/src/main/java/io/github/dunwu/db/redis/SortedSetDemo.java @@ -0,0 +1,63 @@ +package io.github.dunwu.db.redis; + +import cn.hutool.core.util.RandomUtil; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.Tuple; + +import java.util.Set; + +/** + * @author Zhang Peng + * @date 2022-05-20 + */ +public class SortedSetDemo { + + public static final String TEST_KEY = "test:zset"; + public static final Jedis conn = new Jedis("localhost"); + + public static void main(String[] args) { + conn.select(0); + // zadd(conn); + zrem(conn); + // zrank(conn); + // zrange(conn); + zcard(conn); + conn.close(); + } + + public static void zadd(Jedis conn) { + for (int i = 0; i < 100; i++) { + conn.zadd(TEST_KEY, RandomUtil.randomDouble(10000.0), RandomUtil.randomString(6)); + } + conn.zadd(TEST_KEY, 20000.0, "THETOP"); + } + + public static void zrem(Jedis conn) { + int len = 10; + int end = -len - 1; + conn.zremrangeByRank(TEST_KEY, 0, end); + } + + public static void zcard(Jedis conn) { + System.out.println("count = " + conn.zcard(TEST_KEY)); + } + + public static void zrank(Jedis conn) { + System.out.println("THETOP 从低到高排名:" + conn.zrank(TEST_KEY, "THETOP")); + System.out.println("THETOP 从高到低排名:" + conn.zrevrank(TEST_KEY, "THETOP")); + } + + public static void zrange(Jedis conn) { + System.out.println("查看从低到高第 1 名:" + conn.zrange(TEST_KEY, 0, 0)); + System.out.println("查看从高到低第 1 名:" + conn.zrevrange(TEST_KEY, 0, 0)); + System.out.println("查看从高到低前 10 名:" + conn.zrevrange(TEST_KEY, 0, 9)); + Set tuples = conn.zrevrangeWithScores(TEST_KEY, 0, 0); + for (Tuple tuple : tuples) { + System.out.println(tuple.getElement()); + System.out.println(tuple.getScore()); + } + + System.out.println("查看从高到低前 10 名:" + conn.zrevrangeWithScores(TEST_KEY, 0, 0)); + } + +} diff --git a/docs/.markdownlint.json b/docs/.markdownlint.json index 3df31ed2..1ab9a8fa 100644 --- a/docs/.markdownlint.json +++ b/docs/.markdownlint.json @@ -13,5 +13,6 @@ "MD036": false, "fenced-code-language": false, "no-hard-tabs": false, - "whitespace": false + "whitespace": false, + "emphasis-style": { "style": "consistent" } } diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 692b7642..fd060731 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -1,114 +1,222 @@ -/** - * @see https://vuepress.vuejs.org/zh/ - */ +const htmlModules = require('./config/htmlModules.js') + module.exports = { port: '4000', - dest: 'dist', - base: '/db-tutorial/', + dest: 'docs/.temp', + base: '/db-tutorial/', // 默认'/'。如果你想将你的网站部署到如 https://foo.github.io/bar/,那么 base 应该被设置成 "/bar/",(否则页面将失去样式等文件) title: 'DB-TUTORIAL', - description: '数据库教程', - head: [['link', { rel: 'icon', href: `/favicon.ico` }]], + description: '☕ db-tutorial 是一个数据库教程。', + theme: 'vdoing', // 使用依赖包主题 + // theme: require.resolve('../../vdoing'), // 使用本地主题 + head: [ + // 注入到页面 中的标签,格式[tagName, { attrName: attrValue }, innerHTML?] + ['link', { rel: 'icon', href: '/img/favicon.ico' }], //favicons,资源放在public文件夹 + ['meta', { name: 'keywords', content: 'vuepress,theme,blog,vdoing' }], + ['meta', { name: 'theme-color', content: '#11a8cd' }], // 移动浏览器主题颜色 + + ['meta', { name: 'wwads-cn-verify', content: 'mxqWx62nfQQ9ocT4e5DzISHzOWyF4s' }], // 广告相关,你可以去掉 + ['script', { src: 'https://cdn.wwads.cn/js/makemoney.js', type: 'text/javascript' }], // 广告相关,你可以去掉 + ], markdown: { + // lineNumbers: true, + extractHeaders: ['h2', 'h3', 'h4', 'h5', 'h6'], // 提取标题到侧边栏的级别,默认['h2', 'h3'] externalLinks: { target: '_blank', - rel: 'noopener noreferrer', - }, + rel: 'noopener noreferrer' + } }, + // 主题配置 themeConfig: { - logo: 'https://raw.githubusercontent.com/dunwu/images/dev/common/dunwu-logo-200.png', - repo: 'dunwu/db-tutorial', - repoLabel: 'Github', - docsDir: 'docs', - docsBranch: 'master', - editLinks: true, - smoothScroll: true, - locales: { - '/': { - label: '简体中文', - selectText: 'Languages', - editLinkText: '帮助我们改善此页面!', - lastUpdated: '上次更新', - nav: [ - { - text: 'SQL', - link: '/sql/', - }, - { - text: 'NOSQL', - link: '/nosql/', - }, - { - text: 'Mysql', - link: '/sql/mysql/', - }, - { - text: 'Redis', - link: '/nosql/redis/', - }, - { - text: 'Elasticsearch', - link: '/nosql/elasticsearch/', - }, - { - text: 'MongoDB', - link: '/nosql/mongodb/', - }, - { - text: '🎯 博客', - link: 'https://github.com/dunwu/blog', - target: '_blank', - rel: '', - }, - ], - sidebar: 'auto', - sidebarDepth: 2, + nav: [ + { text: '数据库综合', link: '/12.数据库/01.数据库综合/' }, + { text: '数据库中间件', link: '/12.数据库/02.数据库中间件/' }, + { + text: '关系型数据库', + link: '/12.数据库/03.关系型数据库/', + items: [ + { text: '综合', link: '/12.数据库/03.关系型数据库/01.综合/' }, + { text: 'Mysql', link: '/12.数据库/03.关系型数据库/02.Mysql/' }, + { text: '其他', link: '/12.数据库/03.关系型数据库/99.其他/' } + ] + }, + { + text: '文档数据库', + items: [{ text: 'MongoDB', link: '/12.数据库/04.文档数据库/01.MongoDB/' }] + }, + { + text: 'KV数据库', + items: [{ text: 'Redis', link: '/12.数据库/05.KV数据库/01.Redis/' }] }, + { + text: '搜索引擎数据库', + items: [ + { text: 'Elasticsearch', link: '/12.数据库/07.搜索引擎数据库/01.Elasticsearch/' }, + { text: 'Elastic技术栈', link: '/12.数据库/07.搜索引擎数据库/02.Elastic/' } + ] + } + ], + sidebarDepth: 2, // 侧边栏显示深度,默认1,最大2(显示到h3标题) + logo: 'https://raw.githubusercontent.com/dunwu/images/master/common/dunwu-logo.png', // 导航栏logo + repo: 'dunwu/db-tutorial', // 导航栏右侧生成Github链接 + searchMaxSuggestions: 10, // 搜索结果显示最大数 + lastUpdated: '上次更新', // 更新的时间,及前缀文字 string | boolean (取值为git提交时间) + + docsDir: 'docs', // 编辑的文件夹 + editLinks: true, // 编辑链接 + editLinkText: '📝 帮助改善此页面!', + + // 以下配置是Vdoing主题改动的和新增的配置 + sidebar: { mode: 'structuring', collapsable: true }, // 侧边栏 'structuring' | { mode: 'structuring', collapsable: + // Boolean} | 'auto' | 自定义 温馨提示:目录页数据依赖于结构化的侧边栏数据,如果你不设置为'structuring',将无法使用目录页 + + sidebarOpen: true, // 初始状态是否打开侧边栏,默认true + updateBar: { + // 最近更新栏 + showToArticle: true // 显示到文章页底部,默认true + // moreArticle: '/archives' // “更多文章”跳转的页面,默认'/archives' + }, + // titleBadge: false, // 文章标题前的图标是否显示,默认true + // titleBadgeIcons: [ // 文章标题前图标的地址,默认主题内置图标 + // '图标地址1', + // '图标地址2' + // ], + // bodyBgImg: [ + // 'https://cdn.jsdelivr.net/gh/xugaoyi/image_store/blog/20200507175828.jpeg', + // 'https://cdn.jsdelivr.net/gh/xugaoyi/image_store/blog/20200507175845.jpeg', + // 'https://cdn.jsdelivr.net/gh/xugaoyi/image_store/blog/20200507175846.jpeg' + // ], // body背景大图,默认无。 单张图片 String || 多张图片 Array, 多张图片时每隔15秒换一张。 + + // categoryText: '随笔', // 碎片化文章(_posts文件夹的文章)预设生成的分类值,默认'随笔' + + // contentBgStyle: 1, + + category: true, // 是否打开分类功能,默认true。 如打开,会做的事情有:1. 自动生成的frontmatter包含分类字段 2.页面中显示与分类相关的信息和模块 3.自动生成分类页面(在@pages文件夹)。如关闭,则反之。 + tag: true, // 是否打开标签功能,默认true。 如打开,会做的事情有:1. 自动生成的frontmatter包含标签字段 2.页面中显示与标签相关的信息和模块 3.自动生成标签页面(在@pages文件夹)。如关闭,则反之。 + archive: true, // 是否打开归档功能,默认true。 如打开,会做的事情有:1.自动生成归档页面(在@pages文件夹)。如关闭,则反之。 + + author: { + // 文章默认的作者信息,可在md文件中单独配置此信息 String | {name: String, href: String} + name: 'dunwu', // 必需 + href: 'https://github.com/dunwu' // 可选的 + }, + social: { + // 社交图标,显示于博主信息栏和页脚栏 + // iconfontCssFile: '//at.alicdn.com/t/font_1678482_u4nrnp8xp6g.css', // 可选,阿里图标库在线css文件地址,对于主题没有的图标可自由添加 + icons: [ + { + iconClass: 'icon-youjian', + title: '发邮件', + link: 'mailto:forbreak@163.com' + }, + { + iconClass: 'icon-github', + title: 'GitHub', + link: 'https://github.com/dunwu' + } + ] }, + footer: { + // 页脚信息 + createYear: 2019, // 博客创建年份 + copyrightInfo: '钝悟(dunwu) | CC-BY-SA-4.0' // 博客版权信息,支持a标签 + }, + htmlModules }, + + // 插件 plugins: [ [ - '@vuepress/active-header-links', + require('./plugins/love-me'), { - sidebarLinkSelector: '.sidebar-link', - headerAnchorSelector: '.header-anchor', - }, + // 鼠标点击爱心特效 + color: '#11a8cd', // 爱心颜色,默认随机色 + excludeClassName: 'theme-vdoing-content' // 要排除元素的class, 默认空'' + } ], - ['@vuepress/back-to-top', true], + + ['fulltext-search'], // 全文搜索 + + // ['thirdparty-search', { // 可以添加第三方搜索链接的搜索框(原官方搜索框的参数仍可用) + // thirdparty: [ // 可选,默认 [] + // { + // title: '在GitHub中搜索', + // frontUrl: 'https://github.com/search?q=', // 搜索链接的前面部分 + // behindUrl: '' // 搜索链接的后面部分,可选,默认 '' + // }, + // { + // title: '在npm中搜索', + // frontUrl: 'https://www.npmjs.com/search?q=', + // }, + // { + // title: '在Bing中搜索', + // frontUrl: 'https://cn.bing.com/search?q=' + // } + // ] + // }], + [ - '@vuepress/pwa', + 'one-click-copy', { - serviceWorker: true, - updatePopup: true, - }, + // 代码块复制按钮 + copySelector: ['div[class*="language-"] pre', 'div[class*="aside-code"] aside'], // String or Array + copyMessage: '复制成功', // default is 'Copy successfully and then paste it for use.' + duration: 1000, // prompt message display time. + showInMobile: false // whether to display on the mobile side, default: false. + } ], [ - '@vuepress/last-updated', + 'demo-block', { - transformer: (timestamp, lang) => { - // 不要忘了安装 moment - const moment = require('moment') - moment.locale(lang) - return moment(timestamp).fromNow() - }, - }, + // demo演示模块 https://github.com/xiguaxigua/vuepress-plugin-demo-block + settings: { + // jsLib: ['http://xxx'], // 在线示例(jsfiddle, codepen)中的js依赖 + // cssLib: ['http://xxx'], // 在线示例中的css依赖 + // vue: 'https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js', // 在线示例中的vue依赖 + jsfiddle: false, // 是否显示 jsfiddle 链接 + codepen: true, // 是否显示 codepen 链接 + horizontal: false // 是否展示为横向样式 + } + } ], - ['@vuepress/medium-zoom', true], [ - 'container', + 'vuepress-plugin-zooming', // 放大图片 { - type: 'vue', - before: '

',
-        after: '
', - }, + selector: '.theme-vdoing-content img:not(.no-zoom)', + options: { + bgColor: 'rgba(0,0,0,0.6)' + } + } ], [ - 'container', + '@vuepress/last-updated', // "上次更新"时间格式 { - type: 'upgrade', - before: (info) => ``, - after: '', - }, + transformer: (timestamp, lang) => { + const dayjs = require('dayjs') // https://day.js.org/ + return dayjs(timestamp).format('YYYY/MM/DD, HH:mm:ss') + } + } ], - ['flowchart'], + [ + 'vuepress-plugin-comment', // 评论 + { + choosen: 'gitalk', + options: { + clientID: '7dd8c87a20cff437d2ed', + clientSecret: '4e28d81a9a0280796b2b45ce2944424c6f2c1531', + repo: 'db-tutorial', // GitHub 仓库 + owner: 'dunwu', // GitHub仓库所有者 + admin: ['dunwu'], // 对仓库有写权限的人 + // distractionFreeMode: true, + pagerDirection: 'last', // 'first'正序 | 'last'倒序 + id: '<%- (frontmatter.permalink || frontmatter.to.path).slice(-16) %>', // 页面的唯一标识,长度不能超过50 + title: '「评论」<%- frontmatter.title %>', // GitHub issue 的标题 + labels: ['Gitalk', 'Comment'], // GitHub issue 的标签 + body: '页面:<%- window.location.origin + (frontmatter.to.path || window.location.pathname) %>' // GitHub issue 的内容 + } + } + ] ], + + // 监听文件变化并重新构建 + extraWatchFiles: ['.vuepress/config.js', '.vuepress/config/htmlModules.js'] } diff --git a/docs/.vuepress/config/baiduCode.js b/docs/.vuepress/config/baiduCode.js new file mode 100644 index 00000000..b0c50903 --- /dev/null +++ b/docs/.vuepress/config/baiduCode.js @@ -0,0 +1 @@ +module.exports = '' diff --git a/docs/.vuepress/config/htmlModules.js b/docs/.vuepress/config/htmlModules.js new file mode 100644 index 00000000..fc0a47eb --- /dev/null +++ b/docs/.vuepress/config/htmlModules.js @@ -0,0 +1,69 @@ +/** 插入自定义html模块 (可用于插入广告模块等) + * { + * homeSidebarB: htmlString, 首页侧边栏底部 + * + * sidebarT: htmlString, 全局左侧边栏顶部 + * sidebarB: htmlString, 全局左侧边栏底部 + * + * pageT: htmlString, 全局页面顶部 + * pageB: htmlString, 全局页面底部 + * pageTshowMode: string, 页面顶部-显示方式:未配置默认全局;'article' => 仅文章页①; 'custom' => 仅自定义页① + * pageBshowMode: string, 页面底部-显示方式:未配置默认全局;'article' => 仅文章页①; 'custom' => 仅自定义页① + * + * windowLB: htmlString, 全局左下角② + * windowRB: htmlString, 全局右下角② + * } + * + * ①注:在.md文件front matter配置`article: false`的页面是自定义页,未配置的默认是文章页(首页除外)。 + * ②注:windowLB 和 windowRB:1.展示区块最大宽高200px*400px。2.请给自定义元素定一个不超过200px*400px的宽高。3.在屏幕宽度小于960px时无论如何都不会显示。 + */ + +module.exports = { + // 万维广告 + // pageT: ` + //
+ // + // `, + windowRB: ` +
+ + `, +} + +// module.exports = { +// homeSidebarB: `
自定义模块测试
`, +// sidebarT: `
自定义模块测试
`, +// sidebarB: `
自定义模块测试
`, +// pageT: `
自定义模块测试
`, +// pageB: `
自定义模块测试
`, +// windowLB: `
自定义模块测试
`, +// windowRB: `
自定义模块测试
`, +// } diff --git a/docs/.vuepress/enhanceApp.js b/docs/.vuepress/enhanceApp.js index 7b3605fc..5bfa34f4 100644 --- a/docs/.vuepress/enhanceApp.js +++ b/docs/.vuepress/enhanceApp.js @@ -1,7 +1,59 @@ -export default ({ Vue, isServer }) => { +/** + * to主题使用者:你可以去掉本文件的所有代码 + */ +export default ({ + Vue, // VuePress 正在使用的 Vue 构造函数 + options, // 附加到根实例的一些选项 + router, // 当前应用的路由实例 + siteData, // 站点元数据 + isServer // 当前应用配置是处于 服务端渲染 还是 客户端 +}) => { + + // 用于监控在路由变化时检查广告拦截器 (to主题使用者:你可以去掉本文件的所有代码) if (!isServer) { - import('vue-toasted' /* webpackChunkName: "notification" */).then(module => { - Vue.use(module.default) + router.afterEach(() => { + //check if wwads' fire function was blocked after document is ready with 3s timeout (waiting the ad loading) + docReady(function () { + setTimeout(function () { + if (window._AdBlockInit === undefined) { + ABDetected(); + } + }, 3000); + }); + + // 删除事件改为隐藏事件 + setTimeout(() => { + const pageAD = document.querySelector('.page-wwads'); + if (!pageAD) return; + const btnEl = pageAD.querySelector('.wwads-hide'); + if (btnEl) { + btnEl.onclick = () => { + pageAD.style.display = 'none'; + } + } + // 显示广告模块 + if (pageAD.style.display === 'none') { + pageAD.style.display = 'flex'; + } + }, 900); }) } } + + +function ABDetected() { + const h = ""; + const wwadsEl = document.getElementsByClassName("wwads-cn"); + const wwadsContentEl = document.querySelector('.wwads-content'); + if (wwadsEl[0] && !wwadsContentEl) { + wwadsEl[0].innerHTML = h; + } +}; + +//check document ready +function docReady(t) { + "complete" === document.readyState || + "interactive" === document.readyState + ? setTimeout(t, 1) + : document.addEventListener("DOMContentLoaded", t); +} diff --git a/docs/.vuepress/plugins/love-me/index.js b/docs/.vuepress/plugins/love-me/index.js new file mode 100644 index 00000000..2851beb0 --- /dev/null +++ b/docs/.vuepress/plugins/love-me/index.js @@ -0,0 +1,12 @@ +const path = require('path') +const LoveMyPlugin = (options = {}) => ({ + define() { + const COLOR = + options.color || + 'rgb(' + ~~(255 * Math.random()) + ',' + ~~(255 * Math.random()) + ',' + ~~(255 * Math.random()) + ')' + const EXCLUDECLASS = options.excludeClassName || '' + return { COLOR, EXCLUDECLASS } + }, + enhanceAppFiles: [path.resolve(__dirname, 'love-me.js')] +}) +module.exports = LoveMyPlugin diff --git a/docs/.vuepress/plugins/love-me/love-me.js b/docs/.vuepress/plugins/love-me/love-me.js new file mode 100644 index 00000000..5c0369ac --- /dev/null +++ b/docs/.vuepress/plugins/love-me/love-me.js @@ -0,0 +1,89 @@ +export default () => { + if (typeof window !== 'undefined') { + ;(function (e, t, a) { + function r() { + for (var e = 0; e < s.length; e++) + s[e].alpha <= 0 + ? (t.body.removeChild(s[e].el), s.splice(e, 1)) + : (s[e].y--, + (s[e].scale += 0.004), + (s[e].alpha -= 0.013), + (s[e].el.style.cssText = + 'left:' + + s[e].x + + 'px;top:' + + s[e].y + + 'px;opacity:' + + s[e].alpha + + ';transform:scale(' + + s[e].scale + + ',' + + s[e].scale + + ') rotate(45deg);background:' + + s[e].color + + ';z-index:99999')) + requestAnimationFrame(r) + } + function n() { + var t = 'function' == typeof e.onclick && e.onclick + + e.onclick = function (e) { + // 过滤指定元素 + let mark = true + EXCLUDECLASS && + e.path && + e.path.forEach((item) => { + if (item.nodeType === 1) { + typeof item.className === 'string' && item.className.indexOf(EXCLUDECLASS) > -1 ? (mark = false) : '' + } + }) + + if (mark) { + t && t(), o(e) + } + } + } + function o(e) { + var a = t.createElement('div') + ;(a.className = 'heart'), + s.push({ + el: a, + x: e.clientX - 5, + y: e.clientY - 5, + scale: 1, + alpha: 1, + color: COLOR + }), + t.body.appendChild(a) + } + function i(e) { + var a = t.createElement('style') + a.type = 'text/css' + try { + a.appendChild(t.createTextNode(e)) + } catch (t) { + a.styleSheet.cssText = e + } + t.getElementsByTagName('head')[0].appendChild(a) + } + // function c() { + // return "rgb(" + ~~ (255 * Math.random()) + "," + ~~ (255 * Math.random()) + "," + ~~ (255 * Math.random()) + ")" + // } + var s = [] + ;(e.requestAnimationFrame = + e.requestAnimationFrame || + e.webkitRequestAnimationFrame || + e.mozRequestAnimationFrame || + e.oRequestAnimationFrame || + e.msRequestAnimationFrame || + function (e) { + setTimeout(e, 1e3 / 60) + }), + i( + ".heart{width: 10px;height: 10px;position: fixed;background: #f00;transform: rotate(45deg);-webkit-transform: rotate(45deg);-moz-transform: rotate(45deg);}.heart:after,.heart:before{content: '';width: inherit;height: inherit;background: inherit;border-radius: 50%;-webkit-border-radius: 50%;-moz-border-radius: 50%;position: fixed;}.heart:after{top: -5px;}.heart:before{left: -5px;}" + ), + n(), + r() + })(window, document) + } +} diff --git a/docs/.vuepress/public/images/dunwu-logo-100.png b/docs/.vuepress/public/images/dunwu-logo-100.png deleted file mode 100644 index 12d81778..00000000 Binary files a/docs/.vuepress/public/images/dunwu-logo-100.png and /dev/null differ diff --git a/docs/.vuepress/public/images/dunwu-logo-200.png b/docs/.vuepress/public/images/dunwu-logo-200.png deleted file mode 100644 index ea0a019c..00000000 Binary files a/docs/.vuepress/public/images/dunwu-logo-200.png and /dev/null differ diff --git a/docs/.vuepress/public/images/dunwu-logo-50.png b/docs/.vuepress/public/images/dunwu-logo-50.png deleted file mode 100644 index 90a19762..00000000 Binary files a/docs/.vuepress/public/images/dunwu-logo-50.png and /dev/null differ diff --git a/docs/.vuepress/public/img/bg.gif b/docs/.vuepress/public/img/bg.gif new file mode 100644 index 00000000..d4bf3c41 Binary files /dev/null and b/docs/.vuepress/public/img/bg.gif differ diff --git a/docs/.vuepress/public/images/dunwu-logo.png b/docs/.vuepress/public/img/dunwu-logo.png similarity index 100% rename from docs/.vuepress/public/images/dunwu-logo.png rename to docs/.vuepress/public/img/dunwu-logo.png diff --git a/docs/.vuepress/public/img/favicon.ico b/docs/.vuepress/public/img/favicon.ico new file mode 100644 index 00000000..51e9bfa0 Binary files /dev/null and b/docs/.vuepress/public/img/favicon.ico differ diff --git a/docs/.vuepress/public/img/more.png b/docs/.vuepress/public/img/more.png new file mode 100644 index 00000000..830613ba Binary files /dev/null and b/docs/.vuepress/public/img/more.png differ diff --git a/docs/.vuepress/public/img/other.png b/docs/.vuepress/public/img/other.png new file mode 100644 index 00000000..87f80989 Binary files /dev/null and b/docs/.vuepress/public/img/other.png differ diff --git a/docs/.vuepress/public/markmap/01.html b/docs/.vuepress/public/markmap/01.html new file mode 100644 index 00000000..c4e0bdbc --- /dev/null +++ b/docs/.vuepress/public/markmap/01.html @@ -0,0 +1,113 @@ + + + + + + + Markmap + + + + + + + + + diff --git a/docs/.vuepress/styles/index.styl b/docs/.vuepress/styles/index.styl new file mode 100644 index 00000000..3113dd61 --- /dev/null +++ b/docs/.vuepress/styles/index.styl @@ -0,0 +1,93 @@ +.home-wrapper .banner .banner-conent .hero h1{ + font-size 2.8rem!important +} +// 文档中适配 +table + width auto +.page >*:not(.footer),.card-box + box-shadow: none!important + +.page + @media (min-width $contentWidth + 80) + padding-top $navbarHeight!important +.home-wrapper .banner .banner-conent + padding 0 2.9rem + box-sizing border-box +.home-wrapper .banner .slide-banner .slide-banner-wrapper .slide-item a + h2 + margin-top 2rem + font-size 1.2rem!important + p + padding 0 1rem + +// 评论区颜色重置 +.gt-container + .gt-ico-tip + &::after + content: '。( Win + . ) or ( ⌃ + ⌘ + ␣ ) open Emoji' + color: #999 + .gt-meta + border-color var(--borderColor)!important + .gt-comments-null + color var(--textColor) + opacity .5 + .gt-header-textarea + color var(--textColor) + background rgba(180,180,180,0.1)!important + .gt-btn + border-color $accentColor!important + background-color $accentColor!important + .gt-btn-preview + background-color rgba(255,255,255,0)!important + color $accentColor!important + a + color $accentColor!important + .gt-svg svg + fill $accentColor!important + .gt-comment-content,.gt-comment-admin .gt-comment-content + background-color rgba(150,150,150,0.1)!important + &:hover + box-shadow 0 0 25px rgba(150,150,150,.5)!important + .gt-comment-body + color var(--textColor)!important + + +// qq徽章 +.qq + position: relative; +.qq::after + content: "可撩"; + background: $accentColor; + color:#fff; + padding: 0 5px; + border-radius: 10px; + font-size:12px; + position: absolute; + top: -4px; + right: -35px; + transform:scale(0.85); + +// demo模块图标颜色 +body .vuepress-plugin-demo-block__wrapper + &,.vuepress-plugin-demo-block__display + border-color rgba(160,160,160,.3) + .vuepress-plugin-demo-block__footer:hover + .vuepress-plugin-demo-block__expand::before + border-top-color: $accentColor !important; + border-bottom-color: $accentColor !important; + svg + fill: $accentColor !important; + + +// 全文搜索框 +.suggestions + overflow: auto + max-height: calc(100vh - 6rem) + @media (max-width: 719px) { + width: 90vw; + min-width: 90vw!important; + margin-right: -20px; + } + .highlight + color: $accentColor + font-weight: bold diff --git a/docs/.vuepress/styles/palette.styl b/docs/.vuepress/styles/palette.styl new file mode 100644 index 00000000..d98e697a --- /dev/null +++ b/docs/.vuepress/styles/palette.styl @@ -0,0 +1,62 @@ + +// 原主题变量已弃用,以下是vdoing使用的变量,你可以在这个文件内修改它们。 + +//***vdoing主题-变量***// + +// // 颜色 + +// $bannerTextColor = #fff // 首页banner区(博客标题)文本颜色 +// $accentColor = #11A8CD +// $arrowBgColor = #ccc +// $badgeTipColor = #42b983 +// $badgeWarningColor = darken(#ffe564, 35%) +// $badgeErrorColor = #DA5961 + +// // 布局 +// $navbarHeight = 3.6rem +// $sidebarWidth = 18rem +// $contentWidth = 860px +// $homePageWidth = 1100px +// $rightMenuWidth = 230px // 右侧菜单 + +// // 代码块 +// $lineNumbersWrapperWidth = 2.5rem + +// 浅色模式 +.theme-mode-light + --bodyBg: rgba(255,255,255,1) + --mainBg: rgba(255,255,255,1) + --sidebarBg: rgba(255,255,255,.8) + --blurBg: rgba(255,255,255,.9) + --textColor: #004050 + --textLightenColor: #0085AD + --borderColor: rgba(0,0,0,.15) + --codeBg: #f6f6f6 + --codeColor: #525252 + codeThemeLight() + +// 深色模式 +.theme-mode-dark + --bodyBg: rgba(30,30,34,1) + --mainBg: rgba(30,30,34,1) + --sidebarBg: rgba(30,30,34,.8) + --blurBg: rgba(30,30,34,.8) + --textColor: rgb(140,140,150) + --textLightenColor: #0085AD + --borderColor: #2C2C3A + --codeBg: #252526 + --codeColor: #fff + codeThemeDark() + +// 阅读模式 +.theme-mode-read + --bodyBg: rgba(245,245,213,1) + --mainBg: rgba(245,245,213,1) + --sidebarBg: rgba(245,245,213,.8) + --blurBg: rgba(245,245,213,.9) + --textColor: #004050 + --textLightenColor: #0085AD + --borderColor: rgba(0,0,0,.15) + --codeBg: #282c34 + --codeColor: #fff + codeThemeDark() diff --git a/docs/nosql/nosql-selection.md "b/docs/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/01.Nosql\346\212\200\346\234\257\351\200\211\345\236\213.md" similarity index 92% rename from docs/nosql/nosql-selection.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/01.Nosql\346\212\200\346\234\257\351\200\211\345\236\213.md" index c5788c62..13835ed6 100644 --- a/docs/nosql/nosql-selection.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/01.Nosql\346\212\200\346\234\257\351\200\211\345\236\213.md" @@ -1,8 +1,19 @@ -# Nosql 技术选型 +--- +title: Nosql技术选型 +date: 2020-02-09 02:18:58 +categories: + - 数据库 + - 数据库综合 +tags: + - 数据库 + - 综合 + - Nosql +permalink: /pages/0e1012/ +--- -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209020702.png) +# Nosql 技术选型 -[TOC] +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209020702.png) ## 一、Nosql 简介 @@ -16,7 +27,7 @@ 随着大数据时代的到来,越来越多的网站、应用系统需要支撑海量数据存储,高并发请求、高可用、高可扩展性等特性要求。传统的关系型数据库在应付这些调整已经显得力不从心,暴露了许多能以克服的问题。由此,各种各样的 NoSQL(Not Only SQL)数据库作为传统关系型数据的一个有力补充得到迅猛发展。 -![nosql-history](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209005228.png) +![nosql-history](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209005228.png) **NoSQL,泛指非关系型的数据库**,可以理解为 SQL 的一个有力补充。 @@ -45,7 +56,7 @@ 将表放入存储系统中有两种方法,而我们绝大部分是采用行存储的。 行存储法是将各行放入连续的物理位置,这很像传统的记录和文件系统。 列存储法是将数据按照列存储到数据库中,与行存储类似,下图是两种存储方法的图形化解释: -![按行存储和按列存储模式](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209005316.png) +![按行存储和按列存储模式](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209005316.png) ### 列式数据库产品 @@ -69,13 +80,13 @@ 列式数据库由于其针对不同列的数据特征而发明的不同算法,使其**往往有比行式数据库高的多的压缩率**,普通的行式数据库一般压缩率在 3:1 到 5:1 左右,而列式数据库的压缩率一般在 8:1 到 30:1 左右。 比较常见的,通过字典表压缩数据: 下面中才是那张表本来的样子。经过字典表进行数据压缩后,表中的字符串才都变成数字了。正因为每个字符串在字典表里只出现一次了,所以达到了压缩的目的(有点像规范化和非规范化 Normalize 和 Denomalize) -![通过字典表压缩数据](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209005406.png) +![通过字典表压缩数据](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209005406.png) - **查询效率高** 读取多条数据的同一列效率高,因为这些列都是存储在一起的,一次磁盘操作可以数据的指定列全部读取到内存中。 下图通过一条查询的执行过程说明列式存储(以及数据压缩)的优点 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209005611.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209005611.png) ``` 执行步骤如下: @@ -116,19 +127,19 @@ KV 存储非常适合存储**不涉及过多数据关系业务关系的数据** - Redis - ![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209010410.png) + ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209010410.png) Redis 是一个使用 ANSI C 编写的开源、支持网络、基于内存、可选持久性的键值对存储数据库。从 2015 年 6 月开始,Redis 的开发由 Redis Labs 赞助,而 2013 年 5 月至 2015 年 6 月期间,其开发由 Pivotal 赞助。在 2013 年 5 月之前,其开发由 VMware 赞助。根据月度排行网站 DB-Engines.com 的数据显示,Redis 是最流行的键值对存储数据库。 - Cassandra - ![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209010451.png) + ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209010451.png) Apache Cassandra(社区内一般简称为 C\*)是一套开源分布式 NoSQL 数据库系统。它最初由 Facebook 开发,用于储存收件箱等简单格式数据,集 Google BigTable 的数据模型与 Amazon Dynamo 的完全分布式架构于一身。Facebook 于 2008 将 Cassandra 开源,此后,由于 Cassandra 良好的可扩展性和性能,被 Apple, Comcast,Instagram, Spotify, eBay, Rackspace, Netflix 等知名网站所采用,成为了一种流行的分布式结构化数据存储方案。 - LevelDB - ![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209011140.png) + ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209011140.png) LevelDB 是一个由 Google 公司所研发的键/值对(Key/Value Pair)嵌入式数据库管理系统编程库, 以开源的 BSD 许可证发布。 @@ -165,13 +176,13 @@ KV 存储非常适合存储**不涉及过多数据关系业务关系的数据** - MongoDB - ![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209012320.png) + ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209012320.png) **MongoDB**是一种面向文档的数据库管理系统,由 C++ 撰写而成,以此来解决应用程序开发社区中的大量现实问题。2007 年 10 月,MongoDB 由 10gen 团队所发展。2009 年 2 月首度推出。 - CouchDB - ![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209012418.png) + ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209012418.png) Apache CouchDB 是一个开源数据库,专注于易用性和成为"**完全拥抱 web 的数据库**"。它是一个使用 JSON 作为存储格式,JavaScript 作为查询语言,MapReduce 和 HTTP 作为 API 的 NoSQL 数据库。其中一个显著的功能就是多主复制。CouchDB 的第一个版本发布在 2005 年,在 2008 年成为了 Apache 的项目。 @@ -223,11 +234,11 @@ MongonDB 还是支持多文档事务的 Consistency(一致性)和 Durability(持 现在有如下文档集合: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209014530.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209014530.png) 正排索引得到索引如下: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209014723.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209014723.png) 可见,正排索引适用于根据文档名称查询文档内容 @@ -237,7 +248,7 @@ MongonDB 还是支持多文档事务的 Consistency(一致性)和 Durability(持 带有单词频率信息的倒排索引如下: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209014842.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209014842.png) 可见,倒排索引适用于根据关键词来查询文档内容 @@ -251,7 +262,7 @@ MongonDB 还是支持多文档事务的 Consistency(一致性)和 Durability(持 - Solr - ![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209014947.png) + ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209014947.png) Solr 是 Apache Lucene 项目的开源企业搜索平台。其主要功能包括全文检索、命中标示、分面搜索、动态聚类、数据库集成,以及富文本(如 Word、PDF)的处理。Solr 是高度可扩展的,并提供了分布式搜索和索引复制 @@ -285,7 +296,7 @@ MongonDB 还是支持多文档事务的 Consistency(一致性)和 Durability(持 ## 六、图数据库 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209015751.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209015751.png) **图形数据库应用图论存储实体之间的关系信息**。最常见例子就是社会网络中人与人之间的关系。关系型数据库用于存储“关系型”数据的效果并不好,其查询复杂、缓慢、超出预期,而图形数据库的独特设计恰恰弥补了这个缺陷,解决关系型数据库存储和处理复杂关系型数据功能较弱的问题。 @@ -293,19 +304,19 @@ MongonDB 还是支持多文档事务的 Consistency(一致性)和 Durability(持 - Neo4j - ![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209015817.png) + ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209015817.png) Neo4j 是由 Neo4j,Inc。开发的图形数据库管理系统。由其开发人员描述为具有原生图存储和处理的符合 ACID 的事务数据库,根据 DB-Engines 排名, Neo4j 是最流行的图形数据库。 - ArangoDB - ![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209015858.png) + ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209015858.png) ArangoDB 是由 triAGENS GmbH 开发的原生多模型数据库系统。数据库系统支持三个重要的数据模型(键/值,文档,图形),其中包含一个数据库核心和统一查询语言 AQL(ArangoDB 查询语言)。查询语言是声明性的,允许在单个查询中组合不同的数据访问模式。ArangoDB 是一个 NoSQL 数据库系统,但 AQL 在很多方面与 SQL 类似。 - Titan - ![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209015923.png) + ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209015923.png) Titan 是一个可扩展的图形数据库,针对存储和查询包含分布在多机群集中的数百亿个顶点和边缘的图形进行了优化。Titan 是一个事务性数据库,可以支持数千个并发用户实时执行复杂的图形遍历。 @@ -367,7 +378,7 @@ Neo4j 中,存储节点时使用了”index-free adjacency”,即每个节点 - **大流量系统** - 如电商单品页,后台考虑选关系型数据库,前台考虑选内存型数据库。 - **日志型系统** - 原始数据考虑选列式数据库,日志搜索考虑选搜索引擎。 - **搜索型系统** - 例如站内搜索,非通用搜索,如商品搜索,后台考虑选关系型数据库,前台考虑选搜索引擎。 -- **事务型系统** - 如库存,交易,记账,考虑选关系型数据库+K-V数据库(作为缓存)+分布式事务。 +- **事务型系统** - 如库存,交易,记账,考虑选关系型数据库+K-V 数据库(作为缓存)+分布式事务。 - **离线计算** - 如大量数据分析,考虑选列式数据库或关系型数据。 - **实时计算** - 如实时监控,可以考虑选内存型数据库或者列式数据库。 @@ -375,4 +386,4 @@ Neo4j 中,存储节点时使用了”index-free adjacency”,即每个节点 ## 参考资料 -- [NoSQL 还是 SQL ?这一篇讲清楚](https://juejin.im/post/5b6d62ddf265da0f491bd200) +- [NoSQL 还是 SQL ?这一篇讲清楚](https://juejin.im/post/5b6d62ddf265da0f491bd200) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/02.\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\346\225\260\346\215\256\345\272\223\347\264\242\345\274\225.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/02.\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\346\225\260\346\215\256\345\272\223\347\264\242\345\274\225.md" new file mode 100644 index 00000000..6a6b7ed9 --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/02.\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\346\225\260\346\215\256\345\272\223\347\264\242\345\274\225.md" @@ -0,0 +1,217 @@ +--- +title: 数据结构与数据库索引 +date: 2022-03-27 23:39:10 +categories: + - 数据库 + - 数据库综合 +tags: + - 数据库 + - 综合 + - 数据结构 + - 索引 +permalink: /pages/d7cd88/ +--- + +# 数据结构与数据库索引 + +> 关键词:链表、数组、散列表、红黑树、B+ 树、LSM 树、跳表 + +## 引言 + +**数据库**是“按照 **数据结构** 来组织、存储和管理数据的仓库”。是一个长期存储在计算机内的、有组织的、可共享的、统一管理的大量数据的集合。 + +——上面这句定义对数据库的定义来自百度百科。通过这个定义,我们也能明显看出数据结构是实现数据库的基石。 + +从本质来看,数据库只负责两件事:读数据、写数据;而数据结构研究的是如何合理组织数据,尽可能提升读、写数据的效率,这恰好是数据库的核心问题。因此,数据结构与数据库这两个领域有非常多的交集。其中,数据库索引最能体现二者的紧密关联。 + +**索引是数据库为了提高查找效率的一种数据结构**。索引基于原始数据衍生而来,它的主要作用是缩小检索的数据范围,提升查询性能。通俗来说,索引在数据库中的作用就像是一本书的目录索引。索引对于良好的性能非常关键,在数据量小且负载较低时,不恰当的索引对于性能的影响可能还不明显;但随着数据量逐渐增大,性能则会急剧下降。因此,**索引优化应该是查询性能优化的最有效手段**。 + +很多数据库允许单独添加和删除索引,而不影响数据库的内容,它只会影响查询性能。维护额外的结构势必会引入开销,特别是在新数据写入时。对于写入,它很难超过简单地追加文件方式的性能,因为那已经是最简单的写操作了。由于每次写数据时,需要更新索引,因此任何类型的索引通常都会降低写的速度。 + +本文以一些常见的数据库为例,分析它们的索引采用了什么样的数据结构,有什么利弊,为何如此设计。 + +## 数组和链表 + +数组和链表分别代表了连续空间和不连续空间的存储方式,它们是线性表(Linear List)的典型代表。其他所有的数据结构,比如栈、队列、二叉树、B+ 树等,实际上都是这两者的结合和变化。 + +**数组用连续的内存空间来存储数据**。数组**支持随机访问,根据下标随机访问的时间复杂度为 `O(1)`**。但这并不代表数组的查找时间复杂度也是 `O(1)`。 + +- **对于无序数组,只能顺序查找,其时间复杂度为 `O(n)`**。 +- **对于有序数组,可以应用二分查找法,其时间复杂度为 `O(log n)`**。 + +在有序数组上应用二分查找法如此高效,为什么几乎没有数据库直接使用数组作为索引?这是因为它的限制条件:**数据有序**——为了保证数据有序,每次添加、删除数组数据时,都必须要进行数据调整,来保证其有序,而 **数组的插入/删除操作,时间复杂度为 `O(n)`**。此外,由于数组空间大小固定,每次扩容只能采用复制数组的方式。数组的这些特性,决定了它不适合用于数据频繁变化的应用场景。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320115836.png) + +**链表用不连续的内存空间来存储数据;并通过一个指针按顺序将这些空间串起来,形成一条链**。 + +区别于数组,链表中的元素不是存储在内存中连续的一片区域,链表中的数据存储在每一个称之为「结点」复合区域里,在每一个结点除了存储数据以外,还保存了到下一个节点的指针(Pointer)。由于不必按顺序存储,**链表的插入/删除操作,时间复杂度为 `O(1)`**,但是,链表只支持顺序访问,其 **查找时间复杂度为 `O(n)`**。其低效的查找方式,决定了链表不适合作为索引。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320174829.png) + +## 哈希索引 + +哈希表是一种以键 - 值(key-value)对形式存储数据的结构,我们只要输入待查找的值即 key,就可以找到其对应的值即 Value。 + +**哈希表** 使用 **哈希函数** 组织数据,以支持快速插入和搜索的数据结构。哈希表的本质是一个数组,其思路是:使用 Hash 函数将 Key 转换为数组下标,利用数组的随机访问特性,使得我们能在 `O(1)` 的时间代价内完成检索。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320201844.png) + +有两种不同类型的哈希表:**哈希集合** 和 **哈希映射**。 + +- **哈希集合** 是集合数据结构的实现之一,用于存储非重复值。 +- **哈希映射** 是映射 数据结构的实现之一,用于存储键值对。 + +哈希索引基于哈希表实现,**只适用于等值查询**。对于每一行数据,哈希索引都会将所有的索引列计算一个哈希码(`hashcode`),哈希码是一个较小的值。哈希索引将所有的哈希码存储在索引中,同时在哈希表中保存指向每个数据行的指针。 + +✔ 哈希索引的**优点**: + +- 因为索引数据结构紧凑,所以**查询速度非常快**。 + +❌ 哈希索引的**缺点**: + +- 哈希索引值包含哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行。不过,访问内存中的行的速度很快,所以大部分情况下这一点对性能影响不大。 +- **哈希索引数据不是按照索引值顺序存储的**,所以**无法用于排序**。 +- 哈希索引**不支持部分索引匹配查找**,因为哈希索引时使用索引列的全部内容来进行哈希计算的。如,在数据列 (A,B) 上建立哈希索引,如果查询只有数据列 A,无法使用该索引。 +- 哈希索引**只支持等值比较查询**,包括 `=`、`IN()`、`<=>`;不支持任何范围查询,如 `WHERE price > 100`。 +- 哈希索引有**可能出现哈希冲突** + - 出现哈希冲突时,必须遍历链表中所有的行指针,逐行比较,直到找到符合条件的行。 + - 如果哈希冲突多的话,维护索引的代价会很高。 + +> 因为种种限制,所以哈希索引只适用于特定的场合。而一旦使用哈希索引,则它带来的性能提升会非常显著。例如,Mysql 中的 Memory 存储引擎就显示的支持哈希索引。 + +## B-Tree 索引 + +通常我们所说的 B 树索引是指 `B-Tree` 索引,它是目前关系型数据库中查找数据最为常用和有效的索引,大多数存储引擎都支持这种索引。使用 `B-Tree` 这个术语,是因为 MySQL 在 `CREATE TABLE` 或其它语句中使用了这个关键字,但实际上不同的存储引擎可能使用不同的数据结构,比如 InnoDB 使用的是 `B+Tree`索引;而 MyISAM 使用的是 `B-Tree`索引。 + +`B-Tree` 索引中的 B 是指 `balance`,意为平衡。需要注意的是,`B-Tree` 索引并不能找到一个给定键值的具体行,它找到的只是被查找数据行所在的页,接着数据库会把页读入到内存,再在内存中进行查找,最后得到要查找的数据。 + +### 二叉搜索树 + +二叉搜索树的特点是:每个节点的左儿子小于父节点,父节点又小于右儿子。其查询时间复杂度是 `O(log n)`。 + +当然为了维持 `O(log n)` 的查询复杂度,你就需要保持这棵树是平衡二叉树。为了做这个保证,更新的时间复杂度也是 `O(log n)`。 + +随着数据库中数据的增加,索引本身大小随之增加,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘 I/O 消耗,相对于内存存取,I/O 存取的消耗要高几个数量级。可以想象一下一棵几百万节点的二叉树的深度是多少?如果将这么大深度的一颗二叉树放磁盘上,每读取一个节点,需要一次磁盘的 I/O 读取,整个查找的耗时显然是不能够接受的。那么如何减少查找过程中的 I/O 存取次数? + +一种行之有效的解决方法是减少树的深度,将**二叉树变为 N 叉树**(多路搜索树),而 **B+ 树就是一种多路搜索树**。 + +### `B+Tree` 索引 + +B+ 树索引适用于**全键值查找**、**键值范围查找**和**键前缀查找**,其中键前缀查找只适用于最左前缀查找。 + +理解 `B+Tree`,只需要理解其最重要的两个特征即可: + +- 第一,所有的关键字(可以理解为数据)都存储在叶子节点,非叶子节点并不存储真正的数据,所有记录节点都是按键值大小顺序存放在同一层叶子节点上。 +- 其次,所有的叶子节点由指针连接。如下图为简化了的`B+Tree`。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200304235424.jpg) + +根据叶子节点的内容,索引类型分为主键索引和非主键索引。 + +- **聚簇索引(clustered)**:又称为主键索引,其叶子节点存的是整行数据。因为无法同时把数据行存放在两个不同的地方,所以**一个表只能有一个聚簇索引**。**InnoDB 的聚簇索引实际是在同一个结构中保存了 B 树的索引和数据行**。 +- 非主键索引的叶子节点内容是主键的值。在 InnoDB 里,非主键索引也被称为**二级索引(secondary)**。数据存储在一个位置,索引存储在另一个位置,索引中包含指向数据存储位置的指针。可以有多个,小于 249 个。 + +**聚簇表示数据行和相邻的键值紧凑地存储在一起,因为数据紧凑,所以访问快**。因为无法同时把数据行存放在两个不同的地方,所以**一个表只能有一个聚簇索引**。 + +**聚簇索引和非聚簇索引的查询有什么区别** + +- 如果语句是 `select * from T where ID=500`,即聚簇索引查询方式,则只需要搜索 ID 这棵 B+ 树; +- 如果语句是 `select * from T where k=5`,即非聚簇索引查询方式,则需要先搜索 k 索引树,得到 ID 的值为 500,再到 ID 索引树搜索一次。这个过程称为**回表**。 + +也就是说,**基于非聚簇索引的查询需要多扫描一棵索引树**。因此,我们在应用中应该尽量使用主键查询。 + +**显然,主键长度越小,非聚簇索引的叶子节点就越小,非聚簇索引占用的空间也就越小。** + +自增主键是指自增列上定义的主键,在建表语句中一般是这么定义的: NOT NULL PRIMARY KEY AUTO_INCREMENT。从性能和存储空间方面考量,自增主键往往是更合理的选择。有没有什么场景适合用业务字段直接做主键的呢?还是有的。比如,有些业务的场景需求是这样的: + +- 只有一个索引; +- 该索引必须是唯一索引。 + +由于没有其他索引,所以也就不用考虑其他索引的叶子节点大小的问题。这时候我们就要优先考虑上一段提到的“尽量使用主键查询”原则,直接将这个索引设置为主键,可以避免每次查询需要搜索两棵树。 + +--- + +内存是半导体元件。对于内存而言,只要给出了内存地址,我们就可以直接访问该地址取出数据。这个过程具有高效的随机访问特性,因此内存也叫随机访问存储器(Random Access Memory,即 RAM)。内存的访问速度很快,但是价格相对较昂贵,因此一般的计算机内存空间都相对较小。 + +而磁盘是机械器件。磁盘访问数据时,需要等磁盘盘片旋转到磁头下,才能读取相应的数据。尽管磁盘的旋转速度很快,但是和内存的随机访问相比,性能差距非常大。一般来说,如果是随机读写,会有 10 万到 100 万倍左右的差距。但如果是顺序访问大批量数据的话,磁盘的性能和内存就是一个数量级的。 + +磁盘的最小读写单位是扇区,较早期的磁盘一个扇区是 **`512`** 字节。随着磁盘技术的发展,目前常见的磁盘扇区是 **`4K`** 个字节。操作系统一次会读写多个扇区,所以操作系统的最小读写单位是块(Block),也叫作簇(Cluster)。当我们要从磁盘中读取一个数据时,操作系统会一次性将整个块都读出来。因此,对于大批量的顺序读写来说,磁盘的效率会比随机读写高许多。 + +假设有一个有序数组存储在硬盘中,如果它足够大,那么它会存储在多个块中。当我们要对这个数组使用二分查找时,需要先找到中间元素所在的块,将这个块从磁盘中读到内存里,然后在内存中进行二分查找。如果下一步要读的元素在其他块中,则需要再将相应块从磁盘中读入内存。直到查询结束,这个过程可能会多次访问磁盘。我们可以看到,这样的检索性能非常低。 + +由于磁盘相对于内存而言访问速度实在太慢,因此,对于磁盘上数据的高效检索,我们有一个极其重要的原则:对磁盘的访问次数要尽可能的少! + +将索引和数据分离就是一种常见的设计思路。在数据频繁变化的场景中,有序数组并不是一个最好的选择,二叉检索树或者哈希表往往更有普适性。但是,哈希表由于缺乏范围检索的能力,在一些场合也不适用。因此,二叉检索树这种树形结构是许多常见检索系统的实施方案。 + +随着索引数据越来越大,直到无法完全加载到内存中,这是需要将索引数据也存入磁盘中。B+ 树给出了将树形索引的所有节点都存在磁盘上的高效检索方案。操作系统对磁盘数据的访问是以块为单位的。因此,如果我们想将树型索引的一个节点从磁盘中读出,即使该节点的数据量很小(比如说只有几个字节),但磁盘依然会将整个块的数据全部读出来,而不是只读这一小部分数据,这会让有效读取效率很低。B+ 树的一个关键设计,就是让一个节点的大小等于一个块的大小。节点内存储的数据,不是一个元素,而是一个可以装 m 个元素的有序数组。这样一来,我们就可以将磁盘一次读取的数据全部利用起来,使得读取效率最大化。 + +B+ 树还有另一个设计,就是将所有的节点分为内部节点和叶子节点。内部节点仅存储 key 和维持树形结构的指针,并不存储 key 对应的数据(无论是具体数据还是文件位置信息)。这样内部节点就能存储更多的索引数据,我们也就可以使用最少的内部节点,将所有数据组织起来了。而叶子节点仅存储 key 和对应数据,不存储维持树形结构的指针。通过这样的设计,B+ 树就能做到节点的空间利用率最大化。此外,B+ 树还将同一层的所有节点串成了有序的双向链表,这样一来,B+ 树就同时具备了良好的范围查询能力和灵活调整的能力了。 + +因此,B+ 树是一棵完全平衡的 m 阶多叉树。所谓的 m 阶,指的是每个节点最多有 m 个子节点,并且每个节点里都存了一个紧凑的可包含 m 个元素的数组。 + +即使是复杂的 B+ 树,我们将它拆解开来,其实也是由简单的数组、链表和树组成的,而且 B+ 树的检索过程其实也是二分查找。因此,如果 B+ 树完全加载在内存中的话,它的检索效率其实并不会比有序数组或者二叉检索树更 +高,也还是二分查找的 log(n) 的效率。并且,它还比数组和二叉检索树更加复杂,还会带来额外的开销。 + +另外,这一节还有一个很重要的设计思想需要你掌握,那就是将索引和数据分离。通过这样的方式,我们能将索引的数组大小保持在一个较小的范围内,让它能加载在内存中。在许多大规模系统中,都是使用这个设计思想来精简索引的。而且,B+ 树的内部节点和叶子节点的区分,其实也是索引和数据分离的一次实践。 + +MySQL 中的 B+ 树实现其实有两种,一种是 MyISAM 引擎,另一种是 InnoDB 引擎。它们的核心区别就在于,数据和索引是否是分离的。 + +在 MyISAM 引擎中,B+ 树的叶子节点仅存储了数据的位置指针,这是一种索引和数据分离的设计方案,叫作非聚集索引。如果要保证 MyISAM 的数据一致性,那我们需要在表级别上进行加锁处理。 + +在 InnoDB 中,B+ 树的叶子节点直接存储了具体数据,这是一种索引和数据一体的方案。叫作聚集索引。由于数据直接就存在索引的叶子节点中,因此 InnoDB 不需要给全表加锁来保证一致性,它只需要支持行级的锁就可以了。 + +## LSM 树 + +B+ 树的数据都存储在叶子节点中,而叶子节点一般都存储在磁盘中。因此,每次插入的新数据都需要随机写入磁盘,而随机写入的性能非常慢。如果是一个日志系统,每秒钟要写入上千条甚至上万条数据,这样的磁盘操作代价会使得系统性能急剧下降,甚至无法使用。 + +操作系统对磁盘的读写是以块为单位的,我们能否以块为单位写入,而不是每次插入一个数据都要随机写入磁盘呢?这样是不是就可以大幅度减少写入操作了呢?解决方案就是:**LSM 树**(Log Structured Merge Trees)。 + +LSM 树就是根据这个思路设计了这样一个机制:当数据写入时,延迟写磁盘,将数据先存放在内存中的树里,进行常规的存储和查询。当内存中的树持续变大达到阈值时,再批量地以块为单位写入磁盘的树中。因此,LSM 树至少需要由两棵树组成,一棵是存储在内存中较小的 C0 树,另一棵是存储在磁盘中较大的 C1 树。 + +LSM 树具有以下 3 个特点: + +1. 将索引分为内存和磁盘两部分,并在内存达到阈值时启动树合并(Merge Trees); +2. 用批量写入代替随机写入,并且用预写日志 WAL 技术(Write AheadLog,预写日志技术)保证内存数据,在系统崩溃后可以被恢复; +3. 数据采取类似日志追加写的方式写入(Log Structured)磁盘,以顺序写的方式提高写 + 入效率。 + +LSM 树的这些特点,使得它相对于 B+ 树,在写入性能上有大幅提升。所以,许多 NoSQL 系统都使用 LSM 树作为检索引擎,而且还对 LSM 树进行了优化以提升检索性能。 + +## 倒排索引 + +倒排索引的核心其实并不复杂,它的具体实现其实是哈希表,只是它不是将文档 ID 或者题目作为 key,而是反过来,通过将内容或者属性作为 key 来存储对应的文档列表,使得我们能在 O(1) 的时间代价内完成查询。 + +尽管原理并不复杂,但是倒排索引是许多检索引擎的核心。比如说,数据库的全文索引功能、搜索引擎的索引、广告引擎和推荐引擎,都使用了倒排索引技术来实现检索功能。 + +## 索引的维护 + +### 创建索引 + +- **数据压缩**:一个是尽可能地将数据加载到内存中,因为内存的检索效率大大高于磁盘。那为了将数据更多地加载到内存中,索引压缩是一个重要的研究方向。 +- **分支处理**:另一个是将大数据集合拆成多个小数据集合来处理。这其实就是分布式系统的核心思想。 + +### 更新索引 + +(1)Double Buffer(双缓冲)机制 + +就是在内存中同时保存两份一样的索引,一个是索引 A,一个是索引 B。两个索引保持一个读、一个写,并且来回切换,最终完成高性能的索引更新。 + +优点:简单高效 + +缺点:达到一定数据量级后,会带来翻倍的内存开销,甚至有些索引存储在磁盘上的情况下,更是无法使用此机制。 + +(2)全量索引和增量索引 + +将新接收到的数据单独建立一个可以存在内存中的倒排索引,也就是增量索引。当查询发生的时候,我们会同时查询全量索引和增量索引,将合并的结果作为总的结果输出。 + +因为增量索引相对全量索引而言会小很多,内存资源消耗在可承受范围,所以我们可以使用 Double Buffer 机制 +对增量索引进行索引更新。这样一来,增量索引就可以做到无锁访问。而全量索引本身就是只读的,也不需要加锁。因此,整个检索过程都可以做到无锁访问,也就提高了系统的检索效率。 + +## 参考资料 + +- [《数据密集型应用系统设计》](https://book.douban.com/subject/30329536/) +- [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) +- [检索技术核心 20 讲](https://time.geekbang.org/column/intro/100048401) +- [Data Structures for Databases](https://www.cise.ufl.edu/~mschneid/Research/papers/HS05BoCh.pdf) +- [Data Structures and Algorithms for Big Databases](https://people.csail.mit.edu/bradley/BenderKuszmaul-tutorial-xldb12.pdf) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/README.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/README.md" new file mode 100644 index 00000000..d2b801e8 --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/README.md" @@ -0,0 +1,25 @@ +--- +title: 数据库综合 +date: 2022-04-11 16:52:35 +categories: + - 数据库 + - 数据库综合 +tags: + - 数据库 + - 综合 +permalink: /pages/3c3c45/ +hidden: true +--- + +# 数据库综合 + +## 📖 内容 + +- [Nosql 技术选型](01.Nosql技术选型.md) +- [数据结构与数据库索引](02.数据结构与数据库索引.md) + +## 📚 资料 + +## 🚪 传送 + +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file diff --git a/docs/middleware/shardingsphere.md "b/docs/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/01.Shardingsphere/01.ShardingSphere\347\256\200\344\273\213.md" similarity index 85% rename from docs/middleware/shardingsphere.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/01.Shardingsphere/01.ShardingSphere\347\256\200\344\273\213.md" index fec0e7ec..f8fe8d6f 100644 --- a/docs/middleware/shardingsphere.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/01.Shardingsphere/01.ShardingSphere\347\256\200\344\273\213.md" @@ -1,4 +1,18 @@ -# ShardingSphere +--- +title: ShardingSphere 简介 +date: 2020-10-08 20:30:30 +categories: + - 数据库 + - 数据库中间件 + - Shardingsphere +tags: + - 数据库 + - 中间件 + - 分库分表 +permalink: /pages/5ed2a2/ +--- + +# ShardingSphere 简介 ## 简介 @@ -6,7 +20,7 @@ ShardingSphere 是一套开源的分布式数据库中间件解决方案组成的生态圈,它由 Sharding-JDBC、Sharding-Proxy 和 Sharding-Sidecar(计划中)这 3 款相互独立的产品组成。 他们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201008151613.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201008151613.png) #### ShardingSphere-JDBC @@ -16,7 +30,7 @@ ShardingSphere 是一套开源的分布式数据库中间件解决方案组成 - 支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP 等。 - 支持任意实现 JDBC 规范的数据库,目前支持 MySQL,Oracle,SQLServer,PostgreSQL 以及任何遵循 SQL92 标准的数据库。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201008151213.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201008151213.png) #### Sharding-Proxy @@ -25,7 +39,7 @@ ShardingSphere 是一套开源的分布式数据库中间件解决方案组成 - 向应用程序完全透明,可直接当做 MySQL/PostgreSQL 使用。 - 适用于任何兼容 MySQL/PostgreSQL 协议的的客户端。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201008151434.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201008151434.png) #### Sharding-Sidecar(TODO) @@ -33,7 +47,7 @@ ShardingSphere 是一套开源的分布式数据库中间件解决方案组成 Database Mesh 的关注重点在于如何将分布式的数据访问应用与数据库有机串联起来,它更加关注的是交互,是将杂乱无章的应用与数据库之间的交互进行有效地梳理。 使用 Database Mesh,访问数据库的应用和数据库终将形成一个巨大的网格体系,应用和数据库只需在网格体系中对号入座即可,它们都是被啮合层所治理的对象。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201008151557.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201008151557.png) | _Sharding-JDBC_ | _Sharding-Proxy_ | _Sharding-Sidecar_ | | | :-------------- | :--------------- | :----------------- | ------ | @@ -50,7 +64,7 @@ ShardingSphere-JDBC 采用无中心化架构,适用于 Java 开发的高性能 Apache ShardingSphere 是多接入端共同组成的生态圈。 通过混合使用 ShardingSphere-JDBC 和 ShardingSphere-Proxy,并采用同一注册中心统一配置分片策略,能够灵活的搭建适用于各种场景的应用系统,使得架构师更加自由地调整适合与当前业务的最佳系统架构。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201008151658.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201008151658.png) ### 功能列表 @@ -77,4 +91,4 @@ Apache ShardingSphere 是多接入端共同组成的生态圈。 通过混合使 ## 参考资料 - [shardingsphere Github](https://github.com/apache/incubator-shardingsphere) -- [shardingsphere 官方文档](https://shardingsphere.apache.org/document/current/cn/overview/) +- [shardingsphere 官方文档](https://shardingsphere.apache.org/document/current/cn/overview/) \ No newline at end of file diff --git a/docs/middleware/shardingsphere-jdbc.md "b/docs/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/01.Shardingsphere/02.ShardingSphereJdbc.md" similarity index 96% rename from docs/middleware/shardingsphere-jdbc.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/01.Shardingsphere/02.ShardingSphereJdbc.md" index 14702801..a7247d00 100644 --- a/docs/middleware/shardingsphere-jdbc.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/01.Shardingsphere/02.ShardingSphereJdbc.md" @@ -1,4 +1,18 @@ -# shardingsphere-jdbc +--- +title: ShardingSphere Jdbc +date: 2020-12-28 00:01:28 +categories: + - 数据库 + - 数据库中间件 + - Shardingsphere +tags: + - 数据库 + - 中间件 + - 分库分表 +permalink: /pages/8448de/ +--- + +# ShardingSphere Jdbc ## 简介 @@ -8,7 +22,7 @@ shardingsphere-jdbc 定位为轻量级 Java 框架,在 Java 的 JDBC 层提供 - 支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP 等。 - 支持任意实现 JDBC 规范的数据库,目前支持 MySQL,Oracle,SQLServer,PostgreSQL 以及任何遵循 SQL92 标准的数据库。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201008151213.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201008151213.png) ## 快速入门 @@ -74,7 +88,7 @@ DataSource dataSource = ShardingSphereDataSourceFactory.createDataSource(dataSou ShardingSphere 的 3 个产品的数据分片主要流程是完全一致的。 核心由 `SQL 解析 => 执行器优化 => SQL 路由 => SQL 改写 => SQL 执行 => 结果归并`的流程组成。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201008153551.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201008153551.png) - QL 解析:分为词法解析和语法解析。 先通过词法解析器将 SQL 拆分为一个个不可再分的单词。再使用语法解析器对 SQL 进行理解,并最终提炼出解析上下文。 解析上下文包括表、选择项、排序项、分组项、聚合函数、分页信息、查询条件以及可能需要修改的占位符的标记。 - 执行器优化:合并和优化分片条件,如 OR 等。 @@ -123,4 +137,4 @@ SQL 解析作为分库分表类产品的核心,其性能和兼容性是最重 ### 执行引擎 -### 归并引擎 +### 归并引擎 \ No newline at end of file diff --git a/docs/middleware/flyway.md "b/docs/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/02.Flyway.md" similarity index 96% rename from docs/middleware/flyway.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/02.Flyway.md" index e1555c53..6e953e2b 100644 --- a/docs/middleware/flyway.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/02.Flyway.md" @@ -1,8 +1,19 @@ -# Flyway +--- +title: 版本管理中间件 Flyway +date: 2019-08-22 09:02:39 +categories: + - 数据库 + - 数据库中间件 +tags: + - 数据库 + - 中间件 + - 版本管理 +permalink: /pages/e2648c/ +--- + +# 版本管理中间件 Flyway > Flyway 是一个数据迁移工具。 -> -> 关键词: ## 简介 @@ -386,7 +397,7 @@ migrations 最常用的编写形式就是 SQL。 为了被 Flyway 自动识别,SQL migrations 的文件命名必须遵循规定的模式: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/flyway/sql-migrations.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/flyway/sql-migrations.png) - **Prefix** - `V` 代表 versioned migrations (可配置), `U` 代表 undo migrations (可配置)、 `R` 代表 repeatable migrations (可配置) - **Version** - 版本号通过`.`(点)或`_`(下划线)分隔 (repeatable migrations 不需要) @@ -405,7 +416,7 @@ migrations 最常用的编写形式就是 SQL。 为了被 Flyway 自动识别,JAVA migrations 的文件命名必须遵循规定的模式: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/flyway/java-migrations.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/flyway/java-migrations.png) - **Prefix** - `V` 代表 versioned migrations (可配置), `U` 代表 undo migrations (可配置)、 `R` 代表 repeatable migrations (可配置) - **Version** - 版本号通过`.`(点)或`_`(下划线)分隔 (repeatable migrations 不需要) @@ -490,4 +501,4 @@ Flyway 的功能主要围绕着 7 个基本命令:[Migrate](https://flywaydb.o ## :door: 传送门 -| [我的 Github 博客](https://github.com/dunwu/blog) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) | +| [钝悟的博客](https://dunwu.github.io/blog/) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) | \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/README.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/README.md" new file mode 100644 index 00000000..cdadf2f4 --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/README.md" @@ -0,0 +1,33 @@ +--- +title: 数据库中间件和代理 +date: 2022-04-11 16:52:35 +categories: + - 数据库 + - 数据库中间件 +tags: + - 数据库 + - 中间件 +permalink: /pages/addb05/ +hidden: true +--- + +# 数据库中间件和代理 + +## 📖 内容 + +- [ShardingSphere 简介](01.Shardingsphere/01.ShardingSphere简介.md) +- [ShardingSphere Jdbc](01.Shardingsphere/02.ShardingSphereJdbc.md) +- [版本管理中间件 Flyway](02.Flyway.md) + +## 📚 资料 + +- [**Seata**](https://github.com/seata/seata) - 分布式事务中间件。 +- [**ShardingSphere**](https://github.com/apache/shardingsphere) - 关系型数据库读写分离、分库分表中间件。 +- [**Flyway**](https://github.com/flyway/flyway) - 关系型数据库版本管理中间件。 +- [**Canal**](https://github.com/alibaba/canal) - 基于 MySQL 的 binlog,提供增量数据订阅和消费。 +- [**Twemproxy**](https://github.com/twitter/twemproxy) - Twitter 开源的一个 Redis 和 Memcache 的中间代理服务。 +- [**Codis**](https://github.com/CodisLabs/codis) - Redis 分布式集群方案。 + +## 🚪 传送 + +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file diff --git a/docs/sql/common/sql-interview.md "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/01.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223\351\235\242\350\257\225.md" similarity index 90% rename from docs/sql/common/sql-interview.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/01.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223\351\235\242\350\257\225.md" index af562b1b..81f9a582 100644 --- a/docs/sql/common/sql-interview.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/01.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223\351\235\242\350\257\225.md" @@ -1,6 +1,20 @@ -# 关系型数据库面试题 +--- +title: 关系型数据库面试 +date: 2020-01-15 23:21:02 +categories: + - 数据库 + - 关系型数据库 + - 综合 +tags: + - 数据库 + - 关系型数据库 + - 面试 +permalink: /pages/9bb28f/ +--- + +# 关系型数据库面试 -## 一、索引和约束 +## 索引和约束 ### 什么是索引 @@ -72,7 +86,7 @@ 对于任意结点,其内部的关键字 Key 是升序排列的。每个节点中都包含了 data。
- +
对于每个结点,主要包含一个关键字数组 `Key[]`,一个指针数组(指向儿子)`Son[]`。 @@ -91,7 +105,7 @@ B+Tree 是 B-Tree 的变种: - 非叶子节点不存储 data,只存储 key;叶子节点不存储指针。
- +
由于并不是所有节点都具有相同的域,因此 B+Tree 中叶节点和内节点一般大小不同。这点与 B-Tree 不同,虽然 B-Tree 中不同节点存放的 key 和指针可能数量不一致,但是每个节点的域和上限是一致的,所以在实现中 B-Tree 往往对每个节点申请同等大小的空间。 @@ -101,7 +115,7 @@ B+Tree 是 B-Tree 的变种: 一般在数据库系统或文件系统中使用的 B+Tree 结构都在经典 B+Tree 的基础上进行了优化,增加了顺序访问指针。
- +
在 B+Tree 的每个叶子节点增加一个指向相邻叶子节点的指针,就形成了带有顺序访问指针的 B+Tree。 @@ -239,7 +253,7 @@ MySQL 会一直向右匹配直到遇到范围查询 `(>,<,BETWEEN,LIKE)` 就停 - `FOREIGN KEY` - 在一个表中存在的另一个表的主键称此表的外键。用于预防破坏表之间连接的动作,也能防止非法数据插入外键列,因为它必须是它指向的那个表中的值之一。 - `CHECK` - 用于控制字段的值范围。 -## 二、并发控制 +## 并发控制 ### 乐观锁和悲观锁 @@ -323,11 +337,11 @@ MVCC 不能解决幻读问题,**Next-Key 锁就是为了解决幻读问题** 当两个事务同时执行,一个锁住了主键索引,在等待其他相关索引。另一个锁定了非主键索引,在等待主键索引。这样就会发生死锁。发生死锁后,`InnoDB` 一般都可以检测到,并使一个事务释放锁回退,另一个获取锁完成事务。 -## 三、事务 +## 事务 > 事务简单来说:**一个 Session 中所进行所有的操作,要么同时成功,要么同时失败**。具体来说,事务指的是满足 ACID 特性的一组操作,可以通过 `Commit` 提交一个事务,也可以使用 `Rollback` 进行回滚。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库事务.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/RDB/数据库事务.png) ### ACID @@ -340,7 +354,7 @@ ACID — 数据库事务正确执行的四个基本要素: **一个支持事务(Transaction)中的数据库系统,必需要具有这四种特性,否则在事务过程(Transaction processing)当中无法保证数据的正确性,交易过程极可能达不到交易。** -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库ACID.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/RDB/数据库ACID.png) ### 并发一致性问题 @@ -350,25 +364,25 @@ ACID — 数据库事务正确执行的四个基本要素: T1 和 T2 两个事务都对一个数据进行修改,T1 先修改,T2 随后修改,T2 的修改覆盖了 T1 的修改。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库并发一致性-丢失修改.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/RDB/数据库并发一致性-丢失修改.png) - **脏读** T1 修改一个数据,T2 随后读取这个数据。如果 T1 撤销了这次修改,那么 T2 读取的数据是脏数据。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库并发一致性-脏数据.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/RDB/数据库并发一致性-脏数据.png) - **不可重复读** T2 读取一个数据,T1 对该数据做了修改。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库并发一致性-不可重复读.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/RDB/数据库并发一致性-不可重复读.png) - **幻读** T1 读取某个范围的数据,T2 在这个范围内插入新的数据,T1 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库并发一致性-幻读.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/RDB/数据库并发一致性-幻读.png) 并发一致性解决方案: @@ -428,7 +442,7 @@ T1 读取某个范围的数据,T2 在这个范围内插 | 并发性能 | 无影响 | 严重衰退 | 略微衰退 | | 适合场景 | 业务方处理不一致 | 短事务 & 低并发 | 长事务 & 高并发 | -## 四、分库分表 +## 分库分表 ### 什么是分库分表 @@ -448,7 +462,7 @@ T1 读取某个范围的数据,T2 在这个范围内插 > **垂直切分**,是 **把一个有很多字段的表给拆分成多个表,或者是多个库上去**。一般来说,会 **将较少的、访问频率较高的字段放到一个表里去**,然后 **将较多的、访问频率较低的字段放到另外一个表里去**。因为数据库是有缓存的,访问频率高的行字段越少,就可以在缓存里缓存更多的行,性能就越好。这个一般在表层面做的较多一些。 -![image-20200114211639899](https://raw.githubusercontent.com/dunwu/images/dev/snap/image-20200114211639899.png) +![image-20200114211639899](https://raw.githubusercontent.com/dunwu/images/master/snap/image-20200114211639899.png) 一般来说,满足下面的条件就可以考虑扩容了: @@ -461,7 +475,7 @@ T1 读取某个范围的数据,T2 在这个范围内插 > **水平拆分** 又称为 **Sharding**,它是将同一个表中的记录拆分到多个结构相同的表中。当 **单表数据量太大** 时,会极大影响 **SQL 执行的性能** 。分表是将原来一张表的数据分布到数据库集群的不同节点上,从而缓解单点的压力。 -![image-20200114211203589](https://raw.githubusercontent.com/dunwu/images/dev/snap/image-20200114211203589.png) +![image-20200114211203589](https://raw.githubusercontent.com/dunwu/images/master/snap/image-20200114211203589.png) 一般来说,**单表有 200 万条数据** 的时候,性能就会相对差一些了,需要考虑分表了。但是,这也要视具体情况而定,可能是 100 万条,也可能是 500 万条,SQL 越复杂,就最好让单表行数越少。 @@ -564,7 +578,7 @@ T1 读取某个范围的数据,T2 在这个范围内插 来自淘宝综合业务平台团队,它利用对 2 的倍数取余具有向前兼容的特性(如对 4 取余得 1 的数对 2 取余也是 1)来分配数据,避免了行级别的数据迁移,但是依然需要进行表级别的迁移,同时对扩容规模和分表数量都有限制。总得来说,这些方案都不是十分的理想,多多少少都存在一些缺点,这也从一个侧面反映出了 Sharding 扩容的难度。 -## 五、集群 +## 集群 > 这个专题需要根据熟悉哪个数据库而定,但是主流、成熟的数据库都会实现一些基本功能,只是实现方式、策略上有所差异。由于本人较为熟悉 Mysql,所以下面主要介绍 Mysql 系统架构问题。 @@ -580,7 +594,7 @@ Mysql 支持两种复制:基于行的复制和基于语句的复制。 - **I/O 线程** :负责从主服务器上读取二进制日志文件,并写入从服务器的日志中。 - **SQL 线程** :负责读取日志并执行 SQL 语句以更新数据。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/mysql/master-slave.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/mysql/master-slave.png) ### 读写分离 @@ -594,9 +608,9 @@ MySQL 读写分离能提高性能的原因在于: - 从服务器可以配置 `MyISAM` 引擎,提升查询性能以及节约系统开销; - 增加冗余,提高可用性。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/mysql/master-slave-proxy.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/mysql/master-slave-proxy.png) -## 六、数据库优化 +## 数据库优化 数据库优化的路线一般为:SQL 优化、结构优化、配置优化、硬件优化。前两个方向一般是普通开发的考量点,而后两个方向一般是 DBA 的考量点。 @@ -829,7 +843,7 @@ SQL 关键字尽量大写,如:Oracle 默认会将 SQL 语句中的关键字 数据库扩容、使用高配设备等等。核心就是一个字:钱。 -## 七、数据库理论 +## 数据库理论 ### 函数依赖 @@ -866,10 +880,9 @@ SQL 关键字尽量大写,如:Oracle 默认会将 SQL 语句中的关键字 高级别范式的依赖于低级别的范式,1NF 是最低级别的范式。
- +
- #### 第一范式 (1NF) 属性不可分。 @@ -948,7 +961,7 @@ Sname, Sdept 和 Mname 都部分依赖于键码,当一个学生选修了多门 | 学院-1 | 院长-1 | | 学院-2 | 院长-2 | -## 八、Mysql 存储引擎 +## 存储引擎 Mysql 有多种存储引擎,**不同的存储引擎保存数据和索引的方式是不同的,但表的定义则是在 Mysql 服务层统一处理的**。 @@ -974,7 +987,7 @@ InnoDB 和 MyISAM 是目前使用的最多的两种 Mysql 存储引擎。 - InnoDB 支持故障恢复。 - MyISAM 不支持故障恢复。 -## 九、数据库比较 +## 数据库比较 ### 常见数据库比较 @@ -1058,6 +1071,78 @@ where rr>5 and rr<=10; > 数据类型对比表摘自 [SQL 通用数据类型](https://www.runoob.com/sql/sql-datatypes-general.html)、[SQL 用于各种数据库的数据类型](https://www.runoob.com/sql/sql-datatypes.html) +## SQL FAQ + +### SELECT COUNT(\*)、SELECT COUNT(1) 和 SELECT COUNT(具体字段) 性能有差别吗? + +在 MySQL InnoDB 存储引擎中,`COUNT(*)` 和 `COUNT(1)` 都是对所有结果进行 `COUNT`。因此`COUNT(*)`和`COUNT(1)`本质上并没有区别,执行的复杂度都是 `O(N)`,也就是采用全表扫描,进行循环 + 计数的方式进行统计。 + +如果是 MySQL MyISAM 存储引擎,统计数据表的行数只需要`O(1)`的复杂度,这是因为每张 MyISAM 的数据表都有一个 meta 信息存储了`row_count`值,而一致性则由表级锁来保证。因为 InnoDB 支持事务,采用行级锁和 MVCC 机制,所以无法像 MyISAM 一样,只维护一个`row_count`变量,因此需要采用扫描全表,进行循环 + 计数的方式来完成统计。 + +需要注意的是,在实际执行中,`COUNT(*)`和`COUNT(1)`的执行时间可能略有差别,不过你还是可以把它俩的执行效率看成是相等的。 + +另外在 InnoDB 引擎中,如果采用`COUNT(*)`和`COUNT(1)`来统计数据行数,要尽量采用二级索引。因为主键采用的索引是聚簇索引,聚簇索引包含的信息多,明显会大于二级索引(非聚簇索引)。对于`COUNT(*)`和`COUNT(1)`来说,它们不需要查找具体的行,只是统计行数,系统会自动采用占用空间更小的二级索引来进行统计。 + +然而如果想要查找具体的行,那么采用主键索引的效率更高。如果有多个二级索引,会使用 key_len 小的二级索引进行扫描。当没有二级索引的时候,才会采用主键索引来进行统计。 + +这里我总结一下: + +1. 一般情况下,三者执行的效率为 `COUNT(*)`= `COUNT(1)`> `COUNT(字段)`。我们尽量使用`COUNT(*)`,当然如果你要统计的是某个字段的非空数据行数,则另当别论,毕竟比较执行效率的前提是结果一样才可以。 +2. 如果要统计`COUNT(*)`,尽量在数据表上建立二级索引,系统会自动采用`key_len`小的二级索引进行扫描,这样当我们使用`SELECT COUNT(*)`的时候效率就会提升,有时候可以提升几倍甚至更高。 + +> ——摘自[极客时间 - SQL 必知必会](https://time.geekbang.org/column/intro/192) + +### ORDER BY 是对分的组排序还是对分组中的记录排序呢? + +ORDER BY 就是对记录进行排序。如果你在 ORDER BY 前面用到了 GROUP BY,实际上这是一种分组的聚合方式,已经把一组的数据聚合成为了一条记录,再进行排序的时候,相当于对分的组进行了排序。 + +### SELECT 语句内部的执行步骤 + +一条完整的 SELECT 语句内部的执行顺序是这样的: + +1. FROM 子句组装数据(包括通过 ON 进行连接); +2. WHERE 子句进行条件筛选; +3. GROUP BY 分组 ; +4. 使用聚集函数进行计算; +5. HAVING 筛选分组; +6. 计算所有的表达式; +7. SELECT 的字段; +8. ORDER BY 排序; +9. LIMIT 筛选。 + +> ——摘自[极客时间 - SQL 必知必会](https://time.geekbang.org/column/intro/192) + +### 解哪种情况下应该使用 EXISTS,哪种情况应该用 IN + +索引是个前提,其实选择与否还是要看表的大小。你可以将选择的标准理解为小表驱动大表。在这种方式下效率是最高的。 + +比如下面这样: + +``` + SELECT * FROM A WHERE cc IN (SELECT cc FROM B) + SELECT * FROM A WHERE EXISTS (SELECT cc FROM B WHERE B.cc=A.cc) +``` + +当 A 小于 B 时,用 EXISTS。因为 EXISTS 的实现,相当于外表循环,实现的逻辑类似于: + +``` + for i in A + for j in B + if j.cc == i.cc then ... +``` + +当 B 小于 A 时用 IN,因为实现的逻辑类似于: + +``` + for i in B + for j in A + if j.cc == i.cc then ... +``` + +哪个表小就用哪个表来驱动,A 表小就用 EXISTS,B 表小就用 IN。 + +> ——摘自[极客时间 - SQL 必知必会](https://time.geekbang.org/column/intro/192) + ## 参考资料 - [数据库面试题(开发者必看)](https://juejin.im/post/5a9ca0d6518825555c1d1acd) @@ -1069,4 +1154,4 @@ where rr>5 and rr<=10; - [ShardingSphere 分布式事务](https://shardingsphere.apache.org/document/current/cn/features/transaction/) - [mysql 和 oracle 的区别](https://zhuanlan.zhihu.com/p/39651803) - [RUNOOB SQL 教程](https://www.runoob.com/sql/sql-tutorial.html) -- [如果有人问你数据库的原理,叫他看这篇文章](https://gameinstitute.qq.com/community/detail/107154) +- [如果有人问你数据库的原理,叫他看这篇文章](https://gameinstitute.qq.com/community/detail/107154) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/02.SQL\350\257\255\346\263\225\345\237\272\347\241\200\347\211\271\346\200\247.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/02.SQL\350\257\255\346\263\225\345\237\272\347\241\200\347\211\271\346\200\247.md" new file mode 100644 index 00000000..36f19e5b --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/02.SQL\350\257\255\346\263\225\345\237\272\347\241\200\347\211\271\346\200\247.md" @@ -0,0 +1,601 @@ +--- +title: SQL 语法基础特性 +date: 2018-06-15 16:07:17 +categories: + - 数据库 + - 关系型数据库 + - 综合 +tags: + - 数据库 + - 关系型数据库 + - SQL +permalink: /pages/b71c9e/ +--- + +# SQL 语法基础特性 + +> 本文针对关系型数据库的基本语法。限于篇幅,本文侧重说明用法,不会展开讲解特性、原理。 +> +> 本文语法主要针对 Mysql,但大部分的语法对其他关系型数据库也适用。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200115160512.png) + +## SQL 简介 + +### 数据库术语 + +- `数据库(database)` - 保存有组织的数据的容器(通常是一个文件或一组文件)。 +- `数据表(table)` - 某种特定类型数据的结构化清单。 +- `模式(schema)` - 关于数据库和表的布局及特性的信息。模式定义了数据在表中如何存储,包含存储什么样的数据,数据如何分解,各部分信息如何命名等信息。数据库和表都有模式。 +- `列(column)` - 表中的一个字段。所有表都是由一个或多个列组成的。 +- `行(row)` - 表中的一个记录。 +- `主键(primary key)` - 一列(或一组列),其值能够唯一标识表中每一行。 + +### SQL 语法 + +> SQL(Structured Query Language),标准 SQL 由 ANSI 标准委员会管理,从而称为 ANSI SQL。各个 DBMS 都有自己的实现,如 PL/SQL、Transact-SQL 等。 + +#### SQL 语法结构 + +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/mysql/sql-syntax.png) + +SQL 语法结构包括: + +- **`子句`** - 是语句和查询的组成成分。(在某些情况下,这些都是可选的。) +- **`表达式`** - 可以产生任何标量值,或由列和行的数据库表 +- **`谓词`** - 给需要评估的 SQL 三值逻辑(3VL)(true/false/unknown)或布尔真值指定条件,并限制语句和查询的效果,或改变程序流程。 +- **`查询`** - 基于特定条件检索数据。这是 SQL 的一个重要组成部分。 +- **`语句`** - 可以持久地影响纲要和数据,也可以控制数据库事务、程序流程、连接、会话或诊断。 + +#### SQL 语法要点 + +- **SQL 语句不区分大小写**,但是数据库表名、列名和值是否区分,依赖于具体的 DBMS 以及配置。 + +例如:`SELECT` 与 `select` 、`Select` 是相同的。 + +- **多条 SQL 语句必须以分号(`;`)分隔**。 + +- 处理 SQL 语句时,**所有空格都被忽略**。SQL 语句可以写成一行,也可以分写为多行。 + +```sql +-- 一行 SQL 语句 +UPDATE user SET username='robot', password='robot' WHERE username = 'root'; + +-- 多行 SQL 语句 +UPDATE user +SET username='robot', password='robot' +WHERE username = 'root'; +``` + +- SQL 支持三种注释 + +```sql +## 注释1 +-- 注释2 +/* 注释3 */ +``` + +#### SQL 分类 + +#### 数据定义语言(DDL) + +数据定义语言(Data Definition Language,DDL)是 SQL 语言集中负责数据结构定义与数据库对象定义的语言。 + +DDL 的主要功能是**定义数据库对象**。 + +DDL 的核心指令是 `CREATE`、`ALTER`、`DROP`。 + +#### 数据操纵语言(DML) + +数据操纵语言(Data Manipulation Language, DML)是用于数据库操作,对数据库其中的对象和数据运行访问工作的编程语句。 + +DML 的主要功能是 **访问数据**,因此其语法都是以**读写数据库**为主。 + +DML 的核心指令是 `INSERT`、`UPDATE`、`DELETE`、`SELECT`。这四个指令合称 CRUD(Create, Read, Update, Delete),即增删改查。 + +#### 事务控制语言(TCL) + +事务控制语言 (Transaction Control Language, TCL) 用于**管理数据库中的事务**。这些用于管理由 DML 语句所做的更改。它还允许将语句分组为逻辑事务。 + +TCL 的核心指令是 `COMMIT`、`ROLLBACK`。 + +#### 数据控制语言(DCL) + +数据控制语言 (Data Control Language, DCL) 是一种可对数据访问权进行控制的指令,它可以控制特定用户账户对数据表、查看表、预存程序、用户自定义函数等数据库对象的控制权。 + +DCL 的核心指令是 `GRANT`、`REVOKE`。 + +DCL 以**控制用户的访问权限**为主,因此其指令作法并不复杂,可利用 DCL 控制的权限有:`CONNECT`、`SELECT`、`INSERT`、`UPDATE`、`DELETE`、`EXECUTE`、`USAGE`、`REFERENCES`。 + +根据不同的 DBMS 以及不同的安全性实体,其支持的权限控制也有所不同。 + +--- + +**(以下为 DML 语句用法)** + +## 增删改查(CRUD) + +增删改查,又称为 **`CRUD`**,是数据库基本操作中的基本操作。 + +### 插入数据 + +> - `INSERT INTO` 语句用于向表中插入新记录。 + +#### 插入完整的行 + +```sql +INSERT INTO user +VALUES (10, 'root', 'root', 'xxxx@163.com'); +``` + +#### 插入行的一部分 + +```sql +INSERT INTO user(username, password, email) +VALUES ('admin', 'admin', 'xxxx@163.com'); +``` + +#### 插入查询出来的数据 + +```sql +INSERT INTO user(username) +SELECT name +FROM account; +``` + +### 更新数据 + +> - `UPDATE` 语句用于更新表中的记录。 + +```sql +UPDATE user +SET username='robot', password='robot' +WHERE username = 'root'; +``` + +### 删除数据 + +> - `DELETE` 语句用于删除表中的记录。 +> - `TRUNCATE TABLE` 可以清空表,也就是删除所有行。 + +#### 删除表中的指定数据 + +```sql +DELETE FROM user WHERE username = 'robot'; +``` + +#### 清空表中的数据 + +```sql +TRUNCATE TABLE user; +``` + +### 查询数据 + +> - `SELECT` 语句用于从数据库中查询数据。 +> - `DISTINCT` 用于返回唯一不同的值。它作用于所有列,也就是说所有列的值都相同才算相同。 +> - `LIMIT` 限制返回的行数。可以有两个参数,第一个参数为起始行,从 0 开始;第二个参数为返回的总行数。 +> - `ASC` :升序(默认) +> - `DESC` :降序 + +#### 查询单列 + +```sql +SELECT prod_name FROM products; +``` + +#### 查询多列 + +```sql +SELECT prod_id, prod_name, prod_price FROM products; +``` + +#### 查询所有列 + +```sql +SELECT * FROM products; +``` + +#### 查询不同的值 + +```sql +SELECT DISTINCT vend_id FROM products; +``` + +#### 限制查询数量 + +```sql +-- 返回前 5 行 +SELECT * FROM products LIMIT 5; +SELECT * FROM products LIMIT 0, 5; +-- 返回第 3 ~ 5 行 +SELECT * FROM products LIMIT 2, 3; +``` + +## 过滤数据(WHERE) + +子查询是嵌套在较大查询中的 SQL 查询。子查询也称为**内部查询**或**内部选择**,而包含子查询的语句也称为**外部查询**或**外部选择**。 + +- 子查询可以嵌套在 `SELECT`,`INSERT`,`UPDATE` 或 `DELETE` 语句内或另一个子查询中。 + +- 子查询通常会在另一个 `SELECT` 语句的 `WHERE` 子句中添加。 + +- 您可以使用比较运算符,如 `>`,`<`,或 `=`。比较运算符也可以是多行运算符,如 `IN`,`ANY` 或 `ALL`。 + +- 子查询必须被圆括号 `()` 括起来。 + +- 内部查询首先在其父查询之前执行,以便可以将内部查询的结果传递给外部查询。执行过程可以参考下图: + +

+ sql-subqueries +

+ +**子查询的子查询** + +```sql +SELECT cust_name, cust_contact +FROM customers +WHERE cust_id IN (SELECT cust_id + FROM orders + WHERE order_num IN (SELECT order_num + FROM orderitems + WHERE prod_id = 'RGAN01')); +``` + +### WHERE 子句 + +在 SQL 语句中,数据根据 `WHERE` 子句中指定的搜索条件进行过滤。 + +`WHERE` 子句的基本格式如下: + +```sql +SELECT ……(列名) FROM ……(表名) WHERE ……(子句条件) +``` + +`WHERE` 子句用于过滤记录,即缩小访问数据的范围。`WHERE` 后跟一个返回 `true` 或 `false` 的条件。 + +`WHERE` 可以与 `SELECT`,`UPDATE` 和 `DELETE` 一起使用。 + +**`SELECT` 语句中的 `WHERE` 子句** + +```sql +SELECT * FROM Customers +WHERE cust_name = 'Kids Place'; +``` + +**`UPDATE` 语句中的 `WHERE` 子句** + +```sql +UPDATE Customers +SET cust_name = 'Jack Jones' +WHERE cust_name = 'Kids Place'; +``` + +**`DELETE` 语句中的 `WHERE` 子句** + +```sql +DELETE FROM Customers +WHERE cust_name = 'Kids Place'; +``` + +可以在 `WHERE` 子句中使用的操作符: + +### 比较操作符 + +| 运算符 | 描述 | +| ------ | ------------------------------------------------------ | +| `=` | 等于 | +| `<>` | 不等于。注释:在 SQL 的一些版本中,该操作符可被写成 != | +| `>` | 大于 | +| `<` | 小于 | +| `>=` | 大于等于 | +| `<=` | 小于等于 | + +### 范围操作符 + +| 运算符 | 描述 | +| --------- | -------------------------- | +| `BETWEEN` | 在某个范围内 | +| `IN` | 指定针对某个列的多个可能值 | + +- `IN` 操作符在 `WHERE` 子句中使用,作用是在指定的几个特定值中任选一个值。 + +- `BETWEEN` 操作符在 `WHERE` 子句中使用,作用是选取介于某个范围内的值。 + +**IN 示例** + +```sql +SELECT * +FROM products +WHERE vend_id IN ('DLL01', 'BRS01'); +``` + +**BETWEEN 示例** + +```sql +SELECT * +FROM products +WHERE prod_price BETWEEN 3 AND 5; +``` + +### 逻辑操作符 + +| 运算符 | 描述 | +| ------ | ---------- | +| `AND` | 并且(与) | +| `OR` | 或者(或) | +| `NOT` | 否定(非) | + +`AND`、`OR`、`NOT` 是用于对过滤条件的逻辑处理指令。 + +- `AND` 优先级高于 `OR`,为了明确处理顺序,可以使用 `()`。`AND` 操作符表示左右条件都要满足。 +- `OR` 操作符表示左右条件满足任意一个即可。 + +- `NOT` 操作符用于否定一个条件。 + +**AND 示例** + +```sql +SELECT prod_id, prod_name, prod_price +FROM products +WHERE vend_id = 'DLL01' AND prod_price <= 4; +``` + +**OR 示例** + +```sql +SELECT prod_id, prod_name, prod_price +FROM products +WHERE vend_id = 'DLL01' OR vend_id = 'BRS01'; +``` + +**NOT 示例** + +```sql +SELECT * +FROM products +WHERE prod_price NOT BETWEEN 3 AND 5; +``` + +### 通配符 + +| 运算符 | 描述 | +| ------ | -------------------------- | +| `LIKE` | 搜索某种模式 | +| `%` | 表示任意字符出现任意次数 | +| `_` | 表示任意字符出现一次 | +| `[]` | 必须匹配指定位置的一个字符 | + +`LIKE` 操作符在 `WHERE` 子句中使用,作用是确定字符串是否匹配模式。只有字段是文本值时才使用 `LIKE`。 + +`LIKE` 支持以下通配符匹配选项: + +- `%` 表示任何字符出现任意次数。 +- `_` 表示任何字符出现一次。 +- `[]` 必须匹配指定位置的一个字符。 + +> 注意:**不要滥用通配符,通配符位于开头处匹配会非常慢**。 + +`%` 示例: + +```sql +SELECT prod_id, prod_name, prod_price +FROM products +WHERE prod_name LIKE '%bean bag%'; +``` + +`_` 示例: + +```sql +SELECT prod_id, prod_name, prod_price +FROM products +WHERE prod_name LIKE '__ inch teddy bear'; +``` + +- + +## 排序(ORDER BY) + +> `ORDER BY` 用于对结果集进行排序。 + +`ORDER BY` 有两种排序模式: + +- `ASC` :升序(默认) +- `DESC` :降序 + +可以按多个列进行排序,并且为每个列指定不同的排序方式。 + +指定多个列的排序示例: + +```sql +SELECT * FROM products +ORDER BY prod_price DESC, prod_name ASC; +``` + +## 数据定义(CREATE、ALTER、DROP) + +> DDL 的主要功能是定义数据库对象(如:数据库、数据表、视图、索引等)。 + +### 数据库(DATABASE) + +#### 创建数据库 + +```sql +CREATE DATABASE IF NOT EXISTS db_tutorial; +``` + +#### 删除数据库 + +```sql +DROP DATABASE IF EXISTS db_tutorial; +``` + +#### 选择数据库 + +```sql +USE db_tutorial; +``` + +### 数据表(TABLE) + +#### 删除数据表 + +```sql +DROP TABLE IF EXISTS user; +DROP TABLE IF EXISTS vip_user; +``` + +#### 创建数据表 + +**普通创建** + +```sql +CREATE TABLE user ( + id INT(10) UNSIGNED NOT NULL COMMENT 'Id', + username VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '用户名', + password VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '密码', + email VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '邮箱' +) COMMENT ='用户表'; +``` + +**根据已有的表创建新表** + +```sql +CREATE TABLE vip_user AS +SELECT * +FROM user; +``` + +#### 修改数据表 + +##### 添加列 + +```sql +ALTER TABLE user +ADD age int(3); +``` + +##### 删除列 + +```sql +ALTER TABLE user +DROP COLUMN age; +``` + +##### 修改列 + +```sql +ALTER TABLE `user` +MODIFY COLUMN age tinyint; +``` + +### 视图(VIEW) + +> 视图是基于 SQL 语句的结果集的可视化的表。**视图是虚拟的表,本身不存储数据,也就不能对其进行索引操作**。对视图的操作和对普通表的操作一样。 + +视图的作用: + +- 简化复杂的 SQL 操作,比如复杂的连接。 +- 只使用实际表的一部分数据。 +- 通过只给用户访问视图的权限,保证数据的安全性。 +- 更改数据格式和表示。 + +#### 创建视图 + +```sql +CREATE VIEW top_10_user_view AS +SELECT id, username +FROM user +WHERE id < 10; +``` + +#### 删除视图 + +```sql +DROP VIEW top_10_user_view; +``` + +### 索引(INDEX) + +> 通过索引可以更加快速高效地查询数据。用户无法看到索引,它们只能被用来加速查询。 + +更新一个包含索引的表需要比更新一个没有索引的表花费更多的时间,这是由于索引本身也需要更新。因此,理想的做法是仅仅在常常被搜索的列(以及表)上面创建索引。 + +唯一索引:唯一索引表明此索引的每一个索引值只对应唯一的数据记录。 + +#### 创建索引 + +```sql +CREATE INDEX idx_email + ON user(email); +``` + +#### 创建唯一索引 + +```sql +CREATE UNIQUE INDEX uniq_username + ON user(username); +``` + +#### 删除索引 + +```sql +ALTER TABLE user +DROP INDEX idx_email; +ALTER TABLE user +DROP INDEX uniq_username; +``` + +#### 添加主键 + +```sql +ALTER TABLE user +ADD PRIMARY KEY (id); +``` + +#### 删除主键 + +```sql +ALTER TABLE user +DROP PRIMARY KEY; +``` + +### 约束 + +> SQL 约束用于规定表中的数据规则。 + +- 如果存在违反约束的数据行为,行为会被约束终止。 +- 约束可以在创建表时规定(通过 CREATE TABLE 语句),或者在表创建之后规定(通过 ALTER TABLE 语句)。 +- 约束类型 + - `NOT NULL` - 指示某列不能存储 NULL 值。 + - `UNIQUE` - 保证某列的每行必须有唯一的值。 + - `PRIMARY KEY` - NOT NULL 和 UNIQUE 的结合。确保某列(或两个列多个列的结合)有唯一标识,有助于更容易更快速地找到表中的一个特定的记录。 + - `FOREIGN KEY` - 保证一个表中的数据匹配另一个表中的值的参照完整性。 + - `CHECK` - 保证列中的值符合指定的条件。 + - `DEFAULT` - 规定没有给列赋值时的默认值。 + +创建表时使用约束条件: + +```sql +CREATE TABLE Users ( + Id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增Id', + Username VARCHAR(64) NOT NULL UNIQUE DEFAULT 'default' COMMENT '用户名', + Password VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '密码', + Email VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '邮箱地址', + Enabled TINYINT(4) DEFAULT NULL COMMENT '是否有效', + PRIMARY KEY (Id) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; +``` + +## 参考资料 + +- [《SQL 必知必会》](https://book.douban.com/subject/35167240/) +- [『浅入深出』MySQL 中事务的实现](https://draveness.me/mysql-transaction) +- [MySQL 的学习--触发器](https://www.cnblogs.com/CraryPrimitiveMan/p/4206942.html) +- [维基百科词条 - SQL](https://zh.wikipedia.org/wiki/SQL) +- [https://www.sitesbay.com/sql/index](https://www.sitesbay.com/sql/index) +- [SQL Subqueries](https://www.w3resource.com/sql/subqueries/understanding-sql-subqueries.php) +- [Quick breakdown of the types of joins](https://stackoverflow.com/questions/6294778/mysql-quick-breakdown-of-the-types-of-joins) +- [SQL UNION](https://www.w3resource.com/sql/sql-union.php) +- [SQL database security](https://www.w3resource.com/sql/database-security/create-users.php) +- [Mysql 中的存储过程](https://www.cnblogs.com/chenpi/p/5136483.html) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/03.SQL\350\257\255\346\263\225\351\253\230\347\272\247\347\211\271\346\200\247.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/03.SQL\350\257\255\346\263\225\351\253\230\347\272\247\347\211\271\346\200\247.md" new file mode 100644 index 00000000..2131af12 --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/03.SQL\350\257\255\346\263\225\351\253\230\347\272\247\347\211\271\346\200\247.md" @@ -0,0 +1,610 @@ +--- +title: SQL 语法高级特性 +date: 2022-04-27 22:13:55 +categories: + - 数据库 + - 关系型数据库 + - 综合 +tags: + - 数据库 + - 关系型数据库 + - SQL +permalink: /pages/1ae1ca/ +--- + +# SQL 语法高级特性 + +> 本文针对关系型数据库的基本语法。限于篇幅,本文侧重说明用法,不会展开讲解特性、原理。 +> +> 本文语法主要针对 Mysql,但大部分的语法对其他关系型数据库也适用。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200115160512.png) + +## 连接和组合 + +### 连接(JOIN) + +> 连接用于连接多个表,使用 `JOIN` 关键字,并且条件语句使用 `ON` 而不是 `WHERE`。 + +如果一个 `JOIN` 至少有一个公共字段并且它们之间存在关系,则该 `JOIN` 可以在两个或多个表上工作。 + +`JOIN` 保持基表(结构和数据)不变。**连接可以替换子查询,并且比子查询的效率一般会更快**。 + +`JOIN` 有两种连接类型:内连接和外连接。 + +
+ sql-join +
+ +#### 内连接(INNER JOIN) + +内连接又称等值连接,**使用 `INNER JOIN` 关键字**。在没有条件语句的情况下**返回笛卡尔积**。 + +```sql +SELECT vend_name, prod_name, prod_price +FROM vendors INNER JOIN products +ON vendors.vend_id = products.vend_id; +``` + +##### 自连接(`=`) + +自连接可以看成内连接的一种,只是**连接的表是自身**而已。**自然连接是把同名列通过 `=` 连接起来**的,同名列可以有多个。 + +```sql +SELECT c1.cust_id, c1.cust_name, c1.cust_contact +FROM customers c1, customers c2 +WHERE c1.cust_name = c2.cust_name +AND c2.cust_contact = 'Jim Jones'; +``` + +##### 自然连接(NATURAL JOIN) + +内连接提供连接的列,而自然连接**自动连接所有同名列**。自然连接使用 `NATURAL JOIN` 关键字。 + +```sql +SELECT * +FROM Products +NATURAL JOIN Customers; +``` + +#### 外连接(OUTER JOIN) + +外连接返回一个表中的所有行,并且仅返回来自此表中满足连接条件的那些行,即两个表中的列是相等的。外连接分为左外连接、右外连接、全外连接(Mysql 不支持)。 + +##### 左连接(LEFT JOIN) + +左外连接就是保留左表没有关联的行。 + +```sql +SELECT customers.cust_id, orders.order_num +FROM customers LEFT JOIN orders +ON customers.cust_id = orders.cust_id; +``` + +##### 右连接(RIGHT JOIN) + +右外连接就是保留右表没有关联的行。 + +```sql +SELECT customers.cust_id, orders.order_num +FROM customers RIGHT JOIN orders +ON customers.cust_id = orders.cust_id; +``` + +### 组合(UNION) + +> `UNION` 运算符**将两个或更多查询的结果组合起来,并生成一个结果集**,其中包含来自 `UNION` 中参与查询的提取行。 + +`UNION` 基本规则: + +- 所有查询的列数和列顺序必须相同。 +- 每个查询中涉及表的列的数据类型必须相同或兼容。 +- 通常返回的列名取自第一个查询。 + +默认会去除相同行,如果需要保留相同行,使用 `UNION ALL`。 + +只能包含一个 `ORDER BY` 子句,并且必须位于语句的最后。 + +应用场景: + +- 在一个查询中从不同的表返回结构数据。 +- 对一个表执行多个查询,按一个查询返回数据。 + +组合查询示例: + +```sql +SELECT cust_name, cust_contact, cust_email +FROM customers +WHERE cust_state IN ('IL', 'IN', 'MI') +UNION +SELECT cust_name, cust_contact, cust_email +FROM customers +WHERE cust_name = 'Fun4All'; +``` + +### JOIN vs UNION + +- `JOIN` 中连接表的列可能不同,但在 `UNION` 中,所有查询的列数和列顺序必须相同。 +- `UNION` 将查询之后的行放在一起(垂直放置),但 `JOIN` 将查询之后的列放在一起(水平放置),即它构成一个笛卡尔积。 + +## 函数 + +> 🔔 注意:不同数据库的函数往往各不相同,因此不可移植。本节主要以 Mysql 的函数为例。 + +### 文本处理 + +| 函数 | 说明 | +| :------------------: | :--------------------: | +| `LEFT()`、`RIGHT()` | 左边或者右边的字符 | +| `LOWER()`、`UPPER()` | 转换为小写或者大写 | +| `LTRIM()`、`RTIM()` | 去除左边或者右边的空格 | +| `LENGTH()` | 长度 | +| `SOUNDEX()` | 转换为语音值 | + +其中, **SOUNDEX()** 可以将一个字符串转换为描述其语音表示的字母数字模式。 + +```sql +SELECT * +FROM mytable +WHERE SOUNDEX(col1) = SOUNDEX('apple') +``` + +### 日期和时间处理 + +- 日期格式:`YYYY-MM-DD` +- 时间格式:`HH:MM:SS` + +| 函 数 | 说 明 | +| :-------------: | :----------------------------: | +| `AddDate()` | 增加一个日期(天、周等) | +| `AddTime()` | 增加一个时间(时、分等) | +| `CurDate()` | 返回当前日期 | +| `CurTime()` | 返回当前时间 | +| `Date()` | 返回日期时间的日期部分 | +| `DateDiff()` | 计算两个日期之差 | +| `Date_Add()` | 高度灵活的日期运算函数 | +| `Date_Format()` | 返回一个格式化的日期或时间串 | +| `Day()` | 返回一个日期的天数部分 | +| `DayOfWeek()` | 对于一个日期,返回对应的星期几 | +| `Hour()` | 返回一个时间的小时部分 | +| `Minute()` | 返回一个时间的分钟部分 | +| `Month()` | 返回一个日期的月份部分 | +| `Now()` | 返回当前日期和时间 | +| `Second()` | 返回一个时间的秒部分 | +| `Time()` | 返回一个日期时间的时间部分 | +| `Year()` | 返回一个日期的年份部分 | + +```sql +mysql> SELECT NOW(); +``` + +``` +2018-4-14 20:25:11 +``` + +### 数值处理 + +| 函数 | 说明 | +| :----: | :----: | +| SIN() | 正弦 | +| COS() | 余弦 | +| TAN() | 正切 | +| ABS() | 绝对值 | +| SQRT() | 平方根 | +| MOD() | 余数 | +| EXP() | 指数 | +| PI() | 圆周率 | +| RAND() | 随机数 | + +### 汇总 + +| 函 数 | 说 明 | +| :-------: | :--------------: | +| `AVG()` | 返回某列的平均值 | +| `COUNT()` | 返回某列的行数 | +| `MAX()` | 返回某列的最大值 | +| `MIN()` | 返回某列的最小值 | +| `SUM()` | 返回某列值之和 | + +`AVG()` 会忽略 NULL 行。 + +使用 DISTINCT 可以让汇总函数值汇总不同的值。 + +```sql +SELECT AVG(DISTINCT col1) AS avg_col +FROM mytable +``` + +## 分组 + +### GROUP BY + +> `GROUP BY` 子句将记录分组到汇总行中,`GROUP BY` 为每个组返回一个记录。 + +`GROUP BY` 可以按一列或多列进行分组。 + +`GROUP BY` 通常还涉及聚合函数:COUNT,MAX,SUM,AVG 等。 + +`GROUP BY` 按分组字段进行排序后,`ORDER BY` 可以以汇总字段来进行排序。 + +分组示例: + +```sql +SELECT cust_name, COUNT(cust_address) AS addr_num +FROM Customers GROUP BY cust_name; +``` + +分组后排序示例: + +```sql +SELECT cust_name, COUNT(cust_address) AS addr_num +FROM Customers GROUP BY cust_name +ORDER BY cust_name DESC; +``` + +### HAVING + +> `HAVING` 用于对汇总的 `GROUP BY` 结果进行过滤。`HAVING` 要求存在一个 `GROUP BY` 子句。 + +`WHERE` 和 `HAVING` 可以在相同的查询中。 + +`HAVING` vs `WHERE`: + +- `WHERE` 和 `HAVING` 都是用于过滤。 +- `HAVING` 适用于汇总的组记录;而 `WHERE` 适用于单个记录。 + +使用 `WHERE` 和 `HAVING` 过滤数据示例: + +```sql +SELECT cust_name, COUNT(*) AS num +FROM Customers +WHERE cust_email IS NOT NULL +GROUP BY cust_name +HAVING COUNT(*) >= 1; +``` + +--- + +**(以下为 DDL 语句用法)** + +## 事务 + +不能回退 `SELECT` 语句,回退 `SELECT` 语句也没意义;也不能回退 `CREATE` 和 `DROP` 语句。 + +**MySQL 默认采用隐式提交策略(`autocommit`)**,每执行一条语句就把这条语句当成一个事务然后进行提交。当出现 `START TRANSACTION` 语句时,会关闭隐式提交;当 `COMMIT` 或 `ROLLBACK` 语句执行后,事务会自动关闭,重新恢复隐式提交。 + +通过 `set autocommit=0` 可以取消自动提交,直到 `set autocommit=1` 才会提交;`autocommit` 标记是针对每个连接而不是针对服务器的。 + +事务处理指令: + +- `START TRANSACTION` - 指令用于标记事务的起始点。 +- `SAVEPOINT` - 指令用于创建保留点。 +- `ROLLBACK TO` - 指令用于回滚到指定的保留点;如果没有设置保留点,则回退到 `START TRANSACTION` 语句处。 +- `COMMIT` - 提交事务。 +- `RELEASE SAVEPOINT`:删除某个保存点。 +- `SET TRANSACTION`:设置事务的隔离级别。 + +事务处理示例: + +```sql +-- 开始事务 +START TRANSACTION; + +-- 插入操作 A +INSERT INTO `user` +VALUES (1, 'root1', 'root1', 'xxxx@163.com'); + +-- 创建保留点 updateA +SAVEPOINT updateA; + +-- 插入操作 B +INSERT INTO `user` +VALUES (2, 'root2', 'root2', 'xxxx@163.com'); + +-- 回滚到保留点 updateA +ROLLBACK TO updateA; + +-- 提交事务,只有操作 A 生效 +COMMIT; +``` + +### ACID + +### 事务隔离级别 + +--- + +**(以下为 DCL 语句用法)** + +## 权限控制 + +`GRANT` 和 `REVOKE` 可在几个层次上控制访问权限: + +- 整个服务器,使用 `GRANT ALL` 和 `REVOKE ALL`; +- 整个数据库,使用 ON database.\*; +- 特定的表,使用 ON database.table; +- 特定的列; +- 特定的存储过程。 + +新创建的账户没有任何权限。 + +账户用 `username@host` 的形式定义,`username@%` 使用的是默认主机名。 + +MySQL 的账户信息保存在 mysql 这个数据库中。 + +```sql +USE mysql; +SELECT user FROM user; +``` + +### 创建账户 + +```sql +CREATE USER myuser IDENTIFIED BY 'mypassword'; +``` + +### 修改账户名 + +```sql +UPDATE user SET user='newuser' WHERE user='myuser'; +FLUSH PRIVILEGES; +``` + +### 删除账户 + +```sql +DROP USER myuser; +``` + +### 查看权限 + +```sql +SHOW GRANTS FOR myuser; +``` + +### 授予权限 + +```sql +GRANT SELECT, INSERT ON *.* TO myuser; +``` + +### 删除权限 + +```sql +REVOKE SELECT, INSERT ON *.* FROM myuser; +``` + +### 更改密码 + +```sql +SET PASSWORD FOR myuser = 'mypass'; +``` + +## 存储过程 + +存储过程的英文是 Stored Procedure。它可以视为一组 SQL 语句的批处理。一旦存储过程被创建出来,使用它就像使用函数一样简单,我们直接通过调用存储过程名即可。 + +定义存储过程的语法格式: + +```sql +CREATE PROCEDURE 存储过程名称 ([参数列表]) +BEGIN + 需要执行的语句 +END +``` + +存储过程定义语句类型: + +- `CREATE PROCEDURE` 用于创建存储过程 +- `DROP PROCEDURE` 用于删除存储过程 +- `ALTER PROCEDURE` 用于修改存储过程 + +### 使用存储过程 + +创建存储过程的要点: + +- `DELIMITER` 用于定义语句的结束符 +- 存储过程的 3 种参数类型: + - `IN`:存储过程的入参 + - `OUT`:存储过程的出参 + - `INPUT`:既是存储过程的入参,也是存储过程的出参 +- 流控制语句: + - `BEGIN…END`:`BEGIN…END` 中间包含了多个语句,每个语句都以(`;`)号为结束符。 + - `DECLARE`:`DECLARE` 用来声明变量,使用的位置在于 `BEGIN…END` 语句中间,而且需要在其他语句使用之前进行变量的声明。 + - `SET`:赋值语句,用于对变量进行赋值。 + - `SELECT…INTO`:把从数据表中查询的结果存放到变量中,也就是为变量赋值。每次只能给一个变量赋值,不支持集合的操作。 + - `IF…THEN…ENDIF`:条件判断语句,可以在 `IF…THEN…ENDIF` 中使用 `ELSE` 和 `ELSEIF` 来进行条件判断。 + - `CASE`:`CASE` 语句用于多条件的分支判断。 + +创建存储过程示例: + +```sql +DROP PROCEDURE IF EXISTS `proc_adder`; +DELIMITER ;; +CREATE DEFINER=`root`@`localhost` PROCEDURE `proc_adder`(IN a int, IN b int, OUT sum int) +BEGIN + DECLARE c int; + if a is null then set a = 0; + end if; + + if b is null then set b = 0; + end if; + + set sum = a + b; +END +;; +DELIMITER ; +``` + +使用存储过程示例: + +```sql +set @b=5; +call proc_adder(2,@b,@s); +select @s as sum; +``` + +### 存储过程的利弊 + +存储过程的优点: + +- **执行效率高**:一次编译多次使用。 +- **安全性强**:在设定存储过程的时候可以设置对用户的使用权限,这样就和视图一样具有较强的安全性。 +- **可复用**:将代码封装,可以提高代码复用。 +- **性能好** + - 由于是预先编译,因此具有很高的性能。 + - 一个存储过程替代大量 T_SQL 语句 ,可以降低网络通信量,提高通信速率。 + +存储过程的缺点: + +- **可移植性差**:存储过程不能跨数据库移植。由于不同数据库的存储过程语法几乎都不一样,十分难以维护(不通用)。 +- **调试困难**:只有少数 DBMS 支持存储过程的调试。对于复杂的存储过程来说,开发和维护都不容易。 +- **版本管理困难**:比如数据表索引发生变化了,可能会导致存储过程失效。我们在开发软件的时候往往需要进行版本管理,但是存储过程本身没有版本控制,版本迭代更新的时候很麻烦。 +- **不适合高并发的场景**:高并发的场景需要减少数据库的压力,有时数据库会采用分库分表的方式,而且对可扩展性要求很高,在这种情况下,存储过程会变得难以维护,增加数据库的压力,显然就不适用了。 + +> _综上,存储过程的优缺点都非常突出,是否使用一定要慎重,需要根据具体应用场景来权衡_。 + +### 触发器 + +> 触发器可以视为一种特殊的存储过程。 +> +> 触发器是一种与表操作有关的数据库对象,当触发器所在表上出现指定事件时,将调用该对象,即表的操作事件触发表上的触发器的执行。 + +#### 触发器特性 + +可以使用触发器来进行审计跟踪,把修改记录到另外一张表中。 + +MySQL 不允许在触发器中使用 `CALL` 语句 ,也就是不能调用存储过程。 + +**`BEGIN` 和 `END`** + +当触发器的触发条件满足时,将会执行 `BEGIN` 和 `END` 之间的触发器执行动作。 + +> 🔔 注意:在 MySQL 中,分号 `;` 是语句结束的标识符,遇到分号表示该段语句已经结束,MySQL 可以开始执行了。因此,解释器遇到触发器执行动作中的分号后就开始执行,然后会报错,因为没有找到和 BEGIN 匹配的 END。 +> +> 这时就会用到 `DELIMITER` 命令(`DELIMITER` 是定界符,分隔符的意思)。它是一条命令,不需要语句结束标识,语法为:`DELIMITER new_delemiter`。`new_delemiter` 可以设为 1 个或多个长度的符号,默认的是分号 `;`,我们可以把它修改为其他符号,如 `$` - `DELIMITER $` 。在这之后的语句,以分号结束,解释器不会有什么反应,只有遇到了 `$`,才认为是语句结束。注意,使用完之后,我们还应该记得把它给修改回来。 + +**`NEW` 和 `OLD`** + +- MySQL 中定义了 `NEW` 和 `OLD` 关键字,用来表示触发器的所在表中,触发了触发器的那一行数据。 +- 在 `INSERT` 型触发器中,`NEW` 用来表示将要(`BEFORE`)或已经(`AFTER`)插入的新数据; +- 在 `UPDATE` 型触发器中,`OLD` 用来表示将要或已经被修改的原数据,`NEW` 用来表示将要或已经修改为的新数据; +- 在 `DELETE` 型触发器中,`OLD` 用来表示将要或已经被删除的原数据; +- 使用方法: `NEW.columnName` (columnName 为相应数据表某一列名) + +#### 触发器指令 + +> 提示:为了理解触发器的要点,有必要先了解一下创建触发器的指令。 + +`CREATE TRIGGER` 指令用于创建触发器。 + +语法: + +```sql +CREATE TRIGGER trigger_name +trigger_time +trigger_event +ON table_name +FOR EACH ROW +BEGIN + trigger_statements +END; +``` + +说明: + +- trigger_name:触发器名 +- trigger_time: 触发器的触发时机。取值为 `BEFORE` 或 `AFTER`。 +- trigger_event: 触发器的监听事件。取值为 `INSERT`、`UPDATE` 或 `DELETE`。 +- table_name: 触发器的监听目标。指定在哪张表上建立触发器。 +- FOR EACH ROW: 行级监视,Mysql 固定写法,其他 DBMS 不同。 +- trigger_statements: 触发器执行动作。是一条或多条 SQL 语句的列表,列表内的每条语句都必须用分号 `;` 来结尾。 + +创建触发器示例: + +```sql +DELIMITER $ +CREATE TRIGGER `trigger_insert_user` +AFTER INSERT ON `user` +FOR EACH ROW +BEGIN + INSERT INTO `user_history`(user_id, operate_type, operate_time) + VALUES (NEW.id, 'add a user', now()); +END $ +DELIMITER ; +``` + +查看触发器示例: + +```sql +SHOW TRIGGERS; +``` + +删除触发器示例: + +```sql +DROP TRIGGER IF EXISTS trigger_insert_user; +``` + +## 游标 + +> 游标(CURSOR)是一个存储在 DBMS 服务器上的数据库查询,它不是一条 `SELECT` 语句,而是被该语句检索出来的结果集。在存储过程中使用游标可以对一个结果集进行移动遍历。 + +游标主要用于交互式应用,其中用户需要对数据集中的任意行进行浏览和修改。 + +使用游标的步骤: + +1. **定义游标**:通过 `DECLARE cursor_name CURSOR FOR <语句>` 定义游标。这个过程没有实际检索出数据。 +2. **打开游标**:通过 `OPEN cursor_name` 打开游标。 +3. **取出数据**:通过 `FETCH cursor_name INTO var_name ...` 获取数据。 +4. **关闭游标**:通过 `CLOSE cursor_name` 关闭游标。 +5. **释放游标**:通过 `DEALLOCATE PREPARE` 释放游标。 + +游标使用示例: + +```sql +DELIMITER $ +CREATE PROCEDURE getTotal() +BEGIN + DECLARE total INT; + -- 创建接收游标数据的变量 + DECLARE sid INT; + DECLARE sname VARCHAR(10); + -- 创建总数变量 + DECLARE sage INT; + -- 创建结束标志变量 + DECLARE done INT DEFAULT false; + -- 创建游标 + DECLARE cur CURSOR FOR SELECT id,name,age from cursor_table where age>30; + -- 指定游标循环结束时的返回值 + DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = true; + SET total = 0; + OPEN cur; + FETCH cur INTO sid, sname, sage; + WHILE(NOT done) + DO + SET total = total + 1; + FETCH cur INTO sid, sname, sage; + END WHILE; + + CLOSE cur; + SELECT total; +END $ +DELIMITER ; + +-- 调用存储过程 +call getTotal(); +``` + +## 参考资料 + +- [《SQL 必知必会》](https://book.douban.com/subject/35167240/) +- [『浅入深出』MySQL 中事务的实现](https://draveness.me/mysql-transaction) +- [MySQL 的学习--触发器](https://www.cnblogs.com/CraryPrimitiveMan/p/4206942.html) +- [维基百科词条 - SQL](https://zh.wikipedia.org/wiki/SQL) +- [https://www.sitesbay.com/sql/index](https://www.sitesbay.com/sql/index) +- [SQL Subqueries](https://www.w3resource.com/sql/subqueries/understanding-sql-subqueries.php) +- [Quick breakdown of the types of joins](https://stackoverflow.com/questions/6294778/mysql-quick-breakdown-of-the-types-of-joins) +- [SQL UNION](https://www.w3resource.com/sql/sql-union.php) +- [SQL database security](https://www.w3resource.com/sql/database-security/create-users.php) +- [Mysql 中的存储过程](https://www.cnblogs.com/chenpi/p/5136483.html) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/03.\346\211\251\345\261\225SQL.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/03.\346\211\251\345\261\225SQL.md" new file mode 100644 index 00000000..4b80d891 --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/03.\346\211\251\345\261\225SQL.md" @@ -0,0 +1,85 @@ +--- +title: 扩展 SQL +date: 2020-10-10 19:03:05 +categories: + - 数据库 + - 关系型数据库 + - 综合 +tags: + - 数据库 + - 关系型数据库 + - SQL +permalink: /pages/55e9a7/ +--- + +# 扩展 SQL + +## 数据库 + +## 表 + +### 查看表的基本信息 + +```sql +SELECT * FROM information_schema.tables +WHERE table_schema = 'test' AND table_name = 'user'; +``` + +### 查看表的列信息 + +```sql +SELECT * FROM information_schema.columns +WHERE table_schema = 'test' AND table_name = 'user'; +``` + +### 如何批量删除大量数据 + +如果要根据时间范围批量删除大量数据,最简单的语句如下: + +```sql +delete from orders +where timestamp < SUBDATE(CURDATE(),INTERVAL 3 month); +``` + +上面的语句,大概率执行会报错,提示删除失败,因为需要删除的数据量太大了,所以需要分批删除。 + +可以先通过一次查询,找到符合条件的历史订单中最大的那个订单 ID,然后在删除语句中把删除的条件转换成按主键删除。 + +```sql +select max(id) from orders +where timestamp < SUBDATE(CURDATE(),INTERVAL 3 month); + +-- 分批删除,? 填上一条语句查到的最大 ID +delete from orders +where id <= ? +order by id limit 1000; +``` + +### 修改表的编码格式 + +utf8mb4 编码是 utf8 编码的超集,兼容 utf8,并且能存储 4 字节的表情字符。如果表的编码指定为 utf8,在保存 emoji 字段时会报错。 + +```sql +ALTER TABLE CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; +``` + +## 其他 + +### 显示哪些线程正在运行 + +```sql +mysql> show processlist; ++----+-----------------+-----------------+------+---------+-------+------------------------+------------------+ +| Id | User | Host | db | Command | Time | State | Info | ++----+-----------------+-----------------+------+---------+-------+------------------------+------------------+ +| 5 | event_scheduler | localhost | NULL | Daemon | 40230 | Waiting on empty queue | NULL | +| 10 | root | localhost:10120 | NULL | Query | 0 | init | show processlist | ++----+-----------------+-----------------+------+---------+-------+------------------------+------------------+ +2 rows in set (0.00 sec) +``` + +Mysql 连接完成后,如果你没有后续的动作,这个连接就处于空闲状态,你可以在 `show processlist` 命令中看到它。其中的 Command 列显示为“Sleep”的这一行,就表示现在系统里面有一个空闲连接。客户端如果太长时间没动静,连接器就会自动将它断开。这个时间是由参数 wait_timeout 控制的,默认值是 8 小时。 + +## 参考资料 + +- [《SQL 必知必会》](https://book.douban.com/subject/35167240/) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/99.SqlCheatSheet.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/99.SqlCheatSheet.md" new file mode 100644 index 00000000..d152f9df --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/99.SqlCheatSheet.md" @@ -0,0 +1,192 @@ +--- +title: SQL Cheat Sheet +date: 2022-07-16 14:17:08 +categories: + - 数据库 + - 关系型数据库 + - 综合 +tags: + - 数据库 + - 关系型数据库 + - SQL +permalink: /pages/e438a7/ +--- + +# SQL Cheat Sheet + +## 查找数据的查询 + +### **SELECT**: 用于从数据库中选择数据 + +- `SELECT` \* `FROM` table_name; + +### **DISTINCT**: 用于过滤掉重复的值并返回指定列的行 + +- `SELECT DISTINCT` column_name; + +### **WHERE**: 用于过滤记录/行 + +- `SELECT` column1, column2 `FROM` table_name `WHERE` condition; +- `SELECT` \* `FROM` table_name `WHERE` condition1 `AND` condition2; +- `SELECT` \* `FROM` table_name `WHERE` condition1 `OR` condition2; +- `SELECT` \* `FROM` table_name `WHERE NOT` condition; +- `SELECT` \* `FROM` table_name `WHERE` condition1 `AND` (condition2 `OR` condition3); +- `SELECT` \* `FROM` table_name `WHERE EXISTS` (`SELECT` column_name `FROM` table_name `WHERE` condition); + +### **ORDER BY**: 用于结果集的排序,升序(ASC)或者降序(DESC) + +- `SELECT` \* `FROM` table_name `ORDER BY` column; +- `SELECT` \* `FROM` table_name `ORDER BY` column `DESC`; +- `SELECT` \* `FROM` table_name `ORDER BY` column1 `ASC`, column2 `DESC`; + +### **SELECT TOP**: 用于指定从表顶部返回的记录数 + +- `SELECT TOP` number columns_names `FROM` table_name `WHERE` condition; +- `SELECT TOP` percent columns_names `FROM` table_name `WHERE` condition; +- 并非所有数据库系统都支持`SELECT TOP`。 MySQL 中是`LIMIT`子句 +- `SELECT` column_names `FROM` table_name `LIMIT` offset, count; + +### **LIKE**: 用于搜索列中的特定模式,WHERE 子句中使用的运算符 + +- % (percent sign) 是一个表示零个,一个或多个字符的通配符 +- \_ (underscore) 是一个表示单个字符通配符 +- `SELECT` column_names `FROM` table_name `WHERE` column_name `LIKE` pattern; +- `LIKE` ‘a%’ (查找任何以“a”开头的值) +- `LIKE` ‘%a’ (查找任何以“a”结尾的值) +- `LIKE` ‘%or%’ (查找任何包含“or”的值) +- `LIKE` ‘\_r%’ (查找任何第二位是“r”的值) +- `LIKE` ‘a*%*%’ (查找任何以“a”开头且长度至少为 3 的值) +- `LIKE` ‘[a-c]%’(查找任何以“a”或“b”或“c”开头的值) + +### **IN**: 用于在 WHERE 子句中指定多个值的运算符 + +- 本质上,IN 运算符是多个 OR 条件的简写 +- `SELECT` column_names `FROM` table_name `WHERE` column_name `IN` (value1, value2, …); +- `SELECT` column_names `FROM` table_name `WHERE` column_name `IN` (`SELECT STATEMENT`); + +### **BETWEEN**: 用于过滤给定范围的值的运算符 + +- `SELECT` column_names `FROM` table_name `WHERE` column_name `BETWEEN` value1 `AND` value2; +- `SELECT` \* `FROM` Products `WHERE` (column_name `BETWEEN` value1 `AND` value2) `AND NOT` column_name2 `IN` (value3, value4); +- `SELECT` \* `FROM` Products `WHERE` column_name `BETWEEN` #01/07/1999# AND #03/12/1999#; + +### **NULL**: 代表一个字段没有值 + +- `SELECT` \* `FROM` table_name `WHERE` column_name `IS NULL`; +- `SELECT` \* `FROM` table_name `WHERE` column_name `IS NOT NULL`; + +### **AS**: 用于给表或者列分配别名 + +- `SELECT` column_name `AS` alias_name `FROM` table_name; +- `SELECT` column_name `FROM` table_name `AS` alias_name; +- `SELECT` column_name `AS` alias_name1, column_name2 `AS` alias_name2; +- `SELECT` column_name1, column_name2 + ‘, ‘ + column_name3 `AS` alias_name; + +### **UNION**: 用于组合两个或者多个 SELECT 语句的结果集的运算符 + +- 每个 SELECT 语句必须拥有相同的列数 +- 列必须拥有相似的数据类型 +- 每个 SELECT 语句中的列也必须具有相同的顺序 +- `SELECT` columns_names `FROM` table1 `UNION SELECT` column_name `FROM` table2; +- `UNION` 仅允许选择不同的值, `UNION ALL` 允许重复 + +### **ANY|ALL**: 用于检查 WHERE 或 HAVING 子句中使用的子查询条件的运算符 + +- `ANY` 如果任何子查询值满足条件,则返回 true。 +- `ALL` 如果所有子查询值都满足条件,则返回 true。 +- `SELECT` columns_names `FROM` table1 `WHERE` column_name operator (`ANY`|`ALL`) (`SELECT` column_name `FROM` table_name `WHERE` condition); + +### **GROUP BY**: 通常与聚合函数(COUNT,MAX,MIN,SUM,AVG)一起使用,用于将结果集分组为一列或多列 + +- `SELECT` column_name1, COUNT(column_name2) `FROM` table_name `WHERE` condition `GROUP BY` column_name1 `ORDER BY` COUNT(column_name2) DESC; + +### **HAVING**: HAVING 子句指定 SELECT 语句应仅返回聚合值满足指定条件的行。它被添加到 SQL 语言中,因为 WHERE 关键字不能与聚合函数一起使用。 + +- `SELECT` `COUNT`(column_name1), column_name2 `FROM` table `GROUP BY` column_name2 `HAVING` `COUNT(`column_name1`)` > 5; + +## 修改数据的查询 + +### **INSERT INTO**: 用于在表中插入新记录/行 + +- `INSERT INTO` table_name (column1, column2) `VALUES` (value1, value2); +- `INSERT INTO` table_name `VALUES` (value1, value2 …); + +### **UPDATE**: 用于修改表中的现有记录/行 + +- `UPDATE` table_name `SET` column1 = value1, column2 = value2 `WHERE` condition; +- `UPDATE` table_name `SET` column_name = value; + +### **DELETE**: 用于删除表中的现有记录/行 + +- `DELETE FROM` table_name `WHERE` condition; +- `DELETE` \* `FROM` table_name; + +## 聚合查询 + +### **COUNT**: 返回出现次数 + +- `SELECT COUNT (DISTINCT` column_name`)`; + +### **MIN() and MAX()**: 返回所选列的最小/最大值 + +- `SELECT MIN (`column_names`) FROM` table_name `WHERE` condition; +- `SELECT MAX (`column_names`) FROM` table_name `WHERE` condition; + +### **AVG()**: 返回数字列的平均值 + +- `SELECT AVG (`column_name`) FROM` table_name `WHERE` condition; + +### **SUM()**: 返回数值列的总和 + +- `SELECT SUM (`column_name`) FROM` table_name `WHERE` condition; + +## 连接查询 + +### **INNER JOIN**: 内连接,返回在两张表中具有匹配值的记录 + +- `SELECT` column_names `FROM` table1 `INNER JOIN` table2 `ON` table1.column_name=table2.column_name; +- `SELECT` table1.column_name1, table2.column_name2, table3.column_name3 `FROM` ((table1 `INNER JOIN` table2 `ON` relationship) `INNER JOIN` table3 `ON` relationship); + +### **LEFT (OUTER) JOIN**: 左外连接,返回左表(table1)中的所有记录,以及右表中的匹配记录(table2) + +- `SELECT` column_names `FROM` table1 `LEFT JOIN` table2 `ON` table1.column_name=table2.column_name; + +### **RIGHT (OUTER) JOIN**: 右外连接,返回右表(table2)中的所有记录,以及左表(table1)中匹配的记录 + +- `SELECT` column_names `FROM` table1 `RIGHT JOIN` table2 `ON` table1.column_name=table2.column_name; + +### **FULL (OUTER) JOIN**: 全外连接,全连接是左右外连接的并集. 连接表包含被连接的表的所有记录, 如果缺少匹配的记录, 以 NULL 填充。 + +- `SELECT` column_names `FROM` table1 `FULL OUTER JOIN` table2 `ON` table1.column_name=table2.column_name; + +### **Self JOIN**: 自连接,表自身连接 + +- `SELECT` column_names `FROM` table1 T1, table1 T2 `WHERE` condition; + +## 视图查询 + +### **CREATE**: 创建视图 + +- `CREATE VIEW` view_name `AS SELECT` column1, column2 `FROM` table_name `WHERE` condition; + +### **SELECT**: 检索视图 + +- `SELECT` \* `FROM` view_name; + +### **DROP**: 删除视图 + +- `DROP VIEW` view_name; + +## 修改表的查询 + +### **ADD**: 添加字段 + +- `ALTER TABLE` table_name `ADD` column_name column_definition; + +### **MODIFY**: 修改字段数据类型 + +- `ALTER TABLE` table_name `MODIFY` column_name column_type; + +### **DROP**: 删除字段 + +- `ALTER TABLE` table_name `DROP COLUMN` column_name; \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/README.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/README.md" new file mode 100644 index 00000000..4db87d95 --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/README.md" @@ -0,0 +1,46 @@ +--- +title: 关系型数据库综合知识 +date: 2020-07-16 11:14:07 +categories: + - 数据库 + - 关系型数据库 + - 综合 +tags: + - 数据库 + - 关系型数据库 +permalink: /pages/22f2e3/ +hidden: true +--- + +# 关系型数据库综合知识 + +## 📖 内容 + +### [关系型数据库面试总结](01.关系型数据库面试.md) 💯 + +### [SQL 语法基础特性](02.SQL语法基础特性.md) + +### [SQL 语法高级特性](03.SQL语法高级特性.md) + +### [扩展 SQL](03.扩展SQL.md) + +### [SQL Cheat Sheet](99.SqlCheatSheet.md) + +## 📚 资料 + +- **官方** + - [Mysql 官网](https://www.mysql.com/) + - [Mysql 官方文档](https://dev.mysql.com/doc/) + - [Mysql 官方文档之命令行客户端](https://dev.mysql.com/doc/refman/8.0/en/mysql.html) +- **书籍** + - [《高性能 MySQL》](https://item.jd.com/11220393.html) - Mysql 经典 + - [《SQL 必知必会》](https://book.douban.com/subject/35167240/) - SQL 入门 +- **教程** + - [runoob.com MySQL 教程](http://www.runoob.com/mymysql-tutorial.html) - 入门级 SQL 教程 + - [mysql-tutorial](https://github.com/jaywcjlove/mysql-tutorial) +- **更多资源** + - [awesome-mysql](https://github.com/jobbole/awesome-mysql-cn) + +## 🚪 传送 + +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file diff --git a/docs/sql/mysql/mysql-quickstart.md "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/01.Mysql\345\272\224\347\224\250\346\214\207\345\215\227.md" similarity index 82% rename from docs/sql/mysql/mysql-quickstart.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/01.Mysql\345\272\224\347\224\250\346\214\207\345\215\227.md" index 7fdcc185..db45a48a 100644 --- a/docs/sql/mysql/mysql-quickstart.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/01.Mysql\345\272\224\347\224\250\346\214\207\345\215\227.md" @@ -1,45 +1,30 @@ +--- +title: Mysql 应用指南 +date: 2020-07-13 10:08:37 +categories: + - 数据库 + - 关系型数据库 + - Mysql +tags: + - 数据库 + - 关系型数据库 + - Mysql +permalink: /pages/5fe0f3/ +--- + # Mysql 应用指南 - - -- [1. SQL 执行过程](#1-sql-执行过程) -- [2. 存储引擎](#2-存储引擎) - - [2.1. 选择存储引擎](#21-选择存储引擎) - - [2.2. MyISAM](#22-myisam) - - [2.3. InnoDB](#23-innodb) -- [3. 数据类型](#3-数据类型) - - [3.1. 整型](#31-整型) - - [3.2. 浮点型](#32-浮点型) - - [3.3. 字符串](#33-字符串) - - [3.4. 时间和日期](#34-时间和日期) - - [3.5. BLOB 和 TEXT](#35-blob-和-text) - - [3.6. 枚举类型](#36-枚举类型) - - [3.7. 类型的选择](#37-类型的选择) -- [4. 索引](#4-索引) -- [5. 锁](#5-锁) -- [6. 事务](#6-事务) -- [7. 性能优化](#7-性能优化) -- [8. 复制](#8-复制) - - [8.1. 主从复制](#81-主从复制) - - [8.2. 读写分离](#82-读写分离) -- [9. 分布式事务](#9-分布式事务) -- [10. 分库分表](#10-分库分表) -- [11. 参考资料](#11-参考资料) -- [12. 传送门](#12-传送门) - - - -## 1. SQL 执行过程 +## SQL 执行过程 学习 Mysql,最好是先从宏观上了解 Mysql 工作原理。 -> 参考:[Mysql 工作流](docs/sql/mysql/mysql-index.md) +> 参考:[Mysql 工作流](02.MySQL工作流.md) -## 2. 存储引擎 +## 存储引擎 在文件系统中,Mysql 将每个数据库(也可以成为 schema)保存为数据目录下的一个子目录。创建表示,Mysql 会在数据库子目录下创建一个和表同名的 `.frm` 文件保存表的定义。因为 Mysql 使用文件系统的目录和文件来保存数据库和表的定义,大小写敏感性和具体平台密切相关。Windows 中大小写不敏感;类 Unix 中大小写敏感。**不同的存储引擎保存数据和索引的方式是不同的,但表的定义则是在 Mysql 服务层统一处理的。** -### 2.1. 选择存储引擎 +### 选择存储引擎 #### Mysql 内置的存储引擎 @@ -88,7 +73,7 @@ mysql> SHOW ENGINES; ALTER TABLE mytable ENGINE = InnoDB ``` -### 2.2. MyISAM +### MyISAM MyISAM 设计简单,数据以紧密格式存储。对于只读数据,或者表比较小、可以容忍修复操作,则依然可以使用 MyISAM。 @@ -96,7 +81,7 @@ MyISAM 引擎使用 B+Tree 作为索引结构,**叶节点的 data 域存放的 MyISAM 提供了大量的特性,包括:全文索引、压缩表、空间函数等。但是,MyISAM 不支持事务和行级锁。并且 MyISAM 不支持崩溃后的安全恢复。 -### 2.3. InnoDB +### InnoDB InnoDB 是 MySQL 默认的事务型存储引擎,只有在需要 InnoDB 不支持的特性时,才考虑使用其它存储引擎。 @@ -110,9 +95,9 @@ InnoDB 是基于聚簇索引建立的,与其他存储引擎有很大不同。 支持真正的在线热备份。其它存储引擎不支持在线热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。 -## 3. 数据类型 +## 数据类型 -### 3.1. 整型 +### 整型 `TINYINT`, `SMALLINT`, `MEDIUMINT`, `INT`, `BIGINT` 分别使用 `8`, `16`, `24`, `32`, `64` 位存储空间,一般情况下越小的列越好。 @@ -120,7 +105,7 @@ InnoDB 是基于聚簇索引建立的,与其他存储引擎有很大不同。 `INT(11)` 中的数字只是规定了交互工具显示字符的个数,对于存储和计算来说是没有意义的。 -### 3.2. 浮点型 +### 浮点型 `FLOAT` 和 `DOUBLE` 为浮点类型。 @@ -128,7 +113,7 @@ InnoDB 是基于聚簇索引建立的,与其他存储引擎有很大不同。 `FLOAT`、`DOUBLE` 和 `DECIMAL` 都可以指定列宽,例如 `DECIMAL(18, 9)` 表示总共 18 位,取 9 位存储小数部分,剩下 9 位存储整数部分。 -### 3.3. 字符串 +### 字符串 主要有 `CHAR` 和 `VARCHAR` 两种类型,一种是定长的,一种是变长的。 @@ -136,7 +121,7 @@ InnoDB 是基于聚簇索引建立的,与其他存储引擎有很大不同。 `VARCHAR` 会保留字符串末尾的空格,而 `CHAR` 会删除。 -### 3.4. 时间和日期 +### 时间和日期 MySQL 提供了两种相似的日期时间类型:`DATATIME` 和 `TIMESTAMP`。 @@ -160,17 +145,17 @@ MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提 应该尽量使用 TIMESTAMP,因为它比 DATETIME 空间效率更高。 -### 3.5. BLOB 和 TEXT +### BLOB 和 TEXT `BLOB` 和 `TEXT` 都是为了存储大的数据而设计,前者存储二进制数据,后者存储字符串数据。 不能对 `BLOB` 和 `TEXT` 类型的全部内容进行排序、索引。 -### 3.6. 枚举类型 +### 枚举类型 大多数情况下没有使用枚举类型的必要,其中一个缺点是:枚举的字符串列表是固定的,添加和删除字符串(枚举选项)必须使用`ALTER TABLE`(如果只只是在列表末尾追加元素,不需要重建表)。 -### 3.7. 类型的选择 +### 类型的选择 - 整数类型通常是标识列最好的选择,因为它们很快并且可以使用 `AUTO_INCREMENT`。 @@ -178,25 +163,25 @@ MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提 - 应该尽量避免用字符串类型作为标识列,因为它们很消耗空间,并且通常比数字类型慢。对于 `MD5`、`SHA`、`UUID` 这类随机字符串,由于比较随机,所以可能分布在很大的空间内,导致 `INSERT` 以及一些 `SELECT` 语句变得很慢。 - 如果存储 UUID ,应该移除 `-` 符号;更好的做法是,用 `UNHEX()` 函数转换 UUID 值为 16 字节的数字,并存储在一个 `BINARY(16)` 的列中,检索时,可以通过 `HEX()` 函数来格式化为 16 进制格式。 -## 4. 索引 +## 索引 -> 详见:[Mysql 索引](mysql-index.md) +> 详见:[Mysql 索引](05.Mysql索引.md) -## 5. 锁 +## 锁 -> 详见:[Mysql 锁](mysql-lock.md) +> 详见:[Mysql 锁](04.Mysql锁.md) -## 6. 事务 +## 事务 -> 详见:[Mysql 事务](mysql-transaction.md) +> 详见:[Mysql 事务](03.Mysql事务.md) -## 7. 性能优化 +## 性能优化 -> 详见:[Mysql 性能优化](mysql-optimization.md) +> 详见:[Mysql 性能优化](06.Mysql性能优化.md) -## 8. 复制 +## 复制 -### 8.1. 主从复制 +### 主从复制 Mysql 支持两种复制:基于行的复制和基于语句的复制。 @@ -209,10 +194,10 @@ Mysql 支持两种复制:基于行的复制和基于语句的复制。 - **SQL 线程** :负责读取中继日志并重放其中的 SQL 语句。
- +
-### 8.2. 读写分离 +### 读写分离 主服务器用来处理写操作以及实时性要求比较高的读操作,而从服务器用来处理读操作。 @@ -225,27 +210,16 @@ MySQL 读写分离能提高性能的原因在于: - 增加冗余,提高可用性。
- +
------- - -(分割线)以下为高级特性,也是关系型数据库通用方案 - -## 9. 分布式事务 - -> 参考:[分布式事务基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/theory/distributed-transaction.md) - -## 10. 分库分表 - -> 参考:[分布式存储基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/theory/distributed-storage.md) -## 11. 参考资料 +## 参考资料 - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) - [20+ 条 MySQL 性能优化的最佳经验](https://www.jfox.info/20-tiao-mysql-xing-nen-you-hua-de-zui-jia-jing-yan.html) - [How to create unique row ID in sharded databases?](https://stackoverflow.com/questions/788829/how-to-create-unique-row-id-in-sharded-databases) - [SQL Azure Federation – Introduction](http://geekswithblogs.net/shaunxu/archive/2012/01/07/sql-azure-federation-ndash-introduction.aspx) -## 12. 传送门 +## 传送门 -◾ 🏠 [DB-TUTORIAL 首页](https://github.com/dunwu/db-tutorial) ◾ 🎯 [我的博客](https://github.com/dunwu/blog) ◾ +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file diff --git a/docs/sql/mysql/mysql-workflow.md "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/02.MySQL\345\267\245\344\275\234\346\265\201.md" similarity index 90% rename from docs/sql/mysql/mysql-workflow.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/02.MySQL\345\267\245\344\275\234\346\265\201.md" index 338c4479..05d9f88a 100644 --- a/docs/sql/mysql/mysql-workflow.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/02.MySQL\345\267\245\344\275\234\346\265\201.md" @@ -1,25 +1,20 @@ -# MySQL 工作流 - - +--- +title: MySQL 工作流 +date: 2020-07-16 11:14:07 +categories: + - 数据库 + - 关系型数据库 + - Mysql +tags: + - 数据库 + - 关系型数据库 + - Mysql +permalink: /pages/8262aa/ +--- -- [1. 基础架构](#1-基础架构) -- [2. 查询过程](#2-查询过程) - - [2.1. (一)连接](#21-一连接) - - [2.2. (二)查询缓存](#22-二查询缓存) - - [2.3. (三)语法分析](#23-三语法分析) - - [2.4. (四)查询优化](#24-四查询优化) - - [2.5. (五)查询执行引擎](#25-五查询执行引擎) - - [2.6. (六)返回结果](#26-六返回结果) -- [3. 更新过程](#3-更新过程) - - [3.1. redo log](#31-redo-log) - - [3.2. bin log](#32-bin-log) - - [3.3. redo log vs. bin log](#33-redo-log-vs-bin-log) - - [3.4. 两阶段提交](#34-两阶段提交) -- [4. 参考资料](#4-参考资料) - - +# MySQL 工作流 -## 1. 基础架构 +## 基础架构 大体来说,MySQL 可以分为 Server 层和存储引擎层两部分。 @@ -27,24 +22,24 @@ **存储引擎层负责数据的存储和提取**。其架构模式是插件式的,支持 InnoDB、MyISAM、Memory 等多个存储引擎。现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始成为了默认存储引擎。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200227201908.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200227201908.jpg) -## 2. 查询过程 +## 查询过程 SQL 语句在 MySQL 中是如何执行的? -MySQL 整个查询执行过程,总的来说分为 6 个步骤: +MySQL 整个查询执行过程,总的来说分为 6 个步骤,分别对应 6 个组件: -1. 客户端和 MySQL 服务器建立连接;客户端向 MySQL 服务器发送一条查询请求。 +1. 连接器:客户端和 MySQL 服务器建立连接;连接器负责跟客户端建立连接、获取权限、维持和管理连接。 2. MySQL 服务器首先检查查询缓存,如果命中缓存,则立刻返回结果。否则进入下一阶段。 3. MySQL 服务器进行 SQL 分析:语法分析、词法分析。 4. MySQL 服务器用优化器生成对应的执行计划。 5. MySQL 服务器根据执行计划,调用存储引擎的 API 来执行查询。 6. MySQL 服务器将结果返回给客户端,同时缓存查询结果。 -### 2.1. (一)连接 +### (一)连接器 -使用 MySQL 第一步自然是要连接数据库。 +使用 MySQL 第一步自然是要连接数据库。**连接器负责跟客户端建立连接、获取权限、维持和管理连接**。 MySQL 客户端/服务端通信是**半双工模式**:即任一时刻,要么是服务端向客户端发送数据,要么是客户端向服务器发送数据。客户端用一个单独的数据包将查询请求发送给服务器,所以当查询语句很长的时候,需要设置`max_allowed_packet`参数。但是需要注意的是,如果查询实在是太大,服务端会拒绝接收更多数据并抛出异常。 @@ -61,7 +56,7 @@ MySQL 客户端连接命令:`mysql -h<主机> -P<端口> -u<用户名> -p<密 - **定期断开长连接**。使用一段时间,或者程序里面判断执行过一个占用内存的大查询后,断开连接,之后要查询再重连。 - 如果你用的是 MySQL 5.7 或更新版本,可以在每次执行一个比较大的操作后,通过执行 `mysql_reset_connection` 来重新初始化连接资源。这个过程不需要重连和重新做权限验证,但是会将连接恢复到刚刚创建完时的状态。 -### 2.2. (二)查询缓存 +### (二)查询缓存 > **不建议使用数据库缓存,因为往往弊大于利**。 @@ -69,8 +64,7 @@ MySQL 客户端连接命令:`mysql -h<主机> -P<端口> -u<用户名> -p<密 MySQL 将缓存存放在一个引用表(不要理解成`table`,可以认为是类似于`HashMap`的数据结构),通过一个哈希值索引,这个哈希值通过查询本身、当前要查询的数据库、客户端协议版本号等一些可能影响结果的信息计算得来。所以两个查询在任何字符上的不同(例如:空格、注释),都会导致缓存不会命中。 -**如果查询中包含任何用户自定义函数、存储函数、用户变量、临时表、mysql 库中的系统表,其查询结果** -**都不会被缓存**。比如函数`NOW()`或者`CURRENT_DATE()`会因为不同的查询时间,返回不同的查询结果,再比如包含`CURRENT_USER`或者`CONNECION_ID()`的查询语句会因为不同的用户而返回不同的结果,将这样的查询结果缓存起来没有任何的意义。 +**如果查询中包含任何用户自定义函数、存储函数、用户变量、临时表、mysql 库中的系统表,其查询结果都不会被缓存**。比如函数`NOW()`或者`CURRENT_DATE()`会因为不同的查询时间,返回不同的查询结果,再比如包含`CURRENT_USER`或者`CONNECION_ID()`的查询语句会因为不同的用户而返回不同的结果,将这样的查询结果缓存起来没有任何的意义。 **不建议使用数据库缓存,因为往往弊大于利**。查询缓存的失效非常频繁,只要有对一个表的更新,这个表上所有的查询缓存都会被清空。因此很可能你费劲地把结果存起来,还没使用呢,就被一个更新全清空了。对于更新压力大的数据库来说,查询缓存的命中率会非常低。除非你的业务就是有一张静态表,很长时间才会更新一次。比如,一个系统配置表,那这张表上的查询才适合使用查询缓存。 @@ -82,14 +76,14 @@ select SQL_CACHE * from T where ID=10; > 注意:MySQL 8.0 版本直接将查询缓存的整块功能删掉了。 -### 2.3. (三)语法分析 +### (三)语法分析 如果没有命中查询缓存,就要开始真正执行语句了。首先,MySQL 需要知道你要做什么,因此需要对 SQL 语句做解析。MySQL 通过关键字对 SQL 语句进行解析,并生成一颗对应的语法解析树。这个过程中,分析器主要通过语法规则来验证和解析。比如 SQL 中是否使用了错误的关键字或者关键字的顺序是否正确等等。预处理则会根据 MySQL 规则进一步检查解析树是否合法。比如检查要查询的数据表和数据列是否存在等等。 - 分析器先会先做“**词法分析**”。你输入的是由多个字符串和空格组成的一条 SQL 语句,MySQL 需要识别出里面的字符串分别是什么,代表什么。MySQL 从你输入的"select"这个关键字识别出来,这是一个查询语句。它也要把字符串“T”识别成“表名 T”,把字符串“ID”识别成“列 ID”。 - 接下来,要做“**语法分析**”。根据词法分析的结果,语法分析器会根据语法规则,判断你输入的这个 SQL 语句是否满足 MySQL 语法。如果你的语句不对,就会收到“You have an error in your SQL syntax”的错误提醒,比如下面这个语句 select 少打了开头的字母“s”。 -### 2.4. (四)查询优化 +### (四)查询优化 经过了分析器,MySQL 就知道你要做什么了。在开始执行之前,还要先经过优化器的处理。 @@ -122,11 +116,11 @@ MySQL 的查询优化器是一个非常复杂的部件,它使用了非常多 随着 MySQL 的不断发展,优化器使用的优化策略也在不断的进化,这里仅仅介绍几个非常常用且容易理解的优化策略,其他的优化策略,大家自行查阅吧。 -### 2.5. (五)查询执行引擎 +### (五)查询执行引擎 在完成解析和优化阶段以后,MySQL 会生成对应的执行计划,查询执行引擎根据执行计划给出的指令逐步执行得出结果。整个执行过程的大部分操作均是通过调用存储引擎实现的接口来完成,这些接口被称为`handler API`。查询过程中的每一张表由一个`handler`实例表示。实际上,MySQL 在查询优化阶段就为每一张表创建了一个`handler`实例,优化器可以根据这些实例的接口来获取表的相关信息,包括表的所有列名、索引统计信息等。存储引擎接口提供了非常丰富的功能,但其底层仅有几十个接口,这些接口像搭积木一样完成了一次查询的大部分操作。 -### 2.6. (六)返回结果 +### (六)返回结果 查询过程的最后一个阶段就是将结果返回给客户端。即使查询不到数据,MySQL 仍然会返回这个查询的相关信息,比如该查询影响到的行数以及执行时间等等。 @@ -134,11 +128,11 @@ MySQL 的查询优化器是一个非常复杂的部件,它使用了非常多 结果集返回客户端是一个增量且逐步返回的过程。有可能 MySQL 在生成第一条结果时,就开始向客户端逐步返回结果集了。这样服务端就无须存储太多结果而消耗过多内存,也可以让客户端第一时间获得返回结果。需要注意的是,结果集中的每一行都会以一个满足 ① 中所描述的通信协议的数据包发送,再通过 TCP 协议进行传输,在传输过程中,可能对 MySQL 的数据包进行缓存然后批量发送。 -## 3. 更新过程 +## 更新过程 MySQL 更新过程和 MySQL 查询过程类似,也会将流程走一遍。不一样的是:**更新流程还涉及两个重要的日志模块,:redo log(重做日志)和 binlog(归档日志)**。 -### 3.1. redo log +### redo log **redo log 是 InnoDB 引擎特有的日志**。**redo log 即重做日志**。redo log 是物理日志,记录的是“在某个数据页上做了什么修改”。 @@ -146,11 +140,11 @@ MySQL 更新过程和 MySQL 查询过程类似,也会将流程走一遍。不 InnoDB 的 redo log 是固定大小的,比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么这块“粉板”总共就可以记录 4GB 的操作。从头开始写,写到末尾就又回到开头循环写。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200630180342.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200630180342.png) 有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为**crash-safe**。 -### 3.2. bin log +### bin log **bin log 即归档日志**。binlog 是逻辑日志,记录的是这个语句的原始逻辑。 @@ -160,7 +154,7 @@ binlog 是可以追加写入的,即写到一定大小后会切换到下一个 `sync_binlog` 这个参数设置成 1 的时候,表示每次事务的 binlog 都持久化到磁盘。这个参数我也建议你设置成 1,这样可以保证 MySQL 异常重启之后 binlog 不丢失。 -### 3.3. redo log vs. bin log +### redo log vs. bin log 这两种日志有以下三点不同。 @@ -178,9 +172,9 @@ binlog 是可以追加写入的,即写到一定大小后会切换到下一个 这里我给出这个 update 语句的执行流程图,图中浅色框表示是在 InnoDB 内部执行的,深色框表示是在执行器中执行的。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200714133806.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200714133806.png) -### 3.4. 两阶段提交 +### 两阶段提交 redo log 的写入拆成了两个步骤:prepare 和 commit,这就是"两阶段提交"。为什么日志需要“两阶段提交”。 @@ -193,7 +187,7 @@ redo log 的写入拆成了两个步骤:prepare 和 commit,这就是"两阶 可以看到,如果不使用“两阶段提交”,那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致。 -## 4. 参考资料 +## 参考资料 - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) -- [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) +- [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) \ No newline at end of file diff --git a/docs/sql/mysql/mysql-transaction.md "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/03.Mysql\344\272\213\345\212\241.md" similarity index 81% rename from docs/sql/mysql/mysql-transaction.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/03.Mysql\344\272\213\345\212\241.md" index 24e074e3..75a07c06 100644 --- a/docs/sql/mysql/mysql-transaction.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/03.Mysql\344\272\213\345\212\241.md" @@ -1,43 +1,31 @@ +--- +title: Mysql 事务 +date: 2020-06-03 19:32:09 +categories: + - 数据库 + - 关系型数据库 + - Mysql +tags: + - 数据库 + - 关系型数据库 + - Mysql + - 事务 +permalink: /pages/00b04d/ +--- + # Mysql 事务 > 不是所有的 Mysql 存储引擎都实现了事务处理。支持事务的存储引擎有:`InnoDB` 和 `NDB Cluster`。不支持事务的存储引擎,代表有:`MyISAM`。 > > 用户可以根据业务是否需要事务处理(事务处理可以保证数据安全,但会增加系统开销),选择合适的存储引擎。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716074533.png) - - - -- [1. 事务简介](#1-事务简介) -- [2. 事务用法](#2-事务用法) - - [2.1. 事务处理指令](#21-事务处理指令) - - [2.2. AUTOCOMMIT](#22-autocommit) -- [3. ACID](#3-acid) -- [4. 事务隔离级别](#4-事务隔离级别) - - [4.1. 事务隔离简介](#41-事务隔离简介) - - [4.2. 未提交读](#42-未提交读) - - [4.3. 提交读](#43-提交读) - - [4.4. 可重复读](#44-可重复读) - - [4.5. 串行化](#45-串行化) - - [4.6. 隔离级别小结](#46-隔离级别小结) -- [5. 死锁](#5-死锁) - - [5.1. 死锁的原因](#51-死锁的原因) - - [5.2. 避免死锁](#52-避免死锁) - - [5.3. 解决死锁](#53-解决死锁) -- [6. 分布式事务](#6-分布式事务) -- [7. 事务最佳实践](#7-事务最佳实践) - - [7.1. 尽量使用低级别事务隔离](#71-尽量使用低级别事务隔离) - - [7.2. 避免行锁升级表锁](#72-避免行锁升级表锁) - - [7.3. 缩小事务范围](#73-缩小事务范围) -- [8. 参考资料](#8-参考资料) - - - -## 1. 事务简介 +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220721072721.png) + +## 事务简介 > 事务简单来说:**一个 Session 中所进行所有的操作,要么同时成功,要么同时失败**。进一步说,事务指的是满足 ACID 特性的一组操作,可以通过 `Commit` 提交一个事务,也可以使用 `Rollback` 进行回滚。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库事务.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/RDB/数据库事务.png) **事务就是一组原子性的 SQL 语句**。具体来说,事务指的是满足 ACID 特性的一组操作。 @@ -49,11 +37,11 @@ T1 和 T2 两个线程都对一个数据进行修改,T1 先修改,T2 随后修改,T2 的修改覆盖了 T1 的修改。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库并发一致性-丢失修改.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/RDB/数据库并发一致性-丢失修改.png) -## 2. 事务用法 +## 事务用法 -### 2.1. 事务处理指令 +### 事务处理指令 Mysql 中,使用 `START TRANSACTION` 语句开始一个事务;使用 `COMMIT` 语句提交所有的修改;使用 `ROLLBACK` 语句撤销所有的修改。不能回退 `SELECT` 语句,回退 `SELECT` 语句也没意义;也不能回退 `CREATE` 和 `DROP` 语句。 @@ -115,7 +103,7 @@ SELECT * FROM user; 1 root1 root1 xxxx@163.com ``` -### 2.2. AUTOCOMMIT +### AUTOCOMMIT **MySQL 默认采用隐式提交策略(`autocommit`)**。每执行一条语句就把这条语句当成一个事务然后进行提交。当出现 `START TRANSACTION` 语句时,会关闭隐式提交;当 `COMMIT` 或 `ROLLBACK` 语句执行后,事务会自动关闭,重新恢复隐式提交。 @@ -132,7 +120,7 @@ SET autocommit = 0; SET autocommit = 1; ``` -## 3. ACID +## ACID ACID 是数据库事务正确执行的四个基本要素。 @@ -155,13 +143,13 @@ ACID 是数据库事务正确执行的四个基本要素。 - 在并发的情况下,多个事务并行执行,事务不仅要满足原子性,还需要满足隔离性,才能满足一致性。 - 事务满足持久化是为了能应对系统崩溃的情况。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库ACID.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/RDB/数据库ACID.png) > MySQL 默认采用自动提交模式(`AUTO COMMIT`)。也就是说,如果不显式使用 `START TRANSACTION` 语句来开始一个事务,那么每个查询操作都会被当做一个事务并自动提交。 -## 4. 事务隔离级别 +## 事务隔离级别 -### 4.1. 事务隔离简介 +### 事务隔离简介 在并发环境下,事务的隔离性很难保证,因此会出现很多并发一致性问题: @@ -172,8 +160,8 @@ ACID 是数据库事务正确执行的四个基本要素。 在 SQL 标准中,定义了四种事务隔离级别(级别由低到高): -- **未提交读** -- **提交读** +- **读未提交** +- **读提交** - **可重复读** - **串行化** @@ -196,63 +184,61 @@ SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; ``` -### 4.2. 未提交读 +### 读未提交 -**`未提交读(READ UNCOMMITTED)` 是指:事务中的修改,即使没有提交,对其它事务也是可见的**。 +**`读未提交(read uncommitted)` 是指:事务中的修改,即使没有提交,对其它事务也是可见的**。 -未提交读的问题:事务可以读取未提交的数据,也被称为 **脏读(Dirty Read)**。 +读未提交的问题:事务可以读取未提交的数据,也被称为 **脏读(Dirty Read)**。 T1 修改一个数据,T2 随后读取这个数据。如果 T1 撤销了这次修改,那么 T2 读取的数据是脏数据。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库并发一致性-脏数据.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/RDB/数据库并发一致性-脏数据.png) -### 4.3. 提交读 +### 读提交 -**`提交读(READ COMMITTED)` 是指:一个事务只能读取已经提交的事务所做的修改**。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。提交读解决了脏读的问题。 +**`读提交(read committed)` 是指:事务提交后,其他事务才能看到它的修改**。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。读提交解决了脏读的问题。 -提交读是大多数数据库的默认事务隔离级别。 +读提交是大多数数据库的默认事务隔离级别。 -提交读有时也叫不可重复读,它的问题是:执行两次相同的查询,得到的结果可能不一致。 +读提交有时也叫不可重复读,它的问题是:执行两次相同的查询,得到的结果可能不一致。 T2 读取一个数据,T1 对该数据做了修改。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库并发一致性-不可重复读.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/RDB/数据库并发一致性-不可重复读.png) -### 4.4. 可重复读 +### 可重复读 **`可重复读(REPEATABLE READ)` 是指:保证在同一个事务中多次读取同样数据的结果是一样的**。可重复读解决了不可重复读问题。 可重复读是 Mysql 的默认事务隔离级别。 -可重复读的问题:当某个事务读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务又再次读取该范围的记录时,会产生 **幻读(Phantom Read)**。 - -T1 读取某个范围的数据,T2 在这个范围内插入新的数据,T1 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同。 +可重复读的问题:事务 T1 读取某个范围内的记录时,事务 T2 在该范围内插入了新的记录,T1 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同,即为 **幻读(Phantom Read)**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库并发一致性-幻读.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/RDB/数据库并发一致性-幻读.png) -### 4.5. 串行化 +### 串行化 -**`串行化(SERIALIXABLE)` 是指:强制事务串行执行**。 +**`串行化(SERIALIXABLE)` 是指:强制事务串行执行,对于同一行记录,加读写锁,一旦出现锁冲突,必须等前面的事务释放锁**。 强制事务串行执行,则避免了所有的并发问题。串行化策略会在读取的每一行数据上都加锁,这可能导致大量的超时和锁竞争。这对于高并发应用基本上是不可接受的,所以一般不会采用这个级别。 -### 4.6. 隔离级别小结 +### 隔离级别小结 -- **`未提交读(READ UNCOMMITTED)`** - 事务中的修改,即使没有提交,对其它事务也是可见的。 -- **`提交读(READ COMMITTED)`** - 一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。 +- **`读未提交(READ UNCOMMITTED)`** - 事务中的修改,即使没有提交,对其它事务也是可见的。 +- **`读提交(READ COMMITTED)`** - 一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。 - **`重复读(REPEATABLE READ)`** - 保证在同一个事务中多次读取同样数据的结果是一样的。 -- **`串行化(SERIALIXABLE)`** - 强制事务串行执行。 +- **`串行化(SERIALIXABLE)`** - 对于同一行记录,加读写锁,一旦出现锁冲突,必须等前面的事务释放锁。 数据库隔离级别解决的问题: -| 隔离级别 | 脏读 | 不可重复读 | 幻读 | -| :------: | :--: | :--------: | :--: | -| 未提交读 | ❌ | ❌ | ❌ | -| 提交读 | ✔️ | ❌ | ❌ | -| 可重复读 | ✔️ | ✔️ | ❌ | -| 可串行化 | ✔️ | ✔️ | ✔️ | +| 隔离级别 | 丢失修改 | 脏读 | 不可重复读 | 幻读 | +| :------: | :------: | :--: | :--------: | :--: | +| 读未提交 | ✔️ | ❌ | ❌ | ❌ | +| 读提交 | ✔️ | ✔️ | ❌ | ❌ | +| 可重复读 | ✔️ | ✔️ | ✔️ | ❌ | +| 可串行化 | ✔️ | ✔️ | ✔️ | ✔️ | -## 5. 死锁 +## 死锁 **死锁是指两个或多个事务竞争同一资源,并请求锁定对方占用的资源,从而导致恶性循环的现象**。 @@ -262,7 +248,7 @@ T1 读取某个范围的数据,T2 在这个范围内插 - 多个事务同时锁定同一个资源时,也会产生死锁。 -### 5.1. 死锁的原因 +### 死锁的原因 行锁的具体实现算法有三种:record lock、gap lock 以及 next-key lock。record lock 是专门对索引项加锁;gap lock 是对索引项之间的间隙加锁;next-key lock 则是前面两种的组合,对索引项以其之间的间隙加锁。 @@ -280,21 +266,21 @@ T1 读取某个范围的数据,T2 在这个范围内插 > INSERT INTO `demo`.`order_record`(`order_no`, `status`, `create_date`) VALUES (5, 1, ‘2019-07-13 10:57:03’); -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200630153139.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200630153139.png) **另一个死锁场景** InnoDB 存储引擎的主键索引为聚簇索引,其它索引为辅助索引。如果使用辅助索引来更新数据库,就需要使用聚簇索引来更新数据库字段。如果两个更新事务使用了不同的辅助索引,或一个使用了辅助索引,一个使用了聚簇索引,就都有可能导致锁资源的循环等待。由于本身两个事务是互斥,也就构成了以上死锁的四个必要条件了。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200630154606.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200630154606.png) 出现死锁的步骤: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200630154619.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200630154619.png) 综上可知,在更新操作时,我们应该尽量使用主键来更新表字段,这样可以有效避免一些不必要的死锁发生。 -### 5.2. 避免死锁 +### 避免死锁 预防死锁的注意事项: @@ -308,7 +294,7 @@ InnoDB 存储引擎的主键索引为聚簇索引,其它索引为辅助索引 我们还可以使用其它的方式来代替数据库实现幂等性校验。例如,使用 Redis 以及 ZooKeeper 来实现,运行效率比数据库更佳。 -### 5.3. 解决死锁 +### 解决死锁 当出现死锁以后,有两种策略: @@ -323,7 +309,7 @@ InnoDB 存储引擎的主键索引为聚簇索引,其它索引为辅助索引 主动死锁检测在发生死锁的时候,是能够快速发现并进行处理的,但是它也是有额外负担的。你可以想象一下这个过程:每当一个事务被锁的时候,就要看看它所依赖的线程有没有被别人锁住,如此循环,最后判断是否出现了循环等待,也就是死锁。 -## 6. 分布式事务 +## 分布式事务 在单一数据节点中,事务仅限于对单一数据库资源的访问控制,称之为 **本地事务**。几乎所有的成熟的关系型数据库都提供了对本地事务的原生支持。 @@ -348,21 +334,21 @@ InnoDB 存储引擎的主键索引为聚簇索引,其它索引为辅助索引 - 本地消息表/MQ 事务 都适用于事务中参与方支持操作幂等,对一致性要求不高,业务上能容忍数据不一致到一个人工检查周期,事务涉及的参与方、参与环节较少,业务上有对账/校验系统兜底。 - Saga 事务 由于 Saga 事务不能保证隔离性,需要在业务层控制并发,适合于业务场景事务并发操作同一资源较少的情况。 Saga 相比缺少预提交动作,导致补偿动作的实现比较麻烦,例如业务是发送短信,补偿动作则得再发送一次短信说明撤销,用户体验比较差。Saga 事务较适用于补偿动作容易处理的场景。 -> 分布式事务详细说明、分析请参考:[分布式事务基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/theory/distributed-transaction.md) +> 分布式事务详细说明、分析请参考:[分布式事务基本原理](https://dunwu.github.io/blog/pages/e1881c/) -## 7. 事务最佳实践 +## 事务最佳实践 高并发场景下的事务到底该如何调优? -### 7.1. 尽量使用低级别事务隔离 +### 尽量使用低级别事务隔离 结合业务场景,尽量使用低级别事务隔离 -### 7.2. 避免行锁升级表锁 +### 避免行锁升级表锁 在 InnoDB 中,行锁是通过索引实现的,如果不通过索引条件检索数据,行锁将会升级到表锁。我们知道,表锁是会严重影响到整张表的操作性能的,所以应该尽力避免。 -### 7.3. 缩小事务范围 +### 缩小事务范围 有时候,数据库并发访问量太大,会出现以下异常: @@ -374,7 +360,7 @@ MySQLQueryInterruptedException: Query execution was interrupted 又因为锁的竞争是不公平的,当多个事务同时对一条记录进行更新时,极端情况下,一个更新操作进去排队系统后,可能会一直拿不到锁,最后因超时被系统打断踢出。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200630112600.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200630112600.png) 如上图中的操作,虽然都是在一个事务中,但锁的申请在不同时间,只有当其他操作都执行完,才会释放所有锁。因为扣除库存是更新操作,属于行锁,这将会影响到其他操作该数据的事务,所以我们应该尽量避免长时间地持有该锁,尽快释放该锁。又因为先新建订单和先扣除库存都不会影响业务,所以我们可以将扣除库存操作放到最后,也就是使用执行顺序 1,以此尽量减小锁的持有时间。 @@ -382,8 +368,8 @@ MySQLQueryInterruptedException: Query execution was interrupted 知道了这个设定,对我们使用事务有什么帮助呢?那就是,如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。 -## 8. 参考资料 +## 参考资料 - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) - [《Java 性能调优实战》](https://time.geekbang.org/column/intro/100028001) -- [ShardingSphere 分布式事务](https://shardingsphere.apache.org/document/current/cn/features/transaction/) +- [ShardingSphere 分布式事务](https://shardingsphere.apache.org/document/current/cn/features/transaction/) \ No newline at end of file diff --git a/docs/sql/mysql/mysql-lock.md "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/04.Mysql\351\224\201.md" similarity index 92% rename from docs/sql/mysql/mysql-lock.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/04.Mysql\351\224\201.md" index 1fbe52fd..7474e504 100644 --- a/docs/sql/mysql/mysql-lock.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/04.Mysql\351\224\201.md" @@ -1,25 +1,23 @@ -# Mysql 锁 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716064947.png) +--- +title: Mysql 锁 +date: 2020-09-07 07:54:19 +categories: + - 数据库 + - 关系型数据库 + - Mysql +tags: + - 数据库 + - 关系型数据库 + - Mysql + - 锁 +permalink: /pages/f1f151/ +--- - - -- [1. 悲观锁和乐观锁](#1-悲观锁和乐观锁) -- [2. 表级锁和行级锁](#2-表级锁和行级锁) -- [3. 读写锁](#3-读写锁) -- [4. 意向锁](#4-意向锁) -- [5. MVCC](#5-mvcc) - - [5.1. MVCC 思想](#51-mvcc-思想) - - [5.2. 版本号](#52-版本号) - - [5.3. Undo 日志](#53-undo-日志) - - [5.4. ReadView](#54-readview) - - [5.5. 快照读与当前读](#55-快照读与当前读) -- [6. 行锁](#6-行锁) -- [7. 参考资料](#7-参考资料) +# Mysql 锁 - +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716064947.png) -## 1. 悲观锁和乐观锁 +## 悲观锁和乐观锁 确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性,**乐观锁和悲观锁是并发控制主要采用的技术手段。** @@ -44,7 +42,7 @@ where id=#{id} and version=#{version}; > 更详细的乐观锁说可以参考:[使用 mysql 乐观锁解决并发问题](https://www.cnblogs.com/laoyeye/p/8097684.html) -## 2. 表级锁和行级锁 +## 表级锁和行级锁 从数据库的锁粒度来看,MySQL 中提供了两种封锁粒度:行级锁和表级锁。 @@ -57,7 +55,7 @@ where id=#{id} and version=#{version}; 在 `InnoDB` 中,**行锁是通过给索引上的索引项加锁来实现的**。**如果没有索引,`InnoDB` 将会通过隐藏的聚簇索引来对记录加锁**。 -## 3. 读写锁 +## 读写锁 - 独享锁(Exclusive),简写为 X 锁,又称写锁。使用方式:`SELECT ... FOR UPDATE;` - 共享锁(Shared),简写为 S 锁,又称读锁。使用方式:`SELECT ... LOCK IN SHARE MODE;` @@ -66,7 +64,7 @@ where id=#{id} and version=#{version}; **`InnoDB` 下的行锁、间隙锁、next-key 锁统统属于独享锁**。 -## 4. 意向锁 +## 意向锁 **当存在表级锁和行级锁的情况下,必须先申请意向锁(表级锁,但不是真的加锁),再获取行级锁**。使用意向锁(Intention Locks)可以更容易地支持多粒度封锁。 @@ -97,13 +95,13 @@ where id=#{id} and version=#{version}; - 任意 IS/IX 锁之间都是兼容的,因为它们只表示想要对表加锁,而不是真正加锁; - 这里兼容关系针对的是表级锁,而表级的 IX 锁和行级的 X 锁兼容,两个事务可以对两个数据行加 X 锁。(事务 T1 想要对数据行 R1 加 X 锁,事务 T2 想要对同一个表的数据行 R2 加 X 锁,两个事务都需要对该表加 IX 锁,但是 IX 锁是兼容的,并且 IX 锁与行级的 X 锁也是兼容的,因此两个事务都能加锁成功,对同一个表中的两个数据行做修改。) -## 5. MVCC +## MVCC **多版本并发控制(Multi-Version Concurrency Control, MVCC)可以视为行级锁的一个变种。它在很多情况下都避免了加锁操作,因此开销更低**。不仅是 Mysql,包括 Oracle、PostgreSQL 等其他数据库都实现了各自的 MVCC,实现机制没有统一标准。 MVCC 是 `InnoDB` 存储引擎实现隔离级别的一种具体方式,**用于实现提交读和可重复读这两种隔离级别**。而未提交读隔离级别总是读取最新的数据行,要求很低,无需使用 MVCC。可串行化隔离级别需要对所有读取的行都加锁,单纯使用 MVCC 无法实现。 -### 5.1. MVCC 思想 +### MVCC 思想 加锁能解决多个事务同时执行时出现的并发一致性问题。在实际场景中读操作往往多于写操作,因此又引入了读写锁来避免不必要的加锁操作,例如读和读没有互斥关系。读写锁中读和写操作仍然是互斥的。 @@ -112,14 +110,14 @@ MVCC 的思想是: - **保存数据在某个时间点的快照,写操作(DELETE、INSERT、UPDATE)更新最新的版本快照;而读操作去读旧版本快照,没有互斥关系**。这一点和 `CopyOnWrite` 类似。 - 脏读和不可重复读最根本的原因是**事务读取到其它事务未提交的修改**。在事务进行读取操作时,为了解决脏读和不可重复读问题,**MVCC 规定只能读取已经提交的快照**。当然一个事务可以读取自身未提交的快照,这不算是脏读。 -### 5.2. 版本号 +### 版本号 InnoDB 的 MVCC 实现是:在每行记录后面保存两个隐藏列,一个列保存行的创建时间,另一个列保存行的过期时间(这里的时间是指系统版本号)。每开始一个新事务,系统版本号会自动递增,事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。 - 系统版本号 `SYS_ID`:是一个递增的数字,每开始一个新的事务,系统版本号就会自动递增。 - 事务版本号 `TRX_ID` :事务开始时的系统版本号。 -### 5.3. Undo 日志 +### Undo 日志 MVCC 的多版本指的是多个版本的快照,快照存储在 Undo 日志中,该日志通过回滚指针 `ROLL_PTR` 把一个数据行的所有快照连接起来。 @@ -135,11 +133,11 @@ UPDATE t SET x="c" WHERE id=1; `INSERT`、`UPDATE`、`DELETE` 操作会创建一个日志,并将事务版本号 `TRX_ID` 写入。`DELETE` 可以看成是一个特殊的 `UPDATE`,还会额外将 DEL 字段设置为 1。 -### 5.4. ReadView +### ReadView MVCC 维护了一个一致性读视图 `consistent read view` ,主要包含了当前系统**未提交的事务列表** `TRX_IDs {TRX_ID_1, TRX_ID_2, ...}`,还有该列表的最小值 `TRX_ID_MIN` 和 `TRX_ID_MAX`。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200715135809.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200715135809.png) 这样,对于当前事务的启动瞬间来说,一个数据版本的 row trx_id,有以下几种可能: @@ -159,7 +157,7 @@ MVCC 维护了一个一致性读视图 `consistent read view` ,主要包含了 在数据行快照不可使用的情况下,需要沿着 Undo Log 的回滚指针 ROLL_PTR 找到下一个快照,再进行上面的判断。 -### 5.5. 快照读与当前读 +### 快照读与当前读 快照读 @@ -186,7 +184,7 @@ SELECT * FROM table WHERE ? lock in share mode; SELECT * FROM table WHERE ? for update; ``` -## 6. 行锁 +## 行锁 行锁的具体实现算法有三种:record lock、gap lock 以及 next-key lock。 @@ -202,10 +200,10 @@ MVCC 不能解决幻读问题,**Next-Key 锁就是为了解决幻读问题** 当两个事务同时执行,一个锁住了主键索引,在等待其他相关索引。另一个锁定了非主键索引,在等待主键索引。这样就会发生死锁。发生死锁后,`InnoDB` 一般都可以检测到,并使一个事务释放锁回退,另一个获取锁完成事务。 -## 7. 参考资料 +## 参考资料 - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) - [《Java 性能调优实战》](https://time.geekbang.org/column/intro/100028001) - [数据库系统原理](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/数据库系统原理.md) - [数据库两大神器【索引和锁】](https://juejin.im/post/5b55b842f265da0f9e589e79) -- [使用 mysql 乐观锁解决并发问题](https://www.cnblogs.com/laoyeye/p/8097684.html) +- [使用 mysql 乐观锁解决并发问题](https://www.cnblogs.com/laoyeye/p/8097684.html) \ No newline at end of file diff --git a/docs/sql/mysql/mysql-index.md "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/05.Mysql\347\264\242\345\274\225.md" similarity index 74% rename from docs/sql/mysql/mysql-index.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/05.Mysql\347\264\242\345\274\225.md" index 0400c7a4..1c1dde2e 100644 --- a/docs/sql/mysql/mysql-index.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/05.Mysql\347\264\242\345\274\225.md" @@ -1,101 +1,118 @@ +--- +title: Mysql 索引 +date: 2020-07-16 11:14:07 +categories: + - 数据库 + - 关系型数据库 + - Mysql +tags: + - 数据库 + - 关系型数据库 + - Mysql + - 索引 +permalink: /pages/fcb19c/ +--- + # Mysql 索引 > 索引是提高 MySQL 查询性能的一个重要途径,但过多的索引可能会导致过高的磁盘使用率以及过高的内存占用,从而影响应用程序的整体性能。应当尽量避免事后才想起添加索引,因为事后可能需要监控大量的 SQL 才能定位到问题所在,而且添加索引的时间肯定是远大于初始添加索引所需要的时间,可见索引的添加也是非常有技术含量的。 > > 接下来将向你展示一系列创建高性能索引的策略,以及每条策略其背后的工作原理。但在此之前,先了解与索引相关的一些算法和数据结构,将有助于更好的理解后文的内容。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200715172009.png) - - - -- [1. 索引简介](#1-索引简介) - - [1.1. 索引的优缺点](#11-索引的优缺点) - - [1.2. 何时使用索引](#12-何时使用索引) -- [2. 索引的数据结构](#2-索引的数据结构) - - [2.1. 哈希索引](#21-哈希索引) - - [2.2. B 树索引](#22-b-树索引) - - [2.3. 全文索引](#23-全文索引) - - [2.4. 空间数据索引](#24-空间数据索引) -- [3. 索引的类型](#3-索引的类型) - - [3.1. 主键索引(`PRIMARY`)](#31-主键索引primary) - - [3.2. 唯一索引(`UNIQUE`)](#32-唯一索引unique) - - [3.3. 普通索引(`INDEX`)](#33-普通索引index) - - [3.4. 全文索引(`FULLTEXT`)](#34-全文索引fulltext) - - [3.5. 联合索引](#35-联合索引) -- [4. 索引的策略](#4-索引的策略) - - [4.1. 索引基本原则](#41-索引基本原则) - - [4.2. 独立的列](#42-独立的列) - - [4.3. 覆盖索引](#43-覆盖索引) - - [4.4. 使用索引来排序](#44-使用索引来排序) - - [4.5. 前缀索引](#45-前缀索引) - - [4.6. 最左前缀匹配原则](#46-最左前缀匹配原则) - - [4.7. = 和 in 可以乱序](#47--和-in-可以乱序) -- [5. 索引最佳实践](#5-索引最佳实践) -- [6. 参考资料](#6-参考资料) - - - -## 1. 索引简介 - -**_索引优化应该是查询性能优化的最有效手段_**。 - -### 1.1. 索引的优缺点 - -B+ 树索引,按照顺序存储数据,所以 Mysql 可以用来做 ORDER BY 和 GROUP BY 操作。因为数据是有序的,所以 B+ 树也就会将相关的列值都存储在一起。最后,因为索引中存储了实际的列值,所以某些查询只使用索引就能够完成全部查询。 +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200715172009.png) + +## 索引简介 + +**索引是数据库为了提高查找效率的一种数据结构**。 + +索引对于良好的性能非常关键,在数据量小且负载较低时,不恰当的索引对于性能的影响可能还不明显;但随着数据量逐渐增大,性能则会急剧下降。因此,索引优化应该是查询性能优化的最有效手段。 + +### 索引的优缺点 + +B 树是最常见的索引,按照顺序存储数据,所以 Mysql 可以用来做 `ORDER BY` 和 `GROUP BY` 操作。因为数据是有序的,所以 B 树也就会将相关的列值都存储在一起。最后,因为索引中存储了实际的列值,所以某些查询只使用索引就能够完成全部查询。 ✔ 索引的优点: -- 索引大大减少了服务器需要扫描的数据量,从而加快检索速度。 -- 支持行级锁的数据库,如 InnoDB 会在访问行的时候加锁。使用索引可以减少访问的行数,从而减少锁的竞争,提高并发。 -- 索引可以帮助服务器避免排序和临时表。 -- 索引可以将随机 I/O 变为顺序 I/O。 +- **索引大大减少了服务器需要扫描的数据量**,从而加快检索速度。 +- **索引可以帮助服务器避免排序和临时表**。 +- **索引可以将随机 I/O 变为顺序 I/O**。 +- 支持行级锁的数据库,如 InnoDB 会在访问行的时候加锁。**使用索引可以减少访问的行数,从而减少锁的竞争,提高并发**。 - 唯一索引可以确保每一行数据的唯一性,通过使用索引,可以在查询的过程中使用优化隐藏器,提高系统的性能。 ❌ 索引的缺点: -- 创建和维护索引要耗费时间,这会随着数据量的增加而增加。 +- **创建和维护索引要耗费时间**,这会随着数据量的增加而增加。 - **索引需要占用额外的物理空间**,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立组合索引那么需要的空间就会更大。 -- 写操作(`INSERT`/`UPDATE`/`DELETE`)时很可能需要更新索引,导致数据库的写操作性能降低。 +- **写操作(`INSERT`/`UPDATE`/`DELETE`)时很可能需要更新索引,导致数据库的写操作性能降低**。 -### 1.2. 何时使用索引 +### 何时使用索引 > 索引能够轻易将查询性能提升几个数量级。 ✔ 什么情况**适用**索引: -- 表经常进行 `SELECT` 操作; -- 表的数据量比较大; -- 列名经常出现在 `WHERE` 或连接(`JOIN`)条件中 +- **频繁读操作( `SELECT` )** +- **表的数据量比较大**。 +- **列名经常出现在 `WHERE` 或连接(`JOIN`)条件中**。 ❌ 什么情况**不适用**索引: -- **频繁写操作**( `INSERT`/`UPDATE`/`DELETE` )- 需要更新索引空间; -- **非常小的表** - 对于非常小的表,大部分情况下简单的全表扫描更高效。 -- 列名不经常出现在 `WHERE` 或连接(`JOIN`)条件中 - 索引就会经常不命中,没有意义,还增加空间开销。 -- 对于特大型表,建立和使用索引的代价将随之增长。可以考虑使用分区技术或 Nosql。 +- **频繁写操作**( `INSERT`/`UPDATE`/`DELETE` ),也就意味着需要更新索引。 +- **列名不经常出现在 `WHERE` 或连接(`JOIN`)条件中**,也就意味着索引会经常无法命中,没有意义,还增加空间开销。 +- **非常小的表**,对于非常小的表,大部分情况下简单的全表扫描更高效。 +- **特大型的表**,建立和使用索引的代价将随之增长。可以考虑使用分区技术或 Nosql。 + +## 索引的数据结构 + +在 Mysql 中,索引是在存储引擎层而不是服务器层实现的。所以,并没有统一的索引标准;不同存储引擎的索引的数据结构也不相同。 + +### 数组 -## 2. 索引的数据结构 +数组是用连续的内存空间来存储数据,并且支持随机访问。 -### 2.1. 哈希索引 +有序数组可以使用二分查找法,其时间复杂度为 `O(log n)`,无论是等值查询还是范围查询,都非常高效。 -> Hash 索引只有精确匹配索引所有列的查询才有效。 +但数组有两个重要限制: -哈希表是一种以键 - 值(key-value)存储数据的结构,我们只要输入待查找的值即 key,就可以找到其对应的值即 Value。哈希的思路很简单,把值放在数组里,用一个哈希函数把 key 换算成一个确定的位置,然后把 value 放在数组的这个位置。 +- 数组的空间大小固定,如果要扩容只能采用复制数组的方式。 +- 插入、删除时间复杂度为 `O(n)`。 -对于每一行数据,对所有的索引列计算一个 `hashcode`。哈希索引将所有的 `hashcode` 存储在索引中,同时在 Hash 表中保存指向每个数据行的指针。 +这意味着,如果使用数组作为索引,如果要保证数组有序,其更新操作代价高昂。 -哈希索引的**优点**: +### 哈希索引 + +哈希表是一种以键 - 值(key-value)对形式存储数据的结构,我们只要输入待查找的值即 key,就可以找到其对应的值即 Value。 + +**哈希表** 使用 **哈希函数** 组织数据,以支持快速插入和搜索的数据结构。哈希表的本质是一个数组,其思路是:使用 Hash 函数将 Key 转换为数组下标,利用数组的随机访问特性,使得我们能在 `O(1)` 的时间代价内完成检索。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320201844.png) + +有两种不同类型的哈希表:**哈希集合** 和 **哈希映射**。 + +- **哈希集合** 是集合数据结构的实现之一,用于存储非重复值。 +- **哈希映射** 是映射 数据结构的实现之一,用于存储键值对。 + +哈希索引基于哈希表实现,**只适用于等值查询**。对于每一行数据,哈希索引都会将所有的索引列计算一个哈希码(`hashcode`),哈希码是一个较小的值。哈希索引将所有的哈希码存储在索引中,同时在哈希表中保存指向每个数据行的指针。 + +在 Mysql 中,只有 Memory 存储引擎显示支持哈希索引。 + +✔ 哈希索引的**优点**: - 因为索引数据结构紧凑,所以**查询速度非常快**。 -哈希索引的**缺点**: +❌ 哈希索引的**缺点**: + +- 哈希索引值包含哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行。不过,访问内存中的行的速度很快,所以大部分情况下这一点对性能影响不大。 +- **哈希索引数据不是按照索引值顺序存储的**,所以**无法用于排序**。 +- 哈希索引**不支持部分索引匹配查找**,因为哈希索引时使用索引列的全部内容来进行哈希计算的。如,在数据列 (A,B) 上建立哈希索引,如果查询只有数据列 A,无法使用该索引。 +- 哈希索引**只支持等值比较查询**,包括 `=`、`IN()`、`<=>`;不支持任何范围查询,如 `WHERE price > 100`。 +- 哈希索引有**可能出现哈希冲突** + - 出现哈希冲突时,必须遍历链表中所有的行指针,逐行比较,直到找到符合条件的行。 + - 如果哈希冲突多的话,维护索引的代价会很高。 -- 哈希索引数据不是按照索引值顺序存储的,所以**无法用于排序**。 -- 哈希索引**不支持部分索引匹配查找**。如,在数据列 (A,B) 上建立哈希索引,如果查询只有数据列 A,无法使用该索引。 -- 哈希索引**只支持等值比较查询**,不支持任何范围查询,如 `WHERE price > 100`。 -- 哈希索引有**可能出现哈希冲突**,出现哈希冲突时,必须遍历链表中所有的行指针,逐行比较,直到找到符合条件的行。 +> 因为种种限制,所以哈希索引只适用于特定的场合。而一旦使用哈希索引,则它带来的性能提升会非常显著。 -### 2.2. B 树索引 +### B 树索引 通常我们所说的索引是指`B-Tree`索引,它是目前关系型数据库中查找数据最为常用和有效的索引,大多数存储引擎都支持这种索引。使用`B-Tree`这个术语,是因为 MySQL 在`CREATE TABLE`或其它语句中使用了这个关键字,但实际上不同的存储引擎可能使用不同的数据结构,比如 InnoDB 就是使用的`B+Tree`。 @@ -120,7 +137,7 @@ B+ 树索引适用于**全键值查找**、**键值范围查找**和**键前缀 - 第一,所有的关键字(可以理解为数据)都存储在叶子节点,非叶子节点并不存储真正的数据,所有记录节点都是按键值大小顺序存放在同一层叶子节点上。 - 其次,所有的叶子节点由指针连接。如下图为简化了的`B+Tree`。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200304235424.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200304235424.jpg) 根据叶子节点的内容,索引类型分为主键索引和非主键索引。 @@ -147,7 +164,7 @@ B+ 树索引适用于**全键值查找**、**键值范围查找**和**键前缀 这时候我们就要优先考虑上一段提到的“尽量使用主键查询”原则,直接将这个索引设置为主键,可以避免每次查询需要搜索两棵树。 -### 2.3. 全文索引 +### 全文索引 MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较是否相等。查找条件使用 MATCH AGAINST,而不是普通的 WHERE。 @@ -155,17 +172,17 @@ MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而 InnoDB 存储引擎在 MySQL 5.6.4 版本中也开始支持全文索引。 -### 2.4. 空间数据索引 +### 空间数据索引 MyISAM 存储引擎支持空间数据索引(R-Tree),可以用于地理数据存储。空间数据索引会从所有维度来索引数据,可以有效地使用任意维度来进行组合查询。 必须使用 GIS 相关的函数来维护数据。 -## 3. 索引的类型 +## 索引的类型 主流的关系型数据库一般都支持以下索引类型: -### 3.1. 主键索引(`PRIMARY`) +### 主键索引(`PRIMARY`) 主键索引:一种特殊的唯一索引,不允许有空值。一个表只能有一个主键(在 InnoDB 中本质上即聚簇索引),一般是在建表的时候同时创建主键索引。 @@ -177,7 +194,7 @@ CREATE TABLE `table` ( ) ``` -### 3.2. 唯一索引(`UNIQUE`) +### 唯一索引(`UNIQUE`) 唯一索引:**索引列的值必须唯一,但允许有空值**。如果是组合索引,则列值的组合必须唯一。 @@ -188,7 +205,7 @@ CREATE TABLE `table` ( ) ``` -### 3.3. 普通索引(`INDEX`) +### 普通索引(`INDEX`) 普通索引:最基本的索引,没有任何限制。 @@ -199,7 +216,7 @@ CREATE TABLE `table` ( ) ``` -### 3.4. 全文索引(`FULLTEXT`) +### 全文索引(`FULLTEXT`) 全文索引:主要用来查找文本中的关键字,而不是直接与索引中的值相比较。 @@ -213,7 +230,7 @@ CREATE TABLE `table` ( ) ``` -### 3.5. 联合索引 +### 联合索引 组合索引:多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀集合。 @@ -224,7 +241,7 @@ CREATE TABLE `table` ( ) ``` -## 4. 索引的策略 +## 索引的策略 假设有以下表: @@ -240,7 +257,7 @@ CREATE TABLE `t` ( ) ENGINE=InnoDB; ``` -### 4.1. 索引基本原则 +### 索引基本原则 - **索引不是越多越好,不要为所有列都创建索引**。要考虑到索引的维护代价、空间占用和查询时回表的代价。索引一定是按需创建的,并且要尽可能确保足够轻量。一旦创建了多字段的联合索引,我们要考虑尽可能利用索引本身完成数据查询,减少回表的成本。 - 要**尽量避免冗余和重复索引**。 @@ -248,7 +265,7 @@ CREATE TABLE `t` ( - **尽量的扩展索引,不要新建索引**。 - **频繁作为 `WHERE` 过滤条件的列应该考虑添加索引**。 -### 4.2. 独立的列 +### 独立的列 **“独立的列” 是指索引列不能是表达式的一部分,也不能是函数的参数**。 @@ -263,7 +280,7 @@ SELECT actor_id FROM actor WHERE actor_id + 1 = 5; SELECT ... WHERE TO_DAYS(current_date) - TO_DAYS(date_col) <= 10; ``` -### 4.3. 覆盖索引 +### 覆盖索引 **覆盖索引是指,索引上的信息足够满足查询请求,不需要回表查询数据。** @@ -296,7 +313,7 @@ select * from T where k between 3 and 5 **由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段。** -### 4.4. 使用索引来排序 +### 使用索引来排序 Mysql 有两种方式可以生成排序结果:通过排序操作;或者按索引顺序扫描。 @@ -308,7 +325,7 @@ Mysql 有两种方式可以生成排序结果:通过排序操作;或者按 2. 从索引 (city,name,age) 取下一个记录,同样取出这三个字段的值,作为结果集的一部分直接返回; 3. 重复执行步骤 2,直到查到第 1000 条记录,或者是不满足 city='杭州’条件时循环结束。 -### 4.5. 前缀索引 +### 前缀索引 有时候需要索引很长的字符列,这会让索引变得大且慢。 @@ -341,7 +358,7 @@ from SUser; 此外,**`order by` 无法使用前缀索引,无法把前缀索引用作覆盖索引**。 -### 4.6. 最左前缀匹配原则 +### 最左前缀匹配原则 不只是索引的全部定义,只要满足最左前缀,就可以利用索引来加速检索。这个最左前缀可以是联合索引的最左 N 个字段,也可以是字符串索引的最左 M 个字符。 @@ -374,21 +391,21 @@ customer_id_selectivity: 0.0373 COUNT(*): 16049 ``` -### 4.7. = 和 in 可以乱序 +### = 和 in 可以乱序 **不需要考虑 `=`、`IN` 等的顺序**,Mysql 会自动优化这些条件的顺序,以匹配尽可能多的索引列。 【示例】如有索引 (a, b, c, d),查询条件 `c > 3 and b = 2 and a = 1 and d < 4` 与 `a = 1 and c > 3 and b = 2 and d < 4` 等顺序都是可以的,MySQL 会自动优化为 a = 1 and b = 2 and c > 3 and d < 4,依次命中 a、b、c、d。 -## 5. 索引最佳实践 +## 索引最佳实践 创建了索引,并非一定有效。比如不满足前缀索引、最左前缀匹配原则、查询条件涉及函数计算等情况都无法使用索引。此外,即使 SQL 本身符合索引的使用条件,MySQL 也会通过评估各种查询方式的代价,来决定是否走索引,以及走哪个索引。 因此,在尝试通过索引进行 SQL 性能优化的时候,务必通过执行计划(`EXPLAIN`)或实际的效果来确认索引是否能有效改善性能问题,否则增加了索引不但没解决性能问题,还增加了数据库增删改的负担。如果对 EXPLAIN 给出的执行计划有疑问的话,你还可以利用 `optimizer_trace` 查看详细的执行计划做进一步分析。 -## 6. 参考资料 +## 参考资料 - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) - [数据库两大神器【索引和锁】](https://juejin.im/post/5b55b842f265da0f9e589e79) - [MySQL 索引背后的数据结构及算法原理](http://blog.codinglabs.org/articles/theory-of-mysql-index.html) -- [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) +- [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) \ No newline at end of file diff --git a/docs/sql/mysql/mysql-optimization.md "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/06.Mysql\346\200\247\350\203\275\344\274\230\345\214\226.md" similarity index 92% rename from docs/sql/mysql/mysql-optimization.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/06.Mysql\346\200\247\350\203\275\344\274\230\345\214\226.md" index 057a309d..c1c29c68 100644 --- a/docs/sql/mysql/mysql-optimization.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/06.Mysql\346\200\247\350\203\275\344\274\230\345\214\226.md" @@ -1,31 +1,25 @@ +--- +title: Mysql 性能优化 +date: 2020-06-03 20:16:48 +categories: + - 数据库 + - 关系型数据库 + - Mysql +tags: + - 数据库 + - 关系型数据库 + - Mysql + - 性能 +permalink: /pages/396816/ +--- + # Mysql 性能优化 - - -- [1. 数据结构优化](#1-数据结构优化) - - [1.1. 数据类型优化](#11-数据类型优化) - - [1.2. 表设计](#12-表设计) - - [1.3. 范式和反范式](#13-范式和反范式) - - [1.4. 索引优化](#14-索引优化) -- [2. SQL 优化](#2-sql-优化) - - [2.1. 优化 `COUNT()` 查询](#21-优化-count-查询) - - [2.2. 优化关联查询](#22-优化关联查询) - - [2.3. 优化 `GROUP BY` 和 `DISTINCT`](#23-优化-group-by-和-distinct) - - [2.4. 优化 `LIMIT`](#24-优化-limit) - - [2.5. 优化 UNION](#25-优化-union) - - [2.6. 优化查询方式](#26-优化查询方式) -- [3. 执行计划(`EXPLAIN`)](#3-执行计划explain) -- [4. optimizer trace](#4-optimizer-trace) -- [5. 数据模型和业务](#5-数据模型和业务) -- [6. 参考资料](#6-参考资料) - - - -## 1. 数据结构优化 +## 数据结构优化 良好的逻辑设计和物理设计是高性能的基石。 -### 1.1. 数据类型优化 +### 数据类型优化 #### 数据类型优化基本原则 @@ -44,16 +38,16 @@ - 应该尽量避免用字符串类型作为标识列,因为它们很消耗空间,并且通常比数字类型慢。对于 `MD5`、`SHA`、`UUID` 这类随机字符串,由于比较随机,所以可能分布在很大的空间内,导致 `INSERT` 以及一些 `SELECT` 语句变得很慢。 - 如果存储 UUID ,应该移除 `-` 符号;更好的做法是,用 `UNHEX()` 函数转换 UUID 值为 16 字节的数字,并存储在一个 `BINARY(16)` 的列中,检索时,可以通过 `HEX()` 函数来格式化为 16 进制格式。 -### 1.2. 表设计 +### 表设计 应该避免的设计问题: - **太多的列** - 设计者为了图方便,将大量冗余列加入表中,实际查询中,表中很多列是用不到的。这种宽表模式设计,会造成不小的性能代价,尤其是 `ALTER TABLE` 非常耗时。 -- **太多的关联** - 所谓的实体 - 属性 - 值(EVA)设计模式是一个常见的糟糕设计模式。Mysql 限制了每个关联操作最多只能有 61 张表,但 EVA 模式需要许多自关联。 +- **太多的关联** - 所谓的实体 - 属性 - 值(EAV)设计模式是一个常见的糟糕设计模式。Mysql 限制了每个关联操作最多只能有 61 张表,但 EAV 模式需要许多自关联。 - **枚举** - 尽量不要用枚举,因为添加和删除字符串(枚举选项)必须使用 `ALTER TABLE`。 - 尽量避免 `NULL` -### 1.3. 范式和反范式 +### 范式和反范式 **范式化目标是尽量减少冗余,而反范式化则相反**。 @@ -69,7 +63,7 @@ 在真实世界中,很少会极端地使用范式化或反范式化。实际上,应该权衡范式和反范式的利弊,混合使用。 -### 1.4. 索引优化 +### 索引优化 > 索引优化应该是查询性能优化的最有效手段。 > @@ -98,7 +92,7 @@ - **覆盖索引** - **自增字段作主键** -## 2. SQL 优化 +## SQL 优化 使用 `EXPLAIN` 命令查看当前 SQL 是否使用了索引,优化后,再通过执行计划(`EXPLAIN`)来查看优化效果。 @@ -112,7 +106,7 @@ SQL 优化基本思路: - **使用索引来覆盖查询** -### 2.1. 优化 `COUNT()` 查询 +### 优化 `COUNT()` 查询 `COUNT()` 有两种作用: @@ -136,7 +130,7 @@ FROM world.city WHERE id <= 5; 有时候某些业务场景并不需要完全精确的统计值,可以用近似值来代替,`EXPLAIN` 出来的行数就是一个不错的近似值,而且执行 `EXPLAIN` 并不需要真正地去执行查询,所以成本非常低。通常来说,执行 `COUNT()` 都需要扫描大量的行才能获取到精确的数据,因此很难优化,MySQL 层面还能做得也就只有覆盖索引了。如果不还能解决问题,只有从架构层面解决了,比如添加汇总表,或者使用 Redis 这样的外部缓存系统。 -### 2.2. 优化关联查询 +### 优化关联查询 在大数据场景下,表与表之间通过一个冗余字段来关联,要比直接使用 `JOIN` 有更好的性能。 @@ -173,11 +167,11 @@ while(outer_row) { 可以看到,最外层的查询是根据`A.xx`列来查询的,`A.c`上如果有索引的话,整个关联查询也不会使用。再看内层的查询,很明显`B.c`上如果有索引的话,能够加速查询,因此只需要在关联顺序中的第二张表的相应列上创建索引即可。 -### 2.3. 优化 `GROUP BY` 和 `DISTINCT` +### 优化 `GROUP BY` 和 `DISTINCT` Mysql 优化器会在内部处理的时候相互转化这两类查询。它们都**可以使用索引来优化,这也是最有效的优化方法**。 -### 2.4. 优化 `LIMIT` +### 优化 `LIMIT` 当需要分页操作时,通常会使用 `LIMIT` 加上偏移量的办法实现,同时加上合适的 `ORDER BY` 字句。**如果有对应的索引,通常效率会不错,否则,MySQL 需要做大量的文件排序操作**。 @@ -210,13 +204,13 @@ SELECT id FROM t WHERE id > 10000 LIMIT 10; 其他优化的办法还包括使用预先计算的汇总表,或者关联到一个冗余表,冗余表中只包含主键列和需要做排序的列。 -### 2.5. 优化 UNION +### 优化 UNION MySQL 总是通过创建并填充临时表的方式来执行 `UNION` 查询。因此很多优化策略在`UNION`查询中都没有办法很好的时候。经常需要手动将`WHERE`、`LIMIT`、`ORDER BY`等字句“下推”到各个子查询中,以便优化器可以充分利用这些条件先优化。 除非确实需要服务器去重,否则就一定要使用`UNION ALL`,如果没有`ALL`关键字,MySQL 会给临时表加上`DISTINCT`选项,这会导致整个临时表的数据做唯一性检查,这样做的代价非常高。当然即使使用 ALL 关键字,MySQL 总是将结果放入临时表,然后再读出,再返回给客户端。虽然很多时候没有这个必要,比如有时候可以直接把每个子查询的结果返回给客户端。 -### 2.6. 优化查询方式 +### 优化查询方式 #### 切分大查询 @@ -257,7 +251,7 @@ SELECT * FROM tag_post WHERE tag_id=1234; SELECT * FROM post WHERE post.id IN (123,456,567,9098,8904); ``` -## 3. 执行计划(`EXPLAIN`) +## 执行计划(`EXPLAIN`) 如何判断当前 SQL 是否使用了索引?如何检验修改后的 SQL 确实有优化效果? @@ -310,7 +304,7 @@ possible_keys: PRIMARY > 更多内容请参考:[MySQL 性能优化神器 Explain 使用分析](https://segmentfault.com/a/1190000008131735) -## 4. optimizer trace +## optimizer trace 在 MySQL 5.6 及之后的版本中,我们可以使用 optimizer trace 功能查看优化器生成执行计划的整个过程。有了这个功能,我们不仅可以了解优化器的选择过程,更可以了解每一个执行环节的成本,然后依靠这些信息进一步优化查询。 @@ -323,14 +317,14 @@ SELECT * FROM information_schema.OPTIMIZER_TRACE; SET optimizer_trace="enabled=off"; ``` -## 5. 数据模型和业务 +## 数据模型和业务 - 表字段比较复杂、易变动、结构难以统一的情况下,可以考虑使用 Nosql 来代替关系数据库表存储,如 ElasticSearch、MongoDB。 - 在高并发情况下的查询操作,可以使用缓存(如 Redis)代替数据库操作,提高并发性能。 - 数据量增长较快的表,需要考虑水平分表或分库,避免单表操作的性能瓶颈。 - 除此之外,我们应该通过一些优化,尽量避免比较复杂的 JOIN 查询操作,例如冗余一些字段,减少 JOIN 查询;创建一些中间表,减少 JOIN 查询。 -## 6. 参考资料 +## 参考资料 - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) - [《Java 性能调优实战》](https://time.geekbang.org/column/intro/100028001) diff --git a/docs/sql/mysql/mysql-ops.md "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/20.Mysql\350\277\220\347\273\264.md" similarity index 92% rename from docs/sql/mysql/mysql-ops.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/20.Mysql\350\277\220\347\273\264.md" index eb0360e3..6550078b 100644 --- a/docs/sql/mysql/mysql-ops.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/20.Mysql\350\277\220\347\273\264.md" @@ -1,44 +1,25 @@ +--- +title: Mysql 运维 +date: 2019-11-26 21:37:17 +categories: + - 数据库 + - 关系型数据库 + - Mysql +tags: + - 数据库 + - 关系型数据库 + - Mysql + - 运维 +permalink: /pages/e33b92/ +--- + # Mysql 运维 > 如果你的公司有 DBA,那么我恭喜你,你可以无视 Mysql 运维。如果你的公司没有 DBA,那你就好好学两手 Mysql 基本运维操作,行走江湖,防身必备。 - - -- [1. 安装部署](#1-安装部署) - - [1.1. Windows 安装](#11-windows-安装) - - [1.2. CentOS 安装](#12-centos-安装) - - [1.3. 初始化数据库密码](#13-初始化数据库密码) - - [1.4. 配置远程访问](#14-配置远程访问) - - [1.5. 跳过登录认证](#15-跳过登录认证) -- [2. 基本运维](#2-基本运维) - - [2.1. 客户端连接](#21-客户端连接) - - [2.2. 查看连接](#22-查看连接) - - [2.3. 创建用户](#23-创建用户) - - [2.4. 查看用户](#24-查看用户) - - [2.5. 授权](#25-授权) - - [2.6. 撤销授权](#26-撤销授权) - - [2.7. 查看授权](#27-查看授权) - - [2.8. 更改用户密码](#28-更改用户密码) - - [2.9. 备份与恢复](#29-备份与恢复) - - [2.10. 卸载](#210-卸载) - - [2.11. 主从节点部署](#211-主从节点部署) -- [3. 服务器配置](#3-服务器配置) - - [3.1. 配置文件路径](#31-配置文件路径) - - [3.2. 配置项语法](#32-配置项语法) - - [3.3. 常用配置项说明](#33-常用配置项说明) -- [4. 常见问题](#4-常见问题) - - [4.1. Too many connections](#41-too-many-connections) - - [4.2. 时区(time_zone)偏差](#42-时区time_zone偏差) - - [4.3. 数据表损坏如何修复](#43-数据表损坏如何修复) - - [4.4. 数据结构](#44-数据结构) -- [5. 脚本](#5-脚本) -- [6. 参考资料](#6-参考资料) - - - -## 1. 安装部署 - -### 1.1. Windows 安装 +## 安装部署 + +### Windows 安装 (1)下载 Mysql 5.7 免安装版 @@ -85,7 +66,7 @@ mysqld -install 在控制台执行 `net start mysql` 启动服务。 -### 1.2. CentOS 安装 +### CentOS 安装 > 本文仅介绍 rpm 安装方式 @@ -175,7 +156,7 @@ systemctl restart mysqld systemctl stop mysqld ``` -### 1.3. 初始化数据库密码 +### 初始化数据库密码 查看一下初始密码 @@ -198,7 +179,7 @@ ALTER user 'root'@'localhost' IDENTIFIED BY '你的密码'; 注:密码强度默认为中等,大小写字母、数字、特殊符号,只有修改成功后才能修改配置再设置更简单的密码 -### 1.4. 配置远程访问 +### 配置远程访问 ```sql CREATE USER 'root'@'%' IDENTIFIED BY '你的密码'; @@ -207,7 +188,7 @@ ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '你的密码'; FLUSH PRIVILEGES; ``` -### 1.5. 跳过登录认证 +### 跳过登录认证 ```shell vim /etc/my.cnf @@ -219,9 +200,9 @@ vim /etc/my.cnf 执行 `systemctl restart mysqld`,重启 mysql -## 2. 基本运维 +## 基本运维 -### 2.1. 客户端连接 +### 客户端连接 语法:`mysql -h<主机> -P<端口> -u<用户名> -p<密码>` @@ -247,13 +228,13 @@ Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> ``` -### 2.2. 查看连接 +### 查看连接 连接完成后,如果你没有后续的动作,这个连接就处于空闲状态,你可以在 `show processlist` 命令中看到它。客户端如果太长时间没动静,连接器就会自动将它断开。这个时间是由参数 `wait_timeout` 控制的,默认值是 8 小时。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200714115031.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200714115031.png) -### 2.3. 创建用户 +### 创建用户 ```sql CREATE USER 'username'@'host' IDENTIFIED BY 'password'; @@ -279,7 +260,7 @@ CREATE USER 'pig'@'%'; > > 所以,需要加上 `IDENTIFIED WITH mysql_native_password`,例如:`CREATE USER 'slave'@'%' IDENTIFIED WITH mysql_native_password BY '123456';` -### 2.4. 查看用户 +### 查看用户 ```sql -- 查看所有用户 @@ -287,7 +268,7 @@ SELECT DISTINCT CONCAT('User: ''', user, '''@''', host, ''';') AS query FROM mysql.user; ``` -### 2.5. 授权 +### 授权 命令: @@ -320,7 +301,7 @@ GRANT privileges ON databasename.tablename TO 'username'@'host' WITH GRANT OPTIO GRANT ALL ON *.* TO 'root'@'%' IDENTIFIED BY '密码' WITH GRANT OPTION; ``` -### 2.6. 撤销授权 +### 撤销授权 命令: @@ -344,14 +325,14 @@ REVOKE SELECT ON *.* FROM 'pig'@'%'; 具体信息可以用命令`SHOW GRANTS FOR 'pig'@'%';` 查看。 -### 2.7. 查看授权 +### 查看授权 ```SQL -- 查看用户权限 SHOW GRANTS FOR 'root'@'%'; ``` -### 2.8. 更改用户密码 +### 更改用户密码 ```sql SET PASSWORD FOR 'username'@'host' = PASSWORD('newpassword'); @@ -369,7 +350,7 @@ SET PASSWORD = PASSWORD("newpassword"); SET PASSWORD FOR 'pig'@'%' = PASSWORD("123456"); ``` -### 2.9. 备份与恢复 +### 备份与恢复 Mysql 备份数据使用 mysqldump 命令。 @@ -420,7 +401,7 @@ mysql -h -P -u -p < backup.sql mysql -u -p --all-databases < backup.sql ``` -### 2.10. 卸载 +### 卸载 (1)查看已安装的 mysql @@ -440,7 +421,7 @@ mysql-community-libs-8.0.12-1.el7.x86_64 yum remove mysql-community-server.x86_64 ``` -### 2.11. 主从节点部署 +### 主从节点部署 假设需要配置一个主从 Mysql 服务器环境 @@ -646,13 +627,57 @@ mysql> show global variables like "%read_only%"; > 注:设置 slave 服务器为只读,并不影响主从同步。 -## 3. 服务器配置 +### 慢查询 + +查看慢查询是否开启 + +```sql +show variables like '%slow_query_log'; +``` + +可以通过 `set global slow_query_log` 命令设置慢查询是否开启:ON 表示开启;OFF 表示关闭。 + +```sql +set global slow_query_log='ON'; +``` + +查看慢查询时间阈值 + +```sql +show variables like '%long_query_time%'; +``` + +设置慢查询阈值 + +```sql +set global long_query_time = 3; +``` + +### 隔离级别 + +查看隔离级别: + +```sql +mysql> show variables like 'transaction_isolation'; + ++-----------------------+----------------+ + +| Variable_name | Value | + ++-----------------------+----------------+ + +| transaction_isolation | READ-COMMITTED | + ++-----------------------+----------------+ +``` + +## 服务器配置 > **_大部分情况下,默认的基本配置已经足够应付大多数场景,不要轻易修改 Mysql 服务器配置,除非你明确知道修改项是有益的。_** > > 尽量不要使用 Mysql 的缓存功能,因为其要求每次请求参数完全相同,才能命中缓存。这种方式实际上并不高效,还会增加额外开销,实际业务场景中一般使用 Redis 等 key-value 存储来解决缓存问题,性能远高于 Mysql 的查询缓存。 -### 3.1. 配置文件路径 +### 配置文件路径 配置 Mysql 首先要确定配置文件在哪儿。 @@ -668,7 +693,7 @@ Default options are read from the following files in the given order: /etc/my.cnf /etc/mysql/my.cnf /usr/etc/my.cnf ~/.my.cnf ``` -### 3.2. 配置项语法 +### 配置项语法 **Mysql 配置项设置都使用小写,单词之间用下划线或横线隔开(二者是等价的)。** @@ -680,9 +705,9 @@ Default options are read from the following files in the given order: /usr/sbin/mysqld --auto_increment_offset=5 ``` -### 3.3. 常用配置项说明 +### 常用配置项说明 -> 这里介绍比较常用的基本配置,更多配置项说明可以参考:[Mysql 服务器配置说明](mysql-config.md) +> 这里介绍比较常用的基本配置,更多配置项说明可以参考:[Mysql 服务器配置说明](21.Mysql配置.md) 先给出一份常用配置模板,内容如下: @@ -779,9 +804,9 @@ port = 3306 - 注意:仍然可能出现报错信息 Can't create a new thread;此时观察系统 `cat /proc/mysql` 进程号/limits,观察进程 ulimit 限制情况 - 过小的话,考虑修改系统配置表,`/etc/security/limits.conf` 和 `/etc/security/limits.d/90-nproc.conf` -## 4. 常见问题 +## 常见问题 -### 4.1. Too many connections +### Too many connections **现象** @@ -852,7 +877,7 @@ mysql soft nofile 65535 如果是使用 rpm 方式安装 mysql,检查 **mysqld.service** 文件中的 `LimitNOFILE` 是否配置的太小。 -### 4.2. 时区(time_zone)偏差 +### 时区(time_zone)偏差 **现象** @@ -893,7 +918,7 @@ Query OK, 0 rows affected (0.00 sec) 修改 `my.cnf` 文件,在 `[mysqld]` 节下增加 `default-time-zone='+08:00'` ,然后重启。 -### 4.3. 数据表损坏如何修复 +### 数据表损坏如何修复 使用 myisamchk 来修复,具体步骤: @@ -903,7 +928,7 @@ Query OK, 0 rows affected (0.00 sec) 使用 repair table 或者 OPTIMIZE table 命令来修复,REPAIR TABLE table_name 修复表 OPTIMIZE TABLE table_name 优化表 REPAIR TABLE 用于修复被破坏的表。 OPTIMIZE TABLE 用于回收闲置的数据库空间,当表上的数据行被删除时,所占据的磁盘空间并没有立即被回收,使用了 OPTIMIZE TABLE 命令后这些空间将被回收,并且对磁盘上的数据行进行重排(注意:是磁盘上,而非数据库) -### 4.4. 数据结构 +### 数据结构 > 问题现象:ERROR 1071: Specified key was too long; max key length is 767 bytes @@ -911,14 +936,14 @@ Query OK, 0 rows affected (0.00 sec) 解决方法:优化索引结构,索引字段不宜过长。 -## 5. 脚本 +## 脚本 这里推荐我写的几个一键运维脚本,非常方便,欢迎使用: - [Mysql 安装脚本](https://github.com/dunwu/linux-tutorial/tree/master/codes/linux/soft/mysql-install.sh) - [Mysql 备份脚本](https://github.com/dunwu/linux-tutorial/tree/master/codes/linux/soft/mysql-backup.sh) -## 6. 参考资料 +## 参考资料 - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) - https://www.cnblogs.com/xiaopotian/p/8196464.html @@ -928,4 +953,4 @@ Query OK, 0 rows affected (0.00 sec) - https://www.cnblogs.com/xyabk/p/8967990.html - [MySQL 8.0 主从(Master-Slave)配置](https://blog.csdn.net/zyhlwzy/article/details/80569422) - [Mysql 主从同步实战](https://juejin.im/post/58eb5d162f301e00624f014a) -- [MySQL 备份和恢复机制](https://juejin.im/entry/5a0aa2026fb9a045132a369f) +- [MySQL 备份和恢复机制](https://juejin.im/entry/5a0aa2026fb9a045132a369f) \ No newline at end of file diff --git a/docs/sql/mysql/mysql-config.md "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/21.Mysql\351\205\215\347\275\256.md" similarity index 96% rename from docs/sql/mysql/mysql-config.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/21.Mysql\351\205\215\347\275\256.md" index 25c4f002..d558d801 100644 --- a/docs/sql/mysql/mysql-config.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/21.Mysql\351\205\215\347\275\256.md" @@ -1,16 +1,23 @@ -# Mysql 服务器配置 +--- +title: Mysql 配置 +date: 2020-02-29 22:32:57 +categories: + - 数据库 + - 关系型数据库 + - Mysql +tags: + - 数据库 + - 关系型数据库 + - Mysql + - 配置 +permalink: /pages/5da42d/ +--- + +# Mysql 配置 > 版本:![mysql](https://img.shields.io/badge/mysql-8.0-blue) - - -- [1. 基本配置](#1-基本配置) -- [2. 配置项说明](#2-配置项说明) -- [3. 参考资料](#3-参考资料) - - - -## 1. 基本配置 +## 基本配置 ```ini [mysqld] @@ -55,7 +62,7 @@ socket = /var/lib/mysql/mysql.sock port = 3306 ``` -## 2. 配置项说明 +## 配置项说明 ```ini [client] @@ -253,7 +260,8 @@ max_binlog_size = 1000M # 关于 binlog 日志格式问题,请查阅网络资料 binlog_format = row -# 默认值 N=1,使 binlog 在每 N 次 binlog 写入后与硬盘同步,ps:1 最慢 +# 表示每 N 次写入 binlog 后,持久化到磁盘,默认值 N=1 +# 建议设置成 1,这样可以保证 MySQL 异常重启之后 binlog 不丢失。 # sync_binlog = 1 # MyISAM 引擎配置 @@ -412,8 +420,9 @@ innodb_flush_log_at_timeout = 1 # 说明:参数可设为 0,1,2; # 参数 0:表示每秒将 log buffer 内容刷新到系统 buffer 中,再调用系统 flush 操作写入磁盘文件。 -# 参数 1:表示每次事物提交,将 log buffer 内容刷新到系统 buffer 中,再调用系统 flush 操作写入磁盘文件。 -# 参数 2:表示每次事物提交,将 log buffer 内容刷新到系统 buffer 中,隔 1 秒后再调用系统 flush 操作写入磁盘文件。 +# 参数 1:表示每次事务提交,redo log 都直接持久化到磁盘。 +# 参数 2:表示每次事务提交,隔 1 秒后再将 redo log 持久化到磁盘。 +# 建议设置成 1,这样可以保证 MySQL 异常重启之后数据不丢失。 innodb_flush_log_at_trx_commit = 1 # 说明:限制 Innodb 能打开的表的数据,如果库里的表特别多的情况,请增加这个。 @@ -477,7 +486,7 @@ auto-rehash socket = /var/lib/mysql/mysql.sock ``` -## 3. 参考资料 +## 参考资料 - [《高性能 MySQL》](https://item.jd.com/11220393.html) -- [Mysql 配置文件/etc/my.cnf 解析](https://www.jianshu.com/p/5f39c486561b) +- [Mysql 配置文件/etc/my.cnf 解析](https://www.jianshu.com/p/5f39c486561b) \ No newline at end of file diff --git a/docs/sql/mysql/mysql-faq.md "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/99.Mysql\345\270\270\350\247\201\351\227\256\351\242\230.md" similarity index 86% rename from docs/sql/mysql/mysql-faq.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/99.Mysql\345\270\270\350\247\201\351\227\256\351\242\230.md" index 98909e0b..02facdb9 100644 --- a/docs/sql/mysql/mysql-faq.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/99.Mysql\345\270\270\350\247\201\351\227\256\351\242\230.md" @@ -1,15 +1,23 @@ -# Mysql FAQ +--- +title: Mysql 常见问题 +date: 2020-09-12 10:43:53 +categories: + - 数据库 + - 关系型数据库 + - Mysql +tags: + - 数据库 + - 关系型数据库 + - Mysql + - FAQ +permalink: /pages/7b0caf/ +--- + +# Mysql 常见问题 > **📦 本文以及示例源码已归档在 [db-tutorial](https://github.com/dunwu/db-tutorial/)** - - -- [1. 为什么表数据删掉一半,表文件大小不变](#1-为什么表数据删掉一半表文件大小不变) -- [2. 参考资料](#2-参考资料) - - - -## 1. 为什么表数据删掉一半,表文件大小不变 +## 为什么表数据删掉一半,表文件大小不变 【问题】数据库占用空间太大,我把一个最大的表删掉了一半的数据,怎么表文件的大小还是没变? @@ -35,7 +43,7 @@ 要达到收缩空洞的目的,可以使用重建表的方式。 -## 2. 参考资料 +## 参考资料 - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) -- [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) +- [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/README.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/README.md" new file mode 100644 index 00000000..076d5964 --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/README.md" @@ -0,0 +1,70 @@ +--- +title: Mysql 教程 +date: 2020-02-10 14:27:39 +categories: + - 数据库 + - 关系型数据库 + - Mysql +tags: + - 数据库 + - 关系型数据库 + - Mysql +permalink: /pages/a5b63b/ +hidden: true +--- + +# Mysql 教程 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716103611.png) + +## 📖 内容 + +### [Mysql 应用指南](01.Mysql应用指南.md) + +### [Mysql 工作流](02.MySQL工作流.md) + +### [Mysql 事务](03.Mysql事务.md) + +> 关键词:`ACID`、`AUTOCOMMIT`、`事务隔离级别`、`死锁`、`分布式事务` + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220721072721.png) + +### [Mysql 锁](04.Mysql锁.md) + +> 关键词:`乐观锁`、`表级锁`、`行级锁`、`意向锁`、`MVCC`、`Next-key 锁` + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716064947.png) + +### [Mysql 索引](05.Mysql索引.md) + +> 关键词:`Hash`、`B 树`、`聚簇索引`、`回表` + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200715172009.png) + +### [Mysql 性能优化](06.Mysql性能优化.md) + +### [Mysql 运维](20.Mysql运维.md) 🔨 + +### [Mysql 配置](21.Mysql配置.md) 🔨 + +### [Mysql 常见问题](99.Mysql常见问题) + +## 📚 资料 + +- **官方** + - [Mysql 官网](https://www.mysql.com/) + - [Mysql 官方文档](https://dev.mysql.com/doc/) + - [Mysql 官方文档之命令行客户端](https://dev.mysql.com/doc/refman/8.0/en/mysql.html) +- **书籍** + - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) - 经典,适合 DBA 或作为开发者的参考手册 + - [《MySQL 必知必会》](https://book.douban.com/subject/3354490/) - 适合入门者 +- **教程** + - [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) + - [runoob.com MySQL 教程](http://www.runoob.com/mysql/mysql-tutorial.html) + - [mysql-tutorial](https://github.com/jaywcjlove/mysql-tutorial) +- **更多资源** + - [awesome-mysql](https://github.com/jobbole/awesome-mysql-cn) + +## 🚪 传送 + +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file diff --git a/docs/sql/postgresql.md "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/01.PostgreSQL.md" similarity index 91% rename from docs/sql/postgresql.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/01.PostgreSQL.md" index cad63ae9..61c81ca0 100644 --- a/docs/sql/postgresql.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/01.PostgreSQL.md" @@ -1,10 +1,24 @@ +--- +title: PostgreSQL 应用指南 +date: 2019-08-22 09:02:39 +categories: + - 数据库 + - 关系型数据库 + - 其他 +tags: + - 数据库 + - 关系型数据库 + - PostgreSQL +permalink: /pages/52609d/ +--- + # PostgreSQL 应用指南 > [PostgreSQL](https://www.postgresql.org/) 是一个关系型数据库(RDBM)。 > > 关键词:Database, RDBM, psql -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20180920181010182614.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20180920181010182614.png) ## 安装 @@ -14,7 +28,7 @@ 官方下载页面要求用户选择相应版本,然后动态的给出安装提示,如下图所示: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20180920181010174348.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20180920181010174348.png) 前 3 步要求用户选择,后 4 步是根据选择动态提示的安装步骤 @@ -182,4 +196,4 @@ psql -h 127.0.0.1 -U user_name db_name < dump.sql ## :door: 传送门 -| [我的 Github 博客](https://github.com/dunwu/blog) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) | +| [钝悟的博客](https://dunwu.github.io/blog/) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) | \ No newline at end of file diff --git a/docs/sql/h2.md "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/02.H2.md" similarity index 96% rename from docs/sql/h2.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/02.H2.md" index b8844a41..7073f93a 100644 --- a/docs/sql/h2.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/02.H2.md" @@ -1,3 +1,17 @@ +--- +title: H2 应用指南 +date: 2019-08-22 09:02:39 +categories: + - 数据库 + - 关系型数据库 + - 其他 +tags: + - 数据库 + - 关系型数据库 + - H2 +permalink: /pages/f27c0c/ +--- + # H2 应用指南 ## 概述 @@ -14,11 +28,11 @@ H2 允许用户通过浏览器接口方式访问 SQL 数据库。 2. 启动方式:在 bin 目录下,双击 jar 包;执行 `java -jar h2*.jar`;执行脚本:`h2.bat` 或 `h2.sh`。 3. 在浏览器中访问:`http://localhost:8082`,应该可以看到下图中的页面: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/h2/h2-console.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/h2/h2-console.png) 点击 **Connect** ,可以进入操作界面: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/h2/h2-console-02.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/h2/h2-console-02.png) 操作界面十分简单,不一一细说。 @@ -457,4 +471,4 @@ H2 可以通过 CreateCluster 工具创建集群,示例步骤如下(在在 ## :door: 传送门 -| [我的 Github 博客](https://github.com/dunwu/blog) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) | +| [钝悟的博客](https://dunwu.github.io/blog/) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) | \ No newline at end of file diff --git a/docs/sql/sqlite.md "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/03.Sqlite.md" similarity index 93% rename from docs/sql/sqlite.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/03.Sqlite.md" index 70f5c45f..bcc3b9c1 100644 --- a/docs/sql/sqlite.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/03.Sqlite.md" @@ -1,9 +1,29 @@ +--- +title: sqlite +date: 2019-08-22 09:02:39 +categories: + - 数据库 + - 关系型数据库 + - 其他 +tags: + - 数据库 + - 关系型数据库 + - SQLite +permalink: /pages/bdcd7e/ +--- + # SQLite -> SQLite 是一个实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。 -> :point_right: [完整示例源码](https://github.com/dunwu/db-tutorial/tree/master/codes/javadb/javadb-sqlite) +> SQLite 是一个无服务器的、零配置的、事务性的的开源数据库引擎。 +> 💻 [完整示例源码](https://github.com/dunwu/db-tutorial/tree/master/codes/javadb/javadb-sqlite) + +## SQLite 简介 + +SQLite 是一个C语言编写的轻量级、全功能、无服务器、零配置的的开源数据库引擎。 + +SQLite 的设计目标是嵌入式的数据库,很多嵌入式产品中都使用了它。SQLite 十分轻量,占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了。SQLite 能够支持Windows/Linux/Unix等等主流的操作系统,同时能够跟很多程序语言相结合,同样比起Mysql、PostgreSQL这两款开源的世界著名数据库管理系统来讲,它的处理速度比他们都快。 -## 简介 +SQLite 大小只有 3M 左右,可以将整个 SQLite 嵌入到应用中,而不用采用传统的客户端/服务器(Client/Server)的架构。这样做的好处就是非常轻便,在许多智能设备和应用中都可以使用 SQLite,比如微信就采用了 SQLite 作为本地聊天记录的存储。 ### 优点 @@ -33,11 +53,11 @@ Sqlite 可在 UNIX(Linux, Mac OS-X, Android, iOS)和 Windows(Win32, WinCE, 一般,Linux 和 Mac 上会预安装 sqlite。如果没有安装,可以在[官方下载地址](https://www.sqlite.org/download.html)下载合适安装版本,自行安装。 -## 语法 +## SQLite 语法 > 这里不会详细列举所有 SQL 语法,仅列举 SQLite 除标准 SQL 以外的,一些自身特殊的 SQL 语法。 > -> :point_right: 扩展阅读:[标准 SQL 基本语法](https://github.com/dunwu/blog/blob/master/docs/database/sql/sql.md) +> 📖 扩展阅读:[标准 SQL 基本语法](https://github.com/dunwu/blog/blob/master/docs/database/sql/sql.md) ### 大小写敏感 @@ -129,7 +149,7 @@ sqlite3 test.db .dump > /home/test.sql sqlite3 test.db < test.sql ``` -## 数据类型 +## SQLite 数据类型 SQLite 使用一个更普遍的动态类型系统。在 SQLite 中,值的数据类型与值本身是相关的,而不是与它的容器相关。 @@ -282,7 +302,7 @@ goodbye|20 $ ``` -## JAVA Client +## SQLite JAVA Client (1)在[官方下载地址](https://bitbucket.org/xerial/sqlite-jdbc/downloads)下载 sqlite-jdbc-(VERSION).jar ,然后将 jar 包放在项目中的 classpath。 @@ -363,6 +383,7 @@ Connection connection = DriverManager.getConnection("jdbc:sqlite::memory:"); ## 参考资料 - [SQLite 官网](https://www.sqlite.org/index.html) +- [SQLite Github](https://github.com/sqlite/sqlite) - [SQLite 官方文档](https://www.sqlite.org/docs.html) - [SQLite 官方命令行手册](https://www.sqlite.org/cli.html) - http://www.runoob.com/sqlite/sqlite-commands.html @@ -371,4 +392,4 @@ Connection connection = DriverManager.getConnection("jdbc:sqlite::memory:"); ## :door: 传送门 -| [我的 Github 博客](https://github.com/dunwu/blog) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) | +| [钝悟的博客](https://dunwu.github.io/blog/) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) | \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/README.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/README.md" new file mode 100644 index 00000000..cc289d7b --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/README.md" @@ -0,0 +1,27 @@ +--- +title: 关系型数据库其他知识 +date: 2022-04-11 16:52:35 +categories: + - 数据库 + - 关系型数据库 + - 其他 +tags: + - 数据库 + - 关系型数据库 +permalink: /pages/ca9888/ +hidden: true +--- + +# 关系型数据库其他知识 + +## 📖 内容 + +- [PostgreSQL 应用指南](01.PostgreSQL.md) +- [H2 应用指南](02.H2.md) +- [SqLite 应用指南](03.Sqlite.md) + +## 📚 资料 + +## 🚪 传送 + +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/README.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/README.md" new file mode 100644 index 00000000..6d0e15db --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/README.md" @@ -0,0 +1,75 @@ +--- +title: 关系型数据库 +date: 2022-04-11 16:52:35 +categories: + - 数据库 + - 关系型数据库 +tags: + - 数据库 + - 关系型数据库 +permalink: /pages/bb43eb/ +hidden: true +--- + +# 关系型数据库 + +## 📖 内容 + +### 关系型数据库综合 + +- [关系型数据库面试总结](01.综合/01.关系型数据库面试.md) 💯 +- [SQL 语法基础特性](01.综合/02.SQL语法基础特性.md) +- [SQL 语法高级特性](01.综合/03.SQL语法高级特性.md) +- [扩展 SQL](01.综合/03.扩展SQL.md) +- [SQL Cheat Sheet](01.综合/99.SqlCheatSheet.md) + +### Mysql + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716103611.png) + +- [Mysql 应用指南](02.Mysql/01.Mysql应用指南.md) ⚡ +- [Mysql 工作流](02.Mysql/02.MySQL工作流.md) - 关键词:`连接`、`缓存`、`语法分析`、`优化`、`执行引擎`、`redo log`、`bin log`、`两阶段提交` +- [Mysql 事务](02.Mysql/03.Mysql事务.md) - 关键词:`ACID`、`AUTOCOMMIT`、`事务隔离级别`、`死锁`、`分布式事务` +- [Mysql 锁](02.Mysql/04.Mysql锁.md) - 关键词:`乐观锁`、`表级锁`、`行级锁`、`意向锁`、`MVCC`、`Next-key 锁` +- [Mysql 索引](02.Mysql/05.Mysql索引.md) - 关键词:`Hash`、`B 树`、`聚簇索引`、`回表` +- [Mysql 性能优化](02.Mysql/06.Mysql性能优化.md) +- [Mysql 运维](02.Mysql/20.Mysql运维.md) 🔨 +- [Mysql 配置](02.Mysql/21.Mysql配置.md) 🔨 +- [Mysql 问题](02.Mysql/99.Mysql常见问题.md) + +### 其他 + +- [PostgreSQL 应用指南](99.其他/01.PostgreSQL.md) +- [H2 应用指南](99.其他/02.H2.md) +- [SqLite 应用指南](99.其他/03.Sqlite.md) + +## 📚 资料 + +### 综合 + +- [《数据库的索引设计与优化》](https://book.douban.com/subject/26419771/) +- [《SQL 必知必会》](https://book.douban.com/subject/35167240/) - SQL 入门经典 + +### Mysql + +- **官方** + - [Mysql 官网](https://www.mysql.com/) + - [Mysql 官方文档](https://dev.mysql.com/doc/) + - [Mysql 官方文档之命令行客户端](https://dev.mysql.com/doc/refman/8.0/en/mysql.html) +- **书籍** + - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) - 经典,适合 DBA 或作为开发者的参考手册 + - [《MySQL 必知必会》](https://book.douban.com/subject/3354490/) - MySQL 入门经典 +- **教程** + - [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) + - [runoob.com MySQL 教程](http://www.runoob.com/mysql/mysql-tutorial.html) + - [mysql-tutorial](https://github.com/jaywcjlove/mysql-tutorial) +- **更多资源** + - [awesome-mysql](https://github.com/jobbole/awesome-mysql-cn) + +### 其他 + +- [《Oracle Database 9i/10g/11g 编程艺术》](https://book.douban.com/subject/5402711/) + +## 🚪 传送 + +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file diff --git a/docs/nosql/mongodb/mongodb-quickstart.md "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/01.MongoDB\345\272\224\347\224\250\346\214\207\345\215\227.md" similarity index 96% rename from docs/nosql/mongodb/mongodb-quickstart.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/01.MongoDB\345\272\224\347\224\250\346\214\207\345\215\227.md" index 5b64f483..78856a46 100644 --- a/docs/nosql/mongodb/mongodb-quickstart.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/01.MongoDB\345\272\224\347\224\250\346\214\207\345\215\227.md" @@ -1,31 +1,18 @@ -# MongoDB 应用指南 +--- +title: MongoDB 应用指南 +date: 2020-09-07 07:54:19 +categories: + - 数据库 + - 文档数据库 + - MongoDB +tags: + - 数据库 + - 文档数据库 + - MongoDB +permalink: /pages/3288f3/ +--- - - -- [简介](#简介) - - [MongoDB 发展](#mongodb-发展) - - [MongoDB 和 RDBMS](#mongodb-和-rdbms) - - [MongoDB 特性](#mongodb-特性) -- [MongoDB 概念](#mongodb-概念) - - [数据库](#数据库) - - [文档](#文档) - - [集合](#集合) - - [元数据](#元数据) -- [MongoDB 数据类型](#mongodb-数据类型) -- [MongoDB CRUD](#mongodb-crud) - - [数据库操作](#数据库操作) - - [集合操作](#集合操作) - - [插入文档操作](#插入文档操作) - - [查询文档操作](#查询文档操作) - - [更新文档操作](#更新文档操作) - - [删除文档操作](#删除文档操作) - - [索引操作](#索引操作) -- [MongoDB 聚合操作](#mongodb-聚合操作) - - [管道](#管道) - - [聚合步骤](#聚合步骤) -- [参考资料](#参考资料) - - +# MongoDB 应用指南 ## 简介 @@ -679,4 +666,4 @@ db.<集合>.aggregate(pipeline, {options}); - [MongoDB 官网](https://www.mongodb.com/) - [MongoDB Github](https://github.com/mongodb/mongo) -- [MongoDB 教程](https://www.runoob.com/mongodb/mongodb-tutorial.html) +- [MongoDB 教程](https://www.runoob.com/mongodb/mongodb-tutorial.html) \ No newline at end of file diff --git a/docs/nosql/mongodb/mongodb-crud.md "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/02.MongoDB\347\232\204CRUD\346\223\215\344\275\234.md" similarity index 77% rename from docs/nosql/mongodb/mongodb-crud.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/02.MongoDB\347\232\204CRUD\346\223\215\344\275\234.md" index bcc84174..e4207ac9 100644 --- a/docs/nosql/mongodb/mongodb-crud.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/02.MongoDB\347\232\204CRUD\346\223\215\344\275\234.md" @@ -1,8 +1,18 @@ -# MongoDB CRUD 操作 - - - - +--- +title: MongoDB 的 CRUD 操作 +date: 2020-09-25 21:23:41 +categories: + - 数据库 + - 文档数据库 + - MongoDB +tags: + - 数据库 + - 文档数据库 + - MongoDB +permalink: /pages/7efbac/ +--- + +# MongoDB 的 CRUD 操作 ## 一、基本 CRUD 操作 @@ -17,7 +27,7 @@ MongoDB 提供以下操作向一个 collection 插入 document > 注:以上操作都是原子操作。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200924112342.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200924112342.svg) 插入操作的特性: @@ -33,7 +43,7 @@ db.inventory.insertOne({ item: 'canvas', qty: 100, tags: ['cotton'], - size: { h: 28, w: 35.5, uom: 'cm' }, + size: { h: 28, w: 35.5, uom: 'cm' } }) ``` @@ -45,20 +55,20 @@ db.inventory.insertMany([ item: 'journal', qty: 25, tags: ['blank', 'red'], - size: { h: 14, w: 21, uom: 'cm' }, + size: { h: 14, w: 21, uom: 'cm' } }, { item: 'mat', qty: 85, tags: ['gray'], - size: { h: 27.9, w: 35.5, uom: 'cm' }, + size: { h: 27.9, w: 35.5, uom: 'cm' } }, { item: 'mousepad', qty: 25, tags: ['gel', 'blue'], - size: { h: 19, w: 22.85, uom: 'cm' }, - }, + size: { h: 19, w: 22.85, uom: 'cm' } + } ]) ``` @@ -66,7 +76,7 @@ db.inventory.insertMany([ MongoDB 提供 [`db.collection.find()`](https://docs.mongodb.com/manual/reference/method/db.collection.find/#db.collection.find) 方法来检索 document。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200924113832.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200924113832.svg) ### Update 操作 @@ -82,7 +92,7 @@ MongoDB 提供以下操作来更新 collection 中的 document - [`db.collection.updateMany(, , )`](https://docs.mongodb.com/manual/reference/method/db.collection.updateMany/#db.collection.updateMany) - [`db.collection.replaceOne(, , )`](https://docs.mongodb.com/manual/reference/method/db.collection.replaceOne/#db.collection.replaceOne) -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200924114043.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200924114043.svg) 【示例】插入测试数据 @@ -92,7 +102,7 @@ db.inventory.insertMany([ item: 'canvas', qty: 100, size: { h: 28, w: 35.5, uom: 'cm' }, - status: 'A', + status: 'A' }, { item: 'journal', qty: 25, size: { h: 14, w: 21, uom: 'cm' }, status: 'A' }, { item: 'mat', qty: 85, size: { h: 27.9, w: 35.5, uom: 'cm' }, status: 'A' }, @@ -100,39 +110,39 @@ db.inventory.insertMany([ item: 'mousepad', qty: 25, size: { h: 19, w: 22.85, uom: 'cm' }, - status: 'P', + status: 'P' }, { item: 'notebook', qty: 50, size: { h: 8.5, w: 11, uom: 'in' }, - status: 'P', + status: 'P' }, { item: 'paper', qty: 100, size: { h: 8.5, w: 11, uom: 'in' }, status: 'D' }, { item: 'planner', qty: 75, size: { h: 22.85, w: 30, uom: 'cm' }, - status: 'D', + status: 'D' }, { item: 'postcard', qty: 45, size: { h: 10, w: 15.25, uom: 'cm' }, - status: 'A', + status: 'A' }, { item: 'sketchbook', qty: 80, size: { h: 14, w: 21, uom: 'cm' }, - status: 'A', + status: 'A' }, { item: 'sketch pad', qty: 95, size: { h: 22.85, w: 30.5, uom: 'cm' }, - status: 'A', - }, + status: 'A' + } ]) ``` @@ -143,7 +153,7 @@ db.inventory.updateOne( { item: 'paper' }, { $set: { 'size.uom': 'cm', status: 'P' }, - $currentDate: { lastModified: true }, + $currentDate: { lastModified: true } } ) ``` @@ -155,7 +165,7 @@ db.inventory.updateMany( { qty: { $lt: 50 } }, { $set: { 'size.uom': 'in', status: 'P' }, - $currentDate: { lastModified: true }, + $currentDate: { lastModified: true } } ) ``` @@ -169,8 +179,8 @@ db.inventory.replaceOne( item: 'paper', instock: [ { warehouse: 'A', qty: 60 }, - { warehouse: 'B', qty: 40 }, - ], + { warehouse: 'B', qty: 40 } + ] } ) ``` @@ -191,7 +201,7 @@ MongoDB 提供以下操作来删除 collection 中的 document - [`db.collection.deleteOne()`](https://docs.mongodb.com/manual/reference/method/db.collection.deleteOne/#db.collection.deleteOne):删除一条 document - [`db.collection.deleteMany()`](https://docs.mongodb.com/manual/reference/method/db.collection.deleteMany/#db.collection.deleteMany):删除多条 document -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200924120007.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200924120007.svg) 删除操作的特性: @@ -236,9 +246,9 @@ try { _id: 4, char: 'Dithras', class: 'barbarian', - lvl: 4, - }, - }, + lvl: 4 + } + } }, { insertOne: { @@ -246,23 +256,23 @@ try { _id: 5, char: 'Taeln', class: 'fighter', - lvl: 3, - }, - }, + lvl: 3 + } + } }, { updateOne: { filter: { char: 'Eldon' }, - update: { $set: { status: 'Critical Injury' } }, - }, + update: { $set: { status: 'Critical Injury' } } + } }, { deleteOne: { filter: { char: 'Brisbane' } } }, { replaceOne: { filter: { char: 'Meldane' }, - replacement: { char: 'Tanys', class: 'oracle', lvl: 4 }, - }, - }, + replacement: { char: 'Tanys', class: 'oracle', lvl: 4 } + } + } ]) } catch (e) { print(e) @@ -294,20 +304,20 @@ try { ### 术语和概念 -| SQL 术语和概念 | MongoDB 术语和概念 | -| :-------------------------- | :----------------------------------------------------------- | -| database | [database](https://docs.mongodb.com/manual/reference/glossary/#term-database) | -| table | [collection](https://docs.mongodb.com/manual/reference/glossary/#term-collection) | -| row | [document](https://docs.mongodb.com/manual/reference/glossary/#term-document) 或 [BSON](https://docs.mongodb.com/manual/reference/glossary/#term-bson) | -| column | [field](https://docs.mongodb.com/manual/reference/glossary/#term-field) | -| index | [index](https://docs.mongodb.com/manual/reference/glossary/#term-index) | -| table joins | [`$lookup`](https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/#pipe._S_lookup)、嵌入式文档 | -| primary key | [primary key](https://docs.mongodb.com/manual/reference/glossary/#term-primary-key)
MongoDB 中自动设置主键为 [`_id`](https://docs.mongodb.com/manual/reference/glossary/#term-id) 字段 | -| aggregation (e.g. group by) | aggregation pipeline
参考 [SQL to Aggregation Mapping Chart](https://docs.mongodb.com/manual/reference/sql-aggregation-comparison/). | -| SELECT INTO NEW_TABLE | [`$out`](https://docs.mongodb.com/manual/reference/operator/aggregation/out/#pipe._S_out)
参考 [SQL to Aggregation Mapping Chart](https://docs.mongodb.com/manual/reference/sql-aggregation-comparison/) | +| SQL 术语和概念 | MongoDB 术语和概念 | +| :-------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| database | [database](https://docs.mongodb.com/manual/reference/glossary/#term-database) | +| table | [collection](https://docs.mongodb.com/manual/reference/glossary/#term-collection) | +| row | [document](https://docs.mongodb.com/manual/reference/glossary/#term-document) 或 [BSON](https://docs.mongodb.com/manual/reference/glossary/#term-bson) | +| column | [field](https://docs.mongodb.com/manual/reference/glossary/#term-field) | +| index | [index](https://docs.mongodb.com/manual/reference/glossary/#term-index) | +| table joins | [`$lookup`](https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/#pipe._S_lookup)、嵌入式文档 | +| primary key | [primary key](https://docs.mongodb.com/manual/reference/glossary/#term-primary-key)
MongoDB 中自动设置主键为 [`_id`](https://docs.mongodb.com/manual/reference/glossary/#term-id) 字段 | +| aggregation (e.g. group by) | aggregation pipeline
参考 [SQL to Aggregation Mapping Chart](https://docs.mongodb.com/manual/reference/sql-aggregation-comparison/). | +| SELECT INTO NEW_TABLE | [`$out`](https://docs.mongodb.com/manual/reference/operator/aggregation/out/#pipe._S_out)
参考 [SQL to Aggregation Mapping Chart](https://docs.mongodb.com/manual/reference/sql-aggregation-comparison/) | | MERGE INTO TABLE | [`$merge`](https://docs.mongodb.com/manual/reference/operator/aggregation/merge/#pipe._S_merge) (MongoDB 4.2 开始支持)
参考 [SQL to Aggregation Mapping Chart](https://docs.mongodb.com/manual/reference/sql-aggregation-comparison/). | -| UNION ALL | [`$unionWith`](https://docs.mongodb.com/manual/reference/operator/aggregation/unionWith/#pipe._S_unionWith) (MongoDB 4.4 开始支持) | -| transactions | [transactions](https://docs.mongodb.com/manual/core/transactions/) | +| UNION ALL | [`$unionWith`](https://docs.mongodb.com/manual/reference/operator/aggregation/unionWith/#pipe._S_unionWith) (MongoDB 4.4 开始支持) | +| transactions | [transactions](https://docs.mongodb.com/manual/core/transactions/) | ## 参考资料 @@ -317,4 +327,4 @@ try { - [MongoDB 官方免费教程](https://university.mongodb.com/) - **教程** - [MongoDB 教程](https://www.runoob.com/mongodb/mongodb-tutorial.html) - - [MongoDB 高手课](https://time.geekbang.org/course/intro/100040001) + - [MongoDB 高手课](https://time.geekbang.org/course/intro/100040001) \ No newline at end of file diff --git a/docs/nosql/mongodb/mongodb-aggregation.md "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/03.MongoDB\347\232\204\350\201\232\345\220\210\346\223\215\344\275\234.md" similarity index 94% rename from docs/nosql/mongodb/mongodb-aggregation.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/03.MongoDB\347\232\204\350\201\232\345\220\210\346\223\215\344\275\234.md" index f5087f65..6c4d00a3 100644 --- a/docs/nosql/mongodb/mongodb-aggregation.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/03.MongoDB\347\232\204\350\201\232\345\220\210\346\223\215\344\275\234.md" @@ -1,4 +1,19 @@ -# MongoDB 聚合操作 +--- +title: MongoDB 的聚合操作 +date: 2020-09-21 21:22:57 +categories: + - 数据库 + - 文档数据库 + - MongoDB +tags: + - 数据库 + - 文档数据库 + - MongoDB + - 聚合 +permalink: /pages/75daa5/ +--- + +# MongoDB 的聚合操作 聚合操作处理数据记录并返回计算结果。聚合操作将来自多个 document 的值分组,并可以对分组的数据执行各种操作以返回单个结果。 MongoDB 提供了三种执行聚合的方式:聚合管道,map-reduce 函数和单一目的聚合方法。 @@ -14,7 +29,7 @@ MongoDB Pipeline 由多个阶段([stages](https://docs.mongodb.com/manual/refe 同一个阶段可以在 pipeline 中出现多次,但 [`$out`](https://docs.mongodb.com/manual/reference/operator/aggregation/out/#pipe._S_out)、[`$merge`](https://docs.mongodb.com/manual/reference/operator/aggregation/merge/#pipe._S_merge),和 [`$geoNear`](https://docs.mongodb.com/manual/reference/operator/aggregation/geoNear/#pipe._S_geoNear) 阶段除外。所有可用 pipeline 阶段可以参考:[Aggregation Pipeline Stages](https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/#aggregation-pipeline-operator-reference)。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200921092725.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200921092725.png) - 第一阶段:[`$match`](https://docs.mongodb.com/manual/reference/operator/aggregation/match/#pipe._S_match) 阶段按状态字段过滤 document,然后将状态等于“ A”的那些 document 传递到下一阶段。 - 第二阶段:[`$group`](https://docs.mongodb.com/manual/reference/operator/aggregation/group/#pipe._S_group) 阶段按 cust_id 字段对 document 进行分组,以计算每个唯一 cust_id 的金额总和。 @@ -224,7 +239,7 @@ Pipeline 的内存限制为 100 MB。 Map-reduce 是一种数据处理范式,用于将大量数据汇总为有用的聚合结果。为了执行 map-reduce 操作,MongoDB 提供了 [`mapReduce`](https://docs.mongodb.com/manual/reference/command/mapReduce/#dbcmd.mapReduce) 数据库命令。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200921155546.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200921155546.svg) 在上面的操作中,MongoDB 将 map 阶段应用于每个输入 document(即 collection 中与查询条件匹配的 document)。 map 函数分发出多个键-值对。对于具有多个值的那些键,MongoDB 应用 reduce 阶段,该阶段收集并汇总聚合的数据。然后,MongoDB 将结果存储在 collection 中。可选地,reduce 函数的输出可以通过 finalize 函数来进一步汇总聚合结果。 @@ -240,7 +255,7 @@ MongoDB 支持一下单一目的的聚合操作: 所有这些操作都汇总了单个 collection 中的 document。尽管这些操作提供了对常见聚合过程的简单访问,但是它们相比聚合 pipeline 和 map-reduce,缺少灵活性和丰富的功能性。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200921155935.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200921155935.svg) ## SQL 和 MongoDB 聚合对比 @@ -274,9 +289,9 @@ db.orders.insertMany([ price: 25, items: [ { sku: 'oranges', qty: 5, price: 2.5 }, - { sku: 'apples', qty: 5, price: 2.5 }, + { sku: 'apples', qty: 5, price: 2.5 } ], - status: 'A', + status: 'A' }, { _id: 2, @@ -285,9 +300,9 @@ db.orders.insertMany([ price: 70, items: [ { sku: 'oranges', qty: 8, price: 2.5 }, - { sku: 'chocolates', qty: 5, price: 10 }, + { sku: 'chocolates', qty: 5, price: 10 } ], - status: 'A', + status: 'A' }, { _id: 3, @@ -296,9 +311,9 @@ db.orders.insertMany([ price: 50, items: [ { sku: 'oranges', qty: 10, price: 2.5 }, - { sku: 'pears', qty: 10, price: 2.5 }, + { sku: 'pears', qty: 10, price: 2.5 } ], - status: 'A', + status: 'A' }, { _id: 4, @@ -306,7 +321,7 @@ db.orders.insertMany([ ord_date: new Date('2020-03-18'), price: 25, items: [{ sku: 'oranges', qty: 10, price: 2.5 }], - status: 'A', + status: 'A' }, { _id: 5, @@ -314,7 +329,7 @@ db.orders.insertMany([ ord_date: new Date('2020-03-19'), price: 50, items: [{ sku: 'chocolates', qty: 5, price: 10 }], - status: 'A', + status: 'A' }, { _id: 6, @@ -323,9 +338,9 @@ db.orders.insertMany([ price: 35, items: [ { sku: 'carrots', qty: 10, price: 1.0 }, - { sku: 'apples', qty: 10, price: 2.5 }, + { sku: 'apples', qty: 10, price: 2.5 } ], - status: 'A', + status: 'A' }, { _id: 7, @@ -333,7 +348,7 @@ db.orders.insertMany([ ord_date: new Date('2020-03-20'), price: 25, items: [{ sku: 'oranges', qty: 10, price: 2.5 }], - status: 'A', + status: 'A' }, { _id: 8, @@ -342,9 +357,9 @@ db.orders.insertMany([ price: 75, items: [ { sku: 'chocolates', qty: 5, price: 10 }, - { sku: 'apples', qty: 10, price: 2.5 }, + { sku: 'apples', qty: 10, price: 2.5 } ], - status: 'A', + status: 'A' }, { _id: 9, @@ -354,9 +369,9 @@ db.orders.insertMany([ items: [ { sku: 'carrots', qty: 5, price: 1.0 }, { sku: 'apples', qty: 10, price: 2.5 }, - { sku: 'oranges', qty: 10, price: 2.5 }, + { sku: 'oranges', qty: 10, price: 2.5 } ], - status: 'A', + status: 'A' }, { _id: 10, @@ -364,14 +379,14 @@ db.orders.insertMany([ ord_date: new Date('2020-03-23'), price: 25, items: [{ sku: 'oranges', qty: 10, price: 2.5 }], - status: 'A', - }, + status: 'A' + } ]) ``` SQL 和 MongoDB 聚合方式对比: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200921200556.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200921200556.png) ## 参考资料 @@ -381,4 +396,4 @@ SQL 和 MongoDB 聚合方式对比: - [MongoDB 官方免费教程](https://university.mongodb.com/) - **教程** - [MongoDB 教程](https://www.runoob.com/mongodb/mongodb-tutorial.html) - - [MongoDB 高手课](https://time.geekbang.org/course/intro/100040001) + - [MongoDB 高手课](https://time.geekbang.org/course/intro/100040001) \ No newline at end of file diff --git a/docs/nosql/mongodb/mongodb-transaction.md "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/04.MongoDB\344\272\213\345\212\241.md" similarity index 62% rename from docs/nosql/mongodb/mongodb-transaction.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/04.MongoDB\344\272\213\345\212\241.md" index cc107ec5..527172f5 100644 --- a/docs/nosql/mongodb/mongodb-transaction.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/04.MongoDB\344\272\213\345\212\241.md" @@ -1,3 +1,18 @@ +--- +title: MongoDB 事务 +date: 2020-09-20 23:12:17 +categories: + - 数据库 + - 文档数据库 + - MongoDB +tags: + - 数据库 + - 文档数据库 + - MongoDB + - 事务 +permalink: /pages/4574fe/ +--- + # MongoDB 事务 writeConcern 可以决定写操作到达多少个节点才算成功。 @@ -14,9 +29,9 @@ journal 则定义如何才算成功。取值包括: 【示例】在集群中使用 writeConcern 参数 ```javascript -db.transaction.insert({"count":1}, {"writeConcern":{w:"majoriy"}}) -db.transaction.insert({"count":1}, {"writeConcern":{w:"4"}}) -db.transaction.insert({"count":1}, {"writeConcern":{w:"all"}}) +db.transaction.insert({ count: 1 }, { writeConcern: { w: 'majoriy' } }) +db.transaction.insert({ count: 1 }, { writeConcern: { w: '4' } }) +db.transaction.insert({ count: 1 }, { writeConcern: { w: 'all' } }) ``` 【示例】配置延迟节点,模拟网络延迟 @@ -26,5 +41,4 @@ conf=rs.conf() conf.memebers[2].slaveDelay=5 conf.memebers[2].priority=0 rs.reconfig(conf) -``` - +``` \ No newline at end of file diff --git a/docs/nosql/mongodb/mongodb-model.md "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/05.MongoDB\345\273\272\346\250\241.md" similarity index 90% rename from docs/nosql/mongodb/mongodb-model.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/05.MongoDB\345\273\272\346\250\241.md" index 9ca77bae..5d3d082c 100644 --- a/docs/nosql/mongodb/mongodb-model.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/05.MongoDB\345\273\272\346\250\241.md" @@ -1,29 +1,24 @@ +--- +title: MongoDB 建模 +date: 2020-09-09 20:47:14 +categories: + - 数据库 + - 文档数据库 + - MongoDB +tags: + - 数据库 + - 文档数据库 + - MongoDB + - 建模 +permalink: /pages/562f99/ +--- + # MongoDB 建模 MongoDB 的数据模式是一种灵活模式,关系型数据库要求你在插入数据之前必须先定义好一个表的模式结构,而 MongoDB 的集合则并不限制 document 结构。这种灵活性让对象和数据库文档之间的映射变得很容易。即使数据记录之间有很大的变化,每个文档也可以很好的映射到各条不同的记录。 当然在实际使用中,同一个集合中的文档往往都有一个比较类似的结构。 数据模型设计中最具挑战性的是在应用程序需求,数据库引擎性能要求和数据读写模式之间做权衡考量。当设计数据模型的时候,一定要考虑应用程序对数据的使用模式(如查询,更新和处理)以及数据本身的天然结构。 - - -- [MongoDB 数据建模入门](#mongodb-数据建模入门) - - [(一)定义数据集](#一定义数据集) - - [(二)思考 JSON 结构](#二思考-json-结构) - - [(三)确定哪些字段作为嵌入式数据](#三确定哪些字段作为嵌入式数据) -- [数据模型简介](#数据模型简介) - - [灵活的 Schema](#灵活的-schema) - - [Document 结构](#document-结构) - - [原子写操作](#原子写操作) - - [数据使用和性能](#数据使用和性能) -- [Schema 校验](#schema-校验) - - [指定校验规则](#指定校验规则) - - [JSON Schema](#json-schema) - - [其它查询表达式](#其它查询表达式) - - [行为](#行为) -- [参考资料](#参考资料) - - - ## MongoDB 数据建模入门 > 参考:https://docs.mongodb.com/guides/server/introduction/#what-you-ll-need @@ -125,7 +120,7 @@ This looks very different from the tabular data structure you started with in St 嵌入式 document 通过将相关数据存储在单个 document 结构中来捕获数据之间的关系。 MongoDB document 可以将 document 结构嵌入到另一个 document 中的字段或数组中。这些非规范化的数据模型允许应用程序在单个数据库操作中检索和操纵相关数据。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200910193231.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200910193231.png) 对于 MongoDB 中的很多场景,非规范化数据模型都是最佳的。 @@ -137,7 +132,7 @@ This looks very different from the tabular data structure you started with in St 引用通过包含从一个 document 到另一个 document 的链接或引用来存储数据之间的关系。 应用程序可以解析这些引用以访问相关数据。 广义上讲,这些是规范化的数据模型。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200910193234.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200910193234.png) 通常,在以下场景使用引用式的数据模型: @@ -204,21 +199,21 @@ db.createCollection('students', { properties: { name: { bsonType: 'string', - description: 'must be a string and is required', + description: 'must be a string and is required' }, year: { bsonType: 'int', minimum: 2017, maximum: 3017, - description: 'must be an integer in [ 2017, 3017 ] and is required', + description: 'must be an integer in [ 2017, 3017 ] and is required' }, major: { enum: ['Math', 'English', 'Computer Science', 'History', null], - description: 'can only be one of the enum values and is required', + description: 'can only be one of the enum values and is required' }, gpa: { bsonType: ['double'], - description: 'must be a double if the field exists', + description: 'must be a double if the field exists' }, address: { bsonType: 'object', @@ -226,17 +221,17 @@ db.createCollection('students', { properties: { street: { bsonType: 'string', - description: 'must be a string if the field exists', + description: 'must be a string if the field exists' }, city: { bsonType: 'string', - description: 'must be a string and is required', - }, - }, - }, - }, - }, - }, + description: 'must be a string and is required' + } + } + } + } + } + } }) ``` @@ -258,9 +253,9 @@ db.createCollection('contacts', { $or: [ { phone: { $type: 'string' } }, { email: { $regex: /@mongodb\.com$/ } }, - { status: { $in: ['Unknown', 'Incomplete'] } }, - ], - }, + { status: { $in: ['Unknown', 'Incomplete'] } } + ] + } }) ``` @@ -286,9 +281,9 @@ db.contacts.insert([ name: 'Anne', phone: '+1 555 123 456', city: 'London', - status: 'Complete', + status: 'Complete' }, - { _id: 2, name: 'Ivan', city: 'Vancouver' }, + { _id: 2, name: 'Ivan', city: 'Vancouver' } ]) ``` @@ -304,16 +299,16 @@ db.runCommand({ properties: { phone: { bsonType: 'string', - description: 'must be a string and is required', + description: 'must be a string and is required' }, name: { bsonType: 'string', - description: 'must be a string and is required', - }, - }, - }, + description: 'must be a string and is required' + } + } + } }, - validationLevel: 'moderate', + validationLevel: 'moderate' }) ``` @@ -343,22 +338,22 @@ db.createCollection('contacts2', { properties: { phone: { bsonType: 'string', - description: 'must be a string and is required', + description: 'must be a string and is required' }, email: { bsonType: 'string', pattern: '@mongodb.com$', description: - 'must be a string and match the regular expression pattern', + 'must be a string and match the regular expression pattern' }, status: { enum: ['Unknown', 'Incomplete'], - description: 'can only be one of the enum values', - }, - }, - }, + description: 'can only be one of the enum values' + } + } + } }, - validationAction: 'warn', + validationAction: 'warn' }) ``` @@ -389,4 +384,4 @@ MongoDB 允许这条操作执行,但是服务器会记录下告警信息。 - [MongoDB 官方免费教程](https://university.mongodb.com/) - **教程** - [MongoDB 教程](https://www.runoob.com/mongodb/mongodb-tutorial.html) - - [MongoDB 高手课](https://time.geekbang.org/course/intro/100040001) + - [MongoDB 高手课](https://time.geekbang.org/course/intro/100040001) \ No newline at end of file diff --git a/docs/nosql/mongodb/mongodb-model-example.md "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/06.MongoDB\345\273\272\346\250\241\347\244\272\344\276\213.md" similarity index 89% rename from docs/nosql/mongodb/mongodb-model-example.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/06.MongoDB\345\273\272\346\250\241\347\244\272\344\276\213.md" index 80ebc212..c6513321 100644 --- a/docs/nosql/mongodb/mongodb-model-example.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/06.MongoDB\345\273\272\346\250\241\347\244\272\344\276\213.md" @@ -1,20 +1,19 @@ -# MongoDB 建模示例 - - - -- [关系型模型](#关系型模型) - - [嵌入式文档一对一关系模型](#嵌入式文档一对一关系模型) - - [嵌入式文档一对多关系模型](#嵌入式文档一对多关系模型) - - [引用式文档一对多关系模型](#引用式文档一对多关系模型) -- [树形结构模型](#树形结构模型) - - [具有父节点的树形结构模型](#具有父节点的树形结构模型) - - [具有子节点的树形结构模型](#具有子节点的树形结构模型) - - [具有祖先的树形结构模型](#具有祖先的树形结构模型) - - [具有实体化路径的树形结构模型](#具有实体化路径的树形结构模型) - - [具有嵌套集的树形结构模型](#具有嵌套集的树形结构模型) -- [参考资料](#参考资料) +--- +title: MongoDB 建模示例 +date: 2020-09-12 10:43:53 +categories: + - 数据库 + - 文档数据库 + - MongoDB +tags: + - 数据库 + - 文档数据库 + - MongoDB + - 建模 +permalink: /pages/88c7d3/ +--- - +# MongoDB 建模示例 ## 关系型模型 @@ -371,7 +370,7 @@ review collection 存储所有的评论 ## 树形结构模型 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200911194846.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200911194846.svg) ### 具有父节点的树形结构模型 @@ -525,27 +524,27 @@ db.categories.insertMany([ ### 具有嵌套集的树形结构模型 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200911204252.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200911204252.svg) ```javascript db.categories.insertMany([ - { _id: "Books", parent: 0, left: 1, right: 12 }, - { _id: "Programming", parent: "Books", left: 2, right: 11 }, - { _id: "Languages", parent: "Programming", left: 3, right: 4 }, - { _id: "Databases", parent: "Programming", left: 5, right: 10 }, - { _id: "MongoDB", parent: "Databases", left: 6, right: 7 }, - { _id: "dbm", parent: "Databases", left: 8, right: 9 } -]); + { _id: 'Books', parent: 0, left: 1, right: 12 }, + { _id: 'Programming', parent: 'Books', left: 2, right: 11 }, + { _id: 'Languages', parent: 'Programming', left: 3, right: 4 }, + { _id: 'Databases', parent: 'Programming', left: 5, right: 10 }, + { _id: 'MongoDB', parent: 'Databases', left: 6, right: 7 }, + { _id: 'dbm', parent: 'Databases', left: 8, right: 9 } +]) ``` 可以查询以检索节点的后代: ```javascript -var databaseCategory = db.categories.findOne({ _id: "Databases" }); +var databaseCategory = db.categories.findOne({ _id: 'Databases' }) db.categories.find({ left: { $gt: databaseCategory.left }, right: { $lt: databaseCategory.right } -}); +}) ``` ## 设计模式 @@ -554,7 +553,7 @@ db.categories.find({ 解决方案是:列转行 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200919225901.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200919225901.png) ### 管理文档不同版本 @@ -583,4 +582,4 @@ MongoDB 文档格式非常灵活,势必会带来版本维护上的难度。 ## 参考资料 -- [Data Model Examples and Patterns](https://docs.mongodb.com/manual/applications/data-models/) +- [Data Model Examples and Patterns](https://docs.mongodb.com/manual/applications/data-models/) \ No newline at end of file diff --git a/docs/nosql/mongodb/mongodb-index.md "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/07.MongoDB\347\264\242\345\274\225.md" similarity index 93% rename from docs/nosql/mongodb/mongodb-index.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/07.MongoDB\347\264\242\345\274\225.md" index 5f697cab..b9b7cc5e 100644 --- a/docs/nosql/mongodb/mongodb-index.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/07.MongoDB\347\264\242\345\274\225.md" @@ -1,3 +1,18 @@ +--- +title: MongoDB 索引 +date: 2020-09-21 21:22:57 +categories: + - 数据库 + - 文档数据库 + - MongoDB +tags: + - 数据库 + - 文档数据库 + - MongoDB + - 索引 +permalink: /pages/10c674/ +--- + # MongoDB 索引 ## MongoDB 索引简介 @@ -12,7 +27,7 @@ 索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200921210621.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200921210621.svg) ### createIndex() 方法 @@ -53,4 +68,4 @@ db.collection.createIndex( { name: -1 } ) - [MongoDB 官方免费教程](https://university.mongodb.com/) - **教程** - [MongoDB 教程](https://www.runoob.com/mongodb/mongodb-tutorial.html) - - [MongoDB 高手课](https://time.geekbang.org/course/intro/100040001) + - [MongoDB 高手课](https://time.geekbang.org/course/intro/100040001) \ No newline at end of file diff --git a/docs/nosql/mongodb/mongodb-replication.md "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/08.MongoDB\345\244\215\345\210\266.md" similarity index 90% rename from docs/nosql/mongodb/mongodb-replication.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/08.MongoDB\345\244\215\345\210\266.md" index 406ed218..220f9a6d 100644 --- a/docs/nosql/mongodb/mongodb-replication.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/08.MongoDB\345\244\215\345\210\266.md" @@ -1,20 +1,19 @@ -# MongoDB 复制 - - +--- +title: MongoDB 复制 +date: 2020-09-20 23:12:17 +categories: + - 数据库 + - 文档数据库 + - MongoDB +tags: + - 数据库 + - 文档数据库 + - MongoDB + - 复制 +permalink: /pages/505407/ +--- -- [副本和可用性](#副本和可用性) -- [MongoDB 副本](#mongodb-副本) -- [异步复制](#异步复制) - - [慢操作](#慢操作) - - [复制延迟和流控](#复制延迟和流控) -- [故障转移](#故障转移) -- [读操作](#读操作) - - [读优先](#读优先) - - [数据可见性](#数据可见性) - - [镜像读取](#镜像读取) -- [参考资料](#参考资料) - - +# MongoDB 复制 ## 副本和可用性 @@ -28,15 +27,15 @@ MongoDB 中的副本集是一组维护相同数据集的 mongod 进程。一个 **主节点负责接收所有写操作**。副本集只能有一个主副本,能够以 [`{ w: "majority" }`](https://docs.mongodb.com/manual/reference/write-concern/#writeconcern."majority") 来确认集群中节点的写操作成功情况;尽管在某些情况下,另一个 MongoDB 实例可能会暂时认为自己也是主要的。主节点在其操作日志(即 [oplog](https://docs.mongodb.com/manual/core/replica-set-oplog/))中记录了对其数据集的所有更改。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920165054.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920165054.svg) **从节点复制主节点的操作日志,并将操作应用于其数据集**,以便同步主节点的数据。如果主节点不可用,则符合条件的从节点将选举新的主节点。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920165055.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920165055.svg) 在某些情况下(例如,有一个主节点和一个从节点,但由于成本限制,禁止添加另一个从节点),您可以选择将 mongod 实例作为仲裁节点添加到副本集。仲裁节点参加选举但不保存数据(即不提供数据冗余)。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920165053.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920165053.svg) 仲裁节点将永远是仲裁节点。在选举期间,主节点可能会降级成为次节点,而次节点可能会升级成为主节点。 @@ -62,7 +61,7 @@ MongoDB 中的副本集是一组维护相同数据集的 mongod 进程。一个 当主节点与集群中的其他成员通信的时间超过配置的 `electionTimeoutMillis`(默认为 10 秒)时,符合选举要求的从节点将要求选举,并提名自己为新的主节点。集群尝试完成选举新主节点并恢复正常工作。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920175429.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920175429.svg) 选举完成前,副本集无法处理写入操作。如果将副本集配置为:在主节点处于脱机状态时,在次节点上运行,则副本集可以继续提供读取查询。 @@ -80,7 +79,7 @@ MongoDB 中的副本集是一组维护相同数据集的 mongod 进程。一个 默认情况下,客户端从主节点读取数据;但是,客户端可以指定读取首选项,以将读取操作发送到从节点。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920204024.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920204024.svg) 异步复制到从节点意味着向从节点读取数据可能会返回与主节点不一致的数据。 @@ -109,4 +108,4 @@ MongoDB 中的副本集是一组维护相同数据集的 mongod 进程。一个 - [MongoDB 官方免费教程](https://university.mongodb.com/) - **教程** - [MongoDB 教程](https://www.runoob.com/mongodb/mongodb-tutorial.html) - - [MongoDB 高手课](https://time.geekbang.org/course/intro/100040001) + - [MongoDB 高手课](https://time.geekbang.org/course/intro/100040001) \ No newline at end of file diff --git a/docs/nosql/mongodb/mongodb-sharding.md "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/09.MongoDB\345\210\206\347\211\207.md" similarity index 88% rename from docs/nosql/mongodb/mongodb-sharding.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/09.MongoDB\345\210\206\347\211\207.md" index 12faedee..a0d437be 100644 --- a/docs/nosql/mongodb/mongodb-sharding.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/09.MongoDB\345\210\206\347\211\207.md" @@ -1,18 +1,19 @@ -# MongoDB 分片 - - +--- +title: MongoDB 分片 +date: 2020-09-20 23:12:17 +categories: + - 数据库 + - 文档数据库 + - MongoDB +tags: + - 数据库 + - 文档数据库 + - MongoDB + - 分片 +permalink: /pages/ad08f5/ +--- -- [分片集群](#分片集群) - - [分片的分布](#分片的分布) - - [连接分片集群](#连接分片集群) -- [分片 Key](#分片-key) -- [分片策略](#分片策略) - - [Hash 分片](#hash-分片) - - [范围分片](#范围分片) -- [分片集群中的区域](#分片集群中的区域) -- [参考资料](#参考资料) - - +# MongoDB 分片 ## 分片集群简介 @@ -36,7 +37,7 @@ MongoDB 分片集群含以下组件: - [mongos](https://docs.mongodb.com/manual/core/sharded-cluster-query-router/):mongos 充当查询路由器,在客户端应用程序和分片集群之间提供接口。从 MongoDB 4.4 开始,mongos 可以支持 [hedged reads](https://docs.mongodb.com/manual/core/sharded-cluster-query-router/#mongos-hedged-reads) 以最大程度地减少延迟。 - [config servers](https://docs.mongodb.com/manual/core/sharded-cluster-config-servers/):提供集群元数据存储和分片数据分布的映射。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920210057.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920210057.svg) ### 分片集群的分布 @@ -48,7 +49,7 @@ MongoDB 数据库可以同时包含分片和未分片的集合的 collection。 分片和未分片的 collection: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920212159.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920212159.svg) ### 路由节点 mongos @@ -56,7 +57,7 @@ MongoDB 数据库可以同时包含分片和未分片的集合的 collection。 连接 [`mongos`](https://docs.mongodb.com/manual/reference/program/mongos/#bin.mongos) 的方式和连接 [`mongod`](https://docs.mongodb.com/manual/reference/program/mongod/#bin.mongod) 相同,例如通过 [`mongo`](https://docs.mongodb.com/manual/reference/program/mongo/#bin.mongo) shell 或 [MongoDB 驱动程序](https://docs.mongodb.com/drivers/?jump=docs)。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920212157.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920212157.svg) 路由节点的作用: @@ -103,7 +104,7 @@ Hash 分片策略会先计算分片 Key 字段值的哈希值;然后,根据 > 注意:使用哈希索引解析查询时,MongoDB 会自动计算哈希值,应用程序不需要计算哈希。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920213343.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920213343.svg) 尽管分片 Key 范围可能是“接近”的,但它们的哈希值不太可能在同一 [chunk](https://docs.mongodb.com/manual/reference/glossary/#term-chunk) 上。基于 Hash 的数据分发有助于更均匀的数据分布,尤其是在分片 Key 单调更改的数据集中。 @@ -113,7 +114,7 @@ Hash 分片策略会先计算分片 Key 字段值的哈希值;然后,根据 范围分片根据分片 Key 值将数据划分为多个范围。然后,根据分片 Key 值为每个 [chunk](https://docs.mongodb.com/manual/reference/glossary/#term-chunk) 分配一个范围。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920213345.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920213345.svg) 值比较近似的一系列分片 Key 更有可能驻留在同一 [chunk](https://docs.mongodb.com/manual/reference/glossary/#term-chunk) 上。范围分片的效率取决于选择的分片 Key。分片 Key 考虑不周全会导致数据分布不均,这可能会削弱分片的某些优势或导致性能瓶颈。 @@ -125,7 +126,7 @@ Hash 分片策略会先计算分片 Key 字段值的哈希值;然后,根据 每个区域覆盖一个或多个分片 Key 值范围。区域覆盖的每个范围始终包括其上下边界。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920214854.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920214854.svg) 在定义要覆盖的区域的新范围时,必须使用分片 Key 中包含的字段。如果使用复合分片 Key,则范围必须包含分片 Key 的前缀。 @@ -139,4 +140,4 @@ Hash 分片策略会先计算分片 Key 字段值的哈希值;然后,根据 - [MongoDB 官方免费教程](https://university.mongodb.com/) - **教程** - [MongoDB 教程](https://www.runoob.com/mongodb/mongodb-tutorial.html) - - [MongoDB 高手课](https://time.geekbang.org/course/intro/100040001) + - [MongoDB 高手课](https://time.geekbang.org/course/intro/100040001) \ No newline at end of file diff --git a/docs/nosql/mongodb/mongodb-ops.md "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/20.MongoDB\350\277\220\347\273\264.md" similarity index 96% rename from docs/nosql/mongodb/mongodb-ops.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/20.MongoDB\350\277\220\347\273\264.md" index aed62043..2f528495 100644 --- a/docs/nosql/mongodb/mongodb-ops.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/20.MongoDB\350\277\220\347\273\264.md" @@ -1,20 +1,19 @@ -# MongoDB 运维 - - +--- +title: MongoDB 运维 +date: 2020-09-09 20:47:14 +categories: + - 数据库 + - 文档数据库 + - MongoDB +tags: + - 数据库 + - 文档数据库 + - MongoDB + - 运维 +permalink: /pages/5e3c30/ +--- -- [MongoDB 安装](#mongodb-安装) - - [Windows](#windows) - - [Linux](#linux) - - [设置用户名、密码](#设置用户名密码) -- [备份和恢复](#备份和恢复) - - [数据备份](#数据备份) - - [数据恢复](#数据恢复) -- [导入导出](#导入导出) - - [导入操作](#导入操作) - - [导出操作](#导出操作) -- [参考资料](#参考资料) - - +# MongoDB 运维 ## MongoDB 安装 @@ -297,4 +296,4 @@ $ mongoexport -h 127.0.0.1 --port 27017 -d test -c product --type csv -f name,pr - [MongoDB 官网](https://www.mongodb.com/) - [MongoDB Github](https://github.com/mongodb/mongo) - [MongoDB 官方免费教程](https://university.mongodb.com/) -- [MongoDB 教程](https://www.runoob.com/mongodb/mongodb-tutorial.html) +- [MongoDB 教程](https://www.runoob.com/mongodb/mongodb-tutorial.html) \ No newline at end of file diff --git a/docs/nosql/mongodb/README.md "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/README.md" similarity index 61% rename from docs/nosql/mongodb/README.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/README.md" index bf7ccf42..cc6f228b 100644 --- a/docs/nosql/mongodb/README.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/README.md" @@ -1,3 +1,18 @@ +--- +title: MongoDB 教程 +date: 2020-09-09 20:47:14 +categories: + - 数据库 + - 文档数据库 + - MongoDB +tags: + - 数据库 + - 文档数据库 + - MongoDB +permalink: /pages/b1a116/ +hidden: true +--- + # MongoDB 教程 > MongoDB 是一个基于文档的分布式数据库,由 C++ 语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。 @@ -8,23 +23,25 @@ ## 📖 内容 -### [MongoDB 应用指南](mongodb-quickstart.md) +### [MongoDB 应用指南](01.MongoDB应用指南.md) + +### [MongoDB 的 CRUD 操作](02.MongoDB的CRUD操作.md) -### [MongoDB CRUD 操作](mongodb-crud.md) +### [MongoDB 聚合操作](03.MongoDB的聚合操作.md) -### [MongoDB 聚合操作](mongodb-aggregation.md) +### [MongoDB 事务](04.MongoDB事务.md) -### [MongoDB 建模](mongodb-model.md) +### [MongoDB 建模](05.MongoDB建模.md) -### [MongoDB 建模示例](mongodb-model-example.md) +### [MongoDB 建模示例](06.MongoDB建模示例.md) -### [MongoDB 索引](mongodb-index.md) +### [MongoDB 索引](07.MongoDB索引.md) -### [MongoDB 复制](mongodb-replication.md) +### [MongoDB 复制](08.MongoDB复制.md) -### [MongoDB 分片](mongodb-sharding.md) +### [MongoDB 分片](09.MongoDB分片.md) -### [MongoDB 运维](mongodb-ops.md) +### [MongoDB 运维](20.MongoDB运维.md) ## 📚 资料 @@ -42,4 +59,4 @@ ## 🚪 传送 -◾ 🏠 [DB-TUTORIAL 首页](https://github.com/dunwu/db-tutorial) ◾ 🎯 [我的博客](https://github.com/dunwu/blog) ◾ +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file diff --git a/docs/nosql/redis/redis-interview.md "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/01.Redis\351\235\242\350\257\225\346\200\273\347\273\223.md" similarity index 93% rename from docs/nosql/redis/redis-interview.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/01.Redis\351\235\242\350\257\225\346\200\273\347\273\223.md" index 2c9f795f..be358ab9 100644 --- a/docs/nosql/redis/redis-interview.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/01.Redis\351\235\242\350\257\225\346\200\273\347\273\223.md" @@ -1,19 +1,19 @@ -# Redis 面试总结 - - - -- [Redis 数据类型](#redis-数据类型) -- [Redis 内存淘汰](#redis-内存淘汰) -- [Redis 持久化](#redis-持久化) -- [Redis 事务](#redis-事务) -- [Redis 管道](#redis-管道) -- [Redis 高并发](#redis-高并发) -- [Redis 复制](#redis-复制) -- [Redis 哨兵](#redis-哨兵) -- [Redis vs. Memcached](#redis-vs-memcached) -- [参考资料](#参考资料) +--- +title: Redis 面试总结 +date: 2020-07-13 17:03:42 +categories: + - 数据库 + - KV数据库 + - Redis +tags: + - 数据库 + - KV数据库 + - Redis + - 面试 +permalink: /pages/451b73/ +--- - +# Redis 面试总结 ## Redis 数据类型 @@ -93,7 +93,7 @@ > **_Redis 持久化_** > -> 详情可以参考:[Redis 持久化](redis-persistence.md) +> 详情可以参考:[Redis 持久化](04.Redis持久化.md) (1)Redis 支持两种持久化方式:RDB 和 AOF。 @@ -129,7 +129,7 @@ AOF 丢数据比 RDB 少,但文件会比 RDB 文件大很多。 > **_Redis 的事务特性、原理_** > -> 详情参考:[Redis 应用指南之 事务](redis-quickstart.md#六redis-事务) +> 详情参考:[Redis 应用指南之 事务](02.Redis应用指南.md#六redis-事务) **Redis 提供的不是严格的事务,Redis 只保证串行执行命令,并且能保证全部执行,但是执行命令失败时并不会回滚,而是会继续执行下去**。 @@ -169,7 +169,7 @@ Redis 是一种基于 C/S 模型以及请求/响应协议的 TCP 服务。Redis > **_Redis 集群_** > -> 详情可以参考:[Redis 集群](redis-cluster.md) +> 详情可以参考:[Redis 集群](07.Redis集群.md) (1)单线程 @@ -209,7 +209,7 @@ Redis 集群基于复制特性实现节点间的数据一致性。 > **_Redis 复制_** > -> 详情可以参考:[Redis 复制](redis-replication.md) +> 详情可以参考:[Redis 复制](05.Redis复制.md) (1)旧版复制基于 `SYNC` 命令实现。分为同步(sync)和命令传播(command propagate)两个操作。这种方式存在缺陷:不能高效处理断线重连后的复制情况。 @@ -247,13 +247,13 @@ Redis 集群基于复制特性实现节点间的数据一致性。 > **_Redis 哨兵_** > -> 详情可以参考:[Redis 哨兵](redis-sentinel.md) +> 详情可以参考:[Redis 哨兵](06.Redis哨兵.md) (1)Redis 的高可用是通过哨兵来实现(Raft 协议的 Redis 实现)。Sentinel(哨兵)可以监听主服务器,并在主服务器进入下线状态时,自动从从服务器中选举出新的主服务器。 由一个或多个 Sentinel 实例组成的 Sentinel 系统可以监视任意多个主服务器,以及这些主服务器的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200131135847.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200131135847.png) ## Redis vs. Memcached @@ -288,4 +288,4 @@ Memcached 是多线程,非阻塞 IO 复用的网络模型;Redis 使用单线 ## 参考资料 - [面试中关于 Redis 的问题看这篇就够了](https://juejin.im/post/5ad6e4066fb9a028d82c4b66) -- [advanced-java](https://github.com/doocs/advanced-java#缓存) +- [advanced-java](https://github.com/doocs/advanced-java#缓存) \ No newline at end of file diff --git a/docs/nosql/redis/redis-quickstart.md "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/02.Redis\345\272\224\347\224\250\346\214\207\345\215\227.md" similarity index 92% rename from docs/nosql/redis/redis-quickstart.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/02.Redis\345\272\224\347\224\250\346\214\207\345\215\227.md" index 1b4fc445..417cfdc7 100644 --- a/docs/nosql/redis/redis-quickstart.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/02.Redis\345\272\224\347\224\250\346\214\207\345\215\227.md" @@ -1,49 +1,18 @@ -# Redis 应用指南 +--- +title: Redis 应用指南 +date: 2020-01-30 21:48:57 +categories: + - 数据库 + - KV数据库 + - Redis +tags: + - 数据库 + - KV数据库 + - Redis +permalink: /pages/94e9d6/ +--- - - -- [一、Redis 简介](#一redis-简介) - - [Redis 使用场景](#redis-使用场景) - - [Redis 的优势](#redis-的优势) - - [Redis 与 Memcached](#redis-与-memcached) -- [二、Redis 数据类型](#二redis-数据类型) - - [STRING](#string) - - [HASH](#hash) - - [LIST](#list) - - [SET](#set) - - [ZSET](#zset) -- [三、Redis 内存淘汰](#三redis-内存淘汰) - - [内存淘汰要点](#内存淘汰要点) - - [主键过期时间](#主键过期时间) - - [淘汰策略](#淘汰策略) - - [如何选择淘汰策略](#如何选择淘汰策略) - - [内部实现](#内部实现) -- [四、Redis 持久化](#四redis-持久化) -- [五、Redis 事件](#五redis-事件) - - [文件事件](#文件事件) - - [时间事件](#时间事件) - - [事件的调度与执行](#事件的调度与执行) -- [六、Redis 事务](#六redis-事务) - - [MULTI](#multi) - - [EXEC](#exec) - - [DISCARD](#discard) - - [WATCH](#watch) - - [Rollback](#rollback) -- [七、Redis 管道](#七redis-管道) -- [八、Redis 发布与订阅](#八redis-发布与订阅) -- [九、Redis 复制](#九redis-复制) - - [旧版复制](#旧版复制) - - [新版复制](#新版复制) - - [部分重同步](#部分重同步) - - [PSYNC 命令](#psync-命令) - - [心跳检测](#心跳检测) -- [十、Redis 哨兵](#十redis-哨兵) -- [十一、Redis 集群](#十一redis-集群) -- [Redis Client](#redis-client) -- [扩展阅读](#扩展阅读) -- [参考资料](#参考资料) - - +# Redis 应用指南 ## 一、Redis 简介 @@ -117,7 +86,7 @@ Redis 基本数据类型:STRING、HASH、LIST、SET、ZSET Redis 高级数据类型:BitMap、HyperLogLog、GEO -> :bulb: 更详细的特性及原理说明请参考:[Redis 数据类型和应用](redis-datatype.md) +> :bulb: 更详细的特性及原理说明请参考:[Redis 数据类型和应用](03.Redis数据类型和应用.md) ## 三、Redis 内存淘汰 @@ -195,7 +164,7 @@ Redis 支持两种持久化方式:RDB 和 AOF。 - RDB - **RDB 即快照方式,它将某个时间点的所有 Redis 数据保存到一个经过压缩的二进制文件(RDB 文件)中**。 - AOF - `AOF(Append Only File)` 是以文本日志形式将所有写命令追加到 AOF 文件的末尾,以此来记录数据的变化。当服务器重启的时候会重新载入和执行这些命令来恢复原始的数据。AOF 适合作为 **热备**。 -> :bulb: 更详细的特性及原理说明请参考:[Redis 持久化](redis-persistence.md) +> :bulb: 更详细的特性及原理说明请参考:[Redis 持久化](04.Redis持久化.md) ## 五、Redis 事件 @@ -215,7 +184,7 @@ Redis 基于 Reactor 模式开发了自己的网络时间处理器。 文件事件处理器有四个组成部分:套接字、I/O 多路复用程序、文件事件分派器、事件处理器。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200130172525.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200130172525.png) ### 时间事件 @@ -277,7 +246,7 @@ def main(): 从事件处理的角度来看,服务器运行流程如下:
- +
## 六、Redis 事务 @@ -423,12 +392,12 @@ pipe.exec(); Redis 提供了 5 个发布与订阅命令: -| 命令 | 描述 | -| -------------- | ------------------------------------------------------------ | -| `SUBSCRIBE` | `SUBSCRIBE channel [channel ...]`—订阅指定频道。 | -| `UNSUBSCRIBE` | `UNSUBSCRIBE [channel [channel ...]]`—取消订阅指定频道。 | -| `PUBLISH` | `PUBLISH channel message`—发送信息到指定的频道。 | -| `PSUBSCRIBE` | `PSUBSCRIBE pattern [pattern ...]`—订阅符合指定模式的频道。 | +| 命令 | 描述 | +| -------------- | ------------------------------------------------------------------- | +| `SUBSCRIBE` | `SUBSCRIBE channel [channel ...]`—订阅指定频道。 | +| `UNSUBSCRIBE` | `UNSUBSCRIBE [channel [channel ...]]`—取消订阅指定频道。 | +| `PUBLISH` | `PUBLISH channel message`—发送信息到指定的频道。 | +| `PSUBSCRIBE` | `PSUBSCRIBE pattern [pattern ...]`—订阅符合指定模式的频道。 | | `PUNSUBSCRIBE` | `PUNSUBSCRIBE [pattern [pattern ...]]`—取消订阅符合指定模式的频道。 | 订阅者订阅了频道之后,发布者向频道发送字符串消息会被所有订阅者接收到。 @@ -487,13 +456,13 @@ Redis 2.8 版本以后的复制功能基于 `PSYNC` 命令实现。`PSYNC` 命 从服务器通过向主服务器发送命令 `REPLCONF ACK ` 来进行心跳检测,以及命令丢失检测。 -> :bulb: 更详细的特性及原理说明请参考:[Redis 复制](redis-replication.md) +> :bulb: 更详细的特性及原理说明请参考:[Redis 复制](05.Redis复制.md) ## 十、Redis 哨兵 Sentinel(哨兵)可以监听主服务器,并在主服务器进入下线状态时,自动从从服务器中选举出新的主服务器。 -> 💡 更详细的特性及原理说明请参考:[Redis 哨兵](redis-sentinel.md) +> 💡 更详细的特性及原理说明请参考:[Redis 哨兵](06.Redis哨兵.md) ## 十一、Redis 集群 @@ -519,7 +488,7 @@ redis 官方推荐的 Java Redis Client: ## 扩展阅读 -> 💡 Redis 常用于分布式缓存,有关缓存的特性和原理请参考:[缓存基本原理](https://dunwu.github.io/blog/design/theory/cache-theory/) +> 💡 Redis 常用于分布式缓存,有关缓存的特性和原理请参考:[缓存基本原理](https://dunwu.github.io/design/distributed/分布式缓存.html) ## 参考资料 @@ -539,4 +508,4 @@ redis 官方推荐的 Java Redis Client: - [redisson 官方文档(中文,略有滞后)](https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95) - [redisson 官方文档(英文)](https://github.com/redisson/redisson/wiki/Table-of-Content) - [CRUG | Redisson PRO vs. Jedis: Which Is Faster? 翻译](https://www.jianshu.com/p/82f0d5abb002) - - [redis 分布锁 Redisson 性能测试](https://blog.csdn.net/everlasting_188/article/details/51073505) + - [redis 分布锁 Redisson 性能测试](https://blog.csdn.net/everlasting_188/article/details/51073505) \ No newline at end of file diff --git a/docs/nosql/redis/redis-datatype.md "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/03.Redis\346\225\260\346\215\256\347\261\273\345\236\213\345\222\214\345\272\224\347\224\250.md" similarity index 94% rename from docs/nosql/redis/redis-datatype.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/03.Redis\346\225\260\346\215\256\347\261\273\345\236\213\345\222\214\345\272\224\347\224\250.md" index f7e89450..05ab7421 100644 --- a/docs/nosql/redis/redis-datatype.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/03.Redis\346\225\260\346\215\256\347\261\273\345\236\213\345\222\214\345\272\224\347\224\250.md" @@ -1,43 +1,27 @@ -# Redis 数据类型 +--- +title: Redis 数据类型和应用 +date: 2020-06-24 10:45:38 +categories: + - 数据库 + - KV数据库 + - Redis +tags: + - 数据库 + - KV数据库 + - Redis + - 数据类型 +permalink: /pages/ed757c/ +--- + +# Redis 数据类型和应用 > Redis 提供了多种数据类型,每种数据类型有丰富的命令支持。 > > 使用 Redis ,不仅要了解其数据类型的特性,还需要根据业务场景,灵活的、高效的使用其数据类型来建模。 - - -- [一、Redis 基本数据类型](#一redis-基本数据类型) - - [STRING](#string) - - [HASH](#hash) - - [LIST](#list) - - [SET](#set) - - [ZSET](#zset) - - [通用命令](#通用命令) -- [二、Redis 高级数据类型](#二redis-高级数据类型) - - [BitMap](#bitmap) - - [HyperLogLog](#hyperloglog) - - [GEO](#geo) -- [三、Redis 数据类型应用](#三redis-数据类型应用) - - [案例-最受欢迎文章](#案例-最受欢迎文章) - - [案例-管理令牌](#案例-管理令牌) - - [案例-购物车](#案例-购物车) - - [案例-页面缓存](#案例-页面缓存) - - [案例-数据行缓存](#案例-数据行缓存) - - [案例-网页分析](#案例-网页分析) - - [案例-记录日志](#案例-记录日志) - - [案例-统计数据](#案例-统计数据) - - [案例-查找 IP 所属地](#案例-查找-ip-所属地) - - [案例-服务的发现与配置](#案例-服务的发现与配置) - - [案例-自动补全](#案例-自动补全) - - [案例-广告定向](#案例-广告定向) - - [案例-职位搜索](#案例-职位搜索) -- [参考资料](#参考资料) - - - ## 一、Redis 基本数据类型 -![Redis 数据类型](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200226113813.png) +![Redis 数据类型](https://raw.githubusercontent.com/dunwu/images/master/snap/20200226113813.png) | 数据类型 | 可以存储的值 | 操作 | | -------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------- | @@ -52,7 +36,7 @@ ### STRING
- +
**适用场景:缓存、计数器、共享 Session** @@ -84,7 +68,7 @@ OK ### HASH
- +
**适用场景:存储结构化数据**,如一个对象:用户信息、产品信息等。 @@ -129,7 +113,7 @@ OK ### LIST
- +
**适用场景:用于存储列表型数据**。如:粉丝列表、商品列表等。 @@ -173,7 +157,7 @@ OK ### SET
- +
**适用场景:用于存储去重的列表型数据**。 @@ -219,7 +203,7 @@ OK ### ZSET
- +
适用场景:由于可以设置 score,且不重复。**适合用于存储各种排行数据**,如:按评分排序的有序商品集合、按时间排序的有序文章集合。 @@ -270,8 +254,8 @@ OK Redis 的 `SORT` 命令可以对 `LIST`、`SET`、`ZSET` 进行排序。 -| 命令 | 描述 | -| ------ | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | +| 命令 | 描述 | +| ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `SORT` | `SORT source-key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC | DESC] [ALPHA] [STORE dest-key]`—根据给定选项,对输入 `LIST`、`SET`、`ZSET` 进行排序,然后返回或存储排序的结果。 | 示例: @@ -448,7 +432,7 @@ redis> PFCOUNT databases # 估计数量增一 使用 `HASH` 类型存储文章信息。其中:key 是文章 ID;field 是文章的属性 key;value 是属性对应值。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200225143038.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200225143038.jpg) 操作: @@ -460,7 +444,7 @@ redis> PFCOUNT databases # 估计数量增一 使用 `ZSET` 类型分别存储按照时间排序和按照评分排序的文章 ID 集合。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200225145742.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200225145742.jpg) 操作: @@ -470,7 +454,7 @@ redis> PFCOUNT databases # 估计数量增一 (3)为了防止重复投票,使用 `SET` 类型记录每篇文章 ID 对应的投票集合。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200225150105.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200225150105.jpg) 操作: @@ -479,7 +463,7 @@ redis> PFCOUNT databases # 估计数量增一 (4)假设 user:115423 给 article:100408 投票,分别需要高更新评分排序集合以及投票集合。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200225150138.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200225150138.jpg) 当需要对一篇文章投票时,程序需要用 ZSCORE 命令检查记录文章发布时间的有序集合,判断文章的发布时间是否超过投票有效期(比如:一星期)。 @@ -595,7 +579,7 @@ redis> PFCOUNT databases # 估计数量增一 取出群组里的文章: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200225214210.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200225214210.jpg) - 通过对存储群组文章的集合和存储文章评分的有序集合执行 `ZINTERSTORE` 命令,可以得到按照文章评分排序的群组文章。 - 通过对存储群组文章的集合和存储文章发布时间的有序集合执行 `ZINTERSTORE` 命令,可以得到按照文章发布时间排序的群组文章。 @@ -1200,4 +1184,4 @@ SDIFF interviewee:002 job:003 - [Redis 命令参考](http://redisdoc.com/) - **文章** - [一看就懂系列之 详解 redis 的 bitmap 在亿级项目中的应用](https://blog.csdn.net/u011957758/article/details/74783347) - - [Fast, easy, realtime metrics using Redis bitmaps](http://blog.getspool.com/2011/11/29/fast-easy-realtime-metrics-using-redis-bitmaps/) + - [Fast, easy, realtime metrics using Redis bitmaps](http://blog.getspool.com/2011/11/29/fast-easy-realtime-metrics-using-redis-bitmaps/) \ No newline at end of file diff --git a/docs/nosql/redis/redis-persistence.md "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/04.Redis\346\214\201\344\271\205\345\214\226.md" similarity index 93% rename from docs/nosql/redis/redis-persistence.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/04.Redis\346\214\201\344\271\205\345\214\226.md" index 3fed2369..cac2b4c1 100644 --- a/docs/nosql/redis/redis-persistence.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/04.Redis\346\214\201\344\271\205\345\214\226.md" @@ -1,3 +1,18 @@ +--- +title: Redis 持久化 +date: 2020-06-24 10:45:38 +categories: + - 数据库 + - KV数据库 + - Redis +tags: + - 数据库 + - KV数据库 + - Redis + - 持久化 +permalink: /pages/4de901/ +--- + # Redis 持久化 > Redis 支持持久化,即把数据存储到硬盘中。 @@ -13,32 +28,6 @@ > > Redis 提供了两种持久方式:RDB 和 AOF。你可以同时开启两种持久化方式。在这种情况下, 当 redis 重启的时候会优先载入 AOF 文件来恢复原始的数据,因为在通常情况下 AOF 文件保存的数据集要比 RDB 文件保存的数据集要完整。 - - -- [一、RDB](#一rdb) - - [RDB 简介](#rdb-简介) - - [RDB 的创建](#rdb-的创建) - - [RDB 的载入](#rdb-的载入) - - [RDB 的文件结构](#rdb-的文件结构) - - [RDB 的配置](#rdb-的配置) -- [二、AOF](#二aof) - - [AOF 简介](#aof-简介) - - [AOF 的创建](#aof-的创建) - - [AOF 的载入](#aof-的载入) - - [AOF 的重写](#aof-的重写) - - [AOF 的配置](#aof-的配置) -- [三、RDB 和 AOF](#三rdb-和-aof) - - [如何选择持久化](#如何选择持久化) - - [RDB 切换为 AOF](#rdb-切换为-aof) - - [AOF 和 RDB 的相互作用](#aof-和-rdb-的相互作用) -- [四、Redis 备份](#四redis-备份) - - [备份过程](#备份过程) - - [容灾备份](#容灾备份) -- [五、要点总结](#五要点总结) -- [参考资料](#参考资料) - - - ## 一、RDB ### RDB 简介 @@ -101,7 +90,7 @@ RDB 文件是一个经过压缩的二进制文件,由多个部分组成。 对于不同类型(STRING、HASH、LIST、SET、SORTED SET)的键值对,RDB 文件会使用不同的方式来保存它们。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/redis/redis-rdb-structure.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/redis/redis-rdb-structure.png) Redis 本身提供了一个 RDB 文件检查工具 redis-check-dump。 @@ -181,7 +170,7 @@ AOF 载入过程如下: 6. 载入完毕。
- +
### AOF 的重写 @@ -203,7 +192,7 @@ AOF 重写并非读取和分析现有 AOF 文件的内容,而是直接从数 - 由于彼此不是在同一个进程中工作,AOF 重写不影响 AOF 写入和同步。当子进程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新旧两个 AOF 文件所保存的数据库状态一致。 - 最后,服务器用新的 AOF 文件替换就的 AOF 文件,以此来完成 AOF 重写操作。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200130153716.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200130153716.png) 可以通过设置 `auto-aof-rewrite-percentage` 和 `auto-aof-rewrite-min-size`,使得 Redis 在满足条件时,自动执行 `BGREWRITEAOF`。 @@ -295,7 +284,7 @@ Redis 的容灾备份基本上就是对数据进行备份,并将这些备份 ## 五、要点总结 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200224214047.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200224214047.png) ## 参考资料 @@ -307,4 +296,4 @@ Redis 的容灾备份基本上就是对数据进行备份,并将这些备份 - [《Redis 实战》](https://item.jd.com/11791607.html) - [《Redis 设计与实现》](https://item.jd.com/11486101.html) - **教程** - - [Redis 命令参考](http://redisdoc.com/) + - [Redis 命令参考](http://redisdoc.com/) \ No newline at end of file diff --git a/docs/nosql/redis/redis-replication.md "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/05.Redis\345\244\215\345\210\266.md" similarity index 87% rename from docs/nosql/redis/redis-replication.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/05.Redis\345\244\215\345\210\266.md" index 34e216ef..15c4756c 100644 --- a/docs/nosql/redis/redis-replication.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/05.Redis\345\244\215\345\210\266.md" @@ -1,37 +1,25 @@ +--- +title: Redis 复制 +date: 2020-06-24 10:45:38 +categories: + - 数据库 + - KV数据库 + - Redis +tags: + - 数据库 + - KV数据库 + - Redis + - 复制 +permalink: /pages/379cd8/ +--- + # Redis 复制 > 在 Redis 中,**可以通过执行 `SLAVEOF` 命令或设置 `slaveof` 选项,让一个服务器去复制(replicate)另一个服务器**,其中,后者叫主服务器(master),前者叫从服务器(slave)。 > > Redis 2.8 以前的复制不能高效处理断线后重复制的情况,而 Redis 2.8 新添的部分重同步可以解决这个问题。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200712182603.png) - - - -- [一、复制简介](#一复制简介) -- [二、旧版复制](#二旧版复制) - - [同步](#同步) - - [命令传播](#命令传播) - - [旧版复制的缺陷](#旧版复制的缺陷) -- [三、新版复制](#三新版复制) - - [部分重同步](#部分重同步) - - [PSYNC 命令](#psync-命令) -- [四、心跳检测](#四心跳检测) - - [检测主从连接状态](#检测主从连接状态) - - [辅助实现 min-slaves 选项](#辅助实现-min-slaves-选项) - - [检测命令丢失](#检测命令丢失) -- [五、复制的流程](#五复制的流程) - - [步骤 1. 设置主从服务器](#步骤-1-设置主从服务器) - - [步骤 2. 主从服务器建立 TCP 连接。](#步骤-2-主从服务器建立-tcp-连接) - - [步骤 3. 发送 PING 检查通信状态。](#步骤-3-发送-ping-检查通信状态) - - [步骤 4. 身份验证。](#步骤-4-身份验证) - - [步骤 5. 发送端口信息。](#步骤-5-发送端口信息) - - [步骤 6. 同步。](#步骤-6-同步) - - [步骤 7. 命令传播。](#步骤-7-命令传播) -- [六、复制的配置项](#六复制的配置项) -- [参考资料](#参考资料) - - +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200712182603.png) ## 一、复制简介 @@ -68,7 +56,7 @@ Redis 的复制功能分为同步(sync)和命令传播(command propagate 3. 主服务器执行 `BGSAVE` 完毕后,主服务器会将生成的 RDB 文件发送给从服务器。从服务器接收并载入 RDB 文件,更新自己的数据库状态。 4. 主服务器将记录在缓冲区中的所有写命令发送给从服务器,从服务器执行这些写命令,更新自己的数据库状态。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200224220353.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200224220353.png) ### 命令传播 @@ -113,7 +101,7 @@ Redis 的复制功能分为同步(sync)和命令传播(command propagate - 如果主从服务器的复制偏移量相同,则说明二者的数据库状态一致; - 反之,则说明二者的数据库状态不一致。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/redis/redis-replication-offset.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/redis/redis-replication-offset.png) #### 复制积压缓冲区 @@ -160,7 +148,7 @@ Redis 的复制功能分为同步(sync)和命令传播(command propagate - 假如主从服务器的 **master run id 相同**,并且**指定的偏移量(offset)在内存缓冲区中还有效**,复制就会从上次中断的点开始继续。 - 如果其中一个条件不满足,就会进行完全重新同步(在 2.8 版本之前就是直接进行完全重新同步)。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/redis/redis-psync-workflow.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/redis/redis-psync-workflow.png) ## 四、心跳检测 @@ -199,7 +187,7 @@ min-slaves-max-lag 10 ### 检测命令丢失 -如果因为网络故障,主服务传播给从服务器的写命令丢失,那么从服务器定时向主服务器发送 `REPLCONF ACK` 命令时,主服务器将发觉从服务器的复制偏移量少于自己的。然后,主服务器就会根据从服务器提交的复制偏移量,在复制积压缓冲区中找到从服务器缺少的数据,并将这些数据重新发送给从服务器。 +如果因为网络故障,主服务传播给从服务器的写命令丢失,那么从服务器定时向主服务器发送 `REPLCONF ACK` 命令时,主服务器将发觉从服务器的复制偏移量少于自己的。然后,主服务器就会根据从服务器提交的复制偏移量,在复制积压缓冲区中找到从服务器缺少的数据,并将这些数据重新发送给从服务器。 ## 五、复制的流程 @@ -307,4 +295,4 @@ REPLCONF ACK - [《Redis 实战》](https://item.jd.com/11791607.html) - [《Redis 设计与实现》](https://item.jd.com/11486101.html) - **教程** - - [Redis 命令参考](http://redisdoc.com/) + - [Redis 命令参考](http://redisdoc.com/) \ No newline at end of file diff --git a/docs/nosql/redis/redis-sentinel.md "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/06.Redis\345\223\250\345\205\265.md" similarity index 91% rename from docs/nosql/redis/redis-sentinel.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/06.Redis\345\223\250\345\205\265.md" index eb2f3529..88e6b652 100644 --- a/docs/nosql/redis/redis-sentinel.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/06.Redis\345\223\250\345\205\265.md" @@ -1,32 +1,31 @@ +--- +title: Redis 哨兵 +date: 2020-06-24 10:45:38 +categories: + - 数据库 + - KV数据库 + - Redis +tags: + - 数据库 + - KV数据库 + - Redis + - 哨兵 +permalink: /pages/615afe/ +--- + # Redis 哨兵 > Redis 哨兵(Sentinel)是 Redis 的**高可用性**(Hight Availability)解决方案。 > -> Redis 哨兵是 [Raft 算法](https://github.com/dunwu/blog/blob/master/source/_posts/theory/raft.md) 的具体实现。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200713072747.png) - - - -- [一、哨兵简介](#一哨兵简介) -- [二、启动哨兵](#二启动哨兵) -- [三、监控](#三监控) - - [检测服务器状态](#检测服务器状态) - - [获取服务器信息](#获取服务器信息) -- [四、通知](#四通知) - - [向服务器发送消息](#向服务器发送消息) - - [接收服务器的消息](#接收服务器的消息) -- [五、选举 Leader](#五选举-leader) -- [六、故障转移](#六故障转移) -- [参考资料](#参考资料) +> Redis 哨兵是 [Raft 算法](https://dunwu.github.io/blog/pages/4907dc/) 的具体实现。 - +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200713072747.png) ## 一、哨兵简介 Redis 哨兵(Sentinel)是 Redis 的**高可用性**(Hight Availability)解决方案:由一个或多个 Sentinel 实例组成的 Sentinel 系统可以监视任意多个主服务器,以及这些主服务器的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200131135847.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200131135847.png) Sentinel 的主要功能如下: @@ -86,7 +85,7 @@ Sentinel 模式下 Redis 服务器只支持 `PING`、`SENTINEL`、`INFO`、`SUBS 对于每个与 Sentinel 连接的服务器,Sentinel 既会向服务器的 `__sentinel__:hello` 频道发送消息,也会订阅服务器的 `__sentinel__:hello` 频道的消息。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200131153842.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200131153842.png) ### 向服务器发送消息 @@ -108,7 +107,7 @@ Sentinel 对 `__sentinel__:hello` 频道的订阅会一直持续到 Sentinel 与 > Redis Sentinel 系统选举 Leader 的算法是 [Raft](https://ramcloud.atlassian.net/wiki/download/attachments/6586375/raft.pdf) 的实现。 > -> Raft 是一种共识性算法,想了解其原理,可以参考 [深入剖析共识性算法 Raft](https://github.com/dunwu/blog/blob/master/source/_posts/theory/raft.md)。 +> Raft 是一种共识性算法,想了解其原理,可以参考 [深入剖析共识性算法 Raft](https://dunwu.github.io/blog/pages/4907dc/)。 **当一个主服务器被判断为客观下线时,监视这个下线主服务器的各个 Sentinel 会进行协商,选举出一个领头的 Sentinel,并由领头 Sentinel 对下线主服务器执行故障转移操作**。 @@ -182,4 +181,4 @@ Sentinel Leader 将旧的主服务器标记为从服务器。当旧的主服务 - [Redis 命令参考](http://redisdoc.com/) - **文章** - [渐进式解析 Redis 源码 - 哨兵 sentinel](http://www.web-lovers.com/redis-source-sentinel.html) - - [深入剖析 Redis 系列(二) - Redis 哨兵模式与高可用集群](https://juejin.im/post/5b7d226a6fb9a01a1e01ff64) + - [深入剖析 Redis 系列(二) - Redis 哨兵模式与高可用集群](https://juejin.im/post/5b7d226a6fb9a01a1e01ff64) \ No newline at end of file diff --git a/docs/nosql/redis/redis-cluster.md "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/07.Redis\351\233\206\347\276\244.md" similarity index 52% rename from docs/nosql/redis/redis-cluster.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/07.Redis\351\233\206\347\276\244.md" index 3907ac14..c8e0a89c 100644 --- a/docs/nosql/redis/redis-cluster.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/07.Redis\351\233\206\347\276\244.md" @@ -1,42 +1,33 @@ +--- +title: Redis 集群 +date: 2020-06-24 10:45:38 +categories: + - 数据库 + - KV数据库 + - Redis +tags: + - 数据库 + - KV数据库 + - Redis + - 集群 +permalink: /pages/77dfbe/ +--- + # Redis 集群 > **[Redis 集群(Redis Cluster)](https://redis.io/topics/cluster-tutorial) 是 Redis 官方提供的分布式数据库方案**。 > > 既然是分布式,自然具备分布式系统的基本特性:可扩展、高可用、一致性。 > -> - Redis 集群通过划分 hash 槽来分片,进行数据分享。 +> - Redis 集群通过划分 hash 槽来分区,进行数据分享。 > - Redis 集群采用主从模型,提供复制和故障转移功能,来保证 Redis 集群的高可用。 > - 根据 CAP 理论,Consistency、Availability、Partition tolerance 三者不可兼得,而 Redis 集群的选择是 AP。Redis 集群节点间采用异步通信方式,不保证强一致性,尽力达到最终一致性。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200713100613.png) - - - -- [一、Redis Cluster 分区](#一redis-cluster-分区) - - [集群节点](#集群节点) - - [分配 Hash 槽](#分配-hash-槽) - - [寻址](#寻址) - - [重新分片](#重新分片) - - [ASK 错误](#ask-错误) -- [二、Redis Cluster 故障转移](#二redis-cluster-故障转移) - - [复制](#复制) - - [故障检测](#故障检测) - - [故障转移](#故障转移) -- [三、Redis Cluster 通信](#三redis-cluster-通信) -- [四、Redis Cluster 应用](#四redis-cluster-应用) - - [集群限制](#集群限制) - - [集群配置](#集群配置) -- [五、其他方案](#五其他方案) - - [客户端分区方案](#客户端分区方案) - - [代理分区方案](#代理分区方案) - - [查询路由方案](#查询路由方案) -- [参考资料](#参考资料) - - +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200713100613.png) -## 一、Redis Cluster 分区 +## 1. Redis Cluster 分区 -### 集群节点 +### 1.1. 集群节点 Redis 集群由多个节点组成,节点刚启动时,彼此是相互独立的。**节点通过握手( `CLUSTER MEET` 命令)来将其他节点添加到自己所处的集群中**。 @@ -46,11 +37,11 @@ Redis 集群由多个节点组成,节点刚启动时,彼此是相互独立 Redis 集群节点分为主节点(master)和从节点(slave),其中主节点用于处理槽,而从节点则用于复制某个主节点,并在被复制的主节点下线时,代替下线主节点继续处理命令请求。 -### 分配 Hash 槽 +### 1.2. 分配 Hash 槽 分布式存储需要解决的首要问题是把 **整个数据集** 按照 **分区规则** 映射到 **多个节点** 的问题,即把 **数据集** 划分到 **多个节点** 上,每个节点负责 **整体数据** 的一个 **子集**。 -**Redis 集群通过划分 hash 槽来将数据分区**。Redis 集群通过分片的方式来保存数据库的键值对:**集群的整个数据库被分为 16384 个哈希槽(slot)**,数据库中的每个键都属于这 16384 个槽的其中一个,集群中的每个节点可以处理 0 个或最多 16384 个槽。**如果数据库中有任何一个槽没有得到处理,那么集群处于下线状态**。 +**Redis 集群通过划分 hash 槽来将数据分区**。Redis 集群通过分区的方式来保存数据库的键值对:**集群的整个数据库被分为 16384 个哈希槽(slot)**,数据库中的每个键都属于这 16384 个槽的其中一个,集群中的每个节点可以处理 0 个或最多 16384 个槽。**如果数据库中有任何一个槽没有得到处理,那么集群处于下线状态**。 通过向节点发送 [`CLUSTER ADDSLOTS`](https://redis.io/commands/cluster-addslots) 命令,可以将一个或多个槽指派给节点负责。 @@ -65,19 +56,19 @@ OK - 节点B存储的哈希槽范围是:5501 – 11000 - 节点C存储的哈希槽范围是:11001 – 16384 -### 寻址 +### 1.3. 寻址 当客户端向节点发送与数据库键有关的命令时,接受命令的节点会**计算出命令要处理的数据库属于哪个槽**,并**检查这个槽是否指派给了自己**: - 如果键所在的槽正好指派给了当前节点,那么当前节点直接执行命令。 - 如果键所在的槽没有指派给当前节点,那么节点会向客户端返回一个 MOVED 错误,指引客户端重定向至正确的节点。 -#### 计算键属于哪个槽 +#### 1.3.1. 计算键属于哪个槽 决定一个 key 应该分配到那个槽的算法是:**计算该 key 的 CRC16 结果再模 16834**。 ``` -slot = CRC16(KEY) & 16384 +HASH_SLOT = CRC16(KEY) mod 16384 ``` 当节点计算出 key 所属的槽为 i 之后,节点会根据以下条件判断槽是否由自己负责: @@ -86,7 +77,7 @@ slot = CRC16(KEY) & 16384 clusterState.slots[i] == clusterState.myself ``` -#### MOVED 错误 +#### 1.3.2. MOVED 错误 当节点发现键所在的槽并非自己负责处理的时候,节点就会向客户端返回一个 `MOVED` 错误,指引客户端转向正在负责槽的节点。 @@ -98,33 +89,33 @@ MOVED : > 个人理解:MOVED 这种操作有点类似 HTTP 协议中的重定向。 -### 重新分片 +### 1.4. 重新分区 -Redis 集群的**重新分片操作可以将任意数量的已经指派给某个节点(源节点)的槽改为指派给另一个节点(目标节点),并且相关槽所属的键值对也会从源节点被移动到目标节点**。 +Redis 集群的**重新分区操作可以将任意数量的已经指派给某个节点(源节点)的槽改为指派给另一个节点(目标节点),并且相关槽所属的键值对也会从源节点被移动到目标节点**。 -重新分片操作**可以在线进**行,在重新分片的过程中,集群不需要下线,并且源节点和目标节点都可以继续处理命令请求。 +重新分区操作**可以在线进**行,在重新分区的过程中,集群不需要下线,并且源节点和目标节点都可以继续处理命令请求。 -Redis 集群的重新分片操作由 Redis 集群管理软件 **redis-trib** 负责执行的,redis-trib 通过向源节点和目标节点发送命令来进行重新分片操作。 +Redis 集群的重新分区操作由 Redis 集群管理软件 **redis-trib** 负责执行的,redis-trib 通过向源节点和目标节点发送命令来进行重新分区操作。 -重新分片的实现原理如下图所示: +重新分区的实现原理如下图所示: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/redis/redis-cluster-trib.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/redis/redis-cluster-trib.png) -### ASK 错误 +### 1.5. ASK 错误 -`ASK` 错误与 `MOVED` 的区别在于:**ASK 错误只是两个节点在迁移槽的过程中使用的一种临时措施**,在客户端收到关于槽 i 的 ASK 错误之后,客户端只会在接下来的一次命令请求中将关于槽 i 的命令请求发送至 ASK 错误所指示的节点,但这种转向不会对客户端今后发送关于槽 i 的命令请求产生任何影响,客户端仍然会将关于槽 i 的命令请求发送至目前负责处理槽 i 的节点,除非 ASK 错误再次出现。 +`ASK` 错误与 `MOVED` 的区别在于:**ASK 错误只是两个节点在迁移槽的过程中使用的一种临时措施**,在客户端收到关于槽 X 的 ASK 错误之后,客户端只会在接下来的一次命令请求中将关于槽 X 的命令请求发送至 ASK 错误所指示的节点,但这种转向不会对客户端今后发送关于槽 X 的命令请求产生任何影响,客户端仍然会将关于槽 X 的命令请求发送至目前负责处理槽 X 的节点,除非 ASK 错误再次出现。 判断 ASK 错误的过程如下图所示: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/redis/redis-ask.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/redis/redis-ask.png) -## 二、Redis Cluster 故障转移 +## 2. Redis Cluster 故障转移 -### 复制 +### 2.1. 复制 -Redis 复制机制可以参考:[Redis 复制](redis-replication.md) +Redis 复制机制可以参考:[Redis 复制](docs/05.KV数据库/01.Redis/05.Redis复制.md) -### 故障检测 +### 2.2. 故障检测 **集群中每个节点都会定期向集群中的其他节点发送 PING 消息,以此来检测对方是否在线**。 @@ -136,18 +127,18 @@ Redis 复制机制可以参考:[Redis 复制](redis-replication.md) - 疑似下线状态(PFAIL),即在规定的时间内,没有应答 PING 消息; -### 故障转移 +### 2.3. 故障转移 1. 下线主节点的所有从节点中,会有一个从节点被选中。 2. 被选中的从节点会执行 `SLAVEOF no one` 命令,成为新的主节点。 3. 新的主节点会撤销所有对已下线主节点的槽指派,并将这些槽全部指派给自己。 4. 新的主节点向集群广播一条 PONG 消息,告知其他节点这个从节点已变成主节点。 -#### 选举新的主节点 +### 2.4. 选举新的主节点 Redis 集群选举新的主节点流程基于[共识算法:Raft](https://www.jianshu.com/p/8e4bbe7e276c) -## 三、Redis Cluster 通信 +## 3. Redis Cluster 通信 集群中的节点通过发送和接收消息来进行通信。 @@ -159,33 +150,35 @@ Redis 集群节点发送的消息主要有以下五种: - `FAIL` - 当一个主节点 A 判断另一个主节点 B 已经进入 FAIL 状态时,节点 A 会向集群广播一条关于节点 B 的 FAIL 消息,所有收到这条消息的节点都会立即将节点 B 标记为已下线。 - `PUBLISH` - 当节点收到一个 PUBLISH 命令时,节点会执行这个命令,并向集群广播一条 PUBLISH 消息,所有接受到这条消息的节点都会执行相同的 PUBLISH 命令。 -## 四、Redis Cluster 应用 +## 4. Redis Cluster 应用 -### 集群限制 +### 4.1. 集群功能限制 -`Redis` 集群相对 **单机** 在功能上存在一些限制,需要 **开发人员** 提前了解,在使用时做好规避。 +Redis 集群相对 **单机**,存在一些功能限制,需要 **开发人员** 提前了解,在使用时做好规避。 -- `key` **批量操作** 支持有限。 +- `key` **批量操作** 支持有限:类似 `mset`、`mget` 操作,目前只支持对具有相同 `slot` 值的 `key` 执行 **批量操作**。对于 **映射为不同** `slot` 值的 `key` 由于执行 `mget`、`mget` 等操作可能存在于多个节点上,因此不被支持。 -类似 `mset`、`mget` 操作,目前只支持对具有相同 `slot` 值的 `key` 执行 **批量操作**。对于 **映射为不同** `slot` 值的 `key` 由于执行 `mget`、`mget` 等操作可能存在于多个节点上,因此不被支持。 +- `key` **事务操作** 支持有限:只支持 **多** `key` 在 **同一节点上** 的 **事务操作**,当多个 `key` 分布在 **不同** 的节点上时 **无法** 使用事务功能。 -- `key` **事务操作** 支持有限。 +- `key` 作为 **数据分区** 的最小粒度,不能将一个 **大的键值** 对象如 `hash`、`list` 等映射到 **不同的节点**。 -只支持 **多** `key` 在 **同一节点上** 的 **事务操作**,当多个 `key` 分布在 **不同** 的节点上时 **无法** 使用事务功能。 +- 不支持 **多数据库空间**:**单机** 下的 Redis 可以支持 `16` 个数据库(`db0 ~ db15`),**集群模式** 下只能使用 **一个** 数据库空间,即 `db0`。 -- `key` 作为 **数据分区** 的最小粒度 +- **复制结构** 只支持一层:**从节点** 只能复制 **主节点**,不支持 **嵌套树状复制** 结构。 -不能将一个 **大的键值** 对象如 `hash`、`list` 等映射到 **不同的节点**。 +### 4.2. 集群规模限制 -- 不支持 **多数据库空间** +Redis Cluster 的优点是易于使用。分区、主从复制、弹性扩容这些功能都可以做到自动化,通过简单的部署就可以获得一个大容量、高可靠、高可用的 Redis 集群,并且对于应用来说,近乎于是透明的。 -**单机** 下的 `Redis` 可以支持 `16` 个数据库(`db0 ~ db15`),**集群模式** 下只能使用 **一个** 数据库空间,即 `db0`。 +所以,**Redis Cluster 非常适合构建中小规模 Redis 集群**,这里的中小规模指的是,大概几个到几十个节点这样规模的 Redis 集群。 -- **复制结构** 只支持一层 +但是 Redis Cluster 不太适合构建超大规模集群,主要原因是,它采用了去中心化的设计。 -**从节点** 只能复制 **主节点**,不支持 **嵌套树状复制** 结构。 +Redis 的每个节点上,都保存了所有槽和节点的映射关系表,客户端可以访问任意一个节点,再通过重定向命令,找到数据所在的那个节点。那么,这个映射关系表是如何更新的呢?Redis Cluster 采用了一种去中心化的流言 (Gossip) 协议来传播集群配置的变化。 -### 集群配置 +Gossip 协议的优点是去中心化;缺点是传播速度慢,并且是集群规模越大,传播的越慢。 + +### 4.3. 集群配置 我们后面会部署一个 Redis 集群作为例子,在那之前,先介绍一下集群在 redis.conf 中的参数。 @@ -194,86 +187,75 @@ Redis 集群节点发送的消息主要有以下五种: - **cluster-node-timeout** `` - 这是集群中的节点能够失联的最大时间,超过这个时间,该节点就会被认为故障。如果主节点超过这个时间还是不可达,则用它的从节点将启动故障迁移,升级成主节点。注意,任何一个节点在这个时间之内如果还是没有连上大部分的主节点,则此节点将停止接收任何请求。 - **cluster-slave-validity-factor** `` - 如果设置成0,则无论从节点与主节点失联多久,从节点都会尝试升级成主节点。如果设置成正数,则 cluster-node-timeout 乘以 cluster-slave-validity-factor 得到的时间,是从节点与主节点失联后,此从节点数据有效的最长时间,超过这个时间,从节点不会启动故障迁移。假设 cluster-node-timeout=5,cluster-slave-validity-factor=10,则如果从节点跟主节点失联超过 50 秒,此从节点不能成为主节点。注意,如果此参数配置为非 0,将可能出现由于某主节点失联却没有从节点能顶上的情况,从而导致集群不能正常工作,在这种情况下,只有等到原来的主节点重新回归到集群,集群才恢复运作。 - **cluster-migration-barrier** `` - 主节点需要的最小从节点数,只有达到这个数,主节点失败时,它从节点才会进行迁移。更详细介绍可以看本教程后面关于副本迁移到部分。 -- **cluster-require-full-coverage** - 在部分 key 所在的节点不可用时,如果此参数设置为”yes”(默认值), 则整个集群停止接受操作;如果此参数设置为”no”,则集群依然为可达节点上的 key 提供读操作。 - -## 五、其他方案 +- **cluster-require-full-coverage** `` - 在部分 key 所在的节点不可用时,如果此参数设置为”yes”(默认值), 则整个集群停止接受操作;如果此参数设置为”no”,则集群依然为可达节点上的 key 提供读操作。 -### 客户端分区方案 +## 5. 其他 Redis 集群方案 -**客户端** 就已经决定数据会被 **存储** 到哪个 `redis` 节点或者从哪个 `redis` 节点 **读取数据**。其主要思想是采用 **哈希算法** 将 `Redis` 数据的 `key` 进行散列,通过 `hash` 函数,特定的 `key`会 **映射** 到特定的 `Redis` 节点上。 +Redis Cluster 不太适合用于大规模集群,所以,如果要构建超大 Redis 集群,需要选择替代方案。一般有三种方案类型: -![img](https://user-gold-cdn.xitu.io/2018/9/4/165a4f9e74a09b36?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) +- 客户端分区方案 +- 代理分区方案 +- 查询路由方案 -**客户端分区方案** 的代表为 `Redis Sharding`,`Redis Sharding` 是 `Redis Cluster` 出来之前,业界普遍使用的 `Redis` **多实例集群** 方法。`Java` 的 `Redis` 客户端驱动库 `Jedis`,支持 `Redis Sharding` 功能,即 `ShardedJedis` 以及 **结合缓存池** 的 `ShardedJedisPool`。 +### 5.1. 客户端分区方案 -- **优点** +**客户端** 就已经决定数据会被 **存储** 到哪个 Redis 节点或者从哪个 Redis 节点 **读取数据**。其主要思想是采用 **哈希算法** 将 Redis 数据的 `key` 进行散列,通过 `hash` 函数,特定的 `key`会 **映射** 到特定的 Redis 节点上。 -不使用 **第三方中间件**,**分区逻辑** 可控,**配置** 简单,节点之间无关联,容易 **线性扩展**,灵活性强。 +**客户端分区方案** 的代表为 Redis Sharding,Redis Sharding 是 Redis Cluster 出来之前,业界普遍使用的 Redis **多实例集群** 方法。Java 的 Redis 客户端驱动库 [**Jedis**](https://github.com/redis/jedis),支持 Redis Sharding 功能,即 ShardedJedis 以及 **结合缓存池** 的 ShardedJedisPool。 -- **缺点** +- **优点**:不使用 **第三方中间件**,**分区逻辑** 可控,**配置** 简单,节点之间无关联,容易 **线性扩展**,灵活性强。 -**客户端** 无法 **动态增删** 服务节点,客户端需要自行维护 **分发逻辑**,客户端之间 **无连接共享**,会造成 **连接浪费**。 +- **缺点**:**客户端** 无法 **动态增删** 服务节点,客户端需要自行维护 **分发逻辑**,客户端之间 **无连接共享**,会造成 **连接浪费**。 -### 代理分区方案 +### 5.2. 代理分区方案 **客户端** 发送请求到一个 **代理组件**,**代理** 解析 **客户端** 的数据,并将请求转发至正确的节点,最后将结果回复给客户端。 - **优点**:简化 **客户端** 的分布式逻辑,**客户端** 透明接入,切换成本低,代理的 **转发** 和 **存储** 分离。 - **缺点**:多了一层 **代理层**,加重了 **架构部署复杂度** 和 **性能损耗**。 -![img](https://user-gold-cdn.xitu.io/2018/9/4/165a4f9e6f8b3a44?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) - -**代理分区** 主流实现的有方案有 `Twemproxy` 和 `Codis`。 +**代理分区** 主流实现的有方案有 **[Twemproxy](https://github.com/twitter/twemproxy)** 和 [**Codis**](https://github.com/CodisLabs/codis)。 -#### Twemproxy +#### 5.2.1. Twemproxy -`Twemproxy` 也叫 `nutcraker`,是 `twitter` 开源的一个 `redis` 和 `memcache` 的 **中间代理服务器** 程序。`Twemproxy` 作为 **代理**,可接受来自多个程序的访问,按照 **路由规则**,转发给后台的各个 `Redis` 服务器,再原路返回。`Twemproxy` 存在 **单点故障** 问题,需要结合 `Lvs` 和 `Keepalived` 做 **高可用方案**。 +**[Twemproxy](https://github.com/twitter/twemproxy)** 也叫 `nutcraker`,是 Twitter 开源的一个 Redis 和 Memcache 的 **中间代理服务器** 程序。 -![img](https://user-gold-cdn.xitu.io/2018/9/4/165a4f9e751d0773?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) +**[Twemproxy](https://github.com/twitter/twemproxy)** 作为 **代理**,可接受来自多个程序的访问,按照 **路由规则**,转发给后台的各个 Redis 服务器,再原路返回。**[Twemproxy](https://github.com/twitter/twemproxy)** 存在 **单点故障** 问题,需要结合 Lvs 和 Keepalived 做 **高可用方案**。 - **优点**:应用范围广,稳定性较高,中间代理层 **高可用**。 - **缺点**:无法平滑地 **水平扩容/缩容**,无 **可视化管理界面**,运维不友好,出现故障,不能 **自动转移**。 -#### Codis - -`Codis` 是一个 **分布式** `Redis` 解决方案,对于上层应用来说,连接 `Codis-Proxy` 和直接连接 **原生的** `Redis-Server` 没有的区别。`Codis` 底层会 **处理请求的转发**,不停机的进行 **数据迁移** 等工作。`Codis` 采用了无状态的 **代理层**,对于 **客户端** 来说,一切都是透明的。 - -![img](https://user-gold-cdn.xitu.io/2018/9/4/165a4f9e7509b300?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) - -- **优点** - -实现了上层 `Proxy` 和底层 `Redis` 的 **高可用**,**数据分片** 和 **自动平衡**,提供 **命令行接口** 和 `RESTful API`,提供 **监控** 和 **管理** 界面,可以动态 **添加** 和 **删除** `Redis` 节点。 - -- **缺点** - -**部署架构** 和 **配置** 复杂,不支持 **跨机房** 和 **多租户**,不支持 **鉴权管理**。 +#### 5.2.2. Codis -### 查询路由方案 +[**Codis**](https://github.com/CodisLabs/codis) 是一个 **分布式** Redis 解决方案,对于上层应用来说,连接 Codis-Proxy 和直接连接 **原生的** Redis-Server 没有的区别。[**Codis**](https://github.com/CodisLabs/codis) 底层会 **处理请求的转发**,不停机的进行 **数据迁移** 等工作。[**Codis**](https://github.com/CodisLabs/codis) 采用了无状态的 **代理层**,对于 **客户端** 来说,一切都是透明的。 -**客户端随机地** 请求任意一个 `Redis` 实例,然后由 `Redis` 将请求 **转发** 给 **正确** 的 `Redis` 节点。`Redis Cluster` 实现了一种 **混合形式** 的 **查询路由**,但并不是 **直接** 将请求从一个 `Redis` 节点 **转发** 到另一个 `Redis` 节点,而是在 **客户端** 的帮助下直接 **重定向**( `redirected`)到正确的 `Redis` 节点。 +- **优点**:实现了上层 Proxy 和底层 Redis 的 **高可用**,**数据分区** 和 **自动平衡**,提供 **命令行接口** 和 RESTful API,提供 **监控** 和 **管理** 界面,可以动态 **添加** 和 **删除** Redis 节点。 -![img](https://user-gold-cdn.xitu.io/2018/9/4/165a4f9e84b4b379?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) +- **缺点**:**部署架构** 和 **配置** 复杂,不支持 **跨机房** 和 **多租户**,不支持 **鉴权管理**。 -- **优点** +### 5.3. 查询路由方案 -**无中心节点**,数据按照 **槽** 存储分布在多个 `Redis` 实例上,可以平滑的进行节点 **扩容/缩容**,支持 **高可用** 和 **自动故障转移**,运维成本低。 +**客户端随机地** 请求任意一个 Redis 实例,然后由 Redis 将请求 **转发** 给 **正确** 的 Redis 节点。Redis Cluster 实现了一种 **混合形式** 的 **查询路由**,但并不是 **直接** 将请求从一个 Redis 节点 **转发** 到另一个 Redis 节点,而是在 **客户端** 的帮助下直接 **重定向**( `redirected`)到正确的 Redis 节点。 -- **缺点** +- **优点**:**去中心化**,数据按照 **槽** 存储分布在多个 Redis 实例上,可以平滑的进行节点 **扩容/缩容**,支持 **高可用** 和 **自动故障转移**,运维成本低。 -严重依赖 `Redis-trib` 工具,缺乏 **监控管理**,需要依赖 `Smart Client` (**维护连接**,**缓存路由表**,`MultiOp` 和 `Pipeline` 支持)。`Failover` 节点的 **检测过慢**,不如 **中心节点** `ZooKeeper` 及时。`Gossip` 消息具有一定开销。无法根据统计区分 **冷热数据**。 +- **缺点**:重度依赖 Redis-trib 工具,缺乏 **监控管理**,需要依赖 Smart Client (**维护连接**,**缓存路由表**,`MultiOp` 和 `Pipeline` 支持)。Failover 节点的 **检测过慢**,不如有 **中心节点** 的集群及时(如 ZooKeeper)。Gossip 消息采用广播方式,集群规模越大,开销越大。无法根据统计区分 **冷热数据**。 -## 参考资料 +## 6. 参考资料 - **官网** - [Redis 官网](https://redis.io/) - [Redis github](https://github.com/antirez/redis) - [Redis 官方文档中文版](http://redis.cn/) +- **中间件** + - [Twemproxy](https://github.com/twitter/twemproxy) + - [Codis](https://github.com/CodisLabs/codis) - **书籍** - [《Redis 实战》](https://item.jd.com/11791607.html) - [《Redis 设计与实现》](https://item.jd.com/11486101.html) - **教程** - - [Redis 命令参考](http://redisdoc.com/) + - [后端存储实战课](https://time.geekbang.org/column/intro/100046801) - **文章** - [Redis 集群教程](http://ifeve.com/redis-cluster-tutorial/) - [Redis 集群的原理和搭建](https://www.jianshu.com/p/c869feb5581d) - - [深入剖析 Redis 系列(三) - Redis 集群模式搭建与原理详解](https://juejin.im/post/5b8fc5536fb9a05d2d01fb11) + - [深入剖析 Redis 系列(三) - Redis 集群模式搭建与原理详解](https://juejin.im/post/5b8fc5536fb9a05d2d01fb11) \ No newline at end of file diff --git a/docs/nosql/redis/redis-action.md "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/08.Redis\345\256\236\346\210\230.md" similarity index 81% rename from docs/nosql/redis/redis-action.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/08.Redis\345\256\236\346\210\230.md" index 75790c78..bc425f9a 100644 --- a/docs/nosql/redis/redis-action.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/08.Redis\345\256\236\346\210\230.md" @@ -1,16 +1,18 @@ -# Redis 实战 - - +--- +title: Redis 实战 +date: 2020-06-24 10:45:38 +categories: + - 数据库 + - KV数据库 + - Redis +tags: + - 数据库 + - KV数据库 + - Redis +permalink: /pages/1fc9c4/ +--- -- [一、应用场景](#一应用场景) - - [缓存](#缓存) - - [BitMap 和 BloomFilter](#bitmap-和-bloomfilter) - - [分布式锁](#分布式锁) -- [二、技巧](#二技巧) - - [keys 和 scan](#keys-和-scan) -- [参考资料](#参考资料) - - +# Redis 实战 ## 一、应用场景 @@ -22,7 +24,7 @@ Redis 可以应用于很多场景,这里列举几个经典的应用场景。 Redis 有多种数据类型,以及丰富的操作命令,并且有着高性能、高可用的特性,非常适合用于分布式缓存。 -> 缓存应用的基本原理,请参考 [**缓存基本原理**](https://github.com/dunwu/blog/blob/master/source/_posts/theory/cache.md) 第四 ~ 第六节内容。 +> 缓存应用的基本原理,请参考 [**缓存基本原理**](https://dunwu.github.io/design/distributed/分布式缓存.html) 第四 ~ 第六节内容。 ### BitMap 和 BloomFilter @@ -30,7 +32,7 @@ Redis 除了 5 种基本数据类型外,还支持 BitMap 和 BloomFilter(即 BitMap 和 BloomFilter 都可以用于解决缓存穿透问题。要点在于:过滤一些不可能存在的数据。 -> 什么是缓存穿透,可以参考:[**缓存基本原理**](https://github.com/dunwu/blog/blob/master/source/_posts/theory/cache.md) +> 什么是缓存穿透,可以参考:[**缓存基本原理**](https://dunwu.github.io/design/distributed/分布式缓存.html) 小数据量可以用 BitMap,大数据量可以用布隆过滤器。 @@ -42,7 +44,7 @@ BitMap 和 BloomFilter 都可以用于解决缓存穿透问题。要点在于: - **避免永远不释放锁** - 使用 `expire` 加一个过期时间,避免一直不释放锁,导致阻塞。 - **原子性** - setnx 和 expire 必须合并为一个原子指令,避免 setnx 后,机器崩溃,没来得及设置 expire,从而导致锁永不释放。 -> 更多分布式锁的实现方式及细节,请参考:[分布式锁基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/theory/distributed-lock.md) +> 更多分布式锁的实现方式及细节,请参考:[分布式锁基本原理](https://dunwu.github.io/blog/pages/40ac64/) ## 二、技巧 @@ -72,4 +74,4 @@ BitMap 和 BloomFilter 都可以用于解决缓存穿透问题。要点在于: - **教程** - [Redis 命令参考](http://redisdoc.com/) - **文章** - - [《我们一起进大厂》系列- Redis 基础](https://juejin.im/post/5db66ed9e51d452a2f15d833) + - [《我们一起进大厂》系列- Redis 基础](https://juejin.im/post/5db66ed9e51d452a2f15d833) \ No newline at end of file diff --git a/docs/nosql/redis/redis-ops.md "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/20.Redis\350\277\220\347\273\264.md" similarity index 96% rename from docs/nosql/redis/redis-ops.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/20.Redis\350\277\220\347\273\264.md" index 8664a3f4..57ef46d0 100644 --- a/docs/nosql/redis/redis-ops.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/20.Redis\350\277\220\347\273\264.md" @@ -1,36 +1,24 @@ +--- +title: Redis 运维 +date: 2020-06-24 10:45:38 +categories: + - 数据库 + - KV数据库 + - Redis +tags: + - 数据库 + - KV数据库 + - Redis + - 运维 +permalink: /pages/537098/ +--- + # Redis 运维 > **Redis** 是一个高性能的 key-value 数据库。 > > SET 操作每秒钟 110000 次;GET 操作每秒钟 81000 次。 - - -- [一、Redis 安装](#一redis-安装) - - [Window 下安装](#window-下安装) - - [Linux 下安装](#linux-下安装) - - [Ubuntu 下安装](#ubuntu-下安装) - - [开机启动](#开机启动) - - [开放防火墙端口](#开放防火墙端口) - - [Redis 安装脚本](#redis-安装脚本) -- [二、Redis 单机使用和配置](#二redis-单机使用和配置) - - [启动 Redis](#启动-redis) - - [Redis 常见配置](#redis-常见配置) - - [设为守护进程](#设为守护进程) - - [压力测试](#压力测试) -- [三、Redis 集群使用和配置](#三redis-集群使用和配置) - - [集群规划](#集群规划) - - [部署集群](#部署集群) - - [部署哨兵](#部署哨兵) - - [扩容](#扩容) -- [四、Redis 命令](#四redis-命令) - - [通用命令](#通用命令) - - [集群命令](#集群命令) -- [五、客户端](#五客户端) -- [参考资料](#参考资料) - - - ## 一、Redis 安装 ### Window 下安装 @@ -216,7 +204,7 @@ Redis 默认访问不需要密码,如果需要设置密码,需要如下配 | `dir ./` | 指定本地数据库存放目录 | | `slaveof ` | 设置当本机为 slav 服务时,设置 master 服务的 IP 地址及端口,在 Redis 启动时,它会自动从 master 进行数据同步 | | `masterauth ` | 当 master 服务设置了密码保护时,slav 服务连接 master 的密码 | -| `requirepass foobared` | 设置 Redis 连接密码,如果配置了连接密码,客户端在连接 Redis 时需要通过 AUTH 命令提供密码,默认关闭 | +| `requirepass foobared` | 设置 Redis 连接密码,如果配置了连接密码,客户端在连接 Redis 时需要通过 `AUTH ` 命令提供密码,默认关闭 | | `maxclients 128` | 设置同一时间最大客户端连接数,默认无限制,Redis 可以同时打开的客户端连接数为 Redis 进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis 会关闭新的连接并向客户端返回 max number of clients reached 错误信息 | | `maxmemory ` | 指定 Redis 最大内存限制,Redis 在启动时会把数据加载到内存中,达到最大内存后,Redis 会先尝试清除已到期或即将到期的 Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis 新的 vm 机制,会把 Key 存放内存,Value 会存放在 swap 区 | | `appendonly no` | 指定是否在每次更新操作后进行日志记录,Redis 在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis 本身同步数据文件是按上面 save 条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为 no | @@ -631,11 +619,11 @@ rebalance:表明让 Redis 自动根据节点数进行均衡哈希槽分配。 --cluster-use-empty-masters:表明 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200712125827.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200712125827.png) 执行结束后,查看状态: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200712130234.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200712130234.png) ## 四、Redis 命令 @@ -694,4 +682,4 @@ redis-cli --cluster reshard 172.22.6.3 7001 - **教程** - [Redis 命令参考](http://redisdoc.com/) - **文章** - - [深入剖析 Redis 系列(三) - Redis 集群模式搭建与原理详解](https://juejin.im/post/5b8fc5536fb9a05d2d01fb11) + - [深入剖析 Redis 系列(三) - Redis 集群模式搭建与原理详解](https://juejin.im/post/5b8fc5536fb9a05d2d01fb11) \ No newline at end of file diff --git a/docs/nosql/redis/README.md "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/README.md" similarity index 62% rename from docs/nosql/redis/README.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/README.md" index 1028a657..f65b88bb 100644 --- a/docs/nosql/redis/README.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/README.md" @@ -1,40 +1,55 @@ +--- +title: Redis 教程 +date: 2020-02-10 14:27:39 +categories: + - 数据库 + - KV数据库 + - Redis +tags: + - 数据库 + - KV数据库 + - Redis +permalink: /pages/fe3808/ +hidden: true +--- + # Redis 教程 > Redis 最典型的应用场景是作为分布式缓存。 > > 学习 Redis,有必要深入理解缓存的原理,以及 Redis 作为一种缓存方案,在系统应用中的定位。 > -> 参考:[缓存基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/theory/cache.md),有助于理解缓存的特性、原理,使用缓存常见的问题及解决方案。 +> 参考:[缓存基本原理](https://dunwu.github.io/design/distributed/分布式缓存.html),有助于理解缓存的特性、原理,使用缓存常见的问题及解决方案。 ## 📖 内容 -### [Redis 面试总结 💯](redis-interview.md) +### [Redis 面试总结 💯](01.Redis面试总结.md) -### [Redis 应用指南 ⚡](redis-quickstart.md) +### [Redis 应用指南 ⚡](02.Redis应用指南.md) > 关键词:`内存淘汰`、`事件`、`事务`、`管道`、`发布与订阅` -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200713105627.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200713105627.png) -### [Redis 数据类型和应用](redis-datatype.md) +### [Redis 数据类型和应用](03.Redis数据类型和应用.md) > 关键词:`STRING`、`HASH`、`LIST`、`SET`、`ZSET`、`BitMap`、`HyperLogLog`、`Geo` -![Redis 数据类型](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200226113813.png) +![Redis 数据类型](https://raw.githubusercontent.com/dunwu/images/master/snap/20200226113813.png) -### [Redis 持久化](redis-persistence.md) +### [Redis 持久化](04.Redis持久化.md) > 关键词:`RDB`、`AOF`、`SAVE`、`BGSAVE`、`appendfsync` -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200224214047.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200224214047.png) -### [Redis 复制](redis-replication.md) +### [Redis 复制](05.Redis复制.md) > 关键词:`SLAVEOF`、`SYNC`、`PSYNC`、`REPLCONF ACK` -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200712182603.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200712182603.png) -### [Redis 哨兵](redis-sentinel.md) +### [Redis 哨兵](06.Redis哨兵.md) > Redis 哨兵(Sentinel)是 Redis 的高可用性(Hight Availability)解决方案。 > @@ -42,19 +57,19 @@ > > 关键词:`Sentinel`、`PING`、`INFO`、`Raft` -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200713072747.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200713072747.png) -### [Redis 集群](redis-cluster.md) +### [Redis 集群](07.Redis集群.md) > 关键词:`CLUSTER MEET`、`Hash slot`、`MOVED`、`ASK`、`SLAVEOF no one`、`redis-trib` -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200713100613.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200713100613.png) -### [Redis 实战](redis-action.md) +### [Redis 实战](08.Redis实战.md) > 关键词:`缓存`、`分布式锁`、`布隆过滤器` -### [Redis 运维 🔨](redis-ops.md) +### [Redis 运维 🔨](20.Redis运维.md) > 关键词:`安装`、`命令`、`集群`、`客户端` @@ -85,4 +100,4 @@ ## 🚪 传送 -◾ 🏠 [DB-TUTORIAL 首页](https://github.com/dunwu/db-tutorial) ◾ 🎯 [我的博客](https://github.com/dunwu/blog) ◾ +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file diff --git a/docs/nosql/redis/redis-cheat-sheets.pdf "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/redis-cheat-sheets.pdf" similarity index 100% rename from docs/nosql/redis/redis-cheat-sheets.pdf rename to "docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/redis-cheat-sheets.pdf" diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/01.HBase\345\277\253\351\200\237\345\205\245\351\227\250.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/01.HBase\345\277\253\351\200\237\345\205\245\351\227\250.md" new file mode 100644 index 00000000..2dc28428 --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/01.HBase\345\277\253\351\200\237\345\205\245\351\227\250.md" @@ -0,0 +1,292 @@ +--- +title: HBase 快速入门 +date: 2020-02-10 14:27:39 +categories: + - 数据库 + - 列式数据库 + - HBase +tags: + - 数据库 + - 列式数据库 + - 大数据 + - HBase +permalink: /pages/7ab03c/ +--- + +# HBase 快速入门 + +## HBase 简介 + +### 为什么需要 HBase + +在介绍 HBase 之前,我们不妨先了解一下为什么需要 HBase,或者说 HBase 是为了达到什么目的而产生。 + +在 HBase 诞生之前,Hadoop 可以通过 HDFS 来存储结构化、半结构甚至非结构化的数据,它是传统数据库的补充,是海量数据存储的最佳方法,它针对大文件的存储,批量访问和流式访问都做了优化,同时也通过多副本解决了容灾问题。 + +Hadoop 的缺陷在于:它只能执行批处理,并且只能以顺序方式访问数据。这意味着即使是最简单的工作,也必须搜索整个数据集,即:**Hadoop 无法实现对数据的随机访问**。实现数据的随机访问是传统的关系型数据库所擅长的,但它们却不能用于海量数据的存储。在这种情况下,必须有一种新的方案来**同时解决海量数据存储和随机访问的问题**,HBase 就是其中之一 (HBase,Cassandra,couchDB,Dynamo 和 MongoDB 都能存储海量数据并支持随机访问)。 + +> 注:数据结构分类: +> +> - 结构化数据:即以关系型数据库表形式管理的数据; +> - 半结构化数据:非关系模型的,有基本固定结构模式的数据,例如日志文件、XML 文档、JSON 文档、Email 等; +> - 非结构化数据:没有固定模式的数据,如 WORD、PDF、PPT、EXL,各种格式的图片、视频等。 + +### 什么是 HBase + +**HBase 是一个构建在 HDFS(Hadoop 文件系统)之上的列式数据库**。 + +HBase 是一种类似于 `Google’s Big Table` 的数据模型,它是 Hadoop 生态系统的一部分,它将数据存储在 HDFS 上,客户端可以通过 HBase 实现对 HDFS 上数据的随机访问。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200601170449.png) + +HBase 的**核心特性**如下: + +- **分布式** + - **伸缩性**:支持通过增减机器进行水平扩展,以提升整体容量和性能 + - **高可用**:支持 RegionServers 之间的自动故障转移 + - **自动分区**:Region 分散在集群中,当行数增长的时候,Region 也会自动的分区再均衡 +- **超大数据集**:HBase 被设计用来读写超大规模的数据集(数十亿行至数百亿行的表) +- **支持结构化、半结构化和非结构化的数据**:由于 HBase 基于 HDFS 构建,所以和 HDFS 一样,支持结构化、半结构化和非结构化的数据 +- **非关系型数据库** + - **不支持标准 SQL 语法** + - **没有真正的索引** + - **不支持复杂的事务**:只支持行级事务,即单行数据的读写都是原子性的 + +HBase 的其他特性 + +- 读写操作遵循强一致性 +- 过滤器支持谓词下推 +- 易于使用的 Java 客户端 API +- 它支持线性和模块化可扩展性。 +- HBase 表支持 Hadoop MapReduce 作业的便捷基类 +- 很容易使用 Java API 进行客户端访问 +- 为实时查询提供块缓存 BlockCache 和布隆过滤器 +- 它通过服务器端过滤器提供查询谓词下推 + +### 什么时候使用 HBase + +根据上一节对于 HBase 特性的介绍,我们可以梳理出 HBase 适用、不适用的场景: + +HBase 不适用场景: + +- 需要索引 +- 需要复杂的事务 +- 数据量较小(比如:数据量不足几百万行) + +HBase 适用场景: + +- 能存储海量数据并支持随机访问(比如:数据量级达到十亿级至百亿级) +- 存储结构化、半结构化数据 +- 硬件资源充足 + +> 一言以蔽之——HBase 适用的场景是:**实时地随机访问超大数据集**。 + +HBase 的典型应用场景 + +- 存储监控数据 +- 存储用户/车辆 GPS 信息 +- 存储用户行为数据 +- 存储各种日志数据,如:访问日志、操作日志、推送日志等。 +- 存储短信、邮件等消息类数据 +- 存储网页数据 + +### HBase 数据模型简介 + +前面已经提及,HBase 是一个列式数据库,其数据模型和关系型数据库有所不同。其数据模型的关键术语如下: + +- Table:HBase 表由多行组成。 +- Row:HBase 中的一行由一个行键和一个或多个列以及与之关联的值组成。 行在存储时按行键的字母顺序排序。 为此,行键的设计非常重要。 目标是以相关行彼此靠近的方式存储数据。 常见的行键模式是网站域。 如果您的行键是域,您应该将它们反向存储(org.apache.www、org.apache.mail、org.apache.jira)。 这样,所有 Apache 域在表中彼此靠近,而不是根据子域的第一个字母展开。 +- Column:HBase 中的列由列族和列限定符组成,它们由 :(冒号)字符分隔。 +- Column Family:通常出于性能原因,列族在物理上将一组列及其值放在一起。 每个列族都有一组存储属性,例如它的值是否应该缓存在内存中,它的数据是如何压缩的,它的行键是如何编码的,等等。 表中的每一行都有相同的列族,尽管给定的行可能不在给定的列族中存储任何内容。 +- 列限定符:将列限定符添加到列族以提供给定数据片段的索引。 给定列族内容,列限定符可能是 content:html,另一个可能是 content:pdf。 尽管列族在表创建时是固定的,但列限定符是可变的,并且行之间可能有很大差异。 +- Cell:单元格是行、列族和列限定符的组合,包含一个值和一个时间戳,代表值的版本。 +- Timestamp:时间戳写在每个值旁边,是给定版本值的标识符。 默认情况下,时间戳表示写入数据时 RegionServer 上的时间,但您可以在将数据放入单元格时指定不同的时间戳值。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/bigdata/hbase/1551164224778.png) + +### 特性比较 + +#### HBase vs. RDBMS + +| RDBMS | HBase | +| ---------------------------------------- | -------------------------------------------------- | +| RDBMS 有它的模式,描述表的整体结构的约束 | HBase 无模式,它不具有固定列模式的概念;仅定义列族 | +| 支持的文件系统有 FAT、NTFS 和 EXT | 支持的文件系统只有 HDFS | +| 使用提交日志来存储日志 | 使用预写日志 (WAL) 来存储日志 | +| 使用特定的协调系统来协调集群 | 使用 ZooKeeper 来协调集群 | +| 存储的都是中小规模的数据表 | 存储的是超大规模的数据表,并且适合存储宽表 | +| 通常支持复杂的事务 | 仅支持行级事务 | +| 适用于结构化数据 | 适用于半结构化、结构化数据 | +| 使用主键 | 使用 row key | + +#### HBase vs. HDFS + +| HDFS | HBase | +| ----------------------------------------- | ---------------------------------------------------- | +| HDFS 提供了一个用于分布式存储的文件系统。 | HBase 提供面向表格列的数据存储。 | +| HDFS 为大文件提供优化存储。 | HBase 为表格数据提供了优化。 | +| HDFS 使用块文件。 | HBase 使用键值对数据。 | +| HDFS 数据模型不灵活。 | HBase 提供了一个灵活的数据模型。 | +| HDFS 使用文件系统和处理框架。 | HBase 使用带有内置 Hadoop MapReduce 支持的表格存储。 | +| HDFS 主要针对一次写入多次读取进行了优化。 | HBase 针对读/写许多进行了优化。 | + +#### 行式数据库 vs. 列式数据库 + +| 行式数据库 | 列式数据库 | +| ------------------------------ | ------------------------------ | +| 对于添加/修改操作更高效 | 对于读取操作更高效 | +| 读取整行数据 | 仅读取必要的列数据 | +| 最适合在线事务处理系统(OLTP) | 不适合在线事务处理系统(OLTP) | +| 将行数据存储在连续的页内存中 | 将列数据存储在非连续的页内存中 | + +列式数据库的优点: + +- 支持数据压缩 +- 支持快速数据检索 +- 简化了管理和配置 +- 有利于聚合查询(例如 COUNT、SUM、AVG、MIN 和 MAX)的高性能 +- 分区效率很高,因为它提供了自动分片机制的功能,可以将较大的区域分配给较小的区域 + +列式数据库的缺点: + +- JOIN 查询和来自多个表的数据未优化 +- 必须为频繁的删除和更新创建拆分,因此降低了存储效率 +- 由于非关系数据库的特性,分区和索引的设计非常困难 + +## HBase 安装 + +- [独立模式](https://hbase.apache.org/book.html#quickstart) +- [伪分布式模式](https://hbase.apache.org/book.html#quickstart_pseudo) +- [全分布式模式](https://hbase.apache.org/book.html#quickstart_fully_distributed) +- [Docker 部署](https://github.com/big-data-europe/docker-hbase) + +## HBase Hello World 示例 + +1. 连接 HBase + + 在 HBase 安装目录的 `/bin` 目录下执行 `hbase shell` 命令进入 HBase 控制台。 + + ```shell + $ ./bin/hbase shell + hbase(main):001:0> + ``` + +2. 输入 `help` 可以查看 HBase Shell 命令。 + +3. 创建表 + + 可以使用 `create` 命令创建一张新表。必须要指定表名和 Column Family。 + + ```shell + hbase(main):001:0> create 'test', 'cf' + 0 row(s) in 0.4170 seconds + + => Hbase::Table - test + ``` + +4. 列出表信息 + + 使用 `list` 命令来确认新建的表已存在。 + + ```shell + hbase(main):002:0> list 'test' + TABLE + test + 1 row(s) in 0.0180 seconds + + => ["test"] + ``` + + 可以使用 `describe` 命令可以查看表的细节信息,包括配置信息 + + ```shell + hbase(main):003:0> describe 'test' + Table test is ENABLED + test + COLUMN FAMILIES DESCRIPTION + {NAME => 'cf', VERSIONS => '1', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CELLS => 'FALSE', CACHE_DATA_ON_WRITE => + 'false', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW', CACHE_INDEX_ON_WRITE => 'f + alse', IN_MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETCH_BLOCKS_ON_OPEN => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE + => '65536'} + 1 row(s) + Took 0.9998 seconds + ``` + +5. 向表中写数据 + + 可以使用 `put` 命令向 HBase 表中写数据。 + + ```shell + hbase(main):003:0> put 'test', 'row1', 'cf:a', 'value1' + 0 row(s) in 0.0850 seconds + + hbase(main):004:0> put 'test', 'row2', 'cf:b', 'value2' + 0 row(s) in 0.0110 seconds + + hbase(main):005:0> put 'test', 'row3', 'cf:c', 'value3' + 0 row(s) in 0.0100 seconds + ``` + +6. 一次性扫描表的所有数据 + + 使用 `scan` 命令来扫描表数据。 + + ```shell + hbase(main):006:0> scan 'test' + ROW COLUMN+CELL + row1 column=cf:a, timestamp=1421762485768, value=value1 + row2 column=cf:b, timestamp=1421762491785, value=value2 + row3 column=cf:c, timestamp=1421762496210, value=value3 + 3 row(s) in 0.0230 seconds + ``` + +7. 查看一行数据 + + 使用 `get` 命令可以查看一行表数据。 + + ```shell + hbase(main):007:0> get 'test', 'row1' + COLUMN CELL + cf:a timestamp=1421762485768, value=value1 + 1 row(s) in 0.0350 seconds + ``` + +8. 禁用表 + + 如果想要删除表或修改表设置,必须先使用 `disable` 命令禁用表。如果想再次启用表,可以使用 `enable` 命令。 + + ```shell + hbase(main):008:0> disable 'test' + 0 row(s) in 1.1820 seconds + + hbase(main):009:0> enable 'test' + 0 row(s) in 0.1770 seconds + ``` + +9. 删除表 + + 使用 `drop` 命令可以删除表。 + + ```shell + hbase(main):011:0> drop 'test' + 0 row(s) in 0.1370 seconds + ``` + +10. 退出 HBase Shell + + 使用 `quit` 命令,就能退出 HBase Shell 控制台。 + +## 参考资料 + +- **官方** + - [HBase 官网](http://hbase.apache.org/) + - [HBase 官方文档](https://hbase.apache.org/book.html) + - [HBase 官方文档中文版](http://abloz.com/hbase/book.html) +- **书籍** + - [Hadoop 权威指南](https://book.douban.com/subject/27600204/) +- **文章** + - [Bigtable: A Distributed Storage System for Structured Data](https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/bigtable-osdi06.pdf) + - [An In-Depth Look at the HBase Architecture](https://mapr.com/blog/in-depth-look-hbase-architecture) +- **教程** + - https://github.com/heibaiying/BigData-Notes + - https://www.cloudduggu.com/hbase/introduction/ diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/02.HBase\346\225\260\346\215\256\346\250\241\345\236\213.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/02.HBase\346\225\260\346\215\256\346\250\241\345\236\213.md" new file mode 100644 index 00000000..d2af7ecb --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/02.HBase\346\225\260\346\215\256\346\250\241\345\236\213.md" @@ -0,0 +1,84 @@ +--- +title: HBase 数据模型 +date: 2023-03-16 15:58:10 +categories: + - 数据库 + - 列式数据库 + - HBase +tags: + - 数据库 + - 列式数据库 + - 大数据 + - HBase +permalink: /pages/c8cfeb/ +--- + +# HBase 数据模型 + +HBase 是一个面向 `列` 的数据库管理系统,这里更为确切的而说,HBase 是一个面向 `列族` 的数据库管理系统。表 schema 仅定义列族,表具有多个列族,每个列族可以包含任意数量的列,列由多个单元格(cell)组成,单元格可以存储多个版本的数据,多个版本数据以时间戳进行区分。 + +## HBase 逻辑存储结构 + +- **`Table`**:Table 由 Row 和 Column 组成。 +- **`Row`**:Row 是列族(Column Family)的集合。 +- **`Row Key`**:**`Row Key` 是用来检索记录的主键**。 + - `Row Key` 是未解释的字节数组,所以理论上,任何数据都可以通过序列化表示成字符串或二进制,从而存为 HBase 的键值。 + - 表中的行,是按照 `Row Key` 的字典序进行排序。这里需要注意以下两点: + - 因为字典序对 Int 排序的结果是 1,10,100,11,12,13,14,15,16,17,18,19,2,20,21,…,9,91,92,93,94,95,96,97,98,99。如果你使用整型的字符串作为行键,那么为了保持整型的自然序,行键必须用 0 作左填充。 + - 行的一次读写操作是原子性的 (不论一次读写多少列)。 + - 所有对表的访问都要通过 Row Key,有以下三种方式: + - 通过指定的 `Row Key` 进行访问; + - 通过 `Row Key` 的 range 进行访问,即访问指定范围内的行; + - 进行全表扫描。 +- **`Column Family`**:即列族。HBase 表中的每个列,都归属于某个列族。列族是表的 Schema 的一部分,所以列族需要在创建表时进行定义。 + - 一个表的列族必须作为表模式定义的一部分预先给出,但是新的列族成员可以随后按需加入。 + - 同一个列族的所有成员具有相同的前缀,例如 `info:format`,`info:geo` 都属于 `info` 这个列族。 +- **`Column Qualifier`**:列限定符。可以理解为是具体的列名,例如 `info:format`,`info:geo` 都属于 `info` 这个列族,它们的列限定符分别是 `format` 和 `geo`。列族和列限定符之间始终以冒号分隔。需要注意的是列限定符不是表 Schema 的一部分,你可以在插入数据的过程中动态创建列。 +- **`Column`**:HBase 中的列由列族和列限定符组成,由 `:`(冒号) 进行分隔,即一个完整的列名应该表述为 `列族名 :列限定符`。 +- **`Cell`**:`Cell` 是行,列族和列限定符的组合,并包含值和时间戳。HBase 中通过 `row key` 和 `column` 确定的为一个存储单元称为 `Cell`,你可以等价理解为关系型数据库中由指定行和指定列确定的一个单元格,但不同的是 HBase 中的一个单元格是由多个版本的数据组成的,每个版本的数据用时间戳进行区分。 + - `Cell` 由行和列的坐标交叉决定,是有版本的。默认情况下,版本号是自动分配的,为 HBase 插入 `Cell` 时的时间戳。`Cell` 的内容是未解释的字节数组。 + - +- **`Timestamp`**:`Cell` 的版本通过时间戳来索引,时间戳的类型是 64 位整型,时间戳可以由 HBase 在数据写入时自动赋值,也可以由客户显式指定。每个 `Cell` 中,不同版本的数据按照时间戳倒序排列,即最新的数据排在最前面。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/bigdata/hbase/1551164224778.png) + +## HBase 物理存储结构 + +HBase 自动将表水平划分成区域(Region)。每个 Region 由表中 Row 的子集构成。每个 Region 由它所属的表的起始范围来表示(包含的第一行和最后一行)。初始时,一个表只有一个 Region,随着 Region 膨胀,当超过一定阈值时,会在某行的边界上分裂成两个大小基本相同的新 Region。在第一次划分之前,所有加载的数据都放在原始 Region 所在的那台服务器上。随着表变大,Region 个数也会逐渐增加。Region 是在 HBase 集群上分布数据的最小单位。 + +## HBase 数据模型示例 + +下图为 HBase 中一张表的: + +- RowKey 为行的唯一标识,所有行按照 RowKey 的字典序进行排序; +- 该表具有两个列族,分别是 personal 和 office; +- 其中列族 personal 拥有 name、city、phone 三个列,列族 office 拥有 tel、addres 两个列。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200601172926.png) + +> _图片引用自 : HBase 是列式存储数据库吗_ *https://www.iteblog.com/archives/2498.html* + +## HBase 表特性 + +Hbase 的表具有以下特点: + +- **容量大**:一个表可以有数十亿行,上百万列; +- **面向列**:数据是按照列存储,每一列都单独存放,数据即索引,在查询时可以只访问指定列的数据,有效地降低了系统的 I/O 负担; +- **稀疏性**:空 (null) 列并不占用存储空间,表可以设计的非常稀疏 ; +- **数据多版本**:每个单元中的数据可以有多个版本,按照时间戳排序,新的数据在最上面; +- **存储类型**:所有数据的底层存储格式都是字节数组 (byte[])。 + +## 参考资料 + +- **官方** + - [HBase 官网](http://hbase.apache.org/) + - [HBase 官方文档](https://hbase.apache.org/book.html) + - [HBase 官方文档中文版](http://abloz.com/hbase/book.html) +- **书籍** + - [Hadoop 权威指南](https://book.douban.com/subject/27600204/) +- **文章** + - [Bigtable: A Distributed Storage System for Structured Data](https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/bigtable-osdi06.pdf) + - [An In-Depth Look at the HBase Architecture](https://mapr.com/blog/in-depth-look-hbase-architecture) +- **教程** + - https://github.com/heibaiying/BigData-Notes + - https://www.cloudduggu.com/hbase/introduction/ \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/03.HBaseSchema\350\256\276\350\256\241.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/03.HBaseSchema\350\256\276\350\256\241.md" new file mode 100644 index 00000000..d4e29c79 --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/03.HBaseSchema\350\256\276\350\256\241.md" @@ -0,0 +1,229 @@ +--- +title: HBase Schema 设计 +date: 2023-03-15 20:28:32 +categories: + - 数据库 + - 列式数据库 + - HBase +tags: + - 大数据 + - HBase +permalink: /pages/a69528/ +--- + +# HBase Schema 设计 + +## HBase Schema 设计要素 + +- 这个表应该有多少 Column Family +- Column Family 使用什么数据 +- 每个 Column Family 有有多少列 +- 列名是什么,尽管列名不必在建表时定义,但读写数据是要知道的 +- 单元应该存放什么数据 +- 每个单元存储多少时间版本 +- 行健(rowKey)结构是什么,应该包含什么信息 + +## Row Key 设计 + +### Row Key 的作用 + +在 HBase 中,所有对表的访问都要通过 Row Key,有三种访问方式: + +- 使用 `get` 命令,查询指定的 Row Key,即精确查找。 +- 使用 scan 命令,根据 Row Key 进行范围查找。 +- 全表扫描,即直接扫描表中所有行记录。 + +此外,在 HBase 中,表中的行,是按照 Row Key 的字典序进行排序的。 + +由此,可见,Row Key 的良好设计对于 HBase CRUD 的性能至关重要。 + +### Row Key 的设计原则 + +**长度原则** + +RowKey 是一个二进制码流,可以是任意字符串,最大长度为 64kb,实际应用中一般为 10-100byte,以 byte[]形式保存,一般设计成定长。建议越短越好,不要超过 16 个字节,原因如下: + +1. 数据的持久化文件 HFile 中时按照 Key-Value 存储的,如果 RowKey 过长,例如超过 100byte,那么 1000w 行的记录,仅 RowKey 就需占用近 1GB 的空间。这样会极大影响 HFile 的存储效率。 +2. MemStore 会缓存部分数据到内存中,若 RowKey 字段过长,内存的有效利用率就会降低,就不能缓存更多的数据,从而降低检索效率。 +3. 目前操作系统都是 64 位系统,内存 8 字节对齐,控制在 16 字节,8 字节的整数倍利用了操作系统的最佳特性。 + +**唯一原则** + +必须在设计上保证 RowKey 的唯一性。由于在 HBase 中数据存储是 Key-Value 形式,若向 HBase 中同一张表插入相同 RowKey 的数据,则原先存在的数据会被新的数据覆盖。 + +**排序原则** + +HBase 的 RowKey 是按照 ASCII 有序排序的,因此我们在设计 RowKey 的时候要充分利用这点。 + +**散列原则** + +设计的 RowKey 应均匀的分布在各个 HBase 节点上。 + +### 热点问题 + +Region 是在 HBase 集群上分布数据的最小单位。每个 Region 由它所属的表的起始范围来表示(即起始 Row Key 和结束 Row Key)。 + +如果,Row Key 使用单调递增的整数或时间戳,就会产生一个问题:因为 Hbase 的 Row Key 是就近存储的,这会导致一段时间内大部分读写集中在某一个 Region 或少数 Region 上(根据二八原则,最近产生的数据,往往是读写频率最高的数据),即所谓 **热点问题**。 + +#### 反转(Reversing) + +第一种咱们要分析的方法是反转,顾名思义它就是把固定长度或者数字格式的 RowKey 进行反转,反转分为一般数据反转和时间戳反转,其中以时间戳反转较常见。 + +- **反转固定格式的数值** - 以手机号为例,手机号的前缀变化比较少(如 `152、185` 等),但后半部分变化很多。如果将它反转过来,可以有效地避免热点。不过其缺点就是失去了有序性。 +- **反转时间** - 如果数据访问以查找最近的数据为主,可以将时间戳存储为反向时间戳(例如: `timestamp = Long.MAX_VALUE – timestamp`),这样有利于扫描最近的数据。 + +#### 加盐(Salting) + +这里的“加盐”与密码学中的“加盐”不是一回事。它是指在 RowKey 的前面增加一些前缀,加盐的前缀种类越多,RowKey 就被打得越散。 + +需要注意的是分配的随机前缀的种类数量应该和我们想把数据分散到的那些 region 的数量一致。只有这样,加盐之后的 rowkey 才会根据随机生成的前缀分散到各个 region 中,避免了热点现象。 + +#### 哈希(Hashing) + +其实哈希和加盐的适用场景类似,但我们前缀不可以是随机的,因为必须要让客户端能够完整地重构 RowKey。所以一般会拿原 RowKey 或其一部分计算 Hash 值,然后再对 Hash 值做运算作为前缀。 + +## HBase Schema 设计规则 + +### Column Family 设计 + +HBase 不能很好处理 2 ~ 3 个以上的 Column Family,所以 **HBase 表应尽可能减少 Column Family 数**。如果可以,请只使用一个列族,只有需要经常执行 Column 范围查询时,才引入多列族。也就是说,尽量避免同时查询多个列族。 + +- **Column Family 数量多,会影响数据刷新**。HBase 的数据刷新是在每个 Region 的基础上完成的。因此,如果一个 Column Family 携带大量导致刷新的数据,那么相邻的列族即使携带的数据量很小,也会被刷新。当存在许多 Column Family 时,刷新交互会导致一堆不必要的 IO。 此外,在表/区域级别的压缩操作也会在每个存储中发生。 +- **Column Family 数量多,会影响查找效率**。如:Column Family A 有 100 万行,Column Family B 有 10 亿行,那么 Column Family A 的数据可能会分布在很多很多区域(和 RegionServers)。 这会降低 Column Family A 的批量扫描效率。 + +Column Family 名尽量简短,最好是一个字符。Column Family 会在列限定符中被频繁使用,缩短长度有利于节省空间并提升效率。 + +### Row 设计 + +**HBase 中的 Row 按 Row Key 的字典顺序排序**。 + +- **不要将 Row Key 设计为单调递增的**,例如:递增的整数或时间戳 + + - 问题:因为 Hbase 的 Row Key 是就近存储的,这样会导致一段时间内大部分写入集中在某一个 Region 上,即所谓热点问题。 + + - 解决方法一、加盐:这里的不是指密码学的加盐,而是指将随机分配的前缀添加到行键的开头。这么做是为了避免相同前缀的 Row Key 数据被存储在相邻位置,从而导致热点问题。示例如下: + + - ``` + foo0001 + foo0002 + foo0003 + foo0004 + + 改为 + + a-foo0003 + b-foo0001 + c-foo0003 + c-foo0004 + d-foo0002 + ``` + + - 解决方法二、Hash:Row Key 的前缀使用 Hash + +- **尽量减少行和列的长度** + +- **反向时间戳**:反向时间戳可以极大地帮助快速找到值的最新版本。 + +- **行健不能改变**:唯一可以改变的方式是先删除后插入。 + +- **Row Key 和 Column Family**:Row Key 从属于 Column Family,因此,相同的 Row Key 可以存在每一个 Column Family 中而不会出现冲突。 + +### Version 设计 + +最大、最小 Row 版本号:表示 HBase 会保留的版本号数的上下限。均可以通过 HColumnDescriptor 对每个列族进行配置 + +Row 版本号过大,会大大增加 StoreFile 的大小;所以,最大 Row 版本号应按需设置。HBase 会在主要压缩时,删除多余的版本。 + +### TTL 设计 + +Column Family 会设置一个以秒为单位的 TTL,一旦达到 TTL 时,HBase 会自动删除行记录。 + +仅包含过期行的存储文件在次要压缩时被删除。 将 hbase.store.delete.expired.storefile 设置为 false 会禁用此功能。将最小版本数设置为 0 以外的值也会禁用此功能。 + +在较新版本的 HBase 上,还支持在 Cell 上设置 TTL,与 Column Family 的 TTL 不同的是,单位是毫秒。 + +### Column Family 属性配置 + +- HFile 数据块,默认是 64KB,数据库的大小影响数据块索引的大小。数据块大的话一次加载进内存的数据越多,扫描查询效果越好。但是数据块小的话,随机查询性能更好 + +``` +> create 'mytable',{NAME => 'cf1', BLOCKSIZE => '65536'} +复制代码 +``` + +- 数据块缓存,数据块缓存默认是打开的,如果一些比较少访问的数据可以选择关闭缓存 + +``` +> create 'mytable',{NAME => 'cf1', BLOCKCACHE => 'FALSE'} +复制代码 +``` + +- 数据压缩,压缩会提高磁盘利用率,但是会增加 CPU 的负载,看情况进行控制 + +``` +> create 'mytable',{NAME => 'cf1', COMPRESSION => 'SNAPPY'} +复制代码 +``` + +Hbase 表设计是和需求相关的,但是遵守表设计的一些硬性指标对性能的提升还是很有帮助的,这里整理了一些设计时用到的要点。 + +## Schema 设计案例 + +### 案例:日志数据和时序数据 + +假设采集以下数据 + +- Hostname +- Timestamp +- Log event +- Value/message + +应该如何设计 Row Key? + +(1)Timestamp 在 Row Key 头部 + +如果 Row Key 设计为 `[timestamp][hostname][log-event]` 形式,会出现热点问题。 + +如果针对时间的扫描很重要,可以采用时间戳分桶策略,即 + +``` +bucket = timestamp % bucketNum +``` + +计算出桶号后,将 Row Key 指定为:`[bucket][timestamp][hostname][log-event]` + +如上所述,要为特定时间范围选择数据,需要对每个桶执行扫描。 例如,100 个桶将在键空间中提供广泛的分布,但需要 100 次扫描才能获取单个时间戳的数据,因此需要权衡取舍。 + +(2)Hostname 在 Row Key 头部 + +如果主机样本量很大,将 Row Key 设计为 `[hostname][log-event][timestamp]`,这样有利于扫描 hostname。 + +(3)Timestamp 还是反向 Timestamp + +如果数据访问以查找最近的数据为主,可以将时间戳存储为反向时间戳(例如: `timestamp = Long.MAX_VALUE – timestamp`),这样有利于扫描最近的数据。 + +(4)Row Key 是可变长度还是固定长度 + +拼接 Row Key 的关键字长度不一定是固定的,例如 hostname 有可能很长,也有可能很短。如果想要统一长度,可以参考以下做法: + +- 将关键字 Hash 编码:使用某种 Hash 算法计算关键字,并取固定长度的值(例如:8 位或 16 位)。 +- 使用数字替代关键字:例如:使用事件类型 Code 替换事件类型;hostname 如果是 IP,可以转换为 long +- 截取关键字:截取后的关键字需要有足够的辨识度,长度大小根据具体情况权衡。 + +(5)时间分片 + +``` +[hostname][log-event][timestamp1] +[hostname][log-event][timestamp2] +[hostname][log-event][timestamp3] +``` + +上面的例子中,每个详细事件都有单独的行键,可以重写如下,即每个时间段存储一次: + +``` +[hostname][log-event][timerange] +``` + +## 参考资料 + +- [HBase 官方文档之 HBase and Schema Design](https://hbase.apache.org/book.html#schema) diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/04.HBase\346\236\266\346\236\204.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/04.HBase\346\236\266\346\236\204.md" new file mode 100644 index 00000000..85423705 --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/04.HBase\346\236\266\346\236\204.md" @@ -0,0 +1,160 @@ +--- +title: HBase 架构 +date: 2020-07-24 06:52:07 +categories: + - 数据库 + - 列式数据库 + - HBase +tags: + - 大数据 + - HBase +permalink: /pages/62f8d9/ +--- + +# HBase 架构 + +> **_HBase 是一个在 HDFS 上开发的面向列的分布式数据库。_** + +## HBase 存储架构 + +> 在 HBase 中,表被分割成多个更小的块然后分散的存储在不同的服务器上,这些小块叫做 Regions,存放 Regions 的地方叫做 RegionServer。Master 进程负责处理不同的 RegionServer 之间的 Region 的分发。 + +### 概览 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200612151239.png) + +HBase 主要处理两种文件:预写日志(WAL)和实际数据文件 HFile。一个基本的流程是客户端首先联系 ZooKeeper 集群查找行键。上述过程是通过 ZooKeeper 获取欧含有 `-ROOT-` 的 region 服务器来完成的。通过含有 `-ROOT-` 的 region 服务器可以查询到含有 `.META.` 表中对应的 region 服务器名,其中包含请求的行键信息。这两种内容都会被缓存下来,并且只查询一次。最终,通过查询 .META. 服务器来获取客户端查询的行键数据所在 region 的服务器名。 + +### Region + +HBase Table 中的所有行按照 `Row Key` 的字典序排列。HBase Table 根据 Row Key 的范围分片,每个分片叫做 `Region`。一个 `Region` 包含了在 start key 和 end key 之间的所有行。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/bigdata/hbase/1551165887616.png) + +**HBase 支持自动分区**:每个表初始只有一个 `Region`,随着数据不断增加,`Region` 会不断增大,当增大到一个阀值的时候,`Region` 就会分裂为两个新的 `Region`。当 Table 中的行不断增多,就会有越来越多的 `Region`。 + +`Region` 是 HBase 中**分布式存储和负载均衡的最小单元**。这意味着不同的 `Region` 可以分布在不同的 `Region Server` 上。但一个 `Region` 是不会拆分到多个 Server 上的。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200601181219.png) + +### Region Server + +`Region` 只不过是表被拆分,并分布在 Region Server。 + +`Region Server` 运行在 HDFS 的 DataNode 上。它具有以下组件: + +- **WAL(Write Ahead Log,预写日志)**:用于存储尚未进持久化存储的数据记录,以便在发生故障时进行恢复。如果写 WAL 失败了,那么修改数据的完整操作就是失败的。 + - 通常情况,每个 RegionServer 只有一个 WAL 实例。在 2.0 之前,WAL 的实现叫做 HLog + - WAL 位于 `/hbase/WALs/` 目录下 + - 如果每个 RegionServer 只有一个 WAL,由于 HDFS 必须是连续的,导致必须写 WAL 连续的,然后出现性能问题。MultiWAL 可以让 RegionServer 同时写多个 WAL 并行的,通过 HDFS 底层的多管道,最终提升总的吞吐量,但是不会提升单个 Region 的吞吐量。 +- **BlockCache**:**读缓存**。它将频繁读取的数据存储在内存中,如果存储不足,它将按照 `最近最少使用原则` 清除多余的数据。 +- **MemStore**:**写缓存**。它存储尚未写入磁盘的新数据,并会在数据写入磁盘之前对其进行排序。每个 Region 上的每个列族都有一个 MemStore。 +- **HFile**:**将行数据按照 Key/Values 的形式存储在文件系统上**。HFile 是 HBase 在 HDFS 中存储数据的格式,它包含多层的索引,这样在 HBase 检索数据的时候就不用完全的加载整个文件。HFile 存储的根目录默认为为 `/hbase`。索引的大小(keys 的大小,数据量的大小)影响 block 的大小,在大数据集的情况下,block 的大小设置为每个 RegionServer 1GB 也是常见的。 + - 起初,HFile 中并没有任何 Block,数据还存在于 MemStore 中。 + - Flush 发生时,创建 HFile Writer,第一个空的 Data Block 出现,初始化后的 Data Block 中为 Header 部分预留了空间,Header 部分用来存放一个 Data Block 的元数据信息。 + - 而后,位于 MemStore 中的 KeyValues 被一个个 append 到位于内存中的第一个 Data Block 中: + +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/bigdata/hbase/1551166602999.png) + +Region Server 存取一个子表时,会创建一个 Region 对象,然后对表的每个列族创建一个 `Store` 实例,每个 `Store` 会有 0 个或多个 `StoreFile` 与之对应,每个 `StoreFile` 则对应一个 `HFile`,HFile 就是实际存储在 HDFS 上的文件。 + +## HBase 系统架构 + +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/bigdata/hbase/1551164744748.png) + +和 HDFS、YARN 一样,**HBase 也遵循 master / slave 架构**: + +- HBase 有一个 master 节点。**master 节点负责协调管理 region server 节点**。 + - master 负责将 region 分配给 region server 节点; + - master 负责恢复 region server 节点的故障。 +- HBase 有多个 region server 节点。**region server 节点负责零个或多个 region 的管理并响应客户端的读写请求。region server 节点还负责 region 的划分并通知 master 节点有了新的子 region**。 +- HBase 依赖 ZooKeeper 来实现故障恢复。 + +### Master Server + +**Master Server 负责协调 Region Server**。具体职责如下: + +- 为 Region Server 分配 Region ; +- 负责 Region Server 的负载均衡 ; +- 发现失效的 Region Server 并重新分配其上的 Region; +- GFS 上的垃圾文件回收; +- 处理 Schema 的更新请求。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/bigdata/hbase/1551166513572.png) + +### Region Server + +- Region Server 负责维护 Master Server 分配给它的 Region,并处理发送到 Region 上的 IO 请求; +- 当 Region 过大,Region Server 负责自动分区,并通知 Master Server 记录更新。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200612151602.png) + +### ZooKeeper + +**HBase 依赖 ZooKeeper 作为分布式协调服务来维护集群中的服务器状态**。Zookeeper 维护哪些服务器是活动的和可用的,并提供服务器故障通知。集群至少应该有 3 个节点。 + +ZooKeeper 的作用: + +- 保证任何时候,集群中只有一个 Master; +- 存储所有 Region 的寻址入口; +- 实时监控 Region Server 的状态,将 Region Server 的上线和下线信息实时通知给 Master; +- 存储 HBase 的 Schema,包括有哪些 Table,每个 Table 有哪些 Column Family 等信息。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/bigdata/hbase/1551166447147.png) + +以上,最重要的一点是 ZooKeeper 如何保证 HBase 集群中只有一个 Master Server 的呢? + +- 所有 Master Server 会竞争 Zookeeper 的 znode 锁(一个临时节点),只有一个 Master Server 能够创建成功,此时该 Master 就是主 Master。 +- 主 Master 会定期向 Zookeeper 发送心跳。从 Master 则通过 Watcher 机制对主 Master 所在节点进行监听。 +- 如果,主 Master 未能及时发送心跳,则其持有的 ZooKeeper 会话会过期,相应的 znode 锁(一个临时节点)会被自动删除。这会触发定义在该节点上的 Watcher 事件,所有从 Master 会得到通知,并再次开始竞争 znode 锁,直到完成主 Master 的选举。 + +HBase 内部保留名为 hbase:meta 的特殊目录表(catalog table)。它维护着当前集群上所有 region 的列表、状态和位置。hbase:meta 表中的项使用 region 作为键。region 名由所属的表名、region 的起始行、region的创建时间以及基于整体计算得出的 MD5 组成。 + +## HBase 读写流程 + +### 写入数据的流程 + +1. Client 向 Region Server 提交写请求; +2. Region Server 找到目标 Region; +3. Region 检查数据是否与 Schema 一致; +4. 如果客户端没有指定版本,则获取当前系统时间作为数据版本; +5. 将更新写入 WAL Log; +6. 将更新写入 Memstore; +7. 判断 Memstore 存储是否已满,如果存储已满则需要 flush 为 Store Hfile 文件。 + +> 更为详细写入流程可以参考:[HBase - 数据写入流程解析](http://hbasefly.com/2016/03/23/hbase_writer/) + +### 读取数据的流程 + +以下是客户端首次读写 HBase 上数据的流程: + +1. 客户端从 Zookeeper 获取 `META` 表所在的 Region Server; +2. 客户端访问 `META` 表所在的 Region Server,从 `META` 表中查询到访问行键所在的 Region Server,之后客户端将缓存这些信息以及 `META` 表的位置; +3. 客户端从行键所在的 Region Server 上获取数据。 + +如果再次读取,客户端将从缓存中获取行键所在的 Region Server。这样客户端就不需要再次查询 `META` 表,除非 Region 移动导致缓存失效,这样的话,则将会重新查询并更新缓存。 + +注:`META` 表是 HBase 中一张特殊的表,它保存了所有 Region 的位置信息,META 表自己的位置信息则存储在 ZooKeeper 上。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200601182655.png) + +> 更为详细读取数据流程参考: +> +> [HBase 原理-数据读取流程解析](http://hbasefly.com/2016/12/21/hbase-getorscan/) +> +> [HBase 原理-迟到的‘数据读取流程部分细节](http://hbasefly.com/2017/06/11/hbase-scan-2/) + +## 参考资料 + +- **官方** + - [HBase 官网](http://hbase.apache.org/) + - [HBase 官方文档](https://hbase.apache.org/book.html) + - [HBase 官方文档中文版](http://abloz.com/hbase/book.html) + - [HBase API](https://hbase.apache.org/apidocs/index.html) +- **教程** + - [BigData-Notes](https://github.com/heibaiying/BigData-Notes) +- **文章** + - [Bigtable: A Distributed Storage System for Structured Data](https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/bigtable-osdi06.pdf) + - [An In-Depth Look at the HBase Architecture](https://mapr.com/blog/in-depth-look-hbase-architecture/) + - [入门 HBase,看这一篇就够了](https://juejin.im/post/5c666cc4f265da2da53eb714) + - https://bighadoop.wordpress.com/tag/hbase/ \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/10.HBaseJavaApi\345\237\272\347\241\200\347\211\271\346\200\247.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/10.HBaseJavaApi\345\237\272\347\241\200\347\211\271\346\200\247.md" new file mode 100644 index 00000000..ec871c15 --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/10.HBaseJavaApi\345\237\272\347\241\200\347\211\271\346\200\247.md" @@ -0,0 +1,555 @@ +--- +title: HBase Java API 基础特性 +date: 2023-03-15 20:28:32 +categories: + - 数据库 + - 列式数据库 + - HBase +tags: + - 大数据 + - HBase +permalink: /pages/a8cad3/ +--- + +# HBase Java API 基础特性 + +## HBase Client API + +### HBase Java API 示例 + +引入依赖 + +```xml + + org.apache.hbase + hbase-client + 2.1.4 + +``` + +示例 + +```java +public class HBaseUtils { + + private static Connection connection; + + static { + Configuration configuration = HBaseConfiguration.create(); + configuration.set("hbase.zookeeper.property.clientPort", "2181"); + // 如果是集群 则主机名用逗号分隔 + configuration.set("hbase.zookeeper.quorum", "hadoop001"); + try { + connection = ConnectionFactory.createConnection(configuration); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * 创建 HBase 表 + * + * @param tableName 表名 + * @param columnFamilies 列族的数组 + */ + public static boolean createTable(String tableName, List columnFamilies) { + try { + HBaseAdmin admin = (HBaseAdmin) connection.getAdmin(); + if (admin.tableExists(TableName.valueOf(tableName))) { + return false; + } + TableDescriptorBuilder tableDescriptor = TableDescriptorBuilder.newBuilder(TableName.valueOf(tableName)); + columnFamilies.forEach(columnFamily -> { + ColumnFamilyDescriptorBuilder cfDescriptorBuilder = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(columnFamily)); + cfDescriptorBuilder.setMaxVersions(1); + ColumnFamilyDescriptor familyDescriptor = cfDescriptorBuilder.build(); + tableDescriptor.setColumnFamily(familyDescriptor); + }); + admin.createTable(tableDescriptor.build()); + } catch (IOException e) { + e.printStackTrace(); + } + return true; + } + + + /** + * 删除 hBase 表 + * + * @param tableName 表名 + */ + public static boolean deleteTable(String tableName) { + try { + HBaseAdmin admin = (HBaseAdmin) connection.getAdmin(); + // 删除表前需要先禁用表 + admin.disableTable(TableName.valueOf(tableName)); + admin.deleteTable(TableName.valueOf(tableName)); + } catch (Exception e) { + e.printStackTrace(); + } + return true; + } + + /** + * 插入数据 + * + * @param tableName 表名 + * @param rowKey 唯一标识 + * @param columnFamilyName 列族名 + * @param qualifier 列标识 + * @param value 数据 + */ + public static boolean putRow(String tableName, String rowKey, String columnFamilyName, String qualifier, + String value) { + try { + Table table = connection.getTable(TableName.valueOf(tableName)); + Put put = new Put(Bytes.toBytes(rowKey)); + put.addColumn(Bytes.toBytes(columnFamilyName), Bytes.toBytes(qualifier), Bytes.toBytes(value)); + table.put(put); + table.close(); + } catch (IOException e) { + e.printStackTrace(); + } + return true; + } + + + /** + * 插入数据 + * + * @param tableName 表名 + * @param rowKey 唯一标识 + * @param columnFamilyName 列族名 + * @param pairList 列标识和值的集合 + */ + public static boolean putRow(String tableName, String rowKey, String columnFamilyName, List> pairList) { + try { + Table table = connection.getTable(TableName.valueOf(tableName)); + Put put = new Put(Bytes.toBytes(rowKey)); + pairList.forEach(pair -> put.addColumn(Bytes.toBytes(columnFamilyName), Bytes.toBytes(pair.getKey()), Bytes.toBytes(pair.getValue()))); + table.put(put); + table.close(); + } catch (IOException e) { + e.printStackTrace(); + } + return true; + } + + + /** + * 根据 rowKey 获取指定行的数据 + * + * @param tableName 表名 + * @param rowKey 唯一标识 + */ + public static Result getRow(String tableName, String rowKey) { + try { + Table table = connection.getTable(TableName.valueOf(tableName)); + Get get = new Get(Bytes.toBytes(rowKey)); + return table.get(get); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + + /** + * 获取指定行指定列 (cell) 的最新版本的数据 + * + * @param tableName 表名 + * @param rowKey 唯一标识 + * @param columnFamily 列族 + * @param qualifier 列标识 + */ + public static String getCell(String tableName, String rowKey, String columnFamily, String qualifier) { + try { + Table table = connection.getTable(TableName.valueOf(tableName)); + Get get = new Get(Bytes.toBytes(rowKey)); + if (!get.isCheckExistenceOnly()) { + get.addColumn(Bytes.toBytes(columnFamily), Bytes.toBytes(qualifier)); + Result result = table.get(get); + byte[] resultValue = result.getValue(Bytes.toBytes(columnFamily), Bytes.toBytes(qualifier)); + return Bytes.toString(resultValue); + } else { + return null; + } + + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + + /** + * 检索全表 + * + * @param tableName 表名 + */ + public static ResultScanner getScanner(String tableName) { + try { + Table table = connection.getTable(TableName.valueOf(tableName)); + Scan scan = new Scan(); + return table.getScanner(scan); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + + /** + * 检索表中指定数据 + * + * @param tableName 表名 + * @param filterList 过滤器 + */ + + public static ResultScanner getScanner(String tableName, FilterList filterList) { + try { + Table table = connection.getTable(TableName.valueOf(tableName)); + Scan scan = new Scan(); + scan.setFilter(filterList); + return table.getScanner(scan); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + /** + * 检索表中指定数据 + * + * @param tableName 表名 + * @param startRowKey 起始 RowKey + * @param endRowKey 终止 RowKey + * @param filterList 过滤器 + */ + + public static ResultScanner getScanner(String tableName, String startRowKey, String endRowKey, + FilterList filterList) { + try { + Table table = connection.getTable(TableName.valueOf(tableName)); + Scan scan = new Scan(); + scan.withStartRow(Bytes.toBytes(startRowKey)); + scan.withStopRow(Bytes.toBytes(endRowKey)); + scan.setFilter(filterList); + return table.getScanner(scan); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + /** + * 删除指定行记录 + * + * @param tableName 表名 + * @param rowKey 唯一标识 + */ + public static boolean deleteRow(String tableName, String rowKey) { + try { + Table table = connection.getTable(TableName.valueOf(tableName)); + Delete delete = new Delete(Bytes.toBytes(rowKey)); + table.delete(delete); + } catch (IOException e) { + e.printStackTrace(); + } + return true; + } + + + /** + * 删除指定行指定列 + * + * @param tableName 表名 + * @param rowKey 唯一标识 + * @param familyName 列族 + * @param qualifier 列标识 + */ + public static boolean deleteColumn(String tableName, String rowKey, String familyName, + String qualifier) { + try { + Table table = connection.getTable(TableName.valueOf(tableName)); + Delete delete = new Delete(Bytes.toBytes(rowKey)); + delete.addColumn(Bytes.toBytes(familyName), Bytes.toBytes(qualifier)); + table.delete(delete); + table.close(); + } catch (IOException e) { + e.printStackTrace(); + } + return true; + } + +} +``` + +## 数据库连接 + +在上面的代码中,在类加载时就初始化了 Connection 连接,并且之后的方法都是复用这个 Connection,这时我们可能会考虑是否可以使用自定义连接池来获取更好的性能表现?实际上这是没有必要的。 + +首先官方对于 `Connection` 的使用说明如下: + +``` +Connection Pooling For applications which require high-end multithreaded +access (e.g., web-servers or application servers that may serve many +application threads in a single JVM), you can pre-create a Connection, +as shown in the following example: + +对于高并发多线程访问的应用程序(例如,在单个 JVM 中存在的为多个线程服务的 Web 服务器或应用程序服务器), +您只需要预先创建一个 Connection。例子如下: + +// Create a connection to the cluster. +Configuration conf = HBaseConfiguration.create(); +try (Connection connection = ConnectionFactory.createConnection(conf); + Table table = connection.getTable(TableName.valueOf(tablename))) { + // use table as needed, the table returned is lightweight +} +``` + +之所以能这样使用,这是因为 Connection 并不是一个简单的 socket 连接,[接口文档](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Connection.html) 中对 Connection 的表述是: + +``` +A cluster connection encapsulating lower level individual connections to actual servers and a +connection to zookeeper. Connections are instantiated through the ConnectionFactory class. +The lifecycle of the connection is managed by the caller, who has to close() the connection +to release the resources. + +Connection 是一个集群连接,封装了与多台服务器(Matser/Region Server)的底层连接以及与 zookeeper 的连接。 +连接通过 ConnectionFactory 类实例化。连接的生命周期由调用者管理,调用者必须使用 close() 关闭连接以释放资源。 +``` + +之所以封装这些连接,是因为 HBase 客户端需要连接三个不同的服务角色: + +- **Zookeeper** :主要用于获取 `meta` 表的位置信息,Master 的信息; +- **HBase Master** :主要用于执行 HBaseAdmin 接口的一些操作,例如建表等; +- **HBase RegionServer** :用于读、写数据。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230315202403.png) + +Connection 对象和实际的 Socket 连接之间的对应关系如下图: + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230315202426.png) + +在 HBase 客户端代码中,真正对应 Socket 连接的是 `RpcConnection` 对象。HBase 使用 `PoolMap` 这种数据结构来存储客户端到 HBase 服务器之间的连接。`PoolMap` 的内部有一个 `ConcurrentHashMap` 实例,其 key 是 `ConnectionId`(封装了服务器地址和用户 ticket),value 是一个 `RpcConnection` 对象的资源池。当 HBase 需要连接一个服务器时,首先会根据 `ConnectionId` 找到对应的连接池,然后从连接池中取出一个连接对象。 + +``` +@InterfaceAudience.Private +public class PoolMap implements Map { + private PoolType poolType; + + private int poolMaxSize; + + private Map> pools = new ConcurrentHashMap<>(); + + public PoolMap(PoolType poolType) { + this.poolType = poolType; + } + ..... +``` + +HBase 中提供了三种资源池的实现,分别是 `Reusable`,`RoundRobin` 和 `ThreadLocal`。具体实现可以通 `hbase.client.ipc.pool.type` 配置项指定,默认为 `Reusable`。连接池的大小也可以通过 `hbase.client.ipc.pool.size` 配置项指定,默认为 1,即每个 Server 1 个连接。也可以通过修改配置实现: + +``` +config.set("hbase.client.ipc.pool.type",...); +config.set("hbase.client.ipc.pool.size",...); +connection = ConnectionFactory.createConnection(config); +``` + +由此可以看出 HBase 中 Connection 类已经实现了对连接的管理功能,所以我们不必在 Connection 上在做额外的管理。 + +另外,Connection 是线程安全的,但 Table 和 Admin 却不是线程安全的,因此正确的做法是一个进程共用一个 Connection 对象,而在不同的线程中使用单独的 Table 和 Admin 对象。Table 和 Admin 的获取操作 `getTable()` 和 `getAdmin()` 都是轻量级,所以不必担心性能的消耗,同时建议在使用完成后显示的调用 `close()` 方法来关闭它们。 + +## 概述 + +HBase 的主要客户端操作是由 `org.apache.hadoop.hbase.client.HTable` 提供的。创建 HTable 实例非常耗时,所以,建议每个线程只创建一次 HTable 实例。 + +HBase 所有修改数据的操作都保证了行级别的原子性。要么读到最新的修改,要么等待系统允许写入改行修改 + +用户要尽量使用批处理(batch)更新来减少单独操作同一行数据的次数 + +写操作中设计的列的数目并不会影响该行数据的原子性,行原子性会同时保护到所有列 + +创建 HTable 实例(指的是在 java 中新建该类),每个实例都要扫描.META. 表,以检查该表是否存在,推荐用户只创建一次 HTable 实例,而且是每个线程创建一个 + +如果用户需要多个 HTable 实例,建议使用 HTablePool 类(类似连接池) + +## CRUD 操作 + +### put + +`Table` 接口提供了两个 `put` 方法 + +```java +// 写入单行 put +void put(Put put) throws IOException; +// 批量写入 put +void put(List puts) throws IOException; +``` + +Put 类提供了多种构造器方法用来初始化实例。 + +Put 类还提供了一系列有用的方法: + +多个 `add` 方法:用于添加指定的列数据。 + +`has` 方法:用于检查是否存在特定的单元格,而不需要遍历整个集合 + +`getFamilyMap` 方法:可以遍历 Put 实例中每一个可用的 KeyValue 实例 + +getRow 方法:用于获取 rowkey +Put.heapSize() 可以计算当前 Put 实例所需的堆大小,既包含其中的数据,也包含内部数据结构所需的空间 + +#### KeyValue 类 + +特定单元格的数据以及坐标,坐标包括行键、列族名、列限定符以及时间戳 +`KeyValue(byte[] row, int roffset, int rlength, byte[] family, int foffoset, int flength, byte[] qualifier, int qoffset, int qlength, long timestamp, Type type, byte[] value, int voffset, int vlength)` +每一个字节数组都有一个 offset 参数和一个 length 参数,允许用户提交一个已经存在的字节数组进行字节级别操作。 +行目前来说指的是行键,即 Put 构造器里的 row 参数。 + +#### 客户端的写缓冲区 + +每一个 put 操作实际上都是一个 RPC 操作,它将客户端数据传送到服务器然后返回。 + +HBase 的 API 配备了一个客户端的写缓冲区,缓冲区负责收集 put 操作,然后调用 RPC 操作一次性将 put 送往服务器。 + +```java +void setAutoFlush(boolean autoFlush) +boolean isAutoFlush() +``` + +默认情况下,客户端缓冲区是禁用的。可以通过 `table.setAutoFlush(false)` 来激活缓冲区。 + +#### Put 列表 + +批量提交 `put` 列表: + +```java +void put(List puts) throws IOException +``` + +注意:批量提交可能会有部分修改失败。 + +#### 原子性操作 compare-and-set + +`checkAndPut` 方法提供了 CAS 机制来保证 put 操作的原子性。 + +### get + +``` +Result get(Get get) throws IOException +``` + +```csharp +Get(byte[] row) +Get(byte[] row, RowLock rowLock) +Get addColumn(byte[] family, byte[] qualifier) +Get addFamily(byte[] family) +``` + +#### Result 类 + +当用户使用 `get()` 方法获取数据,HBase 返回的结果包含所有匹配的单元格数据,这些数据被封装在一个 `Result` 实例中返回给用户。 + +Result 类提供的方法如下: + +```java +byte[] getValue(byte[] family, byte[] qualifier) +byte[] value() +byte[] getRow() +int size() +boolean isEmpty() +KeyValue[] raw() +List list() +``` + +## delete + +``` +void delete(Delete delete) throws IOException +``` + +```csharp +Delte(byte[] row) +Delete(byte[] row, long timestamp, RowLock rowLock) +``` + +```csharp +Delete deleteFamily(byte[] family) +Delete deleteFamily(byte[] family, long timestamp) +Delete deleteColumns(byte[] family, byte[] qualifier) +Delete deleteColumn(byte[] family, byte[] qualifier) // 只删除最新版本 +``` + +## 批处理操作 + +Row 是 Put、Get、Delete 的父类。 + +```java +void batch(List actions, Object[] results) throws IOException, InterruptedException +Object batch(List actions) throws IOException, InterruptedException +``` + +## 行锁 + +region 服务器提供了行锁特性,这个特性保证了只有一个客户端能获取一行数据相应的锁,同时对该行进行修改。 + +如果不显示指定锁,服务器会隐式加锁。 + +## 扫描 + +scan,类似数据库系统中的 cursor,利用了 HBase 提供的底层顺序存储的数据结构。 + +调用 HTable 的 getScanner 就可以返回扫描器 + +```java +ResultScanner getScanner(Scan scan) throws IOException +ResultScanner getScanner(byte[] family) throws IOException +``` + +Scan 类构造器可以有 startRow,区间一般为 [startRow, stopRow) + +```csharp +Scan(byte[] startRow, Filter filter) +Scan(byte[] startRow) +``` + +### ResultScanner + +以行为单位进行返回 + +```java +Result next() throws IOException +Result[] next(int nbRows) throws IOException +void close() +``` + +### 缓存与批量处理 + +每一个 next()调用都会为每行数据生成一个单独的 RPC 请求 + +可以设置扫描器缓存 + +```cpp +void setScannerCaching(itn scannerCaching) +int getScannerCaching() +``` + +缓存是面向行一级操作,批量是面向列一级操作 + +```cpp +void setBatch(int batch) +int getBatch +``` + +RPC 请求的次数=(行数\*每行列数)/Min(每行的列数,批量大小)/扫描器缓存 + +## 各种特性 + +`Bytes` 类提供了一系列将原生 Java 类型和字节数组互转的方法。 + +## 参考资料 + +- [《HBase 权威指南》](https://item.jd.com/11321037.html) +- [《HBase 权威指南》官方源码](https://github.com/larsgeorge/hbase-book) diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/11.HBaseJavaApi\351\253\230\347\272\247\347\211\271\346\200\247\344\271\213\350\277\207\346\273\244\345\231\250.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/11.HBaseJavaApi\351\253\230\347\272\247\347\211\271\346\200\247\344\271\213\350\277\207\346\273\244\345\231\250.md" new file mode 100644 index 00000000..7dbbceb0 --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/11.HBaseJavaApi\351\253\230\347\272\247\347\211\271\346\200\247\344\271\213\350\277\207\346\273\244\345\231\250.md" @@ -0,0 +1,382 @@ +--- +title: HBase Java API 高级特性之过滤器 +date: 2023-03-16 09:45:10 +categories: + - 数据库 + - 列式数据库 + - HBase +tags: + - 大数据 + - HBase + - API +permalink: /pages/a3347e/ +--- + +# HBase Java API 高级特性之过滤器 + +HBase 中两种主要的数据读取方法是 `get()` 和 `scan()`,它们都支持直接访问数据和通过指定起止 row key 访问数据。此外,可以指定列族、列、时间戳和版本号来进行条件查询。它们的缺点是不支持细粒度的筛选功能。为了弥补这种不足,`Get` 和 `Scan` 支持通过过滤器(`Filter`)对 row key、列或列值进行过滤。 + +HBase 提供了一些内置过滤器,也允许用户通过继承 `Filter` 类来自定义过滤器。所有的过滤器都在服务端生效,称为 **谓词下推**。这样可以保证被过滤掉的数据不会被传到客户端。 + +![](https://www.oreilly.com/api/v2/epubs/9781449314682/files/httpatomoreillycomsourceoreillyimages889252.png) + +_图片来自 HBase 权威指南_ + +HBase 过滤器层次结构的最底层是 `Filter` 接口和 `FilterBase` 抽象类。大部分过滤器都直接继承自 `FilterBase`。 + +## 比较过滤器 + +所有比较过滤器均继承自 `CompareFilter`。`CompareFilter` 比 `FilterBase` 多了一个 `compare()` 方法,它需要传入参数定义比较操作的过程:比较运算符和比较器。 + +创建一个比较过滤器需要两个参数,分别是**比较运算符**和**比较器实例**。 + +``` + public CompareFilter(final CompareOp compareOp,final ByteArrayComparable comparator) { + this.compareOp = compareOp; + this.comparator = comparator; + } +``` + +### 比较运算符 + +- LESS (<) +- LESS_OR_EQUAL (<=) +- EQUAL (=) +- NOT_EQUAL (!=) +- GREATER_OR_EQUAL (>=) +- GREATER (>) +- NO_OP (排除所有符合条件的值) + +比较运算符均定义在枚举类 `CompareOperator` 中 + +``` +@InterfaceAudience.Public +public enum CompareOperator { + LESS, + LESS_OR_EQUAL, + EQUAL, + NOT_EQUAL, + GREATER_OR_EQUAL, + GREATER, + NO_OP, +} +``` + +> 注意:在 1.x 版本的 HBase 中,比较运算符定义在 `CompareFilter.CompareOp` 枚举类中,但在 2.0 之后这个类就被标识为 @deprecated ,并会在 3.0 移除。所以 2.0 之后版本的 HBase 需要使用 `CompareOperator` 这个枚举类。 + +### 比较器 + +所有比较器均继承自 `ByteArrayComparable` 抽象类,常用的有以下几种: + +- **BinaryComparator** : 使用 `Bytes.compareTo(byte [],byte [])` 按字典序比较指定的字节数组。 +- **BinaryPrefixComparator** : 按字典序与指定的字节数组进行比较,但只比较到这个字节数组的长度。 +- **RegexStringComparator** : 使用给定的正则表达式与指定的字节数组进行比较。仅支持 `EQUAL` 和 `NOT_EQUAL` 操作。 +- **SubStringComparator** : 测试给定的子字符串是否出现在指定的字节数组中,比较不区分大小写。仅支持 `EQUAL` 和 `NOT_EQUAL` 操作。 +- **NullComparator** :判断给定的值是否为空。 +- **BitComparator** :按位进行比较。 + +`BinaryPrefixComparator` 和 `BinaryComparator` 的区别不是很好理解,这里举例说明一下: + +在进行 `EQUAL` 的比较时,如果比较器传入的是 `abcd` 的字节数组,但是待比较数据是 `abcdefgh`: + +- 如果使用的是 `BinaryPrefixComparator` 比较器,则比较以 `abcd` 字节数组的长度为准,即 `efgh` 不会参与比较,这时候认为 `abcd` 与 `abcdefgh` 是满足 `EQUAL` 条件的; +- 如果使用的是 `BinaryComparator` 比较器,则认为其是不相等的。 + +### 比较过滤器种类 + +比较过滤器共有五个(Hbase 1.x 版本和 2.x 版本相同): + +- **RowFilter** :基于行键来过滤数据; +- **FamilyFilterr** :基于列族来过滤数据; +- **QualifierFilterr** :基于列限定符(列名)来过滤数据; +- **ValueFilterr** :基于单元格 (cell) 的值来过滤数据; +- **DependentColumnFilter** :指定一个参考列来过滤其他列的过滤器,过滤的原则是基于参考列的时间戳来进行筛选 。 + +前四种过滤器的使用方法相同,均只要传递比较运算符和运算器实例即可构建,然后通过 `setFilter` 方法传递给 `scan`: + +``` + Filter filter = new RowFilter(CompareOperator.LESS_OR_EQUAL, + new BinaryComparator(Bytes.toBytes("xxx"))); + scan.setFilter(filter); +``` + +`DependentColumnFilter` 的使用稍微复杂一点,这里单独做下说明。 + +### DependentColumnFilter + +可以把 `DependentColumnFilter` 理解为**一个 valueFilter 和一个时间戳过滤器的组合**。`DependentColumnFilter` 有三个带参构造器,这里选择一个参数最全的进行说明: + +``` +DependentColumnFilter(final byte [] family, final byte[] qualifier, + final boolean dropDependentColumn, final CompareOperator op, + final ByteArrayComparable valueComparator) +``` + +- **family** :列族 +- **qualifier** :列限定符(列名) +- **dropDependentColumn** :决定参考列是否被包含在返回结果内,为 true 时表示参考列被返回,为 false 时表示被丢弃 +- **op** :比较运算符 +- **valueComparator** :比较器 + +这里举例进行说明: + +``` +DependentColumnFilter dependentColumnFilter = new DependentColumnFilter( + Bytes.toBytes("student"), + Bytes.toBytes("name"), + false, + CompareOperator.EQUAL, + new BinaryPrefixComparator(Bytes.toBytes("xiaolan"))); +``` + +- 首先会去查找 `student:name` 中值以 `xiaolan` 开头的所有数据获得 `参考数据集`,这一步等同于 valueFilter 过滤器; +- 其次再用参考数据集中所有数据的时间戳去检索其他列,获得时间戳相同的其他列的数据作为 `结果数据集`,这一步等同于时间戳过滤器; +- 最后如果 `dropDependentColumn` 为 true,则返回 `参考数据集`+`结果数据集`,若为 false,则抛弃参考数据集,只返回 `结果数据集`。 + +## 专用过滤器 + +专用过滤器通常直接继承自 `FilterBase`,用于更特定的场景。 + +### 单列列值过滤器 (SingleColumnValueFilter) + +基于某列(参考列)的值决定某行数据是否被过滤。其实例有以下方法: + +- **setFilterIfMissing(boolean filterIfMissing)** :默认值为 false,即如果该行数据不包含参考列,其依然被包含在最后的结果中;设置为 true 时,则不包含; +- **setLatestVersionOnly(boolean latestVersionOnly)** :默认为 true,即只检索参考列的最新版本数据;设置为 false,则检索所有版本数据。 + +``` +SingleColumnValueFilter singleColumnValueFilter = new SingleColumnValueFilter( + "student".getBytes(), + "name".getBytes(), + CompareOperator.EQUAL, + new SubstringComparator("xiaolan")); +singleColumnValueFilter.setFilterIfMissing(true); +scan.setFilter(singleColumnValueFilter); +``` + +### 单列列值排除器 (SingleColumnValueExcludeFilter) + +`SingleColumnValueExcludeFilter` 继承自上面的 `SingleColumnValueFilter`,过滤行为与其相反。 + +### 行键前缀过滤器 (PrefixFilter) + +基于 RowKey 值决定某行数据是否被过滤。 + +``` +PrefixFilter prefixFilter = new PrefixFilter(Bytes.toBytes("xxx")); +scan.setFilter(prefixFilter); +``` + +### 列名前缀过滤器 (ColumnPrefixFilter) + +基于列限定符(列名)决定某行数据是否被过滤。 + +``` +ColumnPrefixFilter columnPrefixFilter = new ColumnPrefixFilter(Bytes.toBytes("xxx")); + scan.setFilter(columnPrefixFilter); +``` + +### 分页过滤器 (PageFilter) + +可以使用这个过滤器实现对结果按行进行分页,创建 PageFilter 实例的时候需要传入每页的行数。 + +``` +public PageFilter(final long pageSize) { + Preconditions.checkArgument(pageSize >= 0, "must be positive %s", pageSize); + this.pageSize = pageSize; + } +``` + +下面的代码体现了客户端实现分页查询的主要逻辑,这里对其进行一下解释说明: + +客户端进行分页查询,需要传递 `startRow`(起始 RowKey),知道起始 `startRow` 后,就可以返回对应的 pageSize 行数据。这里唯一的问题就是,对于第一次查询,显然 `startRow` 就是表格的第一行数据,但是之后第二次、第三次查询我们并不知道 `startRow`,只能知道上一次查询的最后一条数据的 RowKey(简单称之为 `lastRow`)。 + +我们不能将 `lastRow` 作为新一次查询的 `startRow` 传入,因为 scan 的查询区间是[startRow,endRow) ,即前开后闭区间,这样 `startRow` 在新的查询也会被返回,这条数据就重复了。 + +同时在不使用第三方数据库存储 RowKey 的情况下,我们是无法通过知道 `lastRow` 的下一个 RowKey 的,因为 RowKey 的设计可能是连续的也有可能是不连续的。 + +由于 Hbase 的 RowKey 是按照字典序进行排序的。这种情况下,就可以在 `lastRow` 后面加上 `0` ,作为 `startRow` 传入,因为按照字典序的规则,某个值加上 `0` 后的新值,在字典序上一定是这个值的下一个值,对于 HBase 来说下一个 RowKey 在字典序上一定也是等于或者大于这个新值的。 + +所以最后传入 `lastRow`+`0`,如果等于这个值的 RowKey 存在就从这个值开始 scan,否则从字典序的下一个 RowKey 开始 scan。 + +> 25 个字母以及数字字符,字典排序如下: +> +> ``` +> '0' < '1' < '2' < ... < '9' < 'a' < 'b' < ... < 'z' +> ``` + +分页查询主要实现逻辑: + +``` +byte[] POSTFIX = new byte[] { 0x00 }; +Filter filter = new PageFilter(15); + +int totalRows = 0; +byte[] lastRow = null; +while (true) { + Scan scan = new Scan(); + scan.setFilter(filter); + if (lastRow != null) { + // 如果不是首行 则 lastRow + 0 + byte[] startRow = Bytes.add(lastRow, POSTFIX); + System.out.println("start row: " + + Bytes.toStringBinary(startRow)); + scan.withStartRow(startRow); + } + ResultScanner scanner = table.getScanner(scan); + int localRows = 0; + Result result; + while ((result = scanner.next()) != null) { + System.out.println(localRows++ + ": " + result); + totalRows++; + lastRow = result.getRow(); + } + scanner.close(); + //最后一页,查询结束 + if (localRows == 0) break; +} +System.out.println("total rows: " + totalRows); +``` + +> 需要注意的是在多台 Regin Services 上执行分页过滤的时候,由于并行执行的过滤器不能共享它们的状态和边界,所以有可能每个过滤器都会在完成扫描前获取了 PageCount 行的结果,这种情况下会返回比分页条数更多的数据,分页过滤器就有失效的可能。 + +### 时间戳过滤器 (TimestampsFilter) + +``` +List list = new ArrayList<>(); +list.add(1554975573000L); +TimestampsFilter timestampsFilter = new TimestampsFilter(list); +scan.setFilter(timestampsFilter); +``` + +### 首次行键过滤器 (FirstKeyOnlyFilter) + +`FirstKeyOnlyFilter` 只扫描每行的第一列,扫描完第一列后就结束对当前行的扫描,并跳转到下一行。相比于全表扫描,其性能更好,通常用于行数统计的场景,因为如果某一行存在,则行中必然至少有一列。 + +``` +FirstKeyOnlyFilter firstKeyOnlyFilter = new FirstKeyOnlyFilter(); +scan.set(firstKeyOnlyFilter); +``` + +## 包装过滤器 + +包装过滤器就是通过包装其他过滤器以实现某些拓展的功能。 + +### SkipFilter 过滤器 + +`SkipFilter` 包装一个过滤器,当被包装的过滤器遇到一个需要过滤的 KeyValue 实例时,则拓展过滤整行数据。下面是一个使用示例: + +``` +// 定义 ValueFilter 过滤器 +Filter filter1 = new ValueFilter(CompareOperator.NOT_EQUAL, + new BinaryComparator(Bytes.toBytes("xxx"))); +// 使用 SkipFilter 进行包装 +Filter filter2 = new SkipFilter(filter1); +``` + +### WhileMatchFilter 过滤器 + +`WhileMatchFilter` 包装一个过滤器,当被包装的过滤器遇到一个需要过滤的 KeyValue 实例时,`WhileMatchFilter` 则结束本次扫描,返回已经扫描到的结果。下面是其使用示例: + +``` +Filter filter1 = new RowFilter(CompareOperator.NOT_EQUAL, + new BinaryComparator(Bytes.toBytes("rowKey4"))); + +Scan scan = new Scan(); +scan.setFilter(filter1); +ResultScanner scanner1 = table.getScanner(scan); +for (Result result : scanner1) { + for (Cell cell : result.listCells()) { + System.out.println(cell); + } +} +scanner1.close(); + +System.out.println("--------------------"); + +// 使用 WhileMatchFilter 进行包装 +Filter filter2 = new WhileMatchFilter(filter1); + +scan.setFilter(filter2); +ResultScanner scanner2 = table.getScanner(scan); +for (Result result : scanner1) { + for (Cell cell : result.listCells()) { + System.out.println(cell); + } +} +scanner2.close(); +rowKey0/student:name/1555035006994/Put/vlen=8/seqid=0 +rowKey1/student:name/1555035007019/Put/vlen=8/seqid=0 +rowKey2/student:name/1555035007025/Put/vlen=8/seqid=0 +rowKey3/student:name/1555035007037/Put/vlen=8/seqid=0 +rowKey5/student:name/1555035007051/Put/vlen=8/seqid=0 +rowKey6/student:name/1555035007057/Put/vlen=8/seqid=0 +rowKey7/student:name/1555035007062/Put/vlen=8/seqid=0 +rowKey8/student:name/1555035007068/Put/vlen=8/seqid=0 +rowKey9/student:name/1555035007073/Put/vlen=8/seqid=0 +-------------------- +rowKey0/student:name/1555035006994/Put/vlen=8/seqid=0 +rowKey1/student:name/1555035007019/Put/vlen=8/seqid=0 +rowKey2/student:name/1555035007025/Put/vlen=8/seqid=0 +rowKey3/student:name/1555035007037/Put/vlen=8/seqid=0 +``` + +可以看到被包装后,只返回了 `rowKey4` 之前的数据。 + +## FilterList + +以上都是讲解单个过滤器的作用,当需要多个过滤器共同作用于一次查询的时候,就需要使用 `FilterList`。`FilterList` 支持通过构造器或者 `addFilter` 方法传入多个过滤器。 + +``` +// 构造器传入 +public FilterList(final Operator operator, final List filters) +public FilterList(final List filters) +public FilterList(final Filter... filters) + +// 方法传入 + public void addFilter(List filters) + public void addFilter(Filter filter) +``` + +多个过滤器组合的结果由 `operator` 参数定义 ,其可选参数定义在 `Operator` 枚举类中。只有 `MUST_PASS_ALL` 和 `MUST_PASS_ONE` 两个可选的值: + +- **MUST_PASS_ALL** :相当于 AND,必须所有的过滤器都通过才认为通过; +- **MUST_PASS_ONE** :相当于 OR,只有要一个过滤器通过则认为通过。 + +``` +@InterfaceAudience.Public + public enum Operator { + /** !AND */ + MUST_PASS_ALL, + /** !OR */ + MUST_PASS_ONE + } +``` + +使用示例如下: + +``` +List filters = new ArrayList(); + +Filter filter1 = new RowFilter(CompareOperator.GREATER_OR_EQUAL, + new BinaryComparator(Bytes.toBytes("XXX"))); +filters.add(filter1); + +Filter filter2 = new RowFilter(CompareOperator.LESS_OR_EQUAL, + new BinaryComparator(Bytes.toBytes("YYY"))); +filters.add(filter2); + +Filter filter3 = new QualifierFilter(CompareOperator.EQUAL, + new RegexStringComparator("ZZZ")); +filters.add(filter3); + +FilterList filterList = new FilterList(filters); + +Scan scan = new Scan(); +scan.setFilter(filterList); +``` + +## 参考资料 + +- [《HBase 权威指南》](https://item.jd.com/11321037.html) +- [《HBase 权威指南》官方源码](https://github.com/larsgeorge/hbase-book) diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/12.HBaseJavaApi\351\253\230\347\272\247\347\211\271\346\200\247\344\271\213\345\215\217\345\244\204\347\220\206\345\231\250.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/12.HBaseJavaApi\351\253\230\347\272\247\347\211\271\346\200\247\344\271\213\345\215\217\345\244\204\347\220\206\345\231\250.md" new file mode 100644 index 00000000..b6db0202 --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/12.HBaseJavaApi\351\253\230\347\272\247\347\211\271\346\200\247\344\271\213\345\215\217\345\244\204\347\220\206\345\231\250.md" @@ -0,0 +1,24 @@ +--- +title: HBase Java API 高级特性之协处理器 +date: 2023-03-16 09:46:37 +categories: + - 数据库 + - 列式数据库 + - HBase +tags: + - 大数据 + - HBase + - API +permalink: /pages/5f1bc3/ +--- + +# HBase Java API 高级特性之协处理器 + +## 简述 + +在使用 HBase 时,如果你的数据量达到了数十亿行或数百万列,此时能否在查询中返回大量数据将受制于网络的带宽,即便网络状况允许,但是客户端的计算处理也未必能够满足要求。在这种情况下,协处理器(Coprocessors)应运而生。它允许你将业务计算代码放入在 RegionServer 的协处理器中,将处理好的数据再返回给客户端,这可以极大地降低需要传输的数据量,从而获得性能上的提升。同时协处理器也允许用户扩展实现 HBase 目前所不具备的功能,如权限校验、二级索引、完整性约束等。 + +## 参考资料 + +- [《HBase 权威指南》](https://item.jd.com/11321037.html) +- [《HBase 权威指南》官方源码](https://github.com/larsgeorge/hbase-book) diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/13.HBaseJavaApi\345\205\266\344\273\226\351\253\230\347\272\247\347\211\271\346\200\247.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/13.HBaseJavaApi\345\205\266\344\273\226\351\253\230\347\272\247\347\211\271\346\200\247.md" new file mode 100644 index 00000000..ee69608c --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/13.HBaseJavaApi\345\205\266\344\273\226\351\253\230\347\272\247\347\211\271\346\200\247.md" @@ -0,0 +1,157 @@ +--- +title: HBase Java API 其他高级特性 +date: 2023-03-31 16:20:27 +categories: + - 数据库 + - 列式数据库 + - HBase +tags: + - 大数据 + - HBase + - API +permalink: /pages/ce5ca0/ +--- + +# HBase Java API 其他高级特性 + +## 计数器 + +HBase 提供了一种高级功能:计数器(counter)。**HBase 计数器可以用于实时统计,无需延时较高的批量处理操作**。HBase 有一种机制可以将列当作计数器:即读取并修改(其实就是一种 CAS 模式),其保证了在一次操作中的原子性。否则,用户需要对一行数据加锁,然后读取数据,再对当前数据做加法,最后写回 HBase 并释放行锁,这一系列操作会引起大量的资源竞争问题。 + +早期的 HBase 版本会在每次计数器更新操作调用一次 RPC 请求,新版本中可以在一次 RPC 请求中完成多个计数器的更新操作,但是多个计数器必须在同一行。 + +### 计数器使用 Shell 命令行 + +计数器不需要初始化,创建一个新列时初始值为 0,第一次 `incr` 操作返回 1。 + +计数器使用 `incr` 命令,增量可以是正数也可以是负数,但是必须是长整数 Long: + +```bash +incr '','','',[''] +``` + +计数器使用的例子: + +```python +hbase(main):001:0> create 'counters','daily','weekly','monthly' +0 row(s) in 1.2260 seconds + +hbase(main):002:0> incr 'counters','20190301','daily:hites',1 +COUNTER VALUE = 1 + +hbase(main):003:0> incr'counters','20190301','daily:hites',1 +COUNTER VALUE = 2 + +hbase(main):004:0> get_counter 'counters','20190301','daily:hites' +COUNTER VALUE = 2 +``` + +需要注意的是,增加的参数必须是长整型 Long,如果按照错误的格式更新了计数器(如字符串格式),下次调用 `incr` 会得到错误的结果: + +```python +hbase(main):005:0> put 'counters','20190301','daily:clicks','1' +0 row(s) in 1.3250 seconds + +hbase(main):006:0> incr'counters','20190301','daily:clicks',1 +COUNTER VALUE = 3530822107858468865 +``` + +### 单计数器 + +操作一个计数器,类似 shell 命令 `incr` + +```java +HTable table = new HTable(conf, "counters"); + +long cnt1 = table.incrementColumnValue(Bytes.toBytes("20190301"), + Bytes.toBytes("daily"), + Bytes.toBytes("hits"), + 1L); + +long cnt2 = table.incrementColumnValue(Bytes.toBytes("20190301"), + Bytes.toBytes("daily"), + Bytes.toBytes("hits"), + 1L); + +long current = table.incrementColumnValue(Bytes.toBytes("20190301"), + Bytes.toBytes("daily"), + Bytes.toBytes("hits"), + 0); +``` + +### 多计数器 + +使用 `Table` 的 `increment()` 方法可以操作一行的多个计数器,需要构建 `Increment` 实例,并且指定行键: + +```cpp +HTable table = new HTable(conf, "counters"); + +Increment incr1 = new Increment(Bytes.toBytes("20190301")); +incr1.addColumn(Bytes.toBytes("daily"), Bytes.toBytes("clicks"),1); +incr1.addColumn(Bytes.toBytes("daily"), Bytes.toBytes("hits"), 1); +incr1.addColumn(Bytes.toBytes("weekly"), Bytes.toBytes("clicks"), 2); +incr1.addColumn(Bytes.toBytes("weekly"), Bytes.toBytes("hits"), 2); + +Result result = table.increment(incr1); +for(Cell cell : result.rawCells()) { + // ... +} +``` + +Increment 类还有一种构造器: + +```csharp +Increment(byte[] row, RowLock rowLock) +``` + +`rowLock` 参数可选,可以设置用户自定义锁,可以限制其他写程序操作此行,但是不保证读的操作性。 + +## 连接管理 + +### 连接管理简介 + +在 HBase Java API 中,`Connection` 类代表了一个集群连接,封装了与多台服务器(Matser/Region Server)的底层连接以及与 zookeeper 的连接。`Connection` 通过 `ConnectionFactory` 类实例化,而连接的生命周期则由调用者管理,调用者必须显示调用 `close()` 来释放连接。`Connection` 是线程安全的。创建 `Connection` 实例的开销很高,因此一个进程只需要实例化一个 `Connection` 即可。 + +`Table` 接口用于对指定的 HBase 表进行 CRUD 操作。一般,通过 `Connection` 获取 `Table` 实例,用完后,调用 `close()` 释放连接。 + +`Admin` 接口主要用于创建、删除、查看、启用/禁用 HBase 表,以及一些其他管理操作。一般,通过 `Connection` 获取 `Admin` 实例,用完后,调用 `close()` 释放连接。 + +`Table` 和 `Admin` 实例都是轻量级且并非线程安全的。建议每个线程只实例化一个 `Table` 或 `Admin` 实例。 + +### 连接池 + +问题:HBase 为什么没有提供 `Connection` 的连接池来获取更好的性能?是否需要自定义 `Connection` 连接池? + +答:不需要。官方对于 `Connection` 的使用说明中,明确指出:对于高并发多线程访问的应用程序,一个进程中只需要预先创建一个 `Connection`。 + +问题:HBase 老版本中 `HTablePool` 为什么废弃?是否需要自定义 Table 的连接池? + +答:不需要。Table 和 Admin 的连接本质上是复用 Connection,实例化是一个较为轻量级的操作,因此,并不需要缓存或池化。实际上,HBase Java API 官方就是这么建议的。 + +下面是管理 HBase 连接的一个正确编程模型 + +```java +// 所有进程共用一个 connection 对象 +connection = ConnectionFactory.createConnection(config); + +// 每个线程使用单独的 table 对象 +Table table = connection.getTable(TableName.valueOf("tableName")); +try { + ... +} finally { + table.close(); +} + +Admin admin = connection.getAdmin(); +try { + ... +} finally { + admin.close(); +} +``` + +## 参考资料 + +- [《HBase 权威指南》](https://item.jd.com/11321037.html) +- [《HBase 权威指南》官方源码](https://github.com/larsgeorge/hbase-book) +- [连接 HBase 的正确姿势](https://developer.aliyun.com/article/581702) diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/14.HBaseJavaApi\347\256\241\347\220\206\345\212\237\350\203\275.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/14.HBaseJavaApi\347\256\241\347\220\206\345\212\237\350\203\275.md" new file mode 100644 index 00000000..3915ce86 --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/14.HBaseJavaApi\347\256\241\347\220\206\345\212\237\350\203\275.md" @@ -0,0 +1,124 @@ +--- +title: HBase Java API 管理功能 +date: 2023-04-13 16:36:48 +categories: + - 数据库 + - 列式数据库 + - HBase +tags: + - 大数据 + - HBase + - API +permalink: /pages/b59ba2/ +--- + +# HBase Java API 管理功能 + +## 初始化 Admin 实例 + +```java +Configuration conf = HBaseConfiguration.create(); +Connection connection = ConnectionFactory.createConnection(conf); +Admin admin = connection.getAdmin(); +``` + +## 管理命名空间 + +### 查看命名空间 + +```java +TableName[] tableNames = admin.listTableNamesByNamespace("test"); +for (TableName tableName : tableNames) { + System.out.println(tableName.getName()); +} +``` + +### 创建命名空间 + +```java +NamespaceDescriptor namespace = NamespaceDescriptor.create("test").build(); +admin.createNamespace(namespace); +``` + +### 修改命名空间 + +```java +NamespaceDescriptor namespace = NamespaceDescriptor.create("test") + .addConfiguration("Description", "Test Namespace") + .build(); +admin.modifyNamespace(namespace); +``` + +### 删除命名空间 + +```java +admin.deleteNamespace("test"); +``` + +## 管理表 + +### 创建表 + +```java +TableName tableName = TableName.valueOf("test:test"); +HTableDescriptor tableDescriptor = new HTableDescriptor(tableName); +HColumnDescriptor columnDescriptor = new HColumnDescriptor(Bytes.toBytes("cf")); +tableDescriptor.addFamily(columnDescriptor); +admin.createTable(tableDescriptor); +``` + +### 删除表 + +```java +admin.deleteTable(TableName.valueOf("test:test")); +``` + +### 修改表 + +```java +// 原始表 +TableName tableName = TableName.valueOf("test:test"); +HColumnDescriptor columnDescriptor = new HColumnDescriptor("cf1"); +HTableDescriptor tableDescriptor = new HTableDescriptor(tableName) + .addFamily(columnDescriptor) + .setValue("Description", "Original Table"); +admin.createTable(tableDescriptor, Bytes.toBytes(1L), Bytes.toBytes(10000L), 50); + +// 修改表 +HTableDescriptor newTableDescriptor = admin.getTableDescriptor(tableName); +HColumnDescriptor newColumnDescriptor = new HColumnDescriptor("cf2"); +newTableDescriptor.addFamily(newColumnDescriptor) + .setMaxFileSize(1024 * 1024 * 1024L) + .setValue("Description", "Modified Table"); + +// 修改表必须先禁用再想修改 +admin.disableTable(tableName); +admin.modifyTable(tableName, newTableDescriptor); +``` + +### 禁用表 + +需要注意:HBase 表在删除前,必须先禁用。 + +```java +admin.disableTable(TableName.valueOf("test:test")); +``` + +### 启用表 + +``` +admin.enableTable(TableName.valueOf("test:test")); +``` + +### 查看表是否有效 + +```java +boolean isOk = admin.isTableAvailable(tableName); +System.out.println("Table available: " + isOk); +``` + +## 参考资料 + +- [《HBase 权威指南》](https://item.jd.com/11321037.html) +- [《HBase 权威指南》官方源码](https://github.com/larsgeorge/hbase-book) +- [连接 HBase 的正确姿势](https://developer.aliyun.com/article/581702) diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/21.HBase\350\277\220\347\273\264.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/21.HBase\350\277\220\347\273\264.md" new file mode 100644 index 00000000..3b9c76cb --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/21.HBase\350\277\220\347\273\264.md" @@ -0,0 +1,83 @@ +--- +title: HBase 运维 +date: 2019-05-07 20:19:25 +categories: + - 数据库 + - 列式数据库 + - HBase +tags: + - 大数据 + - HBase + - 运维 +permalink: /pages/f808fc/ +--- + +# HBase 运维 + +## 配置文件 + +- `backup-masters` - 默认情况下不存在。列出主服务器应在其上启动备份主进程的主机,每行一个主机。 +- `hadoop-metrics2-hbase.properties` - 用于连接 HBase Hadoop 的 Metrics2 框架。 +- `hbase-env.cmd` and hbase-env.sh - 用于 Windows 和 Linux / Unix 环境的脚本,用于设置 HBase 的工作环境,包括 Java,Java 选项和其他环境变量的位置。 +- `hbase-policy.xml` - RPC 服务器用于对客户端请求进行授权决策的默认策略配置文件。仅在启用 HBase 安全性时使用。 +- `hbase-site.xml` - 主要的 HBase 配置文件。此文件指定覆盖 HBase 默认配置的配置选项。您可以在 docs / hbase-default.xml 中查看(但不要编辑)默认配置文件。您还可以在 HBase Web UI 的 HBase 配置选项卡中查看群集的整个有效配置(默认值和覆盖)。 +- `log4j.properties` - log4j 日志配置。 +- `regionservers` - 包含应在 HBase 集群中运行 RegionServer 的主机列表。默认情况下,此文件包含单个条目 localhost。它应包含主机名或 IP 地址列表,每行一个,并且如果群集中的每个节点将在其 localhost 接口上运行 RegionServer,则应仅包含 localhost。 + +## 环境要求 + +- Java + - HBase 2.0+ 要求 JDK8+ + - HBase 1.2+ 要求 JDK7+ +- SSH - 环境要支持 SSH +- DNS - 环境中要在 hosts 配置本机 hostname 和本机 IP +- NTP - HBase 集群的时间要同步,可以配置统一的 NTP +- 平台 - 生产环境不推荐部署在 Windows 系统中 +- Hadoop - 依赖 Hadoop 配套版本 +- Zookeeper - 依赖 Zookeeper 配套版本 + +## 运行模式 + +### 单点 + +hbase-site.xml 配置如下: + +```xml + + + hbase.rootdir + hdfs://namenode.example.org:8020/hbase + + + hbase.cluster.distributed + false + + +``` + +### 分布式 + +hbase-site.xm 配置如下: + +```xml + + + hbase.rootdir + hdfs://namenode.example.org:8020/hbase + + + hbase.cluster.distributed + true + + + hbase.zookeeper.quorum + node-a.example.com,node-b.example.com,node-c.example.com + + +``` + +## 引用和引申 + +### 扩展阅读 + +- [Apache HBase Configuration](http://hbase.apache.org/book.html#configuration) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/22.HBase\345\221\275\344\273\244.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/22.HBase\345\221\275\344\273\244.md" new file mode 100644 index 00000000..209ef620 --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/22.HBase\345\221\275\344\273\244.md" @@ -0,0 +1,205 @@ +--- +title: HBase 命令 +date: 2020-06-02 22:28:18 +categories: + - 数据库 + - 列式数据库 + - HBase +tags: + - 大数据 + - HBase +permalink: /pages/263c40/ +--- + +# HBase 命令 + +> 进入 HBase Shell 控制台:`./bin/hbase shell` +> +> 如果有 kerberos 认证,需要事先使用相应的 keytab 进行一下认证(使用 kinit 命令),认证成功之后再使用 hbase shell 进入可以使用 whoami 命令可查看当前用户. + +## 基本命令 + +- 获取帮助信息:`help` +- 获取命令的详细帮助信息:`help 'status'` +- 查看服务器状态:`status` +- 查看版本信息:`version` +- 查看当前登录用户:`whoami` + +## DDL + +### 创建表 + +【语法】`create '表名称','列族名称 1','列族名称 2','列名称 N'` + +【示例】 + +```shell +# 创建一张名为 test 的表,columnFamliy1、columnFamliy2 是 table1 表的列族。 +create 'test','columnFamliy1','columnFamliy2' +``` + +### 启用、禁用表 + +- 启用表:`enable 'test'` +- 禁用表:`disable 'test'` +- 检查表是否被启用:`is_enabled 'test'` +- 检查表是否被禁用:`is_disabled 'test'` + +### 删除表 + +注意:删除表前需要先禁用表 + +```shell +disable 'test' +drop 'test' +``` + +### 修改表 + +#### 添加列族 + +**命令格式**: alter '表名', '列族名' + +```shell +alter 'test', 'teacherInfo' +``` + +#### 删除列族 + +**命令格式**:alter '表名', {NAME => '列族名', METHOD => 'delete'} + +```shell +alter 'test', {NAME => 'teacherInfo', METHOD => 'delete'} +``` + +#### 更改列族存储版本的限制 + +默认情况下,列族只存储一个版本的数据,如果需要存储多个版本的数据,则需要修改列族的属性。修改后可通过 `desc` 命令查看。 + +```shell +alter 'test',{NAME=>'columnFamliy1',VERSIONS=>3} +``` + +### 查看表 + +- 查看所有表:`list` +- 查看表的详细信息:`describe 'test'` +- 检查表是否存在:`exists 'test'` + +## 增删改 + +### 插入数据 + +**命令格式**:`put '表名', '行键','列族:列','值'` + +**注意:如果新增数据的行键值、列族名、列名与原有数据完全相同,则相当于更新操作** + +```shell +put 'test', 'rowkey1', 'columnFamliy1:a', 'valueA' +put 'test', 'rowkey1', 'columnFamliy1:b', 'valueB' +put 'test', 'rowkey1', 'columnFamliy1:c', 'valueC' + +put 'test', 'rowkey2', 'columnFamliy1:a', 'valueA' +put 'test', 'rowkey2', 'columnFamliy1:b', 'valueB' +put 'test', 'rowkey2', 'columnFamliy1:c', 'valueC' + +put 'test', 'rowkey3', 'columnFamliy1:a', 'valueA' +put 'test', 'rowkey3', 'columnFamliy1:b', 'valueB' +put 'test', 'rowkey3', 'columnFamliy1:c', 'valueC' + +put 'test', 'rowkey1', 'columnFamliy2:a', 'valueA' +put 'test', 'rowkey1', 'columnFamliy2:b', 'valueB' +put 'test', 'rowkey1', 'columnFamliy2:c', 'valueC' +``` + +### 获取指定行、列族、列 + +- 获取指定行中所有列的数据信息:`get 'test','rowkey2'` +- 获取指定行中指定列族下所有列的数据信息:`get 'test','rowkey2','columnFamliy1'` +- 获取指定行中指定列的数据信息:`get 'test','rowkey2','columnFamliy1:a'` + +### 删除指定行、列 + +- 删除指定行:`delete 'test','rowkey2'` +- 删除指定行中指定列的数据:`delete 'test','rowkey2','columnFamliy1:a'` + +## 查询 + +hbase 中访问数据有两种基本的方式: + +- 按指定 rowkey 获取数据:`get` 方法; +- 按指定条件获取数据:`scan` 方法。 + +`scan` 可以设置 begin 和 end 参数来访问一个范围内所有的数据。get 本质上就是 begin 和 end 相等的一种特殊的 scan。 + +### get 查询 + +- 获取指定行中所有列的数据信息:`get 'test','rowkey2'` +- 获取指定行中指定列族下所有列的数据信息:`get 'test','rowkey2','columnFamliy1'` +- 获取指定行中指定列的数据信息:`get 'test','rowkey2','columnFamliy1:a'` + +### scan 查询 + +#### 查询整表数据 + +```shell +scan 'test' +``` + +#### 查询指定列簇的数据 + +```shell +scan 'test', {COLUMN=>'columnFamliy1'} +``` + +#### 条件查询 + +```shell +# 查询指定列的数据 +scan 'test', {COLUMNS=> 'columnFamliy1:a'} +``` + +除了列 `(COLUMNS)` 修饰词外,HBase 还支持 `Limit`(限制查询结果行数),`STARTROW`(`ROWKEY` 起始行,会先根据这个 `key` 定位到 `region`,再向后扫描)、`STOPROW`(结束行)、`TIMERANGE`(限定时间戳范围)、`VERSIONS`(版本数)、和 `FILTER`(按条件过滤行)等。 + +如下代表从 `rowkey2` 这个 `rowkey` 开始,查找下两个行的最新 3 个版本的 name 列的数据: + +```shell +scan 'test', {COLUMNS=> 'columnFamliy1:a',STARTROW => 'rowkey2',STOPROW => 'rowkey3',LIMIT=>2, VERSIONS=>3} +``` + +#### 条件过滤 + +Filter 可以设定一系列条件来进行过滤。如我们要查询值等于 24 的所有数据: + +```shell +scan 'test', FILTER=>"ValueFilter(=,'binary:24')" +``` + +值包含 valueA 的所有数据: + +```shell +scan 'test', FILTER=>"ValueFilter(=,'substring:valueA')" +``` + +列名中的前缀为 b 的: + +```shell +scan 'test', FILTER=>"ColumnPrefixFilter('b')" +``` + +FILTER 中支持多个过滤条件通过括号、AND 和 OR 进行组合: + +```shell +# 列名中的前缀为 b 且列值中包含1998的数据 +scan 'test', FILTER=>"ColumnPrefixFilter('b') AND ValueFilter ValueFilter(=,'substring:A')" +``` + +`PrefixFilter` 用于对 Rowkey 的前缀进行判断: + +```shell +scan 'test', FILTER=>"PrefixFilter('wr')" +``` + +## 参考资料 + +- [Hbase 常用 Shell 命令](https://github.com/heibaiying/BigData-Notes/blob/master/notes/Hbase_Shell.md) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/README.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/README.md" new file mode 100644 index 00000000..588cb320 --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/README.md" @@ -0,0 +1,50 @@ +--- +title: HBase 教程 +date: 2020-09-09 17:53:08 +categories: + - 数据库 + - 列式数据库 + - HBase +tags: + - 大数据 + - HBase +permalink: /pages/417be6/ +hidden: true +--- + +# HBase 教程 + +## 📖 内容 + +- [HBase 快速入门](01.HBase快速入门.md) +- [HBase 数据模型](02.HBase数据模型.md) +- [HBase Schema 设计](03.HBaseSchema设计.md) +- [HBase 架构](04.HBase架构.md) +- [HBase Java API 基础特性](10.HBaseJavaApi基础特性.md) +- [HBase Java API 高级特性之过滤器](11.HBaseJavaApi高级特性之过滤器.md) +- [HBase Java API 高级特性之协处理器](12.HBaseJavaApi高级特性之协处理器.md) +- [HBase Java API 其他高级特性](13.HBaseJavaApi其他高级特性.md) +- [HBase 运维](21.HBase运维.md) +- [HBase 命令](22.HBase命令.md) +- HBase 配置 +- HBase 灾备 + +## 📚 资料 + +- **官方** + - [HBase 官网](http://hbase.apache.org/) + - [HBase 官方文档](https://hbase.apache.org/book.html) + - [HBase 官方文档中文版](http://abloz.com/hbase/book.html) + - [HBase API](https://hbase.apache.org/apidocs/index.html) +- **教程** + - [BigData-Notes](https://github.com/heibaiying/BigData-Notes) +- **书籍** + - [《Hadoop 权威指南(第四版)》](https://item.jd.com/12109713.html) +- **文章** + - [Bigtable: A Distributed Storage System for Structured Data](https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/bigtable-osdi06.pdf) + - [Intro to HBase](https://www.slideshare.net/alexbaranau/intro-to-hbase) + - [深入理解 Hbase 架构](https://segmentfault.com/a/1190000019959411) + +## 🚪 传送 + +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ diff --git a/docs/nosql/cassandra.md "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/02.Cassandra.md" similarity index 87% rename from docs/nosql/cassandra.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/02.Cassandra.md" index 25b97bbf..7a18759d 100644 --- a/docs/nosql/cassandra.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/02.Cassandra.md" @@ -1,17 +1,22 @@ +--- +title: Cassandra +date: 2019-08-22 09:02:39 +categories: + - 数据库 + - 列式数据库 +tags: + - 数据库 + - 列式数据库 + - Cassandra +permalink: /pages/ca3ca5/ +--- + # Cassandra > Apache Cassandra 是一个高度可扩展的分区行存储。行被组织成具有所需主键的表。 > > 最新版本:v4.0 - - -- [Quick Start](#quick-start) -- [简介](#简介) -- [更多内容](#更多内容) - - - ## Quick Start ### 安装 @@ -51,4 +56,4 @@ Cassandra 的主要特点就是它不是一个数据库,而是由一堆数据 ## :door: 传送门 -| [我的 Github 博客](https://github.com/dunwu/blog) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) | +| [钝悟的博客](https://dunwu.github.io/blog/) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) | \ No newline at end of file diff --git a/docs/nosql/elasticsearch/elasticsearch-interview.md "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/01.Elasticsearch\351\235\242\350\257\225\346\200\273\347\273\223.md" similarity index 99% rename from docs/nosql/elasticsearch/elasticsearch-interview.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/01.Elasticsearch\351\235\242\350\257\225\346\200\273\347\273\223.md" index 382c866d..a81581f8 100644 --- a/docs/nosql/elasticsearch/elasticsearch-interview.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/01.Elasticsearch\351\235\242\350\257\225\346\200\273\347\273\223.md" @@ -1,4 +1,19 @@ -# Elasticsearch 面试 +--- +title: Elasticsearch 面试总结 +date: 2020-06-16 07:10:44 +categories: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch +tags: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch + - 面试 +permalink: /pages/0cb563/ +--- + +# Elasticsearch 面试总结 ## 集群部署 @@ -628,4 +643,4 @@ d(x,y) + d(y,z) >= d(x,z) -- 三角不等式 (3)查询相似词如下:计算单词与根节点的编辑距离 d,然后递归查找每个子节点标号为 d-n 到 d+n(包含)的边。假如被检查的节点与搜索单词的距离 d 小于 n,则返回该节点并继续查询。比如输入 cape 且最大容忍距离为 1,则先计算和根的编辑距离 d(“book”, “cape”)=4,然后接着找和根节点之间编辑距离为 3 到 5 的,这个就找到了 cake 这个节点,计算 d(“cake”, “cape”)=1,满足条件所以返回 cake,然后再找和 cake 节点编辑距离是 0 到 2 的,分别找到 cape 和 cart 节点,这样就得到 cape 这个满足条件的结果。 -![img](https://pic4.zhimg.com/80/v2-79f2a89041e546d9feccf55e4ff1c0d7_720w.jpg) +![img](https://pic4.zhimg.com/80/v2-79f2a89041e546d9feccf55e4ff1c0d7_720w.jpg) \ No newline at end of file diff --git "a/docs/nosql/elasticsearch/Elasticsearch\345\277\253\351\200\237\345\205\245\351\227\250.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/02.Elasticsearch\345\277\253\351\200\237\345\205\245\351\227\250.md" similarity index 97% rename from "docs/nosql/elasticsearch/Elasticsearch\345\277\253\351\200\237\345\205\245\351\227\250.md" rename to "docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/02.Elasticsearch\345\277\253\351\200\237\345\205\245\351\227\250.md" index 50fb00f4..2ba7638b 100644 --- "a/docs/nosql/elasticsearch/Elasticsearch\345\277\253\351\200\237\345\205\245\351\227\250.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/02.Elasticsearch\345\277\253\351\200\237\345\205\245\351\227\250.md" @@ -1,3 +1,17 @@ +--- +title: Elasticsearch 快速入门 +date: 2020-06-16 07:10:44 +categories: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch +tags: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch +permalink: /pages/98c3a5/ +--- + # Elasticsearch 快速入门 > **[Elasticsearch](https://github.com/elastic/elasticsearch) 是一个分布式、RESTful 风格的搜索和数据分析引擎**,能够解决不断涌现出的各种用例。 作为 Elastic Stack 的核心,它集中存储您的数据,帮助您发现意料之中以及意料之外的情况。 @@ -6,7 +20,7 @@ > > _以下简称 ES_。 -## 一、Elasticsearch 简介 +## Elasticsearch 简介 ### 什么是 Elasticsearch @@ -91,7 +105,7 @@ Document 使用 JSON 格式表示,下面是一个例子。 | type | 数据表 | | docuemnt | 一行数据 | -## 二、ElasticSearch 基本原理 +## ElasticSearch 基本原理 ### ES 写数据过程 @@ -100,7 +114,7 @@ Document 使用 JSON 格式表示,下面是一个例子。 - 实际的 node 上的 `primary shard` 处理请求,然后将数据同步到 `replica node`。 - `coordinating node` 如果发现 `primary node` 和所有 `replica node` 都搞定之后,就返回响应结果给客户端。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210712104055.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210712104055.png) ### ES 读数据过程 @@ -231,4 +245,4 @@ buffer 每 refresh 一次,就会产生一个 `segment file`,所以默认情 - [Install Elasticsearch with RPM](https://www.elastic.co/guide/en/elasticsearch/reference/current/rpm.html#rpm) - [https://www.ruanyifeng.com/blog/2017/08/elasticsearch.html](https://www.ruanyifeng.com/blog/2017/08/elasticsearch.html) - [es-introduction](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/es-introduction.md) - - [es-write-query-search](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/es-write-query-search.md) + - [es-write-query-search](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/es-write-query-search.md) \ No newline at end of file diff --git "a/docs/nosql/elasticsearch/Elasticsearch\347\256\200\344\273\213.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/03.Elasticsearch\347\256\200\344\273\213.md" similarity index 94% rename from "docs/nosql/elasticsearch/Elasticsearch\347\256\200\344\273\213.md" rename to "docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/03.Elasticsearch\347\256\200\344\273\213.md" index a7ed3944..74f2c01d 100644 --- "a/docs/nosql/elasticsearch/Elasticsearch\347\256\200\344\273\213.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/03.Elasticsearch\347\256\200\344\273\213.md" @@ -1,3 +1,17 @@ +--- +title: Elasticsearch 简介 +date: 2022-02-22 21:01:01 +categories: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch +tags: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch +permalink: /pages/0fb506/ +--- + # Elasticsearch 简介 Elasticsearch 是一个基于 Lucene 的搜索和数据分析工具,它提供了一个分布式服务。Elasticsearch 是遵从 Apache 开源条款的一款开源产品,是当前主流的企业级搜索引擎。 @@ -9,31 +23,14 @@ Elasticsearch 是一个基于 Lucene 的搜索和数据分析工具,它提供 - StackOverflow 结合全文搜索与地理位置查询,以及**more-like-this**功能来找到相关的问题和答案。 - Github 使用 Elasticsearch 检索 1300 亿行的代码。 - - -- [1. Elasticsearch 特点](#1-elasticsearch-特点) -- [2. Elasticsearch 发展历史](#2-elasticsearch-发展历史) -- [3. Elasticsearch 概念](#3-elasticsearch-概念) - - [3.1. 近实时(NRT)](#31-近实时nrt) - - [3.2. 索引(Index)](#32-索引index) - - [3.3. ~~类型(Type)~~](#33-类型type) - - [3.4. 文档(Document)](#34-文档document) - - [3.5. 节点(Node)](#35-节点node) - - [3.6. 集群(Cluster)](#36-集群cluster) - - [3.7. 分片(Shards)](#37-分片shards) - - [3.8. 副本(Replicas)](#38-副本replicas) -- [4. 参考资料](#4-参考资料) - - - -## 1. Elasticsearch 特点 +## Elasticsearch 特点 - 分布式的实时文件存储,每个字段都被索引并可被搜索; - 分布式的实时分析搜索引擎; - 可弹性扩展到上百台服务器规模,处理 PB 级结构化或非结构化数据; - 开箱即用(安装即可使用),它提供了许多合理的缺省值,并对初学者隐藏了复杂的搜索引擎理论。只需很少的学习既可在生产环境中使用。 -## 2. Elasticsearch 发展历史 +## Elasticsearch 发展历史 - 2010 年 2 月 8 日,Elasticsearch 第一个公开版本发布。 @@ -89,15 +86,15 @@ Elasticsearch 是一个基于 Lucene 的搜索和数据分析工具,它提供 - 引入了真正的内存断路器,它可以更精准地检测出无法处理的请求,并防止它们使单个节点不稳定; - Zen2 是 Elasticsearch 的全新集群协调层,提高了可靠性、性能和用户体验,变得更快、更安全,并更易于使用。 -## 3. Elasticsearch 概念 +## Elasticsearch 概念 下列有一些概念是 Elasticsearch 的核心。从一开始就理解这些概念将极大地帮助简化学习 Elasticsearch 的过程。 -### 3.1. 近实时(NRT) +### 近实时(NRT) Elasticsearch 是一个近乎实时的搜索平台。这意味着**从索引文档到可搜索文档的时间有一点延迟**(通常是一秒)。 -### 3.2. 索引(Index) +### 索引(Index) 索引在不同语境,有着不同的含义 @@ -125,7 +122,7 @@ Elasticsearch 是一个近乎实时的搜索平台。这意味着**从索引文 #### 倒排索引 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220108215559.PNG) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220108215559.PNG) #### index template @@ -288,13 +285,13 @@ GET my_index/_search?q=full_name:John DELETE my_index ``` -### 3.3. ~~类型(Type)~~ +### ~~类型(Type)~~ ~~type 是一个逻辑意义上的分类或者叫分区,允许在同一索引中建立多个 type。本质是相当于一个过滤条件,高版本将会废弃 type 概念。~~ > ~~**6.0.0 版本及之后,废弃 type**~~ -### 3.4. 文档(Document) +### 文档(Document) Elasticsearch 是面向文档的,**文档是所有可搜索数据的最小单位**。 @@ -338,7 +335,7 @@ Elasticsearch 使用 [_JSON_](http://en.wikipedia.org/wiki/Json) 作为文档的 } ``` -### 3.5. 节点(Node) +### 节点(Node) #### 节点简介 @@ -376,7 +373,7 @@ Elasticsearch 实例本质上是一个 Java 进程。一台机器上可以运行 > > 开发环境中一个节点可以承担多种角色。但是,在生产环境中,节点应该设置为单一角色。 -### 3.6. 集群(Cluster) +### 集群(Cluster) #### 集群简介 @@ -420,7 +417,7 @@ Elasticsearch 的集群监控信息中包含了许多的统计数据,其中最 - **`yellow`**:所有的主分片都正常运行,但不是所有的副本分片都正常运行。 - **`red`**:有主分片没能正常运行。 -### 3.7. 分片(Shards) +### 分片(Shards) #### 分片简介 @@ -456,7 +453,7 @@ Elasticsearch 是利用分片将数据分发到集群内各处的。分片是数 - 影响搜索结果的相关性打分,影响统计结果的准确性 - 单节点上过多的分片,会导致资源浪费,同时也会影响性能 -### 3.8. 副本(Replicas) +### 副本(Replicas) 副本主要是针对主分片(Shards)的复制,Elasticsearch 中主分片可以拥有 0 个或多个的副本。 @@ -469,7 +466,7 @@ Elasticsearch 是利用分片将数据分发到集群内各处的。分片是数 > 每个 Elasticsearch 分片都是 Lucene 索引。单个 Lucene 索引中可以包含最大数量的文档。截止 LUCENE-5843,限制是 2,147,483,519(= `Integer.MAX_VALUE` - 128)文档。您可以使用\_cat/shardsAPI 监控分片大小。 -## 4. 参考资料 +## 参考资料 - [Elasticsearch 官网](https://www.elastic.co/) -- [Elasticsearch 简介](https://www.knowledgedict.com/tutorial/elasticsearch-intro.html) +- [Elasticsearch 简介](https://www.knowledgedict.com/tutorial/elasticsearch-intro.html) \ No newline at end of file diff --git "a/docs/nosql/elasticsearch/Elasticsearch\347\264\242\345\274\225\347\256\241\347\220\206.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/04.Elasticsearch\347\264\242\345\274\225.md" similarity index 94% rename from "docs/nosql/elasticsearch/Elasticsearch\347\264\242\345\274\225\347\256\241\347\220\206.md" rename to "docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/04.Elasticsearch\347\264\242\345\274\225.md" index b2d3659b..f4ac7d86 100644 --- "a/docs/nosql/elasticsearch/Elasticsearch\347\264\242\345\274\225\347\256\241\347\220\206.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/04.Elasticsearch\347\264\242\345\274\225.md" @@ -1,33 +1,25 @@ -# Elasticsearch 索引管理 - - - -- [1. 索引管理操作](#1-索引管理操作) - - [1.1. 索引删除](#11-索引删除) - - [1.2. 索引别名](#12-索引别名) -- [2. Settings 详解](#2-settings-详解) - - [2.1. 固定属性](#21-固定属性) - - [2.2. 索引静态配置](#22-索引静态配置) - - [2.3. 索引动态配置](#23-索引动态配置) -- [3. Mapping 详解](#3-mapping-详解) - - [3.1. 映射分类](#31-映射分类) - - [3.2. 基础类型](#32-基础类型) - - [3.3. 复杂类型](#33-复杂类型) - - [3.4. 特殊类型](#34-特殊类型) - - [3.5. Mapping 属性](#35-mapping-属性) -- [4. 索引查询](#4-索引查询) - - [4.1. 多个 index、多个 type 查询](#41-多个index多个type查询) - - [4.2. URI 搜索](#42-uri搜索) - - [4.3. 查询流程](#43-查询流程) -- [5. 参考资料](#5-参考资料) - - - -## 1. 索引管理操作 +--- +title: Elasticsearch 索引 +date: 2022-02-22 21:01:01 +categories: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch +tags: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch + - 索引 +permalink: /pages/293175/ +--- + +# Elasticsearch 索引 + +## 索引管理操作 Elasticsearch 索引管理主要包括如何进行索引的创建、索引的删除、副本的更新、索引读写权限、索引别名的配置等等内容。 -### 1.1. 索引删除 +### 索引删除 ES 索引删除操作向 ES 集群的 http 接口发送指定索引的 delete http 请求即可,可以通过 curl 命令,具体如下: @@ -49,7 +41,7 @@ curl -X DELETE http://10.10.10.66:9200/my_index?pretty } ``` -### 1.2. 索引别名 +### 索引别名 ES 的索引别名就是给一个索引或者多个索引起的另一个名字,典型的应用场景是针对索引使用的平滑切换。 @@ -87,7 +79,7 @@ POST /_aliases ES 索引别名有个典型的应用场景是平滑切换,更多细节可以查看 [Elasticsearch(ES)索引零停机(无需重启)无缝平滑切换的方法](https://www.knowledgedict.com/tutorial/elasticsearch-index-smooth-shift.html)。 -## 2. Settings 详解 +## Settings 详解 Elasticsearch 索引的配置项主要分为**静态配置属性**和**动态配置属性**,静态配置属性是索引创建后不能修改,而动态配置属性则可以随时修改。 @@ -179,13 +171,13 @@ PUT /my_index } ``` -### 2.1. 固定属性 +### 固定属性 - **_`index.creation_date`_**:顾名思义索引的创建时间戳。 - **_`index.uuid`_**:索引的 uuid 信息。 - **_`index.version.created`_**:索引的版本号。 -### 2.2. 索引静态配置 +### 索引静态配置 - **_`index.number_of_shards`_**:索引的主分片数,默认值是 **_`5`_**。这个配置在索引创建后不能修改;在 es 层面,可以通过 **_`es.index.max_number_of_shards`_** 属性设置索引最大的分片数,默认为 **_`1024`_**。 - **_`index.codec`_**:数据存储的压缩算法,默认值为 **_`LZ4`_**,可选择值还有 **_`best_compression`_**,它比 LZ4 可以获得更好的压缩比(即占据较小的磁盘空间,但存储性能比 LZ4 低)。 @@ -202,12 +194,12 @@ PUT /my_index - **_`filter`_**:定义新的 token filter,如同义词 filter。 - **_`analyzer`_**:配置新的分析器,一般是 char_filter、tokenizer 和一些 token filter 的组合。 -### 2.3. 索引动态配置 +### 索引动态配置 - **_`index.number_of_replicas`_**:索引主分片的副本数,默认值是 **_`1`_**,该值必须大于等于 0,这个配置可以随时修改。 - **_`index.refresh_interval`_**:执行新索引数据的刷新操作频率,该操作使对索引的最新更改对搜索可见,默认为 **_`1s`_**。也可以设置为 **_`-1`_** 以禁用刷新。更详细信息参考 [Elasticsearch 动态修改 refresh_interval 刷新间隔设置](https://www.knowledgedict.com/tutorial/elasticsearch-refresh_interval-settings.html)。 -## 3. Mapping 详解 +## Mapping 详解 在 Elasticsearch 中,**`Mapping`**(映射),用来定义一个文档以及其所包含的字段如何被存储和索引,可以在映射中事先定义字段的数据类型、字段的权重、分词器等属性,就如同在关系型数据库中创建数据表时会设置字段的类型。 @@ -219,7 +211,7 @@ Mapping 会把 json 文档映射成 Lucene 所需要的扁平格式 - 一个 Type 有一个 Mapping 定义 - 7.0 开始,不需要在 Mapping 定义中指定 type 信息 -### 3.1. 映射分类 +### 映射分类 在 Elasticsearch 中,映射可分为静态映射和动态映射。在关系型数据库中写入数据之前首先要建表,在建表语句中声明字段的属性,在 Elasticsearch 中,则不必如此,Elasticsearch 最重要的功能之一就是让你尽可能快地开始探索数据,文档写入 Elasticsearch 中,它会根据字段的类型自动识别,这种机制称为**动态映射**,而**静态映射**则是写入数据之前对字段的属性进行手工设置。 @@ -316,7 +308,7 @@ PUT books/it/1 - **`false`**:忽略新的字段。 - **`strict`**:严格模式,发现新的字段抛出异常。 -### 3.2. 基础类型 +### 基础类型 | 类型 | 关键字 | | :--------- | :------------------------------------------------------------------ | @@ -327,7 +319,7 @@ PUT books/it/1 | 二进制类型 | binary | | 范围类型 | range | -### 3.3. 复杂类型 +### 复杂类型 | 类型 | 关键字 | | :------- | :----- | @@ -335,7 +327,7 @@ PUT books/it/1 | 对象类型 | object | | 嵌套类型 | nested | -### 3.4. 特殊类型 +### 特殊类型 | 类型 | 关键字 | | :----------- | :---------- | @@ -347,7 +339,7 @@ PUT books/it/1 | 附件类型 | attachment | | 抽取类型 | percolator | -### 3.5. Mapping 属性 +### Mapping 属性 Elasticsearch 的 mapping 中的字段属性非常多,具体如下表格: @@ -365,9 +357,9 @@ Elasticsearch 的 mapping 中的字段属性非常多,具体如下表格: | **_`search_analyzer`_** | 指定搜索时的分析器,搜索时的优先级最高。 | | **_`null_value`_** | 用于需要对 Null 值实现搜索的场景,只有 Keyword 类型支持此配置。 | -## 4. 索引查询 +## 索引查询 -### 4.1. 多个 index、多个 type 查询 +### 多个 index、多个 type 查询 Elasticsearch 的搜索 api 支持**一个索引(index)的多个类型(type)查询**以及**多个索引(index)**的查询。 @@ -401,7 +393,7 @@ GET /_all/_search?q=tag:wow GET /_search?q=tag:wow ``` -### 4.2. URI 搜索 +### URI 搜索 Elasticsearch 支持用 uri 搜索,可用 get 请求里面拼接相关的参数,并用 curl 相关的命令就可以进行测试。 @@ -468,13 +460,13 @@ URI 中允许的参数: | search_type | 搜索的方式,可以是*dfs_query_then_fetch*或*query_then_fetch*。默认为*query_then_fetch* | | allow_partial_search_results | 是否可以返回部分结果。如设置为 false,表示如果请求产生部分结果,则设置为返回整体故障;默认为 true,表示允许请求在超时或部分失败的情况下获得部分结果 | -### 4.3. 查询流程 +### 查询流程 在 Elasticsearch 中,查询是一个比较复杂的执行模式,因为我们不知道那些 document 会被匹配到,任何一个 shard 上都有可能,所以一个 search 请求必须查询一个索引或多个索引里面的所有 shard 才能完整的查询到我们想要的结果。 找到所有匹配的结果是查询的第一步,来自多个 shard 上的数据集在分页返回到客户端之前会被合并到一个排序后的 list 列表,由于需要经过一步取 top N 的操作,所以 search 需要进过两个阶段才能完成,分别是 query 和 fetch。 -## 5. 参考资料 +## 参考资料 - [Elasticsearch 官网](https://www.elastic.co/) -- [Elasticsearch 索引映射类型及 mapping 属性详解](https://www.knowledgedict.com/tutorial/elasticsearch-index-mapping.html) +- [Elasticsearch 索引映射类型及 mapping 属性详解](https://www.knowledgedict.com/tutorial/elasticsearch-index-mapping.html) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/05.Elasticsearch\346\230\240\345\260\204.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/05.Elasticsearch\346\230\240\345\260\204.md" new file mode 100644 index 00000000..acd1f5bc --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/05.Elasticsearch\346\230\240\345\260\204.md" @@ -0,0 +1,354 @@ +--- +title: Elasticsearch 映射 +date: 2022-05-16 19:54:24 +categories: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch +tags: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch + - 索引 +permalink: /pages/d1bae4/ +--- + +# Elasticsearch 映射 + +在 Elasticsearch 中,**`Mapping`**(映射),用来定义一个文档以及其所包含的字段如何被存储和索引,可以在映射中事先定义字段的数据类型、字段的权重、分词器等属性,就如同在关系型数据库中创建数据表时会设置字段的类型。 + +Mapping 会把 JSON 文档映射成 Lucene 所需要的扁平格式 + +一个 Mapping 属于一个索引的 Type + +- 每个文档都属于一个 Type +- 一个 Type 有一个 Mapping 定义 +- 7.0 开始,不需要在 Mapping 定义中指定 type 信息 + +每个 `document` 都是 `field` 的集合,每个 `field` 都有自己的数据类型。映射数据时,可以创建一个 `mapping`,其中包含与 `document` 相关的 `field` 列表。映射定义还包括元数据 `field`,例如 `_source` ,它自定义如何处理 `document` 的关联元数据。 + +## 映射方式 + +在 Elasticsearch 中,映射可分为静态映射和动态映射。在关系型数据库中写入数据之前首先要建表,在建表语句中声明字段的属性,在 Elasticsearch 中,则不必如此,Elasticsearch 最重要的功能之一就是让你尽可能快地开始探索数据,文档写入 Elasticsearch 中,它会根据字段的类型自动识别,这种机制称为**动态映射**,而**静态映射**则是写入数据之前对字段的属性进行手工设置。 + +### 静态映射 + +ES 官方将静态映射称为**显式映射([Explicit mapping](https://www.elastic.co/guide/en/elasticsearch/reference/current/explicit-mapping.html))**。**静态映射**是在创建索引时显示的指定索引映射。静态映射和 SQL 中在建表语句中指定字段属性类似。相比动态映射,通过静态映射可以添加更详细、更精准的配置信息。例如: + +- 哪些字符串字段应被视为全文字段。 +- 哪些字段包含数字、日期或地理位置。 +- 日期值的格式。 +- 用于控制动态添加字段的自定义规则。 + +【示例】创建索引时,显示指定 mapping + +```javascript +PUT /my-index-000001 +{ + "mappings": { + "properties": { + "age": { "type": "integer" }, + "email": { "type": "keyword" }, + "name": { "type": "text" } + } + } +} +``` + +【示例】在已存在的索引中,指定一个 field 的属性 + +```javascript +PUT /my-index-000001/_mapping +{ + "properties": { + "employee-id": { + "type": "keyword", + "index": false + } + } +} +``` + +【示例】查看 mapping + +``` +GET /my-index-000001/_mapping +``` + +【示例】查看指定 field 的 mapping + +``` +GET /my-index-000001/_mapping/field/employee-id +``` + +### 动态映射 + +动态映射机制,允许用户不手动定义映射,Elasticsearch 会自动识别字段类型。在实际项目中,如果遇到的业务在导入数据之前不确定有哪些字段,也不清楚字段的类型是什么,使用动态映射非常合适。当 Elasticsearch 在文档中碰到一个以前没见过的字段时,它会利用动态映射来决定该字段的类型,并自动把该字段添加到映射中。 + +示例:创建一个名为 `data` 的索引、其 `mapping` 类型为 `_doc`,并且有一个类型为 `long` 的字段 `count`。 + +```bash +PUT data/_doc/1 +{ "count": 5 } +``` + +#### 动态字段映射 + +动态字段映射([Dynamic field mappings](https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-field-mapping.html))是用于管理动态字段检测的规则。当 Elasticsearch 在文档中检测到新字段时,默认情况下会动态将该字段添加到类型映射中。 + +在 mapping 中可以通过将 [`dynamic`](https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic.html) 参数设置为 `true` 或 `runtime` 来开启动态映射。 + +[`dynamic`](https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic.html) 不同设置的作用: + +| 可选值 | 说明 | +| --------- | ------------------------------------------------------------------------------------------------------------------- | +| `true` | 新字段被添加到 mapping 中。mapping 的默认设置。 | +| `runtime` | 新字段被添加到 mapping 中并作为运行时字段——这些字段不会被索引,但是可以在查询时出现在 `_source` 中。 | +| `false` | 新字段不会被索引或搜索,但仍会出现在返回匹配的 `_source` 字段中。这些字段不会添加到映射中,并且必须显式添加新字段。 | +| `strict` | 如果检测到新字段,则会抛出异常并拒绝文档。必须将新字段显式添加到映射中。 | + +> 需要注意的是:对已有字段,一旦已经有数据写入,就不再支持修改字段定义。如果希望改变字段类型,必须重建索引。这是由于 Lucene 实现的倒排索引,一旦生成后,就不允许修改。如果修改了字段的数据类型,会导致已被索引的字段无法被搜索。 + +启用动态字段映射后,Elasticsearch 使用内置规则来确定如何映射每个字段的数据类型。规则如下: + +| **JSON 数据类型** | **`"dynamic":"true"`** | **`"dynamic":"runtime"`** | +| ------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------- | --------------------------- | +| `null` | 没有字段被添加 | 没有字段被添加 | +| `true` or `false` | `boolean` 类型 | `boolean` 类型 | +| 浮点型数字 | `float` 类型 | `double` 类型 | +| 数字 | 数字型 | `long` 类型 | +| JSON 对象 | `object` 类型 | 没有字段被添加 | +| 数组 | 由数组中第一个非空值决定 | 由数组中第一个非空值决定 | +| 开启[日期检测](https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-field-mapping.html#date-detection)的字符串 | `date` 类型 | `date` 类型 | +| 开启[数字检测](https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-field-mapping.html#numeric-detection)的字符串 | `float` 类型或 `long`类型 | `double` 类型或 `long` 类型 | +| 什么也没开启的字符串 | 带有 `.keyword` 子 field 的 `text` 类型 | `keyword` 类型 | + +下面举一个例子认识动态 mapping,在 Elasticsearch 中创建一个新的索引并查看它的 mapping,命令如下: + +```bash +PUT books +GET books/_mapping +``` + +此时 books 索引的 mapping 是空的,返回结果如下: + +```json +{ + "books": { + "mappings": {} + } +} +``` + +再往 books 索引中写入一条文档,命令如下: + +```bash +PUT books/it/1 +{ + "id": 1, + "publish_date": "2019-11-10", + "name": "master Elasticsearch" +} +``` + +文档写入完成之后,再次查看 mapping,返回结果如下: + +```json +{ + "books": { + "mappings": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "publish_date": { + "type": "date" + } + } + } + } +} +``` + +动态映射有时可能会错误的识别字段类型,这种情况下,可能会导致一些功能无法正常使用,如 Range 查询。所以,使用动态 mapping 要结合实际业务需求来综合考虑,如果将 Elasticsearch 当作主要的数据存储使用,并且希望出现未知字段时抛出异常来提醒你注意这一问题,那么开启动态 mapping 并不适用。 + +#### 动态模板 + +**动态模板([dynamic templates](https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-templates.html))**是用于给 `mapping` 动态添加字段的自定义规则。 + +动态模板可以设置匹配条件,只有匹配的情况下才使用动态模板: + +- `match_mapping_type` 对 Elasticsearch 检测到的数据类型进行操作 +- `match` 和 `unmatch` 使用模式匹配字段名称 +- `path_match` 和 `path_unmatch` 对字段的完整虚线路径进行操作 +- 如果动态模板没有定义 `match_mapping_type`、`match` 或 `path_match`,则不会匹配任何字段。您仍然可以在批量请求的 `dynamic_templates` 部分按名称引用模板。 + +【示例】当设置 `'dynamic':'true'` 时,Elasticsearch 会将字符串字段映射为带有关键字子字段的文本字段。如果只是索引结构化内容并且对全文搜索不感兴趣,可以让 Elasticsearch 仅将字段映射为关键字字段。这种情况下,只有完全匹配才能搜索到这些字段。 + +```javascript +PUT my-index-000001 +{ + "mappings": { + "dynamic_templates": [ + { + "strings_as_keywords": { + "match_mapping_type": "string", + "mapping": { + "type": "keyword" + } + } + } + ] + } +} +``` + +## 运行时字段 + +运行时字段是在查询时评估的字段。运行时字段有以下作用: + +- 在不重新索引数据的情况下,向现有文档添加字段 +- 在不了解数据结构的情况下,也可以处理数据 +- 在查询时覆盖从索引字段返回的值 +- 为特定用途定义字段而不修改底层架构 + +检索 Elasticsearch 时,运行时字段和其他字段并没有什么不同。 + +需要注意的是:使用 `_search` API 上的 `fields` 参数来检索运行时字段的值。运行时字段不会显示在 `_source` 中,但 `fields` API 适用于所有字段,即使是那些未作为原始 `_source` 的一部分发送的字段。 + +运行时字段在处理日志数据时很有用,尤其是当日志是不确定的数据结构时:这种情况下,会降低搜索速度,但您的索引大小要小得多,您可以更快地处理日志,而无需为它们设置索引。 + +### 运行时字段的优点 + +因为**运行时字段没有被索引**,所以添加运行时字段不会增加索引大小。用户可以直接在 mapping 中定义运行时字段,从而节省存储成本并提高采集数据的速度。定义了运行时字段后,可以立即在搜索请求、聚合、过滤和排序中使用它。 + +如果将运行时字段设为索引字段,则无需修改任何引用运行时字段的查询。更好的是,您可以引用字段是运行时字段的一些索引,以及字段是索引字段的其他索引。您可以灵活地选择要索引哪些字段以及保留哪些字段作为运行时字段。 + +就其核心而言,运行时字段最重要的好处是能够在您提取字段后将字段添加到文档中。此功能简化了映射决策,因为您不必预先决定如何解析数据,并且可以使用运行时字段随时修改映射。使用运行时字段允许更小的索引和更快的摄取时间,这结合使用更少的资源并降低您的运营成本。 + +## 字段数据类型 + +在 Elasticsearch 中,每个字段都有一个字段数据类型或字段类型,用于指示字段包含的数据类型(例如字符串或布尔值)及其预期用途。字段类型按系列分组。同一族中的类型具有完全相同的搜索行为,但可能具有不同的空间使用或性能特征。 + +Elasticsearch 提供了非常丰富的数据类型,官方将其分为以下几类: + +- **普通类型** + - [`binary`](https://www.elastic.co/guide/en/elasticsearch/reference/current/binary.html):编码为 Base64 字符串的二进制值。 + - [`boolean`](https://www.elastic.co/guide/en/elasticsearch/reference/current/boolean.html):布尔类型,值为 true 或 false。 + - [Keywords](https://www.elastic.co/guide/en/elasticsearch/reference/current/keyword.html):keyword 族类型,包括 `keyword`、`constant_keyword` 和 `wildcard`。 + - [Numbers](https://www.elastic.co/guide/en/elasticsearch/reference/current/number.html):数字类型,如 `long` 和 `double` + - **Dates**:日期类型,包括 [`date`](https://www.elastic.co/guide/en/elasticsearch/reference/current/date.html) 和 [`date_nanos`](https://www.elastic.co/guide/en/elasticsearch/reference/current/date_nanos.html)。 + - [`alias`](https://www.elastic.co/guide/en/elasticsearch/reference/current/field-alias.html):用于定义存在字段的别名。 +- **对象类型** + - [`object`](https://www.elastic.co/guide/en/elasticsearch/reference/current/object.html):JSON 对象 + - [`flattened`](https://www.elastic.co/guide/en/elasticsearch/reference/current/flattened.html):整个 JSON 对象作为单个字段值。 + - [`nested`](https://www.elastic.co/guide/en/elasticsearch/reference/current/nested.html):保留其子字段之间关系的 JSON 对象。 + - [`join`](https://www.elastic.co/guide/en/elasticsearch/reference/current/parent-join.html):为同一索引中的文档定义父/子关系。 +- **结构化数据类型** + + - [Range](https://www.elastic.co/guide/en/elasticsearch/reference/current/range.html):范围类型,例如:`long_range`、`double_range`、`date_range` 和 `ip_range`。 + - [`ip`](https://www.elastic.co/guide/en/elasticsearch/reference/current/ip.html):IPv4 和 IPv6 地址。 + - [`version`](https://www.elastic.co/guide/en/elasticsearch/reference/current/version.html):版本号。支持 [Semantic Versioning](https://semver.org/) 优先规则。 + - [`murmur3`](https://www.elastic.co/guide/en/elasticsearch/plugins/8.2/mapper-murmur3.html):计算并存储 hash 值。 + +- **聚合数据类型** + + - [`aggregate_metric_double`](https://www.elastic.co/guide/en/elasticsearch/reference/current/aggregate-metric-double.html):预先聚合的指标值 + - [`histogram`](https://www.elastic.co/guide/en/elasticsearch/reference/current/histogram.html):直方图式的预聚合数值。 + +- **文本搜索类型** + - [`text` fields](https://www.elastic.co/guide/en/elasticsearch/reference/current/text.html):text 族类型,包括 `text` 和 `match_only_text`。 + - [`annotated-text`](https://www.elastic.co/guide/en/elasticsearch/plugins/8.2/mapper-annotated-text.html):包含特殊标记的文本。用于识别命名实体。 + - [`completion`](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters.html#completion-suggester):用于自动补全。 + - [`search_as_you_type`](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-as-you-type.html):键入时完成的类似文本的类型。 + - [`token_count`](https://www.elastic.co/guide/en/elasticsearch/reference/current/token-count.html):文本中标记的计数。 +- **文档排名类型** + - [`dense_vector`](https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html):记录浮点数的密集向量。 + - [`rank_feature`](https://www.elastic.co/guide/en/elasticsearch/reference/current/rank-feature.html):记录一个数字特征,为了在查询时提高命中率。 + - [`rank_features`](https://www.elastic.co/guide/en/elasticsearch/reference/current/rank-features.html):记录多个数字特征,为了在查询时提高命中率。 +- **空间数据类型** + + - [`geo_point`](https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-point.html):地理经纬度 + - [`geo_shape`](https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-shape.html):复杂的形状,例如多边形 + - [`point`](https://www.elastic.co/guide/en/elasticsearch/reference/current/point.html):任意笛卡尔点 + - [`shape`](https://www.elastic.co/guide/en/elasticsearch/reference/current/shape.html):任意笛卡尔几何形状 + +- **其他类型** + - [`percolator`](https://www.elastic.co/guide/en/elasticsearch/reference/current/percolator.html):使用 [Query DSL](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html) 编写的索引查询 + +## 元数据字段 + +一个文档中,不仅仅包含数据 ,也包含**元数据**。元数据是用于描述文档的信息。 + +- **标识元数据字段** + - [`_index`](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-index-field.html):文档所属的索引。 + - [`_id`](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-id-field.html):文档的 ID。 +- **文档 source 元数据字段** + - [`_source`](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-source-field.html):文档正文的原始 JSON。 + - [`_size`](https://www.elastic.co/guide/en/elasticsearch/plugins/8.2/mapper-size.html):`_source` 字段的大小(以字节为单位),由 [`mapper-size`](https://www.elastic.co/guide/en/elasticsearch/plugins/8.2/mapper-size.html) 插件提供。 +- **文档计数元数据字段** + - [`_doc_count`](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-doc-count-field.html):当文档表示预聚合数据时,用于存储文档计数的自定义字段。 +- **索引元数据字段** + - [`_field_names`](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-field-names-field.html):文档中的所有非空字段。 + - [`_ignored`](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-ignored-field.html):文档中所有的由于 [`ignore_malformed`](https://www.elastic.co/guide/en/elasticsearch/reference/current/ignore-malformed.html) 而在索引时被忽略的字段。 +- **路由元数据字段** + - [`_routing`](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-routing-field.html):将文档路由到特定分片的自定义路由值。 +- **其他元数据字段** + - [`_meta`](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-meta-field.html):应用程序特定的元数据。 + - [`_tier`](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-tier-field.html):文档所属索引的当前数据层首选项。 + +## 映射参数 + +Elasticsearch 提供了以下映射参数: + +- [`analyzer`](https://www.elastic.co/guide/en/elasticsearch/reference/current/analyzer.html):指定在索引或搜索文本字段时用于文本分析的分析器。 +- [`coerce`](https://www.elastic.co/guide/en/elasticsearch/reference/current/coerce.html):如果开启,Elasticsearch 将尝试清理脏数据以适应字段的数据类型。 +- [`copy_to`](https://www.elastic.co/guide/en/elasticsearch/reference/current/copy-to.html):允许将多个字段的值复制到一个组字段中,然后可以将其作为单个字段进行查询。 +- [`doc_values`](https://www.elastic.co/guide/en/elasticsearch/reference/current/doc-values.html):默认情况下,所有字段都是被 +- [`dynamic`](https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic.html):是否开启动态映射。 +- [`eager_global_ordinals`](https://www.elastic.co/guide/en/elasticsearch/reference/current/eager-global-ordinals.html):当在 global ordinals 的时候,refresh 以后下一次查询字典就需要重新构建,在追求查询的场景下很影响查询性能。可以使用 eager_global_ordinals,即在每次 refresh 以后即可更新字典,字典常驻内存,减少了查询的时候构建字典的耗时。 +- [`enabled`](https://www.elastic.co/guide/en/elasticsearch/reference/current/enabled.html):只能应用于顶级 mapping 定义和 `object` 字段。设置为 `false` 后,Elasticsearch 解析时,会完全跳过该字段。 +- [`fielddata`](https://www.elastic.co/guide/en/elasticsearch/reference/current/fielddata.html):默认情况下, `text` 字段是可搜索的,但不可用于聚合、排序或脚本。如果为字段设置 `fielddata=true`,就会通过反转倒排索引将 fielddata 加载到内存中。请注意,这可能会占用大量内存。如果想对 `text` 字段进行聚合、排序或脚本操作,fielddata 是唯一方法。 +- [`fields`](https://www.elastic.co/guide/en/elasticsearch/reference/current/multi-fields.html):有时候,同一个字段需要以不同目的进行索引,此时可以通过 `fields` 进行配置。 +- [`format`](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html):用于格式化日期类型。 +- [`ignore_above`](https://www.elastic.co/guide/en/elasticsearch/reference/current/ignore-above.html):字符串长度大于 `ignore_above` 所设,则不会被索引或存储。 +- [`ignore_malformed`](https://www.elastic.co/guide/en/elasticsearch/reference/current/ignore-malformed.html):有时候,同一个字段,可能会存储不同的数据类型。默认情况下,Elasticsearch 解析字段数据类型失败时,会引发异常,并拒绝整个文档。 如果设置 `ignore_malformed` 为 `true`,则允许忽略异常。这种情况下,格式错误的字段不会被索引,但文档中的其他字段可以正常处理。 +- [`index_options`](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-options.html) 用于控制将哪些信息添加到倒排索引以进行搜索和突出显示。只有 `text` 和 `keyword` 等基于术语(term)的字段类型支持此配置。 +- [`index_phrases`](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-phrases.html):如果启用,两个词的组合(shingles)将被索引到一个单独的字段中。这允许以更大的索引为代价,更有效地运行精确的短语查询(无 slop)。请注意,当停用词未被删除时,此方法效果最佳,因为包含停用词的短语将不使用辅助字段,并将回退到标准短语查询。接受真或假(默认)。 +- [`index_prefixes`](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-prefixes.html):index_prefixes 参数启用 term 前缀索引以加快前缀搜索。 +- [`index`](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-index.html):`index` 选项控制字段值是否被索引。默认为 true。 +- [`meta`](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-field-meta.html):附加到字段的元数据。此元数据对 Elasticsearch 是不透明的,它仅适用于多个应用共享相同索引的元数据信息,例如:单位。 +- [`normalizer`](https://www.elastic.co/guide/en/elasticsearch/reference/current/normalizer.html):`keyword` 字段的 `normalizer` 属性类似于 [`analyzer`](https://www.elastic.co/guide/en/elasticsearch/reference/current/analyzer.html) ,只是它保证分析链只产生单个标记。 `normalizer` 在索引 `keyword` 之前应用,以及在搜索时通过查询解析器(例如匹配查询)或通过术语级别查询(例如术语查询)搜索关键字字段时应用。 +- [`norms`](https://www.elastic.co/guide/en/elasticsearch/reference/current/norms.html):`norms` 存储在查询时使用的各种规范化因子,以便计算文档的相关性评分。 +- [`null_value`](https://www.elastic.co/guide/en/elasticsearch/reference/current/null-value.html):null 值无法被索引和搜索。当一个字段被设为 null,则被视为没有值。`null_value` 允许将空值替换为指定值,以便对其进行索引和搜索。 +- [`position_increment_gap`](https://www.elastic.co/guide/en/elasticsearch/reference/current/position-increment-gap.html):分析的文本字段会考虑术语位置,以便能够支持邻近或短语查询。当索引具有多个值的文本字段时,值之间会添加一个“假”间隙,以防止大多数短语查询在值之间匹配。此间隙的大小使用 `position_increment_gap` 配置,默认为 100。 +- [`properties`](https://www.elastic.co/guide/en/elasticsearch/reference/current/properties.html):类型映射、对象字段和嵌套字段包含的子字段,都称为属性。这些属性可以是任何数据类型,包括对象和嵌套。 +- [`search_analyzer`](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-analyzer.html):通常,在索引时和搜索时应使用相同的分析器,以确保查询中的术语与倒排索引中的术语格式相同。但是,有时在搜索时使用不同的分析器可能是有意义的,例如使用 [`edge_ngram`](https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-edgengram-tokenizer.html) 标记器实现自动补全或使用同义词搜索时。 +- [`similarity`](https://www.elastic.co/guide/en/elasticsearch/reference/current/similarity.html):Elasticsearch 允许为每个字段配置文本评分算法或相似度。相似度设置提供了一种选择文本相似度算法的简单方法,而不是默认的 BM25,例如布尔值。只有 `text` 和 `keyword` 等基于文本的字段类型支持此配置。 +- [`store`](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-store.html):默认情况下,对字段值进行索引以使其可搜索,但不会存储它们。这意味着可以查询该字段,但无法检索原始字段值。通常这不重要,字段值已经是默认存储的 `_source` 字段的一部分。如果您只想检索单个字段或几个字段的值,而不是整个 `_source`,则可以通过 [source filtering](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-fields.html#source-filtering) 来实现。 +- [`term_vector`](https://www.elastic.co/guide/en/elasticsearch/reference/current/term-vector.html):term_vector 包含有关分析过程产生的术语的信息,包括: + - 术语列表 + - 每个 term 的位置(或顺序) + - 起始和结束字符偏移量,用于将 term 和原始字符串进行映射 + - 有效负载(如果可用) - 用户定义的,与 term 位置相关的二进制数据 + +## 映射配置 + +- `index.mapping.total_fields.limit`:索引中的最大字段数。字段和对象映射以及字段别名计入此限制。默认值为 `1000`。 +- `index.mapping.depth.limit`:字段的最大深度,以内部对象的数量来衡量。例如,如果所有字段都在根对象级别定义,则深度为 `1`。如果有一个对象映射,则深度为 `2`,以此类推。默认值为 `20`。 +- `index.mapping.nested_fields.limit`:索引中不同 `nested` 映射的最大数量。 `nested` 类型只应在特殊情况下使用,即需要相互独立地查询对象数组。为了防止设计不佳的映射,此设置限制了每个索引的唯一 `nested` 类型的数量。默认值为 `50`。 +- `index.mapping.nested_objects.limit`:单个文档中,所有 `nested` 类型中包含的最大嵌套 JSON 对象数。当文档包含太多 `nested` 对象时,此限制有助于防止出现内存溢出。默认值为 `10000`。 +- `index.mapping.field_name_length.limit`:设置字段名称的最大长度。默认为 Long.MAX_VALUE(无限制)。 + +## 参考资料 + +- [Elasticsearch 官方文档之 Mapping](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html) \ No newline at end of file diff --git "a/docs/nosql/elasticsearch/Elasticsearch\346\237\245\350\257\242.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/05.Elasticsearch\346\237\245\350\257\242.md" similarity index 93% rename from "docs/nosql/elasticsearch/Elasticsearch\346\237\245\350\257\242.md" rename to "docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/05.Elasticsearch\346\237\245\350\257\242.md" index c9f8f7c2..16f50b2e 100644 --- "a/docs/nosql/elasticsearch/Elasticsearch\346\237\245\350\257\242.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/05.Elasticsearch\346\237\245\350\257\242.md" @@ -1,61 +1,27 @@ +--- +title: Elasticsearch 查询 +date: 2022-01-18 08:01:08 +categories: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch +tags: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch + - 查询 +permalink: /pages/83bd15/ +--- + # Elasticsearch 查询 Elasticsearch 查询语句采用基于 RESTful 风格的接口封装成 JSON 格式的对象,称之为 Query DSL。Elasticsearch 查询分类大致分为**全文查询**、**词项查询**、**复合查询**、**嵌套查询**、**位置查询**、**特殊查询**。Elasticsearch 查询从机制分为两种,一种是根据用户输入的查询词,通过排序模型计算文档与查询词之间的**相关度**,并根据评分高低排序返回;另一种是**过滤机制**,只根据过滤条件对文档进行过滤,不计算评分,速度相对较快。 - - -- [1. 全文查询](#1-全文查询) - - [1.1. intervals query](#11-intervals-query) - - [1.2. match query](#12-match-query) - - [1.3. match_bool_prefix query](#13-match_bool_prefix-query) - - [1.4. match_phrase query](#14-match_phrase-query) - - [1.5. match_phrase_prefix query](#15-match_phrase_prefix-query) - - [1.6. multi_match query](#16-multi_match-query) - - [1.7. combined_fields query](#17-combined_fields-query) - - [1.8. common_terms query](#18-common_terms-query) - - [1.9. query_string query](#19-query_string-query) - - [1.10. simple_query_string query](#110-simple_query_string-query) - - [1.11. 全文查询完整示例](#111-全文查询完整示例) -- [2. 词项查询](#2-词项查询) - - [2.1. exists query](#21-exists-query) - - [2.2. fuzzy query](#22-fuzzy-query) - - [2.3. ids query](#23-ids-query) - - [2.4. prefix query](#24-prefix-query) - - [2.5. range query](#25-range-query) - - [2.6. regexp query](#26-regexp-query) - - [2.7. term query](#27-term-query) - - [2.8. terms query](#28-terms-query) - - [2.9. type query](#29-type-query) - - [2.10. wildcard query](#210-wildcard-query) - - [2.11. 词项查询完整示例](#211-词项查询完整示例) -- [3. 复合查询](#3-复合查询) - - [3.1. bool query](#31-bool-query) - - [3.2. boosting query](#32-boosting-query) - - [3.3. constant_score query](#33-constant_score-query) - - [3.4. dis_max query](#34-dis_max-query) - - [3.5. function_score query](#35-function_score-query) - - [3.6. indices query](#36-indices-query) -- [4. 嵌套查询](#4-嵌套查询) - - [4.1. nested query](#41-nested-query) - - [4.2. has_child query](#42-has_child-query) - - [4.3. has_parent query](#43-has_parent-query) -- [5. 位置查询](#5-位置查询) - - [5.1. geo_distance query](#51-geo_distance-query) - - [5.2. geo_bounding_box query](#52-geo_bounding_box-query) - - [5.3. geo_polygon query](#53-geo_polygon-query) - - [5.4. geo_shape query](#54-geo_shape-query) -- [6. 特殊查询](#6-特殊查询) - - [6.1. more_like_this query](#61-more_like_this-query) - - [6.2. script query](#62-script-query) - - [6.3. percolate query](#63-percolate-query) - - - -## 1. 全文查询 +## 全文查询 ES 全文查询主要用于在全文字段上,主要考虑查询词与文档的相关性(Relevance)。 -### 1.1. intervals query +### intervals query [**`intervals query`**](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-intervals-query.html) 根据匹配词的顺序和近似度返回文档。 @@ -97,7 +63,7 @@ POST _search } ``` -### 1.2. match query +### match query [**`match query`**](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html) **用于搜索单个字段**,首先会针对查询语句进行解析(经过 analyzer),主要是对查询语句进行分词,分词后查询语句的任何一个词项被匹配,文档就会被搜到,默认情况下相当于对分词后词项进行 or 匹配操作。 @@ -212,7 +178,7 @@ GET /_search } ``` -### 1.3. match_bool_prefix query +### match_bool_prefix query [**`match_bool_prefix query`**](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-bool-prefix-query.html) 分析其输入并根据这些词构造一个布尔查询。除了最后一个术语之外的每个术语都用于术语查询。最后一个词用于 `prefix query`。 @@ -250,7 +216,7 @@ GET /_search 上面的示例 `match_bool_prefix query` 查询可以匹配包含 `quick brown fox` 的字段,但它也可以快速匹配 `brown fox`。它还可以匹配包含 `quick`、`brown` 和以 `f` 开头的字段,出现在任何位置。 -### 1.4. match_phrase query +### match_phrase query [**`match_phrase query`**](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query-phrase.html) 即短语匹配,首先会把 query 内容分词,分词器可以自定义,同时文档还要满足以下两个条件才会被搜索到: @@ -287,7 +253,7 @@ GET demo/_search > - are 的位置应该比 How 的位置大 1 。 > - you 的位置应该比 How 的位置大 2 。 -### 1.5. match_phrase_prefix query +### match_phrase_prefix query [**`match_phrase_prefix query`**](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query-phrase-prefix.html) 和 [**`match_phrase query`**](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query-phrase.html) 类似,只不过 [**`match_phrase_prefix query`**](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query-phrase-prefix.html) 最后一个 term 会被作为前缀匹配。 @@ -302,7 +268,7 @@ GET demo/_search } ``` -### 1.6. multi_match query +### multi_match query [**`multi_match query`**](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html) 是 **`match query`** 的升级,**用于搜索多个字段**。 @@ -359,7 +325,7 @@ GET kibana_sample_data_ecommerce/_search } ``` -### 1.7. combined_fields query +### combined_fields query [**`combined_fields query`**](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-combined-fields-query.html) 支持搜索多个文本字段,就好像它们的内容已被索引到一个组合字段中一样。该查询会生成以 term 为中心的输入字符串视图:首先它将查询字符串解析为独立的 term,然后在所有字段中查找每个 term。当匹配结果可能跨越多个文本字段时,此查询特别有用,例如文章的标题、摘要和正文: @@ -380,7 +346,7 @@ GET /_search 字段前缀权重根据组合字段模型进行计算。例如,如果 title 字段的权重为 2,则匹配度打分时会将 title 中的每个 term 形成的组合字段,按出现两次进行打分。 -### 1.8. common_terms query +### common_terms query > 7.3.0 废弃 @@ -433,7 +399,7 @@ GET books/_search } ``` -### 1.9. query_string query +### query_string query [**`query_string query`**](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html) 是与 Lucene 查询语句的语法结合非常紧密的一种查询,允许在一个查询语句中使用多个特殊条件关键字(如:AND | OR | NOT)对多个字段进行查询,建议熟悉 Lucene 查询语法的用户去使用。 @@ -453,7 +419,7 @@ GET /_search } ``` -### 1.10. simple_query_string query +### simple_query_string query [**`simple_query_string query`**](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-simple-query-string-query.html) 是一种适合直接暴露给用户,并且具有非常完善的查询语法的查询语句,接受 Lucene 查询语法,解析过程中发生错误不会抛出异常。 @@ -487,7 +453,7 @@ GET /_search 注意:要使用上面的字符,请使用反斜杠 `/` 对其进行转义。 -### 1.11. 全文查询完整示例 +### 全文查询完整示例 ```bash #设置 position_increment_gap @@ -535,7 +501,7 @@ POST groups/_search DELETE groups ``` -## 2. 词项查询 +## 词项查询 **`Term`(词项)是表达语意的最小单位**。搜索和利用统计语言模型进行自然语言处理都需要处理 Term。 @@ -556,7 +522,7 @@ DELETE groups - **[`type` query](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-type-query.html)** - **[`wildcard` query](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-wildcard-query.html)** -### 2.1. exists query +### exists query [**`exists query`**](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-exists-query.html) 会返回字段中至少有一个非空值的文档。 @@ -595,7 +561,7 @@ GET kibana_sample_data_ecommerce/_search - `{ "user" : [null] }` 虽然有 user 字段,但是值为空。 - `{ "foo" : "bar" }` 没有 user 字段。 -### 2.2. fuzzy query +### fuzzy query [**`fuzzy query`**(模糊查询)](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-fuzzy-query.html)返回包含与搜索词相似的词的文档。ES 使用 [Levenshtein edit distance(Levenshtein 编辑距离)](https://en.wikipedia.org/wiki/Levenshtein_distance)测量相似度或模糊度。 @@ -628,7 +594,7 @@ GET books/_search 注意:如果配置了 [`search.allow_expensive_queries`](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html#query-dsl-allow-expensive-queries) ,则 fuzzy query 不能执行。 -### 2.3. ids query +### ids query [**`ids query`**](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-ids-query.html) 根据 ID 返回文档。 此查询使用存储在 `_id` 字段中的文档 ID。 @@ -643,7 +609,7 @@ GET /_search } ``` -### 2.4. prefix query +### prefix query [**`prefix query`**](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-prefix-query.html#prefix-query-ex-request) 用于查询某个字段中包含指定前缀的文档。 @@ -662,7 +628,7 @@ GET /_search } ``` -### 2.5. range query +### range query [**`range query`**](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-range-query.html) 即范围查询,用于匹配在某一范围内的数值型、日期类型或者字符串型字段的文档。比如搜索哪些书籍的价格在 50 到 100 之间、哪些书籍的出版时间在 2015 年到 2019 年之间。**使用 range 查询只能查询一个字段,不能作用在多个字段上**。 @@ -719,7 +685,7 @@ GET kibana_sample_data_ecommerce/_search } ``` -### 2.6. regexp query +### regexp query [**`regexp query`**](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html) 返回与正则表达式相匹配的 term 所属的文档。 @@ -746,7 +712,7 @@ GET /_search > 注意:如果配置了[`search.allow_expensive_queries`](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html#query-dsl-allow-expensive-queries) ,则 [**`regexp query`**](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html) 会被禁用。 -### 2.7. term query +### term query [**`term query`**](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-term-query.html) 用来查找指定字段中包含给定单词的文档,term 查询不被解析,只有查询词和文档中的词精确匹配才会被搜索到,应用场景为查询人名、地名等需要精准匹配的需求。 @@ -800,7 +766,7 @@ DELETE my-index-000001 > > 要搜索 text 字段值,需改用 match 查询。 -### 2.8. terms query +### terms query [**`terms query`**](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-terms-query.html) 与 [**`term query`**](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-term-query.html) 相同,但可以搜索多个值。 @@ -857,7 +823,7 @@ GET my-index-000001/_search?pretty DELETE my-index-000001 ``` -### 2.9. type query +### type query > 7.0.0 后废弃 @@ -876,7 +842,7 @@ GET /_search } ``` -### 2.10. wildcard query +### wildcard query [**`wildcard query`**](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-wildcard-query.html) 即通配符查询,返回与通配符模式匹配的文档。 @@ -901,7 +867,7 @@ GET /_search > 注意:如果配置了[`search.allow_expensive_queries`](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html#query-dsl-allow-expensive-queries) ,则[**`wildcard query`**](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-wildcard-query.html) 会被禁用。 -### 2.11. 词项查询完整示例 +### 词项查询完整示例 ```bash DELETE products @@ -985,11 +951,11 @@ POST /products/_search } ``` -## 3. 复合查询 +## 复合查询 复合查询就是把一些简单查询组合在一起实现更复杂的查询需求,除此之外,复合查询还可以控制另外一个查询的行为。 -### 3.1. bool query +### bool query bool 查询可以把任意多个简单查询组合在一起,使用 must、should、must_not、filter 选项来表示简单查询之间的逻辑,每个选项都可以出现 0 次到多次,它们的含义如下: @@ -1037,7 +1003,7 @@ GET books/_search 有关布尔查询更详细的信息参考 [bool query(组合查询)详解](https://www.knowledgedict.com/tutorial/elasticsearch-query-bool.html)。 -### 3.2. boosting query +### boosting query boosting 查询用于需要对两个查询的评分进行调整的场景,boosting 查询会把两个查询封装在一起并降低其中一个查询的评分。 @@ -1068,7 +1034,7 @@ GET books/_search boosting 查询中指定了抑制因子为 0.2,publish_time 的值在 2015-01-01 之后的文档得分不变,publish_time 的值在 2015-01-01 之前的文档得分为原得分的 0.2 倍。 -### 3.3. constant_score query +### constant_score query constant*score query 包装一个 filter query,并返回匹配过滤器查询条件的文档,且它们的相关性评分都等于 \_boost* 参数值(可以理解为原有的基于 tf-idf 或 bm25 的相关分固定为 1.0,所以最终评分为 _1.0 \* boost_,即等于 _boost_ 参数值)。下面的查询语句会返回 title 字段中含有关键词 _elasticsearch_ 的文档,所有文档的评分都是 1.8: @@ -1088,7 +1054,7 @@ GET books/_search } ``` -### 3.4. dis_max query +### dis_max query dis_max query 与 bool query 有一定联系也有一定区别,dis_max query 支持多并发查询,可返回与任意查询条件子句匹配的任何文档类型。与 bool 查询可以将所有匹配查询的分数相结合使用的方式不同,dis_max 查询只使用最佳匹配查询条件的分数。请看下面的例子: @@ -1115,7 +1081,7 @@ GET books/_search } ``` -### 3.5. function_score query +### function_score query function_score query 可以修改查询的文档得分,这个查询在有些情况下非常有用,比如通过评分函数计算文档得分代价较高,可以改用过滤器加自定义评分函数的方式来取代传统的评分方式。 @@ -1161,7 +1127,7 @@ GET books/_search 关于 function_score 的更多详细内容请查看 [Elasticsearch function_score 查询最强详解](https://www.knowledgedict.com/tutorial/elasticsearch-function_score.html)。 -### 3.6. indices query +### indices query indices query 适用于需要在多个索引之间进行查询的场景,它允许指定一个索引名字列表和内部查询。indices query 中有 query 和 no_match_query 两部分,query 中用于搜索指定索引列表中的文档,no_match_query 中的查询条件用于搜索指定索引列表之外的文档。下面的查询语句实现了搜索索引 books、books2 中 title 字段包含关键字 javascript,其他索引中 title 字段包含 basketball 的文档,查询语句如下: @@ -1186,7 +1152,7 @@ GET books/_search } ``` -## 4. 嵌套查询 +## 嵌套查询 在 Elasticsearch 这样的分布式系统中执行全 SQL 风格的连接查询代价昂贵,是不可行的。相应地,为了实现水平规模地扩展,Elasticsearch 提供了以下两种形式的 join: @@ -1198,7 +1164,7 @@ GET books/_search 父子关系可以存在单个的索引的两个类型的文档之间。has_child 查询将返回其子文档能满足特定查询的父文档,而 has_parent 则返回其父文档能满足特定查询的子文档。 -### 4.1. nested query +### nested query 文档中可能包含嵌套类型的字段,这些字段用来索引一些数组对象,每个对象都可以作为一条独立的文档被查询出来(用嵌套查询)。 @@ -1217,7 +1183,7 @@ PUT /my_index } ``` -### 4.2. has_child query +### has_child query 文档的父子关系创建索引时在映射中声明,这里以员工(employee)和工作城市(branch)为例,它们属于不同的类型,相当于数据库中的两张表,如果想把员工和他们工作的城市关联起来,需要告诉 Elasticsearch 文档之间的父子关系,这里 employee 是 child type,branch 是 parent type,在映射中声明,执行命令: @@ -1309,7 +1275,7 @@ GET company/branch/_search?pretty } ``` -### 4.3. has_parent query +### has_parent query 通过父文档查询子文档使用 has_parent 查询。比如,搜索哪些 employee 工作在 UK,查询命令如下: @@ -1327,7 +1293,7 @@ GET company/employee/_search } ``` -## 5. 位置查询 +## 位置查询 Elasticsearch 可以对地理位置点 geo_point 类型和地理位置形状 geo_shape 类型的数据进行搜索。为了学习方便,这里准备一些城市的地理坐标作为测试数据,每一条文档都包含城市名称和地理坐标这两个字段,这里的坐标点取的是各个城市中心的一个位置。首先把下面的内容保存到 geo.json 文件中: @@ -1372,7 +1338,7 @@ PUT geo curl -XPOST "http://localhost:9200/_bulk?pretty" --data-binary @geo.json ``` -### 5.1. geo_distance query +### geo_distance query geo_distance query 可以查找在一个中心点指定范围内的地理点文档。例如,查找距离天津 200km 以内的城市,搜索结果中会返回北京,命令如下: @@ -1419,7 +1385,7 @@ GET geo/_search 其中 location 对应的经纬度字段;unit 为 `km` 表示将距离以 `km` 为单位写入到每个返回结果的 sort 键中;distance_type 为 `plane` 表示使用快速但精度略差的 `plane` 计算方式。 -### 5.2. geo_bounding_box query +### geo_bounding_box query geo_bounding_box query 用于查找落入指定的矩形内的地理坐标。查询中由两个点确定一个矩形,然后在矩形区域内查询匹配的文档。 @@ -1450,7 +1416,7 @@ GET geo/_search } ``` -### 5.3. geo_polygon query +### geo_polygon query geo_polygon query 用于查找在指定**多边形**内的地理点。例如,呼和浩特、重庆、上海三地组成一个三角形,查询位置在该三角形区域内的城市,命令如下: @@ -1483,7 +1449,7 @@ GET geo/_search } ``` -### 5.4. geo_shape query +### geo_shape query geo_shape query 用于查询 geo_shape 类型的地理数据,地理形状之间的关系有相交、包含、不相交三种。创建一个新的索引用于测试,其中 location 字段的类型设为 geo_shape 类型。 @@ -1552,9 +1518,9 @@ GET geoshape/_search } ``` -## 6. 特殊查询 +## 特殊查询 -### 6.1. more_like_this query +### more_like_this query more_like_this query 可以查询和提供文本类似的文档,通常用于近似文本的推荐等场景。查询命令如下: @@ -1589,7 +1555,7 @@ GET books/_search - include 是否把输入文档作为结果返回。 - boost 整个 query 的权重,默认为 1.0。 -### 6.2. script query +### script query Elasticsearch 支持使用脚本进行查询。例如,查询价格大于 180 的文档,命令如下: @@ -1607,7 +1573,7 @@ GET books/_search } ``` -### 6.3. percolate query +### percolate query 一般情况下,我们是先把文档写入到 Elasticsearch 中,通过查询语句对文档进行搜索。percolate query 则是反其道而行之的做法,它会先注册查询条件,根据文档来查询 query。例如,在 my-index 索引中有一个 laptop 类型,文档有 price 和 name 两个字段,在映射中声明一个 percolator 类型的 query,命令如下: @@ -1665,4 +1631,4 @@ GET /my-index/_search } ``` -文档符合 query 中的条件,返回结果中可以查到上文中注册的 bool query。percolate query 的这种特性适用于数据分类、数据路由、事件监控和预警等场景。 +文档符合 query 中的条件,返回结果中可以查到上文中注册的 bool query。percolate query 的这种特性适用于数据分类、数据路由、事件监控和预警等场景。 \ No newline at end of file diff --git "a/docs/nosql/elasticsearch/Elasticsearch\351\253\230\344\272\256.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/06.Elasticsearch\351\253\230\344\272\256.md" similarity index 96% rename from "docs/nosql/elasticsearch/Elasticsearch\351\253\230\344\272\256.md" rename to "docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/06.Elasticsearch\351\253\230\344\272\256.md" index 585f8d0f..6c9e404a 100644 --- "a/docs/nosql/elasticsearch/Elasticsearch\351\253\230\344\272\256.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/06.Elasticsearch\351\253\230\344\272\256.md" @@ -1,17 +1,23 @@ +--- +title: Elasticsearch 高亮搜索及显示 +date: 2022-02-22 21:01:01 +categories: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch +tags: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch + - 高亮 +permalink: /pages/e1b769/ +--- + # Elasticsearch 高亮搜索及显示 Elasticsearch 的高亮(highlight)可以让您从搜索结果中的一个或多个字段中获取突出显示的摘要,以便向用户显示查询匹配的位置。当您请求突出显示(即高亮)时,响应结果的 highlight 字段中包括高亮的字段和高亮的片段。Elasticsearch 默认会用 `` 标签标记关键字。 - - -- [1. 高亮参数](#1-高亮参数) -- [2. 自定义高亮片段](#2-自定义高亮片段) -- [3. 多字段高亮](#3-多字段高亮) -- [4. 高亮性能分析](#4-高亮性能分析) - - - -## 1. 高亮参数 +## 高亮参数 ES 提供了如下高亮参数: @@ -39,7 +45,7 @@ ES 提供了如下高亮参数: | `tags_schema` | 设置为使用内置标记模式的样式。 | | `type` | 使用的高亮模式,可选项为**_`unified`_**、**_`plain`_**或**_`fvh`_**。默认为 _`unified`_。 | -## 2. 自定义高亮片段 +## 自定义高亮片段 如果我们想使用自定义标签,在高亮属性中给需要高亮的字段加上 `pre_tags` 和 `post_tags` 即可。例如,搜索 title 字段中包含关键词 javascript 的书籍并使用自定义 HTML 标签高亮关键词,查询语句如下: @@ -60,7 +66,7 @@ GET /books/_search } ``` -## 3. 多字段高亮 +## 多字段高亮 关于搜索高亮,还需要掌握如何设置多字段搜索高亮。比如,搜索 title 字段的时候,我们期望 description 字段中的关键字也可以高亮,这时候就需要把 `require_field_match` 属性的取值设置为 `fasle`。`require_field_match` 的默认值为 `true`,只会高亮匹配的字段。多字段高亮的查询语句如下: @@ -80,7 +86,7 @@ GET /books/_search } ``` -## 4. 高亮性能分析 +## 高亮性能分析 Elasticsearch 提供了三种高亮器,分别是**默认的 highlighter 高亮器**、**postings-highlighter 高亮器**和 **fast-vector-highlighter 高亮器**。 @@ -120,4 +126,4 @@ PUT /example } } } -``` +``` \ No newline at end of file diff --git "a/docs/nosql/elasticsearch/Elasticsearch\346\216\222\345\272\217.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/07.Elasticsearch\346\216\222\345\272\217.md" similarity index 92% rename from "docs/nosql/elasticsearch/Elasticsearch\346\216\222\345\272\217.md" rename to "docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/07.Elasticsearch\346\216\222\345\272\217.md" index 3ee9a3e3..710e34e3 100644 --- "a/docs/nosql/elasticsearch/Elasticsearch\346\216\222\345\272\217.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/07.Elasticsearch\346\216\222\345\272\217.md" @@ -1,27 +1,29 @@ +--- +title: Elasticsearch 排序 +date: 2022-01-19 22:49:16 +categories: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch +tags: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch + - 排序 +permalink: /pages/24baff/ +--- + # Elasticsearch 排序 在 Elasticsearch 中,默认排序是**按照相关性的评分(\_score)**进行降序排序,也可以按照**字段的值排序**、**多级排序**、**多值字段排序、基于 geo(地理位置)排序以及自定义脚本排序**,除此之外,对于相关性的评分也可以用 rescore 二次、三次打分,它可以限定重新打分的窗口大小(window size),并针对作用范围内的文档修改其得分,从而达到精细化控制结果相关性的目的。 - - -- [1. 默认相关性排序](#1-默认相关性排序) - - [1.1. TF-IDF 模型](#11-tf-idf-模型) - - [1.2. BM25 模型](#12-bm25-模型) -- [2. 字段的值排序](#2-字段的值排序) -- [3. 多字段排序](#3-多字段排序) -- [4. 多值字段的排序](#4-多值字段的排序) -- [5. 地理位置上的距离排序](#5-地理位置上的距离排序) -- [6. 参考资料](#6-参考资料) - - - -## 1. 默认相关性排序 +## 默认相关性排序 在 Elasticsearch 中,默认情况下,文档是按照相关性得分倒序排列的,其对应的相关性得分字段用 `_score` 来表示,它是浮点数类型,`_score` 评分越高,相关性越高。评分模型的选择可以通过 `similarity` 参数在映射中指定。 相似度算法可以按字段指定,只需在映射中为不同字段选定即可,如果要修改已有字段的相似度算法,只能通过为数据重新建立索引来达到目的。关于更多 es 相似度算法可以参考 [深入理解 es 相似度算法(相关性得分计算)](https://www.knowledgedict.com/tutorial/elasticsearch-similarity.html)。 -### 1.1. TF-IDF 模型 +### TF-IDF 模型 Elasticsearch 在 5.4 版本以前,text 类型的字段,默认采用基于 tf-idf 的向量空间模型。 @@ -53,11 +55,11 @@ Elasticsearch 在 5.4 版本以前,text 类型的字段,默认采用基于 t 一旦词频 TF 和逆文档频率 IDF 计算完成,就可以使用 TF-IDF 公式来计算文档的得分。 -### 1.2. BM25 模型 +### BM25 模型 Elasticsearch 在 5.4 版本之后,针对 text 类型的字段,默认采用的是 BM25 评分模型,而不是基于 tf-idf 的向量空间模型,评分模型的选择可以通过 `similarity` 参数在映射中指定。 -## 2. 字段的值排序 +## 字段的值排序 在 Elasticsearch 中按照字段的值排序,可以利用 `sort` 参数实现。 @@ -110,7 +112,7 @@ GET books/_search 从如上返回结果,可以看出,`max_score` 和 `_score` 字段都返回 `null`,返回字段多出 `sort` 字段,包含排序字段的分值。计算 \_`score` 的花销巨大,如果不根据相关性排序,记录 \_`score` 是没有意义的。如果无论如何都要计算 \_`score`,可以将 `track_scores` 参数设置为 `true`。 -## 3. 多字段排序 +## 多字段排序 如果我们想要结合使用 price、date 和 \_score 进行查询,并且匹配的结果首先按照价格排序,然后按照日期排序,最后按照相关性排序,具体示例如下: @@ -148,7 +150,7 @@ GET books/_search 多级排序并不一定包含 `_score`。你可以根据一些不同的字段进行排序,如地理距离或是脚本计算的特定值。 -## 4. 多值字段的排序 +## 多值字段的排序 一种情形是字段有多个值的排序,需要记住这些值并没有固有的顺序;一个多值的字段仅仅是多个值的包装,这时应该选择哪个进行排序呢? @@ -163,7 +165,7 @@ GET books/_search } ``` -## 5. 地理位置上的距离排序 +## 地理位置上的距离排序 es 的地理位置排序使用 **`_geo_distance`** 来进行距离排序,如下示例: @@ -196,6 +198,6 @@ _\_geo_distance_ 的选项具体如下: - **_`ignore_unmapped`_**:未映射字段时,是否忽略处理,可选项有 **_`true`_** 和 **_`false`_**;默认为 _false_,表示如果未映射字段,查询将引发异常;若设置 _true_,将忽略未映射的字段,并且不匹配此查询的任何文档。 - **_`validation_method`_**:指定检验经纬度数据的方式,可选项有 **_`IGNORE_MALFORMED`_**、**_`COERCE`_** 和 **_`STRICT`_**;_IGNORE_MALFORMED_ 表示可接受纬度或经度无效的地理点,即忽略数据;_COERCE_ 表示另外尝试并推断正确的地理坐标;_STRICT_ 为默认值,表示遇到不正确的地理坐标直接抛出异常。 -## 6. 参考资料 +## 参考资料 -- [Elasticsearch 教程](https://www.knowledgedict.com/tutorial/elasticsearch-intro.html) +- [Elasticsearch 教程](https://www.knowledgedict.com/tutorial/elasticsearch-intro.html) \ No newline at end of file diff --git "a/docs/nosql/elasticsearch/Elasticsearch\350\201\232\345\220\210.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/08.Elasticsearch\350\201\232\345\220\210.md" similarity index 92% rename from "docs/nosql/elasticsearch/Elasticsearch\350\201\232\345\220\210.md" rename to "docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/08.Elasticsearch\350\201\232\345\220\210.md" index e8a5872d..451ef126 100644 --- "a/docs/nosql/elasticsearch/Elasticsearch\350\201\232\345\220\210.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/08.Elasticsearch\350\201\232\345\220\210.md" @@ -1,31 +1,23 @@ +--- +title: Elasticsearch 聚合 +date: 2022-01-19 22:49:16 +categories: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch +tags: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch + - 聚合 +permalink: /pages/f89f66/ +--- + # Elasticsearch 聚合 Elasticsearch 是一个分布式的全文搜索引擎,索引和搜索是 Elasticsearch 的基本功能。事实上,Elasticsearch 的聚合(Aggregations)功能也十分强大,允许在数据上做复杂的分析统计。Elasticsearch 提供的聚合分析功能主要有**指标聚合(metrics aggregations)**、**桶聚合(bucket aggregations)**、**管道聚合(pipeline aggregations)** 和 **矩阵聚合(matrix aggregations)** 四大类,管道聚合和矩阵聚合官方说明是在试验阶段,后期会完全更改或者移除,这里不再对管道聚合和矩阵聚合进行讲解。 - - -- [1. 聚合的具体结构](#1-聚合的具体结构) -- [2. 指标聚合](#2-指标聚合) - - [2.1. Max Aggregation](#21-max-aggregation) - - [2.2. Min Aggregation](#22-min-aggregation) - - [2.3. Avg Aggregation](#23-avg-aggregation) - - [2.4. Sum Aggregation](#24-sum-aggregation) - - [2.5. Value Count Aggregation](#25-value-count-aggregation) - - [2.6. Cardinality Aggregation](#26-cardinality-aggregation) - - [2.7. Stats Aggregation](#27-stats-aggregation) - - [2.8. Extended Stats Aggregation](#28-extended-stats-aggregation) - - [2.9. Percentiles Aggregation](#29-percentiles-aggregation) - - [2.10. Percentiles Ranks Aggregation](#210-percentiles-ranks-aggregation) -- [3. 桶聚合](#3-桶聚合) - - [3.1. Terms Aggregation](#31-terms-aggregation) - - [3.2. Filter Aggregation](#32-filter-aggregation) - - [3.3. Filters Aggregation](#33-filters-aggregation) - - [3.4. Range Aggregation](#34-range-aggregation) -- [4. 参考资料](#4-参考资料) - - - -## 1. 聚合的具体结构 +## 聚合的具体结构 所有的聚合,无论它们是什么类型,都遵从以下的规则。 @@ -80,13 +72,13 @@ POST /player/_search?size=0 } ``` -## 2. 指标聚合 +## 指标聚合 指标聚合(又称度量聚合)主要从不同文档的分组中提取统计数据,或者,从来自其他聚合的文档桶来提取统计数据。 这些统计数据通常来自数值型字段,如最小或者平均价格。用户可以单独获取每项统计数据,或者也可以使用 stats 聚合来同时获取它们。更高级的统计数据,如平方和或者是标准差,可以通过 extended stats 聚合来获取。 -### 2.1. Max Aggregation +### Max Aggregation Max Aggregation 用于最大值统计。例如,统计 sales 索引中价格最高的是哪本书,并且计算出对应的价格的 2 倍值,查询语句如下: @@ -129,7 +121,7 @@ GET /sales/_search?size=0 } ``` -### 2.2. Min Aggregation +### Min Aggregation Min Aggregation 用于最小值统计。例如,统计 sales 索引中价格最低的是哪本书,查询语句如下: @@ -159,7 +151,7 @@ GET /sales/_search?size=0 } ``` -### 2.3. Avg Aggregation +### Avg Aggregation Avg Aggregation 用于计算平均值。例如,统计 exams 索引中考试的平均分数,如未存在分数,默认为 60 分,查询语句如下: @@ -194,7 +186,7 @@ GET /exams/_search?size=0 除了常规的平均值聚合计算外,elasticsearch 还提供了加权平均值的聚合计算,详情参见 [Elasticsearch 指标聚合之 Weighted Avg Aggregation](https://www.knowledgedict.com/tutorial/elasticsearch-aggregations-metrics-weighted-avg-aggregation.html)。 -### 2.4. Sum Aggregation +### Sum Aggregation Sum Aggregation 用于计算总和。例如,统计 sales 索引中 type 字段中匹配 hat 的价格总和,查询语句如下: @@ -229,7 +221,7 @@ GET /exams/_search?size=0 } ``` -### 2.5. Value Count Aggregation +### Value Count Aggregation Value Count Aggregation 可按字段统计文档数量。例如,统计 books 索引中包含 author 字段的文档数量,查询语句如下: @@ -257,7 +249,7 @@ GET /books/_search?size=0 } ``` -### 2.6. Cardinality Aggregation +### Cardinality Aggregation Cardinality Aggregation 用于基数统计,其作用是先执行类似 SQL 中的 distinct 操作,去掉集合中的重复项,然后统计去重后的集合长度。例如,在 books 索引中对 language 字段进行 cardinality 操作可以统计出编程语言的种类数,查询语句如下: @@ -293,7 +285,7 @@ GET /books/_search?size=0 } ``` -### 2.7. Stats Aggregation +### Stats Aggregation Stats Aggregation 用于基本统计,会一次返回 count、max、min、avg 和 sum 这 5 个指标。例如,在 exams 索引中对 grade 字段进行分数相关的基本统计,查询语句如下: @@ -325,7 +317,7 @@ GET /exams/_search?size=0 } ``` -### 2.8. Extended Stats Aggregation +### Extended Stats Aggregation Extended Stats Aggregation 用于高级统计,和基本统计功能类似,但是会比基本统计多出以下几个统计结果,sum_of_squares(平方和)、variance(方差)、std_deviation(标准差)、std_deviation_bounds(平均值加/减两个标准差的区间)。在 exams 索引中对 grade 字段进行分数相关的高级统计,查询语句如下: @@ -364,7 +356,7 @@ GET /exams/_search?size=0 } ``` -### 2.9. Percentiles Aggregation +### Percentiles Aggregation Percentiles Aggregation 用于百分位统计。百分位数是一个统计学术语,如果将一组数据从大到小排序,并计算相应的累计百分位,某一百分位所对应数据的值就称为这一百分位的百分位数。默认情况下,累计百分位为 [ 1, 5, 25, 50, 75, 95, 99 ]。以下例子给出了在 latency 索引中对 load_time 字段进行加载时间的百分位统计,查询语句如下: @@ -422,7 +414,7 @@ GET latency/_search } ``` -### 2.10. Percentiles Ranks Aggregation +### Percentiles Ranks Aggregation Percentiles Ranks Aggregation 与 Percentiles Aggregation 统计恰恰相反,就是想看当前数值处在什么范围内(百分位), 假如你查一下当前值 500 和 600 所处的百分位,发现是 90.01 和 100,那么说明有 90.01 % 的数值都在 500 以内,100 % 的数值在 600 以内。 @@ -499,7 +491,7 @@ GET latency/_search } ``` -## 3. 桶聚合 +## 桶聚合 bucket 可以理解为一个桶,它会遍历文档中的内容,凡是符合某一要求的就放入一个桶中,分桶相当于 SQL 中的 group by。从另外一个角度,可以将指标聚合看成单桶聚合,即把所有文档放到一个桶中,而桶聚合是多桶型聚合,它根据相应的条件进行分组。 @@ -517,7 +509,7 @@ bucket 可以理解为一个桶,它会遍历文档中的内容,凡是符合 | 空值聚合(Missing Aggregation) | 空值聚合,可以把文档集中所有缺失字段的文档分到一个桶中。 | | 地理点范围聚合(Geo Distance Aggregation) | 用于对地理点(geo point)做范围统计。 | -### 3.1. Terms Aggregation +### Terms Aggregation Terms Aggregation 用于词项的分组聚合。最为经典的用例是获取 X 中最频繁(top frequent)的项目,其中 X 是文档中的某个字段,如用户的名称、标签或分类。由于 terms 聚集统计的是每个词条,而不是整个字段值,因此通常需要在一个非分析型的字段上运行这种聚集。原因是, 你期望“big data”作为词组统计,而不是“big”单独统计一次,“data”再单独统计一次。 @@ -607,7 +599,7 @@ Terms Aggregation 用于词项的分组聚合。最为经典的用例是获取 X 默认情况下返回按文档计数从高到低的前 10 个分组,可以通过 size 参数指定返回的分组数。 -### 3.2. Filter Aggregation +### Filter Aggregation Filter Aggregation 是过滤器聚合,可以把符合过滤器中的条件的文档分到一个桶中,即是单分组聚合。 @@ -628,7 +620,7 @@ Filter Aggregation 是过滤器聚合,可以把符合过滤器中的条件的 } ``` -### 3.3. Filters Aggregation +### Filters Aggregation Filters Aggregation 是多过滤器聚合,可以把符合多个过滤条件的文档分到不同的桶中,即每个分组关联一个过滤条件,并收集所有满足自身过滤条件的文档。 @@ -668,7 +660,7 @@ Filters Aggregation 是多过滤器聚合,可以把符合多个过滤条件的 } ``` -### 3.4. Range Aggregation +### Range Aggregation Range Aggregation 范围聚合是一个基于多组值来源的聚合,可以让用户定义一系列范围,每个范围代表一个分组。在聚合执行的过程中,从每个文档提取出来的值都会检查每个分组的范围,并且使相关的文档落入分组中。注意,范围聚合的每个范围内包含 from 值但是排除 to 值。 @@ -739,6 +731,6 @@ Range Aggregation 范围聚合是一个基于多组值来源的聚合,可以 } ``` -## 4. 参考资料 +## 参考资料 -- [Elasticsearch 教程](https://www.knowledgedict.com/tutorial/elasticsearch-intro.html) +- [Elasticsearch 教程](https://www.knowledgedict.com/tutorial/elasticsearch-intro.html) \ No newline at end of file diff --git "a/docs/nosql/elasticsearch/Elasticsearch\345\210\206\346\236\220\345\231\250.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/09.Elasticsearch\345\210\206\346\236\220\345\231\250.md" similarity index 84% rename from "docs/nosql/elasticsearch/Elasticsearch\345\210\206\346\236\220\345\231\250.md" rename to "docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/09.Elasticsearch\345\210\206\346\236\220\345\231\250.md" index ff9253d9..7f7b2a6d 100644 --- "a/docs/nosql/elasticsearch/Elasticsearch\345\210\206\346\236\220\345\231\250.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/09.Elasticsearch\345\210\206\346\236\220\345\231\250.md" @@ -1,36 +1,35 @@ +--- +title: Elasticsearch 分析器 +date: 2022-02-22 21:01:01 +categories: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch +tags: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch + - 分词 +permalink: /pages/a5a001/ +--- + # Elasticsearch 分析器 -在 ES 中,不管是索引任务还是搜索工作,都需要使用 analyzer(分析器)。分析器,分为**内置分析器**和**自定义的分析器**。 +文本分析是把全文本转换为一系列单词(term/token)的过程,也叫分词。在 Elasticsearch 中,分词是通过 analyzer(分析器)来完成的,不管是索引还是搜索,都需要使用 analyzer(分析器)。分析器,分为**内置分析器**和**自定义的分析器**。 -分析器进一步由**字符过滤器**(**Character Filters**)、**分词器**(**Tokenizer**)和**词元过滤器**(**Token Filters**)三部分组成。它的执行顺序如下: +分析器可以进一步细分为**字符过滤器**(**Character Filters**)、**分词器**(**Tokenizer**)和**词元过滤器**(**Token Filters**)三部分。它的执行顺序如下: **_character filters_** -> **_tokenizer_** -> **_token filters_** - - -- [1. 字符过滤器(Character Filters)](#1-字符过滤器character-filters) - - [1.1. HTML strip character filter](#11-html-strip-character-filter) - - [1.2. Mapping character filter](#12-mapping-character-filter) - - [1.3. Pattern Replace character filter](#13-pattern-replace-character-filter) -- [2. 分词器(Tokenizer)](#2-分词器tokenizer) - - [2.1. elasticsearch-plugin 使用](#21-elasticsearch-plugin-使用) - - [2.2. elasticsearch-analysis-ik 安装](#22-elasticsearch-analysis-ik-安装) - - [2.3. elasticsearch-analysis-ik 使用](#23-elasticsearch-analysis-ik-使用) -- [3. 词元过滤器(Token Filters)](#3-词元过滤器token-filters) - - [3.1. 同义词](#31-同义词) -- [4. 参考资料](#4-参考资料) - - - -## 1. 字符过滤器(Character Filters) +## 字符过滤器(Character Filters) -character filter 的输入是原始的文本 text,如果配置了多个,它会按照配置的顺序执行,目前 ES 自带的 character filter 主要由如下 3 类: +character filter 的输入是原始的文本 text,如果配置了多个,它会按照配置的顺序执行,目前 ES 自带的 character filter 主要有如下 3 类: -1. html strip character filter:从文本中剥离 HTML 元素,并用其解码值替换 HTML 实体(如,将 **_`&amp;`_** 替换为 **_`&`_**)。 -2. mapping character filter:自定义一个 map 映射,可以进行一些自定义的替换,如常用的大写变小写也可以在该环节设置。 -3. pattern replace character filter:使用 java 正则表达式来匹配应替换为指定替换字符串的字符,此外,替换字符串可以引用正则表达式中的捕获组。 +1. **html strip character filter**:从文本中剥离 HTML 元素,并用其解码值替换 HTML 实体(如,将 **_`&amp;`_** 替换为 **_`&`_**)。 +2. **mapping character filter**:自定义一个 map 映射,可以进行一些自定义的替换,如常用的大写变小写也可以在该环节设置。 +3. **pattern replace character filter**:使用 java 正则表达式来匹配应替换为指定替换字符串的字符,此外,替换字符串可以引用正则表达式中的捕获组。 -### 1.1. HTML strip character filter +### HTML strip character filter HTML strip 如下示例: @@ -51,7 +50,7 @@ GET /_analyze [ \nI'm so happy!\n ] ``` -### 1.2. Mapping character filter +### Mapping character filter Mapping character filter 接收键和值映射(key => value)作为配置参数,每当在预处理过程中遇到与键值映射中的键相同的字符串时,就会使用该键对应的值去替换它。 @@ -97,7 +96,7 @@ GET /_analyze [ My license plate is 25015 ] ``` -### 1.3. Pattern Replace character filter +### Pattern Replace character filter Pattern Replace character filter 支持如下三个参数: @@ -120,7 +119,7 @@ Pattern Replace character filter 支持如下三个参数: } ``` -## 2. 分词器(Tokenizer) +## 分词器(Tokenizer) tokenizer 即分词器,也是 analyzer 最重要的组件,它对文本进行分词;**一个 analyzer 必需且只可包含一个 tokenizer**。 @@ -130,7 +129,7 @@ ES 自带默认的分词器是 standard tokenizer,标准分词器提供基于 ES 默认提供的分词器 standard 对中文分词不优化,效果差,一般会安装第三方中文分词插件,通常首先 [elasticsearch-analysis-ik](https://github.com/medcl/elasticsearch-analysis-ik) 插件,它其实是 ik 针对的 ES 的定制版。 -### 2.1. elasticsearch-plugin 使用 +### elasticsearch-plugin 使用 在安装 elasticsearch-analysis-ik 第三方之前,我们首先要了解 es 的插件管理工具 **_`elasticsearch-plugin`_** 的使用。 @@ -163,7 +162,7 @@ elasticsearch-plugin remove {plugin_name} > 在安装插件时,要保证安装的插件与 ES 版本一致。 -### 2.2. elasticsearch-analysis-ik 安装 +### elasticsearch-analysis-ik 安装 在确定要安装的 ik 版本之后,执行如下命令: @@ -179,7 +178,7 @@ libexec/plugins/analysis-ik/ libexec/config/analysis-ik/ ``` -### 2.3. elasticsearch-analysis-ik 使用 +### elasticsearch-analysis-ik 使用 ES 5.X 版本开始安装完的 elasticsearch-analysis-ik 提供了两个分词器,分别对应名称是 **_ik_max_word_** 和 **_ik_smart_**,ik_max_word 是索引侧的分词器,走全切模式,ik_smart 是搜索侧的分词器,走智能分词,属于搜索模式。 @@ -296,7 +295,7 @@ elasticsearch-analysis-ik 配置文件为 `IKAnalyzer.cfg.xml`,它位于 `libe > 当然,如果开发者认为 ik 默认的词表有问题,也可以进行调整,文件都在 `libexec/config/analysis-ik` 下,如 main.dic 为主词典,stopword.dic 为停用词表。 -## 3. 词元过滤器(Token Filters) +## 词元过滤器(Token Filters) token filters 叫词元过滤器,或词项过滤器,对 tokenizer 分出的词进行过滤处理。常用的有转小写、停用词处理、同义词处理等等。**一个 analyzer 可包含 0 个或多个词项过滤器,按配置顺序进行过滤**。 @@ -331,7 +330,7 @@ PUT /test_index } ``` -### 3.1. 同义词 +### 同义词 Elasticsearch 同义词通过专有的同义词过滤器(synonym token filter)来进行工作,它允许在分析(analysis)过程中方便地处理同义词,一般是通过配置文件配置同义词。此外,同义词可以再建索引时(index-time synonyms)或者检索时(search-time synonyms)使用。 @@ -402,6 +401,6 @@ elasticsearch 的同义词有如下两种形式: 此时,elasticsearch 中不会将“土豆”和“potato”视为同义词关系,所以多个同义词要写在一起,这往往是开发中经常容易疏忽的点。 -## 4. 参考资料 +## 参考资料 -- [Elasticsearch 教程](https://www.knowledgedict.com/tutorial/elasticsearch-intro.html) +- [Elasticsearch 教程](https://www.knowledgedict.com/tutorial/elasticsearch-intro.html) \ No newline at end of file diff --git "a/docs/nosql/elasticsearch/Elasticsearch\346\200\247\350\203\275\344\274\230\345\214\226.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/10.Elasticsearch\346\200\247\350\203\275\344\274\230\345\214\226.md" similarity index 88% rename from "docs/nosql/elasticsearch/Elasticsearch\346\200\247\350\203\275\344\274\230\345\214\226.md" rename to "docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/10.Elasticsearch\346\200\247\350\203\275\344\274\230\345\214\226.md" index a0fae067..0105a61f 100644 --- "a/docs/nosql/elasticsearch/Elasticsearch\346\200\247\350\203\275\344\274\230\345\214\226.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/10.Elasticsearch\346\200\247\350\203\275\344\274\230\345\214\226.md" @@ -1,45 +1,27 @@ +--- +title: Elasticsearch 性能优化 +date: 2022-01-21 19:54:43 +categories: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch +tags: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch + - 性能 +permalink: /pages/2d95ce/ +--- + # Elasticsearch 性能优化 Elasticsearch 是当前流行的企业级搜索引擎,设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。作为一个开箱即用的产品,在生产环境上线之后,我们其实不一定能确保其的性能和稳定性。如何根据实际情况提高服务的性能,其实有很多技巧。这章我们分享从实战经验中总结出来的 elasticsearch 性能优化,主要从硬件配置优化、索引优化设置、查询方面优化、数据结构优化、集群架构优化等方面讲解。 - - -- [1. 硬件配置优化](#1-硬件配置优化) - - [1.1. CPU 配置](#11-cpu-配置) - - [1.2. 内存配置](#12-内存配置) - - [1.3. 磁盘](#13-磁盘) -- [2. 索引优化设置](#2-索引优化设置) - - [2.1. 批量提交](#21-批量提交) - - [2.2. 增加 Refresh 时间间隔](#22-增加-refresh-时间间隔) - - [2.3. 修改 index_buffer_size 的设置](#23-修改-index_buffer_size-的设置) - - [2.4. 修改 translog 相关的设置](#24-修改-translog-相关的设置) - - [2.5. 注意 \_id 字段的使用](#25-注意-_id-字段的使用) - - [2.6. 注意 \_all 字段及 \_source 字段的使用](#26-注意-_all-字段及-_source-字段的使用) - - [2.7. 合理的配置使用 index 属性](#27-合理的配置使用-index-属性) - - [2.8. 减少副本数量](#28-减少副本数量) -- [3. 查询方面优化](#3-查询方面优化) - - [3.1. 路由优化](#31-路由优化) - - [3.2. Filter VS Query](#32-filter-vs-query) - - [3.3. 深度翻页](#33-深度翻页) - - [3.4. 脚本(script)合理使用](#34-脚本script合理使用) -- [4. 数据结构优化](#4-数据结构优化) - - [4.1. 尽量减少不需要的字段](#41-尽量减少不需要的字段) - - [4.2. Nested Object vs Parent/Child](#42-nested-object-vs-parentchild) - - [4.3. 选择静态映射,非必需时,禁止动态映射](#43-选择静态映射非必需时禁止动态映射) -- [5. 集群架构设计](#5-集群架构设计) - - [5.1. 主节点、数据节点和协调节点分离](#51-主节点数据节点和协调节点分离) - - [5.2. 关闭 data 节点服务器中的 http 功能](#52-关闭-data-节点服务器中的-http-功能) - - [5.3. 一台服务器上最好只部署一个 node](#53-一台服务器上最好只部署一个-node) - - [5.4. 集群分片设置](#54-集群分片设置) -- [6. 参考资料](#6-参考资料) - - - -## 1. 硬件配置优化 +## 硬件配置优化 升级硬件设备配置一直都是提高服务能力最快速有效的手段,在系统层面能够影响应用性能的一般包括三个因素:CPU、内存和 IO,可以从这三方面进行 ES 的性能优化工作。 -### 1.1. CPU 配置 +### CPU 配置 一般说来,CPU 繁忙的原因有以下几个: @@ -49,7 +31,7 @@ Elasticsearch 是当前流行的企业级搜索引擎,设计用于云计算中 大多数 Elasticsearch 部署往往对 CPU 要求不高。因此,相对其它资源,具体配置多少个(CPU)不是那么关键。你应该选择具有多个内核的现代处理器,常见的集群使用 2 到 8 个核的机器。**如果你要在更快的 CPUs 和更多的核数之间选择,选择更多的核数更好**。多个内核提供的额外并发远胜过稍微快一点点的时钟频率。 -### 1.2. 内存配置 +### 内存配置 如果有一种资源是最先被耗尽的,它可能是内存。排序和聚合都很耗内存,所以有足够的堆空间来应付它们是很重要的。即使堆空间是比较小的时候,也能为操作系统文件缓存提供额外的内存。因为 Lucene 使用的许多数据结构是基于磁盘的格式,Elasticsearch 利用操作系统缓存能产生很大效果。 @@ -77,7 +59,7 @@ Elasticsearch 是当前流行的企业级搜索引擎,设计用于云计算中 保持线程池的现有设置,目前 ES 的线程池较 1.X 有了较多优化设置,保持现状即可;默认线程池大小等于 CPU 核心数。如果一定要改,按公式 ( ( CPU 核心数 \* 3 ) / 2 ) + 1 设置;不能超过 CPU 核心数的 2 倍;但是不建议修改默认配置,否则会对 CPU 造成硬伤。 -### 1.3. 磁盘 +### 磁盘 硬盘对所有的集群都很重要,对大量写入的集群更是加倍重要(例如那些存储日志数据的)。硬盘是服务器上最慢的子系统,这意味着那些写入量很大的集群很容易让硬盘饱和,使得它成为集群的瓶颈。 @@ -97,11 +79,11 @@ Elasticsearch 是当前流行的企业级搜索引擎,设计用于云计算中 **最后,避免使用网络附加存储(NAS)**。人们常声称他们的 NAS 解决方案比本地驱动器更快更可靠。除却这些声称,我们从没看到 NAS 能配得上它的大肆宣传。NAS 常常很慢,显露出更大的延时和更宽的平均延时方差,而且它是单点故障的。 -## 2. 索引优化设置 +## 索引优化设置 索引优化主要是在 Elasticsearch 的插入层面优化,Elasticsearch 本身索引速度其实还是蛮快的,具体数据,我们可以参考官方的 benchmark 数据。我们可以根据不同的需求,针对索引优化。 -### 2.1. 批量提交 +### 批量提交 当有大量数据提交的时候,建议采用批量提交(Bulk 操作);此外使用 bulk 请求时,每个请求不超过几十 M,因为太大会导致内存使用过大。 @@ -111,7 +93,7 @@ Elasticsearch 是当前流行的企业级搜索引擎,设计用于云计算中 如果在提交过程中,遇到 EsRejectedExecutionException 异常的话,则说明集群的索引性能已经达到极限了。这种情况,要么提高服务器集群的资源,要么根据业务规则,减少数据收集速度,比如只收集 Warn、Error 级别以上的日志。 -### 2.2. 增加 Refresh 时间间隔 +### 增加 Refresh 时间间隔 为了提高索引性能,Elasticsearch 在写入数据的时候,采用延迟写入的策略,即数据先写到内存中,当超过默认 1 秒(index.refresh_interval)会进行一次写入操作,就是将内存中 segment 数据刷新到磁盘中,此时我们才能将数据搜索出来,所以这就是为什么 Elasticsearch 提供的是近实时搜索功能,而不是实时搜索功能。 @@ -119,7 +101,7 @@ Elasticsearch 是当前流行的企业级搜索引擎,设计用于云计算中 > 在加载大量数据时候可以暂时不用 refresh 和 repliccas,index.refresh_interval 设置为-1,index.number_of_replicas 设置为 0。 -### 2.3. 修改 index_buffer_size 的设置 +### 修改 index_buffer_size 的设置 索引缓冲的设置可以控制多少内存分配给索引进程。这是一个全局配置,会应用于一个节点上所有不同的分片上。 @@ -130,7 +112,7 @@ indices.memory.min_index_buffer_size: 48mb `indices.memory.index_buffer_size` 接受一个百分比或者一个表示字节大小的值。默认是 10%,意味着分配给节点的总内存的 10%用来做索引缓冲的大小。这个数值被分到不同的分片(shards)上。如果设置的是百分比,还可以设置 `min_index_buffer_size` (默认 48mb)和 `max_index_buffer_size`(默认没有上限)。 -### 2.4. 修改 translog 相关的设置 +### 修改 translog 相关的设置 一是控制数据从内存到硬盘的操作频率,以减少硬盘 IO。可将 sync_interval 的时间设置大一些。默认为 5s。 @@ -144,29 +126,29 @@ index.translog.sync_interval: 5s index.translog.flush_threshold_size: 512mb ``` -### 2.5. 注意 \_id 字段的使用 +### 注意 \_id 字段的使用 \_id 字段的使用,应尽可能避免自定义 \_id,以避免针对 ID 的版本管理;建议使用 ES 的默认 ID 生成策略或使用数字类型 ID 做为主键。 -### 2.6. 注意 \_all 字段及 \_source 字段的使用 +### 注意 \_all 字段及 \_source 字段的使用 **\_**all 字段及 \_source 字段的使用,应该注意场景和需要,\_all 字段包含了所有的索引字段,方便做全文检索,如果无此需求,可以禁用;\_source 存储了原始的 document 内容,如果没有获取原始文档数据的需求,可通过设置 includes、excludes 属性来定义放入 \_source 的字段。 -### 2.7. 合理的配置使用 index 属性 +### 合理的配置使用 index 属性 合理的配置使用 index 属性,analyzed 和 not_analyzed,根据业务需求来控制字段是否分词或不分词。只有 groupby 需求的字段,配置时就设置成 not_analyzed,以提高查询或聚类的效率。 -### 2.8. 减少副本数量 +### 减少副本数量 Elasticsearch 默认副本数量为 3 个,虽然这样会提高集群的可用性,增加搜索的并发数,但是同时也会影响写入索引的效率。 在索引过程中,需要把更新的文档发到副本节点上,等副本节点生效后在进行返回结束。使用 Elasticsearch 做业务搜索的时候,建议副本数目还是设置为 3 个,但是像内部 ELK 日志系统、分布式跟踪系统中,完全可以将副本数目设置为 1 个。 -## 3. 查询方面优化 +## 查询方面优化 Elasticsearch 作为业务搜索的近实时查询时,查询效率的优化显得尤为重要。 -### 3.1. 路由优化 +### 路由优化 当我们查询文档的时候,Elasticsearch 如何知道一个文档应该存放到哪个分片中呢?它其实是通过下面这个公式来计算出来的。 @@ -189,7 +171,7 @@ routing 默认值是文档的 id,也可以采用自定义值,比如用户 ID 向上面自定义的用户查询,如果 routing 设置为 userid 的话,就可以直接查询出数据来,效率提升很多。 -### 3.2. Filter VS Query +### Filter VS Query 尽可能使用过滤器上下文(Filter)替代查询上下文(Query) @@ -198,7 +180,7 @@ routing 默认值是文档的 id,也可以采用自定义值,比如用户 ID Elasticsearch 针对 Filter 查询只需要回答「是」或者「否」,不需要像 Query 查询一样计算相关性分数,同时 Filter 结果可以缓存。 -### 3.3. 深度翻页 +### 深度翻页 在使用 Elasticsearch 过程中,应尽量避免大翻页的出现。 @@ -208,15 +190,15 @@ Elasticsearch 针对 Filter 查询只需要回答「是」或者「否」,不 也可以结合实际业务特点,文档 id 大小如果和文档创建时间是一致有序的,可以以文档 id 作为分页的偏移量,并将其作为分页查询的一个条件。 -### 3.4. 脚本(script)合理使用 +### 脚本(script)合理使用 我们知道脚本使用主要有 3 种形式,内联动态编译方式、\_script 索引库中存储和文件脚本存储的形式;一般脚本的使用场景是粗排,尽量用第二种方式先将脚本存储在 \_script 索引库中,起到提前编译,然后通过引用脚本 id,并结合 params 参数使用,即可以达到模型(逻辑)和数据进行了分离,同时又便于脚本模块的扩展与维护。具体 ES 脚本的深入内容请参考 [Elasticsearch 脚本模块的详解](https://www.knowledgedict.com/tutorial/elasticsearch-script.html)。 -## 4. 数据结构优化 +## 数据结构优化 基于 Elasticsearch 的使用场景,文档数据结构尽量和使用场景进行结合,去掉没用及不合理的数据。 -### 4.1. 尽量减少不需要的字段 +### 尽量减少不需要的字段 如果 Elasticsearch 用于业务搜索服务,一些不需要用于搜索的字段最好不存到 ES 中,这样即节省空间,同时在相同的数据量下,也能提高搜索性能。 @@ -230,7 +212,7 @@ index.mapping.total_fields.limit: 1000 index.mapping.depth.limit: 20 ``` -### 4.2. Nested Object vs Parent/Child +### Nested Object vs Parent/Child 尽量避免使用 nested 或 parent/child 的字段,能不用就不用;nested query 慢,parent/child query 更慢,比 nested query 慢上百倍;因此能在 mapping 设计阶段搞定的(大宽表设计或采用比较 smart 的数据结构),就不要用父子关系的 mapping。 @@ -246,17 +228,17 @@ index.mapping.nested_fields.limit: 50 | 缺点 | 更新父文档或子文档时需要更新整个文档 | 为了维护 join 关系,需要占用部分内存,读取性能较差 | | 场景 | 子文档偶尔更新,查询频繁 | 子文档更新频繁 | -### 4.3. 选择静态映射,非必需时,禁止动态映射 +### 选择静态映射,非必需时,禁止动态映射 尽量避免使用动态映射,这样有可能会导致集群崩溃,此外,动态映射有可能会带来不可控制的数据类型,进而有可能导致在查询端出现相关异常,影响业务。 此外,Elasticsearch 作为搜索引擎时,主要承载 query 的匹配和排序的功能,那数据的存储类型基于这两种功能的用途分为两类,一是需要匹配的字段,用来建立倒排索引对 query 匹配用,另一类字段是用做粗排用到的特征字段,如 ctr、点击数、评论数等等。 -## 5. 集群架构设计 +## 集群架构设计 合理的部署 Elasticsearch 有助于提高服务的整体可用性。 -### 5.1. 主节点、数据节点和协调节点分离 +### 主节点、数据节点和协调节点分离 Elasticsearch 集群在架构拓朴时,采用主节点、数据节点和负载均衡节点分离的架构,在 5.x 版本以后,又可将数据节点再细分为“Hot-Warm”的架构模式。 @@ -306,23 +288,23 @@ node.attr.box_type: warm - node.master:true 和 node.data:false,该 node 服务器只作为一个主节点,但不存储任何索引数据,该 node 服务器将使用自身空闲的资源,来协调各种创建索引请求或者查询请求,并将这些请求合理分发到相关的 node 服务器上。 - node.master:false 和 node.data:false,该 node 服务器即不会被选作主节点,也不会存储任何索引数据。该服务器主要用于查询负载均衡。在查询的时候,通常会涉及到从多个 node 服务器上查询数据,并将请求分发到多个指定的 node 服务器,并对各个 node 服务器返回的结果进行一个汇总处理,最终返回给客户端。 -### 5.2. 关闭 data 节点服务器中的 http 功能 +### 关闭 data 节点服务器中的 http 功能 针对 Elasticsearch 集群中的所有数据节点,不用开启 http 服务。将其中的配置参数这样设置,`http.enabled:false`,同时也不要安装 head, bigdesk, marvel 等监控插件,这样保证 data 节点服务器只需处理创建/更新/删除/查询索引数据等操作。 http 功能可以在非数据节点服务器上开启,上述相关的监控插件也安装到这些服务器上,用于监控 Elasticsearch 集群状态等数据信息。这样做一来出于数据安全考虑,二来出于服务性能考虑。 -### 5.3. 一台服务器上最好只部署一个 node +### 一台服务器上最好只部署一个 node 一台物理服务器上可以启动多个 node 服务器节点(通过设置不同的启动 port),但一台服务器上的 CPU、内存、硬盘等资源毕竟有限,从服务器性能考虑,不建议一台服务器上启动多个 node 节点。 -### 5.4. 集群分片设置 +### 集群分片设置 ES 一旦创建好索引后,就无法调整分片的设置,而在 ES 中,一个分片实际上对应一个 lucene 索引,而 lucene 索引的读写会占用很多的系统资源,因此,分片数不能设置过大;所以,在创建索引时,合理配置分片数是非常重要的。一般来说,我们遵循一些原则: 1. 控制每个分片占用的硬盘容量不超过 ES 的最大 JVM 的堆空间设置(一般设置不超过 32 G,参考上面的 JVM 内存设置原则),因此,如果索引的总容量在 500 G 左右,那分片大小在 16 个左右即可;当然,最好同时考虑原则 2。 2. 考虑一下 node 数量,一般一个节点有时候就是一台物理机,如果分片数过多,大大超过了节点数,很可能会导致一个节点上存在多个分片,一旦该节点故障,即使保持了 1 个以上的副本,同样有可能会导致数据丢失,集群无法恢复。所以,**一般都设置分片数不超过节点数的 3 倍**。 -## 6. 参考资料 +## 参考资料 -- [Elasticsearch 教程](https://www.knowledgedict.com/tutorial/elasticsearch-intro.html) +- [Elasticsearch 教程](https://www.knowledgedict.com/tutorial/elasticsearch-intro.html) \ No newline at end of file diff --git a/docs/nosql/elasticsearch/ElasticsearchRestApi.md "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/11.ElasticsearchRestApi.md" similarity index 92% rename from docs/nosql/elasticsearch/ElasticsearchRestApi.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/11.ElasticsearchRestApi.md" index 813c4112..57cdbac2 100644 --- a/docs/nosql/elasticsearch/ElasticsearchRestApi.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/11.ElasticsearchRestApi.md" @@ -1,3 +1,18 @@ +--- +title: Elasticsearch Rest API +date: 2020-06-16 07:10:44 +categories: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch +tags: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch + - API +permalink: /pages/4b1907/ +--- + # ElasticSearch Rest API > **[Elasticsearch](https://github.com/elastic/elasticsearch) 是一个分布式、RESTful 风格的搜索和数据分析引擎**,能够解决不断涌现出的各种用例。 作为 Elastic Stack 的核心,它集中存储您的数据,帮助您发现意料之中以及意料之外的情况。 @@ -8,38 +23,7 @@ > > REST API 最详尽的文档应该参考:[ES 官方 REST API](https://www.elastic.co/guide/en/elasticsearch/reference/current/rest-apis.html) - - -- [1. ElasticSearch Rest API 语法格式](#1-elasticsearch-rest-api-语法格式) -- [2. 索引 API](#2-索引-api) - - [2.1. 创建索引](#21-创建索引) - - [2.2. 删除索引](#22-删除索引) - - [2.3. 查看索引](#23-查看索引) - - [2.4. 索引别名](#24-索引别名) - - [2.5. 打开/关闭索引](#25-打开关闭索引) -- [3. 文档](#3-文档) - - [3.1. 创建文档](#31-创建文档) - - [3.2. 删除文档](#32-删除文档) - - [3.3. 更新文档](#33-更新文档) - - [3.4. 查询文档](#34-查询文档) - - [3.5. 全文搜索](#35-全文搜索) - - [3.6. 逻辑运算](#36-逻辑运算) - - [3.7. 批量执行](#37-批量执行) - - [3.8. 批量读取](#38-批量读取) - - [3.9. 批量查询](#39-批量查询) - - [3.10. URI Search 查询语义](#310-uri-search-查询语义) - - [3.11. Request Body & DSL](#311-request-body--dsl) -- [4. 集群 API](#4-集群-api) - - [4.1. 集群健康 API](#41-集群健康-api) - - [4.2. 集群状态 API](#42-集群状态-api) -- [5. 节点 API](#5-节点-api) -- [6. 分片 API](#6-分片-api) -- [7. 监控 API](#7-监控-api) -- [8. 参考资料](#8-参考资料) - - - -## 1. ElasticSearch Rest API 语法格式 +## ElasticSearch Rest API 语法格式 向 Elasticsearch 发出的请求的组成部分与其它普通的 HTTP 请求是一样的: @@ -55,11 +39,24 @@ curl -X '://:/?' -d '' - `QUERY_STRING`:一些可选的查询请求参数,例如?pretty 参数将使请求返回更加美观易读的 JSON 数据 - `BODY`:一个 JSON 格式的请求主体(如果请求需要的话) -## 2. 索引 API +ElasticSearch Rest API 分为两种: + +- **URI Search**:在 URL 中使用查询参数 +- **Request Body Search**:基于 JSON 格式的、更加完备的 DSL + +URI Search 示例: + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220530072511.png) + +Request Body Search 示例: + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220530072654.png) + +## 索引 API > 参考资料:[Elasticsearch 官方之 cat 索引 API](https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-indices.html) -### 2.1. 创建索引 +### 创建索引 新建 Index,可以直接向 ES 服务器发出 `PUT` 请求。 @@ -103,7 +100,7 @@ PUT /user action.auto_create_index: false ``` -### 2.2. 删除索引 +### 删除索引 然后,我们可以通过发送 `DELETE` 请求,删除这个 Index。 @@ -118,7 +115,7 @@ DELETE /index_one,index_two DELETE /index_* ``` -### 2.3. 查看索引 +### 查看索引 可以通过 GET 请求查看索引信息 @@ -149,7 +146,7 @@ GET /_cat/indices/kibana*?pri&v&h=health,index,pri,rep,docs.count,mt GET /_cat/indices?v&h=i,tm&s=tm:desc ``` -### 2.4. 索引别名 +### 索引别名 ES 的索引别名就是给一个索引或者多个索引起的另一个名字,典型的应用场景是针对索引使用的平滑切换。 @@ -187,7 +184,7 @@ POST /_aliases ES 索引别名有个典型的应用场景是平滑切换,更多细节可以查看 [Elasticsearch(ES)索引零停机(无需重启)无缝平滑切换的方法](https://www.knowledgedict.com/tutorial/elasticsearch-index-smooth-shift.html)。 -### 2.5. 打开/关闭索引 +### 打开/关闭索引 通过在 `POST` 中添加 `_close` 或 `_open` 可以打开、关闭索引。 @@ -200,7 +197,7 @@ POST kibana_sample_data_ecommerce/_open POST kibana_sample_data_ecommerce/_close ``` -## 3. 文档 +## 文档 ```bash ############Create Document############ @@ -356,7 +353,7 @@ DELETE test DELETE test2 ``` -### 3.1. 创建文档 +### 创建文档 #### 指定 ID @@ -400,7 +397,7 @@ POST /user/_doc } ``` -### 3.2. 删除文档 +### 删除文档 语法格式: @@ -414,7 +411,7 @@ DELETE /_index/_doc/_id DELETE /user/_doc/1 ``` -### 3.3. 更新文档 +### 更新文档 #### 先删除,再写入 @@ -454,7 +451,7 @@ POST /user/_update/1 } ``` -### 3.4. 查询文档 +### 查询文档 #### 指定 ID 查询 @@ -547,7 +544,7 @@ $ curl 'localhost:9200/user/admin/_search?pretty' 返回的记录中,每条记录都有一个`_score`字段,表示匹配的程序,默认是按照这个字段降序排列。 -### 3.5. 全文搜索 +### 全文搜索 ES 的查询非常特别,使用自己的[查询语法](https://www.elastic.co/guide/en/elasticsearch/reference/5.5/query-dsl.html),要求 GET 请求带有数据体。 @@ -614,7 +611,7 @@ $ curl 'localhost:9200/user/admin/_search' -d ' 上面代码指定,从位置 1 开始(默认是从位置 0 开始),只返回一条结果。 -### 3.6. 逻辑运算 +### 逻辑运算 如果有多个搜索关键字, Elastic 认为它们是`or`关系。 @@ -643,7 +640,7 @@ $ curl -H 'Content-Type: application/json' 'localhost:9200/user/admin/_search?pr }' ``` -### 3.7. 批量执行 +### 批量执行 支持在一次 API 调用中,对不同的索引进行操作 @@ -671,7 +668,7 @@ POST _bulk > 说明:上面的示例如果执行多次,执行结果都不一样。 -### 3.8. 批量读取 +### 批量读取 读多个索引 @@ -733,7 +730,7 @@ GET /_mget } ``` -### 3.9. 批量查询 +### 批量查询 ```bash POST kibana_sample_data_ecommerce/_msearch @@ -743,7 +740,7 @@ POST kibana_sample_data_ecommerce/_msearch {"query" : {"match_all" : {}},"size":2} ``` -### 3.10. URI Search 查询语义 +### URI Search 查询语义 Elasticsearch URI Search 遵循 QueryString 查询语义,其形式如下: @@ -892,7 +889,7 @@ GET /movies/_search?q=title:"Lord Rings"~2 } ``` -### 3.11. Request Body & DSL +### Request Body & DSL Elasticsearch 除了 URI Search 查询方式,还支持将查询语句通过 Http Request Body 发起查询。 @@ -1032,7 +1029,7 @@ POST movies/_search } ``` -## 4. 集群 API +## 集群 API > [Elasticsearch 官方之 Cluster API](https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster.html) @@ -1076,7 +1073,7 @@ GET /_nodes/ra*:2 GET /_nodes/ra*:2* ``` -### 4.1. 集群健康 API +### 集群健康 API ```bash GET /_cluster/health @@ -1085,7 +1082,7 @@ GET /_cluster/health/kibana_sample_data_ecommerce,kibana_sample_data_flights GET /_cluster/health/kibana_sample_data_flights?level=shards ``` -### 4.2. 集群状态 API +### 集群状态 API 集群状态 API 返回表示整个集群状态的元数据。 @@ -1093,7 +1090,7 @@ GET /_cluster/health/kibana_sample_data_flights?level=shards GET /_cluster/state ``` -## 5. 节点 API +## 节点 API > [Elasticsearch 官方之 cat Nodes API](https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-nodes.html)——返回有关集群节点的信息。 @@ -1104,7 +1101,7 @@ GET /_cat/nodes?v=true GET /_cat/nodes?v=true&h=id,ip,port,v,m ``` -## 6. 分片 API +## 分片 API > [Elasticsearch 官方之 cat Shards API](https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-shards.html)——shards 命令是哪些节点包含哪些分片的详细视图。它会告诉你它是主还是副本、文档数量、它在磁盘上占用的字节数以及它所在的节点。 @@ -1117,7 +1114,7 @@ GET /_cat/shards/my-index-* GET /_cat/shards?h=index,shard,prirep,state,unassigned.reason ``` -## 7. 监控 API +## 监控 API Elasticsearch 中集群相关的健康、统计等相关的信息都是围绕着 `cat` API 进行的。 @@ -1156,9 +1153,9 @@ GET /_cat /_cat/templates ``` -## 8. 参考资料 +## 参考资料 - **官方** - [Elasticsearch 官网](https://www.elastic.co/cn/products/elasticsearch) - [Elasticsearch Github](https://github.com/elastic/elasticsearch) - - [Elasticsearch 官方文档](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) + - [Elasticsearch 官方文档](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) \ No newline at end of file diff --git a/docs/nosql/elasticsearch/ElasticsearchHighLevelRestJavaApi.md "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/12.ElasticsearchHighLevelRestJavaApi.md" similarity index 88% rename from docs/nosql/elasticsearch/ElasticsearchHighLevelRestJavaApi.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/12.ElasticsearchHighLevelRestJavaApi.md" index 1d68005e..509abf33 100644 --- a/docs/nosql/elasticsearch/ElasticsearchHighLevelRestJavaApi.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/12.ElasticsearchHighLevelRestJavaApi.md" @@ -1,33 +1,25 @@ +--- +title: ElasticSearch Java API 之 High Level REST Client +date: 2022-03-01 18:55:46 +categories: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch +tags: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch + - API +permalink: /pages/201e43/ +--- + # ElasticSearch Java API 之 High Level REST Client > Elasticsearch 官方的 High Level REST Client 在 7.1.5.0 版本废弃。所以本文中的 API 不推荐使用。 - - -- [1. 快速开始](#1-快速开始) - - [1.1. 引入依赖](#11-引入依赖) - - [1.2. 创建连接和关闭](#12-创建连接和关闭) -- [2. 索引 API](#2-索引-api) - - [2.1. 测试准备](#21-测试准备) - - [2.2. 创建索引](#22-创建索引) - - [2.3. 删除索引](#23-删除索引) - - [2.4. 判断索引是否存在](#24-判断索引是否存在) -- [3. 文档 API](#3-文档-api) - - [3.1. 文档测试准备](#31-文档测试准备) - - [3.2. 创建文档](#32-创建文档) - - [3.3. 删除文档](#33-删除文档) - - [3.4. 更新文档](#34-更新文档) - - [3.5. 查看文档](#35-查看文档) - - [3.6. 获取匹配条件的记录总数](#36-获取匹配条件的记录总数) - - [3.7. 分页查询](#37-分页查询) - - [3.8. 条件查询](#38-条件查询) -- [4. 参考资料](#4-参考资料) - - - -## 1. 快速开始 - -### 1.1. 引入依赖 +## 快速开始 + +### 引入依赖 在 pom.xml 中引入以下依赖: @@ -39,7 +31,7 @@ ``` -### 1.2. 创建连接和关闭 +### 创建连接和关闭 ```java // 创建连接 @@ -52,9 +44,9 @@ RestHighLevelClient client = new RestHighLevelClient( client.close(); ``` -## 2. 索引 API +## 索引 API -### 2.1. 测试准备 +### 测试准备 ```java public static final String INDEX = "mytest"; @@ -73,7 +65,7 @@ public static final String MAPPING_JSON = private RestHighLevelClient client; ``` -### 2.2. 创建索引 +### 创建索引 ```java // 创建索引 @@ -93,7 +85,7 @@ CreateIndexRequest createIndexRequest = new CreateIndexRequest(INDEX); Assertions.assertTrue(createIndexResponse.isAcknowledged()); ``` -### 2.3. 删除索引 +### 删除索引 ```java // 删除索引 @@ -102,7 +94,7 @@ DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(INDEX); Assertions.assertTrue(deleteResponse.isAcknowledged()); ``` -### 2.4. 判断索引是否存在 +### 判断索引是否存在 ```java GetIndexRequest getIndexRequest = new GetIndexRequest(INDEX); @@ -111,9 +103,9 @@ GetIndexRequest getIndexRequest = new GetIndexRequest(INDEX); Assertions.assertTrue(client.indices().exists(getIndexAliasRequest, RequestOptions.DEFAULT)); ``` -## 3. 文档 API +## 文档 API -### 3.1. 文档测试准备 +### 文档测试准备 ```java public static final String INDEX = "mytest"; @@ -166,7 +158,7 @@ public void destroy() throws IOException { } ``` -### 3.2. 创建文档 +### 创建文档 RestHighLevelClient Api 使用 `IndexRequest` 来构建创建文档的请求参数。 @@ -206,7 +198,7 @@ public void onFailure(Exception e) { }); ``` -### 3.3. 删除文档 +### 删除文档 RestHighLevelClient Api 使用 `DeleteRequest` 来构建删除文档的请求参数。 @@ -239,7 +231,7 @@ public void onFailure(Exception e) { }); ``` -### 3.4. 更新文档 +### 更新文档 RestHighLevelClient Api 使用 `UpdateRequest` 来构建更新文档的请求参数。 @@ -278,7 +270,7 @@ public void onFailure(Exception e) { }); ``` -### 3.5. 查看文档 +### 查看文档 RestHighLevelClient Api 使用 `GetRequest` 来构建查看文档的请求参数。 @@ -310,7 +302,7 @@ public void onFailure(Exception e) { }); ``` -### 3.6. 获取匹配条件的记录总数 +### 获取匹配条件的记录总数 ```java @Test @@ -329,7 +321,7 @@ public void count() throws IOException { } ``` -### 3.7. 分页查询 +### 分页查询 ```java @ParameterizedTest @@ -358,7 +350,7 @@ public void pageTest(int page) throws IOException { } ``` -### 3.8. 条件查询 +### 条件查询 ```java @Test @@ -383,7 +375,7 @@ public void matchPhraseQuery() throws IOException { } ``` -## 4. 参考资料 +## 参考资料 - **官方** - - [Java High Level REST Client](https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high.html) + - [Java High Level REST Client](https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high.html) \ No newline at end of file diff --git "a/docs/nosql/elasticsearch/Elasticsearch\351\233\206\347\276\244\345\222\214\345\210\206\347\211\207.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/13.Elasticsearch\351\233\206\347\276\244\345\222\214\345\210\206\347\211\207.md" similarity index 96% rename from "docs/nosql/elasticsearch/Elasticsearch\351\233\206\347\276\244\345\222\214\345\210\206\347\211\207.md" rename to "docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/13.Elasticsearch\351\233\206\347\276\244\345\222\214\345\210\206\347\211\207.md" index 93b7d29d..56401b5b 100644 --- "a/docs/nosql/elasticsearch/Elasticsearch\351\233\206\347\276\244\345\222\214\345\210\206\347\211\207.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/13.Elasticsearch\351\233\206\347\276\244\345\222\214\345\210\206\347\211\207.md" @@ -1,33 +1,24 @@ +--- +title: Elasticsearch 集群和分片 +date: 2022-03-01 20:52:25 +categories: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch +tags: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch + - 集群 + - 分片 +permalink: /pages/9a2546/ +--- + # Elasticsearch 集群和分片 - - -- [1. 集群](#1-集群) - - [1.1. 空集群](#11-空集群) - - [1.2. 集群健康](#12-集群健康) - - [1.3. 添加索引](#13-添加索引) - - [1.4. 添加故障转移](#14-添加故障转移) - - [1.5. 水平扩容](#15-水平扩容) - - [1.6. 更多的扩容](#16-更多的扩容) - - [1.7. 应对故障](#17-应对故障) -- [2. 分片](#2-分片) - - [2.1. 使文本可被搜索](#21-使文本可被搜索) - - [2.2. 不变性](#22-不变性) - - [2.3. 动态更新索引](#23-动态更新索引) - - [2.4. 删除和更新](#24-删除和更新) - - [2.5. 近实时搜索](#25-近实时搜索) - - [2.6. refresh API](#26-refresh-api) - - [2.7. 持久化变更](#27-持久化变更) - - [2.8. flush API](#28-flush-api) - - [2.9. 段合并](#29-段合并) - - [2.10. optimize API](#210-optimize-api) -- [3. 参考资料](#3-参考资料) - - - -## 1. 集群 - -### 1.1. 空集群 +## 集群 + +### 空集群 如果我们启动了一个单独的节点,里面不包含任何的数据和索引,那我们的集群看起来就是一个包含空内容节点的集群。 @@ -43,7 +34,7 @@ 作为用户,我们可以将请求发送到集群中的任何节点,包括主节点。 每个节点都知道任意文档所处的位置,并且能够将我们的请求直接转发到存储我们所需文档的节点。 无论我们将请求发送到哪个节点,它都能负责从各个包含我们所需文档的节点收集回数据,并将最终结果返回給客户端。 Elasticsearch 对这一切的管理都是透明的。 -### 1.2. 集群健康 +### 集群健康 Elasticsearch 的集群监控信息中包含了许多的统计数据,其中最为重要的一项就是 _集群健康_ , 它在 `status` 字段中展示为 `green` 、 `yellow` 或者 `red` 。 @@ -74,7 +65,7 @@ GET /_cluster/health - **`yellow`**:所有的主分片都正常运行,但不是所有的副本分片都正常运行。 - **`red`**:有主分片没能正常运行。 -### 1.3. 添加索引 +### 添加索引 我们往 Elasticsearch 添加数据时需要用到 _索引_ —— 保存相关数据的地方。索引实际上是指向一个或者多个物理分片的逻辑命名空间 。 @@ -92,7 +83,7 @@ Elasticsearch 是利用分片将数据分发到集群内各处的。分片是数 让我们在包含一个空节点的集群内创建名为 `blogs` 的索引。 索引在默认情况下会被分配 5 个主分片, 但是为了演示目的,我们将分配 3 个主分片和一份副本(每个主分片拥有一个副本分片): -```java +```json PUT /blogs { "settings" : { @@ -137,7 +128,7 @@ PUT /blogs 当前我们的集群是正常运行的,但是在硬件故障时有丢失数据的风险。 -### 1.4. 添加故障转移 +### 添加故障转移 当集群中只有一个节点在运行时,意味着会有一个单点故障问题——没有冗余。 幸运的是,我们只需再启动一个节点即可防止数据丢失。 @@ -181,7 +172,7 @@ PUT /blogs 我们的集群现在不仅仅是正常运行的,并且还处于 _始终可用_ 的状态。 -### 1.5. 水平扩容 +### 水平扩容 怎样为我们的正在增长中的应用程序按需扩容呢? 当启动了第三个节点,我们的集群将拥有三个节点的集群——为了分散负载而对分片进行重新分配。 @@ -193,7 +184,7 @@ PUT /blogs 分片是一个功能完整的搜索引擎,它拥有使用一个节点上的所有资源的能力。 我们这个拥有 6 个分片(3 个主分片和 3 个副本分片)的索引可以最大扩容到 6 个节点,每个节点上存在一个分片,并且每个分片拥有所在节点的全部资源。 -### 1.6. 更多的扩容 +### 更多的扩容 但是如果我们想要扩容超过 6 个节点怎么办呢? @@ -201,7 +192,7 @@ PUT /blogs 在运行中的集群上是可以动态调整副本分片数目的,我们可以按需伸缩集群。让我们把副本数从默认的 `1` 增加到 `2` : -```sense +```json PUT /blogs/_settings { "number_of_replicas" : 2 @@ -218,7 +209,7 @@ PUT /blogs/_settings > > 但是更多的副本分片数提高了数据冗余量:按照上面的节点配置,我们可以在失去 2 个节点的情况下不丢失任何数据。 -### 1.7. 应对故障 +### 应对故障 我们之前说过 Elasticsearch 可以应对节点故障,接下来让我们尝试下这个功能。 如果我们关闭第一个节点,这时集群的状态为关闭了一个节点后的集群。 @@ -238,7 +229,7 @@ PUT /blogs/_settings 到目前为止,你应该对分片如何使得 Elasticsearch 进行水平扩容以及数据保障等知识有了一定了解。 接下来我们将讲述关于分片生命周期的更多细节。 -## 2. 分片 +## 分片 > - 为什么搜索是 _近_ 实时的? > - 为什么文档的 CRUD (创建-读取-更新-删除) 操作是 _实时_ 的? @@ -246,7 +237,7 @@ PUT /blogs/_settings > - 为什么删除文档不会立刻释放空间? > - `refresh`, `flush`, 和 `optimize` API 都做了什么, 你什么情况下应该使用他们? -### 2.1. 使文本可被搜索 +### 使文本可被搜索 必须解决的第一个挑战是如何使文本可被搜索。 传统的数据库每个字段存储单个值,但这对全文检索并不够。文本字段中的每个单词需要被搜索,对数据库意味着需要单个字段有索引多值(这里指单词)的能力。 @@ -269,7 +260,7 @@ the | X | | X | ... 早期的全文检索会为整个文档集合建立一个很大的倒排索引并将其写入到磁盘。 一旦新的索引就绪,旧的就会被其替换,这样最近的变化便可以被检索到。 -### 2.2. 不变性 +### 不变性 倒排索引被写入磁盘后是 _不可改变_ 的:它永远不会修改。 不变性有重要的价值: @@ -280,7 +271,7 @@ the | X | | X | ... 当然,一个不变的索引也有不好的地方。主要事实是它是不可变的! 你不能修改它。如果你需要让一个新的文档 可被搜索,你需要重建整个索引。这要么对一个索引所能包含的数据量造成了很大的限制,要么对索引可被更新的频率造成了很大的限制。 -### 2.3. 动态更新索引 +### 动态更新索引 下一个需要被解决的问题是怎样在保留不变性的前提下实现倒排索引的更新?答案是: 用更多的索引。 @@ -314,7 +305,7 @@ Elasticsearch 基于 Lucene, 这个 java 库引入了 按段搜索 的概念。 当一个查询被触发,所有已知的段按顺序被查询。词项统计会对所有段的结果进行聚合,以保证每个词和每个文档的关联都被准确计算。 这种方式可以用相对较低的成本将新文档添加到索引。 -### 2.4. 删除和更新 +### 删除和更新 段是不可改变的,所以既不能从把文档从旧的段中移除,也不能修改旧的段来进行反映文档的更新。 取而代之的是,每个提交点会包含一个 `.del` 文件,文件中会列出这些被删除文档的段信息。 @@ -324,7 +315,7 @@ Elasticsearch 基于 Lucene, 这个 java 库引入了 按段搜索 的概念。 在 [段合并](https://www.elastic.co/guide/cn/elasticsearch/guide/current/merge-process.html) , 我们展示了一个被删除的文档是怎样被文件系统移除的。 -### 2.5. 近实时搜索 +### 近实时搜索 随着按段(per-segment)搜索的发展,一个新的文档从索引到可被搜索的延迟显著降低了。新文档在几分钟之内即可被检索,但这样还是不够快。 @@ -344,7 +335,7 @@ Lucene 允许新段被写入和打开—使其包含的文档在未进行一次 ![The buffer contents have been written to a segment, which is searchable, but is not yet commited](https://www.elastic.co/guide/cn/elasticsearch/guide/current/images/elas_1105.png) -### 2.6. refresh API +### refresh API 在 Elasticsearch 中,写入和打开一个新段的轻量的过程叫做 _refresh_ 。 默认情况下每个分片会每秒自动刷新一次。这就是为什么我们说 Elasticsearch 是 _近_ 实时搜索: 文档的变化并不是立即对搜索可见,但会在一秒之内变为可见。 @@ -390,7 +381,7 @@ PUT /my_logs/_settings > `refresh_interval` 需要一个 _持续时间_ 值, 例如 `1s` (1 秒) 或 `2m` (2 分钟)。 一个绝对值 _1_ 表示的是 _1 毫秒_ --无疑会使你的集群陷入瘫痪。 -### 2.7. 持久化变更 +### 持久化变更 如果没有用 `fsync` 把数据从文件系统缓存刷(flush)到硬盘,我们不能保证数据在断电甚至是程序正常退出之后依然存在。为了保证 Elasticsearch 的可靠性,需要确保数据变化被持久化到磁盘。 @@ -437,7 +428,7 @@ translog 也被用来提供实时 CRUD 。当你试着通过 ID 查询、更新 ![After a flush, the segments are fully commited and the transaction log is cleared](https://www.elastic.co/guide/cn/elasticsearch/guide/current/images/elas_1109.png) -### 2.8. flush API +### flush API 这个执行一个提交并且截断 translog 的行为在 Elasticsearch 被称作一次 _flush_ 。 分片每 30 分钟被自动刷新(flush),或者在 translog 太大的时候也会刷新。请查看 [`translog` 文档](https://www.elastic.co/guide/en/elasticsearch/reference/2.4/index-modules-translog.html#_translog_settings) 来设置,它可以用来 控制这些阈值: @@ -477,7 +468,7 @@ POST /_flush?wait_for_ongoing > > 如果你不确定这个行为的后果,最好是使用默认的参数( `"index.translog.durability": "request"` )来避免数据丢失。 -### 2.9. 段合并 +### 段合并 由于自动刷新流程每秒会创建一个新的段 ,这样会导致短时间内的段数量暴增。而段数目太多会带来较大的麻烦。 每一个段都会消耗文件句柄、内存和 cpu 运行周期。更重要的是,每个搜索请求都必须轮流检查每个段;所以段越多,搜索也就越慢。 @@ -507,7 +498,7 @@ Elasticsearch 通过在后台进行段合并来解决这个问题。小的段被 合并大的段需要消耗大量的 I/O 和 CPU 资源,如果任其发展会影响搜索性能。Elasticsearch 在默认情况下会对合并流程进行资源限制,所以搜索仍然 有足够的资源很好地执行。 -### 2.10. optimize API +### optimize API `optimize` API 大可看做是 _强制合并_ API。它会将一个分片强制合并到 `max_num_segments` 参数指定大小的段数目。 这样做的意图是减少段的数量(通常减少到一个),来提升搜索性能。 @@ -525,6 +516,6 @@ POST /logstash-2014-10/_optimize?max_num_segments=1 > 请注意,使用 `optimize` API 触发段合并的操作不会受到任何资源上的限制。这可能会消耗掉你节点上全部的 I/O 资源, 使其没有余裕来处理搜索请求,从而有可能使集群失去响应。 如果你想要对索引执行 `optimize`,你需要先使用分片分配(查看 [迁移旧索引](https://www.elastic.co/guide/cn/elasticsearch/guide/current/retiring-data.html#migrate-indices))把索引移到一个安全的节点,再执行。 -## 3. 参考资料 +## 参考资料 -- [Elasticsearch 官方文档之 集群内的原理](https://www.elastic.co/guide/cn/elasticsearch/guide/current/distributed-cluster.html) +- [Elasticsearch 官方文档之 集群内的原理](https://www.elastic.co/guide/cn/elasticsearch/guide/current/distributed-cluster.html) \ No newline at end of file diff --git "a/docs/nosql/elasticsearch/Elasticsearch\350\277\220\347\273\264.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/20.Elasticsearch\350\277\220\347\273\264.md" similarity index 90% rename from "docs/nosql/elasticsearch/Elasticsearch\350\277\220\347\273\264.md" rename to "docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/20.Elasticsearch\350\277\220\347\273\264.md" index 691ee364..084843fb 100644 --- "a/docs/nosql/elasticsearch/Elasticsearch\350\277\220\347\273\264.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/20.Elasticsearch\350\277\220\347\273\264.md" @@ -1,22 +1,23 @@ +--- +title: Elasticsearch 运维 +date: 2020-06-16 07:10:44 +categories: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch +tags: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch + - 运维 +permalink: /pages/fdaf15/ +--- + # Elasticsearch 运维 > [Elasticsearch](https://github.com/elastic/elasticsearch) 是一个分布式、RESTful 风格的搜索和数据分析引擎,能够解决不断涌现出的各种用例。 作为 Elastic Stack 的核心,它集中存储您的数据,帮助您发现意料之中以及意料之外的情况。 - - -- [1. Elasticsearch 安装](#1-elasticsearch-安装) -- [2. Elasticsearch 集群规划](#2-elasticsearch-集群规划) -- [3. Elasticsearch 配置](#3-elasticsearch-配置) -- [4. Elasticsearch FAQ](#4-elasticsearch-faq) - - [4.1. elasticsearch 不允许以 root 权限来运行](#41-elasticsearch-不允许以-root-权限来运行) - - [4.2. vm.max_map_count 不低于 262144](#42-vmmax_map_count-不低于-262144) - - [4.3. nofile 不低于 65536](#43-nofile-不低于-65536) - - [4.4. nproc 不低于 2048](#44-nproc-不低于-2048) -- [5. 参考资料](#5-参考资料) - - - -## 1. Elasticsearch 安装 +## Elasticsearch 安装 > [Elasticsearch 官方下载安装说明](https://www.elastic.co/cn/downloads/elasticsearch) @@ -32,7 +33,7 @@ 执行 `curl http://localhost:9200/` 测试服务是否启动 -## 2. Elasticsearch 集群规划 +## Elasticsearch 集群规划 ElasticSearch 集群需要根据业务实际情况去合理规划。 @@ -49,7 +50,7 @@ ElasticSearch 集群需要根据业务实际情况去合理规划。 - 我们 es 集群的日增量数据大概是 2000 万条,每天日增量数据大概是 500MB,每月增量数据大概是 6 亿,15G。目前系统已经运行了几个月,现在 es 集群里数据总量大概是 100G 左右。 - 目前线上有 5 个索引(这个结合你们自己业务来,看看自己有哪些数据可以放 es 的),每个索引的数据量大概是 20G,所以这个数据量之内,我们每个索引分配的是 8 个 shard,比默认的 5 个 shard 多了 3 个 shard。 -## 3. Elasticsearch 配置 +## Elasticsearch 配置 ES 的默认配置文件为 `config/elasticsearch.yml` @@ -123,9 +124,9 @@ discovery.zen.ping.unicast.hosts: ['host1', 'host2:port', 'host3[portX-portY]'] #设置集群中master节点的初始列表,可以通过这些节点来自动发现新加入集群的节点。 ``` -## 4. Elasticsearch FAQ +## Elasticsearch FAQ -### 4.1. elasticsearch 不允许以 root 权限来运行 +### elasticsearch 不允许以 root 权限来运行 **问题:**在 Linux 环境中,elasticsearch 不允许以 root 权限来运行。 @@ -148,7 +149,7 @@ chown -R elk:elk /opt # 假设你的 elasticsearch 安装在 opt 目录下 su elk ``` -### 4.2. vm.max_map_count 不低于 262144 +### vm.max_map_count 不低于 262144 **问题:**`vm.max_map_count` 表示虚拟内存大小,它是一个内核参数。elasticsearch 默认要求 `vm.max_map_count` 不低于 262144。 @@ -177,7 +178,7 @@ sysctl -p > > 这种情况下,你只能选择直接修改宿主机上的参数了。 -### 4.3. nofile 不低于 65536 +### nofile 不低于 65536 **问题:** `nofile` 表示进程允许打开的最大文件数。elasticsearch 进程要求可以打开的最大文件数不低于 65536。 @@ -194,7 +195,7 @@ echo "* soft nofile 65536" > /etc/security/limits.conf echo "* hard nofile 131072" > /etc/security/limits.conf ``` -### 4.4. nproc 不低于 2048 +### nproc 不低于 2048 **问题:** `nproc` 表示最大线程数。elasticsearch 要求最大线程数不低于 2048。 @@ -211,8 +212,8 @@ echo "* soft nproc 2048" > /etc/security/limits.conf echo "* hard nproc 4096" > /etc/security/limits.conf ``` -## 5. 参考资料 +## 参考资料 - [Elasticsearch 官方下载安装说明](https://www.elastic.co/cn/downloads/elasticsearch) - [Install Elasticsearch with RPM](https://www.elastic.co/guide/en/elasticsearch/reference/current/rpm.html#rpm) -- [Elasticsearch 使用积累](http://siye1982.github.io/2015/09/17/es-optimize/) +- [Elasticsearch 使用积累](http://siye1982.github.io/2015/09/17/es-optimize/) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/README.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/README.md" new file mode 100644 index 00000000..c7372c85 --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/README.md" @@ -0,0 +1,77 @@ +--- +title: Elasticsearch 教程 +date: 2022-04-11 16:52:35 +categories: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch +tags: + - 数据库 + - 搜索引擎数据库 + - Elasticsearch +permalink: /pages/74675e/ +hidden: true +--- + +# Elasticsearch 教程 + +> Elasticsearch 是一个基于 Lucene 的搜索和数据分析工具,它提供了一个分布式服务。Elasticsearch 是遵从 Apache 开源条款的一款开源产品,是当前主流的企业级搜索引擎。 + +## 📖 内容 + +### [Elasticsearch 面试总结](01.Elasticsearch面试总结.md) 💯 + +### [Elasticsearch 快速入门](02.Elasticsearch快速入门.md) + +### [Elasticsearch 简介](03.Elasticsearch简介.md) + +### [Elasticsearch 索引管理](04.Elasticsearch索引.md) + +### [Elasticsearch 映射](05.Elasticsearch映射.md) + +### [Elasticsearch 查询](05.Elasticsearch查询.md) + +### [Elasticsearch 高亮](06.Elasticsearch高亮.md) + +### [Elasticsearch 排序](07.Elasticsearch排序.md) + +### [Elasticsearch 聚合](08.Elasticsearch聚合.md) + +### [Elasticsearch 分析器](09.Elasticsearch分析器.md) + +### [Elasticsearch 性能优化](10.Elasticsearch性能优化.md) + +### [Elasticsearch Rest API](11.ElasticsearchRestApi.md) + +### [ElasticSearch Java API 之 High Level REST Client](12.ElasticsearchHighLevelRestJavaApi.md) + +### [Elasticsearch 集群和分片](13.Elasticsearch集群和分片.md) + +### [Elasticsearch 运维](20.Elasticsearch运维.md) + +## 📚 资料 + +- **官方** + - [Elasticsearch 官网](https://www.elastic.co/cn/products/elasticsearch) + - [Elasticsearch Github](https://github.com/elastic/elasticsearch) + - [Elasticsearch 官方文档](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) + - [Elasticsearch: The Definitive Guide](https://www.elastic.co/guide/en/elasticsearch/guide/master/index.html) - ElasticSearch 官方学习资料 +- **书籍** + - [《Elasticsearch 实战》](https://book.douban.com/subject/30380439/) +- **教程** + - [ELK Stack 权威指南](https://github.com/chenryn/logstash-best-practice-cn) + - [Elasticsearch 教程](https://www.knowledgedict.com/tutorial/elasticsearch-intro.html) +- **文章** + - [Elasticsearch+Logstash+Kibana 教程](https://www.cnblogs.com/xing901022/p/4704319.html) + - [ELK(Elasticsearch、Logstash、Kibana)安装和配置](https://github.com/judasn/Linux-Tutorial/blob/master/ELK-Install-And-Settings.md) + - **性能调优相关**的工程实践 + - [Elasticsearch Performance Tuning Practice at eBay](https://www.ebayinc.com/stories/blogs/tech/elasticsearch-performance-tuning-practice-at-ebay/) + - [Elasticsearch at Kickstarter](https://kickstarter.engineering/elasticsearch-at-kickstarter-db3c487887fc) + - [9 tips on ElasticSearch configuration for high performance](https://www.loggly.com/blog/nine-tips-configuring-elasticsearch-for-high-performance/) + - [Elasticsearch In Production - Deployment Best Practices](https://medium.com/@abhidrona/elasticsearch-deployment-best-practices-d6c1323b25d7) +- **更多资源** + - [GitHub: Awesome ElasticSearch](https://github.com/dzharii/awesome-elasticsearch) + +## 🚪 传送 + +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file diff --git a/docs/nosql/elasticsearch/elastic/elastic-quickstart.md "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/01.Elastic\345\277\253\351\200\237\345\205\245\351\227\250.md" similarity index 98% rename from docs/nosql/elasticsearch/elastic/elastic-quickstart.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/01.Elastic\345\277\253\351\200\237\345\205\245\351\227\250.md" index 1d5dc7ad..82e6f4ee 100644 --- a/docs/nosql/elasticsearch/elastic/elastic-quickstart.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/01.Elastic\345\277\253\351\200\237\345\205\245\351\227\250.md" @@ -1,3 +1,17 @@ +--- +title: Elastic 快速入门 +date: 2020-06-16 07:10:44 +categories: + - 数据库 + - 搜索引擎数据库 + - Elastic +tags: + - 数据库 + - 搜索引擎数据库 + - Elastic +permalink: /pages/553160/ +--- + # Elastic 快速入门 > 开源协议:[Apache 2.0](https://github.com/elastic/elasticsearch/tree/7.4/licenses/APACHE-LICENSE-2.0.txt) @@ -273,4 +287,4 @@ Filebeat 将每个事件的传递状态存储在注册表文件中。所以它 - **文章** - [什么是 ELK Stack?](https://www.elastic.co/cn/what-is/elk-stack) - [https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/es-introduction.md](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/es-introduction.md) - - [es-write-query-search](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/es-write-query-search.md) + - [es-write-query-search](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/es-write-query-search.md) \ No newline at end of file diff --git a/docs/nosql/elasticsearch/elastic/elastic-beats.md "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/02.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Filebeat.md" similarity index 86% rename from docs/nosql/elasticsearch/elastic/elastic-beats.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/02.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Filebeat.md" index ba7b0d4b..3f03ea08 100644 --- a/docs/nosql/elasticsearch/elastic/elastic-beats.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/02.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Filebeat.md" @@ -1,13 +1,16 @@ --- title: Elastic 技术栈之 Filebeat -date: 2017-01-03 +date: 2020-06-16 07:10:44 categories: -- javatool + - 数据库 + - 搜索引擎数据库 + - Elastic tags: -- java -- javatool -- log -- elastic + - 数据库 + - 搜索引擎数据库 + - Elastic + - Filebeat +permalink: /pages/b7f079/ --- # Elastic 技术栈之 Filebeat @@ -27,7 +30,7 @@ Beats 有多种类型,可以根据实际应用需要选择合适的类型。 - **Packetbeat:**网络数据包分析器,提供有关您的应用程序服务器之间交换的事务的信息。 - **Filebeat:**从您的服务器发送日志文件。 - **Metricbeat:**是一个服务器监视代理程序,它定期从服务器上运行的操作系统和服务收集指标。 -- **Winlogbeat:**提供Windows事件日志。 +- **Winlogbeat:**提供 Windows 事件日志。 > **参考** > @@ -43,7 +46,7 @@ Beats 有多种类型,可以根据实际应用需要选择合适的类型。 在任何环境下,应用程序都有停机的可能性。 Filebeat 读取并转发日志行,如果中断,则会记住所有事件恢复联机状态时所在位置。 -Filebeat带有内部模块(auditd,Apache,Nginx,System和MySQL),可通过一个指定命令来简化通用日志格式的收集,解析和可视化。 +Filebeat 带有内部模块(auditd,Apache,Nginx,System 和 MySQL),可通过一个指定命令来简化通用日志格式的收集,解析和可视化。 FileBeat 不会让你的管道超负荷。FileBeat 如果是向 Logstash 传输数据,当 Logstash 忙于处理数据,会通知 FileBeat 放慢读取速度。一旦拥塞得到解决,FileBeat 将恢复到原来的速度并继续传播。 @@ -99,10 +102,10 @@ filebeat: ```yaml filebeat.prospectors: -- type: log - enabled: true - paths: - - /var/log/*.log + - type: log + enabled: true + paths: + - /var/log/*.log ``` #### output.elasticsearch @@ -113,12 +116,12 @@ filebeat.prospectors: ```yaml output.elasticsearch: - hosts: ["192.168.1.42:9200"] + hosts: ['192.168.1.42:9200'] ``` #### output.logstash -如果你希望使用 filebeat 向 logstash输出数据,然后由 logstash 再向elasticsearch 输出数据,需要配置 output.logstash。 +如果你希望使用 filebeat 向 logstash 输出数据,然后由 logstash 再向 elasticsearch 输出数据,需要配置 output.logstash。 > **注意** > @@ -130,7 +133,7 @@ output.elasticsearch: ```yaml output.logstash: - hosts: ["127.0.0.1:5044"] + hosts: ['127.0.0.1:5044'] ``` 此外,还需要在 logstash 的配置文件(如 logstash.conf)中指定 beats input 插件: @@ -166,7 +169,7 @@ output { ```yaml setup.kibana: - host: "localhost:5601" + host: 'localhost:5601' ``` #### setup.template.settings @@ -175,7 +178,7 @@ setup.kibana: 在 Filebeat 中,setup.template.settings 用于配置索引模板。 -Filebeat 推荐的索引模板文件由 Filebeat 软件包安装。如果您接受 filebeat.yml 配置文件中的默认配置,Filebeat在成功连接到 Elasticsearch 后自动加载模板。 +Filebeat 推荐的索引模板文件由 Filebeat 软件包安装。如果您接受 filebeat.yml 配置文件中的默认配置,Filebeat 在成功连接到 Elasticsearch 后自动加载模板。 您可以通过在 Filebeat 配置文件中配置模板加载选项来禁用自动模板加载,或加载自己的模板。您还可以设置选项来更改索引和索引模板的名称。 @@ -189,7 +192,7 @@ Filebeat 推荐的索引模板文件由 Filebeat 软件包安装。如果您接 #### setup.dashboards -Filebeat 附带了示例 Kibana 仪表板。在使用仪表板之前,您需要创建索引模式 `filebeat- *`,并将仪表板加载到Kibana 中。为此,您可以运行 `setup` 命令或在 `filebeat.yml` 配置文件中配置仪表板加载。 +Filebeat 附带了示例 Kibana 仪表板。在使用仪表板之前,您需要创建索引模式 `filebeat- *`,并将仪表板加载到 Kibana 中。为此,您可以运行 `setup` 命令或在 `filebeat.yml` 配置文件中配置仪表板加载。 为了在 Kibana 中加载 Filebeat 的仪表盘,需要在 `filebeat.yml` 配置中启动开关: @@ -200,7 +203,6 @@ setup.dashboards.enabled: true > **参考** > > 更多内容可以参考:[configuration-dashboards](https://www.elastic.co/guide/en/beats/filebeat/current/configuration-dashboards.html) -> ## 命令 @@ -231,7 +233,7 @@ filebeat 提供了一系列命令来完成各种功能。 ## 模块 -Filebeat 提供了一套预构建的模块,让您可以快速实施和部署日志监视解决方案,并附带示例仪表板和数据可视化。这些模块支持常见的日志格式,例如Nginx,Apache2和MySQL 等。 +Filebeat 提供了一套预构建的模块,让您可以快速实施和部署日志监视解决方案,并附带示例仪表板和数据可视化。这些模块支持常见的日志格式,例如 Nginx,Apache2 和 MySQL 等。 ### 运行模块的步骤 @@ -280,16 +282,16 @@ prospector:负责管理 harvester 并找到所有需要读取的文件源。 ```yaml filebeat.prospectors: -- type: log - paths: - - /var/log/*.log - - /var/path2/*.log + - type: log + paths: + - /var/log/*.log + - /var/path2/*.log ``` -Filebeat保持每个文件的状态,并经常刷新注册表文件中的磁盘状态。状态用于记住 harvester 正在读取的最后偏移量,并确保发送所有日志行。 +Filebeat 保持每个文件的状态,并经常刷新注册表文件中的磁盘状态。状态用于记住 harvester 正在读取的最后偏移量,并确保发送所有日志行。 Filebeat 将每个事件的传递状态存储在注册表文件中。所以它能保证事件至少传递一次到配置的输出,没有数据丢失。 ## 资料 -[Beats 官方文档](https://www.elastic.co/guide/en/beats/libbeat/current/index.html) +[Beats 官方文档](https://www.elastic.co/guide/en/beats/libbeat/current/index.html) \ No newline at end of file diff --git a/docs/nosql/elasticsearch/elastic/elastic-beats-ops.md "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/03.Filebeat\350\277\220\347\273\264.md" similarity index 96% rename from docs/nosql/elasticsearch/elastic/elastic-beats-ops.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/03.Filebeat\350\277\220\347\273\264.md" index 19ad8fd9..375f44b9 100644 --- a/docs/nosql/elasticsearch/elastic/elastic-beats-ops.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/03.Filebeat\350\277\220\347\273\264.md" @@ -1,3 +1,18 @@ +--- +title: Filebeat 运维 +date: 2020-06-16 07:10:44 +categories: + - 数据库 + - 搜索引擎数据库 + - Elastic +tags: + - 数据库 + - 搜索引擎数据库 + - Elastic + - Filebeat +permalink: /pages/7c067f/ +--- + # Filebeat 运维 > Beats 平台集合了多种单一用途数据采集器。它们从成百上千或成千上万台机器和系统向 Logstash 或 Elasticsearch 发送数据。 @@ -235,4 +250,4 @@ setup.kibana: - [Beats 官网](https://www.elastic.co/cn/products/beats) - [Beats Github](https://github.com/elastic/beats) -- [Beats 官方文档](https://www.elastic.co/guide/en/beats/libbeat/current/index.html) +- [Beats 官方文档](https://www.elastic.co/guide/en/beats/libbeat/current/index.html) \ No newline at end of file diff --git a/docs/nosql/elasticsearch/elastic/elastic-kibana.md "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/04.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Kibana.md" similarity index 96% rename from docs/nosql/elasticsearch/elastic/elastic-kibana.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/04.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Kibana.md" index e0ec60b7..e803d546 100644 --- a/docs/nosql/elasticsearch/elastic/elastic-kibana.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/04.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Kibana.md" @@ -1,3 +1,18 @@ +--- +title: Elastic 技术栈之 Kibana +date: 2020-06-16 07:10:44 +categories: + - 数据库 + - 搜索引擎数据库 + - Elastic +tags: + - 数据库 + - 搜索引擎数据库 + - Elastic + - Kibana +permalink: /pages/002159/ +--- + # Elastic 技术栈之 Kibana ## Discover @@ -273,7 +288,7 @@ Visualize 工具使您能够以多种方式(如饼图、柱状图、曲线图 为每个范围定义一个存储桶: 1. 单击 `Split Slices`。 -2. 在 `Aggregation` 列表中选择 `Terms`。*注意:这里的 Terms 是 Elk 采集数据时定义好的字段或标签*。 +2. 在 `Aggregation` 列表中选择 `Terms`。_注意:这里的 Terms 是 Elk 采集数据时定义好的字段或标签_。 3. 在 `Field` 列表中选择 `level.keyword`。 4. 点击 ![images/apply-changes-button.png](https://www.elastic.co/guide/en/kibana/6.1/images/apply-changes-button.png) 按钮来更新图表。 @@ -302,4 +317,4 @@ Visualize 工具使您能够以多种方式(如饼图、柱状图、曲线图 1. 点击侧面导航栏中的 Dashboard。 2. 点击添加显示保存的可视化列表。 3. 点击之前保存的 `Visualize`,然后点击列表底部的小向上箭头关闭可视化列表。 -4. 将鼠标悬停在可视化对象上会显示允许您编辑,移动,删除和调整可视化对象大小的容器控件。 +4. 将鼠标悬停在可视化对象上会显示允许您编辑,移动,删除和调整可视化对象大小的容器控件。 \ No newline at end of file diff --git a/docs/nosql/elasticsearch/elastic/elastic-kibana-ops.md "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/05.Kibana\350\277\220\347\273\264.md" similarity index 97% rename from docs/nosql/elasticsearch/elastic/elastic-kibana-ops.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/05.Kibana\350\277\220\347\273\264.md" index a6e46a16..5a81a10e 100644 --- a/docs/nosql/elasticsearch/elastic/elastic-kibana-ops.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/05.Kibana\350\277\220\347\273\264.md" @@ -1,3 +1,18 @@ +--- +title: Kibana 运维 +date: 2020-06-16 07:10:44 +categories: + - 数据库 + - 搜索引擎数据库 + - Elastic +tags: + - 数据库 + - 搜索引擎数据库 + - Elastic + - Kibana +permalink: /pages/fc47af/ +--- + # Kibana 运维 > 通过 Kibana,您可以对自己的 Elasticsearch 进行可视化,还可以在 Elastic Stack 中进行导航,这样您便可以进行各种操作了,从跟踪查询负载,到理解请求如何流经您的整个应用,都能轻松完成。 @@ -290,7 +305,7 @@ Visualize 工具使您能够以多种方式(如饼图、柱状图、曲线图 为每个范围定义一个存储桶: 1. 单击 `Split Slices`。 -2. 在 `Aggregation` 列表中选择 `Terms`。*注意:这里的 Terms 是 Elk 采集数据时定义好的字段或标签*。 +2. 在 `Aggregation` 列表中选择 `Terms`。_注意:这里的 Terms 是 Elk 采集数据时定义好的字段或标签_。 3. 在 `Field` 列表中选择 `level.keyword`。 4. 点击 ![images/apply-changes-button.png](https://www.elastic.co/guide/en/kibana/6.1/images/apply-changes-button.png) 按钮来更新图表。 @@ -343,4 +358,4 @@ Unable to fetch mapping. Do you have indices matching the pattern? - [Kibana 官网](https://www.elastic.co/cn/products/kibana) - [Kibana Github](https://github.com/elastic/kibana) -- [Kibana 官方文档](https://www.elastic.co/guide/en/kibana/current/index.html) +- [Kibana 官方文档](https://www.elastic.co/guide/en/kibana/current/index.html) \ No newline at end of file diff --git a/docs/nosql/elasticsearch/elastic/elastic-logstash.md "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/06.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Logstash.md" similarity index 96% rename from docs/nosql/elasticsearch/elastic/elastic-logstash.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/06.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Logstash.md" index 6ba3b631..708cb24f 100644 --- a/docs/nosql/elasticsearch/elastic/elastic-logstash.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/06.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Logstash.md" @@ -1,4 +1,19 @@ -# Elastic 技术栈之 Logstash 基础 +--- +title: Elastic 技术栈之 Logstash +date: 2020-06-16 07:10:44 +categories: + - 数据库 + - 搜索引擎数据库 + - Elastic +tags: + - 数据库 + - 搜索引擎数据库 + - Elastic + - Logstash +permalink: /pages/55ce99/ +--- + +# Elastic 技术栈之 Logstash > 本文是 Elastic 技术栈(ELK)的 Logstash 应用。 > @@ -146,14 +161,14 @@ input { - Lists ```javascript - path => [ "/var/log/messages", "/var/log/*.log" ] - uris => [ "http://elastic.co", "http://example.net" ] +path => ['/var/log/messages', '/var/log/*.log'] +uris => ['http://elastic.co', 'http://example.net'] ``` - Boolean ```javascript - ssl_enable => true +ssl_enable => true ``` - Bytes @@ -168,7 +183,7 @@ input { - Codec ```javascript - codec => "json" +codec => 'json' ``` - Hash @@ -184,25 +199,25 @@ match => { - Number ```javascript - port => 33 +port => 33 ``` - Password ```javascript - my_password => "password" +my_password => 'password' ``` - URI ```javascript - my_uri => "http://foo:bar@example.net" +my_uri => 'http://foo:bar@example.net' ``` - Path ```javascript - my_path => "/tmp/logstash" +my_path => '/tmp/logstash' ``` - String @@ -324,7 +339,7 @@ output { java 应用配置 - (1)在 Java 应用的 pom.xml 中引入 jar 包: +(1)在 Java 应用的 pom.xml 中引入 jar 包: ```xml @@ -398,11 +413,11 @@ output { java 应用配置 - (1)在 Java 应用的 pom.xml 中引入 jar 包: +(1)在 Java 应用的 pom.xml 中引入 jar 包: - 与 **TCP 应用** 一节中的引入依赖包完全相同。 +与 **TCP 应用** 一节中的引入依赖包完全相同。 - (2)接着,在 logback.xml 中添加 appender +(2)接着,在 logback.xml 中添加 appender ```xml @@ -495,4 +510,4 @@ kill -9 ${PID} ## 推荐阅读 - [Elastic 技术栈](https://github.com/dunwu/JavaStack/blob/master/docs/javatool/elastic/README.md) -- [JavaStack](https://github.com/dunwu/JavaStack) +- [JavaStack](https://github.com/dunwu/JavaStack) \ No newline at end of file diff --git a/docs/nosql/elasticsearch/elastic/elastic-logstash-ops.md "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/07.Logstash\350\277\220\347\273\264.md" similarity index 99% rename from docs/nosql/elasticsearch/elastic/elastic-logstash-ops.md rename to "docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/07.Logstash\350\277\220\347\273\264.md" index 8ef59148..83927491 100644 --- a/docs/nosql/elasticsearch/elastic/elastic-logstash-ops.md +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/07.Logstash\350\277\220\347\273\264.md" @@ -1,3 +1,18 @@ +--- +title: Logstash 运维 +date: 2020-06-16 07:10:44 +categories: + - 数据库 + - 搜索引擎数据库 + - Elastic +tags: + - 数据库 + - 搜索引擎数据库 + - Elastic + - Logstash +permalink: /pages/92df30/ +--- + # Logstash 运维 > [Logstash](https://github.com/elastic/logstash) 是开源的服务器端数据处理管道,能够同时从多个来源采集数据,转换数据,然后将数据发送到您最喜欢的“存储库”中。 @@ -492,4 +507,4 @@ kill -9 ${PID} - [Logstash 官方文档](https://www.elastic.co/guide/en/logstash/current/index.html) - [logstash-logback-encoder](https://github.com/logstash/logstash-logback-encoder) - [ELK Stack 权威指南](https://github.com/chenryn/logstash-best-practice-cn) -- [ELK(Elasticsearch、Logstash、Kibana)安装和配置](https://github.com/judasn/Linux-Tutorial/blob/master/ELK-Install-And-Settings.md) +- [ELK(Elasticsearch、Logstash、Kibana)安装和配置](https://github.com/judasn/Linux-Tutorial/blob/master/ELK-Install-And-Settings.md) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/README.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/README.md" new file mode 100644 index 00000000..9e12932f --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/README.md" @@ -0,0 +1,58 @@ +--- +title: Elastic 技术栈 +date: 2022-04-11 16:52:35 +categories: + - 数据库 + - 搜索引擎数据库 + - Elastic +tags: + - 数据库 + - 搜索引擎数据库 + - Elastic +permalink: /pages/7bf7f7/ +hidden: true +--- + +# Elastic 技术栈 + +> **Elastic 技术栈通常被用来作为日志采集、检索、可视化的解决方案。** +> +> ELK 是 elastic 公司旗下三款产品 [Elasticsearch](https://www.elastic.co/products/elasticsearch) 、[Logstash](https://www.elastic.co/products/logstash) 、[Kibana](https://www.elastic.co/products/kibana) 的首字母组合。 +> +> [Logstash](https://www.elastic.co/products/logstash) 传输和处理你的日志、事务或其他数据。 +> +> [Kibana](https://www.elastic.co/products/kibana) 将 Elasticsearch 的数据分析并渲染为可视化的报表。 +> +> Elastic 技术栈,在 ELK 的基础上扩展了一些新的产品,如:[Beats](https://www.elastic.co/products/beats) 、[X-Pack](https://www.elastic.co/products/x-pack) 。 + +## 📖 内容 + +- [Elastic 快速入门](01.Elastic快速入门.md) +- [Elastic 技术栈之 Filebeat](02.Elastic技术栈之Filebeat.md) +- [Filebeat 运维](03.Filebeat运维.md) +- [Elastic 技术栈之 Kibana](04.Elastic技术栈之Kibana.md) +- [Kibana 运维](05.Kibana运维.md) +- [Elastic 技术栈之 Logstash](06.Elastic技术栈之Logstash.md) +- [Logstash 运维](07.Logstash运维.md) + +## 📚 资料 + +- **官方** + - [Logstash 官网](https://www.elastic.co/cn/products/logstash) + - [Logstash Github](https://github.com/elastic/logstash) + - [Logstash 官方文档](https://www.elastic.co/guide/en/logstash/current/index.html) + - [Kibana 官网](https://www.elastic.co/cn/products/kibana) + - [Kibana Github](https://github.com/elastic/kibana) + - [Kibana 官方文档](https://www.elastic.co/guide/en/kibana/current/index.html) + - [Beats 官网](https://www.elastic.co/cn/products/beats) + - [Beats Github](https://github.com/elastic/beats) + - [Beats 官方文档](https://www.elastic.co/guide/en/beats/libbeat/current/index.html) +- **第三方工具** + - [logstash-logback-encoder](https://github.com/logstash/logstash-logback-encoder) +- **文章** + - [Elasticsearch+Logstash+Kibana 教程](https://www.cnblogs.com/xing901022/p/4704319.html) + - [ELK(Elasticsearch、Logstash、Kibana)安装和配置](https://github.com/judasn/Linux-Tutorial/blob/master/ELK-Install-And-Settings.md) + +## 🚪 传送 + +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/README.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/README.md" new file mode 100644 index 00000000..ff1852cf --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/README.md" @@ -0,0 +1,435 @@ +--- +title: 数据库 +date: 2022-02-22 21:01:01 +categories: + - 数据库 +tags: + - 数据库 +permalink: /pages/012488/ +hidden: true +--- + +

+ + logo + +

+ +

+ + + star + + + + fork + + + + build + + + + code style + + +

+ +

DB-TUTORIAL

+ +> 💾 **db-tutorial** 是一个数据库教程。 +> +> - 🔁 项目同步维护:[Github](https://github.com/dunwu/db-tutorial/) | [Gitee](https://gitee.com/turnon/db-tutorial/) +> - 📖 电子书阅读:[Github Pages](https://dunwu.github.io/db-tutorial/) | [Gitee Pages](https://turnon.gitee.io/db-tutorial/) + +## 数据库综合 + +### 分布式存储原理 + +#### 分布式理论 + +- [分布式一致性](https://dunwu.github.io/blog/pages/dac0e2/) +- [深入剖析共识性算法 Paxos](https://dunwu.github.io/blog/pages/874539/) +- [深入剖析共识性算法 Raft](https://dunwu.github.io/blog/pages/e40812/) +- [分布式算法 Gossip](https://dunwu.github.io/blog/pages/d15993/) + +#### 分布式关键技术 + +##### 流量调度 + +- [流量控制](https://dunwu.github.io/blog/pages/282676/) +- [负载均衡](https://dunwu.github.io/blog/pages/98a1c1/) +- [服务路由](https://dunwu.github.io/blog/pages/d04ece/) +- [分布式会话基本原理](https://dunwu.github.io/blog/pages/3e66c2/) + +##### 数据调度 + +- [缓存基本原理](https://dunwu.github.io/blog/pages/471208/) +- [读写分离基本原理](https://dunwu.github.io/blog/pages/7da6ca/) +- [分库分表基本原理](https://dunwu.github.io/blog/pages/103382/) +- [分布式 ID 基本原理](https://dunwu.github.io/blog/pages/0b2e59/) +- [分布式事务基本原理](https://dunwu.github.io/blog/pages/910bad/) +- [分布式锁基本原理](https://dunwu.github.io/blog/pages/69360c/) + +### 其他 + +- [Nosql 技术选型](01.数据库综合/01.Nosql技术选型.md) +- [数据结构与数据库索引](01.数据库综合/02.数据结构与数据库索引.md) + +## 数据库中间件 + +- [ShardingSphere 简介](02.数据库中间件/01.Shardingsphere/01.ShardingSphere简介.md) +- [ShardingSphere Jdbc](02.数据库中间件/01.Shardingsphere/02.ShardingSphereJdbc.md) +- [版本管理中间件 Flyway](02.数据库中间件/02.Flyway.md) + +## 关系型数据库 + +> [关系型数据库](03.关系型数据库) 整理主流关系型数据库知识点。 + +### 关系型数据库综合 + +- [关系型数据库面试总结](03.关系型数据库/01.综合/01.关系型数据库面试.md) 💯 +- [SQL 语法基础特性](03.关系型数据库/01.综合/02.SQL语法基础特性.md) +- [SQL 语法高级特性](03.关系型数据库/01.综合/03.SQL语法高级特性.md) +- [扩展 SQL](03.关系型数据库/01.综合/03.扩展SQL.md) +- [SQL Cheat Sheet](03.关系型数据库/01.综合/99.SqlCheatSheet.md) + +### Mysql + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716103611.png) + +- [Mysql 应用指南](03.关系型数据库/02.Mysql/01.Mysql应用指南.md) ⚡ +- [Mysql 工作流](03.关系型数据库/02.Mysql/02.MySQL工作流.md) - 关键词:`连接`、`缓存`、`语法分析`、`优化`、`执行引擎`、`redo log`、`bin log`、`两阶段提交` +- [Mysql 事务](03.关系型数据库/02.Mysql/03.Mysql事务.md) - 关键词:`ACID`、`AUTOCOMMIT`、`事务隔离级别`、`死锁`、`分布式事务` +- [Mysql 锁](03.关系型数据库/02.Mysql/04.Mysql锁.md) - 关键词:`乐观锁`、`表级锁`、`行级锁`、`意向锁`、`MVCC`、`Next-key 锁` +- [Mysql 索引](03.关系型数据库/02.Mysql/05.Mysql索引.md) - 关键词:`Hash`、`B 树`、`聚簇索引`、`回表` +- [Mysql 性能优化](03.关系型数据库/02.Mysql/06.Mysql性能优化.md) +- [Mysql 运维](03.关系型数据库/02.Mysql/20.Mysql运维.md) 🔨 +- [Mysql 配置](03.关系型数据库/02.Mysql/21.Mysql配置.md) 🔨 +- [Mysql 问题](03.关系型数据库/02.Mysql/99.Mysql常见问题.md) + +### 其他 + +- [PostgreSQL 应用指南](03.关系型数据库/99.其他/01.PostgreSQL.md) +- [H2 应用指南](03.关系型数据库/99.其他/02.H2.md) +- [SqLite 应用指南](03.关系型数据库/99.其他/03.Sqlite.md) + +## 文档数据库 + +### MongoDB + +> MongoDB 是一个基于文档的分布式数据库,由 C++ 语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。 +> +> MongoDB 是一个介于关系型数据库和非关系型数据库之间的产品。它是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似 json 的 bson 格式,因此可以存储比较复杂的数据类型。 +> +> MongoDB 最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。 + +- [MongoDB 应用指南](04.文档数据库/01.MongoDB/01.MongoDB应用指南.md) +- [MongoDB 的 CRUD 操作](04.文档数据库/01.MongoDB/02.MongoDB的CRUD操作.md) +- [MongoDB 聚合操作](04.文档数据库/01.MongoDB/03.MongoDB的聚合操作.md) +- [MongoDB 事务](04.文档数据库/01.MongoDB/04.MongoDB事务.md) +- [MongoDB 建模](04.文档数据库/01.MongoDB/05.MongoDB建模.md) +- [MongoDB 建模示例](04.文档数据库/01.MongoDB/06.MongoDB建模示例.md) +- [MongoDB 索引](04.文档数据库/01.MongoDB/07.MongoDB索引.md) +- [MongoDB 复制](04.文档数据库/01.MongoDB/08.MongoDB复制.md) +- [MongoDB 分片](04.文档数据库/01.MongoDB/09.MongoDB分片.md) +- [MongoDB 运维](04.文档数据库/01.MongoDB/20.MongoDB运维.md) + +## KV 数据库 + +### Redis + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200713105627.png) + +- [Redis 面试总结](05.KV数据库/01.Redis/01.Redis面试总结.md) 💯 +- [Redis 应用指南](05.KV数据库/01.Redis/02.Redis应用指南.md) ⚡ - 关键词:`内存淘汰`、`事件`、`事务`、`管道`、`发布与订阅` +- [Redis 数据类型和应用](05.KV数据库/01.Redis/03.Redis数据类型和应用.md) - 关键词:`STRING`、`HASH`、`LIST`、`SET`、`ZSET`、`BitMap`、`HyperLogLog`、`Geo` +- [Redis 持久化](05.KV数据库/01.Redis/04.Redis持久化.md) - 关键词:`RDB`、`AOF`、`SAVE`、`BGSAVE`、`appendfsync` +- [Redis 复制](05.KV数据库/01.Redis/05.Redis复制.md) - 关键词:`SLAVEOF`、`SYNC`、`PSYNC`、`REPLCONF ACK` +- [Redis 哨兵](05.KV数据库/01.Redis/06.Redis哨兵.md) - 关键词:`Sentinel`、`PING`、`INFO`、`Raft` +- [Redis 集群](05.KV数据库/01.Redis/07.Redis集群.md) - 关键词:`CLUSTER MEET`、`Hash slot`、`MOVED`、`ASK`、`SLAVEOF no one`、`redis-trib` +- [Redis 实战](05.KV数据库/01.Redis/08.Redis实战.md) - 关键词:`缓存`、`分布式锁`、`布隆过滤器` +- [Redis 运维](05.KV数据库/01.Redis/20.Redis运维.md) 🔨 - 关键词:`安装`、`命令`、`集群`、`客户端` + +## 列式数据库 + +### HBase + +- [HBase 快速入门](06.列式数据库/01.HBase/01.HBase快速入门.md) +- [HBase 数据模型](06.列式数据库/01.HBase/02.HBase数据模型.md) +- [HBase Schema 设计](06.列式数据库/01.HBase/03.HBaseSchema设计.md) +- [HBase 架构](06.列式数据库/01.HBase/04.HBase架构.md) +- [HBase Java API 基础特性](06.列式数据库/01.HBase/10.HBaseJavaApi基础特性.md) +- [HBase Java API 高级特性之过滤器](06.列式数据库/01.HBase/11.HBaseJavaApi高级特性之过滤器.md) +- [HBase Java API 高级特性之协处理器](06.列式数据库/01.HBase/12.HBaseJavaApi高级特性之协处理器.md) +- [HBase Java API 其他高级特性](06.列式数据库/01.HBase/13.HBaseJavaApi其他高级特性.md) +- [HBase 运维](06.列式数据库/01.HBase/21.HBase运维.md) +- [HBase 命令](06.列式数据库/01.HBase/22.HBase命令.md) + +## 搜索引擎数据库 + +### Elasticsearch + +> Elasticsearch 是一个基于 Lucene 的搜索和数据分析工具,它提供了一个分布式服务。Elasticsearch 是遵从 Apache 开源条款的一款开源产品,是当前主流的企业级搜索引擎。 + +- [Elasticsearch 面试总结](07.搜索引擎数据库/01.Elasticsearch/01.Elasticsearch面试总结.md) 💯 +- [Elasticsearch 快速入门](07.搜索引擎数据库/01.Elasticsearch/02.Elasticsearch快速入门.md) +- [Elasticsearch 简介](07.搜索引擎数据库/01.Elasticsearch/03.Elasticsearch简介.md) +- [Elasticsearch 索引](07.搜索引擎数据库/01.Elasticsearch/04.Elasticsearch索引.md) +- [Elasticsearch 查询](07.搜索引擎数据库/01.Elasticsearch/05.Elasticsearch查询.md) +- [Elasticsearch 高亮](07.搜索引擎数据库/01.Elasticsearch/06.Elasticsearch高亮.md) +- [Elasticsearch 排序](07.搜索引擎数据库/01.Elasticsearch/07.Elasticsearch排序.md) +- [Elasticsearch 聚合](07.搜索引擎数据库/01.Elasticsearch/08.Elasticsearch聚合.md) +- [Elasticsearch 分析器](07.搜索引擎数据库/01.Elasticsearch/09.Elasticsearch分析器.md) +- [Elasticsearch 性能优化](07.搜索引擎数据库/01.Elasticsearch/10.Elasticsearch性能优化.md) +- [Elasticsearch Rest API](07.搜索引擎数据库/01.Elasticsearch/11.ElasticsearchRestApi.md) +- [ElasticSearch Java API 之 High Level REST Client](07.搜索引擎数据库/01.Elasticsearch/12.ElasticsearchHighLevelRestJavaApi.md) +- [Elasticsearch 集群和分片](07.搜索引擎数据库/01.Elasticsearch/13.Elasticsearch集群和分片.md) +- [Elasticsearch 运维](07.搜索引擎数据库/01.Elasticsearch/20.Elasticsearch运维.md) + +### Elastic + +- [Elastic 快速入门](07.搜索引擎数据库/02.Elastic/01.Elastic快速入门.md) +- [Elastic 技术栈之 Filebeat](07.搜索引擎数据库/02.Elastic/02.Elastic技术栈之Filebeat.md) +- [Filebeat 运维](07.搜索引擎数据库/02.Elastic/03.Filebeat运维.md) +- [Elastic 技术栈之 Kibana](07.搜索引擎数据库/02.Elastic/04.Elastic技术栈之Kibana.md) +- [Kibana 运维](07.搜索引擎数据库/02.Elastic/05.Kibana运维.md) +- [Elastic 技术栈之 Logstash](07.搜索引擎数据库/02.Elastic/06.Elastic技术栈之Logstash.md) +- [Logstash 运维](07.搜索引擎数据库/02.Elastic/07.Logstash运维.md) + +## 资料 📚 + +### 数据库综合资料 + +- [DB-Engines](https://db-engines.com/en/ranking) - 数据库流行度排名 +- **书籍** + - [《数据密集型应用系统设计》](https://book.douban.com/subject/30329536/) - 这可能是目前最好的分布式存储书籍,强力推荐【进阶】 +- **教程** + - [CMU 15445 数据库基础课程](https://15445.courses.cs.cmu.edu/fall2019/schedule.html) + - [CMU 15721 数据库高级课程](https://15721.courses.cs.cmu.edu/spring2020/schedule.html) + - [检索技术核心 20 讲](https://time.geekbang.org/column/intro/100048401) - 极客教程【进阶】 + - [后端存储实战课](https://time.geekbang.org/column/intro/100046801) - 极客教程【入门】:讲解存储在电商领域的种种应用和一些基本特性 +- **论文** + - [Efficiency in the Columbia Database Query Optimizer](https://15721.courses.cs.cmu.edu/spring2018/papers/15-optimizer1/xu-columbia-thesis1998.pdf) + - [How Good Are Query Optimizers, Really?](http://www.vldb.org/pvldb/vol9/p204-leis.pdf) + - [Architecture of a Database System](https://dsf.berkeley.edu/papers/fntdb07-architecture.pdf) + - [Data Structures for Databases](https://www.cise.ufl.edu/~mschneid/Research/papers/HS05BoCh.pdf) +- **文章** + - [Data Structures and Algorithms for Big Databases](https://people.csail.mit.edu/bradley/BenderKuszmaul-tutorial-xldb12.pdf) + +### 关系型数据库资料 + +- **综合资料** + - [《数据库的索引设计与优化》](https://book.douban.com/subject/26419771/) + - [《SQL 必知必会》](https://book.douban.com/subject/35167240/) - SQL 的基本概念和语法【入门】 +- **Oracle 资料** + - [《Oracle Database 9i/10g/11g 编程艺术》](https://book.douban.com/subject/5402711/) + +#### Mysql 资料 + +- **官方** + - [Mysql 官网](https://www.mysql.com/) + - [Mysql 官方文档](https://dev.mysql.com/doc/) + - **官方 PPT** + - [How to Analyze and Tune MySQL Queries for Better Performance](https://www.mysql.com/cn/why-mysql/presentations/tune-mysql-queries-performance/) + - [MySQL Performance Tuning 101](https://www.mysql.com/cn/why-mysql/presentations/mysql-performance-tuning101/) + - [MySQL Performance Schema & Sys Schema](https://www.mysql.com/cn/why-mysql/presentations/mysql-performance-sys-schema/) + - [MySQL Performance: Demystified Tuning & Best Practices](https://www.mysql.com/cn/why-mysql/presentations/mysql-performance-tuning-best-practices/) + - [MySQL Security Best Practices](https://www.mysql.com/cn/why-mysql/presentations/mysql-security-best-practices/) + - [MySQL Cluster Deployment Best Practices](https://www.mysql.com/cn/why-mysql/presentations/mysql-cluster-deployment-best-practices/) + - [MySQL High Availability with InnoDB Cluster](https://www.mysql.com/cn/why-mysql/presentations/mysql-high-availability-innodb-cluster/) +- **书籍** + - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) - 经典,适合 DBA 或作为开发者的参考手册【进阶】 + - [《MySQL 技术内幕:InnoDB 存储引擎》](https://book.douban.com/subject/24708143/) + - [《MySQL 必知必会》](https://book.douban.com/subject/3354490/) - Mysql 的基本概念和语法【入门】 +- **教程** + - [runoob.com MySQL 教程](http://www.runoob.com/mysql/mysql-tutorial.html) - 入门级 SQL 教程 + - [mysql-tutorial](https://github.com/jaywcjlove/mysql-tutorial) +- **文章** + - [MySQL 索引背后的数据结构及算法原理](http://blog.codinglabs.org/articles/theory-of-mysql-index.html) + - [Some study on database storage internals](https://medium.com/@kousiknath/data-structures-database-storage-internals-1f5ed3619d43) + - [Sharding Pinterest: How we scaled our MySQL fleet](https://medium.com/@Pinterest_Engineering/sharding-pinterest-how-we-scaled-our-mysql-fleet-3f341e96ca6f) + - [Guide to MySQL High Availability](https://www.mysql.com/cn/why-mysql/white-papers/mysql-guide-to-high-availability-solutions/) + - [Choosing MySQL High Availability Solutions](https://dzone.com/articles/choosing-mysql-high-availability-solutions) + - [High availability with MariaDB TX: The definitive guide](https://mariadb.com/sites/default/files/content/Whitepaper_High_availability_with_MariaDB-TX.pdf) + - Mysql 相关经验 + - [Booking.com: Evolution of MySQL System Design](https://www.percona.com/live/mysql-conference-2015/sessions/bookingcom-evolution-mysql-system-design) ,Booking.com 的 MySQL 数据库使用的演化,其中有很多不错的经验分享,我相信也是很多公司会遇到的的问题。 + - [Tracking the Money - Scaling Financial Reporting at Airbnb](https://medium.com/airbnb-engineering/tracking-the-money-scaling-financial-reporting-at-airbnb-6d742b80f040) ,Airbnb 的数据库扩展的经验分享。 + - [Why Uber Engineering Switched from Postgres to MySQL](https://eng.uber.com/mysql-migration/) ,无意比较两个数据库谁好谁不好,推荐这篇 Uber 的长文,主要是想让你从中学习到一些经验和技术细节,这是一篇很不错的文章。 + - Mysql 集群复制 + - [Monitoring Delayed Replication, With A Focus On MySQL](https://engineering.imvu.com/2013/01/09/monitoring-delayed-replication-with-a-focus-on-mysql/) + - [Mitigating replication lag and reducing read load with freno](https://githubengineering.com/mitigating-replication-lag-and-reducing-read-load-with-freno/) + - [Better Parallel Replication for MySQL](https://medium.com/booking-com-infrastructure/better-parallel-replication-for-mysql-14e2d7857813) + - [Evaluating MySQL Parallel Replication Part 2: Slave Group Commit](https://medium.com/booking-com-infrastructure/evaluating-mysql-parallel-replication-part-2-slave-group-commit-459026a141d2) + - [Evaluating MySQL Parallel Replication Part 3: Benchmarks in Production](https://medium.com/booking-com-infrastructure/evaluating-mysql-parallel-replication-part-3-benchmarks-in-production-db5811058d74) + - [Evaluating MySQL Parallel Replication Part 4: More Benchmarks in Production](https://medium.com/booking-com-infrastructure/evaluating-mysql-parallel-replication-part-4-more-benchmarks-in-production-49ee255043ab) + - [Evaluating MySQL Parallel Replication Part 4, Annex: Under the Hood](https://medium.com/booking-com-infrastructure/evaluating-mysql-parallel-replication-part-4-annex-under-the-hood-eb456cf8b2fb) + - Mysql 数据分区 + - [StackOverflow: MySQL sharding approaches?](https://stackoverflow.com/questions/5541421/mysql-sharding-approaches) + - [Why you don’t want to shard](https://www.percona.com/blog/2009/08/06/why-you-dont-want-to-shard/) + - [How to Scale Big Data Applications](https://www.percona.com/sites/default/files/presentations/How to Scale Big Data Applications.pdf) + - [MySQL Sharding with ProxySQL](https://www.percona.com/blog/2016/08/30/mysql-sharding-with-proxysql/) + - 各公司的 Mysql 数据分区经验分享 + - [MailChimp: Using Shards to Accommodate Millions of Users](https://devs.mailchimp.com/blog/using-shards-to-accommodate-millions-of-users/) + - [Uber: Code Migration in Production: Rewriting the Sharding Layer of Uber’s Schemaless Datastore](https://eng.uber.com/schemaless-rewrite/) + - [Sharding & IDs at Instagram](https://instagram-engineering.com/sharding-ids-at-instagram-1cf5a71e5a5c) + - [Airbnb: How We Partitioned Airbnb’s Main Database in Two Weeks](https://medium.com/airbnb-engineering/how-we-partitioned-airbnb-s-main-database-in-two-weeks-55f7e006ff21) +- **更多资源** + - [awesome-mysql](https://github.com/jobbole/awesome-mysql-cn) - MySQL 的资源列表 + +### Nosql 数据库综合 + +- Martin Fowler 在 YouTube 上分享的 NoSQL 介绍 [Introduction To NoSQL](https://youtu.be/qI_g07C_Q5I), 以及他参与编写的 [NoSQL Distilled - NoSQL 精粹](https://book.douban.com/subject/25662138/),这本书才 100 多页,是本难得的关于 NoSQL 的书,很不错,非常易读。 +- [NoSQL Databases: a Survey and Decision Guidance](https://medium.com/baqend-blog/nosql-databases-a-survey-and-decision-guidance-ea7823a822d#.nhzop4d23),这篇文章可以带你自上而下地从 CAP 原理到开始了解 NoSQL 的种种技术,是一篇非常不错的文章。 +- [Distribution, Data, Deployment: Software Architecture Convergence in Big Data Systems](https://resources.sei.cmu.edu/asset_files/WhitePaper/2014_019_001_90915.pdf),这是卡内基·梅隆大学的一篇讲分布式大数据系统的论文。其中主要讨论了在大数据时代下的软件工程中的一些关键点,也说到了 NoSQL 数据库。 +- [No Relation: The Mixed Blessings of Non-Relational Databases](http://ianvarley.com/UT/MR/Varley_MastersReport_Full_2009-08-07.pdf),这篇论文虽然有点年代久远。但这篇论文是 HBase 的基础,你花上一点时间来读读,就可以了解到,对各种非关系型数据存储优缺点的一个很好的比较。 +- [NoSQL Data Modeling Techniques](https://highlyscalable.wordpress.com/2012/03/01/nosql-data-modeling-techniques/) ,NoSQL 建模技术。这篇文章我曾经翻译在了 CoolShell 上,标题为 [NoSQL 数据建模技术](https://coolshell.cn/articles/7270.htm),供你参考。 + - [MongoDB - Data Modeling Introduction](https://docs.mongodb.com/manual/core/data-modeling-introduction/) ,虽然这是 MongoDB 的数据建模介绍,但是其很多观点可以用于其它的 NoSQL 数据库。 + - [Firebase - Structure Your Database](https://firebase.google.com/docs/database/android/structure-data) ,Google 的 Firebase 数据库使用 JSON 建模的一些最佳实践。 +- 因为 CAP 原理,所以当你需要选择一个 NoSQL 数据库的时候,你应该看看这篇文档 [Visual Guide to NoSQL Systems](http://blog.nahurst.com/visual-guide-to-nosql-systems)。 + +选 SQL 还是 NoSQL,这里有两篇文章,值得你看看。 + +- [SQL vs. NoSQL Databases: What’s the Difference?](https://www.upwork.com/hiring/data/sql-vs-nosql-databases-whats-the-difference/) +- [Salesforce: SQL or NoSQL](https://engineering.salesforce.com/sql-or-nosql-9eaf1d92545b) + +### 列式数据库资料 + +#### Cassandra 资料 + +- 沃尔玛实验室有两篇文章值得一读。 + - [Avoid Pitfalls in Scaling Cassandra Cluster at Walmart](https://medium.com/walmartlabs/avoid-pitfalls-in-scaling-your-cassandra-cluster-lessons-and-remedies-a71ca01f8c04) + - [Storing Images in Cassandra at Walmart](https://medium.com/walmartlabs/building-object-store-storing-images-in-cassandra-walmart-scale-a6b9c02af593) +- [Yelp: How We Scaled Our Ad Analytics with Apache Cassandra](https://engineeringblog.yelp.com/2016/08/how-we-scaled-our-ad-analytics-with-cassandra.html) ,Yelp 的这篇博客也有一些相关的经验和教训。 +- [Discord: How Discord Stores Billions of Messages](https://blog.discordapp.com/how-discord-stores-billions-of-messages-7fa6ec7ee4c7) ,Discord 公司分享的一个如何存储十亿级消息的技术文章。 +- [Cassandra at Instagram](https://www.slideshare.net/DataStax/cassandra-at-instagram-2016) ,Instagram 的一个 PPT,其中介绍了 Instagram 中是怎么使用 Cassandra 的。 +- [Netflix: Benchmarking Cassandra Scalability on AWS - Over a million writes per second](https://medium.com/netflix-techblog/benchmarking-cassandra-scalability-on-aws-over-a-million-writes-per-second-39f45f066c9e) ,Netflix 公司在 AWS 上给 Cassandra 做的一个 Benchmark。 + +#### HBase 资料 + +- [Imgur Notification: From MySQL to HBASE](https://medium.com/imgur-engineering/imgur-notifications-from-mysql-to-hbase-9dba6fc44183) +- [Pinterest: Improving HBase Backup Efficiency](https://medium.com/@Pinterest_Engineering/improving-hbase-backup-efficiency-at-pinterest-86159da4b954) +- [IBM : Tuning HBase performance](https://www.ibm.com/support/knowledgecenter/en/SSPT3X_2.1.2/com.ibm.swg.im.infosphere.biginsights.analyze.doc/doc/bigsql_TuneHbase.html) +- [HBase File Locality in HDFS](http://www.larsgeorge.com/2010/05/hbase-file-locality-in-hdfs.html) +- [Apache Hadoop Goes Realtime at Facebook](http://borthakur.com/ftp/RealtimeHadoopSigmod2011.pdf) +- [Storage Infrastructure Behind Facebook Messages: Using HBase at Scale](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.294.8459&rep=rep1&type=pdf) +- [GitHub: Awesome HBase](https://github.com/rayokota/awesome-hbase) + +针对于 HBase 有两本书你可以考虑一下。 + +- 首先,先推荐两本书,一本是偏实践的《[HBase 实战](https://book.douban.com/subject/25706541/)》,另一本是偏大而全的手册型的《[HBase 权威指南](https://book.douban.com/subject/10748460/)》。 +- 当然,你也可以看看官方的 [The Apache HBase™ Reference Guide](http://hbase.apache.org/0.94/book/book.html) +- 另外两个列数据库: + - [ClickHouse - Open Source Distributed Column Database at Yandex](https://clickhouse.yandex/) + - [Scaling Redshift without Scaling Costs at GIPHY](https://engineering.giphy.com/scaling-redshift-without-scaling-costs/) + +### KV 数据库资料 + +#### Redis 资料 + +- **官网** + - [Redis 官网](https://redis.io/) + - [Redis github](https://github.com/antirez/redis) + - [Redis 官方文档中文版](http://redis.cn/) + - [Redis 命令参考](http://redisdoc.com/) +- **书籍** + - [《Redis 实战》](https://item.jd.com/11791607.html) + - [《Redis 设计与实现》](https://item.jd.com/11486101.html) +- **源码** + - [《Redis 实战》配套 Python 源码](https://github.com/josiahcarlson/redis-in-action) +- **资源汇总** + - [awesome-redis](https://github.com/JamzyWang/awesome-redis) +- **Redis Client** + - [spring-data-redis 官方文档](https://docs.spring.io/spring-data/redis/docs/1.8.13.RELEASE/reference/html/) + - [redisson 官方文档(中文,略有滞后)](https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95) + - [redisson 官方文档(英文)](https://github.com/redisson/redisson/wiki/Table-of-Content) + - [CRUG | Redisson PRO vs. Jedis: Which Is Faster? 翻译](https://www.jianshu.com/p/82f0d5abb002) + - [redis 分布锁 Redisson 性能测试](https://blog.csdn.net/everlasting_188/article/details/51073505) +- **文章** + - [Learn Redis the hard way (in production) at Trivago](http://tech.trivago.com/2017/01/25/learn-redis-the-hard-way-in-production/) + - [Twitter: How Twitter Uses Redis To Scale - 105TB RAM, 39MM QPS, 10,000+ Instances](http://highscalability.com/blog/2014/9/8/how-twitter-uses-redis-to-scale-105tb-ram-39mm-qps-10000-ins.html) + - [Slack: Scaling Slack’s Job Queue - Robustly Handling Billions of Tasks in Milliseconds Using Kafka and Redis](https://slack.engineering/scaling-slacks-job-queue-687222e9d100) + - [GitHub: Moving persistent data out of Redis at GitHub](https://githubengineering.com/moving-persistent-data-out-of-redis/) + - [Instagram: Storing Hundreds of Millions of Simple Key-Value Pairs in Redis](https://engineering.instagram.com/storing-hundreds-of-millions-of-simple-key-value-pairs-in-redis-1091ae80f74c) + - [Redis in Chat Architecture of Twitch (from 27:22)](https://www.infoq.com/presentations/twitch-pokemon) + - [Deliveroo: Optimizing Session Key Storage in Redis](https://deliveroo.engineering/2016/10/07/optimising-session-key-storage.html) + - [Deliveroo: Optimizing Redis Storage](https://deliveroo.engineering/2017/01/19/optimising-membership-queries.html) + - [GitHub: Awesome Redis](https://github.com/JamzyWang/awesome-redis) + +### 文档数据库资料 + +- [Couchbase Ecosystem at LinkedIn](https://engineering.linkedin.com/blog/2017/12/couchbase-ecosystem-at-linkedin) +- [SimpleDB at Zendesk](https://medium.com/zendesk-engineering/resurrecting-amazon-simpledb-9404034ec506) +- [Data Points - What the Heck Are Document Databases?](https://msdn.microsoft.com/en-us/magazine/hh547103.aspx) + +#### MongoDB 资料 + +- **官方** + - [MongoDB 官网](https://www.mongodb.com/) + - [MongoDB Github](https://github.com/mongodb/mongo) + - [MongoDB 官方免费教程](https://university.mongodb.com/) +- **教程** + - [MongoDB 教程](https://www.runoob.com/mongodb/mongodb-tutorial.html) + - [MongoDB 高手课](https://time.geekbang.org/course/intro/100040001) +- **数据** + - [mongodb-json-files](https://github.com/ozlerhakan/mongodb-json-files) +- **文章** + - [Introduction to MongoDB](https://www.slideshare.net/mdirolf/introduction-to-mongodb) + - [eBay: Building Mission-Critical Multi-Data Center Applications with MongoDB](https://www.mongodb.com/blog/post/ebay-building-mission-critical-multi-data-center-applications-with-mongodb) + - [The AWS and MongoDB Infrastructure of Parse: Lessons Learned](https://medium.baqend.com/parse-is-gone-a-few-secrets-about-their-infrastructure-91b3ab2fcf71) + - [Migrating Mountains of Mongo Data](https://medium.com/build-addepar/migrating-mountains-of-mongo-data-63e530539952) +- **更多资源** + - [Github: Awesome MongoDB](https://github.com/ramnes/awesome-mongodb) + +### 搜索引擎数据库资料 + +#### ElasticSearch + +- **官方** + - [Elasticsearch 官网](https://www.elastic.co/cn/products/elasticsearch) + - [Elasticsearch Github](https://github.com/elastic/elasticsearch) + - [Elasticsearch 官方文档](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) + - [Elasticsearch: The Definitive Guide](https://www.elastic.co/guide/en/elasticsearch/guide/master/index.html) - ElasticSearch 官方学习资料 +- **书籍** + - [《Elasticsearch 实战》](https://book.douban.com/subject/30380439/) +- **教程** + - [ELK Stack 权威指南](https://github.com/chenryn/logstash-best-practice-cn) + - [Elasticsearch 教程](https://www.knowledgedict.com/tutorial/elasticsearch-intro.html) +- **文章** + - [Elasticsearch+Logstash+Kibana 教程](https://www.cnblogs.com/xing901022/p/4704319.html) + - [ELK(Elasticsearch、Logstash、Kibana)安装和配置](https://github.com/judasn/Linux-Tutorial/blob/master/ELK-Install-And-Settings.md) + - **性能调优相关**的工程实践 + - [Elasticsearch Performance Tuning Practice at eBay](https://www.ebayinc.com/stories/blogs/tech/elasticsearch-performance-tuning-practice-at-ebay/) + - [Elasticsearch at Kickstarter](https://kickstarter.engineering/elasticsearch-at-kickstarter-db3c487887fc) + - [9 tips on ElasticSearch configuration for high performance](https://www.loggly.com/blog/nine-tips-configuring-elasticsearch-for-high-performance/) + - [Elasticsearch In Production - Deployment Best Practices](https://medium.com/@abhidrona/elasticsearch-deployment-best-practices-d6c1323b25d7) +- **更多资源** + - [GitHub: Awesome ElasticSearch](https://github.com/dzharii/awesome-elasticsearch) + +### 图数据库 + +- 首先是 IBM Devloperworks 上的两个简介性的 PPT。 + - [Intro to graph databases, Part 1, Graph databases and the CRUD operations](https://www.ibm.com/developerworks/library/cl-graph-database-1/cl-graph-database-1-pdf.pdf) + - [Intro to graph databases, Part 2, Building a recommendation engine with a graph database](https://www.ibm.com/developerworks/library/cl-graph-database-2/cl-graph-database-2-pdf.pdf) +- 然后是一本免费的电子书《[Graph Database](http://graphdatabases.com)》。 +- 接下来是一些图数据库的介绍文章。 + - [Handling Billions of Edges in a Graph Database](https://www.infoq.com/presentations/graph-database-scalability) + - [Neo4j case studies with Walmart, eBay, AirBnB, NASA, etc](https://neo4j.com/customers/) + - [FlockDB: Distributed Graph Database for Storing Adjacency Lists at Twitter](https://blog.twitter.com/engineering/en_us/a/2010/introducing-flockdb.html) + - [JanusGraph: Scalable Graph Database backed by Google, IBM and Hortonworks](https://architecht.io/google-ibm-back-new-open-source-graph-database-project-janusgraph-1d74fb78db6b) + - [Amazon Neptune](https://aws.amazon.com/neptune/) + +### 时序数据库 + +- [What is Time-Series Data & Why We Need a Time-Series Database](https://blog.timescale.com/what-the-heck-is-time-series-data-and-why-do-i-need-a-time-series-database-dcf3b1b18563) +- [Time Series Data: Why and How to Use a Relational Database instead of NoSQL](https://blog.timescale.com/time-series-data-why-and-how-to-use-a-relational-database-instead-of-nosql-d0cd6975e87c) +- [Beringei: High-performance Time Series Storage Engine @Facebook](https://code.facebook.com/posts/952820474848503/beringei-a-high-performance-time-series-storage-engine/) +- [Introducing Atlas: Netflix’s Primary Telemetry Platform @Netflix](https://medium.com/netflix-techblog/introducing-atlas-netflixs-primary-telemetry-platform-bd31f4d8ed9a) +- [Building a Scalable Time Series Database on PostgreSQL](https://blog.timescale.com/when-boring-is-awesome-building-a-scalable-time-series-database-on-postgresql-2900ea453ee2) +- [Scaling Time Series Data Storage - Part I @Netflix](https://medium.com/netflix-techblog/scaling-time-series-data-storage-part-i-ec2b6d44ba39) +- [Design of a Cost Efficient Time Series Store for Big Data](https://medium.com/@leventov/design-of-a-cost-efficient-time-series-store-for-big-data-88c5dc41af8e) +- [GitHub: Awesome Time-Series Database](https://github.com/xephonhq/awesome-time-series-database) + +## 传送 🚪 + +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ diff --git a/docs/@pages/archivesPage.md b/docs/@pages/archivesPage.md new file mode 100644 index 00000000..4e2d4eda --- /dev/null +++ b/docs/@pages/archivesPage.md @@ -0,0 +1,6 @@ +--- +archivesPage: true +title: 归档 +permalink: /archives/ +article: false +--- diff --git a/docs/@pages/categoriesPage.md b/docs/@pages/categoriesPage.md new file mode 100644 index 00000000..15f359b3 --- /dev/null +++ b/docs/@pages/categoriesPage.md @@ -0,0 +1,6 @@ +--- +categoriesPage: true +title: 分类 +permalink: /categories/ +article: false +--- diff --git a/docs/@pages/tagsPage.md b/docs/@pages/tagsPage.md new file mode 100644 index 00000000..943f890c --- /dev/null +++ b/docs/@pages/tagsPage.md @@ -0,0 +1,6 @@ +--- +tagsPage: true +title: 标签 +permalink: /tags/ +article: false +--- diff --git a/docs/README.md b/docs/README.md index 1bd66c88..d4f91507 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,101 +1,113 @@ --- home: true -heroImage: https://raw.githubusercontent.com/dunwu/images/dev/common/dunwu-logo-200.png +heroImage: img/bg.gif heroText: DB-TUTORIAL -tagline: 💾 db-tutorial 是一个数据库教程。 -actionLink: / +tagline: ☕ db-tutorial 是一个数据库教程。 +bannerBg: none +postList: none footer: CC-BY-SA-4.0 Licensed | Copyright © 2018-Now Dunwu --- -![license](https://badgen.net/github/license/dunwu/db-tutorial) -![build](https://api.travis-ci.com/dunwu/db-tutorial.svg?branch=master) +

+ + + star + + + + fork + + + + build + + + + code style + + +

> 💾 **db-tutorial** 是一个数据库教程。 > > - 🔁 项目同步维护:[Github](https://github.com/dunwu/db-tutorial/) | [Gitee](https://gitee.com/turnon/db-tutorial/) > - 📖 电子书阅读:[Github Pages](https://dunwu.github.io/db-tutorial/) | [Gitee Pages](https://turnon.gitee.io/db-tutorial/) -## 📖 内容 +## 数据库综合 -### 关系型数据库 +### 分布式存储原理 -> [关系型数据库](sql) 整理主流关系型数据库知识点。 +#### 分布式理论 -#### [共性知识](sql/common) +- [分布式一致性](https://dunwu.github.io/blog/pages/dac0e2/) +- [深入剖析共识性算法 Paxos](https://dunwu.github.io/blog/pages/874539/) +- [深入剖析共识性算法 Raft](https://dunwu.github.io/blog/pages/e40812/) +- [分布式算法 Gossip](https://dunwu.github.io/blog/pages/d15993/) -- [关系型数据库面试总结](sql/common/sql-interview.md) 💯 -- [SQL Cheat Sheet](sql/common/sql-cheat-sheet.md) 是一个 SQL 入门教程。 -- [分布式存储基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/theory/distributed-storage.md) -- [分布式事务基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/theory/distributed-transaction.md) +#### 分布式关键技术 -#### [Mysql](sql/mysql) 📚 +##### 流量调度 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716103611.png) +- [流量控制](https://dunwu.github.io/blog/pages/282676/) +- [负载均衡](https://dunwu.github.io/blog/pages/98a1c1/) +- [服务路由](https://dunwu.github.io/blog/pages/d04ece/) +- [分布式会话基本原理](https://dunwu.github.io/blog/pages/3e66c2/) -- [Mysql 应用指南](sql/mysql/mysql-quickstart.md) ⚡ -- [Mysql 工作流](sql/mysql/mysql-workflow.md) - 关键词:`连接`、`缓存`、`语法分析`、`优化`、`执行引擎`、`redo log`、`bin log`、`两阶段提交` -- [Mysql 索引](sql/mysql/mysql-index.md) - 关键词:`Hash`、`B 树`、`聚簇索引`、`回表` -- [Mysql 锁](sql/mysql/mysql-lock.md) - 关键词:`乐观锁`、`表级锁`、`行级锁`、`意向锁`、`MVCC`、`Next-key 锁` -- [Mysql 事务](sql/mysql/mysql-transaction.md) - 关键词:`ACID`、`AUTOCOMMIT`、`事务隔离级别`、`死锁`、`分布式事务` -- [Mysql 性能优化](sql/mysql/mysql-optimization.md) -- [Mysql 运维](sql/mysql/mysql-ops.md) 🔨 -- [Mysql 配置](sql/mysql/mysql-config.md) -- [Mysql 问题](sql/mysql/mysql-faq.md) +##### 数据调度 -#### 其他关系型数据库 +- [缓存基本原理](https://dunwu.github.io/blog/pages/471208/) +- [读写分离基本原理](https://dunwu.github.io/blog/pages/7da6ca/) +- [分库分表基本原理](https://dunwu.github.io/blog/pages/103382/) +- [分布式 ID 基本原理](https://dunwu.github.io/blog/pages/0b2e59/) +- [分布式事务基本原理](https://dunwu.github.io/blog/pages/910bad/) +- [分布式锁基本原理](https://dunwu.github.io/blog/pages/69360c/) -- [H2 应用指南](sql/h2.md) -- [SqLite 应用指南](sql/sqlite.md) -- [PostgreSQL 应用指南](sql/postgresql.md) +### 其他 -### Nosql 数据库 +- [Nosql 技术选型](12.数据库/01.数据库综合/01.Nosql技术选型.md) +- [数据结构与数据库索引](12.数据库/01.数据库综合/02.数据结构与数据库索引.md) -> [Nosql 数据库](nosql) 整理主流 Nosql 数据库知识点。 +## 数据库中间件 -- [Nosql 技术选型](nosql/nosql-selection.md) +- [ShardingSphere 简介](12.数据库/02.数据库中间件/01.Shardingsphere/01.ShardingSphere简介.md) +- [ShardingSphere Jdbc](12.数据库/02.数据库中间件/01.Shardingsphere/02.ShardingSphereJdbc.md) +- [版本管理中间件 Flyway](12.数据库/02.数据库中间件/02.Flyway.md) -#### [Redis](nosql/redis) 📚 +## 关系型数据库 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200713105627.png) +> [关系型数据库](12.数据库/03.关系型数据库) 整理主流关系型数据库知识点。 -- [Redis 面试总结](nosql/redis/redis-interview.md) 💯 -- [Redis 应用指南](nosql/redis/redis-quickstart.md) ⚡ - 关键词:`内存淘汰`、`事件`、`事务`、`管道`、`发布与订阅` -- [Redis 数据类型和应用](nosql/redis/redis-datatype.md) - 关键词:`STRING`、`HASH`、`LIST`、`SET`、`ZSET`、`BitMap`、`HyperLogLog`、`Geo` -- [Redis 持久化](nosql/redis/redis-persistence.md) - 关键词:`RDB`、`AOF`、`SAVE`、`BGSAVE`、`appendfsync` -- [Redis 复制](nosql/redis/redis-replication.md) - 关键词:`SLAVEOF`、`SYNC`、`PSYNC`、`REPLCONF ACK` -- [Redis 哨兵](nosql/redis/redis-sentinel.md) - 关键词:`Sentinel`、`PING`、`INFO`、`Raft` -- [Redis 集群](nosql/redis/redis-cluster.md) - 关键词:`CLUSTER MEET`、`Hash slot`、`MOVED`、`ASK`、`SLAVEOF no one`、`redis-trib` -- [Redis 实战](nosql/redis/redis-action.md) - 关键词:`缓存`、`分布式锁`、`布隆过滤器` -- [Redis 运维](nosql/redis/redis-ops.md) 🔨 - 关键词:`安装`、`命令`、`集群`、`客户端` +### 关系型数据库综合 -#### [Elasticsearch](nosql/elasticsearch) 📚 +- [关系型数据库面试总结](12.数据库/03.关系型数据库/01.综合/01.关系型数据库面试.md) 💯 +- [SQL 语法基础特性](12.数据库/03.关系型数据库/01.综合/02.SQL语法基础特性.md) +- [SQL 语法高级特性](12.数据库/03.关系型数据库/01.综合/03.SQL语法高级特性.md) +- [扩展 SQL](12.数据库/03.关系型数据库/01.综合/03.扩展SQL.md) +- [SQL Cheat Sheet](12.数据库/03.关系型数据库/01.综合/99.SqlCheatSheet.md) -> Elasticsearch 是一个基于 Lucene 的搜索和数据分析工具,它提供了一个分布式服务。Elasticsearch 是遵从 Apache 开源条款的一款开源产品,是当前主流的企业级搜索引擎。 +### Mysql -- [Elasticsearch 面试总结](nosql/elasticsearch/elasticsearch-interview.md) 💯 -- [Elasticsearch 快速入门](nosql/elasticsearch/Elasticsearch快速入门.md) -- [Elasticsearch 简介](nosql/elasticsearch/Elasticsearch简介.md) -- [Elasticsearch Rest API](nosql/elasticsearch/ElasticsearchRestApi.md) -- [ElasticSearch Java API 之 High Level REST Client](nosql/elasticsearch/ElasticsearchHighLevelRestJavaApi.md) -- [Elasticsearch 索引管理](nosql/elasticsearch/Elasticsearch索引管理.md) -- [Elasticsearch 查询](nosql/elasticsearch/Elasticsearch查询.md) -- [Elasticsearch 高亮](nosql/elasticsearch/Elasticsearch高亮.md) -- [Elasticsearch 排序](nosql/elasticsearch/Elasticsearch排序.md) -- [Elasticsearch 聚合](nosql/elasticsearch/Elasticsearch聚合.md) -- [Elasticsearch 分析器](nosql/elasticsearch/Elasticsearch分析器.md) -- [Elasticsearch 运维](nosql/elasticsearch/Elasticsearch运维.md) -- [Elasticsearch 性能优化](nosql/elasticsearch/Elasticsearch性能优化.md) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716103611.png) -#### HBase +- [Mysql 应用指南](12.数据库/03.关系型数据库/02.Mysql/01.Mysql应用指南.md) ⚡ +- [Mysql 工作流](12.数据库/03.关系型数据库/02.Mysql/02.MySQL工作流.md) - 关键词:`连接`、`缓存`、`语法分析`、`优化`、`执行引擎`、`redo log`、`bin log`、`两阶段提交` +- [Mysql 事务](12.数据库/03.关系型数据库/02.Mysql/03.Mysql事务.md) - 关键词:`ACID`、`AUTOCOMMIT`、`事务隔离级别`、`死锁`、`分布式事务` +- [Mysql 锁](12.数据库/03.关系型数据库/02.Mysql/04.Mysql锁.md) - 关键词:`乐观锁`、`表级锁`、`行级锁`、`意向锁`、`MVCC`、`Next-key 锁` +- [Mysql 索引](12.数据库/03.关系型数据库/02.Mysql/05.Mysql索引.md) - 关键词:`Hash`、`B 树`、`聚簇索引`、`回表` +- [Mysql 性能优化](12.数据库/03.关系型数据库/02.Mysql/06.Mysql性能优化.md) +- [Mysql 运维](12.数据库/03.关系型数据库/02.Mysql/20.Mysql运维.md) 🔨 +- [Mysql 配置](12.数据库/03.关系型数据库/02.Mysql/21.Mysql配置.md) 🔨 +- [Mysql 问题](12.数据库/03.关系型数据库/02.Mysql/99.Mysql常见问题.md) -> [HBase](https://dunwu.github.io/bigdata-tutorial/hbase) 📚 因为常用于大数据项目,所以将其文档和源码整理在 [bigdata-tutorial](https://dunwu.github.io/bigdata-tutorial/) 项目中。 +### 其他 -- [HBase 原理](https://github.com/dunwu/bigdata-tutorial/blob/master/docs/hbase/HBase原理.md) ⚡ -- [HBase 命令](https://github.com/dunwu/bigdata-tutorial/blob/master/docs/hbase/HBase命令.md) -- [HBase 应用](https://github.com/dunwu/bigdata-tutorial/blob/master/docs/hbase/HBase应用.md) -- [HBase 运维](https://github.com/dunwu/bigdata-tutorial/blob/master/docs/hbase/HBase运维.md) +- [PostgreSQL 应用指南](12.数据库/03.关系型数据库/99.其他/01.PostgreSQL.md) +- [H2 应用指南](12.数据库/03.关系型数据库/99.其他/02.H2.md) +- [SqLite 应用指南](12.数据库/03.关系型数据库/99.其他/03.Sqlite.md) -#### [MongoDB](nosql/mongodb) 📚 +## 文档数据库 + +### MongoDB > MongoDB 是一个基于文档的分布式数据库,由 C++ 语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。 > @@ -103,38 +115,208 @@ footer: CC-BY-SA-4.0 Licensed | Copyright © 2018-Now Dunwu > > MongoDB 最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。 -- [MongoDB 应用指南](nosql/mongodb/mongodb-quickstart.md) -- [MongoDB 聚合操作](nosql/mongodb/mongodb-aggregation.md) -- [MongoDB 建模](nosql/mongodb/mongodb-model.md) -- [MongoDB 建模示例](nosql/mongodb/mongodb-model-example.md) -- [MongoDB 索引](nosql/mongodb/mongodb-index.md) -- [MongoDB 复制](nosql/mongodb/mongodb-replication.md) -- [MongoDB 分片](nosql/mongodb/mongodb-sharding.md) -- [MongoDB 运维](nosql/mongodb/mongodb-ops.md) +- [MongoDB 应用指南](12.数据库/04.文档数据库/01.MongoDB/01.MongoDB应用指南.md) +- [MongoDB 的 CRUD 操作](12.数据库/04.文档数据库/01.MongoDB/02.MongoDB的CRUD操作.md) +- [MongoDB 聚合操作](12.数据库/04.文档数据库/01.MongoDB/03.MongoDB的聚合操作.md) +- [MongoDB 事务](12.数据库/04.文档数据库/01.MongoDB/04.MongoDB事务.md) +- [MongoDB 建模](12.数据库/04.文档数据库/01.MongoDB/05.MongoDB建模.md) +- [MongoDB 建模示例](12.数据库/04.文档数据库/01.MongoDB/06.MongoDB建模示例.md) +- [MongoDB 索引](12.数据库/04.文档数据库/01.MongoDB/07.MongoDB索引.md) +- [MongoDB 复制](12.数据库/04.文档数据库/01.MongoDB/08.MongoDB复制.md) +- [MongoDB 分片](12.数据库/04.文档数据库/01.MongoDB/09.MongoDB分片.md) +- [MongoDB 运维](12.数据库/04.文档数据库/01.MongoDB/20.MongoDB运维.md) + +## KV 数据库 + +### Redis + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200713105627.png) + +- [Redis 面试总结](12.数据库/05.KV数据库/01.Redis/01.Redis面试总结.md) 💯 +- [Redis 应用指南](12.数据库/05.KV数据库/01.Redis/02.Redis应用指南.md) ⚡ - 关键词:`内存淘汰`、`事件`、`事务`、`管道`、`发布与订阅` +- [Redis 数据类型和应用](12.数据库/05.KV数据库/01.Redis/03.Redis数据类型和应用.md) - 关键词:`STRING`、`HASH`、`LIST`、`SET`、`ZSET`、`BitMap`、`HyperLogLog`、`Geo` +- [Redis 持久化](12.数据库/05.KV数据库/01.Redis/04.Redis持久化.md) - 关键词:`RDB`、`AOF`、`SAVE`、`BGSAVE`、`appendfsync` +- [Redis 复制](12.数据库/05.KV数据库/01.Redis/05.Redis复制.md) - 关键词:`SLAVEOF`、`SYNC`、`PSYNC`、`REPLCONF ACK` +- [Redis 哨兵](12.数据库/05.KV数据库/01.Redis/06.Redis哨兵.md) - 关键词:`Sentinel`、`PING`、`INFO`、`Raft` +- [Redis 集群](12.数据库/05.KV数据库/01.Redis/07.Redis集群.md) - 关键词:`CLUSTER MEET`、`Hash slot`、`MOVED`、`ASK`、`SLAVEOF no one`、`redis-trib` +- [Redis 实战](12.数据库/05.KV数据库/01.Redis/08.Redis实战.md) - 关键词:`缓存`、`分布式锁`、`布隆过滤器` +- [Redis 运维](12.数据库/05.KV数据库/01.Redis/20.Redis运维.md) 🔨 - 关键词:`安装`、`命令`、`集群`、`客户端` -### 中间件 +## 列式数据库 -- [版本管理中间件 flyway](middleware/flyway.md) -- [分库分表中间件 ShardingSphere](middleware/shardingsphere.md) +### HBase -## 📚 资料 +- [HBase 快速入门](12.数据库/06.列式数据库/01.HBase/01.HBase快速入门.md) +- [HBase 数据模型](12.数据库/06.列式数据库/01.HBase/02.HBase数据模型.md) +- [HBase Schema 设计](12.数据库/06.列式数据库/01.HBase/03.HBaseSchema设计.md) +- [HBase 架构](12.数据库/06.列式数据库/01.HBase/04.HBase架构.md) +- [HBase Java API 基础特性](12.数据库/06.列式数据库/01.HBase/10.HBaseJavaApi基础特性.md) +- [HBase Java API 高级特性之过滤器](12.数据库/06.列式数据库/01.HBase/11.HBaseJavaApi高级特性之过滤器.md) +- [HBase Java API 高级特性之协处理器](12.数据库/06.列式数据库/01.HBase/12.HBaseJavaApi高级特性之协处理器.md) +- [HBase Java API 其他高级特性](12.数据库/06.列式数据库/01.HBase/13.HBaseJavaApi其他高级特性.md) +- [HBase 运维](12.数据库/06.列式数据库/01.HBase/21.HBase运维.md) +- [HBase 命令](12.数据库/06.列式数据库/01.HBase/22.HBase命令.md) -### Mysql 资料 +## 搜索引擎数据库 + +### Elasticsearch + +> Elasticsearch 是一个基于 Lucene 的搜索和数据分析工具,它提供了一个分布式服务。Elasticsearch 是遵从 Apache 开源条款的一款开源产品,是当前主流的企业级搜索引擎。 + +- [Elasticsearch 面试总结](12.数据库/07.搜索引擎数据库/01.Elasticsearch/01.Elasticsearch面试总结.md) 💯 +- [Elasticsearch 快速入门](12.数据库/07.搜索引擎数据库/01.Elasticsearch/02.Elasticsearch快速入门.md) +- [Elasticsearch 简介](12.数据库/07.搜索引擎数据库/01.Elasticsearch/03.Elasticsearch简介.md) +- [Elasticsearch 索引](12.数据库/07.搜索引擎数据库/01.Elasticsearch/04.Elasticsearch索引.md) +- [Elasticsearch 查询](12.数据库/07.搜索引擎数据库/01.Elasticsearch/05.Elasticsearch查询.md) +- [Elasticsearch 高亮](12.数据库/07.搜索引擎数据库/01.Elasticsearch/06.Elasticsearch高亮.md) +- [Elasticsearch 排序](12.数据库/07.搜索引擎数据库/01.Elasticsearch/07.Elasticsearch排序.md) +- [Elasticsearch 聚合](12.数据库/07.搜索引擎数据库/01.Elasticsearch/08.Elasticsearch聚合.md) +- [Elasticsearch 分析器](12.数据库/07.搜索引擎数据库/01.Elasticsearch/09.Elasticsearch分析器.md) +- [Elasticsearch 性能优化](12.数据库/07.搜索引擎数据库/01.Elasticsearch/10.Elasticsearch性能优化.md) +- [Elasticsearch Rest API](12.数据库/07.搜索引擎数据库/01.Elasticsearch/11.ElasticsearchRestApi.md) +- [ElasticSearch Java API 之 High Level REST Client](12.数据库/07.搜索引擎数据库/01.Elasticsearch/12.ElasticsearchHighLevelRestJavaApi.md) +- [Elasticsearch 集群和分片](12.数据库/07.搜索引擎数据库/01.Elasticsearch/13.Elasticsearch集群和分片.md) +- [Elasticsearch 运维](12.数据库/07.搜索引擎数据库/01.Elasticsearch/20.Elasticsearch运维.md) + +### Elastic + +- [Elastic 快速入门](12.数据库/07.搜索引擎数据库/02.Elastic/01.Elastic快速入门.md) +- [Elastic 技术栈之 Filebeat](12.数据库/07.搜索引擎数据库/02.Elastic/02.Elastic技术栈之Filebeat.md) +- [Filebeat 运维](12.数据库/07.搜索引擎数据库/02.Elastic/03.Filebeat运维.md) +- [Elastic 技术栈之 Kibana](12.数据库/07.搜索引擎数据库/02.Elastic/04.Elastic技术栈之Kibana.md) +- [Kibana 运维](12.数据库/07.搜索引擎数据库/02.Elastic/05.Kibana运维.md) +- [Elastic 技术栈之 Logstash](12.数据库/07.搜索引擎数据库/02.Elastic/06.Elastic技术栈之Logstash.md) +- [Logstash 运维](12.数据库/07.搜索引擎数据库/02.Elastic/07.Logstash运维.md) + +## 资料 📚 + +### 数据库综合资料 + +- [DB-Engines](https://db-engines.com/en/ranking) - 数据库流行度排名 +- **书籍** + - [《数据密集型应用系统设计》](https://book.douban.com/subject/30329536/) - 这可能是目前最好的分布式存储书籍,强力推荐【进阶】 +- **教程** + - [CMU 15445 数据库基础课程](https://15445.courses.cs.cmu.edu/fall2019/schedule.html) + - [CMU 15721 数据库高级课程](https://15721.courses.cs.cmu.edu/spring2020/schedule.html) + - [检索技术核心 20 讲](https://time.geekbang.org/column/intro/100048401) - 极客教程【进阶】 + - [后端存储实战课](https://time.geekbang.org/column/intro/100046801) - 极客教程【入门】:讲解存储在电商领域的种种应用和一些基本特性 +- **论文** + - [Efficiency in the Columbia Database Query Optimizer](https://15721.courses.cs.cmu.edu/spring2018/papers/15-optimizer1/xu-columbia-thesis1998.pdf) + - [How Good Are Query Optimizers, Really?](http://www.vldb.org/pvldb/vol9/p204-leis.pdf) + - [Architecture of a Database System](https://dsf.berkeley.edu/papers/fntdb07-architecture.pdf) + - [Data Structures for Databases](https://www.cise.ufl.edu/~mschneid/Research/papers/HS05BoCh.pdf) +- **文章** + - [Data Structures and Algorithms for Big Databases](https://people.csail.mit.edu/bradley/BenderKuszmaul-tutorial-xldb12.pdf) + +### 关系型数据库资料 + +- **综合资料** + - [《数据库的索引设计与优化》](https://book.douban.com/subject/26419771/) + - [《SQL 必知必会》](https://book.douban.com/subject/35167240/) - SQL 的基本概念和语法【入门】 +- **Oracle 资料** + - [《Oracle Database 9i/10g/11g 编程艺术》](https://book.douban.com/subject/5402711/) + +#### Mysql 资料 - **官方** - [Mysql 官网](https://www.mysql.com/) - - [Mysql 官方文档](https://dev.mysql.com/doc/refman/8.0/en/) - - [Mysql 官方文档之命令行客户端](https://dev.mysql.com/doc/refman/8.0/en/mysql.html) + - [Mysql 官方文档](https://dev.mysql.com/doc/) + - **官方 PPT** + - [How to Analyze and Tune MySQL Queries for Better Performance](https://www.mysql.com/cn/why-mysql/presentations/tune-mysql-queries-performance/) + - [MySQL Performance Tuning 101](https://www.mysql.com/cn/why-mysql/presentations/mysql-performance-tuning101/) + - [MySQL Performance Schema & Sys Schema](https://www.mysql.com/cn/why-mysql/presentations/mysql-performance-sys-schema/) + - [MySQL Performance: Demystified Tuning & Best Practices](https://www.mysql.com/cn/why-mysql/presentations/mysql-performance-tuning-best-practices/) + - [MySQL Security Best Practices](https://www.mysql.com/cn/why-mysql/presentations/mysql-security-best-practices/) + - [MySQL Cluster Deployment Best Practices](https://www.mysql.com/cn/why-mysql/presentations/mysql-cluster-deployment-best-practices/) + - [MySQL High Availability with InnoDB Cluster](https://www.mysql.com/cn/why-mysql/presentations/mysql-high-availability-innodb-cluster/) - **书籍** - - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) - 经典,适合 DBA 或作为开发者的参考手册 - - [《MySQL 必知必会》](https://book.douban.com/subject/3354490/) - 适合入门者 + - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) - 经典,适合 DBA 或作为开发者的参考手册【进阶】 + - [《MySQL 技术内幕:InnoDB 存储引擎》](https://book.douban.com/subject/24708143/) + - [《MySQL 必知必会》](https://book.douban.com/subject/3354490/) - Mysql 的基本概念和语法【入门】 - **教程** - [runoob.com MySQL 教程](http://www.runoob.com/mysql/mysql-tutorial.html) - 入门级 SQL 教程 - [mysql-tutorial](https://github.com/jaywcjlove/mysql-tutorial) +- **文章** + - [MySQL 索引背后的数据结构及算法原理](http://blog.codinglabs.org/articles/theory-of-mysql-index.html) + - [Some study on database storage internals](https://medium.com/@kousiknath/data-structures-database-storage-internals-1f5ed3619d43) + - [Sharding Pinterest: How we scaled our MySQL fleet](https://medium.com/@Pinterest_Engineering/sharding-pinterest-how-we-scaled-our-mysql-fleet-3f341e96ca6f) + - [Guide to MySQL High Availability](https://www.mysql.com/cn/why-mysql/white-papers/mysql-guide-to-high-availability-solutions/) + - [Choosing MySQL High Availability Solutions](https://dzone.com/articles/choosing-mysql-high-availability-solutions) + - [High availability with MariaDB TX: The definitive guide](https://mariadb.com/sites/default/files/content/Whitepaper_High_availability_with_MariaDB-TX.pdf) + - Mysql 相关经验 + - [Booking.com: Evolution of MySQL System Design](https://www.percona.com/live/mysql-conference-2015/sessions/bookingcom-evolution-mysql-system-design) ,Booking.com 的 MySQL 数据库使用的演化,其中有很多不错的经验分享,我相信也是很多公司会遇到的的问题。 + - [Tracking the Money - Scaling Financial Reporting at Airbnb](https://medium.com/airbnb-engineering/tracking-the-money-scaling-financial-reporting-at-airbnb-6d742b80f040) ,Airbnb 的数据库扩展的经验分享。 + - [Why Uber Engineering Switched from Postgres to MySQL](https://eng.uber.com/mysql-migration/) ,无意比较两个数据库谁好谁不好,推荐这篇 Uber 的长文,主要是想让你从中学习到一些经验和技术细节,这是一篇很不错的文章。 + - Mysql 集群复制 + - [Monitoring Delayed Replication, With A Focus On MySQL](https://engineering.imvu.com/2013/01/09/monitoring-delayed-replication-with-a-focus-on-mysql/) + - [Mitigating replication lag and reducing read load with freno](https://githubengineering.com/mitigating-replication-lag-and-reducing-read-load-with-freno/) + - [Better Parallel Replication for MySQL](https://medium.com/booking-com-infrastructure/better-parallel-replication-for-mysql-14e2d7857813) + - [Evaluating MySQL Parallel Replication Part 2: Slave Group Commit](https://medium.com/booking-com-infrastructure/evaluating-mysql-parallel-replication-part-2-slave-group-commit-459026a141d2) + - [Evaluating MySQL Parallel Replication Part 3: Benchmarks in Production](https://medium.com/booking-com-infrastructure/evaluating-mysql-parallel-replication-part-3-benchmarks-in-production-db5811058d74) + - [Evaluating MySQL Parallel Replication Part 4: More Benchmarks in Production](https://medium.com/booking-com-infrastructure/evaluating-mysql-parallel-replication-part-4-more-benchmarks-in-production-49ee255043ab) + - [Evaluating MySQL Parallel Replication Part 4, Annex: Under the Hood](https://medium.com/booking-com-infrastructure/evaluating-mysql-parallel-replication-part-4-annex-under-the-hood-eb456cf8b2fb) + - Mysql 数据分区 + - [StackOverflow: MySQL sharding approaches?](https://stackoverflow.com/questions/5541421/mysql-sharding-approaches) + - [Why you don’t want to shard](https://www.percona.com/blog/2009/08/06/why-you-dont-want-to-shard/) + - [How to Scale Big Data Applications](https://www.percona.com/sites/default/files/presentations/How to Scale Big Data Applications.pdf) + - [MySQL Sharding with ProxySQL](https://www.percona.com/blog/2016/08/30/mysql-sharding-with-proxysql/) + - 各公司的 Mysql 数据分区经验分享 + - [MailChimp: Using Shards to Accommodate Millions of Users](https://devs.mailchimp.com/blog/using-shards-to-accommodate-millions-of-users/) + - [Uber: Code Migration in Production: Rewriting the Sharding Layer of Uber’s Schemaless Datastore](https://eng.uber.com/schemaless-rewrite/) + - [Sharding & IDs at Instagram](https://instagram-engineering.com/sharding-ids-at-instagram-1cf5a71e5a5c) + - [Airbnb: How We Partitioned Airbnb’s Main Database in Two Weeks](https://medium.com/airbnb-engineering/how-we-partitioned-airbnb-s-main-database-in-two-weeks-55f7e006ff21) - **更多资源** - - [awesome-mysql](https://github.com/jobbole/awesome-mysql-cn) + - [awesome-mysql](https://github.com/jobbole/awesome-mysql-cn) - MySQL 的资源列表 -### Redis 资料 +### Nosql 数据库综合 + +- Martin Fowler 在 YouTube 上分享的 NoSQL 介绍 [Introduction To NoSQL](https://youtu.be/qI_g07C_Q5I), 以及他参与编写的 [NoSQL Distilled - NoSQL 精粹](https://book.douban.com/subject/25662138/),这本书才 100 多页,是本难得的关于 NoSQL 的书,很不错,非常易读。 +- [NoSQL Databases: a Survey and Decision Guidance](https://medium.com/baqend-blog/nosql-databases-a-survey-and-decision-guidance-ea7823a822d#.nhzop4d23),这篇文章可以带你自上而下地从 CAP 原理到开始了解 NoSQL 的种种技术,是一篇非常不错的文章。 +- [Distribution, Data, Deployment: Software Architecture Convergence in Big Data Systems](https://resources.sei.cmu.edu/asset_files/WhitePaper/2014_019_001_90915.pdf),这是卡内基·梅隆大学的一篇讲分布式大数据系统的论文。其中主要讨论了在大数据时代下的软件工程中的一些关键点,也说到了 NoSQL 数据库。 +- [No Relation: The Mixed Blessings of Non-Relational Databases](http://ianvarley.com/UT/MR/Varley_MastersReport_Full_2009-08-07.pdf),这篇论文虽然有点年代久远。但这篇论文是 HBase 的基础,你花上一点时间来读读,就可以了解到,对各种非关系型数据存储优缺点的一个很好的比较。 +- [NoSQL Data Modeling Techniques](https://highlyscalable.wordpress.com/2012/03/01/nosql-data-modeling-techniques/) ,NoSQL 建模技术。这篇文章我曾经翻译在了 CoolShell 上,标题为 [NoSQL 数据建模技术](https://coolshell.cn/articles/7270.htm),供你参考。 + - [MongoDB - Data Modeling Introduction](https://docs.mongodb.com/manual/core/data-modeling-introduction/) ,虽然这是 MongoDB 的数据建模介绍,但是其很多观点可以用于其它的 NoSQL 数据库。 + - [Firebase - Structure Your Database](https://firebase.google.com/docs/database/android/structure-data) ,Google 的 Firebase 数据库使用 JSON 建模的一些最佳实践。 +- 因为 CAP 原理,所以当你需要选择一个 NoSQL 数据库的时候,你应该看看这篇文档 [Visual Guide to NoSQL Systems](http://blog.nahurst.com/visual-guide-to-nosql-systems)。 + +选 SQL 还是 NoSQL,这里有两篇文章,值得你看看。 + +- [SQL vs. NoSQL Databases: What’s the Difference?](https://www.upwork.com/hiring/data/sql-vs-nosql-databases-whats-the-difference/) +- [Salesforce: SQL or NoSQL](https://engineering.salesforce.com/sql-or-nosql-9eaf1d92545b) + +### 列式数据库资料 + +#### Cassandra 资料 + +- 沃尔玛实验室有两篇文章值得一读。 + - [Avoid Pitfalls in Scaling Cassandra Cluster at Walmart](https://medium.com/walmartlabs/avoid-pitfalls-in-scaling-your-cassandra-cluster-lessons-and-remedies-a71ca01f8c04) + - [Storing Images in Cassandra at Walmart](https://medium.com/walmartlabs/building-object-store-storing-images-in-cassandra-walmart-scale-a6b9c02af593) +- [Yelp: How We Scaled Our Ad Analytics with Apache Cassandra](https://engineeringblog.yelp.com/2016/08/how-we-scaled-our-ad-analytics-with-cassandra.html) ,Yelp 的这篇博客也有一些相关的经验和教训。 +- [Discord: How Discord Stores Billions of Messages](https://blog.discordapp.com/how-discord-stores-billions-of-messages-7fa6ec7ee4c7) ,Discord 公司分享的一个如何存储十亿级消息的技术文章。 +- [Cassandra at Instagram](https://www.slideshare.net/DataStax/cassandra-at-instagram-2016) ,Instagram 的一个 PPT,其中介绍了 Instagram 中是怎么使用 Cassandra 的。 +- [Netflix: Benchmarking Cassandra Scalability on AWS - Over a million writes per second](https://medium.com/netflix-techblog/benchmarking-cassandra-scalability-on-aws-over-a-million-writes-per-second-39f45f066c9e) ,Netflix 公司在 AWS 上给 Cassandra 做的一个 Benchmark。 + +#### HBase 资料 + +- [Imgur Notification: From MySQL to HBASE](https://medium.com/imgur-engineering/imgur-notifications-from-mysql-to-hbase-9dba6fc44183) +- [Pinterest: Improving HBase Backup Efficiency](https://medium.com/@Pinterest_Engineering/improving-hbase-backup-efficiency-at-pinterest-86159da4b954) +- [IBM : Tuning HBase performance](https://www.ibm.com/support/knowledgecenter/en/SSPT3X_2.1.2/com.ibm.swg.im.infosphere.biginsights.analyze.doc/doc/bigsql_TuneHbase.html) +- [HBase File Locality in HDFS](http://www.larsgeorge.com/2010/05/hbase-file-locality-in-hdfs.html) +- [Apache Hadoop Goes Realtime at Facebook](http://borthakur.com/ftp/RealtimeHadoopSigmod2011.pdf) +- [Storage Infrastructure Behind Facebook Messages: Using HBase at Scale](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.294.8459&rep=rep1&type=pdf) +- [GitHub: Awesome HBase](https://github.com/rayokota/awesome-hbase) + +针对于 HBase 有两本书你可以考虑一下。 + +- 首先,先推荐两本书,一本是偏实践的《[HBase 实战](https://book.douban.com/subject/25706541/)》,另一本是偏大而全的手册型的《[HBase 权威指南](https://book.douban.com/subject/10748460/)》。 +- 当然,你也可以看看官方的 [The Apache HBase™ Reference Guide](http://hbase.apache.org/0.94/book/book.html) +- 另外两个列数据库: + - [ClickHouse - Open Source Distributed Column Database at Yandex](https://clickhouse.yandex/) + - [Scaling Redshift without Scaling Costs at GIPHY](https://engineering.giphy.com/scaling-redshift-without-scaling-costs/) + +### KV 数据库资料 + +#### Redis 资料 - **官网** - [Redis 官网](https://redis.io/) @@ -154,8 +336,24 @@ footer: CC-BY-SA-4.0 Licensed | Copyright © 2018-Now Dunwu - [redisson 官方文档(英文)](https://github.com/redisson/redisson/wiki/Table-of-Content) - [CRUG | Redisson PRO vs. Jedis: Which Is Faster? 翻译](https://www.jianshu.com/p/82f0d5abb002) - [redis 分布锁 Redisson 性能测试](https://blog.csdn.net/everlasting_188/article/details/51073505) +- **文章** + - [Learn Redis the hard way (in production) at Trivago](http://tech.trivago.com/2017/01/25/learn-redis-the-hard-way-in-production/) + - [Twitter: How Twitter Uses Redis To Scale - 105TB RAM, 39MM QPS, 10,000+ Instances](http://highscalability.com/blog/2014/9/8/how-twitter-uses-redis-to-scale-105tb-ram-39mm-qps-10000-ins.html) + - [Slack: Scaling Slack’s Job Queue - Robustly Handling Billions of Tasks in Milliseconds Using Kafka and Redis](https://slack.engineering/scaling-slacks-job-queue-687222e9d100) + - [GitHub: Moving persistent data out of Redis at GitHub](https://githubengineering.com/moving-persistent-data-out-of-redis/) + - [Instagram: Storing Hundreds of Millions of Simple Key-Value Pairs in Redis](https://engineering.instagram.com/storing-hundreds-of-millions-of-simple-key-value-pairs-in-redis-1091ae80f74c) + - [Redis in Chat Architecture of Twitch (from 27:22)](https://www.infoq.com/presentations/twitch-pokemon) + - [Deliveroo: Optimizing Session Key Storage in Redis](https://deliveroo.engineering/2016/10/07/optimising-session-key-storage.html) + - [Deliveroo: Optimizing Redis Storage](https://deliveroo.engineering/2017/01/19/optimising-membership-queries.html) + - [GitHub: Awesome Redis](https://github.com/JamzyWang/awesome-redis) + +### 文档数据库资料 -### MongoDB 资料 +- [Couchbase Ecosystem at LinkedIn](https://engineering.linkedin.com/blog/2017/12/couchbase-ecosystem-at-linkedin) +- [SimpleDB at Zendesk](https://medium.com/zendesk-engineering/resurrecting-amazon-simpledb-9404034ec506) +- [Data Points - What the Heck Are Document Databases?](https://msdn.microsoft.com/en-us/magazine/hh547103.aspx) + +#### MongoDB 资料 - **官方** - [MongoDB 官网](https://www.mongodb.com/) @@ -168,7 +366,61 @@ footer: CC-BY-SA-4.0 Licensed | Copyright © 2018-Now Dunwu - [mongodb-json-files](https://github.com/ozlerhakan/mongodb-json-files) - **文章** - [Introduction to MongoDB](https://www.slideshare.net/mdirolf/introduction-to-mongodb) + - [eBay: Building Mission-Critical Multi-Data Center Applications with MongoDB](https://www.mongodb.com/blog/post/ebay-building-mission-critical-multi-data-center-applications-with-mongodb) + - [The AWS and MongoDB Infrastructure of Parse: Lessons Learned](https://medium.baqend.com/parse-is-gone-a-few-secrets-about-their-infrastructure-91b3ab2fcf71) + - [Migrating Mountains of Mongo Data](https://medium.com/build-addepar/migrating-mountains-of-mongo-data-63e530539952) +- **更多资源** + - [Github: Awesome MongoDB](https://github.com/ramnes/awesome-mongodb) + +### 搜索引擎数据库资料 -## 🚪 传送 +#### ElasticSearch -◾ 🏠 [DB-TUTORIAL 首页](https://github.com/dunwu/db-tutorial) ◾ 🎯 [我的博客](https://github.com/dunwu/blog) ◾ +- **官方** + - [Elasticsearch 官网](https://www.elastic.co/cn/products/elasticsearch) + - [Elasticsearch Github](https://github.com/elastic/elasticsearch) + - [Elasticsearch 官方文档](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) + - [Elasticsearch: The Definitive Guide](https://www.elastic.co/guide/en/elasticsearch/guide/master/index.html) - ElasticSearch 官方学习资料 +- **书籍** + - [《Elasticsearch 实战》](https://book.douban.com/subject/30380439/) +- **教程** + - [ELK Stack 权威指南](https://github.com/chenryn/logstash-best-practice-cn) + - [Elasticsearch 教程](https://www.knowledgedict.com/tutorial/elasticsearch-intro.html) +- **文章** + - [Elasticsearch+Logstash+Kibana 教程](https://www.cnblogs.com/xing901022/p/4704319.html) + - [ELK(Elasticsearch、Logstash、Kibana)安装和配置](https://github.com/judasn/Linux-Tutorial/blob/master/ELK-Install-And-Settings.md) + - **性能调优相关**的工程实践 + - [Elasticsearch Performance Tuning Practice at eBay](https://www.ebayinc.com/stories/blogs/tech/elasticsearch-performance-tuning-practice-at-ebay/) + - [Elasticsearch at Kickstarter](https://kickstarter.engineering/elasticsearch-at-kickstarter-db3c487887fc) + - [9 tips on ElasticSearch configuration for high performance](https://www.loggly.com/blog/nine-tips-configuring-elasticsearch-for-high-performance/) + - [Elasticsearch In Production - Deployment Best Practices](https://medium.com/@abhidrona/elasticsearch-deployment-best-practices-d6c1323b25d7) +- **更多资源** + - [GitHub: Awesome ElasticSearch](https://github.com/dzharii/awesome-elasticsearch) + +### 图数据库 + +- 首先是 IBM Devloperworks 上的两个简介性的 PPT。 + - [Intro to graph databases, Part 1, Graph databases and the CRUD operations](https://www.ibm.com/developerworks/library/cl-graph-database-1/cl-graph-database-1-pdf.pdf) + - [Intro to graph databases, Part 2, Building a recommendation engine with a graph database](https://www.ibm.com/developerworks/library/cl-graph-database-2/cl-graph-database-2-pdf.pdf) +- 然后是一本免费的电子书《[Graph Database](http://graphdatabases.com)》。 +- 接下来是一些图数据库的介绍文章。 + - [Handling Billions of Edges in a Graph Database](https://www.infoq.com/presentations/graph-database-scalability) + - [Neo4j case studies with Walmart, eBay, AirBnB, NASA, etc](https://neo4j.com/customers/) + - [FlockDB: Distributed Graph Database for Storing Adjacency Lists at Twitter](https://blog.twitter.com/engineering/en_us/a/2010/introducing-flockdb.html) + - [JanusGraph: Scalable Graph Database backed by Google, IBM and Hortonworks](https://architecht.io/google-ibm-back-new-open-source-graph-database-project-janusgraph-1d74fb78db6b) + - [Amazon Neptune](https://aws.amazon.com/neptune/) + +### 时序数据库 + +- [What is Time-Series Data & Why We Need a Time-Series Database](https://blog.timescale.com/what-the-heck-is-time-series-data-and-why-do-i-need-a-time-series-database-dcf3b1b18563) +- [Time Series Data: Why and How to Use a Relational Database instead of NoSQL](https://blog.timescale.com/time-series-data-why-and-how-to-use-a-relational-database-instead-of-nosql-d0cd6975e87c) +- [Beringei: High-performance Time Series Storage Engine @Facebook](https://code.facebook.com/posts/952820474848503/beringei-a-high-performance-time-series-storage-engine/) +- [Introducing Atlas: Netflix’s Primary Telemetry Platform @Netflix](https://medium.com/netflix-techblog/introducing-atlas-netflixs-primary-telemetry-platform-bd31f4d8ed9a) +- [Building a Scalable Time Series Database on PostgreSQL](https://blog.timescale.com/when-boring-is-awesome-building-a-scalable-time-series-database-on-postgresql-2900ea453ee2) +- [Scaling Time Series Data Storage - Part I @Netflix](https://medium.com/netflix-techblog/scaling-time-series-data-storage-part-i-ec2b6d44ba39) +- [Design of a Cost Efficient Time Series Store for Big Data](https://medium.com/@leventov/design-of-a-cost-efficient-time-series-store-for-big-data-88c5dc41af8e) +- [GitHub: Awesome Time-Series Database](https://github.com/xephonhq/awesome-time-series-database) + +## 传送 🚪 + +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ diff --git a/docs/nosql/README.md b/docs/nosql/README.md deleted file mode 100644 index 78275888..00000000 --- a/docs/nosql/README.md +++ /dev/null @@ -1,97 +0,0 @@ -# Nosql 数据库 - -## 📖 内容 - -### 列式数据库 - -#### [HBase](hbase.md) - -### K-V 数据库 - -#### [Redis](redis/README.md) - -#### [Cassandra](cassandra.md) - -### 文档数据库 - -#### [MongoDB](mongodb) - -### 搜索引擎数据库 - -#### [Elasticsearch](elasticsearch) 📚 - -> Elasticsearch 是一个基于 Lucene 的搜索和数据分析工具,它提供了一个分布式服务。Elasticsearch 是遵从 Apache 开源条款的一款开源产品,是当前主流的企业级搜索引擎。 - -- [Elasticsearch 面试总结](elasticsearch/elasticsearch-interview.md) 💯 -- [Elasticsearch 快速入门](elasticsearch/Elasticsearch快速入门.md) -- [Elasticsearch 简介](elasticsearch/Elasticsearch简介.md) -- [Elasticsearch Rest API](elasticsearch/ElasticsearchRestApi.md) -- [ElasticSearch Java API 之 High Level REST Client](elasticsearch/ElasticsearchHighLevelRestJavaApi.md) -- [Elasticsearch 索引管理](elasticsearch/Elasticsearch索引管理.md) -- [Elasticsearch 查询](elasticsearch/Elasticsearch查询.md) -- [Elasticsearch 高亮](elasticsearch/Elasticsearch高亮.md) -- [Elasticsearch 排序](elasticsearch/Elasticsearch排序.md) -- [Elasticsearch 聚合](elasticsearch/Elasticsearch聚合.md) -- [Elasticsearch 分析器](elasticsearch/Elasticsearch分析器.md) -- [Elasticsearch 运维](elasticsearch/Elasticsearch运维.md) -- [Elasticsearch 性能优化](elasticsearch/Elasticsearch性能优化.md) - -### 图数据库 - -TODO: 待补充 - -## 📚 资料 - -### Mysql 资料 - -- **官方** - - [Mysql 官网](https://www.mysql.com/) - - [Mysql 官方文档](https://dev.mysql.com/doc/refman/8.0/en/) - - [Mysql 官方文档之命令行客户端](https://dev.mysql.com/doc/refman/8.0/en/mysql.html) -- **书籍** - - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) - 经典,适合 DBA 或作为开发者的参考手册 - - [《MySQL 必知必会》](https://book.douban.com/subject/3354490/) - 适合入门者 -- **教程** - - [runoob.com MySQL 教程](http://www.runoob.com/mysql/mysql-tutorial.html) - 入门级 SQL 教程 - - [mysql-tutorial](https://github.com/jaywcjlove/mysql-tutorial) -- **更多资源** - - [awesome-mysql](https://github.com/jobbole/awesome-mysql-cn) - -### Redis 资料 - -- **官网** - - [Redis 官网](https://redis.io/) - - [Redis github](https://github.com/antirez/redis) - - [Redis 官方文档中文版](http://redis.cn/) - - [Redis 命令参考](http://redisdoc.com/) -- **书籍** - - [《Redis 实战》](https://item.jd.com/11791607.html) - - [《Redis 设计与实现》](https://item.jd.com/11486101.html) -- **源码** - - [《Redis 实战》配套 Python 源码](https://github.com/josiahcarlson/redis-in-action) -- **资源汇总** - - [awesome-redis](https://github.com/JamzyWang/awesome-redis) -- **Redis Client** - - [spring-data-redis 官方文档](https://docs.spring.io/spring-data/redis/docs/1.8.13.RELEASE/reference/html/) - - [redisson 官方文档(中文,略有滞后)](https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95) - - [redisson 官方文档(英文)](https://github.com/redisson/redisson/wiki/Table-of-Content) - - [CRUG | Redisson PRO vs. Jedis: Which Is Faster? 翻译](https://www.jianshu.com/p/82f0d5abb002) - - [redis 分布锁 Redisson 性能测试](https://blog.csdn.net/everlasting_188/article/details/51073505) - -### MongoDB 资料 - -- **官方** - - [MongoDB 官网](https://www.mongodb.com/) - - [MongoDB Github](https://github.com/mongodb/mongo) - - [MongoDB 官方免费教程](https://university.mongodb.com/) -- **教程** - - [MongoDB 教程](https://www.runoob.com/mongodb/mongodb-tutorial.html) - - [MongoDB 高手课](https://time.geekbang.org/course/intro/100040001) -- **数据** - - [mongodb-json-files](https://github.com/ozlerhakan/mongodb-json-files) -- **文章** - - [Introduction to MongoDB](https://www.slideshare.net/mdirolf/introduction-to-mongodb) - -## 🚪 传送 - -◾ 🏠 [DB-TUTORIAL 首页](https://github.com/dunwu/db-tutorial) ◾ 🎯 [我的博客](https://github.com/dunwu/blog) ◾ diff --git a/docs/nosql/elasticsearch/README.md b/docs/nosql/elasticsearch/README.md deleted file mode 100644 index ec6569f5..00000000 --- a/docs/nosql/elasticsearch/README.md +++ /dev/null @@ -1,84 +0,0 @@ -# Elasticsearch 教程 - -> Elasticsearch 是一个基于 Lucene 的搜索和数据分析工具,它提供了一个分布式服务。Elasticsearch 是遵从 Apache 开源条款的一款开源产品,是当前主流的企业级搜索引擎。 - -## 📖 内容 - -### [Elasticsearch 面试总结](elasticsearch-interview.md) 💯 - -### [Elasticsearch 快速入门](Elasticsearch快速入门.md) - -### [Elasticsearch 简介](Elasticsearch简介.md) - -### [Elasticsearch Rest API](ElasticsearchRestApi.md) - -### [ElasticSearch Java API 之 High Level REST Client](ElasticsearchHighLevelRestJavaApi.md) - -### [Elasticsearch 索引管理](Elasticsearch索引管理.md) - -### [Elasticsearch 查询](Elasticsearch查询.md) - -### [Elasticsearch 高亮](Elasticsearch高亮.md) - -### [Elasticsearch 排序](Elasticsearch排序.md) - -### [Elasticsearch 聚合](Elasticsearch聚合.md) - -### [Elasticsearch 分析器](Elasticsearch分析器.md) - -### [Elasticsearch 集群和分片](Elasticsearch集群和分片.md) - -### [Elasticsearch 运维](Elasticsearch运维.md) - -### [Elasticsearch 性能优化](Elasticsearch性能优化.md) - -### Elastic 技术栈 - -> **Elastic 技术栈通常被用来作为日志采集、检索、可视化解决方案。** -> -> ELK 是 elastic 公司旗下三款产品 [Elasticsearch](https://www.elastic.co/products/elasticsearch) 、[Logstash](https://www.elastic.co/products/logstash) 、[Kibana](https://www.elastic.co/products/kibana) 的首字母组合。 -> -> [Logstash](https://www.elastic.co/products/logstash) 传输和处理你的日志、事务或其他数据。 -> -> [Kibana](https://www.elastic.co/products/kibana) 将 Elasticsearch 的数据分析并渲染为可视化的报表。 -> -> Elastic 技术栈,在 ELK 的基础上扩展了一些新的产品,如:[Beats](https://www.elastic.co/products/beats) 、[X-Pack](https://www.elastic.co/products/x-pack) 。 - -- [Elastic 技术栈快速入门](nosql/elasticsearch/elastic/elastic-quickstart.md) -- [Beats 应用指南](nosql/elasticsearch/elastic/elastic-beats.md) -- [Beats 运维](nosql/elasticsearch/elastic/elastic-beats-ops.md) -- [Kibana 应用指南](nosql/elasticsearch/elastic/elastic-kibana.md) -- [Kibana 运维](nosql/elasticsearch/elastic/elastic-kibana-ops.md) -- [Logstash 应用指南](nosql/elasticsearch/elastic/elastic-logstash.md) -- [Logstash 运维](nosql/elasticsearch/elastic/elastic-logstash-ops.md) - -## 📚 资料 - -- **官方** - - [Elasticsearch 官网](https://www.elastic.co/cn/products/elasticsearch) - - [Elasticsearch Github](https://github.com/elastic/elasticsearch) - - [Elasticsearch 官方文档](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) - - [Logstash 官网](https://www.elastic.co/cn/products/logstash) - - [Logstash Github](https://github.com/elastic/logstash) - - [Logstash 官方文档](https://www.elastic.co/guide/en/logstash/current/index.html) - - [Kibana 官网](https://www.elastic.co/cn/products/kibana) - - [Kibana Github](https://github.com/elastic/kibana) - - [Kibana 官方文档](https://www.elastic.co/guide/en/kibana/current/index.html) - - [Beats 官网](https://www.elastic.co/cn/products/beats) - - [Beats Github](https://github.com/elastic/beats) - - [Beats 官方文档](https://www.elastic.co/guide/en/beats/libbeat/current/index.html) -- **官方** - - [《Elasticsearch 实战》](https://item.jd.com/12454556.html) -- **第三方工具** - - [logstash-logback-encoder](https://github.com/logstash/logstash-logback-encoder) -- **教程** - - [Elasticsearch 权威指南(中文版)](https://es.xiaoleilu.com/index.html) - - [ELK Stack 权威指南](https://github.com/chenryn/logstash-best-practice-cn) - - [Elasticsearch 教程](https://www.knowledgedict.com/tutorial/elasticsearch-intro.html) -- **博文** - - [Elasticsearch+Logstash+Kibana 教程](https://www.cnblogs.com/xing901022/p/4704319.html) - - [ELK(Elasticsearch、Logstash、Kibana)安装和配置](https://github.com/judasn/Linux-Tutorial/blob/master/ELK-Install-And-Settings.md) - -## 🚪 传送 - -◾ 🏠 [DB-TUTORIAL 首页](https://github.com/dunwu/db-tutorial) ◾ 🎯 [我的博客](https://github.com/dunwu/blog) ◾ diff --git a/docs/nosql/hbase.md b/docs/nosql/hbase.md deleted file mode 100644 index 0362092c..00000000 --- a/docs/nosql/hbase.md +++ /dev/null @@ -1,143 +0,0 @@ -# HBase - - - -- [简介](#简介) -- [基础](#基础) -- [原理](#原理) - - [数据模型](#数据模型) - - [HBase 架构](#hbase-架构) -- [HBase 和 RDBMS](#hbase-和-rdbms) -- [API](#api) -- [附录](#附录) - - [命令行](#命令行) -- [更多内容](#更多内容) - - [扩展阅读](#扩展阅读) - - [参考资料](#参考资料) - - - -## 简介 - -HBase 是建立在 HDFS 基础上的面向列的分布式数据库。 - -- HBase 参考了谷歌的 BigTable 建模,实现的编程语言为 Java。 -- 它是 Hadoop 项目的子项目,运行于 HDFS 文件系统之上。 - -HBase 适用场景:实时地随机访问超大数据集。 - -在 [CAP 理论](https://zh.wikipedia.org/wiki/CAP%E5%AE%9A%E7%90%86)中,HBase 属于 CP 类型的系统。 - -## 基础 - -[HBase 维护](HBase运维.md) - -## 原理 - -### 数据模型 - -HBase 是一个面向列的数据库,在表中它由行排序。 - -HBase 表模型结构为: - -- 表(table)是行的集合。 -- 行(row)是列族的集合。 -- 列族(column family)是列的集合。 -- 列(row)是键值对的集合。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/bigdata/hbase/1551164163369.png) - -HBase 表的单元格(cell)由行和列的坐标交叉决定,是有版本的。默认情况下,版本号是自动分配的,为 HBase 插入单元格时的时间戳。单元格的内容是未解释的字节数组。 - -行的键也是未解释的字节数组,所以理论上,任何数据都可以通过序列化表示成字符串或二进制,从而存为 HBase 的键值。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/bigdata/hbase/1551164224778.png) - -### HBase 架构 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/bigdata/hbase/1551164744748.png) - -和 HDFS、YARN 一样,HBase 也采用 master / slave 架构: - -- HBase 有一个 master 节点。master 节点负责将区域(region)分配给 region 节点;恢复 region 节点的故障。 -- HBase 有多个 region 节点。region 节点负责零个或多个区域(region)的管理并相应客户端的读写请求。region 节点还负责区域的划分并通知 master 节点有了新的子区域。 - -HBase 依赖 ZooKeeper 来实现故障恢复。 - -#### Regin - -HBase 表按行键范围水平自动划分为区域(region)。每个区域由表中行的子集构成。每个区域由它所属的表、它所含的第一行及最后一行来表示。 - -**区域只不过是表被拆分,并分布在区域服务器。** - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/bigdata/hbase/1551165887616.png) - -#### Master 服务器 - -区域分配、DDL(create、delete)操作由 HBase master 服务器处理。 - -- master 服务器负责协调 region 服务器 - - 协助区域启动,出现故障恢复或负载均衡情况时,重新分配 region 服务器 - - 监控集群中的所有 region 服务器 -- 支持 DDL 接口(创建、删除、更新表) - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/bigdata/hbase/1551166513572.png) - -#### Regin 服务器 - -区域服务器运行在 HDFS 数据节点上,具有以下组件 - -- `WAL` - Write Ahead Log 是 HDFS 上的文件。WAL 存储尚未持久存储到永久存储的新数据,它用于在发生故障时进行恢复。 - -- `BlockCache` - 是读缓存。它将频繁读取的数据存储在内存中。至少最近使用的数据在完整时被逐出。 -- `MemStore` - 是写缓存。它存储尚未写入磁盘的新数据。在写入磁盘之前对其进行排序。每个区域每个列族有一个 MemStore。 -- `Hfiles` - 将行存储为磁盘上的排序键值对。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/bigdata/hbase/1551166602999.png) - -#### ZooKeeper - -HBase 使用 ZooKeeper 作为分布式协调服务来维护集群中的服务器状态。Zookeeper 维护哪些服务器是活动的和可用的,并提供服务器故障通知。集群至少应该有 3 个节点。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/bigdata/hbase/1551166447147.png) - -## HBase 和 RDBMS - -| HBase | RDBMS | -| --------------------------------------------------- | ------------------------------------------ | -| HBase 无模式,它不具有固定列模式的概念;仅定义列族。 | RDBMS 有它的模式,描述表的整体结构的约束。 | -| 它专门创建为宽表。 HBase 是横向扩展。 | 这些都是细而专为小表。很难形成规模。 | -| 没有任何事务存在于 HBase。 | RDBMS 是事务性的。 | -| 它反规范化的数据。 | 它具有规范化的数据。 | -| 它用于半结构以及结构化数据是非常好的。 | 用于结构化数据非常好。 | - -## API - -Java API 归纳总结在这里:[HBase 应用](hbase-api-java.md) - -## 附录 - -### 命令行 - -HBase 命令行可以参考这里:[HBase 命令行](HBase命令.md) - -## 更多内容 - -### 扩展阅读 - -- [HBase 命令](HBase命令.md) -- [HBase 运维](HBase运维.md) - -### 参考资料 - -#### 官方 - -- [HBase 官网](http://hbase.apache.org/) -- [HBase 官方文档](https://hbase.apache.org/book.html) -- [HBase 官方文档中文版](http://abloz.com/hbase/book.html) -- [HBase API](https://hbase.apache.org/apidocs/index.html) - -#### 文章 - -- [Bigtable: A Distributed Storage System for Structured Data](https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/bigtable-osdi06.pdf) -- [An In-Depth Look at the HBase Architecture](https://mapr.com/blog/in-depth-look-hbase-architecture) diff --git a/docs/package.json b/docs/package.json deleted file mode 100644 index 953d3851..00000000 --- a/docs/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "db-tutorial", - "version": "1.0.0", - "private": true, - "scripts": { - "clean": "rimraf dist && rimraf .temp", - "build": "npm run clean && vuepress build ./ --temp .temp", - "start": "vuepress dev ./ --temp .temp", - "lint": "markdownlint -r markdownlint-rule-emphasis-style -c ./.markdownlint.json **/*.md -i node_modules", - "lint:fix": "markdownlint -f -r markdownlint-rule-emphasis-style -c ./.markdownlint.json **/*.md -i node_modules", - "show-help": "vuepress --help", - "view-info": "vuepress view-info ./ --temp .temp" - }, - "devDependencies": { - "@vuepress/plugin-active-header-links": "^1.8.2", - "@vuepress/plugin-back-to-top": "^1.8.2", - "@vuepress/plugin-medium-zoom": "^1.8.2", - "@vuepress/plugin-pwa": "^1.8.2", - "@vuepress/theme-vue": "^1.8.2", - "markdownlint-cli": "^0.30.0", - "markdownlint-rule-emphasis-style": "^1.0.1", - "rimraf": "^3.0.1", - "vue-toasted": "^1.1.25", - "vuepress": "^1.8.2", - "vuepress-plugin-flowchart": "^1.5.0" - }, - "dependencies": { - "moment": "^2.29.1" - } -} diff --git a/docs/sql/README.md b/docs/sql/README.md deleted file mode 100644 index 4ce384b1..00000000 --- a/docs/sql/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# 关系型数据库 - -> 关系数据库,是建立在关系模型基础上的数据库,借助于集合代数等数学概念和方法来处理数据库中的数据。现实世界中的各种实体以及实体之间的各种联系均用关系模型来表示。关系模型由关系数据结构、关系操作集合、关系完整性约束三部分组成。 - -## 📖 内容 - -### [共性知识](common) - -- [关系型数据库面试总结](common/sql-interview.md) 💯 -- [SQL Cheat Sheet](common/sql-cheat-sheet.md) 是一个 SQL 入门教程。 -- [分布式存储基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/theory/distributed-storage.md) -- [分布式事务基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/theory/distributed-transaction.md) - -### [Mysql](mysql/README.md) - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716103611.png) - -- [Mysql 应用指南](mysql/mysql-quickstart.md) ⚡ -- [Mysql 工作流](mysql/mysql-index.md) - 关键词:`连接`、`缓存`、`语法分析`、`优化`、`执行引擎`、`redo log`、`bin log`、`两阶段提交` -- [Mysql 索引](mysql/mysql-index.md) - 关键词:`Hash`、`B 树`、`聚簇索引`、`回表` -- [Mysql 锁](mysql/mysql-lock.md) - 关键词:`乐观锁`、`表级锁`、`行级锁`、`意向锁`、`MVCC`、`Next-key 锁` -- [Mysql 事务](mysql/mysql-transaction.md) - 关键词:`ACID`、`AUTOCOMMIT`、`事务隔离级别`、`死锁`、`分布式事务` -- [Mysql 性能优化](mysql/mysql-optimization.md) -- [Mysql 运维](mysql/mysql-ops.md) 🔨 -- [Mysql 配置](mysql/mysql-config.md) -- [Mysql 问题](mysql/mysql-faq.md) - -### 其他关系型数据库 - -- [H2 入门](h2.md) -- [SqLite 入门](sqlite.md) -- [PostgreSQL 入门](postgresql.md) - -## 📚 资料 - -- **官方** - - [Mysql 官网](https://www.mysql.com/) - - [Mysql 官方文档](https://dev.mysql.com/doc/refman/8.0/en/) - - [Mysql 官方文档之命令行客户端](https://dev.mysql.com/doc/refman/8.0/en/mysql.html) -- **书籍** - - [《高性能 MySQL》](https://item.jd.com/11220393.html) - Mysql 经典 - - [《SQL 必知必会》](https://item.jd.com/11232698.html) - SQL 入门 -- **教程** - - [runoob.com MySQL 教程](http://www.runoob.com/mymysql-tutorial.html) - 入门级 SQL 教程 - - [mysql-tutorial](https://github.com/jaywcjlove/mysql-tutorial) -- **更多资源** - - [awesome-mysql](https://github.com/jobbole/awesome-mysql-cn) - -## 🚪 传送 - -◾ 🏠 [DB-TUTORIAL 首页](https://github.com/dunwu/db-tutorial) ◾ 🎯 [我的博客](https://github.com/dunwu/blog) ◾ diff --git a/docs/sql/common/README.md b/docs/sql/common/README.md deleted file mode 100644 index d6df95da..00000000 --- a/docs/sql/common/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# 关系型数据库共性知识 - -## 📖 内容 - -### [关系型数据库面试题 💯](sql-interview.md) - -### [SQL Cheat Sheet](sql-cheat-sheet.md) - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200115160512.png) - -### [分布式存储基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/theory/distributed-storage.md) - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716110854.png) - -### [分布式事务基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/theory/distributed-transaction.md) - -## 📚 资料 - -- **官方** - - [Mysql 官网](https://www.mysql.com/) - - [Mysql 官方文档](https://dev.mysql.com/doc/refman/8.0/en/) - - [Mysql 官方文档之命令行客户端](https://dev.mysql.com/doc/refman/8.0/en/mysql.html) -- **书籍** - - [《高性能 MySQL》](https://item.jd.com/11220393.html) - Mysql 经典 - - [《SQL 必知必会》](https://item.jd.com/11232698.html) - SQL 入门 -- **教程** - - [runoob.com MySQL 教程](http://www.runoob.com/mymysql-tutorial.html) - 入门级 SQL 教程 - - [mysql-tutorial](https://github.com/jaywcjlove/mysql-tutorial) -- **更多资源** - - [awesome-mysql](https://github.com/jobbole/awesome-mysql-cn) - -## 🚪 传送 - -◾ 🏠 [DB-TUTORIAL 首页](https://github.com/dunwu/db-tutorial) ◾ 🎯 [我的博客](https://github.com/dunwu/blog) ◾ diff --git a/docs/sql/common/sql-advanced.md b/docs/sql/common/sql-advanced.md deleted file mode 100644 index ed9abfcb..00000000 --- a/docs/sql/common/sql-advanced.md +++ /dev/null @@ -1,25 +0,0 @@ -# 高级 SQL - -> 不同于 [SQL Cheat Sheet](sql-cheat-sheet.md) 中的一般语法,本文主要整理收集一些高级但是很有用的 SQL - -## 数据库 - -## 表 - -查看表的基本信息 - -```sql -SELECT * FROM information_schema.tables -WHERE table_schema = 'test' AND table_name = 'user'; -``` - -查看表的列信息 - -```sql -SELECT * FROM information_schema.columns -WHERE table_schema = 'test' AND table_name = 'user'; -``` - -## 参考资料 - -- [《SQL 必知必会》](https://item.jd.com/11232698.html) diff --git a/docs/sql/common/sql-cheat-sheet.md b/docs/sql/common/sql-cheat-sheet.md deleted file mode 100644 index 85a89c69..00000000 --- a/docs/sql/common/sql-cheat-sheet.md +++ /dev/null @@ -1,1105 +0,0 @@ -# SQL Cheat Sheet - -> 本文针对关系型数据库的基本语法。限于篇幅,本文侧重说明用法,不会展开讲解特性、原理。 -> -> 本文语法主要针对 Mysql,但大部分的语法对其他关系型数据库也适用。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200115160512.png) - -## 一、基本概念 - -### 数据库术语 - -- `数据库(database)` - 保存有组织的数据的容器(通常是一个文件或一组文件)。 -- `数据表(table)` - 某种特定类型数据的结构化清单。 -- `模式(schema)` - 关于数据库和表的布局及特性的信息。模式定义了数据在表中如何存储,包含存储什么样的数据,数据如何分解,各部分信息如何命名等信息。数据库和表都有模式。 -- `列(column)` - 表中的一个字段。所有表都是由一个或多个列组成的。 -- `行(row)` - 表中的一个记录。 -- `主键(primary key)` - 一列(或一组列),其值能够唯一标识表中每一行。 - -### SQL 语法 - -> SQL(Structured Query Language),标准 SQL 由 ANSI 标准委员会管理,从而称为 ANSI SQL。各个 DBMS 都有自己的实现,如 PL/SQL、Transact-SQL 等。 - -#### SQL 语法结构 - -![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/mysql/sql-syntax.png) - -SQL 语法结构包括: - -- **`子句`** - 是语句和查询的组成成分。(在某些情况下,这些都是可选的。) -- **`表达式`** - 可以产生任何标量值,或由列和行的数据库表 -- **`谓词`** - 给需要评估的 SQL 三值逻辑(3VL)(true/false/unknown)或布尔真值指定条件,并限制语句和查询的效果,或改变程序流程。 -- **`查询`** - 基于特定条件检索数据。这是 SQL 的一个重要组成部分。 -- **`语句`** - 可以持久地影响纲要和数据,也可以控制数据库事务、程序流程、连接、会话或诊断。 - -#### SQL 语法要点 - -- **SQL 语句不区分大小写**,但是数据库表名、列名和值是否区分,依赖于具体的 DBMS 以及配置。 - -例如:`SELECT` 与 `select` 、`Select` 是相同的。 - -- **多条 SQL 语句必须以分号(`;`)分隔**。 - -- 处理 SQL 语句时,**所有空格都被忽略**。SQL 语句可以写成一行,也可以分写为多行。 - -```sql --- 一行 SQL 语句 -UPDATE user SET username='robot', password='robot' WHERE username = 'root'; - --- 多行 SQL 语句 -UPDATE user -SET username='robot', password='robot' -WHERE username = 'root'; -``` - -- SQL 支持三种注释 - -```sql -## 注释1 --- 注释2 -/* 注释3 */ -``` - -#### SQL 分类 - -#### 数据定义语言(DDL) - -数据定义语言(Data Definition Language,DDL)是 SQL 语言集中负责数据结构定义与数据库对象定义的语言。 - -DDL 的主要功能是**定义数据库对象**。 - -DDL 的核心指令是 `CREATE`、`ALTER`、`DROP`。 - -#### 数据操纵语言(DML) - -数据操纵语言(Data Manipulation Language, DML)是用于数据库操作,对数据库其中的对象和数据运行访问工作的编程语句。 - -DML 的主要功能是 **访问数据**,因此其语法都是以**读写数据库**为主。 - -DML 的核心指令是 `INSERT`、`UPDATE`、`DELETE`、`SELECT`。这四个指令合称 CRUD(Create, Read, Update, Delete),即增删改查。 - -#### 事务控制语言(TCL) - -事务控制语言 (Transaction Control Language, TCL) 用于**管理数据库中的事务**。这些用于管理由 DML 语句所做的更改。它还允许将语句分组为逻辑事务。 - -TCL 的核心指令是 `COMMIT`、`ROLLBACK`。 - -#### 数据控制语言(DCL) - -数据控制语言 (Data Control Language, DCL) 是一种可对数据访问权进行控制的指令,它可以控制特定用户账户对数据表、查看表、预存程序、用户自定义函数等数据库对象的控制权。 - -DCL 的核心指令是 `GRANT`、`REVOKE`。 - -DCL 以**控制用户的访问权限**为主,因此其指令作法并不复杂,可利用 DCL 控制的权限有:`CONNECT`、`SELECT`、`INSERT`、`UPDATE`、`DELETE`、`EXECUTE`、`USAGE`、`REFERENCES`。 - -根据不同的 DBMS 以及不同的安全性实体,其支持的权限控制也有所不同。 - ---- - -**(以下为 DML 语句用法)** - -## 二、增删改查 - -> 增删改查,又称为 CRUD,数据库基本操作中的基本操作。 - -### 插入数据 - -> - `INSERT INTO` 语句用于向表中插入新记录。 - -**插入完整的行** - -```sql -INSERT INTO user -VALUES (10, 'root', 'root', 'xxxx@163.com'); -``` - -**插入行的一部分** - -```sql -INSERT INTO user(username, password, email) -VALUES ('admin', 'admin', 'xxxx@163.com'); -``` - -**插入查询出来的数据** - -```sql -INSERT INTO user(username) -SELECT name -FROM account; -``` - -### 更新数据 - -> - `UPDATE` 语句用于更新表中的记录。 - -```sql -UPDATE user -SET username='robot', password='robot' -WHERE username = 'root'; -``` - -### 删除数据 - -> - `DELETE` 语句用于删除表中的记录。 -> - `TRUNCATE TABLE` 可以清空表,也就是删除所有行。 - -**删除表中的指定数据** - -```sql -DELETE FROM user -WHERE username = 'robot'; -``` - -**清空表中的数据** - -```sql -TRUNCATE TABLE user; -``` - -### 查询数据 - -> - `SELECT` 语句用于从数据库中查询数据。 -> - `DISTINCT` 用于返回唯一不同的值。它作用于所有列,也就是说所有列的值都相同才算相同。 -> - `LIMIT` 限制返回的行数。可以有两个参数,第一个参数为起始行,从 0 开始;第二个参数为返回的总行数。 -> - `ASC` :升序(默认) -> - `DESC` :降序 - -**查询单列** - -```sql -SELECT prod_name -FROM products; -``` - -**查询多列** - -```sql -SELECT prod_id, prod_name, prod_price -FROM products; -``` - -**查询所有列** - -```sql -ELECT * -FROM products; -``` - -**查询不同的值** - -```sql -SELECT DISTINCT -vend_id FROM products; -``` - -**限制查询结果** - -```sql --- 返回前 5 行 -SELECT * FROM mytable LIMIT 5; -SELECT * FROM mytable LIMIT 0, 5; --- 返回第 3 ~ 5 行 -SELECT * FROM mytable LIMIT 2, 3; -``` - -## 三、子查询 - -> 子查询是嵌套在较大查询中的 SQL 查询。子查询也称为**内部查询**或**内部选择**,而包含子查询的语句也称为**外部查询**或**外部选择**。 - -- 子查询可以嵌套在 `SELECT`,`INSERT`,`UPDATE` 或 `DELETE` 语句内或另一个子查询中。 - -- 子查询通常会在另一个 `SELECT` 语句的 `WHERE` 子句中添加。 - -- 您可以使用比较运算符,如 `>`,`<`,或 `=`。比较运算符也可以是多行运算符,如 `IN`,`ANY` 或 `ALL`。 - -- 子查询必须被圆括号 `()` 括起来。 - -- 内部查询首先在其父查询之前执行,以便可以将内部查询的结果传递给外部查询。执行过程可以参考下图: - -

- sql-subqueries -

- -**子查询的子查询** - -```sql -SELECT cust_name, cust_contact -FROM customers -WHERE cust_id IN (SELECT cust_id - FROM orders - WHERE order_num IN (SELECT order_num - FROM orderitems - WHERE prod_id = 'RGAN01')); -``` - -### WHERE - -> `WHERE` 子句用于过滤记录,即缩小访问数据的范围。`WHERE` 后跟一个返回 `true` 或 `false` 的条件。 -> -> `WHERE` 可以与 `SELECT`,`UPDATE` 和 `DELETE` 一起使用。 - -可以在 `WHERE` 子句中使用的操作符: - -| 运算符 | 描述 | -| ------- | ------------------------------------------------------ | -| = | 等于 | -| <> | 不等于。注释:在 SQL 的一些版本中,该操作符可被写成 != | -| > | 大于 | -| < | 小于 | -| >= | 大于等于 | -| <= | 小于等于 | -| BETWEEN | 在某个范围内 | -| LIKE | 搜索某种模式 | -| IN | 指定针对某个列的多个可能值 | - -**`SELECT` 语句中的 `WHERE` 子句** - -```sql -SELECT * FROM Customers -WHERE cust_name = 'Kids Place'; -``` - -**`UPDATE` 语句中的 `WHERE` 子句** - -```sql -UPDATE Customers -SET cust_name = 'Jack Jones' -WHERE cust_name = 'Kids Place'; -``` - -**`DELETE` 语句中的 `WHERE` 子句** - -```sql -DELETE FROM Customers -WHERE cust_name = 'Kids Place'; -``` - -### IN 和 BETWEEN - -> `IN` 操作符在 `WHERE` 子句中使用,作用是在指定的几个特定值中任选一个值。 -> -> `BETWEEN` 操作符在 `WHERE` 子句中使用,作用是选取介于某个范围内的值。 - -**IN 示例** - -```sql -SELECT * -FROM products -WHERE vend_id IN ('DLL01', 'BRS01'); -``` - -**BETWEEN 示例** - -```sql -SELECT * -FROM products -WHERE prod_price BETWEEN 3 AND 5; -``` - -### AND、OR、NOT - -> `AND`、`OR`、`NOT` 是用于对过滤条件的逻辑处理指令。 -> -> `AND` 优先级高于 `OR`,为了明确处理顺序,可以使用 `()`。`AND` 操作符表示左右条件都要满足。 -> -> `OR` 操作符表示左右条件满足任意一个即可。 -> -> `NOT` 操作符用于否定一个条件。 - -**AND 示例** - -```sql -SELECT prod_id, prod_name, prod_price -FROM products -WHERE vend_id = 'DLL01' AND prod_price <= 4; -``` - -**OR 示例** - -```sql -SELECT prod_id, prod_name, prod_price -FROM products -WHERE vend_id = 'DLL01' OR vend_id = 'BRS01'; -``` - -**NOT 示例** - -```sql -SELECT * -FROM products -WHERE prod_price NOT BETWEEN 3 AND 5; -``` - -### LIKE - -> `LIKE` 操作符在 `WHERE` 子句中使用,作用是确定字符串是否匹配模式。只有字段是文本值时才使用 `LIKE`。 -> -> `LIKE` 支持两个通配符匹配选项:`%` 和 `_`。 -> -> - `%` 表示任何字符出现任意次数。 -> - `_` 表示任何字符出现一次。 -> -> 不要滥用通配符,通配符位于开头处匹配会非常慢。 - -`%` 示例: - -```sql -SELECT prod_id, prod_name, prod_price -FROM products -WHERE prod_name LIKE '%bean bag%'; -``` - -`_` 示例: - -```sql -SELECT prod_id, prod_name, prod_price -FROM products -WHERE prod_name LIKE '__ inch teddy bear'; -``` - -## 四、连接和组合 - -### 连接(JOIN) - -> 连接用于连接多个表,使用 `JOIN` 关键字,并且条件语句使用 `ON` 而不是 `WHERE`。 - -如果一个 `JOIN` 至少有一个公共字段并且它们之间存在关系,则该 `JOIN` 可以在两个或多个表上工作。 - -`JOIN` 保持基表(结构和数据)不变。**连接可以替换子查询,并且比子查询的效率一般会更快**。 - -`JOIN` 有两种连接类型:内连接和外连接。 - -
- sql-join -
- -#### 内连接(INNER JOIN) - -内连接又称等值连接,**使用 `INNER JOIN` 关键字**。在没有条件语句的情况下**返回笛卡尔积**。 - -```sql -SELECT vend_name, prod_name, prod_price -FROM vendors INNER JOIN products -ON vendors.vend_id = products.vend_id; -``` - -##### 自连接(`=`) - -自连接可以看成内连接的一种,只是**连接的表是自身**而已。**自然连接是把同名列通过 `=` 连接起来**的,同名列可以有多个。 - -```sql -SELECT c1.cust_id, c1.cust_name, c1.cust_contact -FROM customers c1, customers c2 -WHERE c1.cust_name = c2.cust_name -AND c2.cust_contact = 'Jim Jones'; -``` - -##### 自然连接(NATURAL JOIN) - -内连接提供连接的列,而自然连接**自动连接所有同名列**。自然连接使用 `NATURAL JOIN` 关键字。 - -```sql -SELECT * -FROM Products -NATURAL JOIN Customers; -``` - -#### 外连接(OUTER JOIN) - -外连接返回一个表中的所有行,并且仅返回来自此表中满足连接条件的那些行,即两个表中的列是相等的。外连接分为左外连接、右外连接、全外连接(Mysql 不支持)。 - -##### 左连接(LEFT JOIN) - -左外连接就是保留左表没有关联的行。 - -```sql -SELECT customers.cust_id, orders.order_num -FROM customers LEFT JOIN orders -ON customers.cust_id = orders.cust_id; -``` - -##### 右连接(RIGHT JOIN) - -右外连接就是保留右表没有关联的行。 - -```sql -SELECT customers.cust_id, orders.order_num -FROM customers RIGHT JOIN orders -ON customers.cust_id = orders.cust_id; -``` - -### 组合(UNION) - -> `UNION` 运算符**将两个或更多查询的结果组合起来,并生成一个结果集**,其中包含来自 `UNION` 中参与查询的提取行。 - -`UNION` 基本规则: - -- 所有查询的列数和列顺序必须相同。 -- 每个查询中涉及表的列的数据类型必须相同或兼容。 -- 通常返回的列名取自第一个查询。 - -默认会去除相同行,如果需要保留相同行,使用 `UNION ALL`。 - -只能包含一个 `ORDER BY` 子句,并且必须位于语句的最后。 - -应用场景: - -- 在一个查询中从不同的表返回结构数据。 -- 对一个表执行多个查询,按一个查询返回数据。 - -组合查询示例: - -```sql -SELECT cust_name, cust_contact, cust_email -FROM customers -WHERE cust_state IN ('IL', 'IN', 'MI') -UNION -SELECT cust_name, cust_contact, cust_email -FROM customers -WHERE cust_name = 'Fun4All'; -``` - -### JOIN vs UNION - -- `JOIN` 中连接表的列可能不同,但在 `UNION` 中,所有查询的列数和列顺序必须相同。 -- `UNION` 将查询之后的行放在一起(垂直放置),但 `JOIN` 将查询之后的列放在一起(水平放置),即它构成一个笛卡尔积。 - -## 五、函数 - -> 🔔 注意:不同数据库的函数往往各不相同,因此不可移植。本节主要以 Mysql 的函数为例。 - -### 文本处理 - -| 函数 | 说明 | -| :------------------: | :--------------------: | -| `LEFT()`、`RIGHT()` | 左边或者右边的字符 | -| `LOWER()`、`UPPER()` | 转换为小写或者大写 | -| `LTRIM()`、`RTIM()` | 去除左边或者右边的空格 | -| `LENGTH()` | 长度 | -| `SOUNDEX()` | 转换为语音值 | - -其中, **SOUNDEX()** 可以将一个字符串转换为描述其语音表示的字母数字模式。 - -```sql -SELECT * -FROM mytable -WHERE SOUNDEX(col1) = SOUNDEX('apple') -``` - -### 日期和时间处理 - -- 日期格式:`YYYY-MM-DD` -- 时间格式:`HH:MM:SS` - -| 函 数 | 说 明 | -| :-------------: | :----------------------------: | -| `AddDate()` | 增加一个日期(天、周等) | -| `AddTime()` | 增加一个时间(时、分等) | -| `CurDate()` | 返回当前日期 | -| `CurTime()` | 返回当前时间 | -| `Date()` | 返回日期时间的日期部分 | -| `DateDiff()` | 计算两个日期之差 | -| `Date_Add()` | 高度灵活的日期运算函数 | -| `Date_Format()` | 返回一个格式化的日期或时间串 | -| `Day()` | 返回一个日期的天数部分 | -| `DayOfWeek()` | 对于一个日期,返回对应的星期几 | -| `Hour()` | 返回一个时间的小时部分 | -| `Minute()` | 返回一个时间的分钟部分 | -| `Month()` | 返回一个日期的月份部分 | -| `Now()` | 返回当前日期和时间 | -| `Second()` | 返回一个时间的秒部分 | -| `Time()` | 返回一个日期时间的时间部分 | -| `Year()` | 返回一个日期的年份部分 | - -```sql -mysql> SELECT NOW(); -``` - -``` -2018-4-14 20:25:11 -``` - -### 数值处理 - -| 函数 | 说明 | -| :----: | :----: | -| SIN() | 正弦 | -| COS() | 余弦 | -| TAN() | 正切 | -| ABS() | 绝对值 | -| SQRT() | 平方根 | -| MOD() | 余数 | -| EXP() | 指数 | -| PI() | 圆周率 | -| RAND() | 随机数 | - -### 汇总 - -| 函 数 | 说 明 | -| :-------: | :--------------: | -| `AVG()` | 返回某列的平均值 | -| `COUNT()` | 返回某列的行数 | -| `MAX()` | 返回某列的最大值 | -| `MIN()` | 返回某列的最小值 | -| `SUM()` | 返回某列值之和 | - -`AVG()` 会忽略 NULL 行。 - -使用 DISTINCT 可以让汇总函数值汇总不同的值。 - -```sql -SELECT AVG(DISTINCT col1) AS avg_col -FROM mytable -``` - -## 六、排序和分组 - -### ORDER BY - -> `ORDER BY` 用于对结果集进行排序。 - -`ORDER BY` 有两种排序模式: - -- `ASC` :升序(默认) -- `DESC` :降序 - -可以按多个列进行排序,并且为每个列指定不同的排序方式。 - -指定多个列的排序示例: - -```sql -SELECT * FROM products -ORDER BY prod_price DESC, prod_name ASC; -``` - -### GROUP BY - -> `GROUP BY` 子句将记录分组到汇总行中,`GROUP BY` 为每个组返回一个记录。 - -`GROUP BY` 可以按一列或多列进行分组。 - -`GROUP BY` 通常还涉及聚合函数:COUNT,MAX,SUM,AVG 等。 - -`GROUP BY` 按分组字段进行排序后,`ORDER BY` 可以以汇总字段来进行排序。 - -分组示例: - -```sql -SELECT cust_name, COUNT(cust_address) AS addr_num -FROM Customers GROUP BY cust_name; -``` - -分组后排序示例: - -```sql -SELECT cust_name, COUNT(cust_address) AS addr_num -FROM Customers GROUP BY cust_name -ORDER BY cust_name DESC; -``` - -### HAVING - -> `HAVING` 用于对汇总的 `GROUP BY` 结果进行过滤。`HAVING` 要求存在一个 `GROUP BY` 子句。 - -`WHERE` 和 `HAVING` 可以在相同的查询中。 - -`HAVING` vs `WHERE`: - -- `WHERE` 和 `HAVING` 都是用于过滤。 -- `HAVING` 适用于汇总的组记录;而 `WHERE` 适用于单个记录。 - -使用 `WHERE` 和 `HAVING` 过滤数据示例: - -```sql -SELECT cust_name, COUNT(*) AS num -FROM Customers -WHERE cust_email IS NOT NULL -GROUP BY cust_name -HAVING COUNT(*) >= 1; -``` - ---- - -**(以下为 DDL 语句用法)** - -## 七、数据定义 - -> DDL 的主要功能是定义数据库对象(如:数据库、数据表、视图、索引等)。 - -### 数据库(DATABASE) - -#### 创建数据库 - -```sql -CREATE DATABASE test; -``` - -#### 删除数据库 - -```sql -DROP DATABASE test; -``` - -#### 选择数据库 - -```sql -USE test; -``` - -### 数据表(TABLE) - -#### 创建数据表 - -**普通创建** - -```sql -CREATE TABLE user ( - id int(10) unsigned NOT NULL COMMENT 'Id', - username varchar(64) NOT NULL DEFAULT 'default' COMMENT '用户名', - password varchar(64) NOT NULL DEFAULT 'default' COMMENT '密码', - email varchar(64) NOT NULL DEFAULT 'default' COMMENT '邮箱' -) COMMENT='用户表'; -``` - -**根据已有的表创建新表** - -```sql -CREATE TABLE vip_user AS -SELECT * FROM user; -``` - -#### 删除数据表 - -```sql -DROP TABLE user; -``` - -#### 修改数据表 - -**添加列** - -```sql -ALTER TABLE user -ADD age int(3); -``` - -**删除列** - -```sql -ALTER TABLE user -DROP COLUMN age; -``` - -**修改列** - -```sql -ALTER TABLE `user` -MODIFY COLUMN age tinyint; -``` - -**添加主键** - -```sql -ALTER TABLE user -ADD PRIMARY KEY (id); -``` - -**删除主键** - -```sql -ALTER TABLE user -DROP PRIMARY KEY; -``` - -### 视图(VIEW) - -> 视图是基于 SQL 语句的结果集的可视化的表。**视图是虚拟的表,本身不存储数据,也就不能对其进行索引操作**。对视图的操作和对普通表的操作一样。 - -视图的作用: - -- 简化复杂的 SQL 操作,比如复杂的连接。 -- 只使用实际表的一部分数据。 -- 通过只给用户访问视图的权限,保证数据的安全性。 -- 更改数据格式和表示。 - -#### 创建视图 - -```sql -CREATE VIEW top_10_user_view AS -SELECT id, username -FROM user -WHERE id < 10; -``` - -#### 删除视图 - -```sql -DROP VIEW top_10_user_view; -``` - -### 索引(INDEX) - -> 通过索引可以更加快速高效地查询数据。用户无法看到索引,它们只能被用来加速查询。 - -更新一个包含索引的表需要比更新一个没有索引的表花费更多的时间,这是由于索引本身也需要更新。因此,理想的做法是仅仅在常常被搜索的列(以及表)上面创建索引。 - -唯一索引:唯一索引表明此索引的每一个索引值只对应唯一的数据记录。 - -#### 创建索引 - -```sql -CREATE INDEX user_index -ON user (id); -``` - -#### 创建唯一索引 - -```sql -CREATE UNIQUE INDEX user_index -ON user (id); -``` - -#### 删除索引 - -```sql -ALTER TABLE user -DROP INDEX user_index; -``` - -### 约束 - -> SQL 约束用于规定表中的数据规则。 - -- 如果存在违反约束的数据行为,行为会被约束终止。 -- 约束可以在创建表时规定(通过 CREATE TABLE 语句),或者在表创建之后规定(通过 ALTER TABLE 语句)。 -- 约束类型 - - `NOT NULL` - 指示某列不能存储 NULL 值。 - - `UNIQUE` - 保证某列的每行必须有唯一的值。 - - `PRIMARY KEY` - NOT NULL 和 UNIQUE 的结合。确保某列(或两个列多个列的结合)有唯一标识,有助于更容易更快速地找到表中的一个特定的记录。 - - `FOREIGN KEY` - 保证一个表中的数据匹配另一个表中的值的参照完整性。 - - `CHECK` - 保证列中的值符合指定的条件。 - - `DEFAULT` - 规定没有给列赋值时的默认值。 - -创建表时使用约束条件: - -```sql -CREATE TABLE Users ( - Id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增Id', - Username VARCHAR(64) NOT NULL UNIQUE DEFAULT 'default' COMMENT '用户名', - Password VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '密码', - Email VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '邮箱地址', - Enabled TINYINT(4) DEFAULT NULL COMMENT '是否有效', - PRIMARY KEY (Id) -) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; -``` - ---- - -**(以下为 TCL 语句用法)** - -## 八、事务处理 - -不能回退 `SELECT` 语句,回退 `SELECT` 语句也没意义;也不能回退 `CREATE` 和 `DROP` 语句。 - -**MySQL 默认采用隐式提交策略(`autocommit`)**,每执行一条语句就把这条语句当成一个事务然后进行提交。当出现 `START TRANSACTION` 语句时,会关闭隐式提交;当 `COMMIT` 或 `ROLLBACK` 语句执行后,事务会自动关闭,重新恢复隐式提交。 - -通过 `set autocommit=0` 可以取消自动提交,直到 `set autocommit=1` 才会提交;`autocommit` 标记是针对每个连接而不是针对服务器的。 - -事务处理指令: - -- `START TRANSACTION` - 指令用于标记事务的起始点。 -- `SAVEPOINT` - 指令用于创建保留点。 -- `ROLLBACK TO` - 指令用于回滚到指定的保留点;如果没有设置保留点,则回退到 `START TRANSACTION` 语句处。 -- `COMMIT` - 提交事务。 - -事务处理示例: - -```sql --- 开始事务 -START TRANSACTION; - --- 插入操作 A -INSERT INTO `user` -VALUES (1, 'root1', 'root1', 'xxxx@163.com'); - --- 创建保留点 updateA -SAVEPOINT updateA; - --- 插入操作 B -INSERT INTO `user` -VALUES (2, 'root2', 'root2', 'xxxx@163.com'); - --- 回滚到保留点 updateA -ROLLBACK TO updateA; - --- 提交事务,只有操作 A 生效 -COMMIT; -``` - ---- - -**(以下为 DCL 语句用法)** - -## 九、权限控制 - -`GRANT` 和 `REVOKE` 可在几个层次上控制访问权限: - -- 整个服务器,使用 `GRANT ALL` 和 `REVOKE ALL`; -- 整个数据库,使用 ON database.\*; -- 特定的表,使用 ON database.table; -- 特定的列; -- 特定的存储过程。 - -新创建的账户没有任何权限。 - -账户用 `username@host` 的形式定义,`username@%` 使用的是默认主机名。 - -MySQL 的账户信息保存在 mysql 这个数据库中。 - -```sql -USE mysql; -SELECT user FROM user; -``` - -### 创建账户 - -```sql -CREATE USER myuser IDENTIFIED BY 'mypassword'; -``` - -### 修改账户名 - -```sql -UPDATE user SET user='newuser' WHERE user='myuser'; -FLUSH PRIVILEGES; -``` - -### 删除账户 - -```sql -DROP USER myuser; -``` - -### 查看权限 - -```sql -SHOW GRANTS FOR myuser; -``` - -### 授予权限 - -```sql -GRANT SELECT, INSERT ON *.* TO myuser; -``` - -### 删除权限 - -```sql -REVOKE SELECT, INSERT ON *.* FROM myuser; -``` - -### 更改密码 - -```sql -SET PASSWORD FOR myuser = 'mypass'; -``` - -## 十、存储过程和游标 - -> 存储过程可以看成是对一系列 SQL 操作的批处理,保存在数据库中。它就像我们编程语言中的函数一样,封装了我们的代码(PLSQL、T-SQL)。 - -### 存储过程优缺点 - -存储过程的优点: - -- 代码封装,保证了一定的安全性。 -- 让编程语言进行调用,提高代码复用。 -- 由于是预先编译,因此具有很高的性能。 -- 一个存储过程替代大量 T_SQL 语句 ,可以降低网络通信量,提高通信速率。 - -存储过程的缺点: - -- 由于不同数据库的存储过程语法几乎都不一样,十分难以维护(不通用)。 -- 业务逻辑放在数据库上,难以迭代。 - -### 使用存储过程 - -创建存储过程的要点: - -- 命令行中创建存储过程需要自定义分隔符,因为命令行是以 `;` 为结束符,而存储过程中也包含了分号,因此会错误把这部分分号当成是结束符,造成语法错误。 -- 包含 in、out 和 inout 三种参数。 -- 给变量赋值都需要用 select into 语句。 -- 每次只能给一个变量赋值,不支持集合的操作。 - -创建存储过程示例: - -```sql -DROP PROCEDURE IF EXISTS `proc_adder`; -DELIMITER ;; -CREATE DEFINER=`root`@`localhost` PROCEDURE `proc_adder`(IN a int, IN b int, OUT sum int) -BEGIN - DECLARE c int; - if a is null then set a = 0; - end if; - - if b is null then set b = 0; - end if; - - set sum = a + b; -END -;; -DELIMITER ; -``` - -使用存储过程示例: - -```sql -set @b=5; -call proc_adder(2,@b,@s); -select @s as sum; -``` - -### 游标 - -> 游标(CURSOR)是一个存储在 DBMS 服务器上的数据库查询,它不是一条 `SELECT` 语句,而是被该语句检索出来的结果集。在存储过程中使用游标可以对一个结果集进行移动遍历。 - -游标主要用于交互式应用,其中用户需要对数据集中的任意行进行浏览和修改。 - -使用游标的四个步骤: - -1. 声明游标,这个过程没有实际检索出数据; -2. 打开游标; -3. 取出数据; -4. 关闭游标; - -游标使用示例: - -```sql -DELIMITER $ -CREATE PROCEDURE getTotal() -BEGIN - DECLARE total INT; - -- 创建接收游标数据的变量 - DECLARE sid INT; - DECLARE sname VARCHAR(10); - -- 创建总数变量 - DECLARE sage INT; - -- 创建结束标志变量 - DECLARE done INT DEFAULT false; - -- 创建游标 - DECLARE cur CURSOR FOR SELECT id,name,age from cursor_table where age>30; - -- 指定游标循环结束时的返回值 - DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = true; - SET total = 0; - OPEN cur; - FETCH cur INTO sid, sname, sage; - WHILE(NOT done) - DO - SET total = total + 1; - FETCH cur INTO sid, sname, sage; - END WHILE; - - CLOSE cur; - SELECT total; -END $ -DELIMITER ; - --- 调用存储过程 -call getTotal(); -``` - -### 触发器 - -> 触发器可以视为一种特殊的存储过程。 -> -> 触发器是一种与表操作有关的数据库对象,当触发器所在表上出现指定事件时,将调用该对象,即表的操作事件触发表上的触发器的执行。 - -#### 触发器特性 - -可以使用触发器来进行审计跟踪,把修改记录到另外一张表中。 - -MySQL 不允许在触发器中使用 `CALL` 语句 ,也就是不能调用存储过程。 - -**`BEGIN` 和 `END`** - -当触发器的触发条件满足时,将会执行 `BEGIN` 和 `END` 之间的触发器执行动作。 - -> 🔔 注意:在 MySQL 中,分号 `;` 是语句结束的标识符,遇到分号表示该段语句已经结束,MySQL 可以开始执行了。因此,解释器遇到触发器执行动作中的分号后就开始执行,然后会报错,因为没有找到和 BEGIN 匹配的 END。 -> -> 这时就会用到 `DELIMITER` 命令(`DELIMITER` 是定界符,分隔符的意思)。它是一条命令,不需要语句结束标识,语法为:`DELIMITER new_delemiter`。`new_delemiter` 可以设为 1 个或多个长度的符号,默认的是分号 `;`,我们可以把它修改为其他符号,如 `$` - `DELIMITER $` 。在这之后的语句,以分号结束,解释器不会有什么反应,只有遇到了 `$`,才认为是语句结束。注意,使用完之后,我们还应该记得把它给修改回来。 - -**`NEW` 和 `OLD`** - -- MySQL 中定义了 `NEW` 和 `OLD` 关键字,用来表示触发器的所在表中,触发了触发器的那一行数据。 -- 在 `INSERT` 型触发器中,`NEW` 用来表示将要(`BEFORE`)或已经(`AFTER`)插入的新数据; -- 在 `UPDATE` 型触发器中,`OLD` 用来表示将要或已经被修改的原数据,`NEW` 用来表示将要或已经修改为的新数据; -- 在 `DELETE` 型触发器中,`OLD` 用来表示将要或已经被删除的原数据; -- 使用方法: `NEW.columnName` (columnName 为相应数据表某一列名) - -#### 触发器指令 - -> 提示:为了理解触发器的要点,有必要先了解一下创建触发器的指令。 - -`CREATE TRIGGER` 指令用于创建触发器。 - -语法: - -```sql -CREATE TRIGGER trigger_name -trigger_time -trigger_event -ON table_name -FOR EACH ROW -BEGIN - trigger_statements -END; -``` - -说明: - -- trigger_name:触发器名 -- trigger_time: 触发器的触发时机。取值为 `BEFORE` 或 `AFTER`。 -- trigger_event: 触发器的监听事件。取值为 `INSERT`、`UPDATE` 或 `DELETE`。 -- table_name: 触发器的监听目标。指定在哪张表上建立触发器。 -- FOR EACH ROW: 行级监视,Mysql 固定写法,其他 DBMS 不同。 -- trigger_statements: 触发器执行动作。是一条或多条 SQL 语句的列表,列表内的每条语句都必须用分号 `;` 来结尾。 - -创建触发器示例: - -```sql -DELIMITER $ -CREATE TRIGGER `trigger_insert_user` -AFTER INSERT ON `user` -FOR EACH ROW -BEGIN - INSERT INTO `user_history`(user_id, operate_type, operate_time) - VALUES (NEW.id, 'add a user', now()); -END $ -DELIMITER ; -``` - -查看触发器示例: - -```sql -SHOW TRIGGERS; -``` - -删除触发器示例: - -```sql -DROP TRIGGER IF EXISTS trigger_insert_user; -``` - -## 参考资料 - -- [《SQL 必知必会》](https://item.jd.com/11232698.html) -- [『浅入深出』MySQL 中事务的实现](https://draveness.me/mysql-transaction) -- [MySQL 的学习--触发器](https://www.cnblogs.com/CraryPrimitiveMan/p/4206942.html) -- [维基百科词条 - SQL](https://zh.wikipedia.org/wiki/SQL) -- [https://www.sitesbay.com/sql/index](https://www.sitesbay.com/sql/index) -- [SQL Subqueries](https://www.w3resource.com/sql/subqueries/understanding-sql-subqueries.php) -- [Quick breakdown of the types of joins](https://stackoverflow.com/questions/6294778/mysql-quick-breakdown-of-the-types-of-joins) -- [SQL UNION](https://www.w3resource.com/sql/sql-union.php) -- [SQL database security](https://www.w3resource.com/sql/database-security/create-users.php) -- [Mysql 中的存储过程](https://www.cnblogs.com/chenpi/p/5136483.html) diff --git a/docs/sql/mysql/README.md b/docs/sql/mysql/README.md deleted file mode 100644 index bb84426b..00000000 --- a/docs/sql/mysql/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# Mysql 教程 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716103611.png) - -## 📖 内容 - -### [Mysql 应用指南](mysql-quickstart.md) - -### [Mysql 工作流](mysql-workflow.md) - -### [Mysql 索引](mysql-index.md) - -> 关键词:`Hash`、`B 树`、`聚簇索引`、`回表` - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200715172009.png) - -### [Mysql 锁](mysql-lock.md) - -> 关键词:`乐观锁`、`表级锁`、`行级锁`、`意向锁`、`MVCC`、`Next-key 锁` - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716064947.png) - -### [Mysql 事务](mysql-transaction.md) - -> 关键词:`ACID`、`AUTOCOMMIT`、`事务隔离级别`、`死锁`、`分布式事务` - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716074533.png) - -### [Mysql 性能优化](mysql-optimization.md) - -### [Mysql 运维](mysql-ops.md) 🔨 - -### [Mysql 配置](mysql-config.md) 🔨 - ---- - -相关扩展知识: - -- [关系型数据库面试总结](https://github.com/dunwu/db-tutorial/blob/master/docs/sql/sql-interview.md) 💯 -- [SQL Cheat Sheet](https://github.com/dunwu/db-tutorial/blob/master/docs/sql/sql-cheat-sheet.md) -- [分布式事务基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/theory/distributed-transaction.md) - -## 📚 资料 - -- **官方** - - [Mysql 官网](https://www.mysql.com/) - - [Mysql 官方文档](https://dev.mysql.com/doc/refman/8.0/en/) - - [Mysql 官方文档之命令行客户端](https://dev.mysql.com/doc/refman/8.0/en/mysql.html) -- **书籍** - - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) - 经典,适合 DBA 或作为开发者的参考手册 - - [《MySQL 必知必会》](https://book.douban.com/subject/3354490/) - 适合入门者 -- **教程** - - [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) - - [runoob.com MySQL 教程](http://www.runoob.com/mysql/mysql-tutorial.html) - - [mysql-tutorial](https://github.com/jaywcjlove/mysql-tutorial) -- **更多资源** - - [awesome-mysql](https://github.com/jobbole/awesome-mysql-cn) - -## 🚪 传送 - -◾ 🏠 [DB-TUTORIAL 首页](https://github.com/dunwu/db-tutorial) ◾ 🎯 [我的博客](https://github.com/dunwu/blog) ◾ diff --git a/package.json b/package.json new file mode 100644 index 00000000..41b3b676 --- /dev/null +++ b/package.json @@ -0,0 +1,41 @@ +{ + "name": "db-tutorial", + "version": "1.0.0", + "private": true, + "scripts": { + "clean": "rimraf docs/.temp", + "start": "node --max_old_space_size=4096 ./node_modules/vuepress/cli.js dev docs", + "build": "node --max_old_space_size=4096 ./node_modules/vuepress/cli.js build docs", + "deploy": "bash scripts/deploy.sh", + "updateTheme": "yarn remove vuepress-theme-vdoing && rm -rf node_modules && yarn && yarn add vuepress-theme-vdoing -D", + "editFm": "node utils/editFrontmatter.js", + "lint": "markdownlint -r markdownlint-rule-emphasis-style -c docs/.markdownlint.json **/*.md -i node_modules", + "lint:fix": "markdownlint -f -r markdownlint-rule-emphasis-style -c docs/.markdownlint.json **/*.md -i node_modules", + "show-help": "vuepress --help", + "view-info": "vuepress view-info ./ --temp docs/.temp" + }, + "devDependencies": { + "dayjs": "^1.11.7", + "inquirer": "^9.1.4", + "json2yaml": "^1.1.0", + "markdownlint-cli": "^0.33.0", + "markdownlint-rule-emphasis-style": "^1.0.1", + "rimraf": "^4.1.2", + "vue-toasted": "^1.1.25", + "vuepress": "1.9.9", + "vuepress-plugin-baidu-tongji": "^1.0.1", + "vuepress-plugin-comment": "^0.7.3", + "vuepress-plugin-demo-block": "^0.7.2", + "vuepress-plugin-flowchart": "^1.4.2", + "vuepress-plugin-fulltext-search": "^2.2.1", + "vuepress-plugin-one-click-copy": "^1.0.2", + "vuepress-plugin-thirdparty-search": "^1.0.2", + "vuepress-plugin-zooming": "^1.1.7", + "vuepress-theme-vdoing": "^1.12.9", + "yamljs": "^0.3.0", + "markdownlint-cli": "^0.25.0", + "markdownlint-rule-emphasis-style": "^1.0.1", + "rimraf": "^3.0.1", + "vue-toasted": "^1.1.25" + } +} diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 00000000..c9848e74 --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,10 @@ +/** + * @see https://prettier.io/docs/en/options.html + * @see https://prettier.io/docs/en/configuration.html + */ +module.exports = { + tabWidth: 2, + semi: false, + singleQuote: true, + trailingComma: 'none' +} diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 3596f5ee..678bbf2d 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -2,45 +2,45 @@ # ------------------------------------------------------------------------------ # gh-pages 部署脚本 -# @author Zhang Peng +# @author Zhang Peng # @since 2020/2/10 # ------------------------------------------------------------------------------ # 装载其它库 -ROOT_DIR=$(cd `dirname $0`/..; pwd) +ROOT_DIR=$( + cd $(dirname $0)/.. + pwd +) # 确保脚本抛出遇到的错误 set -e -cd ${ROOT_DIR}/docs - # 生成静态文件 -npm install npm run build # 进入生成的文件夹 -cd dist +cd ${ROOT_DIR}/docs/.temp # 如果是发布到自定义域名 # echo 'www.example.com' > CNAME -git init -git checkout -b gh-pages && git add . -git commit -m 'deploy' - -# 如果发布到 https://.github.io/ -GITHUB_REPO=github.com/dunwu/db-tutorial.git -GITEE_REPO=gitee.com/turnon/db-tutorial.git if [[ ${GITHUB_TOKEN} && ${GITEE_TOKEN} ]]; then - echo "使用 token 公钥部署 gh-pages" - # ${GITHUB_TOKEN} 是 Github 私人令牌;${GITEE_TOKEN} 是 Gitee 私人令牌 - # ${GITHUB_TOKEN} 和 ${GITEE_TOKEN} 都是环境变量;travis-ci 构建时会传入变量 - git push --force --quiet "https://dunwu:${GITHUB_TOKEN}@${GITHUB_REPO}" gh-pages - git push --force --quiet "https://turnon:${GITEE_TOKEN}@${GITEE_REPO}" gh-pages + msg='自动部署' + GITHUB_URL=https://dunwu:${GITHUB_TOKEN}@github.com/dunwu/db-tutorial.git + GITEE_URL=https://turnon:${GITEE_TOKEN}@gitee.com/turnon/db-tutorial.git + git config --global user.name "dunwu" + git config --global user.email "forbreak@163.com" else - echo "使用 ssh 公钥部署 gh-pages" - git push -f git@github.com:dunwu/db-tutorial.git gh-pages - git push -f git@gitee.com:turnon/db-tutorial.git gh-pages + msg='手动部署' + GITHUB_URL=git@github.com:dunwu/db-tutorial.git + GITEE_URL=git@gitee.com:turnon/db-tutorial.git fi - -cd ${ROOT_DIR} +git init +git add -A +git commit -m "${msg}" +# 推送到github gh-pages分支 +git push -f "${GITHUB_URL}" master:gh-pages +git push -f "${GITEE_URL}" master:gh-pages + +cd - +rm -rf ${ROOT_DIR}/docs/.temp diff --git a/test.db b/test.db deleted file mode 100644 index 6fe996f5..00000000 Binary files a/test.db and /dev/null differ diff --git a/utils/config.yml b/utils/config.yml new file mode 100644 index 00000000..d387646b --- /dev/null +++ b/utils/config.yml @@ -0,0 +1,15 @@ +# 批量添加和修改、删除front matter配置文件 + +# 需要批量处理的路径,docs文件夹内的文件夹 (数组,映射路径:path[0]/path[1]/path[2] ... ) +path: + - docs # 第一个成员必须是docs + +# 要删除的字段 (数组) +delete: + # - tags + + # 要添加、修改front matter的数据 (front matter中没有的数据则添加,已有的数据则覆盖) +data: + # author: + # name: xugaoyi + # link: https://github.com/xugaoyi diff --git a/utils/editFrontmatter.js b/utils/editFrontmatter.js new file mode 100644 index 00000000..0998bf3d --- /dev/null +++ b/utils/editFrontmatter.js @@ -0,0 +1,98 @@ +/** + * 批量添加和修改front matter ,需要配置 ./config.yml 文件。 + */ +const fs = require('fs') // 文件模块 +const path = require('path') // 路径模块 +const matter = require('gray-matter') // front matter解析器 https://github.com/jonschlinkert/gray-matter +const jsonToYaml = require('json2yaml') +const yamlToJs = require('yamljs') +const inquirer = require('inquirer') // 命令行操作 +const chalk = require('chalk') // 命令行打印美化 +const readFileList = require('./modules/readFileList') +const { type, repairDate } = require('./modules/fn') +const log = console.log + +const configPath = path.join(__dirname, 'config.yml') // 配置文件的路径 + +main() + +/** + * 主体函数 + */ +async function main() { + const promptList = [ + { + type: 'confirm', + message: chalk.yellow('批量操作frontmatter有修改数据的风险,确定要继续吗?'), + name: 'edit' + } + ] + let edit = true + + await inquirer.prompt(promptList).then((answers) => { + edit = answers.edit + }) + + if (!edit) { + // 退出操作 + return + } + + const config = yamlToJs.load(configPath) // 解析配置文件的数据转为js对象 + + if (type(config.path) !== 'array') { + log(chalk.red('路径配置有误,path字段应该是一个数组')) + return + } + + if (config.path[0] !== 'docs') { + log(chalk.red("路径配置有误,path数组的第一个成员必须是'docs'")) + return + } + + const filePath = path.join(__dirname, '..', ...config.path) // 要批量修改的文件路径 + const files = readFileList(filePath) // 读取所有md文件数据 + + files.forEach((file) => { + let dataStr = fs.readFileSync(file.filePath, 'utf8') // 读取每个md文件的内容 + const fileMatterObj = matter(dataStr) // 解析md文件的front Matter。 fileMatterObj => {content:'剔除frontmatter后的文件内容字符串', data:{}, ...} + let matterData = fileMatterObj.data // 得到md文件的front Matter + + let mark = false + // 删除操作 + if (config.delete) { + if (type(config.delete) !== 'array') { + log(chalk.yellow('未能完成删除操作,delete字段的值应该是一个数组!')) + } else { + config.delete.forEach((item) => { + if (matterData[item]) { + delete matterData[item] + mark = true + } + }) + } + } + + // 添加、修改操作 + if (type(config.data) === 'object') { + Object.assign(matterData, config.data) // 将配置数据合并到front Matter对象 + mark = true + } + + // 有操作时才继续 + if (mark) { + if (matterData.date && type(matterData.date) === 'date') { + matterData.date = repairDate(matterData.date) // 修复时间格式 + } + const newData = + jsonToYaml + .stringify(matterData) + .replace(/\n\s{2}/g, '\n') + .replace(/"/g, '') + + '---\r\n' + + fileMatterObj.content + fs.writeFileSync(file.filePath, newData) // 写入 + log(chalk.green(`update frontmatter:${file.filePath} `)) + } + }) +} diff --git a/utils/modules/fn.js b/utils/modules/fn.js new file mode 100644 index 00000000..8d4635b7 --- /dev/null +++ b/utils/modules/fn.js @@ -0,0 +1,25 @@ +// 类型判断 +exports.type = function (o) { + var s = Object.prototype.toString.call(o) + return s.match(/\[object (.*?)\]/)[1].toLowerCase() +} + +// 修复date时区格式的问题 +exports.repairDate = function (date) { + date = new Date(date) + return `${date.getUTCFullYear()}-${zero(date.getUTCMonth() + 1)}-${zero(date.getUTCDate())} ${zero( + date.getUTCHours() + )}:${zero(date.getUTCMinutes())}:${zero(date.getUTCSeconds())}` +} + +// 日期的格式 +exports.dateFormat = function (date) { + return `${date.getFullYear()}-${zero(date.getMonth() + 1)}-${zero(date.getDate())} ${zero(date.getHours())}:${zero( + date.getMinutes() + )}:${zero(date.getSeconds())}` +} + +// 小于10补0 +function zero(d) { + return d.toString().padStart(2, '0') +} diff --git a/utils/modules/readFileList.js b/utils/modules/readFileList.js new file mode 100644 index 00000000..74a4eb6b --- /dev/null +++ b/utils/modules/readFileList.js @@ -0,0 +1,49 @@ +/** + * 读取所有md文件数据 + */ +const fs = require('fs') // 文件模块 +const path = require('path') // 路径模块 +const docsRoot = path.join(__dirname, '..', '..', 'docs') // docs文件路径 + +function readFileList(dir = docsRoot, filesList = []) { + const files = fs.readdirSync(dir) + files.forEach((item, index) => { + let filePath = path.join(dir, item) + const stat = fs.statSync(filePath) + if (stat.isDirectory() && item !== '.vuepress') { + readFileList(path.join(dir, item), filesList) //递归读取文件 + } else { + if (path.basename(dir) !== 'docs') { + // 过滤docs目录级下的文件 + + const filename = path.basename(filePath) + const fileNameArr = filename.split('.') + const firstDotIndex = filename.indexOf('.') + const lastDotIndex = filename.lastIndexOf('.') + + let name = null, + type = null + if (fileNameArr.length === 2) { + // 没有序号的文件 + name = fileNameArr[0] + type = fileNameArr[1] + } else if (fileNameArr.length >= 3) { + // 有序号的文件(或文件名中间有'.') + name = filename.substring(firstDotIndex + 1, lastDotIndex) + type = filename.substring(lastDotIndex + 1) + } + + if (type === 'md') { + // 过滤非md文件 + filesList.push({ + name, + filePath + }) + } + } + } + }) + return filesList +} + +module.exports = readFileList