大狗哥传奇

  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

dom4j

发表于 2020-05-28 更新于 2020-12-02 分类于 java

SAXReader

feature属性,禁止校验dtd文件

使用 dom4j 解析 xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import com.sun.org.apache.xerces.internal.impl.Constants;

/**
* 在读取文件时,去掉dtd的验证,可以缩短运行时间
*/
public static SAXReader getSAXReader(){
SAXReader saxReader = new SAXReader(DOMDocumentFactory.getInstance(),false);
try {
saxReader.setFeature(Constants.XERCES_FEATURE_PREFIX + Constants.LOAD_EXTERNAL_DTD_FEATURE, false); //设置不需要校验头文件
} catch (Exception e) {
e.printStackTrace();
}
return saxReader;
}

读取String为DOMDocument

1
2
String xml = "...xml...";
DOMDocument document = (DOMDocument) getSAXReader().read(new StringReader(xml));

DOMElement

顺序读取文本内容和子元素

1
2
3
4
5
6
7
8
9
DOMElement root = doc.getRootElement();
for(Object element : root.content()){
if( o instanceof DOMElement){
System.out.println(((DOMElement)o).asXML())
}
if( o instanceof DOMText){
System.out.println(((DOMText)o).getText().trim())
}
}

XPATH

dom4j直接获取值

1
2
3
4
5
6
7
8
9
doc.selectObject("substring(/root/name/text(),2)");

//选择所有有id属性元素(List<DOMAttribute>)
doc.selectObject("/root/@id")
//获取第一个匹配标签的id属性的值
doc.selectObject("string(/root/@id)")

//搜索当前dom的子节点
dom.selectNodes("child/name/text()")

获取tomcat运行端口

1
2
3
4
5
6
7
8
9
10
11
//通过classpath定位tomcat配置文件conf/server.xml,使用xpath去解析
File serverXml = new File("/Users/li/java/apache-tomcat-7.0.70/conf/server.xml");
DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
domFactory.setNamespaceAware(true); // never forget this!
DocumentBuilder builder = domFactory.newDocumentBuilder();
Document doc = builder.parse(serverXml);
XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();
XPathExpression expr = xpath.compile("/Server/Service[@name='Catalina']/Connector[starts-with(@protocol,'HTTP')]/@port[1]");
String result = (String) expr.evaluate(doc, XPathConstants.STRING);
port = result != null && result.length() > 0 ? Integer.valueOf(result) : null;

redis-持久化

发表于 2020-05-26 更新于 2020-06-30 分类于 redis

RDB

RDB 是 redis 在指定时间内生成的数据集的时间点快照,可使用SAVE命令手动生成备份文件

优点:

  • 一个时间点的压缩过的快照文件,非常适用于备份
  • 适用于作为容灾处理,可以存储在其他数据中心
  • 仅需子进程去生成快照,不阻塞 redis 主线程
  • 启动时恢复数据比 AOF 快

缺点:

  • RDB 不利于记录短时间内的数据更新操作
  • RDB 最多可能会丢失备份间隔时间内的数据
  • 当数据过大时,生成快照耗费时间过多

AOF

AOF 是一个记录了 redis 所有写操作的日志,它可以在服务器启动时重新执行这些写命令来还原数据库。 AOF 文件中记录的写命令使用 redis 协议来保存数据,新命令会被追加到文件的结尾。redis 还可以对 AOF 文件进行重写,优化 AOF 文件的体积

优点:

  • 可靠性高,AOF 可供选择的策略no fsync at all, fsync every second, fsync at every query,默认情况下使用的是 fsync every second。同步的频率越高,占用 CPU 越多,同步效果越好
  • AOF 日志一个追加文件
  • AOF 可通过重写优化其体积
  • AOF 文件格式易于理解,方便转换

缺点:

  • AOF 文件一般大于 RDB
  • AOF 消耗性能大

配置

1
2
3
4
5
6
7
8
9
10
# RDB 每60秒,至少1000个key被修改过
save 60 1000

# AOF
appendonly yes
# AOF策略
#1 no
#2 everysec
#3 always
appendfsync everysec

可以使用redis-check-aof修复 aof 文件 可以使用redis-check-rdb修复 rdb 文件

