Redsi自定义客户端分片实现

今天设计一个基于客户端的分片缓存方案...比如如果业务量不是很大的话,这种情况下是可以自己实现分片逻辑的,这样出了问题,也比较好定位嘛,接下来我们就分析并实现一种这样的方案:

主要包括缓存分片和读写分离以及失效转移,三个功能

缓存分片:

& 随机分片

& HASH一致性分片

读写分离:

& 随机分片

& 轮询分片

首先看一下项目整体结构:


代码整体不复杂也不多:看看核心接口

& 分片接口

package com.redis.shard.strategy;

/**
 * @author 18011618
 * 基于分片策略接口
 * 默认实现机制基于关键字的hash取模
 */
public interface ShardingStrategy {
   /**
    * 基于hash算法
    * @param key 输入的字符串
    * @param nodeCount 节点数量
    * @return
    */
   public <T> int shard(T key, int nodeCount);
}

& 读写分离接口

package com.redis.shard.strategy;

/**
 * @author 18011618
 * 用来提供读写分离策略的接口
 * 基于轮询
 * 基于随机
 */
public interface SelectShardStrategy {
   
   /**
    * 读写分离机制
    * @param nodeCount
    * @return
    */
   public int selectShard(int nodeCount);
}
 

看看具体实现:

package com.redis.shard.strategy.impl;

import com.redis.shard.strategy.ShardingStrategy;

/**
 * @author 18011618
 * 实现具体的hash分片
 */
public class HashShardingStrategy implements ShardingStrategy {
   @Override
   public <T> int shard(T key, int nodeCount) {
      return key.hashCode() % nodeCount;
   }
   
}
 

随机选择:

package com.redis.shard.strategy.impl;

import java.util.Date;
import java.util.Random;

import com.redis.shard.strategy.SelectShardStrategy;

/**
 * @author 18011618
 * 基于随机实现策略
 */
public class RandomSelectStrategy implements SelectShardStrategy{
   
   private Random random = new Random(new Date().getTime());
   
   @Override
   public int selectShard(int nodeCount) {
      return random.nextInt(nodeCount);
   }
   
}
 

轮询策略:

 
package com.redis.shard.strategy.impl;

import java.util.concurrent.atomic.AtomicLong;

import com.redis.shard.strategy.SelectShardStrategy;
/**
 * 基于轮询的策略
 * @author 18011618
 */
public class RoundRobinSelectStrategy implements SelectShardStrategy {
   private AtomicLong atomic = new AtomicLong(0);
   @Override
   public int selectShard(int nodeCount) {
      long value = atomic.incrementAndGet();
      if(value == Long.MAX_VALUE){
         atomic.set(0);
      }
      return (int)value % nodeCount;
   }
   
}
 

接下来看看分片节点信息的封装:

package com.redis.shard;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

import redis.clients.jedis.JedisPool;

import com.redis.shard.strategy.SelectShardStrategy;
import com.redis.shard.strategy.impl.RandomSelectStrategy;
import com.redis.shard.strategy.impl.RoundRobinSelectStrategy;

/**
 * @author 18011618
 * 封装客户端缓存分片的节点信息
 */
public class RedisShardNode {
   //191.168.1.1:6350,191.168.1.2:6350
   public static final String NODE_SEPARATOR = ",";
   //191.168.1.1:6350
   public static final String HOST_PORT_SEPARATOR = ":";

   //主节点
   private JedisPool master;
   //从节点
   private List<JedisPool> slaves;
   
   //读写分离机制 如果开启了读写分离会启用该策略
   public SelectShardStrategy selectStrategy;
   
   //指定默认读写策略为轮询
   public RedisShardNode( JedisPool master, List<JedisPool> slaves){
      this.master = master;
      this.slaves = slaves;
      this.selectStrategy = new RandomSelectStrategy();
   }
   
