当前位置: 首页 > news >正文

Redis-主从与哨兵架构

Jedis使用

Jedis连接代码示例:

1、引入依赖

<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>2.9.0</version>
</dependency>

2、访问代码

public class JedisSingleTest
{public static void main(String[] args){JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();jedisPoolConfig.setMaxTotal(20);jedisPoolConfig.setMaxIdle(10);jedisPoolConfig.setMinIdle(5);// timeout,这里既是连接超时又是读写超时,从Jedis 2.8开始有区分connectionTimeout和soTimeout的构造函数JedisPool jedisPool = new JedisPool(jedisPoolConfig, "192.168.0.60", 6379, 3000, null);Jedis jedis = jedisPool.getResource();System.out.println(jedis.set("single", "gc"));  // OKSystem.out.println(jedis.get("single"));		// gc}
}

注意:如果是购买的云服务器,需要在安全组添加端口规则

管道(Pipeline)

客户端可以一次性发送多个请求而不用等待服务器的响应,待所有命令都发送完后再一次性读取服务的响应,这样可以极大的降低多条命令执行的网络传输开销,管道执行多条命令的网络开销实际上只相当于一次命令执行的网络开销。需要注意到是用pipeline方式打包命令发送,redis必须在处理完所有命令前先缓存起所有命令的处理结果。打包的命令越多,缓存消耗内存也越多。所以并不是打包的命令越多越好。

pipeline中发送的每个command都会被server立即执行,如果执行失败,将会在此后的响应中得到信息;也就是pipeline并不是表达“所有command都一起成功”的语义,管道中前面命令失败,后面命令不会有影响,继续执行

Pipeline pl = jedis.pipelined();
for (int i = 0; i < 10; i++)
{pl.incr("pipelineKey");pl.set("gc" + i, "gc");// 模拟管道报错// pl.setbit("gc", -1, true);
}
List<Object> results = pl.syncAndReturnAll();
System.out.println(results);输出:[1, OK, 2, OK, 3, OK, 4, OK, 5, OK, 6, OK, 7, OK, 8, OK, 9, OK, 10, OK]
报错输出:[11, OK, redis.clients.jedis.exceptions.JedisDataException: ERR bit offset is not an integer or out of range, 12, OK, 省略...]

Redis Lua脚本

Redis在2.6推出了脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行。使用脚本的好处:

1、减少网络开销:本来5次网络请求的操作,可以用一个请求完成,原先5次请求的逻辑放在redis服务器上完成。使用脚本,减少了网络往返时延。这点跟管道类似

2、原子操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。管道不是原子的,不过redis的批量操作命令(类似mset)是原子的。

3、替代redis的事务功能:redis自带的事务功能很鸡肋,而redis的lua脚本几乎实现了常规的事务功能,官方推荐如果要使用redis的事务功能可以用redis lua替代。

官网文档描述:

A Redis script is transactional by definition, so everything you can do with a Redis transaction, you can also do with a script, and usually the script will be both simpler and faster.

从Redis2.6.0版本开始,通过内置的Lua解释器,可以使用EVAL命令对Lua脚本进行求值。语法:

EVAL script numkeys key [key ...] arg [arg ...]

script参数是一段Lua脚本程序,它会被运行在Redis服务器上下文中,这段脚本不必(也不应该)定义为一个Lua函数

numkeys参数用于指定键名参数的个数。

键名参数 key [key ...],可以在Lua中通过全局变量KEYS数组访问:KEYS[1],KEYS[2],以此类推。

附加参数 arg [arg ...],可以在Lua中通过全局变量ARGV数组访问,ARGV[1] 、 ARGV[2] ,以此类推

127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"

在 Lua 脚本中,可以使用redis.call()函数来执行Redis命令

// lua脚本模拟一个商品减库存的原子操作
// lua脚本命令执行方式:redis-cli --eval /tmp/test.lua , 10
jedis.set("product_count_10016", "10");  //初始化商品10016的库存
String script = " local count = redis.call('get', KEYS[1]) " +" local a = tonumber(count) " +" local b = tonumber(ARGV[1]) " +" if a >= b then " +"   redis.call('set', KEYS[1], a-b) " +"   return 1 " +" end " +" return 0 ";
Object obj = jedis.eval(script, Arrays.asList("product_count_10016"), Arrays.asList("12");
System.out.println(obj); 

注意,不要在Lua脚本中出现死循环和耗时的运算,否则redis会阻塞,将不接受其他的命令, 所以使用时要注意不能出现死循环、耗时的运算。redis是单进程、单线程执行脚本。管道不会阻塞redis。

Redis哨兵高可用架构

sentinel哨兵是特殊的redis服务,不提供读写服务,主要用来监控redis实例节点。

哨兵架构下client端第一次从哨兵找出redis的主节点,后续就直接访问redis的主节点,不会每次都通过sentinel代理访问redis的主节点,当redis的主节点发生变化,哨兵会第一时间感知到,并且将新的redis主节点通知给client端(这里面redis的client端一般都实现了订阅功能,订阅sentinel发布的节点变动消息)

redis哨兵架构搭建步骤

1、复制一份sentinel.conf文件
cp sentinel.conf sentinel-26379.conf2、将相关配置修改为如下值:
port 26379
daemonize yes
pidfile "/var/run/redis-sentinel-26379.pid"
logfile "26379.log"
dir "/usr/local/redis-5.0.3/data"
# sentinel monitor <master-redis-name> <master-redis-ip> <master-redis-port> <quorum>
# quorum是一个数字,指明当有多少个sentinel认为一个master失效时(值一般为:sentinel总数/2 + 1),master才算真正失效
sentinel monitor mymaster 192.168.0.60 6379 2   # mymaster这个名字随便取,客户端访问时会用到3、启动sentinel哨兵实例
src/redis-sentinel sentinel-26379.conf4、查看sentinel的info信息
src/redis-cli -p 26379
127.0.0.1:26379>info
可以看到Sentinel的info里已经识别出了redis的主从5、可以自己再配置两个sentinel,端口26380和26381,注意上述配置文件里的对应数字都要修改

云服务器的配置项<master-redis-ip>修改为公网IP后测试成功

sentinel集群都启动完毕后,会将哨兵集群的元数据信息写入所有sentinel的配置文件里去(追加在文件的最下面),我们查看下如下配置文件sentinel-26379.conf,如下所示:

sentinel known-replica mymaster 192.168.0.60 6381	#代表主节点的从节点信息
sentinel known-replica mymaster 192.168.0.60 6380	#代表主节点的从节点信息
sentinel known-sentinel mymaster 192.168.0.60 26381 569cb30eefa70d33927d8dfd79360bd11b58b9de #代表感知到的其它哨兵节点
sentinel known-sentinel mymaster 192.168.0.60 26380 a38226cc8e7f3f71427eaa1bb2b50de07eadd21d #代表感知到的其它哨兵节点

如果哨兵节点之间互相感应不到,检查配置文件中,用于标识哨兵唯一性的myid是不是重复的

注意:如果哨兵节点已经启动了,然后再复制一份修改为其他的哨兵节点,这种情况如果没有删除已经生成的myid,就会导致哨兵节点之间不想感应不到,因为它们的myid是相同的

当redis主节点如果挂了,哨兵集群会重新选举出新的redis主节点,同时会修改所有sentinel节点配置文件的集群元数据信息,比如6379的redis如果挂了,假设选举出的新主节点是6380,则sentinel文件里的集群元数据信息会变成如下所示:

sentinel known-replica mymaster 192.168.0.60 6379	#代表主节点的从节点信息
sentinel known-replica mymaster 192.168.0.60 6381	#代表主节点的从节点信息
sentinel known-sentinel mymaster 192.168.0.60 26381 569cb30eefa70d33927d8dfd79360bd11b58b9de #代表感知到的其它哨兵节点
sentinel known-sentinel mymaster 192.168.0.60 26380 a38226cc8e7f3f71427eaa1bb2b50de07eadd21d #代表感知到的其它哨兵节点

同时还会修改sentinel文件里之前配置的mymaster对应的6379端口,改为6380

sentinel monitor mymaster 192.168.0.60 6380 2

当6379的redis实例再次启动时,哨兵集群根据集群元数据信息就可以将6379端口的redis节点作为从节点加入集群

测试代码:

public class JedisSingleTest2
{public static void main(String[] args){JedisPoolConfig config = new JedisPoolConfig();config.setMaxTotal(20);config.setMaxIdle(10);config.setMinIdle(5);String masterName = "mymaster";Set<String> sentinels = new HashSet<>();sentinels.add(new HostAndPort("192.168.0.60", 26379).toString());sentinels.add(new HostAndPort("192.168.0.60", 26380).toString());sentinels.add(new HostAndPort("192.168.0.60", 26381).toString());// JedisSentinelPool其实本质跟JedisPool类似,都是与redis主节点建立的连接池// JedisSentinelPool并不是说与sentinel建立的连接池,而是通过sentinel发现redis主节点并与其建立连接JedisSentinelPool jedisSentinelPool = new JedisSentinelPool(masterName, sentinels, config, 3000, null);Jedis jedis = null;try{jedis = jedisSentinelPool.getResource();System.out.println(jedis.set("sentinel", "gc"));System.out.println(jedis.get("sentinel"));}catch (Exception e){e.printStackTrace();}finally{// 注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。if (jedis != null)jedis.close();}}
}

哨兵的Spring Boot整合Redis连接

1、引入相关依赖:

<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>

2、springboot项目核心配置:

server:port: 8080spring:redis:database: 0timeout: 3000sentinel:    #哨兵模式master: mymaster #主服务器所在集群名称nodes: 192.168.0.60:26379,192.168.0.60:26380,192.168.0.60:26381lettuce:pool:max-idle: 50min-idle: 10max-active: 100max-wait: 1000

访问代码:

@RestController
public class IndexController {private static final Logger logger = LoggerFactory.getLogger(IndexController.class);@Autowiredprivate StringRedisTemplate stringRedisTemplate;/*** 测试节点挂了哨兵重新选举新的master节点,客户端是否能动态感知到* 新的master选举出来后,哨兵会把消息发布出去,客户端实际上是实现了一个消息监听机制,* 当哨兵把新master的消息发布出去,客户端会立马感知到新master的信息,从而动态切换访问的masterip** @throws InterruptedException*/@RequestMapping("/test_sentinel")public void testSentinel() throws InterruptedException {int i = 1;while (true){try {stringRedisTemplate.opsForValue().set("gc"+i, i+"");System.out.println("设置key:"+ "gc" + i);i++;Thread.sleep(1000);}catch (Exception e){logger.error("错误:", e);}}}
}

StringRedisTemplate与RedisTemplate详解

spring 封装了 RedisTemplate 对象来进行对redis的各种操作,它支持所有的 redis 原生的 api。在RedisTemplate中提供了几个常用的接口方法的使用,分别是:

private ValueOperations<K, V> valueOps;
private HashOperations<K, V> hashOps;
private ListOperations<K, V> listOps;
private SetOperations<K, V> setOps;
private ZSetOperations<K, V> zSetOps;

RedisTemplate中定义了对5种数据结构操作:

redisTemplate.opsForValue();	//操作字符串
redisTemplate.opsForHash();		//操作hash
redisTemplate.opsForList();		//操作list
redisTemplate.opsForSet();		//操作set
redisTemplate.opsForZSet();		//操作有序set

StringRedisTemplate继承自RedisTemplate,也一样拥有上面这些操作。

StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。

RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。

Redis客户端命令对应的RedisTemplate中的方法列表:

String类型结构
RedisRedisTemplate rt
set key valuert.opsForValue().set("key","value")
get keyrt.opsForValue().get("key")
del keyrt.delete("key")
strlen keyrt.opsForValue().size("key")
getset key valuert.opsForValue().getAndSet("key","value")
getrange key start endrt.opsForValue().get("key",start,end)
append key valuert.opsForValue().append("key","value")
Hash结构
hmset key field1 value1 field2 value2...rt.opsForHash().putAll("key",map) //map是一个集合对象
hset key field valuert.opsForHash().put("key","field","value")
hexists key fieldrt.opsForHash().hasKey("key","field")
hgetall keyrt.opsForHash().entries("key") //返回Map对象
hvals keyrt.opsForHash().values("key") //返回List对象
hkeys keyrt.opsForHash().keys("key") //返回List对象
hmget key field1 field2...rt.opsForHash().multiGet("key",keyList)
hsetnx key field valuert.opsForHash().putIfAbsent("key","field","value"
hdel key field1 field2rt.opsForHash().delete("key","field1","field2")
hget key fieldrt.opsForHash().get("key","field")
List结构
lpush list node1 node2 node3...rt.opsForList().leftPush("list","node")
rt.opsForList().leftPushAll("list",list) //list是集合对象
rpush list node1 node2 node3...rt.opsForList().rightPush("list","node")
rt.opsForList().rightPushAll("list",list) //list是集合对象
lindex key indexrt.opsForList().index("list", index)
llen keyrt.opsForList().size("key")
lpop keyrt.opsForList().leftPop("key")
rpop keyrt.opsForList().rightPop("key")
lpushx list nodert.opsForList().leftPushIfPresent("list","node")
rpushx list nodert.opsForList().rightPushIfPresent("list","node")
lrange list start endrt.opsForList().range("list",start,end)
lrem list count valuert.opsForList().remove("list",count,"value")
lset key index valuert.opsForList().set("list",index,"value")
Set结构
sadd key member1 member2...rt.boundSetOps("key").add("member1","member2",...)
rt.opsForSet().add("key", set) //set是一个集合对象
scard keyrt.opsForSet().size("key")
sidff key1 key2rt.opsForSet().difference("key1","key2") //返回一个集合对象
sinter key1 key2rt.opsForSet().intersect("key1","key2")//同上
sunion key1 key2rt.opsForSet().union("key1","key2")//同上
sdiffstore des key1 key2rt.opsForSet().differenceAndStore("key1","key2","des")
sinter des key1 key2rt.opsForSet().intersectAndStore("key1","key2","des")
sunionstore des key1 key2rt.opsForSet().unionAndStore("key1","key2","des")
sismember key memberrt.opsForSet().isMember("key","member")
smembers keyrt.opsForSet().members("key")
spop keyrt.opsForSet().pop("key")
srandmember key countrt.opsForSet().randomMember("key",count)
srem key member1 member2...rt.opsForSet().remove("key","member1","member2",...)

相关文章:

  • 孩子学习过程中一些代码记录
  • LLMLingua:集成LlamaIndex,对提示进行压缩,提供大语言模型的高效推理
  • RabbitMQ之发送者(生产者)可靠性
  • 【运维面试100问】(六)buffer和cache的区别
  • 在CentOS 7.9上搭建高性能的FastDFS+Nginx文件服务器集群并实现外部远程访问
  • 加载minio中存储的静态文件html,不显示样式与js
  • 基于51单片机超声波测距汽车避障系统
  • IBM X3650M4安装ESXI6.5卡在/lsl_mr3.v00
  • 泛型边界的问题
  • leetcode - 2232. Minimize Result by Adding Parentheses to Expression
  • C#中的警告CS0120、CS0176、CS0183、CS0618、CS8600、CS8602、CS8604、CS8625及处理
  • 分布式篇---第六篇
  • ​Spring Boot 分片上传文件
  • Windows下安装MySQL
  • 5个AI绘图工具,强到离谱国内可用!
  • 【comparator, comparable】小总结
  • Apache Spark Streaming 使用实例
  • ES6简单总结(搭配简单的讲解和小案例)
  • in typeof instanceof ===这些运算符有什么作用
  • javascript面向对象之创建对象
  • javascript数组去重/查找/插入/删除
  • JAVA之继承和多态
  • Material Design
  • SAP云平台里Global Account和Sub Account的关系
  • 爱情 北京女病人
  • 测试如何在敏捷团队中工作?
  • 分布式事物理论与实践
  • 开放才能进步!Angular和Wijmo一起走过的日子
  • 如何优雅的使用vue+Dcloud(Hbuild)开发混合app
  • 使用 Node.js 的 nodemailer 模块发送邮件(支持 QQ、163 等、支持附件)
  • 微信小程序--------语音识别(前端自己也能玩)
  • 物联网链路协议
  • 学习笔记TF060:图像语音结合,看图说话
  • 用Node EJS写一个爬虫脚本每天定时给心爱的她发一封暖心邮件
  • 原生Ajax
  • hi-nginx-1.3.4编译安装
  • python最赚钱的4个方向,你最心动的是哪个?
  • Semaphore
  • ​卜东波研究员:高观点下的少儿计算思维
  • #FPGA(基础知识)
  • #HarmonyOS:软件安装window和mac预览Hello World
  • #使用清华镜像源 安装/更新 指定版本tensorflow
  • #预处理和函数的对比以及条件编译
  • (1) caustics\
  • (1)(1.11) SiK Radio v2(一)
  • (14)Hive调优——合并小文件
  • (HAL)STM32F103C6T8——软件模拟I2C驱动0.96寸OLED屏幕
  • (SpringBoot)第二章:Spring创建和使用
  • (二)springcloud实战之config配置中心
  • (附源码)ssm旅游企业财务管理系统 毕业设计 102100
  • (免费领源码)Java#Springboot#mysql农产品销售管理系统47627-计算机毕业设计项目选题推荐
  • (转)原始图像数据和PDF中的图像数据
  • .net 打包工具_pyinstaller打包的exe太大?你需要站在巨人的肩膀上-VC++才是王道
  • .Net(C#)自定义WinForm控件之小结篇
  • .NET处理HTTP请求