redis sds

SDS

Redis源码定义了几种不同长度的sdshdr结构体,sdshdr8、sdshdr16、sdshdr32和sdshdr64

这些结构体用来表示 SDS 对象的头部信息

每个sdshdr结构体包含三个成员变量:

  • len:表示已使用的长度,用来记录字符串当前实际使用的字节数
  • alloc:表示分配的总长度,即为了存储该字符串所分配的内存大小
  • flags:存储类型信息,其中前3位用于存储特定标志,后5位预留

这些结构体都使用了__attribute__((__packed__)) 属性来取消结构体成员之间的字节对齐,意味着这些结构体中的成员变量在内存中是按照定义顺序依次排列,并且不会被额外填充以对齐到某个边界

什么使用packed属性后可以节约3个字节的情况,以sdshdr32为例:

  • sdshdr32结构体中,len和alloc分别为uint32_t类型,占用4字节
  • flags为unsigned char类型,占用1字节
  • buf是一个柔性数组成员,不占用结构体自身的空间

未使用__attribute__((__packed__))属性时,编译器会对结构体进行字节对齐,通常是按照成员中占用空间最大的基本数据类型进行对齐。对于sdshdr32结构体,如果没有使用packed属性,编译器可能会在flags成员后填充3个字节,以保证下一个成员buf从合适的位置开始,这样总大小可能会是12字节(4字节 + 4字节 + 1字节 + 3字节填充)

而使用了__attribute__((__packed__))属性后,结构体取消了字节对齐,不会自动添加填充字节。这样,sdshdr32结构体的大小只需计算成员变量本身的大小,即4字节(len)+ 4字节(alloc)+ 1字节(flags),总共只有9字节,相比未使用packed属性的情况,节省了3个字节的内存空间

这种优化带来的好处是节省了内存空间,尤其在大规模使用动态字符串时,可以显著减少内存的使用量,提升Redis的性能和效率

打印结构体的大小和成员在内存中的地址

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>
#include <stdint.h>

// 定义 sdshdr32 结构体,去掉 __attribute__((__packed__))
struct sdshdr32{
    uint32_t len; // 已使用长度,4字节
    uint8_t extra; // 增加一个额外的成员变量,1字节
    uint32_t alloc; // 总长度,4字节
    unsigned char flags; // 前3位存储类型,后5位预留,1字节
    char buf[]; // 柔性数组
};

int main(){
    struct sdshdr32 s;

    // 打印结构体大小
    printf("Size of sdshdr32: %zu bytes\n", sizeof(s));

    // 打印各成员在内存中的地址偏移

    printf("Offset of len: %zu\n", (size_t)&(((struct sdshdr32*)0)->len));
    printf("Offset of extra: %zu\n", (size_t)&(((struct sdshdr32*)0)->extra));
    printf("Offset of alloc: %zu\n", (size_t)&(((struct sdshdr32*)0)->alloc));
    printf("Offset of flags: %zu\n", (size_t)&(((struct sdshdr32*)0)->flags));
    printf("Offset of buf: %zu\n", (size_t)&(((struct sdshdr32*)0)->buf));

    return 0;
}

uint8_t extra 成员变量,它是一个单字节的变量,这可能导致编译器在 extraalloc 之间插入填充字节,使得结构体的大小增加,以确保每个成员按照适当的对齐方式排列

(size_t)&(((struct sdshdr32 *)0)->len) 用来获取结构体成员在内存中的偏移量(即成员的地址)

  1. (struct sdshdr32 \*)0
    • (struct sdshdr32 *) 是一个类型转换操作,将整数 0 转换为指向 sdshdr32 结构体的指针类型
    • 0 通常被用作一个特殊值,表示空指针。在这里,我们不关心实际的对象,只是想得到一个合法的结构体指针
  2. ((struct sdshdr32 \*)0)->len
    • ((struct sdshdr32 *)0) 返回一个指向结构体 sdshdr32 的指针,即使它是一个无效的地址(因为0地址通常不是合法地址,但在这里我们只是想要获取结构体的成员偏移量)
    • ->len 表示对指针指向的结构体成员 len 进行访问
  3. &(((struct sdshdr32 \*)0)->len)
    • & 运算符获取其后表达式的地址,这里是获取 ((struct sdshdr32 *)0)->len 的地址,即 len 成员的地址
  4. (size_t)
    • 强制类型转换为 size_t 类型,确保输出是地址的整数值

