标签(空格分隔): 架构
Required: Java 1.6+
Dbs是一个开源、基于XML的分布式的Web服务架构。每一种架构都是对普遍问题或特别问题的一种解决方案。Dbs可能要解决的是这样的一种特殊问题。
网站、客户端、接口 ---》 业务层服务 ---》 数据层服务
主要解决业务层及数据层的痛点,可能存在多种技术框架;业务层如何便捷的使用数据层服务,业务层之间的模块之间如何实现服务共享,
现有的解决方案提供服务注册中心,业务层服务、数据层服务都注册上去;业务层与数据层之间通过RPC的方式进行数据交换。
如果业务层与数据层之间通信要穿越网段、机房、防火墙,那注册中心就是个问题?
Dbs的解决方案则是数据层向业务层模块进行注册、业务层如果存在多个模块,各位模块相互注册,则彼此之间可以相互使用服务,就像使用本地服务一样。
Dbs底层的通信协议支持多种:HTTP、RPC等
maven依赖配置
<dependency>
<groupId>com.jarveis</groupId>
<artifactId>frame</artifactId>
<version>2.5.8</version>
</dependency>
frame配置文件(config.xml)
<?xml version="1.0" encoding="utf-8"?>
<config>
<module>
<parser clazz="com.jarveis.frame.dbs.DbsParser" />
</module>
</config>
编写服务类
@Function(code="10001")
@Before(filters="logger,param")
@After(filters="param,logger")
public class EchoHello implements Service {
public Param callService(Param in) {
Param out = null;
try {
out = new Param(Param.RESP);
String name = in.getBody().getString("@name");
out.getBody().setProperty("@message", "Hello " + name);
out.getHead().setProperty(Param.LABEL_ERROR, Param.ERROR_SUCCESS);
} catch (Exception ex) {
if ( out != null ) {
out.getHead().setProperty(Param.LABEL_ERROR, Param.ERROR_EXCEPTION);
}
}
return out;
}
}
服务启动:
<web-app>
......
<listener>
<listener-class>com.jarveis.frame.dbs.server.DbsContextListenter</listener-class>
</listener>
<servlet>
<servlet-name>dbs</servlet-name>
<servlet-class>com.jarveis.frame.dbs.server.DbsServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dbs</servlet-name>
<url-pattern>*.service</url-pattern>
</servlet-mapping>
......
</web-app>
服务请求: http://127.0.0.1:8080/dbs.service
Filter是拦截业务的规则性组件,负责拦截业务请求。 Filter分为前置拦截组件和后置拦截组件。 创建Filter,需要实现Filter接口。
@Interceptor(code="logger" )
public class LoggerFilter implements Filter {
public int init() {}
public int destory() {}
public int filter(Param param) {
try {
logger.info(param.toString());
} catch (Exception ex) {
logger.error(ex.getMessage(), ex);
}
return 0;
}
}
系统提供的默认Filter
Service是业务服务组件,负责处理单个特定的业务请求。 创建Service,需要实现Service接口。
@Function(code="10001")
@Before(filters="logger,param")
@After(filters="param,logger")
public class EchoHello implements Service {
public Param callService(Param in) {
Param out = null;
try {
out = new Param(Param.RESP);
String name = in.getBody().getString("@name");
out.getBody().setProperty("@message", "Hello " + name);
out.getHead().setProperty(Param.LABEL_ERROR, Param.ERROR_SUCCESS);
} catch (Exception ex) {
if ( out != null ) {
out.getHead().setProperty(Param.LABEL_ERROR, Param.ERROR_EXCEPTION);
}
}
return out;
}
}
@Function(code="10002")
@Before(filters="logger,param")
@After(filters="param,logger")
public class EchoHtml implements Service {
public Param callService(Param in) {
Param out = null;
try {
out = new Param(Param.RESP);
String name = in.getBody().getString("@name");
out.getBody().addCDATA("Hello " + name);
out.getHead().setProperty(Param.LABEL_ERROR, Param.ERROR_SUCCESS);
} catch (Exception ex) {
if ( out != null ) {
out.getHead().setProperty(Param.LABEL_ERROR, Param.ERROR_EXCEPTION);
}
}
return out;
}
}
在业务处理的过程中,会遇到多个服务之间的调用.对于服务来说有可能是本地的服务,也有可能是远程的服务(稍后的分布中会有说明).对于业务来说并不关心这些,希望两者是统一的.
Dbs会将所有的服务注册到本地的注册中心,用户只用关心本地注册中心有没有这些服务即可.关于调用的是本地的还是远程的交给代理服务来解决.
@Function(code="10002")
@Before(filters="logger,param")
@After(filters="param,logger")
public class EchoHtml implements Service {
public Param callService(Param in) {
Param out = null;
try {
out = new Param(Param.RESP);
String name = in.getBody().getString("@name");
Param subOut = ServiceProxy.callService("10003", in);
if (Param.ERROR_SUCCESS.equals(subOut.getHead().getString(Param.LABEL_ERROR))) {
out.getBody().addCDATA("Hello " + name);
out.getHead().setProperty(Param.LABEL_ERROR, Param.ERROR_SUCCESS);
} else {
out.getHead().setProperty(Param.LABEL_ERROR, "3001");
}
} catch (Exception ex) {
if ( out != null ) {
out.getHead().setProperty(Param.LABEL_ERROR, Param.ERROR_EXCEPTION);
}
}
return out;
}
}
@Function(code="10003")
@Before(filters="logger,param")
@After(filters="param,logger")
public class CheckName implements Service {
public Param callService(Param in) {
Param out = null;
try {
out = new Param(Param.RESP);
String name = in.getBody().getString("@name");
if ("Tom".equals(name)){
out.getHead().setProperty(Param.LABEL_ERROR, Param.ERROR_SUCCESS);
} else {
out.getHead().setProperty(Param.LABEL_ERROR, "3001");
}
} catch (Exception ex) {
if ( out != null ) {
out.getHead().setProperty(Param.LABEL_ERROR, Param.ERROR_EXCEPTION);
}
}
return out;
}
}
服务功能的相互调用,会导致服务的相应抽象,比如有一个服务可以适配多个服务.同时也面临着另一个问题的产生,抽象的服务不想对外提供服务,仅限于本地内部服务的调用,此时我们引用了作用域的概念;Dbs支持服务的作用域分为,public和private;
public作用域,定义了服务可以对外提供服务;内部的服务可以调用,外部的服务也可以调用.Dbs默认服务是public作用域. private作用域,定义了服务可以对内提供服务;内部的服务可以调用,外部的服务不可以调用.
@Function(code="10003", scope = Scope.PRIVATE)
@Before(filters="logger,param")
@After(filters="param,logger")
public class CheckName implements Service {
public Param callService(Param in) {
Param out = null;
try {
out = new Param(Param.RESP);
String name = in.getBody().getString("@name");
if ("Tom".equals(name)){
out.getHead().setProperty(Param.LABEL_ERROR, Param.ERROR_SUCCESS);
} else {
out.getHead().setProperty(Param.LABEL_ERROR, "3001");
}
} catch (Exception ex) {
if ( out != null ) {
out.getHead().setProperty(Param.LABEL_ERROR, Param.ERROR_EXCEPTION);
}
}
return out;
}
}
@Function(code="10004")
@Before(filters="logger,param")
@After(filters="param,logger")
public class UploadService implements Service {
public Param callService(Param in) {
Param out = null;
try {
out = new Param(Param.RESP);
String uploadPath = DbsCache.getConst("upload_folder");
List<FileItem> formItems = DbsCache.getUploads();
if (formItems != null && formItems.size() > 0) {
// 迭代表单数据
for (FileItem item : formItems) {
// 处理不在表单中的字段
if (!item.isFormField()) {
String fileName = new File(item.getName()).getName();
String filePath = uploadPath + File.separator + fileName;
File storeFile = new File(filePath);
// 保存文件到硬盘
item.write(storeFile);
}
}
}
out.getHead().setProperty(Param.LABEL_ERROR, Param.ERROR_SUCCESS);
} catch (Exception ex) {
if (out != null) {
out.getHead().setProperty(Param.LABEL_ERROR, Param.ERROR_EXCEPTION);
}
}
return out;
}
}
@Function(code="10005")
@Before(filters="logger,param")
@After(filters="param,logger")
public class UploadService implements Service {
public Param callService(Param in) {
Param out = null;
try {
out = new Param(Param.RESP);
byte[] bytes = DbsCache.getStream();
String str = new String(bytes);
out.getHead().setProperty(Param.LABEL_ERROR, Param.ERROR_SUCCESS);
} catch (Exception ex) {
if (out != null) {
out.getHead().setProperty(Param.LABEL_ERROR, Param.ERROR_EXCEPTION);
}
}
return out;
}
}
如果使用上传组件,dbs.xml需要增加如下配置:
<dbs>
...
<constants>
...
<constant name="upload_memory_size" value="3145728" />
<constant name="upload_file_size" value="41943040" />
<constant name="upload_request_size" value="52428800" />
<constant name="upload_folder" value="D:\\tmp" />
...
</constants>
...
</dbs>
Param是输入和输出数据的标准对象,Dbs将接收到的输入参数封装成Param对象方便Dbs框架内的数据交换及处理,Dbs最终会将返回的Param对象,转换成用户需要的数据格式;
对于数据的输入与输出并没有作相同格式的限定,也就是说json的输入可得到(json|xml|html)格式的输出.同样对于xml的输入也可以得到(json|xml|html)格式的输出.输出的格式是按照在传入的返回数据格式来生成的.
输入支持
输出支持
json数据格式
输入和输出
{
"head": {
"dataType": "返回数据格式",
"device": "设备标识",
"token": "会话标识",
"funcId": "服务编号"
},
"body": {
propKey: propValue,
...
}
}
/* 实例-输入 */
{
"head": {
"dataType": "json",
"device": "a7bf1feda8124fd7a15b302691ba164f",
"token": "dbded8a69c9a41d4908a24f5c58ae419",
"funcId": "10001"
},
"body": {
name: "jvsframe"
}
}
/* 实例-输出 */
{
"head": {
"dataType": "json",
"device": "a7bf1feda8124fd7a15b302691ba164f",
"token": "dbded8a69c9a41d4908a24f5c58ae419",
"funcId": "10001"
},
"body": {
message: "Hello jvsframe"
}
}
xml数据格式
输入
<Req>
<head device="设备标识" token="会话标识" funcId="服务编号" dataType="返回数据格式" />
<body propKey="propValue" />
</Req>
<!-- 实例 -->
<Req>
<head device="a7bf1feda8124fd7a15b302691ba164f" token=“dbded8a69c9a41d4908a24f5c58ae419” funcId="10001" dataType="xml"/>
<body name="jvsframe" />
</Req>
输出
<?xml version="1.0" encoding="UTF-8"?>
<Resp>
<head device="设备标识" token=“会话标识” funcId="服务编号" dataType="返回数据格式"/>
<body propKey=propValue ...>
(<extensibleNode proKey=propValue ... />)
</body>
</Resp>
/* 实例 */
<Resp>
<head device="a7bf1feda8124fd7a15b302691ba164f" token=“dbded8a69c9a41d4908a24f5c58ae419” funcId="10001" dataType="xml"/>
<body message="Hello jvsframe" />
</Resp>
html数据格式
输入:使用json,xml的输入数据格式,定义dataType="html"
/* 实例-输入 */
{
"head": {
"dataType": "html",
"device": "a7bf1feda8124fd7a15b302691ba164f",
"token": "dbded8a69c9a41d4908a24f5c58ae419",
"funcId": "10001"
},
"body": {
name: "jvsframe"
}
}
或
<Req>
<head device="a7bf1feda8124fd7a15b302691ba164f" token=“dbded8a69c9a41d4908a24f5c58ae419” funcId="10001" dataType="html"/>
<body name="jvsframe" />
</Req>
输出
<?xml version="1.0" encoding="UTF-8"?>
<Resp>
<head device="设备标识" token=“会话标识” funcId="服务编号" dataType="返回数据格式"/>
<body>
<![CDATA[
(html code|javascript code)
]]>
</body>
</Resp>
/* 实例-输出 */
<?xml version="1.0" encoding="UTF-8"?>
<Resp>
<head device="设备标识" token=“会话标识” funcId="服务编号" dataType="返回数据格式"/>
<body>
<![CDATA[
<h1>Hello jvsframe</h1>
]]>
</body>
</Resp>
或
<?xml version="1.0" encoding="UTF-8"?>
<Resp>
<head device="设备标识" token=“会话标识” funcId="服务编号" dataType="返回数据格式"/>
<body>
<![CDATA[
<script>alert('Hello jvsframe')</script>
]]>
</body>
</Resp>
为发方便前端多原化的请求方式,dbs支持提供了两种类型的请求方式.
http://127.0.0.1:8080/dbs.service?message={"head":{"device":"a7bf1feda8124fd7a15b302691ba164f","token":"dbded8a69c9a41d4908a24f5c58ae419","funcId":"10001","dataType":"json"},"body":{"name":"jvsframe"}}
http://127.0.0.1:8080/dbs.service?_message=xml格式
http://127.0.0.1:8080/dbs.service?message=<Req><head device="a7bf1feda8124fd7a15b302691ba164f" token="dbded8a69c9a41d4908a24f5c58ae419" funcId="10001" dataType="xml"/><body name="jvsframe"/>
http://127.0.0.1:8080/10001.service?_device=a7bf1feda8124fd7a15b302691ba164f&_token=dbded8a69c9a41d4908a24f5c58ae419&name=jvsframe
http://127.0.0.1:8080/{返回数据格式}/10001.service?参数集
http://127.0.0.1:8080/json/10001.service?_device=a7bf1feda8124fd7a15b302691ba164f&_token=dbded8a69c9a41d4908a24f5c58ae419&name=jvsframe
http://127.0.0.1:8080(/{访问标识}/{设备编号}/{渠道}/{版本号})/{返回数据格式}/10001.service?参数集
http://127.0.0.1:8080/dbded8a69c9a41d4908a24f5c58ae419/a7bf1feda8124fd7a15b302691ba164f/json/10001.service?name=jvsframe
业务开发的过程中会使用到一些配置信息,Dbs提供了基于dbs.xml的配置处理.
/* dbs.xml */
<dbs>
...
<constants>
<constant name="配置名" value="配置值" />
</constants>
...
</dbs>
如何获取配置数据,可以通过DbsCache.getConst("配置名"),方法来获取配置值.
public Param callService(Param in) {
Param out = null;
try {
out = new Param(Param.RESP);
String name = in.getBody().getString("@name");
String whiteNames = DbsCache.getConst("white.list");
if (whiteNames.indexOf(name)){
out.getHead().setProperty(Param.LABEL_ERROR, Param.ERROR_SUCCESS);
} else {
out.getHead().setProperty(Param.LABEL_ERROR, "3001");
}
} catch (Exception ex) {
if ( out != null ) {
out.getHead().setProperty(Param.LABEL_ERROR, Param.ERROR_EXCEPTION);
}
}
return out;
}
启动入口
com.jarveis.frame.dbs.jetty.Dbs4Jetty
配置参数
dbs.contextPath = / 应用程序的上下文路径,默认的值("/")
dbs.httpPort = 8080 应用程序的端口号,默认值(8080)
dbs.poolSize = 500 应用程序的处理线程池大小,默认值(500)
完整配置
java -Ddbs.contextPath=/test -Ddbs.httpPort=8080 -Ddbs.poolSize=200 com.jarveis.frame.dbs.jetty.Dbs4Jetty
在大型应用中,服务都是模块化的,对于模块与模块之间的调用.Dbs给出了自己的解决方案.
模块与模块之间通信,需要知道调用模块的通信地址.如果A模块与B模块建立了联系,那A模块与B模块之间的服务则可相互调用;如果再有C模块接入A模块或B模块,那A模块,B模块,C模块之间的服务则可相互调用.
分布式配置 A模块(dbs.xml),端口号:8001
<constants>
<!-- 本地主机,服务使用方 -->
<constant name="dbs_local" value="" />
<!-- 远程主机,服务提供方 -->
<constant name="dbs_remote" value="http://127.0.0.1:8001" />
</constants>
B模块(dbs.xml),端口号:8002
<constants>
<!-- 本地主机,服务使用方 -->
<constant name="dbs_local" value="http://127.0.0.1:8002" />
<!-- 远程主机,服务提供方 -->
<constant name="dbs_remote" value="http://127.0.0.1:8001" />
</constants>
C模块(dbs.xml),端口号:8003
<constants>
<!-- 本地主机,服务使用方 -->
<constant name="dbs_local" value="http://127.0.0.1:8003" />
<!-- 远程主机,服务提供方 -->
<constant name="dbs_remote" value="http://127.0.0.1:8001" />
</constants>
Puddle是一个开源、基于Ehcache,Redis搭建的二级缓存框架。一级缓存使用Ehcache,二级缓存使用Redis。Puddle参考并借用了J2Cache的思想及相关代码,并跟据自身项目需要开发的一套缓存组件。
maven依赖配置
<dependency>
<groupId>com.jarveis</groupId>
<artifactId>frame</artifactId>
<version>2.3.3</version>
</dependency>
frame配置文件(config.xml)
<?xml version="1.0" encoding="utf-8"?>
<config>
...
<module>
...
<parser clazz="com.jarveis.frame.cache.Puddle" />
...
</module>
<redisConfig>
<datasource id="db56" default="true">
<property name="ip" value="192.168.1.175" />
<property name="host" value="192.168.1.175" />
<property name="port" value="6379" />
<property name="password" value="1qaz2wsx" />
<property name="database" value="0" />
<property name="timeout" value="20000" />
<property name="maxTotal" value="100" />
<property name="maxIdle" value="20" />
<property name="maxWait" value="10000" />
<property name="testOnBorrow" value="true" />
<property name="testOnReturn" value="true" />
<!-- 通知 -->
<property name="channel" value="j2cache_channel" />
</datasource>
</redisConfig>
<puddleConfig>
<puddle>
<!-- broadcast: (jgroups | redis) -->
<property name="broadcast" value="jgroups" />
<!-- l1_provider: (echache) -->
<property name="l1_provider" value="ehcache" />
<!-- l2_provider: (redis) -->
<property name="l2_provider" value="redis" />
<!-- serialization: (fst | fst-snappy | fastjson | java) -->
<property name="serialization" value="fst" />
</puddle>
</puddleConfig>
...
</config>
新增配置文件,ehcache.xml, network.xml; ehcache.xml提供了一级缓存(ehcache)组件所需要的配置。
<ehcache updateCheck="false" dynamicConfig="false">
<diskStore path="java.io.tmpdir" />
<cacheManagerEventListenerFactory
class="" properties="" />
<defaultCache maxElementsInMemory="1000" eternal="false"
timeToIdleSeconds="1800" timeToLiveSeconds="1800"
overflowToDisk="true">
</defaultCache>
<cache name="session" maxElementsInMemory="5000" eternal="false"
timeToIdleSeconds="1800" timeToLiveSeconds="1800"
overflowToDisk="false" />
</ehcache>
network.xml提供了缓存节点间通信组件(jgroups)所需的配置。如果缓存节点的通信使用redis,可以忽略此配置文件。
<config xmlns="urn:org:jgroups"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:org:jgroups http://www.jgroups.org/schema/JGroups-3.4.xsd">
<UDP
mcast_addr="${jgroups.udp.mcast_addr:235.5.5.5}"
mcast_port="${jgroups.udp.mcast_port:45588}"
tos="8"
ucast_recv_buf_size="20M"
ucast_send_buf_size="640K"
mcast_recv_buf_size="25M"
mcast_send_buf_size="640K"
loopback="true"
max_bundle_size="64K"
max_bundle_timeout="30"
ip_ttl="${jgroups.udp.ip_ttl:2}"
enable_diagnostics="true"
thread_naming_pattern="cl"
timer_type="new"
timer.min_threads="4"
timer.max_threads="10"
timer.keep_alive_time="3000"
timer.queue_max_size="500"
thread_pool.enabled="true"
thread_pool.min_threads="2"
thread_pool.max_threads="8"
thread_pool.keep_alive_time="5000"
thread_pool.queue_enabled="true"
thread_pool.queue_max_size="10000"
thread_pool.rejection_policy="discard"
oob_thread_pool.enabled="true"
oob_thread_pool.min_threads="1"
oob_thread_pool.max_threads="8"
oob_thread_pool.keep_alive_time="5000"
oob_thread_pool.queue_enabled="false"
oob_thread_pool.queue_max_size="100"
oob_thread_pool.rejection_policy="Run"/>
<PING timeout="2000" num_initial_members="3"/>
<MERGE2 max_interval="30000" min_interval="10000"/>
<FD_SOCK/>
<FD_ALL/>
<VERIFY_SUSPECT timeout="1500" />
<BARRIER />
<pbcast.NAKACK use_mcast_xmit="true"
retransmit_timeout="300,600,1200"
discard_delivered_msgs="true"/>
<pbcast.STABLE stability_delay="1000"
desired_avg_gossip="50000"
max_bytes="4M"/>
<pbcast.GMS print_local_addr="true"
print_physical_addrs="true"
join_timeout="3000"
view_bundling="true"
max_join_attempts="3"/>
<UFC max_credits="2M" min_threshold="0.4"/>
<MFC max_credits="2M" min_threshold="0.4"/>
<FRAG2 frag_size="60K" />
<pbcast.STATE_TRANSFER />
</config>
如何在程序中使用缓存组件,请参考如下代码:
CacheChannel cache = Puddle.getChannel();
String region = "const";
String key = "message";
cache.put(region, key, "hello world");
// 设置缓存有效期,时间单位:秒
// cache.put(region, key, "hello world", 300); //当前缓存的有效期为5分种
主要包括
@Table(name = "user")
public class User {
@Column(primaryKey = true)
private Long suid;
@Column
private String uname;
@Column
private String uphone;
public User(){}
public Long getSuid() {
return suid;
}
public void setSuid(Long suid) {
this.suid = suid;
}
public String getUname() {
return this.uname;
}
public void setUname(String uname) {
this.uname = uname;
}
public String getUphone() {
return this.uphone;
}
public void setUphone(String uphone) {
this.uphone = uphone;
}
}
public class CreateUserService implements Service {
public Param callService(Param in) {
String uname = in.getProperty("@uname"); // 用户名
String uphone = in.getProperty("@uphone"); // 手机号
User user = new User();
long suid = generateCode();
user.setSuid(suid);
user.setUphone(uphone);
user.setUname(uname);
JdbcUtil.save(s);
}
}
public class LoadUserService implements Service {
public Param callService(Param in) {
String uphone = in.getProperty("@uphone"); // 手机号
HashMap<String, String> params = new HashMap<String, String>(1);
params.put("uphone", uphone);
Map mapResult = new HashMap();
try {
mapResult = (Map) JdbcUtil.query("select * from user where uphone = :uphone limit 1", new MapHandler(), params);
} catch (SQLException e) {
logger.error(e.getMessage(), e);
}
Param item = out.getBody().addParam("user");
item.setProperty("@suid", (Long) mapResult.get("suid"));
item.setProperty("@uname", (String) mapResult.get("uname"));
item.setProperty("@uphone", (String) mapResult.get("uphone"));
}
}
public class LoadUserService implements Service {
public Param callService(Param in) {
String uphone = in.getProperty("@uphone"); // 手机号
HashMap<String, String> params = new HashMap<String, String>(1);
params.put("uphone", uphone);
User user = new User();
try {
user = (Map) JdbcUtil.query("select * from user where uphone = :uphone limit 1", new BeanHandler(User.class), params);
} catch (SQLException e) {
logger.error(e.getMessage(), e);
}
Param item = out.getBody().addParam("user");
item.setProperty("@suid", user.getSuid("suid"));
item.setProperty("@uname", user.getUname("uname"));
item.setProperty("@uphone", user.getUphone("uphone"));
}
}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。