微服务好处:实现跨团队的解藕,实现更高的并发(目前单机只能实现c10k)不用在拷贝代码,基础服务可以公用,更好的支持服务治理,能够更好的兼容云计算平台。 RPCrpc:向调用本地方法一样调用远程函数 客户端:一般利用动态代理生成一个接口的实现类,在这个实现类里通过网络把接口名称,参数,方法序列化后传出去,然后控制同步调用还是异步调用,异步调用需要设置一个回调函数,客户端还需要维护负载均衡,超时处理,连接池管理等,连接池维护了和多个server的连接,靠此做负载均衡,当某个服务器宕机后去除该连接。请求上下文维护了请求ID和回调函数,超时的请求当回复报文到达后由于找不到请求上下文就会丢弃。 服务端:维护连接,网络收到请求后反序列化获得方法名称,接口名称,参数名称后通过反射进行调用,然后将结果在传回客户端。 序列化的方式:一种是只序列化字段的值,反序列化的时候重新构建对象在把值设置进去,另外一种方式直接将整个对象的结构序列化成二进制,前者节省空间,后者反序列化速度快,目前的序列化框架也是在反序列化时间和占用空间之间权衡。在点类似哈夫曼编码,或者数据库怎么存储一行一行的数据。 注册中心一般有三种模式,f5做集中式代理,客户端嵌入式代理例如dubbo,还有一种是综合上面两种,多个客户端共用一个代理,代理作为一个独立进程部署在和客户端服务器同一台物理机上,servicemesh就是这种模式。
配置中心配置中心的需求:保证高可用,实时通知,灰度发布,权限控制,一键回滚,环境隔离(开发 测试 生产)目前的开源实现:nacos disconf apollo。
任务调度
分布式锁
SET resource_name my_random_value NX PX 30000 统一监控:
缓存先清空缓存还是先更新数据库? 如果是更新缓存而不是删除缓存:则不管哪种方式都会造成缓存和数据库不一致,如果是删除缓存:则先删除缓存在更新数据库,如果更新数据库失败了也没有太大影响,缓存被清了重新加载即可。但是也要考虑到缓存穿透的问题,如果这个时候大流量进来是否会压垮数据库? 以上是考虑到分布式事务中一个成功一个失败的情况,但是这种概率毕竟是小的,可以用在并发量不是很高但是对数据一致性要求很高的情况,如果并发很高建议先更新数据库后清空缓存。 如果先清空缓存,后更新数据库,在还没有更新到数据库的情况下另外一个事务去查询,发现缓存没命中就去数据库取,然后又写入缓存,之后上一个事务的数据库更新,这样就导致了缓存和数据库不一致,如果先更新数据库在清空缓存,更新完数据库后缓存还没更新,这个时候来读取缓存是旧的值,也出现不一致,但是最终清空缓存后会一致。不过这种方式也会产生永久不一致,但是概率很小,例如一个读请求,没有命中缓存,这个时候可能另一个线程刚好清空缓存,然后他就去数据里面取,但是又有一个线程在他读完数据库后将数据库改为另外一个值,这样那个读请求写入到缓存的数据就是脏数据了。 redis采用单线程模型,对只有io操作来说性能很好,但是redis也提供了计算功能,如排序聚合,cpu在计算的时候所有的io操作都是阻塞的。 memecached先申请一块内存将其分割成大小不等的若干内存块以存储不同大小的键值对。这种方式效率高但是可能产生空间浪费。而redis只是单纯的包装了下malloc和free. redis提供了两种方式持久化数据,一种方式是把某一时刻所有的数据都写入磁盘,另外一种方式通过增量日志的形式 memecache提供了cas来保证数据一致性,redis提供了事务,将一连串指令一起执行或者回滚 memechache只能通过一致性哈希来进行集群,而redis提供了集群功能,客户端做路由选择那个master节点,master节点可以有多个slave节点做为备用和读。
如何防止缓存雪崩:缓存要高可用,可以设置多级缓存,如何预防缓存穿透:设置不同的失效时间 消息队列
消息存储每个commitlog大小为1G,第二个文件的起始偏移量就是1G的byte大小,当根据一个偏移量获取对应哪个文件的时候,根据偏移量对1G区域就可以,这些commitlog文件通过一个 文件队列维护,每次写文件返回队列的最后一个文件,然后需要加锁,创建完文件后会进行预热,预热的时候会在每一个内存页4kb里面写一个byte0。让系统会对缓存页缓存防止真正写入的时候发生缺页,mmap的机制是只会记录一个虚拟地址,当缺页的才会去获取物理内存的地址,创建文件有两种方式,一种是FileChannel.map获取MappedByteBuffer 另外一种是使用堆外内存池,然后flush 消息的消费一个队列只能被一个客户端消费,当有多个队列,只有一个客户端的时候,这个客户端需要去4个队列上消费,当只有一个队列的时候只会有一个客户端可以收到消息,所以一般情况下需要客户端数量和队列数量一致,客户端一般会保存每个队列消费的位置,因为这个队列只会有一个客户端消费,所以这个客户端每次消费都会记录下队列的offset,broker端也会记录同一个grouo消费的offset
分库分表一般三种方式:在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 分布式事务
事物补偿
设计规范
框架事务(seata)一阶段 框架会拦截业务sql,根据语句执行前结果生成 undolog , 根据语句执行后对结果生成 redolog , 根据数据库表名加主键生成行锁 二阶段 如果事务正常结束,将删除 undolog redolog 行锁,如果事务将回滚,则执行 undolog sql , 删除中间数据 在执行 undolog 的时候会校验脏写,也就是有没有其他事务已经修改了这行记录,也就是用 redolog 做对比,如果出现脏写只能人工修数据 (二阶段的清理工作可以异步执行) 开启事务的时候会向tc申请一个全局的事务id,这个事务id会通过rpc框架的拦截器传入到被调用端,然后放入threadlocal,被调用方在执行sql的时候会去检查一下是否在一个全局事务里。 默认的隔离级别为读未提交,因为事务一阶段已经本地事务提交而全局事务并没有完成后续可能会回滚,其他事务可以看到这个这个状态,提供的读已提交的方式是通过 for update,当解析到该语句的时候会检查是否存在行锁冲突,如果存在冲突就等待直到释放。
CAPC 一致性 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,当然数据库也是为业务服务的,某些业务或者说大部分业务都没有强一致性的需求。 秒杀的处理
docker
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文件来遮挡原来的文件就可以了。 发布与部署目前的大部分公司采用下面的部署方式
原文链接 https://mp.weixin.qq.com/s/dAltPlwo9nddBZ69r7ao1Q |
|
来自: hlhq1 > 《Framework》