(1)分布式协调:这个其实是zk很经典的一个用法,简单来说,就好比,你A系统发送个请求到mq,然后B消息消费之后处理了。那A系统如何知道B系统的处理结果?用zk就可以实现分布式系统之间的协调工作。A系统发送请求之后可以在zk上对某个节点的值注册个监听器,一旦B系统处理完了就修改zk那个节点的值,A立马就可以收到通知,完美解决。
(2)分布式锁:对某一个数据连续发出两个修改操作,两台机器同时收到了请求,但是只能一台机器先执行另外一个机器再执行。那么此时就可以使用zk分布式锁,一个机器接收到了请求之后先获取zk上的一把分布式锁,就是可以去创建一个znode,接着执行操作;然后另外一个机器也尝试去创建那个znode,结果发现自己创建不了,因为被别人创建了。。。。那只能等着,等第一个机器执行完了自己再执行。
(3)元数据/配置信息管理:zk可以用作很多系统的配置信息的管理,比如kafka、storm等等很多分布式系统都会选用zk来做一些元数据、配置信息的管理,包括dubbo注册中心不也支持zk么
(4)HA高可用性:这个应该是很常见的,比如hadoop、hdfs、yarn等很多大数据系统,都选择基于zk来开发HA高可用机制,就是一个重要进程一般会做主备两个,主进程挂了立马通过zk感知到切换到备用进程
首先Java提供的原生锁机制(syncronized,lock)无法保证在分布式系统中,高并发场景对共享数据的修改问题(如超卖问题),此时我们需要使用分布式锁;
其解决的就是不同机器上的多线程对共享资源的正确访问;
分布式锁有三种实现方式:
1 基于数据库实现分布式锁;
1 悲观锁
利用select...where...for update
排他锁
但是需要注意,where collection = lock
,这个collection
字段必须要走索引,否则会锁表. 有些情况下,比如表不大, MySQL优化器会不走这个索引, 导致锁表问题;
2 乐观锁
实现方式有二: CAS+乐观锁 -> CAS会有ABA的问题,加上乐观锁的话,就可以解决(友情参考AtomicReference和AtomicStampeReference,后者可以解决前者CAS计算中的ABA问题)
缺点:
2 基于Redis
实现分布式锁;
使用命令
(1) SETNX
SETNX key val: 当且仅当key不存在时, set一个key为val的字符串,若key不存在进行保存,返回1; 若key存在则什么都不做, 返回0;
(2) expire
expire key timeout:为key设置一个超时时间,若超过这个时间,锁会自动释放, 避免死锁;
(3) delete
delete key: 删除key
实现思想
(1) 获取锁的时候,使用sentnx加锁, 并使用expire命令为锁添加一个超时时间, 超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,用来在释放锁的时候进行判断;
(2) 获取锁的时候还设置一个获取的超时时间, 若超过这个时间则放弃获取锁;
(3) 释放锁的时候,通过UUID判断是不是该锁, 若是该锁, 则执行delete进行锁释放;
Redis有自己专门实现的分布式锁:Redlock
缺点:
3 基于Zookeeper
实现分布式锁;
作为一个为分布式应用提供一致性服务的开源组件, 其实现分布式锁的原理如下:
方案:
可使用ZK第三方库Curator
客户端,其直接封装了一个可重入的所服务
Curator提供的InterProcessMutex是分布式锁的实现。acquire方法用户获取锁,release方法用于释放锁。
缺点:
分布式锁 | 优点 | 缺点 |
---|---|---|
Zookeeper | 有等待锁的队列,大大提升抢锁效率 | 添加和删除节点性能低 |
Redis | Set和Del指令的性能较高 | 没有等待锁的队列,只能在客户端自旋来等锁,效率低下 |
三种方案的比较:
从理解的难易程度角度(从低到高)数据库 > 缓存 > Zookeeper
从实现的复杂性角度(从低到高)Zookeeper >= 缓存 > 数据库
从性能角度(从高到低)缓存 > Zookeeper >= 数据库
从可靠性角度(从高到低)Zookeeper > 缓存 > 数据库
spring session + redis
<dependency>
<groupId>org.springframework.sessiongroupId>
<artifactId>spring-session-data-redisartifactId>
<version>1.2.1.RELEASEversion>
dependency>
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>2.8.1version>
dependency>
# spring配置文件
<bean id="redisHttpSessionConfiguration"
class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
<property name="maxInactiveIntervalInSeconds" value="600"/>
bean>
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="100" />
<property name="maxIdle" value="10" />
bean>
<bean id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" destroy-method="destroy">
<property name="hostName" value="${redis_hostname}"/>
<property name="port" value="${redis_port}"/>
<property name="password" value="${redis_pwd}" />
<property name="timeout" value="3000"/>
<property name="usePool" value="true"/>
<property name="poolConfig" ref="jedisPoolConfig"/>
bean>
# web.xml
<filter>
<filter-name>springSessionRepositoryFilterfilter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxyfilter-class>
filter>
<filter-mapping>
<filter-name>springSessionRepositoryFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
@Controller
@RequestMapping("/test")
public class TestController {
@RequestMapping("/putIntoSession")
@ResponseBody
public String putIntoSession(HttpServletRequest request, String username){
request.getSession().setAttribute("name", “leo”);
return "ok";
}
@RequestMapping("/getFromSession")
@ResponseBody
public String getFromSession(HttpServletRequest request, Model model){
String name = request.getSession().getAttribute("name");
return name;
}
}
给sping session配置基于redis来存储session数据,然后配置了一个spring session的过滤器,这样的话,session相关操作都会交给spring session来管了。接着在代码中,就用原生的session操作,就是直接基于spring sesion从redis中获取数据了。
事务解决的是ACID问题,分布式事务就是在集群环境下,如何使数据从一个一致性状态到另一个一致性的状态。
分布式事务方案
方案1: XA方案
XA方案:即两阶段提交方案
两阶段提交,有一个事务管理器的概念,负责协调多个数据库(资源管理器)的事务,事务管理器先问问各个数据库准备好了吗? 如果每个数据库都恢复 OK, 那么就正式提交事务,在各个数据库上执行操作, 如果任何一个数据库回答了 NO, 那么就回滚事务;
这种分布式事务,比较适合单块应用里, 跨多个库的分布式事务, 而且因为严重依赖于数据库层面来搞定复杂的事务, 效率很低, 不适合高并发的场景;
方案2: TCC方案
TCC 的全程是: Try Confirm Cancel
该方案其实是用到了补偿的概念,分为三个阶段:
举例:跨行转账
/**
1)Try阶段:先把两个银行账户中的资金给它冻结住就不让操作了
2)Confirm阶段:执行实际的转账操作,A银行账户的资金扣减,B银行账户的资金增加
3)Cancel阶段:如果任何一个银行的操作执行失败,那么就需要回滚进行补偿,就是比如A银行账户如果已经扣减了,
但是B银行账户资金增加失败了,那么就得把A银行账户资金给加回去
*/
这种方案说实话几乎很少用人使用,我们用的也比较少,因为这个事务回滚实际上是严重依赖于你自己写代码来回滚和补偿了,会造成补偿代码巨大,非常之恶心。
但是也有使用的场景:一致性要求极高,是你系统中核心之核心的场景,比如常见的就是资金类的场景,那你可以用TCC方案了,自己编写大量的业务逻辑,自己判断一个事务中的各个环节是否ok,不ok就执行补偿(回滚代码)
方案3: 本地消息表
国外 ebay提出并使用的该套思想
大致思想:
该方案保证了最终一致性,哪怕事务B失败了,但是A会不断重发消息,直到B那边成功为止
但是,其最大的问题在于严重依赖于数据库的消息表来管理事务. 一般使用也很少
方案4: 可靠消息最终一致性方案
该方案放弃了使用本地消息表,而是直接基于MQ实现事务;比如阿里的RocketMQ就支持消息事务;
大致思想:
该方案是我们最常用到的方案
你们公司是如何做的选择?
我们在像放款这种特别严格的场景,用的是TCC来保证强一致性;
然后其他的一些场景基于了阿里的RocketMQ来实现了分布式事务。
但是分布式事务,在绝大多数情况下是不需要使用的,因为无论我们选择哪个,都会导致你那块儿代码会复杂10倍。很多情况下,系统A调用系统B、系统C、系统D,我们可能根本就不做分布式事务。如果调用报错会打印异常日志
因此99%的分布式接口调用,不要做分布式事务,直接就是监控(发邮件、发短信)、记录日志(一旦出错,完整的日志)、事后快速的定位、排查和出解决方案、修复数据。
Dubbo
为什么拆分?
如果不拆分系统, 开发效率极其低下,问题很多. 但是拆分系统之后, 每个人负责自己的小部分,可以大幅度提升复杂系统大型团队的开发效率.
你们系统如何拆分的?
为什么要用Dubbo? 不用可以吗?
如何不用Dubbo(或者Cloud), 也是可以的,只不过就是各个系统之间直接基于SpringMVC,纯Http接口相互通信. 但是你要考虑多台系统弄之间的超时重试,负载均衡等各种问题,维护成本太高;
而Dubbo,它是一种 RPC 架构, 也就是本地执行接口调用的时候Dubbo会代理这个调用请求, 跟远程机器网络通信, 帮你处理负载均衡, 服务实例上下线自动感知, 超时重试等问题.
Remote Procedure Call 即远程过程调用, 允许一台计算机调用另一台计算机上的程序得到结果,而代码中不需要做额外的编程,就像在本地调用一样;
RPC框架中主要有三个角色:provider,Consumer和Registry;分别是暴露服务的服务提供方,调用远程服务的服务消费方和服务注册与发现的注册中心
服务提供方将服务注册到注册中心,服务调用方像注册中心发起订阅请求得到响应,服务调用方调用服务提供方提供的服务;
Dubbo工作原理:共10层
首先我们要知道,注册中心最好做集群,这样任意一台宕机后,将自动切换到另一台.如果单注册表中心,就不能保证宕机切换的问题. 但是注册中心全部宕掉之后,服务提供者和服务消费者扔能通过本地缓存通讯;
Consumer 端在发起调用之前会先走 filter 链;provider 端在接收到请求时也是先走 filter 链,然后才进行真正的业务逻辑处理。默认情况下,在 consumer 和 provider 的 filter 链中都会有 Monitorfilter。
1、MonitorFilter 向 DubboMonitor 发送数据
2、DubboMonitor 将数据进行聚合后(默认聚合 1min 中的统计数据)暂存到ConcurrentMap
3、SimpleMonitorService 将这些聚合数据塞入 BlockingQueue queue 中(队列大写为 100000)
4、SimpleMonitorService 使用一个后台线程(线程名为:DubboMonitorAsyncWriteLogThread)将 queue 中的数据写入文件(该线程以死循环的形式来写)
5、SimpleMonitorService 还会使用一个含有 1 个线程(线程名字:DubboMonitorTimer)的线程池每隔 5min 钟,将文件中的统计数据画成图表
注册中心分组实现测试和生产环境的隔离
1)dubbo协议
dubbo://192.168.0.1:20188
默认就是走dubbo协议的,单一长连接,NIO异步通信,基于hessian作为序列化协议
适用的场景就是:传输数据量很小(每次请求在100kb以内),但是并发量很高
为了要支持高并发场景,一般是服务提供者就几台机器,但是服务消费者有上百台,可能每天调用量达到上亿次!此时用长连接是最合适的,就是跟每个服务消费者维持一个长连接就可以,可能总共就100个连接。然后后面直接基于长连接NIO异步通信,可以支撑高并发请求。
否则如果上亿次请求每次都是短连接的话,服务提供者会扛不住。
而且因为走的是单一长连接,所以传输数据量太大的话,会导致并发能力降低。所以一般建议是传输数据量很小,支撑`高并发访问。
2)rmi协议
走java二进制序列化,多个短连接,适合消费者和提供者数量差不多,适用于文件的传输,一般较少用
3)hessian协议
走hessian序列化协议,多个短连接,适用于提供者数量比消费者数量还多,适用于文件的传输,一般较少用
4)http协议
走json序列化
5)webservice
走SOAP文本序列化
所以dubbo实际基于不同的通信协议,支持hessian、java二进制序列化、json、SOAP文本序列化多种序列化协议。但是hessian是其默认的序列化协议。
负载均衡策略
1)random loadbalance
默认情况下,dubbo是random load balance随机调用实现负载均衡,可以对provider不同实例设置不同的权重,会按照权重来负载均衡,权重越大分配流量越高。
2)roundrobin loadbalance
还有roundrobin loadbalance,这个的话默认就是均匀地将流量打到各个机器上去,但是如果各个机器的性能不一样,容易导致性能差的机器负载过高。所以此时需要调整权重,让性能差的机器承载权重小一些,流量少一些。
3)leastactive loadbalance
这个就是自动感知一下,如果某个机器性能越差,那么接收的请求越少,越不活跃,此时就会给不活跃的性能差的机器更少的请求
4)consistanthash loadbalance
一致性Hash算法,相同参数的请求一定分发到一个provider上去,provider挂掉的时候,会基于虚拟节点均匀分配剩余的流量,抖动不会太大。如果你需要的不是随机负载均衡,是要一类请求都到一个节点,那就走这个一致性hash策略。
集群容错策略
1)failover cluster模式
失败自动切换,自动重试其他机器,默认就是这个,常见于读操作
2)failfast cluster模式
一次调用失败就立即失败,常见于写操作
3)failsafe cluster模式
出现异常时忽略掉,常用于不重要的接口调用,比如记录日志
4)failback cluster模式
失败了后台自动记录请求,然后定时重发,比较适合于写消息队列这种
5)forking cluster
并行调用多个provider,只要一个成功就立即返回
6)broadcacst cluster
逐个调用所有的provider
动态代理策略
默认使用 javassist 动态字节码生成,创建代理类
但是可以通过spi扩展机制配置自己的动态代理策略
SPI思想
SPI:Service Provider Interface
微内核,可插拔,大量的组件,Protocol负责rpc调用的东西,你可以实现自己的rpc调用组件,实现Protocol接口,给自己的一个实现类即可。
Dubbo系统在运行的会后,会判断应该选择Protocol接口的哪个实现类来实例化对象.它会去找一个你配置的Protocol,将你配置的Protocol实现类加载到JVM中来, 然后实例化对象,如果你没有特殊的实现和配置,就走默认的实现;
SPI机制是怎么玩的
Dubbo的Protocol接口
@SPI("dubbo")
public interface Protocol {
int getDefaultPort();
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
@Adaptive
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
void destroy();
}
在dubbo自己的jar里,在 /META_INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol 文件中:
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
http=com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
所以说,这就看到了dubbo的spi机制默认是怎么玩儿的了,其实就是Protocol接口,@SPI(“dubbo”)说的是,通过SPI机制来提供实现类,实现类是通过dubbo作为默认key去配置文件里找到的,配置文件名称与接口全限定名一样的,通过dubbo作为key可以找到默认的实现了就是com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol。
dubbo的默认网络通信协议,就是dubbo协议,用的DubboProtocol
如果想要动态替换掉默认的实现类,需要使用@Adaptive接口,Protocol接口中,有两个方法加了@Adaptive注解,就是说那俩接口会被代理实现。
服务治理思想
(1) 调用链路自动生成
基于Dubbo做的分布式系统中, 对各个服务之间的调用自动记录下来, 然后自动将各个服务之间的依赖关系和调用链路生成出来,做成一张图,专门页面显示出来;(系统中workflow执行流程,就是通过日志回写的形式,将服务调用之间的关系的展示)
(2) 服务访问压力以及时长统计
需要自动统计各个接口和服务之间的调用次数以及访问延时,而且要分成两个级别。一个级别是接口粒度,就是每个服务的每个接口每天被调用多少次,TP50,TP90,TP99,三个档次的请求延时分别是多少;第二个级别是从源头入口开始,一个完整的请求链路经过几十个服务之后,完成一次请求,每天全链路走多少次,全链路请求延时的TP50,TP90,TP99,分别是多少。
TP指标: 指在一个时间段内,统计该方法每次调用所消耗的时间,并将这些时间按从小到大的顺序进行排序,并取出结果为:总次数 * 指标数 = 对应TP指标的值, 在取出排序好的时间。
TP50:指在一个时间段内(如5分钟),统计该方法每次调用所消耗的时间,并将这些时间按从小到大的顺序进行排序,取第50%的那个值作为TP50 值;配置此监控指标对应的报警阀值后,需要保证在这个时间段内该方法所有调用的消耗时间至少有50%的值要小于此阀值,否则系统将会报警。
服务降级
Dubbo的服务降级思路:
失败重试和超时重试
所谓失败重试,就是consumer调用provider要是失败了,比如抛异常了,此时应该是可以重试的,或者调用超时了也可以重试。
<dubbo:reference id="xxxx" interface="xx" check="true" async="false" retries="3" timeout="2000"/>
只需要在Dubbo的配置中,将retries和timeout配置出来,比如重试3次,超时时间2000毫秒;
如果重试三次都失败的话,就会走降级策略
所谓幂等性,就是说一个接口,多次发起同一个请求,你这个接口得保证结果是准确的,比如不能多扣款,不能多插入一条数据,不能将统计值多加了1。这就是幂等性;
其实保证幂等性主要是三点:
(1)对于每个请求必须有一个唯一的标识,举个例子:订单支付请求,肯定得包含订单id,一个订单id最多支付一次;
(2)每次处理完请求之后,必须有一个记录标识这个请求处理过了,比如说常见的方案是在mysql中记录个状态(版本号),比如支付之前记录一条这个订单的支付流水,而且支付流水采;
(3)每次接收请求需要进行判断之前是否处理过的逻辑处理,比如说,如果有一个订单已经支付了,就已经有了一条支付流水,那么如果重复发送这个请求,则此时先插入支付流水,orderId已经存在了,唯一键约束生效,报错插入不进去的。然后你就不用再扣款了。
//避免重复支付的例子
/**
比如说用redis用orderId作为唯一键。只有成功插入这个支付流水,才可以执行实际的支付扣款。
要求是支付一个订单,必须插入一条支付流水,order_id建一个唯一键,unique key
所以你在支付一个订单之前,先插入一条支付流水,order_id就已经进去了
你就可以写一个标识到redis里面去,set order_id payed,下一次重复请求过来了,先查redis的order_id对应的value,
如果是payed就说明已经支付过了,你就别重复支付了
然后呢,你再重复支付这个订单的时候,你写尝试插入一条支付流水,数据库给你报错了,说unique key冲突了,整个事务回滚就可以了
来保存一个是否处理过的标识也可以,服务的不同实例可以一起操作redis。
*/
首先,一般来说,我个人给你的建议是,你们从业务逻辑上最好设计的这个系统不需要这种顺序性的保证,因为一旦引入顺序性保障,会导致系统复杂度上升,而且会带来效率低下,热点数据压力过大,等问题;
方案1
使用dubbo的一致性hash负载均衡策略,将比如某一个订单id对应的请求都给分发到某个机器上去,接着就是在那个机器上因为可能还是多线程并发执行的, 因此需要立即将某个订单id对应的请求扔一个内存队列里去,强制排队,这样来确保他们的顺序性。
方案2
使用分布式锁 保证100%的顺序性;但是分布式锁会存在多次获取释放的过程,比较消耗性能;
简单思路:
(1)上来你的服务就得去注册中心注册吧,你是不是得有个注册中心,保留各个服务的信心,可以用zookeeper来做,对吧
(2)然后你的消费者需要去注册中心拿对应的服务信息吧,对吧,而且每个服务可能会存在于多台机器上
(3)接着你就该发起一次请求了,咋发起?蒙圈了是吧。当然是基于动态代理了,你面向接口获取到一个动态代理,这个动态代理就是接口在本地的一个代理,然后这个代理会找到服务对应的机器地址
(4)然后找哪个机器发送请求?那肯定得有个负载均衡算法了,比如最简单的可以随机轮询是不是
(5)接着找到一台机器,就可以跟他发送请求了,第一个问题咋发送?你可以说用netty了,nio方式;第二个问题发送啥格式数据?你可以说用hessian序列化协议了,或者是别的,对吧。然后请求过去了。。
(6)服务器那边一样的,需要针对你自己的服务生成一个动态代理,监听某个网络端口了,然后代理你本地的服务代码。接收到请求的时候,就调用对应的服务代码。
SpringCloud
Spring Cloud 核心包含: Eureka, Ribbon, Feign
Eureka,Ribbon,Feign,Zuul
Eureka服务注册中心: 它是Spring Cloud最关键的模块
其核心有2大功能,一个是服务注册与发现 ;一个是心跳与故障
剩下的Ribbon(负载均衡),Feign(服务调用)都是在Eureka的基础上进行使用的
其工作原理如下:
Eureka之所以维护一个读写缓存和一个只读缓存,是为了解决 并发冲突的问题, 即我们所说的读写需要互斥的问题
接上面Eureka服务注册于发现完成之后,开始进行服务调用:
在进行服务调用的时候,Feign,它是通过对一个接口添加了@FeignClient
注解,所有被@FeignClient
标注的接口会生成动态代理,然后针对动态代理去调用接口中方法的时候,Feign会在底层生成 HTTP 协议格式的请求
.
底层的话,使用HTTP通信的框架组件,HttpClient, 先得使用 Ribbon去本地服务中维护的Eureka注册表的缓存中获取对方机器的列表,然后进行负载均衡,选择出一台机器出来, 接着针对那台机器发送 HTTP 请求过去即可;
(1) 两个服务注册中心的原理
Eureka的服务注册原理在上面已经说过了
Eureka集群架构原理
Eureka集群中,其每一个机器的地位都是对等的,各个服务可以向任何一个 Eureka 实例进行服务注册和服务发现, 集群里任何一个 Eureka 实例接收到写请求之后, 会自动同步给其它所有的Eureka实例
Zookeeper集群原理
Zookeeper服务注册和发现, 其集群中存在 Leader 和 Follower 两种角色,只有Leader可以负责写,也就是服务注册,它会把数据同步给Follower, 读的时候Leader/Follower都可以读
(2) 两个服务注册中心的一致性保证
CP or AP
CAP: C 一致性, A 可用性, P 分区容错性
Zookeeper 采取的是 CP原则
Zookeeper是有一个 Leader 节点会接收数据, 然后同步写其他Follower节点, 一旦 Leader 挂了,要重新选举 Leader, 这个过程为了保证 C, 就牺牲了 A, 不可用一段时间, 但是一个 Leader 选举好了, 那么就可以继续写数据了, 保证强一致性;
Eureka 采取的是 AP原则
Eureka 是 peer 模式, 可能某一个 Eureka 还没同步数据到其它 Eureka,结果自己挂掉, 此时还是可以继续从别的Eureka上拉取注册表, 但是看到的就不是最新的数据了, 但是保证了可用性和最终一致性;
(3) 两个服务注册中心的时效性
Zookeeper
zk 的时效性特别好, 进行服务注册或者有服务挂掉,一般都是秒级的感知
Eureka
Eureka的默认配置非常糟糕, 服务发现感知要到几十秒甚至分钟级别
(4) 两个服务注册中心的容量
Zookeeper, 不适合大规模的服务实例, 因为服务上下线的时候, 需要瞬间推送数据通知到所有的其他服务实例, 所以一旦服务规模太大(几千的服务实例), 就会导致网络宽带被大量占用
Eureka, 也很难支撑大规模的服务实例, 因为每个 Eureka 实例都要接收所有的请求,实例多了压力太大, 扛不住,也很难到几千服务实例
Eureka注册中心集群中,如果有任何一台Eureka挂掉, 我们所有的服务注册和服务发现都会自动转到去其它Eureka实例
Zookeeper注册中心集群, 由于ZK保证的是CP,而放弃了A即可用性,其在高可用上很难达到
Zookeeper做注册中心的时候,基本没有遇到过
但是Eureka 我们在使用的时候必须优化其默认的参数
#eureka server刷新readCacheMap的时间,注意,client读取的是readCacheMap,
#readCacheMap即ReadOnly缓存
#这个时间决定了多久会把readWriteCacheMap的缓存更新到readCacheMap上
#默认30s
eureka.server.responseCacheUpdateIntervalMs = 30000
#eureka client刷新本地缓存时间
#默认30s
eureka.client.registryFetchchintervalSeconds = 30000
#表示eureka client发送心跳给server端的频率
eureka.client.leaseRenewalIntervalInSeconds = 30
#清理间隔
eureka.server.evictionIntervalTimerInMs = 60000
#租期到期时间
eureka.instance.lease.ExpirationDurationInSeconds = 90
#关闭自我保护
eureka.server.enable-self-preservation=false
首先,网关的核心功能:
其次,网关技术类型:
大厂一般自研, 中小公司主要是使用Zuul, 也可以不用网关,直接通过Nginx反向代理,做负载均衡
可通过Apollo配置中心进行操作
首先我们需要知道,所有的请求到后台,最合理的配置应该是先到LVS,通过LVS做负载均衡均匀分发到Nginx上,然后再通过Nginx均匀分发到Zuul集群中
LVS+Nginx是运维工程师需要关注的
Dubbo
通过自定义负载均衡实现灰度发布;
实现
LoadBalance
接口或者继承AbstractLoadBalance
重写策略
根据dubbo SPI发现机制,在resources下添加META-INF/dubbo/com.alibaba.dubbo.rpc.cluster.LoadBalance
逻辑:目标服务的端口和灰度服务端口的一致,并且请求方法的第一个参数类型是Long(userId)并且是灰度用户,则判断为灰度服务,否则按照默认随机调用其余非灰度服务
Spring Cloud
Zuul+数据库配置实现灰度发布
准备一个数据库和一个表(也可以用Apollo配置中心,redis,zk)放一个灰度发布启用表
在Zuul里面开启ZuulFilter(即继承,复写其run方法,在run方法中写具体的灰度发布逻辑)
服务熔断
当下游的服务因为某种原因突然变得不可用或响应过慢,上游服务为了保证自己整体服务的可用性,不再继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。
服务熔断其实是框架级的处理,在Cloud中我们使用的熔断框架是Hystrix
//滑动窗口的大小,默认为20
circuitBreaker.requestVolumeThreshold
//过多长时间,熔断器再次检测是否开启,默认为5000,即5s钟
circuitBreaker.sleepWindowInMilliseconds
//错误率,默认50%
circuitBreaker.errorThresholdPercentage
**以上Hystrix配置表示,每当20个请求中,有50%失败时,熔断器就会打开,此时再调用此服务,将会直接返回失败,不再调远程服务。直到5s钟之后,重新检测该触发条件,判断是否把熔断器关闭,或者继续打开。
这些属于框架层级的实现,我们只要实现对应接口就好!
服务降级
降级方式:
服务降级大多是属于一种业务级别的处理,我们在开发中最常用的降级方式就是开关降级
做法很简单,做个开关,然后将开关放配置中心(数据字典也是可以的)!在配置中心更改开关,决定哪些服务进行降级。
在应用程序中部下开关的这个过程,业内也有一个名词,称为埋点,哪些业务需要埋点?
Spring Cloud 生产优化:
有一个普遍的现象,就是Cloud系统在第一次启动的时候, 人家调用你的应用经常会出现 timeout
这是因为,每个服务每次请求的时候,他会去初始化一个Ribbon的组件, 初始化这些组件需要耗费一定的时间,所以很容易会导致 调用timeout的问题; 我们只需要让每个服务启动的时候就直接初始化 Ribbon 相关的组件,避免第一次请求的时候初始化
ribbon:
eager-load:
enabled: true
zuul:
ribbon:
eager-load:
enabled: true
方式大致有2种:
基于数据库唯一索引
对于插入类操作, 一般都是建议在数据库表中设计一些唯一索引
举例订单发货操作:
我们可以将order_id设置为唯一索引,插入发货单的时候,同一个order_id就只能创建一个发货单
基于Redis实现一套幂等性防重框架
对于像扣减库存,累加积分这种更新操作, 我们可以基于 Redis 实现一套接口的防重框架
首先需要在拦截器中,拦截所有的请求,对所有请求的参数进行处理
将参数+接口名称 拼接在一起,作为key去redis中判断一下,是否存在这个key, 之前附加这些参数的请求是否发起过,如果没有就可以把这些参数+接口名称 作为一个key 存储到redis中
然后把请求放行,去执行这个请求(如果请求失败可以将redis中的key删除)
如果说调用方重试再次发起这个请求,此时就可以判断出参数组成的key在redis中已经存在了, 我们就任务是重复调用
Dubbo 的定位始终是一款 RPC 框架,而 Spring Cloud 的目标是微服务架构下的一站式解决方案。
因此我们可以将Dubbo看做是Spring Cloud的一个子集
除此之外 Spring Cloud 还提供了配置、消息、安全、调用链跟踪等分布式问题解决方案。这都是Dubbo所不具备的