NoSQL(Not Only SQL),不仅仅是SQL,是一项全新的数据库理念,泛指非关系型数据库。
(1)High performance-对数据库高并发读写的需求
(2)Huge Storage-对海量数据的高效率存储和访问的需求
(3)High Scalability && High Availability-对数据库的高扩展性和高可用性的需求
NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题
mongoDB
Redis
1.易扩展
NoSQL数据库种类繁多,但是一个共同的特点都是去掉关系数据库的关系型特性。数据之间无关系,这样就非常容易扩展,也无形之间,在架构层面上带来了可扩展的能力。2.大数据量,高性能
NoSQL 数据库都具有非常高的读写性能,尤其是在大数据量下,同样表现优秀,这得益于它的无关系性,数据库结构简单。3.灵活的数据模型
NoSQL 无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式,而在关系型数据库里,增删字段是一件非常麻烦的事情,如果是大数据量的表,增加字段简直就是一个噩梦。这点在大数量的Web2.0时代尤其明显。4.高可用
NoSQL在不太影响性能的情况,就可以方便的实现高可用的架构,比如Cassandra,HBase模型,通过复制模型也能实现高可用。 由C语言实现的直接操作内存的开源的高性能的数据库软件
Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库,他通过提供多种键值数据类型来适应不同场景下的存储需求,目前为止Redis支持的键值数据类型如下:
字符串类型散列类型列表类型集合类型有序结合类型Redis安装包:以源码形式提供的 .c文件
将.c文件编译为.o文件,需要安装:gcc
将编译后的文件安装在Linux系统上
编译成功
安装成功
安装成功后,在/usr/local/redis/bin下有几个可执行文件
redis-benchmark ---性能测试工具 redis-check-aof ---AOF文件修复工具 redis-check-dump ---RDB文件检查工具(快照持久化文件) redis-cli ---命令行客户端 redis-server ---redis服务器启动命令前端模式启动
/usr/local/redis/bin/redis-server后台模式启动
1.修改配置文件
vim /usr/local/redis/redis.conf启动时,指定配置文件
cd /usr/local/redis/ ./bin/redis-server ./redis.confredis默认端口6379,通过当前服务进行查看
ps -ef | grep -i rediscentos7开放redis端口
firewall-cmd --permanent --zone=public --add-port=6379/tcp启动redis客户端
./bin/redis-cliRedis的配置文件位于Redis安装目录下,名为redis.conf(Window名为redis.windows.conf)
//1.redis默认不是以守护进程的方式运行的,可以通过配置修改,使用yes启用守护进程 daemonize no //2.当redis以守护进程运行时,redis默认把pid写入/var/run/redis.pid文件,可以通过pidfile指定 pidfile /var/run/redis.pid //3.指定redis监听端口,默认端口为6379 port 6379 //4.绑定主机地址 bind 127.0.0.1 //5.当客户机闲置多长时间后关闭连接,如果指定为0,表示关闭该功能 timeout 0 //6.指定日志记录级别,redis总共支持4个级别:debug,verbose,notice,warning,默认为verbose loglevel verbose //7.设置数据库数量,默认数据库为0,可以使用select <dbid>命令连接上指定数据库id,默认有0-15总共16个数据库 databases 16 //8.指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合 save <秒数> <更新数> //9.指定存储到本地数据库是否压缩数据,默认为yes,redis采用LZF压缩,如果为了节省cpu时间,可以关闭该选项,但会导致数据库文件变得巨大 rdbcompression yes //10.指定本地数据库文件名,默认值为dump.rdb dbfilename dump.rdb //11.指定本地数据库存放目录 dir ./ //12.设置redis连接密码,如果配置了连接密码,客户端在连接redis时需要通过AUTH <password>命令提供密码,默认关闭。 requirepass foobared //13.设置最大客户端连接数 maxclients 1000 //14.设置redis最大内存限制,建议: maxmemory <bytes> //15.指定是否在每次更新后进行日志记录,redis在默认情况下时异步的把数据写入硬盘,如果不开启,可能会在断电时导致一段时间内的数据丢失,因为redis本身同步数据文件是上面的save条件来同步的,所以有的数据会在一段时间内只存在于内存中,默认为no。 appendonly no //16.指定更新日志文件名,默认为appendonly.aof appendfilename appendonly.aof redis作为优秀的缓存中间件,时常会存储大量的数据,即使采用了集群部署来动态扩容,也应该即时的整理内存,维持系统性能(如果数据一直新增,内存很快就会沾满) 在redis中有两种解决方案: 1.为数据设置超时时间,设定内存空间,建议内存不要超过1G 256-512m 2.采用LRU算法动态将不用的数据删除。内存管理的一种页面置换算法,对于在内存中但又不用的数据块(内存块)叫做LRU,操作系统会根据那些数据属于LRU而将其移出内存而腾出空间来加载另外的数据。 (1)volatile-lru:设置超时时间的数据中,删除最不常用的数据. (2)allkeys-lru:查询所有的key中最近不常使用的数据进行删除,这是应用最广泛的策略。 (3)volatile-random:在已经设定了超时的数据中随机删除。 (4)allkeys-random:查询所有key之后随机删除。 (5)volatile-ttl:查询所有设定超时时间的数据之后,将马上要过期的数据进行删除操作。 (6)Noeviction:如果设置为该属性,则不会进行删除操作,如果内存溢出,则报错返回。 (7)volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键。 (8)allkeys-lfu:从所有键中驱逐使用频率最少的键。查询PID ps -ef | grep -i redis
kill -9 PID
./bin/redis-cli shutdown
key名称不区分大小写
String 是redis的最基本的数据类型,一个key对应一个value。
String类型是二进制安全的,意思是redis的String可以包含任何数据。比如jpg图片或者序列化的对象。
String类型是redis最基本的数据类型,一个键最大能存储512Mb
二进制安全是指:在传输数据时,保证二进制数据的信息安全,也就是不被篡改,破译等,如果被攻击,能够及时检测出来。
二进制安全的特点:
1.编码,解码发生在客户端完成,执行效率高
2.不需要频繁的编解码,不会出现乱码。
1.String类型通常用于保存单个字符串或JSON字符串数据
2.因为String是二进制安全的,所以你完全可以把一个图片文案的内容作为字符串来存储。
3.计数器
redis中的Hash类型类似于java中的一个JavaBean
Redis Hash是一个String类型的filed和value的映射表,hash特别适合用于存储对象,Redis中每个hash可以存储2^32-1键值对(40多亿)。可以看成具有Key和Value的MAP类型,该类型非常适合于存储键值对象的信息,改类型的数据仅占用很少的内存空间(相比于JSON)
redis的HASH实际上是内部存储的value为一个HashMap,并提供了直接存取这个Map成员的接口。
1.新建项目,添加redis的依赖jar包:jedis-3.0.0.jar
2.使用jedis连接redis服务器
Jedis jedis = new Jedis("192.168.159.128",6379);3.redis服务器有密码则还需验证密码
jedis.auth("password");4.测试是否连接成功
//测试redis服务器是否连接成功,连接成功则会在控制台输出PONG System.out.println(jedis.ping());5.redis存取字符串类型测试
/** * 字符串类型的存取 */ @Test public void demo02(){ Jedis jedis = new Jedis("192.168.159.128",6379); jedis.append("strName", "胡卫平"); System.out.println(jedis.get("strName")); jedis.del("name:1"); System.out.println(jedis.get("name:1")); jedis.close(); }6.redis作用的使用
/** * Redis作用:为了减轻数据库的访问压力 * 需求:判断某key是否存在,若存在,就从redis中查询 * 若不存在,就查询数据库,并且将数据库查询出的数据存入Redis中 */ @Test public void demo03(){ Jedis jedis = new Jedis("192.168.159.128",6379); String key = "uname:1"; if(jedis.exists(key)){ System.out.println("Redis中查询得到:"+key+"="+jedis.get(key)); }else{ QueryRunner runner = new QueryRunner(JDBCUtil.dataSource); try { String query = (String)runner.query("select uname from account where id=?", new ScalarHandler(), key.split(":")[1]); System.out.println("Mysql中查询得到:"+key+"="+query); jedis.set(key, query); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } jedis.close(); } }7.Jedis连接池优化
添加连接池的依赖包:commons-pool2-2.6.1.jar,以及相应的log4j的jar包 - /** * Jedis连接池优化 */ @Test public void demo04(){ //1.创建连接池Redis Pool //配置连接池的基本xinxi JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(5); //最大连接数 config.setMaxIdle(1); //最大空闲数 //2.创建连接池 JedisPool pool = new JedisPool(config, "192.168.159.128",6379); //3.获取连接对象 Jedis jedis = pool.getResource(); System.out.println(jedis.get("uname:2")); }8.创建连接池工具类
package com.nike.demo01; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; public class JedisPoolUtil { private static JedisPool pool; static{ //1.创建连接池Redis Pool //配置连接池的基本xinxi JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(5); //最大连接数 config.setMaxIdle(1); //最大空闲数 //...其他配置 //2.创建连接池 pool = new JedisPool(config, "192.168.159.128",6379); } public static Jedis getJedis(){ return pool.getResource(); } //关闭redis连接 public static void close(Jedis jedis){ jedis.close(); } }9.测试工具类
@Test public void demo05(){ Jedis jedis = JedisPoolUtil.getJedis(); System.out.println(jedis.get("uname:7")); JedisPoolUtil.close(jedis); }10.Jedis读取Hash类型数据操作
/** * 使用hash类型模拟对象的存储 * 需求:1.判断redis中是否存在改key,如果存在,直接返回对应值; * 2.如果不存在,查询数据库,将数据库查询到的结果存储到数据库中,并返回给用户。 */ @Test public void demo06(){ Jedis jedis = JedisPoolUtil.getJedis(); String key = "account:7"; if(jedis.exists(key)){ Map<String, String> map = jedis.hgetAll(key); Set<Entry<String, String>> entrySet = map.entrySet(); System.out.println("从Redis数据库查询得到的结果:"); for (Entry<String, String> entry : entrySet) { System.out.println(entry.getKey()+":"+entry.getValue()); } }else{ QueryRunner runner = new QueryRunner(JDBCUtil.getDataSource()); String sql = "select * from account where id = ?"; try { Map<String, Object> query = runner.query(sql, new MapHandler(), key.split(":")[1]); Set<Entry<String, Object>> entrySet = query.entrySet(); System.out.println("从MySql数据库查询得到的结果:"); for (Entry<String, Object> entry : entrySet) { jedis.hset(key, entry.getKey(), entry.getValue().toString()); System.out.println(entry.getKey()+":"+entry.getValue().toString()); } } catch (SQLException e) { e.printStackTrace(); } } JedisPoolUtil.close(jedis); }将Redis中查询的结果通过BeanUtil封装成一个java对象
@Test public void demo07(){ Jedis jedis = JedisPoolUtil.getJedis(); String key = "account:7"; Map<String, String> map = jedis.hgetAll(key); Account account = new Account(); try { BeanUtils.populate(account, map); System.out.println(account); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } JedisPoolUtil.close(jedis); } }Spring data 提供了 RedisTemplate 模板
它封装了 redis 连接池管理的逻辑,业务代码无需关心获取,释放连接逻辑;
Spring redis 同时支持了Jedis,Jredis,rjc客户端操作;
在 RedisTemplate 中提供了几个常用的接口方法的使用,分别是
//cache singleton objects (where possible: private ValueOperations<K,V> valueOps; private ListOperations<K,V> listOps; private SetOperation<K,V> setOps; private ZSetOperation<K,V> zSetOps; @Resourse(name="redisTemplate") ListOperations<String,String> list;1.导入相关依赖的jar包
目前还搞不定2.对应实体Bean进行序列化操作
package com.nike.spring.domain; import java.io.Serializable; public class Account implements Serializable{ 。 private static final long serialVersionUID = 1L; private int id; private String uname; private String upwd; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUname() { return uname; } public void setUname(String uname) { this.uname = uname; } public String getUpwd() { return upwd; } public void setUpwd(String upwd) { this.upwd = upwd; } @Override public String toString() { return "Account [id=" + id + ", uname=" + uname + ", upwd=" + upwd + "]"; } }3.编写相应配置文件spring_redis.xml,利用RedisTemplate操作Redis
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 1.配置连接池配置信息 --> <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <!-- 最大连接数 --> <property name="maxTotal" value="100"/> <!-- 最大空闲数 --> <property name="maxIdle" value="20"/> <!-- 最大建立连接等待时间 --> <property name="maxWaitMillis" value="2000"/> <!-- 指明是否从池中取出连接连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个 --> <property name="testOnBorrow" value="true"/> </bean> <!-- 2.配置连接工厂 --> <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> <property name="usePool" value="true"/> <property name="hostName" value="192.168.159.128"/> <property name="port" value="6379"/> <constructor-arg index="0" ref="jedisPoolConfig"/> </bean> <!-- redisTemplate配置,redisTemplate是对redis操作的扩展,有更多的操作,封装使操作更便捷 --> <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"> <property name="connectionFactory" ref="jedisConnectionFactory"/> </bean> </beans>4.编写测试类
package com.nike.spring.test; import javax.annotation.Resource; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * spring整合redis的测试类 * @author 猪猪 * */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:spring_redis.xml") public class SpringTest01 { @Resource(name="redisTemplate") private RedisTemplate<String, String> redisTemplate; @Test public void demo01(){ ValueOperations<String, String> value = redisTemplate.opsForValue(); if(redisTemplate.hasKey("uname:1")){ System.out.println(value.get("uname:1")); } } }类似于Java语言中的LinkedList类型
Redis列表是简单的字符串列表,按照插入的顺序排序,你可以添加一个元素到列表的头部或尾部,一个列表最多可以包含2^32-1个元素。
项目常用于:
1.对数据量大的集合数据删减
列表数据显示,关注列表,粉丝列表,留言评价等…分页,热点新闻等利用LRANGE还可以很方便的实现分页功能,在博客系统中,每页的博文评论也可以存入一个单独的list中2.任务队列
list通常用来实现一个消息队列,而且可以确保先后顺序,不必像MySQL那样通过order by进行排序介绍:在处理web客户端发送的命令请求时,某些操作的执行时间可能会比我们预期的长一些,通过将执行任务的相关信息放入队列里面,并在之后对队列进行处理,用户可以推迟执行那些需要一段时间才能完成的工作,这种将工作交给任务处理器来执行的做法被称为任务队列(task quene)。Redis的Set使String类型的无序集合,集合的成员使唯一的,这意味着集合中不能出现重复的数据。
Redis中的集合使通过哈希表实现的,所以添加,删除,查找的难度都是o(1),集合中最大的成员数为2^32-1,类似于Java中的Hashtable集合。
redis的集合对象 set 的底层数据结构特别神奇,底层采用了intset和hashtable两种数据结构存储的,intset可以理解为数组,hashtable就是普通哈希表(key为set的值,value为null)。intset内部是一个数组(int8_t coentents[]数组),而且存储数据的时候是有序的,应为在查找数据的时候是通过二分查找来实现的。
常应用于:对两个集合间的数据进行交集,并集,差集运算。
1.可以非常方便的实现如同共同关注,共同喜好,二度好友等功能,
2.利用唯一性,可以统计访问网站的所有独立ip
1.Redis有序集合和集合是一样的也是String类型元素的集合,且不允许重复的成员;
2.不同的是每个元素关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大排序;
3.有序集合的成员是唯一的,但分数却可以重复;
4.集合是通过哈希表实现的,所以添加,删除,查找的复杂难度都是o(1)。集合中的最大成员数为2^32-1.
常应用于:排行榜
1.比如twitter的public timeline 可以以发表时间作为score来存储,这样获取时就是自动按时间排好序的。
2.比如一个存储全班成绩的Sorted set,其集合value可以学生的学号,而score就可以是其考试得分,这样在插入数据时,就已经进行了天然的排序。
3.还可以用Sorted Set来做带权重的任务队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务,让重要的任务优先执行。
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收信息。
Redis客户端可以订阅任意数量的频道
构建实时消息系统,比如普通的即时聊天,群聊等功能
1.在一个博客网站中,有100个粉丝订阅了你,当你发布新文章时,就可以推送消息给粉丝们。
2.微信公众号模式
Redis下,数据库是由一个整数索引标识,而不是一个数据名称。默认情况下,一个客户端连接到的数据库0.
redis配置文件中下面的参数来控制数据库总数
database 16 //(从0开始)命令
select db //数据库切换 move key db //将指定的key移动到指定的数据库 flushdb //清空当前数据库所有的key flushall //清除整个redis的数据库所有的keyRedis可以一次执行多个命令(按顺序串行化执行,执行中不会被其他命令插入,不许加塞,允许在一次单独的步骤中执行一组命令),并且带有一下两个重要保证:
批量操作在发送EXEC命令前被放入队列缓存。
收到EXEC命令后进入事务执行,事务中任意命令执行失败,其余命令依然执行。
在事务执行过程中,其他客户端提交的命令不会插入到事务执行命令序列中。
1.Redis会将一个事务中所有命令序列化,然后按顺序执行;
2.执行中不会被其他命令插入,不允许出现加塞行为
一个事务从开始到执行会经历以下3个阶段:
开始事务
命令入队
执行事务
一个事务的例子,它先以MULTI开始一个事务,然后将多个命令入队到事务中,最后由EXEC命令触发事务
A向B转账50元
1.输入MUTLI命令开始,输入的命令都会依次进入命令队列中,但不会执行;
2.直到输入EXEC后,redis会将之前队列中的命令依次执行。
3.输入命令队列的过程中可以通过DISCARD来放弃队列执行
Redis事务命令
DISCARD //取消事务,放弃执行所有事务 EXEC //执行所有事务块内的命令 MULTI //标记一个事务块的开始 UNWATCH //取消WATCH命令对所有key的监视 WATCH key [key ...] //监视一个或多个key,如果在事务执行之前这个(或这些个)key被其他命令所改动,那么事务将被打断。事务的错误处理
如果执行的某个命令报出了错误(业务错误),则只有报错的命令不会执行,而其他命令都会执行,不会回滚。
队列中的某个命令出现了报告错误(命令语法上的错误),执行时整个的所有队列都会被取消
如果在执行了WATCH命令之后,EXEC或DISCARD命令先被执行的话,那就不需要执行UNWATCH了
应用场景
一组命令必须同时都执行,或都不执行。
我们想要保证一组命令执行过程中不被其他命令插入
商品秒杀
Redis官方给出的警告,当内存不足时,Redis会根据配置的缓存策略淘汰部分keys,以保证写入成功。当无淘汰策略时或没有找到适合淘汰的key时,Redis直接返回out of memory错误
数据存放于内存中
内存:高效,断电(关机)内存数据会丢失
硬盘:读写速度慢于内存,断电数据不会丢失
RDB:是Redis的默认持久化机制,RDB相当于照快照,保存的是一种状态。几十G的数据---->几kb快照
快照是默认的持久化方式,这种方式是将内存中数据以快照的方式写入到二进制文件中,默认文件名为dump.rdb
优点:快照保存数据极快,还原数据极快,适用于灾难备份。
缺点:小内存机器不适合使用,RDB机制符合要求就会照快照。
快照条件:
由于快照方式是在一定时间间隔做一次,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改,如果应用要求不能丢失任何修改的话,可以采用aof持久化方式。
Append-only file:aof 比快照有更好的持久性,是由于使用aof时,redis会将每一个收到的命令都通过write函数追加到文件中(appendonly.file)。当redis重启时会通过重新执行文件中保存的命令来在内存中重建整个数据库的内容。
有以下三种方式(默认:每秒fsync一次):
appendonly yes //启用aof持久化方式#appendfsync always //收到写命令就立即写入磁盘,最慢,但是保证完全的持久化appendfsync everysec //每秒钟写入磁盘一次,在性能和持久化方面做了很好的折中#appendfsync no //完全依赖 os ,性能最好,持久化没有保证产生的问题:
aof的方式也同时带来了一个问题。持久化文件会变得越来越大.
对强一致性要求比较高的,应采用实时同步方案,即查询缓存查询不到再从DB查询,保存到缓存;更新缓存时,先更新数据库,再将缓存的设置过期(建议不要更新缓存内容,直接设置缓存过期)。
@Cacheable:查询时使用,注意Long类型需转换成String类型,否则会抛异常;
@CachePut:更新时使用,使用此注解,一定会从DB上查询数据
@CacheEvict:删除时使用
@Caching:组合使用法
缓存穿透是指查询一个一定不存在的数据,由于缓存不命中时需要查询数据库,查不到数据则不写入缓存,这将导致这个不存在的数据每次查询都要到数据库去查询,造成缓存穿透。
解决办法:持久层查询不到就缓存空结果,查询时先判断缓存中是否exists(key),如果有直接返回空,没有则查询后返回,注意insert时需清空查询的key,否则即便DB中有值也查询不到(当然也可以设置空缓存的过期时间)。
对于并发程度较高的,可采用异步队列的方式同步,可采用kafka等消息中间件处理消息生产和消费
canal实现方式是模拟mysql slave和master的同步机制,监控DB bitlog的日志更新来触发缓存的更新,此种方法可以释放程序员的双手,减少工作量,但在使用时有些局限性
面对mysql的API进行编程,利用触发器惊醒缓存同步,但UDF主要是C/C++语言实现,学习成本高。
雪崩:缓存大量失效的时候,引发大量查询数据库。
解决办法:1. 用锁/分布式或队列串行访问;
2.缓存失效时间均匀分布。
一般来说,要将redis运用于工程项目中,只使用一台Redis是万万不能的,原因如下:
从结构上,单个Redis服务器会触发单点故障,并且一台服务器需要处理所有请求负载,压力较大;从容量上,单个Redis服务器内部容量有限,就算一台Redis服务器内容量为256G,也不应该将所有内容用作Redis存储内存,一般来说,单台Redis最大使用内存不应该超过20G。应用场景:电子商务网站上的商品,一般都是一次上传,无数次浏览的,说专业点也就是"多读少写"。
一个Redis服务可以有多个该服务的复制品,这个Redis服务称为Master,其他复制称为Slaves,Master主要负责写请求,Slaves负责读请求。
主库只负责写请求,每次有数据更新都将更新的数据同步到它所有的从库,而从库只负责读数据,这样一来,就有了两个好处:
读写分离,不仅可以提高服务器的负载能力,并且可以根据读请求的规模自由增加或减少从库的数量。数据被复制成了好几份,就算有一台机器出现了故障,也可以使用其他机器的数据快速恢复。在Redis中,要实现主从复制架构十分简单,只需要在数据库配置文件中加上如下命令即可:
--port 6378 //从服务器的端口 --slaveof 127.0.0.1 6379 //指定 主服务器 ./bin/redis-server ./redis.conf -port 6378 -slaveof 127.0.0.1 6379 //加上slaveof参数启动另一个Redis实例作为从库 并且监听6378端口登陆到从服务器
./bin/redis-cli -p 6380 slaveof on one //变回主 slaveof IP port //变回从(1)Twitter开发的twemproxy
(2)豌豆荚开发的codis
(3)redis官方开发的redis-cluster
Redis集群搭建至少需要3(Master)+ 3(slave)才能建立集群,采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。
1.所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。
2.节点的fail是通过集群中超过集群中超半数的节点检测失效时才生效
3.客户端于redis节点直连,不需要中间proxy,客户端不需要连接集群所有节点,连接集群中任何一个节点即可。
4.redis-cluster中把所有的物理节点映射到[0-16383]slot上(不一定是平均分配),cluster负责维护
5.redis集群预分好16384个哈希槽,当需要在Redis 集群中放置一个key-value时,redis预先对key使用crc16算法算出一个结果,然后把结果对16384求余数,这样每个key都会对应一个编号在0-16383之间的哈希槽,redis会根据节点数量大致均等的将哈希槽映射到不同的节点。
容错性:是指软件检测应用程序所运行的软件或硬件中发生错误并从错误中恢复的能力,通常可以从系统的可靠性,可用性,可测性等几个方面来衡量
redis-cluster投票:容错
1.投票过程是集群中所有master参与,如果半数以上master节点于master节点通信超时(cluster-note-timeout),认为当前节点挂掉
2.什么时候整个节点不可用(cluster_state:fail)
如果集群任意master挂掉,且当前master没有slave,集群进入fail状态,也可以理解为集群的slot映射[0-16383]不完整是进入fail状态,如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态
(官方推荐)三个主节点分别是A,B,C三个节点,他们可以是一台机器上的三个端口,也可以是三台不同的服务器,那么,采用哈希槽的方式来分配16384个slot的话,它们三个节点分别承担的slot区间是
节点A覆盖:0-5460;
节点B覆盖:5461-10922
节点C覆盖:01923-16383
新增一个节点:redis cluster的这种做法是从各个节点的前面各拿取一部分slot到D上
节点D覆盖:0-1364,5461-6826,10923-12287
最少需要6台机器,才能完成Redis Cluster集群
1.创建Redis节点安装目录
mkdir /usr/local/redis_cluster //指定目录下,创建redis_cluster2.在redis_cluster目录下,创建7001-7006个文件夹
mkdir 7001 7002 7003 7004 7005 70063.并将redis.conf分别拷贝到7001-7006文件夹下
cp redis/redis.conf ./70014.分别修改如下配置文件,修改内容如下:
同时protected-mode是为了禁止公网访问redis cache,加强了redis安全,它启用的条件有两个:
1.没有bind ip
2.没有设置访问密码
5.启动各个redis节点
将redis/src文件拷贝到各个redis 7001-7006目录下
启动各个节点
cd redis_cluster/ ./7001/src/redis-server ./7001/redis.conf //依次启动检测redis节点启动状态
ps -ef | grep redis6.创建集群
Redis官方提供了redis-trib.rb这个工具,就在解压目录的src目录中,(为了方便你操作)将其复制到/usr/local/bin目录下。可直接访问此命令(需要安装ruby)
redis-trib.rb create --replicas 1 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006安装ruby
yum -y install ruby ruby-devel rubygems rpm-build gem install redis安装rvm,升级ruby
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB curl -sSL https://get.rvm.io | bash -s stable source /etc/profile.d/rvm.sh rvm -v rvm list known rvm install 2.5创建集群
gem install redis redis-trib.rb create --replicas 1 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006访问集群
redis-cli -h 127.0.0.1 -c -p 7001查看节点信息
查看所有节点信息