redis集群实践

Redis介绍与实践

在Linux cent os上搭建 redis。

单机Redis搭建

1,redis是c语言开发的,需要安装c语言的编译环境gcc

yum install gcc-c++

2,将redis源码包上传到服务器上redis-3.0.0.tar.gz并解压

tar zxvf redis-3.0.0.tar.gz

3,编译redis,进入到解压的目录里,输入命令make

cd redis-3.0.0.tar.gz

make

4,安装redis 指定redis安装目录

make install PREFIX=/usr/local/redis

到这就是安装成功了

启动redis

进入到redis的安装目录

1,前端启动 前端启动的弊端会占据控制台 不能进行其他操作

./redis-server

2,后台启动 启动信息隐藏起来

需要将解压的redis源码目录中的redis.conf 复制到/usr/local/redis/bin目录下

cp redis.conf /usr/local/redis/bin/

然后修改这个文件 改成yes
这里写图片描述
然后启动

./redis-server redis.conf

3,查看redis进程

ps aux|grep redis

4,关闭redis

./redis-cli shutdown

连接redis

采用redis-cli连接redis 可以连接远程的也可以连接本地 的

默认连接本地的运行在6379的redis服务

./redis-cli

连接远程的redis服务 -h IP -p端口

./redis-cli -h 192.168.25.153 -p 6379

Redis 常用命令 和五种数据类型

String:key-value(做缓存)

Redis中所有的数据都是字符串。命令不区分大小写,key是区分大小写的。Redis是单线程的。Redis中不适合保存内容大的数据。

get、set、

incr:加一(生成id)

Decr:减一

Hash:key-fields-values(做缓存)

相当于一个key对于一个map,map中还有key-value

使用hash对key进行归类。

Hset:向hash中添加内容

Hget:从hash中取内容

List:有顺序可重复

192.168.25.153:6379> lpush list1 a b c d

(integer) 4

192.168.25.153:6379> lrange list1 0 -1

1) “d”

2) “c”

3) “b”

4) “a”

192.168.25.153:6379> rpush list1 1 2 3 4

(integer) 8

192.168.25.153:6379> lrange list1 0 -1

1) “d”

2) “c”

3) “b”

4) “a”

5) “1”

6) “2”

7) “3”

8) “4”

192.168.25.153:6379>

192.168.25.153:6379> lpop list1

“d”

192.168.25.153:6379> lrange list1 0 -1

1) “c”

2) “b”

3) “a”

4) “1”

5) “2”

6) “3”

7) “4”

192.168.25.153:6379> rpop list1

“4”

192.168.25.153:6379> lrange list1 0 -1

1) “c”

2) “b”

3) “a”

4) “1”

5) “2”

6) “3”

192.168.25.153:6379>

Set:元素无顺序,不能重复

192.168.25.153:6379> sadd set1 a b c c c d

(integer) 4

192.168.25.153:6379> smembers set1

1) “b”

2) “c”

3) “d”

4) “a”

192.168.25.153:6379> srem set1 a

(integer) 1

192.168.25.153:6379> smembers set1

1) “b”

2) “c”

3) “d”

192.168.25.153:6379>

还有集合运算命令,自学。

SortedSet(zset):有顺序,不能重复 对服务器消耗大

192.168.25.153:6379> zadd zset1 2 a 5 b 1 c 6 d

(integer) 4

192.168.25.153:6379> zrange zset1 0 -1

1) “c”

2) “a”

3) “b”

4) “d”

192.168.25.153:6379> zrem zset1 a

(integer) 1

192.168.25.153:6379> zrange zset1 0 -1

1) “c”

2) “b”

3) “d”

192.168.25.153:6379> zrevrange zset1 0 -1

1) “d”

2) “b”

3) “c”

192.168.25.153:6379> zrange zset1 0 -1 withscores

1) “c”

2) “1”

3) “b”

4) “5”

5) “d”

6) “6”

192.168.25.153:6379> zrevrange zset1 0 -1 withscores

1) “d”

2) “6”

3) “b”

4) “5”

5) “c”

6) “1”

192.168.25.153:6379>

Key命令过期时间

设置key的过期时间 单位是秒

expire key1 100

