使用阿里云学习Redis

redis

1.引入

1.技术的分类

  1. 解决功能性问题:java,jsp,tomcat,html,linux,jdbc
  2. 解决拓展性问题:spring,springmvc,mybatis
  3. 解决性能的问题:nosql,java线程,nginx,hadoop,mq,elasticsearch

2.nosql的引入原因使用阿里云学习Redis

2.nosql数据库

1.介绍

nosql=not only sql。泛指非关系型数据库

以key-value进行存储

  • 不遵循sql标准
  • 不支持acid,acid:原子性,一致性,隔离性,持久性
  • 远超sql性能

2.使用场景

  • 高并发读写
  • 海量数据读写
  • 对数据高可扩展性的

3.不适合场景

  • 需要事务处理
  • 基于sql的结构化查询存储,处理复杂关系,需要即席查询

4.nosql的例子

  1. memcache
  2. redis
  3. mongoDB
  4. hbase
  5. cassandra

5.大数据时代的存储方式

  1. 行式存储

    记录查询好

  2. 列式存储

    查询平均值等好

3.redis概述和安装

1.概述

  1. 开源的key-value存储系统
  2. 支持value类型:string,list,set,zset(有序集合)和hash
  3. 与memcahced数据缓存在内存中
  4. 区别:redis会周期性把更新数据写入到磁盘
  5. 主从同步
  6. ........

2.安装和启动

6.2.6 for linux

注:redis不考虑windows下的支持

1.安装linux

去阿里云申请一个试用版本,安装ftp和shell工具,我使用的是xftp,自行选择。

2.安装redis

​ 1.使用ftp工具,上传安装压缩包

​ 2.使用shell工具安装gcc环境

yum install gcc

​ 3.解压

tar -zxvf redis-(你的版本号).tar.gz

​ 4.进入redis目录

cd redis-xxxx

​ 5.在目录下进行make命令

6. make install
7. 查看安装
cd /user/local/bin

3.启动

  1. 前台启动

    redis-server

    启动后shell无法进行其他操作,不推荐

    停止使用ctrl+c

  2. 后台启动

    解压目录下的redis.conf文件复制到新目录下etc

    cp redis.conf /etc/redis.conf
  3. 使用vi编辑器,将复制后的文件daemonize no修改为

    daemonize yes

    wq保存

  4. 到redis的安装目录下

    cd /usr/local/bin

    使用修改后的配置文件启动

    redis-server /etc/redis.conf
  5. 查看启动进程和端口号

    ps -ef |grep redis
  6. 客户端连接redis

    redis-cli

    如果改过端口号

    redis-cli -p 6666

    ping命令返回PONG

3.关闭reids

  1. 进程关闭

    redis-cli shutdown
  2. 连接后关闭

    127.0.0.1:端口号>shutdown
  3. ps -ef | grep redis
    redis-cli -p 进程号 shutdown

4.redis相关知识

端口号6379--9宫格的Merz 女演员Alessia Merz

默认16个数据库,类似数组0开始,默认使用0号库

redis-cli
127.0.0.1:6379>select x(这里x是0-15)库

memcached和redis对比

  1. memcached支持类型单一,redis支持多,如list,map
  2. memcached不支持持久化,redis支持持久化也支持内存
  3. 串行操作vs(memcached)多线程+锁vs(redis)单线程+多路Io复用

5.redis五大数据类型

1.key(redis键)

  • keys *
     查看当前库所有key(匹配keys*1)
    
  • set k1 value1
     插入key-value
    
  • exists k1

    (integer) 1表示存在k1

  • type k1

    string表示返回的value的类型

  • del k1

    (integer) 1表示删除k1

  • unlink k1

    根据value选择非阻塞删除

    仅将keycongkeyspace元数据中删除,真正的删除在后续异步操作

  • expire k1 时间(秒)(时间也可以不写,表示永不过期)
    ttl key1

    返回-2表示已经过期了

    返回-1表示永远不过期

    其他的表示还剩多少秒过期

  • dbsize

    查看数据库中有多少条数据

  • flushdb

    清空当前库

  • flushall

    通杀所有的库

