做者 | 董鹏 阿里巴巴技术专家java
好处:实现跨团队的解耦,实现更高的并发(目前单机只能实现 c10k)不用再拷贝代码,基础服务能够公用,更好的支持服务治理,可以更好的兼容云计算平台。mysql
客户端还须要维护负载均衡、超时处理、链接池管理等,链接池维护了和多个 server 的链接,靠此作负载均衡,当某个服务器宕机后去除该链接。请求上下文维护了请求 ID 和回调函数,超时的请求当回复报文到达后因为找不到请求上下文就会丢弃。linux
前者节省空间,后者反序列化速度快,目前的序列化框架也是在反序列化时间和占用空间之间权衡。有点相似哈夫曼编码,或者数据库怎么存储一行一行的数据。git
通常有 3 种模式:ajax
zookeeper 不适合作注册中心的缘由:zookeeper 为了一致性牺牲了可用性,可是注册中心实际上对一致性要求并不高,不一致产生的后果也就是某个服务下线了而客户端并不知道,可是客户端经过重试其余节点就能够了。另外当发生网络分区的时候,若是超过半数节点挂了,zookeeper 就不可用,可是实际上它应该仍然能够对它所在机房的节点提供注册服务,例如三个机房分别放了 2 台、2 台、1 台,若是各个机房之间网络断了,可是机房内部上是通的,这样注册中心不可用即便内部节点也不能服务了。redis
zookeeper 并非严格的一致性,它支持读写分离,其它节点收到写请求会转发给 master 节点,而其它节点能够支持读请求,当数据尚未从主节点复制过来的时候读到的多是过时的数据。算法
配置中心的需求:保证高可用、实时通知、灰度发布、权限控制、一键回滚、环境隔离(开发/测试/生产)等,目前的开源实现:nacos disconf apollo。sql
apollo 有如下 4 个模块:docker
- portal 做为一个管理后台,提供管理员操做的入口。 有独立的数据库;
- adminservice 提供配置的修改和发布服务的底层服务,和 configservice 公用一个数据库 configdb,每次修改配置就会往数据库里插入一条记录 releasemessage;
- configservice 用一个定时任务去扫描数据库是否有新的 releasemessage,有的话就通知客户端,而客户端采用定时轮询的方式去查询 configservice 是否有新消息,这里采用 deferredresult 异步执行;
- eruka 为 adminservice 和 configservice 提供了注册发现的服务。客户端获取到配置文件后也会写入磁盘。
SET resource_name my_random_value NX PX 30000
先清空缓存仍是先更新数据库?shell
以上是考虑到分布式事务中一个成功一个失败的状况,可是这种几率毕竟是小的,能够用在并发量不是很高可是对数据一致性要求很高的状况,若是并发很高建议先更新数据库后清空缓存。
若是先清空缓存,后更新数据库,在尚未更新到数据库的状况下另一个事务去查询,发现缓存没命中就去数据库取,而后又写入缓存,以后上一个事务的数据库更新,这样就致使了缓存和数据库不一致,若是先更新数据库再清空缓存,更新完数据库后缓存还没更新,这个时候来读取缓存是旧的值,也出现不一致,可是最终清空缓存后会一致。
不过这种方式也会产生永久不一致,可是几率很小,例如一个读请求,没有命中缓存,这个时候可能另外一个线程恰好清空缓存,而后它就去数据里面取,可是又有一个线程在它读完数据库后将数据库改成另一个值,这样那个读请求写入到缓存的数据就是脏数据了。
redis 采用单线程模型,对只有 io 操做来讲性能很好,可是 redis 也提供了计算功能,如排序聚合,cpu 在计算的时候全部的 io 操做都是阻塞的。
memecached 先申请一块内存,将其分割成大小不等的若干内存块以存储不一样大小的键值对。这种方式效率高可是可能产生空间浪费。而 redis 只是单纯的包装了下 malloc 和 free。
redis 提供了两种方式持久化数据,一种方式是把某一时刻全部的数据都写入磁盘,另一种方式经过增量日志的形式
memecache 提供了 cas 来保证数据一致性;redis 提供了事务,将一连串指令一块儿执行或者回滚。
memechache 只能经过一致性哈希来进行集群,而 redis 提供了集群功能,客户端作路由选择那个 master 节点,master 节点能够有多个 slave 节点作为备用和读。
redis 中的字符串没有采用 c 语言里的结构,额外加上了空闲内存和已占用内存,这样读取的时候因为已经知道 char 数组大小,因此能够直接取出,避免遍历操做,当字符串变大或缩小的时候能够避免从新分配内存,能够用到空闲空间,也就是 redis 会预分配一个空间。另外 redis 里的哈希,用了两个 table 存储,主要为了扩容,也就是 rehash,这样当扩容的时候双方就能够互换,redis 采用渐近式扩容,也就是每一次操做都执行两个哈希表,当新增的时候只在新表。set 数据结构能够用来存储总的点赞次数,而 zset 是一个有序链表,为了加快查询用跳表进行存储。
严格的一致,只能一个生产者,发送到一个 broker 上,而后只有一个队列一个消费者,可是这种模式有不少弊端,一个地方异常将阻塞整个流程,RocketMQ 将这个问题交给应用层处理,也就是发送端本身选择发送到哪一个队列,例如同一个订单的消息发送到同一个队列。可是算法在其中一个队列异常的时候也会有问题。
只要网络上传输确定会有这种问题,因此应用层最好可以支持幂等,或者用一张去重表存储每个处理过的消息 ID。
每一个 commitlog 大小为 1G,第二个文件的起始偏移量就是 1G 的 byte 大小,当根据一个偏移量获取对应某个文件的时候,根据偏移量对 1G 取余就能够,这些 commitlog 文件经过一个文件队列维护,每次写文件返回队列的最后一个文件,而后须要加锁。
建立完文件后会进行预热,预热的时候会在每个内存页 4kb 里面写一个 byte0,让系统对缓存页缓存,防止真正写入的时候发生缺页,mmap 的机制是只会记录一个虚拟地址,当缺页时才会去获取物理内存的地址。
建立文件有两种方式:
一个队列只能被一个客户端消费。
当存在多个队列,但只有一个客户端的时候,这个客户端须要去 4 个队列上消费,当只有一个队列的时候只会有一个客户端能够收到消息,因此通常状况下须要客户端数量和队列数量一致,客户端通常会保存每一个队列消费的位置,由于这个队列只会有一个客户端消费,因此这个客户端每次消费都会记录下队列的 offset,broker 端,也会记录同一个 grouo 消费的 offset。
MappedByteBuffer 的原理是老的 read 是先将数据从文件系统读取到操做系统内核缓存,而后再将数据拷贝到用户态的内存供应用使用,而使用 mmap 能够将文件的数据或者某一段数据映射到虚拟内存,这个时候并无进行数据读取,当用户访问虚拟内存的地址的时候会触发缺页异常,这个时候会从底层文件系统直接将数据读取到用户态内存。而 MappedByteBuffer 经过 FileChannel 的 map 方法进行映射的时候会返回一个虚拟地址,MappedByteBuffer就是经过这个虚拟地址配合 UnSafe 获取字节数据。
操做系统在触发缺页异常的时候会去文件系统读取数据加载到内存,这个时候通常会进行预读取,通常为 4KB,当系统下次访问数据的时候就不会发生缺页异常,由于数据已经在内存里了,为了让 MappedByteBuffer 读取文件的速度更高,咱们能够对 MappedByteBuffer 所映射的文件进行预热,例如将每一个 pagecache 写一个数据,这样在真正写数据的时候就不会发生缺页了。
通常三种方式:在 dao 层和 orm 层利用 mybatis 拦截器,基于 jdbc 层进行拦截重写 JDBC 接口作加强,基于数据库代理。
jdbc 代理,实现 datasource,connection,preparestatement,druid 解析 sql,生成执行计划,利用 resultset 对结果集进行合并(group by order max sum)。
分表策略,通常是哈希,要保证分库和分表的算法彻底没有关联,否则会数据分布不均匀。
数据扩容的时候能够经过配置中心动态的修改写入策略,如何一开始能够先读老表,数据同时写入新表和老表,等数据迁移完成后,在读新表并双写,以后在读新表写新表。
数据库自增 id,一次取多个,单机限制,另外数据库自增 id 内部也用了个锁,只是在 sql 执行结束即便事务没提交也会释放锁。
雪花算法变种 : 15 位时间戳,4 位自增序列,2 位区分订单类型,7 位机器ID,2 位分库后缀,2 位分表后缀,共 32 位。
利用 zookeeper 的顺序节点获取自增 ID。
两阶段提交:事务管理器,资源管理器,一阶段准备,二阶段提交 (XA 方案对业务无侵入,由数据库厂商提供支持,可是性能不好)。
事物补偿
TCC :也是两阶段,第一阶段尝试锁定资源,第二阶段确认或者回滚。
设计规范:
框架事务(seata)
开启事务的时候会向 tc 申请一个全局的事务 id,这个事务 id 会经过 rpc 框架的拦截器传入到被调用端,而后放入 threadlocal,被调用方在执行 sql 的时候会去检查一下是否在一个全局事务里。
默认的隔离级别为读未提交,由于事务一阶段已经本地事务提交而全局事务并无完成,后续可能会回滚,其余事务能够看到这个状态,提供的读已提交的方式是经过 for update,当解析到该语句的时候会检查是否存在行锁冲突,若是存在冲突就等待直到释放。
一致性消息队列:先发送半消息,若是成功了在执行本地事务,本地事务成功就提交半消息,本地事务失败就回滚半消息,若是消息队列长期没有收到确认或者回滚能够反查本地事务的状态,消费端收到消息后,执行消费端业务,若是执行失败能够从新获取,执行成功发送消费成功的确认。
MYCAT
能够简单地这样理解:MySQL 单机是C;主从同步复制 CP;主从异步复制 AP。
Zookeeper 选择了 P,可是既没有实现 C,也没有实现 A,而是选择最终一致性。能够在多个节点上读取,可是只容许一个节点接受写请求,其余节点接收的写请求会转发给主节点,只要过半节点返回成功就会提交。
若是一个客户端链接的正好是没有被提交的 follower 节点,那么这个节点上读取到的数据就是旧的,这样就出现了数据的不一致,因此没有彻底实现 C。因为须要过半节点返回成功才提交,若是超过半数返回失败或者不返回,那么 zookeeper 将出现不可用,因此也没有彻底实现 A。
固然衡量一个系统是 CP 仍是 AP,能够根据它牺牲 A 更多仍是牺牲 C 更多,而 ZK 其实就是牺牲了 A 来知足 C,当超过集群半数的节点宕机后,系统将不可用,这也是不建议使用 zk 作注册中心的缘由。
CAP 理论只是描述了在分布式环境中一致性、可用性、分区容忍不能同时知足,并无让咱们必定要三选二,因为网络分区在分布式环境下是不可避免的,因此为了追求高可用,每每咱们会牺牲强一执行,采用弱一致性和最终一致性的方案,也就是著名的 BASE 理论,而 base 理论实际上是针对传统关系型数据的 ACID 而言的。
但 ACID 的提出是基于单节点下的,在分布式环境下,如何协调数据一致性,也就是在数据的隔离级别上作出取舍,即便是单机的关系型数据库为了提升性能,也就是可用性,定义了隔离级别,去打破 ACID 里面的强一致性 C,固然数据库也是为业务服务的,某些业务或者说大部分业务都没有强一致性的需求。
正常电商采用第三种,秒杀采用第一种,不超卖的控制不用放在应用层,直接在 sql 层加 where 语句进行判断,可是 mysql 针对同一行记录也就是同一个商品的减库存,确定会高并发下争取行锁,这将致使数据库的 tps 降低(死锁检测会遍历全部须要等待锁的链接,这个操做很是耗 cpu),从而影响其余商品的销售,因此咱们能够将请求在应用层进行排队,若是份额较少能够直接舍弃,另外一种方案是在数据库层排队,这种方案须要采用 mysql 的补丁。
docker 在建立容器进程的时候能够指定一组 namespace 参数,这样容器就只能看到当前 namespace 所限定的资源、文件、设备、网络、用户、配置信息,而对于宿主机和其余不相关的程序就看不到了,PID namespace 让进程只看到当前 namespace 内的进程,Mount namespace 让进程只看到当前 namespace 内的挂载点信息,Network namespace 让进程只看到当前 namespace 内的网卡和配置信息,
全名 linux control group,用来限制一个进程组可以使用的资源上限,如 CPU、内存、网络等,另外 Cgroup 还可以对进程设置优先级和将进程挂起和恢复,cgroup 对用户暴露的接口是一个文件系统,/sys/fs/cgroup 下这个目录下面有 cpuset,memery 等文件,每个能够被管理的资源都会有一个文件,如何对一个进程设置资源访问上限呢?
在 /sys/fs/cgroup 目录下新建一个文件夹,系统会默认建立上面一系列文件,而后 docker 容器启动后,将进程 ID 写入 taskid 文件中,在根据 docker 启动时候传人的参数修改对应的资源文件。
经过 chroot 来更改 change root file system 更改进程的根目录到挂载的位置,通常会经过 chroot 挂载一个完整的 linux 的文件系统,可是不包括 linux 内核,这样当咱们交付一个 docker 镜像的时候,不只包含须要运行的程序还包括这个程序依赖运行的这个环境,由于咱们打包了整个依赖的 linux 文件系统,对一个应用来讲,操做系统才是它所依赖的最完整的依赖库。
docker 在镜像的设计中引入层的概念,也就是用户在制做 docker 镜像中的每一次修改,都是在原来的 rootfs 上新增一层 roofs,以后经过一种联合文件系统 union fs 的技术进行合并,合并的过程当中若是两个 rootfs 中有相同的文件,则会用最外层的文件覆盖原来的文件来进行去重操做。
举个例子,咱们从镜像中心 pull 一个 mysql 的镜像到本地,当咱们经过这个镜像建立一个容器的时候,就在这个镜像原有的层上新加了一个增 roofs,这个文件系统只保留增量修改,包括文件的新增删除、修改,这个增量层会借助 union fs 和原有层一块儿挂载到同一个目录,这个增长的层能够读写,原有的其余层只能读,因而就保证了全部对 docker 镜像的操做都是增量。
以后用户能够 commit 这个镜像将对该镜像的修改生成一个新的镜像,新的镜像就包含了原有的层和新增的层,只有最原始的层才是一个完整的 linux fs, 那么既然只读层不容许修改,我怎么删除只读层的文件呢?这时只须要在读写层(也就是最外层),生成一个 whiteout 文件来遮挡原来的文件就能够了。
目前的大部分公司采用下面的部署方式。
-
阿里云 - 云原生应用平台 - 基础软件中台团队(原容器平台基础软件团队)诚邀 Kubernetes/容器/ Serverless/应用交付技术领域专家( P6-P8 )加盟。
工做年限:建议 P6-7 三年起,P8 五年起,具体看实际能力。
工做地点:
简历马上回复,2~3 周出结果。节后入职。
基础产品事业部是阿里云智能事业群的核心研发部门,负责计算、存储、网络、安全、中间件、系统软件等研发。而云原生应用平台基础软件终态团队致力于打造稳定、标准、先进的云原生应用系统平台,推进行业面向云原生技术升级与革命。
在这里,既有 CNCF TOC 和 SIG 联席主席,也有 etcd 创始人、K8s Operator 创始人与 Kubernetes 核心维护成员组成的、国内最顶尖的 Kubernetes 技术团队。
在这里,你将同来自全球的云原生技术领域专家们(如 Helm 项目的创始人、Istio 项目的创始人)密切合做,在独一无二的场景与规模中从事 Kubernetes、Service Mesh、Serverless、Open Application Model ( OAM )等云计算生态核心技术的研发与落地工做,在业界标杆级的平台上,既赋能阿里巴巴全球经济体,更服务全世界的开发者用户。
技术要求:Go/Rust/Java/C++,Linux,分布式系统
lei.zhang AT alibaba-inc.com