目录
事务
redis的事务没有回滚操作
常用命令:
1.discard:取消事务
2.exec:执行事务中的命令
3.multi:标记事务开启
4.unwatch:取消watch命令对使用key的监视
5.watch key [key...]:监视一个或多个key,如果在事务执行前这个key被其他命令修改,事务被打断
正常执行流程:multi,cmd1,cmd2,......,exec
放弃事务流程:multi,cmd1,cmd2,......,discard
一条出错全部放弃流程(语法出错如忘记写value:set k1):multi,cmd1,出错cmd,exec
出错命令不影响其他命令流程(运行时异常,逻辑有问题):multi,set email a@qq.com,incr email,exec
watch监控流程:先监控在开启事务multi
管道
发布/订阅(了解)
Redis复制(replica)
就是主从复制,master以写为主,slave以读为主,当master数据变化时,自动将新的数据异步同步到其他slave数据库。
**功能:**1.读写分离 2.容灾恢复 3.数据备份 4.水平扩容支撑高并发
**配置:**slave数据库配置master数据库,如果master有密码,slave要配置masterauth来设置校验密码,否则master拒绝访问请求。
操作:
配置文件:
指定端口:
从机配置主机:
启动redis后查看:
哨兵(sentinel)监控
主从复制中当master数据库挂了就无法再往redis中进行写操作,因为slave集群中无法选举出一个新的master,所以引入了哨兵监控。
哨兵的作用:
1、监控redis的运行状态,包括master和slave
2、当master宕机了,能自动将一个slave切换为新的master
sentinel.conf:
redis数据库和redis哨兵是两种不同的程序
启动命令:
启动redis数据库:redis-server /redis6379.conf(配置文件) 默认端口:6379
启动redis哨兵:redis-sentinel /sentinel26379.conf(配置文件) 默认端口:26379
或者:redis-server /sentinel26379.conf --sentinel
**哨兵运行流程:**正常运行-》sdown主观下线(存在哨兵没有在规定时间内收到master的心跳)-》odown客观下线(所有哨兵讨论后(达到quorum)确认没有收到心跳)-》在哨兵中选择leader哨兵-》由leader通过故障切换流程选出新的master。
选举leader哨兵的方法:Raft算法
**Raft算法基本思路:**先到先得,即一轮选举中,哨兵a向b发送成为leader的申请,如果b未同意过其他哨兵,则同意a为leader。
选举新master的原理图:
哨兵的使用建议:
集群分片
哨兵监控+主从复制 并不能保证数据零丢失,因此有了集群分片
集群 与 哨兵+主从 是替换关系
集群算法-分片-槽位slot:
slot槽位映射一般使用3中办法:
1、哈希取余分区:
缺点:扩容后或者某个节点挂了容易导致之前存储key的大规模的混乱
2、一致性哈希算法分区:
优点:容错性 , 扩展性
缺点:数据倾斜问题
3、哈希槽分区。
**Redis集群不能保证数据强一致性:**当客户端在master完成写操作后,如果master还没来得及向slave复制数据就突然挂了,那么当slave被切换为master时,数据就会不一致。
配置Redis集群:
1.分别为每个redis数据库上创建一个对应的配置文件:
2.文件内容:
3.以集群方式启动每个redis服务:
4.建立主从关系:这个命令随机设置主从关系
5.可以依次打开每个redis检查主从关系:
6.也可以使用 cluster nodes 查看集群关系:
集群读写:
key会先计算:crc16(key)%16384 得到 槽位, 对应的槽位要到相应的redis节点
如果槽位不在当前redis节点会报错
解决方法:防止路由失效加参数-c,这样会将key存放到相应的redis节点
-c:以集群模式运行客户端
节点从属调整
redis集群中master挂了后,会自动将slave提升为master,原来的master恢复后变为slave,可以使用下面的命令切换master和slave。
命令:cluster failover
redis-cli 的从机将 与其主机调换角色。
该命令只能在群集slave节点执行,让slave节点进行一次人工故障切换。
主从扩容
用哪个redis启动的集群,在进行扩容的时候,指定的ip和端口,必须为那个redis
将启动的redis数据库加到集群中:
检查集群情况:
重新分派槽位:
设置分派数量:(可以设置为 16384 / master数)
再次检查集群情况:
为新的master分配slave:
主从缩容
1.先清除slave
slave成功删除
2.把槽位重新分派
检查集群情况:要删除的master变成了一个master的slave
3.删除master
检查集群情况:
注意:
Springboot整合redis
就像java要访问mysql需要一些中间操作:jdbc,jdbctemplate,mybatis;
java访问redis也需要,一般使用3种:jedis,lettuce,RedisTemplate(推荐)
jedis存在一些安全问题,lettuce能力比jedis更强
RedisTemplate就是spring对lettuce的封装。
springboot连接redis常见问题
注释bind配置或设置为bind 0.0.0.0
保护模式设置为no
Linux系统的防火墙设置
redis的ip地址和密码是否正确
记得写访问redis的服务器端口号和auth密码
Jedis使用
操作步骤:
创建一个java项目:redis-project
导入pom依赖:
XML
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.qiu</groupId>
<artifactId>redis-project</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 设置为springboot工程-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.10</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<junit.version>4.12</junit.version>
<log4j.version>1.2.17</log4j.version>
<lombok.version>1.16.18</lombok.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- springboot 通用依赖模块-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- jedis依赖-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.1</version>
</dependency>
<!-- 通用基础配置-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
</dependencies>
</project>
编写配置文件:application.properties
XML
server.port=7777
spring.application.name=redis_study
入门程序:
java
import redis.clients.jedis.Jedis;
import redis.clients.jedis.args.ListPosition;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class JedisDemo {
public static void main(String[] args) {
//通过ip和端口获取redis的connection
Jedis jedis = new Jedis("192.168.234.127",6379);
//指定redis的密码
jedis.auth("123456");
//测试是否连通
jedis.flushDB();
System.out.println("jedis.ping() = " + jedis.ping());
//keys *
Set<String> keys = jedis.keys("*");
System.out.println("keys = " + keys.toString());
//string
System.out.println("============string===============");
jedis.set("k1","v1");
String s = jedis.get("k1");
System.out.println("s = " + s);
jedis.mset("k2", "v2", "k3", "v3");
List<String> mget = jedis.mget("k1", "k2", "k3");
System.out.println("mget = " + mget.toString());
//列表
System.out.println("============列表===============");
jedis.lpush("l1","l11","l12","l13");
List<String> l1 = jedis.lrange("l1", 0, -1);
System.out.println("l1 = " + l1.toString());
jedis.linsert("l1", ListPosition.AFTER,"l11","l11.5");
List<String> l11 = jedis.lrange("l1", 0, -1);
System.out.println("l1 = " + l11.toString());
//hash
System.out.println("============hash===============");
jedis.hset("h1","name","qiu");
String hget = jedis.hget("h1", "name");
System.out.println("hget = " + hget);
Map<String,String> map = new HashMap<String,String>();
map.put("name","liu");
map.put("age","23");
map.put("addr","广东");
jedis.hset("h2",map);
List<String> hmget = jedis.hmget("h2", "name", "addr", "age");
System.out.println("hmget = " + hmget.toString());
//set
System.out.println("============set===============");
jedis.sadd("set1","setv1","setv2","setv3");
Set<String> set1 = jedis.smembers("set1");
System.out.println("set1. = " + set1.toString());
long set11 = jedis.scard("set1");
System.out.println("set11 = " + set11);
jedis.srem("set2","setv1");
jedis.sadd("set2","setv1","set2v2");
Set<String> set2 = jedis.smembers("set2");
System.out.println("set2 = " + set2.toString());
Set<String> sdiff = jedis.sdiff("set1", "set2");
System.out.println("sdiff = " + sdiff.toString());
//zset
System.out.println("============zset===============");
jedis.zadd("z1",1d,"v1");
jedis.zadd("z1",2d,"v2");
jedis.zadd("z1",0d,"v3");
List<String> z1 = jedis.zrange("z1", 0, -1);
System.out.println("z1 = " + z1.toString());
}
}
lettuce使用
导入lettuce依赖
java
<!-- lettuce依赖-->
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>6.2.1.RELEASE</version>
</dependency>
java
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;
public class LettuceDemo {
public static void main(String[] args) {
//使用构建器链式编程来builder RedisURI
RedisURI uri = RedisURI.builder().redis("192.168.234.127").withPort(6379)
.withAuthentication("default","123456").build();
//创建连接客户端
RedisClient client = RedisClient.create(uri);
StatefulRedisConnection connect = client.connect();
//通过connect创建操作命令
RedisCommands cmd = connect.sync();
//命令
cmd.set("k1","v1");
//关闭资源
connect.close();
client.shutdown();
}
}
集成redistemplate
redistemplate连接单机:
添加依赖:
XML
<!-- springboot与redis整合依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
spring与redis整合包:
spring-boot-starter-data-redis
swagger:调用微服务接口的工具
配置文件修改:
XML
server.port=7777
spring.application.name=redis_study
# logging
logging.level.root = info
logging.level.com.qiu.redis = info
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger- %msg%n
# swagger
spring.swagger2.enadble=true
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
# redis 单机
spring.redis.database=0
spring.redis.host=192.168.234.127
spring.redis.port=6379
spring.redis.password=123456
#连接池
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1ms
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
编写配置类:
swagger配置类:
java
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Value("${spring.swagger2.enabled}")
private Boolean enadbled;
@Bean
public Docket createRestApi(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(enadbled)
.select()
.apis(RequestHandlerSelectors.basePackage("com.qiu.redis"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("springboot使用swagger构建api接口文档 "+ DateTimeFormatter.ofPattern("yyyy-MM-dd").format(LocalDate.now()))
.description("springboot+redis整合")
.version("1.0")
.build();
}
}
编写控制层和业务层代码:
service:
java
@Service
@Slf4j
public class OrderService {
private static final String ORDER_KEY = "ord:";
@Resource
private RedisTemplate redisTemplate;
// 添加京东订单
public void addOrder(){
int keyId = ThreadLocalRandom.current().nextInt(1000)+1;
String serialNo = UUID.randomUUID().toString();
String key = ORDER_KEY+keyId;
String value = "京东订单"+serialNo;
//string类型
redisTemplate.opsForValue().set(key,value);
log.info("********key:{}",key);
log.info("********value:{}",value);
}
//获取订单
public String getOrderById(Integer keyId){
return (String) redisTemplate.opsForValue().get(ORDER_KEY+keyId);
}
}
controller:
java
@RestController
@Slf4j
@Api(tags = "订单接口")
public class OrderController {
@Resource
private OrderService orderService;
@ApiOperation("新增订单")
@PostMapping(value = "/order/add")
public void addOrder(){
orderService.addOrder();
}
@ApiOperation("按照keyid查询订单")
@PostMapping("order/{keyId")
public void getOederById(@PathVariable Integer keyId){
String orderById = orderService.getOrderById(keyId);
System.out.println("orderById = " + orderById);
}
}
启动服务
访问:
但此时访问redis数据库:
序列化问题
解决方法1:不使用RedisTemplate,使用他的子类StringRedisTemplate
解决方法2:配置springboot项目中redis配置类
java
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String,Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory){
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(lettuceConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
redistemplate连接集群:
这时可以正常操作,但如果redis服务有一个宕机了,springboot无法动态感知集群的最新消息(即redis已经将slave升级为master但springboot不知道)
第一种方法的操作:(不推荐)
第三种方法操作:
在application配置文件中添加