nosql分类
键值key-value存储的nosql 1.redis,Memcached
文档型数据库 MongoDb
图形数据库 Graph
redis和memcached是key-value的nosql,主要用来做缓存的
redis是一个高性能的开源的、c语言开发的nosql,数据保存在内存中
redis是以key-value形式存储,和传统的关系型数据库不一样,不一定遵循传统数据库的一些
基本要求,比如说,不遵循sql标准,事务,表结构等等,redis严格上不是一种数据库,是一种
数据结构化存储方法的集合
数据结构:数组,list,set,map
redis提供了一堆操作方法,我们使用这些方法就可以存入字符串,组织成各种类型数据结构
(String,list,set,map等),使用起来更加方便。
特点(优势)
1.数据保存在内存,存取速度快,并发能力强
2.支持存储的value类型相对更多,包括String(字符串),list(列表),set(集合),zset(sorted set–有序集合)和hash(哈希)
3.支持集群(主从同步),数据可以主服务器向任意数量从服务器上同步,从服务器可以是关联其他从服务器的主服务器。
4.支持持久化,可以将数据保存在硬盘的文件中
5.支持订阅/发布功能 qq群-群发
总结:
1.开源免费
2.数据存储:存放在内存,还支持持久化-存取速度快,并发能力强,数据安全高
3.支持value类型更多
4.支持多个语言客户端
5.还支持集群(支持高并发,海量数据)
存储过期:一个数据存储时为他设置过期时间,时间一到数据就没有了,例如道具,会员,优惠券,订单,红包等
入门:key
set name “zs” 设置name的值为zs
get name
expire name 20 设置name的过期时间为20s
ttl name 查看name还有多少剩余时间
keys * 查看所有的key
del name 删除name
设置密码
requirepass 123456 //在redis配置文件中redis.windows.config(前面不能有空格)
启动的时候要用配置文件启动 redis-server.exe redis.windows.conf,否则不成功
string
mset age 18 id 01 设置多个
mget age id 获取多个
incr age 自增
decr age 自减
incrby age 5 自增5个
decrby age 5 自减5个
flushall 清空整个redis服务器数据,所有数据库清空
flushdb 清空当前库 ,redis默认有16个数据库,默认是0,切换1,select 1
list
lpush stars ls //从左给stars插入ls
rpush stars zs //从右给stars插入zs
lrange stars 0 -1 //展示stars所有值
lpop stars //删除stars最左边的值
rpop stars //删除stars最右边的值
lrem stars 2 ls //从左开始删除stars 2个ls
lrem stars 0 ls //删除所有的ls
lrem stars -2 ls //从右开始删除stars 2个ls
lindex 2 //展示下标2
ltirm stars 0 1 //只保留stars下标0到1
hash则是h开头
连接redis
Jedis jedis = new Jedis("localhost");
// jedis.auth("123456");//密码,配置文件中写的redis.windows.conf,没有可以不写
jedis.set("name","肖战");
System.out.println(jedis.get("name"));
jedis.close();
连接池连接
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(10);//最大连接池
config.setMaxIdle(2);//最大空闲连接
config.setMaxWaitMillis(2000);//最大等待时间
config.setTestOnBorrow(true);//在获取连接的时候验证连接是否有效
JedisPool pool = new JedisPool(config,"localhost",6379,2000,null,0);//配置,ip,端口,超时时间,密码,数据库
Jedis jedis = pool.getResource();//获取连接
jedis.flushAll();//清空所有数据库数据
jedis.set("name","zs");
System.out.println(jedis.get("name"));
jedis.close();//如果检测到使用了连接池就是归还,否则就是关闭连接
pool.destroy();//销毁连接池对象
封装工具类测试
package org;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public enum JedisUtil {
INSTANCE;
static JedisPool pool =null;
static{
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(10);//最大连接池
config.setMaxIdle(2);//最大空闲连接
config.setMaxWaitMillis(2000);//最大等待时间
config.setTestOnBorrow(true);//在获取连接的时候验证连接是否有效
pool = new JedisPool(config,"localhost",6379,2000,null,0);//配置,ip,端口,超时时间,密码,
}
public Jedis getConnection(){
return pool.getResource();
}
public void closeConnection(Jedis jedis){
if(jedis!=null) {
jedis.close();
}
}
}
=============================================测试
Jedis jedis = JedisUtil.INSTANCE.getConnection();
jedis.flushAll();
jedis.set("name","zs");
jedis.set("age","15");
jedis.keys("*").forEach(System.out::println);
jedis.del("name");
System.out.println(jedis.exists("name"));
JedisUtil.INSTANCE.closeConnection(jedis);
springdata:spring对数据操作支持规范
springboot使用redis
pom依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring.redis.database=0
spring.redis.port=6379
spring.redis.host=localhost
#连接池最大连接数
spring.redis.jedis.pool.max-active=8
#连接池最大空闲连接
spring.redis.jedis.pool.max-idle=8
#连接池最大阻塞等待时间(复数没有限制)
spring.redis.jedis.pool.max-wait=2000ms
#连接池中最小空闲连接
spring.redis.jedis.pool.min-idle=0
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate; //操作字符串
@Test
public void test(String[] args) {
System.out.println(redisTemplate);
redisTemplate.opsForValue().set("name","张三");
System.out.println(redisTemplate.opsForValue().get("name"));
stringRedisTemplate.opsForValue().set("age","ls");
redis常用配置(redis.windows.conf)
保存策略,只要满足,60s操作10000次,300s操作10次,900s操作1次,就把内存持久化到磁盘
(绑定)bind 127.0.0.1 只允许ip地址为127.0.0.1的可以访问
protected-mode yes 保护模式,开启
port 6379 端口号
timeout 0 最大超时时间,即不操作(为0表示不归还)
loglevel notice 日志级别为notice
logfile " " 指定日志文件在当前路径下
redis持久化配置(重点)
rdb就是上面说的的save
满足save条件的持久化到磁盘,不满足持久化到日志中再保存到磁盘
小结:
淘汰策略:
淘汰一些数据,达到redis数据都是有效的,节约内存资源。选择合适的淘汰策略
volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰
allkeys-lru:从所有数据集中挑选最近使用最少的数据淘汰
redis确定驱逐某个键值对后,会删除这个数据并,并将这个数据变更消息发布到本地(AOF持久化)
和从机(主从连接)
如何使用?
在配置文件中,设置淘汰策略,设置内存大小maxmemory
面试题
1.那些场景使用redis?
点赞,缓存,防攻击,做计数器,去重
2.使用什么来操作redis
spring-boot-starter-data-redis
3.你使用过memcached?
没有,使用redis,redis比他优秀,支持的数据类型更多,不仅支持存储在内存中,还支持在磁盘中
4.为什么要使用redis-优点
存储在内存中效率高,支持很多数据类型,支持存储在内存和磁盘中
5.redis怎么实现栈和队列
通过控制list一边进一边出,就是队列,控制进出同一个地方就是栈
6.事务四大特性
原子性,一致性,隔离性,持久性
7.为什么要使用连接池
节省资源,提高效率
8.redis是怎么存储数据的?
数据存储在内存中,为了安全支持持久化,rdb,aof
9.redis数据满了时会怎么办?
淘汰策略,在配置文件中设置一个最大内存和淘汰策略
什么是缓存?
通常将数据从数据库中同步一份到内存中,客户端直接从内存中查询数据,减少了和数据库的交互次数,提高查询性能(内存读写很快),减轻数据库压力
降低数据库压力,直接从缓存中查询
提高查询效率,内存级别查询效率要高于磁盘查询
哪些数据适合缓存?
1.经常查询的热点数据
2.不经常变的数据(数据变化会导致缓存中的数据跟着变,如果比较频繁,性能开销比较大)
缓存的流程
1.第一次查询,先看缓存是否有,有就返回
2.如果缓存没有数据,去数据库查询数据
3.把数据同步一份到缓存
4.返回数据
注意:数据库数据被修改,缓存要清空,或者重置
缓存分类:
传统缓存:mybatis二级缓存,jpa二级缓存。数据放到当前服务器内存中
中央缓存:redis,memcached等key value nosql
传统缓存方案的优缺点
1.再集群环境中,每个应用都有一个本地缓存,当缓存发生改变会造成不同步问题
2.本地缓存本身要占用应用的内存空间
分布式缓存方案-中央缓存
使用redis作为缓存,解决缓存不同步问题,redis是独立的服务,缓存不用占用应用本身的内存空间
springboot操作
1.依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.配置
spring:
redis:
database: 0
port: 6379
host: localhost
password: 123456
jedis:
pool:
max-active: 8
max-idle: 8
max-wait: 2000ms
min-idle: 0
3.实现类中
@Autowired
private RedisTemplate redisTemplate;
public List<CourseType> treeData() {
Object object =redisTemplate.opsForValue().get(CourseConstants.COURSE_TYPE_DATA);
if(object==null){
List<CourseType> courseTypes = courseTypeMapper.loadTreeData();
redisTemplate.opsForValue().set(CourseConstants.COURSE_TYPE_DATA,courseTypes);
return courseTypes;
}else{
List<CourseType> courseTypes = (List<CourseType>) object;
return courseTypes;
}
//以下三个方法需要重新实现新的逻辑,数据库数据发生改变后,redis数据也要改
@Override
public boolean insert(CourseType entity) {
redisTemplate.delete(CourseConstants.COURSE_TYPE_DATA);
return super.insert(entity);
}
@Override
public boolean updateById(CourseType entity) {
redisTemplate.delete(CourseConstants.COURSE_TYPE_DATA);
return super.updateById(entity);
}
@Override
public boolean deleteById(Serializable id) {
redisTemplate.delete(CourseConstants.COURSE_TYPE_DATA);
return super.deleteById(id);
}
注意,存储redis的类需要序列化,实现序列化接口
以上操作太冗余,所以进行优化Spring cache
1.导包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.配置
spring:
redis:
database: 0
port: 6379
host: localhost
password: 123456
jedis:
pool:
max-active: 8
max-idle: 8
max-wait: 2000ms
min-idle: 0
3.配置类
@Configuration
public class CacheConfig extends CachingConfigurerSupport {
@Resource
private RedisConnectionFactory factory;
@Bean
@Override
public KeyGenerator keyGenerator() {
return (o,method,objects)->{
StringBuilder sb = new StringBuilder();
sb.append(o.getClass().getName()).append(".");
sb.append(method.getName()).append(".");
for (Object obj: objects) {
sb.append(obj.toString());
}
System.out.println("keyGenerator=" + sb.toString());
return sb.toString();
};
}
@Bean
public RedisTemplate<Object,Object> redisTemplate(){
RedisTemplate<Object,Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
//JSON格式序列化
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
//key的序列化
redisTemplate.setKeySerializer(genericJackson2JsonRedisSerializer);
//value的序列化
redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
//hash结构key的虚拟化
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//hash结构value的虚拟化
redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
return redisTemplate;
}
}
4.需要的地方加 @Cacheable //触发缓存写入 @CacheEvict触发缓存清除
@Override
@Cacheable(cacheNames = CourseConstants.COURSE_TYPE_DATA,key = "'All'")
public List<CourseType> treeData() {
return courseTypeMapper.loadTreeData();
}
//以下三个方法需要重新实现新的逻辑,数据库数据发生改变后,redis数据也要改
@CacheEvict(cacheNames = CourseConstants.COURSE_TYPE_DATA,key = "'All'")
@Override
public boolean insert(CourseType entity) {
redisTemplate.delete(CourseConstants.COURSE_TYPE_DATA);
return super.insert(entity);
}
@CacheEvict(cacheNames = CourseConstants.COURSE_TYPE_DATA,key = "'All'")
@Override
public boolean updateById(CourseType entity) {
redisTemplate.delete(CourseConstants.COURSE_TYPE_DATA);
return super.updateById(entity);
}
@CacheEvict(cacheNames = CourseConstants.COURSE_TYPE_DATA,key = "'All'")
@Override
public boolean deleteById(Serializable id) {
redisTemplate.delete(CourseConstants.COURSE_TYPE_DATA);
return super.deleteById(id);
}
但是上面这种方式,可能会有缓存击穿/雪崩
缓存穿透
当查询redis和数据库都没有没有的数据,而用户不断发起请求,会导致数据库压力过大
解决方法 1.业务层校验数据 2.不存在数据设置短过期时间
缓存击穿
当redis中热点key过期的瞬间,大量请求访问,全部到达数据库,从而压垮数据库
解决方法 1.设置热点数据不过时 2.快过时的时候取更新(定时任务) 3.加
缓存雪崩
Redis中缓存的数据大面积同时失效,引起数据库压力过大甚至宕机
解决方法 1.集群 2.数据预热(提前走一遍) 3.热点数据永不过期
解决方案:
1.为了解决穿透问题,数据库没有的数据也要在redis中设置一个null
2.雪崩和击穿
方案1 热点数据永不过期
方案2 双重判断上锁(击穿) ,分开过期(雪崩)
private Lock lock = new ReentrantLock();
@Override
public List<CourseType> treeData() {
Object object = redisTemplate.opsForValue().get(CourseConstants.COURSE_TYPE_DATA);
if(object==null){
lock.lock();//击穿
object = redisTemplate.opsForValue().get(CourseConstants.COURSE_TYPE_DATA);
if(object!=null){ //第一个请求进来没有数据,那么就会在下面存入缓存,然后第二个人进来直接就可以在此处获取,避免缓存击穿
return (List<CourseType>) object;
}
List<CourseType> courseTypes = courseTypeMapper.loadTreeData();
//如果没有数据则放入一个空
if(courseTypes == null){
courseTypes = new ArrayList<>();
}
//方案1:热点数据永不过期
//redisTemplate.opsForValue().set(CourseConstants.COURSE_TYPE_DATA,courseTypes);
//方案2:热点数据要过期,但不一起过期
redisTemplate.opsForValue().set(CourseConstants.COURSE_TYPE_DATA,courseTypes,new Random().
nextInt(10000)+30*24*60*60, TimeUnit.SECONDS);
System.out.println("没有");
lock.unlock();
return courseTypes;
}else{
List<CourseType> courseTypes = (List<CourseType>) object;
System.out.println("有");
return courseTypes;
}
}
@Override
public boolean insert(CourseType entity) {
redisTemplate.delete(CourseConstants.COURSE_TYPE_DATA);
return super.insert(entity);
}
@Override
public boolean updateById(CourseType entity) {
redisTemplate.delete(CourseConstants.COURSE_TYPE_DATA);
return super.updateById(entity);
}
@Override
public boolean deleteById(Serializable id) {
redisTemplate.delete(CourseConstants.COURSE_TYPE_DATA);
return super.deleteById(id);
}
这种双写存在延迟,如果并发来了就可能出问题,所以解决方案如下
缓存问题-双写一致性
延时双删:
先删缓存,再删除数据库
睡眠3s
放入消息队列–消费者一直删除直到删除成功,一直不成功则管理员手动删除
@Override
public boolean insert(CourseType entity) {
redisTemplate.delete(CourseConstants.COURSE_TYPE_DATA);
boolean result = super.insert(entity);
try {
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e){
e.printStackTrace();
}
//@TODO rabbitMQ确保一定成功
//删除缓存
redisTemplate.delete(CourseConstants.COURSE_TYPE_DATA);
return result;
}
//@CacheEvict(cacheNames = CourseConstants.COURSE_TYPE_DATA,key = "'All'")
@Override
public boolean updateById(CourseType entity) {
redisTemplate.delete(CourseConstants.COURSE_TYPE_DATA);
boolean result = super.updateById(entity);
try {
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e){
e.printStackTrace();
}
//@TODO rabbitMQ确保一定成功
//删除缓存
return result;
}
//@CacheEvict(cacheNames = CourseConstants.COURSE_TYPE_DATA,key = "'All'")
@Override
public boolean deleteById(Serializable id) {
redisTemplate.delete(CourseConstants.COURSE_TYPE_DATA);
boolean result = super.deleteById(id);
try {
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e){
e.printStackTrace();
}
//@TODO rabbitMQ确保一定成功
//删除缓存
return result;
}