【技术应用】java基于UNIX域套接字unix domain socket连接redis
- 一、前言
- 二、实现思路
- 三、代码实现
- 1、java socket基于redis.sock连接redis
- 2、Lettuce框架基于redis.sock连接redis
一、前言
在公司工作中经常涉及到一些中小型项目,这些项目都会涉及使用redis数据库,但是程序代码和redis一般都位于同一个服务器上,所以不涉及到跨网络请求,但是java操作redis时,使用的操作redis请求(redis://localhost:6379
)往往经过网卡,也影响了性能;
通过学习redis我们了解到,redis是支持UNIX域套接字请求,但是需要配置redis.conf
文件,生成redis.sock
文件,以便客户端使用;
二、实现思路
1)我们知道所有的client
和server
端的数据交互都是通过tcp协议
实现的,在java中我们可以使用socket
实现,这些操作比较麻烦,命令需要我们自己封装,但是有助于我们更好的理解;
此方案仅供学习参考
redis
客户端和服务端之间通信的协议是RESP(REdis Serialization Protocol)
,传输层使用TCP。
a、请求格式
*3\r\n
$3\r\n
set\r\n
$4\r\n
name\r\n
$8\r\n
zhangsan\r\n
*3 表示下面有3条命令
$3 表示后面3个字符是一个命令,即set
$4 表示后面4个字符是一个命令,即name
$8 表示后面8个字符是一个命令,即zhangsan
所以这个请求格式就表示set name zhangsan命令。
b、响应格式
对于简单字符串,回复的第一个字节是“+”
对于错误,回复的第一个字节是“ - ”
对于整数,回复的第一个字节是“:”
对于批量字符串,回复的第一个字节是“$”
对于数组,回复的第一个字节是“ *”
2)使用redis
的Jedis、Lettuce、Redisson框架实现UNIX Domain Socket
连接redis,但是博主只通过lettuce框架实现了功能,Jedis和redisson没有找到好的实现方式,如果有实现的朋友,可以分享一下;
3)redis服务端设置redis.sock
a、修改redis.conf配置文件
# Unix socket.
#
# Specify the path for the Unix socket that will be used to listen for
# incoming connections. There is no default, so Redis will not listen
# on a unix socket when not specified.
#
unixsocket /tmp/redis.sock
unixsocketperm 755
# Close the connection after a client is idle for N seconds (0 to disable)
b、redis.sock默认目录
三、代码实现
1、java socket基于redis.sock连接redis
1)pom.xml文件
<dependency>
<groupId>com.github.jnr</groupId>
<artifactId>jnr-unixsocket</artifactId>
<version>0.38.8</version>
</dependency>
2)关键代码
关键在于通过UNIX Domain Socket
实现socket
连接
package com.sk.init;
import jnr.unixsocket.UnixSocket;
import jnr.unixsocket.UnixSocketAddress;
import jnr.unixsocket.UnixSocketChannel;
import lombok.Cleanup;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@Slf4j
@Component
public class MyClient03 {
//定义属性
//redis.sock路径
private static final String path="/tmp/redis.sock";
/**
* 主函数
*/
@SneakyThrows
public void start() {
//1.建立unixSocket
File sockFile = new File(path);
UnixSocketAddress address = new UnixSocketAddress(sockFile);
UnixSocketChannel channel = UnixSocketChannel.open(address);
UnixSocket unixSocket = new UnixSocket(channel);
//2.判断是否连接到服务器
log.info("是否连接到redis服务器:"+ unixSocket.isConnected());
//3. 获取iOutputStream输入流,这是发给服务器的请求信息
@Cleanup OutputStream outputStream = unixSocket.getOutputStream();
//4. 获取inputStream输入流,这是服务器返回的响应
@Cleanup InputStream inputStream = unixSocket.getInputStream();
//5.开始执行命令
//(1)执行auth命令
exec(outputStream,inputStream,"*2\r\n$4\r\nauth\r\n$8\r\n******\r\n");
//(12)执行ping命令
exec(outputStream,inputStream,"*1\r\n$4\r\nping\r\n");
//(3)执行set命令
exec(outputStream,inputStream,"*3\r\n$3\r\nset\r\n$5\r\ndream\r\n$6\r\nmonkey\r\n");
//(4)执行get命令
exec(outputStream,inputStream,"*2\r\n$3\r\nget\r\n$5\r\ndream\r\n");
//(5)执行del命令
exec(outputStream,inputStream,"*2\r\n$3\r\ndel\r\n$5\r\ndream\r\n");
//(6)执行exists命令
exec(outputStream,inputStream,"*2\r\n$6\r\nexists\r\n$5\r\ndream\r\n");
}
private void exec(OutputStream outputStream, InputStream inputStream,String command) throws IOException {
//1.redis协议字符串 ping
//String sendInfo="*1\r\n$4\r\nping\r\n";
//2. 发送授权命令
outputStream.write(command.getBytes());
//3.创建字节数组装响应数据
byte[] responseByte = new byte[1024];
//4.读取服务器响应到字节数组
int length = inputStream.read(responseByte);
//5.将字节数组转换成字符串
String responseInfo = new String(responseByte, 0, length);
log.info("执行结果:"+responseInfo);
}
}
3)执行结果
21:26:36.696 [http-nio-8098-exec-1] INFO com.sk.init.MyClient03.start(MyClient03.java:40) - 是否连接到redis服务器:true
21:26:36.710 [http-nio-8098-exec-1] INFO com.sk.init.MyClient03.exec(MyClient03.java:71) - 执行结果:+OK
21:26:36.710 [http-nio-8098-exec-1] INFO com.sk.init.MyClient03.exec(MyClient03.java:71) - 执行结果:+PONG
21:26:36.711 [http-nio-8098-exec-1] INFO com.sk.init.MyClient03.exec(MyClient03.java:71) - 执行结果:+OK
21:26:36.711 [http-nio-8098-exec-1] INFO com.sk.init.MyClient03.exec(MyClient03.java:71) - 执行结果:$6
monkey
21:26:36.711 [http-nio-8098-exec-1] INFO com.sk.init.MyClient03.exec(MyClient03.java:71) - 执行结果::1
21:26:36.712 [http-nio-8098-exec-1] INFO com.sk.init.MyClient03.exec(MyClient03.java:71) - 执行结果::0
2、Lettuce框架基于redis.sock连接redis
1)pom.xml依赖
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>6.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
<classifier>linux-x86_64</classifier>
</dependency>
注:必须引入netty-transport-native-epoll
依赖,不然Epoll.isAvailable()
为false
,导致程序无法正常执行,具体问题下一篇文章会介绍;
2)关键代码
package com.sk.init;
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
@Slf4j
@Component
public class MyClient05 {
//格式:redis-socket://path[?[timeout=timeout[d|h|m|s|ms|us|ns]][&_database=database_]]
//完整:redis-socket:///tmp/redis?timeout=10s&_database=0
private final static String path = "redis-socket:///tmp/redis.sock?timeout=100s&database=0";
public void start(){
//创建jedis
RedisURI uri = RedisURI.create("redis-socket:///tmp/redis.sock");
uri.setDatabase(0);
uri.setTimeout(Duration.of(100, ChronoUnit.SECONDS));
uri.setPassword("******");
RedisClient redisClient = RedisClient.create(uri);
StatefulRedisConnection<String, String> connection = redisClient.connect();
log.info("---------------11111111-------------------");
connection.setAutoFlushCommands(true);
RedisCommands<String, String> jedis = connection.sync();
//jedis.auth("Dsh0406%");
log.info("----------------------------------");
//执行命令
String ping = jedis.ping();
log.info("ping result:{}",ping);
String set = jedis.set("dream", "girls");
log.info("set result:{}",set);
String get = jedis.get("dream");
log.info("get result:{}",get);
Long del = jedis.del("dream");
log.info("del result:{}",del);
Long exists = jedis.exists("dream");
log.info("exists result:{}",exists);
}
}
3)执行结果
20:50:42.453 [http-nio-8098-exec-1] INFO com.sk.init.MyClient05.start(MyClient05.java:31) - ---------------11111111-------------------
20:50:42.454 [http-nio-8098-exec-1] INFO com.sk.init.MyClient05.start(MyClient05.java:39) - ----------------------------------
20:50:42.457 [http-nio-8098-exec-1] INFO com.sk.init.MyClient05.start(MyClient05.java:43) - ping result:PONG
20:50:42.460 [http-nio-8098-exec-1] INFO com.sk.init.MyClient05.start(MyClient05.java:45) - set result:OK
20:50:42.472 [http-nio-8098-exec-1] INFO com.sk.init.MyClient05.start(MyClient05.java:47) - get result:girls
20:50:42.474 [http-nio-8098-exec-1] INFO com.sk.init.MyClient05.start(MyClient05.java:49) - del result:1
20:50:42.476 [http-nio-8098-exec-1] INFO com.sk.init.MyClient05.start(MyClient05.java:51) - exists result:0
问题:
在实现过程中遇到一个问题,如果redis服务端设置了密码,连接一直报错,提示没有设置密码,但是使用jedis.auth("******")
代码设置了密码,但是一直提示密码问题,报错是在redisClient.connect()
方法调用上,所以猜测密码应该在RedisClient.create(path)
的path
上设置,
由于UNIX域套节字是在本机提供服务的,也不需要设置密码,所以验证时就把密码取消掉了。
问题解决:
最开始加载redis.sock
方式为:
private final static String path = "redis-socket:///tmp/redis.sock?timeout=100s&database=0";
RedisClient redisClient = RedisClient.create(path);
这是Lettuce
定制URL连接方式
修改为Lettuce
标准的RedisURI
连接方式:
RedisURI uri = RedisURI.create("redis-socket:///tmp/redis.sock");
uri.setDatabase(0);
uri.setTimeout(Duration.of(100, ChronoUnit.SECONDS));
uri.setPassword("******");
RedisClient redisClient = RedisClient.create(uri);
功能还是要多分析多验证
20:43:07.508 [http-nio-8098-exec-1] ERROR org.apache.juli.logging.DirectJDKLog.log(DirectJDKLog.java:175) - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to /tmp/redis.sock] with root cause
io.lettuce.core.RedisCommandExecutionException: NOAUTH Authentication required.
at io.lettuce.core.internal.ExceptionFactory.createExecutionException(ExceptionFactory.java:147) ~[lettuce-core-6.2.1.RELEASE.jar!/:6.2.1.RELEASE]
at io.lettuce.core.internal.ExceptionFactory.createExecutionException(ExceptionFactory.java:116) ~[lettuce-core-6.2.1.RELEASE.jar!/:6.2.1.RELEASE]
at io.lettuce.core.protocol.AsyncCommand.completeResult(AsyncCommand.java:120) ~[lettuce-core-6.2.1.RELEASE.jar!/:6.2.1.RELEASE]
at io.lettuce.core.protocol.AsyncCommand.complete(AsyncCommand.java:111) ~[lettuce-core-6.2.1.RELEASE.jar!/:6.2.1.RELEASE]
at io.lettuce.core.protocol.CommandHandler.complete(CommandHandler.java:747) ~[lettuce-core-6.2.1.RELEASE.jar!/:6.2.1.RELEASE]
at io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:682) ~[lettuce-core-6.2.1.RELEASE.jar!/:6.2.1.RELEASE]
at io.lettuce.core.protocol.CommandHandler.channelRead(CommandHandler.java:599) ~[lettuce-core-6.2.1.RELEASE.jar!/:6.2.1.RELEASE]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442) ~[netty-transport-4.1.86.Final.jar!/:4.1.86.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.86.Final.jar!/:4.1.86.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.86.Final.jar!/:4.1.86.Final]
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) ~[netty-transport-4.1.86.Final.jar!/:4.1.86.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) ~[netty-transport-4.1.86.Final.jar!/:4.1.86.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.86.Final.jar!/:4.1.86.Final]
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) ~[netty-transport-4.1.86.Final.jar!/:4.1.86.Final]
at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:800) ~[netty-transport-classes-epoll-4.1.86.Final.jar!/:4.1.86.Final]
at io.netty.channel.epoll.EpollDomainSocketChannel$EpollDomainUnsafe.epollInReady(EpollDomainSocketChannel.java:138) ~[netty-transport-classes-epoll-4.1.86.Final.jar!/:4.1.86.Final]
at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:499) ~[netty-transport-classes-epoll-4.1.86.Final.jar!/:4.1.86.Final]
at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:397) ~[netty-transport-classes-epoll-4.1.86.Final.jar!/:4.1.86.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997) ~[netty-common-4.1.86.Final.jar!/:4.1.86.Final]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.86.Final.jar!/:4.1.86.Final]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.86.Final.jar!/:4.1.86.Final]
at java.lang.Thread.run(Thread.java:750) ~[?:1.8.0_352]
=如果文章对你有帮助,请点赞、收藏、评论=