redis-主从复制

发表于 2020-05-26 更新于 2020-06-30 分类于 redis

基础概念

redis 提供了基础的主从复制功能,它允许一个从节点实例完整拷贝主节点实例。当主从连接断开后,从节点会自动重连主节点

  • 主从节点连接后,主节点想从节点发送一连串的流指令,以用来拷贝主节点所有的写命令
  • 当从节点因故障断开连接后重连后,从节点尝试执行部分同步,即仅同步执行在断开连接时间内的写命令流
  • 当部分同步不可用时,从节点会请求一个全量同步,这需要主从点生成一个数据的快照,并将其发送给从节点,发送后主节点才可继续执行其他命令

redis 默认采用异步方式同步数据,耗费时间少性能高,可以使用wait命令,使其使用同步方式同步数据。wait仅确认待同步的数据,不保证同步过程的强一致性。

  • redis 使用异步方式同步数据
  • 主节点可以有多个从节点
  • 从节点可以有子从节点,类似瀑布模式
  • 主节点采用非阻塞的方式进行传递数据的,可同时支持查询数据
  • 从节点采用非阻塞的方式接受数据,可同时支持查询旧数据

当关闭主节点的持久化策略时,当主节点宕机重启丢失所有数据时,会将空的数据库同步到所有从节点,造成所有节点的数据丢失。所以这时,节点的自动重启功能需要被禁止。

从节点不具有写权限

配置从节点

只需要将下述配置放到从节点的配置文件中即可

1
2
3
4
# xxx的从节点
slaveof 192.168.1.1 6379
# 是否为只读模式,默认情况下为yes
slave-read-only yes

主从复制的细节

每一个主节点都有个一个 replication ID ,和一个用于记录写命令数据的复制缓存区,每个 byte 的缓冲数据都有一个偏移量,偏移量是一直增长的。 redis-主从复制_2020-05-26-17-16-39.png

  1. 当从节点连接到主节点时,他使用 psync 命令来发送它们记录的 replication ID 和迄今为止处理的写命令的偏移量。
  2. 若从节点匹配主节点 replication ID ,并且的复制缓存区中,有从节点上送的偏移量记录,那么从节点将同步偏移量之后的所有命令发送到从节点更新数据。
  3. 若从节点上送的 replication ID 不匹配,或者主节点的复制缓存区没有相关偏移量的写命令,那么就会进行全量复制
  4. 主节点使用 BGSAVE 命令,将当前数据生成 rdb 文件,然后将文件发送给从节点
  5. 从节点收到 rdb 数据后,将其加载到内存中,同时丢失旧数据

linux-自定义补全

发表于 2020-05-24 更新于 2020-06-30 分类于 linux

在 linux 中,通常我们可以使用<tab><tab>来进行补全,但是我们自己写的脚本却没有自动补全的功能。我们通过 linux 内置的 complete 命令来设定自定义补全。

一般情况下我们定义一个配置文件专门用于自定义配置文件

1
2
#/usr/bin/env bash
complete -W "now tomorrow never" dothis

要使其生效,我们需要source config.file,我们可以将这个命令追加到.bash_profile中

用法

  1. complete -W [wordlist] dothis

    例如: complete -W "now tomorrow never" dothis

    $ dothis <tab><tab> never now tomorrow

  2. complete -d dothis 补全目录
  3. complete -e dothis 补全文件
  4. complete -F function dothis 根据函数 function 的中定义的值去显示,该值是实时生效的

    例如:

    1
    2
    3
    4
    5
    #/usr/bin/env bash
    list(){
    COMPREPLY=(`cat .list`)
    }
    complete -F list dothis

    当我们修改.list文件的内容时,补全也跟着变动

redis-分区

发表于 2020-05-22 更新于 2020-06-30 分类于 redis

分区是指将将数据分别存储在不同 redis 节点,每个节点仅存储所有 key 的一部分。

使用分区的优势

  • 可存储大量数据,不受单台服务器的物理内存限制
  • 分流