Redis 源码

鸡汤

阅读代码是很好的锻炼耐心和毅力的机会

看别人代码的过程,即针对一个疑问,收集线索,有点连成线的过程,中间肯定有一段时间非常难熬与枯燥

完所有的代码,所有的线索都连成一条线,就能体会柳暗花明了

并不是说阅读了大量的代码就能写出很牛的代码,写代码需要对当前需求的把握和清晰的逻辑思维,实践中可以慢慢培养。千万不要读得太多,而写得太少

不在浮沙筑高台

并不推荐一上来就是看源码,一般在某个方向上有一定的基本知识积累了才开始去尝试阅读

c 服务器的后台代码,需要对 linux 网络/系统编程有一定的认识,甚至读过 W.Richard Stevens 的几本经典之作。完全的新人去阅读代码,只会信心受打击

初学者在某一技术方向上有基本的积累后,找一个优秀的开源项目试着阅读

网上很多的资料以及文档,为读懂源码提供很多的帮助

见识业界的编程规范,优秀的框架或者模式,前人在大量的实践中总结出来的,必定是行而有效的,夯实在某个技术方向上的认知

练就你的耐心和毅力

阅读源码本身是枯燥乏味的过程,经常看一个模块一两天,来来回回往往复复,假使心浮气躁,容易浅尝辄止,半途而废

如何阅读 Redis 源码?

首先阅读数据结构,和其他部分耦合最少,实现 redis 的基础, 数据结构算法书上都可了解到, 读最轻松、难度最低

横沙岛2日游

横沙岛位于长江口最东端 是一座原生态的小岛

这里保留了原始、淳朴的田园生活 远离城市的喧嚣

网红打卡点小白楼 第二次来了

找出hugo站内未引用文件

3年乞丐阿里云这周到期,换到 新人第一年 59 的 ucloud(当年公司离 ucloud所在的城市概念非常近)

把图片迁移到赛博菩萨 cloudflare R2后,治下今日洁癖,清理下未引用的图片

用 GPT,生成 GO colly 爬本地 <img 地址的代码,小改下,再 filepath.Walk 加map过滤下,几分钟的事

打印了 50多个没引用的文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
package main

import (
	"fmt"
	"log"
	"net/url"
	"os"
	"path/filepath"
	"strings"

	"github.com/gocolly/colly"
)

func main() {
	dir := "d:/www/static/img"
	var files = make(map[string]struct{})
	filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		if !info.IsDir() {
			path = strings.ReplaceAll(path, "\\", "/")
			path = path[len(dir):]
			path = "img" + path
			files[path] = struct{}{}
			if strings.Contains(path, "apm") {
				println()
			}

		}
		return nil
	})

	c := colly.NewCollector()

	c.OnHTML("a[href]", func(e *colly.HTMLElement) {
		link := e.Attr("href")
		// 过滤掉外部链接,仅限于本站
		if strings.HasPrefix(link, "/") || strings.HasPrefix(link, "http://localhost") {
			// 把相对链接转换为绝对链接
			e.Request.Visit(e.Request.AbsoluteURL(link))
		}
	})
	// 处理 <img> 标签
	c.OnHTML("img", func(e *colly.HTMLElement) {
		imgSrc := e.Attr("src")
		if strings.HasPrefix(imgSrc, "http") {
			parsedURL, err := url.Parse(imgSrc)
			if err != nil {
				fmt.Println("Error parsing URL:", err)
				return
			}
			// 获取路径部分
			path := parsedURL.Path

			// 去掉开头的斜杠(如果需要)
			trimmedPath := strings.TrimPrefix(path, "/")

			_, exists := files[trimmedPath]
			if exists {
				delete(files, trimmedPath)
			}

		} else {

			_, exists := files[imgSrc]
			if exists {
				delete(files, imgSrc)
			}
		}
	})

	// 处理请求错误
	c.OnRequest(func(r *colly.Request) {
		//	fmt.Println("Visiting", r.URL)
	})

	c.OnError(func(_ *colly.Response, err error) {
		log.Println("Request error:", err)
	})

	// 启动爬虫
	err := c.Visit("http://localhost:1313/")
	if err != nil {
		log.Fatal(err)
	}

	for v := range files {

		fmt.Println(v)
	}

}

