diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 05e0570..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: "[BUG] XXX" -labels: '' -assignees: '' - ---- - -**Brief** - -**Is this a BUG REPORT or FEATURE REQUEST?** - -**What happened?** - -**What you expected to happen?** - - -**How to reproduce it (as minimally and precisely as possible)** - -**Your Eviroment versions** - - -**Anything else we need to know?** diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index aaae579..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: "[FEATURE] XXXX" -labels: '' -assignees: '' - ---- - -**What this PR does / why we need it?** - - -**Which issue this PR fixes?** -# - -**Special notes for your reviewer** - -**Release note** diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml deleted file mode 100644 index 6b992ec..0000000 --- a/.github/workflows/gh-pages.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: github pages - -on: - push: - branches: - - main # Set a branch to deploy - pull_request: - -jobs: - deploy: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - with: - submodules: true # Fetch Hugo themes (true OR recursive) - fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod - - - name: Setup Hugo - uses: peaceiris/actions-hugo@v2 - with: - hugo-version: 'latest' - # extended: true - - - name: Build - run: hugo --minify - - - name: Deploy - uses: peaceiris/actions-gh-pages@v3 - if: github.ref == 'refs/heads/main' - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./public diff --git a/.github/workflows/syc2gitee.yml b/.github/workflows/syc2gitee.yml deleted file mode 100644 index 6bb5c93..0000000 --- a/.github/workflows/syc2gitee.yml +++ /dev/null @@ -1,37 +0,0 @@ -# This is a basic workflow to help you get started with Actions - -name: sync-to-gitee - -# Controls when the workflow will run -on: - push: - pull_request: - branches: [ main ] - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - repo-sync: - env: - dst_key: ${{ secrets.GITEE_PRIVATE_KEY }} - dst_token: ${{ secrets.GITEE_TOKEN }} - #gitee_user: ${{ secrets.GITEE_USER }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - name: checkout - - - name: sync to gitee - uses: Yikun/hub-mirror-action@master - if: env.dst_key && env.dst_token - with: - src: 'github/${{ github.repository_owner }}' - dst: 'gitee/${{ github.repository_owner }}' - dst_key: ${{ secrets.GITEE_PRIVATE_KEY }} - dst_token: ${{ secrets.GITEE_TOKEN }} - # 如果是组织,指定组织即可,默认为用户 user - # account_type: org - static_list: ${{ github.event.repository.name }} - force_update: true diff --git a/themes/mini/exampleSite/layouts/.gitkeep b/.nojekyll similarity index 100% rename from themes/mini/exampleSite/layouts/.gitkeep rename to .nojekyll diff --git a/404.html b/404.html new file mode 100644 index 0000000..fc559a8 --- /dev/null +++ b/404.html @@ -0,0 +1,7 @@ +easystack
\ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index ee2ce87..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,100 +0,0 @@ -# How to contribute - -## Report issues - -A great way to contribute to the project is to send a detailed report when you encounter an issue. We always appreciate a well-written, thorough bug report and feature propose, and will thank you for it! - -### Issues format - -When reporting issues, refer to this format: - -- Is this a BUG REPORT or FEATURE REQUEST? -- What happened? -- What you expected to happen? -- How to reproduce it (as minimally and precisely as possible) -- Your Eviroment versions -- Anything else we need to know? - - -## Submit pull requests - -If you are a beginner and expect this project as the gate to open source world, this tutorial is one of the best -choices for you. Just follow the guidance and you will find the pleasure to becoming a contributor. - -### Step 1: Fork repository - -Before making modifications of this project, you need to make sure that this project have been forked to your own -repository. It means that there will be parallel development between this repo and your own repo, so be careful -to avoid the inconsistency between these two repos. - -### Step 2: Clone the remote repository - -If you want to download the code to the local machine, ```git``` is the best way: -``` -git clone https://your_repo_url/projectname.git -``` - -### Step 3: Develop code locally - -To avoid inconsistency between multiple branches, we SUGGEST checking out to a new branch: -``` -git checkout -b new_branch_name origin/master -``` -Then you can change the code arbitrarily. - -### Step 4: Push the code to the remote repository - -After updating the code, you should push the update in the formal way: -``` -git add . -git status (Check the update status) -git commit -m "Your commit title" -git commit --amend (Add the concrete description of your commit) -git push origin new_branch_name -``` - -### Step 5: Pull a request to repository - -In the last step, your need to pull a compare request between your new branch and development branch. After -finishing the pull request, the CI will be automatically set up for building test. - -### Pull requests format - -When submitting pull requests, refer to this format: - -- What this PR does / why we need it? -- Which issue this PR fixes? -- Special notes for your reviewer -- Release note - - -### How to new post - -All posts houses under 'content/posts', You can create a new post by creating a new .md file, prefixing it with anything you want. incremental numbers or date string will work well if you prefer. - -The post contains two parts, the header part and content part. the header part of this file which starts and ends with 3 horizontal hyphen(---) is called the front-matter and every post that we write needs to be a front matter included here: - -- title: This is the title of your post. -- date: This is the time that shows the posting time of your blog. The first portion is in the year-month-date format. You can edit the date and time as you wish. -- description: Add any description you like. -- author: Who is the post owner, we recommend to use your name or github id. -- categories: which category would this post be in. the posts will be listed by the category name. -- tags: Any tag would this post be under. -- draft: Is this post draft or not, the value is either 'true' or 'false'. - -You can refer to the following example and have your modification: - -``` ---- -title: "your post title" -date: 2021-10-09T15:50:43+08:00 -draft: false -description: "any description you would prefer to here" -author: "freesky-edward" -categories: [] -tags: ["container"] ---- -``` - -The content part can be any content following the markdown rules. - diff --git a/README.md b/README.md deleted file mode 100644 index ef56668..0000000 --- a/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# easystack.github.io - -## Introduction -This is a repository houses the techonical blogs for easystack company. - - -## Contribution - - -## Blog style - - -## License - - -## Get help - - diff --git a/archetypes/default.md b/archetypes/default.md deleted file mode 100644 index 00e77bd..0000000 --- a/archetypes/default.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: "{{ replace .Name "-" " " | title }}" -date: {{ .Date }} -draft: true ---- - diff --git a/categories/container/index.html b/categories/container/index.html new file mode 100644 index 0000000..275d279 --- /dev/null +++ b/categories/container/index.html @@ -0,0 +1,8 @@ +container | easystack
avatar

easystack

Open Computer To Futhre

\ No newline at end of file diff --git a/categories/container/index.xml b/categories/container/index.xml new file mode 100644 index 0000000..25977b7 --- /dev/null +++ b/categories/container/index.xml @@ -0,0 +1 @@ +container on easystackhttps://easystack.github.io/categories/container/Recent content in container on easystackHugo -- gohugo.iozhTue, 09 Nov 2021 15:50:43 +0800记一次kubelet 内存泄漏https://easystack.github.io/posts/%E8%AE%B0%E4%B8%80%E6%AC%A1kubelet-%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/Tue, 09 Nov 2021 15:50:43 +0800https://easystack.github.io/posts/%E8%AE%B0%E4%B8%80%E6%AC%A1kubelet-%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/背景 🔗一次生产环境中,发现 kubelet 内存达到上 GB 以上,这个不符合平常使用情况,首先想到的是内存泄漏,那么肯定使用 pprof 以及调查为什么会产生内存泄漏 profile 🔗基 \ No newline at end of file diff --git a/categories/index.html b/categories/index.html new file mode 100644 index 0000000..6d6a33e --- /dev/null +++ b/categories/index.html @@ -0,0 +1,9 @@ +easystack
avatar

easystack

Open Computer To Futhre

前端 (5) +CONTAINER (1) +PROBLEM (1)
\ No newline at end of file diff --git a/categories/index.xml b/categories/index.xml new file mode 100644 index 0000000..a657287 --- /dev/null +++ b/categories/index.xml @@ -0,0 +1 @@ +Categories on easystackhttps://easystack.github.io/categories/Recent content in Categories on easystackHugo -- gohugo.iozhWed, 06 Apr 2022 15:40:35 +0800前端https://easystack.github.io/categories/%E5%89%8D%E7%AB%AF/Wed, 06 Apr 2022 15:40:35 +0800https://easystack.github.io/categories/%E5%89%8D%E7%AB%AF/containerhttps://easystack.github.io/categories/container/Tue, 09 Nov 2021 15:50:43 +0800https://easystack.github.io/categories/container/problemhttps://easystack.github.io/categories/problem/Tue, 09 Nov 2021 15:50:43 +0800https://easystack.github.io/categories/problem/ \ No newline at end of file diff --git a/categories/problem/index.html b/categories/problem/index.html new file mode 100644 index 0000000..c791b86 --- /dev/null +++ b/categories/problem/index.html @@ -0,0 +1,8 @@ +problem | easystack
avatar

easystack

Open Computer To Futhre

\ No newline at end of file diff --git a/categories/problem/index.xml b/categories/problem/index.xml new file mode 100644 index 0000000..a96b75f --- /dev/null +++ b/categories/problem/index.xml @@ -0,0 +1 @@ +problem on easystackhttps://easystack.github.io/categories/problem/Recent content in problem on easystackHugo -- gohugo.iozhTue, 09 Nov 2021 15:50:43 +0800记一次kubelet 内存泄漏https://easystack.github.io/posts/%E8%AE%B0%E4%B8%80%E6%AC%A1kubelet-%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/Tue, 09 Nov 2021 15:50:43 +0800https://easystack.github.io/posts/%E8%AE%B0%E4%B8%80%E6%AC%A1kubelet-%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/背景 🔗一次生产环境中,发现 kubelet 内存达到上 GB 以上,这个不符合平常使用情况,首先想到的是内存泄漏,那么肯定使用 pprof 以及调查为什么会产生内存泄漏 profile 🔗基 \ No newline at end of file diff --git "a/categories/\345\211\215\347\253\257/index.html" "b/categories/\345\211\215\347\253\257/index.html" new file mode 100644 index 0000000..0f6a728 --- /dev/null +++ "b/categories/\345\211\215\347\253\257/index.html" @@ -0,0 +1,12 @@ +前端 | easystack
avatar

easystack

Open Computer To Futhre

