一、开篇
Redis作为目前通用的缓存选型,因其高性能而倍受欢迎。Redis的2.x版本仅支持单机模式,从3.0版本开始引入集群模式。
Redis的Java生态的客户端当中包含Jedis、Redisson、Lettuce,不同的客户端具备不同的能力是使用方式,本文主要分析Jedis客户端。
Jedis客户端同时支持单机模式、分片模式、集群模式的访问模式,通过构建Jedis类对象实现单机模式下的数据访问,通过构建ShardedJedis类对象实现分片模式的数据访问,通过构建JedisCluster类对象实现集群模式下的数据访问。
Jedis客户端支持单命令和Pipeline方式访问Redis集群,通过Pipeline的方式能够提高集群访问的效率。
本文的整体分析基于Jedis的3.5.0版本进行分析,相关源码均参考此版本。
二、Jedis访问模式对比
Jedis客户端操作Redis主要分为三种模式,分表是单机模式、分片模式、集群模式。
单机模式主要是创建Jedis对象来操作单节点的Redis,只适用于访问单个Redis节点。
分片模式(ShardedJedis)主要是通过创建ShardedJedisPool对象来访问分片模式的多个Redis节点,是Redis没有集群功能之前客户端实现的一个数据分布式方案,本质上是客户端通过一致性哈希来实现数据分布式存储。
集群模式(JedisCluster)主要是通过创建JedisCluster对象来访问集群模式下的多个Redis节点,是Redis3.0引入集群模式后客户端实现的集群访问访问,本质上是通过引入槽(slot)概念以及通过CRC16哈希槽算法来实现数据分布式存储。
单机模式不涉及任何分片的思想,所以我们着重分析分片模式和集群模式的理念。
2.1 分片模式
分片模式本质属于基于客户端的分片,在客户端实现如何根据一个key找到Redis集群中对应的节点的方案。
Jedis的客户端分片模式采用一致性Hash来实现,一致性Hash算法的好处是当Redis节点进行增减时只会影响新增或删除节点前后的小部分数据,相对于取模等算法来说对数据的影响范围较小。
Redis在大部分场景下作为缓存进行使用,所以不用考虑数据丢失致使缓存穿透造成的影响,在Redis节点增减时可以不用考虑部分数据无法命中的问题。
分片模式的整体应用如下图所示,核心在于客户端的一致性Hash策略。
(引用自:www.cnblogs.com)
2.2 集群模式
集群模式本质属于服务器分片技术,由Redis集群本身提供分片功能,从Redis 3.0版本开始正式提供。
集群的原理是:一个 Redis 集群包含16384 个哈希槽(Hash slot), Redis保存的每个键都属于这16384个哈希槽的其中一个, 集群使用公式CRC16(key)%16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键key的CRC16校验和 。
集群中的每个节点负责处理一部分哈希槽。举个例子, 一个集群可以有三个哈希槽, 其中:
节点 A 负责处理 0 号至 5500 号哈希槽。
节点 B 负责处理 5501 号至 11000 号哈希槽。
节点 C 负责处理 11001 号至 16383 号哈希槽。
Redis在集群模式下对于key的读写过程首先将对应的key值进行CRC16计算得到对应的哈希值,将哈希值对槽位总数取模映射到对应的槽位,最终映射到对应的节点进行读写。以命令set("key", "value")为例子,它会使用CRC16算法对key进行计算得到哈希值28989,然后对16384进行取模得到12605,最后找到12605对应的Redis节点,最终跳转到该节点执行set命令。
集群模式的整体应用如下图所示,核心在于集群哈希槽的设计以及重定向命令。
(引用自:www.jianshu.com)
三、Jedis的基础用法
// Jedis单机模式的访问
public void main(String[] args) {
// 创建Jedis对象
jedis = new Jedis("localhost", 6379);
// 执行hmget操作
jedis.hmget("foobar", "foo");
// 关闭Jedis对象
jedis.close();
}
// Jedis分片模式的访问
public void main(String[] args) {
HostAndPort redis1 = HostAndPortUtil.getRedisServers().get(0);
HostAndPort redis2 = HostAndPortUtil.getRedisServers().get(1);
List shards = new ArrayList(2);
JedisShardInfo shard1 = new JedisShardInfo(redis1);
JedisShardInfo shard2 = new JedisShardInfo(redis2);
// 创建ShardedJedis对象
ShardedJedis shardedJedis = new ShardedJedis(shards);
// 通过ShardedJedis对象执行set操作
shardedJedis.set("a", "bar");
}
// Jedis集群模式的访问
public void main(String[] args) {
// 构建redis的集群池
Set nodes = new HashSet<>();
nodes.add(new HostAndPort("127.0.0.1", 7001));
nodes.add(new HostAndPort("127.0.0.1", 7002));
nodes.add(new HostAndPort("127.0.0.1", 7003));
// 创建JedisCluster
JedisCluster cluster = new JedisCluster(nodes);
// 执行JedisCluster对象中的方法
cluster.set("cluster-test", "my jedis cluster test");
String result = cluster.get("cluster-test");
}
Jedis通过创建Jedis的类对象来实现单机模式下的数据访问,通过构建JedisCluster类对象来实现集群模式下的数据访问。
要理解Jedis的访问Redis的整个过程,可以通过先理解单机模式下的访问流程,在这个基础上再分析集群模式的访问流程会比较合适。
四、Jedis单机模式的访问
Jedis访问单机模式Redis的整体流程图如下所示,从图中可以看出核心的流程包含Jedis对象的创建以及通过Jedis对象实现Redis的访问。
熟悉Jedis访问单机Redis的过程,本身就是需要了解Jedis的创建过程以及执行Redis命令的过程。
4.1 创建过程
Jedis本身的类关系图如下图所示,从图中我们能够看到Jedis继承自BinaryJedis类。
在BinaryJedis类中存在和Redis对接的Client类对象,Jedis通过父类的BinaryJedis的Client对象实现Redis的读写。
Jedis类在创建过程中通过父类BinaryJedis创建了Client对象,而了解Client对象是进一步理解访问过程的关键。
public class Jedis extends BinaryJedis implements JedisCommands, MultiKeyCommands,
AdvancedJedisCommands, ScriptingCommands, BasicCommands, ClusterCommands, SentinelCommands, ModuleCommands {
protected JedisPoolAbstract dataSource = null;
public Jedis(final String host, final int port) {
// 创建父类BinaryJedis对象
super(host, port);
}
}
public class BinaryJedis implements BasicCommands, BinaryJedisCommands, MultiKeyBinaryCommands,
AdvancedBinaryJedisCommands, BinaryScriptingCommands, Closeable {
// 访问redis的Client对象
protected Client client = null;
public BinaryJedis(final String host, final int port) {
// 创建Client对象访问redis
client = new Client(host, port);
}
}
Client类的类关系图如下图所示,Client对象继承自BinaryClient和Connection类。在BinaryClient类中存在Redis访问密码等相关参数,在Connection类在存在访问Redis的socket对象以及对应的输入输出流。本质上Connection是和Redis进行通信的核心类。
Client类在创建过程中初始化核心父类Connection对象,而Connection是负责和Redis直接进行通信。
public class Client extends BinaryClient implements Commands {
public Client(final String host, final int port) {
super(host, port);
}
}
public class BinaryClient extends Connection {
// 存储和Redis连接的相关信息
private boolean isInMulti;
private String user;
private String password;
private int db;
private boolean isInWatch;
public BinaryClient(final String host, final int port) {
super(host, port);
}
}
public class Connection implements Closeable {
// 管理和Redis连接的socket信息及对应的输入输出流
private JedisSocketFactory jedisSocketFactory;
private Socket socket;
private RedisOutputStream outputStream;
private RedisInputStream inputStream;
private int infiniteSoTimeout = 0;
private boolean broken = false;
public Connection(final String host, final int port, final boolean ssl,
SSLSocketFactory sslSocketFactory, SSLParameters sslParameters,
HostnameVerifier hostnameVerifier) {
// 构建DefaultJedisSocketFactory来创建和Redis连接的Socket对象
this(new DefaultJedisSocketFactory(host, port, Protocol.DEFAULT_TIMEOUT,
Protocol.DEFAULT_TIMEOUT, ssl, sslSocketFactory, sslParameters, hostnameVerifier));
}
}
4.2 访问过程
以Jedis执行set命令为例,整个过程如下:
public class Jedis extends BinaryJedis implements JedisCommands, MultiKeyCommands,
AdvancedJedisCommands, ScriptingCommands, BasicCommands, ClusterCommands, SentinelCommands, ModuleCommands {
@Override
public String set(final String key, final String value) {
checkIsInMultiOrPipeline();
// client执行set操作
client.set(key, value);
return client.getStatusCodeReply();
}
}
public class Client extends BinaryClient implements Commands {
@Override
public void set(final String key, final String value) {
// 执行set命令
set(SafeEncoder.encode(key), SafeEncoder.encode(value));
}
}
public class BinaryClient extends Connection {
public void set(final byte[] key, final byte[] value) {
// 发送set指令
sendCommand(SET, key, value);
}
}
public class Connection implements Closeable {
public void sendCommand(final ProtocolCommand cmd, final byte[]... args) {
try {
// socket连接redis
connect();
// 按照redis的协议发送命令
Protocol.sendCommand(outputStream, cmd, args);
} catch (JedisConnectionException ex) {
}
}
}
五、Jedis分片模式的访问
基于前面已经介绍的Redis分片模式的一致性Hash的原理来理解Jedis的分片模式的访问。
关于Redis分片模式的概念:Redis在3.0版本之前没有集群模式的概念,这导致单节点能够存储的数据有限,通过Redis的客户端如Jedis在客户端通过一致性Hash算法来实现数据的分片存储。
本质上Redis的分片模式跟Redis本身没有任何关系,只是通过客户端来解决单节点数据有限存储的问题。
ShardedJedis访问Redis的核心在于构建对象的时候初始化一致性Hash对象,构建一致性Hash经典的Hash值和node的映射关系。构建完映射关系后执行set等操作就是Hash值到node的寻址过程,寻址完成后直接进行单节点的操作。
5.1 创建过程
ShardedJedis的创建过程在于父类的Sharded中关于一致性Hash相关的初始化过程,核心在于构建一致性的虚拟节点以及虚拟节点和Redis节点的映射关系。
源码中最核心的部分代码在于根据根据权重映射成未160个虚拟节点,通过虚拟节点来定位到具体的Redis节点。
public class Sharded> {
public static final int DEFAULT_WEIGHT = 1;
// 保存虚拟节点和redis的node节点的映射关系
private TreeMap nodes;
// hash算法
private final Hashing algo;
// 保存redis节点和访问该节点的Jedis的连接信息
private final Map, R> resources = new LinkedHashMap<>();
public Sharded(List shards, Hashing algo) {
this.algo = algo;
initialize(shards);
}
private void initialize(List shards) {
nodes = new TreeMap<>();
// 遍历每个redis的节点并设置hash值到节点的映射关系
for (int i = 0; i != shards.size(); ++i) {
final S shardInfo = shards.get(i);
// 根据权重映射成未160个虚拟节点
int N = 160 * shardInfo.getWeight();
if (shardInfo.getName() == null) for (int n = 0; n < N; n++) {
// 构建hash值和节点映射关系
nodes.put(this.algo.hash("SHARD-" + i + "-NODE-" + n), shardInfo);
}
else for (int n = 0; n < N; n++) {
nodes.put(this.algo.hash(shardInfo.getName() + "*" + n), shardInfo);
}
// 保存每个节点的访问对象
resources.put(shardInfo, shardInfo.createResource());
}
}
}
5.2 访问过程
ShardedJedis的访问过程就是一致性Hash的计算过程,核心的逻辑就是:通过Hash算法对访问的key进行Hash计算生成Hash值,根据Hash值获取对应Redis节点,根据对应的Redis节点获取对应的访问对象Jedis。
获取访问对象Jedis之后就可以直接进行命令操作。
public class Sharded> {
public static final int DEFAULT_WEIGHT = 1;
private TreeMap nodes;
private final Hashing algo;
// 保存redis节点和访问该节点的Jedis的连接信息
private final Map, R> resources = new LinkedHashMap<>();
public R getShard(String key) {
// 根据redis节点找到对应的访问对象Jedis
return resources.get(getShardInfo(key));
}
public S getShardInfo(String key) {
return getShardInfo(SafeEncoder.encode(getKeyTag(key)));
}
public S getShardInfo(byte[] key) {
// 针对访问的key生成对应的hash值
// 根据hash值找到对应的redis节点
SortedMap tail = nodes.tailMap(algo.hash(key));
if (tail.isEmpty()) {
return nodes.get(nodes.firstKey());
}
return tail.get(tail.firstKey());
}
}
六、Jedis集群模式的访问
基于前面介绍的Redis的集群原理来理解Jedis的集群模式的访问。
Jedis能够实现key和哈希槽的定位的核心机制在于哈希槽和Redis节点的映射,而这个发现过程基于Redis的cluster slot命令。
关于Redis集群操作的命令:Redis通过cluster slots会返回Redis集群的整体状况。返回每一个Redis节点的信息包含:
127.0.0.1:30001> cluster slots
1) 1) (integer) 0 // 开始槽位
2) (integer) 5460 // 结束槽位
3) 1) "127.0.0.1" // master节点的host
2) (integer) 30001 // master节点的port
3) "09dbe9720cda62f7865eabc5fd8857c5d2678366" // 节点的编码
4) 1) "127.0.0.1" // slave节点的host
2) (integer) 30004 // slave节点的port
3) "821d8ca00d7ccf931ed3ffc7e3db0599d2271abf" // 节点的编码
2) 1) (integer) 5461
2) (integer) 10922
3) 1) "127.0.0.1"
2) (integer) 30002
3) "c9d93d9f2c0c524ff34cc11838c2003d8c29e013"
4) 1) "127.0.0.1"
2) (integer) 30005
3) "faadb3eb99009de4ab72ad6b6ed87634c7ee410f"
3) 1) (integer) 10923
2) (integer) 16383
3) 1) "127.0.0.1"
2) (integer) 30003
3) "044ec91f325b7595e76dbcb18cc688b6a5b434a1"
4) 1) "127.0.0.1"
2) (integer) 30006
3) "58e6e48d41228013e5d9c1c37c5060693925e97e"
Jedis访问集群模式Redis的整体流程图如下所示,从图中可以看出核心的流程包含JedisCluster对象的创建以及通过JedisCluster对象实现Redis的访问。
JedisCluster对象的创建核心在于创建JedisClusterInfoCache对象并通过集群发现来建立slot和集群节点的映射关系。
JedisCluster对Redis集群的访问在于获取key所在的Redis节点并通过Jedis对象进行访问。
6.1 创建过程
JedisCluster的类关系如下图所示,在图中可以看到核心变量JedisSlotBasedConnectionHandler对象。
JedisCluster的父类BinaryJedisCluster创建了JedisSlotBasedConnectionHandler对象,该对象负责和Redis的集群进行通信。
public class JedisCluster extends BinaryJedisCluster implements JedisClusterCommands,
MultiKeyJedisClusterCommands, JedisClusterScriptingCommands {
public JedisCluster(Set jedisClusterNode, int connectionTimeout, int soTimeout,
int maxAttempts, String password, String clientName, final GenericObjectPoolConfig poolConfig,
boolean ssl, SSLSocketFactory sslSocketFactory, SSLParameters sslParameters,
HostnameVerifier hostnameVerifier, JedisClusterHostAndPortMap hostAndPortMap) {
// 访问父类BinaryJedisCluster
super(jedisClusterNode, connectionTimeout, soTimeout, maxAttempts, password, clientName, poolConfig,
ssl, sslSocketFactory, sslParameters, hostnameVerifier, hostAndPortMap);
}
}
public class BinaryJedisCluster implements BinaryJedisClusterCommands,
MultiKeyBinaryJedisClusterCommands, JedisClusterBinaryScriptingCommands, Closeable {
public BinaryJedisCluster(Set jedisClusterNode, int connectionTimeout, int soTimeout,
int maxAttempts, String user, String password, String clientName, GenericObjectPoolConfig poolConfig,
boolean ssl, SSLSocketFactory sslSocketFactory, SSLParameters sslParameters,
HostnameVerifier hostnameVerifier, JedisClusterHostAndPortMap hostAndPortMap) {
// 创建JedisSlotBasedConnectionHandler对象
this.connectionHandler = new JedisSlotBasedConnectionHandler(jedisClusterNode, poolConfig,
connectionTimeout, soTimeout, user, password, clientName, ssl, sslSocketFactory, sslParameters, hostnameVerifier, hostAndPortMap);
this.maxAttempts = maxAttempts;
}
}
JedisSlotBasedConnectionHandler的核心在于创建并初始化JedisClusterInfoCache对象,该对象缓存了Redis集群的信息。
JedisClusterInfoCache对象的初始化过程通过initializeSlotsCache来完成,主要目的用于实现集群节点和槽位发现。
public class JedisSlotBasedConnectionHandler extends JedisClusterConnectionHandler {
public JedisSlotBasedConnectionHandler(Set nodes, GenericObjectPoolConfig poolConfig,
int connectionTimeout, int soTimeout, String user, String password, String clientName,
boolean ssl, SSLSocketFactory sslSocketFactory, SSLParameters sslParameters,
HostnameVerifier hostnameVerifier, JedisClusterHostAndPortMap portMap) {
super(nodes, poolConfig, connectionTimeout, soTimeout, user, password, clientName,
ssl, sslSocketFactory, sslParameters, hostnameVerifier, portMap);
}
}
public abstract class JedisClusterConnectionHandler implements Closeable {
public JedisClusterConnectionHandler(Set nodes, final GenericObjectPoolConfig poolConfig,
int connectionTimeout, int soTimeout, int infiniteSoTimeout, String user, String password, String clientName,
boolean ssl, SSLSocketFactory sslSocketFactory, SSLParameters sslParameters,
HostnameVerifier hostnameVerifier, JedisClusterHostAndPortMap portMap) {
// 创建JedisClusterInfoCache对象
this.cache = new JedisClusterInfoCache(poolConfig, connectionTimeout, soTimeout, infiniteSoTimeout,
user, password, clientName, ssl, sslSocketFactory, sslParameters, hostnameVerifier, portMap);
// 初始化jedis的Slot信息
initializeSlotsCache(nodes, connectionTimeout, soTimeout, infiniteSoTimeout,
user, password, clientName, ssl, sslSocketFactory, sslParameters, hostnameVerifier);
}
private void initializeSlotsCache(Set startNodes,
int connectionTimeout, int soTimeout, int infiniteSoTimeout, String user, String password, String clientName,
boolean ssl, SSLSocketFactory sslSocketFactory, SSLParameters sslParameters, HostnameVerifier hostnameVerifier) {
for (HostAndPort hostAndPort : startNodes) {
try (Jedis jedis = new Jedis(hostAndPort.getHost(), hostAndPort.getPort(), connectionTimeout,
soTimeout, infiniteSoTimeout, ssl, sslSocketFactory, sslParameters, hostnameVerifier)) {
// 通过discoverClusterNodesAndSlots进行集群发现
cache.discoverClusterNodesAndSlots(jedis);
return;
} catch (JedisConnectionException e) {
}
}
}
}
JedisClusterInfoCache的nodes用来保存Redis集群的节点信息,slots用来保存槽位和集群节点的信息。
nodes和slots维持的对象都是JedisPool对象,该对象维持了和Redis的连接信息。集群的发现过程由discoverClusterNodesAndSlots来实现,本质是执行Redis的集群发现命令cluster slots实现的。
public class JedisClusterInfoCache {
// 负责保存redis集群的节点信息
private final Map nodes = new HashMap<>();
// 负责保存redis的槽位和redis节点的映射关系
private final Map slots = new HashMap<>();
// 负责集群的发现逻辑
public void discoverClusterNodesAndSlots(Jedis jedis) {
w.lock();
try {
reset();
List slots = jedis.clusterSlots();
for (Object slotInfoObj : slots) {
List slotInfo = (List) slotInfoObj;
if (slotInfo.size() <= MASTER_NODE_INDEX) {
continue;
}
// 获取redis节点对应的槽位信息
List slotNums = getAssignedSlotArray(slotInfo);
// hostInfos
int size = slotInfo.size();
for (int i = MASTER_NODE_INDEX; i < size; i++) {
List hostInfos = (List) slotInfo.get(i);
if (hostInfos.isEmpty()) {
continue;
}
HostAndPort targetNode = generateHostAndPort(hostInfos);
// 负责保存redis节点信息
setupNodeIfNotExist(targetNode);
if (i == MASTER_NODE_INDEX) {
// 负责保存槽位和redis节点的映射关系
assignSlotsToNode(slotNums, targetNode);
}
}
}
} finally {
w.unlock();
}
}
public void assignSlotsToNode(List targetSlots, HostAndPort targetNode) {
w.lock();
try {
JedisPool targetPool = setupNodeIfNotExist(targetNode);
// 保存槽位和对应的JedisPool对象
for (Integer slot : targetSlots) {
slots.put(slot, targetPool);
}
} finally {
w.unlock();
}
}
public JedisPool setupNodeIfNotExist(HostAndPort node) {
w.lock();
try {
// 生产redis节点对应的nodeKey
String nodeKey = getNodeKey(node);
JedisPool existingPool = nodes.get(nodeKey);
if (existingPool != null) return existingPool;
// 生产redis节点对应的JedisPool
JedisPool nodePool = new JedisPool(poolConfig, node.getHost(), node.getPort(),
connectionTimeout, soTimeout, infiniteSoTimeout, user, password, 0, clientName,
ssl, sslSocketFactory, sslParameters, hostnameVerifier);
// 保存redis节点的key和对应的JedisPool对象
nodes.put(nodeKey, nodePool);
return nodePool;
} finally {
w.unlock();
}
}
}
JedisPool的类关系如下图所示,其中内部internalPool是通过apache common pool来实现的池化。
JedisPool内部的internalPool通过JedisFactory的makeObject来创建Jedis对象。
每个Redis节点都会对应一个JedisPool对象,通过JedisPool来管理Jedis的申请释放复用等。
public class JedisPool extends JedisPoolAbstract {
public JedisPool() {
this(Protocol.DEFAULT_HOST, Protocol.DEFAULT_PORT);
}
}
public class JedisPoolAbstract extends Pool {
public JedisPoolAbstract() {
super();
}
}
public abstract class Pool implements Closeable {
protected GenericObjectPool internalPool;
public void initPool(final GenericObjectPoolConfig poolConfig, PooledObjectFactory factory) {
if (this.internalPool != null) {
try {
closeInternalPool();
} catch (Exception e) {
}
}
this.internalPool = new GenericObjectPool<>(factory, poolConfig);
}
}
class JedisFactory implements PooledObjectFactory {
@Override
public PooledObject makeObject() throws Exception {
// 创建Jedis对象
final HostAndPort hp = this.hostAndPort.get();
final Jedis jedis = new Jedis(hp.getHost(), hp.getPort(), connectionTimeout, soTimeout,
infiniteSoTimeout, ssl, sslSocketFactory, sslParameters, hostnameVerifier);
try {
// Jedis对象连接
jedis.connect();
if (user != null) {
jedis.auth(user, password);
} else if (password != null) {
jedis.auth(password);
}
if (database != 0) {
jedis.select(database);
}
if (clientName != null) {
jedis.clientSetname(clientName);
}
} catch (JedisException je) {
jedis.close();
throw je;
}
// 将Jedis对象包装成DefaultPooledObject进行返回
return new DefaultPooledObject<>(jedis);
}
}
6.2 访问过程
JedisCluster访问Redis的过程通过JedisClusterCommand来实现重试机制,最终通过Jedis对象来实现访问。从实现的角度来说JedisCluster是在Jedis之上封装了一层,进行集群节点定位以及重试机制等。
以set命令为例,整个访问通过JedisClusterCommand实现如下:
计算key所在的Redis节点。
获取Redis节点对应的Jedis对象。
通过Jedis对象进行set操作。
public class JedisCluster extends BinaryJedisCluster implements JedisClusterCommands,
MultiKeyJedisClusterCommands, JedisClusterScriptingCommands {
@Override
public String set(final String key, final String value, final SetParams params) {
return new JedisClusterCommand(connectionHandler, maxAttempts) {
@Override
public String execute(Jedis connection) {
return connection.set(key, value, params);
}
}.run(key);
}
}
JedisClusterCommand的run方法核心主要定位Redis的key所在的Redis节点,然后获取与该节点对应的Jedis对象进行访问。
在Jedis对象访问异常后,JedisClusterCommand会进行重试操作并按照一定策略执行renewSlotCache方法进行重集群节点重发现动作。
public abstract class JedisClusterCommand {
public T run(String key) {
// 针对key进行槽位的计算
return runWithRetries(JedisClusterCRC16.getSlot(key), this.maxAttempts, false, null);
}
private T runWithRetries(final int slot, int attempts, boolean tryRandomNode, JedisRedirectionException redirect) {
Jedis connection = null;
try {
if (redirect != null) {
connection = this.connectionHandler.getConnectionFromNode(redirect.getTargetNode());
if (redirect instanceof JedisAskDataException) {
connection.asking();
}
} else {
if (tryRandomNode) {
connection = connectionHandler.getConnection();
} else {
// 根据slot去获取Jedis对象
connection = connectionHandler.getConnectionFromSlot(slot);
}
}
// 执行真正的Redis的命令
return execute(connection);
} catch (JedisNoReachableClusterNodeException jnrcne) {
throw jnrcne;
} catch (JedisConnectionException jce) {
releaseConnection(connection);
connection = null;
if (attempts <= 1) {
// 保证最后两次机会去重新刷新槽位和节点的对应的信息
this.connectionHandler.renewSlotCache();
}
// 按照重试次数进行重试操作
return runWithRetries(slot, attempts - 1, tryRandomNode, redirect);
} catch (JedisRedirectionException jre) {
// 针对返回Move命令立即触发重新刷新槽位和节点的对应信息
if (jre instanceof JedisMovedDataException) {
// it rebuilds cluster's slot cache recommended by Redis cluster specification
this.connectionHandler.renewSlotCache(connection);
}
releaseConnection(connection);
connection = null;
return runWithRetries(slot, attempts - 1, false, jre);
} finally {
releaseConnection(connection);
}
}
}
JedisSlotBasedConnectionHandler的cache对象维持了slot和node的映射关系,通过getConnectionFromSlot方法来获取该slot对应的Jedis对象。
public class JedisSlotBasedConnectionHandler extends JedisClusterConnectionHandler {
protected final JedisClusterInfoCache cache;
@Override
public Jedis getConnectionFromSlot(int slot) {
// 获取槽位对应的JedisPool对象
JedisPool connectionPool = cache.getSlotPool(slot);
if (connectionPool != null) {
// 从JedisPool对象中获取Jedis对象
return connectionPool.getResource();
} else {
// 获取失败就重新刷新槽位信息
renewSlotCache();
connectionPool = cache.getSlotPool(slot);
if (connectionPool != null) {
return connectionPool.getResource();
} else {
//no choice, fallback to new connection to random node
return getConnection();
}
}
}
}
七、Jedis的Pipeline实现
Pipeline的技术核心思想是将多个命令发送到服务器而不用等待回复,最后在一个步骤中读取该答复。这种模式的好处在于节省了请求响应这种模式的网络开销。
Redis的普通命令如set和Pipeline批量操作的核心的差别在于set命令的操作会直接发送请求到Redis并同步等待结果返回,而Pipeline的操作会发送请求但不立即同步等待结果返回,具体的实现可以从Jedis的源码一探究竟。
原生的Pipeline在集群模式下相关的key必须Hash到同一个节点才能生效,原因在于Pipeline下的Client对象只能其中的一个节点建立了连接。
在集群模式下归属于不同节点的key能够使用Pipeline就需要针对每个key保存对应的节点的client对象,在最后执行获取数据的时候一并获取。本质上可以认为在单节点的Pipeline的基础上封装成一个集群式的Pipeline。
7.1 Pipeline用法分析
Pipeline访问单节点的Redis的时候,通过Jedis对象的Pipeline方法返回Pipeline对象,其他的命令操作通过该Pipeline对象进行访问。
Pipeline从使用角度来分析,会批量发送多个命令并最后统一使用syncAndReturnAll来一次性返回结果。
public void pipeline() {
jedis = new Jedis(hnp.getHost(), hnp.getPort(), 500);
Pipeline p = jedis.pipelined();
// 批量发送命令到redis
p.set("foo", "bar");
p.get("foo");
// 同步等待响应结果
List results = p.syncAndReturnAll();
assertEquals(2, results.size());
assertEquals("OK", results.get(0));
assertEquals("bar", results.get(1));
}
public abstract class PipelineBase extends Queable implements BinaryRedisPipeline, RedisPipeline {
@Override
public Response set(final String key, final String value) {
// 发送命令
getClient(key).set(key, value);
// pipeline的getResponse只是把待响应的请求聚合到pipelinedResponses对象当中
return getResponse(BuilderFactory.STRING);
}
}
public class Queable {
private Queue> pipelinedResponses = new LinkedList<>();
protected Response getResponse(Builder builder) {
Response lr = new Response<>(builder);
// 统一保存到响应队列当中
pipelinedResponses.add(lr);
return lr;
}
}
public class Pipeline extends MultiKeyPipelineBase implements Closeable {
public List syncAndReturnAll() {
if (getPipelinedResponseLength() > 0) {
// 根据批量发送命令的个数即需要批量返回命令的个数,通过client对象进行批量读取
List unformatted = client.getMany(getPipelinedResponseLength());
List formatted = new ArrayList<>();
for (Object o : unformatted) {
try {
// 格式化每个返回的结果并最终保存在列表中进行返回
formatted.add(generateResponse(o).get());
} catch (JedisDataException e) {
formatted.add(e);
}
}
return formatted;
} else {
return java.util.Collections. emptyList();
}
}
}
普通set命令发送请求给Redis后立即通过getStatusCodeReply来获取响应结果,所以这是一种请求响应的模式。
getStatusCodeReply在获取响应结果的时候会通过flush()命令强制发送报文到Redis服务端然后通过读取响应结果。
public class BinaryJedis implements BasicCommands, BinaryJedisCommands, MultiKeyBinaryCommands,
AdvancedBinaryJedisCommands, BinaryScriptingCommands, Closeable {
@Override
public String set(final byte[] key, final byte[] value) {
checkIsInMultiOrPipeline();
// 发送命令
client.set(key, value);
// 等待请求响应
return client.getStatusCodeReply();
}
}
public class Connection implements Closeable {
public String getStatusCodeReply() {
// 通过flush立即发送请求
flush();
// 处理响应请求
final byte[] resp = (byte[]) readProtocolWithCheckingBroken();
if (null == resp) {
return null;
} else {
return SafeEncoder.encode(resp);
}
}
}
public class Connection implements Closeable {
protected void flush() {
try {
// 针对输出流进行flush操作保证报文的发出
outputStream.flush();
} catch (IOException ex) {
broken = true;
throw new JedisConnectionException(ex);
}
}
}
八、结束语
Jedis作为Redis官方首选的Java客户端开发包,支持绝大部分的Redis的命令,也是日常中使用较多的Redis客户端。
了解了Jedis的实现原理,除了能够支持Redis的日常操作外,还能更好的应对Redis的额外操作诸如扩容时的技术选型。
通过介绍Jedis针对单机模式、分配模式、集群模式三种场景访问方式,让大家有个从宏观到微观的理解过程,掌握Jedis的核心思想并更好的应用到实践当中。
作者:vivo互联网服务器团队-Wang Zhi
你可能感兴趣的:(深入剖析Redis客户端Jedis的特性和原理)
python常用的第三方库下载,python常用第三方库下载
w666666Wwwwwww
python
大家好,给大家分享一下python常用的第三方库下载,很多人还不知道这一点。下面详细解释一下。现在让我们来看看!以下步骤只适用于winds系统1.先去下载所需要的库,注意一定要符合自己所下载Python的版本,就是说版本一定要相同。https://www.lfd.uci.edu/~gohlke/pythonlibs/比如我下载的是python3.7,需要下载的numpy就是numpy37,再根据自
程序员做二次开发时应该注意哪些方面?
help-assignment
日常闲聊 日常闲聊
进行二次开发时,程序员需要注意以下几个方面,以确保项目顺利进行并达到预期目标:1.理解原始系统全面了解系统架构:熟悉原始系统的整体架构、数据流和核心功能,以便更好地进行改进和扩展。阅读文档:仔细阅读原始系统的设计文档和用户手册,了解系统的功能和限制。代码分析:对原始代码进行详细分析,了解其结构、逻辑和依赖关系。2.保持代码一致性编码规范:遵循原始系统的编码规范和风格,确保代码一致性,便于维护和扩展
【音视频】如何对wav音频文件进行opus编解码?
川弥
音视频 python 视频编解码 bash linux ffmpeg 音频 数据分析
目录前言一、opus编码二、方法1.在Linux下,使用ffmpeg命令行首先,安装FFmpeg编码WAV文件为Opus解码Opus文件回WAV修改参数2.使用bash脚本3.使用Python库函数总结前言详细的opus编解码过程一、opus编码Opus是一种常用的开放源代码的音频压缩格式,由Internet工程任务组(IETF)制定。它旨在提供高质量的语音和音乐传输服务,特别是在网络带宽受限的情
【STM32进阶笔记】FATFS文件系统(上)
二土电子
STM32进阶笔记 stm32 笔记 嵌入式硬件 FAT 文件系统
本专栏争取每周三更新直到更新完成,期待大家的订阅关注,欢迎互相学习交流。 本文需要一些SD卡的前置知识,后续文章会介绍,这里先介绍一下FATFS文件系统。关于FATFS的文章分为上下两篇,上篇主要介绍什么是FAT文件系统以及FATFS的移植,下篇主要介绍FATFS的一些API函数。目录一、FATFS文件系统简介1.1FATFS引入1.2FATFS特点二、FATFS文件系统移植2.1FATFS
Linux系统上同时打印到物理打印机并生成PDF副本方法研究
码农君莫笑
Linux 打印 国产化 chrome 前端 打印 linux 信管通
在Linux系统上,想要同时打印到物理打印机并生成PDF副本,可以使用CUPS(通用Unix打印系统)和虚拟PDF打印机结合的方式。以下是几种可行的方案:方法1:使用CUPS设置多个打印机(物理+PDF)CUPS(CommonUnixPrintingSystem)是Linux默认的打印服务,支持同时向多个打印机发送任务。步骤安装CUPS(如果未安装)在大多数Linux发行版上,CUPS默认已安装。
马斯克-全球最大算力集群-grok3效果任何
数据分析能量站
机器学习 人工智能
就在刚刚,科技界巨头埃隆・马斯克正式揭晓了x.AI旗下的最新力作——Grok3。一经发布,Grok3便凭借其卓越表现,被赞誉为全球范围内最具智慧与力量的人工智能。(有待继续观察)作为x.AI精心打造的新型聊天机器人,Grok3展现出了令人惊叹的推理天赋,面对复杂问题时,能够凭借严密逻辑抽丝剥茧,给出精准解答。不仅如此,它还配备了如DeepSearch(深度搜索)这般的前沿功能,让信息获取与知识挖掘
使用LangChain加载College Confidential网页数据
qahaj
langchain 前端 javascript python
在本文中,我们将介绍如何使用LangChain加载CollegeConfidential的数据并将其转换为我们可以下游使用的文档格式。我们将详细讲解如何使用CollegeConfidentialLoader进行网页内容加载,并结合一些示例代码,帮助你快速上手。技术背景介绍CollegeConfidential是一个提供超过3800所大学和学院信息的平台。对于需要从这个平台获取数据以便后续处理和分析
windows10安装Docker部署Jar包
Love_Erlc
springboot Docker spring boot java docker 后端
windows10安装Docker部署Jar包文章目录windows10安装Docker部署Jar包步骤:一、拉取Jdk基础镜像二、创建文件夹存放Dockerfile文件和需要部署的Jar包,我创建在D盘,文件夹为/usr/local/webapps三、在刚才创建的/usr/local/webapps文件夹下创建Dockerfile文件并用记事本打开Dockerfile文件编辑需要填写的命令四、根
windows10安装Docker部署Jar包并更新Jar包部署详解
Love_Erlc
Docker springboot spring boot docker idea 后端 java
windows10安装Docker部署Jar包并更新Jar包部署文章目录windows10安装Docker部署Jar包并更新Jar包部署步骤一、生成Dockerfile文件二、编辑Dockerfile文件三、构建镜像四、创建容器五、运行查看结果六、修改项目重新生成jar包,替换原来C:\Users\64641\jar目录下的jar包七、停止容器并启动容器八、查看运行结果步骤一、生成Dockerfi
wav文件详解
满舅娘
wav文件详解分类:视频音频图像处理算法2013-10-1013:5066人阅读评论(0)收藏举报音频wav格式介绍ffmpeg目录(?)[+]工具我们这里使用的工具有ffmpeg,cooledit,ultraedit。音频文件我们以这音频文件为介绍例子文件链接http://pan.baidu.com/s/1j6fbt
Android如何将采集到的音频PCM文件转为WAV并保存
不会写代码的猴子
ffmpeg工具使用 Android 音视频 android 音视频
1.Android音频采集添加权限动态申请权限PermissionX是一个用于处理Android运行时权限的框架。它的目的是简化和标准化处理运行时权限的申请、拒绝和永久拒绝等操作,让开发者可以更轻松地管理权限。PermissionX通过在每个系统版本上进行额外的适配,确保了对于不同版本的Android系统都能进行有效的权限处理。例如,从Android13开始,READ_EXTERNAL_STORA
现代游戏音频系统架构深度解析——以AudioSystemController为核心的沉浸式声效管理方案
晴空了无痕
项目框架 游戏 架构
一、架构全景与设计哲学本文将以重构后的AudioSystemController为核心,深入探讨基于FMOD引擎的高性能音频管理系统设计。该体系采用"分层-事件驱动"架构,通过多轨道混音管理、动态资源加载、空间音频处理三大核心模块,构建了适应复杂游戏场景的声效解决方案。我们将从以下三个维度展开技术分析:二、核心模块技术解析2.1中枢控制器(AudioSystemController)namespa
priority_queue 的使用 —— 求第 k 小的和
黄铎彦
大二下学期 算法 数据结构 c++
原题再现其实一想到第k小,马上就要想到priority_queue!结果,我第一版交了个C语言版本上去。一开始的思路想通过直接操纵a、b两个数组的下标来输出,但是我考虑得太简单了!认真一想发现这个操纵规则我自己也没搞懂。接下来我依然没考虑到priority_queue。我使用了list,试图在双层嵌套循环中每次都find_if,然后插入,并控制元素个数不超过,最后返回list::back()。马上
对于Windows 11备份和还原的探索
黄铎彦
大一上学期 windows
前言本来我的安全意识不足,认为自己的电脑随便玩都没事。但是几天之前就出事了。当我想打开威睿工作站(VMwareWorkstation)的Linux虚拟机的时候,发现它开不起来了!威睿的图标下面的圈子一直在转,转了好几分钟都没办法。重装威睿也没办法!于是,我就用了华为的F10恢复出厂设置。这一恢复可不得了。所有软件都得重装!像微信、QQ之类零零碎碎的软件不说,特别是VS,得等半天,真的耗不起!于是我
【C++第二十章】红黑树
A.A呐
C++ c++ 开发语言
【C++第二十章】红黑树红黑树介绍红黑树是一种自平衡的二叉搜索树,通过颜色标记和特定规则保持树的平衡性,从而在动态插入、删除等操作中维持较高的效率。它的最长路径不会超过最短路径的两倍,它的查找效率比AVL树更慢(对于CPU来说可以忽略不计),但是它不会像AVL树那样花费更大的代价去实现严格平衡(旋转)。1.红黑树与AVL树特性红黑树AVL树平衡标准通过颜色规则约束,允许一定不平衡严格平衡(左右子树
【Java】Java 常用核心类篇 —— 基本类型包装类
hrrrrb
# Java 基础 java 开发语言
目录基本类型包装类概念和作用主要特点和功能使用场景Byte类常量构造方法常用方法类型转换方法字符串相关方法比较方法Short类常量构造方法常用方法类型转换方法字符串相关方法比较方法Integer类常量构造方法常用方法类型转换方法字符串相关方法位操作方法比较方法Long类常量构造方法常用方法类型转换方法字符串相关方法位操作方法比较方法Double类常量构造方法常用方法类型转换方法字符串相关方法比较方
RabbitMQ消息堆积导致服务崩溃的急救手册:三步止血法+根治方案
Java侥幸弟
性能优化 stable diffusion
“凌晨3点,RabbitMQ队列飙到100万条,服务直接瘫痪!”——这是某电商平台技术负责人上周的真实经历。消息堆积引发的雪崩效应,轻则业务卡顿,重则数据丢失。今天这篇实战指南,手把手教你从紧急止血到根治优化,让崩溃的MQ服务快速“起死回生”!一、紧急止血:三步让服务先活过来当监控报警显示队列积压量突破天际,服务已崩溃或即将崩溃时,先做这三件事:1.立即暂停生产者(断流)操作:临时关闭消息生产者或
全面披露!华为云分布式云原生技术与实践
CSDN云原生
云原生 华为云 分布式
出品|CSDN云原生随着云原生应用深入企业各个业务场景,云原生正在走向分布式,跨云跨地域统一协同治理,保证一致应用体验等新的需求日渐突出。分布式云原生都涉及哪些核心技术?有哪些典型的应用场景?值得我们去探究。HCDE(HuaweiCloudDeveloperExperts)是经华为云认证的熟悉一种或多种华为云开放能力,并对赋能全球开发者有突出贡献的个人,旨在帮助全球开发者成长,构建全球开发者生态。
如何解决fork: retry: 资源暂时不可用
醉心编码
shell基础知识及技巧 linux 服务器 运维
当出现/bin/sh:fork:retry:资源暂时不可用这样的报错时,说明Linux的openfile最大连接数已满。解决的方法如下:1)修改/etc/security/limits.d/90-nproc.conf,将npoc设置最大。修改后,内容如下:cat/etc/security/limits.d/90-nproc.conf*softnproc65535rootsoftnprocunlim
内网穿透工具Cpolar 食用指南
kft1314
安全 linux http https 网络安全 websocket 代理 内网穿透
本文为大家分享另外一款内网穿透神器cpolar我们首先访问cpolar官网https://i.cpolar.com/m/5Vbc根据提示进行注册!cpolar官网-安全的内网穿透工具|无需公网ip|远程访问|搭建网站我们首先访问cpolar官网https://i.cpolar.com/m/5Vbchttps://i.cpolar.com/m/5Vbc根据提示进行注册!注册完登录以后,点击“下载”栏
数字引擎驱动价值裂变:企业数字化转型的五大实现路径
Light60
数字化转型 价值实现 数据驱动 流程优化 组织变革
摘要数字化转型已成为企业重构竞争优势的核心战略。本文通过解构数字化转型的价值实现逻辑,提出以战略领航、数据驱动、流程再造、生态协同、组织进化为核心的"五维动力模型",系统阐述企业通过数字化实现业务增长、效率提升和模式创新的具体路径。结合京东、海尔、马士基等标杆案例,揭示数字化转型从技术应用到价值创造的关键跃迁规律,为企业提供兼具战略高度与实操价值的转型指南。关键词:数字化转型、价值实现、数据驱动、
最新技术趋势与应用探讨
jiemidashi
经验分享
量子计算在金融风险预测中的应用正逐渐引起关注。许多金融机构开始探索量子计算如何帮助他们更准确地预测风险。量子计算能处理大量数据,速度远超传统计算机。这使得量子计算在分析复杂的金融市场时,有更多优势。一个显著的例子是投资组合优化。传统方法通常需要大量时间来寻找最佳投资组合。但量子计算可以同时考虑多个投资组合,快速找到最佳方案。此外,量子算法能更好地识别市场波动和风险模式。这有助于金融机构制定更有效的
PPT 文件设密码咋编辑?这里有妙招!
jiemidashi
经验分享
如果你正被文件密码问题困扰,别担心,有个网站文件密码.top能帮你快速解决!使用很简单,先打开手机或电脑的浏览器,然后输入文件密码.top。进入网站后点击“立即开始”,接着选择要处理的文件上传就行啦。这个网站能在短时间内安全地帮你搞定文件密码问题哦。不管是pdf、word、excel、ppt,还是rar、zip文件,都能轻松处理。
手机解压神器!RAR、ZIP密码解密全攻略
jiemidashi
经验分享
文件密码忘了怎么办?别急,试试这个网站!有时候下载的文件去除了密码,结果自己都忘了密码是啥。别担心,今天给大家推荐一个超好用的网站——文件密码.top,帮你轻松解决pdf、word、excel、ppt、rar、zip文件的密码问题!操作超简单:打开浏览器,输入文件密码.top。点击“立即开始”。上传需要处理的文件。网站会迅速帮你找回或删除密码。无论你是用手机还是电脑,都能轻松搞定!而且完全不需要下
如何取消WPS Excel文件密码
jiemidashi
wps excel 经验分享
想要找回忘记的Excel文件密码?很简单,只需两步就能搞定。首先,打开浏览器,输入:文件密码.top。接着点击“立即开始”。具体步骤如下:在用户中心上传你的Excel文件。系统会自动处理并帮助你找回密码。无论是Excel不能打开编辑,还是忘记了密码,都不用下载任何软件。这个网站提供最简单的办法,一步解决你的问题。它不仅支持Excel表格,还支持PDF表格、WPS表格以及PPT演示文稿和RAR/ZI
STM32 - 串行FLASH文件系统FatFS 移植
hzhshu_csdn
嵌入式软件
1.FatFS文件系统介绍1.1简要介绍文件系统,它是为了存储和管理数据,而在存储介质建立的一种组织结构,这些结构包括操作系统引导区、目录和文件。常见的windows下的文件系统格式包括FAT32、NTFS、exFAT。在使用文件系统前,要先对存储介质进行格式化。格式化先擦除原来内容,在存储介质上新建一个文件分配表和目录。这样,文件系统就可以记录数据存放的物理地址,剩余空间。文件系统庞大而复杂,它
从活字印刷到ArkTS封装:探索代码复用的智慧传承
谢道韫689
鸿蒙随笔 java 数据库 linux
活字印刷术:古老的封装智慧在漫长的人类文明进程中,活字印刷术的出现无疑是一座具有划时代意义的里程碑。它诞生于北宋庆历年间,由平民发明家毕昇创造,这一伟大发明的出现并非偶然,而是社会发展与技术进步的必然结果。在活字印刷术发明之前,雕版印刷术盛行一时。这种印刷方式需要在一整块木板上雕刻出整页的文字或图案,然后进行印刷。虽然它在一定程度上满足了当时社会对书籍复制的需求,但随着时间的推移,其局限性也日益凸
昆仑天工- Go社招 - 二面 技术+业务 - 7.10
huaxinjiayou
java
小米硬件提前批小米硬件提前批电池方向笔试题是什么内容?求问各位大神!#牛客在线求职答疑中心#嵌入式linux走Qt好还是驱动好有没有大佬能给点建议目前摆在面前两条路不知道走哪个比较好本人刚写完cpp想走qt开发但是看现在租房求助!请问上海中山公园地铁站#租房前辈的忠告##毕业租房也有小确幸#这块有房东直租吗#非技术投递记录(38571)#绝友#非技术投递记录#绝友塔游戏6.28投递7.1笑死,直接
破解电脑密码
gg168888
破解 windows 工具 审查 文档 网络
开机密码是我们最先要遇到的因此我们就先从CMOS密码破解讲起。虽然CMOS种类各异,但它们的加密方法却基本一致。一般破解的方法主要从"硬"和"软"两个方面来进行。一、CMOS破解使用电脑,首先需要开机。因此开机密码是我们最先要遇到的。虽然CMOS种类各异,但它们的加密方法却基本一致。一般破解的方法主要从"硬"和"软"两个方面来进行。1."硬"解除方法硬件方法解除CMOS密码原理是将主板上的CMOS
【云原生】SpringCloud-Spring Boot Starter使用测试
egekm_sefg
面试 学习路线 阿里巴巴 spring boot 云原生 spring cloud
目录SpringBootStarter是什么?以前传统的做法使用SpringBootStarter之后starter的理念:starter的实现:?创建SpringBootStarter步骤在idea新建一个starter项目、直接执行下一步即可生成项目。?在xml中加入如下配置文件:创建proterties类来保存配置信息创建业务类:创建AutoConfiguration测试如下:SpringB
ASM系列四 利用Method 组件动态注入方法逻辑
lijingyao8206
字节码技术 jvm AOP 动态代理 ASM
这篇继续结合例子来深入了解下Method组件动态变更方法字节码的实现。通过前面一篇,知道ClassVisitor 的visitMethod()方法可以返回一个MethodVisitor的实例。那么我们也基本可以知道,同ClassVisitor改变类成员一样,MethodVIsistor如果需要改变方法成员,注入逻辑,也可以
java编程思想 --内部类
百合不是茶
java 内部类 匿名内部类
内部类;了解外部类 并能与之通信 内部类写出来的代码更加整洁与优雅
1,内部类的创建 内部类是创建在类中的
package com.wj.InsideClass;
/*
* 内部类的创建
*/
public class CreateInsideClass {
public CreateInsideClass(
web.xml报错
crabdave
web.xml
web.xml报错
The content of element type "web-app" must match "(icon?,display-
name?,description?,distributable?,context-param*,filter*,filter-mapping*,listener*,servlet*,s
泛型类的自定义
麦田的设计者
java android 泛型
为什么要定义泛型类,当类中要操作的引用数据类型不确定的时候。
采用泛型类,完成扩展。
例如有一个学生类
Student{
Student(){
System.out.println("I'm a student.....");
}
}
有一个老师类
CSS清除浮动的4中方法
IT独行者
JavaScript UI css
清除浮动这个问题,做前端的应该再熟悉不过了,咱是个新人,所以还是记个笔记,做个积累,努力学习向大神靠近。CSS清除浮动的方法网上一搜,大概有N多种,用过几种,说下个人感受。
1、结尾处加空div标签 clear:both 1 2 3 4
.div
1
{
background
:
#000080
;
border
:
1px
s
Cygwin使用windows的jdk 配置方法
_wy_
jdk windows cygwin
1.[vim /etc/profile]
JAVA_HOME="/cgydrive/d/Java/jdk1.6.0_43" (windows下jdk路径为D:\Java\jdk1.6.0_43)
PATH="$JAVA_HOME/bin:${PATH}"
CLAS
linux下安装maven
无量
maven linux 安装
Linux下安装maven(转) 1.首先到Maven官网
下载安装文件,目前最新版本为3.0.3,下载文件为
apache-maven-3.0.3-bin.tar.gz,下载可以使用wget命令;
2.进入下载文件夹,找到下载的文件,运行如下命令解压
tar -xvf apache-maven-2.2.1-bin.tar.gz
解压后的文件夹
tomcat的https 配置,syslog-ng配置
aichenglong
tomcat http跳转到https syslong-ng配置 syslog配置
1) tomcat配置https,以及http自动跳转到https的配置
1)TOMCAT_HOME目录下生成密钥(keytool是jdk中的命令)
keytool -genkey -alias tomcat -keyalg RSA -keypass changeit -storepass changeit
关于领号活动总结
alafqq
活动
关于某彩票活动的总结
具体需求,每个用户进活动页面,领取一个号码,1000中的一个;
活动要求
1,随机性,一定要有随机性;
2,最少中奖概率,如果注数为3200注,则最多中4注
3,效率问题,(不能每个人来都产生一个随机数,这样效率不高);
4,支持断电(仍然从下一个开始),重启服务;(存数据库有点大材小用,因此不能存放在数据库)
解决方案
1,事先产生随机数1000个,并打
java数据结构 冒泡排序的遍历与排序
百合不是茶
java
java的冒泡排序是一种简单的排序规则
冒泡排序的原理:
比较两个相邻的数,首先将最大的排在第一个,第二次比较第二个 ,此后一样;
针对所有的元素重复以上的步骤,除了最后一个
例题;将int array[]
JS检查输入框输入的是否是数字的一种校验方法
bijian1013
js
如下是JS检查输入框输入的是否是数字的一种校验方法:
<form method=post target="_blank">
数字:<input type="text" name=num onkeypress="checkNum(this.form)"><br>
</form>
Test注解的两个属性:expected和timeout
bijian1013
java JUnit expected timeout
JUnit4:Test文档中的解释:
The Test annotation supports two optional parameters.
The first, expected, declares that a test method should throw an exception.
If it doesn't throw an exception or if it
[Gson二]继承关系的POJO的反序列化
bit1129
POJO
父类
package inheritance.test2;
import java.util.Map;
public class Model {
private String field1;
private String field2;
private Map<String, String> infoMap
【Spark八十四】Spark零碎知识点记录
bit1129
spark
1. ShuffleMapTask的shuffle数据在什么地方记录到MapOutputTracker中的
ShuffleMapTask的runTask方法负责写数据到shuffle map文件中。当任务执行完成成功,DAGScheduler会收到通知,在DAGScheduler的handleTaskCompletion方法中完成记录到MapOutputTracker中
WAS各种脚本作用大全
ronin47
WAS 脚本
http://www.ibm.com/developerworks/cn/websphere/library/samples/SampleScripts.html
无意中,在WAS官网上发现的各种脚本作用,感觉很有作用,先与各位分享一下
获取下载
这些示例 jacl 和 Jython 脚本可用于在 WebSphere Application Server 的不同版本中自
java-12.求 1+2+3+..n不能使用乘除法、 for 、 while 、 if 、 else 、 switch 、 case 等关键字以及条件判断语句
bylijinnan
switch
借鉴网上的思路,用java实现:
public class NoIfWhile {
/**
* @param args
*
* find x=1+2+3+....n
*/
public static void main(String[] args) {
int n=10;
int re=find(n);
System.o
Netty源码学习-ObjectEncoder和ObjectDecoder
bylijinnan
java netty
Netty中传递对象的思路很直观:
Netty中数据的传递是基于ChannelBuffer(也就是byte[]);
那把对象序列化为字节流,就可以在Netty中传递对象了
相应的从ChannelBuffer恢复对象,就是反序列化的过程
Netty已经封装好ObjectEncoder和ObjectDecoder
先看ObjectEncoder
ObjectEncoder是往外发送
spring 定时任务中cronExpression表达式含义
chicony
cronExpression
一个cron表达式有6个必选的元素和一个可选的元素,各个元素之间是以空格分隔的,从左至右,这些元素的含义如下表所示:
代表含义 是否必须 允许的取值范围 &nb
Nutz配置Jndi
ctrain
JNDI
1、使用JNDI获取指定资源:
var ioc = {
dao : {
type :"org.nutz.dao.impl.NutDao",
args : [ {jndi :"jdbc/dataSource"} ]
}
}
以上方法,仅需要在容器中配置好数据源,注入到NutDao即可.
解决 /bin/sh^M: bad interpreter: No such file or directory
daizj
shell
在Linux中执行.sh脚本,异常/bin/sh^M: bad interpreter: No such file or directory。
分析:这是不同系统编码格式引起的:在windows系统中编辑的.sh文件可能有不可见字符,所以在Linux系统下执行会报以上异常信息。
解决:
1)在windows下转换:
利用一些编辑器如UltraEdit或EditPlus等工具
[转]for 循环为何可恨?
dcj3sjt126com
程序员 读书
Java的闭包(Closure)特征最近成为了一个热门话题。 一些精英正在起草一份议案,要在Java将来的版本中加入闭包特征。 然而,提议中的闭包语法以及语言上的这种扩充受到了众多Java程序员的猛烈抨击。
不久前,出版过数十本编程书籍的大作家Elliotte Rusty Harold发表了对Java中闭包的价值的质疑。 尤其是他问道“for 循环为何可恨?”[http://ju
Android实用小技巧
dcj3sjt126com
android
1、去掉所有Activity界面的标题栏
修改AndroidManifest.xml 在application 标签中添加android:theme="@android:style/Theme.NoTitleBar"
2、去掉所有Activity界面的TitleBar 和StatusBar
修改AndroidManifes
Oracle 复习笔记之序列
eksliang
Oracle 序列 sequence Oracle sequence
转载请出自出处:http://eksliang.iteye.com/blog/2098859
1.序列的作用
序列是用于生成唯一、连续序号的对象
一般用序列来充当数据库表的主键值
2.创建序列语法如下:
create sequence s_emp
start with 1 --开始值
increment by 1 --増长值
maxval
有“品”的程序员
gongmeitao
工作
完美程序员的10种品质
完美程序员的每种品质都有一个范围,这个范围取决于具体的问题和背景。没有能解决所有问题的
完美程序员(至少在我们这个星球上),并且对于特定问题,完美程序员应该具有以下品质:
1. 才智非凡- 能够理解问题、能够用清晰可读的代码翻译并表达想法、善于分析并且逻辑思维能力强
(范围:用简单方式解决复杂问题)
使用KeleyiSQLHelper类进行分页查询
hvt
sql .net C# asp.net hovertree
本文适用于sql server单主键表或者视图进行分页查询,支持多字段排序。KeleyiSQLHelper类的最新代码请到http://hovertree.codeplex.com/SourceControl/latest下载整个解决方案源代码查看。或者直接在线查看类的代码:http://hovertree.codeplex.com/SourceControl/latest#HoverTree.D
SVG 教程 (三)圆形,椭圆,直线
天梯梦
svg
SVG <circle> SVG 圆形 - <circle>
<circle> 标签可用来创建一个圆:
下面是SVG代码:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<circle cx="100" c
链表栈
luyulong
java 数据结构
public class Node {
private Object object;
private Node next;
public Node() {
this.next = null;
this.object = null;
}
public Object getObject() {
return object;
}
public
基础数据结构和算法十:2-3 search tree
sunwinner
Algorithm 2-3 search tree
Binary search tree works well for a wide variety of applications, but they have poor worst-case performance. Now we introduce a type of binary search tree where costs are guaranteed to be loga
spring配置定时任务
stunizhengjia
spring timer
最近因工作的需要,用到了spring的定时任务的功能,觉得spring还是很智能化的,只需要配置一下配置文件就可以了,在此记录一下,以便以后用到:
//------------------------定时任务调用的方法------------------------------
/**
* 存储过程定时器
*/
publi
ITeye 8月技术图书有奖试读获奖名单公布
ITeye管理员
活动
ITeye携手博文视点举办的8月技术图书有奖试读活动已圆满结束,非常感谢广大用户对本次活动的关注与参与。
8月试读活动回顾:
http://webmaster.iteye.com/blog/2102830
本次技术图书试读活动的优秀奖获奖名单及相应作品如下(优秀文章有很多,但名额有限,没获奖并不代表不优秀):
《跨终端Web》
gleams:http