net.sz.framework 框架 轻松搭建数据服务中心----读写分离数据一致性,滑动缓存
前言
前文讲述了net.sz.framework 框架的基础实现功能,本文主讲 net.sz.framework.db 和 net.sz.framework.szthread;
net.sz.framework.db 是 net.sz.framework 底层框架下的orm框架,仿照翻译了hibernate实现功能,虽然不足hibernate强大;但在于其功能实现单一高效和高可控性;
net.sz.framework.szthread 是 net.sz.framework 底层框架下的线程控制中心和线程池概念;
以上就不在赘述,前面的文章已经将结果了;
叙述
无论你是做何种软件开发,都离不开数据;
数据一般我们都会有两个问题一直在脑后徘徊,那就是读和写的问题;
一般正常情况下数据我们可能出现的存储源是数据库(mysql,sqlserver,sqlite,Nosql等)、文件数据库(excel,xml,cvs等)
无论是合作数据格式都只是在意数据的存储;保证数据不丢失等情况;
那么我们为了数据的读取和写入高效会想尽办法去处理数据,已达到我们需求范围类的数据最高效最稳当的方式;
今天我们准备的是 orm框架下面的 SqliteDaoImpl 对 sqlite数据源 进行测试和代码设计;换其他数据源也是大同小异;
准备工作
新建项目 maven java项目 net.sz.dbserver
我们在项目下面创建model、cache、db、main这几个包;