\ No newline at end of file diff --git "a/categories/\345\211\215\347\253\257/index.xml" "b/categories/\345\211\215\347\253\257/index.xml" new file mode 100644 index 0000000..12cbd4e --- /dev/null +++ "b/categories/\345\211\215\347\253\257/index.xml" @@ -0,0 +1 @@ +前端 on easystackhttps://easystack.github.io/categories/%E5%89%8D%E7%AB%AF/Recent content in 前端 on easystackHugo -- gohugo.iozhWed, 06 Apr 2022 15:40:35 +0800TypeScript项目实践——在使用Typescript中,如何使用第三方依赖?https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%E7%AC%AC%E4%B8%89%E6%96%B9%E4%BE%9D%E8%B5%96/Wed, 06 Apr 2022 15:40:35 +0800https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%E7%AC%AC%E4%B8%89%E6%96%B9%E4%BE%9D%E8%B5%96/TypeScript 项目实践 🔗该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用TTypeScript项目实践——在使用Typescript中,如何正确的转换类型?https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E6%AD%A3%E7%A1%AE%E7%9A%84%E8%BD%AC%E6%8D%A2%E7%B1%BB%E5%9E%8B/Wed, 06 Apr 2022 15:35:35 +0800https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E6%AD%A3%E7%A1%AE%E7%9A%84%E8%BD%AC%E6%8D%A2%E7%B1%BB%E5%9E%8B/TypeScript 项目实践 🔗该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用TTypeScript项目实践——在使用TypeScript中,如何减少类型的重复定义?https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E5%87%8F%E5%B0%91%E7%B1%BB%E5%9E%8B%E7%9A%84%E9%87%8D%E5%A4%8D%E5%AE%9A%E4%B9%89/Wed, 06 Apr 2022 15:11:31 +0800https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E5%87%8F%E5%B0%91%E7%B1%BB%E5%9E%8B%E7%9A%84%E9%87%8D%E5%A4%8D%E5%AE%9A%E4%B9%89/TypeScript 项目实践 🔗该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用TTypeScript项目实践——在使用Typescript中,如何减少any使用?https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E5%87%8F%E5%B0%91any%E4%BD%BF%E7%94%A8/Wed, 06 Apr 2022 14:58:47 +0800https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E5%87%8F%E5%B0%91any%E4%BD%BF%E7%94%A8/TypeScript 项目实践 🔗该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用TTypeScript项目实践——在使用TypeScript中,配合VSCode可维护性、开发效率有哪些提升?https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E9%85%8D%E5%90%88vscode%E5%8F%AF%E7%BB%B4%E6%8A%A4%E6%80%A7%E5%BC%80%E5%8F%91%E6%95%88%E7%8E%87%E6%9C%89%E5%93%AA%E4%BA%9B%E6%8F%90%E5%8D%87/Wed, 06 Apr 2022 14:46:52 +0800https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E9%85%8D%E5%90%88vscode%E5%8F%AF%E7%BB%B4%E6%8A%A4%E6%80%A7%E5%BC%80%E5%8F%91%E6%95%88%E7%8E%87%E6%9C%89%E5%93%AA%E4%BA%9B%E6%8F%90%E5%8D%87/TypeScript 项目实践 🔗该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用T \ No newline at end of file diff --git a/config.yaml b/config.yaml deleted file mode 100644 index 53bb641..0000000 --- a/config.yaml +++ /dev/null @@ -1,84 +0,0 @@ -baseURL: https://easystack.github.io -languageCode: zh -title: easystack -theme: mini - -# Default content language, support en (English) / zh (Chinese) / nl (Dutch), default 'en' -defaultContentLanguage: zh - -# !!! exampleSite only, you may need to delete the line: `themesDir: ../../` -#themesDir: ../../ - -hasCJKLanguage: true -permalinks: - posts: /posts/:title/ - -googleAnalytics: "" -disqusShortname: "" - -# Hugo Configure Markup -# More info: https://gohugo.io/getting-started/configuration-markup# -markup: - highlight: - guessSyntax: true - style: emacs - tableOfContents: - endLevel: 3 - ordered: false - startLevel: 2 - -# Social links in footer, support github,twitter,stackoverflow,facebook -social: - # e.g. - github: easystack - #twitter: your-github-link - #stackoverflow: your-github-link - # facebook: your-facebook-link - - -# Site parameters -params: - # Site Author - author: easystack - # Author biography - bio: Open Computer To Futhre - # Site Description, used in HTML meat - description: easystack's Blog - - - ########################################### - # Optional Configuration - ########################################### - - # To enable RSS, you could set `enableRSS: true`, default is `true` - enableRSS: true - # To enable comments, you may need to set `disqusShortname` - enableComments: true - # To enable comments, you may need to set `googleAnalytics` - enableGoogleAnalytics: false - # To enable table of content, you could set `showToc: true`, default is `false` - showToc: true - # To hidden powerBy message in the page footer, you could set: `showPowerBy: false`, default is `true` - showPowerBy: true - # To enable math typesetting , you could set `math: true`, default is `false` - math: false - # To hidden post summary in home page, you could set `hiddenPostSummaryInHomePage: true`, default is `false` - hiddenPostSummaryInHomePage: false - # Website copy write, default: '© Copyright 2021 ❤️ {params.author}' - copyright: '' - - # Extra links in navigation - links: - ## e.g. - # - name: Project - # path: /project - - # You can put your custom css and js to `static` directory, or use remote css and js files which start with `http://` or `https://` - customCSS: - ## e.g. - # - css/custom.css # local css in `static/css/custom.css` - # - https://example.com/custom.css # remote css - customJS: - ## e.g. - # - js/custom.js # local js in `static/js/custom.js` - # - https://example.com/custom.js # remote js \ No newline at end of file diff --git "a/content/posts/TypeScript\351\241\271\347\233\256\345\256\236\350\267\265\342\200\224\342\200\224\345\234\250\344\275\277\347\224\250TypeScript\344\270\255\357\274\214\345\246\202\344\275\225\345\207\217\345\260\221\347\261\273\345\236\213\347\232\204\351\207\215\345\244\215\345\256\232\344\271\211\357\274\237.md" "b/content/posts/TypeScript\351\241\271\347\233\256\345\256\236\350\267\265\342\200\224\342\200\224\345\234\250\344\275\277\347\224\250TypeScript\344\270\255\357\274\214\345\246\202\344\275\225\345\207\217\345\260\221\347\261\273\345\236\213\347\232\204\351\207\215\345\244\215\345\256\232\344\271\211\357\274\237.md" deleted file mode 100644 index da66d0d..0000000 --- "a/content/posts/TypeScript\351\241\271\347\233\256\345\256\236\350\267\265\342\200\224\342\200\224\345\234\250\344\275\277\347\224\250TypeScript\344\270\255\357\274\214\345\246\202\344\275\225\345\207\217\345\260\221\347\261\273\345\236\213\347\232\204\351\207\215\345\244\215\345\256\232\344\271\211\357\274\237.md" +++ /dev/null @@ -1,115 +0,0 @@ ---- -title: "TypeScript项目实践——在使用TypeScript中,如何减少类型的重复定义?" -date: 2022-04-06T15:11:31+08:00 -draft: false -description: "" -author: "前端团队" -categories: ["前端"] -tags: ["前端","TypeScript"] ---- - -# TypeScript 项目实践 - -该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用TypeScript的同行提供参考,高效正确的使用TypeScript。 - -## 在使用TypeScript中,如何减少类型的重复定义? - -后台管理系统,每个模块大致就是列表、详情、表单和一些操作。在定义Typescript的时候,往往会重复定义一些类似的结构。如何减少类型的重复定义呢? - -### 模型已经完整定义,使用部分字段作为的新的类型 - -在定义列表的展示信息和详情的展示信息的时候,已经定义了一个比较完善的类。之后再去处理创建、编辑之类的操作可能只需要其中几个参数。 - -```typescript - -// 用户列表的信息 -class Uesr { - id: string; - name: string; - email: string; - createTime: string; -} -``` -例如:用户列表展示需要显示的信息比较完整,但是添加用户的时候可能只需要name、email。如果我们能把这两个属性摘出来就行了。内置的 Pick 类型,就可以完美解决我们的问题。 - -```typescript -// 添加用户 -function create(formValue: Pick) { - // todo: -} - -// 为什么用 pick?我们看下Pick的实现原理 -// 意思是选取指定一组属性,并组成一个新的类型 -type Pick = { - [P in K]: T[P]; -}; -``` -或者,当我们创建的时候,表单中除了 User.id,其他都是必填项。但是重新定义一个新的不包含id的类型的话,又显得有些重复了。但是用 Pick 的话,需要写的 keys 又太多了。这个时候就需要用内置 Omit 来处理了。 - -```typescript -// 添加用户 -function create(formValue: Omit) { - // todo: -} - -// 我们来看为什么 Omit 能满足我们的需求 -// Omit的原理就是从类型 T 中剔除 K 中的所有属性。 -// 此处的 keyof any 而不是 keyof T, 我也是有点不明白,就去查了一下。简单来说就是社区选择的结果. -// 不明白的可以看 https://github.com/Microsoft/TypeScript/issues/30825 -type Omit = Pick>; - -// 这里牵扯到 Exclude 类型,这里就先介绍下吧。 -// 表示提取存在于 T,但不存在于 U 的类型组成的联合类型。 -type Extract = T extends U ? T : never; -``` -注意:此处 keyof any 得到的是 string | nuber | symbol , 因为类型 key 的类型只能为 string | nuber | symbol 。 - -### 使用方法的参数类型,作为函数的返回值 - -某些时候我们在某个函数上定义的参数类型,可能是跟某些参数或者某些值的类型是相同的,这个时候我们就可以直接提取此函数的参数类型。具体实现就是使用 Parameters 类型。 - -例如,我们我们定义了一个格式化返回数据的函数,函数中我们定义了参数类型。然后我们的请求接口返回的数据与此相同。这个时候就能直接使用格式化函数的参数类型。 - -```typescript -// 格式化返回数据的函数 -function formatData(data: { - id: string; - name: string; - e_mail: string; - create_time: string; -}) { - // ... -} - -// 获取列表信息 -function getPersonLists(): Parameters[0][] { - // ... -} -``` - -我们通过下图可以看出, T0 此时的类型就是 formatData 中参数 data 的类型。 -![](/images/typescript/047a512a-57fa-48df-8a8c-946908bdec40.png) -[在线示例](https://www.typescriptlang.org/play?#code/FAMwrgdgxgLglgewgAhAgTgWwIYwCK7YAUAJoQFzIDewyycJlAzjOnBAOYDctyE2mAKbNW7br0EB9HHAA2Itpx50o6QbinwhCsTwC+ASmrJedAPRnkAOhsm9wYDACeAB0HIAKgAZkAXmQACtjoAoIwguhMADzObgggqBg4+IQAfADaXgC6wEA) - -### 将已存在的类型,组合成新类型 -在项目中,有些是需要集合其他几个类型或者类型中的部分参数而成的新类型。 - -```typescript -// 函数中的传参可能是需要多个类型中的参数,并且有些是可选项。 -function create(options: Pick - & Partial> - & Pick -) { - // ... -} - -// Partial 表示 -``` - -代码中的 create 参数,需要 User 类型中的 name、email,需要 Department 中的 department、project,也需要 UserRole 中的 role、type。我们用 & 和 内置类型组合成一个新的类型。 - - - -### 总结 - -在写 Typescript 中,合理使用内置的类型,可以有效的减少重复定义类型,减少无意义的代码。当然,内置类型肯定是不限于我们所介绍的这些以及使用场景。并且我们当内置类型满足不了我们的需求的时候,我们也可以自定义类型。 \ No newline at end of file diff --git "a/content/posts/TypeScript\351\241\271\347\233\256\345\256\236\350\267\265\342\200\224\342\200\224\345\234\250\344\275\277\347\224\250TypeScript\344\270\255\357\274\214\351\205\215\345\220\210VSCode\345\217\257\347\273\264\346\212\244\346\200\247\343\200\201\345\274\200\345\217\221\346\225\210\347\216\207\346\234\211\345\223\252\344\272\233\346\217\220\345\215\207\357\274\237 .md" "b/content/posts/TypeScript\351\241\271\347\233\256\345\256\236\350\267\265\342\200\224\342\200\224\345\234\250\344\275\277\347\224\250TypeScript\344\270\255\357\274\214\351\205\215\345\220\210VSCode\345\217\257\347\273\264\346\212\244\346\200\247\343\200\201\345\274\200\345\217\221\346\225\210\347\216\207\346\234\211\345\223\252\344\272\233\346\217\220\345\215\207\357\274\237 .md" deleted file mode 100644 index 3aebc77..0000000 --- "a/content/posts/TypeScript\351\241\271\347\233\256\345\256\236\350\267\265\342\200\224\342\200\224\345\234\250\344\275\277\347\224\250TypeScript\344\270\255\357\274\214\351\205\215\345\220\210VSCode\345\217\257\347\273\264\346\212\244\346\200\247\343\200\201\345\274\200\345\217\221\346\225\210\347\216\207\346\234\211\345\223\252\344\272\233\346\217\220\345\215\207\357\274\237 .md" +++ /dev/null @@ -1,205 +0,0 @@ ---- -title: "TypeScript项目实践——在使用TypeScript中,配合VSCode可维护性、开发效率有哪些提升?" -date: 2022-04-06T14:46:52+08:00 -draft: false -description: "" -author: "前端团队" -categories: ["前端"] -tags: ["前端","TypeScript"] ---- - -# TypeScript 项目实践 - -该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用TypeScript的同行提供参考,高效正确的使用TypeScript。 - -## 在使用TypeScript中,配合VSCode可维护性、开发效率有哪些提升? - - 本文主要总结我们在开发过程遇到代码维护性、开发效率方面比较典型的问题,以及使用TypeScript带来的改善。 - -### 友好的类型提示、错误检测 - -在维护一个项目经常遇到各自各样问题,但最常用的解决办法肯定有一种是: - -> console.log 输出看下参数(或者下个断点) - - -分析下为什么总要看参数,原因一般也就这几种: - -> 代码不是我写的,搞不清楚接口参数。 - -> 很久之前的写的,接口参数定义忘记了。 - -> Javascript就这样,不下断点,能知道参数最终结构啊!(这是被坑过的。。。js自由的代价,代码不执行到当前行,永远是可变的数据结构) - -例如,这样一段JavaScript代码: - -``` javascript -const createModal = options => { - //... -} -``` - -在通常业务开发中很难看到清楚options参数结构描述,即使有,在多个版本迭代后也可能变了不准确,如何解决这些问题呐?在正确的使用TypeScript后,这些大部分可以得到改善,比如上面的代码使用TypeScript实现: - -```TypeScript -interface Options { - title?: string; - content?: string; - onOK: ()=> void; - onCancel: ()=> void; -} - -const createModal = (options: Options)=> { - //... -} -``` - -通过阅读上面的代码,可以清楚的了解这个方法的参数结构,以及是否必传、参数值类型等信息,再配合VSCode可以得到这样的使用体验: -![](/images/typescript/4651df33-64c9-4728-8e74-59cc13f9785b.gif) - -而且在TypeScript中还有枚举之类特性,可以让常量定义提示更准确,修改更便利,在编码过程中变量的值、函数入参出参数、对象的成员,可以快速、清楚知道,对开发效率的提高,体验过都会离不开。 - -### 友好的代码调用关系树,以及IDE代码重构工具支持 - -``` TypeScript -// 1.公共方法 -const createForm = options => { - options.fields.forEach(field=>{ - if(field.inputType === 'text'){ - // TODO ... - } - - if(field.inputType === 'radio'){ - // TODO: ... - } - }) -} - -createForm({ fields:[{inputType: 'text'}] }); -createForm({ fields:[{inputType: 'radio'}] }); - - -// 2.业务接口变动 - -function fetchUserInfo(){ - // TODO: ajax request server - - const responseJson = { - username: 'test1', - realname: 'test1_nickname' - }; - - return Promise.resolve(responseJson) -} - -// calls -var user = await fetchUserInfo(); -var realname = user.realname; -``` -像上面的示例代码中的两段代码,第一段如果要增强或者优化公共方法的options ,第二段出现需求变更导致接口数据调整,在前端代码中需要做的改动很简单但量却很大的,常规手段就是查找替换,不熟悉代码的人要改动就很难评估影响。在TypeScript中可以怎么解决这样的问题,首页要正确的声明类型,然后在使用VSCode更改代码可以这样: - -![](/images/typescript/4651df33-64c9-4728-8e74-59cc13f9785b.gif) -![](/images/typescript/bc25b3d4-f0b4-4814-936e-2f7aaab54e79.gif) - -> 快速一键改名,变量、方法、 - -> 参数变更,快速检测相关引用通知错误提示 - -> 快速查找方法引用 - -有这些功能支持后,优化重构代码的便利可以大大提升,可以随着业务变化对代码进行一定范围的重构,便于提高代码质量。 - -### 针对大量功能相似但又有区别抽象能力 -![](/images/typescript/dd3b9cf9-7983-409e-8cb0-2db028ca87a9.png) -![](/images/typescript/dd9133f3-d2c5-4b6b-9f02-bc9693cc233e.png) - -像这样一个管理功能,增删改查很常见,在一团队里实现的人不同就有不同的设计,比如这样: - -``` TypeScript -// coder a ... -class ListAComponent { - list=[]; - query(){ - // TODO: validate filter ... - - // TODO: fetch data ... - - // TODO: update list ... - } -} - -// coder b ... -class ListBComponent { - data=[]; - search(){ - // TODO: validate filter ... - - // TODO: fetch data ... - - // TODO: update list ... - } -} - -// coder c ... -class ListCComponent { - dataSet=[]; - load(){ - // TODO: validate filter ... - - // TODO: fetch data ... - - // TODO: update list ... - } -} - -// ... -``` -每个人都有自己的实践,其中的逻辑也有很多相似,这样容易造成一个结果,维护一段代码就要熟悉一种风格,在团队成员快速成长的过程中更是会不断变化着,但解决这种问题的办法很早就有了,为什么很少有人用呐,JavaScript缺少个关键字抽象以及配套的IDE支持,不过TypeScript中实现了,下面看一下用TypeScript怎么解决上面的问题以及能减少重复逻辑: - -首先,我们根据上面的列表业务进行抽象定义公共逻辑,并定义抽象方法为列表不同部分预留需要实现接口; - -```TypeScript -abstract class ListComponent { - list = new Array(); - loading = false; - - onSearch(): Promise { - // validate filter - if (!this.checkFilter(this.filter)) { - return Promise.resolve(); - } - - this.loading = true; - - // fetch data - return this - .search(this.filter) - .then((rows) => { - // update list - this.list = rows; - this.loading = false; - }) - .catch(error => { - // update list (error handle) - this.list = []; - this.loading = false; - this.toast(error.message); - }) - - } - - toast(message: string) { - // TODO: toast message - }; - abstract filter: Filter; - protected abstract checkFilter(filter: Filter): boolean; - protected abstract search(filter: Filter): Promise; -} -``` -然后,列表去继承功能并实现接口,像下面这样: -![](/images/typescript/bffb582b-14b9-45d0-a596-eaa43e303782.gif) - -配合IDE可以快速得到我们需要做的事情,只关注实现不同的业务逻辑、定义显示的数据结构、以及显示部分,同时提升项目可维护性,类似的功能可以得到一样的接口就能更快速的理解逻辑。 - -### 总结 -以上是我在前端代码维护中遇到问题,以及在使用正确使用TypeScript定义类型后如何解决,这里强调下要正确使用,因为TypeScript的一个特色是JavaScript类型的超集,它可以编译成纯JavaScript ,所以完全是可以在按照JavaScript的方式去用,我想这本意是让使用者快速适应,但是实际应用中会造成另一个问题没有类型,按照JavaScript的方式编码有时候会没有类型声明,会产生很多any 、unknown 类型,这个一定要在项目禁止,最次也要减少影响范围,不然上面的一切美好可能都会成为虚妄。 \ No newline at end of file diff --git "a/content/posts/TypeScript\351\241\271\347\233\256\345\256\236\350\267\265\342\200\224\342\200\224\345\234\250\344\275\277\347\224\250Typescript\344\270\255\357\274\214\345\246\202\344\275\225\344\275\277\347\224\250\347\254\254\344\270\211\346\226\271\344\276\235\350\265\226\357\274\237.md" "b/content/posts/TypeScript\351\241\271\347\233\256\345\256\236\350\267\265\342\200\224\342\200\224\345\234\250\344\275\277\347\224\250Typescript\344\270\255\357\274\214\345\246\202\344\275\225\344\275\277\347\224\250\347\254\254\344\270\211\346\226\271\344\276\235\350\265\226\357\274\237.md" deleted file mode 100644 index 960c572..0000000 --- "a/content/posts/TypeScript\351\241\271\347\233\256\345\256\236\350\267\265\342\200\224\342\200\224\345\234\250\344\275\277\347\224\250Typescript\344\270\255\357\274\214\345\246\202\344\275\225\344\275\277\347\224\250\347\254\254\344\270\211\346\226\271\344\276\235\350\265\226\357\274\237.md" +++ /dev/null @@ -1,89 +0,0 @@ ---- -title: "TypeScript项目实践——在使用Typescript中,如何使用第三方依赖?" -date: 2022-04-06T15:40:35+08:00 -draft: false -description: "" -author: "前端团队" -categories: ["前端"] -tags: ["前端","TypeScript"] ---- - -# TypeScript 项目实践 - -该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用TypeScript的同行提供参考,高效正确的使用TypeScript。 - -## 在使用Typescript中,如何使用第三方依赖? - -在实际使用Typescript项目开发过程中, 我们经常会引入第三方依赖包,且使用npm来管理第三方依赖包的安装管理, 如下: - -```bash -// 以 ng-zorro-antd 为例, npm 安装 -$ npm install ng-zorro-antd --save -``` -```typescript -// 项目中直接 import 导入即可 -import { NzModalModule } from 'ng-zorro-antd/modal'; -``` -然而是否所有的包都可以通过上述方式直接 import 导入? 当然不行。 - -ng-zorro-antd 是基于 TypeScript 开发,包含完整的声明文件。比如我们安装 ng-zorro-antd 后可以发现 node_modules/ ng-zorro-antd中包含 ng-zorro-antd.d.ts 或者package.json 中包含 typings 属性指定了声明文件。 -![](/images/typescript/1e267ba5-e9a0-454c-bdf2-69e9aab09c0e.png) - -但实际上有很多包并不是基于TypeScript开发的,自然也不会导出Typescript声明文件。由于历史原因,仍然有很多基于javascript开发的第三方依赖,比如jquery。或者即使有些包基于TypeScript开发的, 但如果没有导出声明文件, 也是没用的,直接导入的话就会编译报错, 告诉我们没有找到 相关模块或者声明。 - -```typescript -// 以jquery为例 -npm install jquery --save - -// 项目代码 -import * as $ from 'jquery'; - -// Could not find a declaration file for module 'jquery'. '/root/my-app/node_modules/jquery/dist/jquery.js' implicitly has an 'any' type. -// Try `npm i --save-dev @types/jquery` if it exists or add a new declaration (.d.ts) file containing `declare module 'jquery';` -``` - -我们会发现, 即使已经安装了jQuery, 依然会编译报错,因为编译器查找到定位到 jquery module 的声明文件。 那么Typescript 模块依赖声明文件又是如何查找的? - -### Typescript 如何查找第三方依赖声明? - -TypeScript是模仿Node.js运行时的解析策略来在编译阶段定位模块定义文件, 查找依赖声明文件,按照上层递归 node_modules 查找,具体来说就是: - -* node_modules/jquery/jquery.d.ts -* node_modules/jquery/package.json types/typings属性定义 -* 如果找不到,则会去 node_modules 中的@types(默认情况,目录可以修改)目录下去寻找对应包名的模块声明文件 -* 向上层 node_modules 目录继续查找 - -如果都没有查找到, 则编译提示报错,针对安装的依赖包中缺少声明文件情况,社区提供两种解决方案: - -* 手动下载 jquery 的声明文件(优先推荐使用, 需要注意版本问题) -* Typescript 2以前也会使用 typings search 查找依赖声明 (不常用) -* 自行定义一份*.d.ts 声明文件,并将 jquery 声明为 module - -### 下载第三方依赖包@types声明 - -至于自定义 .d.ts 文件 declare module,其实非常简单,直接在src文件夹下定义 typings/jquery/jquery.d.ts 文件。 - -然后在jquery.d.ts文件中编写声明: - -```typescript -declare module 'jquery' { - // export function init(): void - // 亦或者扩展模块声明, 比如我们在业务代码中 jquery 新增了一个方法 - // export function func(): number -} - -// 或者可直接简写 -declare module 'jquery'; -``` - -修改 tsconfig.json 配置文件,typeRoots 属性中新增一个添加 typings 目录,这样如果node_modules/@types 中找不到声明文件,就会去src/typings中查找。 - -```json -{ - "typesRoot": [ - "node_modules/@types", - "src/typings" - ] -} -``` -然后大工告成。 \ No newline at end of file diff --git "a/content/posts/TypeScript\351\241\271\347\233\256\345\256\236\350\267\265\342\200\224\342\200\224\345\234\250\344\275\277\347\224\250Typescript\344\270\255\357\274\214\345\246\202\344\275\225\345\207\217\345\260\221any\344\275\277\347\224\250\357\274\237.md" "b/content/posts/TypeScript\351\241\271\347\233\256\345\256\236\350\267\265\342\200\224\342\200\224\345\234\250\344\275\277\347\224\250Typescript\344\270\255\357\274\214\345\246\202\344\275\225\345\207\217\345\260\221any\344\275\277\347\224\250\357\274\237.md" deleted file mode 100644 index 96d3ceb..0000000 --- "a/content/posts/TypeScript\351\241\271\347\233\256\345\256\236\350\267\265\342\200\224\342\200\224\345\234\250\344\275\277\347\224\250Typescript\344\270\255\357\274\214\345\246\202\344\275\225\345\207\217\345\260\221any\344\275\277\347\224\250\357\274\237.md" +++ /dev/null @@ -1,138 +0,0 @@ ---- -title: "TypeScript项目实践——在使用Typescript中,如何减少any使用?" -date: 2022-04-06T14:58:47+08:00 -draft: false -description: "" -author: "前端团队" -categories: ["前端"] -tags: ["前端","TypeScript"] ---- - -# TypeScript 项目实践 - -该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用TypeScript的同行提供参考,高效正确的使用TypeScript。 - -## 在使用Typescript中,如何减少any使用? -本文主要是总结项目中实践经验,在Typescript中减少any的使用。 - -### 联合类型代替any -当一个对象需要指定多个类型时,使用了any作为类型,为了减少any的使用,我们可以用联合类型(|)组成多类型来代替any。 -例如:定义一个格式化数字类型和日期类型formatTime方法,方法的入参类型就可以使用联合类型(number | Date) - -```typescript -// bad -formatTime(time: any) { - if (typeof time === 'number') { - time = new Date(time * 1000); - } - const year = time.getFullYear(); - const month = time.getMonth() + 1; - const day = time.getDate(); - const hours = time.getHours(); - const minu = time.getMinutes(); - const second = time.getSeconds(); - return `${year}-${month}-${day} ${hours}:${minu}:${second}`; -} - -// good 使用联合类型代替any -function formatTime(time: number | Date) { - ... -} - -// 传入Date类型参数 -formatTime(new Date()); -// 传入number类型参数 -formatTime(1600000000); -``` - -### & 集合生成一个新类型代替any -当一个对象需要指定的类型缺失属性时,使用了any作为类型,为了减少any的使用我们可以用集合(&)组成新类型来代替any。 -例如:已有一个createInstance方法,需定义一个迁移方法migrationInstance,入参类型与createInstance相似,只是多一个oldID属性,我们可以用集合(&)组成含有oldID的新类型。 - -```typescript -export class ApiInstanceService { - - // 定义createInstance - createInstance(params: { - name: string; - cpu: number; - member: number; - ... - }) { - ... - } - - // bad - migrationInstance (params: any) { - ... - } - - // good 将createInstance的参数类型通过集合方式生成包含oldID的新类型 - migrationInstance(params: Parameters[0] & { oldID: string }) { - ... - } -} -``` - -### ReturnType获取函数返回值的类型 -当一个对象类型为函数返回值的类型,并且无法定位到这个类型时,使用了any作为类型,为了减少any的使用。 -例如:调用了setInterval方法,当前无法确定返回值的类型,使用ReturnType获取返回值类型。 - -```typescript -// bad -let interval: any; - -// good -let interval!: ReturnType; - -interval = setInterval(() => { -... -}, 3000); -``` - -### as指定类型 -当一个对象中的属性在当前类型中匹配不到时,使用了any作为类型,为了减少any的使用 -我们用as指定类型来代替any。(注意:指定的类型必须是正确的类型) -例如:定义searchChange方法,为了获取e.target.value,将参数e.target通过as指定 -HTMLInputElement类型获取value。 - -```typescript -// 触发input调用searchChange方法 - - -// bad -searchChange(e: any) { - const value = e.target.value; - ..... -} - -// good e.target通过as指定HTMLInputElement类型获取value -searchChange(e: Event) { - const value = (e.target as HTMLInputElement).value; - ..... -} -``` - -### 可索引接口代替any -当一个对象中的属性存在不确定性,无法进行固定约束,使用了any作为类型,为了减少any的使用,我们可以用索引接口代替any. -例如:TemplateOptions中的formlyItemStyle属性用于指定样式数据,例如:{marginBottom: '0'} formlyItemStyle属性无法固定约束类型,我们使用索引接口代替any。 - -``` typescript -// TemplateOptions中的formlyItemStyle属性用于指定样式,例如:{marginBottom: '0'} - -// bad -interface TemplateOptions { - //... - formlyItemStyle?: any; -} - -// 索引接口代替any -interface TemplateOptions { - ... - formlyItemStyle?: { [key: string]: string }; -} -``` - -### 总结 -代码中应减少any使用,可增加代码的可读性和可维护性,频繁使用 any 不利于项目的后续维护, -会出现本可避免的bug。 \ No newline at end of file diff --git "a/content/posts/TypeScript\351\241\271\347\233\256\345\256\236\350\267\265\342\200\224\342\200\224\345\234\250\344\275\277\347\224\250Typescript\344\270\255\357\274\214\345\246\202\344\275\225\346\255\243\347\241\256\347\232\204\350\275\254\346\215\242\347\261\273\345\236\213\357\274\237.md" "b/content/posts/TypeScript\351\241\271\347\233\256\345\256\236\350\267\265\342\200\224\342\200\224\345\234\250\344\275\277\347\224\250Typescript\344\270\255\357\274\214\345\246\202\344\275\225\346\255\243\347\241\256\347\232\204\350\275\254\346\215\242\347\261\273\345\236\213\357\274\237.md" deleted file mode 100644 index 0f9ddb3..0000000 --- "a/content/posts/TypeScript\351\241\271\347\233\256\345\256\236\350\267\265\342\200\224\342\200\224\345\234\250\344\275\277\347\224\250Typescript\344\270\255\357\274\214\345\246\202\344\275\225\346\255\243\347\241\256\347\232\204\350\275\254\346\215\242\347\261\273\345\236\213\357\274\237.md" +++ /dev/null @@ -1,150 +0,0 @@ ---- -title: "TypeScript项目实践——在使用Typescript中,如何正确的转换类型?" -date: 2022-04-06T15:35:35+08:00 -draft: false -description: "" -author: "前端团队" -categories: ["前端"] -tags: ["前端","TypeScript"] ---- - -# TypeScript 项目实践 - -该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用TypeScript的同行提供参考,高效正确的使用TypeScript。 - -## 在使用Typescript中,如何正确的转换类型? -类型断言是用来告诉TypeScript值的详细类型, 在实际项目中合理使用可以减少我们的工作量, 下面结合项目中的一些使用场景, 概括下类型断言在实际项目中的常见用途。 - -### 使用断言定义空对象 - -例如项目中存在用户对象数据, 当定义用户对象的时候, 可以使用interface的方式 - -```typescript -interface IUser { - id: number; - name: string; -} - -const user: IUser = {}; -``` - -但是user对象一开始是个空的对象, 接口请求成功后, 才会给对象赋值, 这时候在编译时就会报错, 提示缺少id, name属性 - -![](/images/typescript/cc3b43c3-a842-416a-914e-d7e075d507ba.png) - -这种情况就可以利用类型断言, 满足初始化是空对象, 后续再赋值 - -```typescript -interface IUser { - id: number; - name: string; -} - -const user: IUser = {} as IUser; -user.id = 1; -user.name = 'admin'; -或者可以使用尖括号<>语法 - - -interface IUser { - id: number; - name: string; -} - -const user: IUser = {}; -user.id = 1; -user.name = 'Xiao Ming'; -``` - -### 使用双重断言指定DOM事件类型 - -例如在项目中经常需要处理 DOM 事件或元素 - -```typescript -handler(event: Event): void { - const mouseEvent = event as MouseEvent; -} - -function handler(event: Event) { - let element = event as HTMLElement; // Error: 'Event' 和 'HTMLElement' 中的任何一个都不能赋值给另外一个 -} - -function handler(event: Event) { - let element = event as unknown as HTMLElement; -} -``` - -在上面的例子中,若直接使用 event as HTMLElement 会报错,因为 Event 和 HTMLElement 互相不兼容。若使用双重断言,则可以打破「要使得 A 能够被断言为 B,只需要 A 兼容 B 或 B 兼容 A 即可」的限制,将任何一个类型断言为任何另一个类型。若使用了这种双重断言,那么十有八九是非常错误的,它很可能会导致运行时错误。除非迫不得已,千万别用双重断言。 - -### 使用常量断言as const约束对象和定义枚举对象 - -项目中在给接口赋值时, 如果某个接口里面的所有属性只能被读取而不能修改, 可以通过readonly - - -interface IUser { - readonly id: number; - readonly name: string; -} - -const user: IUser = { - id: 1, - name: 'admin', -}; - -user.name = 'member'; -成功限制name属性不允许修改 -![](/images/typescript/0ecb6eb6-e14d-4029-a162-3966ce2593c9.png) - -如果user对象里面属性比较多且数据结构复杂, 这时候限制每个属性都得处理, typescript支持在类型后面加上as const, 这样相当于给对象里面每个属性添加上了readonly - -as const 用作枚举 - -```typescript -enum roleEnum { - admin = 'Admin', - member = 'Member', -} -const role1 = roleEnum.admin; -const roles = { - admin: 'Admin', - member: 'Member', -} as const -type ValueOf = T[keyof T]; -type RoleEnumType = ValueOf; -const role2: RoleEnumType = roles.admin; -``` - -枚举将限制为必要的输入集,而常量字符串允许使用不属于您的逻辑的字符串。 这样可以确保在输入数据时不会因输入域中不存在的内容而出错,并且还提高了代码的可读性。 - -### type与类型断言 -例如用户数据存在id和name两个属性, 当获取对象值的时候, - -```typescript -type keys = 'id' | 'name'; - -const user = { - id: 1, - name: 'admin', -}; - -const getValue = (key: any) => { - return user[key]; -}; -``` -提示错误 -![](/images/typescript/aba66a1c-98fc-41dc-b9ce-6f7944484181.png) - -除了使用类型约束方式, 还可以利用类型断言解决(该方式常用于第三方库的callback, 对返回值类型没有约束的情况) - -```typescript -type keys = 'id' | 'name'; - -const user = { - id: 1, - name: 'admin', -}; - -const getValue = (key: any) => { - return user[key as keys]; -}; -``` \ No newline at end of file diff --git "a/content/posts/containerd\347\256\200\344\273\213.md" "b/content/posts/containerd\347\256\200\344\273\213.md" deleted file mode 100644 index 2ccf6aa..0000000 --- "a/content/posts/containerd\347\256\200\344\273\213.md" +++ /dev/null @@ -1,390 +0,0 @@ ---- -title: "Containerd 简介" -date: 2021-10-09T15:50:43+08:00 -draft: false -description: "containerd 入门篇" -author: "yylt" -categories: [] -tags: ["container"] ---- - -# 历史 - -- Docker Daemon从最初集成在docker命令中(1.11版本前), -- 独立成单独二进制程序(1.11版本开始)2016.12 - - containerd是容器技术标准化之后的产物,为了能够兼容OCI标准,从Docker Daemon剥离 - - containerd分v0.2.x , v1.x版本 - - v0.2.x 由docker daemon集成 - - v1.x 开始支持oci标准 - - containerd项目地址: github.com/docker/containerd -- containerd捐献给cncf 2017.03 - - 项目地址修改为 github.com/containerd/containerd - - v1.0.0正式版发布 2017.12 支持OCI接口 - - v1.1.0正式版 2018.4 支持CRI接口 - - v1.2.0正式版 2018.10 runtime v2稳定 -- shim - - shim是一个真实运行的容器的真实载体 - -# 架构 - -- 组件如图 - -![1](/images/containerd-arch.png) - -# glossary - -- 服务: 提供grpc服务 - -- sandbox:一种特殊容器,主要作用准备容器隔离的环境,包括网络,io目录,cgroup parent - -- container:业务容器,每个进程都由一个task 管理 - -- shim:管理器(containerd) 和 runtime的粘和层,因为runc是二进制文件执行,无法常驻内存,使用shim一方面方便管理,另一方面扩展runtime; shim管理 sandbox + container(s) - -- task:代表进程+io - -- runtime v2: 可以理解 task 管理器 - - -# 服务 - -- 每个服务都是plugin方式加入,包括不限于image, runtime, grpc server等 -- plugin特点 - - 插件URI为 Type+ID,如 `io.containerd.snapshotter.v1.overlayfs`,io.containerd.snapshotter.v1是Type,overlayfs是ID - - 插件类型之间有依赖, 从下向上排序: - - ContentPlugin, SnapshotPlugin - - MetadataPlugin - - GCPlugin,RuntimePlugin,DiffPlugin - - ServicePlugin - - InternalPlugin,GRPCPlugin -- 数据结构 - -```mermaid -classDiagram - class Meta{ - +'ocispec.Platform' Platforms - +'map[string]string' Exports - +string[] Capabilities - } - class Plugin{ - +Registration Registration - +Meta Meta - +string[] Requires - +object Config - +Err() error - +Instance() (object, error) - } - class Registration{ - +string Type - +string ID - +string[] Requires - +object Config - +Init(context) Plugin - +URI() string - } - - -``` - -## diff 插件 - -- 用于snaphost的操作 -- diff/walking/plugin目录, DiffPlugin类型,依赖MetadataPlugin -- 提供两个方法 - - `Compare` 根据提供的 lower,upper 信息,得到oscispec.Descriptor - - `Apply` 从镜像压缩文件中读取内容,并按序执行多个处理器,主要是校验,解压,自定义插件等 - -## scheduler - -- 调度器,gc/scheduler目录, GCPlugin类型,依赖MetadataPlugin -- 配置信息 - - PauseThreshold: 暂停阈值,平均每秒2%的暂停时间,使用该值计算下次等待周期,默认为0.02, 最大值0.5 - - DeletionThreshold:删除阈值,在删除多少次之后至少保证发生gc调度,0表示不会被删除触发,默认0 - - MutationThreshold: 突变阈值 - - ScheduleDelay:调度延迟,当发生手动触发或者阈值触发后,延迟多久才开始GC,默认0ms - - StartupDelay: 慢启动,默认100ms -- goroutine - - gc实际是执行content.GarbageCollect - -## monitor - -- 容器的起停状态同步, InternalPlugin类型,依赖ServicePlugin -- goroutine - - 过滤标签 `containerd.io/restart.status`的container - - 对比container和预期状态是否相符 - - 重新执行start或stop - -## cgroup - -- 进程oom监听和指标统计,在 metrics/cgroup 目录, TaskMonitorPlugin类型 -- 作用:task的指标和oom监听 -- 主要是shim 在使用该模块,shim需要监听 task 是否发生OOM - -## runtime v1 - -- 容器进程管理器,, RuntimePlugin类型,依赖MetadataPlugin -- 配置 - - shim: shim二进制文件地址 - - runtime: 被shim使用的runtime二进制文件地址 - - no_shim: 直接调用runtime,跳过shim - - shim_debug: 是否打开shim调试 - -## runtime v2 - -- 容器进程管理器, RuntimePluginV2类型,依赖MetadataPlugin -- 在[client](https://github.com/containerd/containerd/blob/34fb8d8967595292adce04b53b12ee33499cd6b8/client.go#L102)中被配置为默认的runtime -- 初始化时会执行以下内容 - - 创建目录 {root/state}/io.containerd.runtime.v2.task - - 读取 {state}/io.containerd.runtime.v2.task目录 - - 恢复shim 管理 - -``` -tips: -fifo文件ro打开:当无写端,若是nonblocking模式,则返回,否则阻塞 -fifo文件wo打开:当无读端,返回ENXIO,若读端关闭时写数据,则返回SIGPIPE -fifo文件rw打开:总是成功 -``` - -## snapshot - -- 容器的rootfs管理器,SnapshotPlugin类型 -- linux 平台 - - snapshots/{native, overlay, devmapper,btrfs} - - containerd/{aufs,zfs} -- windows平台 - - snapshots/{lcow, windows} - -```bash -# man 8 mount -# references: https://wiki.archlinux.org/title/Overlay_filesystem -tips: -1 overlay 挂载 -mount -t overlay overlay -o lowerdir=/lower:/lower2,upperdir=/upper,workdir=/work /merged -// upperdir 一般是可写层 -// workdir 空目录, -2 devmapper 挂载 -mount -t ext4 /dev/mapper/xx {target} -3 native 挂载 -mount -t bind {src} {dst} -4 btrfs -mount -t btrfs -``` - -# services - -- 提供rpc服务和数据库操作等 -- services/目录下,具体有以下的内容 - -## container, image, lease, namespace 服务: - -- local.go: 是service Plugin类型,对数据库的操作,以及event的发送 -- service.go: 是grpc Plugin类型,提供grpc服务,并使用local内相同接口 - -## content 服务 - -- service.go: 提供grpc服务,CRUD content数据 -- contentserver : content具体操作(content) - -## task 服务 - -- local.go: 调用runtime v2同名接口,包括容器创建,开始,结束,删除,终端,状态等信息 -- server.go: 提供grpc服务 - -## diff 服务: - -- local.go: 使用diff插件 -- service.go: 提供grpc服务 - -## event服务: - -- service.go: 提供grpc服务,包括转发,推送,订阅功能 - -## introspection 服务: - -- service.go: 提供grpc服务,列举注册的plugin - -## healthcheck 服务: - -- service.go: 提供 grpc服务,检查指定服务的健康 - -## version服务: - -- service.go: 提供 grpc服务,检查containerd version - -## opt服务: - -- 配置目录 root - - 添加 {root}/bin 到环境变量 PATH中 - - 添加 {root}/lib 到环境变量LD\_LIBRARY\_PATH中 - -## server - -- 基础plugin,扩展插件的初始化和服务启动 - - 流程 - - 注册 ContentPlugin插件 - - 注册 MetadataPlugin插件 - - 注册 proxy插件(ContentPlugin或SnapshotPlugin类型) - - 注册DiffPlugin插件 (stream processor类型),目前主要是处理 加密的镜像 - - 监听event类型 - - /tasks/oom - - /images/ - -```toml -version = 2 -root = "/var/lib/mycontainerd" #持久化目录 -state = "/var/run/mycontainerd" #非持久化目录 -oom_score = 0 # containerd的oom分数 -... -[proxy_plugins] - [proxy_plugins.stargz] - type = "snapshot" #支持snapshot和content类型 - address = "/run/containerd-stargz-grpc/containerd-stargz-grpc.sock" -[stream_processors] - [stream_processors."io.containerd.ocicrypt.decoder.v1.tar.gzip"] - accepts = ["application/vnd.oci.image.layer.v1.tar+gzip+encrypted"] - returns = "application/vnd.oci.image.layer.v1.tar+gzip" - path = "/usr/local/bin/ctd-decoder" - -[cgroup] - path = "" # containerd 所在路径,去除 ‘/sys/fs/cgroup/'前缀的 -[plugins] - [plugins."io.containerd.gc.v1.scheduler"] - pause_threshold = 0.02 - deletion_threshold = 0 - mutation_threshold = 100 - schedule_delay = "0s" - startup_delay = "100ms" - [plugins."io.containerd.internal.v1.restart"] - interval = "10s" #restart插件,遍历container的周期 - [plugins."io.containerd.metadata.v1.bolt"] - content_sharing_policy = "shared" #content策略,shared则所有不同ns的都可以访问,另一种是isolated - [plugins."io.containerd.monitor.v1.cgroups"] - no_prometheus = false - [plugins."io.containerd.runtime.v1.linux"] - shim = "containerd-shim" - runtime = "runc" - runtime_root = "" - no_shim = false - shim_debug = false - [plugins."io.containerd.runtime.v2.task"] - platforms = ["linux/amd64"] #镜像支持的平台类型 - [plugins."io.containerd.service.v1.diff-service"] - default = ["walking"] #walking是内置的diff插件 -``` - -# cri - -- GRPCPlugin类型,依赖ServicePlugin -- 作用: 提供cri接口, cri 常见配置 - -```toml -[plugins.cri] - disable_tcp_service = true - stream_server_address = "127.0.0.1" # unix 监听地址 - stream_server_port = "0" # unix 监听端口,0是随机分配 - stream_idle_timeout = "4h0m0s" #idle连接超时时间 - enable_selinux = false - sandbox_image = "hub.easystack.io/captain/pause-amd64:3.0" - stats_collect_period = 10 #单位秒,snapshot状态收集 - systemd_cgroup = false #只适用io.containerd.runtime.v1.linux - enable_tls_streaming = false # 是否支持tls - max_container_log_line_size = 16384 #单行日志最大byte - disable_cgroup = false # 当rootless 运行时,需要设置为true - disable_apparmor = false - restrict_oom_score_adj = false - netns_mounts_under_state_dir = false #将net ns设置到{state}/netns目录下,而不是默认的/run/netns下 - max_concurrent_downloads = 3 - unset_seccomp_profile = "" #seccomp配置文件 - disable_proc_mount = false #禁用k8s ProcMount, 对于k8s <= 1.11时,必须设置为true - - tolerate_missing_hugetlb_controller = true #1.5+,对于k8s <1.19,不支持配置hugetlb - disable_hugetlb_controller = true #1.5+, 适用于rootless+cgroupv2+systemd - ignore_image_defined_volumes = true #1.5+,创建container时,忽略image中定义的volume - [plugins.cri.x509_key_pair_streaming] #stream tls配置 - tls_cert_file = "" - tls_key_file = "" - [plugins.cri.image_decryption] # 解密镜像,参考项目 github.com/containerd/imgcrypt - key_model = none - [plugins.cri.containerd] - snapshotter = "overlayfs" #使用何种snapshot - default_runtime_name = "runc" #默认runtime,必须出现在后续的列表中 - no_pivot = false #只适用io.containerd.runtime.v1.linux - disable_snapshot_annotations = false #stargz snapshot需要配置 - discard_unpacked_layers = false #当执行content GC时,是否回收已经unpack的content - [plugins.cri.containerd.default_runtime] #被弃用 - privileged_without_host_devices = false # 设置特权模式下时,是否映射主机上设备到容器内 - [plugins.cri.containerd.untrusted_workload_runtime] #被弃用 - privileged_without_host_devices = false - [plugins.cri.containerd.runtimes] - [plugins.cri.containerd.runtimes.runc] - runtime_type = "io.containerd.runc.v2" #将被用于解析为具体二进制文件名 - [plugins.cri.containerd.runtimes.runc.options] - # NoPivotRoot disables pivot root when creating a container. - NoPivotRoot = false - # NoNewKeyring disables new keyring for the container. - NoNewKeyring = false - # 设置shim cgroup目录,默认是和containerd一起 - ShimCgroup = "" - # 设置shim log user id. - IoUid = 0 - # 设置shim log group id. - IoGid = 0 - # 设置二进制文件名,否则将使用type字段 - BinaryName = "" - # runc的目录,默认为 {state}/runc. - Root = "" - # criu 二进制文件路径. - CriuPath = "" - # 使用cgroup的systemd 驱动,注意需要和kubelet中同样设置 - SystemdCgroup = true - # CriuImagePath is the criu image path - CriuImagePath = "" - # CriuWorkPath is the criu work path. - CriuWorkPath = "" - privileged_without_host_devices = false - [plugins.cri.containerd.runtimes.gvisor] - runtime_type = "io.containerd.gvisor.v2" - pod_annotations =[""] - container_annotations = [""] - base_runtime_spec="" # 文件绝对路径,且内容为oci runtime文件格式 - [plugins.cri.containerd.runtimes.firecracker] - runtime_type = "io.containerd.firecracker.v2" - [plugins.cri.cni] - bin_dir = "/opt/cni/bin" - conf_dir = "/etc/cni/net.d" - max_conf_num = 1 #若有多个cni配置文件,则按照此值调用多个cni配置文件 - [plugins.cri.registry] - config_path= /xxx # 目录,若配置该值,则以下内容被忽略 - [plugins.cri.registry.configs] #被弃用,使用config_path代替,在1.7后被移除 - [plugins.cri.registry.configs."hub.easystack.io"] - [plugins.cri.registry.configs."hub.easystack.io".tls] - insecure_skip_verify = true - [plugins.cri.registry.mirrors] #被弃用,使用config_path代替,在1.7后被移除 - [plugins.cri.registry.mirrors."docker.io"] - endpoint = ["https://registry-1.docker.io"] - [plugins.cri.registry.mirrors."hub.easystack.io"] - endpoint = ["https://hub.easystack.io"] -``` - -- 关于仓库目录配置方式 - -``` -config_path= /opt/mycontainerd/hosts - -# 目录内容 -. -├── docker.io -│   └── hosts.toml -└── k8s.gcr.io - └── hosts.toml - -# hosts.toml内容 -server = "https://k8s.gcr.io" - -[host."https://k8s.gcr.io"] - skip_verify = true - capabilities = [ "pull", "resolve", "push" ] - ca: "" #文件路径或文件内容 -``` - - diff --git a/content/posts/memoryLeakContainerd.md b/content/posts/memoryLeakContainerd.md deleted file mode 100644 index a4bf138..0000000 --- a/content/posts/memoryLeakContainerd.md +++ /dev/null @@ -1,234 +0,0 @@ ---- -title: "记一次kubelet 内存泄漏" -date: 2021-11-09T15:50:43+08:00 -draft: false -description: "kubelet memroy leak" -author: "yylt" -categories: ["container","problem"] -tags: ["containerd","kubelete"] ---- - - -# 背景 - -一次生产环境中,发现 kubelet 内存达到上 GB 以上,这个不符合平常使用情况,首先想到的是内存泄漏,那么肯定使用 pprof 以及调查为什么会产生内存泄漏 - -# profile - -## 基础 -- cpu `/debug/pprof/profile`,主要分析耗时和优化算法,得到 profile 文件 -- heap: `/debug/pprof/heap`,查看活动对象的内存分配情况,得到 profile 文件 -- threadcreate: `/debug/pprof/threadcreate`, 线程创建概况报告程序中导致创建新的操作系统线程的部分 -- goroutine: 报告所有当前 goroutine 的堆栈信息,**没有 profile 文件** -- trace: `/debug/pprof/trace` 当前程序的执行跟踪,go tool trace 中使用 - -# kubelet -查看平台的内存使用情况,如下 -注:(以下数据非当时环境,是之后重新复现后取的,比发生泄漏环境要低的多) - -```shell -# top - PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND -31599 root 20 0 2877396 983.5m 66424 S 4.3 6.2 28:00.93 kubelet -``` - -kubelet 数据默认打开 pprof, 通过 [pprof](https://github.com/google/pprof) 拿到数据 , 通过以下命令方式 - -```shell -# pprof -tls_ca {cafile} -tls_key {keyfile} -tls_cert {certfile} - https://{host}:10250/debug/pprof/heap -``` - -拿到数据后,还可以拿到正常节点的数据做对比,如下 - -正常 kubelet 内存使用 -![](https://s2.loli.net/2022/04/21/SLKZQjG9Yvdx4Wk.png) - -不正常 kubelet 内存使用 -![](https://s2.loli.net/2022/04/21/jCefqQ7JARYrSno.png) - -## 基础 -描述下 kubelet 当前目录和会涉及的代码片段 -``` -pkg/kubelet/ -├── cri #cri接口 -├── server # http hanlder入口 -├── stats # 容器 cpu/memory,filesystem info 抓取 -├── kuberuntime # 桥接容器运行时 和 kubelet操作 -└── cm #container manager缩写,控制器内容包括 cgroup,topology,device等 -``` - -可以直接跨越到 cri/ 内查看 当前接口实现,未设置超时时间 -```go -func (r *remoteRuntimeService) ListContainerStats(filter *runtimeapi.ContainerStatsFilter) ([]*runtimeapi.ContainerStats, error) { - // Do not set timeout, because writable layer stats collection takes time. - // TODO(random-liu): Should we assume runtime should cache the result, and set timeout here? - ctx, cancel := getContextWithCancel() - defer cancel() - // 这里未设置超时时间 - resp, err := r.runtimeClient.ListContainerStats(ctx, &runtimeapi.ListContainerStatsRequest{ - Filter: filter, - }) -``` - -当前调用链大致如下 , 可以看到及时客户端退出,但是 kubelet 到 containerd 的连接还是会保持,直到拿到数据为止 -![](https://s2.loli.net/2022/04/26/bM1swGoBIDTx3gF.png) - -那么考虑就是如何复现这个问题,但通过测试代码 [1] 并未复现问题,这里其实一直是阻塞调查的地方,那么不妨换个思路,现继续挖下为什么 containerd 没有返回数据呢? - -到这里,其实可以发现 kubelet 都集中在 `stats.ListPodStats` 花费上,该调用会最终到 containerd 的 Stats 接口上,那么应该继续分析 containerd ,这应该是产生问题的根本原因 - -# containerd - -通过抓取 heap 和 goroutine 信息分析,而我们环境中 containerd 的 debug socket 已经打开,所以比较方便拿到 containerd profile 信息 - -```shell -# ctr pprof heap > heap.profile -# ctr pprof goroutines > goroutine.profile -``` - -拿到数据后,还可以拿到正常节点的数据做对比 - -正常 containerd 内存使用 -![](https://s2.loli.net/2022/04/21/e1gxopz2HduOiQM.png) - -不正常 containerd 内存使用 -![](https://s2.loli.net/2022/04/21/qSt6okRhXIEYWUZ.png) - -看到的是 SPDY 内存消耗较多,我们知道 SPDY 是 HTTP/2 前身,主要用于流式连接,当前主要是 接口 exec, attach, portfoward 在接口上,以下是简单对 exec 的代码理解 , 然后使用了 exec 确能稳定复现问题,复现代码如下 - -```sh -#!/bin/bash -i=0 -NUM=$1 -while [ "$i" -lt $NUM ]; do - (kubectl exec -i test-74cf75b654-gw5hk -- ls /opt)& - let "i += 1" -done -``` - -## exec 流程 - -流程需要从 kubelet 开始分析 , 通过前文中对 kubelet 目录的简单介绍,那么入口肯定是在 server 目录内,直接到主题 Exec,对应函数是 `getExec(request *restful.Request, response *restful.Response)` , 行为简单描述如下 - - 校验参数,通常 stdout/stderr 为 true,目前常用的是 `-it`,也就是需要配置 stdin 和 ttry 是否为 true - - 查询 pod 当前是否支持 exec - - 执行 GetExec 获取 url - - 创建代理,转发 apiserver 数据 stdin 和 stdout - -cri 服务 主要分为两部分,重点描述 ServerExec - - GetExec:创建 url 路由,增加 token `host:port/exec/{token}` - - ServerExec: 创建 task 和 process,并绑定标准输入和输出等 - -ServerExec -- 升级 http 为 SPDY stream,看上去 spdy 是比较重,因为要至少建立三个 goroutine `handler.waitForStreams(streamCh, expectedStreams, expired.C)` -- 执行 Exec (在 streamRuntime 内),等待 process 结束,或者上游退出 - -```go -//创建Task, task实际是 containerd/containerd/task.go 类型 -task, err := container.Task(ctx, nil) -if err != nil { - return nil, errors.Wrap(err, "failed to load task") -} -pspec := spec.Process -pspec.Args = opts.cmd -pspec.Terminal = opts.tty -if opts.tty { - oci.WithEnv([]string{"TERM=xterm"})(ctx, nil, nil, spec) -} - -volatileRootDir := c.getVolatileContainerRootDir(id) -var execIO *cio.ExecIO -// 创建process, 实际是 containerd/containerd/process.go 类型 -process, err := task.Exec(ctx, execID, pspec, - func(id string) (containerdio.IO, error) { - var err error - execIO, err = cio.NewExecIO(id, volatileRootDir, opts.tty, opts.stdin != nil) - return execIO, err - }, -) -// 获取 process 退出管道 -exitCh, err := process.Wait(ctx) -err := process.Start(ctx) - -// 将execIO 和 http 流绑定 -// 内部会创建协程 stdin ,stdout, waitGroup -attachDone := execIO.Attach(cio.AttachOptions{ - Stdin: opts.stdin, - Stdout: opts.stdout, - Stderr: opts.stderr, - Tty: opts.tty, - StdinOnce: true, - CloseStdin: func() error { - return process.CloseIO(ctx, containerd.WithStdinCloser) - }, -}) - -select { -case <-execCtx.Done(): -case exitRes := <-exitCh: -} -``` - -这里是阻塞在调用 exec 上,但是和 cpuAndMemoryStats 并无关系,如果是有关系,那么也只有在 shim 这一级,因为都要调用 shim 的 rpc 接口 - -# shim -当前 runc 使用的 shim 是 containerd-shim-runc-v2, 在内置的 shim server 上有一个方便调试的方式,发送 USER1 信号 可以获取 goroutines 信息,具体代码如下 - -```go -// runtime/v2/shim/shim_unix.go -func setupDumpStacks(dump chan<- os.Signal) { - signal.Notify(dump, syscall.SIGUSR1) -} -``` - -```sh -// 发送 USER1 信号 -kill -10 {pid} -// 获取containerd 日志,获取BEGIN goroutine stack dump 日志 -journalctl -eu containerd >contaienrd.log -``` - -查看 shim 的 goroutines 信息,因为和 ttrpc 有关,那直接找 ttrpc 信息吧,可以发现以下信息,这里显然不应该有那么多 goroutine hang 在 `vendor/github.com/containerd/ttrpc/server.go:444 ` 行,那么继续看下 ttrpc 有关实现呢 -``` -$ cat shim.goroutine.profile |grep ttrpc/server | sort |uniq -c |sort - 1 vendor/github.com/containerd/ttrpc/server.go:362 +0x149 - 1 vendor/github.com/containerd/ttrpc/server.go:404 +0x5ee - 1 vendor/github.com/containerd/ttrpc/server.go:431 +0x41a - 1 vendor/github.com/containerd/ttrpc/server.go:459 +0x6bd - 1 vendor/github.com/containerd/ttrpc/server.go:87 +0x107 - 2 vendor/github.com/containerd/ttrpc/server.go:127 +0x2a7 - 2 vendor/github.com/containerd/ttrpc/server.go:332 +0x2ce - 6 vendor/github.com/containerd/ttrpc/server.go:438 +0xf2 - 164 vendor/github.com/containerd/ttrpc/server.go:444 +0x245 - 169 vendor/github.com/containerd/ttrpc/server.go:434 +0x63f -``` - -## ttrpc -**reqCh, respCh, msgCh 均为非缓存 channel** - -服务端大致如下 -![](https://s2.loli.net/2022/04/21/AwBVbSm9kuL2U1h.png) - -- 每创建一个连接, 都会产生 2 个协程来处理会话 -- recv 协程: 从客户端读取数据,并校验合法性写入 Channel 中,交给 worker 来处理 -- worker 协程:收到请求后,**并发创建协程**调用注册的服务 - -1.0.1 版本 客户端大致如下 -![](https://s2.loli.net/2022/04/21/WjuCD8eSObodZwY.png) -- 和服务端类似,工作协程同时处理接收和发送请求, -- 重点是 **发送和接收是在一个协程中处理,并通过内部 waitCall 来同步数据** - -发生死锁的情况是 客户端 阻塞在 send 过程,此时因为无法处理返回的 Resp 信息,继而导致服务端的应答数据阻塞在 net write buffer 中 -什么情况会导致 send 阻塞,网络发送过程有以下情况 , 进程将数据拷贝到内核缓存区,之后由软中断发送出去,该控制写缓存大小为 tcp_wmem, 内核参数配置为 `4096 16384 4194304` - -针对上述导致死锁的情况,有一个相关 patch[2] 解决,该 patch 修改方式如下图 - -1.1.0 版本 客户端 -![](https://s2.loli.net/2022/04/21/Uc5GHopBs1brWDA.png) - -发送和接收 都通过不同的协程处理,不再出现竞争情况发生 - -# 参考 - -[1]. https://gist.github.com/yylt/0d3f2d554fa7eddd9cafe406ef0c9d75 -[2]. https://github.com/containerd/ttrpc/pull/94 diff --git "a/content/posts/\344\272\221\345\216\237\347\224\237Redis\345\256\236\350\267\265.md" "b/content/posts/\344\272\221\345\216\237\347\224\237Redis\345\256\236\350\267\265.md" deleted file mode 100644 index 4a758ba..0000000 --- "a/content/posts/\344\272\221\345\216\237\347\224\237Redis\345\256\236\350\267\265.md" +++ /dev/null @@ -1,480 +0,0 @@ ---- -title: "云原生Redis实践" -date: 2022-03-18T14:00:43+08:00 -draft: false -description: "Redis与容器实践探索" -author: "软件基础设施团队" -categories: [] -tags: ["container", "paas", "redis", "k8s"] ---- - -# **云原生Redis实践** - -## **Redis与容器实践探索** - -### **Redis云原生控制器设计** - -#### **什么是云原生控制器** - -**设计思想** - -云原生控制器是控制器设计模式在云原生场景下的变种,即将云原生技术与控制器模式相结合,形成基于云原语指定的视图进行执行模式。 - -云原生控制器一般存在于对云基础设施资源或云上软件基础设施资源的控制管理的场景。控制器与基础设施模型建立Observe关系,通过视图对基础设施模型进行CRUD操作,触发控制器执行对应操作,完成对基础设施资源的处理。 - - **K8S Operator** - -K8S Operator SDK是目前使用最广泛的云原生控制器框架,采用controller-runtime框架,集成了K8S Informer,对监听到的Model(CRD)CRUD事件进行了缓存处理,以便控制器能够高效处理回调请求。 - -![](/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.001.png) - -**通过K8S Operator实现统一云原生控制面** - -云原生控制面的统一可以减轻API管理带来的技术负债,利用K8S APIServer的横向扩展能力,定义控制器对应的GVR(GroupVersionResource),将抽象基础设施模型合并为具象的业务视图,统一API原语,实现控制面的统一。 - -![](/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.002.png) - -#### **业务控制面与业务数据面分离** - -在实际应用场景中,我们会将控制器与生产业务进行分离设计,分离设计可以带来诸多优势: - -- 可用域隔离,生产业务压力激增时,可以单独进行业务数据面维护,不会抢占控制器资源,导致控制器宕机。 -- 业务数据面基础资源不受部署介质限制,业务数据面所需要的硬件资源与业务控制面差别较大,不建议混合。 -- 在云上软件基础设施场景,业务数据面一般具备较强的弹性,控制器可以对底层平台进行水平伸缩(副本调节或容量扩缩,例如对IaaS层资源进行扩容,并触发K8S HPA),而不影响控制器本身高可用性。 -- 业务控制面与业务数据面可独立升级,实现解耦式进化。 - -![](/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.003.png) - -#### **通过Operator对云原生Redis实例进行业务层控制** - -在Redis容器化部署场景下,使用Operator作为控制器对Redis生命周期以及业务控制进行管理,是比较合适的方案。 - -控制器在操作容器化Redis实例时,可以通过暴露的SVC地址或Operator通过监听业务数据面对应CR的变更,访问对应SVC或在对应的业务数据面运行对应的Job,来对Redis业务进行管理: - -![](/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.004.png) - -**Redis实例的生命周期管理** - -Redis实例的生命周期包含实例的创建、删除、修改等过程,这些过程被设计为通过云原生控制器接管时,能够很大程度提升业务面的容错性,使状态机解耦。 - -当Redis创建事件进入Informer队列后,会随即被控制器取出,并创创建对应的K8S资源,并对应修改Redis CR Model的状态字段,完成生命周期管理。 - -![](/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.005.png) - -**Redis业务控制** - -业务控制通过在业务数据平面启动Job Pod的方式,非侵入式对Redis业务进行操作。 - -![](/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.006.png) - -#### **多控制器** - -采用K8S Operator作为云原生控制器,能够支持多控制器模式,我们可以通过在Operator中增加一组控制器对多个Model资源进行控制,实现Redis业务层面功能的扩展: - -![](/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.007.png) - - - -### **Redis云原生高可用架构模式** - -高可用和可拓展是我们考量Redis的两个重要指标,其中高可用在Redis运行时中保障业务运行时的稳定性以及数据的稳定性提供了了至关重要的作用,下面,我们以Redis部署形态为维度,对云原生场景下Redis的高可用方案实现进行阐述。 - -K8S 是一个容器编排系统,可以自动化容器应用的部署、扩展和管理。K8S 提供了一些基础特性: - -- 自动装箱 -- 服务发现与负载均衡 -- 自动化上线和回滚 -- 水平扩缩容 -- 存储编排 -- 自我修复 - -Redis 云原生的目标,主要有以下几个: - -- **资源的抽象和交付由 K8S 来完成,无需再关注具体机型。**在物理机时代我们需要根据不同机型上的 CPU 和内存配置来决定每个机型的机器上可以部署的 Redis 实例的数量。通过 Redis 云原生,我们只需要跟 K8s 声明需要的 CPU 和内存的大小,剩下的调度、资源供给、机器筛选由 K8s 来完成。 -- **节点的调度由 K8S 来完成。**在实际部署一个 Redis 集群时,为了保证高可用,需要让 Redis 集群的一些组件满足一定的放置策略。要满足放置策略,在物理机时代需要运维系统负责完成机器的筛选以及计算的逻辑,这个逻辑相对比较复杂。K8s 本身提供了丰富的调度能力,可以轻松实现这些放置策略,从而降低运维系统的负担。 -- **节点的管理和状态保持由 K8S 完成。**在物理机时代,如果某台物理机挂了,需要运维系统介入了解其上部署的服务和组件,然后在另外一些可用的机器节点上重新拉起新的节点,填补因为机器宕机而缺少的节点。如果由 K8s 来完成节点的管理和状态的保持,就可以降低运维系统的复杂度。 -- **标准化 Redis 的部署和运维的模式。**尽量减少人工介入,提升运维自动化能力,这是最重要的一点。 - -#### **Redis 架构模式** - -下面介绍一下云原生高可用支持的 Redis 架构模式。 - -##### **单机版Redis实例** - -默认情况下,每台Redis服务器都是主节点。 - -单机版限制: - -1. 内存容量有限 -1. 处理能力有限 -1. 无法高可用 - -![](/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.008.png) - -##### **读写分离版Redis实例** - -Redis 的复制(replication)功能允许用户根据一个 Redis 服务器来创建任意多个该服务器的复制品,其中被复制的服务器为主服务器(master),而通过复制创建出来的服务器复制品则为从服务器(slave)。 - -Master一般是用来做write,Slave一般是用来read。这样的结构可以有效的缓解系统的读写压力。 - -因为Master是负责write,那么就会出现主从数据的不同步,就需要通过sync,把Master的数据同步给Slave。 - -- 全量复制:slave刚接入,需要把master上的数据全部同步 -- 增量复制:slave,网络中断,超时,导致部分数据需要增量同步 - -优点: - -1. 支持主从复制,可以进行读写分离 -1. 为了分载Master的读操作压力,Slave服务器可以为客户端提供只读操作的服务,写服务仍然必须由Master来完成 -1. Slave同样可以接受其它Slaves的连接和同步请求,这样可以有效的分载Master的同步压力。 -1. Master Server是以非阻塞的方式为Slaves提供服务。所以在Master-Slave同步期间,客户端仍然可以提交查询或修改请求。 -1. Slave Server同样是以非阻塞的方式完成数据同步。在同步期间,如果有客户端提交查询请求,Redis则返回同步之前的数据 - -缺点: - -1. Redis不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的IP才能恢复。 -1. 主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性。 -1. Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。 - -![](/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.009.png) - -##### **哨兵版Redis实例** - -Redis Sentinel 是一个分布式系统中监控 Redis 主从服务器的监视器,并在主服务器下线时自动进行故障转移。其中三个特性: - -- ​ 监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。 -- ​ 提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。 -- ​ 自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作。 - -优点: - -1. 保证高可用 -1. 监控各个节点 -1. 自动故障迁移 - -缺点: - -1. 主从模式切换需要时间,会丢数据 -1. 需要维护sentinel的信息,水平扩展很难处理 -1. 没有解决 master 写的压力 - -![](/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.010.png) - -##### **集群版Redis实例** - -Redis 集群(Cluster)实例采用分布式架构,每个节点都采用一主一从的高可用架构,能够进行自动容灾切换和故障迁移。多种集群规格可适配不同的业务压力,可按需扩展数据库性能。每个数据分片均为双副本(分别部署在不同机器上)高可用架构,主节点发生故障后,系统会自动进行主备切换保证服务高可用。 - -Redis 集群是一个由多个节点组成的分布式服务器群,它具有复制、高可用和分片特性;Redi s集群没有中心节点,并且带有复制和故障转移特性,这可以避免单个节点成为性能瓶颈,或者因为某个节点下线而导致整个集群下线。 - -以下图所示的三主三从集群为例,每个主节点处理各自的数据,提供读写能力,从节点异步复制主节点的数据。 - -Redis集群 方案,采用的是虚拟槽分区,槽范围是0-16383,一共有16384个槽(Slots)。槽(Slots)是集群内数据管理和迁移的基本单位。比如上图的集群中有3个主节点,每个节点大致负责5500个槽的读写,节点会维护自身负责的虚拟槽。cluster集群中的主节点负责处理槽(存储数据),从节点则是主节点的复制品。 - -优点: - -1. 能够提升Redis横向扩容能力 -1. 数据分片存储,减少单个节请求压力 -1. slave在master故障时,能够提升为master对外提供服务,提升集群整理可用性 - -缺点: - -1. 对Client有一定要求,需要使用支持Move操作的smartClient,根据请求找到数据对应分槽节点 -1. 需要为Client暴露所有节点的地址,使Client可以访问该节点分槽中的数据 -1. 在节点达到一定规模(1000左右),由于分槽元数据同步会使得整个Redis性能大幅下降。 - -![](/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.011.png) - -##### **代理模式版Redis实例** - -在Redis的集群和读写分离架构中,代理服务器(Proxy)承担着路由转发、负载均衡与故障转移等职责。 - -代理服务器(Proxy)是Redis实例中的一个组件,不会占用数据分片的资源,通过多个Proxy节点实现负载均衡及故障转移。 - -优点: - -1. 客户端(Jedis)直连有限的proxy节点,会比较轻量和简单 -1. 集群规模理论上可以非常大,因为proxy对外隐藏了集群规模 - -缺点: - -1. 多了一层proxy访问,性能会有影响 -1. 需要第三方的proxy实现,集群水平扩容时proxy要想平滑动态更新集群配置,需要开发工具支持 -1. 代理层对Redis访问请求进行了转发,其能够支持的请求命令相较于原生Redis存在一定限制(根据选用代理框架不同,限制的程度则不同) - -目前比较流行的代理框架: - -- **predixy**:高性能全特征redis代理,支持Redis Sentinel和Redis Cluster。 -- **redis-cluster-proxy**:Redis官方推出的集群模式代理组件,能够代理集群模式下的Redis,但无法支持上游认证且在Redis商业化后,该项目已不再维护。 -- **twemproxy**:快速、轻量级memcached和redis代理,Twitter推特公司开发,目前业界广泛采用的redis代理。 -- **codis**:redis集群代理解决方案,豌豆荚公司开发,相对比较成熟。 -- **envoy**:由Lyft推出并贡献给CNCF社区,目前已经成为CNCF毕业项目,作为service mash的sidecar,提供redis集群代理模式与多主代理模式。 - -###### **Envoy代理模型** - -Evnoy提供了Redis多主的代理模式,通过Envoy,可以轻松将Client请求负载到后端多master上,并提供了多种负载模型,能够实现多写、单写、多写单写结合等方式负载。 - -Envoy作为Redis代理,同样能够支持集群模式,通过随机访问集群模式中的一个节点,通过CLUSTER SLOT命令获取到当前集群的分槽、节点信息,并缓存在Envoy的\_redis\_addresses对象中,在访问对应分槽数据时,对Client请求进行转发,能够解决集群模式需要对外暴露地址的问题,请求由Envoy进行拦截,并找对对应的数据分槽,进行转发,最后汇总到Client,并且支持AUTH命令的转发,在Envoy中同样可以为Redis设置上游Client认证密码与下游Redis集群认证密码,提供上下游加密模式,提升集群整体安全性。 - -![](/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.012.png) - -###### **Codis代理模型** - -Twemproxy是推特开源的,它最大的缺点就是无法平滑的扩缩容,而Codis解决了Twemproxy扩缩容的问题,而且兼容了Twemproxy。 - -Codis把所有的key分为1024个槽,每一个槽位都对应了一个分组,具体槽位的分配,可以进行自定义。首先要根据CRC32算法,针对Key算出32位的哈希值,除以1024取余,就能算出这个Key属于哪个槽,根据槽与分组的映射关系去对应的分组当中处理数据。CRC全称是循环冗余校验,主要在数据存储和通信领域保证数据正确性的校验手段。 - -但是codis proxy它本身也存在单点问题,所以需要对proxy做一个集群。Codis使用Zookeeper来保存映射关系,由proxy上来同步配置信息,它不止支持zookeeper,还有etcd和本地文件。 - -![](/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.013.png) - -### **Redis在云原生下的可观测性** - -可观测性(Observability)是指通过一个系统向外输出的信息了解其内部发生的事情的能力。Kubernetes 体系下的云原生应用天生就有着动态管理、频繁扩缩容的特性,我们通过监控告警、日志查询、事件记录等能力来保证 Redis 在云原生下的可观测性。 - -#### **监控告警** - -作为伴随着 Kubernetes 共同成长并发展成熟的开源项目,Prometheus 已经成为了云原生场景下监控的事实标准。它有着高效、可拓展、易于集成、易于管理等优势,并且还拥有强大的数据模型和查询语言 PromQL。我们在 Redis 云原生实践中的监控功能也是结合 Prometheus 完成的,下面对其实现方案做相关介绍。 - -![](/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.014.png) - -监控系统架构示意图 - -负责向 Prometheus 暴露监控指标数据的程序一般称之为 exporter ,它收集大部分 Redis的状态指标和性能指标,满足日常监控需求。 - -Redis 在诸如主从或是 Cluster 的集群模式下都会存在多个节点,为了保证可以收集到每个节点的监控指标,我们将 Redis Exporter 与 Redis 节点本身通过同一个 Pod 来部署。同一个 Redis 实例会创建出一个 Metrics Service 将收集的指标暴露出来,供 Prometheus 采集。 - -云原生场景下的 Prometheus 通常都会采用 Operator 模式来部署,借由此不但可以云原生的部署和管理诸多组件,还可以充分利用 Kubernetes 的诸如 ConfigMap,CRD(Custom Resource Definition)等能力来简化配置。 - -在传统部署模式下,Prometheus 的监控目标(Target)一般会通过 static\_config 的方式进行静态配置或是通过一个服务注册中心来完成。ServiceMonitor CRD 允许声明式地定义需要被监控的Target Service 的动态集合,它根据 Prometheus 的公开约定通过 Label 来筛选目标,然后通过这些约定自动发现新的目标而无需重新配置。 - -对于 Redis Exporter 暴露出的大量指标,我们需要根据自己的需要进行过滤和处理。一般取最关心的连接数、内存使用和 Key 数量等关键指标通过 PromQL 查询处理后供前端展示即可。 - -通过对于上述监控指标设置告警阈值指标,对接平台的告警中心(Alertmanager)即可实现告警功能。 - -#### **日志查询** - -日志系统负责记录着应用运行过程中产生的日志,相较于监控,可以更为细致的展现程序的运行状态。在云原生场景下,容器中运行程序产生的日志一般都直接输出到stdout,由相应的日志采集器负责统一收集。 - -![](/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.015.png) - -日志系统架构示意图 - -我们在云原生 Redis 中的日志主要分两部分:工作负载中应用程序产生的日志和Redis 本身的运行日志。如架构图所示,两者分别位于控制面与数据面,为了进行跨平面的统一日志查询,需要一些工具来帮助搭建日志查询系统。 - -##### **Fluentd 与 Fluent Bit** - -Fluentd 也是从 CNCF 毕业的项目,凭借着高效灵活的特性在云原生时代逐渐替代了传统ELK(ElasticSearch,Logstash,Kibana)系统中日志采集器(Logstash)组件。在我们的架构中,它主要起到聚合器的作用,而具体到各个容器的的采集工作则由它的同源项目 Fluent Bit 来完成。 - -Fluent Bit 则是在 Fluentd 生态系统下诞生的一个更轻量的新项目,它占用资源更少,性能更高,也更适合作为云原生场景下的日志采集器。 - -完成日志采集之后,剩下的存储、分析、查询及展示工作则仍交给 Elasticsearch 和 Kibana 这对经典组合。 - -##### **Elasticsearch 与 Kibana** - -Elasticsearch 是一个准实时的分布式存储、搜索、分析数据库引擎。相较传统数据库,它自带分词功能,对模糊查询的支持更为强大,非常适合用来存储日志信息。Kibana 则是与 Elasticsearch 协同的日志分析和可视化展示工具,包括数据分析和图表化展示等功能。 - -由于云原生架构下的日志极为分散,通过上述工具搭建的能将集群日志、应用日志、系统日志等进行统一采集管理的平台则显得尤为重要,同时也是保障系统可观测性的关键一环。 - -#### **事件记录** - -事件记录分为两部分:用户的操作事件和系统的操作事件。前者负责记录用户的关键操作,包括操作人、时间、动作、级别和类型等信息。后者则负责记录 Redis 实例的一些状态变更。 - -事件记录可以很好地进行问题定位和追溯。通过对接告警中心,系统管理员还可以定义一些关键操作用短信、钉钉或者邮件的方式通知到相应人员,提高系统的管理能力。 - -### **Redis性能提升** - -#### **Redis对象系统与数据结构分析** - -Redis中有很多常见的数据结构,如简单动态字符串(SDS)、双端链表、字典、压缩列表、整数集合等等,但Redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构构建了一个对象系统。对象系统中包含了字符串对象、列表对象、哈希对象、集合对象和有序集合对象这五种类型,这五种类型也是我们在日常使用Redis数据库中直接操作的对象。 - -通过对象系统,Redis可以针对不同的使用场景为对象设置不同的数据结构实现,从而优化对象在不同场景下的使用效率。 - -##### **字符串** - -Redis中用到最多的就是字符串的使用,作为一种常见的数据类型,Redis没有直接使用C语言传统的字符串表示,而是自己创建了一种动态字符串(SDS)。 - -优势(主要针对与C语言字符串的比较): - -- 常数复杂度获取字符串长度 - - 针对C字符串必须便利整个字符串才能获取长度。 -- 杜绝缓冲区溢出 - - 针对要修改字符串内容时,当需要修改的长度大于已分配长度,并且没有重新分配空间,导致溢出。 - - SDS API在对SDS进行修改时,会自动将空间扩展至所需大小,不需要手动操作。 -- 空间预分配:不仅为SDS分配修改所需要的空间,还会额外分配未使用的空间(基于当前修改的最大值 预期下次分配的结果,已使用和未使用free各占一半) - - 惰性释放:SDS字符串缩短操作时不立即重新分配,而是使用free属性记录下来,等待将来使用。 - -` `![](/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.016.png) - -##### **有序集合** - -有序集合类型 (Sorted Set或ZSet) 相比于集合类型多了一个排序属性 score(分值),对于有序集合 ZSet 来说,每个存储元素相当于有两个值组成的,一个是有序结合的元素值,一个是排序值。有序集合保留了集合不能有重复成员的特性(分值可以重复),但不同的是,有序集合中 的元素可以排序。 - -有序集合的底层编码实现主要是跳跃表和压缩列表。使用压缩列表的判断条件**(**同时满足**):** - -- 有序集合的数量小于128个。 -- 所有元素成员的长度都小于64字节。 - -##### **压缩列表** - -当一个列表键之包含少量的列表项,并且列表项要么就是小数值,要么就是长度比较短的字符 串,那么Redis就会将压缩列表来作为列表键的底层实现。(压缩列表是列表键和哈希键的底层实现之一) - -压缩列表存在的意义是为了节约内存,之所以说这种存储结构节省内存,是相较于数组的存储思路而言的。我们知道,数组要求每个元素的大小相同,如果我们要存储不同长度的字符串,那我们就需要用最大长度的字符串大小作为元素的大小(假设是20个字节)。存储小于 20 个字节长度的字符串的时候,便会浪费部分存储空间。 - -` `数组的优势占用一片连续的空间可以很好的利用CPU缓存访问数据。如果我们想要保留这种优势,又想节省存储空间我们可以对数组进行压缩。 - -` `但是这样有一个问题,我们在遍历它的时候由于不知道每个元素的大小是多少,因此也就无法计算出下一个节点的具体位置。这个时候我们可以给每个节点增加一个lenght的属性以此来记录前一个节点的具体位置。 - -` `![](/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.017.png) - -##### **跳跃表** - -跳跃表是一种有序数据结构,通过在每个节点中维持多个指向其它节点的指针,从而达到快速访问节点的目的,作为Redis中有序集合键的底层实现之一。 - -跳跃表在链表的基础上增加了多级索引以提升查找的效率,但其是一个空间换时间的方案,必然会带来一个问题——索引是占内存的。原始链表中存储的有可能是很大的对象,而索引结点只需要存储关键值值和几个指针,并不需要存储对象,因此当节点本身比较大或者元素数量比较多的时候,其优势必然会被放大,而缺点则可以忽略。 - -` `![](/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.018.png) - -##### **列表对象** - -列表(list)类型是用来存储多个有序的字符串,列表中的每个字符串称为元素(element),一个列表最多可以存储232-1个元素。在Redis中,可以对列表两端插入(push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素等。列表是一种比较灵活的数据结构,它可以充当栈和队列的角色,在实际开发上有很多应用场景。有以下特点: - -- 列表中的元素是有序的,可以通过索引下标获取某个元素或者某个范围内的元素列表。 -- 列表中的元素可以是重复的。 - -当元素的个数(512)以及元素的值(64字节)时,使用压缩列表,否则则使用双端链表。 - -` `![](/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.019.png) - -##### **哈希对象** - -哈希类型的内部编码有两种:压缩列表和哈希表。只有当存储的数据量比较小的情况下,Redis 才使用压缩列表来实现字典类型。具体需要满足两个条件: - -- 当哈希类型元素个数小于hash-max-ziplist-entries配置(默认512个) -- 所有值都小于hash-max-ziplist-value配置(默认64字节) - -ziplist使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。当哈希类型无法满足ziplist的条件时,Redis会使用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1)。 - -` `![](/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.020.png) - -![](/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.021.png) - -##### **集合对象** - -集合类型的内部编码有两种: - -- intset(整数集合):当集合中的元素都是整数且元素个数小于set-maxintset-entries配置(默认512个)时,Redis会选用intset来作为集合的内部实现,从而减少内存的使用。 -- hashtable(哈希表):当集合类型无法满足intset的条件时,Redis会使用hashtable作为集合的内部实现 - -` `![](/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.022.png) - -#### **Redis内存消耗与优化** - -Redis是基于内存的数据库,内存占用过高会导致Redis响应变慢,甚至会引起数据丢失以及故障恢复变慢等问题。 - -Redis内存消耗主要在于其主进程消耗和子进程消耗。而主进程消耗又主要包括自身内存、对象内存、缓冲区内存、内存碎片五个方面。 - -##### **自身内存** - -Redis自身内存指的是进程本身所占用的内存,通常可以忽略不计(一个空的Redis进程所消耗的内存几乎忽略不计) - -##### **对象内存** - -对象内存的占用主要是来源于五种常见数据结构(字符串、列表、哈希、集合、有序集合)的存储,不同的数据结构在不同的场景下使用不同的底层实现。因此在使用Redis的使用过程中需要根据场景选择合适的对象,从而避免内存溢出。 - -Redis的每一种对象都是key-value的键值对形式。每个键值对的创建都包含两个对象,key对象和value对象。因此对象内存的消耗可以理解为sizeof(key)+sizeof(value)。而key对象都是字符串类型的,在使用过程中我们不应该忽略key对象所占用的内存,应该避免使用过大的key。 - -##### **缓冲区内存** - -Redis主要有三个缓冲区,客户端缓冲区、AOF缓冲区、复制积压缓冲区。 - -` `![](/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.023.png) - -` `缓冲区的优势有两个: - -- 缓和CPU和I/O设备速度不匹配 -- 减少磁盘同一时间的读写速度 - -但是使用不当时也会带来很多问题,常见的问题就是超出内存限制导致内存溢出,如写的速度过快导致缓冲区内存需求增加、存入大数据等等。 - -客户端缓冲区是为了解决客户端和服务端请求和处理速度不匹配问题的,它又分为输入和输出缓冲区。输入缓冲区会先把客户端发送过来的命令暂存起来,Redis 主线程再从输入缓冲区中读取命令,进行处理。当在处理完数据后,会把结果写入到输出缓冲区,再通过输出缓冲区返回给客户端。 - -![](/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.024.png) - -AOF缓冲区是AOF持久化时所用到的缓冲区,AOF缓冲区消耗的内存取决于AOF重写时间和写入命令量。 - -![](/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.025.png) - -复制积压缓冲区则是在集群环境中为了保证主从节点数据同步的所设置的。主节点在向从节点传输 RDB 文件的同时,会继续接收客户端发送的写命令请求。这些写命令就会先保存在复制缓冲区中,等 RDB 文件传输完成后,再发送给从节点去执行。主节点上会为每个从节点都维护一个复制缓冲区,来保证主从节点间的数据同步。 - -![](/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.026.png) - -##### **内存碎片** - -内存碎片主要是由于操作系统的内存分配机制和Redis内存分配器的分配策略所决定的。内存分配器为了更好地管理和重复利用内存, 分配内存策略一般采用固定范围的内存块进行分配。例如当保存5KB对象时内存分配器可能会采用8KB的块存储, 而剩下的3KB空间变为了内存碎片不能再分配给其他对象存储。 - -###### **子进程内存消耗** - -子进程内存消耗主要指执行AOF/RDB重写时Redis创建的子进程内存消耗。Redis持久化在执行RDB快照和AOF重写时主进程会fork出一个子进程,由子进程完成快照和重写操作,虽然使用了写时复制的技术,子进程可以不用完全复制父进程的所有物理内存,但是仍然需要复制其内存页表,在此期间如果有写入操作则需要复制出一份副本出来。因此子进程同样会消耗一部分内存,其消耗的内存量取决于RDB和AOF期间的写入命令量。在执行RDB和AOF重写的时候为了防止内存溢出,会预留一部分内存。 - -#### **Redis性能进行诊断分析与优化** - -Redis采用单线程模型,当执行某个命令耗时较长时会影响其它命令的执行效率。因此,虽然Redis是一个高性能低时延的缓存服务,但是如果在耗时较长的任务发生时,任然会有性能问题出现。 - -##### **内存诊断与优化** - -###### **内存交换引起的性能问题** - -内存使用率是Redis服务在使用过程中十分重要的一个性能指标,如果Redis实例的内存使用率超过最大可用内存,那么操作系统会将内存与Swap空间交换,把内存中旧的或不再使用的内容写入硬盘上的Swap分区,以便留出新的物理内存给新页或活动页(page)使用。 - -通常我们可以在客户端使用“info memory”查看Redis服务使用的内存情况,其中“used\_memery”表示Redis服务已使用的内存,当“used\_memory”>最大可用内存时,Redis实例正在进行内存交换或者已经内存交换完毕。如果Redis进程上发生内存交换,那么Redis及使用Redis数据的应用性能都会受到严重影响。 - -![](/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.027.png) - -在硬盘上进行读写操作要比在内存上进行读写操作,时间上慢了近5个数量级,内存是0.1μs单位、而硬盘是10ms。如果Redis进程上发生内存交换,那么Redis和依赖Redis上数据的应用会受到严重的性能影响。 通过查看used\_memory指标可知道Redis正在使用的内存情况,如果used\_memory>可用最大内存,那就说明Redis实例正在进行内存交换或者已经内存交换完毕。管理员根据这个情况,执行相对应的应急措施。 - -###### **跟踪内存使用率** - -如果在使用Redis期间没有使用持久化策略,那么缓存数据在Redis崩溃时就有丢失的危险。因为当Redis内存使用率超过可用内存的95%时,部分数据开始在内存与swap空间来回交换,这时就可能有丢失数据的危险。 - -当开启并触发快照功能时,Redis会fork一个子进程把当前内存中的数据完全复制一份写入到硬盘上。因此若是当前使用内存超过可用内存的45%时触发快照功能,那么此时进行的内存交换会变的非常危险(可能会丢失数据)。 倘若在这个时候实例上有大量频繁的更新操作,问题会变得更加严重。 - -通过减少Redis的内存占用率,来避免这样的问题,或者使用下面的技巧来避免内存交换发生: - -- 对于缓存数据小于4GB,可选择采用32位的Redis实例,达到较少内存的占用空间效果。 -- 尽可能的使用Hash数据结构。因为Redis在储存小于100个字段的Hash结构上,其存储效率是非常高的。 -- 设置key的过期时间,Redis会在key过期时自动删除key。 -- 回收key。在Redis配置文件中(Redis.conf),通过设置“maxmemory”属性的值可以限制Redis最大使用的内存,修改后重启实例生效。 - -##### **延迟诊断与优化** - -在Redis实例中,[可以通过]()跟踪命令处理总数[(total_commands_processed)来判断延迟问题所在](),因为Redis是个单线程模型,客户端过来的命令是按照顺序执行的。比较常见的延迟是带宽,通过千兆网卡的延迟大约有200μs。倘若明显看到命令的响应时间变慢,延迟高于200μs,那可能是Redis命令队列里等待处理的命令数量比较多。 如上所述,延迟时间增加导致响应时间变慢可能是由于一个或多个慢命令引起的,这时可以看到每秒命令处理数在明显下降,甚至于后面的命令完全被阻塞,导致Redis性能降低。要分析解决这个性能问题,需要跟踪命令处理数的数量和延迟时间。 - -![](/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.028.png) - -当客户端明显发现响应时间过慢时,可以通过记录的total\_commands\_processed历史数据值来判断命理处理总数是上升趋势还是下降趋势,以便排查问题。 - -##### **跟踪Redis延迟性能** - -Redis之所以这么流行的主要原因之一就是低延迟特性带来的高性能,所以说解决延迟问题是提高Redis性能最直接的办法。拿1G带宽来说,若是延迟时间远高于200μs,那明显是出现了性能问题。 虽然在服务器上会有一些慢的IO操作,但Redis是单核接受所有客户端的请求,所有请求是按良好的顺序排队执行。因此若是一个客户端发过来的命令是个慢操作,那么其他所有请求必须等待它完成后才能继续执行。 - -可以通过一下几个方法分析延迟性能问题: - -- 使用slowlog查看引发延迟的慢命令 -- 监控客户端的连接 -- 限制客户端的连接数 -- 加强内存管理 - -##### **内存碎片诊断与优化** - -内存碎片率同样是影响Redis服务的重要性能指标之一。内存碎片率(mem\_fragmenttation\_ratio)稍大于1是合理的,这个值表示内存碎片率比较低,也说明redis没有发生内存交换。但如果内存碎片率超过1.5,那就说明Redis消耗了实际需要物理内存的150%,其中50%是内存碎片率。若是内存碎片率低于1的话,说明Redis内存分配超出了物理内存,操作系统正在进行内存交换。 - -![](/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.029.png) - -内存碎片的优化方法: - -- 重启Redis服务器,将额外产生的内存碎片失效并重新作为新内存使用。 -- 限制内存交换,增加可用物理内存或减少实Redis内存占用。 -- 修改内存分配器。 - -针对Redis相关的性能优化,首先可以根据业务需要选择合适的数据类型,并为不同的应用场景设置相应的紧凑存储参数。其次如果不需要进行数据持久化,可以关闭以提高处理性能和内存使用率。最后需要控制实际内存的使用率以及客户端的连接数。 - diff --git a/themes/mini/static/css/style.css b/css/style.css similarity index 100% rename from themes/mini/static/css/style.css rename to css/style.css diff --git a/themes/mini/static/images/avatar.png b/images/avatar.png similarity index 100% rename from themes/mini/static/images/avatar.png rename to images/avatar.png diff --git a/static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.001.png b/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.001.png similarity index 100% rename from static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.001.png rename to images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.001.png diff --git a/static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.002.png b/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.002.png similarity index 100% rename from static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.002.png rename to images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.002.png diff --git a/static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.003.png b/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.003.png similarity index 100% rename from static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.003.png rename to images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.003.png diff --git a/static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.004.png b/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.004.png similarity index 100% rename from static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.004.png rename to images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.004.png diff --git a/static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.005.png b/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.005.png similarity index 100% rename from static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.005.png rename to images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.005.png diff --git a/static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.006.png b/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.006.png similarity index 100% rename from static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.006.png rename to images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.006.png diff --git a/static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.007.png b/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.007.png similarity index 100% rename from static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.007.png rename to images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.007.png diff --git a/static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.008.png b/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.008.png similarity index 100% rename from static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.008.png rename to images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.008.png diff --git a/static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.009.png b/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.009.png similarity index 100% rename from static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.009.png rename to images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.009.png diff --git a/static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.010.png b/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.010.png similarity index 100% rename from static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.010.png rename to images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.010.png diff --git a/static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.011.png b/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.011.png similarity index 100% rename from static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.011.png rename to images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.011.png diff --git a/static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.012.png b/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.012.png similarity index 100% rename from static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.012.png rename to images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.012.png diff --git a/static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.013.png b/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.013.png similarity index 100% rename from static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.013.png rename to images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.013.png diff --git a/static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.014.png b/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.014.png similarity index 100% rename from static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.014.png rename to images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.014.png diff --git a/static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.015.png b/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.015.png similarity index 100% rename from static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.015.png rename to images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.015.png diff --git a/static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.016.png b/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.016.png similarity index 100% rename from static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.016.png rename to images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.016.png diff --git a/static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.017.png b/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.017.png similarity index 100% rename from static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.017.png rename to images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.017.png diff --git a/static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.018.png b/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.018.png similarity index 100% rename from static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.018.png rename to images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.018.png diff --git a/static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.019.png b/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.019.png similarity index 100% rename from static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.019.png rename to images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.019.png diff --git a/static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.020.png b/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.020.png similarity index 100% rename from static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.020.png rename to images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.020.png diff --git a/static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.021.png b/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.021.png similarity index 100% rename from static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.021.png rename to images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.021.png diff --git a/static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.022.png b/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.022.png similarity index 100% rename from static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.022.png rename to images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.022.png diff --git a/static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.023.png b/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.023.png similarity index 100% rename from static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.023.png rename to images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.023.png diff --git a/static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.024.png b/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.024.png similarity index 100% rename from static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.024.png rename to images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.024.png diff --git a/static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.025.png b/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.025.png similarity index 100% rename from static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.025.png rename to images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.025.png diff --git a/static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.026.png b/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.026.png similarity index 100% rename from static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.026.png rename to images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.026.png diff --git a/static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.027.png b/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.027.png similarity index 100% rename from static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.027.png rename to images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.027.png diff --git a/static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.028.png b/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.028.png similarity index 100% rename from static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.028.png rename to images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.028.png diff --git a/static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.029.png b/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.029.png similarity index 100% rename from static/images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.029.png rename to images/cloud_native_redis/Aspose.Words.45d5b06a-ceae-4e74-82e0-6b9784e4c3aa.029.png diff --git "a/static/images/cloud_native_redis/\344\272\221\345\216\237\347\224\237Redis\345\256\236\350\267\265.md" "b/images/cloud_native_redis/\344\272\221\345\216\237\347\224\237Redis\345\256\236\350\267\265.md" similarity index 100% rename from "static/images/cloud_native_redis/\344\272\221\345\216\237\347\224\237Redis\345\256\236\350\267\265.md" rename to "images/cloud_native_redis/\344\272\221\345\216\237\347\224\237Redis\345\256\236\350\267\265.md" diff --git a/static/images/containerd-arch.png b/images/containerd-arch.png similarity index 100% rename from static/images/containerd-arch.png rename to images/containerd-arch.png diff --git a/themes/mini/static/images/favicon.ico b/images/favicon.ico similarity index 100% rename from themes/mini/static/images/favicon.ico rename to images/favicon.ico diff --git a/static/images/typescript/047a512a-57fa-48df-8a8c-946908bdec40.png b/images/typescript/047a512a-57fa-48df-8a8c-946908bdec40.png similarity index 100% rename from static/images/typescript/047a512a-57fa-48df-8a8c-946908bdec40.png rename to images/typescript/047a512a-57fa-48df-8a8c-946908bdec40.png diff --git a/static/images/typescript/0ecb6eb6-e14d-4029-a162-3966ce2593c9.png b/images/typescript/0ecb6eb6-e14d-4029-a162-3966ce2593c9.png similarity index 100% rename from static/images/typescript/0ecb6eb6-e14d-4029-a162-3966ce2593c9.png rename to images/typescript/0ecb6eb6-e14d-4029-a162-3966ce2593c9.png diff --git a/static/images/typescript/1e267ba5-e9a0-454c-bdf2-69e9aab09c0e.png b/images/typescript/1e267ba5-e9a0-454c-bdf2-69e9aab09c0e.png similarity index 100% rename from static/images/typescript/1e267ba5-e9a0-454c-bdf2-69e9aab09c0e.png rename to images/typescript/1e267ba5-e9a0-454c-bdf2-69e9aab09c0e.png diff --git a/static/images/typescript/2fec720b-504e-4277-b359-ee5af4a423a2.gif b/images/typescript/2fec720b-504e-4277-b359-ee5af4a423a2.gif similarity index 100% rename from static/images/typescript/2fec720b-504e-4277-b359-ee5af4a423a2.gif rename to images/typescript/2fec720b-504e-4277-b359-ee5af4a423a2.gif diff --git a/static/images/typescript/4651df33-64c9-4728-8e74-59cc13f9785b.gif b/images/typescript/4651df33-64c9-4728-8e74-59cc13f9785b.gif similarity index 100% rename from static/images/typescript/4651df33-64c9-4728-8e74-59cc13f9785b.gif rename to images/typescript/4651df33-64c9-4728-8e74-59cc13f9785b.gif diff --git a/static/images/typescript/aba66a1c-98fc-41dc-b9ce-6f7944484181.png b/images/typescript/aba66a1c-98fc-41dc-b9ce-6f7944484181.png similarity index 100% rename from static/images/typescript/aba66a1c-98fc-41dc-b9ce-6f7944484181.png rename to images/typescript/aba66a1c-98fc-41dc-b9ce-6f7944484181.png diff --git a/static/images/typescript/bc25b3d4-f0b4-4814-936e-2f7aaab54e79.gif b/images/typescript/bc25b3d4-f0b4-4814-936e-2f7aaab54e79.gif similarity index 100% rename from static/images/typescript/bc25b3d4-f0b4-4814-936e-2f7aaab54e79.gif rename to images/typescript/bc25b3d4-f0b4-4814-936e-2f7aaab54e79.gif diff --git a/static/images/typescript/bffb582b-14b9-45d0-a596-eaa43e303782.gif b/images/typescript/bffb582b-14b9-45d0-a596-eaa43e303782.gif similarity index 100% rename from static/images/typescript/bffb582b-14b9-45d0-a596-eaa43e303782.gif rename to images/typescript/bffb582b-14b9-45d0-a596-eaa43e303782.gif diff --git a/static/images/typescript/cc3b43c3-a842-416a-914e-d7e075d507ba.png b/images/typescript/cc3b43c3-a842-416a-914e-d7e075d507ba.png similarity index 100% rename from static/images/typescript/cc3b43c3-a842-416a-914e-d7e075d507ba.png rename to images/typescript/cc3b43c3-a842-416a-914e-d7e075d507ba.png diff --git a/static/images/typescript/dd3b9cf9-7983-409e-8cb0-2db028ca87a9.png b/images/typescript/dd3b9cf9-7983-409e-8cb0-2db028ca87a9.png similarity index 100% rename from static/images/typescript/dd3b9cf9-7983-409e-8cb0-2db028ca87a9.png rename to images/typescript/dd3b9cf9-7983-409e-8cb0-2db028ca87a9.png diff --git a/static/images/typescript/dd9133f3-d2c5-4b6b-9f02-bc9693cc233e.png b/images/typescript/dd9133f3-d2c5-4b6b-9f02-bc9693cc233e.png similarity index 100% rename from static/images/typescript/dd9133f3-d2c5-4b6b-9f02-bc9693cc233e.png rename to images/typescript/dd9133f3-d2c5-4b6b-9f02-bc9693cc233e.png diff --git a/index.html b/index.html new file mode 100644 index 0000000..6ef1f6b --- /dev/null +++ b/index.html @@ -0,0 +1,6 @@ +easystack
avatar