ttl查看key的有效期 看到正数说明正在倒数过期时间 -1 代表永久存在 -2代表不存在

ttl key1

persist清除key过期时间

persist key1

Redis 持久化方案

Redis的所有数据都是保存到内存中的。 在redis.conf中配置

Rdb:快照形式,定期把内存中当前时刻的数据保存到磁盘。Redis默认支持的持久化方案。

aof形式:append only file。把所有对redis数据库操作的命令,增删改操作的命令。保存到文件中。数据库恢复时把所有的命令执行一遍即可。

在redis.conf配置文件中配置

两种持久化方案同时开启使用aof文件来恢复数据库。

Redis 集群

Redis-cluster 投票:容错

这里写图片描述

当集群中有一半机器认为一个机器挂了的话 那就是挂了

架构细节

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

(2)节点的fail是通过集群中超过半数的节点检测失效时才生效.

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

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

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

搭建集群

Redis集群中至少应该有三个节点。要保证集群的高可用,需要每个节点有一个备份机。

Redis集群至少需要6台服务器。

搭建伪分布式。可以使用一台虚拟机运行6个redis实例。需要修改redis的端口号7001-7006

因为集群之间的通信需要ruby来进行维持,所以先搭建ruby环境。

1,安装Ruby

yum install ruby

yum install rubygems

2,安装ruby脚本运行使用的包 上传到服务器然后安装

这里写图片描述

gem install redis-3.0.0.gem

3,需要6个redis实例运行在不同的端口7001-7006,创建修改redis.conf的端口 并且每个配置文件还需要把cluster-enabled yes 前的注释去掉 表示开启集群

redis集群之前需要将里面内容清空 一个干净的redis 即redis安装目录下的/bin/dump.rdb 这个文件是用来存储redis内容的 需要把它删除掉 然后在集群

这里写图片描述

端口号以此改完7001-7006

这里写图片描述

4,开启这6个redis服务 自己写脚本开启

cd redis-0
cd bin
./redis-server redis.conf
cd ..
cd ..
cd redis-1
cd bin
./redis-server redis.conf
cd ..
cd ..
cd redis-2
cd bin
./redis-server redis.conf
cd ..
cd ..
cd redis-3
cd bin
./redis-server redis.conf
cd ..
cd ..
cd redis-4
cd bin
./redis-server redis.conf
cd ..
cd ..
cd redis-5
cd bin
./redis-server redis.conf
cd ..
cd ..

创建关闭脚本

redis-0/bin/redis-cli -p 7001 shutdown
redis-1/bin/redis-cli -p 7002 shutdown
redis-2/bin/redis-cli -p 7003 shutdown
redis-3/bin/redis-cli -p 7004 shutdown
redis-4/bin/redis-cli -p 7005 shutdown
redis-5/bin/redis-cli -p 7006 shutdown

5,使用ruby脚本 搭建集群之间的联系 将redis源码目录下的src下的redis-trib.rb文件复制到集群目录下

然后使用搭建 ip是redis所在的ip 这个ip对应的服务器要将对应的端口开放并且还有开放对应的端口+10000的端口也开放如下面的ip 7001开放 17001也要开放 等等

./redis-trib.rb create --replicas 1 101.200.63.90:7001 101.200.63.90:7002 101.200.63.90:7003 101.200.63.90:7004 101.200.63.90:7005 101.200.63.90:7006

如果出现错误 说:call’: ERR Slot 12539 is already busy 则redis集群的旧数据未清理 需要登录到每个redis进行以下命令输入:

flushall

cluster reset

看到OK则集群搭建完毕

集群登录使用

采用 redis-cli连接集群 -c表示集群连接

./redis-cli -p 7002 -c

这里写图片描述

集群搭建完成

Jedis连接redis

单机版redis连接和操作

先引入依赖pom依赖

        <!-- Redis客户端 -->
            <dependency>
                <groupId>redis.clients</groupId>
                <artifactId>jedis</artifactId>
            </dependency>

然后测试连接

package cn.e3mall.jedis;
import java.util.HashSet;
import java.util.Set;
import org.junit.Test;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPool;

