微服务设计与实现
互联网架构发展历程
单一架构/All in one-ORM
| 垂直应用架构|MVC-Struts|SpringMVC
|分布式应用架构-RPC WebService|AVRO|Thrift-SOA
|流动式计算架构-弹性服务|SOA治理-Dubbo|SpingCloud
什么微服务?
微服务,又叫微服务架构,是一种软件架构方式。它将应用构建成一系列按业务领域划分模块的、小的自治服务。 在微服务架构中,每个服务都是自我包含的,并且实现了单一的业务功能。微服务是SOA(Service Oriented Architecture)设计思想下诞生产物.
微
: 单一/独立/易部署/可扩展/持续集成
微服务设计历程
RPC 实现- 服务间通信 | 服务拆分 | 服务治理-容错|负载均衡|服务治理
RPC 实现- 服务间通信
系统间通信:串口通信|蓝牙|网络
|射频
网络
: Http协议 (Http服务端编程-tomcat |Http客户端编程-RestTemplate/HttpClient – 应用层协议 高级
TCP/IP(可靠传输)
| UDP(不可靠): 传输层协议,属于较为底层协议,通讯效率高 优于 HTTP****
基于NIO网络通讯框架Netty使用基于TCP/IP
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>5.0.0.Alpha2</version>
</dependency>
服务端编程
//1.创建服务启动引导
ServerBootstrap sbt=new ServerBootstrap();
//2.设置请求转发和响应线程池
EventLoopGroup boss=new NioEventLoopGroup();
EventLoopGroup worker=new NioEventLoopGroup();
//3.设置线程池
sbt.group(boss,worker);
//4.设置服务器实现类
sbt.channel(NioServerSocketChannel.class);
//5.初始化通讯管道-重点
sbt.childHandler(new ServerChannelInitializer());
//6.设置服务器端口并启动服务器
System.out.println("我是服务器....");
ChannelFuture future = sbt.bind(9999).sync();
//7.关闭服务器通讯管道
future.channel().closeFuture().sync();
//8.释放资源
boss.shutdownGracefully();
worker.shutdownGracefully();
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
//添加最终处理者
pipeline.addLast(new ServerChannelHandlerAdapter());
}
}
public class ServerChannelHandlerAdapter extends ChannelHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.err.println("错了:"+cause.getMessage());
}
//接收客户端请求,并给出响应
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf= (ByteBuf) msg;
System.out.println("服务器收到:"+byteBuf.toString(CharsetUtil.UTF_8));
byte[] bytes = new Date().toString().getBytes();
ByteBuf buf=ctx.alloc().buffer();
buf.writeBytes(bytes);
ChannelFuture future = ctx.writeAndFlush(buf);
//关闭通道
future.addListener(ChannelFutureListener.CLOSE);
}
客户端编程
//1.创建客户端启动引导
Bootstrap bt=new Bootstrap();
//2.设置请求转发和响应线程池
EventLoopGroup worker=new NioEventLoopGroup();
//3.设置线程池
bt.group(worker);
//4.设置服务器实现类
bt.channel(NioSocketChannel.class);
//5.初始化通讯管道-重点
bt.handler(new ClientChannelInitializer());
//6.设置服务器端口并启动服务器
ChannelFuture future = bt.connect("127.0.0.1",9999).sync();
//7.关闭服务器通讯管道
future.channel().closeFuture().sync();
//8.释放资源
worker.shutdownGracefully();
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
public class ClientChannelInitializer extends ChannelInitializer<SocketChannel> {
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
//添加最终处理者
pipeline.addLast(new ClientChannelHandlerAdapter());
}
}
public class ClientChannelHandlerAdapter extends ChannelHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.err.println("错了:"+cause.getMessage());
}
//发送数据给服务器
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
byte[] bytes = "你好,我是客户端!".getBytes();
ByteBuf byteBuf= ctx.alloc().buffer();
byteBuf.writeBytes(bytes);
ctx.writeAndFlush(byteBuf);
}
//接收服务器响应
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf= (ByteBuf) msg;
System.out.println("客户端收到:"+buf.toString(CharsetUtil.UTF_8));
}
}
Netty如何捕获序列化异常?
注册异常捕获,并且在出错的时候关闭连接
ChannelFuture future = ctx.writeAndFlush(new User());
//注册序列化异常处理
future.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
//序列化出错关闭连接
future.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
Netty如何在Channel中发送对象?
- MessageToMessageEncoder(编码器,作用于输出方向)
public class CustomMessageToMessageEncoder extends MessageToMessageEncoder<Object> {
/**
*
* @param ctx
* @param o 需要编码的对象
* @param out 将编码的对象放置到输出的数据帧中即可
* @throws Exception
*/
protected void encode(ChannelHandlerContext ctx, Object o, List<Object> out) throws Exception {
System.out.println("编码...");
ByteBuf buf=ctx.alloc().buffer();
byte[] bytes = SerializationUtils.serialize((Serializable) o);
buf.writeBytes(bytes);
out.add(buf);
}
}
- MessageToMessageDecoder(解码器,作用于输入方向)
public class CustomMessageToMessageDecoder extends MessageToMessageDecoder<ByteBuf> {
/**
*
* @param ctx
* @param byteBuf :需要解码的数据
* @param out :需要将就解码的对象放置到数据帧中
* @throws Exception
*/
protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List<Object> out) throws Exception {
System.out.println("解码...");
byte[] bytes=new byte[byteBuf.readableBytes()];
byteBuf.readBytes(bytes);//将bytebuf中的数据读入到bytes数值中
//解码数据
Object o= SerializationUtils.deserialize(bytes);
//将解码的数据放置到数据帧中
out.add(o);
}
}
挂载编解码器
//服务端
ChannelPipeline pipeline = socketChannel.pipeline();
//添加对象解码器
pipeline.addLast(new CustomMessageToMessageDecoder());
//添加对象编码器
pipeline.addLast(new CustomMessageToMessageEncoder());
//添加最终处理者
pipeline.addLast(new ServerChannelHandlerAdapter());
//客户端
ChannelPipeline pipeline = socketChannel.pipeline();
//添加对象解码器
pipeline.addLast(new CustomMessageToMessageDecoder());
//添加对象编码器
pipeline.addLast(new CustomMessageToMessageEncoder());
//添加最终处理者
pipeline.addLast(new ClientChannelHandlerAdapter());
如何解决Netty在网络传输过程中的半包和粘包问题?
采取策略是先编码再编帧,解码前先接帧
注意:一定是给数据添加
固定
大小的数据帧头
ChannelPipeline pipeline = socketChannel.pipeline();
//添加数据帧解码器
pipeline.addLast(new LengthFieldBasedFrameDecoder(65535,0,2,0,2));
//添加对象帧编码器
pipeline.addLast(new LengthFieldPrepender(2));
//添加对象解码器
pipeline.addLast(new CustomMessageToMessageDecoder());
//添加对象编码器
pipeline.addLast(new CustomMessageToMessageEncoder());
//添加最终处理者
pipeline.addLast(new ServerChannelHandlerAdapter());
Netty网络编程流程图
微服务架构设计
微服务架构之Dubbo |ˈdʌbəʊ|
Apache Dubbo™ (incubating)是一款高性能Java RPC框架 。Apache Dubbo (incubating) |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用
,智能容错和负载均衡
,以及服务自动注册和发现
。
Dubbo 特性一览表
Dubbo和项目集成
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.5</version>
</dependency>
<!--zk 注册中心 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.7.1</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.7.1</version>
</dependency>
服务提供端
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!--配置dubbo框架,应用信息-->
<dubbo:application name="demo-service-provider"/>
<!--配置zookeeper注册中心-->
<dubbo:registry protocol="zookeeper" address="CentOS:2181"
client="curator"/>
<!--配置netty服务端网络服务器信息-->
<dubbo:protocol name="dubbo" port="20880"/>
<!--注册服务提供方-->
<dubbo:service interface="com.xxx.service.IDemoService"
ref="demoService" />
</beans>
服务消费方
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!--配置dubbo框架,应用信息-->
<dubbo:application name="demo-service-consumer"/>
<!--配置zookeeper注册中心-->
<dubbo:registry protocol="zookeeper" address="CentOS:2181"
client="curator"/>
<!--创建服务引用代理bean信息-->
<dubbo:reference id="demoService"
loadbalance="roundrobin"
interface="com.xxx.service.IDemoService"/>
</beans>
参考:https://github.com/apache/incubator-dubbo-samples/tree/master/dubbo-samples-basic
服务注册形式
dubbo/
└── providers
├── dubbo://172.16.12.2:20880/com.xxx.service.IDemoService?anyhost=true&application=demo-service-provider&bean.name=com.xxx.service.IDemoService&dubbo=2.0.2&generic=false&interface=com.xxx.service.IDemoService&methods=invoke,sum,multi&pid=12492&side=provider×tamp=1550458014537
├── dubbo://172.16.12.2:20881/com.xxx.service.IDemoService?anyhost=true&application=demo-service-provider&bean.name=com.xxx.service.IDemoService&dubbo=2.0.2&generic=false&interface=com.xxx.service.IDemoService&methods=invoke,sum,multi&pid=12492&side=provider×tamp=1550458014537
Spring Boot 构建 Duubo
参考:http://start.dubbo.io/
更多使用&整合案例参考:https://github.com/apache/incubator-dubbo-samples
Duubo常见策略
- 注册中心:
Zookeeper
/Redis - 通信协议: Dubbo协议 / Rmi协议 / Hessian协议
- 网络传输:
Netty Transporter
| Mina Transporter - 序列化:Hessian Serialization |
Java Serialization
- 代理策略:Javassist ProxyFactory |
Jdk ProxyFactory
- 集群容错:
Failover Cluster
|Failfast Cluster
| Failsafe Cluster | Failback Cluster | Forking Cluster | Broadcast Cluster - 负载均衡:``Random LoadBalance` |RoundRobin LoadBalance |LeastActive LoadBalance |ConsistentHash LoadBalance
- 路由:条件路由规则| 脚本路由
常见示例使用
启动时检查
Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check="true"
。可以通过 check="false"
关闭检查.
<dubbo:reference id="demoService"
loadbalance="roundrobin"
proxy="jdk"
check="false"
interface="com.xxx.service.IDemoService"/>
如果是xml配置信息,用户可以考虑使用全局配置
<dubbo:consumer check="false" loadbalance="roundrobin" proxy="jdk"/>
@Reference(
version = "1.0.0",
loadbalance = "roundrobin",
proxy = "jdk",
check = false
)
private IDemoService demoService;
# 在application.properties中配置全局参数
dubbo.consumer.check=false
dubbo.consumer.proxy=jdk
dubbo.consumer.loadbalance=roundrobin
集群容错
在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover
重试。
注意
:缺省容错策略只适合幂等性操作,不适合修改(非幂等性)操作
各节点关系:
- 这里的
Invoker
是Provider
的一个可调用Service
的抽象,Invoker
封装了Provider
地址及Service
接口信息 -
Directory
代表多个Invoker
,可以把它看成List<Invoker>
,但与List
不同的是,它的值可能是动态变化的,比如注册中心推送变更 -
Cluster
将Directory
中的多个Invoker
伪装成一个Invoker
,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个 -
Router
负责从多个Invoker
中按路由规则选出子集,比如读写分离,应用隔离等 -
LoadBalance
负责从多个Invoker
中选出具体的一个用于本次调用,选的过程包含了负载均衡算法,调用失败后,需要重选
配置集群容错策略
服务引用方配置
<dubbo:consumer check="false" loadbalance="roundrobin" proxy="jdk" cluster="failover" retries="10"/>
或者
<dubbo:reference id="demoService"
cluster="failback"
timeout="100"
interface="com.xxx.service.IDemoService"/>
在服务暴露方配置(建议配置,可以被服务引用方覆盖)
<dubbo:service interface="com.xxx.service.IDemoService"
cluster="failsafe"
ref="demoService" />
注意优先级别:
consumer 低于 service 低于 reference
存在问题?:目前Dubbo的集群容错,还不能够将容错策略粒度控制在方法级别,这就导致了在一个业务方法中,如果有写操作,用户就只能使用failfast容错策略,因为failover会导致数据重复修改.
负载均衡
在集群负载均衡时,Dubbo 提供了多种均衡策略,缺省为 random
随机调用。可以自行扩展负载均衡策略,
radmon/roundrobin/leastactive/consistenthash
<dubbo:reference id="demoService"
timeout="100"
loadbalance="random"
cluster="failfast"
interface="com.xxx.service.IDemoService"/>
扩展负责均衡
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.RpcException;
import com.alibaba.dubbo.rpc.cluster.LoadBalance;
import java.util.List;
import java.util.Random;
public class MyRandomLoadBalance implements LoadBalance {
public <T> Invoker<T> select(List<Invoker<T>> list, URL url, Invocation invocation) throws RpcException {
System.out.println("load balance");
int length=list.size();
int i = new Random().nextInt(length);
return list.get(i);
}
}
多协议
<dubbo:protocol name="dubbo" port="20880"/>
<dubbo:protocol name="rmi" port="20881"/>
<!--注册服务提供方-->
<dubbo:service interface="com.xxx.service.IDemoService"
cluster="failsafe"
protocol="rmi,dubbo"
ref="demoService" />
<!--服务引用方-->
<dubbo:reference id="demoService"
timeout="100"
protocol="rmi"
interface="com.xxx.service.IDemoService"/>
服务分组
<!--当一个接口有两个实现的时候,注册服务提供方-->
<dubbo:service interface="com.xxx.service.IDemoService"
ref="demoService1" group="g1"/>
<dubbo:service interface="com.xxx.service.IDemoService"
ref="demoService2" group="g2"/>
<!--服务引用方-->
<dubbo:reference id="d1"
timeout="100"
group="g1"
interface="com.xxx.service.IDemoService"/>
<dubbo:reference id="d2"
timeout="100"
group="g2"
interface="com.xxx.service.IDemoService"/>
多版本
<!--当一个接口有两个实现的时候,注册服务提供方-->
<dubbo:service interface="com.xxx.service.IDemoService"
ref="demoService1" version="1.1.0"/>
<dubbo:service interface="com.xxx.service.IDemoService"
ref="demoService2" version="1.1.1"/>
<!--服务引用方-->
<dubbo:reference id="d1"
timeout="100"
version="1.1.1"
interface="com.xxx.service.IDemoService"/>
结果缓存
<dubbo:service interface="com.xxx.service.IDemoService"
version="1.0.1"
cache="lru"
ref="demoService1" />
<dubbo:reference id="demoService"
version="1.0.1"
protocol="rmi"
cache="lru"
interface="com.xxx.service.IDemoService"/>
隐式参数
可以通过 RpcContext
上的 setAttachment
和 getAttachment
在服务消费方和提供方之间进行参数的隐式传递。
import com.alibaba.dubbo.rpc.*;
public class CustomFilter implements Filter {
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
boolean consumerSide = RpcContext.getContext().isConsumerSide();
System.out.println("CustomFilter isConsumerSide:"+consumerSide);
if(consumerSide){
RpcContext.getContext().setAttachment("user","客户端用户!");
}else{
System.out.println("服务器收到:"+RpcContext.getContext().getAttachment("user"));
}
return invoker.invoke(invocation);
}
}
Dubbokeeper基本使用(服务治理)**
- 必须安装tomcat7+
- 将dubbokeeper-ui-1.0.1.war拷贝到webapp页面
- 启动tomcat自动解压war包,关闭tomcat
- 修改解压应用中dubbo.preperties
dubbo.application.name=common-monitor
dubbo.application.owner=bieber
dubbo.registry.address=zookeeper://CentOS:2181
#use netty4
dubbo.reference.client=netty4
#peeper config
peeper.zookeepers=CentOS:2181
peeper.zookeeper.session.timeout=60000
#logger
monitor.log.home=/monitor-log
monitor.collect.interval=6000
将所有连接zookeeper连接信息修改为自己的注册中心位置即可
- 重新启动tomcat即可
访问:http://localhost:8989/dubbokeeper-ui-1.0.1
微服务设计与区别
)]
Dubbokeeper基本使用(服务治理)**
- 必须安装tomcat7+
- 将dubbokeeper-ui-1.0.1.war拷贝到webapp页面
- 启动tomcat自动解压war包,关闭tomcat
- 修改解压应用中dubbo.preperties
dubbo.application.name=common-monitor
dubbo.application.owner=bieber
dubbo.registry.address=zookeeper://CentOS:2181
#use netty4
dubbo.reference.client=netty4
#peeper config
peeper.zookeepers=CentOS:2181
peeper.zookeeper.session.timeout=60000
#logger
monitor.log.home=/monitor-log
monitor.collect.interval=6000
将所有连接zookeeper连接信息修改为自己的注册中心位置即可
- 重新启动tomcat即可
访问:http://localhost:8989/dubbokeeper-ui-1.0.1