Redis

零、 目录

  • 高并发思路
  • 电商网站中缓存数据库的设计
  • 缓存介绍
  • 按照redis
  • redis常用命令
  • redis其他数据结构
  • 数据分布式存储
  • Jedis客户端
  • 哈希一致性
  • 补充

一、 高并发思路

  1. 技术: tomcat集群+ nginx

    1. 理论上引入20台nginx — 100万/s的并发量
    2. 问题: 当100万个用户同时增删该查时 , 就出现了系统瓶颈
    3. 系统瓶颈: 后台程序能支持100万/秒的访问 , 但是数据库扛不住100万/s的访问
  2. redis
    1. 分布式 、 nosql 、 可以持久化+内存 、 内存 、 数据库
    2. 分布式: 数据库被划分
    3. nosql(not only sequence query language): 不仅仅支持关系型 、 结构化的数据库 , 而且支持非关系型 、 非结构化数据
    4. 可持久化+内存: 启动恢复机制(redis启动之后立即恢复之前的数据)
    5. 数据库: 数据存储
    6. 作用: 可以分布式的存储海量的数据 , 放到内存中 , 可以做缓存数据库

二、 电商网站中缓存数据库如何设计

  1. 缓存可以如何添加

    1. 数据库缓存:

      1. 执行的过程包括sql和组织查询结果 , 根据sql可以创建缓存 , 存储已经查过的resultSet , 节省了资源调度重组resultSet
    2. 持久层缓存:
      1. 减少数据库获取的结果转化为对象的过程 , 缓存直接调用保存的对象结果
    3. 业务层缓存:减少调用层次
    4. 控制层缓存: 减少调用层次
  2. 问题: 可以不可以过多的使用缓存?
    1. 缓存是占用内存空间的 ,过多的缓存插入 , 容易造成数据的冗余 , 在内存不够时 , 清空逻辑会交叉导致数据库失效
  3. 结论: 缓存引入的最终目的:
    1. 减少数据库的访问压力
    2. 减少网路传输
    3. 减少封装层次

三、缓存介绍

  1. 主流的缓存结构:

    1. ehcache(很多数据库底层缓存用的就是ehcache)并发量差
    2. memoryCache , 10年前 , 并发量高(100万左右/秒) , 缺点: 不落地(数据不能持久化 , 在宕机后不能立即恢复丢失的数据 , 严重的情况下容易造成缓存击穿 , 这也是被redis取代的主要原因)
    3. redis: 持久化 , 可以在宕机恢复后迅速的解决数据丢失的问题
  2. 问题: 如果在缓存服务器宕机后 , 无法进行数据恢复/没有解决数据丢失的问题 , 会导致“雪崩”(也叫缓存击穿)
    1. 雪崩(缓存击穿): 海量用户访问请求涌入 , 一旦缓存失效(宕机后缓存数据丢失) , 所有访问涌入数据库 , 数据库无法承受海量的数据的查询 , 导致数据库服务器宕机 , 这时重启数据库 , 但是请求没有消失甚至用户在不断的刷新(请求瞬间翻倍) , 就发生了数据库在重启->宕机->重启中循环 , 导致整个系统崩溃。

    1. 解决雪崩问题:

      1. 缓存永不宕机: 启动集群 , 永远让集群的一部分起作用 , 剩余的一部分做备用
      2. 缓存技术必须要支持恢复数据 , 持久化 。

四、 安装redis

  1. 登录linux , 并创建管理目录
  2. 下载安装包并解压

    下载命令wget "http://bj-yzjd.cn-bj.ufileos.com/redis-3.2.11.tar.gz"
    解压tar -xvf redis-3.2.11.tar.gz
    
  3. 进入解压之后的文件 , 执行安装

    make && make install
    
  4. 启动redis

    redis-server
    

    则启动成功

  5. 使用redis

    1. redis启动成功之后该linux主机立即成为一台redis服务器 , 此时使用redis时需要再打开一个该虚拟机的连接之后启动redis客户端
    2. 使用redis需要启动redis客户端

      redis-cli
      
    3. 如果想在同一个连接中启动服务和客户端 , 则启动redis时可以使redis服务器在后台运行

      redis-server &
      
  6. 停止redis服务

    1. 在占用控制台的服务连接中直接Ctrl+c即可停止服务
    2. 在客户端中

      shutdown
      
  7. 检查后台 运行的 redis

    ps -ef|grep redis
    
    1. redis-server 表示redis服务
    2. *表示所有IP都能够访问当前redis服务 , 如果列出一些列的IP地址 , 则除这些ip地址以外的访问都被拒绝。