easystack

Open Computer To Futhre

TypeScript 项目实践 🔗该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用T
TypeScript 项目实践 🔗该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用T
TypeScript 项目实践 🔗该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用T
TypeScript 项目实践 🔗该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用T
TypeScript 项目实践 🔗该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用T
云原生Redis实践 🔗Redis与容器实践探索 🔗Redis云原生控制器设计 🔗什么是云原生控制器 🔗设计思想 云原生控制器是控制器设计模式在云原生
背景 🔗一次生产环境中,发现 kubelet 内存达到上 GB 以上,这个不符合平常使用情况,首先想到的是内存泄漏,那么肯定使用 pprof 以及调查为什么会产生内存泄漏 profile 🔗基
历史 🔗 Docker Daemon从最初集成在docker命令中(1.11版本前), 独立成单独二进制程序(1.11版本开始)2016.12 containe
\ No newline at end of file diff --git a/index.xml b/index.xml new file mode 100644 index 0000000..19c31ff --- /dev/null +++ b/index.xml @@ -0,0 +1 @@ +easystackhttps://easystack.github.io/Recent content on easystackHugo -- gohugo.iozhWed, 06 Apr 2022 15:40:35 +0800TypeScript项目实践——在使用Typescript中,如何使用第三方依赖?https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%E7%AC%AC%E4%B8%89%E6%96%B9%E4%BE%9D%E8%B5%96/Wed, 06 Apr 2022 15:40:35 +0800https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%E7%AC%AC%E4%B8%89%E6%96%B9%E4%BE%9D%E8%B5%96/TypeScript 项目实践 🔗该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用TTypeScript项目实践——在使用Typescript中,如何正确的转换类型?https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E6%AD%A3%E7%A1%AE%E7%9A%84%E8%BD%AC%E6%8D%A2%E7%B1%BB%E5%9E%8B/Wed, 06 Apr 2022 15:35:35 +0800https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E6%AD%A3%E7%A1%AE%E7%9A%84%E8%BD%AC%E6%8D%A2%E7%B1%BB%E5%9E%8B/TypeScript 项目实践 🔗该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用TTypeScript项目实践——在使用TypeScript中,如何减少类型的重复定义?https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E5%87%8F%E5%B0%91%E7%B1%BB%E5%9E%8B%E7%9A%84%E9%87%8D%E5%A4%8D%E5%AE%9A%E4%B9%89/Wed, 06 Apr 2022 15:11:31 +0800https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E5%87%8F%E5%B0%91%E7%B1%BB%E5%9E%8B%E7%9A%84%E9%87%8D%E5%A4%8D%E5%AE%9A%E4%B9%89/TypeScript 项目实践 🔗该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用TTypeScript项目实践——在使用Typescript中,如何减少any使用?https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E5%87%8F%E5%B0%91any%E4%BD%BF%E7%94%A8/Wed, 06 Apr 2022 14:58:47 +0800https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E5%87%8F%E5%B0%91any%E4%BD%BF%E7%94%A8/TypeScript 项目实践 🔗该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用TTypeScript项目实践——在使用TypeScript中,配合VSCode可维护性、开发效率有哪些提升?https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E9%85%8D%E5%90%88vscode%E5%8F%AF%E7%BB%B4%E6%8A%A4%E6%80%A7%E5%BC%80%E5%8F%91%E6%95%88%E7%8E%87%E6%9C%89%E5%93%AA%E4%BA%9B%E6%8F%90%E5%8D%87/Wed, 06 Apr 2022 14:46:52 +0800https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E9%85%8D%E5%90%88vscode%E5%8F%AF%E7%BB%B4%E6%8A%A4%E6%80%A7%E5%BC%80%E5%8F%91%E6%95%88%E7%8E%87%E6%9C%89%E5%93%AA%E4%BA%9B%E6%8F%90%E5%8D%87/TypeScript 项目实践 🔗该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用T云原生Redis实践https://easystack.github.io/posts/%E4%BA%91%E5%8E%9F%E7%94%9Fredis%E5%AE%9E%E8%B7%B5/Fri, 18 Mar 2022 14:00:43 +0800https://easystack.github.io/posts/%E4%BA%91%E5%8E%9F%E7%94%9Fredis%E5%AE%9E%E8%B7%B5/云原生Redis实践 🔗Redis与容器实践探索 🔗Redis云原生控制器设计 🔗什么是云原生控制器 🔗设计思想 云原生控制器是控制器设计模式在云原生记一次kubelet 内存泄漏https://easystack.github.io/posts/%E8%AE%B0%E4%B8%80%E6%AC%A1kubelet-%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/Tue, 09 Nov 2021 15:50:43 +0800https://easystack.github.io/posts/%E8%AE%B0%E4%B8%80%E6%AC%A1kubelet-%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/背景 🔗一次生产环境中,发现 kubelet 内存达到上 GB 以上,这个不符合平常使用情况,首先想到的是内存泄漏,那么肯定使用 pprof 以及调查为什么会产生内存泄漏 profile 🔗基Containerd 简介https://easystack.github.io/posts/containerd-%E7%AE%80%E4%BB%8B/Sat, 09 Oct 2021 15:50:43 +0800https://easystack.github.io/posts/containerd-%E7%AE%80%E4%BB%8B/历史 🔗 Docker Daemon从最初集成在docker命令中(1.11版本前), 独立成单独二进制程序(1.11版本开始)2016.12 containe \ No newline at end of file diff --git a/page/1/index.html b/page/1/index.html new file mode 100644 index 0000000..45890ea --- /dev/null +++ b/page/1/index.html @@ -0,0 +1 @@ +https://easystack.github.io/ \ No newline at end of file diff --git "a/posts/containerd-\347\256\200\344\273\213/index.html" "b/posts/containerd-\347\256\200\344\273\213/index.html" new file mode 100644 index 0000000..19e3a51 --- /dev/null +++ "b/posts/containerd-\347\256\200\344\273\213/index.html" @@ -0,0 +1,192 @@ +Containerd 简介 | easystack

