一、简介
利用OTS为系统提供准实时系统交易流水持久化和离线批量数据存储服务。各系统的支撑系统,所有信息都会调用该服务,访问量大,存储和访问速度要求快,故服务的构建采用REST风格(SpringMVC框架天然的支持REST风格请求),对于准实时请求的响应和数据存储,采用NoSQL数据存储,即OTS。
在网站访问量还不大的情况下,单个数据库完全可以应付。随着访问量的上升,几乎大部分使用MySQL架构的网站在数据库上都开始出现了性能问题,web程序不再仅仅专注在功能上,同时也在追求性能。RDS for MySQL会从主从读写分离、分库分表(DRDS)多方面解决高并发的问题。大数据量高并发环境下的MySQL应用开发越来越复杂,分表分库的规则把握都是需要经验的。虽然有像淘宝这样技术实力强大的公司开发了透明的中间件层来屏蔽开发者的复杂性,但是避免不了整个架构的复杂性。
MySQL数据库也经常存储一些大文本字段,导致数据库表非常的大,在做数据库恢复的时候就导致非常的慢,不容易快速恢复数据库。比如1000万4KB大小的文本就接近40GB的大小,如果能把这些数据从MySQL省去,MySQL将变得非常的小。关系数据库很强大,但是它并不能很好的应付所有的应用场景。MySQL的扩展性差(需要复杂的技术来实现),大数据下IO压力大,表结构更改困难,正是当前使用MySQL的开发人员面临的问题。
而采用NoSQL可以避免如上的细节问题,NoSQL的优势:
NoSQL数据库种类繁多,但是一个共同的特点都是去掉关系数据库的关系型特性。数据之间无关系,这样就非常容易扩展。也无形之间,在架构的层面上带来了可扩展的能力。
NoSQL数据库都具有非常高的读写性能,尤其在大数据量下,同样表现优秀。这得益于它的无关系性,数据库的结构简单。
NoSQL无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式。而在关系数据库里,增删字段是一件非常麻烦的事情。如果是非常大数据量的表,增加字段简直就是一个噩梦。这点在大数据量的web2.0时代尤其明显。
阿里云表格存储(TableStore)OTS是 NoSQL 数据库的一种,提供海量 NoSQL 数据存储,支持 schemafree 的数据模型,提供单行级别的事务。服务端自动对数据进行分区和负载均衡,让单表数据从 GB 到 TB 再到 PB,访问并发从 0 至百万都无需繁琐的扩容流程,写性能在 TB 及 PB 级数据规模都能保持在单个毫秒,读性能只依赖结果数据集,而不受数据量的影响。所以相比 OLTP(联机事务处理)场景,表格存储更适用于 Web 规模级应用程序,包括社交网络、游戏、媒体共享和 IoT(物联网)、日志监控等场景。
二、框架搭建
1、浏览器发送rest请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
|
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <!DOCTYPE html> <!-- Head --> < head >
< meta charset = "utf-8" />
< title >SpringMVC - Rest</ title >
< meta name = "description" content = "Dashboard" />
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" />
< meta http-equiv = "X-UA-Compatible" content = "IE=edge" />
< meta http-equiv = "Content-Type" content = "text/html; charset=utf-8" />
< link rel = "shortcut icon" href = "assets/img/favicon.png"
type = "image/x-icon" >
</ head >
<!-- /Head --> <!-- Body --> < body >
<!-- Page Body -->
< div class = "page-body" >
< div class = "row" >
< div class = "col-lg-12 col-sm-12 col-xs-12" >
< div class = "widget flat radius-bordered" >
< div class = "widget-header bordered-bottom bordered-themeprimary" >
< i class = "widget-icon fa fa-text-width" ></ i > < span
class = "widget-caption" >OTS构建SpringMVC REST风格的准实时系统</ span >
< div class = "widget-buttons" >
< a href = "#" data-action = "refresh" > < i class = "fa fa-undo" ></ i >
</ a > < a href = "#" data-toggle = "config" > < i class = "fa fa-cog" ></ i >
</ a > < a href = "#" data-toggle = "maximize" > < i class = "fa fa-expand" ></ i >
</ a > < a href = "#" data-toggle = "collapse" > < i class = "fa fa-minus" ></ i >
</ a > < a href = "#" data-toggle = "dispose" > < i class = "fa fa-times" ></ i >
</ a >
</ div >
<!--Widget Buttons-->
</ div >
<!--Widget Header-->
< div class = "widget-body no-padding" >
< div class = "widget-main" >
< table class = "table" >
< thead >
< tr >
< th width = "30%" >返回类型</ th >
< th >请求示例</ th >
</ tr >
</ thead >
< tbody >
< tr >
< td >Web Page</ td >
< td >
< h5 >
查询用户name为
< a href = "./getPageResult/许士" >许士</ a > 的记录.
</ h5 >
< h5 >
查询用户name为
< a href = "./getPageResult/柯思" >柯思</ a > 的记录.
</ h5 >
</ td >
</ tr >
< tr >
< td >JSON</ td >
< td >
< h5 >
查询用户name为
< a href = "./getJsonResult/许士" >许士</ a > 的记录.
</ h5 >
< h5 >
查询用户name为
< a href = "./getJsonResult/柯思" >柯思</ a > 的记录.
</ h5 >
</ td >
</ tr >
</ tbody >
</ table >
</ div >
<!--Widget Main Container-->
</ div >
<!--Widget Body-->
</ div >
<!--Widget-->
</ div >
</ div >
</ div >
</ body >
<!-- /Body --> </ html >
|
2、Controller层映射对应方法
返回界面:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
package com.zt.mvc.web.controller;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.zt.mvc.web.model.User;
import com.zt.ots.OTSOperation;
@Controller @RequestMapping ( "/getPageResult" )
class WebController {
/**
* 查询name=传入值的OTS记录
* @param name
* @param model
* @return
*/
@RequestMapping (value = "/{name}" , method = RequestMethod.GET)
public String getName( @PathVariable String name, ModelMap model) {
// 调用OTS查询功能
List<User> users = OTSOperation.getRowByFilter(name);
if (users != null && users.size() > 0 )
model.addAttribute( "user" , users.get( 0 ));
else
model.addAttribute( "user" , new User());
return "result" ;
}
} |
返回JSON:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
package com.zt.mvc.web.controller;
import java.util.List;
import org.slf4j.Logger;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.zt.mvc.web.error.ErrorHolder;
import com.zt.mvc.web.exception.ResourceNFException;
import com.zt.mvc.web.model.User;
import com.zt.ots.OTSOperation;
@Controller @RequestMapping ( "/getJsonResult" )
class JSonController {
/**
* 根据name查询,返回user对象
* @param name
* @param model
* @return
* @throws ResourceNFException
*/
@RequestMapping (value = "/{name}" , method = RequestMethod.GET)
@ResponseBody
public User getName( @PathVariable String name, ModelMap model) throws ResourceNFException {
System.out.println( "Name: " + name);
List<User> users = OTSOperation.getRowByFilter(name);
if (users != null && users.size() > 0 )
return users.get( 0 );
else
return new User();
}
@ExceptionHandler
@ResponseBody
public ResponseEntity<ErrorHolder> handle(ResourceNFException e) {
Logger logger = null ;
logger.warn( "The resource was not found" , e);
return new ResponseEntity<ErrorHolder>( new ErrorHolder( "The resource was not found" ),
HttpStatus.NOT_FOUND);
}
} |
3、OTS过滤器查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
package com.zt.ots;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import com.aliyun.openservices.ots.ClientException;
import com.aliyun.openservices.ots.OTSClient;
import com.aliyun.openservices.ots.OTSException;
import com.aliyun.openservices.ots.model.ColumnValue;
import com.aliyun.openservices.ots.model.PrimaryKeyValue;
import com.aliyun.openservices.ots.model.RangeIteratorParameter;
import com.aliyun.openservices.ots.model.Row;
import com.aliyun.openservices.ots.model.RowPrimaryKey;
import com.aliyun.openservices.ots.model.condition.RelationalCondition;
import com.zt.mvc.web.model.User;
/** * @author wanglu
* 描述:OTS 操作类
*
*/
public class OTSOperation {
private static final String COLUMN_GID_NAME = "gid" ;
private static final String COLUMN_UID_NAME = "uid" ;
private static final String COLUMN_NAME_NAME = "name" ;
private static final String COLUMN_ADDRESS_NAME = "address" ;
private static final String COLUMN_AGE_NAME = "age" ;
private static final String COLUMN_MOBILE_NAME = "mobile" ;
/**
* 使用过滤功能读取一行数据
* @param gid
* @param uid
*/
public static List<User> getRowByFilter(String name) {
final String accessId = "<access_key_id>" ;
final String accessKey = "<access_key_sceret>" ;
final String instanceName = "cloud-report" ; // 仅做测试的project
OTSClient client = new OTSClient(endPoint, accessId, accessKey, instanceName);
final String tableName = "User_Info" ;
try {
// 查询范围为:start primary key = (gid=0, INF_MIN)
// end primary key = (gid=500, INF_MAX)
// 且满足条件为:name == '方法输入name值'的所有行。
RangeIteratorParameter param = new RangeIteratorParameter(tableName);
RowPrimaryKey startPk = new RowPrimaryKey();
startPk.addPrimaryKeyColumn(COLUMN_GID_NAME, PrimaryKeyValue.fromLong( 0 ));
startPk.addPrimaryKeyColumn(COLUMN_UID_NAME, PrimaryKeyValue.INF_MIN);
RowPrimaryKey endPk = new RowPrimaryKey();
endPk.addPrimaryKeyColumn(COLUMN_GID_NAME, PrimaryKeyValue.fromLong( 500 ));
endPk.addPrimaryKeyColumn(COLUMN_UID_NAME, PrimaryKeyValue.INF_MAX);
param.setInclusiveStartPrimaryKey(startPk);
param.setExclusiveEndPrimaryKey(endPk);
RelationalCondition filter = new RelationalCondition(COLUMN_NAME_NAME, RelationalCondition.CompareOperator.EQUAL, ColumnValue.fromString(name));
param.setFilter(filter);
Iterator<Row> rowIter = client.createRangeIterator(param);
int totalRows = 0 ;
List<User> result = new LinkedList<User>();
User user;
while (rowIter.hasNext()) {
Row row = rowIter.next();
user = new User();
user.setGid(row.getColumns().get(COLUMN_GID_NAME).toString());
user.setUid(row.getColumns().get(COLUMN_UID_NAME).toString());
user.setName(row.getColumns().get(COLUMN_NAME_NAME).toString());
user.setAddress(row.getColumns().get(COLUMN_ADDRESS_NAME).toString());
user.setAge(row.getColumns().get(COLUMN_AGE_NAME).toString());
user.setMobile(row.getColumns().get(COLUMN_MOBILE_NAME).toString());
result.add(user);
totalRows++;
System.out.println(row);
}
System.out.println( "Total rows read: " + totalRows);
// 如果没有抛出异常,则说明成功
System.out.println( "Get row succeeded." );
return result;
} catch (ClientException ex) {
// 如果抛出客户端异常,则说明参数等有误
System.out.println( "Get row failed." );
} catch (OTSException ex) {
// 如果抛出服务器端异常,则说明请求失败
System.out.println( "Get row failed." );
} finally {
client.shutdown();
}
return null ;
}
} |
4、查询结果返回前端显示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
<%@ page language= "java" import = "java.util.*" pageEncoding= "UTF-8" %>
<!DOCTYPE html> <!-- Head --> <head> <meta charset= "utf-8" />
<title>SpringMVC - Rest</title>
<meta name= "description" content= "Dashboard" />
<meta name= "viewport" content= "width=device-width, initial-scale=1.0" />
<meta http-equiv= "X-UA-Compatible" content= "IE=edge" />
<meta http-equiv= "Content-Type" content= "text/html; charset=utf-8" />
<link rel= "shortcut icon" href= "../assets/img/favicon.png"
type= "image/x-icon" >
</head> <body> <!-- Page Body -->
<div class = "page-body" >
<!-- Stacked To horizontal -->
<h5 class = "row-title before-blue" >Rest请求结果:</h5>
<div class = "grid-example" >
<div class = "row" >
<div class = "col-md-3" >Name - ${user.name}</div>
<div class = "col-md-3" >Address - ${user.address}</div>
<div class = "col-md-3" >Age - ${user.age}</div>
<div class = "col-md-3" >Mobile - ${user.mobile}</div>
</div>
</div>
</div>
</body> <!-- /Body --> </html> |
三、示例运行
1、首界面
将项目部署到Tomcat中,启动Tomcat,浏览器中输入地址:localhost:8080/rest-springmvc-ots/,回车
2、返回结果为web page的请求
在返回类型为web page中,点击‘许士’超链接,经过OTS准实时查询,观察到浏览器的链接变为:localhost:8080/rest-springmvc-ots/getPageResult/许士,该rest请求表示发送查询name为‘许士’的请求,返回OTS准实时查询结果。当然,不过不通过超链接,直接在浏览器窗口输入rest请求localhost:8080/rest-springmvc-ots/getPageResult/许士,也是可以的。
再如,查询name为‘柯思’的记录:localhost:8080/rest-springmvc-ots/getPageResult/柯思
3、返回结果为json的请求
在返回类型为json时,点击首页中的超链接,或者发送rest请求,即可得到OTS返回结果为JSON格式的结果:localhost:8080/rest-springmvc-ots/getJsonResult/许士:
再如,查询name为‘柯思’的记录:localhost:8080/rest-springmvc-ots/getJsonResult/柯思
四、开发建议
开发参考:OTS 开发参考 Git:https://github.com/aliyun/aliyun-tablestore-java-sdk
开发参考:SpringMVC REST开发参考:http://jinnianshilongnian.iteye.com/blog/1996071
使用参考:Gogs:https://www.oschina.net/p/gogs