# Technical Documentation: ty4z2008/Qix

> ℹ️ **Provenance:** Hybrid Fusion: `ty4z2008/Qix` + `ty4z2008/ty4z2008.github.io` · [CodeWiki Reference](https://codewiki.google/github.com/ty4z2008/Qix) · Recency: Active (< 180 days)

## 1. Project Overview & Quickstart (ty4z2008/Qix)

[](http://makeapullrequest.com)   

## About Me

WeiBo: [@廖君_Jun](http://weibo.com/ty4z2008)

Twitter: [@廖君](https://twitter.com/ty4z2008)

E-Mail: ty4z2008@gmail.com

Scale System Channel: [https://t.me/scalesystem](https://t.me/scalesystem)

**NOTE** 

There may be some incorrect information in the article. I hope i can correct error with you.  you can contact me with Email or PR

## Pull Request welcome:blush:

## My translation

### node-mysql document translate

- [node-mysql offcial document](https://github.com/felixge/node-mysql/blob/master/Readme.md)

- [node-mysql Chinese document](https://github.com/ty4z2008/Qix/blob/master/node.md)

## Machine Learning And deep learning Resources

- [Chapter 1](https://github.com/ty4z2008/Qix/blob/master/dl.md)

- [Chapter 2](https://github.com/ty4z2008/Qix/blob/master/dl2.md)

## Golang learning resources

- [Chapter 1](https://github.com/ty4z2008/Qix/blob/master/golang.md)


## PostgreSQL database resources

- [Chapter 1](https://github.com/ty4z2008/Qix/blob/master/pg.md)


## Distributed system resources

- [Chapter 1](https://github.com/ty4z2008/Qix/blob/master/ds.md)

## Database system resources

- [Chapter 1](https://github.com/ty4z2008/Qix/blob/master/db.md)



## Additional notes

Dear friends. In order to respect to  the efforts   of authorship. In the reading process, when you find that resource the authorship is incorrect I also want you to[Submit feedback](https://github.com/ty4z2008/Qix/issues)。Thanks buddy！

## License

[MIT License](https://github.com/ty4z2008/Qix/blob/master/License.md)

## 2. Official Technical Reference & Guides (ty4z2008/ty4z2008.github.io)

## File: README.md

#### 启示录

- [关于PostgreSQL的时间和货币区域问题](./_posts/2015-04-20-first-blog.markdown)
- [PostgreSQL的psql命令分析](./_posts/2016-03-12-postgresql-psql-command-analyze.markdown)
- [Atom常用插件](./_posts/2016-03-13-atom-plugins.markdown)
- [Qcon 2016年PPT演示小工具](./_posts/2016-04-25-qcon-2016.markdown)
- [Golang:存储单位转换判断](./_posts/2016-05-01-golang-byte-conv.markdown)
- [如何写一份好的README](./_posts/2016-05-08-how-to-write-a-good-README.markdown)
- [纪录一次真实的系统故障总结](./_posts/2016-05-31-a-real-accident-of-information-system.markdown)
- [阅读：大型网站技术架构.技术原理与案例分析（一）](./_posts/2016-05-31-large-scale-website-architecture-part-1.markdown)
- [阅读：大型网站技术架构.技术原理与案例分析（二）](./_posts/2016-05-31-large-scale-website-architecture-part-2.markdown)
- [阅读：大型网站技术架构.技术原理与案例分析（三）](./_posts/2016-05-31-large-scale-website-architecture-part-3.markdown)
- [对于新手容易在数据库上犯的五个错误](./_posts/2016-06-11-five-mistakes-beginners-make-when-database.markdown)
- [阅读《Code Simplicity:The Science of Software development》笔记](./_posts/2017-05-02-reading-code-simplicty-book-note.markdown)
- [如何阅读一篇论文](./_posts/2018-10-18-how-to-reading-a-paper.md)
- [Dockerfile最佳实践Tips](./_posts/2018-12-01-dockerfile-best-practices.md)
- [迁移私有化git到gitlab](./_posts/2018-12-07-migrate-private-git-to-private-gitlab.md)
- [PostgreSQL开发者模式错误反馈与日志设置](./_posts/2018-12-09-postgres-runtime-config-logging.md)
- [在Kubernetes中使用Taints和Toleration划分专用节点](./_posts/2018-12-21-use-taints-and-toleration-manage-pod-kubernetes.md)
- [在JS中如何检测对象的改变？](./_posts/2018-12-28-how-to-use-watch-Object-change.md)
- [TiDB 源码阅读笔记：开始](./_posts/2019-06-11-reading-tidb-code-1.md)
- [后端之路](./_posts/2019-07-12-backend-roadmap-zh.md)
- [优化MySQL数据插入速度](./_posts/2019-09-01-how-to-speed-up-insert.md)
- [sed和awk工具，以及正则表达式总结](./_posts/2019-09-05-sed-and-awk-regular-expression-summary.md)
- [一张图了解Google技术栈](./_posts/2019-09-15-about-google-tech-stack.md)
- [[翻译]MySQL 可视化explain查询计划](./_posts/2019-09-25-visual-explain-plan-for-mysql.md)
- [[翻译]消息队列OpenMessaging之使用场景概述](./_posts/2019-10-04-use-case-of-openmessaging-specification-zh-cn.md)
- [关于AnalyticDB 3.0的调研和分析](./_posts/2020-01-15-about-aliyun-analyticdb-for-mysql.md)
- [如何阅读技术文档](./_posts/2020-02-02-how-to-read-document.md)
- [MIT 6.824 分布式系统课程第一课：介绍笔记](./_posts/2020-02-26-mit-6.824-lecture-1-introduce.md)
- [MIT 6.824 分布式系统课程第二课：RPC和多线程](./_posts/2020-03-03-mit-6.824-lecture-2-rpc-and-threads.md)
- [MIT 6.824 分布式系统课程第三课：GFS](./_posts/2020-03-14-mit-6.824-lecture-3-gfs.md)
- [MIT 6.824 分布式系统课程第四课：主备复制](./_posts/2020-03-28-mit-6.824-lecture-4-primary-backup-replication.md)
- [MIT 6.824 分布式系统课程第五课：Go线程和Raft](./_posts/2020-04-03-mit-6.824-lecture-5-go-thread-and-raft.md)
- [MIT 6.824 分布式系统课程第六课: 错误容忍：Raft（1）](./_posts/2020-04-29-mit-6.824-lecture-6-fault-tolerance-raft-1.md)
- [企业级私有镜像仓库Harbor](./_posts/2020-10-20-harbor-a-enterprise-private-registry.mdd)

---

## File: _config.yml

title: 启示录 Blog
SEOTitle: Jeffery的博客 | Jeffery Blog
header-img: img/home-bg-art.jpg
email: ty4z2008@gmail.com
keyword: "ty4z2008, Jeffery, @ty4z2008, Web, 数据库, 前端,"
url: "https://blog.microdba.com"              # your host, for absolute URL
baseurl: ""                             # for example, '/blog' if your blog hosted on 'host/blog'

# Publish posts or collection documents with a future date.
future: true

# SNS settings
RSS: false
weibo_username:     ty4z2008
zhihu_username:     websec
github_username:    ty4z2008
twitter_username:   ty4z2008
#facebook_username:  ty4z2008



# Build settings
# from 2016, 'pygments' is unsupported on GitHub Pages. Use 'rouge' for highlighting instead.
highlighter: rouge
permalink: pretty
paginate: 10
exclude: ["less","node_modules","Gruntfile.js","package.json","README.md","README.zh.md"]
anchorjs: true                          # if you want to customize anchor. check out line:181 of `post.html`
# If you have timezone issue (e.g. #68) in China, uncomment to use this:
#timezone: CN  



# Gems
# from PR#40, to support local preview for Jekyll 3.0
# make sure you have this gem installed
# `$ gem install jekyll-paginate`
plugins: [jekyll-paginate]



# Markdown settings
# replace redcarpet to kramdown,
# although redcarpet can auto highlight code, the lack of header-id make the catalog impossible, so I switch to kramdown
# document: http://jekyllrb.com/docs/configuration/#kramdown
markdown: kramdown
kramdown:
  input: GFM                            # use Github Flavored Markdown !important
  syntax_highlighter_opts:
    span:
      line_numbers: false
    block:
      line_numbers: true
      start_line: 1



# Disqus settings
disqus_username: websec

# Netease settings
netease_comment: false

# Gitalk comment
# gitalk_comment: false
# gitalk_config:
#   clientID: ae5ef2c72ebccb988d35
#   clientSecret: 1e4abbe1d79f575d99142c28750c6d72fc878351
#   repo: ty4z2008.github.io
#   owner: ty4z2008
#   admin: ty4z2008

# Analytics settings
# Baidu Analytics 
# ba_track_id: [your track id]

# Google Analytics
ga_track_id: 'UA-50183451-1'            # Format: UA-xxxxxx-xx
ga_domain: blog.microdba.com



# Sidebar settings
sidebar: true                           # whether or not using Sidebar.
sidebar-about-description: "生活不只是眼前的苟且，还有诗和远方"
sidebar-avatar: ''      # use absolute URL, seeing it's used in both `/` and `/about/`



# Featured Tags
featured-tags: true                     # whether or not using Feature-Tags
featured-condition-size: 0              # A tag will be featured if the size of it is more than this condition value



# Progressive Web Apps
chrome-tab-theme-color: "#000000"
service-worker: true



# MathJax rendering for layout:page (e.g. post preview)
page-mathjax: false



# Friends
friends: [
    {
        title: "Google",
        href: "https://google.com/"
    },
    {
        title: "Web导航",
        href: "https://frontend.microdba.com/"
    }
]

---

## File: package.json

{
    "name": "启示录-blog",
    "title": "启示录",
    "author": "Jeffery <ty4z2008@gmail.com>",
    "version": "1.0.0",
    "homepage": "https://blog.microdba.com/",
    "repository": {
        "type": "git",
        "url": "https://github.com/ty4z2008/ty4z2008.github.io"
    },
    "bugs": "https://github.com/ty4z2008/ty4z2008.github.io/issues",
    "devDependencies": {
        "grunt": "~0.4.5",
        "grunt-contrib-less": "~0.11.4",
        "grunt-contrib-watch": "~0.6.1",
        "grunt-banner": "~0.2.3",
        "grunt-contrib-uglify": "~0.5.1"
    },
    "scripts": {
        "preview": "cd _site; python -m SimpleHTTPServer 8020",
        "py3view": "cd _site; python3 -m http.server 8020",
        "watch"  : "grunt watch & npm run preview & jekyll serve -w",
        "py3wa"  : "grunt watch & npm run py3view & jekyll serve -w",
        "boil"   : "git push boilerplate boilerplate:master",
        "push"   : "git push origin master --tag",
        "cafe"   : "git co gitcafe-pages; git merge master; git push gitcafe gitcafe-pages:gitcafe-pages --tag; git co master;"
    }
}

---

## File: _includes/about/zh.md

> 生活不只是眼前的苟且，还有诗和远方 


Hey,I'm Jeffery

---

## File: _posts/2018-10-18-how-to-reading-a-paper.md

---
layout: post
title:  "如何阅读一篇论文"
date:   2018-10-28 15:16:29
categories: Thinking
tags: 阅读笔记
---

如何阅读一篇论文

Learning to efficiently read a paper is a critical but rarely taught skill.

学习高效的阅读论文重要却鲜少为人所教之事

Three-Pass Approach 三阶段阅读法

第一阶段：掌握该论文的整体观点，讲述的是一个什么观点

第二阶段：领略论文的内容，但非所有的细节

第三阶段：了解整理论文的细节



**Phase 1:**

快速的扫描整篇文章，并知道整篇论文的架构。决定是否进行下一阶段。此阶段至少需要5～10分钟的时间。并整理步骤：

1. Carefully read the title,Abstract, and introduction.

   仔细阅读标题、大纲与前言。

2. Read the section and sub-section headings, but ignore everything else 

   阅读文章中的章节标题，但是先忽略其中的内容

3. Read the conlusions 

   阅读结论

4. Glance over the references, mentally ticking off the ones you've already read.

   快速阅读参考资料，并在心中勾选阅读过资料

验证是否完第一阶段，要必须能回答下面五个问题：

1. Category: what type of paper is this? A measurement paper? An analysis of an existing system? A description of a research prototype?

   论文的类型/属性：这篇论文的类型是什么？是量化测量的研究？分析已经存在的系统？描述一研究的原理？

2. Context: Which other paper is it related to? Which theoretical bases were used to analyze the problem?

   背景/脉络：这篇论文与其他论文有什么关系？这篇论文是采用什么理论分析问题？

3. Correctness: Do the assumption appear to be valid?

   正确性：此论点假设是否正确？是否靠谱

4. Contributions: What are the paper's main contributions?

   贡献：这篇论文的主要贡献是什么？

5. Clarity: Is the paper well written?

   清晰度：这边论文表达的文笔是否清晰？

通过上面五个问题，可以知道对阅读论文的议题有没有兴趣、有没有充分了解这个领域相关的知识、或者作者的见解是否有价值。第一阶段阅读完对我们的帮助在于大致上看一篇「你可能现在不会用到，但是以后也许有机会使用的到」的文章，第一次阅读对于你拿到一份不甚熟悉的领域的文章时，比较有帮助。

如果是自己写研究型论文的时候，可以设想审核你文章的人或者事读者是否能通过第一步就能大致的了解全文、在写作时注意段落与副标题之间的连贯性、写摘要时要精确并且易于理解。

如果审核你文章的人没办法一遍阅读后很快抓住你的主旨，论文很可能被拒绝。如果是读者第一遍没有办法通过第一遍之内看懂你的论文，则很有可能不会有人读。

**Phase 2**

第二阶段阅读，要把重点放在论文本身的内容，但仍需要忽略一些细节的部分，如证明。这个阶段略记一些论文的关键处，或是旁加备注与评论。

1. 仔细阅读论文中的示意图、分析图、或者是其他图标类型的内容。特别注意这些图标表示的内容。一如图表中的坐标线是否完整被标示？图中的标记数据是否与研究的结论是否匹配？这些失误可以在短时间内区分作品是否从真实验中获得数据？
2. 记录那些未读到过的相关参考资料，为将来更近一步阅读做准备。（这个方法有助于对这篇论文本身的背景有更多的理解）

「如果阅读某一领域的书或者是论文具有广度之后，读它的参考资料就可以理清一套架构。」

第二次阅读大略会花上一个小时左右，这次阅读之后应该要掌握论文的主旨、内容、以及论文中所描述的证明点。这样细读的过程对于属于自己领域的论文非常合适，但是对于你所要进行的研究主题可能仍不够深刻。

这阶段完成之后，很有可能还无法了解论文。那可能有以下因素：

1. 主题对于你来说比较新颖、不太熟悉。有些专业的术语、缩写对于你来说是新的。
2. 作者使用的证明与实验技巧是你无法理解的，导致论文中很多地方无法意会。
3. 该论文有可能在无主张，遣词不佳，并且过多的引用参考资料导致需要阅读大量的参考文献才能看懂「喜欢`掉书袋`的文章」
4. 夜深人静，太累了。你该休息了。

如果遇到上面的情况，你可以选择如下：

1. 别看了，把论文放在一边，希望这边论文在你未来的职业成功生涯中不需要理解它
2. 重新再读一遍，从文章的背景下手重新来过
3. 管它呢，继续进入第3阶段阅读。



**Phase 3**

第三次阅读在于要彻底了解这边论文的意旨，如果你是论文的审核人，大概也会需要这第三次阅读。第三次阅读的主要目的是：

1. 把文章作者的想法跟着走一遍，与其提出相同的假设。如作者般重新将假设、实验等工作重新来过一次。这个重新验证的过程，不仅让你可以很容易的确认该论文的创新之处，更发现该论文所隐藏的一些失败或未验证的假设。

此过程重视文章的细枝末节，你必须确认且验证每个状态下所设定的各种假设。此外，假设此时自己是作者，你会如何表达这个特别的观点？这样验证与求真的比较，让你对论文中的证明与表达技巧更加敏锐，并且有助于自己增加更多方面的研究工具。经过这个过程，可以帮助你找到未来研究的灵感。

对于新手可能需要5小时以上，老手大概1个小时左右。阅读完该论文之后你应该可以借此勾勒出该论文的结构，并可点出该论文中存在的缺点与优点。找出论文中隐含的假设、遗漏的相关文献及有关实验或技术文件（工程、科学领域）。如果是财务领域应该聊相关的统计或计量技术。

**写文献综述（Literature review）**

如何写好文献综述？

1. 通过[Google Scholar](https://scholar.google.com.hk/)或者是[CiteSeer](http://citeseerx.ist.psu.edu) 根据关键词找出3～5篇这个领域最新的论文。并且阅读它们
2. 通过阅读发现最近相关工作的状况，如果是幸运的话可以找到一篇总结的论文，那么可以深度的阅读
3. 在这些3～5篇的论文中找出参考文献出现最多的作者和论文。这些一般是这个领域的关键论文和研究者
4. 下载这些论文并且去这些研究者的博客看看最近发表的论文
5. 看看相关领域的顶级会议投稿，一般优秀的研究者都会发布论文到这些顶级的会议
6. 在相关的会议投稿中，找到与之相关研究领域的论文。
7. 快速的阅读，快速识别出来最近一些比较高质量的工作。并且与之前你收集的资料相对比，看看是否有相同的文献，快速的阅读。
8. 抽出每篇论文的关键点，并开始书写综述。



一篇论文的结构：

1. 摘要（100～150字）
2. 介绍：介绍问题，方案概要。描述问题的重要性
3. 相关工作，在总结之前
4. 本文其余部分的概叙：“这篇论文的结构是，在第二章节，我们介绍...，第三章节描述....，最后xxxx我们描述一下未来的工作在第五段”
5. 主要内容，包含实验和至少一个方案。插图、一个清晰的问题程序模型。不要太过教条主义。描述真实性，包含实现细节、平台、位置、依赖。最小资源等。主要是实验的必备条件。以及它的实际价值是什么，通过图表的形式来说明。
   - 问题
   - 方案和架构
   - 结果/结论
6. 相关工作，如果只是开始还未结束
7. 总结和未来工作：一般是复述主题
8. 致谢
9. 参考文献
10. 附录（不强制）。描述协议的详细细节、证明细节、简单的术语但是细节非常重要


- [How to read a paper](http://ccr.sigcomm.org/online/files/p83-keshavA.pdf)
- [How to read a research paper](https://www.eecs.harvard.edu/~michaelm/postscripts/ReadPaper.pdf)
- [Writing Reviews for Systems
  Conferences](https://people.inf.ethz.ch/troscoe/pubs/review-writing.pdf)
- [Writing Technical Articles](http://researchswinger.org/others/writing-schulz.pdf)
- [Whitesides’ Group: Writing a
Paper](https://intra.ece.ucr.edu/~rlake/Whitesides_writing_res_paper.pdf)

---

## File: _posts/2018-12-01-dockerfile-best-practices.md

[Binary file]

---

## File: _posts/2018-12-07-migrate-private-git-to-private-gitlab.md

---
layout: post
title:  "迁移私有化git到gitlab"
date:   2018-12-09 15:16:29
categories: Docker
tags: 阅读笔记
---

摘要

主要从原因、调研、部署、迁移以及总结几个方面

**原因**

我们使用git用四年，团队中的所有项目都是在git下面开发。代码库的代码量在10GB+左右。随着团队的发展。工程化以及自动化的需求与呼声越来越高。我们希望通过git来实现gitops、devops、CI/CD。旧的git服务是使用[gitosis](https://github.com/res0nat0r/gitosis)搭建的.如果是自己开发改造的时间太长。

**调研**

选择了两个git工具：[gogs](gogs.io)和[gitlab](gitlab.com)

|    -      |  Gogs     |   Gitlab    |
|---        |---        |---    |
| 基本的GIT  | 1         |  1     |
| 管理界面   | 1         |  1     |
| CI/CD     |  0        |   1    |
| Codereview|   0       |   1    |

基于以上的考虑，我们最终选择Gitlab。

**部署**

官方建议是使用2核4Gb的配置。这里我主要是用的docker进行部署。使用docker部署主要是考虑到更新版本方便。当出现故障的时候能快速恢复。容器化主要处理的问题是数据落盘问题。这里我们使用磁盘挂载的形式，把数据目录存储在物理主机。然后对物理主机进行磁盘备份。

在使用部署之前还有需要处理一些日常git体验的问题。

1. git如果是使用ssh协议拉取代码，那么最常用的是22端口。解决这个问题是把主机的ssh端口修改掉。修改`/etc/ssh/sshd_config`的port为非22端口。然后重启ssh服务。（注：一般的云服务对端口要防火墙，防火墙也需要配置）。
2. 时间问题，这里通过指定时区即可

下面是docker-compose 的yaml文件配置例子，对于生产环境image最好指定版本，防止后面出现版本文本导致旧的数据不同步

```
version: '3.6'

services:
    gitlab:
        restart: always
        image: gitlab/gitlab-ce:11.5.3-ce.0
        ports:
        - "80:80"
        - "22:22"
        volumes:
        - /data0/gitlab/config:/etc/gitlab:Z
        - /data0/gitlab/logs:/var/log/gitlab:Z
        - /data0/gitlab/data:/var/opt/gitlab:Z
        hostname: 'code.domain.com'
        environment:
        - DEBUG=false

        - TZ=Asia/Shanghai
        - GITLAB_TIMEZONE=Shanghai

        - GITLAB_HTTPS=false
        - SSL_SELF_SIGNED=false

        - GITLAB_HOST=code.domain.com
        - GITLAB_PORT=80
        - GITLAB_SSH_PORT=22
        - GITLAB_RELATIVE_URL_ROOT=
        - GITLAB_SECRETS_DB_KEY_BASE=vjdLTdCdpKPs9qJT7zLvdm4JWhXJdPbRqw9CJvkTvLn4K7R3kMF7RqHrVR3rRF4z #pwgen -Bsv1 64
        - GITLAB_SECRETS_SECRET_KEY_BASE=Lc9HXj7VtXRcwjcs4hfvgWW7Vz4sRdTHw3gRxfVrvxNdrJP3qtfJXqz4vRvkNqvK #pwgen -Bsv1 64
        - GITLAB_SECRETS_OTP_KEY_BASE=XbhxW7tRnNCWjRJbnLh4LW34FrLXsM4PnrspxvHRPzmFXdHkzr4fm9H7wv9Wj9jX #pwgen -Bsv1 64

        - GITLAB_ROOT_PASSWORD=password
        - GITLAB_ROOT_EMAIL=yourname@email_domain.com

        - GITLAB_NOTIFY_ON_BROKEN_BUILDS=true
        - GITLAB_NOTIFY_PUSHER=false

        - GITLAB_EMAIL=yourname@email_domain.com
        - GITLAB_EMAIL_REPLY_TO=yourname@email_domain.com
        - GITLAB_INCOMING_EMAIL_ADDRESS=yourname@email_domain.com

        - GITLAB_BACKUP_SCHEDULE=daily
        - GITLAB_BACKUP_TIME=02:00

        - SMTP_ENABLED=true
        - SMTP_DOMAIN=exmail.qq.com
        - SMTP_HOST=smtp.exmail.qq.com
        - SMTP_PORT=465
        - SMTP_USER=yourname@email_domain.com
        - SMTP_PASS=password
        - SMTP_STARTTLS=true
        - SMTP_AUTHENTICATION=login
        networks:
          gitlib:
            aliases:
              - code.internal.domain.com
              - code.domain.com
networks:
  gitlib:
    external: true
```

**迁移**

当gitlab服务起来之后就是导入就的项目了。

之前的项目地址是`git@git.domain.com:project.git`。而gitlab中项目是属于群组或用户的。

1. 先登录你的web界面添加一个群组或用户。
2. 进入宿主机的`/data0/gitlab/data/git-data/repositories`目录，首先新建群组（与群组名对应）或用户目录（与用户名对应）。
3. 进入刚刚新建的目录
4. 通过scp命令获取是rsync命令把`project.git`导入到当前目录的`project.git`中
5. 如果你是使用root用户导入的先需要修改权限

   ```bash
   chown -R chrony project.git && chmod +770 -R project.git && echo 'project migrate done'
   ```
5. 登录到gitlab容器里面
6.  执行`gitlab-rake gitlab:import:repos`进行恢复。
7.  完成

**总结**

致此就完成了新旧git的迁移。在后续你就可以对项目设立保护分支。进行git-flow。或者是Gitops。团队工程的发展离不开开发工具的更新与迭代。引入新的工具可以让整个团队的软件工程得到一些积极方面的促进。

上面的部署比较适合小的团队。如果是大的团队在HA方面可以对DB、redis使用云服务的db服务或者是单独部署。数据文件存储可以使用NFS。部署多个gitlab服务。然后外部通过slb进行访问。图例：

---

## File: _posts/2018-12-09-postgres-runtime-config-logging.md

---
layout: post
title:  "PostgreSQL开发者模式错误反馈与日志设置"
date:   2018-12-09 15:53:49
categories: PostgreSQL 数据库系统
tags: postgresql
---

**when何时记录**

```
#client_min_messages = notice
log_min_messages = debug5 #debug级别是提供给开发人员使用的，这个可以看到程序调用的信息以及SQL转化为数据结构的信息，每分钟的级别
```

**where记录到哪里**

```
#log_destination = 'stderr'
logging_collector = on   #打开日志收集
log_directory = 'pg_log' #日志目录
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'   
```

**what写什么日志**

```
debug_print_parse = on #解析树
debug_print_rewritten = on #查询重写后的SQL
debug_print_plan = on    #执行计划详细
debug_pretty_print = on #对debug_print_parse,debug_print_rewritten,debug_print_plan可读性格式化
#log_checkpoints = off #如果是研究pg的磁盘IO，这个需要设置为on
log_connections = on #连接日志
log_disconnection = on #断开连接日志
#log_duration=on #语句执行时间，对于分析
```

参考：

- [Error Reporting and Logging](https://www.postgresql.org/docs/current/static/runtime-config-logging.html)

---

## File: _posts/2018-12-21-use-taints-and-toleration-manage-pod-kubernetes.md

---
layout: post
title:  "在Kubernetes中使用Taints和Toleration划分专用节点"
date:   2018-12-21 15:53:49
categories: kubernetes Docker
tags: kubernetes
---

#### 背景

在生产环境中，可能会遇到某些节点只为提供给专门的Pod运行。这里可以使用k8s提供的`容忍`和`污点`组合来解决这样的困境。

#### 概念

节点亲和性可以让pod调度到指定的一类节点，污点(taint)则相反,它让节点能排斥一类Pod。在k8s集群里面所有的非系统Pod对于带有污点的节点是不会调度过去的。

结合背景，Pods的服务其实想调度到指定的节点，然后这个节点不能运行其他的Pods。


#### 操作

下面创建一个Busybox服务，我希望Busybox只能调度到一类标记有busybox的节点，并且这类节点不允许调度其他的Pod

1. 首先我们给节点打上标签BusyBox，并且不允许其他节点调度

```bash
kubectl label node node-name  node-role=busybox

#添加
kubectl taint nodes node-name role=busybox:NoSchedule
#移除
kubectl taint nodes node-name role=NoSchedule-

``` 

2. 创建busybox YAML文件

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: busybox
  labels:
    app: busybox
spec:
  replicas: 1 
  selector:
    matchLabels:
      app: busybox
 
  template:
    metadata:
      labels:
        app: busybox
    spec:
      tolerations:
      - key: "role"
        operator: "Equal"
        value: "busybox"
        effect: "NoSchedule"
        tolerationSeconds: 2000
      nodeSelector:
        node-role: busybox     
      containers:
      - image: busybox
        command:
          - sleep
          - "3600"
        imagePullPolicy: IfNotPresent
        name: busybox
        volumeMounts:
        - name: tz-config
          mountPath: /etc/localtime
      restartPolicy: Always
      volumes:
        - name: tz-config
          hostPath:
            path: /usr/share/zoneinfo/Asia/Shanghai  
```

相比较常用的yaml配置增加了下面一段`tolerations`和`nodeSelector`.`nodeSelector`是选择打有`busybox`标记的节点.`tolerations`是允许带有`role=busybox`污点的节点调度上去。`nodeSelector`是选择节点，`tolerations`是让节点排除`role=busybox`条件成立的Pods。

`tolerationSeconds` 是当 pod 需要被驱逐时，可以继续在 node 上运行的时间,单位秒。


```yaml
tolerations:
- key: "role"
  operator: "Equal"
  value: "busybox"
  effect: "NoSchedule"
  tolerationSeconds: 2000
nodeSelector:
  node-role: busybox 
``` 

**关于operator值**

operator的值为Exists，这时无需指定value
operator的值为Equal并且value相等

在官方资料里面，标记节点污点参数（对应`effect`）有三个可选值。

- NoSchedule 新的Pod如果没有查找到带有污点的节点就不调度，在节点已经存在的继续运行

- PreferNoSchedule 如果一个Pod没有声明容忍这个Taint，则系统会尽量避免把这个pod调度到这一节点上去，但不是强制的。节点上已经存在的pod继续运行

- NoExecute 定义pod的驱逐行为，以应对节点故障。NoExecute这个Taint效果对节点上正在运行的pod有以下影响：

没有设置Toleration的Pod会被立刻驱逐

配置了对应Toleration的pod，如果没有为tolerationSeconds赋值，则会一直留在该节点

配置了对应Toleration的pod且指定了tolerationSeconds值，则会在指定时间后驱逐



参考资料:

- [Advanced Scheduling and Taints and Tolerations](https://docs.okd.io/latest/admin_guide/scheduling/taints_tolerations.html)
- [Taints and Tolerations](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/)

---

## File: _posts/2018-12-28-how-to-use-watch-Object-change.md

---
layout: post
title:  "在JS中如何检测对象的改变？"
date:   2018-12-28 22:53:49
categories: JavaScript
tags: JavaScript
---

有时候在业务当中会遇到当某个对象改变的时候做一些逻辑处理。在Vue里面数据的双向绑定则是也提到监控对象的变化。主要是利用Object.defineProperty特性。下面是一段对Object.defineProperty扩展实现对象的监控。

```javascript
//https://gist.github.com/eligrey/384583
// object.watch
if (!Object.prototype.watch) {
    //在所有对象定义一个watch熟悉，设置为不可重写
    Object.defineProperty(Object.prototype, "watch", {
        //当且仅当该属性的enumerable为true时，该属性才能够出现在对象的枚举属性中。
          enumerable: false
        //当且仅当该属性的 configurable 为 true 时，该属性描述符才能够被改变，同时该属性也能从对应的对象上被删除
        , configurable: true 
        //当且仅当该属性的writable为true时，value才能被赋值运算符改变。
        , writable: false
        //该属性对应的值。可以是任何有效的 JavaScript 值（数值，对象，函数等）。
        , value: function (prop, handler) {
        //watch属性的值是一个函数。参数为对象的key和回调
            var
              oldval = this[prop]
            , newval = oldval
            //读取时直接返回
            , getter = function () {
                return newval;
            }
            //修改时执行回调，回调的参数是key,旧值，新值
            , setter = function (val) {
                oldval = newval;
                return newval = handler.call(this, prop, oldval, val);
            }
            ;
            // can't watch constants
            if (delete this[prop]) { 
                Object.defineProperty(this, prop, {
                    //一个给属性提供 getter 的方法，如果没有 getter 则为 undefined。
                      get: getter
                    //修改时执行
                    , set: setter
                    , enumerable: true
                    , configurable: true
                });
            }
        }
    });
}

// object.unwatch,解除绑定
if (!Object.prototype.unwatch) {
    Object.defineProperty(Object.prototype, "unwatch", {
          enumerable: false
        , configurable: true
        , writable: false
        , value: function (prop) {
            var val = this[prop];
            delete this[prop]; // remove accessors
            this[prop] = val;
        }
    });
}
//example
let a = {test:1};    
a.watch('test',function(){
    console.log('change')
});
a.test = 3;
a.unwatch('test');
a.test = 2;
```

有一个开发者基于上面的思想实现了一个库，可以在多种环境下面使用。[Watch.JS](https://github.com/melanke/Watch.JS/blob/master/src/watch.js)

参考资料

- [Object.defineProperty()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty)

---

## File: _posts/2019-06-11-reading-tidb-code-1.md

---
layout: post
title:  "TiDB 源码阅读笔记：开始"
date:   2019-06-11 00:53:49
categories: tidb 数据库系统
tags: tidb
---

**源码编译**

```shell
export GO111MODULE=on
export GOPROXY=https://goproxy.io
make

#启动服务
./bin/tidb-server

#连接
mysql -uroot -P4000 -uroot -h 127.0.0.1
```



**Connect连接**

server/server.go:Run():监听端口，接收连接。解析协议分发给不同的函数

在Run()函数中，通过插件（plugin包）的形式处理连接。

tidb-server/main.go:Main():解析参数、检查版本、注册存储（TiKV/本地TiKV）、注册监控、加载配置、合并参数、校验配置、设置全局变量、写入启动日志、注册jaeger跟踪、打印版本信息、设置binlog、启动监控、创建存储域、创建服务器、发送信号通知启动、创建服务器、清除、执行完毕。定义服务如何启动

Client —进入server/conn.go:Run()：解析连接，分发command到不同的函数:

Run()详细流程：对于连接会设置一个timeout记时，当超过（setReadTimeout()）配置时间时关闭连接。读取网络包(cc.readPacket).当读取失败时断开连接。分发Command(conn.go:dispatch())。

Command：分为Ping、Operation、Query、预处理、关闭连接、设置参数、改变用户、发送、对大数据类型的数据处理（`TEXT`,`TINYTEXT`）、参数重置

- 如果是SQL——conn.go:handleQuery:解析SQL为AST—vaildator校验——逻辑优化——物理优化——物理计划----
- TiDBContext : session数据结构

Session/session.go:定义核心的数据结构、每个会话都拥有一个session

Session/session.go:executeStatement

Q1:Session主要包含什么？

```go
//包含的内容在Session interface中定义
sessionctx.Context 
    Status
    LastInsertID
    LastMessage
    AffectedRows
    Execute
    String
    CommitTxn
    RollbackTxn
    PrepareStmt
    ExecutePreparedStmt
    DropPreparedStmt
    SetClientCapability
    SetConnectionID
    SetCommandValue
    SetProcessInfo
    SetTLSState
    SetCollation
    SetSessionManager
    Close
    Auth
    ShowProcess
    PrepareTxnCtx
    FieldList

```

执行上下文、授权、进程信息、TLS状态、命令、预备执行语句、事务状态、当前的状态值、影响的行、字符集、SQL语句。

#### SQL层架构







**解析SQL为AST**

这部分由[parser](github.com/pingcap/parser) 处理。负责将SQL解析为抽象语法树。

parser组件逻辑（拿到AST的后续处理）：

```
SELECT Hello;
```

解析过程：AST—>parser—>parser.py—>SelectStmt

SQL解析图：[https://pingcap.github.io/sqlgram](https://pingcap.github.io/sqlgram/#ALTER)

如果对语法感兴趣可以看:yacc、lex

**进入validator组件**

Visitor模式：检查语法、遍历AST树。检查是否合法在planner/preprocess.go:Preprocess()

访问者模式：主要将数据结构与数据操作分离。

**进入逻辑优化**

基于规则的优化RBO

executor/builder.go/compiler.go：逻辑计划处理核心

planner/optimize.go：执行优化

planner/core/preprocess.go:Build():构建出逻辑计划

planner/core/expression_rewriter.go:EvalAstExpr

planner/core/expression.go：expression是一个接口（interface）:编译SQL的值为常量（`select 'hello'`）、编译为SQL的函数（`select cast('1,2,2') as k`）、编译为column、编译为corralate column

expression包含表达式相关逻辑，包括各种运算符、内建函数、聚合函数（sum、count）

逻辑优化部分代码：planner/ruler*.go

列裁剪、分区裁剪等

**进入物理优化**

基于代价的优化CBO,得到物理执行计划。

**用物理计划构建出一个tidb executor**

executor/adapter.go

executor是执行器逻辑部分，包含大部分的执行逻辑。

火山模型：数据库执行器中实现的设计模式。该模式把关系代数中的每一种操作抽象为Operator 树，Operator之间使用迭代器串联。对SQL语法的AST抽象





解析方式：首先对表扫描，hash聚合、排序、输出值

Chunk format: [http://arrow.apache.org/](http://arrow.apache.org/)

Apache Arrow 是一种基于内存的列式数据结构

util/codec/bytes.go:关系数据存储到tikv的中。key:tableId_rowid。value是数据



执行一条DDL SQL:

```sql
create table t2(id int,name varchar(10));
```



session.go—>ddl.go—>ddl_worker.go—>domain.go—>ddl_worker.go—>ddl.go—>domain.go—>split_region.go



Debug `select 1;`



参考：

- [聊聊 Apache Arrow](https://www.infoq.cn/article/apache-arrow)
- [Jaeger: open source, end-to-end distributed tracing](https://www.jaegertracing.io/)
- [Zap: Blazing fast, structured, leveled logging in Go](https://godoc.org/go.uber.org/zap)
- [从Go高性能日志库zap看如何实现高性能Go组件](https://mp.weixin.qq.com/s/i0bMh_gLLrdnhAEWlF-xDw)

---

## File: _posts/2019-07-12-backend-roadmap-zh.md

[Binary file]

---

## File: _posts/2019-09-01-how-to-speed-up-insert.md

---
layout: post
title:  "优化MySQL数据插入速度"
date:   2019-09-01 20:53:49
categories: 数据库系统
tags: MySQL
---

有时会遇到需要大量写入数据的场景（恢复表、批量插入），当数据插入时会觉得每秒处理的数据记录(row)并不是特别如意。

先看看插入一条记录需要经过那些阶段：

- 建立连接：TCP的3次握手，建立连接。
- 发送数据到数据库服务器
- 解析SQL
- 打开表
- 插入记录：批量插入也会被转换成一条条插入
- 写入索引：每条记录都有索引。写入记录的数量为当前表中存在的索引数目：1 x N 条索引
- 完成

从上面的阶段可以看到，可以优化的部分是最后三条。我们可以考虑通过下面的方式优化插入速度

- 如果是要插入多条记录到MySQL，可以利用`INSERT INTO tableName VALUES`语句，把多条记录合并为一条这样可以减少多次建立连接、发送数据、解析SQL的时间。

- 临时调整`bulk_insert_buffer_size`为一个比较大的值，这里我建议设置为当前系统**可用**内存的1/4。这个参数是处理插入数据的临时缓存buffer。

- `innodb_flush_log_at_trx_commit` 调整。

     为1时，表示每次事务提交时，并立即刷入到磁盘，才执行事务已经完成，系统默认配置。
    为0时指每秒刷新写入到磁盘一次，事务写入到log buffer就认为已经完成，当MySQL进程崩溃时，1秒内的数据会丢失。

    为2时，日志为每秒写入磁盘，与为1的差别是，事务完成是当写入系统缓存时标记为完成。当OS出现问题会丢1秒的数据，而mysqld服务崩溃的时候不会丢弃。

    这里在临时导入数据时建议设置为0.

- 如果数据来自文件，可以使用`LOAD DATA`语句导入数据

- 对于存在默认值的列，只有插入列与默认值不通时才执行插入，这种方式对于已有数据处理的时间是不划算的，虽然是是优化项目。

- 对于innodb的存储引擎，可以临时关闭外键检查`foreign_key_checks`/`unique_checks`参数.

    ```SQL
    SET foreign_key_checks=0;
    SET unique_checks=0;
    ... SQL import statements ...
    SET foreign_key_checks=1;
    SET unique_checks=1;
    ```



**总结**

优化项有很多项目，对于是恢复数据的场景。可以使用下面的参数设置：

```sql
SET foreign_key_checks=0;
SET unique_checks=0;
SET bulk_insert_buffer_size=1/4系统可用内存
```

以上都是会话级别有效的参数.其他的连接不受影响。

下面的参数为系统级别参数，需要重启服务：

```sql
SET innodb_flush_log_at_trx_commit=0;
```

**参考阅读**

- [insert optimization](https://dev.mysql.com/doc/refman/8.0/en/insert-optimization.html)
- [Bulk Data Loading for InnoDB Tables](https://dev.mysql.com/doc/refman/8.0/en/optimizing-innodb-bulk-data-loading.html)

---

## File: _posts/2019-09-05-sed-and-awk-regular-expression-summary.md

---
layout: post
title:  "sed和awk工具，以及正则表达式总结"
date:   2019-09-05 01:53:49
categories: 操作系统
tags: Linux
---
sed和awk命令正则表达式的使用总结

正则表达式
-------------------

以下符号可以在grep、egrep、sed、awk中使用:

| 符号    | 描述                 |
| ------- | -------------------- |
| `.`     | 任意1个字符          |
| `.*`    | 任意字符             |
| `[a-z]` | 任意一个小写字符     |
| `^`     | 以什么字符开始       |
| `$`     | 以此匹配的字符结尾   |
| `*`     | `*`前面的字符重复N次 |


以下表达式符号为可用列表，不一定所有的都可以使用:

| 符号      | 描述                                 |
| --------- | ------------------------------------ |
| `(A|B)`   | A 或者是 B                           |
| `\<`      | 单词锚点开始                         |
| `\>`      | 单词锚点结束                         |
| `\( \)`   | 一组字符、可能是\1,\2等              |
| `?`       | 断言，匹配前面的子表达式零次或一次。 |
| `\{n\}`   | 前面的子表达式重复n次                |
| `\{n,m\}` | 前面的子表达式重复n至m次             |
| `\{n,\}`  | 前面的子表达式至少重复n次            |



sed：流编辑
-------------
`sed`一般用在文本内容的查找和替换，譬如：

```bash
cmd | sed 's/old/new/g' #在cmd的输出结果中，把old字符全部用new替换
sed 's/old/new/g' infile > outfile # 把infile文件中的old字符全部替换为new
```

`sed` 功能包括:

| 功能              | 描述                                        |
| ----------------- | ------------------------------------------- |
| `s/old/new/`      | 替换old为new                                |
| `s/old/new/g`     | 替换所有的old为new                          |
| `/RE/s/old/new/g` | 匹配包含RE行的内容把old替换为new            |
| `3s/old/new/g`    | 替换第3行中所有的old为new                   |
| `3,6s/old/new/g`  | 替换第3行到6行中所有的old为new              |
| `/RE/d`           | 删除包含RE的行                              |
| `1d`              | 删除第1行                                   |
| `10,25d`          | 删除第10到15行                              |
| `/A/,/B/d`        | 删除以A开头或B结尾的行                      |
| `s/RE/-&-/g`      | 查找RE字符，在RE前后加入`-`.&代表匹配的字符 |
| `/RE/p`           | 输出包含RE字符的行号，需要使用`sed -n`      |
| `1,10p`           | 打印1-10行,需要使用`sed -n                  |


例子:

  ```bash
 cat /etc/passwd | sed 's/:.*//'            # 列出用户名
   cat /var/adm/messages | sed 's/.*fail.*/!!!&!!!/'    # 打印出经过
   ifconfig hme0 | sed '/inet/!d;/inet/s/.*inet \(.*\) netm.*/\1/;'
    # 删除没有以 "inet"开头的行, 并且答应IP.
  ```




AWK 编程语言
------------------------

Awk是一个扩展文本处理语言，它一般用在打印字段或者一列文本。譬如：


```bash
cmd | awk '{ print $2 }' #输出cmd命令结果的第二列
awk '{ print $2 }' infile #打印文件第二列内容
```

print -输出声明的变量:

```bash
{ print $2 }#打印第二列2
{ print $2,$4 }         #打印第2和4列
{ print $2 "\t" $4 }         #打印第2和4列，并且在中间加入tab空行
{ print "user:",$1,"uid",$3 }    #按格式化文本的方式输出内容
{ printf "%10s %-5s\n",$1,$2 }  #格式化列，并在输出内容$1,$2，右边加10个空白字符，左边加5个空白字符
```

查找--代码在` { print ... }`之间:

```bash
/RE/ #包含RE字符的内容
BEGIN            #任何在BEGIN之后列出的操作（在{}内）将在Unix awk开始扫描输入之前执行
END          #而END之后列出的操作将在扫描完全部的输入之后执行。
NR == 4 #行号为4
NR > 2 #行号大于3
NR > 2 && NR < 11   #行号在3和10之间
$3 ~ /RE/       if #第三列包含 RE
$9 !~ /RE/      if #第9列不包含RE
```

内置变量 - awk中的变量,同样在` { ... }`语句中:

```bash
NR          #行号
FS = ":"         #设置输出格式以:间隔
OFS = "\t"      set #输出内容以tab间隔
name = $1        #变量name的值为$1
sum = sum + $5 #变量sum为sum+$5列
sum += $5        #和上面一样
x = 5            #变量 x = 5
print x          #输出变量 x
print "name:" #输出的内容前加上name:
```

例子:

 ```bash
ls -l | awk '{ print "file",$9,"has size",$5,"bytes" }   # 格式化ls命令结果
   cat /etc/passwd | awk 'BEGIN { FS=":" } { print $1 }'    # 列出用户名
   IP=`ifconfig hme0 | awk '/inet/ { print $2 }'`       # 查询IP时，并赋值给变量IP
   cat logfile.txt | awk '                  # report
    BEGIN  { print "My report on blah\n" 
            printf "%10s %-20s | %s\n","hostname","time","fault" }
    /fail/ { print "AHOY! The following line is a failure!" }
          { sum = sum + $5; printf "%10s %-20s | %s\n",$2,$3,$1 }
    END    { print "total faults:",sum }'
 ```



**参考阅读**

- [regexp, sed & awk](http://www.brendangregg.com/Unix/re-sed-awk.txt)
- [正则表达式手册](https://tool.oschina.net/uploads/apidocs/jquery/regexp.html)

---

## File: _posts/2019-09-15-about-google-tech-stack.md

---
layout: post
title:  "一张图了解Google技术栈"
date:   2019-09-15 23:16:29
categories: Thinking
tags: 阅读笔记
---

一张图了解Google技术栈.

图中涵盖Google的所使用的技术，以及对应的开源产品或SAAS产品。




[xmind源文件](/attachment/20190915/google-stack.xmind)

参考资料

- [xg2xg](https://github.com/jhuangtw-dev/xg2xg)

---

## File: _posts/2019-09-25-visual-explain-plan-for-mysql.md

---
layout: post
title:  "[翻译]MySQL 可视化explain查询计划"
date:   2019-09-25 13:16:29
categories: Thinking
tags: MySQL
---


> `explain`拓展特性需要在MySQL 5.6.的版本以上。本文是讲述workbench的可视化查询计划如何阅读内容.

示例SQL：

```mysql
SELECT CONCAT(customer.last_name, ', ', customer.first_name) AS customer, address.phone, film.title
FROM rental
INNER JOIN customer ON rental.customer_id = customer.customer_id
INNER JOIN address ON customer.address_id = address.address_id
INNER JOIN inventory ON rental.inventory_id = inventory.inventory_id
INNER JOIN film ON inventory.film_id = film.film_id
WHERE rental.return_date IS NULL
AND rental_date + INTERVAL film.rental_duration DAY < CURRENT_DATE()
LIMIT 5;
```



从图中阅读的执行顺序方式：
自底向上、从左到右

**图形注释**

- 圆：代表操作，譬如GROUP、SORT
- 框：子查询
- 菱形：join，连接
- 标准框：数据表

**文字约定**

- 标准框：表名或者别名
- 加粗的字体：键/索引
- 框顶部右边的字：扫描行
- 框顶部左边的字：关系扫描代价成本（5.7版本）
- `nest loop`菱形右边文字：连接之后产生的记录
- 菱形顶部的字：JOIN的代价成本（5.7版本）

**可视化图信息解释**

|名称     | 颜色 | 图中文字                       | 解释                                  |
| --------------- | ----- | -------------------------------------------- | ------------------------------------------------------------ |
| SYSTEM          | 蓝色  | Single row: system constant（系统常量）      | 代价低，匹配一条。一般是主键或唯一索引做查询条件       |
| CONST           | 蓝色  | Single row: constant（常量）                 | 代价低，匹配一条.主键查找                                    |
| EQ_REF          | 绿色  | Unique Key Lookup                            | 代价低 -- 使用唯一索引扫，一般出现在多表连接时使用主键或唯一索引作为关联 |
| REF             | 绿色  | Non-Unique Key Lookup                        | 随着记录增加，代价也会增加。使用非唯一索引或唯一索引的前缀扫描，返回匹配某个单独值的记录行 |
| FULLTEXT        | 黄色  | Fulltext Index Search                        | 全文检索，代价低。特定的查询                                 |
| REF_OR_NULL     | 绿色  | Key Lookup + Fetch NULL Values               | 扫描行少时代价低;行数较多时代价高。                          |
| INDEX_MERGE     | 绿色  | Index Merge                                  | 适中 -- 索引合并查询中选择更好的索引来查询。对于多个索引分别进行条件查询，然后将结果合并。如果是index_merge for intersect会需要使用复合索引进一步优化 |
| UNIQUE_SUBQUERY | 橙色  | Unique Key Lookup into table of subquery     | 代价低-- 子查询中使用唯一索引查询                            |
| INDEX_SUBQUERY  | 橙色  | Non-Unique Key Lookup into table of subquery | 代价低--子查询中使用索引查询                                 |
| RANGE           | 橙色  | Index Range Scan                             | 代价中 -- 使用索引进行了范围查找`<`,`>`,`<=`,`>=`,`is null`,`in`,`between` |
| INDEX           | 红色  | Full Index Scan                              | 高 --全索引扫表。对于特别大的索引就应该较大                  |
| ALL             | 红色  | Full Table Scan                              | 代价非常高 -- 全表扫描，无使用索引。                         |
| UNKNOWN         | 黑色  | unknown                                      | 当无法匹配到查询类型时显示                                   |


**总结**

查询计划时进行优化时的重要部分，在命令行中，我们遇到通过`explain extended`不是特别直观时，可以使用[workbench](https://www.mysql.com/products/workbench/)工具查看

**参考资料**

- [代价模型cost model](https://dev.mysql.com/doc/refman/8.0/en/cost-model.html)
- [Visual Explain Plan](https://dev.mysql.com/doc/workbench/en/wb-performance-explain.html)

---

## File: _posts/2019-10-04-use-case-of-openmessaging-specification-zh-cn.md

[Binary file]

---

## File: _posts/2020-01-15-about-aliyun-analyticdb-for-mysql.md

[Binary file]

---

## File: _posts/2020-02-02-how-to-read-document.md

---
layout: post
title:  "如何阅读技术文档"
date:   2020-02-02 23:29:49
categories: reading
tags: 消息队列 阅读笔记 Golang
---

## 如何阅读技术文档

> 在技术领域经常会接触众多的文档，譬如技术文档、需求文档等。在这里主要讨论的是如何阅读技术文档。它是我们学习新技术除了书籍以外最直接的方法

在某项目中，需要使用到消息队列。经过调研后，选择RabbitMQ作为消息队列的基础服务。服务端语言选择Golang。今天以RabbitMQ为例，来分析如何阅读文档

**第一步：先跑起来**

一个成熟的项目都会有快速指引。进入官网，找到类似`Quick start`或`Get started`的文档。作者会提到如何快速的运行起来一个项目。MQ 项目中是[Get Started](https://www.rabbitmq.com/#getstarted)。它提供安装和教程两部分。



在这里我们只需要使用一行命令，利用现有的容器化运行起来

```bash
docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management
```

**第二步：hello world**

`hello world`是我们开启新世界的钥匙，环境配置好后就需要写第一段代码。因为是消息队列，以发送成功一条`hello world`消息为入口。官方提供了Golang操作MQ发送一条消息的文档。



从文档中，我们知道前置条件。你需要一台已经安装完成的MQ消息队列服务端，这一步已经完成。阅读文档得知：

1. RabbitMQ 是扮演的者消息`经纪人`的角色。负责对消息进行收、转发以及广播。类似邮差

2. 消息包含有三个角色：产生消息的叫生产者、消息传输的通道为队列、收到消息的客户端为消费者

3. 使用Go操作MQ，依赖使用`amqp`库。通过`go get`安装

    ```bash
    go get github.com/streadway/amqp
    ```

4. 按照第2点我们需要实现发送和接收部分。这是两段不同职能的代码，前者为生产者，后者为消费者

**第三步：编码**

看完理论之后，就要开始写代码。在官方文档中，有给出`send.go`的实现：

```go
package main

import (
    "log"

    "github.com/streadway/amqp"
)
//错误处理，当发送或者是连接失败时输出错误flag以及详细
//错误信息
func failOnError(err error, msg string) {
    if err != nil {
        log.Fatalf("%s: %s", msg, err)
    }
}

func main() {
  //建立连接
  //amqp为协议
  //用户名和密码为guest
  //服务器地址:localhost
  //端口: 5672
    conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
    failOnError(err, "Failed to connect to RabbitMQ")
  //程序体运行完成之后，关闭与server端的连接
    defer conn.Close()

  //建立一个channel
    ch, err := conn.Channel()
    failOnError(err, "Failed to open a channel")
  //程序体运行完成之后，关闭通道。
  //defer是一个堆，先进后执行
    defer ch.Close()
    //声明队列，在MQ中需要发送消息前需要声明
  //主要是标示队列拥有的一些属性
  //是否自动删除、队列名称、持久化
    q, err := ch.QueueDeclare(
        "hello", // name
        false,   // durable
        false,   // delete when unused
        false,   // exclusive
        false,   // no-wait
        nil,     // arguments
    )
    failOnError(err, "Failed to declare a queue")
    
  //发送消息
    body := "Hello World!"
    err = ch.Publish(
        "",     // exchange
        q.Name, // routing key
        false,  // mandatory
        false,  // immediate
        amqp.Publishing{
            ContentType: "text/plain",
            Body:        []byte(body),
    })
    log.Printf(" [x] Sent %s", body)
    failOnError(err, "Failed to publish a message")
}
```

执行上面的程序可以看到消息发送成功的日志。通过上面`send.go`中的代码，里面有些内容并没有详细在文档中说明。例如`channel`是什么，为什么要在使用队列之前先打开一个通道才能使用？声明队列的每个参数的含义是什么？发送消息除了消息主体`Hello world`，其他参数的意义是什么？以及我们如何消化生产的消息。

**最后**

上面有好几个问题，带着这些问题去翻阅文档就能慢慢的从冰山一角到一览群山。有些概念上的问题，在文档中一般会在`concept`部分体现。譬如什么是[Channel](https://www.rabbitmq.com/tutorials/amqp-concepts.html#amqp-channels)。它是为了复用单个TCP连接以达到操作多个队列的目的。



#### 参考文献

- [RabbitMQ website](https://www.rabbitmq.com/)
- [RabbitMQ  concept](https://www.rabbitmq.com/tutorials/amqp-concepts.html)
- [Golang](https://golang.org/)
- [Tutorial one go](https://www.rabbitmq.com/tutorials/tutorial-one-go.html)

---

## File: _posts/2020-02-26-mit-6.824-lecture-1-introduce.md

[Binary file]

---

## File: _posts/2020-03-03-mit-6.824-lecture-2-rpc-and-threads.md

[Binary file]

---

## File: _posts/2020-03-14-mit-6.824-lecture-3-gfs.md

---
layout: post
title:  "MIT 6.824 分布式系统课程第三课：GFS"
date:   2020-03-14 20:29:49
categories: 分布式系统
tags: 
    - 6.824 
    - 阅读笔记
catalog: true
---

笔记：[GFS](https://pdos.csail.mit.edu/6.824/notes/l-gfs.txt)

视频：[Lecture 3: GFS](https://www.youtube.com/watch?v=EpIgvowZr00)

论文地址：[The Google File System](https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/gfs-sosp2003.pdf)

**课程概要**

主要讨论Google第一代文件系统GFS。

**正文**

这篇论文的价值？


- 分布式存储系统是分布式系统的关键

    接口和语义化应该如何定义、内部是如何工作的

- GFS 涵盖的内容在6.824中有很多相关知识的涉及

    并行性能、容错、复制、一致性

- 这篇论文属于分布式系统的标志性论文之一，从应用程序到网络。并且得到工业界的验证

构建分布式存储的难点在哪儿？

- 高性能：跨服务器的数据文件分片获取
- 多服务器：标志着更多的故障
- 容错：多副本
- 复制：潜在的不一致
- 一致性：导致性能降低

我们究竟想要如何样的一致性？

- 理想模型：所有的操作就像单机一样
- 服务端使用磁盘存储
- 服务端一次只执行一次客户端操作（即使并发）
- 读始终是最新的写入结果（即使服务器重启或崩溃）

因此，假设有几个场景c1和c2是并发写。在写完成后，发起C3和C4读取。那么会得到什么结果？



答案：可能是1或者是2，但是两个读都应该是相同的值返回（强一致性模型），但是单机无法做到好的容错。在现实分布式系统中肯定存在副本。

为了容错而产生的复制，让强一致性变得棘手。多副本同步写入，势必带来的是性能影响。性能/一致性之间需要取舍

**一个简单的场景：**两台副本服务器、S1和S2，客户端先发起并行写操作，然后立即读取。

**GFS**

**论文概要**

Big & Fast：Google的很多服务都需要统一的存储系统，Mapreduce、爬虫、索引、日志存储、分析、YouTube（？）、相册等等

Global：跨多个数据中心访问，任何客户端可以读取任何数据中心的文件。

sharding：一个文件可能跨多个服务器/磁盘。并行性能和磁盘利用

automatic recovery：出现故障能自动恢复

一个数据中心一次部署

暂时只供内部使用

大文件顺序访问：读/追加。而GFS又不是DB（事务）

在2003年，GFS是凭借什么获得了SOSP最佳论文？

文中的分布式、分区、错误容忍已经在分布式领域出现很久并不是第一次，主要的原因是：大规模在工业界，真实的世界中应用。对弱一致性和单master的实践。

**GFS架构**



客户端（library，RPC --- 这个被使用者隐藏了起来）

每个文件被切分到独立的64MB块中

chunk server：每个块都有三份副本，所有的文件都可以迁移到其他chunk 服务器。支持并行读/写（MapReduce）

也可以存储大文件

分工独立：master负责读写分发，chunkserver负责具体数据

Master的数据

内存（容量小，速度快）：

- file name table:文件名和chunkserver的映射map (写磁盘 Non-Volatile)
- chunk table: 文件版本（Non-Volatile）、chunkserver列表（Non-Volatile）、租约时间（Volatile）、primary信息，它负责写数据，然后派发副本（Volatile）

磁盘：log（日志，每次写操作）、checkpoint（快照恢复，当重启时能快速恢复到保存的最新状态）

**客户端如何读取一个文件？**

1. 发送一个请求&lt;filename,offset> 到master
2. master根据offset查询chunk server（无缓存时）
3. Master响应最新的chunkserver table给客户端
4. 客户端缓存chunk handle 和chunkserver服务器列表
5. 客户端发送请求&lt;chunk handle,offset>到最近的chunkserver
6. chunkserver从磁盘读取文件并响应给客户端（客户端接收的是buffer，library会合并为真正文件）



master如何知道chunk是在哪个chunkserver?  每次写都会经过master

客户端如果是追加一条记录需要经过哪些步骤？


1. 客户端询问master最新的chunk offset
2. 如果master没有找到primary chunk(租约过期)
    1. 当没有chunkserver可写，并且最新的版本号 丢失时直接返回错误
    2. 从其他的副本选举出新的primary chunkserver，以及文件的最新版本
    3. 增加版本号，并写日志到磁盘
    4. 告诉primary和secondary最新的版本号是多少
    5. 副本写新的版本到磁盘，并且响应结果给新的Primary
3. Master告诉client 主副节点，client会对响应结果缓存。只有当Primary访问不到时才重新请求
4. Client发送文件到所有的chunkserver（存内存）
5. Client通知Primary进行append
6. Primary检查当前租约是否过期，chunkserver是否有足够的存储空间
7. Primary根据offset重新计算新的偏移位置
8. Primary写chunk 文件(64MB)
9. Primary通知secondary 新的offset，并且append 文件到chunk file
10. Primary等待所有的副本响应（包括错误的响应，譬如空间不足、超时等）
11. Primary通知客户端成功或者失败
12. 客户端可以从第一步开始重试

**GFS是如何保证一致性的？**

GFS只 保证一次原子写入，数据已经在一个副本落盘。在经过一定时间后，所有的副本数据会一致。

假设出现网络分区，导致脑裂(split brain，存在两个primary)。出现这种情况，GFS引入60秒的租约时间，出现primary不能连通，就必须等待超时（剩余的时间client可以通过cache的primary信息计算）。

当Primary响应客户端已经成功append 记录，随后客户端立即进行读取，看到的应是最新的文件。（注意：不是所有的客户端都能看到最新的文件，GFS是弱一致性。需要等待时间同步到其他节点，通过“就近”复制.）

**GFS 如何容错？**

错误情况：机器崩溃，崩溃后重启、崩溃后被其他机器替换、消息丢失、分区。

假设设计的GFS是强一致性，需要处理什么问题？

e.g 保证所有的客户端看到的文件内容相同

1. Primary应能识别重复写入的请求（保持幂等性），或者是客户端应该处理重复发送
2. 所有的副本要不全部成功，要不全部失败。（必须全部副本写入才能响应成功）
3. Primary崩溃，有些操作可能会被丢失。新的Primary需要与副本通信找回记录
4. 避免读取到旧的数据，所有的读必须去Primary获取最新的记录。或者是在租约期内的副本。

**GFS性能**



1. 读取吞吐量大（94MB/sec/16台chunkserver），平均每台6MB/sec（机械磁盘顺序查找的速度是30MB/sec、每个网卡大约10MB/sec的速度）交换机的连接接近饱和。因此每台机器的性能还是比较低，得益于可扩展性不错，在生产环境中单个GFS集群可以达到500MB/sec。但当遇到大量文件写入时，也会因为网络瓶颈而导致受限。
2. 对文件进行append追加时，其最大的限制是最后一块chunk的追加，因为主 Chunk 会检查这次记录追 加操作是否会使 Chunk 超过最大尺寸（64MB）。如果超过了最大尺寸，主 Chunk 首先将当前 Chunk 填充到最大尺寸，之后通知所有其他副本做同样的操作，然后通知client要求其对下一个 Chunk 重新进行记录追加操作。

**值得思考的问题**

- 为了更好的支持小文件存储，需要些什么？
- 如何支持上亿的文件？
- 能否把GFS用作通用的文件存储系统？
- 跨异地复制如何优雅？
- 现在的部署是一个GFS部署一个数据中心，那么数据中心容灾如何考虑？
- GFS如何应对缓慢的chunk服务器？

关于GFS工程师采访反馈： [GFS: Evolution on Fast-forward](http://queue.acm.org/detail.cfm?id=1594206)

- 文件数量的增加是一个大问题
- Master的内存是有上限的
- 文件数量增加，Master的GC就会慢下来
- master所能承载的客户端连接有限
- 应用程序需要按照GFS改造
- master的故障转移需要手动处理（10s）

在后面使用BigTable解决小文件存储，并且开发的第二代GFS——Colossus正是为了解决单点master问题

**总结**

GFS的论文阐述了性能、错误容忍、一致性相关的设计和评价。GFS是MapReduce中的基础设施。

亮点：

- 集群式文件系统演变为基础设施
- 存储与管理的分离标志（name管理在master，文件存储在chunkserver）
- 并行吞吐
- 大文件分块存储减少开销
- 主chunk顺序写设计
- 利用租期来解决“脑分裂”问题

不足：

- 单master的限制：内存和CPU
- 对小文件存储不够友好
- master的故障转移不够好
- 弱一致性

**参考文献**

- [GFS: Evolution on Fast-forward](http://queue.acm.org/detail.cfm?id=1594206)
- [The Google File System](https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/gfs-sosp2003.pdf)
- [GFS](https://pdos.csail.mit.edu/6.824/notes/l-gfs.txt)
- [Google File System 中文版](http://blog.bizcloudsoft.com/wp-content/uploads/Google-File-System%E4%B8%AD%E6%96%87%E7%89%88_1.0.pdf)

---

## File: _posts/2020-03-28-mit-6.824-lecture-4-primary-backup-replication.md

[Binary file]

---

## File: _posts/2020-04-03-mit-6.824-lecture-5-go-thread-and-raft.md

---
layout: post
title:  "MIT 6.824 分布式系统课程第五课：Go线程和Raft"
date:   2020-04-03 23:00:49
categories: 分布式系统
tags: 
    - 6.824 
    - 阅读笔记
catalog: true
---

笔记：[Lecture 5: Go, Threads, and Raft](https://pdos.csail.mit.edu/6.824/notes/l-go-concurrency.txt)

视频：[Lecture 5: Go, Threads, and Raft](https://www.youtube.com/watch?v=UzzcUS2OHqo&t=3467s)

论文地址：[The design of a practical system for fault-tolerant virtual machines](https://pdos.csail.mit.edu/6.824/papers/vm-ft.pdf)

**课程概要**

Go语言内存模型、并发原语、并发模式、调试

**正文内容**

[Go内存模型](https://golang.org/ref/mem)这篇文章讲述go语言中的内存模型，它告诉我们如何写出正确的多线程代码。goroutines和并发在go中有着千丝万缕的关系，性能、便捷性。经过语言的不断迭代，goroutines的性能也得到极大的提升。本课的重点在于如何写正确的并发代码，而不是性能。

“write correct code”.

**Goroutines，闭包**

goroutines是轻量级线程，彼此并发运行。当main goroutine退出时，程序自动终止，与此同时程序运行的其他goroutines也会终止运行。

闭包：标示符捕获，gotchas绑定

- closure.go：go中拥有头等函数，配合goroutines使用。对变量的作用域进行限制，让声明的函数只作用域闭包内部。
- loop.go：循环创建goroutine（输出结果不一定按顺序输出，因为并发，并非同步执行）
- bad.go：变量i访问的是gorountine外部变量，每个goroutine访问时的结果都不确定，受外部循环执行快慢的影响。

**Time**

- sleep.go：time.Now()、time.Sleep()
- sleep-cancel.go：不要写无限循环的代码。使用锁时要小心死锁。函数返回（return）前需要释放锁

**Mutexes：互斥量**

- bad.go
- basic.go：基本使用defer
- per-field.go：锁保护是保护变量，不是保护访问共享数据。锁的目的是为了在访问或者是修改变量时，只有单个线程在操作。其结果不受其他线程的影响。所以转账的增和减应在同一个锁里面。

    **关键：**临时暂停其他线程的修改、unlock解锁访问。没有其他线程打断访问

    **锁：**确保lock()与unlock之间的代码执行的原子性，防止竞争访问或者修改

- bank.go：银行转账问题

**条件变量**

- 等待（wait）、信号（signal）、广播（broadcast）
- vote-count-1.go ....vote-count-4.go
- cond.txt，如何避免竞争条件。
    - 锁的使用
    - 使用循环检查条件状态，也可以使用条件变量sync.Cond 
    - 锁应避免在会导致CPU100%的代码段中使用

**Channel：通道**

- 类似同步队列原语
- producor-consumer.go 生产消费者模型
- wait等待前置调用完成
- unbuffered.go：未初始化长度的通道，同步
- deadlock.go：死锁

**Debugging：调试**

- 在Raft目录下可以使用util.go中的DPrintf调试，输出日志
- 使用-race帮助识别数据竞争
    - go test -race -run 2A
    - not a proof
    - race不是万能的，不是所有的竞争数据情况都能识别到
- SIGQUIT
    - SIGQUIT默认监听所有的goroutines.并打印栈
    - Ctrl+\可以发送SIGQUIT到当前进程
- 处理goroutines溢出
    - 使用ps命令查看运行的进程
    - 发送SIGQUIT或者是SIGKILL信号给进程：kill -QUIT pid 或 kill -KILL pid
- 并行
    - 同时跑多个测试用例可以更轻松的发现一些并发bug
    - [Script for running go tests many times in parallel, printing the current status, and logging errors](https://gist.github.com/jonhoo/f686cacb4b9fe716d5aa)

**常用工具**

- 使用man命令查看工具使用的手册
- 或者使用tldr命令快速的查看手册：[tldr.sh](https://tldr.sh/#installation)
- 重定向输出
    - stdout输出正常结果
    - stderr输出错误结果
    - `>` 、`&>`
        -  重定向输出结果到文件，错误会接着显示
        - & 错误和标准输出都写入到文件
    - tee、通道`|`、`|&`
        - tee重定向输出到文件并显示
        - `echo "example" | tee FILE`
        - 使用`|&`可以重定向标准输出和错误
- 文件阅读
    - head、tail、less
    - grep
        - -i不区分大小写
        - -n显示行号
        - grep -in searchString file
    - ripgrep [Ripgrep](https://github.com/BurntSushi/ripgrep#installation)
        - 和grep类似，但支持整个目录

**参考资料**



- [The Go Memory Model](https://golang.org/ref/mem)
- [Go 并发代码](https://pdos.csail.mit.edu/6.824/notes/go-concurrency.tar.gz)
- [頭等函數](https://www.wikiwand.com/zh-hans/%E5%A4%B4%E7%AD%89%E5%87%BD%E6%95%B0)
- [Visualizing Concurrency in Go](https://divan.dev/posts/go_concurrency_visualize/)
- [Go Concurrency Patterns: Pipelines and cancellation](https://blog.golang.org/pipelines)
- [Go Concurrency Patterns: Timing out, moving on](https://blog.golang.org/concurrency-timeouts)

---

## File: _posts/2020-04-29-mit-6.824-lecture-6-fault-tolerance-raft-1.md

---
layout: post
title:  "6.824 分布式系统课程第六课: 错误容忍：Raft（1）"
date:   2020-04-29 11:50:49
categories: 分布式系统
header-mask: 0.25
tags: 
    - 6.824 
    - 阅读笔记
catalog: true
---

笔记：[Lecture 6: Fault Tolerance: Raft (1)](https://pdos.csail.mit.edu/6.824/notes/l-raft.txt)

视频：[Lecture 6: Fault Tolerance: Raft (1)]( youtube.com/watch?v=64Zp3tzNbpE)

论文地址：[In Search of an Understandable Consensus Algorithm](https://pdos.csail.mit.edu/6.824/papers/raft-extended.pdf)

## 课程概要

Raft选举和日志处理（实验2A、2B），第二课讲Raft一致性、客户端行为、快照（实验2C、实验3）

## 正文内容

**选择Raft的原因：Paxos太难理解**

因为相比较Paxos而言，理解起来要容易。第一，Paxos的原论文也比较晦涩难懂。譬如Paxos的single-decree 版本管理。第二，实际生产系统中的应用并不广泛。multi-Paxos的协议也没有得到广泛应用。虽然有些系统使用了Paxos——Chubby。但并没有开源。

目前现存容错系统的处理方式
- MapReduce的复制计算，但是依赖单master
- GFS副本数据，依赖master另外选主
- VMware FT复制服务依赖`test-and-set`选择master

虽然单点master可以避免“脑裂”，但是master始终是单点。

**“脑裂”如何产生？这种故障会导致什么问题？**

假设我们的复制服务是test-and-set模式：多个客户端发送set请求到服务，服务端收到写入请求后，返回当前值（假设是0）给客户端，此时当且只有一个客户端收到结果，且为0.（锁，其他客户端等待）



假设如下的场景：C1，C2，S1，S2。客户端C1可以与S1通信，但是不能和s2通信。那么此时只有S1上存在一份副本，当S1出现宕机时。整个系统就会出现问题。

请问C1是否应该当只有S1在线时，继续进行任务？

1. 如果S2真的宕机，C1必须在没有S2的情况下继续进行。 否则，服务不允许出现故障!
2. 如果S2工作正常，但是C1与S2之间因为网络故障无法连接。。此时C1不应该在没有S2的情况下进行。   因为S2可能还活着，并为C2客户提供服务

面对上面的情况，要么是认为正常故障，继续提供复制。要么因为“脑分裂”导致可能出现错误的操作。



面对上面的问题，计算机是无法区分“服务器故障”和“网络故障”：

两者产生的结果都是：发送给节点的请求无法被响应。

通常比较棘手的这种情况被称为网络分区：C1可以正常与S1通信，C2可以正常与S2通信。但C1+S1无法看到C2+S2的响应。这种情况处理起来比较棘手，在以往的场景下都是需要人介入来处理。或者是单点靠谱的master（FT的test-and-set 策略），或者是靠谱的网络（没有响应就是服务器崩溃）

面对上面的问题的处理方式还不够优美。是否还有更好的处理方式？

解决写复制分区的问题，可以利用：大多数投票的方式处理。

奇数台的服务器，譬如：3、5、7。只要超过一半的机器认可，就可以继续复制。例如3台时需要2台投票同意。

Q：为什么使用这种方式（大多数投票）可以是解决脑分裂遇到的问题？

A：只会出现其中一个分区拥有多数票，打破了不同客户端看到节点数目的对称性。少数票的节点应遵循拥有大多数票分区的复制。一般而言2f + 1可以容忍f个故障服务器，我们也把这样的系统称为仲裁系统。

多数票决策系统有一个特征，就是需要任意两个节点存在交集。譬如：Raft的leader选举，新的选举也不会丢失最新的复制日志。

分布式复制容错的研究已经持续了很多年，在1990前后，诞生了两种分布式复制容错技术Viewstamped Replication和Paxos。随后的15年，Raft诞生，并且得到在工业界的广泛应用。

**下面来看看Raft**

Raft数据结构

状态：

- 磁盘：`currentTerm`、`votedFor`、`log[]`
- 内存：`commitIndex`、`lastApplied`
- leader节点(内存)：`nextIndex()`、`matchIndex[]`

日志追加RPC过程：

- 参数：`term`、`leaderId`、`prevLogIndex`、`prevLogTerm`、`entries[]`、`leaderCommit`
- 回调结果：`term`、`success`
- 接收者
1. 当`term<currentTerm` 返回`false`
2. `preLogIndex`与`preLogTerm`不匹配时返回`false`
3. `entires[]`存在冲突的条目（`index`相同，`term`不同）。应删除此日志以及>index后的所有条目
4. 添加任何在已有的日志中不存在的新条目 
5. 如果`leaderCommit > commitIndex`，则`commitIndex`设置为`min(leaderCommit,entries[].lastIndex)`

投票RPC：

- 参数：term、candidateId、lastLogIndex、lastLogTerm
- 回调结果：term、voteGranted(true意味着收到投票)

接收者：

1. 当`term<currentTerm` 返回false
2. 如果votedFor为空或者与candidateId相同，并且候选人的日志和自己的日志一样新，则给该候选人投票

协定：

- 所有服务器All node
1. 如果`commitIndex > lastApplied`：lastApplied增加，状态机状态设置为`log[lastApplied]`
2. 如果 RPC 的请求或者响应中包含一个 term T 大于 currentTerm，则currentTerm赋值为 T，并切换状态为追随者（Follower）
- 追随者Follower
1. 响应候选者和leader的请求
2. 如果超时还没有收到leader的AppendEntries请求或者是candidated的投票，自己就晋升为candidated
- 候选者Condidates
1. 转换为候选者后开始选举：`currentTerm`自增、投票给自己、重置选举计时器、请求投票RPC给其他服务器
2. 如果大多数投票通过，则成为leader
3. 如果收到来自leader的`AppendEntries`请求，则成为follower
4. 如果超时，则开始新一轮选举
- 领导人Leader
1. 一旦成为leader，就需要向所有节点发送`AppendEntries `RPC心跳包；空闲时重复发送空包以防止选举超时
2. 收到客户端请求，先追加到本地日志，然后更新状态机新值
3. follower上次收到的日志缩影大于将要收到的日志索引`nextIndex`，则通过`AppendEntries`RPC把`nextIndex`之后的日志发送出去。
- 发送成功则将该追随者的 `nextIndex`和`matchIndex`更新
- 因为日志不一致导致的发送失败，`nextIndex`递减并且重新发送
4. 当存在一个N，使得`N > commitIndex`和`matchIndex[i]>=N`，并且`log[N].term == currentTerm`。则更新`commitIndex = N`

在开发时，Raft作为library运行在每个副本中。如图




当客户端发送一条put写命令时：

1. 客户端发送`put/get`命令到leader的K/V层
2. leader添加命令到日志
3. leader发送AppendEntries RPC调用到follower
4. follower存储日志到磁盘
5. leader等待大多数的follower响应（包括自己）
6. 当收到大多数响应时，标记此日志为commited（已提交）状态。这意味当集群机器出现故障时，数据不会丢失。出现leader选举时，下一个leader也能看到最新日志。
7. leader执行命令告知客户端成功
8. 与此同时，leader在下一次进行AppendEntries RPC告知follower这条日志可以执行存储
9. follwer 看到是commit则会实际的写入到KV层

**Q：为什么需要引入日志？**

A：单独的KV DB存储无法完整的保存状态机的状态。日志可以保证命令执行的顺序（只做append），也可以帮助确保leader和follower日志一致。

日志存储临时命令直到提交，日志可以防止leader的命令重发导致重复执行，日志因为进行了持久化，即使服务器重启也能回放。

**Q：是不是所有的副本日志都一样？**

并不是，有些副本的日志可能出现滞后。短暂时间内，日志记录不同。不过，最终所有的副本会收敛为一致，commit机制可以确保所有的命令都被正确执行。

实验2 Raft接口

`rf.Start(command) (index, term, isleader)``

- 实验3中 k/v 服务器的`Put()/Get()`操作通过RPC调用`Start()`
- `Start()`只对leader有意义
- 在新的日志记录启动Raft协议
    - 添加到leader日志
    - leader发送附属日志给AppendEntries RPC
    - `Start()`返回w/o调用RPC后的响应
    - 在`applyCh`部分，k/v 层的`Get()/Put()`操作需等待commit才能算完成
- 如果服务器在commit之前失去leader，则此次通信可能会失败，执行的命令可能会丢失，客户端需要重发
- isleader: 如果服务器不是leader则为false，客户端识别此字段为false时需要重试另外一台
- term: `currentTerm`, 给调用者区分leader是否被降级
- index：区分日志是否已经commit

ApplyMsg，包含日志的最新索引（index）和执行的命令：（command,index）
- 每个提交的日志在applyCh会发送一个ApplyMsg
- 接收消息后执行命令，更新本地副本状态
- leader通过RPC发送响应结果给等待的客户端

Raft设计有两个主要的部分：
- 选择新leader
- 即使出现故障，也能保证日志一致

**讨论：Leader选举**

Q：为什么只有一个leader？

A：保证所有的副本执行相同命令并且保证顺序一致（有些设计是没有leader的，譬如：Paxos）

Raft中Leader的序列编号意义（raft把时间分为多个周期，每个周期都有一个编号）

新leader-->新的周期

在新的周期内，最多只存在一个leader也可能没有。

编号可以帮助服务器找到那个是最新的leader，而不会再次请求旧的leader

Raft中成员何时开始新一轮的选举？

- 没有收到当前消息发送的选举超时消息时
- 增加自己的currentTerm,并发送RPC要其他节点投票
- 会有可能选举多次，导致选举时间变长。但保证了安全
- 旧的leader可能还活着，那么此时就认为它是领导者

如何确保在一个周期内只有一个leader？

- leader必须获得大多数节点的赞成投票（假设节点数为2n，则至少需要获得n+1）
- 每个节点每个周期内只能给投一票。
    - 候选人则是投票给本身
    - 非候选人则投票给第一个询问票的节点
- 同一个周期内，只有一个节点能获得大多数投票
    - 即使网络分区也只会出现一个leader
    - 当有服务器出现故障时也不影响选举

节点如何了解新选举的leader已经产生？

- 获得大多数的投票的leader，对于这些投票的机器认为对方是candidate
- 新leader的通过AppendEntries心跳包发送一个高的周期编号给其他节点。其他节点收到这个心跳包后停止选举，并认为对方是leader

选举失败的可能性原因可能有两个：
- 大多数的服务器无法响应
- 大家都是候选人状态，无法获得到多数票

如果选举不成功怎么办？
- 选举超时（没有心跳），开始新的选举（新的周期）
- 周期编号高的继续，低编号的候选人退出

Raft如何避免分裂投票？

每个节点的超时都是在150～300ms选择一个随机值。随机值打破节点之间的对称性，所有节点的随机值中会出现一个最小的随机值。有足够的时间在是一轮超时获得选举。其他节点看到新的leader发送的AppendEntries 心跳并且不会变为candidate。（随机延时在网络协议中也比较常见）

如何处理选举超时时间？

- 至少能容纳几个心跳包间隔（防止网络丢包），避免不必要的选举而浪费时间
-   随机部分足够长的时间，让一个候选人在下一个候选人开始前发起投票，然后再让下一个候选人开始。
-  足够短的时间对故障做出快速反应，避免长时间的服务中断。
- 足够短的时间，可以在短时间内，允许多重试几次选举。
-  一般要求在5秒内完成选举任务

如果旧的leader不知道新的leader当选怎么办？

- 旧的leader可能没有收到选举消息
- 旧的leader在少数量节点的分区网络中
- 新的leader意味着大多数节点的currentTerm已经递增
    - 旧的leader无法通过AppendEntries获得大多数投票
    - 旧leader无法执行和commit新的log
    - 基于以上，这种情况下就不会产生“脑裂”，但是有一小部分会收到旧的AppendEntries，不过在旧的周期结束时，日志会被丢弃。

## 参考文献

- [Students' Guide to Raft](https://thesquareplanet.com/blog/students-guide-to-raft/)
- [In Search of an Understandable Consensus Algorithm](https://pdos.csail.mit.edu/6.824/papers/raft-extended.pdf)
- [Raft 一致性算法论文译文](https://www.infoq.cn/article/raft-paper)
- [Raft Structure Advice](https://pdos.csail.mit.edu/6.824/labs/raft-structure.txt)
- [Raft Locking Advice](https://pdos.csail.mit.edu/6.824/labs/raft-locking.txt)
- [The Part-Time Parliament](https://lamport.azurewebsites.net/pubs/lamport-paxos.pdf)
- [Paxos Made Simple](https://lamport.azurewebsites.net/pubs/paxos-simple.pdf)
- [Generalized Consensus and Paxos](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/tr-2005-33.pdf)
- [Raft在线演示](http://thesecretlivesofdata.com/raft/)
- [Raft Consensus Algorithm](https://raft.github.io/)
- [Raft lecture (Raft user study)](https://www.youtube.com/watch?v=YbZ3zDzDnrw)

---

## File: _posts/2020-10-20-harbor-a-enterprise-private-registry.md

[Binary file]

---

## File: _posts/2021-06-25-some-logger-guideline.md

[Binary file]

---

## File: pwa/manifest.json

{
  "name": "Jeffery Blog",
  "short_name": "Jeffery Blog",
  "description": "engineer blog",
  "icons": [{
    "src": "icons/128.png",
    "sizes": "128x128",
    "type": "image/png"
  }, {
    "src": "icons/512.png",
    "sizes": "512x512",
    "type": "image/png"
  }],
  "background_color": "#fff",
  "theme_color": "#000",
  "start_url": "/",
  "display": "standalone",
  "orientation": "portrait"
}

--- METRICS ---
- Files Extracted: 29
- Estimated Token Budget: ~27287 tokens
- Recency Window: Active (< 180 days)
- Canonical Reference: https://codewiki.google/github.com/ty4z2008/Qix
