1.redis安装
首先是针对虚拟机可以ping通ip但是不能Ping通域名的情况解决。
首先关闭防火墙。然后cat /etc/resolv.conf这个文件。看是不是
nameServer 8.8.8,8
nameServer 8.8.4.4
如果不是改过来,但是不要进行网络重启:service network restart
这个时候应该是可以ping通域名了。
下面就是下载redis编译工具:yum install gcc和yum install g++
解压redis.tar.gz文件,进去之后进行编译:make
然后安装:make install PREFIX=/usr/local/redis
安装成功之后进入/usr/local/redis/bin下启动redis:./redis-server
2. redis3.0集群搭建
- 解压redis,make进行编译。编译完成之后将会有一个redis.conf文件。
- 安装之后,在/usr/local下新建文件夹叫做redis-cluster文件夹,里面存放六个redis文件夹。分别叫做redis1-redis6
- 将make install之后产生的可执行文件全部拷贝到redis1目录下,将redis.conf也拷贝到下面。
- 修改redis.conf文件的端口号,从7001-7006,然后将cluster-enabled yes打开,最后将daemonize yes。
- 全部编辑好之后,在redis-cluster目录下新建一个脚本叫做start-al.l.sh:
cd redis1
./redis-server redis.conf
cd ..
cd redis2
./redis-server redis.conf
cd ..
cd redis3
./redis-server redis.conf
cd ..
cd redis4
./redis-server redis.conf
cd ..
cd redis5
./redis-server redis.conf
cd ..
cd redis6
./redis-server redis.conf
cd ..
给这个脚本增加一个可执行的权限:chmod +x start-all.sh。然后执行这个文件即可。用ps aux | grep redis:如果可以看到6个redis都起来了,说明是有用了。
搭建服务端集群,需要用ruby环境,所以现在安装ruby.
redis 3.x 之后自带了 redis-trip.rb 命令,此命令用于创建redis 集群,在最初创建的时候调用一次即可,创建完之后,重新启动时,则不需要再调用了。 使用此命令,redis 会帮你分配主从节点,创建相关的node.conf 配置文件(此配置文件记录的是集群节点的信息:主从关系等)。但是redis-trib.rb 是用ruby 写的脚本,要想使用此脚本,必须先安装ruby 环境。
yum install ruby
yum install rubygems:ruby软件包源
将redis-3.0.0.gem复制到linux中,对其进行安装:gem install redis-3.0.0.gem
安装完毕之后,将之前make之后的redis目录下的src目录下的redis-trib.rb拷贝到redis-cluster目录下。
设置集群:./redis-trib.rb create –replicas 1 10.128.24.175:7001 10.128.24.175:7002 10.128.24.175:7003 10.128.24.175:7004 10.128.24.175:7005 10.128.24.175:700
输出以下内容表明集群搭建成功,期间会问你是否接受这种分配方案,就是7001,7002,7003作为主,7004,7005,7006分别作为从服务器。
>>> Creating cluster
Connecting to node 10.128.24.175:7001: OK
Connecting to node 10.128.24.175:7002: OK
Connecting to node 10.128.24.175:7003: OK
Connecting to node 10.128.24.175:7004: OK
Connecting to node 10.128.24.175:7005: OK
Connecting to node 10.128.24.175:7006: OK
>>> Performing hash slots allocation on 6 nodes...
Using 3 masters:
10.128.24.175:7001
10.128.24.175:7002
10.128.24.175:7003
Adding replica 10.128.24.175:7004 to 10.128.24.175:7001
Adding replica 10.128.24.175:7005 to 10.128.24.175:7002
Adding replica 10.128.24.175:7006 to 10.128.24.175:7003
M: db80f5d7987334be095caafec5a377e5dcf85f4d 10.128.24.175:7001
slots:0-5460 (5461 slots) master
M: a25e822374fc35033936a0d55bc133bd80fb5222 10.128.24.175:7002
slots:5461-10922 (5462 slots) master
M: af00fe26cf47c2923dcf33a6cb7d714dd2fcd260 10.128.24.175:7003
slots:10923-16383 (5461 slots) master
S: fb2ec80d34811c8014b4c87918c7ce330779382b 10.128.24.175:7004
replicates db80f5d7987334be095caafec5a377e5dcf85f4d
S: 486137e1f057b85c3f9827929a44aff0286b841c 10.128.24.175:7005
replicates a25e822374fc35033936a0d55bc133bd80fb5222
S: 871555b15c71acf3fe4b44ad53232c29814ec13c 10.128.24.175:7006
replicates af00fe26cf47c2923dcf33a6cb7d714dd2fcd260
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join......
>>> Performing Cluster Check (using node 10.128.24.175:7001)
M: db80f5d7987334be095caafec5a377e5dcf85f4d 10.128.24.175:7001
slots:0-5460 (5461 slots) master
M: a25e822374fc35033936a0d55bc133bd80fb5222 10.128.24.175:7002
slots:5461-10922 (5462 slots) master
M: af00fe26cf47c2923dcf33a6cb7d714dd2fcd260 10.128.24.175:7003
slots:10923-16383 (5461 slots) master
M: fb2ec80d34811c8014b4c87918c7ce330779382b 10.128.24.175:7004
slots: (0 slots) master
replicates db80f5d7987334be095caafec5a377e5dcf85f4d
M: 486137e1f057b85c3f9827929a44aff0286b841c 10.128.24.175:7005
slots: (0 slots) master
replicates a25e822374fc35033936a0d55bc133bd80fb5222
M: 871555b15c71acf3fe4b44ad53232c29814ec13c 10.128.24.175:7006
slots: (0 slots) master
replicates af00fe26cf47c2923dcf33a6cb7d714dd2fcd260
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
- 访问测试。 到/usr/local/redis-cluster/redis1/ 执行 ./redis-cli -p 7001 -c ,可以尝试塞几次值。发现会比较分散地塞到不同的mater中的槽中。
- redis cluser两个小命令。
查看槽信息:我们可以看到一共有16384个槽,分布在这三个mater节点上。
10.128.24.175:7003> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:3
cluster_stats_messages_sent:706
cluster_stats_messages_received:706
cluster nodes,我们可以看到,10.128.24.175:7001中的槽分布是0-5460,10.128.24.175:7002是5461-10922,10.128.24.175:7003是10923-16383
10.128.24.175:7003> cluster nodes
af00fe26cf47c2923dcf33a6cb7d714dd2fcd260 10.128.24.175:7003 myself,master - 0 0 3 connected 10923-16383
db80f5d7987334be095caafec5a377e5dcf85f4d 10.128.24.175:7001 master - 0 1525511739724 1 connected 0-5460
486137e1f057b85c3f9827929a44aff0286b841c 10.128.24.175:7005 slave a25e822374fc35033936a0d55bc133bd80fb5222 0 1525511738718 5 connected
871555b15c71acf3fe4b44ad53232c29814ec13c 10.128.24.175:7006 slave af00fe26cf47c2923dcf33a6cb7d714dd2fcd260 0 1525511734692 6 connected
a25e822374fc35033936a0d55bc133bd80fb5222 10.128.24.175:7002 master - 0 1525511736703 2 connected 5461-10922
fb2ec80d34811c8014b4c87918c7ce330779382b 10.128.24.175:7004 slave db80f5d7987334be095caafec5a377e5dcf85f4d 0 1525511737710 4 connected
3. 相关理论
redis集群后,我们就需要一种数据路由算法将不同key分散存储到不同的redis节点内,通常的做法是获取某个key的hashcode,然后mod,不过这种做法无法很好的支持动态伸缩性需求,一旦节点的增或者删操作,都会导致key无法在redis中命中,所以++在redis3.x之前,基本上都是采用编写一致性hash算法实现redis的集群,但是redis3.x正式支持cluster后,却采用的是hash slot(hash槽)++。
Redis 集群没有使用一致性hash, 而是引入了 哈希槽的概念.
redis集群中一共内置了16384个哈希槽,当set操作时,redis先对key使用crc16验证出一个结果,然后把结果对mod 16384,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。这样做的好处很明显,当需要增加节点时,只需要把其他节点的某些哈希槽挪到新节点就可以了;当需要移除节点时,只需要把移除节点上的哈希槽挪到其他节点就行了。
由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态.
4. 与项目整合
我们可以用策略模式来实现单机版和集群版的测试和更换。
先定义一个接口:
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来连接redis
import org.springframework.beans.factory.annotation.Autowired;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class JedisClientPool implements JedisClient {
@Autowired
private 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;
}
}
集群版本的连接:
JedisCluster使用技巧:
- 单例,内置了所有节点的连接池
- 无需手动借还连接池
- 合理设置commous-pool
import org.springframework.beans.factory.annotation.Autowired;
import redis.clients.jedis.JedisCluster;
public class JedisClientCluster implements JedisClient {
@Autowired
private 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);
}
}
具体使用哪一个呢?很显然我们先配置好用哪个,然后在程序中注入进来即可。
先在application-redis中配置使用哪一个:
<?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">
<context:annotation-config/>
<!-- redis单机版 -->
<!--
<bean id="jedisPool" class="redis.clients.jedis.JedisPool">
<constructor-arg name="host" value="192.168.25.153"/>
<constructor-arg name="port" value="6379"/>
</bean>
<bean id="jedisClientPool" class="com.njupt.swg.jedis.JedisClientPool"/>
-->
<!-- redis集群 -->
<bean id="jedisCluster" class="redis.clients.jedis.JedisCluster">
<constructor-arg>
<set>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="10.128.24.175"/>
<constructor-arg name="port" value="7001"/>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="10.128.24.175"/>
<constructor-arg name="port" value="7002"/>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="10.128.24.175"/>
<constructor-arg name="port" value="7003"/>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="10.128.24.175"/>
<constructor-arg name="port" value="7004"/>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="10.128.24.175"/>
<constructor-arg name="port" value="7005"/>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="10.128.24.175"/>
<constructor-arg name="port" value="7006"/>
</bean>
</set>
</constructor-arg>
</bean>
<bean id="jedisClientCluster" class="com.njupt.swg.jedis.JedisClientCluster"/>
</beans>
这样,我们要用到redis了,直接注入:
@Autowired
private JedisClient jedisClient;
以ContentServiceImpl中的getContentByCid为例,根据cid查询轮播图内容列表,原来的逻辑是直接从数据库取,这里改为从缓存读。
先尝试到缓存中读—没有再从数据库取—将结果添加到缓存中
@Override
public List<TbContent> getContentByCid(long cid) {
//先查询缓存
//添加缓存不能影响正常业务逻辑
try {
//查询缓存
String json = jedisClient.hget("INDEX_CONTENT", cid + "");
//查询到结果,把json转换成List返回
if (StringUtils.isNotBlank(json)) {
List<TbContent> list = JsonUtils.jsonToList(json, TbContent.class);
return list;
}
} catch (Exception e) {
e.printStackTrace();
}
//缓存中没有命中,需要查询数据库
TbContentExample example = new TbContentExample();
TbContentExample.Criteria criteria = example.createCriteria();
//设置查询条件
criteria.andCategoryIdEqualTo(cid);
//执行查询
List<TbContent> list = contentMapper.selectByExample(example);
//把结果添加到缓存
try {
jedisClient.hset("INDEX_CONTENT", cid + "", JsonUtils.objectToJson(list));
} catch (Exception e) {
e.printStackTrace();
}
//返回结果
return list;
}