HibernateShard
多數(shù)據(jù)庫水平分區(qū)解決方案。
1. 簡介
Hibernate 的一個(gè)擴(kuò)展,用于處理多數(shù)據(jù)庫水平分區(qū)架構(gòu)。
由google工程師 2007年 捐獻(xiàn)給 Hibernate社區(qū)。
http://www.hibernate.org/414.html
目前版本: 3.0.0 beta2, 未發(fā)GA版。
條件:Hibernate Core 3.2, JDK 5.0
2. 水平分區(qū)原理
一個(gè)庫表如 Order 存在于多個(gè)數(shù)據(jù)庫實(shí)例上。按特定的分區(qū)邏輯,將該庫表的數(shù)據(jù)存儲在這些實(shí)例中,一條記錄的主鍵 PK,在所有實(shí)例中不得重復(fù)。
水平分區(qū)在大型網(wǎng)站,大型企業(yè)應(yīng)用中經(jīng)常采用。 像
www.sina.com.cn ,www.163.com
www.bt285.cn www.guihua.org
目的出于海量數(shù)據(jù)分散存儲,分散操作,分散查詢以便提高數(shù)據(jù)處理量和整體數(shù)據(jù)處理性能。
使用:
google工程師的設(shè)計(jì)還是非常好的,完全兼容 Hibernate本身的主要接口。
- org.hibernate.Session
- org.hibernate.SessionFactory
- org.hibernate.Criteria
- org.hibernate.Query
org.hibernate.Session
org.hibernate.SessionFactory
org.hibernate.Criteria
org.hibernate.Query
因此程序員開發(fā)變化不大,甚至不需要關(guān)心后臺使用了分區(qū)數(shù)據(jù)庫。程序遷移問題不大。而且配置上比較簡明。
3. 三種策略:
1) ShardAccessStrategy, 查詢操作時(shí),到那個(gè)分區(qū)執(zhí)行。
默認(rèn)提供兩個(gè)實(shí)現(xiàn):
順序策略:SequentialShardAccessStrategy, 每個(gè)query按順序在所有分區(qū)上執(zhí)行。
平行策略:ParallelShardAccessStrategy, 每個(gè)query以多線程方式并發(fā)平行的在所有分區(qū)上執(zhí)行。 此策略下,需要使用線程池機(jī)制滿足特定的性能需要,java.util.concurrent.ThreadPoolExecutor。
2) ShardSelectionStrategy, 新增對象時(shí),存儲到哪個(gè)分區(qū)。
框架默認(rèn)提供了一個(gè)輪詢選擇策略 RoundRobinShardSelectionStrategy, 但一般不這樣使用。
通常采用“attribute-based sharding”機(jī)制,基于屬性分區(qū)。一般是用戶根據(jù)表自己實(shí)現(xiàn)一個(gè)基于屬性分區(qū)的策略類ShardSelectionStrategy ,例如,以下WeatherReport基于continent屬性選擇分區(qū):
- public class WeatherReportShardSelectionStrategy implements ShardSelectionStrategy {
- public ShardId selectShardIdForNewObject(Object obj) {
- if(obj instanceof WeatherReport) {
- return ((WeatherReport)obj).getContinent().getShardId();
- }
- throw new IllegalArgumentException();
- }
public class WeatherReportShardSelectionStrategy implements ShardSelectionStrategy {
public ShardId selectShardIdForNewObject(Object obj) {
if(obj instanceof WeatherReport) {
return ((WeatherReport)obj).getContinent().getShardId();
}
throw new IllegalArgumentException();
}
}
3) ShardResolutionStrategy, 該策略用于查找單個(gè)對象時(shí),判斷它在哪個(gè)或哪幾個(gè)分區(qū)上。
默認(rèn)使用 AllShardsShardResolutionStrategy ,可以自定義例如:
- public class WeatherReportShardResolutionStrategy extends AllShardsShardResolutionStrategy {
- public WeatherReportShardResolutionStrategy(List<ShardId> shardIds) {
- super(shardIds);
- }
-
- public List<ShardId> selectShardIdsFromShardResolutionStrategyData(
- ShardResolutionStrategyData srsd) {
- if(srsd.getEntityName().equals(WeatherReport.class.getName())) {
- return Continent.getContinentByReportId(srsd.getId()).getShardId();
- }
- return super.selectShardIdsFromShardResolutionStrategyData(srsd);
- }
- }
public class WeatherReportShardResolutionStrategy extends AllShardsShardResolutionStrategy {
public WeatherReportShardResolutionStrategy(List<ShardId> shardIds) {
super(shardIds);
}
public List<ShardId> selectShardIdsFromShardResolutionStrategyData(
ShardResolutionStrategyData srsd) {
if(srsd.getEntityName().equals(WeatherReport.class.getName())) {
return Continent.getContinentByReportId(srsd.getId()).getShardId();
}
return super.selectShardIdsFromShardResolutionStrategyData(srsd);
}
}
4. 水平分區(qū)下的查詢
對于簡單查詢 HibernateShard 可以滿足。
水平分區(qū)下多庫查詢是一個(gè)挑戰(zhàn)。主要存在于以下三種操作:
1) distinct
因?yàn)樾枰闅v所有shard分區(qū),并進(jìn)行合并判斷重復(fù)記錄。
2) order by
類似 1)
3) aggregation
count,sim,avg等聚合操作先分散到分區(qū)執(zhí)行,再進(jìn)行匯總。
是不是有點(diǎn)類似于 MapReduce ? 呵呵。
目前 HibernateShard 不支持 1), 2), 對 3) 部分支持
HibernateShard 目前通過 Criteria 接口的實(shí)現(xiàn)對 聚合提供了較好的支持, 因?yàn)?Criteria 以API接口指定了 Projection 操作,邏輯相對簡單。
而HQL,原生 SQL 還不支持此類操作。
5. 再分區(qū)和虛擬分區(qū)
當(dāng)數(shù)據(jù)庫規(guī)模增大,需要調(diào)整分區(qū)邏輯和數(shù)據(jù)存儲時(shí), 需要再分區(qū)。
兩種方式: 1)數(shù)據(jù)庫數(shù)據(jù)遷移其他分區(qū); 2) 改變記錄和分區(qū)映射關(guān)系。這兩種方式都比較麻煩。尤其“改變記錄和分區(qū)映射關(guān)系”,需要調(diào)整 ShardResolutionStrategy。
HibernateShard 提供了一種虛擬分區(qū)層。當(dāng)需要調(diào)整分區(qū)策略時(shí),只需要調(diào)整虛擬分區(qū)和物理分區(qū)映射關(guān)系即可。以下是使用虛擬分區(qū)時(shí)的配置創(chuàng)建過程:
-
- Map<Integer, Integer> virtualShardMap = new HashMap<Integer, Integer>();
- virtualShardMap.put(0, 0);
- virtualShardMap.put(1, 0);
- virtualShardMap.put(2, 1);
- virtualShardMap.put(3, 1);
- ShardedConfiguration shardedConfig =
- new ShardedConfiguration(
- prototypeConfiguration,
- configurations,
- strategyFactory,
- virtualShardMap);
- return shardedConfig.buildShardedSessionFactory();
Map<Integer, Integer> virtualShardMap = new HashMap<Integer, Integer>();
virtualShardMap.put(0, 0);
virtualShardMap.put(1, 0);
virtualShardMap.put(2, 1);
virtualShardMap.put(3, 1);
ShardedConfiguration shardedConfig =
new ShardedConfiguration(
prototypeConfiguration,
configurations,
strategyFactory,
virtualShardMap);
return shardedConfig.buildShardedSessionFactory();
6. 局限:
1)HibernateShard 不支持垂直分區(qū), 垂直+水平混合分區(qū)。
2) 水平分區(qū)下 查詢功能受到一定限制,有些功能不支持。實(shí)踐中,需要在應(yīng)用層面對水平分區(qū)算法進(jìn)行更多的考慮。
3) 不支持跨分區(qū)的 關(guān)系 操作。例如:刪除A分區(qū)上的 s 表,B分區(qū)上的關(guān)聯(lián)子表 t的記錄無法進(jìn)行參照完整性約束檢查。 (其實(shí)這個(gè)相對 跨分區(qū)查詢的挑戰(zhàn)應(yīng)該說小的多,也許google工程師下個(gè)版本會支持,呵呵)
4) 解析策略接口似乎和對象ID全局唯一性有些自相矛盾,
AllShardsShardResolutionStrategy 的接口返回的是給定對象ID所在的 shard ID集合,按理應(yīng)該是明確的一個(gè) shard ID.
參考資料:HibernateShard 參考指南。
現(xiàn)在正開發(fā)的定位模塊用到的定位設(shè)置是塞格車圣導(dǎo)航設(shè)備,發(fā)送指令返回的經(jīng)緯度需要轉(zhuǎn)換成十進(jìn)制,再到GIS系統(tǒng)獲取地理信息描述。以后需要要經(jīng)常用到這方面的知識,隨筆寫下。
將經(jīng)緯度轉(zhuǎn)換成十進(jìn)制
公式:
Decimal Degrees = Degrees + minutes/60 + seconds/3600
例:57°55'56.6" =57+55/60+56.6/3600=57.9323888888888
如把經(jīng)緯度 (longitude,latitude) (205.395583333332,57.9323888888888)轉(zhuǎn)換據(jù)成坐標(biāo)(Degrees,minutes,seconds)(205°23'44.1",57°55'56.6")。
步驟如下:
1、 直接讀取"度":205
2、(205.395583333332-205)*60=23.734999999920 得到"分":23
3、(23.734999999920-23)*60=44.099999995200 得到"秒":44.1
發(fā)送定位指令,終端返回的經(jīng)緯度信息如下:
(ONE072457A3641.2220N11706.2569E000.000240309C0000400)
按照協(xié)議解析

獲得信息體的經(jīng)緯度是主要,其它不要管,直接用String類的substring()方法截掉,獲取的經(jīng)緯度
3641.2220N11706.2569E http://www.bt285.cn
- package com.tdt.test;
-
- import com.tdt.api.gis.LocationInfo;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- public class LonlatConversion {
-
-
-
-
-
-
-
- public static String xypase(String dms, String type) {
- if (dms == null || dms.equals("")) {
- return "0.0";
- }
- double result = 0.0D;
- String temp = "";
-
- if (type.equals("E")) {
- String e1 = dms.substring(0, 3);
-
- String e2 = dms.substring(3, dms.length());
-
- result = Double.parseDouble(e1);
- result += (Double.parseDouble(e2) / 60.0D);
- temp = String.valueOf(result);
- if (temp.length() > 9) {
- temp = e1 + temp.substring(temp.indexOf("."), 9);
- }
- } else if (type.equals("N")) {
- String n1 = dms.substring(0, 2);
- String n2 = dms.substring(2, dms.length());
-
- result = Double.parseDouble(n1);
- result += Double.parseDouble(n2) / 60.0D;
- temp = String.valueOf(result);
- if (temp.length() > 8) {
- temp = n1 + temp.substring(temp.indexOf("."), 8);
- }
- }
- return temp;
- }
- public static void main(String[] args) {
- String info="(ONE072457A3641.2220N11706.2569E000.000240309C0000400)";
- info=info.substring(11,info.length()-13);
-
- String N = info.substring(0, info.indexOf("N"));
-
- String E = info.substring(info.indexOf("N")+1,info.indexOf("E"));
-
- double x = Double.parseDouble(CoordConversion.xypase(E,"E"));
- double y = Double.parseDouble(CoordConversion.xypase(N,"N"));
- String result =LocationInfo.getLocationInfo("test", x, y);
- System.out.println(result);
- }
- }
package com.tdt.test;
import com.tdt.api.gis.LocationInfo;
/**
* <p>Title:坐標(biāo)轉(zhuǎn)換 </p>
*
* <p>Description:</p>
*
* <p>Copyright: Copyright (c) 2009</p>
*
* <p>Company:</p>
*
* @author sunnylocus
* @version 1.0 [2009-03-24]
*
*/
public class LonlatConversion {
/**
*
* @param dms 坐標(biāo)
* @param type 坐標(biāo)類型
* @return String 解析后的經(jīng)緯度
*/
public static String xypase(String dms, String type) {
if (dms == null || dms.equals("")) {
return "0.0";
}
double result = 0.0D;
String temp = "";
if (type.equals("E")) {//經(jīng)度
String e1 = dms.substring(0, 3);//截取3位數(shù)字,經(jīng)度共3位,最多180度
//經(jīng)度是一倫敦為點(diǎn)作南北兩極的線為0度,所有往西和往東各180度
String e2 = dms.substring(3, dms.length());//需要運(yùn)算的小數(shù)
result = Double.parseDouble(e1);
result += (Double.parseDouble(e2) / 60.0D);
temp = String.valueOf(result);
if (temp.length() > 9) {
temp = e1 + temp.substring(temp.indexOf("."), 9);
}
} else if (type.equals("N")) { //緯度,緯度是以赤道為基準(zhǔn),相當(dāng)于把地球分兩半,兩個(gè)半球面上的點(diǎn)和平面夾角0~90度
String n1 = dms.substring(0, 2);//截取2位,緯度共2位,最多90度
String n2 = dms.substring(2, dms.length());
result = Double.parseDouble(n1);
result += Double.parseDouble(n2) / 60.0D;
temp = String.valueOf(result);
if (temp.length() > 8) {
temp = n1 + temp.substring(temp.indexOf("."), 8);
}
}
return temp;
}
public static void main(String[] args) {
String info="(ONE072457A3641.2220N11706.2569E000.000240309C0000400 http://www.guihua.org )";
info=info.substring(11,info.length()-13);
//緯度
String N = info.substring(0, info.indexOf("N"));
//經(jīng)度
String E = info.substring(info.indexOf("N")+1,info.indexOf("E"));
//請求gis,獲取地理信息描述
double x = Double.parseDouble(CoordConversion.xypase(E,"E"));
double y = Double.parseDouble(CoordConversion.xypase(N,"N"));
String result =LocationInfo.getLocationInfo("test", x, y); //System.out.println("徑度:"+x+","+"緯度:"+y);
System.out.println(result);
}
}
運(yùn)行結(jié)果
在濟(jì)南市,位于輕騎路和八澗堡路附近;在環(huán)保科技園國際商務(wù)中心和濟(jì)南市區(qū)賢文莊附近。
一、本圖片生成器具有以下功能特性:
1、可以設(shè)置圖片的寬度、高度、外框顏色、背景色;
2、可以設(shè)置圖片字體的大小、名稱、顏色;
3、可以設(shè)置輸出圖片的格式,如JPEG、GIF等;
4、可以將圖片存儲到一個(gè)文件或者存儲到一個(gè)輸出流;
5、可以為圖片增加若干條干擾線(在生成隨機(jī)碼圖片時(shí)可用此特性);
6、打印在圖片上的文字支持自動換行;
另外,本圖片生成器還用到了模板方法模式。
二、下面列出相關(guān)的源代碼
1、抽象類AbstractImageCreator的源代碼
- public abstract class AbstractImageCreator {
- private static Random rnd = new Random(new Date().getTime());
-
-
- private int width = 200;
-
-
- private int height = 80;
-
-
- private Color rectColor;
-
-
- private Color bgColor;
-
-
- private int lineNum = 0;
-
-
- private String formatName = "JPEG";
-
-
- private Color fontColor = new Color(0, 0, 0);
-
-
- private String fontName = "宋體";
-
-
- private int fontSize = 15;
-
-
-
-
-
-
-
-
- private void drawRandomLine(Graphics graph){
- for(int i=0;i<lineNum;i++){
-
- graph.setColor(getRandomColor(100, 155));
-
-
- int x1 = rnd.nextInt(width);
- int y1 = rnd.nextInt(height);
-
- int x2 = rnd.nextInt(width);
- int y2 = rnd.nextInt(height);
-
-
- graph.drawLine(x1, y1, x2, y2);
- }
- }
-
-
-
-
- private Color getRandomColor(int base, int range){
- if((base + range) > 255) range = 255 - base;
-
- int red = base + rnd.nextInt(range);
- int green = base + rnd.nextInt(range);
- int blue = base + rnd.nextInt(range);
-
- return new Color(red, green, blue);
- }
-
-
- public void drawImage(String text)throws IOException{
- BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
-
- if(rectColor == null) rectColor = new Color(0, 0, 0);
- if(bgColor == null) bgColor = new Color(240, 251, 200);
-
-
- Graphics graph = image.getGraphics();
-
-
- graph.setColor(bgColor);
- graph.fillRect(0, 0, width, height);
-
-
- graph.setColor(rectColor);
- graph.drawRect(0, 0, width-1, height-1);
-
-
- drawRandomLine(graph);
-
-
- drawString(graph, text);
-
-
- graph.dispose();
-
-
- saveImage(image);
- }
-
- protected abstract void drawString(Graphics graph, String text);
-
- protected abstract void saveImage(BufferedImage image)throws IOException;
-
- }
public abstract class AbstractImageCreator {
private static Random rnd = new Random(new Date().getTime());
//圖片寬度
private int width = 200;
//圖片高度
private int height = 80;
//外框顏色
private Color rectColor;
//背景色
private Color bgColor;
//干擾線數(shù)目
private int lineNum = 0;
//圖片格式
private String formatName = "JPEG";
//字體顏色
private Color fontColor = new Color(0, 0, 0);
//字體名稱
private String fontName = "宋體";
//字體大小
private int fontSize = 15;
//##### 這里省略成員變臉的get、set方法 #####
/**
* 畫干擾線
*/
private void drawRandomLine(Graphics graph){
for(int i=0;i<lineNum;i++){
//線條的顏色
graph.setColor(getRandomColor(100, 155));
//線條兩端坐標(biāo)值
int x1 = rnd.nextInt(width);
int y1 = rnd.nextInt(height);
int x2 = rnd.nextInt(width);
int y2 = rnd.nextInt(height);
//畫線條
graph.drawLine(x1, y1, x2, y2);
}
}
/**
* 隨機(jī)獲取顏色對象
*/
private Color getRandomColor(int base, int range){
if((base + range) > 255) range = 255 - base;
int red = base + rnd.nextInt(range);
int green = base + rnd.nextInt(range);
int blue = base + rnd.nextInt(range);
return new Color(red, green, blue);
}
//該方法內(nèi)應(yīng)用了模板方法模式
public void drawImage(String text)throws IOException{
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
if(rectColor == null) rectColor = new Color(0, 0, 0);
if(bgColor == null) bgColor = new Color(240, 251, 200);
//獲取畫布
Graphics graph = image.getGraphics();
//畫長方形
graph.setColor(bgColor);
graph.fillRect(0, 0, width, height);
//外框
graph.setColor(rectColor);
graph.drawRect(0, 0, width-1, height-1);
//畫干擾線
drawRandomLine(graph);
//畫字符串
drawString(graph, text);
//執(zhí)行
graph.dispose();
//輸出圖片結(jié)果
saveImage(image);
}
protected abstract void drawString(Graphics graph, String text);
protected abstract void saveImage(BufferedImage image)throws IOException;
}
2、類DefaultImageCreator的源代碼
該類將生成的圖片存儲到一個(gè)文件中,需要設(shè)置outputFilePath成員變量值,該成員變量值表示圖片的存儲全路徑。
- public class DefaultImageCreator extends AbstractImageCreator {
- private String outputFilePath;
-
- public String getOutputFilePath() {
- return outputFilePath;
- }
-
- public void setOutputFilePath(String outputFilePath) {
- this.outputFilePath = outputFilePath;
- }
-
- public DefaultImageCreator(){
-
- }
-
- public DefaultImageCreator(String outputFilePath){
- this.outputFilePath = outputFilePath;
- }
-
- @Override
- protected void drawString(Graphics graph, String text) {
- graph.setColor(getFontColor());
- Font font = new Font(getFontName(), Font.PLAIN, getFontSize());
- graph.setFont(font);
-
- FontMetrics fm = graph.getFontMetrics(font);
- int fontHeight = fm.getHeight();
-
- int offsetLeft = 0;
- int rowIndex = 1;
- for(int i=0;i<text.length();i++){
- char c = text.charAt(i);
- int charWidth = fm.charWidth(c);
-
-
- if(Character.isISOControl(c) || offsetLeft >= (getWidth()-charWidth)){
- rowIndex++;
- offsetLeft = 0;
- }
-
- graph.drawString(String.valueOf(c), offsetLeft, rowIndex * fontHeight);
- offsetLeft += charWidth;
- }
- }
-
- @Override
- protected void saveImage(BufferedImage image)throws IOException{
- ImageIO.write(image, getFormatName(), new File(outputFilePath));
- }
-
- }
public class DefaultImageCreator extends AbstractImageCreator {
private String outputFilePath;
public String getOutputFilePath() {
return outputFilePath;
}
public void setOutputFilePath(String outputFilePath) {
this.outputFilePath = outputFilePath;
}
public DefaultImageCreator(){
}
public DefaultImageCreator(String outputFilePath){
this.outputFilePath = outputFilePath;
}
@Override
protected void drawString(Graphics graph, String text) {
graph.setColor(getFontColor());
Font font = new Font(getFontName(), Font.PLAIN, getFontSize());
graph.setFont(font);
FontMetrics fm = graph.getFontMetrics(font);
int fontHeight = fm.getHeight(); //字符的高度
int offsetLeft = 0;
int rowIndex = 1;
for(int i=0;i<text.length();i++){
char c = text.charAt(i);
int charWidth = fm.charWidth(c); //字符的寬度
//另起一行
if(Character.isISOControl(c) || offsetLeft >= (getWidth()-charWidth)){
rowIndex++;
offsetLeft = 0;
}
graph.drawString(String.valueOf(c), offsetLeft, rowIndex * fontHeight);
offsetLeft += charWidth;
}
}
@Override
protected void saveImage(BufferedImage image)throws IOException{
ImageIO.write(image, getFormatName(), new File(outputFilePath));
}
}
3、類OutputStreamImageCreator的源代碼
該類將生成的圖片存儲到一個(gè)輸出流中,需要設(shè)置out成員變量值。
- public class OutputStreamImageCreator extends DefaultImageCreator {
- private OutputStream out ;
-
- public OutputStream getOut() {
- return out;
- }
-
- public void setOut(OutputStream out) {
- this.out = out;
- }
-
- public OutputStreamImageCreator(){
-
- }
-
- public OutputStreamImageCreator(OutputStream out){
- this.out = out;
- }
-
- @Override
- public String getOutputFilePath() {
- return null;
- }
-
- @Override
- public void setOutputFilePath(String outputFilePath) {
- outputFilePath = null;
- }
-
- @Override
- protected void saveImage(BufferedImage image) throws IOException {
- if(out!=null) ImageIO.write(image, getFontName(), out);
- }
-
- }
public class OutputStreamImageCreator extends DefaultImageCreator {
private OutputStream out ;
public OutputStream getOut() {
return out;
}
public void setOut(OutputStream out) {
this.out = out;
}
public OutputStreamImageCreator(){
}
public OutputStreamImageCreator(OutputStream out){
this.out = out;
}
@Override
public String getOutputFilePath() {
return null;
}
@Override
public void setOutputFilePath(String outputFilePath) {
outputFilePath = null;
}
@Override
protected void saveImage(BufferedImage image) throws IOException {
if(out!=null) ImageIO.write(image, getFontName(), out);
}
}
三、實(shí)例代碼
1、圖片存儲到文件
StringBuffer sb = new StringBuffer();
- sb.append("中華人民共和國\n");
- sb.append("中華人民共和國\n");
-
- DefaultImageCreator creator = new DefaultImageCreator("c:\\img.jpeg");
- creator.setWidth(150);
- creator.setHeight(100);
- creator.setLineNum(60);
- creator.setFontSize(20);
- creator.drawImage(sb.toString());
StringBuffer sb = new StringBuffer();
sb.append("中華人民共和國\n");
sb.append("中華人民共和國\n");
DefaultImageCreator creator = new DefaultImageCreator("c:\\img.jpeg");
creator.setWidth(150);
creator.setHeight(100);
creator.setLineNum(60);
creator.setFontSize(20);
creator.drawImage(sb.toString());