五、 redis常用命令

  1. redis存储的数据实际上是map形式(key-value|{key , value}|list)的字符串
  2. keys : 获取当前存储空间中所有存在的key
  3. set [key] [value] : 设置key-value , key和value都是字符串
  4. get[key] :通过key获取对应的值
  5. select[整数值0~15] : redis默认存在0~15标号的数据库 分库 , 默认使用第一个库(0号库) , 这个功能是早期版本的冗余功能 , 现在的java代码不支持分库, 所以select 的功能逐渐不被使用
  6. exists [key] : 判断该key是否存在
    1. 与get的区别:

      1. 分析get: 在redis中一个字段允许存储的最大大小为512M
      2. 如果使用get查询 , 如果存在则会返回值 , 此时如果值过大会占用过多的资源 。
      3. 而exists只是判断key是否存在 , 如果存在会返回1 , 如果不存在则返回0.
  7. del [key] : 删除该key对应的键值对
  8. type [key] : 获取key对应值的类型 , 普通的数据类型都是string , 复杂的数据类型有map 和list
  9. help type/help[命令名称] 如:help set : 查看该命令的作用
  10. 实际问题可以在官网中查询对应的命令细节
  11. flushall : 将所有的数据(0~15号库) , flush到持久化文件中
  12. flushdb : flush当前分库的所有数据到持久化文件中 。
  13. incr [key] : 自增 , Integer类型的数据自增(redis中存储是都是String类型 , 在需要自增时, 会先试图转换成Integer在再增) , 如果转换不成功 , 则会报错, incrby [key] index 自增指定的步数
  14. decr [key]: 自减 decr [key] index 自减指定的步数
  15. append [key] [appendValue]: 在value后追加数据
  16. mget [k1] [k2] … : 获取一批key对应的值
  17. mset [k1] [v1] [k2] [v2] … : 设置一批数据 常用的编程语言的API一般不支持这个命令 ,因为使用这中群体操作k-v的命令后不支持数据分片(使用key取hash值取余后 散列存储)和集群计算 ; 这是一个早期的冗余功能
  18. expire [key] 时间数字(单位:秒) :设置当前key对应的value的过期时间
    1. ttl [key] : 查看当前key-value的存活时间

      1. -2 代表过期
      2. -1 代表永久
    2. 可以使用数据中的过期时间来做倒计时 , 或者秒杀 , 但是这个倒计时是秒级别的 。
  19. pexpire [key] 时间(单位毫秒) :做精确时间的秒杀

六、 redis其他数据结构

  1. Hash结构:

    1. 本身是key-value 的形式 , 但是这里的key也是key-value的形式
    2. hset [key] [field] [value] : 赋值
    3. hget [key] [field] : 取值
    4. hmset [key] [field] [value] [field1] [value1] :批量赋值
    5. hmget [key] [field] [field1]: 批量取值
    6. hexists : 查看属性是否存在 , 存在返回1 , 不存在返回0
    7. hdel [key] [field] :删除字段
    8. hkeys [key]:只获取字段名
    9. hvals [key] : 只获取字段值
    10. hlen [key]:获取字段数量
  2. list结构
    1. key-value(双向链表 , 左->上 , 右→下)
    2. lrange [listkey] start end :查看list
    3. lpush [key] value :向对应的list的头部添加信息 , 如果没有改;list则创键后添加
    4. rpush [key] [v1] : 向对应的list的尾部添加字符串元素
    5. linsert : 向对应的list的特定位置之前或之后添加字符串元素
    6. lset:设置list中指定下标的元素值
    7. lrem : 从key对应的list中删除count个value相同的元素
      1. count>0按从头到尾的顺序删除
      2. count<0 时按照从尾到头的顺序删除
      3. count=0 时 删除所有与value相同的元素
    8. ltrim : 保留指定key的值得范围内的数据
    9. lpop : 从list头部删除元素, 并返回删除的元素
    10. rpop : 从list尾部删除元素并返回删除的元素
    11. rpoplpush [list1] [list2]:从第一个尾部删除一个数据并将删除的数据添加到第二个list头部 , 整个操作是原子级的如果第一个list不存在或为空 则执行结果为nil , 如果第二个list不存在则创建
    12. lindex : 返回名称为key的list中index位置的元素
    13. llen : 返回list的长度

