首页
关于
留言
接口
搜索
首页
登录
登录
搜索
KAKA 梦很美
累计撰写
47
篇文章
累计收到
0
条评论
首页
栏目
首页
登录
页面
首页
关于
留言
接口
自定义幻灯片
2022-7-25
CentOS7.4 安装 Redis
下载 在这里博主安装的是 Redis 4,当然你也可以安装 Redis 5,安装方式是一样的。 cd /var/opt wget http://download.redis.io/releases/redis-4.0.13.tar.gz 解压 tar -zxvf redis-4.0.13.tar.gz 安装 cd redis-4.0.13/ make && make install 在安装完成后, 可执行程序已经添加到了 /usr/local/bin 启动服务 现在,我们可以在任何地方直接启动 redis 了。 首先尽量修改 redis 密码 vi /var/opt/redis-4.0.13/redis.conf # 找到 requirepass, 添加密码 redis123: requirepass redis123 ⚠️⚠️⚠️ 坑: 如果外网需要连接服务器 ,需要注意 redis.conf 配置里的 bind 127.0.0.1, 需要修改为服务器本机 IP (不是公网和私网),如果本机 IP 动态,那么可能需要配置为 bind 0.0.0.0。 并且你可能需要打开防火墙配置,增加 redis 端口允许外网访问。默认端口是 6379 启动命令 /usr/local/bin/redis-server /var/opt/redis-4.0.13/redis.conf 关于 php redis 扩展,以下的安装为安装扩展方式,就安装 php73 版本的 redis 扩展而言,执行下面命令就可以了 yum install php73-php-pecl-redis4
2022年-7月-25日
144 阅读
0 评论
操作系统
2022-7-21
MySQL 双主一致性架构优化
一、双主保证高可用 MySQL 数据库集群常使用 一主多从、主从同步、读写分离 的方式来扩充数据库的读性能,保证读库的高可用,但此时写库仍然是单点。 在一个 MySQL 数据库集群中可以设置两个主库,并设置双向同步,以冗余写库的方式来保证写库的高可用。 二、并发引发不一致 数据冗余会引发数据的一致性问题,因为数据的同步有一个时间差,并发的写入可能导致数据同步失败,引起数据丢失: 如上图所述,假设主库使用了 auto increment 来作为自增主键: 两个 MySQL-master 设置双向同步可以用来保证主库的高可用 数据库中现存的记录主键是1,2,3 主库1插入了一条记录,主键为4,并向主库2同步数据 数据同步成功之前,主库2也插入了一条记录,由于数据还没有同步成功,插入记录生成的主键也为4,并向主库1也同步数据 主库1和主库2都插入了主键为4的记录,双主同步失败,数据不一致 三、相同步长免冲突 能否保证两个主库生成的主键一定不冲突呢? 回答: 设置不同的初始值 设置相同的增长步长 就能够做到。 如上图所示: 两个 MySQL-master 设置双向同步可以用来保证主库的高可用 库1的自增初始值是1,库2的自增初始值是2,增长步长都为2 库1中插入数据主键为1/3/5/7,库2中插入数据主键为2/4/6/8,不冲突 数据双向同步后,两个主库会包含全部数据 如上图所示,两个主库最终都将包含1/2/3/4/5/6/7/8所有数据,即使有一个主库挂了,另一个主库也能够保证写库的高可用。 四、上游生成ID避冲突 换一个思路,为何要依赖于数据库的自增ID,来保证数据的一致性呢? 完全可以由 业务上游,使用统一的ID生成器,来保证ID的生成不冲突 如上图所示,调用方插入数据时,带入全局唯一ID,而不依赖于数据库的 auto increment,也能解决这个问题。 至于如何生成全局唯一,趋势递增的ID,可以搜索 分布式ID生成算法 相关内容查看。 五、消除双写不治本 使用 auto increment 两个主库并发写可能导致数据不一致,只使用一个主库提供服务,另一个主库作为 shadow-master,只用来保证高可用,能否避免一致性问题呢? 如上图所示: 两个 MySQL-master 设置双向同步可以用来保证主库的高可用 只有主库1对外提供写入服务 两个主库设置相同的虚IP,在主库1挂掉或者网络异常的时候,虚IP自动漂移,shadow master 顶上,保证主库的高可用 这个切换由于虚IP没有变化,所以切换过程对调用方是透明的,但在极限的情况下,也可能引发数据的不一致: 如上图所示: 两个 MySQL-master 设置双向同步可以用来保证主库的高可用,并设置了相同的虚IP 网络抖动前,主库1对上游提供写入服务,插入了一条记录,主键为4,并向 shadow master 主库2同步数据 突然主库1网络异常,keepalived 检测出异常后,实施虚IP漂移,主库2开始提供服务 在主键4的数据同步成功之前,主库2插入了一条记录,也生成了主键为4的记录,结果导致数据不一致 六、内网DNS探测 虚IP漂移,双主同步延时导致的数据不一致,本质上,需要在双主同步完数据之后,再实施虚IP偏移,使用内网DNS探测,可以实现 shadow master 延时高可用: 使用内网域名连接数据库,例如:db.58daojia.org 主库1和主库2设置双主同步,不使用相同虚IP,而是分别使用ip1和ip2 一开始db.58daojia.org指向ip1 用一个小脚本轮询探测ip1主库的连通性 当ip1主库发生异常时,小脚本delay一个x秒的延时,等待主库2同步完数据之后,再将db.58daojia.org解析到ip2 程序以内网域名进行重连,即可自动连接到ip2主库,并保证了数据的一致性 七、总结 主库高可用,主库一致性,一些小技巧: 双主同步是一种常见的保证写库高可用的方式 设置相同步长,不同初始值,可以避免 auto increment 生成冲突主键 不依赖数据库,业务调用方自己生成全局唯一ID是一个好方法 shadow master 保证写库高可用,只有一个写库提供服务,并不能完全保证一致性 内网DNS探测,可以实现在主库1出现问题后,延时一个时间,再进行主库切换,以保证数据一致性
2022年-7月-21日
156 阅读
0 评论
服务架构
2022-7-17
Redis 乐观锁解决高并发秒杀活动超卖问题
首先, 我们简单理解下乐观锁和悲观锁的概念。 悲观锁 顾名思义, 很悲观; 认为谁都可能对数据进行修改, 所以每次修改数据时都需要进行数据上锁。 乐观锁 顾名思义, 很乐观; 认为谁都可以对数据进行修改, 所以每次修改数据时都不会对数据进行上锁。但是数据修改提交时, 数据库会根据版本记录机制 在同一时间只能修改成功一个。 理解了这两个基础原理后, 其实我们就可以大概清楚 乐观锁和悲观锁其实都可以实现秒杀, 解决商品超卖的问题。但是悲观锁每次修改数据时都会对数据进行上锁, 比如setnx ; 而乐观锁 只需要判断数据版本是否发生变更, 如果没变更就修改成功, 反之就失败。 从性能上来讲, 显然乐观锁 更好。 我认为 Redis 乐观锁, 其实就是 WATCH 监视 和 TRANSACTION 事务 的结合体。 接下来, 我就来具体说说 Redis 乐观锁实现高并发下的秒杀活动 乐观锁 大多数是基于数据版本(VERSION)的记录机制实现的。即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 "version" 字段来实现读取出数据时,将此版本号一同读出。之后更新时,对此版本号加1。此时,将提交数据的版本号与数据库表对应记录的当前版本号进行比对,如果提交的数据版本号大于数据库当前版本号,则予以更新,否则认为是过期数据。Redis 中可以使用watch命令会监视给定的 key,当exec时候如果监视的 key 从调用 watch 后发生过变化,则整个事务会失败。也可以调用watch多次监视多个 key。这样就可以对指定的 key 加乐观锁了。注意watch的 key 是对整个连接有效的,事务也一样。如果连接断开,监视和事务都会被自动清除。当然了exec,discard,unwatch命令都会清除连接中的所有监视。 事务 Redis 中的事务(Transaction)是一组命令的集合。事务同命令一样都是 Redis 最小的执行单位,一个事务中的命令要么都执行,要么都不执行。Redis 事务的实现需要用到 MULTI 和 EXEC 两个命令,事务开始的时候先向 Redis 服务器发送 MULTI 命令,然后依次发送需要在本次事务中处理的命令,最后再发送 EXEC 命令表示事务命令结束。Redis 的事务是下面4个命令来实现的。 multi 开启 Redis 的事务,置客户端为事务态。 exec 提交事务,执行从multi到此命令前的命令队列,置客户端为非事务态。 discard 取消事务,置客户端为非事务态。 watch 监视键值对,作用是 如果事务提交exec时发现监视的监视对发生变化,事务将被取消。 最后, 我用 Golang 代码简单实现基于乐观锁的秒杀, 其他语言也是一样的原理。 package main import ( "context" "fmt" redisv8 "github.com/go-redis/redis/v8" "github.com/raylin666/go-cache/redis" "strconv" "time" ) /** 模拟场景: 商品总数量为30个, 单次抢购的用户数量为50人, 每个人只能抢到1个商品。 **/ var ( // 商品总数量 total_goods = 30 // 已被抢购的商品数量缓存 Key goods_key = "goods_numbers" // 已抢到商品的用户缓存 Key user_exists_key = "user_success" ) func redisConnect() *redis.Client { var opts = new(redis.Options) opts.Network = "tcp" opts.Addr = "127.0.0.1:6379" opts.Password = "123456" opts.DB = 0 client, err := redis.New(context.TODO(), opts) if err != nil { panic(err) } return client } // 抢购逻辑处理 func watchRushToBuy(client *redis.Client, userId int) error { return client.Watch(func(tx *redisv8.Tx) error { // 用户已经抢到商品, 不能重复抢购 if tx.HExists(context.TODO(), user_exists_key, strconv.Itoa(userId)).Val() { fmt.Println(fmt.Sprintf("%d 用户已经抢到商品, 不能重复抢购", userId)) return nil } vint, getErr := tx.Get(context.TODO(), goods_key).Int() if getErr != nil && getErr != redisv8.Nil { return nil } // 不能超过商品总数量 if vint >= total_goods { fmt.Println(fmt.Sprintf("%d 用户您好, 记得下次早点来哦, 商品被抢完啦!", userId)) return nil } // 抢购事务处理 _, txErr := tx.TxPipelined(context.TODO(), func(pipeliner redisv8.Pipeliner) error { txCmd := pipeliner.Incr(context.TODO(), goods_key) if txCmd.Err() != nil { return txCmd.Err() } hsetCmd := pipeliner.HSet(context.TODO(), user_exists_key, userId, 1) // 设置用户领取成功状态失败, 回退商品数量 if hsetCmd.Err() != nil { pipeliner.Decr(context.TODO(), goods_key) return hsetCmd.Err() } return nil }) if txErr != nil { return txErr } // 抢购成功, 处理抢购后的逻辑流程 ... fmt.Println(fmt.Sprintf("恭喜 ID 为 %d 的用户抢到啦", userId)) return nil }, goods_key) } func main() { // 连接 Redis client := redisConnect() tryFunc := func(userId int) { // 未抢购成功的用户可重试抢购 for j := 0; j < 3; j++ { // Redis 监听 Key 变化并开启事务处理 watchErr := watchRushToBuy(client, userId) // 重试抢购 if watchErr != nil { fmt.Println(fmt.Sprintf("%d 用户重试抢购失败 - %v", userId, watchErr)) continue } return } } // 模拟用户的并发请求 (不论执行多少次, 或者并发数量加大, 都能正常抢购且不会超卖商品) for i := 0; i < 50; i++ { go func(i int) { // 抢购逻辑 tryFunc(i) }(i) } time.Sleep(1 * time.Second) value, _ := client.Conn.Get(context.TODO(), goods_key).Int64() fmt.Println(value) } 下图是部分抢购中、已抢购完、Redis 已抢到的用户数据
2022年-7月-17日
219 阅读
0 评论
服务架构
2022-4-23
关于 Redis-Pipeline 引发的思考
Pipeline: 顾名思义, 流水线(管道)的意思。为什么要讨论它呢? (可能是因为它比较香吧) 在 Redis 的操作命令里其实并没有 pipeline, 但是 Redis 又支持 pipeline 的, 而且在各个语言的 Client 中都有相关的实现。 需要注意的是集群模式对于 Pipeline 不太友好,因为 Pipeline 要操作的 key 可能在不同的哈希槽上,此时就需要进行请求的转发,但是这是与 Pipeline 的思想相违背的,所以集群模式下不太支持使用 Pipeline 的操作。 我们不妨先看看平时操作 Redis 命令的执行流程: 那么执行N次命令就需要: N 次时间 = N 次网络时间 + N 次命令时间 可以看到,如果执行 N 次的话(比如 N 次 set 操作),时间开销是非常大的。不过似乎也符合常理, 因为平时不都是这么操作的吗? 那可能只是因为没考虑网络开销。 由于命令时间非常短, 影响时间开销的主要是网络时间。而 Pipeline 正是解决了网络开销问题, 客户端缓冲一堆需要执行的命令(命令排队), 然后一次发送执行。这样的话,时间开销就变为: 1 次 Pipeline(N 条命令) = 1 次网络时间 + N 次命令时间 是不是很棒! 但是需要注意的是: Pipeline 里的操作命令不能过多, 否则会影响客户端的等待时间, 影响网络性能。 写到最后, 本文的目的其实并不是告诉读者 Redis 的 Pipeline 操作, 而是能 Get 到其中的设计思想才是重中之重。在设计模式中也有类似思想, 感兴趣的可以研究下 管道模式 。
2022年-4月-23日
187 阅读
0 评论
Redis
2022-2-17
单体架构和微服务系统架构优缺点
随着业务的发展我们的项目从简单的单体结构逐渐的演化成微服务结构, 我们为什么要拆分成微服务呢?那我们就来说说微服务和单体架构的优缺点吧。 单体架构 单体架构优点 部署容易: 如 php 写的项目,只要一个文件夹复制到支持 php 的环境就可以了,java 只需要一个 jar 包 测试容易: 我们整体项目只要改了一个地方马上就可以测试得出结果 负载均衡就可以解决: 快速部署多个一模一样的项目在不同的机器运行分流 技术单一: 项目不需要复杂的技术栈,往往一套熟悉的技术栈就可以完成开发 用人成本低: 单个程序员可以完成业务接口及数据库的整个流程 单体架构缺点 部署的问题: 对于 php 来说这点还好,但是对于 java 的项目来说,我们需要重新打包整个项目耗费的时间是很长的 代码维护: 由于所有的代码都写在一个项目里面,要想要修改某一个功能点那么需要对项目的整体逻辑和设计有较深的理解,否则代码耦合严重,导致维护难,特别对于新入职的员工来说这将是最容易出现问题的地方 开发效率低: 随着项目需求的不断改变和新的功能新增,老旧的代码又不敢随便删除,导致整个项目变得笨重,这将会增加你阅读代码的时间 系统启动慢: 一个进程包含了所有的业务逻辑,涉及到的启动模块过多,导致系统的启动时间周期过长 可伸缩性差: 系统的扩容只能对这个应用进行扩容,不能做到对某个功能点进行扩容。在高并发的情况下,我们往往不是整个项目的每一个功能都处于高流量高请求的情况下的,很多时候都是某一个功能模块使用的人数比较多,在单体结构下我们没有办法针对单个功能实现分布式扩展,必须整个项目一起部署 系统错误隔离性差: 可用性差,任何一个模块的错误均可能造成整个系统的宕机 线上问题修复周期长: 任何一个线上问题修复需要对整个应用系统全面升级 微服务架构 在2014年被提出,现在国内很多公司已经使用,微服务是一种架构设计,并不是说什么框架或者代替什么。微服务做的事情是按照项目颗粒度进行服务的拆分,把模块单独拿出来做成每一个单独的小项目。微服务的主要特点有:每一个功能模块是一个小项目、独立运行在不同进程或者机器上、不同功能可以又不同的人员开发独立开发不松耦合、独立部署不需要依赖整体项目就可以启动单个服务、分布式管理。每一个服务只要做好自己的事情就好了。在设计微服务的时候还需要考虑到数据库的问题,是所有微服务使用共同一个数据库还是每一个服务单个数据库。 服务调用 每个服务间也可以相互调用(即:双向流调用), 可以通过 RPC、GRPC 这类远程调用方式。调用者与服务之间的通讯,传输协议可基于 TCP、UDP 或者 HTTP 实现,但是更推荐选择 TCP。 例如调用者需要调用商品的服务就可以通过 RPC 或者 RESTful API 来调用,那么 RPC 调用和 RESTful API 两者之间的区别在哪呢? TCP 支持长连接,当调用服务的时候不需要每次都进行三次握手才实现。从性能和网络消耗来说 RPC 都具备了很好的优势 RESTful API 基于 HTTP 的,也就是说每次调用服务都需要进行三次握手建立起通信才可以实现调用,当我们的并发量高的时候这就会浪费很多带宽资源 服务对外的话采用 RESTful API 会比 RPC 更具备优势,因此看自己团队的服务是对内还是对外 RPC 最主要的作用就是用于服务调用 微服务架构优点 易于开发和维护: 一个微服务只会关注一个特定的业务功能,所以他的业务清晰,代码量少,开发和维护单个微服务相当简单,而整个应用是若干个微服务构建而成的,所以整个应用也被维持在一个可控状态。拆分业务,把整体大项目分割成不同小项目运行在不同进程或者机器上实现数据隔离 技术栈不受限: 每个服务可以由不同的团队或者开发者进行开发,外部调用人员不需要操心具体怎么实现的,只需要类似调用自己方法一样或者接口一样按照服务提供者给出来的参数传递即可 独立部署: 每一个服务独立部署,部署一个服务不会影响整体项目,如果部署失败最多是这个服务的功能缺失,并不影响其他功能的使用 按需部署: 针对不同的需求可以给不同的服务自由扩展服务器,根据服务的规模部署满足需求的实例 局部修改容易部署: 当一个服务有新需求或者其他修改,不需要修改整体项目只要管好自己的服务就好了 单个微服务启动较快: 单个微服务代码量较少,所以启动会比较快 按需收缩: 可根据需求,实现细粒度的扩展,例如:系统中的某个微服务遇到了瓶颈,可以结合这个微服务的业务特点,增加内存,升级CPU或者增加节点 可以承受高并发 微服务架构缺点 运维要求较高: 更多的服务意味着更多的运维投入,在单体架构中,只需要保证一个应用的正常运行,而在微服务中,需要保证几十甚至几百个服务正常运行与协作,这给运维带来了很大的挑战 分布式固有的复杂性: 使用微服务构建的是分布式系统,对于一个分布式系统,系统容错,网络延迟等都会带来巨大的挑战 接口调整成本高: 微服务之间通过接口进行通信,如果修改某一微服务API,则所有使用该接口的微服务都需要调整
2022年-2月-17日
193 阅读
0 评论
基础原理
2022-1-18
Mac brew 安装 Golang
最近分析了一下主流市场的编程语言,发现 Golang 语言还算比较火热(有上升势头),准备开始探索一下。 安装 brew install go 安装过程大同小异,会自动安装 Golang 的最新稳定版本,因为我已经安装好了,没有截图,所以剽窃了一张别人的,好让大家有个参考 出现上面的结果就表示安装成功了,我们在终端输入 go version 查看我们的安装版本,我的显示 go version go1.12.5 darwin/amd64,表示我安装的是 v1.12.5 版本。 从安装提示中可以看出需要设置 GOPATH 和 GOROOT 的环境变量,以及设置 PATH. 温馨提示: Go >1.13 版本之后支持通过设置环境变量 GOPROXY 来修改代理地址, 默认代理服务器 https://proxy.golang.org 在国内访问经常出现 timeout 可以通过设置国内代理来加速下载, 在终端执行即可: go env -w GOPROXY=https://goproxy.cn,direct 配置GOPATH 查看 Golang 的环境变量设置的命令 go env 我们需要配置我们的环境变量,在 ~ 目录下使用 ll -a 命令查看是否有 .bash_profile 文件,如果没有,则创建一个,有就 vi 编辑,下面给大家两种示例(我的源码库没有跟安装目录放在一起): 1) 单源码库环境变量配置 # export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib export GOPATH=/Users/linshan/Web/golang/go export GOBIN=$GOPATH/bin export PATH=$PATH:$GOBIN 2) 单源码库环境变量配置 # export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib export GOPATH=/Users/linshan/Web/golang/go (:自由添加目录,其他不变) export GOBIN=$GOPATH/bin export PATH=$PATH:${GOPATH//://bin:}/bin 使修改立刻生效: source ~/.bash_profile 再执行 go env 就可以看到环境变量配置了。 实例说明 GOPATH:为我们开发常用的目录,建议不要和 Go 的安装目录一致,在该文件夹下又有三个文件夹:src、pkg、bin。 这里src是自己新建的, pkg和bin是后面生成的 src:主要存放我们的源代码 bin:存放编译后生成的可执行文件,可以自己执行 pkg: 编译后生成的文件(.a文件)(非 main 函数的文件在 go install 后生成) GOBIN:是 GOPATH 下的bin目录 PATH:环境变量,需要go-bin目录加入到path路径下,生成可执行文件就可以直接运行了。 基础 Golang 安装到此啦~
2022年-1月-18日
207 阅读
0 评论
操作系统
2021-12-12
CentOS7.4 安装高性能消息中间件 RabbitMQ 服务
之所以写下这篇文章,是因为在用 CentOS 7.4 安装 RabbitMQ 时遇到坑了,特此记录以便查阅。 RabbitMQ 是实现了 高级消息队列协议(AMQP) 的开源消息代理软件(亦称面向消息的中间件)。RabbitMQ 服务器是用 Erlang 语言编写的,而群集和故障转移是构建在开放电信平台框架上的。所有主要的编程语言均有与代理接口通讯的客户端库。 废话不多说,直接奔实践去: RabbitMQ 现在已经更新到了 3.8 版本,但是我们在这安装的是 3.7.9,其实都是一样的。安装前有件事需要告知大家,就是安装 RabbitMQ 之前需要安装 erlang, 而且一个很关键的点就是 RabbitMQ 的安装版本必须匹配 erlang 版本。如下截图是版本对应表,大家可以到 RabbitMQ 官方(地址) [查看版本对应关系]。 官方的提示很明显, RabbitMQ 3.7.9 版本需要安装 >20.3.x <21.3.x 版本的 erlang。 首先查看下 yum 的 erlang 版本 yum info erlang 如果显示的Version不是我们想要的版本,则 vim /etc/yum.repos.d/rabbitmq-erlang.repo [rabbitmq-erlang] name=rabbitmq-erlang baseurl=https://dl.bintray.com/rabbitmq-erlang/rpm/erlang/21/el/7 gpgcheck=1 gpgkey=https://dl.bintray.com/rabbitmq/Keys/rabbitmq-release-signing-key.asc repo_gpgcheck=0 enabled=1 然后执行 yum clean all yum makecache 再一次查看erlang版本 yum info erlang OK! 遇到的坑来了,就是安装erlang时候。在执行 yum install erlang 时报错。大概意思是说 /usr/lib64/erlang 冲突, 于是我怀疑系统已经有安装好的 erlang, 执行 yum list installed |grep erlang 发现出来一大堆的 erlang 及扩展依赖,还是 22.x 版本的。 所以我们需要一个个卸载 ,执行命令 yum remove 包名 卸载到主依赖时会自动卸载其他依赖,在这里主依赖是yum remove erlang-erts-22.0.1-1.el7.x86_64 各位也可以试试。 我们再次执行 yum install erlang 安装成功!完成解坑!!! 接下来我们安装 RabbitMQ, 个人喜好, 我的 RabbitMQ rpm放在 /var/opt/rabbitmq目录,你们随意。 mkdir /var/opt/rabbitmq cd /var/opt/rabbitmq 下载 RabbitMQ 源 wget https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.7.9/rabbitmq-server-3.7.9-1.el6.noarch.rpm 安装命令 yum install rabbitmq-server-3.7.9-1.el6.noarch.rpm 安装完成! 接下来我们需要做的事: 启动 RabbitMQ 服务 rabbitmq-server start 设置后台启动命令 rabbitmq-server -detached 查看 RabbitMQ 服务状态 rabbitmqctl status 关闭 RabbitMQ 服务 rabbitmqctl stop 设置为后台守护进程启动并且随系统启动而启动 sudo chkconfig rabbitmq-server on 查看 RabbitMQ 已安装的插件命令 rabbitmq-plugins list MQ 不单止一个进程哦,多执行 ps aux|grep rabbitmq 看看,特别是kill的时候。 接下来,如果直接访问是访问不到 Web 页面的,需要我们安装一个 web 管理端插件 rabbitmq-plugins enable rabbitmq_management 防火墙开放 RabbitMQ 的 15672 和 5672 端口 我这里使用的是 iptables,步骤如下: vi /etc/sysconfig/iptables -A INPUT -m state --state NEW -m tcp -p tcp --dport 15672 -j ACCEPT // 增加一行配置 // 重启 systemctl restart iptables.service 先别急着登录 RabbitMQ 管理面板哦。因为 RabbitMQ 一般默认有一个guest 用户和一个 administrator 管理员用户,但是 guest 用户只可以在本机通过 localhost 的方式访问,如果要通过别的机器登陆 Web 访问就得新加一个用户并且授权。 1.查询用户列表 rabbitmqctl list_users 2.新增一个用户 rabbitmqctl add_user 用户名 用户密码 3.赋予其administrator角色 rabbitmqctl set_user_tags 用户名 administrator 4.设置其用户权限 rabbitmqctl set_permissions -p / 用户名 ".*" ".*" ".*" 5.修改用户密码 rabbitmqctl change_password 用户名 用户密码 6.删除一个用户 rabbitmqctl delete_user 用户名 然后访问 http://ip:15672 就可以访问 RabbitMQ Web 管理界面 (这个是登录后的界面) 文章的最后,推荐安装个 RabbitMQ 延迟队列插件 rabbitmq_delayed_meaage_exchange。 如果rabbitmq-plugins list 命令发现插件没安装 cd /var/opt/rabbitmq // 下载插件 wget https://dl.bintray.com/rabbitmq/community-plugins/3.7.x/rabbitmq_delayed_message_exchange/rabbitmq_delayed_message_exchange-20171201-3.7.x.zip // 解压插件 unzip rabbitmq_delayed_message_exchange-20171201-3.7.x.zip // 放置插件文件夹(我这边的是安装在 /usr/lib/rabbitmq/lib/rabbitmq_server-3.7.9/plugins, 这个路径得看各位安装 rabbitmq 位置了) mv rabbitmq_delayed_message_exchange-20171201-3.7.x.ez /usr/lib/rabbitmq/lib/rabbitmq_server-3.7.9/plugins // 启用插件 rabbitmq-plugins enable rabbitmq_delayed_message_exchange 搞定!!! 可以让你的消息队列飞起来啦~
2021年-12月-12日
164 阅读
0 评论
操作系统
2021-9-21
IM 消息服务架构
IM消息架构主要有: 消息 redis 缓存队列及用户信息 memcache, 消息的数据落地 mysql 消息发送 离线消息服务 过期消息服务 消息Redis缓存队列 服务端落地队列 客户端通过 HTTPS 请求通过接口将IM消息数据传递到服务器,服务器把消息数据写入随机分配的 redis 队列中 服务器后台 loop 服务不停的读取前面的消息队列,从中取出数据,进行分析落地(将消息数据添加到不同分库中,并添加到消息发送队列中(根据消息中的数据把群中每个用户的消息都写入消息发送队列)。 消息发送 客户端通过 scoket(TCP) 连接到服务器的 swoole 服务,swoole 有一个 task 任务,这个任务就是一个循环 loop 服务,从前面存储到的消息发送队列中取出数据,根据数据中的消息接收者发送给消息接收者,从存储用户信息的 memcache 中取出用户的fd和from_id,如果有说明用户在线,直接发送给用户,并且存入离线消息队列中,以免客户端接收信息失败用于信息确认,如果用户不在线直接存入离线消息队列中。用户接受到消息后会向服务器发送一个命令的数据包确认收到消息,服务器收到后从离线消息中删除该条消息。 离线消息服务 客户端通过scoket(TCP)连接到服务器的swoole服务,用心跳保持长连接,客户端成功连接后swoole会给客户端分配一个fd(TCP客户端连接的文件描述符),from_id(TCP连接所在的Reactor线程ID),我们将用户的fd,from_id等用户信息存储到memcache中。同时客户端在连接成功后会发送一个带有请求离线消息的命令的数据包来请求离线消息,我们就将用户存储在离线消息redis中的消息发送给客户端。 过期消息服务 放在离线消息redis中的数据有一个过期处理,超过一定期限的消息将不再发送给用户,将过期的消息从redis中删除,用户在客户端中通过HTTPS接口请求还是可以从数据库中拉取到以往的消息。 IM整体架构图
2021年-9月-21日
172 阅读
0 评论
消息中间件
2021-9-11
Redis 开发准则
命名规范 [强制] 键名可读性和可管理性设计(防止 Key 冲突) <项目名称>:<资源类型>[:<子资源类型>]:资源标识 例如: order:user:info:1 键名建议长度控制在一定范围内, 键名需要尽量做到 “见其名知其意”. [强制] Key 名不要包含特殊字符,如空格、换行、单双引号以及其他转义字符 使用规范 [强制] 正常情况下, 键必须设置过期时间 [强制] (热点数据) 高频访问数据需存入缓存 [强制] 如果缓存的是大文本, 当文本长度过大, 类似的键数量多, 会有较大的内存占用. 并且在访问量高时, 会导致带宽使用过高甚至占满. 因此当文本大小到一定程度时, 例如数十 kb 甚至数百 kb时, 可以考虑使用先压缩, 再缓存 [强制] 根据业务场景合理使用不同的数据类型, 切勿过度使用缓存, 必须遵循合理性 [强制] 禁止使用模糊匹配keys、flushall命令 [强制] 慎用hmgetall, hgetall, zrange, smembers, lrange 命令, 如果操作数据量过大,将会导致阻塞。有遍历需求可以使用 hscan、sscan、zscan 代替 [强制] 注意 缓存穿透, 缓存击穿, 缓存雪崩 问题
2021年-9月-11日
157 阅读
0 评论
规范准则
2021-8-19
聊聊 MySQL 索引数据结构
前言 当你遇到了一条慢 SQL 需要进行优化时,你第一时间能想到的优化手段是什么? 大部分人第一反应可能都是添加索引,在大多数情况下面,索引能够将一条 SQL 语句的查询效率提高几个数量级。 索引的本质:用于快速查找记录的一种 数据结构 索引的常用数据结构: 二叉树 红黑树 Hash 表 B-TREE (B树,并不叫什么B减树) B+TREE 数据结构图形化: 点击查阅 索引数据结构 大家知道 SELECT * FROM t WHERE col = 88 这么一条 SQL 语句如果不走索引进行查找的话,正常地查就是全表扫描:从表的第一行记录开始逐行找,把每一行的 col 字段的值和 88 进行对比,这明显效率是很低的。 而如果走索引的话,查询的流程就完全不一样了(假设现在用一棵平衡二叉树数据结构存储我们的索引列) 此时该二叉树的存储结构(Key - Value):Key 就是索引字段的数据,Value 就是索引所在行的磁盘文件地址。 当最后找到了 88 的时候,就可以把它的 Value 对应的磁盘文件地址拿出来,然后就直接去磁盘上去找这一行的数据,这时候的速度就会比全表扫描要快很多。 但实际上 MySQL 底层并没有用二叉树来存储索引数据,是用的 B+TREE(B+树) 为什么不采用二叉树 假设此时用普通二叉树记录 id 索引列,我们在每插入一行记录的同时还要维护二叉树索引字段 此时找 id = 7 这一行记录时找了 6 次,和我们全表扫描也没什么很大区别。显而易见,二叉树对于这种依次递增的数据列其实是不适合作为索引的数据结构。 为什么不采用 Hash 表 Hash 表:一个快速搜索的数据结构,搜索的时间复杂度 O(1) Hash 函数:将一个任意类型的 key,可以转换成一个 int 类型的下标 假设此时用 Hash 表记录 id 索引列,我们在每插入一行记录的同时还要维护 Hash 表索引字段。 这时候开始查找 id = 7 的树节点仅找了 1 次,效率非常高了。 但 MySQL 的索引依然不采用能够精准定位的Hash 表。因为它不适用于范围查询 为什么不采用红黑树 红黑树 是一种特化的 AVL树(平衡二叉树),都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡;若一棵二叉查找树是红黑树,则它的任一子树必为红黑树。 假设此时用红黑树记录 id 索引列,我们在每插入一行记录的同时还要维护红黑树索引字段。 插入过程中会发现它与普通二叉树不同的是当一棵树的左右子树高度差 > 1 时,它会进行自旋操作,保持树的平衡。 这时候开始查找 id = 7 的树节点只找了 3 次,比所谓的普通二叉树还是要更快的。 但 MySQL 的索引依然不采用能够精确定位和范围查询都优秀的红黑树。 因为当 MySQL 数据量很大的时候,索引的体积也会很大,可能内存放不下,所以需要从磁盘上进行相关读写,如果树的层级太高,则读写磁盘的次数(I/O交互)就会越多,性能就会越差。 红黑树 目前的唯一不足点就是树的高度不可控,所以现在我们的切入点就是树的高度。 目前一个节点是只分配了一个存储 1 个元素,如果要控制高度,我们就可以把一个节点分配的空间更大一点,让它横向存储多个元素,这个时候高度就可控了。这么个改造过程,就变成了 B-TREE B-TREE 和 B+TREE BTREE 是一颗多路平衡查找树 定义如下 每个节点最多有m-1个关键字(可以存有的键值对) 根节点最少可以只有1个关键字 非根节点至少有m/2个关键字 每个节点中的关键字都按照从小到大的顺序排列,每个关键字的左子树中的所有关键字都小于它,而右子树中的所有关键字都大于它 所有叶子节点都位于同一层,或者说根节点到每个叶子节点的长度都相同 每个节点都存有索引和数据,也就是对应的 Key 和 Value 根节点的关键字数量范围:1 <= k <= m-1,非根节点的关键字数量范围:m/2 <= k <= m-1 B-TREE 的查找其实和二叉树很相似 二叉树是每个节点上有一个关键字和两个分支,B-TREE 上每个节点有 k 个关键字和 (k + 1) 个分支。 二叉树的查找只考虑向左还是向右走,而 B-TREE 中需要由多个分支决定。 B-TREE 的查找分两步 首先查找节点,由于 B-TREE 通常是在磁盘上存储的所以这步需要进行磁盘IO操作 查找关键字,当找到某个节点后将该节点读入内存中然后通过顺序或者折半查找来查找关键字。若没有找到关键字,则需要判断大小来找到合适的分支继续查找 B-TREE 和 B+TREE 的相同点 根节点至少一个元素, 根节点至少有两个节点 每个中间节点都包含k-1个元素和k个孩子,其中m/2<=k<=m 每一个叶子节点都包含k-1个元素,其中m/2<=k<=m 所有的叶子节点都位于同一层 每个节点中的元素从小到大排列,节点当中k-1个元素正好是k个孩子包含的元素的值域划分 B+TREE 是 BTREE 的变种, 拥有新特性 有k个子树的中间节点包含有k个元素(B树中是k-1个元素),每个元素不保存数据,只用来索引,所有数据都保存在叶子节点 所有的叶子节点中包含了全部元素的信息,及指向含这些元素记录的指针,且叶子节点本身依关键字的大小自小而大顺序链接 所有的中间节点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素 B+TREE 比 BTREE 的优势 单一节点存储更多的元素,使得查询的IO次数减少 所有查询都要查找到叶子节点,查询性能稳定 所有叶子节点形成有序链表,便于范围查询 我们具体来看一下B+树结构 我们可以直观地看出节点之间含有重复元素,叶子节点还用指针连在了一起,注意: 叶子节点其实还是个双向指针, 图中未体现右到左的指针而已。每个父节点中的元素都出现在了子节点中,是子节点中的最大(或最小)元素 如上图,根节点中元素8是子节点2,5,8的最大元素,也是叶子节点6,8的最大元素,根节点元素15是子节点11,15的最大元素,也是叶子节点13,15的最大元素。需要注意的是,根节点的最大元素(此处是15)等同于整个B+树的最大元素。无论插入或删除多少元素,始终要保持最大元素在根节点当中。至于叶子节点,由于父节点的元素都出现在了子节点,所以叶子节点包含了全部元素信息。并且每个叶子节点都带有指向下一个节点的指针,形成了一个有序链表 B+树还有一个至关重要的特点,那就是”卫星数据“的位置,所谓 ”卫星数据“,指的是索引元素所指向的数据记录(比如数据库中的某一行) 在B树中,无论中间节点还是叶子节点都带有卫星数据 而在B+树中,只有叶子节点带有卫星数据,其余中间节点仅仅是索引,没有任何数据关联 在数据库的聚集索引中,叶子节点直接包含卫星数据,在非聚集索引中,叶子节点带有指向卫星数据的指针 B+树被设计如此,优势主要体现在查询性能上 单元素查询的时候,B+树会自顶向下逐层查找节点,最终找到匹配的叶子节点,比如我们查找元素3 由此看来,B树的范围查询确实有点繁琐,反观B+树的范围查询则简单的多,只需在链表上做遍历即可 如此看来B+树的链表遍历要比B树的中序遍历简单很多
2021年-8月-19日
134 阅读
0 评论
MySQL
1
2
3
4
5