-------基于HBase存储引擎并实现了排序、分页的Redis缓存策略
思路:
1、 当前系统缓存json,根据key存取value,key 和value都是String类型,直接返回前端。
2、 考虑支持分页,想到了缓存排好序的ResultSet,每一次前端请求,将所有数据缓存到redis,根据filter返回某页的数据,此部分数据是封装好的json。
考虑到使用list会增加开发的工作量,所以暂不用。
3、 考虑缓存ResultSet对象,所以该类要实现序列化,用hadoop的Writable,不用java本身的Serializable。
4、 前端需求不需要分页的仍然用原来接口,缓存json,用不同的annotation设置到查询方法上区分。
注:ResultSet为自己实现了的将hbase列存储数据转为按关系数据库行存储的数据结构。
具体实现:
1、由于每次都传给前端少量数据,所以要传给前端总记录条数方便其确定分页信息,可封转在json里。
2、rediscache操作类增加支持序列化的存取方法:
@Override
public ResultSet getResultSetCache(String key) {
try {
log.info("get object from redisCache :"+key);
ShardedJedis jedis = pool.getResource();
ResultSet value = SerializableUtils.deSerialize(jedis.get(key.getBytes()), ResultSet.class);
pool.returnResource(jedis);
return value;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public boolean setResultSetCache(String key, ResultSet value) {
try {
log.info("add object to redisCache :"+key);
ShardedJedis jedis = pool.getResource();
jedis.set(key.getBytes(),SerializableUtils.serialize(value));
pool.returnResource(jedis);
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
其中SerializableUtils工具类实现对writable子类的序列化、反序列化操作:
public class SerializableUtils {
public static byte[] serialize(Object value) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream oos = new DataOutputStream(bos);
if (value instanceof Writable) {
((T) value).write(oos);
} else {
throw new IOException("not instanceof Writable!");
}
return bos.toByteArray();
}
public static T deSerialize(byte[] byteCode,Class classType) throws IOException{
ByteArrayInputStream bis = new ByteArrayInputStream(byteCode);
T object = null;
try {
DataInputStream dis = new DataInputStream(bis);
object = (T)classType.newInstance();
object.readFields(dis);
return object;
} catch (IOException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
}
java本身的序列化反序列化工具类:
public class SerializableUtil {
public static byte[] serialize(Object value){
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(value);
} catch (IOException e) {
e.printStackTrace();
}
return bos.toByteArray();
}
public static T deSerialize(byte[] bytes){
ByteArrayInputStream bIs = new ByteArrayInputStream(bytes);
try {
ObjectInputStream oIs = new ObjectInputStream(bIs);
return (T)oIs.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
3、 增加两个annotation及两个切面处理类,annotation用以在具体查询方法上添加,区分需不需要排序,需要排序的缓存ResultSet,不需要的缓存Json。
需要分页的切面类主要代码:
String md5key = MD5Util.getMD5(filter.getValue().toString());
Object value = redisCacheManager.getResultSetCache(md5key);
String srcClzzName = "";
boolean flag = false;
if (null != value) {
srcClzzName = pjp.getTarget().getClass().getSimpleName();
JSONObject json = redisCacheManager.getJson(value,filter,srcClzzName);
return json;
} else if ("null".equals(value)) {
return null;
} else { //执行hbase查询
ResultSet orderRs = redisCacheManager.getResultSet(filter,srcClzzName);
boolean ifNeedCache = orderRs.size()>0?true:false;
if (ifNeedCache == true){
redisCacheManager.setResultSetCache(md5key, orderRs);
}
return redisCacheManager.getJson(orderRs, filter, srcClzzName);
}
getResultSet部分代码:
public ResultSet getOrderdResultSet(QueryFilter filter) {
SaleDetailsFilter Salefilter = (SaleDetailsFilter) filter;
String shopId = Salefilter.getShopId();
String date = Salefilter.getDate();//
int proType = Salefilter.getProType();//
byte sku = 1, spu = 0;
byte isSKU = Salefilter.getIsMergeSKU() ? spu : sku;
long[] proNum = Salefilter.getProNums();//
String[] itemNums = Salefilter.getItemNums();//
ResultSet result = new ResultSet();
try {
// set start row;
ByteBuffer rowkey = ByteBuffer.allocate(13);
rowkey.putInt(Integer.parseInt(shopId));
rowkey.putInt(Integer.parseInt(date));
// set product type, default 0
rowkey.putInt(proType);
rowkey.put(isSKU);
byte[] endkey = CommonUtil.getEndKey(rowkey.array(),"int,int,int,byte");
Where where = null;
List orderlist = null;
if ((proNum != null && proNum.length > 0)) {根据前端传入的商品id查询记录
//拼where条件
for (int i = 0; i < proNum.length; i++) {
if(i>0){
where = or(eq("d:ProID",proNum[i]),where);
continue;
}
where = eq("d:ProID",proNum[i]);
}
orderlist = new ArrayList();
Map ordermap = Salefilter.getOrderBy();
if(null!=ordermap){
Set keys = ordermap.keySet();
for(Iterator it = keys.iterator();it.hasNext();){
String key = it.next();
Integer value = ordermap.get(key);
if(value==0){
orderlist.add(asc(key));
}else if(value==1){
orderlist.add(desc(key));
}
}
result = client.simpleQuery(rowkey.array(),endkey, select, tableName, where, orderlist,-1);
}else{
result = client.simpleQuery(rowkey.array(),endkey, select, tableName, where, null,-1);
}
return result;
} else if (itemNums != null && itemNums.length > 0) {
..... } else {
...... }
} catch (Exception e) {
logger.error(e.getMessage(), e);
} catch (Throwable e) {
logger.error(e.getMessage(), e);
e.printStackTrace();
}
return null;
}
当然,用到的很多方法在cp基础上自定义的对hbase的查询接口。
4、 查询接口类增加相应属性、方法,并且hbase表要加入coprocessor的支持,因为用到了聚合查询。
从缓存得到某一页的数据返回前端:
public JSONObject getPagedJson(Object value, QueryFilter filter,String srcClzzName) throws JSONException {
JSONObject result = new JSONObject();
ResultSet tempRs = (ResultSet) value;
long size = tempRs.size();
ResultSet realRs = new ResultSet();
SaleDetailsFilter sdf = (SaleDetailsFilter) filter;
int pageNum = sdf.getPageNum();
int start = (pageNum - 1) * PAGE;
int maxend = PAGE * pageNum-1;
long end = size-(PAGE*pageNum)>=0?maxend:start+size-(pageNum-1)*PAGE-1;
for (int i = start; i <= end; i++) {
realRs.add(tempRs.get(i));
}
JSONObject temp = new JSONObject();
if (realRs.size() > 0) {
temp = trans.toJSONObject(realRs,select);
}
result.put(Transformer.DTL, temp);
result.put(Transformer.SIZE, size);
return result;
}
5、 查询接口类对应的filter增加两个属性,orderby和pageNum。Orderby表示排序的列,map类型,key表示列,值为0或1,表示升序或降序,默认为空,表示不排序。PageNum表示页号,默认第一页。
并将orderby纳入到查询条件中作为生成redis key的json。
pageNum是不能纳入的,因为前端翻页时应该对应redis中的一份数据。redis中的数据的份数由查询的种类决定,每一份都是查询的全量数据。后续考虑用list或索引的方式实现。
6、 redis集群安装—略。
7、 redis java客户端配置文件在web-inf下,包括连接池配置,属性配置,切面配置。