分布式键值系统redis思考二

Redis通常被认为是一个单线程模型的数据库,它使用I/O多路复用技术如epoll进行网络通信,通过文件事件处理器处理连接、读写等事件。这种模型保证了高性能且避免了线程切换的开销。虽然主线程是单线程,但Redis仍有几个后台线程用于AOF重写、定期数据持久化和过期键的惰性删除,以确保这些耗时操作不阻塞主线程的工作。
摘要由CSDN通过智能技术生成

面试官:redis和memcached有什么区别。我:redis是单线程的,memcaced是多线程的。好,那么redis真的是单线程的吗?

网络IO、线程模型

localhost bin]# ps -T -p 3081
   PID   SPID TTY          TIME CMD
  3081   3081 ?        00:00:00 redis-server
  3081   3088 ?        00:00:00 redis-server
  3081   3089 ?        00:00:00 redis-server
  3081   3090 ?        00:00:00 redis-server

纳尼?说好的单线程呢?

从网络IO模型讲起

同步模型(synchronous IO)
阻塞IO(bloking IO)
非阻塞IO(non-blocking IO)
多路复用IO(multiplexing IO)
信号驱动式IO(signal-driven IO)

大家可以了解一下Reactor模式(单线程、多线程),其实Reactor模式中就是利用epoll或者select多路复用函数库来实现。

异步IO(asynchronous IO)

Proactor模式利用异步IO来实现,区别于Reactor,就读事件这个点而言,在于Reactor执行实际的读取操作,而Proactor是操作系统调用内核线程完成读取操作,并将读取的内容放入用户传递过来的缓存区中。

有没有想起Spring中的IOC依赖反转,将对象生命周期控制权讲给Spring容器,那这里我把它形容成读写反转,真正的读写操作交给操作系统。

我们的redis用的是哪种网络IO模型呢,是不是经常听到多路复用,对,没错,redis用的就多路复用的网络模型(I/O多路复用程序的所有功能是通过包装selectepollevportkqueue这些I/O多路复用函数库来实现的),也是我们常提到nio网络模型。
在这里插入图片描述
好,的确有点抽象,我们来看一下java里面如何实现的这个模型的。

服务端逻辑代码

public class MultiplexerTimeServerHandler implements Runnable {

    private Selector selector = null;
    private ServerSocketChannel servChannel = null;
    private int port;
    private boolean stop;

    /**
     * 初始化多路复用器,绑定监听端口
     *
     * @param port
     */
    public MultiplexerTimeServerHandler(int port) {
        try {
            selector = Selector.open();
            servChannel = ServerSocketChannel.open();
            //非阻塞
            servChannel.configureBlocking(false);
            servChannel.socket().bind(new InetSocketAddress(port), 1024);
            //在通道上注册selector,感兴趣事件是OP_ACCEPT
            servChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("服务器监听" + port);
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }


    }

    public void stop() {
        this.stop = true;
    }

    public void run() {
        while (!stop) {
		//循环监听就绪时间
            try {
                selector.select(1000);
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> it = selectionKeys.iterator();
                SelectionKey key = null;
                while (it.hasNext()) {
                    key = it.next();
                    it.remove();
                    try {
                        handleInput(key);
                    } catch (IOException e) {
                        if (key != null) {
                            key.cancel();
                            if (key.channel() != null)
                                key.channel().close();
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        //多路复用器关闭后,所有注册的channel和pipe等资源都会自动去注册并关闭
        // ,所有不需要重复释放资源
        if (selector != null) {
            try {
                selector.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
	//处理事件
    private void handleInput(SelectionKey key) throws IOException {
        if (key.isValid()) {
            if (key.isAcceptable()) {
                ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                SocketChannel sc = ssc.accept();
                sc.configureBlocking(false);
                sc.register(selector, SelectionKey.OP_READ);
            }
            if (key.isReadable()) {
                SocketChannel sc = (SocketChannel) key.channel();
                ByteBuffer readBuff = ByteBuffer.allocate(1024);
                //上面设置了这里是非阻塞的
                int read = sc.read(readBuff);
                if (read > 0) {
                    readBuff.flip();
                    byte[] bytes = new byte[readBuff.remaining()];
                    readBuff.get(bytes);
                    String body = new String(bytes, "utf-8");
                    System.out.println("服务收到命令:" + body);
                    String currentTime = "time".equals(body) ? new Date(System.currentTimeMillis()).toString() : "无效命令";
                    doWrite(sc, currentTime);
                } else if (read < 0) {
                    //对端链路关闭
                    key.cancel();
                    sc.close();
                } else {
                    //读到0字节
                }
            }
        }
    }

    private void doWrite(SocketChannel sc, String content) throws IOException {
        if (content != null && content.trim().length() > 0) {
            byte[] bytes = content.getBytes();
            ByteBuffer byteBuffer = ByteBuffer.allocate(bytes.length);
            byteBuffer.put(bytes);
            byteBuffer.flip();
            //可能存在写半包情况
            sc.write(byteBuffer);
        }
    }
}

服务端启动代码

public class TimeServer {

    public static void main(String... args) throws IOException {
        int port = 8888;

        MultiplexerTimeServerHandler server = new MultiplexerTimeServerHandler(port);
        new Thread(server,"nio-MultiplexerTimeServerHandler-001").start();
    }
}

这里我是用个口令记得,“兴趣事件Selector控,可读可行可连接,实际操作交用户(线程用户态)”。
JAVA NIO 的核心三部分是Channel(通道),Buffer(缓冲区), Selector,想学习的可以自行查阅相关资料。

redis的线程模型

文件事件处理器(file event handler)

1.Redis 基于 Reactor 模式开发了自己的网络事件处理器: 这个处理器被称为文件事件处理器(file event handler)
2.文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字, 并根据套接字目前执行的任务来为套接字关联不同的事件处理器。
3.当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)等操作时, 与操作相对应的文件事件就会产生, 这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。
4.文件事件处理器以单线程方式运行, 但通过使用 I/O 多路复用程序来监听多个套接字, 文件事件处理器既实现了高性能的网络通信模型, 又可以很好地与 redis 服务器中其他同样以单线程方式运行的模块进行对接, 这保持了 Redis 内部单线程设计的简单性。
在这里插入图片描述

Redis是单线程模型为什么效率还这么高?

1.纯内存访问:数据基本存放在内存中,这是10万QPS级别访问的重要基础。
2.非阻塞I/O:Redis采用epoll做为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll中的连接,读写,都转换为了时间,不在I/O上浪费过多的时间。
3.单线程避免了线程切换和竞态产生的消耗。
4.Redis采用单线程模型,每条命令执行如果占用大量时间,会造成其他线程阻塞,对于Redis这种高性能服务是致命的,所以Redis是面向高速执行的数据库。

那多出的几个线程是什么呢

初始化三类线程. 这三类线程被认为是后台执行不影响主线程

BIO_CLOSE_FILE 关闭重写之前的aof文件。
BIO_AOF_FSYNC 定时刷新数据到磁盘上。
BIO_LAZY_FREE 惰性删除过期时间数据。
redis为了保证其高效.一些比较耗时的动作会起线程或者进程来完成.不会阻塞在业务主线程上。

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值