Redis是一种非关系型(K-V)数据库,也叫做缓存数据库,类似于Memcached ,但有着以下优点:
(1)Redis不仅仅支持简单的字符串类型的K-V数据,还支持hash 、list、set、zset等多种数据类型。
(2)Redis支持 master-slave(主—从)模式应用,memcache 支持分布式
(3)Redis支持数据的备份,即master-slave模式的数据备份
(3)Redis支持数据的持久化,也就是缓存的数据不仅能放在缓存中,还能放到磁盘中。memcache 一旦断电,数据全部丢失, redis 可以利用快照和 AOF 把数据存到磁盘中,当恢复时又从磁盘中读取到内存中,当 物理内存使用完毕后,可以把数据写入到磁盘中。
(4)Redis的过期策略不同,memcache在set时就指定,例如set key1 0 0 8,即永不过期。Redis可以通过例如expire 设定,例如expire name 10
Redis的调用过程实际上是客户端像Redis服务器发送网络通信,然后Redis收到请求,并将结果返回给客户端的过程
Redis的Java的客户端在发送请求时,又分为三层
(1)传输层(transfer),用套接字socket进行通讯
(2)消息协议层(message),发送的消息体
(3)api,jedis提供的的各种调用redis服务器的方法
示例来自Redis官网:https://github.com/xetorthio/jedis
集成步骤:
1.添加依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.10.2</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
最简单Jedis的使用方法
Jedis jedis = new Jedis("localhost",6379);
jedis.set("foo", "bar");
String value = jedis.get("foo");
可以很容易的想到输出 “bar”
打开源码看一下
public Jedis(final String host, final int port) {
super(host, port);
}
public class BinaryJedis implements BasicCommands, BinaryJedisCommands, MultiKeyBinaryCommands,
AdvancedBinaryJedisCommands, BinaryScriptingCommands, Closeable {
protected Client client = null;
public BinaryJedis(final String host, final int port) {
client = new Client(host, port);
}
}
public class Connection implements Closeable {
private static final byte[][] EMPTY_ARGS = new byte[0][];
private String host = Protocol.DEFAULT_HOST;
private int port = Protocol.DEFAULT_PORT;
private Socket socket;
private RedisOutputStream outputStream;
private RedisInputStream inputStream;
private int pipelinedCommands = 0;
private int connectionTimeout = Protocol.DEFAULT_TIMEOUT;
private int soTimeout = Protocol.DEFAULT_TIMEOUT;
private boolean broken = false;
private boolean ssl;
private SSLSocketFactory sslSocketFactory;
private SSLParameters sslParameters;
private HostnameVerifier hostnameVerifier;
public Connection() {
}
public Connection(final String host) {
this.host = host;
}
public Connection(final String host, final int port) {
this.host = host;
this.port = port;
}
结合上述代码,我们在初始化Jedis的时候,实际上是先初始化与Redis的连接Connection,但是还没有发起连接。
下面调用Jedis的set方法:
public String set(final String key, String value) {
checkIsInMultiOrPipeline();
client.set(key, value);
return client.getStatusCodeReply();
}
@Override
public void set(final String key, final String value) {
set(SafeEncoder.encode(key), SafeEncoder.encode(value));
}
public void set(final byte[] key, final byte[] value) {
sendCommand(Command.SET, key, value);
}
调用jedis.set(),先将key和value转化为字节数组bete[] key和 byte[] value,调用sendCommand()方法
protected Connection sendCommand(final Command cmd, final byte[]... args) {
try {
connect();
Protocol.sendCommand(outputStream, cmd, args);
pipelinedCommands++;
return this;
} catch (JedisConnectionException ex) {
/*
* When client send request which formed by invalid protocol, Redis send back error message
* before close connection. We try to read it to provide reason of failure.
*/
try {
String errorMessage = Protocol.readErrorLineIfPossible(inputStream);
if (errorMessage != null && errorMessage.length() > 0) {
ex = new JedisConnectionException(errorMessage, ex.getCause());
}
} catch (Exception e) {
/*
* Catch any IOException or JedisConnectionException occurred from InputStream#read and just
* ignore. This approach is safe because reading error message is optional and connection
* will eventually be closed.
*/
}
// Any other exceptions related to connection?
broken = true;
throw ex;
}
}
分析:
(1)在调用set方法的时候,去发起连接,为了频繁建立连接耗费资源,先检验当前是否已连接,如果未连接,发送Socket,并初始化当前Connection的outputStream和inputStream
public void connect() {
if (!isConnected()) {
try {
socket = new Socket();
// ->@wjw_add
socket.setReuseAddress(true);
socket.setKeepAlive(true); // Will monitor the TCP connection is
// valid
socket.setTcpNoDelay(true); // Socket buffer Whetherclosed, to
// ensure timely delivery of data
socket.setSoLinger(true, 0); // Control calls close () method,
// the underlying socket is closed
// immediately
// <-@wjw_add
socket.connect(new InetSocketAddress(host, port), connectionTimeout);
socket.setSoTimeout(soTimeout);
/** 省略 **/
outputStream = new RedisOutputStream(socket.getOutputStream());
inputStream = new RedisInputStream(socket.getInputStream());
} catch (IOException ex) {
broken = true;
throw new JedisConnectionException(ex);
}
}
}
(2)sendCommand(Command.SET, key, value)方法,可以看到在调用set的方法的时候,使用的是枚举类Command.SET
public static enum Command {
PING, SET, GET, QUIT, EXISTS, DEL, TYPE, FLUSHDB, KEYS, RANDOMKEY, RENAME, RENAMENX, RENAMEX, DBSIZE, EXPIRE, EXPIREAT, TTL, SELECT, MOVE, FLUSHALL, GETSET, MGET, SETNX, SETEX, MSET, MSETNX, DECRBY, DECR, INCRBY, INCR, APPEND, SUBSTR, HSET, HGET, HSETNX, HMSET, HMGET, HINCRBY, HEXISTS, HDEL, HLEN, HKEYS, HVALS, HGETALL, RPUSH, LPUSH, LLEN, LRANGE, LTRIM, LINDEX, LSET, LREM, LPOP, RPOP, RPOPLPUSH, SADD, SMEMBERS, SREM, SPOP, SMOVE, SCARD, SISMEMBER, SINTER, SINTERSTORE, SUNION, SUNIONSTORE, SDIFF, SDIFFSTORE, SRANDMEMBER, ZADD, ZRANGE, ZREM, ZINCRBY, ZRANK, ZREVRANK, ZREVRANGE, ZCARD, ZSCORE, MULTI, DISCARD, EXEC, WATCH, UNWATCH, SORT, BLPOP, BRPOP, AUTH, SUBSCRIBE, PUBLISH, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBSUB, ZCOUNT, ZRANGEBYSCORE, ZREVRANGEBYSCORE, ZREMRANGEBYRANK, ZREMRANGEBYSCORE, ZUNIONSTORE, ZINTERSTORE, ZLEXCOUNT, ZRANGEBYLEX, ZREVRANGEBYLEX, ZREMRANGEBYLEX, SAVE, BGSAVE, BGREWRITEAOF, LASTSAVE, SHUTDOWN, INFO, MONITOR, SLAVEOF, CONFIG, STRLEN, SYNC, LPUSHX, PERSIST, RPUSHX, ECHO, LINSERT, DEBUG, BRPOPLPUSH, SETBIT, GETBIT, BITPOS, SETRANGE, GETRANGE, EVAL, EVALSHA, SCRIPT, SLOWLOG, OBJECT, BITCOUNT, BITOP, SENTINEL, DUMP, RESTORE, PEXPIRE, PEXPIREAT, PTTL, INCRBYFLOAT, PSETEX, CLIENT, TIME, MIGRATE, HINCRBYFLOAT, SCAN, HSCAN, SSCAN, ZSCAN, WAIT, CLUSTER, ASKING, PFADD, PFCOUNT, PFMERGE, READONLY, GEOADD, GEODIST, GEOHASH, GEOPOS, GEORADIUS, GEORADIUSBYMEMBER, BITFIELD;
public final byte[] raw;
Command() {
raw = SafeEncoder.encode(this.name());
}
}
(2)也就是不管我们使用Jedis的何种API,比如PING,SET,PUT指令,都去复用的SendCommandd的方法
根据传入的类型不同,拼装不同的指令
//字符串
public static final byte DOLLAR_BYTE = '$';
//长度
public static final byte ASTERISK_BYTE = '*';
//空字符串Simple Strings
public static final byte PLUS_BYTE = '+';
//Errors,报错信息
public static final byte MINUS_BYTE = '-';
//Integers,数字
public static final byte COLON_BYTE = ':';
public static void sendCommand(final RedisOutputStream os, final Command command,
final byte[]... args) {
sendCommand(os, command.raw, args);
}
private static void sendCommand(final RedisOutputStream os, final byte[] command,
final byte[]... args) {
try {
os.write(ASTERISK_BYTE);
os.writeIntCrLf(args.length + 1);
os.write(DOLLAR_BYTE);
os.writeIntCrLf(command.length);
os.write(command);
os.writeCrLf();
for (final byte[] arg : args) {
os.write(DOLLAR_BYTE);
os.writeIntCrLf(arg.length);
os.write(arg);
os.writeCrLf();
}
} catch (IOException e) {
throw new JedisConnectionException(e);
}
}
比如jedis,set(“wukong”,2018),就会输出
2.2.1引入依赖
<dependency>
<groupid>org.springframework.data</groupid>
<artifactid>spring-data-redis</artifactid>
<version>1.7.2.RELEASE</version>
</dependency>
或者
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
@Configuration
@EnableCaching
public class RedisTemplateConfig extends CachingConfigurerSupport {
/**
* redisTemplate相关配置.
*
* @param factory 工厂类
* @return
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer =
new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// 值采用json序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
// 设置hash key 和value序列化模式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
//测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RedisConfig.class})
public class RedisTest {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Test
public void testRedisObj() {
Map<String, Object> properties = new HashMap<>();
properties.put("123", "hello");
properties.put("abc", 456);
redisTemplate.opsForHash().putAll("hash", properties);
Map<Object, Object> ans = redisTemplate.opsForHash().entries("hash");
System.out.println("ans: " + ans);
}
}
输出结果
ans: {123=hello, abc=456}
Jedis是Redis官方推荐的面向Java的操作Redis的客户端,而RedisTemplate是SpringDataRedis中对JedisApi的高度封装。
SpringDataRedis相对于Jedis来说可以方便地更换Redis的Java客户端,比Jedis多了自动管理连接池的特性,方便与其他Spring框架进行搭配使用如:SpringCache