redis集群的搭建以及在项目中作为缓存使用

Linux系统环境:CentOS 7

前提:已经掌握了单机Redis的安装、配置以及使用

至于为什么要使用redis、集群是什么、为什么要使用redis集群,在这里就不废话了,直接步入正题:

redis集群理论

redis-cluster 结构图

redis-cluster 投票:容错

架构细节:

1. 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。

2. 节点的fail是通过集群中超过半数的节点检测失效时才生效(所以一个集群中至少要有三个节点)

3. 客户端与redis节点直连,不需要中间proxy.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。

4. 集群中每一个节点都存放不同的内容,每一个节点都应有备份机。

扫描二维码关注公众号,回复: 1731851 查看本文章

5. redis-cluster把所有的物理节点映射到[0-16383]slot,cluster 负责维护node<->slot<->value

        Redis 集群中内置了16384 个哈希槽,当需要在Redis 集群中放置一个key-value 时,redis先对 key 使用 crc16 算法算出一个结果,然后把结果对16384 求余数,这样每个key 都会对应一个编号在0-16383 之间的哈希槽,redis会根据节点数量大致均等的将哈希槽映射到不同的节点

Redis集群的搭建

        Redis集群中至少应该有三个节点。要保证集群的高可用,需要每个节点有一个备份机。这样我们就至少需要6台服务器。

        因为我的电脑开6台虚拟机会吃力,因为已经开了好多了,这里我们就搭建伪分布式。可以使用一台虚拟机运行6个redis实例。这就需要我们修改redis的端口号为7001-7006。

搭建环境

1. 使用ruby脚本搭建集群。需要ruby的运行环境。

    安装ruby:运行命令yum install ruby yum installrubygems

2. 安装ruby脚本运行使用的包。

    下载包:https://pan.baidu.com/s/1tJmqibjSLedefHutvkWycw  然后传到Linux系统上,安装:

      

搭建步骤

1. 复制6份redis:将/usr/local/bin目录复制6份到redis-cluster文件夹下,命名为redis01-redis06,当然,你的这个目录不一定在这里,视具体情况而定,就是这个目录:


复制结果:


2. 修改每一份的配置文件

    a) 端口号:port 7001 — port 7006

    b) cluster-enabledyes

    c) 注释掉bind配置(默认请情况下就是注释掉的)

3.开启所有的redis,可以一台一台手动开启,也可以编写一个批处理文件:

    a) vim start-all.sh

        

    b) 修改该文件的权限: chomd u+x start-all.sh 即为当前用户添加执行权限

4. 运行 ./start-all.sh  运行之后查看启动状态:

    

5. 将搭建集群所需的ruby脚本文件复制到/usr/local/redis-cluster下

    a) 文件位置:/usr/local/redis/src

    b) 

    c) 复制

        

6. 使用ruby脚本搭建集群

    a) 命令:./redis-trib.rb  create --replicas  1  192.168.25.129:7001  192.168.25.129:7002  192.168.25.129:7003  192.168.25.129:7004  192.168.25.129:7005  192.168.25.129:7006

    b) 含义:create表示创建集群,--replicas  1 表示每一个节点有一台备份机,然后它就会根据后面的节点计算分配槽。如果后面跟的是奇数个节点则会报错。

7. 执行结果


注:如果真的是在6太服务器上面搭建redis集群,第6步的命令在任意一台机器上面执行一次就可以了。(一定要关闭每一台的防火墙,当然,不嫌麻烦的话可以开启每一台上的对应端口,这样最少需要开:6*5=30次)

集群的使用

使用redis-cli连接集群,连接到任意一台均可:

redis01/redis-cli -p 7002 –c

-c:表示连接的是集群,这样就可以自动重定向到其他的节点(当前操作的key对应的槽不一定在当前节点)

然后就可以执行增删改查操作了,方法和普通的单机版redis一样。

Jedis连接集群版Redis

第一步:使用JedisCluster对象。需要一个Set<HostAndPort>参数。Redis节点的列表。

第二步:直接使用JedisCluster对象操作redis。在系统中单例存在。

第三步:打印结果

第四步:系统关闭前,关闭JedisCluster对象。

@Test

publicvoidtestJedisCluster()throwsException{

// 创建一个JedisCluster对象,参数:set类型的nodes,包含若干个HostAndPort对象。

Set<HostAndPort>nodes=newHashSet<>();

nodes.add(newHostAndPort("192.168.25.129",7001));

nodes.add(newHostAndPort("192.168.25.129",7002));

nodes.add(newHostAndPort("192.168.25.129",7003));

nodes.add(newHostAndPort("192.168.25.129",7004));

nodes.add(newHostAndPort("192.168.25.129",7005));

nodes.add(newHostAndPort("192.168.25.129",7006));

JedisClusterjedisCluster=newJedisCluster(nodes);

// 直接使用JedisCluster对象操作redis,每次操作不需要关闭(自带连接池)。

jedisCluster.set("test","123");

Stringstring=jedisCluster.get("test");

System.out.println(string);

// 系统结束前关闭JedisCluster对象。

jedisCluster.close();

}