然后在 model 包 下面创建 ModelTest 类
package net.sz.dbserver.model;
import javax.persistence.Id;
import net.sz.framework.szlog.SzLogger;
/**
*
* <br>
* author 失足程序员<br>
* blog http://www.cnblogs.com/ty408/<br>
* mail 492794628@qq.com<br>
* phone 13882122019<br>
*/
public class ModelTest {
private static SzLogger log = SzLogger.getLogger();
/*主键ID*/
@Id
private long Id;
/*内容*/
private String name;
public ModelTest() {
}
public long getId() {
return Id;
}
public void setId(long Id) {
this.Id = Id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
然后在db包下面建立dbmanager类;
package net.sz.dbserver.db;
import java.sql.Connection;
import java.util.ArrayList;
import net.sz.dbserver.model.ModelTest;
import net.sz.framework.db.Dao;
import net.sz.framework.db.SqliteDaoImpl;
import net.sz.framework.szlog.SzLogger;
import net.sz.framework.utils.PackageUtil;
/**
*
* <br>
* author 失足程序员<br>
* blog http://www.cnblogs.com/ty408/<br>
* mail 492794628@qq.com<br>
* phone 13882122019<br>
*/
public class DBManager {
private static SzLogger log = SzLogger.getLogger();
private static final DBManager IN_ME = new DBManager();
public static DBManager getInstance() {
return IN_ME;
}
Dao dao = null;
public DBManager() {
try {
/*不使用连接池,显示执行sql语句的数据库操作*/
this.dao = new SqliteDaoImpl("/home/sqlitedata/testdb.dat", true);
} catch (Exception e) {
log.error("创建数据库连接", e);
}
}
/**
* 检查并创建数据表结构
*/
public void checkTables() {
/*创建连接,并自动释放*/
try (Connection con = this.dao.getConnection()) {
String packageName = "net.sz.dbserver.model";
/*获取包下面所有类*/
ArrayList<Class<?>> tables = PackageUtil.getClazzs(packageName);
if (tables != null) {
for (Class<?> table : tables) {
/*检查是否是需要创建的表*/
if (this.dao.checkClazz(table)) {
/*创建表结构*/
this.dao.createTable(con, table);
}
}
}
} catch (Exception e) {
log.error("创建表抛异常", e);
}
}
}
我们在dbmanager类里面通过SqliteDaoImpl 类创建了sqlite数据库支持的类似于hibernate的辅助;
在checktables下面会查找我们项目包下面所有类型,并且创建数据表;如果表存在就更新表结构(sqlite特性,不会更新表结构);
我们在checktables函数下面做到了对连接的复用情况;创建后并自动释放代码
接下来main包里面创建主函数启动类
package net.sz.dbserver.main;
import net.sz.dbserver.db.DBManager;
import net.sz.framework.szlog.SzLogger;
/**
*
* <br>
* author 失足程序员<br>
* blog http://www.cnblogs.com/ty408/<br>
* mail 492794628@qq.com<br>
* phone 13882122019<br>
*/
public class MainManager {
private static SzLogger log = SzLogger.getLogger();
public static void main(String[] args) {
log.error("创建数据库,创建数据表结构");
DBManager.getInstance().checkTables();
}
}
以上代码我们完成了数据库文件和数据表的创建
--- exec-maven-plugin:1.2.1:exec (default-cli) @ net.sz.dbserver ---
设置系统字符集sun.stdout.encoding:utf-8
设置系统字符集sun.stderr.encoding:utf-8
日志级别:DEBUG
输出文件日志目录:../log/sz.log
是否输出控制台日志:true
是否输出文件日志:true
是否使用双缓冲输出文件日志:true
[04-07 10:56:38:198:ERROR:MainManager.main():19] 创建数据库,创建数据表结构
[04-07 10:56:38:521:ERROR:Dao.getColumns():532] 类:net.sz.dbserver.model.ModelTest 字段:log is transient or static or final;
[04-07 10:56:38:538:ERROR:SqliteDaoImpl.existsTable():110] 表:ModelTest 检查结果: 无此表
[04-07 10:56:38:561:ERROR:SqliteDaoImpl.createTable():200]
表:
create table if not exists `ModelTest` (
`Id` bigint not null primary key,
`name` varchar(255) null
);
创建完成;
这里的步骤在之前文章《存在即合理,重复轮子orm java版本》里面有详细介绍,不过当前版本和当时文章版本又有更多优化和改进;
准备测试数据
/*创建支持id*/
GlobalUtil.setServerID(1);
for (int i = 0; i < 10; i++) {
ModelTest modelTest = new ModelTest();
/*获取全局唯一id*/
modelTest.setId(GlobalUtil.getId());
/*设置参数*/
modelTest.setName("123");
try {
DBManager.getInstance().getDao().insert(modelTest);
} catch (Exception e) {
log.error("写入数据失败", e);
}
}
输出
--- exec-maven-plugin:1.2.1:exec (default-cli) @ net.sz.dbserver --- 设置系统字符集sun.stdout.encoding:utf-8 设置系统字符集sun.stderr.encoding:utf-8 日志级别:DEBUG 输出文件日志目录:../log/sz.log 是否输出控制台日志:true 是否输出文件日志:true 是否使用双缓冲输出文件日志:true [04-07 11:13:17:904:ERROR:MainManager.main():21] 创建数据库,创建数据表结构 [04-07 11:13:18:203:ERROR:Dao.getColumns():532] 类:net.sz.dbserver.model.ModelTest 字段:log is transient or static or final; [04-07 11:13:18:215:ERROR:SqliteDaoImpl.existsTable():110] 表:ModelTest 检查结果: 已存在 [04-07 11:13:18:216:ERROR:SqliteDaoImpl.existsColumn():130] 数据库:null 表:ModelTest 映射数据库字段:ModelTest 检查结果:已存在,将不会修改 [04-07 11:13:18:216:ERROR:SqliteDaoImpl.createTable():168] 表:ModelTest 字段:Id 映射数据库字段:Id 存在,将不会修改, [04-07 11:13:18:216:ERROR:SqliteDaoImpl.existsColumn():130] 数据库:null 表:ModelTest 映射数据库字段:ModelTest 检查结果:已存在,将不会修改 [04-07 11:13:18:216:ERROR:SqliteDaoImpl.createTable():168] 表:ModelTest 字段:name 映射数据库字段:name 存在,将不会修改, [04-07 11:13:18:245:ERROR:SqliteDaoImpl.existsTable():110] 表:ModelTest 检查结果: 已存在 [04-07 11:13:18:245:ERROR:Dao.insert():1023] 执行 insert into `ModelTest` (`Id`, `name`) values (?, ? ) 添加数据 表:ModelTest [04-07 11:13:18:246:ERROR:Dao.insert():1067] 执行 org.sqlite.jdbc4.JDBC4PreparedStatement@4bf558aa 添加数据 表:ModelTest 结果 影响行数:1 [04-07 11:13:18:272:ERROR:SqliteDaoImpl.existsTable():110] 表:ModelTest 检查结果: 已存在 [04-07 11:13:18:272:ERROR:Dao.insert():1023] 执行 insert into `ModelTest` (`Id`, `name`) values (?, ? ) 添加数据 表:ModelTest [04-07 11:13:18:273:ERROR:Dao.insert():1067] 执行 org.sqlite.jdbc4.JDBC4PreparedStatement@2d38eb89 添加数据 表:ModelTest 结果 影响行数:1 [04-07 11:13:18:295:ERROR:SqliteDaoImpl.existsTable():110] 表:ModelTest 检查结果: 已存在 [04-07 11:13:18:296:ERROR:Dao.insert():1023] 执行 insert into `ModelTest` (`Id`, `name`) values (?, ? ) 添加数据 表:ModelTest [04-07 11:13:18:297:ERROR:Dao.insert():1067] 执行 org.sqlite.jdbc4.JDBC4PreparedStatement@5fa7e7ff 添加数据 表:ModelTest 结果 影响行数:1 [04-07 11:13:18:319:ERROR:SqliteDaoImpl.existsTable():110] 表:ModelTest 检查结果: 已存在 [04-07 11:13:18:319:ERROR:Dao.insert():1023] 执行 insert into `ModelTest` (`Id`, `name`) values (?, ? ) 添加数据 表:ModelTest [04-07 11:13:18:320:ERROR:Dao.insert():1067] 执行 org.sqlite.jdbc4.JDBC4PreparedStatement@4629104a 添加数据 表:ModelTest 结果 影响行数:1 [04-07 11:13:18:343:ERROR:SqliteDaoImpl.existsTable():110] 表:ModelTest 检查结果: 已存在 [04-07 11:13:18:343:ERROR:Dao.insert():1023] 执行 insert into `ModelTest` (`Id`, `name`) values (?, ? ) 添加数据 表:ModelTest [04-07 11:13:18:344:ERROR:Dao.insert():1067] 执行 org.sqlite.jdbc4.JDBC4PreparedStatement@27f8302d 添加数据 表:ModelTest 结果 影响行数:1 [04-07 11:13:18:368:ERROR:SqliteDaoImpl.existsTable():110] 表:ModelTest 检查结果: 已存在 [04-07 11:13:18:368:ERROR:Dao.insert():1023] 执行 insert into `ModelTest` (`Id`, `name`) values (?, ? ) 添加数据 表:ModelTest [04-07 11:13:18:369:ERROR:Dao.insert():1067] 执行 org.sqlite.jdbc4.JDBC4PreparedStatement@4d76f3f8 添加数据 表:ModelTest 结果 影响行数:1 [04-07 11:13:18:391:ERROR:SqliteDaoImpl.existsTable():110] 表:ModelTest 检查结果: 已存在 [04-07 11:13:18:391:ERROR:Dao.insert():1023] 执行 insert into `ModelTest` (`Id`, `name`) values (?, ? ) 添加数据 表:ModelTest [04-07 11:13:18:392:ERROR:Dao.insert():1067] 执行 org.sqlite.jdbc4.JDBC4PreparedStatement@2d8e6db6 添加数据 表:ModelTest 结果 影响行数:1 [04-07 11:13:18:415:ERROR:SqliteDaoImpl.existsTable():110] 表:ModelTest 检查结果: 已存在 [04-07 11:13:18:415:ERROR:Dao.insert():1023] 执行 insert into `ModelTest` (`Id`, `name`) values (?, ? ) 添加数据 表:ModelTest [04-07 11:13:18:416:ERROR:Dao.insert():1067] 执行 org.sqlite.jdbc4.JDBC4PreparedStatement@23ab930d 添加数据 表:ModelTest 结果 影响行数:1 [04-07 11:13:18:438:ERROR:SqliteDaoImpl.existsTable():110] 表:ModelTest 检查结果: 已存在 [04-07 11:13:18:439:ERROR:Dao.insert():1023] 执行 insert into `ModelTest` (`Id`, `name`) values (?, ? ) 添加数据 表:ModelTest [04-07 11:13:18:440:ERROR:Dao.insert():1067] 执行 org.sqlite.jdbc4.JDBC4PreparedStatement@4534b60d 添加数据 表:ModelTest 结果 影响行数:1 [04-07 11:13:18:461:ERROR:SqliteDaoImpl.existsTable():110] 表:ModelTest 检查结果: 已存在 [04-07 11:13:18:462:ERROR:Dao.insert():1023] 执行 insert into `ModelTest` (`Id`, `name`) values (?, ? ) 添加数据 表:ModelTest [04-07 11:13:18:463:ERROR:Dao.insert():1067] 执行 org.sqlite.jdbc4.JDBC4PreparedStatement@3fa77460 添加数据 表:ModelTest 结果 影响行数:1
重构modeltest类
首先在cache包下面创建CacheBase类实现缓存的基本参数
package net.sz.dbserver.cache;
import javax.persistence.Id;
import net.sz.framework.util.AtomInteger;
/**
*
* <br>
* author 失足程序员<br>
* blog http://www.cnblogs.com/ty408/<br>
* mail 492794628@qq.com<br>
* phone 13882122019<br>
*/
public class CacheBase {
/*主键ID*/
@Id
protected long Id;
/*编辑状态 是 transient 字段,不会更新到数据库的*/
private volatile transient boolean edit;
/*版本号 是 transient 字段,不会更新到数据库的*/
private volatile transient AtomInteger versionId;
/*创建时间*/
private volatile transient long createTime;
/*最后获取缓存时间*/
private volatile transient long lastGetCacheTime;
public CacheBase() {
}
/**
* 创建
*/
public void createCache() {
edit = false;
versionId = new AtomInteger(1);
createTime = System.currentTimeMillis();
lastGetCacheTime = System.currentTimeMillis();
}
public long getId() {
return Id;
}
public void setId(long Id) {
this.Id = Id;
}
public boolean isEdit() {
return edit;
}
public void setEdit(boolean edit) {
this.edit = edit;
}
public AtomInteger getVersionId() {
return versionId;
}
public void setVersionId(AtomInteger versionId) {
this.versionId = versionId;
}
public long getCreateTime() {
return createTime;
}
public void setCreateTime(long createTime) {
this.createTime = createTime;
}
public long getLastGetCacheTime() {
return lastGetCacheTime;
}
public void setLastGetCacheTime(long lastGetCacheTime) {
this.lastGetCacheTime = lastGetCacheTime;
}
/**
* 拷贝数据
*
* @param cacheBase
*/
public void copy(CacheBase cacheBase) {
this.Id = cacheBase.Id;
}
}
在cachebase类中,我创建了copy函数用来赋值新数据的;
通过这个类型,我们可以做到定时缓存,滑动缓存效果;
增加版号的作用在于,更新操作标识,是否是编辑状态也是用作更新标识;
于此同时我们把原 ModelTest 唯一键、主键 id 移动到了 cachebase 父类中
修改modeltest类继承cachebase;
public class ModelTest extends CacheBase
改造一下dbmanager
Dao readDao = null;
Dao writeDao = null;
public Dao getReadDao() {
return readDao;
}
public Dao getWriteDao() {
return writeDao;
}
public DBManager() {
try {
/*不使用连接池,显示执行sql语句的数据库操作*/
this.readDao = new SqliteDaoImpl("/home/sqlitedata/testdb.dat", true);
/*不使用连接池,显示执行sql语句的数据库操作*/
this.writeDao = new SqliteDaoImpl("/home/sqlitedata/testdb.dat", true);
} catch (Exception e) {
log.error("创建数据库连接", e);
}
}
加入读取数据库连接、写入数据库连接;
CacheManager
在cache包下面建立cachemanager类;
cachemanager 类型是我们具体和重点思路;
构建了读取,并加入缓存集合;
构建了更新并写入数据库;
同时读取和更新都保证线程安全性特点;
package net.sz.dbserver.cache;
import java.util.concurrent.ConcurrentHashMap;
import net.sz.dbserver.db.DBManager;
import net.sz.framework.szlog.SzLogger;
/**
*
* <br>
* author 失足程序员<br>
* blog http://www.cnblogs.com/ty408/<br>
* mail 492794628@qq.com<br>
* phone 13882122019<br>
*/
public class CacheManager {
private static SzLogger log = SzLogger.getLogger();
private static final CacheManager IN_ME = new CacheManager();
public static CacheManager getInstance() {
return IN_ME;
}
/*缓存集合*/
final ConcurrentHashMap<Long, CacheBase> cacheMap = new ConcurrentHashMap<>();
/**
* 获取一条数据,这里我只是测试,提供思路,
* <br>
* 所以不会去考虑list等情况;
* <br>
* 需要的话可以自行修改
*
* @param <T>
* @param clazz
* @param id
* @return
*/
public <T extends CacheBase> T getCacheBase(Class<T> clazz, long id) {
CacheBase cacheBase = null;
cacheBase = cacheMap.get(id);
if (cacheBase == null) {
try {
/*先读取数据库*/
cacheBase = DBManager.getInstance().getReadDao().getObjectByWhere(clazz, "where id=@id", id);
/*加入同步操作*/
synchronized (cacheMap) {
/*这个时候再次读取缓存,防止并发*/
CacheBase tmp = cacheMap.get(id);
/*双重判断*/
if (tmp == null) {
/*创建缓存标识*/
cacheBase.createCache();
/*加入缓存信息*/
cacheMap.put(id, cacheBase);
} else {
cacheBase = tmp;
}
}
} catch (Exception e) {
log.error("读取数据异常", e);
}
}
if (cacheBase != null) {
/*更新最后获取缓存的时间*/
cacheBase.setLastGetCacheTime(System.currentTimeMillis());
}
return (T) cacheBase;
}
/**
* 更新缓存数据同时更新数据库数据
*
* @param <T>
* @param t
* @return
*/
public <T extends CacheBase> boolean updateCacheBase(T t) {
if (t == null) {
throw new UnsupportedOperationException("参数 T 为 null");
}
try {
CacheBase cacheBase = null;
cacheBase = cacheMap.get(t.getId());
/*理论上,控制得当这里是不可能为空的*/
if (cacheBase != null) {
/*理论上是能绝对同步的,你也可以稍加修改*/
synchronized (cacheBase) {
/*验证编辑状态和版号,保证写入数据是绝对正确的*/
if (cacheBase.isEdit()
&& cacheBase.getVersionId() == t.getVersionId()) {
/*拷贝最新数据操作*/
cacheBase.copy(t);
/*写入数据库,用不写入还是同步写入,看自己需求而一定*/
DBManager.getInstance().getWriteDao().update(cacheBase);
/*保证写入数据库后进行修改 对版本号进行加一操作*/
cacheBase.getVersionId().changeZero(1);
/*设置最新的最后访问时间*/
cacheBase.setLastGetCacheTime(System.currentTimeMillis());
/*修改编辑状态*/
cacheBase.setEdit(false);
log.error("数据已修改,最新版号:" + cacheBase.getVersionId());
return true;
} else {
log.error("版本已经修改无法进行更新操作");
throw new UnsupportedOperationException("版本已经修改无法进行更新操作");
}
}
} else {
log.error("缓存不存在无法修改数据");
throw new UnsupportedOperationException("缓存不存在无法修改数据");
}
} catch (Exception e) {
throw new UnsupportedOperationException("更新数据异常", e);
}
}
/**
* 获取独占编辑状态
*
* @param id
* @return
*/
public boolean updateEdit(long id) {
CacheBase t = null;
t = cacheMap.get(id);
if (t == null) {
throw new UnsupportedOperationException("未找到数据源");
}
return updateEdit(t);
}
/**
* 获取独占编辑状态
*
* @param t
* @return
*/
public boolean updateEdit(CacheBase t) {
if (t == null) {
throw new UnsupportedOperationException("参数 T 为 null");
}
if (!t.isEdit()) {
synchronized (t) {
if (!t.isEdit()) {
/*同步后依然需要双重判定*/
t.setEdit(true);
return true;
}
}
}
return false;
}
}
可能有人要问, 为啥要加锁,加版号或者加编辑状态;
我们先看一张图片

当同一份数据,展示给客户端(web,多线程等)的时候,同时进行获取,进行编辑,我们不可能每次都需要去调用独占编辑;
那么问题来了我们就拿modeltest的name字段说明,当前等于123,当client1和client2都表示数据的名字错误了需要修改成789;
那么在写入数据库的时候总会有先后顺序,那么后面的很可能就覆盖了前面的修改,
我们假如client1先提交,把name字段改为456,这时候client2提交了,789就直接覆盖了456字段,
程序根本不知道字段的覆盖了,也不知道哪一个是正确的;
所以我加入了编辑状态和版号验证;当然你也可以根据你的需求来进行修改
package net.sz.dbserver.main;
import net.sz.dbserver.cache.CacheManager;
import net.sz.dbserver.db.DBManager;
import net.sz.dbserver.model.ModelTest;
import net.sz.framework.szlog.SzLogger;
import net.sz.framework.utils.GlobalUtil;
/**
*
* <br>
* author 失足程序员<br>
* blog http://www.cnblogs.com/ty408/<br>
* mail 492794628@qq.com<br>
* phone 13882122019<br>
*/
public class MainManager {
private static SzLogger log = SzLogger.getLogger();
public static void main(String[] args) {
log.error("创建数据库,创建数据表结构");
DBManager.getInstance().checkTables();
/*创建支持id*/
GlobalUtil.setServerID(1);
ModelTest modelTest = new ModelTest();
/*获取全局唯一id*/
modelTest.setId(GlobalUtil.getId());
/*设置参数*/
modelTest.setName("123");
/*创建测试数据先修改数据库*/
try {
DBManager.getInstance().getReadDao().insert(modelTest);
} catch (Exception e) {
log.error("写入数据失败", e);
}
/*打印一次id*/
log.error("modelTest.getId()=" + modelTest.getId());
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
/*上面的写入数据是为了获取这个id,保证测试代码编辑功能*/
ModelTest cacheBase = CacheManager.getInstance().getCacheBase(ModelTest.class, modelTest.getId());
if (cacheBase != null) {
log.error("成功获得数据");
/*独占编辑状态你可以不需要*/
if (CacheManager.getInstance().updateEdit(cacheBase)) {
log.error("成功获得编辑状态");
/*为了模拟并发,我们采用id,保证唯一的数据查看到底谁写入成功*/
cacheBase.setName(GlobalUtil.getId() + "");
CacheManager.getInstance().updateCacheBase(cacheBase);
log.error("modelTest.getName()=" + cacheBase.getName());
} else {
log.error("获取编辑状态失败");
}
}
} catch (Exception e) {
log.error("更新数据异常", e);
}
}).start();
}
}
}
在mainmanager类main函数测试里面加入3个线程模拟并发状态

