Redis

关系型数据库与非关系型数据库优劣势

参考王晓斌的专栏

关系型数据库(SQLite、Oracle、mysql)

关系型数据库,是指采用了关系模型来组织数据的数据库。

简单来说,关系模型指的就是二维表格模型,而一个关系型数据库就是由二维表及其之间的联系所组成的一个数据组织。类似于Excel表格

关系模型中常用的概念:

  • 关系:可以理解为一张二维表,每个关系都具有一个关系名,就是通常说的表名
  • 元组:可以理解为二维表中的一行,在数据库中经常被称为记录
  • 属性:可以理解为二维表中的一列,在数据库中经常被称为字段
  • 域:属性的取值范围,也就是数据库中某一列的取值限制
  • 关键字:一组可以唯一标识元组的属性,数据库中常称为主键,由一个或多个列组成
  • 关系模式:指对关系的描述。其格式为:关系名(属性1,属性2, … … ,属性N),在数据库中成为表结构

关系型数据库优点

  • 容易理解:二维表结构是非常贴近逻辑世界的一个概念,关系模型相对网状、层次等其他模型来说更容易理解
  • 使用方便:通用的SQL语言使得操作关系型数据库非常方便;
  • 易于维护:丰富的完整性(实体完整性、参照完整性和用户定义的完整性)大大减低了数据冗余和数据不一致的概率;
  • 支持SQL,可用于复杂的查询。

关系型数据库缺点

  • 高并发读写需求:网站的用户并发性非常高,往往达到每秒上万次读写请求,对于传统关系型数据库来说,硬盘I/O是一个很大的瓶颈
  • 海量数据的高效率读写:网站每天产生的数据量是巨大的,对于关系型数据库来说,在一张包含海量数据的表中查询,效率是非常低的
  • 高扩展性和可用性:在基于web的结构当中,数据库是最难进行横向扩展的,当一个应用系统的用户量和访问量与日俱增的时候,数据库却没有办法像web server和app server那样简单的通过添加更多的硬件和服务节点来扩展性能和负载能力。对于很多需要提供24小时不间断服务的网站来说,对数据库系统进行升级和扩展是非常痛苦的事情,往往需要停机维护和数据迁移。

需求与性能

对于很多网站来说,关系型数据库的很多特性不再需要:

  • 事务一致性:关系型数据库在对事物一致性的维护中有很大的开销,而现在很多web2.0系统对事物的读写一致性都不高
  • 读写实时性:对关系数据库来说,插入一条数据之后立刻查询,是肯定可以读出这条数据的,但是对于很多web应用来说,并不要求这么高的实时性,比如发一条消息之后,过几秒乃至十几秒之后才看到这条动态是完全可以接受的
  • 复杂SQL,特别是多表关联查询:任何大数据量的web系统,都非常忌讳多个大表的关联查询,以及复杂的数据分析类型的复杂SQL报表查询,特别是SNS类型的网站,从需求以及产品阶级角度,就避免了这种情况的产生。往往更多的只是单表的主键查询,以及单表的简单条件分页查询,SQL的功能极大的弱化了

在关系型数据库中,导致性能欠佳的最主要原因是多表的关联查询,以及复杂的数据分析类型的复杂SQL报表查询。为了保证数据库的ACID特性,我们必须尽量按照其要求的范式进行设计,关系型数据库中的表都是存储一个格式化的数据结构。每个元组字段的组成都是一样,即使不是每个元组都需要所有的字段,但数据库会为每个元组分配所有的字段,这样的结构可以便于标语表之间进行链接等操作,但从另一个角度来说它也是关系型数据库性能瓶颈的一个因素。

:数据库事务必须具备ACID特性,ACID是Atomic原子性,Consistency一致性,Isolation隔离性,Durability持久性。

非关系型数据库(MongoDb、redis、HBase)

NoSQL一词首先是Carlo Strozzi在1998年提出来的,指的是他开发的一个没有SQL功能,轻量级的,开源的关系型数据库。这个定义跟我们现在对NoSQL的定义有很大的区别,它确确实实字如其名,指的就是“没有SQL”的数据库。但是NoSQL的发展慢慢偏离了初衷,我们要的不是“no sql”,而是“no relational”,也就是我们现在常说的非关系型数据库了。

2009年初,Johan Oskarsson举办了一场关于开源分布式数据库的讨论,Eric Evans在这次讨论中再次提出了NoSQL一词,用于指代那些非关系型的,分布式的,且一般不保证遵循ACID原则的数据存储系统。Eric Evans使用NoSQL这个词,并不是因为字面上的“没有SQL”的意思,他只是觉得很多经典的关系型数据库名字都叫“SQL”,所以为了表示跟这些关系型数据库在定位上的截然不同,就是用了“NoSQL“一词。

