redis
一 Redis
1 NoSQL
介绍
NoSQL,泛指非关系型的数据库,NoSQL即Not-Only SQL,它可以作为关系型数据库的良好补充。
为什么学习NoSQL?
互联网产品的特点:
高并发(多人同时访问)、高性能(读写速度快)、高可用(万一某一台服务器挂掉了,还可以正常访问)、海量数据
传统的关系数据库就出现了性能和扩展的瓶颈,非关系型数据库应时而生,解决了互联网三高和海量数据的问题
NoSQL和关系型数据库是互补关系,在各自的应用场景都有自己特点,一般情况下我们使用关系数据库来持久化数据,对一切热点数据通过Nosql来作为缓存
主流产品
分类 | 特点 | 代表产品 |
---|---|---|
键值存储 | 数据一般存在内存中,读写速度快(10w/s),适合作为缓存服务 当成map即可 | redis |
文档型数据库 | 数据结构要求不严格,适合存储结构不确定或者价值较低的数据 | mongodb |
列存储数据库 | 查找速度快,更容易进行分布式扩展,适合作为文件存储服务 | Hbase |
图形数据库 | 使用“图结构”进行存储,适合做社交网络计算等等 | Neo4j |
图形数据库介绍
https://blog.csdn.net/xlgen157387/article/details/79085901
redis是一种键值对的非关系型数据库
2 Redis介绍
Redis(Remote Dictionary Server)是用C语言开发的一个开源的高性能键值对数据库。它的所有数据都是保存在内存中的,这也就决定了其读写速度之快,是其它硬盘保存数据的系统所无法匹敌的。
官方曾经给出过一组测试数据,50个并发执行100000个请求: 读的速度是110000次/s,写的速度是81000次/s
3 Redis安装和使用
-
下载
Reids官网地址:http://redis.io
中文网地址:https://www.redis.net.cn/
GitHub地址:https://github.com/MSOpenTech/redis/tags -
安装
windows版本解压缩即可 -
目录(windows版)
redis.windows.conf:配置文件
redis-cli.exe:redis的客户端
redis-server.exe:redis服务器端 -
启动
-
方式1 :
- 先双击服务器端 redis-server.exe
- 再双击客户端 redis-cli.exe
-
方式2:在当前解压后的目录下 cmd进入dos窗口
- 先启动服务器端 redis-server.exe redis.windows.conf
- 再启动客户端 redis-cli.exe
redis服务器默认的端口号:6379
客户端连接其他服务器,需要在cmd中输入:
- redis-cli.exe -h ip地址 -p 端口号
-
-
关闭
- 建议在客户端使用:shutdown
- 也可以直接点 x
4 Redis数据结构【重点】
redis存储的是:key-value格式的数据,其中key是字符串类型,value支持五种数据类型(存到数据库中都是字符串)
- string
- hash (map)
- list (linkedlist)
- set (无序唯一)
- zset (sorted set 有序set)
二 redis命令操作
1 字符串(string)
添加 : set 键名 值
查询 : get 键名
删除 : del 键名
新增且设置时间 : se tex 键名 秒数 值
查看剩余存活时间 : ttl 键名
若返回正整数,就是剩余存活时间
若返回-1,永不过时
若返回-2,服务器没有此键值对
扩展:
-
incr 键名 :自增
-
decr 键名 :自减
可以作为数据库的主键的值
2 哈希(hash)
value是一个map
添加 : hset 键名 字段 子值
查询 : hget 键名 字段
删除 : hdel 键名 字段
扩展命令:
- hmset 键名 字段1 子值1 字段2 子值2 :一次性给键设置多个字段和值
- hmget 键名 字段1 字段2 :一次获取多个字段的值
- hgetall 键名 :获取所有的字段和值
https://www.runoob.com/redis/redis-hashes.html
3 redis客户端安装使用
Another Redis Desktop Manager
4 列表(list)
LinkedList 有序
添加
- lpush 键名 值1 值2 rpop
- rpush 键名 值1 值2 值3 … 从右边开始一个一个的往末尾上加
查询 : lrange 键名 开始索引 结束索引
- lrange 键名 0 -1 :查询所有
删除 :
- lpop 键名 : 返回且移除左边的第一个元素
- rpop 键名 : 返回且移除右边的第一个元素
扩展命令:
- llen 键名 :获取集合的长度
list可以做队列使用, 左边加,右边弹
流量削峰
5 集合(set)
唯一无序
添加 : sadd 键名 值1 值2 值3
删除:srem 键名 值1 值2
查询
- smembers 键名 : 查询所有
- sismember 键名 值 : 判断该值是否存在
扩展命令:
- 长度 : scard 键名
- 集合运算 :
- sunion 键1 键2 :并集 我有加你有
- sdiff 键1 键2 :差集 我有你没有
- sinter 键1 键2 :交集 我有你也有
- 也可以将他们的运行结果放入一个新集合中
- sxxxstore 新集合 键1 键2
- 例如: sunionstore s3 s1 s2 将s1和s2的并集放入s3中
交集运算
6 有序集合(zset)
有序且唯一(sorted set)
添加 : zadd 键名 分数1 字段1 分数2 字段2
查询 : zrange 键名 开始索引 结束索引 [withscores]
查询某一个字段的分数 : zscore 键名 字段
删除 : zrem 键名 字段1 字段2
排行榜 zrevrange
7 通用命令
del 键名 : 删除指定key
keys * :查询当去数据库下所有的key
type 键名 : 查询该键的类型
exists 键名 : 查询该键是否存在当前的数据库中
扩展命令:
- 一个redis实例包含16个数据库,默认使用的索引为0的数据库 : select 索引
- 建议大家开发项目的时候 一个项目用一个实例
- flushdb : 清除当前数据库中所有的内容
- flushall : 清除当前实例中所有数据库中的内容
三 redis持久化
Redis的高性能是由于其将所有数据都存储在了内存中,为了使Redis在重启之后仍能保证数据不丢失,需要将数据从内存中同步到硬盘中,这一过程就是持久化。Redis支持两种方式的持久化(RDB和AOF),可以同时使用。
-
RDB(快照方式):默认方式.文件名称:dump.rdb
rdb默认的快照规则:
save 900 1 当15分钟之内有1个key发生变化就拍照
save 300 10 当300秒之内有10个key发生变化就拍照
save 60 10000 当60秒之内有1w个key发生变化就拍照
重启服务器后,默认是从rdb文件中恢复数据到内存中
-
AOF(追加命令到文件中),需要手动开启的,文件名称:appendonly.aof
appendfsync always 每执行一个命令,就会把命令追加到文件中,和关系型数据库相似
appendfsync everysec 每一秒钟,把所有的操作命令追加到文件中
appendfsync no 不同步,等同于rdb
修改配置文件redis.windows.conf中
appendonly no
将他的值改成yes,启动的时候需要指定配置文件启动,重启服务器就生效,虽然开启了aof,但是rdb依然能用.只不过是,当服务器重启的时候,从aof文件将数据恢复到内存中.
当我们正常关闭redis服务器的时候,他会通过不同的方式将内存中的数据保存到硬盘文件中.再次启动的时候,会去文件中把数据恢复到内存中.
若我们把redis作为缓存使用,建议使用rdb方式,高效; 若数据需要不时的持久化,建议使用aof方式.
缓存是允许有数据丢失.
四 应用场景
- 主键生成器, 使用string类型 ,使用incr操作让值进行自增即可
- 缓存使用, 读取一些经常使用、不常修改且敏感度不强的数据的时候就可以将这些数据放入缓存中
- 读取数据的时候优先去缓存中查找.缓存中有直接返回即可.缓存中若没有,就需要查询关系型数据库,查询到之后,把数据也放入到缓存中一份且返回.
- 若缓存中关联的数据发生变更(修改或者删除),这时候就需要把redis中相关的数据清除掉.这样就可以保证关系型数据库中的数据和缓存中的数据是一致的.
- 流量削峰(list类型的使用,作为队列使用)
- 热点排行榜(zset类型,游戏排行榜,下载排行榜)
- …
五 Jedis
1 什么是Jedis
Redis不仅是使用命令来操作,现在基本上主流的语言都有客户端支持,比如java、C、C#、C++、php、
Node.js、Go等。
在官方网站里列一些Java的客户端,有Jedis、Redisson、Jredis、JDBC-Redis、等其中官方推荐使用Jedis和Redisson。 在企业中用的最多的就是Jedis ,github下载地址:https://github.com/xetorthio/jedis
jedis对redis 相当于 jdbc对关系型数据库
2 快速入门
步骤分析
- 导入依赖
- 编码
- 使用构造器创建jedis对象(连接那个主机,端口号)
- 可以使用方法(redis中有那些命令就会有对应的方法)
- 释放资源
<!--jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
public class TestJedis {
@Test
public void testHello(){
//1. 使用构造器创建jedis对象(连接那个主机,端口号)
Jedis jedis = new Jedis("127.0.0.1",6379);
//2. 可以使用方法(redis中有那些命令就会有对应的方法)
jedis.set("hi","jedis");
String value = jedis.get("hi");//jedis
System.out.println(value);
String value_ = jedis.get("hello147");//null
System.out.println(value_);
//3. 释放资源
jedis.close();
}
}
常用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 |
setex(key,秒数,value) | 设置一个key且设置存活时间 |
3 连接池
jedis连接资源的创建与销毁是很消耗程序性能,所以jedis为我们提供了jedis的池化技术。
步骤分析
- 导入依赖
- 编码
- 创建jedis连接池的配置对象
- 配置连接信息
- 使用配置对象创建jedis连接池(JedisPool)
- 从连接池中获取jedis
- 使用jedis
- 用完之后千万别忘记归还
- 创建jedis连接池的配置对象
@Test
public void testPool(){
//1. 创建jedis连接池的配置对象
JedisPoolConfig config = new JedisPoolConfig();
// - 配置连接信息
config.setMaxTotal(10);//最大连接数
config.setMaxIdle(2);//最大空闲数
//2. 使用配置对象创建jedis连接池(JedisPool)
JedisPool pool = new JedisPool(config,"127.0.0.1",6379);
//3. 从连接池中获取jedis
Jedis jedis = pool.getResource();
//4. 使用jedis
jedis.set("heihei","pool");
System.out.println(jedis.get("heihei"));
//5. 用完之后千万别忘记归还
jedis.close();
}
@Test
public void testPool2(){
//1. 创建jedis连接池的配置对象
JedisPoolConfig config = new JedisPoolConfig();
// - 配置连接信息
config.setMaxTotal(10);//最大连接数
config.setMaxIdle(2);//最大空闲数
config.setMaxWaitMillis(2000);//设置获取连接的超时时间
//2. 使用配置对象创建jedis连接池(JedisPool)
JedisPool pool = new JedisPool(config,"127.0.0.1",6379);
//3. 从连接池中获取jedis 千万别忘记归还,否则过段时间就会报错
for (int i = 0; i < 12; i++) {
Jedis jedis = pool.getResource();
System.out.println(jedis);
}
}
4 抽取工具类
jedis.properties
放在main/resources目录下
jedis.host=127.0.0.1
jedis.port=6379
jedis.maxTotal=50
jedis.maxIdle=10
JedisUtils
public class JedisUtils {
private static JedisPool jedisPool;
//初始化jedis
static {
ResourceBundle bundle = ResourceBundle.getBundle("jedis");
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(Integer.parseInt(bundle.getString("jedis.maxTotal")));
config.setMaxIdle(Integer.parseInt(bundle.getString("jedis.maxIdle")));
jedisPool = new JedisPool(config,bundle.getString("jedis.host"),Integer.parseInt(bundle.getString("jedis.port")));
}
//获取连接的方法
public static Jedis getJedis(){
return jedisPool.getResource();
}
}
六 案例-优化导航条
1 需求:
分类导航条:之前我们在每个页面加载成功的时候都需要发送ajax请求从数据库中获取所有分类(list),这些数据轻易不改变,又不是敏感数据,经常要用.我们就可以把这些数据放入redis中
2 分析
- 使用redis进行优化
- 使用redis的string结构
- redis中key: category_list
- redis中value:list的集合的json字符串
2 步骤分析:
假如没有接口
3 代码实现:
public class CategoryApp {
public String findAllFromRedis() throws JsonProcessingException {
//1.先从redis中获取
Jedis jedis = JedisUtils.getJedis();
String listStr = jedis.get("category_list");
//2.判断有无获取到值
//2.1 若没有获取到,再从mysql中获取
if (StrUtil.isBlank(listStr)) {
SqlSession sqlSession = MybatisUtils.openSession();
CategoryDao categoryDao = sqlSession.getMapper(CategoryDao.class);
List<Category> list = categoryDao.findAll();
MybatisUtils.commitAndClose(sqlSession);
//需要将list集合转成json字符串放入redis中
listStr = new ObjectMapper().writeValueAsString(list);
jedis.set("category_list",listStr);
System.out.println("从mysql中获取");
}else{
//2.2 若获取到了,直接返回
System.out.println("从redis中获取");
}
//释放jedis
jedis.close();
//返回字符串
return listStr;
}
}
CategoryServlet
七 面向接口编程
没有使用面向接口编程遇到的一些问题
软件工程基本原则:高内聚,松耦合(低耦合)
-
高内聚:就是软件或者模块或者类的内部要高度的聚集和关联
例如:mybatis的查询操作.内部封装了获取连接,设置参数,执行sql,封装结果的操作
-
松耦合:就是降低模块间的依赖关系
例如:mybatis切换数据库的操作,只需要切换驱动类和sql即可.不需要修改mybatis的代码.
要想实现这种基本原则,在软件开发中就会有众多原则.今天我们来了解下开闭原则:
-
开闭原则:对修改关闭 对扩展开发
一个好的软件是在不修改源代码的情况下,可以扩展你的功能.而实现开闭原则的关键就是抽象化(使用接口或者抽象类).
案例优化下
提供一个CategoryService实现类
八 对象管理
上面案例依然存在的问题
1 工厂设计模式(能听懂即可)
举个现实生活的栗子:
tom需要一辆车:
方式1:自己买零件 组装
方式2:拿钱提需求,找汽车工厂帮我们提供这种车
java中
之前需要一个对象,使用new
创建出来;现在及以后需要一个对象,直接去工厂获取,工厂可以帮我们生产需要的对象
进入企业开发,我们不再手动 new 对象,都是从某一个工厂来获得
2 工厂实现(不需要写,只需要理解思想)
思路分析
步骤分析
- 编写一个mybeans.xml,放在src下
- 将所有的service的实现类配置到此文件中(指定的格式)
- 编写一个工厂类 MyBeanFactory
- 编写一个成员变量:map,用来存放所有的service实现类对象
- 编写一个静态代码块:在工厂类加载的时候把xml文件进行加载且解析,创建配置好的所有的实现类对象,放入map中
- 编写一个静态方法getBean:通过指定的key(id)去map中查询指定的实现类的对象返回
代码实现
编写myBeans.xml 配置文件
<beans>
<bean id="userService" class="cn.itcast.service.impl.UserServiceImpl"/>
</beans>
编写MyBeanFactory类
public class MyBeanFactory {
//1 编写一个成员变量:map,用来存放所有的service实现类对象
private static Map<String,Object> map;
//2 编写一个静态代码块:在工厂类加载的时候把xml文件进行加载且解析,创建配置好的所有的实现类对象,放入map中
static {
map = new HashMap<>();
InputStream is = null;
try {
//以下代码只需要能看明白即可
//a.加载配置文件
is = MyBeanFactory1.class.getClassLoader().getResourceAsStream("mybeans.xml");
//b.使用dom4j读取配置文件
Document doc = new SAXReader().read(is);
//c.使用xpath获取所有的bean标签 list集合
List<Element> list = doc.selectNodes("/beans/bean");
//d.遍历list集合,获取每个bean标签
for (Element element : list) {
//e.获取bean标签的id属性(作为map的key),获取class属性值,使用反射创建出对象(作为map的value),放入map中
String id = element.attributeValue("id");
String clazz = element.attributeValue("class");
Object obj = Class.forName(clazz).newInstance();
map.put(id,obj);
}
System.out.println("beanFactory 构建完毕,里面对象的个数为:"+map.size());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//3 编写一个静态方法getBean:通过指定的key(id)去map中查询指定的实现类的对象返回
public static Object getBean(String id){
return map.get(id);
}
}
思考
目前配置文件是在第一次加载工厂类的时候加载的,且配置文件名字也是固定的.
这样第一次使用的人会特别慢,可以怎么办?
监听器:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vBAkAwuO-1621483835798)(redis.assets/image-20200626110636722.png)]
MyBeanFactory:
public class MyBeanFactory {
private static Map<String,Object> map = new HashMap<>();
public static void init(InputStream is){
try {
//使用dom4j+xpath 解析myBeans.xml 初始化map集合
//2.获取document对象
Document document = new SAXReader().read(is);
//3.使用xpath获取所有的bean标签
List<Element> list = document.selectNodes("/beans/bean");
//4.遍历集合获取到每个bean标签
for (Element element : list) {
//5.获取每个bean标签的id属性
String id = element.attributeValue("id");
//6.获取每个bean标签的class属性
String clazz = element.attributeValue("class");
//7.使用反射技术创建class对象的实现类的对象
Object obj = Class.forName(clazz).newInstance();
//8.将id属性作为key,实现类对象作为value放入到map中
map.put(id,obj);
}
System.out.println(map.size());
} catch (DocumentException e) {
e.printStackTrace();
throw new RuntimeException("加载配置文件异常");
} catch (IllegalAccessException |InstantiationException |ClassNotFoundException e) {
e.printStackTrace();
throw new RuntimeException("解析配置文件时出现了异常");
} finally {
//释放资源
if (is!=null) {
try {
is.close();
} catch (IOException e) {
}
is = null;
}
}
}
//使用id查询指定的实现类对象
public static Object getBean(String id){
return map.get(id);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dJz6t2wz-1621483835799)(03.用户&面向接口编程&地址.assets/image-20200626110704704.png)]