redis作为高速缓存在我们的开发场景中经常应用,Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库。
Redis 与其他 key - value 缓存产品有以下三个特点:
Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
Redis支持数据的备份,即master-slave模式的数据备份。
Redis的优势
性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。
在Java语言中最常用的连接redis服务的客户端包就是jedis,jedis实现对redis操作处理的几个连接类,Jedis、jedisPool、ShardedJedis、ShardedJedisPool,JedisSentinelPool几部分,jedis操作单实例的redis,jedisPool单实例的连接池实现,ShardedJedis操作多节点的redis,即分片存储实现,ShardedJedisPool多实例redis节点分片连接池,JedisSentinelPool哨兵模式连接池,我们通过对这几部分简单应用理解jedis对redis的操作方式。
1,单实例模式:单个redis服务节点,
通过简单对单节点的jedis对象直接操作demo学习jedis,示例代码:
import
redis.clients.jedis.Jedis;
public
class
JedisUtil {
private
static
Jedis
jedis
=
null
;
private
final
String
host
;
private
final
Integer
port
;
private
String
password
;
public
JedisUtil(
final
String
host
,
final
int
port
) {
this
.
host
=
host
;
this
.
port
=
port
;
initJedis();
}
public
JedisUtil(
final
String
host
,
final
int
port
,
final
String
password
) {
this
.
host
=
host
;
this
.
port
=
port
;
this
.
password
=
password
;
initJedis();
}
public
void
initJedis() {
if
(
jedis
==
null
) {
jedis
=
new
Jedis(
host
,
port
);
System.
out
.println(
"创建jedis连接成功"
);
if
(
password
!=
null
) {
jedis
.auth(
password
);
}
}
}
public
void
set(String
key
, String
value
) {
try
{
String
result
=
jedis
.set(
key
,
value
);
System.
out
.println(
result
);
if
(
"OK"
.equals(
result
)) {
}
}
finally
{
jedis
.close();
}
}
public
static
void
main(String[]
args
) {
for
(
int
i
= 0;
i
< 100;
i
++) {
JedisUtil
jedisUtil
=
new
JedisUtil(
"127.0.0.1"
, 4100);
jedisUtil
.set(
"test"
+
i
,
"value"
+
i
);
}
}
}
查看jedis类源码,发现jedis继承了BinaryJedis类和实现了很多Command接口,JedisCommands, MultiKeyCommands,AdvancedJedisCommands, ScriptingCommands, BasicCommands, ClusterCommands, SentinelCommands。
BinaryJedis类是和redis服务连接的交互,JedisCommands接口定义了通用分片和非分片的set,get等接口,BasicCommands接口定义了ping,quit,flushAll,info等基础命令接口,ClusterCommands接口定义了集群接口,SentinelCommands接口定义哨兵接口
jedis实例化源代码过程
public
Jedis
(
final
String
host
,
final
int
port
) {
super
(
host
,
port
);
}
public
BinaryJedis
(
final
String
host
,
final
int
port
) {
client
=
new
Client(
host
,
port
);
}
由于jedis继承了BinaryJedis类,在BinaryJedis中实例化了clent对象,client继承BinaryClient,BinaryClient继承了Connection类,
BinayClient中主要是发送命令的操作,connection是跟redis服务交互。
首先看set方法
/**
* Set the string value as value of the key. The string can't be longer than 1073741824 bytes (1
GB).
*
<p>
Time complexity: O(1)
*
@param
key
*
@param
value
*
@return
Status code reply
*/
public
String set(
final
String
key
, String
value
) {
checkIsInMultiOrPipeline();
client
.set(
key
,
value
);
return
client
.getStatusCodeReply();
}
在调用client的set方法之前,调用了checkIsInMultiOrPipeline();来判断是否设定了mult或者Pipeline模式, 查看手册pipeline 只是把多个redis指令一起发出去,redis并没有保证这些指定的执行是原子的;multi相当于一个redis的transaction的,保证整个操作的原子性,避免由于中途出错而导致最后产生的数据不一致。通过测试得知,pipeline方式执行效率要比其他方式高10倍左右的速度,启用multi写入要比没有开启慢一点。
set方法委托client进行处理
@Override
public
void
set(
final
String
key
,
final
String
value
) {
set
(SafeEncoder.
encode
(
key
), SafeEncoder.
encode
(
value
));
}
掉用了BinaryClient的set方法
public
void
set
(
final
byte
[]
key
,
final
byte
[]
value
) {
sendCommand(Command.
SET
,
key
,
value
);
}
掉用了Connection的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,掉用connection()方法进行连接
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
);
if
(
ssl
) {
if
(
null
==
sslSocketFactory
) {
sslSocketFactory
= (SSLSocketFactory) SSLSocketFactory.
getDefault
();
}
socket
= (SSLSocket)
sslSocketFactory
.createSocket(
socket
,
host
,
port
,
true
);
if
(
null
!=
sslParameters
) {
((SSLSocket)
socket
).setSSLParameters(
sslParameters
);
}
if
((
null
!=
hostnameVerifier
) &&
(!
hostnameVerifier
.verify(
host
, ((SSLSocket)
socket
).getSession()))) {
String
message
= String.
format
(
"The connection to '%s' failed ssl/tls hostname verification."
,
host
);
throw
new
JedisConnectionException(
message
);
}
}
outputStream
=
new
RedisOutputStream(
socket
.getOutputStream());
inputStream
=
new
RedisInputStream(
socket
.getInputStream());
}
catch
(IOException
ex
) {
broken
=
true
;
throw
new
JedisConnectionException(
ex
);
}
}
}
这里主要使用socket进行通信连接,使用长连接进行通讯减少连接开销,并实例化了RedisOutputStream和RedisInputStream来读取内容
2,掉用protocol的sendCommand方法
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
);
}
}
最后掉用client.getStatusCodeReply();获取返回状态
public String getStatusCodeReply() {
flush();
pipelinedCommands
--;
final
byte
[]
resp
= (
byte
[]) readProtocolWithCheckingBroken();
if
(
null
==
resp
) {
return
null
;
}
else
{
return
SafeEncoder.
encode
(
resp
);
}
}
getStatusCodeReply方法中首先掉用了flush()方法,保证之前发送的命令能发送出去,之后掉用了readProtocolWithCheckingBroken()方法,
protected Object readProtocolWithCheckingBroken() {
try
{
return
Protocol.
read
(
inputStream
);
}
catch
(JedisConnectionException
exc
) {
broken
=
true
;
throw
exc
;
}
}
调用Protocol.read进行对RedisInputStream进行读取,
public
static
Object read(
final
RedisInputStream
is
) {
return
process
(
is
);
}
private
static
Object
process
(
final
RedisInputStream
is
) {
final
byte
b
=
is
.readByte();
if
(
b
==
PLUS_BYTE
) {
return
processStatusCodeReply
(
is
);
}
else
if
(
b
==
DOLLAR_BYTE
) {
return
processBulkReply
(
is
);
}
else
if
(
b
==
ASTERISK_BYTE
) {
return
processMultiBulkReply
(
is
);
}
else
if
(
b
==
COLON_BYTE
) {
return
processInteger
(
is
);
}
else
if
(
b
==
MINUS_BYTE
) {
processError
(
is
);
return
null
;
}
else
{
throw
new
JedisConnectionException(
"Unknown reply: "
+ (
char
)
b
);
}
}
最后在read的时候对返回的响应进行了判断,枚举出了几种响应方式,对不同的响应进行不同的处理。
整个交互过程是通过Socket方式通信过程,但是这里也有一个问题就是线程安全问题,显然Jedis实例是线程不安全的,对于多线程共享jedis实例是会有问题的。同时直接使用jedis不能避免的需要反复的创建和销毁Socket,开销很大。