Winse Blog

走走停停都是风景, 熙熙攘攘都向最好, 忙忙碌碌都为明朝, 何畏之.

Jasperreports使用小结

最近接了一个报表的任务,原来也有接触过,但是仅限于了解没有真正的动手画过。这里把从选型到最后成型一路下来遇到的问题整理下。

主要两个大问题:环境的搭建,以及子报表(嵌套报表)的配置。

选择Jasperreports

知道的有Birts、Pentaho、FineReport感觉其实都差不多,大家各自都取长补当更多。由于一穷二白的,没有弄过。网上找了和SpringMVC结合的都是Jasperreport的文章,就这么草率的定下来了。

基本的操作都类似,报表HelloWorld还是比较简单的。下载jaspersoftstudio最新版,然后了解各个区域的作用。

一个完全的报表模板包括如下几个区域:title, pageHeader, columnHeader, groupHeader, detail, groupFooter, columnFoter, pageFooter, summary

整合SpringMVC

原来做报表的同时都是直接连数据库的,过程中遇到各种问题。很多没法维护的事情发生:改个字段,数据库测试/生产链接等等。我这里直接选择通过JavaBean来传递数据。

贴代码之前先说PDF报表字体的问题,本来报表是加粗的,但是服务器生成浏览的时刻没有效果。发现还需要单独添加字体的包:pdf - Bold not working in Jaspersoft Studio for fonts other than sans serif

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#maven
<properties>
      <spring.version>4.2.5.RELEASE</spring.version>
...
      <dependency>
          <groupId>net.sf.jasperreports</groupId>
          <artifactId>jasperreports</artifactId>
          <version>6.3.1</version>
      </dependency>
      <dependency>
          <groupId>net.sf.jasperreports</groupId>
          <artifactId>jasperreports-fonts</artifactId>
          <version>6.0.0</version>
      </dependency>
      <dependency>
          <groupId>com.itextpdf</groupId>
          <artifactId>itextpdf</artifactId>
          <version>5.5.10</version>
      </dependency>
      <dependency>
          <groupId>com.itextpdf</groupId>
          <artifactId>itext-asian</artifactId>
          <version>5.2.0</version>
      </dependency>
      <dependency>
          <groupId>org.codehaus.groovy</groupId>
          <artifactId>groovy-all</artifactId>
          <version>2.4.7</version>
      </dependency>

# spring mvc xml
  <bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
      <property name="order" value="0"></property>
      <property name="basename" value="views"></property>
  </bean>
  <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
      <property name="order" value="1"></property>
      <property name="viewClass">
          <value>org.springframework.web.servlet.view.JstlView</value>
      </property>
...

# views.properties
HELLO.(class)=org.springframework.web.servlet.view.jasperreports.JasperReportsPdfView 
HELLO.url=/WEB-INF/report/helloworld.jasper
HELLO.reportDataKey=datasource

# java Controller
@Controller
@RequestMapping("/report")
public class HelloReportController {

  @RequestMapping("/hello.pdf")
  public ModelAndView printExpress() {
      ModelAndView mv = new ModelAndView("HELLO");

      // 如果直接传对象bean不行,需要使用list传值
      List<HelloWorldData> list = new ArrayList<>();
      list.add(new HelloWorldData("jarperreport", "Hi"));

      mv.addObject("datasource", list);
      return mv;
  }

最后通过浏览器就能查看报表的PDF文件了。

前端所有的页面都是通过ajax来获取展示的,这里通过jquery-media.js来进行展示(生成内嵌的iframe),这也是上面的地址加上pdf后缀的原因。

1
2
3
4
5
6
7
# html
<div class="modal-body" style="max-height: 900px; padding: 10px;">
  <a class="media" href="${contextPath}${url}"></a>
</div>

# jquery
$('a.media', $modal).media({width:"100%", height:600});

子报表

有一个结账的报表,既要展示汇总信息,还得把详情列表也输出出来。一开始的JavaBean:

1
2
3
4
5
6
7
8
9
10
public class InvoiceData {
  private String roomNo;
  private List<InvoiceDetailData> details;
  ...
}
public class InvoiceDetailData {
  private String date;
  private String amount;
  ...
}

但是简单报表是一维的,不能展示list里面的内容。网上一堆资料都是简单的案例,涉及多维度的就Table、Crosstable、Subreport这几个控件。Table的样式调起来麻烦,数据也不知道怎么搞。子报表至少看起来合乎逻辑,操作起来也简单。画好图标以及把对应的字段对应好后,子报表的Datasource直接填 $F{details}

修改views.properties,写好controller后,启动竟然报找不到details子报表路径。根据文章修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 主报表
# 类型必须加哦!
  <parameter name="DetailSubReport" class="net.sf.jasperreports.engine.JasperReport"/>
  ...
  <subreport>
      <reportElement stretchType="RelativeToBandHeight" x="0" y="0" width="520" height="167" uuid="8d69d85b-4fcf-482a-836c-c1698ce42dcd"/>
      <dataSourceExpression><![CDATA[$F{details}]]></dataSourceExpression>
      <subreportExpression><![CDATA[$P{DetailSubReport}]]></subreportExpression>
  </subreport>
  
# views.properties
Invoice.(class)=org.springframework.web.servlet.view.jasperreports.JasperReportsPdfView 
Invoice.url=/WEB-INF/report/invoice.jasper
Invoice.reportDataKey=datasource
Invoice.subReportUrls=DetailSubReport=/WEB-INF/report/InvoiceDetail.jasper

罗马建成非一日之功,再次编译启动后,再次报错,这次的是类型错误。感觉正在慢慢向成功靠近。修改类型后最后启动展示搞定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# javabean
public class InvoiceData {
  private JRDataSource details;
  
  public void setDetails(JRDataSource details) {
      this.details = details;
  }

  public void setDetails(List<InvoiceDetailData> details) {
      this.details = new JRBeanCollectionDataSource(details);
  }

# 主报表
  <field name="details" class="net.sf.jasperreports.engine.JRDataSource">
      <fieldDescription><![CDATA[details]]></fieldDescription>
  </field>

–END

Play2开发环境搭建

用惯了MAVEN后,在用SBT真有种生不如死的感觉。Maven更沉稳成熟些,SBT感觉首先不熟(入门也没maven简单)并且随性。

好了抱怨了这么多。入题,主要碰到的就是两个问题:

  1. Play2的HelloWorld主要卡在网络(也就是sbt的配置);
  2. 导入Eclipse。由于有Maven缺各种插件的体验,这里直接用官网的生成好.class/.project再导入已经存在的项目。

接下来一步步的介绍环境的搭建。

下载Play2和SBT

下载官网的Offline Distribution ,解压后把 activator-dist-1.3.12/repository 的所有文件拷贝到 ~/.ivy2/cache 。反正都会下载到这个目录,拷贝更快。

下载SBT ,下载zip就好。

配置

  1. 在 activator-dist-1.3.12 创建 conf/sbtconfig.txt 。同时在 sbt/conf/sbtconfig.txt 加上同样的语句:
1
-Dsbt.override.build.repos=true
  1. 添加获取jar的repo地址,新建 ~/.sbt/repositories 文件
1
2
3
4
5
6
7
8
9
10
[repositories]
  local
  local-maven: file:///D:/maven/.m2/repository/
  cu: http://cu1:8081/nexus/content/groups/public/
  #oschina: http://maven.oschina.net/content/groups/public/
  jcenter: https://jcenter.bintray.com/
  typesafe-ivy-releases: https://repo.typesafe.com/typesafe/ivy-releases/, [organization]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext], bootOnly
  maven-central
  ivy-typesafe: http://dl.bintray.com/typesafe/ivy-releases, [organization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext]
  ivy-sbt-plugin: http://dl.bintray.com/sbt/sbt-plugin-releases/, [organization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext]

配置相关参考:

创建新项目

添加环境变量自己主动点,activator和sbt都加一下。然后运行 activator new 根据模板创建项目。也可以参考官网的直接写build.sbt。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
R:\>activator new helloworld play-java
ACTIVATOR_HOME=E:\local\usr\share\activator-dist-1.3.12

Fetching the latest list of templates...

OK, application "helloworld" is being created using the "play-java" template.

To run "helloworld" from the command line, "cd helloworld" then:
R:\\helloworld/activator run

To run the test for "helloworld" from the command line, "cd helloworld" then:
R:\\helloworld/activator test

To run the Activator UI for "helloworld" from the command line, "cd helloworld" then:
R:\\helloworld/activator ui

创建好项目后,运行 activator run 看看效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
R:\helloworld>activator run
ACTIVATOR_HOME=E:\local\usr\share\activator-dist-1.3.12
[info] Loading project definition from R:\helloworld\project
[info] Updating {file:/R:/helloworld/project/}helloworld-build...
[info] Resolving org.fusesource.jansi#jansi;1.4 ...
[info] Done updating.
[info] Set current project to helloworld (in build file:/R:/helloworld/)
[info] Updating {file:/R:/helloworld/}root...
[info] Resolving jline#jline;2.12.1 ...
[info] Done updating.

--- (Running the application, auto-reloading is enabled) ---

[info] p.c.s.NettyServer - Listening for HTTP on /0:0:0:0:0:0:0:0:9000

(Server started, use Ctrl+D to stop and go back to the console...)

打开浏览器访问 http://localhost:9000 ,访问的时刻可能会实时的编译会等一段时间。

导入eclipse

前面已经把helloworld跑起来了,接下来是把功能导入eclipse。直接导入或者手动加classpath挺麻烦的,play的一些配置会最终会编译class的。

这里使用 sbteclipse 来生成 eclipse 项目需要的文件。

需要配置二个文件,先添加插件、然后修改配置。

在 helloworld/project/plugins.sbt 最后添加 sbteclipse 插件:

1
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.0.1")

在 helloworld/build.sbt 最后添加配置:

1
2
3
4
5
6
7
import com.typesafe.sbteclipse.plugin.EclipsePlugin.EclipseKeys
// Compile the project before generating Eclipse files, so that generated .scala or .class files for views and routes are present
EclipseKeys.preTasks := Seq(compile in Compile)
EclipseKeys.projectFlavor := EclipseProjectFlavor.Java           // Java project. Don't expect Scala IDE
EclipseKeys.createSrc := EclipseCreateSrc.Default + EclipseCreateSrc.ManagedClasses // Use .class files instead of generated .scala files for views and routes
EclipseKeys.withSource := false
EclipseKeys.withJavadoc := false

然后用 sbt eclipse 生成IDE项目所需文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
R:\helloworld>sbt
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=256m; support was removed in 8
[info] Loading project definition from R:\helloworld\project
[info] Updating {file:/R:/helloworld/project/}helloworld-build...
[info] Resolving org.fusesource.jansi#jansi;1.4 ...
[info] Done updating.
[info] Set current project to helloworld (in build file:/R:/helloworld/)

[helloworld] $ eclipse
[info] About to create Eclipse project files for your project(s).
[info] Updating {file:/R:/helloworld/}root...
[info] Resolving jline#jline;2.12.1 ...
[info] Done updating.
[info] Compiling 6 Scala sources and 10 Java sources to R:\helloworld\target\scala-2.11\classes...
[info] Successfully created Eclipse project files for project(s):
[info] helloworld

[helloworld] $ compile
[success] Total time: 3 s, completed 2016-11-10 13:11:49

[helloworld] $ eclipse
[info] About to create Eclipse project files for your project(s).
[info] Successfully created Eclipse project files for project(s):
[info] helloworld
[helloworld] $

我这是专门重新弄的一个工程,依赖是原来已经下载好了的(下载需要等一段时间)。

然后导入已经存在的项目即可。看最终效果图:

–END

红帽6升级SSH

安全检查报告SSH版本太低存在漏洞需要进行升级,但是红帽没有现成的高版本打包好SSH的rpm。自己动手丰衣足食,没有网上一些朋友遇到那么多的问题,但是也是这纠结过程。

搞一台身边的机器测试安装,如果远程机器搞不好就连不上了!!先手动编译安装一遍,把编译需要的依赖都安装好,然后再rpmbuild就会比较顺利。

运维同事编译安装的步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
加载光盘的packages
# mount -o loop disk1.iso /mnt/disk
# vi /etc/yum.repo.d/local.repo

[root@localhost SOURCES]# ll *.tar.gz
-rw-r--r--. 1 root   root   1499808 3月  10 2016 openssh-7.2p2.tar.gz
-rw-r--r--. 1 root   root   5489494 10月 20 16:41 openssl-1.0.2j.tar.gz
-rw-r--r--. 1 root   root     29229 6月  26 2004 x11-ssh-askpass-1.2.4.1.tar.gz

一、升级 Zlib
1、下载最新版本 Zlib
# ./configure --prefix=/usr/local/zlib
本次遇到GCC未安装
yum -y install gcc
./configure --prefix=/usr/local/zlib
# make
# make install
这样,就把 zlib 编译安装在 /usr/local/zilib 中了。
二、升级 OpenSSL
1、下载最新版本 OpenSSL
which openssl    查看到当前是在/usr/bin/openssl
# ./config --prefix=/usr --shared
本次遇到系统时间不对的,修改好系统时间后config正常。
# make
# make test (这一步很重要哦!是进行 SSL 加密协议的完整测试,如果出现错误就要一定先找出哪里的原因,否则一味继续可能导致最终 SSH 不能使用,后果很严重哦!)
# make install
三、升级 OpenSSH
1、下载最新版本 OpenSSH
#  ./configure --prefix=/usr --sysconfdir=/etc/ssh --with-pam --with-zlib=/usr/local/zlib --with-ssl-dir=/usr/bin/openssl --with-md5-passwords 
(注意,如果 configure 时提示 PAM 有错误,那一般是因为系统中没有安装 pam-devel RPM 包,找到安装光盘,安装 pam-devel 就可以解决啦)如果是sshkeygen提示错误,需要make  clean再重新编译
本次安装过程提示了pam问题,cd /opt/cdrom-mirror/Packages
rpm -ivh pam-devel-1.1.1-10.el6_2.1.x86_64.rpm
./configure --prefix=/usr --sysconfdir=/etc/ssh --with-pam --with-zlib=/usr/local/zlib --with-ssl-dir=/usr/bin/openssl --with-md5-passwords 
# make
# make install
ssh -V 

编译RPM包

机器太多,不太可能一台台的编译安装。首先用rpmbuild打包,然后用createrepo制作本地私有仓库。主要是openssl打包比较纠结!OpenSSH完全依赖OpenSSL的,所以OpenSSL的版本一定要先编译安装好,然后再编译OpenSSH。相应的包下面都有对应的spec文件。

实际操作过程是先打包OpenSSH的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 cd
 mkdir rpmbuild
 cd rpmbuild/
 mkdir -pv {BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}

