Redis主从实现读写分离
前言 大家在工作中可能会遇到这样的需求,即Redis读写分离,目的是为了压力分散化。下面我将为大家介绍借助AWS的ELB实现读写分离,以写主读从为例。 实现 引用库文件 <!--redis客户端--> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.6.2</version> </dependency> 方式一,借助切面 JedisPoolSelector 此类的目的是为读和写分别配置不同的注解,用来区分是主还是从。 packagecom.silence.spring.redis.readwriteseparation;importjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;/** *Createdbykeysilenceon16/10/26. */@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public@interfaceJedisPoolSelector{ Stringvalue(); } JedisPoolAspect 此类的目的是针对主和从的注解,进行动态链接池调配,即主的使用主链接池,从的使用从连接池。 packagecom.silence.spring.redis.readwriteseparation;importorg.aspectj.lang.JoinPoint;importorg.aspectj.lang.annotation.Aspect;importorg.aspectj.lang.annotation.Before;importorg.aspectj.lang.annotation.Pointcut;importorg.aspectj.lang.reflect.MethodSignature;importorg.springframework.beans.BeansException;importorg.springframework.context.ApplicationContext;importorg.springframework.context.ApplicationContextAware;importredis.clients.jedis.JedisPool;importjavax.annotation.PostConstruct;importjava.lang.reflect.Method;importjava.util.Date;/** *Createdbykeysilenceon16/10/26. */@AspectpublicclassJedisPoolAspectimplementsApplicationContextAware{ privateApplicationContextctx;@PostConstruct publicvoidinit(){ System.out.println("jedispoolaspectjstarted@"+newDate()); }@Pointcut("execution(*com.silence.spring.redis.readwriteseparation.util.*.*(..))")privatevoidallMethod(){ }@Before("allMethod()")publicvoidbefore(JoinPointpoint) { Objecttarget=point.getTarget(); Stringmethod=point.getSignature().getName(); Classclassz=target.getClass(); Class<?>[]parameterTypes=((MethodSignature)point.getSignature()) .getMethod().getParameterTypes();try{ Methodm=classz.getMethod(method,parameterTypes);if(m!=null&&m.isAnnotationPresent(JedisPoolSelector.class)){ JedisPoolSelectordata=m .getAnnotation(JedisPoolSelector.class); JedisPooljedisPool=(JedisPool)ctx.getBean(data.value()); DynamicJedisPoolHolder.putJedisPool(jedisPool); } }catch(Exceptione){ e.printStackTrace(); } }publicvoidsetApplicationContext(ApplicationContextapplicationContext)throwsBeansException{this.ctx=applicationContext; } } DynamicJedisPoolHolder 此类目的是存储当前使用的JedisPool,即上面类赋值后的结果保存。 packagecom.silence.spring.redis.readwriteseparation;importredis.clients.jedis.JedisPool;/** *Createdbykeysilenceon16/10/26. */publicclassDynamicJedisPoolHolder{ publicstaticfinalThreadLocal<JedisPool>holder=newThreadLocal<JedisPool>();publicstaticvoidputJedisPool(JedisPooljedisPool){ holder.set(jedisPool); }publicstaticJedisPoolgetJedisPool(){returnholder.get(); } } RedisUtils 此类目的是对Redis具体的调用,里面包含使用主还是从的方式调用。 packagecom.silence.spring.redis.readwriteseparation.util;importcom.silence.spring.redis.readwriteseparation.DynamicJedisPoolHolder;importcom.silence.spring.redis.readwriteseparation.JedisPoolSelector;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;/** *Createdbykeysilenceon16/10/26. */publicclassRedisUtils{ privatestaticLoggerlogger=LoggerFactory.getLogger(RedisUtils.class); @JedisPoolSelector("master") publicStringsetString(finalStringkey,finalStringvalue){ Stringret=DynamicJedisPoolHolder.getJedisPool().getResource().set(key,value); System.out.println("key:"+key+",value:"+value+",ret:"+ret); returnret; } @JedisPoolSelector("slave") publicStringget(finalStringkey){ Stringret=DynamicJedisPoolHolder.getJedisPool().getResource().get(key); System.out.println("key:"+key+",ret:"+ret); returnret; } } spring-datasource.xml <?xmlversion="1.0"encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"> <beanid="poolConfig"class="redis.clients.jedis.JedisPoolConfig"> <!--池中最大链接数--> <propertyname="maxTotal"value="100"/> <!--池中最大空闲链接数--> <propertyname="maxIdle"value="50"/> <!--池中最小空闲链接数--> <propertyname="minIdle"value="20"/> <!--当池中链接耗尽,调用者最大阻塞时间,超出此时间将跑出异常。(单位:毫秒;默认为-1,表示永不超时)--> <propertyname="maxWaitMillis"value="1000"/> <!--参考:http://biasedbit.com/redis-jedispool-configuration/--> <!--调用者获取链接时,是否检测当前链接有效性。无效则从链接池中移除,并尝试继续获取。(默认为false)--> <propertyname="testOnBorrow"value="true"/> <!--向链接池中归还链接时,是否检测链接有效性。(默认为false)--> <propertyname="testOnReturn"value="true"/> <!--调用者获取链接时,是否检测空闲超时。如果超时,则会被移除(默认为false)--> <propertyname="testWhileIdle"value="true"/> <!--空闲链接检测线程一次运行检测多少条链接--> <propertyname="numTestsPerEvictionRun"value="10"/> <!--空闲链接检测线程检测周期。如果为负值,表示不运行检测线程。(单位:毫秒,默认为-1)--> <propertyname="timeBetweenEvictionRunsMillis"value="60000"/> <!--链接获取方式。队列:false;栈:true--> <!--<propertyname="lifo"value="false"/>--> </bean> <beanid="master"class="redis.clients.jedis.JedisPool"> <constructor-argindex="0"ref="poolConfig"/> <constructor-argindex="1"value="192.168.100.110"type="java.lang.String"/> <constructor-argindex="2"value="6379"type="int"/> </bean> <beanid="slave"class="redis.clients.jedis.JedisPool"> <constructor-argindex="0"ref="poolConfig"/> <!--此处Host配置成ELB地址--> <constructor-argindex="1"value="192.168.100.110"type="java.lang.String"/> <constructor-argindex="2"value="6380"type="int"/> </bean> <beanid="redisUtils"class="com.silence.spring.redis.readwriteseparation.util.RedisUtils"> </bean> <beanid="jedisPoolAspect"class="com.silence.spring.redis.readwriteseparation.JedisPoolAspect"/> <aop:aspectj-autoproxyproxy-target-class="true"/></beans> Test packagecom.silence.spring.redis.readwriteseparation;importcom.silence.spring.redis.readwriteseparation.util.RedisUtils;importorg.springframework.context.ApplicationContext;importorg.springframework.context.support.ClassPathXmlApplicationContext;/** *Createdbykeysilenceon16/10/26. */publicclassTest{ publicstaticvoidmain(String[]args){ ApplicationContextctx=newClassPathXmlApplicationContext("spring-datasource.xml"); System.out.println(ctx); RedisUtilsredisUtils=(RedisUtils)ctx.getBean("redisUtils"); redisUtils.setString("aaa","111"); System.out.println(redisUtils.get("aaa")); } } 方式二,依赖注入 与方式一类似,但是需要写死具体使用主的池还是从的池,思路如下:放弃注解的方式,直接将主和从的两个链接池注入到具体实现类中。 RedisUtils packagecom.silence.spring.redis.readwriteseparation.util;importcom.silence.spring.redis.readwriteseparation.DynamicJedisPoolHolder;importcom.silence.spring.redis.readwriteseparation.JedisPoolSelector;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importredis.clients.jedis.JedisPool;/** *Createdbykeysilenceon16/10/26. */publicclassRedisUtils{ privatestaticLoggerlogger=LoggerFactory.getLogger(RedisUtils.class);privateJedisPoolmasterJedisPool;privateJedisPoolslaveJedisPool;publicvoidsetMasterJedisPool(JedisPoolmasterJedisPool){this.masterJedisPool=masterJedisPool; }publicvoidsetSlaveJedisPool(JedisPoolslaveJedisPool){this.slaveJedisPool=slaveJedisPool; }publicStringsetString(finalStringkey,finalStringvalue){ Stringret=masterJedisPool.getResource().set(key,value); System.out.println("key:"+key+",value:"+value+",ret:"+ret);returnret; }publicStringget(finalStringkey){ Stringret=slaveJedisPool.getResource().get(key); System.out.println("key:"+key+",ret:"+ret);returnret; } } spring-datasource.xml <?xmlversion="1.0"encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"> <beanid="poolConfig"class="redis.clients.jedis.JedisPoolConfig"> <!--池中最大链接数--> <propertyname="maxTotal"value="100"/> <!--池中最大空闲链接数--> <propertyname="maxIdle"value="50"/> <!--池中最小空闲链接数--> <propertyname="minIdle"value="20"/> <!--当池中链接耗尽,调用者最大阻塞时间,超出此时间将跑出异常。(单位:毫秒;默认为-1,表示永不超时)--> <propertyname="maxWaitMillis"value="1000"/> <!--参考:http://biasedbit.com/redis-jedispool-configuration/--> <!--调用者获取链接时,是否检测当前链接有效性。无效则从链接池中移除,并尝试继续获取。(默认为false)--> <propertyname="testOnBorrow"value="true"/> <!--向链接池中归还链接时,是否检测链接有效性。(默认为false)--> <propertyname="testOnReturn"value="true"/> <!--调用者获取链接时,是否检测空闲超时。如果超时,则会被移除(默认为false)--> <propertyname="testWhileIdle"value="true"/> <!--空闲链接检测线程一次运行检测多少条链接--> <propertyname="numTestsPerEvictionRun"value="10"/> <!--空闲链接检测线程检测周期。如果为负值,表示不运行检测线程。(单位:毫秒,默认为-1)--> <propertyname="timeBetweenEvictionRunsMillis"value="60000"/> <!--链接获取方式。队列:false;栈:true--> <!--<propertyname="lifo"value="false"/>--> </bean> <beanid="masterJedisPool"class="redis.clients.jedis.JedisPool"> <constructor-argindex="0"ref="poolConfig"/> <constructor-argindex="1"value="192.168.100.110"type="java.lang.String"/> <constructor-argindex="2"value="6379"type="int"/> </bean> <beanid="slaveJedisPool"class="redis.clients.jedis.JedisPool"> <constructor-argindex="0"ref="poolConfig"/> <constructor-argindex="1"value="192.168.100.110"type="java.lang.String"/> <constructor-argindex="2"value="6380"type="int"/> </bean> <beanid="redisUtils"class="com.silence.spring.redis.readwriteseparation.util.RedisUtils"> <propertyname="masterJedisPool"ref="masterJedisPool"/> <propertyname="slaveJedisPool"ref="slaveJedisPool"/> </bean></beans>