0%

优势

  • 模式灵活
    文档按列存储,可以快速增加/删除字段
  • 可扩展性
    支持水平和垂直扩展,扩展操作容易
  • 分布式写操作
    有可以在任何地方任何时间集中读或写任何数据。并且不会有任何单点失败
  • 写速度快

概念

  • 节点:存储数据。

  • 数据中心:节点的集合。

  • 集群:集群是包含一个或多个数据中心的组件。

  • MemTable:存储器驻留的数据结构。提交日志后,数据将被写入mem表。有时,对于单列族,将有多个mem表。

  • SSTable:它是一个磁盘文件,当其内容达到阈值时,数据从mem表中刷新。

  • 布隆过滤器:用于测试元素是否是集合(SSTabble)的成员,它是一种特殊的缓存。用于快速判断一个SSTable中是否包含给定条件的数据,减少磁盘开销

  • 键空间(Keyspace),相当于database

  • 复制因子 - 它是集群中将接收相同数据副本的计算机数。

  • 列族:键空间是一个或多个列族的列表的容器。列族又是一个行集合的容器。每行包含有序列。列族表示数据的结构。每个键空间至少有一个,通常是许多列族。

键空间

1
2
CREATE KEYSPACE testwuz WITH replication = {'class':'SimpleStrategy', 'replication_factor' : 3};
ALTER KEYSPACE "KeySpace Name" WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor' : '1'};

Strategy Name: 副本放置策略,SimpleStrategy,NetworkTopologyStrategy。
Replication Factor : 可以通过使用新的复制因子来更改复制因子。
DURABLE_WRITES : DURABLE_WRITES值可以通过指定其值true / false来更改。 默认情况下为true。 如果设置为false,则不会将更新写入提交日志,反之亦然。

MenTable
SSTable(Read Only,定期进行compaction,垃圾回收、合并SSTable)
CommitLog

Gossip
Partitioner

参考文档

wc教程

cd /tmp
wget https://downloads.apache.org/cassandra/3.11.10/apache-cassandra-3.11.10-bin.tar.gz
tar zxvf apache-cassandra-3.11.10-bin.tar.gz
mv apache-cassandra-3.11.10 /usr/local/
rm -f apache-cassandra-3.11.10-bin.tar.gz

(cat << EOF
export CASSANDRA_HOME=/usr/local/apache-cassandra-3.11.10
PATH=$CASSANDRA_HOME/bin:$PATH
EOF
) >> /etc/profile.d/cassandra.sh
source /etc/profile
cd /usr/local/apache-cassandra-3.11.10/conf

CREATE keyspace dennis WITH replication = {‘class’:’SimpleStrategy’, ‘replication_factor’ : 1};
DESCRIBE keyspaces;
drop keyspace dennis;

use dennis;
DESC tables;

CREATE TABLE student(
id int,
name varchar,
PRIMARY KEY(id)
);
INSERT INTO student (id,name) VALUES (1,’Naruto’);
select * from student where id=2;
DESC table student;

nodetool describering keyspace
nodetool gossipinfo
nodetool describecluster
nodetool ring

INCLUDES 指令

1
2
//包含其他配置文件
include /path/to/local.conf

MODULES 模块

1
2
//包含其他模块
loadmodule /path/to/my_module.so

NETWORK 模块

1
2
3
4
5
6
7
8
9
10
bind 127.0.0.1
//开启保护模式,如果未设置绑定IP或者绑定IP是0.0.0.0,则必须设置密码,防止未设置密码情况下被外网访问
protected-mode yes
port 6379
//设置生效指需要和/proc/sys/net/core/somaxconn 取最小值
tcp-backlog 50000
unixsocket /tmp/redis.sock
unixsocketperm 700
timeout 0
tcp-keepalive 300

GENERAL 模块

daemonize yes
//设置通过upstart、systemd来管理redis进程
supervised no
pidfile redis.pid
loglevel warning
logfile “”
syslog-enabled no
syslog-ident redis
syslog-facility local0
databases 16
always-show-logo yes

SNAPSHOTTING

save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir ${redisPath}

REPLICATION

slaveof
masterauth
//与主服务器断开后依然响应客户端的请求
slave-serve-stale-data yes
slave-read-only yes
//当从服务器连接上主服务器时,需要同步所有数据,此时主服务器有2中策略,生成rdb到文件中,然后把文件传输给从服务器,二是把rdb直接通过socket传给从服务器,默认使用socket
repl-diskless-sync no
//等待多个slave连接上来的时间
repl-diskless-sync-delay 5
repl-ping-slave-period 10
repl-timeout 60
repl-disable-tcp-nodelay no
//待同步缓冲区大小,避免从服务器每次重连后做全量同步
repl-backlog-size 1mb
repl-backlog-ttl 3600
//服务器权重,当主服务器挂掉后选举权重
slave-priority 100
//10秒内至少需要3个从在线,否则停止写
min-slaves-to-write 3
min-slaves-max-lag 10

SECURITY

requirepass foobared
rename-command CONFIG “”

CLIENTS

maxclients 10000

MEMORY MANAGEMENT

maxmemory

// volatile-lru -> Evict using approximated LRU among the keys with an expire set.
// allkeys-lru -> Evict any key using approximated LRU.
// volatile-lfu -> Evict using approximated LFU among the keys with an expire set.
// allkeys-lfu -> Evict any key using approximated LFU.
// volatile-random -> Remove a random key among the ones with an expire set.
// allkeys-random -> Remove a random key, any key.
// volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
// noeviction -> Don’t evict anything, just return an error on write operations.
maxmemory-policy noeviction
maxmemory-samples 5