 cd SOURCES/
 cd ../SPECS/
 cp ~/updatessh/openssh-7.2p2/contrib/redhat/openssh.spec ./
 cd ..
 cd SOURCES/
 vi /etc/resolv.conf
 wget http://pkgs.fedoraproject.org/repo/pkgs/openssh/x11-ssh-askpass-1.2.4.1.tar.gz/8f2e41f3f7eaa8543a2440454637f3c3/x11-ssh-askpass-1.2.4.1.tar.gz
 wget http://pkgs.fedoraproject.org/repo/pkgs/openssh/openssh-7.2p2.tar.gz/13009a9156510d8f27e752659075cced/openssh-7.2p2.tar.gz
 cd ..
 yum groupinstall "X Window System" "Desktop" "Desktop Platform" "General Purpose Desktop"
 yum -y install libX11-devel
 yum install imake
 yum provides \*/Intrinsic.h
 yum install libXt-devel
 yum search gtk
 yum install gtk2 gtk2-devel
 vi SPECS/openssh.spec
 rpmbuild -bb SPECS/openssh.spec

然后坑就摆在那里了:重启sshd失败。

1
2
3
4
5
6
7
8
# 在rpmbuild\RPMS\x86_64目录下面创建createrepo私有仓库
# /etc/yum.repo.d/local.repo增加节点

# yum install openssh 

# 重启
[root@localhost ~]# service sshd restart
然后启动了[失败](具体错没记下来)

感觉是OpenSSL的问题了。然后打包好OpenSSL后安装竟然报错:找不到libssl的动态链接库

1
2
3
4
5
6
7
8
9
10
11
12
Error: Package: wget-1.12-1.4.el6.x86_64 (@anaconda-RedHatEnterpriseLinux-201206132210.x86_64/6.3)
           Requires: libcrypto.so.10()(64bit)
           Removing: openssl-1.0.0-20.el6_2.5.x86_64 (@cdrom)
               libcrypto.so.10()(64bit)
           Updated By: openssl-1.0.2j-1.x86_64 (upgrade)
               Not found
Error: Package: 1:wpa_supplicant-0.7.3-3.el6.x86_64 (@cdrom)
           Requires: libssl.so.10()(64bit)
           Removing: openssl-1.0.0-20.el6_2.5.x86_64 (@cdrom)
               libssl.so.10()(64bit)
           Updated By: openssl-1.0.2j-1.x86_64 (upgrade)
               Not found

官方的出的spec打包的rpm安装后竟然会少东西。百思不得其解,通过查看cdrom openssl-1.0.0-20.el6_2.5.x86_64.rpm与rpmbuild openssl-1.0.2j-1.x86_64.rpm的确还不同:

1
2
3
4
5
6
7
8
[root@localhost ~]# rpm -qlp /opt/cdrom/Packages/openssl-1.0.0-20.el6_2.5.x86_64.rpm | grep libssl
...
/usr/lib64/libssl.so.1.0.0
/usr/lib64/libssl.so.10

[root@localhost ~]# rpm -qlp /opt/cdrom/Packages/openssl-1.0.0-20.el6_2.5.x86_64.rpm | grep libssl
...
/usr/lib64/libssl.so.1.0.0

在spec里面增加libssl.so.10软链接也没用。rpm并没有提供libssl.so.10的 provide 服务(可以通过rpm -qip –provides RPM查看)。

实在想不出办法了,只能先看下官网怎么打包的。最后通过查看 openssl-1.0.0-20.el6_2.5.src.rpm 的打包spec是进行定制了的,把原来编译生成的动态链接库so.$(SHLIB_MAJOR).$(SHLIB_MINOR)文件名改成so.$(SHLIB_SONAMEVER)。主要的两个patch为:

  • openssl-1.0.0-beta3-soversion.patch
  • openssl-1.0.0-beta4-redhat.patch

参考修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
修改Makefile
diff -u openssl-1.0.2j/Makefile.org rpmbuild/SOURCES/openssl-1.0.2j/Makefile.org
--- openssl-1.0.2j/Makefile.org 2016-09-26 17:49:07.000000000 +0800
+++ rpmbuild/SOURCES/openssl-1.0.2j/Makefile.org        2016-10-20 15:28:32.000000000 +0800
@@ -10,6 +10,7 @@
 SHLIB_MAJOR=
 SHLIB_MINOR=
 SHLIB_EXT=
+SHLIB_SONAMEVER=10
 PLATFORM=dist
 OPTIONS=
 CONFIGURE_ARGS=
@@ -342,10 +343,9 @@
 link-shared:
        @ set -e; for i in $(SHLIBDIRS); do \
                $(MAKE) -f $(HERE)/Makefile.shared -e $(BUILDENV) \
-                       LIBNAME=$$i LIBVERSION=$(SHLIB_MAJOR).$(SHLIB_MINOR) \
+                       LIBNAME=$$i LIBVERSION=$(SHLIB_SONAMEVER) \
                        LIBCOMPATVERSIONS=";$(SHLIB_VERSION_HISTORY)" \
                        symlink.$(SHLIB_TARGET); \
-               libs="$$libs -l$$i"; \
        done
 
