Redis核心原理

Redis 多路IO复用及原理
Jedis源码
 Jedis jedis = new Jedis("192.169.4.4", 6379);
 jedis.set("name","aa"); //socket
 jedis.close();
 
 底层是 this.socket = this.jedisSocketFactory.createSocket(); 创建了一个Socket
 socket-->tcp协议--->操作系统的内核 os kernel
 
模拟redis服务端和客户端

服务端

//ServerSocket
public class RedisServer {
    static byte[] bs = new byte[1024];
    static ArrayList<Socket> socketList = null;
    // BIO --> sun --->NIO
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(6379);
        while (true){

            /**
             * 2.accept是阻塞的方法,程序已经放弃了CPU
             */
            System.out.println("wait conn----------");
            Socket clientSocket = serverSocket.accept();
            System.out.println("conn sucess------");
            /**
             * 3.read方法是阻塞的方法
             */
            System.out.println("wait data---------");
            clientSocket.getInputStream().read(bs);
            /**
             * 4.如果此时,开启了两个客户端连接这个服务端。第二个是连接不上的,需要开启多线程实现。 BIO实现
             * 使用单线程实现并发。(因为有的仅仅是连接,没有发送数据)
             */
            System.out.println("data success------------");
            System.out.println(new String(bs));
        }
    }
}

客户端1

public class SocketTest {
    public static void main(String[] args) throws Exception {
        Socket socket = new Socket("localhost", 6379);
//        socket.getOutputStream().write("test".getBytes());
//        socket.close();
        //测试read方法是阻塞的方法
        Scanner scanner = new Scanner(System.in);
        String next = scanner.next();
        socket.getOutputStream().write(next.getBytes());
        socket.close();
    }
}

客户端2

public class SocketTest {
    public static void main(String[] args) throws Exception {
        Socket socket = new Socket("localhost", 6379);
//        socket.getOutputStream().write("test".getBytes());
//        socket.close();
        //测试read方法是阻塞的方法
        Scanner scanner = new Scanner(System.in);
        String next = scanner.next();
        socket.getOutputStream().write(next.getBytes());
        socket.close();
    }
}

BIO实现的服务端

public class BIOServer {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(6379);
        System.out.println("new ServerSocket(8080)");
        while (true) {
            final Socket clientSocket = serverSocket.accept();
            System.out.println("cclient: " + clientSocket.getPort());
            //没来一个连接就创建一个线程,无论这个连接是否会有io交互,实现空轮训。
            new Thread(
                    () -> {
                        try {
                            InputStream in = clientSocket.getInputStream();
                            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
                            while (true) {
                                System.out.println(reader.readLine());
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
            ).start();
        }
    }
}

NIO多路复用服务端实现

public class NIORedis {

    static ByteBuffer byteBuffer = ByteBuffer.allocate(512);
    static ArrayList<SocketChannel> socketList = new ArrayList<>();
    public static void main(String[] args) throws Exception {
        //ServerSocket serverSocket = new ServerSocket(6379);
        //NIO设计理念
        ServerSocketChannel serverSocket = ServerSocketChannel.open();
        InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 6379);
        serverSocket.bind(socketAddress);
        serverSocket.configureBlocking(false);//将accept方法设置成非阻塞
        while (true){
            //1.进来先判断我所有的连接,是不是有数据发送过来了,如果有就打印出来
            for(SocketChannel socketChannel : socketList){
                int read = socketChannel.read(byteBuffer);
                if(read > 0){
                    System.out.println("read------- " + read);
                    byteBuffer.flip();
                    byte[] bs = new byte[read];
                    byteBuffer.get(bs);
                    String content = new String(bs);
                    System.out.println(content);
                    byteBuffer.flip();
                }
            }
            //2.是不是有新的连接加入进来
            SocketChannel clientSocket = serverSocket.accept();
            if(clientSocket != null){
                System.out.println("conn success------------");
                clientSocket.configureBlocking(false);
                socketList.add(clientSocket);
                System.out.println("socketlist size == " + socketList.size());
            }
        }
    }
}

NIO多路复用的测试

 测试:
 1)启动NIORedis.main
 2) 运行 SocketTest.main 模拟一个客户端连接redis
 3) 运行 SocketTest1.main 模拟另一个客户端连接redis
  输出结果:
      conn success------------
       socketlist size == 1
       conn success------------
       socketlist size == 2
 结论:
  1)都能连接
  2) 都能发送数据
 存在的问题:
  1)空循环太多,所有的连接都循环。
  2)for循环代码即java运行在JVM中,JVM运行在os kernel中。
  3)如果可以吧for循环交替给os kernel去执行,效率会很高。

操作系统的select和epollo解决NIO的问题

1)操作系统中有一个select函数。
    int  select(int ndfs,fd_set*readfds,fd_set*writefds,fd_set*exceptefds,struct timeval*timeout)
    这个参数ndfs:等于客户端的连接数+ 原始的标准输入,标准输出,错误,监听,绑定等
	socketList-->在jvm中作为一个入参,调用操作系统的select函数,会感知到哪些客户端有io流传递进来。
   		 A)read流  B)write流
	select(1w)--》也是全轮循。避免不了空轮循,所以select的性能也比较低。
	会轮循,找到感知哪些socket右io流,就返回一个set集合。
