#730 plocate

2021-12-15

Fedora 36 将采用 plocate 替代 mlocate 成为默认的查找索引,据说更快。
而且 Debian 也早采纳了 plocate。

安装

apt show plocate
# Package: plocate
# Version: 1.1.8-2
# Priority: optional
# Section: universe/utils
# Origin: Ubuntu
# Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
# Original-Maintainer: Steinar H. Gunderson <sesse@debian.org>
# Bugs: https://bugs.launchpad.net/ubuntu/+filebug
# Installed-Size: 510 kB
# Depends: libc6 (>= 2.33), libgcc-s1 (>= 3.3.1), libstdc++6 (>= 6), liburing1 (>= 0.7), libzstd1 (>= 1.4.0)
# Suggests: systemd-sysv | powermgmt-base, systemd-sysv | nocache
# Breaks: mlocate
# Replaces: mlocate
# Homepage: https://plocate.sesse.net/
# Task: xubuntu-desktop, ubuntu-budgie-desktop, ubuntu-budgie-desktop-raspi
# Download-Size: 119 kB
# APT-Manual-Installed: yes
# APT-Sources: https://mirrors.cloud.tencent.com/ubuntu impish/universe amd64 Packages
# Description: much faster locate
#  plocate is a locate(1) based on posting lists, giving much faster searches
#  on a much smaller index. It is a drop-in replacement for mlocate in nearly
#  all aspects, and is fast on SSDs and non-SSDs alike.

sudo apt install -y plocate

使用

根据 apt show 信息,plocate 会 break mlocate。
updatedb 命令会改由 plocate 提供。

ll /usr/bin/locate /usr/bin/updatedb /etc/alternatives/locate /etc/alternatives/updatedb
lrwxrwxrwx 1 root root 16 2021-12-15 11:51:56 /etc/alternatives/locate -> /usr/bin/plocate
lrwxrwxrwx 1 root root 26 2021-12-15 11:51:56 /etc/alternatives/updatedb -> /usr/sbin/updatedb.plocate
lrwxrwxrwx 1 root root 24 2021-12-15 11:51:56 /usr/bin/locate -> /etc/alternatives/locate
lrwxrwxrwx 1 root root 26 2021-12-15 11:51:56 /usr/bin/updatedb -> /etc/alternatives/updatedb

根据官网提供的演示数据,其 DB 只有 mlocate 的 40%,查询时间只有 mlocate 的万分之四。

其具体操作和 mlocate 类似:

sudo updatedb

locate go.mod

有没有 2500 倍的提升我不知道,但是感觉好像确实快。挺好!

参考资料与拓展阅读

#729 基于 CentOS Stream 的桌面环境

2021-12-15

CentOS Stream 9 已于月初发布,基于 Fedora 34,这就是 RHEL 9 未来的发展方向。
CentOS Stream 的稳定性应该是比 Fedora 更好,如果我想选择改用 Fedora 当桌面环境,为什么不试试 CentOS Stream 呢?
这里就开始调研一下。

#728 Node 包管理器

2021-12-14

Node 包都存储在 Registry 中,官方 Registry 是 npmjs.org

切换源

npm config set registry https://registry.npm.taobao.org

或者直接修改 ~/.npmrc, 加入:

registry = https://registry.npm.taobao.org

下载的时候也可以指定:

npm info lodash --registry https://registry.npm.taobao.org

包管理器

  • npm download
  • yarn download
    Facebook 出的, 对 npm 做了一些优化,npm 后面也做了类似的优化
  • cnpm download
    淘宝出的,主要是默认使用淘宝自己维护的国内镜像
  • pnpm stars downloads
  • entropic stars downloads
    据说是 NPM 前 CTO 成立的新项目,目标是去中心化

包依赖的语法

  • ~ 匹配最近的小版本, eg: ~1.2.3 会匹配 1.2.x
  • ^ 匹配最新的大版本,eg: ^1.2.3 会匹配所有 1.x.x
  • * 永远最新版本

参考资料与拓展阅读

#727 DNS_PROBE_FINISHED_NXDOMAIN

