rpc框架的学习

简介

Apache Dubbo
Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
1 PRC RMI HESSION DUBBO
2 DUBBO的其他功能
3 DUBBO源码解析(SERVICE,)

首先了解什么叫RPC,为什么要RPC,RPC是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。
首先,要解决通讯的问题,主要是通过在客户端和服务器之间建立TCP连接,远程过程调用的所有交换的数据都在这个连接里传输。连接可以是按需连接,调用结束后就断掉,也可以是长连接,多个远程过程调用共享同一个连接。
第二,要解决寻址的问题,也就是说,A服务器上的应用怎么告诉底层的RPC框架,如何连接到B服务器(如主机或IP地址)以及特定的端口,方法的名称名称是什么,这样才能完成调用。比如基于Web服务协议栈的RPC,就要提供一个endpoint URI,或者是从UDDI服务上查找。如果是RMI调用的话,还需要一个RMI Registry来注册服务的地址。
第三,当A服务器上的应用发起远程过程调用时,方法的参数需要通过底层的网络协议如TCP传递到B服务器,由于网络协议是基于二进制的,内存中的参数的值要序列化成二进制的形式,也就是序列化(Serialize)或编组(marshal),通过寻址和传输将序列化的二进制发送给B服务器。
第四,B服务器收到请求后,需要对参数进行反序列化(序列化的逆操作),恢复为内存中的表达方式,然后找到对应的方法(寻址的一部分)进行本地调用,然后得到返回值。
第五,返回值还要发送回服务器A上的应用,也要经过序列化的方式发送,服务器A接到后,再反序列化,恢复为内存中的表达方式,交给A服务器上的应用

RPC的协议有很多,比如最早的CORBA,Java RMI,Web Service的RPC风格,Hessian,Thrift,甚至Rest API。
在这里插入图片描述

RPC DEMO

接口:

public interface ProviderService {
    String SayHello(String word);
}

实现类

public class ProviderServiceImpl implements ProviderService {
    public String SayHello(String word) {
        return "服务方被调用,传入的参数为:"+word;
    }
}

服务方

