最全的微服务知识科普

微信公众号:内核小王子 关注可了解更多关于数据库,JVM内核相关的知识; 若是你有任何疑问也能够加我pigpdong[^1]java

微服务

好处:实现跨团队的解藕,实现更高的并发(目前单机只能实现c10k)不用在拷贝代码,基础服务能够公用,更好的支持服务治理,可以更好的兼容云计算平台。mysql

RPC

rpc:向调用本地方法同样调用远程函数linux

客户端:通常利用动态代理生成一个接口的实现类,在这个实现类里经过网络把接口名称,参数,方法序列化后传出去,而后控制同步调用仍是异步调用,异步调用须要设置一个回调函数,客户端还须要维护负载均衡,超时处理,链接池管理等,链接池维护了和多个server的链接,靠此作负载均衡,当某个服务器宕机后去除该链接。请求上下文维护了请求ID和回调函数,超时的请求当回复报文到达后因为找不到请求上下文就会丢弃。git

服务端:维护链接,网络收到请求后反序列化得到方法名称,接口名称,参数名称后经过反射进行调用,而后将结果在传回客户端。ajax

序列化的方式:一种是只序列化字段的值,反序列化的时候从新构建对象在把值设置进去,另一种方式直接将整个对象的结构序列化成二进制,前者节省空间,后者反序列化速度快,目前的序列化框架也是在反序列化时间和占用空间之间权衡。有点相似哈夫曼编码,或者数据库怎么存储一行一行的数据。redis

注册中心

通常有三种模式,f5作集中式代理,客户端嵌入式代理例如dubbo,还有一种是综合上面两种,多个客户端共用一个代理,代理做为一个独立进程部署在和客户端服务器同一台物理机上,servicemesh就是这种模式。算法

zookeeper 不适合作注册中心的缘由:zookeeper为了一致性牺牲了可用性,可是注册中心实际上对一致性要求并不高,不一致产生的后果也就是某个服务下线了可是客户端并不知道,可是客户端经过重试其余节点就能够了,另外当发生网络分区的时候,若是超过半数节点挂了,zookeeper就不可用,可是实际上他应该仍然能够对他所在机房的节点提供注册服务的,例如三个机房分别放了2台2台1台,若是各个机房之间网络断了,可是机房内部上通的,可是这样注册中心不可用即便内部节点也不能服务了。zookeeper并非严格的一致性,他支持读写分离,其余节点收到写请求会转发给master节点,而其余节点能够支持读请求,当数据尚未从主节点复制过来的时候读到的多是过时的数据。sql

配置中心

配置中心的需求:保证高可用,实时通知,灰度发布,权限控制,一键回滚,环境隔离(开发 测试 生产)目前的开源实现:nacos disconf apollo。docker

disconf:scan模块扫描注解和监听器,store模块将远程获取到的配置存储到本地,本地一个job检测配置是否有变化,有变化就通知监听器,fetch模块从远程经过http获取配置,watch模块监听zookeeper上节点的变化,有变化就会调用fetch进行获取.shell

apollo:四个模块:portal 做为一个管理后台,提供管理员操做的入口。 有独立的数据库。 adminservice 提供配置的修改和发布服务的底层服务,和 configservice 公用一个数据库configdb,每次修改配置就会往数据库里插入一条记录releasemessage,configservice 用一个定时任务去扫描数据库是否有新的releasemessage,有的话就通知客户端,而客户端采用定时轮询的方式去查询 configservice 是否有新消息,这里采用 deferredresult 异步执行。eruka为adminservice和configservice提供了注册发现的服务。客户端获取到配置文件后也会写入磁盘。