Containerd 简介

+· +3823字 +· +8分钟

历史 🔗

  • Docker Daemon从最初集成在docker命令中(1.11版本前),
  • 独立成单独二进制程序(1.11版本开始)2016.12
    • containerd是容器技术标准化之后的产物,为了能够兼容OCI标准,从Docker Daemon剥离
    • containerd分v0.2.x , v1.x版本
      • v0.2.x 由docker daemon集成
      • v1.x 开始支持oci标准
    • containerd项目地址: github.com/docker/containerd
  • containerd捐献给cncf 2017.03
    • 项目地址修改为 github.com/containerd/containerd
    • v1.0.0正式版发布 2017.12 支持OCI接口
    • v1.1.0正式版 2018.4 支持CRI接口
    • v1.2.0正式版 2018.10 runtime v2稳定
  • shim
    • shim是一个真实运行的容器的真实载体

架构 🔗

  • 组件如图

1

glossary 🔗

  • 服务: 提供grpc服务

  • sandbox:一种特殊容器,主要作用准备容器隔离的环境,包括网络,io目录,cgroup parent

  • container:业务容器,每个进程都由一个task 管理

  • shim:管理器(containerd) 和 runtime的粘和层,因为runc是二进制文件执行,无法常驻内存,使用shim一方面方便管理,另一方面扩展runtime; shim管理 sandbox + container(s)

  • task:代表进程+io

  • runtime v2: 可以理解 task 管理器

服务 🔗

  • 每个服务都是plugin方式加入,包括不限于image, runtime, grpc server等
  • plugin特点
    • 插件URI为 Type+ID,如 io.containerd.snapshotter.v1.overlayfs,io.containerd.snapshotter.v1是Type,overlayfs是ID
    • 插件类型之间有依赖, 从下向上排序:
      • ContentPlugin, SnapshotPlugin
      • MetadataPlugin
      • GCPlugin,RuntimePlugin,DiffPlugin
      • ServicePlugin
      • InternalPlugin,GRPCPlugin
  • 数据结构
classDiagram
+    class Meta{
+        +'ocispec.Platform' Platforms
+        +'map[string]string' Exports
+        +string[] Capabilities
+    }
+    class Plugin{
+        +Registration Registration
+        +Meta Meta
+        +string[] Requires
+        +object Config
+        +Err() error
+        +Instance() (object, error)
+    }
+    class Registration{
+        +string Type
+        +string ID
+        +string[] Requires
+        +object Config
+        +Init(context) Plugin
+        +URI() string
+    }
+

diff 插件 🔗

  • 用于snaphost的操作
  • diff/walking/plugin目录, DiffPlugin类型,依赖MetadataPlugin
  • 提供两个方法
    • Compare 根据提供的 lower,upper 信息,得到oscispec.Descriptor
    • Apply 从镜像压缩文件中读取内容,并按序执行多个处理器,主要是校验,解压,自定义插件等

scheduler 🔗

  • 调度器,gc/scheduler目录, GCPlugin类型,依赖MetadataPlugin
  • 配置信息
    • PauseThreshold: 暂停阈值,平均每秒2%的暂停时间,使用该值计算下次等待周期,默认为0.02, 最大值0.5
    • DeletionThreshold:删除阈值,在删除多少次之后至少保证发生gc调度,0表示不会被删除触发,默认0
    • MutationThreshold: 突变阈值
    • ScheduleDelay:调度延迟,当发生手动触发或者阈值触发后,延迟多久才开始GC,默认0ms
    • StartupDelay: 慢启动,默认100ms
  • goroutine
    • gc实际是执行content.GarbageCollect

monitor 🔗

  • 容器的起停状态同步, InternalPlugin类型,依赖ServicePlugin
  • goroutine
    • 过滤标签 containerd.io/restart.status的container
    • 对比container和预期状态是否相符
    • 重新执行start或stop

cgroup 🔗

  • 进程oom监听和指标统计,在 metrics/cgroup 目录, TaskMonitorPlugin类型
  • 作用:task的指标和oom监听
  • 主要是shim 在使用该模块,shim需要监听 task 是否发生OOM

runtime v1 🔗

  • 容器进程管理器,, RuntimePlugin类型,依赖MetadataPlugin
  • 配置
    • shim: shim二进制文件地址
    • runtime: 被shim使用的runtime二进制文件地址
    • no_shim: 直接调用runtime,跳过shim
    • shim_debug: 是否打开shim调试

runtime v2 🔗

  • 容器进程管理器, RuntimePluginV2类型,依赖MetadataPlugin
  • client中被配置为默认的runtime
  • 初始化时会执行以下内容
    • 创建目录 {root/state}/io.containerd.runtime.v2.task
    • 读取 {state}/io.containerd.runtime.v2.task目录
    • 恢复shim 管理
tips:
+fifo文件ro打开:当无写端,若是nonblocking模式,则返回,否则阻塞
+fifo文件wo打开:当无读端,返回ENXIO,若读端关闭时写数据,则返回SIGPIPE
+fifo文件rw打开:总是成功
+

snapshot 🔗

  • 容器的rootfs管理器,SnapshotPlugin类型
  • linux 平台
    • snapshots/{native, overlay, devmapper,btrfs}
    • containerd/{aufs,zfs}
  • windows平台
    • snapshots/{lcow, windows}
# man 8 mount
+# references: https://wiki.archlinux.org/title/Overlay_filesystem
+tips:
+1 overlay 挂载
+mount -t overlay  overlay -o lowerdir=/lower:/lower2,upperdir=/upper,workdir=/work  /merged
+// upperdir 一般是可写层
+// workdir 空目录,
+2 devmapper 挂载
+mount -t ext4 /dev/mapper/xx {target}
+3 native 挂载
+mount -t bind {src} {dst}
+4 btrfs 
+mount -t btrfs 
+

services 🔗

  • 提供rpc服务和数据库操作等
  • services/目录下,具体有以下的内容

container, image, lease, namespace 服务: 🔗

  • local.go: 是service Plugin类型,对数据库的操作,以及event的发送
  • service.go: 是grpc Plugin类型,提供grpc服务,并使用local内相同接口

content 服务 🔗

  • service.go: 提供grpc服务,CRUD content数据
  • contentserver : content具体操作(content)

task 服务 🔗

  • local.go: 调用runtime v2同名接口,包括容器创建,开始,结束,删除,终端,状态等信息
  • server.go: 提供grpc服务

diff 服务: 🔗

  • local.go: 使用diff插件
  • service.go: 提供grpc服务

event服务: 🔗

  • service.go: 提供grpc服务,包括转发,推送,订阅功能

introspection 服务: 🔗

  • service.go: 提供grpc服务,列举注册的plugin

healthcheck 服务: 🔗

  • service.go: 提供 grpc服务,检查指定服务的健康

version服务: 🔗

  • service.go: 提供 grpc服务,检查containerd version

opt服务: 🔗

  • 配置目录 root
    • 添加 {root}/bin 到环境变量 PATH中
    • 添加 {root}/lib 到环境变量LD_LIBRARY_PATH中

server 🔗

  • 基础plugin,扩展插件的初始化和服务启动
    • 流程
      • 注册 ContentPlugin插件
      • 注册 MetadataPlugin插件
      • 注册 proxy插件(ContentPlugin或SnapshotPlugin类型)
      • 注册DiffPlugin插件 (stream processor类型),目前主要是处理 加密的镜像
    • 监听event类型
      • /tasks/oom
      • /images/
version = 2
+root = "/var/lib/mycontainerd" #持久化目录
+state = "/var/run/mycontainerd" #非持久化目录
+oom_score = 0 # containerd的oom分数
+...
+[proxy_plugins]
+  [proxy_plugins.stargz]
+    type = "snapshot" #支持snapshot和content类型
+    address = "/run/containerd-stargz-grpc/containerd-stargz-grpc.sock"
+[stream_processors]
+  [stream_processors."io.containerd.ocicrypt.decoder.v1.tar.gzip"]
+    accepts = ["application/vnd.oci.image.layer.v1.tar+gzip+encrypted"]
+    returns = "application/vnd.oci.image.layer.v1.tar+gzip"
+    path = "/usr/local/bin/ctd-decoder"
+
+[cgroup]
+  path = "" # containerd 所在路径,去除 ‘/sys/fs/cgroup/'前缀的
+[plugins]
+  [plugins."io.containerd.gc.v1.scheduler"]
+    pause_threshold = 0.02
+    deletion_threshold = 0
+    mutation_threshold = 100
+    schedule_delay = "0s"
+    startup_delay = "100ms"
+  [plugins."io.containerd.internal.v1.restart"]
+    interval = "10s" #restart插件,遍历container的周期
+  [plugins."io.containerd.metadata.v1.bolt"]
+    content_sharing_policy = "shared" #content策略,shared则所有不同ns的都可以访问,另一种是isolated
+  [plugins."io.containerd.monitor.v1.cgroups"]
+    no_prometheus = false
+  [plugins."io.containerd.runtime.v1.linux"]
+    shim = "containerd-shim"
+    runtime = "runc"
+    runtime_root = ""
+    no_shim = false
+    shim_debug = false
+  [plugins."io.containerd.runtime.v2.task"]
+    platforms = ["linux/amd64"] #镜像支持的平台类型
+  [plugins."io.containerd.service.v1.diff-service"]
+    default = ["walking"] #walking是内置的diff插件
+

cri 🔗

  • GRPCPlugin类型,依赖ServicePlugin
  • 作用: 提供cri接口, cri 常见配置
[plugins.cri]
+    disable_tcp_service = true
+    stream_server_address = "127.0.0.1" # unix 监听地址
+    stream_server_port = "0" # unix 监听端口,0是随机分配
+    stream_idle_timeout = "4h0m0s" #idle连接超时时间
+    enable_selinux = false 
+    sandbox_image = "hub.easystack.io/captain/pause-amd64:3.0"
+    stats_collect_period = 10 #单位秒,snapshot状态收集
+    systemd_cgroup = false #只适用io.containerd.runtime.v1.linux
+    enable_tls_streaming = false # 是否支持tls
+    max_container_log_line_size = 16384 #单行日志最大byte
+    disable_cgroup = false # 当rootless 运行时,需要设置为true
+    disable_apparmor = false
+    restrict_oom_score_adj = false
+    netns_mounts_under_state_dir = false #将net ns设置到{state}/netns目录下,而不是默认的/run/netns下
+    max_concurrent_downloads = 3
+    unset_seccomp_profile = "" #seccomp配置文件
+    disable_proc_mount = false #禁用k8s ProcMount, 对于k8s <= 1.11时,必须设置为true
+    
+      tolerate_missing_hugetlb_controller = true #1.5+,对于k8s <1.19,不支持配置hugetlb
+      disable_hugetlb_controller = true #1.5+, 适用于rootless+cgroupv2+systemd
+      ignore_image_defined_volumes = true #1.5+,创建container时,忽略image中定义的volume
+    [plugins.cri.x509_key_pair_streaming] #stream tls配置
+      tls_cert_file = ""
+      tls_key_file = ""
+    [plugins.cri.image_decryption] # 解密镜像,参考项目 github.com/containerd/imgcrypt
+      key_model = none
+    [plugins.cri.containerd]
+      snapshotter = "overlayfs" #使用何种snapshot
+      default_runtime_name = "runc" #默认runtime,必须出现在后续的列表中
+      no_pivot = false #只适用io.containerd.runtime.v1.linux
+        disable_snapshot_annotations = false #stargz snapshot需要配置
+          discard_unpacked_layers = false #当执行content GC时,是否回收已经unpack的content
+      [plugins.cri.containerd.default_runtime] #被弃用
+        privileged_without_host_devices = false # 设置特权模式下时,是否映射主机上设备到容器内
+      [plugins.cri.containerd.untrusted_workload_runtime] #被弃用
+        privileged_without_host_devices = false
+      [plugins.cri.containerd.runtimes]
+        [plugins.cri.containerd.runtimes.runc]
+          runtime_type = "io.containerd.runc.v2" #将被用于解析为具体二进制文件名
+          [plugins.cri.containerd.runtimes.runc.options]
+        # NoPivotRoot disables pivot root when creating a container.
+            NoPivotRoot = false
+        # NoNewKeyring disables new keyring for the container.
+            NoNewKeyring = false
+        # 设置shim cgroup目录,默认是和containerd一起
+            ShimCgroup = ""
+        # 设置shim log user id.
+            IoUid = 0
+        # 设置shim log group id.
+            IoGid = 0
+        # 设置二进制文件名,否则将使用type字段
+            BinaryName = ""
+        # runc的目录,默认为 {state}/runc.
+            Root = ""
+        # criu 二进制文件路径.
+            CriuPath = ""
+        # 使用cgroup的systemd 驱动,注意需要和kubelet中同样设置
+            SystemdCgroup = true
+        # CriuImagePath is the criu image path
+            CriuImagePath = ""
+        # CriuWorkPath is the criu work path.
+            CriuWorkPath = ""
+          privileged_without_host_devices = false
+        [plugins.cri.containerd.runtimes.gvisor]
+          runtime_type = "io.containerd.gvisor.v2"
+          pod_annotations =[""]
+          container_annotations = [""]
+          base_runtime_spec="" # 文件绝对路径,且内容为oci runtime文件格式
+        [plugins.cri.containerd.runtimes.firecracker]
+         runtime_type = "io.containerd.firecracker.v2"
+    [plugins.cri.cni]
+      bin_dir = "/opt/cni/bin"
+      conf_dir = "/etc/cni/net.d"
+      max_conf_num = 1 #若有多个cni配置文件,则按照此值调用多个cni配置文件
+    [plugins.cri.registry]
+        config_path= /xxx # 目录,若配置该值,则以下内容被忽略
+      [plugins.cri.registry.configs] #被弃用,使用config_path代替,在1.7后被移除
+        [plugins.cri.registry.configs."hub.easystack.io"]
+          [plugins.cri.registry.configs."hub.easystack.io".tls]
+            insecure_skip_verify = true
+      [plugins.cri.registry.mirrors] #被弃用,使用config_path代替,在1.7后被移除
+        [plugins.cri.registry.mirrors."docker.io"]
+          endpoint = ["https://registry-1.docker.io"]
+        [plugins.cri.registry.mirrors."hub.easystack.io"]
+          endpoint = ["https://hub.easystack.io"]
+
  • 关于仓库目录配置方式
config_path= /opt/mycontainerd/hosts
+
+# 目录内容
+.
+├── docker.io
+│   └── hosts.toml
+└── k8s.gcr.io
+    └── hosts.toml
+
+# hosts.toml内容
+server = "https://k8s.gcr.io"
+
+[host."https://k8s.gcr.io"]
+  skip_verify = true
+  capabilities = [ "pull", "resolve", "push" ]
+  ca: "" #文件路径或文件内容
+
\ No newline at end of file diff --git a/posts/index.html b/posts/index.html new file mode 100644 index 0000000..837e139 --- /dev/null +++ b/posts/index.html @@ -0,0 +1,16 @@ +easystack
avatar

easystack

Open Computer To Futhre

\ No newline at end of file diff --git a/posts/index.xml b/posts/index.xml new file mode 100644 index 0000000..0173ff3 --- /dev/null +++ b/posts/index.xml @@ -0,0 +1 @@ +Posts on easystackhttps://easystack.github.io/posts/Recent content in Posts on easystackHugo -- gohugo.iozhWed, 06 Apr 2022 15:40:35 +0800TypeScript项目实践——在使用Typescript中,如何使用第三方依赖?https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%E7%AC%AC%E4%B8%89%E6%96%B9%E4%BE%9D%E8%B5%96/Wed, 06 Apr 2022 15:40:35 +0800https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%E7%AC%AC%E4%B8%89%E6%96%B9%E4%BE%9D%E8%B5%96/TypeScript 项目实践 🔗该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用TTypeScript项目实践——在使用Typescript中,如何正确的转换类型?https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E6%AD%A3%E7%A1%AE%E7%9A%84%E8%BD%AC%E6%8D%A2%E7%B1%BB%E5%9E%8B/Wed, 06 Apr 2022 15:35:35 +0800https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E6%AD%A3%E7%A1%AE%E7%9A%84%E8%BD%AC%E6%8D%A2%E7%B1%BB%E5%9E%8B/TypeScript 项目实践 🔗该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用TTypeScript项目实践——在使用TypeScript中,如何减少类型的重复定义?https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E5%87%8F%E5%B0%91%E7%B1%BB%E5%9E%8B%E7%9A%84%E9%87%8D%E5%A4%8D%E5%AE%9A%E4%B9%89/Wed, 06 Apr 2022 15:11:31 +0800https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E5%87%8F%E5%B0%91%E7%B1%BB%E5%9E%8B%E7%9A%84%E9%87%8D%E5%A4%8D%E5%AE%9A%E4%B9%89/TypeScript 项目实践 🔗该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用TTypeScript项目实践——在使用Typescript中,如何减少any使用?https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E5%87%8F%E5%B0%91any%E4%BD%BF%E7%94%A8/Wed, 06 Apr 2022 14:58:47 +0800https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E5%87%8F%E5%B0%91any%E4%BD%BF%E7%94%A8/TypeScript 项目实践 🔗该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用TTypeScript项目实践——在使用TypeScript中,配合VSCode可维护性、开发效率有哪些提升?https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E9%85%8D%E5%90%88vscode%E5%8F%AF%E7%BB%B4%E6%8A%A4%E6%80%A7%E5%BC%80%E5%8F%91%E6%95%88%E7%8E%87%E6%9C%89%E5%93%AA%E4%BA%9B%E6%8F%90%E5%8D%87/Wed, 06 Apr 2022 14:46:52 +0800https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E9%85%8D%E5%90%88vscode%E5%8F%AF%E7%BB%B4%E6%8A%A4%E6%80%A7%E5%BC%80%E5%8F%91%E6%95%88%E7%8E%87%E6%9C%89%E5%93%AA%E4%BA%9B%E6%8F%90%E5%8D%87/TypeScript 项目实践 🔗该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用T云原生Redis实践https://easystack.github.io/posts/%E4%BA%91%E5%8E%9F%E7%94%9Fredis%E5%AE%9E%E8%B7%B5/Fri, 18 Mar 2022 14:00:43 +0800https://easystack.github.io/posts/%E4%BA%91%E5%8E%9F%E7%94%9Fredis%E5%AE%9E%E8%B7%B5/云原生Redis实践 🔗Redis与容器实践探索 🔗Redis云原生控制器设计 🔗什么是云原生控制器 🔗设计思想 云原生控制器是控制器设计模式在云原生记一次kubelet 内存泄漏https://easystack.github.io/posts/%E8%AE%B0%E4%B8%80%E6%AC%A1kubelet-%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/Tue, 09 Nov 2021 15:50:43 +0800https://easystack.github.io/posts/%E8%AE%B0%E4%B8%80%E6%AC%A1kubelet-%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/背景 🔗一次生产环境中,发现 kubelet 内存达到上 GB 以上,这个不符合平常使用情况,首先想到的是内存泄漏,那么肯定使用 pprof 以及调查为什么会产生内存泄漏 profile 🔗基Containerd 简介https://easystack.github.io/posts/containerd-%E7%AE%80%E4%BB%8B/Sat, 09 Oct 2021 15:50:43 +0800https://easystack.github.io/posts/containerd-%E7%AE%80%E4%BB%8B/历史 🔗 Docker Daemon从最初集成在docker命令中(1.11版本前), 独立成单独二进制程序(1.11版本开始)2016.12 containe \ No newline at end of file diff --git "a/posts/typescript\351\241\271\347\233\256\345\256\236\350\267\265\345\234\250\344\275\277\347\224\250typescript\344\270\255\345\246\202\344\275\225\344\275\277\347\224\250\347\254\254\344\270\211\346\226\271\344\276\235\350\265\226/index.html" "b/posts/typescript\351\241\271\347\233\256\345\256\236\350\267\265\345\234\250\344\275\277\347\224\250typescript\344\270\255\345\246\202\344\275\225\344\275\277\347\224\250\347\254\254\344\270\211\346\226\271\344\276\235\350\265\226/index.html" new file mode 100644 index 0000000..f94670f --- /dev/null +++ "b/posts/typescript\351\241\271\347\233\256\345\256\236\350\267\265\345\234\250\344\275\277\347\224\250typescript\344\270\255\345\246\202\344\275\225\344\275\277\347\224\250\347\254\254\344\270\211\346\226\271\344\276\235\350\265\226/index.html" @@ -0,0 +1,38 @@ +TypeScript项目实践——在使用Typescript中,如何使用第三方依赖? | easystack

TypeScript项目实践——在使用Typescript中,如何使用第三方依赖?

+· +1130字 +· +3分钟

TypeScript 项目实践 🔗

