31、Redis
31.1、Redis概述
概念:redis是一款高性能的NOSQL系列的非关系性数据库
什么是NOSQL
NoSQL(NoSQL = Not Only SQL),意即“不仅仅是SQL”,是一项全新的数据库理念,泛指非关系型的数据库。
随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。
NOSQL和关系型数据库比较
优点:
- 成本:nosql数据库简单易部署,基本都是开源软件,不需要像使用oracle那样花费大量成本购买使用,相比关系型数据库价格便宜。
- 查询速度:nosql数据库将数据存储于缓存之中,关系型数据库将数据存储在硬盘中,自然查询速度远不及nosql数据库。
- 存储数据的格式:nosql的存储格式是key,value形式、文档形式、图片形式等等,所以可以存储基础类型以及对象或者是集合等各种格式,而数据库则只支持基础类型。
- 扩展性:关系型数据库有类似join这样的多表查询机制的限制导致扩展很艰难。
缺点:
-
维护的工具和资料有限,因为nosql是属于新的技术,不能和关系型数据库10几年的技术同日而语。
-
不提供对sql的支持,如果不支持sql这样的工业标准,将产生一定用户的学习和使用成本。
-
不提供关系型数据库对事务的处理。
非关系型数据库的优势:
- 性能NOSQL是基于键值对的,可以想象成表中的主键和值的对应关系,而且不需要经过SQL层的解析,所以性能非常高。
- 可扩展性同样也是因为基于键值对,数据之间没有耦合性,所以非常容易水平扩展。
关系型数据库的优势:
- 复杂查询可以用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询。
- 事务支持使得对于安全性能很高的数据访问要求得以实现。对于这两类数据库,对方的优势就是自己的弱势,反之亦然。
总结
关系型数据库与NoSQL数据库并非对立而是互补的关系,即通常情况下使用关系型数据库,在适合使用NoSQL的时候使用NoSQL数据库,让NoSQL数据库对关系型数据库的不足进行弥补。一般会将数据存储在关系型数据库中,在nosql数据库中备份存储关系型数据库的数据
主流的NOSQL产品
• 键值(Key-Value)存储数据库
相关产品: Tokyo Cabinet/Tyrant、Redis、Voldemort、Berkeley DB
典型应用: 内容缓存,主要用于处理大量数据的高访问负载。
数据模型: 一系列键值对
优势: 快速查询
劣势: 存储的数据缺少结构化
• 列存储数据库
相关产品:Cassandra, HBase, Riak
典型应用:分布式的文件系统
数据模型:以列簇式存储,将同一列数据存在一起
优势:查找速度快,可扩展性强,更容易进行分布式扩展
劣势:功能相对局限
• 文档型数据库
相关产品:CouchDB、MongoDB
典型应用:Web应用(与Key-Value类似,Value是结构化的)
数据模型: 一系列键值对
优势:数据结构要求不严格
劣势: 查询性能不高,而且缺乏统一的查询语法
• 图形(Graph)数据库
相关数据库:Neo4J、InfoGrid、Infinite Graph
典型应用:社交网络
数据模型:图结构
优势:利用图结构相关算法。
劣势:需要对整个图做计算才能得出结果,不容易做分布式的集群方案。
什么是Redis
Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库,官方提供测试数据,50个并发执行100000个请求,读的速度是110000次/s,写的速度是81000次/s ,且Redis通过提供多种键值数据类型来适应不同场景下的存储需求,目前为止Redis支持的键值数据类型如下:
- 字符串类型 string
- 哈希类型 hash
- 列表类型 list
- 集合类型 set
- 有序集合类型 sortedset
redis的应用场景
- 缓存(数据查询、短连接、新闻内容、商品内容等等)
- 聊天室的在线好友列表
- 任务队列。(秒杀、抢购、12306等等)
- 应用排行榜
- 网站访问统计
- 数据过期处理(可以精确到毫秒)
- 分布式集群架构中的session分离
31.2、下载安装
官网:https://redis.io
中文网:http://www.redis.net.cn/
解压直接可以使用:
redis.windows.conf
:配置文件redis-cli.exe
:redis的客户端redis-server.exe
:redis服务器端
31.3、命令操作
31.3.1、redis的数据结构
redis的数据结构:
redis存储的是:key,value格式的数据,其中key都是字符串,value有5种不同的数据结构
- value的数据结构:
- 字符串类型
String
- 哈希类型
hash:map格式
- 列表类型
list:linkedlist格式
- 集合类型
set
- 有序集合类型
sortedset
- 字符串类型
31.3.2、字符串类型
字符串类型 String
-
存储:
set key value
set username zhangsan
-
获取:
get key
返回键对应的值get username
-
删除:
del key
del username
31.3.3、哈希类型
哈希类型hash
-
存储:
hset key field value
hset myhash username lisi
-
获取:
hget key field
:获取指定的field对象的值hget myhash username
返回值:
lisi
hgetall key
:获取所有的field和valuehgetall myhash
返回值:
username
lisi
password
123
-
删除:
hdel key field
hdel myhash username
31.3.4、列表类型
列表类型list:简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
- 存储:
lpush key value
:从左边存入列表rpush key value
:从右边存入列表
- 获取:
lrange key start end
:返回获取
- 删除:
lpop key
:从列表的最左边移除一个元素,并将元素返回rpop key
:从列表的最右边移除一个元素,并将元素返回
31.3.5、集合类型
集合类型 set :不允许重复元素
- 存储:
sadd key value
- 获取:
smembers key
:获取set集合中所有的元素 - 删除:
srem key value
:删除set集合中的某个元素
31.3.6、有序集合类型
有序集合类型:sortedset
:不允许重复元素,且元素有顺序
- 存储:
zadd key score value
score:就是一个分数可以是任意值 - 获取:
zrange key start end
- 获取所有的值
zrange key 0 -1
- 获取所有的值和分数
zrange key 0 -1 withscores
- 获取所有的值
- 删除:
zrem key value
31.3.7、通用命令
keys *
:查询所有的键type key
:获取键对应的value的类型del key
:删除指定的key value
31.4、redis持久化
redis是一个内存数据库,当redis服务器重启,获取电脑重启,数据会丢失,我们可以将redis内存中的数据持久化保存到硬盘文件中。
redis持久化机制:RDB、AOF
31.4.1、RDB持久化机制
RDB:默认方式,不需要进行配置,默认就是使用这种机制
- 在一定的间隔时间中,检测key的变化情况,然后持久化
-
编辑
redis.windwos.conf
文件save 900 1
:每15分钟后有一个键发生改变就持久化一次save 300 10
:每5分钟后有十个键发生改变就持久化一次save 60 10000
:每1分钟有一万个键发生改变就持久化一次 -
通过cmd命令行的方式重启redis服务器,并制定配置文件名称
D:\redis\windows-64\redis-2.8.9>redis-server.exe redis.windows.conf
31.4.2、AOF持久化机制
AOF:日志记录的方式,可以记录每一条命令的操作。可以每一次命令操作后,持久化数据
编辑redis.windwos.conf
文件
appendonly on(关闭aof) --> appendonly yes(开启aof)
appendfsync always
:每次操作都进行持久化
appendfsync everyesc
:每隔一秒进行一次持久化
appendfsync no
:不进行持久化
31.5、Jedis
31.5.1、Jedis概述
Jedis:一款Java操作redis数据库的工具
31.5.2、Jedis快速入门
使用步骤:
- 下载Jedis的jar包
- 使用
@Test
public void test1(){
//创建连接
Jedis jedis = new Jedis("localhost",6379);
//操作redis
jedis.set("password","123");
//关闭连接
jedis.close();
}
31.5.3、Jedis操作String类型数据
字符串类型 String
get、set
@Test
public void test1(){
//创建连接
Jedis jedis = new Jedis();//如果使用空参构造,默认值"localhost",6379端口
//存储
jedis.set("password","123");
//获取
String password = jedis.get("password");
System.out.println(password);
//可以使用setex()方法存储可以指定过期时间的 key value
jedis.setex("activecode",20,"hehe");//将activecode:hehe键值对存入redis,并且20秒后自动删除该键值对
//关闭连接
jedis.close();
}
31.5.4、Jedis操作hash类型数据
哈希类型 hash:map格式
hset、hget、hgetAll
//Jedis操作hash类型数据
@Test
public void test2(){
//创建连接
Jedis jedis = new Jedis();//如果使用空参构造,默认值"localhost",6379端口
//存储
jedis.hset("user","name","zhangsan");
jedis.hset("user","age","18");
jedis.hset("user","sex","nan");
//获取
String name = jedis.hget("user", "name");
System.out.println(name);
//获取所有的键值对
Map<String, String> map = jedis.hgetAll("user");
//遍历集合获取所有的键值对
Set<String> keys = map.keySet();
//根据key找值
for (String key : keys) {
String value = map.get(key);
System.out.println(key + ":" +value);
}
//关闭连接
jedis.close();
}
31.5.5、Jedis操作list类型数据
列表类型 list:linkedlist格式。支持重复元素
lpush/rpush、lpop/rpop、lrange start end :范围获取
//Jedis操作list类型数据
@Test
public void test3(){
//创建连接
Jedis jedis = new Jedis();//如果使用空参构造,默认值"localhost",6379端口
//存储
jedis.lpush("mylist","a","b","c");//从左边存
jedis.rpush("mylist","a","b","c");//从右边存
//获取列表的数据
List<String> mylist = jedis.lrange("mylist", 0, -1);
System.out.println(mylist);
//删除列表中的数据(弹出)
String mylist1 = jedis.lpop("mylist");
System.out.println(mylist1);
String mylist2 = jedis.rpop("mylist");
System.out.println(mylist2);
//获取列表的数据
List<String> mylist3 = jedis.lrange("mylist", 0, -1);
System.out.println(mylist3);
//关闭连接
jedis.close();
}
31.5.6、Jedis操作set类型数据
集合类型 set:不允许重复元素
sadd、smembers:获取所有元素
//Jedis操作set类型数据
@Test
public void test4(){
//创建连接
Jedis jedis = new Jedis();//如果使用空参构造,默认值"localhost",6379端口
//存储
jedis.sadd("person","son","man","min");
//获取所有的值
Set<String> person = jedis.smembers("person");
System.out.println(person);
//关闭连接
jedis.close();
}
31.5.7、Jedis操作sortedset类型数据
有序集合类型 sortedset:不允许重复元素,且元素有顺序
zadd/zrange:获取数据
//Jedis操作sortedset类型数据
@Test
public void test5(){
//创建连接
Jedis jedis = new Jedis();//如果使用空参构造,默认值"localhost",6379端口
//存储
jedis.zadd("son",3,"亚瑟");
jedis.zadd("son",20,"后裔");
jedis.zadd("son",55,"孙悟空");
//获取数据
Set<String> son = jedis.zrange("son", 0, -1);
System.out.println(son);
//关闭连接
jedis.close();
}
31.5.8、Jedis连接池
Jedis连接池:JedisPool
- 使用:
- 创建JedisPool连接池对象
- 调用方法
getResource()
方法获取Jedis连接
//jedis连接池
@Test
public void test6(){
//可以通过对象对Jedis连接池对象进行配置
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
//配置连接池的最大连接数
jedisPoolConfig.setMaxTotal(50);
//配置连接池的最大空余连接
jedisPoolConfig.setMaxIdle(10);
//创建Jedis链接池对象
JedisPool jedisPool = new JedisPool(jedisPoolConfig,"localhost");
//通过链接池方法获取连接对象
Jedis jedis = jedisPool.getResource();
//存储数据
jedis.set("hello","hhhh");
//归还资源
jedis.close();
}
31.5.9、Jedis连接池工具类
package com.example.utils;
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;
public class JedisUtils {
//定义一个jedisPool成员变量
private static JedisPool jedisPool;
//定义一个静态代码块实现配置文件的加载
static {
InputStream is = JedisUtils.class.getClassLoader().getResourceAsStream("jedis.properties");
//创建peoperties集合对象加载文件
Properties properties = new Properties();
try {
properties.load(is);
} catch (IOException e) {
e.printStackTrace();
}
//创建配置连接池对象
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
String maxIdle = (String) properties.get("maxIdle");
System.out.println(maxIdle);
String maxTotal = (String) properties.get("maxTotal");
System.out.println(maxTotal);
jedisPoolConfig.setMaxIdle(Integer.parseInt(maxIdle));
jedisPoolConfig.setMaxTotal(Integer.parseInt(maxTotal));
jedisPool = new JedisPool(jedisPoolConfig,properties.getProperty("host"));
}
//获取jedis对象
public static Jedis getJedis(){
return jedisPool.getResource();
}
}
31.5.10、案例
案例需求:
- 提供index.html页面,页面中有一个省份下拉列表
- 当页面加载完成后发送ajax请求,加载所有省份
注意:使用redis缓存一些不经常发生变化的数据。
- 数据库的数据一旦发生改变,则需要更新缓存
- 数据库的表执行 增删改的操作,需要将redis缓存数据情况,再次存入
- 在service对应的增删改方法中,将redis数据删除。
package cn.itcast.web.servlet;
import cn.itcast.service.ProvinceService;
import cn.itcast.service.impl.ProvinceServiceImpl;
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("/provinceServlet")
public class ProvinceServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/* //1.调用service查询
ProvinceService service = new ProvinceServiceImpl();
List<Province> list = service.findAll();
//2.序列化list为json
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(list);*/
//1.调用service查询
ProvinceService service = new ProvinceServiceImpl();
String json = service.findAllJson();
System.out.println(json);
//3.响应结果
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(json);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
package cn.itcast.service.impl;
import cn.itcast.dao.ProvinceDao;
import cn.itcast.dao.impl.ProvinceDaoImpl;
import cn.itcast.domain.Province;
import cn.itcast.jedis.util.JedisPoolUtils;
import cn.itcast.service.ProvinceService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import redis.clients.jedis.Jedis;
import java.util.List;
public class ProvinceServiceImpl implements ProvinceService {
//声明dao
private ProvinceDao dao = new ProvinceDaoImpl();
@Override
public List<Province> findAll() {
return dao.findAll();
}
/**
使用redis缓存
*/
@Override
public String findAllJson() {
//1.先从redis中查询数据
//1.1获取redis客户端连接
Jedis jedis = JedisPoolUtils.getJedis();
String province_json = jedis.get("province");
//2判断 province_json 数据是否为null
if(province_json == null || province_json.length() == 0){
//redis中没有数据
System.out.println("redis中没数据,查询数据库...");
//2.1从数据中查询
List<Province> ps = dao.findAll();
//2.2将list序列化为json
ObjectMapper mapper = new ObjectMapper();
try {
province_json = mapper.writeValueAsString(ps);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
//2.3 将json数据存入redis
jedis.set("province",province_json);
//归还连接
jedis.close();
}else{
System.out.println("redis中有数据,查询缓存...");
}
return province_json;
}
}
package cn.itcast.dao.impl;
import cn.itcast.dao.ProvinceDao;
import cn.itcast.domain.Province;
import cn.itcast.util.JDBCUtils;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import java.util.List;
public class ProvinceDaoImpl implements ProvinceDao {
//1.声明成员变量 jdbctemplement
private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());
@Override
public List<Province> findAll() {
//1.定义sql
String sql = "select * from province ";
//2.执行sql
List<Province> list = template.query(sql, new BeanPropertyRowMapper<Province>(Province.class));
return list;
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="js/jquery-3.3.1.min.js"></script>
<script>
$(function () {
//发送ajax请求,加载所有省份数据
$.get("provinceServlet",{},function (data) {
//[{"id":1,"name":"北京"},{"id":2,"name":"上海"},{"id":3,"name":"广州"},{"id":4,"name":"陕西"}]
//1.获取select
var province = $("#province");
//2.遍历json数组
$(data).each(function () {
//3.创建<option>
var option = "<option name='"+this.id+"'>"+this.name+"</option>";
//4.调用select的append追加option
province.append(option);
});
});
});
</script>
</head>
<body>
<select id="province">
<option>--请选择省份--</option>
</select>
</body>
</html>