LAZY FREEING

lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
slave-lazy-flush no

APPEND ONLY MODE

appendonly no
appendfilename “appendonly.aof”
//no、always、everysec
appendfsync everysec
//当正在rewrite的时候不刷盘
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
//redis在恢复时,会忽略最后一条可能存在问题的指令
aof-load-truncated yes
//混合持久化
aof-use-rdb-preamble no

LUA SCRIPTING

lua-time-limit 5000

REDIS CLUSTER

cluster-enabled yes
cluster-config-file nodes-6379.conf
cluster-node-timeout 15000
cluster-slave-validity-factor 10
cluster-migration-barrier 1
cluster-require-full-coverage yes
cluster-slave-no-failover no

CLUSTER DOCKER/NAT support

cluster-announce-ip 10.1.1.5
cluster-announce-port 6379
cluster-announce-bus-port 6380

SLOW LOG

slowlog-log-slower-than 10000
slowlog-max-len 128

LATENCY MONITOR

latency-monitor-threshold 0

EVENT NOTIFICATION

notify-keyspace-events “”

ADVANCED CONFIG

hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
list-compress-depth 0
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
//主动reshash,cpu时间的1%
activerehashing yes
//强制关闭没有及时读缓冲区的客户端 client-output-buffer-limit
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
client-query-buffer-limit 1gb
proto-max-bulk-len 512mb
hz 10
aof-rewrite-incremental-fsync yes
lfu-log-factor 10
lfu-decay-time 1

ACTIVE DEFRAGMENTATION

activedefrag yes
active-defrag-ignore-bytes 100mb
active-defrag-threshold-lower 10
active-defrag-threshold-upper 100
active-defrag-cycle-min 25
active-defrag-cycle-max 75

字符串

Redis只会使用C字符串作为字面量,在大多数情况下,Redis使用SDS(Simple Dynamic String,简单动态字符串)作为字符串表示

  • 常数复杂度获取字符串长度
  • 杜绝缓冲区溢出
  • 减少修改字符串长度时所需的内存重分配次数,类似于Java中的StringBuffer
  • 二进制安全
  • 兼容部分C字符串函数。

字典

Redis中的字典使用哈希表作为底层实现,每个字典带有两个哈希表,一个平时使用,另一个仅在进行rehash时使用

  • Redis使用MurmurHash2算法来计算键的哈希值
  • 分配到同一个索引上的多个键值对会连接成一个单向链表
  • 渐进式rehash

跳表

  • 由zskiplist和zskiplistNode两个结构组成,zskiplist用于保存跳跃表信息(比如表头节点、表尾节点、长度),而zskiplistNode则用于表示跳跃表节点
  • 每个跳跃表节点的层高都是1至32之间的随机数。
  • 在同一个跳跃表中,多个节点可以包含相同的分值,但每个节点的成员对象必须是唯一的。
  • 跳跃表中的节点按照分值大小进行排序,当分值相同时,节点按照成员对象的大小进行排序

整数集合

  • 底层实现为数组,这个数组以有序、无重复的方式保存集合元素,在有需要时,程序会根据新添加元素的类型,改变这个数组的类型
  • 升级操作为整数集合带来了操作上的灵活性,并且尽可能地节约了内存
  • 整数集合只支持升级操作,不支持降级操作

压缩列表

  • 顺序存储,节约内存。
  • 压缩列表可以包含多个节点,每个节点可以保存一个字节数组或者整数值。
  • 添加新节点到压缩列表,或者从压缩列表中删除节点,可能会引发连锁更新操作,但这种操作出现的几率并不高。

redis对象底层存储数据结构

  • 字符串:int、raw、embstr
  • 列表:ziplist、linkedlist
  • 哈希:ziplist、hashtable
  • 集合:intset、hashtable
  • 有序集合: ziplist、skiplist。

杂七杂八

  • 服务器在执行某些命令之前,会先检查给定键的类型能否执行指定的命令,而检查一个键的类型就是检查键的值对象的类型
  • Redis的对象系统带有引用计数实现的内存回收机制,当一个对象不再被使用时,该对象所占用的内存就会被自动释放
  • Redis会共享值为0到9999的字符串对象,类似于JAVA的对象缓存机制,避免过多分配内存

数据库

  • 数据库都保存在redisServer.db数组中,而数据库的数量则由redisServer.dbnum属性保存,客户端通过修改目标数据库指针,让它指向redisServer.db数组中的不同元素来切换不同的数据库
  • 数据库主要由dict和expires两个字典构成,其中dict字典负责保存键值对,而expires字典则负责保存键的过期时间,存储两个字典的设计目的是节约内存,很多key可能不会设置过期时间
  • 数据库的键总是一个字符串对象,而值则可以是任意一种Redis对象类型,包括字符串对象、哈希表对象、集合对象、列表对象和有序集合对象,分别对应字符串键、哈希表键、集合键、列表键和有序集合键
  • expires字典的键指向数据库中的某个键,而值则记录了数据库键的过期时间,过期时间是一个以毫秒为单位的UNIX时间戳

RDB持久化

  • RDB文件用于保存和还原Redis服务器所有数据库中的所有键值对数据
  • SAVE命令由服务器进程直接执行保存操作,所以该命令会阻塞服务器,BGSAVE令由子进程执行保存操作,所以该命令不会阻塞服务器
  • 服务器状态中会保存所有用save选项设置的保存条件,当任意一个保存条件被满足时,服务器会自动执行BGSAVE命令,默认条件900秒1个key,300秒10个key,600秒10000个key
  • RDB文件是一个经过压缩的二进制文件,由多个部分组成。
  • 对于不同类型的键值对,RDB文件会使用不同的方式来保存它们

