org.apache.maven.plugins
maven-compiler-plugin
1.8
UTF-8
4.1.7.RELEASE
2.5.0
redis.clients
jedis
2.9.0
org.springframework.data
spring-data-redis
1.6.0.RELEASE
org.apache.commons
commons-lang3
3.3.2
commons-logging
commons-logging
1.2
junit
junit
4.12
test
org.springframework
spring-core
${spring.version}
org.springframework
spring-beans
${spring.version}
org.springframework
spring-context
${spring.version}
org.springframework
spring-tx
${spring.version}
org.springframework
spring-web
${spring.version}
org.springframework
spring-webmvc
${spring.version}
org.springframework
spring-jdbc
${spring.version}
org.springframework
spring-test
${spring.version}
test
org.mybatis
mybatis-spring
1.3.2
mysql
mysql-connector-java
5.1.6
com.alibaba
druid
1.0.12
org.aspectj
aspectjweaver
1.8.4
log4j
log4j
1.2.17
javax.servlet
servlet-api
3.0-alpha-1
provided
javax.servlet
jstl
1.2
org.codehaus.jackson
jackson-mapper-asl
1.9.13
com.alibaba
fastjson
1.2.3
com.fasterxml.jackson.core
jackson-annotations
${jackson.version}
com.fasterxml.jackson.core
jackson-core
${jackson.version}
com.fasterxml.jackson.core
jackson-databind
${jackson.version}
commons-io
commons-io
2.4
commons-fileupload
commons-fileupload
1.2.2
org.mybatis
mybatis
3.4.5
org.slf4j
slf4j-api
1.7.21
org.slf4j
slf4j-log4j12
1.7.21
com.google.guava
guava
22.0
<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache-4.1.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan base-package="cn.tx"></context:component-scan>
<!-- redis 相关配置 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="300" />
<property name="maxWaitMillis" value="300" />
<property name="testOnBorrow" value="true" />
</bean>
<bean id="JedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:host-name="192.168.0.209"
p:port="6379"
p:pool-config-ref="poolConfig"/>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="JedisConnectionFactory" />
</bean>
</beans>
package cn.tx.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Service
public class RedisCacheUtil<T>{
@Autowired
@Qualifier("redisTemplate")
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象,Integer、String、实体类等
* @param key 缓存的键值
* @param value 缓存的值
* @return 缓存的对象
*/
public <T> ValueOperations<String,T> setCacheObject(String key, T value)
{
ValueOperations<String,T> operation = redisTemplate.opsForValue();
operation.set(key,value);
return operation;
}
public <T> ValueOperations<String,T> setCacheObject(String key, T value, long time)
{
ValueOperations<String,T> operation = redisTemplate.opsForValue();
operation.set(key, value, time, TimeUnit.SECONDS);
return operation;
}
/**
* 获得缓存的基本对象。
* @param key 缓存键值
* @param
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(String key/*,ValueOperations operation*/ )
{
ValueOperations<String,T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 缓存List数据
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> ListOperations<String, T> setCacheList(String key, List<T> dataList)
{
ListOperations listOperation = redisTemplate.opsForList();
if(null != dataList)
{
int size = dataList.size();
for(int i = 0; i < size ; i ++)
{
listOperation.rightPush(key,dataList.get(i));
}
}
return listOperation;
}
/**
* 获得缓存的list对象
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(String key)
{
List<T> dataList = new ArrayList<T>();
ListOperations<String,T> listOperation = redisTemplate.opsForList();
Long size = listOperation.size(key);
for(int i = 0 ; i < size ; i ++)
{
dataList.add((T) listOperation.leftPop(key));
}
return dataList;
}
/**
* 缓存Set
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public <T> BoundSetOperations<String,T> setCacheSet(String key, Set<T> dataSet)
{
BoundSetOperations<String,T> setOperation = redisTemplate.boundSetOps(key);
/*T[] t = (T[]) dataSet.toArray();
setOperation.add(t);*/
Iterator<T> it = dataSet.iterator();
while(it.hasNext())
{
setOperation.add(it.next());
}
return setOperation;
}
/**
* 获得缓存的set
* @param key
* @param
* @return
*/
public Set<T> getCacheSet(String key/*,BoundSetOperations operation*/ )
{
Set<T> dataSet = new HashSet<T>();
BoundSetOperations<String,T> operation = redisTemplate.boundSetOps(key);
Long size = operation.size();
for(int i = 0 ; i < size ; i++)
{
dataSet.add(operation.pop());
}
return dataSet;
}
/**
* 缓存Map
* @param key
* @param dataMap
* @return
*/
public <T> HashOperations<String,String,T> setCacheMap(String key,Map<String,T> dataMap)
{
HashOperations hashOperations = redisTemplate.opsForHash();
if(null != dataMap)
{
for (Map.Entry<String, T> entry : dataMap.entrySet()) {
/*System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue()); */
hashOperations.put(key,entry.getKey(),entry.getValue());
}
}
return hashOperations;
}
/**
* 获得缓存的Map
* @param key
* @param
* @return
*/
public <T> Map<String,T> getCacheMap(String key/*,HashOperations hashOperation*/ )
{
Map<String, T> map = redisTemplate.opsForHash().entries(key);
/*Map map = hashOperation.entries(key);*/
return map;
}
/**
* 缓存Map
* @param key
* @param dataMap
* @return
*/
public <T> HashOperations<String,Integer,T> setCacheIntegerMap(String key, Map<Integer,T> dataMap)
{
HashOperations hashOperations = redisTemplate.opsForHash();
if(null != dataMap)
{
for (Map.Entry<Integer, T> entry : dataMap.entrySet()) {
/*System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue()); */
hashOperations.put(key,entry.getKey(),entry.getValue());
}
}
return hashOperations;
}
/**
* 获得缓存的Map
* @param key
* @param
* @return
*/
public <T> Map<Integer,T> getCacheIntegerMap(String key/*,HashOperations hashOperation*/ )
{
Map<Integer, T> map = redisTemplate.opsForHash().entries(key);
/*Map map = hashOperation.entries(key);*/
return map;
}
}
package cn.tx.model;
import java.io.Serializable;
public class Account implements Serializable {
private int id;
private String name;
public Account() {
}
public Account(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
package cn.tx.service;
import cn.tx.model.Account;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.ArrayList;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:application.xml")
public class AccountServiceTest {
@Autowired
RedisCacheUtil redisCacheUtil;
@Test
public void selectAll(){
List<Account> accounts = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Account a = new Account();
a.setId(i);
a.setName("liangge");
accounts.add(a);
}
redisCacheUtil.setCacheObject("key1", accounts);
List<Account> key1 = (List<Account>) redisCacheUtil.getCacheObject("key1");
System.out.println(key1);
}
}
Redis的高性能是由于其将所有数据都存储在了内存中,为了使Redis在重启之后仍能保证数据不丢失,需要将数据从内存中同步到硬盘中,这一过程就是持久化。
Redis支持两种方式的持久化,一种是RDB方式,一种是AOF方式。可以单独使用其中一种或将二者结合使用
RDB方式的持久化是通过快照(snapshotting)完成的,当符合一定条件时Redis会自动将内存中的数据进行快照并持久化到硬盘。
问题总结:
通过RDB方式实现持久化,一旦Redis异常退出,就会丢失最后一次快照以后更改的所有数据。这就需要开发者根据具体的应用场合,通过组合设置自动快照条件的方式来将可能发生的数据损失控制在能够接受的范围。如果数据很重要以至于无法承受任何损失,则可以考虑使用AOF方式进行持久化。
默认情况下Redis没有开启AOF(append only file)方式的持久化,可以通过appendonly参数开启:
appendonly yes
开启AOF持久化后每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬
盘中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的,默认的文件名是appendonly.aof,可以通过appendfilename参数修改:appendfilename appendonly.aof。
持久化保证了即使redis服务重启也不会丢失数据,因此redis服务重启后会将硬盘上持久化的数据恢复到内存中,但是当redis服务器的硬盘损坏了可能会导致数据丢失,如果通过redis的主从复制机制就可以避免这种单点故障。
无需特殊配置
修改从redis服务器上的redis.conf文件,添加slaveof 主redisip 主redis端口
上边的配置说明当前该从redis服务器所对应的主redis是192.168.0.209,端口是6379
部分复制说明:
从机连接主机后,会主动发起 PSYNC 命令,从机会提供 master的runid(机器标识,随机生成的一个串) 和 offset(数据偏移量,如果offset主从不一致则说明数据不同步),主机验证 runid 和 offset 是否有效, runid 相当于主机身份验证码,用来验证从机上一次连接的主机,如果runid验证未通过则,则进行全同步,如果验证通过则说明曾经同步过,根据offset同步部分数据。
redis集群管理工具redis-trib.rb依赖ruby环境,首先需要安装ruby环境:
安装ruby
yum install ruby
gem install redis
如果此处报错,说明redis4.0的版本需要ruby的版本较高,故此我们更新ruby版本:
执行下面命令:
(1)gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
(2)curl -sSL https://get.rvm.io | bash -s stable
(3)source /etc/profile.d/rvm.sh
(4)rvm list known
(5)rvm install 2.6
(6)rvm use 2.6.3 default
(7)安装完毕后重新执行:gem install redis
这里在同一台服务器用不同的端口表示不同的redis服务器,如下:
主节点:192.168.0.209:7001 192.168.0.209:7002 192.168.0.209:7003
从节点:192.168.0.209:7004 192.168.0.209:7005 192.168.0.209:7006
在/下创建redis-cluster目录,其下创建7001、7002。。7006目录,如下:
创建文件夹命令
mkdir -p /redis-cluster/700{1…6}/{logs,data} && cd /redis-cluster
生成配置文件命令(一起复制,不要单行复制)
IP=`ip a|grep 'inet' |grep -v '127.0.0.1'|grep -v 'inet6'|awk '{print $2}'|awk -F'/' '{print $1}'`
for i in {
1..6}
do
cat > 700${
i}/redis.conf <<EOF
daemonize yes
port 700${
i}
cluster-enabled yes
cluster-config-file cluster-nodes-700${
i}.conf
cluster-node-timeout 15000
appendonly yes
bind ${
IP}
protected-mode no
dbfilename dump-700${
i}.rdb
logfile /redis-cluster/700${
i}/logs/redis.log
pidfile /redis-cluster/700${
i}/data/redis.pid
dir /redis-cluster/700${
i}/data
appendfilename "appendonly-700${i}.aof"
EOF
done
(1)复制下图代码,创建cluster.sh脚本至/redis-cluster文件夹
#!/bin/bash
# by seven
# date 2019-04-09
# redis管理脚本
# start,stop,restart,status
# 该脚本用于管理redis实例,如启动、关闭、状态等
# exp: redis.sh restart 7000
# redis.sh stop all
# # # # # # # # #
# 参数
IP=`ip a|grep 'inet' |grep -v '127.0.0.1'|grep -v 'inet6'|awk '{print $2}'|awk -F'/' '{print $1}'`
port=$2
if [ ! $# -eq 2 ];then
echo "Warning! 请输入两个参数!"
exit 1
fi
if [ -z ${
port} ];then
echo "Warning! 请输入端口号"
exit 1
fi
# start函数
start(){
IS_PORT_EXISTS=$( ps -ef | grep redis| grep ${
port} | grep -v grep | wc -l )
if [ $IS_PORT_EXISTS -ge 1 ];then
echo "Warning! 端口号已存在!"
exit 1
else
/redis-cluster/redis-server /redis-cluster/${
port}/redis.conf
fi
}
# stop函数
stop(){
IS_PORT_EXISTS=$(ps -ef |grep redis |grep ${
port} |grep -v grep |wc -l)
if [ $IS_PORT_EXISTS -lt 1 ];then
echo "Warning! 端口不存在!"
exit 1
else
/redis-cluster/redis-cli -h ${
IP} -p ${
port} shutdown
fi
}
# status函数
status(){
/redis-cluster/redis-trib.rb check ${
IP}:${
port}
}
case $1 in
start)
start
;;
stop)
stop
;;
status)
status
;;
?|help)
echo $"Usage: 'redis' {start|status|stop|help|?}"
;;
*)
echo $"Usage: 'redis' {start|status|stop|help|?}"
esac
(2)复制redis的src目录下的客户端,服务端和集群脚本至/redis-cluster文件夹
cp /usr/local/tools/redis-test/redis-4.0.14/src/redis-cli /redis-cluster/redis-cli
cp /usr/local/tools/redis-test/redis-4.0.14/src/redis-server /redis-cluster/redis-server
cp /usr/local/tools/redis-test/redis-4.0.14/src/redis-trib.rb /redis-cluster/redis-trib.rb
至此 /redis-cluster文件夹下的目录结构如图:
为cluster.sh脚本赋值
chmod 777 cluster.sh
运行脚本启动6个redis进程
./cluster.sh start 7001
查看启动的redis进程:
执行创建集群命令
执行redis-trib.rb,此脚本是ruby脚本,它依赖ruby环境。
./redis-trib.rb create --replicas 1 192.168.0.209:7001 192.168.0.209:7002 192.168.0.209:7003 192.168.0.209:7004 192.168.0.209:7005 192.168.0.209:7006
说明:
redis集群至少需要3个主节点,每个主节点有一个从节点总共6个节点
replicas指定为1表示每个主节点有一个从节点
注意:
如果执行时报如下错误:
[ERR] Node XXXXXX is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0
解决方法是删除生成的配置文件nodes.conf,如果不行则说明现在创建的结点包括了旧集群的结点信息,需要删除redis的持久化文件后再重启redis,比如:appendonly.aof、dump.rdb
查询集群信息
集群创建成功登陆任意redis结点查询集群中的节点情况。
客户端以集群方式登陆:
说明:
./redis-cli -c -h 192.168.0.209 -p 7001 ,其中-c表示以集群方式连接redis,-h指定ip地址,-p指定端口号
cluster nodes 查询集群结点信息
测试代码
// 连接redis集群
@Test
public void testJedisCluster() {
JedisPoolConfig config = new JedisPoolConfig();
// 最大连接数
config.setMaxTotal(30);
// 最大连接空闲数
config.setMaxIdle(2);
//集群结点
Set<HostAndPort> jedisClusterNode = new HashSet<HostAndPort>();
jedisClusterNode.add(new HostAndPort("192.168.0.209", 7001));
jedisClusterNode.add(new HostAndPort("192.168.0.209", 7002));
jedisClusterNode.add(new HostAndPort("192.168.0.209", 7003));
jedisClusterNode.add(new HostAndPort("192.168.0.209", 7004));
jedisClusterNode.add(new HostAndPort("192.168.0.209", 7005));
jedisClusterNode.add(new HostAndPort("192.168.0.209", 7006));
JedisCluster jc = new JedisCluster(jedisClusterNode, config);
JedisCluster jcd = new JedisCluster(jedisClusterNode);
jcd.set("name", "zhangsan");
String value = jcd.get("name");
System.out.println(value);
}