Redis 是 C 语言开发的一个开源的(听从 BSD 协议)高性能键值对(key-value)的 NoSQL的内存数据库,能够用做缓存、消息中间件等;具备如下特色:node
1. 性能优秀,数据在内存中,读写速度很是快,支持并发 10W QPS;redis
2. 单进程单线程,是线程安全的,采用 IO 多路复用机制;算法
3. 丰富的数据类型,支持字符串(strings)、散列(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)等;spring
4. 支持数据持久化。能够将内存中数据保存在磁盘中,重启时加载;数据库
5. 主从复制,哨兵,高可用;json
能够用做分布式锁。能够做为消息中间件使用,支持发布订阅;缓存
1. 缓存: 提高数据的访问性能安全
2. 可用做低配版的消息中间件,支持发布订阅springboot
3. 可用作分布式锁服务器
4. 能够实现session共享
对比内容 | Redis |
memoryCache |
存储方式 | Redis 支持持久化 | Memcache不支持持久化,会把数据所有存在内存,很难解决缓存雪崩的问题 |
数据类型 | Redis 支持五种数据类型 | Memcache 对数据类型的支持简单,只支持简单的 key-value |
底层模型 | Redis 直接本身构建了 VM 机制 由于通常的系统调用系统函数的话 会浪费必定的时间去移动和请求 |
调用系统 |
Value大小 | Redis 能够达到 1GB | Memcache 只有 1MB |
Redis对象的类型、内部编码、内存回收、共享对象等功能,都须要redisObject支持
这样设计的好处:能够针对不一样场景,对5种经常使用的数据类型设置多种不一样的数据结构实现,从而优化对象在不一样场景下的使用效率;
type: 表明一个 value 对象具体是何种数据类型。
encoding: 不一样数据类型在 redis 内部的存储方式。
好比:type=string 表明 value 存储的是一个普通字符串,那么对应的 encoding 能够是 raw 或者是 int。
若是是 int 则表明实际 redis 内部是按数值型类存储和表示这个字符串的。
固然前提是这个字符串自己能够用数值表示,好比:"123" "456"这样的字符串。
vm字段: 只有打开了 Redis 的虚拟内存功能,此字段才会真正的分配内存,该功能默认是关闭状态的。
Redis 使用 redisObject 来表示全部的 key/value 数据是比较浪费内存的
固然这些内存管理成本的付出主要也是为了给 Redis 不一样数据类型提供一个统一的管理接口,实际做者也提供了多种方法帮助咱们尽可能节省内存使用。
服务端启动:redis-server redis.config
客户端启动: redis-cli -p [服务器端口] -h [服务端ip]
1. 查看全部的key:keys *
2. 设置过时时间:set username nick EX 10 // ttl username 查看key剩余时间
3. 选择库:select 1 // redis有16个库0~15,能够选择不一样的库存储数据,redis集群默认用0号库
4. 判断key是否存在:exits username
5. 删除key:del username
6. 查看key类型: type username
7. 数据持久化: save //保存在dump.rdb文件
8. 清空redis全部数据:flushall //flushdb为清空分库的数据,不删除dump.rdb
9. 单节点批量操做:mset/mget //mset username nick mset address dg
10. 冷点数据设置超时清除:expire username 100 // expire为秒维度,pexpire为毫秒维度
11. 追加数据:若是 key 已经存在而且是一个字符串,append key value
String类型的数据结构,是key-value形式,value为字符串/json串
1. 增(原生redis命令):set user1 {name:nick, age:15, gender:male}
2. 查(原生redis命令):get user1
3. 改(原生redis命令):set user1 {name:nick, age:15, gender:male}
4. 删(原生redis命令):del user1
5. 记步器:incrby/decrby key //对value进行自增/自减,例如统计在线人数;
Hash类型的数据结构是key-value形式,每一条value有是key-value形式
1. 增(原生redis命令):
hset user1 name nick age 10 gender male //name age gender单条添加
hmset user1 name nick age 10 gender male //3个属性批量添加
hincrby user1 age //age自增
2. 查(原生redis命令):
hget user1 name //获取key为user1指向的name这个key指向的数据
hmget user1 name age gender //批量获取数据
hgetall user1 // 获取key为user1指向的全部数据
hkeys user1 // 获取key为user1指向的全部的hkey字段
hlen user1 //获取key为user1指向的数据的个数
3. 改(原生redis命令):hset user1 name nick1
4. 删(原生redis命令):hdel user1 name
List类型字符串列表,按照插入顺序排序。能够添加一个元素到列表的头部(左边)或者尾部(右边)
1. 增(原生redis命令):
LPUSH key value [value ...] // lpush nameList jack // 从左侧push
LINSERT key BEFORE|AFTER pivot value // linsert nameList before jerry tom//从左边,在jerty前面插入tom
LTRIM key begin end // 保留链表范围内的数据元素
rpoplpush key1 key2 // 原子级操做,两个链表数据交互,从key1删除,key2备份
2. 查(原生redis命令):
LPOP key // lpop key 从链表中获取数据
LRANGE key start stop // lrange nameList 0 -1 查看nameList中全部元素的个数
3. 改(原生redis命令):LSET key index value // lset nameList 1 nick2,修改nick->nick2
4. 删(原生redis命令):LREM key count value // lrem key count value 删除count个 数据为value的元素 count取0时,所有删除value
Set集合用来保存多个字符串元素,和list不用的是不容许有重复元素,而且无序
应用场景:用户的兴趣、爱好、关注人列表、粉丝列表等
1. 增(原生redis命令):
SADD key member [member ...] // sadd nameList jack nick tom jerry
2. 查(原生redis命令):
SRANDMEMBER key // srandmember nameList 随机获取一条数据,
SMEMBERS key // smembers nameList 查看nameList key的全部数据
3. 删(原生redis命令):
SREM key member // srem nameList nick
4. 判断元素是否在集合中:
SISMEMBER key member // sismember nameList jack
5. 不一样集合间的交、并、差集
SINTER key [key ...] // sinter nameList nameList2
SUINON key [key ...] // sinter nameList nameList2
SDIFF key [key ...] // sinter nameList nameList2 以第一个key为主
ZSet在Set集合的基础之上绑定了一个score做为排序的依据
1. 增(原生redis命令):
ZADD key [NX|XX] [CH] [INCR] score member [score member ...]
zadd result 100 jack //100为score
ZINCRBY key increment member // zincrby result 50 jack 增长jack成员的分数
2. 查(原生redis命令):
ZCARD key // 查看key里面数据的个数
ZSCORE key member // zscore result jack 查当作员的score
ZRANGE key start stop [WITHSCORES] // zrange result 0 -1 查看排名
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] //根据score筛选
ZRANK key member //计算成员的score排名
3. 删(原生redis命令):
ZREM key member [member ...] // zrem result jack 删除某个成员
ZREMRANGEBYRANK key start stop // 删除指定排名内的升序元素
ZREMRANGEBYSCORE key min max // 删除score在[min, max]范围的元素
4. 不一样集合间的交、并、差集
ZINTERSTORE destination numkeys key [key ...] // zinterstore out 2 result result1,destination表示输出的ZSet 交集的score相加
1. bind 127.0.0.1 ###指定redis只接收来自该IP地址的请求,若是不进行设置,将处理全部请求
2. protected-mode yes ###是否开启保护模式,默认开启;若是关闭,外部网络能够直接访问;若是开启,则须要bind ip或者设置访问密码(安全考虑)
3. port 6379 ###redis监听的端口
4. tcp-backlog 511 ###默认511,此参数标识肯定了TCP链接中已完成队列(完成三次握手以后)的长度,能够理解为进程尚未accept的TCP链接的队列,tcp-backlog必须小于等于Linux系统定义的/proc/sys/net/somaxconn值
Linux的somaxconn默认为128,当系统并发量大,而且客户端速度缓慢的时候,能够将这两个参数一块儿参考设置,建议修改成2048,在/etc/sysctl.conf中添加 net.core.somaxconn=2048,而后终端执行sysctl -p命令生效
5. timeout 0 ###设置客户端空闲超过timeout时,redis服务端会断开链接,为0时表示服务端不会主动断开链接,此值不能小于0
6. tcp-keepalive 300 ###redis3.2之后默认为300秒,配置上这个参数以后,对于一些没有正常关闭的客户端,也能够及时关闭,链接超时受tcp-keepalive和另外三个参数影响:
tcp_keepalive_time default 7200 seconds
tcp_keepalive_probes default 9
tcp_keepalive_intvl default 75 seconds
链接超时公式为: tcp_keepalive_time+tcp_keepalive_intvl*tcp_keepalive_probes=7895s=131.58min7. daemonize yes
8. pidfile /var/run/redis/redis.pid ####redis的进程文件
9. loglevel notice ###服务端日志级别,包括debug(不少信息,方便开发、测试),verbose(许多有用的信息,可是没有debug级别信息多),notice(适当的日志级别,适合生产环境),warn(只有很是重要的信息)
基于上述配置文件的修改项:bind、port
1. 启动redis服务端
redis-server redis.config
2. 启动客户端
redis-cli redis-cli -h [host_ip] -p [port]
3. 中止redis服务端
登陆客户端执行:redis-cli -h [host_ip] -p [port] shutdown
1. 添加maven依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> <version>1.4.7.RELEASE</version> </dependency> </dependencies>
2. application.properties配置
spring.redis.sharedpool.nodes=192.168.75.*:6739,192.168.75.*:6780,192.168.75.*:6381
spring.redis.sharedpool.maxTotal=200
spring.redis.sharedpool.maxIdle=10
spring.redis.sharedpool.minIdle=2
3. springboot管理redis链接池
@Configuration @ConfigurationProperties("spring.redis.sharedpool") @Setter @Getter public class SharedJedisConfigPool { private List<String> nodes; //[192.168.75.132:6739,192.168.75.132:6780,192.168.75.132:6381] private Integer maxTotal; private Integer maxIdle; private Integer minIdle; @Bean public ShardedJedisPool sharedPoolInit() { List<JedisShardInfo> infoList = new ArrayList<>(); for (String node : nodes) { String ip = node.split(":")[0]; int port = Integer.parseInt(node.split(":")[1]); infoList.add(new JedisShardInfo(ip, port)); } GenericObjectPoolConfig config = new GenericObjectPoolConfig(); config.setMaxTotal(maxTotal); config.setMaxIdle(maxIdle); config.setMinIdle(minIdle); return new ShardedJedisPool(config, infoList); } }
4. 链接池测试
class DemoApplicationTests { @Autowired private ShardedJedisPool pool; @Test public void func4() { ShardedJedis jedis = pool.getResource(); jedis.set("user", "nick"); } }
class DemoApplicationTests { @Autowired private ShardedJedisPool pool; @Test public void func1() { String key = "04f2c34c"; ShardedJedis jedis = pool.getResource(); if (jedis.exists(key)) { //redis缓存有数据 String value = jedis.get(key); } else { String key2 = "04f2c34c";//查询数据库 jedis.set(key2, "nick"); } }
}
1. 旧版redis中一般使用客户端分片来解决水平扩容问题,即启动多个redis服务端,客户端决定key交由哪一个节点存储,下次客户端直接到该节点读取key
2. 能够实现将整个数据分布存储在N个数据库节点中,每一个节点只存放总数据量的1/N。
3. 对于须要扩容的场景来讲,在客户端分片后,若是想增长节点,须要对数据进行手工迁移,在迁移时为了保证数据一致性,须要将集群暂时下线,相对比较复杂
自定义简单版分片规则以下:
n个redis服务端几乎均匀存储,hash取余,散列,只要有散列就会有数据倾斜
key.hashCode()结果可正可负
key.hashCode()&Integer.MAX_VALUE结果必定为正数,key和结果惟一对应
key.hashCode()&Integer.MAX_VALUE%n
如3.3.3小节讨论,在使用redis集群时,能够经过自定义的简单版的hash散列方式分片,可是有如下两个缺点,所以引出hash一致性算法
1. 数据倾斜不可避免,只要有hash散列,就必定会有数据倾斜,不可能彻底均匀;
2. 扩容、缩容时,数据迁移量巨大★
SharedJedis实现了分片底层的算法——hash一致性(解决了取余算法再扩缩容时,数据迁移量过大的问题);为何?redis的hash逻辑为:将全部节点信息hash散列到0~2^32-1区间
引入问题:
1. 数据倾斜:
若是节点的数量不多,而hash环空间很大( 0 ~ 2^32),直接进行一致性hash会致使节点在环上的位置会很不均匀,挤在某个很小的区域。最终对分布式集群的每一个实例上储存的缓存数据量不一致,会发生严重的数据倾斜;
2. 缓存雪崩
若是每一个节点在环上只有一个节点,那么能够想象,当某一集群从环中消失时,它本来所负责的任务将所有交由顺时针方向的下一个集群处理。
例如当6379退出时,它本来所负责的缓存将所有交给6380处理。这就意味着6380的访问压力会瞬间增大。若是6380由于压力过大而崩溃,那么更大的压力又会向6381压过去,最终服务压力就像滚雪球同样越滚越大,最终致使雪崩
引入虚拟节点
解决上述两个问题最好的办法就是扩展整个环上的节点数量,所以咱们引入了虚拟节点的概念。一个实际节点将会映射多个虚拟节点,屡次hash,这样Hash环上的空间分割就会变得均匀。
在同一台虚拟机上构建1主3从的主从模式,一个主节点通常最多6个从节点;
0. 主节点redis配置文件bind要绑定0.0.0.0表示全部链接均可以访问,没有绑定的话,不能够访问主;
1. 建立从节点redis启动配置文件,redis02.config、redis03.config,并修改端口为6380、6381
2. 启动3个redis-server实例 redis-server redis.config、redis-server redis02.config、redis-server redis03.config
3. 主从关系建立,规定6379节点master、6380节点做为slave 、6381节点做为slave;
4. 登陆查看节点状态的命令,以客户端登陆6379节点为例:6379> info replication
4. 经过配置文件定义主从关系
5. 经过命令挂在主节点,命令以下:从节点> slaveof masterip masterport
6. 本处经过命令方式指定主从关系,客户端登陆6080、6081服务端,执行第五步命令
7. 查看637九、638一、6382节点的replicatinon信息,
测试过程:
主节点6379写数据,从节点6380读数据
1. 登陆主节点客户端 set name nick
2. 登陆从节点客户端 get name
3. 登陆从节点 flushal 执行失败
4. 主节点shutdown
测试结果:
★从节点配置默认为只读模式read-only,主节点宕机,从节点不会自动变为主节点,从节点info replication状态变为以下:
master_link_status:down
master_last_io_seconds_ago:-1
redis-cluster(redis 3.0)出现以前,redis的使用几乎都是围绕哨兵模式展开,哨兵过程以下:
1. 起单独线程(特殊的redis-cluster)开启对主从结构的监听
2. 监听主,从主节点调用命令info replication,获取主从结构的全部信息,保存在内存,每1秒钟发起一次心跳检测(RPC远程通讯协议),一旦主节点宕机,哨兵集群发起投票选举,过半票数肯定结果,增长可信;
3. 哨兵容忍度:容许宕机的个数是哨兵的容忍度,为了保障多票选取,5个哨兵集群节点的容忍度为2,6个哨兵集群的容忍度为2;2n-1个集群和2n个集群的容忍度是同样的,所以哨兵集群都是奇数个(少用一个资源)
模式:3个哨兵节点,管理6379,6380,6382主从结构,过程以下:
1. 修改哨兵的模板配置文件(sentinel.conf),配置3个哨兵端口:26379,26380,26381
2. 释放保护模式注释,修改成no
3. 修改监听主节点的配置核心内容:sentine monitor mymaster [master_ip] [master_port] [num]
3..1 mymaster是自定义名称 标识当前哨兵监听的主从结构的代号,多个哨兵监听同一个主从结构的话,此处要保持一致
3.2 num表示为主观下限票数,当哨兵集群不断宕机时,最少要剩下的节点数量,和宕机容忍度有关
4. 拷贝三份,分别作如上修改
5. 启动哨兵(启动以前肯定主从集群正常):redis-sentinel sentine.conf
6. 哨兵没有restart等操做
7. kill掉主节点,查看哨兵日志
8. 哨兵发现主节点宕机,开始投票,查看日志num,肯定宕机,投票选取新的节点做为主节点,主节点一旦恢复,则会也从节点角色加入集群,继续提供服务
9. 重启哨兵日志时,删除尾部日志,避免影响本次启动
1. application.properties配置
spring.redis.sentinel=192.168.75.132:26379,192.168.75.132:26380,192.168.75.132:26381 spring.redis.sharedpool.maxTotal=200 spring.redis.sharedpool.maxIdle=10 spring.redis.sharedpool.minIdle=2
2. springboot管理sentinel链接池
@Configuration @ConfigurationProperties("spring.redis.sentinel") @Getter @Setter public class SentinelConfig { private List<String> nodes; private Integer maxTotal; private Integer maxIdle; private Integer minIdle; @Bean public JedisSentinelPool sentinelPoolInit() { //搜集哨兵集群信息 Set<String> sentinelSet = new HashSet<>(); for (String node : nodes) { String ip = node.split(":")[0]; int port = Integer.parseInt(node.split(":")[1]); sentinelSet.add(new HostAndPort(ip, port).toString()); } //链接池配置对象 GenericObjectPoolConfig config = new GenericObjectPoolConfig(); config.setMaxTotal(maxTotal); config.setMaxIdle(maxIdle); config.setMinIdle(minIdle); //构建哨兵管理对象 return new JedisSentinelPool("mymaster", sentinelSet, config); } }
3. 链接池测试
@SpringBootTest class DemoApplicationTests {
@Autowired private JedisSentinelPool sentinelPool; @Test public void func5() { HostAndPort masterIP = sentinelPool.getCurrentHostMaster(); Jedis jedis = sentinelPool.getResource(); //哨兵模式只能操做主节点 String name = jedis.get("name"); } }