AOC持久化

  • AOF文件通过保存所有修改数据库的写命令请求来记录服务器的数据库状态。
  • AOF文件中的所有命令都以Redis命令请求协议的格式保存
  • 命令请求会先保存到AOF缓冲区里面,之后再定期写入并同步到AOF文件
  • appendfsync选项的不同值对AOF持久化功能的安全性以及Redis服务器的性能有很大的影响
  • 服务器只要载入并重新执行保存在AOF文件中的命令,就可以还原数据库本来的状态
  • AOF重写可以产生一个新的AOF文件,这个新的AOF文件和原有的AOF文件所保存的数据库状态一样,但体积更小
  • AOF重写是一个有歧义的名字,该功能是通过读取数据库中的键值对来实现的,程序无须对现有AOF文件进行任何读入、分析或者写入操作
  • 在执行BGREWRITEAOF命令时,Redis服务器会维护一个AOF重写缓冲区,该缓冲区会在子进程创建新AOF文件期间,记录服务器执行的所有写命令。当子进程完成创建新AOF文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新AOF文件的末尾,使得新旧两个AOF文件所保存的数据库状态一致。最后,服务器用新的AOF文件替换旧的AOF文件,以此来完成AOF文件重写操作。

事件

Redis服务器是一个事件驱动程序,服务器处理的事件分为时间事件和文件事件两类

  • 文件事件处理器是基于Reactor模式实现的网络通信程序
  • 文件事件是对套接字操作的抽象:每次套接字变为可应答(acceptable)、可写(writable)或者可读(readable)时,相应的文件事件就会产生
  • 文件事件分为AE_READABLE事件(读事件)和AE_WRITABLE事件(写事件)两类
  • 时间事件分为定时事件和周期性事件:定时事件只在指定的时间到达一次,而周期性事件则每隔一段时间到达一次
  • 服务器在一般情况下只执行serverCron函数一个时间事件,并且这个事件是周期性事件
  • 文件事件和时间事件之间是合作关系,服务器会轮流处理这两种事件,并且处理事件的过程中也不会进行抢占。❑时间事件的实际处理时间通常会比设定的到达时间晚一些

客户端

  • 一个命令请求从发送到完成主要包括以下步骤:
    1)客户端将命令请求发送给服务器;
    2)服务器读取命令请求,并分析出命令参数;
    3)命令执行器根据参数查找命令的实现函数,然后执行实现函数并得出命令回复;
    4)服务器将命令回复返回给客户端
  • serverCron函数默认每隔100毫秒执行一次,它的工作主要包括更新服务器状态信息,处理服务器接收的SIGTERM信号,管理客户端资源和数据库状态,检查并执行持久化操作等等。
  • 服务器从启动到能够处理客户端的命令请求需要执行以下步骤:
    1)初始化服务器状态;
    2)载入服务器配置;
    3)初始化服务器数据结构;
    4)还原数据库状态;
    5)执行事件循环。

复制

  • Sentinel只是一个运行在特殊模式下的Redis服务器,它使用了和普通模式不同的命令表,所以Sentinel模式能够使用的命令和普通Redis服务器能够使用的命令不同

  • Sentinel会读入用户指定的配置文件,为每个要被监视的主服务器创建相应的实例结构,并创建连向主服务器的命令连接和订阅连接,其中命令连接用于向主服务器发送命令请求,而订阅连接则用于接收指定频道的消息

  • Sentinel通过向主服务器发送INFO命令来获得主服务器属下所有从服务器的地址信息,并为这些从服务器创建相应的实例结构,以及连向这些从服务器的命令连接和订阅连接。

  • 在一般情况下,Sentinel以每十秒一次的频率向被监视的主服务器和从服务器发送INFO命令,当主服务器处于下线状态,或者Sentinel正在对主服务器进行故障转移操作时,Sentinel向从服务器发送INFO命令的频率会改为每秒一次

  • 对于监视同一个主服务器和从服务器的多个Sentinel来说,它们会以每两秒一次的频率,通过向被监视服务器的__sentinel__:hello频道发送消息来向其他Sentinel宣告自己的存在。

  • 每个Sentinel也会从__sentinel__:hello频道中接收其他Sentinel发来的信息,并根据这些信息为其他Sentinel创建相应的实例结构,以及命令连接。

  • Sentinel只会与主服务器和从服务器创建命令连接和订阅连接,Sentinel与Sentinel之间则只创建命令连接。

  • Sentinel以每秒一次的频率向实例(包括主服务器、从服务器、其他Sentinel)发送PING命令,并根据实例对PING命令的回复来判断实例是否在线,当一个实例在指定的时长中连续向Sentinel发送无效回复时,Sentinel会将这个实例判断为主观下线。

  • 当Sentinel将一个主服务器判断为主观下线时,它会向同样监视这个主服务器的其他Sentinel进行询问,看它们是否同意这个主服务器已经进入主观下线状态

  • 当Sentinel收集到足够多的主观下线投票之后,它会将主服务器判断为客观下线,并发起一次针对主服务器的故障转移操作