非关系型数据库提出另一种理念,例如,以键值对存储,且结构不固定,每一个元组可以有不一样的字段,每个元组可以根据需要增加一些自己的键值对,这样就不会局限于固定的结构,可以减少一些时间和空间的开销。使用这种方式,用户可以根据需要去添加自己需要的字段,这样,为了获取用户的不同信息,不需要像关系型数据库中,要对多表进行关联查询。仅需要根据id取出相应的value就可以完成查询。但非关系型数据库由于很少的约束,他也不能够提供像SQL所提供的where这种对于字段属性值情况的查询。并且难以体现设计的完整性。他只适合存储一些较为简单的数据,对于需要进行较复杂查询的数据,SQL数据库显的更为合适。

非关系型数据库的优点

  • 无需经过sql层的解析,读写性能很高;
  • 基于键值对,数据没有耦合性,容易扩展;
  • 存储数据的格式:nosql的存储格式是key,value形式、文档形式、图片形式等等,文档形式、图片形式等等,而关系型数据库则只支持基础类型。

非关系型数据库的缺点

  • 不提供sql支持,学习和使用成本较高;
  • 无事务处理,附加功能bi和报表等支持也不好;

非关系型数据库分类

  • 面向高性能并发读写的key-value数据库:key-value数据库的主要特点即使具有极高的并发读写性能,Redis,Tokyo Cabinet,Flare就是这类的代表
  • 面向海量数据访问的面向文档数据库:这类数据库的特点是,可以在海量的数据中快速的查询数据,典型代表为MongoDB以及CouchDB
  • 面向可扩展性的分布式数据库:这类数据库想解决的问题就是传统数据库存在可扩展性上的缺陷,这类数据库可以适应数据量的增加以及数据结构的变化

NoSql概述

什么是NoSQL

NoSQL(NoSQL= Not Only SQL),意指”不仅仅是SQL”,是一项全新的数据库理念,泛指非关系型数据库

NoSQL必要性

对于一些网页应用来说,关系型数据库的优势得不到体现,其具备的特性却导致了性能不如非关系型数据

  • 一致性:在网页应用中,尤其是SNS应用中,一致性却不是显得那么重要,用户A看到的内容和用户B看到同一用户C内容更新不一致是可以容忍的,或者说,两个人看到同一好友的数据更新的时间差那么几秒是可以容忍的,因此,关系型数据库的最大特点在这里已经无用武之地,起码不是那么重要了。
  • 固定的表结构:其扩展性极差,而在SNS中,系统的升级,功能的增加,往往意味着数据结构巨大变动,这一点关系型数据库也难以应付,需要新的结构化数据存储。

于是,非关系型数据库应运而生,由于不可能用一种数据结构化存储应付所有的新的需求,因此,非关系型数据库严格上不是一种数据库,应该是一种数据结构化存储方法的集合。

注:数据的持久存储,尤其是海量数据的持久存储,还是需要一种关系数据库。

Redis入门

Redis概念

Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库,它提供多种简直数据类型来适应不同场景下的存储需求。

支持的键值数据类型(值)

  1. 字符串类型
  2. 散列类型(Map)
  3. 列表类型(LinkList)
  4. 集合类型(HashSet)
  5. 有序集合类型(SortSet)

应用场景

  • 缓存(数据查询、短连接、新闻内容、商品内容)
  • 聊天室的在线好友列表
  • 任务队列
  • 应用排行榜
  • 网站访问统计
  • 数据过期处理
  • 分布式集群架构中的session分离

安装和使用

linux安装(centos)

  1. 安装redis编译的c环境,yum install gcc-c++
  2. 将redis-2.6.16.tar.gz上传到Linux系统中
  3. 解压到/usr/local下 tar -xvf redis-2.6.16.tar.gz -C /usr/local
    tar -xvf redis-2.6.16.tar.gz -C /usr/local
    
  4. 解压到/usr/local下 tar -xvf redis-2.6.16.tar.gz -C /usr/local
    tar -xvf redis-2.6.16.tar.gz -C /usr/local
    
  5. 在redis-2.6.16目录中 使用make PREFIX=/usr/local/redis install命令安装redis到/usr/local/redis中
    make PREFIX=/usr/local/redis install
    
  6. 拷贝redis-2.6.16中的redis.conf到安装目录redis中
  7. 启动redis 在bin下执行命令redis-server redis.conf
  8. 如需远程连接redis,需配置redis端口6379在linux防火墙中开发
    /sbin/iptables -I INPUT -p tcp --dport 6379 -j ACCEPT /etc/rc.d/init.d/iptables save
    
  9. 启动后看到如下欢迎页面,但此窗口不能关闭,窗口关闭就认为redis也关闭了(类似Tomcat通过bin下的startup.bat的方式)
  10. 解决方案:可以通过修改配置文件配置redis后台启动,即服务器启动了但不会创建控制台窗口,将redis.conf文件中的daemonize从false修改成true表示后台启动使用命令查看6379端口是否启动
    ps -ef | grep redis
    