   //根据用户传递的读写策略
   public RedisShardNode(JedisPool master,List<JedisPool>slaves,SelectShardStrategy selectShardStrategy){
      this.master = master;
      this.slaves = slaves;
      this.selectStrategy = selectShardStrategy;
   }
   //通过参数实例化+指定默认策略
   public RedisShardNode(String masterConnStr, List<String> slavesConnStrs){
      String[] masterHostPortArray = masterConnStr.split(HOST_PORT_SEPARATOR);
      //设置master
      this.master = new JedisPool(new GenericObjectPoolConfig(),
            masterHostPortArray[0], Integer.valueOf(masterHostPortArray[1]));
      
      //设置slaves
      this.slaves = new ArrayList<JedisPool>();
      
      for (String slaveConnStr : slavesConnStrs) {
         String[] slaveHostPortArray = slaveConnStr
               .split(HOST_PORT_SEPARATOR);
         this.slaves.add(new JedisPool(new GenericObjectPoolConfig(),
               slaveHostPortArray[0], Integer
                     .valueOf(slaveHostPortArray[1])));
      }
      this.selectStrategy = new RoundRobinSelectStrategy();
   }
   //实例化参数+传递策略
   public RedisShardNode(String masterConnStr, List<String> slavesConnStrs,SelectShardStrategy selectShardStrategy){
      this(masterConnStr,slavesConnStrs);
      this.selectStrategy = selectShardStrategy;
   }

   public JedisPool getMaster() {
      return master;
   }

   public void setMaster(JedisPool master) {
      this.master = master;
   }

   public List<JedisPool> getSlaves() {
      return slaves;
   }

   public void setSlaves(List<JedisPool> slaves) {
      this.slaves = slaves;
   }
   //获取一个从节点
   public JedisPool getSlaveRedisShardNode() {
      int nodeIndex = selectStrategy.selectShard(slaves.size());
      return slaves.get(nodeIndex);
   }
}
 

最后看看客户端分片实现机制:因为要基于JEDISs所以需要基础Jedis

package com.redis.shard;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import redis.clients.jedis.Jedis;

import com.redis.shard.strategy.ShardingStrategy;
import com.redis.shard.strategy.impl.HashShardingStrategy;

/**
 * @author 18011618
 * 封装对外的redis分片客户端使用方法
 */
public class RedisShardClient extends Jedis{
   
   protected final Logger log = LoggerFactory.getLogger(RedisShardClient.class);
   //存储分片节点
   private List<RedisShardNode> redisShardNodes = new ArrayList<RedisShardNode>();
   //分片策略
   private ShardingStrategy shardingStategy = new HashShardingStrategy();
   //是否开启读写
   private boolean readWriteSeparate = false;
   //连接字符串
   private List<String> nodeConnStrs;
   
   //一定要有这个函数 否则反射的时候会报错
   public RedisShardClient(){
      
   }
   
   //传递集群连接字符串
   public RedisShardClient(List<String> nodeConnStrs){
      if (StringUtils.isEmpty(nodeConnStrs)) {
         log.error("The nodeConnStrs {} for Redic is invalid.", nodeConnStrs);
         throw new IllegalArgumentException(
               "The nodeConnStrs for Redic is invalid.");
      }
      this.nodeConnStrs = nodeConnStrs;
      //调用初始化方法
      init();
   }
   
   /**
    * 初始化分片节点
    */
   public void init(){
      for(String nodeStr:nodeConnStrs){
         this.addRedisNode(nodeStr);
      }
   }
   
   private RedisShardClient addRedisNode(String masterConnStr, List<String> slaveConnStrs){
      redisShardNodes.add(new RedisShardNode(masterConnStr, slaveConnStrs));
      return this;
   }
   public RedisShardClient addRedisNode(String nodeConnStr) {
      String[] nodes = nodeConnStr.split(RedisShardNode.NODE_SEPARATOR);
      return addRedisNode(nodes[0], Arrays.asList(Arrays.copyOf(nodes, 1)));
   }
   

   /**
    * read方法 往主节点
    */
   public <T> Jedis getRead(T key){
      int nodeIndex = shardingStategy.shard(key, redisShardNodes.size());
      RedisShardNode node = redisShardNodes.get(nodeIndex);
      //如果开启了读写分离
      if(!readWriteSeparate)
         return node.getMaster().getResource();//从主节点
      //随机选择一个从节点进行读取
      return node.getSlaveRedisShardNode().getResource();
   }
   