该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用TypeScript的同行提供参考,高效正确的使用TypeScript。

在使用Typescript中,如何使用第三方依赖? 🔗

在实际使用Typescript项目开发过程中, 我们经常会引入第三方依赖包,且使用npm来管理第三方依赖包的安装管理, 如下:

// 以 ng-zorro-antd 为例, npm 安装
+$ npm install ng-zorro-antd --save
+
// 项目中直接 import 导入即可
+import { NzModalModule } from 'ng-zorro-antd/modal';
+

然而是否所有的包都可以通过上述方式直接 import 导入? 当然不行。

ng-zorro-antd 是基于 TypeScript 开发,包含完整的声明文件。比如我们安装 ng-zorro-antd 后可以发现 node_modules/ ng-zorro-antd中包含 ng-zorro-antd.d.ts 或者package.json 中包含 typings 属性指定了声明文件。

但实际上有很多包并不是基于TypeScript开发的,自然也不会导出Typescript声明文件。由于历史原因,仍然有很多基于javascript开发的第三方依赖,比如jquery。或者即使有些包基于TypeScript开发的, 但如果没有导出声明文件, 也是没用的,直接导入的话就会编译报错, 告诉我们没有找到 相关模块或者声明。

// 以jquery为例
+npm install jquery --save
+
+// 项目代码
+import * as $ from 'jquery';
+
+// Could not find a declaration file for module 'jquery'. '/root/my-app/node_modules/jquery/dist/jquery.js' implicitly has an 'any' type.
+// Try `npm i --save-dev @types/jquery` if it exists or add a new declaration (.d.ts) file containing `declare module 'jquery';`
+

我们会发现, 即使已经安装了jQuery, 依然会编译报错,因为编译器查找到定位到 jquery module 的声明文件。 那么Typescript 模块依赖声明文件又是如何查找的?

Typescript 如何查找第三方依赖声明? 🔗

TypeScript是模仿Node.js运行时的解析策略来在编译阶段定位模块定义文件, 查找依赖声明文件,按照上层递归 node_modules 查找,具体来说就是:

  • node_modules/jquery/jquery.d.ts
  • node_modules/jquery/package.json types/typings属性定义
  • 如果找不到,则会去 node_modules 中的@types(默认情况,目录可以修改)目录下去寻找对应包名的模块声明文件
  • 向上层 node_modules 目录继续查找

如果都没有查找到, 则编译提示报错,针对安装的依赖包中缺少声明文件情况,社区提供两种解决方案:

  • 手动下载 jquery 的声明文件(优先推荐使用, 需要注意版本问题)
  • Typescript 2以前也会使用 typings search 查找依赖声明 (不常用)
  • 自行定义一份*.d.ts 声明文件,并将 jquery 声明为 module

下载第三方依赖包@types声明 🔗

至于自定义 .d.ts 文件 declare module,其实非常简单,直接在src文件夹下定义 typings/jquery/jquery.d.ts 文件。

然后在jquery.d.ts文件中编写声明:

declare module 'jquery' {
+  // export function init(): void
+  // 亦或者扩展模块声明, 比如我们在业务代码中 jquery 新增了一个方法
+  // export function func(): number
+}
+
+// 或者可直接简写
+declare module 'jquery';
+

修改 tsconfig.json 配置文件,typeRoots 属性中新增一个添加 typings 目录,这样如果node_modules/@types 中找不到声明文件,就会去src/typings中查找。

{
+   "typesRoot": [
+      "node_modules/@types",
+      "src/typings"
+    ]
+}
+

然后大工告成。

\ No newline at end of file diff --git "a/posts/typescript\351\241\271\347\233\256\345\256\236\350\267\265\345\234\250\344\275\277\347\224\250typescript\344\270\255\345\246\202\344\275\225\345\207\217\345\260\221any\344\275\277\347\224\250/index.html" "b/posts/typescript\351\241\271\347\233\256\345\256\236\350\267\265\345\234\250\344\275\277\347\224\250typescript\344\270\255\345\246\202\344\275\225\345\207\217\345\260\221any\344\275\277\347\224\250/index.html" new file mode 100644 index 0000000..e1d051a --- /dev/null +++ "b/posts/typescript\351\241\271\347\233\256\345\256\236\350\267\265\345\234\250\344\275\277\347\224\250typescript\344\270\255\345\246\202\344\275\225\345\207\217\345\260\221any\344\275\277\347\224\250/index.html" @@ -0,0 +1,101 @@ +TypeScript项目实践——在使用Typescript中,如何减少any使用? | easystack

TypeScript项目实践——在使用Typescript中,如何减少any使用?

+· +1405字 +· +3分钟

TypeScript 项目实践 🔗

该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用TypeScript的同行提供参考,高效正确的使用TypeScript。

在使用Typescript中,如何减少any使用? 🔗

本文主要是总结项目中实践经验,在Typescript中减少any的使用。

联合类型代替any 🔗

当一个对象需要指定多个类型时,使用了any作为类型,为了减少any的使用,我们可以用联合类型(|)组成多类型来代替any。 +例如:定义一个格式化数字类型和日期类型formatTime方法,方法的入参类型就可以使用联合类型(number | Date)

// bad
+formatTime(time: any) {
+    if (typeof time === 'number') {
+      time = new Date(time * 1000);
+    }
+    const year = time.getFullYear();
+    const month = time.getMonth() + 1;
+    const day = time.getDate();
+    const hours = time.getHours();
+    const minu = time.getMinutes();
+    const second = time.getSeconds();
+    return `${year}-${month}-${day} ${hours}:${minu}:${second}`;
+}
+
+// good 使用联合类型代替any
+function formatTime(time: number | Date) {
+    ...
+}
+
+// 传入Date类型参数
+formatTime(new Date());
+// 传入number类型参数
+formatTime(1600000000);
+

& 集合生成一个新类型代替any 🔗

当一个对象需要指定的类型缺失属性时,使用了any作为类型,为了减少any的使用我们可以用集合(&)组成新类型来代替any。 +例如:已有一个createInstance方法,需定义一个迁移方法migrationInstance,入参类型与createInstance相似,只是多一个oldID属性,我们可以用集合(&)组成含有oldID的新类型。

export class ApiInstanceService {
+
+  // 定义createInstance
+  createInstance(params: {
+    name: string;
+    cpu: number;
+    member: number;
+    ...
+  }) {
+  ...
+  }
+  
+  // bad
+  migrationInstance (params: any) {
+  ...
+ }
+
+  // good 将createInstance的参数类型通过集合方式生成包含oldID的新类型
+  migrationInstance(params: Parameters<ApiInstanceService['createInstance']>[0] & { oldID: string }) {
+  ...
+  }
+}
+

ReturnType获取函数返回值的类型 🔗

当一个对象类型为函数返回值的类型,并且无法定位到这个类型时,使用了any作为类型,为了减少any的使用。 +例如:调用了setInterval方法,当前无法确定返回值的类型,使用ReturnType获取返回值类型。

// bad
+let interval: any;
+
+// good
+let interval!: ReturnType<typeof setInterval>;
+
+interval = setInterval(() => {
+...
+}, 3000);
+

as指定类型 🔗

当一个对象中的属性在当前类型中匹配不到时,使用了any作为类型,为了减少any的使用 +我们用as指定类型来代替any。(注意:指定的类型必须是正确的类型) +例如:定义searchChange方法,为了获取e.target.value,将参数e.target通过as指定 +HTMLInputElement类型获取value。

// 触发input调用searchChange方法
+<input nz-input (input)="searchChange($event)" />
+
+// bad
+searchChange(e: any) {
+    const value = e.target.value;
+    .....
+}
+
+// good e.target通过as指定HTMLInputElement类型获取value
+searchChange(e: Event) {
+    const value = (e.target as HTMLInputElement).value;
+    .....
+}
+

可索引接口代替any 🔗

当一个对象中的属性存在不确定性,无法进行固定约束,使用了any作为类型,为了减少any的使用,我们可以用索引接口代替any. +例如:TemplateOptions中的formlyItemStyle属性用于指定样式数据,例如:{marginBottom: ‘0’} formlyItemStyle属性无法固定约束类型,我们使用索引接口代替any。

// TemplateOptions中的formlyItemStyle属性用于指定样式,例如:{marginBottom: '0'}
+
+// bad 
+interface TemplateOptions {
+    //...
+    formlyItemStyle?: any;
+}
+
+// 索引接口代替any
+interface TemplateOptions {
+    ...
+    formlyItemStyle?: { [key: string]: string };
+}
+

总结 🔗

代码中应减少any使用,可增加代码的可读性和可维护性,频繁使用 any 不利于项目的后续维护, +会出现本可避免的bug。

\ No newline at end of file diff --git "a/posts/typescript\351\241\271\347\233\256\345\256\236\350\267\265\345\234\250\344\275\277\347\224\250typescript\344\270\255\345\246\202\344\275\225\345\207\217\345\260\221\347\261\273\345\236\213\347\232\204\351\207\215\345\244\215\345\256\232\344\271\211/index.html" "b/posts/typescript\351\241\271\347\233\256\345\256\236\350\267\265\345\234\250\344\275\277\347\224\250typescript\344\270\255\345\246\202\344\275\225\345\207\217\345\260\221\347\261\273\345\236\213\347\232\204\351\207\215\345\244\215\345\256\232\344\271\211/index.html" new file mode 100644 index 0000000..c5b4670 --- /dev/null +++ "b/posts/typescript\351\241\271\347\233\256\345\256\236\350\267\265\345\234\250\344\275\277\347\224\250typescript\344\270\255\345\246\202\344\275\225\345\207\217\345\260\221\347\261\273\345\236\213\347\232\204\351\207\215\345\244\215\345\256\232\344\271\211/index.html" @@ -0,0 +1,67 @@ +TypeScript项目实践——在使用TypeScript中,如何减少类型的重复定义? | easystack

TypeScript项目实践——在使用TypeScript中,如何减少类型的重复定义?

+· +1310字 +· +3分钟

TypeScript 项目实践 🔗

该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用TypeScript的同行提供参考,高效正确的使用TypeScript。

在使用TypeScript中,如何减少类型的重复定义? 🔗

后台管理系统,每个模块大致就是列表、详情、表单和一些操作。在定义Typescript的时候,往往会重复定义一些类似的结构。如何减少类型的重复定义呢?

模型已经完整定义,使用部分字段作为的新的类型 🔗

在定义列表的展示信息和详情的展示信息的时候,已经定义了一个比较完善的类。之后再去处理创建、编辑之类的操作可能只需要其中几个参数。


+// 用户列表的信息
+class Uesr {
+  id: string;
+  name: string;
+  email: string;
+  createTime: string;
+}
+

例如:用户列表展示需要显示的信息比较完整,但是添加用户的时候可能只需要name、email。如果我们能把这两个属性摘出来就行了。内置的 Pick 类型,就可以完美解决我们的问题。

// 添加用户
+function create(formValue: Pick<User, 'name' | 'email'>) {
+  // todo:
+}
+
+// 为什么用 pick?我们看下Pick的实现原理
+// 意思是选取指定一组属性,并组成一个新的类型
+type Pick<T, K extends keyof T> = {
+    [P in K]: T[P];
+};
+

或者,当我们创建的时候,表单中除了 User.id,其他都是必填项。但是重新定义一个新的不包含id的类型的话,又显得有些重复了。但是用 Pick 的话,需要写的 keys 又太多了。这个时候就需要用内置 Omit 来处理了。

// 添加用户
+function create(formValue: Omit<User, 'id'>) {
+  // todo:
+}
+
+// 我们来看为什么 Omit 能满足我们的需求
+// Omit的原理就是从类型 T 中剔除 K 中的所有属性。
+// 此处的 keyof any 而不是 keyof T, 我也是有点不明白,就去查了一下。简单来说就是社区选择的结果.
+// 不明白的可以看 https://github.com/Microsoft/TypeScript/issues/30825
+type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
+
+// 这里牵扯到 Exclude 类型,这里就先介绍下吧。
+// 表示提取存在于 T,但不存在于 U 的类型组成的联合类型。
+type Extract<T, U> = T extends U ? T : never;
+

注意:此处 keyof any 得到的是 string | nuber | symbol , 因为类型 key 的类型只能为 string | nuber | symbol 。

使用方法的参数类型,作为函数的返回值 🔗

某些时候我们在某个函数上定义的参数类型,可能是跟某些参数或者某些值的类型是相同的,这个时候我们就可以直接提取此函数的参数类型。具体实现就是使用 Parameters 类型。

例如,我们我们定义了一个格式化返回数据的函数,函数中我们定义了参数类型。然后我们的请求接口返回的数据与此相同。这个时候就能直接使用格式化函数的参数类型。

// 格式化返回数据的函数
+function formatData(data: {
+  id: string;
+  name: string;
+  e_mail: string;
+  create_time: string;
+}) { 
+  // ... 
+}
+
+// 获取列表信息
+function getPersonLists(): Parameters<typeof formatData>[0][] {
+  // ...
+}
+

我们通过下图可以看出, T0 此时的类型就是 formatData 中参数 data 的类型。

在线示例

将已存在的类型,组合成新类型 🔗

在项目中,有些是需要集合其他几个类型或者类型中的部分参数而成的新类型。

// 函数中的传参可能是需要多个类型中的参数,并且有些是可选项。
+function create(options: Pick<User, 'name' | 'email'>
+  & Partial<Pick<Department, 'department' | 'project'>>
+  & Pick<UserRole, 'role' | 'type'>
+) {
+  // ...
+}
+
+// Partial 表示
+

代码中的 create 参数,需要 User 类型中的 name、email,需要 Department 中的 department、project,也需要 UserRole 中的 role、type。我们用 & 和 内置类型组合成一个新的类型。

总结 🔗

在写 Typescript 中,合理使用内置的类型,可以有效的减少重复定义类型,减少无意义的代码。当然,内置类型肯定是不限于我们所介绍的这些以及使用场景。并且我们当内置类型满足不了我们的需求的时候,我们也可以自定义类型。

\ No newline at end of file diff --git "a/posts/typescript\351\241\271\347\233\256\345\256\236\350\267\265\345\234\250\344\275\277\347\224\250typescript\344\270\255\345\246\202\344\275\225\346\255\243\347\241\256\347\232\204\350\275\254\346\215\242\347\261\273\345\236\213/index.html" "b/posts/typescript\351\241\271\347\233\256\345\256\236\350\267\265\345\234\250\344\275\277\347\224\250typescript\344\270\255\345\246\202\344\275\225\346\255\243\347\241\256\347\232\204\350\275\254\346\215\242\347\261\273\345\236\213/index.html" new file mode 100644 index 0000000..551e1dc --- /dev/null +++ "b/posts/typescript\351\241\271\347\233\256\345\256\236\350\267\265\345\234\250\344\275\277\347\224\250typescript\344\270\255\345\246\202\344\275\225\346\255\243\347\241\256\347\232\204\350\275\254\346\215\242\347\261\273\345\236\213/index.html" @@ -0,0 +1,87 @@ +TypeScript项目实践——在使用Typescript中,如何正确的转换类型? | easystack

TypeScript项目实践——在使用Typescript中,如何正确的转换类型?

+· +1132字 +· +3分钟

TypeScript 项目实践 🔗

该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用TypeScript的同行提供参考,高效正确的使用TypeScript。

在使用Typescript中,如何正确的转换类型? 🔗

类型断言是用来告诉TypeScript值的详细类型, 在实际项目中合理使用可以减少我们的工作量, 下面结合项目中的一些使用场景, 概括下类型断言在实际项目中的常见用途。

使用断言定义空对象 🔗

例如项目中存在用户对象数据, 当定义用户对象的时候, 可以使用interface的方式

interface IUser {
+  id: number;
+  name: string;
+}
+
+const user: IUser = {};
+

但是user对象一开始是个空的对象, 接口请求成功后, 才会给对象赋值, 这时候在编译时就会报错, 提示缺少id, name属性

这种情况就可以利用类型断言, 满足初始化是空对象, 后续再赋值

interface IUser {
+  id: number;
+  name: string;
+}
+
+const user: IUser = {} as IUser;
+user.id = 1;
+user.name = 'admin';
+或者可以使用尖括号<>语法
+
+
+interface IUser {
+  id: number;
+  name: string;
+}
+
+const user: IUser = <IUser>{};
+user.id = 1;
+user.name = 'Xiao Ming';
+

使用双重断言指定DOM事件类型 🔗

例如在项目中经常需要处理 DOM 事件或元素

handler(event: Event): void {
+  const mouseEvent = event as MouseEvent;
+}
+
+function handler(event: Event) {
+    let element = event as HTMLElement; // Error: 'Event' 和 'HTMLElement' 中的任何一个都不能赋值给另外一个
+}
+
+function handler(event: Event) {
+    let element = event as unknown as HTMLElement;
+}
+

在上面的例子中,若直接使用 event as HTMLElement 会报错,因为 Event 和 HTMLElement 互相不兼容。若使用双重断言,则可以打破「要使得 A 能够被断言为 B,只需要 A 兼容 B 或 B 兼容 A 即可」的限制,将任何一个类型断言为任何另一个类型。若使用了这种双重断言,那么十有八九是非常错误的,它很可能会导致运行时错误。除非迫不得已,千万别用双重断言。

使用常量断言as const约束对象和定义枚举对象 🔗

项目中在给接口赋值时, 如果某个接口里面的所有属性只能被读取而不能修改, 可以通过readonly

interface IUser { +readonly id: number; +readonly name: string; +}

const user: IUser = { +id: 1, +name: ‘admin’, +};

user.name = ‘member’; +成功限制name属性不允许修改

如果user对象里面属性比较多且数据结构复杂, 这时候限制每个属性都得处理, typescript支持在类型后面加上as const, 这样相当于给对象里面每个属性添加上了readonly

as const 用作枚举

enum roleEnum {
+  admin = 'Admin',
+  member = 'Member',
+}
+const role1 = roleEnum.admin;
+const roles = {
+  admin: 'Admin',
+  member: 'Member',
+} as const
+type ValueOf<T> = T[keyof T];
+type RoleEnumType = ValueOf<typeof roles>;
+const role2: RoleEnumType = roles.admin;
+

枚举将限制为必要的输入集,而常量字符串允许使用不属于您的逻辑的字符串。 这样可以确保在输入数据时不会因输入域中不存在的内容而出错,并且还提高了代码的可读性。

type与类型断言 🔗

例如用户数据存在id和name两个属性, 当获取对象值的时候,

type keys = 'id' | 'name';
+
+const user = {
+  id: 1,
+  name: 'admin',
+};
+
+const getValue = (key: any) => {
+  return user[key];
+};
+

提示错误

除了使用类型约束方式, 还可以利用类型断言解决(该方式常用于第三方库的callback, 对返回值类型没有约束的情况)

type keys = 'id' | 'name';
+
+const user = {
+  id: 1,
+  name: 'admin',
+};
+
+const getValue = (key: any) => {
+  return user[key as keys];
+};
+
\ No newline at end of file diff --git "a/posts/typescript\351\241\271\347\233\256\345\256\236\350\267\265\345\234\250\344\275\277\347\224\250typescript\344\270\255\351\205\215\345\220\210vscode\345\217\257\347\273\264\346\212\244\346\200\247\345\274\200\345\217\221\346\225\210\347\216\207\346\234\211\345\223\252\344\272\233\346\217\220\345\215\207/index.html" "b/posts/typescript\351\241\271\347\233\256\345\256\236\350\267\265\345\234\250\344\275\277\347\224\250typescript\344\270\255\351\205\215\345\220\210vscode\345\217\257\347\273\264\346\212\244\346\200\247\345\274\200\345\217\221\346\225\210\347\216\207\346\234\211\345\223\252\344\272\233\346\217\220\345\215\207/index.html" new file mode 100644 index 0000000..8d0d71a --- /dev/null +++ "b/posts/typescript\351\241\271\347\233\256\345\256\236\350\267\265\345\234\250\344\275\277\347\224\250typescript\344\270\255\351\205\215\345\220\210vscode\345\217\257\347\273\264\346\212\244\346\200\247\345\274\200\345\217\221\346\225\210\347\216\207\346\234\211\345\223\252\344\272\233\346\217\220\345\215\207/index.html" @@ -0,0 +1,131 @@ +TypeScript项目实践——在使用TypeScript中,配合VSCode可维护性、开发效率有哪些提升? | easystack

TypeScript项目实践——在使用TypeScript中,配合VSCode可维护性、开发效率有哪些提升?

+· +1888字 +· +4分钟

TypeScript 项目实践 🔗

该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用TypeScript的同行提供参考,高效正确的使用TypeScript。

在使用TypeScript中,配合VSCode可维护性、开发效率有哪些提升? 🔗

本文主要总结我们在开发过程遇到代码维护性、开发效率方面比较典型的问题,以及使用TypeScript带来的改善。

友好的类型提示、错误检测 🔗

在维护一个项目经常遇到各自各样问题,但最常用的解决办法肯定有一种是:

console.log 输出看下参数(或者下个断点)

分析下为什么总要看参数,原因一般也就这几种:

代码不是我写的,搞不清楚接口参数。

很久之前的写的,接口参数定义忘记了。

Javascript就这样,不下断点,能知道参数最终结构啊!(这是被坑过的。。。js自由的代价,代码不执行到当前行,永远是可变的数据结构)

例如,这样一段JavaScript代码:

const createModal = options => {
+ //... 
+}
+

在通常业务开发中很难看到清楚options参数结构描述,即使有,在多个版本迭代后也可能变了不准确,如何解决这些问题呐?在正确的使用TypeScript后,这些大部分可以得到改善,比如上面的代码使用TypeScript实现:

interface Options {
+  title?: string;
+  content?: string;
+  onOK: ()=> void;
+  onCancel: ()=> void;
+}
+
+const createModal = (options: Options)=> {
+ //... 
+}
+

通过阅读上面的代码,可以清楚的了解这个方法的参数结构,以及是否必传、参数值类型等信息,再配合VSCode可以得到这样的使用体验:

而且在TypeScript中还有枚举之类特性,可以让常量定义提示更准确,修改更便利,在编码过程中变量的值、函数入参出参数、对象的成员,可以快速、清楚知道,对开发效率的提高,体验过都会离不开。

友好的代码调用关系树,以及IDE代码重构工具支持 🔗

// 1.公共方法
+const createForm = options => {
+  options.fields.forEach(field=>{
+    if(field.inputType === 'text'){    
+      // TODO ...
+    }
+    
+    if(field.inputType === 'radio'){    
+      // TODO: ...
+    }
+  })
+}
+
+createForm({ fields:[{inputType: 'text'}] });
+createForm({ fields:[{inputType: 'radio'}] });
+
+
+// 2.业务接口变动
+
+function fetchUserInfo(){
+  // TODO: ajax request server
+  
+  const responseJson = {
+    username: 'test1',
+    realname: 'test1_nickname'
+  };
+  
+  return Promise.resolve(responseJson)
+}
+
+// calls 
+var user = await fetchUserInfo();
+var realname = user.realname;
+

像上面的示例代码中的两段代码,第一段如果要增强或者优化公共方法的options ,第二段出现需求变更导致接口数据调整,在前端代码中需要做的改动很简单但量却很大的,常规手段就是查找替换,不熟悉代码的人要改动就很难评估影响。在TypeScript中可以怎么解决这样的问题,首页要正确的声明类型,然后在使用VSCode更改代码可以这样:

快速一键改名,变量、方法、

参数变更,快速检测相关引用通知错误提示

快速查找方法引用

有这些功能支持后,优化重构代码的便利可以大大提升,可以随着业务变化对代码进行一定范围的重构,便于提高代码质量。

针对大量功能相似但又有区别抽象能力 🔗

像这样一个管理功能,增删改查很常见,在一团队里实现的人不同就有不同的设计,比如这样:

// coder a ...
+class ListAComponent {
+  list=[];
+  query(){
+    // TODO: validate filter ...
+    
+    // TODO: fetch data ...
+    
+    // TODO: update list ...
+  }
+}
+
+// coder b ...
+class ListBComponent {
+  data=[];
+  search(){
+    // TODO: validate filter ...
+    
+    // TODO: fetch data ...
+    
+    // TODO: update list ...
+  }
+}
+  
+// coder c ...
+class ListCComponent {
+  dataSet=[];
+  load(){
+    // TODO: validate filter ...
+    
+    // TODO: fetch data ...
+    
+    // TODO: update list ...
+  }
+}
+
+// ...
+

每个人都有自己的实践,其中的逻辑也有很多相似,这样容易造成一个结果,维护一段代码就要熟悉一种风格,在团队成员快速成长的过程中更是会不断变化着,但解决这种问题的办法很早就有了,为什么很少有人用呐,JavaScript缺少个关键字抽象以及配套的IDE支持,不过TypeScript中实现了,下面看一下用TypeScript怎么解决上面的问题以及能减少重复逻辑:

首先,我们根据上面的列表业务进行抽象定义公共逻辑,并定义抽象方法为列表不同部分预留需要实现接口;

abstract class ListComponent<Row, Filter> {
+  list = new Array<Row>();
+  loading = false;
+
+  onSearch(): Promise<void> {
+    // validate filter
+    if (!this.checkFilter(this.filter)) {
+      return Promise.resolve();
+    }
+
+    this.loading = true;
+
+    // fetch data
+    return this
+      .search(this.filter)
+      .then((rows) => {
+        // update list
+        this.list = rows;
+        this.loading = false;
+      })
+      .catch(error => {
+        // update list (error handle)
+        this.list = [];
+        this.loading = false;
+        this.toast(error.message);
+      })
+
+  }
+
+  toast(message: string) {
+    // TODO: toast message
+  };
+  abstract filter: Filter;
+  protected abstract checkFilter(filter: Filter): boolean;
+  protected abstract search(filter: Filter): Promise<Row[]>;
+}
+

然后,列表去继承功能并实现接口,像下面这样:

配合IDE可以快速得到我们需要做的事情,只关注实现不同的业务逻辑、定义显示的数据结构、以及显示部分,同时提升项目可维护性,类似的功能可以得到一样的接口就能更快速的理解逻辑。

总结 🔗

以上是我在前端代码维护中遇到问题,以及在使用正确使用TypeScript定义类型后如何解决,这里强调下要正确使用,因为TypeScript的一个特色是JavaScript类型的超集,它可以编译成纯JavaScript ,所以完全是可以在按照JavaScript的方式去用,我想这本意是让使用者快速适应,但是实际应用中会造成另一个问题没有类型,按照JavaScript的方式编码有时候会没有类型声明,会产生很多any 、unknown 类型,这个一定要在项目禁止,最次也要减少影响范围,不然上面的一切美好可能都会成为虚妄。

\ No newline at end of file diff --git "a/posts/\344\272\221\345\216\237\347\224\237redis\345\256\236\350\267\265/index.html" "b/posts/\344\272\221\345\216\237\347\224\237redis\345\256\236\350\267\265/index.html" new file mode 100644 index 0000000..fca82a3 --- /dev/null +++ "b/posts/\344\272\221\345\216\237\347\224\237redis\345\256\236\350\267\265/index.html" @@ -0,0 +1,14 @@ +云原生Redis实践 | easystack

云原生Redis实践

+· +12309字 +· +25分钟

云原生Redis实践 🔗

Redis与容器实践探索 🔗

Redis云原生控制器设计 🔗

什么是云原生控制器 🔗

设计思想

云原生控制器是控制器设计模式在云原生场景下的变种,即将云原生技术与控制器模式相结合,形成基于云原语指定的视图进行执行模式。

云原生控制器一般存在于对云基础设施资源或云上软件基础设施资源的控制管理的场景。控制器与基础设施模型建立Observe关系,通过视图对基础设施模型进行CRUD操作,触发控制器执行对应操作,完成对基础设施资源的处理。

K8S Operator

K8S Operator SDK是目前使用最广泛的云原生控制器框架,采用controller-runtime框架,集成了K8S Informer,对监听到的Model(CRD)CRUD事件进行了缓存处理,以便控制器能够高效处理回调请求。

通过K8S Operator实现统一云原生控制面

云原生控制面的统一可以减轻API管理带来的技术负债,利用K8S APIServer的横向扩展能力,定义控制器对应的GVR(GroupVersionResource),将抽象基础设施模型合并为具象的业务视图,统一API原语,实现控制面的统一。

业务控制面与业务数据面分离 🔗

在实际应用场景中,我们会将控制器与生产业务进行分离设计,分离设计可以带来诸多优势:

  • 可用域隔离,生产业务压力激增时,可以单独进行业务数据面维护,不会抢占控制器资源,导致控制器宕机。
  • 业务数据面基础资源不受部署介质限制,业务数据面所需要的硬件资源与业务控制面差别较大,不建议混合。
  • 在云上软件基础设施场景,业务数据面一般具备较强的弹性,控制器可以对底层平台进行水平伸缩(副本调节或容量扩缩,例如对IaaS层资源进行扩容,并触发K8S HPA),而不影响控制器本身高可用性。
  • 业务控制面与业务数据面可独立升级,实现解耦式进化。

通过Operator对云原生Redis实例进行业务层控制 🔗

在Redis容器化部署场景下,使用Operator作为控制器对Redis生命周期以及业务控制进行管理,是比较合适的方案。

控制器在操作容器化Redis实例时,可以通过暴露的SVC地址或Operator通过监听业务数据面对应CR的变更,访问对应SVC或在对应的业务数据面运行对应的Job,来对Redis业务进行管理:

Redis实例的生命周期管理

Redis实例的生命周期包含实例的创建、删除、修改等过程,这些过程被设计为通过云原生控制器接管时,能够很大程度提升业务面的容错性,使状态机解耦。

当Redis创建事件进入Informer队列后,会随即被控制器取出,并创创建对应的K8S资源,并对应修改Redis CR Model的状态字段,完成生命周期管理。

Redis业务控制

业务控制通过在业务数据平面启动Job Pod的方式,非侵入式对Redis业务进行操作。

多控制器 🔗

采用K8S Operator作为云原生控制器,能够支持多控制器模式,我们可以通过在Operator中增加一组控制器对多个Model资源进行控制,实现Redis业务层面功能的扩展:

Redis云原生高可用架构模式 🔗

高可用和可拓展是我们考量Redis的两个重要指标,其中高可用在Redis运行时中保障业务运行时的稳定性以及数据的稳定性提供了了至关重要的作用,下面,我们以Redis部署形态为维度,对云原生场景下Redis的高可用方案实现进行阐述。

K8S 是一个容器编排系统,可以自动化容器应用的部署、扩展和管理。K8S 提供了一些基础特性:

  • 自动装箱
  • 服务发现与负载均衡
  • 自动化上线和回滚
  • 水平扩缩容
  • 存储编排
  • 自我修复

Redis 云原生的目标,主要有以下几个:

  • **资源的抽象和交付由 K8S 来完成,无需再关注具体机型。**在物理机时代我们需要根据不同机型上的 CPU 和内存配置来决定每个机型的机器上可以部署的 Redis 实例的数量。通过 Redis 云原生,我们只需要跟 K8s 声明需要的 CPU 和内存的大小,剩下的调度、资源供给、机器筛选由 K8s 来完成。
  • **节点的调度由 K8S 来完成。**在实际部署一个 Redis 集群时,为了保证高可用,需要让 Redis 集群的一些组件满足一定的放置策略。要满足放置策略,在物理机时代需要运维系统负责完成机器的筛选以及计算的逻辑,这个逻辑相对比较复杂。K8s 本身提供了丰富的调度能力,可以轻松实现这些放置策略,从而降低运维系统的负担。
  • **节点的管理和状态保持由 K8S 完成。**在物理机时代,如果某台物理机挂了,需要运维系统介入了解其上部署的服务和组件,然后在另外一些可用的机器节点上重新拉起新的节点,填补因为机器宕机而缺少的节点。如果由 K8s 来完成节点的管理和状态的保持,就可以降低运维系统的复杂度。
  • **标准化 Redis 的部署和运维的模式。**尽量减少人工介入,提升运维自动化能力,这是最重要的一点。

Redis 架构模式 🔗

下面介绍一下云原生高可用支持的 Redis 架构模式。

单机版Redis实例 🔗

默认情况下,每台Redis服务器都是主节点。

单机版限制:

  1. 内存容量有限
  2. 处理能力有限
  3. 无法高可用

读写分离版Redis实例 🔗

Redis 的复制(replication)功能允许用户根据一个 Redis 服务器来创建任意多个该服务器的复制品,其中被复制的服务器为主服务器(master),而通过复制创建出来的服务器复制品则为从服务器(slave)。

Master一般是用来做write,Slave一般是用来read。这样的结构可以有效的缓解系统的读写压力。

因为Master是负责write,那么就会出现主从数据的不同步,就需要通过sync,把Master的数据同步给Slave。

  • 全量复制:slave刚接入,需要把master上的数据全部同步
  • 增量复制:slave,网络中断,超时,导致部分数据需要增量同步

优点:

  1. 支持主从复制,可以进行读写分离
  2. 为了分载Master的读操作压力,Slave服务器可以为客户端提供只读操作的服务,写服务仍然必须由Master来完成
  3. Slave同样可以接受其它Slaves的连接和同步请求,这样可以有效的分载Master的同步压力。
  4. Master Server是以非阻塞的方式为Slaves提供服务。所以在Master-Slave同步期间,客户端仍然可以提交查询或修改请求。
  5. Slave Server同样是以非阻塞的方式完成数据同步。在同步期间,如果有客户端提交查询请求,Redis则返回同步之前的数据

缺点:

  1. Redis不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的IP才能恢复。
  2. 主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性。
  3. Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。

哨兵版Redis实例 🔗

Redis Sentinel 是一个分布式系统中监控 Redis 主从服务器的监视器,并在主服务器下线时自动进行故障转移。其中三个特性:

  • ​ 监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
  • ​ 提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
  • ​ 自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作。

优点:

  1. 保证高可用
  2. 监控各个节点
  3. 自动故障迁移

缺点:

  1. 主从模式切换需要时间,会丢数据
  2. 需要维护sentinel的信息,水平扩展很难处理
  3. 没有解决 master 写的压力

集群版Redis实例 🔗

Redis 集群(Cluster)实例采用分布式架构,每个节点都采用一主一从的高可用架构,能够进行自动容灾切换和故障迁移。多种集群规格可适配不同的业务压力,可按需扩展数据库性能。每个数据分片均为双副本(分别部署在不同机器上)高可用架构,主节点发生故障后,系统会自动进行主备切换保证服务高可用。

Redis 集群是一个由多个节点组成的分布式服务器群,它具有复制、高可用和分片特性;Redi s集群没有中心节点,并且带有复制和故障转移特性,这可以避免单个节点成为性能瓶颈,或者因为某个节点下线而导致整个集群下线。

以下图所示的三主三从集群为例,每个主节点处理各自的数据,提供读写能力,从节点异步复制主节点的数据。

Redis集群 方案,采用的是虚拟槽分区,槽范围是0-16383,一共有16384个槽(Slots)。槽(Slots)是集群内数据管理和迁移的基本单位。比如上图的集群中有3个主节点,每个节点大致负责5500个槽的读写,节点会维护自身负责的虚拟槽。cluster集群中的主节点负责处理槽(存储数据),从节点则是主节点的复制品。

优点:

  1. 能够提升Redis横向扩容能力
  2. 数据分片存储,减少单个节请求压力
  3. slave在master故障时,能够提升为master对外提供服务,提升集群整理可用性

缺点:

  1. 对Client有一定要求,需要使用支持Move操作的smartClient,根据请求找到数据对应分槽节点
  2. 需要为Client暴露所有节点的地址,使Client可以访问该节点分槽中的数据
  3. 在节点达到一定规模(1000左右),由于分槽元数据同步会使得整个Redis性能大幅下降。

代理模式版Redis实例 🔗

在Redis的集群和读写分离架构中,代理服务器(Proxy)承担着路由转发、负载均衡与故障转移等职责。

代理服务器(Proxy)是Redis实例中的一个组件,不会占用数据分片的资源,通过多个Proxy节点实现负载均衡及故障转移。

优点:

  1. 客户端(Jedis)直连有限的proxy节点,会比较轻量和简单
  2. 集群规模理论上可以非常大,因为proxy对外隐藏了集群规模

缺点:

  1. 多了一层proxy访问,性能会有影响
  2. 需要第三方的proxy实现,集群水平扩容时proxy要想平滑动态更新集群配置,需要开发工具支持
  3. 代理层对Redis访问请求进行了转发,其能够支持的请求命令相较于原生Redis存在一定限制(根据选用代理框架不同,限制的程度则不同)

目前比较流行的代理框架:

  • predixy:高性能全特征redis代理,支持Redis Sentinel和Redis Cluster。
  • redis-cluster-proxy:Redis官方推出的集群模式代理组件,能够代理集群模式下的Redis,但无法支持上游认证且在Redis商业化后,该项目已不再维护。
  • twemproxy:快速、轻量级memcached和redis代理,Twitter推特公司开发,目前业界广泛采用的redis代理。
  • codis:redis集群代理解决方案,豌豆荚公司开发,相对比较成熟。
  • envoy:由Lyft推出并贡献给CNCF社区,目前已经成为CNCF毕业项目,作为service mash的sidecar,提供redis集群代理模式与多主代理模式。
Envoy代理模型 🔗

Evnoy提供了Redis多主的代理模式,通过Envoy,可以轻松将Client请求负载到后端多master上,并提供了多种负载模型,能够实现多写、单写、多写单写结合等方式负载。

Envoy作为Redis代理,同样能够支持集群模式,通过随机访问集群模式中的一个节点,通过CLUSTER SLOT命令获取到当前集群的分槽、节点信息,并缓存在Envoy的_redis_addresses对象中,在访问对应分槽数据时,对Client请求进行转发,能够解决集群模式需要对外暴露地址的问题,请求由Envoy进行拦截,并找对对应的数据分槽,进行转发,最后汇总到Client,并且支持AUTH命令的转发,在Envoy中同样可以为Redis设置上游Client认证密码与下游Redis集群认证密码,提供上下游加密模式,提升集群整体安全性。

Codis代理模型 🔗

Twemproxy是推特开源的,它最大的缺点就是无法平滑的扩缩容,而Codis解决了Twemproxy扩缩容的问题,而且兼容了Twemproxy。

Codis把所有的key分为1024个槽,每一个槽位都对应了一个分组,具体槽位的分配,可以进行自定义。首先要根据CRC32算法,针对Key算出32位的哈希值,除以1024取余,就能算出这个Key属于哪个槽,根据槽与分组的映射关系去对应的分组当中处理数据。CRC全称是循环冗余校验,主要在数据存储和通信领域保证数据正确性的校验手段。

但是codis proxy它本身也存在单点问题,所以需要对proxy做一个集群。Codis使用Zookeeper来保存映射关系,由proxy上来同步配置信息,它不止支持zookeeper,还有etcd和本地文件。

Redis在云原生下的可观测性 🔗

可观测性(Observability)是指通过一个系统向外输出的信息了解其内部发生的事情的能力。Kubernetes 体系下的云原生应用天生就有着动态管理、频繁扩缩容的特性,我们通过监控告警、日志查询、事件记录等能力来保证 Redis 在云原生下的可观测性。

监控告警 🔗

作为伴随着 Kubernetes 共同成长并发展成熟的开源项目,Prometheus 已经成为了云原生场景下监控的事实标准。它有着高效、可拓展、易于集成、易于管理等优势,并且还拥有强大的数据模型和查询语言 PromQL。我们在 Redis 云原生实践中的监控功能也是结合 Prometheus 完成的,下面对其实现方案做相关介绍。

监控系统架构示意图

负责向 Prometheus 暴露监控指标数据的程序一般称之为 exporter ,它收集大部分 Redis的状态指标和性能指标,满足日常监控需求。

Redis 在诸如主从或是 Cluster 的集群模式下都会存在多个节点,为了保证可以收集到每个节点的监控指标,我们将 Redis Exporter 与 Redis 节点本身通过同一个 Pod 来部署。同一个 Redis 实例会创建出一个 Metrics Service 将收集的指标暴露出来,供 Prometheus 采集。

云原生场景下的 Prometheus 通常都会采用 Operator 模式来部署,借由此不但可以云原生的部署和管理诸多组件,还可以充分利用 Kubernetes 的诸如 ConfigMap,CRD(Custom Resource Definition)等能力来简化配置。

在传统部署模式下,Prometheus 的监控目标(Target)一般会通过 static_config 的方式进行静态配置或是通过一个服务注册中心来完成。ServiceMonitor CRD 允许声明式地定义需要被监控的Target Service 的动态集合,它根据 Prometheus 的公开约定通过 Label 来筛选目标,然后通过这些约定自动发现新的目标而无需重新配置。

对于 Redis Exporter 暴露出的大量指标,我们需要根据自己的需要进行过滤和处理。一般取最关心的连接数、内存使用和 Key 数量等关键指标通过 PromQL 查询处理后供前端展示即可。

通过对于上述监控指标设置告警阈值指标,对接平台的告警中心(Alertmanager)即可实现告警功能。

日志查询 🔗

日志系统负责记录着应用运行过程中产生的日志,相较于监控,可以更为细致的展现程序的运行状态。在云原生场景下,容器中运行程序产生的日志一般都直接输出到stdout,由相应的日志采集器负责统一收集。

日志系统架构示意图

我们在云原生 Redis 中的日志主要分两部分:工作负载中应用程序产生的日志和Redis 本身的运行日志。如架构图所示,两者分别位于控制面与数据面,为了进行跨平面的统一日志查询,需要一些工具来帮助搭建日志查询系统。

Fluentd 与 Fluent Bit 🔗

Fluentd 也是从 CNCF 毕业的项目,凭借着高效灵活的特性在云原生时代逐渐替代了传统ELK(ElasticSearch,Logstash,Kibana)系统中日志采集器(Logstash)组件。在我们的架构中,它主要起到聚合器的作用,而具体到各个容器的的采集工作则由它的同源项目 Fluent Bit 来完成。

Fluent Bit 则是在 Fluentd 生态系统下诞生的一个更轻量的新项目,它占用资源更少,性能更高,也更适合作为云原生场景下的日志采集器。

完成日志采集之后,剩下的存储、分析、查询及展示工作则仍交给 Elasticsearch 和 Kibana 这对经典组合。

Elasticsearch 与 Kibana 🔗

Elasticsearch 是一个准实时的分布式存储、搜索、分析数据库引擎。相较传统数据库,它自带分词功能,对模糊查询的支持更为强大,非常适合用来存储日志信息。Kibana 则是与 Elasticsearch 协同的日志分析和可视化展示工具,包括数据分析和图表化展示等功能。

由于云原生架构下的日志极为分散,通过上述工具搭建的能将集群日志、应用日志、系统日志等进行统一采集管理的平台则显得尤为重要,同时也是保障系统可观测性的关键一环。

事件记录 🔗

事件记录分为两部分:用户的操作事件和系统的操作事件。前者负责记录用户的关键操作,包括操作人、时间、动作、级别和类型等信息。后者则负责记录 Redis 实例的一些状态变更。

事件记录可以很好地进行问题定位和追溯。通过对接告警中心,系统管理员还可以定义一些关键操作用短信、钉钉或者邮件的方式通知到相应人员,提高系统的管理能力。

Redis性能提升 🔗

Redis对象系统与数据结构分析 🔗

Redis中有很多常见的数据结构,如简单动态字符串(SDS)、双端链表、字典、压缩列表、整数集合等等,但Redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构构建了一个对象系统。对象系统中包含了字符串对象、列表对象、哈希对象、集合对象和有序集合对象这五种类型,这五种类型也是我们在日常使用Redis数据库中直接操作的对象。

通过对象系统,Redis可以针对不同的使用场景为对象设置不同的数据结构实现,从而优化对象在不同场景下的使用效率。

字符串 🔗

Redis中用到最多的就是字符串的使用,作为一种常见的数据类型,Redis没有直接使用C语言传统的字符串表示,而是自己创建了一种动态字符串(SDS)。

优势(主要针对与C语言字符串的比较):

  • 常数复杂度获取字符串长度
    • 针对C字符串必须便利整个字符串才能获取长度。
  • 杜绝缓冲区溢出
    • 针对要修改字符串内容时,当需要修改的长度大于已分配长度,并且没有重新分配空间,导致溢出。
    • SDS API在对SDS进行修改时,会自动将空间扩展至所需大小,不需要手动操作。
  • 空间预分配:不仅为SDS分配修改所需要的空间,还会额外分配未使用的空间(基于当前修改的最大值 预期下次分配的结果,已使用和未使用free各占一半)
    • 惰性释放:SDS字符串缩短操作时不立即重新分配,而是使用free属性记录下来,等待将来使用。

有序集合 🔗

有序集合类型 (Sorted Set或ZSet) 相比于集合类型多了一个排序属性 score(分值),对于有序集合 ZSet 来说,每个存储元素相当于有两个值组成的,一个是有序结合的元素值,一个是排序值。有序集合保留了集合不能有重复成员的特性(分值可以重复),但不同的是,有序集合中 的元素可以排序。

有序集合的底层编码实现主要是跳跃表和压缩列表。使用压缩列表的判断条件**(同时满足):**

  • 有序集合的数量小于128个。
  • 所有元素成员的长度都小于64字节。
压缩列表 🔗

当一个列表键之包含少量的列表项,并且列表项要么就是小数值,要么就是长度比较短的字符 串,那么Redis就会将压缩列表来作为列表键的底层实现。(压缩列表是列表键和哈希键的底层实现之一)

压缩列表存在的意义是为了节约内存,之所以说这种存储结构节省内存,是相较于数组的存储思路而言的。我们知道,数组要求每个元素的大小相同,如果我们要存储不同长度的字符串,那我们就需要用最大长度的字符串大小作为元素的大小(假设是20个字节)。存储小于 20 个字节长度的字符串的时候,便会浪费部分存储空间。

数组的优势占用一片连续的空间可以很好的利用CPU缓存访问数据。如果我们想要保留这种优势,又想节省存储空间我们可以对数组进行压缩。

但是这样有一个问题,我们在遍历它的时候由于不知道每个元素的大小是多少,因此也就无法计算出下一个节点的具体位置。这个时候我们可以给每个节点增加一个lenght的属性以此来记录前一个节点的具体位置。

跳跃表 🔗

跳跃表是一种有序数据结构,通过在每个节点中维持多个指向其它节点的指针,从而达到快速访问节点的目的,作为Redis中有序集合键的底层实现之一。

跳跃表在链表的基础上增加了多级索引以提升查找的效率,但其是一个空间换时间的方案,必然会带来一个问题——索引是占内存的。原始链表中存储的有可能是很大的对象,而索引结点只需要存储关键值值和几个指针,并不需要存储对象,因此当节点本身比较大或者元素数量比较多的时候,其优势必然会被放大,而缺点则可以忽略。

列表对象 🔗

列表(list)类型是用来存储多个有序的字符串,列表中的每个字符串称为元素(element),一个列表最多可以存储232-1个元素。在Redis中,可以对列表两端插入(push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素等。列表是一种比较灵活的数据结构,它可以充当栈和队列的角色,在实际开发上有很多应用场景。有以下特点:

  • 列表中的元素是有序的,可以通过索引下标获取某个元素或者某个范围内的元素列表。
  • 列表中的元素可以是重复的。

当元素的个数(512)以及元素的值(64字节)时,使用压缩列表,否则则使用双端链表。

哈希对象 🔗

哈希类型的内部编码有两种:压缩列表和哈希表。只有当存储的数据量比较小的情况下,Redis 才使用压缩列表来实现字典类型。具体需要满足两个条件:

  • 当哈希类型元素个数小于hash-max-ziplist-entries配置(默认512个)
  • 所有值都小于hash-max-ziplist-value配置(默认64字节)

ziplist使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。当哈希类型无法满足ziplist的条件时,Redis会使用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1)。

集合对象 🔗

集合类型的内部编码有两种:

  • intset(整数集合):当集合中的元素都是整数且元素个数小于set-maxintset-entries配置(默认512个)时,Redis会选用intset来作为集合的内部实现,从而减少内存的使用。
  • hashtable(哈希表):当集合类型无法满足intset的条件时,Redis会使用hashtable作为集合的内部实现

Redis内存消耗与优化 🔗

Redis是基于内存的数据库,内存占用过高会导致Redis响应变慢,甚至会引起数据丢失以及故障恢复变慢等问题。

Redis内存消耗主要在于其主进程消耗和子进程消耗。而主进程消耗又主要包括自身内存、对象内存、缓冲区内存、内存碎片五个方面。

自身内存 🔗

Redis自身内存指的是进程本身所占用的内存,通常可以忽略不计(一个空的Redis进程所消耗的内存几乎忽略不计)

对象内存 🔗

对象内存的占用主要是来源于五种常见数据结构(字符串、列表、哈希、集合、有序集合)的存储,不同的数据结构在不同的场景下使用不同的底层实现。因此在使用Redis的使用过程中需要根据场景选择合适的对象,从而避免内存溢出。

Redis的每一种对象都是key-value的键值对形式。每个键值对的创建都包含两个对象,key对象和value对象。因此对象内存的消耗可以理解为sizeof(key)+sizeof(value)。而key对象都是字符串类型的,在使用过程中我们不应该忽略key对象所占用的内存,应该避免使用过大的key。

缓冲区内存 🔗

Redis主要有三个缓冲区,客户端缓冲区、AOF缓冲区、复制积压缓冲区。

缓冲区的优势有两个:

  • 缓和CPU和I/O设备速度不匹配
  • 减少磁盘同一时间的读写速度

但是使用不当时也会带来很多问题,常见的问题就是超出内存限制导致内存溢出,如写的速度过快导致缓冲区内存需求增加、存入大数据等等。

客户端缓冲区是为了解决客户端和服务端请求和处理速度不匹配问题的,它又分为输入和输出缓冲区。输入缓冲区会先把客户端发送过来的命令暂存起来,Redis 主线程再从输入缓冲区中读取命令,进行处理。当在处理完数据后,会把结果写入到输出缓冲区,再通过输出缓冲区返回给客户端。

AOF缓冲区是AOF持久化时所用到的缓冲区,AOF缓冲区消耗的内存取决于AOF重写时间和写入命令量。

复制积压缓冲区则是在集群环境中为了保证主从节点数据同步的所设置的。主节点在向从节点传输 RDB 文件的同时,会继续接收客户端发送的写命令请求。这些写命令就会先保存在复制缓冲区中,等 RDB 文件传输完成后,再发送给从节点去执行。主节点上会为每个从节点都维护一个复制缓冲区,来保证主从节点间的数据同步。

内存碎片 🔗

内存碎片主要是由于操作系统的内存分配机制和Redis内存分配器的分配策略所决定的。内存分配器为了更好地管理和重复利用内存, 分配内存策略一般采用固定范围的内存块进行分配。例如当保存5KB对象时内存分配器可能会采用8KB的块存储, 而剩下的3KB空间变为了内存碎片不能再分配给其他对象存储。

子进程内存消耗 🔗

子进程内存消耗主要指执行AOF/RDB重写时Redis创建的子进程内存消耗。Redis持久化在执行RDB快照和AOF重写时主进程会fork出一个子进程,由子进程完成快照和重写操作,虽然使用了写时复制的技术,子进程可以不用完全复制父进程的所有物理内存,但是仍然需要复制其内存页表,在此期间如果有写入操作则需要复制出一份副本出来。因此子进程同样会消耗一部分内存,其消耗的内存量取决于RDB和AOF期间的写入命令量。在执行RDB和AOF重写的时候为了防止内存溢出,会预留一部分内存。

Redis性能进行诊断分析与优化 🔗

Redis采用单线程模型,当执行某个命令耗时较长时会影响其它命令的执行效率。因此,虽然Redis是一个高性能低时延的缓存服务,但是如果在耗时较长的任务发生时,任然会有性能问题出现。

内存诊断与优化 🔗
内存交换引起的性能问题 🔗

内存使用率是Redis服务在使用过程中十分重要的一个性能指标,如果Redis实例的内存使用率超过最大可用内存,那么操作系统会将内存与Swap空间交换,把内存中旧的或不再使用的内容写入硬盘上的Swap分区,以便留出新的物理内存给新页或活动页(page)使用。

通常我们可以在客户端使用“info memory”查看Redis服务使用的内存情况,其中“used_memery”表示Redis服务已使用的内存,当“used_memory”>最大可用内存时,Redis实例正在进行内存交换或者已经内存交换完毕。如果Redis进程上发生内存交换,那么Redis及使用Redis数据的应用性能都会受到严重影响。

在硬盘上进行读写操作要比在内存上进行读写操作,时间上慢了近5个数量级,内存是0.1μs单位、而硬盘是10ms。如果Redis进程上发生内存交换,那么Redis和依赖Redis上数据的应用会受到严重的性能影响。 通过查看used_memory指标可知道Redis正在使用的内存情况,如果used_memory>可用最大内存,那就说明Redis实例正在进行内存交换或者已经内存交换完毕。管理员根据这个情况,执行相对应的应急措施。

跟踪内存使用率 🔗

如果在使用Redis期间没有使用持久化策略,那么缓存数据在Redis崩溃时就有丢失的危险。因为当Redis内存使用率超过可用内存的95%时,部分数据开始在内存与swap空间来回交换,这时就可能有丢失数据的危险。

当开启并触发快照功能时,Redis会fork一个子进程把当前内存中的数据完全复制一份写入到硬盘上。因此若是当前使用内存超过可用内存的45%时触发快照功能,那么此时进行的内存交换会变的非常危险(可能会丢失数据)。 倘若在这个时候实例上有大量频繁的更新操作,问题会变得更加严重。

通过减少Redis的内存占用率,来避免这样的问题,或者使用下面的技巧来避免内存交换发生:

  • 对于缓存数据小于4GB,可选择采用32位的Redis实例,达到较少内存的占用空间效果。
  • 尽可能的使用Hash数据结构。因为Redis在储存小于100个字段的Hash结构上,其存储效率是非常高的。
  • 设置key的过期时间,Redis会在key过期时自动删除key。
  • 回收key。在Redis配置文件中(Redis.conf),通过设置“maxmemory”属性的值可以限制Redis最大使用的内存,修改后重启实例生效。
延迟诊断与优化 🔗

在Redis实例中,可以通过跟踪命令处理总数(total_commands_processed)来判断延迟问题所在,因为Redis是个单线程模型,客户端过来的命令是按照顺序执行的。比较常见的延迟是带宽,通过千兆网卡的延迟大约有200μs。倘若明显看到命令的响应时间变慢,延迟高于200μs,那可能是Redis命令队列里等待处理的命令数量比较多。 如上所述,延迟时间增加导致响应时间变慢可能是由于一个或多个慢命令引起的,这时可以看到每秒命令处理数在明显下降,甚至于后面的命令完全被阻塞,导致Redis性能降低。要分析解决这个性能问题,需要跟踪命令处理数的数量和延迟时间。

当客户端明显发现响应时间过慢时,可以通过记录的total_commands_processed历史数据值来判断命理处理总数是上升趋势还是下降趋势,以便排查问题。

跟踪Redis延迟性能 🔗

Redis之所以这么流行的主要原因之一就是低延迟特性带来的高性能,所以说解决延迟问题是提高Redis性能最直接的办法。拿1G带宽来说,若是延迟时间远高于200μs,那明显是出现了性能问题。 虽然在服务器上会有一些慢的IO操作,但Redis是单核接受所有客户端的请求,所有请求是按良好的顺序排队执行。因此若是一个客户端发过来的命令是个慢操作,那么其他所有请求必须等待它完成后才能继续执行。

可以通过一下几个方法分析延迟性能问题:

  • 使用slowlog查看引发延迟的慢命令
  • 监控客户端的连接
  • 限制客户端的连接数
  • 加强内存管理
内存碎片诊断与优化 🔗

内存碎片率同样是影响Redis服务的重要性能指标之一。内存碎片率(mem_fragmenttation_ratio)稍大于1是合理的,这个值表示内存碎片率比较低,也说明redis没有发生内存交换。但如果内存碎片率超过1.5,那就说明Redis消耗了实际需要物理内存的150%,其中50%是内存碎片率。若是内存碎片率低于1的话,说明Redis内存分配超出了物理内存,操作系统正在进行内存交换。

内存碎片的优化方法:

  • 重启Redis服务器,将额外产生的内存碎片失效并重新作为新内存使用。
  • 限制内存交换,增加可用物理内存或减少实Redis内存占用。
  • 修改内存分配器。

针对Redis相关的性能优化,首先可以根据业务需要选择合适的数据类型,并为不同的应用场景设置相应的紧凑存储参数。其次如果不需要进行数据持久化,可以关闭以提高处理性能和内存使用率。最后需要控制实际内存的使用率以及客户端的连接数。

\ No newline at end of file diff --git "a/posts/\350\256\260\344\270\200\346\254\241kubelet-\345\206\205\345\255\230\346\263\204\346\274\217/index.html" "b/posts/\350\256\260\344\270\200\346\254\241kubelet-\345\206\205\345\255\230\346\263\204\346\274\217/index.html" new file mode 100644 index 0000000..15aba61 --- /dev/null +++ "b/posts/\350\256\260\344\270\200\346\254\241kubelet-\345\206\205\345\255\230\346\263\204\346\274\217/index.html" @@ -0,0 +1,112 @@ +记一次kubelet 内存泄漏 | easystack

记一次kubelet 内存泄漏

+· +2020字 +· +5分钟

背景 🔗

一次生产环境中,发现 kubelet 内存达到上 GB 以上,这个不符合平常使用情况,首先想到的是内存泄漏,那么肯定使用 pprof 以及调查为什么会产生内存泄漏

profile 🔗

基础 🔗

  • cpu /debug/pprof/profile,主要分析耗时和优化算法,得到 profile 文件
  • heap: /debug/pprof/heap,查看活动对象的内存分配情况,得到 profile 文件
  • threadcreate: /debug/pprof/threadcreate, 线程创建概况报告程序中导致创建新的操作系统线程的部分
  • goroutine: 报告所有当前 goroutine 的堆栈信息,没有 profile 文件
  • trace: /debug/pprof/trace 当前程序的执行跟踪,go tool trace 中使用

kubelet 🔗

查看平台的内存使用情况,如下 +注:(以下数据非当时环境,是之后重新复现后取的,比发生泄漏环境要低的多)

# top
+  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
+31599 root      20   0 2877396 983.5m  66424 S   4.3  6.2  28:00.93 kubelet           
+

kubelet 数据默认打开 pprof, 通过 pprof 拿到数据 , 通过以下命令方式

# pprof -tls_ca {cafile} -tls_key {keyfile} -tls_cert {certfile}
+ https://{host}:10250/debug/pprof/heap
+

拿到数据后,还可以拿到正常节点的数据做对比,如下

正常 kubelet 内存使用

不正常 kubelet 内存使用

基础 🔗

描述下 kubelet 当前目录和会涉及的代码片段

pkg/kubelet/
+├── cri #cri接口
+├── server # http hanlder入口
+├── stats # 容器 cpu/memory,filesystem info 抓取
+├── kuberuntime # 桥接容器运行时 和 kubelet操作
+└── cm #container manager缩写,控制器内容包括 cgroup,topology,device等
+

可以直接跨越到 cri/ 内查看 当前接口实现,未设置超时时间