正常添加的测试数据
[04-07 13:50:50:514:ERROR:MainManager.main():23] 创建数据库,创建数据表结构 [04-07 13:50:50:937:ERROR:Dao.getColumns():532] 类:net.sz.dbserver.model.ModelTest 字段:log is transient or static or final; [04-07 13:50:50:952:ERROR:Dao.getColumns():532] 类:net.sz.dbserver.cache.CacheBase 字段:edit is transient or static or final; [04-07 13:50:50:952:ERROR:Dao.getColumns():532] 类:net.sz.dbserver.cache.CacheBase 字段:versionId is transient or static or final; [04-07 13:50:50:952:ERROR:Dao.getColumns():532] 类:net.sz.dbserver.cache.CacheBase 字段:createTime is transient or static or final; [04-07 13:50:50:952:ERROR:Dao.getColumns():532] 类:net.sz.dbserver.cache.CacheBase 字段:lastGetCacheTime is transient or static or final; [04-07 13:51:37:591:ERROR:MainManager.main():41] modelTest.getId()=7040713505000100000 [04-07 13:51:45:392:ERROR:MainManager.lambda$main$0():49] 成功获得数据 [04-07 13:51:45:392:ERROR:MainManager.lambda$main$0():49] 成功获得数据 [04-07 13:51:45:392:ERROR:MainManager.lambda$main$0():49] 成功获得数据 [04-07 13:51:45:392:ERROR:MainManager.lambda$main$0():52] 成功获得编辑状态 [04-07 13:51:45:392:ERROR:MainManager.lambda$main$0():58] 获取编辑状态失败 [04-07 13:51:45:392:ERROR:MainManager.lambda$main$0():58] 获取编辑状态失败 [04-07 13:51:45:428:ERROR:CacheManager.updateCacheBase():101] 数据已修改,最新版号:2 [04-07 13:51:45:428:ERROR:MainManager.lambda$main$0():56] modelTest.getName()=7040713514500100000

