0%

来源

雪花算法(Snowflake)是一种生成分布式全局唯一ID的算法,生成的ID称为Snowflake IDs或snowflakes。这种算法由Twitter创建,并用于推文的ID。Discord和Instagram等其他公司采用了修改后的版本。

格式

一个Snowflake ID有64位。前41位是时间戳,表示了自选定的时期以来的毫秒数。 接下来的10位代表计算机ID,防止冲突。 其余12位代表每台机器上生成ID的序列号,这允许在同一毫秒内创建多个Snowflake ID。最后以十进制将数字序列化。

SnowflakeID基于时间生成,故可以按时间排序。 此外,一个ID的生成时间可以由其自身推断出来,反之亦然。该特性可以用于按时间筛选ID,以及与之联系的对象。

64位

范例

2022年六月由@Wikipedia所发的一条推文的雪花ID是1541815603606036480。这个数字被转换成二进制就是0 0101 0101 1001 0110 1000 0100 0111 1101 1000 1000|01 0111 1010|0000 0000 0000,其中以竖线分隔成三个部分。

64位的二进制所示

  • 最高位表示符号位
  • 后面的41bit是产生该ID的unix毫秒时间戳
  • 10bit是机器编号,最多可以部署在 2^8 = 1024 机器上
  • 12bit 序列号,同一毫秒最多可以产生 2^12 = 4096 个序列号

算法实现思路

时间戳左移22位,机器编号左移12位,序列号不动,三者按位或运算,得到一个64位二进制,再转成10进制,就是雪花ID

反之,根据雪花ID可以反推导出机器ID,时间戳,序列号

41位时间戳2^41/(1000360024*365) ≈ 69.73057年,引入 epoch基准时间,是为了能是雪花ID能使用的时间更长一点,41位时间戳按照unix时间,最多只能到2039-09-07 23:47:35

位运算

省流不看系列

github jokechat/guid

1
2
docker run --rm -p 8080:80 jokechat/guid:v1.0.3 
curl 127.0.0.1:8080

1
2
3
4
5
6
7
8
9
10
{
"code": 0,
"msg": "success",
"data": {
"id": 645124828453408800,
"workId": 0,
"base32": "13pv378bo0800",
"time": "2023-11-16T04:55:44.943Z"
}
}

代码实现

type.go

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
package snowflake

import "time"

type Option interface {
apply(*Worker)
}

type OptionFunc func(*Worker)

func (o OptionFunc) apply(worker *Worker) {
o(worker)
}

func WithEpoch(epoch time.Time) OptionFunc {
return func(worker *Worker) {
worker.epoch = epoch
}
}

func WithWorkerId(workerId int64) OptionFunc {
return func(worker *Worker) {
worker.workerId = workerId
}
}

snowflake.go

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
package snowflake

import (
"errors"
"fmt"
"sync"
"time"
)

const (
workerBits uint8 = 10
workerMax int64 = -1 ^ (-1 << workerBits)

stepBits uint8 = 12
stepMax int64 = -1 ^ (-1 << stepBits)

workerShift uint8 = stepBits
timeShift uint8 = workerBits + stepBits
)

type Worker struct {
mu sync.Mutex

timestamp int64 // 记录时间戳
workerId int64 // 当前工作节点ID
step int64 // 当前毫秒已经生成的id序列号(从0开始累加) 1毫秒内最多生成4096个ID
epoch time.Time // 开始运行时间

}

func NewWorkerWithOpts(opts ...Option) (*Worker, error) {
w := &Worker{}
for _, opt := range opts {
opt.apply(w)
}

if w.epoch.IsZero() {
return nil, errors.New("epoch is required")
}

if w.workerId < 0 || w.workerId > workerMax {
return nil, errors.New(fmt.Sprintf("worker ID must be in [0,%d]", workerMax))
}

return w, nil
}

func (w *Worker) Next() ID {
w.mu.Lock()
defer w.mu.Unlock()
now := time.Now().UnixMilli()

// 处理时钟回拨问题或处于同一毫秒 ntp 时间变化
if now < w.timestamp {
now = w.timestamp + 1
}

if now == w.timestamp {
w.step = (w.step + 1) & stepMax
if w.step == 0 { // 当前毫秒生成的ID已超上限,等待下一毫秒
for now <= w.timestamp {
now = time.Now().UnixMilli()
}
}
} else {
w.step = 0
// todo 持久化存储当前时间,文件或者redis
}
w.timestamp = now
id := (now-w.epoch.UnixMilli())<<timeShift | (w.workerId << workerShift) | w.step
return ID(id)
}

