概览消息队列篇一:不同的mq的消息模型

极客时间《消息队列高手课》笔记 概览消息队列篇〇:为什么需要消息队列 概览消息队列篇一:不同的mq的消息模型 概览消息队列篇二:如何确保消息不丢失 概览消息队列篇三:重复消息的处理 1. 主题和队列 最基本的队列模型,是按照“队列”的数据结构来设计的,即先进先出。生产者发送消息(入队),消费者获取消息(出队)。 当有多个生产者往同一个队列中发送消息,则这个队列中可以消费到的消息,就是这些生产者生产的所有消息的合集。消息的顺序就是这些生产者发送消息的自然顺序。如果有多个消费者接收同一个队列的消息,这些消费者之间实际上是竞争的关系,每个消费者只能收到队列中的一部分消息,也就是说任何一条消息只能被其中的一个消费者收到。 这时候问题就出现了,如果一份消息需要被多个消费者消费,比如,对于一个订单,它需要被风控系统、支付系统等系统消费,显然上述的模型不能满足这个需求。这时候一个可行的解决方案是:为每个消费者创建一个单独的队列,让生产者发送多份。 但显然这样会浪费较多的资源,同一个消息复制了多份。更重要的是,生产者必须知道有多少个消费者。为每个消费者单独发送一份消息,这实际上违背了消息队列“解耦”这个设计初衷。 为了解决这个问题,演化出“发布-订阅”(pub-sub)模型。 在发布-订阅模型中,消息的发送方称为发布者(Publisher),消息的接收方称为订阅者(Subscriber),服务端存放消息的容器称为主题(Topic)。发布者将消息发送到主题中,订阅者在接收消息之前需要先“订阅主题”。“订阅”在这里既是一个动作,同时还可以认为是主题在消费时的一个逻辑副本,每份订阅中,订阅者都可以接收到主题的所有消息。 当发布-订阅模型中只有一个订阅者时,那它和队列模型就基本上是一样的了。也就是说,发布 - 订阅模型在功能层面上是可以兼容队列模型的。 2. 常见mq的消息模型 2.1 RabbitMQ的消息模型 RabbitMQ是少数仍坚持使用队列模型的产品之一。 那么RabbitMQ是如何解决多个消费者的问题呢? RabbitMQ有一个Exchange模块,在 RabbitMQ 中,Exchange 位于生产者和队列之间,生产者并不关心将消息发送给哪个队列,而是将消息发送给 Exchange,由 Exchange 上配置的策略来决定将消息投递到哪些队列中。 同一份消息如果需要被多个消费者来消费,需要配置 Exchange 将消息发送到多个队列,每个队列中都存放一份完整的消息数据,可以为一个消费者提供消费服务。 2.2 RocketMQ的消息模型 RocketMQ 使用的消息模型是标准的发布-订阅模型 但是,在 RocketMQ 也有队列(Queue)这个概念,并且队列在 RocketMQ 中是一个非常重要的概念,要了解队列在RocketMQ中的作用,我们首先要了解消息确认机制及其带来的问题 为了确保消息不会在传递过程中由于网络或服务器故障丢失,消息队列一般采用“请求-确认”机制来确认消息的成功消费。消费者在成功消费一条消息,完成自己的业务逻辑后,会发送确认給消息队列。消息队列只有收到确认后,才认为一条消息被成功消费。 这种机制很好地保证了消息传递过程中的可靠性,但是其带来另一个问题:在某一条消息被成功消费之前,下一条消息是不能被消费的,否则就会出现消息空洞,违背了有序性这个原则。 也就是说,每个主题在任意时刻,至多只能有一个消费者实例在进行消费,那就没法通过水平扩展消费者的数量来提升消费端总体的消费性能。 为了解决这个问题,RocketMQ在主题下增加了队列的概念,每个主题包含多个队列,通过多个队列来实现多实例并行生产和消费。 RocketMQ 只在队列上保证消息的有序性,主题层面是无法保证消息的严格顺序的。 RocketMQ 中,订阅者的概念是通过消费组(Consumer Group)来体现的。每个消费组都消费主题中一份完整的消息,不同消费组之间消费进度彼此不受影响,也就是说,一条消息被 Consumer Group1 消费过,也会再给 Consumer Group2 消费。 同时,一个消费组中包含多个消费者,同一个组内的消费者是竞争消费的关系,每个消费者负责消费组内的一部分消息。如果一条消息被消费者 Consumer1 消费了,那同组的其他消费者就不会再收到这条消息。 在 Topic 的消费过程中,由于消息需要被不同的组进行多次消费,所以消费完的消息并不会立即被删除,这就需要 RocketMQ 为每个消费组在每个队列上维护一个消费位置(Consumer Offset),这个位置之前的消息都被消费过,之后的消息都没有被消费过,每成功消费一条消息,消费位置就加一。 2.3 Kafka的消息模型 Kafka 的消息模型和 RocketMQ 是完全一样的,唯一的区别是,在 Kafka 中,队列这个概念的名称不一样,Kafka 中对应的名称是“分区(Partition)”,含义和功能是没有任何区别的。

