Redis master slave implements read and write separation

  • 2020-05-17 06:53:18
  • OfStack

preface

You may encounter this requirement at work, that is, Redis read and write separation, for the purpose of stress dispersion. Next, I will introduce the separation of reading and writing with ELB of AWS, taking the master of writing and the slave of reading and writing as an example.

implementation

Reference library file


  <!-- redis The client  -->
  <dependency>
   <groupId>redis.clients</groupId>
   <artifactId>jedis</artifactId>
   <version>2.6.2</version>
  </dependency>

Approach 1, with the help of the cut surface

JedisPoolSelector

The purpose of this class is to configure separate annotations for reading and writing to distinguish between master and slave.


package com.silence.spring.redis.readwriteseparation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created by keysilence on 16/10/26.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface JedisPoolSelector {

  String value();

}

JedisPoolAspect

The purpose of this class is to dynamically link pool deployment for master and slave annotations, that is, master using master link pool and slave using slave connection pool.


package com.silence.spring.redis.readwriteseparation;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import redis.clients.jedis.JedisPool;

import javax.annotation.PostConstruct;
import java.lang.reflect.Method;
import java.util.Date;

/**
 * Created by keysilence on 16/10/26.
 */
@Aspect
public class JedisPoolAspect implements ApplicationContextAware {

  private ApplicationContext ctx;

  @PostConstruct
  public void init() {
    System.out.println("jedis pool aspectj started @" + new Date());
  }

  @Pointcut("execution(* com.silence.spring.redis.readwriteseparation.util.*.*(..))")
  private void allMethod() {

  }

  @Before("allMethod()")
  public void before(JoinPoint point)
  {
    Object target = point.getTarget();
    String method = point.getSignature().getName();

    Class classz = target.getClass();

    Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())
        .getMethod().getParameterTypes();
    try {
      Method m = classz.getMethod(method, parameterTypes);
      if (m != null && m.isAnnotationPresent(JedisPoolSelector.class)) {
        JedisPoolSelector data = m
            .getAnnotation(JedisPoolSelector.class);
        JedisPool jedisPool = (JedisPool) ctx.getBean(data.value());
        DynamicJedisPoolHolder.putJedisPool(jedisPool);
      }

    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.ctx = applicationContext;
  }

}

DynamicJedisPoolHolder

The purpose of this class is to store the JedisPool currently in use, which is the result of the assignment of the above class.


package com.silence.spring.redis.readwriteseparation;

import redis.clients.jedis.JedisPool;

/**
 * Created by keysilence on 16/10/26.
 */
public class DynamicJedisPoolHolder {

  public static final ThreadLocal<JedisPool> holder = new ThreadLocal<JedisPool>();

  public static void putJedisPool(JedisPool jedisPool) {
    holder.set(jedisPool);
  }

  public static JedisPool getJedisPool() {
    return holder.get();
  }

}

RedisUtils

The purpose of this class is to make a specific call to Redis, including a master or slave call.


package com.silence.spring.redis.readwriteseparation.util;

import com.silence.spring.redis.readwriteseparation.DynamicJedisPoolHolder;
import com.silence.spring.redis.readwriteseparation.JedisPoolSelector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Created by keysilence on 16/10/26.
 */
public class RedisUtils {
  private static Logger logger = LoggerFactory.getLogger(RedisUtils.class);

  @JedisPoolSelector("master")
  public String setString(final String key, final String value) {

    String ret = DynamicJedisPoolHolder.getJedisPool().getResource().set(key, value);
    System.out.println("key:" + key + ",value:" + value + ",ret:" + ret);

    return ret;
  }

  @JedisPoolSelector("slave")
  public String get(final String key) {

    String ret = DynamicJedisPoolHolder.getJedisPool().getResource().get(key);
    System.out.println("key:" + key + ",ret:" + ret);

    return ret;
  }

}

spring-datasource.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="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/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

  <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
    <!--  The maximum number of links in the pool  -->
    <property name="maxTotal" value="100"/>
    <!--  The maximum number of free links in the pool  -->
    <property name="maxIdle" value="50"/>
    <!--  Minimum number of free links in the pool  -->
    <property name="minIdle" value="20"/>
    <!--  When the link in the pool is exhausted, the caller has a maximum blocking time, beyond which an exception is run. (in milliseconds; The default is -1 , never over time)  -->
    <property name="maxWaitMillis" value="1000"/>
    <!--  Reference: http://biasedbit.com/redis-jedispool-configuration/ -->
    <!--  Whether the caller detects the validity of the current link when it gets the link. Invalid is removed from the link pool and an attempt is made to continue the fetch. (the default is false )  -->
    <property name="testOnBorrow" value="true" />
    <!--  Whether to check the link validity when returning the link to the link pool. (the default is false )  -->
    <property name="testOnReturn" value="true" />
    <!--  Whether the idle timeout is detected when the caller gets the link. If timeout occurs, it is removed (default is false )  -->
    <property name="testWhileIdle" value="true" />
    <!--  Idle link detects threads 1 The number of links detected in a single run  -->
    <property name="numTestsPerEvictionRun" value="10" />
    <!--  Idle link detection thread detection cycle. Negative values indicate that the detection thread is not running. (in milliseconds, by default -1 )  -->
    <property name="timeBetweenEvictionRunsMillis" value="60000" />
    <!--  Link retrieval method. Queue: false ; The stack: true -->
    <!--<property name="lifo" value="false" />-->
  </bean>

  <bean id="master" class="redis.clients.jedis.JedisPool">
    <constructor-arg index="0" ref="poolConfig"/>
    <constructor-arg index="1" value="192.168.100.110" type="java.lang.String"/>
    <constructor-arg index="2" value="6379" type="int"/>
  </bean>

  <bean id="slave" class="redis.clients.jedis.JedisPool">
    <constructor-arg index="0" ref="poolConfig"/>
    <!--  here Host Configured to ELB address  -->
    <constructor-arg index="1" value="192.168.100.110" type="java.lang.String"/>
    <constructor-arg index="2" value="6380" type="int"/>
  </bean>

  <bean id="redisUtils" class="com.silence.spring.redis.readwriteseparation.util.RedisUtils">
  </bean>

  <bean id="jedisPoolAspect" class="com.silence.spring.redis.readwriteseparation.JedisPoolAspect" />

  <aop:aspectj-autoproxy proxy-target-class="true"/>

