Web基础之Redis
Redis
什么是Redis?Redis是一个基于内存的非关系型数据库,简单来说就是一个可持久化的高速缓存。
常用场景:
- 缓存(数据查询,端链接,新闻内容,商品内容等等)--使用最多
- 聊天室的在线好友列表
- 任务队列(秒杀,抢购,12306等等)
- 应用排行榜
- 网站访问统计
- 数据过期处理(可以精确到毫秒)
- 分布式集群架构中的session分离
高并发的三种优化思路:写入内存而不是写入硬盘、异步处理而不是同步处理、分布式处理
5种数据结构
Redis启动后自动创建16个数据库,默认选中ID为0的数据库,可以使用select id
来选择数据库。
Redis是Key-Value类型的存储系统,其中key值是字符串类型,value值支持5种常用的数据类型:
- 字符串(String)
- 哈希(hash)
- 字符串列表(list)
- 字符串集合(set)
- 有序字符串集合(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/value | hset 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 end | start:开始索引,从0开始;end:结束索引; |
查看指定索引的值 | lindex key index | index:值在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…
删除keyexists 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有两个核心类:JedisPool
、JedisPoolConfig
,后者为连接池配置类。
示例: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):
数据库连接池和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优缺点:
优点:
- 文件实现的数据快照,全量备份,便于数据的传输.比如我们需要把A服务器上的备份文件传输到B服务器上面,直接将rdb文件拷贝即可.
- 文件采用压缩的二进制文件,当重启服务时加载数据文件,比aof方式更快.
缺点:
- rbd采用加密的二进制格式存储文件,由于Redis各个版本之间的兼容性问题也导致rdb由版本兼容问题导致无法再其他的Redis版本中使用.
- 时效性差,容易造成数据的不完整性.因为rdb并不是实时备份,当某个时间段Redis服务出现异常,内存数据丢失,这段时间的数据是无法恢复的,因此易导致数据的丢失.
AOF
AOF的原理是以日志形式记录客户端的写入过程,然后再次启动的时候按照记录进行操作。
AOF有三种记录策略:
- appendfsync always //收到写命令就立即写入磁盘,最慢,但是保证完全的持久化
- appendfsync everysec //每秒钟写入磁盘一次,在性能和持久化方面做了很好的折中
- appendfsync no //完全依赖 os,性能最好,持久化没保证
以日志记录数据的方式缺点是多次写操作写入一个数据时AOF文件会记录每次操作而变得臃肿,并且最后一次操作之后前面的操作记录就失效了。因此Redis引入了重写机制,也就是将过期数据剔除,从而达到减小文件大小的作用。
AOF的优缺点
优点:
- 多种文件写入(fsync)策略.
- 数据实时保存,数据完整性强.即使丢失某些数据,制定好策略最多也是一秒内的数据丢失.
- 可读性强,由于使用的是文本协议格式来存储的数据,可有直接查看操作的命令,同时也可以手动改写命令.
缺点:
- 文件体积过大,加载速度比rbd慢.由于aof记录的是redis操作的日志,一些无效的,可简化的操作也会被记录下来,造成aof文件过大.但该方式可以通过文件重写策略进行优化.
RDB与AOF两种方式对比:
对比 | RDB | AOF |
---|---|---|
启动优先级 | 低 | 高 |
体积 | 小 | 大 |
恢复速度 | 快 | 慢 |
数据安全性 | 可能丢数据 | 根据策略决定 |
轻重 | 重 | 轻 |
兼容性 | 二进制加密,不同版本可能会出现不兼容 | 兼容性好,以文本形式记录命令,可直接查看,甚至直接修改 |
RDB与AOF的选择:
- 针对不同的情况来选择,建议使用两种方式相结合.
- 针对数据安全性、完整性要求高的采用aof方式.
- 针对不太重要的数据可以使用rdb方式.
- 对于数据进行全量备份,便于数据备份的可以采用rdb方式.
主从复制
这东西还是贴个Redis系列的博客吧深入学习Redis
以后再看。。。(〃` 3′〃)