public class rpcProbiderMian {
    public static void main(String[] args) {
        ServerSocket server =  null;
        ObjectOutputStream out = null;
        try {
            server = new ServerSocket(8080);
            Socket socket =null;
            while(true){
                System.out.println("---开始监听---");
                socket = server.accept();
                ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
                String interfaceName = input.readUTF(); //接口名称
                String methodName = input.readUTF(); //方法名称
                Class<?>[] parameterType = (Class<?>[]) input.readObject(); //方法类型
                Object[] arguments = (Object[]) input.readObject();    //参数列表
                System.out.println("接收到的参数:"+Arrays.toString(arguments));
                //根据接口名称获取class
                Class<?> serviceInterfaceClass = Class.forName(interfaceName);
                //根据方法名称和参数类型反射得到方法
                Method method = serviceInterfaceClass.getMethod(methodName, parameterType);
                //服务实例化(这里做简单处理,正常应该根据得到的接口名称serviceInterfaceClass获取对应的service,但本demo只提供一个服务)
                ProviderService service = new ProviderServiceImpl();
                //反射执行这个方法
                Object result = method.invoke(service, arguments);
                //写会处理结果
                out = new ObjectOutputStream(socket.getOutputStream());
                out.writeObject(result);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            try {
                out.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            if(server!=null){
                try {
                    server.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                server = null;
            }
        }
    }
}

消费方

public class prcConsumerMain {

    public static void main(String[] args) {
        try {
            //接口名称
            String interfaceName = ProviderService.class.getName();
            //接口方法
            Method method = ProviderService.class.getMethod("SayHello", java.lang.String.class);
            //参数
            Object[] arguments = {"hello"};
            Socket socket = new Socket("127.0.0.1",8080);
            //发送请求
            ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
            out.writeUTF(interfaceName);
            out.writeUTF(method.getName());
            out.writeObject(method.getParameterTypes());
            out.writeObject(arguments);
            //获取结果
            ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
            Object returnObject = inputStream.readObject();
            System.out.println(returnObject.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

RMI demo

接口

/**
 *
 * 定义一个远程接口,必须继承Remote接口,其中需要远程调用的方法必须抛出RemoteException异常
 */
public interface RmiHelloService extends Remote {
    //远程调用的方法必须有返回值,不能为void
    public String sayHello() throws RemoteException; 
    //必须抛出RemoteException异常,否认则Client运行时会抛出RemoteException异常
    public String sayHelloTo(String somebody) throws RemoteException;
}

实现类

/**
 *
 * 继承UnicastRemoteObject 父类并实现自定义的远程接口
 * 因为UnicastRemoteObject的构造方法抛出了RemoteException异常,因此这里默认的构造方法必须写,必须声明抛出RemoteException异常
 */
public class HelloImpl extends UnicastRemoteObject implements IHello {

    public HelloImpl() throws RemoteException {
    }

    @Override
    public String sayHello() throws RemoteException {
        //System.out.println("hello!");
        return "hello";
    }

    @Override
    public String sayHelloTo(String somebody) throws RemoteException {
        //System.out.println("hello" + somebody);
        return "hello" + somebody;
    }
}

服务方方法

public class rmiProbiderMian {
    public static void main(String[] args) {
        try {
            //创建一个远程对象
            RmiHelloService rmiHello = new RmiHelloServiceImpl();
            //远程主机远程对象注册表Registry的实例,并指定端口为8888,这一步必不可少(Java默认端口是1099),
            // 必不可缺的一步,缺少注册表创建,则无法绑定对象到远程注册表上
            LocateRegistry.createRegistry(8888);
            //把远程对象注册到RMI注册服务器上,并命名为RHello
            //绑定的URL标准格式为:rmi://host:port/name(其中协议名可以省略,下面两种写法都是正确的)
            Naming.bind("rmi://localhost:8888/RmiHello",rmiHello);
            //Naming.bind("//localhost:8888/RmiHello",rmiHello);
            //必须捕获这三个异常,否则需要在main方法中抛出
        }  catch (RemoteException e) {
            System.out.println("创建远程对象发生异常");
            e.printStackTrace();
        } catch (MalformedURLException e) {
            System.out.println("URL畸形异常");
            e.printStackTrace();
        } catch (AlreadyBoundException e) {
            System.out.println("重复绑定对象异常");
            e.printStackTrace();
        }
    }

}

消费方代码

public class rmiConsumerMain {

    public static void main(String[] args) {
            try {
                //在RMI服务注册表中查找名称为RHello的对象,并调用其上的方法
                RmiHelloService localHello = (RmiHelloService)Naming.lookup("rmi://localhost:8888/RmiHello");
                System.out.println(localHello.sayHello());
                System.out.println(localHello.sayHelloTo("大商"));
            } catch (NotBoundException e) {
                e.printStackTrace();
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
    }
}

在这里插入图片描述

DUBBO DEMO

consumer配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans.xsd        http://code.alibabatech.com/schema/dubbo        http://code.alibabatech.com/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">


        <dubbo:application name="consumer" owner="sihai"/>
        <dubbo:registry address="zookeeper://10.201.1.222:2181"></dubbo:registry>
        <!-- timeout="0" 默认是1000ms-->
        <!-- retries="":重试次数,不包含第一次调用,0代表不重试-->
        <!-- 幂等(设置重试次数)【查询、删除、修改】、非幂等(不能设置重试次数)【新增】 -->
        <!--在user-service-provider中暴露的接口服务 -->
        <!-- <dubbo:method name="getUserAddressList" timeout="1000"></dubbo:method> -->
        <dubbo:reference interface="service.provider.api.ProviderService"
                         id="providerService" timeout="5000" retries="3" >
        </dubbo:reference>

    </beans>

provider.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans.xsd        http://code.alibabatech.com/schema/dubbo        http://code.alibabatech.com/schema/dubbo/dubbo.xsd">


    <!-- 1、指定当前服务/应用的名字(同样的服务名字相同,不要和别的服务同名) -->
    <dubbo:application name="user-service-provider01"></dubbo:application>
    <!-- 2、指定注册中心的位置 -->
    <!-- <dubbo:registry address="zookeeper://127.0.0.1:2181"></dubbo:registry> -->
    <dubbo:registry protocol="zookeeper" address="10.201.1.222:2181"></dubbo:registry>

    <!-- 3、指定通信规则(通信协议?通信端口) -->
    <dubbo:protocol name="dubbo" port="20882"></dubbo:protocol>

    <!--服务发布的配置,需要暴露的服务接口-->
    <dubbo:service    interface="service.provider.api.ProviderService"
            ref="providerService" />

    <!--Bean bean定义-->
    <bean id="providerService" class="service.provider.impl.ProviderServiceImpl"/>

</beans>
public class dubboConsumerMain {
        public static void main(String[] args) {
            ClassPathXmlApplicationContext c = new ClassPathXmlApplicationContext("consumer.xml");
            c.start();
            ProviderService providerService = (ProviderService) c.getBean(ProviderService.class);
            String dubbo = providerService.SayHello("dubbo");
            System.out.println(dubbo);
            System.out.println("调用完成....");
            try {
                System.in.read();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
}

public class dubboProviderMain {
    public static void main( String[] args ) throws IOException {
        //加载xml配置文件启动
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("provider.xml");
        context.start();
        System.in.read(); // 按任意键退出
    }
}

HESSIAN DEMO

web.xml

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
    <display-name>hessian-showcase</display-name>
    <servlet>
        <servlet-name>hessian-service</servlet-name>
        <servlet-class>
            com.caucho.hessian.server.HessianServlet
        </servlet-class>
        <init-param>
            <param-name>home-class</param-name>
            <param-value>
                <!-- 服务实现类 -->
                session.service.impl.HelloServiceImpl
            </param-value>
        </init-param>
        <init-param>
            <param-name>home-api</param-name>
            <!-- 服务接口 -->
            <param-value>hession.service.api.HelloService</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>hessian-service</servlet-name>
        <url-pattern>/hessian</url-pattern>
    </servlet-mapping>
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

public interface HelloService {
    public String helloWorld(String message);
}

public class HelloServiceImpl implements HelloService {

    @Override
    public String helloWorld(String message) {
        return "hello," + message;
    }
}

public class hessionConsumerMain {
    public static void main(String[] args) throws MalformedURLException {
        String url = "http://localhost:8080/hessian-showcase/hessian";
        System.out.println(url);
        HessianProxyFactory factory = new HessianProxyFactory();
        ProviderService helloService = (ProviderService) factory.create(ProviderService.class, url);
        System.out.println(helloService.SayHello("jimmy"));
    }
}

RMI使用
前台客户端调用

	List<FuncDto> funcList = (List<FuncDto>) ApplicationContext.getInstance().getClient()
					.remoteInvoke(ModuleConstants.MOUDLE_NAME_CMS,"FuncRecordService", "findAllFunc");

dubbo的一些应用

1 超时
超时(timeout)
在接口调用过程中,consumer调用provider的时候,provider在响应的时候,有可能会慢,如果provider 10s响应,那么consumer也会至少10s才响应。如果这种情况频度很高,那么就会整体降低consumer端服务的性能。
这种响应时间慢的症状,就会像一层一层波浪一样,从底层系统一直涌到最上层,造成整个链路的超时。
所以,consumer不可能无限制地等待provider接口的返回,会设置一个时间阈值,如果超过了这个时间阈值,就不继续等待。
2 重试 超时时间的配置是为了保护服务,避免consumer服务因为provider 响应慢而也变得响应很慢,这样consumer可以尽量保持原有的性能。
但是也有可能provider只是偶尔抖动,那么超时后直接放弃,不做后续处理,就会导致当前请求错误,也会带来业务方面的损失。
那么,对于这种偶尔抖动,可以在超时后重试一下,重试如果正常返回了,那么这次请求就被挽救了,能够正常给前端返回数据,只不过比原来响应慢一点
查询可以重试,增删改不建议重试(如果允许重试,那么应该需要做幂等,不能因为多次处理同一种请求使业务数据造成异常,比如新增2次)
3 熔断
Dubbo + Hystrix 实现服务熔断
Hystrix [hɪst’rɪks],中文含义是豪猪,因其背上长满棘刺,从而拥有了自我保护的能力。本文所说的Hystrix是Netflix开源的一款容错框架,同样具有自我保护能力。为了实现容错和自我保护,下面我们看看Hystrix如何设计和实现的。

根据业务来拆分成一个个的服务,服务与服务之间可以通过RPC相互调用。为了保证其高可用,单个服务通常会集群部署。由于网络原因或者自身的原因,服务并不能保证 100% 可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的 “雪崩”效应。
当对特定的服务的调用的不可用达到一个阀值(Hystrix 是 5 秒 20 次) 熔断器将会被打开

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    <version>2.0.1.RELEASE</version>
</dependency>

@EnableHystrix
@SpringBootApplication
public class HelloDubboServiceUserProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(HelloDubboServiceUserProviderApplication.class, args);
        Main.main(args);
    }
}

@Service(version = "${user.service.version}")
public class UserServiceImpl implements UserService {

    @Value("${dubbo.protocol.port}")
    private String port;

    @HystrixCommand(commandProperties = {
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000")
    })
    @Override
    public String sayHi() {
//        return "Hello Dubbo, i am from port:" + port;
        throw new RuntimeException("Exception to show hystrix enabled.");
    }
}
@EnableHystrix
@SpringBootApplication
public class HelloDubboServiceUserConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(HelloDubboServiceUserConsumerApplication.class, args);
    }
}
@RestController
public class UserController {

    @Reference(version = "${user.service.version}")
    private UserService userService;

    @HystrixCommand(fallbackMethod = "hiError")
    @RequestMapping(value = "hi")
    public String sayHi() {
        return userService.sayHi();
    }

    public String hiError() {
        return "Hystrix fallback";
    }
}

过滤器黑白名单:

public class ValidationFilter implements Filter {
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        Properties prop = new Properties();
        InputStream in = ValidationFilter.class.getResourceAsStream("/ipwhitelist.properties");
        String clientIp = RpcContext.getContext().getRemoteHost();//客户端ip
        try {
            prop.load(in);
            String ipwhitelist = prop.getProperty("ipwhitelist");//ip白名单
            if (ipwhitelist.contains(clientIp)) {
                return invoker.invoke(invocation);
            } else {
                return new RpcResult(new Exception("ip地址:"
                        + clientIp + "没有访问权限"));
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (RpcException e) {
            throw e;
        } catch (Throwable t) {
            throw new RpcException(t.getMessage(), t);
        }
        return invoker.invoke(invocation);
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://code.alibabatech.com/schema/dubbo
        http://code.alibabatech.com/schema/dubbo/dubbo.xsd
        "
>
    <!-- 提供方应用信息,用于计算依赖关系 -->
    <dubbo:application name="common-service" />

    <!-- 使用zookeeper注册中心暴露服务地址 -->
    <dubbo:registry protocol="zookeeper" address="193.112.76.194:2181" />

    <!-- 用dubbo协议在20880端口暴露服务 -->
    <dubbo:protocol name="dubbo" port="20880" />
     <dubbo:provider filter="validation" delay="-1" timeout="6000" retries="0"/>
     <!-- 用户服务接口 -->
    <dubbo:service interface="com.service.UserService" ref="userService" />
    <bean id="userService" class="com.service.UserServiceImpl"></bean>
</beans>
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值