id.go

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
package snowflake

import (
"bytes"
"encoding/base32"
"encoding/binary"
"strconv"
"strings"
"time"
)

type ID uint64

func (i ID) Uint64() uint64 {
return uint64(i)
}

func (i ID) String() string {
return strconv.FormatUint(uint64(i), 10)
}

func (i ID) Base32() string {
bytesBuffer := bytes.NewBuffer([]byte{})
binary.Write(bytesBuffer, binary.BigEndian, i)
return base32.HexEncoding.WithPadding(base32.NoPadding).EncodeToString(bytesBuffer.Bytes())
}

func (i ID) Base32Lower() string {
return strings.ToLower(i.Base32())
}

func (i ID) UnixMilli(epoch time.Time) int64 {
return epoch.UnixMilli() + int64(i.Uint64()>>timeShift)
}

func (i ID) WorkId() uint64 {
d := i.Uint64() >> workerShift & uint64(workerMax)
return d
}

func (i ID) Step() uint64 {
d := i.Uint64() & uint64(stepMax)
return d
}

func (i ID) Time(epoch time.Time) time.Time {
return time.UnixMilli(i.UnixMilli(epoch))
}
1
2
3
4
5
6
7
8
9
func main() {
epoch := time.Date(2019, time.January, 1, 0, 0, 0, 0, time.Local)
w, _ := NewWorkerWithOpts(
WithEpoch(epoch),
WithWorkerId(1),
)
id := w.Next()
spew.Dump(id.Uint64())
}

参考文献

维基百科 雪花算法

vim-tutorial

vim tutorial

整理资料来源

本资料来自 vim-web 一起来说vim语
整理学习 jokechat
理解此部分需要你已经理解vim的几种常用工作模式(正常模式,插入模式,命令模式等)

动词

动词代表了我们打算对文本进行什么样的操作.例如:

1
2
3
4
5
d # 表示delete
r # 表示替换replace
c # 表示表示change
y # 表示复制yank
v # 表示选取visual select

名词

名词代表了我们即将处理的文本.vim中有一个术语对象叫做 text object (文本对象) ,示例如下:

1
2
3
4
5
w # 表示一个单词word
s # 表示一个句子sentence
p # 表示一个段落paragraph
t # 表示一个html标签tag
引号或者各种括号所包含文本称为一个文本块

介词

介词界定了待编辑文本的范围或者位置

1
2
3
4
i # 表示在...之内 inside
a # 表示环绕...around
t # 表示到...位置钱 to
f # 表示到...位置上 forward

数词

数字指定了待编辑文本对象的数量,从这个角度而言,数词也可以看做一种介词.引入介词之后,文本编辑命令的语法就升级成了下面样子:

1
动词 介词/数词 名词

下面是几个例子:

1
2
c3w # 修改三个单词 change three words
d2w # 删除两个单词 delete two words

阅读全文 »

因实际项目需求,需要一些在后台长时间运行一些比较耗时的任务,e.g:邮件,app推送消息,报表生成..,寻了几种方案swoole,php-resque,workman,最终选择了php-resque,比较喜欢Ta的设计简洁清晰,足够轻量,能满足目前的大部分需求

设计思想

导读

php-resque 的设计思想来源于 Ruby 的项目 resque,这是各种语言实现版本,更多文档请查阅官方文档

设计思路&角色划分

Worker 执行者

负责将redis 队列中的 Job取出,并调用执行指定的 Job(Class),同时负责维护 Job 状态;
一个Worker可以处理多个队列,可以开启多个worker加快处理速度

Queue 队列

队列通过 redis 队列实现,

Job 任务

一个Job就是一个需要在后台需要执行的任务,在php-resque的实现中,一个Class就是一个Job

使用基本流程

0x00 将一个后台任务编写为一个独立的Class,这个Class就是一个Job

0x01 在需要使用后台程序的地方,系统将Job Class的名称以及所需参数放入队列。

0x02 以命令行方式开启一个Worker,并通过参数指定Worker所需要处理的队列。

0x03 Worker作为守护进程运行,并且定时检查队列。

0x04 当队列中有Job时,Worker取出Job并运行,即实例化Job Class并执行Class中的方法。

安装

安装依赖

redis

php

使用 TODO

