目录
一、Redis及非关系型数据库NOSQL
redis是一款高性能的NOSQL系列的非关系型数据库。常见的数据库类型有以下两种:
- 关系型数据库:MySQL、Oracle、Microsoft SQL Server、Microsoft Access
- 数据之间有关联关系
- 数据存储在硬盘文件上
- 数据以表的形式存储
- 非关系型数据库:Redis、HBase、MongoDB、Cassandra等
- 数据之间没有关联关系
- 数据存储在内存中
- 数据以key和value键值对的方式存储
如果数据量非常大的情况,我们操作非关系型数据库是非常耗时的,比如要查询一个用户非常多的用户表,此时会比较慢,我们可以利用缓存的方法来解决这个问题。
在需要查询数据的时候,我们首先在缓存中查找有没有该数据,如果有的话直接获取,没有的话从数据库查询后,将数据写到缓存中,这样获取数据的速度将会大大增快。
关系型数据库与NOSQL数据库并非对立而是互补的关系,通常情况下我们使用关系型数据库存储数据,然后在NOSQL数据库中备份关系型数据库的数据。
二、主流NOSQL非关系型数据库
2.1键值(Key-Value)存储数据库
相关产品: Tokyo Cabinet/Tyrant、Redis、Voldemort、Berkeley DB
典型应用: 内容缓存,主要用于处理大量数据的高访问负载。
数据模型: 一系列键值对
优势: 快速查询
劣势: 存储的数据缺少结构化
2.2列存储数据库
相关产品:Cassandra, HBase, Riak
典型应用:分布式的文件系统
数据模型:以列簇式存储,将同一列数据存在一起
优势:查找速度快,可扩展性强,更容易进行分布式扩展
劣势:功能相对局限
2.3文档型数据库
相关产品:CouchDB、MongoDB
典型应用:Web应用(与Key-Value类似,Value是结构化的)
数据模型: 一系列键值对
优势:数据结构要求不严格
劣势: 查询性能不高,而且缺乏统一的查询语法
2.4图形(Graph)数据库
相关数据库:Neo4J、InfoGrid、Infinite Graph
典型应用:社交网络
数据模型:图结构
优势:利用图结构相关算法。
劣势:需要对整个图做计算才能得出结果,不容易做分布式的集群方案
三、Redis
Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库。
官方提供测试数据,50个并发执行100000个请求,读的速度是110000次/s,写的速度是81000次/s ,且Redis通过提供多种键值数据类型来适应不同场景下的存储需求。
Redis的常用场景有以下几种:
- 缓存(数据查询、短连接、新闻内容、商品内容等等)
- 聊天室的在线好友列表
- 任务队列(秒杀、抢购、12306等等)
- 应用排行榜
- 网站访问统计
- 数据过期处理(可以精确到毫秒)
- 分布式集群架构中的session分离
3.1下载安装
官网:https://redis.io
中文网:http://www.redis.net.cn/
也可以在直接点击链接下载(提取码:v8pn),解压直接可以使用,其中有以下几个文件需要注意:
- redis.windows.conf:配置文件
- redis-cli.exe:redis的客户端
- redis-server.exe:redis服务器端
我们首先要启动redis的服务端,双击redis-server.exe,然后启动客户端redis-cli.exe就可以操作了。
3.2Redis的使用
使用Redis有两种方式,一个是命令操作,还有一个是利用Jedis在java客户端进行代码操作。
3.2.1命令操作
操作Redis的数据首先我们要了解Redis的数据结构。
redis存储的是key,value格式的数据,其中key都是字符串,value有5种不同的数据结构:
- 字符串类型 string
- 哈希类型 hash,也就是map集合,存储的是键值对,即值存储的还是一个键值对
- 列表类型 list,元素可重复
- 集合类型 set,元素不可重复
- 有序集合类型 sortedset,有序的不可重复元素集合
接下来我们一个个进行介绍:
1、字符串类型String
存储:set key value
获取:get key
删除:del key
2、哈希类型Hash
存储:hset key field value //field是map集合中的键,value是map集合的值
获取:hget key field //获取key存储的map集合中的field键对应的值
hgetall key //获取key存储的map集合中所有的键值对
删除:hdel key field //删除key存储的map集合中的field键对应的值
3、列表类型List
redis的列表是简单的字符串列表,按照插入顺序进行排序,可以从列表头部或尾部进行插入。
存储:lpush key value //从列表左边添加元素
rpush key value //从列表右边添加元素
获取:lrange key start end //获取某个范围内的元素
lrange key 0 -1 //如果要获取所有元素,则start=0,end=-1
lindex index //获取某个下标的元素
删除:lpop key //删除列表的最左边元素,并将删除的元素返回
rpop key //删除列表的最右边元素,并将删除的元素返回
4、集合类型Set
存储:sadd key value //存储,value也可以写多个,一次存储多个值
获取:smembers key //获取set集合中的所有值
删除:srem key value //删除set集合中的value值
5、有序集合类型SortedSet
redis有序集合SortedSet和集合Set一样,存储的都是String类型元素,且不允许重复元素。
但是SortedSet的每个元素都会关联一个double类型的分数(不同的元素分数可以相同),redis通过分数对集合成员进行从小到大排序。
存储:zadd key score value //存储分数为score,值为value的元素到key集合
获取:zrange key start end //获取范围为start到end的元素
zrange key start end withscores //获取范围为start到end的元素以及对应分数
zrange key 0 -1 //如果要获取所有元素,则start=0,end=-1
删除:zrem key value //删除值为value的元素
6、通用命令
keys *:查询所有的键
type key:查询键存储的数据类型
del key:删除键
3.2.2redis的持久化
redis是一个内存数据库,数据存放在内存中,而内存有一个特点,存储的数据是临时的不能持久化写入。比如当电脑重启或者redis服务器重启这些数据就没有了。
我们可以将redis内存中的数据持久化保存到硬盘文件中,这样当redis重启后可以从文件中重新加载数据
redis持久化一般有两种机制:
- RDB:默认方式,不需要配置,默认使用该机制
- 该方案会在一定的间隔时间中,检测key的变化情况,然后持久化数据
- 持久化的数据会存储到.rdb后缀的文件中
- 我们一般在配置文件redis.windows.conf中修改:
-
save 900 1 # 在900s内,如果至少有1个key发生改变,就持久化数据一次 save 300 10 # 在300s内,如果至少有10个key发生改变,就持久化数据一次 save 60 10000 # 在60s内,如果至少有10000个key发生改变,就持久化数据一次
- 修改完成后需要命令行启动redis服务器:
- AOF:日志记录的方式。(读写次数频繁,性能影响较大,不太推荐)
- 可以记录每一条命令的操作。每次命令操作后都会持久化数据
- 持久化的数据会存储到.aof后缀的文件中
- 同样我们要在配置文件redis.windows.conf中修改:
-
appendonly no # 默认为no,代表关闭,此处改为yes则是开启aof机制 # appendfsync always # 每次操作都进行持久化 # appendfsync everysec # 每秒进行一次持久化 # appendfsync no # 不进行持久化
-
第一个是aof机制的开关,后三个是aof机制的设置。同样修改完成后需要命令行启动服务器。
四、Jedis
Jedis是一款java操作redis数据库的工具。(类似JDBC操作mysql数据库)
首先要下载jedis的jar包(提取码:5al9),然后将jar包导入工程就可以使用了。
4.1Jedis入门
我们在java中新建web工程,导入jar包,往redis中写入一个键值对。
public void test1(){
//获取redis连接
Jedis jedis=new Jedis("localhost",6379);//连接本机地址127.0.0.1,默认端口6379
//操作
jedis.set("username","tz");//存储键名为username,值为tz的键值对
//关闭连接
jedis.close();
}
4.2Jedis基本操作
4.2.1操作String类型
public void test2(){
//获取redis连接
Jedis jedis=new Jedis();//如果使用空参构造,默认值为localhost和6379端口,如果连接本机可以直接使用空参构造
jedis.set("username","tz");//存储键名为username,值为tz的键值对
String username = jedis.get("username");//获取键名为username的值
System.out.println(username);
jedis.del("username");//删除该键
//关闭连接
jedis.close();
}
4.2.2操作Hash类型
public void test3(){
//获取redis连接
Jedis jedis=new Jedis("localhost",6379);//连接本机地址127.0.0.1,默认端口6379
jedis.hset("user","name","tz");//存储键名为user的hash集合,name为tz
jedis.hset("user","age","22");//存储键名为user的hash集合,age为22
String name = jedis.hget("user", "name");//获取user中键名为name的值
String age = jedis.hget("user", "age");//获取user中键名为age的值
System.out.println(name+":"+age);
Map<String, String> user = jedis.hgetAll("user");//获取user中所有的map集合数据
for (String key:user.keySet()) {//输出
System.out.println(key+":"+user.get(key));
}
//关闭连接
jedis.close();
}
4.2.3操作List类型
public void test4(){
//获取redis连接
Jedis jedis=new Jedis("localhost",6379);//连接本机地址127.0.0.1,默认端口6379
jedis.lpush("mylist","a","b","c");//从列表左边存储数据a,b,c
jedis.rpush("mylist","a","b","c");//从列表右边存储数据a,b,c
List<String> mylist = jedis.lrange("mylist", 0, -1);//获取列表中的所有数据
System.out.println(mylist);
String s1 = jedis.lpop("mylist");//弹出最左边的元素
String s2 = jedis.rpop("mylist");//弹出最右边的元素
System.out.println(s1);
System.out.println(s2);
List<String> mylist2 = jedis.lrange("mylist", 0, -1);//获取列表中的所有数据
System.out.println(mylist2);
jedis.close();//关闭连接
}
4.2.4操作Set类型
public void test5(){
//获取redis连接
Jedis jedis=new Jedis("localhost",6379);//连接本机地址127.0.0.1,默认端口6379
jedis.sadd("myset","1","2","3","3");//存储键名为myset的集合,值为"1","2","3",值不允许重复
Set<String> set = jedis.smembers("myset");
System.out.println(set);
jedis.close();//关闭连接
}
4.2.5操作SortedSet类型
public void test6(){
//获取redis连接
Jedis jedis=new Jedis("localhost",6379);//连接本机地址127.0.0.1,默认端口6379
jedis.zadd("language",100,"java");//存储language的集合,值为"java",得分为100
jedis.zadd("language",60,"c");//存储language的集合,值为"c",得分为60
jedis.zadd("language",80,"c++");//存储language的集合,值为"c++",得分为80
Set<String> sortedset = jedis.zrange("language", 0, -1);
System.out.println(sortedset);
jedis.close();//关闭连接
}
4.3Jedis连接池
Jedis连接池和JDBC连接池类似,Jedis连接池不需要创建新的Jedis对象连接Redis,可以大大减少对于创建和回收Redis连接的开销。
4.3.1Jedis连接池使用
在Jedis中,我们通过JedisPool对象来创建Jedis连接池,步骤如下:
- 创建JedisPool连接池对象
- 调用方法getResource()方法获取Jedis连接
public void testJedisPool(){
JedisPoolConfig jedisPoolConfig=new JedisPoolConfig();//创建jedispool的配置对象
jedisPoolConfig.setMaxTotal(50);//设置jedis连接池最大连接数为50
jedisPoolConfig.setMaxIdle(10);//设置最大空闲连接数为10
JedisPool jedisPool=new JedisPool(jedisPoolConfig,"localhost",6379);//利用配置对象构造创建Jedis连接池对象
Jedis jedis = jedisPool.getResource();//获取连接
jedis.set("jedisPool","666");
System.out.println(jedis.get("jedisPool"));//输出
jedis.close();//将jedis归还到连接池中
}
Jedis的常见配置属性如下:
redis.pool.maxTotal=1000 #最大活动对象数
redis.pool.maxIdle=100 #最大能够保持idel状态的对象数
redis.pool.minIdle=50 #最小能够保持idel状态的对象数
redis.pool.maxWaitMillis=10000 #当池内没有返回对象时,最大等待时间
redis.pool.testOnBorrow=true #当调用borrow Object方法时,是否进行有效性检查
redis.pool.testOnReturn=true #当调用return Object方法时,是否进行有效性检查
redis.pool.timeBetweenEvictionRunsMillis=30000 #“空闲链接”检测线程,检测的周期,毫秒数。如果为负值,表示不运行“检测线程”。默认为-1.
redis.pool.testWhileIdle=true #向调用者输出“链接”对象时,是否检测它的空闲超时;
redis.pool.numTestsPerEvictionRun=50 # 对于“空闲链接”检测线程而言,每次检测的链接资源的个数。默认为3.
redis.ip=xxxxxx #redis服务器的IP
redis1.port=6379 #redis服务器的Port
4.3.2Jedis连接池加载配置文件
我们通过代码人工设置属性比较繁琐,所以一般都是放置在配置文件中,读取配置文件创建连接池。
我们新建一个JedisPoolUtils类, 用于获取jedis的连接池,并用配置文件对连接池进行配置。
package Jedis.Util;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPoolConfig;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class JedisPoolUtils {
private static JedisPool jedisPool;
static {//静态代码块,当类加载时就读取配置文件
InputStream stream = JedisPoolUtils.class.getClassLoader().getResourceAsStream("jedis.properties");//得到文件输入流
Properties properties=new Properties();//创建配置对象
try {
properties.load(stream);//将对象关联到配置文件
} catch (IOException e) {
e.printStackTrace();
}
JedisPoolConfig jedisPoolConfig=new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(Integer.parseInt(properties.getProperty("maxTotal")));//从配置文件中读取数据并设置到配置对象中
jedisPoolConfig.setMaxTotal(Integer.parseInt(properties.getProperty("maxIdle")));
jedisPool=new JedisPool(jedisPoolConfig,properties.getProperty("host"),Integer.parseInt(properties.getProperty("port")));//初始化JedisPool
}
public static Jedis getJedisPool(){
return jedisPool.getResource();//获取jedis连接池
}
}
配置文件如下:
然后我们对该工具类进行测试:
public void testJedisPoolUtils(){
Jedis jedis=JedisPoolUtils.getJedisPool();
jedis.set("jedisPoolUtil","777");
System.out.println(jedis.get("jedisPoolUtil"));//输出
jedis.close();//将jedis归还到连接池中
}
五、JedisDemo
5.1实现
我们实现一个案例,该案例提供一个界面,里面有省份的下拉列表,当页面加载完成之后,发送ajax请求,加载所有省份。
首先我们在数据库中新建province这个表:
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,'深圳');
INSERT INTO province VALUES(NULL,'武汉');
然后实现数据访问层Dao,连接数据库完成查询功能,
package ProvinceWebDemo.Dao;
import ProvinceWebDemo.Province;
import ProvinceWebDemo.Utils.JDBCUtils;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import java.util.List;
interface ProvinceDao {
public List<Province> findAll();
}
public class ProvinceDaoImpl implements ProvinceDao {
private JdbcTemplate template=new JdbcTemplate(JDBCUtils.getDataSource());
@Override
public List<Province> findAll() {
String sql="select * from province";
List<Province> list = template.query(sql, new BeanPropertyRowMapper<Province>(Province.class));
return list;
}
}
接着在业务逻辑层调用数据访问层的方法,
package ProvinceWebDemo.Service;
import ProvinceWebDemo.Dao.ProvinceDao;
import ProvinceWebDemo.Dao.ProvinceDaoImpl;
import ProvinceWebDemo.Province;
import java.util.List;
interface ProvinceService {
public List<Province> findAll();
}
public class ProvinceServiceImpl implements ProvinceService{
private ProvinceDao dao=new ProvinceDaoImpl();
@Override
public List<Province> findAll() {
return dao.findAll();
}
}
然后我们新建一个servlet,获取数据并将结果序列化为json格式字符串,写回response,
package ProvinceWebDemo;
import ProvinceWebDemo.Service.ProvinceService;
import ProvinceWebDemo.Service.ProvinceServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
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("/provinceServlet")
public class ProvinceServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ProvinceService service=new ProvinceServiceImpl();
List<Province> provinces = service.findAll();//调用服务获取省份数据
ObjectMapper mapper=new ObjectMapper();
String json = mapper.writeValueAsString(provinces);//将集合序列化为json格式字符串
System.out.println(json);
response.setContentType("application/json;charset=utf-8");//设置response格式
response.getWriter().write(json);//将json写入response
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request,response);
}
}
在界面中访问servlet,并显示结果:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="js/jquery-3.3.1.js"></script>
<script>
$(function(){
//发送ajax请求,加载所有省份数据
$.get("provinceServlet",{},function (data) {
var province=$("#province");
$(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>
启动服务器:
5.2Redis缓存优化
我们每次进入这个页面的时候都会访问数据库,查询省份的数据,访问数据库比较耗时。
而这些省份数据不会经常改变,所以我们可以利用redis的缓存技术。
第一次访问我们还是在业务逻辑层调用dao数据访问层的方法获取数据,但是我们会将得到的数据存入redis中,然后返回数据,当后面访问的时候,会先看看redis有没有数据,如果有数据则直接获取redis里的数据,这样就可以不用访问数据库了。
我们在 ProvinceServiceImpl 中新加一个方法findAllJson(),该方法会先看redis中是否有缓存数据,没有的话再查数据库,将省份信息序列化为json字符串返回。
public String findAllJson() {
//使用redis缓存获取数据
Jedis jedis = JedisPoolUtils.getJedisPool();//获取jedis连接
String province_json = jedis.get("province");//查询redis中province数据
if (province_json == null || province_json.length() == 0) {//redis中没有数据
System.out.println("redis中无数据,查询数据库中........");
List<Province> provinces = dao.findAll();//从数据库中查询数据
ObjectMapper objectMapper = new ObjectMapper();
try {
province_json = objectMapper.writeValueAsString(provinces);//将数据序列化为json字符串
} catch (JsonProcessingException e) {
e.printStackTrace();
}
jedis.set("province", province_json);//将json写入到redis缓存中
jedis.close();//关闭连接
} else {
System.out.println("redis中有数据,查询redis缓存中........");
}
return province_json;//返回json
}
我们在servlet中就可以直接调用该方法得到json字符串,
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ProvinceService service=new ProvinceServiceImpl();
String json = service.findAllJson();//调用redis缓存,获取省份数据的json字符串
System.out.println(json);
response.setContentType("application/json;charset=utf-8");//设置response格式
response.getWriter().write(json);//将json写入response
}
然后启动服务器,可以看到第一次走的是数据库,刷新页面后第二次就走的redis缓存,
注意:使用redis缓存一些不经常发生变化的数据。
数据库的数据一旦发生改变(即当数据库的表执行 增删改 等相关操作时),则需要将redis缓存数据清空,然后再次存入。