2.String

  • mset

    mset k1 v1 k2 v2 k3 v3

    同时设置一个或多个key-value

  • mget

    mget k1 k2 k3

    同时获取一个或多个value

  • msetnx

    在mset基础上,拥有原子性,只有所有都不存在才能存储

  • getrang <开始num> <结束num[0-length-1]>

    根据key获取value的局部

    set k1 0123456
    getrange k1 0 3 

    "0123"为结果

  • setrange <开始的num[从0]> <设置的局部value>

    set k1 0123456
    setrange k1 0 99

    get k1得到"9923456"

  • setex

    在set的同时能够设置过期时间

    setex k2 20 v2
  • getset

    用value替换原value

    getset k1 newV1

    get k1得到"newV1"

3.List

redis列表是简单的字符串列表,按照插入顺序排序。可以添加元素到头部(左部)或者尾部(右部)

底层是双向链表,操作性能高,但是查询性能低

  • lpush

    left-push 头插法,每次放入元素放在第一个

    lpush k1 v1 v2 v3
  • rpush

    right-push 尾插法,每次元素放在最后一个

    rpush k2 v1 v2 v3
  • lrange

    从左边开始取出值 0 -1表示取出所有值

    lrange k1 0 -1

    1) "v3"
    2) "v2"
    3) "v1"

  • lpop/rpop
  • 从左边1/右边吐出count个值,吐出后删除,值吐完键也不存在

    lpop k1 2

    1) "v3"
    2) "v2"

  • rpoplpush

    从key1右边吐出一个值插入到key2的左边

    rpoplpush k1 k2
  • lindex

    取出单个值,index为从左到右的位置,第一个为0

    lindex k2 3
  • llen

    获取列表的长度

    llen k2
  • linsert before

    linsert after

    在value的值前/后插入一个新的值(左边检索到的第一个value,有相同的值对第一个值操作)

    linsert k2 after v1 v99
  • lrem

    从左边删除n个value值

    lrem k2 2 v1
  • lset

    将第index的值替换成value(index从0开始)

    lset k2 1 v1

List底层是快速列表(QuickList)

在列表元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist,即压缩列表,使用一段连续的空间。

当数据元素比较多时,用多个ziplist连接起来,构成链表。

类比成为数组×链表

4.Set

set对外提供的功能和list类似时一个列表的功能,特殊之处在于set可以自动排重。

底层时一个value为null的hash表,添加查找,删除都是O(1)

  • sadd

    将一个或多个value加入集合key,已经存在的会被忽略

    sadd k1 v1 v2 v3
  • smembers

    取出集合的所有值

    smembers k1
  • sismember

    判断集合是否含有value,有为1没有为0

    sismember k1 v1
  • scard

    返回集合的元素个数

    scard k1
  • srem

    删除集合中的某个元素

    srem k1 v1
  • spop

    从集合中随机吐出一个值

    spop k1
  • srandmember

    随机从改集合中取出n个值(不删除)

    srandmember k1 3
  • smove value

    把集合中一个值从一个集合移动到另一个集合中

    smove k1 k2 v3
  • sinter

    返回两个集合的交集

    sinter k1 k2
  • sunion

    返回两个集合的并集

    sunion k1 k2
  • sdiff

    返回两个集合的差集元素(key1存在的,key2不存在的)

    sdiff k1 k2

底层数据结构就是一个字典,利用了一个哈希表。类似java底层的HashMap

5.Hash

  1. 第一种

    user:{id=1,name=zhangsan,age=20}

  2. 第二种

    user:id 1

    user:name zhangsan

    user:age 20

  3. 第三种Hash

    使用阿里云学习Redis

  • hset

    存一个值

    hset user:101 id 1
    hset user:101 name zhangsan
  • hget

    取一个值

    hget user:101 id
  • hmset ......

    存多个值

    hmset user101 id 2 name san age 20
  • hexists

    查询key中的一个field是否存在

    hexists user101 id

    1为存在1,0为不存在

  • hkeys

    查询一个key的所有(不显示值)

     hkeys user101
  • hvals

    查询一个key的所有(不显示域)

    hvals user101
  • hincrby

    key中field的值加上increment

    hincrby user101 age 20

    age原本20加上20为40

  • hsetnx

    设置key的field的值为value,如果已经存在则不能设置

    hsetnx user101 gender 1

Hash类型对应有两种,ziplist压缩列表和hashtable哈希表。field-value长度短数量少使用ziplist,否则使用hashtable

Zset

redis有序集合zset和set很像,是一个没有重复元素的字符串集合。

