2018年4月7日
背景:
大型商业项目,无论是java还是php,或是python,都会对redis数据库进行开发,redis数据库相比于其他的key-value数据库还是优势明显的。redis的叙述,在前文《Redis (REmote DIctionary Server):一个高性能的key-value数据库的学习(1)》有提到,这里不多介绍。
运用:
在实际工作中,工程代码已经有了良好的封装(企业内部都会有自己的开发框架,而不是只拿第三方开源框架来直接用),所以更多时候,我实在思考框架这样编码的思路和逻辑;开发过一两次任务后,发现用的真方便,而且逻辑严谨。上文列举的例子是:用protocol buffer来序列化数据,然后将二进制数据存放到redis;下面继续说一下如何操作java对象,将Object序列化和保存;
我们在使用MySQL数据库时,这是一个关系型数据库,常用的套路是代码分层,Service -- Dao -- Mapper/Sql ;从业务开始到操作层到数据库语言,这是一个好方法,redis 也同样是这样的操作套路,不过redis是key-value数据库,所以有一些不同:
1)redis的操作也有增删改查,但是方法的名字极其简单地,如set/get等等,当然深入使用还有更多发现;
2)redis的操作层本质上只有一个,那就是redis为java开发者提供的对数据库进行操作的API,service和dao就是在其基础上封装上去的;
Jedis的简单使用
我们常常使用jedis来连接使用redis;但是在公司项目里,却不是使用Jedis来操作的,这个问题我会继续跟进。
例子:
import redis.clients.jedis.Jedis;
public class test5 {
public static void main (String [] args){
Jedis jedis = new Jedis("10.0.1.195");
// Jedis jedis = new Jedis("localhost"); //连接效果与详细IP一样
System.out.println("connect successfully!");
System.out.println("service running : "+ jedis.ping());
System.out.println("========================");
jedis.set("mmb", "8888");
System.out.println("redis 存储的字符串为: "+ jedis.get("mmb"));
System.out.println("========================");
}
}
结果:
connect successfully!
service running : PONG
========================
redis 存储的字符串为: 8888
========================
代码实例:
下面还是以游戏公告为例子,
Dao层:
我对redis的Dao层进行了封装,实际调用的还是Jedis API:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import **.redis.JRedis;
import **.proclamation.ProclamationInfoList;
import **.proclamation.RoleProclamationInfo;
import **.dao.redis.base.RedisKeyUtil;
import **.base.StorageKey;
@Repository
public class ProclamationRepo {
@Autowired
private JRedis jRedis;
private static final String PROCLAMATION_SYSTEM_NOTICE_KEY_STR = "proclamation_system_notice_key_do_not_change_it";
private byte[] getProclamationListNoticeKey(){
return RedisKeyUtil.getByteArray(PROCLAMATION_SYSTEM_NOTICE_KEY_STR, StorageKey.global_world_channel_proclamationlist_notice);
}
public void saveProclamationInfoList(ProclamationInfoList info){
byte[] key = getProclamationListNoticeKey();
byte[] bytes = info.copyTo().toByteArray();
jRedis.set(key, bytes);
}
public ProclamationInfoList getProclamationNotice(){
byte[] key = getProclamationListNoticeKey();
byte[] bytes = jRedis.get(key);
ProclamationInfoList proclamationInfoList = new ProclamationInfoList();
if (bytes == null) {
saveProclamationInfoList(proclamationInfoList);
}else{
proclamationInfoList.parseFrom(bytes);
}
return proclamationInfoList;
}
private byte[] getRoleProclamationInfoKey(long roleId) { //该方法可以忽略
return RedisKeyUtil.getByteArray(roleId, StorageKey.role_proclamation_info_key);
}
public void saveRoleProclamationInfo(RoleProclamationInfo roleProclamationInfo) { //该方法可以忽略
byte[] key = getRoleProclamationInfoKey(roleProclamationInfo.getRoleId());
byte[] bytes = roleProclamationInfo.toByteArray();
jRedis.set(key, bytes);
}
public RoleProclamationInfo getRoleProclamationInfo(long roleId){ //该方法可以忽略
byte[] key = getRoleProclamationInfoKey(roleId);
byte[] bytes = jRedis.get(key);
if (bytes == null) {
RoleProclamationInfo roleProclamationInfo = new RoleProclamationInfo(roleId);
roleProclamationInfo.setRoleId(roleId);
saveRoleProclamationInfo(roleProclamationInfo);
return roleProclamationInfo;
} else {
RoleProclamationInfo roleProclamationInfo = new RoleProclamationInfo(bytes);
roleProclamationInfo.setRoleId(roleId);
return roleProclamationInfo;
}
}
}
byte[] key = getProclamationListNoticeKey();
byte[] bytes = info.copyTo().toByteArray();
jRedis.set(key, bytes);
}
public ProclamationInfoList getProclamationNotice(){
byte[] key = getProclamationListNoticeKey();
byte[] bytes = jRedis.get(key);
ProclamationInfoList proclamationInfoList = new ProclamationInfoList();
if (bytes == null) {
saveProclamationInfoList(proclamationInfoList);
}else{
proclamationInfoList.parseFrom(bytes);
}
return proclamationInfoList;
}
private byte[] getRoleProclamationInfoKey(long roleId) { //该方法可以忽略
return RedisKeyUtil.getByteArray(roleId, StorageKey.role_proclamation_info_key);
}
public void saveRoleProclamationInfo(RoleProclamationInfo roleProclamationInfo) { //该方法可以忽略
byte[] key = getRoleProclamationInfoKey(roleProclamationInfo.getRoleId());
byte[] bytes = roleProclamationInfo.toByteArray();
jRedis.set(key, bytes);
}
public RoleProclamationInfo getRoleProclamationInfo(long roleId){ //该方法可以忽略
byte[] key = getRoleProclamationInfoKey(roleId);
byte[] bytes = jRedis.get(key);
if (bytes == null) {
RoleProclamationInfo roleProclamationInfo = new RoleProclamationInfo(roleId);
roleProclamationInfo.setRoleId(roleId);
saveRoleProclamationInfo(roleProclamationInfo);
return roleProclamationInfo;
} else {
RoleProclamationInfo roleProclamationInfo = new RoleProclamationInfo(bytes);
roleProclamationInfo.setRoleId(roleId);
return roleProclamationInfo;
}
}
}
分析:
1)JRedis的引入,通过spring的IOC,@Autowired ,实例化导入;(JRedis应该是开源框架的一个数据结构,有待查实,但是功能定位确是和Jedis相似)
@Autowired
private JRedis jRedis;
----------------------------------
补充:JRedis 为redis的客户端框架,为开发者提供的;参考 https://redis.io/clients#java;
2)字符串 PROCLAMATION_SYSTEM_NOTICE_KEY_STR为 redis 存放 ProclamationListNotice 的一个标志,确保不会和其他保存在reids的键产生重名冲突;
3)getProclamationListNoticeKey方法,获取redis数据的key,调用了框架底层代码,传入标志和其他信息,通过拼装字符串,返回一个key;最底层使用的是redis的相关jar包,RedisStorageKey类的 getByteArray方法:
3.1
public static byte[] getByteArray(String platFormId, StorageKey keyFlag){
return RedisStorageKey.getByteArray(platFormId, (short)keyFlag.code);
}
3.2
public static byte[] getByteArray(String platformId, short keyFlag) {
//***这里是一堆处理
return bytes;
}
4)saveProclamationInfoList方法,保存公告列表数据:
byte[] key = getProclamationListNoticeKey();
byte[] bytes = info.copyTo().toByteArray();
jRedis.set(key, bytes);
简单理解,获取key,将 ProclamationInfoList 序列化为二进制数组,作为value,最后set保存;
Q1:你会问:ProclamationInfoList 序列化为二进制数组这个过程如何实现呢?
A1:ok,前文《开发日常小结(2):如何使用Google提供的Protocol buffer协议实现序列化呢?--游戏公告序列化实例详解》可以解答:
@Override
public ProclamationInfoListProto copyTo() {
ProclamationInfoListProto.Builder builder = ProclamationInfoListProto.newBuilder();
for (ProclamationInfo info : proclamationInfoList) {
builder.addProclamationInfoList(info.copyTo());
}
return builder.build();
}
@Override
public byte[] toByteArray() {
return copyTo().toByteArray();
}
本质就是使用了protobuffer序列化结构(proto生成的java类),这个过程就是:;
5)getProclamationNotice方法,获取公告列表数据:
public ProclamationInfoList getProclamationNotice(){
byte[] key = getProclamationListNoticeKey();
byte[] bytes = jRedis.get(key);
ProclamationInfoList proclamationInfoList = new ProclamationInfoList();
if (bytes == null) {
saveProclamationInfoList(proclamationInfoList);
}else{
proclamationInfoList.parseFrom(bytes);
}
return proclamationInfoList;
}
@Override
public void parseFrom(byte[] bytes) {
try {
ProclamationInfoListProto proto = ProclamationInfoListProto.parseFrom(bytes);
copyFrom(proto);
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
}
再往下追,就是google的封装好的方法了,看不了了!!
public static com.xs.fun.base.proto.service.WorldChannelProtoBuffer.ProclamationInfoListProto parseFrom(byte[] data)
throws com.google.protobuf.InvalidProtocolBufferException {
return PARSER.parseFrom(data);
}
Service层:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.**.proclamation.ProclamationInfoList;
import com.**.redis.ProclamationRepo;
import com.**.service.BaseProclamationService;
@Service
public class ProclamationService {
@Autowired
private ProclamationRepo proclamationRepo;
//保存新公告
public void saveProclamationInfoList(ProclamationInfoList info){
proclamationRepo.saveProclamationInfoList(info);
}
//获取保存的新公告
public ProclamationInfoList getProclamationNotice(){
return proclamationRepo.getProclamationNotice();
}
}
总结:
1)综上所述,reids的操作,和mysql一样可以代码分层,划分为service,dao和action(JRedis)三层;
2)序列化与数据保存流程,可以参考下图: