Fork me on GitHub

条形码处理类库 ZXing

Published on:
Tags: google zxing

ZXing 详细介绍

ZXing是一个开源Java类库用于解析多种格式的1D/2D条形码。目标是能够对QR编码、Data Matrix、UPC的1D条形码进行解码。 其提供了多种平台下的客户端包括:J2ME、J2SE和Android。

示例代码

import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

/**
* 二维码工具类
*/
public class QRCodeUtil {
  private static final int width = 300;// 默认二维码宽度
  private static final int height = 300;// 默认二维码高度
  private static final String format = "png";// 默认二维码文件格式
  private static final Map<EncodeHintType, Object> hints = new HashMap();// 二维码参数

  static {
      hints.put(EncodeHintType.CHARACTER_SET, "utf-8");// 字符编码
      hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);// 容错等级 L、M、Q、H 其中 L 为最低, H 为最高
      hints.put(EncodeHintType.MARGIN, 2);// 二维码与图片边距
  }
  /**
   * 返回一个 BufferedImage 对象
   * @param content 二维码内容
   * @param width   宽
   * @param height  高
   */
  public static BufferedImage toBufferedImage(String content, int width, int height) throws WriterException, IOException {
      BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints);
      return MatrixToImageWriter.toBufferedImage(bitMatrix);
  }
  /**
   * 将二维码图片输出到一个流中
   * @param content 二维码内容
   * @param stream  输出流
   * @param width   宽
   * @param height  高
   */
  public static void writeToStream(String content, OutputStream stream, int width, int height) throws WriterException, IOException {
      BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints);
      MatrixToImageWriter.writeToStream(bitMatrix, format, stream);
  }
  /**
   * 生成二维码图片文件
   * @param content 二维码内容
   * @param path    文件保存路径
   * @param width   宽
   * @param height  高
   */
  public static void createQRCode(String content, String path, int width, int height) throws WriterException, IOException {
      BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints);
      //toPath() 方法由 jdk1.7 及以上提供
      MatrixToImageWriter.writeToPath(bitMatrix, format, new File(path).toPath());
  }
}

npm registry太慢?怎么办?使用nrm

Published on:
Tags: npm nrm

转载自:http://cnodejs.org/topic/5326e78c434e04172c006826

开发的npm registry 管理工具 nrm, 能够查看和切换当前使用的registry, 最近NPM经常 down 掉, 这个还是很有用的哈哈

Install

$ npm install -g nrm

Example

$ nrm ls

* npm ---- https://registry.npmjs.org/
  cnpm --- http://r.cnpmjs.org/
  eu ----- http://registry.npmjs.eu/
  au ----- http://registry.npmjs.org.au/
  sl ----- http://npm.strongloop.com/
  nj ----- https://registry.nodejitsu.com/
$ nrm use cnpm //switch registry to cnpm

    Registry has been set to: http://r.cnpmjs.org/

cmd

nrm help // show help
nrm list // show all registries
nrm use cnpm // switch to cnpm
nrm home // go to a registry home page

Registries

分布式锁(Redis实现)使用说明

Published on:

概述

GitHub release GitHub stars GitHub forks GitHub watchers

项目地址

distributed-lock

License

分布式锁,默认是redis实现,可扩展接口增加zk、等其他实现,这个分布式锁采用redis实现,根据CAP理论保证了可用性、分区容错性、和最终一致性。

实现的分布式锁特性

  1. 这把锁是非阻塞锁,可以根据超时时间和重试频率来定义重试次数
  2. 这把锁支持失效时间,极端情况下解锁失败,到达时间之后锁会自动删除
  3. 这把锁是非重入锁,一个线程获得锁之后,在释放锁之前,其他线程无法再次获得锁,只能根据获取锁超时时间和重试策略进行多次尝试获取锁。
  4. 因为这把锁是非阻塞的,所以性能很好,支持高并发
  5. 使用方无需手动获取锁和释放锁,锁的控制完全由框架控制操作,避免使用方由于没有释放锁或释放锁失败导致死锁的问题

实现的分布式锁缺点

  1. 通过超时时间来控制锁的失效时间其实并不完美,但是根据性能和CAP理论有做取舍
  2. 这把锁不支持阻塞,因为要达到高的性能阻塞的特性是要牺牲

使用步骤

Maven中引入

<dependency>
    <groupId>cn.tsoft.framework</groupId>
    <artifactId>distributed-lock</artifactId>
    <version>1.1.0-SNAPSHOT</version>
</dependency>

spring中引入配置

<import resource="classpath:spring-lock.xml" />

使用到了RedisClient

具体可以查看《RedisCliet使用说明》

<aop:aspectj-autoproxy />
<context:component-scan base-package="cn.tsoft.framework" />
<context:property-placeholder location="classpath:redis.properties"/>
<import resource="classpath:spring-redis.xml" />

代码中使用

import cn.tsoft.framework.lock.Lock;
import  cn.tsoft.framework.lock.LockCallBack;
import  cn.tsoft.framework.lock.DefaultLockCallBack;
 
@Autowired
Lock lock;

//方法一
T t = lock.lock("Test_key_2",20,60,new LockCallBack<T>(){
    public T handleObtainLock(){
        dosomething();
    }
    public T handleNotObtainLock() throws LockCantObtainException{
        return T;//throw new LockCantObtainException();
    }
    public T handleException(LockInsideExecutedException e) throws LockInsideExecutedException{
        return T;//throw new e;
    }
});

//方法二
T t = lock.lock("Test_key_2",LockRetryFrequncy.VERY_QUICK,20,60,new DefaultLockCallBack<T>(T,T){
    public T handleObtainLock(){
        dosomething();
    }
});

锁重试策略说明

/**
 * 锁重试获取频率策略
 * 
 * @author ningyu
 *
 */
LockRetryFrequncy.VERY_QUICK;  //非常快
LockRetryFrequncy.QUICK;       //快
LockRetryFrequncy.NORMAL;      //中
LockRetryFrequncy.SLOW;        //慢
LockRetryFrequncy.VERYSLOW;    //很慢
//例如:
//以获取锁的超时时间为:1秒来计算
//VERY_QUICK的重试次数为:100次
//QUICK的重试次数为:20次
//NORMAL的重试次数为:10次
//SLOW的重试次数为:2次
//QUICK的重试次数为:1次
//这个重试策略根据自身业务来选择合适的重试策略

Example

第一种用法

//锁名称:Test_key_2
//获取锁超时时间:20秒
//锁最大过期时间:60秒
//内部执行回调,包含(1.获取到锁回调,2.没有获取到锁回调,3.获取到锁内部执行业务代码报错)
//默认策略:NORMAL
lock.lock("Test_key_2",20,60,new LockCallBack<String>() {
   @Override
   public String handleException(LockInsideExecutedException e) throws LockInsideExecutedException {
       logger.error("获取到锁,内部执行报错");
       return "Exception";         
   }
 
   @Override
   public String handleNotObtainLock() throws LockCantObtainException {
          logger.error("没有获取到锁");
       return "NotObtainLock";
   }
 
   @Override
   public String handleObtainLock() {
       logger.info("获取到锁");
       dosomething();
       return "ok";
   }
);

第二种用法

//锁名称:Test_key_2
//获取锁超时时间:20秒
//锁最大过期时间:60秒
//内部执行回调,使用默认回调实现,只需要实现获取到锁后需要执行的方法,当遇到没有获取锁和获取锁内部执行错误时会返回构造函数中设置的值(支持泛型)
//默认策略:NORMAL
lock.lock("Test_key_2",20,60,new DefaultLockCallBack<String>("NotObtainLock", "Exception") {
   @Override
   public String handleObtainLock() {
       logger.info("获取到锁");
       dosomething();
       return "ok";
   }
);

第三种用法

//锁名称:Test_key_2
//锁重试获取频率:VERY_QUICK 非常快
//获取锁超时时间:20秒
//锁最大过期时间:60秒
//内部执行回调,包含(1.获取到锁回调,2.没有获取到锁回调,3.获取到锁内部执行业务代码报错)
lock.lock("Test_key_2",LockRetryFrequncy.VERY_QUICK,20,60,new LockCallBack<String>() {
   @Override
   public String handleException(LockInsideExecutedException e) throws LockInsideExecutedException {
       logger.error("获取到锁,内部执行报错");
       return "Exception";         
   }
 
   @Override
   public String handleNotObtainLock() throws LockCantObtainException {
          logger.error("没有获取到锁");
       return "NotObtainLock";
   }
 
   @Override
   public String handleObtainLock() {
       logger.info("获取到锁");
       dosomething();
       return "ok";
   }
);

第四种用法

//锁名称:Test_key_2
//锁重试获取频率:VERY_QUICK 非常快
//获取锁超时时间:20秒
//锁最大过期时间:60秒
//内部执行回调,使用默认回调实现,只需要实现获取到锁后需要执行的方法,当遇到没有获取锁和获取锁内部执行错误时会返回构造函数中设置的值(支持泛型)
lock.lock("Test_key_2",LockRetryFrequncy.VERY_QUICK,20,60,new DefaultLockCallBack<String>("NotObtainLock", "Exception") {
   @Override
   public String handleObtainLock() {
       logger.info("获取到锁");
       dosomething();
       return "ok";
   }
);

注意事项

  1. 获取锁的超时时间和重试策略直接影响获取锁重试的次数,根据业务场景来定义适合的重试获取锁的频次,避免线程阻塞。
  2. 场景:
    1. 快速响应给客户端的场景,超时时间尽量短,超时时间 < 锁后执行时间,例如:秒杀、抢购
    2. 可以容忍响应速度的场景,锁后执行时间*2 > 超时时间 >=锁后执行时间
  3. 根据业务场景来定义锁的最大过期时间,理论上业务执行越慢过期时间越大,因为是并发锁,为了杜绝因为获得锁而没有释放造成的问题
  4. 建议 锁后执行时间*1.5 > 锁超时时间 > 锁后执行时间,避免并发问题
  5. 获取锁后执行的代码块一定是小而快的,就像事务块使用原则一样,禁止重而长的逻辑包在里面造成其他线程获取锁失败率过高,如果逻辑很复杂需要分析那一块需要支持并发就把需要并发的代码包在里面。

RedisClient升级支持Sentinel使用说明

Published on:
Tags: Redis

项目地址

redis-client

   

RedisClient操作单点Redis使用文档:《RedisClient使用》 以下是支持Sentinel(哨兵)+Redis集群的RedisClient(架构封装的Java访问Redis的客户端程序)高级使用方式

Redis集群方式:Master-Slave(1 - n 为一套集群可以多套) Sentinel集群方式:Sentinel(n台,n>=3),投票人数:n-1(参与Master是否宕机以及下一任Master选举的投票人数)

1. Maven中引用(目前预览版)

<dependency>
  <groupId>cn.tsoft.framework</groupId>
  <artifactId>redis-client</artifactId>
  <version>1.2.0-SNAPSHOT</version>
</dependency>

2. 配置说明

原始(基础)配置:

redis.pool.maxTotal=1000
redis.pool.maxIdle=50
redis.pool.minIdle=10
redis.pool.testOnBorrow=true
redis.pool.testOnReturn=true
redis.ip=192.168.0.65
redis.port=6379
redis.timeout=2000
redis.password=123456

sentinel新增配置

# sentinel
redis.mastername=mymaster
redis.sentinels=127.0.0.1:26379,127.0.0.1:26380,127.0.0.1:26381

redis.mastername指的是monitor master的名称 redis.sentinels指的是哨兵的ip:port集合(ip和port需要替换)

删除配置

#redis.ip=192.168.0.65
#redis.port=6379

ps.由于使用了sentinel自动发现redis服务因此不需要此配置,注释或删除即可

3. spring配置说明

xml配置跟以前pool的配置方式有所不同,单节点redispool配置使用的是:redis.clients.jedis.JedisPoolConfigredis.clients.jedis.JedisPool sentinel的配置替换为:redis.clients.jedis.JedisPoolConfigcn.tsoft.framework.redis.pool.JedisSentinelPoolFactory

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context  http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
     
    <aop:aspectj-autoproxy />
    <context:component-scan base-package="cn.tsoft.framework.redis" />
     
    <bean id="redisClient" class="cn.tsoft.framework.redis.client.impl.RedisClientImpl">
        <property name="jedisSentinelPoolFactory" ref="jedisSentinelPoolFactory" />
    </bean>
    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxTotal" value="${redis.pool.maxTotal}" />
        <property name="maxIdle" value="${redis.pool.maxIdle}" />
        <property name="minIdle" value="${redis.pool.minIdle}" />
        <property name="testOnBorrow" value="${redis.pool.testOnBorrow}" />
        <property name="testOnReturn" value="${redis.pool.testOnReturn}" />
    </bean>
     
    <bean id="jedisSentinelPoolFactory" class="cn.tsoft.framework.redis.pool.JedisSentinelPoolFactory">
        <property name="poolConfig" ref="jedisPoolConfig" />
        <property name="masterName" value="${redis.mastername}" />
        <property name="sentinels" value="${redis.sentinels}" />
        <property name="timeout" value="${redis.timeout}" />
        <property name="password" value="${redis.password}" />
    </bean>
</beans>

ps.以上配置在redis-client-1.2.0-SNAPSHOT.jar包的spring-redis-sentinel.xml文件中

4. 项目中引用

<!-- redis.properties加载方式采用UCM的统一配置加载,具体可以查看global中的配置,如需要替换global的配置只需要在项目自定义配置中配置相同的key来进行属性覆盖  -->
<context:component-scan base-package="cn.tsoft.framework.redis" />
<import resource="classpath:spring-redis-sentinel.xml" />

ps.替换掉以前的:<import resource="classpath:spring-redis.xml" />

5. 注意事项

5.1. pool使用只允许使用一种,要么使用jedis pool要么使用jedis sentinel pool,两者不允许共存,redisclient启动会检测pool的设置是否合法,不合法会throw出异常,可能遇见的异常如下:

异常 描述 解决办法
RedisClientException(“There can only be one pool! Will not work.”) 只能存在一个pool的设置 检查xml配置,确定使用的pool,只允许保留一个pool设置,直接引用redis-client.jar中的(spring-redis.xml、spring-redis-sentinel.xml)可以解决这个问题
RedisClientException(“No connection pool found! Will not work.”) 没有找到pool的设置 检查xml配置,是否有pool的设置,直接引用redis-client.jar中的(spring-redis.xml、spring-redis-sentinel.xml)可以解决这个问题

5.2. API使用起来跟以前没有任何变化,只是配置发生了变化

Webpack 打包优化之速度篇

Published on:

文章来源:https://jeffjade.com/2017/08/12/125-webpack-package-optimization-for-speed/ 作者:@晚晴幽草轩轩主

在前文 Webpack 打包优化之体积篇中,对如何减小 Webpack 打包体积,做了些探讨;当然,那些法子对于打包速度的提升,也是大有裨益。然而,打包速度之于开发体验及时构建,相当重要;所以有必要对其做更为深入的研究,以便完善工作流,这就是本文存在的缘由。

Webpack Package optimization

Webpack Package optimization

减小文件搜索范围

在使用实际项目开发中,为了提升开发效率,很明显你会使用很多成熟第三方库;即便自己写的代码,模块间相互引用,为了方便也会使用相对路劲,或者别名(alias);这中间如果能使得 Webpack 更快寻找到目标,将对打包速度产生很是积极的影响。于此,我们需要做的即:减小文件搜索范围,从而提升速度;实现这一点,可以有如下两法:

配置 resolve.modules

Webpackresolve.modules配置模块库(即 node_modules)所在的位置,在 js 里出现 import 'vue' 这样不是相对、也不是绝对路径的写法时,会去 node_modules 目录下找。但是默认的配置,会采用向上递归搜索的方式去寻找,但通常项目目录里只有一个 node_modules,且是在项目根目录,为了减少搜索范围,可以直接写明 node_modules 的全路径;同样,对于别名(alias)的配置,亦当如此:

function resolve (dir) {
  return path.join(__dirname, '..', dir)
}
module.exports = {
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    modules: [
      resolve('src'),
      resolve('node_modules')
    ],
    alias: {
      'vue$': 'vue/dist/vue.common.js',
      'src': resolve('src'),
      'assets': resolve('src/assets'),
      'components': resolve('src/components'),
      // ...
      'store': resolve('src/store')
    }
  },
  ...
}

需要额外补充一点的是,这是 Webpack2.* 以上的写法。在 1.* 版本中,使用的是 resolve.root,如今已经被弃用为 resolve.modules;同时被弃用的还有resolve.fallbackresolve.modulesDirectories

设置 test & include & exclude

Webpack 的装载机(loaders),允许每个子项都可以有以下属性:

test:必须满足的条件(正则表达式,不要加引号,匹配要处理的文件)
exclude:不能满足的条件(排除不处理的目录)
include:导入的文件将由加载程序转换的路径或文件数组(把要处理的目录包括进来)
loader:一串“!”分隔的装载机(2.0版本以上,”-loader”不可以省略)
loaders:作为字符串的装载器阵列

对于include,更精确指定要处理的目录,这可以减少不必要的遍历,从而减少性能损失。同样,对于已经明确知道的,不需要处理的目录,则应该予以排除,从而进一步提升性能。假设你有一个第三方组件的引用,它肯定位于node_modules,通常它将有一个 src 和一个 dist 目录。如果配置 Webpack 来排除 node_modules,那么它将从 dist 已经编译的目录中获取文件。否则会再次编译它们。故而,合理的设置 include & exclude,将会极大地提升 Webpack 打包优化速度,比如像这样:

module: {
  preLoaders: [
    {
      test: /\.js$/,
      loader: 'eslint',
      include: [resolve('src')],
      exclude: /node_modules/
    },
    {
      test: /\.svg$/,
      loader: 'svgo?' + JSON.stringify(svgoConfig),
      include: [resolve('src/assets/icons')],
      exclude: /node_modules/
    }
  ],
  loaders: [
    {
      test: /\.vue$/,
      loader: 'vue-loader',
      include: [resolve('src')],
      exclude: /node_modules\/(?!(autotrack|dom-utils))|vendor\.dll\.js/
    },
    {
      test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
      loader: 'url',
      exclude: /assets\/icons/,
      query: {
        limit: 10000,
        name: utils.assetsPath('img/[name].[hash:7].[ext]')
      }
    }
  ]
}

增强代码代码压缩工具

Webpack 默认提供的 UglifyJS 插件,由于采用单线程压缩,速度颇慢 ;推荐采用 webpack-parallel-uglify-plugin 插件,她可以并行运行 UglifyJS 插件,更加充分而合理的使用 CPU 资源,这可以大大减少的构建时间;当然,该插件应用于生产环境而非开发环境,其做法如下,

new webpack.optimize.UglifyJsPlugin({
  compress: {
    warnings: false
  },
  sourceMap: true
})

替换如上自带的 UglifyJsPlugin 写法为如下配置即可:

var ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
new ParallelUglifyPlugin({
  cacheDir: '.cache/',
  uglifyJS:{
    output: {
      comments: false
    },
    compress: {
      warnings: false
    }
  }
})

当然也有其他同类型的插件,比如:webpack-uglify-parallel,但根据自己实践效果来看,并没有 webpack-parallel-uglify-plugin 表现的那么卓越,有兴趣的朋友,可以更全面的做下对比,择优选用。需要额外说明的是,webpack-parallel-uglify-plugin 插件的运用,会相对 UglifyJsPlugin 打出的包,看起来略大那么一丢丢(其实可以忽略不计);如果在你使用时也是如此,那么在打包速度跟包体积之间,你应该有自己的抉择。

用 Happypack 来加速代码构建

你知道,Webpack 中为了方便各种资源和类型的加载,设计了以 loader 加载器的形式读取资源,但是受限于 nodejs 的编程模型影响,所有的 loader 虽然以 async 的形式来并发调用,但是还是运行在单个 node 的进程,以及在同一个事件循环中,这就直接导致了些问题:当同时读取多个loader文件资源时,比如babel-loader需要 transform 各种jsxes6的资源文件。在这种同步计算同时需要大量耗费 cpu 运算的过程中,node的单进程模型就无优势了,而 Happypack 就是针对解决此类问题而生的存在。

Webpack-Happypack

Webpack-Happypack

Happypack 的处理思路是:将原有的 webpackloader 的执行过程,从单一进程的形式扩展多进程模式,从而加速代码构建;原本的流程保持不变,这样可以在不修改原有配置的基础上,来完成对编译过程的优化,具体配置如下:

var HappyPack = require('happypack');
var happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
module: {
  loaders: [
    {
      test: /\.js[x]?$/,
      include: [resolve('src')],
      exclude: /node_modules/,
      loader: 'happypack/loader?id=happybabel'
    }
  ]
},
plugins: [
  new HappyPack({
    id: 'happybabel',
    loaders: ['babel-loader'],
    threadPool: happyThreadPool,
    cache: true,
    verbose: true
  })
]

可以研究看到,通过在 loader 中配置直接指向 happypack 提供的 loader,对于文件实际匹配的处理 loader,则是通过配置在 plugin 属性来传递说明,这里 happypack 提供的 loaderplugin 的衔接匹配,则是通过id=happybabel来完成。配置完成后,laoder的工作模式就转变成了如下所示:

Webpack-Happypack

Webpack-Happypack

Happypack 在编译过程中,除了利用多进程的模式加速编译,还同时开启了 cache 计算,能充分利用缓存读取构建文件,对构建的速度提升也是非常明显的;更多关于 happyoack 个中原理,可参见 @淘宝前端团队(FED) 的这篇:happypack 原理解析。如果你使用的 Vue.js 框架来开发,也可参考 vue-webpack-happypack 相关配置。

设置 babel 的 cacheDirectory 为true

babel-loader is slow! 所以不仅要使用excludeinclude,尽可能准确的指定要转化内容的范畴,而且要充分利用缓存,进一步提升性能。babel-loader 提供了 cacheDirectory特定选项(默认 false):设置时,给定的目录将用于缓存加载器的结果。 未来的 Webpack 构建将尝试从缓存中读取,以避免在每次运行时运行潜在昂贵的 Babel 重新编译过程。如果值为空(loader: ‘babel-loader?cacheDirectory’)或true(loader: babel-loader?cacheDirectory=true),node_modules/.cache/babel-loadernode_modules 在任何根目录中找不到任何文件夹时,加载程序将使用默认缓存目录或回退到默认的OS临时文件目录。实际使用中,效果显著;配置示例如下:

rules: [
  {
    test: /\.js$/,
    loader: 'babel-loader?cacheDirectory=true',
    exclude: /node_modules/,
    include: [resolve('src'), resolve('test')]
  },
  ... ...
]

设置 noParse

如果你确定一个模块中,没有其它新的依赖,就可以配置这项, Webpack 将不再扫描这个文件中的依赖,这对于比较大型类库,将能促进性能表现,具体可以参见以下配置:

module: {
  noParse: /node_modules\/(element-ui\.js)/,
  rules: [
    {
      ...
    }
}

拷贝静态文件

在前文 Webpack 打包优化之体积篇中提到,引入 DllPluginDllReferencePlugin 来提前构建一些第三方库,来优化 Webpack 打包。而在生产环境时,就需要将提前构建好的包,同步到 dist 中;这里拷贝静态文件,你可以使用 copy-webpack-plugin 插件:把指定文件夹下的文件复制到指定的目录;其配置如下:

var CopyWebpackPlugin = require('copy-webpack-plugin')
plugins: [
  ...
  // copy custom static assets
  new CopyWebpackPlugin([
    {
      from: path.resolve(__dirname, '../static'),
      to: config.build.assetsSubDirectory,
      ignore: ['.*']
    }
  ])
]

当然,这种工作,实现的法子很多,比如可以借助 shelljs,可以参见这里的实现 vue-boilerplate-template

Webpack 打包优化之体积篇

Published on:

文章来源:https://jeffjade.com/2017/08/06/124-webpack-packge-optimization-for-volume/ 作者:@晚晴幽草轩轩主

谈及如今欣欣向荣的前端圈,不仅有各类框架百花齐放,如VueReactAngular等等,就打包工具而言,发展也是如火如荼,百家争鸣;从早期的王者Browserify, Grunt,到后来赢得宝座的 Gulp, 以及独树一帜的 fis3, 以及下一代打包神器 Rollup ;在 browserify,grunt,gulp,rollup,webpack 可以一窥其中部分对比。在本文要探究的是,当前打包工具绝对霸者 Webpack

Webpack Package optimization

Webpack Package optimization

Webpack,当前各大主流框架默认配备的打包方案,对其如何使用,已有较完备中英文文档;并且,各主流框架也有对应 CLI 予以基础配置,故不作为探讨范畴。从产品层来讲,如何使得构建的包体积小、运行快,这有必要不断摸索实践,提炼升级,使之臻于最佳。本文将从以下些许方面,对 Webpack 打包体积方面,做下优化探讨(备注: Webpack实践版本: 3.3.0):

定位 webpack 大的原因

这里推荐使用 webpack-bundle-analyzer —— Webpack 插件和 CLI 实用程序,她可以将内容束展示为方便交互的直观树状图,让你明白你所构建包中真正引入的内容;我们可以借助她,发现它大体有哪些模块组成,找到不合时宜的存在,然后优化它。我们可以在 项目的 package.json 文件中注入如下命令,以方便运行她(npm run analyz),默认会打开 http://127.0.0.1:8888 作为展示。

“analyz”: “NODE_ENV=production npm_config_report=true npm run build”

webpack-bundle-analyzer

webpack-bundle-analyzer

当然,同类型的还有 webpack-chart 以及 webpack-analyse,这两个站点也是以可视方式呈现构造的组件,可以让你清楚的看到模块的组成部分;不过稍显麻烦的是,你需要运行以下命令,生成工具分析所需要的 json 文件:

webpack --profile --json > stats.json
// 如果,运行指定的 weboack 文件,可用此命令
webpack --config build/webpack.prod.conf.js  --profile --json > stats.json

引入 DllPlugin 和 DllReferencePlugin

DllPlugin 和 DllReferencePlugin 提供了以大幅度提高构建时间性能的方式拆分软件包的方法。其中原理是,将特定的第三方NPM包模块提前构建👌,然后通过页面引入。这不仅能够使得 vendor 文件可以大幅度减小,同时,也极大的提高了构件速度。鉴于篇幅,具体用法可参见:webpack.dll.conf.js

外部引入模块(CDN)

如今前端开发,自然是使用ES6甚至更高版本,撸将起来才更嗨。但由于浏览器兼容问题,仍得使用 babel 转换。而这 babel-polyfill 也得引入以确保兼容;还比如项目开发中常用到的 moment, lodash等,都是挺大的存在,如果必须引入的话,即考虑外部引入之,再借助 externals 予以指定, webpack可以处理使之不参与打包,而依旧可以在代码中通过CMDAMD或者window/global全局的方式访问。

// webpack 中予以指定
externals: {
  // 'vue': 'Vue',
  // 'lodash': '_',
  'babel-polyfill': 'window'
}
//
<script src="//cdn.bootcss.com/autotrack/2.4.1/autotrack.js"></script>
<script src="//cdn.bootcss.com/babel-polyfill/7.0.0-alpha.15/polyfill.min.js"></script>

需要补充的是 externals 中:key 是 require 的包名,value 是全局的变量。

让每个第三包“引有所值”

确定引入的必要性

前端发展到如今时期,倘若项目采用了 MVVM模式框架,数据双向绑定,那么像 jQuery 这般类库,不能说没有丝毫引入的必要,至少可以说确实没有引入的必要。对此,如果还有些顾虑,完全可以参考下 YOU MIGHT NOT NEED JQUERY;用原生写几行代码就可以解决的事儿,实在不易引入这么个庞然大物,平添烦恼。

避免类库引而不用

倘若这类情况发生,对整个打包体积,不仅大而且亏。项目一旦大了,很难人为保证每个引入的类库,都被有用到,尤其是二次开发。所以工具的利用十分必要,强烈推荐类如 Eslint 这般工具,并且注入对应规则,对声明却未使用的代码,给予强制提醒;这不仅可以有效的规避类似情形发生(也适用于普通变量的检测),而且还能使得团队代码风格,尽可能地保持相似;要知道代码足够遵守规则,也可让压缩工具更有效压缩代码,一举多得,何乐不为?

尽量使用模块化引入

如果说 jQuery 确实没有引入必要,很多人会同意;但对于 lodash 这类依赖的工具,并不是所有人都会去造一发轮子的。然而全包引入 400kb 的体量,可否有让你心肝一颤?幸好的是,lodash 提供了模块化的引入方式;可按需引入,快哉快哉:

import { debounce } from 'lodash'
import { throttle } from 'lodash'
// 改成如下写法
import debounce from 'lodash/debounce'
import throttle from 'lodash/throttle'

擅懒如你的优秀程序员,是否也发现这样写颇为麻烦?那么恭喜你,这个问题已经被解决;lodash-webpack-pluginbabel-plugin-lodash 的存在(组合使用),即是解决这问题的。它可将全路径引用的 lodash, 自动转变为模块化按使用引入(如下例示);并且所需配置也十分简单,就不在此赘述(温馨提示:当涉及些特殊方法时,尚需些留意)。

// 引入组件,自动转换
import _ from 'lodash'
_.debounce()
_.throttle()

额外补充的是,即便采用如上写法,还是不够快捷,每个用到的文件,都写一遍 import,实在多有不便。更可取的是,将项目所需的方法,统一引入,按需添加,组建出本地 lodash 类库,然后 export 给框架层(比如 Vue.prototype),以便全局使用;详情可参见:vue-modular-import-lodash

// helper 文件夹下 lodash,统一引入你需要的方法
import _ from 'lodash'
export default {
  cloneDeep: _.cloneDeep,
  debounce: _.debounce,
  throttle: _.throttle,
  size: _.size,
  pick: _.pick,
  isEmpty: _.isEmpty
}
// 注入到全局
import _ from '@helper/lodash.js'
Vue.prototype.$_ = _
// vue 组件内运用
this.$_.debounce()

尽可能引入更合适的包

作为前端开发的你,想必知道有 momentjs 的存在(Parse, validate, manipulate, and display dates in javascript.);更多的是,你想必知道它很好用,然而它的体态却十分丰满(丰盈),没念及此,是否有重新造轮子的冲动?SpaceTime: A lightweight way to manipulate, traverse, compare, and format dates and times across planet Earth。 具有与 monent 相似 api 的新类库,其体积又相对小很多(当然,据观察其灵活度略逊一筹);date-fns:现代JavaScript日期实用程序库( Modern JavaScript date utility library ),如 lodash 一样,可支持模块化;知道这些或者更多的你,会如何选择?

按需异步加载模块

关于前端开发优化,重要的一条是,尽可能合并请求及资源,如常用的请求数据合并,压缩合并 js,构造雪碧图诸此等等(当然得适当,注意体积,过大不宜);但,同时也当因需制宜,根据需要去异步加载,避免无端就引入早成的浪费。webpack 也是内置对这方面的支持; 假如,你使用的是 Vue,将一个组件(以及其所有依赖)改为异步加载,所需要的只是把:

import Foo from './Foo.vue'

改为如下写法:

const Foo = () => import('./Foo.vue')

如此分割之时,该组件所依赖的其他组件或其他模块,都会自动被分割进对应的 chunk 里,实现异步加载,当然也支持把组件按组分块,将同组中组件,打包在同个异步 chunk 中。如此能够非常有效的抑制 Javascript 包过大,同时也使得资源的利用更加合理化。

生产环境,压缩混淆并移除console

现代化中等规模以上的开发中,区分开发环境测试环境生产环境,并根据需要予以区别对待,已然成为行业共识;可能的话,还会有预发布环境。对待生产环境,压缩混淆可以很有效的减小包的体积;同时,如果能够移除使用比较频繁的 console,而不是简单的替换为空方法,也是精彩的一笔小优化。如果使用 UglifyJsPlugin 插件来压缩代码,加入如下配置,即可移除掉代码中的 console

new webpack.optimize.UglifyJsPlugin({
  compress: {
    warnings: false,
    drop_console: true,
    pure_funcs: ['console.log']
  },
  sourceMap: false
})

Webpack3 新功能: Scope Hoisting

截止目前(17-08-06), Webpack 最新版本是 3.4.1;Webpack在 3.0 版本,提供了一个新的功能:Scope Hoisting,又译作“作用域提升”。只需在配置文件中添加一个新的插件,就可以让 Webpack 打包出来的代码文件更小、运行的更快:

module.exports = {
  plugins: [
    new webpack.optimize.ModuleConcatenationPlugin()
  ]
}

据悉这个 Scope HoistingTree Shaking,最初都是由 Rollup 实现的。在个人中实践中,这个功能的注入,对打包体积虽有影响,却不甚明显,有兴趣的盆友可以试下;更对关于此功能讯息,可参见 Webpack 3 的新功能:Scope Hoisting

下一篇 《Webpack 打包优化之速度篇

五种开源协议(GPL,LGPL,BSD,MIT,Apache)

Published on:

什么是许可协议?

什么是许可,当你为你的产品签发许可,你是在出让自己的权利,不过,你仍然拥有版权和专利(如果申请了的话),许可的目的是,向使用你产品的人提供 一定的权限。

不管产品是免费向公众分发,还是出售,制定一份许可协议非常有用,否则,对于前者,你相当于放弃了自己所有的权利,任何人都没有义务表明你的原始作 者身份,对于后者,你将不得不花费比开发更多的精力用来逐个处理用户的授权问题。

开源许可协议使这些事情变得简单,开发者很容易向一个项目贡献自己的代码,它还可以保护你原始作者的身份,使你 至少获得认可,开源许可协议还可以阻止其它人将某个产品据为己有。以下是开源界的 5 大许可协议。

GNU GPL

GNU General Public Licence (GPL) 有可能是开源界最常用的许可模式。GPL 保证了所有开发者的权利,同时为使用者提供了足够的复制,分发,修改的权利:

  • 可自由复制 你可以将软件复制到你的电脑,你客户的电脑,或者任何地方。复制份数没有任何限制。
  • 可自由分发 在你的网站提供下载,拷贝到U盘送人,或者将源代码打印出来从窗户扔出去(环保起见,请别这样做)。
  • 可以用来盈利 你可以在分发软件的时候收费,但你必须在收费前向你的客户提供该软件的 GNU GPL 许可协议,以便让他们知道,他们可以从别的渠道免费得到这份软件,以及你收费的理由。
  • 可自由修改 如果你想添加或删除某个功能,没问题,如果你想在别的项目中使用部分代码,也没问题,唯一的要求是,使用了这段代码的项目也必须使用 GPL 协议。

需要注意的是,分发的时候,需要明确提供源代码和二进制文件,另外,用于某些程序的某些协议有一些问题和限制,你可以看一下 @PierreJoye 写的 Practical Guide to GPL Compliance 一文。使用 GPL 协议,你必须在源代码代码中包含相应信息,以及协议本身。

GNU LGPL

GNU 还有另外一种协议,叫做 LGPL (Lesser General Public Licence),它对产品所保留的权利比 GPL 少,总的来说,LGPL 适合那些用于非 GPL 或非开源产品的开源类库或框架。因为 GPL 要求,使用了 GPL 代码的产品必须也使用 GPL 协议,开发者不允许将 GPL 代码用于商业产品。LGPL 绕过了这一限制。

BSD

BSD 在软件分发方面的限制比别的开源协议(如 GNU GPL)要少。该协议有多种版本,最主要的版本有两个,新 BSD 协议与简单 BSD 协议,这两种协议经过修正,都和 GPL 兼容,并为开源组织所认可。

新 BSD 协议(3条款协议)在软件分发方面,除需要包含一份版权提示和免责声明之外,没有任何限制。另外,该协议还禁止拿开发者的名义为衍生产品背书,但简单 BSD 协议删除了这一条款。

MIT

MIT 协议可能是几大开源协议中最宽松的一个,核心条款是:

该软件及其相关文档对所有人免费,可以任意处置,包括使用,复制,修改,合并,发表,分发,再授权,或者销售。唯一的限制是,软件中必须包含上述版 权和许可提示。

这意味着:

  • 你可以自由使用,复制,修改,可以用于自己的项目。
  • 可以免费分发或用来盈利。
  • 唯一的限制是必须包含许可声明。

MIT 协议是所有开源许可中最宽松的一个,除了必须包含许可声明外,再无任何限制。

Apache

Apache 协议 2.0 和别的开源协议相比,除了为用户提供版权许可之外,还有专利许可,对于那些涉及专利内容的开发者而言,该协议最适合(这里有 一篇文章阐述这个问题)。

Apache 协议还有以下需要说明的地方:

  • 永久权利 一旦被授权,永久拥有。
  • 全球范围的权利 在一个国家获得授权,适用于所有国家。假如你在美国,许可是从印度授权的,也没有问题。
  • 授权免费,且无版税 前期,后期均无任何费用。
  • 授权无排他性 任何人都可以获得授权
  • 授权不可撤消 一旦获得授权,没有任何人可以取消。比如,你基于该产品代码开发了衍生产品,你不用担心会在某一天被禁止使用该代码。

分发代码方面包含一些要求,主要是,要在声明中对参与开发的人给予认可并包含一份许可协议原文。

Creative Commons

Creative Commons (CC) 并非严格意义上的开源许可,它主要用于设计。Creative Commons 有多种协议,每种都提供了相应授权模式,CC 协议主要包含 4 种基本形式:

  • 署名权 必须为原始作者署名,然后才可以修改,分发,复制。
  • 保持一致 作品同样可以在 CC 协议基础上修改,分发,复制。
  • 非商业 作品可以被修改,分发,复制,但不能用于商业用途。但商业的定义有些模糊,比如,有的人认为非商业用途指的是不能销售,有的认为是甚至不能放在有广告的网 站,也有人认为非商业的意思是非盈利。
  • 不能衍生新作品 你可以复制,分发,但不能修改,也不能以此为基础创作自己的作品。

这些许可形式可以结合起来用,其中最严厉的组合是“署名,非商用,不能衍生新作品”,意味着,你可以分享作品,但不能改动或以此盈利,而且必须为原 作者署名。在这种许可模式下,原始作者对作品还拥有完全的控制权,而最宽松的组合是“署名”,意味着,只要为原始作者署名了,就可以自由处置。

因为在此之前,我用了国内的一些开源程序,但是呢这些程序都是需要商业授权的,不知道能不能免费的自己搭建起来给企业用。比如说 shopex,康盛的产品, PHPCMS等等。。。。 如果真用了,他们会找上门来问你要版权么?

利用Zipkin对Spring Cloud应用进行服务追踪分析

Published on:

文章转自:https://yq.aliyun.com/articles/60165 作者:@libinjingshan

摘要: 本文简单介绍了如何利用Zipkin对SpringCloud应用进行服务分析。在实际的应用场景中,Zipkin可以结合压力测试工具一起使用,分析系统在大压力下的可用性和性能。

设想这么一种情况,如果你的微服务数量逐渐增大,服务间的依赖关系越来越复杂,怎么分析它们之间的调用关系及相互的影响?

服务追踪分析

一个由微服务构成的应用系统通过服务来划分问题域,通过REST请求服务API来连接服务来完成完整业务。对于入口的一个调用可能需要有多个后台服务协同完成,链路上任何一个调用超时或出错都可能造成前端请求的失败。服务的调用链也会越来越长,并形成一个树形的调用链。

1.png

随着服务的增多,对调用链的分析也会越来越负责。设想你在负责下面这个系统,其中每个小点都是一个微服务,他们之间的调用关系形成了复杂的网络。

2.png

有密集恐惧症的同学就忽略吧。

针对服务化应用全链路追踪的问题,Google发表了Dapper论文,介绍了他们如何进行服务追踪分析。其基本思路是在服务调用的请求和响应中加入ID,标明上下游请求的关系。利用这些信息,可以可视化地分析服务调用链路和服务间的依赖关系。

Spring Cloud Sleuth和Zipkin

对应Dpper的开源实现是Zipkin,支持多种语言包括JavaScript,Python,Java, Scala, Ruby, C#, Go等。其中Java由多种不同的库来支持。

在这个示例中,我们准备开发两个基于Spring Cloud的应用,利用Spring Cloud Sleuth来和Zipkin进行集成。Spring Cloud Sleuth是对Zipkin的一个封装,对于Span、Trace等信息的生成、接入HTTP Request,以及向Zipkin Server发送采集信息等全部自动完成。

这是Spring Cloud Sleuth的概念图。

3.png

服务REST调用

本次演示的服务有两个:tracedemo做为前端服务接收用户的请求,tracebackend为后端服务,tracedemo通过http协议调用后端服务。

利用RestTemplate进行HTTP请求调用 tracedemo应用通过restTemplate调用后端tracedemo服务,注意,URL中指明tracedemo的地址为backend

@RequestMapping("/")
public String callHome(){
    LOG.log(Level.INFO, "calling trace demo backend");
    return restTemplate.getForObject("http://backend:8090", String.class);
}

后端服务响应HTTP请求,输出一行日志后返回经典的“hello world”。

@RequestMapping("/")
public String home(){
    LOG.log(Level.INFO, "trace demo backend is being called");
    return "Hello World.";
}

引入Sleuth和Zipkin依赖包

可以看到,这是典型的两个spring应用通过RestTemplate进行访问的方式,哪在HTTP请求中注入追踪信息并把相关信息发送到Zipkin Server呢?答案在两个应用所加载的JAR包里。

本示例采用gradle来构建应用,在build.gradle中加载了sleuth和zipkin相关的JAR包:

dependencies {
    compile('org.springframework.cloud:spring-cloud-starter-sleuth')
    compile('org.springframework.cloud:spring-cloud-sleuth-zipkin')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

Spring应用在监测到Java依赖包中有sleuth和zipkin后,会自动在RestTemplate的调用过程中向HTTP请求注入追踪信息,并向Zipkin Server发送这些信息。

哪么Zipkin Server的地址又是在哪里指定的呢?答案是在application.properties中:

spring.zipkin.base-url=http://zipkin-server:9411

注意Zipkin Server的地址为zipkin-server

构建Docker镜像

为这两个服务创建相同的Dockerfile,用于生成Docker镜像:

FROM java:8-jre-alpine
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/' /etc/apk/repositories
VOLUME /tmp
ADD build/libs/*.jar app.jar
RUN sh -c 'touch /app.jar'
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

构建容器镜像的步骤如下:

cd tracedemo
./gradlew build
docker build -t zipkin-demo-frontend .

cd ../tracebackend
./gradlew build
docker build -t zipkin-demo-backend .

构建镜像完成后用docker push命令上传到你的镜像仓库。

Zipkin Server

利用Annotation声明方式创建Zipkin 在build.gradle中引入Zipkin依赖包。

dependencies {
    compile('org.springframework.boot:spring-boot-starter')
    compile('io.zipkin.java:zipkin-server')
    runtime('io.zipkin.java:zipkin-autoconfigure-ui')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

在主程序Class增加一个注解@EnableZipkinServer

@SpringBootApplication
@EnableZipkinServer
public class ZipkinApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZipkinApplication.class, args);
    }
}

application.properties将端口指定为9411。

server.port=9411

构建Docker镜像

Dockerfile和前面的两个服务一样,这里就不重复了。

在阿里云容器服务上部署 创建docker-compose.yml文件,内容如下:

version: "2"
services:
  zipkin-server:
    image: registry.cn-hangzhou.aliyuncs.com/jingshanlb/zipkin-demo-server
    labels:
      aliyun.routing.port_9411: http://zipkin
    restart: always

  frontend:
    image: registry.cn-hangzhou.aliyuncs.com/jingshanlb/zipkin-demo-frontend
    labels:
      aliyun.routing.port_8080: http://frontend
    links:
      - zipkin-server
      - backend
    restart: always

  backend:
    image: registry.cn-hangzhou.aliyuncs.com/jingshanlb/zipkin-demo-backend
    links:
      - zipkin-server
    restart: always

在阿里云容器服务上使用编排模版创建应用,访问zipkin端点,可以看到服务分析的效果。

访问前端应用3次,页面显示3次服务调用。

4.png

点击其中任意一个trace,可以看到请求链路上不同span所花费的时间。

5.png

进入Dependencies页面,还可以看到服务之间的依赖关系。

6.png

从这个过程可以看出,Zipkin和Spring Cloud的集成做得很好。而且对服务追踪分析的可视化也很直观。

注意的是,在生产环境中还需要为Zipkin配置数据库,这里就不详细介绍了。

本文的示例代码在此:https://github.com/binblee/zipkin-demo

小节

本文简单介绍了如何利用Zipkin对SpringCloud应用进行服务分析。在实际的应用场景中,Zipkin可以结合压力测试工具一起使用,分析系统在大压力下的可用性和性能。这部分内容未来会在DevOps系列中继续介绍。

Spring Cloud学习-Eureka、Ribbon和Feign

Published on:

前沿

这篇文章比较适合入门,对于spring cloud生态的成员有一个大致的了解,其实spring cloud生态将netflix的产品进行了很好的整合,netflix早几年就在服务治理这块有很深入的研究,出品了很多服务治理的工具hystrix就是很有名的一个,具体可以查看:https://github.com/netflix,刚好在微服务盛行的年代服务治理是必不可少的一环,现在在微服务开发套件这块常用也就是下面这两种选择:

  1. spring cloud套件,成熟上手快
  2. 自建微服务架构
    1. UCM,统一配置管理(百度的disconf、阿里的diamond、点评的lion,等很多开源的)。
    2. RPC,阿里的Dubbo、点评的Pigeon,当当改的DubboX,grpc,等等很多开源的,还有很多公司自研的。
    3. 服务治理,netflix的hystrix老牌的功能强大的服务治理工具,有熔断、降级等功能,很多公司会结合监控套件开发自己的服务治理工具。
    4. 开发框架(rpc、restful这个一般公司都有自研的开发框架)
    5. 注册中心(zookeeper、redis、Consul、SmartStack、Eureka,其中一些已经是spring cloud生态的一员了)。
    6. 网关,restful的使用nginx+lua,这也是openAPI网关常用的手段
    7. 负载均衡,这个结合选用的rpc框架来选择。一般rpc框架都有负载均衡的功能。
    8. 服务治理熔断,使用hystrix(也已经是spring cloud生态的一员了)
    9. 监控,使用pinpoint、点评的cat、等其他开源的APM工具
    10. DevOPS,持续交付一般也是自己构架的,采用jenkins打包docker镜像,使用docker生态的工具构建容器化发布平台。

下面文章转自:https://www.jianshu.com/p/0aef3724e6bc 作者:@杜琪

Talk is cheap,show me the code , 书上得来终觉浅,绝知此事要躬行。在自己真正实现的过程中,会遇到很多莫名其妙的问题,而正是在解决这些问题的过程中,你会发现自己之前思维的盲点。

引子

看完《微服务设计》后,算是补上了自己在服务化这块的理论知识,在业界,一般有两种微服务的实践方法:基于dubbo的微服务架构、基于Spring Cloud的微服务架构。从概念上来讲,Dubbo和Spring Cloud并不能放在一起对比,因为Dubbo仅仅是一个RPC框架,实现Java程序的远程调用,实施服务化的中间件则需要自己开发;而Spring Cloud则是实施微服务的一系列套件,包括:服务注册与发现、断路器、服务状态监控、配置管理、智能路由、一次性令牌、全局锁、分布式会话管理、集群状态管理等。

在有赞,我们基于Dubbo实施服务化,刚开始是基于ZooKeeper进行服务注册与发现,现在已经转成使用Etcd。我这次学习Spring Cloud,则是想成体系得学习下微服务架构的实现,也许能够对基于Dubbo实施微服务架构有所借鉴。

Spring Cloud下有很多工程:

  • Spring Cloud Config:依靠git仓库实现的中心化配置管理。配置资源可以映射到Spring的不同开发环境中,但是也可以使用在非Spring应用中。
  • Spring Cloud Netflix:不同的Netflix OSS组件的集合:Eureka、Hystrix、Zuul、Archaius等。
  • Spring Cloud Bus:事件总线,利用分布式消息将多个服务连接起来。非常适合在集群中传播状态的改变事件(例如:配置变更事件)
  • Spring Cloud Consul:服务发现和配置管理,由Hashicorp团队开发。

我决定先从Spring Cloud Netflix看起,它提供了如下的功能特性:

  • 服务发现:Eureka-server实例作为服务提供者,可以注册到服务注册中心,Eureka客户端可以通过Spring管理的bean发现实例;
  • 服务发现:嵌套式的Eureka服务可以通过声明式的Java配置文件创建;
  • 断路器:利用注解,可以创建一个简单的Hystrix客户端;
  • 断路器:通过Java配置文件可以创建内嵌的Hystrix控制面板;
  • 声明式REST客户端:使用Feign可以创建声明式、模板化的HTTP客户端;
  • 客户端负载均衡器:Ribbon
  • 路由器和过滤器:Zuul可以在微服务架构中提供路由功能、身份验证、服务迁移、金丝雀发布等功能。

本文计划利用Eureka实现一个简答的服务注册于发现的例子,需要创建三个角色:服务注册中心、服务提供者、服务消费者。

实践

1. 服务注册中心

在IDEA中创建一个Spring Cloud工程,引入Eureka-Server包,pom文件整体如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example.springcloud</groupId>
    <artifactId>service-register</artifactId>
    <version>1.0-SNAPSHOT</version>


    <!-- spring boot的parent 配置文件,有大部分spring boot需要用的Jar包 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.1.RELEASE</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <!-- spring boot的maven打包插件 -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <!-- eureka-server -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
        </dependency>

        <!-- spring boot test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Camden.SR5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

在src/main/java包下创建一个名为hello的包,然后创建EurekaServiceRegisterApplication类,并用@EnableEurekaServer和@SpringBootApplication两个注解修饰,后者是Spring Boot应用都需要用的,这里不作过多解释;@EnableEurekaServer注解的作用是触发Spring Boot的自动配置机制,由于我们之前在pom文件中导入了eureka-server,spring boot会在容器中创建对应的bean。EurekaServiceRegisterApplication的代码如下:

package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

/**
 * Created by IntelliJ IDEA.
 * User: duqi
 * Date: 2017/3/2
 * Time: 20:02
 */

@EnableEurekaServer //通过@EnableEurekaServer启动一个服务注册中心给其他应用使用
@SpringBootApplication
public class EurekaServiceRegisterApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaServiceRegisterApplication.class, args);
    }
}

在application.properties中还需要增加如下配置,才能创建一个真正可以使用的服务注册中心。

#注册服务的端口号
server.port=8761

#是否需要注册到注册中心,因为该项目本身作为服务注册中心,所以为false
eureka.client.register-with-eureka=false
#是否需要从注册中心获取服务列表,原因同上,为false
eureka.client.fetch-registry=false
#注册服务器的地址:服务提供者和服务消费者都要依赖这个地址
eureka.client.service-url.defaultZone=http://localhost:${server.port}/eureka

logging.level.com.netflix.eureka=OFF
logging.level.com.netflix.discovery=OFF

启动注册服务,并访问:http://localhost:8761,就可以看到如下界面。

eureka

服务注册中心后台

2. 服务提供者

创建一个Spring Boot工程,代表服务提供者,该服务提供者会暴露一个加法服务,接受客户端传来的加数和被加数,并返回两者的和。

工程的pom文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example.springcloud</groupId>
    <artifactId>service-client</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.1.RELEASE</version>
        <relativePath/> 
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Camden.SR5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

其中的关键在于spring-cloud-starter-eureka这个Jar包,其中包含了eureka的客户端实现。

在src/main/java/hello下创建工程的主类EurekaServerProducerApplication,使用@EnableDiscoveryClient注解修饰,该注解在服务启动的时候,可以触发服务注册的过程,向配置文件中指定的服务注册中心(Eureka-Server)的地址注册自己提供的服务。EurekaServerProducerApplication的源码如下:

package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * Created by IntelliJ IDEA.
 * User: duqi
 * Date: 2017/3/2
 * Time: 20:34
 */
@EnableDiscoveryClient
@SpringBootApplication
public class EurekaServerProducerApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaServerProducerApplication.class, args);
    }

}

配置文件的内容如下:

#服务提供者的名字
spring.application.name=compute-service

#服务提供者的端口号
server.port=8888

#服务注册中心的地址
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/

服务提供者的基本框架搭好后,需要实现服务的具体内容,在ServiceInstanceRestController类中实现,它的具体代码如下:

package hello;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * Created by IntelliJ IDEA.
 * User: duqi
 * Date: 2017/3/2
 * Time: 20:36
 */
@RestController
public class ServiceInstanceRestController {

    private static final Logger logger = LoggerFactory.getLogger(ServiceInstanceRestController.class);

    @Autowired
    private DiscoveryClient discoveryClient; //服务发现客户端

    @GetMapping(value = "/add")
    public Integer add(@RequestParam Integer a, @RequestParam Integer b) {
        ServiceInstance instance = discoveryClient.getLocalServiceInstance();
        Integer r = a + b;
        logger.info("/add, host:" + instance.getHost() + ", service_id:" + instance.getServiceId() + ", result:" + r);
        return r;
    }
}

先启动服务注册中心的工程,然后再启动服务提供者,在访问:localhost:8761,如下图所示,服务提供者已经注册到服务注册中心啦。

eureka2

服务提供者注册到服务注册中心

在Spring Cloud Netflix中,使用Ribbon实现客户端负载均衡,使用Feign实现声明式HTTP客户端调用——即写得像本地函数调用一样。

3. 服务消费者-Ribbon

创建一个Spring boot工程,引入ribbon和eureka,pom文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example.springcloud</groupId>
    <artifactId>serviceconsumer</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.1.RELEASE</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <!-- 客户端负载均衡 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-ribbon</artifactId>
        </dependency>

        <!-- eureka客户端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>

        <!-- spring boot实现Java Web服务-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Camden.SR5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

创建EurekaConsumerApplication类,定义REST客户端实例,代码如下:

package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
 * Created by IntelliJ IDEA.
 * User: duqi
 * Date: 2017/3/2
 * Time: 22:55
 */
@EnableDiscoveryClient //开启服务发现的能力
@SpringBootApplication
public class EurekaConsumerApplication {

    @Bean //定义REST客户端,RestTemplate实例
    @LoadBalanced //开启负债均衡的能力
    RestTemplate restTemplate() {
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(EurekaConsumerApplication.class, args);
    }
}

application.properties中定义了服务注册中心的地址、消费者服务的端口号、消费者服务的名称这些内容:

#应用名称
spring.application.name=ribbon-consumer

#端口号
server.port=9000

#注册中心的地址
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/

消费者服务的入口为:ConsumerController,我们通过这个实例进行测试。消费者服务启动过程中,会从服务注册中心中拉最新的服务列表,当浏览器触发对应的请求,就会根据COMPUTE-SERVICE查找服务提供者的IP和端口号,然后发起调用。

package hello;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
 * Created by IntelliJ IDEA.
 * User: duqi
 * Date: 2017/3/2
 * Time: 22:58
 */
@RestController
public class ConsumerController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping(value = "/add")
    public String add() {
        return restTemplate.getForEntity("http://COMPUTE-SERVICE/add?a=10&b=20", String.class).getBody();
    }
}

首先启动服务注册中心,第二分别启动两个服务提供者(IP相同、端口不同即可),然后启动服务消费者。

eureka3

两个服务提供者

在浏览器里访问localhost:9000/add两次,可以看到请求有时候会在8888端口的服务,有时候会到8889的服务。具体背后选择的原理,还有待后续研究。

4. 服务消费者-Feign

使用类似restTemplate.getForEntity("http://COMPUTE-SERVICE/add?a=10&b=20",String.class).getBody()这样的语句进行服务间调用并非不可以,只是我们在服务化的过程中,希望跨服务调用能够看起来像本地调用,这也是我理解的Feign的使用场景。

创建一个spring boot工程,该工程的pom文件与上一节的类似,只是把ribbon的依赖换为feign的即可,代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example.springcloud</groupId>
    <artifactId>serviceconsumer</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.1.RELEASE</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <!-- Feign实现声明式HTTP客户端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
        </dependency>

        <!-- eureka客户端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>

        <!-- spring boot实现Java Web服务-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Camden.SR5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

首先创建应用程序启动类:EurekaConsumerApplication,代码如下:

package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;


/**
 * Created by IntelliJ IDEA.
 * User: duqi
 * Date: 2017/3/19
 * Time: 16:59
 */
@EnableDiscoveryClient //用于启动服务发现功能
@EnableFeignClients //用于启动Fegin功能
@SpringBootApplication
public class EurekaConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaConsumerApplication.class);
    }
}

然后定义远程调用的接口,在hello包下创建depend包,然后创建ComputeClient接口,使用@FeignClient(“COMPUTE-SERVICE”)注解修饰,COMPUTE-SERVICE就是服务提供者的名称,然后定义要使用的服务,代码如下:

package hello.depend;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * Created by IntelliJ IDEA.
 * User: duqi
 * Date: 2017/3/19
 * Time: 17:02
 */
@FeignClient("COMPUTE-SERVICE")
public interface ComputeClient {

    @RequestMapping(method = RequestMethod.GET, value = "/add")
    Integer add(@RequestParam(value = "a") Integer a, @RequestParam(value = "b") Integer b);
}

在ConsumerController中,像引入普通的spring bean一样引入ComputeClient对象,其他的和Ribbon的类似。

package hello;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import hello.depend.ComputeClient;

/**
 * Created by IntelliJ IDEA.
 * User: duqi
 * Date: 2017/3/19
 * Time: 17:06
 */
@RestController
public class ConsumerController {

    @Autowired
    private ComputeClient computeClient;

    @RequestMapping(value = "/add", method = RequestMethod.GET)
    public Integer add() {
        return computeClient.add(10, 20);
    }
}

application.properties的内容如下:

#应用名称
spring.application.name=fegin-consumer

#端口号
server.port=9000

#注册中心的地址
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/

启动fegin消费者,访问localhost:9000/add,也可以看到服务提供者已经收到了消费者发来的请求。

log1

请求到达服务提供者1

log2

请求到达服务提供者2

源码下载

参考资料

作者:杜琪 链接:http://www.jianshu.com/p/0aef3724e6bc 來源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

RedisClient使用说明

Published on:
Tags: Redis

项目地址

redis-client

   

Maven引入

<dependency>
  <groupId>cn.tsoft.framework</groupId>
  <artifactId>redis-client</artifactId>
  <version>1.1.0-SNAPSHOT</version>
</dependency>

Spring引入

<import resource="classpath:spring-redis.xml" />

Api使用说明

ps.本次版本增加了namespace、泛型的支持(存、取直接使用java对象),namespace可以有效的避免key名称冲突和对以后做sharding提供了基础,泛型则是提升使用友好度,本次版本包装了驱动(jedis)的95%的方法,有一些性能不好的方法没有开放,新增了一些使用上更加友好的方法。

常规操作的command实现:RedisClientImpl

二进制操作的command实现:BinaryRedisClientImpl

两者都支持直接存、取java对象,区别在于前者序列化为json以string的方式发送到redis服务器,后者序列化为byte[]以字节方式发送到redis服务,通过redis-cli工具前者可以很明确的看到存的值,后者看到的是二进制编码。

接口方法

1.png

回调接口

cn.tsoft.framework.redis.callback.GetDataCallBack

接口提供两个方法

    /**
     * ttl时间,不是所有命令都支持ttl设置
     * */
    int getExpiredTime();
 
    /**
     * 执行回调方法
     */
    R invoke();

ps.int getExpiredTime();这个方法并不是所有命令都支持(hget系列不支持,因为hash的attr是不支持ttl设置的,ttl必须设置在hash的key上并不是hash的attr上),因此不支持ttl的命令就采用默认的空实现。 在使用get*和hget*方法时,如果key返回为null,则通过该接口的R invoke();方法获取数据并放到redis中。 hgetAllObjects方法上的GetDataCallBack gbs参数是无效的传入null即可。 如果在get方法获取不到值时不想走数据回调时传入null即可。

示例:

//不设置回调
Metadata resule = redisClient.get(bizkey, nameSpace, Metadata.class, null);
 
List<Metadata > resule = redisClient.get(bizkey, nameSpace, new TypeReference<List<Metadata>>() {}, null);
 
//设置回调
List<Long> resule = redisClient.get(bizkey, nameSpace, new TypeReference<List<Long>>() {}, new GetDataCallBack<List<Long>>() {
            @Override
            public int getExpiredTime() {
                return 3600;
            }
            @Override
            public List<Long> invoke() {
                return getMetadataSourceProvider().getUserRoles(uid);
            }
        });
 
List<Long> resule = redisClient.hgetObject(bizkey, nameSpace, String.valueOf(uid), new TypeReference<List<Long>>() {}, new GetDataCallBack<List<Long>>() {
            @Override
            public int getExpiredTime() {
                return 0;
            }
            @Override
            public List<Long> invoke() {
                return getMetadataSourceProvider().getUserRoles(uid);
            }
        });

参数说明

get*方法的参数Class value和TypeReference type的区别,前者不支持嵌套泛型,后者支持嵌套泛型,举一个例子说明

Metadata value = redisClient.get(bizkey, nameSpace, Metadata.class, null);
 
List<Metadata> list = redisClient.get(bizkey, nameSpace, new TypeReference<List<Metadata>>(){}, null);

综合使用示例

redisClient.set(bizkey, namespace, new Metadata(), 60);//set并设置ttl60秒
redisClient.set(bizkey, namespace, new Metadata(), -1);//set不设置ttl
redisClient.setnx(bizkey, namespace, "aaaa");//key不存在时才设置值
redisClient.setex(bizkey, namespace, 60, new Metadata());//set一个key并设置ttl60秒,等价于第一行的用法
//setbit和setrange用法不多做说明,参考redis.io上面的command说明
 
redisClient.get(bizkey, namespace, new GetDataCallBack<String>(){
            @Override
            public int getExpiredTime() {
                return 60;
            }
            @Override
            public String invoke() {
                return "aaaa";
            }
});//获取,找不到取数据并set进去
 
redisClient.get(bizkey, namespace, Metadata.class, new GetDataCallBack<Metadata>(){
            @Override
            public int getExpiredTime() {
                return 60;
            }
            @Override
            public Metadata invoke() {
                return new Metadata();
            }
});//获取值,类型:Metadata
 
redisClient.get(bizkey, namespace, new TypeReference<List<Metadata.class>>(){}, new GetDataCallBack<List<Metadata>>(){
            @Override
            public int getExpiredTime() {
                return 60;
            }
            @Override
            public List<Metadata> invoke() {
                return new ArrayList<Metadata>;
            }
});//获取值,类型:List<Metadata>
 
redisClient.get(bizkey, namespace, new TypeReference<List<Metadata.class>>(){}, null);//获取值,类型:List<Metadata>
 
//getbit、getrange、getSet、hget、hgetAll、hgetObject、hgetAllObjects,用法不多做说明,参考redis.io上面的command说明
 
//管道,批量发送多条命令,但是不支持namespace需要手动添加namespace
Pipeline pipelined = redisClient.pipelined();
pipelined.set(key, value);
pipelined.get(key);
pipelined.syncAndReturnAll(); //发送命令并接受返回值
pipelined.sync();//发送命令不接受返回值
 
//其他z*、incr、decr、h*、s*命令不做说明,参考redis.io上面的command说明