func (r *remoteRuntimeService) ListContainerStats(filter *runtimeapi.ContainerStatsFilter) ([]*runtimeapi.ContainerStats, error) {
+	// Do not set timeout, because writable layer stats collection takes time.
+	// TODO(random-liu): Should we assume runtime should cache the result, and set timeout here?
+	ctx, cancel := getContextWithCancel()
+	defer cancel()
+	// 这里未设置超时时间
+	resp, err := r.runtimeClient.ListContainerStats(ctx, &runtimeapi.ListContainerStatsRequest{
+		Filter: filter,
+	})
+

当前调用链大致如下 , 可以看到及时客户端退出,但是 kubelet 到 containerd 的连接还是会保持,直到拿到数据为止

那么考虑就是如何复现这个问题,但通过测试代码 [1] 并未复现问题,这里其实一直是阻塞调查的地方,那么不妨换个思路,现继续挖下为什么 containerd 没有返回数据呢?

到这里,其实可以发现 kubelet 都集中在 stats.ListPodStats 花费上,该调用会最终到 containerd 的 Stats 接口上,那么应该继续分析 containerd ,这应该是产生问题的根本原因

containerd 🔗

通过抓取 heap 和 goroutine 信息分析,而我们环境中 containerd 的 debug socket 已经打开,所以比较方便拿到 containerd profile 信息

# ctr pprof heap > heap.profile
+# ctr pprof goroutines > goroutine.profile
+

拿到数据后,还可以拿到正常节点的数据做对比

正常 containerd 内存使用

不正常 containerd 内存使用

看到的是 SPDY 内存消耗较多,我们知道 SPDY 是 HTTP/2 前身,主要用于流式连接,当前主要是 接口 exec, attach, portfoward 在接口上,以下是简单对 exec 的代码理解 , 然后使用了 exec 确能稳定复现问题,复现代码如下

#!/bin/bash
+i=0
+NUM=$1
+while [ "$i" -lt $NUM ]; do
+   (kubectl exec -i test-74cf75b654-gw5hk -- ls /opt)&
+   let "i += 1"
+done
+

exec 流程 🔗

流程需要从 kubelet 开始分析 , 通过前文中对 kubelet 目录的简单介绍,那么入口肯定是在 server 目录内,直接到主题 Exec,对应函数是 getExec(request *restful.Request, response *restful.Response) , 行为简单描述如下 +- 校验参数,通常 stdout/stderr 为 true,目前常用的是 -it,也就是需要配置 stdin 和 ttry 是否为 true +- 查询 pod 当前是否支持 exec +- 执行 GetExec 获取 url +- 创建代理,转发 apiserver 数据 stdin 和 stdout

cri 服务 主要分为两部分,重点描述 ServerExec +- GetExec:创建 url 路由,增加 token host:port/exec/{token} +- ServerExec: 创建 task 和 process,并绑定标准输入和输出等

ServerExec

  • 升级 http 为 SPDY stream,看上去 spdy 是比较重,因为要至少建立三个 goroutine handler.waitForStreams(streamCh, expectedStreams, expired.C)
  • 执行 Exec (在 streamRuntime 内),等待 process 结束,或者上游退出
//创建Task, task实际是 containerd/containerd/task.go 类型
+task, err := container.Task(ctx, nil)
+if err != nil {
+	return nil, errors.Wrap(err, "failed to load task")
+}
+pspec := spec.Process
+pspec.Args = opts.cmd
+pspec.Terminal = opts.tty
+if opts.tty {
+	oci.WithEnv([]string{"TERM=xterm"})(ctx, nil, nil, spec)
+}
+
+volatileRootDir := c.getVolatileContainerRootDir(id)
+var execIO *cio.ExecIO
+// 创建process, 实际是 containerd/containerd/process.go 类型
+process, err := task.Exec(ctx, execID, pspec,
+	func(id string) (containerdio.IO, error) {
+		var err error
+		execIO, err = cio.NewExecIO(id, volatileRootDir, opts.tty, opts.stdin != nil)
+		return execIO, err
+	},
+)
+// 获取 process 退出管道
+exitCh, err := process.Wait(ctx)
+err := process.Start(ctx)
+
+// 将execIO 和 http 流绑定
+// 内部会创建协程 stdin ,stdout, waitGroup
+attachDone := execIO.Attach(cio.AttachOptions{
+	Stdin:     opts.stdin,
+	Stdout:    opts.stdout,
+	Stderr:    opts.stderr,
+	Tty:       opts.tty,
+	StdinOnce: true,
+	CloseStdin: func() error {
+		return process.CloseIO(ctx, containerd.WithStdinCloser)
+	},
+})
+
+select {
+case <-execCtx.Done():
+case exitRes := <-exitCh:
+}
+

这里是阻塞在调用 exec 上,但是和 cpuAndMemoryStats 并无关系,如果是有关系,那么也只有在 shim 这一级,因为都要调用 shim 的 rpc 接口

shim 🔗

当前 runc 使用的 shim 是 containerd-shim-runc-v2, 在内置的 shim server 上有一个方便调试的方式,发送 USER1 信号 可以获取 goroutines 信息,具体代码如下

// runtime/v2/shim/shim_unix.go
+func setupDumpStacks(dump chan<- os.Signal) {
+	signal.Notify(dump, syscall.SIGUSR1)
+}
+
// 发送 USER1 信号
+kill -10 {pid}
+// 获取containerd 日志,获取BEGIN goroutine stack dump 日志
+journalctl -eu containerd >contaienrd.log
+

查看 shim 的 goroutines 信息,因为和 ttrpc 有关,那直接找 ttrpc 信息吧,可以发现以下信息,这里显然不应该有那么多 goroutine hang 在 vendor/github.com/containerd/ttrpc/server.go:444 行,那么继续看下 ttrpc 有关实现呢

$ cat shim.goroutine.profile |grep ttrpc/server | sort |uniq -c |sort
+      1 	vendor/github.com/containerd/ttrpc/server.go:362 +0x149
+      1 	vendor/github.com/containerd/ttrpc/server.go:404 +0x5ee
+      1 	vendor/github.com/containerd/ttrpc/server.go:431 +0x41a
+      1 	vendor/github.com/containerd/ttrpc/server.go:459 +0x6bd
+      1 	vendor/github.com/containerd/ttrpc/server.go:87 +0x107
+      2 	vendor/github.com/containerd/ttrpc/server.go:127 +0x2a7
+      2 	vendor/github.com/containerd/ttrpc/server.go:332 +0x2ce
+      6 	vendor/github.com/containerd/ttrpc/server.go:438 +0xf2
+    164 	vendor/github.com/containerd/ttrpc/server.go:444 +0x245
+    169 	vendor/github.com/containerd/ttrpc/server.go:434 +0x63f
+

ttrpc 🔗

reqCh, respCh, msgCh 均为非缓存 channel

服务端大致如下

  • 每创建一个连接, 都会产生 2 个协程来处理会话
  • recv 协程: 从客户端读取数据,并校验合法性写入 Channel 中,交给 worker 来处理
  • worker 协程:收到请求后,并发创建协程调用注册的服务

1.0.1 版本 客户端大致如下

  • 和服务端类似,工作协程同时处理接收和发送请求,
  • 重点是 发送和接收是在一个协程中处理,并通过内部 waitCall 来同步数据

发生死锁的情况是 客户端 阻塞在 send 过程,此时因为无法处理返回的 Resp 信息,继而导致服务端的应答数据阻塞在 net write buffer 中 +什么情况会导致 send 阻塞,网络发送过程有以下情况 , 进程将数据拷贝到内核缓存区,之后由软中断发送出去,该控制写缓存大小为 tcp_wmem, 内核参数配置为 4096 16384 4194304

针对上述导致死锁的情况,有一个相关 patch[2] 解决,该 patch 修改方式如下图

1.1.0 版本 客户端

发送和接收 都通过不同的协程处理,不再出现竞争情况发生

参考 🔗

[1]. https://gist.github.com/yylt/0d3f2d554fa7eddd9cafe406ef0c9d75 +[2]. https://github.com/containerd/ttrpc/pull/94

\ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000..5758e31 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1 @@ +https://easystack.github.io/categories/2022-04-06T15:40:35+08:00https://easystack.github.io/2022-04-06T15:40:35+08:00https://easystack.github.io/posts/2022-04-06T15:40:35+08:00https://easystack.github.io/tags/2022-04-06T15:40:35+08:00https://easystack.github.io/tags/typescript/2022-04-06T15:40:35+08:00https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%E7%AC%AC%E4%B8%89%E6%96%B9%E4%BE%9D%E8%B5%96/2022-04-06T15:40:35+08:00https://easystack.github.io/tags/%E5%89%8D%E7%AB%AF/2022-04-06T15:40:35+08:00https://easystack.github.io/categories/%E5%89%8D%E7%AB%AF/2022-04-06T15:40:35+08:00https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E6%AD%A3%E7%A1%AE%E7%9A%84%E8%BD%AC%E6%8D%A2%E7%B1%BB%E5%9E%8B/2022-04-06T15:35:35+08:00https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E5%87%8F%E5%B0%91%E7%B1%BB%E5%9E%8B%E7%9A%84%E9%87%8D%E5%A4%8D%E5%AE%9A%E4%B9%89/2022-04-06T15:11:31+08:00https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E5%87%8F%E5%B0%91any%E4%BD%BF%E7%94%A8/2022-04-06T14:58:47+08:00https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E9%85%8D%E5%90%88vscode%E5%8F%AF%E7%BB%B4%E6%8A%A4%E6%80%A7%E5%BC%80%E5%8F%91%E6%95%88%E7%8E%87%E6%9C%89%E5%93%AA%E4%BA%9B%E6%8F%90%E5%8D%87/2022-04-06T14:46:52+08:00https://easystack.github.io/tags/container/2022-03-18T14:00:43+08:00https://easystack.github.io/tags/k8s/2022-03-18T14:00:43+08:00https://easystack.github.io/tags/paas/2022-03-18T14:00:43+08:00https://easystack.github.io/tags/redis/2022-03-18T14:00:43+08:00https://easystack.github.io/posts/%E4%BA%91%E5%8E%9F%E7%94%9Fredis%E5%AE%9E%E8%B7%B5/2022-03-18T14:00:43+08:00https://easystack.github.io/categories/container/2021-11-09T15:50:43+08:00https://easystack.github.io/tags/containerd/2021-11-09T15:50:43+08:00https://easystack.github.io/tags/kubelete/2021-11-09T15:50:43+08:00https://easystack.github.io/categories/problem/2021-11-09T15:50:43+08:00https://easystack.github.io/posts/%E8%AE%B0%E4%B8%80%E6%AC%A1kubelet-%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/2021-11-09T15:50:43+08:00https://easystack.github.io/posts/containerd-%E7%AE%80%E4%BB%8B/2021-10-09T15:50:43+08:00 \ No newline at end of file diff --git a/tags/container/index.html b/tags/container/index.html new file mode 100644 index 0000000..11e6c97 --- /dev/null +++ b/tags/container/index.html @@ -0,0 +1,10 @@ +container | easystack
avatar

easystack

Open Computer To Futhre

\ No newline at end of file diff --git a/tags/container/index.xml b/tags/container/index.xml new file mode 100644 index 0000000..771897c --- /dev/null +++ b/tags/container/index.xml @@ -0,0 +1 @@ +container on easystackhttps://easystack.github.io/tags/container/Recent content in container on easystackHugo -- gohugo.iozhFri, 18 Mar 2022 14:00:43 +0800云原生Redis实践https://easystack.github.io/posts/%E4%BA%91%E5%8E%9F%E7%94%9Fredis%E5%AE%9E%E8%B7%B5/Fri, 18 Mar 2022 14:00:43 +0800https://easystack.github.io/posts/%E4%BA%91%E5%8E%9F%E7%94%9Fredis%E5%AE%9E%E8%B7%B5/云原生Redis实践 🔗Redis与容器实践探索 🔗Redis云原生控制器设计 🔗什么是云原生控制器 🔗设计思想 云原生控制器是控制器设计模式在云原生Containerd 简介https://easystack.github.io/posts/containerd-%E7%AE%80%E4%BB%8B/Sat, 09 Oct 2021 15:50:43 +0800https://easystack.github.io/posts/containerd-%E7%AE%80%E4%BB%8B/历史 🔗 Docker Daemon从最初集成在docker命令中(1.11版本前), 独立成单独二进制程序(1.11版本开始)2016.12 containe \ No newline at end of file diff --git a/tags/containerd/index.html b/tags/containerd/index.html new file mode 100644 index 0000000..375a8d3 --- /dev/null +++ b/tags/containerd/index.html @@ -0,0 +1,8 @@ +containerd | easystack
avatar

easystack

Open Computer To Futhre

\ No newline at end of file diff --git a/tags/containerd/index.xml b/tags/containerd/index.xml new file mode 100644 index 0000000..7cb2d3d --- /dev/null +++ b/tags/containerd/index.xml @@ -0,0 +1 @@ +containerd on easystackhttps://easystack.github.io/tags/containerd/Recent content in containerd on easystackHugo -- gohugo.iozhTue, 09 Nov 2021 15:50:43 +0800记一次kubelet 内存泄漏https://easystack.github.io/posts/%E8%AE%B0%E4%B8%80%E6%AC%A1kubelet-%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/Tue, 09 Nov 2021 15:50:43 +0800https://easystack.github.io/posts/%E8%AE%B0%E4%B8%80%E6%AC%A1kubelet-%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/背景 🔗一次生产环境中,发现 kubelet 内存达到上 GB 以上,这个不符合平常使用情况,首先想到的是内存泄漏,那么肯定使用 pprof 以及调查为什么会产生内存泄漏 profile 🔗基 \ No newline at end of file diff --git a/tags/index.html b/tags/index.html new file mode 100644 index 0000000..af1e559 --- /dev/null +++ b/tags/index.html @@ -0,0 +1,14 @@ +easystack
avatar

easystack

Open Computer To Futhre

TYPESCRIPT (5) +前端 (5) +CONTAINER (2) +CONTAINERD (1) +K8S (1) +KUBELETE (1) +PAAS (1) +REDIS (1)
\ No newline at end of file diff --git a/tags/index.xml b/tags/index.xml new file mode 100644 index 0000000..e8bede8 --- /dev/null +++ b/tags/index.xml @@ -0,0 +1 @@ +Tags on easystackhttps://easystack.github.io/tags/Recent content in Tags on easystackHugo -- gohugo.iozhWed, 06 Apr 2022 15:40:35 +0800TypeScripthttps://easystack.github.io/tags/typescript/Wed, 06 Apr 2022 15:40:35 +0800https://easystack.github.io/tags/typescript/前端https://easystack.github.io/tags/%E5%89%8D%E7%AB%AF/Wed, 06 Apr 2022 15:40:35 +0800https://easystack.github.io/tags/%E5%89%8D%E7%AB%AF/containerhttps://easystack.github.io/tags/container/Fri, 18 Mar 2022 14:00:43 +0800https://easystack.github.io/tags/container/k8shttps://easystack.github.io/tags/k8s/Fri, 18 Mar 2022 14:00:43 +0800https://easystack.github.io/tags/k8s/paashttps://easystack.github.io/tags/paas/Fri, 18 Mar 2022 14:00:43 +0800https://easystack.github.io/tags/paas/redishttps://easystack.github.io/tags/redis/Fri, 18 Mar 2022 14:00:43 +0800https://easystack.github.io/tags/redis/containerdhttps://easystack.github.io/tags/containerd/Tue, 09 Nov 2021 15:50:43 +0800https://easystack.github.io/tags/containerd/kubeletehttps://easystack.github.io/tags/kubelete/Tue, 09 Nov 2021 15:50:43 +0800https://easystack.github.io/tags/kubelete/ \ No newline at end of file diff --git a/tags/k8s/index.html b/tags/k8s/index.html new file mode 100644 index 0000000..2d241a4 --- /dev/null +++ b/tags/k8s/index.html @@ -0,0 +1,10 @@ +k8s | easystack
avatar

easystack

Open Computer To Futhre

\ No newline at end of file diff --git a/tags/k8s/index.xml b/tags/k8s/index.xml new file mode 100644 index 0000000..32c0302 --- /dev/null +++ b/tags/k8s/index.xml @@ -0,0 +1 @@ +k8s on easystackhttps://easystack.github.io/tags/k8s/Recent content in k8s on easystackHugo -- gohugo.iozhFri, 18 Mar 2022 14:00:43 +0800云原生Redis实践https://easystack.github.io/posts/%E4%BA%91%E5%8E%9F%E7%94%9Fredis%E5%AE%9E%E8%B7%B5/Fri, 18 Mar 2022 14:00:43 +0800https://easystack.github.io/posts/%E4%BA%91%E5%8E%9F%E7%94%9Fredis%E5%AE%9E%E8%B7%B5/云原生Redis实践 🔗Redis与容器实践探索 🔗Redis云原生控制器设计 🔗什么是云原生控制器 🔗设计思想 云原生控制器是控制器设计模式在云原生 \ No newline at end of file diff --git a/tags/kubelete/index.html b/tags/kubelete/index.html new file mode 100644 index 0000000..85d92a7 --- /dev/null +++ b/tags/kubelete/index.html @@ -0,0 +1,8 @@ +kubelete | easystack
avatar

easystack

Open Computer To Futhre

\ No newline at end of file diff --git a/tags/kubelete/index.xml b/tags/kubelete/index.xml new file mode 100644 index 0000000..f0e84be --- /dev/null +++ b/tags/kubelete/index.xml @@ -0,0 +1 @@ +kubelete on easystackhttps://easystack.github.io/tags/kubelete/Recent content in kubelete on easystackHugo -- gohugo.iozhTue, 09 Nov 2021 15:50:43 +0800记一次kubelet 内存泄漏https://easystack.github.io/posts/%E8%AE%B0%E4%B8%80%E6%AC%A1kubelet-%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/Tue, 09 Nov 2021 15:50:43 +0800https://easystack.github.io/posts/%E8%AE%B0%E4%B8%80%E6%AC%A1kubelet-%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/背景 🔗一次生产环境中,发现 kubelet 内存达到上 GB 以上,这个不符合平常使用情况,首先想到的是内存泄漏,那么肯定使用 pprof 以及调查为什么会产生内存泄漏 profile 🔗基 \ No newline at end of file diff --git a/tags/paas/index.html b/tags/paas/index.html new file mode 100644 index 0000000..5e597e3 --- /dev/null +++ b/tags/paas/index.html @@ -0,0 +1,10 @@ +paas | easystack
avatar

easystack

Open Computer To Futhre

\ No newline at end of file diff --git a/tags/paas/index.xml b/tags/paas/index.xml new file mode 100644 index 0000000..ad1c6c6 --- /dev/null +++ b/tags/paas/index.xml @@ -0,0 +1 @@ +paas on easystackhttps://easystack.github.io/tags/paas/Recent content in paas on easystackHugo -- gohugo.iozhFri, 18 Mar 2022 14:00:43 +0800云原生Redis实践https://easystack.github.io/posts/%E4%BA%91%E5%8E%9F%E7%94%9Fredis%E5%AE%9E%E8%B7%B5/Fri, 18 Mar 2022 14:00:43 +0800https://easystack.github.io/posts/%E4%BA%91%E5%8E%9F%E7%94%9Fredis%E5%AE%9E%E8%B7%B5/云原生Redis实践 🔗Redis与容器实践探索 🔗Redis云原生控制器设计 🔗什么是云原生控制器 🔗设计思想 云原生控制器是控制器设计模式在云原生 \ No newline at end of file diff --git a/tags/redis/index.html b/tags/redis/index.html new file mode 100644 index 0000000..1d48d75 --- /dev/null +++ b/tags/redis/index.html @@ -0,0 +1,10 @@ +redis | easystack
avatar

easystack

Open Computer To Futhre

\ No newline at end of file diff --git a/tags/redis/index.xml b/tags/redis/index.xml new file mode 100644 index 0000000..8a24226 --- /dev/null +++ b/tags/redis/index.xml @@ -0,0 +1 @@ +redis on easystackhttps://easystack.github.io/tags/redis/Recent content in redis on easystackHugo -- gohugo.iozhFri, 18 Mar 2022 14:00:43 +0800云原生Redis实践https://easystack.github.io/posts/%E4%BA%91%E5%8E%9F%E7%94%9Fredis%E5%AE%9E%E8%B7%B5/Fri, 18 Mar 2022 14:00:43 +0800https://easystack.github.io/posts/%E4%BA%91%E5%8E%9F%E7%94%9Fredis%E5%AE%9E%E8%B7%B5/云原生Redis实践 🔗Redis与容器实践探索 🔗Redis云原生控制器设计 🔗什么是云原生控制器 🔗设计思想 云原生控制器是控制器设计模式在云原生 \ No newline at end of file diff --git a/tags/typescript/index.html b/tags/typescript/index.html new file mode 100644 index 0000000..583ec69 --- /dev/null +++ b/tags/typescript/index.html @@ -0,0 +1,12 @@ +TypeScript | easystack
avatar

easystack

Open Computer To Futhre

\ No newline at end of file diff --git a/tags/typescript/index.xml b/tags/typescript/index.xml new file mode 100644 index 0000000..b3e2fb2 --- /dev/null +++ b/tags/typescript/index.xml @@ -0,0 +1 @@ +TypeScript on easystackhttps://easystack.github.io/tags/typescript/Recent content in TypeScript on easystackHugo -- gohugo.iozhWed, 06 Apr 2022 15:40:35 +0800TypeScript项目实践——在使用Typescript中,如何使用第三方依赖?https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%E7%AC%AC%E4%B8%89%E6%96%B9%E4%BE%9D%E8%B5%96/Wed, 06 Apr 2022 15:40:35 +0800https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%E7%AC%AC%E4%B8%89%E6%96%B9%E4%BE%9D%E8%B5%96/TypeScript 项目实践 🔗该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用TTypeScript项目实践——在使用Typescript中,如何正确的转换类型?https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E6%AD%A3%E7%A1%AE%E7%9A%84%E8%BD%AC%E6%8D%A2%E7%B1%BB%E5%9E%8B/Wed, 06 Apr 2022 15:35:35 +0800https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E6%AD%A3%E7%A1%AE%E7%9A%84%E8%BD%AC%E6%8D%A2%E7%B1%BB%E5%9E%8B/TypeScript 项目实践 🔗该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用TTypeScript项目实践——在使用TypeScript中,如何减少类型的重复定义?https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E5%87%8F%E5%B0%91%E7%B1%BB%E5%9E%8B%E7%9A%84%E9%87%8D%E5%A4%8D%E5%AE%9A%E4%B9%89/Wed, 06 Apr 2022 15:11:31 +0800https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E5%87%8F%E5%B0%91%E7%B1%BB%E5%9E%8B%E7%9A%84%E9%87%8D%E5%A4%8D%E5%AE%9A%E4%B9%89/TypeScript 项目实践 🔗该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用TTypeScript项目实践——在使用Typescript中,如何减少any使用?https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E5%87%8F%E5%B0%91any%E4%BD%BF%E7%94%A8/Wed, 06 Apr 2022 14:58:47 +0800https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E5%87%8F%E5%B0%91any%E4%BD%BF%E7%94%A8/TypeScript 项目实践 🔗该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用TTypeScript项目实践——在使用TypeScript中,配合VSCode可维护性、开发效率有哪些提升?https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E9%85%8D%E5%90%88vscode%E5%8F%AF%E7%BB%B4%E6%8A%A4%E6%80%A7%E5%BC%80%E5%8F%91%E6%95%88%E7%8E%87%E6%9C%89%E5%93%AA%E4%BA%9B%E6%8F%90%E5%8D%87/Wed, 06 Apr 2022 14:46:52 +0800https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E9%85%8D%E5%90%88vscode%E5%8F%AF%E7%BB%B4%E6%8A%A4%E6%80%A7%E5%BC%80%E5%8F%91%E6%95%88%E7%8E%87%E6%9C%89%E5%93%AA%E4%BA%9B%E6%8F%90%E5%8D%87/TypeScript 项目实践 🔗该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用T \ No newline at end of file diff --git "a/tags/\345\211\215\347\253\257/index.html" "b/tags/\345\211\215\347\253\257/index.html" new file mode 100644 index 0000000..500fd73 --- /dev/null +++ "b/tags/\345\211\215\347\253\257/index.html" @@ -0,0 +1,12 @@ +前端 | easystack
avatar

easystack

Open Computer To Futhre

\ No newline at end of file diff --git "a/tags/\345\211\215\347\253\257/index.xml" "b/tags/\345\211\215\347\253\257/index.xml" new file mode 100644 index 0000000..bfaadf1 --- /dev/null +++ "b/tags/\345\211\215\347\253\257/index.xml" @@ -0,0 +1 @@ +前端 on easystackhttps://easystack.github.io/tags/%E5%89%8D%E7%AB%AF/Recent content in 前端 on easystackHugo -- gohugo.iozhWed, 06 Apr 2022 15:40:35 +0800TypeScript项目实践——在使用Typescript中,如何使用第三方依赖?https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%E7%AC%AC%E4%B8%89%E6%96%B9%E4%BE%9D%E8%B5%96/Wed, 06 Apr 2022 15:40:35 +0800https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%E7%AC%AC%E4%B8%89%E6%96%B9%E4%BE%9D%E8%B5%96/TypeScript 项目实践 🔗该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用TTypeScript项目实践——在使用Typescript中,如何正确的转换类型?https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E6%AD%A3%E7%A1%AE%E7%9A%84%E8%BD%AC%E6%8D%A2%E7%B1%BB%E5%9E%8B/Wed, 06 Apr 2022 15:35:35 +0800https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E6%AD%A3%E7%A1%AE%E7%9A%84%E8%BD%AC%E6%8D%A2%E7%B1%BB%E5%9E%8B/TypeScript 项目实践 🔗该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用TTypeScript项目实践——在使用TypeScript中,如何减少类型的重复定义?https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E5%87%8F%E5%B0%91%E7%B1%BB%E5%9E%8B%E7%9A%84%E9%87%8D%E5%A4%8D%E5%AE%9A%E4%B9%89/Wed, 06 Apr 2022 15:11:31 +0800https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E5%87%8F%E5%B0%91%E7%B1%BB%E5%9E%8B%E7%9A%84%E9%87%8D%E5%A4%8D%E5%AE%9A%E4%B9%89/TypeScript 项目实践 🔗该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用TTypeScript项目实践——在使用Typescript中,如何减少any使用?https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E5%87%8F%E5%B0%91any%E4%BD%BF%E7%94%A8/Wed, 06 Apr 2022 14:58:47 +0800https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E5%A6%82%E4%BD%95%E5%87%8F%E5%B0%91any%E4%BD%BF%E7%94%A8/TypeScript 项目实践 🔗该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用TTypeScript项目实践——在使用TypeScript中,配合VSCode可维护性、开发效率有哪些提升?https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E9%85%8D%E5%90%88vscode%E5%8F%AF%E7%BB%B4%E6%8A%A4%E6%80%A7%E5%BC%80%E5%8F%91%E6%95%88%E7%8E%87%E6%9C%89%E5%93%AA%E4%BA%9B%E6%8F%90%E5%8D%87/Wed, 06 Apr 2022 14:46:52 +0800https://easystack.github.io/posts/typescript%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%E5%9C%A8%E4%BD%BF%E7%94%A8typescript%E4%B8%AD%E9%85%8D%E5%90%88vscode%E5%8F%AF%E7%BB%B4%E6%8A%A4%E6%80%A7%E5%BC%80%E5%8F%91%E6%95%88%E7%8E%87%E6%9C%89%E5%93%AA%E4%BA%9B%E6%8F%90%E5%8D%87/TypeScript 项目实践 🔗该系列文章主要是总结TypeScript在项目中实践经验,遇到什么样的问题,针对具体问题的解决方案,目的是给希望使用或正在使用T \ No newline at end of file diff --git a/themes/mini/CHANGELOG b/themes/mini/CHANGELOG deleted file mode 100644 index 44dccc9..0000000 --- a/themes/mini/CHANGELOG +++ /dev/null @@ -1,19 +0,0 @@ -## 2021.08.21 Version 2.2.0 - -- [x] Support multiple languages - -## 2021.08.17 Version 2.1.0 - -- [x] Adding ability to disable disqus comments on a single post - -## 2021.03.01 Version 2.0.0 - -- [x] Refactor -- [x] Support hugo 0.80.0 - - -## 2017.12.09 Version 1.0.0 - -- [x] Support twitter card. -- [x] Remove name in `.Site.Params`. It's duplicate with `.Site.title`. -- [x] Add `author` in `.Site.Params`. diff --git a/themes/mini/LICENSE.md b/themes/mini/LICENSE.md deleted file mode 100644 index a261a9f..0000000 --- a/themes/mini/LICENSE.md +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 201-present [nodejh](http://nodejh.com) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/themes/mini/README-zh_CN.md b/themes/mini/README-zh_CN.md deleted file mode 100644 index e89454e..0000000 --- a/themes/mini/README-zh_CN.md +++ /dev/null @@ -1,212 +0,0 @@ -# Hugo Theme Mini - -[English](https://github.com/nodejh/hugo-theme-mini/tree/master/README.md) | 简体中文 - -一个简约的响应式 Hugo 主题。 - -![./images/screenshot.png](https://raw.githubusercontent.com/nodejh/hugo-theme-mini/master/images/screenshot.png) - -- [在线 Demo](https://nodejh.github.io/hugo-theme-mini) -- [示例网站源码](https://github.com/nodejh/hugo-theme-mini/tree/master/exampleSite) - -特性: - -- 快 -- 简约 -- 响应式 -- 归档页 -- 标签页 - - -## 1. 安装 - - -### 1.1 使用 Hugo 模块安装 (推荐) - -> ⚠️ 如果你使用的是 [二进制包](https://gohugo.io/getting-started/installing/#binary-cross-platform) 安装的 Hugo,那么你需要在电脑上安装 Go 语言。 你可以使用下面的命令检查是否安装 Go: -> ``` -> $ go version -> ``` -> Go 语言从 v1.14 开始支持模块. [下载 Go](https://golang.org/dl/)。 - -1. 在项目目录初始化 hugo 模块系统,如果之前已经执行过则忽略此步骤: - - ```bash - $ hugo mod init github.com// - ``` - -2. 在 `config.yaml` 中添加主题: - - ```yaml - theme: - - github.com/nodejh/hugo-theme-mini - ``` - -### 1.2 使用 Git Submodule 安装 - - -1. 在项目目录中执行下面的命令,将 hugo-theme-mini 作为 submodule: - - ```bash - $ git submodule add https://github.com/nodejh/hugo-theme-mini.git themes/mini - ``` - -2. 在 `config.yaml` 中配置主题: - - ```yaml - theme: mini - ``` - -更多信息可参考 Hugo 官方文档 [setup guide](//gohugo.io/overview/installing/)。 - - -## 2. 开始使用 - -成功安装主题后,在生成网站前还需要进行少部分的配置。 - - -### 2.1 修改配置文件 - -在 [`exampleSite`](https://github.com/nodejh/hugo-theme-mini/tree/master/exampleSite) 目录中有一个 [`config.yaml`](https://github.com/nodejh/hugo-theme-mini/blob/master/exampleSite/config.yaml) 的配置文件,你可以将其复制到你的项目根目录中,将一些配置项修改为你的配置。这些配置都可以随意修改。 - - -> ⚠️ 你需要删除这行配置: `themesDir: ../../` 。 - -### 2.2 默认语言 - -你可以通过 `defaultContentLanguage` 配置设置默认语言: - -```yaml -defaultContentLanguage: en -``` - -默认是 `en`。目前支持以下语言: - -- `en`: 英语 -- `zh`: 汉语 -- `nl`: 荷兰语 - -更多关于多语言的信息可以参考:[Multilingual Mode](https://gohugo.io/content-management/multilingual/)。 - - -### 2.2 评论功能 - -要使用评论功能,你需要添加下面的配置: - -- 设置 Disqus: `disqusShortname: your-disqus-shorname` -- 启用评论: - - ```yaml - params: - enableComments: true - ``` - -### 2.3 Google 分析 - -要使用 Google 分析功能,你需要添加下面的配置: - -- 设置 Google Analytics ID: `googleAnalytics: your-google-analytics-id` -- 启用 Google Analytics: - - ```yaml - params: - enableGoogleAnalytics: true - ``` - -### 2.4 Logo 和 favicon - -你可以替换网站中的 Log 和 favicon,只需要将你的图片放在网站的 `static/images` 中,并分别命名为 `avatar.png` 和 `avicon.ico`。下面是项目目录示例: - -```shell -- content -- static -└── images - ├── avatar.png - └── favicon.ico -``` - -### 2.5 运行网站 - -为了检查网站运行情况,你可以在本地启动 hugo server: - -```bash -$ hugo server -``` - -现在你就可以在浏览器中打开 http://localhost:1313 查看你的网站了。 - -### 2.6 生产环境 - -如果要将网站部署到生产环境 (例如支持 Google Analytics),你需要在 `hugo` 命令前增加环境变量 `HUGO_ENV=production`。例如: - -```bash -HUGO_ENV=production hugo -``` - -注意:上面的命令对 Windows 无效。如你使用 Windows,则需要使用下面的命令: - -```bash -set HUGO_ENV=production -hugo -``` - -## 3. 可选配置 - -### 3.1 Table of Content - -如果要启用目录,你可以将 `showToc` 设置为 `true`: - -```yaml -showToc: true -``` - - -### 3.2 在某页面禁用评论 - -要在某页面禁用评论,你可以在页面的 Front Matter 中将 `enableComments` 设置为 `false`。 - -例如: - -```yaml ---- -title: Some title -enableComments: false ---- -``` - -### 3.3 自定义 CSS 和 JS - -你可以将自定义 CSS 和 JS 放在 `static` 中,也可以使用远程的 CSS 或 JS 文件。 - -例如: - -```yaml -customCSS: - - css/custom.css # local css in `static/css/custom.css` - - https://example.com/custom.css # remote css -customJS: - - js/custom.js # local js in `static/js/custom.js` - - https://example.com/custom.js # remote js -``` - - -### 3.4 数学排版 - -该主题使用了 [KaTeX](https://katex.org/) 来支持数学符号拍版。 - -- 全局支持数学排版:在项目的配置文件中将 `math` 设置为 `true` -- 在某页面支持数学拍版:在某页面 Front Matter 中将 `math` 设置为 `true` - -### 3.5 在首页隐藏文章摘要 - -如果要在首页隐藏文章摘要,你可以将 `hiddenPostSummaryInHomePage` 设置为 `true`,默认是 `false`。 - -例如: - -```yaml -hiddenPostSummaryInHomePage: true -``` - -## License - -[MIT](https://github.com/nodejh/hugo-theme-mini/blob/master/LICENSE.md) \ No newline at end of file diff --git a/themes/mini/README.md b/themes/mini/README.md deleted file mode 100644 index fcf2681..0000000 --- a/themes/mini/README.md +++ /dev/null @@ -1,210 +0,0 @@ -# Hugo Theme Mini - -English | [简体中文](https://github.com/nodejh/hugo-theme-mini/tree/master/README-zh_CN.md) - -A fast, minimalist and responsive hugo theme. - -![./images/screenshot.png](https://raw.githubusercontent.com/nodejh/hugo-theme-mini/master/images/screenshot.png) - -- [Online demo](https://nodejh.github.io/hugo-theme-mini) -- [Example Site Source](https://github.com/nodejh/hugo-theme-mini/tree/master/exampleSite) - -Features: - -- Fast -- Minimalist -- Responsive -- Archive -- Tags - - -## 1. Installation - - -### 1.1 As a Hugo Module (recommended) - -> ⚠️ If you installed a [Hugo binary](https://gohugo.io/getting-started/installing/#binary-cross-platform), you may not have Go installed on your machine. To check if Go is installed: -> ``` -> $ go version -> ``` -> Go modules were considered production ready in v1.14. [Download Go](https://golang.org/dl/). - -1. From your project's root directory, initiate the hugo module system if you haven't already: - - ```bash - $ hugo mod init github.com// - ``` - -2. Add the theme's repo to your `config.yaml`: - - ```yaml - theme: - - github.com/nodejh/hugo-theme-mini - ``` - -### 1.2 As Git Submodule - -1. Inside the folder of your Hugo site run: - - ```bash - $ git submodule add https://github.com/nodejh/hugo-theme-mini.git themes/mini - ``` - -2. Add the theme's directory to your `config.yaml`: - - ```yaml - theme: mini - ``` - -For more information read the official [setup guide](//gohugo.io/overview/installing/) of Hugo. - - -## 2. Getting started - -After installing the theme successfully it requires a just a few more steps to get your site running. - - -### 2.1 The config file - -Take a look inside the [`exampleSite`](https://github.com/nodejh/hugo-theme-mini/tree/master/exampleSite) folder of this theme. You'll find a file called [`config.yaml`](https://github.com/nodejh/hugo-theme-mini/blob/master/exampleSite/config.yaml). To use it, copy the [`config.yaml`](https://github.com/nodejh/hugo-theme-mini/blob/master/exampleSite/config.yaml) in the root folder of your Hugo site. Feel free to change the strings in this theme. - -> ⚠️ You may need to delete the line: `themesDir: ../../` - -### 2.2 Default Content Language - -You can set default content language by `defaultContentLanguage`: - -```yaml -defaultContentLanguage: en -``` - -Default is `en`. Now support: - -- `en`: English -- `zh`: Chinese -- `nl`: Dutch - -More about multiple languages: [Multilingual Mode](https://gohugo.io/content-management/multilingual/). - -### 2.3 Add Comments - -To enable comments, add following to your config file: - -- Disqus shortname: `disqusShortname: your-disqus-shortname` -- Enable Comment: - - ```yaml - params: - enableComments: true - ``` - -### 2.4 Google Analytics - -To enable google analytics, add following to your config file: - -- Google Analytics ID: `googleAnalytics: your-google-analytics-id` -- Enable Google Analytics: - - ```yaml - params: - enableGoogleAnalytics: true - ``` - -### 2.5 Logo and favicon - -You can replace the log in the top of each page and favicon with your own images. To do that put your own logo and favicon into the `images` directory of your website static directory, then named them `avatar.png` and `favicon.ico`. For example: - -``` -- content -- static -└── images - ├── avatar.png - └── favicon.ico -``` - -### 2.6 Nearly finished - -In order to see your site in action, run Hugo's built-in local server. - -```bash -$ hugo server -``` - -Now enter http://localhost:1313 in the address bar of your browser. - -### 2.7 Production - -To run in production (e.g. to have Google Analytics show up), run HUGO_ENV=production before your build command. For example: - -```bash -HUGO_ENV=production hugo -``` - -Note: The above command will not work on Windows. If you are running a Windows OS, use the below command: - -```bash -set HUGO_ENV=production -hugo -``` - - -## 3. Optional Configuration - -### 3.1 Table of Content - -To enable table of content, you could set `showToc` to `true`. - -For example: - -```yaml -showToc: true -``` - -### 3.2 Disable Comments on a single post - -You can set `enableComments` to `false` in front matter to disable disqus comments on a single post. - -For example: - -```yaml ---- -title: Some title -enableComments: false ---- -``` - -### 3.3 Custom CSS and JS - -You can put your custom css and js files to `static` directory, or use remote css and js files which start with `http://` or `https://`. - -For example: - -```yaml -customCSS: - - css/custom.css # local css in `static/css/custom.css` - - https://example.com/custom.css # remote css -customJS: - - js/custom.js # local js in `static/js/custom.js` - - https://example.com/custom.js # remote js -``` - -### 3.4 Math Typesetting - -Mathematical notation is enabled by [KaTeX](https://katex.org/). - -- To enable KaTex globally set the parameter `math` to `true` in project’s configuration -- To enable KaTex on a per page basis include the parameter `math` to `true` in content files - -### 3.5 Hidden Post Summary in Home Page - -To hidden post summary in home page, you could set `hiddenPostSummaryInHomePage` to `true`, default is `false`. - -For example: - -```yaml -hiddenPostSummaryInHomePage: true -``` - -## License - -[MIT](https://github.com/nodejh/hugo-theme-mini/blob/master/LICENSE.md) diff --git a/themes/mini/archetypes/default.md b/themes/mini/archetypes/default.md deleted file mode 100644 index 93e7403..0000000 --- a/themes/mini/archetypes/default.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: "{{ replace .TranslationBaseName "-" " " | title }}" -date: {{ .Date }} -tags: -categories: -draft: false - ---- diff --git a/themes/mini/data/months_nl.yaml b/themes/mini/data/months_nl.yaml deleted file mode 100644 index c979892..0000000 --- a/themes/mini/data/months_nl.yaml +++ /dev/null @@ -1,12 +0,0 @@ -1: "januari" -2: "februari" -3: "maart" -4: "april" -5: "mei" -6: "juni" -7: "juli" -8: "augustus" -9: "september" -10: "oktober" -11: "november" -12: "december" \ No newline at end of file diff --git a/themes/mini/exampleSite/.gitignore b/themes/mini/exampleSite/.gitignore deleted file mode 100644 index ca4d540..0000000 --- a/themes/mini/exampleSite/.gitignore +++ /dev/null @@ -1,27 +0,0 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so - -# Folders -_obj -_test - -# Architecture specific extensions/prefixes -*.[568vq] -[568vq].out - -*.cgo1.go -*.cgo2.c -_cgo_defun.c -_cgo_gotypes.go -_cgo_export.* - -_testmain.go - -*.exe -*.test - -/public -/themes -.DS_Store diff --git a/themes/mini/exampleSite/LICENSE b/themes/mini/exampleSite/LICENSE deleted file mode 100644 index 4527efb..0000000 --- a/themes/mini/exampleSite/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Steve Francia - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/themes/mini/exampleSite/README.md b/themes/mini/exampleSite/README.md deleted file mode 100644 index ccea0a0..0000000 --- a/themes/mini/exampleSite/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# Hugo Theme Mini Example Site - -This repository offers an example site for [Hugo Theme mini](https://github.com/nodejh/hugo-theme-mini) and also it provides the default content for [Online Demo](http://nodejh.github.io/hugo-theme-mini). - -# Using - -1. [Install Hugo](https://gohugo.io/overview/installing/) -2. Clone this repository - - ```bash - $ git clone https://github.com/nodejh/hugo-theme-mini - $ cd exampleSite - ``` -3. Run Hugo server. The exampleSite use theme `hugo-theme-mini` by setting `themesDir` as `../../` - - ```bash - $ hugo server - ``` diff --git a/themes/mini/exampleSite/config-example.yaml b/themes/mini/exampleSite/config-example.yaml deleted file mode 100644 index 00adc63..0000000 --- a/themes/mini/exampleSite/config-example.yaml +++ /dev/null @@ -1,36 +0,0 @@ -baseURL: https://nodejh.github.io/hugo-theme-mini -languageCode: en-us -title: Hugo -theme: hugo-theme-mini -paginate: 3 - -# !!! exampleSite only, you may need to delete the line: `themesDir: ../../` -themesDir: ../../ - -hasCJKLanguage: true -permalinks: - posts: /posts/:title/ - -googleAnalytics: UA-123-45 -disqusShortname: nodejh - - -markup: - highlight: - guessSyntax: true - style: emacs - - -social: - github: https://github.com/nodejh - twitter: https://github.com/nodejh - stackoverflow: https://stackoverflow.com/users/4518882/nodejh - - -params: - author: nodejh - bio: Software Engineer - description: My Blog - enableRSS: true - enableComments: true - enableGoogleAnalytics: true diff --git a/themes/mini/exampleSite/config.yaml b/themes/mini/exampleSite/config.yaml deleted file mode 100644 index 1854e16..0000000 --- a/themes/mini/exampleSite/config.yaml +++ /dev/null @@ -1,84 +0,0 @@ -baseURL: https://example.com -languageCode: en-us -title: Hugo -theme: hugo-theme-mini - -# Default content language, support en (English) / zh (Chinese) / nl (Dutch), default 'en' -defaultContentLanguage: en - -# !!! exampleSite only, you may need to delete the line: `themesDir: ../../` -themesDir: ../../ - -hasCJKLanguage: true -permalinks: - posts: /posts/:title/ - -googleAnalytics: your-google-analytics-id -disqusShortname: your-disqus-shortname - -# Hugo Configure Markup -# More info: https://gohugo.io/getting-started/configuration-markup# -markup: - highlight: - guessSyntax: true - style: emacs - tableOfContents: - endLevel: 3 - ordered: false - startLevel: 2 - -# Social links in footer, support github,twitter,stackoverflow,facebook -social: - # e.g. - github: your-github-link - twitter: your-github-link - stackoverflow: your-github-link - # facebook: your-facebook-link - - -# Site parameters -params: - # Site Author - author: Author - # Author biography - bio: Software Engineer - # Site Description, used in HTML meat - description: My Blog - - - ########################################### - # Optional Configuration - ########################################### - - # To enable RSS, you could set `enableRSS: true`, default is `true` - enableRSS: true - # To enable comments, you may need to set `disqusShortname` - enableComments: true - # To enable comments, you may need to set `googleAnalytics` - enableGoogleAnalytics: true - # To enable table of content, you could set `showToc: true`, default is `false` - showToc: true - # To hidden powerBy message in the page footer, you could set: `showPowerBy: false`, default is `true` - showPowerBy: true - # To enable math typesetting , you could set `math: true`, default is `false` - math: false - # To hidden post summary in home page, you could set `hiddenPostSummaryInHomePage: true`, default is `false` - hiddenPostSummaryInHomePage: false - # Website copy write, default: '© Copyright 2021 ❤️ {params.author}' - copyright: '' - - # Extra links in navigation - links: - ## e.g. - # - name: Project - # path: /project - - # You can put your custom css and js to `static` directory, or use remote css and js files which start with `http://` or `https://` - customCSS: - ## e.g. - # - css/custom.css # local css in `static/css/custom.css` - # - https://example.com/custom.css # remote css - customJS: - ## e.g. - # - js/custom.js # local js in `static/js/custom.js` - # - https://example.com/custom.js # remote js \ No newline at end of file diff --git a/themes/mini/exampleSite/content/about/_index.md b/themes/mini/exampleSite/content/about/_index.md deleted file mode 100644 index 7cb001a..0000000 --- a/themes/mini/exampleSite/content/about/_index.md +++ /dev/null @@ -1,26 +0,0 @@ -+++ -title = "About" -description = "Hugo, the world's fastest framework for building websites" -date = "2019-02-28" -aliases = ["about-us", "about-hugo", "contact"] -author = "Hugo Authors" -enableComments = false -+++ - -Written in Go, Hugo is an open source static site generator available under the [Apache Licence 2.0.](https://github.com/gohugoio/hugo/blob/master/LICENSE) Hugo supports TOML, YAML and JSON data file types, Markdown and HTML content files and uses shortcodes to add rich content. Other notable features are taxonomies, multilingual mode, image processing, custom output formats, HTML/CSS/JS minification and support for Sass SCSS workflows. - -Hugo makes use of a variety of open source projects including: - -* https://github.com/yuin/goldmark -* https://github.com/alecthomas/chroma -* https://github.com/muesli/smartcrop -* https://github.com/spf13/cobra -* https://github.com/spf13/viper - -Hugo is ideal for blogs, corporate websites, creative portfolios, online magazines, single page applications or even a website with thousands of pages. - -Hugo is for people who want to hand code their own website without worrying about setting up complicated runtimes, dependencies and databases. - -Websites built with Hugo are extremely fast, secure and can be deployed anywhere including, AWS, GitHub Pages, Heroku, Netlify and any other hosting provider. - -Learn more and contribute on [GitHub](https://github.com/gohugoio). diff --git a/themes/mini/exampleSite/content/posts/emoji-support.md b/themes/mini/exampleSite/content/posts/emoji-support.md deleted file mode 100644 index 5b464de..0000000 --- a/themes/mini/exampleSite/content/posts/emoji-support.md +++ /dev/null @@ -1,47 +0,0 @@ -+++ -author = "Hugo Authors" -title = "Emoji Support" -date = "2019-03-05" -description = "Guide to emoji usage in Hugo" -tags = [ - "emoji", -] - -+++ - -Emoji can be enabled in a Hugo project in a number of ways. - -The [`emojify`](https://gohugo.io/functions/emojify/) function can be called directly in templates or [Inline Shortcodes](https://gohugo.io/templates/shortcode-templates/#inline-shortcodes). - -To enable emoji globally, set `enableEmoji` to `true` in your site's [configuration](https://gohugo.io/getting-started/configuration/) and then you can type emoji shorthand codes directly in content files; e.g. - -

🙈 :see_no_evil: 🙉 :hear_no_evil: 🙊 :speak_no_evil:

-
- -The [Emoji cheat sheet](http://www.emoji-cheat-sheet.com/) is a useful reference for emoji shorthand codes. - -*** - -**N.B.** The above steps enable Unicode Standard emoji characters and sequences in Hugo, however the rendering of these glyphs depends on the browser and the platform. To style the emoji you can either use a third party emoji font or a font stack; e.g. - -{{< highlight html >}} -.emoji { - font-family: Apple Color Emoji, Segoe UI Emoji, NotoColorEmoji, Segoe UI Symbol, Android Emoji, EmojiSymbols; -} -{{< /highlight >}} - -{{< css.inline >}} - -{{< /css.inline >}} diff --git a/themes/mini/exampleSite/content/posts/markdown-syntax.md b/themes/mini/exampleSite/content/posts/markdown-syntax.md deleted file mode 100644 index 06990d7..0000000 --- a/themes/mini/exampleSite/content/posts/markdown-syntax.md +++ /dev/null @@ -1,148 +0,0 @@ -+++ -author = "Hugo Authors" -title = "Markdown Syntax Guide" -date = "2019-03-11" -description = "Sample article showcasing basic Markdown syntax and formatting for HTML elements." -tags = [ - "markdown", - "css", - "html", -] -categories = [ - "themes", - "syntax", -] -series = ["Themes Guide"] -aliases = ["migrate-from-jekyl"] -+++ - -This article offers a sample of basic Markdown syntax that can be used in Hugo content files, also it shows whether basic HTML elements are decorated with CSS in a Hugo theme. - - -## Headings - -The following HTML `

`—`

` elements represent six levels of section headings. `

` is the highest section level while `

` is the lowest. - -# H1 -## H2 -### H3 -#### H4 -##### H5 -###### H6 - -## Paragraph - -Xerum, quo qui aut unt expliquam qui dolut labo. Aque venitatiusda cum, voluptionse latur sitiae dolessi aut parist aut dollo enim qui voluptate ma dolestendit peritin re plis aut quas inctum laceat est volestemque commosa as cus endigna tectur, offic to cor sequas etum rerum idem sintibus eiur? Quianimin porecus evelectur, cum que nis nust voloribus ratem aut omnimi, sitatur? Quiatem. Nam, omnis sum am facea corem alique molestrunt et eos evelece arcillit ut aut eos eos nus, sin conecerem erum fuga. Ri oditatquam, ad quibus unda veliamenimin cusam et facea ipsamus es exerum sitate dolores editium rerore eost, temped molorro ratiae volorro te reribus dolorer sperchicium faceata tiustia prat. - -Itatur? Quiatae cullecum rem ent aut odis in re eossequodi nonsequ idebis ne sapicia is sinveli squiatum, core et que aut hariosam ex eat. - -## Blockquotes - -The blockquote element represents content that is quoted from another source, optionally with a citation which must be within a `footer` or `cite` element, and optionally with in-line changes such as annotations and abbreviations. - -#### Blockquote without attribution - -> Tiam, ad mint andaepu dandae nostion secatur sequo quae. -> **Note** that you can use *Markdown syntax* within a blockquote. - -#### Blockquote with attribution - -> Don't communicate by sharing memory, share memory by communicating.
-> — Rob Pike[^1] - -[^1]: The above quote is excerpted from Rob Pike's [talk](https://www.youtube.com/watch?v=PAAkCSZUG1c) during Gopherfest, November 18, 2015. - -## Tables - -Tables aren't part of the core Markdown spec, but Hugo supports supports them out-of-the-box. - - Name | Age ---------|------ - Bob | 27 - Alice | 23 - -#### Inline Markdown within tables - -| Italics | Bold | Code | -| -------- | -------- | ------ | -| *italics* | **bold** | `code` | - -## Code Blocks - -#### Code block with backticks - -```html - - - - - Example HTML5 Document - - -

Test

- - -``` - -#### Code block indented with four spaces - - - - - - Example HTML5 Document - - -

Test

- - - -#### Code block with Hugo's internal highlight shortcode -{{< highlight html >}} - - - - - Example HTML5 Document - - -

Test

- - -{{< /highlight >}} - -## List Types - -#### Ordered List - -1. First item -2. Second item -3. Third item - -#### Unordered List - -* List item -* Another item -* And another item - -#### Nested list - -* Fruit - * Apple - * Orange - * Banana -* Dairy - * Milk - * Cheese - -## Other Elements — abbr, sub, sup, kbd, mark - -GIF is a bitmap image format. - -H2O - -Xn + Yn = Zn - -Press CTRL+ALT+Delete to end the session. - -Most salamanders are nocturnal, and hunt for insects, worms, and other small creatures. diff --git a/themes/mini/exampleSite/content/posts/math-typesetting.md b/themes/mini/exampleSite/content/posts/math-typesetting.md deleted file mode 100644 index 48fdc79..0000000 --- a/themes/mini/exampleSite/content/posts/math-typesetting.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -author: Hugo Authors -title: Math Typesetting -date: 2019-03-08 -description: A brief guide to setup KaTeX -math: true ---- - -Mathematical notation in a Hugo project can be enabled by using third party JavaScript libraries. - - -In this example we will be using [KaTeX](https://katex.org/) - -- Create a partial under `/layouts/partials/math.html` -- Within this partial reference the [Auto-render Extension](https://katex.org/docs/autorender.html) or host these scripts locally. -- Include the partial in your templates like so: - -```bash -{{ if or .Params.math .Site.Params.math }} -{{ partial "math.html" . }} -{{ end }} -``` - -- To enable KaTex globally set the parameter `math` to `true` in a project's configuration -- To enable KaTex on a per page basis include the parameter `math: true` in content files - -**Note:** Use the online reference of [Supported TeX Functions](https://katex.org/docs/supported.html) - -{{< math.inline >}} -{{ if or .Page.Params.math .Site.Params.math }} - - - - -{{ end }} -{{}} - -### Examples - -{{< math.inline >}} -

-Inline math: \(\varphi = \dfrac{1+\sqrt5}{2}= 1.6180339887…\) -

-{{}} - -Block math: -$$ - \varphi = 1+\frac{1} {1+\frac{1} {1+\frac{1} {1+\cdots} } } -$$ diff --git a/themes/mini/exampleSite/content/posts/placeholder-text.md b/themes/mini/exampleSite/content/posts/placeholder-text.md deleted file mode 100644 index 9ed5f69..0000000 --- a/themes/mini/exampleSite/content/posts/placeholder-text.md +++ /dev/null @@ -1,45 +0,0 @@ -+++ -author = "Hugo Authors" -title = "Placeholder Text" -date = "2019-03-09" -description = "Lorem Ipsum Dolor Si Amet" -tags = [ - "markdown", - "text", -] -+++ - -Lorem est tota propiore conpellat pectoribus de pectora summo. Redit teque digerit hominumque toris verebor lumina non cervice subde tollit usus habet Arctonque, furores quas nec ferunt. Quoque montibus nunc caluere tempus inhospita parcite confusaque translucet patri vestro qui optatis lumine cognoscere flos nubis! Fronde ipsamque patulos Dryopen deorum. - -1. Exierant elisi ambit vivere dedere -2. Duce pollice -3. Eris modo -4. Spargitque ferrea quos palude - -Rursus nulli murmur; hastile inridet ut ab gravi sententia! Nomine potitus silentia flumen, sustinet placuit petis in dilapsa erat sunt. Atria tractus malis. - -1. Comas hunc haec pietate fetum procerum dixit -2. Post torum vates letum Tiresia -3. Flumen querellas -4. Arcanaque montibus omnes -5. Quidem et - -# Vagus elidunt - - - -[The Van de Graaf Canon](https://en.wikipedia.org/wiki/Canons_of_page_construction#Van_de_Graaf_canon) - -## Mane refeci capiebant unda mulcebat - -Victa caducifer, malo vulnere contra dicere aurato, ludit regale, voca! Retorsit colit est profanae esse virescere furit nec; iaculi matertera et visa est, viribus. Divesque creatis, tecta novat collumque vulnus est, parvas. **Faces illo pepulere** tempus adest. Tendit flamma, ab opes virum sustinet, sidus sequendo urbis. - -Iubar proles corpore raptos vero auctor imperium; sed et huic: manus caeli Lelegas tu lux. Verbis obstitit intus oblectamina fixis linguisque ausus sperare Echionides cornuaque tenent clausit possit. Omnia putatur. Praeteritae refert ausus; ferebant e primus lora nutat, vici quae mea ipse. Et iter nil spectatae vulnus haerentia iuste et exercebat, sui et. - -Eurytus Hector, materna ipsumque ut Politen, nec, nate, ignari, vernum cohaesit sequitur. Vel **mitis temploque** vocatus, inque alis, *oculos nomen* non silvis corpore coniunx ne displicet illa. Crescunt non unus, vidit visa quantum inmiti flumina mortis facto sic: undique a alios vincula sunt iactata abdita! Suspenderat ego fuit tendit: luna, ante urbem Propoetides **parte**. - -{{< css.inline >}} - -{{< /css.inline >}} diff --git a/themes/mini/exampleSite/content/posts/rich-content.md b/themes/mini/exampleSite/content/posts/rich-content.md deleted file mode 100644 index c1ea871..0000000 --- a/themes/mini/exampleSite/content/posts/rich-content.md +++ /dev/null @@ -1,35 +0,0 @@ -+++ -author = "Hugo Authors" -title = "Rich Content" -date = "2019-03-10" -description = "A brief description of Hugo Shortcodes" -tags = [ - "shortcodes", - "privacy", -] -draft = true -+++ - -Hugo ships with several [Built-in Shortcodes](https://gohugo.io/content-management/shortcodes/#use-hugos-built-in-shortcodes) for rich content, along with a [Privacy Config](https://gohugo.io/about/hugo-and-gdpr/) and a set of Simple Shortcodes that enable static and no-JS versions of various social media embeds. - ---- - -## YouTube Privacy Enhanced Shortcode - -{{< youtube ZJthWmvUzzc >}} - -
- ---- - -## Twitter Simple Shortcode - -{{< twitter_simple 1085870671291310081 >}} - -
- ---- - -## Vimeo Simple Shortcode - -{{< vimeo_simple 48912912 >}} diff --git a/themes/mini/exampleSite/content/posts/test.md b/themes/mini/exampleSite/content/posts/test.md deleted file mode 100644 index f2dbc9f..0000000 --- a/themes/mini/exampleSite/content/posts/test.md +++ /dev/null @@ -1,84 +0,0 @@ -+++ -author = "Test" -title = "Code Content" -date = "2021-03-10" -description = "A brief description of Hugo Shortcodes" -tags = [ - "shortcodes", - "privacy", -] -draft = true -+++ - -## t1 - -aaaa - - -Test [aaa](http://example.com) text. - -### t1.1 - -aaaa - - -### t1.2 - -aaaa - - -#### t1.2.1 - -aaaa - - -#### t1.2.2 - -aaaa - - -## t2 - -aaaa - -## t3 - -1. One

- -/``` -testing -some -code -/``` - -2. Two

-3. Three

- -超宽显示 `var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";var a = "text";` 超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示超宽显示 `var a = "text";` - -``` -2021-08-02 17:51:23.718 ERROR org.apache.flink.runtime.entrypoint.ClusterEntrypoint [] - Fatal error occurred in the cluster entrypoint. -org.apache.flink.util.FlinkException: Application failed unexpectedly. - at org.apache.flink.client.deployment.application.ApplicationDispatcherBootstrap.lambda$runApplicationAndShutdownClusterAsync$0(ApplicationDispatcherBootstrap.java:170) ~[flink-dist_2.12-1.13.1.jar:1.13.1] - at java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:836) ~[?:1.8.0_292] - at java.util.concurrent.CompletableFuture$UniHandle.tryFire(CompletableFuture.java:811) ~[?:1.8.0_292] - at java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:488) ~[?:1.8.0_292] - at java.util.concurrent.CompletableFuture.completeExceptionally(CompletableFuture.java:1990) ~[?:1.8.0_292] - at org.apache.flink.client.deployment.application.ApplicationDispatcherBootstrap.runApplicationEntryPoint(ApplicationDispatcherBootstrap.java:257) ~[flink-dist_2.12-1.13.1.jar:1.13.1] - at org.apache.flink.client.deployment.application.ApplicationDispatcherBootstrap.lambda$runApplicationAsync$1(ApplicationDispatcherBootstrap.java:212) ~[flink-dist_2.12-1.13.1.jar:1.13.1] - at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [?:1.8.0_292] - at java.util.concurrent.FutureTask.run(FutureTask.java:266) [?:1.8.0_292] - at org.apache.flink.runtime.concurrent.akka.ActorSystemScheduledExecutorAdapter$ScheduledFutureTask.run(ActorSystemScheduledExecutorAdapter.java:159) [flink-dist_2.12-1.13.1.jar:1.13.1] - at akka.dispatch.TaskInvocation.run(AbstractDispatcher.scala:40) [flink-dist_2.12-1.13.1.jar:1.13.1] - at akka.dispatch.ForkJoinExecutorConfigurator$AkkaForkJoinTask.exec(ForkJoinExecutorConfigurator.scala:44) [flink-dist_2.12-1.13.1.jar:1.13.1] - at akka.dispatch.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260) [flink-dist_2.12-1.13.1.jar:1.13.1] - at akka.dispatch.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339) [flink-dist_2.12-1.13.1.jar:1.13.1] - at akka.dispatch.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979) [flink-dist_2.12-1.13.1.jar:1.13.1] -``` - - -``` -test -test -test -``` \ No newline at end of file diff --git a/themes/mini/exampleSite/static/.gitignore b/themes/mini/exampleSite/static/.gitignore deleted file mode 100644 index e69de29..0000000 diff --git a/themes/mini/i18n/en.yaml b/themes/mini/i18n/en.yaml deleted file mode 100644 index 192114b..0000000 --- a/themes/mini/i18n/en.yaml +++ /dev/null @@ -1,50 +0,0 @@ -home: - other: Home - -archive: - other: Archive - -tags: - other: Tags - -about: - other: About - -subscribe: - other: Subscribe - -404title: - other: 404 - Page Not Found - -404subtitle: - other: The content you're looking for doesn't seem to exist. - -readMore: - other: Read more - -minuteRead: - other: "{{ .ReadingTime }} minute read" - -publishDate: - other: '{{ .PublishDate.Format "Jan 2, 2006" }}' - -publishDateFull: - other: '{{ .PublishDate.Format "Jan 2, 2006" }}' - -wordCount: - one: "{{ .WordCount }} word" - other: "{{ .WordCount }} words" - -postsNewer: - other: Newer posts - -postsOlder: - other: Older posts - -poweredBy: - other: >- - Powered by Hugo Theme By nodejh - -publishDateShort: - other: '{{ .PublishDate.Format "Jan 2" }}' diff --git a/themes/mini/i18n/nl.yaml b/themes/mini/i18n/nl.yaml deleted file mode 100644 index d2f8704..0000000 --- a/themes/mini/i18n/nl.yaml +++ /dev/null @@ -1,53 +0,0 @@ -home: - other: Huis - -archive: - other: Archief - -tags: - other: Tags - -subscribe: - other: Abonneren - -about: - other: Over - -404title: - other: 404 - Pagina Niet Gevonden - -404subtitle: - other: 'De pagina waar u naar op zoek bent, lijkt niet te bestaan.' - -readMore: - other: Lees meer - -minuteRead: - other: "{{ .ReadingTime }} minuten leestijd" - -publishDate: - other: >- - {{ .PublishDate.Day }} {{ index $.Site.Data.months_nl (printf "%d" .PublishDate.Month) }} {{ .PublishDate.Year }} - -publishDateFull: - other: >- - {{ .PublishDate.Day }} {{ index $.Site.Data.months_nl (printf "%d" .PublishDate.Month) }} {{ .PublishDate.Year }} - -wordCount: - one: "{{ .WordCount }} woord" - other: "{{ .WordCount }} woorden" - -postsNewer: - other: Nieuwere posts - -postsOlder: - other: Oudere posts - -poweredBy: - other: >- - Gemaakt met Hugo Thema door nodejh - -publishDateShort: - other: >- - {{ .PublishDate.Day }} {{ index $.Site.Data.months_nl (printf "%d" .PublishDate.Month) }} diff --git a/themes/mini/i18n/zh.yaml b/themes/mini/i18n/zh.yaml deleted file mode 100644 index 803ca9f..0000000 --- a/themes/mini/i18n/zh.yaml +++ /dev/null @@ -1,50 +0,0 @@ -home: - other: 首页 - -archive: - other: 归档 - -tags: - other: 标签 - -subscribe: - other: 订阅 - -about: - other: 关于 - -404title: - other: "404" - -404subtitle: - other: 页面不存在 - -readMore: - other: 更多内容 - -minuteRead: - other: "{{ .ReadingTime }}分钟" - -publishDate: - other: '{{ .PublishDate.Format "2006/01/02" }}' - -publishDateFull: - other: '{{ .PublishDate.Format "2006年01月02日" }}' - -wordCount: - one: "{{ .WordCount }}字" - other: "{{ .WordCount }}字" - -postsNewer: - other: 下一页 - -postsOlder: - other: 上一页 - -poweredBy: - other: >- - Powered by Hugo Theme By nodejh - -publishDateShort: - other: '{{ .PublishDate.Format "01/02" }}' diff --git a/themes/mini/images/screenshot.png b/themes/mini/images/screenshot.png deleted file mode 100644 index 78831d8..0000000 Binary files a/themes/mini/images/screenshot.png and /dev/null differ diff --git a/themes/mini/images/tn.png b/themes/mini/images/tn.png deleted file mode 100644 index 6d8e2e8..0000000 Binary files a/themes/mini/images/tn.png and /dev/null differ diff --git a/themes/mini/layouts/404.html b/themes/mini/layouts/404.html deleted file mode 100644 index ae1af4f..0000000 --- a/themes/mini/layouts/404.html +++ /dev/null @@ -1,40 +0,0 @@ -{{ define "main" }} - -
-
- {{ with .Site.Params.title404 }} -

{{ . }}

- {{ else }} -

{{ i18n "404title" }}

- {{ end }} - - {{ with .Site.Params.subtitle404 }} -
{{ . }}
- {{ else }} -
{{ i18n "404subtitle" }}
- {{ end }} -
- - {{ with .Site.Params.readMore }} -

{{ . }}

- {{ else }} -

{{ i18n "readMore" }}

- {{ end }} - - - - - {{ $pages := .Site.RegularPages }} - {{ $paginator := .Paginate ($pages) }} - {{ range $paginator.Pages }} - {{ $title := .Title }} - {{ $date := dateFormat "Jan 2, 2006" .Date }} -
-
-

{{ $title }}

-
{{ $date }}
-
-
- {{ end }} -
-{{ end }} diff --git a/themes/mini/layouts/_default/_markup/render-heading.html b/themes/mini/layouts/_default/_markup/render-heading.html deleted file mode 100644 index 714cf12..0000000 --- a/themes/mini/layouts/_default/_markup/render-heading.html +++ /dev/null @@ -1 +0,0 @@ -{{ .Text | safeHTML }} 🔗 \ No newline at end of file diff --git a/themes/mini/layouts/_default/_markup/render-image.html b/themes/mini/layouts/_default/_markup/render-image.html deleted file mode 100644 index ed9fb61..0000000 --- a/themes/mini/layouts/_default/_markup/render-image.html +++ /dev/null @@ -1,3 +0,0 @@ -

- {{ .Text }} -

\ No newline at end of file diff --git a/themes/mini/layouts/_default/_markup/render-link.html b/themes/mini/layouts/_default/_markup/render-link.html deleted file mode 100644 index 70ac35e..0000000 --- a/themes/mini/layouts/_default/_markup/render-link.html +++ /dev/null @@ -1 +0,0 @@ -{{ .Text | safeHTML }} \ No newline at end of file diff --git a/themes/mini/layouts/_default/baseof.html b/themes/mini/layouts/_default/baseof.html deleted file mode 100644 index 937f823..0000000 --- a/themes/mini/layouts/_default/baseof.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - {{ block "title" . }}{{ with .Params.Title }}{{ . }} | {{ end }}{{ .Site.Title }}{{ end }} - - {{ partial "head.html" . }} - - - - - {{ partial "navigation.html" . }} - -
- {{ block "main" . }}{{ end }} -
- - {{ partial "footer.html" . }} - - diff --git a/themes/mini/layouts/_default/list.html b/themes/mini/layouts/_default/list.html deleted file mode 100644 index 9e88396..0000000 --- a/themes/mini/layouts/_default/list.html +++ /dev/null @@ -1,38 +0,0 @@ -{{ define "main" }} -{{ partial "profile.html" . }} - -
- {{ range .Data.Pages.GroupByDate "2006" }} -
-

- {{ .Key }} -

- - {{ range .Pages }} -
-
- -
-
- - {{ .Title }} - - - {{ with .Params.tags }} -
- {{ range . }} - {{ . }} - {{ end }} -
- {{ end}} -
-
- {{ end }} -
- {{ end }} - - -
- - -{{ end }} \ No newline at end of file diff --git a/themes/mini/layouts/_default/single.html b/themes/mini/layouts/_default/single.html deleted file mode 100644 index c4159a8..0000000 --- a/themes/mini/layouts/_default/single.html +++ /dev/null @@ -1,45 +0,0 @@ -{{ define "main" }} - -
-

{{ .Title }}

- -
- - - · - - - {{ i18n "wordCount" . }} - - - · - - - {{ i18n "minuteRead" . }} - -
- - - {{ if or .Site.Params.showToc .Params.showToc }} - {{ partial "toc.html" . }} - {{ end }} - - -
- {{ .Content }} -
- - {{ with .Params.tags }} -
- {{ range . }} - {{ . }} - {{ end }} -
- {{ end}} - - {{ partial "comment.html" . }} - -
- -{{ end }} - diff --git a/themes/mini/layouts/_default/taxonomy.html b/themes/mini/layouts/_default/taxonomy.html deleted file mode 100644 index ef6d0e9..0000000 --- a/themes/mini/layouts/_default/taxonomy.html +++ /dev/null @@ -1,41 +0,0 @@ -{{ define "main" }} -{{ partial "profile.html" . }} - -
- - {{ range .Data.Pages.GroupByDate "2006" }} -
-

- {{ .Key }} -

- - {{ range .Pages }} -
-
- -
-
- - {{ .Title }} - - - {{ with .Params.tags }} -
- {{ range . }} - {{ . }} - {{ end }} -
- {{ end}} -
-
- {{ end }} -
- - {{ end }} - - - -
- - -{{ end }} \ No newline at end of file diff --git a/themes/mini/layouts/_default/terms.html b/themes/mini/layouts/_default/terms.html deleted file mode 100644 index a7e3abc..0000000 --- a/themes/mini/layouts/_default/terms.html +++ /dev/null @@ -1,15 +0,0 @@ -{{ define "main" }} -{{ partial "profile.html" . }} -
- {{ $data := .Data }} - {{ range $key, $value := .Data.Terms.ByCount }} - {{ if ($value.Name) }} - - - {{ $value.Name | upper }} ({{ $value.Count }}) - - - {{ end }} - {{ end }} -
-{{ end }} \ No newline at end of file diff --git a/themes/mini/layouts/index.html b/themes/mini/layouts/index.html deleted file mode 100644 index d43a0cd..0000000 --- a/themes/mini/layouts/index.html +++ /dev/null @@ -1,47 +0,0 @@ -{{ define "main" }} -{{ partial "profile.html" . }} - -
- {{ $pages := .Site.RegularPages }} - {{ $paginator := .Paginate ($pages) }} - {{ range $paginator.Pages }} - {{ $title := .Title }} - {{ $summary := .Summary }} -
-
-

{{ $title }}

-
- -
-
- - {{ if ne .Site.Params.hiddenPostSummaryInHomePage true }} -
{{ $summary | plainify | htmlUnescape }}
- {{ end }} - -
- {{ end }} - - {{ if or ($paginator.HasPrev) ($paginator.HasNext) }} - - {{ end }} - - -
-{{ end }} diff --git a/themes/mini/layouts/partials/comment.html b/themes/mini/layouts/partials/comment.html deleted file mode 100644 index f7a16a8..0000000 --- a/themes/mini/layouts/partials/comment.html +++ /dev/null @@ -1,6 +0,0 @@ -{{ if or (and .Site.Params.enableComments (ne .Params.enableComments false)) (eq .Params.enableComments true) }} -
- - {{ template "_internal/disqus.html" . }} -
-{{ end }} \ No newline at end of file diff --git a/themes/mini/layouts/partials/footer.html b/themes/mini/layouts/partials/footer.html deleted file mode 100644 index 27a7518..0000000 --- a/themes/mini/layouts/partials/footer.html +++ /dev/null @@ -1,34 +0,0 @@ -
- {{ if .Site.Social }} - {{ partial "social.html" . }} - {{ end }} - - - - {{ if ne .Site.Params.showPowerBy false }} -
- {{ i18n "poweredBy" | safeHTML }} -
- {{ end }} -
- -{{ range .Site.Params.customJS }} - {{ if ( or ( hasPrefix . "http://" ) ( hasPrefix . "https://" ) ) }} - - - {{ else }} - - - {{ end }} -{{ end }} diff --git a/themes/mini/layouts/partials/head.html b/themes/mini/layouts/partials/head.html deleted file mode 100644 index 43cc32e..0000000 --- a/themes/mini/layouts/partials/head.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - -{{ hugo.Generator }} -{{/* NOTE: For Production make sure you add `HUGO_ENV="production"` before your build command */}} -{{ if eq (getenv "HUGO_ENV") "production" | or (eq .Site.Params.env "production") }} - -{{ else }} - -{{ end }} - - - -{{ range .Site.Params.customCSS }} - {{ if ( or ( hasPrefix . "http://" ) ( hasPrefix . "https://" ) ) }} - - - {{ else }} - - - {{ end }} -{{ end }} - - - -{{ if .Site.Params.enableGoogleAnalytics }} - {{ template "_internal/google_analytics_async.html" . }} -{{ end }} - -{{ if or .Params.math .Site.Params.math }} - {{ partial "math.html" . }} -{{ end }} - -{{ if .OutputFormats.Get "RSS" }} - {{ with .OutputFormats.Get "RSS" }} - - - {{ end }} -{{ end }} \ No newline at end of file diff --git a/themes/mini/layouts/partials/math.html b/themes/mini/layouts/partials/math.html deleted file mode 100644 index 7c1279a..0000000 --- a/themes/mini/layouts/partials/math.html +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/themes/mini/layouts/partials/navigation.html b/themes/mini/layouts/partials/navigation.html deleted file mode 100644 index c96fc49..0000000 --- a/themes/mini/layouts/partials/navigation.html +++ /dev/null @@ -1,16 +0,0 @@ - diff --git a/themes/mini/layouts/partials/profile.html b/themes/mini/layouts/partials/profile.html deleted file mode 100644 index ad5a296..0000000 --- a/themes/mini/layouts/partials/profile.html +++ /dev/null @@ -1,15 +0,0 @@ -
- {{ if .Site.Params.avatarLink }} - - avatar - - {{ else }} - avatar - {{ end }} - -

{{ .Site.Title }}

- - {{ with .Site.Params.Bio }} -

{{ . | markdownify }}

- {{ end }} -
\ No newline at end of file diff --git a/themes/mini/layouts/partials/social.html b/themes/mini/layouts/partials/social.html deleted file mode 100644 index ebc9d66..0000000 --- a/themes/mini/layouts/partials/social.html +++ /dev/null @@ -1,10 +0,0 @@ - diff --git a/themes/mini/layouts/partials/svgs/facebook.svg b/themes/mini/layouts/partials/svgs/facebook.svg deleted file mode 100644 index 47882f3..0000000 --- a/themes/mini/layouts/partials/svgs/facebook.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - Facebook - Created with Sketch. - - - - - - - - - - \ No newline at end of file diff --git a/themes/mini/layouts/partials/svgs/github.svg b/themes/mini/layouts/partials/svgs/github.svg deleted file mode 100644 index 64b26cf..0000000 --- a/themes/mini/layouts/partials/svgs/github.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - Github - Created with Sketch. - - - - - - - - - - \ No newline at end of file diff --git a/themes/mini/layouts/partials/svgs/heart.svg b/themes/mini/layouts/partials/svgs/heart.svg deleted file mode 100644 index 75f6d3e..0000000 --- a/themes/mini/layouts/partials/svgs/heart.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/themes/mini/layouts/partials/svgs/stackoverflow.svg b/themes/mini/layouts/partials/svgs/stackoverflow.svg deleted file mode 100644 index 6469fa9..0000000 --- a/themes/mini/layouts/partials/svgs/stackoverflow.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - stackoverflow - Created with Sketch. - - - - - - - - - - \ No newline at end of file diff --git a/themes/mini/layouts/partials/svgs/twitter.svg b/themes/mini/layouts/partials/svgs/twitter.svg deleted file mode 100644 index 03cabec..0000000 --- a/themes/mini/layouts/partials/svgs/twitter.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - - - diff --git a/themes/mini/layouts/partials/toc.html b/themes/mini/layouts/partials/toc.html deleted file mode 100644 index 9490e59..0000000 --- a/themes/mini/layouts/partials/toc.html +++ /dev/null @@ -1,14 +0,0 @@ -{{ with .TableOfContents }} - {{ if ne . "" }} - - {{ end }} -{{ end }} diff --git a/themes/mini/layouts/section/about.html b/themes/mini/layouts/section/about.html deleted file mode 100644 index 753f06d..0000000 --- a/themes/mini/layouts/section/about.html +++ /dev/null @@ -1,11 +0,0 @@ -{{ define "main" }} -{{ partial "profile.html" . }} - -
- {{ .Content }} - - {{ partial "comment.html" . }} -
- - -{{ end }} \ No newline at end of file diff --git a/themes/mini/theme.toml b/themes/mini/theme.toml deleted file mode 100644 index aecffe2..0000000 --- a/themes/mini/theme.toml +++ /dev/null @@ -1,12 +0,0 @@ -name = "mini" -license = "MIT" -licenselink = "https://github.com/nodejh/hugo-theme-mini/blob/master/LICENSE.md" -description = "A fast, minimalist and responsive hugo theme for bloggers." -homepage = "https://github.com/nodejh/hugo-theme-mini" -tags = ["blog", "fast", "minimalist", "responsive", "simple", "beautiful", "tags", "pages"] -features = ["fast", "minimalist", "responsive", "archive", "tags"] -min_version = "0.75.0" - -[author] - name = "nodejh" - homepage = "https://github.com/nodejh" \ No newline at end of file