public class JedisTest {
    /**
     * 单机版测试连接
     * @throws Exception
     */
    @Test
    public void testJedis() throws Exception {
        //创建一个连接Jedis对象,参数:host、port
        Jedis jedis = new Jedis("101.200.63.90", 6379);
        //直接使用jedis操作redis。所有jedis的命令都对应一个方法。
        jedis.set("test123", "my first jedis test");
        String string = jedis.get("test123");
        System.out.println(string);
        //关闭连接
        jedis.close();
    }
    /**
     * 连接池测试
     * @throws Exception
     */
    @Test
    public void testJedisPool() throws Exception {
        //创建一个连接池对象,两个参数host、port
        JedisPool jedisPool = new JedisPool("101.200.63.90", 6379);
        //从连接池获得一个连接,就是一个jedis对象。
        Jedis jedis = jedisPool.getResource();
        //使用jedis操作redis
        String string = jedis.get("test123");
        System.out.println(string);
        //关闭连接,每次使用完毕后关闭连接。连接池回收资源。
        jedis.close();
        //关闭连接池。
        jedisPool.close();
    }
    /**
     * 连接集群测试
     * @throws Exception
     */
    @Test
    public void testJedisCluster() throws Exception {
        //创建一个JedisCluster对象。有一个参数nodes是一个set类型。set中包含若干个HostAndPort对象。
        Set<HostAndPort> nodes = new HashSet<>();
        nodes.add(new HostAndPort("101.200.63.90",7001));
        nodes.add(new HostAndPort("101.200.63.90",7002));
        nodes.add(new HostAndPort("101.200.63.90",7003));
        nodes.add(new HostAndPort("101.200.63.90",7004));
        nodes.add(new HostAndPort("101.200.63.90",7005));
        nodes.add(new HostAndPort("101.200.63.90",7006));
        JedisCluster jedisCluster = new JedisCluster(nodes);
        //直接使用JedisCluster对象操作redis。
        jedisCluster.set("test", "123");
        String string = jedisCluster.get("test");
        System.out.println(string);
        //关闭JedisCluster对象
        jedisCluster.close();
    }
}

集群连接测试

package cn.e3mall.jedis;

import java.util.HashSet;
import java.util.Set;

import org.junit.Test;

import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPool;

public class JedisTest {
    /**
     * 连接集群测试
     * @throws Exception
     */
    @Test
    public void testJedisCluster() throws Exception {
        //创建一个JedisCluster对象。有一个参数nodes是一个set类型。set中包含若干个HostAndPort对象。
        Set<HostAndPort> nodes = new HashSet<>();
        nodes.add(new HostAndPort("101.200.63.90",7001));
        nodes.add(new HostAndPort("101.200.63.90",7002));
        nodes.add(new HostAndPort("101.200.63.90",7003));
        nodes.add(new HostAndPort("101.200.63.90",7004));
        nodes.add(new HostAndPort("101.200.63.90",7005));
        nodes.add(new HostAndPort("101.200.63.90",7006));
        JedisCluster jedisCluster = new JedisCluster(nodes);
        //直接使用JedisCluster对象操作redis。
        jedisCluster.set("test", "123");
        String string = jedisCluster.get("test");
        System.out.println(string);
        //关闭JedisCluster对象
        jedisCluster.close();
    }
}

Jedis工具类连接

为什么要使用工具类,因为jedis单机连接和集群连接方式不一样,来回切换比较麻烦,所有写个工具类,用策略模式写

一个接口,两个不同的实现类,一个单机实现类一个集群实现类,需要用哪个就在配置文件中写哪个。

接口:

package cn.e3mall.common.jedis;

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);
}

单机实现类

package cn.e3mall.common.jedis;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
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;
    }

}

集群实现类

package cn.e3mall.common.jedis;

import redis.clients.jedis.JedisCluster;

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);
    }

}

