NoSQL之Redis介绍

摘要:

1、关系型数据库&NoSQL
2、Redis部署模式
3、Redis底层协议分析

一、关系型数据库&Nosql

1、复杂的查询
在传统的关系型数据库中查询一个复杂的业务需要写很复杂的 sql 语句。
2、伸缩性
在传统的关系型数据库业务增大系统需要扩容只能是纵向的形式扩展操作性能也与遇到瓶颈。
3、传统数据库遵循 ACID 规则。而 Nosql 一般为分布式而分布式一般遵循 CAP 定理。
介绍:
NoSQL 称作 Not Only SQL 的缩写,是对不同于传统的关系型数据库的数据库管理系统的统称。

ACID规则
A (Atomicity) 原子性
C (Consistency) 一致性
I (Isolation) 独立性
D (Durability) 持久性

CAP定理
一致性(Consistency) (所有节点在同一时间具有相同的数据)
可用性(Availability) (保证每个请求不管成功或者失败都有响应)
分隔容忍(Partition tolerance) (系统中任意信息的丢失或失败不会影响系统的继续运作)

  • nosql 分类
名称 存储
redis 键值对(key-value)
Hbase 宽列(wide cloumn)
mongoDb 文档(document)
Neo4j 图(graph)

Redis介绍

介绍:Redis 是一个由 Salvatore Sanfilippo 写的 key-value 存储系统。
Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存亦可持
久化的日志型、Key-Value 数据库,并提供多种语言的 API。
它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Map), 列表
(list), 集合(sets) 和 有序集合(sorted sets)等类型。
Github 源码:https://github.com/antirez/redis
Redis 官网:https://redis.io/

总结:完全开源免费的、高性能的 key-value 数据库.支持数据持久化、支持多种数据结构存储。
适用场景:存储缓存、投票、会话 session、排行榜、计数器、发布订阅等

二、Redis部署模式

1、Redis单机版

Redis单机版

单机版三个问题:
1、内存容量有限 2、处理能力有限 3、无法高可用。

1、Redis多机版

Redis多机版

特性:1、复制(Replication)2、哨兵(Sentinel) 3、集群(Cluster)
Redis 多机版特性功能:
复制:扩展系统对于读的能力
哨兵:为服务器提供高可用特性,减少故障停机出现
集群:扩展内存容量,增加机器,提高性能读写能力和存储以及提供高可用特性

  • 复制
    Redis 的复制(replication)功能允许用户根据一个 Redis 服务器来创建任意多个该服务器
    的复制品,其中被复制的服务器为主服务器(master),而通过复制创建出来的服务器复
    制品则为从服务器(slave)。 只要主从服务器之间的网络连接正常,主从服务器两者会具
    有相同的数据,主服务器就会一直将发生在自己身上的数据更新同步 给从服务器,从而一
    直保
    复制

    特点:
    1、master/slave 角色
    2、master/slave 数据相同
    3、降低 master 读压力在转交从库
    问题:无法保证高可用, 没有解决 master 写的压力
  • 哨兵
    Redis sentinel 是一个分布式系统中监控 redis 主从服务器,并在主服务器下线时自动进行故障转移。其中三个特性:
    监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
    提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
    自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel会开始一次自动故障迁移操作。
    哨兵

    特点:
    1、保证高可用
    2、监控各个节点
    3、自动故障迁移
    缺点:主从模式,切换需要时间丢数据, 没有解决 master 写的压力
  • 集群(proxy 型)
    proxy型

    Twemproxy 是一个 Twitter 开源的一个 redis 和 memcache 快速/轻量级代理服务器;
    Twemproxy 是一个快速的单线程代理程序,支持 Memcached ASCII 协议和 redis 协议。
    特点:1、多种 hash 算法:MD5、CRC16、CRC32、CRC32a、hsieh、murmur、Jenkins
    2、支持失败节点自动删除
    3、后端 Sharding 分片逻辑对业务透明,业务方的读写方式和操作单个 Redis 一致

缺点:增加了新的 proxy,需要维护其高可用。 failover 逻辑需要自己实现,其本身不能支持故障的自动转移
可扩展性差,进行扩缩容都需要手动干预。

  • 集群(直连型)
    直连型

    特点:
    1、无中心架构(不存在哪个节点影响性能瓶颈),少了 proxy 层。
    2、数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布。
    3、可扩展性,可线性扩展到 1000 个节点,节点可动态添加或删除。
    4、高可用性,部分节点不可用时,集群仍可用。通过增加 Slave 做备份数据副本。
    5、实现故障自动 failover,节点之间通过 gossip 协议交换状态信息,用投票机制完成 Slave到 Master 的角色提升。
    缺点:
    1、资源隔离性较差,容易出现相互影响的情况。
    2、数据通过异步复制,不保证数据的强一致性。

三、分析Redis底层协议

  • Redis本质上也是一种键值数据库,但它保持键值数据库简单快捷特点的同时,又吸收了部分关系型数据库的优点。
    从而使它的位置处于关系型数据库和键值数据库之间。Redis不仅能保存String类型的数据,还能保存List类型(有序)和Set类型(无序)的数据,而且还能完成排序(Sort)等高级功能,在实现INCR、SETNX等功能的时候,保证了其操作的原子性,初次之外,还支持主从复制等功能。

  • Redis是用C语言开发的,客户端支持的语言非常多,如下:


    客户端

    Java调用的客户端也有很多,如下:


    Java客户端

最常用的是Jedis,也是官方推荐的,从github也可以找答案(redis集群)https://github.com/xetorthio/jedis

So what can I do with Jedis?

1、如何快速开始?

How do I use it?

2、分析Redis本质

Redis的本质

4、源码的开发

  • 连接类
package com.nuih.study.connection;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

/**
 * @version: V1.0
 * @author: hejianhui
 * @className: Connection
 * @packageName: com.nuih.study.connection
 * @description: 连接类
 * @data: 2018-11-30 22:50
 **/
public class Connection {

    private Socket socket;
    private  String host;
    private int port;
    private OutputStream outputStream;
    private InputStream inputStream;

    public Connection(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public Socket getSocket() {
        return socket;
    }

    public void setSocket(Socket socket) {
        this.socket = socket;
    }

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public OutputStream getOutputStream() {
        return outputStream;
    }

    public void setOutputStream(OutputStream outputStream) {
        this.outputStream = outputStream;
    }

    public InputStream getInputStream() {
        return inputStream;
    }

    public void setInputStream(InputStream inputStream) {
        this.inputStream = inputStream;
    }

    /**
     * @author:  hejianhui
     * @methodsName: connect
     * @description: 创建连接
     */
    public void connect(){
        try {
            socket = new Socket(host, port);
            inputStream = socket.getInputStream();
            outputStream = socket.getOutputStream();
        }catch (IOException e){
            e.printStackTrace();
        }
    }

    /**
     * @author:  hejianhui
     * @methodsName: getStatusCodeReply
     * @description: 获取回复
     */
    public String getStatusCodeReply(){
        byte b[] = new byte[1024];
        try {
            socket.getInputStream().read(b);
        }catch (IOException e){
            e.printStackTrace();
        }
        return new String(b);
    }
  }
  • 客户端代码
package com.nuih.study.client;

import com.nuih.study.connection.Connection;
import com.nuih.study.protocol.Protocol;

public class Client {

    Connection connection;

    public Client(String host, int port){
        connection = new Connection(host,port);
    }

    public String set(final String key, String value){
        // 1、建立连接
        // 2、发送命令请求
        // 3、获取结果
        connection.connect();
        Protocol.sendCommand(connection.getOutputStream(), Protocol.Command.SET, SafeEncoder.encode(key), SafeEncoder.encode(value));
        return connection.getStatusCodeReply();
    }

    public String get(final String key){
        // 1、建立连接
        // 2、发送命令请求
        // 3、获取结果
        connection.connect();
        Protocol.sendCommand(connection.getOutputStream(), Protocol.Command.GET, SafeEncoder.encode(key));
        return connection.getStatusCodeReply();
    }
}

package com.nuih.study.client;

public class SafeEncoder {

    public static byte[] encode(String str){
        return str.getBytes();
    }
}
  • 通过抓包方式模拟一个redis服务端
package com.nuih.study.hack;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @version: V1.0
 * @author: hejianhui
 * @className: RedisServer
 * @packageName: com.nuih.study.hack
 * @description: RedisServer
 * @data: 2018-11-30 22:50
 **/
public class RedisServer {

    public static void main(String[] args){
        try{

            ServerSocket serverSocket = new ServerSocket(6379);
            Socket accept = serverSocket.accept();
            byte[] b = new byte[1024];
            accept.getInputStream().read(b);
            System.out.println(new String(b));

        }catch (IOException e){
            e.printStackTrace();
        }
    }
}
  • RESP消息协议层处理代码
package com.nuih.study.protocol;

import java.io.IOException;
import java.io.OutputStream;

/**
 * @version: V1.0
 * @author: hejianhui
 * @className: Protocol
 * @packageName: com.nuih.study.protocol
 * @description: Protocol
 * @data: 2018-11-30 22:50
 **/
public class Protocol {

    public static final String DOLLAR_BYTE = "$";
    public static final String ASTERISK_BYTE = "*";
    public static final String BLANK_BYTE = "\r\n";

    public  static void  sendCommand(OutputStream outputStream, Command command, byte[]... args){

        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append(ASTERISK_BYTE).append(args.length+1).append(BLANK_BYTE);
        stringBuffer.append(DOLLAR_BYTE).append(command.name().length()).append(BLANK_BYTE);
        stringBuffer.append(command).append(BLANK_BYTE);

        for(byte[] arg : args){
            stringBuffer.append(DOLLAR_BYTE).append(arg.length).append(BLANK_BYTE);
        }

        try {
            outputStream.write(stringBuffer.toString().getBytes());
        }catch (IOException e){
            e.printStackTrace();
        }
    }

    /**
     * 操作命令
     */
    public static enum Command{
        SET,GET
    }
}
  • 模拟jedis访问
package com.nuih.study;

import com.nuih.study.client.Client;
import redis.clients.jedis.Jedis;

public class App {

    public static void main(String[] args){
        //Jedis jedis = new Jedis("localhost", 6379);

        Client jedis = new Client("localhost", 6379);
        jedis.set("name", "hejianhui");
        String value = jedis.get("name");
        System.out.println(value);
    }
}

抓包数据截图


image.png

抓包数据说明如下:

*3 数组3

$3 字符串3
SET

$4 字符串4
name

$9 字符串9
hejianhui

Redis官方协议说明如图:https://redis.io/topics/protocol

协议说明
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容