1、为什么拆分微服务
拆分的原因有两点:
1)便于快速需求变更和灵活上线。
2)便于水平扩展。
拆分的问题影响不大:
1)微服务的部署运维需要大量机器,可以使用rancher管理docker镜像,进行部署发布管理;
2)分布式事务和服务雪崩问题,我们的微服务之间没有调用,都是给前端调的,所以也不存在这个问题。
2、工作原理与流程
dubbo的技术架构分10层,分别是:
Service\config\proxy\registry\cluster\monitor\protocol\exchange\transport\serialize。
dubbo的工作流程是:
privider到服务注册中心暴露服务接口;
服务注册中心将服务地址列表推送给订阅了这某个服务的consumer;
Consumer通过服务目录,选择一个privider进行调用;
privider和consumer都接入monitor进行监控。
3、Config配置
服务提供者:
在服务提供者中,通过配置文件配置注册到服务注册中心的信息,具体包括:
spring.application.name应用名称
Spring.dubbo.server是否服务端
spring.dubbo.registry注册中心地址
spring.dubbo.protocol.name协议名称
spring.dubbo.protocol.port协议端口
之后,在需要发布的service类上,添加注解@Service(version=“1.0.0”)就可以了。
服务消费者:
服务消费者,通过配置文件配置注册到服务注册中心的信息,包括:
spring.application.name 应用名称
spring.dubbo.registry注册中心地址
之后,在需要调服务接口的地方,添加服务的引用,即:
@Reference(version = "1.0.0")
CityDubboService cityDubboService;
此外:
为了能让服务消费者中,能够使用服务的对象类,所以需要在服务提供者里把发布的服务打成jar包,然后给服务消费者用。
4、Proxy动态代理
dubbo支持jdk\javassist两种动态代理,默认使用javassist。
JDKProxy和JavassistProxy比较:
1)JDKProxy只支持接口实现类的动态代理;
2)JavassistProxy还支持非接口实现类的动态代理。
5、Registry负载均衡
Dubbo支持多种负载均衡策略,可用于多个provider的选择。
在Reference注解的参数里,配置loadBalanace:
1)Random:表示完全随机
2)RoundRobin:表示一个一个换着来,轮循
3)ConsistentHash:相同参数的请求,发到同一个provider。
6、Cluster高可用重试
Dubbo支持多种高可用策略,可用于服务挂了之后的选择:
在Reference注解的参数里,配置cluster:
1)Failover:挂了以后,读另一个
2)Failback:挂了以后,重发
3)Forking:并发调多个Provider,效率高,只要有一个返回就行
4)Broadcast:广播,一个一个调所有的Provider.
另外,还可以配置retries,来明确失败了以后,重试几次。
7、Protocol通信协议
dubbo支持多种通信协议,包括dubbo、rmi、hessian、thrift等等,一般我们用两种:
dubbo协议:主要用于高并发且数据量小的场景,consumer多,privider少,每次传输数据小于100K。采用NIO建立consumer和privider之间的长连接。
rmi协议:主要用于数据量大的场景,一旦数据量大了,dubbo协议的效率就低了,用rmi协议效率提高不少。
8、Serialize序列化协议
Dubbo支持多种序列化协议,包括hessian2\fastJson\JDK自带的等,一般用hessian2的序列化、反序列化效率最高。
9、SPI原理
Service provider interface,是用来实现dubbo接口多个实现的扩展。
dubbo内部有很多扩展点,也就是一个接口对应多个实现类,我们都使用@SPI给注解出来了。同时,在resources的META-INF文件夹下,建一个dubbo.internal文件夹,里面放一个接口全限定名的文件,里面标出xxxx= xxxx.xxxx.ddd.ccImpl,方便调用。
dubbo在使用ExtentionLoader加载扩展点时,会根据扩展点的配置,选择当前使用哪一个实现类,选择的方式包括:
1)在@SPI的参数里面,标出使用哪个实现类;
2)在某个实现类上,加@Adaptive注解。
10、Filter
Dubbo支持使用过滤器,Filter也是Dubbo的一个扩展点,支持多种Filter,Filter会在Provider方法前执行。
Dubbo自定义Filter可以通过实现Filter接口来实现,方法有两种:
1)第一种,在实现类上加@Activate来加载,这种定义的Filter会变成Dubbo的原生filter调用链的一部分。
2)第二种,直接在consumer的@Reference注解里,或者provider的@Service注解里面,配置参数filter。这种情况,我们不光可以配置使用哪个filter,还可以指定filter的执行顺序。默认情况下,dubbo原生的filter会先执行,然后再执行自定义的filter,通过配置,我们可以改变这种顺序,比如:
——配置成filter1,default,filter2,意思是先执行filter1,再执行dubbo原生filter,再执行filter2
——配置成filter1,filter2,-token,意思是先执行dubbo原生filter,再执行filter1和filter2,但是dubbo原生中的tokenFilter不执行,被减掉了。
11、链路跟踪
所谓链路跟踪,就是跟踪dubbo微服务的调用链路,由于dubbo微服务可能一层调一层,调好多层,所以需要知道到底是谁调了谁。
链路跟踪的方法可以借助Filter实现,声明一个Filter,给所有的provider和consumer调用,在这个Filter当中通过UUID生成一个唯一的全局TraceId,这个TraceId会从头传到尾不变化,这样就知道了这条调用链路。
@Activate(group = {"provider","consumer"},order = -9999)
public class TraceFilter implements Filter {
@Override
public Result invoke(Invoker> invoker, Invocation invocation) throws RpcException {
String traceId= invocation.getAttachment("traceId");
if(StringUtils.isBlank(traceId)){
traceId = UUID.randomUUID().toString();
}
RpcContext.getContext().setAttachment("traceId", traceId);
System.out.println(invoker.getInterface()+" Method: "+invocation.getMethodName()+" TraceId:"+traceId);
return invoker.invoke(invocation);
}
}
12、服务降级
dubbo自带了一套服务降级的方法,只要在consumer的@Reference注解里配置mock,然后实现一个Mock类就可以自动降级了。
13、如何保证微服务调用的幂等性
与消息中间件类似,当consumer调用provider时,有时可能由于通信故障或者超时,导致没调用成功,就会重试调用。这时provider就会收到两条请求,我们要保证服务调用的幂等性。
与消息中间件类似,保证幂等性的方法,是通过业务层面来保证,常规的办法就是:
1)为每一次执行请求增加一个唯一的ID,我们在操作时,先去数据库里查一下是不是已经执行过了,如果已经执行过了,那就不执行。
2)引入Redis,同样也为每一次执行请求增加一个唯一ID,在我们操作时,查看Redis里有没有这个ID的纪录,如果没有,往Redis里面插一条这个ID的记录,执行服务调用;如果有,则不执行。
14、如何保证微服务执行的顺序性
要保证微服务执行的顺序性,需要依赖dubbo的一致性哈希负载均衡策略。
由于dubbo服务是分布式部署的,请求有可能发送到多个provider,执行的顺序就乱了。而使用一致性哈希负载均衡策略,参数一样的请求都会发送给同一个provider执行,这样就保证了服务调用的顺序性。
如果这个provider内部是用多线程来执行的,那我们为了保证多线程不会影响执行顺序,还可以将请求先放到一个队列里,强行排队执行。
15、如何设计一个微服务框架
一个完整的微服务框架至少需要包含如下几个部分:
1)服务注册中心,保存服务目录,让consumer能够获取provider的情况;
2)服务代理,通过动态代理代理consumer和provider,实现服务之间的调用;
3)因为是分布式的,所以需要支持负载均衡策略,如loadBalance和ribbon;
4)为保证高可用,还需要支持故障重试,和服务降级;
5)服务之间通信,是通过RPC走,还是走restful,如果走RPC,那可以用netty通信;如果走restful,那就直接走httpClient调。
6)如果走RPC,还需要序列化对象;而restful走的是json;
7)看需不需要服务网关,统一服务的入口,如果要的话,还可以参与gateway或者zuul。