缺点

  • 涉及多个 key 的操作,若 key 在不同的节点上,则无法执行
  • 分区是基于 key 的,若一个 key 中大量数据,它是无法分配到不同的节点
  • 分区处理数据相对比较复杂,特别是处理 RDB/AOF 文件时
  • 调整容量比较复杂,容量调整时需要实时平衡各个节点的数据

分区方式

redis 使用 hash 函数将 key 转换为一个数字,然后根据节点数量进行模运算,根据余数将其分配到指定的节点。

  • hash 分配具有一致性,即同一个 key 计算出的 hash 值是固定的
  • 客户端分区 有调用端选择正确的节点读写数据
  • 代理服务分区 使用一个中间服务器,来转发请求
  • 请求路由 随机请求一个服务器,由服务器确认数据在哪个节点,当数据请求到错误的节点时,自动转发请求到正确的节点

重分配

当需要新增或者移除节点时,redis 需要将 key 重新进行分配到剩余节点上。redis

redis-清除策略

发表于 2020-05-22 更新于 2020-06-30 分类于 redis

当 redis 内存使用达到最大限制时,当新的数据需要添加时会根据配置的过期策略(maxmemory_policy)表现出不同的行为

  • noeviction 直接返回错误
  • allkeys-lru 从所有 key 中选择一些最长时间未使用的 key 进行删除
  • volatile-lru: 从设置了过期时间的 key 中选择一些最长时间未使用的 key 进行删除
  • allkeys-random 从所有 key 中随机删除
  • volatile-random 从设置了过期时间的 key 中随机删除
  • volatile-ttl 从设置了过期时间的 key 中选择存活时间最短的 key 进行删除
  • allkeys-lfu 从所有 key 中选择一个最少使用的 key 进行删除
  • volatile-lfu 从设置了过期时间的 key 中选择一个最少使用的 key 进行删除

LRU

redis 并没有实现理论上的 LRU 算法,而是抽取一小部分的 key,将其中最久未使用的一个 key 进行过期 可以通过配置maxmemory-samples指定抽样的个数,默认是 5,抽样个数越大,则收集的越精确,但是占用 cpu 时间越多。当抽样个数达到 10 时,已经非常接近理论上的 LRU。 redis 内部维护一个双向链表,根据最近访问时间来进行排序

lFU

redis 莫里斯计数法来统计一个 key 的访问量。它的特点是访问量数值小的时候是增长的快,访问量数值大的时候增长的慢。我们可以通过lfu-log-factor 10来控制其增长速度。下面是不同参数时,访问量增长的速度增长,根据我们的 key 实际访问量,我们可以定义一个合适的值以方便更好的区分 key 的最近访问频率,以便淘汰最少访问次数的 key redis-清除策略_2020-05-22-17-08-10.png

redis 每分钟会计算 key 是否被访问过,若没有则将 key 的最近访问次数减去一个系数,这个系数根据配置lfu-decay-time 1来控制

redis事务

发表于 2020-05-22 更新于 2020-06-30 分类于 redis

使用 script

redis script 是原子性的,天然就支持事务,redis 事务可以做的操作,一定可以使用 script 实现,且更高效简单。

multi

建立一个命令队列,将后续命令顺序存在在临时队列中而不是执行,直到碰到 exec 或者 discard 如果后续命令语法错误,将会直接触发 discard multi 命令后,redis 是可以执行其他客户端命令的

discard

将 multi 缓存的命令队列丢弃

watch

1
watch [keys ...]

监听一个或多个 key,将当前 key 的值存储起来

unwatch

清空 watch 监听的 key

exec

  • 执行前判断是否有 watch 监听的 key,如有判断当前监听的 key 的值是否变动,若变动则不执行 exec
  • 一旦开始执行 multi 命令队列,redis 核心程序顺序执行此命令队列,此操作时原子性的,不存在其他客户端命令在其中间执行
  • 处于 redis 执行效率,命令队列执行过程中,如果命令执行失败或报错,仅将错误信息返回,并不会影响其他命令的执行
  • 不支持回滚
  • 清空 multi 队列
  • 清空 watch 监听的 key

redis-过期时间

发表于 2020-05-20 更新于 2020-06-30 分类于 redis

概述