 build-shared: do_$(SHLIB_TARGET) link-shared
@@ -356,7 +356,7 @@
                        libs="$(LIBKRB5) $$libs"; \
                fi; \
                $(CLEARENV) && $(MAKE) -f Makefile.shared -e $(BUILDENV) \
-                       LIBNAME=$$i LIBVERSION=$(SHLIB_MAJOR).$(SHLIB_MINOR) \
+                       LIBNAME=$$i LIBVERSION=$(SHLIB_SONAMEVER) \
                        LIBCOMPATVERSIONS=";$(SHLIB_VERSION_HISTORY)" \
                        LIBDEPS="$$libs $(EX_LIBS)" \
                        link_a.$(SHLIB_TARGET); \

修改Configure1
diff -u openssl-1.0.2j/Configure rpmbuild/SOURCES/openssl-1.0.2j/Configure
--- openssl-1.0.2j/Configure    2016-09-26 17:49:07.000000000 +0800
+++ rpmbuild/SOURCES/openssl-1.0.2j/Configure   2016-10-20 16:40:33.000000000 +0800
@@ -1781,7 +1781,7 @@
        elsif ($shared_extension ne "" && $shared_extension =~ /^\.s([ol])\.[^\.]*\.[^\.]*$/)
                {
                my $sotmp = $1;
-               s/^SHARED_LIBS_LINK_EXTS=.*/SHARED_LIBS_LINK_EXTS=.s$sotmp.\$(SHLIB_MAJOR) .s$sotmp/;
+               s/^SHARED_LIBS_LINK_EXTS=.*/SHARED_LIBS_LINK_EXTS=.s$sotmp.\$(SHLIB_SONAMEVER) .s$sotmp/;
                }
        elsif ($shared_extension ne "" && $shared_extension =~ /^\.[^\.]*\.[^\.]*\.dylib$/)
                {

修改Configure2
rpmbuild/SOURCES/openssl-1.0.2j/Configure 文件中 so.\$(SHLIB_MAJOR).\$(SHLIB_MINOR) 替换成 so.\$(SHLIB_SONAMEVER)

修改spec
[root@localhost ~]# diff openssl-1.0.2j/openssl.spec rpmbuild/SPECS/openssl.spec
110a111,121
> version=%{version}
> soversion=10
> rename so.${soversion} so.${version} $RPM_BUILD_ROOT%{_libdir}/*.so.${soversion}
> for lib in $RPM_BUILD_ROOT/usr/lib64/*.so.${version} ; do
>         chmod 755 ${lib}
>         ln -s -f `basename ${lib}` $RPM_BUILD_ROOT/usr/lib64/`basename ${lib} .${version}`
>         ln -s -f `basename ${lib}` $RPM_BUILD_ROOT/usr/lib64/`basename ${lib} .${version}`.${soversion}
> 
> done
> 

然后打包OpenSSL,用Yum更新OpenSSL;再打包OpenSSH,最后再用Yum安装OpenSSH。

配置

打包好完成后完成大半的任务了,但是重启过程出现了一些问题:

  1. error while loading shared libraries: libcrypto.so.10: cannot enable executable stack as shared object requires: Permission denied

运行: execstack -c libcrypto.so.10 解决。 http://www.linuxquestions.org/questions/linux-kernel-70/longterm-and-grsec-on-slackware-13-0-a-903612/

  1. 重启后远程密码登录上不,但是机器重启是可以登录的,而且su通过密码也是可以切换的。

由于su切换没问题,应该不是加解密的问题。最后经常是selinux的问题: setenforce 0 ; vi /etc/selinux/config 完成配置。

到此纠结的SSH升级告一段落。后面上百台机器通过puppet就可以搞定了。

最后分享一个牛逼到不能再牛逼升级配置的文章: http://www.tsingfun.com/html/2016/env_0330/1332.html 包括了升级过程中你遇到和没遇到的所有问题了。

再记

在测试机上面搞的都是默认的配置啊,安全级别本来就不高。但是到生产就不同了,本来加了防护的。需要特别注意!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#centos6.8
#直接用openssl-1.0.1e-48.el6
[root@localhost yum.repos.d]# mount -o loop CentOS-6.8-x86_64-bin-DVD1.iso /opt/cdrom
[root@localhost yum.repos.d]# vi local.repo

[root@localhost rpmbuild]#  yum groupinstall "X Window System" "Desktop" "Desktop Platform" "General Purpose Desktop"

[root@localhost yum.repos.d]# yum install -y libX11-devel imake libXt-devel gtk2 gtk2-devel 

[root@localhost rpmbuild]# yum install -y rpm-build
[root@localhost rpmbuild]# yum install -y openssl-devel   krb5-devel pam-devel 

[root@localhost rpmbuild]# yum install gcc

[root@localhost rpmbuild]# rpmbuild -bb SPEC/openssh.spec

自己做的repo:

[root@hadoop-master1 ssh]# ll
total 6192
-rw-r--r-- 1 root root  439708 Oct 21 17:53 openssh-7.2p2-1.x86_64.rpm
-rw-r--r-- 1 root root   41752 Oct 21 17:53 openssh-askpass-7.2p2-1.x86_64.rpm
-rw-r--r-- 1 root root   22684 Oct 21 17:53 openssh-askpass-gnome-7.2p2-1.x86_64.rpm
-rw-r--r-- 1 root root  581836 Oct 21 17:53 openssh-clients-7.2p2-1.x86_64.rpm
-rw-r--r-- 1 root root   16948 Oct 21 17:53 openssh-debuginfo-7.2p2-1.x86_64.rpm
-rw-r--r-- 1 root root  391544 Oct 21 17:53 openssh-server-7.2p2-1.x86_64.rpm
-rw-r--r-- 1 root root 3226970 Oct 21 17:23 openssl-1.0.1e-48.el6.src.rpm
-rw-r--r-- 1 root root 1595916 May 12 18:49 openssl-1.0.1e-48.el6.x86_64.rpm
drwxr-xr-x 2 root root    4096 Oct 21 18:47 repodata

注意点:

1 selinux关掉

2 开个telnet以防万一

yum install telnet-server
chkconfig telnet on
service xinetd restart

3 PAM
vi /etc/ssh/sshd_config
UsePAM no(反正要确认pam,或者看看/etc/pam.d/sshd是否满足要求)

–END

SparkSQL查看调试生成代码

网站和一些书籍都有介绍SparkSQL(DataFrame)会根据相应的操作生成最终运行的语句。这里从一个简单的、低级的问题入手到最后通过查看生成的代码查找问题的根源,并简单介绍怎么来调试SparkSQL。

问题来源:

1
2
3
4
5
6
7
8
9
case class Access(id:String,url:String,time:String){
def compute():(String, Int)
}
Object Access {
def apply(row:Row): Option[Access]
}

# main
df.map(Access(_)).filter(!_.isEmpty).map(_.get).map(_.compute)

运行之后 compute 总是报 NullPointerException 异常。按RDD以及Scala的操作都是没法理解的,怎么就变成 Access(null,null,null) 了呢?后面尽管改成 df.flatMap(Access(_)).map(_.compute) 后运行正常了,但是还是想看看SparkSQL到底干了啥!!!

SparkSQL干了什么

Spark RDD是在 RDD#compute 中明确定义好了操作的。而SparkSQL的操作最终转换成了LogicalPlan,看不出它做了什么东东。

其实,与数据库SQL的explain看执行计划类似,SparkSQL也有explain的方法来查看程序的执行计划。(这里代码全部贴出来了,根据情况自己去掉注释啊)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
object AccessAnalyser {

  def main(args: Array[String]): Unit = {

    // conf

    // clean
    new File("target/generated-sources").listFiles().filter(_.isFile()).foreach(_.delete)

    sys.props("org.codehaus.janino.source_debugging.enable") = "true"
    sys.props("org.codehaus.janino.source_debugging.dir") = "target/generated-sources"

    val input = "r:/match10.dat"
    val output = "r:/output"
    def delete(f: File): Unit = {
      if (f.isDirectory) f.listFiles().foreach(delete)
      f.delete()
    }
    delete(new File(output))

    // program

    val conf = new SparkConf().setAppName("DPI Analyser").setMaster("local[10]")
    // fix windows path.
    conf.set(/*SQLConf.WAREHOUSE_PATH*/ "spark.sql.warehouse.dir", "spark-warehouse")