June 30, 2022 · 1 min · 李昌

Go汇编之定义基本数据类型

1. Go汇编基础 这里只介绍本文会用到的语法 4个虚拟寄存器 FP: Frame pointer:伪FP寄存器对应函数的栈帧指针,一般用来访问函数的参数和返回值;golang语言中,函数的参数和返回值,函数中的局部变量,函数中调用子函数的参数和返回值都是存储在栈中的,我们把这一段栈内存称为栈帧(frame),伪FP寄存器对应栈帧的底部,但是伪FP只包括函数的参数和返回值这部分内存,其他部分由伪SP寄存器表示;注意golang中函数的返回值也是通过栈帧返回的,这也是golang函数可以有多个返回值的原因; PC: Program counter:指令计数器,用于分支和跳转,它是汇编的IP寄存器的别名; SB: Static base pointer:一般用于声明函数或者全局变量,对应代码区(text)内存段底部;可认为是内存的起源,所以符号foo(SB)就是名称foo作为内存中的一个地址。这种形式被用于命名全局函数和数据,如果将<>添加到名称中,如foo<>(SB),则代表此标识符只在当前源文件中可见。可对名称添加偏移量,如foo+4(SB)指foo开头之后的四个字节。 SP: Stack pointer:指向当前栈帧的局部变量的开始位置,一般用来引用函数的局部变量,这里需要注意汇编中也有一个SP寄存器,它们的区别是:1.伪SP寄存器指向栈帧(不包括函数参数和返回值部分)的底部,真SP寄存器对应栈的顶部;所以伪SP寄存器一般用于寻址函数局部变量,真SP寄存器一般用于调用子函数时,寻址子函数的参数和返回值(后面会有具体示例演示);2.当需要区分伪寄存器和真寄存器的时候只需要记住一点:伪寄存器一般需要一个标识符和偏移量为前缀,如果没有标识符前缀则是真寄存器。比如(SP)、+8(SP)没有标识符前缀为真SP寄存器,而a(SP)、b+8(SP)有标识符为前缀表示伪寄存器; 所有用户定义的符号都作为偏移量写入伪寄存器 FP(参数和局部变量)和 SB(全局变量) 常量 Go汇编语言中常量以$美元符号为前缀。常量的类型有整数常量、浮点数常量、字符常量和字符串常量等几种类型。 $1 // 十进制 $0xf4f8fcff // 十六进制 $1.5 // 浮点数 $'a' // 字符 $"abcd" DATA指令 DATA命令用于初始化包变量,DATA命令的语法如下: DATA symbol+offset(SB)/width, value 其中symbol为变量在汇编语言中对应的标识符,offset是符号开始地址的偏移量,width是要初始化内存的宽度大小,value是要初始化的值。其中当前包中Go语言定义的符号symbol,在汇编代码中对应·symbol,其中·中点符号为一个特殊的unicode符号;DATA命令示例如下 DATA ·Id+0(SB)/1,$0x37 DATA ·Id+1(SB)/1,$0x25 这两条指令的含义是将全局变量Id赋值为16进制数0x2537,也就是十进制的9527; 我们也可以合并成一条指令 GLOBL 用于将符号导出,例如将全局变量导出(所谓导出就是把汇编中的全局变量导出到go代码中声明的相同变量上,否则go代码中声明的变量感知不到汇编中变量的值的变化),其语法如下: GLOBL symbol(SB), width 其中symbol对应汇编中符号的名字,width为符号对应内存的大小;GLOBL命令示例如下: GLOBL ·Id, $8这条指令的含义是导出一个全局变量Id,其大小是8字节(byte); 结合DATA和GLOBL指令,我们就可以初始化并导出一个全局变量.例如: GLOBL ·Id, $8 DATA ·Id+0(SB)/8,$0x12345 2....