redis 可对 key 设定过期时间,当达到指定时间后,将会自动将 key 删除

  • expire 几秒后失效
  • pexpire 几毫秒后失效
  • expireat 指定秒失效
  • pexpireat 指定毫秒失效
  • ttl 查询剩余秒数
  • pttl 查询剩余毫秒数
  • persist 移出过期时间
  • 过期时间设置可被del,set,getset和其他写命令给重置
  • rename会将原 key 的过期时间同步过新 key 上

过期时间

key 的过期时间使用的是 Unix 时间戳,不同服务器的时间差异可能导致 key 提前或延迟过期。多个节点同步数据时,可能各个节点的数据是否已经失效时不同的。

key 的过期时间作为一个属性,存储在 key 的中

过期策略

被动删除,当客户端请求已经失效的 key 时,此时 redis 会直接删除该 key。但是有些 key 可能一直不被访问,所以 redis 还提供了定期删除的策略。redis 每次测试一小部分 key 是否过期,然后执行删除。

  • 一般情况下一秒执行 10 此定时任务
  • 随机测试 20 个 key,删除所有已经过期的
  • 当 25%的 key 都是过期的,会重新执行此任务

同步过期命令

当 key 过期时,redis 会同步一个del命令到从节点,或者 AOF 文件。

redis发布订阅

发表于 2020-05-19 更新于 2020-06-30 分类于 redis

redis 提供发布订阅功能,客户端可订阅一个或多个 channel,也可以取消订阅。使用redis-cli进入订阅模式,只能使用ctrl-c取消订阅

发布订阅

订阅

1
subscribe [channel1] [channel2]

取消订阅

1
unsubscribe [channel1] [channel2]

发布消息

1
publish [channel] [message]

发布消息后,发布的客户端返回的是当前订阅 channel 的个数。订阅方会受到包含 channel 和 message

模式订阅

psubscribe 和 punsubscribe 基本与 subscribe,unsubscribe 用法一样,只是它支持使用通配符。例如

1
2
psubscribe news.*
punsubscribe news.*

事件订阅

redis 支持订阅操作数据的事件,事件订阅使用普通的订阅发布机制,不具备可靠性,当发布订阅客户端断开连接时,这段时间的订阅信息会丢失。

事件订阅影响 redis 性能,默认情况下是关闭的,可以通过配置文件打开,一般建议使用CONFIG SET临时设置 redis发布订阅_2020-05-25-15-16-11.png 输入的参数中至少要有一个 K 或者 E,否则的话,不管其余的参数是什么,都不会有任何通知被分发。(大小写不敏感)

字符 发送的通知
K 键空间通知,所有通知以  __keyspace@<db>__  为前缀,针对 Key
E 键事件通知,所有通知以  __keyevent@<db>__  为前缀,针对 event
g DEL 、 EXPIRE 、 RENAME  等类型无关的通用命令的通知
$ 字符串命令的通知
l 列表命令的通知
s 集合命令的通知
h 哈希命令的通知
z 有序集合命令的通知
x 过期事件:每当有过期键被删除时发送
e 驱逐(evict)事件:每当有键因为  maxmemory  政策而被删除时发送
A 参数  g$lshzxe  的别名,相当于是 All
  1. notify-keyspace-events "Ex" 表示对过期事件进行通知发送
  2. notify-keyspace-events "Kx"表示想监控某个 key 的失效事件
  3. notify-keyspace-events "AKx"将参数设为字符串 AKE 表示发送所有类型的通知。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 临时设置监控事件通知
config set notify-keyspace-events "KEx"

# 模式订阅
PSUBSCRIBE __key*
# 过期事件测试, 10秒后出触发失效事件
setex name 10 tx

# 订阅方收到消息
1) "pmessage"
2) "__key*"
3) "__keyspace@0__:name"
4) "expired"
1) "pmessage"
2) "__key*"
3) "__keyevent@0__:expired"
4) "name"

我们可以看到K与E的区别主要在于触发的channel和message的不同 _ 集群模式下,需要在所有节点上都开启事件通知,又因为监听事件主要是基于 key 的事件,因此需要在正确的节点上订阅消息_

redis-script

发表于 2020-05-19 更新于 2020-12-02 分类于 redis

简介

redis 内置 lua 解释器,支持解析执行 lua script。可以通过 eval 和 evalsha 命令来使用