Spring中使用Redis集群:

定义接口,接口中定义了常用的一些操作。

至于为什么定义接口:因为下面给出了两套接口的实现,一个是单机版的实现,一个是集群版的实现。本地开发的时候使用redis单机版进行开发测试,项目上线的时候使用集群版,这样的话在单机版和集群版之间的切换只需要更改spring的配置文件即可。

public interface JedisClient {

	String set(String key, String value);

	String get(String key);

	Boolean exists(String key);

	Long expire(String key, int seconds);

	Long ttl(String key);

	Long incr(String key);

	Long hset(String key, String field, String value);

	String hget(String key, String field);

	Long hdel(String key, String... field);
}

单机版实现类(jedis连接池):

public class JedisClientPool implements JedisClient {
	
	private JedisPool jedisPool;

	public JedisPool getJedisPool() {
		return jedisPool;
	}

	public void setJedisPool(JedisPool jedisPool) {
		this.jedisPool = jedisPool;
	}

	@Override
	public String set(String key, String value) {
		Jedis jedis = jedisPool.getResource();
		String result = jedis.set(key, value);
		jedis.close();
		return result;
	}

	@Override
	public String get(String key) {
		Jedis jedis = jedisPool.getResource();
		String result = jedis.get(key);
		jedis.close();
		return result;
	}

	@Override
	public Boolean exists(String key) {
		Jedis jedis = jedisPool.getResource();
		Boolean result = jedis.exists(key);
		jedis.close();
		return result;
	}

	@Override
	public Long expire(String key, int seconds) {
		Jedis jedis = jedisPool.getResource();
		Long result = jedis.expire(key, seconds);
		jedis.close();
		return result;
	}

	@Override
	public Long ttl(String key) {
		Jedis jedis = jedisPool.getResource();
		Long result = jedis.ttl(key);
		jedis.close();
		return result;
	}

	@Override
	public Long incr(String key) {
		Jedis jedis = jedisPool.getResource();
		Long result = jedis.incr(key);
		jedis.close();
		return result;
	}

	@Override
	public Long hset(String key, String field, String value) {
		Jedis jedis = jedisPool.getResource();
		Long result = jedis.hset(key, field, value);
		jedis.close();
		return result;
	}

	@Override
	public String hget(String key, String field) {
		Jedis jedis = jedisPool.getResource();
		String result = jedis.hget(key, field);
		jedis.close();
		return result;
	}

	@Override
	public Long hdel(String key, String... field) {
		Jedis jedis = jedisPool.getResource();
		Long result = jedis.hdel(key, field);
		jedis.close();
		return result;
	}

}

集群版实现类:

public class JedisClientCluster implements JedisClient {
	
	private JedisCluster jedisCluster;
	
	public JedisCluster getJedisCluster() {
		return jedisCluster;
	}

	public void setJedisCluster(JedisCluster jedisCluster) {
		this.jedisCluster = jedisCluster;
	}

	@Override
	public String set(String key, String value) {
		return jedisCluster.set(key, value);
	}

	@Override
	public String get(String key) {
		return jedisCluster.get(key);
	}

	@Override
	public Boolean exists(String key) {
		return jedisCluster.exists(key);
	}

	@Override
	public Long expire(String key, int seconds) {
		return jedisCluster.expire(key, seconds);
	}

	@Override
	public Long ttl(String key) {
		return jedisCluster.ttl(key);
	}

	@Override
	public Long incr(String key) {
		return jedisCluster.incr(key);
	}

	@Override
	public Long hset(String key, String field, String value) {
		return jedisCluster.hset(key, field, value);
	}

	@Override
	public String hget(String key, String field) {
		return jedisCluster.hget(key, field);
	}

	@Override
	public Long hdel(String key, String... field) {
		return jedisCluster.hdel(key, field);
	}

}

Spring配置文件中进行配置:

单机版和集群版的配置只能同时存在一个,不然自动注入的时候就会报异常ununique.

<!-- 连接redis单机版  -->
<bean class="cn.e3mall.common.jedis.JedisClientPool">
    <property name="jedisPool" ref="jedisPool"/>
</bean>
<bean class="redis.clients.jedis.JedisPool" id="jedisPool">
    <constructor-arg name="host" value="192.168.25.129"/>
    <constructor-arg name="port" value="6379"/>
</bean>
	
<!-- 连接redis集群 -->
<bean id="jedisClientCluster" class="cn.e3mall.common.jedis.JedisClientCluster">
    <property name="jedisCluster" ref="jedisCluste"/>
