您的位置:首页 > 数据库 > Redis

大数据 互联网架构阶段 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特性

自己查去吧 哈哈
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