大数据 互联网架构阶段 Redis
2018-01-18 11:34
309 查看
Redis
零、 目录
高并发思路电商网站中缓存数据库的设计
缓存介绍
按照redis
redis常用命令
redis其他数据结构
数据分布式存储
Jedis客户端
哈希一致性
补充
一、 高并发思路
技术: tomcat集群+ nginx理论上引入20台nginx — 100万/s的并发量
问题: 当100万个用户同时增删该查时 , 就出现了系统瓶颈
系统瓶颈: 后台程序能支持100万/秒的访问 , 但是数据库扛不住100万/s的访问
redis
分布式 、 nosql 、 可以持久化+内存 、 内存 、 数据库
分布式: 数据库被划分
nosql(not only sequence query language): 不仅仅支持关系型 、 结构化的数据库 , 而且支持非关系型 、 非结构化数据
可持久化+内存: 启动恢复机制(redis启动之后立即恢复之前的数据)
数据库: 数据存储
作用: 可以分布式的存储海量的数据 , 放到内存中 , 可以做缓存数据库
二、 电商网站中缓存数据库如何设计
缓存可以如何添加数据库缓存:
执行的过程包括sql和组织查询结果 , 根据sql可以创建缓存 , 存储已经查过的resultSet , 节省了资源调度重组resultSet
持久层缓存:
减少数据库获取的结果转化为对象的过程 , 缓存直接调用保存的对象结果
业务层缓存:减少调用层次
控制层缓存: 减少调用层次
问题: 可以不可以过多的使用缓存?
缓存是占用内存空间的 ,过多的缓存插入 , 容易造成数据的冗余 , 在内存不够时 , 清空逻辑会交叉导致数据库失效
结论: 缓存引入的最终目的:
减少数据库的访问压力
减少网路传输
减少封装层次
三、缓存介绍
主流的缓存结构:ehcache(很多数据库底层缓存用的就是ehcache)并发量差
memoryCache , 10年前 , 并发量高(100万左右/秒) , 缺点: 不落地(数据不能持久化 , 在宕机后不能立即恢复丢失的数据 , 严重的情况下容易造成缓存击穿 , 这也是被redis取代的主要原因)
redis: 持久化 , 可以在宕机恢复后迅速的解决数据丢失的问题
问题: 如果在缓存服务器宕机后 , 无法进行数据恢复/没有解决数据丢失的问题 , 会导致“雪崩”(也叫缓存击穿)
1. 雪崩(缓存击穿): 海量用户访问请求涌入 , 一旦缓存失效(宕机后缓存数据丢失) , 所有访问涌入数据库 , 数据库无法承受海量的数据的查询 , 导致数据库服务器宕机 , 这时重启数据库 , 但是请求没有消失甚至用户在不断的刷新(请求瞬间翻倍) , 就发生了数据库在重启->宕机->重启中循环 , 导致整个系统崩溃。
解决雪崩问题:
缓存永不宕机: 启动集群 , 永远让集群的一部分起作用 , 剩余的一部分做备用
缓存技术必须要支持恢复数据 , 持久化 。
四、 安装redis
登录linux , 并创建管理目录下载安装包并解压
下载命令 wget "http://bj-yzjd.cn-bj.ufileos.com/redis-3.2.11.tar.gz" 解压 tar -xvf redis-3.2.11.tar.gz
进入解压之后的文件 , 执行安装
make && make install
启动redis
redis-server
则启动成功
使用redis
redis启动成功之后该linux主机立即成为一台redis服务器 , 此时使用redis时需要再打开一个该虚拟机的连接之后启动redis客户端
使用redis需要启动redis客户端
redis-cli
如果想在同一个连接中启动服务和客户端 , 则启动redis时可以使redis服务器在后台运行
redis-server &
停止redis服务
在占用控制台的服务连接中直接Ctrl+c即可停止服务
在客户端中
shutdown
检查后台 运行的 redis
ps -ef|grep redis
redis-server 表示redis服务
*表示所有IP都能够访问当前redis服务 , 如果列出一些列的IP地址 , 则除这些ip地址以外的访问都被拒绝。
五、 redis常用命令
redis存储的数据实际上是map形式(key-value|{key , value}|list)的字符串keys : 获取当前存储空间中所有存在的key
set [key] [value] : 设置key-value , key和value都是字符串
get[key] :通过key获取对应的值
select[整数值0~15] : redis默认存在0~15标号的数据库 分库 , 默认使用第一个库(0号库) , 这个功能是早期版本的冗余功能 , 现在的java代码不支持分库, 所以select 的功能逐渐不被使用
exists [key] : 判断该key是否存在
与get的区别:
分析get: 在redis中一个字段允许存储的最大大小为512M
如果使用get查询 , 如果存在则会返回值 , 此时如果值过大会占用过多的资源 。
而exists只是判断key是否存在 , 如果存在会返回1 , 如果不存在则返回0.
del [key] : 删除该key对应的键值对
type [key] : 获取key对应值的类型 , 普通的数据类型都是string , 复杂的数据类型有map 和list
help type/help[命令名称] 如:help set : 查看该命令的作用
实际问题可以在官网中查询对应的命令细节
flushall : 将所有的数据(0~15号库) , flush到持久化文件中
flushdb : flush当前分库的所有数据到持久化文件中 。
incr [key] : 自增 , Integer类型的数据自增(redis中存储是都是String类型 , 在需要自增时, 会先试图转换成Integer在再增) , 如果转换不成功 , 则会报错, incrby [key] index 自增指定的步数
decr [key]: 自减 decr [key] index 自减指定的步数
append [key] [appendValue]: 在value后追加数据
mget [k1] [k2] … : 获取一批key对应的值
mset [k1] [v1] [k2] [v2] … : 设置一批数据 常用的编程语言的API一般不支持这个命令 ,因为使用这中群体操作k-v的命令后不支持数据分片(使
4000
用key取hash值取余后 散列存储)和集群计算 ; 这是一个早期的冗余功能
expire [key] 时间数字(单位:秒) :设置当前key对应的value的过期时间
ttl [key] : 查看当前key-value的存活时间
-2 代表过期
-1 代表永久
可以使用数据中的过期时间来做倒计时 , 或者秒杀 , 但是这个倒计时是秒级别的 。
pexpire [key] 时间(单位毫秒) :做精确时间的秒杀
六、 redis其他数据结构
Hash结构:本身是key-value 的形式 , 但是这里的key也是key-value的形式
hset [key] [field] [value] : 赋值
hget [key] [field] : 取值
hmset [key] [field] [value] [field1] [value1] :批量赋值
hmget [key] [field] [field1]: 批量取值
hexists : 查看属性是否存在 , 存在返回1 , 不存在返回0
hdel [key] [field] :删除字段
hkeys [key]:只获取字段名
hvals [key] : 只获取字段值
hlen [key]:获取字段数量
list结构
key-value(双向链表 , 左->上 , 右→下)
lrange [listkey] start end :查看list
lpush [key] value :向对应的list的头部添加信息 , 如果没有改;list则创键后添加
rpush [key] [v1] : 向对应的list的尾部添加字符串元素
linsert : 向对应的list的特定位置之前或之后添加字符串元素
lset:设置list中指定下标的元素值
lrem : 从key对应的list中删除count个value相同的元素
count>0按从头到尾的顺序删除
count<0 时按照从尾到头的顺序删除
count=0 时 删除所有与value相同的元素
ltrim : 保留指定key的值得范围内的数据
lpop : 从list头部删除元素, 并返回删除的元素
rpop : 从list尾部删除元素并返回删除的元素
rpoplpush [list1] [list2]:从第一个尾部删除一个数据并将删除的数据添加到第二个list头部 , 整个操作是原子级的如果第一个list不存在或为空 则执行结果为nil , 如果第二个list不存在则创建
lindex : 返回名称为key的list中index位置的元素
llen : 返回list的长度
七、 数据分布式存储
要完成数据的分片存储 , 需要至少多个redis实例启动多个redis时 , 每一个redis会占用一个端口 , 如果端口冲突 , 则会发生启动失败 , 所以要更改redis的默认配置文件
修改配置文件
进入到redis根目录下的redis.conf文件修改
直接输入:set number 使左侧的行号显示
第61行 把bind注释掉
第80行 保护模式关闭
第84行修改默认端口 , 避免和其他redis冲突 , redis默认是6379
第105行当客户端空闲时间1小时就自动断开连接 , 0秒表示不启用超时设置
第128行daemonize设置成yes让redis启动时由守护进程管理(也就是在后台执行)
第150行 , 不同的redis设置不同的pid文件(和端口同名)
设置日志级别 , 使用默认就行
设置flush动作规则 , 默认900秒以内 至少有1条数据改动 则执行flush , 在300秒以内至少有10条数据变动则执行flush , 在60秒以内至少有10000条数据有变动则执行flush操作 。 默认即可
修改完之后保存并退出
复制整个redis文件夹 为 r2 , r3
并且修改r2 r3中配置文件的端口和pid dump文件的名字
进入到r1目录下 , 执行redis-server redis6379.conf
进入到r2目录下 , 执行redis-server redis6380.conf
进入到r3目录下 , 执行redis-server redis6381.conf
执行完之后检测三个redis实例是否启动成功 ps -ef|grep redis
此时如果需要开启redis客户端 字需要 执行 redis-cli -p 端口号
八、 Jedis客户端
redis集群部署完成 , 就是执行数据的存储数据来源: 代码 执行过程中产生
如何使用代码来做redis数据的缓存? Jedis客户端
在使用之前需要先导入Jedis的jar包
<jedis.version>2.6.0</jedis.version> <!-- jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>${jedis.version}</version> </dependency>
Jedis示例:
/** * 测试单个结点(单个redis)连接 * */ @Test public 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); } //第一次执行结果为从数据库中获取 //第二次执行结果我从缓存中获取数据 }
自定义分片算法 ,将数据分片存入多个redis实例
/** * 自定义分片计算逻辑 * */ @Test public 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)); } } }
使用hash取余法将数据分片存储
/** * 哈希取余分片存储逻辑 * */ @Test public 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实例上 }
jedis分片 , 使用的hash一致性
/** * Jedis分片 使用哈希一致型完成数据分片存储(Jedis默认的分片算法) * */ @Test public void test_05() { //需要构造存储多个reids实例信息 的list List<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的实例的数据量并不是完全平均的 , 会有一定量的数据偏移 }
jedis池的使用
/** * Jedis池 * */ @Test public void test_06() { //需要构造存储多个reids实例信息 的list List<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); }
九、哈希一致性
哈希是一种散列算法使用哈希取余算法进行分片存储的问题 :
会造成大规模的数据倾斜(散列必定倾斜) , 而哈希一致型一定程度的解决了数据倾斜
使用哈希取余算法进行分片 存储之后 , 如果redis实例有变动 , 则数据迁移量过大 。
哈希取余算法导致数据迁移量巨大
当redis集群数量进行增加减少的时候 , n变化导致数据命中的变化量非常大 , 所以需要进行数据迁移
而且redis结点越多 , 数据迁移量越大
哈希一致型
jedis中引入另外一种hash散列算法 — hash一致性
是由1997年麻绳理工的学生发明 : 其原理是引入一个 2^32-1个结点的整数环
把节点使用ip+端口做哈希散列计算 , 得到43亿中的一个值 , 投射到环中
然后把所有的数据key进行hash散列计算 也投射到环上
其中node代表的是redis , 其余的是数据
环上的数据会顺时针寻找最近的结点后存储
这样在redis增加或减少时 , 数据量的迁移是较少的 , 而且reids结点越多 , 数据量迁移越少
解决数据偏移问题
单独的使用节点的ip+端口做映射,毕竟节点数量是有限的
有可能在映射时的各自分布位置并不平均,导致数据偏移量非常大
解决数据的平衡性引入虚拟节点 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中 所以虚拟节点越多平衡性越好
补充:
数据库缓存hash特性
自己查去吧 哈哈
相关文章推荐
- 大数据 互联网架构阶段 Redis(三)redis集群
- 大数据 互联网架构阶段 Nginx的使用
- 大数据互联网架构阶段 前台系统架构 跨域请求
- 大数据互联网架构阶段 全文检索技术
- 大数据互联网架构阶段 Java爬虫
- 大数据互联网架构阶段 Spring框架导致的406错误
- 大数据互联网架构阶段 Linux下安装mysql启动的常见问题
- 大数据 互联网架构阶段 电商项目简介
- [置顶] 精华【分布式微服务云架构dubbo+zookeeper+springmvc+mybatis+shiro+redis】分布式大型互联网企业架构!
- 精华【分布式、微服务、云架构、dubbo+zookeeper+springmvc+mybatis+shiro+redis】分布式大型互联网企业架构!
- (架构设计)观察者模式+redis队列实现不同项目之间数据的交互
- [置顶] 企业大型互联网分布式架构{Java分布式架构 dubbo + springmvc + mybatis + ehcache + redis }
- 精华【分布式微服务云架构dubbo+zookeeper+springmvc+mybatis+shiro+redis】分布式大型互联网企业架构!
- Java互联网大数据技术视频资料 Nginx,ActiveMQ,Redis,Solr,kafka等
- 微信公号“架构师之路”学习笔记(六)-互联网一致性架构设计(session一致性,主从一致性,双主一致性,缓存一致性,冗余一致性,消息时序一致性,分布式事务一致性,数据扣减一致性等)
- C++自制Redis数据库(七) 决战架构设计--从数据的角度贯通始终,表白我的心。
- 《Redis系列专题》 之 大规模互联网应用Redis架构要点(精华)
- 在线实时数据清洗架构(3)—— 缓存选型 Redis
- 分布式架构真正适用于大型互联网项目的架构! dubbo+zookeeper+springmvc+mybatis+shiro+redis
- 大数据环境下互联网行业数据仓库/数据平台的架构之漫谈