June 10, 2022 · 2 min · 李昌

CgroupV2

1. Cgroup概览 cgroup是Linux内核提供的一种按层次组织进程,并对进程资源按层次进行分配和限制的机制。 cgroup 主要由两部分组成——core和controller。core主要负责分层组织进程。 controller负责为属于当前cgroup的进程分配和限制资源. 多个cgroup以树形结构组织,系统中每个进程都属于一个cgroup,一个进程中的所有线程都属于同一个cgroup。 controller可以在cgroup上有选择的开启,开启后的controller将影响这个cgroup内的所有进程。 使用如下命令挂载cgroupv2 mount -t cgroup2 none $MOUNT_POINT # MOUNT_POINT 是任意你想要挂载到的位置 直接在$MOUNT_POINT创建一个文件夹即可创建一个cgroup mkdir $MOUNT_POINT/$GROUP_NAME 每个cgroup内都有一个cgroup.procs接口文件,其中逐行列出了属于当前cgroup的所有进程的PID。需要注意的是,PID可能重复出现且无序。 若想将某个进程移动到一个cgroup中,只需将其PID写入cgroup.procs文件,进程中的所有线程也会迁移到该cgroup中。fork出的子进程依然属于这个cgroup。 若要删除一个cgroup,需要注意一点:这个cgroup内需要没有任何子进程且仅与僵尸进程相关联,且没有子cgroup.满足了上述条件后,将其作为一个空目录删除即可,使用rm -rf无法对cgroup目录进行删除。 rmdir $MOUNT_POINT/$GROUP_NAME /proc/$PID/cgroup中包含一个进程所属的cgroup。 $ cat /proc/self/cgroup # self表示当前shell进程 0::/user.slice/user-1000.slice/session-2.scope 如果进程成为僵尸进程并且随后删除了与之关联的 cgroup,则将“(已删除)”附加到路径中: $ cat /proc/842/cgroup 0::/test-cgroup/test-cgroup-nested (deleted) CgroupV2还支持线程模式 每个非根 cgroup 都有一个cgroup.events文件,其中包含populated字段,指示 cgroup 的子层次结构中是否有实时进程。 如果 cgroup 及其后代中没有实时进程,则其值为 0; 否则为1. 例如:考虑如下cgroup结构,括号内数字代表cgroup内进程数: A(4) - B(0) - C(1) \ D(0) 则A、B 和 C 的populated字段将为 1,而 D 为 0。在 C 中的一个进程退出后,B 和 C 的populated字段将翻转为“0”,文件修改事件将在两个cgroup的cgroup....

May 19, 2022 · 2 min · 李昌

Linux中的信号

1. Linux 中的信号 信号这个概念在很早期的 Unix 系统上就有。它一般会从 1 开始编号,通常来说,信号编号是 1 到 31,这个编号在所有的 Unix 系统上都是一样的。 取值 名称 解释 默认动作 1 SIGHUP 挂起 2 SIGINT 中断 3 SIGQUIT 退出 4 SIGILL 非法指令 5 SIGTRAP 断点或陷阱指令 6 SIGABRT abort发出的信号 7 SIGBUS 非法内存访问 8 SIGFPE 浮点异常 9 SIGKILL kill信号 不能被忽略、处理和阻塞 10 SIGUSR1 用户信号1 11 SIGSEGV 无效内存访问 12 SIGUSR2 用户信号2 13 SIGPIPE 管道破损,没有读端的管道写数据 14 SIGALRM alarm发出的信号 15 SIGTERM 终止信号 16 SIGSTKFLT 栈溢出 17 SIGCHLD 子进程退出 默认忽略 18 SIGCONT 进程继续 19 SIGSTOP 进程停止 不能被忽略、处理和阻塞 20 SIGTSTP 进程停止 21 SIGTTIN 进程停止,后台进程从终端读数据时 22 SIGTTOU 进程停止,后台进程想终端写数据时 23 SIGURG I/O有紧急数据到达当前进程 默认忽略 24 SIGXCPU 进程的CPU时间片到期 25 SIGXFSZ 文件大小的超出上限 26 SIGVTALRM 虚拟时钟超时 27 SIGPROF profile时钟超时 28 SIGWINCH 窗口大小改变 默认忽略 29 SIGIO I/O相关 30 SIGPWR 关机 默认忽略 31 SIGSYS 系统调用异常 用一句话来概括,信号(Signal)其实就是 Linux 进程收到的一个通知。这些通知产生的源头有很多种,通知的类型也有很多种。比如下面这几个典型的场景:...

