处理并发问题的重点不在于你的设计是怎样的,而在于你要评估你的并发,并在并发范围内处理。
你预估你的并发是多少,然后测试r+m是否支持。缓存的目的是为了应对普通对象数据库的读写限制,依托与nosql的优势进行高速读写。
redis本身也有并发瓶颈。所以你要把读写和并发区分开来处理。只读业务是不是可以用mysql分布做只读库和只读表,进行读写分离+库分布,
拆库拆表不能搞定再考虑上多级缓存
任何设计,你外面套一层,就多一倍的维护成本,缓存不是万金油。
这里多级缓存主要指的是二级缓存技术,也就是依托nosql的高速读取优势。
1.第一次执行select完毕会将查到的数据写入sqlsession内的hashmap中缓存起来
2.第二次执行select会从缓存中查数据,如果select相同切传参数一样,那么就能从缓存中返回数据,不用去数据库了,从而提高了效率
注意事项:
1.如果sqlsession执行了dml操作(insert、update、delete),并commit了,那么mybatis就会清空当前sqlsession缓存中的所有缓存数据,
这样可以保证缓存中的存的数据永远和数据库中一致,避免出现脏读
2.当一个sqlsession结束后那么他里面的一级缓存也就不存在了,mybatis默认是开启一级缓存,不需要配置
3.mybatis的缓存是基于[namespace:sql语句:参数]来进行缓存的,意思就是,sqlsession的hashmap存储缓存数据时,
是使用[namespace:sql:参数]作为key,查询返回的语句作为value保存的。
例如:-1242243203:1146242777:winclpt.bean.usermapper.getuser:0:2147483647:select * from user where id=?:19
二级缓存默认是没有开启的,需要在xml配置setting全局参数中配置开启二级缓存
<settings> <setting name="cacheenabled" value="true"/><!--开启mybatis缓存 默认是false:关闭二级缓存--><settings>
然后再具体的xml里配置
<cache eviction="lru" flushinterval="60000" size="512" readonly="true"/>当前mapper下所有语句开启二级缓存
这里配置了一个lru缓存,并每隔60秒刷新,最大存储512个对象,而却返回的对象是只读的
若想禁用当前select语句的二级缓存,添加usecache="false"修改如下:
<select id="getcountbyname" parametertype="java.util.map" resulttype="integer" statementtype="callable" usecache="false">
具体流程:
1.当一个sqlseesion执行了一次select后,在关闭此session的时候,会将查询结果缓存到二级缓存
2.当另一个sqlsession执行select时,首先会在他自己的一级缓存中找,如果没找到,就回去二级缓存中找,
找到了就返回,就不用去数据库了,从而减少了数据库压力提高了性能
注意事项:
1.如果sqlsession执行了dml操作(insert、update、delete),并commit了,那么mybatis就会清空当前mapper缓存中的所有缓存数据,这样可以保证缓存中的存的数据永远和数据库中一致,避免出现脏读
2.mybatis的缓存是基于[namespace:sql语句:参数]来进行缓存的,意思就是,sqlsession的hashmap存储缓存数据时,是使用[namespace:sql:参数]作为key,查询返回的语句作为value保存的。例如:-1242243203:1146242777:winclpt.bean.usermapper.getuser:0:2147483647:select * from user where id=?:19
<!--redis start--> <dependency> <groupid>org.springframework.data</groupid> <artifactid>spring-data-redis</artifactid> <version>1.6.0.release</version> </dependency> <dependency> <groupid>redis.clients</groupid> <artifactid>jedis</artifactid> <version>2.7.3</version> </dependency> <!--redis end-->
在xml配置文件里配置各种bean
<!--jedispool线程池--> <!--redis是单线程的,为了满足java多线程需求,jedis引入线程池的概念--> <bean id="jedispool" class="redis.clients.jedis.jedispoolconfig"> <property name="maxidle" value="100"/> <property name="maxtotal" value="700"/> <property name="maxwaitmillis" value="1000"/> <property name="testonborrow" value="false"/> </bean> <!--创建redis连接工厂--> <bean id="jedisconnectionfactory" class="org.springframework.data.redis.connection.jedis.jedisconnectionfactory"> <property name="hostname" value="${redis.hostname}"/> <property name="port" value="${redis.port}"/> <property name="password" value="${redis.password}"/> <property name="database" value="${redis.database}"/> <property name="poolconfig" ref="jedispool"/> </bean> <!--配置redistemplate --> <bean id="redistemplate" class="org.springframework.data.redis.core.redistemplate"> <property name="connectionfactory" ref="jedisconnectionfactory"/> <!--<property name="defaultserializer">--> <!--<bean class="org.springframework.data.redis.serializer.stringredisserializer"/>--> <!--</property>--> </bean> <!--配置缓存配置信息--> <!--这是用的spring的cachemanager,不支持缓存有效期动态配置--> <!--<bean id="rediscachemanager" class="org.springframework.data.redis.cache.rediscachemanager">--> <!--<!–配置redis模板–>--> <!--<constructor-arg name="redisoperations" ref="redistemplate"/>--> <!--<!–默认缓存失效时间–>--> <!--<property name="defaultexpiration" value="120"/>--> <!--<!–是否使用前缀–>--> <!--<property name="useprefix" value="false"/>--> <!--</bean>--> <!--手动实现的rediscachemanager,支持缓存有效期动态配置,可在@cacheable中的value属性添加有效时间--> <bean id="myrediscachemanager" class="com.djkj.demo.common.myrediscachemanager"> <!--配置redis模板--> <constructor-arg name="redisoperations" ref="redistemplate"/> <!--默认缓存失效时间--> <property name="defaultexpiration" value="120"/> <!--是否使用前缀--> <property name="useprefix" value="false"/> <!--缓存名字和有效期的分隔符--> <property name="separator" value="#"/> <!-- 多个缓存有效期,一般的单个工程可以省略此项 --> <!--<property name="expires">--> <!--<map>--> <!--<entry key="caiya_a" value="1800"/>--> <!--</map>--> <!--</property>--> </bean> <!--配置rediscacheconfig,redis缓存启动类--> <bean id="rediscacheconfig" class="com.djkj.demo.common.rediscacheconfig"> <constructor-arg ref="jedisconnectionfactory"/> <constructor-arg ref="redistemplate"/> <constructor-arg ref="myrediscachemanager"/> </bean>
这里jedispool的数据源地址根据实际情况设置,reis默认port为6379,如果你的redis设置了校验密码则这里需要否则不用,database设置的0,redis默认生成15个数据库,0表示采用的是第一个。
最主要的是rediscacheconfig缓存启动类和myrediscachemanager缓存管理类,启动类里设置了缓存的key的生成策略,管理类主要实现了自定义有效期package com.djkj.demo.common; import org.springframework.cache.annotation.cachingconfigurersupport; import org.springframework.cache.annotation.enablecaching; import org.springframework.cache.interceptor.keygenerator; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration; import org.springframework.data.redis.cache.rediscachemanager; import org.springframework.data.redis.connection.jedis.jedisconnectionfactory; import org.springframework.data.redis.core.redistemplate; import java.lang.reflect.method; @configuration @enablecaching public class rediscacheconfig extends cachingconfigurersupport { private volatile jedisconnectionfactory jedisconnectionfactory; private volatile redistemplate<string,string> redistemplate; private volatile rediscachemanager rediscachemanager; public rediscacheconfig(){ super(); } public rediscacheconfig(jedisconnectionfactory jedisconnectionfactory ,redistemplate<string,string> redistemplate, rediscachemanager rediscachemanager){ this.jedisconnectionfactory = jedisconnectionfactory; this.redistemplate = redistemplate; this.rediscachemanager = rediscachemanager; } public jedisconnectionfactory getjedisconnectionfactory() { return jedisconnectionfactory; } public redistemplate<string, string> getredistemplate() { return redistemplate; } public rediscachemanager getrediscachemanager() { return rediscachemanager; }
//主键生成策略 @bean public keygenerator customkeygenerator(){ return new keygenerator() { @override public object generate(object o, method method, object... objects) { stringbuilder stringbuilder=new stringbuilder(); stringbuilder.append(o.getclass().getname()); stringbuilder.append(method.getname()); for (object obj : objects) { stringbuilder.append(obj.tostring().hashcode()); } system.out.println(stringbuilder.tostring()); return stringbuilder.tostring(); } }; } }
myrediscachemanager
package com.djkj.demo.common; import org.apache.commons.lang3.stringutils; import org.apache.log4j.logger; import org.springframework.cache.cache; import org.springframework.data.redis.cache.rediscache; import org.springframework.data.redis.cache.rediscachemanager; import org.springframework.data.redis.cache.rediscacheprefix; import org.springframework.data.redis.core.redisoperations; import javax.script.scriptengine; import javax.script.scriptenginemanager; import javax.script.scriptexception; import java.util.collection; import java.util.collections; import java.util.regex.pattern; public class myrediscachemanager extends rediscachemanager { private static final logger logger = logger.getlogger(myrediscachemanager.class); private static final scriptengine scriptengine = new scriptenginemanager().getenginebyname("javascript"); private static final pattern pattern = pattern.compile("[+\\-*/%]"); private string defaultcachename; private string separator = "#"; public myrediscachemanager(redisoperations redisoperations) { this(redisoperations, collections.<string>emptylist()); } public myrediscachemanager(redisoperations redisoperations, collection<string> cachenames) { super(redisoperations, cachenames); } @override public cache getcache(string name) { string cachename=""; string expirationstr=""; long expiration=0l; string[] params = name.split(getseparator()); if(params.length>=1){ cachename = params[0]; } if(params.length>=2){ expirationstr = params[1]; } if(stringutils.isblank(cachename)){ cachename = defaultcachename; } cache cache = (rediscache) super.getcache(cachename); if (cache == null) { return null; } if(stringutils.isnotempty(expirationstr)){ try { expiration = double.valueof(expirationstr).longvalue(); }catch (exception e){ logger.error("expiration exchange failed!"); } } if (expiration==null || expiration == 0l) { logger.warn("default expiration time will be used for cache '{}' because cannot parse '{}', cachename : " + cachename + ", name : " + name); return cache; } return new rediscache(cachename, (isuseprefix() ? getcacheprefix().prefix(cachename) : null), getredisoperations(), expiration); } public string getseparator() { return separator; } public void setseparator(string separator) { this.separator = separator; } private long getexpiration(final string name, final int separatorindex) { long expiration = null; string expirationasstring = name.substring(separatorindex + 1); try { // calculate expiration, support arithmetic expressions. if(pattern.matcher(expirationasstring).find()){ expiration = (long) double.parsedouble(scriptengine.eval(expirationasstring).tostring()); }else{ expiration = long.parselong(expirationasstring); } } catch (numberformatexception ex) { logger.error(string.format("cannnot separate expiration time from cache: '%s'", name), ex); } catch (scriptexception e) { logger.error(string.format("cannnot separate expiration time from cache: '%s'", name), e); } return expiration; } @override public void setuseprefix(boolean useprefix) { super.setuseprefix(useprefix); } @override public void setcacheprefix(rediscacheprefix cacheprefix) { super.setcacheprefix(cacheprefix); } public void setdefaultcachename(string defaultcachename) { this.defaultcachename = defaultcachename; } }
根据符号 # 将缓存名切割,前面的作为缓存名,后面的作为有效期
//设置缓存有效时间和刷新时间
@cacheable(value="testpojocache" ,keygenerator = "customkeygenerator")
@override
public list<testpojo> query(testpojo bean) {
list<testpojo> testlist = testpojomapper.query(bean);
if(testlist.size()>0){
for(testpojo pojo:testlist){
pojo.settime(sdf.format(new date()));
pojo.setattr1(bean.getattr1());
}
}
return testlist;
}
testpojocache表示缓存name,30表示有效期,keygenerator表示key的生成策略,用的是在配置类里通过@bean配置的bean。
如对本文有疑问, 点击进行留言回复!!
网友评论