[TOC]
前置知识学习补充 Redis数据库基础入门介绍与安装 - https://blog.weiyigeek.top/2019/4-17-49.html
Redis数据库基础数据类型介绍与使用 - https://blog.weiyigeek.top/2020/5-17-50.html
Redis基础运维之原理介绍和主从配置 - https://blog.weiyigeek.top/2019/4-17-97.html
Redis基础运维之哨兵和集群安装配置 - https://blog.weiyigeek.top/2019/4-17-576.html
Redis基础运维之在K8S中的安装与配置 - https://blog.weiyigeek.top/2019/4-17-524.html
Redis数据库性能测试及优化配置 - https://blog.weiyigeek.top/2019/4-17-527.html
0x00 Redis 管理工具 AnotherRedisDesktopManage 描述: 一个更快更好更稳定的redis桌面管理器,兼容Linux、windows、mac。更重要的是,加载大量密钥时不会崩溃。 项目地址: https://github.com/qishibo/AnotherRedisDesktopManager/releases
PhpRedisAdmin 描述: phpRedisAdmin是一个简单的web界面,用于管理Redis数据库。它是根据知识共享署名3.0许可证发布的。此代码由Erik Duckerlboer开发和维护。 项目地址: https://github.com/erikdubbelboer/phpRedisAdmin
例如,以docker方式进行部署PhpRedisAdmin1 2 3 4 5 6 7 8 9 10 docker run --rm -it -e REDIS_1_HOST=myredis.host -e REDIS_1_NAME=MyRedis -p 80:80 erikdubbelboer/phpredisadmin REDIS_1_HOST - define host of the Redis server REDIS_1_NAME - define name of the Redis server REDIS_1_PORT - define port of the Redis server REDIS_1_AUTH - define password of the Redis server ADMIN_USER - define username for user-facing Basic Auth ADMIN_PASS - define password for user-facing Basic Auth
redis-manager 1 2 3 4 5 6 $sudo docker run -d --net=host --name redis-manager \-e DATASOURCE_DATABASE='redis_manager' \ -e DATASOURCE_URL='jdbc:mysql://192.168.12.215:32106/redis_manager?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2b8' \ -e DATASOURCE_USERNAME='root' \ -e DATASOURCE_PASSWORD='weiyigeek.top' \ reasonduan/redis-manager
0x01 客户端脚本连接 (0) Redis Lua 脚本连接示例 Redis 脚本使用 Lua 解释器来执行脚本,通过内嵌支持 Lua 环境,执行脚本的常用命令为 EVAL。
EVAL script numkeys key [key …] arg [arg …] #执行 Lua 脚本。
script:Lua 脚本程序
numkeys:用于指定键名参数的个数。
key [key …]:从 EVAL 的第三个参数开始算起,表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用 1 为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。
arg [arg …]: 附加参数
EVALSHA sha1 numkeys key [key …] arg [arg …] #根据给定的 sha1 校验码,执行缓存在服务器中的脚本
sha1:通过 SCRIPT LOAD 生成的 sha1 校验码。
SCRIPT EXISTS script [script …] 查看指定的脚本是否已经被保存在缓存当中。
SCRIPT FLUSH 从脚本缓存中移除所有脚本。
SCRIPT KILL 杀死运行的Lua脚本,主要用于终止运行时间过长的脚本,比如一个因为 BUG 而发生无限循环的脚本
SCRIPT LOAD script 将脚本 script 添加到脚本缓存中,但并不立即执行这个脚本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 192.168.1.100:6379[7]> EVAL "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second 1) "key1" 2) "key2" 3) "first" 4) "second" > SCRIPT LOAD "return 'hello moto'" "232fd51614574cf0867b83d384a5e898cfd24e5a" > SCRIPT EXISTS 232fd51614574cf0867b83d384a5e898cfd24e5a 1) (integer ) 1 > EVALSHA "232fd51614574cf0867b83d384a5e898cfd24e5a" 0 "hello moto" > SCRIPT FLUSH > SCRIPT Kill
(1) PHP 脚本连接使用 redis 示例 扩展官网:https://pecl.php.net/package/redis //下载与php对应得版本(非常注意操作系统得位数) 环境:WINDOW7/XMAPP/PHP7.2 安装扩展:1 2 3 4 5 6 7 8 9 #Step1.下载TS版本且32位系统 PHP 7.2 Thread Safe (TS) x86 #Step2.解压php_redis.dll到php根目录中ext目录中 #Step3.在php.ini配置启用redis扩展;extension = php_redis #Step4.执行php -m 查看是否有该模块 @Linux下安装redis(百度即可) PHP redis 驱动:下载地址为:https://github.com/phpredis/phpredis/releases pipize/php-config
php连接使用redis案例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 <?php $redis = new \Redis(); try { $link = $redis->pconnect('127.0.0.1' ,6379 ,1 ,NULL ,100 ); if (!$link){ throw new Exception ("!Redis服务器连接失败" ); }else { echo "Redis连接成功!<br>" ; } }catch (Exception $e){ print_r($e); exit (0 ); } if ($redis->auth('weiyigeek' )){ echo "Redis AUTH 认证成功!<br>" ; }else { echo "Redis AUTH 认证失败!" ; } $redis->set('name' ,'Weiyigeek' ); $redis->mSet(['id' =>'Weiyi' ,'age' =>22 ]); $redis->setEx('key' ,120 ,'value' ); $strLen = $redis->strlen('name' ); $type = $redis->type('age' ); $redis->incrBy('age' ,8 ); $getValue = $redis->mGet(['name' ,'id' ,'age' ]); echo "name键的字符串长度:" .$strLen."<br>" ;echo "<pre>" ;var_dump($type); var_dump($getValue); echo "</pre> <br>" ;$redis->delete('list1' ); $redis->lpush('list1' ,'A' ,'C' ); $redis->rpush('list1' ,'B' ); $redis->delete('key1' ); $redis->rPush('key1' , 'A' ,'B' ,'C' ,'D' ); $lcount = $redis->lLen('key1' ); echo "key1元素个数:" .$lcount."<br>" ;echo "<pre>" ;var_dump($redis->lRange('list1' , 0 , -1 )); var_dump($redis->lRange('key1' , 0 , -1 )); echo "</pre> <br>" ;$redis->delete('driver1' ); $redis->hSet('driver1' ,'name' ,'张伟' ); $redis->hSet('driver1' ,'age' ,'22' ); $redis->hSet('driver1' ,'gender' ,0 ); $redis->hSet('driver1' ,'love' ,'Play Computer!' ); $hcount = $redis->hLen('driver1' ); $hlen = $redis->hStrLen('driver1' ,'love' ); $var = $redis->hGet('driver1' ,'name' ); $var1 = $redis->hMGet('driver1' ,array ('age' ,'gender' ,'love' )); echo "散列driver1的长度为:" .$hcount.", Love字段值的长度:" .$hlen."<br><pre>" ;var_dump($var); var_dump($var1); echo "</pre><br>" ;$redis->sRem('set1' ,'A' ,'B' ,'C' ,'D' ); $redis->sAdd('set1' ,'B' ,'A' ,'C' ,'D' ); $redis->sAdd('set2' ,'B' ,'D' ,'E' ,'F' ); $sCount = $redis->sCard('set1' ); $sinter = $redis->sInter('set1' ,'set2' ); $diffCount = $redis->sDiffStore('set3' , 'set1' , 'set2' ); echo "集合set1的长度为:" .$sCount.", set1与set2差集元素的个数为:" .$diffCount."<br><pre>" ;var_dump($sinter); var_dump($redis->sMembers('set3' )); echo "</pre><br>" ;$redis->delete('zset1' ); $redis->zAdd('zset1' ,1.0 ,'zhanghai' ); $redis->zAdd('zset1' ,3.0 ,'Weiyigeek' ); $redis->zAdd('zset1' ,7.5 ,'Chenghuiming' ); $redis->zAdd('zset1' ,8.6 ,'Kangkang' ); $zCount = $redis->zCard('zset1' ); $zCount1 = $redis->zCount('zset1' ,0 ,5.0 ); $desc = $redis->zRange('zset1' ,0 ,-1 ,true ); $asc = $redis->zRevRange('zset1' ,0 ,-1 ,true ); echo "有序集合zset1有 " .$zCount."个元素,且score分数在0~5之间的个数:" .$zCount1."<br><pre>" ;var_dump($desc); var_dump($asc); echo "</pre>" ;
(2) C# 连接使用redis 高性能Redis协议封装,支持.Net Core,经过一年多日均80亿调用量验证 项目地址:http://git.newlifex.com/NewLife/NewLife.Redis
1)基础 Redis Redis实现标准协议以及基础字符串操作,完整实现由独立开源项目NewLife.Redis提供;采取连接池加同步阻塞架构,具有超低延迟(200~600us)以及超高吞吐量的特点。 在物流行业大数据实时计算中广泛应有,经过日均100亿次调用量验证。
NewLife.Redis 一个完整的Redis协议的功能的实现,但是redis的核心功能并没有在这里面,Redis的核心功能的实现是在NewLife.Core里面,Redis的核心功能就是有这两个类实现:
有一个NewLife.Caching的命名空间,里面有一个Redis类里面实现了Redis的基本功能
另一个类是RedisClient是Redis的客户端(代表对服务器的一个连接)
Redis的封装有两层:
一层是NewLife.Core里面的Redis以及RedisClient
另一层就是NewLife.Redis,这里面的FullRedis是对Redis的实现了Redis的所有的高级功能(是Redis的一个扩展)
1 2 3 4 5 // 实例化Redis,默认端口6379可以省略,密码有两种写法 //var rds = Redis.Create("127.0.0.1", 7); var rds = Redis.Create("pass@", 7); //var rds = Redis.Create("server=;password=pass", 7); rds.Log = XTrace.Log; // 调试日志。正式使用时注释
2)C#案例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 /**打开Program.css**/ using NewLife.Threading; namespace Test { class Program { static void Main(String[] args) { Xtrace.UseConsole(); //向控制台输出日志,方便调试使用查看结果 FullRedis.Register(); //集合FULLREDIS,否则Redis.create会得到默认的redis对象 Test1(); //调用静态方法 Console.ReadKey() } } } static void Test1() { var ic = Redis.Create("pass@", 7);//创建Redis实例,得到FullRedis对象 //var ic = new FullRedis();//另一种实例化的方式 //ic.Server = ""; //ic.Db = 3; //Redis中数据库 ic.Log = XTrace.Log;//显示日志,进行Redis操作把日志输出,生产环境不用输出日志 // 简单操作 Console.WriteLine("共有缓存对象 {0} 个", ic.Count);//缓存对象数量 ic.Set("name", "大石头");//Set K-V结构,Set第二个参数可以是任何类型 Console.WriteLine(ic.Get<String>("name"));//Get泛型,指定获取的类型 ic.Set("time", DateTime.Now, 1);//过期时间秒 Console.WriteLine(ic.Get<DateTime>("time").ToFullString()); Thread.Sleep(1100); Console.WriteLine(ic.Get<DateTime>("time").ToFullString()); // 列表 var list = ic.GetList<DateTime>("list"); list.Add(DateTime.Now); list.Add(DateTime.Now.Date); list.RemoveAt(1); Console.WriteLine(list[list.Count - 1].ToFullString()); // 字典 var dic = ic.GetDictionary<DateTime>("dic"); dic.Add("xxx", DateTime.Now); Console.WriteLine(dic["xxx"].ToFullString()); // 队列 var mq = ic.GetQueue<String>("queue"); mq.Add(new[] { "abc", "g", "e", "m" }); var arr = mq.Take(3); Console.WriteLine(arr.Join(",")); // 集合 var set = ic.GetSet<String>("181110_1234"); set.Add("xx1"); set.Add("xx2"); set.Add("xx3"); Console.WriteLine(set.Count); Console.WriteLine(set.Contains("xx2")); Console.WriteLine("共有缓存对象 {0} 个", ic.Count); }
weiyigeek.top-C#案例
总结:
数据库中不合法的时间处理,习惯大于小于号不习惯用等于号,这样可以处理很多意外的数据
Set的时候最好指定过期时间防止有些需要删除的数据
Redis异步尽量不用,因为Redis延迟本身很小,大概在100us-200us,再一个就是Redis本身是单线程的,异步任务切换的耗时比网络耗时还要大
3) Redis简单案例 网站搜索的热搜词,用.NET Core和StackExchange.Redis以及Jquery-ui 主要是用了里面的autocomplete;
搜索关键字的时候不存在则添加,存在则将利用有序集合中的zincrby进行分数排序;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 #AutoController using AutoCompleteDemo.Common; using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; using System.Linq; namespace AutoCompleteDemo.Controllers { public class AutoController : Controller { private readonly IRedis _redis; private readonly string _searchKey = "search"; public AutoController(IRedis redis) { _redis = redis; } public IActionResult Index() { //keys IList<string> keys = new List<string>() { "kobe","johnson","jabbar","west","o'neal","baylor","mccann","worthy","gasol","chamberlain", "fisher","odom","bynum","horry","rambis","riley","clarkson","Williams","young","Russell", "ingram","randle","nance","brown","deng","yi","ariza","artest","walton","vujacic", "james","paul","curry","park","yao","kevin","wade","rose","popovich","leonard", "aldridge","ginobili","duncan","lavine","rubio","garnett","wiggins","westbrook","durant","ibaka", "nowitzki","pierce","crawford","love","smith","iguodala","barnes","green","thompson","harden", "lillard","mccollum","lin","jackson","nash","stoudemire","whiteside","dragic","Howard","batum" }; //init Random random = new Random(); var tran = _redis.GetTransaction(); for (int i = 0; i < 2000000; i++) { tran.SortedSetIncrementAsync(_searchKey, keys[random.Next(0, 70)], 1); } tran.ExecuteAsync(); return View(); } public IActionResult GetHotKey(string key="") { if (string.IsNullOrEmpty(key)) {//default var res = _redis.ZRevRange(_searchKey, 0, 9); //将分数从高到低排序 desc var list = (from i in res select i.ToString()); return Json(list); } else {//by user input var res = _redis.ZRevRange(_searchKey, 0, -1); var list = (from i in res select i.ToString()).Where(x => x.Contains(key)).Take(10).ToList(); return Json(list); } } [HttpPost] public IActionResult SetHotKey(string key) { if (!string.IsNullOrWhiteSpace(key)) { _redis.ZIncrby(_searchKey,key); //other //... return Json(new { code = "000", msg = "OK" }); } else { return Json(new { code = "999", msg = "keyword can not be empty!" }); } } } } #Index.cshtml @{ ViewData["Title"] = "Auto Complete"; } <div class="row"> <div class="col-md-6 col-md-offset-4" style="padding-top:50px;"> <input id="key" name="key" placeholder="search" class="form-control col-md-4"> <button class="btn btn-primary" type="button" id="searchSubmit">Search</button> <div id="result"></div> </div> </div> @section scripts{ <script type="text/javascript"> $(function () { //show hot keyword $("#key").autocomplete({ source: function (request, response) { $.ajax({ url: "@Url.Action("GetHotKey", "Auto")", //关联上面的方法 dataType: "json", data: { key: request.term }, success: function (data) { response(data); } }); }, }); //search $("#searchSubmit").click(function () { $.ajax({ url: "@Url.Action("SetHotKey", "Auto")", dataType: "json", type: "POST", data: { key: $("#key").val() }, success: function (data) { if (data.code == "000") { $("<p>search successful!</p>").appendTo("#result"); } else { $("<p>"+data.msg+"</p>").appendTo("#result"); } } }); }); }); </script> }
weiyigeek.top-搜索热词
(3) JAVA 连接操作 redis 示例 描述:采用Jedis jar包进行利用java操作Redis,所以必须将其导入工程的lib中,下面简单演示一下使用;
config.properties1 2 3 RedisUrl=10.20.10.248:6379 RedisAuth=weiyigeek.top
基础实例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 import redis.clients.jedis.Jedis;public class RedisDemo1 { public static void main (String[] args) { String[] RedisUrl=null ; String RedisAuth=null ; try { Properties prop = new Properties(); InputStream ins = RedisDemo1.class.getClassLoader().getResourceAsStream("config.properties" ); prop.load(ins); RedisUrl=prop.getProperty("RedisUrl" ).split(":" ); RedisAuth=prop.getProperty("RedisAuth" ); System.out.println("RedisServer:" + prop.getProperty("RedisUrl" ) + "\nPassword:" + RedisAuth ); Jedis jedis = new Jedis(RedisUrl[0 ],Integer.parseInt(RedisUrl[1 ])); System.out.println("正在Redis认证连接..." ); jedis.auth(RedisAuth); System.out.println("服务正在运行: " +jedis.ping()+"\n" ); jedis.select(1 ); String key = "WeiyiGeek" ; jedis.set(key, "www.weiyigeek.top" ); jedis.set("count" , "1" ); jedis.setex("Key" , 60 , "60 Sec" ); System.out.println("当前数据库总键数:" +jedis.dbSize()); if (jedis.exists(key)) { System.out.println("Redis中WeyiGeek键存储的字符串为:" + jedis.get(key)); System.out.println("其类型为 : " + jedis.type(key)); } System.out.println("incr key = " + jedis.incr("count" )); System.out.println("incrby key 5 = " + jedis.incrBy("count" , 5 )); Set<String> keys = jedis.keys("*" ); for (Iterator iterator = keys.iterator(); iterator.hasNext();) { String k = (String) iterator.next(); if (jedis.type(k).equalsIgnoreCase("string" )) { System.out.println(k + " - " + jedis.get(k)); } } System.out.println("" ); jedis.flushAll(); jedis.disconnect(); } catch (IOException e) { e.printStackTrace(); } } }
执行结果:1 2 3 4 5 6 7 8 9 10 11 12 13 RedisServer:10.20.10.248:6379 Password:weiyigeek.top 正在Redis认证连接... 服务正在运行: PONG 当前数据库总键数:3 Redis中WeyiGeek键存储的字符串为:www.weiyigeek.top 其类型为 : string incr key = 2 incrby key 5 = 7 count - 7 WeiyiGeek - www.weiyigeek.top Key - 60 Sec
(4) Python 连接Redis Sentinel实例 描述: 在客户端使用哨兵时,需要先先连接一个Sentinel实例,然后再使用 SENTINEL get-master-addr-by-name master-name
获取Redis地址信息。
通过连接返回的Redis地址信息,通过ROLE命令查询是否是Master。如果是连接进入正常的服务环节。否则应该断开重新查询。
当Sentinel发起failover后,切换了新的Master,Sentinel会发送 CLIENT KILL TYPE normal命令给客户端,客户端需要主动断开对老的Master的链接,然后重新查询新的Master地址,再重复走上面的流程。这样的方式仍然相对不够实时,可以通过Sentinel提供的Pub/Sub
来更快地监听到failover事件加快重连。
如果需要实现读写分离/读走Slave,那可以走SENTINEL slaves
来查询Slave列表并连接。
由于Redis是异步复制,所以Sentinel其实无法达到强一致性,它承诺的是最终一致性:最后一次failover的Redis Master赢者通吃,其他Slave的数据将被丢弃,重新从新的Master复制数据, 此外还有前面提到的分区带来的一致性问题。
其次,Sentinel的选举算法依赖时间,因此要确保所有机器的时间同步,如果发现时间不一致,Sentinel实现了一个TITL模式来保护系统的可用性。
脚本实例: 1 2 3 4 5 6 7 8 9 from redis.sentinel import Sentinelsentinel = Sentinel([('localhost' , 26379 )], socket_timeout=0.1 ) print(sentinel.discover_master('mymaster' )) print(sentinel.discover_slaves('mymaster' )) master = sentinel.master_for('mymaster' , socket_timeout=0.1 ) master.set('foo' , 'bar' ) slave = sentinel.slave_for('mymaster' , socket_timeout=0.1 ) slave.get('foo' ) 'bar'
Tips : (可选) 客户端可以通过SENTINEL sentinels
来更新自己的Sentinel实例列表。
0x0n 入坑出坑 (1) 安装编译 问题1.Redis进行源码编译时显示zmalloc.h:50:10: fatal error: jemalloc/jemalloc.h: No such file or directory
错误 错误信息: 1 2 3 4 5 6 7 8 9 10 11 12 /opt/soft/redis-6.2.5
错误原因: 错误的本质是我们在开始执行 make 时遇到了错误(大部分是由于gcc未安装),然后我们安装好了gcc 后,我们再执行make ,这时就出现了jemalloc/jemalloc.h: No such file or directory
。因为上次的编译失败有残留的文件,所以需要清理下然后重新编译就可以了。解决办法: 清理上次编译残留文件重新编译 Tips: 网上其它采用的方式是 make MALLOC=libc
,虽然该方法最后也是可以成功安装好 redis 但是有一些隐患的,首先我们要知道redis 需要使用内存分配器的, make MALLOC=jemalloc
就是指定内存分配器为 jemalloc ,make MALLOC=libc
就是指定内存分配器为 libc ,这个是有安全隐患的,jemalloc 内存分配器在实践中处理内存碎片是要比libc 好的,而且在README.md 文档也说明到了,jemalloc内存分配器也是包含在源码包里面的,可以在 deps 目录下看到 jemalloc 目录。
(2) 运行使用 问题0.使用命令行-a 参数时会报Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
警告 解决办法: 1 redis-cli -h 172.16.24.214 -a weiyigeek -c --no-auth-warning
问题1.警告超委托内存设置为0后台保存可能在低内存条件下失败 报错信息: 1 WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
解决办法: 1 2 3 4 5 6 echo 1 > /proc/sys/vm/overcommit_memoryvim /etc/sysctl.conf vm.overcommit_memory=1
Tips: 建议在配置文件中配置Redis错误日志文件而非直接打印: logfile /var/log/redis/redis-server.log
问题2.当创建redis集群时显示Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0.
错误 错误信息: 1 [ERR] Node 172.16.243.94:6379 is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0.
问题原因: 由于前面初始化集群时卡住,导致部分节点的nodes.conf文件中更新了新节点数据,需要删除数据,存在旧的集群相关配置未进行清理以及数据卡槽不为空。解决方法: 重置集群节点。1 2 3 4 5 6 7 rm -rf appendonly.aof dump.rdb nodes.conf redis-cli -p 7001 -c flushdb cluster reset
问题3.Redis集群启动报Node 172.16.24.214:6379 replied with error: ERR Unknown node
错误。 错误信息: 1 2 Node 172.16.24.214:6379 replied with error: ERR Unknown node 436c6a1d7e4c5f782e1e0620b831211ebb0a41a4
问题原因: 节点地址与其在集群中ID不匹配。解决办法: 重新强制节点群集与制定节点握手使其加入到集群中。如redis-cli -h 172.16.24.214 -a weiyigeek -c cluster meet 172.16.243.97 6379
命令
问题4.Redis集群进行卡槽迁移时报The following slots are open: xxxx.
错误。 错误信息: 1 2 3 4 5 6 7 8 [OK] All nodes agree about slots configuration. >>> Check for open slots... [WARNING] Node 192.168.1.100:7000 has slots in importing state (5798,11479). [WARNING] Node 192.168.1.100:7001 has slots in importing state (1734,5798). [WARNING] Node 192.168.1.101:7002 has slots in importing state (11479). [WARNING] The following slots are open: 5798,11479,1734 >>> Check slots coverage... [OK] All 16379 slots covered.
解决办法: 取消对指定卡槽 slot 进行导入(import)或者迁移(migrate)。如redis-cli -h 172.16.243.97 -a weiyigeek -c cluster setslot 5798 stable
问题5.集群初始化时一直等待 Waiting for the cluster to join
错误。 问题原因: 最开始部署时从使用docker-comopose部署的方法套用过来,由于redis.conf配置文件中参数cluster-announce-ip
选项配置了宿主机的ip,当初始化时集群节点之间需要通讯,但是再k8s中宿主机ip已经不适用,导致无法通讯一致卡住在那个位置。解决办法: 配置中禁用 cluster-announce-ip
选项,采用上面的update脚本进行解决。
问题6.当报无法连接的时候,通过 telnet192.168.1.13:6379 是无法连接通,则说明配置的哪里有问题导致的 错误信息: 【2】can't connect to node 192.168.1.1:6379
问题原因: redis的配置文件,发现,在配置文件中配置bind 127.0.0.1这个地址,修改指定的IP地址,可以同时指定127.0.0.1,这样本机和ip地址都可以访问1 2 3 4 5 bind 192.168.1.13 127.0.0.1bind 0.0.0.0
问题7.使用check进行检测集群运行状态时显示【4】[ERR] Not all 16379 slots are covered by nodes
错误。 错误原因: 由于redis clster集群节点宕机(或节点的redis服务重启),导致了部分slot数据分片丢失;在在删除节点的时候一定要注意删除的是否是Master主节点。解决办法: 官方是推荐使用fix 来修复集群,修复完成后检测下是否正确(查看别的节点)1 2 redis-cli -h 172.16.243.97 -a weiyigeek --cluster fix --cluster-fix-with-unreachable-masters 172.16.24.214:6379
特别注意: 在部分节点重启后重新回到集群中的过程期间,在check集群状态的时候会出现”[ERR] Not all 16379 slots are covered by nodes.”这个报错, 需要稍微等待一会,等重启节点完全回到集群中后,这个报错就会消失!