May 18, 2022 · 1 min · 李昌

manjaro初始化配置

proxy 见:https://yangchnet.github.io/Dessert/posts/env/%E5%AE%89%E8%A3%85%E4%B8%8E%E9%85%8D%E7%BD%AEclash/ 系统更新 首先要换源 sudo pacman-mirrors -i -c China -m rank 在弹出的窗口中选择你要切换的源。 然后 sudo pacman -Syyu 安装yay包管理 sudo pacman -S yay vim 配置 见:https://yangchnet.github.io/Dessert/posts/linux/vim%E9%85%8D%E7%BD%AE/ 输入法配置 安装fcitx5(输入法框架) yay -S fcitx5-im 配置fcitx5的环境变量: vim ~/.pam_environment 内容为: GTK_IM_MODULE DEFAULT=fcitx QT_IM_MODULE DEFAULT=fcitx XMODIFIERS DEFAULT=\@im=fcitx SDL_IM_MODULE DEFAULT=fcitx 安装fcitx5-rime(输入法引擎) yay -S fcitx5-rime 安装fcitx5-gtk yay -S fcitx5-gtk # 不装的话,部分软件可能会出现不能输入中文的情况 安装rime-cloverpinyin(输入方案) yay -S rime-cloverpinyin 如果出现问题可能还需要做下面这步: yay -S base-devel 创建并写入rime-cloverpinyin的输入方案: vim ~/.local/share/fcitx5/rime/default.custom.yaml 内容为: patch: "menu/page_size": 5 schema_list: - schema: clover 可参考:https://github....

April 11, 2022 · 1 min · 李昌

一份好用的golang应用Dockerfile模板

# 编译环境FROMgolang:alpine as builder # 设置go环境变量ENV GO111MODULE=on \ GOPROXY=https://goproxy.cn,direct# 工作目录WORKDIR/app# 将项目拷贝到docker中COPY . .# 拉取包,编译RUN go mod tidy && CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags "-static"' -o hello-app .# 运行环境FROMscratch# 设置时区COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/ShanghaiENV TZ Asia/Shanghai# 安装证书RUN apt-get -qq update \ && apt-get -qq install -y --no-install-recommends ca-certificates curlWORKDIR/app# 将编译好的可执行文件从编译环境中拷贝到运行环境中COPY --from=builder /app/hello-app .# 启动ENTRYPOINT ["./hello-app"]# 端口EXPOSE10000

April 2, 2022 · 1 min · 李昌

Linux下的用户和用户组管理

