Redis
概念
一款高性能的NoSQL系列的非关系型数据库
关系型:MySQL Oracle
数据之间有关联关系
数据存储在硬盘的文件中
非关系型:Redis HBase
数据使用键值对/列/文档/图形进行存储,数据之间没有关联关系
数据存储在内存中
数据量很大时,操作非关系型数据库很耗时,因此如果经常查询一些不太经常发生变化的数据,可以使用缓存的思想
在内存中开辟一块区域作为缓存
从缓存中获取数据
有数据
返回数据
没有数据
查询数据库
将数据放入缓存
返回数据
一般将数据存储在关系型数据库中,在非关系型数据库中备份这些数据
应用场景
缓存(数据查询、短连接、新闻内容、商品内容等等)
聊天室的在线好友列表
任务队列(秒杀、抢购、12306等等)
应用排行榜
网站访问统计
数据过期处理(可以精确到毫秒)
分布式集群架构中的session分离
下载安装
官网 https://redis.io
中文网 http://www.redis.net.cn
redis一般部署在Linux系统上
解压即安装
解压后的文件
redis.windows.conf 配置文件
redis-cli.exe redis的客户端
redis-server.exe redis的服务器端
双击redis-server.exe启动服务器
双击redis-cli.exe启动客户端,输入命令
set username zhangsan 存储键值对
get username 获取值
命令操作
redis的数据结构
redis存储的是key,value格式的数据
key都是字符串,value有5种数据类型
字符串string 哈希hash(map) 列表list(linkedlist) 集合set(hashset) 有序集合sortedset
list允许重复元素,set不允许重复元素,sortedset不允许重复元素且存入数据会自动排序
key value
myString zhangsan
myHash name lisi
age 23
gender female
myList [zhangsan, lisi, wangwu, zhangsan] 元素可重复
mySet [zhangsan, lisi, wangwu] 元素不可重复
mySortedSet [zhangsan, lisi, wangwu] 元素不可重复且会自动排序
命令操作5种类型数据 更多命令查看redis中文网的教程
string
存储 set key value 设置同一key的不同value会覆盖前面的
获取 get key 获取不存在的key会返回<nil>
删除 del key
hash
存储 hset key field value
hset myHash username zhangsan
hset myHash password 123
获取hash中某个field对应的值 hget key field
hget myHash username
获取整个hash hgetall key
hgetall myHash
返回1>"username"
2>"zhangsan"
3>"password"
4>"123"
删除 hdel key field
hdel myHash username
list
添加 lpush key value
lpush myList a
lpush myList b
rpush myList c
元素顺序 b a c
获取 lrange key start end
获取所有元素 lrange myList 0 -1
删除
lpop key 删除列表左边的第一个元素并返回该元素
lpop myList
rpop key 删除列表右边的第一个元素并返回该元素
rpop myList
set
存储 sadd key value
sadd mySet a b c 获取元素的顺序和插入顺序不一定一致
获取set中所有元素 smembers key
删除某个元素 srem key value
srem mySet a
sortedset
存储 zadd key score value
zadd mySortedSet 60 zhangsan
zadd mySortedSet 70 lisi
zadd mySortedSet 80 wangwu
获取 zrange key start end
zrange mySortedSet 0 -1 获取所有元素
返回1>"zhangsan"
2>"lisi"
3>"wangwu"
zrange mySortedSet 0 -1 withscore
返回1>"zhangsan"
2>"60"
3>"lisi"
4>"70"
5>"wangwu"
6>"80"
删除 zrem key value
zrem mySortedSet zhangsan
通用命令
keys * 获取所有的键
type key 获取键对应值的数据类型
del key 删除键
持久化
redis是一个内存数据库,数据是临时的,关电脑或重启redis服务器数据就会丢失
可以将redis内存中的数据持久化保存到硬盘的文件中
redis持久化机制
RDB 默认方式,不需要进行配置,默认使用这种机制
在一定的时间间隔内,检测key的变化情况,然后持久化数据
对性能的影响比较低,推荐使用
AOF 日志记录的方式
记录每一条命令,在每一次命令操作后,持久化数据
对性能的影响比较高,不推荐使用
RDB使用
编辑redis.windows.conf
save 900 1 15分钟后有1次key改变就持久化一次
save 300 10 5分钟后有10次key改变就持久化一次
save 60 10000 1分钟有10000次key改变就持久化一次
在redis安装位置启动命令行
redis-server.exe redis.windows.conf 启动服务器
双击启动客户端
改变键
达到上述持久化要求后会在安装位置生成一个dump.rdb文件来存储数据
之后重启服务器和客户端,数据就不会丢失,可以获取
AOF使用
编辑redis.windows.conf
appendonly no改为appendonly yes 开启AOF
# appendfsync always 每一次命令操作都进行持久化
appendfsync everysec 每隔一秒持久化一次
# appendfsync no 不进行持久化 redis就是一个很大的map集合
在redis安装位置启动命令行
redis-server.exe redis.windows.conf 启动服务器
双击启动客户端
改变键
达到上述持久化要求后会在安装位置生成一个appendonly.aof文件来存储数据
之后重启服务器和客户端,数据就不会丢失,可以获取
Jedis
一款Java操作redis数据库的工具
类似于JDBC,导入mysql-connector的jar包后就可以使用Java操作MySQL数据库
使用
导入jar包
commons-pool2-2.3.jar
jedis-2.7.0.jar
使用
//获取连接
Jedis jedis = new Jedis("localhost", 6379);//空参默认使用localhost和6379
//操作
jedis.set("username", "zhangsan");
//关闭连接
jedis.close();
操作5种数据类型 命令和方法名一样
字符串string
set
get
setex 存储可以指定过期时间的key和value 时间到了自动删除
可以用于邮箱激活链接
哈希hash(map)
hset
hget
hgetall hgetAll
列表list(linkedlist)
lpush
rpush
lpop
rpop
lrange start end
集合set(hashset)
sadd
smembers
有序集合sortedset
zadd
package jedis.test;
import jedis.util.JedisPoolUtils;
import org.junit.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class JedisTest {
//快速入门
@Test
public void test1() {
//获取连接
Jedis jedis = new Jedis("localhost", 6379);
//操作
jedis.set("username", "zhangsan");
//关闭连接
jedis.close();
}
//字符串string
@Test
public void test2() {
//获取连接
Jedis jedis = new Jedis();
//操作
jedis.set("username", "zhangsan");
String username = jedis.get("username");
System.out.println(username);//zhangsan
//使用setex()方法存储可以指定过期时间的key和value 时间到了自动删除
jedis.setex("activecode", 20, "hehe");//20s过期的激活码
//关闭连接
jedis.close();
}
//hash
@Test
public void test3() {
//获取连接
Jedis jedis = new Jedis();
//操作
jedis.hset("user", "name", "lisi");
jedis.hset("user", "age", "24");
jedis.hset("user", "gender", "female");
String name = jedis.hget("user", "name");
String age = jedis.hget("user", "age");
String gender = jedis.hget("user", "gender");
System.out.println("name:" + name + ", age:" +age + ", gender:" + gender);
//获取hash中的所有键值对
Map<String, String> user = jedis.hgetAll("user");
for (String key : user.keySet()) {
String value = user.get(key);
System.out.println(key + ":" + value);
}
//关闭连接
jedis.close();
}
//list
@Test
public void test4() {
//获取连接
Jedis jedis = new Jedis();
//操作
jedis.lpush("myList", "a", "b", "c");
jedis.rpush("myList", "a", "b", "c");
List<String> myList = jedis.lrange("myList", 0, -1);
System.out.println(myList);//[c, b, a, a, b, c]
String leftElem = jedis.lpop("myList");
System.out.println(leftElem);//c
String rightElem = jedis.rpop("myList");
System.out.println(rightElem);//c
List<String> myList1 = jedis.lrange("myList", 0, -1);
System.out.println(myList1);//[b, a, a, b]
//关闭连接
jedis.close();
}
//set
@Test
public void test5() {
//获取连接
Jedis jedis = new Jedis();
//操作
jedis.sadd("mySet", "java", "php", "c++");
Set<String> mySet = jedis.smembers("mySet");
System.out.println(mySet);//[c++, java, php]
//关闭连接
jedis.close();
}
//sortedset
@Test
public void test6() {
//获取连接
Jedis jedis = new Jedis();
//操作
jedis.zadd("mySortedSet", 3, "亚瑟");
jedis.zadd("mySortedSet", 30, "后羿");
jedis.zadd("mySortedSet", 25, "孙悟空");
Set<String> mySortedSet = jedis.zrange("mySortedSet", 0, -1);
System.out.println(mySortedSet);//[亚瑟, 孙悟空, 后羿]
jedis.zadd("mySortedSet", 55, "孙悟空");
Set<String> mySortedSet1 = jedis.zrange("mySortedSet", 0, -1);
System.out.println(mySortedSet1);//[亚瑟, 后羿, 孙悟空]
//关闭连接
jedis.close();
}
//Jedis连接池
@Test
public void test7() {
//创建配置对象
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(50);//最大连接数
config.setMaxIdle(10);//最大空闲连接数
//创建Jedis连接池对象
JedisPool jedisPool = new JedisPool(config, "localhost", 6379);
//获取连接
Jedis jedis = jedisPool.getResource();
//操作
jedis.set("hehe", "haha");
String hehe = jedis.get("hehe");
System.out.println(hehe);//haha
//归还连接
jedis.close();
}
//JedisPoolUtils的使用
@Test
public void test8() {
//使用工具类的静态方法获取连接
Jedis jedis = JedisPoolUtils.getJedis();
jedis.set("hello", "world");
String hello = jedis.get("hello");
System.out.println(hello);//world
jedis.close();
}
}
Jedis连接池 JedisPool 对连接进行复用
使用
创建配置对象
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(50);//最大连接数
config.setMaxIdle(10);//最大空闲连接数
创建JedisPool连接池对象
参数传递配置对象,主机地址和端口号
JedisPool jedisPool = new JedisPool(config, "localhost", 6379);
调用getResource方法获取Jedis连接
Jedis jedis = jedisPool.getResource();
操作数据库
将连接归还给连接池
jedis.close();
创建JedisPool的工具类JedisPoolUtils,用于加载配置文件,配置连接池的参数
Jedis工具类
package jedis.util;
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;
/*
JedisPool工具类
用于加载配置文件,配置连接池的参数
提供获取连接的方法
*/
public class JedisPoolUtils {
private static JedisPool jedisPool;
static {
//读取配置文件
InputStream is = JedisPoolUtils.class.getClassLoader().getResourceAsStream("jedis.properties");
//创建Properties对象
Properties properties = new Properties();
//关联文件
try {
properties.load(is);
} catch (IOException e) {
e.printStackTrace();
}
//获取数据,设置到Jedis配置对象JedisPoolConfig中
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(Integer.parseInt(properties.getProperty("maxTotal")));
config.setMaxIdle(Integer.parseInt(properties.getProperty("maxIdle")));
//初始化JedisPool对象
jedisPool = new JedisPool(config, properties.getProperty("host"), Integer.parseInt(properties.getProperty("port")));
}
/**
* 获取连接的方法
*/
public static Jedis getJedis() {
return jedisPool.getResource();
}
}
案例
需求
1、提供index.html页面,页面中有一个省份下拉列表
2、当页面加载完成后,发送ajax请求,加载所有省份
分析
数据库
CREATE DATABASE day20; -- 创建数据库
USE day20; -- 使用数据库
CREATE TABLE province( -- 创建表
ID INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20) NOT NULL
);
-- 插入数据
INSERT INTO province VALUES(NULL, "北京");
INSERT INTO province VALUES(NULL, "上海");
INSERT INTO province VALUES(NULL, "广州");
INSERT INTO province VALUES(NULL, "深圳");
导入jar包
复制druid.properties到项目中
index.html
有一个下拉列表,只有请选择省份这一行
页面加载完成后,index.html中的JavaScript代码会发送一个ajax请求
url是findProvinceServlet,获取数据库中的省份信息
findProvinceServlet
调用service层的方法,返回List<Province>
将返回的数据序列化为json,并填充到响应数据中
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(response.getWriter(), mapper);
ProvinceService
查询redis是否存在数据
不存在
调用dao层的方法
将数据写入redis
返回数据
存在
直接返回数据
ProvinceDao
使用JDBC操作数据库
注意
在$.get参数的最后加上"json"会报错
使用redis缓存一些不经常发生变化的数据
数据库的数据一旦发生变化(数据库的表增删改),需要更新缓存(清空redis缓存的数据,再次存入)
更新缓存:在service中的增删改相关方法中删除对应的键 这样在查询方法中找不到对应的键就会自动再次查询数据库并将数据存入缓存
开发
配置文件
druid.properties
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///day20
username=root
password=root
initialSize=5
maxActive=10
maxWait=3000
jedis.properties
host=127.0.0.1
port=6379
maxTotal=50
maxIdle=10
html
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>选择省份页面</title>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
<script>
$(function () {
$.get("findProvincesServlet", {}, function (data) {
//获取select
var province = $("#province");
//遍历返回的json数组,每次遍历创建一个option,并使用append方法追加到select中
$(data).each(function () {
var option = "<option name='" + this.id + "'>" + this.name + "</option>";
province.append(option);
});
});
});
</script>
</head>
<body>
<select id="province">
<option>--请选择省份--</option>
</select>
</body>
</html>
util
JDBCUtils
package util;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
/**
* JDBC工具类 使用Durid连接池
*/
public class JDBCUtils {
private static DataSource ds ;
static {
try {
//1.加载配置文件
Properties pro = new Properties();
//使用ClassLoader加载配置文件,获取字节输入流
InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties");
pro.load(is);
//2.初始化连接池对象
ds = DruidDataSourceFactory.createDataSource(pro);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取连接池对象
*/
public static DataSource getDataSource(){
return ds;
}
/**
* 获取连接Connection对象
*/
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
}
domain
Province
package domain;
public class Province {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Province{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
web.servlet
FindProvincesServlet
package web.servlet;
import service.ProvinceService;
import service.impl.ProvinceServiceImpl;
import java.io.IOException;
@javax.servlet.annotation.WebServlet("/findProvincesServlet")
public class FindProvincesServlet extends javax.servlet.http.HttpServlet {
protected void doPost(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {
response.setContentType("application/json;charset=utf-8");
//调用service层的方法查询所有省份
ProvinceService service = new ProvinceServiceImpl();
//List<Province> provinces = service.findProvinces();
String provinces_json = service.findProvincesJson();
//将返回的数据序列化为json,并填充到响应数据中
//ObjectMapper mapper = new ObjectMapper();
//String provinces_json = mapper.writeValueAsString(provinces);
response.getWriter().write(provinces_json);
}
protected void doGet(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {
this.doPost(request, response);
}
}
service
ProvinceService
package service;
import domain.Province;
import java.util.List;
public interface ProvinceService {
public List<Province> findProvinces();
public String findProvincesJson();
}
impl/ProvinceServiceImpl
package service.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import dao.ProvinceDao;
import dao.impl.ProvinceDaoImpl;
import domain.Province;
import jedis.util.JedisPoolUtils;
import redis.clients.jedis.Jedis;
import service.ProvinceService;
import java.util.List;
public class ProvinceServiceImpl implements ProvinceService {
private ProvinceDao dao = new ProvinceDaoImpl();
@Override
public List<Province> findProvinces() {
return dao.findProvinces();
}
/**
* 使用redis缓存
* @return
*/
@Override
public String findProvincesJson() {
//先从redis中查询数据 先获取redis的客户端连接
Jedis jedis = JedisPoolUtils.getJedis();
String province_json = jedis.get("province");
//判断province是否为空
if (province_json == null || province_json.length() == 0) {
//从数据库查询
System.out.println("redis中没有数据,查询数据库");
List<Province> provinces = dao.findProvinces();
ObjectMapper mapper = new ObjectMapper();
try {
province_json = mapper.writeValueAsString(provinces);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
//将数据存入redis
jedis.set("province", province_json);
} else {
System.out.println("redis中有数据,查询缓存");
}
jedis.close();
return province_json;
}
}
dao
ProvinceDao
package dao;
import domain.Province;
import java.util.List;
public interface ProvinceDao {
public List<Province> findProvinces();
}
impl/ProvinceDaoImpl
package dao.impl;
import dao.ProvinceDao;
import domain.Province;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import util.JDBCUtils;
import java.util.List;
public class ProvinceDaoImpl implements ProvinceDao {
JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());
@Override
public List<Province> findProvinces() {
//使用JDBC操作数据库
//定义sql
String sql = "select * from province";
//执行sql
List<Province> provinces = template.query(sql, new BeanPropertyRowMapper<Province>(Province.class));
return provinces;
}
}