redis是什么?
Redis 是一个基于内存的高性能key-value数据库,将数据加载到内存中,读写效率高于硬盘。
Nosql数据库的优势
1)易扩展
这些类型的数据存储不需要固定的模式,无需多余的操作就可以进行横向的扩展。相对于关系型数据库可以减少表和字段特别多的情况。也无型之间在架构的层面上带来了可扩展的能力
2)大数据量提高性能
3)多样灵活的数据模型
使用redis有哪些好处?
(1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
(2)支持丰富数据类型,支持string,list,set,sorted set,hash
(3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
(4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除
** 支持数据类型**
支持的数据类型:字符串、hash、列表List、set、sorted set
①、字符串:
存:set 键 值
取:get 键
重点方法:
setnx 键 值 →分布式锁重要的方法:当改建不存在时候,进行写操作返回1.当改键存在时,不进行写操作,返回值为0
②、Hash:
存:HMSET 键 字段1 值1 字段2 值2 。。。。。。
取:HGETALL 键
③、List:
存:lpush 键 值1 值2 。。。。。
lpush 键 值3
取:lrange 键 开始下标(从0开始) 结束下标
④、Set:
存:sadd 键 值1 值2 。。。。。
sadd 键 值3
取:smembers 键
⑤、sorted Set
存:zadd 键 下标 值
zadd 键 下标 值
取:zrange 键 起始位置(从0开始) 结束位置 withscores
什么是Redis持久化
持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。
**
分布式锁
java锁在分布式系统中并不是完全有效的,那么要解决高并发的线程安全问题就需要用分布式锁。
①setnx key 值
②用户1 执行操作 setnx返回值为1 说明该键不存在,可以进行mysql的更新操作
③用户2 自行操作 setnx返回值为0 已经有用户正在执行操作,不能进行mysql更新操作
④用户1 执行完之后 删除 键 key (del key)让用户2可以继续操作
上面是一个Redis分布式锁的一个简单的操作
***会遇到的问题(死锁):如果用户1在执行操作的时候 程序挂掉了,没有进行 删除键的操作,让后续用户无法正常进行。怎么办?
在②步中:
(1)如果直接获取了锁,就进行后续操作
(2)如果没有获取到锁,需要判断这个锁是否为死锁
把这个锁的值变成 long类型的 currentTime(当前时间) ,给这个锁定义一个死锁时间(超时时间),比如1000ms
(执行一个操作最多1秒钟)
具体方法→①用户1拿到锁的时候 setnx key(锁) currentTime +1000ms 把key 的值 设置成 当前时间加一个自己定义的超时时间 *(currentTime +1000ms = timeout ) *
②用户2 如果拿不到锁(返回值为0) get key(锁) =long timeout 拿到这个值
③和当前系统时间比较 如果 currentTime >= timeout ;说明锁已经出现死锁
④执行 get key(锁)= new timeout 并且 set key(锁)=currentTime + 1000ms
如果 new timeout >当前时间currentTime 说明这个锁已经被其他用户拿走,被其他用户get到并且重新set了一个超时时间,继续循环;
如果 new timeout <当前时间currentTime 说明这个锁还是死锁,我是在死锁后面的第一个用户,我可以拿走并进行数据库的操作然后 删除 键 key ;
有点难理解,拿个图来解释
timeout = currentTime + 1000ms =1000
用户2
if(setnx key = 0 拿不到锁){
get key =long timeout = 1000;
currentTime = 1500;
if(currentTime >= timeout){
get key = timeout = 1000;
set key = currenTime(1500) + 1000ms = 此时的 new timeout等于2500
if( new timeout> timeout){
2500 > 1000
拿到锁并且执行走出这个循环 执行数据库的操作 然后删除key;
}
}
}
用户3
if(setnx key = 0 拿不到锁){
get key =long timeout = 1000;
currentTime = 1500; //设定极端情况与上一个用户同一时间抢锁
if(currentTime >= timeout){
get key = new timeout = 2500 //此时的new timout 已经被上个用户修改成2500
set key = currenTime(1500) + 1000ms = 此时的 new timeout等于2500
if(new timeout> timeout){
2500 > 2500 不成立所以 拿不到锁 继续循环
}
}
}
**
Session共享
pom中引入该依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
<version>1.4.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<version>1.3.3.RELEASE</version>
</dependency>
添加redis配置
添加一个配置类
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3000)
public class SessionConfig {
}
简单的Controller 测试类
@RestController
@RequestMapping(value = "/")
public class Hello {
@RequestMapping(value = "/set", method = RequestMethod.GET)
public Map<String, Object> firstResp (HttpServletRequest request){
request.getSession().setAttribute("Testkey", "Session");
Map<String, Object> map = new HashMap<>();
map.put("Testkey", "Session");
return map;
}
@RequestMapping(value = "/query", method = RequestMethod.GET)
public Object sessions (HttpServletRequest request){
Map<String, Object> map = new HashMap<>();
map.put("sessionId", request.getSession().getId());
map.put("Testkey", request.getSession().getAttribute("Testkey"));
return map;
}
}
**
Redis缓存
POM依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</exclusion>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.0.0</version>
</dependency>
<!-- cache -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
application配置
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=localhost
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=123456
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=10
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=5
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=1
# 连接超时时间(毫秒)
spring.redis.timeout=2000
#开启缓存
spring.cache.type=redis
spring.datasource.username=root
spring.datasource.password=admin
spring.datasource.url=jdbc:mysql://localhost:3306/permission?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=UTC
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
Service层
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Autowired
UserMapper userMapper;
@Cacheable(cacheNames = "userById",key="#userId")
@Override
public User selectById(int userId) {
return userMapper.selectById(userId);
}
}
启动类
@EnableCaching
@SpringBootApplication
@MapperScan("com.miro.dao")
public class SpringbootRedisApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootRedisApplication.class, args);
}
}
Controller层
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserMapper userMapper;
@Autowired
private UserServiceImpl userService;
@RequestMapping("getUser")
public User getUser(int userId){
return userService.selectById(userId);
}
}
再进行增删改操作的时候加CacheEvict(cacheNames=“”,allEntries=true) 会将缓存数据删除,下次查询时会从数据库查询数据
缓存穿透
当查询到一个数据在缓存中一定不存在时,如果对这个数据进行高频率的读操作,相当于大量请求进入到了数据库。当查询出的数据为NULL时,就把这个NULL作为数据缓存到redis中。
缓存击穿
访问量巨大的热点数据失效
利用其它缓存再缓存一次数据,例如mybatis缓存额外缓存一下。
缓存雪崩
指所有的缓存数据同时失效,所有的请求直接进入数据库,造成数据库宕机。
解决方案:可以将缓存数据的失效时间设置为不一致。