go 微信bot

网页微信禁止登录

https://web.wechat.com/?lang=zh_CN

UOS微信

UOS下的微信只是网页版套了个electron

/cgi-bin/mmwebwx-bin/webwxnewloginpage

加2个固定的头,并存下cookie供之后用

1
2
extspam ='Gp8ICJXXX'
client-version' = '2.0.0'
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
	"Text": "",
	"Ret": "0",
	"Message": "",
	"Skey": "@crypt_456ecb62_870xxx",
	"Wxsid": "Wxxxx",
	"Wxuin": "122356789",
	"PassTicket": "xx,
	"Isgrayscale": "1",
	"Host": "wx2.qq.com"
}

DIY

自己撸一份go简易自用的,不算 struct 也就 600 来行

Elastic APM

在 es apm 强制 fleet 前就用了

UI

gocron-modify

vps 里自用定时任务,gocron fork 后改一下

go

  • macaron > gin
  • rakyll/stati > goembed

front

  • webpack > vite 5
  • vuex > pinia

check-update

版本控,列一些关注的更新

dev

  • go
  • mysql
  • redis
  • smartgit
  • jprofiler
  • arthas
  • git
  • perfino
  • idea
  • goland
  • clion
  • clink
  • rclone
  • xshell

software

  • emeditor
  • chrome
  • seafile
  • everything
  • obsidian
  • winrar
  • sumatrapdf
  • utoruns
  • cports

github

  • gohugoio/hugo
  • alibaba/arthas
  • adnanh/webhook
  • voidint/g
  • Microsoft/WSL
  • apache/rocketmq
  • chrisant996/clink
  • com/alibaba/fastjson
  • rclone/rclone
  • upx/upx
  • fatedier/frp
  • sharkdp/fd
  • minio/minio
  • alist-org/alist

maven

  • cn/hutool/hutool-all
  • com/alibaba/fastjson
  • org/springframework/cloud/spring-cloud-dependencies
  • org/springframework/boot/spring-boot-dependencies
  • net/logstash/logback/logstash-logback-encoder

个人消息推送-企业微信群机器人

  • 钉钉:多安装一个app

  • 邮件:不够简单快捷,邮件太多容易泛滥

  • telegram:需要手机长期挂代理

  • 企业微信自建应用:认证300块、微信开放平台注册过还会(该域名主体为第三方服务商,请使用企业主体域名),开放平台还没得注销(注销)

  • server酱,早期公众号的通道推送消息,微信发布下线公告,企业微信应用消息通道,免费用户一天额度只有5条,且只显示标题


    企业微信注销过一次后自建应用废了

    发了一段时间邮件

还是企业微信群聊机器人最方便

群聊机器人

1
https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=uuid
1
2
3
4
5
6
{
    "msgtype": "text",
    "text": {
        "content": "xxx",
    }
}

https://github.com/ncarlier/webhookd

有 Post hook notifications

小定制一下,把 执行状态,耗时 等,也加到 payload里

微信里实时收到结果,舒服