2021-12-14
  1. 浏览器突然打不开 zhihu.com, 报 DNS_PROBE_FINISHED_NXDOMAIN
  2. Windows 网络诊断之后说是 DNS 不可用。
  3. 经过检查,使用 DHCP 获取到的 DNS 172.16.0.1
  4. 改成 AliDNS: 223.5.5.5, 223.6.6.6 之后就好了。
  5. 然后再改回默认的 DNS 发现也能正常访问了。

我应该在出现问题的时候先尝试 nslookup 一下,看看 DNS 解析出来的到底是个什么结果。
下次遇到再继续更新。

C:\Users\Administrator>ipconfig /all | findstr DNS
   主 DNS 后缀 . . . . . . . . . . . :
   连接特定的 DNS 后缀 . . . . . . . :
   DNS 服务器  . . . . . . . . . . . : 172.16.0.1
   连接特定的 DNS 后缀 . . . . . . . :
   DNS 服务器  . . . . . . . . . . . : fec0:0:0:ffff::1%1
   连接特定的 DNS 后缀 . . . . . . . :
   连接特定的 DNS 后缀 . . . . . . . :
   连接特定的 DNS 后缀 . . . . . . . :
   连接特定的 DNS 后缀 . . . . . . . :

C:\Users\Administrator>nslookup zhihu.com
服务器:  UnKnown
Address:  172.16.0.1

非权威应答:
名称:    zhihu.com
Address:  103.41.167.234

#726 Tornado HTTP 服务的 “ACK”

2021-12-13
  1. 一个请求会先到 A 服务,然后通过 HTTP 调用的方式传到 B 服务 (基于 Tornado)
  2. 面对突然涌来的大量请求,B 服务负载迅速升高,响应时间拉长
  3. A 服务的部分请求因为超时断开,然后重发

也就是说,在负载较高的时候,B 会收到一些重复请求。因为负载高的时候,B 其实已经接收过一遍,只是没有响应。
B 服务处理任务结束之后,会将请求加入幂等。但是在 B 服务正在处理的时候,还是会有重复处理的可能性。
如果是一些对数据可靠性有一定要求的场景,这样重复的处理就不能接受。

比较合适的设计应该是 A 和 B 之间通过 MQ 通讯,根本不可能有这样的情况发生,哪怕请求再多也可以按照自己的速度消费,使系统保持最佳状态运行。而且,服务之间解耦之后,可以避免相互影响。

但是,如果不能改变 A B 两个服务的现有设计(调用关系),可以做的事情:

  1. 通过请求的正常返回来当 ACK 机制 (本文原本想重点说的内容)
  2. 将请求的接收和处理拆开
  3. 请求收到,加入队列,然后就可以返回了,这个时间非常短,这个过程出现问题的可能性非常低
  4. 如果在处理请求之前,连接断开,那就结束流程,抛弃任务
  5. A 服务的重试机制延迟一个合适的时间(比如 3 分钟)处理,可以通过 MQ, DB, Redis 实现

两个方案都应该能大幅减小出现重复请求的情况,双管齐下效果会更好。
方案一, 如果引入 MQ 或 DB 的话,就会觉得为什么不在 A 服务做;如果不引入新组件的化,复杂是需要保证服务突然挂掉之后,队列数据的恢复。只能在启动服务时通过扫描日志来恢复数据。
方案二,无论是业务还是代码,影响范围比较小,易于实现。

Tornado 实现 ACK 机制

客户端在正常流程处理完成之前,断开连接,会触发 on_connection_close 调用,可以在这个里面做手脚。

class MainHandler(tornado.web.RequestHandler):
    def post(self):
        if self._finished:
            return
        # do something

    def on_connection_close(self):
        LOG.debug('connection closed')
        self._finished = True
        super().on_connection_close()

#723 Linux 文件创建时间

2021-12-12
stat /tmp/edge_shutdown_crash.txt
  文件:/tmp/edge_shutdown_crash.txt
  大小:2          块:8          IO 块:4096   普通文件
设备:801h/2049d   Inode:9306564     硬链接:1
权限:(0664/-rw-rw-r--)  Uid:( 1000/ markjour)   Gid:( 1000/ markjour)
最近访问:2021-12-12 10:24:17.318846619 +0800
最近更改:2021-12-12 10:42:27.982887293 +0800
最近改动:2021-12-12 10:42:27.982887293 +0800
创建时间:2021-12-12 10:24:17.318846619 +0800

# %w     time of file birth, human-readable; - if unknown
# %W     time of file birth, seconds since Epoch; 0 if unknown
# %x     time of last access, human-readable
# %X     time of last access, seconds since Epoch
# %y     time of last data modification, human-readable
# %Y     time of last data modification, seconds since Epoch
# %z     time of last status change, human-readable
# %Z     time of last status change, seconds since Epoch
stat -c "%w" /tmp/edge_shutdown_crash.txt
2021-12-12 10:24:17.318846619 +0800

sudo debugfs -R 'stat /tmp/edge_shutdown_crash.txt' /dev/sda1 | cat
debugfs 1.46.3 (27-Jul-2021)
Inode: 9306564   Type: regular    Mode:  0664   Flags: 0x80000
Generation: 3654863154    Version: 0x00000000:00000001
User:  1000   Group:  1000   Project:     0   Size: 2
File ACL: 0
Links: 1   Blockcount: 8
Fragment:  Address: 0    Number: 0    Size: 0
 ctime: 0x61b56193:ea56adf4 -- Sun Dec 12 10:42:27 2021
 atime: 0x61b55d51:4c04da6c -- Sun Dec 12 10:24:17 2021
 mtime: 0x61b56193:ea56adf4 -- Sun Dec 12 10:42:27 2021
crtime: 0x61b55d51:4c04da6c -- Sun Dec 12 10:24:17 2021
Size of extra inode fields: 32
Inode checksum: 0xd223c644
EXTENTS:
(0):39635628

#722 etcd 的应用

2021-12-09

简介

etcd 是 CoreOS 的一个子项目,KV 数据库,原是设计用于存储集群共享配置数据,基于 Apache 协议开源。
PS: CoreOS 2018 年被 RedHat 收购,然后更名为 Container Linux, 后来和 RedHat 的 Project Atomic 合并,形成 Fedora CoreOS 项目。

本质是一个 KV 存储,从这个角度看和 Redis 很像。但:

Item etcd Redis
性能 非常高
存储方式 磁盘 + 内存缓存 内存
持久化 boltdb AOF + rdb
数据类型 string 丰富
API gRPC RESP
一致性 Raft 复制

PS:性能:更高 QPS + 更低延迟
PS:RESP:REdis Serialization Protocol(TCP 文本协议)

作用

本质就是一个轻量级的 KV 存储系统,由于其高并发、支持分布式(强一致性)、支持版本控制、支持实时更新通知等特点,所以常用于:

  • 服务发现:帮助分布式系统中的服务相互找到并建立连接。
  • 配置管理:集中管理和动态更新分布式系统的配置信息。
  • 分布式锁:确保在分布式环境中,同一时间只有一个客户端能获取锁,避免资源冲突。

概念

  1. Raft 算法

    Raft 是一种用于管理复制日志的一致性算法,节点之间通过复制日志来达成一致状态。
    当客户端发起写请求时,领导者(Leader)节点接收请求并将其记录到日志中,接着把日志条目复制到其他跟随者(Follower)节点。
    只有当多数节点都成功复制了该日志条目后,该条目才会被提交,数据更新才会被应用。

  2. 角色相关概念

    • Node(节点)/Member(成员):指 etcd 集群中的单个服务器实例,每个节点都有一个唯一标识,存储着部分或全部的数据副本,并且参与集群的一致性协议。
    • Leader(领导者):负责处理客户端写请求和协调日志复制的节点。同一时刻集群中只有一个领导者,它会接收客户端的写操作,并将这些操作广播给其他跟随者节点。
    • Follower(跟随者):跟随领导者的指令,接收并复制领导者发送的日志条目。跟随者不直接处理客户端的写请求,只响应领导者的请求。
    • Candidate(候选人):在 Raft 算法的选举过程中,当跟随者在一定时间内没有收到领导者的心跳信息时,它会转变为候选人状态。
      候选人会发起新一轮的选举,向其他节点请求投票。如果获得多数节点的投票,候选人就会成为新的领导者。
    • Peer(对等节点):指集群中地位平等的其他节点,节点之间相互通信以实现数据复制、选举等功能。
  3. Term(任期)

    逻辑时间概念,用于划分不同的选举周期,确保选举的正确性和防止过期的领导者重新掌权,同时在日志复制和消息传递中也起到重要的同步和协调作用。
    每个任期从一次选举开始,有一个唯一的递增编号。
    在一个任期内,最多只能有一个领导者。
    如果选举失败或领导者故障,会开启一个新的任期。