eval script numkey keys[key ...] argv[argv ...]

  • 第一个参数是 script,这个 script 无需定义一个 function(也不应该),它可以直接被 redis 执行的上下文
  • 第二个参数是KEYS参数的数量
  • 剩余的参数既是KEYS和ARGV的参数

例如

1
2
3
4
5
> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"

调用 redis

我们可以使用以下两个方法在 lua 调用 redis 命令

  • redis.call()
  • redis.pcall()

这两个命令基本类似,唯一的区别在于当执行出错时,redis.call()会直接抛出异常,而 redis.pcall()仅将错误信息组织成报文返回

示例

1
2
# 0 表示script中无参数
eval "return redis.call('set','foo','bar')" 0

lua 中所有关于 redis 命令的操作在执行前需要分析哪个 key 会被操作,所以明确指定哪些 key 会被操作,确定 lua 将会被发送到哪个节点去执行。lua 操作的 keys 需要确保在同一个节点上,可以通过hash槽的方式确保其在同一个节点上。

原子性

lua script 的所有操作时原子性的

evalsha

eval 命令每次执行 script 时会将 script 本身发送至服务器,redis 为了避免带宽消耗,提供了 evalsha 命令。它使用 script 的sha1值来替代 script 本身,若服务器有该 script 的缓存,则直接执行,否则返回失败,并提示你使用 eval 命令。

例如

1
2
3
4
5
6
7
8
> set foo bar
OK
> eval "return redis.call('get','foo')" 0
"bar"
> evalsha 6b1bf486c81ceb7edf3c093f4c48582e38c0e791 0
"bar"
> evalsha ffffffffffffffffffffffffffffffffffffffff 0
(error) `NOSCRIPT` No matching script. Please use [EVAL](/commands/eval).

每个 eval 执行的 script 都会被永久缓存在 redis 服务器中,可使用script命令来操作缓存

  1. SCRIPT FLUSH 清空所有缓存的 lua script
  2. SCRIPT EXISTS sha1 sha2 ... shaN 查看对应sha1的 lua script 是否存在
  3. SCRIPT LOAD script 手动加载 lua script
  4. SCRIPT KILL 用来打断一个长时间执行但未达到最大 script 执行时限的 lua script

lua 脚本需要注意的地方

redis5 之后,当 lua 脚本执行成功后,redis 将写命令同步到从节点或者 aof 文件中,而不是同步 script 所以在之前的版本 lua 脚本需要按照以下标准

  • 不使用系统时间或者其他外部的变量
  • 禁止使用 redis 的 randomkey,srandmember,time命令
  • lua 使用smembers会先排序
  • 禁止使用 lua 的math.random和math.randomseed方法
  • 禁止赋值全局变量

批量

1
2
3
eval "for i,v in pairs(argv) do
redis.call('set',KEYS[i],ARGV[i])
end" numkeys [KEYS...] [ARGV...]

在集群模式下执行lua时报错

lua script attempted to access a non local key in a cluster node

redis 在执行 lua 时,将会对上送的kEYS进行计算,计算其是否属于同一个节点,如果属于同一节点,那么就会将 lua 请求转发到该节点去执行。若你在 lua 中手动写死 key,而不是通过参数上送的话,那么 redis 就可能随机选定一个节点去执行,实际执行过程中,就有一定几率发现当前节点是不正确的。 这是因为 redis 仅允许在单个节点执行 lua ,所以需要确保 key 在同一个节点上,为了确保 key 在同一个节点,我们可以使用hash tag,即用{}将 key 的一部分包裹起来,比如说{dbconfig}_c1,{dbconfig}_c2,这样在计算hash槽时只会计算{}内,从而确保 key 在同一个节点。

执行脚本文件

1
2
3
4
5
6
7
8
9
10
11
-- print输出在redis-server服务器的日志上
-- 删除大于hello1的key
print('--------------->')
local l=redis.call("keys","*")
for i=1,#l,1 do
if('hello1'< l[i]) then
redis.call('del',l[i])
end

end
return l
1…567…15

lijun

与其感慨路难行,不如马上就出发
145 日志
30 分类
117 标签
© 2019 – 2020 lijun