Redis

什么是Redis?Redis是一个基于内存的非关系型数据库,简单来说就是一个可持久化的高速缓存。

常用场景:

  1. 缓存(数据查询,端链接,新闻内容,商品内容等等)--使用最多
  2. 聊天室的在线好友列表
  3. 任务队列(秒杀,抢购,12306等等)
  4. 应用排行榜
  5. 网站访问统计
  6. 数据过期处理(可以精确到毫秒)
  7. 分布式集群架构中的session分离

高并发的三种优化思路:写入内存而不是写入硬盘、异步处理而不是同步处理、分布式处理

5种数据结构

Redis启动后自动创建16个数据库,默认选中ID为0的数据库,可以使用select id来选择数据库。

Redis是Key-Value类型的存储系统,其中key值是字符串类型,value值支持5种常用的数据类型:

  1. 字符串(String)
  2. 哈希(hash)
  3. 字符串列表(list)
  4. 字符串集合(set)
  5. 有序字符串集合(sorted set)

String字符串类型

字符串类型是Redis中最为基础的数据存储类型,字符串类型的Value最多可以容纳的数据长度是512M,常用命令:

功能命令说明
设置(修改)值set key value该key存在则进行覆盖操作,该操作总是返回"OK"。
获取值get key获取该key关联的字符串value值。如果value值不是字符串
会报错,如果key不存在则返回nil。
删除数据del key key2 key3...根据指定的key删除对应的数据。可一次删除多个
批量设置值mset key1 value1 key2 value2同时设置多对键值对
批量取值mget key1 key2同时获取过个key值对应的value值
设置值(返回原来的值)getset key value给指定的key设置value值,返回原来的值(如果原来没有值返回null)

Hash哈希类型

Redis中的Hash类型可以看成具有String Key和String Value的map容器。

功能命令说明
为指定的key设定field/valuehset key field value设置单个值(hset stu name zhangsan)
获取指定key的field对应的value值hget key field获取单个值 (hget stu name)
删除指定key的field对应的value值hdel key field field2 ...可以删除多个值(hdel stu name age)
为指定的key批量设置值hmset key field value field2 value2批量设置值
获取指定key的多个值hmget key field field2 field3 ...批量获取值
获取指定key的所有键值对hgetall key获取所有的键值对

List 队列

List类型是按照插入顺序排序的字符串链表,可保存重复数据。在插入时,如果该键不存在,Redis将为该键创建一个新的链表。如果链表中所有的元素均被移除,那么该键也将会被从数据库中删除。

功能命令说明
头部插入数据lpush key value1 value2...从list的头部开始插入数据
尾部插入数据rpush key value1 value2...从list的尾部开始插入数据
查看list中所有数据lrange key 0 -1索引0指的是列表中第一个元素,-1指列表中最后一个元素
查看list中指定索引区间的元素lrange key start endstart:开始索引,从0开始;end:结束索引;
查看指定索引的值lindex key indexindex:值在list中的索引
从list的头部弹出一个值lpop key获取并移除list头部的第一个值
从list的尾部弹出一个值rpop key获取并移除list头部的最后一个值

Set 无序集合

和List类型不同的是,Set集合中不允许出现重复的元素。

功能命令说明
设置值sadd key value value1 value2...
查看set中的所有值smembers key
移除并返回集合中的任意一个元素spop key
删除值【set集合中是元素删除完后set消失】srem key members member1 member2...
获取集合中的成员数scard key

SortedSet 有序集合

有序集合中,每个元素都带有score(权重),以此来对元素进行排序。它有三个元素:key、member和score。

功能命令说明
添加一个或多个成员zadd key score1 member1 score2 member2 ...
获取有序集合的成员数zcard key
通过索引区间返回有序集合成指定区间内的成员zrange key start stop [withscores]
通过索引区间返回有序集合成指定区间内的成员【顺序排列】zrange key start stop withscores
通过索引区间返回有序集合成指定区间内的成员【倒序排列】zrevrange key start stop withscores
移除有序集合中的一个或多个成员zrem key member member1 ...