不同之处是每个有序集合的每个对象都关联一个评分,评分被用来按照从最低分倒最高分方式排序集合中的成员。集合的成员是唯一的,但是评分可以是重复的。

  • zadd

    给每个值绑定一个score存储

    zadd topn 200 java 300 c++ 400 mysql 500 php
  • zrange [withscores]

    按照score排序显示从start倒stop之间的值

    加上withscores会在显示值的时候同时在后面显示score

    zrange topn 0 -1
    zrange topn 0 -1 withscores
  • zrangebyscore [withscores]

    按照score(从小到大)排序,显示min到max之间的值

    zrangebyscore topn 300 500
    zrangebyscore topn 300 500 withscores
  • zrevrangebyscore [withscores]

    按照score(从大到小)排序,显示max到min之间的值

    zrevrangebyscore topn 500 400
    zrevrangebyscore topn 500 400 withscores
  • zincrby

    对key的value绑定的score增加increment

    zincrby topn 50 java
  • zrem

    删除指定值的元素

    zrem topn php
  • zcount

    统计从min到max之间的值的个数

    zcount topn 200 300
  • zrank

    返回value值的排名,注意从0开始,返回2为第3位

    zrank topn mysql

zset结构比较特别,类似java的Map<String,Double>。

zset使用两个数据结构

  1. hash,hash关联value和score,保证value的唯一性,通过value得到score
  2. 跳跃表,跳跃表目的在于给value排序,更具socre范围找到value

跳跃表:

如下图查找51

使用阿里云学习Redis

6.redis配置文件

1.单位

  1. 开头定义了一些单位,只支持bytes,不支持bit,大小写无关

2.Include

  1. include,类似c语言的导入,表示可以包含其他文件的配置

3.网络相关

  1. bind=127.0.0.1表示只能通过本地来访问

    加个#注释掉就支持远程连接了

  2. protected-mode yes表示开启保护模式,只能本机访问,原创不能访问,修改为protected-mode no就可以远程访问了
  3. port 6379 不需要改变
  4. tcp-backlog 511

    backlog是一个连接队列,backlog队列总和=未完成三次握手队列+已经完成三次握手队列

    在高并发的情况下需要修改

  5. timeout 0

    timeout表示一段时间后不操作redis,就超时了。0表示永不超时,可以设置时间(以秒为单位)

  6. tcp-keepalive 300

    tcp检测的间隔时间

4.通用(GENERAL)

  1. daemonize no

    redis后台启动,修改为yes

  2. pidfile

    存放pidfile的位置,redis每次操作有一个进程号,保存到pidfile中

  3. loglevel XXXX

    1. debug
    2. verbose 类似java的info
    3. notice 生产环境
    4. warning 警告
  4. logfile XXXX

    设置日志的存储路径

  5. databases 16

    设置redis默认dababase的个数

5.安全(SECURITY)

默认没有密码,可以设置密码

  1. 文件修改
  2. config get requirepass

    config set requriepass "123456"

    auth 1234566

6.限制LIMITS

  1. maxclients

    最大客户端的数量,默认没有设置

  2. maxmemory

    建议必须设置,内存占满会宕机

    设置可同内存量,一旦达到上限,redis会移除部分数据,根据maxmemory-policy来指定

  3. maxmemory-policy
  4. maxmemory-samples

    设置样本数量

7.redis发布和订阅

redis发布订阅(pub/sub)是一种很消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息

redis客户端可以订阅任意数量的频道

使用阿里云学习Redis

  1. 打开一个客户端订阅channel1

    subscribe channel1
  2. 打开另一个客户端,给channel1发布消息hello

    publish channel1 hello

    得到

    1) "message"
    2) "channel1"
    3) "hello"

8.redis6新数据类型

1.bitmaps

  1. 本身不是数据类型,就是字符串,但是它可以对字符串的位进行操作
  2. bitmaps可以想象成一个以位为单位的数组,数组每个单元只能存储0和1,下表在bitmaps叫偏移量

使用阿里云学习Redis

可以充当boolean数组

  1. setbit 1/0

    把offset位设置为1

    setbit k1 1 1
  2. getbit

    取出offset位的值

    getbit k1 1
  3. bitcount [start end]

    start和end表示一个字节(8位),例如 0 3表示,从第一个8位后取出8*3位来

    其中end为 -1表示最后一个字节,-2表示倒数第二个字节

    bitcount k1 1 2
  4. bitop and ...

    bitop and k1 k2

    对一个或多个 key 求逻辑并,并将结果保存到 destkey 。

    and可以替换为orxor异或,not逻辑非

