解决功能性的问题
Java、Servlet、Jsp、Tomcat、RDBMS、JDBC、Linux、Svn 等
解决扩展性的问题
Spring、 SpringMVC、SpringBoot、Hibernate、MyBatis等
解决性能的问题
NoSQL、Java多线程、Nginx、MQ、ElasticSearch、Hadoop等
Web1.0的时代,数据访问量很有限,用一夫当关的高性能的单节点服务器可以解决大部分问题.
Web2.0时代的到来,用户访问量大幅度提升,同时产生了大量的用户数据,加上后来的智能移动设备的普及,所有的互联网平台都面临了巨大的性能挑战.
思考: Session共享问题如何解决?
方案一、存在Cookie中
此种方案需要将Session数据以Cookie的形式存在客户端,不安全,网络负担效率低
方案二、存在文件服务器或者是数据库里
此种方案会导致大量的IO操作,效率低.
方案三、Session复制
此种方案会导致每个服务器之间必须将Session广播到集群内的每个节点,Session数据会冗余,节点越多浪费越大,存在广播风暴问题.
方案四、存在Redis中
目前来看,此种方案是最好的。将Session数据存在内存中,每台服务器都从内存中读取数据,速度快,结构还相对简单.
将活跃的数据缓存到Redis中,客户端的请求先打到缓存中来获取对应的数据,如果能获取到,直接返回,不需要从MySQL中读取。如果缓存中没有,再从MySQL数据库中读取数据,将读取的数据返回并存一份到Redis中,方便下次读取.
扩展: 对于持久化的数据库来说,单个库单个表存在性能瓶颈,因此会通过水平切分、垂直切分、读取分离等技术提升性能,此种解决方案会破坏一定的业务逻辑,但是可以换取更高的性能.
1.NoSQL(NoSQL = Not Only SQL ),意即“不仅仅是SQL”,泛指非关系型的数据库。
NoSQL 不依赖业务逻辑方式存储,而以简单的key-value模式存储。因此大大的增加了 数据库的扩展能力。
2.NoSQL的特点
不遵循SQL标准
不支持ACID
远超于SQL的性能。
3.NoSQL的适用场景
对数据高并发的读写
海量数据的读写
对数据高可扩展性的
4.NoSQL的不适用场景
需要事务支持
基于sql的结构化查询存储,处理复杂的关系,需要即席查询。
5.建议: 用不着sql的和用了sql也不行的情况,请考虑用NoSql
Memcached
Redis
mongoDB
列式数据库
先看行式数据库
思考: 如下两条SQL的快慢
select * from users where id =3
select avg(age) from users
再看列式数据库
HBase
Cassandra
Neo4j
http://db-engines.com/en/ranking
Redis是一个开源的key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,Redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
1.配合关系型数据库做高速缓存
2.由于其拥有持久化能力,利用其多样的数据结构存储特定的数据
1.Redis官方网站 http://Redis.io
2.Redis中文官方网站 http://www.Redis.net.cn
3.2.5 for Linux
不用考虑在Windows环境下对Redis的支持
Redis官方没有提供对Windows环境的支持,是微软的开源小组开发了对Redis对Windows的支持.
5.安装gcc与g++
能上网的情况:
yum install gcc
yum install gcc-c++
6.重新进入到Redis的目录中执行 make distclean后再执行make 命令.
7.执行完make后,可跳过Redis test步骤,直接执行 make install
1.默认前台方式启动
2.推荐后台方式启动
1.使用redis-cli 命令访问启动好的Redis
2.测试验证,通过 ping 命令 查看是否 返回 PONG
1.单实例关闭
2.多实例关闭
端口号来自一位影星的名字 . Alessia Merz
Redis默认创建16个库,每个库对应一个下标,从0开始.
通过客户端连接后默认进入到0 号库,推荐只使用0号库.
使用命令 select 库的下标 来切换数据库,例如 select 8
多路复用是指使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select和poll函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)。
Memcached 是 多线程 + 锁.
Redis 是 单线程 + 多路IO复用.
keys * | 查看当前库的所有键 |
---|---|
exists | 判断某个键是否存在 |
type | 查看键的类型 |
del | 删除某个键 |
expire | 为键值设置过期时间,单位秒 |
ttl | 查看还有多久过期,-1表示永不过期,-2表示已过期 |
dbsize | 查看当前数据库中key的数量 |
flushdb | 清空当前库 |
Flushall | 通杀全部库 |
get | 查询对应键值 |
---|---|
set | 添加键值对 |
append | 将给定的 |
strlen | 获取值的长度 |
senx | 只有在key 不存在时设置key的值 |
incr | 将key中存储的数字值增1 只能对数字值操作,如果为空,新增值为1 |
decr | 将key中存储的数字值减1 只能对数字之操作,如果为空,新增值为-1 |
incrby /decrby | 将key中存储的数字值增减,自定义步长 |
mset | 同时设置一个或多个key-value对 |
mget | 同时获取一个或多个value |
msetnx | 同时设置一个或多个key-value对,当且仅当所有给定的key都不存在 |
getrange | 获得值的范围,类似java中的substring |
setrange | 用 |
setex | 设置键值的同时,设置过去时间,单位秒 |
getset | 以新换旧,设置了新值的同时获取旧值 |
5.详说 incr key 操作的原子性
单键多值
Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素导列表的头部(左边)或者尾部(右边)。
它的底层实际是个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能会较差
lpush/rpush | 从左边/右边插入一个或多个值。 |
---|---|
lpop/rpop | 从左边/右边吐出一个值。 值在键在,值光键亡。 |
rpoplpush | 从 |
lrange | 按照索引下标获得元素(从左到右) |
lindex | 按照索引下标获得元素(从左到右) |
llen | 获得列表长度 |
linsert | 在 |
lrem | 从左边删除n个value(从左到右) |
sadd | 将一个或多个 member 元素加入到集合 key 当中,已经存在于集合的 member 元素将被忽略。 |
---|---|
smembers | 取出该集合的所有值。 |
sismember | 判断集合 |
scard | 返回该集合的元素个数。 |
srem | 删除集合中的某个元素。 |
spop | 随机从该集合中吐出一个值。 |
srandmember | 随机从该集合中取出n个值。 不会从集合中删除 |
sinter | 返回两个集合的交集元素。 |
sunion | 返回两个集合的并集元素。 |
sdiff | 返回两个集合的差集元素。 |
第一种方案: 用户ID为key ,VALUE为JavaBean序列化后的字符串
缺点: 每次修改用户的某个属性需要,先反序列化改好后再序列化回去。开销较大
第二种方案: 用户ID+属性名作为key, 属性值作为Value.
缺点: 用户ID数据冗余
第三种方案: 通过 key(用户ID) + field(属性标签) 就可以操作对应属性数据了,既不需要重复存储数据,也不会带来序列化和并发修改控制的问题
5.常用操作
hset | 给 |
---|---|
hget | 从 |
hmset | 批量设置hash的值 |
hexists key | 查看哈希表 key 中,给定域 field 是否存在。 |
hkeys | 列出该hash集合的所有field |
hvals | 列出该hash集合的所有value |
hincrby | 为哈希表 key 中的域 field 的值加上增量 increment |
hsetnx | 将哈希表 key 中的域 field 的值设置为 value ,当且仅当域 field 不存在 |
zadd | 将一个或多个 member 元素及其 score 值加入到有序集 key 当中 |
---|---|
zrange | 返回有序集 key 中,下标在 |
zrangebyscore key min max [withscores] [limit offset count] | 返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列。 |
zrevrangebyscore key max min [withscores] [limit offset count] | 同上,改为从大到小排列。 |
zincrby | 为元素的score加上增量 |
zrem | 删除该集合下,指定值的元素 |
zcount | 统计该集合,分数区间内的元素个数 |
zrank | 返回该值在集合中的排名,从0开始。 |
计量单位说明,大小写不敏感
include
类似jsp中的include,多实例的情况可以把公用的配置文件提取出来
ip地址的绑定 bind
一个空闲的客户端维持多少秒会关闭,0为永不关闭。
对访问客户端的一种心跳检测,每个n秒检测一次,官方推荐设置为60秒
是否为后台进程
存放pid文件的位置,每个实例会产生一个不同的pid文件
四个级别根据使用阶段来选择,生产环境选择notice 或者warning
日志文件名称
是否将Redis日志输送到linux系统日志服务中
日志的标志
输出日志的设备
设定库的数量 默认16
在命令行中设置密码
最大客户端连接数
设置Redis可以使用的内存量。一旦到达内存使用上限,Redis将会试图移除内部数据,移除规则可以通过maxmemory-policy来指定。如果Redis无法根据移除 规则来移除内存中的数据,或者设置了“不允许移除”,
那么Redis则会针对那些需要申请内存的指令返回错误信息,比如SET、LPUSH等。
设置样本数量,LRU算法和最小TTL算法都并非是精确的算法,而是估算值,所以你可以设置样本的大小。
一般设置3到7的数字,数值越小样本越不准确,但是性能消耗也越小。
Jedis所需要的jar包 ,可通过Maven的依赖引入
Commons-pool-1.6.jar
Jedis-2.1.0.jar
使用Windows环境下Eclipse连接虚拟机中的Redis注意事项
public class Demo01 {
public static void main(String[] args) {
//连接本地的 Redis 服务
Jedis jedis = new Jedis("127.0.0.1",6379);
//查看服务是否运行,打出pong表示OK
System.out.println("connection is OK=======>:"+jedis.ping());
}
}
Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断
Redis事务的主要作用就是串联多个命令防止别的命令插队
从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,至到输入Exec后,Redis会将之前的命令队列中的命令依次执行。
组队的过程中可以通过discard来放弃组队。
组队中某个命令出现了报告错误,执行时整个的所有队列会都会被取消。
如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚。
想想一个场景: 有很多人有你的账户,同时去参加双十一抢购
通过事务解决问题
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。
WATCH key[key….]
在执行multi之前,先执行watch key1 [key2],可以监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
unwatch
取消 WATCH 命令对所有 key 的监视。
如果在执行 WATCH 命令之后, EXEC 命令或 DISCARD 命令先被执行了的话,那么就不需要再执行 UNWATCH 了。
三特性
单独的隔离操作
事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
没有隔离级别的概念
队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行,也就不存在“事务内的查询要看到事务里的更新,在事务外查询不能看到”这个让人万分头痛的问题
不保证原子性
Redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚
解决计数器和人员记录的事务操作
秒杀并发模拟 ab工具
CentOS6 默认安装 ,CentOS7需要手动安装
联网: yum install httpd-tools
无网络: 进入cd /run/media/root/CentOS 7 x86_64/Packages
顺序安装
apr-1.4.8-3.el7.x86_64.rpm
apr-util-1.5.2-6.el7.x86_64.rpm
httpd-tools-2.4.6-67.el7.centos.x86_64.rpm
ab –n 请求数 -c 并发数 -p 指定请求数据文件
-T “application/x-www-form-urlencoded” 测试的请求
超卖问题
请求超时问题
节省每次连接redis服务带来的消耗,把连接好的实例反复利用
连接池参数:
MaxTotal:控制一个pool可分配多少个jedis实例,通过pool.getResource()来获取;如果赋值为-1,则表示不限制;如果pool已经分配了MaxTotal个jedis实例,则此时pool的状态为exhausted。
maxIdle:控制一个pool最多有多少个状态为idle(空闲)的jedis实例;
MaxWaitMillis:表示当borrow一个jedis实例时,最大的等待毫秒数,如果超过等待时间,则直接抛JedisConnectionException;
testOnBorrow:获得一个jedis实例的时候是否检查连接可用性(ping());如果为true,则得到的jedis实例均是可用的;
LUA脚本
Lua 是一个小巧的脚本语言,Lua脚本可以很容易的被C/C++ 代码调用,也可以反过来调用C/C++的函数,Lua并没有提供强大的库,一个完整的Lua解释器不过200k,所以Lua不适合作为开发独立应用程序的语言,而是作为嵌入式脚本语言。
很多应用程序、游戏使用LUA作为自己的嵌入式脚本语言,以此来实现可配置性、可扩展性。这其中包括魔兽争霸地图、魔兽世界、博德之门、愤怒的小鸟等众多游戏插件或外挂
LUA脚本在Redis中的优势
将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis的次数。提升性能。
LUA脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作
但是注意redis的lua脚本功能,只有在2.6以上的版本才可以使用。
利用lua脚本淘汰用户,解决超卖问题。
Redis提供了2个不同形式的持久化方式 RDB 和 AOF
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。
备份是如何执行的
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。
关于fork
在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,Linux中引入了“写时复制技术”,一般情况父进程和子进程会共用同一段物理内存,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。
RDB保存的文件
在redis.conf中配置文件名称,默认为dump.rdb
RDB文件的保存路径
默认为Redis启动时命令行所在的目录下,也可以修改
RDB的保存策略
手动保存快照
save: 只管保存,其它不管,全部阻塞
bgsave:按照保存策略自动保存
RDB的相关配置
stop-writes-on-bgsave-error yes
当Redis无法写入磁盘的话,直接关掉Redis的写操作
rdbcompression yes
进行rdb保存时,将文件压缩
rdbchecksum yes
在存储快照后,还可以让Redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能
优点: 节省磁盘空间,恢复速度快.
缺点: 虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能。 在备份周期在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就
会丢失最后一次快照后的所有修改
以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,Redis启动之初会读取该文件重新构建数据,换言之,Redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
AOF默认不开启,需要手动在配置文件中配置
可以在redis.conf中配置文件名称,默认为 appendonly.aof
AOF文件的保存路径,同RDB的路径一致
AOF和RDB同时开启,redis听谁的?
AOF文件故障备份
AOF的备份机制和性能虽然和RDB不同, 但是备份和恢复的操作同RDB一样,都是拷贝备份文件,需要恢复时再拷贝到Redis工作目录下,启动系统即加载
AOF文件故障恢复
如遇到AOF文件损坏,可通过
redis-check-aof --fix appendonly.aof 进行恢复
AOF同步频率设置
AOF采用文件追加方式,文件会越来越大为避免出现此种情况,新增了重写机制,当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集.可以使用命令bgrewriteaof。
Redis如何实现重写
AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似。
何时重写
重写虽然可以节约大量磁盘空间,减少恢复时间。但是每次重写还是有一定的负担的,因此设定Redis要满足一定条件才会进行重写。
系统载入时或者上次重写完毕时,Redis会记录此时AOF大小,设为base_size,如果Redis的AOF当前大小>= base_size +base_size*100% (默认)且当前大小>=64mb(默认)的情况下,Redis会对AOF进行重写。
优点:
备份机制更稳健,丢失数据概率更低。
可读的日志文本,通过操作AOF稳健,可以处理误操作。
缺点:
比起RDB占用更多的磁盘空间
恢复备份速度要慢
每次读写都同步的话,有一定的性能压力。
主从复制,就是主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主。
读写分离,性能扩展
容灾快速恢复
拷贝多个redis.conf文件include
开启daemonize yes
Pid文件名字pidfile
指定端口port
Log文件名字
Dump.rdb名字dbfilename
Appendonly 关掉或者换名字
info replication 打印主从复制的相关信息
slaveof
中途变更转向:会清除之前的数据,重新建立拷贝最新的
风险是一旦某个slave宕机,后面的slave都没法备份
当一个master宕机后,后面的slave可以立刻升为master,其后面的slave不用做任何修改。
用 slaveof no one 将从机变为主机。
反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库.
配置哨兵
调整为一主二从模式
自定义的/myredis目录下新建sentinel.conf文件
在配置文件中填写内容
sentinel monitor mymaster 127.0.0.1 6379 1
其中mymaster为监控对象起的服务器名称, 1 为 至少有多少个哨兵同意迁移的
数量。
启动哨兵
执行redis-sentinel /myredis/sentinel.conf
能上网:
执行yum install ruby
执行yum install rubygems
不能上网:
cd /run/media/root/CentOS 7 x86_64/Packages 获取如下rpm包
拷贝到/opt/rpmruby/目录下,并cd到此目录
执行:rpm -Uvh *.rpm --nodeps –force 按照依赖安装各个rpm包
按照依赖安装各个rpm包
执行在opt目录下执行 gem install --local redis-3.2.0.gem
拷贝多个redis.conf文件
开启daemonize yes
Pid文件名字
指定端口
Log文件名字
Dump.rdb名字
Appendonly 关掉或者换名字
cluster-enabled yes 打开集群模式
cluster-config-file nodes-端口号.conf 设定节点配置文件名
cluster-node-timeout 15000 设定节点失联时间,超过该时间(毫秒),集群自动进行主从切换
进入到 cd /opt/redis-3.2.5/src
执行
./redis-trib.rb create --replicas 1
192.168.31.211:6379 192.168.31.211:6380 192.168.31.211:6381
192.168.31.211:6389 192.168.31.211:6390 192.168.31.211:6391
注意: IP地址修改为当前服务器的地址,端口号为每个Redis实例对应的端口号.
以集群的方式进入客户端
redis-cli -c -p 端口号
通过cluster nodes 命令查看集群信息
redis cluster 如何分配这六个节点
一个集群至少要有三个主节点。
选项 --replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。
分配原则尽量保证每个主数据库运行在不同的IP地址,每个从库和主库不在一个IP地址上。
什么是slots
节点 A 负责处理 0 号至 5500 号插槽。
节点 B 负责处理 5501 号至 11000 号插槽。
节点 C 负责处理 11001 号至 16383 号插槽
在redis-cli每次录入、查询键值,redis都会计算出该key应该送往的插槽,如果不是该客户端对应服务器的插槽,redis会报错,并告知应前往的redis实例地址和端口.
redis-cli客户端提供了 –c 参数实现自动重定向。
如 redis-cli -c –p 6379 登入后,再录入、查询键值对可以自动重定向。
不在一个slot下的键值,是不能使用mget,mset等多键操作。
可以通过{}来定义组的概念,从而使key中{}内相同内容的键值对放到一个slot中去
如果主节点下线?从节点能否自动升为主节点?
主节点恢复后,主从关系会如何?
如果所有某一段插槽的主从节点都当掉,redis服务是否还能继续?
redis.conf中的参数 cluster-require-full-coverage
public class JedisClusterTest {
public static void main(String[] args) {
Set<HostAndPort> set =new HashSet<HostAndPort>();
set.add(new HostAndPort("192.168.31.211",6379));
JedisCluster jedisCluster=new JedisCluster(set);
jedisCluster.set("k1", "v1");
System.out.println(jedisCluster.get("k1"));
}
}
优点
实现扩容
分摊压力
无中心配置相对简单
缺点
多键操作是不被支持的
多键的Redis事务是不被支持的。lua脚本不被支持。
由于集群方案出现较晚,很多公司已经采用了其他的集群方案,而代理或者客户端分片的方案想要迁移至redis cluster,需要整体迁移而不是逐步过渡,复杂度较大。