结构

image
image

和 Redis 对比

  1. 两者都是 key-value 类型存储
  2. Redis 是 C 写的,而 etcd 是 Go 写的
  3. Redis 支持多种数据类型,而 etcd 不支持
  4. Redis 支持 Lua 编程,而 etcd 不支持
  5. Redis 支持简单的权限控制,而 etcd 不支持
  6. Redis 采用 RESP 私有协议,而 etcd 采用 GRPC 或 HTTP (JSON)
  7. Redis 支持复制集的方式同步数据,而 etcd 则是通过 Raft 实现强一致性
  8. Redis 通过 RDB (快照) / AOF (增量) 的方式持久化

部署 (Ubuntu)

sudo apt install -y etcd

etcd --version

sudo systemctl start etcd
sudo systemctl status etcd

部署 (Docker)

docker-compose.yml:

version: "3"

networks:
    etcd_net:
        driver: bridge
        ipam:
            driver: default
            config:
                - subnet: ${NETWORK_CONFIG_SUBNET}

services:
    etcd-0:
        networks:
            etcd_net:
                ipv4_address: ${ETCD_01_NETWORKS_ETCD_NET_ADDRESS}
        image: quay.io/coreos/etcd:latest
        ports:
            - ${ETCD_01_NETWORKS_ETCD_NET_ADDRESS}:4001:4001
            - ${ETCD_01_NETWORKS_ETCD_NET_ADDRESS}:2380:2380
            - ${ETCD_01_NETWORKS_ETCD_NET_ADDRESS}:2379:2379
        hostname: etcd-0
        environment:
            - GOMAXPROCS=2
        command: >-
            /usr/local/bin/etcd
            -name etcd-0
            -advertise-client-urls http://etcd-0:2379,http://etcd-0:4001
            -listen-client-urls http://${ETCD_01_NETWORKS_ETCD_NET_ADDRESS}:2379,http://${ETCD_01_NETWORKS_ETCD_NET_ADDRESS}:4001
            -initial-advertise-peer-urls http://etcd-0:2380
            -listen-peer-urls http://${ETCD_01_NETWORKS_ETCD_NET_ADDRESS}:2380
            -initial-cluster-token etcd-cluster
            -initial-cluster etcd-0=http://etcd-0:2380,etcd-1=http://etcd-1:2380,etcd-2=http://etcd-2:2380
            -initial-cluster-state new
    etcd-1:
        networks:
            etcd_net:
                ipv4_address: ${ETCD_02_NETWORKS_ETCD_NET_ADDRESS}
        image: quay.io/coreos/etcd:latest
        ports:
            - ${ETCD_02_NETWORKS_ETCD_NET_ADDRESS}:4001:4001
            - ${ETCD_02_NETWORKS_ETCD_NET_ADDRESS}:2380:2380
            - ${ETCD_02_NETWORKS_ETCD_NET_ADDRESS}:2379:2379
        hostname: etcd-1
        environment:
            - GOMAXPROCS=2
        command: >-
            /usr/local/bin/etcd
            -name etcd-1
            -advertise-client-urls http://etcd-1:2379,http://etcd-1:4001
            -listen-client-urls http://${ETCD_02_NETWORKS_ETCD_NET_ADDRESS}:2379,http://${ETCD_02_NETWORKS_ETCD_NET_ADDRESS}:4001
            -initial-advertise-peer-urls http://etcd-1:2380
            -listen-peer-urls http://${ETCD_02_NETWORKS_ETCD_NET_ADDRESS}:2380
            -initial-cluster-token etcd-cluster
            -initial-cluster etcd-0=http://etcd-0:2380,etcd-1=http://etcd-1:2380,etcd-2=http://etcd-2:2380
            -initial-cluster-state new
    etcd-2:
        networks:
            etcd_net:
                ipv4_address: ${ETCD_03_NETWORKS_ETCD_NET_ADDRESS}
        image: quay.io/coreos/etcd:latest
        ports:
            - ${ETCD_03_NETWORKS_ETCD_NET_ADDRESS}:4001:4001
            - ${ETCD_03_NETWORKS_ETCD_NET_ADDRESS}:2380:2380
            - ${ETCD_03_NETWORKS_ETCD_NET_ADDRESS}:2379:2379
        hostname: etcd-2
        environment:
            - GOMAXPROCS=2
        command: >-
            /usr/local/bin/etcd
            -name etcd-2
            -advertise-client-urls http://etcd-2:2379,http://etcd-2:4001
            -listen-client-urls http://${ETCD_03_NETWORKS_ETCD_NET_ADDRESS}:2379,http://${ETCD_03_NETWORKS_ETCD_NET_ADDRESS}:4001
            -initial-advertise-peer-urls http://etcd-2:2380
            -listen-peer-urls http://${ETCD_03_NETWORKS_ETCD_NET_ADDRESS}:2380
            -initial-cluster-token etcd-cluster
            -initial-cluster etcd-0=http://etcd-0:2380,etcd-1=http://etcd-1:2380,etcd-2=http://etcd-2:2380
            -initial-cluster-state new