1.概述 Linux 是多用户多任务操作系统,换句话说,Linux 系统支持多个用户在同一时间内登陆,不同用户可以执行不同的任务,并且互不影响。 例如,某台 Linux 服务器上有 4 个用户,分别是 root、www、ftp 和 mysql,在同一时间内,root 用户可能在查看系统日志、管理维护系统;www 用户可能在修改自己的网页程序;ftp 用户可能在上传软件到服务器;mysql 用户可能在执行自己的 SQL 查询,每个用户互不干扰,有条不紊地进行着自己的工作。与此同时,每个用户之间不能越权访问,比如 www 用户不能执行 mysql 用户的 SQL 查询操作,ftp 用户也不能修改 www 用户的网页程序。 不同用户具有不问的权限,毎个用户在权限允许的范围内完成不间的任务,Linux 正是通过这种权限的划分与管理,实现了多用户多任务的运行机制。 因此,如果要使用 Linux 系统的资源,就必须向系统管理员申请一个账户,然后通过这个账户进入系统(账户和用户是一个概念)。通过建立不同属性的用户,一方面可以合理地利用和控制系统资源,另一方面也可以帮助用户组织文件,提供对用户文件的安全性保护。 每个用户都有唯一的用户名和密码。在登录系统时,只有正确输入用户名和密码,才能进入系统和自己的主目录。 用户组是具有相同特征用户的逻辑集合。简单的理解,有时我们需要让多个用户具有相同的权限,比如查看、修改某一个文件的权限,一种方法是分别对多个用户进行文件访问授权,如果有 10 个用户的话,就需要授权 10 次,那如果有 100、1000 甚至更多的用户呢? 显然,这种方法不太合理。最好的方式是建立一个组,让这个组具有查看、修改此文件的权限,然后将所有需要访问此文件的用户放入这个组中。那么,所有用户就具有了和组一样的权限,这就是用户组。 将用户分组是 Linux 系统中对用户进行管理及控制访问权限的一种手段,通过定义用户组,很多程序上简化了对用户的管理工作。 2.用户和用户组的关系 用户和用户组的对应关系有以下 4 种: 一对一:一个用户可以存在一个组中,是组中的唯一成员; 一对多:一个用户可以存在多个用户组中,此用户具有这多个组的共同权限; 多对一:多个用户可以存在一个组中,这些用户具有和组相同的权限; 多对多:多个用户可以存在多个组中,也就是以上 3 种关系的扩展。 用户和组之间的关系可用下图表示:图 1 Linux用户和用户组 3. UID和GID(用户ID和组ID) 登陆 Linux 系统时,虽然输入的是自己的用户名和密码,但其实 Linux 并不认识你的用户名称,它只认识用户名对应的 ID 号(也就是一串数字)。Linux 系统将所有用户的名称与 ID 的对应关系都存储在 /etc/passwd 文件中。...

March 26, 2022 · 10 min · 李昌

概览MySQL篇三:锁、事务和隔离

极客时间《MySQL实战45讲》笔记 MySQL中的隔离级别 见本地事务的隔离 事务隔离的实现 事务之间的隔离是如何实现的? 在 MySQL 中,实际上每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚操作,都可以得到前一个状态的值。 假设一个值从 1 被按顺序改成了 2、3、4,在回滚日志里面就会有类似下面的记录。 当前值是 4,但是在查询这条记录的时候,不同时刻启动的事务会有不同的 read-view。如图中看到的,在视图 A、B、C 里面,这一个记录的值分别是 1、2、4,同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)。对于 read-view A,要得到 1,就必须将当前值依次执行图中所有的回滚操作得到。 但这种回滚日志不能一直保留,当系统判断,没有事务再需要用到这些回滚日志时,回滚日志会被删除,也即当系统中没有比这个回滚日志更早的read-view时。 如何尽量避免长事务 为什么要避免长事务? 长事务意味着系统里面会存在很老的事务视图。由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就会导致大量占用存储空间。 在 MySQL 5.5 及以前的版本,回滚日志是跟数据字典一起放在 ibdata 文件里的,即使长事务最终提交,回滚段被清理,文件也不会变小。 除了对回滚段的影响,长事务还占用锁资源,也可能拖垮整个库。 如何避免长事务? MySQL 的事务启动方式有以下几种: 显式启动事务语句, begin 或 start transaction。配套的提交语句是 commit,回滚语句是 rollback。 set autocommit=0,这个命令会将这个线程的自动提交关掉。意味着如果你只执行一个 select 语句,这个事务就启动了,而且并不会自动提交。这个事务持续存在直到你主动执行 commit 或 rollback 语句,或者断开连接。 因此,建议总是使用 set autocommit=1, 通过显式语句的方式来启动事务。 但这样会多一次“交互”,针对这个问题,使用commit work and chain 语法。 在 autocommit 为 1 的情况下,用 begin 显式启动的事务,如果执行 commit 则提交事务。如果执行 commit work and chain,则是提交事务并自动启动下一个事务,这样也省去了再次执行 begin 语句的开销。同时带来的好处是从程序开发的角度明确地知道每个语句是否处于事务中。...