    val sc = new SparkContext(conf)
    val sqlContext = new SQLContext(sc)

    import sqlContext.implicits._
    import org.apache.spark.sql.functions._

    val df = sqlContext.read
      .format("com.databricks.spark.csv")
      .option("header", "false") // Use first line of all files as header
      .option("quote", "'")
      .option("escape", "'")
      .option("delimiter", ",")
      .load(input)

    df
      .flatMap(Access(_))
      //      .map(Access(_)).filter((t: Option[Access]) => !t.isEmpty).map(_.get) // sparksql不合适用Option
      .map(_.compute)
      .explain(true)
      //      .toDF("id", "score")
      //      .groupBy("id").agg(sum("score") as "score")
      //      .sort("score", "id")
      //      .repartition(1)
      //      .write.format("com.databricks.spark.csv").save(output)

    sc.stop()
  }

}

运行上面的代码,在console窗口输出了任务的执行计划:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
== Parsed Logical Plan ==
'SerializeFromObject [staticinvoke(class org.apache.spark.unsafe.types.UTF8String, StringType, fromString, assertnotnull(input[0, scala.Tuple2, true], top level non-flat input object)._1, true) AS _1#20, assertnotnull(input[0, scala.Tuple2, true], top level non-flat input object)._2 AS _2#21]
+- 'MapElements <function1>, obj#19: scala.Tuple2
   +- 'DeserializeToObject unresolveddeserializer(newInstance(class com.github.winse.spark.access.Access)), obj#18: com.github.winse.spark.access.Access
      +- SerializeFromObject [staticinvoke(class org.apache.spark.unsafe.types.UTF8String, StringType, fromString, assertnotnull(input[0, com.github.winse.spark.access.Access, true], top level non-flat input object).id, true) AS id#12, staticinvoke(class org.apache.spark.unsafe.types.UTF8String, StringType, fromString, assertnotnull(input[0, com.github.winse.spark.access.Access, true], top level non-flat input object).url, true) AS url#13, staticinvoke(class org.apache.spark.unsafe.types.UTF8String, StringType, fromString, assertnotnull(input[0, com.github.winse.spark.access.Access, true], top level non-flat input object).time, true) AS time#14]
         +- MapPartitions <function1>, obj#11: com.github.winse.spark.access.Access
            +- DeserializeToObject createexternalrow(_c0#0.toString, _c1#1.toString, _c2#2.toString, StructField(_c0,StringType,true), StructField(_c1,StringType,true), StructField(_c2,StringType,true)), obj#10: org.apache.spark.sql.Row
               +- Relation[_c0#0,_c1#1,_c2#2] csv

== Physical Plan ==
*SerializeFromObject [staticinvoke(class org.apache.spark.unsafe.types.UTF8String, StringType, fromString, assertnotnull(input[0, scala.Tuple2, true], top level non-flat input object)._1, true) AS _1#20, assertnotnull(input[0, scala.Tuple2, true], top level non-flat input object)._2 AS _2#21]
+- *MapElements <function1>, obj#19: scala.Tuple2
   +- MapPartitions <function1>, obj#11: com.github.winse.spark.access.Access
      +- DeserializeToObject createexternalrow(_c0#0.toString, _c1#1.toString, _c2#2.toString, StructField(_c0,StringType,true), StructField(_c1,StringType,true), StructField(_c2,StringType,true)), obj#10: org.apache.spark.sql.Row
         +- *Scan csv [_c0#0,_c1#1,_c2#2] Format: CSV, InputPaths: file:/r:/match10.dat, PushedFilters: [], ReadSchema: struct<_c0:string,_c1:string,_c2:string>

OK,看到执行计划了,那生成的代码长什么样呢?以及怎么调试这些生成的代码呢?

Hack 源码

在进行调试之前,先改一下代码重新编译下catalyst用于调试,并替换maven下面的spark-catalyst_2.11 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
winse@Lenovo-PC ~/git/spark/sql/catalyst
$ git diff .
diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/codegen/CodeGenerator.scala b/sql/catalyst/                                                                                          src/main/scala/org/apache/spark/sql/catalyst/expressions/codegen/CodeGenerator.scala
index 16fb1f6..56bfbf7 100644
--- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/codegen/CodeGenerator.scala
+++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/codegen/CodeGenerator.scala
@@ -854,7 +854,7 @@ object CodeGenerator extends Logging {
     val parentClassLoader = new ParentClassLoader(Utils.getContextOrSparkClassLoader)
     evaluator.setParentClassLoader(parentClassLoader)
     // Cannot be under package codegen, or fail with java.lang.InstantiationException
-    evaluator.setClassName("org.apache.spark.sql.catalyst.expressions.GeneratedClass")
     evaluator.setDefaultImports(Array(
       classOf[Platform].getName,
       classOf[InternalRow].getName,
@@ -875,12 +875,14 @@ object CodeGenerator extends Logging {

     logDebug({
       // Only add extra debugging info to byte code when we are going to print the source code.
-      evaluator.setDebuggingInformation(true, true, false)
+      evaluator.setDebuggingInformation(true, true, true)
       s"\n$formatted"
     })

     try {
-      evaluator.cook("generated.java", code.body)
+      evaluator.cook(code.body)
       recordCompilationStats(evaluator)
     } catch {
       case e: Exception =>

E:\git\spark\sql\catalyst>mvn clean package -DskipTests -Dmaven.test.skip=true

SparkSQL生成代码用的是janino,官网文档有提供debugging的资料:http://janino-compiler.github.io/janino/#debugging 。简单说明下三处修改:

  • 查看org.codehaus.janino.Scanner构造方法,如果配置了debugging以及optionalFileName==null就会把源码保存到临时文件。
  • 一开始没想到要注释掉setClassName的,后面把CodeGenerator#doCompile拷贝出来慢慢和官网提供的例子对,就把setClassName换成setExtendedClass竟然成了弹出了源码页面。又看到下面就setExtendedClass就注释掉setClassName就ok了。
  • 源代码里面的参数不能查看的,就是编译的时刻把这个选项去掉了。把debugVars设置为true。

运行调试

先做好调试准备工作:

  • 在compute方法里面打一个断点然后调试运行
  • 修改log4j日志级别: log4j.logger.org.apache.spark.sql.catalyst.expressions.codegen=DEBUG
  • 把项目导入eclipse(IDEA弹不出源代码)

然后运行。点击Debug视图的GeneratedIterator,在弹出的代码视图点击查找源码按钮,再弹出的添加源代码对话框(Edit Source Lookup Path)添加路径target/generated-sources(注意这里要用绝对路径)!接下来就一步步的调就行了。

调试着生成的代码能更好的理解前面explain的执行计划。看到代码就好理解最开始的Access(null,null,null)了:对象到字段反序列化的问题。

–END

Maven创建自己的Archetype

最近经常用到scala,创建的小工程也挺多的。每次都的复制一些properties和plugins挺繁琐的。准备自己搞一个archetype,以后直接用archetype生成一步到位(相当于一个模板)。

首先创建一个模板工程

把需要修改的属性和插件,以及一些常用到的文件都放置好,如log4j.properties等。

使用命令创建archetype工程

1
mvn clean archetype:create-from-project

注意: maven-archetype-plugin插件需要定位mvn.bat,而我的maven-3.3.9的命令名称为mvn.cmd,需要简单暴力的复制一个。

生成后,在 target\generated-sources\archetype 目录即为创建archetype工程。

清理IDE相关文件

target\generated-sources\archetype\src\main\resources\META-INF\maven 下面的 archetype-metadata.xml 为Archetype的元数据(真正包括那些文件的配置)。可以根据实际情况进行编辑

文件夹 target\generated-sources\archetype\src\main\resources\archetype-resources 下包括所有(新建时)需要拷贝的文件,但同时目录下面也包括了IDE相关文件,可以把这些文件 删掉

本地安装

清理完文件后,回到 target\generated-sources\archetype 执行 mvn install 把这个原型(Archetype)安装到本地。

使用

1
2
3
4
mvn archetype:generate -B \
-DarchetypeGroupId=com.example -DarchetypeArtifactId=scala-simple-archetype -DarchetypeVersion=1.0 \
-DarchetypeCatalog=local \
-DgroupId=com.github.winse -DartifactId=Hello

注意 archetypeCatalog 属性,如果不配置为本地(local/internal )的话要等很久(可以用-X输出调试信息查看操作停在哪)。对于intellij idea可以在 Default Settings - Build,Exection,Deployment - Build Tools - Maven - Runner 加上 VM Options 参数: -DarchetypeCatalog=local

–END