Redis入门
- Redis是什么
- 介绍:Redis是一个开源的key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,Redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
- 常用位置
- 配合关系型数据库做高速缓存
- 高频次,热门访问的数据,降低数据库IO
- 实时计算中常常用于储存临时数据,用于存放以下特点的数据:
- 高频次
- 读写时效性高
- 总数据量不大
- 临时性
- 做分布式锁:分布式锁需要访问数据和写入数据两个操作一起完成,因为redis是单线程的,保证了每个指令的原子性,而且有一个命令是setnx让写入和查看在一起执行。所以经常用来做分布式锁。
- Redis与类似产品的对比
- 和hbase对比
- 共同点
- 基于key-value(s)方式的存储,不适合进行分析查询。
- 不同点
- 数据量:hbase数据量远远大于redis
- 性能: Redis 存取效率更高,使用经常变化的数据。
- 数据存储时效性:redis 更适用于高频访问的临时数据
- hbase 更适合长期存储
- 全量数据不再内存中
- 和Memcached对比
- 共同点
- key-value模式
- 一般是作为缓存数据库辅助持久化的数据库
- 全量数据都在内存中
- 不同点
- redis还支持多种数据结构的存储,比如 list、set、hash、zset等。Memcached只支持string
- redis支持持久化,主要用作备份恢复
- Redis启动与配置
- 去官网下载包解压
- 配置 redis.conf,也可不配置,直接使用自带的配置文件启动,不改配置的话,只能在本机访问。直接拷贝redis文件夹下的redis.conf文件修改。
60601
#后台启动2daemonize yes34# 开启则远端机器不能访问redis-server,不开启保护则远端机器访问时需要加密码,requirepass项配置密码5# 如果开启了protected-mode,那么在没有设定bind ip且没有设密码的情况下,Redis只允许接受本机的相应6protected-mode no78# 客户端登录redis服务的密码9#requirepass 1231011#是绑定本机的IP地址,(准确的是:本机的网卡对应的IP地址,每一个网卡都有一个IP地址),而不是redis允许来自其他计算机的IP地址。12# 生产环境要写你应用服务器的地址13# bind 127.0.0.11415#server端口号16port 63791718# rdb和aof文件的保存路径,19dir ./2021# rdb的文件名22dbfilename dump6379.rdb2324# aof持久化开启/关闭25appendonly yes2627# aof的文件名称28appendfilename "appendonly6379.aof"2930# aof多久存盘一次,即同步频率设置,共三种策略,一般选每秒存31# 每次操作存32#appendfsync always33# 每秒存34appendfsync everysec35# 不主动进行同步,把同步时机交给操作系统 大约分钟级36#appendfsync no3738# 系统载入时或者上次重写完毕时,Redis会记录此时AOF大小,设为base_size,如果Redis的AOF当前大小>= base_size +base_size*100% (默认)且当前大小>=64mb(默认)的情况下,Redis会对AOF进行重写39auto-aof-rewrite-percentage 10040auto-aof-rewrite-min-size 64mb4142# 进程号43pidfile /var/run/redis_6379.pid4445# rdb的保存策略 900秒之后存了一次、300秒之后存了10次、300秒之后存了10000次46save 900 147save 300 1048save 60 100004950# 当Redis无法写入磁盘的话,直接关掉Redis的写操作51stop-writes-on-bgsave-error yes5253# 进行rdb保存时,将文件压缩54rdbcompression yes5556#在存储快照后,还可以让Redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能57rdbchecksum yes5859# 在哨兵自动切换主机时,首先通过这个有限级判断,越小越高60slave-priority 100 - 启动
- 服务端启动
221
redis-server 配置文件的名字2# 如 redis-server redis.conf - 客户端启动
441
redis-cli -h 主机 -p 端口2#如 redis-cli -h 127.0.0.1 -p 637934# 若有密码,再在客户端执行 auth 密码 - 测试验证:
221
ping2#返回pong成功 - 关闭服务
331
进入客户端后执行shutdown2或者3直接 redis-cli -p 6378 shutdown - Redis的命令行操作
- 选择数据库
441
# 默认16个数据库,类似数组下标从0开始,初始默认使用0号库2select <dbid>34如 select 1 - 五大数据类型对应的操作
- 五大数据类型都可用的操作
16161
keys *2查询当前库的所有键3exists <key>4判断某个键是否存在5del <key>6删除某个键7expire <key> <seconds>8为键值设置过期时间,单位秒。9ttl <key>10查看还有多少秒过期,-1表示永不过期,-2表示已过期11dbsize12查看当前数据库的key的数量13flushdb14清空当前库15flushall16通杀全部库(会触发rdb存盘) - String
23231
String是Redis最基本的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value。23String类型是二进制安全的。意味着Redis的string可以包含任何数据。比如jpg图片或者序列化的对象。45String类型是Redis最基本的数据类型,一个Redis中字符串value最多可以是512M678get <key>9查询对应键值10set <key> <value>11添加键值对12setex <key> <过期时间> <value>13设置键值的同时,设置过期时间,单位秒。14setnx <key> <value>15只有在 key 不存在时设置 key 的值16incr <key>17将 key 中储存的数字值增118只能对数字值操作,如果为空,新增值为119mset <key1> <value1> <key2> <value2> .....20同时设置一个或多个 key-value对21mget <key1> <key2> <key3> .....22同时获取一个或多个 value23 - List
18181
单键多值2Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素导列表的头部(左边)或者尾部(右边)。3它的底层实际是个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能会较差。45lpush/rpush <key> <value1> <value2> <value3> ....6从左边/右边插入一个或多个值。7lpop/rpop <key>8从左边/右边吐出一个值。9值在键在,值光键亡10rpoplpush <key1> <key2>11从<key1>列表右边吐出一个值,插到<key2>列表左边。12lrange <key> <index>13按照索引下标获得元素(从左到右)14lindex <key> <index>15按照索引下标获得元素(从左到右)16llen <key>17获得列表长度18 - set
18181
Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。2Redis的Set是string类型的无序集合。它底层其实是一个value为null的hash表,所以添加,删除,查找的复杂度都是O(1)。34sadd <key> <value1> <value2> .....5将一个或多个 member 元素加入到集合 key 当中,已经存在于集合的 member 元素将被忽略。6smembers <key>7取出该集合的所有值。8sismember <key> <value>9判断集合<key>是否为含有该<value>值,有返回1,没有返回010srem <key> <value1> <value2> ....11删除集合中的某个元素。12sinter <key1> <key2>13返回两个集合的交集元素。14sunion <key1> <key2>15返回两个集合的并集元素。16sdiff <key1> <key2>17返回两个集合的差集元素。18 - hash
19191
Redis hash 是一个键值对集合。2Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。3类似Java里面的Map<String,Object>456hset <key> <field> <value>7给<key>集合中的 <field>键赋值<value>8hget <key1> <field>9从<key1>集合<field> 取出 value10hmset <key1> <field1> <value1> <field2> <value2>...11批量设置hash的值12hexists key <field>13查看哈希表 key 中,给定域 field 是否存在。14hgetall <key>15列出该hash集合的所有field和values16hincrby <key> <field> <increment>17为哈希表 key 中的域 field 的值加上增量 increment1819 - zset (sorted set)
211
Redis有序集合zset与普通集合set非常相似,是一个没有重复元素的字符串集合。不同之处是有序集合的没有成员都关联了一个评分(score) ,这个评分(score)被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但是评分可以是重复了 。2因为元素是有序的, 所以你也可以很快的根据评分(score)或者次序(position)来获取一个范围的元素。访问有序集合的中间元素也是非常快的,因此你能够使用有序集合作为一个没有重复成员的智能列表。345zadd <key> <score1> <value1> <score2> <value2>...6将一个或多个 member 元素及其 score 值加入到有序集 key 当中。7zrange <key> <start> <stop> [WITHSCORES]8返回有序集 key 中,下标在<start> <stop>之间的元素9带WITHSCORES,可以让分数一起和值返回到结果集。10有序输出从小到大11zrevrange <key> <start> <stop> [WITHSCORES]12同上,逆序按评分从大到小13zincrby <key> <increment> <value>14为元素的score加上增量15zrem <key> <value>16删除该集合下,指定值的元素17zcount <key> <min> <max>18统计该集合,分数区间内的元素个数19zrank <key> <value>20返回该值在集合中的排名,从0开始。21 - Redis的持久化(2个不同形式的持久化方式):
- RDB (Redis DataBase):
- 介绍:在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。用到Linux的写时复制技术
- 自动存盘(bgsave):fork子进程存盘,但是整个过程主进程是不进行任何IO操作。
- 手动存盘(save):主进程存盘,其他操作全部阻塞
- AOF (Append Of File):
- 介绍:以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,Redis启动之初会读取该文件重新构建数据,换言之,Redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
- AOF文件故障恢复:redis-check-aof --fix appendonly.aof 检测aof文件的正确性并修复
- redis正确使用aof备份的方式,(若在使用过程中添加aof方式增加数据可靠性):
- 先在客户端中执行下面命令,设置aof开启,这时会产生一个描述当前redis的aof文件(有所有的redis数据)
111
config set appendonly yes - 然后再更改配置文件中的aof的设置,这样停止服务后aof文件就会保留
- 经典问题
- AOF和RDB同时开启,redis听谁的?听aof的
- Redis如何实现重写?
答:AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似。 - sentinal是如何使得原来的下属服从新主的,是修改了配置文件了么?
答:确实改配置文件 但是更主要的是sentinel 会发送slaveof命令 给相关的机器 - 备份是如何执行的?
答:Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。 - Redis的主从复制
- 一主多从或链型结构
- 一主多从
- 链式结构
- 常用命令
10101
# 仆跟随主的命令2#ip 和port为主的,这个命令在从上运行3slaveof <ip> <port>45# 仆不跟随主的命令,自己变为主(反客为主),需要其他从再确认主6slaveof no one78# 打印主从复制的相关信息9info replication10 - 经典问题
- 切入点问题?slave1、slave2是从头开始复制还是从切入点开始复制?比如从k4进来,那之前的123是否也可以复制
答:redis从机会复制全量数据。mysql的主从则只从当前。不同数据库的特点决定不同的复制策略 - 从机是否可以写?set可否? 不可以写只可读
- 主机shutdown后情况如何?从机是上位还是原地待命
答:从机原地待命,所以一般会使用哨兵监控主机自动切换从机 - 哨兵模式(sentinal)
- 功能
- 反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库.
- 哨兵可以做路由功能,可以路由jedis的请求到主机
- 配置和启动命令
- 配置 sentinel.conf
441
# mymaster:是主机名2# 1 :当有一个哨兵认为主机挂掉的时候就需要切换主3sentinel monitor mymaster 192.168.11.103 6379 14protected-mode no - 启动
- 先启动主从机,并执行slaveof配置主机
- 执行 redis-sentinel sentinel.conf
- 当主机挂掉,根据切换主机规则,从机自动上位
- 生产环境哨兵怎么用?
- 每个从配一个哨兵,主也配一个哨兵
- 主挂掉哨兵会发起投票,当投票数大于配置文件的数时,切换主机
- 切换主机规则根据三个条件判断,优先级、偏移量最大的(偏移量是指获得原主数据最多的),选择runid最小的从服务
- Redis集群
- 介绍:
- Redis 集群实现了对Redis的水平扩容,即启动N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N。
- 是一个无中心的集群!!!
- 配置与启动
- 配置:集群模式,在之前配置基础上加上如下几项
991
# 打开集群模式2cluster-enabled yes3# 设定节点配置文件名4cluster-config-file nodes-6379.conf5# 设定节点失联时间,超过该时间(毫秒),集群自动进行主从切换。6cluster-node-timeout 1500078# 默认是yes ,如果选no那么即使某一部分的slot完全下线(包括从机),集群也会继续以现存的数据提供服务。9cluster-require-full-coverage yes - 启动
- 服务端启动
- 启动所有redis-server
- 然后进行合体指令(需要先安装ruby,)
13131
安装ruby环境21、 yum -y install ruby-libs ruby ruby-irb ruby-rdoc rubygems32、拷贝redis-3.2.0.gem到/opt目录下43、执行在opt目录下执行 gem install --local redis-3.2.0.gem56# 切换到redis的src文件夹下7cd /opt/redis-3.2.3/src89# 此处不要用127.0.0.1, 请用真实IP地址10# 一个集群至少要有三个主节点11# 选项 --replicas 1 表示我们希望为集群中的每个主节点创建一个从节点12# 分配原则尽量保证每个主数据库运行在不同的IP地址,每个从库和主库不在一个IP地址上。13./redis-trib.rb create --replicas 1 192.168.75.102:6379 192.168.75.102:6380 192.168.75.102:6381 192.168.75.102:6389 192.168.75.102:6390 192.168.75.102:6391 - 客户端启动:
使用redis-cli -c -h <host> -p <prot> 启动客户端,-c 参数实现自动重定向。不加-c参数会报错。 - 集群中的常用指令
13131
# 命令查看集群信息2cluster nodes34# 不在一个slot下的键值,是不能使用mget,mset等多键操作。5# 可以通过{}来定义组的概念,从而使key中{}内相同内容的键值对放到一个slot中去。6mset k1{k} v1 k2{k} v2 k2{k} v278# 计算键 key 应该被放置在哪个槽上。9CLUSTER KEYSLOT <key>10# 返回槽 slot 目前包含的键值对数量。11CLUSTER COUNTKEYSINSLOT <slot>12# 返回 count 个 slot 槽中的键。13CLUSTER GETKEYSINSLOT <slot> <count> - 经典问题
- 目前市面上的redis集群方案
- 客户端方案,早期方案通过JedisShardInfo来实现分片
问题:1 分片规则耦合在客户端。 2 需要自己实现很多功能。 3 不提供高可用 - 第三方代理中间件模式:twemproxy、 codis
问题:1 成为瓶颈和风险点 2 版本基本上不再更新了 - redis3.0以后出的官方redis-cluster方案
问题:有高可用,但没有读写分离 - 什么是slots
- 一个 Redis 集群包含 16384 个插槽(hash slot), 数据库中的每个键都属于这 16384 个插槽的其中一个, 集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和 。
- 集群中的每个节点负责处理一部分插槽。 举个例子, 如果一个集群可以有主节点, 其中:
- 节点 A 负责处理 0 号至 5500 号插槽。
- 节点 B 负责处理 5501 号至 11000 号插槽。
- 节点 C 负责处理 11001 号至 16383 号插槽。
- 如果主节点下线?从节点能否自动升为主节点?
答:会 - 主节点恢复后,主从关系会如何?
答:恢复后变为从 - 如果所有某一段插槽的主从节点都当掉,redis服务是否还能继续?
答:redis.conf中的参数 cluster-require-full-coverage 。 默认是yes ,如果选no那么即使某一部分的slot完全下线(包括从机),集群也会继续以现存的数据提供服务。 - Idea访问Redis
- 创建maven工程,添加maven加依赖
881
<!-- Java的Redis客户端Jedis -->2<dependencies>3<dependency>4<groupId>redis.clients</groupId>5<artifactId>jedis</artifactId>6<version>3.2.0</version>7</dependency>8</dependencies> - 编写代码
- 普通的Jedis开发
78781
package com.shuai;23import redis.clients.jedis.Jedis;45import java.util.*;67/**8* @author Shuai9* @create 2020-07-14 0:1710*/11public class Test1 {12public static void main(String[] args) {1314//连接本地的 Redis 服务15Jedis jedis = new Jedis("hadoop102", 6379);16//查看服务是否运行,打出pong表示OK17System.out.println("connection is OK==========>: " + jedis.ping());1819//---------下面的全是逻辑代码,环境准备只需要第一行代码-----20//key21Set<String> keys = jedis.keys("*");22for (Iterator iterator = keys.iterator(); iterator.hasNext(); ) {23String key = (String) iterator.next();24System.out.println(key);25}26System.out.println("jedis.exists====>" + jedis.exists("k2"));27System.out.println(jedis.ttl("k1"));2829//Jedis-API: String30System.out.println(jedis.get("k1"));31jedis.set("k4", "k4_Redis");32System.out.println("----------------------------------------");33jedis.mset("str1", "v1", "str2", "v2", "str3", "v3");34System.out.println(jedis.mget("str1", "str2", "str3"));3536//Jedis-API: List37List<String> list = jedis.lrange("mylist", 0, -1);38for (String element : list) {39System.out.println(element);40}4142//Jedis-API: set43jedis.sadd("orders", "jd001");44jedis.sadd("orders", "jd002");45jedis.sadd("orders", "jd003");46Set<String> set1 = jedis.smembers("orders");47for (Iterator iterator = set1.iterator(); iterator.hasNext(); ) {48String string = (String) iterator.next();49System.out.println(string);50}51jedis.srem("orders", "jd002");5253//Jedis-API: hash54jedis.hset("hash1", "userName", "lisi");55System.out.println(jedis.hget("hash1", "userName"));56Map<String, String> map = new HashMap<String, String>();57map.put("telphone", "13810169999");58map.put("address", "atguigu");59map.put("email", "abc@163.com");60jedis.hmset("hash2", map);61List<String> result = jedis.hmget("hash2", "telphone", "email");62for (String element : result) {63System.out.println(element);64}6566//Jedis-API: zset67jedis.zadd("zset01", 60d, "v1");68jedis.zadd("zset01", 70d, "v2");69jedis.zadd("zset01", 80d, "v3");70jedis.zadd("zset01", 90d, "v4");71Set<String> s1 = jedis.zrange("zset01", 0, -1);72for (Iterator iterator = s1.iterator(); iterator.hasNext(); ) {73String string = (String) iterator.next();74System.out.println(string);75}76}77}78 - 集群的Jedis开发
1
package com.shuai;23import redis.clients.jedis.HostAndPort;4import redis.clients.jedis.JedisCluster;5import redis.clients.jedis.JedisPoolConfig;67import java.util.HashSet;8import java.util.Set;910/**11* @author Shuai12* @create 2020-07-16 17:1313*/14public class JedisClusterTest {15public static void main(String[] args) {16JedisCluster jedisCluter = JedisClusterTest.getJedisCluter();17jedisCluter.set("k111", "v111");18jedisCluter.set("k222", "v222");19jedisCluter.set("k333", "v333");20System.out.println(jedisCluter.get("k111"));21System.out.println(jedisCluter.get("k222"));22System.out.println(jedisCluter.get("k333"));23}2425public static JedisCluster jedisCluster = null;2627public static JedisCluster getJedisCluter() {28if (jedisCluster == null) {29Set<HostAndPort> hostAndPortSet = new HashSet<>();30hostAndPortSet.add(new HostAndPort("192.168.75.102", 6379));31hostAndPortSet.add(new HostAndPort("hadoop102", 6380));32hostAndPortSet.add(new HostAndPort("hadoop102", 6381));3334JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();35jedisPoolConfig.setMaxTotal(20);36jedisPoolConfig.setBlockWhenExhausted(true);37jedisPoolConfig.setMaxWaitMillis(2000);38jedisPoolConfig.setMaxIdle(5);39jedisPoolConfig.setMinIdle(5);40jedisPoolConfig.setTestOnBorrow(true);4142JedisCluster jedisCluster = new JedisCluster(hostAndPortSet, jedisPoolConfig);43return jedisCluster;44} else {45return jedisCluster;46}47}48}49

浙公网安备 33010602011771号