通过发号策略,给每一个过来的长地址,发一个号即可,小型系统直接用mysql的自增索引就搞定了。如果是大型应用,可以考虑各种分布式key-value系统做发号器。不停的自增就行了。第一个使用这个服务的人得到的短地址是http://xx.xx/0 第二个是 http://xx.xx/1 第11个是 http://xx.xx/a 第依次往后,相当于实现了一个62进制的自增字段即可。
常用的url压缩算法是短地址映射法。具体步骤是:
随机、轮询、最少使用、一致性哈希(除了一致性哈希外,都有加权)
常见6种负载均衡算法:轮询,随机,源地址哈希,加权轮询,加权随机,最小连接数。
nginx5种负载均衡算法:轮询,weight轮询,ip_hash,fair(响应时间),url_hash
可以,因为dubbo在注册中心挂掉之后,会从原先的缓存中读取连接地址。
调用方:
将方法名方法参数传入InvokerInvocationHandler的invoke方法中,对于Object中的方法toString, hashCode, equals直接调用invoker的对应方法。
然后进入(故障转移集群)MockClusterInvoker.invoke()方法中。三种调用策略:①不需要mock, 直接调用FailoverClusterInvoker。②强制mock,调用mock。③先调FailoverClusterInvoker,调用失败在mock.
FailoverClusterInvoker默认调用策略。①通过目录服务查找到所有订阅的服务提供者的Invoker对象。②路由服务根据策略(比如:容错策略)来过滤选择调用的Invokers。③通过负载均衡策略LoadBalance来选择一个Invoker
执行选择的Invoker.invoker(invocation),经过监听器链,经过过滤器链,执行到远程调用的DubboInvoker。
6.获取调用结果:①Oneway返回空RpcResult②异步,直接返回空RpcResult, ResponseFuture回调③同步, ResponseFuture模式同步转异步,等待响应返回
消费方:
通过Invocation获取服务名和端口组成serviceKey=com.alibaba.dubbo.demo.DemoService:20880, 从DubboProtocol的exproterMap中获取暴露服务的DubboExporter, 在从dubboExporter 获取invoker返回
经过过滤器链。
经过监听器链。
到达执行真正调用的invoker, 这个invoker由代理工厂ProxyFactory.getInvoker(demoService, DemoService.class, registryUrl)创建,具体请看代理那部分介绍。
1.dubbo采用RPC的方式交互,SpringCloud采用Http,restful协议进行交互。
2.dubbo依赖zookeeper进行服务注册,Springloud自己拥有自己的服务注册中心。
3.dubbo需要强依赖,需要持有相同的类或者jar包,springcloud弱依赖,但需要通过接口文档进行约束。
4.C数据一致性,A服务可用性,P服务对网络分区故障的容错性,Zookeeper 保证的是CP,euraka保证的是AP。
setnx指令,设置锁的有效时间防止死锁。设置一个随机值来标识锁的持有人,利用这个随机值来释放锁。
server模式。
最大堆最小堆大小。
年轻代和老年代的比例。
开启优化。
使用偏向锁。
gc年龄。
maxThread。
minThread。
acceptCount。
connectionTimeout。
查询与删除操作是天然幂等
唯一索引,防止新增脏数据
token机制,防止页面重复提交
悲观锁 for update
乐观锁(通过版本号/时间戳实现, 通过条件限制where avai_amount-#subAmount# >= 0)
分布式锁
状态机幂等(如果状态机已经处于下一个状态,这时候来了一个上一个状态的变更,理论上是不能够变更的,这样的话,保证了有限状态机的幂等。)
select + insert(并发不高的后台系统,或者一些任务JOB,为了支持幂等,支持重复执行)
设计一个数据结构,有用户id,当前秒数,调用次数。每次请求时对比当前秒数和该对象内的是否一致,一致的话累加调用次数。不一致的话,将当前秒数替换成新的,调用次数清0。
同域下的单点登录,只需共享session即可。
登录业务系统,跳转至SSO服务器,判断用户名密码正确,在sso域下种下cookie,在session中标记为登录,返回一个ticket,跳转到业务系统,业务系统再拿这个ticket跑去SSO服务器验证ticket是否有效,有效的话,在业务系统session中设置为已登录即可。
相比于单系统登录,sso需要一个独立的认证中心,只有认证中心能接受用户的用户名密码等安全信息,其他系统不提供登录入口,只接受认证中心的间接授权。间接授权通过令牌实现,sso认证中心验证用户的用户名密码没问题,创建授权令牌,在接下来的跳转过程中,授权令牌作为参数发送给各个子系统,子系统拿到令牌,即得到了授权,可以借此创建局部会话,局部会话登录方式与单系统的登录方式相同。这个过程,也就是单点登录的原理,用下图说明
学勤系统与账户余额系统
事务消息与普通消息的区别就在于消息生产环节,生产者首先预发送一条消息到MQ(这也被称为发送half消息)
MQ接受到消息后,先进行持久化,则存储中会新增一条状态为待发送的消息
然后返回ACK给消息生产者,此时MQ不会触发消息推送事件
生产者预发送消息成功后,执行本地事务
执行本地事务,执行完成后,发送执行结果给MQ
MQ会根据结果删除或者更新消息状态为可发送
正在处理的实现事务功能,下次自动回滚。
队列实现持久化储存,下次启动自动载入。
添加标志位,未处理 0,处理中 1,已处理 2。每次启动的时候,把所有状态为 1 的,置为 0。
漏桶:水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求。
令牌桶算法:系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入Token,如果桶已经满了就不再加了.新请求来临时,会各自拿走一个Token,如果没有Token就拒绝服务。
基于redis实现的限流:假设每分钟访问次数不能超过10次,在Redis中创建一个键,过期60秒,对此服务接口的访问就把键值加1,在60秒内增加到10的时候,禁止访问服务接口。
计数器,滑动窗口(假设窗口为10s,则建立一个大小为10的数组,然后每次让当前秒数除10,落到哪个格子就累加,每一时刻数组的和就是窗口的数值)
令牌桶可以应对突发的大流量
新建一个topic,partition是原来的10倍;然后写一个临时的分发数据的consumer程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的10倍数量的queue;接着临时征用10倍的机器来部署consumer,每一批consumer消费一个临时queue的数据;等快速消费完积压数据之后,得恢复原先部署架构,重新用原先的consumer机器来消费消息;
Kafka消息发送分同步(sync)、异步(async)两种方式。默认是使用同步方式,可通过producer.type属性进行配置;Kafka保证消息被安全生产,有三个选项分别是0,1,-1。
通过request.required.acks属性进行配置:
0代表:不进行消息接收是否成功的确认(默认值);
1代表:当Leader副本接收成功后,返回接收成功确认信息;
网络异常:
acks设置为0时,不和Kafka集群进行消息接受确认,当网络发生异常等情况时,存在消息丢失的可能;
客户端异常:
异步发送时,消息并没有直接发送至Kafka集群,而是在Client端按一定规则缓存并批量发送。在这期间,如果客户端发生死机等情况,都会导致消息的丢失;
缓冲区满了:
异步发送时,Client端缓存的消息超出了缓冲池的大小,也存在消息丢失的可能;
Leader副本异常:
acks设置为1时,Leader副本接收成功,Kafka集群就返回成功确认信息,而Follower副本可能还在同步。这时Leader副本突然出现异常,新Leader副本(原Follower副本)未能和其保持一致,就会出现消息丢失的情况;
以上就是消息丢失的几种情况,在日常应用中,我们需要结合自身的应用场景来选择不同的配置。
想要更高的吞吐量就设置:异步、ack=0;想要不丢失消息数据就选:同步、ack=-1策略
消息持久化:Exchange 设置持久化:durable:true;
Queue 设置持久化;
Message持久化发送。
如果某个分区patition的Leader挂了,那么其它跟随者将会进行选举产生一个新的leader,之后所有的读写就会转移到这个新的Leader上,在kafka中,其不是采用常见的多数选举的方式进行副本的Leader选举,而是会在Zookeeper上针对每个Topic维护一个称为ISR(in-sync replica,已同步的副本)的集合,显然还有一些副本没有来得及同步。只有这个ISR列表里面的才有资格成为leader(先使用ISR里面的第一个,如果不行依次类推,因为ISR里面的是同步副本,消息是最完整且各个节点都是一样的)。
其实很简单主要是用二分查找算法,比如我们要查找一条offest=10000的文件,kafka首先会在对应分区下的log文件里采用二分查看定位到某个记录该offest
=10000这条消息的log,然后从相应的index文件定位其偏移量,然后拿着偏移量到log里面直接获取。这样就完成了一个消息的检索过程。
以两个节点(rabbit01、rabbit02)为例来进行说明。rabbit01和rabbit02两个节点仅有相同的元数据,即队列的结构,但消息实体只存在于其中一个节点rabbit01(或者rabbit02)中。
在普通集群的基础上,把需要的队列做成镜像队列,消息实体会主动在镜像节点间同步,而不是在客户端取数据时临时拉取,也就是说多少节点消息就会备份多少份。该模式带来的副作用也很明显,除了降低系统性能外,如果镜像队列数量过多,加之大量的消息进入,集群内部的网络带宽将会被这种同步通讯大大消耗掉。所以在对可靠性要求较高的场合中适用
Broker NIO异步消息处理,实现了IO线程与业务线程分离;
磁盘顺序写;
零拷贝(跳过用户缓冲区的拷贝,建立一个磁盘空间和内存的直接映射,数据不再复制到用户态缓冲区);
分区/分段(每次文件操作都是对一个小文件的操作,非常轻便,同时也增加了并行处理能力);
批量发送 (可以指定缓存的消息达到某个量的时候就发出去,或者缓存了固定的时间后就发送出去,大大减少服务端的I/O次数)
ZooKeeper 运行期间,集群中至少有过半的机器保存了最新数据。集群超过半数的机器能够正常工作,集群就能够对外提供服务。
zookeeper可以选出N台机器作主机,它可以实现M:N的备份;keepalive只能选出1台机器作主机,所以keepalive只能实现M:1的备份。
通常有以下两种部署方案:双机房部署(一个稳定性更好、设备更可靠的机房,这个机房就是主要机房,而另外一个机房则更加廉价一些,例如,对于一个由 7 台机器组成的 ZooKeeper 集群,通常在主要机房中部署 4 台机器,剩下的 3 台机器部署到另外一个机房中);三机房部署(无论哪个机房发生了故障,剩下两个机房的机器数量都超过半数。在三个机房中都部署若干个机器来组成一个 ZooKeeper 集群。假设机器总数为 N,各机房机器数:N1 = (N-1)/2 ,N2=1~(N-N1)/2 ,N3 = N - N1 - N2 )。
对于大促时候的秒杀活动,一般运营会配置静态的活动页面,配置静态活动页面主要有两个目的一方面是为了便于在各种社交媒体分发,另一方面是因为秒杀活动页的流量是大促期间最大的,通过配置成静态页面可以将页面发布在公有云上动态的横向扩展;
将秒杀活动的静态页面提前刷新到CDN节点,通过CDN节点的页面缓存来缓解访问压力和公司网络带宽,CDN上缓存js、css和图片;
将活动H5页面部署在公有云的web server上,使用公有云最大的好处就是能够根据活动的火爆程度动态扩容而且成本较低,同时将访问压力隔离在公司系统外部;
在提供真正商品秒杀业务功能的app server上,需要进行交易限流、熔断控制,防止因为秒杀交易影响到其他正常服务的提供,我们在限流和熔断方面使用了hystrix,在核心交易的controller层通过hystrix进行交易并发限流控制,当交易流量超出我们设定的限流最大值时,会对新交易进行熔断处理固定返回静态失败报文。
服务降级处理,除了上面讲到的限流和熔断控制,我们还设定了降级开关,对于首页、购物车、订单查询、大数据等功能都会进行一定程度的服务降级,例如我们会对首页原先动态生成的大数据页面布局降级为所有人看到的是一样的页面、购物车也会降级为不在一级页面的tabbar上的购物车图标上显示商品数量、历史订单的查询也会提供时间周期较短的查询、大数据商品推荐也会提供一样的商品推荐,通过这样的降级处理能够很好的保证各个系统在大促期间能够正常的提供最基本的服务,保证用户能够正常下单完成付款。
(1)使用redis的set集合
(2)使用redis的bitmap(注意内存消耗)
加载DB时同步,其他则等待;DB端做SQL合并,Queue合并排队处理;
部分缓存设置为永不过期;
前端js,控制按钮。前端放置令牌。
数据库唯一索引。redis看key是否存在。或者数据库字段状态。
利用堆快照,查看到底是哪些对象占用大量内存导致经常gc
DB层面,有可能是sql,索引,表过大,数据库压力。
缓存层面:有可能缓存命中率差,redis性能瓶颈,需要扩容
服务器压力:服务器处理瓶颈
Java层面:代码写法
前端层面:cdn压力,页面压力
分布式锁,或者幂等接口,CAS乐观锁
可以借鉴下stackoverflow,视频网站等等的推荐算法。
悲观锁,乐观锁,存储过程放在mysql数据库中。
全局队列,把1000任务放在一个队列里面,然后每个人都是取,完成任务。
分为10个队列,每个人分别到自己对应的队列中去取务。
调用可以实现跟踪系统,可以在业务日志中添加调用链ID,各个环节RPC均添加调用时延,QPS等。
非业务组件应该少加入业务代码,服务调用采用买点,也会采用配置采样率方式,买点即当前节点的上下文信息,包含TraceId,RPCId,开始结束时间,类型,协议,调用方IP,端口,服务名等,以及其他异常信息,报文等扩展,日志采用离线+实时的如flume结合kafka等,应按照TraceId汇总日志后按RPCId顺序整理。
2 如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel 标记为主观下线;
如果一个 Master 被标记为主观下线,则正在监视这个 Master 的所有 Sentinel 要以每秒一次的频率确认 Master 的确进入了主观下线状态;
当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认 Master 的确进入了主观下线状态,则 Master 会被标记为客观下线;
在一般情况下, 每个 Sentinel 会以每 10 秒一次的频率向它已知的所有 Master,Slave 发送 INFO 命令;当 Master 被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次;
若没有足够数量的 Sentinel 同意 Master 已经下线, Master 的客观下线状态就会被移除;
监控( Monitoring ):Redis Sentinel 实时监控主服务器和从服务器运行状态;
自动故障转移:如果一个 master 不正常运行了,哨兵可以启动一个故障转移进程,将一个 slave 升级成为 master,其他的 slave 被重新配置使用新的 master,并且应用程序使用 Redis 服务端通知的新地址;
雪花算法:
1 bit:不用,为啥呢?因为二进制里第一个 bit 为如果是 1,那么都是负数,但是我们生成的 id 都是正数,所以第一个 bit 统一都是 0。
41 bit:表示的是时间戳,单位是毫秒。41 bit 可以表示的数字多达 2^41 - 1,也就是可以标识 2^41 - 1 个毫秒值,换算成年就是表示69年的时间。
10 bit:记录工作机器 id,代表的是这个服务最多可以部署在 2^10台机器上哪,也就是1024台机器。但是 10 bit 里 5 个 bit 代表机房 id,5 个 bit 代表机器 id。意思就是最多代表 2^5个机房(32个机房),每个机房里可以代表 2^5 个机器(32台机器)。
利用哈希环进行一致性哈希
Redis实现比较复杂,流程如下:
根据lockKey区进行setnx(set not exist,顾名思义,如果key值为空,则正常设置,返回1,否则不会进行设置并返回0)操作,如果设置成功,表示已经获得锁,否则并没有获取锁。
如果没有获得锁,去Redis上拿到该key对应的值,在该key上我们存储一个时间戳(用毫秒表示,t1),为了避免死锁以及其他客户端占用该锁超过一定时间(5秒),使用该客户端当前时间戳,与存储的时间戳作比较。
如果没有超过该key的使用时限,返回false,表示其他人正在占用该key,不能强制使用;如果已经超过时限,那我们就可以进行解锁,使用我们的时间戳来代替该字段的值。
但是如果在setnx失败后,get该值却无法拿到该字段时,说明操作之前该锁已经被释放,这个时候,最好的办法就是重新执行一遍setnx方法来获取其值以获得该锁。
从实现难度上来说,Zookeeper实现非常简单,实现分布式锁的基本逻辑:
客户端调用create()方法创建名为“locknode/guid-lock-”的节点,需要注意的是,这里节点的创建类型需要设置为EPHEMERAL_SEQUENTIAL。
客户端调用getChildren(“locknode”)方法来获取所有已经创建的子节点。
客户端获取到所有子节点path之后,如果发现自己在步骤1中创建的节点是所有节点中序号最小的,那么就认为这个客户端获得了锁。
区别:
Redis分布式锁,需要自己不断去尝试获取锁,比较消耗性能
ZooKeeper分布式锁,获取不到锁,注册个监听器即可,不需要不断主动尝试获取锁,性能开销较小
如果Redis获取锁的那个客户端挂了,那么只能等待超时时间之后才能释放锁
redlock算法实现:
假设有5个完全独立的redis主服务器
获取当前时间戳
client尝试按照顺序使用相同的key,value获取所有redis服务的锁,在获取锁的过程中的获取时间比锁过期时间短很多,这是为了不要过长时间等待已经关闭的redis服务。并且试着获取下一个redis实例。比如:TTL为5s,设置获取锁最多用1s,所以如果一秒内无法获取锁,就放弃获取这个锁,从而尝试获取下个锁
client通过获取所有能获取的锁后的时间减去第一步的时间,这个时间差要小于TTL时间并且至少有3个redis实例成功获取锁,才算真正的获取锁成功
如果成功获取锁,则锁的真正有效时间是 TTL减去第三步的时间差 的时间;比如:TTL 是5s,获取所有锁用了2s,则真正锁有效时间为3s(其实应该再减去时钟漂移);
redlock的争议点:(fgc导致的问题)
对于提升效率的场景下,RedLock 太重。
对于对正确性要求极高的场景下,RedLock 并不能保证正确性。
3pc 将2pc中的一阶段拆为 canCommit和prepareCommit
二阶段提交有几个缺点:
二阶段无法解决的问题:协调者再发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。
paxos算法的推导,
如果只有一个人投票的话,那么每个人必须接受第一个提议。
这样又会导致三个人分别投不同的票,形不成大多数。
推导出可以选多次票,但多次票有可能投自己又投别人,形成多个大多数,所以又加了限制条件,只能同意之前同意过的。
再推导出当两个机子重连的话,机子必须接受第一个发给他的提案,这样就违背了之前选好的。
所以当服务器重连的时候,必须发给他之前同意好的提案。
https://blog.51cto.com/12615191/2086264
zab协议:
原子广播:
崩溃恢复:
针对这些问题,ZAB 定义了 2 个原则:
ZAB 协议确保那些已经在 Leader 提交的事务最终会被所有服务器提交。
ZAB 协议确保丢弃那些只在 Leader 提出/复制,但没有提交的事务。
如果让 Leader 选举算法能够保证新选举出来的 Leader 服务器拥有集群总所有机器编号(即 ZXID 最大)的事务,那么就能够保证这个新选举出来的 Leader 一定具有所有已经提交的提案。
第一层:service 层,接口层,给服务提供者和消费者来实现的
第二层:config 层,配置层,主要是对 dubbo 进行各种配置的
第三层:proxy 层,服务代理层,无论是 consumer 还是 provider,dubbo 都会给你生成代理,代理之间进行网络通信
第四层:registry 层,服务注册层,负责服务的注册与发现
第五层:cluster 层,集群层,封装多个服务提供者的路由以及负载均衡,将多个实例组合成一个服务
第六层:monitor 层,监控层,对 rpc 接口的调用次数和调用时间进行监控
第七层:protocal 层,远程调用层,封装 rpc 调用
第八层:exchange 层,信息交换层,封装请求响应模式,同步转异步
第九层:transport 层,网络传输层,抽象 mina 和 netty 为统一接口
服务消费方(client)调用以本地调用方式调用服务;
client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;
client stub找到服务地址,并将消息发送到服务端;
server stub收到消息后进行解码;
server stub根据解码结果调用本地的服务;
本地服务执行并将结果返回给server stub;
server stub将返回结果打包成消息并发送至消费方;
client stub接收到消息,并进行解码;
MESI协议保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。
(另外一种硬件层面的解决是总线锁)
用途:类似文件系统的分布式协调服务。
选举原理:
Zookeeper集群中只有超过半数以上的服务器启动,集群才能正常工作;
在集群正常工作之前,myid小的服务器给myid大的服务器投票,直到集群正常工作,选出Leader;
适用场景:
命名服务
配置管理
集群管理
分布式锁
客户端注册Watcher到服务端;
服务端发生数据变更;
服务端通知客户端数据变更;
一致性又可以分为强一致性与弱一致性。
强一致性可以理解为在任意时刻,所有节点中的数据是一样的。同一时间点,你在节点A中获取到key1的值与在节点B中获取到key1的值应该都是一样的。
弱一致性包含很多种不同的实现,目前分布式系统中广泛实现的是最终一致性。
zookeeper,判断某个节点下的子节点到达一定数目后,则执行,否则等待。
MQ系统的数据如何保证不丢失
发送消息后和接收消息后 确认机制 加上持久化
唯一主键,或者使用redis做id,
避免阻塞
https://www.cnblogs.com/leaves1024/p/11073191.html
https://blog.csdn.net/chizizhixin/article/details/78563595
https://blog.csdn.net/lsh2366254/article/details/84910011
使用同一个queue
实时队列采用双队列模式,生产者将行为记录写入Queue1,worker服务从Queue1消费新鲜数据,如果异常则写入Queue2(主要保存异常数据),RetryWorker会监听Queue2,消费异常数据,如果还未处理成功按照一定的策略等待或者将异常数据再写入Queue2,如果数据发生积压可以调整worker的消费游标,从最新数据重新开始消费,保证了最新data得到处理,中间未处理的一段则可以启动backupWorker指定起止游标在消费完指定区间的数据后,backupWorker会自动停止。
Copyright© 2013-2019