Codis源码解析——Jodis
来源:互联网 发布:linux vi 文件末尾 编辑:程序博客网 时间:2024/06/03 16:19
我们在java项目里面连接已经搭建好的Codis集群时,需要用到其java客户端——Jodis。这一篇我们就来看看Jodis是如何操作对Codis集群进行操作的。
import io.codis.jodis.JedisResourcePool;import io.codis.jodis.RoundRobinJedisPool;import redis.clients.jedis.Jedis;/** * @author wujiang * @version 1.0.0 * @date 2017/7/12 */public class CodisDemo { public static void main(String[] args) { JedisResourcePool jedisPool = RoundRobinJedisPool.create() .curatorClient("10.0.2.15:2181", 30000).zkProxyDir("/jodis/codis-wujiang").build(); try (Jedis jedis = jedisPool.getResource()) { //省略代码 } }}
如果在运行中遇到报错
Redis.clients.jedis.exceptions.JedisException: Proxy list empty
可以参考之前的一篇博客Jodis报错- JedisException- Proxy list empty进行解决。
Jodis中最重要的一个类就是RoundRobinJedisPool。首先来看看这个类的继承结构和方法
在RoundRobinJedisPool.class中有一个内部类Builder,是用来与zk集群进行连接的。在build之前,一直都是在设置Builder的属性,例如将Builder的connectionTimeoutMs和soTimeoutMs分别设置为2000,database设置为0,传入zk地址和路径等等。然后会调用下面的build方法
public static final class Builder { private CuratorFramework curatorClient; private boolean closeCurator; private String zkProxyDir; private String zkAddr; private int zkSessionTimeoutMs; private JedisPoolConfig poolConfig; private int connectionTimeoutMs; private int soTimeoutMs; private String password; private int database; private String clientName; . public RoundRobinJedisPool build() { this.validate(); return new RoundRobinJedisPool(this.curatorClient, this.closeCurator, this.zkProxyDir, this.poolConfig, this.connectionTimeoutMs, this.soTimeoutMs, this.password, this.database, this.clientName, null); } . private void validate() { //首先检查zkProxyDir和zkAddr是否为空 Preconditions.checkNotNull(this.zkProxyDir, "zkProxyDir can not be null"); //初始化curatorClient并启动 if (this.curatorClient == null) { Preconditions.checkNotNull(this.zkAddr, "zk client can not be null"); this.curatorClient = CuratorFrameworkFactory.builder().connectString(this.zkAddr).sessionTimeoutMs(this.zkSessionTimeoutMs).retryPolicy(new BoundedExponentialBackoffRetryUntilElapsed(100, 30000, -1L)).build(); this.curatorClient.start(); this.closeCurator = true; } else if (this.curatorClient.getState() == CuratorFrameworkState.LATENT) { this.curatorClient.start(); } if (this.poolConfig == null) { this.poolConfig = new JedisPoolConfig(); } }}
初始化的CuratorClient如下所示,curator-client组件可以作为zookeeper client来使用,它提供了zk实例创建/重连机制等
最关键的是后面创建RoundRobinJedisPool这一步。
private RoundRobinJedisPool(CuratorFramework curatorClient, boolean closeCurator, String zkProxyDir, JedisPoolConfig poolConfig, int connectionTimeoutMs, int soTimeoutMs, String password, int database, String clientName) { //新建ImmutableList<PooledObject>,每个PooledObject包含了proxy addr以及jedisPool this.pools = ImmutableList.of(); this.nextIdx = new AtomicInteger(-1); this.poolConfig = poolConfig; this.connectionTimeoutMs = connectionTimeoutMs; this.soTimeoutMs = soTimeoutMs; this.password = password; this.database = database; this.clientName = clientName; this.curatorClient = curatorClient; //true this.closeCurator = closeCurator; this.watcher = new PathChildrenCache(curatorClient, zkProxyDir, true); //监听zkProxyDir下面的变化,也就是当集群中新加入proxy或者有proxy宕机之后,都会由watcher得到该消息 this.watcher.getListenable().addListener(new PathChildrenCacheListener() { private void logEvent(PathChildrenCacheEvent event) { StringBuilder msg = new StringBuilder("Receive child event: "); msg.append("type=").append(event.getType()); ChildData data = event.getData(); if (data != null) { msg.append(", path=").append(data.getPath()); msg.append(", stat=").append(data.getStat()); if (data.getData() != null) { msg.append(", bytes length=").append(data.getData().length); } else { msg.append(", no bytes"); } } else { msg.append(", no data"); } RoundRobinJedisPool.LOG.info(msg.toString()); } public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception { this.logEvent(event); //如果zk下监听事件类型为添加、更新、删除(这是通过静态代码块注入的),就重置pool if (RoundRobinJedisPool.RESET_TYPES.contains(event.getType())) { RoundRobinJedisPool.this.resetPools(); } } }); try { //以BUILD_INITIAL_CACHE模式启动watcher this.watcher.start(StartMode.BUILD_INITIAL_CACHE); } catch (Exception var11) { this.close(); throw new JedisException(var11); } this.resetPools();}//启动watcher的方法public void rebuild() throws Exception { Preconditions.checkState(!this.executorService.isShutdown(), "cache has been closed"); this.ensurePath(); this.clear(); ///jodis/codis-wujiang下面的proxy,本例中只有一个proxy-00e6b8c9d5cc40ed5f81ea515b2b7695 List<String> children = (List)this.client.getChildren().forPath(this.path); Iterator var2 = children.iterator(); while(var2.hasNext()) { String child = (String)var2.next(); ///jodis/codis-wujiang/proxy-00e6b8c9d5cc40ed5f81ea515b2b7695 String fullPath = ZKPaths.makePath(this.path, child); this.internalRebuildNode(fullPath); if (this.rebuildTestExchanger != null) { this.rebuildTestExchanger.exchange(new Object()); } } this.offerOperation(new RefreshOperation(this, PathChildrenCache.RefreshMode.FORCE_GET_DATA_AND_STAT)); }
下一步,resetPool,根据zk中的proxy以及对应的jedis变化,更新集群中的redisPool。这样pool中永远都有每个proxy以及proxy中对应的jedisPool。
注意,下面的addr2Pool是RoundRobinJedisPool当前的pool,从zk当前的数据中取出现在的所有proxy,从addr2Pool中删除,并放入builder中。最后的RoundRobinJedisPool.pool相当于是builder.build出来的。再将addr2Pool中剩余的proxy连接关闭(这里用了一个trick,JedisPool实际上是proxy连接)
private static final class PooledObject { //addr是proxy的19000端口地址 public final String addr; public final JedisPool pool; public PooledObject(String addr, JedisPool pool) { this.addr = addr; this.pool = pool; }} private void resetPools() { ImmutableList<RoundRobinJedisPool.PooledObject> pools = this.pools; Map<String, RoundRobinJedisPool.PooledObject> addr2Pool = Maps.newHashMapWithExpectedSize(pools.size()); UnmodifiableIterator var3 = pools.iterator(); while(var3.hasNext()) { RoundRobinJedisPool.PooledObject pool = (RoundRobinJedisPool.PooledObject)var3.next(); addr2Pool.put(pool.addr, pool); } com.google.common.collect.ImmutableList.Builder<RoundRobinJedisPool.PooledObject> builder = ImmutableList.builder(); Iterator var14 = this.watcher.getCurrentData().iterator(); while(var14.hasNext()) { ChildData childData = (ChildData)var14.next(); try { //封装了proxy addr(19000端口)以及state(是否在线) CodisProxyInfo proxyInfo = (CodisProxyInfo)MAPPER.readValue(childData.getData(), CodisProxyInfo.class); if ("online".equals(proxyInfo.getState())) { String addr = proxyInfo.getAddr(); RoundRobinJedisPool.PooledObject pool = (RoundRobinJedisPool.PooledObject)addr2Pool.remove(addr); //第一次进来的时候,pool为null,会执行下面的pool的构造方法 if (pool == null) { LOG.info("Add new proxy: " + addr); String[] hostAndPort = addr.split(":"); String host = hostAndPort[0]; int port = Integer.parseInt(hostAndPort[1]); pool = new RoundRobinJedisPool.PooledObject(addr, new JedisPool(this.poolConfig, host, port, this.connectionTimeoutMs, this.soTimeoutMs, this.password, this.database, this.clientName, false, (SSLSocketFactory)null, (SSLParameters)null, (HostnameVerifier)null)); } builder.add(pool); } } catch (Throwable var12) { LOG.warn("parse " + childData.getPath() + " failed", var12); } } this.pools = builder.build(); var14 = addr2Pool.values().iterator(); while(var14.hasNext()) { RoundRobinJedisPool.PooledObject pool = (RoundRobinJedisPool.PooledObject)var14.next(); LOG.info("Remove proxy: " + pool.addr); pool.pool.close(); } }
创建好的RoundRobinJedisPool.pools如下所示
当一个pool准备好,下一步我们的操作就是从pool中取出相应的jedis实例,并进行相关操作。这个pool里面只有服务正常的proxy,error的proxy会从zk被剔除掉,详见 Codis源码解析——proxy添加到集群
public Jedis getResource() { ImmutableList<RoundRobinJedisPool.PooledObject> pools = this.pools; if (pools.isEmpty()) { throw new JedisException("Proxy list empty"); } else { int current; int next; do { //nextIdx初始值是-1 current = this.nextIdx.get(); //pools.size取决于集群中有多少个proxy,可以看到,负载均衡算法采取的是轮询 next = current >= pools.size() - 1 ? 0 : current + 1; } while(!this.nextIdx.compareAndSet(current, next)); return ((RoundRobinJedisPool.PooledObject)pools.get(next)).pool.getResource(); }}
Jodis首先让你本地的java程序通过zookeeper(或者etcd)连接到proxy,并监听proxy的变化,因为可能根据需要对proxy做水平扩容。监听过程中,对由PooledObject组成的pool进行刷新,然后每次需要对codis集群进行操作的时候,都按照轮询负载均衡算法从pool中取出一个jedis实例进行操作。
说明
如有转载,请注明出处:
http://blog.csdn.net/antony9118/article/details/77776072
- Codis源码解析——Jodis
- Codis源码解析——codis-server添加到集群
- Codis源码解析——sharedBackendConn
- Codis源码解析——proxy的启动
- Codis源码解析——proxy监听redis请求
- Codis源码解析——dashboard的启动(1)
- Codis源码解析——dashboard的启动(2)
- Codis源码解析——fe的启动
- Codis源码解析——proxy添加到集群
- Codis源码解析——slot的分配
- Codis源码解析——处理slot操作(1)
- Codis源码解析——处理slot操作(2)
- Codis源码解析——sentinel的重同步(1)
- Codis源码解析——sentinel的重同步(2)
- jodis
- CODIS源码剖析二(codis-proxy功能实现)
- codis
- Codis
- 在虚拟机安装centos7
- [luogu-2678]noip2015day2-T1 跳石头 题解
- Hybrid App (一)
- 使用线程池插入数据报Could not open JDBC Connection for transaction 异常
- bg fg jobs的简单操作
- Codis源码解析——Jodis
- JS-面向对象
- JS中的this指向
- Cmake使用例程
- MYIR-ZYNQ7000系列-zturn教程(1)-从新建工程到下载bit文件
- 185. Department Top Three Salaries
- 设计模式--单例模式(Singleton)
- 全志R16平台tinav2.1系统下调通rtl8188eu(草稿)
- jdbc总结