通用命令

  • keys pattern获取与pattern匹配的key,*表示任意个字符,?表示一个字符
  • del key1 key2…删除key
  • exists key判断该key是否存在,1代表存在,0代表不存在
  • type key获取指定key的类型。该命令将以字符串的格式返回。 返回的字符串为string、list、set、hash,如果key不存在返回none

Redis的Java客户端Jedis

导入依赖(jedis.jar`common-pool2.jar`),这个是maven坐标:

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.1.0</version>
</dependency>

常用API:

方法描述
new Jedis(host, port)创建jedis对象,参数host是redis服务器地址,参数port是redis服务端口
set(key,value)设置字符串类型的数据
get(key)获得字符串类型的数据
hset(key,field,value)设置哈希类型的数据
hget(key,field)获得哈希类型的数据
lpush(key,values)设置列表类型的数据
lpop(key)列表左面弹栈
rpop(key)列表右面弹栈
del(key)删除指定的key

示例:


Jedis 示例

//1.创建redis核心对象:arg1-host      arg2-端口
Jedis jedis = new Jedis("localhost",6379);
//2.存值
jedis.set("name","Redis");
//3.取值
String name = jedis.get("name");
System.out.println(name);
//4.释放资源
jedis.close();

JedisPool

JedisPool有两个核心类:JedisPoolJedisPoolConfig,后者为连接池配置类。

示例:


JedisPool 示例

//1 获得连接池配置对象,设置配置项
JedisPoolConfig config = new JedisPoolConfig();
// 1.1 最大连接数
config.setMaxTotal(30);
// 1.2  最大空闲连接数
config.setMaxIdle(10);
//2 获得连接池
JedisPool jedisPool = new JedisPool(config, "localhost", 6379);
//3 获得核心对象
Jedis jedis = jedisPool.getResource();

//释放资源
if(jedis != null){
    jedis.close();
}
if(jedisPool != null){
    jedisPool.close();
}

Jedis简单工具类示例:


JedisUtil

package com.bilibili.query_students.util;

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

import java.util.ResourceBundle;

public class JedisUtil {
    private static JedisPool jedisPool;
    private static int maxTotal;
    private static int maxIdle;
    private static String host;
    private static int port;

    static {
        //ResourceBundle类和Properties类相似,一般用于解决地域性问题
        ResourceBundle jedisPorperties = ResourceBundle.getBundle("jedis");
        maxTotal = Integer.parseInt(jedisPorperties.getString("maxTotal"));
        maxIdle = Integer.parseInt(jedisPorperties.getString("maxIdle"));
        host = jedisPorperties.getString("host");
        port = Integer.parseInt(jedisPorperties.getString("port"));
    }

    static {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxIdle(maxIdle);
        config.setMaxTotal(maxTotal);
        jedisPool = new JedisPool(config, host, port);
    }

    public static Jedis getJedis(){
        return jedisPool.getResource();
    }

    public static void release(Jedis jedis){
        if (jedis != null) {
            jedis.close();
        }
    }
}


Redis与Mysql实战对比

需求:使用ajax请求将所有的学员信息加载到页面的table中。

准备数据库:


新建表

CREATE TABLE `stu_info` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `student_name` varchar(255) DEFAULT NULL,
  `student_no` varchar(255) DEFAULT NULL,
  `sex` int(11) DEFAULT NULL COMMENT '1-男  2-女',
  `class_id` int(11) DEFAULT NULL,
  `test_id` int(11) DEFAULT NULL,
  `subject_no` int(11) DEFAULT NULL COMMENT '学科编号:1-java  2-php 3-python 4-UI 5-前端 6-其他',
  `password` varchar(255) DEFAULT NULL,
  `test_status` int(11) DEFAULT NULL COMMENT '考试状态:0-结束  1-未结束',
  `test_type` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8;

随便准备一些数据(准备了2000条):
数据

前端页面:


HTML代码

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- The above 3 meta tags *must* come test in the head; any other head content must come *after* these tags -->
    <meta name="description" content="">
    <meta name="author" content="">
    <link rel="stylesheet" href="../css/bootstrap.css">
    <title>Stu</title>
    <style>
        .stuTable{
            width: 70%;
            margin: 20px auto;
        }
    </style>
</head>
<body>
<div class="container">
<button onclick="" id="btn" class="btn btn-primary center-block">查询</button>
</div>
<table class="stuTable table table-bordered " id="stuTable" >
    <caption class="text-center h2">学员列表</caption>
    <tr>
        <th>ID</th>
        <th>姓名</th>
        <th>学号</th>
        <th>性别</th>
        <th>班级</th>
        <th>学科</th>
    </tr>
</table>
</body>
<script src="../js/jquery.js"></script>
<script src="/js/myjs/students.js"></script>
</html>

页面引入的Js代码:


Js代码

$('#btn').click(function () {
    $.get('/getStuListServlet', function (stuData) {
        //注意这里需要把字符串转化为JSON对象,然后调用JQuery的each方法
        $.each(JSON.parse(stuData), function (index, element) {
            element.sex = element.sex === '1' ? '男' : '女';
            element.class_id = '就业班';
            element.subject_no = 'java';
            $("<tr><td>" + element.id + "</td><td>" + element.student_name + "</td><td>" + element.student_no + "</td><td>" + element.sex + "</td><td>" + element.class_id + "</td><td>" + element.subject_no + "</td></tr>").appendTo($("#stuTable"));
        })
    });
});

导入的jar包(这个Demo没用Maven):
jar包

数据库连接池和Jedis的连接池配置文件:


配置文件

<c3p0-config>
  <!-- 使用默认的配置读取连接池对象 -->
  <default-config>
      <!--  1. 数据库的连接参数 -->
    <property name="driverClass">com.mysql.jdbc.Driver</property>
    <property name="jdbcUrl">jdbc:mysql://localhost:3306/forservlet</property>
    <property name="user">root</property>
    <property name="password">1234</property>
    
    <!-- 2. 连接池参数 -->
      <!--初始连接数-->
    <property name="initialPoolSize">5</property>
      <!--最大连接数-->
    <property name="maxPoolSize">10</property>
    <!--等待多久以后抛出异常-->
    <property name="checkoutTimeout">2000</property>
  </default-config>

    <!-- 命名配置 -->
  <named-config name="otherc3p0"> 
    <!--  连接参数 -->
    <property name="driverClass">com.mysql.jdbc.Driver</property>
    <property name="jdbcUrl">jdbc:mysql://localhost:3306/how2sql</property>
    <property name="user">root</property>
    <property name="password">root</property>
    
    <!-- 连接池参数 -->
    <property name="initialPoolSize">5</property>
    <property name="maxPoolSize">8</property>
    <property name="checkoutTimeout">1000</property>
  </named-config>
</c3p0-config>
host=127.0.0.1
port=6379
maxTotal=50
maxIdle=10

接着是Java代码:

JavaBean:Student类


实体类Student

package com.bilibili.query_students.bean;

public class Student {
    private Integer id;
    private String student_name;
    private String student_no;
    private Integer sex;
    private Integer class_id;
    private Integer subject_no;


    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getStudent_name() {
        return student_name;
    }

    public void setStudent_name(String student_name) {
        this.student_name = student_name;
    }

    public String getStudent_no() {
        return student_no;
    }

    public void setStudent_no(String student_no) {
        this.student_no = student_no;
    }

    public Integer getSex() {
        return sex;
    }

    public void setSex(Integer sex) {
        this.sex = sex;
    }

    public Integer getClass_id() {
        return class_id;
    }

    public void setClass_id(Integer class_id) {
        this.class_id = class_id;
    }

    public Integer getSubject_no() {
        return subject_no;
    }

    public void setSubject_no(Integer subject_no) {
        this.subject_no = subject_no;
    }
}

数据库和Jedis工具类:


工具类

数据库工具类

package com.bilibili.query_students.util;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;