七、 数据分布式存储

  1. 要完成数据的分片存储 , 需要至少多个redis实例
  2. 启动多个redis时 , 每一个redis会占用一个端口 , 如果端口冲突 , 则会发生启动失败 , 所以要更改redis的默认配置文件
  3. 修改配置文件
    1. 进入到redis根目录下的redis.conf文件修改
    2. 直接输入:set number 使左侧的行号显示
    3. 第61行 把bind注释掉
    4. 第80行 保护模式关闭
    5. 第84行修改默认端口 , 避免和其他redis冲突 , redis默认是6379
    6. 第105行当客户端空闲时间1小时就自动断开连接 , 0秒表示不启用超时设置
    7. 第128行daemonize设置成yes让redis启动时由守护进程管理(也就是在后台执行)
    8. 第150行 , 不同的redis设置不同的pid文件(和端口同名)
    9. 设置日志级别 , 使用默认就行
    10. 设置flush动作规则 , 默认900秒以内 至少有1条数据改动 则执行flush , 在300秒以内至少有10条数据变动则执行flush , 在60秒以内至少有10000条数据有变动则执行flush操作 。 默认即可
    11. 修改完之后保存并退出
    12. 复制整个redis文件夹 为 r2 , r3
    13. 并且修改r2 r3中配置文件的端口和pid dump文件的名字
    14. 进入到r1目录下 , 执行redis-server redis6379.conf
    15. 进入到r2目录下 , 执行redis-server redis6380.conf
    16. 进入到r3目录下 , 执行redis-server redis6381.conf
    17. 执行完之后检测三个redis实例是否启动成功 ps -ef|grep redis
    18. 此时如果需要开启redis客户端 字需要 执行 redis-cli -p 端口号