   /**
    * write方法
    */
   public <T> Jedis getWrite(T key){
      int nodeIndex = shardingStategy.shard(key, redisShardNodes.size());
      RedisShardNode node = redisShardNodes.get(nodeIndex);
      return node.getMaster().getResource();
   }
   
   /**
    * 重写get方法
    */
   @Override
   public String get(String key) {
      Jedis jedis = getRead(key);
      String result = jedis.get(key);
      jedis.close();
      return result;
   }

   /**
    * 重写set方法
    */
   @Override
   public String set(String key, String value) {
      Jedis jedis =getWrite(key);
      String result = jedis.set(key, value);
      jedis.close();
      return result;
   }

}
 

最后看一下配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

   <bean id="redisShardClient" class="com.redis.shard.RedisShardClient" init-method="init">
      <!--配置读写分离开关 -->
      <property name="readWriteSeparate" value="${redis.readWrite}"/>
      <property name="nodeConnStrs">
      <!-- 配置多个节点 -->
         <list>
            <value>${reids.node1}</value>
<!--            <value>${reids.node2}</value>
 -->         </list>
      </property>
   </bean>

</beans>

OK,再写一个测试类:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.redis.shard.RedisShardClient;
@SuppressWarnings("all")
public class RediShardTest  {
   public static void main(String[] args) {
      String path ="/spring/spring_redis_shard_client_test.xml";
      ApplicationContext context = new ClassPathXmlApplicationContext();
      RedisShardClient redisShardClient = (RedisShardClient)context.getBean("redisShardClient");
      redisShardClient.set("uname", "jhp");
      
      String uname = redisShardClient.get("uname");
      System.out.println("uname:"+uname);
   }
}
 

对应的配置文件:

具体配置文件内容:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<bean
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations" value="classpath:spring/redic.properties"/>
</bean>

<import resource="classpath:spring/spring_redis_client_shard.xml"/>
</beans>

以及属性配置文件:

reids.node1=localhost:6379,localhost:6379
redis.readWrite=true 

到此一个简单的基于客户端分片功能就实现了,大家可以增加更加复杂的策略完成更多的功能,比如HASH一致性,这个在dubbo的

cluster模块中loadbalance是有实现,可以去参考

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
内容简介: Redis的的是完全开源免费的,遵守BSD协议,是一个高性能的键值数据库。是当前最热门的的的NoSql数据库之一,也被人们称为数据结构服务器。本课程从Redis基本数据类型开始,了解不同数据类型的用法和底层实现 。进一步学习Redis的一些高级特性与工作原理。了解Redis在分布式环境中的工作方式,和实际项目的使用及问题解决。 为什么学Redis? 原因很简单,快!这个问题在大并发,高负载的网站中必须考虑.redis数据库中的所有数据都存储在内存中。由于内存的读写速度远快于硬盘,因此Redis在性能上对比其他基于硬盘存储的数据库有非常明显的优势。项目中使用Redis,主要是从两个角度去考虑:性能状语从句:并发。当然,Redis的的的还具备可以做分布式锁等其他功能,但是如果只是为了分布式锁这些其他功能,完全还有其他中间件代替,并不是非要使用Redis的的的。因此,这个问题主要从性能和并发两个角度去答。性能:我们在碰到需要执行耗时特别久,且结果不频繁变动的SQL,就特别适合将运行结果放入缓存,这样,后面的请求就去缓存中读取,请求使得能够迅速响应。 并发: 在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常。这个时候,就需要使用的的Redis的做一个缓冲操作,让请求先访问到的Redis的的,而不是直接访问数据库。redis优势:1.运行在内存,速度快官方号称支持并发11瓦特读操作,并发8瓦特写操作,可以说是相当彪悍了。2.数据虽在内存,但是提供了持久化的支持,即可以将内存中的数据异步写入到硬盘中,同时不影响继续提供服务3.支持数据结构丰富(string(字符串),list(链表),set(集合),zset(sorted set - 有序集合))和Hash(哈希类型,md5加密出来的那个串)课程大纲: 为了让大家快速系统了解Redis核心知识全貌,我为你总结了「Redis核心框架图」,帮你梳理学习重点,建议收藏!!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值