etcdctl

etcdctl - A simple command line client for etcd.

ENDPOINTS=ip:port,ip:port,ip:port

# set
etcdctl --endpoints=$ENDPOINTS put test1 111
etcdctl --endpoints=$ENDPOINTS put test2 222
etcdctl --endpoints=$ENDPOINTS put test3 333

# get
etcdctl --endpoints=$ENDPOINTS get test1
etcdctl --endpoints=$ENDPOINTS get test1 --write-out="json"
etcdctl --endpoints=$ENDPOINTS get test --prefix

# delete
etcdctl --endpoints=$ENDPOINTS del test1
etcdctl --endpoints=$ENDPOINTS del test --prefix

# 集群信息
etcdctl --write-out=table --endpoints=$ENDPOINTS endpoint status
etcdctl --write-out=table --endpoints=$ENDPOINTS member list

示例 (Golang)

go get go.etcd.io/etcd/client/v3
package main

import (
    "context"
    "fmt"
    "time"

    etcd "go.etcd.io/etcd/client/v3"
)

func main() {
    cli, err := etcd.New(etcd.Config{
        Endpoints:   []string{"192.168.31.204:2379"},
        DialTimeout: 3 * time.Second,
    })
    if err != nil {
        fmt.Printf("etcd ConnectError: %v\n", err)
        return
    }
    defer cli.Close()

    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    resp, err := cli.Get(ctx, "test1")
    cancel()
    if err != nil {
        fmt.Printf("etcd GetError: %v\n", err)
    }

    for _, kv := range resp.Kvs {
        fmt.Printf("etcd KeyVlue: %s, %s\n", kv.Key, kv.Value)
    }
}

示例 (Python)

python3 -m pip install -u etcd3
import etcd3
etcd = etcd3.client(host='etcd-host-01', port=2379)

testkey = '/key'
testval = '1234'

etcd.put(testkey, testval)

v = etcd.get(testkey)
print(v)
assert v == testval

etcd.delete(testkey)

参考资料与拓展阅读

#721 程序员的 10 个习惯

2021-12-08

公众号四猿外的文章《这 10 个程序员的好习惯,让我变强了》,很有同感,这里总结一下:

  1. 要看官方文档,网上的资料鱼龙混杂
  2. 开发者的可靠性:自测没有问题了再交付
  3. 日志设计合理,保证调试时,有恰当的信息来定位问题
  4. 精通 Git
  5. 优先功能实现,别急着优化
    过早优化是魔鬼,应该根据实际运行的情况来优化
  6. 先做明确的需求,不确定或者模糊的需求先往后放
  7. 积极主动:发现问题,提出方案,解决问题
  8. 开发时间评估,保证有冗余时间,以处理可能的意外
  9. 学习编程主要靠上手写代码,不要光看文档
  10. 英语。不需要很好,能看懂英文文档就行