bitmaps对于大数据量用户可以节省空间,但是对于大量僵尸用户不合适,因为很多都是0

2.HyperLogLog

去重统计问题的解决

  1. mysql的distinct count 计算不重复的个数
  2. 使用redis提供的hash,set,bitmaps数据结构处理
什么是基数?

{1,3,1,3,4} 它的基数集合为{1,3,4} 基数为3

基数就是不重复的数的数量

HyperLogLog用12KB可以计算2^64个元素的基数

  1. pfadd

    pfadd k1 "c++"

    重复添加返回0表示去重了,加入不成功

  2. pfcount

    计算近似基数

    pfcount k1
  3. pfmerge ...

    把key中的数据整合到destkey中

    pfmerge k5 k1 k2

3.Geospatial

经纬度

  1. geoadd [经度,维度] [名称] [经度,维度] [名称]...

    geoadd china 100 20 hongze

    北极和南极无法添加,一般会下载城市数据,直接通过java导入

    坐标超出范围会返回错误

  2. geopos <名称>

    geopos china hongze

    返回保存到key的名称对应的经纬度

  3. geodist [m/km/kt/mi]

    geodist china hongze huaian
  4. georadius <longitude经度> <latitude维度> radius [m/km/kt/mi]

    georadius china 80 40 1000 km

9.Jedis操作redis

jedis(java的一个工具)

准备工作:

  1. 修改redis的默认端口,个人修改为6666
  2. bind注释掉
  3. protected-mode no 将yes修改为no

注意事项:

  • 如果使用云服务器需要添加安全组规则,请自行百度
  • 将服务器的防火墙中,添加6666端口放行

新建maven工程,加入dependency

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.2.0</version>
</dependency>

新建类

package com.yikolemon;

import org.junit.Test;
import redis.clients.jedis.Jedis;

import java.util.List;
import java.util.Set;

public class JedisDemo1 {
    public static void main(String[] args) {
        //创建jedis对象
        Jedis jedis=new Jedis("47.113.200.76",6666);
        //测试
        String value=jedis.ping();
        System.out.println(value);
    }
    @Test
    public void demo1(){
        //创建jedis对象
        Jedis jedis=new Jedis("47.113.200.76",6666);
        Set<String> keys = jedis.keys("*");
        for (String key:
             keys) {
            System.out.println(key);
        }
    }

    @Test
    public void demo2(){
        //创建jedis对象
        Jedis jedis=new Jedis("47.113.200.76",6666);
        //添加一个数据
        /*jedis.set("name","san");
        String name = jedis.get("name");
        System.out.println(name);*/
        /*mset添加多个*/
        jedis.mset("k1","v1","k2","v2","k3","v3");
        List<String> mget = jedis.mget("k1", "k2", "k3");
        for (String value :
                mget) {
            System.out.println(value);
        }
    }

    @Test
    public void demo3(){
        //创建jedis对象
        Jedis jedis=new Jedis("47.113.200.76",6666);
        jedis.lpush("key1","san","yiko","lemon");
        List<String> key = jedis.lrange("key1", 0, -1);
        for (String value :
                key) {
            System.out.println(value);
        }
    }
}

测试方法太多,可以自行按照上文测试

10.Springboot整合redis

1.pom导入

<!-- redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- spring2.X集成redis所需common-pool2-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.6.0</version>
</dependency>

2.编写application.yml

spring:
  redis:
    #ip
    host: 47.113.200.76
    #端口号
    port: 6666
    #redis数据库索引,默认为0
    database: 0
    #连接超时时间
    timeout: 1800000
    lettuce:
      pool:
        #连接池最大连接数
        max-active: 20
        #最大阻塞等待时间
        max-wait: -1
        #连接池最大空闲连接
        max-idle: 5
        #连接池最小空闲连接
        min-idle: 0

3.导入jackson编写config

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.1</version>
</dependency>

新建包config

package com.redis.redis_springboot.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setConnectionFactory(factory);
//key序列化方式
        template.setKeySerializer(redisSerializer);
//value序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(600))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                .disableCachingNullValues();
        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }
}

11.redis事务

1.redis事务的使用

Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

Redis事务的主要作用就是串联多个命令防止别的命令插队。

  • Multi命令开启事务
  • Exec执行之前的命令
  • discard放弃组队