任务调度

  • 1.执行器也就是应用自己,任务单元也就是具体执行任务的线程 可以主动注册调度器中,并在启动的时候进行更新,例如删除已经清空的任务
  • 2.调度中心支持集群部署避免单点,能够选举一个主节点其余为slave
  • 3.支持负载均衡算法为每一个任务随机选择执行器,可以支持失败重试,将执行很慢或者失去链接的执行器移除
  • 4.支持控制任务并发,例如是否容许一个任务没执行完这个任务又被调度
  • 5.支持任务依赖,例如一个任务没执行完另外一个任务不能执行,或者自动执行另一个任务
  • 6.支持任务分片,将一个任务根据参数分片到不一样的执行器上一块儿执行。
  • 7.能够取消一个任务
  • 8.已经支持glue模式,能够不用发布就执行一个任务单元

分布式锁

  • 1)redis setnx里面已经有参数能够支持分布式锁,可是最好能把锁的拥有方存到value里,释放的时候作比较,否则可能释放错锁,也就是会出现A释放了B的锁。
  • 2)zk采用建立临时节点,其余建立失败的线程监听锁的状态。
SET resource_name my_random_value NX PX 30000
复制代码

统一监控:

  • 1)收集日志并分析,日志也能够和rpc链路进行关联,也能够对日志进行降噪或者压缩存储
  • 2)提供api的方式以及拦截器模式,能够基于javaagent作到无嵌入
  • 3)实现opentracing链路追踪
  • 4)能够基于disruptor ringbuffer的生产消费者模式
  • 5)海量数据的存储,elasticsearch
  • 6)报表生成,监控指标设置
  • 7)各个节点进行收集,消息上传到服务端统一处理
  • 8)监控指标:rpc链路,数据库,cpu指标等,http状态,各类中间件
  • 9)日志收集能够经过直接在日志框架上加拦截器,或者用flink+kafka收集

缓存

先清空缓存仍是先更新数据库?

若是是更新缓存而不是删除缓存:则无论哪一种方式都会形成缓存和数据库不一致,若是是删除缓存:则先删除缓存在更新数据库,若是更新数据库失败了也没有太大影响,缓存被清了从新加载便可。可是也要考虑到缓存穿透的问题,若是这个时候大流量进来是否会压垮数据库?

以上是考虑到分布式事务中一个成功一个失败的状况,可是这种几率毕竟是小的,能够用在并发量不是很高可是对数据一致性要求很高的状况,若是并发很高建议先更新数据库后清空缓存。

若是先清空缓存,后更新数据库,在尚未更新到数据库的状况下另一个事务去查询,发现缓存没命中就去数据库取,而后又写入缓存,以后上一个事务的数据库更新,这样就致使了缓存和数据库不一致,若是先更新数据库在清空缓存,更新完数据库后缓存还没更新,这个时候来读取缓存是旧的值,也出现不一致,可是最终清空缓存后会一致。不过这种方式也会产生永久不一致,可是几率很小,例如一个读请求,没有命中缓存,这个时候可能另外一个线程恰好清空缓存,而后他就去数据里面取,可是又有一个线程在他读完数据库后将数据库改成另一个值,这样那个读请求写入到缓存的数据就是脏数据了。

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

发送消息流程

  • 1.先获取topic对应的路由信息(路由信息会从namesrv返回,在客户端缓存,返回这个topic对应哪几个broker以及每一个broker上有多少个队列)
  • 2.若是没有获取到,可能没有topic,须要自动建立,自动建立是客户端发信息个namesrv,namesrv在去请求broker,broker建立好后返回
  • 3.根据路由策略获取一个queue(从全部的queue中根据对应的路由策略获取queue,而后在判断这个queue对应的broker是否健康,健康就返回) 这个地方就能够作到broker的高可用
  • 4.因此 咱们发现消息是发给哪一个broker的哪一个queue是在客户端发送的时候决定的,不是在生成commitlog以后在派发的,这样咱们就能够指定都某一个固定queue了
  • 5.消息发送的时候会构建发送请求,里面包含了消息体和队列信息,topic信息等,消息体里面会增长一个消息ID,
  • 6.若是消息重试屡次后仍是失败就会进入死信队列,一个固定的topic

