Java 微服务框架 Redkale 入门介绍
Redkale 功能
Redkale虽然只有1.xM大小,但是麻雀虽小五脏俱全。既可作为服务器使用,也可当工具包使用。作为独立的工具包提供以下功能:
1、convert包提供JSON的序列化和反序列化功能,类似Gson、Jackson。
2、convert包提供Java对象二进制的序列化和反序列化功能,类似Protobuf。
3、source包提供很简便的数据库操作功能,类似JPA、Hibernate。
4、net包提供TCP/UDP服务功能, 类似Mina。
5、net.http提供HTTP服务, 类似Tomcat、Netty。
6、ResourceFactory提供轻量级的依赖注入功能, 类似Google Guice。
Redkale 服务器
Redkale作为服务器的目录如下:
bin : 存放启动关闭脚本(start.sh、shutdown.sh、start.bat、shutdown.bat)
conf : 存放服务器所需配置文件:
application.xml: 服务配置文件 (必需);
logging.properties:日志配置文件 (可选);
persistence.xml:数据库配置文件 (可选);
lib : 存放服务所依赖的第三方包,redkale.jar 放在此处。
logs : logging.properties 配置中默认的日志存放目录。
root : application.xml 配置中HTTP服务所需页面的默认根目录。
Redkale启动的流程如下:
1、加载 application.xml 并解析。
2、初始化
3、解析所有的
4、初始化并启动所有
5、初始化单个Server:
5.1、扫描classpath加载所有可用的Service实现类(没有标记为@AutoLoad(false)的类)并实例化,然后相互依赖注入。
5.2、Service实例在依赖注入过程中加载所需的DataSource、CacheSource资源。
5.3、调用所有本地模式Service的init方法。
5.4、扫描classpath加载所有可用的Servlet实现类(没有标记为@AutoLoad(false)的类)并实例化 (优先实例化WebSocketServlet)。
5.5、给所有Servlet依赖注入所需的Service。
5.6、调用所有Servlet的init方法。
5.7、启动Server的服务监听。
6、启动进程本身的监听服务。
基于Redkale的开发与调试
基于Redkale创建一个Java应用程序工程(即使是Web项目也不要创建Java-Web工程),引用redkale.jar 并创建Redkale所需的几个目录和文件。一个普通的Web项目只需要编写业务层的Service和接入层的HttpServlet的代码。数据库DataSource通过配置文件进行设置。
编写完代码可以通过启动脚本进行调试, 也可以在IDE设置项目的主类为 org.redkale.boot.Application 或者工程内定义主类进行启动调试:
public final class Bootstrap {
public static void main(String[] args) throws Exception {
org.redkale.boot.Application.main(args);
}
}
若需要调试单个Service,可以通过 Application.singleton 方法进行调试:
public static void main(String[] args) throws Exception {
UserService service = Application.singleton(UserService.class);
LoginBean bean = new LoginBean();
bean.setAccount("myaccount");
bean.setPassword("123456");
System.out.println(service.login(bean));
}
Application.singleton 运行流程与通过bin脚本启动的流程基本一致,区别在于singleton运行时不会启动Server和Application自身的服务监听。Redkale提倡接入层(Servlet)与业务层(Service)分开,Service在代码上不能依赖于Servlet,因此调试Service自身逻辑时不需要启动接入层服务(类似WebSocket依赖Servlet的功能除外)。
Redkale的依赖注入
Redkale内置的依赖注入实现很简单,只有三个类: javax.annotation.Resource、org.redkale.util.ResourceType、org.redkale.util.ResourceFactory,采用反射技术,由于依赖注入通常不会在频繁的操作中进行,因此性能要求不会很高。其中前两个是注解,ResourceFactory是主要操作类,主要提供注册和注入两个接口。ResourceFactory的依赖注入不仅提供其他依赖注入框架的常规功能,还能动态的自动更新通过inject注入的资源。
public class AService {
@Resource(name = "property.id")
private String id;
@Resource(name = "property.id") //property.开头的资源名允许String自动转换成primitive数值类型
private int intid;
@Resource(name = "bigint")
private BigInteger bigint;
@Resource(name = "seqid")
private int seqid;
@Resource
private ResourceTest.BService bservice;
@Override
public String toString() {
return "{id:/"" + id + "/", intid: " + intid + ", bigint:" + bigint + "}";
}
/** 以下省略getter setter方法 */
}
public class BService {
@Resource(name = "property.id")
private String id;
@Resource
private AService aservice;
private String name = "";
@java.beans.ConstructorProperties({"name"})
public BService(String name) {
this.name = name;
}
@Override
public String toString() {
return "{name:/"" + name + "/", id: " + id + ", aserivce:" + aservice + "}";
}
/** 以下省略getter setter方法 */
}
public static void main(String[] args) throws Exception {
ResourceFactory factory = ResourceFactory.root();
factory.register("property.id", "2345"); //注入String类型的property.id
AService aservice = new AService();
BService bservice = new BService("eee");
factory.register(aservice); //放进Resource池内,默认的资源名name为""
factory.register(bservice); //放进Resource池内,默认的资源名name为""
factory.inject(aservice); //给aservice注入id、bservice,bigint没有资源,所以为null
factory.inject(bservice); //给bservice注入id、aservice
System.out.println(aservice); //输出结果为:{id:"2345", intid:2345, bigint:null, bservice:{name:eee}}
System.out.println(bservice); //输出结果为:{name:"eee", id:2345, aserivce:{id:"2345", intid:2345, bigint:null, bservice:{name:eee}}}
factory.register("seqid", 200); //放进Resource池内, 同时ResourceFactory会自动更新aservice的seqid值
System.out.println(factory.find("seqid", int.class)); //输出结果为:200
factory.register("bigint", new BigInteger("66666")); //放进Resource池内, 同时ResourceFactory会自动更新aservice对象的bigint值
System.out.println(aservice); //输出结果为:{id:"2345", intid:2345, bigint:66666, bservice:{name:eee}}可以看出seqid与bigint值都已自动更新
factory.register("property.id", "6789"); //更新Resource池内的id资源值, 同时ResourceFactory会自动更新aservice、bservice的id值
System.out.println(aservice); //输出结果为:{id:"6789", intid:6789, bigint:66666, bservice:{name:eee}}
System.out.println(bservice); //输出结果为:{name:"eee", id:6789, aserivce:{id:"6789", intid:6789, bigint:66666, bservice:{name:eee}}}
bservice = new BService("ffff");
factory.register(bservice); //更新Resource池内name=""的BService资源, 同时ResourceFactory会自动更新aservice的bservice对象
factory.inject(bservice);
System.out.println(aservice); //输出结果为:{id:"6789", intid: 6789, bigint:66666, bservice:{name:ffff}}
}
如上例,通过ResourceFactory.inject注入的对象都会自动更新资源的变化,若不想自动更新可以使用带boolean autoSync参数的register系列方法(autoSync传false)注册新资源。
Redkale 架构部署
通常一个系统会分为三层:接入层、业务层、数据层。对应到Redkale的组件是: Servlet、Service、Source。大部分系统提供的是HTTP服务,为了方便演示Redkale从集中式到分布式的变化,以一个简单的HTTP服务作为范例。
开发一个极简单的小论坛系统。包含三个模块:
用户模块 UserSerivice: 提供用户注册、登录、更新资料等功能, UserServlet作为接入层。
帖子模块 ForumSerivice: 提供看帖、发帖、删帖等功能, ForumServlet作为接入层。
通知模块 NotifySerivice: 提供用户操作、回帖等消息通知功能, NotifyWebSocket是WebSocket的Servlet, 且name为 ws_notify,
作为接入层。
其中数据源有:
DataSource: 在persistence.xml里配置的数据库Source的name为demodb ,三个模块都需要使用demodb。
CacheSource: 仅供UserSerivice用于存放session的缓存Service,name为 usersessions, 且session只存放用户ID( int 类型)。
1、单点部署
在早期用户量很少或者开发、调试环境中只需部署一个进程就可满足需求。
如上图,所有模块的HttpServlet、Service与Source数据库操作全部署在一起。 application.xml作简单的配置即可:
2、多点部署
在生产环境需要避免单点问题,一个服务一般会部署多套。在此做个简单的容灾部署,最前端部署一个nginx作反向代理和负载均衡服务器,后面部署两套系统。
如上图,两个进程间的Serivce都是本地模式,两者会通过SNCP服务保持数据同步,若DataSource开启了数据缓存也会自动同步。两套的配置文件相同,配置如下:
3、分层部署
随着业务的复杂度增加,接入层与业务层混在一起会越来越难部署和维护,因此需要进行分层部署。
如上图,对HttpServlet与Service进行了分离。每个接入层的Service都是远程模式,业务层只需提供SNCP供远程调用。
接入层中每个进程的配置相同,配置如下:
业务层中每个进程的配置相同,配置如下:
4、微服务部署
当用户量和发帖量增加到上百万的时候,明显地将所有模块的服务部署到一个进程里是不行的。 因此需要将Service服务都独立部署形成微服务架构。
如上图,将Serivice都独立部署并进行容灾部署,当然如果有需要,Servlet之间、Source都可以各自分离独立部署。不同类型的Service之间都是远程模式调用。
接入层中每个进程的配置相同,配置如下:
用户模块UserService服务群中各个进程的配置相同,配置如下:
通知模块NotifyService服务群中各个进程的配置相同,配置如下:
帖子模块ForumService服务群中各个进程的配置相同,配置如下:
5、API网关式部署
随着用户量到了上千万时,一个UserService的服务进程是无法提供全部用户服务。 因此可以考虑按用户段进行分布式部署。将192.168.50.110、192.168.50.111上的UserService服务改成网关式的服务。下面是以 Service本地模式介绍中的UserService 为范例进行编写:
@ResourceType({UserService.class})
public class UserServiceGateWay extends UserService {
@Resource(name = "userservice_reg")
private UserService regUserService; //只用于注册的服务节点
@Resource(name = "userservice_mob")
private UserService mobUserService; //只用于查询手机号码对应的userid的服务节点
@Resource(name = "userservice_node01")
private UserService userService01; //userid小于2000000的用户的服务节点
@Resource(name = "userservice_node02")
private UserService userService02; //userid小于4000000的用户的服务节点
@Resource(name = "userservice_node03")
private UserService userService03; //userid小于6000000的用户的服务节点
@Resource(name = "userservice_node04")
private UserService userService04; //userid大于6000000的用户的服务节点
private UserService getService(int userid) {
if (userid <= 200_0000) return userService01;
if (userid <= 400_0000) return userService02;
if (userid <= 600_0000) return userService03;
return userService04;
}
@Override
public UserInfo findUserInfo(int userid) {
return this.getService(userid).findUserInfo(userid);
}
@Override
public RetResult login(LoginBean bean) { //手机号码用long存储,0表示无手机号码
int userid = mobUserService.findUserid(bean.getMobile());
if (userid < 1) return new RetResult<>(10001, "not found mobile " + bean.getMobile());
return this.getService(userid).login(bean);
}
@Override
public void register(UserInfo user) {
regUserService.register(user); //会生成userid
this.getService(user.getUserid()).putUserInfo(user);
}
@Override
public UserInfo updateUsername(int userid, String username) {
return this.getService(userid).updateUsername(userid, username);
}
}
从代码看出,UserServiceGateWay继承了UserService, 确保了UserService对外的服务接口不变,上面代码是用户量在600-800万之间的写法,通过简单的用户ID分段,根据不同用户ID调不同的服务节点。
如上图,网关下的UserService部署分三类: userservice_reg只用于注册用户;userservice_mob提供查询手机号码与用户ID间的关系的服务;userservice_node按用户段提供已有用户的服务。且每个UserService的实例在UserServiceGateWay都是远程模式。每种类型可以部署多个节点(为了结构图简单,上图每个类型只部署一个节点)。UserServiceGateWay(192.168.50.110、192.168.50.111)的配置如下:
由以上几种部署方式的范例可以看出,Redkale提供了非常强大的架构,集中式到微服务架构不需要增加修改一行代码即可随意切换,即使网关式部署也只是新增很少的代码就可切换,且不影响其他服务。真正可以做到敏捷开发,复杂的系统都可如小系统般快速地开发出来。
为了降低接入层与业务层代码的耦合, 可以将Service分接口与实现两个类,接入层只加载接口包、业务层使用实现包。
appplication.xml 配置说明
推荐阅读
-
4个理由告诉你Java为何排行第一
本文由码农网 –单劼原创翻译,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划!Java已经有20年的历史了,甚...
-
写给精明Java开发者的测试技巧
我们都会为我们的代码编写测试,不是吗?毫无疑问,我知道这个问题的答案可能会从“当然,但你知道怎样才能避免写测试吗?”到“必须...
-
Java 微服务框架 Redkale 入门介绍
Redkale功能Redkale虽然只有1.xM大小,但是麻雀虽小五脏俱全。既可作为服务器使用,也可当工具包使用。作为独立的工...
-
Java内存管理原理及内存区域详解
一、概述Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干不同的数据区域,这些区域都有各自的用途以及创建和销毁...
-
2015年Java开发岗位面试题归类
下面是我自己收集整理的Java岗位今天面经遇到的面试题,可以用它来好好准备面试。一、Java基础1.String...
-
Java 虚拟机类加载机制和字节码执行引擎
引言我们知道java代码编译后生成的是字节码,那虚拟机是如何加载这些class字节码文件的呢?加载之后又是如何进行方法调用的呢?...
-
Java常量池理解与总结
一.相关概念什么是常量用final修饰的成员变量表示常量,值一旦给定就无法改变!final修饰的变量有三种:静态...
-
Java 实现线程死锁
概述春节的时候去面试了一家公司,笔试题里面有一道是使用简单的代码实现线程的‘死锁’,当时没有想到这道题考的是Sync...
-
Java:过去、未来的互联网编程之王
Java对你而言是什么?一门你大学里学过的语言?一个IT行业的通用语言?你相信Java已经为下一次互联网爆炸做好了准备么?Java...
-
20个高级Java面试题汇总
本文由码农网 –小峰原创翻译,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划!这是一个高级Java面试系列题中...
