com.akala.dbcache.core
类 BaseManager

java.lang.Object
  继承者 com.akala.dbcache.core.BaseManager

public class BaseManager
extends java.lang.Object

免责声明:本代码所有权归作者所有,在保持源代码不被破坏以及所有人署名的基础上,任何组织或个人可自由无限使用,使用者需自行承担所有风险。

实例网站


舍得网高效数据库缓存源代码及文档下载


警告:只有从上面这个地址下载才能保证代码是最新的。

该Class是数据库缓存/分布式经典解决方案,可以承受超大规模的应用,该方案简单明了,效率高,配置起来非常简单,可以直接用。^_^
相对于代码行数,该Class可以说是全亚洲最强大的数据库操作工具,本来想说全世界最强,想想还是谦虚点好,开个玩笑:)
缓存思路:
对于单个对象的缓存,用HashMap就可以了,稍微复杂一点用LRU算法包装一个HashMap,再复杂一点的分布式用memcached即可,没什么太难的。
但是对于列表缓存,似乎没有太好的通用办法,一般的思路是只取列表的id,然后根据id去对象缓存中查找,下面分析本人如何改进列表缓存

一:mysql和hibernate的底层在做通用的列表缓存时都是根据查询条件把列表结果缓存起来,但是只要该表的记录
有任何变化(增加/删除/修改),列表缓存要全部清除,这样只要一个表的记录经常变化(通常情况都会这样),列表缓存几乎失效,命中率太低了。

二:本人想了一个办法改善了一下列表缓存,在表的记录有改变时,遍历所有列表缓存,只有那些被影响到的列表缓存才会被删除,而不是直接清除所有
列表缓存。这样处理有个好处,可以缓存各种查询条件(如等于、大于、不等于、小于)的列表缓存,但也有个潜在的性能问题,由于需要遍历,系统开销
比较大,如果列表缓存最大长度设置成10000,两个4核的CPU每秒也只能遍历完300多次,这样如果每秒有超过300个insert/update/delete,系统就吃不消了。

三:在前面两种解决办法都不完美的情况下,本人和同事经过几个星期的思索,总算得出了根据表的某几个字段做散列的缓存办法,这种办法无需大规模遍历,
所以系统开销很小,由于这种列表缓存按照字段做了散列,所以命中率极高。思路如下:

每个表有3个缓存Map(key=value键值对),第一个Map是对象缓存A,在A中,key是数据库的id,Value是数据库对象(也就是一行数据);第二个Map是
通用列表缓存B,B的最大长度一般1000左右,在B中,key是查询条件拼出来的String(如start=0,length=15#active=0#state=0#category=109),Value是该条件查询下
的所有id组成的List;第三个Map是散列缓存C,在C中,key是散列的字段(如根据userId散列的话,其中某个key就是userId=109这样的String)组成的
String,value是一个和B类似的HashMap。其中只有B这个Map是需要遍历的,不知道说明白了没有,看完小面这个例子应该就明白了,就用论坛的回复表作说明:
假设回复表T中假设有字段id,topicId,postUserId等字段(topicId就是帖子的id,postUserId是发布者id)。

1:第一种情况,也是最常用的情况,就是获取一个帖子对应的回复,sql语句应该是象
select id from T where topicId=2008 order by createTime desc limit 0,5
select id from T where topicId=2008 order by createTime desc limit 5,5
select id from T where topicId=2008 order by createTime desc limit 10,5
的样子,那么这种列表很显然用topicId做散列是最好的,把上面三个列表缓存(可以是N个)都散列到key是topicId=2008这一个Map中,当id是2008的帖子有新的
回复时,系统自动把key是topicId=2008的散列Map清除即可。由于这种散列不需要遍历,因此可以设置成很大,例如100000,这样10万个帖子对应的所有回复列表都可以缓存起来,
当有一个帖子有新的回复时,其余99999个帖子对应的回复列表都不会动,缓存的命中率极高。

2:第二种情况,就是后台需要显示最新的回复,sql语句应该是象
select id from T order by createTime desc limit 0,50
的样子,这种情况不需要散列,因为后台不可能有太多人访问,常用列表也不会太多,所以直接放到通用列表缓存B中即可。

3:第三种情况,获取一个用户的回复,sql语句象
select id from T where userId=2046 order by createTime desc limit 0,15
select id from T where userId=2046 order by createTime desc limit 15,15
的样子,那么这种列表和第一种情况类似,用userId做散列即可。

4:第四种情况,获取一个用户对某个帖子的回复,sql语句象
select id from T where topicId=2008 and userId=2046 order by createTime desc limit 0,15
的样子,这种情况比较少见,一般以topicId=2008为准,也放到key是topicId=2008这个散列Map里即可。

缓存情况如下图:

对象缓存A是:(A的最大长度可以设置大点,就看一段时间内常用的记录数)

Key键(long型)

Value值(类型T

11

Id=11T对象

22

Id=22T对象

133

Id=133T对象

……

列表缓存B是:(B是一个不带查询条件的列表缓存,通常列表不会太多,最大长度设置1000就可以了,不能设置太大,因为需要遍历)

Key键(String型)

Value值(ArrayList型)

from T order by createTime desc limit 0,50

ArrayList,对应取出来的所有id

from T order by createTime desc limit 50,50

ArrayList,对应取出来的所有id

from T order by createTime desc limit 100,50

ArrayList,对应取出来的所有id

……

 

散列缓存C是:(C是散列缓存,无须遍历,可以设置成大一点,如一个网站同时在线人数是5000,那么设置成5000010000都是没有问题的)

Key键(String型)

Value值(HashMap

userId=2046

Key键(String型)

Value值(ArrayList

userId=2046#0,5

id组成的List

userId=2046#5,5

id组成的List

userId=2046#15,5

id组成的List

……

userId=2047

Key键(String型)

Value值(ArrayList

userId=2047#0,5

id组成的List

userId=2047#5,5

id组成的List

userId=2047#15,5

id组成的List

……

userId=2048

Key键(String型)

Value值(ArrayList

userId=2048#topicId=2008#0,5

id组成的List

userId=2048#5,5

id组成的List

userId=2048#15,5

id组成的List

……

 

总结:这种缓存思路可以存储大规模的列表,缓存命中率极高,因此可以承受超大规模的应用,但是需要技术人员根据自身业务逻辑来
配置需要做散列的字段,一般用一个表的索引键做散列(注意顺序,最散的字段放前面),假设以userId为例,可以存储N个用户的M种列表,如果某个
用户的相关数据发生变化,其余N-1个用户的列表缓存纹丝不动。以上说明的都是如何缓存列表,缓存长度和缓存列表思路完全一样,如缓存象
select count(*) from T where topicId=2008
这样的长度,也是放到topicId=2008这个散列Map中。如果再配合好使用mysql的内存表和memcached,加上F5设备做分布式负载均衡,该系统 对付像1000万IP/天这种规模级的应用都足够了,除搜索引擎外一般的应用网站到不了这种规模。
任何问题请发送mail到liuaike@shedewang.com

另请参见:
舍得网

构造方法摘要
BaseManager()
           
 
方法摘要
 void clearAllCache()
          删除所有缓存!!!!!!!!!!!
 BaseRecord create(BaseRecord record)
          创建一个数据库记录,并把对象放入本机缓存和memcached缓存。
 boolean delete(BaseRecord br)
          从数据库中删除数据,如果有必要,可以重写该方法删除缓存中的纪录和列表中的list缓存!
 boolean deleteById(long id)
          根据ID从数据库中删除数据,如果有必要,可以重写该方法删除缓存中的纪录和列表中的list缓存!
 boolean deleteById(java.lang.String id)
          根据ID从数据库中删除数据,如果有必要,可以重写该方法删除缓存中的纪录和列表中的list缓存!
 BaseRecord findById(long id)
          根据id获取记录。
 BaseRecord findById(java.lang.String id)
          根据id获取记录。
 BaseRecord findByProperty(java.lang.String fieldName, java.lang.Object value)
          根据某一个字段的值来获取对象
 BaseRecord getFromMemCachedServer(java.lang.String key)
          从memcached server获取对象,对象必须实现java.io.Serializable。
 java.lang.String getHashFieldsList()
           
 java.lang.String getHibernateConfigFile()
           
 int getLength(java.util.List<org.hibernate.criterion.SimpleExpression> expList)
          根据条件得到数据库记录的长度,Integer对象可以直接存在memcached缓存中,所以没有必要序列化!
 java.util.List<BaseRecord> getList(java.util.List<org.hibernate.criterion.SimpleExpression> expList, java.util.List<org.hibernate.criterion.Order> orders, int start, int length)
          每个表必须有id这个字段,每个类必须有id这个field。
 int getProjectionLength(java.util.List<org.hibernate.criterion.SimpleExpression> expList, org.hibernate.criterion.Projection project)
          如果要用distinct,project必须是Projections.countDistinct("bbsThemeId");的形式
 java.util.List getProjectionList(java.util.List<org.hibernate.criterion.SimpleExpression> expList, java.util.List<org.hibernate.criterion.Order> orders, org.hibernate.criterion.Projection project, int start, int length)
          此处返回的List是Long对象或者Iterator对象,而不是BaseRecord!!!!!!!!!!
 java.lang.Class getRecordClass()
           
 boolean putToUpdateMap(BaseRecord record)
          非即时更新数据库对象。
 void removeFromCache(long id, boolean realRemove, boolean isLocal)
          分布式从缓存中去掉对象,下次读取就会从memcached读取或者从数据库读取。
 void removeListCache(BaseRecord bt)
          清除缓存,供本机调用。
 void removeListCache(BaseRecord bt, boolean isLocal)
          本地(localhost)调用,在jsp中调用isLocal一律用true。
 void set2MemCachedServer(BaseRecord br)
          把om对象放入memcached server。
 void setHashFieldsList(java.lang.String hashFieldsList)
           
 void setHibernateConfigFile(java.lang.String hibernateConfigFile)
           
 void setRecordClass(java.lang.Class recordClass)
           
 boolean update(BaseRecord record, boolean clearListCache)
          更新一个数据库对象。
 
从类 java.lang.Object 继承的方法
equals, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
 

构造方法详细信息

BaseManager

public BaseManager()
方法详细信息

clearAllCache

public void clearAllCache()
删除所有缓存!!!!!!!!!!!
删除所有缓存,尽供后台调用,前台页面别瞎用!


findById

public BaseRecord findById(long id)
根据id获取记录。第一步从本机内存中取,如果没有则转向memcached server获取,
如果memcached server也没有才从数据库中获取,这样可以大大减轻数据库服务器的压力。

参数:
id - 记录的id
返回:
BaseRecord对象

findById

public BaseRecord findById(java.lang.String id)
根据id获取记录。第一步从本机内存中取,如果没有则转向memcached server获取,
如果memcached server也没有才从数据库中获取,这样可以大大减轻数据库服务器的压力。

参数:
id - 记录的id
返回:
BaseRecord 对象

findByProperty

public BaseRecord findByProperty(java.lang.String fieldName,
                                 java.lang.Object value)
根据某一个字段的值来获取对象

参数:
fieldName - 字段名
value - 字段值
返回:
BaseRecord 对象

deleteById

public boolean deleteById(long id)
根据ID从数据库中删除数据,如果有必要,可以重写该方法删除缓存中的纪录和列表中的list缓存!

参数:
id - long
返回:
boolean

deleteById

public boolean deleteById(java.lang.String id)
根据ID从数据库中删除数据,如果有必要,可以重写该方法删除缓存中的纪录和列表中的list缓存!

参数:
id - String
返回:
boolean

delete

public boolean delete(BaseRecord br)
从数据库中删除数据,如果有必要,可以重写该方法删除缓存中的纪录和列表中的list缓存!

参数:
br - BaseRecord
返回:
boolean

update

public boolean update(BaseRecord record,
                      boolean clearListCache)
更新一个数据库对象。如修改一个用户昵称时,不会影响任何排序,那么就不需要清除列表缓存。

参数:
record - 要更新的对象
clearListCache - true表示需要清除列表缓存 false表示不需要
返回:
boolean

create

public BaseRecord create(BaseRecord record)
创建一个数据库记录,并把对象放入本机缓存和memcached缓存。

参数:
record - BaseRecord
返回:
BaseRecord 返回数据库中的对象,

getList

public java.util.List<BaseRecord> getList(java.util.List<org.hibernate.criterion.SimpleExpression> expList,
                                          java.util.List<org.hibernate.criterion.Order> orders,
                                          int start,
                                          int length)
每个表必须有id这个字段,每个类必须有id这个field。自定义条件查询列表,理论上这个方法
可以满足所有需求,特别注意缓存key的拼法!!!!!
在memcached缓存上存的则不是List而是由#分开的id列表,如:13#14#25#256#887#987
key是象s10l20,createTime desc$age<90#aget>80#pid=12343#这样的字符串

参数:
expList - 查询条件
orders - 排序
start - 开始位置
length - 获取长度
返回:
List 数据库记录

getLength

public int getLength(java.util.List<org.hibernate.criterion.SimpleExpression> expList)
根据条件得到数据库记录的长度,Integer对象可以直接存在memcached缓存中,所以没有必要序列化!

参数:
cs - 查询条件
返回:
长度

getProjectionList

public java.util.List getProjectionList(java.util.List<org.hibernate.criterion.SimpleExpression> expList,
                                        java.util.List<org.hibernate.criterion.Order> orders,
                                        org.hibernate.criterion.Projection project,
                                        int start,
                                        int length)
此处返回的List是Long对象或者Iterator对象,而不是BaseRecord!!!!!!!!!!
遍历该List方法
Iterator iterator = list.iterator();
while(iterator.hasNext()) {
Object[] o = (Object[]) iterator.next();
//...
}

参数:
expList -
orders -
project - 包含sum count group等复杂组合查询条件的Projection(s)
start -
length -
返回:
List

getProjectionLength

public int getProjectionLength(java.util.List<org.hibernate.criterion.SimpleExpression> expList,
                               org.hibernate.criterion.Projection project)
如果要用distinct,project必须是Projections.countDistinct("bbsThemeId");的形式

参数:
expList -
project - 包含sum count group等复杂组合查询条件的Projection(s)
返回:
int

putToUpdateMap

public boolean putToUpdateMap(BaseRecord record)
非即时更新数据库对象。
利用缓存更新数据库,在压力特别大的时候用,比如在更新帖子的点击次数,这种情况没必要立即更新而且更新频繁所以采用缓存
这种情况一般不更新缓存,因为一般这种点击次数的修改不会影响排列次序,如果做影响排列顺序的修改(如优先级)则
必须用update()方法!

参数:
record - 需要update的对象
返回:
boolean

removeFromCache

public void removeFromCache(long id,
                            boolean realRemove,
                            boolean isLocal)
分布式从缓存中去掉对象,下次读取就会从memcached读取或者从数据库读取。在jsp或其他地方调用isLocal一律用true。

参数:
id - the id
realRemove - 是否真的删除,当删除数据库时调用
isLocal - 是否是本地调用

removeListCache

public void removeListCache(BaseRecord bt,
                            boolean isLocal)
本地(localhost)调用,在jsp中调用isLocal一律用true。
自动删除列表缓存,列表缓存的key必须是由字段名称=字段值组成,如#boardId=1#threadId=3#state=1#
所以删除时只要利用要删除的对象的字段值组成一个条件字符串,再看key中的条件是否满足这些条件就可以
决定是否要删除这些缓存List
这是一个比较好的自动删除缓存的办法

参数:
bt - BaseRecord对象,如User
isLocal - 是否本地调用.

removeListCache

public void removeListCache(BaseRecord bt)
清除缓存,供本机调用。注意,如果修改的字段影响了两种排序,典型的像state由0改为1,那么修改之前也要调用一下该方法。
如果修改了字段只影响一个排序,如修改了帖子的更新时间,那么修改之前不要调用该方法,直接用update(BaseRecord,true)即可。
如果修改的字段不影响排序,典型的像修改了用户的昵称,那么用update(BaseRecord,false)即可,即不需要清除列表缓存。

参数:
bt -

getFromMemCachedServer

public BaseRecord getFromMemCachedServer(java.lang.String key)
从memcached server获取对象,对象必须实现java.io.Serializable。

参数:
key - 远程的key。
返回:
BaseRecord对象

set2MemCachedServer

public void set2MemCachedServer(BaseRecord br)
把om对象放入memcached server。key是像com.chongai.om.Node#13434这样的字符串,后面的数字是对应的id

参数:
om - BaseRecord对象,可以是任何继承BaseRecord的对象

getHibernateConfigFile

public java.lang.String getHibernateConfigFile()

setHibernateConfigFile

public void setHibernateConfigFile(java.lang.String hibernateConfigFile)

getRecordClass

public java.lang.Class getRecordClass()

setRecordClass

public void setRecordClass(java.lang.Class recordClass)

getHashFieldsList

public java.lang.String getHashFieldsList()

setHashFieldsList

public void setHashFieldsList(java.lang.String hashFieldsList)


Copyright © 2005-2008 shedewang.com Corp. All Rights Reserved.