消息存储

每一个commitlog大小为1G,第二个文件的起始偏移量就是1G的byte大小,当根据一个偏移量获取对应哪一个文件的时候,根据偏移量对1G取余就能够,这些commitlog文件经过一个 文件队列维护,每次写文件返回队列的最后一个文件,而后须要加锁,建立完文件后会进行预热,预热的时候会在每个内存页4kb里面写一个byte0。让系统会对缓存页缓存防止真正写入的时候发生缺页,mmap的机制是只会记录一个虚拟地址,当缺页的才会去获取物理内存的地址,建立文件有两种方式,一种是FileChannel.map获取MappedByteBuffer 另一种是使用堆外内存池,而后flush

消息的消费

一个队列只能被一个客户端消费,当有多个队列,只有一个客户端的时候,这个客户端须要去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,一次取多个,单机限制,另外数据库自增id内部也用了个锁,只是在sql执行结束即便事务没提交也会释放锁。

雪花算法变种 : 15位时间戳,4位自增序列,2位区分订单类型,7位机器ID,2位分库后缀,2位分表后缀 共32位

利用zookeeper的顺序节点获取自增ID

分布式事务

两阶段提交:事务管理器,资源管理器,一阶段准备,二阶段提交 (XA方案对业务无侵入,由数据库厂商提供支持,可是性能不好)

事物补偿

TCC :也是两阶段,第一阶段 尝试锁定资源 第二阶段确认或者回滚

设计规范

  • 业务操做分红两部,例如转帐:尝试阶段为冻结余额,第二阶段提交为 从冻结余额扣款,回滚为解冻
  • 事务协调器记录主事务日志和分支事务日志,支持在任意一步发生异常后进行补偿或者逆向补偿保证最终一致性
  • 并发控制,下降锁的粒度提升并发,保证两个事务间不须要加排他锁,例如热点帐户的转帐操做,因为第一阶段进行了冻结,因此后面的扣减余额不一样事务之间没有影响。
  • 容许空回滚:可能一阶段的尝试操做发生超时,而后二阶段发起回滚,回滚的时候要判断一阶段是否进行过操做,若是一阶段没有收到请求,回滚操做直接返回成功。
  • 避免一阶段操做悬挂:可能一阶段超时,二阶段回滚后,一阶段的请求到达,这时候要拒绝一阶段的尝试操做。
  • 幂等控制,因为第一阶段和第二阶段的操做可能都会执行屡次,另外操做接口最好能提供状态查询接口供后台的补偿任务正常执行

框架事务(seata)

一阶段 框架会拦截业务sql,根据语句执行前结果生成 undolog , 根据语句执行后对结果生成 redolog , 根据数据库表名加主键生成行锁

二阶段 若是事务正常结束,将删除 undolog redolog 行锁,若是事务将回滚,则执行 undolog sql , 删除中间数据 在执行 undolog 的时候会校验脏写,也就是有没有其余事务已经修改了这行记录,也就是用 redolog 作对比,若是出现脏写只能人工修数据 (二阶段的清理工做能够异步执行)

开启事务的时候会向tc申请一个全局的事务id,这个事务id会经过rpc框架的拦截器传入到被调用端,而后放入threadlocal,被调用方在执行sql的时候会去检查一下是否在一个全局事务里。

默认的隔离级别为读未提交,由于事务一阶段已经本地事务提交而全局事务并无完成后续可能会回滚,其余事务能够看到这个这个状态,提供的读已提交的方式是经过 for update,当解析到该语句的时候会检查是否存在行锁冲突,若是存在冲突就等待直到释放。

  • 1.tm 向 tc 发起开启一个全局事务,生成一个全局惟一的 xid
  • 2.xid 在微服务调用链上进行传递
  • 3.rm 向 tc 注册分支事务
  • 4.tm 向 tc 发起全局提交或者回滚决议
  • 5.tc 向 rm 发起回滚或提交请求