集群

  • 节点通过握手来将其他节点添加到自己所处的集群当中。
  • 集群中的16384个槽可以分别指派给集群中的各个节点,每个节点都会记录哪些槽指派给了自己,而哪些槽又被指派给了其他节点
  • 节点在接到一个命令请求时,会先检查这个命令请求要处理的键所在的槽是否由自己负责,如果不是的话,节点将向客户端返回一个MOVED错误,MOVED错误携带的信息可以指引客户端转向至正在负责相关槽的节点。
  • 重新分片的关键是将属于某个槽的所有键值对从一个节点转移至另一个节点
  • 如果节点A正在迁移槽i至节点B,那么当节点A没能在自己的数据库中找到命令指定的数据库键时,节点A会向客户端返回一个ASK错误,指引客户端到节点B继续查找指定的数据库键。
  • MOVED错误表示槽的负责权已经从一个节点转移到了另一个节点,而ASK错误只是两个节点在迁移槽的过程中使用的一种临时措施
  • 集群里的从节点用于复制主节点,并在主节点下线时,代替主节点继续处理命令请求
  • 集群中的节点通过发送和接收消息来进行通信,常见的消息包括MEET、PING、PONG、PUBLISH、FAIL五种

发布与订阅

  • 服务器状态在pubsub_channels字典保存了所有频道的订阅关系:SUBSCRIBE命令负责将客户端和被订阅的频道关联到这个字典里面,而UNSUBSCRIBE命令则负责解除客户端和被退订频道之间的关联
  • 服务器状态在pubsub_patterns链表保存了所有模式的订阅关系:PSUBSCRIBE命令负责将客户端和被订阅的模式记录到这个链表中,而PUNSUBSCRIBE命令则负责移除客户端和被退订模式在链表中的记录
  • PUBLISH命令通过访问pubsub_channels字典来向频道的所有订阅者发送消息,通过访问pubsub_patterns链表来向所有匹配频道的模式的订阅者发送消息
  • PUBSUB命令的三个子命令都是通过读取pubsub_channels字典和pubsub_patterns链表中的信息来实现的

事务

  • 事务提供了一种将多个命令打包,然后一次性、有序地执行的机制
  • 多个命令会被入队到事务队列中,然后按先进先出(FIFO)的顺序执行
  • 事务在执行过程中不会被中断,当事务队列中的所有命令都被执行完毕之后,事务才会结束
  • 带有WATCH命令的事务会将客户端和被监视的键在数据库的watched_keys字典中进行关联,当键被修改时,程序会将所有监视被修改键的客户端的REDIS_DIRTY_CAS标志打开
  • 只有在客户端的REDIS_DIRTY_CAS标志未被打开时,服务器才会执行客户端提交的事务,否则的话,服务器将拒绝执行客户端提交的事务
  • Redis的事务总是具有ACID中的原子性、一致性和隔离性,当服务器运行在AOF持久化模式下,并且appendfsync选项的值为always时,事务也具有耐久性

脚本

  • Redis服务器在启动时,会对内嵌的Lua环境执行一系列修改操作,从而确保内嵌的Lua环境可以满足Redis在功能性、安全性等方面的需要
  • Redis服务器专门使用一个伪客户端来执行Lua脚本中包含的Redis命令
  • Redis使用脚本字典来保存所有被EVAL命令执行过,或者被SCRIPTLOAD命令载入过的Lua脚本,这些脚本可以用于实现SCRIPT EXISTS命令,以及实现脚本复制功能
  • EVAL命令为客户端输入的脚本在Lua环境中定义一个函数,并通过调用这个函数来执行脚本
  • EVALSHA命令通过直接调用Lua环境中已定义的函数来执行脚本
  • SCRIPT FLUSH命令会清空服务器lua_scripts字典中保存的脚本,并重置Lua环境
  • SCRIPT EXISTS命令接受一个或多个SHA1校验和为参数,并通过检查lua_scripts字典来确认校验和对应的脚本是否存在
  • SCRIPT LOAD命令接受一个Lua脚本为参数,为该脚本在Lua环境中创建函数,并将脚本保存到lua_scripts字典中
  • 服务器在执行脚本之前,会为Lua环境设置一个超时处理钩子,当脚本出现超时运行情况时,客户端可以通过向服务器发送SCRIPT KILL命令来让钩子停止正在执行的脚本,或者发送SHUTDOWN nosave命令来让钩子关闭整个服务器
  • 主服务器复制EVAL、SCRIPT FLUSH、SCRIPT LOAD三个命令的方法和复制普通Redis命令一样,只要将相同的命令传播给从服务器就可以了

排序

  • SORT命令通过将被排序键包含的元素载入到数组里面,然后对数组进行排序来完成对键进行排序的工作
  • 在默认情况下,SORT命令假设被排序键包含的都是数字值,并且以数字值的方式来进行排序
  • 如果SORT命令使用了ALPHA选项,那么SORT命令假设被排序键包含的都是字符串值,并且以字符串的方式来进行排序
  • SORT命令的排序操作由快速排序算法实现
  • SORT命令会根据用户是否使用了DESC选项来决定是使用升序对比还是降序对比来比较被排序的元素,升序对比会产生升序排序结果,被排序的元素按值的大小从小到大排列,降序对比会产生降序排序结果,被排序的元素按值的大小从大到小排列。❑当SORT命令使用了BY选项时,命令使用其他键的值作为权重来进行排序操作
  • 当SORT命令使用了LIMIT选项时,命令只保留排序结果集中LIMIT选项指定的元素
  • 当SORT命令使用了GET选项时,命令会根据排序结果集中的元素,以及GET选项给定的模式,查找并返回其他键的值,而不是返回被排序的元素
  • 当SORT命令使用了STORE选项时,命令会将排序结果集保存在指定的键里面