</bean>
<bean id="jedisCluste" class="redis.clients.jedis.JedisCluster">
    <constructor-arg name="nodes">
        <set>
            <!-- 这里配置集群中的任意一台节点即可 -->
            <bean class="redis.clients.jedis.HostAndPort">
                <constructor-arg name="host" value="192.168.25.129"/>
                <constructor-arg name="port" value="7001"/>
            </bean>
            <bean class="redis.clients.jedis.HostAndPort">
                <constructor-arg name="host" value="192.168.25.129"/>
                <constructor-arg name="port" value="7002"/>
            </bean>
            <bean class="redis.clients.jedis.HostAndPort">
                <constructor-arg name="host" value="192.168.25.129"/>
                <constructor-arg name="port" value="7003"/>
            </bean>
            <bean class="redis.clients.jedis.HostAndPort">
                <constructor-arg name="host" value="192.168.25.129"/>
                <constructor-arg name="port" value="7004"/>
            </bean>
            <bean class="redis.clients.jedis.HostAndPort">
                <constructor-arg name="host" value="192.168.25.129"/>
                <constructor-arg name="port" value="7005"/>
            </bean>
            <bean class="redis.clients.jedis.HostAndPort">
                <constructor-arg name="host" value="192.168.25.129"/>
                <constructor-arg name="port" value="7006"/>
            </bean>
        </set>
    </constructor-arg>
</bean>

具体使用:

查询的时候先查缓存,查到直接返回,未查到查询数据库,然后放入缓存中。

增删改的时候删除对应的缓存。

@Service
public class ContentServiceImpl2 implements ContentService {

	@Value("${IMAGE_SERVER_URL}")
	private String IMAGE_SERVER_URL; // 图片服务器地址

	@Value("${CONTENT_LIST}")
	private String CONTENT_LIST; // redis缓存中内容列表的key
	
	@Autowired
	private TbContentMapper contentMapper;

	@Autowired // 根据类型注入
	private JedisClient jedisClient;

	@Override
	public int addContent(TbContent content) {
		Date nowDate = new Date();
		content.setCreated(nowDate);
		content.setUpdated(nowDate);
		// 插入到数据库
		int rows = contentMapper.insertSelective(content);
		// 缓存同步,删除缓存中对应的数据
		Long categoryId = content.getCategoryId();
		jedisClient.hdel(CONTENT_LIST, categoryId.toString());
		
		
		return rows;
	}

	@Override
	public int updateContent(TbContent content) {
		content.setUpdated(new Date());
		int rows = contentMapper.updateByPrimaryKeySelective(content);
		// 缓存同步,删除缓存中对应的数据
		Long categoryId = content.getCategoryId();
		jedisClient.hdel(CONTENT_LIST, categoryId.toString());
		return rows;
	}

	@Override
	public E3Result deleteContent(List<Long> ids) throws Exception {
		// 删除图片服务器上图片
		FastDFSClient fastDFSClient = new FastDFSClient("classpath:conf/client.conf"); // 初始化FastDFS客户端
		TbContentExample example = new TbContentExample();
		example.createCriteria().andIdIn(ids);
		List<TbContent> Contents = contentMapper.selectByExample(example);
		// 遍历删除图片
		for (TbContent tbContent : Contents) {
			if (tbContent.getPic() != null) {
				String url1 = tbContent.getPic().replace(IMAGE_SERVER_URL, "");
				fastDFSClient.deleteFile1(url1);
			}
			if (tbContent.getPic2() != null) {
				String url2 = tbContent.getPic2().replace(IMAGE_SERVER_URL, "");
				fastDFSClient.deleteFile1(url2);
			}
		}
		// 删除数据库数据
		contentMapper.deleteByExample(example);
		// 缓存同步,删除缓存中对应的数据
		Long categoryId = Contents.get(0).getCategoryId();
		jedisClient.hdel("CONTENT_LIST", categoryId.toString());
		
		return E3Result.ok();
	}

	// 查询时先查询缓存,查不到再查询数据库,并将结果放入redis中
	@Override
	public List<TbContent> getContentByCid(long categoryId) {
		// 查询缓存
		try {
			// 如果缓存中有就直接响应结果
			String json = jedisClient.hget(CONTENT_LIST, categoryId + "");
			if (StringUtils.isNotBlank(json)) {
				List<TbContent> list = JsonUtils.jsonToList(json, TbContent.class);
				return list;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		// 没有则查询数据库
		TbContentExample example = new TbContentExample();
		Criteria criteria = example.createCriteria();
		// 设置条件
		criteria.andCategoryIdEqualTo(categoryId);
		// 执行查询
		List<TbContent> list = contentMapper.selectByExample(example);
		// 把结果添加到缓存
		try {
			jedisClient.hset(CONTENT_LIST, categoryId + "", JsonUtils.objectToJson(list));
		} catch (Exception e) {
			e.printStackTrace();
		}
		return list;
	}
}
代码只是作为学习参考使用,具体的使用方法还需要根据自己的业务逻辑情况进行具体实现。

猜你喜欢

转载自blog.csdn.net/qq_39056805/article/details/80656811