public class DataSourceUtil {
    private static DataSource dataSource = new ComboPooledDataSource();

    public static DataSource getDataSource(){
        return dataSource;
    }

    public static JdbcTemplate getJdbcTemplate(){
        return new JdbcTemplate(dataSource);
    }
}

Jedis工具类:

package com.bilibili.query_students.util;

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

import java.util.ResourceBundle;

public class JedisUtil {
    private static JedisPool jedisPool;
    private static int maxTotal;
    private static int maxIdle;
    private static String host;
    private static int port;

    static {
        ResourceBundle jedisPorperties = ResourceBundle.getBundle("jedis");
        maxTotal = Integer.parseInt(jedisPorperties.getString("maxTotal"));
        maxIdle = Integer.parseInt(jedisPorperties.getString("maxIdle"));
        host = jedisPorperties.getString("host");
        port = Integer.parseInt(jedisPorperties.getString("port"));
    }

    static {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxIdle(maxIdle);
        config.setMaxTotal(maxTotal);
        jedisPool = new JedisPool(config, host, port);
    }

    public static Jedis getJedis(){
        return jedisPool.getResource();
    }

    public static void release(Jedis jedis){
        if (jedis != null) {
            jedis.close();
        }
    }
}

然后是三层架构:


后端

控制层:

package com.bilibili.query_students.web;

import com.bilibili.query_students.service.StudentService;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/getStuListServlet")
public class StuListServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        StudentService studentService = new StudentService();
        String stuJsonData = studentService.queryAllStudent();
        response.setContentType("text/plain;charset=utf-8");
        response.getWriter().print(stuJsonData);
    }
}

业务层:

package com.bilibili.query_students.service;

import com.alibaba.druid.support.json.JSONUtils;
import com.bilibili.query_students.bean.Student;
import com.bilibili.query_students.dao.StudentDao;
import com.bilibili.query_students.util.JedisUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import redis.clients.jedis.Jedis;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

@WebServlet(name = "StudentService")
public class StudentService extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }

    public String queryAllStudent() {
        Jedis jedis = JedisUtil.getJedis();
        long redisBegin = System.currentTimeMillis();
        String stuData = jedis.get("stuData");
        if (stuData != null) {
            long redisEnd = System.currentTimeMillis();
            System.out.println("redis : "+(redisEnd-redisBegin));
        }

        //Redis中没查询到则到MySQL中查询,并将结果添加到Redis中
        if (stuData == null) {
            StudentDao studentDao = new StudentDao();
            long start = System.currentTimeMillis();
            List<Student> list = studentDao.queryAll();
            long end = System.currentTimeMillis();
            System.out.println("sql : "+(end - start));
            try {
                stuData = new ObjectMapper().writeValueAsString(list);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            jedis.set("stuData", stuData);
        }
        JedisUtil.release(jedis);
        return stuData;
    }
}

持久层:

package com.bilibili.query_students.dao;

import com.bilibili.query_students.bean.Student;
import com.bilibili.query_students.util.DataSourceUtil;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.List;

public class StudentDao {
    public List<Student> queryAll(){
        JdbcTemplate jdbcTemplate = DataSourceUtil.getJdbcTemplate();
        String sql = "select * from stu_info";
        List<Student> stus = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Student.class));
        return stus;
    }
}


查询时间对比:

时间对比

第一次查询在SQL中查询,2000条花费74毫秒,后面的查询均在Redis中查,花费1毫秒。


小结:Redis非常适合作为高并发的缓存。

Redis持久化

redis 支持两种持久化方式,一种是 RDB(快照)也是默认方式,另一种是 Append-only file(缩写AOF)的方式。

RDB

快照是默认持久化方式,也就是将内存中的所有数据写入硬盘。该方式有三种保存方式:一种是直接在主进程使用save命令,此时会此命令阻塞主进程,Redis将不再接收请求,直到数据写入完毕。第二种是调用bgsave命令,此时会fork出一条子进程,主进程继续接受请求,子进程进行持久化操作,持久化完毕后自动退出,这种方式会消耗更多的内存,优点是不会阻塞主进程。还有一种是自动保存机制,可以设置在单位时间内若果达到多少条修改便会自动保存。