慢日志

  • Redis的慢查询日志功能用于记录执行时间超过指定时长的命令
  • Redis服务器将所有的慢查询日志保存在服务器状态的slowlog链表中,每个链表节点都包含一个slowlogEntry结构,每个slowlogEntry结构代表一条慢查询日志
  • 打印和删除慢查询日志可以通过遍历slowlog链表来完成
  • slowlog链表的长度就是服务器所保存慢查询日志的数量
  • 新的慢查询日志会被添加到slowlog链表的表头,如果日志的数量超过slowlog-max-len选项的值,那么多出来的日志会被删除

监视器

  • 客户端可以通过执行MONITOR命令,将客户端转换成监视器,接收并打印服务器处理的每个命令请求的相关信息
  • 当一个客户端从普通客户端变为监视器时,该客户端的REDIS_MONITOR标识会被打开
  • 服务器将所有监视器都记录在monitors链表中
  • 每次处理命令请求时,服务器都会遍历monitors链表,将相关信息发送给监视器

二进制数组

  • Redis使用SDS来保存位数组
  • SDS使用逆序来保存位数组,这种保存顺序简化了SETBIT命令的实现,使得SETBIT命令可以在不移动现有二进制位的情况下,对位数组进行空间扩展
  • BITCOUNT命令使用了查表算法和variable-precision SWAR算法来优化命令的执行效率
  • BITOP命令的所有操作都使用C语言内置的位操作来实现

redis内存过期策略

  • volatile-lru -> Evict using approximated LRU among the keys with an expire set.
  • allkeys-lru -> Evict any key using approximated LRU.
  • volatile-lfu -> Evict using approximated LFU among the keys with an expire set.
  • allkeys-lfu -> Evict any key using approximated LFU.
  • volatile-random -> Remove a random key among the ones with an expire set.
  • allkeys-random -> Remove a random key, any key.
  • volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
  • noeviction -> Don’t evict anything, just return an error on write operations.

浮点数比较大小使用

Double.compare( d1, d2),
Float.compare(f1, f2);
Math.abs(a-b)<1e-6
不能使用=,equals,compareTo(b)==0比较大小

javap命令简述

javap是jdk自带的反解析工具。根据class字节码文件,反解析出当前类对应的code区(汇编指令)、本地变量表、异常表和代码行偏移量映射表、常量池等等信息

javap用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-help  --help  -?        输出此用法消息
-version 版本信息,其实是当前javap所在jdk的版本信息,不是class在哪个jdk下生成的。
-v -verbose 输出附加信息(包括行号、本地变量表,反汇编等详细信息)
-l 输出行号和本地变量表
-public 仅显示公共类和成员
-protected 显示受保护的/公共类和成员
-package 显示程序包/受保护的/公共类 和成员 (默认)
-p -private 显示所有类和成员
-c 对代码进行反汇编
-s 输出内部类型签名
-sysinfo 显示正在处理的类的系统信息 (路径, 大小, 日期, MD5 散列)
-constants 显示静态最终常量
-classpath <path> 指定查找用户类文件的位置
-bootclasspath <path> 覆盖引导类文件的位置

javap 结果解析