</beans>

Test


package com.silence.spring.redis.readwriteseparation;

import com.silence.spring.redis.readwriteseparation.util.RedisUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Created by keysilence on 16/10/26.
 */
public class Test {

  public static void main(String[] args) {

    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-datasource.xml");

    System.out.println(ctx);

    RedisUtils redisUtils = (RedisUtils) ctx.getBean("redisUtils");
    redisUtils.setString("aaa", "111");

    System.out.println(redisUtils.get("aaa"));
  }

}

Approach 2, dependency injection

Similar to method 1, but need to specify whether to use the master pool or the slave pool, the idea is as follows:
Instead of annotations, the master and slave link pools are injected directly into the concrete implementation class.

RedisUtils


package com.silence.spring.redis.readwriteseparation.util;

import com.silence.spring.redis.readwriteseparation.DynamicJedisPoolHolder;
import com.silence.spring.redis.readwriteseparation.JedisPoolSelector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.JedisPool;

/**
 * Created by keysilence on 16/10/26.
 */
public class RedisUtils {
  private static Logger logger = LoggerFactory.getLogger(RedisUtils.class);

  private JedisPool masterJedisPool;

  private JedisPool slaveJedisPool;

  public void setMasterJedisPool(JedisPool masterJedisPool) {
    this.masterJedisPool = masterJedisPool;
  }

  public void setSlaveJedisPool(JedisPool slaveJedisPool) {
    this.slaveJedisPool = slaveJedisPool;
  }

  public String setString(final String key, final String value) {

    String ret = masterJedisPool.getResource().set(key, value);
    System.out.println("key:" + key + ",value:" + value + ",ret:" + ret);

    return ret;
  }

  public String get(final String key) {

    String ret = slaveJedisPool.getResource().get(key);
    System.out.println("key:" + key + ",ret:" + ret);

    return ret;
  }

}

spring-datasource.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="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/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

  <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
    <!--  The maximum number of links in the pool  -->
    <property name="maxTotal" value="100"/>
    <!--  The maximum number of free links in the pool  -->
    <property name="maxIdle" value="50"/>
    <!--  Minimum number of free links in the pool  -->
    <property name="minIdle" value="20"/>
    <!--  When the link in the pool is exhausted, the caller has a maximum blocking time, beyond which an exception is run. (in milliseconds; The default is -1 , never over time)  -->
    <property name="maxWaitMillis" value="1000"/>
    <!--  Reference: http://biasedbit.com/redis-jedispool-configuration/ -->
    <!--  Whether the caller detects the validity of the current link when it gets the link. Invalid is removed from the link pool and an attempt is made to continue the fetch. (the default is false )  -->
    <property name="testOnBorrow" value="true" />
    <!--  Whether to check the link validity when returning the link to the link pool. (the default is false )  -->
    <property name="testOnReturn" value="true" />
    <!--  Whether the idle timeout is detected when the caller gets the link. If timeout occurs, it is removed (default is false )  -->
    <property name="testWhileIdle" value="true" />
    <!--  Idle link detects threads 1 The number of links detected in a single run  -->
    <property name="numTestsPerEvictionRun" value="10" />
    <!--  Idle link detection thread detection cycle. Negative values indicate that the detection thread is not running. (in milliseconds, by default -1 )  -->
    <property name="timeBetweenEvictionRunsMillis" value="60000" />
    <!--  Link retrieval method. Queue: false ; The stack: true -->
    <!--<property name="lifo" value="false" />-->
  </bean>

  <bean id="masterJedisPool" class="redis.clients.jedis.JedisPool">
    <constructor-arg index="0" ref="poolConfig"/>
    <constructor-arg index="1" value="192.168.100.110" type="java.lang.String"/>
    <constructor-arg index="2" value="6379" type="int"/>
  </bean>

  <bean id="slaveJedisPool" class="redis.clients.jedis.JedisPool">
    <constructor-arg index="0" ref="poolConfig"/>
    <constructor-arg index="1" value="192.168.100.110" type="java.lang.String"/>
    <constructor-arg index="2" value="6380" type="int"/>
  </bean>

  <bean id="redisUtils" class="com.silence.spring.redis.readwriteseparation.util.RedisUtils">
    <property name="masterJedisPool" ref="masterJedisPool"/>
    <property name="slaveJedisPool" ref="slaveJedisPool"/>
  </bean>

</beans>


Related articles: