NoSql是为了解决高并发、大规模存储、高可用、高可扩展等一系列问题而产生的数据库解决方案。
NoSql,非关系型的数据库,全称Not only sql。它不能替代关系型数据库,只能作为关系型数据库的一个良好补充。
目前的NoSql有:
key-value对存储(Redis MemcacheDB, 快, 适合做缓存)
列存储(Hbase, 适合大规模存储)
文档存储(MongoDB, 像SQL, 灵活)
图形数据库(Neo4j 面向关系)
Redis是使用C语言开发的一个高性能键值数据库。
键值类型有:
String 字符类型
hashmap 散列类型
list 列表类型
set 集合类型, 唯一
sortedset/zset 有序集合类型
不同的键值类型对应不同的操作. 具体见redis文档
Redis持久化
1)Rdb方式
Redis默认方式,redis通过快照将数据持久化到磁盘中。
一旦redis被非法关闭,就会丢失最后一次持久化后的数据。
2)Aof方式
Redis默认不使用。Aof方式是将每次对数据库的操作记录存储到aof持久化文件中。
开启aof方式的持久化方案: 将redis.conf中的appendonly改为yes。
如果数据不允许丢失,那么要使用aof方式。
同时使用aof和rdb方式时,如果redis重启,则数据从aof文件加载。
主从复制
作用:
1)避免单点故障
2)读写分离, 从库承担读的任务, 从库承担持久化的任务
从库也可以作为其他库的主库, 如下结构能减轻主库同步压力
复制原理:
1)当从库和主库建立MS关系后,会向主数据库发送SYNC命令;
2)主库接收到SYNC命令后, 开始在后台保存快照(RDB持久化,即使禁用rdb也会进行),并将期间接收到的写命令缓存起来;
3)主库将快照文件和 缓存的写命令 发送给从库;
4)从库接收到后,载入快照文件并且执行收到的缓存写命令 ;
5)之后,主库每接到写命令时就会将命令发送从库,从而保证数据一致
注:Redis目前实现了无磁盘复制功能, 不会在磁盘生成快照,而直接通过网络发送给从数据库,避免IO性能问题。
开启无磁盘复制:repl-diskless-sync yes
主从复制架构中的宕机恢复
1. 从Redis宕机
从库重新启动后会自动加入到主从架构中,自动完成数据同步(增量复制)。
2. 主Redis宕机
1)在从库中执行SLAVEOF NO ONE命令,断开主从关系, 并且提升从库为主库继续服务;
2)将主库重新启动后,执行SLAVEOF命令,将其设置为从库,这时数据就能更新回来;
手动处理容易出错, 推荐使用Redis的哨兵(sentinel)功能。
哨兵是一个独立进程, 对Redis的运行情况进行监控, 在主库宕机后会自动将从库转为主库
启动哨兵进程首先需要创建哨兵配置文件:
sentinel.conf
内容:
sentinel monitor redisName 127.0.0.1 6379 1
说明:
redisName 监控主数据的名称,自定义
127.0.0.1:监控的主数据库的IP
6379:监控的主数据库的端口
1:最低通过票数
启动哨兵进程:
redis-sentinel ./sentinel.conf
哨兵无需配置slave,只需要指定master,哨兵会自动发现slave
可以配置多个哨兵, 多个哨兵不仅同时监控主从数据库,哨兵之间也互为监控。
集群
整个Redis集群提供了16384个插槽,客户端只需要连接集群中任何一个节点即可访问整个集群
配置核心是redis-trib.rb的ruby脚本
1)创建并分配16384个插槽:
./redis-trib.rb create --replicas 0 192.168.56.102:6379 192.168.56.102:6380 192.168.56.102:6381
--replicas 0:指定了从数据的数量为0
2)重新分配插槽:
./redis-trib.rb reshard 192.168.56.102:6380
3)删除节点:
./redis-trib.rb del-node 192.168.56.102:6380 4a9b8886ba5261e82597f5590fcdb49ea47c4c6c
当集群中的任何一个节点下线,就会导致插槽区有空档不完整,集群将不可用
所以集群一般会构建成: 集群+主从复制
创建集群, 创建顺序为主库(3个)、从库(3个):
./redis-trib.rb create --replicas 1 192.168.56.102:6379 192.168.56.102:6380 192.168.56.102:6381 192.168.56.102:6479 192.168.56.102:6480 192.168.56.102:6481
于是某台数据库宕机不会影响集群正常服务, 类似哨兵有自我恢复功能
Jedis示例
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:spring/applicationContext*.xml") public class JedisTest { @Test public void jedisByConn() { // 避免使用, 用连接池代替 Jedis jedis = new Jedis("192.168.133.13", 6379); jedis.set("test", "hello redis"); String string = jedis.get("test"); System.out.println(string); jedis.close(); } @Test public void jedisPool() { JedisPool pool = new JedisPool("192.168.133.13", 6379); Jedis jedis = pool.getResource(); String string = jedis.get("test"); System.out.println(string); jedis.close(); pool.close(); } @Test public void testJedisCluster() { Set<HostAndPort> nodes = new HashSet<HostAndPort>(); for (int i = 0; i < 6; ++i) { nodes.add(new HostAndPort("192.168.133.13", 7001 + i)); } JedisCluster cluster = new JedisCluster(nodes); cluster.set("name", "张三"); System.out.println(cluster.get("name")); cluster.close(); } @Resource(name = "jedisClientSingle") private JedisClient jedisClient1; @Test public void jedisSingleSpring() { jedisClient1.set("client1", "1000"); String string = jedisClient1.get("client1"); System.out.println(string); } @Resource(name = "jedisClientCluster") private JedisClient jedisClient2; @Test public void jedisClusterSpring() { jedisClient2.set("client2", "2000"); String string = jedisClient2.get("client2"); System.out.println(string); } }
spring配置
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <!-- 最大连接数 --> <property name="maxTotal" value="30" /> <!-- 最大空闲连接数 --> <property name="maxIdle" value="10" /> <!-- 每次释放连接的最大数目 --> <property name="numTestsPerEvictionRun" value="1024" /> <!-- 释放连接的扫描间隔(毫秒) --> <property name="timeBetweenEvictionRunsMillis" value="30000" /> <!-- 连接最小空闲时间 --> <property name="minEvictableIdleTimeMillis" value="1800000" /> <!-- 连接空闲多久后释放, 当空闲时间 > 该值 且 空闲连接 > 最大空闲连接数 时直接释放 --> <property name="softMinEvictableIdleTimeMillis" value="10000" /> <!-- 获取连接时的最大等待毫秒数,小于零:阻塞不确定的时间,默认-1 --> <property name="maxWaitMillis" value="1500" /> <!-- 在获取连接的时候检查有效性, 默认false --> <property name="testOnBorrow" value="true" /> <!-- 在空闲时检查有效性, 默认false --> <property name="testWhileIdle" value="true" /> <!-- 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true --> <property name="blockWhenExhausted" value="false" /> </bean> <!-- 单机版redis, "池"化, 避免频繁打开关闭连接 --> <bean id = "jedisPool" class = "redis.clients.jedis.JedisPool" destroy-method="close"> <!-- 需关闭池 --> <constructor-arg name = "host" value="192.168.133.13"></constructor-arg> <constructor-arg name = "port" value="6379"></constructor-arg> <constructor-arg name="poolConfig" ref="jedisPoolConfig" /> <!-- 也可不配,直接使用默认值 --> </bean> <!-- 集群版redis --> <bean id = "jedisCluster" class = "redis.clients.jedis.JedisCluster" destroy-method="close"> <constructor-arg name = "nodes"> <set> <bean class = "redis.clients.jedis.HostAndPort"> <constructor-arg name = "host" value="192.168.133.13"></constructor-arg> <constructor-arg name = "port" value="7001"></constructor-arg> </bean> <bean class = "redis.clients.jedis.HostAndPort"> <constructor-arg name = "host" value="192.168.133.13"></constructor-arg> <constructor-arg name = "port" value="7002"></constructor-arg> </bean> <bean class = "redis.clients.jedis.HostAndPort"> <constructor-arg name = "host" value="192.168.133.13"></constructor-arg> <constructor-arg name = "port" value="7003"></constructor-arg> </bean> <bean class = "redis.clients.jedis.HostAndPort"> <constructor-arg name = "host" value="192.168.133.13"></constructor-arg> <constructor-arg name = "port" value="7004"></constructor-arg> </bean> <bean class = "redis.clients.jedis.HostAndPort"> <constructor-arg name = "host" value="192.168.133.13"></constructor-arg> <constructor-arg name = "port" value="7005"></constructor-arg> </bean> <bean class = "redis.clients.jedis.HostAndPort"> <constructor-arg name = "host" value="192.168.133.13"></constructor-arg> <constructor-arg name = "port" value="7006"></constructor-arg> </bean> </set> </constructor-arg> <constructor-arg name="poolConfig" ref="jedisPoolConfig"></constructor-arg> <!-- 也可不配,直接使用默认值 --> </bean>
Redis与并发
并发访问临界资源存在安全问题, 两大解决方法
直接: 使用锁等机制进行原子性及可见性控制
间接: 使用队列等串行化.
Reids使用单线程, 对Redis的请求会被系统排队, 可利用这个特点检查从Redis的返回值, 以此决定访问顺序