Pipenv
2022-11-13
~/.local/lib/python3.10/site-packages/pkg_resources/__init__.py:123: PkgResourcesDeprecationWarning: 1.1build1 is an invalid version and will not be supported in a future release
warnings.warn(
~/.local/lib/python3.10/site-packages/pkg_resources/__init__.py:123: PkgResourcesDeprecationWarning: 0.1.43ubuntu1 is an invalid version and will not be supported in a future release
warnings.warn(
~/.local/lib/python3.10/site-packages/pkg_resources/__init__.py:123: PkgResourcesDeprecationWarning: 2.9.0.Odd.Olm is an invalid version and will not be supported in a future release
warnings.warn(
Pipenv 最近执行命令的时候会有这些提示,看这个意思是检测了一些 Python 包的版本不被支持。
用 traceback 排查之后,确认是在 Python PATH 下检查所有包的版本。
出问题的三个包是:
- distro-info
<LegacyVersion('1.1build1')> /usr/lib/python3/dist-packages
- python-debian
<LegacyVersion('0.1.43ubuntu1')> /usr/lib/python3/dist-packages
- Shredder
<LegacyVersion('2.9.0.Odd.Olm')> /usr/lib/python3/dist-packages
查找路径:
~/.local/lib/python3.10/site-packages/pipenv/patched
~/.local/lib/python3.10/site-packages/pipenv/vendor
~/Projects/Mine/staticize
/usr/lib/python310.zip
/usr/lib/python3.10
/usr/lib/python3.10/lib-dynload
~/.local/lib/python3.10/site-packages
~/.local/lib/python3.10/site-packages/mackup-0.8.33-py3.10.egg
/usr/local/lib/python3.10/dist-packages
/usr/lib/python3/dist-packages
相关代码:
File "~/.local/lib/python3.10/site-packages/pkg_resources/__init__.py", line 997, in __init__
self.scan(search_path)
File "~/.local/lib/python3.10/site-packages/pkg_resources/__init__.py", line 1030, in scan
self.add(dist)
File "~/.local/lib/python3.10/site-packages/pkg_resources/__init__.py", line 1050, in add
dists.sort(key=operator.attrgetter('hashcmp'), reverse=True)
File "~/.local/lib/python3.10/site-packages/pkg_resources/__init__.py", line 2623, in hashcmp
self.parsed_version,
File "~/.local/lib/python3.10/site-packages/pkg_resources/__init__.py", line 2671, in parsed_version
self._parsed_version = parse_version(self.version)
File "~/.local/lib/python3.10/site-packages/pkg_resources/__init__.py", line 122, in parse_version
print(''.join(traceback.format_stack()))
HTTPS TLS 信息安全
2022-11-11
HTTPS = HTTP + TLS
原来是 TCP -> HTTP
现在是 TCP -> TLS -> HTTP
提升了安全性的同时,降低了一些性能。
更重要的是,互联网基础设施层面开始淘汰 HTTP:
- 包括谷歌在内的浏览器厂商将使用 HTTP 访问的网站标记为不安全网站,Google 还会降低 HTTP 网站的排名。
- 其他平台,比如苹果 App Store,微信小程序等,都要求使用 HTTPS 协议。
1、怎么提升安全性
- 加密数据传输: TLS 数据加密,难以被窃听和解密。即使攻击者能够截获传输的数据包,他们也不能轻易读取其中的内容。
- 身份验证: 受信任的第三方机构颁发的 TLS 证书,可以用来验证服务器身份。
- 完整性验证: 使用消息摘要算法(如 SHA-256)来验证数据的完整性,防止数据被篡改(中间人攻击)。
2、降低了多少性能
相关通信过程在 2021/01/08,了解 HTTPS 背后的原理 有描述。
损耗主要在哪些环节呢?
- 握手
- 加密、解密
- 数据传输(加密之后数据增大)
- 证书验证 - 优化:浏览器缓存证书
3、如何优化
参考 HTTPS 的资源消耗,针对消耗点进行优化。
4、工具
5、清单
在 2012 年的 RFC 6797 中,HTTP 严格传输安全被定义为网络安全标准。 创建这个标准的主要目的,是为了。
个人
2022-11-11
先说一句,文章链接中的单词不小心写错了,customer,不要在意这些细节。
我司曾经强调过在内外交流中区分客户和用户,不要混用(主要是统一表达方式)。今天突然想到这个问题,特意来仔细琢磨一下。
我司的定义
我记得大概是说:
- 客户:购买我们产品的人
- 用户:客户使用我们产品去服务的对象,也就是客户的客户
我们提供的是消息触达服务(邮件和短信),落到产品中,基础功能可以免费使用(定量),增值功能需要收费。这是背景。
在这个过程中,客户是我们产品的使用者,客户使用我们的产品给他们的客户发送消息。
客户的客户,也就是消息的接收方,就称之为用户。
PS:客户又分成付费客户和免费客户。
这个和原义不大相符,作为内部规范,怎么定义都行,上面怎么说就怎么来。
我认为
-
客户和用户
- 客户,Customer,这描述的是商务关系,认为是购买产品的人,没毛病。
- 用户,User,应该是使用我们产品的人,也就是我们的服务对象。
我在网上找了一遍,大概都是这个意思。和我对这个两个词语义上的理解是吻合的。
-
客户内部的角色
- 决策人(KP)
- 购买人/付款人
- 使用人
他们应该整体作为一个客户,不能区分开来,认为是决策人,或者购买人是客户,使用人是用户。
比如爸爸为孩子挑选了一款平板电脑,妈妈付钱,孩子使用。在平板电脑公司来看,应该认为他们是一个整体,然后找到 KP 做营销,告知这款平板多么利于学习,价格多么合适,还有最后一天的节日优惠。然后为了企业的品牌形象与二次营销,应该提升产品的质量和使用体验(持续更新的学习资料 + 游戏性能)。
-
客户使用我们的产品去通知也好,去营销也好,这些消息的接收方,应该就只是叫做收信方。
通过上面的定义,我们应该很容易理解下面几个观点:
- 仅根据是不是有商务关系(付钱)判断是不是客户。
- 用户分成免费用户、付费用户。
- 免费用户一定不是客户。
免费用户:仅在免费额度范围内使用基础服务、部分产品提供的免费试用。
- 付费用户不一定是客户,因为可能是客户购买之后提供给其他人使用的。
- 我以前做过的一款产品就是客户购买之后,客户的客户使用。
因为日常生活中,很多时候,客户(购买者)往往就是用户(使用者),我们才容易搞混。
在一些业务场景下,我们假定购买者就是使用者,那么付费用户也可以说成是付费客户。
免费用户不能说免费客户(因为没有商务关系)。
产品和服务
在写这篇文章的时候,我又想起另一个概念来了,我们是提供产品,还是提供服务?
如果没记错,在内部会议上讨论过,老板阐述了自己的观点。
当时的讨论内容我忘了,我这里就说说我现在的想法。
比如,我买周黑鸭送给了客户,客户转手把周黑鸭送给朋友吃了。
周黑鸭的客户是我,没有问题。周黑鸭的用户是谁?
如果按照提供产品的观点,最后产品被老板的朋友吃了,那么老板的朋友是用户。
周黑鸭应该为我提供体面又具有性价比的产品,同时为最后吃到鸭脖子的人提供美味(产品体验)。
如果按照提供服务的理念,周黑鸭公司提供的是礼品服务,那么是我在使用这个服务,所以我既是客户,又是用户。
周黑鸭应该为我提供体面又具有性价比的礼品服务,同时为我提供最好的送礼效果(产品体验),就是让我的客户收到礼物之后觉得满意,然后我和客户的友好度 +1。
这个例子可能不太恰当,但是我想表达的意思都说清楚了。
再补充一个,账户与账号
账户 |
Account |
用户 |
User |
账户名 |
Account Name |
用户名 |
User Name |
账户 ID(账号) |
Account ID |
用户 ID |
User ID |
账户是用户在系统内的身份,理论上一个用户可能有多个账户。
- 大多数情况,一个用户一个账户。
- 更重要的是,用户是服务对象,落实到管理系统中,用户就是账户。
- 就算一个用户使用多个身份信息(手机号)注册了多个账户,也没人会特意去调查、区分它们。
所以用户和账户也经常混用。
如果一个用户有多个账户的时候还是会发生一些沟通上的不便,严谨一点,内部对齐客户、用户、账户这几个概念,在不同场景下根据上下文区分使用,还是有一些必要性的。
就系统开发来说:
Linux
2022-11-10
我们编写程序 write 数据到文件中时,其实数据不会立马写入磁盘,而是会经过层层缓存。每层缓存都有自己的刷新时机,每层缓存都刷新后才会写入磁盘。这些缓存的存在是为了加速读写操作,因为如果每次读写都对应真实磁盘操作,那么读写的效率会大大降低。带来的坏处是如果期间发生掉电或者别的故障,还未写入磁盘的数据就丢失了。对于数据安全敏感的应用,比如数据库,比如交易程序,这是无法忍受的。所以操作系统提供了保证文件落盘的机制。我们来看下这些机制的原理和使用。
编程语言 Clang
2022-10-31
英文原文:https://levelup.gitconnected.com/why-modern-alternative-languages-never-replace-c-c-cbf0afc5f1dc
中文翻译:https://mp.weixin.qq.com/s/tr69w96gOO7ia81ttilgVA
总结一下:
- C/C 是优秀的系统级编程语言,无数基础设施是用 C/C 开发的
- C/C++ 可以完全控制机器,开发者拥有最大的灵活性
- C/C++ 性能非常好
- C/C++ 学术友好,简单,高效,直接(没有封装得很抽象)
- 所有操作系统提供 C API,其他语言也都提供了和 C 交互的方式
开发者
2022-10-31
发现一个问题,暂时没有任何思路:
一个线上执行了 66 天的服务,突然在前天凌晨 1:30 没有日志输出了,从外网连接不上,内网可以连上。
等后续有了进展再更新。
Golang DNS
2022-10-30
A 记录
package main
import (
"fmt"
"net"
)
func main() {
// 方法 1
// func ResolveIPAddr(network, address string) (*IPAddr, error)
ipAddr, err := net.ResolveIPAddr("ip", "www.google.com")
if err != nil {
fmt.Println("解析IP地址失败:", err)
} else {
fmt.Println("IP地址是:", ipAddr.IP)
}
// 方法 2
// func LookupMX(name string) ([]*MX, error)
// func LookupTXT(name string) ([]string, error)
// func LookupIP(host string) ([]IP, error)
// func LookupHost(host string) ([]string, error) // only IPv4
ips, err := net.LookupHost("www.google.com")
if err != nil {
fmt.Println("解析主机名失败:", err)
} else {
for _, ip := range ips {
fmt.Println("IP地址是:", ip)
}
}
}
MX 记录
package main
import (
"fmt"
"net"
)
func main() {
records, err := net.LookupMX("qq.com")
if err != nil {
fmt.Println("解析MX记录失败:", err)
} else {
fmt.Printf("%#v", records)
}
}
指定 DNS 服务器
func queryMX(dns string, domain string) ([]*net.MX, error) {
resolver := &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
dialer := &net.Dialer{}
return dialer.DialContext(ctx, "udp", dns)
},
}
return resolver.LookupMX(context.Background(), domain)
}
完整版本
package main
import (
"context"
"fmt"
"net"
"strings"
"time"
)
var dnsServers = []string{
"223.5.5.5:53",
"114.114.114.114:53",
"8.8.8.8:53",
}
type IPType int
const (
NotIP IPType = iota
IPv4
IPv6
)
const (
DNSLookupTimeout = 500 * time.Millisecond // time.Duration
)
func GetIPType(host string) IPType {
ip := net.ParseIP(host)
if ip == nil {
return NotIP
}
if ip.To4() != nil {
return IPv4
}
if ip.To16() != nil {
return IPv6
}
return NotIP
}
type Resolver struct {
DNS string
Resolver *net.Resolver
}
func NewResolver(dns string) Resolver {
resolver := Resolver{}
resolver.DNS = dns
resolver.Resolver = &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
dialer := &net.Dialer{}
return dialer.DialContext(ctx, "udp", dns)
},
}
return resolver
}
func (r Resolver) QueryA(domain string) ([]string, error) {
return r.Resolver.LookupHost(context.Background(), domain)
}
func (r Resolver) QueryMX(domain string) ([]*net.MX, error) {
ctx, cancel := context.WithTimeout(context.Background(), DNSLookupTimeout)
defer cancel()
return r.Resolver.LookupMX(ctx, domain)
}
func (r Resolver) Query(domain string) ([]*net.MX, error) {
addrs, err := r.QueryMX(domain)
if err != nil {
return nil, err
}
var result []*net.MX
for _, addr := range addrs {
// hostname := strings.TrimRight(addr.Host, ".")
ipType := GetIPType(addr.Host)
fmt.Printf("%s: %#v\n", addr.Host, ipType)
if ipType == NotIP {
ips, err := r.QueryA(addr.Host)
if err != nil {
continue
}
for _, ip := range ips {
result = append(result, &net.MX{Host: ip, Pref: addr.Pref})
}
}
}
return result, nil
}
var resolvers map[string]Resolver
func Query(domain string) ([]*net.MX, error) {
var addrs []*net.MX
var err error
for _, dns := range dnsServers {
var r Resolver
if r, ok := resolvers[dns]; !ok {
r = NewResolver(dns)
resolvers[dns] = r
}
addrs, err = r.Query(domain)
if err == nil {
break
}
fmt.Printf("Error: %s: %s: %#v\n", dns, domain, err)
}
return addrs, err
}
func QueryAsync(domain string, ch chan<- []*net.MX, errCh chan<- error) {
addrs, err := Query(domain)
if err != nil {
errCh <- err
return
}
ch <- addrs
}
func init() {
resolvers = make(map[string]Resolver)
}
func main() {
{
domains := []string{"qq.com", "gmail.com", "google.com"}
for _, domain := range domains {
fmt.Println(strings.Repeat("=", 100))
addrs, err := Query(domain)
if err != nil {
fmt.Printf("Error: %#v\n", err)
} else {
for _, addr := range addrs {
fmt.Printf("%#v\n", addr)
}
}
}
}
{
fmt.Println(strings.Repeat("=", 100))
ch := make(chan []*net.MX)
errCh := make(chan error)
go QueryAsync("google.com", ch, errCh)
select {
case addrs := <-ch:
fmt.Println("MX Records:")
for _, addr := range addrs {
fmt.Printf("%#v\n", addr)
}
case err := <-errCh:
fmt.Println("Error:", err)
}
}
}
个人
2022-10-27
CSDN 会拦截复制,提示需要登录。
近日在网上学到一招,在控制台输入一行代码就好了:
document.designMode = "on";
计算机网络
2022-10-27
git clone git@github.com:tornado/tornado
Cloning into 'tornado'...
ssh: connect to host github.com port 22: Connection refused
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
ping github.com
正在 Ping github.com [::1] 具有 32 字节的数据:
来自 ::1 的回复: 时间<1ms
来自 ::1 的回复: 时间<1ms
来自 ::1 的回复: 时间<1ms
::1 的 Ping 统计信息:
数据包: 已发送 = 3,已接收 = 3,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
最短 = 0ms,最长 = 0ms,平均 = 0ms
Control-C
ping -4 github.com
正在 Ping github.com [127.0.0.1] 具有 32 字节的数据:
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=64
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=64
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=64
127.0.0.1 的 Ping 统计信息:
数据包: 已发送 = 3,已接收 = 3,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
最短 = 0ms,最长 = 0ms,平均 = 0ms
Control-C
nslookup github.com
Server: 192.168.31.1
Address: 192.168.31.1#53
Non-authoritative answer:
Name: github.com
Address: 127.0.0.1
Name: github.com
Address: ::1
配置 DNS 为 AliDNS 223.5.5.5
,233.6.6.6
:
nslookup github.com
Server: 223.5.5.5
Address: 223.5.5.5#53
Non-authoritative answer:
Name: github.com
Address: 20.205.243.166
问题依旧。
ping 20.205.243.166
正在 Ping 20.205.243.166 具有 32 字节的数据:
请求超时。
请求超时。
请求超时。
20.205.243.166 的 Ping 统计信息:
数据包: 已发送 = 3,已接收 = 0,丢失 = 3 (100% 丢失),
Control-C
ping baidu.com
正在 Ping baidu.com [39.156.66.10] 具有 32 字节的数据:
来自 39.156.66.10 的回复: 字节=32 时间=23ms TTL=52
来自 39.156.66.10 的回复: 字节=32 时间=24ms TTL=52
来自 39.156.66.10 的回复: 字节=32 时间=23ms TTL=52
39.156.66.10 的 Ping 统计信息:
数据包: 已发送 = 3,已接收 = 3,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
最短 = 23ms,最长 = 24ms,平均 = 23ms
Control-C
总结:
- hosts 没有特殊配置
- 改成可用 DNS,问题没有修复
ipconfig /flushdns
也不管用
- 重启也没有好
那就奇怪了,这个 ping 里面的本地回环地址是哪里来的呢?
最后,
ipconfig /displaydns
可以看到,还是用的回环地址
ipconfig /all
发现还有一个 DNSv6 配置。。。
DNSv6 设置成 AliDNS 的 IPv6 地址:2400:3200::1
,2400:3200:baba::1
再试,
ping github.com
正在 Ping github.com [20.205.243.166] 具有 32 字节的数据:
请求超时。
请求超时。
请求超时。
请求超时。
20.205.243.166 的 Ping 统计信息:
数据包: 已发送 = 4,已接收 = 0,丢失 = 4 (100% 丢失),
终于好了。😂
事情的起源是 git clone 失败,最后 git clone 可以了。
ping 不通,可能是网络问题,也可能是 github 网络配置。
这就不管了。
日志
2022-10-07
简单的日志系统
我还没有用过 ELK 这样的系统(只实验性使用过 Graylog),使用过这些日志管理方案:
- 日志就通过文件存放在服务器上,然后登录服务器进行日志文件分析,排查问题。
- 再进一步就是,将日志文件定期 rsync 到一台服务器上,方便日志管理与日志搜索。
-
服务直接写 syslog,然后配置好 rsyslog 同步就行。
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
A(应用程序):::process -->|写入日志| B(syslog 接口):::process
B -->|发送日志消息| C(本地 rsyslog 守护进程):::process
C -->|存储本地日志| D(本地日志文件):::process
C -->|配置转发规则| E{是否转发到远程?}:::process
E -->|是| F(通过 UDP/TCP 发送):::process
F -->|接收日志消息| G(远程 rsyslog 守护进程):::process
G -->|存储远程日志| H(远程日志文件):::process
E -->|否| D
-
不要忘了,日志写入数据库在某些场景下也是一个可选方案,比如内部管理系统的登录日志、操作日志等。
理论上日志可以直接写入远程日志系统,但是我想应该不会有线上服务这样做。网络稳定性问题(可能丢失日志、服务阻塞)、性能消耗、增加系统复杂性等。
syslog 是系统提供的日志接口,rsyslog 提供了 TCP 日志可靠传输、本地日志队列功能,而且是 Linux 世界广泛采用的基础服务,我觉得可以接受。
日志系统
-
ELK / EFK:
Elasticsearch # 搜索引擎
Logstash # 日志采集、过滤、预处理
Kabana # 数据可视化
Filebeat # Logstash 替代方案,更加轻量级
graph LR
Logs --> Filebeat --> ES
Logs --> Logstash --> ES
-
FELK:Filebeat 将日志采集到 Logstash,处理之后导入 ES
graph LR
Logs --> Filebeat --> Logstash --> ES
-
FELK + Kafka
Filebeat 将日志采集到 Kafka,再由 Logstash 从 Kafka 读取日志,处理完成之后导入 ES
graph LR
Logs --> Filebeat --> Kafka --> Logstash --> ES
-
Graylog:
Filebeat
Graylog Sidebar
Graylog
Elasticsearch
MongoDB
-
LPG:
Loki # 搜索引擎
Promtail # 日志采集、过滤、预处理
Grafana # 数据可视化
设计
graph TD
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
A(服务):::process -->|生成日志| B(rsyslog):::process
B --> C(Kafka):::process
C --> D(ELK):::process
- 服务写 syslog,通过 rsyslog 同步到
接收,解析,处理,转换,格式化