修改后的数据;
保证了并发写入、修改的问题,保证了数据的一致性;
实现滑动缓存
在cache包下面建立里CheckCacheTimer定时器类
package net.sz.dbserver.cache;
import java.util.HashMap;
import java.util.Map;
import net.sz.framework.szlog.SzLogger;
import net.sz.framework.szthread.TimerTaskModel;
/**
*
* <br>
* author 失足程序员<br>
* blog http://www.cnblogs.com/ty408/<br>
* mail 492794628@qq.com<br>
* phone 13882122019<br>
*/
public class CheckCacheTimer extends TimerTaskModel {
private static SzLogger log = SzLogger.getLogger();
public CheckCacheTimer(int intervalTime) {
super(intervalTime);
}
@Override
public void run() {
/*考虑缓存的清理的都放在这里、当然有很多值的注意细节有待细化*/
HashMap<Long, CacheBase> tmp = new HashMap(CacheManager.getInstance().cacheMap);
for (Map.Entry<Long, CacheBase> entry : tmp.entrySet()) {
Long key = entry.getKey();
CacheBase value = entry.getValue();
if (!value.isEdit()) {
/*如果数据不在编辑状态、且30分钟无访问清理*/
if (System.currentTimeMillis() - value.getLastGetCacheTime() > 30 * 60 * 1000) {
synchronized (CacheManager.getInstance().cacheMap) {
if (!value.isEdit()) {
/*如果数据不在编辑状态、且30分钟无访问清理*/
if (System.currentTimeMillis() - value.getLastGetCacheTime() > 30 * 60 * 1000) {
CacheManager.getInstance().cacheMap.remove(value.getId());
}
}
}
}
}
}
}
}
在cachemanager类构造函数加入
public CacheManager() {
/*创建一秒钟检查的定时器*/
ThreadPool.addTimerTask(ThreadPool.GlobalThread, new CheckCacheTimer(1000));
}
滑动缓存就构建完成了,
这里就不在测试了,理论就是这么个理论;思路就是这么个思路;
脱离数据源的单纯缓存器
改造CacheBase类
package net.sz.net.sz.framework.caches;
import net.sz.framework.util.AtomInteger;
/**
*
* <br>
* author 失足程序员<br>
* blog http://www.cnblogs.com/ty408/<br>
* mail 492794628@qq.com<br>
* phone 13882122019<br>
*/
public abstract class CacheBase {
/*编辑状态 */
private volatile transient boolean edit;
/*版本号 */
private volatile transient AtomInteger versionId;
/*创建时间*/
private volatile transient long createTime;
/*最后获取缓存时间*/
private volatile transient long lastGetCacheTime;
/*true 表示是滑动缓存*/
private volatile transient boolean slide;
/*清理时间*/
private volatile transient long clearTime;
public CacheBase() {
}
/**
* 创建
* @param slide
* @param clearTime
*/
protected CacheBase(boolean slide, long clearTime) {
this.slide = slide;
this.clearTime = clearTime;
}
/**
*
*/
protected void createCache(){
this.edit = false;
this.versionId = new AtomInteger(1);
this.createTime = System.currentTimeMillis();
this.lastGetCacheTime = System.currentTimeMillis();
}
/**
*
*/
protected void copyCache(CacheBase tmp){
this.edit = tmp.edit;
this.versionId = tmp.getVersionId();
this.createTime = tmp.getClearTime();
this.lastGetCacheTime = System.currentTimeMillis();
}
public boolean isEdit() {
return edit;
}
public void setEdit(boolean edit) {
this.edit = edit;
}
public AtomInteger getVersionId() {
return versionId;
}
public void setVersionId(AtomInteger versionId) {
this.versionId = versionId;
}
public long getCreateTime() {
return createTime;
}
public void setCreateTime(long createTime) {
this.createTime = createTime;
}
public long getLastGetCacheTime() {
return lastGetCacheTime;
}
public void setLastGetCacheTime(long lastGetCacheTime) {
this.lastGetCacheTime = lastGetCacheTime;
}
public boolean isSlide() {
return slide;
}
public void setSlide(boolean slide) {
this.slide = slide;
}
public long getClearTime() {
return clearTime;
}
public void setClearTime(long clearTime) {
this.clearTime = clearTime;
}
}
改造CacheManager类
package net.sz.net.sz.framework.caches;
import java.util.concurrent.ConcurrentHashMap;
import net.sz.framework.szlog.SzLogger;
import net.sz.framework.szthread.ThreadPool;
/**
*
* <br>
* author 失足程序员<br>
* blog http://www.cnblogs.com/ty408/<br>
* mail 492794628@qq.com<br>
* phone 13882122019<br>
*/
public class CacheManager {
private static SzLogger log = SzLogger.getLogger();
/*缓存集合*/
final ConcurrentHashMap<String, CacheBase> cacheMap = new ConcurrentHashMap<>();
public CacheManager() {
/*创建一秒钟检查的定时器*/
ThreadPool.addTimerTask(ThreadPool.GlobalThread, new CheckCacheTimer(1000, this));
}
/**
* 默认30分钟清理的滑动缓存、如果存在缓存键、将不再添加
*
* @param key
* @param object
* @return
*/
public boolean addCache(String key, CacheBase object) {
return addCache(key, object, 30 * 60 * 1000);
}
/**
* 默认滑动缓存、如果存在缓存键、将不再添加
*
* @param key
* @param object
* @param clearTime 滑动缓存的清理时间
* @return
*/
public boolean addCache(String key, CacheBase object, long clearTime) {
return addCache(key, object, clearTime, true);
}
/**
* 默认滑动缓存、如果存在缓存键、将不再添加
*
* @param key
* @param object
* @param clearTime 清理缓存的间隔时间
* @param isSlide true表示滑动缓存,
* @return
*/
public boolean addCache(String key, CacheBase object, long clearTime, boolean isSlide) {
return addCache(key, object, clearTime, isSlide, false);
}
/**
*
* @param key
* @param object
* @param clearTime 清理缓存的间隔时间
* @param isSlide true表示滑动缓存,
* @param put true 表示强制添加数据集合,及已经存在键数据
* @return
*/
public boolean addCache(String key, CacheBase object, long clearTime, boolean isSlide, boolean put) {
if (put) {
object.createCache();
cacheMap.put(key, object);
if (log.isDebugEnabled()) {
log.debug("强制添加缓存键=" + key);
}
return true;
} else if (!cacheMap.containsKey(key)) {
/*加入同步操作*/
synchronized (key) {
if (!cacheMap.containsKey(key)) {
object.createCache();
cacheMap.put(key, object);
return true;
} else {
if (log.isDebugEnabled()) {
log.debug("数据已经添加,不能再次添加");
}
}
}
} else {
if (log.isDebugEnabled()) {
log.debug("数据已经添加,不能再次添加");
}
}
return false;
}
public boolean removeCache(String key) {
cacheMap.remove(key);
return true;
}
public CacheBase getCache(String key) {
return getCache(key, CacheBase.class);
}
/**
* 获取一条数据,这里我只是测试,提供思路,
* <br>
* 所以不会去考虑list等情况;
* <br>
* 需要的话可以自行修改
*
* @param <T>
* @param clazz
* @param key
* @return
*/
public <T extends CacheBase> T getCache(String key, Class<T> clazz) {
CacheBase cacheBase = null;
cacheBase = cacheMap.get(key);
if (cacheBase != null) {
/*更新最后获取缓存的时间*/
cacheBase.setLastGetCacheTime(System.currentTimeMillis());
}
return (T) cacheBase;
}
/**
* 更新缓存数据同时更新数据库数据
*
* @param key
* @param object
* @return
*/
public boolean updateCacheBase(String key, CacheBase object) {
if (object == null) {
throw new UnsupportedOperationException("参数 object 为 null");
}
CacheBase cacheBase = null;
cacheBase = cacheMap.get(key);
/*理论上,控制得当这里是不可能为空的*/
if (cacheBase != null) {
/*理论上是能绝对同步的,你也可以稍加修改*/
synchronized (key) {
/*验证编辑状态和版号,保证写入数据是绝对正确的*/
if (cacheBase.getVersionId() == object.getVersionId()) {
/*拷贝最新数据操作*/
cacheMap.put(key, object);
/*保证写入数据库后进行修改 对版本号进行加一操作*/
object.getVersionId().changeZero(1);
/*设置最新的最后访问时间*/
object.setLastGetCacheTime(System.currentTimeMillis());
/*修改编辑状态*/
object.setEdit(false);
if (log.isDebugEnabled()) {
log.debug("数据已修改,最新版号:" + object.getVersionId());
}
return true;
} else {
if (log.isDebugEnabled()) {
log.debug("版本已经修改无法进行更新操作");
}
throw new UnsupportedOperationException("版本已经修改无法进行更新操作");
}
}
} else {
if (log.isDebugEnabled()) {
log.debug("缓存不存在无法修改数据");
}
throw new UnsupportedOperationException("缓存不存在无法修改数据");
}
}
/**
* 获取独占编辑状态
*
* @param key
* @return
*/
public boolean updateEdit(String key) {
CacheBase cacheBase = null;
cacheBase = cacheMap.get(key);
if (cacheBase == null) {
throw new UnsupportedOperationException("未找到数据源");
}
return updateEdit(key, cacheBase);
}
/**
* 获取独占编辑状态
*
* @param key
* @param cacheBase
* @return
*/
public boolean updateEdit(String key, CacheBase cacheBase) {
if (cacheBase == null) {
throw new UnsupportedOperationException("参数 cacheBase 为 null");
}
if (!cacheBase.isEdit()) {
synchronized (key) {
if (!cacheBase.isEdit()) {
/*同步后依然需要双重判定*/
cacheBase.setEdit(true);
/*设置最新的最后访问时间*/
cacheBase.setLastGetCacheTime(System.currentTimeMillis());
return true;
}
}
}
return false;
}
}
改造CheckCacheTimer类
package net.sz.net.sz.framework.caches;
import java.util.HashMap;
import java.util.Map;
import net.sz.framework.szlog.SzLogger;
import net.sz.framework.szthread.TimerTaskModel;
/**
*
* <br>
* author 失足程序员<br>
* blog http://www.cnblogs.com/ty408/<br>
* mail 492794628@qq.com<br>
* phone 13882122019<br>
*/
public class CheckCacheTimer extends TimerTaskModel {
private static SzLogger log = SzLogger.getLogger();
private final CacheManager cacheManager;
public CheckCacheTimer(int intervalTime, CacheManager cacheManager) {
super(intervalTime);
this.cacheManager = cacheManager;
}
@Override
public void run() {
/*考虑缓存的清理的都放在这里、当然有很多值的注意细节有待细化*/
HashMap<String, CacheBase> tmp = new HashMap(this.cacheManager.cacheMap);
for (Map.Entry<String, CacheBase> entry : tmp.entrySet()) {
String key = entry.getKey();
CacheBase value = entry.getValue();
/*理论上,这里是能够保证绝对缓存,同步*/
if (!value.isEdit()) {
/*滑动缓存清理*/
if (value.isSlide() && System.currentTimeMillis() - value.getLastGetCacheTime() < value.getClearTime()) {
continue;
}
/*固定缓存清理*/
if (!value.isSlide() && System.currentTimeMillis() - value.getCreateTime() < value.getClearTime()) {
continue;
}
this.cacheManager.removeCache(key);
}
}
}
}
脱离了数据源的缓存器;
求大神指教了;如果觉得可以点个推荐;
觉得不好请手下留情不要点击反对哦,
net.sz.framework 框架 轻松搭建数据服务中心----读写分离数据一致性,滑动缓存的更多相关文章
- net.sz.framework 框架 轻松搭建服务---让你更专注逻辑功能---初探
前言 在之前的文章中,讲解过 threadmodel,socket tcp ,socket http,log,astart ,scripts: 都是分片讲解,从今天开始,将带大家,一窥 net.sz. ...
- net.sz.framework 框架 ORM 消消乐超过亿条数据排行榜分析 天王盖地虎
序言 天王盖地虎, 老婆马上生孩子了,在家待产,老婆喜欢玩消消乐类似的休闲游戏,闲置状态,无聊的分析一下消消乐游戏的一些技术问题: 由于我主要是服务器研发,客户端属于半吊子,所以就分析一下消消乐排行榜 ...
- net.sz.framework 框架 登录服务器架构 单服2 万 TPS(QPS)
前言 无论我们做什么系统,95%的系统都离不开注册,登录: 而游戏更加关键,频繁登录,并发登录,导量登录:如果登录承载不起来,那么游戏做的再好,都是徒然,进不去啊: 序言 登录所需要的承载,包含程序和 ...
- 在现有的mysql主从基础上,搭建mycat实现数据的读写分离
环境准备:MySQL服务器做好主从复制:centos6的系统 主:192.168.164.131 从:192.168.164.144 mycat服务器:192.168.164.141 a.将MySQL ...
- elasticsearch 冷热数据的读写分离
步骤 一.冷热分离集群配置 比如三个机器共六个node的es集群. 每个机器上各挂载一个ssd 和 一个sata.每个机器需要启动两个es进程.每个进程对应不同类型的磁盘. 关键配置: node.ma ...
- 深入了解Entity Framework框架及访问数据的几种方式
一.前言 1.Entity Framework概要 Entity Framework是微软以ADO.NET为基础所发展出来的对象关系映射(O/R Mapping)解决方案.该框架曾经为.NET Fra ...
- 重要参考文档---MySQL 8.0.29 使用yum方式安装,开启navicat远程连接,搭建主从,读写分离(需要使用到ProxySQL,此文不讲述这个)
yum方式安装 echo "删除系统默认或之前可能安装的其他版本的 mysql" for i in $(rpm -qa|grep mysql);do rpm -e $i --nod ...
- redis搭建主从复用-读写分离
1:安装redis5.0.3 2:解压到/usr/local/redis 3:在/opt/redis/下创建三个文件夹 data,存放数据的目录 log,存放日志的目录 conf,存放配置的目录 co ...
- Spring和MyBatis实现数据的读写分离
移步: http://blog.csdn.net/he90227/article/details/51248200
随机推荐
- 新手学js的效果图1---( 淘宝等商城货物查看特效)
本人结合之前所学一起写了,多个特效,只是新手自己瞎鼓捣的,思路清晰,具体实现的货物放大镜等,替换当中的img地址就可以查看特效 <!DOCTYPE html> <html lang= ...
- 2017-2-23 C#基础 中间变量
用中间变量做这个题 1."请输入年份:"(1-9999) "请输入月份:"(1-12) "请输入日期:"(要判断大小月,判断闰年) 判断输入 ...
- 每天一个linux命令(50)--date命令
在Linux环境中,不管是编程还是其他维护,时间是必不可少的,也经常会用到时间的运算,熟练运用date 命令来表示自己想要表示的时间,肯定可以给自己的工作带来诸多方便. 1.命令格式: date [参 ...
- 浅析=======Struts2之==========valueStack
今天刚学习了struts2的valueStack,在这里把自己学到的做个分享,说道valueStack就不得不提OGNL表达式=== struts2工作流程 1.OGNL(Object Graph N ...
- 深入源码剖析String,StringBuilder,StringBuffer
[String,StringBuffer,StringBulider] 深入源码剖析String,StringBuilder,StringBuffer [作者:高瑞林] [博客地址]http://ww ...
- 瞎谈CNN:通过优化求解输入图像
本文同步自我的知乎专栏: From Beijing with Love 机器学习和优化问题 很多机器学习方法可以归结为优化问题,对于一个参数模型,比如神经网络,用来表示的话,训练模型其实就是下面的参数 ...
- ACM 整数划分(四)
整数划分(四) 时间限制:1000 ms | 内存限制:65535 KB 难度:3 描述 暑假来了,hrdv 又要留学校在参加ACM集训了,集训的生活非常Happy(ps:你懂得),可是他最近 ...
- 在.NET项目中使用PostSharp,使用CacheManager实现多种缓存框架的处理
在前面几篇随笔中,介绍了PostSharp的使用,以及整合MemoryCache,<在.NET项目中使用PostSharp,实现AOP面向切面编程处理>.<在.NET项目中使用Pos ...
- WF学习思维导图
原文 来自我的有道笔记-老文重发系列 如果配置加载核心服务,那么需要将持久化服务和跟踪服务放在一个数据库中! 1.用工作流的优点 a.提供将复杂任务分解的途径,通过将每个操作分解到活动中更便于业务 ...
- 【转】Django HTTP请求的处理流程
Django 和其他 Web 框架的 HTTP 处理的流程大致相同,Django 处理一个 Request 的过程是首先通过中间件,然后再通过默认的 URL 方式进行的.我们可以在 Middlewar ...