2)epollo对比:
    epollo_wait 可以自动感知io流的连接,从而只针对有io流的操作进行计算或者执行,不用全轮训,效率高。
JAVA线程与操作系统调用关系:

线程追踪

 strace -ff -o ./test/james TestServer.java        (BIOServer.java编译的)
   strace:追踪一个程序启动内部有多少线程,每个线程对操作系统内核调用了什么事情
   ff: 主线程和子线程fork
   -o: 把结果输出
   ./test/james: 输出目录
   TestServer: 哪个程序

BIOServer.java

public class BIOServer {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(6379);
        System.out.println("new ServerSocket(8080)");
        
        while (true) {
            final Socket clientSocket = serverSocket.accept();
            System.out.println("cclient: " + clientSocket.getPort());
            new Thread(
                    () -> {
                        try {
                            InputStream in = clientSocket.getInputStream();
                            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
                            while (true) {
                                System.out.println(reader.readLine());
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
            ).start();
        }
    }
}

jps

21015 TestServer
cd /proc/21015/fd 得到:
	0 -> /dev/pts/1
	1 -> /dev/pts/1
	2 -> /dev/pts/1
	3 -> /xxx/jdk1.8/jre/lib/rt.jar
	4 -> socket:[27385942]
	5 -> socket:[12238532]   这个就是监听了tcp的端口8088

strace结果:

cd /usr/local/c/test
	james.21015   #主线程
	james.21016   #main方法中主方法的线程
	james.21017
	james.21018
	james.21019
	james.21020
	james.21021
	james.21022
	james.21023
	james.21024
此时还在监听,没有创建new Thread(),否则应该是11个线程。

task结果:

cd /proc/20215/task   #每个线程对应的文件
	21015    # clone了一个线程=21016  新来的线程都是clone的
	21016   
	21017
	21018
	21019
	21020
	21021
	21022
	21023
redis多线程与单线程
redis6.0之前 单线程

多个客户端连接redis。其实是与操作系统kernel创建socket连接。kernel通过epollo能感知socket1和socket2有io数据发送过来,就会通知redis去对相关的连接操作。

真正计算操作的是一个单线程。worker线程,每一个操作,例如socket1中的set xx=>都对应了3个步骤: read io–>计算–>write io。一个线程串行执行后再去计算向下一个操作,get xx。
在这里插入图片描述

redis6.0之后 多线程

除了worker线程外,还有io线程(对于连接中有操作的连接都会开辟io线程)。

其中io线程负责: read io,write io

worker线程负责:计算。
在这里插入图片描述

Redis的key设计原则:

1)key短小:因为key也会存储到内存中。

2)唯一不重复

3)功能 业务 表明 作为开头

String类型
基础命令

1)最大不能超过512M

2)设置命令:

​ set age 23 ex 10 //10s后过期

​ setnx name test //不存在name时候,返回1设置成功,存在返回0失败。用途:实现分布式锁。

​ mset country china city beijing //批量设置,原子性的。减少和redis的连接,提升效率

订单系统:

​ 下订单–>生成一个订单号(唯一id):自增账号。使用redis的自增。但是这种方式性能比较低,redis的并发(性能)可以达到10w连接处理,假设有10个订单挺尸需要得到一个自增主键,就会极大降低redis的使用性能。

​ 解决:一次性从redis获取1000个自增账号,并且把这1000个账号缓存起来,然后使用。

Session共享

多个tomcat实现session共享,实现单点登录。

Hash类型
List类型

栈: 先进后出。lpush+lpop

队列:先进先出。lpush+rpop

阻塞队列:blocking-queue 如果队列不为空,就是和普通队列一样。如果队列为空,会阻塞在这里,等到有了数据就使用。
在这里插入图片描述

Set集合

可以用于抽奖

spop active:001 2 对key=active:001 中的元素抽取2个值。

可以用于用户–发送多条消息。每条消息–多个用户点赞。

Zset有序集合

每个元素绑定一个分值,用于排序。

Redis源码

redis是c语言开发的。

单节点redis的库有16个。分布式集群只有一个数据库。

./redis-server redis.conf

main函数:

​ 重置参数,解析redis.conf,设置参数。

initServer():

​ Redis的进程是操作系统给的。

​ 客户端的连接信息,放在clients中,就是一个list空间。

redis数据恢复:

​ Aop开关打开没有,打开了先恢复。

​ RDB恢复

Redis应用场景

涉及到支付金钱,一定不能用redis锁。会涉及到数据丢失的问题。为什么?

​ 一般都是主从架构,从节点是不会工作的,等主节点挂了从节点就会工作。

​ 用户1一把锁。往主节点redis中设置了lock,并且会吧这个lock同步到从节点中。但是在同步的过程中,主节点挂了,用户B往从节点中请求,就能获取到这个锁。造成不安全。

Redis锁:

​ 4个用户,买票100张。

​ A用户往redis中先插入一个key,如果能插入成功,就可以去买票,100-1=99,买完之后吧这个key清除.此时B用户往redis中插入同样一个key,不能成功,需要等待A完成之后才行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值