javap -c -l TestDate.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
Warning: Binary file TestDate contains com.justest.test.TestDate
Compiled from "TestDate.java"
public class com.justest.test.TestDate {
//默认的构造方法,在构造方法执行时主要完成一些初始化操作,包括一些成员变量的初始化赋值等操作
public co
Code:m.justest.test.TestDate();
0: aload_0 //从本地变量表中加载索引为0的变量的值,也即this的引用,压入栈
1: invokespecial #10 //出栈,调用java/lang/Object."<init>":()V 初始化对象,就是this指定的对象的init()方法完成初始化
4: aload_0 // 4到6表示,调用this.count = 0,也即为count复制为0。这里this引用入栈
5: iconst_0 //将常量0,压入到操作数栈
6: putfield //出栈前面压入的两个值(this引用,常量值0), 将0取出,并赋值给count
9: return
//指令与代码行数的偏移对应关系,每一行第一个数字对应代码行数,第二个数字对应前面code中指令前面的数字
LineNumberTable:
line 5: 0
line 7: 4
line 5: 9
//局部变量表,start+length表示这个变量在字节码中的生命周期起始和结束的偏移位置(this生命周期从头0到结尾10),slot就是这个变量在局部变量表中的槽位(槽位可复用),name就是变量名称,Signatur局部变量类型描述
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/justest/test/TestDate;

public static void main(java.lang.String[]);
Code:
// new指令,创建一个class com/justest/test/TestDate对象,new指令并不能完全创建一个对象,对象只有在初,只有在调用初始化方法完成后(也就是调用了invokespecial指令之后),对象才创建成功,
0: new //创建对象,并将对象引用压入栈
3: dup //将操作数栈定的数据复制一份,并压入栈,此时栈中有两个引用值
4: invokespecial #20 //pop出栈引用值,调用其构造函数,完成对象的初始化
7: astore_1 //pop出栈引用值,将其(引用)赋值给局部变量表中的变量testDate
8: aload_1 //将testDate的引用值压入栈,因为testDate.test1();调用了testDate,这里使用aload_1从局部变量表中获得对应的变量testDate的值并压入操作数栈
9: invokevirtual #21 // Method test1:()V 引用出栈,调用testDate的test1()方法
12: return //整个main方法结束返回
LineNumberTable:
line 10: 0
line 11: 8
line 12: 12
//局部变量表,testDate只有在创建完成并赋值后,才开始声明周期
LocalVariableTable:
Start Length Slot Name Signature
0 13 0 args [Ljava/lang/String;
8 5 1 testDate Lcom/justest/test/TestDate;

public void test1();
Code:
0: new #27 // 0到7创建Date对象,并赋值给date变量
3: dup
4: invokespecial #29 // Method java/util/Date."<init>":()V
7: astore_1
8: ldc #30 // String wangerbei,将常量“wangerbei”压入栈
10: astore_2 //将栈中的“wangerbei”pop出,赋值给name1
11: aload_0 //11到14,对应test2(date,name1);默认前面加this.
12: aload_1 //从局部变量表中取出date变量
13: aload_2 //取出name1变量
14: invokevirtual #32 // Method test2: (Ljava/util/Date;Ljava/lang/String;)V 调用test2方法
// 17到38对应System.out.println(date+name1);
17: getstatic #36 // Field java/lang/System.out:Ljava/io/PrintStream;
//20到35是jvm中的优化手段,多个字符串变量相加,不会两两创建一个字符串对象,而使用StringBuilder来创建一个对象
20: new #42 // class java/lang/StringBuilder
23: dup
24: invokespecial #44 // Method java/lang/StringBuilder."<init>":()V
27: aload_1
28: invokevirtual #45 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
31: aload_2
32: invokevirtual #49 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
35: invokevirtual #52 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
38: invokevirtual #56 // Method java/io/PrintStream.println:(Ljava/lang/String;)V invokevirtual指令表示基于类调用方法
41: return
LineNumberTable:
line 15: 0
line 16: 8
line 17: 11
line 18: 17
line 19: 41
LocalVariableTable:
Start Length Slot Name Signature
0 42 0 this Lcom/justest/test/TestDate;
8 34 1 date Ljava/util/Date;
11 31 2 name1 Ljava/lang/String;

public void test2(java.util.Date, java.lang.String);
Code:
0: aconst_null //将一个null值压入栈
1: astore_1 //将null赋值给dateP
2: ldc #66 // String zhangsan 从常量池中取出字符串“zhangsan”压入栈中
4: astore_2 //将字符串赋值给name2
5: return
LineNumberTable:
line 22: 0
line 23: 2
line 24: 5
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this Lcom/justest/test/TestDate;
0 6 1 dateP Ljava/util/Date;
0 6 2 name2 Ljava/lang/String;

public void test3();
Code:
0: aload_0 //取出this,压入栈
1: dup //复制操作数栈栈顶的值,并压入栈,此时有两个this对象引用值在操作数组栈
2: getfield #12// Field count:I this出栈,并获取其count字段,然后压入栈,此时栈中有一个this和一个count的值
5: iconst_1 //取出一个int常量1,压入操作数栈
6: iadd // 从栈中取出count和1,将count值和1相加,结果入栈
7: putfield #12 // Field count:I 一次弹出两个,第一个弹出的是上一步计算值,第二个弹出的this,将值赋值给this的count字段
10: return
LineNumberTable:
line 27: 0
line 28: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/justest/test/TestDate;
public void test4();
Code:
0: iconst_0
1: istore_1
2: iconst_0
3: istore_2
4: iload_1
5: iconst_1
6: iadd
7: istore_2
8: iload_1
9: iconst_1
10: iadd
11: istore_2
12: return
LineNumberTable:
line 33: 0
line 35: 2
line 36: 4
line 38: 8
line 39: 12
//看下面,b和c的槽位slot一样,这是因为b的作用域就在方法块中,方法块结束,局部变量表中的槽位就被释放,后面的变量就可以复用这个槽位
LocalVariableTable:
Start Length Slot Name Signature
0 13 0 this Lcom/justest/test/TestDate;
2 11 1 a I
4 4 2 b I
12 1 2 c I
}

使用”对象”.class不能进行第一次静态初始化,使用class.forName()会进行第一次初始化

子类对象 install of 父类 true

子类.class == 子类对象 getClass true

getDeclaredAnnotation

getDeclaredClassed

getDeclaredConstructor

getDeclaredField

getDeclaredMethod

getMethods 返回 本类、父类、父类接口的 public方法
getDeclaredMethods 返回本类的所有方法

method可以 invoke()
constructor 可以使用newInstance 构造对象
Field 可以用来 get set值

method.getModifiers()

如何提高反射效率

使用高性能反射包 reflectAsm
缓存反射对象 避免每次都要去重复字节码中获取
method反射可以设置method.setAccessible(true)来关闭检查
尽量不要使用getMethods后再遍历删选 直接使用getMethod(name)来根据方法名获取
getMethods返回数组时,每个method都做了一次拷贝
getMethod只会返回拷贝的方法

jvm会缓存method,但是返回给外部时返回一个拷贝

反射是线程安全的,通过cas获取

普通方法 10亿 9ms
反射方法 10亿 5699ms
关闭检测 10亿 1959ms

普通方法和反射方法的差距

1、Method#invoke方法会对参数做封装和解封装操作
2、需要检查发放的可见性
3、需要校验参数
4、放射方法难以内敛
5、JIT无法优化

查看集群健康信息

1
curl -X GET "http://localhost:9200/_cat/health?v"

查看节点的详细信息

1
curl -X GET "http://localhost:9200/_cat/nodes?v"

查看索引信息

1
curl -X GET "http://localhost:9200/_cat/indices?v"

创建索引(索引会自动创建)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
curl -X PUT "http://localhost:9200/customer?pretty"
curl -XPUT "http://localhost:9200/customer33?pretty" -H 'Content-Type: application/json' -d '{
"settings":{
"number_of_shards":5, //每个索引的主分片数,默认值是5,这个配置在索引创建后不能修改。
"number_of_replicas":2, //设置副本数量
//自定义索引默认分析器
"index":{
"analysis":{
"analyzer":{
"default":{
"tokenizer":"standard", //分词器
"filter":[ //过滤器
"asciifolding",
"lowercase",
"ourEnglishFilter"
]
}
},
"filter":{
"ourEnglishFilter":{
"type":"kstem"
}
}
}
}
},
"mapping": {
"_doc": {
"properties": {
"commodity_id": {
"type": "long"
},
"commodity_name": {
"type": "text"
},
"picture_url": {
"type": "keyword"
},
"price": {
"type": "double"
}
}
}
}
}'