使用阿里云学习Redis

  1. 例1:
  2. 全部成功

    127.0.0.1:6666> multi
    OK
    127.0.0.1:6666(TX)> set k1 v1
    QUEUED
    127.0.0.1:6666(TX)> set k2 v2
    QUEUED
    127.0.0.1:6666(TX)> exec
    1) OK
    2) OK
  3. 例2

    组队时错误

    127.0.0.1:6666(TX)> set k1 v1
    QUEUED
    127.0.0.1:6666(TX)> set k2
    (error) ERR wrong number of arguments for 'set' command
    127.0.0.1:6666(TX)> set k3 v3
    QUEUED
    127.0.0.1:6666(TX)> exec
    (error) EXECABORT Transaction discarded because of previous errors.
  4. 例3

    组队成功,但是在执行时有错误,会忽略错误继续执行

    127.0.0.1:6666(TX)> set k1 v1
    QUEUED
    127.0.0.1:6666(TX)> incr k1
    QUEUED
    127.0.0.1:6666(TX)> set k2 v2
    QUEUED
    127.0.0.1:6666(TX)> exec
    1) OK
    2) (error) ERR value is not an integer or out of range
    3) OK

2.悲观锁和乐观锁

  1. 悲观锁

    每次取数据再修改,在取数据时上锁,操作完成后再解锁

    传统的关系型数据库里边就用到了很多这种锁机制,比如行锁表锁等,读锁写锁等,都是在做操作之前先上锁。
  2. 乐观锁

    在获取数据时加上一个版本号,一个操作同步更新版本号。所有操作的取数阶段都不影响,在操作时检查自己取出的版本号和数据库中存在的版本号是否一致,一致就执行,不一致就不能执行。

    使用例子:抢票,能查到剩余票量但是不能保证抢到

    乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。

问题:

乐观锁时如何保证,操作前和查询和操作的原子性?

3.redis执行锁

watch key1 [key2]

在执行multi之前,先执行watch key1 [key2],可以监视一个(或多个) key ,如果在事务**执行之前这个(或这些) key被其他命令所改动,那么事务将被打断停止。

127.0.0.1:6666> watch k1 k2
OK
127.0.0.1:6666> multi
OK

unwatch可以取消对所有key的监视

如果在执行 WATCH 命令之后,EXEC 命令或DISCARD 命令先被执行了的话,那么就不需要再执行UNWATCH 了。

4.redis事务三特性

  • 单独的隔离操作

    事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

  • 没有隔离级别的概念

    队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行

    隔离级别:mysql事务内查询能看到之前的操作,事务之外就看不到
  • 不保证原子性

    事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚

5.redis事务案例

商品秒杀

额定商品库存 ,一人一次,到时秒杀

并发模拟工具:

1.ab模拟测试

centos6自带,centos7需要

手动安装

yum install httpd-tools
  • -n 请求的数量
  • -c 请求中的并发数量
  • -p 提交参数(post)提交
  • -T content-type格式类型
ab -n 1000 -c100 -p ~/postfile -T application/x-www-form-urlencoded http://192.168.2.115:8081/Seckill/doseckill
#~/postfile是file
#application/x-www-form-urlencoded... 不要自己写其中的localhost要改成ip地址

file:在linux中新建(在当前路径中创建)

vi postfile

在postfile

prodid=0101&

保存退出

测试后发现由于并发问题出现了负数,结果和预期不匹配

2.问题

  1. 连接超时问题

    redis不能同时处理多个问题,如果问题一直等待,就会出现连接超时问题

  2. 并发问题

    没有进行锁,出现了并发问题

3.解决问题

  1. 解决连接超时问题

    使用jedis连接池(工具类自己写),通过工具类设置连接池的超时时长,从而解决问题

  2. 并发问题

    使用jedis

    Transaction multi=jedis.multimulti.decr(kcKey);multi.sadd(userKey,uid);List<Object> results=multi.exec();//仅供参考

    3.库存遗留问题

    什么是库存遗留问题?

    100个人抢50个商品,由于并发最后剩了20个没人被抢走,这就是库存遗留问题

    使用LUA脚本语言

    Lua 是一个小巧的脚本语言,Lua脚本可以很容易的被C/C++ 代码调用,也可以反过来调用C/C++的函数,Lua并没有提供强大的库,一个完整的Lua解释器不过200k,所以Lua不适合作为开发独立应用程序的语言,而是作为嵌入式脚本语言。

    很多应用程序、游戏使用LUA作为自己的嵌入式脚本语言,以此来实现可配置性、可扩展性。

    LUA脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作。

    但是注意redis的lua脚本功能,只有在Redis 2.6以上的版本才可以使用。

    redis 2.6版本以后,通过lua脚本解决争抢问题,实际上是redis 利用其单线程的特性,用任务队列的方式解决多任务并发问题。