这是Redis持久化配置选项:

###快照持久化选项

# 在win上这三个初始设定不生效,不知道为什么,自己从新设置的却可以
save 900 1 #900 秒内如果超过 1 个 key 被修改,则发起快照保存
save 300 10 #300 秒内容如超过 10 个 key 被修改,则发起快照保存
save 60 10000 #60 秒内如果超过10000个key被修改,则发起快照保存
stop-write-on-bgsave-error ###创建快照失败后是否仍然继续执行写命令
rdbcompression yes ###是否对快照进行压缩
dbfilename dump。rdb ###快照命名
 
 
###AOF持久化选项
appendonly no ###是否进行AOF持久化
appendfaync everysec ###自动AOF间隔时长
no-appendfaync-on-rewrite no ###压缩时能否进行同步操作
# 重写需要同时满足下面的两个条件
auto-aof-rewrite-percentage 100 ###(文件增长率)相比上一次重写之后体积大于一倍(100%)执行压缩操作
auto-aof-rewrite-min-size 64mb ###体积大于64mb时执行压缩操作
 
 
dir ./ ###快照文件和AOF文件保存位置

RDB优缺点:
优点:

  1. 文件实现的数据快照,全量备份,便于数据的传输.比如我们需要把A服务器上的备份文件传输到B服务器上面,直接将rdb文件拷贝即可.
  2. 文件采用压缩的二进制文件,当重启服务时加载数据文件,比aof方式更快.

缺点:

  1. rbd采用加密的二进制格式存储文件,由于Redis各个版本之间的兼容性问题也导致rdb由版本兼容问题导致无法再其他的Redis版本中使用.
  2. 时效性差,容易造成数据的不完整性.因为rdb并不是实时备份,当某个时间段Redis服务出现异常,内存数据丢失,这段时间的数据是无法恢复的,因此易导致数据的丢失.

AOF

AOF的原理是以日志形式记录客户端的写入过程,然后再次启动的时候按照记录进行操作。

AOF有三种记录策略:

  1. appendfsync always //收到写命令就立即写入磁盘,最慢,但是保证完全的持久化
  2. appendfsync everysec //每秒钟写入磁盘一次,在性能和持久化方面做了很好的折中
  3. appendfsync no //完全依赖 os,性能最好,持久化没保证

以日志记录数据的方式缺点是多次写操作写入一个数据时AOF文件会记录每次操作而变得臃肿,并且最后一次操作之后前面的操作记录就失效了。因此Redis引入了重写机制,也就是将过期数据剔除,从而达到减小文件大小的作用。

AOF的优缺点
优点:

  1. 多种文件写入(fsync)策略.
  2. 数据实时保存,数据完整性强.即使丢失某些数据,制定好策略最多也是一秒内的数据丢失.
  3. 可读性强,由于使用的是文本协议格式来存储的数据,可有直接查看操作的命令,同时也可以手动改写命令.

缺点:

  • 文件体积过大,加载速度比rbd慢.由于aof记录的是redis操作的日志,一些无效的,可简化的操作也会被记录下来,造成aof文件过大.但该方式可以通过文件重写策略进行优化.

RDB与AOF两种方式对比:

对比RDBAOF
启动优先级
体积
恢复速度
数据安全性可能丢数据根据策略决定
轻重
兼容性二进制加密,不同版本可能会出现不兼容兼容性好,以文本形式记录命令,可直接查看,甚至直接修改

RDB与AOF的选择:

  1. 针对不同的情况来选择,建议使用两种方式相结合.
  2. 针对数据安全性、完整性要求高的采用aof方式.
  3. 针对不太重要的数据可以使用rdb方式.
  4. 对于数据进行全量备份,便于数据备份的可以采用rdb方式.

主从复制

这东西还是贴个Redis系列的博客吧深入学习Redis

以后再看。。。(〃` 3′〃)

标签: Web, Redis

添加新评论