查看索引设置

1
curl "http://127.0.0.1:9200/customer/_settings?pretty"

更新索引配置

1
curl -X PUT "http://127.0.0.1:9200/customer/_settings?pretty" -H 'Content-Type: application/json' -d '{"number_of_replicas": 3}'

查看索引字段信息

1
2
3
4
5
curl  http://192.168.253.206:9200/xinhua_en?pretty
curl "http://127.0.0.1:9200/customer/_mapping?pretty"
curl "http://127.0.0.1:9200/videos/fulltext/_mapping?pretty"


新增/更新数据

1
2
3
4
5
6
7
8
# PUT会将新的json值完全替换掉旧的;而POST方式只会更新相同字段的值,其他数据不会改变,新提交的字段若不存在则增加
# PUT和DELETE操作是幂等的
# POST操作不是幂等的,比如常见的POST重复加载问题:当我们多次发出同样的POST请求后,其结果是创建了若干的资源
# 创建操作可以使用POST,也可以使用PUT,区别就在于POST是作用在一个集合资源(/articles)之上的,而PUT操作是作用在一个具体资源之上的(/articles/123)。

curl -X PUT 'http://localhost:9200/customer/person/2' -H 'Content-Type: application/json' -d '{"user": "dennis","title": "工程师","desc": "数据库管理aaa"}'
# 更新带版本号
curl -X POST 'http://localhost:9200/customer/person/2/update?version=3' -H 'Content-Type: application/json' -d '{"user": "dennis","title": "工程师","desc": "数据库管理aaa"}'

查询数据

1
2
3
4
5
6
curl "http://127.0.0.1:9200/videos/_search"
curl "http://127.0.0.1:9200/customer/person/_search"
curl "http://127.0.0.1:9200/customer/person/_search?q=title:%E6%9D%8E%E9%B9%8F"
# 获取source数据
curl "http://127.0.0.1:9200/videos/fulltext/578406/_source?pretty"

删除数据

删除后不会马上删除,只是标记状态,会在添加索引的时候删除

1
curl -X DELETE  "http://192.168.0.252:9200/customer/person/1"

删除索引

1
curl -X DELETE "http://localhost:9200/customer?pretty"

分词分析

1
2
curl "http://127.0.0.1:9200/customer/person/_analyze" -H 'Content-Type: application/json'  -d '{"text":"搜索引擎","analyze":"ik_max_word"}'
curl "http://127.0.0.1:9200/customer/_analyze" -H 'Content-Type: application/json' -d '{"text":"搜索引擎","analyze":"ik_max_word"}'

相关配置

cluster.name: es_cluster

默认为节点的主机名

node.name: node-206
path.data: /data0/elasticsearch/data
path.logs: /data0/elasticsearch/log
network.host: 192.168.253.206
http.port: 9200
bootstrap.memory_lock: false
bootstrap.system_call_filter: false

以文件的方式提供主机列表,可以动态修改,而不用重启节点

discovery.seed_providers

discovery.zen.ping.unicast.resolve_timeout

最小节点数量,基本的原则是这里需要设置成 N/2+1

discovery.zen.minimum_master_nodes: 2

两个节点的通讯超时时间 主节点-从节点

discovery.zen.ping.timeout: 3

加入集群超时时间

discovery.zen.join_timeout: 3

设置全新群集中符合主机要求的节点的初始集合. 默认情况下,该列表为空,这意味着该节点希望加入已经被引导的集群

cluster.initial_master_nodes

选定主节点发现时间间隔,默认1S

discovery.find_peers_interval

是否有资格成为master ,如果服务器配置不一致 建议设置高配服务器为true

node.master: true

设置为false的话这个节点就只能做master的候选 而不会去做索引的读写等操作,让master专心做领导(前提是集群节点比较多)

node.data: false

静态配置主机列表

discovery.seed_hosts 新配置,静态设置设置主机列表

discovery.zen.ping.unicast.hosts: [“192.168.253.207”, “192.168.253.206”,”192.168.253.205”]

自动发现

组播地址

discovery.zen.ping.multicast.group: 224.2.2.4

组播端口

discovery.zen.ping.multicast.port: 54328

广播消息ttl

discovery.zen.ping.multicast.ttl: 3

绑定的地址,null表示绑定所有可用的网络接口

discovery.zen.ping.multicast.address:null

多播自动发现禁用开关

discovery.zen.ping.multicast.enabled:true

一个集群必须有一个主节点(master node)。用来处理请求、索引的创建、修改、节点管理等。当有了master节点,该节点就要对各子节点进行周期性(心跳机制)的探测,保证整个集群的健康

es有两种集群故障探查机制