12.redis持久化

Redis 提供了2个不同形式的持久化方式。

  • RDB(Redis DataBase)
  • AOF(Append Of File)

1.RDB

1.介绍

在指定的时间间隔内将内存中的数据集快照写入磁盘, 也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里

例:每隔10秒,把这个时刻的数据,写入到硬盘中

Reids创建一个子进程,将数据写到一个临时文件中,等到持久化结束了,再用临时文件替换上次持久化的文件。(写时复制技术)

  • 优点:RDB更加高效
  • 缺点:最后一次持久化后的数据可能丢失,最后的循环时间没到,服务器挂了,这段时间的更新的数据就没了

2.使用

  1. 配置redis.conf

注意,保存dump.rdb是shell运行redis-server时,所处的路径

在/启动就保存到/,在bin中启动就保存到bin

~不是路径,注意

2.AOF

1.介绍

日志的形式来记录每个写操作(增量保存),将Redis执行过的所有写指令记录下来(读操作不记录), 只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作

2.使用

AOF默认不开启,要在redis.conf中修改,默认为:appendonly.aof

AOF和RDB保存路径相同

AOF和RDB同时开启,系统默认取AOF的数据(数据不会丢失)

  1. 恢复

    当连接中止再重新启动时,AOF的数据会恢复到redis中

  2. 异常修复aof

    如果遇到aof文件损坏,通过/usr/local/bin/redis-check-aof--fixappendonly.aof进行恢复

    如果aof文件读取错误了,可以修复aof文件

    redis-check-aof --fix appendonly.aof 

3.配置

  1. AOF同步频率设置

    appendfsync always

    始终同步,每次redis写入都记入日志,性能差但是完整性号

    appendfsync everysec

    一秒钟记录一次,宕机可能会丢失数据

    appendfsync no

    redis不主动进行同步,把同步时机交给操作系统

  2. Rewrite压缩

    AOF采用文件追加方式,文件会越来越大为避免出现此种情况,新增了重写机制, 当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩, 只保留可以恢复数据的最小指令集.可以使用命令bgrewriteaof

    AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),redis4.0版本后的重写,是指上就是把rdb 的快照,以二级制的形式附在新的aof头部,作为已有的历史数据,替换掉原来的流水账操作。

    如果 no-appendfsync-on-rewrite=yes ,不写入aof文件只写入缓存,用户请求不会阻塞,但是在这段时间如果宕机会丢失这段时间的缓存数据。(降低数据安全性,提高性能)

    如果Redis的AOF当前大小>= base_size +base_size*100% (默认)且当前大小>=64mb(默认)的情况下,Redis会对AOF进行重写。

4.总结

优点:

  • n 备份机制更稳健,丢失数据概率更低。
  • n 可读的日志文本,通过操作AOF稳健,可以处理误操作。

缺点:

  • 比起RDB占用更多的磁盘空间。
  • 恢复备份速度要慢。
  • 每次读写都同步的话,有一定的性能压力。
  • 存在个别Bug,造成恢复不能。

13.主从复制

1.定义

主机数据更新后根据配置和策略, 自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主

2.意义

  • 读写分离
  • 容灾快速恢复

    一台读取服务器挂了,可以切换到其他服务器读

    主服务器只能有一台

3.操作演示

  1. 创建/myredis的文件夹
  2. 复制redis.conf到文件夹中
  3. 配置一主两从,三个redis.conf的配置文件

    • redis6666.conf
    • reids6667.conf
    • redis6668.conf
  4. 写配置文件

    include /myredis/redis.conf
    pidfile /var/run/redis_6666.pid
    port 6666
    dbfilename dump6666.rdb
  5. 启动

    redis-server /myredis/redis6666.conf
    redis-server /myredis/redis6667.conf
    redis-server /myredis/redis6668.conf
  6. 查看主机

    info replication
  7. 设置主从机

    在从机上执行slaveof 主机ip 端口号

    slaveof 127.0.0.1 6666
  8. 测试

    从机不能做写操作

    127.0.0.1:6668> set k2 v2
    (error) READONLY You can't write against a read only replica.

