redis笔记
目录
简介
redis是一个高性能的key-value数据库,有一下3个特点:
- 支持数据的持久化;
- 提供字符串、hash、列表、无序集合、有序集合5种数据类型;
- 支持备份,即master-slave模式;
Redis与MemoryCache比较:
- 性能差不多,redis单线程,MemoryCache多线程,并发处理好;
- MemoryCache只支持字符串;
- redis支持持久化;
安装使用
linux下安装:
下载->解压->进入根目录->执行make && make install;
linux下运行:redis-server redis.conf
windows下运行:redis-server.exe redis.windows.conf;
连接:redis-cli.exe -h 127.0.0.1 -p 6379
若要外部访问,则需要在防火墙中开放相应端口,即在/etc/sysconfig/iptables文件中参照开放22端口的方法,复制一行,改为6379,然后重启防火墙(service iptables restart);
常用配置
daemonize no #守护线程方式运行,默认非守护
protected-mode yes #保护模式,禁用外网访问,默认开启
pidfile /var/run/redis.pid #守护时,pid保存路径
port 6379 #端口
bind 127.0.0.1 #绑定主机ip
timeout 300 #客户端闲置多久关闭连接,0时关闭该功能
loglevel verbose #日志级别,共四个:debug、verbose、notice、warning;
logfile stdout #日志记录方式
databases 16 #数据库数量,通过select切换
save 900 1
save 300 10
save 60 10000 #多长时间有多少更新时,同步到数据库,以上时默认配置;
rdbcompression yes #保存时是否压缩,默认LZF压缩
dbfilename dump.rdb #保存的文件名
dir ./ #保存的目录
slave of ip port #本机为slave时,指定master的ip、port
requirepass foobared #连接密码
maxclients 128 #最大客户端数量,默认无限制
maxmemory <bytes> #最大内存限制
appendonly no #是否每次更新记录日志
appendfilename appendonly.aof #日志文件名
appendfsync everysec #日志更新条件:no、always、everysec
vm-enabled no #是否启用虚拟内存机制
vm-swap-file /tmp/redis.swp #虚拟内存文件全名
vm-max-memory 0 #大于该值的value数据存到虚拟内存
vm-page-size 32 #分页大小
vm-pages #分页数量
vm-max-threads 4 #swap文件的访问线程数
include /path/to/local.conf #从其他文件读取配置
数据类型
- 字符串:二进制安全,可以存任何数据,比如图片,最大512M;
- 哈希:键值对集合,适合存对象;
- 列表:双向链表,增删快;适合排行榜、消息队列等场景;
- 无序集合Set:哈希实现,不重复,并提供了求交集、并集、差集的功能;
- 有序集合:Set集合加了权重参数,适合排行榜,带权重的消息队列。
命令
redis命令详情参见:https://www.runoob.com/redis/redis-commands.html
redis桌面客户端工具:https://github.com/qishibo/AnotherRedisDesktopManager/releases
小结:每种数据类型都有自己的操作命令,且一般操作哈希的以H开头,操作列表以l或r或b开头,操作集合以s开头,有序集合以z开头。
发布订阅
订阅者订阅后,就可以收到发布者推送的消息;subscribe xxx订阅xxx,publish xxx发布到xxx。subscribe前加un表示退订,加p表示按正则匹配频道。
事务
只保证三点:①exec前,命令只放入队列缓存;②事务执行后,即使执行失败,仍然继续;③命令执行时,不会插入其他客户端命令。reids中,单个命令是原子的,事务不能保证原子性。
集群
分片对象
jedis分片对象:启动多个redis节点,每个节点处理一部分数据;
自定义的分片计算:假设有n个结点,按key的hash与上Integer最大值得到一个正整数,在对n求余,得到的余数为对应redis节点;
jedis的分片计算:jedis内部封装了hash一致性算法,开发者只需要通过JedisShardInfo创建连接池即可使用;具体分片连接池代码示例见java连接redis;
hash一致性算法:取0到2^32-1的区间,将节点信息字符串通过hash映射到该区间的一个节点,然后将key也通过hash映射到该区间一个节点,然后顺时针找最近的redis节点,则该key就在该节点处理;该算法具有以下特点:
- 区间弧段随着节点数增多而减少,扩容时迁移数据量随之减少;
- 算法还引入虚拟节点,默认为160×节点权重值(权重默认1),这样虚拟节点越多,平衡性越好;故还可以通过调整权重值来调整数据处理比例;
主从复制
上面分片式的分布式集群不能保证数据的高可用,当有节点故障时,不能实现自动故障转移从而导致缓存雪崩,主从复制则是集群高可用的基础结构之一;
主从复制结构:即一个master主节点下有多个slave从节点,主节点数据可以读写,从节点工作在只读模式,并从主节点同步数据;redis支持1主多从,多级主从,一般最多两级主从,6个从节点;下面以1主2从为例搭建:
- 准备3个redis节点,3个redis.conf配置文件;
- 若从节点还有从节点,则将从节点只读关闭(slave-read-only no);
- 启动三个节点;
- 登录到子节点,执行命令:slaveof 主节点ip 端口;(若在子节点中添加了配置:slaveof ip port,则不需要执行该命令)
- 主从复制完成;
但是该形式的主从复制结构只能完成数据同步,无法转移故障;
哨兵模式
哨兵是redis提供的监听主从结构的第三方进程,所有相同的监听哨兵形成集群管理主从结构;即调用info replication命令查看主从信息,同时定时对主节点进行心跳检测,一旦发现主节点宕机则会投票选举新的主节点,并通知主从结构中所有节点更新配置;
宕机机制与选举:选举使用的是过半机制,即集群中超过半数的哨兵判断主节点宕机了才真正宕机,主节点宕机后哨兵会投票选举新的主节点,票数操作一半的当选;(选举依据:网络连接正常->5秒内回复过INFO命令->10×down-after-milliseconds内与主连接过的->从服务器优先级->复制偏移量->运行id较小)
当哨兵集群无法正常工作时,是需要重新搭建哨兵环境的,哨兵的配置文件在运行过程中可能会发生改变,重搭环境时,需要在此配好这些文件;
哨兵模式的集群搭建:
- 启动主节点:redis-server redis.conf;
- 启动从节点:redis-server redis.conf;
- 修改哨兵配置文件,启动哨兵进程:redis-sentinel sentinel.conf;
- 哨兵集群搭建完成;
哨兵集群解决了分布式集群中单个数据分片的高可用问题,但集群分片之间互不相通;
附:sentinel.conf配置文件说明:
# bind 127.0.0.1 192.168.1.1
# 守护进程模式
daemonize yes
# 保护模式,默认是yes,改为no
protected-mode no
# 端口
port 26379
# sentinel announce-ip <ip>
# sentinel announce-port <port>
# sentinel announce-ip 1.2.3.4
#工作路径
dir /tmp
# 日志文件路径和文件名(填相对路径时,是相对的工作路径)
logfile "./sentinel.log"
# 配置主节点名称、ip、端口、主观下线票数(哨兵集群最少哨兵个数)
# ip不要填127.0.0.1,会造成代码访问失效
sentinel monitor mymaster 192.168.1.1 6379 2
# 主节点名称和密码
sentinel auth-pass mymaster 1234
# master或slave多长时间(默认30秒)不能使用后标记为s_down状态。
sentinel down-after-milliseconds mymaster 30000
#执行故障转移时,最多有多少个从服务器同时对新的主服务器进行同步
sentinel parallel-syncs mymaster 1
# 投票失败后过多久继续投票,默认3minutes,改为3s方便测试
sentinel failover-timeout mymaster 3000
# sentinel notification-script mymaster /var/redis/notify.sh
# sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
cluster集群
redis3.0之后,出现了redis-cluster的集群计数,解决了之前集群中的分片互相不相关的问题;
特点
- 节点两两之间互通,使用二进制协议通讯来优化传输速度;
- 哨兵进程消失;监听和投票工作交个master完成;
- 客户端可以不关心数据分配,由集群内部处理;
- 引入hash槽:所有key不直接和节点信息绑定,而是绑定槽道号(slot),每个主节点各自维护维护一批槽道号,如果槽道号迁移,key也一并迁移(微调);
槽道
即每一个redis的集群节点的内存中保存两个组成槽道的数据结构,由两部分内存数据组成计算逻辑;分别是位序列和索引数组,范围都是[0, 16384(2^14)];这两种数据的功能如下:
- 位序列(私有):每一位都是二进制,1表示在当前节点,0表示不在当前节点;这样就可以直接通过下标值快速判断当前节点能否处理;
- 索引数组(公有):除了位序列外,每个元素还指向了对应节点信息对象的引用,在判断不在当前节点处理时,则会redirect到正确节点;
搭建
集群的搭建需要使用ruby语言环境(ruby -v查询是否安装),redis内部包含一个用ruby语言编写好的命令文件redis-trib.rb,简化了集群的搭建、扩容、管理等;下面示例搭建一个结构为3个master(集群至少需要三个主节点)和3个slave的集群(注意:在重新搭建集群时要先删除aof、dump、node等文件):
- 修改redis.conf配置文件,修改内容为:
# 注释bind
# 关闭保护模式
protected-mode no
# 修改端口号
port 8000
# 后台运行
daemonize yes
# 日志文件路径(相对redis根目录)
logfile "./cluster8000.log"
# 持久化文件名
dump dump8000.rdb
# 改为使用aof方式持久化
appendonly yes
appendfilename "appendonly8000.aof"
# 持久化同步策略:no、always、everysec
appendfsync everysec
# 开启集群
cluster-enabled yes
# 节点文件名
cluster-config-file ./nodes8000.conf
# 请求超时时间(默认15秒)
cluster-node-timeout 15000
- 拷贝六份配置文件,并修改端口号和相关的文件名("%s/8000/8001/g");
- 启动六个节点:redis-server xxxx.conf;
- 将redis/src下的redis-trib.rb拷贝到/usr/local/bin目录下;
- 如果节点设置的有密码,则用find搜索clients.rb文件,然后在类似/usr/lib/ruby/gems/1.8/gems/redis-3.2.1/lib/redis/client.rb的文件中添加**:password => “1234”**:
- 创建集群:redis-trib.rb create ip1:port1 ip2:port (–replicas 1);(只添加三个主节点,–replicas 1代表自动为每一个master至少分配一个slave)
- 添加剩下的三个从节点;
- 集群搭建完成。
使用
集群搭建完成后,其登录命令为:redis-cli -c -h ip -p port(-c代表集群方式登录);登录后就可以查看和管理集群了;
cluster info;查看集群状态,包含状态、槽道、大小、节点数、发送接收大小等信息,其中epoch是集群的逻辑计算时间,与集群的变化和事件有关,数字越大代表配置或操作越新,该值整个集群保持一致;
cluster nodes:查看集群节点信息,包含id、ip:port、角色及状态、主节点id、管理的槽道号范围等信息;
动态添加新节点:redis-trib.rb add-node args;参数为新节点的ip:port和集群中任意节点的ip:port;添加从节点时–slave 和–master-id 必须同时配置就且指定主节点id;默认添加的新主节点没有分配槽道,需要通过命令redis-trib.rb reshard ip:port迁移,参数为任意旧节点;(注意:不能添加有节点数据和缓存数据的节点,也不能迁移有数据的槽道)
删除节点:redis-trib.rb del-node ip:port id,参数为集群中节点的ip,端口和id;(注意:只能删除没有槽道的主节点或slave节点)
平衡slot:redis-trib.rb rebalance --weight id=权重值 ip:port,权重值默认为1,该命令可以修改权重值,在根据权重值修改槽道号范围;
附:两种持久化模式,
- aof:实时记录所有写命令,数据保存在aof文件;小量数据性能是rdb一半,海量时差不多;
- dump:调用save命令时,保存当前内存数据到dump文件;
- 同时开启时,aof优先级更高;
redis原命令
搭建:
- 修改好配置文件,启动6个redis;
- -c方式登录任意节点执行握手命令:cluster meet ip port;(是其他节点的ip和port)
- 观察集群节点,此时还没有分配槽道;
- 分配槽道号:cluster addsolts 槽道号;(槽道号为一个或多个数字)
# 推荐使用shell脚本分配
#分配节点1
for i in {1..5461}; do redis-cli -h 10.9.17.153 -c -p 8000 cluster addslots $i;done
# 分配节点2······
- 添加从节点:登录希望成为从节点的节点执行cluster replicate 主节点id命令;
- 集群搭建完成。
空槽道迁移:目标节点导入:cluster setslot 槽道号 importing 源节点id;源节点导出:cluster setslot 槽道号 migrating 目标节点id;(一次只能迁移一个!);通知过半主节点槽道迁移了:cluster setslot 槽道号 node 主节点id;对于非空槽道迁移,则是在通知其他节点前加两步:确定指定槽道所包含的keys:cluster getkeysinslot 槽道号 范围(需要在槽道号对应的节点上执行命令),迁移key:migrate 目标ip port “” 0 500 keys k1 k2 k3···;
Java连接redis
一、直接在代码中连接redis:
@Test
public void test01(){
Jedis jedis = new Jedis("192.168.140.101", 6379);
jedis.auth("1234");
//业务代码······
jedis.close();
}
二、使用连接池直接在代码中连接:
@Test
public void test02(){
JedisPool pool = new JedisPool("192.168.140.101", 6379);
Jedis jedis = pool.getResource();
jedis.auth("1234");
//业务代码······
jedis.close();
pool.close();
}
三、分片连接池连接:
@Test
public void test03(){
List<JedisShardInfo> infos = new ArrayList<>();
JedisShardInfo info = new JedisShardInfo("192.168.140.101", 6379);
info.setPassword("1234");
infos.add(info);
//更多分片节点······
ShardedJedisPool pool = new ShardedJedisPool(
new JedisPoolConfig(), infos);
ShardedJedis jedis = pool.getResource();
//业务代码······
jedis.close();
pool.close();
}
四、连接哨兵:
@Test
public void testSentinel(){
Set<String> sentinels = new HashSet<>();
sentinels.add("192.168.140.101:26379");
sentinels.add("192.168.140.101:26380");
sentinels.add("192.168.140.101:26381");
JedisSentinelPool jedisSentinelPool =
new JedisSentinelPool("mymaster", sentinels, "1234");
Jedis jedis = jedisSentinelPool.getResource();
//业务代码······
jedis.close();
}
五、连接redis集群:
//只需要连接集群中的任意一个节点,会自动查询到其他节点信息;
//同时操作key时,还会计算槽道号,从而得到处理的主机;
@Test
public void testCluster02() throws IOException {
JedisCluster jedisCluster = new JedisCluster(
new HostAndPort("192.168.140.101", 8004),
0, 500, 3, "1234",
new JedisPoolConfig());
//业务代码······
jedisCluster.close();
}
数据一致性
情况一:更新数据库后执行清空缓存异常,导致缓存中仍然是旧的数据;解决办法是:每次更新数据时要先清空缓存,再更新数据库,若清空缓存失败,则暂时不更新;
情况二:高并发情况下,清了缓存,还没来得及更新数据库就被其他查询请求将旧数据存放到了缓存;解决:使用队列,每次更新操作之前先将key入缓存队列,其他查询在查询之前先检查缓存队列,若非空,则正常操作,否则直接查询数据库—>优化解决:创建固定长度的列表,更新之前先计算Hash取余的值,然后将key放在该值对应的索引位置上,有其他查询时,同样先计算Hash取余值,根据该值去列表查询是否存在,若不存在,则正常操作,否则直接查询数据库。
缓存问题
缓存穿透
指查询一个一定不存在的数据,而查不到的数据又不会写入缓存,所以每次这个查询都要去数据库,失去了缓存的意义,大流量时还可能会使数据库无法承受,其解决方案有两种:
- 空结果也进行缓存;
- 采用布隆过滤器;
缓存雪崩
一般是由于我们设置缓存时采用了相同的过期时间,从而导致缓存某一时刻同时失效,使得数据出现周期性的高压力,其解决方案是给每个key设置为一个随机的过期时间;
缓存击穿
当某个热点key缓存失效时,这时大量的访问会直接请求数据库,造成数据库压力,热点key应该具有以下特点:
- 这种key的访问量可能非常大;
- 这个key的缓存构建需要一定的时间;
其解决方案有:
- 设置永不过期:
- 设置真正的永不过期;
- 把过期时间设置在value中,查询时发现过期了则通过一个后台线程重新构建;
- 也可以在预计会有热点访问之前先手动访问一次;
- 使用互斥锁,只让一个线程构建,其他线程必须等待其构建完成;
- 提前互斥锁,发现过期时延长1秒过期时间,同时重新构建缓存;
Redis全面笔记:安装、集群与缓存问题
本文是Redis笔记,介绍了Redis特点、与MemoryCache对比,涵盖安装使用、常用配置、数据类型等内容。重点阐述集群模式,如主从复制、哨兵模式、cluster集群搭建与使用。还提及Java连接Redis方法,以及数据一致性和缓存穿透、雪崩、击穿等问题的解决方案。
1292





