目录
概念
Redis是一个非关系型数据库(NoSql)内存数据库。以key-value方式进行存储。Redis以单线程方式存储(保证线程安全),可以设计有效期,使用持久化机制保证数据库高可用。
应用场景
- 令牌生成(临时 有效期)
- 短信验证码(临时 有效期)
- 热点数据(使用Redis减轻查询数据库压力)
- 使用Redis实现消息中间件(不推荐) 发布订阅
- 分布式锁(使用ZooKeeper或者redis实现分布式锁)
- 网站计数器(由于redis是单线程,在高并发情况下,保证记录全局count唯一性)
五种数据类型
-
String(字符串)
-
List(列表)
-
Hash(字典)
-
Set(集合)
-
Sorted Set(有序集合)
SpringBoot整合Redis
1、pom文件引入
<dependencies>
<!-- SpringBoot web 核心组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- SpringBoot对Redis支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
2、yml配置文件
spring:
redis:
database: 1
host: 132.232.44.194
port: 6379
password: 123456
jedis:
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 0
timeout: 10000
3、后端相关代码
RedisService封装对redis的一些常用操作
@Component
public class RedisService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
public void set(String key, Object object, Long time) {
// 让该方法能够支持多种数据类型存放
if (object instanceof String) {
setString(key, object);
}
// 如果存放时Set类型
if (object instanceof Set) {
setSet(key, object);
}
// 设置有效期
if (time != null) {
stringRedisTemplate.expire(key, time, TimeUnit.SECONDS);
}
}
public void setString(String key, Object object) {
String value = (String) object;
// 存放string类型
stringRedisTemplate.opsForValue().set(key, value);
}
public void setSet(String key, Object object) {
Set<String> valueSet = (Set<String>) object;
for (String string : valueSet) {
stringRedisTemplate.opsForSet().add(key, string);
}
}
public String getString(String key) {
return stringRedisTemplate.opsForValue().get(key);
}
}
@RestController
public class IndexController {
@Autowired
private RedisService redisService;
@RequestMapping("/setString")
public String setString(String key, String object) {
redisService.set(key, object, 60l);
return "success";
}
@RequestMapping("/get")
public String get(String key) {
return redisService.getString(key);
}
}
发布订阅
首先创建一个订阅频道
redis 127.0.0.1:6379> SUBSCRIBE redisChat
另一个客户端在频道上发布消息
redis 127.0.0.1:6379> PUBLISH redisChat "Redis is good"
这时订阅者就会推送出相应消息
主从复制
概念
将服务器分为主服务器和从服务器,主的服务器可以允许做读写操作,从服务器只允许做读的操作。
应用场景
- 集群(多台服务器)
- 读写分离
- 日志备份
- 高可用
过程
1:当一个从数据库启动时,会向主数据库发送sync命令,
2:主数据库接收到sync命令后会开始在后台保存快照(执行rdb操作),并将保存期间接收到的命令缓存起来
3:当快照完成后,redis会将快照文件和所有缓存的命令发送给从数据库。
4:从数据库收到后,会载入快照文件并执行收到的缓存的命令。
修改从Redis从配置文件
修改slave从redis中的 redis.conf文件
slaveof 192.168.33.130 6379
然后在主服务器中存入一个值后,从服务器就可以接收到该值了。
Redis 哨兵机制
Redis的哨兵(sentinel) 系统用于管理多个 Redis 服务器,该系统执行以下三个任务:
- 监控(Monitoring): 哨兵(sentinel) 会不断地检查你的Master和Slave是否运作正常
- 提醒(Notification):当被监控的某个 Redis出现问题时, 哨兵(sentinel) 可以通过 API 向管理员或者其他应用程序发送通知
- 自动故障迁移(Automatic failover):当一个Master不能正常工作时,哨兵(sentinel) 会开始一次自动故障迁移操作,它会将失效Master的其中一个Slave升级为新的Master, 并让失效Master的其他Slave改为复制新的Master; 当客户端试图连接失效的Master时,集群也会向客户端返回新Master的地址,使得集群可以使用Master代替失效Master
哨兵(sentinel) 是一个分布式系统,你可以在一个架构中运行多个哨兵(sentinel) 进程,这些进程使用流言协议(gossipprotocols)来接收关于Master是否下线的信息,并使用投票协议(agreement protocols)来决定是否执行自动故障迁移,以及选择哪个Slave作为新的Master
每个哨兵(sentinel) 会向其它哨兵(sentinel)、master、slave定时发送消息,以确认对方是否”活”着,如果发现对方在指定时间(可配置)内未回应,则暂时认为对方已挂(所谓的”主观认为宕机” Subjective Down,简称sdown)
若“哨兵群”中的多数sentinel,都报告某一master没响应,系统才认为该master"彻底死亡"(即:客观上的真正down机,Objective Down,简称odown),通过一定的vote算法,从剩下的slave节点中,选一台提升为master,然后自动修改相关配置
虽然哨兵(sentinel) 释出为一个单独的可执行文件 redis-sentinel ,但实际上它只是一个运行在特殊模式下的 Redis 服务器,你可以在启动一个普通 Redis 服务器时通过给定 --sentinel 选项来启动哨兵(sentinel)
修改配置
1.拷贝到etc目录
cp sentinel.conf /usr/local/redis/etc
2.修改sentinel.conf配置文件
sentinel monitor mymast 192.168.110.133 6379 1 #主节点 名称 IP 端口号 选举次数
sentinel auth-pass mymaster 123456
3. 修改心跳检测 5000毫秒
sentinel down-after-milliseconds mymaster 5000
4.设置合格节点
sentinel parallel-syncs mymaster 2 #做多多少合格节点
5.启动哨兵模式
./redis-server /usr/local/redis/etc/sentinel.conf --sentinel &
Redis持久化
Redis持久化,就是将内存数据保存到硬盘。(因为redis的值放在内存中,防止突然断电的情况,都会对数据进行持久化操作)
RDB持久化
RDB 是以二进制文件,是在某个时间 点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复。
优缺点
- 使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能
- RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失
AOF持久化
实时日志记录形式,只会记录写的操作,效率不高,会影响到整体性能。
Redis事物
Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证:
- 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断
- 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行
一个事务从开始到执行会经历以下三个阶段:
开始事务。
命令入队。
执行事务。
Linux操作事务
先以 MULTI 开始一个事务, 然后将多个命令入队到事务中, 最后由 EXEC 命令触发事务, 一并执行事务中的所有命令
redis 127.0.0.1:6379> MULTI
OK
redis 127.0.0.1:6379> SET book-name "Mastering C++ in 21 days"
QUEUED
redis 127.0.0.1:6379> GET book-name
QUEUED
redis 127.0.0.1:6379> SADD tag "C++" "Programming" "Mastering Series"
QUEUED
redis 127.0.0.1:6379> SMEMBERS tag
QUEUED
redis 127.0.0.1:6379> EXEC
1) OK
2) "Mastering C++ in 21 days"
3) (integer) 3
SpringBoot操作Redis事务
public void setString(String key, Object object) {
stringRedisTemplate.setEnableTransactionSupport(true);
// 开启事务
stringRedisTemplate.multi();
try {
// 如果是String 类型
String value = (String) object;
stringRedisTemplate.opsForValue().set(key, value);
} catch (Exception e) {
// 回滚
stringRedisTemplate.discard();
} finally {
// 提交
stringRedisTemplate.exec();
}
}
雪崩效应
在高并发下,如果redis宕机后,短时间内有大量请求访问数据库,会发生雪崩效应。
解决方案
可以使用EhCache+Redis实现二级缓存,先走本地缓存,本地缓存如果没有,在走网络连接,查询Redis,这样可以减轻Redis压力。
ehCache+Redis实现两级级缓存
Redis与数据库、EhCache的区别
redis与数据库相比
- 相同点:都是需要进行网络连接
- 不同点:存放介质不同,redis存放在内存中,数据库存放在硬盘上
- 从效率上讲,数据库需要做IO操作,性能比直接操作内存效率要低
EhCache不需要走网络连接,直接从内存中获取,但需要注意不要产生内存溢出
二级缓存的逻辑
现在用EhCache作为一级缓存,Redis作为二级缓存,先走本地内存查询,本地缓存如果没有,再走网络连接,这样效率会更高。
pom文件引入
<dependencies>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- SpringBoot web 核心组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--开启 cache 缓存 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- ehcache缓存 -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.9.1</version><!--$NO-MVN-MAN-VER$ -->
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<!-- mysql 依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
后端相关代码
首先封装对EhCache的基本操作的工具类
//封装对EhCache的基本操作
@Component
public class EhCacheUtils {
@Autowired
private EhCacheCacheManager ehCacheCacheManager;
// cacheName 和 key 区别 就是 redis中的db库 组
// 添加本地缓存 (相同的key 会直接覆盖)
public void put(String cacheName, String key, Object value) {
Cache cache = ehCacheCacheManager.getCacheManager().getCache(cacheName);
Element element = new Element(key, value);
cache.put(element);
}
// 获取本地缓存
public Object get(String cacheName, String key) {
Cache cache = ehCacheCacheManager.getCacheManager().getCache(cacheName);
Element element = cache.get(key);
return element == null ? null : element.getObjectValue();
}
//清除缓存
public void remove(String cacheName, String key) {
Cache cache = ehCacheCacheManager.getCacheManager().getCache(cacheName);
cache.remove(key);
}
}
添加Redis的Service
@Component
public class RedisService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
public void setString(String key, Object object) {
String value = (String) object;
stringRedisTemplate.opsForValue().set(key, value);
}
public void setSet(String key, Object object) {
Set<String> value = (Set<String>) object;
for (String oj : value) {
stringRedisTemplate.opsForSet().add(key, oj);
}
}
public String getString(String key) {
return stringRedisTemplate.opsForValue().get(key);
}
}
整合二级缓存
需要注意的是,Redis存放的是Json格式,而EhCache存放的是对象,所以需要格式转换。
@Service
public class UserService {
@Autowired
private EhCacheUtils ehCacheUtils;
@Autowired
private RedisService redisService;
@Autowired
private UserMapper userMapper;
private String cacheName = "userCache";
/**
* 1.先查询一级缓存
* 1.1如果一级缓存有对应的值,则直接返回
* 1.2如果没有,则查询二级缓存
* 1.2.1如果二级缓存有对应的值,则修改一级缓存,并返回
* 1.2.2如果没有,查询数据库,修改一级二级缓存
* @param id
* @return
*/
public Users getUser(Long id) {
// 1.先查询一级缓存 key 以 当前的类名+方法名称+id +参数值FD
String key = this.getClass().getName() + "-" + Thread.currentThread().getStackTrace()[1].getMethodName()
+ "-id:" + id;
// 1.1 查询一级缓存数据有对应的值存在,如果存在直接返回
Users users = (Users) ehCacheUtils.get(cacheName, key);
if (users != null) {
System.out.println("key:" + key + ",从一级缓存获取数据:users:" + users.toString());
return users;
}
// 1.1 查询一级缓存数据没有对应的值存在,直接查询二级缓存redis redis 中如何存放对象呢? json格式
// 2.查询二级缓存
String userJSON = redisService.getString(key);
// 如果redis缓存中有这个对应的 值,修改一级缓存
if (!StringUtils.isEmpty(userJSON)) {
JSONObject jsonObject = new JSONObject();
Users resultUser = jsonObject.parseObject(userJSON, Users.class);
// 存放在一级缓存
ehCacheUtils.put(cacheName, key, resultUser);
return resultUser;
}
// 3.查询db 数据库
Users user = userMapper.getUser(id);
if (user == null) {
return null;
}
// 存放二级缓存redis
redisService.setString(key, new JSONObject().toJSONString(user));
// 存放在一级缓存
ehCacheUtils.put(cacheName, key, user);
return user;
}
}
Redis集群
官方推荐redis-cluster,我们之间介绍redis-cluster。
Redis 在 3.0 版本以后就推出了集群模式。Redis 集群采用了P2P的模式,完全去中心化。Redis 把所有的 Key 分成了 16384 个 slot,每个 Redis 实例负责其中一部分 slot 。集群中的所有信息(节点、端口、slot等),都通过节点之间定期的数据交换而更新。 Redis 客户端可以在任意一个 Redis 实例发出请求,如果所需数据不在该实例中,通过重定向命令引导客户端访问所需的实例。
首先,在redis的每一个节点上,都有这么两个东西,一个是插槽(slot)可以理解为是一个可以存储两个数值的一个变量这个变量的取值范围是:0-16383。还有一个就是cluster我个人把这个cluster理解为是一个集群管理的插件。当我们的存取的key到达的时候,redis会根据crc16的算法得出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作。
Redis集群使用CRC16对key进行hash,集群固定使用16384对hash出来的值取模。因为取模结果一定在16384之内,所以集群中的sharding(分片)实际就是如何将16384个值在n个主节点间分配(从节点是主节点的近似副本,原因见3),如何分配取决于你的配置。
Redis生产级集群需要容灾,为此,一般部署为n个主+n*m个从。n大小主要取决于单机性能,m大小主要取决于机器稳定性。
Redis集群是弱一致性的,此处的一致,主要指主从之间的数据一致性。主要是因为redis在做数据更新时,不要求主从数据同步复制一定要成功。
集群最小的主数量为3,主数量应为奇数,以便做选举判决。