使用Docker封装java应用

使用Docker封装java应用本文介绍如何使用docker应用封装一个java应用(名字叫cspj)。这个java应用涉及数据持久化以及RMI调用。 1. docker介绍docker是一种容器技术,对操作系统、文件系统、网络等进行了封装,使其中的进程可以完整运行。 docker和虚拟机是不同的技术。虚拟机虚拟了一套硬件环境,需要在这个硬件环境之上安装完整的操作系统、jdk等相关软件,才能运行一个java应用,虚拟机和宿主机在操作系统层面就是相互隔离的。以Linux下的docker为例,它使用的依旧是宿主机中的Linux内核,只不过在宿主机的用户层上虚拟了一个容器,这个容器中的Linux只是操作系统中的一小部分,使用的依旧是宿主机的Linux内核。比如宿主机是Ubuntu,docker虚拟的操作系统可以是alpine,这只是虚拟了alpine和Ubuntu不同的部分。 一个docker容器中一般只运行一个应用,这和虚拟机也是不同的。比如我们的一个应用有java应用,有数据库mysql,那么java应用运行在一个容器里,mySql运行在另一个容器里。他们之间可以通过docker虚拟的网络进行交互。 docker中的容器就是运行中的进程。它是通过镜像进行启动的。docker中的镜像就相当于一个模板,启动一个容器就相当于通过模板创建一个可执行的应用。因此,只要镜像不变,所有通过这个镜像创建的容器都是一摸一样的。又因为docker进行了操作系统、文件系统、网络等方面的封装,所以这个镜像就可以在各种不同的环境上运行,从而保证一致的执行效果。 容器运行之后,在其中会有一个可读写层,这是用来临时保存容器中应用在运行中产生的数据的。当这个容器被销毁之后,所保存的数据也就消失了。使用原有的镜像重新运行一个新的容器,就又是一个全新的应用了。 所以,如果我们需要对容器中的数据进行持久化,就需要用到volume或者bind mounts技术。比如我们的java应用中有一个内置文件数据库Derby,如果需要保留对这个文件数据库的修改,同时又不想改变镜像文件,就可以把这个文件数据库使用volume或bind mounts技术保存到宿主机的文件系统中。这样,即使容器被销毁,容器中所修改的文件数据库也会被保留下来。 还有一种方法保存容器中的临时数据,就是使用commit命令把容器可读写层中的临时数据也一起生成一个新的镜像。以后通过这个新镜像运行的容器,就都保留了这部分数据,这部分数据也就成了新镜像的一层,而且无法被修改。通过这个新镜像运行的容器,会生成一个新的可读写层,用来临时保存此次运行中生成的数据。如果一直使用commit保存数据,新的镜像就会越来越大。docker官方不推荐使用这种方法保存数据。 在详细说一下docker的镜像。docker的镜像是使用Dockerfile制作的。Dockerfile是一个脚本,docker build命令会读取这个脚本,按照其指令构造镜像。docker的镜像是一层一层的。每一个Dockerfile指令,都会生成镜像中的一层。 我们自己制作的docker镜像通常不会从最底层开始构建。比如我们要制作一个java应用的镜像,我们就要依赖于openjdk:8-alpine的官方镜像。在这个基础之上,再制作我们的java应用镜像层。而官方的openjdk:8-alpine则是基于alpine操作系统制作的镜像,在这个操作系统之上,它为我们设置好了各种环境变量,我们在这个镜像之上就可以直接制作我们自己的java应用镜像,而不必关心jdk的设置了。 aphine 是一个特别简洁的官方的Linux操作系统系统容器镜像,只有5M大小。从中也可以看出docker和虚拟机的区别,虚拟机中运行的操作系统一定是完整的操作系统,通常都会有几个G的大小。 2. 环境准备Intel-Core-i7 CPU, 安装Windows10操作系统,使用VirtualBox安装了CentOS-7虚拟机。我们将在CentOS-7虚拟机上安装Docker。关于如何在安装设置虚拟机,请参看这里。 如果要执行8.2节中的实例,必须使用VMWare虚拟机安装CentOS-7系统,因为VMWare支持nested vm。还需要设置vmware虚拟机的处理器中,选择“虚拟化Intel VT-x/EPT或AMD-V/RVI(V)。 如果使用AMD处理器,则可以使用VirtualBox安装CentOS-7,因为最新的VirtualBox-6支持在AMD系统上打开netstad vm。 VirtualBox中安装的CentOS-7系统的IP地址是192.168.56.104. 3. 安装DockerDocker分为社区版和企业版,我们使用社区版即可。 卸载旧docker$ sudo yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-engine安装docker# 安装依赖$ sudo yum install -y yum-utils \ device-mapper-persistent-data \ lvm2# 设置国内镜像源$ sudo yum-config-manager \ --add-repo \ https://mirrors.ustc.edu.cn/docker-ce/linux/centos/docker-ce.repo# 安装最新版本$ sudo yum install docker-ce docker-ce-cli containerd.io# 启动$ sudo systemctl start docker# 验证,或从docker官方下载hello-world镜像并根据镜像运行容器。这个镜像只有不到2K$ sudo docker run hello-world# 把用户添加到docker组中,这样执行docker命令时就不必使用sudo了$ sudo usermod -aG docker your-user设置镜像加速器可以加速从Docker Hub获取镜像的速度。在/etc/docker/daemon.json文件中(如不存在请新建)添加如下内容: ...

October 4, 2019 · 10 min · jiezi

RPC框架是啥之Java自带RPC实现,RMI框架入门

本博客 猫叔的博客,转载请申明出处学习系列RPC框架是啥?Java自带RPC实现,RMI框架入门首先RMI(Remote Method Invocation)是Java特有的一种RPC实现,它能够使部署在不同主机上的Java对象进行通信与方法调用,它是一种基于Java的远程方法调用技术。 让我们优先来实现一个RMI的RPC案例吧。 项目源码地址:RPC_Demo,记得是项目里面的comgithubrmi1、首先我们需要为服务端创建一个接口方法,而且这个接口最好继承Remotepackage com.github.rmi.server;import java.rmi.Remote;import java.rmi.RemoteException;/** * Create by UncleCatMySelf in 21:03 2019\4\20 0020 */public interface MyService extends Remote { String say(String someOne)throws RemoteException;}2、对于接口实现类,RMI接口方法定义必须显式声明抛出RemoteException异常,服务端方法实现必须继承UnicastRemoteObject类,该类定义了服务调用与服务提供方对象实现,并建立一对一的连接。package com.github.rmi.server;import java.rmi.RemoteException;import java.rmi.server.UnicastRemoteObject;/** * Create by UncleCatMySelf in 21:05 2019\4\20 0020 */public class MyServiceImpl extends UnicastRemoteObject implements MyService { protected MyServiceImpl() throws RemoteException { } public String say(String someOne) throws RemoteException { return someOne + ",Welcome to Study!"; }}3、这里我们还需要一个针对服务端的配置类,因为RMI的通信端口是随机产生的,因此有可能会被防火墙拦截。为了防止被防火墙拦截,需要强制制定RMI的通信端口,一般通过自定义一个RMISocketFactory类来实现。package com.github.rmi.config;import java.io.IOException;import java.net.ServerSocket;import java.net.Socket;import java.rmi.server.RMISocketFactory;/** * Create by UncleCatMySelf in 21:15 2019\4\20 0020 */public class CustomerSocketFactory extends RMISocketFactory { public Socket createSocket(String host, int port) throws IOException { return new Socket(host, port); } public ServerSocket createServerSocket(int port) throws IOException { if (port == 0){ port = 8855; } System.out.println("RMI 通信端口 : " + port); return new ServerSocket(port); }}4、好了,这时你可以写出服务端的启动代码了。package com.github.rmi.server;import com.github.rmi.config.CustomerSocketFactory;import java.rmi.Naming;import java.rmi.registry.LocateRegistry;import java.rmi.server.RMISocketFactory;/** * Create by UncleCatMySelf in 21:07 2019\4\20 0020 */public class ServerMain { public static void main(String[] args) throws Exception { //注册服务 LocateRegistry.createRegistry(8866); //指定通信端口,防止被防火墙拦截 RMISocketFactory.setSocketFactory(new CustomerSocketFactory()); //创建服务 MyService myService = new MyServiceImpl(); Naming.bind("rmi://localhost:8866/myService",myService); System.out.println("RMI 服务端启动正常"); }}5、客户端的启动就相对比较简单,我们仅需要进入服务,并调用对应的远程方法即可。package com.github.rmi.client;import com.github.rmi.server.MyService;import java.rmi.Naming;/** * Create by UncleCatMySelf in 21:10 2019\4\20 0020 */public class ClientMain { public static void main(String[] args) throws Exception { //服务引入 MyService myService = (MyService) Naming.lookup("rmi://localhost:8866/myService"); //调用远程方法 System.out.println("RMI 服务端调用返回:" + myService.say("MySelf")); }}最后可以看看效果。 ...

April 22, 2019 · 1 min · jiezi

远程调用

远程调用包括远程过程调用(RPC)和远程方法调用(RMI)1.请求-应答协议请求-应答协议描述了一个基于消息传递的范型,支持消息双向传输。涉及三个通信原语(1)doOperation:向指定的服务器发送请求消息。(2)getRequest:远程服务器获取客户端的请求。(3)sendReply:发送应答消息给客户端。有三种方式保证doOperation的可靠传输:(1)重发请求消息:一直重发消息直到保证收到请求结果或者认定服务器故障。(2)过滤重复请求:在服务器过滤掉重复的请求。(3)保存重传历史:在服务器保存消息结果,相同的请求直接返回结果。RPC调用语义调用语义重发请求消息过滤请求消息重新执行或者重传消息应答或许(0次或一次)否不适用不适用最多一次是是重传消息应答最少一次是否重新执行2.远程过程调用(RPC)RPC(Remote Procedure Call)允许客户端程序可以调用不同于客户端的计算机的服务器程序的进程。RPC调用过程图片来源:https://www.cs.rutgers.edu/~p…解释下调用的过程:(1)客户端(client functions)调用了一个客户端代理(client stub)。(2)客户端将传输的对象参数序列化为二进制,调用客户端socket进行通信。(3)客户端socket将参数通过网络传递给服务端socket。(4)服务端socket将接收到的参数交给服务端代理(server stub)。(5)服务端代理将接收到的参数反序列化为对象,然后调用服务端(server functions)真正的方法。(6)服务端将方法调用的结果返回给服务端代理。(7)服务端代理将调用结果进行序列化,传递给服务端socket。(8)服务端socket接收到二进制的调用结果之后,通过网络传输给客户端socket。(9)客户端socket将调用结果传递给客户端代理。(10)客户端代理将调用结果反序列化传递给客户端。http请求是基于HTTP协议,适用于不用企业间的方法调用,http请求是低效的,因为每次都需要建立一个连接。RPC适用于企业内部系统之间的调用,RPC可以基于TCP协议,也可以基于HTTP2协议。Dubbo等框架实现了RPC。可以使用XML、JSON、Fastjson、Thrift、Avro、Protobuf或是其它进行消息的序列化。多个消息在一个TCP连接上传输时,TCP会将发送的大消息用多个数据包发送,将发送的小消息合并为一个数据包发送。当多个消息用一个数据包发送时,可以使用文本分割法或长度前缀法得到一个完整的消息。文本分割法:每个完整的消息之后,添加一个分隔符,根据之前的分隔符判断之前的文本是不是同一个消息。长度前缀法:在消息开头添加传输的消息长度。参考资料:https://blog.csdn.net/baiye_x…http://www.360doc.com/content...https://github.com/hzy38324/s… https://www.zhihu.com/search?…《分布式系统概念与设计》https://blog.51cto.com/zero01…http://www.cnblogs.com/zhuxia...3.远程方法调用(RMI)RMI允许一个进程对象的方法可以调用另一个进程对象的方法。不管是否需在同一计算机内,只要是不同进程对象的之间的方法调用就是远程方法调用,而同一进程的方法调用就是本地方法调用。(1)通信模块:两个相互协作的通信模块执行请求-应答协议,在客户端和服务器之间传递请求和应答消息。(2)远程引用模块:负责在本地对象引用和远程对象引用之间进行翻译。每个进程的远程引用模块都会维护一个远程对象表,该表记录了该进程的本地对象引用和远程对象引用的对应关系。(3)伺服器:提供远程对象主体的类的实例。很久以前自己在csdn上写过的一篇关于RMI的文章:https://blog.csdn.net/u012734…其他参考资料:https://blog.csdn.net/mawanli…https://www.cnblogs.com/nashi…《分布式系统概念与设计》4.RPC和RMI的联系和区别(1)RPC和RMI都支持接口编程。(2)RPC和RMI都是基于请求-应答协议,并支持最多一次、最少一次等调用语义。(3)RPC和RMI的输入输出参数都可以是值。而RMI支持输入输出参数是对象引用。

April 6, 2019 · 1 min · jiezi