4.常用操作

  1. 一主二仆

    1. 从服务器挂了

      • 从服务器挂了重启,默认为主服务器,不能重新变成从服务器
      • 从服务器重启,重设从服务器,会完全复制主服务器的数据。
    2. 主服务器挂了

      • 主服务器挂掉,从服务器还是从服务器,但是能看到主服务器挂了
      • 主服务器重启后,依然是主服务器。
  2. 树形主仆

    6666主——6667仆——6668仆

    6667是6666的从服务器,是6668的主服务器

  3. 从服务器上位

    如果主服务器挂了,没有服务器上主服务器,其中一台服务器就可以上位成为主服务器

    slaveof no one

    成为主服务器时

5.复制原理

  1. 从服务器连上主服务器,向主服务器发送数据同步消息
  2. 主服务器收到同步消息,把主服务器数据进行持久化rdb,把rdb文件发送给从服务器,从服务器拿到rdb进行读取(全量复制)
  3. 每次主服务器写操作,和从服务器进行数据同步(增量复制)

14.哨兵模式

1.作用

主服务器挂掉,使从服务器快速上位

2.使用

  1. 自定义的/myredis目录下新建sentinel.conf文件,名字绝不能错
sentinel monitor mymaster 127.0.0.1 6666 1
其中1的意思是,至少有一个从机同意上位为主服务器
  1. 启动

    redis-sentinel sentinel.conf

    哨兵启动完成,默认端口26379,可以看到6666有两个从机6667和6668

  2. 挂掉主服务器6666

    其中两台服务器选一台成为主服务器(如6667),而原来的另一台的从服务器(6668),需要改成这个新的主服务器(6667)的从服务器。

  3. 重启6666

    重启6666后,原来挂的服务器不能 重新变成原来的主服务器

  • 问题:复制延迟

3.选取规则zhongxing

  1. 优先级在redis.conf中默认:

    replica-priority 100

    值越小优先级越高

  2. 偏移量最大,优先

    偏移量:从服务器和主服务器数据差最少的优先
  3. runid

    redis实例启动会随机生成40位的uid

15.集群

容量不够,redis如何进行扩容?

并发写操作, redis如何分摊?

之前通过代理主机来解决,但是redis3.0中提供了解决方案。就是无中心化集群配置

无中心化服务器,任何一台服务器都能成为入口,它们互相连通,不是自己的操作不进行,传递给其他服务器。

  • Redis 集群实现了对Redis的水平扩容,即启动N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N。
  • Redis 集群通过分区来提供一定程度的可用性: 即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求。

1.操作演示

  1. 将/myredis下rdb,aof文件删除
  2. 创建6个配置文件,3主3从

    6666,6667,6668,7777,7778,7779

    在原有配置文件上添加

    cluster-enabled yes    
    #打开集群模式
    cluster-config-file nodes-6666.conf 
    #设定节点配置文件名
    cluster-node-timeout 15000   
    #设定节点失联时间,超过该时间(毫秒),集群自动进行主从切换。

    总配置文件

    include /myredis/redis.conf
    pidfile /var/run/redis_6666.pid
    port 6666
    dbfilename dump6666.rdb
    cluster-enabled yes    
    cluster-config-file nodes-6666.conf 
    cluster-node-timeout 15000   
  3. 启动6个redis服务

    redis-sever /myredis/redis6666.conf
    在一个目录下启动,并且保证目录下有生成的nodes-xxxx.conf文件,表示启动成功
  4. 集群

    进入redis的安装目录(注意redis版本)

    cd  /opt/redis-6.2.6/src

    注意必须在安装目录src下操作

    redis-cli --cluster create --cluster-replicas 1 47.113.200.76:6666 47.113.200.76:6667 47.113.200.76:6668 47.113.200.76:7777 47.113.200.76:7778 47.113.200.76:7779

    不能写127.0.0.1,要写真实ip。再yes就行了。

    例:47.113.200.76

    问题:Waiting for the cluster to join

    机器没有开放redis集群总线端口,开启即可。注意安全组和防火墙都开

    总线端口是端口+10000,如6666就是16666

未完待续

上一篇:asp.net Repeater拖拽实现排序并同步排序字段到数据库中


下一篇:NULL与排序