能够说出redis的常用数据类型
能够使用redis的string操作命令
能够使用redis的hash操作命令
能够使用redis的list操作命令
能够使用redis的set操作命令
能够使用redis的zset操作命令
能够使用jedis操作Redis
能够理解Redis持久化
NoSQL(NoSQL = Not Only SQL),意即“不仅仅是SQL”,是一项全新的数据库理念,泛指非关系型的数据库。
键值(Key-Value)存储数据库 Redis
列存储数据库(分布式)
文档型数据库 (Web应用与Key-Value类似,Value是结构化的)Mongo DB
图形(Graph)数据库(图结构)
在大数据存取上具备关系型数据库无法比拟的性能优势,例如:
易扩展
NoSQL数据库种类繁多,但是一个共同的特点都是去掉关系数据库的关系型特性。数据之间无关系,这样就非常容易扩展。也无形之间,在架构的层面上带来了可扩展的能力。
大数据量,高性能
NoSQL数据库都具有非常高的读写性能,尤其在大数据量下,同样表现优秀。这得益于它的无关系性,数据库的结构简单、在内存中存取数据。
灵活的数据模型
NoSQL无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式。而在关系数据库里,增删字段是一件非常麻烦的事情。如果是非常大数据量的表,增加字段简直就是一个噩梦。这点在大数据量的Web2.0时代尤其明显。
高可用
NoSQL在不太影响性能的情况,就可以方便的实现高可用的架构。比如Cassandra,HBase模型,通过复制模型也能实现高可用。
1.4. 为什么要学习NoSQL
Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库,数据是保存在内存里面的. 官方提供测试数据,50个并发执行100000个请求,读的速度是110000次/s,写的速度是81000次/s ,且Redis通过提供多种键值数据类型来适应不同场景下的存储需求,目前为止Redis支持的键值数据类型如下:
端口:6379,默认不需要密码
yum -y install gcc-c++
tar -zxf redis-4.0.14.tar.gz
cd redis-4.0.14
make
mkdir /usr/local/redis
make install PREFIX=/usr/local/redis
cd /usr/local/redis/bin
cp /root/redis-4.0.14/redis.conf /usr/local/redis/bin -- 这句命令的作用是将redis-4.0.14中的redis.conf文件拷贝到bin目录
# 修改配置文件
vi redis.conf
# Redis后台启动
修改 daemonize 为 yes
# Redis服务器可以跨网络访问
修改 bind 为 0.0.0.0
# 开启aof持久化,这个可以不做
appendonly yes
./redis-server redis.conf
redis中存储的数据是以key-value的形式存在的.其中value支持5种数据类型 .在日常开发中主要使用比较多的有字符串、哈希、字符串列表、字符串集合四种类型,其中最为常用的是字符串类型。
字符串(String)
哈希(hash) 类似HashMap
字符串列表(list)
字符串集合(set) 类似HashSet
有序的字符串集合(sorted-set或者叫zset)
key不要太长(不能>1024个字节)
也不要太短 . 可读性差.
项目名:模块:key eg: mm:user:name
string是redis最基本的类型,用的也是最多的,一个key对应一个value。 一个键最大能存储512MB.
缓存功能:字符串最经典的使用场景,redis作为缓存层,Mysql作为储存层,绝大部分请求数据都是在redis中操作,由于redis具有支撑高并发特性,所以缓存通常能起到加速读写和降低 后端压力的作用。
计数器功能:比如视频播放次数,点赞次数。
ID递增
命令 | 描述 |
---|---|
SET key value(重点) | 设置指定 key 的值 |
GET key(重点) | 获取指定 key 的值 |
DEL key | 删除key |
GETSET key value | 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。 |
SETEX key seconds value(重点) | 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。 |
SETNX key value | 只有在 key 不存在时设置 key 的值。 |
INCR key(重点) | 将 key 中储存的数字值增一。 |
INCRBY key increment | 将 key 所储存的值加上给定的增量值(increment) 。 |
DECR key | 将 key 中储存的数字值减一。 |
DECRBY key decrement | key 所储存的值减去给定的减量值(decrement) 。 |
商品编号、订单号采用string的递增数字特性生成。
定义商品编号key:product:id
192.168.101.3:7003> INCR product:id
(integer) 2
192.168.101.3:7003> INCR product:id
(integer) 3
Redis中hash 是一个键值对集合。
Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。
Redis存储hash可以看成是String key 和String value的map容器. 也就是说把值看成map集合.
它特别适合存储对象相比较而言,将一个对象类型存储在Hash类型里要比存储在String类型里占用更少的内存空间并方便存取整个对象
用一个对象来存储用户信息,商品信息,订单信息等等。
命令 | 命令描述 |
---|---|
hset key filed value | 将哈希表 key 中的字段 field 的值设为 value |
hmset key field1 value1 [field2 value2]…(重点) | 同时将多个 field-value (字段-值)对设置到哈希表 key 中 |
hget key filed | 获取存储在哈希表中指定字段的值 |
hmget key filed1 filed2 (重点) | 获取多个给定字段的值 |
hdel key filed1 [filed2] (重点) | 删除一个或多个哈希表字段 |
hlen key | 获取哈希表中字段的数量 |
del key | 删除整个hash(对象) |
HGETALL key (重点) | 获取在哈希表中指定 key 的所有字段和值 |
HKEYS key | 获取所有哈希表中的字段 |
HVALS key | 获取哈希表中所有值 |
ArrayList使用数组方式存储数据,所以根据索引查询数据速度快,而新增或者删除元素时需要设计到位移操作,所以比较慢。
LinkedList使用双向链表方式存储数据,每个元素都记录前后元素的指针,所以插入、删除数据时只是更改前后元素的指针指向即可,速度非常快。然后通过下标查询元素时需要从头开始索引,所以比较慢,但是如果查询前几个元素或后几个元素速度比较快。
列表类型(list)可以存储一个有序的字符串列表(链表)
,常用的操作是向列表两端添加元素,或者获得列表的某一个片段。
列表类型内部是使用双向链表(double linked list)实现的,所以向列表两端添加元素的时间复杂度为0(1),获取越接近两端的元素速度就越快。这意味着即使是一个有几千万个元素的列表,获取头部或尾部的10条记录也是极快的。
如好友列表,粉丝列表,消息队列,最新消息排行等。
rpush方法就相当于将消息放入到队列中,lpop/rpop就相当于从队列中拿去消息进行消费
命令 | 命令描述 |
---|---|
lpush key value1 value2…(重点) | 将一个或多个值插入到列表头部(左边) |
rpush key value1 value2…(重点) | 在列表中添加一个或多个值(右边) |
lpop key(重点) | 左边弹出一个 相当于移除第一个 |
rpop key(重点) | 右边弹出一个 相当于移除最后一个 |
llen key | 返回指定key所对应的list中元素个数 |
LINDEX key index | 通过索引获取列表中的元素 |
LINSERT key BEFORE| AFTER pivot value | 在列表的元素前或者后插入元素 |
商品评论列表
Redis的Set是string类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
Redis 中 集合是通过哈希表实现的,所以添加,删除,查找的时间复杂度都是O(1)。集合中最大的成员数为 2的32次方 -1 (4294967295, 每个集合可存储40多亿个成员)。
Redis还提供了多个集合之间的交集、并集、差集的运算
特点:无序+唯一
投票记录
共同好友、共同兴趣、分类标签
命令 | 命令描述 |
---|---|
sadd key member1 [member2] (重点) | 向集合添加一个或多个成员 |
srem key member1 [member2] | 移除一个成员或者多个成员 |
smembers key | 返回集合中的所有成员,查看所有 |
SCARD key | 获取集合的成员数 |
SPOP key | 移除并返回集合中的一个随机元素 |
SDIFF key1 [key2] (重点) | 返回给定所有集合的差集 |
SUNION key1 [key2] (重点) | 返回所有给定集合的并集 |
SINTER key1 [key2] (重点) | 返回给定所有集合的交集 |
差集:第一个集合有的第二个集合没有的,哪个在前依哪个为标准。
共同好友
Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
有序集合的成员是唯一的,但分数(score)却可以重复。
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。
特点: 有序(根据分数排序)+唯一
排行榜:例如视频网站需要对用户上传的视频做排行榜.
命令 | 命令描述 |
---|---|
ZADD key score member [score member …](重点) | 增加元素 |
ZSCORE key member | 获取元素的分数 |
ZREM key member [member …] | 删除元素 |
ZCARD key | 获得集合中元素的数量 |
ZRANGE key start stop[WITHSCORES] (重点) | 获得排名在某个范围的元素列表 |
ZREVRANGE key start stop (重点) | 按照分数从高到低排序 |
商品销售排行榜
--商品编号1001的销量是9,商品编号1002的销量是10
ZADD items:sellsort 9 1001 10 1002
--商品编号1001的销量加1
ZINCRBY items:sellsort 1 1001
--商品销量前10名()
ZREVRANGE items:sellsort 0 9
keys *: 查询所有的key
exists key:判断是否有指定的key 若有返回1,否则返回0
expire key 秒数:设置这个key在缓存中的存活时间 (重要)
ttl key:展示指定key的剩余时间
若返回值为 -1:永不过期
若返回值为 -2:已过期或者不存在
del key:删除指定key (重要)
rename key 新key:重命名
type key:判断一个key的类型
ping :测试连接是否连接
redis默认是16个数据库, 编号是从0~15. 【默认是0号库】
Redis的高性能是由于其将所有数据都存储在了内存中,为了使Redis在重启之后仍能保证数据不丢失,需要将数据从内存中同步到硬盘(文件)中,这一过程就是持久化。
Redis支持两种方式的持久化,一种是RDB方式,一种是AOF方式。可以单独使用其中一种或将二者结合使用。
RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。 这种方式是默认已经开启了,不需要配置.
其中,上面配置的是RDB方式数据持久化时机:
关键字 | 时间(秒) | key修改数量 | 解释 |
---|---|---|---|
save | 900 | 1 | 每900秒(15分钟)至少有1个key发生变化,则dump内存快照 |
save | 300 | 10 | 每300秒(5分钟)至少有10个key发生变化,则dump内存快照 |
save | 60 | 10000 | 每60秒(1分钟)至少有10000个key发生变化,则dump内存快照 |
AOF持久化机制会将每一个收到的写命令都通过write函数追加到文件中,默认的文件名是appendonly.aof。 这种方式默认是没有开启的,要使用时候需要配置.
在redis.windows.conf配置文件中有如下配置:
将appendonly修改为yes, 但是启动redis的时候需要指定该文件,也就是意味着不能直接点击了, 需要输入命令启动:
开启aof持久化机制后,默认会在目录下产生一个appendonly.aof文件
关键字 | 持久化时机 | 解释 |
---|---|---|
appendfsync | always | 每执行一次更新命令,持久化一次 |
appendfsync | everysec | 每秒钟持久化一次 |
appendfsync | no | 不持久化 |
jedis的介绍
Jedis的入门
Redis不仅是使用命令来操作,现在基本上主流的语言都有客户端支持,比如java、C、C#、C++、php、Node.js、Go等。 在官方网站里列一些Java的客户端,有Jedis、Redisson、Jredis、JDBC-Redis、等其中官方推荐使用Jedis和Redisson。 在企业中用的最多的就是Jedis,Jedis同样也是托管在github上.
说白了Jedis就是使用Java操作Redis的客户端(工具包)
地址:https://github.com/xetorthio/jedis。
文档地址:http://xetorthio.github.io/jedis/
方法 | 解释 |
---|---|
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) | 列表右面弹栈 |
sadd(String key, String… members) | 设置set类型的数据 |
zrange(String key, long start, long end) | 获得在某个范围的元素列表 |
del(key) | 删除key |
需求: 使用java代码操作Redis 进行增(改)删查
步骤:
@Test
public void test01(){
//1.创建于服务器的连接
Jedis jedis = new Jedis("localhost",6379);
//2. 调用jedis对象的方法操作redis数据库: 要调用的方法名和redis操作的命令是一样的
jedis.set("user:password","123");
//3. 关闭连接
jedis.close();
}
@Test
public void test02(){
//目标:从redis服务器中获取user:nickname的值
//1.创建于服务器的连接
Jedis jedis = new Jedis("localhost",6379);
//2. 调用get(key)
String nickname = jedis.get("user:nickname");
System.out.println(nickname);
jedis.close();
}
@Test
public void test03(){
//1.创建于服务器的连接
Jedis jedis = new Jedis("localhost",6379);
jedis.setex("user:email",20,"[email protected]");
jedis.close();
}
jedis连接资源的创建与销毁是很消耗程序性能,所以jedis为我们提供了jedis的池化技术,jedisPool在创建时初始化一些连接资源存储到连接池中,使用jedis连接资源时不需要创建,而是从连接池中获取一个资源进行redis的操作,使用完毕后,不需要销毁该jedis连接资源,而是将该资源归还给连接池,供其他请求使用。
需求: 从Jedis的连接池里面获得jedis
步骤:
@Test
public void test04(){
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(30);
jedisPoolConfig.setMaxIdle(20);
jedisPoolConfig.setMaxWaitMillis(3000);
//使用jedis连接池
JedisPool jedisPool = new JedisPool(jedisPoolConfig,"localhost",6379);
//从连接池获取连接
Jedis jedis = jedisPool.getResource();
//调用jedis的方法
String nickname = jedis.get("user:nickname");
System.out.println(nickname);
//将连接归还回连接池
jedis.close();
}
目的: 1.保证连接池只有一个 2.简化获得jedis对象的代码
步骤:
配置文件jedisconfig.properties
jedis.maxTotal=30
jedis.maxIdle=20
jedis.maxWaitMillis=3000
jedis.host=localhost
jedis.port=6379
JedisUtil工具类
package com.itheima.utils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.ResourceBundle;
/**
* 包名:com.weijisheng.utils
* @author Leevi
* 日期2021-11-29
* 使用jedis的步骤:
* 1. 拷贝jar包
* 2. 拷贝配置文件
* 3. 拷贝工具类
*/
public class JedisUtil {
private static JedisPool jedisPool;
static {
//读取jedisconfig.properties配置文件
ResourceBundle resourceBundle = ResourceBundle.getBundle("jedisconfig");
Integer maxTotal = Integer.valueOf(resourceBundle.getString("jedis.maxTotal"));
Integer maxIdle = Integer.valueOf(resourceBundle.getString("jedis.maxIdle"));
Integer maxWaitMillis = Integer.valueOf(resourceBundle.getString("jedis.maxWaitMillis"));
String host = resourceBundle.getString("jedis.host");
Integer port = Integer.valueOf(resourceBundle.getString("jedis.port"));
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(maxTotal);
jedisPoolConfig.setMaxIdle(maxIdle);
jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
//使用jedis连接池
jedisPool = new JedisPool(jedisPoolConfig,host,port);
}
/**
* 从连接池获取连接
* @return
*/
public static Jedis getJedis(){
return jedisPool.getResource();
}
}
访问index.html页面,使用ajax请求加载省份列表(响应json)
axios.get('province').then(function(response){
//获得数据赋值, 绑定
})
//1.调用业务 获得List list
//2.把list转成json响应
public List findAll(){
//调用Dao
}
CREATE TABLE `province` (
`pid` int NOT NULL AUTO_INCREMENT,
`pname` varchar(40) DEFAULT NULL,
PRIMARY KEY (`pid`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
INSERT INTO `province` VALUES ('1', '广东');
INSERT INTO `province` VALUES ('2', '湖北');
INSERT INTO `province` VALUES ('3', '湖南');
INSERT INTO `province` VALUES ('4', '四川');
INSERT INTO `province` VALUES ('5', '山东');
INSERT INTO `province` VALUES ('6', '山西');
INSERT INTO `province` VALUES ('7', '广西');
public class Province implements Serializable {
private Integer pid;
private String pname;
@Override
public String toString() {
return "Province{" +
"pid=" + pid +
", pname='" + pname + '\'' +
'}';
}
public Integer getPid() {
return pid;
}
public void setPid(Integer pid) {
this.pid = pid;
}
public String getPname() {
return pname;
}
public void setPname(String pname) {
this.pname = pname;
}
}
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>省份展示页面title>
<script src="js/axios-0.18.0.js">script>
<script src="js/vuejs-2.5.16.js">script>
head>
<body>
<div id="app">
<select>
<option>请选择省份option>
<option v-for="(province,index) in provinceList" :value="province.pid" v-html="province.pname">option>
select>
div>
<script>
var vue = new Vue({
el:"#app",
data:{
provinceList:[]
},
methods:{
findAll(){
//发送异步请求,获取所有省份信息
axios.get("province?action=findAll").then(response=>{
this.provinceList = response.data.data
})
}
},
created(){
this.findAll()
}
});
script>
body>
html>
@WebServlet("/province")
public class ProvinceServlet extends BaseServlet {
private ProvinceService provinceService = new ProvinceService();
public void findAll(HttpServletRequest request, HttpServletResponse response) throws IOException {
ResultBean resultBean = new ResultBean(true);
try {
//调用业务层的方法查询所有省份信息
List<Province> provinceList = provinceService.findAll();
//将响应数据封装到ResultBean对象中
resultBean.setData(provinceList);
} catch (Exception e) {
e.printStackTrace();
resultBean.setFlag(false);
resultBean.setErrorMsg("查询省份失败");
}
//将resultBean对象转换成json字符串输出到客户端
JsonUtils.printResult(response,resultBean);
}
}
/**
*
* 优化前: 业务层直接掉dao层的方法查询mysql数据库
*
* 优化后:
* 1. 从redis中查询省份信息
* 2. 如果查询不到则调用dao层的方法到mysql数据库查询,将查询到的数据存储到redis
* 3. 如果查询到了,就直接使用redis中的数据
*/
public class ProvinceService {
private ProvinceDao provinceDao = new ProvinceDao();
public List<Province> findAll() throws Exception {
//1. 从redis中查询省份信息
Jedis jedis = JedisUtil.getJedis();
String jsonStr = jedis.get("province:list");
//2. 判断jsonStr是否为null
if (jsonStr == null) {
//说明redis中没有存储省份信息
//调用dao层的方法查询省份信息
List<Province> provinceList = provinceDao.findAll();
//将provinceList转换成jsonStr
jsonStr = JSON.toJSONString(provinceList);
//将jsonStr存储到redis
jedis.set("province:list",jsonStr);
}
//3. 将jsonStr转换成List
List<Province> list = JSON.parseArray(jsonStr, Province.class);
jedis.close();
return list;
}
}
package com.weijisheng.dao;
import com.weijisheng.pojo.Province;
import com.weijisheng.utils.DruidUtil;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.List;
/**
* @author weijisheng
* @create 2021-11-29 14:53
*/
public class ProviceDao {
public List<Province> findAll() throws SQLException {
DataSource dataSource = DruidUtil.getDataSource();
QueryRunner queryRunner = new QueryRunner(dataSource);
List<Province> query = queryRunner.query("select *from province", new BeanListHandler<>(Province.class));
return query;
}
}