myJavaEE

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

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的返回值, 以此决定访问顺序

posted on 2017-04-13 22:28  myJavaEE  阅读(279)  评论(0编辑  收藏  举报