ubantu安装(阿里云)

  1. 下载
    sudo wget http://download.redis.io/releases/redis-3.2.6.tar.gz
    
  2. 将下载的文件解压到/usr/local/redis
    mkdir /usr/local/redis
    tar -zxvf redis-2.6.16.tar.gz /usr/local/redis
    
  3. 进入解压后的目录并且通过gcc编译安装
    make 
    make PREFIX=/usr/local/redis install
    
  4. 进入到/usr/local/bin 目录下发现多了如下几个文件,并且执行命令,查看版本信息,出现下面信息则安装成功
    root@Fy:/etc/redis/data# cd /usr/local/bin
    root@Fy:/usr/local/bin# ls 
    redis-check-rdb  redis-server redis-benchmark  redis-cli redis-check-aof  redis-sentinel
    root@Fy:/usr/local/bin# redis-server -v
    Redis server v=3.2.6 sha=00000000:0 malloc=jemalloc-4.0.3 bits=64 build=7ee11011f83a265b
    
  5. 设置配置信息
    • 创建目录:
      cd /etc
      mkdir redis
      cd /var
      mkdir redis
      cd redis
      mkdir data log run
      
    • 配置conf文件,拷贝安装目录下的redis.conf文件到/etc/redis目录下
      cp /usr/local/redis/redis-3.2.6/redis.conf /etc/redis/
      
    • 修改配置信息
      vim /etc/redis/redis.conf
      
    • 修改pid目录为新建目录
      # If a pid file is specified, Redis writes it where specified at startup
      # and removes it at exit.
      #
      # When the server runs non daemonized, no pid file is created if none is
      # specified in the configuration. When the server is daemonized, the pid file
      # is used even if not specified, defaulting to "/var/run/redis.pid".
      #
      # Creating a pid file is best effort: if Redis is not able to create it
      # nothing bad happens, the server will start and run normally.
      pidfile /var/redis/run/redis.pid
      
    • 修改dump目录为新建目录
      # The working directory.
      #
      # The DB will be written inside this directory, with the filename specified
      # above using the 'dbfilename' configuration directive.
      #
      # The Append Only File will also be created inside this directory.
      #
      # Note that you must specify a directory here, not a file name.
      dir /var/redis/data
      
    • 修改log存储目录为新建目录
      # Specify the log file name. Also the empty string can be used to force
      # Redis to log on the standard output. Note that if you use standard
      # output for logging but daemonize, logs will be sent to /dev/null
      logfile /var/redis/log/redis.log
      
    • 持久化
      appendonly yes
      appendfilename "appendonly.aof"  
      
    • redis后台启动
      将redis.conf文件中的daemonize从no修改成yes表示后台启动
      
  6. 启动redis,执行命令
    redis-server /etc/redis/redis.conf
    
  7. 查看进程,如出现如下显示则表示redis启动成功
    root@Fy:~# ps -ef|grep redis
    root     24740     1  0 17:40 ?        00:00:04 redis-server 127.0.0.1:6379
    root     24986 24620  0 18:45 pts/0    00:00:00 vim redis.conf
    root     25062 25001  0 19:12 pts/1    00:00:00 grep --color=auto redis
    
  8. 终端操作redis,然后执行命令ping,若输出为pong,则证明服务成功启动。
    redis-cli -p 6379
    
  9. 相应命令
  10. 开放端口,在阿里云安全组设置端口开放,重启电脑,使用下面指令查看端口开放情况,第三列为0.0.0.0:6379那么就是端口开放
    netstat -nltp
    
  11. 配置Redis访问密码,在配置文件中requirepass 参数设置为自己需要的密码

Java操作Redis

Jedis介绍

Jedis = Java + redis用于使用Java代码对redis进行操作

导入jar包

分别是Jedis以及连接池jar包

Jedis简单使用

  1. 建立连接,并设置密码
    //           1.获得连接对象
    Jedis jedis = new Jedis("127.0.0.1",6379);
    jedis.auth("pwd");
    
  2. 存储数据
    //        2.存储数据
    jedis.set("k2","bbb");
    
  3. 获取数据
    //            3.获取数据
    String k2 = jedis.get("k2")
    

没有配置完善可能出现的问题:

  1. 没有开放端口,通过命令查询端口开放情况
    netstat -nltp
    
  2. 在配置文件中没有设置密码
  3. 关闭防火墙

Jedis常见Api

使用连接池JedisPool

构造方法

JedisPool(org.apache.commons.pool.impl.GenericObjectPool.Config poolConfig, String host) 
           
JedisPool(org.apache.commons.pool.impl.GenericObjectPool.Config poolConfig, String host, int port) 
           