八、 Jedis客户端

  1. redis集群部署完成 , 就是执行数据的存储
  2. 数据来源: 代码 执行过程中产生
  3. 如何使用代码来做redis数据的缓存? Jedis客户端
  4. 在使用之前需要先导入Jedis的jar包

    <jedis.version>2.6.0</jedis.version><!-- jedis -->
    <dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>${jedis.version}</version>
    </dependency>
    
  5. Jedis示例:

    /*** 测试单个结点(单个redis)连接* */@Testpublic void test_01() {//创建Jedis对象 , 并在构造方法中设置redis主机的ip和占用的端口Jedis jedis = new Jedis("106.75.48.3",6379);//使用Jedis进行简单的操作//存数据jedis.set("name", "tianjie");//取数据String name = jedis.get("name");System.out.println("name="+name);}
    /*** 模拟数据缓存执行逻辑 , 数据库的查询操作* */
    @Test
    public void test_02() {System.out.println("用户开始查询数据");//模拟客户端传来的参数String name  = "name";//创建一个Jedis客户端Jedis jedis = new Jedis("106.75.48.3" , 6379);//执行逻辑 //1. 先查询缓存中是否有数据 , 如果有则返回缓存中的数据//2. 如果缓存中没有数据则去数据库中查询数据 , 并且 把查询到的数据存入缓存中 , 供后续使用 , 返回数据库中查询到的信息String gname  = jedis.get("name");if(gname != null && !gname.equals("")) {//缓存中有数据System.out.println("从缓存中获取到的数据为:name = "+gname);}else {//缓存中没有数据//执行从数据库查询数据   略String dbname = "outman";//数据库查询到的数据//将查询到的数据存入缓存中 jedis.set("name", dbname);//返回从数据库中查到的数据System.out.println("从数据库获取到的数据为:name = "+dbname);}//第一次执行结果为从数据库中获取//第二次执行结果我从缓存中获取数据
    }
    
  6. 自定义分片算法 ,将数据分片存入多个redis实例

    /*** 自定义分片计算逻辑* */@Testpublic void test_03() {//模拟需要存储的数据String k1 = "四十二章经第一章";String v1 = "111111111111111111";String k2 = "四十二章经第二章";String v2 = "222222222222222222";String k3 = "四十二章经第三章";String v3 = "333333333333333333";List<String> keyList = new ArrayList<String >();keyList.add(k1);keyList.add(k2);keyList.add(k3);Map<String  , String > map = new HashMap<String , String >();map.put(k1, v1);map.put(k2, v2);map.put(k3, v3);for(String key : keyList) {if("四十二章经第一章".equals(key)) {//存入第一个redis结点Jedis jedis = new Jedis("106.75.48.3" , 6379);jedis.set(key, map.get(key));}else if("四十二章经第二章".equals(key)) {//存入第二个redis结点Jedis jedis = new Jedis("106.75.48.3" , 6380);jedis.set(key, map.get(key));}else if("四十二章经第三章".equals(key)) {//存入第三个redis结点Jedis jedis = new Jedis("106.75.48.3" , 6381);jedis.set(key, map.get(key));}}}
    
  7. 使用hash取余法将数据分片存储

    /*** 哈希取余分片存储逻辑* */@Testpublic void test_04() {//模拟需要被存储的数据List<String> keyList = new ArrayList<String>();Map<String , String> map = new HashMap<String, String>();for(int i = 0 ; i<100 ; i++) {String key  = "key_"+i;String  value = "value_"+i;keyList.add(key);map.put(key, value);}//使用哈希取余法分片存储//定义结点(redis实例)数量int n = 3;for(String key : keyList) {//执行哈希取余 , 哈希结果可能为负数 , 此时需要与Integer的最大数进行与操作Integer num =( key.hashCode()&Integer.MAX_VALUE )%n; if(num == 0) {//存入第一个结点Jedis jedis = new Jedis("106.75.48.3" , 6379);jedis.set(key, map.get(key));jedis.close();}else if(num == 1) {//存入第二个结点Jedis jedis = new Jedis("106.75.48.3" , 6380);jedis.set(key, map.get(key));jedis.close();}else if(num == 2) {//存入第三个结点Jedis jedis = new Jedis("106.75.48.3" , 6381);jedis.set(key, map.get(key));jedis.close();}}//执行结果  100个键值对几乎均匀的 分布存储在三台redis实例上}
    
  8. jedis分片 , 使用的hash一致性

    /*** Jedis分片 使用哈希一致型完成数据分片存储(Jedis默认的分片算法)* */@Testpublic void test_05() {//需要构造存储多个reids实例信息 的listList<JedisShardInfo> jedisList = new ArrayList<JedisShardInfo>();//创建结点信息JedisShardInfo info1 = new JedisShardInfo("106.75.48.3" , 6379);JedisShardInfo info2 = new JedisShardInfo("106.75.48.3" , 6380);JedisShardInfo info3 = new JedisShardInfo("106.75.48.3" , 6381);//list保存结点信息jedisList.add(info1);jedisList.add(info2);jedisList.add(info3);//构造一个Jedis分片对象 , 将list闯入构造方法中 , 狗后续分片ShardedJedis jedis = new ShardedJedis(jedisList);//模拟海量数据执行数据分片存储for( int i= 0 ; i<1000 ; i++) {jedis.set("key_"+i, "value_"  + i);}jedis.close();//数据通过哈希一致型算法分片存储在了多个reids实例中//单数每一个reids的实例的数据量并不是完全平均的 , 会有一定量的数据偏移}
    
  9. jedis池的使用

    /*** Jedis池* */@Testpublic void test_06() {//需要构造存储多个reids实例信息 的listList<JedisShardInfo> jedisList = new ArrayList<JedisShardInfo>();//创建结点信息JedisShardInfo info1 = new JedisShardInfo("106.75.48.3" , 6379);JedisShardInfo info2 = new JedisShardInfo("106.75.48.3" , 6380);JedisShardInfo info3 = new JedisShardInfo("106.75.48.3" , 6381);//list保存结点信息jedisList.add(info1);jedisList.add(info2);jedisList.add(info3);//对于连接来将 类似于JDBC连接处可以设置很多参数JedisPoolConfig config = new JedisPoolConfig();//设置最大连接数config.setMaxTotal(200);//创建Jeids连接池ShardedJedisPool pool = new ShardedJedisPool(config, jedisList);//使用连接处获取数据ShardedJedis jedis = pool.getResource();for(int i = 0 ; i<100 ; i++) {String value = jedis.get("key_"+i);System.out.println("获取到key_"+i+"的值为"+value);}//归还连接pool.returnResource(jedis);}
    

九、哈希一致性

  1. 哈希是一种散列算法
  2. 使用哈希取余算法进行分片存储的问题 :
    1. 会造成大规模的数据倾斜(散列必定倾斜) , 而哈希一致型一定程度的解决了数据倾斜
    2. 使用哈希取余算法进行分片 存储之后 , 如果redis实例有变动 , 则数据迁移量过大 。
  3. 哈希取余算法导致数据迁移量巨大
    1. 当redis集群数量进行增加减少的时候 , n变化导致数据命中的变化量非常大 , 所以需要进行数据迁移
    2. 而且redis结点越多 , 数据迁移量越大
  4. 哈希一致型

    1. jedis中引入另外一种hash散列算法 — hash一致性
    2. 是由1997年麻绳理工的学生发明 : 其原理是引入一个 2^32-1个结点的整数环
    3. 把节点使用ip+端口做哈希散列计算 , 得到43亿中的一个值 , 投射到环中
    4. 然后把所有的数据key进行hash散列计算 也投射到环上
    5. 其中node代表的是redis , 其余的是数据
    6. 环上的数据会顺时针寻找最近的结点后存储
    7. 这样在redis增加或减少时 , 数据量的迁移是较少的 , 而且reids结点越多 , 数据量迁移越少
    8. 解决数据偏移问题

      1. 单独的使用节点的ip+端口做映射,毕竟节点数量是有限的
      2. 有可能在映射时的各自分布位置并不平均,导致数据偏移量非常大

        解决数据的平衡性引入虚拟节点
        node1的ip是192.168.40.156
        node2的ip是192.168.40.157
        各自引入2个虚拟节点(虚拟节点的数量是非常大的)
        node1-1=hash(192.168.40.156#1)
        node1-2=hash(192.168.40.156#2)
        node2-1=hash(192.168.40.157#1)
        node2-2=hash(192.168.40.157#2)
        每一个虚拟节点在哈希环上也会接收顺时针寻找最近节点的key们
        通过增加节点数量(虚拟的),完成数据的映射平衡
        凡是投影到node1-1,node1-2的key,都会中真实存储在node1中
        所以虚拟节点越多平衡性越好
        

补充:

  1. 数据库缓存
  2. hash特性
  3. 自己查去吧 哈哈

大数据 互联网架构阶段 Redis相关推荐

  1. 大数据 互联网架构阶段 Redis(三)redis集群

    Redis(三) redis集群 一. redis哨兵模式的缺点 问题一 : 横向扩展不方便 , 一旦扩展 , 无论代码结构多么简单, 都需要修改 问题二 : 散列分布式算法是hash一致性 , 无论 ...

  2. 大数据互联网架构阶段 Redis(二)

    Redis(二) 零 . 目录 将缓存引入电商项目 主从复制 哨兵模式 集群容忍度 CAP理论 十. 将缓存引入电商项目 使用Spring框架维护Jedis池对象 引入一个配置文件 applicati ...

  3. 大数据互联网架构阶段 QuartZ定时任务+RabbitMQ消息队列

    QuartZ定时任务+RabbitMQ消息队列 一 .QuartZ定时任务解决订单系统遗留问题 情景分析: 在电商项目中 , 订单生成后 , 数据库商品数量-1 , 但是用户迟迟不进行支付操作 , 这 ...

  4. 大数据 互联网架构阶段 电商项目简介

    电商项目简介 零.目录 电商项目特点 电商项目技术难点 电商项目简介 开发工具 电商项目架构 开发环境 一.电商项目特点 分布式 数十台服务器,甚至百台.千台.万台,包括:Nigix负载均衡集群.To ...

  5. 大数据互联网架构阶段 大型电商项目数据库设计时应该注意的点

    大型电商项目数据库设计时应该注意的点 一. id的设计 int(int)类型: 性能高 , 但是分布式数据库时 , id易重复 long(bigint)类型:性能高 , 比int类型容纳的数据更多 , ...

  6. 大数据互联网架构阶段 全文检索技术

    全文检索 一. 全文检索的引出 我们生活中的数据总体分为两种:结构化数据 和非结构化数据 . 结构化数据: 指具有固定格式或有限长度的数据,如数据库,元数据等. 非结构化数据: 指不定长或无固定格式的 ...

  7. 大数据互联网架构阶段 前台系统架构 跨域请求

    电商项目 前台系统的架构 零.目录 前台架构设计 前台分类树 跨域请求技术 jsonp httpClient 一 .前台架构设计 不能直接访问数据库 , 需要通过后台访问数据 架构: 单通道连接资源 ...

  8. 大数据 互联网架构阶段 Nginx的使用

    Nginx 一.情景分析 如何构建一个tomcat集群 , 两种构建方式 但是此时将项目部署到tomcat集群上之后用户还是只能通过固定的ip和端口访问固定的服务器 ,并没有达到构建tomcat集群时 ...

  9. 大数据互联网架构阶段 数据库三范式与反范式

    数据库范式 一. 三范式 主键: 创建表时可以不设置主键 , 但是没有设置主键的表 , 底层会认为所有的键都是主键 ,所以在创建时使用了所有的字段创建索引 , 在查询时索引的存在几乎没有意义 . 复合 ...

最新文章

  1. RegularExpressions(4) RegularExpressions 成员(一)
  2. SDOI2018IIIDX
  3. 4.边缘光照的描边shader
  4. 计算机单词修改是否正确,计算机组装必懂的53个单词及装机步骤51条.doc
  5. 配置V530交换机步骤
  6. html5手机常亮,vue开发的webapp中的手机物理返回键,以及屏幕常亮处理
  7. CentOS系统基本设置
  8. 图:Flash渲染控件安装失败原因所在.
  9. Spring 测试代码的写法以及一个c3p0的错误
  10. 漫聊科技发展史——1. 人工智能的发展史
  11. 论文笔记:Straight to the Tree: Constituency Parsing with Neural Syntactic Distance
  12. 从配置,外观,写一篇小米13测评报告
  13. 菜鸟最爱,60行代码打造一款音乐播放器!
  14. 通过使用阿里云的OCR图文识别 实现识别功能
  15. 【OpenCV-Python】教程:3-16 利用Grabcut交互式前景提取
  16. Uniapp Video MP4视频播放失败,只有声音,黑屏,视频播放不了,解决办法
  17. 电竞英雄联盟数据API接口 - 【比赛列表】API调用示例代码
  18. 厦门宝讯网捷:拼多多一件代发怎么做?
  19. NLP语义技术演进:从DP依存句法到SDP依存语义再到AMR抽象语义分析概述与开源实现...
  20. AMD将坚持x86架构,不会投身ARM架构怀抱

热门文章

  1. Flask 生成下载文件
  2. ios NSComparator 三种枚举类型
  3. Linux下的各文件夹的作用(转)
  4. [转载]ios简单sqlite使用
  5. jupyter 数据分析可视化案例_Python数据分析及可视化实例之Anaconda、Jupyter简介
  6. 天津理工上机c语言报告5,天津理工大学c语言上机报告7.doc
  7. Java黑皮书课后题第7章:**7.34(对字符串中的字符排序)使用以下方法头编写一个方法,返回一个排序好的字符串。编写一个测试程序,提示用户输入一个字符串,显示排序好的字符串
  8. C语言学习之怎样引用指针变量
  9. 华南理工大学和浙大计算机学院,浙江大学和华南理工大学的办学实力比较
  10. Linux的vim编辑器中的翻页命令