第一种是通过master进行的,master会ping集群中所有的其他node,确保它们是否是存活着的.
第二种,每个node都会去ping master node来确保master node是存活的,否则就会发起一个选举过程.
discovery.zen.fd.ping_interval:每隔多长时间会ping一次node,默认是1s
discovery.zen.fd.ping_timeout:每次ping的timeout等待时长是多长时间,默认是30s
discovery.zen.fd.ping_retries:如果一个node被ping多少次都失败了,就会认为node故障,默认是3次

mater挂掉后策略

all:一旦master当即,那么所有的操作都会被拒绝
write:这是默认的选项,所有的写操作都会被拒绝,但是读操作是被允许

每个node都会接收publish message,然后ack这个message,但是不会应用这个更新.
如果master没有在discovery.zen.commit_timeout指定的时间内(默认是30s),从至少discovery.zen.minimum_master_nodes个节点获取ack响应,那么这次cluster state change事件就会被reject,不会应用.

但是一旦在指定时间内,指定数量的node都返回了ack消息,那么cluster state就会被commit,然后一个message会被发送给所有的node.
所有的node接收到那个commit message之后,接着才会将之前接收到的集群状态应用到自己本地的状态副本中去.
接着master会等待所有节点再次响应是否更新自己本地副本状态成功,在一个等待超时时长内,如果接收到了响应,那么就会继续处理内存queue中保存的下一个更新状态.
discovery.zen.publish_timeout默认是30s,这个超时等待时长是从plublish cluster state开始计算的

https://zhouze-java.github.io/2019/01/25/Elasticsearch-103-%E9%9B%86%E7%BE%A4%E9%83%A8%E7%BD%B2%E5%8F%8Azen-discovery%E9%9B%86%E7%BE%A4%E5%8F%91%E7%8E%B0%E6%9C%BA%E5%88%B6/

几乎所有的API操作,比如index,delete,search,等等,都不是说client跟master通信,而是client跟任何一个node进行通信,那个node再将请求转发给对应的node来进行执行

discovery.zen.hosts_provider:file

然后以$ES_PATH_CONF/unicast_hosts.txt下面描述的格式创建文件。每当对unicast_hosts.txt文件进行更改时,Elasticsearch都会选择新的更改,并使用新的主机列表。

基于文件的发现插件会增强单播主机列表 elasticsearch.yml:如果存在有效的单播主机条目, discovery.zen.ping.unicast.hosts则除了提供的那些之外,还将使用它们 unicast_hosts.txt。

该discovery.zen.ping.unicast.resolve_timeout设置还适用于通过基于文件的发现由地址指定的节点的DNS查找。同样需要指定时间单位,默认为5秒

插件:
bigdesk 对集群状态监控
head:可视化操作集群
kibana:用于读取es中的索引库type信息,并使用可视化的方式展示出来。包含柱状图、饼状图、折线图、仪表盘等

shards:分片,每个索引默认5个分片 0,1,2,3,4 默认每个分片有一个副本
replicas:副本
recovery:恢复,重启数据引擎
gateway:网关
discovery.zen:集群管理
transport:客户端

https://learnku.com/docs/elasticsearch73/7.3/index-some-documents/6450
https://www.elastic.co/guide/en/elasticsearch/reference/7.9/getting-started-index.html

对比项目 Elatisticsearch Mongodb
索引(index) 数据库(database)
文档类型(es7已经不区分文档类型) 集合(collection)
节点/角色 Master:主节点,Master eligible:合格节点,Data:数据节点,Coordinating:协调节点,Ingest:ingest节点 configs,mongos,mongodb
集群方案 分片(副本) -> segment 分片(副本)-> chuck
分片方案 默认根据id hash后分片,可以指定路由分片 根据片键范围分片(必须),片键确定后无法修改
分片时机 创建索引时指定分片数量,且指定后不能更改 自动分片(Auto-Sharding),当分片到达一定的大小后自动分片
集群平衡 增加/下线节点、增加副本时集群自动平衡,集群平衡时只需要移动分片(主或副本)即可,主/副本可处于集群任一节点上 mongos会定期检查集群是否平衡,集群平衡分片的主和副本需要同时迁移,由于一个分片是一个副本集,所以一个分片的主/副本在固定的机器上
冷热分离 通过设置node.rack_id,node.attr.box_type标记冷节点和热节点,可以方便的将冷热数据分离 通过设置副本集的tag,可将分片数据均衡到指定副本集,可以方便实现冷热数据分离
分片方式 hash分片 范围分片、hash分片、组合分片、标签分片
mongodb可以配置读副本集的primary/Secondary,写数据时可以配置 Write Concern 保障读写数据的一致性

es 具有更好的扩容缩容能力,mongo可以添加分片,但是删除分片非常麻烦

CSRF概念

CSRF跨站点请求伪造(Cross—Site Request Forgery),攻击者利用用户合法身份(已经登陆认证并且存在session中),来发送恶意请求

CSRF攻击过程

  • 1.用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;
  • 2.在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;
  • 3.用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;
  • 4.网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;
    1. 浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行

防御CSRF攻击

  • 验证HTTP Referer字段(能够防范大部分攻击,但是Referer可能被篡改)
  • 在请求中添加token并验证(请求body中添加token,实施相对复杂)
  • 在HTTP头中自定义属性并验证(请求header中添加token,适用于ajax)
  • 认证信息不放在session中,如:把认证信息存放在 local Storage中
  • 提交验证码或者二次确认、

tomcat防御CSRF

tomcat 可以适用 CsrfPreventionFilter防御csrf攻击

写出高效代码

*《Effective Java》
*《代码整洁之道》
*《Java解惑》
*《Java编程思想》
*《深入理解计算机系统》