JedisPool(org.apache.commons.pool.impl.GenericObjectPool.Config poolConfig, String host, int port, int timeout) 
           
JedisPool(org.apache.commons.pool.impl.GenericObjectPool.Config poolConfig, String host, int port, int timeout, String password) 
           
JedisPool(org.apache.commons.pool.impl.GenericObjectPool.Config poolConfig, String host, int port, int timeout, String password, int database) 
           
JedisPool(String host) 
           
JedisPool(String host, int port) 

redis.clients.jedis.JedisPoolConfig extend org.apache.commons.pool.impl.GenericObjectPool.Config poolConfig,通过设置
JedisPoolConfig对象可以完成对连接池配置

连接池配置

  JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
//连接超时时是否阻塞,false时报异常,ture阻塞直到超时, 默认true
jedisPoolConfig.setBlockWhenExhausted(true);

//逐出策略(默认DefaultEvictionPolicy(当连接超过最大空闲时间,或连接数超过最大空闲连接数))
jedisPoolConfig.setEvictionPolicyClassName("org.apache.commons.pool2.impl.DefaultEvictionPolicy");

//最大空闲连接数, 默认8个
jedisPoolConfig.setMaxIdle(8);

//最大连接数, 默认8个
jedisPoolConfig.setMaxTotal(8);

//获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间,  默认-1
jedisPoolConfig.setMaxWaitMillis(-1);

//逐出连接的最小空闲时间 默认1800000毫秒(30分钟)
jedisPoolConfig.setMinEvictableIdleTimeMillis(1800000);

//最小空闲连接数, 默认0
jedisPoolConfig.setMinIdle(0);

//每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3
jedisPoolConfig.setNumTestsPerEvictionRun(3);

//对象空闲多久后逐出, 当空闲时间>该值 且 空闲连接>最大空闲数 时直接逐出,不再根据MinEvictableIdleTimeMillis判断  (默认逐出策略)   
jedisPoolConfig.setSoftMinEvictableIdleTimeMillis(1800000);

// 获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间,
// 默认-1
jedisPoolConfig.setMaxWaitMillis(100);

//对拿到的connection进行validateObject校验
jedisPoolConfig.setTestOnBorrow(true);

//在进行returnObject对返回的connection进行validateObject校验
jedisPoolConfig.setTestOnReturn(true);

//定时对线程池中空闲的链接进行validateObject校验
jedisPoolConfig.setTestWhileIdle(true);

使用连接池对redis数据库进行操作

 @Test
    public void jedisPoolTest(){

        //        1.获取连接池对象
        JedisPoolConfig poolConfig =new JedisPoolConfig();
		
        //        2进行配置文件设置

        //        设置pool最大闲置个数
        poolConfig.setMaxIdle(10);

        //        设置pool最小闲置个数
        poolConfig.setMinIdle(5);

        //        设置最大连接数
        poolConfig.setMaxTotal(100);

        //        3.获得连接池
        JedisPool jedisPool = new JedisPool(poolConfig,"127.0.0.1",6379,3000,"pwd");

        //        4.获得连接池的连接对象jedis,并且执行
        Jedis jedis = jedisPool.getResource();
        System.out.println(jedis.get("k2"));
    }

建立连接池工具类

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 * 作为redis连接池工具类,可以直接从工具类获取Jedis的resource
 */
public class JedisPoolUtils {

    //    1.定义一个全局连接池
    private static JedisPool jedisPool;

    //    配置连接池
    static {
        JedisPoolConfig poolConfig = new JedisPoolConfig();

        //        2.从配置文件获取需要字段
        Properties properties = getProperties();

        //        3.设置pool最小闲置个数
        poolConfig.setMinIdle(Integer.parseInt(properties.getProperty("MinIdle")));

        //        4.设置pool最大闲置个数
        poolConfig.setMaxIdle(Integer.parseInt(properties.getProperty("MaxIdle")));

        //        5.设置最大连接数
        poolConfig.setMaxTotal(Integer.parseInt(properties.getProperty("MaxTotal")));

        //        6.将配置设置到连接池
        jedisPool = new JedisPool(
                poolConfig,
                properties.getProperty("URL"),
                Integer.parseInt(properties.getProperty("PORT")),
                Integer.parseInt(properties.getProperty("TIMEOUT")),
                properties.getProperty("PWD")
        );
    }

    /**
     * 6.获取Jedis对象,操作redis
     * @return Jedis
     */
    public static Jedis getJedis() {
        return jedisPool.getResource();
    }

    /**
     * 2.获取配置文件中的内容,存储到properties中
     * @return properties
     */
    private static Properties getProperties() {
        Properties properties = new Properties();
        InputStream inputStream = JedisPoolUtils.class.getClassLoader().getResourceAsStream("com/company/config/Jedis.properties");
        try {
            properties.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return properties;
    }
}