August 18, 2024
ShardingSphere Proxy 是 Apache ShardingSphere 的一个子项目,是一个基于 MySQL 协议的数据库中间件,用于实现分库分表、读写分离等功能。在使用过程中,遇到了一些问题,记录如下。
这里主要针对的是 分库分表 的使用场景。
问题概述
#
数据库往往是一个系统最容易出现瓶颈的点,当遇到数据库瓶颈时,我们可以通过数据拆分来缓解问题。数据拆分的方式通常分为横向拆分和纵向拆分,横向拆分即分库分表;纵向拆分即把一个库表中的字段拆分到不同的库表中去。这两种手段并不互斥,而是在实际情况中相辅相成。本文即是横向拆分相关内容。
- 常见的部署方式有哪些?
- 数据分片规则怎么配置?
- 数据分片数应该怎么确定?
- 数据分片后唯一索引还有用吗?
- 数据分片后数据迁移?
- 数据分片后如何确定实际执行 SQL 语句?
- 数据分片后后的查询优化?
0. 常见的部署方式
#
官方提供了两种部署方式:
- 单机部署:将 ShardingSphere Proxy 部署在单台服务器上,用于测试和开发环境。
- 集群部署:将 ShardingSphere Proxy 部署在多台服务器上,用于生产环境。集群模式下使用 zookeeper 来存储元数据。
关于元数据,元数据是 ShardingSphere Proxy 的核心,用于存储分库分表规则、读写分离规则等信息。
官方建议使用集群模式部署 生产环境的 ShardingSphere Proxy
如果不按照官方的指引,选择部署了多个 Standalone 模式的 ShardingSphere Proxy,那么需要注意“每个这样的 proxy 节点会有自己的元信息,他们之间并不互通”。这样一些情况下会出现节点之间元数据不一致的问题,参看如下测试:
# 启动 3 个 standalone 模式的 ShardingSphere Proxy
+-------+
| LB |
+-------+
|
|-------------|--------------|
| | |
+-------+ +-------+ +-------+
| Node1 | | Node2 | | Node3 |
+-------+ +-------+ +-------+
初始表结构如下:
...
February 26, 2024
NATS 就是一个消息中间件,提供了 Pub/Sub 核心数据流,并基于此构建了 Request/Reply API 和 JetStream 用来提供可靠的分布式存储能力,和更高的 QoS(至少一次 + ACK)。
1. 核心概念
#
序号 |
名词(ENG) |
名词(zh-CN) |
解释 |
1 |
Publish |
发布 |
发布动作,往 subject 中投递一条消息
|
2 |
Subscribe |
订阅 |
订阅动作,表示想要接受发布相应 subject 的消息 |
3 |
Subject |
主题 |
唯一标识,用于标识一种或者一类事件的概念。订阅时,可以使用 nats 约定的通匹配符来接收一类 subjects,如 orders.> 。 |
4 |
Core Nats |
NATS 核心 |
CORE NATS 提供了以下能力:
- PUB / SUB https://docs.nats.io/nats-concepts/core-nats/pubsub - Request / Reply https://docs.nats.io/nats-concepts/core-nats/reqreply - Queue Groups https://docs.nats.io/nats-concepts/core-nats/queue
提供 最多一次 的消息传递保证 |
5 |
Request / Reply |
请求 / 回复 |
NATS 基于 PUB / SUB 实现的对 request 异步回复的功能,依赖于消息中的 reply 字段。reply 是内置实现,随机生成一个 “inbox” subject。
订阅者中间也是存在 Queue Group 概念的。
注意:发起 request 如果没有reply,那么服务端会返回 No Responders 消息。 |
6 |
Queue groups |
队列组 |
订阅者使用同一个队列名称,它们就会成为一个队列组。每次队列组接收到消息时,只有队列组中_随机选择_的一个订阅者会消费一条消息,内置的负载均衡。 |
7 |
Message |
消息 or 事件 |
一条消息包含了以下内容:
- Subject. - A payload in the form of a byte array. - Any number of header fields. - An optional ‘reply’ address field. |
8 |
Subject wildcards
https://docs.nats.io/nats-concepts/subjects#wildcards |
主题通配符 |
订阅者可以使用这些通配符_通过单个订阅收听多个主题。_
注意:但发布者将始终使用完全指定的主题,而不使用通配符。 |
9 |
JetStream
https://docs.nats.io/nats-concepts/jetstream |
无 / 流 |
NATS 中一个功能特性,它是一个内置的分布式存储,在 CORE nats 的基础上扩展了更多的功能和更高的 QoS。功能上:
- Stream:将 NATS 消息存储在流中,提供多种保留策略、限制、丢弃策略和主题映射功能。 - Consumer: 让客户端应用订阅或拉取流中的消息,支持多种重放策略、确认机制和流控功能 - Persistence: 将流的数据复制到多个 NATS 服务器,提供容错能力和加密存储功能 - KV Store: 可以将消息与键关联,提供存储、检索、删除和监听键值变化的功能。 - Object Store: JetStream 可以存储任意大小的对象(如文件),提供分块、校验和、元数据等功能。
其中最核心(开发常用)的概念就是:stream 和 consumer。 |
10 |
Stream
https://docs.nats.io/nats-concepts/jetstream/streams |
流 |
流即是消息存储,它定义了消息的存储方式以及保留的限制。
更具体的内容参见 #5.1 Stream 配置解析 |
11 |
Consumer |
消费者 |
消费者作为客户端的接口,使用存储在流中的消息,跟踪客户端传递和确认的消息。Nats 同时支持 pull 和 push 两种消费模式;consumer 还提供了 durable 配置,用于持久化 consumer 消费信息(除非设置了 InactiveThreshold)
更具体的内容参见 #5.2 Consumer 配置解析 |
12 |
Replay / Redelivery |
|
|
13 |
Raft Group
https://docs.nats.io/running-a-nats-service/configuration/clustering/jetstream_clustering |
RAFT 组 |
对于特定内容达成一致的分组,nats 中有 meta, stream, consumer 几种组。
Meta: 全部节点都是组成员。负责:JetStream API + 集群管理。
Stream: (根据 replicas 配置选择组内的服务器成员)。负责:stream 数据
Consumer: (根据 stream group的成员来确定消费者组内的成员)。负责:消费者状态
|
14 |
KV Store |
键值存储 |
|
15 |
Object Store |
对象存储 |
|
2. 软件架构(集群模式)
#
Nats 自身是提供了多种运行方式:
...
January 27, 2022
需求和背景分析
#
一提到定时任务的使用场景,我们肯定能想到很多场景,比如:
- 每天晚上12点执行一次清理数据的任务
- 每天凌晨1点给符合条件的用户发送推广邮件
- 每个月10号结算工资
- 每隔5分钟检查一次服务器的状态
- 每天根据用户的配置,给用户发送站内消息提醒
- …
从常见的场景中,我们可以提炼出一些定时任务的特点:
序号 |
特点 |
说明 |
1 |
定时 |
执行时间有规则 |
2 |
可靠 |
可以延迟执行,但不能不执行;可以不执行,但是不能多执行 |
3 |
并发(可能) |
某些场景下,可以运行多个 cron 进程来提高执行效率 |
4 |
可执行 |
这个有点废话了,但是这个关系到 cronjob 的设计,因此在这里还是提出来 |
但是作为一个系统来说,我们需要更多的功能来提升用户体验,保证平台的可靠和稳定。我们设想下以下的场景:
- 定时任务已经触发了,但是有没有执行,执行结果是什么?
- 如果一个定时任务长时间运行,那么它正常吗?
- 一个定时任务还在运行,但是下一个触发时机又到来了,该怎么办?
- 如果服务器资源已经处于高位,那么要被触发的任务还触发吗?
- …
最后,基本的访问控制,权限分配和API设计,这些都是系统功能的一部分,但不作为本文的重点考虑对象。
一些概念
#
到这里我们可以提取一些概念,来帮助我们设计一个系统:
序号 |
名词 |
解释 |
1 |
CronJob |
定时任务实例,描述定时任务资源的一个实体 |
2 |
Job |
执行中的定时任务 |
3 |
Scheduler |
调度器,负责触发 CronJob 执行和监控 Job 的执行状态;或许还会存储一些 CronJob 的状态 |
4 |
JobRuntime |
job 运行时,准备 Job 运行时需要的资源; 可以参考 k8s CRI |
这里 JobRuntime 是抽象化的概念,因为 Job 可以是k8s上的POD,也可以是物理机的进程,还可以是一个进程中的coroutine。
...
October 23, 2018
声明:本文对etcd的原理,实现细节,性能等均不考虑,仅将etcd作为一个分布式的K-V存储组件。本文提价代码均在: github.com/yeqown/server-common/tree/master/framework/etcd
一个核心
#
etcd, 分布式Key-Value存储工具。详细资料由此去
两个对象
#
- 服务提供者(在测试环境中,我定义为单独的服务实例),也就是服务的提供者,需要向其他服务暴露自己的ip和端口,方便调用。
- 服务调用者(同样地,在测试环境中我定义为反向代理网关程序),也就是服务的调用者,需要获取到 可使用 地服务地址并调用。
关于服务注册与发现
#
就具体场景而言:我们的生产环境中使用了一个代理网关服务器,用于转发移动端和PC端的API请求,并完成其他功能。所有的服务实例配置都是硬编码在网关程序中,顶多就是抽离出来成了一个配置文件。这样做的缺点很明显:“非动态”。也就意味着,一旦有服务Down掉,那么用户访问则可能异常,甚至导致整个服务的崩溃;其次,需要对服务进行扩容的情况下,则需要先进行服务部署再更新网关程序,步骤繁琐且容易出错。
那么如果我们设计成为如下图的样子:
对于新添加的服务实例,只需要启动新的服务,并注册到etcd相应的路径下就行了。
注册:对于同一组服务,配置一个统一的前缀(如图上的"/specServer"),不同实例使用ID加以区分。
将现行服务改造成为上述模式需要解决的问题:
#
- etcd 配置安装
- 网关程序改造(监听etcd的节点夹子/prefix;适配动态的服务实例调用)
- 服务实例改造(注册服务实例到etcd;心跳更新;其他配套设施,异常退出删除注册信息)
etcd安装配置在github.com已经非常详细了。在这里贴一下我在本地测试时候启动的脚本(这部分是从etcd-demo获取到的,做了针对端口的改动):
#!/bin/bash
# For each machine
TOKEN=token-01
CLUSTER_STATE=new
NAME_1=machine1
NAME_2=machine2
NAME_3=machine3
HOST_1=127.0.0.1
HOST_2=127.0.0.1
HOST_3=127.0.0.1
CLUSTER=${NAME_1}=http://${HOST_1}:2380,${NAME_2}=http://${HOST_2}:2381,${NAME_3}=http://${HOST_3}:2382
# For machine 1
THIS_NAME=${NAME_1}
THIS_IP=${HOST_1}
etcd --data-dir=machine1.etcd --name ${THIS_NAME} \
--initial-advertise-peer-urls http://${THIS_IP}:2380 --listen-peer-urls http://${THIS_IP}:2380 \
--advertise-client-urls http://${THIS_IP}:2377 --listen-client-urls http://${THIS_IP}:2377 \
--initial-cluster ${CLUSTER} \
--initial-cluster-state ${CLUSTER_STATE} --initial-cluster-token ${TOKEN} &
# For machine 2
THIS_NAME=${NAME_2}
THIS_IP=${HOST_2}
etcd --data-dir=machine2.etcd --name ${THIS_NAME} \
--initial-advertise-peer-urls http://${THIS_IP}:2381 --listen-peer-urls http://${THIS_IP}:2381 \
--advertise-client-urls http://${THIS_IP}:2378 --listen-client-urls http://${THIS_IP}:2378 \
--initial-cluster ${CLUSTER} \
--initial-cluster-state ${CLUSTER_STATE} --initial-cluster-token ${TOKEN} &
# For machine 3
THIS_NAME=${NAME_3}
THIS_IP=${HOST_3}
etcd --data-dir=machine3.etcd --name ${THIS_NAME} \
--initial-advertise-peer-urls http://${THIS_IP}:2382 --listen-peer-urls http://${THIS_IP}:2382 \
--advertise-client-urls http://${THIS_IP}:2379 --listen-client-urls http://${THIS_IP}:2379 \
--initial-cluster ${CLUSTER} \
--initial-cluster-state ${CLUSTER_STATE} --initial-cluster-token ${TOKEN} &
对于程序的改造,鉴于服务较多且etcd操作流程大体一致,便简单包装了一下,项目地址见文首位置。
...
April 17, 2018
在开始之前必须明确的是,分布式和集群的区别,简单的说:
1.分布式是一种工作方式,把一个系统的不同功能放在不同的机器上;
2.集群是一种物理形态,同一个任务放在不同的机器上;
这样说,也并不是说这两个概念是完全不同,还互相独立,而是实际应用中相辅相成。
我们常说的负载均衡的背后就是集群部署,某些公司为了能扛住突然增长的流量,会采用加很多台服务器的方式来提高性能。看下图:
对于小公司来说,这样部署的方式在于:简单快速,成本在可接受范围。(如果愿意多花点时间进行代码重构和技术再选型或许效果会更好,当然估计要换个不懂技术的老板,然后忽悠他😊)
但是也不是没有隐患,一方面,集群部署虽然能提高系统的可用性,但是如果多台机器离线,会导致其他机器压力增大,如果严重超过机器负载能力,会导致越来越多的机器离线,一旦解决不及时便会导致整个应用崩溃。其次,集群部署为了保证数据的一致性,一般多采用相同数据源,因此集群并不能无限制扩张。
分布式系统的应用场景
#
分布式主要解决的问题是提升应用的负载能力。
分布式的优缺点
#
将一个系统的不同模块分别部署在不同的机器上。这里不得不说到微服务,微服务是把系统服务拆分成为独立的服务来部署(这里说的独立
,是数据独立和部署独立,不依赖于其他服务)。
从本质上来说微服务也是分布式部署的一种。只是相比一般分布式应用,拆分更加彻底。
优点
#
分成多个模块,并使用接口通信,低耦合;
团队协作开发,事先约定好接口,独立开发,效率更高;
部署更加灵活;
缺点
#
依赖网络通信,增加额外开发通信接口。
总结
#
以上说的都是垃圾,只希望打开思路。某邓同志说:能抗住压力的应用才是好应用。
参考资料
#
https://blog.csdn.net/boonya/article/details/55046568
https://www.jianshu.com/p/39c1e4ec0d63