1. 原型模式
原型模式主要用于对象的复制,实现一个接口(实现 Cloneable 接口), 重写一个方法(重写 Object 类中的 clone 方法),即完成了原型模式。 原型模式中的拷贝分为”浅拷贝”和”深拷贝”。
浅拷贝: 对值类型的成员变量进行值的复制,对引用类型的成员变量只复制引用,不复制引用的对象。
深拷贝: 对值类型的成员变量进行值的复制,对引用类型的成员变量也进行引用对象的复制。
1.1 浅拷贝
1 | package com.epoint.common.util; |
1.2 深拷贝
1 | package com.epoint.common.util; |
1.3 测试类
1 | package com.epoint.common.util; |
1.4 和单例模式关系
这两种设计模式都是处理对象创建的设计模式,区别在于:
原型模式是在已指定对象的基础上,然后通过拷贝这些原型对象创建新的对象;而单例模式模式的核心是将类的构造方法私有化,之后在类的内部产生实例化对象,并通过静态方法返回实例化对象的应用。
2. Redis
Redis是一个Key-Value的内存数据库(NoSQL),同时具备持久化的能力。同时,Redis提供面向多种语言的API,并且诸如Spring这样的框架已经给予Redis很好的支持,我们常用(Java)的Redis Client工具是Jedis。
2.1 使用场景
2.1.1 计数器
可以对 String 进行自增自减运算,从而实现计数器功能。
Redis 这种内存型数据库的读写性能非常高,很适合存储频繁读写的计数量。
2.1.2 缓存
将热点数据放到内存中,设置内存的最大使用量以及淘汰策略来保证缓存的命中率。
2.1.3 会话缓存
可以使用 Redis 来统一存储多台应用服务器的会话信息。
当应用服务器不再存储用户的会话信息,也就不再具有状态,一个用户可以请求任意一个应用服务器,从而更容易实现高可用性以及可伸缩性。
2.1.4 全页缓存(FPC)
除基本的会话token之外,Redis还提供很简便的FPC平台。
以Magento为例,Magento提供一个插件来使用Redis作为全页缓存后端。此外,对WordPress的用户来说,Pantheon有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。
2.1.5 查找表
例如 DNS 记录就很适合使用 Redis 进行存储。
查找表和缓存类似,也是利用了 Redis 快速的查找特性。但是查找表的内容不能失效,而缓存的内容可以失效,因为缓存不作为可靠的数据来源。
2.1.6 消息队列(发布/订阅功能)
List 是一个双向链表,可以通过 lpush 和 rpop 写入和读取消息
不过最好使用 Kafka、RabbitMQ 等消息中间件。
2.1.7 分布式锁实现
在分布式场景下,无法使用单机环境下的锁来对多个节点上的进程进行同步。
可以使用 Redis 自带的 SETNX 命令实现分布式锁,除此之外,还可以使用官方提供的 RedLock 分布式锁实现。
2.1.8 其它
Set 可以实现交集、并集等操作,从而实现共同好友等功能。
ZSet 可以实现有序性操作,从而实现排行榜等功能。
2.2 java客户端
2.2.1 Redis请求通信协议
Redis客户端与服务端通信使用 RESP(REdis Serialization Protocol)协议
它是一个序列化协议,支持如下几种数据类型,具体类型判断通过第一个字节判断,之间通过”\r\n”来分隔
- 简单字符串 以”+” 开头
- 错误类型 以”-“ 开头
- 整数 以”:” 开头
- 块字符串 以”$” 开头
- 数组 以”*” 开头
客户端每次发送一个块字符串数组到服务端,服务端根据命令执行后返回结果
简单字符串
以”+”字符开头,后面接实际字符串,最后以”\r\n”结尾
因为字符是通过’\r\n’来判断结尾的,所以此种类型中的字符串内容就不能包含这特殊字符,如果有需要可以使用块字符串类型
例子:+OK\r\n
错误类型
以”-“字符开头,后面接着错误错误信息,最后以”\r\n”结尾
例子:-Error message\r\n
整数
以”:”字符开头,数值,,最后以”\r\n”结尾
例子::1000\r\n
块字符串
以”$”字符开头,后面是字符串的实际长度,之后以”\r\n”分隔,接着是字符串内容,最后以’\r\n’结尾
例子:空字符串:$0\r\n\r\n Null(不存在的值):$-1\r\n
数组
以”*”开头,后面是数组长度,之后以”\r\n”分隔,后面是具体的其他的数据值(数据类型不要求一致)
例子:空数组:*0\r\n 队列阻塞超时:*-1\r\n
2.2.2 使用过程中的常见问题及解决
无法从连接池获取到Jedis连接
JedisPool默认的maxTotal值为8,下面代码从JedisPool中获取了8个Jedis资源,但是没有归还资源。因此,当第9次尝试获取Jedis资源的时候,则无法调用jedisPool.getResource().ping()。
1 | GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); |
正确示例如下:
1 | Jedis jedis = null; |
具体还有一些其他原因导致无法获得连接,例如:业务并发量大,而maxTotal值设置得过小;Jedis连接被拒绝,一般是由于Redis的域名配置或网络问题等原因导致。
2.3 redis速度快的原因
纯内存操作,避免大量访问数据库,减少直接读取磁盘数据,redis 将数据储存在内存里面,读写数据的时候都不会受到硬盘 I/O 速度的限制,所以速度快;
单线程操作,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
采用了非阻塞I/O多路复用机制,采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗),多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作,从而提高效率;
灵活多样的数据结构,redis内部使用一个redisObject对象来表示所有的key和value。redisObject主要的信息包括数据类型、编码方式、数据指针、虚拟内存等。它包含String,Hash,List,Set,Sorted Set五种数据类型,针对不同的场景使用对应的数据类型,减少内存使用的同时,节省网络流量传输。
2.4 持久化实现
Redis持久化有两种实现方式:
RDB(指定的时间间隔内保存数据快照)
全称Redis Database Backup file(Redis数据备份文件),也被叫做Redis数据快照。我们可以通过执行save或bgsave命令让Redis在本地生成RDB快照文件,这个RDB文件包含了整个实例接近完整的数据内容。
AOF(先把命令追加到操作日志的尾部,保存所有的历史操作)
全称为Append Only File(追加日志文件)。它与RDB不同的是,AOF中记录的是每一个命令的详细信息,包括完整的命令类型、参数等。只要产生写命令,就会实时写入到AOF文件中。
| # | RDB | AOF |
|---|---|---|
| 持久化方式 | 生成某一时刻的数据快照文件 | 实时记录每一个写命令到文件 |
| 数据完整性 | 不完整,取决于备份周期 | 相对完整性高,取决于文件刷盘方式 |
| 文件大小 | 压缩二进制写入,文件较小 | 原始的操作命令,文件大 |
| 宕机恢复时间 | 快 | 慢 |
| 恢复优先级 | 低 | 高 |
| 持久化代价 | 高,消耗大量CPU和内存 | 低,只占用磁盘IO资源 |
| 使用场景 | 数据备份、主从全量复制、对丢数据不敏感的业务场景快速数据恢复 | 对于丢失数据敏感的场景,例如涉及金钱交易相关的业务 |
2.5 高可用实现
Redis高可用的几种常见方式如下:
Redis多副本(主从复制)
Redis多副本,采用主从(replication)部署结构,相较于单副本而言最大的特点就是主从实例间数据实时同步,并且提供数据持久化和备份策略。主从实例部署在不同的物理服务器上,根据公司的基础环境配置,可以实现同时对外提供服务和读写分离策略。
Redis Sentinel(哨兵)
Redis Sentinel是社区版本推出的原生高可用解决方案,其部署架构主要包括两部分:Redis Sentinel集群和Redis数据集群。
其中Redis Sentinel集群是由若干Sentinel节点组成的分布式集群,可以实现故障发现、故障自动转移、配置中心和客户端通知。Redis Sentinel的节点数量要满足2n+1(n>=1)的奇数个。
Redis Cluster(Redis集群)
Redis Cluster是社区版推出的Redis分布式集群解决方案,主要解决Redis分布式方面的需求,比如,当遇到单机内存,并发和流量等瓶颈的时候,Redis Cluster能起到很好的负载均衡的目的。
Redis Cluster集群节点最小配置6个节点以上(3主3从),其中主节点提供读写操作,从节点作为备用节点,不提供请求,只作为故障转移使用。
Redis Cluster采用虚拟槽分区,所有的键根据哈希函数映射到0~16383个整数槽内,每个节点负责维护一部分槽以及槽所印映射的键值数据。
评论区