March 23, 2022 · 2 min · 李昌

概览MySQL篇二:持久化

极客时间《MySQL实战45讲》笔记 什么是change buffer,有什么作用 当需要更新一个数据页时,如果数据页在内存中就直接更新,而如果这个数据页还没有在内存中的话,在不影响数据一致性的前提下,InnoDB 会将这些更新操作缓存在 change buffer 中,这样就不需要从磁盘中读入这个数据页了。在下次查询需要访问这个数据页的时候,将数据页读入内存,然后执行 change buffer 中与这个页有关的操作。通过这种方式就能保证这个数据逻辑的正确性。这个操作称为merge. 这里需要注意的是,change buffer看起来像是内存缓存一类的东西,但是change buffer是可以持久化的数据。也就是说,change buffer在内存中有拷贝,也会被写入到磁盘上。 如果将change buffer也存在磁盘上,而数据也是存储在磁盘上,那么change buffer相比直接读取磁盘数据快在哪里呢? 从磁盘读取一条记录,是随机读写,而写change buffer,是顺序读写。这二者的速度存在较大差异。随机读写由于存在磁头移道等物理操作,因此比较慢,但顺序读写比较快速。 change buffer的限制 change buffer只是暂时的将更新操作保存下来,而并没有去读取真正的数据。考虑以下情况,表中要求某一字段为唯一的,而在更新时不小心插入了一个与原有某数据重复的条目,这显然是不被允许的。 因此,当表中存在唯一索引、唯一值等限制,这时候就不能用change buffer了。只有普通索引和不存在值唯一性约束的列,才可以用change buffer。 change buffer用的是buffer pool中的内存,因此不能无限增大。change buffer 的大小,可以通过参数 innodb_change_buffer_max_size 来动态设置。这个参数设置为 50 的时候,表示 change buffer 的大小最多只能占用 buffer pool 的 50%。 change buffer的使用场景 merge 的时候是真正进行数据更新的时刻,而 change buffer 的主要目的就是将记录的变更动作缓存下来,所以在一个数据页做 merge 之前,change buffer 记录的变更越多(也就是这个页面上要更新的次数越多),收益就越大。 因此,对于写多读少的业务来说,页面在写完以后马上被访问到的概率比较小,此时 change buffer 的使用效果最好。这种业务模型常见的就是账单类、日志类的系统。 反过来,假设一个业务的更新模式是写入之后马上会做查询,那么即使满足了条件,将更新先记录在 change buffer,但之后由于马上要访问这个数据页,会立即触发 merge 过程。这样随机访问 IO 的次数不会减少,反而增加了 change buffer 的维护代价。所以,对于这种业务模式来说,change buffer 反而起到了副作用。...

March 23, 2022 · 4 min · 李昌

概览MySQL篇四:主备与高可用

极客时间《MySQL实战45讲》笔记 主备的基本原理 在状态 1 中,客户端的读写都直接访问节点 A,而节点 B 是 A 的备库,只是将 A 的更新都同步过来,到本地执行。这样可以保持节点 B 和 A 的数据是相同的。 当需要切换的时候,就切成状态 2。这时候客户端读写访问的都是节点 B,而节点 A 是 B 的备库。 那么从状态1切换到状态2的内部流程是什么样的? 备库 B 跟主库 A 之间维持了一个长连接。主库 A 内部有一个线程,专门用于服务备库 B 的这个长连接。一个事务日志同步的完整过程是这样的: 在备库 B 上通过 change master 命令,设置主库 A 的 IP、端口、用户名、密码,以及要从哪个位置开始请求 binlog,这个位置包含文件名和日志偏移量。 在备库 B 上执行 start slave 命令,这时候备库会启动两个线程,就是图中的 io_thread 和 sql_thread。其中 io_thread 负责与主库建立连接。 主库 A 校验完用户名、密码后,开始按照备库 B 传过来的位置,从本地读取 binlog,发给 B。 备库 B 拿到 binlog 后,写到本地文件,称为中转日志(relay log)。 sql_thread 读取中转日志,解析出日志里的命令,并执行。 binlog 的三种格式对比 主备复制依赖于bin log,那么bin log中是什么内容。...

March 23, 2022 · 5 min · 李昌