一致性消息队列:先发送半消息,若是成功了在执行本地事务,本地事务成功就提交半消息,本地事务失败就回滚半消息,若是消息队列长期没有收到确认或者回滚能够反查本地事务的状态,消费端收到消息后,执行消费端业务,若是执行失败能够从新获取,执行成功发送消费成功的确认。

MYCAT

CAP

C 一致性 A 可用性 P 分区容忍性 能够简单地这样理解: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,固然数据库也是为业务服务的,某些业务或者说大部分业务都没有强一致性的需求。

秒杀的处理

  • 动静分离:ajax 不刷新页面,缓存,cdn
  • 发现热点数据:业务流程上变通让热点业务隔离出来,也经过链路监控获取一段时间的热点数据
  • 隔离:业务隔离,数据库隔离
  • 兜底方案:服务降级,限流
  • 流量削峰: 排队,过滤无效请求,答题或者验证码,消息队列
  • 减库存:(下单减库存用户不付款须要回滚,付款减库存最终可能库存不足须要退款,下单后占库存一段时间后在回滚) 正常电商第三种,秒杀采用第一种,不超卖的控制不用放在应用层,直接在sql层加where语句进行判断,可是mysql针对同一行记录也就是同一个商品的减库存,确定会高并发下争取行锁,这将致使数据库的tps降低(死锁检测会遍历全部须要等待锁的链接这个操做很是耗cpu),从而影响其余商品的销售,因此咱们能够将请求在应用层进行排队,若是份额较少能够直接舍弃,另外一种方案是在数据库层排队,这种方案须要采用mysql的补丁

docker

namespace

docker在建立容器进程的时候能够指定一组namespace参数,这样容器就只能看到当前namespace所限定的资源,文件,设备,网络。用户,配置信息,而对于宿主机和其余不相关的程序就看不到了,PID namespace让进程只看到当前namespace内的进程,Mount namespace让进程只看到当前namespace内的挂载点信息,Network namespace让进程只看到当前namespace内的网卡和配置信息,

cgroup

全名 linux control group,用来限制一个进程组可以使用的资源上限,如CPU,内存,网络等,另外Cgroup还可以对进程设置优先级和将进程挂起和恢复,cgroup对用户暴露的接口是一个文件系统,/sys/fs/cgroup下 这个目录下面有 cpuset,memery等文件,每个能够被管理的资源都会有一个文件,如何对一个进程设置资源访问上限呢?在/sys/fs/cgroup目录下新建一个文件夹,系统会默认建立上面一系列文件,而后docker容器启动后,将进程ID写入taskid文件中,在根据docker启动时候传人的参数修改对应的资源文件

chroot

经过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文件来遮挡原来的文件就能够了。

发布与部署

目前的大部分公司采用下面的部署方式

  • 1.建立pileline 指定项目名称和对应的tag,以及依赖工程,一个pipeline指一个完整的项目生命周期(开发提交代码到代码仓库,打包,部署到开发环境,自动化测试,部署到测试环境,部署到生产环境)
  • 2.根据项目名称和tag去gitlab上拉取最新的代码(利用java里的Runtime执行shell脚本)
  • 3.利用maven进行打包,这个时候能够为maven建立一个单独的workspace(shell脚本)
  • 4.根据预先写好的docfile,拷贝maven打的包生成镜像,并上传镜像 (shell脚本)
  • 5.经过k8s的api在测试环境发布升级
  • 6.经过灰度等方案发布到生产环境

微信公众号:内核小王子 关注可了解更多关于数据库,JVM内核相关的知识; 若是你有任何疑问也能够加我pigpdong[^1]

历史文章:

JAVA和操做系统交互细节

经过MySQL存储原理来分析排序和锁

网络内核之TCP是如何发送和接收消息的

相关文章
相关标签/搜索