Job相关

编写Job

将Job插入队列

Job状态查看

Worker相关

启动,重启,停止

日志收集

工欲善其事必先利其器

brew

brew 又叫Homebrew,是Mac OSX上的软件包管理工具,能在Mac中方便的安装软件或者卸载软件, 只需要一个命令, 非常方便

安装

1
2
# 安装命令
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

搜索

1
brew search xx

安装列表

1
brew list

服务

1
brew services [-v|--verbose] [list | run | start | stop | restart | cleanup] [...]

卸载

1
brew uninstall, rm, remove [--force] [--ignore-dependencies] formula:

oh my zsh

是最为流行的 zsh 配置文件,提供了大量的主题和插件,极大的拓展了 zsh 的功能

1
2
3
4
5
6
7
8
9
10
11
# Mac
brew install zsh

# centos
yum install zsh

# 修改为zsh
chsh -s `which zsh`

# 安装 oh my zsh
sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"

阅读全文 »


术业有专攻,选择合适的工具做合适的事情,事半功倍
下面贴一贴自己常用的IDE


Mac下最好用的终端工具-iTerm2

  • 分割窗口
  • 快捷键
  • 搜索
  • 自动完成
  • 选中复制
  • 粘贴历史
  • 即时重放
  • 配置型强
  • 更多功能特性

效率神器 Alfred

  • 最爱剪贴板
  • 史上最酸爽效率神器,用过都说好 ☺
阅读全文 »

资料整理来源知乎 如何提高自己的核心竞争力

  • 能力不行就靠努力,你的努力,你的想法让你的上司知道
  • 能独立的表达自己的观点,却不能不合群,缺失团队精神的没人喜欢
  • 对’公司规则’,’团队统一标准’要表示服从,却不能卑躬屈膝
  • 要积极地参与公司的团建活动。AA制的活动不想去,也请把钱付了。也是为自己积赞人品
  • 看到能力比自己强的同事少去苛责别人嫉妒别人。看到不如自己的同事要多多帮助。默默无闻弥补你上司的短板,不要在背后说你老板的坏话。
  • 学会沟通,学会合作,学会学习。对于没有什么核心竞争力的员工来说,这三样技能比工作能力更重要!
  • 不要玻璃心。
  • 沟通是情绪的转移,信息的转移,感情的互动。喜悦心、包容心、同理心、赞美心、爱心。
  • 任何合作都是利益的合作,缺乏利益驱动的合作基本都是废话,不值得做的事情, 谁就不会去做好;那么,对于一个“没有核心竞争力”的人来说,你看中的利益应该是什么?我认为应该是:你的团队认可你!用心和他人合作比你一天到晚想着法子证明自己更重要。和他人合作,不但能取长补短,还能让你默默无闻中永远安全。

别调皮,乖乖写注释去

  • 总有一些小朋友不乖,总是忘了写注释,于是用svn钩子拦截一些空注释

0x01 切换到svn服务器目录

1
2
# 假定svn所在目录为 /var/svn/svnrepos
cd /var/svn/svnrepos/hooks

0x02 创建pre-commit

1
2
3
4
# 创建文件
touch pre-commit
# 赋予执行权限
chmod u+x pre-commit
阅读全文 »

身为一个懒癌晚期患者,能敲一个单词,绝不敲两下

  • 重复的次数多了就该想想办法偷偷懒了,例如这货:ssh免密免密登录服务器,再也不用翻笔记找密码了

0x01 客户端创建公私钥

1
2
ssh-keygen
# 按照提示输入文件名称即可,例如:devServer

0x02 将公钥复制服务器

1
2
3
# ssh-copy-id 会将客户端公钥复制到服务器  `~/.ssh/authorized_keys`中
# 按照体术输入服务器用户名密码即可
ssh-copy-id -i devServer.pub username@domain
阅读全文 »

没有瘾的生活,总是过分平常,一直过下去,就好像自己都要消失在这个世界上了。 所以我们总需要一种类似执念、迷恋、热爱或沉溺的东西,才可以在这时常令人绝望的世界里,勇敢地活下去。 我们总要沉浸某个领域,才会知道这个自己是怎样的自己,才会明白自己要的究竟是什么,才会在这个自己重新浮上来之后, 如获得了新生般有了新的眼光、新的视野,而那种上瘾的感觉将变成一个秘密,一个充满爱的秘密,于是那些艰难险阻也就再也算不上什么了……