微服务设计与实现

互联网架构发展历程

单一架构/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网络编程流程图

微服务架构设计原则 微服务架构的实现_apache

微服务架构设计

微服务架构设计原则 微服务架构的实现_apache_02

微服务架构之Dubbo |ˈdʌbəʊ|

Apache Dubbo™ (incubating)是一款高性能Java RPC框架 。Apache Dubbo (incubating) |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用智能容错和负载均衡,以及服务自动注册和发现

微服务架构设计原则 微服务架构的实现_微服务_03

Dubbo 特性一览表

微服务架构设计原则 微服务架构的实现_微服务_04

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 重试。

注意:缺省容错策略只适合幂等性操作,不适合修改(非幂等性)操作

微服务架构设计原则 微服务架构的实现_微服务架构设计原则_05

各节点关系:

  • 这里的 InvokerProvider 的一个可调用 Service 的抽象,Invoker 封装了 Provider 地址及 Service 接口信息
  • Directory 代表多个 Invoker,可以把它看成 List<Invoker> ,但与 List 不同的是,它的值可能是动态变化的,比如注册中心推送变更
  • ClusterDirectory 中的多个 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);
    }
}

微服务架构设计原则 微服务架构的实现_微服务架构设计原则_06

多协议

<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 上的 setAttachmentgetAttachment 在服务消费方和提供方之间进行参数的隐式传递。

微服务架构设计原则 微服务架构的实现_ide_07

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);
    }
}

微服务架构设计原则 微服务架构的实现_apache_08

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