需要用集群或者单机的话只需要更改一下配置文件applicationContext-redis.xml

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

    <!-- 连接reids单机版 -->
    <bean class="cn.e3mall.common.jedis.JedisClientPool" id="jedisClientPool">
        <property name="jedisPool" ref="jedisPool"></property>
    </bean>
    <bean id="jedisPool" class="redis.clients.jedis.JedisPool">
        <constructor-arg name="host" value="101.200.63.90"></constructor-arg>
        <constructor-arg name="port" value="6379"></constructor-arg>
    </bean>

    <!-- 连接redis集群 
    <bean class="cn.e3mall.common.jedis.JedisClientCluster" id="jedisClientCluster">
        <property name="jedisCluster" ref="jedisCluster"></property>
    </bean>
    <bean class="redis.clients.jedis.JedisCluster" id="jedisCluster">
        <constructor-arg name="nodes">
            <set>
                <bean class="redis.clients.jedis.HostAndPort">
                    <constructor-arg name="host" value="101.200.63.90"></constructor-arg>
                    <constructor-arg name="port" value="7001"></constructor-arg>
                </bean>
                <bean class="redis.clients.jedis.HostAndPort">
                    <constructor-arg name="host" value="101.200.63.90"></constructor-arg>
                    <constructor-arg name="port" value="7002"></constructor-arg>
                </bean>
                <bean class="redis.clients.jedis.HostAndPort">
                    <constructor-arg name="host" value="101.200.63.90"></constructor-arg>
                    <constructor-arg name="port" value="7003"></constructor-arg>
                </bean>
                <bean class="redis.clients.jedis.HostAndPort">
                    <constructor-arg name="host" value="101.200.63.90"></constructor-arg>
                    <constructor-arg name="port" value="7004"></constructor-arg>
                </bean>
                <bean class="redis.clients.jedis.HostAndPort">
                    <constructor-arg name="host" value="101.200.63.90"></constructor-arg>
                    <constructor-arg name="port" value="7005"></constructor-arg>
                </bean>
                <bean class="redis.clients.jedis.HostAndPort">
                    <constructor-arg name="host" value="101.200.63.90"></constructor-arg>
                    <constructor-arg name="port" value="7006"></constructor-arg>
                </bean>
            </set>
        </constructor-arg>
    </bean>
    -->
</beans>

用哪个就把哪个注释放开 这两个只能选择一个 只能一个注释放开另一个就要注释掉

封装(单机和集群)代码测试

package cn.e3mall.jedis;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cn.e3mall.common.jedis.JedisClient;
/**
 * 封装jedis查询  单机和集群 不用改代码 只需更改配置文件 测试
 建议开发阶段用单机 上线阶段用集群
 * @author ljg
 *
 */
public class JedisClienTest {
    @Test
    public void testJedisClient() throws Exception {
        //初始化Spring容器
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext-redis.xml");
        //从容器中获得JedisClient对象
        JedisClient jedisClient = applicationContext.getBean(JedisClient.class);
        jedisClient.set("first", "100");
        String result = jedisClient.get("first");
        System.out.println(result);


    }

}

Redis缓存 与缓存同步

使用redis缓存不能影响正常业务逻辑

列如缓存 先判断缓存中有没有数据 有的话直接返回 因为redis只能存储String类型的 所以将数据转换成json串进行存储


    @Autowired
    private JedisClient jedisClient;


/**
     *根据内容分类id查询内容分类列表
     */
    @Override
    public List<TbContent> getContentListByCid(long cid) {
        try{
            //查询缓存
            String json = jedisClient.hget(CONTENT_KEY, cid+"");
            //判断缓存是否为空
            if(StringUtils.isBlank(json)){
                //将jsonh转换成list返回
                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(cid);
        //执行查询
        List<TbContent> list = contentMapper.selectByExampleWithBLOBs(example);

        //向缓存中添加数据  到这意思就是缓存中没有数据 所有添加
        try{
            //将数据转换成json
            String json = JsonUtils.objectToJson(list);
            //存入缓存中
            jedisClient.hset(CONTENT_KEY, cid+"", json);
        }catch (Exception e){
            e.printStackTrace();
        }

        return list;
    }

缓存同步 即将存在的缓存删掉 增删改都需要

@Override
    public E3Result addConten(TbContent content) {
        content.setCreated(new Date());
        content.setUpdated(new Date());
        contentMapper.insert(content);
        //缓存同步 增删改都需要 删除redis中的数据
        jedisClient.hdel(CONTENT_KEY, content.getCategoryId().toString());

        return E3Result.ok();
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值